agora inbox for [email protected]
help / color / mirror / Atom feed[PATCH 3/5] Make archiver process an auxiliary process
868+ messages / 15 participants
[nested] [flat]
* [PATCH 3/6] Make archiver process an auxiliary process
@ 2018-11-07 07:53 Kyotaro Horiguchi <[email protected]>
0 siblings, 0 replies; 868+ 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] 868+ 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; 868+ 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] 868+ 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; 868+ 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] 868+ 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; 868+ 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] 868+ 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; 868+ 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] 868+ 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; 868+ 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] 868+ 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; 868+ 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] 868+ 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; 868+ 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] 868+ 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; 868+ 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] 868+ 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; 868+ 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] 868+ 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; 868+ 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] 868+ 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; 868+ 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] 868+ messages in thread
* Re: [PATCH] Incremental sort (was: PoC: Partial sort)
@ 2019-07-04 13:25 James Coleman <[email protected]>
2019-07-08 22:37 ` Re: [PATCH] Incremental sort (was: PoC: Partial sort) Alexander Korotkov <[email protected]>
0 siblings, 1 reply; 868+ messages in thread
From: James Coleman @ 2019-07-04 13:25 UTC (permalink / raw)
To: Tomas Vondra <[email protected]>; +Cc: Rafia Sabih <[email protected]>; Shaun Thomas <[email protected]>; Dmitry Dolgov <[email protected]>; Alexander Korotkov <[email protected]>; Tom Lane <[email protected]>; PostgreSQL Developers <[email protected]>
On Mon, Jun 24, 2019 at 11:10 AM James Coleman <[email protected]> wrote:
> As I see it the two most significant concerning cases right now are:
> 1. Very large batches (in particular where the batch is effectively
> all of the matching rows such that we're really just doing a standard
> sort).
> 2. Many very small batches.
...
> Alternatively I've been thinking about ways to achieve a hybrid
> approach: using single-batch suffix-only sort for large batches and
> multi-batch full sort for very small batches. For example, I could
> imagine maintaining two different tuplesorts (one for full sort and
> one for suffix-only sort) and inserting tuples into the full sorter
> until the minimum batch size, and then (like now) checking every tuple
> to see when we've finished the batch. If we finish the batch by, say,
> 2 * minimum batch size, then we perform the full sort. If we don't
> find a new batch by that point, we'd go back and check to see if the
> tuples we've accumulated so far are actually all the same batch. If
> so, we'd move all of them to the suffix-only sorter and continue
> adding rows until we hit a new batch. If not, we'd perform the full
> sort, but only return tuples out of the full sorter until we encounter
> the current batch, and then we'd move the remainder into the
> suffix-only sorter.
Over the past week or two I've implemented this approach. The attached
patch implements the sort retaining the minimum group size logic
already in the patch, but then after 32 tuples only looks for a
different prefix key group for the next 32 tuples. If it finds a new
prefix key group, then everything happens exactly as before. However
if it doesn't find a new prefix key group in that number of tuples,
then it assumes it's found a large set of tuples in a single prefix
key group. To guarantee this is the case it has to transfer tuples
from the full sorting tuplesort into a presorted prefix tuplesort and
(leaving out a few details here about how it handles the possibility
of multiple prefix key groups already in the sort) continues to fill
up that optimized sort with the large group.
This approach should allow us to, broadly speaking, have our cake and
eat it too with respect to small and large batches of tuples in prefix
key groups. There is of course the cost of switching modes, but this
is much like the cost that bound sort pays to switch into top-n heap
sorting mode; there's an inflection point, and you pay some extra cost
right at it, but it's worth it in the broad case.
Initial testing shows promising performance (akin to the original
patch for small groups and similar to my variant that only ever sorted
by single prefix key groups for large batches)
A couple of thoughts:
- It'd be nice if tuplesort allowed us to pull back out tuples in FIFO
manner without sorting. That'd lower the inflection point cost of
switching modes.
- I haven't adjusted costing yet for this change in approach; I wanted
to take a more holistic look at that after getting this working.
- I don't have strong feelings about the group size inflection points.
More perf testing would be useful, and also it's plausible we should
do more adjusting of those heuristics based on the size of the bound,
if we have one.
- The guc enable_sort current also disabled incremental sort, which
makes sense, but it also means there's not a good way to tweak a plan
using a full sort into a plan that uses incremental sort. That seems
not the greatest, but I'm not sure what the best solution would be.
- I did a lot of comment work in this patch, but comments for changes
to tuplesort.c and elsewhere still need to be cleaned up.
A few style notes:
- I know some of the variable declarations don't line up well; I need
to figure out how to get Vim to do what appears to be the standard
style in PG source.
- All code and comment lines are the right length, I think with the
exception of debug printf statements. I'm not sure if long strings are
an exception.
- Lining up arguments is another thing Vim isn't setup to do, though
maybe someone has some thoughts on a good approach.
I'm planning to look at the formatting utility when I get a chance,
but it'd be nice to have the editor handle the basic setup most of the
time.
Process questions:
- Do I need to explicitly move the patch somehow to the next CF?
- Since I've basically taken over patch ownership, should I move my
name from reviewer to author in the CF app? And can there be two
authors listed there?
James Coleman
Attachments:
[text/x-patch] incremental-sort-29.patch (136.3K, ../../CAAaqYe-7h6tE8+ZGuSkAutGPERN4J2DG4JjWZxUw7Pf=fEPeug@mail.gmail.com/2-incremental-sort-29.patch)
download | inline diff:
commit 12be7f7f997debe4e05e84b69c03ecf7051b1d79
Author: jcoleman <[email protected]>
Date: Fri May 31 14:40:17 2019 +0000
Implement incremental sort
Incremental sort is an optimized variant of multikey sort for cases
when the input is already sorted by a prefix of the sort keys. For
example when a sort by (key1, key2 ... keyN) is requested, and the
input is already sorted by (key1, key2 ... keyM), M < N, we can
divide the input into groups where keys (key1, ... keyM) are equal,
and only sort on the remaining columns.
The implemented algorithm operates in two different modes:
- Fetching a minimum number of tuples without checking prefix key
group membership and sorting on all columns when safe.
- Fetching all tuples for a single prefix key group and sorting on
solely the unsorted columns.
We always begin in the first mode, and employ a heuristic to switch
into the second mode if we believe it's beneficial.
Sorting incrementally can potentially use less memory (and possibly
avoid spilling to disk), avoid fetching and sorting all tuples in the
dataset (particularly useful when a LIMIT clause has been specified),
and begin returning tuples before the entire result set is available.
Small datasets which fit entirely in memory and must be fully realized
and sorted may be slightly slower, which we reflect in the costing
implementation.
The hybrid mode approach allows us to optimize for both very small
groups (where the overhead of a new tuplesort is high) and very large
groups (where we can lower cost by not having to sort on already sorted
columns), albeit at some extra cost while switching between modes.
Co-authored-by: Alexander Korotkov <[email protected]>
diff --git a/doc/src/sgml/config.sgml b/doc/src/sgml/config.sgml
index 84341a30e5..9ba845b53a 100644
--- a/doc/src/sgml/config.sgml
+++ b/doc/src/sgml/config.sgml
@@ -4368,6 +4368,20 @@ ANY <replaceable class="parameter">num_sync</replaceable> ( <replaceable class="
</listitem>
</varlistentry>
+ <varlistentry id="guc-enable-incrementalsort" xreflabel="enable_incrementalsort">
+ <term><varname>enable_incrementalsort</varname> (<type>boolean</type>)
+ <indexterm>
+ <primary><varname>enable_incrementalsort</varname> configuration parameter</primary>
+ </indexterm>
+ </term>
+ <listitem>
+ <para>
+ Enables or disables the query planner's use of incremental sort
+ steps. The default is <literal>on</literal>.
+ </para>
+ </listitem>
+ </varlistentry>
+
<varlistentry id="guc-enable-indexscan" xreflabel="enable_indexscan">
<term><varname>enable_indexscan</varname> (<type>boolean</type>)
<indexterm>
diff --git a/src/backend/commands/explain.c b/src/backend/commands/explain.c
index 92969636b7..f9489d8704 100644
--- a/src/backend/commands/explain.c
+++ b/src/backend/commands/explain.c
@@ -80,6 +80,8 @@ static void show_upper_qual(List *qual, const char *qlabel,
ExplainState *es);
static void show_sort_keys(SortState *sortstate, List *ancestors,
ExplainState *es);
+static void show_incremental_sort_keys(IncrementalSortState *incrsortstate,
+ List *ancestors, ExplainState *es);
static void show_merge_append_keys(MergeAppendState *mstate, List *ancestors,
ExplainState *es);
static void show_agg_keys(AggState *astate, List *ancestors,
@@ -93,7 +95,7 @@ static void show_grouping_set_keys(PlanState *planstate,
static void show_group_keys(GroupState *gstate, List *ancestors,
ExplainState *es);
static void show_sort_group_keys(PlanState *planstate, const char *qlabel,
- int nkeys, AttrNumber *keycols,
+ int nkeys, int nPresortedKeys, AttrNumber *keycols,
Oid *sortOperators, Oid *collations, bool *nullsFirst,
List *ancestors, ExplainState *es);
static void show_sortorder_options(StringInfo buf, Node *sortexpr,
@@ -101,6 +103,8 @@ static void show_sortorder_options(StringInfo buf, Node *sortexpr,
static void show_tablesample(TableSampleClause *tsc, PlanState *planstate,
List *ancestors, ExplainState *es);
static void show_sort_info(SortState *sortstate, ExplainState *es);
+static void show_incremental_sort_info(IncrementalSortState *incrsortstate,
+ ExplainState *es);
static void show_hash_info(HashState *hashstate, ExplainState *es);
static void show_tidbitmap_info(BitmapHeapScanState *planstate,
ExplainState *es);
@@ -1215,6 +1219,9 @@ ExplainNode(PlanState *planstate, List *ancestors,
case T_Sort:
pname = sname = "Sort";
break;
+ case T_IncrementalSort:
+ pname = sname = "Incremental Sort";
+ break;
case T_Group:
pname = sname = "Group";
break;
@@ -1841,6 +1848,12 @@ ExplainNode(PlanState *planstate, List *ancestors,
show_sort_keys(castNode(SortState, planstate), ancestors, es);
show_sort_info(castNode(SortState, planstate), es);
break;
+ case T_IncrementalSort:
+ show_incremental_sort_keys(castNode(IncrementalSortState, planstate),
+ ancestors, es);
+ show_incremental_sort_info(castNode(IncrementalSortState, planstate),
+ es);
+ break;
case T_MergeAppend:
show_merge_append_keys(castNode(MergeAppendState, planstate),
ancestors, es);
@@ -2175,12 +2188,29 @@ show_sort_keys(SortState *sortstate, List *ancestors, ExplainState *es)
Sort *plan = (Sort *) sortstate->ss.ps.plan;
show_sort_group_keys((PlanState *) sortstate, "Sort Key",
- plan->numCols, plan->sortColIdx,
+ plan->numCols, 0, plan->sortColIdx,
plan->sortOperators, plan->collations,
plan->nullsFirst,
ancestors, es);
}
+/*
+ * Show the sort keys for a IncrementalSort node.
+ */
+static void
+show_incremental_sort_keys(IncrementalSortState *incrsortstate,
+ List *ancestors, ExplainState *es)
+{
+ IncrementalSort *plan = (IncrementalSort *) incrsortstate->ss.ps.plan;
+
+ show_sort_group_keys((PlanState *) incrsortstate, "Sort Key",
+ plan->sort.numCols, plan->presortedCols,
+ plan->sort.sortColIdx,
+ plan->sort.sortOperators, plan->sort.collations,
+ plan->sort.nullsFirst,
+ ancestors, es);
+}
+
/*
* Likewise, for a MergeAppend node.
*/
@@ -2191,7 +2221,7 @@ show_merge_append_keys(MergeAppendState *mstate, List *ancestors,
MergeAppend *plan = (MergeAppend *) mstate->ps.plan;
show_sort_group_keys((PlanState *) mstate, "Sort Key",
- plan->numCols, plan->sortColIdx,
+ plan->numCols, 0, plan->sortColIdx,
plan->sortOperators, plan->collations,
plan->nullsFirst,
ancestors, es);
@@ -2215,7 +2245,7 @@ show_agg_keys(AggState *astate, List *ancestors,
show_grouping_sets(outerPlanState(astate), plan, ancestors, es);
else
show_sort_group_keys(outerPlanState(astate), "Group Key",
- plan->numCols, plan->grpColIdx,
+ plan->numCols, 0, plan->grpColIdx,
NULL, NULL, NULL,
ancestors, es);
@@ -2284,7 +2314,7 @@ show_grouping_set_keys(PlanState *planstate,
if (sortnode)
{
show_sort_group_keys(planstate, "Sort Key",
- sortnode->numCols, sortnode->sortColIdx,
+ sortnode->numCols, 0, sortnode->sortColIdx,
sortnode->sortOperators, sortnode->collations,
sortnode->nullsFirst,
ancestors, es);
@@ -2341,7 +2371,7 @@ show_group_keys(GroupState *gstate, List *ancestors,
/* The key columns refer to the tlist of the child plan */
ancestors = lcons(gstate, ancestors);
show_sort_group_keys(outerPlanState(gstate), "Group Key",
- plan->numCols, plan->grpColIdx,
+ plan->numCols, 0, plan->grpColIdx,
NULL, NULL, NULL,
ancestors, es);
ancestors = list_delete_first(ancestors);
@@ -2354,13 +2384,14 @@ show_group_keys(GroupState *gstate, List *ancestors,
*/
static void
show_sort_group_keys(PlanState *planstate, const char *qlabel,
- int nkeys, AttrNumber *keycols,
+ int nkeys, int nPresortedKeys, AttrNumber *keycols,
Oid *sortOperators, Oid *collations, bool *nullsFirst,
List *ancestors, ExplainState *es)
{
Plan *plan = planstate->plan;
List *context;
List *result = NIL;
+ List *resultPresorted = NIL;
StringInfoData sortkeybuf;
bool useprefix;
int keyno;
@@ -2400,9 +2431,13 @@ show_sort_group_keys(PlanState *planstate, const char *qlabel,
nullsFirst[keyno]);
/* Emit one property-list item per sort key */
result = lappend(result, pstrdup(sortkeybuf.data));
+ if (keyno < nPresortedKeys)
+ resultPresorted = lappend(resultPresorted, exprstr);
}
ExplainPropertyList(qlabel, result, es);
+ if (nPresortedKeys > 0)
+ ExplainPropertyList("Presorted Key", resultPresorted, es);
}
/*
@@ -2612,6 +2647,168 @@ show_sort_info(SortState *sortstate, ExplainState *es)
}
}
+/*
+ * If it's EXPLAIN ANALYZE, show tuplesort stats for a incremental sort node
+ */
+static void
+show_incremental_sort_info(IncrementalSortState *incrsortstate,
+ ExplainState *es)
+{
+ if (es->analyze && incrsortstate->sort_Done &&
+ incrsortstate->fullsort_state != NULL)
+ {
+ /* TODO: is it valid to get space used etc. only once given we re-use the sort? */
+ /* TODO: maybe show average, min, max sort group size? */
+
+ Tuplesortstate *fullsort_state = incrsortstate->fullsort_state;
+ TuplesortInstrumentation fullsort_stats;
+ const char *fullsort_sortMethod;
+ const char *fullsort_spaceType;
+ Tuplesortstate *prefixsort_state = incrsortstate->prefixsort_state;
+ TuplesortInstrumentation prefixsort_stats;
+ const char *prefixsort_sortMethod;
+ const char *prefixsort_spaceType;
+
+ tuplesort_get_stats(fullsort_state, &fullsort_stats);
+ fullsort_sortMethod = tuplesort_method_name(fullsort_stats.sortMethod);
+ fullsort_spaceType = tuplesort_space_type_name(fullsort_stats.spaceType);
+ if (prefixsort_state != NULL)
+ {
+ tuplesort_get_stats(prefixsort_state, &prefixsort_stats);
+ prefixsort_sortMethod = tuplesort_method_name(prefixsort_stats.sortMethod);
+ prefixsort_spaceType = tuplesort_space_type_name(prefixsort_stats.spaceType);
+ }
+
+ if (es->format == EXPLAIN_FORMAT_TEXT)
+ {
+ appendStringInfoSpaces(es->str, es->indent * 2);
+ appendStringInfo(es->str, "Sort Method: Full: %s %s: %ldkB",
+ fullsort_sortMethod, fullsort_spaceType,
+ fullsort_stats.spaceUsed);
+ if (prefixsort_state != NULL)
+ appendStringInfo(es->str, ", Prefix-only: %s %s: %ldkB\n",
+ prefixsort_sortMethod, prefixsort_spaceType,
+ prefixsort_stats.spaceUsed);
+ else
+ appendStringInfo(es->str, "\n");
+ appendStringInfoSpaces(es->str, es->indent * 2);
+ appendStringInfo(es->str, "Sort Groups: Full: %ld",
+ incrsortstate->fullsort_group_count);
+ if (prefixsort_state != NULL)
+ appendStringInfo(es->str, ", Prefix-only: %ld\n",
+ incrsortstate->prefixsort_group_count);
+ else
+ appendStringInfo(es->str, "\n");
+ }
+ else
+ {
+ /* TODO */
+ ExplainPropertyText("Full Sort Method", fullsort_sortMethod, es);
+ ExplainPropertyInteger("Full Sort Space Used", "kB",
+ fullsort_stats.spaceUsed, es);
+ ExplainPropertyText("Full Sort Space Type", fullsort_spaceType, es);
+ ExplainPropertyInteger("Full Sort Groups", NULL,
+ incrsortstate->fullsort_group_count, es);
+
+ if (prefixsort_state != NULL)
+ {
+ ExplainPropertyText("Prefix Sort Method", prefixsort_sortMethod, es);
+ ExplainPropertyInteger("Prefix Sort Space Used", "kB",
+ prefixsort_stats.spaceUsed, es);
+ ExplainPropertyText("Prefix Sort Space Type", prefixsort_spaceType, es);
+ ExplainPropertyInteger("Prefix Sort Groups", NULL,
+ incrsortstate->prefixsort_group_count, es);
+ }
+ }
+ }
+
+ if (incrsortstate->shared_info != NULL)
+ {
+ int n;
+ bool opened_group = false;
+
+ for (n = 0; n < incrsortstate->shared_info->num_workers; n++)
+ {
+ IncrementalSortInfo *incsort_info =
+ &incrsortstate->shared_info->sinfo[n];
+ TuplesortInstrumentation *fullsort_instrument;
+ const char *fullsort_sortMethod;
+ const char *fullsort_spaceType;
+ long fullsort_spaceUsed;
+ int64 fullsort_group_count;
+ TuplesortInstrumentation *prefixsort_instrument;
+ const char *prefixsort_sortMethod;
+ const char *prefixsort_spaceType;
+ long prefixsort_spaceUsed;
+ int64 prefixsort_group_count;
+
+ fullsort_instrument = &incsort_info->fullsort_instrument;
+ fullsort_group_count = incsort_info->fullsort_group_count;
+
+ prefixsort_instrument = &incsort_info->prefixsort_instrument;
+ prefixsort_group_count = incsort_info->prefixsort_group_count;
+
+ if (fullsort_instrument->sortMethod == SORT_TYPE_STILL_IN_PROGRESS)
+ continue; /* ignore any unfilled slots */
+
+ fullsort_sortMethod = tuplesort_method_name(
+ fullsort_instrument->sortMethod);
+ fullsort_spaceType = tuplesort_space_type_name(
+ fullsort_instrument->spaceType);
+ fullsort_spaceUsed = fullsort_instrument->spaceUsed;
+
+ if (prefixsort_instrument)
+ {
+ prefixsort_sortMethod = tuplesort_method_name(
+ prefixsort_instrument->sortMethod);
+ prefixsort_spaceType = tuplesort_space_type_name(
+ prefixsort_instrument->spaceType);
+ prefixsort_spaceUsed = prefixsort_instrument->spaceUsed;
+ }
+
+ if (es->format == EXPLAIN_FORMAT_TEXT)
+ {
+ appendStringInfoSpaces(es->str, es->indent * 2);
+ appendStringInfo(es->str,
+ "Worker %d: Full Sort Method: %s %s: %ldkB Groups: %ld",
+ n, fullsort_sortMethod, fullsort_spaceType,
+ fullsort_spaceUsed, fullsort_group_count);
+ if (prefixsort_instrument)
+ appendStringInfo(es->str,
+ ", Prefix Sort Method: %s %s: %ldkB Groups: %ld",
+ prefixsort_sortMethod, prefixsort_spaceType,
+ prefixsort_spaceUsed, prefixsort_group_count);
+ else
+ appendStringInfo(es->str, "\n");
+ }
+ else
+ {
+ if (!opened_group)
+ {
+ ExplainOpenGroup("Workers", "Workers", false, es);
+ opened_group = true;
+ }
+ ExplainOpenGroup("Worker", NULL, true, es);
+ ExplainPropertyInteger("Worker Number", NULL, n, es);
+ ExplainPropertyText("Full Sort Method", fullsort_sortMethod, es);
+ ExplainPropertyInteger("Full Sort Space Used", "kB", fullsort_spaceUsed, es);
+ ExplainPropertyText("Full Sort Space Type", fullsort_spaceType, es);
+ ExplainPropertyInteger("Full Sort Groups", NULL, fullsort_group_count, es);
+ if (prefixsort_instrument)
+ {
+ ExplainPropertyText("Prefix Sort Method", prefixsort_sortMethod, es);
+ ExplainPropertyInteger("Prefix Sort Space Used", "kB", prefixsort_spaceUsed, es);
+ ExplainPropertyText("Prefix Sort Space Type", prefixsort_spaceType, es);
+ ExplainPropertyInteger("Prefix Sort Groups", NULL, prefixsort_group_count, es);
+ }
+ ExplainCloseGroup("Worker", NULL, true, es);
+ }
+ }
+ if (opened_group)
+ ExplainCloseGroup("Workers", "Workers", false, es);
+ }
+}
+
/*
* Show information on hash buckets/batches.
*/
diff --git a/src/backend/executor/Makefile b/src/backend/executor/Makefile
index cc09895fa5..572aca05fb 100644
--- a/src/backend/executor/Makefile
+++ b/src/backend/executor/Makefile
@@ -24,8 +24,8 @@ OBJS = execAmi.o execCurrent.o execExpr.o execExprInterp.o \
nodeLimit.o nodeLockRows.o nodeGatherMerge.o \
nodeMaterial.o nodeMergeAppend.o nodeMergejoin.o nodeModifyTable.o \
nodeNestloop.o nodeProjectSet.o nodeRecursiveunion.o nodeResult.o \
- nodeSamplescan.o nodeSeqscan.o nodeSetOp.o nodeSort.o nodeUnique.o \
- nodeValuesscan.o \
+ nodeSamplescan.o nodeSeqscan.o nodeSetOp.o \
+ nodeSort.o nodeIncrementalSort.o nodeUnique.o nodeValuesscan.o \
nodeCtescan.o nodeNamedtuplestorescan.o nodeWorktablescan.o \
nodeGroup.o nodeSubplan.o nodeSubqueryscan.o nodeTidscan.o \
nodeForeignscan.o nodeWindowAgg.o tstoreReceiver.o tqueue.o spi.o \
diff --git a/src/backend/executor/execAmi.c b/src/backend/executor/execAmi.c
index 1f18e5d3a2..8680e7d911 100644
--- a/src/backend/executor/execAmi.c
+++ b/src/backend/executor/execAmi.c
@@ -31,6 +31,7 @@
#include "executor/nodeGroup.h"
#include "executor/nodeHash.h"
#include "executor/nodeHashjoin.h"
+#include "executor/nodeIncrementalSort.h"
#include "executor/nodeIndexonlyscan.h"
#include "executor/nodeIndexscan.h"
#include "executor/nodeLimit.h"
@@ -254,6 +255,10 @@ ExecReScan(PlanState *node)
ExecReScanSort((SortState *) node);
break;
+ case T_IncrementalSortState:
+ ExecReScanIncrementalSort((IncrementalSortState *) node);
+ break;
+
case T_GroupState:
ExecReScanGroup((GroupState *) node);
break;
@@ -559,8 +564,16 @@ ExecSupportsBackwardScan(Plan *node)
case T_CteScan:
case T_Material:
case T_Sort:
+ /* these don't evaluate tlist */
return true;
+ case T_IncrementalSort:
+ /*
+ * Unlike full sort, incremental sort keeps only a single group
+ * of tuples in memory, so it can't scan backwards.
+ */
+ return false;
+
case T_LockRows:
case T_Limit:
return ExecSupportsBackwardScan(outerPlan(node));
diff --git a/src/backend/executor/execParallel.c b/src/backend/executor/execParallel.c
index 53cd2fc666..bf11a08644 100644
--- a/src/backend/executor/execParallel.c
+++ b/src/backend/executor/execParallel.c
@@ -31,6 +31,7 @@
#include "executor/nodeForeignscan.h"
#include "executor/nodeHash.h"
#include "executor/nodeHashjoin.h"
+#include "executor/nodeIncrementalSort.h"
#include "executor/nodeIndexscan.h"
#include "executor/nodeIndexonlyscan.h"
#include "executor/nodeSeqscan.h"
@@ -280,6 +281,10 @@ ExecParallelEstimate(PlanState *planstate, ExecParallelEstimateContext *e)
/* even when not parallel-aware, for EXPLAIN ANALYZE */
ExecSortEstimate((SortState *) planstate, e->pcxt);
break;
+ case T_IncrementalSortState:
+ /* even when not parallel-aware, for EXPLAIN ANALYZE */
+ ExecIncrementalSortEstimate((IncrementalSortState *) planstate, e->pcxt);
+ break;
default:
break;
@@ -493,6 +498,10 @@ ExecParallelInitializeDSM(PlanState *planstate,
/* even when not parallel-aware, for EXPLAIN ANALYZE */
ExecSortInitializeDSM((SortState *) planstate, d->pcxt);
break;
+ case T_IncrementalSortState:
+ /* even when not parallel-aware, for EXPLAIN ANALYZE */
+ ExecIncrementalSortInitializeDSM((IncrementalSortState *) planstate, d->pcxt);
+ break;
default:
break;
@@ -955,6 +964,7 @@ ExecParallelReInitializeDSM(PlanState *planstate,
break;
case T_HashState:
case T_SortState:
+ case T_IncrementalSortState:
/* these nodes have DSM state, but no reinitialization is required */
break;
@@ -1015,6 +1025,9 @@ ExecParallelRetrieveInstrumentation(PlanState *planstate,
case T_SortState:
ExecSortRetrieveInstrumentation((SortState *) planstate);
break;
+ case T_IncrementalSortState:
+ ExecIncrementalSortRetrieveInstrumentation((IncrementalSortState *) planstate);
+ break;
case T_HashState:
ExecHashRetrieveInstrumentation((HashState *) planstate);
break;
@@ -1301,6 +1314,11 @@ ExecParallelInitializeWorker(PlanState *planstate, ParallelWorkerContext *pwcxt)
/* even when not parallel-aware, for EXPLAIN ANALYZE */
ExecSortInitializeWorker((SortState *) planstate, pwcxt);
break;
+ case T_IncrementalSortState:
+ /* even when not parallel-aware, for EXPLAIN ANALYZE */
+ ExecIncrementalSortInitializeWorker((IncrementalSortState *) planstate,
+ pwcxt);
+ break;
default:
break;
diff --git a/src/backend/executor/execProcnode.c b/src/backend/executor/execProcnode.c
index c227282975..a9dd08fa6f 100644
--- a/src/backend/executor/execProcnode.c
+++ b/src/backend/executor/execProcnode.c
@@ -88,6 +88,7 @@
#include "executor/nodeGroup.h"
#include "executor/nodeHash.h"
#include "executor/nodeHashjoin.h"
+#include "executor/nodeIncrementalSort.h"
#include "executor/nodeIndexonlyscan.h"
#include "executor/nodeIndexscan.h"
#include "executor/nodeLimit.h"
@@ -314,6 +315,11 @@ ExecInitNode(Plan *node, EState *estate, int eflags)
estate, eflags);
break;
+ case T_IncrementalSort:
+ result = (PlanState *) ExecInitIncrementalSort((IncrementalSort *) node,
+ estate, eflags);
+ break;
+
case T_Group:
result = (PlanState *) ExecInitGroup((Group *) node,
estate, eflags);
@@ -694,6 +700,10 @@ ExecEndNode(PlanState *node)
ExecEndSort((SortState *) node);
break;
+ case T_IncrementalSortState:
+ ExecEndIncrementalSort((IncrementalSortState *) node);
+ break;
+
case T_GroupState:
ExecEndGroup((GroupState *) node);
break;
@@ -840,6 +850,29 @@ ExecSetTupleBound(int64 tuples_needed, PlanState *child_node)
sortState->bound = tuples_needed;
}
}
+ else if (IsA(child_node, IncrementalSortState))
+ {
+ /*
+ * If it is a Sort node, notify it that it can use bounded sort.
+ *
+ * Note: it is the responsibility of nodeSort.c to react properly to
+ * changes of these parameters. If we ever redesign this, it'd be a
+ * good idea to integrate this signaling with the parameter-change
+ * mechanism.
+ */
+ IncrementalSortState *sortState = (IncrementalSortState *) child_node;
+
+ if (tuples_needed < 0)
+ {
+ /* make sure flag gets reset if needed upon rescan */
+ sortState->bounded = false;
+ }
+ else
+ {
+ sortState->bounded = true;
+ sortState->bound = tuples_needed;
+ }
+ }
else if (IsA(child_node, AppendState))
{
/*
diff --git a/src/backend/executor/nodeIncrementalSort.c b/src/backend/executor/nodeIncrementalSort.c
new file mode 100644
index 0000000000..c3b903e568
--- /dev/null
+++ b/src/backend/executor/nodeIncrementalSort.c
@@ -0,0 +1,1107 @@
+/*-------------------------------------------------------------------------
+ *
+ * nodeIncremenalSort.c
+ * Routines to handle incremental sorting of relations.
+ *
+ * DESCRIPTION
+ *
+ * Incremental sort is an optimized variant of multikey sort for cases
+ * when the input is already sorted by a prefix of the sort keys. For
+ * example when a sort by (key1, key2 ... keyN) is requested, and the
+ * input is already sorted by (key1, key2 ... keyM), M < N, we can
+ * divide the input into groups where keys (key1, ... keyM) are equal,
+ * and only sort on the remaining columns.
+ *
+ * Consider the following example. We have input tuples consisting of
+ * two integers (X, Y) already presorted by X, while it's required to
+ * sort them by both X and Y. Let input tuples be following.
+ *
+ * (1, 5)
+ * (1, 2)
+ * (2, 9)
+ * (2, 1)
+ * (2, 5)
+ * (3, 3)
+ * (3, 7)
+ *
+ * Incremental sort algorithm would split the input into the following
+ * groups, which have equal X, and then sort them by Y individually:
+ *
+ * (1, 5) (1, 2)
+ * (2, 9) (2, 1) (2, 5)
+ * (3, 3) (3, 7)
+ *
+ * After sorting these groups and putting them altogether, we would get
+ * the following result which is sorted by X and Y, as requested:
+ *
+ * (1, 2)
+ * (1, 5)
+ * (2, 1)
+ * (2, 5)
+ * (2, 9)
+ * (3, 3)
+ * (3, 7)
+ *
+ * Incremental sort may be more efficient than plain sort, particularly
+ * on large datasets, as it reduces the amount of data to sort at once,
+ * making it more likely it fits into work_mem (eliminating the need to
+ * spill to disk). But the main advantage of incremental sort is that
+ * it can start producing rows early, before sorting the whole dataset,
+ * which is a significant benefit especially for queries with LIMIT.
+ *
+ * Portions Copyright (c) 1996-2018, PostgreSQL Global Development Group
+ * Portions Copyright (c) 1994, Regents of the University of California
+ *
+ *
+ * IDENTIFICATION
+ * src/backend/executor/nodeIncremenalSort.c
+ *
+ *-------------------------------------------------------------------------
+ */
+
+#include "postgres.h"
+
+#include "access/htup_details.h"
+#include "executor/execdebug.h"
+#include "executor/nodeIncrementalSort.h"
+#include "miscadmin.h"
+#include "utils/lsyscache.h"
+#include "utils/tuplesort.h"
+
+/*
+ * Prepare information for presorted_keys comparison.
+ */
+static void
+preparePresortedCols(IncrementalSortState *node)
+{
+ IncrementalSort *plannode = (IncrementalSort *) node->ss.ps.plan;
+ int presortedCols,
+ i;
+
+ Assert(IsA(plannode, IncrementalSort));
+ presortedCols = plannode->presortedCols;
+
+ node->presorted_keys = (PresortedKeyData *) palloc(presortedCols *
+ sizeof(PresortedKeyData));
+
+ /* Pre-cache comparison functions for each pre-sorted key. */
+ for (i = 0; i < presortedCols; i++)
+ {
+ Oid equalityOp,
+ equalityFunc;
+ PresortedKeyData *key;
+
+ key = &node->presorted_keys[i];
+ key->attno = plannode->sort.sortColIdx[i];
+
+ equalityOp = get_equality_op_for_ordering_op(
+ plannode->sort.sortOperators[i], NULL);
+ if (!OidIsValid(equalityOp))
+ elog(ERROR, "missing equality operator for ordering operator %u",
+ plannode->sort.sortOperators[i]);
+
+ equalityFunc = get_opcode(equalityOp);
+ if (!OidIsValid(equalityFunc))
+ elog(ERROR, "missing function for operator %u", equalityOp);
+
+ /* Lookup the comparison function */
+ fmgr_info_cxt(equalityFunc, &key->flinfo, CurrentMemoryContext);
+
+ /* We can initialize the callinfo just once and re-use it */
+ key->fcinfo = palloc0(SizeForFunctionCallInfo(2));
+ InitFunctionCallInfoData(*key->fcinfo, &key->flinfo, 2,
+ plannode->sort.collations[i], NULL, NULL);
+ key->fcinfo->args[0].isnull = false;
+ key->fcinfo->args[1].isnull = false;
+ }
+}
+
+/*
+ * Check whether a given tuple belongs to the current sort group.
+ *
+ * We do this by comparing its first 'presortedCols' column values to
+ * the pivot tuple of the current group.
+ *
+ */
+static bool
+isCurrentGroup(IncrementalSortState *node, TupleTableSlot *pivot, TupleTableSlot *tuple)
+{
+ int presortedCols, i;
+
+ Assert(IsA(node->ss.ps.plan, IncrementalSort));
+
+ presortedCols = ((IncrementalSort *) node->ss.ps.plan)->presortedCols;
+
+ /*
+ * That the input is sorted by keys * (0, ... n) implies that the tail keys
+ * are more likely to change. Therefore we do our comparison starting from
+ * the last pre-sorted column to optimize for early detection of
+ * inequality and minimizing the number of function calls..
+ */
+ for (i = presortedCols - 1; i >= 0; i--)
+ {
+ Datum datumA,
+ datumB,
+ result;
+ bool isnullA,
+ isnullB;
+ AttrNumber attno = node->presorted_keys[i].attno;
+ PresortedKeyData *key;
+
+ datumA = slot_getattr(pivot, attno, &isnullA);
+ datumB = slot_getattr(tuple, attno, &isnullB);
+
+ /* Special case for NULL-vs-NULL, else use standard comparison */
+ if (isnullA || isnullB)
+ {
+ if (isnullA == isnullB)
+ continue;
+ else
+ return false;
+ }
+
+ key = &node->presorted_keys[i];
+
+ key->fcinfo->args[0].value = datumA;
+ key->fcinfo->args[1].value = datumB;
+
+ /* just for paranoia's sake, we reset isnull each time */
+ key->fcinfo->isnull = false;
+
+ result = FunctionCallInvoke(key->fcinfo);
+
+ /* Check for null result, since caller is clearly not expecting one */
+ if (key->fcinfo->isnull)
+ elog(ERROR, "function %u returned NULL", key->flinfo.fn_oid);
+
+ if (!DatumGetBool(result))
+ return false;
+ }
+ return true;
+}
+
+/*
+ * Switch to presorted prefix mode.
+ *
+ * When we determine that we've likely encountered a large batch of tuples all
+ * having the same presorted prefix values, we want to optimize tuplesort by
+ * only sorting on unsorted suffix keys.
+ *
+ * The problem is that we've already accumulated several tuples in another
+ * tuplesort configured to sort by all columns (assuming that there may be
+ * more than one prefix key group). So to switch to presorted prefix mode we
+ * have to go back an look at all the tuples we've already accumulated and
+ * verify they're all part of the same prefix key group before sorting them
+ * solely by unsorted suffix keys.
+ *
+ * While it's likely that all already fetch tuples are all part of a single
+ * prefix group, we also have to handle the possibility that there is at least
+ * one different prefix key group before the large prefix key group.
+ */
+static void
+switchToPresortedPrefixMode(IncrementalSortState *node)
+{
+ ScanDirection dir;
+ int64 nTuples = 0;
+ bool lastTuple = false;
+ bool firstTuple = true;
+ TupleDesc tupDesc;
+ PlanState *outerNode;
+ IncrementalSort *plannode = (IncrementalSort *) node->ss.ps.plan;
+
+ dir = node->ss.ps.state->es_direction;
+ outerNode = outerPlanState(node);
+ tupDesc = ExecGetResultType(outerNode);
+
+ if (node->prefixsort_state == NULL)
+ {
+ Tuplesortstate *prefixsort_state;
+ int presortedCols = plannode->presortedCols;
+
+ /*
+ * Optimize the sort by assuming the prefix columns are all equal
+ * and thus we only need to sort by any remaining columns.
+ */
+ prefixsort_state = tuplesort_begin_heap(
+ tupDesc,
+ plannode->sort.numCols - presortedCols,
+ &(plannode->sort.sortColIdx[presortedCols]),
+ &(plannode->sort.sortOperators[presortedCols]),
+ &(plannode->sort.collations[presortedCols]),
+ &(plannode->sort.nullsFirst[presortedCols]),
+ work_mem,
+ NULL,
+ false);
+ node->prefixsort_state = prefixsort_state;
+ }
+ else
+ {
+ /* Next group of presorted data */
+ tuplesort_reset(node->prefixsort_state);
+ }
+
+ /*
+ * If the current node has a bound, then it's reasonably likely that a
+ * large prefix key group will benefit from bounded sort, so configure
+ * the tuplesort to allow for that optimization.
+ */
+ if (node->bounded)
+ {
+ SO1_printf("Setting bound on presorted prefix tuplesort to: %ld\n",
+ node->bound - node->bound_Done);
+ tuplesort_set_bound(node->prefixsort_state,
+ node->bound - node->bound_Done);
+ }
+
+ for (;;)
+ {
+ lastTuple = node->n_fullsort_remaining - nTuples == 1;
+
+ /*
+ * When we encounter multiple prefix key groups inside the full sort
+ * tuplesort we have to carry over the last read tuple into the next
+ * batch.
+ */
+ if (firstTuple && !TupIsNull(node->transfer_tuple))
+ {
+ tuplesort_puttupleslot(node->prefixsort_state, node->transfer_tuple);
+ nTuples++;
+
+ /* The carried over tuple is our new group pivot tuple. */
+ ExecCopySlot(node->group_pivot, node->transfer_tuple);
+ }
+ else
+ {
+ tuplesort_gettupleslot(node->fullsort_state,
+ ScanDirectionIsForward(dir),
+ false, node->transfer_tuple, NULL);
+
+ /*
+ * If this is our first time through the loop, then we need to save the
+ * first tuple we get as our new group pivot.
+ */
+ if (TupIsNull(node->group_pivot))
+ ExecCopySlot(node->group_pivot, node->transfer_tuple);
+
+ if (isCurrentGroup(node, node->group_pivot, node->transfer_tuple))
+ {
+ tuplesort_puttupleslot(node->prefixsort_state, node->transfer_tuple);
+ nTuples++;
+ }
+ else
+ {
+ /* The tuple isn't part of the current batch so we need to carry
+ * it over into the next set up tuples we transfer out of the full
+ * sort tuplesort into the presorted prefix tuplesort. We don't
+ * actually have to do anything special to save the tuple since
+ * we've already loaded it into the node->transfer_tuple slot, and,
+ * even though that slot points to memory inside the full sort
+ * tuplesort, we can't reset that tuplesort anyway until we've
+ * fully transferred out of its tuples, so this reference is safe.
+ * We do need to reset the group pivot tuple though since we've
+ * finished the current prefix key group.
+ */
+ ExecClearTuple(node->group_pivot);
+ break;
+ }
+ }
+
+ firstTuple = false;
+
+ if (lastTuple)
+ /*
+ * We retain the current group pivot tuple since we haven't yet
+ * found the end of the current prefix key group.
+ */
+ break;
+ }
+
+ /*
+ * Track how many tuples remain in the full sort batch so that we know if
+ * we need to sort multiple prefix key groups before processing tuples
+ * remaining in the large single prefix key group we think we've
+ * encountered.
+ */
+ SO1_printf("Moving %ld tuples to presorted prefix tuplesort\n", nTuples);
+ node->n_fullsort_remaining -= nTuples;
+ SO1_printf("Setting n_fullsort_remaining to %ld\n", node->n_fullsort_remaining);
+
+ if (lastTuple)
+ {
+ /*
+ * We've confirmed that all tuples remaining in the full sort batch
+ * is in the same prefix key group and moved all of those tuples into
+ * the presorted prefix tuplesort. Now we can save our pivot comparison
+ * tuple and continue fetching tuples from the outer execution node to
+ * load into the presorted prefix tuplesort.
+ */
+ ExecCopySlot(node->group_pivot, node->transfer_tuple);
+ SO_printf("Setting execution_status to INCSORT_LOADPREFIXSORT (switchToPresortedPrefixMode)\n");
+ node->execution_status = INCSORT_LOADPREFIXSORT;
+
+ /* Make sure we clear the transfer tuple slot so that next time we
+ * encounter a large prefix key group we don't incorrectly assume
+ * we have a tuple carried over from the previous group.
+ */
+ ExecClearTuple(node->transfer_tuple);
+ }
+ else
+ {
+ /*
+ * We finished a group but didn't consume all of the tuples from the
+ * full sort batch sorter, so we'll sort this batch, let the inner node
+ * read out all of those tuples, and then come back around to find
+ * another batch.
+ */
+ SO1_printf("Sorting presorted prefix tuplesort with %ld tuples\n", nTuples);
+ tuplesort_performsort(node->prefixsort_state);
+ node->prefixsort_group_count++;
+
+ if (node->bounded)
+ {
+ /*
+ * If the current node has a bound, and we've already sorted n
+ * tuples, then the functional bound remaining is
+ * (original bound - n), so store the current number of processed
+ * tuples for use in configuring sorting bound.
+ */
+ SO2_printf("Changing bound_Done from %ld to %ld\n",
+ Min(node->bound, node->bound_Done + nTuples), node->bound_Done);
+ node->bound_Done = Min(node->bound, node->bound_Done + nTuples);
+ }
+
+ SO_printf("Setting execution_status to INCSORT_READPREFIXSORT (switchToPresortedPrefixMode)\n");
+ node->execution_status = INCSORT_READPREFIXSORT;
+ }
+}
+
+/*
+ * Sorting many small groups with tuplesort is inefficient. In order to
+ * cope with this problem we don't start a new group until the current one
+ * contains at least DEFAULT_MIN_GROUP_SIZE tuples (unfortunately this also
+ * means we can't assume small groups of tuples all have the same prefix keys.)
+ * When we have a bound that's less than DEFAULT_MIN_GROUP_SIZE we start looking
+ * for the new group as soon as we've met our bound to avoid fetching more
+ * tuples than we absolutely have to fetch.
+ */
+#define DEFAULT_MIN_GROUP_SIZE 32
+
+/*
+ * While we've optimized for small prefix key groups by not starting our prefix
+ * key comparisons until we've reached a minimum number of tuples, we don't want
+ * that optimization to cause us to lose out on the benefits of being able to
+ * assume a large group of tuples is fully presorted by its prefix keys.
+ * Therefore we use the DEFAULT_MAX_FULL_SORT_GROUP_SIZE cutoff as a heuristic
+ * for determining when we believe we've encountered a large group, and, if we
+ * get to that point without finding a new prefix key group we transition to
+ * presorted prefix key mode.
+ */
+#define DEFAULT_MAX_FULL_SORT_GROUP_SIZE (2 * DEFAULT_MIN_GROUP_SIZE)
+
+/* ----------------------------------------------------------------
+ * ExecIncrementalSort
+ *
+ * Assuming that outer subtree returns tuple presorted by some prefix
+ * of target sort columns, performs incremental sort. The implemented
+ * algorithm operates in two different modes:
+ * - Fetching a minimum number of tuples without checking prefix key
+ * group membership and sorting on all columns when safe.
+ * - Fetching all tuples for a single prefix key group and sorting on
+ * solely the unsorted columns.
+ * We always begin in the first mode, and employ a heuristic to switch
+ * into the second mode if we believe it's beneficial.
+ *
+ * Sorting incrementally can potentially use less memory, avoid fetching
+ * and sorting all tuples in the the dataset, and begin returning tuples
+ * before the entire result set is available.
+ *
+ * The hybrid mode approach allows us to optimize for both very small
+ * groups (where the overhead of a new tuplesort is high) and very large
+ * groups (where we can lower cost by not having to sort on already sorted
+ * columns), albeit at some extra cost while switching between modes.
+ *
+ * Conditions:
+ * -- none.
+ *
+ * Initial States:
+ * -- the outer child is prepared to return the first tuple.
+ * ----------------------------------------------------------------
+ */
+static TupleTableSlot *
+ExecIncrementalSort(PlanState *pstate)
+{
+ IncrementalSortState *node = castNode(IncrementalSortState, pstate);
+ EState *estate;
+ ScanDirection dir;
+ Tuplesortstate *read_sortstate;
+ Tuplesortstate *fullsort_state;
+ TupleTableSlot *slot;
+ IncrementalSort *plannode = (IncrementalSort *) node->ss.ps.plan;
+ PlanState *outerNode;
+ TupleDesc tupDesc;
+ int64 nTuples = 0;
+ int64 minGroupSize;
+
+ CHECK_FOR_INTERRUPTS();
+
+ estate = node->ss.ps.state;
+ dir = estate->es_direction;
+ fullsort_state = node->fullsort_state;
+
+ if (node->execution_status == INCSORT_READFULLSORT
+ || node->execution_status == INCSORT_READPREFIXSORT)
+ {
+ /*
+ * Return next tuple from the current sorted group set if available.
+ */
+ read_sortstate = node->execution_status == INCSORT_READFULLSORT ?
+ fullsort_state : node->prefixsort_state;
+ slot = node->ss.ps.ps_ResultTupleSlot;
+ if (tuplesort_gettupleslot(read_sortstate, ScanDirectionIsForward(dir),
+ false, slot, NULL) || node->finished)
+ /*
+ * TODO: there isn't a good test case for the node->finished
+ * case directly, but lots of other stuff fails if it's not
+ * there. If the outer node will fail when trying to fetch
+ * too many tuples, then things break if that test isn't here.
+ */
+ return slot;
+ else if (node->n_fullsort_remaining > 0)
+ {
+ /*
+ * When we transition to presorted prefix mode, we might have
+ * accumulated at least one additional prefix key group in the full
+ * sort tuplesort. The first call to switchToPresortedPrefixMode()
+ * pulled the one of those groups out, and we've returned those
+ * tuples to the inner node, but if we tuples remaining in that
+ * tuplesort (i.e., n_fullsort_remaining > 0) at this point we
+ * need to do that again.
+ */
+ SO1_printf("Re-calling switchToPresortedPrefixMode() because n_fullsort_remaining is > 0 (%ld)\n",
+ node->n_fullsort_remaining);
+ switchToPresortedPrefixMode(node);
+ }
+ else
+ {
+ /*
+ * If we don't have any already sorted tuples to read, and we're not
+ * in the middle of transitioning into presorted prefix sort mode,
+ * then it's time to start the process all over again by building
+ * new full sort group.
+ */
+ SO_printf("Setting execution_status to INCSORT_LOADFULLSORT (n_fullsort_remaining > 0)\n");
+ node->execution_status = INCSORT_LOADFULLSORT;
+ }
+ }
+
+ /*
+ * Want to scan subplan in the forward direction while creating the
+ * sorted data.
+ */
+ estate->es_direction = ForwardScanDirection;
+
+ outerNode = outerPlanState(node);
+ tupDesc = ExecGetResultType(outerNode);
+
+ if (node->execution_status == INCSORT_LOADFULLSORT)
+ {
+ /*
+ * Initialize tuplesort module (only needed before the first group).
+ */
+ if (fullsort_state == NULL)
+ {
+ /*
+ * Initialize presorted column support structures for
+ * isCurrentGroup().
+ */
+ preparePresortedCols(node);
+
+ /*
+ * Since we optimize small prefix key groups by accumulating a
+ * minimum number of tuples before sorting, we can't assume that a
+ * group of tuples all have the same prefix key values. Hence we
+ * setup the full sort tuplesort to sort by all requested sort
+ * columns.
+ */
+ fullsort_state = tuplesort_begin_heap(
+ tupDesc,
+ plannode->sort.numCols,
+ plannode->sort.sortColIdx,
+ plannode->sort.sortOperators,
+ plannode->sort.collations,
+ plannode->sort.nullsFirst,
+ work_mem,
+ NULL,
+ false);
+ node->fullsort_state = fullsort_state;
+ }
+ else
+ {
+ /* Reset sort for a new prefix key group. */
+ tuplesort_reset(fullsort_state);
+ }
+
+ /*
+ * Calculate the remaining tuples left if the bounded and configure
+ * both bounded sort and the minimum group size accordingly.
+ */
+ if (node->bounded)
+ {
+ int64 currentBound = node->bound - node->bound_Done;
+
+ /*
+ * Bounded sort isn't likely to be a useful optimization for full
+ * sort mode since we limit full sort mode to a relatively small
+ * number of tuples and tuplesort doesn't switch over to top-n heap
+ * sort anyway unless it hits (2 * bound) tuples.
+ */
+ if (currentBound < DEFAULT_MIN_GROUP_SIZE)
+ tuplesort_set_bound(fullsort_state, currentBound);
+
+ minGroupSize = Min(DEFAULT_MIN_GROUP_SIZE, currentBound);
+ }
+ else
+ minGroupSize = DEFAULT_MIN_GROUP_SIZE;
+
+ /* Because we have to read the next tuple to find out that we've
+ * encountered a new prefix key group on subsequent groups we have to
+ * carry over that extra tuple and add it to the new group's sort here.
+ */
+ if (!TupIsNull(node->group_pivot))
+ {
+ tuplesort_puttupleslot(fullsort_state, node->group_pivot);
+ nTuples++;
+
+ /*
+ * We're in full sort mode accumulating a minimum number of tuples
+ * and not checking for prefix key equality yet, so we can't assume
+ * the group pivot tuple will reamin the same -- unless we're using
+ * a minimum group size of 1, in which case the pivot is obviously
+ * still the pviot.
+ */
+ if (nTuples != minGroupSize)
+ ExecClearTuple(node->group_pivot);
+ }
+
+ for (;;)
+ {
+ /*
+ * TODO: do we need to check for interrupts inside these loops or
+ * will the outer node handle that?
+ */
+
+ slot = ExecProcNode(outerNode);
+
+ /*
+ * When the outer node can't provide us anymore tuples, then we
+ * can sort the current group and return those tuples.
+ */
+ if (TupIsNull(slot))
+ {
+ node->finished = true;
+
+ SO1_printf("Sorting fullsort with %ld tuples\n", nTuples);
+ tuplesort_performsort(fullsort_state);
+ node->fullsort_group_count++;
+
+ SO_printf("Setting execution_status to INCSORT_READFULLSORT (final tuple) \n");
+ node->execution_status = INCSORT_READFULLSORT;
+ break;
+ }
+
+ /* Accumulate the next group of presorted tuples. */
+ if (nTuples < minGroupSize)
+ {
+ /*
+ * If we have yet hit our target minimum group size, then don't
+ * both with checking for inclusion in the current prefix group
+ * since a large number of very tiny sorts is inefficient.
+ */
+ tuplesort_puttupleslot(fullsort_state, slot);
+ nTuples++;
+
+ /* Keep the last tuple of our minimal group as a pivot. */
+ if (nTuples == minGroupSize)
+ ExecCopySlot(node->group_pivot, slot);
+ }
+ else
+ {
+ /*
+ * Once we've accumulated a minimum number of tuples, we start
+ * checking for a new prefix key group. Only after we find
+ * changed prefix keys can we guarantee sort stability of the
+ * tuples we've already accumulated.
+ */
+ if (isCurrentGroup(node, node->group_pivot, slot))
+ {
+ /*
+ * As long as the prefix keys match the pivot tuple then
+ * load the tuple into the tuplesort.
+ */
+ tuplesort_puttupleslot(fullsort_state, slot);
+ nTuples++;
+ }
+ else
+ {
+ /*
+ * Since the tuple we fetched isn't part of the current
+ * prefix key group we can't sort it as part of this
+ * sort group. Instead we need to carry it over to the
+ * next group. We use the group_pivot slot as a temp
+ * container for that purpose even though we won't actually
+ * treat it as a group pivot.
+ */
+ ExecCopySlot(node->group_pivot, slot);
+
+ if (node->bounded)
+ {
+ /*
+ * If the current node has a bound, and we've already
+ * sorted n tuples, then the functional bound remaining
+ * is (original bound - n), so store the current number
+ * of processed tuples for use in configuring sorting
+ * bound.
+ */
+ SO2_printf("Changing bound_Done from %ld to %ld\n",
+ Min(node->bound, node->bound_Done + nTuples), node->bound_Done);
+ node->bound_Done = Min(node->bound, node->bound_Done + nTuples);
+ }
+
+ /*
+ * Once we find changed prefix keys we can complete the
+ * sort and begin reading out the sorted tuples.
+ */
+ SO1_printf("Sorting fullsort tuplesort with %ld tuples\n", nTuples);
+ tuplesort_performsort(fullsort_state);
+ node->fullsort_group_count++;
+ SO_printf("Setting execution_status to INCSORT_READFULLSORT (found end of group)\n");
+ node->execution_status = INCSORT_READFULLSORT;
+ break;
+ }
+ }
+
+ /*
+ * Once we've processed DEFAULT_MAX_FULL_SORT_GROUP_SIZE tuples
+ * then we make the assumption that it's likely that we've found
+ * a large group of tuples having a single prefix key (as long
+ * as the last tuple didn't shift us into reading from the full
+ * sort mode tuplesort).
+ */
+ if (nTuples > DEFAULT_MAX_FULL_SORT_GROUP_SIZE &&
+ node->execution_status != INCSORT_READFULLSORT)
+ {
+ /*
+ * The group pivot we have stored has already been put into the
+ * tuplesort; we don't want to carry it over.
+ */
+ ExecClearTuple(node->group_pivot);
+
+ /*
+ * Unfortunately the tuplesort API doesn't include a way to
+ * retrieve tuples unless a sort has been performed, so we
+ * perform the sort even though we could just as easily rely
+ * on FIFO retrieval semantics when transferring them to the
+ * presorted prefix tuplesort.
+ */
+ SO1_printf("Sorting fullsort tuplesort with %ld tuples\n", nTuples);
+ tuplesort_performsort(fullsort_state);
+ node->fullsort_group_count++;
+
+ /*
+ * If the full sort tuplesort happened to switch into top-n heapsort mode
+ * then we will only be able to retrieve currentBound tuples (since the
+ * tuplesort will have only retained the top-n tuples). This is safe even
+ * though we haven't yet completed fetching the current prefix key group
+ * because the tuples we've "lost" already sorted "below" the retained ones,
+ * and we're already contractually guaranteed to not need any more than the
+ * currentBount tuples.
+ */
+ if (tuplesort_used_bound(node->fullsort_state))
+ {
+ int64 currentBound = node->bound - node->bound_Done;
+ SO2_printf("Read %ld tuples, but setting to %ld because we used bounded sort\n",
+ nTuples, Min(currentBound, nTuples));
+ nTuples = Min(currentBound, nTuples);
+ }
+
+ SO1_printf("Setting n_fullsort_remaining to %ld and calling switchToPresortedPrefixMode()\n",
+ nTuples);
+
+ /*
+ * Track the number of tuples we need to move from the fullsort
+ * to presorted prefix sort (we might have multiple prefix key
+ * groups, so we need a way to see if we've actually finished).
+ */
+ node->n_fullsort_remaining = nTuples;
+
+ /* Transition the tuples to the presorted prefix tuplesort. */
+ switchToPresortedPrefixMode(node);
+
+ /*
+ * Since we know we had tuples to move to the presorted prefix
+ * tuplesort, we know that unless that transition has verified
+ * that all tuples belonged to the same prefix key group (in
+ * which case we can go straight to continuing to load tuples
+ * into that tuplesort), we should have a tuple to return here.
+ *
+ * Either way, the appropriate execution status should have
+ * been set by switchToPresortedPrefixMode(), so we can drop out
+ * of the loop here and let the appropriate path kick in.
+ */
+ break;
+ }
+ }
+ }
+
+ if (node->execution_status == INCSORT_LOADPREFIXSORT)
+ {
+ /*
+ * Since we only enter this state after determining that all remaining
+ * tuples in the full sort tuplesort have the same prefix, we've already
+ * established a current group pivot tuple (but wasn't carried over;
+ * it's already been put into the prefix sort tuplesort).
+ */
+ Assert(!TupIsNull(node->group_pivot));
+
+ for (;;)
+ {
+ slot = ExecProcNode(outerNode);
+
+ /* Check to see if there are no more tuples to fetch. */
+ if (TupIsNull(slot))
+ {
+ node->finished = true;
+ break;
+ }
+
+ if (isCurrentGroup(node, node->group_pivot, slot))
+ {
+ /*
+ * Fetch tuples and put them into the presorted prefix tuplesort
+ * until we find changed prefix keys. Only then can we guarantee
+ * sort stability of the tuples we've already accumulated.
+ */
+ tuplesort_puttupleslot(node->prefixsort_state, slot);
+ nTuples++;
+ }
+ else
+ {
+ /*
+ * Since the tuple we fetched isn't part of the current prefix
+ * key group we can't sort it as part of this sort group.
+ * Instead we need to carry it over to the next group. We use
+ * the group_pivot slot as a temp container for that purpose
+ * even though we won't actually treat it as a group pivot.
+ */
+ ExecCopySlot(node->group_pivot, slot);
+ break;
+ }
+ }
+
+ /* Perform the sort and return the tuples to the inner plan nodes. */
+ SO1_printf("Sorting presorted prefix tuplesort with >= %ld tuples\n", nTuples);
+ tuplesort_performsort(node->prefixsort_state);
+ node->prefixsort_group_count++;
+ SO_printf("Setting execution_status to INCSORT_READPREFIXSORT (found end of group)\n");
+ node->execution_status = INCSORT_READPREFIXSORT;
+
+ if (node->bounded)
+ {
+ /*
+ * If the current node has a bound, and we've already sorted n
+ * tuples, then the functional bound remaining is
+ * (original bound - n), so store the current number of processed
+ * tuples for use in configuring sorting bound.
+ */
+ SO2_printf("Changing bound_Done from %ld to %ld\n",
+ Min(node->bound, node->bound_Done + nTuples), node->bound_Done);
+ node->bound_Done = Min(node->bound, node->bound_Done + nTuples);
+ }
+ }
+
+ /* Restore to user specified direction. */
+ estate->es_direction = dir;
+
+ /*
+ * Remember that we've begun our scan and sort so we know how to handle
+ * rescan.
+ */
+ node->sort_Done = true;
+
+ /* Record shared stats if we're a parallel worker. */
+ if (node->shared_info && node->am_worker)
+ {
+ IncrementalSortInfo *incsort_info =
+ &node->shared_info->sinfo[ParallelWorkerNumber];
+
+ Assert(IsParallelWorker());
+ Assert(ParallelWorkerNumber <= node->shared_info->num_workers);
+
+ tuplesort_get_stats(fullsort_state, &incsort_info->fullsort_instrument);
+ incsort_info->fullsort_group_count = node->fullsort_group_count;
+
+ if (node->prefixsort_state)
+ {
+ tuplesort_get_stats(node->prefixsort_state,
+ &incsort_info->prefixsort_instrument);
+ incsort_info->prefixsort_group_count = node->prefixsort_group_count;
+ }
+ }
+
+ /*
+ * Get the first or next tuple from tuplesort. Returns NULL if no more
+ * tuples.
+ */
+ read_sortstate = node->execution_status == INCSORT_READFULLSORT ?
+ fullsort_state : node->prefixsort_state;
+ slot = node->ss.ps.ps_ResultTupleSlot;
+ (void) tuplesort_gettupleslot(read_sortstate, ScanDirectionIsForward(dir),
+ false, slot, NULL);
+ return slot;
+}
+
+/* ----------------------------------------------------------------
+ * ExecInitIncrementalSort
+ *
+ * Creates the run-time state information for the sort node
+ * produced by the planner and initializes its outer subtree.
+ * ----------------------------------------------------------------
+ */
+IncrementalSortState *
+ExecInitIncrementalSort(IncrementalSort *node, EState *estate, int eflags)
+{
+ IncrementalSortState *incrsortstate;
+
+ SO_printf("ExecInitIncrementalSort: initializing sort node\n");
+
+ /*
+ * Incremental sort can't be used with either EXEC_FLAG_REWIND,
+ * EXEC_FLAG_BACKWARD or EXEC_FLAG_MARK, because we hold only current
+ * bucket in tuplesortstate.
+ */
+ Assert((eflags & (EXEC_FLAG_REWIND |
+ EXEC_FLAG_BACKWARD |
+ EXEC_FLAG_MARK)) == 0);
+
+ /*
+ * create state structure
+ */
+ incrsortstate = makeNode(IncrementalSortState);
+ incrsortstate->ss.ps.plan = (Plan *) node;
+ incrsortstate->ss.ps.state = estate;
+ incrsortstate->ss.ps.ExecProcNode = ExecIncrementalSort;
+
+ incrsortstate->bounded = false;
+ incrsortstate->sort_Done = false;
+ incrsortstate->finished = false;
+ incrsortstate->fullsort_state = NULL;
+ incrsortstate->prefixsort_state = NULL;
+ incrsortstate->group_pivot = NULL;
+ incrsortstate->transfer_tuple = NULL;
+ incrsortstate->n_fullsort_remaining = 0;
+ incrsortstate->bound_Done = 0;
+ incrsortstate->fullsort_group_count = 0;
+ incrsortstate->prefixsort_group_count = 0;
+ incrsortstate->presorted_keys = NULL;
+
+ /*
+ * Miscellaneous initialization
+ *
+ * Sort nodes don't initialize their ExprContexts because they never call
+ * ExecQual or ExecProject.
+ */
+
+ /*
+ * initialize child nodes
+ *
+ * We shield the child node from the need to support REWIND, BACKWARD, or
+ * MARK/RESTORE.
+ */
+ eflags &= ~(EXEC_FLAG_REWIND | EXEC_FLAG_BACKWARD | EXEC_FLAG_MARK);
+
+ outerPlanState(incrsortstate) = ExecInitNode(outerPlan(node), estate, eflags);
+
+ /*
+ * Initialize scan slot and type.
+ */
+ ExecCreateScanSlotFromOuterPlan(estate, &incrsortstate->ss, &TTSOpsMinimalTuple);
+
+ /*
+ * Initialize return slot and type. No need to initialize projection info because
+ * this node doesn't do projections.
+ */
+ ExecInitResultTupleSlotTL(&incrsortstate->ss.ps, &TTSOpsMinimalTuple);
+ incrsortstate->ss.ps.ps_ProjInfo = NULL;
+
+ /* make standalone slot to store previous tuple from outer node */
+ incrsortstate->group_pivot = MakeSingleTupleTableSlot(
+ ExecGetResultType(outerPlanState(incrsortstate)), &TTSOpsMinimalTuple);
+ incrsortstate->transfer_tuple = MakeSingleTupleTableSlot(
+ ExecGetResultType(outerPlanState(incrsortstate)), &TTSOpsMinimalTuple);
+
+ SO_printf("ExecInitIncrementalSort: sort node initialized\n");
+
+ return incrsortstate;
+}
+
+/* ----------------------------------------------------------------
+ * ExecEndIncrementalSort(node)
+ * ----------------------------------------------------------------
+ */
+void
+ExecEndIncrementalSort(IncrementalSortState *node)
+{
+ SO_printf("ExecEndIncrementalSort: shutting down sort node\n");
+
+ /*
+ * clean out the tuple table
+ */
+ ExecClearTuple(node->ss.ss_ScanTupleSlot);
+ /* must drop pointer to sort result tuple */
+ ExecClearTuple(node->ss.ps.ps_ResultTupleSlot);
+ /* must drop stanalone tuple slot from outer node */
+ ExecDropSingleTupleTableSlot(node->group_pivot);
+ ExecDropSingleTupleTableSlot(node->transfer_tuple);
+
+ /*
+ * Release tuplesort resources.
+ */
+ if (node->fullsort_state != NULL)
+ tuplesort_end(node->fullsort_state);
+ node->fullsort_state = NULL;
+ if (node->prefixsort_state != NULL)
+ tuplesort_end(node->prefixsort_state);
+ node->prefixsort_state = NULL;
+
+ /*
+ * Shut down the subplan.
+ */
+ ExecEndNode(outerPlanState(node));
+
+ SO_printf("ExecEndIncrementalSort: sort node shutdown\n");
+}
+
+void
+ExecReScanIncrementalSort(IncrementalSortState *node)
+{
+ PlanState *outerPlan = outerPlanState(node);
+
+ /*
+ * If we haven't sorted yet, just return. If outerplan's chgParam is not
+ * NULL then it will be re-scanned by ExecProcNode, else no reason to
+ * re-scan it at all.
+ */
+ if (!node->sort_Done)
+ return;
+
+ /* must drop pointer to sort result tuple */
+ ExecClearTuple(node->ss.ps.ps_ResultTupleSlot);
+
+ /*
+ * If subnode is to be rescanned then we forget previous sort results; we
+ * have to re-read the subplan and re-sort. Also must re-sort if the
+ * bounded-sort parameters changed or we didn't select randomAccess.
+ *
+ * Otherwise we can just rewind and rescan the sorted output.
+ */
+ node->sort_Done = false;
+ tuplesort_end(node->fullsort_state);
+ node->prefixsort_state = NULL;
+ tuplesort_end(node->fullsort_state);
+ node->prefixsort_state = NULL;
+ node->bound_Done = 0;
+
+ /*
+ * if chgParam of subnode is not null then plan will be re-scanned by
+ * first ExecProcNode.
+ */
+ if (outerPlan->chgParam == NULL)
+ ExecReScan(outerPlan);
+}
+
+/* ----------------------------------------------------------------
+ * Parallel Query Support
+ * ----------------------------------------------------------------
+ */
+
+/* ----------------------------------------------------------------
+ * ExecSortEstimate
+ *
+ * Estimate space required to propagate sort statistics.
+ * ----------------------------------------------------------------
+ */
+void
+ExecIncrementalSortEstimate(IncrementalSortState *node, ParallelContext *pcxt)
+{
+ Size size;
+
+ /* don't need this if not instrumenting or no workers */
+ if (!node->ss.ps.instrument || pcxt->nworkers == 0)
+ return;
+
+ size = mul_size(pcxt->nworkers, sizeof(IncrementalSortInfo));
+ size = add_size(size, offsetof(SharedIncrementalSortInfo, sinfo));
+ shm_toc_estimate_chunk(&pcxt->estimator, size);
+ shm_toc_estimate_keys(&pcxt->estimator, 1);
+}
+
+/* ----------------------------------------------------------------
+ * ExecSortInitializeDSM
+ *
+ * Initialize DSM space for sort statistics.
+ * ----------------------------------------------------------------
+ */
+void
+ExecIncrementalSortInitializeDSM(IncrementalSortState *node, ParallelContext *pcxt)
+{
+ Size size;
+
+ /* don't need this if not instrumenting or no workers */
+ if (!node->ss.ps.instrument || pcxt->nworkers == 0)
+ return;
+
+ size = offsetof(SharedIncrementalSortInfo, sinfo)
+ + pcxt->nworkers * sizeof(IncrementalSortInfo);
+ node->shared_info = shm_toc_allocate(pcxt->toc, size);
+ /* ensure any unfilled slots will contain zeroes */
+ memset(node->shared_info, 0, size);
+ node->shared_info->num_workers = pcxt->nworkers;
+ shm_toc_insert(pcxt->toc, node->ss.ps.plan->plan_node_id,
+ node->shared_info);
+}
+
+/* ----------------------------------------------------------------
+ * ExecSortInitializeWorker
+ *
+ * Attach worker to DSM space for sort statistics.
+ * ----------------------------------------------------------------
+ */
+void
+ExecIncrementalSortInitializeWorker(IncrementalSortState *node, ParallelWorkerContext *pwcxt)
+{
+ node->shared_info =
+ shm_toc_lookup(pwcxt->toc, node->ss.ps.plan->plan_node_id, true);
+ node->am_worker = true;
+}
+
+/* ----------------------------------------------------------------
+ * ExecSortRetrieveInstrumentation
+ *
+ * Transfer sort statistics from DSM to private memory.
+ * ----------------------------------------------------------------
+ */
+void
+ExecIncrementalSortRetrieveInstrumentation(IncrementalSortState *node)
+{
+ Size size;
+ SharedIncrementalSortInfo *si;
+
+ if (node->shared_info == NULL)
+ return;
+
+ size = offsetof(SharedIncrementalSortInfo, sinfo)
+ + node->shared_info->num_workers * sizeof(IncrementalSortInfo);
+ si = palloc(size);
+ memcpy(si, node->shared_info, size);
+ node->shared_info = si;
+}
diff --git a/src/backend/executor/nodeSort.c b/src/backend/executor/nodeSort.c
index 92855278ad..3ea1b1bca1 100644
--- a/src/backend/executor/nodeSort.c
+++ b/src/backend/executor/nodeSort.c
@@ -93,7 +93,8 @@ ExecSort(PlanState *pstate)
plannode->collations,
plannode->nullsFirst,
work_mem,
- NULL, node->randomAccess);
+ NULL,
+ node->randomAccess);
if (node->bounded)
tuplesort_set_bound(tuplesortstate, node->bound);
node->tuplesortstate = (void *) tuplesortstate;
diff --git a/src/backend/nodes/copyfuncs.c b/src/backend/nodes/copyfuncs.c
index 78deade89b..de27b06e15 100644
--- a/src/backend/nodes/copyfuncs.c
+++ b/src/backend/nodes/copyfuncs.c
@@ -921,6 +921,24 @@ _copyMaterial(const Material *from)
}
+/*
+ * CopySortFields
+ *
+ * This function copies the fields of the Sort node. It is used by
+ * all the copy functions for classes which inherit from Sort.
+ */
+static void
+CopySortFields(const Sort *from, Sort *newnode)
+{
+ CopyPlanFields((const Plan *) from, (Plan *) newnode);
+
+ COPY_SCALAR_FIELD(numCols);
+ COPY_POINTER_FIELD(sortColIdx, from->numCols * sizeof(AttrNumber));
+ COPY_POINTER_FIELD(sortOperators, from->numCols * sizeof(Oid));
+ COPY_POINTER_FIELD(collations, from->numCols * sizeof(Oid));
+ COPY_POINTER_FIELD(nullsFirst, from->numCols * sizeof(bool));
+}
+
/*
* _copySort
*/
@@ -932,13 +950,29 @@ _copySort(const Sort *from)
/*
* copy node superclass fields
*/
- CopyPlanFields((const Plan *) from, (Plan *) newnode);
+ CopySortFields(from, newnode);
- COPY_SCALAR_FIELD(numCols);
- COPY_POINTER_FIELD(sortColIdx, from->numCols * sizeof(AttrNumber));
- COPY_POINTER_FIELD(sortOperators, from->numCols * sizeof(Oid));
- COPY_POINTER_FIELD(collations, from->numCols * sizeof(Oid));
- COPY_POINTER_FIELD(nullsFirst, from->numCols * sizeof(bool));
+ return newnode;
+}
+
+
+/*
+ * _copyIncrementalSort
+ */
+static IncrementalSort *
+_copyIncrementalSort(const IncrementalSort *from)
+{
+ IncrementalSort *newnode = makeNode(IncrementalSort);
+
+ /*
+ * copy node superclass fields
+ */
+ CopySortFields((const Sort *) from, (Sort *) newnode);
+
+ /*
+ * copy remainder of node
+ */
+ COPY_SCALAR_FIELD(presortedCols);
return newnode;
}
@@ -4900,6 +4934,9 @@ copyObjectImpl(const void *from)
case T_Sort:
retval = _copySort(from);
break;
+ case T_IncrementalSort:
+ retval = _copyIncrementalSort(from);
+ break;
case T_Group:
retval = _copyGroup(from);
break;
diff --git a/src/backend/nodes/outfuncs.c b/src/backend/nodes/outfuncs.c
index 8400dd319e..b8c3826a17 100644
--- a/src/backend/nodes/outfuncs.c
+++ b/src/backend/nodes/outfuncs.c
@@ -830,10 +830,8 @@ _outMaterial(StringInfo str, const Material *node)
}
static void
-_outSort(StringInfo str, const Sort *node)
+_outSortInfo(StringInfo str, const Sort *node)
{
- WRITE_NODE_TYPE("SORT");
-
_outPlanInfo(str, (const Plan *) node);
WRITE_INT_FIELD(numCols);
@@ -843,6 +841,24 @@ _outSort(StringInfo str, const Sort *node)
WRITE_BOOL_ARRAY(nullsFirst, node->numCols);
}
+static void
+_outSort(StringInfo str, const Sort *node)
+{
+ WRITE_NODE_TYPE("SORT");
+
+ _outSortInfo(str, node);
+}
+
+static void
+_outIncrementalSort(StringInfo str, const IncrementalSort *node)
+{
+ WRITE_NODE_TYPE("INCREMENTALSORT");
+
+ _outSortInfo(str, (const Sort *) node);
+
+ WRITE_INT_FIELD(presortedCols);
+}
+
static void
_outUnique(StringInfo str, const Unique *node)
{
@@ -3755,6 +3771,9 @@ outNode(StringInfo str, const void *obj)
case T_Sort:
_outSort(str, obj);
break;
+ case T_IncrementalSort:
+ _outIncrementalSort(str, obj);
+ break;
case T_Unique:
_outUnique(str, obj);
break;
diff --git a/src/backend/nodes/readfuncs.c b/src/backend/nodes/readfuncs.c
index 6c2626ee62..9e0d42322c 100644
--- a/src/backend/nodes/readfuncs.c
+++ b/src/backend/nodes/readfuncs.c
@@ -2114,12 +2114,13 @@ _readMaterial(void)
}
/*
- * _readSort
+ * ReadCommonSort
+ * Assign the basic stuff of all nodes that inherit from Sort
*/
-static Sort *
-_readSort(void)
+static void
+ReadCommonSort(Sort *local_node)
{
- READ_LOCALS(Sort);
+ READ_TEMP_LOCALS();
ReadCommonPlan(&local_node->plan);
@@ -2128,6 +2129,32 @@ _readSort(void)
READ_OID_ARRAY(sortOperators, local_node->numCols);
READ_OID_ARRAY(collations, local_node->numCols);
READ_BOOL_ARRAY(nullsFirst, local_node->numCols);
+}
+
+/*
+ * _readSort
+ */
+static Sort *
+_readSort(void)
+{
+ READ_LOCALS_NO_FIELDS(Sort);
+
+ ReadCommonSort(local_node);
+
+ READ_DONE();
+}
+
+/*
+ * _readIncrementalSort
+ */
+static IncrementalSort *
+_readIncrementalSort(void)
+{
+ READ_LOCALS(IncrementalSort);
+
+ ReadCommonSort(&local_node->sort);
+
+ READ_INT_FIELD(presortedCols);
READ_DONE();
}
@@ -2761,6 +2788,8 @@ parseNodeString(void)
return_value = _readMaterial();
else if (MATCH("SORT", 4))
return_value = _readSort();
+ else if (MATCH("INCREMENTALSORT", 15))
+ return_value = _readIncrementalSort();
else if (MATCH("GROUP", 5))
return_value = _readGroup();
else if (MATCH("AGG", 3))
diff --git a/src/backend/optimizer/path/allpaths.c b/src/backend/optimizer/path/allpaths.c
index b7723481b0..3efc807164 100644
--- a/src/backend/optimizer/path/allpaths.c
+++ b/src/backend/optimizer/path/allpaths.c
@@ -3884,6 +3884,10 @@ print_path(PlannerInfo *root, Path *path, int indent)
ptype = "Sort";
subpath = ((SortPath *) path)->subpath;
break;
+ case T_IncrementalSortPath:
+ ptype = "IncrementalSort";
+ subpath = ((SortPath *) path)->subpath;
+ break;
case T_GroupPath:
ptype = "Group";
subpath = ((GroupPath *) path)->subpath;
diff --git a/src/backend/optimizer/path/costsize.c b/src/backend/optimizer/path/costsize.c
index a2a9b1f7be..7f820e7351 100644
--- a/src/backend/optimizer/path/costsize.c
+++ b/src/backend/optimizer/path/costsize.c
@@ -127,6 +127,7 @@ bool enable_indexonlyscan = true;
bool enable_bitmapscan = true;
bool enable_tidscan = true;
bool enable_sort = true;
+bool enable_incrementalsort = true;
bool enable_hashagg = true;
bool enable_nestloop = true;
bool enable_material = true;
@@ -1645,9 +1646,9 @@ cost_recursive_union(Path *runion, Path *nrterm, Path *rterm)
}
/*
- * cost_sort
- * Determines and returns the cost of sorting a relation, including
- * the cost of reading the input data.
+ * cost_tuplesort
+ * Determines and returns the cost of sorting a relation using tuplesort,
+ * not including the cost of reading the input data.
*
* If the total volume of data to sort is less than sort_mem, we will do
* an in-memory sort, which requires no I/O and about t*log2(t) tuple
@@ -1674,39 +1675,23 @@ cost_recursive_union(Path *runion, Path *nrterm, Path *rterm)
* specifying nonzero comparison_cost; typically that's used for any extra
* work that has to be done to prepare the inputs to the comparison operators.
*
- * 'pathkeys' is a list of sort keys
- * 'input_cost' is the total cost for reading the input data
* 'tuples' is the number of tuples in the relation
* 'width' is the average tuple width in bytes
* 'comparison_cost' is the extra cost per comparison, if any
* 'sort_mem' is the number of kilobytes of work memory allowed for the sort
* 'limit_tuples' is the bound on the number of output tuples; -1 if no bound
- *
- * NOTE: some callers currently pass NIL for pathkeys because they
- * can't conveniently supply the sort keys. Since this routine doesn't
- * currently do anything with pathkeys anyway, that doesn't matter...
- * but if it ever does, it should react gracefully to lack of key data.
- * (Actually, the thing we'd most likely be interested in is just the number
- * of sort keys, which all callers *could* supply.)
*/
-void
-cost_sort(Path *path, PlannerInfo *root,
- List *pathkeys, Cost input_cost, double tuples, int width,
+static void
+cost_tuplesort(Cost *startup_cost, Cost *run_cost,
+ double tuples, int width,
Cost comparison_cost, int sort_mem,
double limit_tuples)
{
- Cost startup_cost = input_cost;
- Cost run_cost = 0;
double input_bytes = relation_byte_size(tuples, width);
double output_bytes;
double output_tuples;
long sort_mem_bytes = sort_mem * 1024L;
- if (!enable_sort)
- startup_cost += disable_cost;
-
- path->rows = tuples;
-
/*
* We want to be sure the cost of a sort is never estimated as zero, even
* if passed-in tuple count is zero. Besides, mustn't do log(0)...
@@ -1745,7 +1730,7 @@ cost_sort(Path *path, PlannerInfo *root,
*
* Assume about N log2 N comparisons
*/
- startup_cost += comparison_cost * tuples * LOG2(tuples);
+ *startup_cost = comparison_cost * tuples * LOG2(tuples);
/* Disk costs */
@@ -1756,7 +1741,7 @@ cost_sort(Path *path, PlannerInfo *root,
log_runs = 1.0;
npageaccesses = 2.0 * npages * log_runs;
/* Assume 3/4ths of accesses are sequential, 1/4th are not */
- startup_cost += npageaccesses *
+ *startup_cost += npageaccesses *
(seq_page_cost * 0.75 + random_page_cost * 0.25);
}
else if (tuples > 2 * output_tuples || input_bytes > sort_mem_bytes)
@@ -1767,12 +1752,12 @@ cost_sort(Path *path, PlannerInfo *root,
* factor is a bit higher than for quicksort. Tweak it so that the
* cost curve is continuous at the crossover point.
*/
- startup_cost += comparison_cost * tuples * LOG2(2.0 * output_tuples);
+ *startup_cost = comparison_cost * tuples * LOG2(2.0 * output_tuples);
}
else
{
/* We'll use plain quicksort on all the input tuples */
- startup_cost += comparison_cost * tuples * LOG2(tuples);
+ *startup_cost = comparison_cost * tuples * LOG2(tuples);
}
/*
@@ -1783,8 +1768,183 @@ cost_sort(Path *path, PlannerInfo *root,
* here --- the upper LIMIT will pro-rate the run cost so we'd be double
* counting the LIMIT otherwise.
*/
- run_cost += cpu_operator_cost * tuples;
+ *run_cost = cpu_operator_cost * tuples;
+}
+/*
+ * cost_full_sort
+ * Determines and returns the cost of sorting a relation, including the
+ * cost of reading the input data.
+ *
+ * For the precise description of how the cost is calculated, see the comment
+ * for cost_tuplesort().
+ */
+void
+cost_full_sort(Cost *startup_cost, Cost *run_cost,
+ Cost input_total_cost, double tuples, int width,
+ Cost comparison_cost, int sort_mem,
+ double limit_tuples)
+{
+ cost_tuplesort(startup_cost, run_cost,
+ tuples, width,
+ comparison_cost, sort_mem,
+ limit_tuples);
+
+ if (!enable_sort)
+ *startup_cost += disable_cost;
+
+ *startup_cost += input_total_cost;
+}
+
+/*
+ * cost_incremental_sort
+ * Determines and returns the cost of sorting a relation incrementally, when
+ * the input path is already sorted by some of the pathkeys.
+ *
+ * 'presorted_keys' is the number of leading pathkeys by which the input path
+ * is sorted.
+ *
+ * We estimate the number of groups into which the relation is divided by the
+ * leading pathkeys, and then calculate the cost of sorting a single group
+ * with tuplesort using cost_tuplesort().
+ */
+void
+cost_incremental_sort(Path *path,
+ PlannerInfo *root, List *pathkeys, int presorted_keys,
+ Cost input_startup_cost, Cost input_total_cost,
+ double input_tuples, int width, Cost comparison_cost, int sort_mem,
+ double limit_tuples)
+{
+ Cost startup_cost = 0,
+ run_cost = 0,
+ input_run_cost = input_total_cost - input_startup_cost;
+ double output_tuples,
+ output_groups,
+ group_tuples,
+ input_groups;
+ Cost group_startup_cost,
+ group_run_cost,
+ group_input_run_cost;
+ List *presortedExprs = NIL;
+ ListCell *l;
+ int i = 0;
+
+ Assert(presorted_keys != 0);
+
+ if (!enable_sort)
+ startup_cost += disable_cost;
+
+ if (!enable_incrementalsort)
+ startup_cost += disable_cost;
+
+ /*
+ * We want to be sure the cost of a sort is never estimated as zero, even
+ * if passed-in tuple count is zero. Besides, mustn't do log(0)...
+ */
+ if (input_tuples < 2.0)
+ input_tuples = 2.0;
+
+ /* Extract presorted keys as list of expressions */
+ foreach(l, pathkeys)
+ {
+ PathKey *key = (PathKey *)lfirst(l);
+ EquivalenceMember *member = (EquivalenceMember *)
+ linitial(key->pk_eclass->ec_members);
+
+ presortedExprs = lappend(presortedExprs, member->em_expr);
+
+ i++;
+ if (i >= presorted_keys)
+ break;
+ }
+
+ /* Estimate number of groups with equal presorted keys */
+ input_groups = estimate_num_groups(root, presortedExprs, input_tuples, NULL);
+ group_tuples = input_tuples / input_groups;
+ group_input_run_cost = input_run_cost / input_groups;
+
+ /*
+ * Estimate average cost of sorting of one group where presorted keys
+ * are equal. Incremental sort is sensitive to distribution of tuples
+ * to the groups, where we're relying on quite rough assumptions. Thus,
+ * we're pessimistic about incremental sort performance and increase
+ * its average group size by half.
+ */
+ cost_tuplesort(&group_startup_cost, &group_run_cost,
+ 1.5 * group_tuples, width, comparison_cost, sort_mem,
+ limit_tuples);
+
+ /* If we have a LIMIT, adjust the number of groups we'll have to return. */
+ if (limit_tuples > 0 && limit_tuples < input_tuples)
+ {
+ output_tuples = limit_tuples;
+ output_groups = floor(output_tuples / group_tuples) + 1;
+ }
+ else
+ {
+ output_tuples = input_tuples;
+ output_groups = input_groups;
+ }
+
+ /*
+ * Startup cost of incremental sort is the startup cost of its first group
+ * plus the cost of its input.
+ */
+ startup_cost += group_startup_cost
+ + input_startup_cost + group_input_run_cost;
+
+ /*
+ * After we started producing tuples from the first group, the cost of
+ * producing all the tuples is given by the cost to finish processing
+ * this group, plus the total cost to process the remaining groups,
+ * plus the remaining cost of input.
+ */
+ run_cost += group_run_cost
+ + (group_run_cost + group_startup_cost) * (output_groups - 1)
+ + group_input_run_cost * (output_groups - 1);
+
+ /*
+ * Incremental sort adds some overhead by itself. Firstly, it has to
+ * detect the sort groups. This is roughly equal to one extra copy and
+ * comparison per tuple. Secondly, it has to reset the tuplesort context
+ * for every group.
+ */
+ run_cost += (cpu_tuple_cost + comparison_cost) * output_tuples;
+ run_cost += 2.0 * cpu_tuple_cost * output_groups;
+
+ path->rows = input_tuples;
+ path->startup_cost = startup_cost;
+ path->total_cost = startup_cost + run_cost;
+}
+
+/*
+ * cost_sort
+ * Determines and returns the cost of sorting a relation, including
+ * the cost of reading the input data.
+ *
+ * NOTE: some callers currently pass NIL for pathkeys because they
+ * can't conveniently supply the sort keys. Since this routine doesn't
+ * currently do anything with pathkeys anyway, that doesn't matter...
+ * but if it ever does, it should react gracefully to lack of key data.
+ * (Actually, the thing we'd most likely be interested in is just the number
+ * of sort keys, which all callers *could* supply.)
+ */
+void
+cost_sort(Path *path, PlannerInfo *root,
+ List *pathkeys, Cost input_cost, double tuples, int width,
+ Cost comparison_cost, int sort_mem,
+ double limit_tuples)
+
+{
+ Cost startup_cost;
+ Cost run_cost;
+
+ cost_full_sort(&startup_cost, &run_cost,
+ input_cost,
+ tuples, width, comparison_cost, sort_mem,
+ limit_tuples);
+
+ path->rows = tuples;
path->startup_cost = startup_cost;
path->total_cost = startup_cost + run_cost;
}
diff --git a/src/backend/optimizer/path/pathkeys.c b/src/backend/optimizer/path/pathkeys.c
index 08b5061612..454c61e1d8 100644
--- a/src/backend/optimizer/path/pathkeys.c
+++ b/src/backend/optimizer/path/pathkeys.c
@@ -332,6 +332,51 @@ pathkeys_contained_in(List *keys1, List *keys2)
return false;
}
+
+/*
+ * pathkeys_common_contained_in
+ * Same as pathkeys_contained_in, but also sets length of longest
+ * common prefix of keys1 and keys2.
+ */
+bool
+pathkeys_common_contained_in(List *keys1, List *keys2, int *n_common)
+{
+ int n = 0;
+ ListCell *key1,
+ *key2;
+
+ forboth(key1, keys1, key2, keys2)
+ {
+ PathKey *pathkey1 = (PathKey *) lfirst(key1);
+ PathKey *pathkey2 = (PathKey *) lfirst(key2);
+
+ if (pathkey1 != pathkey2)
+ {
+ *n_common = n;
+ return false;
+ }
+ n++;
+ }
+
+ *n_common = n;
+ return (key1 == NULL);
+}
+
+
+/*
+ * pathkeys_common
+ * Returns length of longest common prefix of keys1 and keys2.
+ */
+int
+pathkeys_common(List *keys1, List *keys2)
+{
+ int n;
+
+ (void) pathkeys_common_contained_in(keys1, keys2, &n);
+ return n;
+}
+
+
/*
* get_cheapest_path_for_pathkeys
* Find the cheapest path (according to the specified criterion) that
@@ -1791,19 +1836,23 @@ right_merge_direction(PlannerInfo *root, PathKey *pathkey)
static int
pathkeys_useful_for_ordering(PlannerInfo *root, List *pathkeys)
{
+ int n_common_pathkeys;
+
if (root->query_pathkeys == NIL)
return 0; /* no special ordering requested */
if (pathkeys == NIL)
return 0; /* unordered path */
- if (pathkeys_contained_in(root->query_pathkeys, pathkeys))
- {
- /* It's useful ... or at least the first N keys are */
- return list_length(root->query_pathkeys);
- }
+ (void) pathkeys_common_contained_in(root->query_pathkeys, pathkeys,
+ &n_common_pathkeys);
- return 0; /* path ordering not useful */
+ /*
+ * Return the number of path keys in common, or 0 if there are none.
+ * Any leading common pathkeys could be useful for ordering because
+ * we can use the incremental sort.
+ */
+ return n_common_pathkeys;
}
/*
diff --git a/src/backend/optimizer/plan/createplan.c b/src/backend/optimizer/plan/createplan.c
index 12fba56285..bfb52f21ab 100644
--- a/src/backend/optimizer/plan/createplan.c
+++ b/src/backend/optimizer/plan/createplan.c
@@ -98,6 +98,8 @@ static Plan *create_projection_plan(PlannerInfo *root,
int flags);
static Plan *inject_projection_plan(Plan *subplan, List *tlist, bool parallel_safe);
static Sort *create_sort_plan(PlannerInfo *root, SortPath *best_path, int flags);
+static IncrementalSort *create_incrementalsort_plan(PlannerInfo *root,
+ IncrementalSortPath *best_path, int flags);
static Group *create_group_plan(PlannerInfo *root, GroupPath *best_path);
static Unique *create_upper_unique_plan(PlannerInfo *root, UpperUniquePath *best_path,
int flags);
@@ -241,6 +243,10 @@ static MergeJoin *make_mergejoin(List *tlist,
static Sort *make_sort(Plan *lefttree, int numCols,
AttrNumber *sortColIdx, Oid *sortOperators,
Oid *collations, bool *nullsFirst);
+static IncrementalSort *make_incrementalsort(Plan *lefttree,
+ int numCols, int presortedCols,
+ AttrNumber *sortColIdx, Oid *sortOperators,
+ Oid *collations, bool *nullsFirst);
static Plan *prepare_sort_from_pathkeys(Plan *lefttree, List *pathkeys,
Relids relids,
const AttrNumber *reqColIdx,
@@ -255,6 +261,8 @@ static EquivalenceMember *find_ec_member_for_tle(EquivalenceClass *ec,
Relids relids);
static Sort *make_sort_from_pathkeys(Plan *lefttree, List *pathkeys,
Relids relids);
+static IncrementalSort *make_incrementalsort_from_pathkeys(Plan *lefttree,
+ List *pathkeys, Relids relids, int presortedCols);
static Sort *make_sort_from_groupcols(List *groupcls,
AttrNumber *grpColIdx,
Plan *lefttree);
@@ -457,6 +465,11 @@ create_plan_recurse(PlannerInfo *root, Path *best_path, int flags)
(SortPath *) best_path,
flags);
break;
+ case T_IncrementalSort:
+ plan = (Plan *) create_incrementalsort_plan(root,
+ (IncrementalSortPath *) best_path,
+ flags);
+ break;
case T_Group:
plan = (Plan *) create_group_plan(root,
(GroupPath *) best_path);
@@ -1988,6 +2001,32 @@ create_sort_plan(PlannerInfo *root, SortPath *best_path, int flags)
return plan;
}
+/*
+ * create_incrementalsort_plan
+ *
+ * Do the same as create_sort_plan, but create IncrementalSort plan.
+ */
+static IncrementalSort *
+create_incrementalsort_plan(PlannerInfo *root, IncrementalSortPath *best_path,
+ int flags)
+{
+ IncrementalSort *plan;
+ Plan *subplan;
+
+ /* See comments in create_sort_plan() above */
+ subplan = create_plan_recurse(root, best_path->spath.subpath,
+ flags | CP_SMALL_TLIST);
+ plan = make_incrementalsort_from_pathkeys(subplan,
+ best_path->spath.path.pathkeys,
+ IS_OTHER_REL(best_path->spath.subpath->parent) ?
+ best_path->spath.path.parent->relids : NULL,
+ best_path->presortedCols);
+
+ copy_generic_path_info(&plan->sort.plan, (Path *) best_path);
+
+ return plan;
+}
+
/*
* create_group_plan
*
@@ -5050,17 +5089,24 @@ static void
label_sort_with_costsize(PlannerInfo *root, Sort *plan, double limit_tuples)
{
Plan *lefttree = plan->plan.lefttree;
- Path sort_path; /* dummy for result of cost_sort */
+ Cost startup_cost,
+ run_cost;
- cost_sort(&sort_path, root, NIL,
+ /*
+ * This function shouldn't have to deal with IncrementalSort plans
+ * because they are only created from corresponding Path nodes.
+ */
+ Assert(IsA(plan, Sort));
+
+ cost_full_sort(&startup_cost, &run_cost,
lefttree->total_cost,
lefttree->plan_rows,
lefttree->plan_width,
0.0,
work_mem,
limit_tuples);
- plan->plan.startup_cost = sort_path.startup_cost;
- plan->plan.total_cost = sort_path.total_cost;
+ plan->plan.startup_cost = startup_cost;
+ plan->plan.total_cost = startup_cost + run_cost;
plan->plan.plan_rows = lefttree->plan_rows;
plan->plan.plan_width = lefttree->plan_width;
plan->plan.parallel_aware = false;
@@ -5633,9 +5679,12 @@ make_sort(Plan *lefttree, int numCols,
AttrNumber *sortColIdx, Oid *sortOperators,
Oid *collations, bool *nullsFirst)
{
- Sort *node = makeNode(Sort);
- Plan *plan = &node->plan;
+ Sort *node;
+ Plan *plan;
+
+ node = makeNode(Sort);
+ plan = &node->plan;
plan->targetlist = lefttree->targetlist;
plan->qual = NIL;
plan->lefttree = lefttree;
@@ -5649,6 +5698,37 @@ make_sort(Plan *lefttree, int numCols,
return node;
}
+/*
+ * make_incrementalsort --- basic routine to build a IncrementalSort plan node
+ *
+ * Caller must have built the sortColIdx, sortOperators, collations, and
+ * nullsFirst arrays already.
+ */
+static IncrementalSort *
+make_incrementalsort(Plan *lefttree, int numCols, int presortedCols,
+ AttrNumber *sortColIdx, Oid *sortOperators,
+ Oid *collations, bool *nullsFirst)
+{
+ IncrementalSort *node;
+ Plan *plan;
+
+ node = makeNode(IncrementalSort);
+
+ plan = &node->sort.plan;
+ plan->targetlist = lefttree->targetlist;
+ plan->qual = NIL;
+ plan->lefttree = lefttree;
+ plan->righttree = NULL;
+ node->presortedCols = presortedCols;
+ node->sort.numCols = numCols;
+ node->sort.sortColIdx = sortColIdx;
+ node->sort.sortOperators = sortOperators;
+ node->sort.collations = collations;
+ node->sort.nullsFirst = nullsFirst;
+
+ return node;
+}
+
/*
* prepare_sort_from_pathkeys
* Prepare to sort according to given pathkeys
@@ -5995,6 +6075,42 @@ make_sort_from_pathkeys(Plan *lefttree, List *pathkeys, Relids relids)
collations, nullsFirst);
}
+/*
+ * make_incrementalsort_from_pathkeys
+ * Create sort plan to sort according to given pathkeys
+ *
+ * 'lefttree' is the node which yields input tuples
+ * 'pathkeys' is the list of pathkeys by which the result is to be sorted
+ * 'relids' is the set of relations required by prepare_sort_from_pathkeys()
+ * 'presortedCols' is the number of presorted columns in input tuples
+ */
+static IncrementalSort *
+make_incrementalsort_from_pathkeys(Plan *lefttree, List *pathkeys,
+ Relids relids, int presortedCols)
+{
+ int numsortkeys;
+ AttrNumber *sortColIdx;
+ Oid *sortOperators;
+ Oid *collations;
+ bool *nullsFirst;
+
+ /* Compute sort column info, and adjust lefttree as needed */
+ lefttree = prepare_sort_from_pathkeys(lefttree, pathkeys,
+ relids,
+ NULL,
+ false,
+ &numsortkeys,
+ &sortColIdx,
+ &sortOperators,
+ &collations,
+ &nullsFirst);
+
+ /* Now build the Sort node */
+ return make_incrementalsort(lefttree, numsortkeys, presortedCols,
+ sortColIdx, sortOperators,
+ collations, nullsFirst);
+}
+
/*
* make_sort_from_sortclauses
* Create sort plan to sort according to given sortclauses
@@ -6729,6 +6845,7 @@ is_projection_capable_path(Path *path)
case T_Hash:
case T_Material:
case T_Sort:
+ case T_IncrementalSort:
case T_Unique:
case T_SetOp:
case T_LockRows:
diff --git a/src/backend/optimizer/plan/planner.c b/src/backend/optimizer/plan/planner.c
index 401299e542..16996b1bc2 100644
--- a/src/backend/optimizer/plan/planner.c
+++ b/src/backend/optimizer/plan/planner.c
@@ -4922,8 +4922,8 @@ create_distinct_paths(PlannerInfo *root,
* Build a new upperrel containing Paths for ORDER BY evaluation.
*
* All paths in the result must satisfy the ORDER BY ordering.
- * The only new path we need consider is an explicit sort on the
- * cheapest-total existing path.
+ * The only new paths we need consider is an explicit full or
+ * incremental sort on the cheapest-total existing path.
*
* input_rel: contains the source-data Paths
* target: the output tlist the result Paths must emit
@@ -4962,29 +4962,60 @@ create_ordered_paths(PlannerInfo *root,
foreach(lc, input_rel->pathlist)
{
- Path *path = (Path *) lfirst(lc);
+ Path *input_path = (Path *) lfirst(lc);
+ Path *sorted_path = input_path;
bool is_sorted;
+ int presorted_keys;
+
+ is_sorted = pathkeys_common_contained_in(root->sort_pathkeys,
+ input_path->pathkeys, &presorted_keys);
- is_sorted = pathkeys_contained_in(root->sort_pathkeys,
- path->pathkeys);
- if (path == cheapest_input_path || is_sorted)
+ if (is_sorted)
{
- if (!is_sorted)
+ /* Use the input path as is, but add a projection step if needed */
+ if (sorted_path->pathtarget != target)
+ sorted_path = apply_projection_to_path(root, ordered_rel,
+ sorted_path, target);
+
+ add_path(ordered_rel, sorted_path);
+ }
+ else
+ {
+ if (input_path == cheapest_input_path)
{
- /* An explicit sort here can take advantage of LIMIT */
- path = (Path *) create_sort_path(root,
- ordered_rel,
- path,
- root->sort_pathkeys,
- limit_tuples);
+ /*
+ * Sort the cheapest input path. An explicit sort here can take
+ * advantage of LIMIT.
+ */
+ sorted_path = (Path *) create_sort_path(root,
+ ordered_rel,
+ input_path,
+ root->sort_pathkeys,
+ limit_tuples);
+ /* Add projection step if needed */
+ if (sorted_path->pathtarget != target)
+ sorted_path = apply_projection_to_path(root, ordered_rel,
+ sorted_path, target);
+
+ add_path(ordered_rel, sorted_path);
+ }
+ if (presorted_keys > 0)
+ {
+ /* Also consider incremental sort. */
+ sorted_path = (Path *) create_incremental_sort_path(root,
+ ordered_rel,
+ input_path,
+ root->sort_pathkeys,
+ presorted_keys,
+ limit_tuples);
+
+ /* Add projection step if needed */
+ if (sorted_path->pathtarget != target)
+ sorted_path = apply_projection_to_path(root, ordered_rel,
+ sorted_path, target);
+
+ add_path(ordered_rel, sorted_path);
}
-
- /* Add projection step if needed */
- if (path->pathtarget != target)
- path = apply_projection_to_path(root, ordered_rel,
- path, target);
-
- add_path(ordered_rel, path);
}
}
diff --git a/src/backend/optimizer/plan/setrefs.c b/src/backend/optimizer/plan/setrefs.c
index dc11f098e0..878cb6b934 100644
--- a/src/backend/optimizer/plan/setrefs.c
+++ b/src/backend/optimizer/plan/setrefs.c
@@ -648,6 +648,7 @@ set_plan_refs(PlannerInfo *root, Plan *plan, int rtoffset)
case T_Hash:
case T_Material:
case T_Sort:
+ case T_IncrementalSort:
case T_Unique:
case T_SetOp:
diff --git a/src/backend/optimizer/plan/subselect.c b/src/backend/optimizer/plan/subselect.c
index efd0fbc21c..41a5e18195 100644
--- a/src/backend/optimizer/plan/subselect.c
+++ b/src/backend/optimizer/plan/subselect.c
@@ -2686,6 +2686,7 @@ finalize_plan(PlannerInfo *root, Plan *plan,
case T_Hash:
case T_Material:
case T_Sort:
+ case T_IncrementalSort:
case T_Unique:
case T_SetOp:
case T_Group:
diff --git a/src/backend/optimizer/util/pathnode.c b/src/backend/optimizer/util/pathnode.c
index d884d2bb00..91066b238c 100644
--- a/src/backend/optimizer/util/pathnode.c
+++ b/src/backend/optimizer/util/pathnode.c
@@ -2777,6 +2777,57 @@ create_set_projection_path(PlannerInfo *root,
return pathnode;
}
+/*
+ * create_incremental_sort_path
+ * Creates a pathnode that represents performing an incremental sort.
+ *
+ * 'rel' is the parent relation associated with the result
+ * 'subpath' is the path representing the source of data
+ * 'pathkeys' represents the desired sort order
+ * 'presorted_keys' is the number of keys by which the input path is
+ * already sorted
+ * 'limit_tuples' is the estimated bound on the number of output tuples,
+ * or -1 if no LIMIT or couldn't estimate
+ */
+SortPath *
+create_incremental_sort_path(PlannerInfo *root,
+ RelOptInfo *rel,
+ Path *subpath,
+ List *pathkeys,
+ int presorted_keys,
+ double limit_tuples)
+{
+ IncrementalSortPath *sort = makeNode(IncrementalSortPath);
+ SortPath *pathnode = &sort->spath;
+
+ pathnode->path.pathtype = T_IncrementalSort;
+ pathnode->path.parent = rel;
+ /* Sort doesn't project, so use source path's pathtarget */
+ pathnode->path.pathtarget = subpath->pathtarget;
+ /* For now, assume we are above any joins, so no parameterization */
+ pathnode->path.param_info = NULL;
+ pathnode->path.parallel_aware = false;
+ pathnode->path.parallel_safe = rel->consider_parallel &&
+ subpath->parallel_safe;
+ pathnode->path.parallel_workers = subpath->parallel_workers;
+ pathnode->path.pathkeys = pathkeys;
+
+ pathnode->subpath = subpath;
+
+ cost_incremental_sort(&pathnode->path,
+ root, pathkeys, presorted_keys,
+ subpath->startup_cost,
+ subpath->total_cost,
+ subpath->rows,
+ subpath->pathtarget->width,
+ 0.0, /* XXX comparison_cost shouldn't be 0? */
+ work_mem, limit_tuples);
+
+ sort->presortedCols = presorted_keys;
+
+ return pathnode;
+}
+
/*
* create_sort_path
* Creates a pathnode that represents performing an explicit sort.
diff --git a/src/backend/utils/misc/guc.c b/src/backend/utils/misc/guc.c
index 631f16f5fe..e90692287b 100644
--- a/src/backend/utils/misc/guc.c
+++ b/src/backend/utils/misc/guc.c
@@ -941,6 +941,15 @@ static struct config_bool ConfigureNamesBool[] =
true,
NULL, NULL, NULL
},
+ {
+ {"enable_incrementalsort", PGC_USERSET, QUERY_TUNING_METHOD,
+ gettext_noop("Enables the planner's use of incremental sort steps."),
+ NULL
+ },
+ &enable_incrementalsort,
+ true,
+ NULL, NULL, NULL
+ },
{
{"enable_hashagg", PGC_USERSET, QUERY_TUNING_METHOD,
gettext_noop("Enables the planner's use of hashed aggregation plans."),
diff --git a/src/backend/utils/sort/tuplesort.c b/src/backend/utils/sort/tuplesort.c
index 7b8e67899e..16098ed8eb 100644
--- a/src/backend/utils/sort/tuplesort.c
+++ b/src/backend/utils/sort/tuplesort.c
@@ -125,6 +125,15 @@
#define PARALLEL_SORT(state) ((state)->shared == NULL ? 0 : \
(state)->worker >= 0 ? 1 : 2)
+/*
+ * Initial size of memtuples array. We're trying to select this size so that
+ * array don't exceed ALLOCSET_SEPARATE_THRESHOLD and overhead of allocation
+ * be possible less. However, we don't cosider array sizes less than 1024
+ *
+ */
+#define INITIAL_MEMTUPSIZE Max(1024, \
+ ALLOCSET_SEPARATE_THRESHOLD / sizeof(SortTuple) + 1)
+
/* GUC variables */
#ifdef TRACE_SORT
bool trace_sort = false;
@@ -243,6 +252,14 @@ struct Tuplesortstate
int64 allowedMem; /* total memory allowed, in bytes */
int maxTapes; /* number of tapes (Knuth's T) */
int tapeRange; /* maxTapes-1 (Knuth's P) */
+ int64 maxSpace; /* maximum amount of space occupied among sort
+ of groups, either in-memory or on-disk */
+ bool maxSpaceOnDisk; /* true when maxSpace is value for on-disk
+ space, false when it's value for in-memory
+ space */
+ TupSortStatus maxSpaceStatus; /* sort status when maxSpace was reached */
+ MemoryContext maincontext; /* memory context for tuple sort metadata
+ that persist across multiple batches */
MemoryContext sortcontext; /* memory context holding most sort data */
MemoryContext tuplecontext; /* sub-context of sortcontext for tuple data */
LogicalTapeSet *tapeset; /* logtape.c object for tapes in a temp file */
@@ -647,6 +664,8 @@ static void worker_freeze_result_tape(Tuplesortstate *state);
static void worker_nomergeruns(Tuplesortstate *state);
static void leader_takeover_tapes(Tuplesortstate *state);
static void free_sort_tuple(Tuplesortstate *state, SortTuple *stup);
+static void tuplesort_free(Tuplesortstate *state);
+static void tuplesort_updatemax(Tuplesortstate *state);
/*
* Special versions of qsort just for SortTuple objects. qsort_tuple() sorts
@@ -682,6 +701,7 @@ tuplesort_begin_common(int workMem, SortCoordinate coordinate,
bool randomAccess)
{
Tuplesortstate *state;
+ MemoryContext maincontext;
MemoryContext sortcontext;
MemoryContext tuplecontext;
MemoryContext oldcontext;
@@ -691,13 +711,21 @@ tuplesort_begin_common(int workMem, SortCoordinate coordinate,
elog(ERROR, "random access disallowed under parallel sort");
/*
- * Create a working memory context for this sort operation. All data
- * needed by the sort will live inside this context.
+ * Memory context surviving tuplesort_reset. This memory context holds
+ * data which is useful to keep while sorting multiple similar batches.
*/
- sortcontext = AllocSetContextCreate(CurrentMemoryContext,
+ maincontext = AllocSetContextCreate(CurrentMemoryContext,
"TupleSort main",
ALLOCSET_DEFAULT_SIZES);
+ /*
+ * Create a working memory context for one sort operation. The content of
+ * this context is deleted by tuplesort_reset.
+ */
+ sortcontext = AllocSetContextCreate(maincontext,
+ "TupleSort sort",
+ ALLOCSET_DEFAULT_SIZES);
+
/*
* Caller tuple (e.g. IndexTuple) memory context.
*
@@ -715,7 +743,7 @@ tuplesort_begin_common(int workMem, SortCoordinate coordinate,
* Make the Tuplesortstate within the per-sort context. This way, we
* don't need a separate pfree() operation for it at shutdown.
*/
- oldcontext = MemoryContextSwitchTo(sortcontext);
+ oldcontext = MemoryContextSwitchTo(maincontext);
state = (Tuplesortstate *) palloc0(sizeof(Tuplesortstate));
@@ -740,6 +768,7 @@ tuplesort_begin_common(int workMem, SortCoordinate coordinate,
state->availMem = state->allowedMem;
state->sortcontext = sortcontext;
state->tuplecontext = tuplecontext;
+ state->maincontext = maincontext;
state->tapeset = NULL;
state->memtupcount = 0;
@@ -748,9 +777,7 @@ tuplesort_begin_common(int workMem, SortCoordinate coordinate,
* Initial size of array must be more than ALLOCSET_SEPARATE_THRESHOLD;
* see comments in grow_memtuples().
*/
- state->memtupsize = Max(1024,
- ALLOCSET_SEPARATE_THRESHOLD / sizeof(SortTuple) + 1);
-
+ state->memtupsize = INITIAL_MEMTUPSIZE;
state->growmemtuples = true;
state->slabAllocatorUsed = false;
state->memtuples = (SortTuple *) palloc(state->memtupsize * sizeof(SortTuple));
@@ -814,7 +841,7 @@ tuplesort_begin_heap(TupleDesc tupDesc,
MemoryContext oldcontext;
int i;
- oldcontext = MemoryContextSwitchTo(state->sortcontext);
+ oldcontext = MemoryContextSwitchTo(state->maincontext);
AssertArg(nkeys > 0);
@@ -890,7 +917,7 @@ tuplesort_begin_cluster(TupleDesc tupDesc,
Assert(indexRel->rd_rel->relam == BTREE_AM_OID);
- oldcontext = MemoryContextSwitchTo(state->sortcontext);
+ oldcontext = MemoryContextSwitchTo(state->maincontext);
#ifdef TRACE_SORT
if (trace_sort)
@@ -985,7 +1012,7 @@ tuplesort_begin_index_btree(Relation heapRel,
MemoryContext oldcontext;
int i;
- oldcontext = MemoryContextSwitchTo(state->sortcontext);
+ oldcontext = MemoryContextSwitchTo(state->maincontext);
#ifdef TRACE_SORT
if (trace_sort)
@@ -1063,7 +1090,7 @@ tuplesort_begin_index_hash(Relation heapRel,
randomAccess);
MemoryContext oldcontext;
- oldcontext = MemoryContextSwitchTo(state->sortcontext);
+ oldcontext = MemoryContextSwitchTo(state->maincontext);
#ifdef TRACE_SORT
if (trace_sort)
@@ -1106,7 +1133,7 @@ tuplesort_begin_datum(Oid datumType, Oid sortOperator, Oid sortCollation,
int16 typlen;
bool typbyval;
- oldcontext = MemoryContextSwitchTo(state->sortcontext);
+ oldcontext = MemoryContextSwitchTo(state->maincontext);
#ifdef TRACE_SORT
if (trace_sort)
@@ -1222,17 +1249,19 @@ tuplesort_set_bound(Tuplesortstate *state, int64 bound)
state->sortKeys->abbrev_full_comparator = NULL;
}
+bool
+tuplesort_used_bound(Tuplesortstate *state)
+{
+ return state->boundUsed;
+}
+
/*
- * tuplesort_end
- *
- * Release resources and clean up.
+ * tuplesort_free
*
- * NOTE: after calling this, any pointers returned by tuplesort_getXXX are
- * pointing to garbage. Be careful not to attempt to use or free such
- * pointers afterwards!
+ * Internal routine for freeing resources of tuplesort.
*/
-void
-tuplesort_end(Tuplesortstate *state)
+static void
+tuplesort_free(Tuplesortstate *state)
{
/* context swap probably not needed, but let's be safe */
MemoryContext oldcontext = MemoryContextSwitchTo(state->sortcontext);
@@ -1293,7 +1322,111 @@ tuplesort_end(Tuplesortstate *state)
* Free the per-sort memory context, thereby releasing all working memory,
* including the Tuplesortstate struct itself.
*/
- MemoryContextDelete(state->sortcontext);
+ MemoryContextReset(state->sortcontext);
+}
+
+/*
+ * tuplesort_end
+ *
+ * Release resources and clean up.
+ *
+ * NOTE: after calling this, any pointers returned by tuplesort_getXXX are
+ * pointing to garbage. Be careful not to attempt to use or free such
+ * pointers afterwards!
+ */
+void
+tuplesort_end(Tuplesortstate *state)
+{
+ tuplesort_free(state);
+ MemoryContextDelete(state->maincontext);
+}
+
+/*
+ * tuplesort_updatemax
+ *
+ * Update maximum resource usage statistics.
+ */
+static void
+tuplesort_updatemax(Tuplesortstate *state)
+{
+ int64 spaceUsed;
+ bool spaceUsedOnDisk;
+
+ /*
+ * Note: it might seem we should provide both memory and disk usage for a
+ * disk-based sort. However, the current code doesn't track memory space
+ * accurately once we have begun to return tuples to the caller (since we
+ * don't account for pfree's the caller is expected to do), so we cannot
+ * rely on availMem in a disk sort. This does not seem worth the overhead
+ * to fix. Is it worth creating an API for the memory context code to
+ * tell us how much is actually used in sortcontext?
+ */
+ if (state->tapeset)
+ {
+ spaceUsedOnDisk = true;
+ spaceUsed = LogicalTapeSetBlocks(state->tapeset) * BLCKSZ;
+ }
+ else
+ {
+ spaceUsedOnDisk = false;
+ spaceUsed = state->allowedMem - state->availMem;
+ }
+
+ /*
+ * Sort evicts data to the disk when it didn't manage to fit those data
+ * to the main memory. This is why we assume space used on the disk to
+ * be more important for tracking resource usage than space used in memory.
+ * Note that amount of space occupied by some tuple set on the disk might
+ * be less than amount of space occupied by the same tuple set in the
+ * memory due to more compact representation.
+ */
+ if ((spaceUsedOnDisk && !state->maxSpaceOnDisk) ||
+ (spaceUsedOnDisk == state->maxSpaceOnDisk && spaceUsed > state->maxSpace))
+ {
+ state->maxSpace = spaceUsed;
+ state->maxSpaceOnDisk = spaceUsedOnDisk;
+ state->maxSpaceStatus = state->status;
+ }
+}
+
+/*
+ * tuplesort_reset
+ *
+ * Reset the tuplesort. Reset all the data in the tuplesort, but leave the
+ * meta-information in. After tuplesort_reset, tuplesort is ready to start
+ * a new sort. It allows evade recreation of tuple sort (and save resources)
+ * when sorting multiple small batches.
+ */
+void
+tuplesort_reset(Tuplesortstate *state)
+{
+ tuplesort_updatemax(state);
+ tuplesort_free(state);
+
+ state->status = TSS_INITIAL;
+ state->memtupcount = 0;
+ state->boundUsed = false;
+ state->tapeset = NULL;
+ state->currentRun = 0;
+ state->result_tape = -1;
+ state->bounded = false;
+ state->availMem = state->allowedMem;
+ state->lastReturnedTuple = NULL;
+ state->slabAllocatorUsed = false;
+ state->slabMemoryBegin = NULL;
+ state->slabMemoryEnd = NULL;
+ state->slabFreeHead = NULL;
+ state->growmemtuples = true;
+
+ if (state->memtupsize < INITIAL_MEMTUPSIZE)
+ {
+ if (state->memtuples)
+ pfree(state->memtuples);
+ state->memtuples = (SortTuple *) palloc(INITIAL_MEMTUPSIZE * sizeof(SortTuple));
+ state->memtupsize = INITIAL_MEMTUPSIZE;
+ }
+
+ USEMEM(state, GetMemoryChunkSpace(state->memtuples));
}
/*
@@ -2590,8 +2723,7 @@ mergeruns(Tuplesortstate *state)
* Reset tuple memory. We've freed all the tuples that we previously
* allocated. We will use the slab allocator from now on.
*/
- MemoryContextDelete(state->tuplecontext);
- state->tuplecontext = NULL;
+ MemoryContextResetOnly(state->tuplecontext);
/*
* We no longer need a large memtuples array. (We will allocate a smaller
@@ -2641,7 +2773,8 @@ mergeruns(Tuplesortstate *state)
* from each input tape.
*/
state->memtupsize = numInputTapes;
- state->memtuples = (SortTuple *) palloc(numInputTapes * sizeof(SortTuple));
+ state->memtuples = (SortTuple *) MemoryContextAlloc(state->maincontext,
+ numInputTapes * sizeof(SortTuple));
USEMEM(state, GetMemoryChunkSpace(state->memtuples));
/*
@@ -3138,18 +3271,15 @@ tuplesort_get_stats(Tuplesortstate *state,
* to fix. Is it worth creating an API for the memory context code to
* tell us how much is actually used in sortcontext?
*/
- if (state->tapeset)
- {
+ tuplesort_updatemax(state);
+
+ if (state->maxSpaceOnDisk)
stats->spaceType = SORT_SPACE_TYPE_DISK;
- stats->spaceUsed = LogicalTapeSetBlocks(state->tapeset) * (BLCKSZ / 1024);
- }
else
- {
stats->spaceType = SORT_SPACE_TYPE_MEMORY;
- stats->spaceUsed = (state->allowedMem - state->availMem + 1023) / 1024;
- }
+ stats->spaceUsed = (state->maxSpace + 1023) / 1024;
- switch (state->status)
+ switch (state->maxSpaceStatus)
{
case TSS_SORTEDINMEM:
if (state->boundUsed)
diff --git a/src/include/executor/execdebug.h b/src/include/executor/execdebug.h
index c119fdf4fa..3e48593543 100644
--- a/src/include/executor/execdebug.h
+++ b/src/include/executor/execdebug.h
@@ -86,10 +86,12 @@
#define SO_nodeDisplay(l) nodeDisplay(l)
#define SO_printf(s) printf(s)
#define SO1_printf(s, p) printf(s, p)
+#define SO2_printf(s, p1, p2) printf(s, p1, p2)
#else
#define SO_nodeDisplay(l)
#define SO_printf(s)
#define SO1_printf(s, p)
+#define SO2_printf(s, p1, p2)
#endif /* EXEC_SORTDEBUG */
/* ----------------
diff --git a/src/include/executor/nodeIncrementalSort.h b/src/include/executor/nodeIncrementalSort.h
new file mode 100644
index 0000000000..90d7a81711
--- /dev/null
+++ b/src/include/executor/nodeIncrementalSort.h
@@ -0,0 +1,30 @@
+/*-------------------------------------------------------------------------
+ *
+ * nodeIncrementalSort.h
+ *
+ *
+ *
+ * Portions Copyright (c) 1996-2018, PostgreSQL Global Development Group
+ * Portions Copyright (c) 1994, Regents of the University of California
+ *
+ * src/include/executor/nodeIncrementalSort.h
+ *
+ *-------------------------------------------------------------------------
+ */
+#ifndef NODEINCREMENTALSORT_H
+#define NODEINCREMENTALSORT_H
+
+#include "access/parallel.h"
+#include "nodes/execnodes.h"
+
+extern IncrementalSortState *ExecInitIncrementalSort(IncrementalSort *node, EState *estate, int eflags);
+extern void ExecEndIncrementalSort(IncrementalSortState *node);
+extern void ExecReScanIncrementalSort(IncrementalSortState *node);
+
+/* parallel instrumentation support */
+extern void ExecIncrementalSortEstimate(IncrementalSortState *node, ParallelContext *pcxt);
+extern void ExecIncrementalSortInitializeDSM(IncrementalSortState *node, ParallelContext *pcxt);
+extern void ExecIncrementalSortInitializeWorker(IncrementalSortState *node, ParallelWorkerContext *pcxt);
+extern void ExecIncrementalSortRetrieveInstrumentation(IncrementalSortState *node);
+
+#endif /* NODEINCREMENTALSORT_H */
diff --git a/src/include/nodes/execnodes.h b/src/include/nodes/execnodes.h
index 99b9fa414f..42d5a46974 100644
--- a/src/include/nodes/execnodes.h
+++ b/src/include/nodes/execnodes.h
@@ -1924,6 +1924,20 @@ typedef struct MaterialState
Tuplestorestate *tuplestorestate;
} MaterialState;
+
+/* ----------------
+ * When performing sorting by multiple keys input dataset could be already
+ * presorted by some prefix of these keys. We call them "presorted keys".
+ * PresortedKeyData represents information about one such key.
+ * ----------------
+ */
+typedef struct PresortedKeyData
+{
+ FmgrInfo flinfo; /* comparison function info */
+ FunctionCallInfo fcinfo; /* comparison function call info */
+ OffsetNumber attno; /* attribute number in tuple */
+} PresortedKeyData;
+
/* ----------------
* Shared memory container for per-worker sort information
* ----------------
@@ -1952,6 +1966,60 @@ typedef struct SortState
SharedSortInfo *shared_info; /* one entry per worker */
} SortState;
+/* ----------------
+ * Shared memory container for per-worker incremental sort information
+ * ----------------
+ */
+typedef struct IncrementalSortInfo
+{
+ TuplesortInstrumentation fullsort_instrument;
+ int64 fullsort_group_count;
+ TuplesortInstrumentation prefixsort_instrument;
+ int64 prefixsort_group_count;
+} IncrementalSortInfo;
+
+typedef struct SharedIncrementalSortInfo
+{
+ int num_workers;
+ IncrementalSortInfo sinfo[FLEXIBLE_ARRAY_MEMBER];
+} SharedIncrementalSortInfo;
+
+/* ----------------
+ * IncrementalSortState information
+ * ----------------
+ */
+typedef enum
+{
+ INCSORT_LOADFULLSORT,
+ INCSORT_LOADPREFIXSORT,
+ INCSORT_READFULLSORT,
+ INCSORT_READPREFIXSORT,
+} IncrementalSortExecutionStatus;
+
+typedef struct IncrementalSortState
+{
+ ScanState ss; /* its first field is NodeTag */
+ bool bounded; /* is the result set bounded? */
+ int64 bound; /* if bounded, how many tuples are needed */
+ bool sort_Done; /* sort completed yet? */
+ bool finished; /* fetching tuples from outer node
+ is finished ? */
+ int64 bound_Done; /* value of bound we did the sort with */
+ IncrementalSortExecutionStatus execution_status;
+ int64 n_fullsort_remaining;
+ Tuplesortstate *fullsort_state; /* private state of tuplesort.c */
+ Tuplesortstate *prefixsort_state; /* private state of tuplesort.c */
+ /* the keys by which the input path is already sorted */
+ PresortedKeyData *presorted_keys;
+ int64 fullsort_group_count; /* number of groups with equal presorted keys */
+ int64 prefixsort_group_count; /* number of groups with equal presorted keys */
+ /* slot for pivot tuple defining values of presorted keys within group */
+ TupleTableSlot *group_pivot;
+ TupleTableSlot *transfer_tuple;
+ bool am_worker; /* are we a worker? */
+ SharedIncrementalSortInfo *shared_info; /* one entry per worker */
+} IncrementalSortState;
+
/* ---------------------
* GroupState information
* ---------------------
diff --git a/src/include/nodes/nodes.h b/src/include/nodes/nodes.h
index 4e2fb39105..0500a3199f 100644
--- a/src/include/nodes/nodes.h
+++ b/src/include/nodes/nodes.h
@@ -74,6 +74,7 @@ typedef enum NodeTag
T_HashJoin,
T_Material,
T_Sort,
+ T_IncrementalSort,
T_Group,
T_Agg,
T_WindowAgg,
@@ -130,6 +131,7 @@ typedef enum NodeTag
T_HashJoinState,
T_MaterialState,
T_SortState,
+ T_IncrementalSortState,
T_GroupState,
T_AggState,
T_WindowAggState,
@@ -245,6 +247,7 @@ typedef enum NodeTag
T_ProjectionPath,
T_ProjectSetPath,
T_SortPath,
+ T_IncrementalSortPath,
T_GroupPath,
T_UpperUniquePath,
T_AggPath,
diff --git a/src/include/nodes/pathnodes.h b/src/include/nodes/pathnodes.h
index 441e64eca9..9d45feb37b 100644
--- a/src/include/nodes/pathnodes.h
+++ b/src/include/nodes/pathnodes.h
@@ -1614,6 +1614,15 @@ typedef struct SortPath
Path *subpath; /* path representing input source */
} SortPath;
+/*
+ * IncrementalSortPath
+ */
+typedef struct IncrementalSortPath
+{
+ SortPath spath;
+ int presortedCols; /* number of presorted columns */
+} IncrementalSortPath;
+
/*
* GroupPath represents grouping (of presorted input)
*
diff --git a/src/include/nodes/plannodes.h b/src/include/nodes/plannodes.h
index 70f8b8e22b..f9baee6495 100644
--- a/src/include/nodes/plannodes.h
+++ b/src/include/nodes/plannodes.h
@@ -762,6 +762,17 @@ typedef struct Sort
bool *nullsFirst; /* NULLS FIRST/LAST directions */
} Sort;
+
+/* ----------------
+ * incremental sort node
+ * ----------------
+ */
+typedef struct IncrementalSort
+{
+ Sort sort;
+ int presortedCols; /* number of presorted columns */
+} IncrementalSort;
+
/* ---------------
* group node -
* Used for queries with GROUP BY (but no aggregates) specified.
diff --git a/src/include/optimizer/cost.h b/src/include/optimizer/cost.h
index b3d0b4f6fb..b9d7a77e65 100644
--- a/src/include/optimizer/cost.h
+++ b/src/include/optimizer/cost.h
@@ -53,6 +53,7 @@ extern PGDLLIMPORT bool enable_indexonlyscan;
extern PGDLLIMPORT bool enable_bitmapscan;
extern PGDLLIMPORT bool enable_tidscan;
extern PGDLLIMPORT bool enable_sort;
+extern PGDLLIMPORT bool enable_incrementalsort;
extern PGDLLIMPORT bool enable_hashagg;
extern PGDLLIMPORT bool enable_nestloop;
extern PGDLLIMPORT bool enable_material;
@@ -101,6 +102,15 @@ extern void cost_sort(Path *path, PlannerInfo *root,
List *pathkeys, Cost input_cost, double tuples, int width,
Cost comparison_cost, int sort_mem,
double limit_tuples);
+extern void cost_full_sort(Cost *startup_cost, Cost *run_cost,
+ Cost input_total_cost, double tuples, int width,
+ Cost comparison_cost, int sort_mem,
+ double limit_tuples);
+extern void cost_incremental_sort(Path *path,
+ PlannerInfo *root, List *pathkeys, int presorted_keys,
+ Cost input_startup_cost, Cost input_total_cost,
+ double input_tuples, int width, Cost comparison_cost, int sort_mem,
+ double limit_tuples);
extern void cost_append(AppendPath *path);
extern void cost_merge_append(Path *path, PlannerInfo *root,
List *pathkeys, int n_streams,
diff --git a/src/include/optimizer/pathnode.h b/src/include/optimizer/pathnode.h
index 182ffeef4b..61c3940921 100644
--- a/src/include/optimizer/pathnode.h
+++ b/src/include/optimizer/pathnode.h
@@ -184,6 +184,12 @@ extern ProjectSetPath *create_set_projection_path(PlannerInfo *root,
RelOptInfo *rel,
Path *subpath,
PathTarget *target);
+extern SortPath *create_incremental_sort_path(PlannerInfo *root,
+ RelOptInfo *rel,
+ Path *subpath,
+ List *pathkeys,
+ int presorted_keys,
+ double limit_tuples);
extern SortPath *create_sort_path(PlannerInfo *root,
RelOptInfo *rel,
Path *subpath,
diff --git a/src/include/optimizer/paths.h b/src/include/optimizer/paths.h
index 7345137d1d..e7a40cec3f 100644
--- a/src/include/optimizer/paths.h
+++ b/src/include/optimizer/paths.h
@@ -183,6 +183,8 @@ typedef enum
extern PathKeysComparison compare_pathkeys(List *keys1, List *keys2);
extern bool pathkeys_contained_in(List *keys1, List *keys2);
+extern bool pathkeys_common_contained_in(List *keys1, List *keys2, int *n_common);
+extern int pathkeys_common(List *keys1, List *keys2);
extern Path *get_cheapest_path_for_pathkeys(List *paths, List *pathkeys,
Relids required_outer,
CostSelector cost_criterion,
diff --git a/src/include/utils/tuplesort.h b/src/include/utils/tuplesort.h
index 4521de18e1..65a73af214 100644
--- a/src/include/utils/tuplesort.h
+++ b/src/include/utils/tuplesort.h
@@ -216,6 +216,7 @@ extern Tuplesortstate *tuplesort_begin_datum(Oid datumType,
bool randomAccess);
extern void tuplesort_set_bound(Tuplesortstate *state, int64 bound);
+extern bool tuplesort_used_bound(Tuplesortstate *state);
extern void tuplesort_puttupleslot(Tuplesortstate *state,
TupleTableSlot *slot);
@@ -240,6 +241,8 @@ extern bool tuplesort_skiptuples(Tuplesortstate *state, int64 ntuples,
extern void tuplesort_end(Tuplesortstate *state);
+extern void tuplesort_reset(Tuplesortstate *state);
+
extern void tuplesort_get_stats(Tuplesortstate *state,
TuplesortInstrumentation *stats);
extern const char *tuplesort_method_name(TuplesortMethod m);
diff --git a/src/test/isolation/expected/drop-index-concurrently-1.out b/src/test/isolation/expected/drop-index-concurrently-1.out
index 75dff56bc4..8e6adb66bb 100644
--- a/src/test/isolation/expected/drop-index-concurrently-1.out
+++ b/src/test/isolation/expected/drop-index-concurrently-1.out
@@ -21,7 +21,7 @@ QUERY PLAN
Sort
Sort Key: id, data
- -> Seq Scan on test_dc
+ -> Index Scan using test_dc_pkey on test_dc
Filter: ((data)::text = '34'::text)
step select2: SELECT * FROM test_dc WHERE data=34 ORDER BY id,data;
id data
diff --git a/src/test/regress/expected/incremental_sort.out b/src/test/regress/expected/incremental_sort.out
new file mode 100644
index 0000000000..3a58efdf91
--- /dev/null
+++ b/src/test/regress/expected/incremental_sort.out
@@ -0,0 +1,1160 @@
+-- When we have to sort the entire table, incremental sort will
+-- be slower than plain sort, so it should not be used.
+explain (costs off)
+select * from (select * from tenk1 order by four) t order by four, ten;
+ QUERY PLAN
+-----------------------------------
+ Sort
+ Sort Key: tenk1.four, tenk1.ten
+ -> Sort
+ Sort Key: tenk1.four
+ -> Seq Scan on tenk1
+(5 rows)
+
+-- When there is a LIMIT clause, incremental sort is beneficial because
+-- it only has to sort some of the groups, and not the entire table.
+explain (costs off)
+select * from (select * from tenk1 order by four) t order by four, ten
+limit 1;
+ QUERY PLAN
+-----------------------------------------
+ Limit
+ -> Incremental Sort
+ Sort Key: tenk1.four, tenk1.ten
+ Presorted Key: tenk1.four
+ -> Sort
+ Sort Key: tenk1.four
+ -> Seq Scan on tenk1
+(7 rows)
+
+-- When work_mem is not enough to sort the entire table, incremental sort
+-- may be faster if individual groups still fit into work_mem.
+set work_mem to '2MB';
+explain (costs off)
+select * from (select * from tenk1 order by four) t order by four, ten;
+ QUERY PLAN
+-----------------------------------
+ Incremental Sort
+ Sort Key: tenk1.four, tenk1.ten
+ Presorted Key: tenk1.four
+ -> Sort
+ Sort Key: tenk1.four
+ -> Seq Scan on tenk1
+(6 rows)
+
+reset work_mem;
+-- TODO if an analyze happens here the plans might change; should we
+-- solve by inserting extra rows or by adding a GUC that would somehow
+-- forcing the time of plan we expect.
+create table t(a integer, b integer);
+-- A single large group tested around each mode transition point.
+insert into t(a, b) select 1, i from generate_series(1, 100) n(i);
+explain (costs off) select * from (select * from t order by a) s order by a, b limit 31;
+ QUERY PLAN
+---------------------------------
+ Limit
+ -> Incremental Sort
+ Sort Key: t.a, t.b
+ Presorted Key: t.a
+ -> Sort
+ Sort Key: t.a
+ -> Seq Scan on t
+(7 rows)
+
+select * from (select * from t order by a) s order by a, b limit 31;
+ a | b
+---+----
+ 1 | 1
+ 1 | 2
+ 1 | 3
+ 1 | 4
+ 1 | 5
+ 1 | 6
+ 1 | 7
+ 1 | 8
+ 1 | 9
+ 1 | 10
+ 1 | 11
+ 1 | 12
+ 1 | 13
+ 1 | 14
+ 1 | 15
+ 1 | 16
+ 1 | 17
+ 1 | 18
+ 1 | 19
+ 1 | 20
+ 1 | 21
+ 1 | 22
+ 1 | 23
+ 1 | 24
+ 1 | 25
+ 1 | 26
+ 1 | 27
+ 1 | 28
+ 1 | 29
+ 1 | 30
+ 1 | 31
+(31 rows)
+
+explain (costs off) select * from (select * from t order by a) s order by a, b limit 32;
+ QUERY PLAN
+---------------------------------
+ Limit
+ -> Incremental Sort
+ Sort Key: t.a, t.b
+ Presorted Key: t.a
+ -> Sort
+ Sort Key: t.a
+ -> Seq Scan on t
+(7 rows)
+
+select * from (select * from t order by a) s order by a, b limit 32;
+ a | b
+---+----
+ 1 | 1
+ 1 | 2
+ 1 | 3
+ 1 | 4
+ 1 | 5
+ 1 | 6
+ 1 | 7
+ 1 | 8
+ 1 | 9
+ 1 | 10
+ 1 | 11
+ 1 | 12
+ 1 | 13
+ 1 | 14
+ 1 | 15
+ 1 | 16
+ 1 | 17
+ 1 | 18
+ 1 | 19
+ 1 | 20
+ 1 | 21
+ 1 | 22
+ 1 | 23
+ 1 | 24
+ 1 | 25
+ 1 | 26
+ 1 | 27
+ 1 | 28
+ 1 | 29
+ 1 | 30
+ 1 | 31
+ 1 | 32
+(32 rows)
+
+explain (costs off) select * from (select * from t order by a) s order by a, b limit 33;
+ QUERY PLAN
+---------------------------------
+ Limit
+ -> Incremental Sort
+ Sort Key: t.a, t.b
+ Presorted Key: t.a
+ -> Sort
+ Sort Key: t.a
+ -> Seq Scan on t
+(7 rows)
+
+select * from (select * from t order by a) s order by a, b limit 33;
+ a | b
+---+----
+ 1 | 1
+ 1 | 2
+ 1 | 3
+ 1 | 4
+ 1 | 5
+ 1 | 6
+ 1 | 7
+ 1 | 8
+ 1 | 9
+ 1 | 10
+ 1 | 11
+ 1 | 12
+ 1 | 13
+ 1 | 14
+ 1 | 15
+ 1 | 16
+ 1 | 17
+ 1 | 18
+ 1 | 19
+ 1 | 20
+ 1 | 21
+ 1 | 22
+ 1 | 23
+ 1 | 24
+ 1 | 25
+ 1 | 26
+ 1 | 27
+ 1 | 28
+ 1 | 29
+ 1 | 30
+ 1 | 31
+ 1 | 32
+ 1 | 33
+(33 rows)
+
+explain (costs off) select * from (select * from t order by a) s order by a, b limit 65;
+ QUERY PLAN
+---------------------------------
+ Limit
+ -> Incremental Sort
+ Sort Key: t.a, t.b
+ Presorted Key: t.a
+ -> Sort
+ Sort Key: t.a
+ -> Seq Scan on t
+(7 rows)
+
+select * from (select * from t order by a) s order by a, b limit 65;
+ a | b
+---+----
+ 1 | 1
+ 1 | 2
+ 1 | 3
+ 1 | 4
+ 1 | 5
+ 1 | 6
+ 1 | 7
+ 1 | 8
+ 1 | 9
+ 1 | 10
+ 1 | 11
+ 1 | 12
+ 1 | 13
+ 1 | 14
+ 1 | 15
+ 1 | 16
+ 1 | 17
+ 1 | 18
+ 1 | 19
+ 1 | 20
+ 1 | 21
+ 1 | 22
+ 1 | 23
+ 1 | 24
+ 1 | 25
+ 1 | 26
+ 1 | 27
+ 1 | 28
+ 1 | 29
+ 1 | 30
+ 1 | 31
+ 1 | 32
+ 1 | 33
+ 1 | 34
+ 1 | 35
+ 1 | 36
+ 1 | 37
+ 1 | 38
+ 1 | 39
+ 1 | 40
+ 1 | 41
+ 1 | 42
+ 1 | 43
+ 1 | 44
+ 1 | 45
+ 1 | 46
+ 1 | 47
+ 1 | 48
+ 1 | 49
+ 1 | 50
+ 1 | 51
+ 1 | 52
+ 1 | 53
+ 1 | 54
+ 1 | 55
+ 1 | 56
+ 1 | 57
+ 1 | 58
+ 1 | 59
+ 1 | 60
+ 1 | 61
+ 1 | 62
+ 1 | 63
+ 1 | 64
+ 1 | 65
+(65 rows)
+
+explain (costs off) select * from (select * from t order by a) s order by a, b limit 66;
+ QUERY PLAN
+---------------------------------
+ Limit
+ -> Incremental Sort
+ Sort Key: t.a, t.b
+ Presorted Key: t.a
+ -> Sort
+ Sort Key: t.a
+ -> Seq Scan on t
+(7 rows)
+
+select * from (select * from t order by a) s order by a, b limit 66;
+ a | b
+---+----
+ 1 | 1
+ 1 | 2
+ 1 | 3
+ 1 | 4
+ 1 | 5
+ 1 | 6
+ 1 | 7
+ 1 | 8
+ 1 | 9
+ 1 | 10
+ 1 | 11
+ 1 | 12
+ 1 | 13
+ 1 | 14
+ 1 | 15
+ 1 | 16
+ 1 | 17
+ 1 | 18
+ 1 | 19
+ 1 | 20
+ 1 | 21
+ 1 | 22
+ 1 | 23
+ 1 | 24
+ 1 | 25
+ 1 | 26
+ 1 | 27
+ 1 | 28
+ 1 | 29
+ 1 | 30
+ 1 | 31
+ 1 | 32
+ 1 | 33
+ 1 | 34
+ 1 | 35
+ 1 | 36
+ 1 | 37
+ 1 | 38
+ 1 | 39
+ 1 | 40
+ 1 | 41
+ 1 | 42
+ 1 | 43
+ 1 | 44
+ 1 | 45
+ 1 | 46
+ 1 | 47
+ 1 | 48
+ 1 | 49
+ 1 | 50
+ 1 | 51
+ 1 | 52
+ 1 | 53
+ 1 | 54
+ 1 | 55
+ 1 | 56
+ 1 | 57
+ 1 | 58
+ 1 | 59
+ 1 | 60
+ 1 | 61
+ 1 | 62
+ 1 | 63
+ 1 | 64
+ 1 | 65
+ 1 | 66
+(66 rows)
+
+delete from t;
+-- An initial large group followed by a small group.
+insert into t(a, b) select (case when i < 50 then 1 else 2 end), i from generate_series(1, 100) n(i);
+explain (costs off) select * from (select * from t order by a) s order by a, b limit 55;
+ QUERY PLAN
+---------------------------------
+ Limit
+ -> Incremental Sort
+ Sort Key: t.a, t.b
+ Presorted Key: t.a
+ -> Sort
+ Sort Key: t.a
+ -> Seq Scan on t
+(7 rows)
+
+select * from (select * from t order by a) s order by a, b limit 55;
+ a | b
+---+----
+ 1 | 1
+ 1 | 2
+ 1 | 3
+ 1 | 4
+ 1 | 5
+ 1 | 6
+ 1 | 7
+ 1 | 8
+ 1 | 9
+ 1 | 10
+ 1 | 11
+ 1 | 12
+ 1 | 13
+ 1 | 14
+ 1 | 15
+ 1 | 16
+ 1 | 17
+ 1 | 18
+ 1 | 19
+ 1 | 20
+ 1 | 21
+ 1 | 22
+ 1 | 23
+ 1 | 24
+ 1 | 25
+ 1 | 26
+ 1 | 27
+ 1 | 28
+ 1 | 29
+ 1 | 30
+ 1 | 31
+ 1 | 32
+ 1 | 33
+ 1 | 34
+ 1 | 35
+ 1 | 36
+ 1 | 37
+ 1 | 38
+ 1 | 39
+ 1 | 40
+ 1 | 41
+ 1 | 42
+ 1 | 43
+ 1 | 44
+ 1 | 45
+ 1 | 46
+ 1 | 47
+ 1 | 48
+ 1 | 49
+ 2 | 50
+ 2 | 51
+ 2 | 52
+ 2 | 53
+ 2 | 54
+ 2 | 55
+(55 rows)
+
+delete from t;
+-- An initial small group followed by a large group.
+insert into t(a, b) select (case when i < 5 then i else 9 end), i from generate_series(1, 100) n(i);
+explain (costs off) select * from (select * from t order by a) s order by a, b limit 70;
+ QUERY PLAN
+---------------------------------
+ Limit
+ -> Incremental Sort
+ Sort Key: t.a, t.b
+ Presorted Key: t.a
+ -> Sort
+ Sort Key: t.a
+ -> Seq Scan on t
+(7 rows)
+
+select * from (select * from t order by a) s order by a, b limit 70;
+ a | b
+---+----
+ 1 | 1
+ 2 | 2
+ 3 | 3
+ 4 | 4
+ 9 | 5
+ 9 | 6
+ 9 | 7
+ 9 | 8
+ 9 | 9
+ 9 | 10
+ 9 | 11
+ 9 | 12
+ 9 | 13
+ 9 | 14
+ 9 | 15
+ 9 | 16
+ 9 | 17
+ 9 | 18
+ 9 | 19
+ 9 | 20
+ 9 | 21
+ 9 | 22
+ 9 | 23
+ 9 | 24
+ 9 | 25
+ 9 | 26
+ 9 | 27
+ 9 | 28
+ 9 | 29
+ 9 | 30
+ 9 | 31
+ 9 | 32
+ 9 | 33
+ 9 | 34
+ 9 | 35
+ 9 | 36
+ 9 | 37
+ 9 | 38
+ 9 | 39
+ 9 | 40
+ 9 | 41
+ 9 | 42
+ 9 | 43
+ 9 | 44
+ 9 | 45
+ 9 | 46
+ 9 | 47
+ 9 | 48
+ 9 | 49
+ 9 | 50
+ 9 | 51
+ 9 | 52
+ 9 | 53
+ 9 | 54
+ 9 | 55
+ 9 | 56
+ 9 | 57
+ 9 | 58
+ 9 | 59
+ 9 | 60
+ 9 | 61
+ 9 | 62
+ 9 | 63
+ 9 | 64
+ 9 | 65
+ 9 | 66
+ 9 | 67
+ 9 | 68
+ 9 | 69
+ 9 | 70
+(70 rows)
+
+delete from t;
+-- Small groups of 10 tuples each tested around each mode transition point.
+insert into t(a, b) select i / 10, i from generate_series(1, 70) n(i);
+explain (costs off) select * from (select * from t order by a) s order by a, b limit 31;
+ QUERY PLAN
+---------------------------------
+ Limit
+ -> Incremental Sort
+ Sort Key: t.a, t.b
+ Presorted Key: t.a
+ -> Sort
+ Sort Key: t.a
+ -> Seq Scan on t
+(7 rows)
+
+select * from (select * from t order by a) s order by a, b limit 31;
+ a | b
+---+----
+ 0 | 1
+ 0 | 2
+ 0 | 3
+ 0 | 4
+ 0 | 5
+ 0 | 6
+ 0 | 7
+ 0 | 8
+ 0 | 9
+ 1 | 10
+ 1 | 11
+ 1 | 12
+ 1 | 13
+ 1 | 14
+ 1 | 15
+ 1 | 16
+ 1 | 17
+ 1 | 18
+ 1 | 19
+ 2 | 20
+ 2 | 21
+ 2 | 22
+ 2 | 23
+ 2 | 24
+ 2 | 25
+ 2 | 26
+ 2 | 27
+ 2 | 28
+ 2 | 29
+ 3 | 30
+ 3 | 31
+(31 rows)
+
+explain (costs off) select * from (select * from t order by a) s order by a, b limit 32;
+ QUERY PLAN
+---------------------------------
+ Limit
+ -> Incremental Sort
+ Sort Key: t.a, t.b
+ Presorted Key: t.a
+ -> Sort
+ Sort Key: t.a
+ -> Seq Scan on t
+(7 rows)
+
+select * from (select * from t order by a) s order by a, b limit 32;
+ a | b
+---+----
+ 0 | 1
+ 0 | 2
+ 0 | 3
+ 0 | 4
+ 0 | 5
+ 0 | 6
+ 0 | 7
+ 0 | 8
+ 0 | 9
+ 1 | 10
+ 1 | 11
+ 1 | 12
+ 1 | 13
+ 1 | 14
+ 1 | 15
+ 1 | 16
+ 1 | 17
+ 1 | 18
+ 1 | 19
+ 2 | 20
+ 2 | 21
+ 2 | 22
+ 2 | 23
+ 2 | 24
+ 2 | 25
+ 2 | 26
+ 2 | 27
+ 2 | 28
+ 2 | 29
+ 3 | 30
+ 3 | 31
+ 3 | 32
+(32 rows)
+
+explain (costs off) select * from (select * from t order by a) s order by a, b limit 33;
+ QUERY PLAN
+---------------------------------
+ Limit
+ -> Incremental Sort
+ Sort Key: t.a, t.b
+ Presorted Key: t.a
+ -> Sort
+ Sort Key: t.a
+ -> Seq Scan on t
+(7 rows)
+
+select * from (select * from t order by a) s order by a, b limit 33;
+ a | b
+---+----
+ 0 | 1
+ 0 | 2
+ 0 | 3
+ 0 | 4
+ 0 | 5
+ 0 | 6
+ 0 | 7
+ 0 | 8
+ 0 | 9
+ 1 | 10
+ 1 | 11
+ 1 | 12
+ 1 | 13
+ 1 | 14
+ 1 | 15
+ 1 | 16
+ 1 | 17
+ 1 | 18
+ 1 | 19
+ 2 | 20
+ 2 | 21
+ 2 | 22
+ 2 | 23
+ 2 | 24
+ 2 | 25
+ 2 | 26
+ 2 | 27
+ 2 | 28
+ 2 | 29
+ 3 | 30
+ 3 | 31
+ 3 | 32
+ 3 | 33
+(33 rows)
+
+explain (costs off) select * from (select * from t order by a) s order by a, b limit 65;
+ QUERY PLAN
+---------------------------------
+ Limit
+ -> Incremental Sort
+ Sort Key: t.a, t.b
+ Presorted Key: t.a
+ -> Sort
+ Sort Key: t.a
+ -> Seq Scan on t
+(7 rows)
+
+select * from (select * from t order by a) s order by a, b limit 65;
+ a | b
+---+----
+ 0 | 1
+ 0 | 2
+ 0 | 3
+ 0 | 4
+ 0 | 5
+ 0 | 6
+ 0 | 7
+ 0 | 8
+ 0 | 9
+ 1 | 10
+ 1 | 11
+ 1 | 12
+ 1 | 13
+ 1 | 14
+ 1 | 15
+ 1 | 16
+ 1 | 17
+ 1 | 18
+ 1 | 19
+ 2 | 20
+ 2 | 21
+ 2 | 22
+ 2 | 23
+ 2 | 24
+ 2 | 25
+ 2 | 26
+ 2 | 27
+ 2 | 28
+ 2 | 29
+ 3 | 30
+ 3 | 31
+ 3 | 32
+ 3 | 33
+ 3 | 34
+ 3 | 35
+ 3 | 36
+ 3 | 37
+ 3 | 38
+ 3 | 39
+ 4 | 40
+ 4 | 41
+ 4 | 42
+ 4 | 43
+ 4 | 44
+ 4 | 45
+ 4 | 46
+ 4 | 47
+ 4 | 48
+ 4 | 49
+ 5 | 50
+ 5 | 51
+ 5 | 52
+ 5 | 53
+ 5 | 54
+ 5 | 55
+ 5 | 56
+ 5 | 57
+ 5 | 58
+ 5 | 59
+ 6 | 60
+ 6 | 61
+ 6 | 62
+ 6 | 63
+ 6 | 64
+ 6 | 65
+(65 rows)
+
+explain (costs off) select * from (select * from t order by a) s order by a, b limit 66;
+ QUERY PLAN
+---------------------------------
+ Limit
+ -> Incremental Sort
+ Sort Key: t.a, t.b
+ Presorted Key: t.a
+ -> Sort
+ Sort Key: t.a
+ -> Seq Scan on t
+(7 rows)
+
+select * from (select * from t order by a) s order by a, b limit 66;
+ a | b
+---+----
+ 0 | 1
+ 0 | 2
+ 0 | 3
+ 0 | 4
+ 0 | 5
+ 0 | 6
+ 0 | 7
+ 0 | 8
+ 0 | 9
+ 1 | 10
+ 1 | 11
+ 1 | 12
+ 1 | 13
+ 1 | 14
+ 1 | 15
+ 1 | 16
+ 1 | 17
+ 1 | 18
+ 1 | 19
+ 2 | 20
+ 2 | 21
+ 2 | 22
+ 2 | 23
+ 2 | 24
+ 2 | 25
+ 2 | 26
+ 2 | 27
+ 2 | 28
+ 2 | 29
+ 3 | 30
+ 3 | 31
+ 3 | 32
+ 3 | 33
+ 3 | 34
+ 3 | 35
+ 3 | 36
+ 3 | 37
+ 3 | 38
+ 3 | 39
+ 4 | 40
+ 4 | 41
+ 4 | 42
+ 4 | 43
+ 4 | 44
+ 4 | 45
+ 4 | 46
+ 4 | 47
+ 4 | 48
+ 4 | 49
+ 5 | 50
+ 5 | 51
+ 5 | 52
+ 5 | 53
+ 5 | 54
+ 5 | 55
+ 5 | 56
+ 5 | 57
+ 5 | 58
+ 5 | 59
+ 6 | 60
+ 6 | 61
+ 6 | 62
+ 6 | 63
+ 6 | 64
+ 6 | 65
+ 6 | 66
+(66 rows)
+
+delete from t;
+-- Small groups of only 1 tuple each tested around each mode transition point.
+insert into t(a, b) select i, i from generate_series(1, 70) n(i);
+explain (costs off) select * from (select * from t order by a) s order by a, b limit 31;
+ QUERY PLAN
+---------------------------------
+ Limit
+ -> Incremental Sort
+ Sort Key: t.a, t.b
+ Presorted Key: t.a
+ -> Sort
+ Sort Key: t.a
+ -> Seq Scan on t
+(7 rows)
+
+select * from (select * from t order by a) s order by a, b limit 31;
+ a | b
+----+----
+ 1 | 1
+ 2 | 2
+ 3 | 3
+ 4 | 4
+ 5 | 5
+ 6 | 6
+ 7 | 7
+ 8 | 8
+ 9 | 9
+ 10 | 10
+ 11 | 11
+ 12 | 12
+ 13 | 13
+ 14 | 14
+ 15 | 15
+ 16 | 16
+ 17 | 17
+ 18 | 18
+ 19 | 19
+ 20 | 20
+ 21 | 21
+ 22 | 22
+ 23 | 23
+ 24 | 24
+ 25 | 25
+ 26 | 26
+ 27 | 27
+ 28 | 28
+ 29 | 29
+ 30 | 30
+ 31 | 31
+(31 rows)
+
+explain (costs off) select * from (select * from t order by a) s order by a, b limit 32;
+ QUERY PLAN
+---------------------------------
+ Limit
+ -> Incremental Sort
+ Sort Key: t.a, t.b
+ Presorted Key: t.a
+ -> Sort
+ Sort Key: t.a
+ -> Seq Scan on t
+(7 rows)
+
+select * from (select * from t order by a) s order by a, b limit 32;
+ a | b
+----+----
+ 1 | 1
+ 2 | 2
+ 3 | 3
+ 4 | 4
+ 5 | 5
+ 6 | 6
+ 7 | 7
+ 8 | 8
+ 9 | 9
+ 10 | 10
+ 11 | 11
+ 12 | 12
+ 13 | 13
+ 14 | 14
+ 15 | 15
+ 16 | 16
+ 17 | 17
+ 18 | 18
+ 19 | 19
+ 20 | 20
+ 21 | 21
+ 22 | 22
+ 23 | 23
+ 24 | 24
+ 25 | 25
+ 26 | 26
+ 27 | 27
+ 28 | 28
+ 29 | 29
+ 30 | 30
+ 31 | 31
+ 32 | 32
+(32 rows)
+
+explain (costs off) select * from (select * from t order by a) s order by a, b limit 33;
+ QUERY PLAN
+---------------------------------
+ Limit
+ -> Incremental Sort
+ Sort Key: t.a, t.b
+ Presorted Key: t.a
+ -> Sort
+ Sort Key: t.a
+ -> Seq Scan on t
+(7 rows)
+
+select * from (select * from t order by a) s order by a, b limit 33;
+ a | b
+----+----
+ 1 | 1
+ 2 | 2
+ 3 | 3
+ 4 | 4
+ 5 | 5
+ 6 | 6
+ 7 | 7
+ 8 | 8
+ 9 | 9
+ 10 | 10
+ 11 | 11
+ 12 | 12
+ 13 | 13
+ 14 | 14
+ 15 | 15
+ 16 | 16
+ 17 | 17
+ 18 | 18
+ 19 | 19
+ 20 | 20
+ 21 | 21
+ 22 | 22
+ 23 | 23
+ 24 | 24
+ 25 | 25
+ 26 | 26
+ 27 | 27
+ 28 | 28
+ 29 | 29
+ 30 | 30
+ 31 | 31
+ 32 | 32
+ 33 | 33
+(33 rows)
+
+explain (costs off) select * from (select * from t order by a) s order by a, b limit 65;
+ QUERY PLAN
+---------------------------------
+ Limit
+ -> Incremental Sort
+ Sort Key: t.a, t.b
+ Presorted Key: t.a
+ -> Sort
+ Sort Key: t.a
+ -> Seq Scan on t
+(7 rows)
+
+select * from (select * from t order by a) s order by a, b limit 65;
+ a | b
+----+----
+ 1 | 1
+ 2 | 2
+ 3 | 3
+ 4 | 4
+ 5 | 5
+ 6 | 6
+ 7 | 7
+ 8 | 8
+ 9 | 9
+ 10 | 10
+ 11 | 11
+ 12 | 12
+ 13 | 13
+ 14 | 14
+ 15 | 15
+ 16 | 16
+ 17 | 17
+ 18 | 18
+ 19 | 19
+ 20 | 20
+ 21 | 21
+ 22 | 22
+ 23 | 23
+ 24 | 24
+ 25 | 25
+ 26 | 26
+ 27 | 27
+ 28 | 28
+ 29 | 29
+ 30 | 30
+ 31 | 31
+ 32 | 32
+ 33 | 33
+ 34 | 34
+ 35 | 35
+ 36 | 36
+ 37 | 37
+ 38 | 38
+ 39 | 39
+ 40 | 40
+ 41 | 41
+ 42 | 42
+ 43 | 43
+ 44 | 44
+ 45 | 45
+ 46 | 46
+ 47 | 47
+ 48 | 48
+ 49 | 49
+ 50 | 50
+ 51 | 51
+ 52 | 52
+ 53 | 53
+ 54 | 54
+ 55 | 55
+ 56 | 56
+ 57 | 57
+ 58 | 58
+ 59 | 59
+ 60 | 60
+ 61 | 61
+ 62 | 62
+ 63 | 63
+ 64 | 64
+ 65 | 65
+(65 rows)
+
+explain (costs off) select * from (select * from t order by a) s order by a, b limit 66;
+ QUERY PLAN
+---------------------------------
+ Limit
+ -> Incremental Sort
+ Sort Key: t.a, t.b
+ Presorted Key: t.a
+ -> Sort
+ Sort Key: t.a
+ -> Seq Scan on t
+(7 rows)
+
+select * from (select * from t order by a) s order by a, b limit 66;
+ a | b
+----+----
+ 1 | 1
+ 2 | 2
+ 3 | 3
+ 4 | 4
+ 5 | 5
+ 6 | 6
+ 7 | 7
+ 8 | 8
+ 9 | 9
+ 10 | 10
+ 11 | 11
+ 12 | 12
+ 13 | 13
+ 14 | 14
+ 15 | 15
+ 16 | 16
+ 17 | 17
+ 18 | 18
+ 19 | 19
+ 20 | 20
+ 21 | 21
+ 22 | 22
+ 23 | 23
+ 24 | 24
+ 25 | 25
+ 26 | 26
+ 27 | 27
+ 28 | 28
+ 29 | 29
+ 30 | 30
+ 31 | 31
+ 32 | 32
+ 33 | 33
+ 34 | 34
+ 35 | 35
+ 36 | 36
+ 37 | 37
+ 38 | 38
+ 39 | 39
+ 40 | 40
+ 41 | 41
+ 42 | 42
+ 43 | 43
+ 44 | 44
+ 45 | 45
+ 46 | 46
+ 47 | 47
+ 48 | 48
+ 49 | 49
+ 50 | 50
+ 51 | 51
+ 52 | 52
+ 53 | 53
+ 54 | 54
+ 55 | 55
+ 56 | 56
+ 57 | 57
+ 58 | 58
+ 59 | 59
+ 60 | 60
+ 61 | 61
+ 62 | 62
+ 63 | 63
+ 64 | 64
+ 65 | 65
+ 66 | 66
+(66 rows)
+
+delete from t;
+drop table t;
diff --git a/src/test/regress/expected/partition_aggregate.out b/src/test/regress/expected/partition_aggregate.out
index 10349ec29c..5f17afe0eb 100644
--- a/src/test/regress/expected/partition_aggregate.out
+++ b/src/test/regress/expected/partition_aggregate.out
@@ -8,6 +8,8 @@ SET enable_partitionwise_aggregate TO true;
SET enable_partitionwise_join TO true;
-- Disable parallel plans.
SET max_parallel_workers_per_gather TO 0;
+-- Disable incremental sort, which can influence selected plans due to fuzz factor.
+SET enable_incrementalsort TO off;
--
-- Tests for list partitioned tables.
--
diff --git a/src/test/regress/expected/sysviews.out b/src/test/regress/expected/sysviews.out
index a1c90eb905..01b7786f01 100644
--- a/src/test/regress/expected/sysviews.out
+++ b/src/test/regress/expected/sysviews.out
@@ -76,6 +76,7 @@ select name, setting from pg_settings where name like 'enable%';
enable_gathermerge | on
enable_hashagg | on
enable_hashjoin | on
+ enable_incrementalsort | on
enable_indexonlyscan | on
enable_indexscan | on
enable_material | on
@@ -89,7 +90,7 @@ select name, setting from pg_settings where name like 'enable%';
enable_seqscan | on
enable_sort | on
enable_tidscan | on
-(17 rows)
+(18 rows)
-- Test that the pg_timezone_names and pg_timezone_abbrevs views are
-- more-or-less working. We can't test their contents in any great detail
diff --git a/src/test/regress/parallel_schedule b/src/test/regress/parallel_schedule
index 8fb55f045e..f5f4f7c9f9 100644
--- a/src/test/regress/parallel_schedule
+++ b/src/test/regress/parallel_schedule
@@ -78,7 +78,7 @@ test: brin gin gist spgist privileges init_privs security_label collate matview
# ----------
# Another group of parallel tests
# ----------
-test: create_table_like alter_generic alter_operator misc async dbsize misc_functions sysviews tsrf tidscan
+test: create_table_like alter_generic alter_operator misc async dbsize misc_functions sysviews tsrf tidscan incremental_sort
# rules cannot run concurrently with any test that creates
# a view or rule in the public schema
diff --git a/src/test/regress/serial_schedule b/src/test/regress/serial_schedule
index a39ca1012a..7afd0cc373 100644
--- a/src/test/regress/serial_schedule
+++ b/src/test/regress/serial_schedule
@@ -88,6 +88,7 @@ test: select_distinct_on
test: select_implicit
test: select_having
test: subselect
+test: incremental_sort
test: union
test: case
test: join
diff --git a/src/test/regress/sql/incremental_sort.sql b/src/test/regress/sql/incremental_sort.sql
new file mode 100644
index 0000000000..b9df37412f
--- /dev/null
+++ b/src/test/regress/sql/incremental_sort.sql
@@ -0,0 +1,78 @@
+-- When we have to sort the entire table, incremental sort will
+-- be slower than plain sort, so it should not be used.
+explain (costs off)
+select * from (select * from tenk1 order by four) t order by four, ten;
+
+-- When there is a LIMIT clause, incremental sort is beneficial because
+-- it only has to sort some of the groups, and not the entire table.
+explain (costs off)
+select * from (select * from tenk1 order by four) t order by four, ten
+limit 1;
+
+-- When work_mem is not enough to sort the entire table, incremental sort
+-- may be faster if individual groups still fit into work_mem.
+set work_mem to '2MB';
+explain (costs off)
+select * from (select * from tenk1 order by four) t order by four, ten;
+reset work_mem;
+
+-- TODO if an analyze happens here the plans might change; should we
+-- solve by inserting extra rows or by adding a GUC that would somehow
+-- forcing the time of plan we expect.
+create table t(a integer, b integer);
+
+-- A single large group tested around each mode transition point.
+insert into t(a, b) select 1, i from generate_series(1, 100) n(i);
+explain (costs off) select * from (select * from t order by a) s order by a, b limit 31;
+select * from (select * from t order by a) s order by a, b limit 31;
+explain (costs off) select * from (select * from t order by a) s order by a, b limit 32;
+select * from (select * from t order by a) s order by a, b limit 32;
+explain (costs off) select * from (select * from t order by a) s order by a, b limit 33;
+select * from (select * from t order by a) s order by a, b limit 33;
+explain (costs off) select * from (select * from t order by a) s order by a, b limit 65;
+select * from (select * from t order by a) s order by a, b limit 65;
+explain (costs off) select * from (select * from t order by a) s order by a, b limit 66;
+select * from (select * from t order by a) s order by a, b limit 66;
+delete from t;
+
+-- An initial large group followed by a small group.
+insert into t(a, b) select (case when i < 50 then 1 else 2 end), i from generate_series(1, 100) n(i);
+explain (costs off) select * from (select * from t order by a) s order by a, b limit 55;
+select * from (select * from t order by a) s order by a, b limit 55;
+delete from t;
+
+-- An initial small group followed by a large group.
+insert into t(a, b) select (case when i < 5 then i else 9 end), i from generate_series(1, 100) n(i);
+explain (costs off) select * from (select * from t order by a) s order by a, b limit 70;
+select * from (select * from t order by a) s order by a, b limit 70;
+delete from t;
+
+-- Small groups of 10 tuples each tested around each mode transition point.
+insert into t(a, b) select i / 10, i from generate_series(1, 70) n(i);
+explain (costs off) select * from (select * from t order by a) s order by a, b limit 31;
+select * from (select * from t order by a) s order by a, b limit 31;
+explain (costs off) select * from (select * from t order by a) s order by a, b limit 32;
+select * from (select * from t order by a) s order by a, b limit 32;
+explain (costs off) select * from (select * from t order by a) s order by a, b limit 33;
+select * from (select * from t order by a) s order by a, b limit 33;
+explain (costs off) select * from (select * from t order by a) s order by a, b limit 65;
+select * from (select * from t order by a) s order by a, b limit 65;
+explain (costs off) select * from (select * from t order by a) s order by a, b limit 66;
+select * from (select * from t order by a) s order by a, b limit 66;
+delete from t;
+
+-- Small groups of only 1 tuple each tested around each mode transition point.
+insert into t(a, b) select i, i from generate_series(1, 70) n(i);
+explain (costs off) select * from (select * from t order by a) s order by a, b limit 31;
+select * from (select * from t order by a) s order by a, b limit 31;
+explain (costs off) select * from (select * from t order by a) s order by a, b limit 32;
+select * from (select * from t order by a) s order by a, b limit 32;
+explain (costs off) select * from (select * from t order by a) s order by a, b limit 33;
+select * from (select * from t order by a) s order by a, b limit 33;
+explain (costs off) select * from (select * from t order by a) s order by a, b limit 65;
+select * from (select * from t order by a) s order by a, b limit 65;
+explain (costs off) select * from (select * from t order by a) s order by a, b limit 66;
+select * from (select * from t order by a) s order by a, b limit 66;
+delete from t;
+
+drop table t;
diff --git a/src/test/regress/sql/partition_aggregate.sql b/src/test/regress/sql/partition_aggregate.sql
index dcd6edbad2..6a8db29a07 100644
--- a/src/test/regress/sql/partition_aggregate.sql
+++ b/src/test/regress/sql/partition_aggregate.sql
@@ -9,6 +9,8 @@ SET enable_partitionwise_aggregate TO true;
SET enable_partitionwise_join TO true;
-- Disable parallel plans.
SET max_parallel_workers_per_gather TO 0;
+-- Disable incremental sort, which can influence selected plans due to fuzz factor.
+SET enable_incrementalsort TO off;
--
-- Tests for list partitioned tables.
^ permalink raw reply [nested|flat] 868+ messages in thread
* Re: [PATCH] Incremental sort (was: PoC: Partial sort)
2019-07-04 13:25 Re: [PATCH] Incremental sort (was: PoC: Partial sort) James Coleman <[email protected]>
@ 2019-07-08 22:37 ` Alexander Korotkov <[email protected]>
2019-07-09 13:29 ` Re: [PATCH] Incremental sort (was: PoC: Partial sort) James Coleman <[email protected]>
0 siblings, 1 reply; 868+ messages in thread
From: Alexander Korotkov @ 2019-07-08 22:37 UTC (permalink / raw)
To: James Coleman <[email protected]>; +Cc: Tomas Vondra <[email protected]>; Rafia Sabih <[email protected]>; Shaun Thomas <[email protected]>; Dmitry Dolgov <[email protected]>; Tom Lane <[email protected]>; PostgreSQL Developers <[email protected]>
On Thu, Jul 4, 2019 at 4:25 PM James Coleman <[email protected]> wrote:
> Process questions:
> - Do I need to explicitly move the patch somehow to the next CF?
We didn't manage to register it on current (July) commitfest. So,
please, register it on next (September) commitfest.
> - Since I've basically taken over patch ownership, should I move my
> name from reviewer to author in the CF app? And can there be two
> authors listed there?
Sure, you're co-author of this patch. Two or more authors could be
listed at CF app, you can find a lot of examples on the list.
------
Alexander Korotkov
Postgres Professional: http://www.postgrespro.com
The Russian Postgres Company
^ permalink raw reply [nested|flat] 868+ messages in thread
* Re: [PATCH] Incremental sort (was: PoC: Partial sort)
2019-07-04 13:25 Re: [PATCH] Incremental sort (was: PoC: Partial sort) James Coleman <[email protected]>
2019-07-08 22:37 ` Re: [PATCH] Incremental sort (was: PoC: Partial sort) Alexander Korotkov <[email protected]>
@ 2019-07-09 13:29 ` James Coleman <[email protected]>
0 siblings, 0 replies; 868+ messages in thread
From: James Coleman @ 2019-07-09 13:29 UTC (permalink / raw)
To: Alexander Korotkov <[email protected]>; +Cc: Tomas Vondra <[email protected]>; Rafia Sabih <[email protected]>; Shaun Thomas <[email protected]>; Dmitry Dolgov <[email protected]>; Tom Lane <[email protected]>; PostgreSQL Developers <[email protected]>
On Mon, Jul 8, 2019 at 6:37 PM Alexander Korotkov
<[email protected]> wrote:
>
> On Thu, Jul 4, 2019 at 4:25 PM James Coleman <[email protected]> wrote:
> > Process questions:
> > - Do I need to explicitly move the patch somehow to the next CF?
>
> We didn't manage to register it on current (July) commitfest. So,
> please, register it on next (September) commitfest.
I've moved it to the September cf.
> > - Since I've basically taken over patch ownership, should I move my
> > name from reviewer to author in the CF app? And can there be two
> > authors listed there?
>
> Sure, you're co-author of this patch. Two or more authors could be
> listed at CF app, you can find a lot of examples on the list.
Done.
James Coleman
^ permalink raw reply [nested|flat] 868+ 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; 868+ 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] 868+ 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; 868+ 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] 868+ 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; 868+ 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] 868+ 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; 868+ 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] 868+ 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; 868+ 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] 868+ 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; 868+ 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] 868+ 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; 868+ 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] 868+ 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; 868+ 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] 868+ 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; 868+ 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] 868+ messages in thread
* [PATCH v2a 2/4] Introduce ternary reloptions
@ 2025-09-04 14:41 Nikolay Shaplov <[email protected]>
0 siblings, 0 replies; 868+ messages in thread
From: Nikolay Shaplov @ 2025-09-04 14:41 UTC (permalink / raw)
Introduce ternary reloption as a replacement for current `vacuum_truncate`
implementation. Remove `vacuum_truncate_set` additional flag and using
`TERNARY_UNSET` value instead.
---
src/backend/access/common/reloptions.c | 129 ++++++++++++++++++++-----
src/backend/commands/vacuum.c | 4 +-
src/include/access/reloptions.h | 26 ++---
src/include/c.h | 16 +++
src/include/utils/rel.h | 3 +-
5 files changed, 135 insertions(+), 43 deletions(-)
diff --git a/src/backend/access/common/reloptions.c b/src/backend/access/common/reloptions.c
index 0af3fea68f..f543f11001 100644
--- a/src/backend/access/common/reloptions.c
+++ b/src/backend/access/common/reloptions.c
@@ -40,9 +40,9 @@
*
* To add an option:
*
- * (i) decide on a type (bool, integer, real, enum, string), name, default
- * value, upper and lower bounds (if applicable); for strings, consider a
- * validation routine.
+ * (i) decide on a type (bool, ternary, integer, real, enum, string), name,
+ * default value, upper and lower bounds (if applicable); for strings,
+ * consider a validation routine.
* (ii) add a record below (or use add_<type>_reloption).
* (iii) add it to the appropriate options struct (perhaps StdRdOptions)
* (iv) add it to the appropriate handling routine (perhaps
@@ -147,15 +147,6 @@ static relopt_bool boolRelOpts[] =
},
false
},
- {
- {
- "vacuum_truncate",
- "Enables vacuum to truncate empty pages at the end of this table",
- RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
- ShareUpdateExclusiveLock
- },
- true
- },
{
{
"deduplicate_items",
@@ -170,6 +161,21 @@ static relopt_bool boolRelOpts[] =
{{NULL}}
};
+static relopt_ternary ternaryRelOpts[] =
+{
+ {
+ {
+ "vacuum_truncate",
+ "Enables vacuum to truncate empty pages at the end of this table",
+ RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
+ ShareUpdateExclusiveLock
+ },
+ TERNARY_UNSET
+ },
+ /* list terminator */
+ {{NULL}}
+};
+
static relopt_int intRelOpts[] =
{
{
@@ -600,6 +606,13 @@ initialize_reloptions(void)
boolRelOpts[i].gen.lockmode));
j++;
}
+ for (i = 0; ternaryRelOpts[i].gen.name; i++)
+ {
+ Assert(DoLockModesConflict(ternaryRelOpts[i].gen.lockmode,
+ ternaryRelOpts[i].gen.lockmode));
+ j++;
+ }
+
for (i = 0; intRelOpts[i].gen.name; i++)
{
Assert(DoLockModesConflict(intRelOpts[i].gen.lockmode,
@@ -640,6 +653,14 @@ initialize_reloptions(void)
j++;
}
+ for (i = 0; ternaryRelOpts[i].gen.name; i++)
+ {
+ relOpts[j] = &ternaryRelOpts[i].gen;
+ relOpts[j]->type = RELOPT_TYPE_TERNARY;
+ relOpts[j]->namelen = strlen(relOpts[j]->name);
+ j++;
+ }
+
for (i = 0; intRelOpts[i].gen.name; i++)
{
relOpts[j] = &intRelOpts[i].gen;
@@ -800,6 +821,9 @@ allocate_reloption(bits32 kinds, int type, const char *name, const char *desc,
case RELOPT_TYPE_BOOL:
size = sizeof(relopt_bool);
break;
+ case RELOPT_TYPE_TERNARY:
+ size = sizeof(relopt_ternary);
+ break;
case RELOPT_TYPE_INT:
size = sizeof(relopt_int);
break;
@@ -883,6 +907,54 @@ add_local_bool_reloption(local_relopts *relopts, const char *name,
add_local_reloption(relopts, (relopt_gen *) newoption, offset);
}
+/*
+ * init_ternary_reloption
+ * Allocate and initialize a new ternary reloption
+ */
+static relopt_ternary *
+init_ternary_reloption(bits32 kinds, const char *name, const char *desc,
+ ternary default_val, LOCKMODE lockmode)
+{
+ relopt_ternary *newoption;
+
+ newoption = (relopt_ternary *) allocate_reloption(kinds,
+ RELOPT_TYPE_TERNARY, name, desc, lockmode);
+ newoption->default_val = default_val;
+
+ return newoption;
+}
+
+/*
+ * add_ternary_reloption
+ * Add a new ternary reloption
+ */
+void
+add_ternary_reloption(bits32 kinds, const char *name, const char *desc,
+ ternary default_val, LOCKMODE lockmode)
+{
+ relopt_ternary *newoption = init_ternary_reloption(kinds, name, desc,
+ default_val, lockmode);
+
+ add_reloption((relopt_gen *) newoption);
+}
+
+/*
+ * add_local_ternary_reloption
+ * Add a new ternary local reloption
+ *
+ * 'offset' is offset of ternary-typed field.
+ */
+void
+add_local_ternary_reloption(local_relopts *relopts, const char *name,
+ const char *desc, ternary default_val,
+ int offset)
+{
+ relopt_ternary *newoption = init_ternary_reloption(RELOPT_KIND_LOCAL,
+ name, desc,
+ default_val, 0);
+
+ add_local_reloption(relopts, (relopt_gen *) newoption, offset);
+}
/*
* init_real_reloption
@@ -1617,6 +1689,18 @@ parse_one_reloption(relopt_value *option, char *text_str, int text_len,
option->gen->name, value)));
}
break;
+ case RELOPT_TYPE_TERNARY:
+ {
+ bool b;
+ parsed = parse_bool(value, &b);
+ option->values.ternary_val = b ? TERNARY_TRUE : TERNARY_FALSE;
+ if (validate && !parsed)
+ ereport(ERROR,
+ (errcode(ERRCODE_INVALID_PARAMETER_VALUE),
+ errmsg("invalid value for ternary option \"%s\": %s",
+ option->gen->name, value)));
+ }
+ break;
case RELOPT_TYPE_INT:
{
relopt_int *optint = (relopt_int *) option->gen;
@@ -1780,17 +1864,6 @@ fillRelOptions(void *rdopts, Size basesize,
char *itempos = ((char *) rdopts) + elems[j].offset;
char *string_val;
- /*
- * If isset_offset is provided, store whether the reloption is
- * set there.
- */
- if (elems[j].isset_offset > 0)
- {
- char *setpos = ((char *) rdopts) + elems[j].isset_offset;
-
- *(bool *) setpos = options[i].isset;
- }
-
switch (options[i].gen->type)
{
case RELOPT_TYPE_BOOL:
@@ -1798,6 +1871,11 @@ fillRelOptions(void *rdopts, Size basesize,
options[i].values.bool_val :
((relopt_bool *) options[i].gen)->default_val;
break;
+ case RELOPT_TYPE_TERNARY:
+ *(ternary *) itempos = options[i].isset ?
+ options[i].values.ternary_val :
+ ((relopt_ternary *) options[i].gen)->default_val;
+ break;
case RELOPT_TYPE_INT:
*(int *) itempos = options[i].isset ?
options[i].values.int_val :
@@ -1912,8 +1990,8 @@ default_reloptions(Datum reloptions, bool validate, relopt_kind kind)
offsetof(StdRdOptions, parallel_workers)},
{"vacuum_index_cleanup", RELOPT_TYPE_ENUM,
offsetof(StdRdOptions, vacuum_index_cleanup)},
- {"vacuum_truncate", RELOPT_TYPE_BOOL,
- offsetof(StdRdOptions, vacuum_truncate), offsetof(StdRdOptions, vacuum_truncate_set)},
+ {"vacuum_truncate", RELOPT_TYPE_TERNARY,
+ offsetof(StdRdOptions, vacuum_truncate)},
{"vacuum_max_eager_freeze_failure_rate", RELOPT_TYPE_REAL,
offsetof(StdRdOptions, vacuum_max_eager_freeze_failure_rate)}
};
@@ -1993,7 +2071,6 @@ build_local_reloptions(local_relopts *relopts, Datum options, bool validate)
elems[i].optname = opt->option->name;
elems[i].opttype = opt->option->type;
elems[i].offset = opt->offset;
- elems[i].isset_offset = 0; /* not supported for local relopts yet */
i++;
}
diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c
index 733ef40ae7..977babff54 100644
--- a/src/backend/commands/vacuum.c
+++ b/src/backend/commands/vacuum.c
@@ -2221,9 +2221,9 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams params,
{
StdRdOptions *opts = (StdRdOptions *) rel->rd_options;
- if (opts && opts->vacuum_truncate_set)
+ if (opts && opts->vacuum_truncate != TERNARY_UNSET)
{
- if (opts->vacuum_truncate)
+ if (opts->vacuum_truncate == TERNARY_TRUE)
params.truncate = VACOPTVALUE_ENABLED;
else
params.truncate = VACOPTVALUE_DISABLED;
diff --git a/src/include/access/reloptions.h b/src/include/access/reloptions.h
index a604a4702c..a436697658 100644
--- a/src/include/access/reloptions.h
+++ b/src/include/access/reloptions.h
@@ -29,6 +29,7 @@
typedef enum relopt_type
{
RELOPT_TYPE_BOOL,
+ RELOPT_TYPE_TERNARY, /* on, off, unset */
RELOPT_TYPE_INT,
RELOPT_TYPE_REAL,
RELOPT_TYPE_ENUM,
@@ -80,6 +81,7 @@ typedef struct relopt_value
union
{
bool bool_val;
+ ternary ternary_val;
int int_val;
double real_val;
int enum_val;
@@ -94,6 +96,12 @@ typedef struct relopt_bool
bool default_val;
} relopt_bool;
+typedef struct relopt_rernary
+{
+ relopt_gen gen;
+ int default_val;
+} relopt_ternary;
+
typedef struct relopt_int
{
relopt_gen gen;
@@ -152,19 +160,6 @@ typedef struct
const char *optname; /* option's name */
relopt_type opttype; /* option's datatype */
int offset; /* offset of field in result struct */
-
- /*
- * isset_offset is an optional offset of a field in the result struct that
- * stores whether the option is explicitly set for the relation or if it
- * just picked up the default value. In most cases, this can be
- * accomplished by giving the reloption a special out-of-range default
- * value (e.g., some integer reloptions use -2), but this isn't always
- * possible. For example, a Boolean reloption cannot be given an
- * out-of-range default, so we need another way to discover the source of
- * its value. This offset is only used if given a value greater than
- * zero.
- */
- int isset_offset;
} relopt_parse_elt;
/* Local reloption definition */
@@ -195,6 +190,8 @@ typedef struct local_relopts
extern relopt_kind add_reloption_kind(void);
extern void add_bool_reloption(bits32 kinds, const char *name, const char *desc,
bool default_val, LOCKMODE lockmode);
+extern void add_ternary_reloption(bits32 kinds, const char *name,
+ const char *desc, int default_val, LOCKMODE lockmode);
extern void add_int_reloption(bits32 kinds, const char *name, const char *desc,
int default_val, int min_val, int max_val,
LOCKMODE lockmode);
@@ -214,6 +211,9 @@ extern void register_reloptions_validator(local_relopts *relopts,
extern void add_local_bool_reloption(local_relopts *relopts, const char *name,
const char *desc, bool default_val,
int offset);
+extern void add_local_ternary_reloption(local_relopts *relopts,
+ const char *name, const char *desc,
+ ternary default_val, int offset);
extern void add_local_int_reloption(local_relopts *relopts, const char *name,
const char *desc, int default_val,
int min_val, int max_val, int offset);
diff --git a/src/include/c.h b/src/include/c.h
index 39022f8a9d..9c59ccccf8 100644
--- a/src/include/c.h
+++ b/src/include/c.h
@@ -488,6 +488,22 @@ typedef void (*pg_funcptr_t) (void);
#include <stdbool.h>
+/*
+ * ternary
+ * Boolean value with an extrea "unset" option
+ *
+ * Ternary data type is used in relation options that can be "true", "false" or
+ * "unset". Since relation options are used deep inside the PostgreSQL code,
+ * this type is declared globally.
+*/
+
+typedef enum ternary
+{
+ TERNARY_FALSE = 0,
+ TERNARY_TRUE = 1,
+ TERNARY_UNSET = -1
+} ternary;
+
/* ----------------------------------------------------------------
* Section 3: standard system types
diff --git a/src/include/utils/rel.h b/src/include/utils/rel.h
index b552359915..7346d618f9 100644
--- a/src/include/utils/rel.h
+++ b/src/include/utils/rel.h
@@ -346,8 +346,7 @@ typedef struct StdRdOptions
bool user_catalog_table; /* use as an additional catalog relation */
int parallel_workers; /* max number of parallel workers */
StdRdOptIndexCleanup vacuum_index_cleanup; /* controls index vacuuming */
- bool vacuum_truncate; /* enables vacuum to truncate a relation */
- bool vacuum_truncate_set; /* whether vacuum_truncate is set */
+ ternary vacuum_truncate; /* enables vacuum to truncate a relation */
/*
* Fraction of pages in a relation that vacuum can eagerly scan and fail
--
2.39.2
--nextPart4155600.3ZeAukHxDK
Content-Disposition: attachment;
filename="v2a-0003-Add-alias-to-be-used-as-unset-state.patch"
Content-Transfer-Encoding: 7Bit
Content-Type: text/x-patch; charset="unicode-2-0-utf-8";
name="v2a-0003-Add-alias-to-be-used-as-unset-state.patch"
^ permalink raw reply [nested|flat] 868+ messages in thread
* [PATCH v2a 2/4] Introduce ternary reloptions
@ 2025-09-04 14:41 Nikolay Shaplov <[email protected]>
0 siblings, 0 replies; 868+ messages in thread
From: Nikolay Shaplov @ 2025-09-04 14:41 UTC (permalink / raw)
Introduce ternary reloption as a replacement for current `vacuum_truncate`
implementation. Remove `vacuum_truncate_set` additional flag and using
`TERNARY_UNSET` value instead.
---
src/backend/access/common/reloptions.c | 129 ++++++++++++++++++++-----
src/backend/commands/vacuum.c | 4 +-
src/include/access/reloptions.h | 26 ++---
src/include/c.h | 16 +++
src/include/utils/rel.h | 3 +-
5 files changed, 135 insertions(+), 43 deletions(-)
diff --git a/src/backend/access/common/reloptions.c b/src/backend/access/common/reloptions.c
index 0af3fea68f..f543f11001 100644
--- a/src/backend/access/common/reloptions.c
+++ b/src/backend/access/common/reloptions.c
@@ -40,9 +40,9 @@
*
* To add an option:
*
- * (i) decide on a type (bool, integer, real, enum, string), name, default
- * value, upper and lower bounds (if applicable); for strings, consider a
- * validation routine.
+ * (i) decide on a type (bool, ternary, integer, real, enum, string), name,
+ * default value, upper and lower bounds (if applicable); for strings,
+ * consider a validation routine.
* (ii) add a record below (or use add_<type>_reloption).
* (iii) add it to the appropriate options struct (perhaps StdRdOptions)
* (iv) add it to the appropriate handling routine (perhaps
@@ -147,15 +147,6 @@ static relopt_bool boolRelOpts[] =
},
false
},
- {
- {
- "vacuum_truncate",
- "Enables vacuum to truncate empty pages at the end of this table",
- RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
- ShareUpdateExclusiveLock
- },
- true
- },
{
{
"deduplicate_items",
@@ -170,6 +161,21 @@ static relopt_bool boolRelOpts[] =
{{NULL}}
};
+static relopt_ternary ternaryRelOpts[] =
+{
+ {
+ {
+ "vacuum_truncate",
+ "Enables vacuum to truncate empty pages at the end of this table",
+ RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
+ ShareUpdateExclusiveLock
+ },
+ TERNARY_UNSET
+ },
+ /* list terminator */
+ {{NULL}}
+};
+
static relopt_int intRelOpts[] =
{
{
@@ -600,6 +606,13 @@ initialize_reloptions(void)
boolRelOpts[i].gen.lockmode));
j++;
}
+ for (i = 0; ternaryRelOpts[i].gen.name; i++)
+ {
+ Assert(DoLockModesConflict(ternaryRelOpts[i].gen.lockmode,
+ ternaryRelOpts[i].gen.lockmode));
+ j++;
+ }
+
for (i = 0; intRelOpts[i].gen.name; i++)
{
Assert(DoLockModesConflict(intRelOpts[i].gen.lockmode,
@@ -640,6 +653,14 @@ initialize_reloptions(void)
j++;
}
+ for (i = 0; ternaryRelOpts[i].gen.name; i++)
+ {
+ relOpts[j] = &ternaryRelOpts[i].gen;
+ relOpts[j]->type = RELOPT_TYPE_TERNARY;
+ relOpts[j]->namelen = strlen(relOpts[j]->name);
+ j++;
+ }
+
for (i = 0; intRelOpts[i].gen.name; i++)
{
relOpts[j] = &intRelOpts[i].gen;
@@ -800,6 +821,9 @@ allocate_reloption(bits32 kinds, int type, const char *name, const char *desc,
case RELOPT_TYPE_BOOL:
size = sizeof(relopt_bool);
break;
+ case RELOPT_TYPE_TERNARY:
+ size = sizeof(relopt_ternary);
+ break;
case RELOPT_TYPE_INT:
size = sizeof(relopt_int);
break;
@@ -883,6 +907,54 @@ add_local_bool_reloption(local_relopts *relopts, const char *name,
add_local_reloption(relopts, (relopt_gen *) newoption, offset);
}
+/*
+ * init_ternary_reloption
+ * Allocate and initialize a new ternary reloption
+ */
+static relopt_ternary *
+init_ternary_reloption(bits32 kinds, const char *name, const char *desc,
+ ternary default_val, LOCKMODE lockmode)
+{
+ relopt_ternary *newoption;
+
+ newoption = (relopt_ternary *) allocate_reloption(kinds,
+ RELOPT_TYPE_TERNARY, name, desc, lockmode);
+ newoption->default_val = default_val;
+
+ return newoption;
+}
+
+/*
+ * add_ternary_reloption
+ * Add a new ternary reloption
+ */
+void
+add_ternary_reloption(bits32 kinds, const char *name, const char *desc,
+ ternary default_val, LOCKMODE lockmode)
+{
+ relopt_ternary *newoption = init_ternary_reloption(kinds, name, desc,
+ default_val, lockmode);
+
+ add_reloption((relopt_gen *) newoption);
+}
+
+/*
+ * add_local_ternary_reloption
+ * Add a new ternary local reloption
+ *
+ * 'offset' is offset of ternary-typed field.
+ */
+void
+add_local_ternary_reloption(local_relopts *relopts, const char *name,
+ const char *desc, ternary default_val,
+ int offset)
+{
+ relopt_ternary *newoption = init_ternary_reloption(RELOPT_KIND_LOCAL,
+ name, desc,
+ default_val, 0);
+
+ add_local_reloption(relopts, (relopt_gen *) newoption, offset);
+}
/*
* init_real_reloption
@@ -1617,6 +1689,18 @@ parse_one_reloption(relopt_value *option, char *text_str, int text_len,
option->gen->name, value)));
}
break;
+ case RELOPT_TYPE_TERNARY:
+ {
+ bool b;
+ parsed = parse_bool(value, &b);
+ option->values.ternary_val = b ? TERNARY_TRUE : TERNARY_FALSE;
+ if (validate && !parsed)
+ ereport(ERROR,
+ (errcode(ERRCODE_INVALID_PARAMETER_VALUE),
+ errmsg("invalid value for ternary option \"%s\": %s",
+ option->gen->name, value)));
+ }
+ break;
case RELOPT_TYPE_INT:
{
relopt_int *optint = (relopt_int *) option->gen;
@@ -1780,17 +1864,6 @@ fillRelOptions(void *rdopts, Size basesize,
char *itempos = ((char *) rdopts) + elems[j].offset;
char *string_val;
- /*
- * If isset_offset is provided, store whether the reloption is
- * set there.
- */
- if (elems[j].isset_offset > 0)
- {
- char *setpos = ((char *) rdopts) + elems[j].isset_offset;
-
- *(bool *) setpos = options[i].isset;
- }
-
switch (options[i].gen->type)
{
case RELOPT_TYPE_BOOL:
@@ -1798,6 +1871,11 @@ fillRelOptions(void *rdopts, Size basesize,
options[i].values.bool_val :
((relopt_bool *) options[i].gen)->default_val;
break;
+ case RELOPT_TYPE_TERNARY:
+ *(ternary *) itempos = options[i].isset ?
+ options[i].values.ternary_val :
+ ((relopt_ternary *) options[i].gen)->default_val;
+ break;
case RELOPT_TYPE_INT:
*(int *) itempos = options[i].isset ?
options[i].values.int_val :
@@ -1912,8 +1990,8 @@ default_reloptions(Datum reloptions, bool validate, relopt_kind kind)
offsetof(StdRdOptions, parallel_workers)},
{"vacuum_index_cleanup", RELOPT_TYPE_ENUM,
offsetof(StdRdOptions, vacuum_index_cleanup)},
- {"vacuum_truncate", RELOPT_TYPE_BOOL,
- offsetof(StdRdOptions, vacuum_truncate), offsetof(StdRdOptions, vacuum_truncate_set)},
+ {"vacuum_truncate", RELOPT_TYPE_TERNARY,
+ offsetof(StdRdOptions, vacuum_truncate)},
{"vacuum_max_eager_freeze_failure_rate", RELOPT_TYPE_REAL,
offsetof(StdRdOptions, vacuum_max_eager_freeze_failure_rate)}
};
@@ -1993,7 +2071,6 @@ build_local_reloptions(local_relopts *relopts, Datum options, bool validate)
elems[i].optname = opt->option->name;
elems[i].opttype = opt->option->type;
elems[i].offset = opt->offset;
- elems[i].isset_offset = 0; /* not supported for local relopts yet */
i++;
}
diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c
index 733ef40ae7..977babff54 100644
--- a/src/backend/commands/vacuum.c
+++ b/src/backend/commands/vacuum.c
@@ -2221,9 +2221,9 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams params,
{
StdRdOptions *opts = (StdRdOptions *) rel->rd_options;
- if (opts && opts->vacuum_truncate_set)
+ if (opts && opts->vacuum_truncate != TERNARY_UNSET)
{
- if (opts->vacuum_truncate)
+ if (opts->vacuum_truncate == TERNARY_TRUE)
params.truncate = VACOPTVALUE_ENABLED;
else
params.truncate = VACOPTVALUE_DISABLED;
diff --git a/src/include/access/reloptions.h b/src/include/access/reloptions.h
index a604a4702c..a436697658 100644
--- a/src/include/access/reloptions.h
+++ b/src/include/access/reloptions.h
@@ -29,6 +29,7 @@
typedef enum relopt_type
{
RELOPT_TYPE_BOOL,
+ RELOPT_TYPE_TERNARY, /* on, off, unset */
RELOPT_TYPE_INT,
RELOPT_TYPE_REAL,
RELOPT_TYPE_ENUM,
@@ -80,6 +81,7 @@ typedef struct relopt_value
union
{
bool bool_val;
+ ternary ternary_val;
int int_val;
double real_val;
int enum_val;
@@ -94,6 +96,12 @@ typedef struct relopt_bool
bool default_val;
} relopt_bool;
+typedef struct relopt_rernary
+{
+ relopt_gen gen;
+ int default_val;
+} relopt_ternary;
+
typedef struct relopt_int
{
relopt_gen gen;
@@ -152,19 +160,6 @@ typedef struct
const char *optname; /* option's name */
relopt_type opttype; /* option's datatype */
int offset; /* offset of field in result struct */
-
- /*
- * isset_offset is an optional offset of a field in the result struct that
- * stores whether the option is explicitly set for the relation or if it
- * just picked up the default value. In most cases, this can be
- * accomplished by giving the reloption a special out-of-range default
- * value (e.g., some integer reloptions use -2), but this isn't always
- * possible. For example, a Boolean reloption cannot be given an
- * out-of-range default, so we need another way to discover the source of
- * its value. This offset is only used if given a value greater than
- * zero.
- */
- int isset_offset;
} relopt_parse_elt;
/* Local reloption definition */
@@ -195,6 +190,8 @@ typedef struct local_relopts
extern relopt_kind add_reloption_kind(void);
extern void add_bool_reloption(bits32 kinds, const char *name, const char *desc,
bool default_val, LOCKMODE lockmode);
+extern void add_ternary_reloption(bits32 kinds, const char *name,
+ const char *desc, int default_val, LOCKMODE lockmode);
extern void add_int_reloption(bits32 kinds, const char *name, const char *desc,
int default_val, int min_val, int max_val,
LOCKMODE lockmode);
@@ -214,6 +211,9 @@ extern void register_reloptions_validator(local_relopts *relopts,
extern void add_local_bool_reloption(local_relopts *relopts, const char *name,
const char *desc, bool default_val,
int offset);
+extern void add_local_ternary_reloption(local_relopts *relopts,
+ const char *name, const char *desc,
+ ternary default_val, int offset);
extern void add_local_int_reloption(local_relopts *relopts, const char *name,
const char *desc, int default_val,
int min_val, int max_val, int offset);
diff --git a/src/include/c.h b/src/include/c.h
index 39022f8a9d..9c59ccccf8 100644
--- a/src/include/c.h
+++ b/src/include/c.h
@@ -488,6 +488,22 @@ typedef void (*pg_funcptr_t) (void);
#include <stdbool.h>
+/*
+ * ternary
+ * Boolean value with an extrea "unset" option
+ *
+ * Ternary data type is used in relation options that can be "true", "false" or
+ * "unset". Since relation options are used deep inside the PostgreSQL code,
+ * this type is declared globally.
+*/
+
+typedef enum ternary
+{
+ TERNARY_FALSE = 0,
+ TERNARY_TRUE = 1,
+ TERNARY_UNSET = -1
+} ternary;
+
/* ----------------------------------------------------------------
* Section 3: standard system types
diff --git a/src/include/utils/rel.h b/src/include/utils/rel.h
index b552359915..7346d618f9 100644
--- a/src/include/utils/rel.h
+++ b/src/include/utils/rel.h
@@ -346,8 +346,7 @@ typedef struct StdRdOptions
bool user_catalog_table; /* use as an additional catalog relation */
int parallel_workers; /* max number of parallel workers */
StdRdOptIndexCleanup vacuum_index_cleanup; /* controls index vacuuming */
- bool vacuum_truncate; /* enables vacuum to truncate a relation */
- bool vacuum_truncate_set; /* whether vacuum_truncate is set */
+ ternary vacuum_truncate; /* enables vacuum to truncate a relation */
/*
* Fraction of pages in a relation that vacuum can eagerly scan and fail
--
2.39.2
--nextPart4155600.3ZeAukHxDK
Content-Disposition: attachment;
filename="v2a-0003-Add-alias-to-be-used-as-unset-state.patch"
Content-Transfer-Encoding: 7Bit
Content-Type: text/x-patch; charset="unicode-2-0-utf-8";
name="v2a-0003-Add-alias-to-be-used-as-unset-state.patch"
^ permalink raw reply [nested|flat] 868+ messages in thread
* [PATCH v2 2/4] Introduce ternary reloptions
@ 2025-09-04 14:41 Nikolay Shaplov <[email protected]>
0 siblings, 0 replies; 868+ messages in thread
From: Nikolay Shaplov @ 2025-09-04 14:41 UTC (permalink / raw)
Introduce ternary reloption as a replacement for current `vacuum_truncate`
implementation. Remove `vacuum_truncate_set` additional flag and using
`TERNARY_UNSET` value instead.
---
src/backend/access/common/reloptions.c | 129 ++++++++++++++++++++-----
src/backend/commands/vacuum.c | 4 +-
src/include/access/reloptions.h | 26 ++---
src/include/c.h | 16 +++
src/include/utils/rel.h | 3 +-
5 files changed, 135 insertions(+), 43 deletions(-)
diff --git a/src/backend/access/common/reloptions.c b/src/backend/access/common/reloptions.c
index 0af3fea68f..f543f11001 100644
--- a/src/backend/access/common/reloptions.c
+++ b/src/backend/access/common/reloptions.c
@@ -40,9 +40,9 @@
*
* To add an option:
*
- * (i) decide on a type (bool, integer, real, enum, string), name, default
- * value, upper and lower bounds (if applicable); for strings, consider a
- * validation routine.
+ * (i) decide on a type (bool, ternary, integer, real, enum, string), name,
+ * default value, upper and lower bounds (if applicable); for strings,
+ * consider a validation routine.
* (ii) add a record below (or use add_<type>_reloption).
* (iii) add it to the appropriate options struct (perhaps StdRdOptions)
* (iv) add it to the appropriate handling routine (perhaps
@@ -147,15 +147,6 @@ static relopt_bool boolRelOpts[] =
},
false
},
- {
- {
- "vacuum_truncate",
- "Enables vacuum to truncate empty pages at the end of this table",
- RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
- ShareUpdateExclusiveLock
- },
- true
- },
{
{
"deduplicate_items",
@@ -170,6 +161,21 @@ static relopt_bool boolRelOpts[] =
{{NULL}}
};
+static relopt_ternary ternaryRelOpts[] =
+{
+ {
+ {
+ "vacuum_truncate",
+ "Enables vacuum to truncate empty pages at the end of this table",
+ RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
+ ShareUpdateExclusiveLock
+ },
+ TERNARY_UNSET
+ },
+ /* list terminator */
+ {{NULL}}
+};
+
static relopt_int intRelOpts[] =
{
{
@@ -600,6 +606,13 @@ initialize_reloptions(void)
boolRelOpts[i].gen.lockmode));
j++;
}
+ for (i = 0; ternaryRelOpts[i].gen.name; i++)
+ {
+ Assert(DoLockModesConflict(ternaryRelOpts[i].gen.lockmode,
+ ternaryRelOpts[i].gen.lockmode));
+ j++;
+ }
+
for (i = 0; intRelOpts[i].gen.name; i++)
{
Assert(DoLockModesConflict(intRelOpts[i].gen.lockmode,
@@ -640,6 +653,14 @@ initialize_reloptions(void)
j++;
}
+ for (i = 0; ternaryRelOpts[i].gen.name; i++)
+ {
+ relOpts[j] = &ternaryRelOpts[i].gen;
+ relOpts[j]->type = RELOPT_TYPE_TERNARY;
+ relOpts[j]->namelen = strlen(relOpts[j]->name);
+ j++;
+ }
+
for (i = 0; intRelOpts[i].gen.name; i++)
{
relOpts[j] = &intRelOpts[i].gen;
@@ -800,6 +821,9 @@ allocate_reloption(bits32 kinds, int type, const char *name, const char *desc,
case RELOPT_TYPE_BOOL:
size = sizeof(relopt_bool);
break;
+ case RELOPT_TYPE_TERNARY:
+ size = sizeof(relopt_ternary);
+ break;
case RELOPT_TYPE_INT:
size = sizeof(relopt_int);
break;
@@ -883,6 +907,54 @@ add_local_bool_reloption(local_relopts *relopts, const char *name,
add_local_reloption(relopts, (relopt_gen *) newoption, offset);
}
+/*
+ * init_ternary_reloption
+ * Allocate and initialize a new ternary reloption
+ */
+static relopt_ternary *
+init_ternary_reloption(bits32 kinds, const char *name, const char *desc,
+ ternary default_val, LOCKMODE lockmode)
+{
+ relopt_ternary *newoption;
+
+ newoption = (relopt_ternary *) allocate_reloption(kinds,
+ RELOPT_TYPE_TERNARY, name, desc, lockmode);
+ newoption->default_val = default_val;
+
+ return newoption;
+}
+
+/*
+ * add_ternary_reloption
+ * Add a new ternary reloption
+ */
+void
+add_ternary_reloption(bits32 kinds, const char *name, const char *desc,
+ ternary default_val, LOCKMODE lockmode)
+{
+ relopt_ternary *newoption = init_ternary_reloption(kinds, name, desc,
+ default_val, lockmode);
+
+ add_reloption((relopt_gen *) newoption);
+}
+
+/*
+ * add_local_ternary_reloption
+ * Add a new ternary local reloption
+ *
+ * 'offset' is offset of ternary-typed field.
+ */
+void
+add_local_ternary_reloption(local_relopts *relopts, const char *name,
+ const char *desc, ternary default_val,
+ int offset)
+{
+ relopt_ternary *newoption = init_ternary_reloption(RELOPT_KIND_LOCAL,
+ name, desc,
+ default_val, 0);
+
+ add_local_reloption(relopts, (relopt_gen *) newoption, offset);
+}
/*
* init_real_reloption
@@ -1617,6 +1689,18 @@ parse_one_reloption(relopt_value *option, char *text_str, int text_len,
option->gen->name, value)));
}
break;
+ case RELOPT_TYPE_TERNARY:
+ {
+ bool b;
+ parsed = parse_bool(value, &b);
+ option->values.ternary_val = b ? TERNARY_TRUE : TERNARY_FALSE;
+ if (validate && !parsed)
+ ereport(ERROR,
+ (errcode(ERRCODE_INVALID_PARAMETER_VALUE),
+ errmsg("invalid value for ternary option \"%s\": %s",
+ option->gen->name, value)));
+ }
+ break;
case RELOPT_TYPE_INT:
{
relopt_int *optint = (relopt_int *) option->gen;
@@ -1780,17 +1864,6 @@ fillRelOptions(void *rdopts, Size basesize,
char *itempos = ((char *) rdopts) + elems[j].offset;
char *string_val;
- /*
- * If isset_offset is provided, store whether the reloption is
- * set there.
- */
- if (elems[j].isset_offset > 0)
- {
- char *setpos = ((char *) rdopts) + elems[j].isset_offset;
-
- *(bool *) setpos = options[i].isset;
- }
-
switch (options[i].gen->type)
{
case RELOPT_TYPE_BOOL:
@@ -1798,6 +1871,11 @@ fillRelOptions(void *rdopts, Size basesize,
options[i].values.bool_val :
((relopt_bool *) options[i].gen)->default_val;
break;
+ case RELOPT_TYPE_TERNARY:
+ *(ternary *) itempos = options[i].isset ?
+ options[i].values.ternary_val :
+ ((relopt_ternary *) options[i].gen)->default_val;
+ break;
case RELOPT_TYPE_INT:
*(int *) itempos = options[i].isset ?
options[i].values.int_val :
@@ -1912,8 +1990,8 @@ default_reloptions(Datum reloptions, bool validate, relopt_kind kind)
offsetof(StdRdOptions, parallel_workers)},
{"vacuum_index_cleanup", RELOPT_TYPE_ENUM,
offsetof(StdRdOptions, vacuum_index_cleanup)},
- {"vacuum_truncate", RELOPT_TYPE_BOOL,
- offsetof(StdRdOptions, vacuum_truncate), offsetof(StdRdOptions, vacuum_truncate_set)},
+ {"vacuum_truncate", RELOPT_TYPE_TERNARY,
+ offsetof(StdRdOptions, vacuum_truncate)},
{"vacuum_max_eager_freeze_failure_rate", RELOPT_TYPE_REAL,
offsetof(StdRdOptions, vacuum_max_eager_freeze_failure_rate)}
};
@@ -1993,7 +2071,6 @@ build_local_reloptions(local_relopts *relopts, Datum options, bool validate)
elems[i].optname = opt->option->name;
elems[i].opttype = opt->option->type;
elems[i].offset = opt->offset;
- elems[i].isset_offset = 0; /* not supported for local relopts yet */
i++;
}
diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c
index 733ef40ae7..977babff54 100644
--- a/src/backend/commands/vacuum.c
+++ b/src/backend/commands/vacuum.c
@@ -2221,9 +2221,9 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams params,
{
StdRdOptions *opts = (StdRdOptions *) rel->rd_options;
- if (opts && opts->vacuum_truncate_set)
+ if (opts && opts->vacuum_truncate != TERNARY_UNSET)
{
- if (opts->vacuum_truncate)
+ if (opts->vacuum_truncate == TERNARY_TRUE)
params.truncate = VACOPTVALUE_ENABLED;
else
params.truncate = VACOPTVALUE_DISABLED;
diff --git a/src/include/access/reloptions.h b/src/include/access/reloptions.h
index a604a4702c..a436697658 100644
--- a/src/include/access/reloptions.h
+++ b/src/include/access/reloptions.h
@@ -29,6 +29,7 @@
typedef enum relopt_type
{
RELOPT_TYPE_BOOL,
+ RELOPT_TYPE_TERNARY, /* on, off, unset */
RELOPT_TYPE_INT,
RELOPT_TYPE_REAL,
RELOPT_TYPE_ENUM,
@@ -80,6 +81,7 @@ typedef struct relopt_value
union
{
bool bool_val;
+ ternary ternary_val;
int int_val;
double real_val;
int enum_val;
@@ -94,6 +96,12 @@ typedef struct relopt_bool
bool default_val;
} relopt_bool;
+typedef struct relopt_rernary
+{
+ relopt_gen gen;
+ int default_val;
+} relopt_ternary;
+
typedef struct relopt_int
{
relopt_gen gen;
@@ -152,19 +160,6 @@ typedef struct
const char *optname; /* option's name */
relopt_type opttype; /* option's datatype */
int offset; /* offset of field in result struct */
-
- /*
- * isset_offset is an optional offset of a field in the result struct that
- * stores whether the option is explicitly set for the relation or if it
- * just picked up the default value. In most cases, this can be
- * accomplished by giving the reloption a special out-of-range default
- * value (e.g., some integer reloptions use -2), but this isn't always
- * possible. For example, a Boolean reloption cannot be given an
- * out-of-range default, so we need another way to discover the source of
- * its value. This offset is only used if given a value greater than
- * zero.
- */
- int isset_offset;
} relopt_parse_elt;
/* Local reloption definition */
@@ -195,6 +190,8 @@ typedef struct local_relopts
extern relopt_kind add_reloption_kind(void);
extern void add_bool_reloption(bits32 kinds, const char *name, const char *desc,
bool default_val, LOCKMODE lockmode);
+extern void add_ternary_reloption(bits32 kinds, const char *name,
+ const char *desc, int default_val, LOCKMODE lockmode);
extern void add_int_reloption(bits32 kinds, const char *name, const char *desc,
int default_val, int min_val, int max_val,
LOCKMODE lockmode);
@@ -214,6 +211,9 @@ extern void register_reloptions_validator(local_relopts *relopts,
extern void add_local_bool_reloption(local_relopts *relopts, const char *name,
const char *desc, bool default_val,
int offset);
+extern void add_local_ternary_reloption(local_relopts *relopts,
+ const char *name, const char *desc,
+ ternary default_val, int offset);
extern void add_local_int_reloption(local_relopts *relopts, const char *name,
const char *desc, int default_val,
int min_val, int max_val, int offset);
diff --git a/src/include/c.h b/src/include/c.h
index 39022f8a9d..9c59ccccf8 100644
--- a/src/include/c.h
+++ b/src/include/c.h
@@ -488,6 +488,22 @@ typedef void (*pg_funcptr_t) (void);
#include <stdbool.h>
+/*
+ * ternary
+ * Boolean value with an extrea "unset" option
+ *
+ * Ternary data type is used in relation options that can be "true", "false" or
+ * "unset". Since relation options are used deep inside the PostgreSQL code,
+ * this type is declared globally.
+*/
+
+typedef enum ternary
+{
+ TERNARY_FALSE = 0,
+ TERNARY_TRUE = 1,
+ TERNARY_UNSET = -1
+} ternary;
+
/* ----------------------------------------------------------------
* Section 3: standard system types
diff --git a/src/include/utils/rel.h b/src/include/utils/rel.h
index b552359915..7346d618f9 100644
--- a/src/include/utils/rel.h
+++ b/src/include/utils/rel.h
@@ -346,8 +346,7 @@ typedef struct StdRdOptions
bool user_catalog_table; /* use as an additional catalog relation */
int parallel_workers; /* max number of parallel workers */
StdRdOptIndexCleanup vacuum_index_cleanup; /* controls index vacuuming */
- bool vacuum_truncate; /* enables vacuum to truncate a relation */
- bool vacuum_truncate_set; /* whether vacuum_truncate is set */
+ ternary vacuum_truncate; /* enables vacuum to truncate a relation */
/*
* Fraction of pages in a relation that vacuum can eagerly scan and fail
--
2.39.2
--nextPart5129932.5fSG56mABF
Content-Disposition: attachment;
filename="v2-0003-Add-alias-to-be-used-as-unset-state.patch"
Content-Transfer-Encoding: 7Bit
Content-Type: text/x-patch; charset="unicode-2-0-utf-8";
name="v2-0003-Add-alias-to-be-used-as-unset-state.patch"
^ permalink raw reply [nested|flat] 868+ messages in thread
* [PATCH v2a 2/4] Introduce ternary reloptions
@ 2025-09-04 14:41 Nikolay Shaplov <[email protected]>
0 siblings, 0 replies; 868+ messages in thread
From: Nikolay Shaplov @ 2025-09-04 14:41 UTC (permalink / raw)
Introduce ternary reloption as a replacement for current `vacuum_truncate`
implementation. Remove `vacuum_truncate_set` additional flag and using
`TERNARY_UNSET` value instead.
---
src/backend/access/common/reloptions.c | 129 ++++++++++++++++++++-----
src/backend/commands/vacuum.c | 4 +-
src/include/access/reloptions.h | 26 ++---
src/include/c.h | 16 +++
src/include/utils/rel.h | 3 +-
5 files changed, 135 insertions(+), 43 deletions(-)
diff --git a/src/backend/access/common/reloptions.c b/src/backend/access/common/reloptions.c
index 0af3fea68f..f543f11001 100644
--- a/src/backend/access/common/reloptions.c
+++ b/src/backend/access/common/reloptions.c
@@ -40,9 +40,9 @@
*
* To add an option:
*
- * (i) decide on a type (bool, integer, real, enum, string), name, default
- * value, upper and lower bounds (if applicable); for strings, consider a
- * validation routine.
+ * (i) decide on a type (bool, ternary, integer, real, enum, string), name,
+ * default value, upper and lower bounds (if applicable); for strings,
+ * consider a validation routine.
* (ii) add a record below (or use add_<type>_reloption).
* (iii) add it to the appropriate options struct (perhaps StdRdOptions)
* (iv) add it to the appropriate handling routine (perhaps
@@ -147,15 +147,6 @@ static relopt_bool boolRelOpts[] =
},
false
},
- {
- {
- "vacuum_truncate",
- "Enables vacuum to truncate empty pages at the end of this table",
- RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
- ShareUpdateExclusiveLock
- },
- true
- },
{
{
"deduplicate_items",
@@ -170,6 +161,21 @@ static relopt_bool boolRelOpts[] =
{{NULL}}
};
+static relopt_ternary ternaryRelOpts[] =
+{
+ {
+ {
+ "vacuum_truncate",
+ "Enables vacuum to truncate empty pages at the end of this table",
+ RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
+ ShareUpdateExclusiveLock
+ },
+ TERNARY_UNSET
+ },
+ /* list terminator */
+ {{NULL}}
+};
+
static relopt_int intRelOpts[] =
{
{
@@ -600,6 +606,13 @@ initialize_reloptions(void)
boolRelOpts[i].gen.lockmode));
j++;
}
+ for (i = 0; ternaryRelOpts[i].gen.name; i++)
+ {
+ Assert(DoLockModesConflict(ternaryRelOpts[i].gen.lockmode,
+ ternaryRelOpts[i].gen.lockmode));
+ j++;
+ }
+
for (i = 0; intRelOpts[i].gen.name; i++)
{
Assert(DoLockModesConflict(intRelOpts[i].gen.lockmode,
@@ -640,6 +653,14 @@ initialize_reloptions(void)
j++;
}
+ for (i = 0; ternaryRelOpts[i].gen.name; i++)
+ {
+ relOpts[j] = &ternaryRelOpts[i].gen;
+ relOpts[j]->type = RELOPT_TYPE_TERNARY;
+ relOpts[j]->namelen = strlen(relOpts[j]->name);
+ j++;
+ }
+
for (i = 0; intRelOpts[i].gen.name; i++)
{
relOpts[j] = &intRelOpts[i].gen;
@@ -800,6 +821,9 @@ allocate_reloption(bits32 kinds, int type, const char *name, const char *desc,
case RELOPT_TYPE_BOOL:
size = sizeof(relopt_bool);
break;
+ case RELOPT_TYPE_TERNARY:
+ size = sizeof(relopt_ternary);
+ break;
case RELOPT_TYPE_INT:
size = sizeof(relopt_int);
break;
@@ -883,6 +907,54 @@ add_local_bool_reloption(local_relopts *relopts, const char *name,
add_local_reloption(relopts, (relopt_gen *) newoption, offset);
}
+/*
+ * init_ternary_reloption
+ * Allocate and initialize a new ternary reloption
+ */
+static relopt_ternary *
+init_ternary_reloption(bits32 kinds, const char *name, const char *desc,
+ ternary default_val, LOCKMODE lockmode)
+{
+ relopt_ternary *newoption;
+
+ newoption = (relopt_ternary *) allocate_reloption(kinds,
+ RELOPT_TYPE_TERNARY, name, desc, lockmode);
+ newoption->default_val = default_val;
+
+ return newoption;
+}
+
+/*
+ * add_ternary_reloption
+ * Add a new ternary reloption
+ */
+void
+add_ternary_reloption(bits32 kinds, const char *name, const char *desc,
+ ternary default_val, LOCKMODE lockmode)
+{
+ relopt_ternary *newoption = init_ternary_reloption(kinds, name, desc,
+ default_val, lockmode);
+
+ add_reloption((relopt_gen *) newoption);
+}
+
+/*
+ * add_local_ternary_reloption
+ * Add a new ternary local reloption
+ *
+ * 'offset' is offset of ternary-typed field.
+ */
+void
+add_local_ternary_reloption(local_relopts *relopts, const char *name,
+ const char *desc, ternary default_val,
+ int offset)
+{
+ relopt_ternary *newoption = init_ternary_reloption(RELOPT_KIND_LOCAL,
+ name, desc,
+ default_val, 0);
+
+ add_local_reloption(relopts, (relopt_gen *) newoption, offset);
+}
/*
* init_real_reloption
@@ -1617,6 +1689,18 @@ parse_one_reloption(relopt_value *option, char *text_str, int text_len,
option->gen->name, value)));
}
break;
+ case RELOPT_TYPE_TERNARY:
+ {
+ bool b;
+ parsed = parse_bool(value, &b);
+ option->values.ternary_val = b ? TERNARY_TRUE : TERNARY_FALSE;
+ if (validate && !parsed)
+ ereport(ERROR,
+ (errcode(ERRCODE_INVALID_PARAMETER_VALUE),
+ errmsg("invalid value for ternary option \"%s\": %s",
+ option->gen->name, value)));
+ }
+ break;
case RELOPT_TYPE_INT:
{
relopt_int *optint = (relopt_int *) option->gen;
@@ -1780,17 +1864,6 @@ fillRelOptions(void *rdopts, Size basesize,
char *itempos = ((char *) rdopts) + elems[j].offset;
char *string_val;
- /*
- * If isset_offset is provided, store whether the reloption is
- * set there.
- */
- if (elems[j].isset_offset > 0)
- {
- char *setpos = ((char *) rdopts) + elems[j].isset_offset;
-
- *(bool *) setpos = options[i].isset;
- }
-
switch (options[i].gen->type)
{
case RELOPT_TYPE_BOOL:
@@ -1798,6 +1871,11 @@ fillRelOptions(void *rdopts, Size basesize,
options[i].values.bool_val :
((relopt_bool *) options[i].gen)->default_val;
break;
+ case RELOPT_TYPE_TERNARY:
+ *(ternary *) itempos = options[i].isset ?
+ options[i].values.ternary_val :
+ ((relopt_ternary *) options[i].gen)->default_val;
+ break;
case RELOPT_TYPE_INT:
*(int *) itempos = options[i].isset ?
options[i].values.int_val :
@@ -1912,8 +1990,8 @@ default_reloptions(Datum reloptions, bool validate, relopt_kind kind)
offsetof(StdRdOptions, parallel_workers)},
{"vacuum_index_cleanup", RELOPT_TYPE_ENUM,
offsetof(StdRdOptions, vacuum_index_cleanup)},
- {"vacuum_truncate", RELOPT_TYPE_BOOL,
- offsetof(StdRdOptions, vacuum_truncate), offsetof(StdRdOptions, vacuum_truncate_set)},
+ {"vacuum_truncate", RELOPT_TYPE_TERNARY,
+ offsetof(StdRdOptions, vacuum_truncate)},
{"vacuum_max_eager_freeze_failure_rate", RELOPT_TYPE_REAL,
offsetof(StdRdOptions, vacuum_max_eager_freeze_failure_rate)}
};
@@ -1993,7 +2071,6 @@ build_local_reloptions(local_relopts *relopts, Datum options, bool validate)
elems[i].optname = opt->option->name;
elems[i].opttype = opt->option->type;
elems[i].offset = opt->offset;
- elems[i].isset_offset = 0; /* not supported for local relopts yet */
i++;
}
diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c
index 733ef40ae7..977babff54 100644
--- a/src/backend/commands/vacuum.c
+++ b/src/backend/commands/vacuum.c
@@ -2221,9 +2221,9 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams params,
{
StdRdOptions *opts = (StdRdOptions *) rel->rd_options;
- if (opts && opts->vacuum_truncate_set)
+ if (opts && opts->vacuum_truncate != TERNARY_UNSET)
{
- if (opts->vacuum_truncate)
+ if (opts->vacuum_truncate == TERNARY_TRUE)
params.truncate = VACOPTVALUE_ENABLED;
else
params.truncate = VACOPTVALUE_DISABLED;
diff --git a/src/include/access/reloptions.h b/src/include/access/reloptions.h
index a604a4702c..a436697658 100644
--- a/src/include/access/reloptions.h
+++ b/src/include/access/reloptions.h
@@ -29,6 +29,7 @@
typedef enum relopt_type
{
RELOPT_TYPE_BOOL,
+ RELOPT_TYPE_TERNARY, /* on, off, unset */
RELOPT_TYPE_INT,
RELOPT_TYPE_REAL,
RELOPT_TYPE_ENUM,
@@ -80,6 +81,7 @@ typedef struct relopt_value
union
{
bool bool_val;
+ ternary ternary_val;
int int_val;
double real_val;
int enum_val;
@@ -94,6 +96,12 @@ typedef struct relopt_bool
bool default_val;
} relopt_bool;
+typedef struct relopt_rernary
+{
+ relopt_gen gen;
+ int default_val;
+} relopt_ternary;
+
typedef struct relopt_int
{
relopt_gen gen;
@@ -152,19 +160,6 @@ typedef struct
const char *optname; /* option's name */
relopt_type opttype; /* option's datatype */
int offset; /* offset of field in result struct */
-
- /*
- * isset_offset is an optional offset of a field in the result struct that
- * stores whether the option is explicitly set for the relation or if it
- * just picked up the default value. In most cases, this can be
- * accomplished by giving the reloption a special out-of-range default
- * value (e.g., some integer reloptions use -2), but this isn't always
- * possible. For example, a Boolean reloption cannot be given an
- * out-of-range default, so we need another way to discover the source of
- * its value. This offset is only used if given a value greater than
- * zero.
- */
- int isset_offset;
} relopt_parse_elt;
/* Local reloption definition */
@@ -195,6 +190,8 @@ typedef struct local_relopts
extern relopt_kind add_reloption_kind(void);
extern void add_bool_reloption(bits32 kinds, const char *name, const char *desc,
bool default_val, LOCKMODE lockmode);
+extern void add_ternary_reloption(bits32 kinds, const char *name,
+ const char *desc, int default_val, LOCKMODE lockmode);
extern void add_int_reloption(bits32 kinds, const char *name, const char *desc,
int default_val, int min_val, int max_val,
LOCKMODE lockmode);
@@ -214,6 +211,9 @@ extern void register_reloptions_validator(local_relopts *relopts,
extern void add_local_bool_reloption(local_relopts *relopts, const char *name,
const char *desc, bool default_val,
int offset);
+extern void add_local_ternary_reloption(local_relopts *relopts,
+ const char *name, const char *desc,
+ ternary default_val, int offset);
extern void add_local_int_reloption(local_relopts *relopts, const char *name,
const char *desc, int default_val,
int min_val, int max_val, int offset);
diff --git a/src/include/c.h b/src/include/c.h
index 39022f8a9d..9c59ccccf8 100644
--- a/src/include/c.h
+++ b/src/include/c.h
@@ -488,6 +488,22 @@ typedef void (*pg_funcptr_t) (void);
#include <stdbool.h>
+/*
+ * ternary
+ * Boolean value with an extrea "unset" option
+ *
+ * Ternary data type is used in relation options that can be "true", "false" or
+ * "unset". Since relation options are used deep inside the PostgreSQL code,
+ * this type is declared globally.
+*/
+
+typedef enum ternary
+{
+ TERNARY_FALSE = 0,
+ TERNARY_TRUE = 1,
+ TERNARY_UNSET = -1
+} ternary;
+
/* ----------------------------------------------------------------
* Section 3: standard system types
diff --git a/src/include/utils/rel.h b/src/include/utils/rel.h
index b552359915..7346d618f9 100644
--- a/src/include/utils/rel.h
+++ b/src/include/utils/rel.h
@@ -346,8 +346,7 @@ typedef struct StdRdOptions
bool user_catalog_table; /* use as an additional catalog relation */
int parallel_workers; /* max number of parallel workers */
StdRdOptIndexCleanup vacuum_index_cleanup; /* controls index vacuuming */
- bool vacuum_truncate; /* enables vacuum to truncate a relation */
- bool vacuum_truncate_set; /* whether vacuum_truncate is set */
+ ternary vacuum_truncate; /* enables vacuum to truncate a relation */
/*
* Fraction of pages in a relation that vacuum can eagerly scan and fail
--
2.39.2
--nextPart4155600.3ZeAukHxDK
Content-Disposition: attachment;
filename="v2a-0003-Add-alias-to-be-used-as-unset-state.patch"
Content-Transfer-Encoding: 7Bit
Content-Type: text/x-patch; charset="unicode-2-0-utf-8";
name="v2a-0003-Add-alias-to-be-used-as-unset-state.patch"
^ permalink raw reply [nested|flat] 868+ messages in thread
* [PATCH v2a 2/4] Introduce ternary reloptions
@ 2025-09-04 14:41 Nikolay Shaplov <[email protected]>
0 siblings, 0 replies; 868+ messages in thread
From: Nikolay Shaplov @ 2025-09-04 14:41 UTC (permalink / raw)
Introduce ternary reloption as a replacement for current `vacuum_truncate`
implementation. Remove `vacuum_truncate_set` additional flag and using
`TERNARY_UNSET` value instead.
---
src/backend/access/common/reloptions.c | 129 ++++++++++++++++++++-----
src/backend/commands/vacuum.c | 4 +-
src/include/access/reloptions.h | 26 ++---
src/include/c.h | 16 +++
src/include/utils/rel.h | 3 +-
5 files changed, 135 insertions(+), 43 deletions(-)
diff --git a/src/backend/access/common/reloptions.c b/src/backend/access/common/reloptions.c
index 0af3fea68f..f543f11001 100644
--- a/src/backend/access/common/reloptions.c
+++ b/src/backend/access/common/reloptions.c
@@ -40,9 +40,9 @@
*
* To add an option:
*
- * (i) decide on a type (bool, integer, real, enum, string), name, default
- * value, upper and lower bounds (if applicable); for strings, consider a
- * validation routine.
+ * (i) decide on a type (bool, ternary, integer, real, enum, string), name,
+ * default value, upper and lower bounds (if applicable); for strings,
+ * consider a validation routine.
* (ii) add a record below (or use add_<type>_reloption).
* (iii) add it to the appropriate options struct (perhaps StdRdOptions)
* (iv) add it to the appropriate handling routine (perhaps
@@ -147,15 +147,6 @@ static relopt_bool boolRelOpts[] =
},
false
},
- {
- {
- "vacuum_truncate",
- "Enables vacuum to truncate empty pages at the end of this table",
- RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
- ShareUpdateExclusiveLock
- },
- true
- },
{
{
"deduplicate_items",
@@ -170,6 +161,21 @@ static relopt_bool boolRelOpts[] =
{{NULL}}
};
+static relopt_ternary ternaryRelOpts[] =
+{
+ {
+ {
+ "vacuum_truncate",
+ "Enables vacuum to truncate empty pages at the end of this table",
+ RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
+ ShareUpdateExclusiveLock
+ },
+ TERNARY_UNSET
+ },
+ /* list terminator */
+ {{NULL}}
+};
+
static relopt_int intRelOpts[] =
{
{
@@ -600,6 +606,13 @@ initialize_reloptions(void)
boolRelOpts[i].gen.lockmode));
j++;
}
+ for (i = 0; ternaryRelOpts[i].gen.name; i++)
+ {
+ Assert(DoLockModesConflict(ternaryRelOpts[i].gen.lockmode,
+ ternaryRelOpts[i].gen.lockmode));
+ j++;
+ }
+
for (i = 0; intRelOpts[i].gen.name; i++)
{
Assert(DoLockModesConflict(intRelOpts[i].gen.lockmode,
@@ -640,6 +653,14 @@ initialize_reloptions(void)
j++;
}
+ for (i = 0; ternaryRelOpts[i].gen.name; i++)
+ {
+ relOpts[j] = &ternaryRelOpts[i].gen;
+ relOpts[j]->type = RELOPT_TYPE_TERNARY;
+ relOpts[j]->namelen = strlen(relOpts[j]->name);
+ j++;
+ }
+
for (i = 0; intRelOpts[i].gen.name; i++)
{
relOpts[j] = &intRelOpts[i].gen;
@@ -800,6 +821,9 @@ allocate_reloption(bits32 kinds, int type, const char *name, const char *desc,
case RELOPT_TYPE_BOOL:
size = sizeof(relopt_bool);
break;
+ case RELOPT_TYPE_TERNARY:
+ size = sizeof(relopt_ternary);
+ break;
case RELOPT_TYPE_INT:
size = sizeof(relopt_int);
break;
@@ -883,6 +907,54 @@ add_local_bool_reloption(local_relopts *relopts, const char *name,
add_local_reloption(relopts, (relopt_gen *) newoption, offset);
}
+/*
+ * init_ternary_reloption
+ * Allocate and initialize a new ternary reloption
+ */
+static relopt_ternary *
+init_ternary_reloption(bits32 kinds, const char *name, const char *desc,
+ ternary default_val, LOCKMODE lockmode)
+{
+ relopt_ternary *newoption;
+
+ newoption = (relopt_ternary *) allocate_reloption(kinds,
+ RELOPT_TYPE_TERNARY, name, desc, lockmode);
+ newoption->default_val = default_val;
+
+ return newoption;
+}
+
+/*
+ * add_ternary_reloption
+ * Add a new ternary reloption
+ */
+void
+add_ternary_reloption(bits32 kinds, const char *name, const char *desc,
+ ternary default_val, LOCKMODE lockmode)
+{
+ relopt_ternary *newoption = init_ternary_reloption(kinds, name, desc,
+ default_val, lockmode);
+
+ add_reloption((relopt_gen *) newoption);
+}
+
+/*
+ * add_local_ternary_reloption
+ * Add a new ternary local reloption
+ *
+ * 'offset' is offset of ternary-typed field.
+ */
+void
+add_local_ternary_reloption(local_relopts *relopts, const char *name,
+ const char *desc, ternary default_val,
+ int offset)
+{
+ relopt_ternary *newoption = init_ternary_reloption(RELOPT_KIND_LOCAL,
+ name, desc,
+ default_val, 0);
+
+ add_local_reloption(relopts, (relopt_gen *) newoption, offset);
+}
/*
* init_real_reloption
@@ -1617,6 +1689,18 @@ parse_one_reloption(relopt_value *option, char *text_str, int text_len,
option->gen->name, value)));
}
break;
+ case RELOPT_TYPE_TERNARY:
+ {
+ bool b;
+ parsed = parse_bool(value, &b);
+ option->values.ternary_val = b ? TERNARY_TRUE : TERNARY_FALSE;
+ if (validate && !parsed)
+ ereport(ERROR,
+ (errcode(ERRCODE_INVALID_PARAMETER_VALUE),
+ errmsg("invalid value for ternary option \"%s\": %s",
+ option->gen->name, value)));
+ }
+ break;
case RELOPT_TYPE_INT:
{
relopt_int *optint = (relopt_int *) option->gen;
@@ -1780,17 +1864,6 @@ fillRelOptions(void *rdopts, Size basesize,
char *itempos = ((char *) rdopts) + elems[j].offset;
char *string_val;
- /*
- * If isset_offset is provided, store whether the reloption is
- * set there.
- */
- if (elems[j].isset_offset > 0)
- {
- char *setpos = ((char *) rdopts) + elems[j].isset_offset;
-
- *(bool *) setpos = options[i].isset;
- }
-
switch (options[i].gen->type)
{
case RELOPT_TYPE_BOOL:
@@ -1798,6 +1871,11 @@ fillRelOptions(void *rdopts, Size basesize,
options[i].values.bool_val :
((relopt_bool *) options[i].gen)->default_val;
break;
+ case RELOPT_TYPE_TERNARY:
+ *(ternary *) itempos = options[i].isset ?
+ options[i].values.ternary_val :
+ ((relopt_ternary *) options[i].gen)->default_val;
+ break;
case RELOPT_TYPE_INT:
*(int *) itempos = options[i].isset ?
options[i].values.int_val :
@@ -1912,8 +1990,8 @@ default_reloptions(Datum reloptions, bool validate, relopt_kind kind)
offsetof(StdRdOptions, parallel_workers)},
{"vacuum_index_cleanup", RELOPT_TYPE_ENUM,
offsetof(StdRdOptions, vacuum_index_cleanup)},
- {"vacuum_truncate", RELOPT_TYPE_BOOL,
- offsetof(StdRdOptions, vacuum_truncate), offsetof(StdRdOptions, vacuum_truncate_set)},
+ {"vacuum_truncate", RELOPT_TYPE_TERNARY,
+ offsetof(StdRdOptions, vacuum_truncate)},
{"vacuum_max_eager_freeze_failure_rate", RELOPT_TYPE_REAL,
offsetof(StdRdOptions, vacuum_max_eager_freeze_failure_rate)}
};
@@ -1993,7 +2071,6 @@ build_local_reloptions(local_relopts *relopts, Datum options, bool validate)
elems[i].optname = opt->option->name;
elems[i].opttype = opt->option->type;
elems[i].offset = opt->offset;
- elems[i].isset_offset = 0; /* not supported for local relopts yet */
i++;
}
diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c
index 733ef40ae7..977babff54 100644
--- a/src/backend/commands/vacuum.c
+++ b/src/backend/commands/vacuum.c
@@ -2221,9 +2221,9 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams params,
{
StdRdOptions *opts = (StdRdOptions *) rel->rd_options;
- if (opts && opts->vacuum_truncate_set)
+ if (opts && opts->vacuum_truncate != TERNARY_UNSET)
{
- if (opts->vacuum_truncate)
+ if (opts->vacuum_truncate == TERNARY_TRUE)
params.truncate = VACOPTVALUE_ENABLED;
else
params.truncate = VACOPTVALUE_DISABLED;
diff --git a/src/include/access/reloptions.h b/src/include/access/reloptions.h
index a604a4702c..a436697658 100644
--- a/src/include/access/reloptions.h
+++ b/src/include/access/reloptions.h
@@ -29,6 +29,7 @@
typedef enum relopt_type
{
RELOPT_TYPE_BOOL,
+ RELOPT_TYPE_TERNARY, /* on, off, unset */
RELOPT_TYPE_INT,
RELOPT_TYPE_REAL,
RELOPT_TYPE_ENUM,
@@ -80,6 +81,7 @@ typedef struct relopt_value
union
{
bool bool_val;
+ ternary ternary_val;
int int_val;
double real_val;
int enum_val;
@@ -94,6 +96,12 @@ typedef struct relopt_bool
bool default_val;
} relopt_bool;
+typedef struct relopt_rernary
+{
+ relopt_gen gen;
+ int default_val;
+} relopt_ternary;
+
typedef struct relopt_int
{
relopt_gen gen;
@@ -152,19 +160,6 @@ typedef struct
const char *optname; /* option's name */
relopt_type opttype; /* option's datatype */
int offset; /* offset of field in result struct */
-
- /*
- * isset_offset is an optional offset of a field in the result struct that
- * stores whether the option is explicitly set for the relation or if it
- * just picked up the default value. In most cases, this can be
- * accomplished by giving the reloption a special out-of-range default
- * value (e.g., some integer reloptions use -2), but this isn't always
- * possible. For example, a Boolean reloption cannot be given an
- * out-of-range default, so we need another way to discover the source of
- * its value. This offset is only used if given a value greater than
- * zero.
- */
- int isset_offset;
} relopt_parse_elt;
/* Local reloption definition */
@@ -195,6 +190,8 @@ typedef struct local_relopts
extern relopt_kind add_reloption_kind(void);
extern void add_bool_reloption(bits32 kinds, const char *name, const char *desc,
bool default_val, LOCKMODE lockmode);
+extern void add_ternary_reloption(bits32 kinds, const char *name,
+ const char *desc, int default_val, LOCKMODE lockmode);
extern void add_int_reloption(bits32 kinds, const char *name, const char *desc,
int default_val, int min_val, int max_val,
LOCKMODE lockmode);
@@ -214,6 +211,9 @@ extern void register_reloptions_validator(local_relopts *relopts,
extern void add_local_bool_reloption(local_relopts *relopts, const char *name,
const char *desc, bool default_val,
int offset);
+extern void add_local_ternary_reloption(local_relopts *relopts,
+ const char *name, const char *desc,
+ ternary default_val, int offset);
extern void add_local_int_reloption(local_relopts *relopts, const char *name,
const char *desc, int default_val,
int min_val, int max_val, int offset);
diff --git a/src/include/c.h b/src/include/c.h
index 39022f8a9d..9c59ccccf8 100644
--- a/src/include/c.h
+++ b/src/include/c.h
@@ -488,6 +488,22 @@ typedef void (*pg_funcptr_t) (void);
#include <stdbool.h>
+/*
+ * ternary
+ * Boolean value with an extrea "unset" option
+ *
+ * Ternary data type is used in relation options that can be "true", "false" or
+ * "unset". Since relation options are used deep inside the PostgreSQL code,
+ * this type is declared globally.
+*/
+
+typedef enum ternary
+{
+ TERNARY_FALSE = 0,
+ TERNARY_TRUE = 1,
+ TERNARY_UNSET = -1
+} ternary;
+
/* ----------------------------------------------------------------
* Section 3: standard system types
diff --git a/src/include/utils/rel.h b/src/include/utils/rel.h
index b552359915..7346d618f9 100644
--- a/src/include/utils/rel.h
+++ b/src/include/utils/rel.h
@@ -346,8 +346,7 @@ typedef struct StdRdOptions
bool user_catalog_table; /* use as an additional catalog relation */
int parallel_workers; /* max number of parallel workers */
StdRdOptIndexCleanup vacuum_index_cleanup; /* controls index vacuuming */
- bool vacuum_truncate; /* enables vacuum to truncate a relation */
- bool vacuum_truncate_set; /* whether vacuum_truncate is set */
+ ternary vacuum_truncate; /* enables vacuum to truncate a relation */
/*
* Fraction of pages in a relation that vacuum can eagerly scan and fail
--
2.39.2
--nextPart4155600.3ZeAukHxDK
Content-Disposition: attachment;
filename="v2a-0003-Add-alias-to-be-used-as-unset-state.patch"
Content-Transfer-Encoding: 7Bit
Content-Type: text/x-patch; charset="unicode-2-0-utf-8";
name="v2a-0003-Add-alias-to-be-used-as-unset-state.patch"
^ permalink raw reply [nested|flat] 868+ messages in thread
* [PATCH v2 2/4] Introduce ternary reloptions
@ 2025-09-04 14:41 Nikolay Shaplov <[email protected]>
0 siblings, 0 replies; 868+ messages in thread
From: Nikolay Shaplov @ 2025-09-04 14:41 UTC (permalink / raw)
Introduce ternary reloption as a replacement for current `vacuum_truncate`
implementation. Remove `vacuum_truncate_set` additional flag and using
`TERNARY_UNSET` value instead.
---
src/backend/access/common/reloptions.c | 129 ++++++++++++++++++++-----
src/backend/commands/vacuum.c | 4 +-
src/include/access/reloptions.h | 26 ++---
src/include/c.h | 16 +++
src/include/utils/rel.h | 3 +-
5 files changed, 135 insertions(+), 43 deletions(-)
diff --git a/src/backend/access/common/reloptions.c b/src/backend/access/common/reloptions.c
index 0af3fea68f..f543f11001 100644
--- a/src/backend/access/common/reloptions.c
+++ b/src/backend/access/common/reloptions.c
@@ -40,9 +40,9 @@
*
* To add an option:
*
- * (i) decide on a type (bool, integer, real, enum, string), name, default
- * value, upper and lower bounds (if applicable); for strings, consider a
- * validation routine.
+ * (i) decide on a type (bool, ternary, integer, real, enum, string), name,
+ * default value, upper and lower bounds (if applicable); for strings,
+ * consider a validation routine.
* (ii) add a record below (or use add_<type>_reloption).
* (iii) add it to the appropriate options struct (perhaps StdRdOptions)
* (iv) add it to the appropriate handling routine (perhaps
@@ -147,15 +147,6 @@ static relopt_bool boolRelOpts[] =
},
false
},
- {
- {
- "vacuum_truncate",
- "Enables vacuum to truncate empty pages at the end of this table",
- RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
- ShareUpdateExclusiveLock
- },
- true
- },
{
{
"deduplicate_items",
@@ -170,6 +161,21 @@ static relopt_bool boolRelOpts[] =
{{NULL}}
};
+static relopt_ternary ternaryRelOpts[] =
+{
+ {
+ {
+ "vacuum_truncate",
+ "Enables vacuum to truncate empty pages at the end of this table",
+ RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
+ ShareUpdateExclusiveLock
+ },
+ TERNARY_UNSET
+ },
+ /* list terminator */
+ {{NULL}}
+};
+
static relopt_int intRelOpts[] =
{
{
@@ -600,6 +606,13 @@ initialize_reloptions(void)
boolRelOpts[i].gen.lockmode));
j++;
}
+ for (i = 0; ternaryRelOpts[i].gen.name; i++)
+ {
+ Assert(DoLockModesConflict(ternaryRelOpts[i].gen.lockmode,
+ ternaryRelOpts[i].gen.lockmode));
+ j++;
+ }
+
for (i = 0; intRelOpts[i].gen.name; i++)
{
Assert(DoLockModesConflict(intRelOpts[i].gen.lockmode,
@@ -640,6 +653,14 @@ initialize_reloptions(void)
j++;
}
+ for (i = 0; ternaryRelOpts[i].gen.name; i++)
+ {
+ relOpts[j] = &ternaryRelOpts[i].gen;
+ relOpts[j]->type = RELOPT_TYPE_TERNARY;
+ relOpts[j]->namelen = strlen(relOpts[j]->name);
+ j++;
+ }
+
for (i = 0; intRelOpts[i].gen.name; i++)
{
relOpts[j] = &intRelOpts[i].gen;
@@ -800,6 +821,9 @@ allocate_reloption(bits32 kinds, int type, const char *name, const char *desc,
case RELOPT_TYPE_BOOL:
size = sizeof(relopt_bool);
break;
+ case RELOPT_TYPE_TERNARY:
+ size = sizeof(relopt_ternary);
+ break;
case RELOPT_TYPE_INT:
size = sizeof(relopt_int);
break;
@@ -883,6 +907,54 @@ add_local_bool_reloption(local_relopts *relopts, const char *name,
add_local_reloption(relopts, (relopt_gen *) newoption, offset);
}
+/*
+ * init_ternary_reloption
+ * Allocate and initialize a new ternary reloption
+ */
+static relopt_ternary *
+init_ternary_reloption(bits32 kinds, const char *name, const char *desc,
+ ternary default_val, LOCKMODE lockmode)
+{
+ relopt_ternary *newoption;
+
+ newoption = (relopt_ternary *) allocate_reloption(kinds,
+ RELOPT_TYPE_TERNARY, name, desc, lockmode);
+ newoption->default_val = default_val;
+
+ return newoption;
+}
+
+/*
+ * add_ternary_reloption
+ * Add a new ternary reloption
+ */
+void
+add_ternary_reloption(bits32 kinds, const char *name, const char *desc,
+ ternary default_val, LOCKMODE lockmode)
+{
+ relopt_ternary *newoption = init_ternary_reloption(kinds, name, desc,
+ default_val, lockmode);
+
+ add_reloption((relopt_gen *) newoption);
+}
+
+/*
+ * add_local_ternary_reloption
+ * Add a new ternary local reloption
+ *
+ * 'offset' is offset of ternary-typed field.
+ */
+void
+add_local_ternary_reloption(local_relopts *relopts, const char *name,
+ const char *desc, ternary default_val,
+ int offset)
+{
+ relopt_ternary *newoption = init_ternary_reloption(RELOPT_KIND_LOCAL,
+ name, desc,
+ default_val, 0);
+
+ add_local_reloption(relopts, (relopt_gen *) newoption, offset);
+}
/*
* init_real_reloption
@@ -1617,6 +1689,18 @@ parse_one_reloption(relopt_value *option, char *text_str, int text_len,
option->gen->name, value)));
}
break;
+ case RELOPT_TYPE_TERNARY:
+ {
+ bool b;
+ parsed = parse_bool(value, &b);
+ option->values.ternary_val = b ? TERNARY_TRUE : TERNARY_FALSE;
+ if (validate && !parsed)
+ ereport(ERROR,
+ (errcode(ERRCODE_INVALID_PARAMETER_VALUE),
+ errmsg("invalid value for ternary option \"%s\": %s",
+ option->gen->name, value)));
+ }
+ break;
case RELOPT_TYPE_INT:
{
relopt_int *optint = (relopt_int *) option->gen;
@@ -1780,17 +1864,6 @@ fillRelOptions(void *rdopts, Size basesize,
char *itempos = ((char *) rdopts) + elems[j].offset;
char *string_val;
- /*
- * If isset_offset is provided, store whether the reloption is
- * set there.
- */
- if (elems[j].isset_offset > 0)
- {
- char *setpos = ((char *) rdopts) + elems[j].isset_offset;
-
- *(bool *) setpos = options[i].isset;
- }
-
switch (options[i].gen->type)
{
case RELOPT_TYPE_BOOL:
@@ -1798,6 +1871,11 @@ fillRelOptions(void *rdopts, Size basesize,
options[i].values.bool_val :
((relopt_bool *) options[i].gen)->default_val;
break;
+ case RELOPT_TYPE_TERNARY:
+ *(ternary *) itempos = options[i].isset ?
+ options[i].values.ternary_val :
+ ((relopt_ternary *) options[i].gen)->default_val;
+ break;
case RELOPT_TYPE_INT:
*(int *) itempos = options[i].isset ?
options[i].values.int_val :
@@ -1912,8 +1990,8 @@ default_reloptions(Datum reloptions, bool validate, relopt_kind kind)
offsetof(StdRdOptions, parallel_workers)},
{"vacuum_index_cleanup", RELOPT_TYPE_ENUM,
offsetof(StdRdOptions, vacuum_index_cleanup)},
- {"vacuum_truncate", RELOPT_TYPE_BOOL,
- offsetof(StdRdOptions, vacuum_truncate), offsetof(StdRdOptions, vacuum_truncate_set)},
+ {"vacuum_truncate", RELOPT_TYPE_TERNARY,
+ offsetof(StdRdOptions, vacuum_truncate)},
{"vacuum_max_eager_freeze_failure_rate", RELOPT_TYPE_REAL,
offsetof(StdRdOptions, vacuum_max_eager_freeze_failure_rate)}
};
@@ -1993,7 +2071,6 @@ build_local_reloptions(local_relopts *relopts, Datum options, bool validate)
elems[i].optname = opt->option->name;
elems[i].opttype = opt->option->type;
elems[i].offset = opt->offset;
- elems[i].isset_offset = 0; /* not supported for local relopts yet */
i++;
}
diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c
index 733ef40ae7..977babff54 100644
--- a/src/backend/commands/vacuum.c
+++ b/src/backend/commands/vacuum.c
@@ -2221,9 +2221,9 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams params,
{
StdRdOptions *opts = (StdRdOptions *) rel->rd_options;
- if (opts && opts->vacuum_truncate_set)
+ if (opts && opts->vacuum_truncate != TERNARY_UNSET)
{
- if (opts->vacuum_truncate)
+ if (opts->vacuum_truncate == TERNARY_TRUE)
params.truncate = VACOPTVALUE_ENABLED;
else
params.truncate = VACOPTVALUE_DISABLED;
diff --git a/src/include/access/reloptions.h b/src/include/access/reloptions.h
index a604a4702c..a436697658 100644
--- a/src/include/access/reloptions.h
+++ b/src/include/access/reloptions.h
@@ -29,6 +29,7 @@
typedef enum relopt_type
{
RELOPT_TYPE_BOOL,
+ RELOPT_TYPE_TERNARY, /* on, off, unset */
RELOPT_TYPE_INT,
RELOPT_TYPE_REAL,
RELOPT_TYPE_ENUM,
@@ -80,6 +81,7 @@ typedef struct relopt_value
union
{
bool bool_val;
+ ternary ternary_val;
int int_val;
double real_val;
int enum_val;
@@ -94,6 +96,12 @@ typedef struct relopt_bool
bool default_val;
} relopt_bool;
+typedef struct relopt_rernary
+{
+ relopt_gen gen;
+ int default_val;
+} relopt_ternary;
+
typedef struct relopt_int
{
relopt_gen gen;
@@ -152,19 +160,6 @@ typedef struct
const char *optname; /* option's name */
relopt_type opttype; /* option's datatype */
int offset; /* offset of field in result struct */
-
- /*
- * isset_offset is an optional offset of a field in the result struct that
- * stores whether the option is explicitly set for the relation or if it
- * just picked up the default value. In most cases, this can be
- * accomplished by giving the reloption a special out-of-range default
- * value (e.g., some integer reloptions use -2), but this isn't always
- * possible. For example, a Boolean reloption cannot be given an
- * out-of-range default, so we need another way to discover the source of
- * its value. This offset is only used if given a value greater than
- * zero.
- */
- int isset_offset;
} relopt_parse_elt;
/* Local reloption definition */
@@ -195,6 +190,8 @@ typedef struct local_relopts
extern relopt_kind add_reloption_kind(void);
extern void add_bool_reloption(bits32 kinds, const char *name, const char *desc,
bool default_val, LOCKMODE lockmode);
+extern void add_ternary_reloption(bits32 kinds, const char *name,
+ const char *desc, int default_val, LOCKMODE lockmode);
extern void add_int_reloption(bits32 kinds, const char *name, const char *desc,
int default_val, int min_val, int max_val,
LOCKMODE lockmode);
@@ -214,6 +211,9 @@ extern void register_reloptions_validator(local_relopts *relopts,
extern void add_local_bool_reloption(local_relopts *relopts, const char *name,
const char *desc, bool default_val,
int offset);
+extern void add_local_ternary_reloption(local_relopts *relopts,
+ const char *name, const char *desc,
+ ternary default_val, int offset);
extern void add_local_int_reloption(local_relopts *relopts, const char *name,
const char *desc, int default_val,
int min_val, int max_val, int offset);
diff --git a/src/include/c.h b/src/include/c.h
index 39022f8a9d..9c59ccccf8 100644
--- a/src/include/c.h
+++ b/src/include/c.h
@@ -488,6 +488,22 @@ typedef void (*pg_funcptr_t) (void);
#include <stdbool.h>
+/*
+ * ternary
+ * Boolean value with an extrea "unset" option
+ *
+ * Ternary data type is used in relation options that can be "true", "false" or
+ * "unset". Since relation options are used deep inside the PostgreSQL code,
+ * this type is declared globally.
+*/
+
+typedef enum ternary
+{
+ TERNARY_FALSE = 0,
+ TERNARY_TRUE = 1,
+ TERNARY_UNSET = -1
+} ternary;
+
/* ----------------------------------------------------------------
* Section 3: standard system types
diff --git a/src/include/utils/rel.h b/src/include/utils/rel.h
index b552359915..7346d618f9 100644
--- a/src/include/utils/rel.h
+++ b/src/include/utils/rel.h
@@ -346,8 +346,7 @@ typedef struct StdRdOptions
bool user_catalog_table; /* use as an additional catalog relation */
int parallel_workers; /* max number of parallel workers */
StdRdOptIndexCleanup vacuum_index_cleanup; /* controls index vacuuming */
- bool vacuum_truncate; /* enables vacuum to truncate a relation */
- bool vacuum_truncate_set; /* whether vacuum_truncate is set */
+ ternary vacuum_truncate; /* enables vacuum to truncate a relation */
/*
* Fraction of pages in a relation that vacuum can eagerly scan and fail
--
2.39.2
--nextPart5129932.5fSG56mABF
Content-Disposition: attachment;
filename="v2-0003-Add-alias-to-be-used-as-unset-state.patch"
Content-Transfer-Encoding: 7Bit
Content-Type: text/x-patch; charset="unicode-2-0-utf-8";
name="v2-0003-Add-alias-to-be-used-as-unset-state.patch"
^ permalink raw reply [nested|flat] 868+ messages in thread
* [PATCH v2 2/4] Introduce ternary reloptions
@ 2025-09-04 14:41 Nikolay Shaplov <[email protected]>
0 siblings, 0 replies; 868+ messages in thread
From: Nikolay Shaplov @ 2025-09-04 14:41 UTC (permalink / raw)
Introduce ternary reloption as a replacement for current `vacuum_truncate`
implementation. Remove `vacuum_truncate_set` additional flag and using
`TERNARY_UNSET` value instead.
---
src/backend/access/common/reloptions.c | 129 ++++++++++++++++++++-----
src/backend/commands/vacuum.c | 4 +-
src/include/access/reloptions.h | 26 ++---
src/include/c.h | 16 +++
src/include/utils/rel.h | 3 +-
5 files changed, 135 insertions(+), 43 deletions(-)
diff --git a/src/backend/access/common/reloptions.c b/src/backend/access/common/reloptions.c
index 0af3fea68f..f543f11001 100644
--- a/src/backend/access/common/reloptions.c
+++ b/src/backend/access/common/reloptions.c
@@ -40,9 +40,9 @@
*
* To add an option:
*
- * (i) decide on a type (bool, integer, real, enum, string), name, default
- * value, upper and lower bounds (if applicable); for strings, consider a
- * validation routine.
+ * (i) decide on a type (bool, ternary, integer, real, enum, string), name,
+ * default value, upper and lower bounds (if applicable); for strings,
+ * consider a validation routine.
* (ii) add a record below (or use add_<type>_reloption).
* (iii) add it to the appropriate options struct (perhaps StdRdOptions)
* (iv) add it to the appropriate handling routine (perhaps
@@ -147,15 +147,6 @@ static relopt_bool boolRelOpts[] =
},
false
},
- {
- {
- "vacuum_truncate",
- "Enables vacuum to truncate empty pages at the end of this table",
- RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
- ShareUpdateExclusiveLock
- },
- true
- },
{
{
"deduplicate_items",
@@ -170,6 +161,21 @@ static relopt_bool boolRelOpts[] =
{{NULL}}
};
+static relopt_ternary ternaryRelOpts[] =
+{
+ {
+ {
+ "vacuum_truncate",
+ "Enables vacuum to truncate empty pages at the end of this table",
+ RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
+ ShareUpdateExclusiveLock
+ },
+ TERNARY_UNSET
+ },
+ /* list terminator */
+ {{NULL}}
+};
+
static relopt_int intRelOpts[] =
{
{
@@ -600,6 +606,13 @@ initialize_reloptions(void)
boolRelOpts[i].gen.lockmode));
j++;
}
+ for (i = 0; ternaryRelOpts[i].gen.name; i++)
+ {
+ Assert(DoLockModesConflict(ternaryRelOpts[i].gen.lockmode,
+ ternaryRelOpts[i].gen.lockmode));
+ j++;
+ }
+
for (i = 0; intRelOpts[i].gen.name; i++)
{
Assert(DoLockModesConflict(intRelOpts[i].gen.lockmode,
@@ -640,6 +653,14 @@ initialize_reloptions(void)
j++;
}
+ for (i = 0; ternaryRelOpts[i].gen.name; i++)
+ {
+ relOpts[j] = &ternaryRelOpts[i].gen;
+ relOpts[j]->type = RELOPT_TYPE_TERNARY;
+ relOpts[j]->namelen = strlen(relOpts[j]->name);
+ j++;
+ }
+
for (i = 0; intRelOpts[i].gen.name; i++)
{
relOpts[j] = &intRelOpts[i].gen;
@@ -800,6 +821,9 @@ allocate_reloption(bits32 kinds, int type, const char *name, const char *desc,
case RELOPT_TYPE_BOOL:
size = sizeof(relopt_bool);
break;
+ case RELOPT_TYPE_TERNARY:
+ size = sizeof(relopt_ternary);
+ break;
case RELOPT_TYPE_INT:
size = sizeof(relopt_int);
break;
@@ -883,6 +907,54 @@ add_local_bool_reloption(local_relopts *relopts, const char *name,
add_local_reloption(relopts, (relopt_gen *) newoption, offset);
}
+/*
+ * init_ternary_reloption
+ * Allocate and initialize a new ternary reloption
+ */
+static relopt_ternary *
+init_ternary_reloption(bits32 kinds, const char *name, const char *desc,
+ ternary default_val, LOCKMODE lockmode)
+{
+ relopt_ternary *newoption;
+
+ newoption = (relopt_ternary *) allocate_reloption(kinds,
+ RELOPT_TYPE_TERNARY, name, desc, lockmode);
+ newoption->default_val = default_val;
+
+ return newoption;
+}
+
+/*
+ * add_ternary_reloption
+ * Add a new ternary reloption
+ */
+void
+add_ternary_reloption(bits32 kinds, const char *name, const char *desc,
+ ternary default_val, LOCKMODE lockmode)
+{
+ relopt_ternary *newoption = init_ternary_reloption(kinds, name, desc,
+ default_val, lockmode);
+
+ add_reloption((relopt_gen *) newoption);
+}
+
+/*
+ * add_local_ternary_reloption
+ * Add a new ternary local reloption
+ *
+ * 'offset' is offset of ternary-typed field.
+ */
+void
+add_local_ternary_reloption(local_relopts *relopts, const char *name,
+ const char *desc, ternary default_val,
+ int offset)
+{
+ relopt_ternary *newoption = init_ternary_reloption(RELOPT_KIND_LOCAL,
+ name, desc,
+ default_val, 0);
+
+ add_local_reloption(relopts, (relopt_gen *) newoption, offset);
+}
/*
* init_real_reloption
@@ -1617,6 +1689,18 @@ parse_one_reloption(relopt_value *option, char *text_str, int text_len,
option->gen->name, value)));
}
break;
+ case RELOPT_TYPE_TERNARY:
+ {
+ bool b;
+ parsed = parse_bool(value, &b);
+ option->values.ternary_val = b ? TERNARY_TRUE : TERNARY_FALSE;
+ if (validate && !parsed)
+ ereport(ERROR,
+ (errcode(ERRCODE_INVALID_PARAMETER_VALUE),
+ errmsg("invalid value for ternary option \"%s\": %s",
+ option->gen->name, value)));
+ }
+ break;
case RELOPT_TYPE_INT:
{
relopt_int *optint = (relopt_int *) option->gen;
@@ -1780,17 +1864,6 @@ fillRelOptions(void *rdopts, Size basesize,
char *itempos = ((char *) rdopts) + elems[j].offset;
char *string_val;
- /*
- * If isset_offset is provided, store whether the reloption is
- * set there.
- */
- if (elems[j].isset_offset > 0)
- {
- char *setpos = ((char *) rdopts) + elems[j].isset_offset;
-
- *(bool *) setpos = options[i].isset;
- }
-
switch (options[i].gen->type)
{
case RELOPT_TYPE_BOOL:
@@ -1798,6 +1871,11 @@ fillRelOptions(void *rdopts, Size basesize,
options[i].values.bool_val :
((relopt_bool *) options[i].gen)->default_val;
break;
+ case RELOPT_TYPE_TERNARY:
+ *(ternary *) itempos = options[i].isset ?
+ options[i].values.ternary_val :
+ ((relopt_ternary *) options[i].gen)->default_val;
+ break;
case RELOPT_TYPE_INT:
*(int *) itempos = options[i].isset ?
options[i].values.int_val :
@@ -1912,8 +1990,8 @@ default_reloptions(Datum reloptions, bool validate, relopt_kind kind)
offsetof(StdRdOptions, parallel_workers)},
{"vacuum_index_cleanup", RELOPT_TYPE_ENUM,
offsetof(StdRdOptions, vacuum_index_cleanup)},
- {"vacuum_truncate", RELOPT_TYPE_BOOL,
- offsetof(StdRdOptions, vacuum_truncate), offsetof(StdRdOptions, vacuum_truncate_set)},
+ {"vacuum_truncate", RELOPT_TYPE_TERNARY,
+ offsetof(StdRdOptions, vacuum_truncate)},
{"vacuum_max_eager_freeze_failure_rate", RELOPT_TYPE_REAL,
offsetof(StdRdOptions, vacuum_max_eager_freeze_failure_rate)}
};
@@ -1993,7 +2071,6 @@ build_local_reloptions(local_relopts *relopts, Datum options, bool validate)
elems[i].optname = opt->option->name;
elems[i].opttype = opt->option->type;
elems[i].offset = opt->offset;
- elems[i].isset_offset = 0; /* not supported for local relopts yet */
i++;
}
diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c
index 733ef40ae7..977babff54 100644
--- a/src/backend/commands/vacuum.c
+++ b/src/backend/commands/vacuum.c
@@ -2221,9 +2221,9 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams params,
{
StdRdOptions *opts = (StdRdOptions *) rel->rd_options;
- if (opts && opts->vacuum_truncate_set)
+ if (opts && opts->vacuum_truncate != TERNARY_UNSET)
{
- if (opts->vacuum_truncate)
+ if (opts->vacuum_truncate == TERNARY_TRUE)
params.truncate = VACOPTVALUE_ENABLED;
else
params.truncate = VACOPTVALUE_DISABLED;
diff --git a/src/include/access/reloptions.h b/src/include/access/reloptions.h
index a604a4702c..a436697658 100644
--- a/src/include/access/reloptions.h
+++ b/src/include/access/reloptions.h
@@ -29,6 +29,7 @@
typedef enum relopt_type
{
RELOPT_TYPE_BOOL,
+ RELOPT_TYPE_TERNARY, /* on, off, unset */
RELOPT_TYPE_INT,
RELOPT_TYPE_REAL,
RELOPT_TYPE_ENUM,
@@ -80,6 +81,7 @@ typedef struct relopt_value
union
{
bool bool_val;
+ ternary ternary_val;
int int_val;
double real_val;
int enum_val;
@@ -94,6 +96,12 @@ typedef struct relopt_bool
bool default_val;
} relopt_bool;
+typedef struct relopt_rernary
+{
+ relopt_gen gen;
+ int default_val;
+} relopt_ternary;
+
typedef struct relopt_int
{
relopt_gen gen;
@@ -152,19 +160,6 @@ typedef struct
const char *optname; /* option's name */
relopt_type opttype; /* option's datatype */
int offset; /* offset of field in result struct */
-
- /*
- * isset_offset is an optional offset of a field in the result struct that
- * stores whether the option is explicitly set for the relation or if it
- * just picked up the default value. In most cases, this can be
- * accomplished by giving the reloption a special out-of-range default
- * value (e.g., some integer reloptions use -2), but this isn't always
- * possible. For example, a Boolean reloption cannot be given an
- * out-of-range default, so we need another way to discover the source of
- * its value. This offset is only used if given a value greater than
- * zero.
- */
- int isset_offset;
} relopt_parse_elt;
/* Local reloption definition */
@@ -195,6 +190,8 @@ typedef struct local_relopts
extern relopt_kind add_reloption_kind(void);
extern void add_bool_reloption(bits32 kinds, const char *name, const char *desc,
bool default_val, LOCKMODE lockmode);
+extern void add_ternary_reloption(bits32 kinds, const char *name,
+ const char *desc, int default_val, LOCKMODE lockmode);
extern void add_int_reloption(bits32 kinds, const char *name, const char *desc,
int default_val, int min_val, int max_val,
LOCKMODE lockmode);
@@ -214,6 +211,9 @@ extern void register_reloptions_validator(local_relopts *relopts,
extern void add_local_bool_reloption(local_relopts *relopts, const char *name,
const char *desc, bool default_val,
int offset);
+extern void add_local_ternary_reloption(local_relopts *relopts,
+ const char *name, const char *desc,
+ ternary default_val, int offset);
extern void add_local_int_reloption(local_relopts *relopts, const char *name,
const char *desc, int default_val,
int min_val, int max_val, int offset);
diff --git a/src/include/c.h b/src/include/c.h
index 39022f8a9d..9c59ccccf8 100644
--- a/src/include/c.h
+++ b/src/include/c.h
@@ -488,6 +488,22 @@ typedef void (*pg_funcptr_t) (void);
#include <stdbool.h>
+/*
+ * ternary
+ * Boolean value with an extrea "unset" option
+ *
+ * Ternary data type is used in relation options that can be "true", "false" or
+ * "unset". Since relation options are used deep inside the PostgreSQL code,
+ * this type is declared globally.
+*/
+
+typedef enum ternary
+{
+ TERNARY_FALSE = 0,
+ TERNARY_TRUE = 1,
+ TERNARY_UNSET = -1
+} ternary;
+
/* ----------------------------------------------------------------
* Section 3: standard system types
diff --git a/src/include/utils/rel.h b/src/include/utils/rel.h
index b552359915..7346d618f9 100644
--- a/src/include/utils/rel.h
+++ b/src/include/utils/rel.h
@@ -346,8 +346,7 @@ typedef struct StdRdOptions
bool user_catalog_table; /* use as an additional catalog relation */
int parallel_workers; /* max number of parallel workers */
StdRdOptIndexCleanup vacuum_index_cleanup; /* controls index vacuuming */
- bool vacuum_truncate; /* enables vacuum to truncate a relation */
- bool vacuum_truncate_set; /* whether vacuum_truncate is set */
+ ternary vacuum_truncate; /* enables vacuum to truncate a relation */
/*
* Fraction of pages in a relation that vacuum can eagerly scan and fail
--
2.39.2
--nextPart5129932.5fSG56mABF
Content-Disposition: attachment;
filename="v2-0003-Add-alias-to-be-used-as-unset-state.patch"
Content-Transfer-Encoding: 7Bit
Content-Type: text/x-patch; charset="unicode-2-0-utf-8";
name="v2-0003-Add-alias-to-be-used-as-unset-state.patch"
^ permalink raw reply [nested|flat] 868+ messages in thread
* [PATCH v2 2/4] Introduce ternary reloptions
@ 2025-09-04 14:41 Nikolay Shaplov <[email protected]>
0 siblings, 0 replies; 868+ messages in thread
From: Nikolay Shaplov @ 2025-09-04 14:41 UTC (permalink / raw)
Introduce ternary reloption as a replacement for current `vacuum_truncate`
implementation. Remove `vacuum_truncate_set` additional flag and using
`TERNARY_UNSET` value instead.
---
src/backend/access/common/reloptions.c | 129 ++++++++++++++++++++-----
src/backend/commands/vacuum.c | 4 +-
src/include/access/reloptions.h | 26 ++---
src/include/c.h | 16 +++
src/include/utils/rel.h | 3 +-
5 files changed, 135 insertions(+), 43 deletions(-)
diff --git a/src/backend/access/common/reloptions.c b/src/backend/access/common/reloptions.c
index 0af3fea68f..f543f11001 100644
--- a/src/backend/access/common/reloptions.c
+++ b/src/backend/access/common/reloptions.c
@@ -40,9 +40,9 @@
*
* To add an option:
*
- * (i) decide on a type (bool, integer, real, enum, string), name, default
- * value, upper and lower bounds (if applicable); for strings, consider a
- * validation routine.
+ * (i) decide on a type (bool, ternary, integer, real, enum, string), name,
+ * default value, upper and lower bounds (if applicable); for strings,
+ * consider a validation routine.
* (ii) add a record below (or use add_<type>_reloption).
* (iii) add it to the appropriate options struct (perhaps StdRdOptions)
* (iv) add it to the appropriate handling routine (perhaps
@@ -147,15 +147,6 @@ static relopt_bool boolRelOpts[] =
},
false
},
- {
- {
- "vacuum_truncate",
- "Enables vacuum to truncate empty pages at the end of this table",
- RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
- ShareUpdateExclusiveLock
- },
- true
- },
{
{
"deduplicate_items",
@@ -170,6 +161,21 @@ static relopt_bool boolRelOpts[] =
{{NULL}}
};
+static relopt_ternary ternaryRelOpts[] =
+{
+ {
+ {
+ "vacuum_truncate",
+ "Enables vacuum to truncate empty pages at the end of this table",
+ RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
+ ShareUpdateExclusiveLock
+ },
+ TERNARY_UNSET
+ },
+ /* list terminator */
+ {{NULL}}
+};
+
static relopt_int intRelOpts[] =
{
{
@@ -600,6 +606,13 @@ initialize_reloptions(void)
boolRelOpts[i].gen.lockmode));
j++;
}
+ for (i = 0; ternaryRelOpts[i].gen.name; i++)
+ {
+ Assert(DoLockModesConflict(ternaryRelOpts[i].gen.lockmode,
+ ternaryRelOpts[i].gen.lockmode));
+ j++;
+ }
+
for (i = 0; intRelOpts[i].gen.name; i++)
{
Assert(DoLockModesConflict(intRelOpts[i].gen.lockmode,
@@ -640,6 +653,14 @@ initialize_reloptions(void)
j++;
}
+ for (i = 0; ternaryRelOpts[i].gen.name; i++)
+ {
+ relOpts[j] = &ternaryRelOpts[i].gen;
+ relOpts[j]->type = RELOPT_TYPE_TERNARY;
+ relOpts[j]->namelen = strlen(relOpts[j]->name);
+ j++;
+ }
+
for (i = 0; intRelOpts[i].gen.name; i++)
{
relOpts[j] = &intRelOpts[i].gen;
@@ -800,6 +821,9 @@ allocate_reloption(bits32 kinds, int type, const char *name, const char *desc,
case RELOPT_TYPE_BOOL:
size = sizeof(relopt_bool);
break;
+ case RELOPT_TYPE_TERNARY:
+ size = sizeof(relopt_ternary);
+ break;
case RELOPT_TYPE_INT:
size = sizeof(relopt_int);
break;
@@ -883,6 +907,54 @@ add_local_bool_reloption(local_relopts *relopts, const char *name,
add_local_reloption(relopts, (relopt_gen *) newoption, offset);
}
+/*
+ * init_ternary_reloption
+ * Allocate and initialize a new ternary reloption
+ */
+static relopt_ternary *
+init_ternary_reloption(bits32 kinds, const char *name, const char *desc,
+ ternary default_val, LOCKMODE lockmode)
+{
+ relopt_ternary *newoption;
+
+ newoption = (relopt_ternary *) allocate_reloption(kinds,
+ RELOPT_TYPE_TERNARY, name, desc, lockmode);
+ newoption->default_val = default_val;
+
+ return newoption;
+}
+
+/*
+ * add_ternary_reloption
+ * Add a new ternary reloption
+ */
+void
+add_ternary_reloption(bits32 kinds, const char *name, const char *desc,
+ ternary default_val, LOCKMODE lockmode)
+{
+ relopt_ternary *newoption = init_ternary_reloption(kinds, name, desc,
+ default_val, lockmode);
+
+ add_reloption((relopt_gen *) newoption);
+}
+
+/*
+ * add_local_ternary_reloption
+ * Add a new ternary local reloption
+ *
+ * 'offset' is offset of ternary-typed field.
+ */
+void
+add_local_ternary_reloption(local_relopts *relopts, const char *name,
+ const char *desc, ternary default_val,
+ int offset)
+{
+ relopt_ternary *newoption = init_ternary_reloption(RELOPT_KIND_LOCAL,
+ name, desc,
+ default_val, 0);
+
+ add_local_reloption(relopts, (relopt_gen *) newoption, offset);
+}
/*
* init_real_reloption
@@ -1617,6 +1689,18 @@ parse_one_reloption(relopt_value *option, char *text_str, int text_len,
option->gen->name, value)));
}
break;
+ case RELOPT_TYPE_TERNARY:
+ {
+ bool b;
+ parsed = parse_bool(value, &b);
+ option->values.ternary_val = b ? TERNARY_TRUE : TERNARY_FALSE;
+ if (validate && !parsed)
+ ereport(ERROR,
+ (errcode(ERRCODE_INVALID_PARAMETER_VALUE),
+ errmsg("invalid value for ternary option \"%s\": %s",
+ option->gen->name, value)));
+ }
+ break;
case RELOPT_TYPE_INT:
{
relopt_int *optint = (relopt_int *) option->gen;
@@ -1780,17 +1864,6 @@ fillRelOptions(void *rdopts, Size basesize,
char *itempos = ((char *) rdopts) + elems[j].offset;
char *string_val;
- /*
- * If isset_offset is provided, store whether the reloption is
- * set there.
- */
- if (elems[j].isset_offset > 0)
- {
- char *setpos = ((char *) rdopts) + elems[j].isset_offset;
-
- *(bool *) setpos = options[i].isset;
- }
-
switch (options[i].gen->type)
{
case RELOPT_TYPE_BOOL:
@@ -1798,6 +1871,11 @@ fillRelOptions(void *rdopts, Size basesize,
options[i].values.bool_val :
((relopt_bool *) options[i].gen)->default_val;
break;
+ case RELOPT_TYPE_TERNARY:
+ *(ternary *) itempos = options[i].isset ?
+ options[i].values.ternary_val :
+ ((relopt_ternary *) options[i].gen)->default_val;
+ break;
case RELOPT_TYPE_INT:
*(int *) itempos = options[i].isset ?
options[i].values.int_val :
@@ -1912,8 +1990,8 @@ default_reloptions(Datum reloptions, bool validate, relopt_kind kind)
offsetof(StdRdOptions, parallel_workers)},
{"vacuum_index_cleanup", RELOPT_TYPE_ENUM,
offsetof(StdRdOptions, vacuum_index_cleanup)},
- {"vacuum_truncate", RELOPT_TYPE_BOOL,
- offsetof(StdRdOptions, vacuum_truncate), offsetof(StdRdOptions, vacuum_truncate_set)},
+ {"vacuum_truncate", RELOPT_TYPE_TERNARY,
+ offsetof(StdRdOptions, vacuum_truncate)},
{"vacuum_max_eager_freeze_failure_rate", RELOPT_TYPE_REAL,
offsetof(StdRdOptions, vacuum_max_eager_freeze_failure_rate)}
};
@@ -1993,7 +2071,6 @@ build_local_reloptions(local_relopts *relopts, Datum options, bool validate)
elems[i].optname = opt->option->name;
elems[i].opttype = opt->option->type;
elems[i].offset = opt->offset;
- elems[i].isset_offset = 0; /* not supported for local relopts yet */
i++;
}
diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c
index 733ef40ae7..977babff54 100644
--- a/src/backend/commands/vacuum.c
+++ b/src/backend/commands/vacuum.c
@@ -2221,9 +2221,9 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams params,
{
StdRdOptions *opts = (StdRdOptions *) rel->rd_options;
- if (opts && opts->vacuum_truncate_set)
+ if (opts && opts->vacuum_truncate != TERNARY_UNSET)
{
- if (opts->vacuum_truncate)
+ if (opts->vacuum_truncate == TERNARY_TRUE)
params.truncate = VACOPTVALUE_ENABLED;
else
params.truncate = VACOPTVALUE_DISABLED;
diff --git a/src/include/access/reloptions.h b/src/include/access/reloptions.h
index a604a4702c..a436697658 100644
--- a/src/include/access/reloptions.h
+++ b/src/include/access/reloptions.h
@@ -29,6 +29,7 @@
typedef enum relopt_type
{
RELOPT_TYPE_BOOL,
+ RELOPT_TYPE_TERNARY, /* on, off, unset */
RELOPT_TYPE_INT,
RELOPT_TYPE_REAL,
RELOPT_TYPE_ENUM,
@@ -80,6 +81,7 @@ typedef struct relopt_value
union
{
bool bool_val;
+ ternary ternary_val;
int int_val;
double real_val;
int enum_val;
@@ -94,6 +96,12 @@ typedef struct relopt_bool
bool default_val;
} relopt_bool;
+typedef struct relopt_rernary
+{
+ relopt_gen gen;
+ int default_val;
+} relopt_ternary;
+
typedef struct relopt_int
{
relopt_gen gen;
@@ -152,19 +160,6 @@ typedef struct
const char *optname; /* option's name */
relopt_type opttype; /* option's datatype */
int offset; /* offset of field in result struct */
-
- /*
- * isset_offset is an optional offset of a field in the result struct that
- * stores whether the option is explicitly set for the relation or if it
- * just picked up the default value. In most cases, this can be
- * accomplished by giving the reloption a special out-of-range default
- * value (e.g., some integer reloptions use -2), but this isn't always
- * possible. For example, a Boolean reloption cannot be given an
- * out-of-range default, so we need another way to discover the source of
- * its value. This offset is only used if given a value greater than
- * zero.
- */
- int isset_offset;
} relopt_parse_elt;
/* Local reloption definition */
@@ -195,6 +190,8 @@ typedef struct local_relopts
extern relopt_kind add_reloption_kind(void);
extern void add_bool_reloption(bits32 kinds, const char *name, const char *desc,
bool default_val, LOCKMODE lockmode);
+extern void add_ternary_reloption(bits32 kinds, const char *name,
+ const char *desc, int default_val, LOCKMODE lockmode);
extern void add_int_reloption(bits32 kinds, const char *name, const char *desc,
int default_val, int min_val, int max_val,
LOCKMODE lockmode);
@@ -214,6 +211,9 @@ extern void register_reloptions_validator(local_relopts *relopts,
extern void add_local_bool_reloption(local_relopts *relopts, const char *name,
const char *desc, bool default_val,
int offset);
+extern void add_local_ternary_reloption(local_relopts *relopts,
+ const char *name, const char *desc,
+ ternary default_val, int offset);
extern void add_local_int_reloption(local_relopts *relopts, const char *name,
const char *desc, int default_val,
int min_val, int max_val, int offset);
diff --git a/src/include/c.h b/src/include/c.h
index 39022f8a9d..9c59ccccf8 100644
--- a/src/include/c.h
+++ b/src/include/c.h
@@ -488,6 +488,22 @@ typedef void (*pg_funcptr_t) (void);
#include <stdbool.h>
+/*
+ * ternary
+ * Boolean value with an extrea "unset" option
+ *
+ * Ternary data type is used in relation options that can be "true", "false" or
+ * "unset". Since relation options are used deep inside the PostgreSQL code,
+ * this type is declared globally.
+*/
+
+typedef enum ternary
+{
+ TERNARY_FALSE = 0,
+ TERNARY_TRUE = 1,
+ TERNARY_UNSET = -1
+} ternary;
+
/* ----------------------------------------------------------------
* Section 3: standard system types
diff --git a/src/include/utils/rel.h b/src/include/utils/rel.h
index b552359915..7346d618f9 100644
--- a/src/include/utils/rel.h
+++ b/src/include/utils/rel.h
@@ -346,8 +346,7 @@ typedef struct StdRdOptions
bool user_catalog_table; /* use as an additional catalog relation */
int parallel_workers; /* max number of parallel workers */
StdRdOptIndexCleanup vacuum_index_cleanup; /* controls index vacuuming */
- bool vacuum_truncate; /* enables vacuum to truncate a relation */
- bool vacuum_truncate_set; /* whether vacuum_truncate is set */
+ ternary vacuum_truncate; /* enables vacuum to truncate a relation */
/*
* Fraction of pages in a relation that vacuum can eagerly scan and fail
--
2.39.2
--nextPart5129932.5fSG56mABF
Content-Disposition: attachment;
filename="v2-0003-Add-alias-to-be-used-as-unset-state.patch"
Content-Transfer-Encoding: 7Bit
Content-Type: text/x-patch; charset="unicode-2-0-utf-8";
name="v2-0003-Add-alias-to-be-used-as-unset-state.patch"
^ permalink raw reply [nested|flat] 868+ messages in thread
* [PATCH v2a 2/4] Introduce ternary reloptions
@ 2025-09-04 14:41 Nikolay Shaplov <[email protected]>
0 siblings, 0 replies; 868+ messages in thread
From: Nikolay Shaplov @ 2025-09-04 14:41 UTC (permalink / raw)
Introduce ternary reloption as a replacement for current `vacuum_truncate`
implementation. Remove `vacuum_truncate_set` additional flag and using
`TERNARY_UNSET` value instead.
---
src/backend/access/common/reloptions.c | 129 ++++++++++++++++++++-----
src/backend/commands/vacuum.c | 4 +-
src/include/access/reloptions.h | 26 ++---
src/include/c.h | 16 +++
src/include/utils/rel.h | 3 +-
5 files changed, 135 insertions(+), 43 deletions(-)
diff --git a/src/backend/access/common/reloptions.c b/src/backend/access/common/reloptions.c
index 0af3fea68f..f543f11001 100644
--- a/src/backend/access/common/reloptions.c
+++ b/src/backend/access/common/reloptions.c
@@ -40,9 +40,9 @@
*
* To add an option:
*
- * (i) decide on a type (bool, integer, real, enum, string), name, default
- * value, upper and lower bounds (if applicable); for strings, consider a
- * validation routine.
+ * (i) decide on a type (bool, ternary, integer, real, enum, string), name,
+ * default value, upper and lower bounds (if applicable); for strings,
+ * consider a validation routine.
* (ii) add a record below (or use add_<type>_reloption).
* (iii) add it to the appropriate options struct (perhaps StdRdOptions)
* (iv) add it to the appropriate handling routine (perhaps
@@ -147,15 +147,6 @@ static relopt_bool boolRelOpts[] =
},
false
},
- {
- {
- "vacuum_truncate",
- "Enables vacuum to truncate empty pages at the end of this table",
- RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
- ShareUpdateExclusiveLock
- },
- true
- },
{
{
"deduplicate_items",
@@ -170,6 +161,21 @@ static relopt_bool boolRelOpts[] =
{{NULL}}
};
+static relopt_ternary ternaryRelOpts[] =
+{
+ {
+ {
+ "vacuum_truncate",
+ "Enables vacuum to truncate empty pages at the end of this table",
+ RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
+ ShareUpdateExclusiveLock
+ },
+ TERNARY_UNSET
+ },
+ /* list terminator */
+ {{NULL}}
+};
+
static relopt_int intRelOpts[] =
{
{
@@ -600,6 +606,13 @@ initialize_reloptions(void)
boolRelOpts[i].gen.lockmode));
j++;
}
+ for (i = 0; ternaryRelOpts[i].gen.name; i++)
+ {
+ Assert(DoLockModesConflict(ternaryRelOpts[i].gen.lockmode,
+ ternaryRelOpts[i].gen.lockmode));
+ j++;
+ }
+
for (i = 0; intRelOpts[i].gen.name; i++)
{
Assert(DoLockModesConflict(intRelOpts[i].gen.lockmode,
@@ -640,6 +653,14 @@ initialize_reloptions(void)
j++;
}
+ for (i = 0; ternaryRelOpts[i].gen.name; i++)
+ {
+ relOpts[j] = &ternaryRelOpts[i].gen;
+ relOpts[j]->type = RELOPT_TYPE_TERNARY;
+ relOpts[j]->namelen = strlen(relOpts[j]->name);
+ j++;
+ }
+
for (i = 0; intRelOpts[i].gen.name; i++)
{
relOpts[j] = &intRelOpts[i].gen;
@@ -800,6 +821,9 @@ allocate_reloption(bits32 kinds, int type, const char *name, const char *desc,
case RELOPT_TYPE_BOOL:
size = sizeof(relopt_bool);
break;
+ case RELOPT_TYPE_TERNARY:
+ size = sizeof(relopt_ternary);
+ break;
case RELOPT_TYPE_INT:
size = sizeof(relopt_int);
break;
@@ -883,6 +907,54 @@ add_local_bool_reloption(local_relopts *relopts, const char *name,
add_local_reloption(relopts, (relopt_gen *) newoption, offset);
}
+/*
+ * init_ternary_reloption
+ * Allocate and initialize a new ternary reloption
+ */
+static relopt_ternary *
+init_ternary_reloption(bits32 kinds, const char *name, const char *desc,
+ ternary default_val, LOCKMODE lockmode)
+{
+ relopt_ternary *newoption;
+
+ newoption = (relopt_ternary *) allocate_reloption(kinds,
+ RELOPT_TYPE_TERNARY, name, desc, lockmode);
+ newoption->default_val = default_val;
+
+ return newoption;
+}
+
+/*
+ * add_ternary_reloption
+ * Add a new ternary reloption
+ */
+void
+add_ternary_reloption(bits32 kinds, const char *name, const char *desc,
+ ternary default_val, LOCKMODE lockmode)
+{
+ relopt_ternary *newoption = init_ternary_reloption(kinds, name, desc,
+ default_val, lockmode);
+
+ add_reloption((relopt_gen *) newoption);
+}
+
+/*
+ * add_local_ternary_reloption
+ * Add a new ternary local reloption
+ *
+ * 'offset' is offset of ternary-typed field.
+ */
+void
+add_local_ternary_reloption(local_relopts *relopts, const char *name,
+ const char *desc, ternary default_val,
+ int offset)
+{
+ relopt_ternary *newoption = init_ternary_reloption(RELOPT_KIND_LOCAL,
+ name, desc,
+ default_val, 0);
+
+ add_local_reloption(relopts, (relopt_gen *) newoption, offset);
+}
/*
* init_real_reloption
@@ -1617,6 +1689,18 @@ parse_one_reloption(relopt_value *option, char *text_str, int text_len,
option->gen->name, value)));
}
break;
+ case RELOPT_TYPE_TERNARY:
+ {
+ bool b;
+ parsed = parse_bool(value, &b);
+ option->values.ternary_val = b ? TERNARY_TRUE : TERNARY_FALSE;
+ if (validate && !parsed)
+ ereport(ERROR,
+ (errcode(ERRCODE_INVALID_PARAMETER_VALUE),
+ errmsg("invalid value for ternary option \"%s\": %s",
+ option->gen->name, value)));
+ }
+ break;
case RELOPT_TYPE_INT:
{
relopt_int *optint = (relopt_int *) option->gen;
@@ -1780,17 +1864,6 @@ fillRelOptions(void *rdopts, Size basesize,
char *itempos = ((char *) rdopts) + elems[j].offset;
char *string_val;
- /*
- * If isset_offset is provided, store whether the reloption is
- * set there.
- */
- if (elems[j].isset_offset > 0)
- {
- char *setpos = ((char *) rdopts) + elems[j].isset_offset;
-
- *(bool *) setpos = options[i].isset;
- }
-
switch (options[i].gen->type)
{
case RELOPT_TYPE_BOOL:
@@ -1798,6 +1871,11 @@ fillRelOptions(void *rdopts, Size basesize,
options[i].values.bool_val :
((relopt_bool *) options[i].gen)->default_val;
break;
+ case RELOPT_TYPE_TERNARY:
+ *(ternary *) itempos = options[i].isset ?
+ options[i].values.ternary_val :
+ ((relopt_ternary *) options[i].gen)->default_val;
+ break;
case RELOPT_TYPE_INT:
*(int *) itempos = options[i].isset ?
options[i].values.int_val :
@@ -1912,8 +1990,8 @@ default_reloptions(Datum reloptions, bool validate, relopt_kind kind)
offsetof(StdRdOptions, parallel_workers)},
{"vacuum_index_cleanup", RELOPT_TYPE_ENUM,
offsetof(StdRdOptions, vacuum_index_cleanup)},
- {"vacuum_truncate", RELOPT_TYPE_BOOL,
- offsetof(StdRdOptions, vacuum_truncate), offsetof(StdRdOptions, vacuum_truncate_set)},
+ {"vacuum_truncate", RELOPT_TYPE_TERNARY,
+ offsetof(StdRdOptions, vacuum_truncate)},
{"vacuum_max_eager_freeze_failure_rate", RELOPT_TYPE_REAL,
offsetof(StdRdOptions, vacuum_max_eager_freeze_failure_rate)}
};
@@ -1993,7 +2071,6 @@ build_local_reloptions(local_relopts *relopts, Datum options, bool validate)
elems[i].optname = opt->option->name;
elems[i].opttype = opt->option->type;
elems[i].offset = opt->offset;
- elems[i].isset_offset = 0; /* not supported for local relopts yet */
i++;
}
diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c
index 733ef40ae7..977babff54 100644
--- a/src/backend/commands/vacuum.c
+++ b/src/backend/commands/vacuum.c
@@ -2221,9 +2221,9 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams params,
{
StdRdOptions *opts = (StdRdOptions *) rel->rd_options;
- if (opts && opts->vacuum_truncate_set)
+ if (opts && opts->vacuum_truncate != TERNARY_UNSET)
{
- if (opts->vacuum_truncate)
+ if (opts->vacuum_truncate == TERNARY_TRUE)
params.truncate = VACOPTVALUE_ENABLED;
else
params.truncate = VACOPTVALUE_DISABLED;
diff --git a/src/include/access/reloptions.h b/src/include/access/reloptions.h
index a604a4702c..a436697658 100644
--- a/src/include/access/reloptions.h
+++ b/src/include/access/reloptions.h
@@ -29,6 +29,7 @@
typedef enum relopt_type
{
RELOPT_TYPE_BOOL,
+ RELOPT_TYPE_TERNARY, /* on, off, unset */
RELOPT_TYPE_INT,
RELOPT_TYPE_REAL,
RELOPT_TYPE_ENUM,
@@ -80,6 +81,7 @@ typedef struct relopt_value
union
{
bool bool_val;
+ ternary ternary_val;
int int_val;
double real_val;
int enum_val;
@@ -94,6 +96,12 @@ typedef struct relopt_bool
bool default_val;
} relopt_bool;
+typedef struct relopt_rernary
+{
+ relopt_gen gen;
+ int default_val;
+} relopt_ternary;
+
typedef struct relopt_int
{
relopt_gen gen;
@@ -152,19 +160,6 @@ typedef struct
const char *optname; /* option's name */
relopt_type opttype; /* option's datatype */
int offset; /* offset of field in result struct */
-
- /*
- * isset_offset is an optional offset of a field in the result struct that
- * stores whether the option is explicitly set for the relation or if it
- * just picked up the default value. In most cases, this can be
- * accomplished by giving the reloption a special out-of-range default
- * value (e.g., some integer reloptions use -2), but this isn't always
- * possible. For example, a Boolean reloption cannot be given an
- * out-of-range default, so we need another way to discover the source of
- * its value. This offset is only used if given a value greater than
- * zero.
- */
- int isset_offset;
} relopt_parse_elt;
/* Local reloption definition */
@@ -195,6 +190,8 @@ typedef struct local_relopts
extern relopt_kind add_reloption_kind(void);
extern void add_bool_reloption(bits32 kinds, const char *name, const char *desc,
bool default_val, LOCKMODE lockmode);
+extern void add_ternary_reloption(bits32 kinds, const char *name,
+ const char *desc, int default_val, LOCKMODE lockmode);
extern void add_int_reloption(bits32 kinds, const char *name, const char *desc,
int default_val, int min_val, int max_val,
LOCKMODE lockmode);
@@ -214,6 +211,9 @@ extern void register_reloptions_validator(local_relopts *relopts,
extern void add_local_bool_reloption(local_relopts *relopts, const char *name,
const char *desc, bool default_val,
int offset);
+extern void add_local_ternary_reloption(local_relopts *relopts,
+ const char *name, const char *desc,
+ ternary default_val, int offset);
extern void add_local_int_reloption(local_relopts *relopts, const char *name,
const char *desc, int default_val,
int min_val, int max_val, int offset);
diff --git a/src/include/c.h b/src/include/c.h
index 39022f8a9d..9c59ccccf8 100644
--- a/src/include/c.h
+++ b/src/include/c.h
@@ -488,6 +488,22 @@ typedef void (*pg_funcptr_t) (void);
#include <stdbool.h>
+/*
+ * ternary
+ * Boolean value with an extrea "unset" option
+ *
+ * Ternary data type is used in relation options that can be "true", "false" or
+ * "unset". Since relation options are used deep inside the PostgreSQL code,
+ * this type is declared globally.
+*/
+
+typedef enum ternary
+{
+ TERNARY_FALSE = 0,
+ TERNARY_TRUE = 1,
+ TERNARY_UNSET = -1
+} ternary;
+
/* ----------------------------------------------------------------
* Section 3: standard system types
diff --git a/src/include/utils/rel.h b/src/include/utils/rel.h
index b552359915..7346d618f9 100644
--- a/src/include/utils/rel.h
+++ b/src/include/utils/rel.h
@@ -346,8 +346,7 @@ typedef struct StdRdOptions
bool user_catalog_table; /* use as an additional catalog relation */
int parallel_workers; /* max number of parallel workers */
StdRdOptIndexCleanup vacuum_index_cleanup; /* controls index vacuuming */
- bool vacuum_truncate; /* enables vacuum to truncate a relation */
- bool vacuum_truncate_set; /* whether vacuum_truncate is set */
+ ternary vacuum_truncate; /* enables vacuum to truncate a relation */
/*
* Fraction of pages in a relation that vacuum can eagerly scan and fail
--
2.39.2
--nextPart4155600.3ZeAukHxDK
Content-Disposition: attachment;
filename="v2a-0003-Add-alias-to-be-used-as-unset-state.patch"
Content-Transfer-Encoding: 7Bit
Content-Type: text/x-patch; charset="unicode-2-0-utf-8";
name="v2a-0003-Add-alias-to-be-used-as-unset-state.patch"
^ permalink raw reply [nested|flat] 868+ messages in thread
* [PATCH v2a 2/4] Introduce ternary reloptions
@ 2025-09-04 14:41 Nikolay Shaplov <[email protected]>
0 siblings, 0 replies; 868+ messages in thread
From: Nikolay Shaplov @ 2025-09-04 14:41 UTC (permalink / raw)
Introduce ternary reloption as a replacement for current `vacuum_truncate`
implementation. Remove `vacuum_truncate_set` additional flag and using
`TERNARY_UNSET` value instead.
---
src/backend/access/common/reloptions.c | 129 ++++++++++++++++++++-----
src/backend/commands/vacuum.c | 4 +-
src/include/access/reloptions.h | 26 ++---
src/include/c.h | 16 +++
src/include/utils/rel.h | 3 +-
5 files changed, 135 insertions(+), 43 deletions(-)
diff --git a/src/backend/access/common/reloptions.c b/src/backend/access/common/reloptions.c
index 0af3fea68f..f543f11001 100644
--- a/src/backend/access/common/reloptions.c
+++ b/src/backend/access/common/reloptions.c
@@ -40,9 +40,9 @@
*
* To add an option:
*
- * (i) decide on a type (bool, integer, real, enum, string), name, default
- * value, upper and lower bounds (if applicable); for strings, consider a
- * validation routine.
+ * (i) decide on a type (bool, ternary, integer, real, enum, string), name,
+ * default value, upper and lower bounds (if applicable); for strings,
+ * consider a validation routine.
* (ii) add a record below (or use add_<type>_reloption).
* (iii) add it to the appropriate options struct (perhaps StdRdOptions)
* (iv) add it to the appropriate handling routine (perhaps
@@ -147,15 +147,6 @@ static relopt_bool boolRelOpts[] =
},
false
},
- {
- {
- "vacuum_truncate",
- "Enables vacuum to truncate empty pages at the end of this table",
- RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
- ShareUpdateExclusiveLock
- },
- true
- },
{
{
"deduplicate_items",
@@ -170,6 +161,21 @@ static relopt_bool boolRelOpts[] =
{{NULL}}
};
+static relopt_ternary ternaryRelOpts[] =
+{
+ {
+ {
+ "vacuum_truncate",
+ "Enables vacuum to truncate empty pages at the end of this table",
+ RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
+ ShareUpdateExclusiveLock
+ },
+ TERNARY_UNSET
+ },
+ /* list terminator */
+ {{NULL}}
+};
+
static relopt_int intRelOpts[] =
{
{
@@ -600,6 +606,13 @@ initialize_reloptions(void)
boolRelOpts[i].gen.lockmode));
j++;
}
+ for (i = 0; ternaryRelOpts[i].gen.name; i++)
+ {
+ Assert(DoLockModesConflict(ternaryRelOpts[i].gen.lockmode,
+ ternaryRelOpts[i].gen.lockmode));
+ j++;
+ }
+
for (i = 0; intRelOpts[i].gen.name; i++)
{
Assert(DoLockModesConflict(intRelOpts[i].gen.lockmode,
@@ -640,6 +653,14 @@ initialize_reloptions(void)
j++;
}
+ for (i = 0; ternaryRelOpts[i].gen.name; i++)
+ {
+ relOpts[j] = &ternaryRelOpts[i].gen;
+ relOpts[j]->type = RELOPT_TYPE_TERNARY;
+ relOpts[j]->namelen = strlen(relOpts[j]->name);
+ j++;
+ }
+
for (i = 0; intRelOpts[i].gen.name; i++)
{
relOpts[j] = &intRelOpts[i].gen;
@@ -800,6 +821,9 @@ allocate_reloption(bits32 kinds, int type, const char *name, const char *desc,
case RELOPT_TYPE_BOOL:
size = sizeof(relopt_bool);
break;
+ case RELOPT_TYPE_TERNARY:
+ size = sizeof(relopt_ternary);
+ break;
case RELOPT_TYPE_INT:
size = sizeof(relopt_int);
break;
@@ -883,6 +907,54 @@ add_local_bool_reloption(local_relopts *relopts, const char *name,
add_local_reloption(relopts, (relopt_gen *) newoption, offset);
}
+/*
+ * init_ternary_reloption
+ * Allocate and initialize a new ternary reloption
+ */
+static relopt_ternary *
+init_ternary_reloption(bits32 kinds, const char *name, const char *desc,
+ ternary default_val, LOCKMODE lockmode)
+{
+ relopt_ternary *newoption;
+
+ newoption = (relopt_ternary *) allocate_reloption(kinds,
+ RELOPT_TYPE_TERNARY, name, desc, lockmode);
+ newoption->default_val = default_val;
+
+ return newoption;
+}
+
+/*
+ * add_ternary_reloption
+ * Add a new ternary reloption
+ */
+void
+add_ternary_reloption(bits32 kinds, const char *name, const char *desc,
+ ternary default_val, LOCKMODE lockmode)
+{
+ relopt_ternary *newoption = init_ternary_reloption(kinds, name, desc,
+ default_val, lockmode);
+
+ add_reloption((relopt_gen *) newoption);
+}
+
+/*
+ * add_local_ternary_reloption
+ * Add a new ternary local reloption
+ *
+ * 'offset' is offset of ternary-typed field.
+ */
+void
+add_local_ternary_reloption(local_relopts *relopts, const char *name,
+ const char *desc, ternary default_val,
+ int offset)
+{
+ relopt_ternary *newoption = init_ternary_reloption(RELOPT_KIND_LOCAL,
+ name, desc,
+ default_val, 0);
+
+ add_local_reloption(relopts, (relopt_gen *) newoption, offset);
+}
/*
* init_real_reloption
@@ -1617,6 +1689,18 @@ parse_one_reloption(relopt_value *option, char *text_str, int text_len,
option->gen->name, value)));
}
break;
+ case RELOPT_TYPE_TERNARY:
+ {
+ bool b;
+ parsed = parse_bool(value, &b);
+ option->values.ternary_val = b ? TERNARY_TRUE : TERNARY_FALSE;
+ if (validate && !parsed)
+ ereport(ERROR,
+ (errcode(ERRCODE_INVALID_PARAMETER_VALUE),
+ errmsg("invalid value for ternary option \"%s\": %s",
+ option->gen->name, value)));
+ }
+ break;
case RELOPT_TYPE_INT:
{
relopt_int *optint = (relopt_int *) option->gen;
@@ -1780,17 +1864,6 @@ fillRelOptions(void *rdopts, Size basesize,
char *itempos = ((char *) rdopts) + elems[j].offset;
char *string_val;
- /*
- * If isset_offset is provided, store whether the reloption is
- * set there.
- */
- if (elems[j].isset_offset > 0)
- {
- char *setpos = ((char *) rdopts) + elems[j].isset_offset;
-
- *(bool *) setpos = options[i].isset;
- }
-
switch (options[i].gen->type)
{
case RELOPT_TYPE_BOOL:
@@ -1798,6 +1871,11 @@ fillRelOptions(void *rdopts, Size basesize,
options[i].values.bool_val :
((relopt_bool *) options[i].gen)->default_val;
break;
+ case RELOPT_TYPE_TERNARY:
+ *(ternary *) itempos = options[i].isset ?
+ options[i].values.ternary_val :
+ ((relopt_ternary *) options[i].gen)->default_val;
+ break;
case RELOPT_TYPE_INT:
*(int *) itempos = options[i].isset ?
options[i].values.int_val :
@@ -1912,8 +1990,8 @@ default_reloptions(Datum reloptions, bool validate, relopt_kind kind)
offsetof(StdRdOptions, parallel_workers)},
{"vacuum_index_cleanup", RELOPT_TYPE_ENUM,
offsetof(StdRdOptions, vacuum_index_cleanup)},
- {"vacuum_truncate", RELOPT_TYPE_BOOL,
- offsetof(StdRdOptions, vacuum_truncate), offsetof(StdRdOptions, vacuum_truncate_set)},
+ {"vacuum_truncate", RELOPT_TYPE_TERNARY,
+ offsetof(StdRdOptions, vacuum_truncate)},
{"vacuum_max_eager_freeze_failure_rate", RELOPT_TYPE_REAL,
offsetof(StdRdOptions, vacuum_max_eager_freeze_failure_rate)}
};
@@ -1993,7 +2071,6 @@ build_local_reloptions(local_relopts *relopts, Datum options, bool validate)
elems[i].optname = opt->option->name;
elems[i].opttype = opt->option->type;
elems[i].offset = opt->offset;
- elems[i].isset_offset = 0; /* not supported for local relopts yet */
i++;
}
diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c
index 733ef40ae7..977babff54 100644
--- a/src/backend/commands/vacuum.c
+++ b/src/backend/commands/vacuum.c
@@ -2221,9 +2221,9 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams params,
{
StdRdOptions *opts = (StdRdOptions *) rel->rd_options;
- if (opts && opts->vacuum_truncate_set)
+ if (opts && opts->vacuum_truncate != TERNARY_UNSET)
{
- if (opts->vacuum_truncate)
+ if (opts->vacuum_truncate == TERNARY_TRUE)
params.truncate = VACOPTVALUE_ENABLED;
else
params.truncate = VACOPTVALUE_DISABLED;
diff --git a/src/include/access/reloptions.h b/src/include/access/reloptions.h
index a604a4702c..a436697658 100644
--- a/src/include/access/reloptions.h
+++ b/src/include/access/reloptions.h
@@ -29,6 +29,7 @@
typedef enum relopt_type
{
RELOPT_TYPE_BOOL,
+ RELOPT_TYPE_TERNARY, /* on, off, unset */
RELOPT_TYPE_INT,
RELOPT_TYPE_REAL,
RELOPT_TYPE_ENUM,
@@ -80,6 +81,7 @@ typedef struct relopt_value
union
{
bool bool_val;
+ ternary ternary_val;
int int_val;
double real_val;
int enum_val;
@@ -94,6 +96,12 @@ typedef struct relopt_bool
bool default_val;
} relopt_bool;
+typedef struct relopt_rernary
+{
+ relopt_gen gen;
+ int default_val;
+} relopt_ternary;
+
typedef struct relopt_int
{
relopt_gen gen;
@@ -152,19 +160,6 @@ typedef struct
const char *optname; /* option's name */
relopt_type opttype; /* option's datatype */
int offset; /* offset of field in result struct */
-
- /*
- * isset_offset is an optional offset of a field in the result struct that
- * stores whether the option is explicitly set for the relation or if it
- * just picked up the default value. In most cases, this can be
- * accomplished by giving the reloption a special out-of-range default
- * value (e.g., some integer reloptions use -2), but this isn't always
- * possible. For example, a Boolean reloption cannot be given an
- * out-of-range default, so we need another way to discover the source of
- * its value. This offset is only used if given a value greater than
- * zero.
- */
- int isset_offset;
} relopt_parse_elt;
/* Local reloption definition */
@@ -195,6 +190,8 @@ typedef struct local_relopts
extern relopt_kind add_reloption_kind(void);
extern void add_bool_reloption(bits32 kinds, const char *name, const char *desc,
bool default_val, LOCKMODE lockmode);
+extern void add_ternary_reloption(bits32 kinds, const char *name,
+ const char *desc, int default_val, LOCKMODE lockmode);
extern void add_int_reloption(bits32 kinds, const char *name, const char *desc,
int default_val, int min_val, int max_val,
LOCKMODE lockmode);
@@ -214,6 +211,9 @@ extern void register_reloptions_validator(local_relopts *relopts,
extern void add_local_bool_reloption(local_relopts *relopts, const char *name,
const char *desc, bool default_val,
int offset);
+extern void add_local_ternary_reloption(local_relopts *relopts,
+ const char *name, const char *desc,
+ ternary default_val, int offset);
extern void add_local_int_reloption(local_relopts *relopts, const char *name,
const char *desc, int default_val,
int min_val, int max_val, int offset);
diff --git a/src/include/c.h b/src/include/c.h
index 39022f8a9d..9c59ccccf8 100644
--- a/src/include/c.h
+++ b/src/include/c.h
@@ -488,6 +488,22 @@ typedef void (*pg_funcptr_t) (void);
#include <stdbool.h>
+/*
+ * ternary
+ * Boolean value with an extrea "unset" option
+ *
+ * Ternary data type is used in relation options that can be "true", "false" or
+ * "unset". Since relation options are used deep inside the PostgreSQL code,
+ * this type is declared globally.
+*/
+
+typedef enum ternary
+{
+ TERNARY_FALSE = 0,
+ TERNARY_TRUE = 1,
+ TERNARY_UNSET = -1
+} ternary;
+
/* ----------------------------------------------------------------
* Section 3: standard system types
diff --git a/src/include/utils/rel.h b/src/include/utils/rel.h
index b552359915..7346d618f9 100644
--- a/src/include/utils/rel.h
+++ b/src/include/utils/rel.h
@@ -346,8 +346,7 @@ typedef struct StdRdOptions
bool user_catalog_table; /* use as an additional catalog relation */
int parallel_workers; /* max number of parallel workers */
StdRdOptIndexCleanup vacuum_index_cleanup; /* controls index vacuuming */
- bool vacuum_truncate; /* enables vacuum to truncate a relation */
- bool vacuum_truncate_set; /* whether vacuum_truncate is set */
+ ternary vacuum_truncate; /* enables vacuum to truncate a relation */
/*
* Fraction of pages in a relation that vacuum can eagerly scan and fail
--
2.39.2
--nextPart4155600.3ZeAukHxDK
Content-Disposition: attachment;
filename="v2a-0003-Add-alias-to-be-used-as-unset-state.patch"
Content-Transfer-Encoding: 7Bit
Content-Type: text/x-patch; charset="unicode-2-0-utf-8";
name="v2a-0003-Add-alias-to-be-used-as-unset-state.patch"
^ permalink raw reply [nested|flat] 868+ messages in thread
* [PATCH v2 2/4] Introduce ternary reloptions
@ 2025-09-04 14:41 Nikolay Shaplov <[email protected]>
0 siblings, 0 replies; 868+ messages in thread
From: Nikolay Shaplov @ 2025-09-04 14:41 UTC (permalink / raw)
Introduce ternary reloption as a replacement for current `vacuum_truncate`
implementation. Remove `vacuum_truncate_set` additional flag and using
`TERNARY_UNSET` value instead.
---
src/backend/access/common/reloptions.c | 129 ++++++++++++++++++++-----
src/backend/commands/vacuum.c | 4 +-
src/include/access/reloptions.h | 26 ++---
src/include/c.h | 16 +++
src/include/utils/rel.h | 3 +-
5 files changed, 135 insertions(+), 43 deletions(-)
diff --git a/src/backend/access/common/reloptions.c b/src/backend/access/common/reloptions.c
index 0af3fea68f..f543f11001 100644
--- a/src/backend/access/common/reloptions.c
+++ b/src/backend/access/common/reloptions.c
@@ -40,9 +40,9 @@
*
* To add an option:
*
- * (i) decide on a type (bool, integer, real, enum, string), name, default
- * value, upper and lower bounds (if applicable); for strings, consider a
- * validation routine.
+ * (i) decide on a type (bool, ternary, integer, real, enum, string), name,
+ * default value, upper and lower bounds (if applicable); for strings,
+ * consider a validation routine.
* (ii) add a record below (or use add_<type>_reloption).
* (iii) add it to the appropriate options struct (perhaps StdRdOptions)
* (iv) add it to the appropriate handling routine (perhaps
@@ -147,15 +147,6 @@ static relopt_bool boolRelOpts[] =
},
false
},
- {
- {
- "vacuum_truncate",
- "Enables vacuum to truncate empty pages at the end of this table",
- RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
- ShareUpdateExclusiveLock
- },
- true
- },
{
{
"deduplicate_items",
@@ -170,6 +161,21 @@ static relopt_bool boolRelOpts[] =
{{NULL}}
};
+static relopt_ternary ternaryRelOpts[] =
+{
+ {
+ {
+ "vacuum_truncate",
+ "Enables vacuum to truncate empty pages at the end of this table",
+ RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
+ ShareUpdateExclusiveLock
+ },
+ TERNARY_UNSET
+ },
+ /* list terminator */
+ {{NULL}}
+};
+
static relopt_int intRelOpts[] =
{
{
@@ -600,6 +606,13 @@ initialize_reloptions(void)
boolRelOpts[i].gen.lockmode));
j++;
}
+ for (i = 0; ternaryRelOpts[i].gen.name; i++)
+ {
+ Assert(DoLockModesConflict(ternaryRelOpts[i].gen.lockmode,
+ ternaryRelOpts[i].gen.lockmode));
+ j++;
+ }
+
for (i = 0; intRelOpts[i].gen.name; i++)
{
Assert(DoLockModesConflict(intRelOpts[i].gen.lockmode,
@@ -640,6 +653,14 @@ initialize_reloptions(void)
j++;
}
+ for (i = 0; ternaryRelOpts[i].gen.name; i++)
+ {
+ relOpts[j] = &ternaryRelOpts[i].gen;
+ relOpts[j]->type = RELOPT_TYPE_TERNARY;
+ relOpts[j]->namelen = strlen(relOpts[j]->name);
+ j++;
+ }
+
for (i = 0; intRelOpts[i].gen.name; i++)
{
relOpts[j] = &intRelOpts[i].gen;
@@ -800,6 +821,9 @@ allocate_reloption(bits32 kinds, int type, const char *name, const char *desc,
case RELOPT_TYPE_BOOL:
size = sizeof(relopt_bool);
break;
+ case RELOPT_TYPE_TERNARY:
+ size = sizeof(relopt_ternary);
+ break;
case RELOPT_TYPE_INT:
size = sizeof(relopt_int);
break;
@@ -883,6 +907,54 @@ add_local_bool_reloption(local_relopts *relopts, const char *name,
add_local_reloption(relopts, (relopt_gen *) newoption, offset);
}
+/*
+ * init_ternary_reloption
+ * Allocate and initialize a new ternary reloption
+ */
+static relopt_ternary *
+init_ternary_reloption(bits32 kinds, const char *name, const char *desc,
+ ternary default_val, LOCKMODE lockmode)
+{
+ relopt_ternary *newoption;
+
+ newoption = (relopt_ternary *) allocate_reloption(kinds,
+ RELOPT_TYPE_TERNARY, name, desc, lockmode);
+ newoption->default_val = default_val;
+
+ return newoption;
+}
+
+/*
+ * add_ternary_reloption
+ * Add a new ternary reloption
+ */
+void
+add_ternary_reloption(bits32 kinds, const char *name, const char *desc,
+ ternary default_val, LOCKMODE lockmode)
+{
+ relopt_ternary *newoption = init_ternary_reloption(kinds, name, desc,
+ default_val, lockmode);
+
+ add_reloption((relopt_gen *) newoption);
+}
+
+/*
+ * add_local_ternary_reloption
+ * Add a new ternary local reloption
+ *
+ * 'offset' is offset of ternary-typed field.
+ */
+void
+add_local_ternary_reloption(local_relopts *relopts, const char *name,
+ const char *desc, ternary default_val,
+ int offset)
+{
+ relopt_ternary *newoption = init_ternary_reloption(RELOPT_KIND_LOCAL,
+ name, desc,
+ default_val, 0);
+
+ add_local_reloption(relopts, (relopt_gen *) newoption, offset);
+}
/*
* init_real_reloption
@@ -1617,6 +1689,18 @@ parse_one_reloption(relopt_value *option, char *text_str, int text_len,
option->gen->name, value)));
}
break;
+ case RELOPT_TYPE_TERNARY:
+ {
+ bool b;
+ parsed = parse_bool(value, &b);
+ option->values.ternary_val = b ? TERNARY_TRUE : TERNARY_FALSE;
+ if (validate && !parsed)
+ ereport(ERROR,
+ (errcode(ERRCODE_INVALID_PARAMETER_VALUE),
+ errmsg("invalid value for ternary option \"%s\": %s",
+ option->gen->name, value)));
+ }
+ break;
case RELOPT_TYPE_INT:
{
relopt_int *optint = (relopt_int *) option->gen;
@@ -1780,17 +1864,6 @@ fillRelOptions(void *rdopts, Size basesize,
char *itempos = ((char *) rdopts) + elems[j].offset;
char *string_val;
- /*
- * If isset_offset is provided, store whether the reloption is
- * set there.
- */
- if (elems[j].isset_offset > 0)
- {
- char *setpos = ((char *) rdopts) + elems[j].isset_offset;
-
- *(bool *) setpos = options[i].isset;
- }
-
switch (options[i].gen->type)
{
case RELOPT_TYPE_BOOL:
@@ -1798,6 +1871,11 @@ fillRelOptions(void *rdopts, Size basesize,
options[i].values.bool_val :
((relopt_bool *) options[i].gen)->default_val;
break;
+ case RELOPT_TYPE_TERNARY:
+ *(ternary *) itempos = options[i].isset ?
+ options[i].values.ternary_val :
+ ((relopt_ternary *) options[i].gen)->default_val;
+ break;
case RELOPT_TYPE_INT:
*(int *) itempos = options[i].isset ?
options[i].values.int_val :
@@ -1912,8 +1990,8 @@ default_reloptions(Datum reloptions, bool validate, relopt_kind kind)
offsetof(StdRdOptions, parallel_workers)},
{"vacuum_index_cleanup", RELOPT_TYPE_ENUM,
offsetof(StdRdOptions, vacuum_index_cleanup)},
- {"vacuum_truncate", RELOPT_TYPE_BOOL,
- offsetof(StdRdOptions, vacuum_truncate), offsetof(StdRdOptions, vacuum_truncate_set)},
+ {"vacuum_truncate", RELOPT_TYPE_TERNARY,
+ offsetof(StdRdOptions, vacuum_truncate)},
{"vacuum_max_eager_freeze_failure_rate", RELOPT_TYPE_REAL,
offsetof(StdRdOptions, vacuum_max_eager_freeze_failure_rate)}
};
@@ -1993,7 +2071,6 @@ build_local_reloptions(local_relopts *relopts, Datum options, bool validate)
elems[i].optname = opt->option->name;
elems[i].opttype = opt->option->type;
elems[i].offset = opt->offset;
- elems[i].isset_offset = 0; /* not supported for local relopts yet */
i++;
}
diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c
index 733ef40ae7..977babff54 100644
--- a/src/backend/commands/vacuum.c
+++ b/src/backend/commands/vacuum.c
@@ -2221,9 +2221,9 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams params,
{
StdRdOptions *opts = (StdRdOptions *) rel->rd_options;
- if (opts && opts->vacuum_truncate_set)
+ if (opts && opts->vacuum_truncate != TERNARY_UNSET)
{
- if (opts->vacuum_truncate)
+ if (opts->vacuum_truncate == TERNARY_TRUE)
params.truncate = VACOPTVALUE_ENABLED;
else
params.truncate = VACOPTVALUE_DISABLED;
diff --git a/src/include/access/reloptions.h b/src/include/access/reloptions.h
index a604a4702c..a436697658 100644
--- a/src/include/access/reloptions.h
+++ b/src/include/access/reloptions.h
@@ -29,6 +29,7 @@
typedef enum relopt_type
{
RELOPT_TYPE_BOOL,
+ RELOPT_TYPE_TERNARY, /* on, off, unset */
RELOPT_TYPE_INT,
RELOPT_TYPE_REAL,
RELOPT_TYPE_ENUM,
@@ -80,6 +81,7 @@ typedef struct relopt_value
union
{
bool bool_val;
+ ternary ternary_val;
int int_val;
double real_val;
int enum_val;
@@ -94,6 +96,12 @@ typedef struct relopt_bool
bool default_val;
} relopt_bool;
+typedef struct relopt_rernary
+{
+ relopt_gen gen;
+ int default_val;
+} relopt_ternary;
+
typedef struct relopt_int
{
relopt_gen gen;
@@ -152,19 +160,6 @@ typedef struct
const char *optname; /* option's name */
relopt_type opttype; /* option's datatype */
int offset; /* offset of field in result struct */
-
- /*
- * isset_offset is an optional offset of a field in the result struct that
- * stores whether the option is explicitly set for the relation or if it
- * just picked up the default value. In most cases, this can be
- * accomplished by giving the reloption a special out-of-range default
- * value (e.g., some integer reloptions use -2), but this isn't always
- * possible. For example, a Boolean reloption cannot be given an
- * out-of-range default, so we need another way to discover the source of
- * its value. This offset is only used if given a value greater than
- * zero.
- */
- int isset_offset;
} relopt_parse_elt;
/* Local reloption definition */
@@ -195,6 +190,8 @@ typedef struct local_relopts
extern relopt_kind add_reloption_kind(void);
extern void add_bool_reloption(bits32 kinds, const char *name, const char *desc,
bool default_val, LOCKMODE lockmode);
+extern void add_ternary_reloption(bits32 kinds, const char *name,
+ const char *desc, int default_val, LOCKMODE lockmode);
extern void add_int_reloption(bits32 kinds, const char *name, const char *desc,
int default_val, int min_val, int max_val,
LOCKMODE lockmode);
@@ -214,6 +211,9 @@ extern void register_reloptions_validator(local_relopts *relopts,
extern void add_local_bool_reloption(local_relopts *relopts, const char *name,
const char *desc, bool default_val,
int offset);
+extern void add_local_ternary_reloption(local_relopts *relopts,
+ const char *name, const char *desc,
+ ternary default_val, int offset);
extern void add_local_int_reloption(local_relopts *relopts, const char *name,
const char *desc, int default_val,
int min_val, int max_val, int offset);
diff --git a/src/include/c.h b/src/include/c.h
index 39022f8a9d..9c59ccccf8 100644
--- a/src/include/c.h
+++ b/src/include/c.h
@@ -488,6 +488,22 @@ typedef void (*pg_funcptr_t) (void);
#include <stdbool.h>
+/*
+ * ternary
+ * Boolean value with an extrea "unset" option
+ *
+ * Ternary data type is used in relation options that can be "true", "false" or
+ * "unset". Since relation options are used deep inside the PostgreSQL code,
+ * this type is declared globally.
+*/
+
+typedef enum ternary
+{
+ TERNARY_FALSE = 0,
+ TERNARY_TRUE = 1,
+ TERNARY_UNSET = -1
+} ternary;
+
/* ----------------------------------------------------------------
* Section 3: standard system types
diff --git a/src/include/utils/rel.h b/src/include/utils/rel.h
index b552359915..7346d618f9 100644
--- a/src/include/utils/rel.h
+++ b/src/include/utils/rel.h
@@ -346,8 +346,7 @@ typedef struct StdRdOptions
bool user_catalog_table; /* use as an additional catalog relation */
int parallel_workers; /* max number of parallel workers */
StdRdOptIndexCleanup vacuum_index_cleanup; /* controls index vacuuming */
- bool vacuum_truncate; /* enables vacuum to truncate a relation */
- bool vacuum_truncate_set; /* whether vacuum_truncate is set */
+ ternary vacuum_truncate; /* enables vacuum to truncate a relation */
/*
* Fraction of pages in a relation that vacuum can eagerly scan and fail
--
2.39.2
--nextPart5129932.5fSG56mABF
Content-Disposition: attachment;
filename="v2-0003-Add-alias-to-be-used-as-unset-state.patch"
Content-Transfer-Encoding: 7Bit
Content-Type: text/x-patch; charset="unicode-2-0-utf-8";
name="v2-0003-Add-alias-to-be-used-as-unset-state.patch"
^ permalink raw reply [nested|flat] 868+ messages in thread
* [PATCH v2a 2/4] Introduce ternary reloptions
@ 2025-09-04 14:41 Nikolay Shaplov <[email protected]>
0 siblings, 0 replies; 868+ messages in thread
From: Nikolay Shaplov @ 2025-09-04 14:41 UTC (permalink / raw)
Introduce ternary reloption as a replacement for current `vacuum_truncate`
implementation. Remove `vacuum_truncate_set` additional flag and using
`TERNARY_UNSET` value instead.
---
src/backend/access/common/reloptions.c | 129 ++++++++++++++++++++-----
src/backend/commands/vacuum.c | 4 +-
src/include/access/reloptions.h | 26 ++---
src/include/c.h | 16 +++
src/include/utils/rel.h | 3 +-
5 files changed, 135 insertions(+), 43 deletions(-)
diff --git a/src/backend/access/common/reloptions.c b/src/backend/access/common/reloptions.c
index 0af3fea68f..f543f11001 100644
--- a/src/backend/access/common/reloptions.c
+++ b/src/backend/access/common/reloptions.c
@@ -40,9 +40,9 @@
*
* To add an option:
*
- * (i) decide on a type (bool, integer, real, enum, string), name, default
- * value, upper and lower bounds (if applicable); for strings, consider a
- * validation routine.
+ * (i) decide on a type (bool, ternary, integer, real, enum, string), name,
+ * default value, upper and lower bounds (if applicable); for strings,
+ * consider a validation routine.
* (ii) add a record below (or use add_<type>_reloption).
* (iii) add it to the appropriate options struct (perhaps StdRdOptions)
* (iv) add it to the appropriate handling routine (perhaps
@@ -147,15 +147,6 @@ static relopt_bool boolRelOpts[] =
},
false
},
- {
- {
- "vacuum_truncate",
- "Enables vacuum to truncate empty pages at the end of this table",
- RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
- ShareUpdateExclusiveLock
- },
- true
- },
{
{
"deduplicate_items",
@@ -170,6 +161,21 @@ static relopt_bool boolRelOpts[] =
{{NULL}}
};
+static relopt_ternary ternaryRelOpts[] =
+{
+ {
+ {
+ "vacuum_truncate",
+ "Enables vacuum to truncate empty pages at the end of this table",
+ RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
+ ShareUpdateExclusiveLock
+ },
+ TERNARY_UNSET
+ },
+ /* list terminator */
+ {{NULL}}
+};
+
static relopt_int intRelOpts[] =
{
{
@@ -600,6 +606,13 @@ initialize_reloptions(void)
boolRelOpts[i].gen.lockmode));
j++;
}
+ for (i = 0; ternaryRelOpts[i].gen.name; i++)
+ {
+ Assert(DoLockModesConflict(ternaryRelOpts[i].gen.lockmode,
+ ternaryRelOpts[i].gen.lockmode));
+ j++;
+ }
+
for (i = 0; intRelOpts[i].gen.name; i++)
{
Assert(DoLockModesConflict(intRelOpts[i].gen.lockmode,
@@ -640,6 +653,14 @@ initialize_reloptions(void)
j++;
}
+ for (i = 0; ternaryRelOpts[i].gen.name; i++)
+ {
+ relOpts[j] = &ternaryRelOpts[i].gen;
+ relOpts[j]->type = RELOPT_TYPE_TERNARY;
+ relOpts[j]->namelen = strlen(relOpts[j]->name);
+ j++;
+ }
+
for (i = 0; intRelOpts[i].gen.name; i++)
{
relOpts[j] = &intRelOpts[i].gen;
@@ -800,6 +821,9 @@ allocate_reloption(bits32 kinds, int type, const char *name, const char *desc,
case RELOPT_TYPE_BOOL:
size = sizeof(relopt_bool);
break;
+ case RELOPT_TYPE_TERNARY:
+ size = sizeof(relopt_ternary);
+ break;
case RELOPT_TYPE_INT:
size = sizeof(relopt_int);
break;
@@ -883,6 +907,54 @@ add_local_bool_reloption(local_relopts *relopts, const char *name,
add_local_reloption(relopts, (relopt_gen *) newoption, offset);
}
+/*
+ * init_ternary_reloption
+ * Allocate and initialize a new ternary reloption
+ */
+static relopt_ternary *
+init_ternary_reloption(bits32 kinds, const char *name, const char *desc,
+ ternary default_val, LOCKMODE lockmode)
+{
+ relopt_ternary *newoption;
+
+ newoption = (relopt_ternary *) allocate_reloption(kinds,
+ RELOPT_TYPE_TERNARY, name, desc, lockmode);
+ newoption->default_val = default_val;
+
+ return newoption;
+}
+
+/*
+ * add_ternary_reloption
+ * Add a new ternary reloption
+ */
+void
+add_ternary_reloption(bits32 kinds, const char *name, const char *desc,
+ ternary default_val, LOCKMODE lockmode)
+{
+ relopt_ternary *newoption = init_ternary_reloption(kinds, name, desc,
+ default_val, lockmode);
+
+ add_reloption((relopt_gen *) newoption);
+}
+
+/*
+ * add_local_ternary_reloption
+ * Add a new ternary local reloption
+ *
+ * 'offset' is offset of ternary-typed field.
+ */
+void
+add_local_ternary_reloption(local_relopts *relopts, const char *name,
+ const char *desc, ternary default_val,
+ int offset)
+{
+ relopt_ternary *newoption = init_ternary_reloption(RELOPT_KIND_LOCAL,
+ name, desc,
+ default_val, 0);
+
+ add_local_reloption(relopts, (relopt_gen *) newoption, offset);
+}
/*
* init_real_reloption
@@ -1617,6 +1689,18 @@ parse_one_reloption(relopt_value *option, char *text_str, int text_len,
option->gen->name, value)));
}
break;
+ case RELOPT_TYPE_TERNARY:
+ {
+ bool b;
+ parsed = parse_bool(value, &b);
+ option->values.ternary_val = b ? TERNARY_TRUE : TERNARY_FALSE;
+ if (validate && !parsed)
+ ereport(ERROR,
+ (errcode(ERRCODE_INVALID_PARAMETER_VALUE),
+ errmsg("invalid value for ternary option \"%s\": %s",
+ option->gen->name, value)));
+ }
+ break;
case RELOPT_TYPE_INT:
{
relopt_int *optint = (relopt_int *) option->gen;
@@ -1780,17 +1864,6 @@ fillRelOptions(void *rdopts, Size basesize,
char *itempos = ((char *) rdopts) + elems[j].offset;
char *string_val;
- /*
- * If isset_offset is provided, store whether the reloption is
- * set there.
- */
- if (elems[j].isset_offset > 0)
- {
- char *setpos = ((char *) rdopts) + elems[j].isset_offset;
-
- *(bool *) setpos = options[i].isset;
- }
-
switch (options[i].gen->type)
{
case RELOPT_TYPE_BOOL:
@@ -1798,6 +1871,11 @@ fillRelOptions(void *rdopts, Size basesize,
options[i].values.bool_val :
((relopt_bool *) options[i].gen)->default_val;
break;
+ case RELOPT_TYPE_TERNARY:
+ *(ternary *) itempos = options[i].isset ?
+ options[i].values.ternary_val :
+ ((relopt_ternary *) options[i].gen)->default_val;
+ break;
case RELOPT_TYPE_INT:
*(int *) itempos = options[i].isset ?
options[i].values.int_val :
@@ -1912,8 +1990,8 @@ default_reloptions(Datum reloptions, bool validate, relopt_kind kind)
offsetof(StdRdOptions, parallel_workers)},
{"vacuum_index_cleanup", RELOPT_TYPE_ENUM,
offsetof(StdRdOptions, vacuum_index_cleanup)},
- {"vacuum_truncate", RELOPT_TYPE_BOOL,
- offsetof(StdRdOptions, vacuum_truncate), offsetof(StdRdOptions, vacuum_truncate_set)},
+ {"vacuum_truncate", RELOPT_TYPE_TERNARY,
+ offsetof(StdRdOptions, vacuum_truncate)},
{"vacuum_max_eager_freeze_failure_rate", RELOPT_TYPE_REAL,
offsetof(StdRdOptions, vacuum_max_eager_freeze_failure_rate)}
};
@@ -1993,7 +2071,6 @@ build_local_reloptions(local_relopts *relopts, Datum options, bool validate)
elems[i].optname = opt->option->name;
elems[i].opttype = opt->option->type;
elems[i].offset = opt->offset;
- elems[i].isset_offset = 0; /* not supported for local relopts yet */
i++;
}
diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c
index 733ef40ae7..977babff54 100644
--- a/src/backend/commands/vacuum.c
+++ b/src/backend/commands/vacuum.c
@@ -2221,9 +2221,9 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams params,
{
StdRdOptions *opts = (StdRdOptions *) rel->rd_options;
- if (opts && opts->vacuum_truncate_set)
+ if (opts && opts->vacuum_truncate != TERNARY_UNSET)
{
- if (opts->vacuum_truncate)
+ if (opts->vacuum_truncate == TERNARY_TRUE)
params.truncate = VACOPTVALUE_ENABLED;
else
params.truncate = VACOPTVALUE_DISABLED;
diff --git a/src/include/access/reloptions.h b/src/include/access/reloptions.h
index a604a4702c..a436697658 100644
--- a/src/include/access/reloptions.h
+++ b/src/include/access/reloptions.h
@@ -29,6 +29,7 @@
typedef enum relopt_type
{
RELOPT_TYPE_BOOL,
+ RELOPT_TYPE_TERNARY, /* on, off, unset */
RELOPT_TYPE_INT,
RELOPT_TYPE_REAL,
RELOPT_TYPE_ENUM,
@@ -80,6 +81,7 @@ typedef struct relopt_value
union
{
bool bool_val;
+ ternary ternary_val;
int int_val;
double real_val;
int enum_val;
@@ -94,6 +96,12 @@ typedef struct relopt_bool
bool default_val;
} relopt_bool;
+typedef struct relopt_rernary
+{
+ relopt_gen gen;
+ int default_val;
+} relopt_ternary;
+
typedef struct relopt_int
{
relopt_gen gen;
@@ -152,19 +160,6 @@ typedef struct
const char *optname; /* option's name */
relopt_type opttype; /* option's datatype */
int offset; /* offset of field in result struct */
-
- /*
- * isset_offset is an optional offset of a field in the result struct that
- * stores whether the option is explicitly set for the relation or if it
- * just picked up the default value. In most cases, this can be
- * accomplished by giving the reloption a special out-of-range default
- * value (e.g., some integer reloptions use -2), but this isn't always
- * possible. For example, a Boolean reloption cannot be given an
- * out-of-range default, so we need another way to discover the source of
- * its value. This offset is only used if given a value greater than
- * zero.
- */
- int isset_offset;
} relopt_parse_elt;
/* Local reloption definition */
@@ -195,6 +190,8 @@ typedef struct local_relopts
extern relopt_kind add_reloption_kind(void);
extern void add_bool_reloption(bits32 kinds, const char *name, const char *desc,
bool default_val, LOCKMODE lockmode);
+extern void add_ternary_reloption(bits32 kinds, const char *name,
+ const char *desc, int default_val, LOCKMODE lockmode);
extern void add_int_reloption(bits32 kinds, const char *name, const char *desc,
int default_val, int min_val, int max_val,
LOCKMODE lockmode);
@@ -214,6 +211,9 @@ extern void register_reloptions_validator(local_relopts *relopts,
extern void add_local_bool_reloption(local_relopts *relopts, const char *name,
const char *desc, bool default_val,
int offset);
+extern void add_local_ternary_reloption(local_relopts *relopts,
+ const char *name, const char *desc,
+ ternary default_val, int offset);
extern void add_local_int_reloption(local_relopts *relopts, const char *name,
const char *desc, int default_val,
int min_val, int max_val, int offset);
diff --git a/src/include/c.h b/src/include/c.h
index 39022f8a9d..9c59ccccf8 100644
--- a/src/include/c.h
+++ b/src/include/c.h
@@ -488,6 +488,22 @@ typedef void (*pg_funcptr_t) (void);
#include <stdbool.h>
+/*
+ * ternary
+ * Boolean value with an extrea "unset" option
+ *
+ * Ternary data type is used in relation options that can be "true", "false" or
+ * "unset". Since relation options are used deep inside the PostgreSQL code,
+ * this type is declared globally.
+*/
+
+typedef enum ternary
+{
+ TERNARY_FALSE = 0,
+ TERNARY_TRUE = 1,
+ TERNARY_UNSET = -1
+} ternary;
+
/* ----------------------------------------------------------------
* Section 3: standard system types
diff --git a/src/include/utils/rel.h b/src/include/utils/rel.h
index b552359915..7346d618f9 100644
--- a/src/include/utils/rel.h
+++ b/src/include/utils/rel.h
@@ -346,8 +346,7 @@ typedef struct StdRdOptions
bool user_catalog_table; /* use as an additional catalog relation */
int parallel_workers; /* max number of parallel workers */
StdRdOptIndexCleanup vacuum_index_cleanup; /* controls index vacuuming */
- bool vacuum_truncate; /* enables vacuum to truncate a relation */
- bool vacuum_truncate_set; /* whether vacuum_truncate is set */
+ ternary vacuum_truncate; /* enables vacuum to truncate a relation */
/*
* Fraction of pages in a relation that vacuum can eagerly scan and fail
--
2.39.2
--nextPart4155600.3ZeAukHxDK
Content-Disposition: attachment;
filename="v2a-0003-Add-alias-to-be-used-as-unset-state.patch"
Content-Transfer-Encoding: 7Bit
Content-Type: text/x-patch; charset="unicode-2-0-utf-8";
name="v2a-0003-Add-alias-to-be-used-as-unset-state.patch"
^ permalink raw reply [nested|flat] 868+ messages in thread
* [PATCH v2 2/4] Introduce ternary reloptions
@ 2025-09-04 14:41 Nikolay Shaplov <[email protected]>
0 siblings, 0 replies; 868+ messages in thread
From: Nikolay Shaplov @ 2025-09-04 14:41 UTC (permalink / raw)
Introduce ternary reloption as a replacement for current `vacuum_truncate`
implementation. Remove `vacuum_truncate_set` additional flag and using
`TERNARY_UNSET` value instead.
---
src/backend/access/common/reloptions.c | 129 ++++++++++++++++++++-----
src/backend/commands/vacuum.c | 4 +-
src/include/access/reloptions.h | 26 ++---
src/include/c.h | 16 +++
src/include/utils/rel.h | 3 +-
5 files changed, 135 insertions(+), 43 deletions(-)
diff --git a/src/backend/access/common/reloptions.c b/src/backend/access/common/reloptions.c
index 0af3fea68f..f543f11001 100644
--- a/src/backend/access/common/reloptions.c
+++ b/src/backend/access/common/reloptions.c
@@ -40,9 +40,9 @@
*
* To add an option:
*
- * (i) decide on a type (bool, integer, real, enum, string), name, default
- * value, upper and lower bounds (if applicable); for strings, consider a
- * validation routine.
+ * (i) decide on a type (bool, ternary, integer, real, enum, string), name,
+ * default value, upper and lower bounds (if applicable); for strings,
+ * consider a validation routine.
* (ii) add a record below (or use add_<type>_reloption).
* (iii) add it to the appropriate options struct (perhaps StdRdOptions)
* (iv) add it to the appropriate handling routine (perhaps
@@ -147,15 +147,6 @@ static relopt_bool boolRelOpts[] =
},
false
},
- {
- {
- "vacuum_truncate",
- "Enables vacuum to truncate empty pages at the end of this table",
- RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
- ShareUpdateExclusiveLock
- },
- true
- },
{
{
"deduplicate_items",
@@ -170,6 +161,21 @@ static relopt_bool boolRelOpts[] =
{{NULL}}
};
+static relopt_ternary ternaryRelOpts[] =
+{
+ {
+ {
+ "vacuum_truncate",
+ "Enables vacuum to truncate empty pages at the end of this table",
+ RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
+ ShareUpdateExclusiveLock
+ },
+ TERNARY_UNSET
+ },
+ /* list terminator */
+ {{NULL}}
+};
+
static relopt_int intRelOpts[] =
{
{
@@ -600,6 +606,13 @@ initialize_reloptions(void)
boolRelOpts[i].gen.lockmode));
j++;
}
+ for (i = 0; ternaryRelOpts[i].gen.name; i++)
+ {
+ Assert(DoLockModesConflict(ternaryRelOpts[i].gen.lockmode,
+ ternaryRelOpts[i].gen.lockmode));
+ j++;
+ }
+
for (i = 0; intRelOpts[i].gen.name; i++)
{
Assert(DoLockModesConflict(intRelOpts[i].gen.lockmode,
@@ -640,6 +653,14 @@ initialize_reloptions(void)
j++;
}
+ for (i = 0; ternaryRelOpts[i].gen.name; i++)
+ {
+ relOpts[j] = &ternaryRelOpts[i].gen;
+ relOpts[j]->type = RELOPT_TYPE_TERNARY;
+ relOpts[j]->namelen = strlen(relOpts[j]->name);
+ j++;
+ }
+
for (i = 0; intRelOpts[i].gen.name; i++)
{
relOpts[j] = &intRelOpts[i].gen;
@@ -800,6 +821,9 @@ allocate_reloption(bits32 kinds, int type, const char *name, const char *desc,
case RELOPT_TYPE_BOOL:
size = sizeof(relopt_bool);
break;
+ case RELOPT_TYPE_TERNARY:
+ size = sizeof(relopt_ternary);
+ break;
case RELOPT_TYPE_INT:
size = sizeof(relopt_int);
break;
@@ -883,6 +907,54 @@ add_local_bool_reloption(local_relopts *relopts, const char *name,
add_local_reloption(relopts, (relopt_gen *) newoption, offset);
}
+/*
+ * init_ternary_reloption
+ * Allocate and initialize a new ternary reloption
+ */
+static relopt_ternary *
+init_ternary_reloption(bits32 kinds, const char *name, const char *desc,
+ ternary default_val, LOCKMODE lockmode)
+{
+ relopt_ternary *newoption;
+
+ newoption = (relopt_ternary *) allocate_reloption(kinds,
+ RELOPT_TYPE_TERNARY, name, desc, lockmode);
+ newoption->default_val = default_val;
+
+ return newoption;
+}
+
+/*
+ * add_ternary_reloption
+ * Add a new ternary reloption
+ */
+void
+add_ternary_reloption(bits32 kinds, const char *name, const char *desc,
+ ternary default_val, LOCKMODE lockmode)
+{
+ relopt_ternary *newoption = init_ternary_reloption(kinds, name, desc,
+ default_val, lockmode);
+
+ add_reloption((relopt_gen *) newoption);
+}
+
+/*
+ * add_local_ternary_reloption
+ * Add a new ternary local reloption
+ *
+ * 'offset' is offset of ternary-typed field.
+ */
+void
+add_local_ternary_reloption(local_relopts *relopts, const char *name,
+ const char *desc, ternary default_val,
+ int offset)
+{
+ relopt_ternary *newoption = init_ternary_reloption(RELOPT_KIND_LOCAL,
+ name, desc,
+ default_val, 0);
+
+ add_local_reloption(relopts, (relopt_gen *) newoption, offset);
+}
/*
* init_real_reloption
@@ -1617,6 +1689,18 @@ parse_one_reloption(relopt_value *option, char *text_str, int text_len,
option->gen->name, value)));
}
break;
+ case RELOPT_TYPE_TERNARY:
+ {
+ bool b;
+ parsed = parse_bool(value, &b);
+ option->values.ternary_val = b ? TERNARY_TRUE : TERNARY_FALSE;
+ if (validate && !parsed)
+ ereport(ERROR,
+ (errcode(ERRCODE_INVALID_PARAMETER_VALUE),
+ errmsg("invalid value for ternary option \"%s\": %s",
+ option->gen->name, value)));
+ }
+ break;
case RELOPT_TYPE_INT:
{
relopt_int *optint = (relopt_int *) option->gen;
@@ -1780,17 +1864,6 @@ fillRelOptions(void *rdopts, Size basesize,
char *itempos = ((char *) rdopts) + elems[j].offset;
char *string_val;
- /*
- * If isset_offset is provided, store whether the reloption is
- * set there.
- */
- if (elems[j].isset_offset > 0)
- {
- char *setpos = ((char *) rdopts) + elems[j].isset_offset;
-
- *(bool *) setpos = options[i].isset;
- }
-
switch (options[i].gen->type)
{
case RELOPT_TYPE_BOOL:
@@ -1798,6 +1871,11 @@ fillRelOptions(void *rdopts, Size basesize,
options[i].values.bool_val :
((relopt_bool *) options[i].gen)->default_val;
break;
+ case RELOPT_TYPE_TERNARY:
+ *(ternary *) itempos = options[i].isset ?
+ options[i].values.ternary_val :
+ ((relopt_ternary *) options[i].gen)->default_val;
+ break;
case RELOPT_TYPE_INT:
*(int *) itempos = options[i].isset ?
options[i].values.int_val :
@@ -1912,8 +1990,8 @@ default_reloptions(Datum reloptions, bool validate, relopt_kind kind)
offsetof(StdRdOptions, parallel_workers)},
{"vacuum_index_cleanup", RELOPT_TYPE_ENUM,
offsetof(StdRdOptions, vacuum_index_cleanup)},
- {"vacuum_truncate", RELOPT_TYPE_BOOL,
- offsetof(StdRdOptions, vacuum_truncate), offsetof(StdRdOptions, vacuum_truncate_set)},
+ {"vacuum_truncate", RELOPT_TYPE_TERNARY,
+ offsetof(StdRdOptions, vacuum_truncate)},
{"vacuum_max_eager_freeze_failure_rate", RELOPT_TYPE_REAL,
offsetof(StdRdOptions, vacuum_max_eager_freeze_failure_rate)}
};
@@ -1993,7 +2071,6 @@ build_local_reloptions(local_relopts *relopts, Datum options, bool validate)
elems[i].optname = opt->option->name;
elems[i].opttype = opt->option->type;
elems[i].offset = opt->offset;
- elems[i].isset_offset = 0; /* not supported for local relopts yet */
i++;
}
diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c
index 733ef40ae7..977babff54 100644
--- a/src/backend/commands/vacuum.c
+++ b/src/backend/commands/vacuum.c
@@ -2221,9 +2221,9 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams params,
{
StdRdOptions *opts = (StdRdOptions *) rel->rd_options;
- if (opts && opts->vacuum_truncate_set)
+ if (opts && opts->vacuum_truncate != TERNARY_UNSET)
{
- if (opts->vacuum_truncate)
+ if (opts->vacuum_truncate == TERNARY_TRUE)
params.truncate = VACOPTVALUE_ENABLED;
else
params.truncate = VACOPTVALUE_DISABLED;
diff --git a/src/include/access/reloptions.h b/src/include/access/reloptions.h
index a604a4702c..a436697658 100644
--- a/src/include/access/reloptions.h
+++ b/src/include/access/reloptions.h
@@ -29,6 +29,7 @@
typedef enum relopt_type
{
RELOPT_TYPE_BOOL,
+ RELOPT_TYPE_TERNARY, /* on, off, unset */
RELOPT_TYPE_INT,
RELOPT_TYPE_REAL,
RELOPT_TYPE_ENUM,
@@ -80,6 +81,7 @@ typedef struct relopt_value
union
{
bool bool_val;
+ ternary ternary_val;
int int_val;
double real_val;
int enum_val;
@@ -94,6 +96,12 @@ typedef struct relopt_bool
bool default_val;
} relopt_bool;
+typedef struct relopt_rernary
+{
+ relopt_gen gen;
+ int default_val;
+} relopt_ternary;
+
typedef struct relopt_int
{
relopt_gen gen;
@@ -152,19 +160,6 @@ typedef struct
const char *optname; /* option's name */
relopt_type opttype; /* option's datatype */
int offset; /* offset of field in result struct */
-
- /*
- * isset_offset is an optional offset of a field in the result struct that
- * stores whether the option is explicitly set for the relation or if it
- * just picked up the default value. In most cases, this can be
- * accomplished by giving the reloption a special out-of-range default
- * value (e.g., some integer reloptions use -2), but this isn't always
- * possible. For example, a Boolean reloption cannot be given an
- * out-of-range default, so we need another way to discover the source of
- * its value. This offset is only used if given a value greater than
- * zero.
- */
- int isset_offset;
} relopt_parse_elt;
/* Local reloption definition */
@@ -195,6 +190,8 @@ typedef struct local_relopts
extern relopt_kind add_reloption_kind(void);
extern void add_bool_reloption(bits32 kinds, const char *name, const char *desc,
bool default_val, LOCKMODE lockmode);
+extern void add_ternary_reloption(bits32 kinds, const char *name,
+ const char *desc, int default_val, LOCKMODE lockmode);
extern void add_int_reloption(bits32 kinds, const char *name, const char *desc,
int default_val, int min_val, int max_val,
LOCKMODE lockmode);
@@ -214,6 +211,9 @@ extern void register_reloptions_validator(local_relopts *relopts,
extern void add_local_bool_reloption(local_relopts *relopts, const char *name,
const char *desc, bool default_val,
int offset);
+extern void add_local_ternary_reloption(local_relopts *relopts,
+ const char *name, const char *desc,
+ ternary default_val, int offset);
extern void add_local_int_reloption(local_relopts *relopts, const char *name,
const char *desc, int default_val,
int min_val, int max_val, int offset);
diff --git a/src/include/c.h b/src/include/c.h
index 39022f8a9d..9c59ccccf8 100644
--- a/src/include/c.h
+++ b/src/include/c.h
@@ -488,6 +488,22 @@ typedef void (*pg_funcptr_t) (void);
#include <stdbool.h>
+/*
+ * ternary
+ * Boolean value with an extrea "unset" option
+ *
+ * Ternary data type is used in relation options that can be "true", "false" or
+ * "unset". Since relation options are used deep inside the PostgreSQL code,
+ * this type is declared globally.
+*/
+
+typedef enum ternary
+{
+ TERNARY_FALSE = 0,
+ TERNARY_TRUE = 1,
+ TERNARY_UNSET = -1
+} ternary;
+
/* ----------------------------------------------------------------
* Section 3: standard system types
diff --git a/src/include/utils/rel.h b/src/include/utils/rel.h
index b552359915..7346d618f9 100644
--- a/src/include/utils/rel.h
+++ b/src/include/utils/rel.h
@@ -346,8 +346,7 @@ typedef struct StdRdOptions
bool user_catalog_table; /* use as an additional catalog relation */
int parallel_workers; /* max number of parallel workers */
StdRdOptIndexCleanup vacuum_index_cleanup; /* controls index vacuuming */
- bool vacuum_truncate; /* enables vacuum to truncate a relation */
- bool vacuum_truncate_set; /* whether vacuum_truncate is set */
+ ternary vacuum_truncate; /* enables vacuum to truncate a relation */
/*
* Fraction of pages in a relation that vacuum can eagerly scan and fail
--
2.39.2
--nextPart5129932.5fSG56mABF
Content-Disposition: attachment;
filename="v2-0003-Add-alias-to-be-used-as-unset-state.patch"
Content-Transfer-Encoding: 7Bit
Content-Type: text/x-patch; charset="unicode-2-0-utf-8";
name="v2-0003-Add-alias-to-be-used-as-unset-state.patch"
^ permalink raw reply [nested|flat] 868+ messages in thread
* [PATCH v2a 2/4] Introduce ternary reloptions
@ 2025-09-04 14:41 Nikolay Shaplov <[email protected]>
0 siblings, 0 replies; 868+ messages in thread
From: Nikolay Shaplov @ 2025-09-04 14:41 UTC (permalink / raw)
Introduce ternary reloption as a replacement for current `vacuum_truncate`
implementation. Remove `vacuum_truncate_set` additional flag and using
`TERNARY_UNSET` value instead.
---
src/backend/access/common/reloptions.c | 129 ++++++++++++++++++++-----
src/backend/commands/vacuum.c | 4 +-
src/include/access/reloptions.h | 26 ++---
src/include/c.h | 16 +++
src/include/utils/rel.h | 3 +-
5 files changed, 135 insertions(+), 43 deletions(-)
diff --git a/src/backend/access/common/reloptions.c b/src/backend/access/common/reloptions.c
index 0af3fea68f..f543f11001 100644
--- a/src/backend/access/common/reloptions.c
+++ b/src/backend/access/common/reloptions.c
@@ -40,9 +40,9 @@
*
* To add an option:
*
- * (i) decide on a type (bool, integer, real, enum, string), name, default
- * value, upper and lower bounds (if applicable); for strings, consider a
- * validation routine.
+ * (i) decide on a type (bool, ternary, integer, real, enum, string), name,
+ * default value, upper and lower bounds (if applicable); for strings,
+ * consider a validation routine.
* (ii) add a record below (or use add_<type>_reloption).
* (iii) add it to the appropriate options struct (perhaps StdRdOptions)
* (iv) add it to the appropriate handling routine (perhaps
@@ -147,15 +147,6 @@ static relopt_bool boolRelOpts[] =
},
false
},
- {
- {
- "vacuum_truncate",
- "Enables vacuum to truncate empty pages at the end of this table",
- RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
- ShareUpdateExclusiveLock
- },
- true
- },
{
{
"deduplicate_items",
@@ -170,6 +161,21 @@ static relopt_bool boolRelOpts[] =
{{NULL}}
};
+static relopt_ternary ternaryRelOpts[] =
+{
+ {
+ {
+ "vacuum_truncate",
+ "Enables vacuum to truncate empty pages at the end of this table",
+ RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
+ ShareUpdateExclusiveLock
+ },
+ TERNARY_UNSET
+ },
+ /* list terminator */
+ {{NULL}}
+};
+
static relopt_int intRelOpts[] =
{
{
@@ -600,6 +606,13 @@ initialize_reloptions(void)
boolRelOpts[i].gen.lockmode));
j++;
}
+ for (i = 0; ternaryRelOpts[i].gen.name; i++)
+ {
+ Assert(DoLockModesConflict(ternaryRelOpts[i].gen.lockmode,
+ ternaryRelOpts[i].gen.lockmode));
+ j++;
+ }
+
for (i = 0; intRelOpts[i].gen.name; i++)
{
Assert(DoLockModesConflict(intRelOpts[i].gen.lockmode,
@@ -640,6 +653,14 @@ initialize_reloptions(void)
j++;
}
+ for (i = 0; ternaryRelOpts[i].gen.name; i++)
+ {
+ relOpts[j] = &ternaryRelOpts[i].gen;
+ relOpts[j]->type = RELOPT_TYPE_TERNARY;
+ relOpts[j]->namelen = strlen(relOpts[j]->name);
+ j++;
+ }
+
for (i = 0; intRelOpts[i].gen.name; i++)
{
relOpts[j] = &intRelOpts[i].gen;
@@ -800,6 +821,9 @@ allocate_reloption(bits32 kinds, int type, const char *name, const char *desc,
case RELOPT_TYPE_BOOL:
size = sizeof(relopt_bool);
break;
+ case RELOPT_TYPE_TERNARY:
+ size = sizeof(relopt_ternary);
+ break;
case RELOPT_TYPE_INT:
size = sizeof(relopt_int);
break;
@@ -883,6 +907,54 @@ add_local_bool_reloption(local_relopts *relopts, const char *name,
add_local_reloption(relopts, (relopt_gen *) newoption, offset);
}
+/*
+ * init_ternary_reloption
+ * Allocate and initialize a new ternary reloption
+ */
+static relopt_ternary *
+init_ternary_reloption(bits32 kinds, const char *name, const char *desc,
+ ternary default_val, LOCKMODE lockmode)
+{
+ relopt_ternary *newoption;
+
+ newoption = (relopt_ternary *) allocate_reloption(kinds,
+ RELOPT_TYPE_TERNARY, name, desc, lockmode);
+ newoption->default_val = default_val;
+
+ return newoption;
+}
+
+/*
+ * add_ternary_reloption
+ * Add a new ternary reloption
+ */
+void
+add_ternary_reloption(bits32 kinds, const char *name, const char *desc,
+ ternary default_val, LOCKMODE lockmode)
+{
+ relopt_ternary *newoption = init_ternary_reloption(kinds, name, desc,
+ default_val, lockmode);
+
+ add_reloption((relopt_gen *) newoption);
+}
+
+/*
+ * add_local_ternary_reloption
+ * Add a new ternary local reloption
+ *
+ * 'offset' is offset of ternary-typed field.
+ */
+void
+add_local_ternary_reloption(local_relopts *relopts, const char *name,
+ const char *desc, ternary default_val,
+ int offset)
+{
+ relopt_ternary *newoption = init_ternary_reloption(RELOPT_KIND_LOCAL,
+ name, desc,
+ default_val, 0);
+
+ add_local_reloption(relopts, (relopt_gen *) newoption, offset);
+}
/*
* init_real_reloption
@@ -1617,6 +1689,18 @@ parse_one_reloption(relopt_value *option, char *text_str, int text_len,
option->gen->name, value)));
}
break;
+ case RELOPT_TYPE_TERNARY:
+ {
+ bool b;
+ parsed = parse_bool(value, &b);
+ option->values.ternary_val = b ? TERNARY_TRUE : TERNARY_FALSE;
+ if (validate && !parsed)
+ ereport(ERROR,
+ (errcode(ERRCODE_INVALID_PARAMETER_VALUE),
+ errmsg("invalid value for ternary option \"%s\": %s",
+ option->gen->name, value)));
+ }
+ break;
case RELOPT_TYPE_INT:
{
relopt_int *optint = (relopt_int *) option->gen;
@@ -1780,17 +1864,6 @@ fillRelOptions(void *rdopts, Size basesize,
char *itempos = ((char *) rdopts) + elems[j].offset;
char *string_val;
- /*
- * If isset_offset is provided, store whether the reloption is
- * set there.
- */
- if (elems[j].isset_offset > 0)
- {
- char *setpos = ((char *) rdopts) + elems[j].isset_offset;
-
- *(bool *) setpos = options[i].isset;
- }
-
switch (options[i].gen->type)
{
case RELOPT_TYPE_BOOL:
@@ -1798,6 +1871,11 @@ fillRelOptions(void *rdopts, Size basesize,
options[i].values.bool_val :
((relopt_bool *) options[i].gen)->default_val;
break;
+ case RELOPT_TYPE_TERNARY:
+ *(ternary *) itempos = options[i].isset ?
+ options[i].values.ternary_val :
+ ((relopt_ternary *) options[i].gen)->default_val;
+ break;
case RELOPT_TYPE_INT:
*(int *) itempos = options[i].isset ?
options[i].values.int_val :
@@ -1912,8 +1990,8 @@ default_reloptions(Datum reloptions, bool validate, relopt_kind kind)
offsetof(StdRdOptions, parallel_workers)},
{"vacuum_index_cleanup", RELOPT_TYPE_ENUM,
offsetof(StdRdOptions, vacuum_index_cleanup)},
- {"vacuum_truncate", RELOPT_TYPE_BOOL,
- offsetof(StdRdOptions, vacuum_truncate), offsetof(StdRdOptions, vacuum_truncate_set)},
+ {"vacuum_truncate", RELOPT_TYPE_TERNARY,
+ offsetof(StdRdOptions, vacuum_truncate)},
{"vacuum_max_eager_freeze_failure_rate", RELOPT_TYPE_REAL,
offsetof(StdRdOptions, vacuum_max_eager_freeze_failure_rate)}
};
@@ -1993,7 +2071,6 @@ build_local_reloptions(local_relopts *relopts, Datum options, bool validate)
elems[i].optname = opt->option->name;
elems[i].opttype = opt->option->type;
elems[i].offset = opt->offset;
- elems[i].isset_offset = 0; /* not supported for local relopts yet */
i++;
}
diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c
index 733ef40ae7..977babff54 100644
--- a/src/backend/commands/vacuum.c
+++ b/src/backend/commands/vacuum.c
@@ -2221,9 +2221,9 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams params,
{
StdRdOptions *opts = (StdRdOptions *) rel->rd_options;
- if (opts && opts->vacuum_truncate_set)
+ if (opts && opts->vacuum_truncate != TERNARY_UNSET)
{
- if (opts->vacuum_truncate)
+ if (opts->vacuum_truncate == TERNARY_TRUE)
params.truncate = VACOPTVALUE_ENABLED;
else
params.truncate = VACOPTVALUE_DISABLED;
diff --git a/src/include/access/reloptions.h b/src/include/access/reloptions.h
index a604a4702c..a436697658 100644
--- a/src/include/access/reloptions.h
+++ b/src/include/access/reloptions.h
@@ -29,6 +29,7 @@
typedef enum relopt_type
{
RELOPT_TYPE_BOOL,
+ RELOPT_TYPE_TERNARY, /* on, off, unset */
RELOPT_TYPE_INT,
RELOPT_TYPE_REAL,
RELOPT_TYPE_ENUM,
@@ -80,6 +81,7 @@ typedef struct relopt_value
union
{
bool bool_val;
+ ternary ternary_val;
int int_val;
double real_val;
int enum_val;
@@ -94,6 +96,12 @@ typedef struct relopt_bool
bool default_val;
} relopt_bool;
+typedef struct relopt_rernary
+{
+ relopt_gen gen;
+ int default_val;
+} relopt_ternary;
+
typedef struct relopt_int
{
relopt_gen gen;
@@ -152,19 +160,6 @@ typedef struct
const char *optname; /* option's name */
relopt_type opttype; /* option's datatype */
int offset; /* offset of field in result struct */
-
- /*
- * isset_offset is an optional offset of a field in the result struct that
- * stores whether the option is explicitly set for the relation or if it
- * just picked up the default value. In most cases, this can be
- * accomplished by giving the reloption a special out-of-range default
- * value (e.g., some integer reloptions use -2), but this isn't always
- * possible. For example, a Boolean reloption cannot be given an
- * out-of-range default, so we need another way to discover the source of
- * its value. This offset is only used if given a value greater than
- * zero.
- */
- int isset_offset;
} relopt_parse_elt;
/* Local reloption definition */
@@ -195,6 +190,8 @@ typedef struct local_relopts
extern relopt_kind add_reloption_kind(void);
extern void add_bool_reloption(bits32 kinds, const char *name, const char *desc,
bool default_val, LOCKMODE lockmode);
+extern void add_ternary_reloption(bits32 kinds, const char *name,
+ const char *desc, int default_val, LOCKMODE lockmode);
extern void add_int_reloption(bits32 kinds, const char *name, const char *desc,
int default_val, int min_val, int max_val,
LOCKMODE lockmode);
@@ -214,6 +211,9 @@ extern void register_reloptions_validator(local_relopts *relopts,
extern void add_local_bool_reloption(local_relopts *relopts, const char *name,
const char *desc, bool default_val,
int offset);
+extern void add_local_ternary_reloption(local_relopts *relopts,
+ const char *name, const char *desc,
+ ternary default_val, int offset);
extern void add_local_int_reloption(local_relopts *relopts, const char *name,
const char *desc, int default_val,
int min_val, int max_val, int offset);
diff --git a/src/include/c.h b/src/include/c.h
index 39022f8a9d..9c59ccccf8 100644
--- a/src/include/c.h
+++ b/src/include/c.h
@@ -488,6 +488,22 @@ typedef void (*pg_funcptr_t) (void);
#include <stdbool.h>
+/*
+ * ternary
+ * Boolean value with an extrea "unset" option
+ *
+ * Ternary data type is used in relation options that can be "true", "false" or
+ * "unset". Since relation options are used deep inside the PostgreSQL code,
+ * this type is declared globally.
+*/
+
+typedef enum ternary
+{
+ TERNARY_FALSE = 0,
+ TERNARY_TRUE = 1,
+ TERNARY_UNSET = -1
+} ternary;
+
/* ----------------------------------------------------------------
* Section 3: standard system types
diff --git a/src/include/utils/rel.h b/src/include/utils/rel.h
index b552359915..7346d618f9 100644
--- a/src/include/utils/rel.h
+++ b/src/include/utils/rel.h
@@ -346,8 +346,7 @@ typedef struct StdRdOptions
bool user_catalog_table; /* use as an additional catalog relation */
int parallel_workers; /* max number of parallel workers */
StdRdOptIndexCleanup vacuum_index_cleanup; /* controls index vacuuming */
- bool vacuum_truncate; /* enables vacuum to truncate a relation */
- bool vacuum_truncate_set; /* whether vacuum_truncate is set */
+ ternary vacuum_truncate; /* enables vacuum to truncate a relation */
/*
* Fraction of pages in a relation that vacuum can eagerly scan and fail
--
2.39.2
--nextPart4155600.3ZeAukHxDK
Content-Disposition: attachment;
filename="v2a-0003-Add-alias-to-be-used-as-unset-state.patch"
Content-Transfer-Encoding: 7Bit
Content-Type: text/x-patch; charset="unicode-2-0-utf-8";
name="v2a-0003-Add-alias-to-be-used-as-unset-state.patch"
^ permalink raw reply [nested|flat] 868+ messages in thread
* [PATCH v2 2/4] Introduce ternary reloptions
@ 2025-09-04 14:41 Nikolay Shaplov <[email protected]>
0 siblings, 0 replies; 868+ messages in thread
From: Nikolay Shaplov @ 2025-09-04 14:41 UTC (permalink / raw)
Introduce ternary reloption as a replacement for current `vacuum_truncate`
implementation. Remove `vacuum_truncate_set` additional flag and using
`TERNARY_UNSET` value instead.
---
src/backend/access/common/reloptions.c | 129 ++++++++++++++++++++-----
src/backend/commands/vacuum.c | 4 +-
src/include/access/reloptions.h | 26 ++---
src/include/c.h | 16 +++
src/include/utils/rel.h | 3 +-
5 files changed, 135 insertions(+), 43 deletions(-)
diff --git a/src/backend/access/common/reloptions.c b/src/backend/access/common/reloptions.c
index 0af3fea68f..f543f11001 100644
--- a/src/backend/access/common/reloptions.c
+++ b/src/backend/access/common/reloptions.c
@@ -40,9 +40,9 @@
*
* To add an option:
*
- * (i) decide on a type (bool, integer, real, enum, string), name, default
- * value, upper and lower bounds (if applicable); for strings, consider a
- * validation routine.
+ * (i) decide on a type (bool, ternary, integer, real, enum, string), name,
+ * default value, upper and lower bounds (if applicable); for strings,
+ * consider a validation routine.
* (ii) add a record below (or use add_<type>_reloption).
* (iii) add it to the appropriate options struct (perhaps StdRdOptions)
* (iv) add it to the appropriate handling routine (perhaps
@@ -147,15 +147,6 @@ static relopt_bool boolRelOpts[] =
},
false
},
- {
- {
- "vacuum_truncate",
- "Enables vacuum to truncate empty pages at the end of this table",
- RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
- ShareUpdateExclusiveLock
- },
- true
- },
{
{
"deduplicate_items",
@@ -170,6 +161,21 @@ static relopt_bool boolRelOpts[] =
{{NULL}}
};
+static relopt_ternary ternaryRelOpts[] =
+{
+ {
+ {
+ "vacuum_truncate",
+ "Enables vacuum to truncate empty pages at the end of this table",
+ RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
+ ShareUpdateExclusiveLock
+ },
+ TERNARY_UNSET
+ },
+ /* list terminator */
+ {{NULL}}
+};
+
static relopt_int intRelOpts[] =
{
{
@@ -600,6 +606,13 @@ initialize_reloptions(void)
boolRelOpts[i].gen.lockmode));
j++;
}
+ for (i = 0; ternaryRelOpts[i].gen.name; i++)
+ {
+ Assert(DoLockModesConflict(ternaryRelOpts[i].gen.lockmode,
+ ternaryRelOpts[i].gen.lockmode));
+ j++;
+ }
+
for (i = 0; intRelOpts[i].gen.name; i++)
{
Assert(DoLockModesConflict(intRelOpts[i].gen.lockmode,
@@ -640,6 +653,14 @@ initialize_reloptions(void)
j++;
}
+ for (i = 0; ternaryRelOpts[i].gen.name; i++)
+ {
+ relOpts[j] = &ternaryRelOpts[i].gen;
+ relOpts[j]->type = RELOPT_TYPE_TERNARY;
+ relOpts[j]->namelen = strlen(relOpts[j]->name);
+ j++;
+ }
+
for (i = 0; intRelOpts[i].gen.name; i++)
{
relOpts[j] = &intRelOpts[i].gen;
@@ -800,6 +821,9 @@ allocate_reloption(bits32 kinds, int type, const char *name, const char *desc,
case RELOPT_TYPE_BOOL:
size = sizeof(relopt_bool);
break;
+ case RELOPT_TYPE_TERNARY:
+ size = sizeof(relopt_ternary);
+ break;
case RELOPT_TYPE_INT:
size = sizeof(relopt_int);
break;
@@ -883,6 +907,54 @@ add_local_bool_reloption(local_relopts *relopts, const char *name,
add_local_reloption(relopts, (relopt_gen *) newoption, offset);
}
+/*
+ * init_ternary_reloption
+ * Allocate and initialize a new ternary reloption
+ */
+static relopt_ternary *
+init_ternary_reloption(bits32 kinds, const char *name, const char *desc,
+ ternary default_val, LOCKMODE lockmode)
+{
+ relopt_ternary *newoption;
+
+ newoption = (relopt_ternary *) allocate_reloption(kinds,
+ RELOPT_TYPE_TERNARY, name, desc, lockmode);
+ newoption->default_val = default_val;
+
+ return newoption;
+}
+
+/*
+ * add_ternary_reloption
+ * Add a new ternary reloption
+ */
+void
+add_ternary_reloption(bits32 kinds, const char *name, const char *desc,
+ ternary default_val, LOCKMODE lockmode)
+{
+ relopt_ternary *newoption = init_ternary_reloption(kinds, name, desc,
+ default_val, lockmode);
+
+ add_reloption((relopt_gen *) newoption);
+}
+
+/*
+ * add_local_ternary_reloption
+ * Add a new ternary local reloption
+ *
+ * 'offset' is offset of ternary-typed field.
+ */
+void
+add_local_ternary_reloption(local_relopts *relopts, const char *name,
+ const char *desc, ternary default_val,
+ int offset)
+{
+ relopt_ternary *newoption = init_ternary_reloption(RELOPT_KIND_LOCAL,
+ name, desc,
+ default_val, 0);
+
+ add_local_reloption(relopts, (relopt_gen *) newoption, offset);
+}
/*
* init_real_reloption
@@ -1617,6 +1689,18 @@ parse_one_reloption(relopt_value *option, char *text_str, int text_len,
option->gen->name, value)));
}
break;
+ case RELOPT_TYPE_TERNARY:
+ {
+ bool b;
+ parsed = parse_bool(value, &b);
+ option->values.ternary_val = b ? TERNARY_TRUE : TERNARY_FALSE;
+ if (validate && !parsed)
+ ereport(ERROR,
+ (errcode(ERRCODE_INVALID_PARAMETER_VALUE),
+ errmsg("invalid value for ternary option \"%s\": %s",
+ option->gen->name, value)));
+ }
+ break;
case RELOPT_TYPE_INT:
{
relopt_int *optint = (relopt_int *) option->gen;
@@ -1780,17 +1864,6 @@ fillRelOptions(void *rdopts, Size basesize,
char *itempos = ((char *) rdopts) + elems[j].offset;
char *string_val;
- /*
- * If isset_offset is provided, store whether the reloption is
- * set there.
- */
- if (elems[j].isset_offset > 0)
- {
- char *setpos = ((char *) rdopts) + elems[j].isset_offset;
-
- *(bool *) setpos = options[i].isset;
- }
-
switch (options[i].gen->type)
{
case RELOPT_TYPE_BOOL:
@@ -1798,6 +1871,11 @@ fillRelOptions(void *rdopts, Size basesize,
options[i].values.bool_val :
((relopt_bool *) options[i].gen)->default_val;
break;
+ case RELOPT_TYPE_TERNARY:
+ *(ternary *) itempos = options[i].isset ?
+ options[i].values.ternary_val :
+ ((relopt_ternary *) options[i].gen)->default_val;
+ break;
case RELOPT_TYPE_INT:
*(int *) itempos = options[i].isset ?
options[i].values.int_val :
@@ -1912,8 +1990,8 @@ default_reloptions(Datum reloptions, bool validate, relopt_kind kind)
offsetof(StdRdOptions, parallel_workers)},
{"vacuum_index_cleanup", RELOPT_TYPE_ENUM,
offsetof(StdRdOptions, vacuum_index_cleanup)},
- {"vacuum_truncate", RELOPT_TYPE_BOOL,
- offsetof(StdRdOptions, vacuum_truncate), offsetof(StdRdOptions, vacuum_truncate_set)},
+ {"vacuum_truncate", RELOPT_TYPE_TERNARY,
+ offsetof(StdRdOptions, vacuum_truncate)},
{"vacuum_max_eager_freeze_failure_rate", RELOPT_TYPE_REAL,
offsetof(StdRdOptions, vacuum_max_eager_freeze_failure_rate)}
};
@@ -1993,7 +2071,6 @@ build_local_reloptions(local_relopts *relopts, Datum options, bool validate)
elems[i].optname = opt->option->name;
elems[i].opttype = opt->option->type;
elems[i].offset = opt->offset;
- elems[i].isset_offset = 0; /* not supported for local relopts yet */
i++;
}
diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c
index 733ef40ae7..977babff54 100644
--- a/src/backend/commands/vacuum.c
+++ b/src/backend/commands/vacuum.c
@@ -2221,9 +2221,9 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams params,
{
StdRdOptions *opts = (StdRdOptions *) rel->rd_options;
- if (opts && opts->vacuum_truncate_set)
+ if (opts && opts->vacuum_truncate != TERNARY_UNSET)
{
- if (opts->vacuum_truncate)
+ if (opts->vacuum_truncate == TERNARY_TRUE)
params.truncate = VACOPTVALUE_ENABLED;
else
params.truncate = VACOPTVALUE_DISABLED;
diff --git a/src/include/access/reloptions.h b/src/include/access/reloptions.h
index a604a4702c..a436697658 100644
--- a/src/include/access/reloptions.h
+++ b/src/include/access/reloptions.h
@@ -29,6 +29,7 @@
typedef enum relopt_type
{
RELOPT_TYPE_BOOL,
+ RELOPT_TYPE_TERNARY, /* on, off, unset */
RELOPT_TYPE_INT,
RELOPT_TYPE_REAL,
RELOPT_TYPE_ENUM,
@@ -80,6 +81,7 @@ typedef struct relopt_value
union
{
bool bool_val;
+ ternary ternary_val;
int int_val;
double real_val;
int enum_val;
@@ -94,6 +96,12 @@ typedef struct relopt_bool
bool default_val;
} relopt_bool;
+typedef struct relopt_rernary
+{
+ relopt_gen gen;
+ int default_val;
+} relopt_ternary;
+
typedef struct relopt_int
{
relopt_gen gen;
@@ -152,19 +160,6 @@ typedef struct
const char *optname; /* option's name */
relopt_type opttype; /* option's datatype */
int offset; /* offset of field in result struct */
-
- /*
- * isset_offset is an optional offset of a field in the result struct that
- * stores whether the option is explicitly set for the relation or if it
- * just picked up the default value. In most cases, this can be
- * accomplished by giving the reloption a special out-of-range default
- * value (e.g., some integer reloptions use -2), but this isn't always
- * possible. For example, a Boolean reloption cannot be given an
- * out-of-range default, so we need another way to discover the source of
- * its value. This offset is only used if given a value greater than
- * zero.
- */
- int isset_offset;
} relopt_parse_elt;
/* Local reloption definition */
@@ -195,6 +190,8 @@ typedef struct local_relopts
extern relopt_kind add_reloption_kind(void);
extern void add_bool_reloption(bits32 kinds, const char *name, const char *desc,
bool default_val, LOCKMODE lockmode);
+extern void add_ternary_reloption(bits32 kinds, const char *name,
+ const char *desc, int default_val, LOCKMODE lockmode);
extern void add_int_reloption(bits32 kinds, const char *name, const char *desc,
int default_val, int min_val, int max_val,
LOCKMODE lockmode);
@@ -214,6 +211,9 @@ extern void register_reloptions_validator(local_relopts *relopts,
extern void add_local_bool_reloption(local_relopts *relopts, const char *name,
const char *desc, bool default_val,
int offset);
+extern void add_local_ternary_reloption(local_relopts *relopts,
+ const char *name, const char *desc,
+ ternary default_val, int offset);
extern void add_local_int_reloption(local_relopts *relopts, const char *name,
const char *desc, int default_val,
int min_val, int max_val, int offset);
diff --git a/src/include/c.h b/src/include/c.h
index 39022f8a9d..9c59ccccf8 100644
--- a/src/include/c.h
+++ b/src/include/c.h
@@ -488,6 +488,22 @@ typedef void (*pg_funcptr_t) (void);
#include <stdbool.h>
+/*
+ * ternary
+ * Boolean value with an extrea "unset" option
+ *
+ * Ternary data type is used in relation options that can be "true", "false" or
+ * "unset". Since relation options are used deep inside the PostgreSQL code,
+ * this type is declared globally.
+*/
+
+typedef enum ternary
+{
+ TERNARY_FALSE = 0,
+ TERNARY_TRUE = 1,
+ TERNARY_UNSET = -1
+} ternary;
+
/* ----------------------------------------------------------------
* Section 3: standard system types
diff --git a/src/include/utils/rel.h b/src/include/utils/rel.h
index b552359915..7346d618f9 100644
--- a/src/include/utils/rel.h
+++ b/src/include/utils/rel.h
@@ -346,8 +346,7 @@ typedef struct StdRdOptions
bool user_catalog_table; /* use as an additional catalog relation */
int parallel_workers; /* max number of parallel workers */
StdRdOptIndexCleanup vacuum_index_cleanup; /* controls index vacuuming */
- bool vacuum_truncate; /* enables vacuum to truncate a relation */
- bool vacuum_truncate_set; /* whether vacuum_truncate is set */
+ ternary vacuum_truncate; /* enables vacuum to truncate a relation */
/*
* Fraction of pages in a relation that vacuum can eagerly scan and fail
--
2.39.2
--nextPart5129932.5fSG56mABF
Content-Disposition: attachment;
filename="v2-0003-Add-alias-to-be-used-as-unset-state.patch"
Content-Transfer-Encoding: 7Bit
Content-Type: text/x-patch; charset="unicode-2-0-utf-8";
name="v2-0003-Add-alias-to-be-used-as-unset-state.patch"
^ permalink raw reply [nested|flat] 868+ messages in thread
* [PATCH v2 2/4] Introduce ternary reloptions
@ 2025-09-04 14:41 Nikolay Shaplov <[email protected]>
0 siblings, 0 replies; 868+ messages in thread
From: Nikolay Shaplov @ 2025-09-04 14:41 UTC (permalink / raw)
Introduce ternary reloption as a replacement for current `vacuum_truncate`
implementation. Remove `vacuum_truncate_set` additional flag and using
`TERNARY_UNSET` value instead.
---
src/backend/access/common/reloptions.c | 129 ++++++++++++++++++++-----
src/backend/commands/vacuum.c | 4 +-
src/include/access/reloptions.h | 26 ++---
src/include/c.h | 16 +++
src/include/utils/rel.h | 3 +-
5 files changed, 135 insertions(+), 43 deletions(-)
diff --git a/src/backend/access/common/reloptions.c b/src/backend/access/common/reloptions.c
index 0af3fea68f..f543f11001 100644
--- a/src/backend/access/common/reloptions.c
+++ b/src/backend/access/common/reloptions.c
@@ -40,9 +40,9 @@
*
* To add an option:
*
- * (i) decide on a type (bool, integer, real, enum, string), name, default
- * value, upper and lower bounds (if applicable); for strings, consider a
- * validation routine.
+ * (i) decide on a type (bool, ternary, integer, real, enum, string), name,
+ * default value, upper and lower bounds (if applicable); for strings,
+ * consider a validation routine.
* (ii) add a record below (or use add_<type>_reloption).
* (iii) add it to the appropriate options struct (perhaps StdRdOptions)
* (iv) add it to the appropriate handling routine (perhaps
@@ -147,15 +147,6 @@ static relopt_bool boolRelOpts[] =
},
false
},
- {
- {
- "vacuum_truncate",
- "Enables vacuum to truncate empty pages at the end of this table",
- RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
- ShareUpdateExclusiveLock
- },
- true
- },
{
{
"deduplicate_items",
@@ -170,6 +161,21 @@ static relopt_bool boolRelOpts[] =
{{NULL}}
};
+static relopt_ternary ternaryRelOpts[] =
+{
+ {
+ {
+ "vacuum_truncate",
+ "Enables vacuum to truncate empty pages at the end of this table",
+ RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
+ ShareUpdateExclusiveLock
+ },
+ TERNARY_UNSET
+ },
+ /* list terminator */
+ {{NULL}}
+};
+
static relopt_int intRelOpts[] =
{
{
@@ -600,6 +606,13 @@ initialize_reloptions(void)
boolRelOpts[i].gen.lockmode));
j++;
}
+ for (i = 0; ternaryRelOpts[i].gen.name; i++)
+ {
+ Assert(DoLockModesConflict(ternaryRelOpts[i].gen.lockmode,
+ ternaryRelOpts[i].gen.lockmode));
+ j++;
+ }
+
for (i = 0; intRelOpts[i].gen.name; i++)
{
Assert(DoLockModesConflict(intRelOpts[i].gen.lockmode,
@@ -640,6 +653,14 @@ initialize_reloptions(void)
j++;
}
+ for (i = 0; ternaryRelOpts[i].gen.name; i++)
+ {
+ relOpts[j] = &ternaryRelOpts[i].gen;
+ relOpts[j]->type = RELOPT_TYPE_TERNARY;
+ relOpts[j]->namelen = strlen(relOpts[j]->name);
+ j++;
+ }
+
for (i = 0; intRelOpts[i].gen.name; i++)
{
relOpts[j] = &intRelOpts[i].gen;
@@ -800,6 +821,9 @@ allocate_reloption(bits32 kinds, int type, const char *name, const char *desc,
case RELOPT_TYPE_BOOL:
size = sizeof(relopt_bool);
break;
+ case RELOPT_TYPE_TERNARY:
+ size = sizeof(relopt_ternary);
+ break;
case RELOPT_TYPE_INT:
size = sizeof(relopt_int);
break;
@@ -883,6 +907,54 @@ add_local_bool_reloption(local_relopts *relopts, const char *name,
add_local_reloption(relopts, (relopt_gen *) newoption, offset);
}
+/*
+ * init_ternary_reloption
+ * Allocate and initialize a new ternary reloption
+ */
+static relopt_ternary *
+init_ternary_reloption(bits32 kinds, const char *name, const char *desc,
+ ternary default_val, LOCKMODE lockmode)
+{
+ relopt_ternary *newoption;
+
+ newoption = (relopt_ternary *) allocate_reloption(kinds,
+ RELOPT_TYPE_TERNARY, name, desc, lockmode);
+ newoption->default_val = default_val;
+
+ return newoption;
+}
+
+/*
+ * add_ternary_reloption
+ * Add a new ternary reloption
+ */
+void
+add_ternary_reloption(bits32 kinds, const char *name, const char *desc,
+ ternary default_val, LOCKMODE lockmode)
+{
+ relopt_ternary *newoption = init_ternary_reloption(kinds, name, desc,
+ default_val, lockmode);
+
+ add_reloption((relopt_gen *) newoption);
+}
+
+/*
+ * add_local_ternary_reloption
+ * Add a new ternary local reloption
+ *
+ * 'offset' is offset of ternary-typed field.
+ */
+void
+add_local_ternary_reloption(local_relopts *relopts, const char *name,
+ const char *desc, ternary default_val,
+ int offset)
+{
+ relopt_ternary *newoption = init_ternary_reloption(RELOPT_KIND_LOCAL,
+ name, desc,
+ default_val, 0);
+
+ add_local_reloption(relopts, (relopt_gen *) newoption, offset);
+}
/*
* init_real_reloption
@@ -1617,6 +1689,18 @@ parse_one_reloption(relopt_value *option, char *text_str, int text_len,
option->gen->name, value)));
}
break;
+ case RELOPT_TYPE_TERNARY:
+ {
+ bool b;
+ parsed = parse_bool(value, &b);
+ option->values.ternary_val = b ? TERNARY_TRUE : TERNARY_FALSE;
+ if (validate && !parsed)
+ ereport(ERROR,
+ (errcode(ERRCODE_INVALID_PARAMETER_VALUE),
+ errmsg("invalid value for ternary option \"%s\": %s",
+ option->gen->name, value)));
+ }
+ break;
case RELOPT_TYPE_INT:
{
relopt_int *optint = (relopt_int *) option->gen;
@@ -1780,17 +1864,6 @@ fillRelOptions(void *rdopts, Size basesize,
char *itempos = ((char *) rdopts) + elems[j].offset;
char *string_val;
- /*
- * If isset_offset is provided, store whether the reloption is
- * set there.
- */
- if (elems[j].isset_offset > 0)
- {
- char *setpos = ((char *) rdopts) + elems[j].isset_offset;
-
- *(bool *) setpos = options[i].isset;
- }
-
switch (options[i].gen->type)
{
case RELOPT_TYPE_BOOL:
@@ -1798,6 +1871,11 @@ fillRelOptions(void *rdopts, Size basesize,
options[i].values.bool_val :
((relopt_bool *) options[i].gen)->default_val;
break;
+ case RELOPT_TYPE_TERNARY:
+ *(ternary *) itempos = options[i].isset ?
+ options[i].values.ternary_val :
+ ((relopt_ternary *) options[i].gen)->default_val;
+ break;
case RELOPT_TYPE_INT:
*(int *) itempos = options[i].isset ?
options[i].values.int_val :
@@ -1912,8 +1990,8 @@ default_reloptions(Datum reloptions, bool validate, relopt_kind kind)
offsetof(StdRdOptions, parallel_workers)},
{"vacuum_index_cleanup", RELOPT_TYPE_ENUM,
offsetof(StdRdOptions, vacuum_index_cleanup)},
- {"vacuum_truncate", RELOPT_TYPE_BOOL,
- offsetof(StdRdOptions, vacuum_truncate), offsetof(StdRdOptions, vacuum_truncate_set)},
+ {"vacuum_truncate", RELOPT_TYPE_TERNARY,
+ offsetof(StdRdOptions, vacuum_truncate)},
{"vacuum_max_eager_freeze_failure_rate", RELOPT_TYPE_REAL,
offsetof(StdRdOptions, vacuum_max_eager_freeze_failure_rate)}
};
@@ -1993,7 +2071,6 @@ build_local_reloptions(local_relopts *relopts, Datum options, bool validate)
elems[i].optname = opt->option->name;
elems[i].opttype = opt->option->type;
elems[i].offset = opt->offset;
- elems[i].isset_offset = 0; /* not supported for local relopts yet */
i++;
}
diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c
index 733ef40ae7..977babff54 100644
--- a/src/backend/commands/vacuum.c
+++ b/src/backend/commands/vacuum.c
@@ -2221,9 +2221,9 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams params,
{
StdRdOptions *opts = (StdRdOptions *) rel->rd_options;
- if (opts && opts->vacuum_truncate_set)
+ if (opts && opts->vacuum_truncate != TERNARY_UNSET)
{
- if (opts->vacuum_truncate)
+ if (opts->vacuum_truncate == TERNARY_TRUE)
params.truncate = VACOPTVALUE_ENABLED;
else
params.truncate = VACOPTVALUE_DISABLED;
diff --git a/src/include/access/reloptions.h b/src/include/access/reloptions.h
index a604a4702c..a436697658 100644
--- a/src/include/access/reloptions.h
+++ b/src/include/access/reloptions.h
@@ -29,6 +29,7 @@
typedef enum relopt_type
{
RELOPT_TYPE_BOOL,
+ RELOPT_TYPE_TERNARY, /* on, off, unset */
RELOPT_TYPE_INT,
RELOPT_TYPE_REAL,
RELOPT_TYPE_ENUM,
@@ -80,6 +81,7 @@ typedef struct relopt_value
union
{
bool bool_val;
+ ternary ternary_val;
int int_val;
double real_val;
int enum_val;
@@ -94,6 +96,12 @@ typedef struct relopt_bool
bool default_val;
} relopt_bool;
+typedef struct relopt_rernary
+{
+ relopt_gen gen;
+ int default_val;
+} relopt_ternary;
+
typedef struct relopt_int
{
relopt_gen gen;
@@ -152,19 +160,6 @@ typedef struct
const char *optname; /* option's name */
relopt_type opttype; /* option's datatype */
int offset; /* offset of field in result struct */
-
- /*
- * isset_offset is an optional offset of a field in the result struct that
- * stores whether the option is explicitly set for the relation or if it
- * just picked up the default value. In most cases, this can be
- * accomplished by giving the reloption a special out-of-range default
- * value (e.g., some integer reloptions use -2), but this isn't always
- * possible. For example, a Boolean reloption cannot be given an
- * out-of-range default, so we need another way to discover the source of
- * its value. This offset is only used if given a value greater than
- * zero.
- */
- int isset_offset;
} relopt_parse_elt;
/* Local reloption definition */
@@ -195,6 +190,8 @@ typedef struct local_relopts
extern relopt_kind add_reloption_kind(void);
extern void add_bool_reloption(bits32 kinds, const char *name, const char *desc,
bool default_val, LOCKMODE lockmode);
+extern void add_ternary_reloption(bits32 kinds, const char *name,
+ const char *desc, int default_val, LOCKMODE lockmode);
extern void add_int_reloption(bits32 kinds, const char *name, const char *desc,
int default_val, int min_val, int max_val,
LOCKMODE lockmode);
@@ -214,6 +211,9 @@ extern void register_reloptions_validator(local_relopts *relopts,
extern void add_local_bool_reloption(local_relopts *relopts, const char *name,
const char *desc, bool default_val,
int offset);
+extern void add_local_ternary_reloption(local_relopts *relopts,
+ const char *name, const char *desc,
+ ternary default_val, int offset);
extern void add_local_int_reloption(local_relopts *relopts, const char *name,
const char *desc, int default_val,
int min_val, int max_val, int offset);
diff --git a/src/include/c.h b/src/include/c.h
index 39022f8a9d..9c59ccccf8 100644
--- a/src/include/c.h
+++ b/src/include/c.h
@@ -488,6 +488,22 @@ typedef void (*pg_funcptr_t) (void);
#include <stdbool.h>
+/*
+ * ternary
+ * Boolean value with an extrea "unset" option
+ *
+ * Ternary data type is used in relation options that can be "true", "false" or
+ * "unset". Since relation options are used deep inside the PostgreSQL code,
+ * this type is declared globally.
+*/
+
+typedef enum ternary
+{
+ TERNARY_FALSE = 0,
+ TERNARY_TRUE = 1,
+ TERNARY_UNSET = -1
+} ternary;
+
/* ----------------------------------------------------------------
* Section 3: standard system types
diff --git a/src/include/utils/rel.h b/src/include/utils/rel.h
index b552359915..7346d618f9 100644
--- a/src/include/utils/rel.h
+++ b/src/include/utils/rel.h
@@ -346,8 +346,7 @@ typedef struct StdRdOptions
bool user_catalog_table; /* use as an additional catalog relation */
int parallel_workers; /* max number of parallel workers */
StdRdOptIndexCleanup vacuum_index_cleanup; /* controls index vacuuming */
- bool vacuum_truncate; /* enables vacuum to truncate a relation */
- bool vacuum_truncate_set; /* whether vacuum_truncate is set */
+ ternary vacuum_truncate; /* enables vacuum to truncate a relation */
/*
* Fraction of pages in a relation that vacuum can eagerly scan and fail
--
2.39.2
--nextPart5129932.5fSG56mABF
Content-Disposition: attachment;
filename="v2-0003-Add-alias-to-be-used-as-unset-state.patch"
Content-Transfer-Encoding: 7Bit
Content-Type: text/x-patch; charset="unicode-2-0-utf-8";
name="v2-0003-Add-alias-to-be-used-as-unset-state.patch"
^ permalink raw reply [nested|flat] 868+ messages in thread
* [PATCH v2a 2/4] Introduce ternary reloptions
@ 2025-09-04 14:41 Nikolay Shaplov <[email protected]>
0 siblings, 0 replies; 868+ messages in thread
From: Nikolay Shaplov @ 2025-09-04 14:41 UTC (permalink / raw)
Introduce ternary reloption as a replacement for current `vacuum_truncate`
implementation. Remove `vacuum_truncate_set` additional flag and using
`TERNARY_UNSET` value instead.
---
src/backend/access/common/reloptions.c | 129 ++++++++++++++++++++-----
src/backend/commands/vacuum.c | 4 +-
src/include/access/reloptions.h | 26 ++---
src/include/c.h | 16 +++
src/include/utils/rel.h | 3 +-
5 files changed, 135 insertions(+), 43 deletions(-)
diff --git a/src/backend/access/common/reloptions.c b/src/backend/access/common/reloptions.c
index 0af3fea68f..f543f11001 100644
--- a/src/backend/access/common/reloptions.c
+++ b/src/backend/access/common/reloptions.c
@@ -40,9 +40,9 @@
*
* To add an option:
*
- * (i) decide on a type (bool, integer, real, enum, string), name, default
- * value, upper and lower bounds (if applicable); for strings, consider a
- * validation routine.
+ * (i) decide on a type (bool, ternary, integer, real, enum, string), name,
+ * default value, upper and lower bounds (if applicable); for strings,
+ * consider a validation routine.
* (ii) add a record below (or use add_<type>_reloption).
* (iii) add it to the appropriate options struct (perhaps StdRdOptions)
* (iv) add it to the appropriate handling routine (perhaps
@@ -147,15 +147,6 @@ static relopt_bool boolRelOpts[] =
},
false
},
- {
- {
- "vacuum_truncate",
- "Enables vacuum to truncate empty pages at the end of this table",
- RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
- ShareUpdateExclusiveLock
- },
- true
- },
{
{
"deduplicate_items",
@@ -170,6 +161,21 @@ static relopt_bool boolRelOpts[] =
{{NULL}}
};
+static relopt_ternary ternaryRelOpts[] =
+{
+ {
+ {
+ "vacuum_truncate",
+ "Enables vacuum to truncate empty pages at the end of this table",
+ RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
+ ShareUpdateExclusiveLock
+ },
+ TERNARY_UNSET
+ },
+ /* list terminator */
+ {{NULL}}
+};
+
static relopt_int intRelOpts[] =
{
{
@@ -600,6 +606,13 @@ initialize_reloptions(void)
boolRelOpts[i].gen.lockmode));
j++;
}
+ for (i = 0; ternaryRelOpts[i].gen.name; i++)
+ {
+ Assert(DoLockModesConflict(ternaryRelOpts[i].gen.lockmode,
+ ternaryRelOpts[i].gen.lockmode));
+ j++;
+ }
+
for (i = 0; intRelOpts[i].gen.name; i++)
{
Assert(DoLockModesConflict(intRelOpts[i].gen.lockmode,
@@ -640,6 +653,14 @@ initialize_reloptions(void)
j++;
}
+ for (i = 0; ternaryRelOpts[i].gen.name; i++)
+ {
+ relOpts[j] = &ternaryRelOpts[i].gen;
+ relOpts[j]->type = RELOPT_TYPE_TERNARY;
+ relOpts[j]->namelen = strlen(relOpts[j]->name);
+ j++;
+ }
+
for (i = 0; intRelOpts[i].gen.name; i++)
{
relOpts[j] = &intRelOpts[i].gen;
@@ -800,6 +821,9 @@ allocate_reloption(bits32 kinds, int type, const char *name, const char *desc,
case RELOPT_TYPE_BOOL:
size = sizeof(relopt_bool);
break;
+ case RELOPT_TYPE_TERNARY:
+ size = sizeof(relopt_ternary);
+ break;
case RELOPT_TYPE_INT:
size = sizeof(relopt_int);
break;
@@ -883,6 +907,54 @@ add_local_bool_reloption(local_relopts *relopts, const char *name,
add_local_reloption(relopts, (relopt_gen *) newoption, offset);
}
+/*
+ * init_ternary_reloption
+ * Allocate and initialize a new ternary reloption
+ */
+static relopt_ternary *
+init_ternary_reloption(bits32 kinds, const char *name, const char *desc,
+ ternary default_val, LOCKMODE lockmode)
+{
+ relopt_ternary *newoption;
+
+ newoption = (relopt_ternary *) allocate_reloption(kinds,
+ RELOPT_TYPE_TERNARY, name, desc, lockmode);
+ newoption->default_val = default_val;
+
+ return newoption;
+}
+
+/*
+ * add_ternary_reloption
+ * Add a new ternary reloption
+ */
+void
+add_ternary_reloption(bits32 kinds, const char *name, const char *desc,
+ ternary default_val, LOCKMODE lockmode)
+{
+ relopt_ternary *newoption = init_ternary_reloption(kinds, name, desc,
+ default_val, lockmode);
+
+ add_reloption((relopt_gen *) newoption);
+}
+
+/*
+ * add_local_ternary_reloption
+ * Add a new ternary local reloption
+ *
+ * 'offset' is offset of ternary-typed field.
+ */
+void
+add_local_ternary_reloption(local_relopts *relopts, const char *name,
+ const char *desc, ternary default_val,
+ int offset)
+{
+ relopt_ternary *newoption = init_ternary_reloption(RELOPT_KIND_LOCAL,
+ name, desc,
+ default_val, 0);
+
+ add_local_reloption(relopts, (relopt_gen *) newoption, offset);
+}
/*
* init_real_reloption
@@ -1617,6 +1689,18 @@ parse_one_reloption(relopt_value *option, char *text_str, int text_len,
option->gen->name, value)));
}
break;
+ case RELOPT_TYPE_TERNARY:
+ {
+ bool b;
+ parsed = parse_bool(value, &b);
+ option->values.ternary_val = b ? TERNARY_TRUE : TERNARY_FALSE;
+ if (validate && !parsed)
+ ereport(ERROR,
+ (errcode(ERRCODE_INVALID_PARAMETER_VALUE),
+ errmsg("invalid value for ternary option \"%s\": %s",
+ option->gen->name, value)));
+ }
+ break;
case RELOPT_TYPE_INT:
{
relopt_int *optint = (relopt_int *) option->gen;
@@ -1780,17 +1864,6 @@ fillRelOptions(void *rdopts, Size basesize,
char *itempos = ((char *) rdopts) + elems[j].offset;
char *string_val;
- /*
- * If isset_offset is provided, store whether the reloption is
- * set there.
- */
- if (elems[j].isset_offset > 0)
- {
- char *setpos = ((char *) rdopts) + elems[j].isset_offset;
-
- *(bool *) setpos = options[i].isset;
- }
-
switch (options[i].gen->type)
{
case RELOPT_TYPE_BOOL:
@@ -1798,6 +1871,11 @@ fillRelOptions(void *rdopts, Size basesize,
options[i].values.bool_val :
((relopt_bool *) options[i].gen)->default_val;
break;
+ case RELOPT_TYPE_TERNARY:
+ *(ternary *) itempos = options[i].isset ?
+ options[i].values.ternary_val :
+ ((relopt_ternary *) options[i].gen)->default_val;
+ break;
case RELOPT_TYPE_INT:
*(int *) itempos = options[i].isset ?
options[i].values.int_val :
@@ -1912,8 +1990,8 @@ default_reloptions(Datum reloptions, bool validate, relopt_kind kind)
offsetof(StdRdOptions, parallel_workers)},
{"vacuum_index_cleanup", RELOPT_TYPE_ENUM,
offsetof(StdRdOptions, vacuum_index_cleanup)},
- {"vacuum_truncate", RELOPT_TYPE_BOOL,
- offsetof(StdRdOptions, vacuum_truncate), offsetof(StdRdOptions, vacuum_truncate_set)},
+ {"vacuum_truncate", RELOPT_TYPE_TERNARY,
+ offsetof(StdRdOptions, vacuum_truncate)},
{"vacuum_max_eager_freeze_failure_rate", RELOPT_TYPE_REAL,
offsetof(StdRdOptions, vacuum_max_eager_freeze_failure_rate)}
};
@@ -1993,7 +2071,6 @@ build_local_reloptions(local_relopts *relopts, Datum options, bool validate)
elems[i].optname = opt->option->name;
elems[i].opttype = opt->option->type;
elems[i].offset = opt->offset;
- elems[i].isset_offset = 0; /* not supported for local relopts yet */
i++;
}
diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c
index 733ef40ae7..977babff54 100644
--- a/src/backend/commands/vacuum.c
+++ b/src/backend/commands/vacuum.c
@@ -2221,9 +2221,9 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams params,
{
StdRdOptions *opts = (StdRdOptions *) rel->rd_options;
- if (opts && opts->vacuum_truncate_set)
+ if (opts && opts->vacuum_truncate != TERNARY_UNSET)
{
- if (opts->vacuum_truncate)
+ if (opts->vacuum_truncate == TERNARY_TRUE)
params.truncate = VACOPTVALUE_ENABLED;
else
params.truncate = VACOPTVALUE_DISABLED;
diff --git a/src/include/access/reloptions.h b/src/include/access/reloptions.h
index a604a4702c..a436697658 100644
--- a/src/include/access/reloptions.h
+++ b/src/include/access/reloptions.h
@@ -29,6 +29,7 @@
typedef enum relopt_type
{
RELOPT_TYPE_BOOL,
+ RELOPT_TYPE_TERNARY, /* on, off, unset */
RELOPT_TYPE_INT,
RELOPT_TYPE_REAL,
RELOPT_TYPE_ENUM,
@@ -80,6 +81,7 @@ typedef struct relopt_value
union
{
bool bool_val;
+ ternary ternary_val;
int int_val;
double real_val;
int enum_val;
@@ -94,6 +96,12 @@ typedef struct relopt_bool
bool default_val;
} relopt_bool;
+typedef struct relopt_rernary
+{
+ relopt_gen gen;
+ int default_val;
+} relopt_ternary;
+
typedef struct relopt_int
{
relopt_gen gen;
@@ -152,19 +160,6 @@ typedef struct
const char *optname; /* option's name */
relopt_type opttype; /* option's datatype */
int offset; /* offset of field in result struct */
-
- /*
- * isset_offset is an optional offset of a field in the result struct that
- * stores whether the option is explicitly set for the relation or if it
- * just picked up the default value. In most cases, this can be
- * accomplished by giving the reloption a special out-of-range default
- * value (e.g., some integer reloptions use -2), but this isn't always
- * possible. For example, a Boolean reloption cannot be given an
- * out-of-range default, so we need another way to discover the source of
- * its value. This offset is only used if given a value greater than
- * zero.
- */
- int isset_offset;
} relopt_parse_elt;
/* Local reloption definition */
@@ -195,6 +190,8 @@ typedef struct local_relopts
extern relopt_kind add_reloption_kind(void);
extern void add_bool_reloption(bits32 kinds, const char *name, const char *desc,
bool default_val, LOCKMODE lockmode);
+extern void add_ternary_reloption(bits32 kinds, const char *name,
+ const char *desc, int default_val, LOCKMODE lockmode);
extern void add_int_reloption(bits32 kinds, const char *name, const char *desc,
int default_val, int min_val, int max_val,
LOCKMODE lockmode);
@@ -214,6 +211,9 @@ extern void register_reloptions_validator(local_relopts *relopts,
extern void add_local_bool_reloption(local_relopts *relopts, const char *name,
const char *desc, bool default_val,
int offset);
+extern void add_local_ternary_reloption(local_relopts *relopts,
+ const char *name, const char *desc,
+ ternary default_val, int offset);
extern void add_local_int_reloption(local_relopts *relopts, const char *name,
const char *desc, int default_val,
int min_val, int max_val, int offset);
diff --git a/src/include/c.h b/src/include/c.h
index 39022f8a9d..9c59ccccf8 100644
--- a/src/include/c.h
+++ b/src/include/c.h
@@ -488,6 +488,22 @@ typedef void (*pg_funcptr_t) (void);
#include <stdbool.h>
+/*
+ * ternary
+ * Boolean value with an extrea "unset" option
+ *
+ * Ternary data type is used in relation options that can be "true", "false" or
+ * "unset". Since relation options are used deep inside the PostgreSQL code,
+ * this type is declared globally.
+*/
+
+typedef enum ternary
+{
+ TERNARY_FALSE = 0,
+ TERNARY_TRUE = 1,
+ TERNARY_UNSET = -1
+} ternary;
+
/* ----------------------------------------------------------------
* Section 3: standard system types
diff --git a/src/include/utils/rel.h b/src/include/utils/rel.h
index b552359915..7346d618f9 100644
--- a/src/include/utils/rel.h
+++ b/src/include/utils/rel.h
@@ -346,8 +346,7 @@ typedef struct StdRdOptions
bool user_catalog_table; /* use as an additional catalog relation */
int parallel_workers; /* max number of parallel workers */
StdRdOptIndexCleanup vacuum_index_cleanup; /* controls index vacuuming */
- bool vacuum_truncate; /* enables vacuum to truncate a relation */
- bool vacuum_truncate_set; /* whether vacuum_truncate is set */
+ ternary vacuum_truncate; /* enables vacuum to truncate a relation */
/*
* Fraction of pages in a relation that vacuum can eagerly scan and fail
--
2.39.2
--nextPart4155600.3ZeAukHxDK
Content-Disposition: attachment;
filename="v2a-0003-Add-alias-to-be-used-as-unset-state.patch"
Content-Transfer-Encoding: 7Bit
Content-Type: text/x-patch; charset="unicode-2-0-utf-8";
name="v2a-0003-Add-alias-to-be-used-as-unset-state.patch"
^ permalink raw reply [nested|flat] 868+ messages in thread
* [PATCH v2 2/4] Introduce ternary reloptions
@ 2025-09-04 14:41 Nikolay Shaplov <[email protected]>
0 siblings, 0 replies; 868+ messages in thread
From: Nikolay Shaplov @ 2025-09-04 14:41 UTC (permalink / raw)
Introduce ternary reloption as a replacement for current `vacuum_truncate`
implementation. Remove `vacuum_truncate_set` additional flag and using
`TERNARY_UNSET` value instead.
---
src/backend/access/common/reloptions.c | 129 ++++++++++++++++++++-----
src/backend/commands/vacuum.c | 4 +-
src/include/access/reloptions.h | 26 ++---
src/include/c.h | 16 +++
src/include/utils/rel.h | 3 +-
5 files changed, 135 insertions(+), 43 deletions(-)
diff --git a/src/backend/access/common/reloptions.c b/src/backend/access/common/reloptions.c
index 0af3fea68f..f543f11001 100644
--- a/src/backend/access/common/reloptions.c
+++ b/src/backend/access/common/reloptions.c
@@ -40,9 +40,9 @@
*
* To add an option:
*
- * (i) decide on a type (bool, integer, real, enum, string), name, default
- * value, upper and lower bounds (if applicable); for strings, consider a
- * validation routine.
+ * (i) decide on a type (bool, ternary, integer, real, enum, string), name,
+ * default value, upper and lower bounds (if applicable); for strings,
+ * consider a validation routine.
* (ii) add a record below (or use add_<type>_reloption).
* (iii) add it to the appropriate options struct (perhaps StdRdOptions)
* (iv) add it to the appropriate handling routine (perhaps
@@ -147,15 +147,6 @@ static relopt_bool boolRelOpts[] =
},
false
},
- {
- {
- "vacuum_truncate",
- "Enables vacuum to truncate empty pages at the end of this table",
- RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
- ShareUpdateExclusiveLock
- },
- true
- },
{
{
"deduplicate_items",
@@ -170,6 +161,21 @@ static relopt_bool boolRelOpts[] =
{{NULL}}
};
+static relopt_ternary ternaryRelOpts[] =
+{
+ {
+ {
+ "vacuum_truncate",
+ "Enables vacuum to truncate empty pages at the end of this table",
+ RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
+ ShareUpdateExclusiveLock
+ },
+ TERNARY_UNSET
+ },
+ /* list terminator */
+ {{NULL}}
+};
+
static relopt_int intRelOpts[] =
{
{
@@ -600,6 +606,13 @@ initialize_reloptions(void)
boolRelOpts[i].gen.lockmode));
j++;
}
+ for (i = 0; ternaryRelOpts[i].gen.name; i++)
+ {
+ Assert(DoLockModesConflict(ternaryRelOpts[i].gen.lockmode,
+ ternaryRelOpts[i].gen.lockmode));
+ j++;
+ }
+
for (i = 0; intRelOpts[i].gen.name; i++)
{
Assert(DoLockModesConflict(intRelOpts[i].gen.lockmode,
@@ -640,6 +653,14 @@ initialize_reloptions(void)
j++;
}
+ for (i = 0; ternaryRelOpts[i].gen.name; i++)
+ {
+ relOpts[j] = &ternaryRelOpts[i].gen;
+ relOpts[j]->type = RELOPT_TYPE_TERNARY;
+ relOpts[j]->namelen = strlen(relOpts[j]->name);
+ j++;
+ }
+
for (i = 0; intRelOpts[i].gen.name; i++)
{
relOpts[j] = &intRelOpts[i].gen;
@@ -800,6 +821,9 @@ allocate_reloption(bits32 kinds, int type, const char *name, const char *desc,
case RELOPT_TYPE_BOOL:
size = sizeof(relopt_bool);
break;
+ case RELOPT_TYPE_TERNARY:
+ size = sizeof(relopt_ternary);
+ break;
case RELOPT_TYPE_INT:
size = sizeof(relopt_int);
break;
@@ -883,6 +907,54 @@ add_local_bool_reloption(local_relopts *relopts, const char *name,
add_local_reloption(relopts, (relopt_gen *) newoption, offset);
}
+/*
+ * init_ternary_reloption
+ * Allocate and initialize a new ternary reloption
+ */
+static relopt_ternary *
+init_ternary_reloption(bits32 kinds, const char *name, const char *desc,
+ ternary default_val, LOCKMODE lockmode)
+{
+ relopt_ternary *newoption;
+
+ newoption = (relopt_ternary *) allocate_reloption(kinds,
+ RELOPT_TYPE_TERNARY, name, desc, lockmode);
+ newoption->default_val = default_val;
+
+ return newoption;
+}
+
+/*
+ * add_ternary_reloption
+ * Add a new ternary reloption
+ */
+void
+add_ternary_reloption(bits32 kinds, const char *name, const char *desc,
+ ternary default_val, LOCKMODE lockmode)
+{
+ relopt_ternary *newoption = init_ternary_reloption(kinds, name, desc,
+ default_val, lockmode);
+
+ add_reloption((relopt_gen *) newoption);
+}
+
+/*
+ * add_local_ternary_reloption
+ * Add a new ternary local reloption
+ *
+ * 'offset' is offset of ternary-typed field.
+ */
+void
+add_local_ternary_reloption(local_relopts *relopts, const char *name,
+ const char *desc, ternary default_val,
+ int offset)
+{
+ relopt_ternary *newoption = init_ternary_reloption(RELOPT_KIND_LOCAL,
+ name, desc,
+ default_val, 0);
+
+ add_local_reloption(relopts, (relopt_gen *) newoption, offset);
+}
/*
* init_real_reloption
@@ -1617,6 +1689,18 @@ parse_one_reloption(relopt_value *option, char *text_str, int text_len,
option->gen->name, value)));
}
break;
+ case RELOPT_TYPE_TERNARY:
+ {
+ bool b;
+ parsed = parse_bool(value, &b);
+ option->values.ternary_val = b ? TERNARY_TRUE : TERNARY_FALSE;
+ if (validate && !parsed)
+ ereport(ERROR,
+ (errcode(ERRCODE_INVALID_PARAMETER_VALUE),
+ errmsg("invalid value for ternary option \"%s\": %s",
+ option->gen->name, value)));
+ }
+ break;
case RELOPT_TYPE_INT:
{
relopt_int *optint = (relopt_int *) option->gen;
@@ -1780,17 +1864,6 @@ fillRelOptions(void *rdopts, Size basesize,
char *itempos = ((char *) rdopts) + elems[j].offset;
char *string_val;
- /*
- * If isset_offset is provided, store whether the reloption is
- * set there.
- */
- if (elems[j].isset_offset > 0)
- {
- char *setpos = ((char *) rdopts) + elems[j].isset_offset;
-
- *(bool *) setpos = options[i].isset;
- }
-
switch (options[i].gen->type)
{
case RELOPT_TYPE_BOOL:
@@ -1798,6 +1871,11 @@ fillRelOptions(void *rdopts, Size basesize,
options[i].values.bool_val :
((relopt_bool *) options[i].gen)->default_val;
break;
+ case RELOPT_TYPE_TERNARY:
+ *(ternary *) itempos = options[i].isset ?
+ options[i].values.ternary_val :
+ ((relopt_ternary *) options[i].gen)->default_val;
+ break;
case RELOPT_TYPE_INT:
*(int *) itempos = options[i].isset ?
options[i].values.int_val :
@@ -1912,8 +1990,8 @@ default_reloptions(Datum reloptions, bool validate, relopt_kind kind)
offsetof(StdRdOptions, parallel_workers)},
{"vacuum_index_cleanup", RELOPT_TYPE_ENUM,
offsetof(StdRdOptions, vacuum_index_cleanup)},
- {"vacuum_truncate", RELOPT_TYPE_BOOL,
- offsetof(StdRdOptions, vacuum_truncate), offsetof(StdRdOptions, vacuum_truncate_set)},
+ {"vacuum_truncate", RELOPT_TYPE_TERNARY,
+ offsetof(StdRdOptions, vacuum_truncate)},
{"vacuum_max_eager_freeze_failure_rate", RELOPT_TYPE_REAL,
offsetof(StdRdOptions, vacuum_max_eager_freeze_failure_rate)}
};
@@ -1993,7 +2071,6 @@ build_local_reloptions(local_relopts *relopts, Datum options, bool validate)
elems[i].optname = opt->option->name;
elems[i].opttype = opt->option->type;
elems[i].offset = opt->offset;
- elems[i].isset_offset = 0; /* not supported for local relopts yet */
i++;
}
diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c
index 733ef40ae7..977babff54 100644
--- a/src/backend/commands/vacuum.c
+++ b/src/backend/commands/vacuum.c
@@ -2221,9 +2221,9 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams params,
{
StdRdOptions *opts = (StdRdOptions *) rel->rd_options;
- if (opts && opts->vacuum_truncate_set)
+ if (opts && opts->vacuum_truncate != TERNARY_UNSET)
{
- if (opts->vacuum_truncate)
+ if (opts->vacuum_truncate == TERNARY_TRUE)
params.truncate = VACOPTVALUE_ENABLED;
else
params.truncate = VACOPTVALUE_DISABLED;
diff --git a/src/include/access/reloptions.h b/src/include/access/reloptions.h
index a604a4702c..a436697658 100644
--- a/src/include/access/reloptions.h
+++ b/src/include/access/reloptions.h
@@ -29,6 +29,7 @@
typedef enum relopt_type
{
RELOPT_TYPE_BOOL,
+ RELOPT_TYPE_TERNARY, /* on, off, unset */
RELOPT_TYPE_INT,
RELOPT_TYPE_REAL,
RELOPT_TYPE_ENUM,
@@ -80,6 +81,7 @@ typedef struct relopt_value
union
{
bool bool_val;
+ ternary ternary_val;
int int_val;
double real_val;
int enum_val;
@@ -94,6 +96,12 @@ typedef struct relopt_bool
bool default_val;
} relopt_bool;
+typedef struct relopt_rernary
+{
+ relopt_gen gen;
+ int default_val;
+} relopt_ternary;
+
typedef struct relopt_int
{
relopt_gen gen;
@@ -152,19 +160,6 @@ typedef struct
const char *optname; /* option's name */
relopt_type opttype; /* option's datatype */
int offset; /* offset of field in result struct */
-
- /*
- * isset_offset is an optional offset of a field in the result struct that
- * stores whether the option is explicitly set for the relation or if it
- * just picked up the default value. In most cases, this can be
- * accomplished by giving the reloption a special out-of-range default
- * value (e.g., some integer reloptions use -2), but this isn't always
- * possible. For example, a Boolean reloption cannot be given an
- * out-of-range default, so we need another way to discover the source of
- * its value. This offset is only used if given a value greater than
- * zero.
- */
- int isset_offset;
} relopt_parse_elt;
/* Local reloption definition */
@@ -195,6 +190,8 @@ typedef struct local_relopts
extern relopt_kind add_reloption_kind(void);
extern void add_bool_reloption(bits32 kinds, const char *name, const char *desc,
bool default_val, LOCKMODE lockmode);
+extern void add_ternary_reloption(bits32 kinds, const char *name,
+ const char *desc, int default_val, LOCKMODE lockmode);
extern void add_int_reloption(bits32 kinds, const char *name, const char *desc,
int default_val, int min_val, int max_val,
LOCKMODE lockmode);
@@ -214,6 +211,9 @@ extern void register_reloptions_validator(local_relopts *relopts,
extern void add_local_bool_reloption(local_relopts *relopts, const char *name,
const char *desc, bool default_val,
int offset);
+extern void add_local_ternary_reloption(local_relopts *relopts,
+ const char *name, const char *desc,
+ ternary default_val, int offset);
extern void add_local_int_reloption(local_relopts *relopts, const char *name,
const char *desc, int default_val,
int min_val, int max_val, int offset);
diff --git a/src/include/c.h b/src/include/c.h
index 39022f8a9d..9c59ccccf8 100644
--- a/src/include/c.h
+++ b/src/include/c.h
@@ -488,6 +488,22 @@ typedef void (*pg_funcptr_t) (void);
#include <stdbool.h>
+/*
+ * ternary
+ * Boolean value with an extrea "unset" option
+ *
+ * Ternary data type is used in relation options that can be "true", "false" or
+ * "unset". Since relation options are used deep inside the PostgreSQL code,
+ * this type is declared globally.
+*/
+
+typedef enum ternary
+{
+ TERNARY_FALSE = 0,
+ TERNARY_TRUE = 1,
+ TERNARY_UNSET = -1
+} ternary;
+
/* ----------------------------------------------------------------
* Section 3: standard system types
diff --git a/src/include/utils/rel.h b/src/include/utils/rel.h
index b552359915..7346d618f9 100644
--- a/src/include/utils/rel.h
+++ b/src/include/utils/rel.h
@@ -346,8 +346,7 @@ typedef struct StdRdOptions
bool user_catalog_table; /* use as an additional catalog relation */
int parallel_workers; /* max number of parallel workers */
StdRdOptIndexCleanup vacuum_index_cleanup; /* controls index vacuuming */
- bool vacuum_truncate; /* enables vacuum to truncate a relation */
- bool vacuum_truncate_set; /* whether vacuum_truncate is set */
+ ternary vacuum_truncate; /* enables vacuum to truncate a relation */
/*
* Fraction of pages in a relation that vacuum can eagerly scan and fail
--
2.39.2
--nextPart5129932.5fSG56mABF
Content-Disposition: attachment;
filename="v2-0003-Add-alias-to-be-used-as-unset-state.patch"
Content-Transfer-Encoding: 7Bit
Content-Type: text/x-patch; charset="unicode-2-0-utf-8";
name="v2-0003-Add-alias-to-be-used-as-unset-state.patch"
^ permalink raw reply [nested|flat] 868+ messages in thread
* [PATCH v2 2/4] Introduce ternary reloptions
@ 2025-09-04 14:41 Nikolay Shaplov <[email protected]>
0 siblings, 0 replies; 868+ messages in thread
From: Nikolay Shaplov @ 2025-09-04 14:41 UTC (permalink / raw)
Introduce ternary reloption as a replacement for current `vacuum_truncate`
implementation. Remove `vacuum_truncate_set` additional flag and using
`TERNARY_UNSET` value instead.
---
src/backend/access/common/reloptions.c | 129 ++++++++++++++++++++-----
src/backend/commands/vacuum.c | 4 +-
src/include/access/reloptions.h | 26 ++---
src/include/c.h | 16 +++
src/include/utils/rel.h | 3 +-
5 files changed, 135 insertions(+), 43 deletions(-)
diff --git a/src/backend/access/common/reloptions.c b/src/backend/access/common/reloptions.c
index 0af3fea68f..f543f11001 100644
--- a/src/backend/access/common/reloptions.c
+++ b/src/backend/access/common/reloptions.c
@@ -40,9 +40,9 @@
*
* To add an option:
*
- * (i) decide on a type (bool, integer, real, enum, string), name, default
- * value, upper and lower bounds (if applicable); for strings, consider a
- * validation routine.
+ * (i) decide on a type (bool, ternary, integer, real, enum, string), name,
+ * default value, upper and lower bounds (if applicable); for strings,
+ * consider a validation routine.
* (ii) add a record below (or use add_<type>_reloption).
* (iii) add it to the appropriate options struct (perhaps StdRdOptions)
* (iv) add it to the appropriate handling routine (perhaps
@@ -147,15 +147,6 @@ static relopt_bool boolRelOpts[] =
},
false
},
- {
- {
- "vacuum_truncate",
- "Enables vacuum to truncate empty pages at the end of this table",
- RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
- ShareUpdateExclusiveLock
- },
- true
- },
{
{
"deduplicate_items",
@@ -170,6 +161,21 @@ static relopt_bool boolRelOpts[] =
{{NULL}}
};
+static relopt_ternary ternaryRelOpts[] =
+{
+ {
+ {
+ "vacuum_truncate",
+ "Enables vacuum to truncate empty pages at the end of this table",
+ RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
+ ShareUpdateExclusiveLock
+ },
+ TERNARY_UNSET
+ },
+ /* list terminator */
+ {{NULL}}
+};
+
static relopt_int intRelOpts[] =
{
{
@@ -600,6 +606,13 @@ initialize_reloptions(void)
boolRelOpts[i].gen.lockmode));
j++;
}
+ for (i = 0; ternaryRelOpts[i].gen.name; i++)
+ {
+ Assert(DoLockModesConflict(ternaryRelOpts[i].gen.lockmode,
+ ternaryRelOpts[i].gen.lockmode));
+ j++;
+ }
+
for (i = 0; intRelOpts[i].gen.name; i++)
{
Assert(DoLockModesConflict(intRelOpts[i].gen.lockmode,
@@ -640,6 +653,14 @@ initialize_reloptions(void)
j++;
}
+ for (i = 0; ternaryRelOpts[i].gen.name; i++)
+ {
+ relOpts[j] = &ternaryRelOpts[i].gen;
+ relOpts[j]->type = RELOPT_TYPE_TERNARY;
+ relOpts[j]->namelen = strlen(relOpts[j]->name);
+ j++;
+ }
+
for (i = 0; intRelOpts[i].gen.name; i++)
{
relOpts[j] = &intRelOpts[i].gen;
@@ -800,6 +821,9 @@ allocate_reloption(bits32 kinds, int type, const char *name, const char *desc,
case RELOPT_TYPE_BOOL:
size = sizeof(relopt_bool);
break;
+ case RELOPT_TYPE_TERNARY:
+ size = sizeof(relopt_ternary);
+ break;
case RELOPT_TYPE_INT:
size = sizeof(relopt_int);
break;
@@ -883,6 +907,54 @@ add_local_bool_reloption(local_relopts *relopts, const char *name,
add_local_reloption(relopts, (relopt_gen *) newoption, offset);
}
+/*
+ * init_ternary_reloption
+ * Allocate and initialize a new ternary reloption
+ */
+static relopt_ternary *
+init_ternary_reloption(bits32 kinds, const char *name, const char *desc,
+ ternary default_val, LOCKMODE lockmode)
+{
+ relopt_ternary *newoption;
+
+ newoption = (relopt_ternary *) allocate_reloption(kinds,
+ RELOPT_TYPE_TERNARY, name, desc, lockmode);
+ newoption->default_val = default_val;
+
+ return newoption;
+}
+
+/*
+ * add_ternary_reloption
+ * Add a new ternary reloption
+ */
+void
+add_ternary_reloption(bits32 kinds, const char *name, const char *desc,
+ ternary default_val, LOCKMODE lockmode)
+{
+ relopt_ternary *newoption = init_ternary_reloption(kinds, name, desc,
+ default_val, lockmode);
+
+ add_reloption((relopt_gen *) newoption);
+}
+
+/*
+ * add_local_ternary_reloption
+ * Add a new ternary local reloption
+ *
+ * 'offset' is offset of ternary-typed field.
+ */
+void
+add_local_ternary_reloption(local_relopts *relopts, const char *name,
+ const char *desc, ternary default_val,
+ int offset)
+{
+ relopt_ternary *newoption = init_ternary_reloption(RELOPT_KIND_LOCAL,
+ name, desc,
+ default_val, 0);
+
+ add_local_reloption(relopts, (relopt_gen *) newoption, offset);
+}
/*
* init_real_reloption
@@ -1617,6 +1689,18 @@ parse_one_reloption(relopt_value *option, char *text_str, int text_len,
option->gen->name, value)));
}
break;
+ case RELOPT_TYPE_TERNARY:
+ {
+ bool b;
+ parsed = parse_bool(value, &b);
+ option->values.ternary_val = b ? TERNARY_TRUE : TERNARY_FALSE;
+ if (validate && !parsed)
+ ereport(ERROR,
+ (errcode(ERRCODE_INVALID_PARAMETER_VALUE),
+ errmsg("invalid value for ternary option \"%s\": %s",
+ option->gen->name, value)));
+ }
+ break;
case RELOPT_TYPE_INT:
{
relopt_int *optint = (relopt_int *) option->gen;
@@ -1780,17 +1864,6 @@ fillRelOptions(void *rdopts, Size basesize,
char *itempos = ((char *) rdopts) + elems[j].offset;
char *string_val;
- /*
- * If isset_offset is provided, store whether the reloption is
- * set there.
- */
- if (elems[j].isset_offset > 0)
- {
- char *setpos = ((char *) rdopts) + elems[j].isset_offset;
-
- *(bool *) setpos = options[i].isset;
- }
-
switch (options[i].gen->type)
{
case RELOPT_TYPE_BOOL:
@@ -1798,6 +1871,11 @@ fillRelOptions(void *rdopts, Size basesize,
options[i].values.bool_val :
((relopt_bool *) options[i].gen)->default_val;
break;
+ case RELOPT_TYPE_TERNARY:
+ *(ternary *) itempos = options[i].isset ?
+ options[i].values.ternary_val :
+ ((relopt_ternary *) options[i].gen)->default_val;
+ break;
case RELOPT_TYPE_INT:
*(int *) itempos = options[i].isset ?
options[i].values.int_val :
@@ -1912,8 +1990,8 @@ default_reloptions(Datum reloptions, bool validate, relopt_kind kind)
offsetof(StdRdOptions, parallel_workers)},
{"vacuum_index_cleanup", RELOPT_TYPE_ENUM,
offsetof(StdRdOptions, vacuum_index_cleanup)},
- {"vacuum_truncate", RELOPT_TYPE_BOOL,
- offsetof(StdRdOptions, vacuum_truncate), offsetof(StdRdOptions, vacuum_truncate_set)},
+ {"vacuum_truncate", RELOPT_TYPE_TERNARY,
+ offsetof(StdRdOptions, vacuum_truncate)},
{"vacuum_max_eager_freeze_failure_rate", RELOPT_TYPE_REAL,
offsetof(StdRdOptions, vacuum_max_eager_freeze_failure_rate)}
};
@@ -1993,7 +2071,6 @@ build_local_reloptions(local_relopts *relopts, Datum options, bool validate)
elems[i].optname = opt->option->name;
elems[i].opttype = opt->option->type;
elems[i].offset = opt->offset;
- elems[i].isset_offset = 0; /* not supported for local relopts yet */
i++;
}
diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c
index 733ef40ae7..977babff54 100644
--- a/src/backend/commands/vacuum.c
+++ b/src/backend/commands/vacuum.c
@@ -2221,9 +2221,9 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams params,
{
StdRdOptions *opts = (StdRdOptions *) rel->rd_options;
- if (opts && opts->vacuum_truncate_set)
+ if (opts && opts->vacuum_truncate != TERNARY_UNSET)
{
- if (opts->vacuum_truncate)
+ if (opts->vacuum_truncate == TERNARY_TRUE)
params.truncate = VACOPTVALUE_ENABLED;
else
params.truncate = VACOPTVALUE_DISABLED;
diff --git a/src/include/access/reloptions.h b/src/include/access/reloptions.h
index a604a4702c..a436697658 100644
--- a/src/include/access/reloptions.h
+++ b/src/include/access/reloptions.h
@@ -29,6 +29,7 @@
typedef enum relopt_type
{
RELOPT_TYPE_BOOL,
+ RELOPT_TYPE_TERNARY, /* on, off, unset */
RELOPT_TYPE_INT,
RELOPT_TYPE_REAL,
RELOPT_TYPE_ENUM,
@@ -80,6 +81,7 @@ typedef struct relopt_value
union
{
bool bool_val;
+ ternary ternary_val;
int int_val;
double real_val;
int enum_val;
@@ -94,6 +96,12 @@ typedef struct relopt_bool
bool default_val;
} relopt_bool;
+typedef struct relopt_rernary
+{
+ relopt_gen gen;
+ int default_val;
+} relopt_ternary;
+
typedef struct relopt_int
{
relopt_gen gen;
@@ -152,19 +160,6 @@ typedef struct
const char *optname; /* option's name */
relopt_type opttype; /* option's datatype */
int offset; /* offset of field in result struct */
-
- /*
- * isset_offset is an optional offset of a field in the result struct that
- * stores whether the option is explicitly set for the relation or if it
- * just picked up the default value. In most cases, this can be
- * accomplished by giving the reloption a special out-of-range default
- * value (e.g., some integer reloptions use -2), but this isn't always
- * possible. For example, a Boolean reloption cannot be given an
- * out-of-range default, so we need another way to discover the source of
- * its value. This offset is only used if given a value greater than
- * zero.
- */
- int isset_offset;
} relopt_parse_elt;
/* Local reloption definition */
@@ -195,6 +190,8 @@ typedef struct local_relopts
extern relopt_kind add_reloption_kind(void);
extern void add_bool_reloption(bits32 kinds, const char *name, const char *desc,
bool default_val, LOCKMODE lockmode);
+extern void add_ternary_reloption(bits32 kinds, const char *name,
+ const char *desc, int default_val, LOCKMODE lockmode);
extern void add_int_reloption(bits32 kinds, const char *name, const char *desc,
int default_val, int min_val, int max_val,
LOCKMODE lockmode);
@@ -214,6 +211,9 @@ extern void register_reloptions_validator(local_relopts *relopts,
extern void add_local_bool_reloption(local_relopts *relopts, const char *name,
const char *desc, bool default_val,
int offset);
+extern void add_local_ternary_reloption(local_relopts *relopts,
+ const char *name, const char *desc,
+ ternary default_val, int offset);
extern void add_local_int_reloption(local_relopts *relopts, const char *name,
const char *desc, int default_val,
int min_val, int max_val, int offset);
diff --git a/src/include/c.h b/src/include/c.h
index 39022f8a9d..9c59ccccf8 100644
--- a/src/include/c.h
+++ b/src/include/c.h
@@ -488,6 +488,22 @@ typedef void (*pg_funcptr_t) (void);
#include <stdbool.h>
+/*
+ * ternary
+ * Boolean value with an extrea "unset" option
+ *
+ * Ternary data type is used in relation options that can be "true", "false" or
+ * "unset". Since relation options are used deep inside the PostgreSQL code,
+ * this type is declared globally.
+*/
+
+typedef enum ternary
+{
+ TERNARY_FALSE = 0,
+ TERNARY_TRUE = 1,
+ TERNARY_UNSET = -1
+} ternary;
+
/* ----------------------------------------------------------------
* Section 3: standard system types
diff --git a/src/include/utils/rel.h b/src/include/utils/rel.h
index b552359915..7346d618f9 100644
--- a/src/include/utils/rel.h
+++ b/src/include/utils/rel.h
@@ -346,8 +346,7 @@ typedef struct StdRdOptions
bool user_catalog_table; /* use as an additional catalog relation */
int parallel_workers; /* max number of parallel workers */
StdRdOptIndexCleanup vacuum_index_cleanup; /* controls index vacuuming */
- bool vacuum_truncate; /* enables vacuum to truncate a relation */
- bool vacuum_truncate_set; /* whether vacuum_truncate is set */
+ ternary vacuum_truncate; /* enables vacuum to truncate a relation */
/*
* Fraction of pages in a relation that vacuum can eagerly scan and fail
--
2.39.2
--nextPart5129932.5fSG56mABF
Content-Disposition: attachment;
filename="v2-0003-Add-alias-to-be-used-as-unset-state.patch"
Content-Transfer-Encoding: 7Bit
Content-Type: text/x-patch; charset="unicode-2-0-utf-8";
name="v2-0003-Add-alias-to-be-used-as-unset-state.patch"
^ permalink raw reply [nested|flat] 868+ messages in thread
* [PATCH v2 2/4] Introduce ternary reloptions
@ 2025-09-04 14:41 Nikolay Shaplov <[email protected]>
0 siblings, 0 replies; 868+ messages in thread
From: Nikolay Shaplov @ 2025-09-04 14:41 UTC (permalink / raw)
Introduce ternary reloption as a replacement for current `vacuum_truncate`
implementation. Remove `vacuum_truncate_set` additional flag and using
`TERNARY_UNSET` value instead.
---
src/backend/access/common/reloptions.c | 129 ++++++++++++++++++++-----
src/backend/commands/vacuum.c | 4 +-
src/include/access/reloptions.h | 26 ++---
src/include/c.h | 16 +++
src/include/utils/rel.h | 3 +-
5 files changed, 135 insertions(+), 43 deletions(-)
diff --git a/src/backend/access/common/reloptions.c b/src/backend/access/common/reloptions.c
index 0af3fea68f..f543f11001 100644
--- a/src/backend/access/common/reloptions.c
+++ b/src/backend/access/common/reloptions.c
@@ -40,9 +40,9 @@
*
* To add an option:
*
- * (i) decide on a type (bool, integer, real, enum, string), name, default
- * value, upper and lower bounds (if applicable); for strings, consider a
- * validation routine.
+ * (i) decide on a type (bool, ternary, integer, real, enum, string), name,
+ * default value, upper and lower bounds (if applicable); for strings,
+ * consider a validation routine.
* (ii) add a record below (or use add_<type>_reloption).
* (iii) add it to the appropriate options struct (perhaps StdRdOptions)
* (iv) add it to the appropriate handling routine (perhaps
@@ -147,15 +147,6 @@ static relopt_bool boolRelOpts[] =
},
false
},
- {
- {
- "vacuum_truncate",
- "Enables vacuum to truncate empty pages at the end of this table",
- RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
- ShareUpdateExclusiveLock
- },
- true
- },
{
{
"deduplicate_items",
@@ -170,6 +161,21 @@ static relopt_bool boolRelOpts[] =
{{NULL}}
};
+static relopt_ternary ternaryRelOpts[] =
+{
+ {
+ {
+ "vacuum_truncate",
+ "Enables vacuum to truncate empty pages at the end of this table",
+ RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
+ ShareUpdateExclusiveLock
+ },
+ TERNARY_UNSET
+ },
+ /* list terminator */
+ {{NULL}}
+};
+
static relopt_int intRelOpts[] =
{
{
@@ -600,6 +606,13 @@ initialize_reloptions(void)
boolRelOpts[i].gen.lockmode));
j++;
}
+ for (i = 0; ternaryRelOpts[i].gen.name; i++)
+ {
+ Assert(DoLockModesConflict(ternaryRelOpts[i].gen.lockmode,
+ ternaryRelOpts[i].gen.lockmode));
+ j++;
+ }
+
for (i = 0; intRelOpts[i].gen.name; i++)
{
Assert(DoLockModesConflict(intRelOpts[i].gen.lockmode,
@@ -640,6 +653,14 @@ initialize_reloptions(void)
j++;
}
+ for (i = 0; ternaryRelOpts[i].gen.name; i++)
+ {
+ relOpts[j] = &ternaryRelOpts[i].gen;
+ relOpts[j]->type = RELOPT_TYPE_TERNARY;
+ relOpts[j]->namelen = strlen(relOpts[j]->name);
+ j++;
+ }
+
for (i = 0; intRelOpts[i].gen.name; i++)
{
relOpts[j] = &intRelOpts[i].gen;
@@ -800,6 +821,9 @@ allocate_reloption(bits32 kinds, int type, const char *name, const char *desc,
case RELOPT_TYPE_BOOL:
size = sizeof(relopt_bool);
break;
+ case RELOPT_TYPE_TERNARY:
+ size = sizeof(relopt_ternary);
+ break;
case RELOPT_TYPE_INT:
size = sizeof(relopt_int);
break;
@@ -883,6 +907,54 @@ add_local_bool_reloption(local_relopts *relopts, const char *name,
add_local_reloption(relopts, (relopt_gen *) newoption, offset);
}
+/*
+ * init_ternary_reloption
+ * Allocate and initialize a new ternary reloption
+ */
+static relopt_ternary *
+init_ternary_reloption(bits32 kinds, const char *name, const char *desc,
+ ternary default_val, LOCKMODE lockmode)
+{
+ relopt_ternary *newoption;
+
+ newoption = (relopt_ternary *) allocate_reloption(kinds,
+ RELOPT_TYPE_TERNARY, name, desc, lockmode);
+ newoption->default_val = default_val;
+
+ return newoption;
+}
+
+/*
+ * add_ternary_reloption
+ * Add a new ternary reloption
+ */
+void
+add_ternary_reloption(bits32 kinds, const char *name, const char *desc,
+ ternary default_val, LOCKMODE lockmode)
+{
+ relopt_ternary *newoption = init_ternary_reloption(kinds, name, desc,
+ default_val, lockmode);
+
+ add_reloption((relopt_gen *) newoption);
+}
+
+/*
+ * add_local_ternary_reloption
+ * Add a new ternary local reloption
+ *
+ * 'offset' is offset of ternary-typed field.
+ */
+void
+add_local_ternary_reloption(local_relopts *relopts, const char *name,
+ const char *desc, ternary default_val,
+ int offset)
+{
+ relopt_ternary *newoption = init_ternary_reloption(RELOPT_KIND_LOCAL,
+ name, desc,
+ default_val, 0);
+
+ add_local_reloption(relopts, (relopt_gen *) newoption, offset);
+}
/*
* init_real_reloption
@@ -1617,6 +1689,18 @@ parse_one_reloption(relopt_value *option, char *text_str, int text_len,
option->gen->name, value)));
}
break;
+ case RELOPT_TYPE_TERNARY:
+ {
+ bool b;
+ parsed = parse_bool(value, &b);
+ option->values.ternary_val = b ? TERNARY_TRUE : TERNARY_FALSE;
+ if (validate && !parsed)
+ ereport(ERROR,
+ (errcode(ERRCODE_INVALID_PARAMETER_VALUE),
+ errmsg("invalid value for ternary option \"%s\": %s",
+ option->gen->name, value)));
+ }
+ break;
case RELOPT_TYPE_INT:
{
relopt_int *optint = (relopt_int *) option->gen;
@@ -1780,17 +1864,6 @@ fillRelOptions(void *rdopts, Size basesize,
char *itempos = ((char *) rdopts) + elems[j].offset;
char *string_val;
- /*
- * If isset_offset is provided, store whether the reloption is
- * set there.
- */
- if (elems[j].isset_offset > 0)
- {
- char *setpos = ((char *) rdopts) + elems[j].isset_offset;
-
- *(bool *) setpos = options[i].isset;
- }
-
switch (options[i].gen->type)
{
case RELOPT_TYPE_BOOL:
@@ -1798,6 +1871,11 @@ fillRelOptions(void *rdopts, Size basesize,
options[i].values.bool_val :
((relopt_bool *) options[i].gen)->default_val;
break;
+ case RELOPT_TYPE_TERNARY:
+ *(ternary *) itempos = options[i].isset ?
+ options[i].values.ternary_val :
+ ((relopt_ternary *) options[i].gen)->default_val;
+ break;
case RELOPT_TYPE_INT:
*(int *) itempos = options[i].isset ?
options[i].values.int_val :
@@ -1912,8 +1990,8 @@ default_reloptions(Datum reloptions, bool validate, relopt_kind kind)
offsetof(StdRdOptions, parallel_workers)},
{"vacuum_index_cleanup", RELOPT_TYPE_ENUM,
offsetof(StdRdOptions, vacuum_index_cleanup)},
- {"vacuum_truncate", RELOPT_TYPE_BOOL,
- offsetof(StdRdOptions, vacuum_truncate), offsetof(StdRdOptions, vacuum_truncate_set)},
+ {"vacuum_truncate", RELOPT_TYPE_TERNARY,
+ offsetof(StdRdOptions, vacuum_truncate)},
{"vacuum_max_eager_freeze_failure_rate", RELOPT_TYPE_REAL,
offsetof(StdRdOptions, vacuum_max_eager_freeze_failure_rate)}
};
@@ -1993,7 +2071,6 @@ build_local_reloptions(local_relopts *relopts, Datum options, bool validate)
elems[i].optname = opt->option->name;
elems[i].opttype = opt->option->type;
elems[i].offset = opt->offset;
- elems[i].isset_offset = 0; /* not supported for local relopts yet */
i++;
}
diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c
index 733ef40ae7..977babff54 100644
--- a/src/backend/commands/vacuum.c
+++ b/src/backend/commands/vacuum.c
@@ -2221,9 +2221,9 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams params,
{
StdRdOptions *opts = (StdRdOptions *) rel->rd_options;
- if (opts && opts->vacuum_truncate_set)
+ if (opts && opts->vacuum_truncate != TERNARY_UNSET)
{
- if (opts->vacuum_truncate)
+ if (opts->vacuum_truncate == TERNARY_TRUE)
params.truncate = VACOPTVALUE_ENABLED;
else
params.truncate = VACOPTVALUE_DISABLED;
diff --git a/src/include/access/reloptions.h b/src/include/access/reloptions.h
index a604a4702c..a436697658 100644
--- a/src/include/access/reloptions.h
+++ b/src/include/access/reloptions.h
@@ -29,6 +29,7 @@
typedef enum relopt_type
{
RELOPT_TYPE_BOOL,
+ RELOPT_TYPE_TERNARY, /* on, off, unset */
RELOPT_TYPE_INT,
RELOPT_TYPE_REAL,
RELOPT_TYPE_ENUM,
@@ -80,6 +81,7 @@ typedef struct relopt_value
union
{
bool bool_val;
+ ternary ternary_val;
int int_val;
double real_val;
int enum_val;
@@ -94,6 +96,12 @@ typedef struct relopt_bool
bool default_val;
} relopt_bool;
+typedef struct relopt_rernary
+{
+ relopt_gen gen;
+ int default_val;
+} relopt_ternary;
+
typedef struct relopt_int
{
relopt_gen gen;
@@ -152,19 +160,6 @@ typedef struct
const char *optname; /* option's name */
relopt_type opttype; /* option's datatype */
int offset; /* offset of field in result struct */
-
- /*
- * isset_offset is an optional offset of a field in the result struct that
- * stores whether the option is explicitly set for the relation or if it
- * just picked up the default value. In most cases, this can be
- * accomplished by giving the reloption a special out-of-range default
- * value (e.g., some integer reloptions use -2), but this isn't always
- * possible. For example, a Boolean reloption cannot be given an
- * out-of-range default, so we need another way to discover the source of
- * its value. This offset is only used if given a value greater than
- * zero.
- */
- int isset_offset;
} relopt_parse_elt;
/* Local reloption definition */
@@ -195,6 +190,8 @@ typedef struct local_relopts
extern relopt_kind add_reloption_kind(void);
extern void add_bool_reloption(bits32 kinds, const char *name, const char *desc,
bool default_val, LOCKMODE lockmode);
+extern void add_ternary_reloption(bits32 kinds, const char *name,
+ const char *desc, int default_val, LOCKMODE lockmode);
extern void add_int_reloption(bits32 kinds, const char *name, const char *desc,
int default_val, int min_val, int max_val,
LOCKMODE lockmode);
@@ -214,6 +211,9 @@ extern void register_reloptions_validator(local_relopts *relopts,
extern void add_local_bool_reloption(local_relopts *relopts, const char *name,
const char *desc, bool default_val,
int offset);
+extern void add_local_ternary_reloption(local_relopts *relopts,
+ const char *name, const char *desc,
+ ternary default_val, int offset);
extern void add_local_int_reloption(local_relopts *relopts, const char *name,
const char *desc, int default_val,
int min_val, int max_val, int offset);
diff --git a/src/include/c.h b/src/include/c.h
index 39022f8a9d..9c59ccccf8 100644
--- a/src/include/c.h
+++ b/src/include/c.h
@@ -488,6 +488,22 @@ typedef void (*pg_funcptr_t) (void);
#include <stdbool.h>
+/*
+ * ternary
+ * Boolean value with an extrea "unset" option
+ *
+ * Ternary data type is used in relation options that can be "true", "false" or
+ * "unset". Since relation options are used deep inside the PostgreSQL code,
+ * this type is declared globally.
+*/
+
+typedef enum ternary
+{
+ TERNARY_FALSE = 0,
+ TERNARY_TRUE = 1,
+ TERNARY_UNSET = -1
+} ternary;
+
/* ----------------------------------------------------------------
* Section 3: standard system types
diff --git a/src/include/utils/rel.h b/src/include/utils/rel.h
index b552359915..7346d618f9 100644
--- a/src/include/utils/rel.h
+++ b/src/include/utils/rel.h
@@ -346,8 +346,7 @@ typedef struct StdRdOptions
bool user_catalog_table; /* use as an additional catalog relation */
int parallel_workers; /* max number of parallel workers */
StdRdOptIndexCleanup vacuum_index_cleanup; /* controls index vacuuming */
- bool vacuum_truncate; /* enables vacuum to truncate a relation */
- bool vacuum_truncate_set; /* whether vacuum_truncate is set */
+ ternary vacuum_truncate; /* enables vacuum to truncate a relation */
/*
* Fraction of pages in a relation that vacuum can eagerly scan and fail
--
2.39.2
--nextPart5129932.5fSG56mABF
Content-Disposition: attachment;
filename="v2-0003-Add-alias-to-be-used-as-unset-state.patch"
Content-Transfer-Encoding: 7Bit
Content-Type: text/x-patch; charset="unicode-2-0-utf-8";
name="v2-0003-Add-alias-to-be-used-as-unset-state.patch"
^ permalink raw reply [nested|flat] 868+ messages in thread
* [PATCH v2a 2/4] Introduce ternary reloptions
@ 2025-09-04 14:41 Nikolay Shaplov <[email protected]>
0 siblings, 0 replies; 868+ messages in thread
From: Nikolay Shaplov @ 2025-09-04 14:41 UTC (permalink / raw)
Introduce ternary reloption as a replacement for current `vacuum_truncate`
implementation. Remove `vacuum_truncate_set` additional flag and using
`TERNARY_UNSET` value instead.
---
src/backend/access/common/reloptions.c | 129 ++++++++++++++++++++-----
src/backend/commands/vacuum.c | 4 +-
src/include/access/reloptions.h | 26 ++---
src/include/c.h | 16 +++
src/include/utils/rel.h | 3 +-
5 files changed, 135 insertions(+), 43 deletions(-)
diff --git a/src/backend/access/common/reloptions.c b/src/backend/access/common/reloptions.c
index 0af3fea68f..f543f11001 100644
--- a/src/backend/access/common/reloptions.c
+++ b/src/backend/access/common/reloptions.c
@@ -40,9 +40,9 @@
*
* To add an option:
*
- * (i) decide on a type (bool, integer, real, enum, string), name, default
- * value, upper and lower bounds (if applicable); for strings, consider a
- * validation routine.
+ * (i) decide on a type (bool, ternary, integer, real, enum, string), name,
+ * default value, upper and lower bounds (if applicable); for strings,
+ * consider a validation routine.
* (ii) add a record below (or use add_<type>_reloption).
* (iii) add it to the appropriate options struct (perhaps StdRdOptions)
* (iv) add it to the appropriate handling routine (perhaps
@@ -147,15 +147,6 @@ static relopt_bool boolRelOpts[] =
},
false
},
- {
- {
- "vacuum_truncate",
- "Enables vacuum to truncate empty pages at the end of this table",
- RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
- ShareUpdateExclusiveLock
- },
- true
- },
{
{
"deduplicate_items",
@@ -170,6 +161,21 @@ static relopt_bool boolRelOpts[] =
{{NULL}}
};
+static relopt_ternary ternaryRelOpts[] =
+{
+ {
+ {
+ "vacuum_truncate",
+ "Enables vacuum to truncate empty pages at the end of this table",
+ RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
+ ShareUpdateExclusiveLock
+ },
+ TERNARY_UNSET
+ },
+ /* list terminator */
+ {{NULL}}
+};
+
static relopt_int intRelOpts[] =
{
{
@@ -600,6 +606,13 @@ initialize_reloptions(void)
boolRelOpts[i].gen.lockmode));
j++;
}
+ for (i = 0; ternaryRelOpts[i].gen.name; i++)
+ {
+ Assert(DoLockModesConflict(ternaryRelOpts[i].gen.lockmode,
+ ternaryRelOpts[i].gen.lockmode));
+ j++;
+ }
+
for (i = 0; intRelOpts[i].gen.name; i++)
{
Assert(DoLockModesConflict(intRelOpts[i].gen.lockmode,
@@ -640,6 +653,14 @@ initialize_reloptions(void)
j++;
}
+ for (i = 0; ternaryRelOpts[i].gen.name; i++)
+ {
+ relOpts[j] = &ternaryRelOpts[i].gen;
+ relOpts[j]->type = RELOPT_TYPE_TERNARY;
+ relOpts[j]->namelen = strlen(relOpts[j]->name);
+ j++;
+ }
+
for (i = 0; intRelOpts[i].gen.name; i++)
{
relOpts[j] = &intRelOpts[i].gen;
@@ -800,6 +821,9 @@ allocate_reloption(bits32 kinds, int type, const char *name, const char *desc,
case RELOPT_TYPE_BOOL:
size = sizeof(relopt_bool);
break;
+ case RELOPT_TYPE_TERNARY:
+ size = sizeof(relopt_ternary);
+ break;
case RELOPT_TYPE_INT:
size = sizeof(relopt_int);
break;
@@ -883,6 +907,54 @@ add_local_bool_reloption(local_relopts *relopts, const char *name,
add_local_reloption(relopts, (relopt_gen *) newoption, offset);
}
+/*
+ * init_ternary_reloption
+ * Allocate and initialize a new ternary reloption
+ */
+static relopt_ternary *
+init_ternary_reloption(bits32 kinds, const char *name, const char *desc,
+ ternary default_val, LOCKMODE lockmode)
+{
+ relopt_ternary *newoption;
+
+ newoption = (relopt_ternary *) allocate_reloption(kinds,
+ RELOPT_TYPE_TERNARY, name, desc, lockmode);
+ newoption->default_val = default_val;
+
+ return newoption;
+}
+
+/*
+ * add_ternary_reloption
+ * Add a new ternary reloption
+ */
+void
+add_ternary_reloption(bits32 kinds, const char *name, const char *desc,
+ ternary default_val, LOCKMODE lockmode)
+{
+ relopt_ternary *newoption = init_ternary_reloption(kinds, name, desc,
+ default_val, lockmode);
+
+ add_reloption((relopt_gen *) newoption);
+}
+
+/*
+ * add_local_ternary_reloption
+ * Add a new ternary local reloption
+ *
+ * 'offset' is offset of ternary-typed field.
+ */
+void
+add_local_ternary_reloption(local_relopts *relopts, const char *name,
+ const char *desc, ternary default_val,
+ int offset)
+{
+ relopt_ternary *newoption = init_ternary_reloption(RELOPT_KIND_LOCAL,
+ name, desc,
+ default_val, 0);
+
+ add_local_reloption(relopts, (relopt_gen *) newoption, offset);
+}
/*
* init_real_reloption
@@ -1617,6 +1689,18 @@ parse_one_reloption(relopt_value *option, char *text_str, int text_len,
option->gen->name, value)));
}
break;
+ case RELOPT_TYPE_TERNARY:
+ {
+ bool b;
+ parsed = parse_bool(value, &b);
+ option->values.ternary_val = b ? TERNARY_TRUE : TERNARY_FALSE;
+ if (validate && !parsed)
+ ereport(ERROR,
+ (errcode(ERRCODE_INVALID_PARAMETER_VALUE),
+ errmsg("invalid value for ternary option \"%s\": %s",
+ option->gen->name, value)));
+ }
+ break;
case RELOPT_TYPE_INT:
{
relopt_int *optint = (relopt_int *) option->gen;
@@ -1780,17 +1864,6 @@ fillRelOptions(void *rdopts, Size basesize,
char *itempos = ((char *) rdopts) + elems[j].offset;
char *string_val;
- /*
- * If isset_offset is provided, store whether the reloption is
- * set there.
- */
- if (elems[j].isset_offset > 0)
- {
- char *setpos = ((char *) rdopts) + elems[j].isset_offset;
-
- *(bool *) setpos = options[i].isset;
- }
-
switch (options[i].gen->type)
{
case RELOPT_TYPE_BOOL:
@@ -1798,6 +1871,11 @@ fillRelOptions(void *rdopts, Size basesize,
options[i].values.bool_val :
((relopt_bool *) options[i].gen)->default_val;
break;
+ case RELOPT_TYPE_TERNARY:
+ *(ternary *) itempos = options[i].isset ?
+ options[i].values.ternary_val :
+ ((relopt_ternary *) options[i].gen)->default_val;
+ break;
case RELOPT_TYPE_INT:
*(int *) itempos = options[i].isset ?
options[i].values.int_val :
@@ -1912,8 +1990,8 @@ default_reloptions(Datum reloptions, bool validate, relopt_kind kind)
offsetof(StdRdOptions, parallel_workers)},
{"vacuum_index_cleanup", RELOPT_TYPE_ENUM,
offsetof(StdRdOptions, vacuum_index_cleanup)},
- {"vacuum_truncate", RELOPT_TYPE_BOOL,
- offsetof(StdRdOptions, vacuum_truncate), offsetof(StdRdOptions, vacuum_truncate_set)},
+ {"vacuum_truncate", RELOPT_TYPE_TERNARY,
+ offsetof(StdRdOptions, vacuum_truncate)},
{"vacuum_max_eager_freeze_failure_rate", RELOPT_TYPE_REAL,
offsetof(StdRdOptions, vacuum_max_eager_freeze_failure_rate)}
};
@@ -1993,7 +2071,6 @@ build_local_reloptions(local_relopts *relopts, Datum options, bool validate)
elems[i].optname = opt->option->name;
elems[i].opttype = opt->option->type;
elems[i].offset = opt->offset;
- elems[i].isset_offset = 0; /* not supported for local relopts yet */
i++;
}
diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c
index 733ef40ae7..977babff54 100644
--- a/src/backend/commands/vacuum.c
+++ b/src/backend/commands/vacuum.c
@@ -2221,9 +2221,9 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams params,
{
StdRdOptions *opts = (StdRdOptions *) rel->rd_options;
- if (opts && opts->vacuum_truncate_set)
+ if (opts && opts->vacuum_truncate != TERNARY_UNSET)
{
- if (opts->vacuum_truncate)
+ if (opts->vacuum_truncate == TERNARY_TRUE)
params.truncate = VACOPTVALUE_ENABLED;
else
params.truncate = VACOPTVALUE_DISABLED;
diff --git a/src/include/access/reloptions.h b/src/include/access/reloptions.h
index a604a4702c..a436697658 100644
--- a/src/include/access/reloptions.h
+++ b/src/include/access/reloptions.h
@@ -29,6 +29,7 @@
typedef enum relopt_type
{
RELOPT_TYPE_BOOL,
+ RELOPT_TYPE_TERNARY, /* on, off, unset */
RELOPT_TYPE_INT,
RELOPT_TYPE_REAL,
RELOPT_TYPE_ENUM,
@@ -80,6 +81,7 @@ typedef struct relopt_value
union
{
bool bool_val;
+ ternary ternary_val;
int int_val;
double real_val;
int enum_val;
@@ -94,6 +96,12 @@ typedef struct relopt_bool
bool default_val;
} relopt_bool;
+typedef struct relopt_rernary
+{
+ relopt_gen gen;
+ int default_val;
+} relopt_ternary;
+
typedef struct relopt_int
{
relopt_gen gen;
@@ -152,19 +160,6 @@ typedef struct
const char *optname; /* option's name */
relopt_type opttype; /* option's datatype */
int offset; /* offset of field in result struct */
-
- /*
- * isset_offset is an optional offset of a field in the result struct that
- * stores whether the option is explicitly set for the relation or if it
- * just picked up the default value. In most cases, this can be
- * accomplished by giving the reloption a special out-of-range default
- * value (e.g., some integer reloptions use -2), but this isn't always
- * possible. For example, a Boolean reloption cannot be given an
- * out-of-range default, so we need another way to discover the source of
- * its value. This offset is only used if given a value greater than
- * zero.
- */
- int isset_offset;
} relopt_parse_elt;
/* Local reloption definition */
@@ -195,6 +190,8 @@ typedef struct local_relopts
extern relopt_kind add_reloption_kind(void);
extern void add_bool_reloption(bits32 kinds, const char *name, const char *desc,
bool default_val, LOCKMODE lockmode);
+extern void add_ternary_reloption(bits32 kinds, const char *name,
+ const char *desc, int default_val, LOCKMODE lockmode);
extern void add_int_reloption(bits32 kinds, const char *name, const char *desc,
int default_val, int min_val, int max_val,
LOCKMODE lockmode);
@@ -214,6 +211,9 @@ extern void register_reloptions_validator(local_relopts *relopts,
extern void add_local_bool_reloption(local_relopts *relopts, const char *name,
const char *desc, bool default_val,
int offset);
+extern void add_local_ternary_reloption(local_relopts *relopts,
+ const char *name, const char *desc,
+ ternary default_val, int offset);
extern void add_local_int_reloption(local_relopts *relopts, const char *name,
const char *desc, int default_val,
int min_val, int max_val, int offset);
diff --git a/src/include/c.h b/src/include/c.h
index 39022f8a9d..9c59ccccf8 100644
--- a/src/include/c.h
+++ b/src/include/c.h
@@ -488,6 +488,22 @@ typedef void (*pg_funcptr_t) (void);
#include <stdbool.h>
+/*
+ * ternary
+ * Boolean value with an extrea "unset" option
+ *
+ * Ternary data type is used in relation options that can be "true", "false" or
+ * "unset". Since relation options are used deep inside the PostgreSQL code,
+ * this type is declared globally.
+*/
+
+typedef enum ternary
+{
+ TERNARY_FALSE = 0,
+ TERNARY_TRUE = 1,
+ TERNARY_UNSET = -1
+} ternary;
+
/* ----------------------------------------------------------------
* Section 3: standard system types
diff --git a/src/include/utils/rel.h b/src/include/utils/rel.h
index b552359915..7346d618f9 100644
--- a/src/include/utils/rel.h
+++ b/src/include/utils/rel.h
@@ -346,8 +346,7 @@ typedef struct StdRdOptions
bool user_catalog_table; /* use as an additional catalog relation */
int parallel_workers; /* max number of parallel workers */
StdRdOptIndexCleanup vacuum_index_cleanup; /* controls index vacuuming */
- bool vacuum_truncate; /* enables vacuum to truncate a relation */
- bool vacuum_truncate_set; /* whether vacuum_truncate is set */
+ ternary vacuum_truncate; /* enables vacuum to truncate a relation */
/*
* Fraction of pages in a relation that vacuum can eagerly scan and fail
--
2.39.2
--nextPart4155600.3ZeAukHxDK
Content-Disposition: attachment;
filename="v2a-0003-Add-alias-to-be-used-as-unset-state.patch"
Content-Transfer-Encoding: 7Bit
Content-Type: text/x-patch; charset="unicode-2-0-utf-8";
name="v2a-0003-Add-alias-to-be-used-as-unset-state.patch"
^ permalink raw reply [nested|flat] 868+ messages in thread
* [PATCH v2a 2/4] Introduce ternary reloptions
@ 2025-09-04 14:41 Nikolay Shaplov <[email protected]>
0 siblings, 0 replies; 868+ messages in thread
From: Nikolay Shaplov @ 2025-09-04 14:41 UTC (permalink / raw)
Introduce ternary reloption as a replacement for current `vacuum_truncate`
implementation. Remove `vacuum_truncate_set` additional flag and using
`TERNARY_UNSET` value instead.
---
src/backend/access/common/reloptions.c | 129 ++++++++++++++++++++-----
src/backend/commands/vacuum.c | 4 +-
src/include/access/reloptions.h | 26 ++---
src/include/c.h | 16 +++
src/include/utils/rel.h | 3 +-
5 files changed, 135 insertions(+), 43 deletions(-)
diff --git a/src/backend/access/common/reloptions.c b/src/backend/access/common/reloptions.c
index 0af3fea68f..f543f11001 100644
--- a/src/backend/access/common/reloptions.c
+++ b/src/backend/access/common/reloptions.c
@@ -40,9 +40,9 @@
*
* To add an option:
*
- * (i) decide on a type (bool, integer, real, enum, string), name, default
- * value, upper and lower bounds (if applicable); for strings, consider a
- * validation routine.
+ * (i) decide on a type (bool, ternary, integer, real, enum, string), name,
+ * default value, upper and lower bounds (if applicable); for strings,
+ * consider a validation routine.
* (ii) add a record below (or use add_<type>_reloption).
* (iii) add it to the appropriate options struct (perhaps StdRdOptions)
* (iv) add it to the appropriate handling routine (perhaps
@@ -147,15 +147,6 @@ static relopt_bool boolRelOpts[] =
},
false
},
- {
- {
- "vacuum_truncate",
- "Enables vacuum to truncate empty pages at the end of this table",
- RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
- ShareUpdateExclusiveLock
- },
- true
- },
{
{
"deduplicate_items",
@@ -170,6 +161,21 @@ static relopt_bool boolRelOpts[] =
{{NULL}}
};
+static relopt_ternary ternaryRelOpts[] =
+{
+ {
+ {
+ "vacuum_truncate",
+ "Enables vacuum to truncate empty pages at the end of this table",
+ RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
+ ShareUpdateExclusiveLock
+ },
+ TERNARY_UNSET
+ },
+ /* list terminator */
+ {{NULL}}
+};
+
static relopt_int intRelOpts[] =
{
{
@@ -600,6 +606,13 @@ initialize_reloptions(void)
boolRelOpts[i].gen.lockmode));
j++;
}
+ for (i = 0; ternaryRelOpts[i].gen.name; i++)
+ {
+ Assert(DoLockModesConflict(ternaryRelOpts[i].gen.lockmode,
+ ternaryRelOpts[i].gen.lockmode));
+ j++;
+ }
+
for (i = 0; intRelOpts[i].gen.name; i++)
{
Assert(DoLockModesConflict(intRelOpts[i].gen.lockmode,
@@ -640,6 +653,14 @@ initialize_reloptions(void)
j++;
}
+ for (i = 0; ternaryRelOpts[i].gen.name; i++)
+ {
+ relOpts[j] = &ternaryRelOpts[i].gen;
+ relOpts[j]->type = RELOPT_TYPE_TERNARY;
+ relOpts[j]->namelen = strlen(relOpts[j]->name);
+ j++;
+ }
+
for (i = 0; intRelOpts[i].gen.name; i++)
{
relOpts[j] = &intRelOpts[i].gen;
@@ -800,6 +821,9 @@ allocate_reloption(bits32 kinds, int type, const char *name, const char *desc,
case RELOPT_TYPE_BOOL:
size = sizeof(relopt_bool);
break;
+ case RELOPT_TYPE_TERNARY:
+ size = sizeof(relopt_ternary);
+ break;
case RELOPT_TYPE_INT:
size = sizeof(relopt_int);
break;
@@ -883,6 +907,54 @@ add_local_bool_reloption(local_relopts *relopts, const char *name,
add_local_reloption(relopts, (relopt_gen *) newoption, offset);
}
+/*
+ * init_ternary_reloption
+ * Allocate and initialize a new ternary reloption
+ */
+static relopt_ternary *
+init_ternary_reloption(bits32 kinds, const char *name, const char *desc,
+ ternary default_val, LOCKMODE lockmode)
+{
+ relopt_ternary *newoption;
+
+ newoption = (relopt_ternary *) allocate_reloption(kinds,
+ RELOPT_TYPE_TERNARY, name, desc, lockmode);
+ newoption->default_val = default_val;
+
+ return newoption;
+}
+
+/*
+ * add_ternary_reloption
+ * Add a new ternary reloption
+ */
+void
+add_ternary_reloption(bits32 kinds, const char *name, const char *desc,
+ ternary default_val, LOCKMODE lockmode)
+{
+ relopt_ternary *newoption = init_ternary_reloption(kinds, name, desc,
+ default_val, lockmode);
+
+ add_reloption((relopt_gen *) newoption);
+}
+
+/*
+ * add_local_ternary_reloption
+ * Add a new ternary local reloption
+ *
+ * 'offset' is offset of ternary-typed field.
+ */
+void
+add_local_ternary_reloption(local_relopts *relopts, const char *name,
+ const char *desc, ternary default_val,
+ int offset)
+{
+ relopt_ternary *newoption = init_ternary_reloption(RELOPT_KIND_LOCAL,
+ name, desc,
+ default_val, 0);
+
+ add_local_reloption(relopts, (relopt_gen *) newoption, offset);
+}
/*
* init_real_reloption
@@ -1617,6 +1689,18 @@ parse_one_reloption(relopt_value *option, char *text_str, int text_len,
option->gen->name, value)));
}
break;
+ case RELOPT_TYPE_TERNARY:
+ {
+ bool b;
+ parsed = parse_bool(value, &b);
+ option->values.ternary_val = b ? TERNARY_TRUE : TERNARY_FALSE;
+ if (validate && !parsed)
+ ereport(ERROR,
+ (errcode(ERRCODE_INVALID_PARAMETER_VALUE),
+ errmsg("invalid value for ternary option \"%s\": %s",
+ option->gen->name, value)));
+ }
+ break;
case RELOPT_TYPE_INT:
{
relopt_int *optint = (relopt_int *) option->gen;
@@ -1780,17 +1864,6 @@ fillRelOptions(void *rdopts, Size basesize,
char *itempos = ((char *) rdopts) + elems[j].offset;
char *string_val;
- /*
- * If isset_offset is provided, store whether the reloption is
- * set there.
- */
- if (elems[j].isset_offset > 0)
- {
- char *setpos = ((char *) rdopts) + elems[j].isset_offset;
-
- *(bool *) setpos = options[i].isset;
- }
-
switch (options[i].gen->type)
{
case RELOPT_TYPE_BOOL:
@@ -1798,6 +1871,11 @@ fillRelOptions(void *rdopts, Size basesize,
options[i].values.bool_val :
((relopt_bool *) options[i].gen)->default_val;
break;
+ case RELOPT_TYPE_TERNARY:
+ *(ternary *) itempos = options[i].isset ?
+ options[i].values.ternary_val :
+ ((relopt_ternary *) options[i].gen)->default_val;
+ break;
case RELOPT_TYPE_INT:
*(int *) itempos = options[i].isset ?
options[i].values.int_val :
@@ -1912,8 +1990,8 @@ default_reloptions(Datum reloptions, bool validate, relopt_kind kind)
offsetof(StdRdOptions, parallel_workers)},
{"vacuum_index_cleanup", RELOPT_TYPE_ENUM,
offsetof(StdRdOptions, vacuum_index_cleanup)},
- {"vacuum_truncate", RELOPT_TYPE_BOOL,
- offsetof(StdRdOptions, vacuum_truncate), offsetof(StdRdOptions, vacuum_truncate_set)},
+ {"vacuum_truncate", RELOPT_TYPE_TERNARY,
+ offsetof(StdRdOptions, vacuum_truncate)},
{"vacuum_max_eager_freeze_failure_rate", RELOPT_TYPE_REAL,
offsetof(StdRdOptions, vacuum_max_eager_freeze_failure_rate)}
};
@@ -1993,7 +2071,6 @@ build_local_reloptions(local_relopts *relopts, Datum options, bool validate)
elems[i].optname = opt->option->name;
elems[i].opttype = opt->option->type;
elems[i].offset = opt->offset;
- elems[i].isset_offset = 0; /* not supported for local relopts yet */
i++;
}
diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c
index 733ef40ae7..977babff54 100644
--- a/src/backend/commands/vacuum.c
+++ b/src/backend/commands/vacuum.c
@@ -2221,9 +2221,9 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams params,
{
StdRdOptions *opts = (StdRdOptions *) rel->rd_options;
- if (opts && opts->vacuum_truncate_set)
+ if (opts && opts->vacuum_truncate != TERNARY_UNSET)
{
- if (opts->vacuum_truncate)
+ if (opts->vacuum_truncate == TERNARY_TRUE)
params.truncate = VACOPTVALUE_ENABLED;
else
params.truncate = VACOPTVALUE_DISABLED;
diff --git a/src/include/access/reloptions.h b/src/include/access/reloptions.h
index a604a4702c..a436697658 100644
--- a/src/include/access/reloptions.h
+++ b/src/include/access/reloptions.h
@@ -29,6 +29,7 @@
typedef enum relopt_type
{
RELOPT_TYPE_BOOL,
+ RELOPT_TYPE_TERNARY, /* on, off, unset */
RELOPT_TYPE_INT,
RELOPT_TYPE_REAL,
RELOPT_TYPE_ENUM,
@@ -80,6 +81,7 @@ typedef struct relopt_value
union
{
bool bool_val;
+ ternary ternary_val;
int int_val;
double real_val;
int enum_val;
@@ -94,6 +96,12 @@ typedef struct relopt_bool
bool default_val;
} relopt_bool;
+typedef struct relopt_rernary
+{
+ relopt_gen gen;
+ int default_val;
+} relopt_ternary;
+
typedef struct relopt_int
{
relopt_gen gen;
@@ -152,19 +160,6 @@ typedef struct
const char *optname; /* option's name */
relopt_type opttype; /* option's datatype */
int offset; /* offset of field in result struct */
-
- /*
- * isset_offset is an optional offset of a field in the result struct that
- * stores whether the option is explicitly set for the relation or if it
- * just picked up the default value. In most cases, this can be
- * accomplished by giving the reloption a special out-of-range default
- * value (e.g., some integer reloptions use -2), but this isn't always
- * possible. For example, a Boolean reloption cannot be given an
- * out-of-range default, so we need another way to discover the source of
- * its value. This offset is only used if given a value greater than
- * zero.
- */
- int isset_offset;
} relopt_parse_elt;
/* Local reloption definition */
@@ -195,6 +190,8 @@ typedef struct local_relopts
extern relopt_kind add_reloption_kind(void);
extern void add_bool_reloption(bits32 kinds, const char *name, const char *desc,
bool default_val, LOCKMODE lockmode);
+extern void add_ternary_reloption(bits32 kinds, const char *name,
+ const char *desc, int default_val, LOCKMODE lockmode);
extern void add_int_reloption(bits32 kinds, const char *name, const char *desc,
int default_val, int min_val, int max_val,
LOCKMODE lockmode);
@@ -214,6 +211,9 @@ extern void register_reloptions_validator(local_relopts *relopts,
extern void add_local_bool_reloption(local_relopts *relopts, const char *name,
const char *desc, bool default_val,
int offset);
+extern void add_local_ternary_reloption(local_relopts *relopts,
+ const char *name, const char *desc,
+ ternary default_val, int offset);
extern void add_local_int_reloption(local_relopts *relopts, const char *name,
const char *desc, int default_val,
int min_val, int max_val, int offset);
diff --git a/src/include/c.h b/src/include/c.h
index 39022f8a9d..9c59ccccf8 100644
--- a/src/include/c.h
+++ b/src/include/c.h
@@ -488,6 +488,22 @@ typedef void (*pg_funcptr_t) (void);
#include <stdbool.h>
+/*
+ * ternary
+ * Boolean value with an extrea "unset" option
+ *
+ * Ternary data type is used in relation options that can be "true", "false" or
+ * "unset". Since relation options are used deep inside the PostgreSQL code,
+ * this type is declared globally.
+*/
+
+typedef enum ternary
+{
+ TERNARY_FALSE = 0,
+ TERNARY_TRUE = 1,
+ TERNARY_UNSET = -1
+} ternary;
+
/* ----------------------------------------------------------------
* Section 3: standard system types
diff --git a/src/include/utils/rel.h b/src/include/utils/rel.h
index b552359915..7346d618f9 100644
--- a/src/include/utils/rel.h
+++ b/src/include/utils/rel.h
@@ -346,8 +346,7 @@ typedef struct StdRdOptions
bool user_catalog_table; /* use as an additional catalog relation */
int parallel_workers; /* max number of parallel workers */
StdRdOptIndexCleanup vacuum_index_cleanup; /* controls index vacuuming */
- bool vacuum_truncate; /* enables vacuum to truncate a relation */
- bool vacuum_truncate_set; /* whether vacuum_truncate is set */
+ ternary vacuum_truncate; /* enables vacuum to truncate a relation */
/*
* Fraction of pages in a relation that vacuum can eagerly scan and fail
--
2.39.2
--nextPart4155600.3ZeAukHxDK
Content-Disposition: attachment;
filename="v2a-0003-Add-alias-to-be-used-as-unset-state.patch"
Content-Transfer-Encoding: 7Bit
Content-Type: text/x-patch; charset="unicode-2-0-utf-8";
name="v2a-0003-Add-alias-to-be-used-as-unset-state.patch"
^ permalink raw reply [nested|flat] 868+ messages in thread
* [PATCH v2a 2/4] Introduce ternary reloptions
@ 2025-09-04 14:41 Nikolay Shaplov <[email protected]>
0 siblings, 0 replies; 868+ messages in thread
From: Nikolay Shaplov @ 2025-09-04 14:41 UTC (permalink / raw)
Introduce ternary reloption as a replacement for current `vacuum_truncate`
implementation. Remove `vacuum_truncate_set` additional flag and using
`TERNARY_UNSET` value instead.
---
src/backend/access/common/reloptions.c | 129 ++++++++++++++++++++-----
src/backend/commands/vacuum.c | 4 +-
src/include/access/reloptions.h | 26 ++---
src/include/c.h | 16 +++
src/include/utils/rel.h | 3 +-
5 files changed, 135 insertions(+), 43 deletions(-)
diff --git a/src/backend/access/common/reloptions.c b/src/backend/access/common/reloptions.c
index 0af3fea68f..f543f11001 100644
--- a/src/backend/access/common/reloptions.c
+++ b/src/backend/access/common/reloptions.c
@@ -40,9 +40,9 @@
*
* To add an option:
*
- * (i) decide on a type (bool, integer, real, enum, string), name, default
- * value, upper and lower bounds (if applicable); for strings, consider a
- * validation routine.
+ * (i) decide on a type (bool, ternary, integer, real, enum, string), name,
+ * default value, upper and lower bounds (if applicable); for strings,
+ * consider a validation routine.
* (ii) add a record below (or use add_<type>_reloption).
* (iii) add it to the appropriate options struct (perhaps StdRdOptions)
* (iv) add it to the appropriate handling routine (perhaps
@@ -147,15 +147,6 @@ static relopt_bool boolRelOpts[] =
},
false
},
- {
- {
- "vacuum_truncate",
- "Enables vacuum to truncate empty pages at the end of this table",
- RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
- ShareUpdateExclusiveLock
- },
- true
- },
{
{
"deduplicate_items",
@@ -170,6 +161,21 @@ static relopt_bool boolRelOpts[] =
{{NULL}}
};
+static relopt_ternary ternaryRelOpts[] =
+{
+ {
+ {
+ "vacuum_truncate",
+ "Enables vacuum to truncate empty pages at the end of this table",
+ RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
+ ShareUpdateExclusiveLock
+ },
+ TERNARY_UNSET
+ },
+ /* list terminator */
+ {{NULL}}
+};
+
static relopt_int intRelOpts[] =
{
{
@@ -600,6 +606,13 @@ initialize_reloptions(void)
boolRelOpts[i].gen.lockmode));
j++;
}
+ for (i = 0; ternaryRelOpts[i].gen.name; i++)
+ {
+ Assert(DoLockModesConflict(ternaryRelOpts[i].gen.lockmode,
+ ternaryRelOpts[i].gen.lockmode));
+ j++;
+ }
+
for (i = 0; intRelOpts[i].gen.name; i++)
{
Assert(DoLockModesConflict(intRelOpts[i].gen.lockmode,
@@ -640,6 +653,14 @@ initialize_reloptions(void)
j++;
}
+ for (i = 0; ternaryRelOpts[i].gen.name; i++)
+ {
+ relOpts[j] = &ternaryRelOpts[i].gen;
+ relOpts[j]->type = RELOPT_TYPE_TERNARY;
+ relOpts[j]->namelen = strlen(relOpts[j]->name);
+ j++;
+ }
+
for (i = 0; intRelOpts[i].gen.name; i++)
{
relOpts[j] = &intRelOpts[i].gen;
@@ -800,6 +821,9 @@ allocate_reloption(bits32 kinds, int type, const char *name, const char *desc,
case RELOPT_TYPE_BOOL:
size = sizeof(relopt_bool);
break;
+ case RELOPT_TYPE_TERNARY:
+ size = sizeof(relopt_ternary);
+ break;
case RELOPT_TYPE_INT:
size = sizeof(relopt_int);
break;
@@ -883,6 +907,54 @@ add_local_bool_reloption(local_relopts *relopts, const char *name,
add_local_reloption(relopts, (relopt_gen *) newoption, offset);
}
+/*
+ * init_ternary_reloption
+ * Allocate and initialize a new ternary reloption
+ */
+static relopt_ternary *
+init_ternary_reloption(bits32 kinds, const char *name, const char *desc,
+ ternary default_val, LOCKMODE lockmode)
+{
+ relopt_ternary *newoption;
+
+ newoption = (relopt_ternary *) allocate_reloption(kinds,
+ RELOPT_TYPE_TERNARY, name, desc, lockmode);
+ newoption->default_val = default_val;
+
+ return newoption;
+}
+
+/*
+ * add_ternary_reloption
+ * Add a new ternary reloption
+ */
+void
+add_ternary_reloption(bits32 kinds, const char *name, const char *desc,
+ ternary default_val, LOCKMODE lockmode)
+{
+ relopt_ternary *newoption = init_ternary_reloption(kinds, name, desc,
+ default_val, lockmode);
+
+ add_reloption((relopt_gen *) newoption);
+}
+
+/*
+ * add_local_ternary_reloption
+ * Add a new ternary local reloption
+ *
+ * 'offset' is offset of ternary-typed field.
+ */
+void
+add_local_ternary_reloption(local_relopts *relopts, const char *name,
+ const char *desc, ternary default_val,
+ int offset)
+{
+ relopt_ternary *newoption = init_ternary_reloption(RELOPT_KIND_LOCAL,
+ name, desc,
+ default_val, 0);
+
+ add_local_reloption(relopts, (relopt_gen *) newoption, offset);
+}
/*
* init_real_reloption
@@ -1617,6 +1689,18 @@ parse_one_reloption(relopt_value *option, char *text_str, int text_len,
option->gen->name, value)));
}
break;
+ case RELOPT_TYPE_TERNARY:
+ {
+ bool b;
+ parsed = parse_bool(value, &b);
+ option->values.ternary_val = b ? TERNARY_TRUE : TERNARY_FALSE;
+ if (validate && !parsed)
+ ereport(ERROR,
+ (errcode(ERRCODE_INVALID_PARAMETER_VALUE),
+ errmsg("invalid value for ternary option \"%s\": %s",
+ option->gen->name, value)));
+ }
+ break;
case RELOPT_TYPE_INT:
{
relopt_int *optint = (relopt_int *) option->gen;
@@ -1780,17 +1864,6 @@ fillRelOptions(void *rdopts, Size basesize,
char *itempos = ((char *) rdopts) + elems[j].offset;
char *string_val;
- /*
- * If isset_offset is provided, store whether the reloption is
- * set there.
- */
- if (elems[j].isset_offset > 0)
- {
- char *setpos = ((char *) rdopts) + elems[j].isset_offset;
-
- *(bool *) setpos = options[i].isset;
- }
-
switch (options[i].gen->type)
{
case RELOPT_TYPE_BOOL:
@@ -1798,6 +1871,11 @@ fillRelOptions(void *rdopts, Size basesize,
options[i].values.bool_val :
((relopt_bool *) options[i].gen)->default_val;
break;
+ case RELOPT_TYPE_TERNARY:
+ *(ternary *) itempos = options[i].isset ?
+ options[i].values.ternary_val :
+ ((relopt_ternary *) options[i].gen)->default_val;
+ break;
case RELOPT_TYPE_INT:
*(int *) itempos = options[i].isset ?
options[i].values.int_val :
@@ -1912,8 +1990,8 @@ default_reloptions(Datum reloptions, bool validate, relopt_kind kind)
offsetof(StdRdOptions, parallel_workers)},
{"vacuum_index_cleanup", RELOPT_TYPE_ENUM,
offsetof(StdRdOptions, vacuum_index_cleanup)},
- {"vacuum_truncate", RELOPT_TYPE_BOOL,
- offsetof(StdRdOptions, vacuum_truncate), offsetof(StdRdOptions, vacuum_truncate_set)},
+ {"vacuum_truncate", RELOPT_TYPE_TERNARY,
+ offsetof(StdRdOptions, vacuum_truncate)},
{"vacuum_max_eager_freeze_failure_rate", RELOPT_TYPE_REAL,
offsetof(StdRdOptions, vacuum_max_eager_freeze_failure_rate)}
};
@@ -1993,7 +2071,6 @@ build_local_reloptions(local_relopts *relopts, Datum options, bool validate)
elems[i].optname = opt->option->name;
elems[i].opttype = opt->option->type;
elems[i].offset = opt->offset;
- elems[i].isset_offset = 0; /* not supported for local relopts yet */
i++;
}
diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c
index 733ef40ae7..977babff54 100644
--- a/src/backend/commands/vacuum.c
+++ b/src/backend/commands/vacuum.c
@@ -2221,9 +2221,9 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams params,
{
StdRdOptions *opts = (StdRdOptions *) rel->rd_options;
- if (opts && opts->vacuum_truncate_set)
+ if (opts && opts->vacuum_truncate != TERNARY_UNSET)
{
- if (opts->vacuum_truncate)
+ if (opts->vacuum_truncate == TERNARY_TRUE)
params.truncate = VACOPTVALUE_ENABLED;
else
params.truncate = VACOPTVALUE_DISABLED;
diff --git a/src/include/access/reloptions.h b/src/include/access/reloptions.h
index a604a4702c..a436697658 100644
--- a/src/include/access/reloptions.h
+++ b/src/include/access/reloptions.h
@@ -29,6 +29,7 @@
typedef enum relopt_type
{
RELOPT_TYPE_BOOL,
+ RELOPT_TYPE_TERNARY, /* on, off, unset */
RELOPT_TYPE_INT,
RELOPT_TYPE_REAL,
RELOPT_TYPE_ENUM,
@@ -80,6 +81,7 @@ typedef struct relopt_value
union
{
bool bool_val;
+ ternary ternary_val;
int int_val;
double real_val;
int enum_val;
@@ -94,6 +96,12 @@ typedef struct relopt_bool
bool default_val;
} relopt_bool;
+typedef struct relopt_rernary
+{
+ relopt_gen gen;
+ int default_val;
+} relopt_ternary;
+
typedef struct relopt_int
{
relopt_gen gen;
@@ -152,19 +160,6 @@ typedef struct
const char *optname; /* option's name */
relopt_type opttype; /* option's datatype */
int offset; /* offset of field in result struct */
-
- /*
- * isset_offset is an optional offset of a field in the result struct that
- * stores whether the option is explicitly set for the relation or if it
- * just picked up the default value. In most cases, this can be
- * accomplished by giving the reloption a special out-of-range default
- * value (e.g., some integer reloptions use -2), but this isn't always
- * possible. For example, a Boolean reloption cannot be given an
- * out-of-range default, so we need another way to discover the source of
- * its value. This offset is only used if given a value greater than
- * zero.
- */
- int isset_offset;
} relopt_parse_elt;
/* Local reloption definition */
@@ -195,6 +190,8 @@ typedef struct local_relopts
extern relopt_kind add_reloption_kind(void);
extern void add_bool_reloption(bits32 kinds, const char *name, const char *desc,
bool default_val, LOCKMODE lockmode);
+extern void add_ternary_reloption(bits32 kinds, const char *name,
+ const char *desc, int default_val, LOCKMODE lockmode);
extern void add_int_reloption(bits32 kinds, const char *name, const char *desc,
int default_val, int min_val, int max_val,
LOCKMODE lockmode);
@@ -214,6 +211,9 @@ extern void register_reloptions_validator(local_relopts *relopts,
extern void add_local_bool_reloption(local_relopts *relopts, const char *name,
const char *desc, bool default_val,
int offset);
+extern void add_local_ternary_reloption(local_relopts *relopts,
+ const char *name, const char *desc,
+ ternary default_val, int offset);
extern void add_local_int_reloption(local_relopts *relopts, const char *name,
const char *desc, int default_val,
int min_val, int max_val, int offset);
diff --git a/src/include/c.h b/src/include/c.h
index 39022f8a9d..9c59ccccf8 100644
--- a/src/include/c.h
+++ b/src/include/c.h
@@ -488,6 +488,22 @@ typedef void (*pg_funcptr_t) (void);
#include <stdbool.h>
+/*
+ * ternary
+ * Boolean value with an extrea "unset" option
+ *
+ * Ternary data type is used in relation options that can be "true", "false" or
+ * "unset". Since relation options are used deep inside the PostgreSQL code,
+ * this type is declared globally.
+*/
+
+typedef enum ternary
+{
+ TERNARY_FALSE = 0,
+ TERNARY_TRUE = 1,
+ TERNARY_UNSET = -1
+} ternary;
+
/* ----------------------------------------------------------------
* Section 3: standard system types
diff --git a/src/include/utils/rel.h b/src/include/utils/rel.h
index b552359915..7346d618f9 100644
--- a/src/include/utils/rel.h
+++ b/src/include/utils/rel.h
@@ -346,8 +346,7 @@ typedef struct StdRdOptions
bool user_catalog_table; /* use as an additional catalog relation */
int parallel_workers; /* max number of parallel workers */
StdRdOptIndexCleanup vacuum_index_cleanup; /* controls index vacuuming */
- bool vacuum_truncate; /* enables vacuum to truncate a relation */
- bool vacuum_truncate_set; /* whether vacuum_truncate is set */
+ ternary vacuum_truncate; /* enables vacuum to truncate a relation */
/*
* Fraction of pages in a relation that vacuum can eagerly scan and fail
--
2.39.2
--nextPart4155600.3ZeAukHxDK
Content-Disposition: attachment;
filename="v2a-0003-Add-alias-to-be-used-as-unset-state.patch"
Content-Transfer-Encoding: 7Bit
Content-Type: text/x-patch; charset="unicode-2-0-utf-8";
name="v2a-0003-Add-alias-to-be-used-as-unset-state.patch"
^ permalink raw reply [nested|flat] 868+ messages in thread
* [PATCH v2 2/4] Introduce ternary reloptions
@ 2025-09-04 14:41 Nikolay Shaplov <[email protected]>
0 siblings, 0 replies; 868+ messages in thread
From: Nikolay Shaplov @ 2025-09-04 14:41 UTC (permalink / raw)
Introduce ternary reloption as a replacement for current `vacuum_truncate`
implementation. Remove `vacuum_truncate_set` additional flag and using
`TERNARY_UNSET` value instead.
---
src/backend/access/common/reloptions.c | 129 ++++++++++++++++++++-----
src/backend/commands/vacuum.c | 4 +-
src/include/access/reloptions.h | 26 ++---
src/include/c.h | 16 +++
src/include/utils/rel.h | 3 +-
5 files changed, 135 insertions(+), 43 deletions(-)
diff --git a/src/backend/access/common/reloptions.c b/src/backend/access/common/reloptions.c
index 0af3fea68f..f543f11001 100644
--- a/src/backend/access/common/reloptions.c
+++ b/src/backend/access/common/reloptions.c
@@ -40,9 +40,9 @@
*
* To add an option:
*
- * (i) decide on a type (bool, integer, real, enum, string), name, default
- * value, upper and lower bounds (if applicable); for strings, consider a
- * validation routine.
+ * (i) decide on a type (bool, ternary, integer, real, enum, string), name,
+ * default value, upper and lower bounds (if applicable); for strings,
+ * consider a validation routine.
* (ii) add a record below (or use add_<type>_reloption).
* (iii) add it to the appropriate options struct (perhaps StdRdOptions)
* (iv) add it to the appropriate handling routine (perhaps
@@ -147,15 +147,6 @@ static relopt_bool boolRelOpts[] =
},
false
},
- {
- {
- "vacuum_truncate",
- "Enables vacuum to truncate empty pages at the end of this table",
- RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
- ShareUpdateExclusiveLock
- },
- true
- },
{
{
"deduplicate_items",
@@ -170,6 +161,21 @@ static relopt_bool boolRelOpts[] =
{{NULL}}
};
+static relopt_ternary ternaryRelOpts[] =
+{
+ {
+ {
+ "vacuum_truncate",
+ "Enables vacuum to truncate empty pages at the end of this table",
+ RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
+ ShareUpdateExclusiveLock
+ },
+ TERNARY_UNSET
+ },
+ /* list terminator */
+ {{NULL}}
+};
+
static relopt_int intRelOpts[] =
{
{
@@ -600,6 +606,13 @@ initialize_reloptions(void)
boolRelOpts[i].gen.lockmode));
j++;
}
+ for (i = 0; ternaryRelOpts[i].gen.name; i++)
+ {
+ Assert(DoLockModesConflict(ternaryRelOpts[i].gen.lockmode,
+ ternaryRelOpts[i].gen.lockmode));
+ j++;
+ }
+
for (i = 0; intRelOpts[i].gen.name; i++)
{
Assert(DoLockModesConflict(intRelOpts[i].gen.lockmode,
@@ -640,6 +653,14 @@ initialize_reloptions(void)
j++;
}
+ for (i = 0; ternaryRelOpts[i].gen.name; i++)
+ {
+ relOpts[j] = &ternaryRelOpts[i].gen;
+ relOpts[j]->type = RELOPT_TYPE_TERNARY;
+ relOpts[j]->namelen = strlen(relOpts[j]->name);
+ j++;
+ }
+
for (i = 0; intRelOpts[i].gen.name; i++)
{
relOpts[j] = &intRelOpts[i].gen;
@@ -800,6 +821,9 @@ allocate_reloption(bits32 kinds, int type, const char *name, const char *desc,
case RELOPT_TYPE_BOOL:
size = sizeof(relopt_bool);
break;
+ case RELOPT_TYPE_TERNARY:
+ size = sizeof(relopt_ternary);
+ break;
case RELOPT_TYPE_INT:
size = sizeof(relopt_int);
break;
@@ -883,6 +907,54 @@ add_local_bool_reloption(local_relopts *relopts, const char *name,
add_local_reloption(relopts, (relopt_gen *) newoption, offset);
}
+/*
+ * init_ternary_reloption
+ * Allocate and initialize a new ternary reloption
+ */
+static relopt_ternary *
+init_ternary_reloption(bits32 kinds, const char *name, const char *desc,
+ ternary default_val, LOCKMODE lockmode)
+{
+ relopt_ternary *newoption;
+
+ newoption = (relopt_ternary *) allocate_reloption(kinds,
+ RELOPT_TYPE_TERNARY, name, desc, lockmode);
+ newoption->default_val = default_val;
+
+ return newoption;
+}
+
+/*
+ * add_ternary_reloption
+ * Add a new ternary reloption
+ */
+void
+add_ternary_reloption(bits32 kinds, const char *name, const char *desc,
+ ternary default_val, LOCKMODE lockmode)
+{
+ relopt_ternary *newoption = init_ternary_reloption(kinds, name, desc,
+ default_val, lockmode);
+
+ add_reloption((relopt_gen *) newoption);
+}
+
+/*
+ * add_local_ternary_reloption
+ * Add a new ternary local reloption
+ *
+ * 'offset' is offset of ternary-typed field.
+ */
+void
+add_local_ternary_reloption(local_relopts *relopts, const char *name,
+ const char *desc, ternary default_val,
+ int offset)
+{
+ relopt_ternary *newoption = init_ternary_reloption(RELOPT_KIND_LOCAL,
+ name, desc,
+ default_val, 0);
+
+ add_local_reloption(relopts, (relopt_gen *) newoption, offset);
+}
/*
* init_real_reloption
@@ -1617,6 +1689,18 @@ parse_one_reloption(relopt_value *option, char *text_str, int text_len,
option->gen->name, value)));
}
break;
+ case RELOPT_TYPE_TERNARY:
+ {
+ bool b;
+ parsed = parse_bool(value, &b);
+ option->values.ternary_val = b ? TERNARY_TRUE : TERNARY_FALSE;
+ if (validate && !parsed)
+ ereport(ERROR,
+ (errcode(ERRCODE_INVALID_PARAMETER_VALUE),
+ errmsg("invalid value for ternary option \"%s\": %s",
+ option->gen->name, value)));
+ }
+ break;
case RELOPT_TYPE_INT:
{
relopt_int *optint = (relopt_int *) option->gen;
@@ -1780,17 +1864,6 @@ fillRelOptions(void *rdopts, Size basesize,
char *itempos = ((char *) rdopts) + elems[j].offset;
char *string_val;
- /*
- * If isset_offset is provided, store whether the reloption is
- * set there.
- */
- if (elems[j].isset_offset > 0)
- {
- char *setpos = ((char *) rdopts) + elems[j].isset_offset;
-
- *(bool *) setpos = options[i].isset;
- }
-
switch (options[i].gen->type)
{
case RELOPT_TYPE_BOOL:
@@ -1798,6 +1871,11 @@ fillRelOptions(void *rdopts, Size basesize,
options[i].values.bool_val :
((relopt_bool *) options[i].gen)->default_val;
break;
+ case RELOPT_TYPE_TERNARY:
+ *(ternary *) itempos = options[i].isset ?
+ options[i].values.ternary_val :
+ ((relopt_ternary *) options[i].gen)->default_val;
+ break;
case RELOPT_TYPE_INT:
*(int *) itempos = options[i].isset ?
options[i].values.int_val :
@@ -1912,8 +1990,8 @@ default_reloptions(Datum reloptions, bool validate, relopt_kind kind)
offsetof(StdRdOptions, parallel_workers)},
{"vacuum_index_cleanup", RELOPT_TYPE_ENUM,
offsetof(StdRdOptions, vacuum_index_cleanup)},
- {"vacuum_truncate", RELOPT_TYPE_BOOL,
- offsetof(StdRdOptions, vacuum_truncate), offsetof(StdRdOptions, vacuum_truncate_set)},
+ {"vacuum_truncate", RELOPT_TYPE_TERNARY,
+ offsetof(StdRdOptions, vacuum_truncate)},
{"vacuum_max_eager_freeze_failure_rate", RELOPT_TYPE_REAL,
offsetof(StdRdOptions, vacuum_max_eager_freeze_failure_rate)}
};
@@ -1993,7 +2071,6 @@ build_local_reloptions(local_relopts *relopts, Datum options, bool validate)
elems[i].optname = opt->option->name;
elems[i].opttype = opt->option->type;
elems[i].offset = opt->offset;
- elems[i].isset_offset = 0; /* not supported for local relopts yet */
i++;
}
diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c
index 733ef40ae7..977babff54 100644
--- a/src/backend/commands/vacuum.c
+++ b/src/backend/commands/vacuum.c
@@ -2221,9 +2221,9 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams params,
{
StdRdOptions *opts = (StdRdOptions *) rel->rd_options;
- if (opts && opts->vacuum_truncate_set)
+ if (opts && opts->vacuum_truncate != TERNARY_UNSET)
{
- if (opts->vacuum_truncate)
+ if (opts->vacuum_truncate == TERNARY_TRUE)
params.truncate = VACOPTVALUE_ENABLED;
else
params.truncate = VACOPTVALUE_DISABLED;
diff --git a/src/include/access/reloptions.h b/src/include/access/reloptions.h
index a604a4702c..a436697658 100644
--- a/src/include/access/reloptions.h
+++ b/src/include/access/reloptions.h
@@ -29,6 +29,7 @@
typedef enum relopt_type
{
RELOPT_TYPE_BOOL,
+ RELOPT_TYPE_TERNARY, /* on, off, unset */
RELOPT_TYPE_INT,
RELOPT_TYPE_REAL,
RELOPT_TYPE_ENUM,
@@ -80,6 +81,7 @@ typedef struct relopt_value
union
{
bool bool_val;
+ ternary ternary_val;
int int_val;
double real_val;
int enum_val;
@@ -94,6 +96,12 @@ typedef struct relopt_bool
bool default_val;
} relopt_bool;
+typedef struct relopt_rernary
+{
+ relopt_gen gen;
+ int default_val;
+} relopt_ternary;
+
typedef struct relopt_int
{
relopt_gen gen;
@@ -152,19 +160,6 @@ typedef struct
const char *optname; /* option's name */
relopt_type opttype; /* option's datatype */
int offset; /* offset of field in result struct */
-
- /*
- * isset_offset is an optional offset of a field in the result struct that
- * stores whether the option is explicitly set for the relation or if it
- * just picked up the default value. In most cases, this can be
- * accomplished by giving the reloption a special out-of-range default
- * value (e.g., some integer reloptions use -2), but this isn't always
- * possible. For example, a Boolean reloption cannot be given an
- * out-of-range default, so we need another way to discover the source of
- * its value. This offset is only used if given a value greater than
- * zero.
- */
- int isset_offset;
} relopt_parse_elt;
/* Local reloption definition */
@@ -195,6 +190,8 @@ typedef struct local_relopts
extern relopt_kind add_reloption_kind(void);
extern void add_bool_reloption(bits32 kinds, const char *name, const char *desc,
bool default_val, LOCKMODE lockmode);
+extern void add_ternary_reloption(bits32 kinds, const char *name,
+ const char *desc, int default_val, LOCKMODE lockmode);
extern void add_int_reloption(bits32 kinds, const char *name, const char *desc,
int default_val, int min_val, int max_val,
LOCKMODE lockmode);
@@ -214,6 +211,9 @@ extern void register_reloptions_validator(local_relopts *relopts,
extern void add_local_bool_reloption(local_relopts *relopts, const char *name,
const char *desc, bool default_val,
int offset);
+extern void add_local_ternary_reloption(local_relopts *relopts,
+ const char *name, const char *desc,
+ ternary default_val, int offset);
extern void add_local_int_reloption(local_relopts *relopts, const char *name,
const char *desc, int default_val,
int min_val, int max_val, int offset);
diff --git a/src/include/c.h b/src/include/c.h
index 39022f8a9d..9c59ccccf8 100644
--- a/src/include/c.h
+++ b/src/include/c.h
@@ -488,6 +488,22 @@ typedef void (*pg_funcptr_t) (void);
#include <stdbool.h>
+/*
+ * ternary
+ * Boolean value with an extrea "unset" option
+ *
+ * Ternary data type is used in relation options that can be "true", "false" or
+ * "unset". Since relation options are used deep inside the PostgreSQL code,
+ * this type is declared globally.
+*/
+
+typedef enum ternary
+{
+ TERNARY_FALSE = 0,
+ TERNARY_TRUE = 1,
+ TERNARY_UNSET = -1
+} ternary;
+
/* ----------------------------------------------------------------
* Section 3: standard system types
diff --git a/src/include/utils/rel.h b/src/include/utils/rel.h
index b552359915..7346d618f9 100644
--- a/src/include/utils/rel.h
+++ b/src/include/utils/rel.h
@@ -346,8 +346,7 @@ typedef struct StdRdOptions
bool user_catalog_table; /* use as an additional catalog relation */
int parallel_workers; /* max number of parallel workers */
StdRdOptIndexCleanup vacuum_index_cleanup; /* controls index vacuuming */
- bool vacuum_truncate; /* enables vacuum to truncate a relation */
- bool vacuum_truncate_set; /* whether vacuum_truncate is set */
+ ternary vacuum_truncate; /* enables vacuum to truncate a relation */
/*
* Fraction of pages in a relation that vacuum can eagerly scan and fail
--
2.39.2
--nextPart5129932.5fSG56mABF
Content-Disposition: attachment;
filename="v2-0003-Add-alias-to-be-used-as-unset-state.patch"
Content-Transfer-Encoding: 7Bit
Content-Type: text/x-patch; charset="unicode-2-0-utf-8";
name="v2-0003-Add-alias-to-be-used-as-unset-state.patch"
^ permalink raw reply [nested|flat] 868+ messages in thread
* [PATCH v2 2/4] Introduce ternary reloptions
@ 2025-09-04 14:41 Nikolay Shaplov <[email protected]>
0 siblings, 0 replies; 868+ messages in thread
From: Nikolay Shaplov @ 2025-09-04 14:41 UTC (permalink / raw)
Introduce ternary reloption as a replacement for current `vacuum_truncate`
implementation. Remove `vacuum_truncate_set` additional flag and using
`TERNARY_UNSET` value instead.
---
src/backend/access/common/reloptions.c | 129 ++++++++++++++++++++-----
src/backend/commands/vacuum.c | 4 +-
src/include/access/reloptions.h | 26 ++---
src/include/c.h | 16 +++
src/include/utils/rel.h | 3 +-
5 files changed, 135 insertions(+), 43 deletions(-)
diff --git a/src/backend/access/common/reloptions.c b/src/backend/access/common/reloptions.c
index 0af3fea68f..f543f11001 100644
--- a/src/backend/access/common/reloptions.c
+++ b/src/backend/access/common/reloptions.c
@@ -40,9 +40,9 @@
*
* To add an option:
*
- * (i) decide on a type (bool, integer, real, enum, string), name, default
- * value, upper and lower bounds (if applicable); for strings, consider a
- * validation routine.
+ * (i) decide on a type (bool, ternary, integer, real, enum, string), name,
+ * default value, upper and lower bounds (if applicable); for strings,
+ * consider a validation routine.
* (ii) add a record below (or use add_<type>_reloption).
* (iii) add it to the appropriate options struct (perhaps StdRdOptions)
* (iv) add it to the appropriate handling routine (perhaps
@@ -147,15 +147,6 @@ static relopt_bool boolRelOpts[] =
},
false
},
- {
- {
- "vacuum_truncate",
- "Enables vacuum to truncate empty pages at the end of this table",
- RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
- ShareUpdateExclusiveLock
- },
- true
- },
{
{
"deduplicate_items",
@@ -170,6 +161,21 @@ static relopt_bool boolRelOpts[] =
{{NULL}}
};
+static relopt_ternary ternaryRelOpts[] =
+{
+ {
+ {
+ "vacuum_truncate",
+ "Enables vacuum to truncate empty pages at the end of this table",
+ RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
+ ShareUpdateExclusiveLock
+ },
+ TERNARY_UNSET
+ },
+ /* list terminator */
+ {{NULL}}
+};
+
static relopt_int intRelOpts[] =
{
{
@@ -600,6 +606,13 @@ initialize_reloptions(void)
boolRelOpts[i].gen.lockmode));
j++;
}
+ for (i = 0; ternaryRelOpts[i].gen.name; i++)
+ {
+ Assert(DoLockModesConflict(ternaryRelOpts[i].gen.lockmode,
+ ternaryRelOpts[i].gen.lockmode));
+ j++;
+ }
+
for (i = 0; intRelOpts[i].gen.name; i++)
{
Assert(DoLockModesConflict(intRelOpts[i].gen.lockmode,
@@ -640,6 +653,14 @@ initialize_reloptions(void)
j++;
}
+ for (i = 0; ternaryRelOpts[i].gen.name; i++)
+ {
+ relOpts[j] = &ternaryRelOpts[i].gen;
+ relOpts[j]->type = RELOPT_TYPE_TERNARY;
+ relOpts[j]->namelen = strlen(relOpts[j]->name);
+ j++;
+ }
+
for (i = 0; intRelOpts[i].gen.name; i++)
{
relOpts[j] = &intRelOpts[i].gen;
@@ -800,6 +821,9 @@ allocate_reloption(bits32 kinds, int type, const char *name, const char *desc,
case RELOPT_TYPE_BOOL:
size = sizeof(relopt_bool);
break;
+ case RELOPT_TYPE_TERNARY:
+ size = sizeof(relopt_ternary);
+ break;
case RELOPT_TYPE_INT:
size = sizeof(relopt_int);
break;
@@ -883,6 +907,54 @@ add_local_bool_reloption(local_relopts *relopts, const char *name,
add_local_reloption(relopts, (relopt_gen *) newoption, offset);
}
+/*
+ * init_ternary_reloption
+ * Allocate and initialize a new ternary reloption
+ */
+static relopt_ternary *
+init_ternary_reloption(bits32 kinds, const char *name, const char *desc,
+ ternary default_val, LOCKMODE lockmode)
+{
+ relopt_ternary *newoption;
+
+ newoption = (relopt_ternary *) allocate_reloption(kinds,
+ RELOPT_TYPE_TERNARY, name, desc, lockmode);
+ newoption->default_val = default_val;
+
+ return newoption;
+}
+
+/*
+ * add_ternary_reloption
+ * Add a new ternary reloption
+ */
+void
+add_ternary_reloption(bits32 kinds, const char *name, const char *desc,
+ ternary default_val, LOCKMODE lockmode)
+{
+ relopt_ternary *newoption = init_ternary_reloption(kinds, name, desc,
+ default_val, lockmode);
+
+ add_reloption((relopt_gen *) newoption);
+}
+
+/*
+ * add_local_ternary_reloption
+ * Add a new ternary local reloption
+ *
+ * 'offset' is offset of ternary-typed field.
+ */
+void
+add_local_ternary_reloption(local_relopts *relopts, const char *name,
+ const char *desc, ternary default_val,
+ int offset)
+{
+ relopt_ternary *newoption = init_ternary_reloption(RELOPT_KIND_LOCAL,
+ name, desc,
+ default_val, 0);
+
+ add_local_reloption(relopts, (relopt_gen *) newoption, offset);
+}
/*
* init_real_reloption
@@ -1617,6 +1689,18 @@ parse_one_reloption(relopt_value *option, char *text_str, int text_len,
option->gen->name, value)));
}
break;
+ case RELOPT_TYPE_TERNARY:
+ {
+ bool b;
+ parsed = parse_bool(value, &b);
+ option->values.ternary_val = b ? TERNARY_TRUE : TERNARY_FALSE;
+ if (validate && !parsed)
+ ereport(ERROR,
+ (errcode(ERRCODE_INVALID_PARAMETER_VALUE),
+ errmsg("invalid value for ternary option \"%s\": %s",
+ option->gen->name, value)));
+ }
+ break;
case RELOPT_TYPE_INT:
{
relopt_int *optint = (relopt_int *) option->gen;
@@ -1780,17 +1864,6 @@ fillRelOptions(void *rdopts, Size basesize,
char *itempos = ((char *) rdopts) + elems[j].offset;
char *string_val;
- /*
- * If isset_offset is provided, store whether the reloption is
- * set there.
- */
- if (elems[j].isset_offset > 0)
- {
- char *setpos = ((char *) rdopts) + elems[j].isset_offset;
-
- *(bool *) setpos = options[i].isset;
- }
-
switch (options[i].gen->type)
{
case RELOPT_TYPE_BOOL:
@@ -1798,6 +1871,11 @@ fillRelOptions(void *rdopts, Size basesize,
options[i].values.bool_val :
((relopt_bool *) options[i].gen)->default_val;
break;
+ case RELOPT_TYPE_TERNARY:
+ *(ternary *) itempos = options[i].isset ?
+ options[i].values.ternary_val :
+ ((relopt_ternary *) options[i].gen)->default_val;
+ break;
case RELOPT_TYPE_INT:
*(int *) itempos = options[i].isset ?
options[i].values.int_val :
@@ -1912,8 +1990,8 @@ default_reloptions(Datum reloptions, bool validate, relopt_kind kind)
offsetof(StdRdOptions, parallel_workers)},
{"vacuum_index_cleanup", RELOPT_TYPE_ENUM,
offsetof(StdRdOptions, vacuum_index_cleanup)},
- {"vacuum_truncate", RELOPT_TYPE_BOOL,
- offsetof(StdRdOptions, vacuum_truncate), offsetof(StdRdOptions, vacuum_truncate_set)},
+ {"vacuum_truncate", RELOPT_TYPE_TERNARY,
+ offsetof(StdRdOptions, vacuum_truncate)},
{"vacuum_max_eager_freeze_failure_rate", RELOPT_TYPE_REAL,
offsetof(StdRdOptions, vacuum_max_eager_freeze_failure_rate)}
};
@@ -1993,7 +2071,6 @@ build_local_reloptions(local_relopts *relopts, Datum options, bool validate)
elems[i].optname = opt->option->name;
elems[i].opttype = opt->option->type;
elems[i].offset = opt->offset;
- elems[i].isset_offset = 0; /* not supported for local relopts yet */
i++;
}
diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c
index 733ef40ae7..977babff54 100644
--- a/src/backend/commands/vacuum.c
+++ b/src/backend/commands/vacuum.c
@@ -2221,9 +2221,9 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams params,
{
StdRdOptions *opts = (StdRdOptions *) rel->rd_options;
- if (opts && opts->vacuum_truncate_set)
+ if (opts && opts->vacuum_truncate != TERNARY_UNSET)
{
- if (opts->vacuum_truncate)
+ if (opts->vacuum_truncate == TERNARY_TRUE)
params.truncate = VACOPTVALUE_ENABLED;
else
params.truncate = VACOPTVALUE_DISABLED;
diff --git a/src/include/access/reloptions.h b/src/include/access/reloptions.h
index a604a4702c..a436697658 100644
--- a/src/include/access/reloptions.h
+++ b/src/include/access/reloptions.h
@@ -29,6 +29,7 @@
typedef enum relopt_type
{
RELOPT_TYPE_BOOL,
+ RELOPT_TYPE_TERNARY, /* on, off, unset */
RELOPT_TYPE_INT,
RELOPT_TYPE_REAL,
RELOPT_TYPE_ENUM,
@@ -80,6 +81,7 @@ typedef struct relopt_value
union
{
bool bool_val;
+ ternary ternary_val;
int int_val;
double real_val;
int enum_val;
@@ -94,6 +96,12 @@ typedef struct relopt_bool
bool default_val;
} relopt_bool;
+typedef struct relopt_rernary
+{
+ relopt_gen gen;
+ int default_val;
+} relopt_ternary;
+
typedef struct relopt_int
{
relopt_gen gen;
@@ -152,19 +160,6 @@ typedef struct
const char *optname; /* option's name */
relopt_type opttype; /* option's datatype */
int offset; /* offset of field in result struct */
-
- /*
- * isset_offset is an optional offset of a field in the result struct that
- * stores whether the option is explicitly set for the relation or if it
- * just picked up the default value. In most cases, this can be
- * accomplished by giving the reloption a special out-of-range default
- * value (e.g., some integer reloptions use -2), but this isn't always
- * possible. For example, a Boolean reloption cannot be given an
- * out-of-range default, so we need another way to discover the source of
- * its value. This offset is only used if given a value greater than
- * zero.
- */
- int isset_offset;
} relopt_parse_elt;
/* Local reloption definition */
@@ -195,6 +190,8 @@ typedef struct local_relopts
extern relopt_kind add_reloption_kind(void);
extern void add_bool_reloption(bits32 kinds, const char *name, const char *desc,
bool default_val, LOCKMODE lockmode);
+extern void add_ternary_reloption(bits32 kinds, const char *name,
+ const char *desc, int default_val, LOCKMODE lockmode);
extern void add_int_reloption(bits32 kinds, const char *name, const char *desc,
int default_val, int min_val, int max_val,
LOCKMODE lockmode);
@@ -214,6 +211,9 @@ extern void register_reloptions_validator(local_relopts *relopts,
extern void add_local_bool_reloption(local_relopts *relopts, const char *name,
const char *desc, bool default_val,
int offset);
+extern void add_local_ternary_reloption(local_relopts *relopts,
+ const char *name, const char *desc,
+ ternary default_val, int offset);
extern void add_local_int_reloption(local_relopts *relopts, const char *name,
const char *desc, int default_val,
int min_val, int max_val, int offset);
diff --git a/src/include/c.h b/src/include/c.h
index 39022f8a9d..9c59ccccf8 100644
--- a/src/include/c.h
+++ b/src/include/c.h
@@ -488,6 +488,22 @@ typedef void (*pg_funcptr_t) (void);
#include <stdbool.h>
+/*
+ * ternary
+ * Boolean value with an extrea "unset" option
+ *
+ * Ternary data type is used in relation options that can be "true", "false" or
+ * "unset". Since relation options are used deep inside the PostgreSQL code,
+ * this type is declared globally.
+*/
+
+typedef enum ternary
+{
+ TERNARY_FALSE = 0,
+ TERNARY_TRUE = 1,
+ TERNARY_UNSET = -1
+} ternary;
+
/* ----------------------------------------------------------------
* Section 3: standard system types
diff --git a/src/include/utils/rel.h b/src/include/utils/rel.h
index b552359915..7346d618f9 100644
--- a/src/include/utils/rel.h
+++ b/src/include/utils/rel.h
@@ -346,8 +346,7 @@ typedef struct StdRdOptions
bool user_catalog_table; /* use as an additional catalog relation */
int parallel_workers; /* max number of parallel workers */
StdRdOptIndexCleanup vacuum_index_cleanup; /* controls index vacuuming */
- bool vacuum_truncate; /* enables vacuum to truncate a relation */
- bool vacuum_truncate_set; /* whether vacuum_truncate is set */
+ ternary vacuum_truncate; /* enables vacuum to truncate a relation */
/*
* Fraction of pages in a relation that vacuum can eagerly scan and fail
--
2.39.2
--nextPart5129932.5fSG56mABF
Content-Disposition: attachment;
filename="v2-0003-Add-alias-to-be-used-as-unset-state.patch"
Content-Transfer-Encoding: 7Bit
Content-Type: text/x-patch; charset="unicode-2-0-utf-8";
name="v2-0003-Add-alias-to-be-used-as-unset-state.patch"
^ permalink raw reply [nested|flat] 868+ messages in thread
* [PATCH v2a 2/4] Introduce ternary reloptions
@ 2025-09-04 14:41 Nikolay Shaplov <[email protected]>
0 siblings, 0 replies; 868+ messages in thread
From: Nikolay Shaplov @ 2025-09-04 14:41 UTC (permalink / raw)
Introduce ternary reloption as a replacement for current `vacuum_truncate`
implementation. Remove `vacuum_truncate_set` additional flag and using
`TERNARY_UNSET` value instead.
---
src/backend/access/common/reloptions.c | 129 ++++++++++++++++++++-----
src/backend/commands/vacuum.c | 4 +-
src/include/access/reloptions.h | 26 ++---
src/include/c.h | 16 +++
src/include/utils/rel.h | 3 +-
5 files changed, 135 insertions(+), 43 deletions(-)
diff --git a/src/backend/access/common/reloptions.c b/src/backend/access/common/reloptions.c
index 0af3fea68f..f543f11001 100644
--- a/src/backend/access/common/reloptions.c
+++ b/src/backend/access/common/reloptions.c
@@ -40,9 +40,9 @@
*
* To add an option:
*
- * (i) decide on a type (bool, integer, real, enum, string), name, default
- * value, upper and lower bounds (if applicable); for strings, consider a
- * validation routine.
+ * (i) decide on a type (bool, ternary, integer, real, enum, string), name,
+ * default value, upper and lower bounds (if applicable); for strings,
+ * consider a validation routine.
* (ii) add a record below (or use add_<type>_reloption).
* (iii) add it to the appropriate options struct (perhaps StdRdOptions)
* (iv) add it to the appropriate handling routine (perhaps
@@ -147,15 +147,6 @@ static relopt_bool boolRelOpts[] =
},
false
},
- {
- {
- "vacuum_truncate",
- "Enables vacuum to truncate empty pages at the end of this table",
- RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
- ShareUpdateExclusiveLock
- },
- true
- },
{
{
"deduplicate_items",
@@ -170,6 +161,21 @@ static relopt_bool boolRelOpts[] =
{{NULL}}
};
+static relopt_ternary ternaryRelOpts[] =
+{
+ {
+ {
+ "vacuum_truncate",
+ "Enables vacuum to truncate empty pages at the end of this table",
+ RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
+ ShareUpdateExclusiveLock
+ },
+ TERNARY_UNSET
+ },
+ /* list terminator */
+ {{NULL}}
+};
+
static relopt_int intRelOpts[] =
{
{
@@ -600,6 +606,13 @@ initialize_reloptions(void)
boolRelOpts[i].gen.lockmode));
j++;
}
+ for (i = 0; ternaryRelOpts[i].gen.name; i++)
+ {
+ Assert(DoLockModesConflict(ternaryRelOpts[i].gen.lockmode,
+ ternaryRelOpts[i].gen.lockmode));
+ j++;
+ }
+
for (i = 0; intRelOpts[i].gen.name; i++)
{
Assert(DoLockModesConflict(intRelOpts[i].gen.lockmode,
@@ -640,6 +653,14 @@ initialize_reloptions(void)
j++;
}
+ for (i = 0; ternaryRelOpts[i].gen.name; i++)
+ {
+ relOpts[j] = &ternaryRelOpts[i].gen;
+ relOpts[j]->type = RELOPT_TYPE_TERNARY;
+ relOpts[j]->namelen = strlen(relOpts[j]->name);
+ j++;
+ }
+
for (i = 0; intRelOpts[i].gen.name; i++)
{
relOpts[j] = &intRelOpts[i].gen;
@@ -800,6 +821,9 @@ allocate_reloption(bits32 kinds, int type, const char *name, const char *desc,
case RELOPT_TYPE_BOOL:
size = sizeof(relopt_bool);
break;
+ case RELOPT_TYPE_TERNARY:
+ size = sizeof(relopt_ternary);
+ break;
case RELOPT_TYPE_INT:
size = sizeof(relopt_int);
break;
@@ -883,6 +907,54 @@ add_local_bool_reloption(local_relopts *relopts, const char *name,
add_local_reloption(relopts, (relopt_gen *) newoption, offset);
}
+/*
+ * init_ternary_reloption
+ * Allocate and initialize a new ternary reloption
+ */
+static relopt_ternary *
+init_ternary_reloption(bits32 kinds, const char *name, const char *desc,
+ ternary default_val, LOCKMODE lockmode)
+{
+ relopt_ternary *newoption;
+
+ newoption = (relopt_ternary *) allocate_reloption(kinds,
+ RELOPT_TYPE_TERNARY, name, desc, lockmode);
+ newoption->default_val = default_val;
+
+ return newoption;
+}
+
+/*
+ * add_ternary_reloption
+ * Add a new ternary reloption
+ */
+void
+add_ternary_reloption(bits32 kinds, const char *name, const char *desc,
+ ternary default_val, LOCKMODE lockmode)
+{
+ relopt_ternary *newoption = init_ternary_reloption(kinds, name, desc,
+ default_val, lockmode);
+
+ add_reloption((relopt_gen *) newoption);
+}
+
+/*
+ * add_local_ternary_reloption
+ * Add a new ternary local reloption
+ *
+ * 'offset' is offset of ternary-typed field.
+ */
+void
+add_local_ternary_reloption(local_relopts *relopts, const char *name,
+ const char *desc, ternary default_val,
+ int offset)
+{
+ relopt_ternary *newoption = init_ternary_reloption(RELOPT_KIND_LOCAL,
+ name, desc,
+ default_val, 0);
+
+ add_local_reloption(relopts, (relopt_gen *) newoption, offset);
+}
/*
* init_real_reloption
@@ -1617,6 +1689,18 @@ parse_one_reloption(relopt_value *option, char *text_str, int text_len,
option->gen->name, value)));
}
break;
+ case RELOPT_TYPE_TERNARY:
+ {
+ bool b;
+ parsed = parse_bool(value, &b);
+ option->values.ternary_val = b ? TERNARY_TRUE : TERNARY_FALSE;
+ if (validate && !parsed)
+ ereport(ERROR,
+ (errcode(ERRCODE_INVALID_PARAMETER_VALUE),
+ errmsg("invalid value for ternary option \"%s\": %s",
+ option->gen->name, value)));
+ }
+ break;
case RELOPT_TYPE_INT:
{
relopt_int *optint = (relopt_int *) option->gen;
@@ -1780,17 +1864,6 @@ fillRelOptions(void *rdopts, Size basesize,
char *itempos = ((char *) rdopts) + elems[j].offset;
char *string_val;
- /*
- * If isset_offset is provided, store whether the reloption is
- * set there.
- */
- if (elems[j].isset_offset > 0)
- {
- char *setpos = ((char *) rdopts) + elems[j].isset_offset;
-
- *(bool *) setpos = options[i].isset;
- }
-
switch (options[i].gen->type)
{
case RELOPT_TYPE_BOOL:
@@ -1798,6 +1871,11 @@ fillRelOptions(void *rdopts, Size basesize,
options[i].values.bool_val :
((relopt_bool *) options[i].gen)->default_val;
break;
+ case RELOPT_TYPE_TERNARY:
+ *(ternary *) itempos = options[i].isset ?
+ options[i].values.ternary_val :
+ ((relopt_ternary *) options[i].gen)->default_val;
+ break;
case RELOPT_TYPE_INT:
*(int *) itempos = options[i].isset ?
options[i].values.int_val :
@@ -1912,8 +1990,8 @@ default_reloptions(Datum reloptions, bool validate, relopt_kind kind)
offsetof(StdRdOptions, parallel_workers)},
{"vacuum_index_cleanup", RELOPT_TYPE_ENUM,
offsetof(StdRdOptions, vacuum_index_cleanup)},
- {"vacuum_truncate", RELOPT_TYPE_BOOL,
- offsetof(StdRdOptions, vacuum_truncate), offsetof(StdRdOptions, vacuum_truncate_set)},
+ {"vacuum_truncate", RELOPT_TYPE_TERNARY,
+ offsetof(StdRdOptions, vacuum_truncate)},
{"vacuum_max_eager_freeze_failure_rate", RELOPT_TYPE_REAL,
offsetof(StdRdOptions, vacuum_max_eager_freeze_failure_rate)}
};
@@ -1993,7 +2071,6 @@ build_local_reloptions(local_relopts *relopts, Datum options, bool validate)
elems[i].optname = opt->option->name;
elems[i].opttype = opt->option->type;
elems[i].offset = opt->offset;
- elems[i].isset_offset = 0; /* not supported for local relopts yet */
i++;
}
diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c
index 733ef40ae7..977babff54 100644
--- a/src/backend/commands/vacuum.c
+++ b/src/backend/commands/vacuum.c
@@ -2221,9 +2221,9 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams params,
{
StdRdOptions *opts = (StdRdOptions *) rel->rd_options;
- if (opts && opts->vacuum_truncate_set)
+ if (opts && opts->vacuum_truncate != TERNARY_UNSET)
{
- if (opts->vacuum_truncate)
+ if (opts->vacuum_truncate == TERNARY_TRUE)
params.truncate = VACOPTVALUE_ENABLED;
else
params.truncate = VACOPTVALUE_DISABLED;
diff --git a/src/include/access/reloptions.h b/src/include/access/reloptions.h
index a604a4702c..a436697658 100644
--- a/src/include/access/reloptions.h
+++ b/src/include/access/reloptions.h
@@ -29,6 +29,7 @@
typedef enum relopt_type
{
RELOPT_TYPE_BOOL,
+ RELOPT_TYPE_TERNARY, /* on, off, unset */
RELOPT_TYPE_INT,
RELOPT_TYPE_REAL,
RELOPT_TYPE_ENUM,
@@ -80,6 +81,7 @@ typedef struct relopt_value
union
{
bool bool_val;
+ ternary ternary_val;
int int_val;
double real_val;
int enum_val;
@@ -94,6 +96,12 @@ typedef struct relopt_bool
bool default_val;
} relopt_bool;
+typedef struct relopt_rernary
+{
+ relopt_gen gen;
+ int default_val;
+} relopt_ternary;
+
typedef struct relopt_int
{
relopt_gen gen;
@@ -152,19 +160,6 @@ typedef struct
const char *optname; /* option's name */
relopt_type opttype; /* option's datatype */
int offset; /* offset of field in result struct */
-
- /*
- * isset_offset is an optional offset of a field in the result struct that
- * stores whether the option is explicitly set for the relation or if it
- * just picked up the default value. In most cases, this can be
- * accomplished by giving the reloption a special out-of-range default
- * value (e.g., some integer reloptions use -2), but this isn't always
- * possible. For example, a Boolean reloption cannot be given an
- * out-of-range default, so we need another way to discover the source of
- * its value. This offset is only used if given a value greater than
- * zero.
- */
- int isset_offset;
} relopt_parse_elt;
/* Local reloption definition */
@@ -195,6 +190,8 @@ typedef struct local_relopts
extern relopt_kind add_reloption_kind(void);
extern void add_bool_reloption(bits32 kinds, const char *name, const char *desc,
bool default_val, LOCKMODE lockmode);
+extern void add_ternary_reloption(bits32 kinds, const char *name,
+ const char *desc, int default_val, LOCKMODE lockmode);
extern void add_int_reloption(bits32 kinds, const char *name, const char *desc,
int default_val, int min_val, int max_val,
LOCKMODE lockmode);
@@ -214,6 +211,9 @@ extern void register_reloptions_validator(local_relopts *relopts,
extern void add_local_bool_reloption(local_relopts *relopts, const char *name,
const char *desc, bool default_val,
int offset);
+extern void add_local_ternary_reloption(local_relopts *relopts,
+ const char *name, const char *desc,
+ ternary default_val, int offset);
extern void add_local_int_reloption(local_relopts *relopts, const char *name,
const char *desc, int default_val,
int min_val, int max_val, int offset);
diff --git a/src/include/c.h b/src/include/c.h
index 39022f8a9d..9c59ccccf8 100644
--- a/src/include/c.h
+++ b/src/include/c.h
@@ -488,6 +488,22 @@ typedef void (*pg_funcptr_t) (void);
#include <stdbool.h>
+/*
+ * ternary
+ * Boolean value with an extrea "unset" option
+ *
+ * Ternary data type is used in relation options that can be "true", "false" or
+ * "unset". Since relation options are used deep inside the PostgreSQL code,
+ * this type is declared globally.
+*/
+
+typedef enum ternary
+{
+ TERNARY_FALSE = 0,
+ TERNARY_TRUE = 1,
+ TERNARY_UNSET = -1
+} ternary;
+
/* ----------------------------------------------------------------
* Section 3: standard system types
diff --git a/src/include/utils/rel.h b/src/include/utils/rel.h
index b552359915..7346d618f9 100644
--- a/src/include/utils/rel.h
+++ b/src/include/utils/rel.h
@@ -346,8 +346,7 @@ typedef struct StdRdOptions
bool user_catalog_table; /* use as an additional catalog relation */
int parallel_workers; /* max number of parallel workers */
StdRdOptIndexCleanup vacuum_index_cleanup; /* controls index vacuuming */
- bool vacuum_truncate; /* enables vacuum to truncate a relation */
- bool vacuum_truncate_set; /* whether vacuum_truncate is set */
+ ternary vacuum_truncate; /* enables vacuum to truncate a relation */
/*
* Fraction of pages in a relation that vacuum can eagerly scan and fail
--
2.39.2
--nextPart4155600.3ZeAukHxDK
Content-Disposition: attachment;
filename="v2a-0003-Add-alias-to-be-used-as-unset-state.patch"
Content-Transfer-Encoding: 7Bit
Content-Type: text/x-patch; charset="unicode-2-0-utf-8";
name="v2a-0003-Add-alias-to-be-used-as-unset-state.patch"
^ permalink raw reply [nested|flat] 868+ messages in thread
* [PATCH v2 2/4] Introduce ternary reloptions
@ 2025-09-04 14:41 Nikolay Shaplov <[email protected]>
0 siblings, 0 replies; 868+ messages in thread
From: Nikolay Shaplov @ 2025-09-04 14:41 UTC (permalink / raw)
Introduce ternary reloption as a replacement for current `vacuum_truncate`
implementation. Remove `vacuum_truncate_set` additional flag and using
`TERNARY_UNSET` value instead.
---
src/backend/access/common/reloptions.c | 129 ++++++++++++++++++++-----
src/backend/commands/vacuum.c | 4 +-
src/include/access/reloptions.h | 26 ++---
src/include/c.h | 16 +++
src/include/utils/rel.h | 3 +-
5 files changed, 135 insertions(+), 43 deletions(-)
diff --git a/src/backend/access/common/reloptions.c b/src/backend/access/common/reloptions.c
index 0af3fea68f..f543f11001 100644
--- a/src/backend/access/common/reloptions.c
+++ b/src/backend/access/common/reloptions.c
@@ -40,9 +40,9 @@
*
* To add an option:
*
- * (i) decide on a type (bool, integer, real, enum, string), name, default
- * value, upper and lower bounds (if applicable); for strings, consider a
- * validation routine.
+ * (i) decide on a type (bool, ternary, integer, real, enum, string), name,
+ * default value, upper and lower bounds (if applicable); for strings,
+ * consider a validation routine.
* (ii) add a record below (or use add_<type>_reloption).
* (iii) add it to the appropriate options struct (perhaps StdRdOptions)
* (iv) add it to the appropriate handling routine (perhaps
@@ -147,15 +147,6 @@ static relopt_bool boolRelOpts[] =
},
false
},
- {
- {
- "vacuum_truncate",
- "Enables vacuum to truncate empty pages at the end of this table",
- RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
- ShareUpdateExclusiveLock
- },
- true
- },
{
{
"deduplicate_items",
@@ -170,6 +161,21 @@ static relopt_bool boolRelOpts[] =
{{NULL}}
};
+static relopt_ternary ternaryRelOpts[] =
+{
+ {
+ {
+ "vacuum_truncate",
+ "Enables vacuum to truncate empty pages at the end of this table",
+ RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
+ ShareUpdateExclusiveLock
+ },
+ TERNARY_UNSET
+ },
+ /* list terminator */
+ {{NULL}}
+};
+
static relopt_int intRelOpts[] =
{
{
@@ -600,6 +606,13 @@ initialize_reloptions(void)
boolRelOpts[i].gen.lockmode));
j++;
}
+ for (i = 0; ternaryRelOpts[i].gen.name; i++)
+ {
+ Assert(DoLockModesConflict(ternaryRelOpts[i].gen.lockmode,
+ ternaryRelOpts[i].gen.lockmode));
+ j++;
+ }
+
for (i = 0; intRelOpts[i].gen.name; i++)
{
Assert(DoLockModesConflict(intRelOpts[i].gen.lockmode,
@@ -640,6 +653,14 @@ initialize_reloptions(void)
j++;
}
+ for (i = 0; ternaryRelOpts[i].gen.name; i++)
+ {
+ relOpts[j] = &ternaryRelOpts[i].gen;
+ relOpts[j]->type = RELOPT_TYPE_TERNARY;
+ relOpts[j]->namelen = strlen(relOpts[j]->name);
+ j++;
+ }
+
for (i = 0; intRelOpts[i].gen.name; i++)
{
relOpts[j] = &intRelOpts[i].gen;
@@ -800,6 +821,9 @@ allocate_reloption(bits32 kinds, int type, const char *name, const char *desc,
case RELOPT_TYPE_BOOL:
size = sizeof(relopt_bool);
break;
+ case RELOPT_TYPE_TERNARY:
+ size = sizeof(relopt_ternary);
+ break;
case RELOPT_TYPE_INT:
size = sizeof(relopt_int);
break;
@@ -883,6 +907,54 @@ add_local_bool_reloption(local_relopts *relopts, const char *name,
add_local_reloption(relopts, (relopt_gen *) newoption, offset);
}
+/*
+ * init_ternary_reloption
+ * Allocate and initialize a new ternary reloption
+ */
+static relopt_ternary *
+init_ternary_reloption(bits32 kinds, const char *name, const char *desc,
+ ternary default_val, LOCKMODE lockmode)
+{
+ relopt_ternary *newoption;
+
+ newoption = (relopt_ternary *) allocate_reloption(kinds,
+ RELOPT_TYPE_TERNARY, name, desc, lockmode);
+ newoption->default_val = default_val;
+
+ return newoption;
+}
+
+/*
+ * add_ternary_reloption
+ * Add a new ternary reloption
+ */
+void
+add_ternary_reloption(bits32 kinds, const char *name, const char *desc,
+ ternary default_val, LOCKMODE lockmode)
+{
+ relopt_ternary *newoption = init_ternary_reloption(kinds, name, desc,
+ default_val, lockmode);
+
+ add_reloption((relopt_gen *) newoption);
+}
+
+/*
+ * add_local_ternary_reloption
+ * Add a new ternary local reloption
+ *
+ * 'offset' is offset of ternary-typed field.
+ */
+void
+add_local_ternary_reloption(local_relopts *relopts, const char *name,
+ const char *desc, ternary default_val,
+ int offset)
+{
+ relopt_ternary *newoption = init_ternary_reloption(RELOPT_KIND_LOCAL,
+ name, desc,
+ default_val, 0);
+
+ add_local_reloption(relopts, (relopt_gen *) newoption, offset);
+}
/*
* init_real_reloption
@@ -1617,6 +1689,18 @@ parse_one_reloption(relopt_value *option, char *text_str, int text_len,
option->gen->name, value)));
}
break;
+ case RELOPT_TYPE_TERNARY:
+ {
+ bool b;
+ parsed = parse_bool(value, &b);
+ option->values.ternary_val = b ? TERNARY_TRUE : TERNARY_FALSE;
+ if (validate && !parsed)
+ ereport(ERROR,
+ (errcode(ERRCODE_INVALID_PARAMETER_VALUE),
+ errmsg("invalid value for ternary option \"%s\": %s",
+ option->gen->name, value)));
+ }
+ break;
case RELOPT_TYPE_INT:
{
relopt_int *optint = (relopt_int *) option->gen;
@@ -1780,17 +1864,6 @@ fillRelOptions(void *rdopts, Size basesize,
char *itempos = ((char *) rdopts) + elems[j].offset;
char *string_val;
- /*
- * If isset_offset is provided, store whether the reloption is
- * set there.
- */
- if (elems[j].isset_offset > 0)
- {
- char *setpos = ((char *) rdopts) + elems[j].isset_offset;
-
- *(bool *) setpos = options[i].isset;
- }
-
switch (options[i].gen->type)
{
case RELOPT_TYPE_BOOL:
@@ -1798,6 +1871,11 @@ fillRelOptions(void *rdopts, Size basesize,
options[i].values.bool_val :
((relopt_bool *) options[i].gen)->default_val;
break;
+ case RELOPT_TYPE_TERNARY:
+ *(ternary *) itempos = options[i].isset ?
+ options[i].values.ternary_val :
+ ((relopt_ternary *) options[i].gen)->default_val;
+ break;
case RELOPT_TYPE_INT:
*(int *) itempos = options[i].isset ?
options[i].values.int_val :
@@ -1912,8 +1990,8 @@ default_reloptions(Datum reloptions, bool validate, relopt_kind kind)
offsetof(StdRdOptions, parallel_workers)},
{"vacuum_index_cleanup", RELOPT_TYPE_ENUM,
offsetof(StdRdOptions, vacuum_index_cleanup)},
- {"vacuum_truncate", RELOPT_TYPE_BOOL,
- offsetof(StdRdOptions, vacuum_truncate), offsetof(StdRdOptions, vacuum_truncate_set)},
+ {"vacuum_truncate", RELOPT_TYPE_TERNARY,
+ offsetof(StdRdOptions, vacuum_truncate)},
{"vacuum_max_eager_freeze_failure_rate", RELOPT_TYPE_REAL,
offsetof(StdRdOptions, vacuum_max_eager_freeze_failure_rate)}
};
@@ -1993,7 +2071,6 @@ build_local_reloptions(local_relopts *relopts, Datum options, bool validate)
elems[i].optname = opt->option->name;
elems[i].opttype = opt->option->type;
elems[i].offset = opt->offset;
- elems[i].isset_offset = 0; /* not supported for local relopts yet */
i++;
}
diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c
index 733ef40ae7..977babff54 100644
--- a/src/backend/commands/vacuum.c
+++ b/src/backend/commands/vacuum.c
@@ -2221,9 +2221,9 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams params,
{
StdRdOptions *opts = (StdRdOptions *) rel->rd_options;
- if (opts && opts->vacuum_truncate_set)
+ if (opts && opts->vacuum_truncate != TERNARY_UNSET)
{
- if (opts->vacuum_truncate)
+ if (opts->vacuum_truncate == TERNARY_TRUE)
params.truncate = VACOPTVALUE_ENABLED;
else
params.truncate = VACOPTVALUE_DISABLED;
diff --git a/src/include/access/reloptions.h b/src/include/access/reloptions.h
index a604a4702c..a436697658 100644
--- a/src/include/access/reloptions.h
+++ b/src/include/access/reloptions.h
@@ -29,6 +29,7 @@
typedef enum relopt_type
{
RELOPT_TYPE_BOOL,
+ RELOPT_TYPE_TERNARY, /* on, off, unset */
RELOPT_TYPE_INT,
RELOPT_TYPE_REAL,
RELOPT_TYPE_ENUM,
@@ -80,6 +81,7 @@ typedef struct relopt_value
union
{
bool bool_val;
+ ternary ternary_val;
int int_val;
double real_val;
int enum_val;
@@ -94,6 +96,12 @@ typedef struct relopt_bool
bool default_val;
} relopt_bool;
+typedef struct relopt_rernary
+{
+ relopt_gen gen;
+ int default_val;
+} relopt_ternary;
+
typedef struct relopt_int
{
relopt_gen gen;
@@ -152,19 +160,6 @@ typedef struct
const char *optname; /* option's name */
relopt_type opttype; /* option's datatype */
int offset; /* offset of field in result struct */
-
- /*
- * isset_offset is an optional offset of a field in the result struct that
- * stores whether the option is explicitly set for the relation or if it
- * just picked up the default value. In most cases, this can be
- * accomplished by giving the reloption a special out-of-range default
- * value (e.g., some integer reloptions use -2), but this isn't always
- * possible. For example, a Boolean reloption cannot be given an
- * out-of-range default, so we need another way to discover the source of
- * its value. This offset is only used if given a value greater than
- * zero.
- */
- int isset_offset;
} relopt_parse_elt;
/* Local reloption definition */
@@ -195,6 +190,8 @@ typedef struct local_relopts
extern relopt_kind add_reloption_kind(void);
extern void add_bool_reloption(bits32 kinds, const char *name, const char *desc,
bool default_val, LOCKMODE lockmode);
+extern void add_ternary_reloption(bits32 kinds, const char *name,
+ const char *desc, int default_val, LOCKMODE lockmode);
extern void add_int_reloption(bits32 kinds, const char *name, const char *desc,
int default_val, int min_val, int max_val,
LOCKMODE lockmode);
@@ -214,6 +211,9 @@ extern void register_reloptions_validator(local_relopts *relopts,
extern void add_local_bool_reloption(local_relopts *relopts, const char *name,
const char *desc, bool default_val,
int offset);
+extern void add_local_ternary_reloption(local_relopts *relopts,
+ const char *name, const char *desc,
+ ternary default_val, int offset);
extern void add_local_int_reloption(local_relopts *relopts, const char *name,
const char *desc, int default_val,
int min_val, int max_val, int offset);
diff --git a/src/include/c.h b/src/include/c.h
index 39022f8a9d..9c59ccccf8 100644
--- a/src/include/c.h
+++ b/src/include/c.h
@@ -488,6 +488,22 @@ typedef void (*pg_funcptr_t) (void);
#include <stdbool.h>
+/*
+ * ternary
+ * Boolean value with an extrea "unset" option
+ *
+ * Ternary data type is used in relation options that can be "true", "false" or
+ * "unset". Since relation options are used deep inside the PostgreSQL code,
+ * this type is declared globally.
+*/
+
+typedef enum ternary
+{
+ TERNARY_FALSE = 0,
+ TERNARY_TRUE = 1,
+ TERNARY_UNSET = -1
+} ternary;
+
/* ----------------------------------------------------------------
* Section 3: standard system types
diff --git a/src/include/utils/rel.h b/src/include/utils/rel.h
index b552359915..7346d618f9 100644
--- a/src/include/utils/rel.h
+++ b/src/include/utils/rel.h
@@ -346,8 +346,7 @@ typedef struct StdRdOptions
bool user_catalog_table; /* use as an additional catalog relation */
int parallel_workers; /* max number of parallel workers */
StdRdOptIndexCleanup vacuum_index_cleanup; /* controls index vacuuming */
- bool vacuum_truncate; /* enables vacuum to truncate a relation */
- bool vacuum_truncate_set; /* whether vacuum_truncate is set */
+ ternary vacuum_truncate; /* enables vacuum to truncate a relation */
/*
* Fraction of pages in a relation that vacuum can eagerly scan and fail
--
2.39.2
--nextPart5129932.5fSG56mABF
Content-Disposition: attachment;
filename="v2-0003-Add-alias-to-be-used-as-unset-state.patch"
Content-Transfer-Encoding: 7Bit
Content-Type: text/x-patch; charset="unicode-2-0-utf-8";
name="v2-0003-Add-alias-to-be-used-as-unset-state.patch"
^ permalink raw reply [nested|flat] 868+ messages in thread
* [PATCH v2 2/4] Introduce ternary reloptions
@ 2025-09-04 14:41 Nikolay Shaplov <[email protected]>
0 siblings, 0 replies; 868+ messages in thread
From: Nikolay Shaplov @ 2025-09-04 14:41 UTC (permalink / raw)
Introduce ternary reloption as a replacement for current `vacuum_truncate`
implementation. Remove `vacuum_truncate_set` additional flag and using
`TERNARY_UNSET` value instead.
---
src/backend/access/common/reloptions.c | 129 ++++++++++++++++++++-----
src/backend/commands/vacuum.c | 4 +-
src/include/access/reloptions.h | 26 ++---
src/include/c.h | 16 +++
src/include/utils/rel.h | 3 +-
5 files changed, 135 insertions(+), 43 deletions(-)
diff --git a/src/backend/access/common/reloptions.c b/src/backend/access/common/reloptions.c
index 0af3fea68f..f543f11001 100644
--- a/src/backend/access/common/reloptions.c
+++ b/src/backend/access/common/reloptions.c
@@ -40,9 +40,9 @@
*
* To add an option:
*
- * (i) decide on a type (bool, integer, real, enum, string), name, default
- * value, upper and lower bounds (if applicable); for strings, consider a
- * validation routine.
+ * (i) decide on a type (bool, ternary, integer, real, enum, string), name,
+ * default value, upper and lower bounds (if applicable); for strings,
+ * consider a validation routine.
* (ii) add a record below (or use add_<type>_reloption).
* (iii) add it to the appropriate options struct (perhaps StdRdOptions)
* (iv) add it to the appropriate handling routine (perhaps
@@ -147,15 +147,6 @@ static relopt_bool boolRelOpts[] =
},
false
},
- {
- {
- "vacuum_truncate",
- "Enables vacuum to truncate empty pages at the end of this table",
- RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
- ShareUpdateExclusiveLock
- },
- true
- },
{
{
"deduplicate_items",
@@ -170,6 +161,21 @@ static relopt_bool boolRelOpts[] =
{{NULL}}
};
+static relopt_ternary ternaryRelOpts[] =
+{
+ {
+ {
+ "vacuum_truncate",
+ "Enables vacuum to truncate empty pages at the end of this table",
+ RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
+ ShareUpdateExclusiveLock
+ },
+ TERNARY_UNSET
+ },
+ /* list terminator */
+ {{NULL}}
+};
+
static relopt_int intRelOpts[] =
{
{
@@ -600,6 +606,13 @@ initialize_reloptions(void)
boolRelOpts[i].gen.lockmode));
j++;
}
+ for (i = 0; ternaryRelOpts[i].gen.name; i++)
+ {
+ Assert(DoLockModesConflict(ternaryRelOpts[i].gen.lockmode,
+ ternaryRelOpts[i].gen.lockmode));
+ j++;
+ }
+
for (i = 0; intRelOpts[i].gen.name; i++)
{
Assert(DoLockModesConflict(intRelOpts[i].gen.lockmode,
@@ -640,6 +653,14 @@ initialize_reloptions(void)
j++;
}
+ for (i = 0; ternaryRelOpts[i].gen.name; i++)
+ {
+ relOpts[j] = &ternaryRelOpts[i].gen;
+ relOpts[j]->type = RELOPT_TYPE_TERNARY;
+ relOpts[j]->namelen = strlen(relOpts[j]->name);
+ j++;
+ }
+
for (i = 0; intRelOpts[i].gen.name; i++)
{
relOpts[j] = &intRelOpts[i].gen;
@@ -800,6 +821,9 @@ allocate_reloption(bits32 kinds, int type, const char *name, const char *desc,
case RELOPT_TYPE_BOOL:
size = sizeof(relopt_bool);
break;
+ case RELOPT_TYPE_TERNARY:
+ size = sizeof(relopt_ternary);
+ break;
case RELOPT_TYPE_INT:
size = sizeof(relopt_int);
break;
@@ -883,6 +907,54 @@ add_local_bool_reloption(local_relopts *relopts, const char *name,
add_local_reloption(relopts, (relopt_gen *) newoption, offset);
}
+/*
+ * init_ternary_reloption
+ * Allocate and initialize a new ternary reloption
+ */
+static relopt_ternary *
+init_ternary_reloption(bits32 kinds, const char *name, const char *desc,
+ ternary default_val, LOCKMODE lockmode)
+{
+ relopt_ternary *newoption;
+
+ newoption = (relopt_ternary *) allocate_reloption(kinds,
+ RELOPT_TYPE_TERNARY, name, desc, lockmode);
+ newoption->default_val = default_val;
+
+ return newoption;
+}
+
+/*
+ * add_ternary_reloption
+ * Add a new ternary reloption
+ */
+void
+add_ternary_reloption(bits32 kinds, const char *name, const char *desc,
+ ternary default_val, LOCKMODE lockmode)
+{
+ relopt_ternary *newoption = init_ternary_reloption(kinds, name, desc,
+ default_val, lockmode);
+
+ add_reloption((relopt_gen *) newoption);
+}
+
+/*
+ * add_local_ternary_reloption
+ * Add a new ternary local reloption
+ *
+ * 'offset' is offset of ternary-typed field.
+ */
+void
+add_local_ternary_reloption(local_relopts *relopts, const char *name,
+ const char *desc, ternary default_val,
+ int offset)
+{
+ relopt_ternary *newoption = init_ternary_reloption(RELOPT_KIND_LOCAL,
+ name, desc,
+ default_val, 0);
+
+ add_local_reloption(relopts, (relopt_gen *) newoption, offset);
+}
/*
* init_real_reloption
@@ -1617,6 +1689,18 @@ parse_one_reloption(relopt_value *option, char *text_str, int text_len,
option->gen->name, value)));
}
break;
+ case RELOPT_TYPE_TERNARY:
+ {
+ bool b;
+ parsed = parse_bool(value, &b);
+ option->values.ternary_val = b ? TERNARY_TRUE : TERNARY_FALSE;
+ if (validate && !parsed)
+ ereport(ERROR,
+ (errcode(ERRCODE_INVALID_PARAMETER_VALUE),
+ errmsg("invalid value for ternary option \"%s\": %s",
+ option->gen->name, value)));
+ }
+ break;
case RELOPT_TYPE_INT:
{
relopt_int *optint = (relopt_int *) option->gen;
@@ -1780,17 +1864,6 @@ fillRelOptions(void *rdopts, Size basesize,
char *itempos = ((char *) rdopts) + elems[j].offset;
char *string_val;
- /*
- * If isset_offset is provided, store whether the reloption is
- * set there.
- */
- if (elems[j].isset_offset > 0)
- {
- char *setpos = ((char *) rdopts) + elems[j].isset_offset;
-
- *(bool *) setpos = options[i].isset;
- }
-
switch (options[i].gen->type)
{
case RELOPT_TYPE_BOOL:
@@ -1798,6 +1871,11 @@ fillRelOptions(void *rdopts, Size basesize,
options[i].values.bool_val :
((relopt_bool *) options[i].gen)->default_val;
break;
+ case RELOPT_TYPE_TERNARY:
+ *(ternary *) itempos = options[i].isset ?
+ options[i].values.ternary_val :
+ ((relopt_ternary *) options[i].gen)->default_val;
+ break;
case RELOPT_TYPE_INT:
*(int *) itempos = options[i].isset ?
options[i].values.int_val :
@@ -1912,8 +1990,8 @@ default_reloptions(Datum reloptions, bool validate, relopt_kind kind)
offsetof(StdRdOptions, parallel_workers)},
{"vacuum_index_cleanup", RELOPT_TYPE_ENUM,
offsetof(StdRdOptions, vacuum_index_cleanup)},
- {"vacuum_truncate", RELOPT_TYPE_BOOL,
- offsetof(StdRdOptions, vacuum_truncate), offsetof(StdRdOptions, vacuum_truncate_set)},
+ {"vacuum_truncate", RELOPT_TYPE_TERNARY,
+ offsetof(StdRdOptions, vacuum_truncate)},
{"vacuum_max_eager_freeze_failure_rate", RELOPT_TYPE_REAL,
offsetof(StdRdOptions, vacuum_max_eager_freeze_failure_rate)}
};
@@ -1993,7 +2071,6 @@ build_local_reloptions(local_relopts *relopts, Datum options, bool validate)
elems[i].optname = opt->option->name;
elems[i].opttype = opt->option->type;
elems[i].offset = opt->offset;
- elems[i].isset_offset = 0; /* not supported for local relopts yet */
i++;
}
diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c
index 733ef40ae7..977babff54 100644
--- a/src/backend/commands/vacuum.c
+++ b/src/backend/commands/vacuum.c
@@ -2221,9 +2221,9 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams params,
{
StdRdOptions *opts = (StdRdOptions *) rel->rd_options;
- if (opts && opts->vacuum_truncate_set)
+ if (opts && opts->vacuum_truncate != TERNARY_UNSET)
{
- if (opts->vacuum_truncate)
+ if (opts->vacuum_truncate == TERNARY_TRUE)
params.truncate = VACOPTVALUE_ENABLED;
else
params.truncate = VACOPTVALUE_DISABLED;
diff --git a/src/include/access/reloptions.h b/src/include/access/reloptions.h
index a604a4702c..a436697658 100644
--- a/src/include/access/reloptions.h
+++ b/src/include/access/reloptions.h
@@ -29,6 +29,7 @@
typedef enum relopt_type
{
RELOPT_TYPE_BOOL,
+ RELOPT_TYPE_TERNARY, /* on, off, unset */
RELOPT_TYPE_INT,
RELOPT_TYPE_REAL,
RELOPT_TYPE_ENUM,
@@ -80,6 +81,7 @@ typedef struct relopt_value
union
{
bool bool_val;
+ ternary ternary_val;
int int_val;
double real_val;
int enum_val;
@@ -94,6 +96,12 @@ typedef struct relopt_bool
bool default_val;
} relopt_bool;
+typedef struct relopt_rernary
+{
+ relopt_gen gen;
+ int default_val;
+} relopt_ternary;
+
typedef struct relopt_int
{
relopt_gen gen;
@@ -152,19 +160,6 @@ typedef struct
const char *optname; /* option's name */
relopt_type opttype; /* option's datatype */
int offset; /* offset of field in result struct */
-
- /*
- * isset_offset is an optional offset of a field in the result struct that
- * stores whether the option is explicitly set for the relation or if it
- * just picked up the default value. In most cases, this can be
- * accomplished by giving the reloption a special out-of-range default
- * value (e.g., some integer reloptions use -2), but this isn't always
- * possible. For example, a Boolean reloption cannot be given an
- * out-of-range default, so we need another way to discover the source of
- * its value. This offset is only used if given a value greater than
- * zero.
- */
- int isset_offset;
} relopt_parse_elt;
/* Local reloption definition */
@@ -195,6 +190,8 @@ typedef struct local_relopts
extern relopt_kind add_reloption_kind(void);
extern void add_bool_reloption(bits32 kinds, const char *name, const char *desc,
bool default_val, LOCKMODE lockmode);
+extern void add_ternary_reloption(bits32 kinds, const char *name,
+ const char *desc, int default_val, LOCKMODE lockmode);
extern void add_int_reloption(bits32 kinds, const char *name, const char *desc,
int default_val, int min_val, int max_val,
LOCKMODE lockmode);
@@ -214,6 +211,9 @@ extern void register_reloptions_validator(local_relopts *relopts,
extern void add_local_bool_reloption(local_relopts *relopts, const char *name,
const char *desc, bool default_val,
int offset);
+extern void add_local_ternary_reloption(local_relopts *relopts,
+ const char *name, const char *desc,
+ ternary default_val, int offset);
extern void add_local_int_reloption(local_relopts *relopts, const char *name,
const char *desc, int default_val,
int min_val, int max_val, int offset);
diff --git a/src/include/c.h b/src/include/c.h
index 39022f8a9d..9c59ccccf8 100644
--- a/src/include/c.h
+++ b/src/include/c.h
@@ -488,6 +488,22 @@ typedef void (*pg_funcptr_t) (void);
#include <stdbool.h>
+/*
+ * ternary
+ * Boolean value with an extrea "unset" option
+ *
+ * Ternary data type is used in relation options that can be "true", "false" or
+ * "unset". Since relation options are used deep inside the PostgreSQL code,
+ * this type is declared globally.
+*/
+
+typedef enum ternary
+{
+ TERNARY_FALSE = 0,
+ TERNARY_TRUE = 1,
+ TERNARY_UNSET = -1
+} ternary;
+
/* ----------------------------------------------------------------
* Section 3: standard system types
diff --git a/src/include/utils/rel.h b/src/include/utils/rel.h
index b552359915..7346d618f9 100644
--- a/src/include/utils/rel.h
+++ b/src/include/utils/rel.h
@@ -346,8 +346,7 @@ typedef struct StdRdOptions
bool user_catalog_table; /* use as an additional catalog relation */
int parallel_workers; /* max number of parallel workers */
StdRdOptIndexCleanup vacuum_index_cleanup; /* controls index vacuuming */
- bool vacuum_truncate; /* enables vacuum to truncate a relation */
- bool vacuum_truncate_set; /* whether vacuum_truncate is set */
+ ternary vacuum_truncate; /* enables vacuum to truncate a relation */
/*
* Fraction of pages in a relation that vacuum can eagerly scan and fail
--
2.39.2
--nextPart5129932.5fSG56mABF
Content-Disposition: attachment;
filename="v2-0003-Add-alias-to-be-used-as-unset-state.patch"
Content-Transfer-Encoding: 7Bit
Content-Type: text/x-patch; charset="unicode-2-0-utf-8";
name="v2-0003-Add-alias-to-be-used-as-unset-state.patch"
^ permalink raw reply [nested|flat] 868+ messages in thread
* [PATCH v2 2/4] Introduce ternary reloptions
@ 2025-09-04 14:41 Nikolay Shaplov <[email protected]>
0 siblings, 0 replies; 868+ messages in thread
From: Nikolay Shaplov @ 2025-09-04 14:41 UTC (permalink / raw)
Introduce ternary reloption as a replacement for current `vacuum_truncate`
implementation. Remove `vacuum_truncate_set` additional flag and using
`TERNARY_UNSET` value instead.
---
src/backend/access/common/reloptions.c | 129 ++++++++++++++++++++-----
src/backend/commands/vacuum.c | 4 +-
src/include/access/reloptions.h | 26 ++---
src/include/c.h | 16 +++
src/include/utils/rel.h | 3 +-
5 files changed, 135 insertions(+), 43 deletions(-)
diff --git a/src/backend/access/common/reloptions.c b/src/backend/access/common/reloptions.c
index 0af3fea68f..f543f11001 100644
--- a/src/backend/access/common/reloptions.c
+++ b/src/backend/access/common/reloptions.c
@@ -40,9 +40,9 @@
*
* To add an option:
*
- * (i) decide on a type (bool, integer, real, enum, string), name, default
- * value, upper and lower bounds (if applicable); for strings, consider a
- * validation routine.
+ * (i) decide on a type (bool, ternary, integer, real, enum, string), name,
+ * default value, upper and lower bounds (if applicable); for strings,
+ * consider a validation routine.
* (ii) add a record below (or use add_<type>_reloption).
* (iii) add it to the appropriate options struct (perhaps StdRdOptions)
* (iv) add it to the appropriate handling routine (perhaps
@@ -147,15 +147,6 @@ static relopt_bool boolRelOpts[] =
},
false
},
- {
- {
- "vacuum_truncate",
- "Enables vacuum to truncate empty pages at the end of this table",
- RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
- ShareUpdateExclusiveLock
- },
- true
- },
{
{
"deduplicate_items",
@@ -170,6 +161,21 @@ static relopt_bool boolRelOpts[] =
{{NULL}}
};
+static relopt_ternary ternaryRelOpts[] =
+{
+ {
+ {
+ "vacuum_truncate",
+ "Enables vacuum to truncate empty pages at the end of this table",
+ RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
+ ShareUpdateExclusiveLock
+ },
+ TERNARY_UNSET
+ },
+ /* list terminator */
+ {{NULL}}
+};
+
static relopt_int intRelOpts[] =
{
{
@@ -600,6 +606,13 @@ initialize_reloptions(void)
boolRelOpts[i].gen.lockmode));
j++;
}
+ for (i = 0; ternaryRelOpts[i].gen.name; i++)
+ {
+ Assert(DoLockModesConflict(ternaryRelOpts[i].gen.lockmode,
+ ternaryRelOpts[i].gen.lockmode));
+ j++;
+ }
+
for (i = 0; intRelOpts[i].gen.name; i++)
{
Assert(DoLockModesConflict(intRelOpts[i].gen.lockmode,
@@ -640,6 +653,14 @@ initialize_reloptions(void)
j++;
}
+ for (i = 0; ternaryRelOpts[i].gen.name; i++)
+ {
+ relOpts[j] = &ternaryRelOpts[i].gen;
+ relOpts[j]->type = RELOPT_TYPE_TERNARY;
+ relOpts[j]->namelen = strlen(relOpts[j]->name);
+ j++;
+ }
+
for (i = 0; intRelOpts[i].gen.name; i++)
{
relOpts[j] = &intRelOpts[i].gen;
@@ -800,6 +821,9 @@ allocate_reloption(bits32 kinds, int type, const char *name, const char *desc,
case RELOPT_TYPE_BOOL:
size = sizeof(relopt_bool);
break;
+ case RELOPT_TYPE_TERNARY:
+ size = sizeof(relopt_ternary);
+ break;
case RELOPT_TYPE_INT:
size = sizeof(relopt_int);
break;
@@ -883,6 +907,54 @@ add_local_bool_reloption(local_relopts *relopts, const char *name,
add_local_reloption(relopts, (relopt_gen *) newoption, offset);
}
+/*
+ * init_ternary_reloption
+ * Allocate and initialize a new ternary reloption
+ */
+static relopt_ternary *
+init_ternary_reloption(bits32 kinds, const char *name, const char *desc,
+ ternary default_val, LOCKMODE lockmode)
+{
+ relopt_ternary *newoption;
+
+ newoption = (relopt_ternary *) allocate_reloption(kinds,
+ RELOPT_TYPE_TERNARY, name, desc, lockmode);
+ newoption->default_val = default_val;
+
+ return newoption;
+}
+
+/*
+ * add_ternary_reloption
+ * Add a new ternary reloption
+ */
+void
+add_ternary_reloption(bits32 kinds, const char *name, const char *desc,
+ ternary default_val, LOCKMODE lockmode)
+{
+ relopt_ternary *newoption = init_ternary_reloption(kinds, name, desc,
+ default_val, lockmode);
+
+ add_reloption((relopt_gen *) newoption);
+}
+
+/*
+ * add_local_ternary_reloption
+ * Add a new ternary local reloption
+ *
+ * 'offset' is offset of ternary-typed field.
+ */
+void
+add_local_ternary_reloption(local_relopts *relopts, const char *name,
+ const char *desc, ternary default_val,
+ int offset)
+{
+ relopt_ternary *newoption = init_ternary_reloption(RELOPT_KIND_LOCAL,
+ name, desc,
+ default_val, 0);
+
+ add_local_reloption(relopts, (relopt_gen *) newoption, offset);
+}
/*
* init_real_reloption
@@ -1617,6 +1689,18 @@ parse_one_reloption(relopt_value *option, char *text_str, int text_len,
option->gen->name, value)));
}
break;
+ case RELOPT_TYPE_TERNARY:
+ {
+ bool b;
+ parsed = parse_bool(value, &b);
+ option->values.ternary_val = b ? TERNARY_TRUE : TERNARY_FALSE;
+ if (validate && !parsed)
+ ereport(ERROR,
+ (errcode(ERRCODE_INVALID_PARAMETER_VALUE),
+ errmsg("invalid value for ternary option \"%s\": %s",
+ option->gen->name, value)));
+ }
+ break;
case RELOPT_TYPE_INT:
{
relopt_int *optint = (relopt_int *) option->gen;
@@ -1780,17 +1864,6 @@ fillRelOptions(void *rdopts, Size basesize,
char *itempos = ((char *) rdopts) + elems[j].offset;
char *string_val;
- /*
- * If isset_offset is provided, store whether the reloption is
- * set there.
- */
- if (elems[j].isset_offset > 0)
- {
- char *setpos = ((char *) rdopts) + elems[j].isset_offset;
-
- *(bool *) setpos = options[i].isset;
- }
-
switch (options[i].gen->type)
{
case RELOPT_TYPE_BOOL:
@@ -1798,6 +1871,11 @@ fillRelOptions(void *rdopts, Size basesize,
options[i].values.bool_val :
((relopt_bool *) options[i].gen)->default_val;
break;
+ case RELOPT_TYPE_TERNARY:
+ *(ternary *) itempos = options[i].isset ?
+ options[i].values.ternary_val :
+ ((relopt_ternary *) options[i].gen)->default_val;
+ break;
case RELOPT_TYPE_INT:
*(int *) itempos = options[i].isset ?
options[i].values.int_val :
@@ -1912,8 +1990,8 @@ default_reloptions(Datum reloptions, bool validate, relopt_kind kind)
offsetof(StdRdOptions, parallel_workers)},
{"vacuum_index_cleanup", RELOPT_TYPE_ENUM,
offsetof(StdRdOptions, vacuum_index_cleanup)},
- {"vacuum_truncate", RELOPT_TYPE_BOOL,
- offsetof(StdRdOptions, vacuum_truncate), offsetof(StdRdOptions, vacuum_truncate_set)},
+ {"vacuum_truncate", RELOPT_TYPE_TERNARY,
+ offsetof(StdRdOptions, vacuum_truncate)},
{"vacuum_max_eager_freeze_failure_rate", RELOPT_TYPE_REAL,
offsetof(StdRdOptions, vacuum_max_eager_freeze_failure_rate)}
};
@@ -1993,7 +2071,6 @@ build_local_reloptions(local_relopts *relopts, Datum options, bool validate)
elems[i].optname = opt->option->name;
elems[i].opttype = opt->option->type;
elems[i].offset = opt->offset;
- elems[i].isset_offset = 0; /* not supported for local relopts yet */
i++;
}
diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c
index 733ef40ae7..977babff54 100644
--- a/src/backend/commands/vacuum.c
+++ b/src/backend/commands/vacuum.c
@@ -2221,9 +2221,9 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams params,
{
StdRdOptions *opts = (StdRdOptions *) rel->rd_options;
- if (opts && opts->vacuum_truncate_set)
+ if (opts && opts->vacuum_truncate != TERNARY_UNSET)
{
- if (opts->vacuum_truncate)
+ if (opts->vacuum_truncate == TERNARY_TRUE)
params.truncate = VACOPTVALUE_ENABLED;
else
params.truncate = VACOPTVALUE_DISABLED;
diff --git a/src/include/access/reloptions.h b/src/include/access/reloptions.h
index a604a4702c..a436697658 100644
--- a/src/include/access/reloptions.h
+++ b/src/include/access/reloptions.h
@@ -29,6 +29,7 @@
typedef enum relopt_type
{
RELOPT_TYPE_BOOL,
+ RELOPT_TYPE_TERNARY, /* on, off, unset */
RELOPT_TYPE_INT,
RELOPT_TYPE_REAL,
RELOPT_TYPE_ENUM,
@@ -80,6 +81,7 @@ typedef struct relopt_value
union
{
bool bool_val;
+ ternary ternary_val;
int int_val;
double real_val;
int enum_val;
@@ -94,6 +96,12 @@ typedef struct relopt_bool
bool default_val;
} relopt_bool;
+typedef struct relopt_rernary
+{
+ relopt_gen gen;
+ int default_val;
+} relopt_ternary;
+
typedef struct relopt_int
{
relopt_gen gen;
@@ -152,19 +160,6 @@ typedef struct
const char *optname; /* option's name */
relopt_type opttype; /* option's datatype */
int offset; /* offset of field in result struct */
-
- /*
- * isset_offset is an optional offset of a field in the result struct that
- * stores whether the option is explicitly set for the relation or if it
- * just picked up the default value. In most cases, this can be
- * accomplished by giving the reloption a special out-of-range default
- * value (e.g., some integer reloptions use -2), but this isn't always
- * possible. For example, a Boolean reloption cannot be given an
- * out-of-range default, so we need another way to discover the source of
- * its value. This offset is only used if given a value greater than
- * zero.
- */
- int isset_offset;
} relopt_parse_elt;
/* Local reloption definition */
@@ -195,6 +190,8 @@ typedef struct local_relopts
extern relopt_kind add_reloption_kind(void);
extern void add_bool_reloption(bits32 kinds, const char *name, const char *desc,
bool default_val, LOCKMODE lockmode);
+extern void add_ternary_reloption(bits32 kinds, const char *name,
+ const char *desc, int default_val, LOCKMODE lockmode);
extern void add_int_reloption(bits32 kinds, const char *name, const char *desc,
int default_val, int min_val, int max_val,
LOCKMODE lockmode);
@@ -214,6 +211,9 @@ extern void register_reloptions_validator(local_relopts *relopts,
extern void add_local_bool_reloption(local_relopts *relopts, const char *name,
const char *desc, bool default_val,
int offset);
+extern void add_local_ternary_reloption(local_relopts *relopts,
+ const char *name, const char *desc,
+ ternary default_val, int offset);
extern void add_local_int_reloption(local_relopts *relopts, const char *name,
const char *desc, int default_val,
int min_val, int max_val, int offset);
diff --git a/src/include/c.h b/src/include/c.h
index 39022f8a9d..9c59ccccf8 100644
--- a/src/include/c.h
+++ b/src/include/c.h
@@ -488,6 +488,22 @@ typedef void (*pg_funcptr_t) (void);
#include <stdbool.h>
+/*
+ * ternary
+ * Boolean value with an extrea "unset" option
+ *
+ * Ternary data type is used in relation options that can be "true", "false" or
+ * "unset". Since relation options are used deep inside the PostgreSQL code,
+ * this type is declared globally.
+*/
+
+typedef enum ternary
+{
+ TERNARY_FALSE = 0,
+ TERNARY_TRUE = 1,
+ TERNARY_UNSET = -1
+} ternary;
+
/* ----------------------------------------------------------------
* Section 3: standard system types
diff --git a/src/include/utils/rel.h b/src/include/utils/rel.h
index b552359915..7346d618f9 100644
--- a/src/include/utils/rel.h
+++ b/src/include/utils/rel.h
@@ -346,8 +346,7 @@ typedef struct StdRdOptions
bool user_catalog_table; /* use as an additional catalog relation */
int parallel_workers; /* max number of parallel workers */
StdRdOptIndexCleanup vacuum_index_cleanup; /* controls index vacuuming */
- bool vacuum_truncate; /* enables vacuum to truncate a relation */
- bool vacuum_truncate_set; /* whether vacuum_truncate is set */
+ ternary vacuum_truncate; /* enables vacuum to truncate a relation */
/*
* Fraction of pages in a relation that vacuum can eagerly scan and fail
--
2.39.2
--nextPart5129932.5fSG56mABF
Content-Disposition: attachment;
filename="v2-0003-Add-alias-to-be-used-as-unset-state.patch"
Content-Transfer-Encoding: 7Bit
Content-Type: text/x-patch; charset="unicode-2-0-utf-8";
name="v2-0003-Add-alias-to-be-used-as-unset-state.patch"
^ permalink raw reply [nested|flat] 868+ messages in thread
* [PATCH v2 2/4] Introduce ternary reloptions
@ 2025-09-04 14:41 Nikolay Shaplov <[email protected]>
0 siblings, 0 replies; 868+ messages in thread
From: Nikolay Shaplov @ 2025-09-04 14:41 UTC (permalink / raw)
Introduce ternary reloption as a replacement for current `vacuum_truncate`
implementation. Remove `vacuum_truncate_set` additional flag and using
`TERNARY_UNSET` value instead.
---
src/backend/access/common/reloptions.c | 129 ++++++++++++++++++++-----
src/backend/commands/vacuum.c | 4 +-
src/include/access/reloptions.h | 26 ++---
src/include/c.h | 16 +++
src/include/utils/rel.h | 3 +-
5 files changed, 135 insertions(+), 43 deletions(-)
diff --git a/src/backend/access/common/reloptions.c b/src/backend/access/common/reloptions.c
index 0af3fea68f..f543f11001 100644
--- a/src/backend/access/common/reloptions.c
+++ b/src/backend/access/common/reloptions.c
@@ -40,9 +40,9 @@
*
* To add an option:
*
- * (i) decide on a type (bool, integer, real, enum, string), name, default
- * value, upper and lower bounds (if applicable); for strings, consider a
- * validation routine.
+ * (i) decide on a type (bool, ternary, integer, real, enum, string), name,
+ * default value, upper and lower bounds (if applicable); for strings,
+ * consider a validation routine.
* (ii) add a record below (or use add_<type>_reloption).
* (iii) add it to the appropriate options struct (perhaps StdRdOptions)
* (iv) add it to the appropriate handling routine (perhaps
@@ -147,15 +147,6 @@ static relopt_bool boolRelOpts[] =
},
false
},
- {
- {
- "vacuum_truncate",
- "Enables vacuum to truncate empty pages at the end of this table",
- RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
- ShareUpdateExclusiveLock
- },
- true
- },
{
{
"deduplicate_items",
@@ -170,6 +161,21 @@ static relopt_bool boolRelOpts[] =
{{NULL}}
};
+static relopt_ternary ternaryRelOpts[] =
+{
+ {
+ {
+ "vacuum_truncate",
+ "Enables vacuum to truncate empty pages at the end of this table",
+ RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
+ ShareUpdateExclusiveLock
+ },
+ TERNARY_UNSET
+ },
+ /* list terminator */
+ {{NULL}}
+};
+
static relopt_int intRelOpts[] =
{
{
@@ -600,6 +606,13 @@ initialize_reloptions(void)
boolRelOpts[i].gen.lockmode));
j++;
}
+ for (i = 0; ternaryRelOpts[i].gen.name; i++)
+ {
+ Assert(DoLockModesConflict(ternaryRelOpts[i].gen.lockmode,
+ ternaryRelOpts[i].gen.lockmode));
+ j++;
+ }
+
for (i = 0; intRelOpts[i].gen.name; i++)
{
Assert(DoLockModesConflict(intRelOpts[i].gen.lockmode,
@@ -640,6 +653,14 @@ initialize_reloptions(void)
j++;
}
+ for (i = 0; ternaryRelOpts[i].gen.name; i++)
+ {
+ relOpts[j] = &ternaryRelOpts[i].gen;
+ relOpts[j]->type = RELOPT_TYPE_TERNARY;
+ relOpts[j]->namelen = strlen(relOpts[j]->name);
+ j++;
+ }
+
for (i = 0; intRelOpts[i].gen.name; i++)
{
relOpts[j] = &intRelOpts[i].gen;
@@ -800,6 +821,9 @@ allocate_reloption(bits32 kinds, int type, const char *name, const char *desc,
case RELOPT_TYPE_BOOL:
size = sizeof(relopt_bool);
break;
+ case RELOPT_TYPE_TERNARY:
+ size = sizeof(relopt_ternary);
+ break;
case RELOPT_TYPE_INT:
size = sizeof(relopt_int);
break;
@@ -883,6 +907,54 @@ add_local_bool_reloption(local_relopts *relopts, const char *name,
add_local_reloption(relopts, (relopt_gen *) newoption, offset);
}
+/*
+ * init_ternary_reloption
+ * Allocate and initialize a new ternary reloption
+ */
+static relopt_ternary *
+init_ternary_reloption(bits32 kinds, const char *name, const char *desc,
+ ternary default_val, LOCKMODE lockmode)
+{
+ relopt_ternary *newoption;
+
+ newoption = (relopt_ternary *) allocate_reloption(kinds,
+ RELOPT_TYPE_TERNARY, name, desc, lockmode);
+ newoption->default_val = default_val;
+
+ return newoption;
+}
+
+/*
+ * add_ternary_reloption
+ * Add a new ternary reloption
+ */
+void
+add_ternary_reloption(bits32 kinds, const char *name, const char *desc,
+ ternary default_val, LOCKMODE lockmode)
+{
+ relopt_ternary *newoption = init_ternary_reloption(kinds, name, desc,
+ default_val, lockmode);
+
+ add_reloption((relopt_gen *) newoption);
+}
+
+/*
+ * add_local_ternary_reloption
+ * Add a new ternary local reloption
+ *
+ * 'offset' is offset of ternary-typed field.
+ */
+void
+add_local_ternary_reloption(local_relopts *relopts, const char *name,
+ const char *desc, ternary default_val,
+ int offset)
+{
+ relopt_ternary *newoption = init_ternary_reloption(RELOPT_KIND_LOCAL,
+ name, desc,
+ default_val, 0);
+
+ add_local_reloption(relopts, (relopt_gen *) newoption, offset);
+}
/*
* init_real_reloption
@@ -1617,6 +1689,18 @@ parse_one_reloption(relopt_value *option, char *text_str, int text_len,
option->gen->name, value)));
}
break;
+ case RELOPT_TYPE_TERNARY:
+ {
+ bool b;
+ parsed = parse_bool(value, &b);
+ option->values.ternary_val = b ? TERNARY_TRUE : TERNARY_FALSE;
+ if (validate && !parsed)
+ ereport(ERROR,
+ (errcode(ERRCODE_INVALID_PARAMETER_VALUE),
+ errmsg("invalid value for ternary option \"%s\": %s",
+ option->gen->name, value)));
+ }
+ break;
case RELOPT_TYPE_INT:
{
relopt_int *optint = (relopt_int *) option->gen;
@@ -1780,17 +1864,6 @@ fillRelOptions(void *rdopts, Size basesize,
char *itempos = ((char *) rdopts) + elems[j].offset;
char *string_val;
- /*
- * If isset_offset is provided, store whether the reloption is
- * set there.
- */
- if (elems[j].isset_offset > 0)
- {
- char *setpos = ((char *) rdopts) + elems[j].isset_offset;
-
- *(bool *) setpos = options[i].isset;
- }
-
switch (options[i].gen->type)
{
case RELOPT_TYPE_BOOL:
@@ -1798,6 +1871,11 @@ fillRelOptions(void *rdopts, Size basesize,
options[i].values.bool_val :
((relopt_bool *) options[i].gen)->default_val;
break;
+ case RELOPT_TYPE_TERNARY:
+ *(ternary *) itempos = options[i].isset ?
+ options[i].values.ternary_val :
+ ((relopt_ternary *) options[i].gen)->default_val;
+ break;
case RELOPT_TYPE_INT:
*(int *) itempos = options[i].isset ?
options[i].values.int_val :
@@ -1912,8 +1990,8 @@ default_reloptions(Datum reloptions, bool validate, relopt_kind kind)
offsetof(StdRdOptions, parallel_workers)},
{"vacuum_index_cleanup", RELOPT_TYPE_ENUM,
offsetof(StdRdOptions, vacuum_index_cleanup)},
- {"vacuum_truncate", RELOPT_TYPE_BOOL,
- offsetof(StdRdOptions, vacuum_truncate), offsetof(StdRdOptions, vacuum_truncate_set)},
+ {"vacuum_truncate", RELOPT_TYPE_TERNARY,
+ offsetof(StdRdOptions, vacuum_truncate)},
{"vacuum_max_eager_freeze_failure_rate", RELOPT_TYPE_REAL,
offsetof(StdRdOptions, vacuum_max_eager_freeze_failure_rate)}
};
@@ -1993,7 +2071,6 @@ build_local_reloptions(local_relopts *relopts, Datum options, bool validate)
elems[i].optname = opt->option->name;
elems[i].opttype = opt->option->type;
elems[i].offset = opt->offset;
- elems[i].isset_offset = 0; /* not supported for local relopts yet */
i++;
}
diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c
index 733ef40ae7..977babff54 100644
--- a/src/backend/commands/vacuum.c
+++ b/src/backend/commands/vacuum.c
@@ -2221,9 +2221,9 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams params,
{
StdRdOptions *opts = (StdRdOptions *) rel->rd_options;
- if (opts && opts->vacuum_truncate_set)
+ if (opts && opts->vacuum_truncate != TERNARY_UNSET)
{
- if (opts->vacuum_truncate)
+ if (opts->vacuum_truncate == TERNARY_TRUE)
params.truncate = VACOPTVALUE_ENABLED;
else
params.truncate = VACOPTVALUE_DISABLED;
diff --git a/src/include/access/reloptions.h b/src/include/access/reloptions.h
index a604a4702c..a436697658 100644
--- a/src/include/access/reloptions.h
+++ b/src/include/access/reloptions.h
@@ -29,6 +29,7 @@
typedef enum relopt_type
{
RELOPT_TYPE_BOOL,
+ RELOPT_TYPE_TERNARY, /* on, off, unset */
RELOPT_TYPE_INT,
RELOPT_TYPE_REAL,
RELOPT_TYPE_ENUM,
@@ -80,6 +81,7 @@ typedef struct relopt_value
union
{
bool bool_val;
+ ternary ternary_val;
int int_val;
double real_val;
int enum_val;
@@ -94,6 +96,12 @@ typedef struct relopt_bool
bool default_val;
} relopt_bool;
+typedef struct relopt_rernary
+{
+ relopt_gen gen;
+ int default_val;
+} relopt_ternary;
+
typedef struct relopt_int
{
relopt_gen gen;
@@ -152,19 +160,6 @@ typedef struct
const char *optname; /* option's name */
relopt_type opttype; /* option's datatype */
int offset; /* offset of field in result struct */
-
- /*
- * isset_offset is an optional offset of a field in the result struct that
- * stores whether the option is explicitly set for the relation or if it
- * just picked up the default value. In most cases, this can be
- * accomplished by giving the reloption a special out-of-range default
- * value (e.g., some integer reloptions use -2), but this isn't always
- * possible. For example, a Boolean reloption cannot be given an
- * out-of-range default, so we need another way to discover the source of
- * its value. This offset is only used if given a value greater than
- * zero.
- */
- int isset_offset;
} relopt_parse_elt;
/* Local reloption definition */
@@ -195,6 +190,8 @@ typedef struct local_relopts
extern relopt_kind add_reloption_kind(void);
extern void add_bool_reloption(bits32 kinds, const char *name, const char *desc,
bool default_val, LOCKMODE lockmode);
+extern void add_ternary_reloption(bits32 kinds, const char *name,
+ const char *desc, int default_val, LOCKMODE lockmode);
extern void add_int_reloption(bits32 kinds, const char *name, const char *desc,
int default_val, int min_val, int max_val,
LOCKMODE lockmode);
@@ -214,6 +211,9 @@ extern void register_reloptions_validator(local_relopts *relopts,
extern void add_local_bool_reloption(local_relopts *relopts, const char *name,
const char *desc, bool default_val,
int offset);
+extern void add_local_ternary_reloption(local_relopts *relopts,
+ const char *name, const char *desc,
+ ternary default_val, int offset);
extern void add_local_int_reloption(local_relopts *relopts, const char *name,
const char *desc, int default_val,
int min_val, int max_val, int offset);
diff --git a/src/include/c.h b/src/include/c.h
index 39022f8a9d..9c59ccccf8 100644
--- a/src/include/c.h
+++ b/src/include/c.h
@@ -488,6 +488,22 @@ typedef void (*pg_funcptr_t) (void);
#include <stdbool.h>
+/*
+ * ternary
+ * Boolean value with an extrea "unset" option
+ *
+ * Ternary data type is used in relation options that can be "true", "false" or
+ * "unset". Since relation options are used deep inside the PostgreSQL code,
+ * this type is declared globally.
+*/
+
+typedef enum ternary
+{
+ TERNARY_FALSE = 0,
+ TERNARY_TRUE = 1,
+ TERNARY_UNSET = -1
+} ternary;
+
/* ----------------------------------------------------------------
* Section 3: standard system types
diff --git a/src/include/utils/rel.h b/src/include/utils/rel.h
index b552359915..7346d618f9 100644
--- a/src/include/utils/rel.h
+++ b/src/include/utils/rel.h
@@ -346,8 +346,7 @@ typedef struct StdRdOptions
bool user_catalog_table; /* use as an additional catalog relation */
int parallel_workers; /* max number of parallel workers */
StdRdOptIndexCleanup vacuum_index_cleanup; /* controls index vacuuming */
- bool vacuum_truncate; /* enables vacuum to truncate a relation */
- bool vacuum_truncate_set; /* whether vacuum_truncate is set */
+ ternary vacuum_truncate; /* enables vacuum to truncate a relation */
/*
* Fraction of pages in a relation that vacuum can eagerly scan and fail
--
2.39.2
--nextPart5129932.5fSG56mABF
Content-Disposition: attachment;
filename="v2-0003-Add-alias-to-be-used-as-unset-state.patch"
Content-Transfer-Encoding: 7Bit
Content-Type: text/x-patch; charset="unicode-2-0-utf-8";
name="v2-0003-Add-alias-to-be-used-as-unset-state.patch"
^ permalink raw reply [nested|flat] 868+ messages in thread
* [PATCH v2a 2/4] Introduce ternary reloptions
@ 2025-09-04 14:41 Nikolay Shaplov <[email protected]>
0 siblings, 0 replies; 868+ messages in thread
From: Nikolay Shaplov @ 2025-09-04 14:41 UTC (permalink / raw)
Introduce ternary reloption as a replacement for current `vacuum_truncate`
implementation. Remove `vacuum_truncate_set` additional flag and using
`TERNARY_UNSET` value instead.
---
src/backend/access/common/reloptions.c | 129 ++++++++++++++++++++-----
src/backend/commands/vacuum.c | 4 +-
src/include/access/reloptions.h | 26 ++---
src/include/c.h | 16 +++
src/include/utils/rel.h | 3 +-
5 files changed, 135 insertions(+), 43 deletions(-)
diff --git a/src/backend/access/common/reloptions.c b/src/backend/access/common/reloptions.c
index 0af3fea68f..f543f11001 100644
--- a/src/backend/access/common/reloptions.c
+++ b/src/backend/access/common/reloptions.c
@@ -40,9 +40,9 @@
*
* To add an option:
*
- * (i) decide on a type (bool, integer, real, enum, string), name, default
- * value, upper and lower bounds (if applicable); for strings, consider a
- * validation routine.
+ * (i) decide on a type (bool, ternary, integer, real, enum, string), name,
+ * default value, upper and lower bounds (if applicable); for strings,
+ * consider a validation routine.
* (ii) add a record below (or use add_<type>_reloption).
* (iii) add it to the appropriate options struct (perhaps StdRdOptions)
* (iv) add it to the appropriate handling routine (perhaps
@@ -147,15 +147,6 @@ static relopt_bool boolRelOpts[] =
},
false
},
- {
- {
- "vacuum_truncate",
- "Enables vacuum to truncate empty pages at the end of this table",
- RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
- ShareUpdateExclusiveLock
- },
- true
- },
{
{
"deduplicate_items",
@@ -170,6 +161,21 @@ static relopt_bool boolRelOpts[] =
{{NULL}}
};
+static relopt_ternary ternaryRelOpts[] =
+{
+ {
+ {
+ "vacuum_truncate",
+ "Enables vacuum to truncate empty pages at the end of this table",
+ RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
+ ShareUpdateExclusiveLock
+ },
+ TERNARY_UNSET
+ },
+ /* list terminator */
+ {{NULL}}
+};
+
static relopt_int intRelOpts[] =
{
{
@@ -600,6 +606,13 @@ initialize_reloptions(void)
boolRelOpts[i].gen.lockmode));
j++;
}
+ for (i = 0; ternaryRelOpts[i].gen.name; i++)
+ {
+ Assert(DoLockModesConflict(ternaryRelOpts[i].gen.lockmode,
+ ternaryRelOpts[i].gen.lockmode));
+ j++;
+ }
+
for (i = 0; intRelOpts[i].gen.name; i++)
{
Assert(DoLockModesConflict(intRelOpts[i].gen.lockmode,
@@ -640,6 +653,14 @@ initialize_reloptions(void)
j++;
}
+ for (i = 0; ternaryRelOpts[i].gen.name; i++)
+ {
+ relOpts[j] = &ternaryRelOpts[i].gen;
+ relOpts[j]->type = RELOPT_TYPE_TERNARY;
+ relOpts[j]->namelen = strlen(relOpts[j]->name);
+ j++;
+ }
+
for (i = 0; intRelOpts[i].gen.name; i++)
{
relOpts[j] = &intRelOpts[i].gen;
@@ -800,6 +821,9 @@ allocate_reloption(bits32 kinds, int type, const char *name, const char *desc,
case RELOPT_TYPE_BOOL:
size = sizeof(relopt_bool);
break;
+ case RELOPT_TYPE_TERNARY:
+ size = sizeof(relopt_ternary);
+ break;
case RELOPT_TYPE_INT:
size = sizeof(relopt_int);
break;
@@ -883,6 +907,54 @@ add_local_bool_reloption(local_relopts *relopts, const char *name,
add_local_reloption(relopts, (relopt_gen *) newoption, offset);
}
+/*
+ * init_ternary_reloption
+ * Allocate and initialize a new ternary reloption
+ */
+static relopt_ternary *
+init_ternary_reloption(bits32 kinds, const char *name, const char *desc,
+ ternary default_val, LOCKMODE lockmode)
+{
+ relopt_ternary *newoption;
+
+ newoption = (relopt_ternary *) allocate_reloption(kinds,
+ RELOPT_TYPE_TERNARY, name, desc, lockmode);
+ newoption->default_val = default_val;
+
+ return newoption;
+}
+
+/*
+ * add_ternary_reloption
+ * Add a new ternary reloption
+ */
+void
+add_ternary_reloption(bits32 kinds, const char *name, const char *desc,
+ ternary default_val, LOCKMODE lockmode)
+{
+ relopt_ternary *newoption = init_ternary_reloption(kinds, name, desc,
+ default_val, lockmode);
+
+ add_reloption((relopt_gen *) newoption);
+}
+
+/*
+ * add_local_ternary_reloption
+ * Add a new ternary local reloption
+ *
+ * 'offset' is offset of ternary-typed field.
+ */
+void
+add_local_ternary_reloption(local_relopts *relopts, const char *name,
+ const char *desc, ternary default_val,
+ int offset)
+{
+ relopt_ternary *newoption = init_ternary_reloption(RELOPT_KIND_LOCAL,
+ name, desc,
+ default_val, 0);
+
+ add_local_reloption(relopts, (relopt_gen *) newoption, offset);
+}
/*
* init_real_reloption
@@ -1617,6 +1689,18 @@ parse_one_reloption(relopt_value *option, char *text_str, int text_len,
option->gen->name, value)));
}
break;
+ case RELOPT_TYPE_TERNARY:
+ {
+ bool b;
+ parsed = parse_bool(value, &b);
+ option->values.ternary_val = b ? TERNARY_TRUE : TERNARY_FALSE;
+ if (validate && !parsed)
+ ereport(ERROR,
+ (errcode(ERRCODE_INVALID_PARAMETER_VALUE),
+ errmsg("invalid value for ternary option \"%s\": %s",
+ option->gen->name, value)));
+ }
+ break;
case RELOPT_TYPE_INT:
{
relopt_int *optint = (relopt_int *) option->gen;
@@ -1780,17 +1864,6 @@ fillRelOptions(void *rdopts, Size basesize,
char *itempos = ((char *) rdopts) + elems[j].offset;
char *string_val;
- /*
- * If isset_offset is provided, store whether the reloption is
- * set there.
- */
- if (elems[j].isset_offset > 0)
- {
- char *setpos = ((char *) rdopts) + elems[j].isset_offset;
-
- *(bool *) setpos = options[i].isset;
- }
-
switch (options[i].gen->type)
{
case RELOPT_TYPE_BOOL:
@@ -1798,6 +1871,11 @@ fillRelOptions(void *rdopts, Size basesize,
options[i].values.bool_val :
((relopt_bool *) options[i].gen)->default_val;
break;
+ case RELOPT_TYPE_TERNARY:
+ *(ternary *) itempos = options[i].isset ?
+ options[i].values.ternary_val :
+ ((relopt_ternary *) options[i].gen)->default_val;
+ break;
case RELOPT_TYPE_INT:
*(int *) itempos = options[i].isset ?
options[i].values.int_val :
@@ -1912,8 +1990,8 @@ default_reloptions(Datum reloptions, bool validate, relopt_kind kind)
offsetof(StdRdOptions, parallel_workers)},
{"vacuum_index_cleanup", RELOPT_TYPE_ENUM,
offsetof(StdRdOptions, vacuum_index_cleanup)},
- {"vacuum_truncate", RELOPT_TYPE_BOOL,
- offsetof(StdRdOptions, vacuum_truncate), offsetof(StdRdOptions, vacuum_truncate_set)},
+ {"vacuum_truncate", RELOPT_TYPE_TERNARY,
+ offsetof(StdRdOptions, vacuum_truncate)},
{"vacuum_max_eager_freeze_failure_rate", RELOPT_TYPE_REAL,
offsetof(StdRdOptions, vacuum_max_eager_freeze_failure_rate)}
};
@@ -1993,7 +2071,6 @@ build_local_reloptions(local_relopts *relopts, Datum options, bool validate)
elems[i].optname = opt->option->name;
elems[i].opttype = opt->option->type;
elems[i].offset = opt->offset;
- elems[i].isset_offset = 0; /* not supported for local relopts yet */
i++;
}
diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c
index 733ef40ae7..977babff54 100644
--- a/src/backend/commands/vacuum.c
+++ b/src/backend/commands/vacuum.c
@@ -2221,9 +2221,9 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams params,
{
StdRdOptions *opts = (StdRdOptions *) rel->rd_options;
- if (opts && opts->vacuum_truncate_set)
+ if (opts && opts->vacuum_truncate != TERNARY_UNSET)
{
- if (opts->vacuum_truncate)
+ if (opts->vacuum_truncate == TERNARY_TRUE)
params.truncate = VACOPTVALUE_ENABLED;
else
params.truncate = VACOPTVALUE_DISABLED;
diff --git a/src/include/access/reloptions.h b/src/include/access/reloptions.h
index a604a4702c..a436697658 100644
--- a/src/include/access/reloptions.h
+++ b/src/include/access/reloptions.h
@@ -29,6 +29,7 @@
typedef enum relopt_type
{
RELOPT_TYPE_BOOL,
+ RELOPT_TYPE_TERNARY, /* on, off, unset */
RELOPT_TYPE_INT,
RELOPT_TYPE_REAL,
RELOPT_TYPE_ENUM,
@@ -80,6 +81,7 @@ typedef struct relopt_value
union
{
bool bool_val;
+ ternary ternary_val;
int int_val;
double real_val;
int enum_val;
@@ -94,6 +96,12 @@ typedef struct relopt_bool
bool default_val;
} relopt_bool;
+typedef struct relopt_rernary
+{
+ relopt_gen gen;
+ int default_val;
+} relopt_ternary;
+
typedef struct relopt_int
{
relopt_gen gen;
@@ -152,19 +160,6 @@ typedef struct
const char *optname; /* option's name */
relopt_type opttype; /* option's datatype */
int offset; /* offset of field in result struct */
-
- /*
- * isset_offset is an optional offset of a field in the result struct that
- * stores whether the option is explicitly set for the relation or if it
- * just picked up the default value. In most cases, this can be
- * accomplished by giving the reloption a special out-of-range default
- * value (e.g., some integer reloptions use -2), but this isn't always
- * possible. For example, a Boolean reloption cannot be given an
- * out-of-range default, so we need another way to discover the source of
- * its value. This offset is only used if given a value greater than
- * zero.
- */
- int isset_offset;
} relopt_parse_elt;
/* Local reloption definition */
@@ -195,6 +190,8 @@ typedef struct local_relopts
extern relopt_kind add_reloption_kind(void);
extern void add_bool_reloption(bits32 kinds, const char *name, const char *desc,
bool default_val, LOCKMODE lockmode);
+extern void add_ternary_reloption(bits32 kinds, const char *name,
+ const char *desc, int default_val, LOCKMODE lockmode);
extern void add_int_reloption(bits32 kinds, const char *name, const char *desc,
int default_val, int min_val, int max_val,
LOCKMODE lockmode);
@@ -214,6 +211,9 @@ extern void register_reloptions_validator(local_relopts *relopts,
extern void add_local_bool_reloption(local_relopts *relopts, const char *name,
const char *desc, bool default_val,
int offset);
+extern void add_local_ternary_reloption(local_relopts *relopts,
+ const char *name, const char *desc,
+ ternary default_val, int offset);
extern void add_local_int_reloption(local_relopts *relopts, const char *name,
const char *desc, int default_val,
int min_val, int max_val, int offset);
diff --git a/src/include/c.h b/src/include/c.h
index 39022f8a9d..9c59ccccf8 100644
--- a/src/include/c.h
+++ b/src/include/c.h
@@ -488,6 +488,22 @@ typedef void (*pg_funcptr_t) (void);
#include <stdbool.h>
+/*
+ * ternary
+ * Boolean value with an extrea "unset" option
+ *
+ * Ternary data type is used in relation options that can be "true", "false" or
+ * "unset". Since relation options are used deep inside the PostgreSQL code,
+ * this type is declared globally.
+*/
+
+typedef enum ternary
+{
+ TERNARY_FALSE = 0,
+ TERNARY_TRUE = 1,
+ TERNARY_UNSET = -1
+} ternary;
+
/* ----------------------------------------------------------------
* Section 3: standard system types
diff --git a/src/include/utils/rel.h b/src/include/utils/rel.h
index b552359915..7346d618f9 100644
--- a/src/include/utils/rel.h
+++ b/src/include/utils/rel.h
@@ -346,8 +346,7 @@ typedef struct StdRdOptions
bool user_catalog_table; /* use as an additional catalog relation */
int parallel_workers; /* max number of parallel workers */
StdRdOptIndexCleanup vacuum_index_cleanup; /* controls index vacuuming */
- bool vacuum_truncate; /* enables vacuum to truncate a relation */
- bool vacuum_truncate_set; /* whether vacuum_truncate is set */
+ ternary vacuum_truncate; /* enables vacuum to truncate a relation */
/*
* Fraction of pages in a relation that vacuum can eagerly scan and fail
--
2.39.2
--nextPart4155600.3ZeAukHxDK
Content-Disposition: attachment;
filename="v2a-0003-Add-alias-to-be-used-as-unset-state.patch"
Content-Transfer-Encoding: 7Bit
Content-Type: text/x-patch; charset="unicode-2-0-utf-8";
name="v2a-0003-Add-alias-to-be-used-as-unset-state.patch"
^ permalink raw reply [nested|flat] 868+ messages in thread
* [PATCH v2 2/4] Introduce ternary reloptions
@ 2025-09-04 14:41 Nikolay Shaplov <[email protected]>
0 siblings, 0 replies; 868+ messages in thread
From: Nikolay Shaplov @ 2025-09-04 14:41 UTC (permalink / raw)
Introduce ternary reloption as a replacement for current `vacuum_truncate`
implementation. Remove `vacuum_truncate_set` additional flag and using
`TERNARY_UNSET` value instead.
---
src/backend/access/common/reloptions.c | 129 ++++++++++++++++++++-----
src/backend/commands/vacuum.c | 4 +-
src/include/access/reloptions.h | 26 ++---
src/include/c.h | 16 +++
src/include/utils/rel.h | 3 +-
5 files changed, 135 insertions(+), 43 deletions(-)
diff --git a/src/backend/access/common/reloptions.c b/src/backend/access/common/reloptions.c
index 0af3fea68f..f543f11001 100644
--- a/src/backend/access/common/reloptions.c
+++ b/src/backend/access/common/reloptions.c
@@ -40,9 +40,9 @@
*
* To add an option:
*
- * (i) decide on a type (bool, integer, real, enum, string), name, default
- * value, upper and lower bounds (if applicable); for strings, consider a
- * validation routine.
+ * (i) decide on a type (bool, ternary, integer, real, enum, string), name,
+ * default value, upper and lower bounds (if applicable); for strings,
+ * consider a validation routine.
* (ii) add a record below (or use add_<type>_reloption).
* (iii) add it to the appropriate options struct (perhaps StdRdOptions)
* (iv) add it to the appropriate handling routine (perhaps
@@ -147,15 +147,6 @@ static relopt_bool boolRelOpts[] =
},
false
},
- {
- {
- "vacuum_truncate",
- "Enables vacuum to truncate empty pages at the end of this table",
- RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
- ShareUpdateExclusiveLock
- },
- true
- },
{
{
"deduplicate_items",
@@ -170,6 +161,21 @@ static relopt_bool boolRelOpts[] =
{{NULL}}
};
+static relopt_ternary ternaryRelOpts[] =
+{
+ {
+ {
+ "vacuum_truncate",
+ "Enables vacuum to truncate empty pages at the end of this table",
+ RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
+ ShareUpdateExclusiveLock
+ },
+ TERNARY_UNSET
+ },
+ /* list terminator */
+ {{NULL}}
+};
+
static relopt_int intRelOpts[] =
{
{
@@ -600,6 +606,13 @@ initialize_reloptions(void)
boolRelOpts[i].gen.lockmode));
j++;
}
+ for (i = 0; ternaryRelOpts[i].gen.name; i++)
+ {
+ Assert(DoLockModesConflict(ternaryRelOpts[i].gen.lockmode,
+ ternaryRelOpts[i].gen.lockmode));
+ j++;
+ }
+
for (i = 0; intRelOpts[i].gen.name; i++)
{
Assert(DoLockModesConflict(intRelOpts[i].gen.lockmode,
@@ -640,6 +653,14 @@ initialize_reloptions(void)
j++;
}
+ for (i = 0; ternaryRelOpts[i].gen.name; i++)
+ {
+ relOpts[j] = &ternaryRelOpts[i].gen;
+ relOpts[j]->type = RELOPT_TYPE_TERNARY;
+ relOpts[j]->namelen = strlen(relOpts[j]->name);
+ j++;
+ }
+
for (i = 0; intRelOpts[i].gen.name; i++)
{
relOpts[j] = &intRelOpts[i].gen;
@@ -800,6 +821,9 @@ allocate_reloption(bits32 kinds, int type, const char *name, const char *desc,
case RELOPT_TYPE_BOOL:
size = sizeof(relopt_bool);
break;
+ case RELOPT_TYPE_TERNARY:
+ size = sizeof(relopt_ternary);
+ break;
case RELOPT_TYPE_INT:
size = sizeof(relopt_int);
break;
@@ -883,6 +907,54 @@ add_local_bool_reloption(local_relopts *relopts, const char *name,
add_local_reloption(relopts, (relopt_gen *) newoption, offset);
}
+/*
+ * init_ternary_reloption
+ * Allocate and initialize a new ternary reloption
+ */
+static relopt_ternary *
+init_ternary_reloption(bits32 kinds, const char *name, const char *desc,
+ ternary default_val, LOCKMODE lockmode)
+{
+ relopt_ternary *newoption;
+
+ newoption = (relopt_ternary *) allocate_reloption(kinds,
+ RELOPT_TYPE_TERNARY, name, desc, lockmode);
+ newoption->default_val = default_val;
+
+ return newoption;
+}
+
+/*
+ * add_ternary_reloption
+ * Add a new ternary reloption
+ */
+void
+add_ternary_reloption(bits32 kinds, const char *name, const char *desc,
+ ternary default_val, LOCKMODE lockmode)
+{
+ relopt_ternary *newoption = init_ternary_reloption(kinds, name, desc,
+ default_val, lockmode);
+
+ add_reloption((relopt_gen *) newoption);
+}
+
+/*
+ * add_local_ternary_reloption
+ * Add a new ternary local reloption
+ *
+ * 'offset' is offset of ternary-typed field.
+ */
+void
+add_local_ternary_reloption(local_relopts *relopts, const char *name,
+ const char *desc, ternary default_val,
+ int offset)
+{
+ relopt_ternary *newoption = init_ternary_reloption(RELOPT_KIND_LOCAL,
+ name, desc,
+ default_val, 0);
+
+ add_local_reloption(relopts, (relopt_gen *) newoption, offset);
+}
/*
* init_real_reloption
@@ -1617,6 +1689,18 @@ parse_one_reloption(relopt_value *option, char *text_str, int text_len,
option->gen->name, value)));
}
break;
+ case RELOPT_TYPE_TERNARY:
+ {
+ bool b;
+ parsed = parse_bool(value, &b);
+ option->values.ternary_val = b ? TERNARY_TRUE : TERNARY_FALSE;
+ if (validate && !parsed)
+ ereport(ERROR,
+ (errcode(ERRCODE_INVALID_PARAMETER_VALUE),
+ errmsg("invalid value for ternary option \"%s\": %s",
+ option->gen->name, value)));
+ }
+ break;
case RELOPT_TYPE_INT:
{
relopt_int *optint = (relopt_int *) option->gen;
@@ -1780,17 +1864,6 @@ fillRelOptions(void *rdopts, Size basesize,
char *itempos = ((char *) rdopts) + elems[j].offset;
char *string_val;
- /*
- * If isset_offset is provided, store whether the reloption is
- * set there.
- */
- if (elems[j].isset_offset > 0)
- {
- char *setpos = ((char *) rdopts) + elems[j].isset_offset;
-
- *(bool *) setpos = options[i].isset;
- }
-
switch (options[i].gen->type)
{
case RELOPT_TYPE_BOOL:
@@ -1798,6 +1871,11 @@ fillRelOptions(void *rdopts, Size basesize,
options[i].values.bool_val :
((relopt_bool *) options[i].gen)->default_val;
break;
+ case RELOPT_TYPE_TERNARY:
+ *(ternary *) itempos = options[i].isset ?
+ options[i].values.ternary_val :
+ ((relopt_ternary *) options[i].gen)->default_val;
+ break;
case RELOPT_TYPE_INT:
*(int *) itempos = options[i].isset ?
options[i].values.int_val :
@@ -1912,8 +1990,8 @@ default_reloptions(Datum reloptions, bool validate, relopt_kind kind)
offsetof(StdRdOptions, parallel_workers)},
{"vacuum_index_cleanup", RELOPT_TYPE_ENUM,
offsetof(StdRdOptions, vacuum_index_cleanup)},
- {"vacuum_truncate", RELOPT_TYPE_BOOL,
- offsetof(StdRdOptions, vacuum_truncate), offsetof(StdRdOptions, vacuum_truncate_set)},
+ {"vacuum_truncate", RELOPT_TYPE_TERNARY,
+ offsetof(StdRdOptions, vacuum_truncate)},
{"vacuum_max_eager_freeze_failure_rate", RELOPT_TYPE_REAL,
offsetof(StdRdOptions, vacuum_max_eager_freeze_failure_rate)}
};
@@ -1993,7 +2071,6 @@ build_local_reloptions(local_relopts *relopts, Datum options, bool validate)
elems[i].optname = opt->option->name;
elems[i].opttype = opt->option->type;
elems[i].offset = opt->offset;
- elems[i].isset_offset = 0; /* not supported for local relopts yet */
i++;
}
diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c
index 733ef40ae7..977babff54 100644
--- a/src/backend/commands/vacuum.c
+++ b/src/backend/commands/vacuum.c
@@ -2221,9 +2221,9 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams params,
{
StdRdOptions *opts = (StdRdOptions *) rel->rd_options;
- if (opts && opts->vacuum_truncate_set)
+ if (opts && opts->vacuum_truncate != TERNARY_UNSET)
{
- if (opts->vacuum_truncate)
+ if (opts->vacuum_truncate == TERNARY_TRUE)
params.truncate = VACOPTVALUE_ENABLED;
else
params.truncate = VACOPTVALUE_DISABLED;
diff --git a/src/include/access/reloptions.h b/src/include/access/reloptions.h
index a604a4702c..a436697658 100644
--- a/src/include/access/reloptions.h
+++ b/src/include/access/reloptions.h
@@ -29,6 +29,7 @@
typedef enum relopt_type
{
RELOPT_TYPE_BOOL,
+ RELOPT_TYPE_TERNARY, /* on, off, unset */
RELOPT_TYPE_INT,
RELOPT_TYPE_REAL,
RELOPT_TYPE_ENUM,
@@ -80,6 +81,7 @@ typedef struct relopt_value
union
{
bool bool_val;
+ ternary ternary_val;
int int_val;
double real_val;
int enum_val;
@@ -94,6 +96,12 @@ typedef struct relopt_bool
bool default_val;
} relopt_bool;
+typedef struct relopt_rernary
+{
+ relopt_gen gen;
+ int default_val;
+} relopt_ternary;
+
typedef struct relopt_int
{
relopt_gen gen;
@@ -152,19 +160,6 @@ typedef struct
const char *optname; /* option's name */
relopt_type opttype; /* option's datatype */
int offset; /* offset of field in result struct */
-
- /*
- * isset_offset is an optional offset of a field in the result struct that
- * stores whether the option is explicitly set for the relation or if it
- * just picked up the default value. In most cases, this can be
- * accomplished by giving the reloption a special out-of-range default
- * value (e.g., some integer reloptions use -2), but this isn't always
- * possible. For example, a Boolean reloption cannot be given an
- * out-of-range default, so we need another way to discover the source of
- * its value. This offset is only used if given a value greater than
- * zero.
- */
- int isset_offset;
} relopt_parse_elt;
/* Local reloption definition */
@@ -195,6 +190,8 @@ typedef struct local_relopts
extern relopt_kind add_reloption_kind(void);
extern void add_bool_reloption(bits32 kinds, const char *name, const char *desc,
bool default_val, LOCKMODE lockmode);
+extern void add_ternary_reloption(bits32 kinds, const char *name,
+ const char *desc, int default_val, LOCKMODE lockmode);
extern void add_int_reloption(bits32 kinds, const char *name, const char *desc,
int default_val, int min_val, int max_val,
LOCKMODE lockmode);
@@ -214,6 +211,9 @@ extern void register_reloptions_validator(local_relopts *relopts,
extern void add_local_bool_reloption(local_relopts *relopts, const char *name,
const char *desc, bool default_val,
int offset);
+extern void add_local_ternary_reloption(local_relopts *relopts,
+ const char *name, const char *desc,
+ ternary default_val, int offset);
extern void add_local_int_reloption(local_relopts *relopts, const char *name,
const char *desc, int default_val,
int min_val, int max_val, int offset);
diff --git a/src/include/c.h b/src/include/c.h
index 39022f8a9d..9c59ccccf8 100644
--- a/src/include/c.h
+++ b/src/include/c.h
@@ -488,6 +488,22 @@ typedef void (*pg_funcptr_t) (void);
#include <stdbool.h>
+/*
+ * ternary
+ * Boolean value with an extrea "unset" option
+ *
+ * Ternary data type is used in relation options that can be "true", "false" or
+ * "unset". Since relation options are used deep inside the PostgreSQL code,
+ * this type is declared globally.
+*/
+
+typedef enum ternary
+{
+ TERNARY_FALSE = 0,
+ TERNARY_TRUE = 1,
+ TERNARY_UNSET = -1
+} ternary;
+
/* ----------------------------------------------------------------
* Section 3: standard system types
diff --git a/src/include/utils/rel.h b/src/include/utils/rel.h
index b552359915..7346d618f9 100644
--- a/src/include/utils/rel.h
+++ b/src/include/utils/rel.h
@@ -346,8 +346,7 @@ typedef struct StdRdOptions
bool user_catalog_table; /* use as an additional catalog relation */
int parallel_workers; /* max number of parallel workers */
StdRdOptIndexCleanup vacuum_index_cleanup; /* controls index vacuuming */
- bool vacuum_truncate; /* enables vacuum to truncate a relation */
- bool vacuum_truncate_set; /* whether vacuum_truncate is set */
+ ternary vacuum_truncate; /* enables vacuum to truncate a relation */
/*
* Fraction of pages in a relation that vacuum can eagerly scan and fail
--
2.39.2
--nextPart5129932.5fSG56mABF
Content-Disposition: attachment;
filename="v2-0003-Add-alias-to-be-used-as-unset-state.patch"
Content-Transfer-Encoding: 7Bit
Content-Type: text/x-patch; charset="unicode-2-0-utf-8";
name="v2-0003-Add-alias-to-be-used-as-unset-state.patch"
^ permalink raw reply [nested|flat] 868+ messages in thread
* [PATCH v2a 2/4] Introduce ternary reloptions
@ 2025-09-04 14:41 Nikolay Shaplov <[email protected]>
0 siblings, 0 replies; 868+ messages in thread
From: Nikolay Shaplov @ 2025-09-04 14:41 UTC (permalink / raw)
Introduce ternary reloption as a replacement for current `vacuum_truncate`
implementation. Remove `vacuum_truncate_set` additional flag and using
`TERNARY_UNSET` value instead.
---
src/backend/access/common/reloptions.c | 129 ++++++++++++++++++++-----
src/backend/commands/vacuum.c | 4 +-
src/include/access/reloptions.h | 26 ++---
src/include/c.h | 16 +++
src/include/utils/rel.h | 3 +-
5 files changed, 135 insertions(+), 43 deletions(-)
diff --git a/src/backend/access/common/reloptions.c b/src/backend/access/common/reloptions.c
index 0af3fea68f..f543f11001 100644
--- a/src/backend/access/common/reloptions.c
+++ b/src/backend/access/common/reloptions.c
@@ -40,9 +40,9 @@
*
* To add an option:
*
- * (i) decide on a type (bool, integer, real, enum, string), name, default
- * value, upper and lower bounds (if applicable); for strings, consider a
- * validation routine.
+ * (i) decide on a type (bool, ternary, integer, real, enum, string), name,
+ * default value, upper and lower bounds (if applicable); for strings,
+ * consider a validation routine.
* (ii) add a record below (or use add_<type>_reloption).
* (iii) add it to the appropriate options struct (perhaps StdRdOptions)
* (iv) add it to the appropriate handling routine (perhaps
@@ -147,15 +147,6 @@ static relopt_bool boolRelOpts[] =
},
false
},
- {
- {
- "vacuum_truncate",
- "Enables vacuum to truncate empty pages at the end of this table",
- RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
- ShareUpdateExclusiveLock
- },
- true
- },
{
{
"deduplicate_items",
@@ -170,6 +161,21 @@ static relopt_bool boolRelOpts[] =
{{NULL}}
};
+static relopt_ternary ternaryRelOpts[] =
+{
+ {
+ {
+ "vacuum_truncate",
+ "Enables vacuum to truncate empty pages at the end of this table",
+ RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
+ ShareUpdateExclusiveLock
+ },
+ TERNARY_UNSET
+ },
+ /* list terminator */
+ {{NULL}}
+};
+
static relopt_int intRelOpts[] =
{
{
@@ -600,6 +606,13 @@ initialize_reloptions(void)
boolRelOpts[i].gen.lockmode));
j++;
}
+ for (i = 0; ternaryRelOpts[i].gen.name; i++)
+ {
+ Assert(DoLockModesConflict(ternaryRelOpts[i].gen.lockmode,
+ ternaryRelOpts[i].gen.lockmode));
+ j++;
+ }
+
for (i = 0; intRelOpts[i].gen.name; i++)
{
Assert(DoLockModesConflict(intRelOpts[i].gen.lockmode,
@@ -640,6 +653,14 @@ initialize_reloptions(void)
j++;
}
+ for (i = 0; ternaryRelOpts[i].gen.name; i++)
+ {
+ relOpts[j] = &ternaryRelOpts[i].gen;
+ relOpts[j]->type = RELOPT_TYPE_TERNARY;
+ relOpts[j]->namelen = strlen(relOpts[j]->name);
+ j++;
+ }
+
for (i = 0; intRelOpts[i].gen.name; i++)
{
relOpts[j] = &intRelOpts[i].gen;
@@ -800,6 +821,9 @@ allocate_reloption(bits32 kinds, int type, const char *name, const char *desc,
case RELOPT_TYPE_BOOL:
size = sizeof(relopt_bool);
break;
+ case RELOPT_TYPE_TERNARY:
+ size = sizeof(relopt_ternary);
+ break;
case RELOPT_TYPE_INT:
size = sizeof(relopt_int);
break;
@@ -883,6 +907,54 @@ add_local_bool_reloption(local_relopts *relopts, const char *name,
add_local_reloption(relopts, (relopt_gen *) newoption, offset);
}
+/*
+ * init_ternary_reloption
+ * Allocate and initialize a new ternary reloption
+ */
+static relopt_ternary *
+init_ternary_reloption(bits32 kinds, const char *name, const char *desc,
+ ternary default_val, LOCKMODE lockmode)
+{
+ relopt_ternary *newoption;
+
+ newoption = (relopt_ternary *) allocate_reloption(kinds,
+ RELOPT_TYPE_TERNARY, name, desc, lockmode);
+ newoption->default_val = default_val;
+
+ return newoption;
+}
+
+/*
+ * add_ternary_reloption
+ * Add a new ternary reloption
+ */
+void
+add_ternary_reloption(bits32 kinds, const char *name, const char *desc,
+ ternary default_val, LOCKMODE lockmode)
+{
+ relopt_ternary *newoption = init_ternary_reloption(kinds, name, desc,
+ default_val, lockmode);
+
+ add_reloption((relopt_gen *) newoption);
+}
+
+/*
+ * add_local_ternary_reloption
+ * Add a new ternary local reloption
+ *
+ * 'offset' is offset of ternary-typed field.
+ */
+void
+add_local_ternary_reloption(local_relopts *relopts, const char *name,
+ const char *desc, ternary default_val,
+ int offset)
+{
+ relopt_ternary *newoption = init_ternary_reloption(RELOPT_KIND_LOCAL,
+ name, desc,
+ default_val, 0);
+
+ add_local_reloption(relopts, (relopt_gen *) newoption, offset);
+}
/*
* init_real_reloption
@@ -1617,6 +1689,18 @@ parse_one_reloption(relopt_value *option, char *text_str, int text_len,
option->gen->name, value)));
}
break;
+ case RELOPT_TYPE_TERNARY:
+ {
+ bool b;
+ parsed = parse_bool(value, &b);
+ option->values.ternary_val = b ? TERNARY_TRUE : TERNARY_FALSE;
+ if (validate && !parsed)
+ ereport(ERROR,
+ (errcode(ERRCODE_INVALID_PARAMETER_VALUE),
+ errmsg("invalid value for ternary option \"%s\": %s",
+ option->gen->name, value)));
+ }
+ break;
case RELOPT_TYPE_INT:
{
relopt_int *optint = (relopt_int *) option->gen;
@@ -1780,17 +1864,6 @@ fillRelOptions(void *rdopts, Size basesize,
char *itempos = ((char *) rdopts) + elems[j].offset;
char *string_val;
- /*
- * If isset_offset is provided, store whether the reloption is
- * set there.
- */
- if (elems[j].isset_offset > 0)
- {
- char *setpos = ((char *) rdopts) + elems[j].isset_offset;
-
- *(bool *) setpos = options[i].isset;
- }
-
switch (options[i].gen->type)
{
case RELOPT_TYPE_BOOL:
@@ -1798,6 +1871,11 @@ fillRelOptions(void *rdopts, Size basesize,
options[i].values.bool_val :
((relopt_bool *) options[i].gen)->default_val;
break;
+ case RELOPT_TYPE_TERNARY:
+ *(ternary *) itempos = options[i].isset ?
+ options[i].values.ternary_val :
+ ((relopt_ternary *) options[i].gen)->default_val;
+ break;
case RELOPT_TYPE_INT:
*(int *) itempos = options[i].isset ?
options[i].values.int_val :
@@ -1912,8 +1990,8 @@ default_reloptions(Datum reloptions, bool validate, relopt_kind kind)
offsetof(StdRdOptions, parallel_workers)},
{"vacuum_index_cleanup", RELOPT_TYPE_ENUM,
offsetof(StdRdOptions, vacuum_index_cleanup)},
- {"vacuum_truncate", RELOPT_TYPE_BOOL,
- offsetof(StdRdOptions, vacuum_truncate), offsetof(StdRdOptions, vacuum_truncate_set)},
+ {"vacuum_truncate", RELOPT_TYPE_TERNARY,
+ offsetof(StdRdOptions, vacuum_truncate)},
{"vacuum_max_eager_freeze_failure_rate", RELOPT_TYPE_REAL,
offsetof(StdRdOptions, vacuum_max_eager_freeze_failure_rate)}
};
@@ -1993,7 +2071,6 @@ build_local_reloptions(local_relopts *relopts, Datum options, bool validate)
elems[i].optname = opt->option->name;
elems[i].opttype = opt->option->type;
elems[i].offset = opt->offset;
- elems[i].isset_offset = 0; /* not supported for local relopts yet */
i++;
}
diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c
index 733ef40ae7..977babff54 100644
--- a/src/backend/commands/vacuum.c
+++ b/src/backend/commands/vacuum.c
@@ -2221,9 +2221,9 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams params,
{
StdRdOptions *opts = (StdRdOptions *) rel->rd_options;
- if (opts && opts->vacuum_truncate_set)
+ if (opts && opts->vacuum_truncate != TERNARY_UNSET)
{
- if (opts->vacuum_truncate)
+ if (opts->vacuum_truncate == TERNARY_TRUE)
params.truncate = VACOPTVALUE_ENABLED;
else
params.truncate = VACOPTVALUE_DISABLED;
diff --git a/src/include/access/reloptions.h b/src/include/access/reloptions.h
index a604a4702c..a436697658 100644
--- a/src/include/access/reloptions.h
+++ b/src/include/access/reloptions.h
@@ -29,6 +29,7 @@
typedef enum relopt_type
{
RELOPT_TYPE_BOOL,
+ RELOPT_TYPE_TERNARY, /* on, off, unset */
RELOPT_TYPE_INT,
RELOPT_TYPE_REAL,
RELOPT_TYPE_ENUM,
@@ -80,6 +81,7 @@ typedef struct relopt_value
union
{
bool bool_val;
+ ternary ternary_val;
int int_val;
double real_val;
int enum_val;
@@ -94,6 +96,12 @@ typedef struct relopt_bool
bool default_val;
} relopt_bool;
+typedef struct relopt_rernary
+{
+ relopt_gen gen;
+ int default_val;
+} relopt_ternary;
+
typedef struct relopt_int
{
relopt_gen gen;
@@ -152,19 +160,6 @@ typedef struct
const char *optname; /* option's name */
relopt_type opttype; /* option's datatype */
int offset; /* offset of field in result struct */
-
- /*
- * isset_offset is an optional offset of a field in the result struct that
- * stores whether the option is explicitly set for the relation or if it
- * just picked up the default value. In most cases, this can be
- * accomplished by giving the reloption a special out-of-range default
- * value (e.g., some integer reloptions use -2), but this isn't always
- * possible. For example, a Boolean reloption cannot be given an
- * out-of-range default, so we need another way to discover the source of
- * its value. This offset is only used if given a value greater than
- * zero.
- */
- int isset_offset;
} relopt_parse_elt;
/* Local reloption definition */
@@ -195,6 +190,8 @@ typedef struct local_relopts
extern relopt_kind add_reloption_kind(void);
extern void add_bool_reloption(bits32 kinds, const char *name, const char *desc,
bool default_val, LOCKMODE lockmode);
+extern void add_ternary_reloption(bits32 kinds, const char *name,
+ const char *desc, int default_val, LOCKMODE lockmode);
extern void add_int_reloption(bits32 kinds, const char *name, const char *desc,
int default_val, int min_val, int max_val,
LOCKMODE lockmode);
@@ -214,6 +211,9 @@ extern void register_reloptions_validator(local_relopts *relopts,
extern void add_local_bool_reloption(local_relopts *relopts, const char *name,
const char *desc, bool default_val,
int offset);
+extern void add_local_ternary_reloption(local_relopts *relopts,
+ const char *name, const char *desc,
+ ternary default_val, int offset);
extern void add_local_int_reloption(local_relopts *relopts, const char *name,
const char *desc, int default_val,
int min_val, int max_val, int offset);
diff --git a/src/include/c.h b/src/include/c.h
index 39022f8a9d..9c59ccccf8 100644
--- a/src/include/c.h
+++ b/src/include/c.h
@@ -488,6 +488,22 @@ typedef void (*pg_funcptr_t) (void);
#include <stdbool.h>
+/*
+ * ternary
+ * Boolean value with an extrea "unset" option
+ *
+ * Ternary data type is used in relation options that can be "true", "false" or
+ * "unset". Since relation options are used deep inside the PostgreSQL code,
+ * this type is declared globally.
+*/
+
+typedef enum ternary
+{
+ TERNARY_FALSE = 0,
+ TERNARY_TRUE = 1,
+ TERNARY_UNSET = -1
+} ternary;
+
/* ----------------------------------------------------------------
* Section 3: standard system types
diff --git a/src/include/utils/rel.h b/src/include/utils/rel.h
index b552359915..7346d618f9 100644
--- a/src/include/utils/rel.h
+++ b/src/include/utils/rel.h
@@ -346,8 +346,7 @@ typedef struct StdRdOptions
bool user_catalog_table; /* use as an additional catalog relation */
int parallel_workers; /* max number of parallel workers */
StdRdOptIndexCleanup vacuum_index_cleanup; /* controls index vacuuming */
- bool vacuum_truncate; /* enables vacuum to truncate a relation */
- bool vacuum_truncate_set; /* whether vacuum_truncate is set */
+ ternary vacuum_truncate; /* enables vacuum to truncate a relation */
/*
* Fraction of pages in a relation that vacuum can eagerly scan and fail
--
2.39.2
--nextPart4155600.3ZeAukHxDK
Content-Disposition: attachment;
filename="v2a-0003-Add-alias-to-be-used-as-unset-state.patch"
Content-Transfer-Encoding: 7Bit
Content-Type: text/x-patch; charset="unicode-2-0-utf-8";
name="v2a-0003-Add-alias-to-be-used-as-unset-state.patch"
^ permalink raw reply [nested|flat] 868+ messages in thread
* [PATCH v2 2/4] Introduce ternary reloptions
@ 2025-09-04 14:41 Nikolay Shaplov <[email protected]>
0 siblings, 0 replies; 868+ messages in thread
From: Nikolay Shaplov @ 2025-09-04 14:41 UTC (permalink / raw)
Introduce ternary reloption as a replacement for current `vacuum_truncate`
implementation. Remove `vacuum_truncate_set` additional flag and using
`TERNARY_UNSET` value instead.
---
src/backend/access/common/reloptions.c | 129 ++++++++++++++++++++-----
src/backend/commands/vacuum.c | 4 +-
src/include/access/reloptions.h | 26 ++---
src/include/c.h | 16 +++
src/include/utils/rel.h | 3 +-
5 files changed, 135 insertions(+), 43 deletions(-)
diff --git a/src/backend/access/common/reloptions.c b/src/backend/access/common/reloptions.c
index 0af3fea68f..f543f11001 100644
--- a/src/backend/access/common/reloptions.c
+++ b/src/backend/access/common/reloptions.c
@@ -40,9 +40,9 @@
*
* To add an option:
*
- * (i) decide on a type (bool, integer, real, enum, string), name, default
- * value, upper and lower bounds (if applicable); for strings, consider a
- * validation routine.
+ * (i) decide on a type (bool, ternary, integer, real, enum, string), name,
+ * default value, upper and lower bounds (if applicable); for strings,
+ * consider a validation routine.
* (ii) add a record below (or use add_<type>_reloption).
* (iii) add it to the appropriate options struct (perhaps StdRdOptions)
* (iv) add it to the appropriate handling routine (perhaps
@@ -147,15 +147,6 @@ static relopt_bool boolRelOpts[] =
},
false
},
- {
- {
- "vacuum_truncate",
- "Enables vacuum to truncate empty pages at the end of this table",
- RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
- ShareUpdateExclusiveLock
- },
- true
- },
{
{
"deduplicate_items",
@@ -170,6 +161,21 @@ static relopt_bool boolRelOpts[] =
{{NULL}}
};
+static relopt_ternary ternaryRelOpts[] =
+{
+ {
+ {
+ "vacuum_truncate",
+ "Enables vacuum to truncate empty pages at the end of this table",
+ RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
+ ShareUpdateExclusiveLock
+ },
+ TERNARY_UNSET
+ },
+ /* list terminator */
+ {{NULL}}
+};
+
static relopt_int intRelOpts[] =
{
{
@@ -600,6 +606,13 @@ initialize_reloptions(void)
boolRelOpts[i].gen.lockmode));
j++;
}
+ for (i = 0; ternaryRelOpts[i].gen.name; i++)
+ {
+ Assert(DoLockModesConflict(ternaryRelOpts[i].gen.lockmode,
+ ternaryRelOpts[i].gen.lockmode));
+ j++;
+ }
+
for (i = 0; intRelOpts[i].gen.name; i++)
{
Assert(DoLockModesConflict(intRelOpts[i].gen.lockmode,
@@ -640,6 +653,14 @@ initialize_reloptions(void)
j++;
}
+ for (i = 0; ternaryRelOpts[i].gen.name; i++)
+ {
+ relOpts[j] = &ternaryRelOpts[i].gen;
+ relOpts[j]->type = RELOPT_TYPE_TERNARY;
+ relOpts[j]->namelen = strlen(relOpts[j]->name);
+ j++;
+ }
+
for (i = 0; intRelOpts[i].gen.name; i++)
{
relOpts[j] = &intRelOpts[i].gen;
@@ -800,6 +821,9 @@ allocate_reloption(bits32 kinds, int type, const char *name, const char *desc,
case RELOPT_TYPE_BOOL:
size = sizeof(relopt_bool);
break;
+ case RELOPT_TYPE_TERNARY:
+ size = sizeof(relopt_ternary);
+ break;
case RELOPT_TYPE_INT:
size = sizeof(relopt_int);
break;
@@ -883,6 +907,54 @@ add_local_bool_reloption(local_relopts *relopts, const char *name,
add_local_reloption(relopts, (relopt_gen *) newoption, offset);
}
+/*
+ * init_ternary_reloption
+ * Allocate and initialize a new ternary reloption
+ */
+static relopt_ternary *
+init_ternary_reloption(bits32 kinds, const char *name, const char *desc,
+ ternary default_val, LOCKMODE lockmode)
+{
+ relopt_ternary *newoption;
+
+ newoption = (relopt_ternary *) allocate_reloption(kinds,
+ RELOPT_TYPE_TERNARY, name, desc, lockmode);
+ newoption->default_val = default_val;
+
+ return newoption;
+}
+
+/*
+ * add_ternary_reloption
+ * Add a new ternary reloption
+ */
+void
+add_ternary_reloption(bits32 kinds, const char *name, const char *desc,
+ ternary default_val, LOCKMODE lockmode)
+{
+ relopt_ternary *newoption = init_ternary_reloption(kinds, name, desc,
+ default_val, lockmode);
+
+ add_reloption((relopt_gen *) newoption);
+}
+
+/*
+ * add_local_ternary_reloption
+ * Add a new ternary local reloption
+ *
+ * 'offset' is offset of ternary-typed field.
+ */
+void
+add_local_ternary_reloption(local_relopts *relopts, const char *name,
+ const char *desc, ternary default_val,
+ int offset)
+{
+ relopt_ternary *newoption = init_ternary_reloption(RELOPT_KIND_LOCAL,
+ name, desc,
+ default_val, 0);
+
+ add_local_reloption(relopts, (relopt_gen *) newoption, offset);
+}
/*
* init_real_reloption
@@ -1617,6 +1689,18 @@ parse_one_reloption(relopt_value *option, char *text_str, int text_len,
option->gen->name, value)));
}
break;
+ case RELOPT_TYPE_TERNARY:
+ {
+ bool b;
+ parsed = parse_bool(value, &b);
+ option->values.ternary_val = b ? TERNARY_TRUE : TERNARY_FALSE;
+ if (validate && !parsed)
+ ereport(ERROR,
+ (errcode(ERRCODE_INVALID_PARAMETER_VALUE),
+ errmsg("invalid value for ternary option \"%s\": %s",
+ option->gen->name, value)));
+ }
+ break;
case RELOPT_TYPE_INT:
{
relopt_int *optint = (relopt_int *) option->gen;
@@ -1780,17 +1864,6 @@ fillRelOptions(void *rdopts, Size basesize,
char *itempos = ((char *) rdopts) + elems[j].offset;
char *string_val;
- /*
- * If isset_offset is provided, store whether the reloption is
- * set there.
- */
- if (elems[j].isset_offset > 0)
- {
- char *setpos = ((char *) rdopts) + elems[j].isset_offset;
-
- *(bool *) setpos = options[i].isset;
- }
-
switch (options[i].gen->type)
{
case RELOPT_TYPE_BOOL:
@@ -1798,6 +1871,11 @@ fillRelOptions(void *rdopts, Size basesize,
options[i].values.bool_val :
((relopt_bool *) options[i].gen)->default_val;
break;
+ case RELOPT_TYPE_TERNARY:
+ *(ternary *) itempos = options[i].isset ?
+ options[i].values.ternary_val :
+ ((relopt_ternary *) options[i].gen)->default_val;
+ break;
case RELOPT_TYPE_INT:
*(int *) itempos = options[i].isset ?
options[i].values.int_val :
@@ -1912,8 +1990,8 @@ default_reloptions(Datum reloptions, bool validate, relopt_kind kind)
offsetof(StdRdOptions, parallel_workers)},
{"vacuum_index_cleanup", RELOPT_TYPE_ENUM,
offsetof(StdRdOptions, vacuum_index_cleanup)},
- {"vacuum_truncate", RELOPT_TYPE_BOOL,
- offsetof(StdRdOptions, vacuum_truncate), offsetof(StdRdOptions, vacuum_truncate_set)},
+ {"vacuum_truncate", RELOPT_TYPE_TERNARY,
+ offsetof(StdRdOptions, vacuum_truncate)},
{"vacuum_max_eager_freeze_failure_rate", RELOPT_TYPE_REAL,
offsetof(StdRdOptions, vacuum_max_eager_freeze_failure_rate)}
};
@@ -1993,7 +2071,6 @@ build_local_reloptions(local_relopts *relopts, Datum options, bool validate)
elems[i].optname = opt->option->name;
elems[i].opttype = opt->option->type;
elems[i].offset = opt->offset;
- elems[i].isset_offset = 0; /* not supported for local relopts yet */
i++;
}
diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c
index 733ef40ae7..977babff54 100644
--- a/src/backend/commands/vacuum.c
+++ b/src/backend/commands/vacuum.c
@@ -2221,9 +2221,9 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams params,
{
StdRdOptions *opts = (StdRdOptions *) rel->rd_options;
- if (opts && opts->vacuum_truncate_set)
+ if (opts && opts->vacuum_truncate != TERNARY_UNSET)
{
- if (opts->vacuum_truncate)
+ if (opts->vacuum_truncate == TERNARY_TRUE)
params.truncate = VACOPTVALUE_ENABLED;
else
params.truncate = VACOPTVALUE_DISABLED;
diff --git a/src/include/access/reloptions.h b/src/include/access/reloptions.h
index a604a4702c..a436697658 100644
--- a/src/include/access/reloptions.h
+++ b/src/include/access/reloptions.h
@@ -29,6 +29,7 @@
typedef enum relopt_type
{
RELOPT_TYPE_BOOL,
+ RELOPT_TYPE_TERNARY, /* on, off, unset */
RELOPT_TYPE_INT,
RELOPT_TYPE_REAL,
RELOPT_TYPE_ENUM,
@@ -80,6 +81,7 @@ typedef struct relopt_value
union
{
bool bool_val;
+ ternary ternary_val;
int int_val;
double real_val;
int enum_val;
@@ -94,6 +96,12 @@ typedef struct relopt_bool
bool default_val;
} relopt_bool;
+typedef struct relopt_rernary
+{
+ relopt_gen gen;
+ int default_val;
+} relopt_ternary;
+
typedef struct relopt_int
{
relopt_gen gen;
@@ -152,19 +160,6 @@ typedef struct
const char *optname; /* option's name */
relopt_type opttype; /* option's datatype */
int offset; /* offset of field in result struct */
-
- /*
- * isset_offset is an optional offset of a field in the result struct that
- * stores whether the option is explicitly set for the relation or if it
- * just picked up the default value. In most cases, this can be
- * accomplished by giving the reloption a special out-of-range default
- * value (e.g., some integer reloptions use -2), but this isn't always
- * possible. For example, a Boolean reloption cannot be given an
- * out-of-range default, so we need another way to discover the source of
- * its value. This offset is only used if given a value greater than
- * zero.
- */
- int isset_offset;
} relopt_parse_elt;
/* Local reloption definition */
@@ -195,6 +190,8 @@ typedef struct local_relopts
extern relopt_kind add_reloption_kind(void);
extern void add_bool_reloption(bits32 kinds, const char *name, const char *desc,
bool default_val, LOCKMODE lockmode);
+extern void add_ternary_reloption(bits32 kinds, const char *name,
+ const char *desc, int default_val, LOCKMODE lockmode);
extern void add_int_reloption(bits32 kinds, const char *name, const char *desc,
int default_val, int min_val, int max_val,
LOCKMODE lockmode);
@@ -214,6 +211,9 @@ extern void register_reloptions_validator(local_relopts *relopts,
extern void add_local_bool_reloption(local_relopts *relopts, const char *name,
const char *desc, bool default_val,
int offset);
+extern void add_local_ternary_reloption(local_relopts *relopts,
+ const char *name, const char *desc,
+ ternary default_val, int offset);
extern void add_local_int_reloption(local_relopts *relopts, const char *name,
const char *desc, int default_val,
int min_val, int max_val, int offset);
diff --git a/src/include/c.h b/src/include/c.h
index 39022f8a9d..9c59ccccf8 100644
--- a/src/include/c.h
+++ b/src/include/c.h
@@ -488,6 +488,22 @@ typedef void (*pg_funcptr_t) (void);
#include <stdbool.h>
+/*
+ * ternary
+ * Boolean value with an extrea "unset" option
+ *
+ * Ternary data type is used in relation options that can be "true", "false" or
+ * "unset". Since relation options are used deep inside the PostgreSQL code,
+ * this type is declared globally.
+*/
+
+typedef enum ternary
+{
+ TERNARY_FALSE = 0,
+ TERNARY_TRUE = 1,
+ TERNARY_UNSET = -1
+} ternary;
+
/* ----------------------------------------------------------------
* Section 3: standard system types
diff --git a/src/include/utils/rel.h b/src/include/utils/rel.h
index b552359915..7346d618f9 100644
--- a/src/include/utils/rel.h
+++ b/src/include/utils/rel.h
@@ -346,8 +346,7 @@ typedef struct StdRdOptions
bool user_catalog_table; /* use as an additional catalog relation */
int parallel_workers; /* max number of parallel workers */
StdRdOptIndexCleanup vacuum_index_cleanup; /* controls index vacuuming */
- bool vacuum_truncate; /* enables vacuum to truncate a relation */
- bool vacuum_truncate_set; /* whether vacuum_truncate is set */
+ ternary vacuum_truncate; /* enables vacuum to truncate a relation */
/*
* Fraction of pages in a relation that vacuum can eagerly scan and fail
--
2.39.2
--nextPart5129932.5fSG56mABF
Content-Disposition: attachment;
filename="v2-0003-Add-alias-to-be-used-as-unset-state.patch"
Content-Transfer-Encoding: 7Bit
Content-Type: text/x-patch; charset="unicode-2-0-utf-8";
name="v2-0003-Add-alias-to-be-used-as-unset-state.patch"
^ permalink raw reply [nested|flat] 868+ messages in thread
* [PATCH v2a 2/4] Introduce ternary reloptions
@ 2025-09-04 14:41 Nikolay Shaplov <[email protected]>
0 siblings, 0 replies; 868+ messages in thread
From: Nikolay Shaplov @ 2025-09-04 14:41 UTC (permalink / raw)
Introduce ternary reloption as a replacement for current `vacuum_truncate`
implementation. Remove `vacuum_truncate_set` additional flag and using
`TERNARY_UNSET` value instead.
---
src/backend/access/common/reloptions.c | 129 ++++++++++++++++++++-----
src/backend/commands/vacuum.c | 4 +-
src/include/access/reloptions.h | 26 ++---
src/include/c.h | 16 +++
src/include/utils/rel.h | 3 +-
5 files changed, 135 insertions(+), 43 deletions(-)
diff --git a/src/backend/access/common/reloptions.c b/src/backend/access/common/reloptions.c
index 0af3fea68f..f543f11001 100644
--- a/src/backend/access/common/reloptions.c
+++ b/src/backend/access/common/reloptions.c
@@ -40,9 +40,9 @@
*
* To add an option:
*
- * (i) decide on a type (bool, integer, real, enum, string), name, default
- * value, upper and lower bounds (if applicable); for strings, consider a
- * validation routine.
+ * (i) decide on a type (bool, ternary, integer, real, enum, string), name,
+ * default value, upper and lower bounds (if applicable); for strings,
+ * consider a validation routine.
* (ii) add a record below (or use add_<type>_reloption).
* (iii) add it to the appropriate options struct (perhaps StdRdOptions)
* (iv) add it to the appropriate handling routine (perhaps
@@ -147,15 +147,6 @@ static relopt_bool boolRelOpts[] =
},
false
},
- {
- {
- "vacuum_truncate",
- "Enables vacuum to truncate empty pages at the end of this table",
- RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
- ShareUpdateExclusiveLock
- },
- true
- },
{
{
"deduplicate_items",
@@ -170,6 +161,21 @@ static relopt_bool boolRelOpts[] =
{{NULL}}
};
+static relopt_ternary ternaryRelOpts[] =
+{
+ {
+ {
+ "vacuum_truncate",
+ "Enables vacuum to truncate empty pages at the end of this table",
+ RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
+ ShareUpdateExclusiveLock
+ },
+ TERNARY_UNSET
+ },
+ /* list terminator */
+ {{NULL}}
+};
+
static relopt_int intRelOpts[] =
{
{
@@ -600,6 +606,13 @@ initialize_reloptions(void)
boolRelOpts[i].gen.lockmode));
j++;
}
+ for (i = 0; ternaryRelOpts[i].gen.name; i++)
+ {
+ Assert(DoLockModesConflict(ternaryRelOpts[i].gen.lockmode,
+ ternaryRelOpts[i].gen.lockmode));
+ j++;
+ }
+
for (i = 0; intRelOpts[i].gen.name; i++)
{
Assert(DoLockModesConflict(intRelOpts[i].gen.lockmode,
@@ -640,6 +653,14 @@ initialize_reloptions(void)
j++;
}
+ for (i = 0; ternaryRelOpts[i].gen.name; i++)
+ {
+ relOpts[j] = &ternaryRelOpts[i].gen;
+ relOpts[j]->type = RELOPT_TYPE_TERNARY;
+ relOpts[j]->namelen = strlen(relOpts[j]->name);
+ j++;
+ }
+
for (i = 0; intRelOpts[i].gen.name; i++)
{
relOpts[j] = &intRelOpts[i].gen;
@@ -800,6 +821,9 @@ allocate_reloption(bits32 kinds, int type, const char *name, const char *desc,
case RELOPT_TYPE_BOOL:
size = sizeof(relopt_bool);
break;
+ case RELOPT_TYPE_TERNARY:
+ size = sizeof(relopt_ternary);
+ break;
case RELOPT_TYPE_INT:
size = sizeof(relopt_int);
break;
@@ -883,6 +907,54 @@ add_local_bool_reloption(local_relopts *relopts, const char *name,
add_local_reloption(relopts, (relopt_gen *) newoption, offset);
}
+/*
+ * init_ternary_reloption
+ * Allocate and initialize a new ternary reloption
+ */
+static relopt_ternary *
+init_ternary_reloption(bits32 kinds, const char *name, const char *desc,
+ ternary default_val, LOCKMODE lockmode)
+{
+ relopt_ternary *newoption;
+
+ newoption = (relopt_ternary *) allocate_reloption(kinds,
+ RELOPT_TYPE_TERNARY, name, desc, lockmode);
+ newoption->default_val = default_val;
+
+ return newoption;
+}
+
+/*
+ * add_ternary_reloption
+ * Add a new ternary reloption
+ */
+void
+add_ternary_reloption(bits32 kinds, const char *name, const char *desc,
+ ternary default_val, LOCKMODE lockmode)
+{
+ relopt_ternary *newoption = init_ternary_reloption(kinds, name, desc,
+ default_val, lockmode);
+
+ add_reloption((relopt_gen *) newoption);
+}
+
+/*
+ * add_local_ternary_reloption
+ * Add a new ternary local reloption
+ *
+ * 'offset' is offset of ternary-typed field.
+ */
+void
+add_local_ternary_reloption(local_relopts *relopts, const char *name,
+ const char *desc, ternary default_val,
+ int offset)
+{
+ relopt_ternary *newoption = init_ternary_reloption(RELOPT_KIND_LOCAL,
+ name, desc,
+ default_val, 0);
+
+ add_local_reloption(relopts, (relopt_gen *) newoption, offset);
+}
/*
* init_real_reloption
@@ -1617,6 +1689,18 @@ parse_one_reloption(relopt_value *option, char *text_str, int text_len,
option->gen->name, value)));
}
break;
+ case RELOPT_TYPE_TERNARY:
+ {
+ bool b;
+ parsed = parse_bool(value, &b);
+ option->values.ternary_val = b ? TERNARY_TRUE : TERNARY_FALSE;
+ if (validate && !parsed)
+ ereport(ERROR,
+ (errcode(ERRCODE_INVALID_PARAMETER_VALUE),
+ errmsg("invalid value for ternary option \"%s\": %s",
+ option->gen->name, value)));
+ }
+ break;
case RELOPT_TYPE_INT:
{
relopt_int *optint = (relopt_int *) option->gen;
@@ -1780,17 +1864,6 @@ fillRelOptions(void *rdopts, Size basesize,
char *itempos = ((char *) rdopts) + elems[j].offset;
char *string_val;
- /*
- * If isset_offset is provided, store whether the reloption is
- * set there.
- */
- if (elems[j].isset_offset > 0)
- {
- char *setpos = ((char *) rdopts) + elems[j].isset_offset;
-
- *(bool *) setpos = options[i].isset;
- }
-
switch (options[i].gen->type)
{
case RELOPT_TYPE_BOOL:
@@ -1798,6 +1871,11 @@ fillRelOptions(void *rdopts, Size basesize,
options[i].values.bool_val :
((relopt_bool *) options[i].gen)->default_val;
break;
+ case RELOPT_TYPE_TERNARY:
+ *(ternary *) itempos = options[i].isset ?
+ options[i].values.ternary_val :
+ ((relopt_ternary *) options[i].gen)->default_val;
+ break;
case RELOPT_TYPE_INT:
*(int *) itempos = options[i].isset ?
options[i].values.int_val :
@@ -1912,8 +1990,8 @@ default_reloptions(Datum reloptions, bool validate, relopt_kind kind)
offsetof(StdRdOptions, parallel_workers)},
{"vacuum_index_cleanup", RELOPT_TYPE_ENUM,
offsetof(StdRdOptions, vacuum_index_cleanup)},
- {"vacuum_truncate", RELOPT_TYPE_BOOL,
- offsetof(StdRdOptions, vacuum_truncate), offsetof(StdRdOptions, vacuum_truncate_set)},
+ {"vacuum_truncate", RELOPT_TYPE_TERNARY,
+ offsetof(StdRdOptions, vacuum_truncate)},
{"vacuum_max_eager_freeze_failure_rate", RELOPT_TYPE_REAL,
offsetof(StdRdOptions, vacuum_max_eager_freeze_failure_rate)}
};
@@ -1993,7 +2071,6 @@ build_local_reloptions(local_relopts *relopts, Datum options, bool validate)
elems[i].optname = opt->option->name;
elems[i].opttype = opt->option->type;
elems[i].offset = opt->offset;
- elems[i].isset_offset = 0; /* not supported for local relopts yet */
i++;
}
diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c
index 733ef40ae7..977babff54 100644
--- a/src/backend/commands/vacuum.c
+++ b/src/backend/commands/vacuum.c
@@ -2221,9 +2221,9 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams params,
{
StdRdOptions *opts = (StdRdOptions *) rel->rd_options;
- if (opts && opts->vacuum_truncate_set)
+ if (opts && opts->vacuum_truncate != TERNARY_UNSET)
{
- if (opts->vacuum_truncate)
+ if (opts->vacuum_truncate == TERNARY_TRUE)
params.truncate = VACOPTVALUE_ENABLED;
else
params.truncate = VACOPTVALUE_DISABLED;
diff --git a/src/include/access/reloptions.h b/src/include/access/reloptions.h
index a604a4702c..a436697658 100644
--- a/src/include/access/reloptions.h
+++ b/src/include/access/reloptions.h
@@ -29,6 +29,7 @@
typedef enum relopt_type
{
RELOPT_TYPE_BOOL,
+ RELOPT_TYPE_TERNARY, /* on, off, unset */
RELOPT_TYPE_INT,
RELOPT_TYPE_REAL,
RELOPT_TYPE_ENUM,
@@ -80,6 +81,7 @@ typedef struct relopt_value
union
{
bool bool_val;
+ ternary ternary_val;
int int_val;
double real_val;
int enum_val;
@@ -94,6 +96,12 @@ typedef struct relopt_bool
bool default_val;
} relopt_bool;
+typedef struct relopt_rernary
+{
+ relopt_gen gen;
+ int default_val;
+} relopt_ternary;
+
typedef struct relopt_int
{
relopt_gen gen;
@@ -152,19 +160,6 @@ typedef struct
const char *optname; /* option's name */
relopt_type opttype; /* option's datatype */
int offset; /* offset of field in result struct */
-
- /*
- * isset_offset is an optional offset of a field in the result struct that
- * stores whether the option is explicitly set for the relation or if it
- * just picked up the default value. In most cases, this can be
- * accomplished by giving the reloption a special out-of-range default
- * value (e.g., some integer reloptions use -2), but this isn't always
- * possible. For example, a Boolean reloption cannot be given an
- * out-of-range default, so we need another way to discover the source of
- * its value. This offset is only used if given a value greater than
- * zero.
- */
- int isset_offset;
} relopt_parse_elt;
/* Local reloption definition */
@@ -195,6 +190,8 @@ typedef struct local_relopts
extern relopt_kind add_reloption_kind(void);
extern void add_bool_reloption(bits32 kinds, const char *name, const char *desc,
bool default_val, LOCKMODE lockmode);
+extern void add_ternary_reloption(bits32 kinds, const char *name,
+ const char *desc, int default_val, LOCKMODE lockmode);
extern void add_int_reloption(bits32 kinds, const char *name, const char *desc,
int default_val, int min_val, int max_val,
LOCKMODE lockmode);
@@ -214,6 +211,9 @@ extern void register_reloptions_validator(local_relopts *relopts,
extern void add_local_bool_reloption(local_relopts *relopts, const char *name,
const char *desc, bool default_val,
int offset);
+extern void add_local_ternary_reloption(local_relopts *relopts,
+ const char *name, const char *desc,
+ ternary default_val, int offset);
extern void add_local_int_reloption(local_relopts *relopts, const char *name,
const char *desc, int default_val,
int min_val, int max_val, int offset);
diff --git a/src/include/c.h b/src/include/c.h
index 39022f8a9d..9c59ccccf8 100644
--- a/src/include/c.h
+++ b/src/include/c.h
@@ -488,6 +488,22 @@ typedef void (*pg_funcptr_t) (void);
#include <stdbool.h>
+/*
+ * ternary
+ * Boolean value with an extrea "unset" option
+ *
+ * Ternary data type is used in relation options that can be "true", "false" or
+ * "unset". Since relation options are used deep inside the PostgreSQL code,
+ * this type is declared globally.
+*/
+
+typedef enum ternary
+{
+ TERNARY_FALSE = 0,
+ TERNARY_TRUE = 1,
+ TERNARY_UNSET = -1
+} ternary;
+
/* ----------------------------------------------------------------
* Section 3: standard system types
diff --git a/src/include/utils/rel.h b/src/include/utils/rel.h
index b552359915..7346d618f9 100644
--- a/src/include/utils/rel.h
+++ b/src/include/utils/rel.h
@@ -346,8 +346,7 @@ typedef struct StdRdOptions
bool user_catalog_table; /* use as an additional catalog relation */
int parallel_workers; /* max number of parallel workers */
StdRdOptIndexCleanup vacuum_index_cleanup; /* controls index vacuuming */
- bool vacuum_truncate; /* enables vacuum to truncate a relation */
- bool vacuum_truncate_set; /* whether vacuum_truncate is set */
+ ternary vacuum_truncate; /* enables vacuum to truncate a relation */
/*
* Fraction of pages in a relation that vacuum can eagerly scan and fail
--
2.39.2
--nextPart4155600.3ZeAukHxDK
Content-Disposition: attachment;
filename="v2a-0003-Add-alias-to-be-used-as-unset-state.patch"
Content-Transfer-Encoding: 7Bit
Content-Type: text/x-patch; charset="unicode-2-0-utf-8";
name="v2a-0003-Add-alias-to-be-used-as-unset-state.patch"
^ permalink raw reply [nested|flat] 868+ messages in thread
* [PATCH v2a 2/4] Introduce ternary reloptions
@ 2025-09-04 14:41 Nikolay Shaplov <[email protected]>
0 siblings, 0 replies; 868+ messages in thread
From: Nikolay Shaplov @ 2025-09-04 14:41 UTC (permalink / raw)
Introduce ternary reloption as a replacement for current `vacuum_truncate`
implementation. Remove `vacuum_truncate_set` additional flag and using
`TERNARY_UNSET` value instead.
---
src/backend/access/common/reloptions.c | 129 ++++++++++++++++++++-----
src/backend/commands/vacuum.c | 4 +-
src/include/access/reloptions.h | 26 ++---
src/include/c.h | 16 +++
src/include/utils/rel.h | 3 +-
5 files changed, 135 insertions(+), 43 deletions(-)
diff --git a/src/backend/access/common/reloptions.c b/src/backend/access/common/reloptions.c
index 0af3fea68f..f543f11001 100644
--- a/src/backend/access/common/reloptions.c
+++ b/src/backend/access/common/reloptions.c
@@ -40,9 +40,9 @@
*
* To add an option:
*
- * (i) decide on a type (bool, integer, real, enum, string), name, default
- * value, upper and lower bounds (if applicable); for strings, consider a
- * validation routine.
+ * (i) decide on a type (bool, ternary, integer, real, enum, string), name,
+ * default value, upper and lower bounds (if applicable); for strings,
+ * consider a validation routine.
* (ii) add a record below (or use add_<type>_reloption).
* (iii) add it to the appropriate options struct (perhaps StdRdOptions)
* (iv) add it to the appropriate handling routine (perhaps
@@ -147,15 +147,6 @@ static relopt_bool boolRelOpts[] =
},
false
},
- {
- {
- "vacuum_truncate",
- "Enables vacuum to truncate empty pages at the end of this table",
- RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
- ShareUpdateExclusiveLock
- },
- true
- },
{
{
"deduplicate_items",
@@ -170,6 +161,21 @@ static relopt_bool boolRelOpts[] =
{{NULL}}
};
+static relopt_ternary ternaryRelOpts[] =
+{
+ {
+ {
+ "vacuum_truncate",
+ "Enables vacuum to truncate empty pages at the end of this table",
+ RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
+ ShareUpdateExclusiveLock
+ },
+ TERNARY_UNSET
+ },
+ /* list terminator */
+ {{NULL}}
+};
+
static relopt_int intRelOpts[] =
{
{
@@ -600,6 +606,13 @@ initialize_reloptions(void)
boolRelOpts[i].gen.lockmode));
j++;
}
+ for (i = 0; ternaryRelOpts[i].gen.name; i++)
+ {
+ Assert(DoLockModesConflict(ternaryRelOpts[i].gen.lockmode,
+ ternaryRelOpts[i].gen.lockmode));
+ j++;
+ }
+
for (i = 0; intRelOpts[i].gen.name; i++)
{
Assert(DoLockModesConflict(intRelOpts[i].gen.lockmode,
@@ -640,6 +653,14 @@ initialize_reloptions(void)
j++;
}
+ for (i = 0; ternaryRelOpts[i].gen.name; i++)
+ {
+ relOpts[j] = &ternaryRelOpts[i].gen;
+ relOpts[j]->type = RELOPT_TYPE_TERNARY;
+ relOpts[j]->namelen = strlen(relOpts[j]->name);
+ j++;
+ }
+
for (i = 0; intRelOpts[i].gen.name; i++)
{
relOpts[j] = &intRelOpts[i].gen;
@@ -800,6 +821,9 @@ allocate_reloption(bits32 kinds, int type, const char *name, const char *desc,
case RELOPT_TYPE_BOOL:
size = sizeof(relopt_bool);
break;
+ case RELOPT_TYPE_TERNARY:
+ size = sizeof(relopt_ternary);
+ break;
case RELOPT_TYPE_INT:
size = sizeof(relopt_int);
break;
@@ -883,6 +907,54 @@ add_local_bool_reloption(local_relopts *relopts, const char *name,
add_local_reloption(relopts, (relopt_gen *) newoption, offset);
}
+/*
+ * init_ternary_reloption
+ * Allocate and initialize a new ternary reloption
+ */
+static relopt_ternary *
+init_ternary_reloption(bits32 kinds, const char *name, const char *desc,
+ ternary default_val, LOCKMODE lockmode)
+{
+ relopt_ternary *newoption;
+
+ newoption = (relopt_ternary *) allocate_reloption(kinds,
+ RELOPT_TYPE_TERNARY, name, desc, lockmode);
+ newoption->default_val = default_val;
+
+ return newoption;
+}
+
+/*
+ * add_ternary_reloption
+ * Add a new ternary reloption
+ */
+void
+add_ternary_reloption(bits32 kinds, const char *name, const char *desc,
+ ternary default_val, LOCKMODE lockmode)
+{
+ relopt_ternary *newoption = init_ternary_reloption(kinds, name, desc,
+ default_val, lockmode);
+
+ add_reloption((relopt_gen *) newoption);
+}
+
+/*
+ * add_local_ternary_reloption
+ * Add a new ternary local reloption
+ *
+ * 'offset' is offset of ternary-typed field.
+ */
+void
+add_local_ternary_reloption(local_relopts *relopts, const char *name,
+ const char *desc, ternary default_val,
+ int offset)
+{
+ relopt_ternary *newoption = init_ternary_reloption(RELOPT_KIND_LOCAL,
+ name, desc,
+ default_val, 0);
+
+ add_local_reloption(relopts, (relopt_gen *) newoption, offset);
+}
/*
* init_real_reloption
@@ -1617,6 +1689,18 @@ parse_one_reloption(relopt_value *option, char *text_str, int text_len,
option->gen->name, value)));
}
break;
+ case RELOPT_TYPE_TERNARY:
+ {
+ bool b;
+ parsed = parse_bool(value, &b);
+ option->values.ternary_val = b ? TERNARY_TRUE : TERNARY_FALSE;
+ if (validate && !parsed)
+ ereport(ERROR,
+ (errcode(ERRCODE_INVALID_PARAMETER_VALUE),
+ errmsg("invalid value for ternary option \"%s\": %s",
+ option->gen->name, value)));
+ }
+ break;
case RELOPT_TYPE_INT:
{
relopt_int *optint = (relopt_int *) option->gen;
@@ -1780,17 +1864,6 @@ fillRelOptions(void *rdopts, Size basesize,
char *itempos = ((char *) rdopts) + elems[j].offset;
char *string_val;
- /*
- * If isset_offset is provided, store whether the reloption is
- * set there.
- */
- if (elems[j].isset_offset > 0)
- {
- char *setpos = ((char *) rdopts) + elems[j].isset_offset;
-
- *(bool *) setpos = options[i].isset;
- }
-
switch (options[i].gen->type)
{
case RELOPT_TYPE_BOOL:
@@ -1798,6 +1871,11 @@ fillRelOptions(void *rdopts, Size basesize,
options[i].values.bool_val :
((relopt_bool *) options[i].gen)->default_val;
break;
+ case RELOPT_TYPE_TERNARY:
+ *(ternary *) itempos = options[i].isset ?
+ options[i].values.ternary_val :
+ ((relopt_ternary *) options[i].gen)->default_val;
+ break;
case RELOPT_TYPE_INT:
*(int *) itempos = options[i].isset ?
options[i].values.int_val :
@@ -1912,8 +1990,8 @@ default_reloptions(Datum reloptions, bool validate, relopt_kind kind)
offsetof(StdRdOptions, parallel_workers)},
{"vacuum_index_cleanup", RELOPT_TYPE_ENUM,
offsetof(StdRdOptions, vacuum_index_cleanup)},
- {"vacuum_truncate", RELOPT_TYPE_BOOL,
- offsetof(StdRdOptions, vacuum_truncate), offsetof(StdRdOptions, vacuum_truncate_set)},
+ {"vacuum_truncate", RELOPT_TYPE_TERNARY,
+ offsetof(StdRdOptions, vacuum_truncate)},
{"vacuum_max_eager_freeze_failure_rate", RELOPT_TYPE_REAL,
offsetof(StdRdOptions, vacuum_max_eager_freeze_failure_rate)}
};
@@ -1993,7 +2071,6 @@ build_local_reloptions(local_relopts *relopts, Datum options, bool validate)
elems[i].optname = opt->option->name;
elems[i].opttype = opt->option->type;
elems[i].offset = opt->offset;
- elems[i].isset_offset = 0; /* not supported for local relopts yet */
i++;
}
diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c
index 733ef40ae7..977babff54 100644
--- a/src/backend/commands/vacuum.c
+++ b/src/backend/commands/vacuum.c
@@ -2221,9 +2221,9 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams params,
{
StdRdOptions *opts = (StdRdOptions *) rel->rd_options;
- if (opts && opts->vacuum_truncate_set)
+ if (opts && opts->vacuum_truncate != TERNARY_UNSET)
{
- if (opts->vacuum_truncate)
+ if (opts->vacuum_truncate == TERNARY_TRUE)
params.truncate = VACOPTVALUE_ENABLED;
else
params.truncate = VACOPTVALUE_DISABLED;
diff --git a/src/include/access/reloptions.h b/src/include/access/reloptions.h
index a604a4702c..a436697658 100644
--- a/src/include/access/reloptions.h
+++ b/src/include/access/reloptions.h
@@ -29,6 +29,7 @@
typedef enum relopt_type
{
RELOPT_TYPE_BOOL,
+ RELOPT_TYPE_TERNARY, /* on, off, unset */
RELOPT_TYPE_INT,
RELOPT_TYPE_REAL,
RELOPT_TYPE_ENUM,
@@ -80,6 +81,7 @@ typedef struct relopt_value
union
{
bool bool_val;
+ ternary ternary_val;
int int_val;
double real_val;
int enum_val;
@@ -94,6 +96,12 @@ typedef struct relopt_bool
bool default_val;
} relopt_bool;
+typedef struct relopt_rernary
+{
+ relopt_gen gen;
+ int default_val;
+} relopt_ternary;
+
typedef struct relopt_int
{
relopt_gen gen;
@@ -152,19 +160,6 @@ typedef struct
const char *optname; /* option's name */
relopt_type opttype; /* option's datatype */
int offset; /* offset of field in result struct */
-
- /*
- * isset_offset is an optional offset of a field in the result struct that
- * stores whether the option is explicitly set for the relation or if it
- * just picked up the default value. In most cases, this can be
- * accomplished by giving the reloption a special out-of-range default
- * value (e.g., some integer reloptions use -2), but this isn't always
- * possible. For example, a Boolean reloption cannot be given an
- * out-of-range default, so we need another way to discover the source of
- * its value. This offset is only used if given a value greater than
- * zero.
- */
- int isset_offset;
} relopt_parse_elt;
/* Local reloption definition */
@@ -195,6 +190,8 @@ typedef struct local_relopts
extern relopt_kind add_reloption_kind(void);
extern void add_bool_reloption(bits32 kinds, const char *name, const char *desc,
bool default_val, LOCKMODE lockmode);
+extern void add_ternary_reloption(bits32 kinds, const char *name,
+ const char *desc, int default_val, LOCKMODE lockmode);
extern void add_int_reloption(bits32 kinds, const char *name, const char *desc,
int default_val, int min_val, int max_val,
LOCKMODE lockmode);
@@ -214,6 +211,9 @@ extern void register_reloptions_validator(local_relopts *relopts,
extern void add_local_bool_reloption(local_relopts *relopts, const char *name,
const char *desc, bool default_val,
int offset);
+extern void add_local_ternary_reloption(local_relopts *relopts,
+ const char *name, const char *desc,
+ ternary default_val, int offset);
extern void add_local_int_reloption(local_relopts *relopts, const char *name,
const char *desc, int default_val,
int min_val, int max_val, int offset);
diff --git a/src/include/c.h b/src/include/c.h
index 39022f8a9d..9c59ccccf8 100644
--- a/src/include/c.h
+++ b/src/include/c.h
@@ -488,6 +488,22 @@ typedef void (*pg_funcptr_t) (void);
#include <stdbool.h>
+/*
+ * ternary
+ * Boolean value with an extrea "unset" option
+ *
+ * Ternary data type is used in relation options that can be "true", "false" or
+ * "unset". Since relation options are used deep inside the PostgreSQL code,
+ * this type is declared globally.
+*/
+
+typedef enum ternary
+{
+ TERNARY_FALSE = 0,
+ TERNARY_TRUE = 1,
+ TERNARY_UNSET = -1
+} ternary;
+
/* ----------------------------------------------------------------
* Section 3: standard system types
diff --git a/src/include/utils/rel.h b/src/include/utils/rel.h
index b552359915..7346d618f9 100644
--- a/src/include/utils/rel.h
+++ b/src/include/utils/rel.h
@@ -346,8 +346,7 @@ typedef struct StdRdOptions
bool user_catalog_table; /* use as an additional catalog relation */
int parallel_workers; /* max number of parallel workers */
StdRdOptIndexCleanup vacuum_index_cleanup; /* controls index vacuuming */
- bool vacuum_truncate; /* enables vacuum to truncate a relation */
- bool vacuum_truncate_set; /* whether vacuum_truncate is set */
+ ternary vacuum_truncate; /* enables vacuum to truncate a relation */
/*
* Fraction of pages in a relation that vacuum can eagerly scan and fail
--
2.39.2
--nextPart4155600.3ZeAukHxDK
Content-Disposition: attachment;
filename="v2a-0003-Add-alias-to-be-used-as-unset-state.patch"
Content-Transfer-Encoding: 7Bit
Content-Type: text/x-patch; charset="unicode-2-0-utf-8";
name="v2a-0003-Add-alias-to-be-used-as-unset-state.patch"
^ permalink raw reply [nested|flat] 868+ messages in thread
* [PATCH v2a 2/4] Introduce ternary reloptions
@ 2025-09-04 14:41 Nikolay Shaplov <[email protected]>
0 siblings, 0 replies; 868+ messages in thread
From: Nikolay Shaplov @ 2025-09-04 14:41 UTC (permalink / raw)
Introduce ternary reloption as a replacement for current `vacuum_truncate`
implementation. Remove `vacuum_truncate_set` additional flag and using
`TERNARY_UNSET` value instead.
---
src/backend/access/common/reloptions.c | 129 ++++++++++++++++++++-----
src/backend/commands/vacuum.c | 4 +-
src/include/access/reloptions.h | 26 ++---
src/include/c.h | 16 +++
src/include/utils/rel.h | 3 +-
5 files changed, 135 insertions(+), 43 deletions(-)
diff --git a/src/backend/access/common/reloptions.c b/src/backend/access/common/reloptions.c
index 0af3fea68f..f543f11001 100644
--- a/src/backend/access/common/reloptions.c
+++ b/src/backend/access/common/reloptions.c
@@ -40,9 +40,9 @@
*
* To add an option:
*
- * (i) decide on a type (bool, integer, real, enum, string), name, default
- * value, upper and lower bounds (if applicable); for strings, consider a
- * validation routine.
+ * (i) decide on a type (bool, ternary, integer, real, enum, string), name,
+ * default value, upper and lower bounds (if applicable); for strings,
+ * consider a validation routine.
* (ii) add a record below (or use add_<type>_reloption).
* (iii) add it to the appropriate options struct (perhaps StdRdOptions)
* (iv) add it to the appropriate handling routine (perhaps
@@ -147,15 +147,6 @@ static relopt_bool boolRelOpts[] =
},
false
},
- {
- {
- "vacuum_truncate",
- "Enables vacuum to truncate empty pages at the end of this table",
- RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
- ShareUpdateExclusiveLock
- },
- true
- },
{
{
"deduplicate_items",
@@ -170,6 +161,21 @@ static relopt_bool boolRelOpts[] =
{{NULL}}
};
+static relopt_ternary ternaryRelOpts[] =
+{
+ {
+ {
+ "vacuum_truncate",
+ "Enables vacuum to truncate empty pages at the end of this table",
+ RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
+ ShareUpdateExclusiveLock
+ },
+ TERNARY_UNSET
+ },
+ /* list terminator */
+ {{NULL}}
+};
+
static relopt_int intRelOpts[] =
{
{
@@ -600,6 +606,13 @@ initialize_reloptions(void)
boolRelOpts[i].gen.lockmode));
j++;
}
+ for (i = 0; ternaryRelOpts[i].gen.name; i++)
+ {
+ Assert(DoLockModesConflict(ternaryRelOpts[i].gen.lockmode,
+ ternaryRelOpts[i].gen.lockmode));
+ j++;
+ }
+
for (i = 0; intRelOpts[i].gen.name; i++)
{
Assert(DoLockModesConflict(intRelOpts[i].gen.lockmode,
@@ -640,6 +653,14 @@ initialize_reloptions(void)
j++;
}
+ for (i = 0; ternaryRelOpts[i].gen.name; i++)
+ {
+ relOpts[j] = &ternaryRelOpts[i].gen;
+ relOpts[j]->type = RELOPT_TYPE_TERNARY;
+ relOpts[j]->namelen = strlen(relOpts[j]->name);
+ j++;
+ }
+
for (i = 0; intRelOpts[i].gen.name; i++)
{
relOpts[j] = &intRelOpts[i].gen;
@@ -800,6 +821,9 @@ allocate_reloption(bits32 kinds, int type, const char *name, const char *desc,
case RELOPT_TYPE_BOOL:
size = sizeof(relopt_bool);
break;
+ case RELOPT_TYPE_TERNARY:
+ size = sizeof(relopt_ternary);
+ break;
case RELOPT_TYPE_INT:
size = sizeof(relopt_int);
break;
@@ -883,6 +907,54 @@ add_local_bool_reloption(local_relopts *relopts, const char *name,
add_local_reloption(relopts, (relopt_gen *) newoption, offset);
}
+/*
+ * init_ternary_reloption
+ * Allocate and initialize a new ternary reloption
+ */
+static relopt_ternary *
+init_ternary_reloption(bits32 kinds, const char *name, const char *desc,
+ ternary default_val, LOCKMODE lockmode)
+{
+ relopt_ternary *newoption;
+
+ newoption = (relopt_ternary *) allocate_reloption(kinds,
+ RELOPT_TYPE_TERNARY, name, desc, lockmode);
+ newoption->default_val = default_val;
+
+ return newoption;
+}
+
+/*
+ * add_ternary_reloption
+ * Add a new ternary reloption
+ */
+void
+add_ternary_reloption(bits32 kinds, const char *name, const char *desc,
+ ternary default_val, LOCKMODE lockmode)
+{
+ relopt_ternary *newoption = init_ternary_reloption(kinds, name, desc,
+ default_val, lockmode);
+
+ add_reloption((relopt_gen *) newoption);
+}
+
+/*
+ * add_local_ternary_reloption
+ * Add a new ternary local reloption
+ *
+ * 'offset' is offset of ternary-typed field.
+ */
+void
+add_local_ternary_reloption(local_relopts *relopts, const char *name,
+ const char *desc, ternary default_val,
+ int offset)
+{
+ relopt_ternary *newoption = init_ternary_reloption(RELOPT_KIND_LOCAL,
+ name, desc,
+ default_val, 0);
+
+ add_local_reloption(relopts, (relopt_gen *) newoption, offset);
+}
/*
* init_real_reloption
@@ -1617,6 +1689,18 @@ parse_one_reloption(relopt_value *option, char *text_str, int text_len,
option->gen->name, value)));
}
break;
+ case RELOPT_TYPE_TERNARY:
+ {
+ bool b;
+ parsed = parse_bool(value, &b);
+ option->values.ternary_val = b ? TERNARY_TRUE : TERNARY_FALSE;
+ if (validate && !parsed)
+ ereport(ERROR,
+ (errcode(ERRCODE_INVALID_PARAMETER_VALUE),
+ errmsg("invalid value for ternary option \"%s\": %s",
+ option->gen->name, value)));
+ }
+ break;
case RELOPT_TYPE_INT:
{
relopt_int *optint = (relopt_int *) option->gen;
@@ -1780,17 +1864,6 @@ fillRelOptions(void *rdopts, Size basesize,
char *itempos = ((char *) rdopts) + elems[j].offset;
char *string_val;
- /*
- * If isset_offset is provided, store whether the reloption is
- * set there.
- */
- if (elems[j].isset_offset > 0)
- {
- char *setpos = ((char *) rdopts) + elems[j].isset_offset;
-
- *(bool *) setpos = options[i].isset;
- }
-
switch (options[i].gen->type)
{
case RELOPT_TYPE_BOOL:
@@ -1798,6 +1871,11 @@ fillRelOptions(void *rdopts, Size basesize,
options[i].values.bool_val :
((relopt_bool *) options[i].gen)->default_val;
break;
+ case RELOPT_TYPE_TERNARY:
+ *(ternary *) itempos = options[i].isset ?
+ options[i].values.ternary_val :
+ ((relopt_ternary *) options[i].gen)->default_val;
+ break;
case RELOPT_TYPE_INT:
*(int *) itempos = options[i].isset ?
options[i].values.int_val :
@@ -1912,8 +1990,8 @@ default_reloptions(Datum reloptions, bool validate, relopt_kind kind)
offsetof(StdRdOptions, parallel_workers)},
{"vacuum_index_cleanup", RELOPT_TYPE_ENUM,
offsetof(StdRdOptions, vacuum_index_cleanup)},
- {"vacuum_truncate", RELOPT_TYPE_BOOL,
- offsetof(StdRdOptions, vacuum_truncate), offsetof(StdRdOptions, vacuum_truncate_set)},
+ {"vacuum_truncate", RELOPT_TYPE_TERNARY,
+ offsetof(StdRdOptions, vacuum_truncate)},
{"vacuum_max_eager_freeze_failure_rate", RELOPT_TYPE_REAL,
offsetof(StdRdOptions, vacuum_max_eager_freeze_failure_rate)}
};
@@ -1993,7 +2071,6 @@ build_local_reloptions(local_relopts *relopts, Datum options, bool validate)
elems[i].optname = opt->option->name;
elems[i].opttype = opt->option->type;
elems[i].offset = opt->offset;
- elems[i].isset_offset = 0; /* not supported for local relopts yet */
i++;
}
diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c
index 733ef40ae7..977babff54 100644
--- a/src/backend/commands/vacuum.c
+++ b/src/backend/commands/vacuum.c
@@ -2221,9 +2221,9 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams params,
{
StdRdOptions *opts = (StdRdOptions *) rel->rd_options;
- if (opts && opts->vacuum_truncate_set)
+ if (opts && opts->vacuum_truncate != TERNARY_UNSET)
{
- if (opts->vacuum_truncate)
+ if (opts->vacuum_truncate == TERNARY_TRUE)
params.truncate = VACOPTVALUE_ENABLED;
else
params.truncate = VACOPTVALUE_DISABLED;
diff --git a/src/include/access/reloptions.h b/src/include/access/reloptions.h
index a604a4702c..a436697658 100644
--- a/src/include/access/reloptions.h
+++ b/src/include/access/reloptions.h
@@ -29,6 +29,7 @@
typedef enum relopt_type
{
RELOPT_TYPE_BOOL,
+ RELOPT_TYPE_TERNARY, /* on, off, unset */
RELOPT_TYPE_INT,
RELOPT_TYPE_REAL,
RELOPT_TYPE_ENUM,
@@ -80,6 +81,7 @@ typedef struct relopt_value
union
{
bool bool_val;
+ ternary ternary_val;
int int_val;
double real_val;
int enum_val;
@@ -94,6 +96,12 @@ typedef struct relopt_bool
bool default_val;
} relopt_bool;
+typedef struct relopt_rernary
+{
+ relopt_gen gen;
+ int default_val;
+} relopt_ternary;
+
typedef struct relopt_int
{
relopt_gen gen;
@@ -152,19 +160,6 @@ typedef struct
const char *optname; /* option's name */
relopt_type opttype; /* option's datatype */
int offset; /* offset of field in result struct */
-
- /*
- * isset_offset is an optional offset of a field in the result struct that
- * stores whether the option is explicitly set for the relation or if it
- * just picked up the default value. In most cases, this can be
- * accomplished by giving the reloption a special out-of-range default
- * value (e.g., some integer reloptions use -2), but this isn't always
- * possible. For example, a Boolean reloption cannot be given an
- * out-of-range default, so we need another way to discover the source of
- * its value. This offset is only used if given a value greater than
- * zero.
- */
- int isset_offset;
} relopt_parse_elt;
/* Local reloption definition */
@@ -195,6 +190,8 @@ typedef struct local_relopts
extern relopt_kind add_reloption_kind(void);
extern void add_bool_reloption(bits32 kinds, const char *name, const char *desc,
bool default_val, LOCKMODE lockmode);
+extern void add_ternary_reloption(bits32 kinds, const char *name,
+ const char *desc, int default_val, LOCKMODE lockmode);
extern void add_int_reloption(bits32 kinds, const char *name, const char *desc,
int default_val, int min_val, int max_val,
LOCKMODE lockmode);
@@ -214,6 +211,9 @@ extern void register_reloptions_validator(local_relopts *relopts,
extern void add_local_bool_reloption(local_relopts *relopts, const char *name,
const char *desc, bool default_val,
int offset);
+extern void add_local_ternary_reloption(local_relopts *relopts,
+ const char *name, const char *desc,
+ ternary default_val, int offset);
extern void add_local_int_reloption(local_relopts *relopts, const char *name,
const char *desc, int default_val,
int min_val, int max_val, int offset);
diff --git a/src/include/c.h b/src/include/c.h
index 39022f8a9d..9c59ccccf8 100644
--- a/src/include/c.h
+++ b/src/include/c.h
@@ -488,6 +488,22 @@ typedef void (*pg_funcptr_t) (void);
#include <stdbool.h>
+/*
+ * ternary
+ * Boolean value with an extrea "unset" option
+ *
+ * Ternary data type is used in relation options that can be "true", "false" or
+ * "unset". Since relation options are used deep inside the PostgreSQL code,
+ * this type is declared globally.
+*/
+
+typedef enum ternary
+{
+ TERNARY_FALSE = 0,
+ TERNARY_TRUE = 1,
+ TERNARY_UNSET = -1
+} ternary;
+
/* ----------------------------------------------------------------
* Section 3: standard system types
diff --git a/src/include/utils/rel.h b/src/include/utils/rel.h
index b552359915..7346d618f9 100644
--- a/src/include/utils/rel.h
+++ b/src/include/utils/rel.h
@@ -346,8 +346,7 @@ typedef struct StdRdOptions
bool user_catalog_table; /* use as an additional catalog relation */
int parallel_workers; /* max number of parallel workers */
StdRdOptIndexCleanup vacuum_index_cleanup; /* controls index vacuuming */
- bool vacuum_truncate; /* enables vacuum to truncate a relation */
- bool vacuum_truncate_set; /* whether vacuum_truncate is set */
+ ternary vacuum_truncate; /* enables vacuum to truncate a relation */
/*
* Fraction of pages in a relation that vacuum can eagerly scan and fail
--
2.39.2
--nextPart4155600.3ZeAukHxDK
Content-Disposition: attachment;
filename="v2a-0003-Add-alias-to-be-used-as-unset-state.patch"
Content-Transfer-Encoding: 7Bit
Content-Type: text/x-patch; charset="unicode-2-0-utf-8";
name="v2a-0003-Add-alias-to-be-used-as-unset-state.patch"
^ permalink raw reply [nested|flat] 868+ messages in thread
* [PATCH v2a 2/4] Introduce ternary reloptions
@ 2025-09-04 14:41 Nikolay Shaplov <[email protected]>
0 siblings, 0 replies; 868+ messages in thread
From: Nikolay Shaplov @ 2025-09-04 14:41 UTC (permalink / raw)
Introduce ternary reloption as a replacement for current `vacuum_truncate`
implementation. Remove `vacuum_truncate_set` additional flag and using
`TERNARY_UNSET` value instead.
---
src/backend/access/common/reloptions.c | 129 ++++++++++++++++++++-----
src/backend/commands/vacuum.c | 4 +-
src/include/access/reloptions.h | 26 ++---
src/include/c.h | 16 +++
src/include/utils/rel.h | 3 +-
5 files changed, 135 insertions(+), 43 deletions(-)
diff --git a/src/backend/access/common/reloptions.c b/src/backend/access/common/reloptions.c
index 0af3fea68f..f543f11001 100644
--- a/src/backend/access/common/reloptions.c
+++ b/src/backend/access/common/reloptions.c
@@ -40,9 +40,9 @@
*
* To add an option:
*
- * (i) decide on a type (bool, integer, real, enum, string), name, default
- * value, upper and lower bounds (if applicable); for strings, consider a
- * validation routine.
+ * (i) decide on a type (bool, ternary, integer, real, enum, string), name,
+ * default value, upper and lower bounds (if applicable); for strings,
+ * consider a validation routine.
* (ii) add a record below (or use add_<type>_reloption).
* (iii) add it to the appropriate options struct (perhaps StdRdOptions)
* (iv) add it to the appropriate handling routine (perhaps
@@ -147,15 +147,6 @@ static relopt_bool boolRelOpts[] =
},
false
},
- {
- {
- "vacuum_truncate",
- "Enables vacuum to truncate empty pages at the end of this table",
- RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
- ShareUpdateExclusiveLock
- },
- true
- },
{
{
"deduplicate_items",
@@ -170,6 +161,21 @@ static relopt_bool boolRelOpts[] =
{{NULL}}
};
+static relopt_ternary ternaryRelOpts[] =
+{
+ {
+ {
+ "vacuum_truncate",
+ "Enables vacuum to truncate empty pages at the end of this table",
+ RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
+ ShareUpdateExclusiveLock
+ },
+ TERNARY_UNSET
+ },
+ /* list terminator */
+ {{NULL}}
+};
+
static relopt_int intRelOpts[] =
{
{
@@ -600,6 +606,13 @@ initialize_reloptions(void)
boolRelOpts[i].gen.lockmode));
j++;
}
+ for (i = 0; ternaryRelOpts[i].gen.name; i++)
+ {
+ Assert(DoLockModesConflict(ternaryRelOpts[i].gen.lockmode,
+ ternaryRelOpts[i].gen.lockmode));
+ j++;
+ }
+
for (i = 0; intRelOpts[i].gen.name; i++)
{
Assert(DoLockModesConflict(intRelOpts[i].gen.lockmode,
@@ -640,6 +653,14 @@ initialize_reloptions(void)
j++;
}
+ for (i = 0; ternaryRelOpts[i].gen.name; i++)
+ {
+ relOpts[j] = &ternaryRelOpts[i].gen;
+ relOpts[j]->type = RELOPT_TYPE_TERNARY;
+ relOpts[j]->namelen = strlen(relOpts[j]->name);
+ j++;
+ }
+
for (i = 0; intRelOpts[i].gen.name; i++)
{
relOpts[j] = &intRelOpts[i].gen;
@@ -800,6 +821,9 @@ allocate_reloption(bits32 kinds, int type, const char *name, const char *desc,
case RELOPT_TYPE_BOOL:
size = sizeof(relopt_bool);
break;
+ case RELOPT_TYPE_TERNARY:
+ size = sizeof(relopt_ternary);
+ break;
case RELOPT_TYPE_INT:
size = sizeof(relopt_int);
break;
@@ -883,6 +907,54 @@ add_local_bool_reloption(local_relopts *relopts, const char *name,
add_local_reloption(relopts, (relopt_gen *) newoption, offset);
}
+/*
+ * init_ternary_reloption
+ * Allocate and initialize a new ternary reloption
+ */
+static relopt_ternary *
+init_ternary_reloption(bits32 kinds, const char *name, const char *desc,
+ ternary default_val, LOCKMODE lockmode)
+{
+ relopt_ternary *newoption;
+
+ newoption = (relopt_ternary *) allocate_reloption(kinds,
+ RELOPT_TYPE_TERNARY, name, desc, lockmode);
+ newoption->default_val = default_val;
+
+ return newoption;
+}
+
+/*
+ * add_ternary_reloption
+ * Add a new ternary reloption
+ */
+void
+add_ternary_reloption(bits32 kinds, const char *name, const char *desc,
+ ternary default_val, LOCKMODE lockmode)
+{
+ relopt_ternary *newoption = init_ternary_reloption(kinds, name, desc,
+ default_val, lockmode);
+
+ add_reloption((relopt_gen *) newoption);
+}
+
+/*
+ * add_local_ternary_reloption
+ * Add a new ternary local reloption
+ *
+ * 'offset' is offset of ternary-typed field.
+ */
+void
+add_local_ternary_reloption(local_relopts *relopts, const char *name,
+ const char *desc, ternary default_val,
+ int offset)
+{
+ relopt_ternary *newoption = init_ternary_reloption(RELOPT_KIND_LOCAL,
+ name, desc,
+ default_val, 0);
+
+ add_local_reloption(relopts, (relopt_gen *) newoption, offset);
+}
/*
* init_real_reloption
@@ -1617,6 +1689,18 @@ parse_one_reloption(relopt_value *option, char *text_str, int text_len,
option->gen->name, value)));
}
break;
+ case RELOPT_TYPE_TERNARY:
+ {
+ bool b;
+ parsed = parse_bool(value, &b);
+ option->values.ternary_val = b ? TERNARY_TRUE : TERNARY_FALSE;
+ if (validate && !parsed)
+ ereport(ERROR,
+ (errcode(ERRCODE_INVALID_PARAMETER_VALUE),
+ errmsg("invalid value for ternary option \"%s\": %s",
+ option->gen->name, value)));
+ }
+ break;
case RELOPT_TYPE_INT:
{
relopt_int *optint = (relopt_int *) option->gen;
@@ -1780,17 +1864,6 @@ fillRelOptions(void *rdopts, Size basesize,
char *itempos = ((char *) rdopts) + elems[j].offset;
char *string_val;
- /*
- * If isset_offset is provided, store whether the reloption is
- * set there.
- */
- if (elems[j].isset_offset > 0)
- {
- char *setpos = ((char *) rdopts) + elems[j].isset_offset;
-
- *(bool *) setpos = options[i].isset;
- }
-
switch (options[i].gen->type)
{
case RELOPT_TYPE_BOOL:
@@ -1798,6 +1871,11 @@ fillRelOptions(void *rdopts, Size basesize,
options[i].values.bool_val :
((relopt_bool *) options[i].gen)->default_val;
break;
+ case RELOPT_TYPE_TERNARY:
+ *(ternary *) itempos = options[i].isset ?
+ options[i].values.ternary_val :
+ ((relopt_ternary *) options[i].gen)->default_val;
+ break;
case RELOPT_TYPE_INT:
*(int *) itempos = options[i].isset ?
options[i].values.int_val :
@@ -1912,8 +1990,8 @@ default_reloptions(Datum reloptions, bool validate, relopt_kind kind)
offsetof(StdRdOptions, parallel_workers)},
{"vacuum_index_cleanup", RELOPT_TYPE_ENUM,
offsetof(StdRdOptions, vacuum_index_cleanup)},
- {"vacuum_truncate", RELOPT_TYPE_BOOL,
- offsetof(StdRdOptions, vacuum_truncate), offsetof(StdRdOptions, vacuum_truncate_set)},
+ {"vacuum_truncate", RELOPT_TYPE_TERNARY,
+ offsetof(StdRdOptions, vacuum_truncate)},
{"vacuum_max_eager_freeze_failure_rate", RELOPT_TYPE_REAL,
offsetof(StdRdOptions, vacuum_max_eager_freeze_failure_rate)}
};
@@ -1993,7 +2071,6 @@ build_local_reloptions(local_relopts *relopts, Datum options, bool validate)
elems[i].optname = opt->option->name;
elems[i].opttype = opt->option->type;
elems[i].offset = opt->offset;
- elems[i].isset_offset = 0; /* not supported for local relopts yet */
i++;
}
diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c
index 733ef40ae7..977babff54 100644
--- a/src/backend/commands/vacuum.c
+++ b/src/backend/commands/vacuum.c
@@ -2221,9 +2221,9 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams params,
{
StdRdOptions *opts = (StdRdOptions *) rel->rd_options;
- if (opts && opts->vacuum_truncate_set)
+ if (opts && opts->vacuum_truncate != TERNARY_UNSET)
{
- if (opts->vacuum_truncate)
+ if (opts->vacuum_truncate == TERNARY_TRUE)
params.truncate = VACOPTVALUE_ENABLED;
else
params.truncate = VACOPTVALUE_DISABLED;
diff --git a/src/include/access/reloptions.h b/src/include/access/reloptions.h
index a604a4702c..a436697658 100644
--- a/src/include/access/reloptions.h
+++ b/src/include/access/reloptions.h
@@ -29,6 +29,7 @@
typedef enum relopt_type
{
RELOPT_TYPE_BOOL,
+ RELOPT_TYPE_TERNARY, /* on, off, unset */
RELOPT_TYPE_INT,
RELOPT_TYPE_REAL,
RELOPT_TYPE_ENUM,
@@ -80,6 +81,7 @@ typedef struct relopt_value
union
{
bool bool_val;
+ ternary ternary_val;
int int_val;
double real_val;
int enum_val;
@@ -94,6 +96,12 @@ typedef struct relopt_bool
bool default_val;
} relopt_bool;
+typedef struct relopt_rernary
+{
+ relopt_gen gen;
+ int default_val;
+} relopt_ternary;
+
typedef struct relopt_int
{
relopt_gen gen;
@@ -152,19 +160,6 @@ typedef struct
const char *optname; /* option's name */
relopt_type opttype; /* option's datatype */
int offset; /* offset of field in result struct */
-
- /*
- * isset_offset is an optional offset of a field in the result struct that
- * stores whether the option is explicitly set for the relation or if it
- * just picked up the default value. In most cases, this can be
- * accomplished by giving the reloption a special out-of-range default
- * value (e.g., some integer reloptions use -2), but this isn't always
- * possible. For example, a Boolean reloption cannot be given an
- * out-of-range default, so we need another way to discover the source of
- * its value. This offset is only used if given a value greater than
- * zero.
- */
- int isset_offset;
} relopt_parse_elt;
/* Local reloption definition */
@@ -195,6 +190,8 @@ typedef struct local_relopts
extern relopt_kind add_reloption_kind(void);
extern void add_bool_reloption(bits32 kinds, const char *name, const char *desc,
bool default_val, LOCKMODE lockmode);
+extern void add_ternary_reloption(bits32 kinds, const char *name,
+ const char *desc, int default_val, LOCKMODE lockmode);
extern void add_int_reloption(bits32 kinds, const char *name, const char *desc,
int default_val, int min_val, int max_val,
LOCKMODE lockmode);
@@ -214,6 +211,9 @@ extern void register_reloptions_validator(local_relopts *relopts,
extern void add_local_bool_reloption(local_relopts *relopts, const char *name,
const char *desc, bool default_val,
int offset);
+extern void add_local_ternary_reloption(local_relopts *relopts,
+ const char *name, const char *desc,
+ ternary default_val, int offset);
extern void add_local_int_reloption(local_relopts *relopts, const char *name,
const char *desc, int default_val,
int min_val, int max_val, int offset);
diff --git a/src/include/c.h b/src/include/c.h
index 39022f8a9d..9c59ccccf8 100644
--- a/src/include/c.h
+++ b/src/include/c.h
@@ -488,6 +488,22 @@ typedef void (*pg_funcptr_t) (void);
#include <stdbool.h>
+/*
+ * ternary
+ * Boolean value with an extrea "unset" option
+ *
+ * Ternary data type is used in relation options that can be "true", "false" or
+ * "unset". Since relation options are used deep inside the PostgreSQL code,
+ * this type is declared globally.
+*/
+
+typedef enum ternary
+{
+ TERNARY_FALSE = 0,
+ TERNARY_TRUE = 1,
+ TERNARY_UNSET = -1
+} ternary;
+
/* ----------------------------------------------------------------
* Section 3: standard system types
diff --git a/src/include/utils/rel.h b/src/include/utils/rel.h
index b552359915..7346d618f9 100644
--- a/src/include/utils/rel.h
+++ b/src/include/utils/rel.h
@@ -346,8 +346,7 @@ typedef struct StdRdOptions
bool user_catalog_table; /* use as an additional catalog relation */
int parallel_workers; /* max number of parallel workers */
StdRdOptIndexCleanup vacuum_index_cleanup; /* controls index vacuuming */
- bool vacuum_truncate; /* enables vacuum to truncate a relation */
- bool vacuum_truncate_set; /* whether vacuum_truncate is set */
+ ternary vacuum_truncate; /* enables vacuum to truncate a relation */
/*
* Fraction of pages in a relation that vacuum can eagerly scan and fail
--
2.39.2
--nextPart4155600.3ZeAukHxDK
Content-Disposition: attachment;
filename="v2a-0003-Add-alias-to-be-used-as-unset-state.patch"
Content-Transfer-Encoding: 7Bit
Content-Type: text/x-patch; charset="unicode-2-0-utf-8";
name="v2a-0003-Add-alias-to-be-used-as-unset-state.patch"
^ permalink raw reply [nested|flat] 868+ messages in thread
* [PATCH v2 2/4] Introduce ternary reloptions
@ 2025-09-04 14:41 Nikolay Shaplov <[email protected]>
0 siblings, 0 replies; 868+ messages in thread
From: Nikolay Shaplov @ 2025-09-04 14:41 UTC (permalink / raw)
Introduce ternary reloption as a replacement for current `vacuum_truncate`
implementation. Remove `vacuum_truncate_set` additional flag and using
`TERNARY_UNSET` value instead.
---
src/backend/access/common/reloptions.c | 129 ++++++++++++++++++++-----
src/backend/commands/vacuum.c | 4 +-
src/include/access/reloptions.h | 26 ++---
src/include/c.h | 16 +++
src/include/utils/rel.h | 3 +-
5 files changed, 135 insertions(+), 43 deletions(-)
diff --git a/src/backend/access/common/reloptions.c b/src/backend/access/common/reloptions.c
index 0af3fea68f..f543f11001 100644
--- a/src/backend/access/common/reloptions.c
+++ b/src/backend/access/common/reloptions.c
@@ -40,9 +40,9 @@
*
* To add an option:
*
- * (i) decide on a type (bool, integer, real, enum, string), name, default
- * value, upper and lower bounds (if applicable); for strings, consider a
- * validation routine.
+ * (i) decide on a type (bool, ternary, integer, real, enum, string), name,
+ * default value, upper and lower bounds (if applicable); for strings,
+ * consider a validation routine.
* (ii) add a record below (or use add_<type>_reloption).
* (iii) add it to the appropriate options struct (perhaps StdRdOptions)
* (iv) add it to the appropriate handling routine (perhaps
@@ -147,15 +147,6 @@ static relopt_bool boolRelOpts[] =
},
false
},
- {
- {
- "vacuum_truncate",
- "Enables vacuum to truncate empty pages at the end of this table",
- RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
- ShareUpdateExclusiveLock
- },
- true
- },
{
{
"deduplicate_items",
@@ -170,6 +161,21 @@ static relopt_bool boolRelOpts[] =
{{NULL}}
};
+static relopt_ternary ternaryRelOpts[] =
+{
+ {
+ {
+ "vacuum_truncate",
+ "Enables vacuum to truncate empty pages at the end of this table",
+ RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
+ ShareUpdateExclusiveLock
+ },
+ TERNARY_UNSET
+ },
+ /* list terminator */
+ {{NULL}}
+};
+
static relopt_int intRelOpts[] =
{
{
@@ -600,6 +606,13 @@ initialize_reloptions(void)
boolRelOpts[i].gen.lockmode));
j++;
}
+ for (i = 0; ternaryRelOpts[i].gen.name; i++)
+ {
+ Assert(DoLockModesConflict(ternaryRelOpts[i].gen.lockmode,
+ ternaryRelOpts[i].gen.lockmode));
+ j++;
+ }
+
for (i = 0; intRelOpts[i].gen.name; i++)
{
Assert(DoLockModesConflict(intRelOpts[i].gen.lockmode,
@@ -640,6 +653,14 @@ initialize_reloptions(void)
j++;
}
+ for (i = 0; ternaryRelOpts[i].gen.name; i++)
+ {
+ relOpts[j] = &ternaryRelOpts[i].gen;
+ relOpts[j]->type = RELOPT_TYPE_TERNARY;
+ relOpts[j]->namelen = strlen(relOpts[j]->name);
+ j++;
+ }
+
for (i = 0; intRelOpts[i].gen.name; i++)
{
relOpts[j] = &intRelOpts[i].gen;
@@ -800,6 +821,9 @@ allocate_reloption(bits32 kinds, int type, const char *name, const char *desc,
case RELOPT_TYPE_BOOL:
size = sizeof(relopt_bool);
break;
+ case RELOPT_TYPE_TERNARY:
+ size = sizeof(relopt_ternary);
+ break;
case RELOPT_TYPE_INT:
size = sizeof(relopt_int);
break;
@@ -883,6 +907,54 @@ add_local_bool_reloption(local_relopts *relopts, const char *name,
add_local_reloption(relopts, (relopt_gen *) newoption, offset);
}
+/*
+ * init_ternary_reloption
+ * Allocate and initialize a new ternary reloption
+ */
+static relopt_ternary *
+init_ternary_reloption(bits32 kinds, const char *name, const char *desc,
+ ternary default_val, LOCKMODE lockmode)
+{
+ relopt_ternary *newoption;
+
+ newoption = (relopt_ternary *) allocate_reloption(kinds,
+ RELOPT_TYPE_TERNARY, name, desc, lockmode);
+ newoption->default_val = default_val;
+
+ return newoption;
+}
+
+/*
+ * add_ternary_reloption
+ * Add a new ternary reloption
+ */
+void
+add_ternary_reloption(bits32 kinds, const char *name, const char *desc,
+ ternary default_val, LOCKMODE lockmode)
+{
+ relopt_ternary *newoption = init_ternary_reloption(kinds, name, desc,
+ default_val, lockmode);
+
+ add_reloption((relopt_gen *) newoption);
+}
+
+/*
+ * add_local_ternary_reloption
+ * Add a new ternary local reloption
+ *
+ * 'offset' is offset of ternary-typed field.
+ */
+void
+add_local_ternary_reloption(local_relopts *relopts, const char *name,
+ const char *desc, ternary default_val,
+ int offset)
+{
+ relopt_ternary *newoption = init_ternary_reloption(RELOPT_KIND_LOCAL,
+ name, desc,
+ default_val, 0);
+
+ add_local_reloption(relopts, (relopt_gen *) newoption, offset);
+}
/*
* init_real_reloption
@@ -1617,6 +1689,18 @@ parse_one_reloption(relopt_value *option, char *text_str, int text_len,
option->gen->name, value)));
}
break;
+ case RELOPT_TYPE_TERNARY:
+ {
+ bool b;
+ parsed = parse_bool(value, &b);
+ option->values.ternary_val = b ? TERNARY_TRUE : TERNARY_FALSE;
+ if (validate && !parsed)
+ ereport(ERROR,
+ (errcode(ERRCODE_INVALID_PARAMETER_VALUE),
+ errmsg("invalid value for ternary option \"%s\": %s",
+ option->gen->name, value)));
+ }
+ break;
case RELOPT_TYPE_INT:
{
relopt_int *optint = (relopt_int *) option->gen;
@@ -1780,17 +1864,6 @@ fillRelOptions(void *rdopts, Size basesize,
char *itempos = ((char *) rdopts) + elems[j].offset;
char *string_val;
- /*
- * If isset_offset is provided, store whether the reloption is
- * set there.
- */
- if (elems[j].isset_offset > 0)
- {
- char *setpos = ((char *) rdopts) + elems[j].isset_offset;
-
- *(bool *) setpos = options[i].isset;
- }
-
switch (options[i].gen->type)
{
case RELOPT_TYPE_BOOL:
@@ -1798,6 +1871,11 @@ fillRelOptions(void *rdopts, Size basesize,
options[i].values.bool_val :
((relopt_bool *) options[i].gen)->default_val;
break;
+ case RELOPT_TYPE_TERNARY:
+ *(ternary *) itempos = options[i].isset ?
+ options[i].values.ternary_val :
+ ((relopt_ternary *) options[i].gen)->default_val;
+ break;
case RELOPT_TYPE_INT:
*(int *) itempos = options[i].isset ?
options[i].values.int_val :
@@ -1912,8 +1990,8 @@ default_reloptions(Datum reloptions, bool validate, relopt_kind kind)
offsetof(StdRdOptions, parallel_workers)},
{"vacuum_index_cleanup", RELOPT_TYPE_ENUM,
offsetof(StdRdOptions, vacuum_index_cleanup)},
- {"vacuum_truncate", RELOPT_TYPE_BOOL,
- offsetof(StdRdOptions, vacuum_truncate), offsetof(StdRdOptions, vacuum_truncate_set)},
+ {"vacuum_truncate", RELOPT_TYPE_TERNARY,
+ offsetof(StdRdOptions, vacuum_truncate)},
{"vacuum_max_eager_freeze_failure_rate", RELOPT_TYPE_REAL,
offsetof(StdRdOptions, vacuum_max_eager_freeze_failure_rate)}
};
@@ -1993,7 +2071,6 @@ build_local_reloptions(local_relopts *relopts, Datum options, bool validate)
elems[i].optname = opt->option->name;
elems[i].opttype = opt->option->type;
elems[i].offset = opt->offset;
- elems[i].isset_offset = 0; /* not supported for local relopts yet */
i++;
}
diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c
index 733ef40ae7..977babff54 100644
--- a/src/backend/commands/vacuum.c
+++ b/src/backend/commands/vacuum.c
@@ -2221,9 +2221,9 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams params,
{
StdRdOptions *opts = (StdRdOptions *) rel->rd_options;
- if (opts && opts->vacuum_truncate_set)
+ if (opts && opts->vacuum_truncate != TERNARY_UNSET)
{
- if (opts->vacuum_truncate)
+ if (opts->vacuum_truncate == TERNARY_TRUE)
params.truncate = VACOPTVALUE_ENABLED;
else
params.truncate = VACOPTVALUE_DISABLED;
diff --git a/src/include/access/reloptions.h b/src/include/access/reloptions.h
index a604a4702c..a436697658 100644
--- a/src/include/access/reloptions.h
+++ b/src/include/access/reloptions.h
@@ -29,6 +29,7 @@
typedef enum relopt_type
{
RELOPT_TYPE_BOOL,
+ RELOPT_TYPE_TERNARY, /* on, off, unset */
RELOPT_TYPE_INT,
RELOPT_TYPE_REAL,
RELOPT_TYPE_ENUM,
@@ -80,6 +81,7 @@ typedef struct relopt_value
union
{
bool bool_val;
+ ternary ternary_val;
int int_val;
double real_val;
int enum_val;
@@ -94,6 +96,12 @@ typedef struct relopt_bool
bool default_val;
} relopt_bool;
+typedef struct relopt_rernary
+{
+ relopt_gen gen;
+ int default_val;
+} relopt_ternary;
+
typedef struct relopt_int
{
relopt_gen gen;
@@ -152,19 +160,6 @@ typedef struct
const char *optname; /* option's name */
relopt_type opttype; /* option's datatype */
int offset; /* offset of field in result struct */
-
- /*
- * isset_offset is an optional offset of a field in the result struct that
- * stores whether the option is explicitly set for the relation or if it
- * just picked up the default value. In most cases, this can be
- * accomplished by giving the reloption a special out-of-range default
- * value (e.g., some integer reloptions use -2), but this isn't always
- * possible. For example, a Boolean reloption cannot be given an
- * out-of-range default, so we need another way to discover the source of
- * its value. This offset is only used if given a value greater than
- * zero.
- */
- int isset_offset;
} relopt_parse_elt;
/* Local reloption definition */
@@ -195,6 +190,8 @@ typedef struct local_relopts
extern relopt_kind add_reloption_kind(void);
extern void add_bool_reloption(bits32 kinds, const char *name, const char *desc,
bool default_val, LOCKMODE lockmode);
+extern void add_ternary_reloption(bits32 kinds, const char *name,
+ const char *desc, int default_val, LOCKMODE lockmode);
extern void add_int_reloption(bits32 kinds, const char *name, const char *desc,
int default_val, int min_val, int max_val,
LOCKMODE lockmode);
@@ -214,6 +211,9 @@ extern void register_reloptions_validator(local_relopts *relopts,
extern void add_local_bool_reloption(local_relopts *relopts, const char *name,
const char *desc, bool default_val,
int offset);
+extern void add_local_ternary_reloption(local_relopts *relopts,
+ const char *name, const char *desc,
+ ternary default_val, int offset);
extern void add_local_int_reloption(local_relopts *relopts, const char *name,
const char *desc, int default_val,
int min_val, int max_val, int offset);
diff --git a/src/include/c.h b/src/include/c.h
index 39022f8a9d..9c59ccccf8 100644
--- a/src/include/c.h
+++ b/src/include/c.h
@@ -488,6 +488,22 @@ typedef void (*pg_funcptr_t) (void);
#include <stdbool.h>
+/*
+ * ternary
+ * Boolean value with an extrea "unset" option
+ *
+ * Ternary data type is used in relation options that can be "true", "false" or
+ * "unset". Since relation options are used deep inside the PostgreSQL code,
+ * this type is declared globally.
+*/
+
+typedef enum ternary
+{
+ TERNARY_FALSE = 0,
+ TERNARY_TRUE = 1,
+ TERNARY_UNSET = -1
+} ternary;
+
/* ----------------------------------------------------------------
* Section 3: standard system types
diff --git a/src/include/utils/rel.h b/src/include/utils/rel.h
index b552359915..7346d618f9 100644
--- a/src/include/utils/rel.h
+++ b/src/include/utils/rel.h
@@ -346,8 +346,7 @@ typedef struct StdRdOptions
bool user_catalog_table; /* use as an additional catalog relation */
int parallel_workers; /* max number of parallel workers */
StdRdOptIndexCleanup vacuum_index_cleanup; /* controls index vacuuming */
- bool vacuum_truncate; /* enables vacuum to truncate a relation */
- bool vacuum_truncate_set; /* whether vacuum_truncate is set */
+ ternary vacuum_truncate; /* enables vacuum to truncate a relation */
/*
* Fraction of pages in a relation that vacuum can eagerly scan and fail
--
2.39.2
--nextPart5129932.5fSG56mABF
Content-Disposition: attachment;
filename="v2-0003-Add-alias-to-be-used-as-unset-state.patch"
Content-Transfer-Encoding: 7Bit
Content-Type: text/x-patch; charset="unicode-2-0-utf-8";
name="v2-0003-Add-alias-to-be-used-as-unset-state.patch"
^ permalink raw reply [nested|flat] 868+ messages in thread
* [PATCH v2a 2/4] Introduce ternary reloptions
@ 2025-09-04 14:41 Nikolay Shaplov <[email protected]>
0 siblings, 0 replies; 868+ messages in thread
From: Nikolay Shaplov @ 2025-09-04 14:41 UTC (permalink / raw)
Introduce ternary reloption as a replacement for current `vacuum_truncate`
implementation. Remove `vacuum_truncate_set` additional flag and using
`TERNARY_UNSET` value instead.
---
src/backend/access/common/reloptions.c | 129 ++++++++++++++++++++-----
src/backend/commands/vacuum.c | 4 +-
src/include/access/reloptions.h | 26 ++---
src/include/c.h | 16 +++
src/include/utils/rel.h | 3 +-
5 files changed, 135 insertions(+), 43 deletions(-)
diff --git a/src/backend/access/common/reloptions.c b/src/backend/access/common/reloptions.c
index 0af3fea68f..f543f11001 100644
--- a/src/backend/access/common/reloptions.c
+++ b/src/backend/access/common/reloptions.c
@@ -40,9 +40,9 @@
*
* To add an option:
*
- * (i) decide on a type (bool, integer, real, enum, string), name, default
- * value, upper and lower bounds (if applicable); for strings, consider a
- * validation routine.
+ * (i) decide on a type (bool, ternary, integer, real, enum, string), name,
+ * default value, upper and lower bounds (if applicable); for strings,
+ * consider a validation routine.
* (ii) add a record below (or use add_<type>_reloption).
* (iii) add it to the appropriate options struct (perhaps StdRdOptions)
* (iv) add it to the appropriate handling routine (perhaps
@@ -147,15 +147,6 @@ static relopt_bool boolRelOpts[] =
},
false
},
- {
- {
- "vacuum_truncate",
- "Enables vacuum to truncate empty pages at the end of this table",
- RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
- ShareUpdateExclusiveLock
- },
- true
- },
{
{
"deduplicate_items",
@@ -170,6 +161,21 @@ static relopt_bool boolRelOpts[] =
{{NULL}}
};
+static relopt_ternary ternaryRelOpts[] =
+{
+ {
+ {
+ "vacuum_truncate",
+ "Enables vacuum to truncate empty pages at the end of this table",
+ RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
+ ShareUpdateExclusiveLock
+ },
+ TERNARY_UNSET
+ },
+ /* list terminator */
+ {{NULL}}
+};
+
static relopt_int intRelOpts[] =
{
{
@@ -600,6 +606,13 @@ initialize_reloptions(void)
boolRelOpts[i].gen.lockmode));
j++;
}
+ for (i = 0; ternaryRelOpts[i].gen.name; i++)
+ {
+ Assert(DoLockModesConflict(ternaryRelOpts[i].gen.lockmode,
+ ternaryRelOpts[i].gen.lockmode));
+ j++;
+ }
+
for (i = 0; intRelOpts[i].gen.name; i++)
{
Assert(DoLockModesConflict(intRelOpts[i].gen.lockmode,
@@ -640,6 +653,14 @@ initialize_reloptions(void)
j++;
}
+ for (i = 0; ternaryRelOpts[i].gen.name; i++)
+ {
+ relOpts[j] = &ternaryRelOpts[i].gen;
+ relOpts[j]->type = RELOPT_TYPE_TERNARY;
+ relOpts[j]->namelen = strlen(relOpts[j]->name);
+ j++;
+ }
+
for (i = 0; intRelOpts[i].gen.name; i++)
{
relOpts[j] = &intRelOpts[i].gen;
@@ -800,6 +821,9 @@ allocate_reloption(bits32 kinds, int type, const char *name, const char *desc,
case RELOPT_TYPE_BOOL:
size = sizeof(relopt_bool);
break;
+ case RELOPT_TYPE_TERNARY:
+ size = sizeof(relopt_ternary);
+ break;
case RELOPT_TYPE_INT:
size = sizeof(relopt_int);
break;
@@ -883,6 +907,54 @@ add_local_bool_reloption(local_relopts *relopts, const char *name,
add_local_reloption(relopts, (relopt_gen *) newoption, offset);
}
+/*
+ * init_ternary_reloption
+ * Allocate and initialize a new ternary reloption
+ */
+static relopt_ternary *
+init_ternary_reloption(bits32 kinds, const char *name, const char *desc,
+ ternary default_val, LOCKMODE lockmode)
+{
+ relopt_ternary *newoption;
+
+ newoption = (relopt_ternary *) allocate_reloption(kinds,
+ RELOPT_TYPE_TERNARY, name, desc, lockmode);
+ newoption->default_val = default_val;
+
+ return newoption;
+}
+
+/*
+ * add_ternary_reloption
+ * Add a new ternary reloption
+ */
+void
+add_ternary_reloption(bits32 kinds, const char *name, const char *desc,
+ ternary default_val, LOCKMODE lockmode)
+{
+ relopt_ternary *newoption = init_ternary_reloption(kinds, name, desc,
+ default_val, lockmode);
+
+ add_reloption((relopt_gen *) newoption);
+}
+
+/*
+ * add_local_ternary_reloption
+ * Add a new ternary local reloption
+ *
+ * 'offset' is offset of ternary-typed field.
+ */
+void
+add_local_ternary_reloption(local_relopts *relopts, const char *name,
+ const char *desc, ternary default_val,
+ int offset)
+{
+ relopt_ternary *newoption = init_ternary_reloption(RELOPT_KIND_LOCAL,
+ name, desc,
+ default_val, 0);
+
+ add_local_reloption(relopts, (relopt_gen *) newoption, offset);
+}
/*
* init_real_reloption
@@ -1617,6 +1689,18 @@ parse_one_reloption(relopt_value *option, char *text_str, int text_len,
option->gen->name, value)));
}
break;
+ case RELOPT_TYPE_TERNARY:
+ {
+ bool b;
+ parsed = parse_bool(value, &b);
+ option->values.ternary_val = b ? TERNARY_TRUE : TERNARY_FALSE;
+ if (validate && !parsed)
+ ereport(ERROR,
+ (errcode(ERRCODE_INVALID_PARAMETER_VALUE),
+ errmsg("invalid value for ternary option \"%s\": %s",
+ option->gen->name, value)));
+ }
+ break;
case RELOPT_TYPE_INT:
{
relopt_int *optint = (relopt_int *) option->gen;
@@ -1780,17 +1864,6 @@ fillRelOptions(void *rdopts, Size basesize,
char *itempos = ((char *) rdopts) + elems[j].offset;
char *string_val;
- /*
- * If isset_offset is provided, store whether the reloption is
- * set there.
- */
- if (elems[j].isset_offset > 0)
- {
- char *setpos = ((char *) rdopts) + elems[j].isset_offset;
-
- *(bool *) setpos = options[i].isset;
- }
-
switch (options[i].gen->type)
{
case RELOPT_TYPE_BOOL:
@@ -1798,6 +1871,11 @@ fillRelOptions(void *rdopts, Size basesize,
options[i].values.bool_val :
((relopt_bool *) options[i].gen)->default_val;
break;
+ case RELOPT_TYPE_TERNARY:
+ *(ternary *) itempos = options[i].isset ?
+ options[i].values.ternary_val :
+ ((relopt_ternary *) options[i].gen)->default_val;
+ break;
case RELOPT_TYPE_INT:
*(int *) itempos = options[i].isset ?
options[i].values.int_val :
@@ -1912,8 +1990,8 @@ default_reloptions(Datum reloptions, bool validate, relopt_kind kind)
offsetof(StdRdOptions, parallel_workers)},
{"vacuum_index_cleanup", RELOPT_TYPE_ENUM,
offsetof(StdRdOptions, vacuum_index_cleanup)},
- {"vacuum_truncate", RELOPT_TYPE_BOOL,
- offsetof(StdRdOptions, vacuum_truncate), offsetof(StdRdOptions, vacuum_truncate_set)},
+ {"vacuum_truncate", RELOPT_TYPE_TERNARY,
+ offsetof(StdRdOptions, vacuum_truncate)},
{"vacuum_max_eager_freeze_failure_rate", RELOPT_TYPE_REAL,
offsetof(StdRdOptions, vacuum_max_eager_freeze_failure_rate)}
};
@@ -1993,7 +2071,6 @@ build_local_reloptions(local_relopts *relopts, Datum options, bool validate)
elems[i].optname = opt->option->name;
elems[i].opttype = opt->option->type;
elems[i].offset = opt->offset;
- elems[i].isset_offset = 0; /* not supported for local relopts yet */
i++;
}
diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c
index 733ef40ae7..977babff54 100644
--- a/src/backend/commands/vacuum.c
+++ b/src/backend/commands/vacuum.c
@@ -2221,9 +2221,9 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams params,
{
StdRdOptions *opts = (StdRdOptions *) rel->rd_options;
- if (opts && opts->vacuum_truncate_set)
+ if (opts && opts->vacuum_truncate != TERNARY_UNSET)
{
- if (opts->vacuum_truncate)
+ if (opts->vacuum_truncate == TERNARY_TRUE)
params.truncate = VACOPTVALUE_ENABLED;
else
params.truncate = VACOPTVALUE_DISABLED;
diff --git a/src/include/access/reloptions.h b/src/include/access/reloptions.h
index a604a4702c..a436697658 100644
--- a/src/include/access/reloptions.h
+++ b/src/include/access/reloptions.h
@@ -29,6 +29,7 @@
typedef enum relopt_type
{
RELOPT_TYPE_BOOL,
+ RELOPT_TYPE_TERNARY, /* on, off, unset */
RELOPT_TYPE_INT,
RELOPT_TYPE_REAL,
RELOPT_TYPE_ENUM,
@@ -80,6 +81,7 @@ typedef struct relopt_value
union
{
bool bool_val;
+ ternary ternary_val;
int int_val;
double real_val;
int enum_val;
@@ -94,6 +96,12 @@ typedef struct relopt_bool
bool default_val;
} relopt_bool;
+typedef struct relopt_rernary
+{
+ relopt_gen gen;
+ int default_val;
+} relopt_ternary;
+
typedef struct relopt_int
{
relopt_gen gen;
@@ -152,19 +160,6 @@ typedef struct
const char *optname; /* option's name */
relopt_type opttype; /* option's datatype */
int offset; /* offset of field in result struct */
-
- /*
- * isset_offset is an optional offset of a field in the result struct that
- * stores whether the option is explicitly set for the relation or if it
- * just picked up the default value. In most cases, this can be
- * accomplished by giving the reloption a special out-of-range default
- * value (e.g., some integer reloptions use -2), but this isn't always
- * possible. For example, a Boolean reloption cannot be given an
- * out-of-range default, so we need another way to discover the source of
- * its value. This offset is only used if given a value greater than
- * zero.
- */
- int isset_offset;
} relopt_parse_elt;
/* Local reloption definition */
@@ -195,6 +190,8 @@ typedef struct local_relopts
extern relopt_kind add_reloption_kind(void);
extern void add_bool_reloption(bits32 kinds, const char *name, const char *desc,
bool default_val, LOCKMODE lockmode);
+extern void add_ternary_reloption(bits32 kinds, const char *name,
+ const char *desc, int default_val, LOCKMODE lockmode);
extern void add_int_reloption(bits32 kinds, const char *name, const char *desc,
int default_val, int min_val, int max_val,
LOCKMODE lockmode);
@@ -214,6 +211,9 @@ extern void register_reloptions_validator(local_relopts *relopts,
extern void add_local_bool_reloption(local_relopts *relopts, const char *name,
const char *desc, bool default_val,
int offset);
+extern void add_local_ternary_reloption(local_relopts *relopts,
+ const char *name, const char *desc,
+ ternary default_val, int offset);
extern void add_local_int_reloption(local_relopts *relopts, const char *name,
const char *desc, int default_val,
int min_val, int max_val, int offset);
diff --git a/src/include/c.h b/src/include/c.h
index 39022f8a9d..9c59ccccf8 100644
--- a/src/include/c.h
+++ b/src/include/c.h
@@ -488,6 +488,22 @@ typedef void (*pg_funcptr_t) (void);
#include <stdbool.h>
+/*
+ * ternary
+ * Boolean value with an extrea "unset" option
+ *
+ * Ternary data type is used in relation options that can be "true", "false" or
+ * "unset". Since relation options are used deep inside the PostgreSQL code,
+ * this type is declared globally.
+*/
+
+typedef enum ternary
+{
+ TERNARY_FALSE = 0,
+ TERNARY_TRUE = 1,
+ TERNARY_UNSET = -1
+} ternary;
+
/* ----------------------------------------------------------------
* Section 3: standard system types
diff --git a/src/include/utils/rel.h b/src/include/utils/rel.h
index b552359915..7346d618f9 100644
--- a/src/include/utils/rel.h
+++ b/src/include/utils/rel.h
@@ -346,8 +346,7 @@ typedef struct StdRdOptions
bool user_catalog_table; /* use as an additional catalog relation */
int parallel_workers; /* max number of parallel workers */
StdRdOptIndexCleanup vacuum_index_cleanup; /* controls index vacuuming */
- bool vacuum_truncate; /* enables vacuum to truncate a relation */
- bool vacuum_truncate_set; /* whether vacuum_truncate is set */
+ ternary vacuum_truncate; /* enables vacuum to truncate a relation */
/*
* Fraction of pages in a relation that vacuum can eagerly scan and fail
--
2.39.2
--nextPart4155600.3ZeAukHxDK
Content-Disposition: attachment;
filename="v2a-0003-Add-alias-to-be-used-as-unset-state.patch"
Content-Transfer-Encoding: 7Bit
Content-Type: text/x-patch; charset="unicode-2-0-utf-8";
name="v2a-0003-Add-alias-to-be-used-as-unset-state.patch"
^ permalink raw reply [nested|flat] 868+ messages in thread
* [PATCH v2a 2/4] Introduce ternary reloptions
@ 2025-09-04 14:41 Nikolay Shaplov <[email protected]>
0 siblings, 0 replies; 868+ messages in thread
From: Nikolay Shaplov @ 2025-09-04 14:41 UTC (permalink / raw)
Introduce ternary reloption as a replacement for current `vacuum_truncate`
implementation. Remove `vacuum_truncate_set` additional flag and using
`TERNARY_UNSET` value instead.
---
src/backend/access/common/reloptions.c | 129 ++++++++++++++++++++-----
src/backend/commands/vacuum.c | 4 +-
src/include/access/reloptions.h | 26 ++---
src/include/c.h | 16 +++
src/include/utils/rel.h | 3 +-
5 files changed, 135 insertions(+), 43 deletions(-)
diff --git a/src/backend/access/common/reloptions.c b/src/backend/access/common/reloptions.c
index 0af3fea68f..f543f11001 100644
--- a/src/backend/access/common/reloptions.c
+++ b/src/backend/access/common/reloptions.c
@@ -40,9 +40,9 @@
*
* To add an option:
*
- * (i) decide on a type (bool, integer, real, enum, string), name, default
- * value, upper and lower bounds (if applicable); for strings, consider a
- * validation routine.
+ * (i) decide on a type (bool, ternary, integer, real, enum, string), name,
+ * default value, upper and lower bounds (if applicable); for strings,
+ * consider a validation routine.
* (ii) add a record below (or use add_<type>_reloption).
* (iii) add it to the appropriate options struct (perhaps StdRdOptions)
* (iv) add it to the appropriate handling routine (perhaps
@@ -147,15 +147,6 @@ static relopt_bool boolRelOpts[] =
},
false
},
- {
- {
- "vacuum_truncate",
- "Enables vacuum to truncate empty pages at the end of this table",
- RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
- ShareUpdateExclusiveLock
- },
- true
- },
{
{
"deduplicate_items",
@@ -170,6 +161,21 @@ static relopt_bool boolRelOpts[] =
{{NULL}}
};
+static relopt_ternary ternaryRelOpts[] =
+{
+ {
+ {
+ "vacuum_truncate",
+ "Enables vacuum to truncate empty pages at the end of this table",
+ RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
+ ShareUpdateExclusiveLock
+ },
+ TERNARY_UNSET
+ },
+ /* list terminator */
+ {{NULL}}
+};
+
static relopt_int intRelOpts[] =
{
{
@@ -600,6 +606,13 @@ initialize_reloptions(void)
boolRelOpts[i].gen.lockmode));
j++;
}
+ for (i = 0; ternaryRelOpts[i].gen.name; i++)
+ {
+ Assert(DoLockModesConflict(ternaryRelOpts[i].gen.lockmode,
+ ternaryRelOpts[i].gen.lockmode));
+ j++;
+ }
+
for (i = 0; intRelOpts[i].gen.name; i++)
{
Assert(DoLockModesConflict(intRelOpts[i].gen.lockmode,
@@ -640,6 +653,14 @@ initialize_reloptions(void)
j++;
}
+ for (i = 0; ternaryRelOpts[i].gen.name; i++)
+ {
+ relOpts[j] = &ternaryRelOpts[i].gen;
+ relOpts[j]->type = RELOPT_TYPE_TERNARY;
+ relOpts[j]->namelen = strlen(relOpts[j]->name);
+ j++;
+ }
+
for (i = 0; intRelOpts[i].gen.name; i++)
{
relOpts[j] = &intRelOpts[i].gen;
@@ -800,6 +821,9 @@ allocate_reloption(bits32 kinds, int type, const char *name, const char *desc,
case RELOPT_TYPE_BOOL:
size = sizeof(relopt_bool);
break;
+ case RELOPT_TYPE_TERNARY:
+ size = sizeof(relopt_ternary);
+ break;
case RELOPT_TYPE_INT:
size = sizeof(relopt_int);
break;
@@ -883,6 +907,54 @@ add_local_bool_reloption(local_relopts *relopts, const char *name,
add_local_reloption(relopts, (relopt_gen *) newoption, offset);
}
+/*
+ * init_ternary_reloption
+ * Allocate and initialize a new ternary reloption
+ */
+static relopt_ternary *
+init_ternary_reloption(bits32 kinds, const char *name, const char *desc,
+ ternary default_val, LOCKMODE lockmode)
+{
+ relopt_ternary *newoption;
+
+ newoption = (relopt_ternary *) allocate_reloption(kinds,
+ RELOPT_TYPE_TERNARY, name, desc, lockmode);
+ newoption->default_val = default_val;
+
+ return newoption;
+}
+
+/*
+ * add_ternary_reloption
+ * Add a new ternary reloption
+ */
+void
+add_ternary_reloption(bits32 kinds, const char *name, const char *desc,
+ ternary default_val, LOCKMODE lockmode)
+{
+ relopt_ternary *newoption = init_ternary_reloption(kinds, name, desc,
+ default_val, lockmode);
+
+ add_reloption((relopt_gen *) newoption);
+}
+
+/*
+ * add_local_ternary_reloption
+ * Add a new ternary local reloption
+ *
+ * 'offset' is offset of ternary-typed field.
+ */
+void
+add_local_ternary_reloption(local_relopts *relopts, const char *name,
+ const char *desc, ternary default_val,
+ int offset)
+{
+ relopt_ternary *newoption = init_ternary_reloption(RELOPT_KIND_LOCAL,
+ name, desc,
+ default_val, 0);
+
+ add_local_reloption(relopts, (relopt_gen *) newoption, offset);
+}
/*
* init_real_reloption
@@ -1617,6 +1689,18 @@ parse_one_reloption(relopt_value *option, char *text_str, int text_len,
option->gen->name, value)));
}
break;
+ case RELOPT_TYPE_TERNARY:
+ {
+ bool b;
+ parsed = parse_bool(value, &b);
+ option->values.ternary_val = b ? TERNARY_TRUE : TERNARY_FALSE;
+ if (validate && !parsed)
+ ereport(ERROR,
+ (errcode(ERRCODE_INVALID_PARAMETER_VALUE),
+ errmsg("invalid value for ternary option \"%s\": %s",
+ option->gen->name, value)));
+ }
+ break;
case RELOPT_TYPE_INT:
{
relopt_int *optint = (relopt_int *) option->gen;
@@ -1780,17 +1864,6 @@ fillRelOptions(void *rdopts, Size basesize,
char *itempos = ((char *) rdopts) + elems[j].offset;
char *string_val;
- /*
- * If isset_offset is provided, store whether the reloption is
- * set there.
- */
- if (elems[j].isset_offset > 0)
- {
- char *setpos = ((char *) rdopts) + elems[j].isset_offset;
-
- *(bool *) setpos = options[i].isset;
- }
-
switch (options[i].gen->type)
{
case RELOPT_TYPE_BOOL:
@@ -1798,6 +1871,11 @@ fillRelOptions(void *rdopts, Size basesize,
options[i].values.bool_val :
((relopt_bool *) options[i].gen)->default_val;
break;
+ case RELOPT_TYPE_TERNARY:
+ *(ternary *) itempos = options[i].isset ?
+ options[i].values.ternary_val :
+ ((relopt_ternary *) options[i].gen)->default_val;
+ break;
case RELOPT_TYPE_INT:
*(int *) itempos = options[i].isset ?
options[i].values.int_val :
@@ -1912,8 +1990,8 @@ default_reloptions(Datum reloptions, bool validate, relopt_kind kind)
offsetof(StdRdOptions, parallel_workers)},
{"vacuum_index_cleanup", RELOPT_TYPE_ENUM,
offsetof(StdRdOptions, vacuum_index_cleanup)},
- {"vacuum_truncate", RELOPT_TYPE_BOOL,
- offsetof(StdRdOptions, vacuum_truncate), offsetof(StdRdOptions, vacuum_truncate_set)},
+ {"vacuum_truncate", RELOPT_TYPE_TERNARY,
+ offsetof(StdRdOptions, vacuum_truncate)},
{"vacuum_max_eager_freeze_failure_rate", RELOPT_TYPE_REAL,
offsetof(StdRdOptions, vacuum_max_eager_freeze_failure_rate)}
};
@@ -1993,7 +2071,6 @@ build_local_reloptions(local_relopts *relopts, Datum options, bool validate)
elems[i].optname = opt->option->name;
elems[i].opttype = opt->option->type;
elems[i].offset = opt->offset;
- elems[i].isset_offset = 0; /* not supported for local relopts yet */
i++;
}
diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c
index 733ef40ae7..977babff54 100644
--- a/src/backend/commands/vacuum.c
+++ b/src/backend/commands/vacuum.c
@@ -2221,9 +2221,9 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams params,
{
StdRdOptions *opts = (StdRdOptions *) rel->rd_options;
- if (opts && opts->vacuum_truncate_set)
+ if (opts && opts->vacuum_truncate != TERNARY_UNSET)
{
- if (opts->vacuum_truncate)
+ if (opts->vacuum_truncate == TERNARY_TRUE)
params.truncate = VACOPTVALUE_ENABLED;
else
params.truncate = VACOPTVALUE_DISABLED;
diff --git a/src/include/access/reloptions.h b/src/include/access/reloptions.h
index a604a4702c..a436697658 100644
--- a/src/include/access/reloptions.h
+++ b/src/include/access/reloptions.h
@@ -29,6 +29,7 @@
typedef enum relopt_type
{
RELOPT_TYPE_BOOL,
+ RELOPT_TYPE_TERNARY, /* on, off, unset */
RELOPT_TYPE_INT,
RELOPT_TYPE_REAL,
RELOPT_TYPE_ENUM,
@@ -80,6 +81,7 @@ typedef struct relopt_value
union
{
bool bool_val;
+ ternary ternary_val;
int int_val;
double real_val;
int enum_val;
@@ -94,6 +96,12 @@ typedef struct relopt_bool
bool default_val;
} relopt_bool;
+typedef struct relopt_rernary
+{
+ relopt_gen gen;
+ int default_val;
+} relopt_ternary;
+
typedef struct relopt_int
{
relopt_gen gen;
@@ -152,19 +160,6 @@ typedef struct
const char *optname; /* option's name */
relopt_type opttype; /* option's datatype */
int offset; /* offset of field in result struct */
-
- /*
- * isset_offset is an optional offset of a field in the result struct that
- * stores whether the option is explicitly set for the relation or if it
- * just picked up the default value. In most cases, this can be
- * accomplished by giving the reloption a special out-of-range default
- * value (e.g., some integer reloptions use -2), but this isn't always
- * possible. For example, a Boolean reloption cannot be given an
- * out-of-range default, so we need another way to discover the source of
- * its value. This offset is only used if given a value greater than
- * zero.
- */
- int isset_offset;
} relopt_parse_elt;
/* Local reloption definition */
@@ -195,6 +190,8 @@ typedef struct local_relopts
extern relopt_kind add_reloption_kind(void);
extern void add_bool_reloption(bits32 kinds, const char *name, const char *desc,
bool default_val, LOCKMODE lockmode);
+extern void add_ternary_reloption(bits32 kinds, const char *name,
+ const char *desc, int default_val, LOCKMODE lockmode);
extern void add_int_reloption(bits32 kinds, const char *name, const char *desc,
int default_val, int min_val, int max_val,
LOCKMODE lockmode);
@@ -214,6 +211,9 @@ extern void register_reloptions_validator(local_relopts *relopts,
extern void add_local_bool_reloption(local_relopts *relopts, const char *name,
const char *desc, bool default_val,
int offset);
+extern void add_local_ternary_reloption(local_relopts *relopts,
+ const char *name, const char *desc,
+ ternary default_val, int offset);
extern void add_local_int_reloption(local_relopts *relopts, const char *name,
const char *desc, int default_val,
int min_val, int max_val, int offset);
diff --git a/src/include/c.h b/src/include/c.h
index 39022f8a9d..9c59ccccf8 100644
--- a/src/include/c.h
+++ b/src/include/c.h
@@ -488,6 +488,22 @@ typedef void (*pg_funcptr_t) (void);
#include <stdbool.h>
+/*
+ * ternary
+ * Boolean value with an extrea "unset" option
+ *
+ * Ternary data type is used in relation options that can be "true", "false" or
+ * "unset". Since relation options are used deep inside the PostgreSQL code,
+ * this type is declared globally.
+*/
+
+typedef enum ternary
+{
+ TERNARY_FALSE = 0,
+ TERNARY_TRUE = 1,
+ TERNARY_UNSET = -1
+} ternary;
+
/* ----------------------------------------------------------------
* Section 3: standard system types
diff --git a/src/include/utils/rel.h b/src/include/utils/rel.h
index b552359915..7346d618f9 100644
--- a/src/include/utils/rel.h
+++ b/src/include/utils/rel.h
@@ -346,8 +346,7 @@ typedef struct StdRdOptions
bool user_catalog_table; /* use as an additional catalog relation */
int parallel_workers; /* max number of parallel workers */
StdRdOptIndexCleanup vacuum_index_cleanup; /* controls index vacuuming */
- bool vacuum_truncate; /* enables vacuum to truncate a relation */
- bool vacuum_truncate_set; /* whether vacuum_truncate is set */
+ ternary vacuum_truncate; /* enables vacuum to truncate a relation */
/*
* Fraction of pages in a relation that vacuum can eagerly scan and fail
--
2.39.2
--nextPart4155600.3ZeAukHxDK
Content-Disposition: attachment;
filename="v2a-0003-Add-alias-to-be-used-as-unset-state.patch"
Content-Transfer-Encoding: 7Bit
Content-Type: text/x-patch; charset="unicode-2-0-utf-8";
name="v2a-0003-Add-alias-to-be-used-as-unset-state.patch"
^ permalink raw reply [nested|flat] 868+ messages in thread
* [PATCH v2a 2/4] Introduce ternary reloptions
@ 2025-09-04 14:41 Nikolay Shaplov <[email protected]>
0 siblings, 0 replies; 868+ messages in thread
From: Nikolay Shaplov @ 2025-09-04 14:41 UTC (permalink / raw)
Introduce ternary reloption as a replacement for current `vacuum_truncate`
implementation. Remove `vacuum_truncate_set` additional flag and using
`TERNARY_UNSET` value instead.
---
src/backend/access/common/reloptions.c | 129 ++++++++++++++++++++-----
src/backend/commands/vacuum.c | 4 +-
src/include/access/reloptions.h | 26 ++---
src/include/c.h | 16 +++
src/include/utils/rel.h | 3 +-
5 files changed, 135 insertions(+), 43 deletions(-)
diff --git a/src/backend/access/common/reloptions.c b/src/backend/access/common/reloptions.c
index 0af3fea68f..f543f11001 100644
--- a/src/backend/access/common/reloptions.c
+++ b/src/backend/access/common/reloptions.c
@@ -40,9 +40,9 @@
*
* To add an option:
*
- * (i) decide on a type (bool, integer, real, enum, string), name, default
- * value, upper and lower bounds (if applicable); for strings, consider a
- * validation routine.
+ * (i) decide on a type (bool, ternary, integer, real, enum, string), name,
+ * default value, upper and lower bounds (if applicable); for strings,
+ * consider a validation routine.
* (ii) add a record below (or use add_<type>_reloption).
* (iii) add it to the appropriate options struct (perhaps StdRdOptions)
* (iv) add it to the appropriate handling routine (perhaps
@@ -147,15 +147,6 @@ static relopt_bool boolRelOpts[] =
},
false
},
- {
- {
- "vacuum_truncate",
- "Enables vacuum to truncate empty pages at the end of this table",
- RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
- ShareUpdateExclusiveLock
- },
- true
- },
{
{
"deduplicate_items",
@@ -170,6 +161,21 @@ static relopt_bool boolRelOpts[] =
{{NULL}}
};
+static relopt_ternary ternaryRelOpts[] =
+{
+ {
+ {
+ "vacuum_truncate",
+ "Enables vacuum to truncate empty pages at the end of this table",
+ RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
+ ShareUpdateExclusiveLock
+ },
+ TERNARY_UNSET
+ },
+ /* list terminator */
+ {{NULL}}
+};
+
static relopt_int intRelOpts[] =
{
{
@@ -600,6 +606,13 @@ initialize_reloptions(void)
boolRelOpts[i].gen.lockmode));
j++;
}
+ for (i = 0; ternaryRelOpts[i].gen.name; i++)
+ {
+ Assert(DoLockModesConflict(ternaryRelOpts[i].gen.lockmode,
+ ternaryRelOpts[i].gen.lockmode));
+ j++;
+ }
+
for (i = 0; intRelOpts[i].gen.name; i++)
{
Assert(DoLockModesConflict(intRelOpts[i].gen.lockmode,
@@ -640,6 +653,14 @@ initialize_reloptions(void)
j++;
}
+ for (i = 0; ternaryRelOpts[i].gen.name; i++)
+ {
+ relOpts[j] = &ternaryRelOpts[i].gen;
+ relOpts[j]->type = RELOPT_TYPE_TERNARY;
+ relOpts[j]->namelen = strlen(relOpts[j]->name);
+ j++;
+ }
+
for (i = 0; intRelOpts[i].gen.name; i++)
{
relOpts[j] = &intRelOpts[i].gen;
@@ -800,6 +821,9 @@ allocate_reloption(bits32 kinds, int type, const char *name, const char *desc,
case RELOPT_TYPE_BOOL:
size = sizeof(relopt_bool);
break;
+ case RELOPT_TYPE_TERNARY:
+ size = sizeof(relopt_ternary);
+ break;
case RELOPT_TYPE_INT:
size = sizeof(relopt_int);
break;
@@ -883,6 +907,54 @@ add_local_bool_reloption(local_relopts *relopts, const char *name,
add_local_reloption(relopts, (relopt_gen *) newoption, offset);
}
+/*
+ * init_ternary_reloption
+ * Allocate and initialize a new ternary reloption
+ */
+static relopt_ternary *
+init_ternary_reloption(bits32 kinds, const char *name, const char *desc,
+ ternary default_val, LOCKMODE lockmode)
+{
+ relopt_ternary *newoption;
+
+ newoption = (relopt_ternary *) allocate_reloption(kinds,
+ RELOPT_TYPE_TERNARY, name, desc, lockmode);
+ newoption->default_val = default_val;
+
+ return newoption;
+}
+
+/*
+ * add_ternary_reloption
+ * Add a new ternary reloption
+ */
+void
+add_ternary_reloption(bits32 kinds, const char *name, const char *desc,
+ ternary default_val, LOCKMODE lockmode)
+{
+ relopt_ternary *newoption = init_ternary_reloption(kinds, name, desc,
+ default_val, lockmode);
+
+ add_reloption((relopt_gen *) newoption);
+}
+
+/*
+ * add_local_ternary_reloption
+ * Add a new ternary local reloption
+ *
+ * 'offset' is offset of ternary-typed field.
+ */
+void
+add_local_ternary_reloption(local_relopts *relopts, const char *name,
+ const char *desc, ternary default_val,
+ int offset)
+{
+ relopt_ternary *newoption = init_ternary_reloption(RELOPT_KIND_LOCAL,
+ name, desc,
+ default_val, 0);
+
+ add_local_reloption(relopts, (relopt_gen *) newoption, offset);
+}
/*
* init_real_reloption
@@ -1617,6 +1689,18 @@ parse_one_reloption(relopt_value *option, char *text_str, int text_len,
option->gen->name, value)));
}
break;
+ case RELOPT_TYPE_TERNARY:
+ {
+ bool b;
+ parsed = parse_bool(value, &b);
+ option->values.ternary_val = b ? TERNARY_TRUE : TERNARY_FALSE;
+ if (validate && !parsed)
+ ereport(ERROR,
+ (errcode(ERRCODE_INVALID_PARAMETER_VALUE),
+ errmsg("invalid value for ternary option \"%s\": %s",
+ option->gen->name, value)));
+ }
+ break;
case RELOPT_TYPE_INT:
{
relopt_int *optint = (relopt_int *) option->gen;
@@ -1780,17 +1864,6 @@ fillRelOptions(void *rdopts, Size basesize,
char *itempos = ((char *) rdopts) + elems[j].offset;
char *string_val;
- /*
- * If isset_offset is provided, store whether the reloption is
- * set there.
- */
- if (elems[j].isset_offset > 0)
- {
- char *setpos = ((char *) rdopts) + elems[j].isset_offset;
-
- *(bool *) setpos = options[i].isset;
- }
-
switch (options[i].gen->type)
{
case RELOPT_TYPE_BOOL:
@@ -1798,6 +1871,11 @@ fillRelOptions(void *rdopts, Size basesize,
options[i].values.bool_val :
((relopt_bool *) options[i].gen)->default_val;
break;
+ case RELOPT_TYPE_TERNARY:
+ *(ternary *) itempos = options[i].isset ?
+ options[i].values.ternary_val :
+ ((relopt_ternary *) options[i].gen)->default_val;
+ break;
case RELOPT_TYPE_INT:
*(int *) itempos = options[i].isset ?
options[i].values.int_val :
@@ -1912,8 +1990,8 @@ default_reloptions(Datum reloptions, bool validate, relopt_kind kind)
offsetof(StdRdOptions, parallel_workers)},
{"vacuum_index_cleanup", RELOPT_TYPE_ENUM,
offsetof(StdRdOptions, vacuum_index_cleanup)},
- {"vacuum_truncate", RELOPT_TYPE_BOOL,
- offsetof(StdRdOptions, vacuum_truncate), offsetof(StdRdOptions, vacuum_truncate_set)},
+ {"vacuum_truncate", RELOPT_TYPE_TERNARY,
+ offsetof(StdRdOptions, vacuum_truncate)},
{"vacuum_max_eager_freeze_failure_rate", RELOPT_TYPE_REAL,
offsetof(StdRdOptions, vacuum_max_eager_freeze_failure_rate)}
};
@@ -1993,7 +2071,6 @@ build_local_reloptions(local_relopts *relopts, Datum options, bool validate)
elems[i].optname = opt->option->name;
elems[i].opttype = opt->option->type;
elems[i].offset = opt->offset;
- elems[i].isset_offset = 0; /* not supported for local relopts yet */
i++;
}
diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c
index 733ef40ae7..977babff54 100644
--- a/src/backend/commands/vacuum.c
+++ b/src/backend/commands/vacuum.c
@@ -2221,9 +2221,9 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams params,
{
StdRdOptions *opts = (StdRdOptions *) rel->rd_options;
- if (opts && opts->vacuum_truncate_set)
+ if (opts && opts->vacuum_truncate != TERNARY_UNSET)
{
- if (opts->vacuum_truncate)
+ if (opts->vacuum_truncate == TERNARY_TRUE)
params.truncate = VACOPTVALUE_ENABLED;
else
params.truncate = VACOPTVALUE_DISABLED;
diff --git a/src/include/access/reloptions.h b/src/include/access/reloptions.h
index a604a4702c..a436697658 100644
--- a/src/include/access/reloptions.h
+++ b/src/include/access/reloptions.h
@@ -29,6 +29,7 @@
typedef enum relopt_type
{
RELOPT_TYPE_BOOL,
+ RELOPT_TYPE_TERNARY, /* on, off, unset */
RELOPT_TYPE_INT,
RELOPT_TYPE_REAL,
RELOPT_TYPE_ENUM,
@@ -80,6 +81,7 @@ typedef struct relopt_value
union
{
bool bool_val;
+ ternary ternary_val;
int int_val;
double real_val;
int enum_val;
@@ -94,6 +96,12 @@ typedef struct relopt_bool
bool default_val;
} relopt_bool;
+typedef struct relopt_rernary
+{
+ relopt_gen gen;
+ int default_val;
+} relopt_ternary;
+
typedef struct relopt_int
{
relopt_gen gen;
@@ -152,19 +160,6 @@ typedef struct
const char *optname; /* option's name */
relopt_type opttype; /* option's datatype */
int offset; /* offset of field in result struct */
-
- /*
- * isset_offset is an optional offset of a field in the result struct that
- * stores whether the option is explicitly set for the relation or if it
- * just picked up the default value. In most cases, this can be
- * accomplished by giving the reloption a special out-of-range default
- * value (e.g., some integer reloptions use -2), but this isn't always
- * possible. For example, a Boolean reloption cannot be given an
- * out-of-range default, so we need another way to discover the source of
- * its value. This offset is only used if given a value greater than
- * zero.
- */
- int isset_offset;
} relopt_parse_elt;
/* Local reloption definition */
@@ -195,6 +190,8 @@ typedef struct local_relopts
extern relopt_kind add_reloption_kind(void);
extern void add_bool_reloption(bits32 kinds, const char *name, const char *desc,
bool default_val, LOCKMODE lockmode);
+extern void add_ternary_reloption(bits32 kinds, const char *name,
+ const char *desc, int default_val, LOCKMODE lockmode);
extern void add_int_reloption(bits32 kinds, const char *name, const char *desc,
int default_val, int min_val, int max_val,
LOCKMODE lockmode);
@@ -214,6 +211,9 @@ extern void register_reloptions_validator(local_relopts *relopts,
extern void add_local_bool_reloption(local_relopts *relopts, const char *name,
const char *desc, bool default_val,
int offset);
+extern void add_local_ternary_reloption(local_relopts *relopts,
+ const char *name, const char *desc,
+ ternary default_val, int offset);
extern void add_local_int_reloption(local_relopts *relopts, const char *name,
const char *desc, int default_val,
int min_val, int max_val, int offset);
diff --git a/src/include/c.h b/src/include/c.h
index 39022f8a9d..9c59ccccf8 100644
--- a/src/include/c.h
+++ b/src/include/c.h
@@ -488,6 +488,22 @@ typedef void (*pg_funcptr_t) (void);
#include <stdbool.h>
+/*
+ * ternary
+ * Boolean value with an extrea "unset" option
+ *
+ * Ternary data type is used in relation options that can be "true", "false" or
+ * "unset". Since relation options are used deep inside the PostgreSQL code,
+ * this type is declared globally.
+*/
+
+typedef enum ternary
+{
+ TERNARY_FALSE = 0,
+ TERNARY_TRUE = 1,
+ TERNARY_UNSET = -1
+} ternary;
+
/* ----------------------------------------------------------------
* Section 3: standard system types
diff --git a/src/include/utils/rel.h b/src/include/utils/rel.h
index b552359915..7346d618f9 100644
--- a/src/include/utils/rel.h
+++ b/src/include/utils/rel.h
@@ -346,8 +346,7 @@ typedef struct StdRdOptions
bool user_catalog_table; /* use as an additional catalog relation */
int parallel_workers; /* max number of parallel workers */
StdRdOptIndexCleanup vacuum_index_cleanup; /* controls index vacuuming */
- bool vacuum_truncate; /* enables vacuum to truncate a relation */
- bool vacuum_truncate_set; /* whether vacuum_truncate is set */
+ ternary vacuum_truncate; /* enables vacuum to truncate a relation */
/*
* Fraction of pages in a relation that vacuum can eagerly scan and fail
--
2.39.2
--nextPart4155600.3ZeAukHxDK
Content-Disposition: attachment;
filename="v2a-0003-Add-alias-to-be-used-as-unset-state.patch"
Content-Transfer-Encoding: 7Bit
Content-Type: text/x-patch; charset="unicode-2-0-utf-8";
name="v2a-0003-Add-alias-to-be-used-as-unset-state.patch"
^ permalink raw reply [nested|flat] 868+ messages in thread
* [PATCH v2 2/4] Introduce ternary reloptions
@ 2025-09-04 14:41 Nikolay Shaplov <[email protected]>
0 siblings, 0 replies; 868+ messages in thread
From: Nikolay Shaplov @ 2025-09-04 14:41 UTC (permalink / raw)
Introduce ternary reloption as a replacement for current `vacuum_truncate`
implementation. Remove `vacuum_truncate_set` additional flag and using
`TERNARY_UNSET` value instead.
---
src/backend/access/common/reloptions.c | 129 ++++++++++++++++++++-----
src/backend/commands/vacuum.c | 4 +-
src/include/access/reloptions.h | 26 ++---
src/include/c.h | 16 +++
src/include/utils/rel.h | 3 +-
5 files changed, 135 insertions(+), 43 deletions(-)
diff --git a/src/backend/access/common/reloptions.c b/src/backend/access/common/reloptions.c
index 0af3fea68f..f543f11001 100644
--- a/src/backend/access/common/reloptions.c
+++ b/src/backend/access/common/reloptions.c
@@ -40,9 +40,9 @@
*
* To add an option:
*
- * (i) decide on a type (bool, integer, real, enum, string), name, default
- * value, upper and lower bounds (if applicable); for strings, consider a
- * validation routine.
+ * (i) decide on a type (bool, ternary, integer, real, enum, string), name,
+ * default value, upper and lower bounds (if applicable); for strings,
+ * consider a validation routine.
* (ii) add a record below (or use add_<type>_reloption).
* (iii) add it to the appropriate options struct (perhaps StdRdOptions)
* (iv) add it to the appropriate handling routine (perhaps
@@ -147,15 +147,6 @@ static relopt_bool boolRelOpts[] =
},
false
},
- {
- {
- "vacuum_truncate",
- "Enables vacuum to truncate empty pages at the end of this table",
- RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
- ShareUpdateExclusiveLock
- },
- true
- },
{
{
"deduplicate_items",
@@ -170,6 +161,21 @@ static relopt_bool boolRelOpts[] =
{{NULL}}
};
+static relopt_ternary ternaryRelOpts[] =
+{
+ {
+ {
+ "vacuum_truncate",
+ "Enables vacuum to truncate empty pages at the end of this table",
+ RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
+ ShareUpdateExclusiveLock
+ },
+ TERNARY_UNSET
+ },
+ /* list terminator */
+ {{NULL}}
+};
+
static relopt_int intRelOpts[] =
{
{
@@ -600,6 +606,13 @@ initialize_reloptions(void)
boolRelOpts[i].gen.lockmode));
j++;
}
+ for (i = 0; ternaryRelOpts[i].gen.name; i++)
+ {
+ Assert(DoLockModesConflict(ternaryRelOpts[i].gen.lockmode,
+ ternaryRelOpts[i].gen.lockmode));
+ j++;
+ }
+
for (i = 0; intRelOpts[i].gen.name; i++)
{
Assert(DoLockModesConflict(intRelOpts[i].gen.lockmode,
@@ -640,6 +653,14 @@ initialize_reloptions(void)
j++;
}
+ for (i = 0; ternaryRelOpts[i].gen.name; i++)
+ {
+ relOpts[j] = &ternaryRelOpts[i].gen;
+ relOpts[j]->type = RELOPT_TYPE_TERNARY;
+ relOpts[j]->namelen = strlen(relOpts[j]->name);
+ j++;
+ }
+
for (i = 0; intRelOpts[i].gen.name; i++)
{
relOpts[j] = &intRelOpts[i].gen;
@@ -800,6 +821,9 @@ allocate_reloption(bits32 kinds, int type, const char *name, const char *desc,
case RELOPT_TYPE_BOOL:
size = sizeof(relopt_bool);
break;
+ case RELOPT_TYPE_TERNARY:
+ size = sizeof(relopt_ternary);
+ break;
case RELOPT_TYPE_INT:
size = sizeof(relopt_int);
break;
@@ -883,6 +907,54 @@ add_local_bool_reloption(local_relopts *relopts, const char *name,
add_local_reloption(relopts, (relopt_gen *) newoption, offset);
}
+/*
+ * init_ternary_reloption
+ * Allocate and initialize a new ternary reloption
+ */
+static relopt_ternary *
+init_ternary_reloption(bits32 kinds, const char *name, const char *desc,
+ ternary default_val, LOCKMODE lockmode)
+{
+ relopt_ternary *newoption;
+
+ newoption = (relopt_ternary *) allocate_reloption(kinds,
+ RELOPT_TYPE_TERNARY, name, desc, lockmode);
+ newoption->default_val = default_val;
+
+ return newoption;
+}
+
+/*
+ * add_ternary_reloption
+ * Add a new ternary reloption
+ */
+void
+add_ternary_reloption(bits32 kinds, const char *name, const char *desc,
+ ternary default_val, LOCKMODE lockmode)
+{
+ relopt_ternary *newoption = init_ternary_reloption(kinds, name, desc,
+ default_val, lockmode);
+
+ add_reloption((relopt_gen *) newoption);
+}
+
+/*
+ * add_local_ternary_reloption
+ * Add a new ternary local reloption
+ *
+ * 'offset' is offset of ternary-typed field.
+ */
+void
+add_local_ternary_reloption(local_relopts *relopts, const char *name,
+ const char *desc, ternary default_val,
+ int offset)
+{
+ relopt_ternary *newoption = init_ternary_reloption(RELOPT_KIND_LOCAL,
+ name, desc,
+ default_val, 0);
+
+ add_local_reloption(relopts, (relopt_gen *) newoption, offset);
+}
/*
* init_real_reloption
@@ -1617,6 +1689,18 @@ parse_one_reloption(relopt_value *option, char *text_str, int text_len,
option->gen->name, value)));
}
break;
+ case RELOPT_TYPE_TERNARY:
+ {
+ bool b;
+ parsed = parse_bool(value, &b);
+ option->values.ternary_val = b ? TERNARY_TRUE : TERNARY_FALSE;
+ if (validate && !parsed)
+ ereport(ERROR,
+ (errcode(ERRCODE_INVALID_PARAMETER_VALUE),
+ errmsg("invalid value for ternary option \"%s\": %s",
+ option->gen->name, value)));
+ }
+ break;
case RELOPT_TYPE_INT:
{
relopt_int *optint = (relopt_int *) option->gen;
@@ -1780,17 +1864,6 @@ fillRelOptions(void *rdopts, Size basesize,
char *itempos = ((char *) rdopts) + elems[j].offset;
char *string_val;
- /*
- * If isset_offset is provided, store whether the reloption is
- * set there.
- */
- if (elems[j].isset_offset > 0)
- {
- char *setpos = ((char *) rdopts) + elems[j].isset_offset;
-
- *(bool *) setpos = options[i].isset;
- }
-
switch (options[i].gen->type)
{
case RELOPT_TYPE_BOOL:
@@ -1798,6 +1871,11 @@ fillRelOptions(void *rdopts, Size basesize,
options[i].values.bool_val :
((relopt_bool *) options[i].gen)->default_val;
break;
+ case RELOPT_TYPE_TERNARY:
+ *(ternary *) itempos = options[i].isset ?
+ options[i].values.ternary_val :
+ ((relopt_ternary *) options[i].gen)->default_val;
+ break;
case RELOPT_TYPE_INT:
*(int *) itempos = options[i].isset ?
options[i].values.int_val :
@@ -1912,8 +1990,8 @@ default_reloptions(Datum reloptions, bool validate, relopt_kind kind)
offsetof(StdRdOptions, parallel_workers)},
{"vacuum_index_cleanup", RELOPT_TYPE_ENUM,
offsetof(StdRdOptions, vacuum_index_cleanup)},
- {"vacuum_truncate", RELOPT_TYPE_BOOL,
- offsetof(StdRdOptions, vacuum_truncate), offsetof(StdRdOptions, vacuum_truncate_set)},
+ {"vacuum_truncate", RELOPT_TYPE_TERNARY,
+ offsetof(StdRdOptions, vacuum_truncate)},
{"vacuum_max_eager_freeze_failure_rate", RELOPT_TYPE_REAL,
offsetof(StdRdOptions, vacuum_max_eager_freeze_failure_rate)}
};
@@ -1993,7 +2071,6 @@ build_local_reloptions(local_relopts *relopts, Datum options, bool validate)
elems[i].optname = opt->option->name;
elems[i].opttype = opt->option->type;
elems[i].offset = opt->offset;
- elems[i].isset_offset = 0; /* not supported for local relopts yet */
i++;
}
diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c
index 733ef40ae7..977babff54 100644
--- a/src/backend/commands/vacuum.c
+++ b/src/backend/commands/vacuum.c
@@ -2221,9 +2221,9 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams params,
{
StdRdOptions *opts = (StdRdOptions *) rel->rd_options;
- if (opts && opts->vacuum_truncate_set)
+ if (opts && opts->vacuum_truncate != TERNARY_UNSET)
{
- if (opts->vacuum_truncate)
+ if (opts->vacuum_truncate == TERNARY_TRUE)
params.truncate = VACOPTVALUE_ENABLED;
else
params.truncate = VACOPTVALUE_DISABLED;
diff --git a/src/include/access/reloptions.h b/src/include/access/reloptions.h
index a604a4702c..a436697658 100644
--- a/src/include/access/reloptions.h
+++ b/src/include/access/reloptions.h
@@ -29,6 +29,7 @@
typedef enum relopt_type
{
RELOPT_TYPE_BOOL,
+ RELOPT_TYPE_TERNARY, /* on, off, unset */
RELOPT_TYPE_INT,
RELOPT_TYPE_REAL,
RELOPT_TYPE_ENUM,
@@ -80,6 +81,7 @@ typedef struct relopt_value
union
{
bool bool_val;
+ ternary ternary_val;
int int_val;
double real_val;
int enum_val;
@@ -94,6 +96,12 @@ typedef struct relopt_bool
bool default_val;
} relopt_bool;
+typedef struct relopt_rernary
+{
+ relopt_gen gen;
+ int default_val;
+} relopt_ternary;
+
typedef struct relopt_int
{
relopt_gen gen;
@@ -152,19 +160,6 @@ typedef struct
const char *optname; /* option's name */
relopt_type opttype; /* option's datatype */
int offset; /* offset of field in result struct */
-
- /*
- * isset_offset is an optional offset of a field in the result struct that
- * stores whether the option is explicitly set for the relation or if it
- * just picked up the default value. In most cases, this can be
- * accomplished by giving the reloption a special out-of-range default
- * value (e.g., some integer reloptions use -2), but this isn't always
- * possible. For example, a Boolean reloption cannot be given an
- * out-of-range default, so we need another way to discover the source of
- * its value. This offset is only used if given a value greater than
- * zero.
- */
- int isset_offset;
} relopt_parse_elt;
/* Local reloption definition */
@@ -195,6 +190,8 @@ typedef struct local_relopts
extern relopt_kind add_reloption_kind(void);
extern void add_bool_reloption(bits32 kinds, const char *name, const char *desc,
bool default_val, LOCKMODE lockmode);
+extern void add_ternary_reloption(bits32 kinds, const char *name,
+ const char *desc, int default_val, LOCKMODE lockmode);
extern void add_int_reloption(bits32 kinds, const char *name, const char *desc,
int default_val, int min_val, int max_val,
LOCKMODE lockmode);
@@ -214,6 +211,9 @@ extern void register_reloptions_validator(local_relopts *relopts,
extern void add_local_bool_reloption(local_relopts *relopts, const char *name,
const char *desc, bool default_val,
int offset);
+extern void add_local_ternary_reloption(local_relopts *relopts,
+ const char *name, const char *desc,
+ ternary default_val, int offset);
extern void add_local_int_reloption(local_relopts *relopts, const char *name,
const char *desc, int default_val,
int min_val, int max_val, int offset);
diff --git a/src/include/c.h b/src/include/c.h
index 39022f8a9d..9c59ccccf8 100644
--- a/src/include/c.h
+++ b/src/include/c.h
@@ -488,6 +488,22 @@ typedef void (*pg_funcptr_t) (void);
#include <stdbool.h>
+/*
+ * ternary
+ * Boolean value with an extrea "unset" option
+ *
+ * Ternary data type is used in relation options that can be "true", "false" or
+ * "unset". Since relation options are used deep inside the PostgreSQL code,
+ * this type is declared globally.
+*/
+
+typedef enum ternary
+{
+ TERNARY_FALSE = 0,
+ TERNARY_TRUE = 1,
+ TERNARY_UNSET = -1
+} ternary;
+
/* ----------------------------------------------------------------
* Section 3: standard system types
diff --git a/src/include/utils/rel.h b/src/include/utils/rel.h
index b552359915..7346d618f9 100644
--- a/src/include/utils/rel.h
+++ b/src/include/utils/rel.h
@@ -346,8 +346,7 @@ typedef struct StdRdOptions
bool user_catalog_table; /* use as an additional catalog relation */
int parallel_workers; /* max number of parallel workers */
StdRdOptIndexCleanup vacuum_index_cleanup; /* controls index vacuuming */
- bool vacuum_truncate; /* enables vacuum to truncate a relation */
- bool vacuum_truncate_set; /* whether vacuum_truncate is set */
+ ternary vacuum_truncate; /* enables vacuum to truncate a relation */
/*
* Fraction of pages in a relation that vacuum can eagerly scan and fail
--
2.39.2
--nextPart5129932.5fSG56mABF
Content-Disposition: attachment;
filename="v2-0003-Add-alias-to-be-used-as-unset-state.patch"
Content-Transfer-Encoding: 7Bit
Content-Type: text/x-patch; charset="unicode-2-0-utf-8";
name="v2-0003-Add-alias-to-be-used-as-unset-state.patch"
^ permalink raw reply [nested|flat] 868+ messages in thread
* [PATCH v2 2/4] Introduce ternary reloptions
@ 2025-09-04 14:41 Nikolay Shaplov <[email protected]>
0 siblings, 0 replies; 868+ messages in thread
From: Nikolay Shaplov @ 2025-09-04 14:41 UTC (permalink / raw)
Introduce ternary reloption as a replacement for current `vacuum_truncate`
implementation. Remove `vacuum_truncate_set` additional flag and using
`TERNARY_UNSET` value instead.
---
src/backend/access/common/reloptions.c | 129 ++++++++++++++++++++-----
src/backend/commands/vacuum.c | 4 +-
src/include/access/reloptions.h | 26 ++---
src/include/c.h | 16 +++
src/include/utils/rel.h | 3 +-
5 files changed, 135 insertions(+), 43 deletions(-)
diff --git a/src/backend/access/common/reloptions.c b/src/backend/access/common/reloptions.c
index 0af3fea68f..f543f11001 100644
--- a/src/backend/access/common/reloptions.c
+++ b/src/backend/access/common/reloptions.c
@@ -40,9 +40,9 @@
*
* To add an option:
*
- * (i) decide on a type (bool, integer, real, enum, string), name, default
- * value, upper and lower bounds (if applicable); for strings, consider a
- * validation routine.
+ * (i) decide on a type (bool, ternary, integer, real, enum, string), name,
+ * default value, upper and lower bounds (if applicable); for strings,
+ * consider a validation routine.
* (ii) add a record below (or use add_<type>_reloption).
* (iii) add it to the appropriate options struct (perhaps StdRdOptions)
* (iv) add it to the appropriate handling routine (perhaps
@@ -147,15 +147,6 @@ static relopt_bool boolRelOpts[] =
},
false
},
- {
- {
- "vacuum_truncate",
- "Enables vacuum to truncate empty pages at the end of this table",
- RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
- ShareUpdateExclusiveLock
- },
- true
- },
{
{
"deduplicate_items",
@@ -170,6 +161,21 @@ static relopt_bool boolRelOpts[] =
{{NULL}}
};
+static relopt_ternary ternaryRelOpts[] =
+{
+ {
+ {
+ "vacuum_truncate",
+ "Enables vacuum to truncate empty pages at the end of this table",
+ RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
+ ShareUpdateExclusiveLock
+ },
+ TERNARY_UNSET
+ },
+ /* list terminator */
+ {{NULL}}
+};
+
static relopt_int intRelOpts[] =
{
{
@@ -600,6 +606,13 @@ initialize_reloptions(void)
boolRelOpts[i].gen.lockmode));
j++;
}
+ for (i = 0; ternaryRelOpts[i].gen.name; i++)
+ {
+ Assert(DoLockModesConflict(ternaryRelOpts[i].gen.lockmode,
+ ternaryRelOpts[i].gen.lockmode));
+ j++;
+ }
+
for (i = 0; intRelOpts[i].gen.name; i++)
{
Assert(DoLockModesConflict(intRelOpts[i].gen.lockmode,
@@ -640,6 +653,14 @@ initialize_reloptions(void)
j++;
}
+ for (i = 0; ternaryRelOpts[i].gen.name; i++)
+ {
+ relOpts[j] = &ternaryRelOpts[i].gen;
+ relOpts[j]->type = RELOPT_TYPE_TERNARY;
+ relOpts[j]->namelen = strlen(relOpts[j]->name);
+ j++;
+ }
+
for (i = 0; intRelOpts[i].gen.name; i++)
{
relOpts[j] = &intRelOpts[i].gen;
@@ -800,6 +821,9 @@ allocate_reloption(bits32 kinds, int type, const char *name, const char *desc,
case RELOPT_TYPE_BOOL:
size = sizeof(relopt_bool);
break;
+ case RELOPT_TYPE_TERNARY:
+ size = sizeof(relopt_ternary);
+ break;
case RELOPT_TYPE_INT:
size = sizeof(relopt_int);
break;
@@ -883,6 +907,54 @@ add_local_bool_reloption(local_relopts *relopts, const char *name,
add_local_reloption(relopts, (relopt_gen *) newoption, offset);
}
+/*
+ * init_ternary_reloption
+ * Allocate and initialize a new ternary reloption
+ */
+static relopt_ternary *
+init_ternary_reloption(bits32 kinds, const char *name, const char *desc,
+ ternary default_val, LOCKMODE lockmode)
+{
+ relopt_ternary *newoption;
+
+ newoption = (relopt_ternary *) allocate_reloption(kinds,
+ RELOPT_TYPE_TERNARY, name, desc, lockmode);
+ newoption->default_val = default_val;
+
+ return newoption;
+}
+
+/*
+ * add_ternary_reloption
+ * Add a new ternary reloption
+ */
+void
+add_ternary_reloption(bits32 kinds, const char *name, const char *desc,
+ ternary default_val, LOCKMODE lockmode)
+{
+ relopt_ternary *newoption = init_ternary_reloption(kinds, name, desc,
+ default_val, lockmode);
+
+ add_reloption((relopt_gen *) newoption);
+}
+
+/*
+ * add_local_ternary_reloption
+ * Add a new ternary local reloption
+ *
+ * 'offset' is offset of ternary-typed field.
+ */
+void
+add_local_ternary_reloption(local_relopts *relopts, const char *name,
+ const char *desc, ternary default_val,
+ int offset)
+{
+ relopt_ternary *newoption = init_ternary_reloption(RELOPT_KIND_LOCAL,
+ name, desc,
+ default_val, 0);
+
+ add_local_reloption(relopts, (relopt_gen *) newoption, offset);
+}
/*
* init_real_reloption
@@ -1617,6 +1689,18 @@ parse_one_reloption(relopt_value *option, char *text_str, int text_len,
option->gen->name, value)));
}
break;
+ case RELOPT_TYPE_TERNARY:
+ {
+ bool b;
+ parsed = parse_bool(value, &b);
+ option->values.ternary_val = b ? TERNARY_TRUE : TERNARY_FALSE;
+ if (validate && !parsed)
+ ereport(ERROR,
+ (errcode(ERRCODE_INVALID_PARAMETER_VALUE),
+ errmsg("invalid value for ternary option \"%s\": %s",
+ option->gen->name, value)));
+ }
+ break;
case RELOPT_TYPE_INT:
{
relopt_int *optint = (relopt_int *) option->gen;
@@ -1780,17 +1864,6 @@ fillRelOptions(void *rdopts, Size basesize,
char *itempos = ((char *) rdopts) + elems[j].offset;
char *string_val;
- /*
- * If isset_offset is provided, store whether the reloption is
- * set there.
- */
- if (elems[j].isset_offset > 0)
- {
- char *setpos = ((char *) rdopts) + elems[j].isset_offset;
-
- *(bool *) setpos = options[i].isset;
- }
-
switch (options[i].gen->type)
{
case RELOPT_TYPE_BOOL:
@@ -1798,6 +1871,11 @@ fillRelOptions(void *rdopts, Size basesize,
options[i].values.bool_val :
((relopt_bool *) options[i].gen)->default_val;
break;
+ case RELOPT_TYPE_TERNARY:
+ *(ternary *) itempos = options[i].isset ?
+ options[i].values.ternary_val :
+ ((relopt_ternary *) options[i].gen)->default_val;
+ break;
case RELOPT_TYPE_INT:
*(int *) itempos = options[i].isset ?
options[i].values.int_val :
@@ -1912,8 +1990,8 @@ default_reloptions(Datum reloptions, bool validate, relopt_kind kind)
offsetof(StdRdOptions, parallel_workers)},
{"vacuum_index_cleanup", RELOPT_TYPE_ENUM,
offsetof(StdRdOptions, vacuum_index_cleanup)},
- {"vacuum_truncate", RELOPT_TYPE_BOOL,
- offsetof(StdRdOptions, vacuum_truncate), offsetof(StdRdOptions, vacuum_truncate_set)},
+ {"vacuum_truncate", RELOPT_TYPE_TERNARY,
+ offsetof(StdRdOptions, vacuum_truncate)},
{"vacuum_max_eager_freeze_failure_rate", RELOPT_TYPE_REAL,
offsetof(StdRdOptions, vacuum_max_eager_freeze_failure_rate)}
};
@@ -1993,7 +2071,6 @@ build_local_reloptions(local_relopts *relopts, Datum options, bool validate)
elems[i].optname = opt->option->name;
elems[i].opttype = opt->option->type;
elems[i].offset = opt->offset;
- elems[i].isset_offset = 0; /* not supported for local relopts yet */
i++;
}
diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c
index 733ef40ae7..977babff54 100644
--- a/src/backend/commands/vacuum.c
+++ b/src/backend/commands/vacuum.c
@@ -2221,9 +2221,9 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams params,
{
StdRdOptions *opts = (StdRdOptions *) rel->rd_options;
- if (opts && opts->vacuum_truncate_set)
+ if (opts && opts->vacuum_truncate != TERNARY_UNSET)
{
- if (opts->vacuum_truncate)
+ if (opts->vacuum_truncate == TERNARY_TRUE)
params.truncate = VACOPTVALUE_ENABLED;
else
params.truncate = VACOPTVALUE_DISABLED;
diff --git a/src/include/access/reloptions.h b/src/include/access/reloptions.h
index a604a4702c..a436697658 100644
--- a/src/include/access/reloptions.h
+++ b/src/include/access/reloptions.h
@@ -29,6 +29,7 @@
typedef enum relopt_type
{
RELOPT_TYPE_BOOL,
+ RELOPT_TYPE_TERNARY, /* on, off, unset */
RELOPT_TYPE_INT,
RELOPT_TYPE_REAL,
RELOPT_TYPE_ENUM,
@@ -80,6 +81,7 @@ typedef struct relopt_value
union
{
bool bool_val;
+ ternary ternary_val;
int int_val;
double real_val;
int enum_val;
@@ -94,6 +96,12 @@ typedef struct relopt_bool
bool default_val;
} relopt_bool;
+typedef struct relopt_rernary
+{
+ relopt_gen gen;
+ int default_val;
+} relopt_ternary;
+
typedef struct relopt_int
{
relopt_gen gen;
@@ -152,19 +160,6 @@ typedef struct
const char *optname; /* option's name */
relopt_type opttype; /* option's datatype */
int offset; /* offset of field in result struct */
-
- /*
- * isset_offset is an optional offset of a field in the result struct that
- * stores whether the option is explicitly set for the relation or if it
- * just picked up the default value. In most cases, this can be
- * accomplished by giving the reloption a special out-of-range default
- * value (e.g., some integer reloptions use -2), but this isn't always
- * possible. For example, a Boolean reloption cannot be given an
- * out-of-range default, so we need another way to discover the source of
- * its value. This offset is only used if given a value greater than
- * zero.
- */
- int isset_offset;
} relopt_parse_elt;
/* Local reloption definition */
@@ -195,6 +190,8 @@ typedef struct local_relopts
extern relopt_kind add_reloption_kind(void);
extern void add_bool_reloption(bits32 kinds, const char *name, const char *desc,
bool default_val, LOCKMODE lockmode);
+extern void add_ternary_reloption(bits32 kinds, const char *name,
+ const char *desc, int default_val, LOCKMODE lockmode);
extern void add_int_reloption(bits32 kinds, const char *name, const char *desc,
int default_val, int min_val, int max_val,
LOCKMODE lockmode);
@@ -214,6 +211,9 @@ extern void register_reloptions_validator(local_relopts *relopts,
extern void add_local_bool_reloption(local_relopts *relopts, const char *name,
const char *desc, bool default_val,
int offset);
+extern void add_local_ternary_reloption(local_relopts *relopts,
+ const char *name, const char *desc,
+ ternary default_val, int offset);
extern void add_local_int_reloption(local_relopts *relopts, const char *name,
const char *desc, int default_val,
int min_val, int max_val, int offset);
diff --git a/src/include/c.h b/src/include/c.h
index 39022f8a9d..9c59ccccf8 100644
--- a/src/include/c.h
+++ b/src/include/c.h
@@ -488,6 +488,22 @@ typedef void (*pg_funcptr_t) (void);
#include <stdbool.h>
+/*
+ * ternary
+ * Boolean value with an extrea "unset" option
+ *
+ * Ternary data type is used in relation options that can be "true", "false" or
+ * "unset". Since relation options are used deep inside the PostgreSQL code,
+ * this type is declared globally.
+*/
+
+typedef enum ternary
+{
+ TERNARY_FALSE = 0,
+ TERNARY_TRUE = 1,
+ TERNARY_UNSET = -1
+} ternary;
+
/* ----------------------------------------------------------------
* Section 3: standard system types
diff --git a/src/include/utils/rel.h b/src/include/utils/rel.h
index b552359915..7346d618f9 100644
--- a/src/include/utils/rel.h
+++ b/src/include/utils/rel.h
@@ -346,8 +346,7 @@ typedef struct StdRdOptions
bool user_catalog_table; /* use as an additional catalog relation */
int parallel_workers; /* max number of parallel workers */
StdRdOptIndexCleanup vacuum_index_cleanup; /* controls index vacuuming */
- bool vacuum_truncate; /* enables vacuum to truncate a relation */
- bool vacuum_truncate_set; /* whether vacuum_truncate is set */
+ ternary vacuum_truncate; /* enables vacuum to truncate a relation */
/*
* Fraction of pages in a relation that vacuum can eagerly scan and fail
--
2.39.2
--nextPart5129932.5fSG56mABF
Content-Disposition: attachment;
filename="v2-0003-Add-alias-to-be-used-as-unset-state.patch"
Content-Transfer-Encoding: 7Bit
Content-Type: text/x-patch; charset="unicode-2-0-utf-8";
name="v2-0003-Add-alias-to-be-used-as-unset-state.patch"
^ permalink raw reply [nested|flat] 868+ messages in thread
* [PATCH v2 2/4] Introduce ternary reloptions
@ 2025-09-04 14:41 Nikolay Shaplov <[email protected]>
0 siblings, 0 replies; 868+ messages in thread
From: Nikolay Shaplov @ 2025-09-04 14:41 UTC (permalink / raw)
Introduce ternary reloption as a replacement for current `vacuum_truncate`
implementation. Remove `vacuum_truncate_set` additional flag and using
`TERNARY_UNSET` value instead.
---
src/backend/access/common/reloptions.c | 129 ++++++++++++++++++++-----
src/backend/commands/vacuum.c | 4 +-
src/include/access/reloptions.h | 26 ++---
src/include/c.h | 16 +++
src/include/utils/rel.h | 3 +-
5 files changed, 135 insertions(+), 43 deletions(-)
diff --git a/src/backend/access/common/reloptions.c b/src/backend/access/common/reloptions.c
index 0af3fea68f..f543f11001 100644
--- a/src/backend/access/common/reloptions.c
+++ b/src/backend/access/common/reloptions.c
@@ -40,9 +40,9 @@
*
* To add an option:
*
- * (i) decide on a type (bool, integer, real, enum, string), name, default
- * value, upper and lower bounds (if applicable); for strings, consider a
- * validation routine.
+ * (i) decide on a type (bool, ternary, integer, real, enum, string), name,
+ * default value, upper and lower bounds (if applicable); for strings,
+ * consider a validation routine.
* (ii) add a record below (or use add_<type>_reloption).
* (iii) add it to the appropriate options struct (perhaps StdRdOptions)
* (iv) add it to the appropriate handling routine (perhaps
@@ -147,15 +147,6 @@ static relopt_bool boolRelOpts[] =
},
false
},
- {
- {
- "vacuum_truncate",
- "Enables vacuum to truncate empty pages at the end of this table",
- RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
- ShareUpdateExclusiveLock
- },
- true
- },
{
{
"deduplicate_items",
@@ -170,6 +161,21 @@ static relopt_bool boolRelOpts[] =
{{NULL}}
};
+static relopt_ternary ternaryRelOpts[] =
+{
+ {
+ {
+ "vacuum_truncate",
+ "Enables vacuum to truncate empty pages at the end of this table",
+ RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
+ ShareUpdateExclusiveLock
+ },
+ TERNARY_UNSET
+ },
+ /* list terminator */
+ {{NULL}}
+};
+
static relopt_int intRelOpts[] =
{
{
@@ -600,6 +606,13 @@ initialize_reloptions(void)
boolRelOpts[i].gen.lockmode));
j++;
}
+ for (i = 0; ternaryRelOpts[i].gen.name; i++)
+ {
+ Assert(DoLockModesConflict(ternaryRelOpts[i].gen.lockmode,
+ ternaryRelOpts[i].gen.lockmode));
+ j++;
+ }
+
for (i = 0; intRelOpts[i].gen.name; i++)
{
Assert(DoLockModesConflict(intRelOpts[i].gen.lockmode,
@@ -640,6 +653,14 @@ initialize_reloptions(void)
j++;
}
+ for (i = 0; ternaryRelOpts[i].gen.name; i++)
+ {
+ relOpts[j] = &ternaryRelOpts[i].gen;
+ relOpts[j]->type = RELOPT_TYPE_TERNARY;
+ relOpts[j]->namelen = strlen(relOpts[j]->name);
+ j++;
+ }
+
for (i = 0; intRelOpts[i].gen.name; i++)
{
relOpts[j] = &intRelOpts[i].gen;
@@ -800,6 +821,9 @@ allocate_reloption(bits32 kinds, int type, const char *name, const char *desc,
case RELOPT_TYPE_BOOL:
size = sizeof(relopt_bool);
break;
+ case RELOPT_TYPE_TERNARY:
+ size = sizeof(relopt_ternary);
+ break;
case RELOPT_TYPE_INT:
size = sizeof(relopt_int);
break;
@@ -883,6 +907,54 @@ add_local_bool_reloption(local_relopts *relopts, const char *name,
add_local_reloption(relopts, (relopt_gen *) newoption, offset);
}
+/*
+ * init_ternary_reloption
+ * Allocate and initialize a new ternary reloption
+ */
+static relopt_ternary *
+init_ternary_reloption(bits32 kinds, const char *name, const char *desc,
+ ternary default_val, LOCKMODE lockmode)
+{
+ relopt_ternary *newoption;
+
+ newoption = (relopt_ternary *) allocate_reloption(kinds,
+ RELOPT_TYPE_TERNARY, name, desc, lockmode);
+ newoption->default_val = default_val;
+
+ return newoption;
+}
+
+/*
+ * add_ternary_reloption
+ * Add a new ternary reloption
+ */
+void
+add_ternary_reloption(bits32 kinds, const char *name, const char *desc,
+ ternary default_val, LOCKMODE lockmode)
+{
+ relopt_ternary *newoption = init_ternary_reloption(kinds, name, desc,
+ default_val, lockmode);
+
+ add_reloption((relopt_gen *) newoption);
+}
+
+/*
+ * add_local_ternary_reloption
+ * Add a new ternary local reloption
+ *
+ * 'offset' is offset of ternary-typed field.
+ */
+void
+add_local_ternary_reloption(local_relopts *relopts, const char *name,
+ const char *desc, ternary default_val,
+ int offset)
+{
+ relopt_ternary *newoption = init_ternary_reloption(RELOPT_KIND_LOCAL,
+ name, desc,
+ default_val, 0);
+
+ add_local_reloption(relopts, (relopt_gen *) newoption, offset);
+}
/*
* init_real_reloption
@@ -1617,6 +1689,18 @@ parse_one_reloption(relopt_value *option, char *text_str, int text_len,
option->gen->name, value)));
}
break;
+ case RELOPT_TYPE_TERNARY:
+ {
+ bool b;
+ parsed = parse_bool(value, &b);
+ option->values.ternary_val = b ? TERNARY_TRUE : TERNARY_FALSE;
+ if (validate && !parsed)
+ ereport(ERROR,
+ (errcode(ERRCODE_INVALID_PARAMETER_VALUE),
+ errmsg("invalid value for ternary option \"%s\": %s",
+ option->gen->name, value)));
+ }
+ break;
case RELOPT_TYPE_INT:
{
relopt_int *optint = (relopt_int *) option->gen;
@@ -1780,17 +1864,6 @@ fillRelOptions(void *rdopts, Size basesize,
char *itempos = ((char *) rdopts) + elems[j].offset;
char *string_val;
- /*
- * If isset_offset is provided, store whether the reloption is
- * set there.
- */
- if (elems[j].isset_offset > 0)
- {
- char *setpos = ((char *) rdopts) + elems[j].isset_offset;
-
- *(bool *) setpos = options[i].isset;
- }
-
switch (options[i].gen->type)
{
case RELOPT_TYPE_BOOL:
@@ -1798,6 +1871,11 @@ fillRelOptions(void *rdopts, Size basesize,
options[i].values.bool_val :
((relopt_bool *) options[i].gen)->default_val;
break;
+ case RELOPT_TYPE_TERNARY:
+ *(ternary *) itempos = options[i].isset ?
+ options[i].values.ternary_val :
+ ((relopt_ternary *) options[i].gen)->default_val;
+ break;
case RELOPT_TYPE_INT:
*(int *) itempos = options[i].isset ?
options[i].values.int_val :
@@ -1912,8 +1990,8 @@ default_reloptions(Datum reloptions, bool validate, relopt_kind kind)
offsetof(StdRdOptions, parallel_workers)},
{"vacuum_index_cleanup", RELOPT_TYPE_ENUM,
offsetof(StdRdOptions, vacuum_index_cleanup)},
- {"vacuum_truncate", RELOPT_TYPE_BOOL,
- offsetof(StdRdOptions, vacuum_truncate), offsetof(StdRdOptions, vacuum_truncate_set)},
+ {"vacuum_truncate", RELOPT_TYPE_TERNARY,
+ offsetof(StdRdOptions, vacuum_truncate)},
{"vacuum_max_eager_freeze_failure_rate", RELOPT_TYPE_REAL,
offsetof(StdRdOptions, vacuum_max_eager_freeze_failure_rate)}
};
@@ -1993,7 +2071,6 @@ build_local_reloptions(local_relopts *relopts, Datum options, bool validate)
elems[i].optname = opt->option->name;
elems[i].opttype = opt->option->type;
elems[i].offset = opt->offset;
- elems[i].isset_offset = 0; /* not supported for local relopts yet */
i++;
}
diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c
index 733ef40ae7..977babff54 100644
--- a/src/backend/commands/vacuum.c
+++ b/src/backend/commands/vacuum.c
@@ -2221,9 +2221,9 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams params,
{
StdRdOptions *opts = (StdRdOptions *) rel->rd_options;
- if (opts && opts->vacuum_truncate_set)
+ if (opts && opts->vacuum_truncate != TERNARY_UNSET)
{
- if (opts->vacuum_truncate)
+ if (opts->vacuum_truncate == TERNARY_TRUE)
params.truncate = VACOPTVALUE_ENABLED;
else
params.truncate = VACOPTVALUE_DISABLED;
diff --git a/src/include/access/reloptions.h b/src/include/access/reloptions.h
index a604a4702c..a436697658 100644
--- a/src/include/access/reloptions.h
+++ b/src/include/access/reloptions.h
@@ -29,6 +29,7 @@
typedef enum relopt_type
{
RELOPT_TYPE_BOOL,
+ RELOPT_TYPE_TERNARY, /* on, off, unset */
RELOPT_TYPE_INT,
RELOPT_TYPE_REAL,
RELOPT_TYPE_ENUM,
@@ -80,6 +81,7 @@ typedef struct relopt_value
union
{
bool bool_val;
+ ternary ternary_val;
int int_val;
double real_val;
int enum_val;
@@ -94,6 +96,12 @@ typedef struct relopt_bool
bool default_val;
} relopt_bool;
+typedef struct relopt_rernary
+{
+ relopt_gen gen;
+ int default_val;
+} relopt_ternary;
+
typedef struct relopt_int
{
relopt_gen gen;
@@ -152,19 +160,6 @@ typedef struct
const char *optname; /* option's name */
relopt_type opttype; /* option's datatype */
int offset; /* offset of field in result struct */
-
- /*
- * isset_offset is an optional offset of a field in the result struct that
- * stores whether the option is explicitly set for the relation or if it
- * just picked up the default value. In most cases, this can be
- * accomplished by giving the reloption a special out-of-range default
- * value (e.g., some integer reloptions use -2), but this isn't always
- * possible. For example, a Boolean reloption cannot be given an
- * out-of-range default, so we need another way to discover the source of
- * its value. This offset is only used if given a value greater than
- * zero.
- */
- int isset_offset;
} relopt_parse_elt;
/* Local reloption definition */
@@ -195,6 +190,8 @@ typedef struct local_relopts
extern relopt_kind add_reloption_kind(void);
extern void add_bool_reloption(bits32 kinds, const char *name, const char *desc,
bool default_val, LOCKMODE lockmode);
+extern void add_ternary_reloption(bits32 kinds, const char *name,
+ const char *desc, int default_val, LOCKMODE lockmode);
extern void add_int_reloption(bits32 kinds, const char *name, const char *desc,
int default_val, int min_val, int max_val,
LOCKMODE lockmode);
@@ -214,6 +211,9 @@ extern void register_reloptions_validator(local_relopts *relopts,
extern void add_local_bool_reloption(local_relopts *relopts, const char *name,
const char *desc, bool default_val,
int offset);
+extern void add_local_ternary_reloption(local_relopts *relopts,
+ const char *name, const char *desc,
+ ternary default_val, int offset);
extern void add_local_int_reloption(local_relopts *relopts, const char *name,
const char *desc, int default_val,
int min_val, int max_val, int offset);
diff --git a/src/include/c.h b/src/include/c.h
index 39022f8a9d..9c59ccccf8 100644
--- a/src/include/c.h
+++ b/src/include/c.h
@@ -488,6 +488,22 @@ typedef void (*pg_funcptr_t) (void);
#include <stdbool.h>
+/*
+ * ternary
+ * Boolean value with an extrea "unset" option
+ *
+ * Ternary data type is used in relation options that can be "true", "false" or
+ * "unset". Since relation options are used deep inside the PostgreSQL code,
+ * this type is declared globally.
+*/
+
+typedef enum ternary
+{
+ TERNARY_FALSE = 0,
+ TERNARY_TRUE = 1,
+ TERNARY_UNSET = -1
+} ternary;
+
/* ----------------------------------------------------------------
* Section 3: standard system types
diff --git a/src/include/utils/rel.h b/src/include/utils/rel.h
index b552359915..7346d618f9 100644
--- a/src/include/utils/rel.h
+++ b/src/include/utils/rel.h
@@ -346,8 +346,7 @@ typedef struct StdRdOptions
bool user_catalog_table; /* use as an additional catalog relation */
int parallel_workers; /* max number of parallel workers */
StdRdOptIndexCleanup vacuum_index_cleanup; /* controls index vacuuming */
- bool vacuum_truncate; /* enables vacuum to truncate a relation */
- bool vacuum_truncate_set; /* whether vacuum_truncate is set */
+ ternary vacuum_truncate; /* enables vacuum to truncate a relation */
/*
* Fraction of pages in a relation that vacuum can eagerly scan and fail
--
2.39.2
--nextPart5129932.5fSG56mABF
Content-Disposition: attachment;
filename="v2-0003-Add-alias-to-be-used-as-unset-state.patch"
Content-Transfer-Encoding: 7Bit
Content-Type: text/x-patch; charset="unicode-2-0-utf-8";
name="v2-0003-Add-alias-to-be-used-as-unset-state.patch"
^ permalink raw reply [nested|flat] 868+ messages in thread
* [PATCH v2a 2/4] Introduce ternary reloptions
@ 2025-09-04 14:41 Nikolay Shaplov <[email protected]>
0 siblings, 0 replies; 868+ messages in thread
From: Nikolay Shaplov @ 2025-09-04 14:41 UTC (permalink / raw)
Introduce ternary reloption as a replacement for current `vacuum_truncate`
implementation. Remove `vacuum_truncate_set` additional flag and using
`TERNARY_UNSET` value instead.
---
src/backend/access/common/reloptions.c | 129 ++++++++++++++++++++-----
src/backend/commands/vacuum.c | 4 +-
src/include/access/reloptions.h | 26 ++---
src/include/c.h | 16 +++
src/include/utils/rel.h | 3 +-
5 files changed, 135 insertions(+), 43 deletions(-)
diff --git a/src/backend/access/common/reloptions.c b/src/backend/access/common/reloptions.c
index 0af3fea68f..f543f11001 100644
--- a/src/backend/access/common/reloptions.c
+++ b/src/backend/access/common/reloptions.c
@@ -40,9 +40,9 @@
*
* To add an option:
*
- * (i) decide on a type (bool, integer, real, enum, string), name, default
- * value, upper and lower bounds (if applicable); for strings, consider a
- * validation routine.
+ * (i) decide on a type (bool, ternary, integer, real, enum, string), name,
+ * default value, upper and lower bounds (if applicable); for strings,
+ * consider a validation routine.
* (ii) add a record below (or use add_<type>_reloption).
* (iii) add it to the appropriate options struct (perhaps StdRdOptions)
* (iv) add it to the appropriate handling routine (perhaps
@@ -147,15 +147,6 @@ static relopt_bool boolRelOpts[] =
},
false
},
- {
- {
- "vacuum_truncate",
- "Enables vacuum to truncate empty pages at the end of this table",
- RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
- ShareUpdateExclusiveLock
- },
- true
- },
{
{
"deduplicate_items",
@@ -170,6 +161,21 @@ static relopt_bool boolRelOpts[] =
{{NULL}}
};
+static relopt_ternary ternaryRelOpts[] =
+{
+ {
+ {
+ "vacuum_truncate",
+ "Enables vacuum to truncate empty pages at the end of this table",
+ RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
+ ShareUpdateExclusiveLock
+ },
+ TERNARY_UNSET
+ },
+ /* list terminator */
+ {{NULL}}
+};
+
static relopt_int intRelOpts[] =
{
{
@@ -600,6 +606,13 @@ initialize_reloptions(void)
boolRelOpts[i].gen.lockmode));
j++;
}
+ for (i = 0; ternaryRelOpts[i].gen.name; i++)
+ {
+ Assert(DoLockModesConflict(ternaryRelOpts[i].gen.lockmode,
+ ternaryRelOpts[i].gen.lockmode));
+ j++;
+ }
+
for (i = 0; intRelOpts[i].gen.name; i++)
{
Assert(DoLockModesConflict(intRelOpts[i].gen.lockmode,
@@ -640,6 +653,14 @@ initialize_reloptions(void)
j++;
}
+ for (i = 0; ternaryRelOpts[i].gen.name; i++)
+ {
+ relOpts[j] = &ternaryRelOpts[i].gen;
+ relOpts[j]->type = RELOPT_TYPE_TERNARY;
+ relOpts[j]->namelen = strlen(relOpts[j]->name);
+ j++;
+ }
+
for (i = 0; intRelOpts[i].gen.name; i++)
{
relOpts[j] = &intRelOpts[i].gen;
@@ -800,6 +821,9 @@ allocate_reloption(bits32 kinds, int type, const char *name, const char *desc,
case RELOPT_TYPE_BOOL:
size = sizeof(relopt_bool);
break;
+ case RELOPT_TYPE_TERNARY:
+ size = sizeof(relopt_ternary);
+ break;
case RELOPT_TYPE_INT:
size = sizeof(relopt_int);
break;
@@ -883,6 +907,54 @@ add_local_bool_reloption(local_relopts *relopts, const char *name,
add_local_reloption(relopts, (relopt_gen *) newoption, offset);
}
+/*
+ * init_ternary_reloption
+ * Allocate and initialize a new ternary reloption
+ */
+static relopt_ternary *
+init_ternary_reloption(bits32 kinds, const char *name, const char *desc,
+ ternary default_val, LOCKMODE lockmode)
+{
+ relopt_ternary *newoption;
+
+ newoption = (relopt_ternary *) allocate_reloption(kinds,
+ RELOPT_TYPE_TERNARY, name, desc, lockmode);
+ newoption->default_val = default_val;
+
+ return newoption;
+}
+
+/*
+ * add_ternary_reloption
+ * Add a new ternary reloption
+ */
+void
+add_ternary_reloption(bits32 kinds, const char *name, const char *desc,
+ ternary default_val, LOCKMODE lockmode)
+{
+ relopt_ternary *newoption = init_ternary_reloption(kinds, name, desc,
+ default_val, lockmode);
+
+ add_reloption((relopt_gen *) newoption);
+}
+
+/*
+ * add_local_ternary_reloption
+ * Add a new ternary local reloption
+ *
+ * 'offset' is offset of ternary-typed field.
+ */
+void
+add_local_ternary_reloption(local_relopts *relopts, const char *name,
+ const char *desc, ternary default_val,
+ int offset)
+{
+ relopt_ternary *newoption = init_ternary_reloption(RELOPT_KIND_LOCAL,
+ name, desc,
+ default_val, 0);
+
+ add_local_reloption(relopts, (relopt_gen *) newoption, offset);
+}
/*
* init_real_reloption
@@ -1617,6 +1689,18 @@ parse_one_reloption(relopt_value *option, char *text_str, int text_len,
option->gen->name, value)));
}
break;
+ case RELOPT_TYPE_TERNARY:
+ {
+ bool b;
+ parsed = parse_bool(value, &b);
+ option->values.ternary_val = b ? TERNARY_TRUE : TERNARY_FALSE;
+ if (validate && !parsed)
+ ereport(ERROR,
+ (errcode(ERRCODE_INVALID_PARAMETER_VALUE),
+ errmsg("invalid value for ternary option \"%s\": %s",
+ option->gen->name, value)));
+ }
+ break;
case RELOPT_TYPE_INT:
{
relopt_int *optint = (relopt_int *) option->gen;
@@ -1780,17 +1864,6 @@ fillRelOptions(void *rdopts, Size basesize,
char *itempos = ((char *) rdopts) + elems[j].offset;
char *string_val;
- /*
- * If isset_offset is provided, store whether the reloption is
- * set there.
- */
- if (elems[j].isset_offset > 0)
- {
- char *setpos = ((char *) rdopts) + elems[j].isset_offset;
-
- *(bool *) setpos = options[i].isset;
- }
-
switch (options[i].gen->type)
{
case RELOPT_TYPE_BOOL:
@@ -1798,6 +1871,11 @@ fillRelOptions(void *rdopts, Size basesize,
options[i].values.bool_val :
((relopt_bool *) options[i].gen)->default_val;
break;
+ case RELOPT_TYPE_TERNARY:
+ *(ternary *) itempos = options[i].isset ?
+ options[i].values.ternary_val :
+ ((relopt_ternary *) options[i].gen)->default_val;
+ break;
case RELOPT_TYPE_INT:
*(int *) itempos = options[i].isset ?
options[i].values.int_val :
@@ -1912,8 +1990,8 @@ default_reloptions(Datum reloptions, bool validate, relopt_kind kind)
offsetof(StdRdOptions, parallel_workers)},
{"vacuum_index_cleanup", RELOPT_TYPE_ENUM,
offsetof(StdRdOptions, vacuum_index_cleanup)},
- {"vacuum_truncate", RELOPT_TYPE_BOOL,
- offsetof(StdRdOptions, vacuum_truncate), offsetof(StdRdOptions, vacuum_truncate_set)},
+ {"vacuum_truncate", RELOPT_TYPE_TERNARY,
+ offsetof(StdRdOptions, vacuum_truncate)},
{"vacuum_max_eager_freeze_failure_rate", RELOPT_TYPE_REAL,
offsetof(StdRdOptions, vacuum_max_eager_freeze_failure_rate)}
};
@@ -1993,7 +2071,6 @@ build_local_reloptions(local_relopts *relopts, Datum options, bool validate)
elems[i].optname = opt->option->name;
elems[i].opttype = opt->option->type;
elems[i].offset = opt->offset;
- elems[i].isset_offset = 0; /* not supported for local relopts yet */
i++;
}
diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c
index 733ef40ae7..977babff54 100644
--- a/src/backend/commands/vacuum.c
+++ b/src/backend/commands/vacuum.c
@@ -2221,9 +2221,9 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams params,
{
StdRdOptions *opts = (StdRdOptions *) rel->rd_options;
- if (opts && opts->vacuum_truncate_set)
+ if (opts && opts->vacuum_truncate != TERNARY_UNSET)
{
- if (opts->vacuum_truncate)
+ if (opts->vacuum_truncate == TERNARY_TRUE)
params.truncate = VACOPTVALUE_ENABLED;
else
params.truncate = VACOPTVALUE_DISABLED;
diff --git a/src/include/access/reloptions.h b/src/include/access/reloptions.h
index a604a4702c..a436697658 100644
--- a/src/include/access/reloptions.h
+++ b/src/include/access/reloptions.h
@@ -29,6 +29,7 @@
typedef enum relopt_type
{
RELOPT_TYPE_BOOL,
+ RELOPT_TYPE_TERNARY, /* on, off, unset */
RELOPT_TYPE_INT,
RELOPT_TYPE_REAL,
RELOPT_TYPE_ENUM,
@@ -80,6 +81,7 @@ typedef struct relopt_value
union
{
bool bool_val;
+ ternary ternary_val;
int int_val;
double real_val;
int enum_val;
@@ -94,6 +96,12 @@ typedef struct relopt_bool
bool default_val;
} relopt_bool;
+typedef struct relopt_rernary
+{
+ relopt_gen gen;
+ int default_val;
+} relopt_ternary;
+
typedef struct relopt_int
{
relopt_gen gen;
@@ -152,19 +160,6 @@ typedef struct
const char *optname; /* option's name */
relopt_type opttype; /* option's datatype */
int offset; /* offset of field in result struct */
-
- /*
- * isset_offset is an optional offset of a field in the result struct that
- * stores whether the option is explicitly set for the relation or if it
- * just picked up the default value. In most cases, this can be
- * accomplished by giving the reloption a special out-of-range default
- * value (e.g., some integer reloptions use -2), but this isn't always
- * possible. For example, a Boolean reloption cannot be given an
- * out-of-range default, so we need another way to discover the source of
- * its value. This offset is only used if given a value greater than
- * zero.
- */
- int isset_offset;
} relopt_parse_elt;
/* Local reloption definition */
@@ -195,6 +190,8 @@ typedef struct local_relopts
extern relopt_kind add_reloption_kind(void);
extern void add_bool_reloption(bits32 kinds, const char *name, const char *desc,
bool default_val, LOCKMODE lockmode);
+extern void add_ternary_reloption(bits32 kinds, const char *name,
+ const char *desc, int default_val, LOCKMODE lockmode);
extern void add_int_reloption(bits32 kinds, const char *name, const char *desc,
int default_val, int min_val, int max_val,
LOCKMODE lockmode);
@@ -214,6 +211,9 @@ extern void register_reloptions_validator(local_relopts *relopts,
extern void add_local_bool_reloption(local_relopts *relopts, const char *name,
const char *desc, bool default_val,
int offset);
+extern void add_local_ternary_reloption(local_relopts *relopts,
+ const char *name, const char *desc,
+ ternary default_val, int offset);
extern void add_local_int_reloption(local_relopts *relopts, const char *name,
const char *desc, int default_val,
int min_val, int max_val, int offset);
diff --git a/src/include/c.h b/src/include/c.h
index 39022f8a9d..9c59ccccf8 100644
--- a/src/include/c.h
+++ b/src/include/c.h
@@ -488,6 +488,22 @@ typedef void (*pg_funcptr_t) (void);
#include <stdbool.h>
+/*
+ * ternary
+ * Boolean value with an extrea "unset" option
+ *
+ * Ternary data type is used in relation options that can be "true", "false" or
+ * "unset". Since relation options are used deep inside the PostgreSQL code,
+ * this type is declared globally.
+*/
+
+typedef enum ternary
+{
+ TERNARY_FALSE = 0,
+ TERNARY_TRUE = 1,
+ TERNARY_UNSET = -1
+} ternary;
+
/* ----------------------------------------------------------------
* Section 3: standard system types
diff --git a/src/include/utils/rel.h b/src/include/utils/rel.h
index b552359915..7346d618f9 100644
--- a/src/include/utils/rel.h
+++ b/src/include/utils/rel.h
@@ -346,8 +346,7 @@ typedef struct StdRdOptions
bool user_catalog_table; /* use as an additional catalog relation */
int parallel_workers; /* max number of parallel workers */
StdRdOptIndexCleanup vacuum_index_cleanup; /* controls index vacuuming */
- bool vacuum_truncate; /* enables vacuum to truncate a relation */
- bool vacuum_truncate_set; /* whether vacuum_truncate is set */
+ ternary vacuum_truncate; /* enables vacuum to truncate a relation */
/*
* Fraction of pages in a relation that vacuum can eagerly scan and fail
--
2.39.2
--nextPart4155600.3ZeAukHxDK
Content-Disposition: attachment;
filename="v2a-0003-Add-alias-to-be-used-as-unset-state.patch"
Content-Transfer-Encoding: 7Bit
Content-Type: text/x-patch; charset="unicode-2-0-utf-8";
name="v2a-0003-Add-alias-to-be-used-as-unset-state.patch"
^ permalink raw reply [nested|flat] 868+ messages in thread
* [PATCH v2a 2/4] Introduce ternary reloptions
@ 2025-09-04 14:41 Nikolay Shaplov <[email protected]>
0 siblings, 0 replies; 868+ messages in thread
From: Nikolay Shaplov @ 2025-09-04 14:41 UTC (permalink / raw)
Introduce ternary reloption as a replacement for current `vacuum_truncate`
implementation. Remove `vacuum_truncate_set` additional flag and using
`TERNARY_UNSET` value instead.
---
src/backend/access/common/reloptions.c | 129 ++++++++++++++++++++-----
src/backend/commands/vacuum.c | 4 +-
src/include/access/reloptions.h | 26 ++---
src/include/c.h | 16 +++
src/include/utils/rel.h | 3 +-
5 files changed, 135 insertions(+), 43 deletions(-)
diff --git a/src/backend/access/common/reloptions.c b/src/backend/access/common/reloptions.c
index 0af3fea68f..f543f11001 100644
--- a/src/backend/access/common/reloptions.c
+++ b/src/backend/access/common/reloptions.c
@@ -40,9 +40,9 @@
*
* To add an option:
*
- * (i) decide on a type (bool, integer, real, enum, string), name, default
- * value, upper and lower bounds (if applicable); for strings, consider a
- * validation routine.
+ * (i) decide on a type (bool, ternary, integer, real, enum, string), name,
+ * default value, upper and lower bounds (if applicable); for strings,
+ * consider a validation routine.
* (ii) add a record below (or use add_<type>_reloption).
* (iii) add it to the appropriate options struct (perhaps StdRdOptions)
* (iv) add it to the appropriate handling routine (perhaps
@@ -147,15 +147,6 @@ static relopt_bool boolRelOpts[] =
},
false
},
- {
- {
- "vacuum_truncate",
- "Enables vacuum to truncate empty pages at the end of this table",
- RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
- ShareUpdateExclusiveLock
- },
- true
- },
{
{
"deduplicate_items",
@@ -170,6 +161,21 @@ static relopt_bool boolRelOpts[] =
{{NULL}}
};
+static relopt_ternary ternaryRelOpts[] =
+{
+ {
+ {
+ "vacuum_truncate",
+ "Enables vacuum to truncate empty pages at the end of this table",
+ RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
+ ShareUpdateExclusiveLock
+ },
+ TERNARY_UNSET
+ },
+ /* list terminator */
+ {{NULL}}
+};
+
static relopt_int intRelOpts[] =
{
{
@@ -600,6 +606,13 @@ initialize_reloptions(void)
boolRelOpts[i].gen.lockmode));
j++;
}
+ for (i = 0; ternaryRelOpts[i].gen.name; i++)
+ {
+ Assert(DoLockModesConflict(ternaryRelOpts[i].gen.lockmode,
+ ternaryRelOpts[i].gen.lockmode));
+ j++;
+ }
+
for (i = 0; intRelOpts[i].gen.name; i++)
{
Assert(DoLockModesConflict(intRelOpts[i].gen.lockmode,
@@ -640,6 +653,14 @@ initialize_reloptions(void)
j++;
}
+ for (i = 0; ternaryRelOpts[i].gen.name; i++)
+ {
+ relOpts[j] = &ternaryRelOpts[i].gen;
+ relOpts[j]->type = RELOPT_TYPE_TERNARY;
+ relOpts[j]->namelen = strlen(relOpts[j]->name);
+ j++;
+ }
+
for (i = 0; intRelOpts[i].gen.name; i++)
{
relOpts[j] = &intRelOpts[i].gen;
@@ -800,6 +821,9 @@ allocate_reloption(bits32 kinds, int type, const char *name, const char *desc,
case RELOPT_TYPE_BOOL:
size = sizeof(relopt_bool);
break;
+ case RELOPT_TYPE_TERNARY:
+ size = sizeof(relopt_ternary);
+ break;
case RELOPT_TYPE_INT:
size = sizeof(relopt_int);
break;
@@ -883,6 +907,54 @@ add_local_bool_reloption(local_relopts *relopts, const char *name,
add_local_reloption(relopts, (relopt_gen *) newoption, offset);
}
+/*
+ * init_ternary_reloption
+ * Allocate and initialize a new ternary reloption
+ */
+static relopt_ternary *
+init_ternary_reloption(bits32 kinds, const char *name, const char *desc,
+ ternary default_val, LOCKMODE lockmode)
+{
+ relopt_ternary *newoption;
+
+ newoption = (relopt_ternary *) allocate_reloption(kinds,
+ RELOPT_TYPE_TERNARY, name, desc, lockmode);
+ newoption->default_val = default_val;
+
+ return newoption;
+}
+
+/*
+ * add_ternary_reloption
+ * Add a new ternary reloption
+ */
+void
+add_ternary_reloption(bits32 kinds, const char *name, const char *desc,
+ ternary default_val, LOCKMODE lockmode)
+{
+ relopt_ternary *newoption = init_ternary_reloption(kinds, name, desc,
+ default_val, lockmode);
+
+ add_reloption((relopt_gen *) newoption);
+}
+
+/*
+ * add_local_ternary_reloption
+ * Add a new ternary local reloption
+ *
+ * 'offset' is offset of ternary-typed field.
+ */
+void
+add_local_ternary_reloption(local_relopts *relopts, const char *name,
+ const char *desc, ternary default_val,
+ int offset)
+{
+ relopt_ternary *newoption = init_ternary_reloption(RELOPT_KIND_LOCAL,
+ name, desc,
+ default_val, 0);
+
+ add_local_reloption(relopts, (relopt_gen *) newoption, offset);
+}
/*
* init_real_reloption
@@ -1617,6 +1689,18 @@ parse_one_reloption(relopt_value *option, char *text_str, int text_len,
option->gen->name, value)));
}
break;
+ case RELOPT_TYPE_TERNARY:
+ {
+ bool b;
+ parsed = parse_bool(value, &b);
+ option->values.ternary_val = b ? TERNARY_TRUE : TERNARY_FALSE;
+ if (validate && !parsed)
+ ereport(ERROR,
+ (errcode(ERRCODE_INVALID_PARAMETER_VALUE),
+ errmsg("invalid value for ternary option \"%s\": %s",
+ option->gen->name, value)));
+ }
+ break;
case RELOPT_TYPE_INT:
{
relopt_int *optint = (relopt_int *) option->gen;
@@ -1780,17 +1864,6 @@ fillRelOptions(void *rdopts, Size basesize,
char *itempos = ((char *) rdopts) + elems[j].offset;
char *string_val;
- /*
- * If isset_offset is provided, store whether the reloption is
- * set there.
- */
- if (elems[j].isset_offset > 0)
- {
- char *setpos = ((char *) rdopts) + elems[j].isset_offset;
-
- *(bool *) setpos = options[i].isset;
- }
-
switch (options[i].gen->type)
{
case RELOPT_TYPE_BOOL:
@@ -1798,6 +1871,11 @@ fillRelOptions(void *rdopts, Size basesize,
options[i].values.bool_val :
((relopt_bool *) options[i].gen)->default_val;
break;
+ case RELOPT_TYPE_TERNARY:
+ *(ternary *) itempos = options[i].isset ?
+ options[i].values.ternary_val :
+ ((relopt_ternary *) options[i].gen)->default_val;
+ break;
case RELOPT_TYPE_INT:
*(int *) itempos = options[i].isset ?
options[i].values.int_val :
@@ -1912,8 +1990,8 @@ default_reloptions(Datum reloptions, bool validate, relopt_kind kind)
offsetof(StdRdOptions, parallel_workers)},
{"vacuum_index_cleanup", RELOPT_TYPE_ENUM,
offsetof(StdRdOptions, vacuum_index_cleanup)},
- {"vacuum_truncate", RELOPT_TYPE_BOOL,
- offsetof(StdRdOptions, vacuum_truncate), offsetof(StdRdOptions, vacuum_truncate_set)},
+ {"vacuum_truncate", RELOPT_TYPE_TERNARY,
+ offsetof(StdRdOptions, vacuum_truncate)},
{"vacuum_max_eager_freeze_failure_rate", RELOPT_TYPE_REAL,
offsetof(StdRdOptions, vacuum_max_eager_freeze_failure_rate)}
};
@@ -1993,7 +2071,6 @@ build_local_reloptions(local_relopts *relopts, Datum options, bool validate)
elems[i].optname = opt->option->name;
elems[i].opttype = opt->option->type;
elems[i].offset = opt->offset;
- elems[i].isset_offset = 0; /* not supported for local relopts yet */
i++;
}
diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c
index 733ef40ae7..977babff54 100644
--- a/src/backend/commands/vacuum.c
+++ b/src/backend/commands/vacuum.c
@@ -2221,9 +2221,9 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams params,
{
StdRdOptions *opts = (StdRdOptions *) rel->rd_options;
- if (opts && opts->vacuum_truncate_set)
+ if (opts && opts->vacuum_truncate != TERNARY_UNSET)
{
- if (opts->vacuum_truncate)
+ if (opts->vacuum_truncate == TERNARY_TRUE)
params.truncate = VACOPTVALUE_ENABLED;
else
params.truncate = VACOPTVALUE_DISABLED;
diff --git a/src/include/access/reloptions.h b/src/include/access/reloptions.h
index a604a4702c..a436697658 100644
--- a/src/include/access/reloptions.h
+++ b/src/include/access/reloptions.h
@@ -29,6 +29,7 @@
typedef enum relopt_type
{
RELOPT_TYPE_BOOL,
+ RELOPT_TYPE_TERNARY, /* on, off, unset */
RELOPT_TYPE_INT,
RELOPT_TYPE_REAL,
RELOPT_TYPE_ENUM,
@@ -80,6 +81,7 @@ typedef struct relopt_value
union
{
bool bool_val;
+ ternary ternary_val;
int int_val;
double real_val;
int enum_val;
@@ -94,6 +96,12 @@ typedef struct relopt_bool
bool default_val;
} relopt_bool;
+typedef struct relopt_rernary
+{
+ relopt_gen gen;
+ int default_val;
+} relopt_ternary;
+
typedef struct relopt_int
{
relopt_gen gen;
@@ -152,19 +160,6 @@ typedef struct
const char *optname; /* option's name */
relopt_type opttype; /* option's datatype */
int offset; /* offset of field in result struct */
-
- /*
- * isset_offset is an optional offset of a field in the result struct that
- * stores whether the option is explicitly set for the relation or if it
- * just picked up the default value. In most cases, this can be
- * accomplished by giving the reloption a special out-of-range default
- * value (e.g., some integer reloptions use -2), but this isn't always
- * possible. For example, a Boolean reloption cannot be given an
- * out-of-range default, so we need another way to discover the source of
- * its value. This offset is only used if given a value greater than
- * zero.
- */
- int isset_offset;
} relopt_parse_elt;
/* Local reloption definition */
@@ -195,6 +190,8 @@ typedef struct local_relopts
extern relopt_kind add_reloption_kind(void);
extern void add_bool_reloption(bits32 kinds, const char *name, const char *desc,
bool default_val, LOCKMODE lockmode);
+extern void add_ternary_reloption(bits32 kinds, const char *name,
+ const char *desc, int default_val, LOCKMODE lockmode);
extern void add_int_reloption(bits32 kinds, const char *name, const char *desc,
int default_val, int min_val, int max_val,
LOCKMODE lockmode);
@@ -214,6 +211,9 @@ extern void register_reloptions_validator(local_relopts *relopts,
extern void add_local_bool_reloption(local_relopts *relopts, const char *name,
const char *desc, bool default_val,
int offset);
+extern void add_local_ternary_reloption(local_relopts *relopts,
+ const char *name, const char *desc,
+ ternary default_val, int offset);
extern void add_local_int_reloption(local_relopts *relopts, const char *name,
const char *desc, int default_val,
int min_val, int max_val, int offset);
diff --git a/src/include/c.h b/src/include/c.h
index 39022f8a9d..9c59ccccf8 100644
--- a/src/include/c.h
+++ b/src/include/c.h
@@ -488,6 +488,22 @@ typedef void (*pg_funcptr_t) (void);
#include <stdbool.h>
+/*
+ * ternary
+ * Boolean value with an extrea "unset" option
+ *
+ * Ternary data type is used in relation options that can be "true", "false" or
+ * "unset". Since relation options are used deep inside the PostgreSQL code,
+ * this type is declared globally.
+*/
+
+typedef enum ternary
+{
+ TERNARY_FALSE = 0,
+ TERNARY_TRUE = 1,
+ TERNARY_UNSET = -1
+} ternary;
+
/* ----------------------------------------------------------------
* Section 3: standard system types
diff --git a/src/include/utils/rel.h b/src/include/utils/rel.h
index b552359915..7346d618f9 100644
--- a/src/include/utils/rel.h
+++ b/src/include/utils/rel.h
@@ -346,8 +346,7 @@ typedef struct StdRdOptions
bool user_catalog_table; /* use as an additional catalog relation */
int parallel_workers; /* max number of parallel workers */
StdRdOptIndexCleanup vacuum_index_cleanup; /* controls index vacuuming */
- bool vacuum_truncate; /* enables vacuum to truncate a relation */
- bool vacuum_truncate_set; /* whether vacuum_truncate is set */
+ ternary vacuum_truncate; /* enables vacuum to truncate a relation */
/*
* Fraction of pages in a relation that vacuum can eagerly scan and fail
--
2.39.2
--nextPart4155600.3ZeAukHxDK
Content-Disposition: attachment;
filename="v2a-0003-Add-alias-to-be-used-as-unset-state.patch"
Content-Transfer-Encoding: 7Bit
Content-Type: text/x-patch; charset="unicode-2-0-utf-8";
name="v2a-0003-Add-alias-to-be-used-as-unset-state.patch"
^ permalink raw reply [nested|flat] 868+ messages in thread
* [PATCH v2 2/4] Introduce ternary reloptions
@ 2025-09-04 14:41 Nikolay Shaplov <[email protected]>
0 siblings, 0 replies; 868+ messages in thread
From: Nikolay Shaplov @ 2025-09-04 14:41 UTC (permalink / raw)
Introduce ternary reloption as a replacement for current `vacuum_truncate`
implementation. Remove `vacuum_truncate_set` additional flag and using
`TERNARY_UNSET` value instead.
---
src/backend/access/common/reloptions.c | 129 ++++++++++++++++++++-----
src/backend/commands/vacuum.c | 4 +-
src/include/access/reloptions.h | 26 ++---
src/include/c.h | 16 +++
src/include/utils/rel.h | 3 +-
5 files changed, 135 insertions(+), 43 deletions(-)
diff --git a/src/backend/access/common/reloptions.c b/src/backend/access/common/reloptions.c
index 0af3fea68f..f543f11001 100644
--- a/src/backend/access/common/reloptions.c
+++ b/src/backend/access/common/reloptions.c
@@ -40,9 +40,9 @@
*
* To add an option:
*
- * (i) decide on a type (bool, integer, real, enum, string), name, default
- * value, upper and lower bounds (if applicable); for strings, consider a
- * validation routine.
+ * (i) decide on a type (bool, ternary, integer, real, enum, string), name,
+ * default value, upper and lower bounds (if applicable); for strings,
+ * consider a validation routine.
* (ii) add a record below (or use add_<type>_reloption).
* (iii) add it to the appropriate options struct (perhaps StdRdOptions)
* (iv) add it to the appropriate handling routine (perhaps
@@ -147,15 +147,6 @@ static relopt_bool boolRelOpts[] =
},
false
},
- {
- {
- "vacuum_truncate",
- "Enables vacuum to truncate empty pages at the end of this table",
- RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
- ShareUpdateExclusiveLock
- },
- true
- },
{
{
"deduplicate_items",
@@ -170,6 +161,21 @@ static relopt_bool boolRelOpts[] =
{{NULL}}
};
+static relopt_ternary ternaryRelOpts[] =
+{
+ {
+ {
+ "vacuum_truncate",
+ "Enables vacuum to truncate empty pages at the end of this table",
+ RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
+ ShareUpdateExclusiveLock
+ },
+ TERNARY_UNSET
+ },
+ /* list terminator */
+ {{NULL}}
+};
+
static relopt_int intRelOpts[] =
{
{
@@ -600,6 +606,13 @@ initialize_reloptions(void)
boolRelOpts[i].gen.lockmode));
j++;
}
+ for (i = 0; ternaryRelOpts[i].gen.name; i++)
+ {
+ Assert(DoLockModesConflict(ternaryRelOpts[i].gen.lockmode,
+ ternaryRelOpts[i].gen.lockmode));
+ j++;
+ }
+
for (i = 0; intRelOpts[i].gen.name; i++)
{
Assert(DoLockModesConflict(intRelOpts[i].gen.lockmode,
@@ -640,6 +653,14 @@ initialize_reloptions(void)
j++;
}
+ for (i = 0; ternaryRelOpts[i].gen.name; i++)
+ {
+ relOpts[j] = &ternaryRelOpts[i].gen;
+ relOpts[j]->type = RELOPT_TYPE_TERNARY;
+ relOpts[j]->namelen = strlen(relOpts[j]->name);
+ j++;
+ }
+
for (i = 0; intRelOpts[i].gen.name; i++)
{
relOpts[j] = &intRelOpts[i].gen;
@@ -800,6 +821,9 @@ allocate_reloption(bits32 kinds, int type, const char *name, const char *desc,
case RELOPT_TYPE_BOOL:
size = sizeof(relopt_bool);
break;
+ case RELOPT_TYPE_TERNARY:
+ size = sizeof(relopt_ternary);
+ break;
case RELOPT_TYPE_INT:
size = sizeof(relopt_int);
break;
@@ -883,6 +907,54 @@ add_local_bool_reloption(local_relopts *relopts, const char *name,
add_local_reloption(relopts, (relopt_gen *) newoption, offset);
}
+/*
+ * init_ternary_reloption
+ * Allocate and initialize a new ternary reloption
+ */
+static relopt_ternary *
+init_ternary_reloption(bits32 kinds, const char *name, const char *desc,
+ ternary default_val, LOCKMODE lockmode)
+{
+ relopt_ternary *newoption;
+
+ newoption = (relopt_ternary *) allocate_reloption(kinds,
+ RELOPT_TYPE_TERNARY, name, desc, lockmode);
+ newoption->default_val = default_val;
+
+ return newoption;
+}
+
+/*
+ * add_ternary_reloption
+ * Add a new ternary reloption
+ */
+void
+add_ternary_reloption(bits32 kinds, const char *name, const char *desc,
+ ternary default_val, LOCKMODE lockmode)
+{
+ relopt_ternary *newoption = init_ternary_reloption(kinds, name, desc,
+ default_val, lockmode);
+
+ add_reloption((relopt_gen *) newoption);
+}
+
+/*
+ * add_local_ternary_reloption
+ * Add a new ternary local reloption
+ *
+ * 'offset' is offset of ternary-typed field.
+ */
+void
+add_local_ternary_reloption(local_relopts *relopts, const char *name,
+ const char *desc, ternary default_val,
+ int offset)
+{
+ relopt_ternary *newoption = init_ternary_reloption(RELOPT_KIND_LOCAL,
+ name, desc,
+ default_val, 0);
+
+ add_local_reloption(relopts, (relopt_gen *) newoption, offset);
+}
/*
* init_real_reloption
@@ -1617,6 +1689,18 @@ parse_one_reloption(relopt_value *option, char *text_str, int text_len,
option->gen->name, value)));
}
break;
+ case RELOPT_TYPE_TERNARY:
+ {
+ bool b;
+ parsed = parse_bool(value, &b);
+ option->values.ternary_val = b ? TERNARY_TRUE : TERNARY_FALSE;
+ if (validate && !parsed)
+ ereport(ERROR,
+ (errcode(ERRCODE_INVALID_PARAMETER_VALUE),
+ errmsg("invalid value for ternary option \"%s\": %s",
+ option->gen->name, value)));
+ }
+ break;
case RELOPT_TYPE_INT:
{
relopt_int *optint = (relopt_int *) option->gen;
@@ -1780,17 +1864,6 @@ fillRelOptions(void *rdopts, Size basesize,
char *itempos = ((char *) rdopts) + elems[j].offset;
char *string_val;
- /*
- * If isset_offset is provided, store whether the reloption is
- * set there.
- */
- if (elems[j].isset_offset > 0)
- {
- char *setpos = ((char *) rdopts) + elems[j].isset_offset;
-
- *(bool *) setpos = options[i].isset;
- }
-
switch (options[i].gen->type)
{
case RELOPT_TYPE_BOOL:
@@ -1798,6 +1871,11 @@ fillRelOptions(void *rdopts, Size basesize,
options[i].values.bool_val :
((relopt_bool *) options[i].gen)->default_val;
break;
+ case RELOPT_TYPE_TERNARY:
+ *(ternary *) itempos = options[i].isset ?
+ options[i].values.ternary_val :
+ ((relopt_ternary *) options[i].gen)->default_val;
+ break;
case RELOPT_TYPE_INT:
*(int *) itempos = options[i].isset ?
options[i].values.int_val :
@@ -1912,8 +1990,8 @@ default_reloptions(Datum reloptions, bool validate, relopt_kind kind)
offsetof(StdRdOptions, parallel_workers)},
{"vacuum_index_cleanup", RELOPT_TYPE_ENUM,
offsetof(StdRdOptions, vacuum_index_cleanup)},
- {"vacuum_truncate", RELOPT_TYPE_BOOL,
- offsetof(StdRdOptions, vacuum_truncate), offsetof(StdRdOptions, vacuum_truncate_set)},
+ {"vacuum_truncate", RELOPT_TYPE_TERNARY,
+ offsetof(StdRdOptions, vacuum_truncate)},
{"vacuum_max_eager_freeze_failure_rate", RELOPT_TYPE_REAL,
offsetof(StdRdOptions, vacuum_max_eager_freeze_failure_rate)}
};
@@ -1993,7 +2071,6 @@ build_local_reloptions(local_relopts *relopts, Datum options, bool validate)
elems[i].optname = opt->option->name;
elems[i].opttype = opt->option->type;
elems[i].offset = opt->offset;
- elems[i].isset_offset = 0; /* not supported for local relopts yet */
i++;
}
diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c
index 733ef40ae7..977babff54 100644
--- a/src/backend/commands/vacuum.c
+++ b/src/backend/commands/vacuum.c
@@ -2221,9 +2221,9 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams params,
{
StdRdOptions *opts = (StdRdOptions *) rel->rd_options;
- if (opts && opts->vacuum_truncate_set)
+ if (opts && opts->vacuum_truncate != TERNARY_UNSET)
{
- if (opts->vacuum_truncate)
+ if (opts->vacuum_truncate == TERNARY_TRUE)
params.truncate = VACOPTVALUE_ENABLED;
else
params.truncate = VACOPTVALUE_DISABLED;
diff --git a/src/include/access/reloptions.h b/src/include/access/reloptions.h
index a604a4702c..a436697658 100644
--- a/src/include/access/reloptions.h
+++ b/src/include/access/reloptions.h
@@ -29,6 +29,7 @@
typedef enum relopt_type
{
RELOPT_TYPE_BOOL,
+ RELOPT_TYPE_TERNARY, /* on, off, unset */
RELOPT_TYPE_INT,
RELOPT_TYPE_REAL,
RELOPT_TYPE_ENUM,
@@ -80,6 +81,7 @@ typedef struct relopt_value
union
{
bool bool_val;
+ ternary ternary_val;
int int_val;
double real_val;
int enum_val;
@@ -94,6 +96,12 @@ typedef struct relopt_bool
bool default_val;
} relopt_bool;
+typedef struct relopt_rernary
+{
+ relopt_gen gen;
+ int default_val;
+} relopt_ternary;
+
typedef struct relopt_int
{
relopt_gen gen;
@@ -152,19 +160,6 @@ typedef struct
const char *optname; /* option's name */
relopt_type opttype; /* option's datatype */
int offset; /* offset of field in result struct */
-
- /*
- * isset_offset is an optional offset of a field in the result struct that
- * stores whether the option is explicitly set for the relation or if it
- * just picked up the default value. In most cases, this can be
- * accomplished by giving the reloption a special out-of-range default
- * value (e.g., some integer reloptions use -2), but this isn't always
- * possible. For example, a Boolean reloption cannot be given an
- * out-of-range default, so we need another way to discover the source of
- * its value. This offset is only used if given a value greater than
- * zero.
- */
- int isset_offset;
} relopt_parse_elt;
/* Local reloption definition */
@@ -195,6 +190,8 @@ typedef struct local_relopts
extern relopt_kind add_reloption_kind(void);
extern void add_bool_reloption(bits32 kinds, const char *name, const char *desc,
bool default_val, LOCKMODE lockmode);
+extern void add_ternary_reloption(bits32 kinds, const char *name,
+ const char *desc, int default_val, LOCKMODE lockmode);
extern void add_int_reloption(bits32 kinds, const char *name, const char *desc,
int default_val, int min_val, int max_val,
LOCKMODE lockmode);
@@ -214,6 +211,9 @@ extern void register_reloptions_validator(local_relopts *relopts,
extern void add_local_bool_reloption(local_relopts *relopts, const char *name,
const char *desc, bool default_val,
int offset);
+extern void add_local_ternary_reloption(local_relopts *relopts,
+ const char *name, const char *desc,
+ ternary default_val, int offset);
extern void add_local_int_reloption(local_relopts *relopts, const char *name,
const char *desc, int default_val,
int min_val, int max_val, int offset);
diff --git a/src/include/c.h b/src/include/c.h
index 39022f8a9d..9c59ccccf8 100644
--- a/src/include/c.h
+++ b/src/include/c.h
@@ -488,6 +488,22 @@ typedef void (*pg_funcptr_t) (void);
#include <stdbool.h>
+/*
+ * ternary
+ * Boolean value with an extrea "unset" option
+ *
+ * Ternary data type is used in relation options that can be "true", "false" or
+ * "unset". Since relation options are used deep inside the PostgreSQL code,
+ * this type is declared globally.
+*/
+
+typedef enum ternary
+{
+ TERNARY_FALSE = 0,
+ TERNARY_TRUE = 1,
+ TERNARY_UNSET = -1
+} ternary;
+
/* ----------------------------------------------------------------
* Section 3: standard system types
diff --git a/src/include/utils/rel.h b/src/include/utils/rel.h
index b552359915..7346d618f9 100644
--- a/src/include/utils/rel.h
+++ b/src/include/utils/rel.h
@@ -346,8 +346,7 @@ typedef struct StdRdOptions
bool user_catalog_table; /* use as an additional catalog relation */
int parallel_workers; /* max number of parallel workers */
StdRdOptIndexCleanup vacuum_index_cleanup; /* controls index vacuuming */
- bool vacuum_truncate; /* enables vacuum to truncate a relation */
- bool vacuum_truncate_set; /* whether vacuum_truncate is set */
+ ternary vacuum_truncate; /* enables vacuum to truncate a relation */
/*
* Fraction of pages in a relation that vacuum can eagerly scan and fail
--
2.39.2
--nextPart5129932.5fSG56mABF
Content-Disposition: attachment;
filename="v2-0003-Add-alias-to-be-used-as-unset-state.patch"
Content-Transfer-Encoding: 7Bit
Content-Type: text/x-patch; charset="unicode-2-0-utf-8";
name="v2-0003-Add-alias-to-be-used-as-unset-state.patch"
^ permalink raw reply [nested|flat] 868+ messages in thread
* [PATCH v2a 2/4] Introduce ternary reloptions
@ 2025-09-04 14:41 Nikolay Shaplov <[email protected]>
0 siblings, 0 replies; 868+ messages in thread
From: Nikolay Shaplov @ 2025-09-04 14:41 UTC (permalink / raw)
Introduce ternary reloption as a replacement for current `vacuum_truncate`
implementation. Remove `vacuum_truncate_set` additional flag and using
`TERNARY_UNSET` value instead.
---
src/backend/access/common/reloptions.c | 129 ++++++++++++++++++++-----
src/backend/commands/vacuum.c | 4 +-
src/include/access/reloptions.h | 26 ++---
src/include/c.h | 16 +++
src/include/utils/rel.h | 3 +-
5 files changed, 135 insertions(+), 43 deletions(-)
diff --git a/src/backend/access/common/reloptions.c b/src/backend/access/common/reloptions.c
index 0af3fea68f..f543f11001 100644
--- a/src/backend/access/common/reloptions.c
+++ b/src/backend/access/common/reloptions.c
@@ -40,9 +40,9 @@
*
* To add an option:
*
- * (i) decide on a type (bool, integer, real, enum, string), name, default
- * value, upper and lower bounds (if applicable); for strings, consider a
- * validation routine.
+ * (i) decide on a type (bool, ternary, integer, real, enum, string), name,
+ * default value, upper and lower bounds (if applicable); for strings,
+ * consider a validation routine.
* (ii) add a record below (or use add_<type>_reloption).
* (iii) add it to the appropriate options struct (perhaps StdRdOptions)
* (iv) add it to the appropriate handling routine (perhaps
@@ -147,15 +147,6 @@ static relopt_bool boolRelOpts[] =
},
false
},
- {
- {
- "vacuum_truncate",
- "Enables vacuum to truncate empty pages at the end of this table",
- RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
- ShareUpdateExclusiveLock
- },
- true
- },
{
{
"deduplicate_items",
@@ -170,6 +161,21 @@ static relopt_bool boolRelOpts[] =
{{NULL}}
};
+static relopt_ternary ternaryRelOpts[] =
+{
+ {
+ {
+ "vacuum_truncate",
+ "Enables vacuum to truncate empty pages at the end of this table",
+ RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
+ ShareUpdateExclusiveLock
+ },
+ TERNARY_UNSET
+ },
+ /* list terminator */
+ {{NULL}}
+};
+
static relopt_int intRelOpts[] =
{
{
@@ -600,6 +606,13 @@ initialize_reloptions(void)
boolRelOpts[i].gen.lockmode));
j++;
}
+ for (i = 0; ternaryRelOpts[i].gen.name; i++)
+ {
+ Assert(DoLockModesConflict(ternaryRelOpts[i].gen.lockmode,
+ ternaryRelOpts[i].gen.lockmode));
+ j++;
+ }
+
for (i = 0; intRelOpts[i].gen.name; i++)
{
Assert(DoLockModesConflict(intRelOpts[i].gen.lockmode,
@@ -640,6 +653,14 @@ initialize_reloptions(void)
j++;
}
+ for (i = 0; ternaryRelOpts[i].gen.name; i++)
+ {
+ relOpts[j] = &ternaryRelOpts[i].gen;
+ relOpts[j]->type = RELOPT_TYPE_TERNARY;
+ relOpts[j]->namelen = strlen(relOpts[j]->name);
+ j++;
+ }
+
for (i = 0; intRelOpts[i].gen.name; i++)
{
relOpts[j] = &intRelOpts[i].gen;
@@ -800,6 +821,9 @@ allocate_reloption(bits32 kinds, int type, const char *name, const char *desc,
case RELOPT_TYPE_BOOL:
size = sizeof(relopt_bool);
break;
+ case RELOPT_TYPE_TERNARY:
+ size = sizeof(relopt_ternary);
+ break;
case RELOPT_TYPE_INT:
size = sizeof(relopt_int);
break;
@@ -883,6 +907,54 @@ add_local_bool_reloption(local_relopts *relopts, const char *name,
add_local_reloption(relopts, (relopt_gen *) newoption, offset);
}
+/*
+ * init_ternary_reloption
+ * Allocate and initialize a new ternary reloption
+ */
+static relopt_ternary *
+init_ternary_reloption(bits32 kinds, const char *name, const char *desc,
+ ternary default_val, LOCKMODE lockmode)
+{
+ relopt_ternary *newoption;
+
+ newoption = (relopt_ternary *) allocate_reloption(kinds,
+ RELOPT_TYPE_TERNARY, name, desc, lockmode);
+ newoption->default_val = default_val;
+
+ return newoption;
+}
+
+/*
+ * add_ternary_reloption
+ * Add a new ternary reloption
+ */
+void
+add_ternary_reloption(bits32 kinds, const char *name, const char *desc,
+ ternary default_val, LOCKMODE lockmode)
+{
+ relopt_ternary *newoption = init_ternary_reloption(kinds, name, desc,
+ default_val, lockmode);
+
+ add_reloption((relopt_gen *) newoption);
+}
+
+/*
+ * add_local_ternary_reloption
+ * Add a new ternary local reloption
+ *
+ * 'offset' is offset of ternary-typed field.
+ */
+void
+add_local_ternary_reloption(local_relopts *relopts, const char *name,
+ const char *desc, ternary default_val,
+ int offset)
+{
+ relopt_ternary *newoption = init_ternary_reloption(RELOPT_KIND_LOCAL,
+ name, desc,
+ default_val, 0);
+
+ add_local_reloption(relopts, (relopt_gen *) newoption, offset);
+}
/*
* init_real_reloption
@@ -1617,6 +1689,18 @@ parse_one_reloption(relopt_value *option, char *text_str, int text_len,
option->gen->name, value)));
}
break;
+ case RELOPT_TYPE_TERNARY:
+ {
+ bool b;
+ parsed = parse_bool(value, &b);
+ option->values.ternary_val = b ? TERNARY_TRUE : TERNARY_FALSE;
+ if (validate && !parsed)
+ ereport(ERROR,
+ (errcode(ERRCODE_INVALID_PARAMETER_VALUE),
+ errmsg("invalid value for ternary option \"%s\": %s",
+ option->gen->name, value)));
+ }
+ break;
case RELOPT_TYPE_INT:
{
relopt_int *optint = (relopt_int *) option->gen;
@@ -1780,17 +1864,6 @@ fillRelOptions(void *rdopts, Size basesize,
char *itempos = ((char *) rdopts) + elems[j].offset;
char *string_val;
- /*
- * If isset_offset is provided, store whether the reloption is
- * set there.
- */
- if (elems[j].isset_offset > 0)
- {
- char *setpos = ((char *) rdopts) + elems[j].isset_offset;
-
- *(bool *) setpos = options[i].isset;
- }
-
switch (options[i].gen->type)
{
case RELOPT_TYPE_BOOL:
@@ -1798,6 +1871,11 @@ fillRelOptions(void *rdopts, Size basesize,
options[i].values.bool_val :
((relopt_bool *) options[i].gen)->default_val;
break;
+ case RELOPT_TYPE_TERNARY:
+ *(ternary *) itempos = options[i].isset ?
+ options[i].values.ternary_val :
+ ((relopt_ternary *) options[i].gen)->default_val;
+ break;
case RELOPT_TYPE_INT:
*(int *) itempos = options[i].isset ?
options[i].values.int_val :
@@ -1912,8 +1990,8 @@ default_reloptions(Datum reloptions, bool validate, relopt_kind kind)
offsetof(StdRdOptions, parallel_workers)},
{"vacuum_index_cleanup", RELOPT_TYPE_ENUM,
offsetof(StdRdOptions, vacuum_index_cleanup)},
- {"vacuum_truncate", RELOPT_TYPE_BOOL,
- offsetof(StdRdOptions, vacuum_truncate), offsetof(StdRdOptions, vacuum_truncate_set)},
+ {"vacuum_truncate", RELOPT_TYPE_TERNARY,
+ offsetof(StdRdOptions, vacuum_truncate)},
{"vacuum_max_eager_freeze_failure_rate", RELOPT_TYPE_REAL,
offsetof(StdRdOptions, vacuum_max_eager_freeze_failure_rate)}
};
@@ -1993,7 +2071,6 @@ build_local_reloptions(local_relopts *relopts, Datum options, bool validate)
elems[i].optname = opt->option->name;
elems[i].opttype = opt->option->type;
elems[i].offset = opt->offset;
- elems[i].isset_offset = 0; /* not supported for local relopts yet */
i++;
}
diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c
index 733ef40ae7..977babff54 100644
--- a/src/backend/commands/vacuum.c
+++ b/src/backend/commands/vacuum.c
@@ -2221,9 +2221,9 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams params,
{
StdRdOptions *opts = (StdRdOptions *) rel->rd_options;
- if (opts && opts->vacuum_truncate_set)
+ if (opts && opts->vacuum_truncate != TERNARY_UNSET)
{
- if (opts->vacuum_truncate)
+ if (opts->vacuum_truncate == TERNARY_TRUE)
params.truncate = VACOPTVALUE_ENABLED;
else
params.truncate = VACOPTVALUE_DISABLED;
diff --git a/src/include/access/reloptions.h b/src/include/access/reloptions.h
index a604a4702c..a436697658 100644
--- a/src/include/access/reloptions.h
+++ b/src/include/access/reloptions.h
@@ -29,6 +29,7 @@
typedef enum relopt_type
{
RELOPT_TYPE_BOOL,
+ RELOPT_TYPE_TERNARY, /* on, off, unset */
RELOPT_TYPE_INT,
RELOPT_TYPE_REAL,
RELOPT_TYPE_ENUM,
@@ -80,6 +81,7 @@ typedef struct relopt_value
union
{
bool bool_val;
+ ternary ternary_val;
int int_val;
double real_val;
int enum_val;
@@ -94,6 +96,12 @@ typedef struct relopt_bool
bool default_val;
} relopt_bool;
+typedef struct relopt_rernary
+{
+ relopt_gen gen;
+ int default_val;
+} relopt_ternary;
+
typedef struct relopt_int
{
relopt_gen gen;
@@ -152,19 +160,6 @@ typedef struct
const char *optname; /* option's name */
relopt_type opttype; /* option's datatype */
int offset; /* offset of field in result struct */
-
- /*
- * isset_offset is an optional offset of a field in the result struct that
- * stores whether the option is explicitly set for the relation or if it
- * just picked up the default value. In most cases, this can be
- * accomplished by giving the reloption a special out-of-range default
- * value (e.g., some integer reloptions use -2), but this isn't always
- * possible. For example, a Boolean reloption cannot be given an
- * out-of-range default, so we need another way to discover the source of
- * its value. This offset is only used if given a value greater than
- * zero.
- */
- int isset_offset;
} relopt_parse_elt;
/* Local reloption definition */
@@ -195,6 +190,8 @@ typedef struct local_relopts
extern relopt_kind add_reloption_kind(void);
extern void add_bool_reloption(bits32 kinds, const char *name, const char *desc,
bool default_val, LOCKMODE lockmode);
+extern void add_ternary_reloption(bits32 kinds, const char *name,
+ const char *desc, int default_val, LOCKMODE lockmode);
extern void add_int_reloption(bits32 kinds, const char *name, const char *desc,
int default_val, int min_val, int max_val,
LOCKMODE lockmode);
@@ -214,6 +211,9 @@ extern void register_reloptions_validator(local_relopts *relopts,
extern void add_local_bool_reloption(local_relopts *relopts, const char *name,
const char *desc, bool default_val,
int offset);
+extern void add_local_ternary_reloption(local_relopts *relopts,
+ const char *name, const char *desc,
+ ternary default_val, int offset);
extern void add_local_int_reloption(local_relopts *relopts, const char *name,
const char *desc, int default_val,
int min_val, int max_val, int offset);
diff --git a/src/include/c.h b/src/include/c.h
index 39022f8a9d..9c59ccccf8 100644
--- a/src/include/c.h
+++ b/src/include/c.h
@@ -488,6 +488,22 @@ typedef void (*pg_funcptr_t) (void);
#include <stdbool.h>
+/*
+ * ternary
+ * Boolean value with an extrea "unset" option
+ *
+ * Ternary data type is used in relation options that can be "true", "false" or
+ * "unset". Since relation options are used deep inside the PostgreSQL code,
+ * this type is declared globally.
+*/
+
+typedef enum ternary
+{
+ TERNARY_FALSE = 0,
+ TERNARY_TRUE = 1,
+ TERNARY_UNSET = -1
+} ternary;
+
/* ----------------------------------------------------------------
* Section 3: standard system types
diff --git a/src/include/utils/rel.h b/src/include/utils/rel.h
index b552359915..7346d618f9 100644
--- a/src/include/utils/rel.h
+++ b/src/include/utils/rel.h
@@ -346,8 +346,7 @@ typedef struct StdRdOptions
bool user_catalog_table; /* use as an additional catalog relation */
int parallel_workers; /* max number of parallel workers */
StdRdOptIndexCleanup vacuum_index_cleanup; /* controls index vacuuming */
- bool vacuum_truncate; /* enables vacuum to truncate a relation */
- bool vacuum_truncate_set; /* whether vacuum_truncate is set */
+ ternary vacuum_truncate; /* enables vacuum to truncate a relation */
/*
* Fraction of pages in a relation that vacuum can eagerly scan and fail
--
2.39.2
--nextPart4155600.3ZeAukHxDK
Content-Disposition: attachment;
filename="v2a-0003-Add-alias-to-be-used-as-unset-state.patch"
Content-Transfer-Encoding: 7Bit
Content-Type: text/x-patch; charset="unicode-2-0-utf-8";
name="v2a-0003-Add-alias-to-be-used-as-unset-state.patch"
^ permalink raw reply [nested|flat] 868+ messages in thread
* [PATCH v2a 2/4] Introduce ternary reloptions
@ 2025-09-04 14:41 Nikolay Shaplov <[email protected]>
0 siblings, 0 replies; 868+ messages in thread
From: Nikolay Shaplov @ 2025-09-04 14:41 UTC (permalink / raw)
Introduce ternary reloption as a replacement for current `vacuum_truncate`
implementation. Remove `vacuum_truncate_set` additional flag and using
`TERNARY_UNSET` value instead.
---
src/backend/access/common/reloptions.c | 129 ++++++++++++++++++++-----
src/backend/commands/vacuum.c | 4 +-
src/include/access/reloptions.h | 26 ++---
src/include/c.h | 16 +++
src/include/utils/rel.h | 3 +-
5 files changed, 135 insertions(+), 43 deletions(-)
diff --git a/src/backend/access/common/reloptions.c b/src/backend/access/common/reloptions.c
index 0af3fea68f..f543f11001 100644
--- a/src/backend/access/common/reloptions.c
+++ b/src/backend/access/common/reloptions.c
@@ -40,9 +40,9 @@
*
* To add an option:
*
- * (i) decide on a type (bool, integer, real, enum, string), name, default
- * value, upper and lower bounds (if applicable); for strings, consider a
- * validation routine.
+ * (i) decide on a type (bool, ternary, integer, real, enum, string), name,
+ * default value, upper and lower bounds (if applicable); for strings,
+ * consider a validation routine.
* (ii) add a record below (or use add_<type>_reloption).
* (iii) add it to the appropriate options struct (perhaps StdRdOptions)
* (iv) add it to the appropriate handling routine (perhaps
@@ -147,15 +147,6 @@ static relopt_bool boolRelOpts[] =
},
false
},
- {
- {
- "vacuum_truncate",
- "Enables vacuum to truncate empty pages at the end of this table",
- RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
- ShareUpdateExclusiveLock
- },
- true
- },
{
{
"deduplicate_items",
@@ -170,6 +161,21 @@ static relopt_bool boolRelOpts[] =
{{NULL}}
};
+static relopt_ternary ternaryRelOpts[] =
+{
+ {
+ {
+ "vacuum_truncate",
+ "Enables vacuum to truncate empty pages at the end of this table",
+ RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
+ ShareUpdateExclusiveLock
+ },
+ TERNARY_UNSET
+ },
+ /* list terminator */
+ {{NULL}}
+};
+
static relopt_int intRelOpts[] =
{
{
@@ -600,6 +606,13 @@ initialize_reloptions(void)
boolRelOpts[i].gen.lockmode));
j++;
}
+ for (i = 0; ternaryRelOpts[i].gen.name; i++)
+ {
+ Assert(DoLockModesConflict(ternaryRelOpts[i].gen.lockmode,
+ ternaryRelOpts[i].gen.lockmode));
+ j++;
+ }
+
for (i = 0; intRelOpts[i].gen.name; i++)
{
Assert(DoLockModesConflict(intRelOpts[i].gen.lockmode,
@@ -640,6 +653,14 @@ initialize_reloptions(void)
j++;
}
+ for (i = 0; ternaryRelOpts[i].gen.name; i++)
+ {
+ relOpts[j] = &ternaryRelOpts[i].gen;
+ relOpts[j]->type = RELOPT_TYPE_TERNARY;
+ relOpts[j]->namelen = strlen(relOpts[j]->name);
+ j++;
+ }
+
for (i = 0; intRelOpts[i].gen.name; i++)
{
relOpts[j] = &intRelOpts[i].gen;
@@ -800,6 +821,9 @@ allocate_reloption(bits32 kinds, int type, const char *name, const char *desc,
case RELOPT_TYPE_BOOL:
size = sizeof(relopt_bool);
break;
+ case RELOPT_TYPE_TERNARY:
+ size = sizeof(relopt_ternary);
+ break;
case RELOPT_TYPE_INT:
size = sizeof(relopt_int);
break;
@@ -883,6 +907,54 @@ add_local_bool_reloption(local_relopts *relopts, const char *name,
add_local_reloption(relopts, (relopt_gen *) newoption, offset);
}
+/*
+ * init_ternary_reloption
+ * Allocate and initialize a new ternary reloption
+ */
+static relopt_ternary *
+init_ternary_reloption(bits32 kinds, const char *name, const char *desc,
+ ternary default_val, LOCKMODE lockmode)
+{
+ relopt_ternary *newoption;
+
+ newoption = (relopt_ternary *) allocate_reloption(kinds,
+ RELOPT_TYPE_TERNARY, name, desc, lockmode);
+ newoption->default_val = default_val;
+
+ return newoption;
+}
+
+/*
+ * add_ternary_reloption
+ * Add a new ternary reloption
+ */
+void
+add_ternary_reloption(bits32 kinds, const char *name, const char *desc,
+ ternary default_val, LOCKMODE lockmode)
+{
+ relopt_ternary *newoption = init_ternary_reloption(kinds, name, desc,
+ default_val, lockmode);
+
+ add_reloption((relopt_gen *) newoption);
+}
+
+/*
+ * add_local_ternary_reloption
+ * Add a new ternary local reloption
+ *
+ * 'offset' is offset of ternary-typed field.
+ */
+void
+add_local_ternary_reloption(local_relopts *relopts, const char *name,
+ const char *desc, ternary default_val,
+ int offset)
+{
+ relopt_ternary *newoption = init_ternary_reloption(RELOPT_KIND_LOCAL,
+ name, desc,
+ default_val, 0);
+
+ add_local_reloption(relopts, (relopt_gen *) newoption, offset);
+}
/*
* init_real_reloption
@@ -1617,6 +1689,18 @@ parse_one_reloption(relopt_value *option, char *text_str, int text_len,
option->gen->name, value)));
}
break;
+ case RELOPT_TYPE_TERNARY:
+ {
+ bool b;
+ parsed = parse_bool(value, &b);
+ option->values.ternary_val = b ? TERNARY_TRUE : TERNARY_FALSE;
+ if (validate && !parsed)
+ ereport(ERROR,
+ (errcode(ERRCODE_INVALID_PARAMETER_VALUE),
+ errmsg("invalid value for ternary option \"%s\": %s",
+ option->gen->name, value)));
+ }
+ break;
case RELOPT_TYPE_INT:
{
relopt_int *optint = (relopt_int *) option->gen;
@@ -1780,17 +1864,6 @@ fillRelOptions(void *rdopts, Size basesize,
char *itempos = ((char *) rdopts) + elems[j].offset;
char *string_val;
- /*
- * If isset_offset is provided, store whether the reloption is
- * set there.
- */
- if (elems[j].isset_offset > 0)
- {
- char *setpos = ((char *) rdopts) + elems[j].isset_offset;
-
- *(bool *) setpos = options[i].isset;
- }
-
switch (options[i].gen->type)
{
case RELOPT_TYPE_BOOL:
@@ -1798,6 +1871,11 @@ fillRelOptions(void *rdopts, Size basesize,
options[i].values.bool_val :
((relopt_bool *) options[i].gen)->default_val;
break;
+ case RELOPT_TYPE_TERNARY:
+ *(ternary *) itempos = options[i].isset ?
+ options[i].values.ternary_val :
+ ((relopt_ternary *) options[i].gen)->default_val;
+ break;
case RELOPT_TYPE_INT:
*(int *) itempos = options[i].isset ?
options[i].values.int_val :
@@ -1912,8 +1990,8 @@ default_reloptions(Datum reloptions, bool validate, relopt_kind kind)
offsetof(StdRdOptions, parallel_workers)},
{"vacuum_index_cleanup", RELOPT_TYPE_ENUM,
offsetof(StdRdOptions, vacuum_index_cleanup)},
- {"vacuum_truncate", RELOPT_TYPE_BOOL,
- offsetof(StdRdOptions, vacuum_truncate), offsetof(StdRdOptions, vacuum_truncate_set)},
+ {"vacuum_truncate", RELOPT_TYPE_TERNARY,
+ offsetof(StdRdOptions, vacuum_truncate)},
{"vacuum_max_eager_freeze_failure_rate", RELOPT_TYPE_REAL,
offsetof(StdRdOptions, vacuum_max_eager_freeze_failure_rate)}
};
@@ -1993,7 +2071,6 @@ build_local_reloptions(local_relopts *relopts, Datum options, bool validate)
elems[i].optname = opt->option->name;
elems[i].opttype = opt->option->type;
elems[i].offset = opt->offset;
- elems[i].isset_offset = 0; /* not supported for local relopts yet */
i++;
}
diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c
index 733ef40ae7..977babff54 100644
--- a/src/backend/commands/vacuum.c
+++ b/src/backend/commands/vacuum.c
@@ -2221,9 +2221,9 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams params,
{
StdRdOptions *opts = (StdRdOptions *) rel->rd_options;
- if (opts && opts->vacuum_truncate_set)
+ if (opts && opts->vacuum_truncate != TERNARY_UNSET)
{
- if (opts->vacuum_truncate)
+ if (opts->vacuum_truncate == TERNARY_TRUE)
params.truncate = VACOPTVALUE_ENABLED;
else
params.truncate = VACOPTVALUE_DISABLED;
diff --git a/src/include/access/reloptions.h b/src/include/access/reloptions.h
index a604a4702c..a436697658 100644
--- a/src/include/access/reloptions.h
+++ b/src/include/access/reloptions.h
@@ -29,6 +29,7 @@
typedef enum relopt_type
{
RELOPT_TYPE_BOOL,
+ RELOPT_TYPE_TERNARY, /* on, off, unset */
RELOPT_TYPE_INT,
RELOPT_TYPE_REAL,
RELOPT_TYPE_ENUM,
@@ -80,6 +81,7 @@ typedef struct relopt_value
union
{
bool bool_val;
+ ternary ternary_val;
int int_val;
double real_val;
int enum_val;
@@ -94,6 +96,12 @@ typedef struct relopt_bool
bool default_val;
} relopt_bool;
+typedef struct relopt_rernary
+{
+ relopt_gen gen;
+ int default_val;
+} relopt_ternary;
+
typedef struct relopt_int
{
relopt_gen gen;
@@ -152,19 +160,6 @@ typedef struct
const char *optname; /* option's name */
relopt_type opttype; /* option's datatype */
int offset; /* offset of field in result struct */
-
- /*
- * isset_offset is an optional offset of a field in the result struct that
- * stores whether the option is explicitly set for the relation or if it
- * just picked up the default value. In most cases, this can be
- * accomplished by giving the reloption a special out-of-range default
- * value (e.g., some integer reloptions use -2), but this isn't always
- * possible. For example, a Boolean reloption cannot be given an
- * out-of-range default, so we need another way to discover the source of
- * its value. This offset is only used if given a value greater than
- * zero.
- */
- int isset_offset;
} relopt_parse_elt;
/* Local reloption definition */
@@ -195,6 +190,8 @@ typedef struct local_relopts
extern relopt_kind add_reloption_kind(void);
extern void add_bool_reloption(bits32 kinds, const char *name, const char *desc,
bool default_val, LOCKMODE lockmode);
+extern void add_ternary_reloption(bits32 kinds, const char *name,
+ const char *desc, int default_val, LOCKMODE lockmode);
extern void add_int_reloption(bits32 kinds, const char *name, const char *desc,
int default_val, int min_val, int max_val,
LOCKMODE lockmode);
@@ -214,6 +211,9 @@ extern void register_reloptions_validator(local_relopts *relopts,
extern void add_local_bool_reloption(local_relopts *relopts, const char *name,
const char *desc, bool default_val,
int offset);
+extern void add_local_ternary_reloption(local_relopts *relopts,
+ const char *name, const char *desc,
+ ternary default_val, int offset);
extern void add_local_int_reloption(local_relopts *relopts, const char *name,
const char *desc, int default_val,
int min_val, int max_val, int offset);
diff --git a/src/include/c.h b/src/include/c.h
index 39022f8a9d..9c59ccccf8 100644
--- a/src/include/c.h
+++ b/src/include/c.h
@@ -488,6 +488,22 @@ typedef void (*pg_funcptr_t) (void);
#include <stdbool.h>
+/*
+ * ternary
+ * Boolean value with an extrea "unset" option
+ *
+ * Ternary data type is used in relation options that can be "true", "false" or
+ * "unset". Since relation options are used deep inside the PostgreSQL code,
+ * this type is declared globally.
+*/
+
+typedef enum ternary
+{
+ TERNARY_FALSE = 0,
+ TERNARY_TRUE = 1,
+ TERNARY_UNSET = -1
+} ternary;
+
/* ----------------------------------------------------------------
* Section 3: standard system types
diff --git a/src/include/utils/rel.h b/src/include/utils/rel.h
index b552359915..7346d618f9 100644
--- a/src/include/utils/rel.h
+++ b/src/include/utils/rel.h
@@ -346,8 +346,7 @@ typedef struct StdRdOptions
bool user_catalog_table; /* use as an additional catalog relation */
int parallel_workers; /* max number of parallel workers */
StdRdOptIndexCleanup vacuum_index_cleanup; /* controls index vacuuming */
- bool vacuum_truncate; /* enables vacuum to truncate a relation */
- bool vacuum_truncate_set; /* whether vacuum_truncate is set */
+ ternary vacuum_truncate; /* enables vacuum to truncate a relation */
/*
* Fraction of pages in a relation that vacuum can eagerly scan and fail
--
2.39.2
--nextPart4155600.3ZeAukHxDK
Content-Disposition: attachment;
filename="v2a-0003-Add-alias-to-be-used-as-unset-state.patch"
Content-Transfer-Encoding: 7Bit
Content-Type: text/x-patch; charset="unicode-2-0-utf-8";
name="v2a-0003-Add-alias-to-be-used-as-unset-state.patch"
^ permalink raw reply [nested|flat] 868+ messages in thread
* [PATCH v2 2/4] Introduce ternary reloptions
@ 2025-09-04 14:41 Nikolay Shaplov <[email protected]>
0 siblings, 0 replies; 868+ messages in thread
From: Nikolay Shaplov @ 2025-09-04 14:41 UTC (permalink / raw)
Introduce ternary reloption as a replacement for current `vacuum_truncate`
implementation. Remove `vacuum_truncate_set` additional flag and using
`TERNARY_UNSET` value instead.
---
src/backend/access/common/reloptions.c | 129 ++++++++++++++++++++-----
src/backend/commands/vacuum.c | 4 +-
src/include/access/reloptions.h | 26 ++---
src/include/c.h | 16 +++
src/include/utils/rel.h | 3 +-
5 files changed, 135 insertions(+), 43 deletions(-)
diff --git a/src/backend/access/common/reloptions.c b/src/backend/access/common/reloptions.c
index 0af3fea68f..f543f11001 100644
--- a/src/backend/access/common/reloptions.c
+++ b/src/backend/access/common/reloptions.c
@@ -40,9 +40,9 @@
*
* To add an option:
*
- * (i) decide on a type (bool, integer, real, enum, string), name, default
- * value, upper and lower bounds (if applicable); for strings, consider a
- * validation routine.
+ * (i) decide on a type (bool, ternary, integer, real, enum, string), name,
+ * default value, upper and lower bounds (if applicable); for strings,
+ * consider a validation routine.
* (ii) add a record below (or use add_<type>_reloption).
* (iii) add it to the appropriate options struct (perhaps StdRdOptions)
* (iv) add it to the appropriate handling routine (perhaps
@@ -147,15 +147,6 @@ static relopt_bool boolRelOpts[] =
},
false
},
- {
- {
- "vacuum_truncate",
- "Enables vacuum to truncate empty pages at the end of this table",
- RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
- ShareUpdateExclusiveLock
- },
- true
- },
{
{
"deduplicate_items",
@@ -170,6 +161,21 @@ static relopt_bool boolRelOpts[] =
{{NULL}}
};
+static relopt_ternary ternaryRelOpts[] =
+{
+ {
+ {
+ "vacuum_truncate",
+ "Enables vacuum to truncate empty pages at the end of this table",
+ RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
+ ShareUpdateExclusiveLock
+ },
+ TERNARY_UNSET
+ },
+ /* list terminator */
+ {{NULL}}
+};
+
static relopt_int intRelOpts[] =
{
{
@@ -600,6 +606,13 @@ initialize_reloptions(void)
boolRelOpts[i].gen.lockmode));
j++;
}
+ for (i = 0; ternaryRelOpts[i].gen.name; i++)
+ {
+ Assert(DoLockModesConflict(ternaryRelOpts[i].gen.lockmode,
+ ternaryRelOpts[i].gen.lockmode));
+ j++;
+ }
+
for (i = 0; intRelOpts[i].gen.name; i++)
{
Assert(DoLockModesConflict(intRelOpts[i].gen.lockmode,
@@ -640,6 +653,14 @@ initialize_reloptions(void)
j++;
}
+ for (i = 0; ternaryRelOpts[i].gen.name; i++)
+ {
+ relOpts[j] = &ternaryRelOpts[i].gen;
+ relOpts[j]->type = RELOPT_TYPE_TERNARY;
+ relOpts[j]->namelen = strlen(relOpts[j]->name);
+ j++;
+ }
+
for (i = 0; intRelOpts[i].gen.name; i++)
{
relOpts[j] = &intRelOpts[i].gen;
@@ -800,6 +821,9 @@ allocate_reloption(bits32 kinds, int type, const char *name, const char *desc,
case RELOPT_TYPE_BOOL:
size = sizeof(relopt_bool);
break;
+ case RELOPT_TYPE_TERNARY:
+ size = sizeof(relopt_ternary);
+ break;
case RELOPT_TYPE_INT:
size = sizeof(relopt_int);
break;
@@ -883,6 +907,54 @@ add_local_bool_reloption(local_relopts *relopts, const char *name,
add_local_reloption(relopts, (relopt_gen *) newoption, offset);
}
+/*
+ * init_ternary_reloption
+ * Allocate and initialize a new ternary reloption
+ */
+static relopt_ternary *
+init_ternary_reloption(bits32 kinds, const char *name, const char *desc,
+ ternary default_val, LOCKMODE lockmode)
+{
+ relopt_ternary *newoption;
+
+ newoption = (relopt_ternary *) allocate_reloption(kinds,
+ RELOPT_TYPE_TERNARY, name, desc, lockmode);
+ newoption->default_val = default_val;
+
+ return newoption;
+}
+
+/*
+ * add_ternary_reloption
+ * Add a new ternary reloption
+ */
+void
+add_ternary_reloption(bits32 kinds, const char *name, const char *desc,
+ ternary default_val, LOCKMODE lockmode)
+{
+ relopt_ternary *newoption = init_ternary_reloption(kinds, name, desc,
+ default_val, lockmode);
+
+ add_reloption((relopt_gen *) newoption);
+}
+
+/*
+ * add_local_ternary_reloption
+ * Add a new ternary local reloption
+ *
+ * 'offset' is offset of ternary-typed field.
+ */
+void
+add_local_ternary_reloption(local_relopts *relopts, const char *name,
+ const char *desc, ternary default_val,
+ int offset)
+{
+ relopt_ternary *newoption = init_ternary_reloption(RELOPT_KIND_LOCAL,
+ name, desc,
+ default_val, 0);
+
+ add_local_reloption(relopts, (relopt_gen *) newoption, offset);
+}
/*
* init_real_reloption
@@ -1617,6 +1689,18 @@ parse_one_reloption(relopt_value *option, char *text_str, int text_len,
option->gen->name, value)));
}
break;
+ case RELOPT_TYPE_TERNARY:
+ {
+ bool b;
+ parsed = parse_bool(value, &b);
+ option->values.ternary_val = b ? TERNARY_TRUE : TERNARY_FALSE;
+ if (validate && !parsed)
+ ereport(ERROR,
+ (errcode(ERRCODE_INVALID_PARAMETER_VALUE),
+ errmsg("invalid value for ternary option \"%s\": %s",
+ option->gen->name, value)));
+ }
+ break;
case RELOPT_TYPE_INT:
{
relopt_int *optint = (relopt_int *) option->gen;
@@ -1780,17 +1864,6 @@ fillRelOptions(void *rdopts, Size basesize,
char *itempos = ((char *) rdopts) + elems[j].offset;
char *string_val;
- /*
- * If isset_offset is provided, store whether the reloption is
- * set there.
- */
- if (elems[j].isset_offset > 0)
- {
- char *setpos = ((char *) rdopts) + elems[j].isset_offset;
-
- *(bool *) setpos = options[i].isset;
- }
-
switch (options[i].gen->type)
{
case RELOPT_TYPE_BOOL:
@@ -1798,6 +1871,11 @@ fillRelOptions(void *rdopts, Size basesize,
options[i].values.bool_val :
((relopt_bool *) options[i].gen)->default_val;
break;
+ case RELOPT_TYPE_TERNARY:
+ *(ternary *) itempos = options[i].isset ?
+ options[i].values.ternary_val :
+ ((relopt_ternary *) options[i].gen)->default_val;
+ break;
case RELOPT_TYPE_INT:
*(int *) itempos = options[i].isset ?
options[i].values.int_val :
@@ -1912,8 +1990,8 @@ default_reloptions(Datum reloptions, bool validate, relopt_kind kind)
offsetof(StdRdOptions, parallel_workers)},
{"vacuum_index_cleanup", RELOPT_TYPE_ENUM,
offsetof(StdRdOptions, vacuum_index_cleanup)},
- {"vacuum_truncate", RELOPT_TYPE_BOOL,
- offsetof(StdRdOptions, vacuum_truncate), offsetof(StdRdOptions, vacuum_truncate_set)},
+ {"vacuum_truncate", RELOPT_TYPE_TERNARY,
+ offsetof(StdRdOptions, vacuum_truncate)},
{"vacuum_max_eager_freeze_failure_rate", RELOPT_TYPE_REAL,
offsetof(StdRdOptions, vacuum_max_eager_freeze_failure_rate)}
};
@@ -1993,7 +2071,6 @@ build_local_reloptions(local_relopts *relopts, Datum options, bool validate)
elems[i].optname = opt->option->name;
elems[i].opttype = opt->option->type;
elems[i].offset = opt->offset;
- elems[i].isset_offset = 0; /* not supported for local relopts yet */
i++;
}
diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c
index 733ef40ae7..977babff54 100644
--- a/src/backend/commands/vacuum.c
+++ b/src/backend/commands/vacuum.c
@@ -2221,9 +2221,9 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams params,
{
StdRdOptions *opts = (StdRdOptions *) rel->rd_options;
- if (opts && opts->vacuum_truncate_set)
+ if (opts && opts->vacuum_truncate != TERNARY_UNSET)
{
- if (opts->vacuum_truncate)
+ if (opts->vacuum_truncate == TERNARY_TRUE)
params.truncate = VACOPTVALUE_ENABLED;
else
params.truncate = VACOPTVALUE_DISABLED;
diff --git a/src/include/access/reloptions.h b/src/include/access/reloptions.h
index a604a4702c..a436697658 100644
--- a/src/include/access/reloptions.h
+++ b/src/include/access/reloptions.h
@@ -29,6 +29,7 @@
typedef enum relopt_type
{
RELOPT_TYPE_BOOL,
+ RELOPT_TYPE_TERNARY, /* on, off, unset */
RELOPT_TYPE_INT,
RELOPT_TYPE_REAL,
RELOPT_TYPE_ENUM,
@@ -80,6 +81,7 @@ typedef struct relopt_value
union
{
bool bool_val;
+ ternary ternary_val;
int int_val;
double real_val;
int enum_val;
@@ -94,6 +96,12 @@ typedef struct relopt_bool
bool default_val;
} relopt_bool;
+typedef struct relopt_rernary
+{
+ relopt_gen gen;
+ int default_val;
+} relopt_ternary;
+
typedef struct relopt_int
{
relopt_gen gen;
@@ -152,19 +160,6 @@ typedef struct
const char *optname; /* option's name */
relopt_type opttype; /* option's datatype */
int offset; /* offset of field in result struct */
-
- /*
- * isset_offset is an optional offset of a field in the result struct that
- * stores whether the option is explicitly set for the relation or if it
- * just picked up the default value. In most cases, this can be
- * accomplished by giving the reloption a special out-of-range default
- * value (e.g., some integer reloptions use -2), but this isn't always
- * possible. For example, a Boolean reloption cannot be given an
- * out-of-range default, so we need another way to discover the source of
- * its value. This offset is only used if given a value greater than
- * zero.
- */
- int isset_offset;
} relopt_parse_elt;
/* Local reloption definition */
@@ -195,6 +190,8 @@ typedef struct local_relopts
extern relopt_kind add_reloption_kind(void);
extern void add_bool_reloption(bits32 kinds, const char *name, const char *desc,
bool default_val, LOCKMODE lockmode);
+extern void add_ternary_reloption(bits32 kinds, const char *name,
+ const char *desc, int default_val, LOCKMODE lockmode);
extern void add_int_reloption(bits32 kinds, const char *name, const char *desc,
int default_val, int min_val, int max_val,
LOCKMODE lockmode);
@@ -214,6 +211,9 @@ extern void register_reloptions_validator(local_relopts *relopts,
extern void add_local_bool_reloption(local_relopts *relopts, const char *name,
const char *desc, bool default_val,
int offset);
+extern void add_local_ternary_reloption(local_relopts *relopts,
+ const char *name, const char *desc,
+ ternary default_val, int offset);
extern void add_local_int_reloption(local_relopts *relopts, const char *name,
const char *desc, int default_val,
int min_val, int max_val, int offset);
diff --git a/src/include/c.h b/src/include/c.h
index 39022f8a9d..9c59ccccf8 100644
--- a/src/include/c.h
+++ b/src/include/c.h
@@ -488,6 +488,22 @@ typedef void (*pg_funcptr_t) (void);
#include <stdbool.h>
+/*
+ * ternary
+ * Boolean value with an extrea "unset" option
+ *
+ * Ternary data type is used in relation options that can be "true", "false" or
+ * "unset". Since relation options are used deep inside the PostgreSQL code,
+ * this type is declared globally.
+*/
+
+typedef enum ternary
+{
+ TERNARY_FALSE = 0,
+ TERNARY_TRUE = 1,
+ TERNARY_UNSET = -1
+} ternary;
+
/* ----------------------------------------------------------------
* Section 3: standard system types
diff --git a/src/include/utils/rel.h b/src/include/utils/rel.h
index b552359915..7346d618f9 100644
--- a/src/include/utils/rel.h
+++ b/src/include/utils/rel.h
@@ -346,8 +346,7 @@ typedef struct StdRdOptions
bool user_catalog_table; /* use as an additional catalog relation */
int parallel_workers; /* max number of parallel workers */
StdRdOptIndexCleanup vacuum_index_cleanup; /* controls index vacuuming */
- bool vacuum_truncate; /* enables vacuum to truncate a relation */
- bool vacuum_truncate_set; /* whether vacuum_truncate is set */
+ ternary vacuum_truncate; /* enables vacuum to truncate a relation */
/*
* Fraction of pages in a relation that vacuum can eagerly scan and fail
--
2.39.2
--nextPart5129932.5fSG56mABF
Content-Disposition: attachment;
filename="v2-0003-Add-alias-to-be-used-as-unset-state.patch"
Content-Transfer-Encoding: 7Bit
Content-Type: text/x-patch; charset="unicode-2-0-utf-8";
name="v2-0003-Add-alias-to-be-used-as-unset-state.patch"
^ permalink raw reply [nested|flat] 868+ messages in thread
* [PATCH v2 2/4] Introduce ternary reloptions
@ 2025-09-04 14:41 Nikolay Shaplov <[email protected]>
0 siblings, 0 replies; 868+ messages in thread
From: Nikolay Shaplov @ 2025-09-04 14:41 UTC (permalink / raw)
Introduce ternary reloption as a replacement for current `vacuum_truncate`
implementation. Remove `vacuum_truncate_set` additional flag and using
`TERNARY_UNSET` value instead.
---
src/backend/access/common/reloptions.c | 129 ++++++++++++++++++++-----
src/backend/commands/vacuum.c | 4 +-
src/include/access/reloptions.h | 26 ++---
src/include/c.h | 16 +++
src/include/utils/rel.h | 3 +-
5 files changed, 135 insertions(+), 43 deletions(-)
diff --git a/src/backend/access/common/reloptions.c b/src/backend/access/common/reloptions.c
index 0af3fea68f..f543f11001 100644
--- a/src/backend/access/common/reloptions.c
+++ b/src/backend/access/common/reloptions.c
@@ -40,9 +40,9 @@
*
* To add an option:
*
- * (i) decide on a type (bool, integer, real, enum, string), name, default
- * value, upper and lower bounds (if applicable); for strings, consider a
- * validation routine.
+ * (i) decide on a type (bool, ternary, integer, real, enum, string), name,
+ * default value, upper and lower bounds (if applicable); for strings,
+ * consider a validation routine.
* (ii) add a record below (or use add_<type>_reloption).
* (iii) add it to the appropriate options struct (perhaps StdRdOptions)
* (iv) add it to the appropriate handling routine (perhaps
@@ -147,15 +147,6 @@ static relopt_bool boolRelOpts[] =
},
false
},
- {
- {
- "vacuum_truncate",
- "Enables vacuum to truncate empty pages at the end of this table",
- RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
- ShareUpdateExclusiveLock
- },
- true
- },
{
{
"deduplicate_items",
@@ -170,6 +161,21 @@ static relopt_bool boolRelOpts[] =
{{NULL}}
};
+static relopt_ternary ternaryRelOpts[] =
+{
+ {
+ {
+ "vacuum_truncate",
+ "Enables vacuum to truncate empty pages at the end of this table",
+ RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
+ ShareUpdateExclusiveLock
+ },
+ TERNARY_UNSET
+ },
+ /* list terminator */
+ {{NULL}}
+};
+
static relopt_int intRelOpts[] =
{
{
@@ -600,6 +606,13 @@ initialize_reloptions(void)
boolRelOpts[i].gen.lockmode));
j++;
}
+ for (i = 0; ternaryRelOpts[i].gen.name; i++)
+ {
+ Assert(DoLockModesConflict(ternaryRelOpts[i].gen.lockmode,
+ ternaryRelOpts[i].gen.lockmode));
+ j++;
+ }
+
for (i = 0; intRelOpts[i].gen.name; i++)
{
Assert(DoLockModesConflict(intRelOpts[i].gen.lockmode,
@@ -640,6 +653,14 @@ initialize_reloptions(void)
j++;
}
+ for (i = 0; ternaryRelOpts[i].gen.name; i++)
+ {
+ relOpts[j] = &ternaryRelOpts[i].gen;
+ relOpts[j]->type = RELOPT_TYPE_TERNARY;
+ relOpts[j]->namelen = strlen(relOpts[j]->name);
+ j++;
+ }
+
for (i = 0; intRelOpts[i].gen.name; i++)
{
relOpts[j] = &intRelOpts[i].gen;
@@ -800,6 +821,9 @@ allocate_reloption(bits32 kinds, int type, const char *name, const char *desc,
case RELOPT_TYPE_BOOL:
size = sizeof(relopt_bool);
break;
+ case RELOPT_TYPE_TERNARY:
+ size = sizeof(relopt_ternary);
+ break;
case RELOPT_TYPE_INT:
size = sizeof(relopt_int);
break;
@@ -883,6 +907,54 @@ add_local_bool_reloption(local_relopts *relopts, const char *name,
add_local_reloption(relopts, (relopt_gen *) newoption, offset);
}
+/*
+ * init_ternary_reloption
+ * Allocate and initialize a new ternary reloption
+ */
+static relopt_ternary *
+init_ternary_reloption(bits32 kinds, const char *name, const char *desc,
+ ternary default_val, LOCKMODE lockmode)
+{
+ relopt_ternary *newoption;
+
+ newoption = (relopt_ternary *) allocate_reloption(kinds,
+ RELOPT_TYPE_TERNARY, name, desc, lockmode);
+ newoption->default_val = default_val;
+
+ return newoption;
+}
+
+/*
+ * add_ternary_reloption
+ * Add a new ternary reloption
+ */
+void
+add_ternary_reloption(bits32 kinds, const char *name, const char *desc,
+ ternary default_val, LOCKMODE lockmode)
+{
+ relopt_ternary *newoption = init_ternary_reloption(kinds, name, desc,
+ default_val, lockmode);
+
+ add_reloption((relopt_gen *) newoption);
+}
+
+/*
+ * add_local_ternary_reloption
+ * Add a new ternary local reloption
+ *
+ * 'offset' is offset of ternary-typed field.
+ */
+void
+add_local_ternary_reloption(local_relopts *relopts, const char *name,
+ const char *desc, ternary default_val,
+ int offset)
+{
+ relopt_ternary *newoption = init_ternary_reloption(RELOPT_KIND_LOCAL,
+ name, desc,
+ default_val, 0);
+
+ add_local_reloption(relopts, (relopt_gen *) newoption, offset);
+}
/*
* init_real_reloption
@@ -1617,6 +1689,18 @@ parse_one_reloption(relopt_value *option, char *text_str, int text_len,
option->gen->name, value)));
}
break;
+ case RELOPT_TYPE_TERNARY:
+ {
+ bool b;
+ parsed = parse_bool(value, &b);
+ option->values.ternary_val = b ? TERNARY_TRUE : TERNARY_FALSE;
+ if (validate && !parsed)
+ ereport(ERROR,
+ (errcode(ERRCODE_INVALID_PARAMETER_VALUE),
+ errmsg("invalid value for ternary option \"%s\": %s",
+ option->gen->name, value)));
+ }
+ break;
case RELOPT_TYPE_INT:
{
relopt_int *optint = (relopt_int *) option->gen;
@@ -1780,17 +1864,6 @@ fillRelOptions(void *rdopts, Size basesize,
char *itempos = ((char *) rdopts) + elems[j].offset;
char *string_val;
- /*
- * If isset_offset is provided, store whether the reloption is
- * set there.
- */
- if (elems[j].isset_offset > 0)
- {
- char *setpos = ((char *) rdopts) + elems[j].isset_offset;
-
- *(bool *) setpos = options[i].isset;
- }
-
switch (options[i].gen->type)
{
case RELOPT_TYPE_BOOL:
@@ -1798,6 +1871,11 @@ fillRelOptions(void *rdopts, Size basesize,
options[i].values.bool_val :
((relopt_bool *) options[i].gen)->default_val;
break;
+ case RELOPT_TYPE_TERNARY:
+ *(ternary *) itempos = options[i].isset ?
+ options[i].values.ternary_val :
+ ((relopt_ternary *) options[i].gen)->default_val;
+ break;
case RELOPT_TYPE_INT:
*(int *) itempos = options[i].isset ?
options[i].values.int_val :
@@ -1912,8 +1990,8 @@ default_reloptions(Datum reloptions, bool validate, relopt_kind kind)
offsetof(StdRdOptions, parallel_workers)},
{"vacuum_index_cleanup", RELOPT_TYPE_ENUM,
offsetof(StdRdOptions, vacuum_index_cleanup)},
- {"vacuum_truncate", RELOPT_TYPE_BOOL,
- offsetof(StdRdOptions, vacuum_truncate), offsetof(StdRdOptions, vacuum_truncate_set)},
+ {"vacuum_truncate", RELOPT_TYPE_TERNARY,
+ offsetof(StdRdOptions, vacuum_truncate)},
{"vacuum_max_eager_freeze_failure_rate", RELOPT_TYPE_REAL,
offsetof(StdRdOptions, vacuum_max_eager_freeze_failure_rate)}
};
@@ -1993,7 +2071,6 @@ build_local_reloptions(local_relopts *relopts, Datum options, bool validate)
elems[i].optname = opt->option->name;
elems[i].opttype = opt->option->type;
elems[i].offset = opt->offset;
- elems[i].isset_offset = 0; /* not supported for local relopts yet */
i++;
}
diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c
index 733ef40ae7..977babff54 100644
--- a/src/backend/commands/vacuum.c
+++ b/src/backend/commands/vacuum.c
@@ -2221,9 +2221,9 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams params,
{
StdRdOptions *opts = (StdRdOptions *) rel->rd_options;
- if (opts && opts->vacuum_truncate_set)
+ if (opts && opts->vacuum_truncate != TERNARY_UNSET)
{
- if (opts->vacuum_truncate)
+ if (opts->vacuum_truncate == TERNARY_TRUE)
params.truncate = VACOPTVALUE_ENABLED;
else
params.truncate = VACOPTVALUE_DISABLED;
diff --git a/src/include/access/reloptions.h b/src/include/access/reloptions.h
index a604a4702c..a436697658 100644
--- a/src/include/access/reloptions.h
+++ b/src/include/access/reloptions.h
@@ -29,6 +29,7 @@
typedef enum relopt_type
{
RELOPT_TYPE_BOOL,
+ RELOPT_TYPE_TERNARY, /* on, off, unset */
RELOPT_TYPE_INT,
RELOPT_TYPE_REAL,
RELOPT_TYPE_ENUM,
@@ -80,6 +81,7 @@ typedef struct relopt_value
union
{
bool bool_val;
+ ternary ternary_val;
int int_val;
double real_val;
int enum_val;
@@ -94,6 +96,12 @@ typedef struct relopt_bool
bool default_val;
} relopt_bool;
+typedef struct relopt_rernary
+{
+ relopt_gen gen;
+ int default_val;
+} relopt_ternary;
+
typedef struct relopt_int
{
relopt_gen gen;
@@ -152,19 +160,6 @@ typedef struct
const char *optname; /* option's name */
relopt_type opttype; /* option's datatype */
int offset; /* offset of field in result struct */
-
- /*
- * isset_offset is an optional offset of a field in the result struct that
- * stores whether the option is explicitly set for the relation or if it
- * just picked up the default value. In most cases, this can be
- * accomplished by giving the reloption a special out-of-range default
- * value (e.g., some integer reloptions use -2), but this isn't always
- * possible. For example, a Boolean reloption cannot be given an
- * out-of-range default, so we need another way to discover the source of
- * its value. This offset is only used if given a value greater than
- * zero.
- */
- int isset_offset;
} relopt_parse_elt;
/* Local reloption definition */
@@ -195,6 +190,8 @@ typedef struct local_relopts
extern relopt_kind add_reloption_kind(void);
extern void add_bool_reloption(bits32 kinds, const char *name, const char *desc,
bool default_val, LOCKMODE lockmode);
+extern void add_ternary_reloption(bits32 kinds, const char *name,
+ const char *desc, int default_val, LOCKMODE lockmode);
extern void add_int_reloption(bits32 kinds, const char *name, const char *desc,
int default_val, int min_val, int max_val,
LOCKMODE lockmode);
@@ -214,6 +211,9 @@ extern void register_reloptions_validator(local_relopts *relopts,
extern void add_local_bool_reloption(local_relopts *relopts, const char *name,
const char *desc, bool default_val,
int offset);
+extern void add_local_ternary_reloption(local_relopts *relopts,
+ const char *name, const char *desc,
+ ternary default_val, int offset);
extern void add_local_int_reloption(local_relopts *relopts, const char *name,
const char *desc, int default_val,
int min_val, int max_val, int offset);
diff --git a/src/include/c.h b/src/include/c.h
index 39022f8a9d..9c59ccccf8 100644
--- a/src/include/c.h
+++ b/src/include/c.h
@@ -488,6 +488,22 @@ typedef void (*pg_funcptr_t) (void);
#include <stdbool.h>
+/*
+ * ternary
+ * Boolean value with an extrea "unset" option
+ *
+ * Ternary data type is used in relation options that can be "true", "false" or
+ * "unset". Since relation options are used deep inside the PostgreSQL code,
+ * this type is declared globally.
+*/
+
+typedef enum ternary
+{
+ TERNARY_FALSE = 0,
+ TERNARY_TRUE = 1,
+ TERNARY_UNSET = -1
+} ternary;
+
/* ----------------------------------------------------------------
* Section 3: standard system types
diff --git a/src/include/utils/rel.h b/src/include/utils/rel.h
index b552359915..7346d618f9 100644
--- a/src/include/utils/rel.h
+++ b/src/include/utils/rel.h
@@ -346,8 +346,7 @@ typedef struct StdRdOptions
bool user_catalog_table; /* use as an additional catalog relation */
int parallel_workers; /* max number of parallel workers */
StdRdOptIndexCleanup vacuum_index_cleanup; /* controls index vacuuming */
- bool vacuum_truncate; /* enables vacuum to truncate a relation */
- bool vacuum_truncate_set; /* whether vacuum_truncate is set */
+ ternary vacuum_truncate; /* enables vacuum to truncate a relation */
/*
* Fraction of pages in a relation that vacuum can eagerly scan and fail
--
2.39.2
--nextPart5129932.5fSG56mABF
Content-Disposition: attachment;
filename="v2-0003-Add-alias-to-be-used-as-unset-state.patch"
Content-Transfer-Encoding: 7Bit
Content-Type: text/x-patch; charset="unicode-2-0-utf-8";
name="v2-0003-Add-alias-to-be-used-as-unset-state.patch"
^ permalink raw reply [nested|flat] 868+ messages in thread
* [PATCH v2 2/4] Introduce ternary reloptions
@ 2025-09-04 14:41 Nikolay Shaplov <[email protected]>
0 siblings, 0 replies; 868+ messages in thread
From: Nikolay Shaplov @ 2025-09-04 14:41 UTC (permalink / raw)
Introduce ternary reloption as a replacement for current `vacuum_truncate`
implementation. Remove `vacuum_truncate_set` additional flag and using
`TERNARY_UNSET` value instead.
---
src/backend/access/common/reloptions.c | 129 ++++++++++++++++++++-----
src/backend/commands/vacuum.c | 4 +-
src/include/access/reloptions.h | 26 ++---
src/include/c.h | 16 +++
src/include/utils/rel.h | 3 +-
5 files changed, 135 insertions(+), 43 deletions(-)
diff --git a/src/backend/access/common/reloptions.c b/src/backend/access/common/reloptions.c
index 0af3fea68f..f543f11001 100644
--- a/src/backend/access/common/reloptions.c
+++ b/src/backend/access/common/reloptions.c
@@ -40,9 +40,9 @@
*
* To add an option:
*
- * (i) decide on a type (bool, integer, real, enum, string), name, default
- * value, upper and lower bounds (if applicable); for strings, consider a
- * validation routine.
+ * (i) decide on a type (bool, ternary, integer, real, enum, string), name,
+ * default value, upper and lower bounds (if applicable); for strings,
+ * consider a validation routine.
* (ii) add a record below (or use add_<type>_reloption).
* (iii) add it to the appropriate options struct (perhaps StdRdOptions)
* (iv) add it to the appropriate handling routine (perhaps
@@ -147,15 +147,6 @@ static relopt_bool boolRelOpts[] =
},
false
},
- {
- {
- "vacuum_truncate",
- "Enables vacuum to truncate empty pages at the end of this table",
- RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
- ShareUpdateExclusiveLock
- },
- true
- },
{
{
"deduplicate_items",
@@ -170,6 +161,21 @@ static relopt_bool boolRelOpts[] =
{{NULL}}
};
+static relopt_ternary ternaryRelOpts[] =
+{
+ {
+ {
+ "vacuum_truncate",
+ "Enables vacuum to truncate empty pages at the end of this table",
+ RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
+ ShareUpdateExclusiveLock
+ },
+ TERNARY_UNSET
+ },
+ /* list terminator */
+ {{NULL}}
+};
+
static relopt_int intRelOpts[] =
{
{
@@ -600,6 +606,13 @@ initialize_reloptions(void)
boolRelOpts[i].gen.lockmode));
j++;
}
+ for (i = 0; ternaryRelOpts[i].gen.name; i++)
+ {
+ Assert(DoLockModesConflict(ternaryRelOpts[i].gen.lockmode,
+ ternaryRelOpts[i].gen.lockmode));
+ j++;
+ }
+
for (i = 0; intRelOpts[i].gen.name; i++)
{
Assert(DoLockModesConflict(intRelOpts[i].gen.lockmode,
@@ -640,6 +653,14 @@ initialize_reloptions(void)
j++;
}
+ for (i = 0; ternaryRelOpts[i].gen.name; i++)
+ {
+ relOpts[j] = &ternaryRelOpts[i].gen;
+ relOpts[j]->type = RELOPT_TYPE_TERNARY;
+ relOpts[j]->namelen = strlen(relOpts[j]->name);
+ j++;
+ }
+
for (i = 0; intRelOpts[i].gen.name; i++)
{
relOpts[j] = &intRelOpts[i].gen;
@@ -800,6 +821,9 @@ allocate_reloption(bits32 kinds, int type, const char *name, const char *desc,
case RELOPT_TYPE_BOOL:
size = sizeof(relopt_bool);
break;
+ case RELOPT_TYPE_TERNARY:
+ size = sizeof(relopt_ternary);
+ break;
case RELOPT_TYPE_INT:
size = sizeof(relopt_int);
break;
@@ -883,6 +907,54 @@ add_local_bool_reloption(local_relopts *relopts, const char *name,
add_local_reloption(relopts, (relopt_gen *) newoption, offset);
}
+/*
+ * init_ternary_reloption
+ * Allocate and initialize a new ternary reloption
+ */
+static relopt_ternary *
+init_ternary_reloption(bits32 kinds, const char *name, const char *desc,
+ ternary default_val, LOCKMODE lockmode)
+{
+ relopt_ternary *newoption;
+
+ newoption = (relopt_ternary *) allocate_reloption(kinds,
+ RELOPT_TYPE_TERNARY, name, desc, lockmode);
+ newoption->default_val = default_val;
+
+ return newoption;
+}
+
+/*
+ * add_ternary_reloption
+ * Add a new ternary reloption
+ */
+void
+add_ternary_reloption(bits32 kinds, const char *name, const char *desc,
+ ternary default_val, LOCKMODE lockmode)
+{
+ relopt_ternary *newoption = init_ternary_reloption(kinds, name, desc,
+ default_val, lockmode);
+
+ add_reloption((relopt_gen *) newoption);
+}
+
+/*
+ * add_local_ternary_reloption
+ * Add a new ternary local reloption
+ *
+ * 'offset' is offset of ternary-typed field.
+ */
+void
+add_local_ternary_reloption(local_relopts *relopts, const char *name,
+ const char *desc, ternary default_val,
+ int offset)
+{
+ relopt_ternary *newoption = init_ternary_reloption(RELOPT_KIND_LOCAL,
+ name, desc,
+ default_val, 0);
+
+ add_local_reloption(relopts, (relopt_gen *) newoption, offset);
+}
/*
* init_real_reloption
@@ -1617,6 +1689,18 @@ parse_one_reloption(relopt_value *option, char *text_str, int text_len,
option->gen->name, value)));
}
break;
+ case RELOPT_TYPE_TERNARY:
+ {
+ bool b;
+ parsed = parse_bool(value, &b);
+ option->values.ternary_val = b ? TERNARY_TRUE : TERNARY_FALSE;
+ if (validate && !parsed)
+ ereport(ERROR,
+ (errcode(ERRCODE_INVALID_PARAMETER_VALUE),
+ errmsg("invalid value for ternary option \"%s\": %s",
+ option->gen->name, value)));
+ }
+ break;
case RELOPT_TYPE_INT:
{
relopt_int *optint = (relopt_int *) option->gen;
@@ -1780,17 +1864,6 @@ fillRelOptions(void *rdopts, Size basesize,
char *itempos = ((char *) rdopts) + elems[j].offset;
char *string_val;
- /*
- * If isset_offset is provided, store whether the reloption is
- * set there.
- */
- if (elems[j].isset_offset > 0)
- {
- char *setpos = ((char *) rdopts) + elems[j].isset_offset;
-
- *(bool *) setpos = options[i].isset;
- }
-
switch (options[i].gen->type)
{
case RELOPT_TYPE_BOOL:
@@ -1798,6 +1871,11 @@ fillRelOptions(void *rdopts, Size basesize,
options[i].values.bool_val :
((relopt_bool *) options[i].gen)->default_val;
break;
+ case RELOPT_TYPE_TERNARY:
+ *(ternary *) itempos = options[i].isset ?
+ options[i].values.ternary_val :
+ ((relopt_ternary *) options[i].gen)->default_val;
+ break;
case RELOPT_TYPE_INT:
*(int *) itempos = options[i].isset ?
options[i].values.int_val :
@@ -1912,8 +1990,8 @@ default_reloptions(Datum reloptions, bool validate, relopt_kind kind)
offsetof(StdRdOptions, parallel_workers)},
{"vacuum_index_cleanup", RELOPT_TYPE_ENUM,
offsetof(StdRdOptions, vacuum_index_cleanup)},
- {"vacuum_truncate", RELOPT_TYPE_BOOL,
- offsetof(StdRdOptions, vacuum_truncate), offsetof(StdRdOptions, vacuum_truncate_set)},
+ {"vacuum_truncate", RELOPT_TYPE_TERNARY,
+ offsetof(StdRdOptions, vacuum_truncate)},
{"vacuum_max_eager_freeze_failure_rate", RELOPT_TYPE_REAL,
offsetof(StdRdOptions, vacuum_max_eager_freeze_failure_rate)}
};
@@ -1993,7 +2071,6 @@ build_local_reloptions(local_relopts *relopts, Datum options, bool validate)
elems[i].optname = opt->option->name;
elems[i].opttype = opt->option->type;
elems[i].offset = opt->offset;
- elems[i].isset_offset = 0; /* not supported for local relopts yet */
i++;
}
diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c
index 733ef40ae7..977babff54 100644
--- a/src/backend/commands/vacuum.c
+++ b/src/backend/commands/vacuum.c
@@ -2221,9 +2221,9 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams params,
{
StdRdOptions *opts = (StdRdOptions *) rel->rd_options;
- if (opts && opts->vacuum_truncate_set)
+ if (opts && opts->vacuum_truncate != TERNARY_UNSET)
{
- if (opts->vacuum_truncate)
+ if (opts->vacuum_truncate == TERNARY_TRUE)
params.truncate = VACOPTVALUE_ENABLED;
else
params.truncate = VACOPTVALUE_DISABLED;
diff --git a/src/include/access/reloptions.h b/src/include/access/reloptions.h
index a604a4702c..a436697658 100644
--- a/src/include/access/reloptions.h
+++ b/src/include/access/reloptions.h
@@ -29,6 +29,7 @@
typedef enum relopt_type
{
RELOPT_TYPE_BOOL,
+ RELOPT_TYPE_TERNARY, /* on, off, unset */
RELOPT_TYPE_INT,
RELOPT_TYPE_REAL,
RELOPT_TYPE_ENUM,
@@ -80,6 +81,7 @@ typedef struct relopt_value
union
{
bool bool_val;
+ ternary ternary_val;
int int_val;
double real_val;
int enum_val;
@@ -94,6 +96,12 @@ typedef struct relopt_bool
bool default_val;
} relopt_bool;
+typedef struct relopt_rernary
+{
+ relopt_gen gen;
+ int default_val;
+} relopt_ternary;
+
typedef struct relopt_int
{
relopt_gen gen;
@@ -152,19 +160,6 @@ typedef struct
const char *optname; /* option's name */
relopt_type opttype; /* option's datatype */
int offset; /* offset of field in result struct */
-
- /*
- * isset_offset is an optional offset of a field in the result struct that
- * stores whether the option is explicitly set for the relation or if it
- * just picked up the default value. In most cases, this can be
- * accomplished by giving the reloption a special out-of-range default
- * value (e.g., some integer reloptions use -2), but this isn't always
- * possible. For example, a Boolean reloption cannot be given an
- * out-of-range default, so we need another way to discover the source of
- * its value. This offset is only used if given a value greater than
- * zero.
- */
- int isset_offset;
} relopt_parse_elt;
/* Local reloption definition */
@@ -195,6 +190,8 @@ typedef struct local_relopts
extern relopt_kind add_reloption_kind(void);
extern void add_bool_reloption(bits32 kinds, const char *name, const char *desc,
bool default_val, LOCKMODE lockmode);
+extern void add_ternary_reloption(bits32 kinds, const char *name,
+ const char *desc, int default_val, LOCKMODE lockmode);
extern void add_int_reloption(bits32 kinds, const char *name, const char *desc,
int default_val, int min_val, int max_val,
LOCKMODE lockmode);
@@ -214,6 +211,9 @@ extern void register_reloptions_validator(local_relopts *relopts,
extern void add_local_bool_reloption(local_relopts *relopts, const char *name,
const char *desc, bool default_val,
int offset);
+extern void add_local_ternary_reloption(local_relopts *relopts,
+ const char *name, const char *desc,
+ ternary default_val, int offset);
extern void add_local_int_reloption(local_relopts *relopts, const char *name,
const char *desc, int default_val,
int min_val, int max_val, int offset);
diff --git a/src/include/c.h b/src/include/c.h
index 39022f8a9d..9c59ccccf8 100644
--- a/src/include/c.h
+++ b/src/include/c.h
@@ -488,6 +488,22 @@ typedef void (*pg_funcptr_t) (void);
#include <stdbool.h>
+/*
+ * ternary
+ * Boolean value with an extrea "unset" option
+ *
+ * Ternary data type is used in relation options that can be "true", "false" or
+ * "unset". Since relation options are used deep inside the PostgreSQL code,
+ * this type is declared globally.
+*/
+
+typedef enum ternary
+{
+ TERNARY_FALSE = 0,
+ TERNARY_TRUE = 1,
+ TERNARY_UNSET = -1
+} ternary;
+
/* ----------------------------------------------------------------
* Section 3: standard system types
diff --git a/src/include/utils/rel.h b/src/include/utils/rel.h
index b552359915..7346d618f9 100644
--- a/src/include/utils/rel.h
+++ b/src/include/utils/rel.h
@@ -346,8 +346,7 @@ typedef struct StdRdOptions
bool user_catalog_table; /* use as an additional catalog relation */
int parallel_workers; /* max number of parallel workers */
StdRdOptIndexCleanup vacuum_index_cleanup; /* controls index vacuuming */
- bool vacuum_truncate; /* enables vacuum to truncate a relation */
- bool vacuum_truncate_set; /* whether vacuum_truncate is set */
+ ternary vacuum_truncate; /* enables vacuum to truncate a relation */
/*
* Fraction of pages in a relation that vacuum can eagerly scan and fail
--
2.39.2
--nextPart5129932.5fSG56mABF
Content-Disposition: attachment;
filename="v2-0003-Add-alias-to-be-used-as-unset-state.patch"
Content-Transfer-Encoding: 7Bit
Content-Type: text/x-patch; charset="unicode-2-0-utf-8";
name="v2-0003-Add-alias-to-be-used-as-unset-state.patch"
^ permalink raw reply [nested|flat] 868+ messages in thread
* [PATCH v2a 2/4] Introduce ternary reloptions
@ 2025-09-04 14:41 Nikolay Shaplov <[email protected]>
0 siblings, 0 replies; 868+ messages in thread
From: Nikolay Shaplov @ 2025-09-04 14:41 UTC (permalink / raw)
Introduce ternary reloption as a replacement for current `vacuum_truncate`
implementation. Remove `vacuum_truncate_set` additional flag and using
`TERNARY_UNSET` value instead.
---
src/backend/access/common/reloptions.c | 129 ++++++++++++++++++++-----
src/backend/commands/vacuum.c | 4 +-
src/include/access/reloptions.h | 26 ++---
src/include/c.h | 16 +++
src/include/utils/rel.h | 3 +-
5 files changed, 135 insertions(+), 43 deletions(-)
diff --git a/src/backend/access/common/reloptions.c b/src/backend/access/common/reloptions.c
index 0af3fea68f..f543f11001 100644
--- a/src/backend/access/common/reloptions.c
+++ b/src/backend/access/common/reloptions.c
@@ -40,9 +40,9 @@
*
* To add an option:
*
- * (i) decide on a type (bool, integer, real, enum, string), name, default
- * value, upper and lower bounds (if applicable); for strings, consider a
- * validation routine.
+ * (i) decide on a type (bool, ternary, integer, real, enum, string), name,
+ * default value, upper and lower bounds (if applicable); for strings,
+ * consider a validation routine.
* (ii) add a record below (or use add_<type>_reloption).
* (iii) add it to the appropriate options struct (perhaps StdRdOptions)
* (iv) add it to the appropriate handling routine (perhaps
@@ -147,15 +147,6 @@ static relopt_bool boolRelOpts[] =
},
false
},
- {
- {
- "vacuum_truncate",
- "Enables vacuum to truncate empty pages at the end of this table",
- RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
- ShareUpdateExclusiveLock
- },
- true
- },
{
{
"deduplicate_items",
@@ -170,6 +161,21 @@ static relopt_bool boolRelOpts[] =
{{NULL}}
};
+static relopt_ternary ternaryRelOpts[] =
+{
+ {
+ {
+ "vacuum_truncate",
+ "Enables vacuum to truncate empty pages at the end of this table",
+ RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
+ ShareUpdateExclusiveLock
+ },
+ TERNARY_UNSET
+ },
+ /* list terminator */
+ {{NULL}}
+};
+
static relopt_int intRelOpts[] =
{
{
@@ -600,6 +606,13 @@ initialize_reloptions(void)
boolRelOpts[i].gen.lockmode));
j++;
}
+ for (i = 0; ternaryRelOpts[i].gen.name; i++)
+ {
+ Assert(DoLockModesConflict(ternaryRelOpts[i].gen.lockmode,
+ ternaryRelOpts[i].gen.lockmode));
+ j++;
+ }
+
for (i = 0; intRelOpts[i].gen.name; i++)
{
Assert(DoLockModesConflict(intRelOpts[i].gen.lockmode,
@@ -640,6 +653,14 @@ initialize_reloptions(void)
j++;
}
+ for (i = 0; ternaryRelOpts[i].gen.name; i++)
+ {
+ relOpts[j] = &ternaryRelOpts[i].gen;
+ relOpts[j]->type = RELOPT_TYPE_TERNARY;
+ relOpts[j]->namelen = strlen(relOpts[j]->name);
+ j++;
+ }
+
for (i = 0; intRelOpts[i].gen.name; i++)
{
relOpts[j] = &intRelOpts[i].gen;
@@ -800,6 +821,9 @@ allocate_reloption(bits32 kinds, int type, const char *name, const char *desc,
case RELOPT_TYPE_BOOL:
size = sizeof(relopt_bool);
break;
+ case RELOPT_TYPE_TERNARY:
+ size = sizeof(relopt_ternary);
+ break;
case RELOPT_TYPE_INT:
size = sizeof(relopt_int);
break;
@@ -883,6 +907,54 @@ add_local_bool_reloption(local_relopts *relopts, const char *name,
add_local_reloption(relopts, (relopt_gen *) newoption, offset);
}
+/*
+ * init_ternary_reloption
+ * Allocate and initialize a new ternary reloption
+ */
+static relopt_ternary *
+init_ternary_reloption(bits32 kinds, const char *name, const char *desc,
+ ternary default_val, LOCKMODE lockmode)
+{
+ relopt_ternary *newoption;
+
+ newoption = (relopt_ternary *) allocate_reloption(kinds,
+ RELOPT_TYPE_TERNARY, name, desc, lockmode);
+ newoption->default_val = default_val;
+
+ return newoption;
+}
+
+/*
+ * add_ternary_reloption
+ * Add a new ternary reloption
+ */
+void
+add_ternary_reloption(bits32 kinds, const char *name, const char *desc,
+ ternary default_val, LOCKMODE lockmode)
+{
+ relopt_ternary *newoption = init_ternary_reloption(kinds, name, desc,
+ default_val, lockmode);
+
+ add_reloption((relopt_gen *) newoption);
+}
+
+/*
+ * add_local_ternary_reloption
+ * Add a new ternary local reloption
+ *
+ * 'offset' is offset of ternary-typed field.
+ */
+void
+add_local_ternary_reloption(local_relopts *relopts, const char *name,
+ const char *desc, ternary default_val,
+ int offset)
+{
+ relopt_ternary *newoption = init_ternary_reloption(RELOPT_KIND_LOCAL,
+ name, desc,
+ default_val, 0);
+
+ add_local_reloption(relopts, (relopt_gen *) newoption, offset);
+}
/*
* init_real_reloption
@@ -1617,6 +1689,18 @@ parse_one_reloption(relopt_value *option, char *text_str, int text_len,
option->gen->name, value)));
}
break;
+ case RELOPT_TYPE_TERNARY:
+ {
+ bool b;
+ parsed = parse_bool(value, &b);
+ option->values.ternary_val = b ? TERNARY_TRUE : TERNARY_FALSE;
+ if (validate && !parsed)
+ ereport(ERROR,
+ (errcode(ERRCODE_INVALID_PARAMETER_VALUE),
+ errmsg("invalid value for ternary option \"%s\": %s",
+ option->gen->name, value)));
+ }
+ break;
case RELOPT_TYPE_INT:
{
relopt_int *optint = (relopt_int *) option->gen;
@@ -1780,17 +1864,6 @@ fillRelOptions(void *rdopts, Size basesize,
char *itempos = ((char *) rdopts) + elems[j].offset;
char *string_val;
- /*
- * If isset_offset is provided, store whether the reloption is
- * set there.
- */
- if (elems[j].isset_offset > 0)
- {
- char *setpos = ((char *) rdopts) + elems[j].isset_offset;
-
- *(bool *) setpos = options[i].isset;
- }
-
switch (options[i].gen->type)
{
case RELOPT_TYPE_BOOL:
@@ -1798,6 +1871,11 @@ fillRelOptions(void *rdopts, Size basesize,
options[i].values.bool_val :
((relopt_bool *) options[i].gen)->default_val;
break;
+ case RELOPT_TYPE_TERNARY:
+ *(ternary *) itempos = options[i].isset ?
+ options[i].values.ternary_val :
+ ((relopt_ternary *) options[i].gen)->default_val;
+ break;
case RELOPT_TYPE_INT:
*(int *) itempos = options[i].isset ?
options[i].values.int_val :
@@ -1912,8 +1990,8 @@ default_reloptions(Datum reloptions, bool validate, relopt_kind kind)
offsetof(StdRdOptions, parallel_workers)},
{"vacuum_index_cleanup", RELOPT_TYPE_ENUM,
offsetof(StdRdOptions, vacuum_index_cleanup)},
- {"vacuum_truncate", RELOPT_TYPE_BOOL,
- offsetof(StdRdOptions, vacuum_truncate), offsetof(StdRdOptions, vacuum_truncate_set)},
+ {"vacuum_truncate", RELOPT_TYPE_TERNARY,
+ offsetof(StdRdOptions, vacuum_truncate)},
{"vacuum_max_eager_freeze_failure_rate", RELOPT_TYPE_REAL,
offsetof(StdRdOptions, vacuum_max_eager_freeze_failure_rate)}
};
@@ -1993,7 +2071,6 @@ build_local_reloptions(local_relopts *relopts, Datum options, bool validate)
elems[i].optname = opt->option->name;
elems[i].opttype = opt->option->type;
elems[i].offset = opt->offset;
- elems[i].isset_offset = 0; /* not supported for local relopts yet */
i++;
}
diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c
index 733ef40ae7..977babff54 100644
--- a/src/backend/commands/vacuum.c
+++ b/src/backend/commands/vacuum.c
@@ -2221,9 +2221,9 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams params,
{
StdRdOptions *opts = (StdRdOptions *) rel->rd_options;
- if (opts && opts->vacuum_truncate_set)
+ if (opts && opts->vacuum_truncate != TERNARY_UNSET)
{
- if (opts->vacuum_truncate)
+ if (opts->vacuum_truncate == TERNARY_TRUE)
params.truncate = VACOPTVALUE_ENABLED;
else
params.truncate = VACOPTVALUE_DISABLED;
diff --git a/src/include/access/reloptions.h b/src/include/access/reloptions.h
index a604a4702c..a436697658 100644
--- a/src/include/access/reloptions.h
+++ b/src/include/access/reloptions.h
@@ -29,6 +29,7 @@
typedef enum relopt_type
{
RELOPT_TYPE_BOOL,
+ RELOPT_TYPE_TERNARY, /* on, off, unset */
RELOPT_TYPE_INT,
RELOPT_TYPE_REAL,
RELOPT_TYPE_ENUM,
@@ -80,6 +81,7 @@ typedef struct relopt_value
union
{
bool bool_val;
+ ternary ternary_val;
int int_val;
double real_val;
int enum_val;
@@ -94,6 +96,12 @@ typedef struct relopt_bool
bool default_val;
} relopt_bool;
+typedef struct relopt_rernary
+{
+ relopt_gen gen;
+ int default_val;
+} relopt_ternary;
+
typedef struct relopt_int
{
relopt_gen gen;
@@ -152,19 +160,6 @@ typedef struct
const char *optname; /* option's name */
relopt_type opttype; /* option's datatype */
int offset; /* offset of field in result struct */
-
- /*
- * isset_offset is an optional offset of a field in the result struct that
- * stores whether the option is explicitly set for the relation or if it
- * just picked up the default value. In most cases, this can be
- * accomplished by giving the reloption a special out-of-range default
- * value (e.g., some integer reloptions use -2), but this isn't always
- * possible. For example, a Boolean reloption cannot be given an
- * out-of-range default, so we need another way to discover the source of
- * its value. This offset is only used if given a value greater than
- * zero.
- */
- int isset_offset;
} relopt_parse_elt;
/* Local reloption definition */
@@ -195,6 +190,8 @@ typedef struct local_relopts
extern relopt_kind add_reloption_kind(void);
extern void add_bool_reloption(bits32 kinds, const char *name, const char *desc,
bool default_val, LOCKMODE lockmode);
+extern void add_ternary_reloption(bits32 kinds, const char *name,
+ const char *desc, int default_val, LOCKMODE lockmode);
extern void add_int_reloption(bits32 kinds, const char *name, const char *desc,
int default_val, int min_val, int max_val,
LOCKMODE lockmode);
@@ -214,6 +211,9 @@ extern void register_reloptions_validator(local_relopts *relopts,
extern void add_local_bool_reloption(local_relopts *relopts, const char *name,
const char *desc, bool default_val,
int offset);
+extern void add_local_ternary_reloption(local_relopts *relopts,
+ const char *name, const char *desc,
+ ternary default_val, int offset);
extern void add_local_int_reloption(local_relopts *relopts, const char *name,
const char *desc, int default_val,
int min_val, int max_val, int offset);
diff --git a/src/include/c.h b/src/include/c.h
index 39022f8a9d..9c59ccccf8 100644
--- a/src/include/c.h
+++ b/src/include/c.h
@@ -488,6 +488,22 @@ typedef void (*pg_funcptr_t) (void);
#include <stdbool.h>
+/*
+ * ternary
+ * Boolean value with an extrea "unset" option
+ *
+ * Ternary data type is used in relation options that can be "true", "false" or
+ * "unset". Since relation options are used deep inside the PostgreSQL code,
+ * this type is declared globally.
+*/
+
+typedef enum ternary
+{
+ TERNARY_FALSE = 0,
+ TERNARY_TRUE = 1,
+ TERNARY_UNSET = -1
+} ternary;
+
/* ----------------------------------------------------------------
* Section 3: standard system types
diff --git a/src/include/utils/rel.h b/src/include/utils/rel.h
index b552359915..7346d618f9 100644
--- a/src/include/utils/rel.h
+++ b/src/include/utils/rel.h
@@ -346,8 +346,7 @@ typedef struct StdRdOptions
bool user_catalog_table; /* use as an additional catalog relation */
int parallel_workers; /* max number of parallel workers */
StdRdOptIndexCleanup vacuum_index_cleanup; /* controls index vacuuming */
- bool vacuum_truncate; /* enables vacuum to truncate a relation */
- bool vacuum_truncate_set; /* whether vacuum_truncate is set */
+ ternary vacuum_truncate; /* enables vacuum to truncate a relation */
/*
* Fraction of pages in a relation that vacuum can eagerly scan and fail
--
2.39.2
--nextPart4155600.3ZeAukHxDK
Content-Disposition: attachment;
filename="v2a-0003-Add-alias-to-be-used-as-unset-state.patch"
Content-Transfer-Encoding: 7Bit
Content-Type: text/x-patch; charset="unicode-2-0-utf-8";
name="v2a-0003-Add-alias-to-be-used-as-unset-state.patch"
^ permalink raw reply [nested|flat] 868+ messages in thread
* [PATCH v2a 2/4] Introduce ternary reloptions
@ 2025-09-04 14:41 Nikolay Shaplov <[email protected]>
0 siblings, 0 replies; 868+ messages in thread
From: Nikolay Shaplov @ 2025-09-04 14:41 UTC (permalink / raw)
Introduce ternary reloption as a replacement for current `vacuum_truncate`
implementation. Remove `vacuum_truncate_set` additional flag and using
`TERNARY_UNSET` value instead.
---
src/backend/access/common/reloptions.c | 129 ++++++++++++++++++++-----
src/backend/commands/vacuum.c | 4 +-
src/include/access/reloptions.h | 26 ++---
src/include/c.h | 16 +++
src/include/utils/rel.h | 3 +-
5 files changed, 135 insertions(+), 43 deletions(-)
diff --git a/src/backend/access/common/reloptions.c b/src/backend/access/common/reloptions.c
index 0af3fea68f..f543f11001 100644
--- a/src/backend/access/common/reloptions.c
+++ b/src/backend/access/common/reloptions.c
@@ -40,9 +40,9 @@
*
* To add an option:
*
- * (i) decide on a type (bool, integer, real, enum, string), name, default
- * value, upper and lower bounds (if applicable); for strings, consider a
- * validation routine.
+ * (i) decide on a type (bool, ternary, integer, real, enum, string), name,
+ * default value, upper and lower bounds (if applicable); for strings,
+ * consider a validation routine.
* (ii) add a record below (or use add_<type>_reloption).
* (iii) add it to the appropriate options struct (perhaps StdRdOptions)
* (iv) add it to the appropriate handling routine (perhaps
@@ -147,15 +147,6 @@ static relopt_bool boolRelOpts[] =
},
false
},
- {
- {
- "vacuum_truncate",
- "Enables vacuum to truncate empty pages at the end of this table",
- RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
- ShareUpdateExclusiveLock
- },
- true
- },
{
{
"deduplicate_items",
@@ -170,6 +161,21 @@ static relopt_bool boolRelOpts[] =
{{NULL}}
};
+static relopt_ternary ternaryRelOpts[] =
+{
+ {
+ {
+ "vacuum_truncate",
+ "Enables vacuum to truncate empty pages at the end of this table",
+ RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
+ ShareUpdateExclusiveLock
+ },
+ TERNARY_UNSET
+ },
+ /* list terminator */
+ {{NULL}}
+};
+
static relopt_int intRelOpts[] =
{
{
@@ -600,6 +606,13 @@ initialize_reloptions(void)
boolRelOpts[i].gen.lockmode));
j++;
}
+ for (i = 0; ternaryRelOpts[i].gen.name; i++)
+ {
+ Assert(DoLockModesConflict(ternaryRelOpts[i].gen.lockmode,
+ ternaryRelOpts[i].gen.lockmode));
+ j++;
+ }
+
for (i = 0; intRelOpts[i].gen.name; i++)
{
Assert(DoLockModesConflict(intRelOpts[i].gen.lockmode,
@@ -640,6 +653,14 @@ initialize_reloptions(void)
j++;
}
+ for (i = 0; ternaryRelOpts[i].gen.name; i++)
+ {
+ relOpts[j] = &ternaryRelOpts[i].gen;
+ relOpts[j]->type = RELOPT_TYPE_TERNARY;
+ relOpts[j]->namelen = strlen(relOpts[j]->name);
+ j++;
+ }
+
for (i = 0; intRelOpts[i].gen.name; i++)
{
relOpts[j] = &intRelOpts[i].gen;
@@ -800,6 +821,9 @@ allocate_reloption(bits32 kinds, int type, const char *name, const char *desc,
case RELOPT_TYPE_BOOL:
size = sizeof(relopt_bool);
break;
+ case RELOPT_TYPE_TERNARY:
+ size = sizeof(relopt_ternary);
+ break;
case RELOPT_TYPE_INT:
size = sizeof(relopt_int);
break;
@@ -883,6 +907,54 @@ add_local_bool_reloption(local_relopts *relopts, const char *name,
add_local_reloption(relopts, (relopt_gen *) newoption, offset);
}
+/*
+ * init_ternary_reloption
+ * Allocate and initialize a new ternary reloption
+ */
+static relopt_ternary *
+init_ternary_reloption(bits32 kinds, const char *name, const char *desc,
+ ternary default_val, LOCKMODE lockmode)
+{
+ relopt_ternary *newoption;
+
+ newoption = (relopt_ternary *) allocate_reloption(kinds,
+ RELOPT_TYPE_TERNARY, name, desc, lockmode);
+ newoption->default_val = default_val;
+
+ return newoption;
+}
+
+/*
+ * add_ternary_reloption
+ * Add a new ternary reloption
+ */
+void
+add_ternary_reloption(bits32 kinds, const char *name, const char *desc,
+ ternary default_val, LOCKMODE lockmode)
+{
+ relopt_ternary *newoption = init_ternary_reloption(kinds, name, desc,
+ default_val, lockmode);
+
+ add_reloption((relopt_gen *) newoption);
+}
+
+/*
+ * add_local_ternary_reloption
+ * Add a new ternary local reloption
+ *
+ * 'offset' is offset of ternary-typed field.
+ */
+void
+add_local_ternary_reloption(local_relopts *relopts, const char *name,
+ const char *desc, ternary default_val,
+ int offset)
+{
+ relopt_ternary *newoption = init_ternary_reloption(RELOPT_KIND_LOCAL,
+ name, desc,
+ default_val, 0);
+
+ add_local_reloption(relopts, (relopt_gen *) newoption, offset);
+}
/*
* init_real_reloption
@@ -1617,6 +1689,18 @@ parse_one_reloption(relopt_value *option, char *text_str, int text_len,
option->gen->name, value)));
}
break;
+ case RELOPT_TYPE_TERNARY:
+ {
+ bool b;
+ parsed = parse_bool(value, &b);
+ option->values.ternary_val = b ? TERNARY_TRUE : TERNARY_FALSE;
+ if (validate && !parsed)
+ ereport(ERROR,
+ (errcode(ERRCODE_INVALID_PARAMETER_VALUE),
+ errmsg("invalid value for ternary option \"%s\": %s",
+ option->gen->name, value)));
+ }
+ break;
case RELOPT_TYPE_INT:
{
relopt_int *optint = (relopt_int *) option->gen;
@@ -1780,17 +1864,6 @@ fillRelOptions(void *rdopts, Size basesize,
char *itempos = ((char *) rdopts) + elems[j].offset;
char *string_val;
- /*
- * If isset_offset is provided, store whether the reloption is
- * set there.
- */
- if (elems[j].isset_offset > 0)
- {
- char *setpos = ((char *) rdopts) + elems[j].isset_offset;
-
- *(bool *) setpos = options[i].isset;
- }
-
switch (options[i].gen->type)
{
case RELOPT_TYPE_BOOL:
@@ -1798,6 +1871,11 @@ fillRelOptions(void *rdopts, Size basesize,
options[i].values.bool_val :
((relopt_bool *) options[i].gen)->default_val;
break;
+ case RELOPT_TYPE_TERNARY:
+ *(ternary *) itempos = options[i].isset ?
+ options[i].values.ternary_val :
+ ((relopt_ternary *) options[i].gen)->default_val;
+ break;
case RELOPT_TYPE_INT:
*(int *) itempos = options[i].isset ?
options[i].values.int_val :
@@ -1912,8 +1990,8 @@ default_reloptions(Datum reloptions, bool validate, relopt_kind kind)
offsetof(StdRdOptions, parallel_workers)},
{"vacuum_index_cleanup", RELOPT_TYPE_ENUM,
offsetof(StdRdOptions, vacuum_index_cleanup)},
- {"vacuum_truncate", RELOPT_TYPE_BOOL,
- offsetof(StdRdOptions, vacuum_truncate), offsetof(StdRdOptions, vacuum_truncate_set)},
+ {"vacuum_truncate", RELOPT_TYPE_TERNARY,
+ offsetof(StdRdOptions, vacuum_truncate)},
{"vacuum_max_eager_freeze_failure_rate", RELOPT_TYPE_REAL,
offsetof(StdRdOptions, vacuum_max_eager_freeze_failure_rate)}
};
@@ -1993,7 +2071,6 @@ build_local_reloptions(local_relopts *relopts, Datum options, bool validate)
elems[i].optname = opt->option->name;
elems[i].opttype = opt->option->type;
elems[i].offset = opt->offset;
- elems[i].isset_offset = 0; /* not supported for local relopts yet */
i++;
}
diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c
index 733ef40ae7..977babff54 100644
--- a/src/backend/commands/vacuum.c
+++ b/src/backend/commands/vacuum.c
@@ -2221,9 +2221,9 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams params,
{
StdRdOptions *opts = (StdRdOptions *) rel->rd_options;
- if (opts && opts->vacuum_truncate_set)
+ if (opts && opts->vacuum_truncate != TERNARY_UNSET)
{
- if (opts->vacuum_truncate)
+ if (opts->vacuum_truncate == TERNARY_TRUE)
params.truncate = VACOPTVALUE_ENABLED;
else
params.truncate = VACOPTVALUE_DISABLED;
diff --git a/src/include/access/reloptions.h b/src/include/access/reloptions.h
index a604a4702c..a436697658 100644
--- a/src/include/access/reloptions.h
+++ b/src/include/access/reloptions.h
@@ -29,6 +29,7 @@
typedef enum relopt_type
{
RELOPT_TYPE_BOOL,
+ RELOPT_TYPE_TERNARY, /* on, off, unset */
RELOPT_TYPE_INT,
RELOPT_TYPE_REAL,
RELOPT_TYPE_ENUM,
@@ -80,6 +81,7 @@ typedef struct relopt_value
union
{
bool bool_val;
+ ternary ternary_val;
int int_val;
double real_val;
int enum_val;
@@ -94,6 +96,12 @@ typedef struct relopt_bool
bool default_val;
} relopt_bool;
+typedef struct relopt_rernary
+{
+ relopt_gen gen;
+ int default_val;
+} relopt_ternary;
+
typedef struct relopt_int
{
relopt_gen gen;
@@ -152,19 +160,6 @@ typedef struct
const char *optname; /* option's name */
relopt_type opttype; /* option's datatype */
int offset; /* offset of field in result struct */
-
- /*
- * isset_offset is an optional offset of a field in the result struct that
- * stores whether the option is explicitly set for the relation or if it
- * just picked up the default value. In most cases, this can be
- * accomplished by giving the reloption a special out-of-range default
- * value (e.g., some integer reloptions use -2), but this isn't always
- * possible. For example, a Boolean reloption cannot be given an
- * out-of-range default, so we need another way to discover the source of
- * its value. This offset is only used if given a value greater than
- * zero.
- */
- int isset_offset;
} relopt_parse_elt;
/* Local reloption definition */
@@ -195,6 +190,8 @@ typedef struct local_relopts
extern relopt_kind add_reloption_kind(void);
extern void add_bool_reloption(bits32 kinds, const char *name, const char *desc,
bool default_val, LOCKMODE lockmode);
+extern void add_ternary_reloption(bits32 kinds, const char *name,
+ const char *desc, int default_val, LOCKMODE lockmode);
extern void add_int_reloption(bits32 kinds, const char *name, const char *desc,
int default_val, int min_val, int max_val,
LOCKMODE lockmode);
@@ -214,6 +211,9 @@ extern void register_reloptions_validator(local_relopts *relopts,
extern void add_local_bool_reloption(local_relopts *relopts, const char *name,
const char *desc, bool default_val,
int offset);
+extern void add_local_ternary_reloption(local_relopts *relopts,
+ const char *name, const char *desc,
+ ternary default_val, int offset);
extern void add_local_int_reloption(local_relopts *relopts, const char *name,
const char *desc, int default_val,
int min_val, int max_val, int offset);
diff --git a/src/include/c.h b/src/include/c.h
index 39022f8a9d..9c59ccccf8 100644
--- a/src/include/c.h
+++ b/src/include/c.h
@@ -488,6 +488,22 @@ typedef void (*pg_funcptr_t) (void);
#include <stdbool.h>
+/*
+ * ternary
+ * Boolean value with an extrea "unset" option
+ *
+ * Ternary data type is used in relation options that can be "true", "false" or
+ * "unset". Since relation options are used deep inside the PostgreSQL code,
+ * this type is declared globally.
+*/
+
+typedef enum ternary
+{
+ TERNARY_FALSE = 0,
+ TERNARY_TRUE = 1,
+ TERNARY_UNSET = -1
+} ternary;
+
/* ----------------------------------------------------------------
* Section 3: standard system types
diff --git a/src/include/utils/rel.h b/src/include/utils/rel.h
index b552359915..7346d618f9 100644
--- a/src/include/utils/rel.h
+++ b/src/include/utils/rel.h
@@ -346,8 +346,7 @@ typedef struct StdRdOptions
bool user_catalog_table; /* use as an additional catalog relation */
int parallel_workers; /* max number of parallel workers */
StdRdOptIndexCleanup vacuum_index_cleanup; /* controls index vacuuming */
- bool vacuum_truncate; /* enables vacuum to truncate a relation */
- bool vacuum_truncate_set; /* whether vacuum_truncate is set */
+ ternary vacuum_truncate; /* enables vacuum to truncate a relation */
/*
* Fraction of pages in a relation that vacuum can eagerly scan and fail
--
2.39.2
--nextPart4155600.3ZeAukHxDK
Content-Disposition: attachment;
filename="v2a-0003-Add-alias-to-be-used-as-unset-state.patch"
Content-Transfer-Encoding: 7Bit
Content-Type: text/x-patch; charset="unicode-2-0-utf-8";
name="v2a-0003-Add-alias-to-be-used-as-unset-state.patch"
^ permalink raw reply [nested|flat] 868+ messages in thread
* [PATCH v2 2/4] Introduce ternary reloptions
@ 2025-09-04 14:41 Nikolay Shaplov <[email protected]>
0 siblings, 0 replies; 868+ messages in thread
From: Nikolay Shaplov @ 2025-09-04 14:41 UTC (permalink / raw)
Introduce ternary reloption as a replacement for current `vacuum_truncate`
implementation. Remove `vacuum_truncate_set` additional flag and using
`TERNARY_UNSET` value instead.
---
src/backend/access/common/reloptions.c | 129 ++++++++++++++++++++-----
src/backend/commands/vacuum.c | 4 +-
src/include/access/reloptions.h | 26 ++---
src/include/c.h | 16 +++
src/include/utils/rel.h | 3 +-
5 files changed, 135 insertions(+), 43 deletions(-)
diff --git a/src/backend/access/common/reloptions.c b/src/backend/access/common/reloptions.c
index 0af3fea68f..f543f11001 100644
--- a/src/backend/access/common/reloptions.c
+++ b/src/backend/access/common/reloptions.c
@@ -40,9 +40,9 @@
*
* To add an option:
*
- * (i) decide on a type (bool, integer, real, enum, string), name, default
- * value, upper and lower bounds (if applicable); for strings, consider a
- * validation routine.
+ * (i) decide on a type (bool, ternary, integer, real, enum, string), name,
+ * default value, upper and lower bounds (if applicable); for strings,
+ * consider a validation routine.
* (ii) add a record below (or use add_<type>_reloption).
* (iii) add it to the appropriate options struct (perhaps StdRdOptions)
* (iv) add it to the appropriate handling routine (perhaps
@@ -147,15 +147,6 @@ static relopt_bool boolRelOpts[] =
},
false
},
- {
- {
- "vacuum_truncate",
- "Enables vacuum to truncate empty pages at the end of this table",
- RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
- ShareUpdateExclusiveLock
- },
- true
- },
{
{
"deduplicate_items",
@@ -170,6 +161,21 @@ static relopt_bool boolRelOpts[] =
{{NULL}}
};
+static relopt_ternary ternaryRelOpts[] =
+{
+ {
+ {
+ "vacuum_truncate",
+ "Enables vacuum to truncate empty pages at the end of this table",
+ RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
+ ShareUpdateExclusiveLock
+ },
+ TERNARY_UNSET
+ },
+ /* list terminator */
+ {{NULL}}
+};
+
static relopt_int intRelOpts[] =
{
{
@@ -600,6 +606,13 @@ initialize_reloptions(void)
boolRelOpts[i].gen.lockmode));
j++;
}
+ for (i = 0; ternaryRelOpts[i].gen.name; i++)
+ {
+ Assert(DoLockModesConflict(ternaryRelOpts[i].gen.lockmode,
+ ternaryRelOpts[i].gen.lockmode));
+ j++;
+ }
+
for (i = 0; intRelOpts[i].gen.name; i++)
{
Assert(DoLockModesConflict(intRelOpts[i].gen.lockmode,
@@ -640,6 +653,14 @@ initialize_reloptions(void)
j++;
}
+ for (i = 0; ternaryRelOpts[i].gen.name; i++)
+ {
+ relOpts[j] = &ternaryRelOpts[i].gen;
+ relOpts[j]->type = RELOPT_TYPE_TERNARY;
+ relOpts[j]->namelen = strlen(relOpts[j]->name);
+ j++;
+ }
+
for (i = 0; intRelOpts[i].gen.name; i++)
{
relOpts[j] = &intRelOpts[i].gen;
@@ -800,6 +821,9 @@ allocate_reloption(bits32 kinds, int type, const char *name, const char *desc,
case RELOPT_TYPE_BOOL:
size = sizeof(relopt_bool);
break;
+ case RELOPT_TYPE_TERNARY:
+ size = sizeof(relopt_ternary);
+ break;
case RELOPT_TYPE_INT:
size = sizeof(relopt_int);
break;
@@ -883,6 +907,54 @@ add_local_bool_reloption(local_relopts *relopts, const char *name,
add_local_reloption(relopts, (relopt_gen *) newoption, offset);
}
+/*
+ * init_ternary_reloption
+ * Allocate and initialize a new ternary reloption
+ */
+static relopt_ternary *
+init_ternary_reloption(bits32 kinds, const char *name, const char *desc,
+ ternary default_val, LOCKMODE lockmode)
+{
+ relopt_ternary *newoption;
+
+ newoption = (relopt_ternary *) allocate_reloption(kinds,
+ RELOPT_TYPE_TERNARY, name, desc, lockmode);
+ newoption->default_val = default_val;
+
+ return newoption;
+}
+
+/*
+ * add_ternary_reloption
+ * Add a new ternary reloption
+ */
+void
+add_ternary_reloption(bits32 kinds, const char *name, const char *desc,
+ ternary default_val, LOCKMODE lockmode)
+{
+ relopt_ternary *newoption = init_ternary_reloption(kinds, name, desc,
+ default_val, lockmode);
+
+ add_reloption((relopt_gen *) newoption);
+}
+
+/*
+ * add_local_ternary_reloption
+ * Add a new ternary local reloption
+ *
+ * 'offset' is offset of ternary-typed field.
+ */
+void
+add_local_ternary_reloption(local_relopts *relopts, const char *name,
+ const char *desc, ternary default_val,
+ int offset)
+{
+ relopt_ternary *newoption = init_ternary_reloption(RELOPT_KIND_LOCAL,
+ name, desc,
+ default_val, 0);
+
+ add_local_reloption(relopts, (relopt_gen *) newoption, offset);
+}
/*
* init_real_reloption
@@ -1617,6 +1689,18 @@ parse_one_reloption(relopt_value *option, char *text_str, int text_len,
option->gen->name, value)));
}
break;
+ case RELOPT_TYPE_TERNARY:
+ {
+ bool b;
+ parsed = parse_bool(value, &b);
+ option->values.ternary_val = b ? TERNARY_TRUE : TERNARY_FALSE;
+ if (validate && !parsed)
+ ereport(ERROR,
+ (errcode(ERRCODE_INVALID_PARAMETER_VALUE),
+ errmsg("invalid value for ternary option \"%s\": %s",
+ option->gen->name, value)));
+ }
+ break;
case RELOPT_TYPE_INT:
{
relopt_int *optint = (relopt_int *) option->gen;
@@ -1780,17 +1864,6 @@ fillRelOptions(void *rdopts, Size basesize,
char *itempos = ((char *) rdopts) + elems[j].offset;
char *string_val;
- /*
- * If isset_offset is provided, store whether the reloption is
- * set there.
- */
- if (elems[j].isset_offset > 0)
- {
- char *setpos = ((char *) rdopts) + elems[j].isset_offset;
-
- *(bool *) setpos = options[i].isset;
- }
-
switch (options[i].gen->type)
{
case RELOPT_TYPE_BOOL:
@@ -1798,6 +1871,11 @@ fillRelOptions(void *rdopts, Size basesize,
options[i].values.bool_val :
((relopt_bool *) options[i].gen)->default_val;
break;
+ case RELOPT_TYPE_TERNARY:
+ *(ternary *) itempos = options[i].isset ?
+ options[i].values.ternary_val :
+ ((relopt_ternary *) options[i].gen)->default_val;
+ break;
case RELOPT_TYPE_INT:
*(int *) itempos = options[i].isset ?
options[i].values.int_val :
@@ -1912,8 +1990,8 @@ default_reloptions(Datum reloptions, bool validate, relopt_kind kind)
offsetof(StdRdOptions, parallel_workers)},
{"vacuum_index_cleanup", RELOPT_TYPE_ENUM,
offsetof(StdRdOptions, vacuum_index_cleanup)},
- {"vacuum_truncate", RELOPT_TYPE_BOOL,
- offsetof(StdRdOptions, vacuum_truncate), offsetof(StdRdOptions, vacuum_truncate_set)},
+ {"vacuum_truncate", RELOPT_TYPE_TERNARY,
+ offsetof(StdRdOptions, vacuum_truncate)},
{"vacuum_max_eager_freeze_failure_rate", RELOPT_TYPE_REAL,
offsetof(StdRdOptions, vacuum_max_eager_freeze_failure_rate)}
};
@@ -1993,7 +2071,6 @@ build_local_reloptions(local_relopts *relopts, Datum options, bool validate)
elems[i].optname = opt->option->name;
elems[i].opttype = opt->option->type;
elems[i].offset = opt->offset;
- elems[i].isset_offset = 0; /* not supported for local relopts yet */
i++;
}
diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c
index 733ef40ae7..977babff54 100644
--- a/src/backend/commands/vacuum.c
+++ b/src/backend/commands/vacuum.c
@@ -2221,9 +2221,9 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams params,
{
StdRdOptions *opts = (StdRdOptions *) rel->rd_options;
- if (opts && opts->vacuum_truncate_set)
+ if (opts && opts->vacuum_truncate != TERNARY_UNSET)
{
- if (opts->vacuum_truncate)
+ if (opts->vacuum_truncate == TERNARY_TRUE)
params.truncate = VACOPTVALUE_ENABLED;
else
params.truncate = VACOPTVALUE_DISABLED;
diff --git a/src/include/access/reloptions.h b/src/include/access/reloptions.h
index a604a4702c..a436697658 100644
--- a/src/include/access/reloptions.h
+++ b/src/include/access/reloptions.h
@@ -29,6 +29,7 @@
typedef enum relopt_type
{
RELOPT_TYPE_BOOL,
+ RELOPT_TYPE_TERNARY, /* on, off, unset */
RELOPT_TYPE_INT,
RELOPT_TYPE_REAL,
RELOPT_TYPE_ENUM,
@@ -80,6 +81,7 @@ typedef struct relopt_value
union
{
bool bool_val;
+ ternary ternary_val;
int int_val;
double real_val;
int enum_val;
@@ -94,6 +96,12 @@ typedef struct relopt_bool
bool default_val;
} relopt_bool;
+typedef struct relopt_rernary
+{
+ relopt_gen gen;
+ int default_val;
+} relopt_ternary;
+
typedef struct relopt_int
{
relopt_gen gen;
@@ -152,19 +160,6 @@ typedef struct
const char *optname; /* option's name */
relopt_type opttype; /* option's datatype */
int offset; /* offset of field in result struct */
-
- /*
- * isset_offset is an optional offset of a field in the result struct that
- * stores whether the option is explicitly set for the relation or if it
- * just picked up the default value. In most cases, this can be
- * accomplished by giving the reloption a special out-of-range default
- * value (e.g., some integer reloptions use -2), but this isn't always
- * possible. For example, a Boolean reloption cannot be given an
- * out-of-range default, so we need another way to discover the source of
- * its value. This offset is only used if given a value greater than
- * zero.
- */
- int isset_offset;
} relopt_parse_elt;
/* Local reloption definition */
@@ -195,6 +190,8 @@ typedef struct local_relopts
extern relopt_kind add_reloption_kind(void);
extern void add_bool_reloption(bits32 kinds, const char *name, const char *desc,
bool default_val, LOCKMODE lockmode);
+extern void add_ternary_reloption(bits32 kinds, const char *name,
+ const char *desc, int default_val, LOCKMODE lockmode);
extern void add_int_reloption(bits32 kinds, const char *name, const char *desc,
int default_val, int min_val, int max_val,
LOCKMODE lockmode);
@@ -214,6 +211,9 @@ extern void register_reloptions_validator(local_relopts *relopts,
extern void add_local_bool_reloption(local_relopts *relopts, const char *name,
const char *desc, bool default_val,
int offset);
+extern void add_local_ternary_reloption(local_relopts *relopts,
+ const char *name, const char *desc,
+ ternary default_val, int offset);
extern void add_local_int_reloption(local_relopts *relopts, const char *name,
const char *desc, int default_val,
int min_val, int max_val, int offset);
diff --git a/src/include/c.h b/src/include/c.h
index 39022f8a9d..9c59ccccf8 100644
--- a/src/include/c.h
+++ b/src/include/c.h
@@ -488,6 +488,22 @@ typedef void (*pg_funcptr_t) (void);
#include <stdbool.h>
+/*
+ * ternary
+ * Boolean value with an extrea "unset" option
+ *
+ * Ternary data type is used in relation options that can be "true", "false" or
+ * "unset". Since relation options are used deep inside the PostgreSQL code,
+ * this type is declared globally.
+*/
+
+typedef enum ternary
+{
+ TERNARY_FALSE = 0,
+ TERNARY_TRUE = 1,
+ TERNARY_UNSET = -1
+} ternary;
+
/* ----------------------------------------------------------------
* Section 3: standard system types
diff --git a/src/include/utils/rel.h b/src/include/utils/rel.h
index b552359915..7346d618f9 100644
--- a/src/include/utils/rel.h
+++ b/src/include/utils/rel.h
@@ -346,8 +346,7 @@ typedef struct StdRdOptions
bool user_catalog_table; /* use as an additional catalog relation */
int parallel_workers; /* max number of parallel workers */
StdRdOptIndexCleanup vacuum_index_cleanup; /* controls index vacuuming */
- bool vacuum_truncate; /* enables vacuum to truncate a relation */
- bool vacuum_truncate_set; /* whether vacuum_truncate is set */
+ ternary vacuum_truncate; /* enables vacuum to truncate a relation */
/*
* Fraction of pages in a relation that vacuum can eagerly scan and fail
--
2.39.2
--nextPart5129932.5fSG56mABF
Content-Disposition: attachment;
filename="v2-0003-Add-alias-to-be-used-as-unset-state.patch"
Content-Transfer-Encoding: 7Bit
Content-Type: text/x-patch; charset="unicode-2-0-utf-8";
name="v2-0003-Add-alias-to-be-used-as-unset-state.patch"
^ permalink raw reply [nested|flat] 868+ messages in thread
* [PATCH v2a 2/4] Introduce ternary reloptions
@ 2025-09-04 14:41 Nikolay Shaplov <[email protected]>
0 siblings, 0 replies; 868+ messages in thread
From: Nikolay Shaplov @ 2025-09-04 14:41 UTC (permalink / raw)
Introduce ternary reloption as a replacement for current `vacuum_truncate`
implementation. Remove `vacuum_truncate_set` additional flag and using
`TERNARY_UNSET` value instead.
---
src/backend/access/common/reloptions.c | 129 ++++++++++++++++++++-----
src/backend/commands/vacuum.c | 4 +-
src/include/access/reloptions.h | 26 ++---
src/include/c.h | 16 +++
src/include/utils/rel.h | 3 +-
5 files changed, 135 insertions(+), 43 deletions(-)
diff --git a/src/backend/access/common/reloptions.c b/src/backend/access/common/reloptions.c
index 0af3fea68f..f543f11001 100644
--- a/src/backend/access/common/reloptions.c
+++ b/src/backend/access/common/reloptions.c
@@ -40,9 +40,9 @@
*
* To add an option:
*
- * (i) decide on a type (bool, integer, real, enum, string), name, default
- * value, upper and lower bounds (if applicable); for strings, consider a
- * validation routine.
+ * (i) decide on a type (bool, ternary, integer, real, enum, string), name,
+ * default value, upper and lower bounds (if applicable); for strings,
+ * consider a validation routine.
* (ii) add a record below (or use add_<type>_reloption).
* (iii) add it to the appropriate options struct (perhaps StdRdOptions)
* (iv) add it to the appropriate handling routine (perhaps
@@ -147,15 +147,6 @@ static relopt_bool boolRelOpts[] =
},
false
},
- {
- {
- "vacuum_truncate",
- "Enables vacuum to truncate empty pages at the end of this table",
- RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
- ShareUpdateExclusiveLock
- },
- true
- },
{
{
"deduplicate_items",
@@ -170,6 +161,21 @@ static relopt_bool boolRelOpts[] =
{{NULL}}
};
+static relopt_ternary ternaryRelOpts[] =
+{
+ {
+ {
+ "vacuum_truncate",
+ "Enables vacuum to truncate empty pages at the end of this table",
+ RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
+ ShareUpdateExclusiveLock
+ },
+ TERNARY_UNSET
+ },
+ /* list terminator */
+ {{NULL}}
+};
+
static relopt_int intRelOpts[] =
{
{
@@ -600,6 +606,13 @@ initialize_reloptions(void)
boolRelOpts[i].gen.lockmode));
j++;
}
+ for (i = 0; ternaryRelOpts[i].gen.name; i++)
+ {
+ Assert(DoLockModesConflict(ternaryRelOpts[i].gen.lockmode,
+ ternaryRelOpts[i].gen.lockmode));
+ j++;
+ }
+
for (i = 0; intRelOpts[i].gen.name; i++)
{
Assert(DoLockModesConflict(intRelOpts[i].gen.lockmode,
@@ -640,6 +653,14 @@ initialize_reloptions(void)
j++;
}
+ for (i = 0; ternaryRelOpts[i].gen.name; i++)
+ {
+ relOpts[j] = &ternaryRelOpts[i].gen;
+ relOpts[j]->type = RELOPT_TYPE_TERNARY;
+ relOpts[j]->namelen = strlen(relOpts[j]->name);
+ j++;
+ }
+
for (i = 0; intRelOpts[i].gen.name; i++)
{
relOpts[j] = &intRelOpts[i].gen;
@@ -800,6 +821,9 @@ allocate_reloption(bits32 kinds, int type, const char *name, const char *desc,
case RELOPT_TYPE_BOOL:
size = sizeof(relopt_bool);
break;
+ case RELOPT_TYPE_TERNARY:
+ size = sizeof(relopt_ternary);
+ break;
case RELOPT_TYPE_INT:
size = sizeof(relopt_int);
break;
@@ -883,6 +907,54 @@ add_local_bool_reloption(local_relopts *relopts, const char *name,
add_local_reloption(relopts, (relopt_gen *) newoption, offset);
}
+/*
+ * init_ternary_reloption
+ * Allocate and initialize a new ternary reloption
+ */
+static relopt_ternary *
+init_ternary_reloption(bits32 kinds, const char *name, const char *desc,
+ ternary default_val, LOCKMODE lockmode)
+{
+ relopt_ternary *newoption;
+
+ newoption = (relopt_ternary *) allocate_reloption(kinds,
+ RELOPT_TYPE_TERNARY, name, desc, lockmode);
+ newoption->default_val = default_val;
+
+ return newoption;
+}
+
+/*
+ * add_ternary_reloption
+ * Add a new ternary reloption
+ */
+void
+add_ternary_reloption(bits32 kinds, const char *name, const char *desc,
+ ternary default_val, LOCKMODE lockmode)
+{
+ relopt_ternary *newoption = init_ternary_reloption(kinds, name, desc,
+ default_val, lockmode);
+
+ add_reloption((relopt_gen *) newoption);
+}
+
+/*
+ * add_local_ternary_reloption
+ * Add a new ternary local reloption
+ *
+ * 'offset' is offset of ternary-typed field.
+ */
+void
+add_local_ternary_reloption(local_relopts *relopts, const char *name,
+ const char *desc, ternary default_val,
+ int offset)
+{
+ relopt_ternary *newoption = init_ternary_reloption(RELOPT_KIND_LOCAL,
+ name, desc,
+ default_val, 0);
+
+ add_local_reloption(relopts, (relopt_gen *) newoption, offset);
+}
/*
* init_real_reloption
@@ -1617,6 +1689,18 @@ parse_one_reloption(relopt_value *option, char *text_str, int text_len,
option->gen->name, value)));
}
break;
+ case RELOPT_TYPE_TERNARY:
+ {
+ bool b;
+ parsed = parse_bool(value, &b);
+ option->values.ternary_val = b ? TERNARY_TRUE : TERNARY_FALSE;
+ if (validate && !parsed)
+ ereport(ERROR,
+ (errcode(ERRCODE_INVALID_PARAMETER_VALUE),
+ errmsg("invalid value for ternary option \"%s\": %s",
+ option->gen->name, value)));
+ }
+ break;
case RELOPT_TYPE_INT:
{
relopt_int *optint = (relopt_int *) option->gen;
@@ -1780,17 +1864,6 @@ fillRelOptions(void *rdopts, Size basesize,
char *itempos = ((char *) rdopts) + elems[j].offset;
char *string_val;
- /*
- * If isset_offset is provided, store whether the reloption is
- * set there.
- */
- if (elems[j].isset_offset > 0)
- {
- char *setpos = ((char *) rdopts) + elems[j].isset_offset;
-
- *(bool *) setpos = options[i].isset;
- }
-
switch (options[i].gen->type)
{
case RELOPT_TYPE_BOOL:
@@ -1798,6 +1871,11 @@ fillRelOptions(void *rdopts, Size basesize,
options[i].values.bool_val :
((relopt_bool *) options[i].gen)->default_val;
break;
+ case RELOPT_TYPE_TERNARY:
+ *(ternary *) itempos = options[i].isset ?
+ options[i].values.ternary_val :
+ ((relopt_ternary *) options[i].gen)->default_val;
+ break;
case RELOPT_TYPE_INT:
*(int *) itempos = options[i].isset ?
options[i].values.int_val :
@@ -1912,8 +1990,8 @@ default_reloptions(Datum reloptions, bool validate, relopt_kind kind)
offsetof(StdRdOptions, parallel_workers)},
{"vacuum_index_cleanup", RELOPT_TYPE_ENUM,
offsetof(StdRdOptions, vacuum_index_cleanup)},
- {"vacuum_truncate", RELOPT_TYPE_BOOL,
- offsetof(StdRdOptions, vacuum_truncate), offsetof(StdRdOptions, vacuum_truncate_set)},
+ {"vacuum_truncate", RELOPT_TYPE_TERNARY,
+ offsetof(StdRdOptions, vacuum_truncate)},
{"vacuum_max_eager_freeze_failure_rate", RELOPT_TYPE_REAL,
offsetof(StdRdOptions, vacuum_max_eager_freeze_failure_rate)}
};
@@ -1993,7 +2071,6 @@ build_local_reloptions(local_relopts *relopts, Datum options, bool validate)
elems[i].optname = opt->option->name;
elems[i].opttype = opt->option->type;
elems[i].offset = opt->offset;
- elems[i].isset_offset = 0; /* not supported for local relopts yet */
i++;
}
diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c
index 733ef40ae7..977babff54 100644
--- a/src/backend/commands/vacuum.c
+++ b/src/backend/commands/vacuum.c
@@ -2221,9 +2221,9 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams params,
{
StdRdOptions *opts = (StdRdOptions *) rel->rd_options;
- if (opts && opts->vacuum_truncate_set)
+ if (opts && opts->vacuum_truncate != TERNARY_UNSET)
{
- if (opts->vacuum_truncate)
+ if (opts->vacuum_truncate == TERNARY_TRUE)
params.truncate = VACOPTVALUE_ENABLED;
else
params.truncate = VACOPTVALUE_DISABLED;
diff --git a/src/include/access/reloptions.h b/src/include/access/reloptions.h
index a604a4702c..a436697658 100644
--- a/src/include/access/reloptions.h
+++ b/src/include/access/reloptions.h
@@ -29,6 +29,7 @@
typedef enum relopt_type
{
RELOPT_TYPE_BOOL,
+ RELOPT_TYPE_TERNARY, /* on, off, unset */
RELOPT_TYPE_INT,
RELOPT_TYPE_REAL,
RELOPT_TYPE_ENUM,
@@ -80,6 +81,7 @@ typedef struct relopt_value
union
{
bool bool_val;
+ ternary ternary_val;
int int_val;
double real_val;
int enum_val;
@@ -94,6 +96,12 @@ typedef struct relopt_bool
bool default_val;
} relopt_bool;
+typedef struct relopt_rernary
+{
+ relopt_gen gen;
+ int default_val;
+} relopt_ternary;
+
typedef struct relopt_int
{
relopt_gen gen;
@@ -152,19 +160,6 @@ typedef struct
const char *optname; /* option's name */
relopt_type opttype; /* option's datatype */
int offset; /* offset of field in result struct */
-
- /*
- * isset_offset is an optional offset of a field in the result struct that
- * stores whether the option is explicitly set for the relation or if it
- * just picked up the default value. In most cases, this can be
- * accomplished by giving the reloption a special out-of-range default
- * value (e.g., some integer reloptions use -2), but this isn't always
- * possible. For example, a Boolean reloption cannot be given an
- * out-of-range default, so we need another way to discover the source of
- * its value. This offset is only used if given a value greater than
- * zero.
- */
- int isset_offset;
} relopt_parse_elt;
/* Local reloption definition */
@@ -195,6 +190,8 @@ typedef struct local_relopts
extern relopt_kind add_reloption_kind(void);
extern void add_bool_reloption(bits32 kinds, const char *name, const char *desc,
bool default_val, LOCKMODE lockmode);
+extern void add_ternary_reloption(bits32 kinds, const char *name,
+ const char *desc, int default_val, LOCKMODE lockmode);
extern void add_int_reloption(bits32 kinds, const char *name, const char *desc,
int default_val, int min_val, int max_val,
LOCKMODE lockmode);
@@ -214,6 +211,9 @@ extern void register_reloptions_validator(local_relopts *relopts,
extern void add_local_bool_reloption(local_relopts *relopts, const char *name,
const char *desc, bool default_val,
int offset);
+extern void add_local_ternary_reloption(local_relopts *relopts,
+ const char *name, const char *desc,
+ ternary default_val, int offset);
extern void add_local_int_reloption(local_relopts *relopts, const char *name,
const char *desc, int default_val,
int min_val, int max_val, int offset);
diff --git a/src/include/c.h b/src/include/c.h
index 39022f8a9d..9c59ccccf8 100644
--- a/src/include/c.h
+++ b/src/include/c.h
@@ -488,6 +488,22 @@ typedef void (*pg_funcptr_t) (void);
#include <stdbool.h>
+/*
+ * ternary
+ * Boolean value with an extrea "unset" option
+ *
+ * Ternary data type is used in relation options that can be "true", "false" or
+ * "unset". Since relation options are used deep inside the PostgreSQL code,
+ * this type is declared globally.
+*/
+
+typedef enum ternary
+{
+ TERNARY_FALSE = 0,
+ TERNARY_TRUE = 1,
+ TERNARY_UNSET = -1
+} ternary;
+
/* ----------------------------------------------------------------
* Section 3: standard system types
diff --git a/src/include/utils/rel.h b/src/include/utils/rel.h
index b552359915..7346d618f9 100644
--- a/src/include/utils/rel.h
+++ b/src/include/utils/rel.h
@@ -346,8 +346,7 @@ typedef struct StdRdOptions
bool user_catalog_table; /* use as an additional catalog relation */
int parallel_workers; /* max number of parallel workers */
StdRdOptIndexCleanup vacuum_index_cleanup; /* controls index vacuuming */
- bool vacuum_truncate; /* enables vacuum to truncate a relation */
- bool vacuum_truncate_set; /* whether vacuum_truncate is set */
+ ternary vacuum_truncate; /* enables vacuum to truncate a relation */
/*
* Fraction of pages in a relation that vacuum can eagerly scan and fail
--
2.39.2
--nextPart4155600.3ZeAukHxDK
Content-Disposition: attachment;
filename="v2a-0003-Add-alias-to-be-used-as-unset-state.patch"
Content-Transfer-Encoding: 7Bit
Content-Type: text/x-patch; charset="unicode-2-0-utf-8";
name="v2a-0003-Add-alias-to-be-used-as-unset-state.patch"
^ permalink raw reply [nested|flat] 868+ messages in thread
* [PATCH v2 2/4] Introduce ternary reloptions
@ 2025-09-04 14:41 Nikolay Shaplov <[email protected]>
0 siblings, 0 replies; 868+ messages in thread
From: Nikolay Shaplov @ 2025-09-04 14:41 UTC (permalink / raw)
Introduce ternary reloption as a replacement for current `vacuum_truncate`
implementation. Remove `vacuum_truncate_set` additional flag and using
`TERNARY_UNSET` value instead.
---
src/backend/access/common/reloptions.c | 129 ++++++++++++++++++++-----
src/backend/commands/vacuum.c | 4 +-
src/include/access/reloptions.h | 26 ++---
src/include/c.h | 16 +++
src/include/utils/rel.h | 3 +-
5 files changed, 135 insertions(+), 43 deletions(-)
diff --git a/src/backend/access/common/reloptions.c b/src/backend/access/common/reloptions.c
index 0af3fea68f..f543f11001 100644
--- a/src/backend/access/common/reloptions.c
+++ b/src/backend/access/common/reloptions.c
@@ -40,9 +40,9 @@
*
* To add an option:
*
- * (i) decide on a type (bool, integer, real, enum, string), name, default
- * value, upper and lower bounds (if applicable); for strings, consider a
- * validation routine.
+ * (i) decide on a type (bool, ternary, integer, real, enum, string), name,
+ * default value, upper and lower bounds (if applicable); for strings,
+ * consider a validation routine.
* (ii) add a record below (or use add_<type>_reloption).
* (iii) add it to the appropriate options struct (perhaps StdRdOptions)
* (iv) add it to the appropriate handling routine (perhaps
@@ -147,15 +147,6 @@ static relopt_bool boolRelOpts[] =
},
false
},
- {
- {
- "vacuum_truncate",
- "Enables vacuum to truncate empty pages at the end of this table",
- RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
- ShareUpdateExclusiveLock
- },
- true
- },
{
{
"deduplicate_items",
@@ -170,6 +161,21 @@ static relopt_bool boolRelOpts[] =
{{NULL}}
};
+static relopt_ternary ternaryRelOpts[] =
+{
+ {
+ {
+ "vacuum_truncate",
+ "Enables vacuum to truncate empty pages at the end of this table",
+ RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
+ ShareUpdateExclusiveLock
+ },
+ TERNARY_UNSET
+ },
+ /* list terminator */
+ {{NULL}}
+};
+
static relopt_int intRelOpts[] =
{
{
@@ -600,6 +606,13 @@ initialize_reloptions(void)
boolRelOpts[i].gen.lockmode));
j++;
}
+ for (i = 0; ternaryRelOpts[i].gen.name; i++)
+ {
+ Assert(DoLockModesConflict(ternaryRelOpts[i].gen.lockmode,
+ ternaryRelOpts[i].gen.lockmode));
+ j++;
+ }
+
for (i = 0; intRelOpts[i].gen.name; i++)
{
Assert(DoLockModesConflict(intRelOpts[i].gen.lockmode,
@@ -640,6 +653,14 @@ initialize_reloptions(void)
j++;
}
+ for (i = 0; ternaryRelOpts[i].gen.name; i++)
+ {
+ relOpts[j] = &ternaryRelOpts[i].gen;
+ relOpts[j]->type = RELOPT_TYPE_TERNARY;
+ relOpts[j]->namelen = strlen(relOpts[j]->name);
+ j++;
+ }
+
for (i = 0; intRelOpts[i].gen.name; i++)
{
relOpts[j] = &intRelOpts[i].gen;
@@ -800,6 +821,9 @@ allocate_reloption(bits32 kinds, int type, const char *name, const char *desc,
case RELOPT_TYPE_BOOL:
size = sizeof(relopt_bool);
break;
+ case RELOPT_TYPE_TERNARY:
+ size = sizeof(relopt_ternary);
+ break;
case RELOPT_TYPE_INT:
size = sizeof(relopt_int);
break;
@@ -883,6 +907,54 @@ add_local_bool_reloption(local_relopts *relopts, const char *name,
add_local_reloption(relopts, (relopt_gen *) newoption, offset);
}
+/*
+ * init_ternary_reloption
+ * Allocate and initialize a new ternary reloption
+ */
+static relopt_ternary *
+init_ternary_reloption(bits32 kinds, const char *name, const char *desc,
+ ternary default_val, LOCKMODE lockmode)
+{
+ relopt_ternary *newoption;
+
+ newoption = (relopt_ternary *) allocate_reloption(kinds,
+ RELOPT_TYPE_TERNARY, name, desc, lockmode);
+ newoption->default_val = default_val;
+
+ return newoption;
+}
+
+/*
+ * add_ternary_reloption
+ * Add a new ternary reloption
+ */
+void
+add_ternary_reloption(bits32 kinds, const char *name, const char *desc,
+ ternary default_val, LOCKMODE lockmode)
+{
+ relopt_ternary *newoption = init_ternary_reloption(kinds, name, desc,
+ default_val, lockmode);
+
+ add_reloption((relopt_gen *) newoption);
+}
+
+/*
+ * add_local_ternary_reloption
+ * Add a new ternary local reloption
+ *
+ * 'offset' is offset of ternary-typed field.
+ */
+void
+add_local_ternary_reloption(local_relopts *relopts, const char *name,
+ const char *desc, ternary default_val,
+ int offset)
+{
+ relopt_ternary *newoption = init_ternary_reloption(RELOPT_KIND_LOCAL,
+ name, desc,
+ default_val, 0);
+
+ add_local_reloption(relopts, (relopt_gen *) newoption, offset);
+}
/*
* init_real_reloption
@@ -1617,6 +1689,18 @@ parse_one_reloption(relopt_value *option, char *text_str, int text_len,
option->gen->name, value)));
}
break;
+ case RELOPT_TYPE_TERNARY:
+ {
+ bool b;
+ parsed = parse_bool(value, &b);
+ option->values.ternary_val = b ? TERNARY_TRUE : TERNARY_FALSE;
+ if (validate && !parsed)
+ ereport(ERROR,
+ (errcode(ERRCODE_INVALID_PARAMETER_VALUE),
+ errmsg("invalid value for ternary option \"%s\": %s",
+ option->gen->name, value)));
+ }
+ break;
case RELOPT_TYPE_INT:
{
relopt_int *optint = (relopt_int *) option->gen;
@@ -1780,17 +1864,6 @@ fillRelOptions(void *rdopts, Size basesize,
char *itempos = ((char *) rdopts) + elems[j].offset;
char *string_val;
- /*
- * If isset_offset is provided, store whether the reloption is
- * set there.
- */
- if (elems[j].isset_offset > 0)
- {
- char *setpos = ((char *) rdopts) + elems[j].isset_offset;
-
- *(bool *) setpos = options[i].isset;
- }
-
switch (options[i].gen->type)
{
case RELOPT_TYPE_BOOL:
@@ -1798,6 +1871,11 @@ fillRelOptions(void *rdopts, Size basesize,
options[i].values.bool_val :
((relopt_bool *) options[i].gen)->default_val;
break;
+ case RELOPT_TYPE_TERNARY:
+ *(ternary *) itempos = options[i].isset ?
+ options[i].values.ternary_val :
+ ((relopt_ternary *) options[i].gen)->default_val;
+ break;
case RELOPT_TYPE_INT:
*(int *) itempos = options[i].isset ?
options[i].values.int_val :
@@ -1912,8 +1990,8 @@ default_reloptions(Datum reloptions, bool validate, relopt_kind kind)
offsetof(StdRdOptions, parallel_workers)},
{"vacuum_index_cleanup", RELOPT_TYPE_ENUM,
offsetof(StdRdOptions, vacuum_index_cleanup)},
- {"vacuum_truncate", RELOPT_TYPE_BOOL,
- offsetof(StdRdOptions, vacuum_truncate), offsetof(StdRdOptions, vacuum_truncate_set)},
+ {"vacuum_truncate", RELOPT_TYPE_TERNARY,
+ offsetof(StdRdOptions, vacuum_truncate)},
{"vacuum_max_eager_freeze_failure_rate", RELOPT_TYPE_REAL,
offsetof(StdRdOptions, vacuum_max_eager_freeze_failure_rate)}
};
@@ -1993,7 +2071,6 @@ build_local_reloptions(local_relopts *relopts, Datum options, bool validate)
elems[i].optname = opt->option->name;
elems[i].opttype = opt->option->type;
elems[i].offset = opt->offset;
- elems[i].isset_offset = 0; /* not supported for local relopts yet */
i++;
}
diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c
index 733ef40ae7..977babff54 100644
--- a/src/backend/commands/vacuum.c
+++ b/src/backend/commands/vacuum.c
@@ -2221,9 +2221,9 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams params,
{
StdRdOptions *opts = (StdRdOptions *) rel->rd_options;
- if (opts && opts->vacuum_truncate_set)
+ if (opts && opts->vacuum_truncate != TERNARY_UNSET)
{
- if (opts->vacuum_truncate)
+ if (opts->vacuum_truncate == TERNARY_TRUE)
params.truncate = VACOPTVALUE_ENABLED;
else
params.truncate = VACOPTVALUE_DISABLED;
diff --git a/src/include/access/reloptions.h b/src/include/access/reloptions.h
index a604a4702c..a436697658 100644
--- a/src/include/access/reloptions.h
+++ b/src/include/access/reloptions.h
@@ -29,6 +29,7 @@
typedef enum relopt_type
{
RELOPT_TYPE_BOOL,
+ RELOPT_TYPE_TERNARY, /* on, off, unset */
RELOPT_TYPE_INT,
RELOPT_TYPE_REAL,
RELOPT_TYPE_ENUM,
@@ -80,6 +81,7 @@ typedef struct relopt_value
union
{
bool bool_val;
+ ternary ternary_val;
int int_val;
double real_val;
int enum_val;
@@ -94,6 +96,12 @@ typedef struct relopt_bool
bool default_val;
} relopt_bool;
+typedef struct relopt_rernary
+{
+ relopt_gen gen;
+ int default_val;
+} relopt_ternary;
+
typedef struct relopt_int
{
relopt_gen gen;
@@ -152,19 +160,6 @@ typedef struct
const char *optname; /* option's name */
relopt_type opttype; /* option's datatype */
int offset; /* offset of field in result struct */
-
- /*
- * isset_offset is an optional offset of a field in the result struct that
- * stores whether the option is explicitly set for the relation or if it
- * just picked up the default value. In most cases, this can be
- * accomplished by giving the reloption a special out-of-range default
- * value (e.g., some integer reloptions use -2), but this isn't always
- * possible. For example, a Boolean reloption cannot be given an
- * out-of-range default, so we need another way to discover the source of
- * its value. This offset is only used if given a value greater than
- * zero.
- */
- int isset_offset;
} relopt_parse_elt;
/* Local reloption definition */
@@ -195,6 +190,8 @@ typedef struct local_relopts
extern relopt_kind add_reloption_kind(void);
extern void add_bool_reloption(bits32 kinds, const char *name, const char *desc,
bool default_val, LOCKMODE lockmode);
+extern void add_ternary_reloption(bits32 kinds, const char *name,
+ const char *desc, int default_val, LOCKMODE lockmode);
extern void add_int_reloption(bits32 kinds, const char *name, const char *desc,
int default_val, int min_val, int max_val,
LOCKMODE lockmode);
@@ -214,6 +211,9 @@ extern void register_reloptions_validator(local_relopts *relopts,
extern void add_local_bool_reloption(local_relopts *relopts, const char *name,
const char *desc, bool default_val,
int offset);
+extern void add_local_ternary_reloption(local_relopts *relopts,
+ const char *name, const char *desc,
+ ternary default_val, int offset);
extern void add_local_int_reloption(local_relopts *relopts, const char *name,
const char *desc, int default_val,
int min_val, int max_val, int offset);
diff --git a/src/include/c.h b/src/include/c.h
index 39022f8a9d..9c59ccccf8 100644
--- a/src/include/c.h
+++ b/src/include/c.h
@@ -488,6 +488,22 @@ typedef void (*pg_funcptr_t) (void);
#include <stdbool.h>
+/*
+ * ternary
+ * Boolean value with an extrea "unset" option
+ *
+ * Ternary data type is used in relation options that can be "true", "false" or
+ * "unset". Since relation options are used deep inside the PostgreSQL code,
+ * this type is declared globally.
+*/
+
+typedef enum ternary
+{
+ TERNARY_FALSE = 0,
+ TERNARY_TRUE = 1,
+ TERNARY_UNSET = -1
+} ternary;
+
/* ----------------------------------------------------------------
* Section 3: standard system types
diff --git a/src/include/utils/rel.h b/src/include/utils/rel.h
index b552359915..7346d618f9 100644
--- a/src/include/utils/rel.h
+++ b/src/include/utils/rel.h
@@ -346,8 +346,7 @@ typedef struct StdRdOptions
bool user_catalog_table; /* use as an additional catalog relation */
int parallel_workers; /* max number of parallel workers */
StdRdOptIndexCleanup vacuum_index_cleanup; /* controls index vacuuming */
- bool vacuum_truncate; /* enables vacuum to truncate a relation */
- bool vacuum_truncate_set; /* whether vacuum_truncate is set */
+ ternary vacuum_truncate; /* enables vacuum to truncate a relation */
/*
* Fraction of pages in a relation that vacuum can eagerly scan and fail
--
2.39.2
--nextPart5129932.5fSG56mABF
Content-Disposition: attachment;
filename="v2-0003-Add-alias-to-be-used-as-unset-state.patch"
Content-Transfer-Encoding: 7Bit
Content-Type: text/x-patch; charset="unicode-2-0-utf-8";
name="v2-0003-Add-alias-to-be-used-as-unset-state.patch"
^ permalink raw reply [nested|flat] 868+ messages in thread
* [PATCH v2 2/4] Introduce ternary reloptions
@ 2025-09-04 14:41 Nikolay Shaplov <[email protected]>
0 siblings, 0 replies; 868+ messages in thread
From: Nikolay Shaplov @ 2025-09-04 14:41 UTC (permalink / raw)
Introduce ternary reloption as a replacement for current `vacuum_truncate`
implementation. Remove `vacuum_truncate_set` additional flag and using
`TERNARY_UNSET` value instead.
---
src/backend/access/common/reloptions.c | 129 ++++++++++++++++++++-----
src/backend/commands/vacuum.c | 4 +-
src/include/access/reloptions.h | 26 ++---
src/include/c.h | 16 +++
src/include/utils/rel.h | 3 +-
5 files changed, 135 insertions(+), 43 deletions(-)
diff --git a/src/backend/access/common/reloptions.c b/src/backend/access/common/reloptions.c
index 0af3fea68f..f543f11001 100644
--- a/src/backend/access/common/reloptions.c
+++ b/src/backend/access/common/reloptions.c
@@ -40,9 +40,9 @@
*
* To add an option:
*
- * (i) decide on a type (bool, integer, real, enum, string), name, default
- * value, upper and lower bounds (if applicable); for strings, consider a
- * validation routine.
+ * (i) decide on a type (bool, ternary, integer, real, enum, string), name,
+ * default value, upper and lower bounds (if applicable); for strings,
+ * consider a validation routine.
* (ii) add a record below (or use add_<type>_reloption).
* (iii) add it to the appropriate options struct (perhaps StdRdOptions)
* (iv) add it to the appropriate handling routine (perhaps
@@ -147,15 +147,6 @@ static relopt_bool boolRelOpts[] =
},
false
},
- {
- {
- "vacuum_truncate",
- "Enables vacuum to truncate empty pages at the end of this table",
- RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
- ShareUpdateExclusiveLock
- },
- true
- },
{
{
"deduplicate_items",
@@ -170,6 +161,21 @@ static relopt_bool boolRelOpts[] =
{{NULL}}
};
+static relopt_ternary ternaryRelOpts[] =
+{
+ {
+ {
+ "vacuum_truncate",
+ "Enables vacuum to truncate empty pages at the end of this table",
+ RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
+ ShareUpdateExclusiveLock
+ },
+ TERNARY_UNSET
+ },
+ /* list terminator */
+ {{NULL}}
+};
+
static relopt_int intRelOpts[] =
{
{
@@ -600,6 +606,13 @@ initialize_reloptions(void)
boolRelOpts[i].gen.lockmode));
j++;
}
+ for (i = 0; ternaryRelOpts[i].gen.name; i++)
+ {
+ Assert(DoLockModesConflict(ternaryRelOpts[i].gen.lockmode,
+ ternaryRelOpts[i].gen.lockmode));
+ j++;
+ }
+
for (i = 0; intRelOpts[i].gen.name; i++)
{
Assert(DoLockModesConflict(intRelOpts[i].gen.lockmode,
@@ -640,6 +653,14 @@ initialize_reloptions(void)
j++;
}
+ for (i = 0; ternaryRelOpts[i].gen.name; i++)
+ {
+ relOpts[j] = &ternaryRelOpts[i].gen;
+ relOpts[j]->type = RELOPT_TYPE_TERNARY;
+ relOpts[j]->namelen = strlen(relOpts[j]->name);
+ j++;
+ }
+
for (i = 0; intRelOpts[i].gen.name; i++)
{
relOpts[j] = &intRelOpts[i].gen;
@@ -800,6 +821,9 @@ allocate_reloption(bits32 kinds, int type, const char *name, const char *desc,
case RELOPT_TYPE_BOOL:
size = sizeof(relopt_bool);
break;
+ case RELOPT_TYPE_TERNARY:
+ size = sizeof(relopt_ternary);
+ break;
case RELOPT_TYPE_INT:
size = sizeof(relopt_int);
break;
@@ -883,6 +907,54 @@ add_local_bool_reloption(local_relopts *relopts, const char *name,
add_local_reloption(relopts, (relopt_gen *) newoption, offset);
}
+/*
+ * init_ternary_reloption
+ * Allocate and initialize a new ternary reloption
+ */
+static relopt_ternary *
+init_ternary_reloption(bits32 kinds, const char *name, const char *desc,
+ ternary default_val, LOCKMODE lockmode)
+{
+ relopt_ternary *newoption;
+
+ newoption = (relopt_ternary *) allocate_reloption(kinds,
+ RELOPT_TYPE_TERNARY, name, desc, lockmode);
+ newoption->default_val = default_val;
+
+ return newoption;
+}
+
+/*
+ * add_ternary_reloption
+ * Add a new ternary reloption
+ */
+void
+add_ternary_reloption(bits32 kinds, const char *name, const char *desc,
+ ternary default_val, LOCKMODE lockmode)
+{
+ relopt_ternary *newoption = init_ternary_reloption(kinds, name, desc,
+ default_val, lockmode);
+
+ add_reloption((relopt_gen *) newoption);
+}
+
+/*
+ * add_local_ternary_reloption
+ * Add a new ternary local reloption
+ *
+ * 'offset' is offset of ternary-typed field.
+ */
+void
+add_local_ternary_reloption(local_relopts *relopts, const char *name,
+ const char *desc, ternary default_val,
+ int offset)
+{
+ relopt_ternary *newoption = init_ternary_reloption(RELOPT_KIND_LOCAL,
+ name, desc,
+ default_val, 0);
+
+ add_local_reloption(relopts, (relopt_gen *) newoption, offset);
+}
/*
* init_real_reloption
@@ -1617,6 +1689,18 @@ parse_one_reloption(relopt_value *option, char *text_str, int text_len,
option->gen->name, value)));
}
break;
+ case RELOPT_TYPE_TERNARY:
+ {
+ bool b;
+ parsed = parse_bool(value, &b);
+ option->values.ternary_val = b ? TERNARY_TRUE : TERNARY_FALSE;
+ if (validate && !parsed)
+ ereport(ERROR,
+ (errcode(ERRCODE_INVALID_PARAMETER_VALUE),
+ errmsg("invalid value for ternary option \"%s\": %s",
+ option->gen->name, value)));
+ }
+ break;
case RELOPT_TYPE_INT:
{
relopt_int *optint = (relopt_int *) option->gen;
@@ -1780,17 +1864,6 @@ fillRelOptions(void *rdopts, Size basesize,
char *itempos = ((char *) rdopts) + elems[j].offset;
char *string_val;
- /*
- * If isset_offset is provided, store whether the reloption is
- * set there.
- */
- if (elems[j].isset_offset > 0)
- {
- char *setpos = ((char *) rdopts) + elems[j].isset_offset;
-
- *(bool *) setpos = options[i].isset;
- }
-
switch (options[i].gen->type)
{
case RELOPT_TYPE_BOOL:
@@ -1798,6 +1871,11 @@ fillRelOptions(void *rdopts, Size basesize,
options[i].values.bool_val :
((relopt_bool *) options[i].gen)->default_val;
break;
+ case RELOPT_TYPE_TERNARY:
+ *(ternary *) itempos = options[i].isset ?
+ options[i].values.ternary_val :
+ ((relopt_ternary *) options[i].gen)->default_val;
+ break;
case RELOPT_TYPE_INT:
*(int *) itempos = options[i].isset ?
options[i].values.int_val :
@@ -1912,8 +1990,8 @@ default_reloptions(Datum reloptions, bool validate, relopt_kind kind)
offsetof(StdRdOptions, parallel_workers)},
{"vacuum_index_cleanup", RELOPT_TYPE_ENUM,
offsetof(StdRdOptions, vacuum_index_cleanup)},
- {"vacuum_truncate", RELOPT_TYPE_BOOL,
- offsetof(StdRdOptions, vacuum_truncate), offsetof(StdRdOptions, vacuum_truncate_set)},
+ {"vacuum_truncate", RELOPT_TYPE_TERNARY,
+ offsetof(StdRdOptions, vacuum_truncate)},
{"vacuum_max_eager_freeze_failure_rate", RELOPT_TYPE_REAL,
offsetof(StdRdOptions, vacuum_max_eager_freeze_failure_rate)}
};
@@ -1993,7 +2071,6 @@ build_local_reloptions(local_relopts *relopts, Datum options, bool validate)
elems[i].optname = opt->option->name;
elems[i].opttype = opt->option->type;
elems[i].offset = opt->offset;
- elems[i].isset_offset = 0; /* not supported for local relopts yet */
i++;
}
diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c
index 733ef40ae7..977babff54 100644
--- a/src/backend/commands/vacuum.c
+++ b/src/backend/commands/vacuum.c
@@ -2221,9 +2221,9 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams params,
{
StdRdOptions *opts = (StdRdOptions *) rel->rd_options;
- if (opts && opts->vacuum_truncate_set)
+ if (opts && opts->vacuum_truncate != TERNARY_UNSET)
{
- if (opts->vacuum_truncate)
+ if (opts->vacuum_truncate == TERNARY_TRUE)
params.truncate = VACOPTVALUE_ENABLED;
else
params.truncate = VACOPTVALUE_DISABLED;
diff --git a/src/include/access/reloptions.h b/src/include/access/reloptions.h
index a604a4702c..a436697658 100644
--- a/src/include/access/reloptions.h
+++ b/src/include/access/reloptions.h
@@ -29,6 +29,7 @@
typedef enum relopt_type
{
RELOPT_TYPE_BOOL,
+ RELOPT_TYPE_TERNARY, /* on, off, unset */
RELOPT_TYPE_INT,
RELOPT_TYPE_REAL,
RELOPT_TYPE_ENUM,
@@ -80,6 +81,7 @@ typedef struct relopt_value
union
{
bool bool_val;
+ ternary ternary_val;
int int_val;
double real_val;
int enum_val;
@@ -94,6 +96,12 @@ typedef struct relopt_bool
bool default_val;
} relopt_bool;
+typedef struct relopt_rernary
+{
+ relopt_gen gen;
+ int default_val;
+} relopt_ternary;
+
typedef struct relopt_int
{
relopt_gen gen;
@@ -152,19 +160,6 @@ typedef struct
const char *optname; /* option's name */
relopt_type opttype; /* option's datatype */
int offset; /* offset of field in result struct */
-
- /*
- * isset_offset is an optional offset of a field in the result struct that
- * stores whether the option is explicitly set for the relation or if it
- * just picked up the default value. In most cases, this can be
- * accomplished by giving the reloption a special out-of-range default
- * value (e.g., some integer reloptions use -2), but this isn't always
- * possible. For example, a Boolean reloption cannot be given an
- * out-of-range default, so we need another way to discover the source of
- * its value. This offset is only used if given a value greater than
- * zero.
- */
- int isset_offset;
} relopt_parse_elt;
/* Local reloption definition */
@@ -195,6 +190,8 @@ typedef struct local_relopts
extern relopt_kind add_reloption_kind(void);
extern void add_bool_reloption(bits32 kinds, const char *name, const char *desc,
bool default_val, LOCKMODE lockmode);
+extern void add_ternary_reloption(bits32 kinds, const char *name,
+ const char *desc, int default_val, LOCKMODE lockmode);
extern void add_int_reloption(bits32 kinds, const char *name, const char *desc,
int default_val, int min_val, int max_val,
LOCKMODE lockmode);
@@ -214,6 +211,9 @@ extern void register_reloptions_validator(local_relopts *relopts,
extern void add_local_bool_reloption(local_relopts *relopts, const char *name,
const char *desc, bool default_val,
int offset);
+extern void add_local_ternary_reloption(local_relopts *relopts,
+ const char *name, const char *desc,
+ ternary default_val, int offset);
extern void add_local_int_reloption(local_relopts *relopts, const char *name,
const char *desc, int default_val,
int min_val, int max_val, int offset);
diff --git a/src/include/c.h b/src/include/c.h
index 39022f8a9d..9c59ccccf8 100644
--- a/src/include/c.h
+++ b/src/include/c.h
@@ -488,6 +488,22 @@ typedef void (*pg_funcptr_t) (void);
#include <stdbool.h>
+/*
+ * ternary
+ * Boolean value with an extrea "unset" option
+ *
+ * Ternary data type is used in relation options that can be "true", "false" or
+ * "unset". Since relation options are used deep inside the PostgreSQL code,
+ * this type is declared globally.
+*/
+
+typedef enum ternary
+{
+ TERNARY_FALSE = 0,
+ TERNARY_TRUE = 1,
+ TERNARY_UNSET = -1
+} ternary;
+
/* ----------------------------------------------------------------
* Section 3: standard system types
diff --git a/src/include/utils/rel.h b/src/include/utils/rel.h
index b552359915..7346d618f9 100644
--- a/src/include/utils/rel.h
+++ b/src/include/utils/rel.h
@@ -346,8 +346,7 @@ typedef struct StdRdOptions
bool user_catalog_table; /* use as an additional catalog relation */
int parallel_workers; /* max number of parallel workers */
StdRdOptIndexCleanup vacuum_index_cleanup; /* controls index vacuuming */
- bool vacuum_truncate; /* enables vacuum to truncate a relation */
- bool vacuum_truncate_set; /* whether vacuum_truncate is set */
+ ternary vacuum_truncate; /* enables vacuum to truncate a relation */
/*
* Fraction of pages in a relation that vacuum can eagerly scan and fail
--
2.39.2
--nextPart5129932.5fSG56mABF
Content-Disposition: attachment;
filename="v2-0003-Add-alias-to-be-used-as-unset-state.patch"
Content-Transfer-Encoding: 7Bit
Content-Type: text/x-patch; charset="unicode-2-0-utf-8";
name="v2-0003-Add-alias-to-be-used-as-unset-state.patch"
^ permalink raw reply [nested|flat] 868+ messages in thread
* [PATCH v2a 2/4] Introduce ternary reloptions
@ 2025-09-04 14:41 Nikolay Shaplov <[email protected]>
0 siblings, 0 replies; 868+ messages in thread
From: Nikolay Shaplov @ 2025-09-04 14:41 UTC (permalink / raw)
Introduce ternary reloption as a replacement for current `vacuum_truncate`
implementation. Remove `vacuum_truncate_set` additional flag and using
`TERNARY_UNSET` value instead.
---
src/backend/access/common/reloptions.c | 129 ++++++++++++++++++++-----
src/backend/commands/vacuum.c | 4 +-
src/include/access/reloptions.h | 26 ++---
src/include/c.h | 16 +++
src/include/utils/rel.h | 3 +-
5 files changed, 135 insertions(+), 43 deletions(-)
diff --git a/src/backend/access/common/reloptions.c b/src/backend/access/common/reloptions.c
index 0af3fea68f..f543f11001 100644
--- a/src/backend/access/common/reloptions.c
+++ b/src/backend/access/common/reloptions.c
@@ -40,9 +40,9 @@
*
* To add an option:
*
- * (i) decide on a type (bool, integer, real, enum, string), name, default
- * value, upper and lower bounds (if applicable); for strings, consider a
- * validation routine.
+ * (i) decide on a type (bool, ternary, integer, real, enum, string), name,
+ * default value, upper and lower bounds (if applicable); for strings,
+ * consider a validation routine.
* (ii) add a record below (or use add_<type>_reloption).
* (iii) add it to the appropriate options struct (perhaps StdRdOptions)
* (iv) add it to the appropriate handling routine (perhaps
@@ -147,15 +147,6 @@ static relopt_bool boolRelOpts[] =
},
false
},
- {
- {
- "vacuum_truncate",
- "Enables vacuum to truncate empty pages at the end of this table",
- RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
- ShareUpdateExclusiveLock
- },
- true
- },
{
{
"deduplicate_items",
@@ -170,6 +161,21 @@ static relopt_bool boolRelOpts[] =
{{NULL}}
};
+static relopt_ternary ternaryRelOpts[] =
+{
+ {
+ {
+ "vacuum_truncate",
+ "Enables vacuum to truncate empty pages at the end of this table",
+ RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
+ ShareUpdateExclusiveLock
+ },
+ TERNARY_UNSET
+ },
+ /* list terminator */
+ {{NULL}}
+};
+
static relopt_int intRelOpts[] =
{
{
@@ -600,6 +606,13 @@ initialize_reloptions(void)
boolRelOpts[i].gen.lockmode));
j++;
}
+ for (i = 0; ternaryRelOpts[i].gen.name; i++)
+ {
+ Assert(DoLockModesConflict(ternaryRelOpts[i].gen.lockmode,
+ ternaryRelOpts[i].gen.lockmode));
+ j++;
+ }
+
for (i = 0; intRelOpts[i].gen.name; i++)
{
Assert(DoLockModesConflict(intRelOpts[i].gen.lockmode,
@@ -640,6 +653,14 @@ initialize_reloptions(void)
j++;
}
+ for (i = 0; ternaryRelOpts[i].gen.name; i++)
+ {
+ relOpts[j] = &ternaryRelOpts[i].gen;
+ relOpts[j]->type = RELOPT_TYPE_TERNARY;
+ relOpts[j]->namelen = strlen(relOpts[j]->name);
+ j++;
+ }
+
for (i = 0; intRelOpts[i].gen.name; i++)
{
relOpts[j] = &intRelOpts[i].gen;
@@ -800,6 +821,9 @@ allocate_reloption(bits32 kinds, int type, const char *name, const char *desc,
case RELOPT_TYPE_BOOL:
size = sizeof(relopt_bool);
break;
+ case RELOPT_TYPE_TERNARY:
+ size = sizeof(relopt_ternary);
+ break;
case RELOPT_TYPE_INT:
size = sizeof(relopt_int);
break;
@@ -883,6 +907,54 @@ add_local_bool_reloption(local_relopts *relopts, const char *name,
add_local_reloption(relopts, (relopt_gen *) newoption, offset);
}
+/*
+ * init_ternary_reloption
+ * Allocate and initialize a new ternary reloption
+ */
+static relopt_ternary *
+init_ternary_reloption(bits32 kinds, const char *name, const char *desc,
+ ternary default_val, LOCKMODE lockmode)
+{
+ relopt_ternary *newoption;
+
+ newoption = (relopt_ternary *) allocate_reloption(kinds,
+ RELOPT_TYPE_TERNARY, name, desc, lockmode);
+ newoption->default_val = default_val;
+
+ return newoption;
+}
+
+/*
+ * add_ternary_reloption
+ * Add a new ternary reloption
+ */
+void
+add_ternary_reloption(bits32 kinds, const char *name, const char *desc,
+ ternary default_val, LOCKMODE lockmode)
+{
+ relopt_ternary *newoption = init_ternary_reloption(kinds, name, desc,
+ default_val, lockmode);
+
+ add_reloption((relopt_gen *) newoption);
+}
+
+/*
+ * add_local_ternary_reloption
+ * Add a new ternary local reloption
+ *
+ * 'offset' is offset of ternary-typed field.
+ */
+void
+add_local_ternary_reloption(local_relopts *relopts, const char *name,
+ const char *desc, ternary default_val,
+ int offset)
+{
+ relopt_ternary *newoption = init_ternary_reloption(RELOPT_KIND_LOCAL,
+ name, desc,
+ default_val, 0);
+
+ add_local_reloption(relopts, (relopt_gen *) newoption, offset);
+}
/*
* init_real_reloption
@@ -1617,6 +1689,18 @@ parse_one_reloption(relopt_value *option, char *text_str, int text_len,
option->gen->name, value)));
}
break;
+ case RELOPT_TYPE_TERNARY:
+ {
+ bool b;
+ parsed = parse_bool(value, &b);
+ option->values.ternary_val = b ? TERNARY_TRUE : TERNARY_FALSE;
+ if (validate && !parsed)
+ ereport(ERROR,
+ (errcode(ERRCODE_INVALID_PARAMETER_VALUE),
+ errmsg("invalid value for ternary option \"%s\": %s",
+ option->gen->name, value)));
+ }
+ break;
case RELOPT_TYPE_INT:
{
relopt_int *optint = (relopt_int *) option->gen;
@@ -1780,17 +1864,6 @@ fillRelOptions(void *rdopts, Size basesize,
char *itempos = ((char *) rdopts) + elems[j].offset;
char *string_val;
- /*
- * If isset_offset is provided, store whether the reloption is
- * set there.
- */
- if (elems[j].isset_offset > 0)
- {
- char *setpos = ((char *) rdopts) + elems[j].isset_offset;
-
- *(bool *) setpos = options[i].isset;
- }
-
switch (options[i].gen->type)
{
case RELOPT_TYPE_BOOL:
@@ -1798,6 +1871,11 @@ fillRelOptions(void *rdopts, Size basesize,
options[i].values.bool_val :
((relopt_bool *) options[i].gen)->default_val;
break;
+ case RELOPT_TYPE_TERNARY:
+ *(ternary *) itempos = options[i].isset ?
+ options[i].values.ternary_val :
+ ((relopt_ternary *) options[i].gen)->default_val;
+ break;
case RELOPT_TYPE_INT:
*(int *) itempos = options[i].isset ?
options[i].values.int_val :
@@ -1912,8 +1990,8 @@ default_reloptions(Datum reloptions, bool validate, relopt_kind kind)
offsetof(StdRdOptions, parallel_workers)},
{"vacuum_index_cleanup", RELOPT_TYPE_ENUM,
offsetof(StdRdOptions, vacuum_index_cleanup)},
- {"vacuum_truncate", RELOPT_TYPE_BOOL,
- offsetof(StdRdOptions, vacuum_truncate), offsetof(StdRdOptions, vacuum_truncate_set)},
+ {"vacuum_truncate", RELOPT_TYPE_TERNARY,
+ offsetof(StdRdOptions, vacuum_truncate)},
{"vacuum_max_eager_freeze_failure_rate", RELOPT_TYPE_REAL,
offsetof(StdRdOptions, vacuum_max_eager_freeze_failure_rate)}
};
@@ -1993,7 +2071,6 @@ build_local_reloptions(local_relopts *relopts, Datum options, bool validate)
elems[i].optname = opt->option->name;
elems[i].opttype = opt->option->type;
elems[i].offset = opt->offset;
- elems[i].isset_offset = 0; /* not supported for local relopts yet */
i++;
}
diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c
index 733ef40ae7..977babff54 100644
--- a/src/backend/commands/vacuum.c
+++ b/src/backend/commands/vacuum.c
@@ -2221,9 +2221,9 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams params,
{
StdRdOptions *opts = (StdRdOptions *) rel->rd_options;
- if (opts && opts->vacuum_truncate_set)
+ if (opts && opts->vacuum_truncate != TERNARY_UNSET)
{
- if (opts->vacuum_truncate)
+ if (opts->vacuum_truncate == TERNARY_TRUE)
params.truncate = VACOPTVALUE_ENABLED;
else
params.truncate = VACOPTVALUE_DISABLED;
diff --git a/src/include/access/reloptions.h b/src/include/access/reloptions.h
index a604a4702c..a436697658 100644
--- a/src/include/access/reloptions.h
+++ b/src/include/access/reloptions.h
@@ -29,6 +29,7 @@
typedef enum relopt_type
{
RELOPT_TYPE_BOOL,
+ RELOPT_TYPE_TERNARY, /* on, off, unset */
RELOPT_TYPE_INT,
RELOPT_TYPE_REAL,
RELOPT_TYPE_ENUM,
@@ -80,6 +81,7 @@ typedef struct relopt_value
union
{
bool bool_val;
+ ternary ternary_val;
int int_val;
double real_val;
int enum_val;
@@ -94,6 +96,12 @@ typedef struct relopt_bool
bool default_val;
} relopt_bool;
+typedef struct relopt_rernary
+{
+ relopt_gen gen;
+ int default_val;
+} relopt_ternary;
+
typedef struct relopt_int
{
relopt_gen gen;
@@ -152,19 +160,6 @@ typedef struct
const char *optname; /* option's name */
relopt_type opttype; /* option's datatype */
int offset; /* offset of field in result struct */
-
- /*
- * isset_offset is an optional offset of a field in the result struct that
- * stores whether the option is explicitly set for the relation or if it
- * just picked up the default value. In most cases, this can be
- * accomplished by giving the reloption a special out-of-range default
- * value (e.g., some integer reloptions use -2), but this isn't always
- * possible. For example, a Boolean reloption cannot be given an
- * out-of-range default, so we need another way to discover the source of
- * its value. This offset is only used if given a value greater than
- * zero.
- */
- int isset_offset;
} relopt_parse_elt;
/* Local reloption definition */
@@ -195,6 +190,8 @@ typedef struct local_relopts
extern relopt_kind add_reloption_kind(void);
extern void add_bool_reloption(bits32 kinds, const char *name, const char *desc,
bool default_val, LOCKMODE lockmode);
+extern void add_ternary_reloption(bits32 kinds, const char *name,
+ const char *desc, int default_val, LOCKMODE lockmode);
extern void add_int_reloption(bits32 kinds, const char *name, const char *desc,
int default_val, int min_val, int max_val,
LOCKMODE lockmode);
@@ -214,6 +211,9 @@ extern void register_reloptions_validator(local_relopts *relopts,
extern void add_local_bool_reloption(local_relopts *relopts, const char *name,
const char *desc, bool default_val,
int offset);
+extern void add_local_ternary_reloption(local_relopts *relopts,
+ const char *name, const char *desc,
+ ternary default_val, int offset);
extern void add_local_int_reloption(local_relopts *relopts, const char *name,
const char *desc, int default_val,
int min_val, int max_val, int offset);
diff --git a/src/include/c.h b/src/include/c.h
index 39022f8a9d..9c59ccccf8 100644
--- a/src/include/c.h
+++ b/src/include/c.h
@@ -488,6 +488,22 @@ typedef void (*pg_funcptr_t) (void);
#include <stdbool.h>
+/*
+ * ternary
+ * Boolean value with an extrea "unset" option
+ *
+ * Ternary data type is used in relation options that can be "true", "false" or
+ * "unset". Since relation options are used deep inside the PostgreSQL code,
+ * this type is declared globally.
+*/
+
+typedef enum ternary
+{
+ TERNARY_FALSE = 0,
+ TERNARY_TRUE = 1,
+ TERNARY_UNSET = -1
+} ternary;
+
/* ----------------------------------------------------------------
* Section 3: standard system types
diff --git a/src/include/utils/rel.h b/src/include/utils/rel.h
index b552359915..7346d618f9 100644
--- a/src/include/utils/rel.h
+++ b/src/include/utils/rel.h
@@ -346,8 +346,7 @@ typedef struct StdRdOptions
bool user_catalog_table; /* use as an additional catalog relation */
int parallel_workers; /* max number of parallel workers */
StdRdOptIndexCleanup vacuum_index_cleanup; /* controls index vacuuming */
- bool vacuum_truncate; /* enables vacuum to truncate a relation */
- bool vacuum_truncate_set; /* whether vacuum_truncate is set */
+ ternary vacuum_truncate; /* enables vacuum to truncate a relation */
/*
* Fraction of pages in a relation that vacuum can eagerly scan and fail
--
2.39.2
--nextPart4155600.3ZeAukHxDK
Content-Disposition: attachment;
filename="v2a-0003-Add-alias-to-be-used-as-unset-state.patch"
Content-Transfer-Encoding: 7Bit
Content-Type: text/x-patch; charset="unicode-2-0-utf-8";
name="v2a-0003-Add-alias-to-be-used-as-unset-state.patch"
^ permalink raw reply [nested|flat] 868+ messages in thread
* [PATCH v2a 2/4] Introduce ternary reloptions
@ 2025-09-04 14:41 Nikolay Shaplov <[email protected]>
0 siblings, 0 replies; 868+ messages in thread
From: Nikolay Shaplov @ 2025-09-04 14:41 UTC (permalink / raw)
Introduce ternary reloption as a replacement for current `vacuum_truncate`
implementation. Remove `vacuum_truncate_set` additional flag and using
`TERNARY_UNSET` value instead.
---
src/backend/access/common/reloptions.c | 129 ++++++++++++++++++++-----
src/backend/commands/vacuum.c | 4 +-
src/include/access/reloptions.h | 26 ++---
src/include/c.h | 16 +++
src/include/utils/rel.h | 3 +-
5 files changed, 135 insertions(+), 43 deletions(-)
diff --git a/src/backend/access/common/reloptions.c b/src/backend/access/common/reloptions.c
index 0af3fea68f..f543f11001 100644
--- a/src/backend/access/common/reloptions.c
+++ b/src/backend/access/common/reloptions.c
@@ -40,9 +40,9 @@
*
* To add an option:
*
- * (i) decide on a type (bool, integer, real, enum, string), name, default
- * value, upper and lower bounds (if applicable); for strings, consider a
- * validation routine.
+ * (i) decide on a type (bool, ternary, integer, real, enum, string), name,
+ * default value, upper and lower bounds (if applicable); for strings,
+ * consider a validation routine.
* (ii) add a record below (or use add_<type>_reloption).
* (iii) add it to the appropriate options struct (perhaps StdRdOptions)
* (iv) add it to the appropriate handling routine (perhaps
@@ -147,15 +147,6 @@ static relopt_bool boolRelOpts[] =
},
false
},
- {
- {
- "vacuum_truncate",
- "Enables vacuum to truncate empty pages at the end of this table",
- RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
- ShareUpdateExclusiveLock
- },
- true
- },
{
{
"deduplicate_items",
@@ -170,6 +161,21 @@ static relopt_bool boolRelOpts[] =
{{NULL}}
};
+static relopt_ternary ternaryRelOpts[] =
+{
+ {
+ {
+ "vacuum_truncate",
+ "Enables vacuum to truncate empty pages at the end of this table",
+ RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
+ ShareUpdateExclusiveLock
+ },
+ TERNARY_UNSET
+ },
+ /* list terminator */
+ {{NULL}}
+};
+
static relopt_int intRelOpts[] =
{
{
@@ -600,6 +606,13 @@ initialize_reloptions(void)
boolRelOpts[i].gen.lockmode));
j++;
}
+ for (i = 0; ternaryRelOpts[i].gen.name; i++)
+ {
+ Assert(DoLockModesConflict(ternaryRelOpts[i].gen.lockmode,
+ ternaryRelOpts[i].gen.lockmode));
+ j++;
+ }
+
for (i = 0; intRelOpts[i].gen.name; i++)
{
Assert(DoLockModesConflict(intRelOpts[i].gen.lockmode,
@@ -640,6 +653,14 @@ initialize_reloptions(void)
j++;
}
+ for (i = 0; ternaryRelOpts[i].gen.name; i++)
+ {
+ relOpts[j] = &ternaryRelOpts[i].gen;
+ relOpts[j]->type = RELOPT_TYPE_TERNARY;
+ relOpts[j]->namelen = strlen(relOpts[j]->name);
+ j++;
+ }
+
for (i = 0; intRelOpts[i].gen.name; i++)
{
relOpts[j] = &intRelOpts[i].gen;
@@ -800,6 +821,9 @@ allocate_reloption(bits32 kinds, int type, const char *name, const char *desc,
case RELOPT_TYPE_BOOL:
size = sizeof(relopt_bool);
break;
+ case RELOPT_TYPE_TERNARY:
+ size = sizeof(relopt_ternary);
+ break;
case RELOPT_TYPE_INT:
size = sizeof(relopt_int);
break;
@@ -883,6 +907,54 @@ add_local_bool_reloption(local_relopts *relopts, const char *name,
add_local_reloption(relopts, (relopt_gen *) newoption, offset);
}
+/*
+ * init_ternary_reloption
+ * Allocate and initialize a new ternary reloption
+ */
+static relopt_ternary *
+init_ternary_reloption(bits32 kinds, const char *name, const char *desc,
+ ternary default_val, LOCKMODE lockmode)
+{
+ relopt_ternary *newoption;
+
+ newoption = (relopt_ternary *) allocate_reloption(kinds,
+ RELOPT_TYPE_TERNARY, name, desc, lockmode);
+ newoption->default_val = default_val;
+
+ return newoption;
+}
+
+/*
+ * add_ternary_reloption
+ * Add a new ternary reloption
+ */
+void
+add_ternary_reloption(bits32 kinds, const char *name, const char *desc,
+ ternary default_val, LOCKMODE lockmode)
+{
+ relopt_ternary *newoption = init_ternary_reloption(kinds, name, desc,
+ default_val, lockmode);
+
+ add_reloption((relopt_gen *) newoption);
+}
+
+/*
+ * add_local_ternary_reloption
+ * Add a new ternary local reloption
+ *
+ * 'offset' is offset of ternary-typed field.
+ */
+void
+add_local_ternary_reloption(local_relopts *relopts, const char *name,
+ const char *desc, ternary default_val,
+ int offset)
+{
+ relopt_ternary *newoption = init_ternary_reloption(RELOPT_KIND_LOCAL,
+ name, desc,
+ default_val, 0);
+
+ add_local_reloption(relopts, (relopt_gen *) newoption, offset);
+}
/*
* init_real_reloption
@@ -1617,6 +1689,18 @@ parse_one_reloption(relopt_value *option, char *text_str, int text_len,
option->gen->name, value)));
}
break;
+ case RELOPT_TYPE_TERNARY:
+ {
+ bool b;
+ parsed = parse_bool(value, &b);
+ option->values.ternary_val = b ? TERNARY_TRUE : TERNARY_FALSE;
+ if (validate && !parsed)
+ ereport(ERROR,
+ (errcode(ERRCODE_INVALID_PARAMETER_VALUE),
+ errmsg("invalid value for ternary option \"%s\": %s",
+ option->gen->name, value)));
+ }
+ break;
case RELOPT_TYPE_INT:
{
relopt_int *optint = (relopt_int *) option->gen;
@@ -1780,17 +1864,6 @@ fillRelOptions(void *rdopts, Size basesize,
char *itempos = ((char *) rdopts) + elems[j].offset;
char *string_val;
- /*
- * If isset_offset is provided, store whether the reloption is
- * set there.
- */
- if (elems[j].isset_offset > 0)
- {
- char *setpos = ((char *) rdopts) + elems[j].isset_offset;
-
- *(bool *) setpos = options[i].isset;
- }
-
switch (options[i].gen->type)
{
case RELOPT_TYPE_BOOL:
@@ -1798,6 +1871,11 @@ fillRelOptions(void *rdopts, Size basesize,
options[i].values.bool_val :
((relopt_bool *) options[i].gen)->default_val;
break;
+ case RELOPT_TYPE_TERNARY:
+ *(ternary *) itempos = options[i].isset ?
+ options[i].values.ternary_val :
+ ((relopt_ternary *) options[i].gen)->default_val;
+ break;
case RELOPT_TYPE_INT:
*(int *) itempos = options[i].isset ?
options[i].values.int_val :
@@ -1912,8 +1990,8 @@ default_reloptions(Datum reloptions, bool validate, relopt_kind kind)
offsetof(StdRdOptions, parallel_workers)},
{"vacuum_index_cleanup", RELOPT_TYPE_ENUM,
offsetof(StdRdOptions, vacuum_index_cleanup)},
- {"vacuum_truncate", RELOPT_TYPE_BOOL,
- offsetof(StdRdOptions, vacuum_truncate), offsetof(StdRdOptions, vacuum_truncate_set)},
+ {"vacuum_truncate", RELOPT_TYPE_TERNARY,
+ offsetof(StdRdOptions, vacuum_truncate)},
{"vacuum_max_eager_freeze_failure_rate", RELOPT_TYPE_REAL,
offsetof(StdRdOptions, vacuum_max_eager_freeze_failure_rate)}
};
@@ -1993,7 +2071,6 @@ build_local_reloptions(local_relopts *relopts, Datum options, bool validate)
elems[i].optname = opt->option->name;
elems[i].opttype = opt->option->type;
elems[i].offset = opt->offset;
- elems[i].isset_offset = 0; /* not supported for local relopts yet */
i++;
}
diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c
index 733ef40ae7..977babff54 100644
--- a/src/backend/commands/vacuum.c
+++ b/src/backend/commands/vacuum.c
@@ -2221,9 +2221,9 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams params,
{
StdRdOptions *opts = (StdRdOptions *) rel->rd_options;
- if (opts && opts->vacuum_truncate_set)
+ if (opts && opts->vacuum_truncate != TERNARY_UNSET)
{
- if (opts->vacuum_truncate)
+ if (opts->vacuum_truncate == TERNARY_TRUE)
params.truncate = VACOPTVALUE_ENABLED;
else
params.truncate = VACOPTVALUE_DISABLED;
diff --git a/src/include/access/reloptions.h b/src/include/access/reloptions.h
index a604a4702c..a436697658 100644
--- a/src/include/access/reloptions.h
+++ b/src/include/access/reloptions.h
@@ -29,6 +29,7 @@
typedef enum relopt_type
{
RELOPT_TYPE_BOOL,
+ RELOPT_TYPE_TERNARY, /* on, off, unset */
RELOPT_TYPE_INT,
RELOPT_TYPE_REAL,
RELOPT_TYPE_ENUM,
@@ -80,6 +81,7 @@ typedef struct relopt_value
union
{
bool bool_val;
+ ternary ternary_val;
int int_val;
double real_val;
int enum_val;
@@ -94,6 +96,12 @@ typedef struct relopt_bool
bool default_val;
} relopt_bool;
+typedef struct relopt_rernary
+{
+ relopt_gen gen;
+ int default_val;
+} relopt_ternary;
+
typedef struct relopt_int
{
relopt_gen gen;
@@ -152,19 +160,6 @@ typedef struct
const char *optname; /* option's name */
relopt_type opttype; /* option's datatype */
int offset; /* offset of field in result struct */
-
- /*
- * isset_offset is an optional offset of a field in the result struct that
- * stores whether the option is explicitly set for the relation or if it
- * just picked up the default value. In most cases, this can be
- * accomplished by giving the reloption a special out-of-range default
- * value (e.g., some integer reloptions use -2), but this isn't always
- * possible. For example, a Boolean reloption cannot be given an
- * out-of-range default, so we need another way to discover the source of
- * its value. This offset is only used if given a value greater than
- * zero.
- */
- int isset_offset;
} relopt_parse_elt;
/* Local reloption definition */
@@ -195,6 +190,8 @@ typedef struct local_relopts
extern relopt_kind add_reloption_kind(void);
extern void add_bool_reloption(bits32 kinds, const char *name, const char *desc,
bool default_val, LOCKMODE lockmode);
+extern void add_ternary_reloption(bits32 kinds, const char *name,
+ const char *desc, int default_val, LOCKMODE lockmode);
extern void add_int_reloption(bits32 kinds, const char *name, const char *desc,
int default_val, int min_val, int max_val,
LOCKMODE lockmode);
@@ -214,6 +211,9 @@ extern void register_reloptions_validator(local_relopts *relopts,
extern void add_local_bool_reloption(local_relopts *relopts, const char *name,
const char *desc, bool default_val,
int offset);
+extern void add_local_ternary_reloption(local_relopts *relopts,
+ const char *name, const char *desc,
+ ternary default_val, int offset);
extern void add_local_int_reloption(local_relopts *relopts, const char *name,
const char *desc, int default_val,
int min_val, int max_val, int offset);
diff --git a/src/include/c.h b/src/include/c.h
index 39022f8a9d..9c59ccccf8 100644
--- a/src/include/c.h
+++ b/src/include/c.h
@@ -488,6 +488,22 @@ typedef void (*pg_funcptr_t) (void);
#include <stdbool.h>
+/*
+ * ternary
+ * Boolean value with an extrea "unset" option
+ *
+ * Ternary data type is used in relation options that can be "true", "false" or
+ * "unset". Since relation options are used deep inside the PostgreSQL code,
+ * this type is declared globally.
+*/
+
+typedef enum ternary
+{
+ TERNARY_FALSE = 0,
+ TERNARY_TRUE = 1,
+ TERNARY_UNSET = -1
+} ternary;
+
/* ----------------------------------------------------------------
* Section 3: standard system types
diff --git a/src/include/utils/rel.h b/src/include/utils/rel.h
index b552359915..7346d618f9 100644
--- a/src/include/utils/rel.h
+++ b/src/include/utils/rel.h
@@ -346,8 +346,7 @@ typedef struct StdRdOptions
bool user_catalog_table; /* use as an additional catalog relation */
int parallel_workers; /* max number of parallel workers */
StdRdOptIndexCleanup vacuum_index_cleanup; /* controls index vacuuming */
- bool vacuum_truncate; /* enables vacuum to truncate a relation */
- bool vacuum_truncate_set; /* whether vacuum_truncate is set */
+ ternary vacuum_truncate; /* enables vacuum to truncate a relation */
/*
* Fraction of pages in a relation that vacuum can eagerly scan and fail
--
2.39.2
--nextPart4155600.3ZeAukHxDK
Content-Disposition: attachment;
filename="v2a-0003-Add-alias-to-be-used-as-unset-state.patch"
Content-Transfer-Encoding: 7Bit
Content-Type: text/x-patch; charset="unicode-2-0-utf-8";
name="v2a-0003-Add-alias-to-be-used-as-unset-state.patch"
^ permalink raw reply [nested|flat] 868+ messages in thread
* [PATCH v2a 2/4] Introduce ternary reloptions
@ 2025-09-04 14:41 Nikolay Shaplov <[email protected]>
0 siblings, 0 replies; 868+ messages in thread
From: Nikolay Shaplov @ 2025-09-04 14:41 UTC (permalink / raw)
Introduce ternary reloption as a replacement for current `vacuum_truncate`
implementation. Remove `vacuum_truncate_set` additional flag and using
`TERNARY_UNSET` value instead.
---
src/backend/access/common/reloptions.c | 129 ++++++++++++++++++++-----
src/backend/commands/vacuum.c | 4 +-
src/include/access/reloptions.h | 26 ++---
src/include/c.h | 16 +++
src/include/utils/rel.h | 3 +-
5 files changed, 135 insertions(+), 43 deletions(-)
diff --git a/src/backend/access/common/reloptions.c b/src/backend/access/common/reloptions.c
index 0af3fea68f..f543f11001 100644
--- a/src/backend/access/common/reloptions.c
+++ b/src/backend/access/common/reloptions.c
@@ -40,9 +40,9 @@
*
* To add an option:
*
- * (i) decide on a type (bool, integer, real, enum, string), name, default
- * value, upper and lower bounds (if applicable); for strings, consider a
- * validation routine.
+ * (i) decide on a type (bool, ternary, integer, real, enum, string), name,
+ * default value, upper and lower bounds (if applicable); for strings,
+ * consider a validation routine.
* (ii) add a record below (or use add_<type>_reloption).
* (iii) add it to the appropriate options struct (perhaps StdRdOptions)
* (iv) add it to the appropriate handling routine (perhaps
@@ -147,15 +147,6 @@ static relopt_bool boolRelOpts[] =
},
false
},
- {
- {
- "vacuum_truncate",
- "Enables vacuum to truncate empty pages at the end of this table",
- RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
- ShareUpdateExclusiveLock
- },
- true
- },
{
{
"deduplicate_items",
@@ -170,6 +161,21 @@ static relopt_bool boolRelOpts[] =
{{NULL}}
};
+static relopt_ternary ternaryRelOpts[] =
+{
+ {
+ {
+ "vacuum_truncate",
+ "Enables vacuum to truncate empty pages at the end of this table",
+ RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
+ ShareUpdateExclusiveLock
+ },
+ TERNARY_UNSET
+ },
+ /* list terminator */
+ {{NULL}}
+};
+
static relopt_int intRelOpts[] =
{
{
@@ -600,6 +606,13 @@ initialize_reloptions(void)
boolRelOpts[i].gen.lockmode));
j++;
}
+ for (i = 0; ternaryRelOpts[i].gen.name; i++)
+ {
+ Assert(DoLockModesConflict(ternaryRelOpts[i].gen.lockmode,
+ ternaryRelOpts[i].gen.lockmode));
+ j++;
+ }
+
for (i = 0; intRelOpts[i].gen.name; i++)
{
Assert(DoLockModesConflict(intRelOpts[i].gen.lockmode,
@@ -640,6 +653,14 @@ initialize_reloptions(void)
j++;
}
+ for (i = 0; ternaryRelOpts[i].gen.name; i++)
+ {
+ relOpts[j] = &ternaryRelOpts[i].gen;
+ relOpts[j]->type = RELOPT_TYPE_TERNARY;
+ relOpts[j]->namelen = strlen(relOpts[j]->name);
+ j++;
+ }
+
for (i = 0; intRelOpts[i].gen.name; i++)
{
relOpts[j] = &intRelOpts[i].gen;
@@ -800,6 +821,9 @@ allocate_reloption(bits32 kinds, int type, const char *name, const char *desc,
case RELOPT_TYPE_BOOL:
size = sizeof(relopt_bool);
break;
+ case RELOPT_TYPE_TERNARY:
+ size = sizeof(relopt_ternary);
+ break;
case RELOPT_TYPE_INT:
size = sizeof(relopt_int);
break;
@@ -883,6 +907,54 @@ add_local_bool_reloption(local_relopts *relopts, const char *name,
add_local_reloption(relopts, (relopt_gen *) newoption, offset);
}
+/*
+ * init_ternary_reloption
+ * Allocate and initialize a new ternary reloption
+ */
+static relopt_ternary *
+init_ternary_reloption(bits32 kinds, const char *name, const char *desc,
+ ternary default_val, LOCKMODE lockmode)
+{
+ relopt_ternary *newoption;
+
+ newoption = (relopt_ternary *) allocate_reloption(kinds,
+ RELOPT_TYPE_TERNARY, name, desc, lockmode);
+ newoption->default_val = default_val;
+
+ return newoption;
+}
+
+/*
+ * add_ternary_reloption
+ * Add a new ternary reloption
+ */
+void
+add_ternary_reloption(bits32 kinds, const char *name, const char *desc,
+ ternary default_val, LOCKMODE lockmode)
+{
+ relopt_ternary *newoption = init_ternary_reloption(kinds, name, desc,
+ default_val, lockmode);
+
+ add_reloption((relopt_gen *) newoption);
+}
+
+/*
+ * add_local_ternary_reloption
+ * Add a new ternary local reloption
+ *
+ * 'offset' is offset of ternary-typed field.
+ */
+void
+add_local_ternary_reloption(local_relopts *relopts, const char *name,
+ const char *desc, ternary default_val,
+ int offset)
+{
+ relopt_ternary *newoption = init_ternary_reloption(RELOPT_KIND_LOCAL,
+ name, desc,
+ default_val, 0);
+
+ add_local_reloption(relopts, (relopt_gen *) newoption, offset);
+}
/*
* init_real_reloption
@@ -1617,6 +1689,18 @@ parse_one_reloption(relopt_value *option, char *text_str, int text_len,
option->gen->name, value)));
}
break;
+ case RELOPT_TYPE_TERNARY:
+ {
+ bool b;
+ parsed = parse_bool(value, &b);
+ option->values.ternary_val = b ? TERNARY_TRUE : TERNARY_FALSE;
+ if (validate && !parsed)
+ ereport(ERROR,
+ (errcode(ERRCODE_INVALID_PARAMETER_VALUE),
+ errmsg("invalid value for ternary option \"%s\": %s",
+ option->gen->name, value)));
+ }
+ break;
case RELOPT_TYPE_INT:
{
relopt_int *optint = (relopt_int *) option->gen;
@@ -1780,17 +1864,6 @@ fillRelOptions(void *rdopts, Size basesize,
char *itempos = ((char *) rdopts) + elems[j].offset;
char *string_val;
- /*
- * If isset_offset is provided, store whether the reloption is
- * set there.
- */
- if (elems[j].isset_offset > 0)
- {
- char *setpos = ((char *) rdopts) + elems[j].isset_offset;
-
- *(bool *) setpos = options[i].isset;
- }
-
switch (options[i].gen->type)
{
case RELOPT_TYPE_BOOL:
@@ -1798,6 +1871,11 @@ fillRelOptions(void *rdopts, Size basesize,
options[i].values.bool_val :
((relopt_bool *) options[i].gen)->default_val;
break;
+ case RELOPT_TYPE_TERNARY:
+ *(ternary *) itempos = options[i].isset ?
+ options[i].values.ternary_val :
+ ((relopt_ternary *) options[i].gen)->default_val;
+ break;
case RELOPT_TYPE_INT:
*(int *) itempos = options[i].isset ?
options[i].values.int_val :
@@ -1912,8 +1990,8 @@ default_reloptions(Datum reloptions, bool validate, relopt_kind kind)
offsetof(StdRdOptions, parallel_workers)},
{"vacuum_index_cleanup", RELOPT_TYPE_ENUM,
offsetof(StdRdOptions, vacuum_index_cleanup)},
- {"vacuum_truncate", RELOPT_TYPE_BOOL,
- offsetof(StdRdOptions, vacuum_truncate), offsetof(StdRdOptions, vacuum_truncate_set)},
+ {"vacuum_truncate", RELOPT_TYPE_TERNARY,
+ offsetof(StdRdOptions, vacuum_truncate)},
{"vacuum_max_eager_freeze_failure_rate", RELOPT_TYPE_REAL,
offsetof(StdRdOptions, vacuum_max_eager_freeze_failure_rate)}
};
@@ -1993,7 +2071,6 @@ build_local_reloptions(local_relopts *relopts, Datum options, bool validate)
elems[i].optname = opt->option->name;
elems[i].opttype = opt->option->type;
elems[i].offset = opt->offset;
- elems[i].isset_offset = 0; /* not supported for local relopts yet */
i++;
}
diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c
index 733ef40ae7..977babff54 100644
--- a/src/backend/commands/vacuum.c
+++ b/src/backend/commands/vacuum.c
@@ -2221,9 +2221,9 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams params,
{
StdRdOptions *opts = (StdRdOptions *) rel->rd_options;
- if (opts && opts->vacuum_truncate_set)
+ if (opts && opts->vacuum_truncate != TERNARY_UNSET)
{
- if (opts->vacuum_truncate)
+ if (opts->vacuum_truncate == TERNARY_TRUE)
params.truncate = VACOPTVALUE_ENABLED;
else
params.truncate = VACOPTVALUE_DISABLED;
diff --git a/src/include/access/reloptions.h b/src/include/access/reloptions.h
index a604a4702c..a436697658 100644
--- a/src/include/access/reloptions.h
+++ b/src/include/access/reloptions.h
@@ -29,6 +29,7 @@
typedef enum relopt_type
{
RELOPT_TYPE_BOOL,
+ RELOPT_TYPE_TERNARY, /* on, off, unset */
RELOPT_TYPE_INT,
RELOPT_TYPE_REAL,
RELOPT_TYPE_ENUM,
@@ -80,6 +81,7 @@ typedef struct relopt_value
union
{
bool bool_val;
+ ternary ternary_val;
int int_val;
double real_val;
int enum_val;
@@ -94,6 +96,12 @@ typedef struct relopt_bool
bool default_val;
} relopt_bool;
+typedef struct relopt_rernary
+{
+ relopt_gen gen;
+ int default_val;
+} relopt_ternary;
+
typedef struct relopt_int
{
relopt_gen gen;
@@ -152,19 +160,6 @@ typedef struct
const char *optname; /* option's name */
relopt_type opttype; /* option's datatype */
int offset; /* offset of field in result struct */
-
- /*
- * isset_offset is an optional offset of a field in the result struct that
- * stores whether the option is explicitly set for the relation or if it
- * just picked up the default value. In most cases, this can be
- * accomplished by giving the reloption a special out-of-range default
- * value (e.g., some integer reloptions use -2), but this isn't always
- * possible. For example, a Boolean reloption cannot be given an
- * out-of-range default, so we need another way to discover the source of
- * its value. This offset is only used if given a value greater than
- * zero.
- */
- int isset_offset;
} relopt_parse_elt;
/* Local reloption definition */
@@ -195,6 +190,8 @@ typedef struct local_relopts
extern relopt_kind add_reloption_kind(void);
extern void add_bool_reloption(bits32 kinds, const char *name, const char *desc,
bool default_val, LOCKMODE lockmode);
+extern void add_ternary_reloption(bits32 kinds, const char *name,
+ const char *desc, int default_val, LOCKMODE lockmode);
extern void add_int_reloption(bits32 kinds, const char *name, const char *desc,
int default_val, int min_val, int max_val,
LOCKMODE lockmode);
@@ -214,6 +211,9 @@ extern void register_reloptions_validator(local_relopts *relopts,
extern void add_local_bool_reloption(local_relopts *relopts, const char *name,
const char *desc, bool default_val,
int offset);
+extern void add_local_ternary_reloption(local_relopts *relopts,
+ const char *name, const char *desc,
+ ternary default_val, int offset);
extern void add_local_int_reloption(local_relopts *relopts, const char *name,
const char *desc, int default_val,
int min_val, int max_val, int offset);
diff --git a/src/include/c.h b/src/include/c.h
index 39022f8a9d..9c59ccccf8 100644
--- a/src/include/c.h
+++ b/src/include/c.h
@@ -488,6 +488,22 @@ typedef void (*pg_funcptr_t) (void);
#include <stdbool.h>
+/*
+ * ternary
+ * Boolean value with an extrea "unset" option
+ *
+ * Ternary data type is used in relation options that can be "true", "false" or
+ * "unset". Since relation options are used deep inside the PostgreSQL code,
+ * this type is declared globally.
+*/
+
+typedef enum ternary
+{
+ TERNARY_FALSE = 0,
+ TERNARY_TRUE = 1,
+ TERNARY_UNSET = -1
+} ternary;
+
/* ----------------------------------------------------------------
* Section 3: standard system types
diff --git a/src/include/utils/rel.h b/src/include/utils/rel.h
index b552359915..7346d618f9 100644
--- a/src/include/utils/rel.h
+++ b/src/include/utils/rel.h
@@ -346,8 +346,7 @@ typedef struct StdRdOptions
bool user_catalog_table; /* use as an additional catalog relation */
int parallel_workers; /* max number of parallel workers */
StdRdOptIndexCleanup vacuum_index_cleanup; /* controls index vacuuming */
- bool vacuum_truncate; /* enables vacuum to truncate a relation */
- bool vacuum_truncate_set; /* whether vacuum_truncate is set */
+ ternary vacuum_truncate; /* enables vacuum to truncate a relation */
/*
* Fraction of pages in a relation that vacuum can eagerly scan and fail
--
2.39.2
--nextPart4155600.3ZeAukHxDK
Content-Disposition: attachment;
filename="v2a-0003-Add-alias-to-be-used-as-unset-state.patch"
Content-Transfer-Encoding: 7Bit
Content-Type: text/x-patch; charset="unicode-2-0-utf-8";
name="v2a-0003-Add-alias-to-be-used-as-unset-state.patch"
^ permalink raw reply [nested|flat] 868+ messages in thread
* [PATCH v2a 2/4] Introduce ternary reloptions
@ 2025-09-04 14:41 Nikolay Shaplov <[email protected]>
0 siblings, 0 replies; 868+ messages in thread
From: Nikolay Shaplov @ 2025-09-04 14:41 UTC (permalink / raw)
Introduce ternary reloption as a replacement for current `vacuum_truncate`
implementation. Remove `vacuum_truncate_set` additional flag and using
`TERNARY_UNSET` value instead.
---
src/backend/access/common/reloptions.c | 129 ++++++++++++++++++++-----
src/backend/commands/vacuum.c | 4 +-
src/include/access/reloptions.h | 26 ++---
src/include/c.h | 16 +++
src/include/utils/rel.h | 3 +-
5 files changed, 135 insertions(+), 43 deletions(-)
diff --git a/src/backend/access/common/reloptions.c b/src/backend/access/common/reloptions.c
index 0af3fea68f..f543f11001 100644
--- a/src/backend/access/common/reloptions.c
+++ b/src/backend/access/common/reloptions.c
@@ -40,9 +40,9 @@
*
* To add an option:
*
- * (i) decide on a type (bool, integer, real, enum, string), name, default
- * value, upper and lower bounds (if applicable); for strings, consider a
- * validation routine.
+ * (i) decide on a type (bool, ternary, integer, real, enum, string), name,
+ * default value, upper and lower bounds (if applicable); for strings,
+ * consider a validation routine.
* (ii) add a record below (or use add_<type>_reloption).
* (iii) add it to the appropriate options struct (perhaps StdRdOptions)
* (iv) add it to the appropriate handling routine (perhaps
@@ -147,15 +147,6 @@ static relopt_bool boolRelOpts[] =
},
false
},
- {
- {
- "vacuum_truncate",
- "Enables vacuum to truncate empty pages at the end of this table",
- RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
- ShareUpdateExclusiveLock
- },
- true
- },
{
{
"deduplicate_items",
@@ -170,6 +161,21 @@ static relopt_bool boolRelOpts[] =
{{NULL}}
};
+static relopt_ternary ternaryRelOpts[] =
+{
+ {
+ {
+ "vacuum_truncate",
+ "Enables vacuum to truncate empty pages at the end of this table",
+ RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
+ ShareUpdateExclusiveLock
+ },
+ TERNARY_UNSET
+ },
+ /* list terminator */
+ {{NULL}}
+};
+
static relopt_int intRelOpts[] =
{
{
@@ -600,6 +606,13 @@ initialize_reloptions(void)
boolRelOpts[i].gen.lockmode));
j++;
}
+ for (i = 0; ternaryRelOpts[i].gen.name; i++)
+ {
+ Assert(DoLockModesConflict(ternaryRelOpts[i].gen.lockmode,
+ ternaryRelOpts[i].gen.lockmode));
+ j++;
+ }
+
for (i = 0; intRelOpts[i].gen.name; i++)
{
Assert(DoLockModesConflict(intRelOpts[i].gen.lockmode,
@@ -640,6 +653,14 @@ initialize_reloptions(void)
j++;
}
+ for (i = 0; ternaryRelOpts[i].gen.name; i++)
+ {
+ relOpts[j] = &ternaryRelOpts[i].gen;
+ relOpts[j]->type = RELOPT_TYPE_TERNARY;
+ relOpts[j]->namelen = strlen(relOpts[j]->name);
+ j++;
+ }
+
for (i = 0; intRelOpts[i].gen.name; i++)
{
relOpts[j] = &intRelOpts[i].gen;
@@ -800,6 +821,9 @@ allocate_reloption(bits32 kinds, int type, const char *name, const char *desc,
case RELOPT_TYPE_BOOL:
size = sizeof(relopt_bool);
break;
+ case RELOPT_TYPE_TERNARY:
+ size = sizeof(relopt_ternary);
+ break;
case RELOPT_TYPE_INT:
size = sizeof(relopt_int);
break;
@@ -883,6 +907,54 @@ add_local_bool_reloption(local_relopts *relopts, const char *name,
add_local_reloption(relopts, (relopt_gen *) newoption, offset);
}
+/*
+ * init_ternary_reloption
+ * Allocate and initialize a new ternary reloption
+ */
+static relopt_ternary *
+init_ternary_reloption(bits32 kinds, const char *name, const char *desc,
+ ternary default_val, LOCKMODE lockmode)
+{
+ relopt_ternary *newoption;
+
+ newoption = (relopt_ternary *) allocate_reloption(kinds,
+ RELOPT_TYPE_TERNARY, name, desc, lockmode);
+ newoption->default_val = default_val;
+
+ return newoption;
+}
+
+/*
+ * add_ternary_reloption
+ * Add a new ternary reloption
+ */
+void
+add_ternary_reloption(bits32 kinds, const char *name, const char *desc,
+ ternary default_val, LOCKMODE lockmode)
+{
+ relopt_ternary *newoption = init_ternary_reloption(kinds, name, desc,
+ default_val, lockmode);
+
+ add_reloption((relopt_gen *) newoption);
+}
+
+/*
+ * add_local_ternary_reloption
+ * Add a new ternary local reloption
+ *
+ * 'offset' is offset of ternary-typed field.
+ */
+void
+add_local_ternary_reloption(local_relopts *relopts, const char *name,
+ const char *desc, ternary default_val,
+ int offset)
+{
+ relopt_ternary *newoption = init_ternary_reloption(RELOPT_KIND_LOCAL,
+ name, desc,
+ default_val, 0);
+
+ add_local_reloption(relopts, (relopt_gen *) newoption, offset);
+}
/*
* init_real_reloption
@@ -1617,6 +1689,18 @@ parse_one_reloption(relopt_value *option, char *text_str, int text_len,
option->gen->name, value)));
}
break;
+ case RELOPT_TYPE_TERNARY:
+ {
+ bool b;
+ parsed = parse_bool(value, &b);
+ option->values.ternary_val = b ? TERNARY_TRUE : TERNARY_FALSE;
+ if (validate && !parsed)
+ ereport(ERROR,
+ (errcode(ERRCODE_INVALID_PARAMETER_VALUE),
+ errmsg("invalid value for ternary option \"%s\": %s",
+ option->gen->name, value)));
+ }
+ break;
case RELOPT_TYPE_INT:
{
relopt_int *optint = (relopt_int *) option->gen;
@@ -1780,17 +1864,6 @@ fillRelOptions(void *rdopts, Size basesize,
char *itempos = ((char *) rdopts) + elems[j].offset;
char *string_val;
- /*
- * If isset_offset is provided, store whether the reloption is
- * set there.
- */
- if (elems[j].isset_offset > 0)
- {
- char *setpos = ((char *) rdopts) + elems[j].isset_offset;
-
- *(bool *) setpos = options[i].isset;
- }
-
switch (options[i].gen->type)
{
case RELOPT_TYPE_BOOL:
@@ -1798,6 +1871,11 @@ fillRelOptions(void *rdopts, Size basesize,
options[i].values.bool_val :
((relopt_bool *) options[i].gen)->default_val;
break;
+ case RELOPT_TYPE_TERNARY:
+ *(ternary *) itempos = options[i].isset ?
+ options[i].values.ternary_val :
+ ((relopt_ternary *) options[i].gen)->default_val;
+ break;
case RELOPT_TYPE_INT:
*(int *) itempos = options[i].isset ?
options[i].values.int_val :
@@ -1912,8 +1990,8 @@ default_reloptions(Datum reloptions, bool validate, relopt_kind kind)
offsetof(StdRdOptions, parallel_workers)},
{"vacuum_index_cleanup", RELOPT_TYPE_ENUM,
offsetof(StdRdOptions, vacuum_index_cleanup)},
- {"vacuum_truncate", RELOPT_TYPE_BOOL,
- offsetof(StdRdOptions, vacuum_truncate), offsetof(StdRdOptions, vacuum_truncate_set)},
+ {"vacuum_truncate", RELOPT_TYPE_TERNARY,
+ offsetof(StdRdOptions, vacuum_truncate)},
{"vacuum_max_eager_freeze_failure_rate", RELOPT_TYPE_REAL,
offsetof(StdRdOptions, vacuum_max_eager_freeze_failure_rate)}
};
@@ -1993,7 +2071,6 @@ build_local_reloptions(local_relopts *relopts, Datum options, bool validate)
elems[i].optname = opt->option->name;
elems[i].opttype = opt->option->type;
elems[i].offset = opt->offset;
- elems[i].isset_offset = 0; /* not supported for local relopts yet */
i++;
}
diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c
index 733ef40ae7..977babff54 100644
--- a/src/backend/commands/vacuum.c
+++ b/src/backend/commands/vacuum.c
@@ -2221,9 +2221,9 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams params,
{
StdRdOptions *opts = (StdRdOptions *) rel->rd_options;
- if (opts && opts->vacuum_truncate_set)
+ if (opts && opts->vacuum_truncate != TERNARY_UNSET)
{
- if (opts->vacuum_truncate)
+ if (opts->vacuum_truncate == TERNARY_TRUE)
params.truncate = VACOPTVALUE_ENABLED;
else
params.truncate = VACOPTVALUE_DISABLED;
diff --git a/src/include/access/reloptions.h b/src/include/access/reloptions.h
index a604a4702c..a436697658 100644
--- a/src/include/access/reloptions.h
+++ b/src/include/access/reloptions.h
@@ -29,6 +29,7 @@
typedef enum relopt_type
{
RELOPT_TYPE_BOOL,
+ RELOPT_TYPE_TERNARY, /* on, off, unset */
RELOPT_TYPE_INT,
RELOPT_TYPE_REAL,
RELOPT_TYPE_ENUM,
@@ -80,6 +81,7 @@ typedef struct relopt_value
union
{
bool bool_val;
+ ternary ternary_val;
int int_val;
double real_val;
int enum_val;
@@ -94,6 +96,12 @@ typedef struct relopt_bool
bool default_val;
} relopt_bool;
+typedef struct relopt_rernary
+{
+ relopt_gen gen;
+ int default_val;
+} relopt_ternary;
+
typedef struct relopt_int
{
relopt_gen gen;
@@ -152,19 +160,6 @@ typedef struct
const char *optname; /* option's name */
relopt_type opttype; /* option's datatype */
int offset; /* offset of field in result struct */
-
- /*
- * isset_offset is an optional offset of a field in the result struct that
- * stores whether the option is explicitly set for the relation or if it
- * just picked up the default value. In most cases, this can be
- * accomplished by giving the reloption a special out-of-range default
- * value (e.g., some integer reloptions use -2), but this isn't always
- * possible. For example, a Boolean reloption cannot be given an
- * out-of-range default, so we need another way to discover the source of
- * its value. This offset is only used if given a value greater than
- * zero.
- */
- int isset_offset;
} relopt_parse_elt;
/* Local reloption definition */
@@ -195,6 +190,8 @@ typedef struct local_relopts
extern relopt_kind add_reloption_kind(void);
extern void add_bool_reloption(bits32 kinds, const char *name, const char *desc,
bool default_val, LOCKMODE lockmode);
+extern void add_ternary_reloption(bits32 kinds, const char *name,
+ const char *desc, int default_val, LOCKMODE lockmode);
extern void add_int_reloption(bits32 kinds, const char *name, const char *desc,
int default_val, int min_val, int max_val,
LOCKMODE lockmode);
@@ -214,6 +211,9 @@ extern void register_reloptions_validator(local_relopts *relopts,
extern void add_local_bool_reloption(local_relopts *relopts, const char *name,
const char *desc, bool default_val,
int offset);
+extern void add_local_ternary_reloption(local_relopts *relopts,
+ const char *name, const char *desc,
+ ternary default_val, int offset);
extern void add_local_int_reloption(local_relopts *relopts, const char *name,
const char *desc, int default_val,
int min_val, int max_val, int offset);
diff --git a/src/include/c.h b/src/include/c.h
index 39022f8a9d..9c59ccccf8 100644
--- a/src/include/c.h
+++ b/src/include/c.h
@@ -488,6 +488,22 @@ typedef void (*pg_funcptr_t) (void);
#include <stdbool.h>
+/*
+ * ternary
+ * Boolean value with an extrea "unset" option
+ *
+ * Ternary data type is used in relation options that can be "true", "false" or
+ * "unset". Since relation options are used deep inside the PostgreSQL code,
+ * this type is declared globally.
+*/
+
+typedef enum ternary
+{
+ TERNARY_FALSE = 0,
+ TERNARY_TRUE = 1,
+ TERNARY_UNSET = -1
+} ternary;
+
/* ----------------------------------------------------------------
* Section 3: standard system types
diff --git a/src/include/utils/rel.h b/src/include/utils/rel.h
index b552359915..7346d618f9 100644
--- a/src/include/utils/rel.h
+++ b/src/include/utils/rel.h
@@ -346,8 +346,7 @@ typedef struct StdRdOptions
bool user_catalog_table; /* use as an additional catalog relation */
int parallel_workers; /* max number of parallel workers */
StdRdOptIndexCleanup vacuum_index_cleanup; /* controls index vacuuming */
- bool vacuum_truncate; /* enables vacuum to truncate a relation */
- bool vacuum_truncate_set; /* whether vacuum_truncate is set */
+ ternary vacuum_truncate; /* enables vacuum to truncate a relation */
/*
* Fraction of pages in a relation that vacuum can eagerly scan and fail
--
2.39.2
--nextPart4155600.3ZeAukHxDK
Content-Disposition: attachment;
filename="v2a-0003-Add-alias-to-be-used-as-unset-state.patch"
Content-Transfer-Encoding: 7Bit
Content-Type: text/x-patch; charset="unicode-2-0-utf-8";
name="v2a-0003-Add-alias-to-be-used-as-unset-state.patch"
^ permalink raw reply [nested|flat] 868+ messages in thread
* [PATCH v2a 2/4] Introduce ternary reloptions
@ 2025-09-04 14:41 Nikolay Shaplov <[email protected]>
0 siblings, 0 replies; 868+ messages in thread
From: Nikolay Shaplov @ 2025-09-04 14:41 UTC (permalink / raw)
Introduce ternary reloption as a replacement for current `vacuum_truncate`
implementation. Remove `vacuum_truncate_set` additional flag and using
`TERNARY_UNSET` value instead.
---
src/backend/access/common/reloptions.c | 129 ++++++++++++++++++++-----
src/backend/commands/vacuum.c | 4 +-
src/include/access/reloptions.h | 26 ++---
src/include/c.h | 16 +++
src/include/utils/rel.h | 3 +-
5 files changed, 135 insertions(+), 43 deletions(-)
diff --git a/src/backend/access/common/reloptions.c b/src/backend/access/common/reloptions.c
index 0af3fea68f..f543f11001 100644
--- a/src/backend/access/common/reloptions.c
+++ b/src/backend/access/common/reloptions.c
@@ -40,9 +40,9 @@
*
* To add an option:
*
- * (i) decide on a type (bool, integer, real, enum, string), name, default
- * value, upper and lower bounds (if applicable); for strings, consider a
- * validation routine.
+ * (i) decide on a type (bool, ternary, integer, real, enum, string), name,
+ * default value, upper and lower bounds (if applicable); for strings,
+ * consider a validation routine.
* (ii) add a record below (or use add_<type>_reloption).
* (iii) add it to the appropriate options struct (perhaps StdRdOptions)
* (iv) add it to the appropriate handling routine (perhaps
@@ -147,15 +147,6 @@ static relopt_bool boolRelOpts[] =
},
false
},
- {
- {
- "vacuum_truncate",
- "Enables vacuum to truncate empty pages at the end of this table",
- RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
- ShareUpdateExclusiveLock
- },
- true
- },
{
{
"deduplicate_items",
@@ -170,6 +161,21 @@ static relopt_bool boolRelOpts[] =
{{NULL}}
};
+static relopt_ternary ternaryRelOpts[] =
+{
+ {
+ {
+ "vacuum_truncate",
+ "Enables vacuum to truncate empty pages at the end of this table",
+ RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
+ ShareUpdateExclusiveLock
+ },
+ TERNARY_UNSET
+ },
+ /* list terminator */
+ {{NULL}}
+};
+
static relopt_int intRelOpts[] =
{
{
@@ -600,6 +606,13 @@ initialize_reloptions(void)
boolRelOpts[i].gen.lockmode));
j++;
}
+ for (i = 0; ternaryRelOpts[i].gen.name; i++)
+ {
+ Assert(DoLockModesConflict(ternaryRelOpts[i].gen.lockmode,
+ ternaryRelOpts[i].gen.lockmode));
+ j++;
+ }
+
for (i = 0; intRelOpts[i].gen.name; i++)
{
Assert(DoLockModesConflict(intRelOpts[i].gen.lockmode,
@@ -640,6 +653,14 @@ initialize_reloptions(void)
j++;
}
+ for (i = 0; ternaryRelOpts[i].gen.name; i++)
+ {
+ relOpts[j] = &ternaryRelOpts[i].gen;
+ relOpts[j]->type = RELOPT_TYPE_TERNARY;
+ relOpts[j]->namelen = strlen(relOpts[j]->name);
+ j++;
+ }
+
for (i = 0; intRelOpts[i].gen.name; i++)
{
relOpts[j] = &intRelOpts[i].gen;
@@ -800,6 +821,9 @@ allocate_reloption(bits32 kinds, int type, const char *name, const char *desc,
case RELOPT_TYPE_BOOL:
size = sizeof(relopt_bool);
break;
+ case RELOPT_TYPE_TERNARY:
+ size = sizeof(relopt_ternary);
+ break;
case RELOPT_TYPE_INT:
size = sizeof(relopt_int);
break;
@@ -883,6 +907,54 @@ add_local_bool_reloption(local_relopts *relopts, const char *name,
add_local_reloption(relopts, (relopt_gen *) newoption, offset);
}
+/*
+ * init_ternary_reloption
+ * Allocate and initialize a new ternary reloption
+ */
+static relopt_ternary *
+init_ternary_reloption(bits32 kinds, const char *name, const char *desc,
+ ternary default_val, LOCKMODE lockmode)
+{
+ relopt_ternary *newoption;
+
+ newoption = (relopt_ternary *) allocate_reloption(kinds,
+ RELOPT_TYPE_TERNARY, name, desc, lockmode);
+ newoption->default_val = default_val;
+
+ return newoption;
+}
+
+/*
+ * add_ternary_reloption
+ * Add a new ternary reloption
+ */
+void
+add_ternary_reloption(bits32 kinds, const char *name, const char *desc,
+ ternary default_val, LOCKMODE lockmode)
+{
+ relopt_ternary *newoption = init_ternary_reloption(kinds, name, desc,
+ default_val, lockmode);
+
+ add_reloption((relopt_gen *) newoption);
+}
+
+/*
+ * add_local_ternary_reloption
+ * Add a new ternary local reloption
+ *
+ * 'offset' is offset of ternary-typed field.
+ */
+void
+add_local_ternary_reloption(local_relopts *relopts, const char *name,
+ const char *desc, ternary default_val,
+ int offset)
+{
+ relopt_ternary *newoption = init_ternary_reloption(RELOPT_KIND_LOCAL,
+ name, desc,
+ default_val, 0);
+
+ add_local_reloption(relopts, (relopt_gen *) newoption, offset);
+}
/*
* init_real_reloption
@@ -1617,6 +1689,18 @@ parse_one_reloption(relopt_value *option, char *text_str, int text_len,
option->gen->name, value)));
}
break;
+ case RELOPT_TYPE_TERNARY:
+ {
+ bool b;
+ parsed = parse_bool(value, &b);
+ option->values.ternary_val = b ? TERNARY_TRUE : TERNARY_FALSE;
+ if (validate && !parsed)
+ ereport(ERROR,
+ (errcode(ERRCODE_INVALID_PARAMETER_VALUE),
+ errmsg("invalid value for ternary option \"%s\": %s",
+ option->gen->name, value)));
+ }
+ break;
case RELOPT_TYPE_INT:
{
relopt_int *optint = (relopt_int *) option->gen;
@@ -1780,17 +1864,6 @@ fillRelOptions(void *rdopts, Size basesize,
char *itempos = ((char *) rdopts) + elems[j].offset;
char *string_val;
- /*
- * If isset_offset is provided, store whether the reloption is
- * set there.
- */
- if (elems[j].isset_offset > 0)
- {
- char *setpos = ((char *) rdopts) + elems[j].isset_offset;
-
- *(bool *) setpos = options[i].isset;
- }
-
switch (options[i].gen->type)
{
case RELOPT_TYPE_BOOL:
@@ -1798,6 +1871,11 @@ fillRelOptions(void *rdopts, Size basesize,
options[i].values.bool_val :
((relopt_bool *) options[i].gen)->default_val;
break;
+ case RELOPT_TYPE_TERNARY:
+ *(ternary *) itempos = options[i].isset ?
+ options[i].values.ternary_val :
+ ((relopt_ternary *) options[i].gen)->default_val;
+ break;
case RELOPT_TYPE_INT:
*(int *) itempos = options[i].isset ?
options[i].values.int_val :
@@ -1912,8 +1990,8 @@ default_reloptions(Datum reloptions, bool validate, relopt_kind kind)
offsetof(StdRdOptions, parallel_workers)},
{"vacuum_index_cleanup", RELOPT_TYPE_ENUM,
offsetof(StdRdOptions, vacuum_index_cleanup)},
- {"vacuum_truncate", RELOPT_TYPE_BOOL,
- offsetof(StdRdOptions, vacuum_truncate), offsetof(StdRdOptions, vacuum_truncate_set)},
+ {"vacuum_truncate", RELOPT_TYPE_TERNARY,
+ offsetof(StdRdOptions, vacuum_truncate)},
{"vacuum_max_eager_freeze_failure_rate", RELOPT_TYPE_REAL,
offsetof(StdRdOptions, vacuum_max_eager_freeze_failure_rate)}
};
@@ -1993,7 +2071,6 @@ build_local_reloptions(local_relopts *relopts, Datum options, bool validate)
elems[i].optname = opt->option->name;
elems[i].opttype = opt->option->type;
elems[i].offset = opt->offset;
- elems[i].isset_offset = 0; /* not supported for local relopts yet */
i++;
}
diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c
index 733ef40ae7..977babff54 100644
--- a/src/backend/commands/vacuum.c
+++ b/src/backend/commands/vacuum.c
@@ -2221,9 +2221,9 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams params,
{
StdRdOptions *opts = (StdRdOptions *) rel->rd_options;
- if (opts && opts->vacuum_truncate_set)
+ if (opts && opts->vacuum_truncate != TERNARY_UNSET)
{
- if (opts->vacuum_truncate)
+ if (opts->vacuum_truncate == TERNARY_TRUE)
params.truncate = VACOPTVALUE_ENABLED;
else
params.truncate = VACOPTVALUE_DISABLED;
diff --git a/src/include/access/reloptions.h b/src/include/access/reloptions.h
index a604a4702c..a436697658 100644
--- a/src/include/access/reloptions.h
+++ b/src/include/access/reloptions.h
@@ -29,6 +29,7 @@
typedef enum relopt_type
{
RELOPT_TYPE_BOOL,
+ RELOPT_TYPE_TERNARY, /* on, off, unset */
RELOPT_TYPE_INT,
RELOPT_TYPE_REAL,
RELOPT_TYPE_ENUM,
@@ -80,6 +81,7 @@ typedef struct relopt_value
union
{
bool bool_val;
+ ternary ternary_val;
int int_val;
double real_val;
int enum_val;
@@ -94,6 +96,12 @@ typedef struct relopt_bool
bool default_val;
} relopt_bool;
+typedef struct relopt_rernary
+{
+ relopt_gen gen;
+ int default_val;
+} relopt_ternary;
+
typedef struct relopt_int
{
relopt_gen gen;
@@ -152,19 +160,6 @@ typedef struct
const char *optname; /* option's name */
relopt_type opttype; /* option's datatype */
int offset; /* offset of field in result struct */
-
- /*
- * isset_offset is an optional offset of a field in the result struct that
- * stores whether the option is explicitly set for the relation or if it
- * just picked up the default value. In most cases, this can be
- * accomplished by giving the reloption a special out-of-range default
- * value (e.g., some integer reloptions use -2), but this isn't always
- * possible. For example, a Boolean reloption cannot be given an
- * out-of-range default, so we need another way to discover the source of
- * its value. This offset is only used if given a value greater than
- * zero.
- */
- int isset_offset;
} relopt_parse_elt;
/* Local reloption definition */
@@ -195,6 +190,8 @@ typedef struct local_relopts
extern relopt_kind add_reloption_kind(void);
extern void add_bool_reloption(bits32 kinds, const char *name, const char *desc,
bool default_val, LOCKMODE lockmode);
+extern void add_ternary_reloption(bits32 kinds, const char *name,
+ const char *desc, int default_val, LOCKMODE lockmode);
extern void add_int_reloption(bits32 kinds, const char *name, const char *desc,
int default_val, int min_val, int max_val,
LOCKMODE lockmode);
@@ -214,6 +211,9 @@ extern void register_reloptions_validator(local_relopts *relopts,
extern void add_local_bool_reloption(local_relopts *relopts, const char *name,
const char *desc, bool default_val,
int offset);
+extern void add_local_ternary_reloption(local_relopts *relopts,
+ const char *name, const char *desc,
+ ternary default_val, int offset);
extern void add_local_int_reloption(local_relopts *relopts, const char *name,
const char *desc, int default_val,
int min_val, int max_val, int offset);
diff --git a/src/include/c.h b/src/include/c.h
index 39022f8a9d..9c59ccccf8 100644
--- a/src/include/c.h
+++ b/src/include/c.h
@@ -488,6 +488,22 @@ typedef void (*pg_funcptr_t) (void);
#include <stdbool.h>
+/*
+ * ternary
+ * Boolean value with an extrea "unset" option
+ *
+ * Ternary data type is used in relation options that can be "true", "false" or
+ * "unset". Since relation options are used deep inside the PostgreSQL code,
+ * this type is declared globally.
+*/
+
+typedef enum ternary
+{
+ TERNARY_FALSE = 0,
+ TERNARY_TRUE = 1,
+ TERNARY_UNSET = -1
+} ternary;
+
/* ----------------------------------------------------------------
* Section 3: standard system types
diff --git a/src/include/utils/rel.h b/src/include/utils/rel.h
index b552359915..7346d618f9 100644
--- a/src/include/utils/rel.h
+++ b/src/include/utils/rel.h
@@ -346,8 +346,7 @@ typedef struct StdRdOptions
bool user_catalog_table; /* use as an additional catalog relation */
int parallel_workers; /* max number of parallel workers */
StdRdOptIndexCleanup vacuum_index_cleanup; /* controls index vacuuming */
- bool vacuum_truncate; /* enables vacuum to truncate a relation */
- bool vacuum_truncate_set; /* whether vacuum_truncate is set */
+ ternary vacuum_truncate; /* enables vacuum to truncate a relation */
/*
* Fraction of pages in a relation that vacuum can eagerly scan and fail
--
2.39.2
--nextPart4155600.3ZeAukHxDK
Content-Disposition: attachment;
filename="v2a-0003-Add-alias-to-be-used-as-unset-state.patch"
Content-Transfer-Encoding: 7Bit
Content-Type: text/x-patch; charset="unicode-2-0-utf-8";
name="v2a-0003-Add-alias-to-be-used-as-unset-state.patch"
^ permalink raw reply [nested|flat] 868+ messages in thread
* [PATCH v2a 2/4] Introduce ternary reloptions
@ 2025-09-04 14:41 Nikolay Shaplov <[email protected]>
0 siblings, 0 replies; 868+ messages in thread
From: Nikolay Shaplov @ 2025-09-04 14:41 UTC (permalink / raw)
Introduce ternary reloption as a replacement for current `vacuum_truncate`
implementation. Remove `vacuum_truncate_set` additional flag and using
`TERNARY_UNSET` value instead.
---
src/backend/access/common/reloptions.c | 129 ++++++++++++++++++++-----
src/backend/commands/vacuum.c | 4 +-
src/include/access/reloptions.h | 26 ++---
src/include/c.h | 16 +++
src/include/utils/rel.h | 3 +-
5 files changed, 135 insertions(+), 43 deletions(-)
diff --git a/src/backend/access/common/reloptions.c b/src/backend/access/common/reloptions.c
index 0af3fea68f..f543f11001 100644
--- a/src/backend/access/common/reloptions.c
+++ b/src/backend/access/common/reloptions.c
@@ -40,9 +40,9 @@
*
* To add an option:
*
- * (i) decide on a type (bool, integer, real, enum, string), name, default
- * value, upper and lower bounds (if applicable); for strings, consider a
- * validation routine.
+ * (i) decide on a type (bool, ternary, integer, real, enum, string), name,
+ * default value, upper and lower bounds (if applicable); for strings,
+ * consider a validation routine.
* (ii) add a record below (or use add_<type>_reloption).
* (iii) add it to the appropriate options struct (perhaps StdRdOptions)
* (iv) add it to the appropriate handling routine (perhaps
@@ -147,15 +147,6 @@ static relopt_bool boolRelOpts[] =
},
false
},
- {
- {
- "vacuum_truncate",
- "Enables vacuum to truncate empty pages at the end of this table",
- RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
- ShareUpdateExclusiveLock
- },
- true
- },
{
{
"deduplicate_items",
@@ -170,6 +161,21 @@ static relopt_bool boolRelOpts[] =
{{NULL}}
};
+static relopt_ternary ternaryRelOpts[] =
+{
+ {
+ {
+ "vacuum_truncate",
+ "Enables vacuum to truncate empty pages at the end of this table",
+ RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
+ ShareUpdateExclusiveLock
+ },
+ TERNARY_UNSET
+ },
+ /* list terminator */
+ {{NULL}}
+};
+
static relopt_int intRelOpts[] =
{
{
@@ -600,6 +606,13 @@ initialize_reloptions(void)
boolRelOpts[i].gen.lockmode));
j++;
}
+ for (i = 0; ternaryRelOpts[i].gen.name; i++)
+ {
+ Assert(DoLockModesConflict(ternaryRelOpts[i].gen.lockmode,
+ ternaryRelOpts[i].gen.lockmode));
+ j++;
+ }
+
for (i = 0; intRelOpts[i].gen.name; i++)
{
Assert(DoLockModesConflict(intRelOpts[i].gen.lockmode,
@@ -640,6 +653,14 @@ initialize_reloptions(void)
j++;
}
+ for (i = 0; ternaryRelOpts[i].gen.name; i++)
+ {
+ relOpts[j] = &ternaryRelOpts[i].gen;
+ relOpts[j]->type = RELOPT_TYPE_TERNARY;
+ relOpts[j]->namelen = strlen(relOpts[j]->name);
+ j++;
+ }
+
for (i = 0; intRelOpts[i].gen.name; i++)
{
relOpts[j] = &intRelOpts[i].gen;
@@ -800,6 +821,9 @@ allocate_reloption(bits32 kinds, int type, const char *name, const char *desc,
case RELOPT_TYPE_BOOL:
size = sizeof(relopt_bool);
break;
+ case RELOPT_TYPE_TERNARY:
+ size = sizeof(relopt_ternary);
+ break;
case RELOPT_TYPE_INT:
size = sizeof(relopt_int);
break;
@@ -883,6 +907,54 @@ add_local_bool_reloption(local_relopts *relopts, const char *name,
add_local_reloption(relopts, (relopt_gen *) newoption, offset);
}
+/*
+ * init_ternary_reloption
+ * Allocate and initialize a new ternary reloption
+ */
+static relopt_ternary *
+init_ternary_reloption(bits32 kinds, const char *name, const char *desc,
+ ternary default_val, LOCKMODE lockmode)
+{
+ relopt_ternary *newoption;
+
+ newoption = (relopt_ternary *) allocate_reloption(kinds,
+ RELOPT_TYPE_TERNARY, name, desc, lockmode);
+ newoption->default_val = default_val;
+
+ return newoption;
+}
+
+/*
+ * add_ternary_reloption
+ * Add a new ternary reloption
+ */
+void
+add_ternary_reloption(bits32 kinds, const char *name, const char *desc,
+ ternary default_val, LOCKMODE lockmode)
+{
+ relopt_ternary *newoption = init_ternary_reloption(kinds, name, desc,
+ default_val, lockmode);
+
+ add_reloption((relopt_gen *) newoption);
+}
+
+/*
+ * add_local_ternary_reloption
+ * Add a new ternary local reloption
+ *
+ * 'offset' is offset of ternary-typed field.
+ */
+void
+add_local_ternary_reloption(local_relopts *relopts, const char *name,
+ const char *desc, ternary default_val,
+ int offset)
+{
+ relopt_ternary *newoption = init_ternary_reloption(RELOPT_KIND_LOCAL,
+ name, desc,
+ default_val, 0);
+
+ add_local_reloption(relopts, (relopt_gen *) newoption, offset);
+}
/*
* init_real_reloption
@@ -1617,6 +1689,18 @@ parse_one_reloption(relopt_value *option, char *text_str, int text_len,
option->gen->name, value)));
}
break;
+ case RELOPT_TYPE_TERNARY:
+ {
+ bool b;
+ parsed = parse_bool(value, &b);
+ option->values.ternary_val = b ? TERNARY_TRUE : TERNARY_FALSE;
+ if (validate && !parsed)
+ ereport(ERROR,
+ (errcode(ERRCODE_INVALID_PARAMETER_VALUE),
+ errmsg("invalid value for ternary option \"%s\": %s",
+ option->gen->name, value)));
+ }
+ break;
case RELOPT_TYPE_INT:
{
relopt_int *optint = (relopt_int *) option->gen;
@@ -1780,17 +1864,6 @@ fillRelOptions(void *rdopts, Size basesize,
char *itempos = ((char *) rdopts) + elems[j].offset;
char *string_val;
- /*
- * If isset_offset is provided, store whether the reloption is
- * set there.
- */
- if (elems[j].isset_offset > 0)
- {
- char *setpos = ((char *) rdopts) + elems[j].isset_offset;
-
- *(bool *) setpos = options[i].isset;
- }
-
switch (options[i].gen->type)
{
case RELOPT_TYPE_BOOL:
@@ -1798,6 +1871,11 @@ fillRelOptions(void *rdopts, Size basesize,
options[i].values.bool_val :
((relopt_bool *) options[i].gen)->default_val;
break;
+ case RELOPT_TYPE_TERNARY:
+ *(ternary *) itempos = options[i].isset ?
+ options[i].values.ternary_val :
+ ((relopt_ternary *) options[i].gen)->default_val;
+ break;
case RELOPT_TYPE_INT:
*(int *) itempos = options[i].isset ?
options[i].values.int_val :
@@ -1912,8 +1990,8 @@ default_reloptions(Datum reloptions, bool validate, relopt_kind kind)
offsetof(StdRdOptions, parallel_workers)},
{"vacuum_index_cleanup", RELOPT_TYPE_ENUM,
offsetof(StdRdOptions, vacuum_index_cleanup)},
- {"vacuum_truncate", RELOPT_TYPE_BOOL,
- offsetof(StdRdOptions, vacuum_truncate), offsetof(StdRdOptions, vacuum_truncate_set)},
+ {"vacuum_truncate", RELOPT_TYPE_TERNARY,
+ offsetof(StdRdOptions, vacuum_truncate)},
{"vacuum_max_eager_freeze_failure_rate", RELOPT_TYPE_REAL,
offsetof(StdRdOptions, vacuum_max_eager_freeze_failure_rate)}
};
@@ -1993,7 +2071,6 @@ build_local_reloptions(local_relopts *relopts, Datum options, bool validate)
elems[i].optname = opt->option->name;
elems[i].opttype = opt->option->type;
elems[i].offset = opt->offset;
- elems[i].isset_offset = 0; /* not supported for local relopts yet */
i++;
}
diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c
index 733ef40ae7..977babff54 100644
--- a/src/backend/commands/vacuum.c
+++ b/src/backend/commands/vacuum.c
@@ -2221,9 +2221,9 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams params,
{
StdRdOptions *opts = (StdRdOptions *) rel->rd_options;
- if (opts && opts->vacuum_truncate_set)
+ if (opts && opts->vacuum_truncate != TERNARY_UNSET)
{
- if (opts->vacuum_truncate)
+ if (opts->vacuum_truncate == TERNARY_TRUE)
params.truncate = VACOPTVALUE_ENABLED;
else
params.truncate = VACOPTVALUE_DISABLED;
diff --git a/src/include/access/reloptions.h b/src/include/access/reloptions.h
index a604a4702c..a436697658 100644
--- a/src/include/access/reloptions.h
+++ b/src/include/access/reloptions.h
@@ -29,6 +29,7 @@
typedef enum relopt_type
{
RELOPT_TYPE_BOOL,
+ RELOPT_TYPE_TERNARY, /* on, off, unset */
RELOPT_TYPE_INT,
RELOPT_TYPE_REAL,
RELOPT_TYPE_ENUM,
@@ -80,6 +81,7 @@ typedef struct relopt_value
union
{
bool bool_val;
+ ternary ternary_val;
int int_val;
double real_val;
int enum_val;
@@ -94,6 +96,12 @@ typedef struct relopt_bool
bool default_val;
} relopt_bool;
+typedef struct relopt_rernary
+{
+ relopt_gen gen;
+ int default_val;
+} relopt_ternary;
+
typedef struct relopt_int
{
relopt_gen gen;
@@ -152,19 +160,6 @@ typedef struct
const char *optname; /* option's name */
relopt_type opttype; /* option's datatype */
int offset; /* offset of field in result struct */
-
- /*
- * isset_offset is an optional offset of a field in the result struct that
- * stores whether the option is explicitly set for the relation or if it
- * just picked up the default value. In most cases, this can be
- * accomplished by giving the reloption a special out-of-range default
- * value (e.g., some integer reloptions use -2), but this isn't always
- * possible. For example, a Boolean reloption cannot be given an
- * out-of-range default, so we need another way to discover the source of
- * its value. This offset is only used if given a value greater than
- * zero.
- */
- int isset_offset;
} relopt_parse_elt;
/* Local reloption definition */
@@ -195,6 +190,8 @@ typedef struct local_relopts
extern relopt_kind add_reloption_kind(void);
extern void add_bool_reloption(bits32 kinds, const char *name, const char *desc,
bool default_val, LOCKMODE lockmode);
+extern void add_ternary_reloption(bits32 kinds, const char *name,
+ const char *desc, int default_val, LOCKMODE lockmode);
extern void add_int_reloption(bits32 kinds, const char *name, const char *desc,
int default_val, int min_val, int max_val,
LOCKMODE lockmode);
@@ -214,6 +211,9 @@ extern void register_reloptions_validator(local_relopts *relopts,
extern void add_local_bool_reloption(local_relopts *relopts, const char *name,
const char *desc, bool default_val,
int offset);
+extern void add_local_ternary_reloption(local_relopts *relopts,
+ const char *name, const char *desc,
+ ternary default_val, int offset);
extern void add_local_int_reloption(local_relopts *relopts, const char *name,
const char *desc, int default_val,
int min_val, int max_val, int offset);
diff --git a/src/include/c.h b/src/include/c.h
index 39022f8a9d..9c59ccccf8 100644
--- a/src/include/c.h
+++ b/src/include/c.h
@@ -488,6 +488,22 @@ typedef void (*pg_funcptr_t) (void);
#include <stdbool.h>
+/*
+ * ternary
+ * Boolean value with an extrea "unset" option
+ *
+ * Ternary data type is used in relation options that can be "true", "false" or
+ * "unset". Since relation options are used deep inside the PostgreSQL code,
+ * this type is declared globally.
+*/
+
+typedef enum ternary
+{
+ TERNARY_FALSE = 0,
+ TERNARY_TRUE = 1,
+ TERNARY_UNSET = -1
+} ternary;
+
/* ----------------------------------------------------------------
* Section 3: standard system types
diff --git a/src/include/utils/rel.h b/src/include/utils/rel.h
index b552359915..7346d618f9 100644
--- a/src/include/utils/rel.h
+++ b/src/include/utils/rel.h
@@ -346,8 +346,7 @@ typedef struct StdRdOptions
bool user_catalog_table; /* use as an additional catalog relation */
int parallel_workers; /* max number of parallel workers */
StdRdOptIndexCleanup vacuum_index_cleanup; /* controls index vacuuming */
- bool vacuum_truncate; /* enables vacuum to truncate a relation */
- bool vacuum_truncate_set; /* whether vacuum_truncate is set */
+ ternary vacuum_truncate; /* enables vacuum to truncate a relation */
/*
* Fraction of pages in a relation that vacuum can eagerly scan and fail
--
2.39.2
--nextPart4155600.3ZeAukHxDK
Content-Disposition: attachment;
filename="v2a-0003-Add-alias-to-be-used-as-unset-state.patch"
Content-Transfer-Encoding: 7Bit
Content-Type: text/x-patch; charset="unicode-2-0-utf-8";
name="v2a-0003-Add-alias-to-be-used-as-unset-state.patch"
^ permalink raw reply [nested|flat] 868+ messages in thread
* [PATCH v2 2/4] Introduce ternary reloptions
@ 2025-09-04 14:41 Nikolay Shaplov <[email protected]>
0 siblings, 0 replies; 868+ messages in thread
From: Nikolay Shaplov @ 2025-09-04 14:41 UTC (permalink / raw)
Introduce ternary reloption as a replacement for current `vacuum_truncate`
implementation. Remove `vacuum_truncate_set` additional flag and using
`TERNARY_UNSET` value instead.
---
src/backend/access/common/reloptions.c | 129 ++++++++++++++++++++-----
src/backend/commands/vacuum.c | 4 +-
src/include/access/reloptions.h | 26 ++---
src/include/c.h | 16 +++
src/include/utils/rel.h | 3 +-
5 files changed, 135 insertions(+), 43 deletions(-)
diff --git a/src/backend/access/common/reloptions.c b/src/backend/access/common/reloptions.c
index 0af3fea68f..f543f11001 100644
--- a/src/backend/access/common/reloptions.c
+++ b/src/backend/access/common/reloptions.c
@@ -40,9 +40,9 @@
*
* To add an option:
*
- * (i) decide on a type (bool, integer, real, enum, string), name, default
- * value, upper and lower bounds (if applicable); for strings, consider a
- * validation routine.
+ * (i) decide on a type (bool, ternary, integer, real, enum, string), name,
+ * default value, upper and lower bounds (if applicable); for strings,
+ * consider a validation routine.
* (ii) add a record below (or use add_<type>_reloption).
* (iii) add it to the appropriate options struct (perhaps StdRdOptions)
* (iv) add it to the appropriate handling routine (perhaps
@@ -147,15 +147,6 @@ static relopt_bool boolRelOpts[] =
},
false
},
- {
- {
- "vacuum_truncate",
- "Enables vacuum to truncate empty pages at the end of this table",
- RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
- ShareUpdateExclusiveLock
- },
- true
- },
{
{
"deduplicate_items",
@@ -170,6 +161,21 @@ static relopt_bool boolRelOpts[] =
{{NULL}}
};
+static relopt_ternary ternaryRelOpts[] =
+{
+ {
+ {
+ "vacuum_truncate",
+ "Enables vacuum to truncate empty pages at the end of this table",
+ RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
+ ShareUpdateExclusiveLock
+ },
+ TERNARY_UNSET
+ },
+ /* list terminator */
+ {{NULL}}
+};
+
static relopt_int intRelOpts[] =
{
{
@@ -600,6 +606,13 @@ initialize_reloptions(void)
boolRelOpts[i].gen.lockmode));
j++;
}
+ for (i = 0; ternaryRelOpts[i].gen.name; i++)
+ {
+ Assert(DoLockModesConflict(ternaryRelOpts[i].gen.lockmode,
+ ternaryRelOpts[i].gen.lockmode));
+ j++;
+ }
+
for (i = 0; intRelOpts[i].gen.name; i++)
{
Assert(DoLockModesConflict(intRelOpts[i].gen.lockmode,
@@ -640,6 +653,14 @@ initialize_reloptions(void)
j++;
}
+ for (i = 0; ternaryRelOpts[i].gen.name; i++)
+ {
+ relOpts[j] = &ternaryRelOpts[i].gen;
+ relOpts[j]->type = RELOPT_TYPE_TERNARY;
+ relOpts[j]->namelen = strlen(relOpts[j]->name);
+ j++;
+ }
+
for (i = 0; intRelOpts[i].gen.name; i++)
{
relOpts[j] = &intRelOpts[i].gen;
@@ -800,6 +821,9 @@ allocate_reloption(bits32 kinds, int type, const char *name, const char *desc,
case RELOPT_TYPE_BOOL:
size = sizeof(relopt_bool);
break;
+ case RELOPT_TYPE_TERNARY:
+ size = sizeof(relopt_ternary);
+ break;
case RELOPT_TYPE_INT:
size = sizeof(relopt_int);
break;
@@ -883,6 +907,54 @@ add_local_bool_reloption(local_relopts *relopts, const char *name,
add_local_reloption(relopts, (relopt_gen *) newoption, offset);
}
+/*
+ * init_ternary_reloption
+ * Allocate and initialize a new ternary reloption
+ */
+static relopt_ternary *
+init_ternary_reloption(bits32 kinds, const char *name, const char *desc,
+ ternary default_val, LOCKMODE lockmode)
+{
+ relopt_ternary *newoption;
+
+ newoption = (relopt_ternary *) allocate_reloption(kinds,
+ RELOPT_TYPE_TERNARY, name, desc, lockmode);
+ newoption->default_val = default_val;
+
+ return newoption;
+}
+
+/*
+ * add_ternary_reloption
+ * Add a new ternary reloption
+ */
+void
+add_ternary_reloption(bits32 kinds, const char *name, const char *desc,
+ ternary default_val, LOCKMODE lockmode)
+{
+ relopt_ternary *newoption = init_ternary_reloption(kinds, name, desc,
+ default_val, lockmode);
+
+ add_reloption((relopt_gen *) newoption);
+}
+
+/*
+ * add_local_ternary_reloption
+ * Add a new ternary local reloption
+ *
+ * 'offset' is offset of ternary-typed field.
+ */
+void
+add_local_ternary_reloption(local_relopts *relopts, const char *name,
+ const char *desc, ternary default_val,
+ int offset)
+{
+ relopt_ternary *newoption = init_ternary_reloption(RELOPT_KIND_LOCAL,
+ name, desc,
+ default_val, 0);
+
+ add_local_reloption(relopts, (relopt_gen *) newoption, offset);
+}
/*
* init_real_reloption
@@ -1617,6 +1689,18 @@ parse_one_reloption(relopt_value *option, char *text_str, int text_len,
option->gen->name, value)));
}
break;
+ case RELOPT_TYPE_TERNARY:
+ {
+ bool b;
+ parsed = parse_bool(value, &b);
+ option->values.ternary_val = b ? TERNARY_TRUE : TERNARY_FALSE;
+ if (validate && !parsed)
+ ereport(ERROR,
+ (errcode(ERRCODE_INVALID_PARAMETER_VALUE),
+ errmsg("invalid value for ternary option \"%s\": %s",
+ option->gen->name, value)));
+ }
+ break;
case RELOPT_TYPE_INT:
{
relopt_int *optint = (relopt_int *) option->gen;
@@ -1780,17 +1864,6 @@ fillRelOptions(void *rdopts, Size basesize,
char *itempos = ((char *) rdopts) + elems[j].offset;
char *string_val;
- /*
- * If isset_offset is provided, store whether the reloption is
- * set there.
- */
- if (elems[j].isset_offset > 0)
- {
- char *setpos = ((char *) rdopts) + elems[j].isset_offset;
-
- *(bool *) setpos = options[i].isset;
- }
-
switch (options[i].gen->type)
{
case RELOPT_TYPE_BOOL:
@@ -1798,6 +1871,11 @@ fillRelOptions(void *rdopts, Size basesize,
options[i].values.bool_val :
((relopt_bool *) options[i].gen)->default_val;
break;
+ case RELOPT_TYPE_TERNARY:
+ *(ternary *) itempos = options[i].isset ?
+ options[i].values.ternary_val :
+ ((relopt_ternary *) options[i].gen)->default_val;
+ break;
case RELOPT_TYPE_INT:
*(int *) itempos = options[i].isset ?
options[i].values.int_val :
@@ -1912,8 +1990,8 @@ default_reloptions(Datum reloptions, bool validate, relopt_kind kind)
offsetof(StdRdOptions, parallel_workers)},
{"vacuum_index_cleanup", RELOPT_TYPE_ENUM,
offsetof(StdRdOptions, vacuum_index_cleanup)},
- {"vacuum_truncate", RELOPT_TYPE_BOOL,
- offsetof(StdRdOptions, vacuum_truncate), offsetof(StdRdOptions, vacuum_truncate_set)},
+ {"vacuum_truncate", RELOPT_TYPE_TERNARY,
+ offsetof(StdRdOptions, vacuum_truncate)},
{"vacuum_max_eager_freeze_failure_rate", RELOPT_TYPE_REAL,
offsetof(StdRdOptions, vacuum_max_eager_freeze_failure_rate)}
};
@@ -1993,7 +2071,6 @@ build_local_reloptions(local_relopts *relopts, Datum options, bool validate)
elems[i].optname = opt->option->name;
elems[i].opttype = opt->option->type;
elems[i].offset = opt->offset;
- elems[i].isset_offset = 0; /* not supported for local relopts yet */
i++;
}
diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c
index 733ef40ae7..977babff54 100644
--- a/src/backend/commands/vacuum.c
+++ b/src/backend/commands/vacuum.c
@@ -2221,9 +2221,9 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams params,
{
StdRdOptions *opts = (StdRdOptions *) rel->rd_options;
- if (opts && opts->vacuum_truncate_set)
+ if (opts && opts->vacuum_truncate != TERNARY_UNSET)
{
- if (opts->vacuum_truncate)
+ if (opts->vacuum_truncate == TERNARY_TRUE)
params.truncate = VACOPTVALUE_ENABLED;
else
params.truncate = VACOPTVALUE_DISABLED;
diff --git a/src/include/access/reloptions.h b/src/include/access/reloptions.h
index a604a4702c..a436697658 100644
--- a/src/include/access/reloptions.h
+++ b/src/include/access/reloptions.h
@@ -29,6 +29,7 @@
typedef enum relopt_type
{
RELOPT_TYPE_BOOL,
+ RELOPT_TYPE_TERNARY, /* on, off, unset */
RELOPT_TYPE_INT,
RELOPT_TYPE_REAL,
RELOPT_TYPE_ENUM,
@@ -80,6 +81,7 @@ typedef struct relopt_value
union
{
bool bool_val;
+ ternary ternary_val;
int int_val;
double real_val;
int enum_val;
@@ -94,6 +96,12 @@ typedef struct relopt_bool
bool default_val;
} relopt_bool;
+typedef struct relopt_rernary
+{
+ relopt_gen gen;
+ int default_val;
+} relopt_ternary;
+
typedef struct relopt_int
{
relopt_gen gen;
@@ -152,19 +160,6 @@ typedef struct
const char *optname; /* option's name */
relopt_type opttype; /* option's datatype */
int offset; /* offset of field in result struct */
-
- /*
- * isset_offset is an optional offset of a field in the result struct that
- * stores whether the option is explicitly set for the relation or if it
- * just picked up the default value. In most cases, this can be
- * accomplished by giving the reloption a special out-of-range default
- * value (e.g., some integer reloptions use -2), but this isn't always
- * possible. For example, a Boolean reloption cannot be given an
- * out-of-range default, so we need another way to discover the source of
- * its value. This offset is only used if given a value greater than
- * zero.
- */
- int isset_offset;
} relopt_parse_elt;
/* Local reloption definition */
@@ -195,6 +190,8 @@ typedef struct local_relopts
extern relopt_kind add_reloption_kind(void);
extern void add_bool_reloption(bits32 kinds, const char *name, const char *desc,
bool default_val, LOCKMODE lockmode);
+extern void add_ternary_reloption(bits32 kinds, const char *name,
+ const char *desc, int default_val, LOCKMODE lockmode);
extern void add_int_reloption(bits32 kinds, const char *name, const char *desc,
int default_val, int min_val, int max_val,
LOCKMODE lockmode);
@@ -214,6 +211,9 @@ extern void register_reloptions_validator(local_relopts *relopts,
extern void add_local_bool_reloption(local_relopts *relopts, const char *name,
const char *desc, bool default_val,
int offset);
+extern void add_local_ternary_reloption(local_relopts *relopts,
+ const char *name, const char *desc,
+ ternary default_val, int offset);
extern void add_local_int_reloption(local_relopts *relopts, const char *name,
const char *desc, int default_val,
int min_val, int max_val, int offset);
diff --git a/src/include/c.h b/src/include/c.h
index 39022f8a9d..9c59ccccf8 100644
--- a/src/include/c.h
+++ b/src/include/c.h
@@ -488,6 +488,22 @@ typedef void (*pg_funcptr_t) (void);
#include <stdbool.h>
+/*
+ * ternary
+ * Boolean value with an extrea "unset" option
+ *
+ * Ternary data type is used in relation options that can be "true", "false" or
+ * "unset". Since relation options are used deep inside the PostgreSQL code,
+ * this type is declared globally.
+*/
+
+typedef enum ternary
+{
+ TERNARY_FALSE = 0,
+ TERNARY_TRUE = 1,
+ TERNARY_UNSET = -1
+} ternary;
+
/* ----------------------------------------------------------------
* Section 3: standard system types
diff --git a/src/include/utils/rel.h b/src/include/utils/rel.h
index b552359915..7346d618f9 100644
--- a/src/include/utils/rel.h
+++ b/src/include/utils/rel.h
@@ -346,8 +346,7 @@ typedef struct StdRdOptions
bool user_catalog_table; /* use as an additional catalog relation */
int parallel_workers; /* max number of parallel workers */
StdRdOptIndexCleanup vacuum_index_cleanup; /* controls index vacuuming */
- bool vacuum_truncate; /* enables vacuum to truncate a relation */
- bool vacuum_truncate_set; /* whether vacuum_truncate is set */
+ ternary vacuum_truncate; /* enables vacuum to truncate a relation */
/*
* Fraction of pages in a relation that vacuum can eagerly scan and fail
--
2.39.2
--nextPart5129932.5fSG56mABF
Content-Disposition: attachment;
filename="v2-0003-Add-alias-to-be-used-as-unset-state.patch"
Content-Transfer-Encoding: 7Bit
Content-Type: text/x-patch; charset="unicode-2-0-utf-8";
name="v2-0003-Add-alias-to-be-used-as-unset-state.patch"
^ permalink raw reply [nested|flat] 868+ messages in thread
* [PATCH v2 2/4] Introduce ternary reloptions
@ 2025-09-04 14:41 Nikolay Shaplov <[email protected]>
0 siblings, 0 replies; 868+ messages in thread
From: Nikolay Shaplov @ 2025-09-04 14:41 UTC (permalink / raw)
Introduce ternary reloption as a replacement for current `vacuum_truncate`
implementation. Remove `vacuum_truncate_set` additional flag and using
`TERNARY_UNSET` value instead.
---
src/backend/access/common/reloptions.c | 129 ++++++++++++++++++++-----
src/backend/commands/vacuum.c | 4 +-
src/include/access/reloptions.h | 26 ++---
src/include/c.h | 16 +++
src/include/utils/rel.h | 3 +-
5 files changed, 135 insertions(+), 43 deletions(-)
diff --git a/src/backend/access/common/reloptions.c b/src/backend/access/common/reloptions.c
index 0af3fea68f..f543f11001 100644
--- a/src/backend/access/common/reloptions.c
+++ b/src/backend/access/common/reloptions.c
@@ -40,9 +40,9 @@
*
* To add an option:
*
- * (i) decide on a type (bool, integer, real, enum, string), name, default
- * value, upper and lower bounds (if applicable); for strings, consider a
- * validation routine.
+ * (i) decide on a type (bool, ternary, integer, real, enum, string), name,
+ * default value, upper and lower bounds (if applicable); for strings,
+ * consider a validation routine.
* (ii) add a record below (or use add_<type>_reloption).
* (iii) add it to the appropriate options struct (perhaps StdRdOptions)
* (iv) add it to the appropriate handling routine (perhaps
@@ -147,15 +147,6 @@ static relopt_bool boolRelOpts[] =
},
false
},
- {
- {
- "vacuum_truncate",
- "Enables vacuum to truncate empty pages at the end of this table",
- RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
- ShareUpdateExclusiveLock
- },
- true
- },
{
{
"deduplicate_items",
@@ -170,6 +161,21 @@ static relopt_bool boolRelOpts[] =
{{NULL}}
};
+static relopt_ternary ternaryRelOpts[] =
+{
+ {
+ {
+ "vacuum_truncate",
+ "Enables vacuum to truncate empty pages at the end of this table",
+ RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
+ ShareUpdateExclusiveLock
+ },
+ TERNARY_UNSET
+ },
+ /* list terminator */
+ {{NULL}}
+};
+
static relopt_int intRelOpts[] =
{
{
@@ -600,6 +606,13 @@ initialize_reloptions(void)
boolRelOpts[i].gen.lockmode));
j++;
}
+ for (i = 0; ternaryRelOpts[i].gen.name; i++)
+ {
+ Assert(DoLockModesConflict(ternaryRelOpts[i].gen.lockmode,
+ ternaryRelOpts[i].gen.lockmode));
+ j++;
+ }
+
for (i = 0; intRelOpts[i].gen.name; i++)
{
Assert(DoLockModesConflict(intRelOpts[i].gen.lockmode,
@@ -640,6 +653,14 @@ initialize_reloptions(void)
j++;
}
+ for (i = 0; ternaryRelOpts[i].gen.name; i++)
+ {
+ relOpts[j] = &ternaryRelOpts[i].gen;
+ relOpts[j]->type = RELOPT_TYPE_TERNARY;
+ relOpts[j]->namelen = strlen(relOpts[j]->name);
+ j++;
+ }
+
for (i = 0; intRelOpts[i].gen.name; i++)
{
relOpts[j] = &intRelOpts[i].gen;
@@ -800,6 +821,9 @@ allocate_reloption(bits32 kinds, int type, const char *name, const char *desc,
case RELOPT_TYPE_BOOL:
size = sizeof(relopt_bool);
break;
+ case RELOPT_TYPE_TERNARY:
+ size = sizeof(relopt_ternary);
+ break;
case RELOPT_TYPE_INT:
size = sizeof(relopt_int);
break;
@@ -883,6 +907,54 @@ add_local_bool_reloption(local_relopts *relopts, const char *name,
add_local_reloption(relopts, (relopt_gen *) newoption, offset);
}
+/*
+ * init_ternary_reloption
+ * Allocate and initialize a new ternary reloption
+ */
+static relopt_ternary *
+init_ternary_reloption(bits32 kinds, const char *name, const char *desc,
+ ternary default_val, LOCKMODE lockmode)
+{
+ relopt_ternary *newoption;
+
+ newoption = (relopt_ternary *) allocate_reloption(kinds,
+ RELOPT_TYPE_TERNARY, name, desc, lockmode);
+ newoption->default_val = default_val;
+
+ return newoption;
+}
+
+/*
+ * add_ternary_reloption
+ * Add a new ternary reloption
+ */
+void
+add_ternary_reloption(bits32 kinds, const char *name, const char *desc,
+ ternary default_val, LOCKMODE lockmode)
+{
+ relopt_ternary *newoption = init_ternary_reloption(kinds, name, desc,
+ default_val, lockmode);
+
+ add_reloption((relopt_gen *) newoption);
+}
+
+/*
+ * add_local_ternary_reloption
+ * Add a new ternary local reloption
+ *
+ * 'offset' is offset of ternary-typed field.
+ */
+void
+add_local_ternary_reloption(local_relopts *relopts, const char *name,
+ const char *desc, ternary default_val,
+ int offset)
+{
+ relopt_ternary *newoption = init_ternary_reloption(RELOPT_KIND_LOCAL,
+ name, desc,
+ default_val, 0);
+
+ add_local_reloption(relopts, (relopt_gen *) newoption, offset);
+}
/*
* init_real_reloption
@@ -1617,6 +1689,18 @@ parse_one_reloption(relopt_value *option, char *text_str, int text_len,
option->gen->name, value)));
}
break;
+ case RELOPT_TYPE_TERNARY:
+ {
+ bool b;
+ parsed = parse_bool(value, &b);
+ option->values.ternary_val = b ? TERNARY_TRUE : TERNARY_FALSE;
+ if (validate && !parsed)
+ ereport(ERROR,
+ (errcode(ERRCODE_INVALID_PARAMETER_VALUE),
+ errmsg("invalid value for ternary option \"%s\": %s",
+ option->gen->name, value)));
+ }
+ break;
case RELOPT_TYPE_INT:
{
relopt_int *optint = (relopt_int *) option->gen;
@@ -1780,17 +1864,6 @@ fillRelOptions(void *rdopts, Size basesize,
char *itempos = ((char *) rdopts) + elems[j].offset;
char *string_val;
- /*
- * If isset_offset is provided, store whether the reloption is
- * set there.
- */
- if (elems[j].isset_offset > 0)
- {
- char *setpos = ((char *) rdopts) + elems[j].isset_offset;
-
- *(bool *) setpos = options[i].isset;
- }
-
switch (options[i].gen->type)
{
case RELOPT_TYPE_BOOL:
@@ -1798,6 +1871,11 @@ fillRelOptions(void *rdopts, Size basesize,
options[i].values.bool_val :
((relopt_bool *) options[i].gen)->default_val;
break;
+ case RELOPT_TYPE_TERNARY:
+ *(ternary *) itempos = options[i].isset ?
+ options[i].values.ternary_val :
+ ((relopt_ternary *) options[i].gen)->default_val;
+ break;
case RELOPT_TYPE_INT:
*(int *) itempos = options[i].isset ?
options[i].values.int_val :
@@ -1912,8 +1990,8 @@ default_reloptions(Datum reloptions, bool validate, relopt_kind kind)
offsetof(StdRdOptions, parallel_workers)},
{"vacuum_index_cleanup", RELOPT_TYPE_ENUM,
offsetof(StdRdOptions, vacuum_index_cleanup)},
- {"vacuum_truncate", RELOPT_TYPE_BOOL,
- offsetof(StdRdOptions, vacuum_truncate), offsetof(StdRdOptions, vacuum_truncate_set)},
+ {"vacuum_truncate", RELOPT_TYPE_TERNARY,
+ offsetof(StdRdOptions, vacuum_truncate)},
{"vacuum_max_eager_freeze_failure_rate", RELOPT_TYPE_REAL,
offsetof(StdRdOptions, vacuum_max_eager_freeze_failure_rate)}
};
@@ -1993,7 +2071,6 @@ build_local_reloptions(local_relopts *relopts, Datum options, bool validate)
elems[i].optname = opt->option->name;
elems[i].opttype = opt->option->type;
elems[i].offset = opt->offset;
- elems[i].isset_offset = 0; /* not supported for local relopts yet */
i++;
}
diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c
index 733ef40ae7..977babff54 100644
--- a/src/backend/commands/vacuum.c
+++ b/src/backend/commands/vacuum.c
@@ -2221,9 +2221,9 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams params,
{
StdRdOptions *opts = (StdRdOptions *) rel->rd_options;
- if (opts && opts->vacuum_truncate_set)
+ if (opts && opts->vacuum_truncate != TERNARY_UNSET)
{
- if (opts->vacuum_truncate)
+ if (opts->vacuum_truncate == TERNARY_TRUE)
params.truncate = VACOPTVALUE_ENABLED;
else
params.truncate = VACOPTVALUE_DISABLED;
diff --git a/src/include/access/reloptions.h b/src/include/access/reloptions.h
index a604a4702c..a436697658 100644
--- a/src/include/access/reloptions.h
+++ b/src/include/access/reloptions.h
@@ -29,6 +29,7 @@
typedef enum relopt_type
{
RELOPT_TYPE_BOOL,
+ RELOPT_TYPE_TERNARY, /* on, off, unset */
RELOPT_TYPE_INT,
RELOPT_TYPE_REAL,
RELOPT_TYPE_ENUM,
@@ -80,6 +81,7 @@ typedef struct relopt_value
union
{
bool bool_val;
+ ternary ternary_val;
int int_val;
double real_val;
int enum_val;
@@ -94,6 +96,12 @@ typedef struct relopt_bool
bool default_val;
} relopt_bool;
+typedef struct relopt_rernary
+{
+ relopt_gen gen;
+ int default_val;
+} relopt_ternary;
+
typedef struct relopt_int
{
relopt_gen gen;
@@ -152,19 +160,6 @@ typedef struct
const char *optname; /* option's name */
relopt_type opttype; /* option's datatype */
int offset; /* offset of field in result struct */
-
- /*
- * isset_offset is an optional offset of a field in the result struct that
- * stores whether the option is explicitly set for the relation or if it
- * just picked up the default value. In most cases, this can be
- * accomplished by giving the reloption a special out-of-range default
- * value (e.g., some integer reloptions use -2), but this isn't always
- * possible. For example, a Boolean reloption cannot be given an
- * out-of-range default, so we need another way to discover the source of
- * its value. This offset is only used if given a value greater than
- * zero.
- */
- int isset_offset;
} relopt_parse_elt;
/* Local reloption definition */
@@ -195,6 +190,8 @@ typedef struct local_relopts
extern relopt_kind add_reloption_kind(void);
extern void add_bool_reloption(bits32 kinds, const char *name, const char *desc,
bool default_val, LOCKMODE lockmode);
+extern void add_ternary_reloption(bits32 kinds, const char *name,
+ const char *desc, int default_val, LOCKMODE lockmode);
extern void add_int_reloption(bits32 kinds, const char *name, const char *desc,
int default_val, int min_val, int max_val,
LOCKMODE lockmode);
@@ -214,6 +211,9 @@ extern void register_reloptions_validator(local_relopts *relopts,
extern void add_local_bool_reloption(local_relopts *relopts, const char *name,
const char *desc, bool default_val,
int offset);
+extern void add_local_ternary_reloption(local_relopts *relopts,
+ const char *name, const char *desc,
+ ternary default_val, int offset);
extern void add_local_int_reloption(local_relopts *relopts, const char *name,
const char *desc, int default_val,
int min_val, int max_val, int offset);
diff --git a/src/include/c.h b/src/include/c.h
index 39022f8a9d..9c59ccccf8 100644
--- a/src/include/c.h
+++ b/src/include/c.h
@@ -488,6 +488,22 @@ typedef void (*pg_funcptr_t) (void);
#include <stdbool.h>
+/*
+ * ternary
+ * Boolean value with an extrea "unset" option
+ *
+ * Ternary data type is used in relation options that can be "true", "false" or
+ * "unset". Since relation options are used deep inside the PostgreSQL code,
+ * this type is declared globally.
+*/
+
+typedef enum ternary
+{
+ TERNARY_FALSE = 0,
+ TERNARY_TRUE = 1,
+ TERNARY_UNSET = -1
+} ternary;
+
/* ----------------------------------------------------------------
* Section 3: standard system types
diff --git a/src/include/utils/rel.h b/src/include/utils/rel.h
index b552359915..7346d618f9 100644
--- a/src/include/utils/rel.h
+++ b/src/include/utils/rel.h
@@ -346,8 +346,7 @@ typedef struct StdRdOptions
bool user_catalog_table; /* use as an additional catalog relation */
int parallel_workers; /* max number of parallel workers */
StdRdOptIndexCleanup vacuum_index_cleanup; /* controls index vacuuming */
- bool vacuum_truncate; /* enables vacuum to truncate a relation */
- bool vacuum_truncate_set; /* whether vacuum_truncate is set */
+ ternary vacuum_truncate; /* enables vacuum to truncate a relation */
/*
* Fraction of pages in a relation that vacuum can eagerly scan and fail
--
2.39.2
--nextPart5129932.5fSG56mABF
Content-Disposition: attachment;
filename="v2-0003-Add-alias-to-be-used-as-unset-state.patch"
Content-Transfer-Encoding: 7Bit
Content-Type: text/x-patch; charset="unicode-2-0-utf-8";
name="v2-0003-Add-alias-to-be-used-as-unset-state.patch"
^ permalink raw reply [nested|flat] 868+ messages in thread
* [PATCH v2 2/4] Introduce ternary reloptions
@ 2025-09-04 14:41 Nikolay Shaplov <[email protected]>
0 siblings, 0 replies; 868+ messages in thread
From: Nikolay Shaplov @ 2025-09-04 14:41 UTC (permalink / raw)
Introduce ternary reloption as a replacement for current `vacuum_truncate`
implementation. Remove `vacuum_truncate_set` additional flag and using
`TERNARY_UNSET` value instead.
---
src/backend/access/common/reloptions.c | 129 ++++++++++++++++++++-----
src/backend/commands/vacuum.c | 4 +-
src/include/access/reloptions.h | 26 ++---
src/include/c.h | 16 +++
src/include/utils/rel.h | 3 +-
5 files changed, 135 insertions(+), 43 deletions(-)
diff --git a/src/backend/access/common/reloptions.c b/src/backend/access/common/reloptions.c
index 0af3fea68f..f543f11001 100644
--- a/src/backend/access/common/reloptions.c
+++ b/src/backend/access/common/reloptions.c
@@ -40,9 +40,9 @@
*
* To add an option:
*
- * (i) decide on a type (bool, integer, real, enum, string), name, default
- * value, upper and lower bounds (if applicable); for strings, consider a
- * validation routine.
+ * (i) decide on a type (bool, ternary, integer, real, enum, string), name,
+ * default value, upper and lower bounds (if applicable); for strings,
+ * consider a validation routine.
* (ii) add a record below (or use add_<type>_reloption).
* (iii) add it to the appropriate options struct (perhaps StdRdOptions)
* (iv) add it to the appropriate handling routine (perhaps
@@ -147,15 +147,6 @@ static relopt_bool boolRelOpts[] =
},
false
},
- {
- {
- "vacuum_truncate",
- "Enables vacuum to truncate empty pages at the end of this table",
- RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
- ShareUpdateExclusiveLock
- },
- true
- },
{
{
"deduplicate_items",
@@ -170,6 +161,21 @@ static relopt_bool boolRelOpts[] =
{{NULL}}
};
+static relopt_ternary ternaryRelOpts[] =
+{
+ {
+ {
+ "vacuum_truncate",
+ "Enables vacuum to truncate empty pages at the end of this table",
+ RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
+ ShareUpdateExclusiveLock
+ },
+ TERNARY_UNSET
+ },
+ /* list terminator */
+ {{NULL}}
+};
+
static relopt_int intRelOpts[] =
{
{
@@ -600,6 +606,13 @@ initialize_reloptions(void)
boolRelOpts[i].gen.lockmode));
j++;
}
+ for (i = 0; ternaryRelOpts[i].gen.name; i++)
+ {
+ Assert(DoLockModesConflict(ternaryRelOpts[i].gen.lockmode,
+ ternaryRelOpts[i].gen.lockmode));
+ j++;
+ }
+
for (i = 0; intRelOpts[i].gen.name; i++)
{
Assert(DoLockModesConflict(intRelOpts[i].gen.lockmode,
@@ -640,6 +653,14 @@ initialize_reloptions(void)
j++;
}
+ for (i = 0; ternaryRelOpts[i].gen.name; i++)
+ {
+ relOpts[j] = &ternaryRelOpts[i].gen;
+ relOpts[j]->type = RELOPT_TYPE_TERNARY;
+ relOpts[j]->namelen = strlen(relOpts[j]->name);
+ j++;
+ }
+
for (i = 0; intRelOpts[i].gen.name; i++)
{
relOpts[j] = &intRelOpts[i].gen;
@@ -800,6 +821,9 @@ allocate_reloption(bits32 kinds, int type, const char *name, const char *desc,
case RELOPT_TYPE_BOOL:
size = sizeof(relopt_bool);
break;
+ case RELOPT_TYPE_TERNARY:
+ size = sizeof(relopt_ternary);
+ break;
case RELOPT_TYPE_INT:
size = sizeof(relopt_int);
break;
@@ -883,6 +907,54 @@ add_local_bool_reloption(local_relopts *relopts, const char *name,
add_local_reloption(relopts, (relopt_gen *) newoption, offset);
}
+/*
+ * init_ternary_reloption
+ * Allocate and initialize a new ternary reloption
+ */
+static relopt_ternary *
+init_ternary_reloption(bits32 kinds, const char *name, const char *desc,
+ ternary default_val, LOCKMODE lockmode)
+{
+ relopt_ternary *newoption;
+
+ newoption = (relopt_ternary *) allocate_reloption(kinds,
+ RELOPT_TYPE_TERNARY, name, desc, lockmode);
+ newoption->default_val = default_val;
+
+ return newoption;
+}
+
+/*
+ * add_ternary_reloption
+ * Add a new ternary reloption
+ */
+void
+add_ternary_reloption(bits32 kinds, const char *name, const char *desc,
+ ternary default_val, LOCKMODE lockmode)
+{
+ relopt_ternary *newoption = init_ternary_reloption(kinds, name, desc,
+ default_val, lockmode);
+
+ add_reloption((relopt_gen *) newoption);
+}
+
+/*
+ * add_local_ternary_reloption
+ * Add a new ternary local reloption
+ *
+ * 'offset' is offset of ternary-typed field.
+ */
+void
+add_local_ternary_reloption(local_relopts *relopts, const char *name,
+ const char *desc, ternary default_val,
+ int offset)
+{
+ relopt_ternary *newoption = init_ternary_reloption(RELOPT_KIND_LOCAL,
+ name, desc,
+ default_val, 0);
+
+ add_local_reloption(relopts, (relopt_gen *) newoption, offset);
+}
/*
* init_real_reloption
@@ -1617,6 +1689,18 @@ parse_one_reloption(relopt_value *option, char *text_str, int text_len,
option->gen->name, value)));
}
break;
+ case RELOPT_TYPE_TERNARY:
+ {
+ bool b;
+ parsed = parse_bool(value, &b);
+ option->values.ternary_val = b ? TERNARY_TRUE : TERNARY_FALSE;
+ if (validate && !parsed)
+ ereport(ERROR,
+ (errcode(ERRCODE_INVALID_PARAMETER_VALUE),
+ errmsg("invalid value for ternary option \"%s\": %s",
+ option->gen->name, value)));
+ }
+ break;
case RELOPT_TYPE_INT:
{
relopt_int *optint = (relopt_int *) option->gen;
@@ -1780,17 +1864,6 @@ fillRelOptions(void *rdopts, Size basesize,
char *itempos = ((char *) rdopts) + elems[j].offset;
char *string_val;
- /*
- * If isset_offset is provided, store whether the reloption is
- * set there.
- */
- if (elems[j].isset_offset > 0)
- {
- char *setpos = ((char *) rdopts) + elems[j].isset_offset;
-
- *(bool *) setpos = options[i].isset;
- }
-
switch (options[i].gen->type)
{
case RELOPT_TYPE_BOOL:
@@ -1798,6 +1871,11 @@ fillRelOptions(void *rdopts, Size basesize,
options[i].values.bool_val :
((relopt_bool *) options[i].gen)->default_val;
break;
+ case RELOPT_TYPE_TERNARY:
+ *(ternary *) itempos = options[i].isset ?
+ options[i].values.ternary_val :
+ ((relopt_ternary *) options[i].gen)->default_val;
+ break;
case RELOPT_TYPE_INT:
*(int *) itempos = options[i].isset ?
options[i].values.int_val :
@@ -1912,8 +1990,8 @@ default_reloptions(Datum reloptions, bool validate, relopt_kind kind)
offsetof(StdRdOptions, parallel_workers)},
{"vacuum_index_cleanup", RELOPT_TYPE_ENUM,
offsetof(StdRdOptions, vacuum_index_cleanup)},
- {"vacuum_truncate", RELOPT_TYPE_BOOL,
- offsetof(StdRdOptions, vacuum_truncate), offsetof(StdRdOptions, vacuum_truncate_set)},
+ {"vacuum_truncate", RELOPT_TYPE_TERNARY,
+ offsetof(StdRdOptions, vacuum_truncate)},
{"vacuum_max_eager_freeze_failure_rate", RELOPT_TYPE_REAL,
offsetof(StdRdOptions, vacuum_max_eager_freeze_failure_rate)}
};
@@ -1993,7 +2071,6 @@ build_local_reloptions(local_relopts *relopts, Datum options, bool validate)
elems[i].optname = opt->option->name;
elems[i].opttype = opt->option->type;
elems[i].offset = opt->offset;
- elems[i].isset_offset = 0; /* not supported for local relopts yet */
i++;
}
diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c
index 733ef40ae7..977babff54 100644
--- a/src/backend/commands/vacuum.c
+++ b/src/backend/commands/vacuum.c
@@ -2221,9 +2221,9 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams params,
{
StdRdOptions *opts = (StdRdOptions *) rel->rd_options;
- if (opts && opts->vacuum_truncate_set)
+ if (opts && opts->vacuum_truncate != TERNARY_UNSET)
{
- if (opts->vacuum_truncate)
+ if (opts->vacuum_truncate == TERNARY_TRUE)
params.truncate = VACOPTVALUE_ENABLED;
else
params.truncate = VACOPTVALUE_DISABLED;
diff --git a/src/include/access/reloptions.h b/src/include/access/reloptions.h
index a604a4702c..a436697658 100644
--- a/src/include/access/reloptions.h
+++ b/src/include/access/reloptions.h
@@ -29,6 +29,7 @@
typedef enum relopt_type
{
RELOPT_TYPE_BOOL,
+ RELOPT_TYPE_TERNARY, /* on, off, unset */
RELOPT_TYPE_INT,
RELOPT_TYPE_REAL,
RELOPT_TYPE_ENUM,
@@ -80,6 +81,7 @@ typedef struct relopt_value
union
{
bool bool_val;
+ ternary ternary_val;
int int_val;
double real_val;
int enum_val;
@@ -94,6 +96,12 @@ typedef struct relopt_bool
bool default_val;
} relopt_bool;
+typedef struct relopt_rernary
+{
+ relopt_gen gen;
+ int default_val;
+} relopt_ternary;
+
typedef struct relopt_int
{
relopt_gen gen;
@@ -152,19 +160,6 @@ typedef struct
const char *optname; /* option's name */
relopt_type opttype; /* option's datatype */
int offset; /* offset of field in result struct */
-
- /*
- * isset_offset is an optional offset of a field in the result struct that
- * stores whether the option is explicitly set for the relation or if it
- * just picked up the default value. In most cases, this can be
- * accomplished by giving the reloption a special out-of-range default
- * value (e.g., some integer reloptions use -2), but this isn't always
- * possible. For example, a Boolean reloption cannot be given an
- * out-of-range default, so we need another way to discover the source of
- * its value. This offset is only used if given a value greater than
- * zero.
- */
- int isset_offset;
} relopt_parse_elt;
/* Local reloption definition */
@@ -195,6 +190,8 @@ typedef struct local_relopts
extern relopt_kind add_reloption_kind(void);
extern void add_bool_reloption(bits32 kinds, const char *name, const char *desc,
bool default_val, LOCKMODE lockmode);
+extern void add_ternary_reloption(bits32 kinds, const char *name,
+ const char *desc, int default_val, LOCKMODE lockmode);
extern void add_int_reloption(bits32 kinds, const char *name, const char *desc,
int default_val, int min_val, int max_val,
LOCKMODE lockmode);
@@ -214,6 +211,9 @@ extern void register_reloptions_validator(local_relopts *relopts,
extern void add_local_bool_reloption(local_relopts *relopts, const char *name,
const char *desc, bool default_val,
int offset);
+extern void add_local_ternary_reloption(local_relopts *relopts,
+ const char *name, const char *desc,
+ ternary default_val, int offset);
extern void add_local_int_reloption(local_relopts *relopts, const char *name,
const char *desc, int default_val,
int min_val, int max_val, int offset);
diff --git a/src/include/c.h b/src/include/c.h
index 39022f8a9d..9c59ccccf8 100644
--- a/src/include/c.h
+++ b/src/include/c.h
@@ -488,6 +488,22 @@ typedef void (*pg_funcptr_t) (void);
#include <stdbool.h>
+/*
+ * ternary
+ * Boolean value with an extrea "unset" option
+ *
+ * Ternary data type is used in relation options that can be "true", "false" or
+ * "unset". Since relation options are used deep inside the PostgreSQL code,
+ * this type is declared globally.
+*/
+
+typedef enum ternary
+{
+ TERNARY_FALSE = 0,
+ TERNARY_TRUE = 1,
+ TERNARY_UNSET = -1
+} ternary;
+
/* ----------------------------------------------------------------
* Section 3: standard system types
diff --git a/src/include/utils/rel.h b/src/include/utils/rel.h
index b552359915..7346d618f9 100644
--- a/src/include/utils/rel.h
+++ b/src/include/utils/rel.h
@@ -346,8 +346,7 @@ typedef struct StdRdOptions
bool user_catalog_table; /* use as an additional catalog relation */
int parallel_workers; /* max number of parallel workers */
StdRdOptIndexCleanup vacuum_index_cleanup; /* controls index vacuuming */
- bool vacuum_truncate; /* enables vacuum to truncate a relation */
- bool vacuum_truncate_set; /* whether vacuum_truncate is set */
+ ternary vacuum_truncate; /* enables vacuum to truncate a relation */
/*
* Fraction of pages in a relation that vacuum can eagerly scan and fail
--
2.39.2
--nextPart5129932.5fSG56mABF
Content-Disposition: attachment;
filename="v2-0003-Add-alias-to-be-used-as-unset-state.patch"
Content-Transfer-Encoding: 7Bit
Content-Type: text/x-patch; charset="unicode-2-0-utf-8";
name="v2-0003-Add-alias-to-be-used-as-unset-state.patch"
^ permalink raw reply [nested|flat] 868+ messages in thread
* [PATCH v2a 2/4] Introduce ternary reloptions
@ 2025-09-04 14:41 Nikolay Shaplov <[email protected]>
0 siblings, 0 replies; 868+ messages in thread
From: Nikolay Shaplov @ 2025-09-04 14:41 UTC (permalink / raw)
Introduce ternary reloption as a replacement for current `vacuum_truncate`
implementation. Remove `vacuum_truncate_set` additional flag and using
`TERNARY_UNSET` value instead.
---
src/backend/access/common/reloptions.c | 129 ++++++++++++++++++++-----
src/backend/commands/vacuum.c | 4 +-
src/include/access/reloptions.h | 26 ++---
src/include/c.h | 16 +++
src/include/utils/rel.h | 3 +-
5 files changed, 135 insertions(+), 43 deletions(-)
diff --git a/src/backend/access/common/reloptions.c b/src/backend/access/common/reloptions.c
index 0af3fea68f..f543f11001 100644
--- a/src/backend/access/common/reloptions.c
+++ b/src/backend/access/common/reloptions.c
@@ -40,9 +40,9 @@
*
* To add an option:
*
- * (i) decide on a type (bool, integer, real, enum, string), name, default
- * value, upper and lower bounds (if applicable); for strings, consider a
- * validation routine.
+ * (i) decide on a type (bool, ternary, integer, real, enum, string), name,
+ * default value, upper and lower bounds (if applicable); for strings,
+ * consider a validation routine.
* (ii) add a record below (or use add_<type>_reloption).
* (iii) add it to the appropriate options struct (perhaps StdRdOptions)
* (iv) add it to the appropriate handling routine (perhaps
@@ -147,15 +147,6 @@ static relopt_bool boolRelOpts[] =
},
false
},
- {
- {
- "vacuum_truncate",
- "Enables vacuum to truncate empty pages at the end of this table",
- RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
- ShareUpdateExclusiveLock
- },
- true
- },
{
{
"deduplicate_items",
@@ -170,6 +161,21 @@ static relopt_bool boolRelOpts[] =
{{NULL}}
};
+static relopt_ternary ternaryRelOpts[] =
+{
+ {
+ {
+ "vacuum_truncate",
+ "Enables vacuum to truncate empty pages at the end of this table",
+ RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
+ ShareUpdateExclusiveLock
+ },
+ TERNARY_UNSET
+ },
+ /* list terminator */
+ {{NULL}}
+};
+
static relopt_int intRelOpts[] =
{
{
@@ -600,6 +606,13 @@ initialize_reloptions(void)
boolRelOpts[i].gen.lockmode));
j++;
}
+ for (i = 0; ternaryRelOpts[i].gen.name; i++)
+ {
+ Assert(DoLockModesConflict(ternaryRelOpts[i].gen.lockmode,
+ ternaryRelOpts[i].gen.lockmode));
+ j++;
+ }
+
for (i = 0; intRelOpts[i].gen.name; i++)
{
Assert(DoLockModesConflict(intRelOpts[i].gen.lockmode,
@@ -640,6 +653,14 @@ initialize_reloptions(void)
j++;
}
+ for (i = 0; ternaryRelOpts[i].gen.name; i++)
+ {
+ relOpts[j] = &ternaryRelOpts[i].gen;
+ relOpts[j]->type = RELOPT_TYPE_TERNARY;
+ relOpts[j]->namelen = strlen(relOpts[j]->name);
+ j++;
+ }
+
for (i = 0; intRelOpts[i].gen.name; i++)
{
relOpts[j] = &intRelOpts[i].gen;
@@ -800,6 +821,9 @@ allocate_reloption(bits32 kinds, int type, const char *name, const char *desc,
case RELOPT_TYPE_BOOL:
size = sizeof(relopt_bool);
break;
+ case RELOPT_TYPE_TERNARY:
+ size = sizeof(relopt_ternary);
+ break;
case RELOPT_TYPE_INT:
size = sizeof(relopt_int);
break;
@@ -883,6 +907,54 @@ add_local_bool_reloption(local_relopts *relopts, const char *name,
add_local_reloption(relopts, (relopt_gen *) newoption, offset);
}
+/*
+ * init_ternary_reloption
+ * Allocate and initialize a new ternary reloption
+ */
+static relopt_ternary *
+init_ternary_reloption(bits32 kinds, const char *name, const char *desc,
+ ternary default_val, LOCKMODE lockmode)
+{
+ relopt_ternary *newoption;
+
+ newoption = (relopt_ternary *) allocate_reloption(kinds,
+ RELOPT_TYPE_TERNARY, name, desc, lockmode);
+ newoption->default_val = default_val;
+
+ return newoption;
+}
+
+/*
+ * add_ternary_reloption
+ * Add a new ternary reloption
+ */
+void
+add_ternary_reloption(bits32 kinds, const char *name, const char *desc,
+ ternary default_val, LOCKMODE lockmode)
+{
+ relopt_ternary *newoption = init_ternary_reloption(kinds, name, desc,
+ default_val, lockmode);
+
+ add_reloption((relopt_gen *) newoption);
+}
+
+/*
+ * add_local_ternary_reloption
+ * Add a new ternary local reloption
+ *
+ * 'offset' is offset of ternary-typed field.
+ */
+void
+add_local_ternary_reloption(local_relopts *relopts, const char *name,
+ const char *desc, ternary default_val,
+ int offset)
+{
+ relopt_ternary *newoption = init_ternary_reloption(RELOPT_KIND_LOCAL,
+ name, desc,
+ default_val, 0);
+
+ add_local_reloption(relopts, (relopt_gen *) newoption, offset);
+}
/*
* init_real_reloption
@@ -1617,6 +1689,18 @@ parse_one_reloption(relopt_value *option, char *text_str, int text_len,
option->gen->name, value)));
}
break;
+ case RELOPT_TYPE_TERNARY:
+ {
+ bool b;
+ parsed = parse_bool(value, &b);
+ option->values.ternary_val = b ? TERNARY_TRUE : TERNARY_FALSE;
+ if (validate && !parsed)
+ ereport(ERROR,
+ (errcode(ERRCODE_INVALID_PARAMETER_VALUE),
+ errmsg("invalid value for ternary option \"%s\": %s",
+ option->gen->name, value)));
+ }
+ break;
case RELOPT_TYPE_INT:
{
relopt_int *optint = (relopt_int *) option->gen;
@@ -1780,17 +1864,6 @@ fillRelOptions(void *rdopts, Size basesize,
char *itempos = ((char *) rdopts) + elems[j].offset;
char *string_val;
- /*
- * If isset_offset is provided, store whether the reloption is
- * set there.
- */
- if (elems[j].isset_offset > 0)
- {
- char *setpos = ((char *) rdopts) + elems[j].isset_offset;
-
- *(bool *) setpos = options[i].isset;
- }
-
switch (options[i].gen->type)
{
case RELOPT_TYPE_BOOL:
@@ -1798,6 +1871,11 @@ fillRelOptions(void *rdopts, Size basesize,
options[i].values.bool_val :
((relopt_bool *) options[i].gen)->default_val;
break;
+ case RELOPT_TYPE_TERNARY:
+ *(ternary *) itempos = options[i].isset ?
+ options[i].values.ternary_val :
+ ((relopt_ternary *) options[i].gen)->default_val;
+ break;
case RELOPT_TYPE_INT:
*(int *) itempos = options[i].isset ?
options[i].values.int_val :
@@ -1912,8 +1990,8 @@ default_reloptions(Datum reloptions, bool validate, relopt_kind kind)
offsetof(StdRdOptions, parallel_workers)},
{"vacuum_index_cleanup", RELOPT_TYPE_ENUM,
offsetof(StdRdOptions, vacuum_index_cleanup)},
- {"vacuum_truncate", RELOPT_TYPE_BOOL,
- offsetof(StdRdOptions, vacuum_truncate), offsetof(StdRdOptions, vacuum_truncate_set)},
+ {"vacuum_truncate", RELOPT_TYPE_TERNARY,
+ offsetof(StdRdOptions, vacuum_truncate)},
{"vacuum_max_eager_freeze_failure_rate", RELOPT_TYPE_REAL,
offsetof(StdRdOptions, vacuum_max_eager_freeze_failure_rate)}
};
@@ -1993,7 +2071,6 @@ build_local_reloptions(local_relopts *relopts, Datum options, bool validate)
elems[i].optname = opt->option->name;
elems[i].opttype = opt->option->type;
elems[i].offset = opt->offset;
- elems[i].isset_offset = 0; /* not supported for local relopts yet */
i++;
}
diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c
index 733ef40ae7..977babff54 100644
--- a/src/backend/commands/vacuum.c
+++ b/src/backend/commands/vacuum.c
@@ -2221,9 +2221,9 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams params,
{
StdRdOptions *opts = (StdRdOptions *) rel->rd_options;
- if (opts && opts->vacuum_truncate_set)
+ if (opts && opts->vacuum_truncate != TERNARY_UNSET)
{
- if (opts->vacuum_truncate)
+ if (opts->vacuum_truncate == TERNARY_TRUE)
params.truncate = VACOPTVALUE_ENABLED;
else
params.truncate = VACOPTVALUE_DISABLED;
diff --git a/src/include/access/reloptions.h b/src/include/access/reloptions.h
index a604a4702c..a436697658 100644
--- a/src/include/access/reloptions.h
+++ b/src/include/access/reloptions.h
@@ -29,6 +29,7 @@
typedef enum relopt_type
{
RELOPT_TYPE_BOOL,
+ RELOPT_TYPE_TERNARY, /* on, off, unset */
RELOPT_TYPE_INT,
RELOPT_TYPE_REAL,
RELOPT_TYPE_ENUM,
@@ -80,6 +81,7 @@ typedef struct relopt_value
union
{
bool bool_val;
+ ternary ternary_val;
int int_val;
double real_val;
int enum_val;
@@ -94,6 +96,12 @@ typedef struct relopt_bool
bool default_val;
} relopt_bool;
+typedef struct relopt_rernary
+{
+ relopt_gen gen;
+ int default_val;
+} relopt_ternary;
+
typedef struct relopt_int
{
relopt_gen gen;
@@ -152,19 +160,6 @@ typedef struct
const char *optname; /* option's name */
relopt_type opttype; /* option's datatype */
int offset; /* offset of field in result struct */
-
- /*
- * isset_offset is an optional offset of a field in the result struct that
- * stores whether the option is explicitly set for the relation or if it
- * just picked up the default value. In most cases, this can be
- * accomplished by giving the reloption a special out-of-range default
- * value (e.g., some integer reloptions use -2), but this isn't always
- * possible. For example, a Boolean reloption cannot be given an
- * out-of-range default, so we need another way to discover the source of
- * its value. This offset is only used if given a value greater than
- * zero.
- */
- int isset_offset;
} relopt_parse_elt;
/* Local reloption definition */
@@ -195,6 +190,8 @@ typedef struct local_relopts
extern relopt_kind add_reloption_kind(void);
extern void add_bool_reloption(bits32 kinds, const char *name, const char *desc,
bool default_val, LOCKMODE lockmode);
+extern void add_ternary_reloption(bits32 kinds, const char *name,
+ const char *desc, int default_val, LOCKMODE lockmode);
extern void add_int_reloption(bits32 kinds, const char *name, const char *desc,
int default_val, int min_val, int max_val,
LOCKMODE lockmode);
@@ -214,6 +211,9 @@ extern void register_reloptions_validator(local_relopts *relopts,
extern void add_local_bool_reloption(local_relopts *relopts, const char *name,
const char *desc, bool default_val,
int offset);
+extern void add_local_ternary_reloption(local_relopts *relopts,
+ const char *name, const char *desc,
+ ternary default_val, int offset);
extern void add_local_int_reloption(local_relopts *relopts, const char *name,
const char *desc, int default_val,
int min_val, int max_val, int offset);
diff --git a/src/include/c.h b/src/include/c.h
index 39022f8a9d..9c59ccccf8 100644
--- a/src/include/c.h
+++ b/src/include/c.h
@@ -488,6 +488,22 @@ typedef void (*pg_funcptr_t) (void);
#include <stdbool.h>
+/*
+ * ternary
+ * Boolean value with an extrea "unset" option
+ *
+ * Ternary data type is used in relation options that can be "true", "false" or
+ * "unset". Since relation options are used deep inside the PostgreSQL code,
+ * this type is declared globally.
+*/
+
+typedef enum ternary
+{
+ TERNARY_FALSE = 0,
+ TERNARY_TRUE = 1,
+ TERNARY_UNSET = -1
+} ternary;
+
/* ----------------------------------------------------------------
* Section 3: standard system types
diff --git a/src/include/utils/rel.h b/src/include/utils/rel.h
index b552359915..7346d618f9 100644
--- a/src/include/utils/rel.h
+++ b/src/include/utils/rel.h
@@ -346,8 +346,7 @@ typedef struct StdRdOptions
bool user_catalog_table; /* use as an additional catalog relation */
int parallel_workers; /* max number of parallel workers */
StdRdOptIndexCleanup vacuum_index_cleanup; /* controls index vacuuming */
- bool vacuum_truncate; /* enables vacuum to truncate a relation */
- bool vacuum_truncate_set; /* whether vacuum_truncate is set */
+ ternary vacuum_truncate; /* enables vacuum to truncate a relation */
/*
* Fraction of pages in a relation that vacuum can eagerly scan and fail
--
2.39.2
--nextPart4155600.3ZeAukHxDK
Content-Disposition: attachment;
filename="v2a-0003-Add-alias-to-be-used-as-unset-state.patch"
Content-Transfer-Encoding: 7Bit
Content-Type: text/x-patch; charset="unicode-2-0-utf-8";
name="v2a-0003-Add-alias-to-be-used-as-unset-state.patch"
^ permalink raw reply [nested|flat] 868+ messages in thread
* [PATCH v2a 2/4] Introduce ternary reloptions
@ 2025-09-04 14:41 Nikolay Shaplov <[email protected]>
0 siblings, 0 replies; 868+ messages in thread
From: Nikolay Shaplov @ 2025-09-04 14:41 UTC (permalink / raw)
Introduce ternary reloption as a replacement for current `vacuum_truncate`
implementation. Remove `vacuum_truncate_set` additional flag and using
`TERNARY_UNSET` value instead.
---
src/backend/access/common/reloptions.c | 129 ++++++++++++++++++++-----
src/backend/commands/vacuum.c | 4 +-
src/include/access/reloptions.h | 26 ++---
src/include/c.h | 16 +++
src/include/utils/rel.h | 3 +-
5 files changed, 135 insertions(+), 43 deletions(-)
diff --git a/src/backend/access/common/reloptions.c b/src/backend/access/common/reloptions.c
index 0af3fea68f..f543f11001 100644
--- a/src/backend/access/common/reloptions.c
+++ b/src/backend/access/common/reloptions.c
@@ -40,9 +40,9 @@
*
* To add an option:
*
- * (i) decide on a type (bool, integer, real, enum, string), name, default
- * value, upper and lower bounds (if applicable); for strings, consider a
- * validation routine.
+ * (i) decide on a type (bool, ternary, integer, real, enum, string), name,
+ * default value, upper and lower bounds (if applicable); for strings,
+ * consider a validation routine.
* (ii) add a record below (or use add_<type>_reloption).
* (iii) add it to the appropriate options struct (perhaps StdRdOptions)
* (iv) add it to the appropriate handling routine (perhaps
@@ -147,15 +147,6 @@ static relopt_bool boolRelOpts[] =
},
false
},
- {
- {
- "vacuum_truncate",
- "Enables vacuum to truncate empty pages at the end of this table",
- RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
- ShareUpdateExclusiveLock
- },
- true
- },
{
{
"deduplicate_items",
@@ -170,6 +161,21 @@ static relopt_bool boolRelOpts[] =
{{NULL}}
};
+static relopt_ternary ternaryRelOpts[] =
+{
+ {
+ {
+ "vacuum_truncate",
+ "Enables vacuum to truncate empty pages at the end of this table",
+ RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
+ ShareUpdateExclusiveLock
+ },
+ TERNARY_UNSET
+ },
+ /* list terminator */
+ {{NULL}}
+};
+
static relopt_int intRelOpts[] =
{
{
@@ -600,6 +606,13 @@ initialize_reloptions(void)
boolRelOpts[i].gen.lockmode));
j++;
}
+ for (i = 0; ternaryRelOpts[i].gen.name; i++)
+ {
+ Assert(DoLockModesConflict(ternaryRelOpts[i].gen.lockmode,
+ ternaryRelOpts[i].gen.lockmode));
+ j++;
+ }
+
for (i = 0; intRelOpts[i].gen.name; i++)
{
Assert(DoLockModesConflict(intRelOpts[i].gen.lockmode,
@@ -640,6 +653,14 @@ initialize_reloptions(void)
j++;
}
+ for (i = 0; ternaryRelOpts[i].gen.name; i++)
+ {
+ relOpts[j] = &ternaryRelOpts[i].gen;
+ relOpts[j]->type = RELOPT_TYPE_TERNARY;
+ relOpts[j]->namelen = strlen(relOpts[j]->name);
+ j++;
+ }
+
for (i = 0; intRelOpts[i].gen.name; i++)
{
relOpts[j] = &intRelOpts[i].gen;
@@ -800,6 +821,9 @@ allocate_reloption(bits32 kinds, int type, const char *name, const char *desc,
case RELOPT_TYPE_BOOL:
size = sizeof(relopt_bool);
break;
+ case RELOPT_TYPE_TERNARY:
+ size = sizeof(relopt_ternary);
+ break;
case RELOPT_TYPE_INT:
size = sizeof(relopt_int);
break;
@@ -883,6 +907,54 @@ add_local_bool_reloption(local_relopts *relopts, const char *name,
add_local_reloption(relopts, (relopt_gen *) newoption, offset);
}
+/*
+ * init_ternary_reloption
+ * Allocate and initialize a new ternary reloption
+ */
+static relopt_ternary *
+init_ternary_reloption(bits32 kinds, const char *name, const char *desc,
+ ternary default_val, LOCKMODE lockmode)
+{
+ relopt_ternary *newoption;
+
+ newoption = (relopt_ternary *) allocate_reloption(kinds,
+ RELOPT_TYPE_TERNARY, name, desc, lockmode);
+ newoption->default_val = default_val;
+
+ return newoption;
+}
+
+/*
+ * add_ternary_reloption
+ * Add a new ternary reloption
+ */
+void
+add_ternary_reloption(bits32 kinds, const char *name, const char *desc,
+ ternary default_val, LOCKMODE lockmode)
+{
+ relopt_ternary *newoption = init_ternary_reloption(kinds, name, desc,
+ default_val, lockmode);
+
+ add_reloption((relopt_gen *) newoption);
+}
+
+/*
+ * add_local_ternary_reloption
+ * Add a new ternary local reloption
+ *
+ * 'offset' is offset of ternary-typed field.
+ */
+void
+add_local_ternary_reloption(local_relopts *relopts, const char *name,
+ const char *desc, ternary default_val,
+ int offset)
+{
+ relopt_ternary *newoption = init_ternary_reloption(RELOPT_KIND_LOCAL,
+ name, desc,
+ default_val, 0);
+
+ add_local_reloption(relopts, (relopt_gen *) newoption, offset);
+}
/*
* init_real_reloption
@@ -1617,6 +1689,18 @@ parse_one_reloption(relopt_value *option, char *text_str, int text_len,
option->gen->name, value)));
}
break;
+ case RELOPT_TYPE_TERNARY:
+ {
+ bool b;
+ parsed = parse_bool(value, &b);
+ option->values.ternary_val = b ? TERNARY_TRUE : TERNARY_FALSE;
+ if (validate && !parsed)
+ ereport(ERROR,
+ (errcode(ERRCODE_INVALID_PARAMETER_VALUE),
+ errmsg("invalid value for ternary option \"%s\": %s",
+ option->gen->name, value)));
+ }
+ break;
case RELOPT_TYPE_INT:
{
relopt_int *optint = (relopt_int *) option->gen;
@@ -1780,17 +1864,6 @@ fillRelOptions(void *rdopts, Size basesize,
char *itempos = ((char *) rdopts) + elems[j].offset;
char *string_val;
- /*
- * If isset_offset is provided, store whether the reloption is
- * set there.
- */
- if (elems[j].isset_offset > 0)
- {
- char *setpos = ((char *) rdopts) + elems[j].isset_offset;
-
- *(bool *) setpos = options[i].isset;
- }
-
switch (options[i].gen->type)
{
case RELOPT_TYPE_BOOL:
@@ -1798,6 +1871,11 @@ fillRelOptions(void *rdopts, Size basesize,
options[i].values.bool_val :
((relopt_bool *) options[i].gen)->default_val;
break;
+ case RELOPT_TYPE_TERNARY:
+ *(ternary *) itempos = options[i].isset ?
+ options[i].values.ternary_val :
+ ((relopt_ternary *) options[i].gen)->default_val;
+ break;
case RELOPT_TYPE_INT:
*(int *) itempos = options[i].isset ?
options[i].values.int_val :
@@ -1912,8 +1990,8 @@ default_reloptions(Datum reloptions, bool validate, relopt_kind kind)
offsetof(StdRdOptions, parallel_workers)},
{"vacuum_index_cleanup", RELOPT_TYPE_ENUM,
offsetof(StdRdOptions, vacuum_index_cleanup)},
- {"vacuum_truncate", RELOPT_TYPE_BOOL,
- offsetof(StdRdOptions, vacuum_truncate), offsetof(StdRdOptions, vacuum_truncate_set)},
+ {"vacuum_truncate", RELOPT_TYPE_TERNARY,
+ offsetof(StdRdOptions, vacuum_truncate)},
{"vacuum_max_eager_freeze_failure_rate", RELOPT_TYPE_REAL,
offsetof(StdRdOptions, vacuum_max_eager_freeze_failure_rate)}
};
@@ -1993,7 +2071,6 @@ build_local_reloptions(local_relopts *relopts, Datum options, bool validate)
elems[i].optname = opt->option->name;
elems[i].opttype = opt->option->type;
elems[i].offset = opt->offset;
- elems[i].isset_offset = 0; /* not supported for local relopts yet */
i++;
}
diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c
index 733ef40ae7..977babff54 100644
--- a/src/backend/commands/vacuum.c
+++ b/src/backend/commands/vacuum.c
@@ -2221,9 +2221,9 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams params,
{
StdRdOptions *opts = (StdRdOptions *) rel->rd_options;
- if (opts && opts->vacuum_truncate_set)
+ if (opts && opts->vacuum_truncate != TERNARY_UNSET)
{
- if (opts->vacuum_truncate)
+ if (opts->vacuum_truncate == TERNARY_TRUE)
params.truncate = VACOPTVALUE_ENABLED;
else
params.truncate = VACOPTVALUE_DISABLED;
diff --git a/src/include/access/reloptions.h b/src/include/access/reloptions.h
index a604a4702c..a436697658 100644
--- a/src/include/access/reloptions.h
+++ b/src/include/access/reloptions.h
@@ -29,6 +29,7 @@
typedef enum relopt_type
{
RELOPT_TYPE_BOOL,
+ RELOPT_TYPE_TERNARY, /* on, off, unset */
RELOPT_TYPE_INT,
RELOPT_TYPE_REAL,
RELOPT_TYPE_ENUM,
@@ -80,6 +81,7 @@ typedef struct relopt_value
union
{
bool bool_val;
+ ternary ternary_val;
int int_val;
double real_val;
int enum_val;
@@ -94,6 +96,12 @@ typedef struct relopt_bool
bool default_val;
} relopt_bool;
+typedef struct relopt_rernary
+{
+ relopt_gen gen;
+ int default_val;
+} relopt_ternary;
+
typedef struct relopt_int
{
relopt_gen gen;
@@ -152,19 +160,6 @@ typedef struct
const char *optname; /* option's name */
relopt_type opttype; /* option's datatype */
int offset; /* offset of field in result struct */
-
- /*
- * isset_offset is an optional offset of a field in the result struct that
- * stores whether the option is explicitly set for the relation or if it
- * just picked up the default value. In most cases, this can be
- * accomplished by giving the reloption a special out-of-range default
- * value (e.g., some integer reloptions use -2), but this isn't always
- * possible. For example, a Boolean reloption cannot be given an
- * out-of-range default, so we need another way to discover the source of
- * its value. This offset is only used if given a value greater than
- * zero.
- */
- int isset_offset;
} relopt_parse_elt;
/* Local reloption definition */
@@ -195,6 +190,8 @@ typedef struct local_relopts
extern relopt_kind add_reloption_kind(void);
extern void add_bool_reloption(bits32 kinds, const char *name, const char *desc,
bool default_val, LOCKMODE lockmode);
+extern void add_ternary_reloption(bits32 kinds, const char *name,
+ const char *desc, int default_val, LOCKMODE lockmode);
extern void add_int_reloption(bits32 kinds, const char *name, const char *desc,
int default_val, int min_val, int max_val,
LOCKMODE lockmode);
@@ -214,6 +211,9 @@ extern void register_reloptions_validator(local_relopts *relopts,
extern void add_local_bool_reloption(local_relopts *relopts, const char *name,
const char *desc, bool default_val,
int offset);
+extern void add_local_ternary_reloption(local_relopts *relopts,
+ const char *name, const char *desc,
+ ternary default_val, int offset);
extern void add_local_int_reloption(local_relopts *relopts, const char *name,
const char *desc, int default_val,
int min_val, int max_val, int offset);
diff --git a/src/include/c.h b/src/include/c.h
index 39022f8a9d..9c59ccccf8 100644
--- a/src/include/c.h
+++ b/src/include/c.h
@@ -488,6 +488,22 @@ typedef void (*pg_funcptr_t) (void);
#include <stdbool.h>
+/*
+ * ternary
+ * Boolean value with an extrea "unset" option
+ *
+ * Ternary data type is used in relation options that can be "true", "false" or
+ * "unset". Since relation options are used deep inside the PostgreSQL code,
+ * this type is declared globally.
+*/
+
+typedef enum ternary
+{
+ TERNARY_FALSE = 0,
+ TERNARY_TRUE = 1,
+ TERNARY_UNSET = -1
+} ternary;
+
/* ----------------------------------------------------------------
* Section 3: standard system types
diff --git a/src/include/utils/rel.h b/src/include/utils/rel.h
index b552359915..7346d618f9 100644
--- a/src/include/utils/rel.h
+++ b/src/include/utils/rel.h
@@ -346,8 +346,7 @@ typedef struct StdRdOptions
bool user_catalog_table; /* use as an additional catalog relation */
int parallel_workers; /* max number of parallel workers */
StdRdOptIndexCleanup vacuum_index_cleanup; /* controls index vacuuming */
- bool vacuum_truncate; /* enables vacuum to truncate a relation */
- bool vacuum_truncate_set; /* whether vacuum_truncate is set */
+ ternary vacuum_truncate; /* enables vacuum to truncate a relation */
/*
* Fraction of pages in a relation that vacuum can eagerly scan and fail
--
2.39.2
--nextPart4155600.3ZeAukHxDK
Content-Disposition: attachment;
filename="v2a-0003-Add-alias-to-be-used-as-unset-state.patch"
Content-Transfer-Encoding: 7Bit
Content-Type: text/x-patch; charset="unicode-2-0-utf-8";
name="v2a-0003-Add-alias-to-be-used-as-unset-state.patch"
^ permalink raw reply [nested|flat] 868+ messages in thread
* [PATCH v2 2/4] Introduce ternary reloptions
@ 2025-09-04 14:41 Nikolay Shaplov <[email protected]>
0 siblings, 0 replies; 868+ messages in thread
From: Nikolay Shaplov @ 2025-09-04 14:41 UTC (permalink / raw)
Introduce ternary reloption as a replacement for current `vacuum_truncate`
implementation. Remove `vacuum_truncate_set` additional flag and using
`TERNARY_UNSET` value instead.
---
src/backend/access/common/reloptions.c | 129 ++++++++++++++++++++-----
src/backend/commands/vacuum.c | 4 +-
src/include/access/reloptions.h | 26 ++---
src/include/c.h | 16 +++
src/include/utils/rel.h | 3 +-
5 files changed, 135 insertions(+), 43 deletions(-)
diff --git a/src/backend/access/common/reloptions.c b/src/backend/access/common/reloptions.c
index 0af3fea68f..f543f11001 100644
--- a/src/backend/access/common/reloptions.c
+++ b/src/backend/access/common/reloptions.c
@@ -40,9 +40,9 @@
*
* To add an option:
*
- * (i) decide on a type (bool, integer, real, enum, string), name, default
- * value, upper and lower bounds (if applicable); for strings, consider a
- * validation routine.
+ * (i) decide on a type (bool, ternary, integer, real, enum, string), name,
+ * default value, upper and lower bounds (if applicable); for strings,
+ * consider a validation routine.
* (ii) add a record below (or use add_<type>_reloption).
* (iii) add it to the appropriate options struct (perhaps StdRdOptions)
* (iv) add it to the appropriate handling routine (perhaps
@@ -147,15 +147,6 @@ static relopt_bool boolRelOpts[] =
},
false
},
- {
- {
- "vacuum_truncate",
- "Enables vacuum to truncate empty pages at the end of this table",
- RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
- ShareUpdateExclusiveLock
- },
- true
- },
{
{
"deduplicate_items",
@@ -170,6 +161,21 @@ static relopt_bool boolRelOpts[] =
{{NULL}}
};
+static relopt_ternary ternaryRelOpts[] =
+{
+ {
+ {
+ "vacuum_truncate",
+ "Enables vacuum to truncate empty pages at the end of this table",
+ RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
+ ShareUpdateExclusiveLock
+ },
+ TERNARY_UNSET
+ },
+ /* list terminator */
+ {{NULL}}
+};
+
static relopt_int intRelOpts[] =
{
{
@@ -600,6 +606,13 @@ initialize_reloptions(void)
boolRelOpts[i].gen.lockmode));
j++;
}
+ for (i = 0; ternaryRelOpts[i].gen.name; i++)
+ {
+ Assert(DoLockModesConflict(ternaryRelOpts[i].gen.lockmode,
+ ternaryRelOpts[i].gen.lockmode));
+ j++;
+ }
+
for (i = 0; intRelOpts[i].gen.name; i++)
{
Assert(DoLockModesConflict(intRelOpts[i].gen.lockmode,
@@ -640,6 +653,14 @@ initialize_reloptions(void)
j++;
}
+ for (i = 0; ternaryRelOpts[i].gen.name; i++)
+ {
+ relOpts[j] = &ternaryRelOpts[i].gen;
+ relOpts[j]->type = RELOPT_TYPE_TERNARY;
+ relOpts[j]->namelen = strlen(relOpts[j]->name);
+ j++;
+ }
+
for (i = 0; intRelOpts[i].gen.name; i++)
{
relOpts[j] = &intRelOpts[i].gen;
@@ -800,6 +821,9 @@ allocate_reloption(bits32 kinds, int type, const char *name, const char *desc,
case RELOPT_TYPE_BOOL:
size = sizeof(relopt_bool);
break;
+ case RELOPT_TYPE_TERNARY:
+ size = sizeof(relopt_ternary);
+ break;
case RELOPT_TYPE_INT:
size = sizeof(relopt_int);
break;
@@ -883,6 +907,54 @@ add_local_bool_reloption(local_relopts *relopts, const char *name,
add_local_reloption(relopts, (relopt_gen *) newoption, offset);
}
+/*
+ * init_ternary_reloption
+ * Allocate and initialize a new ternary reloption
+ */
+static relopt_ternary *
+init_ternary_reloption(bits32 kinds, const char *name, const char *desc,
+ ternary default_val, LOCKMODE lockmode)
+{
+ relopt_ternary *newoption;
+
+ newoption = (relopt_ternary *) allocate_reloption(kinds,
+ RELOPT_TYPE_TERNARY, name, desc, lockmode);
+ newoption->default_val = default_val;
+
+ return newoption;
+}
+
+/*
+ * add_ternary_reloption
+ * Add a new ternary reloption
+ */
+void
+add_ternary_reloption(bits32 kinds, const char *name, const char *desc,
+ ternary default_val, LOCKMODE lockmode)
+{
+ relopt_ternary *newoption = init_ternary_reloption(kinds, name, desc,
+ default_val, lockmode);
+
+ add_reloption((relopt_gen *) newoption);
+}
+
+/*
+ * add_local_ternary_reloption
+ * Add a new ternary local reloption
+ *
+ * 'offset' is offset of ternary-typed field.
+ */
+void
+add_local_ternary_reloption(local_relopts *relopts, const char *name,
+ const char *desc, ternary default_val,
+ int offset)
+{
+ relopt_ternary *newoption = init_ternary_reloption(RELOPT_KIND_LOCAL,
+ name, desc,
+ default_val, 0);
+
+ add_local_reloption(relopts, (relopt_gen *) newoption, offset);
+}
/*
* init_real_reloption
@@ -1617,6 +1689,18 @@ parse_one_reloption(relopt_value *option, char *text_str, int text_len,
option->gen->name, value)));
}
break;
+ case RELOPT_TYPE_TERNARY:
+ {
+ bool b;
+ parsed = parse_bool(value, &b);
+ option->values.ternary_val = b ? TERNARY_TRUE : TERNARY_FALSE;
+ if (validate && !parsed)
+ ereport(ERROR,
+ (errcode(ERRCODE_INVALID_PARAMETER_VALUE),
+ errmsg("invalid value for ternary option \"%s\": %s",
+ option->gen->name, value)));
+ }
+ break;
case RELOPT_TYPE_INT:
{
relopt_int *optint = (relopt_int *) option->gen;
@@ -1780,17 +1864,6 @@ fillRelOptions(void *rdopts, Size basesize,
char *itempos = ((char *) rdopts) + elems[j].offset;
char *string_val;
- /*
- * If isset_offset is provided, store whether the reloption is
- * set there.
- */
- if (elems[j].isset_offset > 0)
- {
- char *setpos = ((char *) rdopts) + elems[j].isset_offset;
-
- *(bool *) setpos = options[i].isset;
- }
-
switch (options[i].gen->type)
{
case RELOPT_TYPE_BOOL:
@@ -1798,6 +1871,11 @@ fillRelOptions(void *rdopts, Size basesize,
options[i].values.bool_val :
((relopt_bool *) options[i].gen)->default_val;
break;
+ case RELOPT_TYPE_TERNARY:
+ *(ternary *) itempos = options[i].isset ?
+ options[i].values.ternary_val :
+ ((relopt_ternary *) options[i].gen)->default_val;
+ break;
case RELOPT_TYPE_INT:
*(int *) itempos = options[i].isset ?
options[i].values.int_val :
@@ -1912,8 +1990,8 @@ default_reloptions(Datum reloptions, bool validate, relopt_kind kind)
offsetof(StdRdOptions, parallel_workers)},
{"vacuum_index_cleanup", RELOPT_TYPE_ENUM,
offsetof(StdRdOptions, vacuum_index_cleanup)},
- {"vacuum_truncate", RELOPT_TYPE_BOOL,
- offsetof(StdRdOptions, vacuum_truncate), offsetof(StdRdOptions, vacuum_truncate_set)},
+ {"vacuum_truncate", RELOPT_TYPE_TERNARY,
+ offsetof(StdRdOptions, vacuum_truncate)},
{"vacuum_max_eager_freeze_failure_rate", RELOPT_TYPE_REAL,
offsetof(StdRdOptions, vacuum_max_eager_freeze_failure_rate)}
};
@@ -1993,7 +2071,6 @@ build_local_reloptions(local_relopts *relopts, Datum options, bool validate)
elems[i].optname = opt->option->name;
elems[i].opttype = opt->option->type;
elems[i].offset = opt->offset;
- elems[i].isset_offset = 0; /* not supported for local relopts yet */
i++;
}
diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c
index 733ef40ae7..977babff54 100644
--- a/src/backend/commands/vacuum.c
+++ b/src/backend/commands/vacuum.c
@@ -2221,9 +2221,9 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams params,
{
StdRdOptions *opts = (StdRdOptions *) rel->rd_options;
- if (opts && opts->vacuum_truncate_set)
+ if (opts && opts->vacuum_truncate != TERNARY_UNSET)
{
- if (opts->vacuum_truncate)
+ if (opts->vacuum_truncate == TERNARY_TRUE)
params.truncate = VACOPTVALUE_ENABLED;
else
params.truncate = VACOPTVALUE_DISABLED;
diff --git a/src/include/access/reloptions.h b/src/include/access/reloptions.h
index a604a4702c..a436697658 100644
--- a/src/include/access/reloptions.h
+++ b/src/include/access/reloptions.h
@@ -29,6 +29,7 @@
typedef enum relopt_type
{
RELOPT_TYPE_BOOL,
+ RELOPT_TYPE_TERNARY, /* on, off, unset */
RELOPT_TYPE_INT,
RELOPT_TYPE_REAL,
RELOPT_TYPE_ENUM,
@@ -80,6 +81,7 @@ typedef struct relopt_value
union
{
bool bool_val;
+ ternary ternary_val;
int int_val;
double real_val;
int enum_val;
@@ -94,6 +96,12 @@ typedef struct relopt_bool
bool default_val;
} relopt_bool;
+typedef struct relopt_rernary
+{
+ relopt_gen gen;
+ int default_val;
+} relopt_ternary;
+
typedef struct relopt_int
{
relopt_gen gen;
@@ -152,19 +160,6 @@ typedef struct
const char *optname; /* option's name */
relopt_type opttype; /* option's datatype */
int offset; /* offset of field in result struct */
-
- /*
- * isset_offset is an optional offset of a field in the result struct that
- * stores whether the option is explicitly set for the relation or if it
- * just picked up the default value. In most cases, this can be
- * accomplished by giving the reloption a special out-of-range default
- * value (e.g., some integer reloptions use -2), but this isn't always
- * possible. For example, a Boolean reloption cannot be given an
- * out-of-range default, so we need another way to discover the source of
- * its value. This offset is only used if given a value greater than
- * zero.
- */
- int isset_offset;
} relopt_parse_elt;
/* Local reloption definition */
@@ -195,6 +190,8 @@ typedef struct local_relopts
extern relopt_kind add_reloption_kind(void);
extern void add_bool_reloption(bits32 kinds, const char *name, const char *desc,
bool default_val, LOCKMODE lockmode);
+extern void add_ternary_reloption(bits32 kinds, const char *name,
+ const char *desc, int default_val, LOCKMODE lockmode);
extern void add_int_reloption(bits32 kinds, const char *name, const char *desc,
int default_val, int min_val, int max_val,
LOCKMODE lockmode);
@@ -214,6 +211,9 @@ extern void register_reloptions_validator(local_relopts *relopts,
extern void add_local_bool_reloption(local_relopts *relopts, const char *name,
const char *desc, bool default_val,
int offset);
+extern void add_local_ternary_reloption(local_relopts *relopts,
+ const char *name, const char *desc,
+ ternary default_val, int offset);
extern void add_local_int_reloption(local_relopts *relopts, const char *name,
const char *desc, int default_val,
int min_val, int max_val, int offset);
diff --git a/src/include/c.h b/src/include/c.h
index 39022f8a9d..9c59ccccf8 100644
--- a/src/include/c.h
+++ b/src/include/c.h
@@ -488,6 +488,22 @@ typedef void (*pg_funcptr_t) (void);
#include <stdbool.h>
+/*
+ * ternary
+ * Boolean value with an extrea "unset" option
+ *
+ * Ternary data type is used in relation options that can be "true", "false" or
+ * "unset". Since relation options are used deep inside the PostgreSQL code,
+ * this type is declared globally.
+*/
+
+typedef enum ternary
+{
+ TERNARY_FALSE = 0,
+ TERNARY_TRUE = 1,
+ TERNARY_UNSET = -1
+} ternary;
+
/* ----------------------------------------------------------------
* Section 3: standard system types
diff --git a/src/include/utils/rel.h b/src/include/utils/rel.h
index b552359915..7346d618f9 100644
--- a/src/include/utils/rel.h
+++ b/src/include/utils/rel.h
@@ -346,8 +346,7 @@ typedef struct StdRdOptions
bool user_catalog_table; /* use as an additional catalog relation */
int parallel_workers; /* max number of parallel workers */
StdRdOptIndexCleanup vacuum_index_cleanup; /* controls index vacuuming */
- bool vacuum_truncate; /* enables vacuum to truncate a relation */
- bool vacuum_truncate_set; /* whether vacuum_truncate is set */
+ ternary vacuum_truncate; /* enables vacuum to truncate a relation */
/*
* Fraction of pages in a relation that vacuum can eagerly scan and fail
--
2.39.2
--nextPart5129932.5fSG56mABF
Content-Disposition: attachment;
filename="v2-0003-Add-alias-to-be-used-as-unset-state.patch"
Content-Transfer-Encoding: 7Bit
Content-Type: text/x-patch; charset="unicode-2-0-utf-8";
name="v2-0003-Add-alias-to-be-used-as-unset-state.patch"
^ permalink raw reply [nested|flat] 868+ messages in thread
* [PATCH v2a 2/4] Introduce ternary reloptions
@ 2025-09-04 14:41 Nikolay Shaplov <[email protected]>
0 siblings, 0 replies; 868+ messages in thread
From: Nikolay Shaplov @ 2025-09-04 14:41 UTC (permalink / raw)
Introduce ternary reloption as a replacement for current `vacuum_truncate`
implementation. Remove `vacuum_truncate_set` additional flag and using
`TERNARY_UNSET` value instead.
---
src/backend/access/common/reloptions.c | 129 ++++++++++++++++++++-----
src/backend/commands/vacuum.c | 4 +-
src/include/access/reloptions.h | 26 ++---
src/include/c.h | 16 +++
src/include/utils/rel.h | 3 +-
5 files changed, 135 insertions(+), 43 deletions(-)
diff --git a/src/backend/access/common/reloptions.c b/src/backend/access/common/reloptions.c
index 0af3fea68f..f543f11001 100644
--- a/src/backend/access/common/reloptions.c
+++ b/src/backend/access/common/reloptions.c
@@ -40,9 +40,9 @@
*
* To add an option:
*
- * (i) decide on a type (bool, integer, real, enum, string), name, default
- * value, upper and lower bounds (if applicable); for strings, consider a
- * validation routine.
+ * (i) decide on a type (bool, ternary, integer, real, enum, string), name,
+ * default value, upper and lower bounds (if applicable); for strings,
+ * consider a validation routine.
* (ii) add a record below (or use add_<type>_reloption).
* (iii) add it to the appropriate options struct (perhaps StdRdOptions)
* (iv) add it to the appropriate handling routine (perhaps
@@ -147,15 +147,6 @@ static relopt_bool boolRelOpts[] =
},
false
},
- {
- {
- "vacuum_truncate",
- "Enables vacuum to truncate empty pages at the end of this table",
- RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
- ShareUpdateExclusiveLock
- },
- true
- },
{
{
"deduplicate_items",
@@ -170,6 +161,21 @@ static relopt_bool boolRelOpts[] =
{{NULL}}
};
+static relopt_ternary ternaryRelOpts[] =
+{
+ {
+ {
+ "vacuum_truncate",
+ "Enables vacuum to truncate empty pages at the end of this table",
+ RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
+ ShareUpdateExclusiveLock
+ },
+ TERNARY_UNSET
+ },
+ /* list terminator */
+ {{NULL}}
+};
+
static relopt_int intRelOpts[] =
{
{
@@ -600,6 +606,13 @@ initialize_reloptions(void)
boolRelOpts[i].gen.lockmode));
j++;
}
+ for (i = 0; ternaryRelOpts[i].gen.name; i++)
+ {
+ Assert(DoLockModesConflict(ternaryRelOpts[i].gen.lockmode,
+ ternaryRelOpts[i].gen.lockmode));
+ j++;
+ }
+
for (i = 0; intRelOpts[i].gen.name; i++)
{
Assert(DoLockModesConflict(intRelOpts[i].gen.lockmode,
@@ -640,6 +653,14 @@ initialize_reloptions(void)
j++;
}
+ for (i = 0; ternaryRelOpts[i].gen.name; i++)
+ {
+ relOpts[j] = &ternaryRelOpts[i].gen;
+ relOpts[j]->type = RELOPT_TYPE_TERNARY;
+ relOpts[j]->namelen = strlen(relOpts[j]->name);
+ j++;
+ }
+
for (i = 0; intRelOpts[i].gen.name; i++)
{
relOpts[j] = &intRelOpts[i].gen;
@@ -800,6 +821,9 @@ allocate_reloption(bits32 kinds, int type, const char *name, const char *desc,
case RELOPT_TYPE_BOOL:
size = sizeof(relopt_bool);
break;
+ case RELOPT_TYPE_TERNARY:
+ size = sizeof(relopt_ternary);
+ break;
case RELOPT_TYPE_INT:
size = sizeof(relopt_int);
break;
@@ -883,6 +907,54 @@ add_local_bool_reloption(local_relopts *relopts, const char *name,
add_local_reloption(relopts, (relopt_gen *) newoption, offset);
}
+/*
+ * init_ternary_reloption
+ * Allocate and initialize a new ternary reloption
+ */
+static relopt_ternary *
+init_ternary_reloption(bits32 kinds, const char *name, const char *desc,
+ ternary default_val, LOCKMODE lockmode)
+{
+ relopt_ternary *newoption;
+
+ newoption = (relopt_ternary *) allocate_reloption(kinds,
+ RELOPT_TYPE_TERNARY, name, desc, lockmode);
+ newoption->default_val = default_val;
+
+ return newoption;
+}
+
+/*
+ * add_ternary_reloption
+ * Add a new ternary reloption
+ */
+void
+add_ternary_reloption(bits32 kinds, const char *name, const char *desc,
+ ternary default_val, LOCKMODE lockmode)
+{
+ relopt_ternary *newoption = init_ternary_reloption(kinds, name, desc,
+ default_val, lockmode);
+
+ add_reloption((relopt_gen *) newoption);
+}
+
+/*
+ * add_local_ternary_reloption
+ * Add a new ternary local reloption
+ *
+ * 'offset' is offset of ternary-typed field.
+ */
+void
+add_local_ternary_reloption(local_relopts *relopts, const char *name,
+ const char *desc, ternary default_val,
+ int offset)
+{
+ relopt_ternary *newoption = init_ternary_reloption(RELOPT_KIND_LOCAL,
+ name, desc,
+ default_val, 0);
+
+ add_local_reloption(relopts, (relopt_gen *) newoption, offset);
+}
/*
* init_real_reloption
@@ -1617,6 +1689,18 @@ parse_one_reloption(relopt_value *option, char *text_str, int text_len,
option->gen->name, value)));
}
break;
+ case RELOPT_TYPE_TERNARY:
+ {
+ bool b;
+ parsed = parse_bool(value, &b);
+ option->values.ternary_val = b ? TERNARY_TRUE : TERNARY_FALSE;
+ if (validate && !parsed)
+ ereport(ERROR,
+ (errcode(ERRCODE_INVALID_PARAMETER_VALUE),
+ errmsg("invalid value for ternary option \"%s\": %s",
+ option->gen->name, value)));
+ }
+ break;
case RELOPT_TYPE_INT:
{
relopt_int *optint = (relopt_int *) option->gen;
@@ -1780,17 +1864,6 @@ fillRelOptions(void *rdopts, Size basesize,
char *itempos = ((char *) rdopts) + elems[j].offset;
char *string_val;
- /*
- * If isset_offset is provided, store whether the reloption is
- * set there.
- */
- if (elems[j].isset_offset > 0)
- {
- char *setpos = ((char *) rdopts) + elems[j].isset_offset;
-
- *(bool *) setpos = options[i].isset;
- }
-
switch (options[i].gen->type)
{
case RELOPT_TYPE_BOOL:
@@ -1798,6 +1871,11 @@ fillRelOptions(void *rdopts, Size basesize,
options[i].values.bool_val :
((relopt_bool *) options[i].gen)->default_val;
break;
+ case RELOPT_TYPE_TERNARY:
+ *(ternary *) itempos = options[i].isset ?
+ options[i].values.ternary_val :
+ ((relopt_ternary *) options[i].gen)->default_val;
+ break;
case RELOPT_TYPE_INT:
*(int *) itempos = options[i].isset ?
options[i].values.int_val :
@@ -1912,8 +1990,8 @@ default_reloptions(Datum reloptions, bool validate, relopt_kind kind)
offsetof(StdRdOptions, parallel_workers)},
{"vacuum_index_cleanup", RELOPT_TYPE_ENUM,
offsetof(StdRdOptions, vacuum_index_cleanup)},
- {"vacuum_truncate", RELOPT_TYPE_BOOL,
- offsetof(StdRdOptions, vacuum_truncate), offsetof(StdRdOptions, vacuum_truncate_set)},
+ {"vacuum_truncate", RELOPT_TYPE_TERNARY,
+ offsetof(StdRdOptions, vacuum_truncate)},
{"vacuum_max_eager_freeze_failure_rate", RELOPT_TYPE_REAL,
offsetof(StdRdOptions, vacuum_max_eager_freeze_failure_rate)}
};
@@ -1993,7 +2071,6 @@ build_local_reloptions(local_relopts *relopts, Datum options, bool validate)
elems[i].optname = opt->option->name;
elems[i].opttype = opt->option->type;
elems[i].offset = opt->offset;
- elems[i].isset_offset = 0; /* not supported for local relopts yet */
i++;
}
diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c
index 733ef40ae7..977babff54 100644
--- a/src/backend/commands/vacuum.c
+++ b/src/backend/commands/vacuum.c
@@ -2221,9 +2221,9 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams params,
{
StdRdOptions *opts = (StdRdOptions *) rel->rd_options;
- if (opts && opts->vacuum_truncate_set)
+ if (opts && opts->vacuum_truncate != TERNARY_UNSET)
{
- if (opts->vacuum_truncate)
+ if (opts->vacuum_truncate == TERNARY_TRUE)
params.truncate = VACOPTVALUE_ENABLED;
else
params.truncate = VACOPTVALUE_DISABLED;
diff --git a/src/include/access/reloptions.h b/src/include/access/reloptions.h
index a604a4702c..a436697658 100644
--- a/src/include/access/reloptions.h
+++ b/src/include/access/reloptions.h
@@ -29,6 +29,7 @@
typedef enum relopt_type
{
RELOPT_TYPE_BOOL,
+ RELOPT_TYPE_TERNARY, /* on, off, unset */
RELOPT_TYPE_INT,
RELOPT_TYPE_REAL,
RELOPT_TYPE_ENUM,
@@ -80,6 +81,7 @@ typedef struct relopt_value
union
{
bool bool_val;
+ ternary ternary_val;
int int_val;
double real_val;
int enum_val;
@@ -94,6 +96,12 @@ typedef struct relopt_bool
bool default_val;
} relopt_bool;
+typedef struct relopt_rernary
+{
+ relopt_gen gen;
+ int default_val;
+} relopt_ternary;
+
typedef struct relopt_int
{
relopt_gen gen;
@@ -152,19 +160,6 @@ typedef struct
const char *optname; /* option's name */
relopt_type opttype; /* option's datatype */
int offset; /* offset of field in result struct */
-
- /*
- * isset_offset is an optional offset of a field in the result struct that
- * stores whether the option is explicitly set for the relation or if it
- * just picked up the default value. In most cases, this can be
- * accomplished by giving the reloption a special out-of-range default
- * value (e.g., some integer reloptions use -2), but this isn't always
- * possible. For example, a Boolean reloption cannot be given an
- * out-of-range default, so we need another way to discover the source of
- * its value. This offset is only used if given a value greater than
- * zero.
- */
- int isset_offset;
} relopt_parse_elt;
/* Local reloption definition */
@@ -195,6 +190,8 @@ typedef struct local_relopts
extern relopt_kind add_reloption_kind(void);
extern void add_bool_reloption(bits32 kinds, const char *name, const char *desc,
bool default_val, LOCKMODE lockmode);
+extern void add_ternary_reloption(bits32 kinds, const char *name,
+ const char *desc, int default_val, LOCKMODE lockmode);
extern void add_int_reloption(bits32 kinds, const char *name, const char *desc,
int default_val, int min_val, int max_val,
LOCKMODE lockmode);
@@ -214,6 +211,9 @@ extern void register_reloptions_validator(local_relopts *relopts,
extern void add_local_bool_reloption(local_relopts *relopts, const char *name,
const char *desc, bool default_val,
int offset);
+extern void add_local_ternary_reloption(local_relopts *relopts,
+ const char *name, const char *desc,
+ ternary default_val, int offset);
extern void add_local_int_reloption(local_relopts *relopts, const char *name,
const char *desc, int default_val,
int min_val, int max_val, int offset);
diff --git a/src/include/c.h b/src/include/c.h
index 39022f8a9d..9c59ccccf8 100644
--- a/src/include/c.h
+++ b/src/include/c.h
@@ -488,6 +488,22 @@ typedef void (*pg_funcptr_t) (void);
#include <stdbool.h>
+/*
+ * ternary
+ * Boolean value with an extrea "unset" option
+ *
+ * Ternary data type is used in relation options that can be "true", "false" or
+ * "unset". Since relation options are used deep inside the PostgreSQL code,
+ * this type is declared globally.
+*/
+
+typedef enum ternary
+{
+ TERNARY_FALSE = 0,
+ TERNARY_TRUE = 1,
+ TERNARY_UNSET = -1
+} ternary;
+
/* ----------------------------------------------------------------
* Section 3: standard system types
diff --git a/src/include/utils/rel.h b/src/include/utils/rel.h
index b552359915..7346d618f9 100644
--- a/src/include/utils/rel.h
+++ b/src/include/utils/rel.h
@@ -346,8 +346,7 @@ typedef struct StdRdOptions
bool user_catalog_table; /* use as an additional catalog relation */
int parallel_workers; /* max number of parallel workers */
StdRdOptIndexCleanup vacuum_index_cleanup; /* controls index vacuuming */
- bool vacuum_truncate; /* enables vacuum to truncate a relation */
- bool vacuum_truncate_set; /* whether vacuum_truncate is set */
+ ternary vacuum_truncate; /* enables vacuum to truncate a relation */
/*
* Fraction of pages in a relation that vacuum can eagerly scan and fail
--
2.39.2
--nextPart4155600.3ZeAukHxDK
Content-Disposition: attachment;
filename="v2a-0003-Add-alias-to-be-used-as-unset-state.patch"
Content-Transfer-Encoding: 7Bit
Content-Type: text/x-patch; charset="unicode-2-0-utf-8";
name="v2a-0003-Add-alias-to-be-used-as-unset-state.patch"
^ permalink raw reply [nested|flat] 868+ messages in thread
* [PATCH v2a 2/4] Introduce ternary reloptions
@ 2025-09-04 14:41 Nikolay Shaplov <[email protected]>
0 siblings, 0 replies; 868+ messages in thread
From: Nikolay Shaplov @ 2025-09-04 14:41 UTC (permalink / raw)
Introduce ternary reloption as a replacement for current `vacuum_truncate`
implementation. Remove `vacuum_truncate_set` additional flag and using
`TERNARY_UNSET` value instead.
---
src/backend/access/common/reloptions.c | 129 ++++++++++++++++++++-----
src/backend/commands/vacuum.c | 4 +-
src/include/access/reloptions.h | 26 ++---
src/include/c.h | 16 +++
src/include/utils/rel.h | 3 +-
5 files changed, 135 insertions(+), 43 deletions(-)
diff --git a/src/backend/access/common/reloptions.c b/src/backend/access/common/reloptions.c
index 0af3fea68f..f543f11001 100644
--- a/src/backend/access/common/reloptions.c
+++ b/src/backend/access/common/reloptions.c
@@ -40,9 +40,9 @@
*
* To add an option:
*
- * (i) decide on a type (bool, integer, real, enum, string), name, default
- * value, upper and lower bounds (if applicable); for strings, consider a
- * validation routine.
+ * (i) decide on a type (bool, ternary, integer, real, enum, string), name,
+ * default value, upper and lower bounds (if applicable); for strings,
+ * consider a validation routine.
* (ii) add a record below (or use add_<type>_reloption).
* (iii) add it to the appropriate options struct (perhaps StdRdOptions)
* (iv) add it to the appropriate handling routine (perhaps
@@ -147,15 +147,6 @@ static relopt_bool boolRelOpts[] =
},
false
},
- {
- {
- "vacuum_truncate",
- "Enables vacuum to truncate empty pages at the end of this table",
- RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
- ShareUpdateExclusiveLock
- },
- true
- },
{
{
"deduplicate_items",
@@ -170,6 +161,21 @@ static relopt_bool boolRelOpts[] =
{{NULL}}
};
+static relopt_ternary ternaryRelOpts[] =
+{
+ {
+ {
+ "vacuum_truncate",
+ "Enables vacuum to truncate empty pages at the end of this table",
+ RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
+ ShareUpdateExclusiveLock
+ },
+ TERNARY_UNSET
+ },
+ /* list terminator */
+ {{NULL}}
+};
+
static relopt_int intRelOpts[] =
{
{
@@ -600,6 +606,13 @@ initialize_reloptions(void)
boolRelOpts[i].gen.lockmode));
j++;
}
+ for (i = 0; ternaryRelOpts[i].gen.name; i++)
+ {
+ Assert(DoLockModesConflict(ternaryRelOpts[i].gen.lockmode,
+ ternaryRelOpts[i].gen.lockmode));
+ j++;
+ }
+
for (i = 0; intRelOpts[i].gen.name; i++)
{
Assert(DoLockModesConflict(intRelOpts[i].gen.lockmode,
@@ -640,6 +653,14 @@ initialize_reloptions(void)
j++;
}
+ for (i = 0; ternaryRelOpts[i].gen.name; i++)
+ {
+ relOpts[j] = &ternaryRelOpts[i].gen;
+ relOpts[j]->type = RELOPT_TYPE_TERNARY;
+ relOpts[j]->namelen = strlen(relOpts[j]->name);
+ j++;
+ }
+
for (i = 0; intRelOpts[i].gen.name; i++)
{
relOpts[j] = &intRelOpts[i].gen;
@@ -800,6 +821,9 @@ allocate_reloption(bits32 kinds, int type, const char *name, const char *desc,
case RELOPT_TYPE_BOOL:
size = sizeof(relopt_bool);
break;
+ case RELOPT_TYPE_TERNARY:
+ size = sizeof(relopt_ternary);
+ break;
case RELOPT_TYPE_INT:
size = sizeof(relopt_int);
break;
@@ -883,6 +907,54 @@ add_local_bool_reloption(local_relopts *relopts, const char *name,
add_local_reloption(relopts, (relopt_gen *) newoption, offset);
}
+/*
+ * init_ternary_reloption
+ * Allocate and initialize a new ternary reloption
+ */
+static relopt_ternary *
+init_ternary_reloption(bits32 kinds, const char *name, const char *desc,
+ ternary default_val, LOCKMODE lockmode)
+{
+ relopt_ternary *newoption;
+
+ newoption = (relopt_ternary *) allocate_reloption(kinds,
+ RELOPT_TYPE_TERNARY, name, desc, lockmode);
+ newoption->default_val = default_val;
+
+ return newoption;
+}
+
+/*
+ * add_ternary_reloption
+ * Add a new ternary reloption
+ */
+void
+add_ternary_reloption(bits32 kinds, const char *name, const char *desc,
+ ternary default_val, LOCKMODE lockmode)
+{
+ relopt_ternary *newoption = init_ternary_reloption(kinds, name, desc,
+ default_val, lockmode);
+
+ add_reloption((relopt_gen *) newoption);
+}
+
+/*
+ * add_local_ternary_reloption
+ * Add a new ternary local reloption
+ *
+ * 'offset' is offset of ternary-typed field.
+ */
+void
+add_local_ternary_reloption(local_relopts *relopts, const char *name,
+ const char *desc, ternary default_val,
+ int offset)
+{
+ relopt_ternary *newoption = init_ternary_reloption(RELOPT_KIND_LOCAL,
+ name, desc,
+ default_val, 0);
+
+ add_local_reloption(relopts, (relopt_gen *) newoption, offset);
+}
/*
* init_real_reloption
@@ -1617,6 +1689,18 @@ parse_one_reloption(relopt_value *option, char *text_str, int text_len,
option->gen->name, value)));
}
break;
+ case RELOPT_TYPE_TERNARY:
+ {
+ bool b;
+ parsed = parse_bool(value, &b);
+ option->values.ternary_val = b ? TERNARY_TRUE : TERNARY_FALSE;
+ if (validate && !parsed)
+ ereport(ERROR,
+ (errcode(ERRCODE_INVALID_PARAMETER_VALUE),
+ errmsg("invalid value for ternary option \"%s\": %s",
+ option->gen->name, value)));
+ }
+ break;
case RELOPT_TYPE_INT:
{
relopt_int *optint = (relopt_int *) option->gen;
@@ -1780,17 +1864,6 @@ fillRelOptions(void *rdopts, Size basesize,
char *itempos = ((char *) rdopts) + elems[j].offset;
char *string_val;
- /*
- * If isset_offset is provided, store whether the reloption is
- * set there.
- */
- if (elems[j].isset_offset > 0)
- {
- char *setpos = ((char *) rdopts) + elems[j].isset_offset;
-
- *(bool *) setpos = options[i].isset;
- }
-
switch (options[i].gen->type)
{
case RELOPT_TYPE_BOOL:
@@ -1798,6 +1871,11 @@ fillRelOptions(void *rdopts, Size basesize,
options[i].values.bool_val :
((relopt_bool *) options[i].gen)->default_val;
break;
+ case RELOPT_TYPE_TERNARY:
+ *(ternary *) itempos = options[i].isset ?
+ options[i].values.ternary_val :
+ ((relopt_ternary *) options[i].gen)->default_val;
+ break;
case RELOPT_TYPE_INT:
*(int *) itempos = options[i].isset ?
options[i].values.int_val :
@@ -1912,8 +1990,8 @@ default_reloptions(Datum reloptions, bool validate, relopt_kind kind)
offsetof(StdRdOptions, parallel_workers)},
{"vacuum_index_cleanup", RELOPT_TYPE_ENUM,
offsetof(StdRdOptions, vacuum_index_cleanup)},
- {"vacuum_truncate", RELOPT_TYPE_BOOL,
- offsetof(StdRdOptions, vacuum_truncate), offsetof(StdRdOptions, vacuum_truncate_set)},
+ {"vacuum_truncate", RELOPT_TYPE_TERNARY,
+ offsetof(StdRdOptions, vacuum_truncate)},
{"vacuum_max_eager_freeze_failure_rate", RELOPT_TYPE_REAL,
offsetof(StdRdOptions, vacuum_max_eager_freeze_failure_rate)}
};
@@ -1993,7 +2071,6 @@ build_local_reloptions(local_relopts *relopts, Datum options, bool validate)
elems[i].optname = opt->option->name;
elems[i].opttype = opt->option->type;
elems[i].offset = opt->offset;
- elems[i].isset_offset = 0; /* not supported for local relopts yet */
i++;
}
diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c
index 733ef40ae7..977babff54 100644
--- a/src/backend/commands/vacuum.c
+++ b/src/backend/commands/vacuum.c
@@ -2221,9 +2221,9 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams params,
{
StdRdOptions *opts = (StdRdOptions *) rel->rd_options;
- if (opts && opts->vacuum_truncate_set)
+ if (opts && opts->vacuum_truncate != TERNARY_UNSET)
{
- if (opts->vacuum_truncate)
+ if (opts->vacuum_truncate == TERNARY_TRUE)
params.truncate = VACOPTVALUE_ENABLED;
else
params.truncate = VACOPTVALUE_DISABLED;
diff --git a/src/include/access/reloptions.h b/src/include/access/reloptions.h
index a604a4702c..a436697658 100644
--- a/src/include/access/reloptions.h
+++ b/src/include/access/reloptions.h
@@ -29,6 +29,7 @@
typedef enum relopt_type
{
RELOPT_TYPE_BOOL,
+ RELOPT_TYPE_TERNARY, /* on, off, unset */
RELOPT_TYPE_INT,
RELOPT_TYPE_REAL,
RELOPT_TYPE_ENUM,
@@ -80,6 +81,7 @@ typedef struct relopt_value
union
{
bool bool_val;
+ ternary ternary_val;
int int_val;
double real_val;
int enum_val;
@@ -94,6 +96,12 @@ typedef struct relopt_bool
bool default_val;
} relopt_bool;
+typedef struct relopt_rernary
+{
+ relopt_gen gen;
+ int default_val;
+} relopt_ternary;
+
typedef struct relopt_int
{
relopt_gen gen;
@@ -152,19 +160,6 @@ typedef struct
const char *optname; /* option's name */
relopt_type opttype; /* option's datatype */
int offset; /* offset of field in result struct */
-
- /*
- * isset_offset is an optional offset of a field in the result struct that
- * stores whether the option is explicitly set for the relation or if it
- * just picked up the default value. In most cases, this can be
- * accomplished by giving the reloption a special out-of-range default
- * value (e.g., some integer reloptions use -2), but this isn't always
- * possible. For example, a Boolean reloption cannot be given an
- * out-of-range default, so we need another way to discover the source of
- * its value. This offset is only used if given a value greater than
- * zero.
- */
- int isset_offset;
} relopt_parse_elt;
/* Local reloption definition */
@@ -195,6 +190,8 @@ typedef struct local_relopts
extern relopt_kind add_reloption_kind(void);
extern void add_bool_reloption(bits32 kinds, const char *name, const char *desc,
bool default_val, LOCKMODE lockmode);
+extern void add_ternary_reloption(bits32 kinds, const char *name,
+ const char *desc, int default_val, LOCKMODE lockmode);
extern void add_int_reloption(bits32 kinds, const char *name, const char *desc,
int default_val, int min_val, int max_val,
LOCKMODE lockmode);
@@ -214,6 +211,9 @@ extern void register_reloptions_validator(local_relopts *relopts,
extern void add_local_bool_reloption(local_relopts *relopts, const char *name,
const char *desc, bool default_val,
int offset);
+extern void add_local_ternary_reloption(local_relopts *relopts,
+ const char *name, const char *desc,
+ ternary default_val, int offset);
extern void add_local_int_reloption(local_relopts *relopts, const char *name,
const char *desc, int default_val,
int min_val, int max_val, int offset);
diff --git a/src/include/c.h b/src/include/c.h
index 39022f8a9d..9c59ccccf8 100644
--- a/src/include/c.h
+++ b/src/include/c.h
@@ -488,6 +488,22 @@ typedef void (*pg_funcptr_t) (void);
#include <stdbool.h>
+/*
+ * ternary
+ * Boolean value with an extrea "unset" option
+ *
+ * Ternary data type is used in relation options that can be "true", "false" or
+ * "unset". Since relation options are used deep inside the PostgreSQL code,
+ * this type is declared globally.
+*/
+
+typedef enum ternary
+{
+ TERNARY_FALSE = 0,
+ TERNARY_TRUE = 1,
+ TERNARY_UNSET = -1
+} ternary;
+
/* ----------------------------------------------------------------
* Section 3: standard system types
diff --git a/src/include/utils/rel.h b/src/include/utils/rel.h
index b552359915..7346d618f9 100644
--- a/src/include/utils/rel.h
+++ b/src/include/utils/rel.h
@@ -346,8 +346,7 @@ typedef struct StdRdOptions
bool user_catalog_table; /* use as an additional catalog relation */
int parallel_workers; /* max number of parallel workers */
StdRdOptIndexCleanup vacuum_index_cleanup; /* controls index vacuuming */
- bool vacuum_truncate; /* enables vacuum to truncate a relation */
- bool vacuum_truncate_set; /* whether vacuum_truncate is set */
+ ternary vacuum_truncate; /* enables vacuum to truncate a relation */
/*
* Fraction of pages in a relation that vacuum can eagerly scan and fail
--
2.39.2
--nextPart4155600.3ZeAukHxDK
Content-Disposition: attachment;
filename="v2a-0003-Add-alias-to-be-used-as-unset-state.patch"
Content-Transfer-Encoding: 7Bit
Content-Type: text/x-patch; charset="unicode-2-0-utf-8";
name="v2a-0003-Add-alias-to-be-used-as-unset-state.patch"
^ permalink raw reply [nested|flat] 868+ messages in thread
* [PATCH v2 2/4] Introduce ternary reloptions
@ 2025-09-04 14:41 Nikolay Shaplov <[email protected]>
0 siblings, 0 replies; 868+ messages in thread
From: Nikolay Shaplov @ 2025-09-04 14:41 UTC (permalink / raw)
Introduce ternary reloption as a replacement for current `vacuum_truncate`
implementation. Remove `vacuum_truncate_set` additional flag and using
`TERNARY_UNSET` value instead.
---
src/backend/access/common/reloptions.c | 129 ++++++++++++++++++++-----
src/backend/commands/vacuum.c | 4 +-
src/include/access/reloptions.h | 26 ++---
src/include/c.h | 16 +++
src/include/utils/rel.h | 3 +-
5 files changed, 135 insertions(+), 43 deletions(-)
diff --git a/src/backend/access/common/reloptions.c b/src/backend/access/common/reloptions.c
index 0af3fea68f..f543f11001 100644
--- a/src/backend/access/common/reloptions.c
+++ b/src/backend/access/common/reloptions.c
@@ -40,9 +40,9 @@
*
* To add an option:
*
- * (i) decide on a type (bool, integer, real, enum, string), name, default
- * value, upper and lower bounds (if applicable); for strings, consider a
- * validation routine.
+ * (i) decide on a type (bool, ternary, integer, real, enum, string), name,
+ * default value, upper and lower bounds (if applicable); for strings,
+ * consider a validation routine.
* (ii) add a record below (or use add_<type>_reloption).
* (iii) add it to the appropriate options struct (perhaps StdRdOptions)
* (iv) add it to the appropriate handling routine (perhaps
@@ -147,15 +147,6 @@ static relopt_bool boolRelOpts[] =
},
false
},
- {
- {
- "vacuum_truncate",
- "Enables vacuum to truncate empty pages at the end of this table",
- RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
- ShareUpdateExclusiveLock
- },
- true
- },
{
{
"deduplicate_items",
@@ -170,6 +161,21 @@ static relopt_bool boolRelOpts[] =
{{NULL}}
};
+static relopt_ternary ternaryRelOpts[] =
+{
+ {
+ {
+ "vacuum_truncate",
+ "Enables vacuum to truncate empty pages at the end of this table",
+ RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
+ ShareUpdateExclusiveLock
+ },
+ TERNARY_UNSET
+ },
+ /* list terminator */
+ {{NULL}}
+};
+
static relopt_int intRelOpts[] =
{
{
@@ -600,6 +606,13 @@ initialize_reloptions(void)
boolRelOpts[i].gen.lockmode));
j++;
}
+ for (i = 0; ternaryRelOpts[i].gen.name; i++)
+ {
+ Assert(DoLockModesConflict(ternaryRelOpts[i].gen.lockmode,
+ ternaryRelOpts[i].gen.lockmode));
+ j++;
+ }
+
for (i = 0; intRelOpts[i].gen.name; i++)
{
Assert(DoLockModesConflict(intRelOpts[i].gen.lockmode,
@@ -640,6 +653,14 @@ initialize_reloptions(void)
j++;
}
+ for (i = 0; ternaryRelOpts[i].gen.name; i++)
+ {
+ relOpts[j] = &ternaryRelOpts[i].gen;
+ relOpts[j]->type = RELOPT_TYPE_TERNARY;
+ relOpts[j]->namelen = strlen(relOpts[j]->name);
+ j++;
+ }
+
for (i = 0; intRelOpts[i].gen.name; i++)
{
relOpts[j] = &intRelOpts[i].gen;
@@ -800,6 +821,9 @@ allocate_reloption(bits32 kinds, int type, const char *name, const char *desc,
case RELOPT_TYPE_BOOL:
size = sizeof(relopt_bool);
break;
+ case RELOPT_TYPE_TERNARY:
+ size = sizeof(relopt_ternary);
+ break;
case RELOPT_TYPE_INT:
size = sizeof(relopt_int);
break;
@@ -883,6 +907,54 @@ add_local_bool_reloption(local_relopts *relopts, const char *name,
add_local_reloption(relopts, (relopt_gen *) newoption, offset);
}
+/*
+ * init_ternary_reloption
+ * Allocate and initialize a new ternary reloption
+ */
+static relopt_ternary *
+init_ternary_reloption(bits32 kinds, const char *name, const char *desc,
+ ternary default_val, LOCKMODE lockmode)
+{
+ relopt_ternary *newoption;
+
+ newoption = (relopt_ternary *) allocate_reloption(kinds,
+ RELOPT_TYPE_TERNARY, name, desc, lockmode);
+ newoption->default_val = default_val;
+
+ return newoption;
+}
+
+/*
+ * add_ternary_reloption
+ * Add a new ternary reloption
+ */
+void
+add_ternary_reloption(bits32 kinds, const char *name, const char *desc,
+ ternary default_val, LOCKMODE lockmode)
+{
+ relopt_ternary *newoption = init_ternary_reloption(kinds, name, desc,
+ default_val, lockmode);
+
+ add_reloption((relopt_gen *) newoption);
+}
+
+/*
+ * add_local_ternary_reloption
+ * Add a new ternary local reloption
+ *
+ * 'offset' is offset of ternary-typed field.
+ */
+void
+add_local_ternary_reloption(local_relopts *relopts, const char *name,
+ const char *desc, ternary default_val,
+ int offset)
+{
+ relopt_ternary *newoption = init_ternary_reloption(RELOPT_KIND_LOCAL,
+ name, desc,
+ default_val, 0);
+
+ add_local_reloption(relopts, (relopt_gen *) newoption, offset);
+}
/*
* init_real_reloption
@@ -1617,6 +1689,18 @@ parse_one_reloption(relopt_value *option, char *text_str, int text_len,
option->gen->name, value)));
}
break;
+ case RELOPT_TYPE_TERNARY:
+ {
+ bool b;
+ parsed = parse_bool(value, &b);
+ option->values.ternary_val = b ? TERNARY_TRUE : TERNARY_FALSE;
+ if (validate && !parsed)
+ ereport(ERROR,
+ (errcode(ERRCODE_INVALID_PARAMETER_VALUE),
+ errmsg("invalid value for ternary option \"%s\": %s",
+ option->gen->name, value)));
+ }
+ break;
case RELOPT_TYPE_INT:
{
relopt_int *optint = (relopt_int *) option->gen;
@@ -1780,17 +1864,6 @@ fillRelOptions(void *rdopts, Size basesize,
char *itempos = ((char *) rdopts) + elems[j].offset;
char *string_val;
- /*
- * If isset_offset is provided, store whether the reloption is
- * set there.
- */
- if (elems[j].isset_offset > 0)
- {
- char *setpos = ((char *) rdopts) + elems[j].isset_offset;
-
- *(bool *) setpos = options[i].isset;
- }
-
switch (options[i].gen->type)
{
case RELOPT_TYPE_BOOL:
@@ -1798,6 +1871,11 @@ fillRelOptions(void *rdopts, Size basesize,
options[i].values.bool_val :
((relopt_bool *) options[i].gen)->default_val;
break;
+ case RELOPT_TYPE_TERNARY:
+ *(ternary *) itempos = options[i].isset ?
+ options[i].values.ternary_val :
+ ((relopt_ternary *) options[i].gen)->default_val;
+ break;
case RELOPT_TYPE_INT:
*(int *) itempos = options[i].isset ?
options[i].values.int_val :
@@ -1912,8 +1990,8 @@ default_reloptions(Datum reloptions, bool validate, relopt_kind kind)
offsetof(StdRdOptions, parallel_workers)},
{"vacuum_index_cleanup", RELOPT_TYPE_ENUM,
offsetof(StdRdOptions, vacuum_index_cleanup)},
- {"vacuum_truncate", RELOPT_TYPE_BOOL,
- offsetof(StdRdOptions, vacuum_truncate), offsetof(StdRdOptions, vacuum_truncate_set)},
+ {"vacuum_truncate", RELOPT_TYPE_TERNARY,
+ offsetof(StdRdOptions, vacuum_truncate)},
{"vacuum_max_eager_freeze_failure_rate", RELOPT_TYPE_REAL,
offsetof(StdRdOptions, vacuum_max_eager_freeze_failure_rate)}
};
@@ -1993,7 +2071,6 @@ build_local_reloptions(local_relopts *relopts, Datum options, bool validate)
elems[i].optname = opt->option->name;
elems[i].opttype = opt->option->type;
elems[i].offset = opt->offset;
- elems[i].isset_offset = 0; /* not supported for local relopts yet */
i++;
}
diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c
index 733ef40ae7..977babff54 100644
--- a/src/backend/commands/vacuum.c
+++ b/src/backend/commands/vacuum.c
@@ -2221,9 +2221,9 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams params,
{
StdRdOptions *opts = (StdRdOptions *) rel->rd_options;
- if (opts && opts->vacuum_truncate_set)
+ if (opts && opts->vacuum_truncate != TERNARY_UNSET)
{
- if (opts->vacuum_truncate)
+ if (opts->vacuum_truncate == TERNARY_TRUE)
params.truncate = VACOPTVALUE_ENABLED;
else
params.truncate = VACOPTVALUE_DISABLED;
diff --git a/src/include/access/reloptions.h b/src/include/access/reloptions.h
index a604a4702c..a436697658 100644
--- a/src/include/access/reloptions.h
+++ b/src/include/access/reloptions.h
@@ -29,6 +29,7 @@
typedef enum relopt_type
{
RELOPT_TYPE_BOOL,
+ RELOPT_TYPE_TERNARY, /* on, off, unset */
RELOPT_TYPE_INT,
RELOPT_TYPE_REAL,
RELOPT_TYPE_ENUM,
@@ -80,6 +81,7 @@ typedef struct relopt_value
union
{
bool bool_val;
+ ternary ternary_val;
int int_val;
double real_val;
int enum_val;
@@ -94,6 +96,12 @@ typedef struct relopt_bool
bool default_val;
} relopt_bool;
+typedef struct relopt_rernary
+{
+ relopt_gen gen;
+ int default_val;
+} relopt_ternary;
+
typedef struct relopt_int
{
relopt_gen gen;
@@ -152,19 +160,6 @@ typedef struct
const char *optname; /* option's name */
relopt_type opttype; /* option's datatype */
int offset; /* offset of field in result struct */
-
- /*
- * isset_offset is an optional offset of a field in the result struct that
- * stores whether the option is explicitly set for the relation or if it
- * just picked up the default value. In most cases, this can be
- * accomplished by giving the reloption a special out-of-range default
- * value (e.g., some integer reloptions use -2), but this isn't always
- * possible. For example, a Boolean reloption cannot be given an
- * out-of-range default, so we need another way to discover the source of
- * its value. This offset is only used if given a value greater than
- * zero.
- */
- int isset_offset;
} relopt_parse_elt;
/* Local reloption definition */
@@ -195,6 +190,8 @@ typedef struct local_relopts
extern relopt_kind add_reloption_kind(void);
extern void add_bool_reloption(bits32 kinds, const char *name, const char *desc,
bool default_val, LOCKMODE lockmode);
+extern void add_ternary_reloption(bits32 kinds, const char *name,
+ const char *desc, int default_val, LOCKMODE lockmode);
extern void add_int_reloption(bits32 kinds, const char *name, const char *desc,
int default_val, int min_val, int max_val,
LOCKMODE lockmode);
@@ -214,6 +211,9 @@ extern void register_reloptions_validator(local_relopts *relopts,
extern void add_local_bool_reloption(local_relopts *relopts, const char *name,
const char *desc, bool default_val,
int offset);
+extern void add_local_ternary_reloption(local_relopts *relopts,
+ const char *name, const char *desc,
+ ternary default_val, int offset);
extern void add_local_int_reloption(local_relopts *relopts, const char *name,
const char *desc, int default_val,
int min_val, int max_val, int offset);
diff --git a/src/include/c.h b/src/include/c.h
index 39022f8a9d..9c59ccccf8 100644
--- a/src/include/c.h
+++ b/src/include/c.h
@@ -488,6 +488,22 @@ typedef void (*pg_funcptr_t) (void);
#include <stdbool.h>
+/*
+ * ternary
+ * Boolean value with an extrea "unset" option
+ *
+ * Ternary data type is used in relation options that can be "true", "false" or
+ * "unset". Since relation options are used deep inside the PostgreSQL code,
+ * this type is declared globally.
+*/
+
+typedef enum ternary
+{
+ TERNARY_FALSE = 0,
+ TERNARY_TRUE = 1,
+ TERNARY_UNSET = -1
+} ternary;
+
/* ----------------------------------------------------------------
* Section 3: standard system types
diff --git a/src/include/utils/rel.h b/src/include/utils/rel.h
index b552359915..7346d618f9 100644
--- a/src/include/utils/rel.h
+++ b/src/include/utils/rel.h
@@ -346,8 +346,7 @@ typedef struct StdRdOptions
bool user_catalog_table; /* use as an additional catalog relation */
int parallel_workers; /* max number of parallel workers */
StdRdOptIndexCleanup vacuum_index_cleanup; /* controls index vacuuming */
- bool vacuum_truncate; /* enables vacuum to truncate a relation */
- bool vacuum_truncate_set; /* whether vacuum_truncate is set */
+ ternary vacuum_truncate; /* enables vacuum to truncate a relation */
/*
* Fraction of pages in a relation that vacuum can eagerly scan and fail
--
2.39.2
--nextPart5129932.5fSG56mABF
Content-Disposition: attachment;
filename="v2-0003-Add-alias-to-be-used-as-unset-state.patch"
Content-Transfer-Encoding: 7Bit
Content-Type: text/x-patch; charset="unicode-2-0-utf-8";
name="v2-0003-Add-alias-to-be-used-as-unset-state.patch"
^ permalink raw reply [nested|flat] 868+ messages in thread
* [PATCH v2a 2/4] Introduce ternary reloptions
@ 2025-09-04 14:41 Nikolay Shaplov <[email protected]>
0 siblings, 0 replies; 868+ messages in thread
From: Nikolay Shaplov @ 2025-09-04 14:41 UTC (permalink / raw)
Introduce ternary reloption as a replacement for current `vacuum_truncate`
implementation. Remove `vacuum_truncate_set` additional flag and using
`TERNARY_UNSET` value instead.
---
src/backend/access/common/reloptions.c | 129 ++++++++++++++++++++-----
src/backend/commands/vacuum.c | 4 +-
src/include/access/reloptions.h | 26 ++---
src/include/c.h | 16 +++
src/include/utils/rel.h | 3 +-
5 files changed, 135 insertions(+), 43 deletions(-)
diff --git a/src/backend/access/common/reloptions.c b/src/backend/access/common/reloptions.c
index 0af3fea68f..f543f11001 100644
--- a/src/backend/access/common/reloptions.c
+++ b/src/backend/access/common/reloptions.c
@@ -40,9 +40,9 @@
*
* To add an option:
*
- * (i) decide on a type (bool, integer, real, enum, string), name, default
- * value, upper and lower bounds (if applicable); for strings, consider a
- * validation routine.
+ * (i) decide on a type (bool, ternary, integer, real, enum, string), name,
+ * default value, upper and lower bounds (if applicable); for strings,
+ * consider a validation routine.
* (ii) add a record below (or use add_<type>_reloption).
* (iii) add it to the appropriate options struct (perhaps StdRdOptions)
* (iv) add it to the appropriate handling routine (perhaps
@@ -147,15 +147,6 @@ static relopt_bool boolRelOpts[] =
},
false
},
- {
- {
- "vacuum_truncate",
- "Enables vacuum to truncate empty pages at the end of this table",
- RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
- ShareUpdateExclusiveLock
- },
- true
- },
{
{
"deduplicate_items",
@@ -170,6 +161,21 @@ static relopt_bool boolRelOpts[] =
{{NULL}}
};
+static relopt_ternary ternaryRelOpts[] =
+{
+ {
+ {
+ "vacuum_truncate",
+ "Enables vacuum to truncate empty pages at the end of this table",
+ RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
+ ShareUpdateExclusiveLock
+ },
+ TERNARY_UNSET
+ },
+ /* list terminator */
+ {{NULL}}
+};
+
static relopt_int intRelOpts[] =
{
{
@@ -600,6 +606,13 @@ initialize_reloptions(void)
boolRelOpts[i].gen.lockmode));
j++;
}
+ for (i = 0; ternaryRelOpts[i].gen.name; i++)
+ {
+ Assert(DoLockModesConflict(ternaryRelOpts[i].gen.lockmode,
+ ternaryRelOpts[i].gen.lockmode));
+ j++;
+ }
+
for (i = 0; intRelOpts[i].gen.name; i++)
{
Assert(DoLockModesConflict(intRelOpts[i].gen.lockmode,
@@ -640,6 +653,14 @@ initialize_reloptions(void)
j++;
}
+ for (i = 0; ternaryRelOpts[i].gen.name; i++)
+ {
+ relOpts[j] = &ternaryRelOpts[i].gen;
+ relOpts[j]->type = RELOPT_TYPE_TERNARY;
+ relOpts[j]->namelen = strlen(relOpts[j]->name);
+ j++;
+ }
+
for (i = 0; intRelOpts[i].gen.name; i++)
{
relOpts[j] = &intRelOpts[i].gen;
@@ -800,6 +821,9 @@ allocate_reloption(bits32 kinds, int type, const char *name, const char *desc,
case RELOPT_TYPE_BOOL:
size = sizeof(relopt_bool);
break;
+ case RELOPT_TYPE_TERNARY:
+ size = sizeof(relopt_ternary);
+ break;
case RELOPT_TYPE_INT:
size = sizeof(relopt_int);
break;
@@ -883,6 +907,54 @@ add_local_bool_reloption(local_relopts *relopts, const char *name,
add_local_reloption(relopts, (relopt_gen *) newoption, offset);
}
+/*
+ * init_ternary_reloption
+ * Allocate and initialize a new ternary reloption
+ */
+static relopt_ternary *
+init_ternary_reloption(bits32 kinds, const char *name, const char *desc,
+ ternary default_val, LOCKMODE lockmode)
+{
+ relopt_ternary *newoption;
+
+ newoption = (relopt_ternary *) allocate_reloption(kinds,
+ RELOPT_TYPE_TERNARY, name, desc, lockmode);
+ newoption->default_val = default_val;
+
+ return newoption;
+}
+
+/*
+ * add_ternary_reloption
+ * Add a new ternary reloption
+ */
+void
+add_ternary_reloption(bits32 kinds, const char *name, const char *desc,
+ ternary default_val, LOCKMODE lockmode)
+{
+ relopt_ternary *newoption = init_ternary_reloption(kinds, name, desc,
+ default_val, lockmode);
+
+ add_reloption((relopt_gen *) newoption);
+}
+
+/*
+ * add_local_ternary_reloption
+ * Add a new ternary local reloption
+ *
+ * 'offset' is offset of ternary-typed field.
+ */
+void
+add_local_ternary_reloption(local_relopts *relopts, const char *name,
+ const char *desc, ternary default_val,
+ int offset)
+{
+ relopt_ternary *newoption = init_ternary_reloption(RELOPT_KIND_LOCAL,
+ name, desc,
+ default_val, 0);
+
+ add_local_reloption(relopts, (relopt_gen *) newoption, offset);
+}
/*
* init_real_reloption
@@ -1617,6 +1689,18 @@ parse_one_reloption(relopt_value *option, char *text_str, int text_len,
option->gen->name, value)));
}
break;
+ case RELOPT_TYPE_TERNARY:
+ {
+ bool b;
+ parsed = parse_bool(value, &b);
+ option->values.ternary_val = b ? TERNARY_TRUE : TERNARY_FALSE;
+ if (validate && !parsed)
+ ereport(ERROR,
+ (errcode(ERRCODE_INVALID_PARAMETER_VALUE),
+ errmsg("invalid value for ternary option \"%s\": %s",
+ option->gen->name, value)));
+ }
+ break;
case RELOPT_TYPE_INT:
{
relopt_int *optint = (relopt_int *) option->gen;
@@ -1780,17 +1864,6 @@ fillRelOptions(void *rdopts, Size basesize,
char *itempos = ((char *) rdopts) + elems[j].offset;
char *string_val;
- /*
- * If isset_offset is provided, store whether the reloption is
- * set there.
- */
- if (elems[j].isset_offset > 0)
- {
- char *setpos = ((char *) rdopts) + elems[j].isset_offset;
-
- *(bool *) setpos = options[i].isset;
- }
-
switch (options[i].gen->type)
{
case RELOPT_TYPE_BOOL:
@@ -1798,6 +1871,11 @@ fillRelOptions(void *rdopts, Size basesize,
options[i].values.bool_val :
((relopt_bool *) options[i].gen)->default_val;
break;
+ case RELOPT_TYPE_TERNARY:
+ *(ternary *) itempos = options[i].isset ?
+ options[i].values.ternary_val :
+ ((relopt_ternary *) options[i].gen)->default_val;
+ break;
case RELOPT_TYPE_INT:
*(int *) itempos = options[i].isset ?
options[i].values.int_val :
@@ -1912,8 +1990,8 @@ default_reloptions(Datum reloptions, bool validate, relopt_kind kind)
offsetof(StdRdOptions, parallel_workers)},
{"vacuum_index_cleanup", RELOPT_TYPE_ENUM,
offsetof(StdRdOptions, vacuum_index_cleanup)},
- {"vacuum_truncate", RELOPT_TYPE_BOOL,
- offsetof(StdRdOptions, vacuum_truncate), offsetof(StdRdOptions, vacuum_truncate_set)},
+ {"vacuum_truncate", RELOPT_TYPE_TERNARY,
+ offsetof(StdRdOptions, vacuum_truncate)},
{"vacuum_max_eager_freeze_failure_rate", RELOPT_TYPE_REAL,
offsetof(StdRdOptions, vacuum_max_eager_freeze_failure_rate)}
};
@@ -1993,7 +2071,6 @@ build_local_reloptions(local_relopts *relopts, Datum options, bool validate)
elems[i].optname = opt->option->name;
elems[i].opttype = opt->option->type;
elems[i].offset = opt->offset;
- elems[i].isset_offset = 0; /* not supported for local relopts yet */
i++;
}
diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c
index 733ef40ae7..977babff54 100644
--- a/src/backend/commands/vacuum.c
+++ b/src/backend/commands/vacuum.c
@@ -2221,9 +2221,9 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams params,
{
StdRdOptions *opts = (StdRdOptions *) rel->rd_options;
- if (opts && opts->vacuum_truncate_set)
+ if (opts && opts->vacuum_truncate != TERNARY_UNSET)
{
- if (opts->vacuum_truncate)
+ if (opts->vacuum_truncate == TERNARY_TRUE)
params.truncate = VACOPTVALUE_ENABLED;
else
params.truncate = VACOPTVALUE_DISABLED;
diff --git a/src/include/access/reloptions.h b/src/include/access/reloptions.h
index a604a4702c..a436697658 100644
--- a/src/include/access/reloptions.h
+++ b/src/include/access/reloptions.h
@@ -29,6 +29,7 @@
typedef enum relopt_type
{
RELOPT_TYPE_BOOL,
+ RELOPT_TYPE_TERNARY, /* on, off, unset */
RELOPT_TYPE_INT,
RELOPT_TYPE_REAL,
RELOPT_TYPE_ENUM,
@@ -80,6 +81,7 @@ typedef struct relopt_value
union
{
bool bool_val;
+ ternary ternary_val;
int int_val;
double real_val;
int enum_val;
@@ -94,6 +96,12 @@ typedef struct relopt_bool
bool default_val;
} relopt_bool;
+typedef struct relopt_rernary
+{
+ relopt_gen gen;
+ int default_val;
+} relopt_ternary;
+
typedef struct relopt_int
{
relopt_gen gen;
@@ -152,19 +160,6 @@ typedef struct
const char *optname; /* option's name */
relopt_type opttype; /* option's datatype */
int offset; /* offset of field in result struct */
-
- /*
- * isset_offset is an optional offset of a field in the result struct that
- * stores whether the option is explicitly set for the relation or if it
- * just picked up the default value. In most cases, this can be
- * accomplished by giving the reloption a special out-of-range default
- * value (e.g., some integer reloptions use -2), but this isn't always
- * possible. For example, a Boolean reloption cannot be given an
- * out-of-range default, so we need another way to discover the source of
- * its value. This offset is only used if given a value greater than
- * zero.
- */
- int isset_offset;
} relopt_parse_elt;
/* Local reloption definition */
@@ -195,6 +190,8 @@ typedef struct local_relopts
extern relopt_kind add_reloption_kind(void);
extern void add_bool_reloption(bits32 kinds, const char *name, const char *desc,
bool default_val, LOCKMODE lockmode);
+extern void add_ternary_reloption(bits32 kinds, const char *name,
+ const char *desc, int default_val, LOCKMODE lockmode);
extern void add_int_reloption(bits32 kinds, const char *name, const char *desc,
int default_val, int min_val, int max_val,
LOCKMODE lockmode);
@@ -214,6 +211,9 @@ extern void register_reloptions_validator(local_relopts *relopts,
extern void add_local_bool_reloption(local_relopts *relopts, const char *name,
const char *desc, bool default_val,
int offset);
+extern void add_local_ternary_reloption(local_relopts *relopts,
+ const char *name, const char *desc,
+ ternary default_val, int offset);
extern void add_local_int_reloption(local_relopts *relopts, const char *name,
const char *desc, int default_val,
int min_val, int max_val, int offset);
diff --git a/src/include/c.h b/src/include/c.h
index 39022f8a9d..9c59ccccf8 100644
--- a/src/include/c.h
+++ b/src/include/c.h
@@ -488,6 +488,22 @@ typedef void (*pg_funcptr_t) (void);
#include <stdbool.h>
+/*
+ * ternary
+ * Boolean value with an extrea "unset" option
+ *
+ * Ternary data type is used in relation options that can be "true", "false" or
+ * "unset". Since relation options are used deep inside the PostgreSQL code,
+ * this type is declared globally.
+*/
+
+typedef enum ternary
+{
+ TERNARY_FALSE = 0,
+ TERNARY_TRUE = 1,
+ TERNARY_UNSET = -1
+} ternary;
+
/* ----------------------------------------------------------------
* Section 3: standard system types
diff --git a/src/include/utils/rel.h b/src/include/utils/rel.h
index b552359915..7346d618f9 100644
--- a/src/include/utils/rel.h
+++ b/src/include/utils/rel.h
@@ -346,8 +346,7 @@ typedef struct StdRdOptions
bool user_catalog_table; /* use as an additional catalog relation */
int parallel_workers; /* max number of parallel workers */
StdRdOptIndexCleanup vacuum_index_cleanup; /* controls index vacuuming */
- bool vacuum_truncate; /* enables vacuum to truncate a relation */
- bool vacuum_truncate_set; /* whether vacuum_truncate is set */
+ ternary vacuum_truncate; /* enables vacuum to truncate a relation */
/*
* Fraction of pages in a relation that vacuum can eagerly scan and fail
--
2.39.2
--nextPart4155600.3ZeAukHxDK
Content-Disposition: attachment;
filename="v2a-0003-Add-alias-to-be-used-as-unset-state.patch"
Content-Transfer-Encoding: 7Bit
Content-Type: text/x-patch; charset="unicode-2-0-utf-8";
name="v2a-0003-Add-alias-to-be-used-as-unset-state.patch"
^ permalink raw reply [nested|flat] 868+ messages in thread
* [PATCH v2 2/4] Introduce ternary reloptions
@ 2025-09-04 14:41 Nikolay Shaplov <[email protected]>
0 siblings, 0 replies; 868+ messages in thread
From: Nikolay Shaplov @ 2025-09-04 14:41 UTC (permalink / raw)
Introduce ternary reloption as a replacement for current `vacuum_truncate`
implementation. Remove `vacuum_truncate_set` additional flag and using
`TERNARY_UNSET` value instead.
---
src/backend/access/common/reloptions.c | 129 ++++++++++++++++++++-----
src/backend/commands/vacuum.c | 4 +-
src/include/access/reloptions.h | 26 ++---
src/include/c.h | 16 +++
src/include/utils/rel.h | 3 +-
5 files changed, 135 insertions(+), 43 deletions(-)
diff --git a/src/backend/access/common/reloptions.c b/src/backend/access/common/reloptions.c
index 0af3fea68f..f543f11001 100644
--- a/src/backend/access/common/reloptions.c
+++ b/src/backend/access/common/reloptions.c
@@ -40,9 +40,9 @@
*
* To add an option:
*
- * (i) decide on a type (bool, integer, real, enum, string), name, default
- * value, upper and lower bounds (if applicable); for strings, consider a
- * validation routine.
+ * (i) decide on a type (bool, ternary, integer, real, enum, string), name,
+ * default value, upper and lower bounds (if applicable); for strings,
+ * consider a validation routine.
* (ii) add a record below (or use add_<type>_reloption).
* (iii) add it to the appropriate options struct (perhaps StdRdOptions)
* (iv) add it to the appropriate handling routine (perhaps
@@ -147,15 +147,6 @@ static relopt_bool boolRelOpts[] =
},
false
},
- {
- {
- "vacuum_truncate",
- "Enables vacuum to truncate empty pages at the end of this table",
- RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
- ShareUpdateExclusiveLock
- },
- true
- },
{
{
"deduplicate_items",
@@ -170,6 +161,21 @@ static relopt_bool boolRelOpts[] =
{{NULL}}
};
+static relopt_ternary ternaryRelOpts[] =
+{
+ {
+ {
+ "vacuum_truncate",
+ "Enables vacuum to truncate empty pages at the end of this table",
+ RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
+ ShareUpdateExclusiveLock
+ },
+ TERNARY_UNSET
+ },
+ /* list terminator */
+ {{NULL}}
+};
+
static relopt_int intRelOpts[] =
{
{
@@ -600,6 +606,13 @@ initialize_reloptions(void)
boolRelOpts[i].gen.lockmode));
j++;
}
+ for (i = 0; ternaryRelOpts[i].gen.name; i++)
+ {
+ Assert(DoLockModesConflict(ternaryRelOpts[i].gen.lockmode,
+ ternaryRelOpts[i].gen.lockmode));
+ j++;
+ }
+
for (i = 0; intRelOpts[i].gen.name; i++)
{
Assert(DoLockModesConflict(intRelOpts[i].gen.lockmode,
@@ -640,6 +653,14 @@ initialize_reloptions(void)
j++;
}
+ for (i = 0; ternaryRelOpts[i].gen.name; i++)
+ {
+ relOpts[j] = &ternaryRelOpts[i].gen;
+ relOpts[j]->type = RELOPT_TYPE_TERNARY;
+ relOpts[j]->namelen = strlen(relOpts[j]->name);
+ j++;
+ }
+
for (i = 0; intRelOpts[i].gen.name; i++)
{
relOpts[j] = &intRelOpts[i].gen;
@@ -800,6 +821,9 @@ allocate_reloption(bits32 kinds, int type, const char *name, const char *desc,
case RELOPT_TYPE_BOOL:
size = sizeof(relopt_bool);
break;
+ case RELOPT_TYPE_TERNARY:
+ size = sizeof(relopt_ternary);
+ break;
case RELOPT_TYPE_INT:
size = sizeof(relopt_int);
break;
@@ -883,6 +907,54 @@ add_local_bool_reloption(local_relopts *relopts, const char *name,
add_local_reloption(relopts, (relopt_gen *) newoption, offset);
}
+/*
+ * init_ternary_reloption
+ * Allocate and initialize a new ternary reloption
+ */
+static relopt_ternary *
+init_ternary_reloption(bits32 kinds, const char *name, const char *desc,
+ ternary default_val, LOCKMODE lockmode)
+{
+ relopt_ternary *newoption;
+
+ newoption = (relopt_ternary *) allocate_reloption(kinds,
+ RELOPT_TYPE_TERNARY, name, desc, lockmode);
+ newoption->default_val = default_val;
+
+ return newoption;
+}
+
+/*
+ * add_ternary_reloption
+ * Add a new ternary reloption
+ */
+void
+add_ternary_reloption(bits32 kinds, const char *name, const char *desc,
+ ternary default_val, LOCKMODE lockmode)
+{
+ relopt_ternary *newoption = init_ternary_reloption(kinds, name, desc,
+ default_val, lockmode);
+
+ add_reloption((relopt_gen *) newoption);
+}
+
+/*
+ * add_local_ternary_reloption
+ * Add a new ternary local reloption
+ *
+ * 'offset' is offset of ternary-typed field.
+ */
+void
+add_local_ternary_reloption(local_relopts *relopts, const char *name,
+ const char *desc, ternary default_val,
+ int offset)
+{
+ relopt_ternary *newoption = init_ternary_reloption(RELOPT_KIND_LOCAL,
+ name, desc,
+ default_val, 0);
+
+ add_local_reloption(relopts, (relopt_gen *) newoption, offset);
+}
/*
* init_real_reloption
@@ -1617,6 +1689,18 @@ parse_one_reloption(relopt_value *option, char *text_str, int text_len,
option->gen->name, value)));
}
break;
+ case RELOPT_TYPE_TERNARY:
+ {
+ bool b;
+ parsed = parse_bool(value, &b);
+ option->values.ternary_val = b ? TERNARY_TRUE : TERNARY_FALSE;
+ if (validate && !parsed)
+ ereport(ERROR,
+ (errcode(ERRCODE_INVALID_PARAMETER_VALUE),
+ errmsg("invalid value for ternary option \"%s\": %s",
+ option->gen->name, value)));
+ }
+ break;
case RELOPT_TYPE_INT:
{
relopt_int *optint = (relopt_int *) option->gen;
@@ -1780,17 +1864,6 @@ fillRelOptions(void *rdopts, Size basesize,
char *itempos = ((char *) rdopts) + elems[j].offset;
char *string_val;
- /*
- * If isset_offset is provided, store whether the reloption is
- * set there.
- */
- if (elems[j].isset_offset > 0)
- {
- char *setpos = ((char *) rdopts) + elems[j].isset_offset;
-
- *(bool *) setpos = options[i].isset;
- }
-
switch (options[i].gen->type)
{
case RELOPT_TYPE_BOOL:
@@ -1798,6 +1871,11 @@ fillRelOptions(void *rdopts, Size basesize,
options[i].values.bool_val :
((relopt_bool *) options[i].gen)->default_val;
break;
+ case RELOPT_TYPE_TERNARY:
+ *(ternary *) itempos = options[i].isset ?
+ options[i].values.ternary_val :
+ ((relopt_ternary *) options[i].gen)->default_val;
+ break;
case RELOPT_TYPE_INT:
*(int *) itempos = options[i].isset ?
options[i].values.int_val :
@@ -1912,8 +1990,8 @@ default_reloptions(Datum reloptions, bool validate, relopt_kind kind)
offsetof(StdRdOptions, parallel_workers)},
{"vacuum_index_cleanup", RELOPT_TYPE_ENUM,
offsetof(StdRdOptions, vacuum_index_cleanup)},
- {"vacuum_truncate", RELOPT_TYPE_BOOL,
- offsetof(StdRdOptions, vacuum_truncate), offsetof(StdRdOptions, vacuum_truncate_set)},
+ {"vacuum_truncate", RELOPT_TYPE_TERNARY,
+ offsetof(StdRdOptions, vacuum_truncate)},
{"vacuum_max_eager_freeze_failure_rate", RELOPT_TYPE_REAL,
offsetof(StdRdOptions, vacuum_max_eager_freeze_failure_rate)}
};
@@ -1993,7 +2071,6 @@ build_local_reloptions(local_relopts *relopts, Datum options, bool validate)
elems[i].optname = opt->option->name;
elems[i].opttype = opt->option->type;
elems[i].offset = opt->offset;
- elems[i].isset_offset = 0; /* not supported for local relopts yet */
i++;
}
diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c
index 733ef40ae7..977babff54 100644
--- a/src/backend/commands/vacuum.c
+++ b/src/backend/commands/vacuum.c
@@ -2221,9 +2221,9 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams params,
{
StdRdOptions *opts = (StdRdOptions *) rel->rd_options;
- if (opts && opts->vacuum_truncate_set)
+ if (opts && opts->vacuum_truncate != TERNARY_UNSET)
{
- if (opts->vacuum_truncate)
+ if (opts->vacuum_truncate == TERNARY_TRUE)
params.truncate = VACOPTVALUE_ENABLED;
else
params.truncate = VACOPTVALUE_DISABLED;
diff --git a/src/include/access/reloptions.h b/src/include/access/reloptions.h
index a604a4702c..a436697658 100644
--- a/src/include/access/reloptions.h
+++ b/src/include/access/reloptions.h
@@ -29,6 +29,7 @@
typedef enum relopt_type
{
RELOPT_TYPE_BOOL,
+ RELOPT_TYPE_TERNARY, /* on, off, unset */
RELOPT_TYPE_INT,
RELOPT_TYPE_REAL,
RELOPT_TYPE_ENUM,
@@ -80,6 +81,7 @@ typedef struct relopt_value
union
{
bool bool_val;
+ ternary ternary_val;
int int_val;
double real_val;
int enum_val;
@@ -94,6 +96,12 @@ typedef struct relopt_bool
bool default_val;
} relopt_bool;
+typedef struct relopt_rernary
+{
+ relopt_gen gen;
+ int default_val;
+} relopt_ternary;
+
typedef struct relopt_int
{
relopt_gen gen;
@@ -152,19 +160,6 @@ typedef struct
const char *optname; /* option's name */
relopt_type opttype; /* option's datatype */
int offset; /* offset of field in result struct */
-
- /*
- * isset_offset is an optional offset of a field in the result struct that
- * stores whether the option is explicitly set for the relation or if it
- * just picked up the default value. In most cases, this can be
- * accomplished by giving the reloption a special out-of-range default
- * value (e.g., some integer reloptions use -2), but this isn't always
- * possible. For example, a Boolean reloption cannot be given an
- * out-of-range default, so we need another way to discover the source of
- * its value. This offset is only used if given a value greater than
- * zero.
- */
- int isset_offset;
} relopt_parse_elt;
/* Local reloption definition */
@@ -195,6 +190,8 @@ typedef struct local_relopts
extern relopt_kind add_reloption_kind(void);
extern void add_bool_reloption(bits32 kinds, const char *name, const char *desc,
bool default_val, LOCKMODE lockmode);
+extern void add_ternary_reloption(bits32 kinds, const char *name,
+ const char *desc, int default_val, LOCKMODE lockmode);
extern void add_int_reloption(bits32 kinds, const char *name, const char *desc,
int default_val, int min_val, int max_val,
LOCKMODE lockmode);
@@ -214,6 +211,9 @@ extern void register_reloptions_validator(local_relopts *relopts,
extern void add_local_bool_reloption(local_relopts *relopts, const char *name,
const char *desc, bool default_val,
int offset);
+extern void add_local_ternary_reloption(local_relopts *relopts,
+ const char *name, const char *desc,
+ ternary default_val, int offset);
extern void add_local_int_reloption(local_relopts *relopts, const char *name,
const char *desc, int default_val,
int min_val, int max_val, int offset);
diff --git a/src/include/c.h b/src/include/c.h
index 39022f8a9d..9c59ccccf8 100644
--- a/src/include/c.h
+++ b/src/include/c.h
@@ -488,6 +488,22 @@ typedef void (*pg_funcptr_t) (void);
#include <stdbool.h>
+/*
+ * ternary
+ * Boolean value with an extrea "unset" option
+ *
+ * Ternary data type is used in relation options that can be "true", "false" or
+ * "unset". Since relation options are used deep inside the PostgreSQL code,
+ * this type is declared globally.
+*/
+
+typedef enum ternary
+{
+ TERNARY_FALSE = 0,
+ TERNARY_TRUE = 1,
+ TERNARY_UNSET = -1
+} ternary;
+
/* ----------------------------------------------------------------
* Section 3: standard system types
diff --git a/src/include/utils/rel.h b/src/include/utils/rel.h
index b552359915..7346d618f9 100644
--- a/src/include/utils/rel.h
+++ b/src/include/utils/rel.h
@@ -346,8 +346,7 @@ typedef struct StdRdOptions
bool user_catalog_table; /* use as an additional catalog relation */
int parallel_workers; /* max number of parallel workers */
StdRdOptIndexCleanup vacuum_index_cleanup; /* controls index vacuuming */
- bool vacuum_truncate; /* enables vacuum to truncate a relation */
- bool vacuum_truncate_set; /* whether vacuum_truncate is set */
+ ternary vacuum_truncate; /* enables vacuum to truncate a relation */
/*
* Fraction of pages in a relation that vacuum can eagerly scan and fail
--
2.39.2
--nextPart5129932.5fSG56mABF
Content-Disposition: attachment;
filename="v2-0003-Add-alias-to-be-used-as-unset-state.patch"
Content-Transfer-Encoding: 7Bit
Content-Type: text/x-patch; charset="unicode-2-0-utf-8";
name="v2-0003-Add-alias-to-be-used-as-unset-state.patch"
^ permalink raw reply [nested|flat] 868+ messages in thread
* [PATCH v2 2/4] Introduce ternary reloptions
@ 2025-09-04 14:41 Nikolay Shaplov <[email protected]>
0 siblings, 0 replies; 868+ messages in thread
From: Nikolay Shaplov @ 2025-09-04 14:41 UTC (permalink / raw)
Introduce ternary reloption as a replacement for current `vacuum_truncate`
implementation. Remove `vacuum_truncate_set` additional flag and using
`TERNARY_UNSET` value instead.
---
src/backend/access/common/reloptions.c | 129 ++++++++++++++++++++-----
src/backend/commands/vacuum.c | 4 +-
src/include/access/reloptions.h | 26 ++---
src/include/c.h | 16 +++
src/include/utils/rel.h | 3 +-
5 files changed, 135 insertions(+), 43 deletions(-)
diff --git a/src/backend/access/common/reloptions.c b/src/backend/access/common/reloptions.c
index 0af3fea68f..f543f11001 100644
--- a/src/backend/access/common/reloptions.c
+++ b/src/backend/access/common/reloptions.c
@@ -40,9 +40,9 @@
*
* To add an option:
*
- * (i) decide on a type (bool, integer, real, enum, string), name, default
- * value, upper and lower bounds (if applicable); for strings, consider a
- * validation routine.
+ * (i) decide on a type (bool, ternary, integer, real, enum, string), name,
+ * default value, upper and lower bounds (if applicable); for strings,
+ * consider a validation routine.
* (ii) add a record below (or use add_<type>_reloption).
* (iii) add it to the appropriate options struct (perhaps StdRdOptions)
* (iv) add it to the appropriate handling routine (perhaps
@@ -147,15 +147,6 @@ static relopt_bool boolRelOpts[] =
},
false
},
- {
- {
- "vacuum_truncate",
- "Enables vacuum to truncate empty pages at the end of this table",
- RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
- ShareUpdateExclusiveLock
- },
- true
- },
{
{
"deduplicate_items",
@@ -170,6 +161,21 @@ static relopt_bool boolRelOpts[] =
{{NULL}}
};
+static relopt_ternary ternaryRelOpts[] =
+{
+ {
+ {
+ "vacuum_truncate",
+ "Enables vacuum to truncate empty pages at the end of this table",
+ RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
+ ShareUpdateExclusiveLock
+ },
+ TERNARY_UNSET
+ },
+ /* list terminator */
+ {{NULL}}
+};
+
static relopt_int intRelOpts[] =
{
{
@@ -600,6 +606,13 @@ initialize_reloptions(void)
boolRelOpts[i].gen.lockmode));
j++;
}
+ for (i = 0; ternaryRelOpts[i].gen.name; i++)
+ {
+ Assert(DoLockModesConflict(ternaryRelOpts[i].gen.lockmode,
+ ternaryRelOpts[i].gen.lockmode));
+ j++;
+ }
+
for (i = 0; intRelOpts[i].gen.name; i++)
{
Assert(DoLockModesConflict(intRelOpts[i].gen.lockmode,
@@ -640,6 +653,14 @@ initialize_reloptions(void)
j++;
}
+ for (i = 0; ternaryRelOpts[i].gen.name; i++)
+ {
+ relOpts[j] = &ternaryRelOpts[i].gen;
+ relOpts[j]->type = RELOPT_TYPE_TERNARY;
+ relOpts[j]->namelen = strlen(relOpts[j]->name);
+ j++;
+ }
+
for (i = 0; intRelOpts[i].gen.name; i++)
{
relOpts[j] = &intRelOpts[i].gen;
@@ -800,6 +821,9 @@ allocate_reloption(bits32 kinds, int type, const char *name, const char *desc,
case RELOPT_TYPE_BOOL:
size = sizeof(relopt_bool);
break;
+ case RELOPT_TYPE_TERNARY:
+ size = sizeof(relopt_ternary);
+ break;
case RELOPT_TYPE_INT:
size = sizeof(relopt_int);
break;
@@ -883,6 +907,54 @@ add_local_bool_reloption(local_relopts *relopts, const char *name,
add_local_reloption(relopts, (relopt_gen *) newoption, offset);
}
+/*
+ * init_ternary_reloption
+ * Allocate and initialize a new ternary reloption
+ */
+static relopt_ternary *
+init_ternary_reloption(bits32 kinds, const char *name, const char *desc,
+ ternary default_val, LOCKMODE lockmode)
+{
+ relopt_ternary *newoption;
+
+ newoption = (relopt_ternary *) allocate_reloption(kinds,
+ RELOPT_TYPE_TERNARY, name, desc, lockmode);
+ newoption->default_val = default_val;
+
+ return newoption;
+}
+
+/*
+ * add_ternary_reloption
+ * Add a new ternary reloption
+ */
+void
+add_ternary_reloption(bits32 kinds, const char *name, const char *desc,
+ ternary default_val, LOCKMODE lockmode)
+{
+ relopt_ternary *newoption = init_ternary_reloption(kinds, name, desc,
+ default_val, lockmode);
+
+ add_reloption((relopt_gen *) newoption);
+}
+
+/*
+ * add_local_ternary_reloption
+ * Add a new ternary local reloption
+ *
+ * 'offset' is offset of ternary-typed field.
+ */
+void
+add_local_ternary_reloption(local_relopts *relopts, const char *name,
+ const char *desc, ternary default_val,
+ int offset)
+{
+ relopt_ternary *newoption = init_ternary_reloption(RELOPT_KIND_LOCAL,
+ name, desc,
+ default_val, 0);
+
+ add_local_reloption(relopts, (relopt_gen *) newoption, offset);
+}
/*
* init_real_reloption
@@ -1617,6 +1689,18 @@ parse_one_reloption(relopt_value *option, char *text_str, int text_len,
option->gen->name, value)));
}
break;
+ case RELOPT_TYPE_TERNARY:
+ {
+ bool b;
+ parsed = parse_bool(value, &b);
+ option->values.ternary_val = b ? TERNARY_TRUE : TERNARY_FALSE;
+ if (validate && !parsed)
+ ereport(ERROR,
+ (errcode(ERRCODE_INVALID_PARAMETER_VALUE),
+ errmsg("invalid value for ternary option \"%s\": %s",
+ option->gen->name, value)));
+ }
+ break;
case RELOPT_TYPE_INT:
{
relopt_int *optint = (relopt_int *) option->gen;
@@ -1780,17 +1864,6 @@ fillRelOptions(void *rdopts, Size basesize,
char *itempos = ((char *) rdopts) + elems[j].offset;
char *string_val;
- /*
- * If isset_offset is provided, store whether the reloption is
- * set there.
- */
- if (elems[j].isset_offset > 0)
- {
- char *setpos = ((char *) rdopts) + elems[j].isset_offset;
-
- *(bool *) setpos = options[i].isset;
- }
-
switch (options[i].gen->type)
{
case RELOPT_TYPE_BOOL:
@@ -1798,6 +1871,11 @@ fillRelOptions(void *rdopts, Size basesize,
options[i].values.bool_val :
((relopt_bool *) options[i].gen)->default_val;
break;
+ case RELOPT_TYPE_TERNARY:
+ *(ternary *) itempos = options[i].isset ?
+ options[i].values.ternary_val :
+ ((relopt_ternary *) options[i].gen)->default_val;
+ break;
case RELOPT_TYPE_INT:
*(int *) itempos = options[i].isset ?
options[i].values.int_val :
@@ -1912,8 +1990,8 @@ default_reloptions(Datum reloptions, bool validate, relopt_kind kind)
offsetof(StdRdOptions, parallel_workers)},
{"vacuum_index_cleanup", RELOPT_TYPE_ENUM,
offsetof(StdRdOptions, vacuum_index_cleanup)},
- {"vacuum_truncate", RELOPT_TYPE_BOOL,
- offsetof(StdRdOptions, vacuum_truncate), offsetof(StdRdOptions, vacuum_truncate_set)},
+ {"vacuum_truncate", RELOPT_TYPE_TERNARY,
+ offsetof(StdRdOptions, vacuum_truncate)},
{"vacuum_max_eager_freeze_failure_rate", RELOPT_TYPE_REAL,
offsetof(StdRdOptions, vacuum_max_eager_freeze_failure_rate)}
};
@@ -1993,7 +2071,6 @@ build_local_reloptions(local_relopts *relopts, Datum options, bool validate)
elems[i].optname = opt->option->name;
elems[i].opttype = opt->option->type;
elems[i].offset = opt->offset;
- elems[i].isset_offset = 0; /* not supported for local relopts yet */
i++;
}
diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c
index 733ef40ae7..977babff54 100644
--- a/src/backend/commands/vacuum.c
+++ b/src/backend/commands/vacuum.c
@@ -2221,9 +2221,9 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams params,
{
StdRdOptions *opts = (StdRdOptions *) rel->rd_options;
- if (opts && opts->vacuum_truncate_set)
+ if (opts && opts->vacuum_truncate != TERNARY_UNSET)
{
- if (opts->vacuum_truncate)
+ if (opts->vacuum_truncate == TERNARY_TRUE)
params.truncate = VACOPTVALUE_ENABLED;
else
params.truncate = VACOPTVALUE_DISABLED;
diff --git a/src/include/access/reloptions.h b/src/include/access/reloptions.h
index a604a4702c..a436697658 100644
--- a/src/include/access/reloptions.h
+++ b/src/include/access/reloptions.h
@@ -29,6 +29,7 @@
typedef enum relopt_type
{
RELOPT_TYPE_BOOL,
+ RELOPT_TYPE_TERNARY, /* on, off, unset */
RELOPT_TYPE_INT,
RELOPT_TYPE_REAL,
RELOPT_TYPE_ENUM,
@@ -80,6 +81,7 @@ typedef struct relopt_value
union
{
bool bool_val;
+ ternary ternary_val;
int int_val;
double real_val;
int enum_val;
@@ -94,6 +96,12 @@ typedef struct relopt_bool
bool default_val;
} relopt_bool;
+typedef struct relopt_rernary
+{
+ relopt_gen gen;
+ int default_val;
+} relopt_ternary;
+
typedef struct relopt_int
{
relopt_gen gen;
@@ -152,19 +160,6 @@ typedef struct
const char *optname; /* option's name */
relopt_type opttype; /* option's datatype */
int offset; /* offset of field in result struct */
-
- /*
- * isset_offset is an optional offset of a field in the result struct that
- * stores whether the option is explicitly set for the relation or if it
- * just picked up the default value. In most cases, this can be
- * accomplished by giving the reloption a special out-of-range default
- * value (e.g., some integer reloptions use -2), but this isn't always
- * possible. For example, a Boolean reloption cannot be given an
- * out-of-range default, so we need another way to discover the source of
- * its value. This offset is only used if given a value greater than
- * zero.
- */
- int isset_offset;
} relopt_parse_elt;
/* Local reloption definition */
@@ -195,6 +190,8 @@ typedef struct local_relopts
extern relopt_kind add_reloption_kind(void);
extern void add_bool_reloption(bits32 kinds, const char *name, const char *desc,
bool default_val, LOCKMODE lockmode);
+extern void add_ternary_reloption(bits32 kinds, const char *name,
+ const char *desc, int default_val, LOCKMODE lockmode);
extern void add_int_reloption(bits32 kinds, const char *name, const char *desc,
int default_val, int min_val, int max_val,
LOCKMODE lockmode);
@@ -214,6 +211,9 @@ extern void register_reloptions_validator(local_relopts *relopts,
extern void add_local_bool_reloption(local_relopts *relopts, const char *name,
const char *desc, bool default_val,
int offset);
+extern void add_local_ternary_reloption(local_relopts *relopts,
+ const char *name, const char *desc,
+ ternary default_val, int offset);
extern void add_local_int_reloption(local_relopts *relopts, const char *name,
const char *desc, int default_val,
int min_val, int max_val, int offset);
diff --git a/src/include/c.h b/src/include/c.h
index 39022f8a9d..9c59ccccf8 100644
--- a/src/include/c.h
+++ b/src/include/c.h
@@ -488,6 +488,22 @@ typedef void (*pg_funcptr_t) (void);
#include <stdbool.h>
+/*
+ * ternary
+ * Boolean value with an extrea "unset" option
+ *
+ * Ternary data type is used in relation options that can be "true", "false" or
+ * "unset". Since relation options are used deep inside the PostgreSQL code,
+ * this type is declared globally.
+*/
+
+typedef enum ternary
+{
+ TERNARY_FALSE = 0,
+ TERNARY_TRUE = 1,
+ TERNARY_UNSET = -1
+} ternary;
+
/* ----------------------------------------------------------------
* Section 3: standard system types
diff --git a/src/include/utils/rel.h b/src/include/utils/rel.h
index b552359915..7346d618f9 100644
--- a/src/include/utils/rel.h
+++ b/src/include/utils/rel.h
@@ -346,8 +346,7 @@ typedef struct StdRdOptions
bool user_catalog_table; /* use as an additional catalog relation */
int parallel_workers; /* max number of parallel workers */
StdRdOptIndexCleanup vacuum_index_cleanup; /* controls index vacuuming */
- bool vacuum_truncate; /* enables vacuum to truncate a relation */
- bool vacuum_truncate_set; /* whether vacuum_truncate is set */
+ ternary vacuum_truncate; /* enables vacuum to truncate a relation */
/*
* Fraction of pages in a relation that vacuum can eagerly scan and fail
--
2.39.2
--nextPart5129932.5fSG56mABF
Content-Disposition: attachment;
filename="v2-0003-Add-alias-to-be-used-as-unset-state.patch"
Content-Transfer-Encoding: 7Bit
Content-Type: text/x-patch; charset="unicode-2-0-utf-8";
name="v2-0003-Add-alias-to-be-used-as-unset-state.patch"
^ permalink raw reply [nested|flat] 868+ messages in thread
* [PATCH v2a 2/4] Introduce ternary reloptions
@ 2025-09-04 14:41 Nikolay Shaplov <[email protected]>
0 siblings, 0 replies; 868+ messages in thread
From: Nikolay Shaplov @ 2025-09-04 14:41 UTC (permalink / raw)
Introduce ternary reloption as a replacement for current `vacuum_truncate`
implementation. Remove `vacuum_truncate_set` additional flag and using
`TERNARY_UNSET` value instead.
---
src/backend/access/common/reloptions.c | 129 ++++++++++++++++++++-----
src/backend/commands/vacuum.c | 4 +-
src/include/access/reloptions.h | 26 ++---
src/include/c.h | 16 +++
src/include/utils/rel.h | 3 +-
5 files changed, 135 insertions(+), 43 deletions(-)
diff --git a/src/backend/access/common/reloptions.c b/src/backend/access/common/reloptions.c
index 0af3fea68f..f543f11001 100644
--- a/src/backend/access/common/reloptions.c
+++ b/src/backend/access/common/reloptions.c
@@ -40,9 +40,9 @@
*
* To add an option:
*
- * (i) decide on a type (bool, integer, real, enum, string), name, default
- * value, upper and lower bounds (if applicable); for strings, consider a
- * validation routine.
+ * (i) decide on a type (bool, ternary, integer, real, enum, string), name,
+ * default value, upper and lower bounds (if applicable); for strings,
+ * consider a validation routine.
* (ii) add a record below (or use add_<type>_reloption).
* (iii) add it to the appropriate options struct (perhaps StdRdOptions)
* (iv) add it to the appropriate handling routine (perhaps
@@ -147,15 +147,6 @@ static relopt_bool boolRelOpts[] =
},
false
},
- {
- {
- "vacuum_truncate",
- "Enables vacuum to truncate empty pages at the end of this table",
- RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
- ShareUpdateExclusiveLock
- },
- true
- },
{
{
"deduplicate_items",
@@ -170,6 +161,21 @@ static relopt_bool boolRelOpts[] =
{{NULL}}
};
+static relopt_ternary ternaryRelOpts[] =
+{
+ {
+ {
+ "vacuum_truncate",
+ "Enables vacuum to truncate empty pages at the end of this table",
+ RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
+ ShareUpdateExclusiveLock
+ },
+ TERNARY_UNSET
+ },
+ /* list terminator */
+ {{NULL}}
+};
+
static relopt_int intRelOpts[] =
{
{
@@ -600,6 +606,13 @@ initialize_reloptions(void)
boolRelOpts[i].gen.lockmode));
j++;
}
+ for (i = 0; ternaryRelOpts[i].gen.name; i++)
+ {
+ Assert(DoLockModesConflict(ternaryRelOpts[i].gen.lockmode,
+ ternaryRelOpts[i].gen.lockmode));
+ j++;
+ }
+
for (i = 0; intRelOpts[i].gen.name; i++)
{
Assert(DoLockModesConflict(intRelOpts[i].gen.lockmode,
@@ -640,6 +653,14 @@ initialize_reloptions(void)
j++;
}
+ for (i = 0; ternaryRelOpts[i].gen.name; i++)
+ {
+ relOpts[j] = &ternaryRelOpts[i].gen;
+ relOpts[j]->type = RELOPT_TYPE_TERNARY;
+ relOpts[j]->namelen = strlen(relOpts[j]->name);
+ j++;
+ }
+
for (i = 0; intRelOpts[i].gen.name; i++)
{
relOpts[j] = &intRelOpts[i].gen;
@@ -800,6 +821,9 @@ allocate_reloption(bits32 kinds, int type, const char *name, const char *desc,
case RELOPT_TYPE_BOOL:
size = sizeof(relopt_bool);
break;
+ case RELOPT_TYPE_TERNARY:
+ size = sizeof(relopt_ternary);
+ break;
case RELOPT_TYPE_INT:
size = sizeof(relopt_int);
break;
@@ -883,6 +907,54 @@ add_local_bool_reloption(local_relopts *relopts, const char *name,
add_local_reloption(relopts, (relopt_gen *) newoption, offset);
}
+/*
+ * init_ternary_reloption
+ * Allocate and initialize a new ternary reloption
+ */
+static relopt_ternary *
+init_ternary_reloption(bits32 kinds, const char *name, const char *desc,
+ ternary default_val, LOCKMODE lockmode)
+{
+ relopt_ternary *newoption;
+
+ newoption = (relopt_ternary *) allocate_reloption(kinds,
+ RELOPT_TYPE_TERNARY, name, desc, lockmode);
+ newoption->default_val = default_val;
+
+ return newoption;
+}
+
+/*
+ * add_ternary_reloption
+ * Add a new ternary reloption
+ */
+void
+add_ternary_reloption(bits32 kinds, const char *name, const char *desc,
+ ternary default_val, LOCKMODE lockmode)
+{
+ relopt_ternary *newoption = init_ternary_reloption(kinds, name, desc,
+ default_val, lockmode);
+
+ add_reloption((relopt_gen *) newoption);
+}
+
+/*
+ * add_local_ternary_reloption
+ * Add a new ternary local reloption
+ *
+ * 'offset' is offset of ternary-typed field.
+ */
+void
+add_local_ternary_reloption(local_relopts *relopts, const char *name,
+ const char *desc, ternary default_val,
+ int offset)
+{
+ relopt_ternary *newoption = init_ternary_reloption(RELOPT_KIND_LOCAL,
+ name, desc,
+ default_val, 0);
+
+ add_local_reloption(relopts, (relopt_gen *) newoption, offset);
+}
/*
* init_real_reloption
@@ -1617,6 +1689,18 @@ parse_one_reloption(relopt_value *option, char *text_str, int text_len,
option->gen->name, value)));
}
break;
+ case RELOPT_TYPE_TERNARY:
+ {
+ bool b;
+ parsed = parse_bool(value, &b);
+ option->values.ternary_val = b ? TERNARY_TRUE : TERNARY_FALSE;
+ if (validate && !parsed)
+ ereport(ERROR,
+ (errcode(ERRCODE_INVALID_PARAMETER_VALUE),
+ errmsg("invalid value for ternary option \"%s\": %s",
+ option->gen->name, value)));
+ }
+ break;
case RELOPT_TYPE_INT:
{
relopt_int *optint = (relopt_int *) option->gen;
@@ -1780,17 +1864,6 @@ fillRelOptions(void *rdopts, Size basesize,
char *itempos = ((char *) rdopts) + elems[j].offset;
char *string_val;
- /*
- * If isset_offset is provided, store whether the reloption is
- * set there.
- */
- if (elems[j].isset_offset > 0)
- {
- char *setpos = ((char *) rdopts) + elems[j].isset_offset;
-
- *(bool *) setpos = options[i].isset;
- }
-
switch (options[i].gen->type)
{
case RELOPT_TYPE_BOOL:
@@ -1798,6 +1871,11 @@ fillRelOptions(void *rdopts, Size basesize,
options[i].values.bool_val :
((relopt_bool *) options[i].gen)->default_val;
break;
+ case RELOPT_TYPE_TERNARY:
+ *(ternary *) itempos = options[i].isset ?
+ options[i].values.ternary_val :
+ ((relopt_ternary *) options[i].gen)->default_val;
+ break;
case RELOPT_TYPE_INT:
*(int *) itempos = options[i].isset ?
options[i].values.int_val :
@@ -1912,8 +1990,8 @@ default_reloptions(Datum reloptions, bool validate, relopt_kind kind)
offsetof(StdRdOptions, parallel_workers)},
{"vacuum_index_cleanup", RELOPT_TYPE_ENUM,
offsetof(StdRdOptions, vacuum_index_cleanup)},
- {"vacuum_truncate", RELOPT_TYPE_BOOL,
- offsetof(StdRdOptions, vacuum_truncate), offsetof(StdRdOptions, vacuum_truncate_set)},
+ {"vacuum_truncate", RELOPT_TYPE_TERNARY,
+ offsetof(StdRdOptions, vacuum_truncate)},
{"vacuum_max_eager_freeze_failure_rate", RELOPT_TYPE_REAL,
offsetof(StdRdOptions, vacuum_max_eager_freeze_failure_rate)}
};
@@ -1993,7 +2071,6 @@ build_local_reloptions(local_relopts *relopts, Datum options, bool validate)
elems[i].optname = opt->option->name;
elems[i].opttype = opt->option->type;
elems[i].offset = opt->offset;
- elems[i].isset_offset = 0; /* not supported for local relopts yet */
i++;
}
diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c
index 733ef40ae7..977babff54 100644
--- a/src/backend/commands/vacuum.c
+++ b/src/backend/commands/vacuum.c
@@ -2221,9 +2221,9 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams params,
{
StdRdOptions *opts = (StdRdOptions *) rel->rd_options;
- if (opts && opts->vacuum_truncate_set)
+ if (opts && opts->vacuum_truncate != TERNARY_UNSET)
{
- if (opts->vacuum_truncate)
+ if (opts->vacuum_truncate == TERNARY_TRUE)
params.truncate = VACOPTVALUE_ENABLED;
else
params.truncate = VACOPTVALUE_DISABLED;
diff --git a/src/include/access/reloptions.h b/src/include/access/reloptions.h
index a604a4702c..a436697658 100644
--- a/src/include/access/reloptions.h
+++ b/src/include/access/reloptions.h
@@ -29,6 +29,7 @@
typedef enum relopt_type
{
RELOPT_TYPE_BOOL,
+ RELOPT_TYPE_TERNARY, /* on, off, unset */
RELOPT_TYPE_INT,
RELOPT_TYPE_REAL,
RELOPT_TYPE_ENUM,
@@ -80,6 +81,7 @@ typedef struct relopt_value
union
{
bool bool_val;
+ ternary ternary_val;
int int_val;
double real_val;
int enum_val;
@@ -94,6 +96,12 @@ typedef struct relopt_bool
bool default_val;
} relopt_bool;
+typedef struct relopt_rernary
+{
+ relopt_gen gen;
+ int default_val;
+} relopt_ternary;
+
typedef struct relopt_int
{
relopt_gen gen;
@@ -152,19 +160,6 @@ typedef struct
const char *optname; /* option's name */
relopt_type opttype; /* option's datatype */
int offset; /* offset of field in result struct */
-
- /*
- * isset_offset is an optional offset of a field in the result struct that
- * stores whether the option is explicitly set for the relation or if it
- * just picked up the default value. In most cases, this can be
- * accomplished by giving the reloption a special out-of-range default
- * value (e.g., some integer reloptions use -2), but this isn't always
- * possible. For example, a Boolean reloption cannot be given an
- * out-of-range default, so we need another way to discover the source of
- * its value. This offset is only used if given a value greater than
- * zero.
- */
- int isset_offset;
} relopt_parse_elt;
/* Local reloption definition */
@@ -195,6 +190,8 @@ typedef struct local_relopts
extern relopt_kind add_reloption_kind(void);
extern void add_bool_reloption(bits32 kinds, const char *name, const char *desc,
bool default_val, LOCKMODE lockmode);
+extern void add_ternary_reloption(bits32 kinds, const char *name,
+ const char *desc, int default_val, LOCKMODE lockmode);
extern void add_int_reloption(bits32 kinds, const char *name, const char *desc,
int default_val, int min_val, int max_val,
LOCKMODE lockmode);
@@ -214,6 +211,9 @@ extern void register_reloptions_validator(local_relopts *relopts,
extern void add_local_bool_reloption(local_relopts *relopts, const char *name,
const char *desc, bool default_val,
int offset);
+extern void add_local_ternary_reloption(local_relopts *relopts,
+ const char *name, const char *desc,
+ ternary default_val, int offset);
extern void add_local_int_reloption(local_relopts *relopts, const char *name,
const char *desc, int default_val,
int min_val, int max_val, int offset);
diff --git a/src/include/c.h b/src/include/c.h
index 39022f8a9d..9c59ccccf8 100644
--- a/src/include/c.h
+++ b/src/include/c.h
@@ -488,6 +488,22 @@ typedef void (*pg_funcptr_t) (void);
#include <stdbool.h>
+/*
+ * ternary
+ * Boolean value with an extrea "unset" option
+ *
+ * Ternary data type is used in relation options that can be "true", "false" or
+ * "unset". Since relation options are used deep inside the PostgreSQL code,
+ * this type is declared globally.
+*/
+
+typedef enum ternary
+{
+ TERNARY_FALSE = 0,
+ TERNARY_TRUE = 1,
+ TERNARY_UNSET = -1
+} ternary;
+
/* ----------------------------------------------------------------
* Section 3: standard system types
diff --git a/src/include/utils/rel.h b/src/include/utils/rel.h
index b552359915..7346d618f9 100644
--- a/src/include/utils/rel.h
+++ b/src/include/utils/rel.h
@@ -346,8 +346,7 @@ typedef struct StdRdOptions
bool user_catalog_table; /* use as an additional catalog relation */
int parallel_workers; /* max number of parallel workers */
StdRdOptIndexCleanup vacuum_index_cleanup; /* controls index vacuuming */
- bool vacuum_truncate; /* enables vacuum to truncate a relation */
- bool vacuum_truncate_set; /* whether vacuum_truncate is set */
+ ternary vacuum_truncate; /* enables vacuum to truncate a relation */
/*
* Fraction of pages in a relation that vacuum can eagerly scan and fail
--
2.39.2
--nextPart4155600.3ZeAukHxDK
Content-Disposition: attachment;
filename="v2a-0003-Add-alias-to-be-used-as-unset-state.patch"
Content-Transfer-Encoding: 7Bit
Content-Type: text/x-patch; charset="unicode-2-0-utf-8";
name="v2a-0003-Add-alias-to-be-used-as-unset-state.patch"
^ permalink raw reply [nested|flat] 868+ messages in thread
* [PATCH v2a 2/4] Introduce ternary reloptions
@ 2025-09-04 14:41 Nikolay Shaplov <[email protected]>
0 siblings, 0 replies; 868+ messages in thread
From: Nikolay Shaplov @ 2025-09-04 14:41 UTC (permalink / raw)
Introduce ternary reloption as a replacement for current `vacuum_truncate`
implementation. Remove `vacuum_truncate_set` additional flag and using
`TERNARY_UNSET` value instead.
---
src/backend/access/common/reloptions.c | 129 ++++++++++++++++++++-----
src/backend/commands/vacuum.c | 4 +-
src/include/access/reloptions.h | 26 ++---
src/include/c.h | 16 +++
src/include/utils/rel.h | 3 +-
5 files changed, 135 insertions(+), 43 deletions(-)
diff --git a/src/backend/access/common/reloptions.c b/src/backend/access/common/reloptions.c
index 0af3fea68f..f543f11001 100644
--- a/src/backend/access/common/reloptions.c
+++ b/src/backend/access/common/reloptions.c
@@ -40,9 +40,9 @@
*
* To add an option:
*
- * (i) decide on a type (bool, integer, real, enum, string), name, default
- * value, upper and lower bounds (if applicable); for strings, consider a
- * validation routine.
+ * (i) decide on a type (bool, ternary, integer, real, enum, string), name,
+ * default value, upper and lower bounds (if applicable); for strings,
+ * consider a validation routine.
* (ii) add a record below (or use add_<type>_reloption).
* (iii) add it to the appropriate options struct (perhaps StdRdOptions)
* (iv) add it to the appropriate handling routine (perhaps
@@ -147,15 +147,6 @@ static relopt_bool boolRelOpts[] =
},
false
},
- {
- {
- "vacuum_truncate",
- "Enables vacuum to truncate empty pages at the end of this table",
- RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
- ShareUpdateExclusiveLock
- },
- true
- },
{
{
"deduplicate_items",
@@ -170,6 +161,21 @@ static relopt_bool boolRelOpts[] =
{{NULL}}
};
+static relopt_ternary ternaryRelOpts[] =
+{
+ {
+ {
+ "vacuum_truncate",
+ "Enables vacuum to truncate empty pages at the end of this table",
+ RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
+ ShareUpdateExclusiveLock
+ },
+ TERNARY_UNSET
+ },
+ /* list terminator */
+ {{NULL}}
+};
+
static relopt_int intRelOpts[] =
{
{
@@ -600,6 +606,13 @@ initialize_reloptions(void)
boolRelOpts[i].gen.lockmode));
j++;
}
+ for (i = 0; ternaryRelOpts[i].gen.name; i++)
+ {
+ Assert(DoLockModesConflict(ternaryRelOpts[i].gen.lockmode,
+ ternaryRelOpts[i].gen.lockmode));
+ j++;
+ }
+
for (i = 0; intRelOpts[i].gen.name; i++)
{
Assert(DoLockModesConflict(intRelOpts[i].gen.lockmode,
@@ -640,6 +653,14 @@ initialize_reloptions(void)
j++;
}
+ for (i = 0; ternaryRelOpts[i].gen.name; i++)
+ {
+ relOpts[j] = &ternaryRelOpts[i].gen;
+ relOpts[j]->type = RELOPT_TYPE_TERNARY;
+ relOpts[j]->namelen = strlen(relOpts[j]->name);
+ j++;
+ }
+
for (i = 0; intRelOpts[i].gen.name; i++)
{
relOpts[j] = &intRelOpts[i].gen;
@@ -800,6 +821,9 @@ allocate_reloption(bits32 kinds, int type, const char *name, const char *desc,
case RELOPT_TYPE_BOOL:
size = sizeof(relopt_bool);
break;
+ case RELOPT_TYPE_TERNARY:
+ size = sizeof(relopt_ternary);
+ break;
case RELOPT_TYPE_INT:
size = sizeof(relopt_int);
break;
@@ -883,6 +907,54 @@ add_local_bool_reloption(local_relopts *relopts, const char *name,
add_local_reloption(relopts, (relopt_gen *) newoption, offset);
}
+/*
+ * init_ternary_reloption
+ * Allocate and initialize a new ternary reloption
+ */
+static relopt_ternary *
+init_ternary_reloption(bits32 kinds, const char *name, const char *desc,
+ ternary default_val, LOCKMODE lockmode)
+{
+ relopt_ternary *newoption;
+
+ newoption = (relopt_ternary *) allocate_reloption(kinds,
+ RELOPT_TYPE_TERNARY, name, desc, lockmode);
+ newoption->default_val = default_val;
+
+ return newoption;
+}
+
+/*
+ * add_ternary_reloption
+ * Add a new ternary reloption
+ */
+void
+add_ternary_reloption(bits32 kinds, const char *name, const char *desc,
+ ternary default_val, LOCKMODE lockmode)
+{
+ relopt_ternary *newoption = init_ternary_reloption(kinds, name, desc,
+ default_val, lockmode);
+
+ add_reloption((relopt_gen *) newoption);
+}
+
+/*
+ * add_local_ternary_reloption
+ * Add a new ternary local reloption
+ *
+ * 'offset' is offset of ternary-typed field.
+ */
+void
+add_local_ternary_reloption(local_relopts *relopts, const char *name,
+ const char *desc, ternary default_val,
+ int offset)
+{
+ relopt_ternary *newoption = init_ternary_reloption(RELOPT_KIND_LOCAL,
+ name, desc,
+ default_val, 0);
+
+ add_local_reloption(relopts, (relopt_gen *) newoption, offset);
+}
/*
* init_real_reloption
@@ -1617,6 +1689,18 @@ parse_one_reloption(relopt_value *option, char *text_str, int text_len,
option->gen->name, value)));
}
break;
+ case RELOPT_TYPE_TERNARY:
+ {
+ bool b;
+ parsed = parse_bool(value, &b);
+ option->values.ternary_val = b ? TERNARY_TRUE : TERNARY_FALSE;
+ if (validate && !parsed)
+ ereport(ERROR,
+ (errcode(ERRCODE_INVALID_PARAMETER_VALUE),
+ errmsg("invalid value for ternary option \"%s\": %s",
+ option->gen->name, value)));
+ }
+ break;
case RELOPT_TYPE_INT:
{
relopt_int *optint = (relopt_int *) option->gen;
@@ -1780,17 +1864,6 @@ fillRelOptions(void *rdopts, Size basesize,
char *itempos = ((char *) rdopts) + elems[j].offset;
char *string_val;
- /*
- * If isset_offset is provided, store whether the reloption is
- * set there.
- */
- if (elems[j].isset_offset > 0)
- {
- char *setpos = ((char *) rdopts) + elems[j].isset_offset;
-
- *(bool *) setpos = options[i].isset;
- }
-
switch (options[i].gen->type)
{
case RELOPT_TYPE_BOOL:
@@ -1798,6 +1871,11 @@ fillRelOptions(void *rdopts, Size basesize,
options[i].values.bool_val :
((relopt_bool *) options[i].gen)->default_val;
break;
+ case RELOPT_TYPE_TERNARY:
+ *(ternary *) itempos = options[i].isset ?
+ options[i].values.ternary_val :
+ ((relopt_ternary *) options[i].gen)->default_val;
+ break;
case RELOPT_TYPE_INT:
*(int *) itempos = options[i].isset ?
options[i].values.int_val :
@@ -1912,8 +1990,8 @@ default_reloptions(Datum reloptions, bool validate, relopt_kind kind)
offsetof(StdRdOptions, parallel_workers)},
{"vacuum_index_cleanup", RELOPT_TYPE_ENUM,
offsetof(StdRdOptions, vacuum_index_cleanup)},
- {"vacuum_truncate", RELOPT_TYPE_BOOL,
- offsetof(StdRdOptions, vacuum_truncate), offsetof(StdRdOptions, vacuum_truncate_set)},
+ {"vacuum_truncate", RELOPT_TYPE_TERNARY,
+ offsetof(StdRdOptions, vacuum_truncate)},
{"vacuum_max_eager_freeze_failure_rate", RELOPT_TYPE_REAL,
offsetof(StdRdOptions, vacuum_max_eager_freeze_failure_rate)}
};
@@ -1993,7 +2071,6 @@ build_local_reloptions(local_relopts *relopts, Datum options, bool validate)
elems[i].optname = opt->option->name;
elems[i].opttype = opt->option->type;
elems[i].offset = opt->offset;
- elems[i].isset_offset = 0; /* not supported for local relopts yet */
i++;
}
diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c
index 733ef40ae7..977babff54 100644
--- a/src/backend/commands/vacuum.c
+++ b/src/backend/commands/vacuum.c
@@ -2221,9 +2221,9 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams params,
{
StdRdOptions *opts = (StdRdOptions *) rel->rd_options;
- if (opts && opts->vacuum_truncate_set)
+ if (opts && opts->vacuum_truncate != TERNARY_UNSET)
{
- if (opts->vacuum_truncate)
+ if (opts->vacuum_truncate == TERNARY_TRUE)
params.truncate = VACOPTVALUE_ENABLED;
else
params.truncate = VACOPTVALUE_DISABLED;
diff --git a/src/include/access/reloptions.h b/src/include/access/reloptions.h
index a604a4702c..a436697658 100644
--- a/src/include/access/reloptions.h
+++ b/src/include/access/reloptions.h
@@ -29,6 +29,7 @@
typedef enum relopt_type
{
RELOPT_TYPE_BOOL,
+ RELOPT_TYPE_TERNARY, /* on, off, unset */
RELOPT_TYPE_INT,
RELOPT_TYPE_REAL,
RELOPT_TYPE_ENUM,
@@ -80,6 +81,7 @@ typedef struct relopt_value
union
{
bool bool_val;
+ ternary ternary_val;
int int_val;
double real_val;
int enum_val;
@@ -94,6 +96,12 @@ typedef struct relopt_bool
bool default_val;
} relopt_bool;
+typedef struct relopt_rernary
+{
+ relopt_gen gen;
+ int default_val;
+} relopt_ternary;
+
typedef struct relopt_int
{
relopt_gen gen;
@@ -152,19 +160,6 @@ typedef struct
const char *optname; /* option's name */
relopt_type opttype; /* option's datatype */
int offset; /* offset of field in result struct */
-
- /*
- * isset_offset is an optional offset of a field in the result struct that
- * stores whether the option is explicitly set for the relation or if it
- * just picked up the default value. In most cases, this can be
- * accomplished by giving the reloption a special out-of-range default
- * value (e.g., some integer reloptions use -2), but this isn't always
- * possible. For example, a Boolean reloption cannot be given an
- * out-of-range default, so we need another way to discover the source of
- * its value. This offset is only used if given a value greater than
- * zero.
- */
- int isset_offset;
} relopt_parse_elt;
/* Local reloption definition */
@@ -195,6 +190,8 @@ typedef struct local_relopts
extern relopt_kind add_reloption_kind(void);
extern void add_bool_reloption(bits32 kinds, const char *name, const char *desc,
bool default_val, LOCKMODE lockmode);
+extern void add_ternary_reloption(bits32 kinds, const char *name,
+ const char *desc, int default_val, LOCKMODE lockmode);
extern void add_int_reloption(bits32 kinds, const char *name, const char *desc,
int default_val, int min_val, int max_val,
LOCKMODE lockmode);
@@ -214,6 +211,9 @@ extern void register_reloptions_validator(local_relopts *relopts,
extern void add_local_bool_reloption(local_relopts *relopts, const char *name,
const char *desc, bool default_val,
int offset);
+extern void add_local_ternary_reloption(local_relopts *relopts,
+ const char *name, const char *desc,
+ ternary default_val, int offset);
extern void add_local_int_reloption(local_relopts *relopts, const char *name,
const char *desc, int default_val,
int min_val, int max_val, int offset);
diff --git a/src/include/c.h b/src/include/c.h
index 39022f8a9d..9c59ccccf8 100644
--- a/src/include/c.h
+++ b/src/include/c.h
@@ -488,6 +488,22 @@ typedef void (*pg_funcptr_t) (void);
#include <stdbool.h>
+/*
+ * ternary
+ * Boolean value with an extrea "unset" option
+ *
+ * Ternary data type is used in relation options that can be "true", "false" or
+ * "unset". Since relation options are used deep inside the PostgreSQL code,
+ * this type is declared globally.
+*/
+
+typedef enum ternary
+{
+ TERNARY_FALSE = 0,
+ TERNARY_TRUE = 1,
+ TERNARY_UNSET = -1
+} ternary;
+
/* ----------------------------------------------------------------
* Section 3: standard system types
diff --git a/src/include/utils/rel.h b/src/include/utils/rel.h
index b552359915..7346d618f9 100644
--- a/src/include/utils/rel.h
+++ b/src/include/utils/rel.h
@@ -346,8 +346,7 @@ typedef struct StdRdOptions
bool user_catalog_table; /* use as an additional catalog relation */
int parallel_workers; /* max number of parallel workers */
StdRdOptIndexCleanup vacuum_index_cleanup; /* controls index vacuuming */
- bool vacuum_truncate; /* enables vacuum to truncate a relation */
- bool vacuum_truncate_set; /* whether vacuum_truncate is set */
+ ternary vacuum_truncate; /* enables vacuum to truncate a relation */
/*
* Fraction of pages in a relation that vacuum can eagerly scan and fail
--
2.39.2
--nextPart4155600.3ZeAukHxDK
Content-Disposition: attachment;
filename="v2a-0003-Add-alias-to-be-used-as-unset-state.patch"
Content-Transfer-Encoding: 7Bit
Content-Type: text/x-patch; charset="unicode-2-0-utf-8";
name="v2a-0003-Add-alias-to-be-used-as-unset-state.patch"
^ permalink raw reply [nested|flat] 868+ messages in thread
* [PATCH v2a 2/4] Introduce ternary reloptions
@ 2025-09-04 14:41 Nikolay Shaplov <[email protected]>
0 siblings, 0 replies; 868+ messages in thread
From: Nikolay Shaplov @ 2025-09-04 14:41 UTC (permalink / raw)
Introduce ternary reloption as a replacement for current `vacuum_truncate`
implementation. Remove `vacuum_truncate_set` additional flag and using
`TERNARY_UNSET` value instead.
---
src/backend/access/common/reloptions.c | 129 ++++++++++++++++++++-----
src/backend/commands/vacuum.c | 4 +-
src/include/access/reloptions.h | 26 ++---
src/include/c.h | 16 +++
src/include/utils/rel.h | 3 +-
5 files changed, 135 insertions(+), 43 deletions(-)
diff --git a/src/backend/access/common/reloptions.c b/src/backend/access/common/reloptions.c
index 0af3fea68f..f543f11001 100644
--- a/src/backend/access/common/reloptions.c
+++ b/src/backend/access/common/reloptions.c
@@ -40,9 +40,9 @@
*
* To add an option:
*
- * (i) decide on a type (bool, integer, real, enum, string), name, default
- * value, upper and lower bounds (if applicable); for strings, consider a
- * validation routine.
+ * (i) decide on a type (bool, ternary, integer, real, enum, string), name,
+ * default value, upper and lower bounds (if applicable); for strings,
+ * consider a validation routine.
* (ii) add a record below (or use add_<type>_reloption).
* (iii) add it to the appropriate options struct (perhaps StdRdOptions)
* (iv) add it to the appropriate handling routine (perhaps
@@ -147,15 +147,6 @@ static relopt_bool boolRelOpts[] =
},
false
},
- {
- {
- "vacuum_truncate",
- "Enables vacuum to truncate empty pages at the end of this table",
- RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
- ShareUpdateExclusiveLock
- },
- true
- },
{
{
"deduplicate_items",
@@ -170,6 +161,21 @@ static relopt_bool boolRelOpts[] =
{{NULL}}
};
+static relopt_ternary ternaryRelOpts[] =
+{
+ {
+ {
+ "vacuum_truncate",
+ "Enables vacuum to truncate empty pages at the end of this table",
+ RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
+ ShareUpdateExclusiveLock
+ },
+ TERNARY_UNSET
+ },
+ /* list terminator */
+ {{NULL}}
+};
+
static relopt_int intRelOpts[] =
{
{
@@ -600,6 +606,13 @@ initialize_reloptions(void)
boolRelOpts[i].gen.lockmode));
j++;
}
+ for (i = 0; ternaryRelOpts[i].gen.name; i++)
+ {
+ Assert(DoLockModesConflict(ternaryRelOpts[i].gen.lockmode,
+ ternaryRelOpts[i].gen.lockmode));
+ j++;
+ }
+
for (i = 0; intRelOpts[i].gen.name; i++)
{
Assert(DoLockModesConflict(intRelOpts[i].gen.lockmode,
@@ -640,6 +653,14 @@ initialize_reloptions(void)
j++;
}
+ for (i = 0; ternaryRelOpts[i].gen.name; i++)
+ {
+ relOpts[j] = &ternaryRelOpts[i].gen;
+ relOpts[j]->type = RELOPT_TYPE_TERNARY;
+ relOpts[j]->namelen = strlen(relOpts[j]->name);
+ j++;
+ }
+
for (i = 0; intRelOpts[i].gen.name; i++)
{
relOpts[j] = &intRelOpts[i].gen;
@@ -800,6 +821,9 @@ allocate_reloption(bits32 kinds, int type, const char *name, const char *desc,
case RELOPT_TYPE_BOOL:
size = sizeof(relopt_bool);
break;
+ case RELOPT_TYPE_TERNARY:
+ size = sizeof(relopt_ternary);
+ break;
case RELOPT_TYPE_INT:
size = sizeof(relopt_int);
break;
@@ -883,6 +907,54 @@ add_local_bool_reloption(local_relopts *relopts, const char *name,
add_local_reloption(relopts, (relopt_gen *) newoption, offset);
}
+/*
+ * init_ternary_reloption
+ * Allocate and initialize a new ternary reloption
+ */
+static relopt_ternary *
+init_ternary_reloption(bits32 kinds, const char *name, const char *desc,
+ ternary default_val, LOCKMODE lockmode)
+{
+ relopt_ternary *newoption;
+
+ newoption = (relopt_ternary *) allocate_reloption(kinds,
+ RELOPT_TYPE_TERNARY, name, desc, lockmode);
+ newoption->default_val = default_val;
+
+ return newoption;
+}
+
+/*
+ * add_ternary_reloption
+ * Add a new ternary reloption
+ */
+void
+add_ternary_reloption(bits32 kinds, const char *name, const char *desc,
+ ternary default_val, LOCKMODE lockmode)
+{
+ relopt_ternary *newoption = init_ternary_reloption(kinds, name, desc,
+ default_val, lockmode);
+
+ add_reloption((relopt_gen *) newoption);
+}
+
+/*
+ * add_local_ternary_reloption
+ * Add a new ternary local reloption
+ *
+ * 'offset' is offset of ternary-typed field.
+ */
+void
+add_local_ternary_reloption(local_relopts *relopts, const char *name,
+ const char *desc, ternary default_val,
+ int offset)
+{
+ relopt_ternary *newoption = init_ternary_reloption(RELOPT_KIND_LOCAL,
+ name, desc,
+ default_val, 0);
+
+ add_local_reloption(relopts, (relopt_gen *) newoption, offset);
+}
/*
* init_real_reloption
@@ -1617,6 +1689,18 @@ parse_one_reloption(relopt_value *option, char *text_str, int text_len,
option->gen->name, value)));
}
break;
+ case RELOPT_TYPE_TERNARY:
+ {
+ bool b;
+ parsed = parse_bool(value, &b);
+ option->values.ternary_val = b ? TERNARY_TRUE : TERNARY_FALSE;
+ if (validate && !parsed)
+ ereport(ERROR,
+ (errcode(ERRCODE_INVALID_PARAMETER_VALUE),
+ errmsg("invalid value for ternary option \"%s\": %s",
+ option->gen->name, value)));
+ }
+ break;
case RELOPT_TYPE_INT:
{
relopt_int *optint = (relopt_int *) option->gen;
@@ -1780,17 +1864,6 @@ fillRelOptions(void *rdopts, Size basesize,
char *itempos = ((char *) rdopts) + elems[j].offset;
char *string_val;
- /*
- * If isset_offset is provided, store whether the reloption is
- * set there.
- */
- if (elems[j].isset_offset > 0)
- {
- char *setpos = ((char *) rdopts) + elems[j].isset_offset;
-
- *(bool *) setpos = options[i].isset;
- }
-
switch (options[i].gen->type)
{
case RELOPT_TYPE_BOOL:
@@ -1798,6 +1871,11 @@ fillRelOptions(void *rdopts, Size basesize,
options[i].values.bool_val :
((relopt_bool *) options[i].gen)->default_val;
break;
+ case RELOPT_TYPE_TERNARY:
+ *(ternary *) itempos = options[i].isset ?
+ options[i].values.ternary_val :
+ ((relopt_ternary *) options[i].gen)->default_val;
+ break;
case RELOPT_TYPE_INT:
*(int *) itempos = options[i].isset ?
options[i].values.int_val :
@@ -1912,8 +1990,8 @@ default_reloptions(Datum reloptions, bool validate, relopt_kind kind)
offsetof(StdRdOptions, parallel_workers)},
{"vacuum_index_cleanup", RELOPT_TYPE_ENUM,
offsetof(StdRdOptions, vacuum_index_cleanup)},
- {"vacuum_truncate", RELOPT_TYPE_BOOL,
- offsetof(StdRdOptions, vacuum_truncate), offsetof(StdRdOptions, vacuum_truncate_set)},
+ {"vacuum_truncate", RELOPT_TYPE_TERNARY,
+ offsetof(StdRdOptions, vacuum_truncate)},
{"vacuum_max_eager_freeze_failure_rate", RELOPT_TYPE_REAL,
offsetof(StdRdOptions, vacuum_max_eager_freeze_failure_rate)}
};
@@ -1993,7 +2071,6 @@ build_local_reloptions(local_relopts *relopts, Datum options, bool validate)
elems[i].optname = opt->option->name;
elems[i].opttype = opt->option->type;
elems[i].offset = opt->offset;
- elems[i].isset_offset = 0; /* not supported for local relopts yet */
i++;
}
diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c
index 733ef40ae7..977babff54 100644
--- a/src/backend/commands/vacuum.c
+++ b/src/backend/commands/vacuum.c
@@ -2221,9 +2221,9 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams params,
{
StdRdOptions *opts = (StdRdOptions *) rel->rd_options;
- if (opts && opts->vacuum_truncate_set)
+ if (opts && opts->vacuum_truncate != TERNARY_UNSET)
{
- if (opts->vacuum_truncate)
+ if (opts->vacuum_truncate == TERNARY_TRUE)
params.truncate = VACOPTVALUE_ENABLED;
else
params.truncate = VACOPTVALUE_DISABLED;
diff --git a/src/include/access/reloptions.h b/src/include/access/reloptions.h
index a604a4702c..a436697658 100644
--- a/src/include/access/reloptions.h
+++ b/src/include/access/reloptions.h
@@ -29,6 +29,7 @@
typedef enum relopt_type
{
RELOPT_TYPE_BOOL,
+ RELOPT_TYPE_TERNARY, /* on, off, unset */
RELOPT_TYPE_INT,
RELOPT_TYPE_REAL,
RELOPT_TYPE_ENUM,
@@ -80,6 +81,7 @@ typedef struct relopt_value
union
{
bool bool_val;
+ ternary ternary_val;
int int_val;
double real_val;
int enum_val;
@@ -94,6 +96,12 @@ typedef struct relopt_bool
bool default_val;
} relopt_bool;
+typedef struct relopt_rernary
+{
+ relopt_gen gen;
+ int default_val;
+} relopt_ternary;
+
typedef struct relopt_int
{
relopt_gen gen;
@@ -152,19 +160,6 @@ typedef struct
const char *optname; /* option's name */
relopt_type opttype; /* option's datatype */
int offset; /* offset of field in result struct */
-
- /*
- * isset_offset is an optional offset of a field in the result struct that
- * stores whether the option is explicitly set for the relation or if it
- * just picked up the default value. In most cases, this can be
- * accomplished by giving the reloption a special out-of-range default
- * value (e.g., some integer reloptions use -2), but this isn't always
- * possible. For example, a Boolean reloption cannot be given an
- * out-of-range default, so we need another way to discover the source of
- * its value. This offset is only used if given a value greater than
- * zero.
- */
- int isset_offset;
} relopt_parse_elt;
/* Local reloption definition */
@@ -195,6 +190,8 @@ typedef struct local_relopts
extern relopt_kind add_reloption_kind(void);
extern void add_bool_reloption(bits32 kinds, const char *name, const char *desc,
bool default_val, LOCKMODE lockmode);
+extern void add_ternary_reloption(bits32 kinds, const char *name,
+ const char *desc, int default_val, LOCKMODE lockmode);
extern void add_int_reloption(bits32 kinds, const char *name, const char *desc,
int default_val, int min_val, int max_val,
LOCKMODE lockmode);
@@ -214,6 +211,9 @@ extern void register_reloptions_validator(local_relopts *relopts,
extern void add_local_bool_reloption(local_relopts *relopts, const char *name,
const char *desc, bool default_val,
int offset);
+extern void add_local_ternary_reloption(local_relopts *relopts,
+ const char *name, const char *desc,
+ ternary default_val, int offset);
extern void add_local_int_reloption(local_relopts *relopts, const char *name,
const char *desc, int default_val,
int min_val, int max_val, int offset);
diff --git a/src/include/c.h b/src/include/c.h
index 39022f8a9d..9c59ccccf8 100644
--- a/src/include/c.h
+++ b/src/include/c.h
@@ -488,6 +488,22 @@ typedef void (*pg_funcptr_t) (void);
#include <stdbool.h>
+/*
+ * ternary
+ * Boolean value with an extrea "unset" option
+ *
+ * Ternary data type is used in relation options that can be "true", "false" or
+ * "unset". Since relation options are used deep inside the PostgreSQL code,
+ * this type is declared globally.
+*/
+
+typedef enum ternary
+{
+ TERNARY_FALSE = 0,
+ TERNARY_TRUE = 1,
+ TERNARY_UNSET = -1
+} ternary;
+
/* ----------------------------------------------------------------
* Section 3: standard system types
diff --git a/src/include/utils/rel.h b/src/include/utils/rel.h
index b552359915..7346d618f9 100644
--- a/src/include/utils/rel.h
+++ b/src/include/utils/rel.h
@@ -346,8 +346,7 @@ typedef struct StdRdOptions
bool user_catalog_table; /* use as an additional catalog relation */
int parallel_workers; /* max number of parallel workers */
StdRdOptIndexCleanup vacuum_index_cleanup; /* controls index vacuuming */
- bool vacuum_truncate; /* enables vacuum to truncate a relation */
- bool vacuum_truncate_set; /* whether vacuum_truncate is set */
+ ternary vacuum_truncate; /* enables vacuum to truncate a relation */
/*
* Fraction of pages in a relation that vacuum can eagerly scan and fail
--
2.39.2
--nextPart4155600.3ZeAukHxDK
Content-Disposition: attachment;
filename="v2a-0003-Add-alias-to-be-used-as-unset-state.patch"
Content-Transfer-Encoding: 7Bit
Content-Type: text/x-patch; charset="unicode-2-0-utf-8";
name="v2a-0003-Add-alias-to-be-used-as-unset-state.patch"
^ permalink raw reply [nested|flat] 868+ messages in thread
* [PATCH v2 2/4] Introduce ternary reloptions
@ 2025-09-04 14:41 Nikolay Shaplov <[email protected]>
0 siblings, 0 replies; 868+ messages in thread
From: Nikolay Shaplov @ 2025-09-04 14:41 UTC (permalink / raw)
Introduce ternary reloption as a replacement for current `vacuum_truncate`
implementation. Remove `vacuum_truncate_set` additional flag and using
`TERNARY_UNSET` value instead.
---
src/backend/access/common/reloptions.c | 129 ++++++++++++++++++++-----
src/backend/commands/vacuum.c | 4 +-
src/include/access/reloptions.h | 26 ++---
src/include/c.h | 16 +++
src/include/utils/rel.h | 3 +-
5 files changed, 135 insertions(+), 43 deletions(-)
diff --git a/src/backend/access/common/reloptions.c b/src/backend/access/common/reloptions.c
index 0af3fea68f..f543f11001 100644
--- a/src/backend/access/common/reloptions.c
+++ b/src/backend/access/common/reloptions.c
@@ -40,9 +40,9 @@
*
* To add an option:
*
- * (i) decide on a type (bool, integer, real, enum, string), name, default
- * value, upper and lower bounds (if applicable); for strings, consider a
- * validation routine.
+ * (i) decide on a type (bool, ternary, integer, real, enum, string), name,
+ * default value, upper and lower bounds (if applicable); for strings,
+ * consider a validation routine.
* (ii) add a record below (or use add_<type>_reloption).
* (iii) add it to the appropriate options struct (perhaps StdRdOptions)
* (iv) add it to the appropriate handling routine (perhaps
@@ -147,15 +147,6 @@ static relopt_bool boolRelOpts[] =
},
false
},
- {
- {
- "vacuum_truncate",
- "Enables vacuum to truncate empty pages at the end of this table",
- RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
- ShareUpdateExclusiveLock
- },
- true
- },
{
{
"deduplicate_items",
@@ -170,6 +161,21 @@ static relopt_bool boolRelOpts[] =
{{NULL}}
};
+static relopt_ternary ternaryRelOpts[] =
+{
+ {
+ {
+ "vacuum_truncate",
+ "Enables vacuum to truncate empty pages at the end of this table",
+ RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
+ ShareUpdateExclusiveLock
+ },
+ TERNARY_UNSET
+ },
+ /* list terminator */
+ {{NULL}}
+};
+
static relopt_int intRelOpts[] =
{
{
@@ -600,6 +606,13 @@ initialize_reloptions(void)
boolRelOpts[i].gen.lockmode));
j++;
}
+ for (i = 0; ternaryRelOpts[i].gen.name; i++)
+ {
+ Assert(DoLockModesConflict(ternaryRelOpts[i].gen.lockmode,
+ ternaryRelOpts[i].gen.lockmode));
+ j++;
+ }
+
for (i = 0; intRelOpts[i].gen.name; i++)
{
Assert(DoLockModesConflict(intRelOpts[i].gen.lockmode,
@@ -640,6 +653,14 @@ initialize_reloptions(void)
j++;
}
+ for (i = 0; ternaryRelOpts[i].gen.name; i++)
+ {
+ relOpts[j] = &ternaryRelOpts[i].gen;
+ relOpts[j]->type = RELOPT_TYPE_TERNARY;
+ relOpts[j]->namelen = strlen(relOpts[j]->name);
+ j++;
+ }
+
for (i = 0; intRelOpts[i].gen.name; i++)
{
relOpts[j] = &intRelOpts[i].gen;
@@ -800,6 +821,9 @@ allocate_reloption(bits32 kinds, int type, const char *name, const char *desc,
case RELOPT_TYPE_BOOL:
size = sizeof(relopt_bool);
break;
+ case RELOPT_TYPE_TERNARY:
+ size = sizeof(relopt_ternary);
+ break;
case RELOPT_TYPE_INT:
size = sizeof(relopt_int);
break;
@@ -883,6 +907,54 @@ add_local_bool_reloption(local_relopts *relopts, const char *name,
add_local_reloption(relopts, (relopt_gen *) newoption, offset);
}
+/*
+ * init_ternary_reloption
+ * Allocate and initialize a new ternary reloption
+ */
+static relopt_ternary *
+init_ternary_reloption(bits32 kinds, const char *name, const char *desc,
+ ternary default_val, LOCKMODE lockmode)
+{
+ relopt_ternary *newoption;
+
+ newoption = (relopt_ternary *) allocate_reloption(kinds,
+ RELOPT_TYPE_TERNARY, name, desc, lockmode);
+ newoption->default_val = default_val;
+
+ return newoption;
+}
+
+/*
+ * add_ternary_reloption
+ * Add a new ternary reloption
+ */
+void
+add_ternary_reloption(bits32 kinds, const char *name, const char *desc,
+ ternary default_val, LOCKMODE lockmode)
+{
+ relopt_ternary *newoption = init_ternary_reloption(kinds, name, desc,
+ default_val, lockmode);
+
+ add_reloption((relopt_gen *) newoption);
+}
+
+/*
+ * add_local_ternary_reloption
+ * Add a new ternary local reloption
+ *
+ * 'offset' is offset of ternary-typed field.
+ */
+void
+add_local_ternary_reloption(local_relopts *relopts, const char *name,
+ const char *desc, ternary default_val,
+ int offset)
+{
+ relopt_ternary *newoption = init_ternary_reloption(RELOPT_KIND_LOCAL,
+ name, desc,
+ default_val, 0);
+
+ add_local_reloption(relopts, (relopt_gen *) newoption, offset);
+}
/*
* init_real_reloption
@@ -1617,6 +1689,18 @@ parse_one_reloption(relopt_value *option, char *text_str, int text_len,
option->gen->name, value)));
}
break;
+ case RELOPT_TYPE_TERNARY:
+ {
+ bool b;
+ parsed = parse_bool(value, &b);
+ option->values.ternary_val = b ? TERNARY_TRUE : TERNARY_FALSE;
+ if (validate && !parsed)
+ ereport(ERROR,
+ (errcode(ERRCODE_INVALID_PARAMETER_VALUE),
+ errmsg("invalid value for ternary option \"%s\": %s",
+ option->gen->name, value)));
+ }
+ break;
case RELOPT_TYPE_INT:
{
relopt_int *optint = (relopt_int *) option->gen;
@@ -1780,17 +1864,6 @@ fillRelOptions(void *rdopts, Size basesize,
char *itempos = ((char *) rdopts) + elems[j].offset;
char *string_val;
- /*
- * If isset_offset is provided, store whether the reloption is
- * set there.
- */
- if (elems[j].isset_offset > 0)
- {
- char *setpos = ((char *) rdopts) + elems[j].isset_offset;
-
- *(bool *) setpos = options[i].isset;
- }
-
switch (options[i].gen->type)
{
case RELOPT_TYPE_BOOL:
@@ -1798,6 +1871,11 @@ fillRelOptions(void *rdopts, Size basesize,
options[i].values.bool_val :
((relopt_bool *) options[i].gen)->default_val;
break;
+ case RELOPT_TYPE_TERNARY:
+ *(ternary *) itempos = options[i].isset ?
+ options[i].values.ternary_val :
+ ((relopt_ternary *) options[i].gen)->default_val;
+ break;
case RELOPT_TYPE_INT:
*(int *) itempos = options[i].isset ?
options[i].values.int_val :
@@ -1912,8 +1990,8 @@ default_reloptions(Datum reloptions, bool validate, relopt_kind kind)
offsetof(StdRdOptions, parallel_workers)},
{"vacuum_index_cleanup", RELOPT_TYPE_ENUM,
offsetof(StdRdOptions, vacuum_index_cleanup)},
- {"vacuum_truncate", RELOPT_TYPE_BOOL,
- offsetof(StdRdOptions, vacuum_truncate), offsetof(StdRdOptions, vacuum_truncate_set)},
+ {"vacuum_truncate", RELOPT_TYPE_TERNARY,
+ offsetof(StdRdOptions, vacuum_truncate)},
{"vacuum_max_eager_freeze_failure_rate", RELOPT_TYPE_REAL,
offsetof(StdRdOptions, vacuum_max_eager_freeze_failure_rate)}
};
@@ -1993,7 +2071,6 @@ build_local_reloptions(local_relopts *relopts, Datum options, bool validate)
elems[i].optname = opt->option->name;
elems[i].opttype = opt->option->type;
elems[i].offset = opt->offset;
- elems[i].isset_offset = 0; /* not supported for local relopts yet */
i++;
}
diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c
index 733ef40ae7..977babff54 100644
--- a/src/backend/commands/vacuum.c
+++ b/src/backend/commands/vacuum.c
@@ -2221,9 +2221,9 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams params,
{
StdRdOptions *opts = (StdRdOptions *) rel->rd_options;
- if (opts && opts->vacuum_truncate_set)
+ if (opts && opts->vacuum_truncate != TERNARY_UNSET)
{
- if (opts->vacuum_truncate)
+ if (opts->vacuum_truncate == TERNARY_TRUE)
params.truncate = VACOPTVALUE_ENABLED;
else
params.truncate = VACOPTVALUE_DISABLED;
diff --git a/src/include/access/reloptions.h b/src/include/access/reloptions.h
index a604a4702c..a436697658 100644
--- a/src/include/access/reloptions.h
+++ b/src/include/access/reloptions.h
@@ -29,6 +29,7 @@
typedef enum relopt_type
{
RELOPT_TYPE_BOOL,
+ RELOPT_TYPE_TERNARY, /* on, off, unset */
RELOPT_TYPE_INT,
RELOPT_TYPE_REAL,
RELOPT_TYPE_ENUM,
@@ -80,6 +81,7 @@ typedef struct relopt_value
union
{
bool bool_val;
+ ternary ternary_val;
int int_val;
double real_val;
int enum_val;
@@ -94,6 +96,12 @@ typedef struct relopt_bool
bool default_val;
} relopt_bool;
+typedef struct relopt_rernary
+{
+ relopt_gen gen;
+ int default_val;
+} relopt_ternary;
+
typedef struct relopt_int
{
relopt_gen gen;
@@ -152,19 +160,6 @@ typedef struct
const char *optname; /* option's name */
relopt_type opttype; /* option's datatype */
int offset; /* offset of field in result struct */
-
- /*
- * isset_offset is an optional offset of a field in the result struct that
- * stores whether the option is explicitly set for the relation or if it
- * just picked up the default value. In most cases, this can be
- * accomplished by giving the reloption a special out-of-range default
- * value (e.g., some integer reloptions use -2), but this isn't always
- * possible. For example, a Boolean reloption cannot be given an
- * out-of-range default, so we need another way to discover the source of
- * its value. This offset is only used if given a value greater than
- * zero.
- */
- int isset_offset;
} relopt_parse_elt;
/* Local reloption definition */
@@ -195,6 +190,8 @@ typedef struct local_relopts
extern relopt_kind add_reloption_kind(void);
extern void add_bool_reloption(bits32 kinds, const char *name, const char *desc,
bool default_val, LOCKMODE lockmode);
+extern void add_ternary_reloption(bits32 kinds, const char *name,
+ const char *desc, int default_val, LOCKMODE lockmode);
extern void add_int_reloption(bits32 kinds, const char *name, const char *desc,
int default_val, int min_val, int max_val,
LOCKMODE lockmode);
@@ -214,6 +211,9 @@ extern void register_reloptions_validator(local_relopts *relopts,
extern void add_local_bool_reloption(local_relopts *relopts, const char *name,
const char *desc, bool default_val,
int offset);
+extern void add_local_ternary_reloption(local_relopts *relopts,
+ const char *name, const char *desc,
+ ternary default_val, int offset);
extern void add_local_int_reloption(local_relopts *relopts, const char *name,
const char *desc, int default_val,
int min_val, int max_val, int offset);
diff --git a/src/include/c.h b/src/include/c.h
index 39022f8a9d..9c59ccccf8 100644
--- a/src/include/c.h
+++ b/src/include/c.h
@@ -488,6 +488,22 @@ typedef void (*pg_funcptr_t) (void);
#include <stdbool.h>
+/*
+ * ternary
+ * Boolean value with an extrea "unset" option
+ *
+ * Ternary data type is used in relation options that can be "true", "false" or
+ * "unset". Since relation options are used deep inside the PostgreSQL code,
+ * this type is declared globally.
+*/
+
+typedef enum ternary
+{
+ TERNARY_FALSE = 0,
+ TERNARY_TRUE = 1,
+ TERNARY_UNSET = -1
+} ternary;
+
/* ----------------------------------------------------------------
* Section 3: standard system types
diff --git a/src/include/utils/rel.h b/src/include/utils/rel.h
index b552359915..7346d618f9 100644
--- a/src/include/utils/rel.h
+++ b/src/include/utils/rel.h
@@ -346,8 +346,7 @@ typedef struct StdRdOptions
bool user_catalog_table; /* use as an additional catalog relation */
int parallel_workers; /* max number of parallel workers */
StdRdOptIndexCleanup vacuum_index_cleanup; /* controls index vacuuming */
- bool vacuum_truncate; /* enables vacuum to truncate a relation */
- bool vacuum_truncate_set; /* whether vacuum_truncate is set */
+ ternary vacuum_truncate; /* enables vacuum to truncate a relation */
/*
* Fraction of pages in a relation that vacuum can eagerly scan and fail
--
2.39.2
--nextPart5129932.5fSG56mABF
Content-Disposition: attachment;
filename="v2-0003-Add-alias-to-be-used-as-unset-state.patch"
Content-Transfer-Encoding: 7Bit
Content-Type: text/x-patch; charset="unicode-2-0-utf-8";
name="v2-0003-Add-alias-to-be-used-as-unset-state.patch"
^ permalink raw reply [nested|flat] 868+ messages in thread
* [PATCH v2 2/4] Introduce ternary reloptions
@ 2025-09-04 14:41 Nikolay Shaplov <[email protected]>
0 siblings, 0 replies; 868+ messages in thread
From: Nikolay Shaplov @ 2025-09-04 14:41 UTC (permalink / raw)
Introduce ternary reloption as a replacement for current `vacuum_truncate`
implementation. Remove `vacuum_truncate_set` additional flag and using
`TERNARY_UNSET` value instead.
---
src/backend/access/common/reloptions.c | 129 ++++++++++++++++++++-----
src/backend/commands/vacuum.c | 4 +-
src/include/access/reloptions.h | 26 ++---
src/include/c.h | 16 +++
src/include/utils/rel.h | 3 +-
5 files changed, 135 insertions(+), 43 deletions(-)
diff --git a/src/backend/access/common/reloptions.c b/src/backend/access/common/reloptions.c
index 0af3fea68f..f543f11001 100644
--- a/src/backend/access/common/reloptions.c
+++ b/src/backend/access/common/reloptions.c
@@ -40,9 +40,9 @@
*
* To add an option:
*
- * (i) decide on a type (bool, integer, real, enum, string), name, default
- * value, upper and lower bounds (if applicable); for strings, consider a
- * validation routine.
+ * (i) decide on a type (bool, ternary, integer, real, enum, string), name,
+ * default value, upper and lower bounds (if applicable); for strings,
+ * consider a validation routine.
* (ii) add a record below (or use add_<type>_reloption).
* (iii) add it to the appropriate options struct (perhaps StdRdOptions)
* (iv) add it to the appropriate handling routine (perhaps
@@ -147,15 +147,6 @@ static relopt_bool boolRelOpts[] =
},
false
},
- {
- {
- "vacuum_truncate",
- "Enables vacuum to truncate empty pages at the end of this table",
- RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
- ShareUpdateExclusiveLock
- },
- true
- },
{
{
"deduplicate_items",
@@ -170,6 +161,21 @@ static relopt_bool boolRelOpts[] =
{{NULL}}
};
+static relopt_ternary ternaryRelOpts[] =
+{
+ {
+ {
+ "vacuum_truncate",
+ "Enables vacuum to truncate empty pages at the end of this table",
+ RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
+ ShareUpdateExclusiveLock
+ },
+ TERNARY_UNSET
+ },
+ /* list terminator */
+ {{NULL}}
+};
+
static relopt_int intRelOpts[] =
{
{
@@ -600,6 +606,13 @@ initialize_reloptions(void)
boolRelOpts[i].gen.lockmode));
j++;
}
+ for (i = 0; ternaryRelOpts[i].gen.name; i++)
+ {
+ Assert(DoLockModesConflict(ternaryRelOpts[i].gen.lockmode,
+ ternaryRelOpts[i].gen.lockmode));
+ j++;
+ }
+
for (i = 0; intRelOpts[i].gen.name; i++)
{
Assert(DoLockModesConflict(intRelOpts[i].gen.lockmode,
@@ -640,6 +653,14 @@ initialize_reloptions(void)
j++;
}
+ for (i = 0; ternaryRelOpts[i].gen.name; i++)
+ {
+ relOpts[j] = &ternaryRelOpts[i].gen;
+ relOpts[j]->type = RELOPT_TYPE_TERNARY;
+ relOpts[j]->namelen = strlen(relOpts[j]->name);
+ j++;
+ }
+
for (i = 0; intRelOpts[i].gen.name; i++)
{
relOpts[j] = &intRelOpts[i].gen;
@@ -800,6 +821,9 @@ allocate_reloption(bits32 kinds, int type, const char *name, const char *desc,
case RELOPT_TYPE_BOOL:
size = sizeof(relopt_bool);
break;
+ case RELOPT_TYPE_TERNARY:
+ size = sizeof(relopt_ternary);
+ break;
case RELOPT_TYPE_INT:
size = sizeof(relopt_int);
break;
@@ -883,6 +907,54 @@ add_local_bool_reloption(local_relopts *relopts, const char *name,
add_local_reloption(relopts, (relopt_gen *) newoption, offset);
}
+/*
+ * init_ternary_reloption
+ * Allocate and initialize a new ternary reloption
+ */
+static relopt_ternary *
+init_ternary_reloption(bits32 kinds, const char *name, const char *desc,
+ ternary default_val, LOCKMODE lockmode)
+{
+ relopt_ternary *newoption;
+
+ newoption = (relopt_ternary *) allocate_reloption(kinds,
+ RELOPT_TYPE_TERNARY, name, desc, lockmode);
+ newoption->default_val = default_val;
+
+ return newoption;
+}
+
+/*
+ * add_ternary_reloption
+ * Add a new ternary reloption
+ */
+void
+add_ternary_reloption(bits32 kinds, const char *name, const char *desc,
+ ternary default_val, LOCKMODE lockmode)
+{
+ relopt_ternary *newoption = init_ternary_reloption(kinds, name, desc,
+ default_val, lockmode);
+
+ add_reloption((relopt_gen *) newoption);
+}
+
+/*
+ * add_local_ternary_reloption
+ * Add a new ternary local reloption
+ *
+ * 'offset' is offset of ternary-typed field.
+ */
+void
+add_local_ternary_reloption(local_relopts *relopts, const char *name,
+ const char *desc, ternary default_val,
+ int offset)
+{
+ relopt_ternary *newoption = init_ternary_reloption(RELOPT_KIND_LOCAL,
+ name, desc,
+ default_val, 0);
+
+ add_local_reloption(relopts, (relopt_gen *) newoption, offset);
+}
/*
* init_real_reloption
@@ -1617,6 +1689,18 @@ parse_one_reloption(relopt_value *option, char *text_str, int text_len,
option->gen->name, value)));
}
break;
+ case RELOPT_TYPE_TERNARY:
+ {
+ bool b;
+ parsed = parse_bool(value, &b);
+ option->values.ternary_val = b ? TERNARY_TRUE : TERNARY_FALSE;
+ if (validate && !parsed)
+ ereport(ERROR,
+ (errcode(ERRCODE_INVALID_PARAMETER_VALUE),
+ errmsg("invalid value for ternary option \"%s\": %s",
+ option->gen->name, value)));
+ }
+ break;
case RELOPT_TYPE_INT:
{
relopt_int *optint = (relopt_int *) option->gen;
@@ -1780,17 +1864,6 @@ fillRelOptions(void *rdopts, Size basesize,
char *itempos = ((char *) rdopts) + elems[j].offset;
char *string_val;
- /*
- * If isset_offset is provided, store whether the reloption is
- * set there.
- */
- if (elems[j].isset_offset > 0)
- {
- char *setpos = ((char *) rdopts) + elems[j].isset_offset;
-
- *(bool *) setpos = options[i].isset;
- }
-
switch (options[i].gen->type)
{
case RELOPT_TYPE_BOOL:
@@ -1798,6 +1871,11 @@ fillRelOptions(void *rdopts, Size basesize,
options[i].values.bool_val :
((relopt_bool *) options[i].gen)->default_val;
break;
+ case RELOPT_TYPE_TERNARY:
+ *(ternary *) itempos = options[i].isset ?
+ options[i].values.ternary_val :
+ ((relopt_ternary *) options[i].gen)->default_val;
+ break;
case RELOPT_TYPE_INT:
*(int *) itempos = options[i].isset ?
options[i].values.int_val :
@@ -1912,8 +1990,8 @@ default_reloptions(Datum reloptions, bool validate, relopt_kind kind)
offsetof(StdRdOptions, parallel_workers)},
{"vacuum_index_cleanup", RELOPT_TYPE_ENUM,
offsetof(StdRdOptions, vacuum_index_cleanup)},
- {"vacuum_truncate", RELOPT_TYPE_BOOL,
- offsetof(StdRdOptions, vacuum_truncate), offsetof(StdRdOptions, vacuum_truncate_set)},
+ {"vacuum_truncate", RELOPT_TYPE_TERNARY,
+ offsetof(StdRdOptions, vacuum_truncate)},
{"vacuum_max_eager_freeze_failure_rate", RELOPT_TYPE_REAL,
offsetof(StdRdOptions, vacuum_max_eager_freeze_failure_rate)}
};
@@ -1993,7 +2071,6 @@ build_local_reloptions(local_relopts *relopts, Datum options, bool validate)
elems[i].optname = opt->option->name;
elems[i].opttype = opt->option->type;
elems[i].offset = opt->offset;
- elems[i].isset_offset = 0; /* not supported for local relopts yet */
i++;
}
diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c
index 733ef40ae7..977babff54 100644
--- a/src/backend/commands/vacuum.c
+++ b/src/backend/commands/vacuum.c
@@ -2221,9 +2221,9 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams params,
{
StdRdOptions *opts = (StdRdOptions *) rel->rd_options;
- if (opts && opts->vacuum_truncate_set)
+ if (opts && opts->vacuum_truncate != TERNARY_UNSET)
{
- if (opts->vacuum_truncate)
+ if (opts->vacuum_truncate == TERNARY_TRUE)
params.truncate = VACOPTVALUE_ENABLED;
else
params.truncate = VACOPTVALUE_DISABLED;
diff --git a/src/include/access/reloptions.h b/src/include/access/reloptions.h
index a604a4702c..a436697658 100644
--- a/src/include/access/reloptions.h
+++ b/src/include/access/reloptions.h
@@ -29,6 +29,7 @@
typedef enum relopt_type
{
RELOPT_TYPE_BOOL,
+ RELOPT_TYPE_TERNARY, /* on, off, unset */
RELOPT_TYPE_INT,
RELOPT_TYPE_REAL,
RELOPT_TYPE_ENUM,
@@ -80,6 +81,7 @@ typedef struct relopt_value
union
{
bool bool_val;
+ ternary ternary_val;
int int_val;
double real_val;
int enum_val;
@@ -94,6 +96,12 @@ typedef struct relopt_bool
bool default_val;
} relopt_bool;
+typedef struct relopt_rernary
+{
+ relopt_gen gen;
+ int default_val;
+} relopt_ternary;
+
typedef struct relopt_int
{
relopt_gen gen;
@@ -152,19 +160,6 @@ typedef struct
const char *optname; /* option's name */
relopt_type opttype; /* option's datatype */
int offset; /* offset of field in result struct */
-
- /*
- * isset_offset is an optional offset of a field in the result struct that
- * stores whether the option is explicitly set for the relation or if it
- * just picked up the default value. In most cases, this can be
- * accomplished by giving the reloption a special out-of-range default
- * value (e.g., some integer reloptions use -2), but this isn't always
- * possible. For example, a Boolean reloption cannot be given an
- * out-of-range default, so we need another way to discover the source of
- * its value. This offset is only used if given a value greater than
- * zero.
- */
- int isset_offset;
} relopt_parse_elt;
/* Local reloption definition */
@@ -195,6 +190,8 @@ typedef struct local_relopts
extern relopt_kind add_reloption_kind(void);
extern void add_bool_reloption(bits32 kinds, const char *name, const char *desc,
bool default_val, LOCKMODE lockmode);
+extern void add_ternary_reloption(bits32 kinds, const char *name,
+ const char *desc, int default_val, LOCKMODE lockmode);
extern void add_int_reloption(bits32 kinds, const char *name, const char *desc,
int default_val, int min_val, int max_val,
LOCKMODE lockmode);
@@ -214,6 +211,9 @@ extern void register_reloptions_validator(local_relopts *relopts,
extern void add_local_bool_reloption(local_relopts *relopts, const char *name,
const char *desc, bool default_val,
int offset);
+extern void add_local_ternary_reloption(local_relopts *relopts,
+ const char *name, const char *desc,
+ ternary default_val, int offset);
extern void add_local_int_reloption(local_relopts *relopts, const char *name,
const char *desc, int default_val,
int min_val, int max_val, int offset);
diff --git a/src/include/c.h b/src/include/c.h
index 39022f8a9d..9c59ccccf8 100644
--- a/src/include/c.h
+++ b/src/include/c.h
@@ -488,6 +488,22 @@ typedef void (*pg_funcptr_t) (void);
#include <stdbool.h>
+/*
+ * ternary
+ * Boolean value with an extrea "unset" option
+ *
+ * Ternary data type is used in relation options that can be "true", "false" or
+ * "unset". Since relation options are used deep inside the PostgreSQL code,
+ * this type is declared globally.
+*/
+
+typedef enum ternary
+{
+ TERNARY_FALSE = 0,
+ TERNARY_TRUE = 1,
+ TERNARY_UNSET = -1
+} ternary;
+
/* ----------------------------------------------------------------
* Section 3: standard system types
diff --git a/src/include/utils/rel.h b/src/include/utils/rel.h
index b552359915..7346d618f9 100644
--- a/src/include/utils/rel.h
+++ b/src/include/utils/rel.h
@@ -346,8 +346,7 @@ typedef struct StdRdOptions
bool user_catalog_table; /* use as an additional catalog relation */
int parallel_workers; /* max number of parallel workers */
StdRdOptIndexCleanup vacuum_index_cleanup; /* controls index vacuuming */
- bool vacuum_truncate; /* enables vacuum to truncate a relation */
- bool vacuum_truncate_set; /* whether vacuum_truncate is set */
+ ternary vacuum_truncate; /* enables vacuum to truncate a relation */
/*
* Fraction of pages in a relation that vacuum can eagerly scan and fail
--
2.39.2
--nextPart5129932.5fSG56mABF
Content-Disposition: attachment;
filename="v2-0003-Add-alias-to-be-used-as-unset-state.patch"
Content-Transfer-Encoding: 7Bit
Content-Type: text/x-patch; charset="unicode-2-0-utf-8";
name="v2-0003-Add-alias-to-be-used-as-unset-state.patch"
^ permalink raw reply [nested|flat] 868+ messages in thread
* [PATCH v2 2/4] Introduce ternary reloptions
@ 2025-09-04 14:41 Nikolay Shaplov <[email protected]>
0 siblings, 0 replies; 868+ messages in thread
From: Nikolay Shaplov @ 2025-09-04 14:41 UTC (permalink / raw)
Introduce ternary reloption as a replacement for current `vacuum_truncate`
implementation. Remove `vacuum_truncate_set` additional flag and using
`TERNARY_UNSET` value instead.
---
src/backend/access/common/reloptions.c | 129 ++++++++++++++++++++-----
src/backend/commands/vacuum.c | 4 +-
src/include/access/reloptions.h | 26 ++---
src/include/c.h | 16 +++
src/include/utils/rel.h | 3 +-
5 files changed, 135 insertions(+), 43 deletions(-)
diff --git a/src/backend/access/common/reloptions.c b/src/backend/access/common/reloptions.c
index 0af3fea68f..f543f11001 100644
--- a/src/backend/access/common/reloptions.c
+++ b/src/backend/access/common/reloptions.c
@@ -40,9 +40,9 @@
*
* To add an option:
*
- * (i) decide on a type (bool, integer, real, enum, string), name, default
- * value, upper and lower bounds (if applicable); for strings, consider a
- * validation routine.
+ * (i) decide on a type (bool, ternary, integer, real, enum, string), name,
+ * default value, upper and lower bounds (if applicable); for strings,
+ * consider a validation routine.
* (ii) add a record below (or use add_<type>_reloption).
* (iii) add it to the appropriate options struct (perhaps StdRdOptions)
* (iv) add it to the appropriate handling routine (perhaps
@@ -147,15 +147,6 @@ static relopt_bool boolRelOpts[] =
},
false
},
- {
- {
- "vacuum_truncate",
- "Enables vacuum to truncate empty pages at the end of this table",
- RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
- ShareUpdateExclusiveLock
- },
- true
- },
{
{
"deduplicate_items",
@@ -170,6 +161,21 @@ static relopt_bool boolRelOpts[] =
{{NULL}}
};
+static relopt_ternary ternaryRelOpts[] =
+{
+ {
+ {
+ "vacuum_truncate",
+ "Enables vacuum to truncate empty pages at the end of this table",
+ RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
+ ShareUpdateExclusiveLock
+ },
+ TERNARY_UNSET
+ },
+ /* list terminator */
+ {{NULL}}
+};
+
static relopt_int intRelOpts[] =
{
{
@@ -600,6 +606,13 @@ initialize_reloptions(void)
boolRelOpts[i].gen.lockmode));
j++;
}
+ for (i = 0; ternaryRelOpts[i].gen.name; i++)
+ {
+ Assert(DoLockModesConflict(ternaryRelOpts[i].gen.lockmode,
+ ternaryRelOpts[i].gen.lockmode));
+ j++;
+ }
+
for (i = 0; intRelOpts[i].gen.name; i++)
{
Assert(DoLockModesConflict(intRelOpts[i].gen.lockmode,
@@ -640,6 +653,14 @@ initialize_reloptions(void)
j++;
}
+ for (i = 0; ternaryRelOpts[i].gen.name; i++)
+ {
+ relOpts[j] = &ternaryRelOpts[i].gen;
+ relOpts[j]->type = RELOPT_TYPE_TERNARY;
+ relOpts[j]->namelen = strlen(relOpts[j]->name);
+ j++;
+ }
+
for (i = 0; intRelOpts[i].gen.name; i++)
{
relOpts[j] = &intRelOpts[i].gen;
@@ -800,6 +821,9 @@ allocate_reloption(bits32 kinds, int type, const char *name, const char *desc,
case RELOPT_TYPE_BOOL:
size = sizeof(relopt_bool);
break;
+ case RELOPT_TYPE_TERNARY:
+ size = sizeof(relopt_ternary);
+ break;
case RELOPT_TYPE_INT:
size = sizeof(relopt_int);
break;
@@ -883,6 +907,54 @@ add_local_bool_reloption(local_relopts *relopts, const char *name,
add_local_reloption(relopts, (relopt_gen *) newoption, offset);
}
+/*
+ * init_ternary_reloption
+ * Allocate and initialize a new ternary reloption
+ */
+static relopt_ternary *
+init_ternary_reloption(bits32 kinds, const char *name, const char *desc,
+ ternary default_val, LOCKMODE lockmode)
+{
+ relopt_ternary *newoption;
+
+ newoption = (relopt_ternary *) allocate_reloption(kinds,
+ RELOPT_TYPE_TERNARY, name, desc, lockmode);
+ newoption->default_val = default_val;
+
+ return newoption;
+}
+
+/*
+ * add_ternary_reloption
+ * Add a new ternary reloption
+ */
+void
+add_ternary_reloption(bits32 kinds, const char *name, const char *desc,
+ ternary default_val, LOCKMODE lockmode)
+{
+ relopt_ternary *newoption = init_ternary_reloption(kinds, name, desc,
+ default_val, lockmode);
+
+ add_reloption((relopt_gen *) newoption);
+}
+
+/*
+ * add_local_ternary_reloption
+ * Add a new ternary local reloption
+ *
+ * 'offset' is offset of ternary-typed field.
+ */
+void
+add_local_ternary_reloption(local_relopts *relopts, const char *name,
+ const char *desc, ternary default_val,
+ int offset)
+{
+ relopt_ternary *newoption = init_ternary_reloption(RELOPT_KIND_LOCAL,
+ name, desc,
+ default_val, 0);
+
+ add_local_reloption(relopts, (relopt_gen *) newoption, offset);
+}
/*
* init_real_reloption
@@ -1617,6 +1689,18 @@ parse_one_reloption(relopt_value *option, char *text_str, int text_len,
option->gen->name, value)));
}
break;
+ case RELOPT_TYPE_TERNARY:
+ {
+ bool b;
+ parsed = parse_bool(value, &b);
+ option->values.ternary_val = b ? TERNARY_TRUE : TERNARY_FALSE;
+ if (validate && !parsed)
+ ereport(ERROR,
+ (errcode(ERRCODE_INVALID_PARAMETER_VALUE),
+ errmsg("invalid value for ternary option \"%s\": %s",
+ option->gen->name, value)));
+ }
+ break;
case RELOPT_TYPE_INT:
{
relopt_int *optint = (relopt_int *) option->gen;
@@ -1780,17 +1864,6 @@ fillRelOptions(void *rdopts, Size basesize,
char *itempos = ((char *) rdopts) + elems[j].offset;
char *string_val;
- /*
- * If isset_offset is provided, store whether the reloption is
- * set there.
- */
- if (elems[j].isset_offset > 0)
- {
- char *setpos = ((char *) rdopts) + elems[j].isset_offset;
-
- *(bool *) setpos = options[i].isset;
- }
-
switch (options[i].gen->type)
{
case RELOPT_TYPE_BOOL:
@@ -1798,6 +1871,11 @@ fillRelOptions(void *rdopts, Size basesize,
options[i].values.bool_val :
((relopt_bool *) options[i].gen)->default_val;
break;
+ case RELOPT_TYPE_TERNARY:
+ *(ternary *) itempos = options[i].isset ?
+ options[i].values.ternary_val :
+ ((relopt_ternary *) options[i].gen)->default_val;
+ break;
case RELOPT_TYPE_INT:
*(int *) itempos = options[i].isset ?
options[i].values.int_val :
@@ -1912,8 +1990,8 @@ default_reloptions(Datum reloptions, bool validate, relopt_kind kind)
offsetof(StdRdOptions, parallel_workers)},
{"vacuum_index_cleanup", RELOPT_TYPE_ENUM,
offsetof(StdRdOptions, vacuum_index_cleanup)},
- {"vacuum_truncate", RELOPT_TYPE_BOOL,
- offsetof(StdRdOptions, vacuum_truncate), offsetof(StdRdOptions, vacuum_truncate_set)},
+ {"vacuum_truncate", RELOPT_TYPE_TERNARY,
+ offsetof(StdRdOptions, vacuum_truncate)},
{"vacuum_max_eager_freeze_failure_rate", RELOPT_TYPE_REAL,
offsetof(StdRdOptions, vacuum_max_eager_freeze_failure_rate)}
};
@@ -1993,7 +2071,6 @@ build_local_reloptions(local_relopts *relopts, Datum options, bool validate)
elems[i].optname = opt->option->name;
elems[i].opttype = opt->option->type;
elems[i].offset = opt->offset;
- elems[i].isset_offset = 0; /* not supported for local relopts yet */
i++;
}
diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c
index 733ef40ae7..977babff54 100644
--- a/src/backend/commands/vacuum.c
+++ b/src/backend/commands/vacuum.c
@@ -2221,9 +2221,9 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams params,
{
StdRdOptions *opts = (StdRdOptions *) rel->rd_options;
- if (opts && opts->vacuum_truncate_set)
+ if (opts && opts->vacuum_truncate != TERNARY_UNSET)
{
- if (opts->vacuum_truncate)
+ if (opts->vacuum_truncate == TERNARY_TRUE)
params.truncate = VACOPTVALUE_ENABLED;
else
params.truncate = VACOPTVALUE_DISABLED;
diff --git a/src/include/access/reloptions.h b/src/include/access/reloptions.h
index a604a4702c..a436697658 100644
--- a/src/include/access/reloptions.h
+++ b/src/include/access/reloptions.h
@@ -29,6 +29,7 @@
typedef enum relopt_type
{
RELOPT_TYPE_BOOL,
+ RELOPT_TYPE_TERNARY, /* on, off, unset */
RELOPT_TYPE_INT,
RELOPT_TYPE_REAL,
RELOPT_TYPE_ENUM,
@@ -80,6 +81,7 @@ typedef struct relopt_value
union
{
bool bool_val;
+ ternary ternary_val;
int int_val;
double real_val;
int enum_val;
@@ -94,6 +96,12 @@ typedef struct relopt_bool
bool default_val;
} relopt_bool;
+typedef struct relopt_rernary
+{
+ relopt_gen gen;
+ int default_val;
+} relopt_ternary;
+
typedef struct relopt_int
{
relopt_gen gen;
@@ -152,19 +160,6 @@ typedef struct
const char *optname; /* option's name */
relopt_type opttype; /* option's datatype */
int offset; /* offset of field in result struct */
-
- /*
- * isset_offset is an optional offset of a field in the result struct that
- * stores whether the option is explicitly set for the relation or if it
- * just picked up the default value. In most cases, this can be
- * accomplished by giving the reloption a special out-of-range default
- * value (e.g., some integer reloptions use -2), but this isn't always
- * possible. For example, a Boolean reloption cannot be given an
- * out-of-range default, so we need another way to discover the source of
- * its value. This offset is only used if given a value greater than
- * zero.
- */
- int isset_offset;
} relopt_parse_elt;
/* Local reloption definition */
@@ -195,6 +190,8 @@ typedef struct local_relopts
extern relopt_kind add_reloption_kind(void);
extern void add_bool_reloption(bits32 kinds, const char *name, const char *desc,
bool default_val, LOCKMODE lockmode);
+extern void add_ternary_reloption(bits32 kinds, const char *name,
+ const char *desc, int default_val, LOCKMODE lockmode);
extern void add_int_reloption(bits32 kinds, const char *name, const char *desc,
int default_val, int min_val, int max_val,
LOCKMODE lockmode);
@@ -214,6 +211,9 @@ extern void register_reloptions_validator(local_relopts *relopts,
extern void add_local_bool_reloption(local_relopts *relopts, const char *name,
const char *desc, bool default_val,
int offset);
+extern void add_local_ternary_reloption(local_relopts *relopts,
+ const char *name, const char *desc,
+ ternary default_val, int offset);
extern void add_local_int_reloption(local_relopts *relopts, const char *name,
const char *desc, int default_val,
int min_val, int max_val, int offset);
diff --git a/src/include/c.h b/src/include/c.h
index 39022f8a9d..9c59ccccf8 100644
--- a/src/include/c.h
+++ b/src/include/c.h
@@ -488,6 +488,22 @@ typedef void (*pg_funcptr_t) (void);
#include <stdbool.h>
+/*
+ * ternary
+ * Boolean value with an extrea "unset" option
+ *
+ * Ternary data type is used in relation options that can be "true", "false" or
+ * "unset". Since relation options are used deep inside the PostgreSQL code,
+ * this type is declared globally.
+*/
+
+typedef enum ternary
+{
+ TERNARY_FALSE = 0,
+ TERNARY_TRUE = 1,
+ TERNARY_UNSET = -1
+} ternary;
+
/* ----------------------------------------------------------------
* Section 3: standard system types
diff --git a/src/include/utils/rel.h b/src/include/utils/rel.h
index b552359915..7346d618f9 100644
--- a/src/include/utils/rel.h
+++ b/src/include/utils/rel.h
@@ -346,8 +346,7 @@ typedef struct StdRdOptions
bool user_catalog_table; /* use as an additional catalog relation */
int parallel_workers; /* max number of parallel workers */
StdRdOptIndexCleanup vacuum_index_cleanup; /* controls index vacuuming */
- bool vacuum_truncate; /* enables vacuum to truncate a relation */
- bool vacuum_truncate_set; /* whether vacuum_truncate is set */
+ ternary vacuum_truncate; /* enables vacuum to truncate a relation */
/*
* Fraction of pages in a relation that vacuum can eagerly scan and fail
--
2.39.2
--nextPart5129932.5fSG56mABF
Content-Disposition: attachment;
filename="v2-0003-Add-alias-to-be-used-as-unset-state.patch"
Content-Transfer-Encoding: 7Bit
Content-Type: text/x-patch; charset="unicode-2-0-utf-8";
name="v2-0003-Add-alias-to-be-used-as-unset-state.patch"
^ permalink raw reply [nested|flat] 868+ messages in thread
* [PATCH v2 2/4] Introduce ternary reloptions
@ 2025-09-04 14:41 Nikolay Shaplov <[email protected]>
0 siblings, 0 replies; 868+ messages in thread
From: Nikolay Shaplov @ 2025-09-04 14:41 UTC (permalink / raw)
Introduce ternary reloption as a replacement for current `vacuum_truncate`
implementation. Remove `vacuum_truncate_set` additional flag and using
`TERNARY_UNSET` value instead.
---
src/backend/access/common/reloptions.c | 129 ++++++++++++++++++++-----
src/backend/commands/vacuum.c | 4 +-
src/include/access/reloptions.h | 26 ++---
src/include/c.h | 16 +++
src/include/utils/rel.h | 3 +-
5 files changed, 135 insertions(+), 43 deletions(-)
diff --git a/src/backend/access/common/reloptions.c b/src/backend/access/common/reloptions.c
index 0af3fea68f..f543f11001 100644
--- a/src/backend/access/common/reloptions.c
+++ b/src/backend/access/common/reloptions.c
@@ -40,9 +40,9 @@
*
* To add an option:
*
- * (i) decide on a type (bool, integer, real, enum, string), name, default
- * value, upper and lower bounds (if applicable); for strings, consider a
- * validation routine.
+ * (i) decide on a type (bool, ternary, integer, real, enum, string), name,
+ * default value, upper and lower bounds (if applicable); for strings,
+ * consider a validation routine.
* (ii) add a record below (or use add_<type>_reloption).
* (iii) add it to the appropriate options struct (perhaps StdRdOptions)
* (iv) add it to the appropriate handling routine (perhaps
@@ -147,15 +147,6 @@ static relopt_bool boolRelOpts[] =
},
false
},
- {
- {
- "vacuum_truncate",
- "Enables vacuum to truncate empty pages at the end of this table",
- RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
- ShareUpdateExclusiveLock
- },
- true
- },
{
{
"deduplicate_items",
@@ -170,6 +161,21 @@ static relopt_bool boolRelOpts[] =
{{NULL}}
};
+static relopt_ternary ternaryRelOpts[] =
+{
+ {
+ {
+ "vacuum_truncate",
+ "Enables vacuum to truncate empty pages at the end of this table",
+ RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
+ ShareUpdateExclusiveLock
+ },
+ TERNARY_UNSET
+ },
+ /* list terminator */
+ {{NULL}}
+};
+
static relopt_int intRelOpts[] =
{
{
@@ -600,6 +606,13 @@ initialize_reloptions(void)
boolRelOpts[i].gen.lockmode));
j++;
}
+ for (i = 0; ternaryRelOpts[i].gen.name; i++)
+ {
+ Assert(DoLockModesConflict(ternaryRelOpts[i].gen.lockmode,
+ ternaryRelOpts[i].gen.lockmode));
+ j++;
+ }
+
for (i = 0; intRelOpts[i].gen.name; i++)
{
Assert(DoLockModesConflict(intRelOpts[i].gen.lockmode,
@@ -640,6 +653,14 @@ initialize_reloptions(void)
j++;
}
+ for (i = 0; ternaryRelOpts[i].gen.name; i++)
+ {
+ relOpts[j] = &ternaryRelOpts[i].gen;
+ relOpts[j]->type = RELOPT_TYPE_TERNARY;
+ relOpts[j]->namelen = strlen(relOpts[j]->name);
+ j++;
+ }
+
for (i = 0; intRelOpts[i].gen.name; i++)
{
relOpts[j] = &intRelOpts[i].gen;
@@ -800,6 +821,9 @@ allocate_reloption(bits32 kinds, int type, const char *name, const char *desc,
case RELOPT_TYPE_BOOL:
size = sizeof(relopt_bool);
break;
+ case RELOPT_TYPE_TERNARY:
+ size = sizeof(relopt_ternary);
+ break;
case RELOPT_TYPE_INT:
size = sizeof(relopt_int);
break;
@@ -883,6 +907,54 @@ add_local_bool_reloption(local_relopts *relopts, const char *name,
add_local_reloption(relopts, (relopt_gen *) newoption, offset);
}
+/*
+ * init_ternary_reloption
+ * Allocate and initialize a new ternary reloption
+ */
+static relopt_ternary *
+init_ternary_reloption(bits32 kinds, const char *name, const char *desc,
+ ternary default_val, LOCKMODE lockmode)
+{
+ relopt_ternary *newoption;
+
+ newoption = (relopt_ternary *) allocate_reloption(kinds,
+ RELOPT_TYPE_TERNARY, name, desc, lockmode);
+ newoption->default_val = default_val;
+
+ return newoption;
+}
+
+/*
+ * add_ternary_reloption
+ * Add a new ternary reloption
+ */
+void
+add_ternary_reloption(bits32 kinds, const char *name, const char *desc,
+ ternary default_val, LOCKMODE lockmode)
+{
+ relopt_ternary *newoption = init_ternary_reloption(kinds, name, desc,
+ default_val, lockmode);
+
+ add_reloption((relopt_gen *) newoption);
+}
+
+/*
+ * add_local_ternary_reloption
+ * Add a new ternary local reloption
+ *
+ * 'offset' is offset of ternary-typed field.
+ */
+void
+add_local_ternary_reloption(local_relopts *relopts, const char *name,
+ const char *desc, ternary default_val,
+ int offset)
+{
+ relopt_ternary *newoption = init_ternary_reloption(RELOPT_KIND_LOCAL,
+ name, desc,
+ default_val, 0);
+
+ add_local_reloption(relopts, (relopt_gen *) newoption, offset);
+}
/*
* init_real_reloption
@@ -1617,6 +1689,18 @@ parse_one_reloption(relopt_value *option, char *text_str, int text_len,
option->gen->name, value)));
}
break;
+ case RELOPT_TYPE_TERNARY:
+ {
+ bool b;
+ parsed = parse_bool(value, &b);
+ option->values.ternary_val = b ? TERNARY_TRUE : TERNARY_FALSE;
+ if (validate && !parsed)
+ ereport(ERROR,
+ (errcode(ERRCODE_INVALID_PARAMETER_VALUE),
+ errmsg("invalid value for ternary option \"%s\": %s",
+ option->gen->name, value)));
+ }
+ break;
case RELOPT_TYPE_INT:
{
relopt_int *optint = (relopt_int *) option->gen;
@@ -1780,17 +1864,6 @@ fillRelOptions(void *rdopts, Size basesize,
char *itempos = ((char *) rdopts) + elems[j].offset;
char *string_val;
- /*
- * If isset_offset is provided, store whether the reloption is
- * set there.
- */
- if (elems[j].isset_offset > 0)
- {
- char *setpos = ((char *) rdopts) + elems[j].isset_offset;
-
- *(bool *) setpos = options[i].isset;
- }
-
switch (options[i].gen->type)
{
case RELOPT_TYPE_BOOL:
@@ -1798,6 +1871,11 @@ fillRelOptions(void *rdopts, Size basesize,
options[i].values.bool_val :
((relopt_bool *) options[i].gen)->default_val;
break;
+ case RELOPT_TYPE_TERNARY:
+ *(ternary *) itempos = options[i].isset ?
+ options[i].values.ternary_val :
+ ((relopt_ternary *) options[i].gen)->default_val;
+ break;
case RELOPT_TYPE_INT:
*(int *) itempos = options[i].isset ?
options[i].values.int_val :
@@ -1912,8 +1990,8 @@ default_reloptions(Datum reloptions, bool validate, relopt_kind kind)
offsetof(StdRdOptions, parallel_workers)},
{"vacuum_index_cleanup", RELOPT_TYPE_ENUM,
offsetof(StdRdOptions, vacuum_index_cleanup)},
- {"vacuum_truncate", RELOPT_TYPE_BOOL,
- offsetof(StdRdOptions, vacuum_truncate), offsetof(StdRdOptions, vacuum_truncate_set)},
+ {"vacuum_truncate", RELOPT_TYPE_TERNARY,
+ offsetof(StdRdOptions, vacuum_truncate)},
{"vacuum_max_eager_freeze_failure_rate", RELOPT_TYPE_REAL,
offsetof(StdRdOptions, vacuum_max_eager_freeze_failure_rate)}
};
@@ -1993,7 +2071,6 @@ build_local_reloptions(local_relopts *relopts, Datum options, bool validate)
elems[i].optname = opt->option->name;
elems[i].opttype = opt->option->type;
elems[i].offset = opt->offset;
- elems[i].isset_offset = 0; /* not supported for local relopts yet */
i++;
}
diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c
index 733ef40ae7..977babff54 100644
--- a/src/backend/commands/vacuum.c
+++ b/src/backend/commands/vacuum.c
@@ -2221,9 +2221,9 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams params,
{
StdRdOptions *opts = (StdRdOptions *) rel->rd_options;
- if (opts && opts->vacuum_truncate_set)
+ if (opts && opts->vacuum_truncate != TERNARY_UNSET)
{
- if (opts->vacuum_truncate)
+ if (opts->vacuum_truncate == TERNARY_TRUE)
params.truncate = VACOPTVALUE_ENABLED;
else
params.truncate = VACOPTVALUE_DISABLED;
diff --git a/src/include/access/reloptions.h b/src/include/access/reloptions.h
index a604a4702c..a436697658 100644
--- a/src/include/access/reloptions.h
+++ b/src/include/access/reloptions.h
@@ -29,6 +29,7 @@
typedef enum relopt_type
{
RELOPT_TYPE_BOOL,
+ RELOPT_TYPE_TERNARY, /* on, off, unset */
RELOPT_TYPE_INT,
RELOPT_TYPE_REAL,
RELOPT_TYPE_ENUM,
@@ -80,6 +81,7 @@ typedef struct relopt_value
union
{
bool bool_val;
+ ternary ternary_val;
int int_val;
double real_val;
int enum_val;
@@ -94,6 +96,12 @@ typedef struct relopt_bool
bool default_val;
} relopt_bool;
+typedef struct relopt_rernary
+{
+ relopt_gen gen;
+ int default_val;
+} relopt_ternary;
+
typedef struct relopt_int
{
relopt_gen gen;
@@ -152,19 +160,6 @@ typedef struct
const char *optname; /* option's name */
relopt_type opttype; /* option's datatype */
int offset; /* offset of field in result struct */
-
- /*
- * isset_offset is an optional offset of a field in the result struct that
- * stores whether the option is explicitly set for the relation or if it
- * just picked up the default value. In most cases, this can be
- * accomplished by giving the reloption a special out-of-range default
- * value (e.g., some integer reloptions use -2), but this isn't always
- * possible. For example, a Boolean reloption cannot be given an
- * out-of-range default, so we need another way to discover the source of
- * its value. This offset is only used if given a value greater than
- * zero.
- */
- int isset_offset;
} relopt_parse_elt;
/* Local reloption definition */
@@ -195,6 +190,8 @@ typedef struct local_relopts
extern relopt_kind add_reloption_kind(void);
extern void add_bool_reloption(bits32 kinds, const char *name, const char *desc,
bool default_val, LOCKMODE lockmode);
+extern void add_ternary_reloption(bits32 kinds, const char *name,
+ const char *desc, int default_val, LOCKMODE lockmode);
extern void add_int_reloption(bits32 kinds, const char *name, const char *desc,
int default_val, int min_val, int max_val,
LOCKMODE lockmode);
@@ -214,6 +211,9 @@ extern void register_reloptions_validator(local_relopts *relopts,
extern void add_local_bool_reloption(local_relopts *relopts, const char *name,
const char *desc, bool default_val,
int offset);
+extern void add_local_ternary_reloption(local_relopts *relopts,
+ const char *name, const char *desc,
+ ternary default_val, int offset);
extern void add_local_int_reloption(local_relopts *relopts, const char *name,
const char *desc, int default_val,
int min_val, int max_val, int offset);
diff --git a/src/include/c.h b/src/include/c.h
index 39022f8a9d..9c59ccccf8 100644
--- a/src/include/c.h
+++ b/src/include/c.h
@@ -488,6 +488,22 @@ typedef void (*pg_funcptr_t) (void);
#include <stdbool.h>
+/*
+ * ternary
+ * Boolean value with an extrea "unset" option
+ *
+ * Ternary data type is used in relation options that can be "true", "false" or
+ * "unset". Since relation options are used deep inside the PostgreSQL code,
+ * this type is declared globally.
+*/
+
+typedef enum ternary
+{
+ TERNARY_FALSE = 0,
+ TERNARY_TRUE = 1,
+ TERNARY_UNSET = -1
+} ternary;
+
/* ----------------------------------------------------------------
* Section 3: standard system types
diff --git a/src/include/utils/rel.h b/src/include/utils/rel.h
index b552359915..7346d618f9 100644
--- a/src/include/utils/rel.h
+++ b/src/include/utils/rel.h
@@ -346,8 +346,7 @@ typedef struct StdRdOptions
bool user_catalog_table; /* use as an additional catalog relation */
int parallel_workers; /* max number of parallel workers */
StdRdOptIndexCleanup vacuum_index_cleanup; /* controls index vacuuming */
- bool vacuum_truncate; /* enables vacuum to truncate a relation */
- bool vacuum_truncate_set; /* whether vacuum_truncate is set */
+ ternary vacuum_truncate; /* enables vacuum to truncate a relation */
/*
* Fraction of pages in a relation that vacuum can eagerly scan and fail
--
2.39.2
--nextPart5129932.5fSG56mABF
Content-Disposition: attachment;
filename="v2-0003-Add-alias-to-be-used-as-unset-state.patch"
Content-Transfer-Encoding: 7Bit
Content-Type: text/x-patch; charset="unicode-2-0-utf-8";
name="v2-0003-Add-alias-to-be-used-as-unset-state.patch"
^ permalink raw reply [nested|flat] 868+ messages in thread
* [PATCH v2 2/4] Introduce ternary reloptions
@ 2025-09-04 14:41 Nikolay Shaplov <[email protected]>
0 siblings, 0 replies; 868+ messages in thread
From: Nikolay Shaplov @ 2025-09-04 14:41 UTC (permalink / raw)
Introduce ternary reloption as a replacement for current `vacuum_truncate`
implementation. Remove `vacuum_truncate_set` additional flag and using
`TERNARY_UNSET` value instead.
---
src/backend/access/common/reloptions.c | 129 ++++++++++++++++++++-----
src/backend/commands/vacuum.c | 4 +-
src/include/access/reloptions.h | 26 ++---
src/include/c.h | 16 +++
src/include/utils/rel.h | 3 +-
5 files changed, 135 insertions(+), 43 deletions(-)
diff --git a/src/backend/access/common/reloptions.c b/src/backend/access/common/reloptions.c
index 0af3fea68f..f543f11001 100644
--- a/src/backend/access/common/reloptions.c
+++ b/src/backend/access/common/reloptions.c
@@ -40,9 +40,9 @@
*
* To add an option:
*
- * (i) decide on a type (bool, integer, real, enum, string), name, default
- * value, upper and lower bounds (if applicable); for strings, consider a
- * validation routine.
+ * (i) decide on a type (bool, ternary, integer, real, enum, string), name,
+ * default value, upper and lower bounds (if applicable); for strings,
+ * consider a validation routine.
* (ii) add a record below (or use add_<type>_reloption).
* (iii) add it to the appropriate options struct (perhaps StdRdOptions)
* (iv) add it to the appropriate handling routine (perhaps
@@ -147,15 +147,6 @@ static relopt_bool boolRelOpts[] =
},
false
},
- {
- {
- "vacuum_truncate",
- "Enables vacuum to truncate empty pages at the end of this table",
- RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
- ShareUpdateExclusiveLock
- },
- true
- },
{
{
"deduplicate_items",
@@ -170,6 +161,21 @@ static relopt_bool boolRelOpts[] =
{{NULL}}
};
+static relopt_ternary ternaryRelOpts[] =
+{
+ {
+ {
+ "vacuum_truncate",
+ "Enables vacuum to truncate empty pages at the end of this table",
+ RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
+ ShareUpdateExclusiveLock
+ },
+ TERNARY_UNSET
+ },
+ /* list terminator */
+ {{NULL}}
+};
+
static relopt_int intRelOpts[] =
{
{
@@ -600,6 +606,13 @@ initialize_reloptions(void)
boolRelOpts[i].gen.lockmode));
j++;
}
+ for (i = 0; ternaryRelOpts[i].gen.name; i++)
+ {
+ Assert(DoLockModesConflict(ternaryRelOpts[i].gen.lockmode,
+ ternaryRelOpts[i].gen.lockmode));
+ j++;
+ }
+
for (i = 0; intRelOpts[i].gen.name; i++)
{
Assert(DoLockModesConflict(intRelOpts[i].gen.lockmode,
@@ -640,6 +653,14 @@ initialize_reloptions(void)
j++;
}
+ for (i = 0; ternaryRelOpts[i].gen.name; i++)
+ {
+ relOpts[j] = &ternaryRelOpts[i].gen;
+ relOpts[j]->type = RELOPT_TYPE_TERNARY;
+ relOpts[j]->namelen = strlen(relOpts[j]->name);
+ j++;
+ }
+
for (i = 0; intRelOpts[i].gen.name; i++)
{
relOpts[j] = &intRelOpts[i].gen;
@@ -800,6 +821,9 @@ allocate_reloption(bits32 kinds, int type, const char *name, const char *desc,
case RELOPT_TYPE_BOOL:
size = sizeof(relopt_bool);
break;
+ case RELOPT_TYPE_TERNARY:
+ size = sizeof(relopt_ternary);
+ break;
case RELOPT_TYPE_INT:
size = sizeof(relopt_int);
break;
@@ -883,6 +907,54 @@ add_local_bool_reloption(local_relopts *relopts, const char *name,
add_local_reloption(relopts, (relopt_gen *) newoption, offset);
}
+/*
+ * init_ternary_reloption
+ * Allocate and initialize a new ternary reloption
+ */
+static relopt_ternary *
+init_ternary_reloption(bits32 kinds, const char *name, const char *desc,
+ ternary default_val, LOCKMODE lockmode)
+{
+ relopt_ternary *newoption;
+
+ newoption = (relopt_ternary *) allocate_reloption(kinds,
+ RELOPT_TYPE_TERNARY, name, desc, lockmode);
+ newoption->default_val = default_val;
+
+ return newoption;
+}
+
+/*
+ * add_ternary_reloption
+ * Add a new ternary reloption
+ */
+void
+add_ternary_reloption(bits32 kinds, const char *name, const char *desc,
+ ternary default_val, LOCKMODE lockmode)
+{
+ relopt_ternary *newoption = init_ternary_reloption(kinds, name, desc,
+ default_val, lockmode);
+
+ add_reloption((relopt_gen *) newoption);
+}
+
+/*
+ * add_local_ternary_reloption
+ * Add a new ternary local reloption
+ *
+ * 'offset' is offset of ternary-typed field.
+ */
+void
+add_local_ternary_reloption(local_relopts *relopts, const char *name,
+ const char *desc, ternary default_val,
+ int offset)
+{
+ relopt_ternary *newoption = init_ternary_reloption(RELOPT_KIND_LOCAL,
+ name, desc,
+ default_val, 0);
+
+ add_local_reloption(relopts, (relopt_gen *) newoption, offset);
+}
/*
* init_real_reloption
@@ -1617,6 +1689,18 @@ parse_one_reloption(relopt_value *option, char *text_str, int text_len,
option->gen->name, value)));
}
break;
+ case RELOPT_TYPE_TERNARY:
+ {
+ bool b;
+ parsed = parse_bool(value, &b);
+ option->values.ternary_val = b ? TERNARY_TRUE : TERNARY_FALSE;
+ if (validate && !parsed)
+ ereport(ERROR,
+ (errcode(ERRCODE_INVALID_PARAMETER_VALUE),
+ errmsg("invalid value for ternary option \"%s\": %s",
+ option->gen->name, value)));
+ }
+ break;
case RELOPT_TYPE_INT:
{
relopt_int *optint = (relopt_int *) option->gen;
@@ -1780,17 +1864,6 @@ fillRelOptions(void *rdopts, Size basesize,
char *itempos = ((char *) rdopts) + elems[j].offset;
char *string_val;
- /*
- * If isset_offset is provided, store whether the reloption is
- * set there.
- */
- if (elems[j].isset_offset > 0)
- {
- char *setpos = ((char *) rdopts) + elems[j].isset_offset;
-
- *(bool *) setpos = options[i].isset;
- }
-
switch (options[i].gen->type)
{
case RELOPT_TYPE_BOOL:
@@ -1798,6 +1871,11 @@ fillRelOptions(void *rdopts, Size basesize,
options[i].values.bool_val :
((relopt_bool *) options[i].gen)->default_val;
break;
+ case RELOPT_TYPE_TERNARY:
+ *(ternary *) itempos = options[i].isset ?
+ options[i].values.ternary_val :
+ ((relopt_ternary *) options[i].gen)->default_val;
+ break;
case RELOPT_TYPE_INT:
*(int *) itempos = options[i].isset ?
options[i].values.int_val :
@@ -1912,8 +1990,8 @@ default_reloptions(Datum reloptions, bool validate, relopt_kind kind)
offsetof(StdRdOptions, parallel_workers)},
{"vacuum_index_cleanup", RELOPT_TYPE_ENUM,
offsetof(StdRdOptions, vacuum_index_cleanup)},
- {"vacuum_truncate", RELOPT_TYPE_BOOL,
- offsetof(StdRdOptions, vacuum_truncate), offsetof(StdRdOptions, vacuum_truncate_set)},
+ {"vacuum_truncate", RELOPT_TYPE_TERNARY,
+ offsetof(StdRdOptions, vacuum_truncate)},
{"vacuum_max_eager_freeze_failure_rate", RELOPT_TYPE_REAL,
offsetof(StdRdOptions, vacuum_max_eager_freeze_failure_rate)}
};
@@ -1993,7 +2071,6 @@ build_local_reloptions(local_relopts *relopts, Datum options, bool validate)
elems[i].optname = opt->option->name;
elems[i].opttype = opt->option->type;
elems[i].offset = opt->offset;
- elems[i].isset_offset = 0; /* not supported for local relopts yet */
i++;
}
diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c
index 733ef40ae7..977babff54 100644
--- a/src/backend/commands/vacuum.c
+++ b/src/backend/commands/vacuum.c
@@ -2221,9 +2221,9 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams params,
{
StdRdOptions *opts = (StdRdOptions *) rel->rd_options;
- if (opts && opts->vacuum_truncate_set)
+ if (opts && opts->vacuum_truncate != TERNARY_UNSET)
{
- if (opts->vacuum_truncate)
+ if (opts->vacuum_truncate == TERNARY_TRUE)
params.truncate = VACOPTVALUE_ENABLED;
else
params.truncate = VACOPTVALUE_DISABLED;
diff --git a/src/include/access/reloptions.h b/src/include/access/reloptions.h
index a604a4702c..a436697658 100644
--- a/src/include/access/reloptions.h
+++ b/src/include/access/reloptions.h
@@ -29,6 +29,7 @@
typedef enum relopt_type
{
RELOPT_TYPE_BOOL,
+ RELOPT_TYPE_TERNARY, /* on, off, unset */
RELOPT_TYPE_INT,
RELOPT_TYPE_REAL,
RELOPT_TYPE_ENUM,
@@ -80,6 +81,7 @@ typedef struct relopt_value
union
{
bool bool_val;
+ ternary ternary_val;
int int_val;
double real_val;
int enum_val;
@@ -94,6 +96,12 @@ typedef struct relopt_bool
bool default_val;
} relopt_bool;
+typedef struct relopt_rernary
+{
+ relopt_gen gen;
+ int default_val;
+} relopt_ternary;
+
typedef struct relopt_int
{
relopt_gen gen;
@@ -152,19 +160,6 @@ typedef struct
const char *optname; /* option's name */
relopt_type opttype; /* option's datatype */
int offset; /* offset of field in result struct */
-
- /*
- * isset_offset is an optional offset of a field in the result struct that
- * stores whether the option is explicitly set for the relation or if it
- * just picked up the default value. In most cases, this can be
- * accomplished by giving the reloption a special out-of-range default
- * value (e.g., some integer reloptions use -2), but this isn't always
- * possible. For example, a Boolean reloption cannot be given an
- * out-of-range default, so we need another way to discover the source of
- * its value. This offset is only used if given a value greater than
- * zero.
- */
- int isset_offset;
} relopt_parse_elt;
/* Local reloption definition */
@@ -195,6 +190,8 @@ typedef struct local_relopts
extern relopt_kind add_reloption_kind(void);
extern void add_bool_reloption(bits32 kinds, const char *name, const char *desc,
bool default_val, LOCKMODE lockmode);
+extern void add_ternary_reloption(bits32 kinds, const char *name,
+ const char *desc, int default_val, LOCKMODE lockmode);
extern void add_int_reloption(bits32 kinds, const char *name, const char *desc,
int default_val, int min_val, int max_val,
LOCKMODE lockmode);
@@ -214,6 +211,9 @@ extern void register_reloptions_validator(local_relopts *relopts,
extern void add_local_bool_reloption(local_relopts *relopts, const char *name,
const char *desc, bool default_val,
int offset);
+extern void add_local_ternary_reloption(local_relopts *relopts,
+ const char *name, const char *desc,
+ ternary default_val, int offset);
extern void add_local_int_reloption(local_relopts *relopts, const char *name,
const char *desc, int default_val,
int min_val, int max_val, int offset);
diff --git a/src/include/c.h b/src/include/c.h
index 39022f8a9d..9c59ccccf8 100644
--- a/src/include/c.h
+++ b/src/include/c.h
@@ -488,6 +488,22 @@ typedef void (*pg_funcptr_t) (void);
#include <stdbool.h>
+/*
+ * ternary
+ * Boolean value with an extrea "unset" option
+ *
+ * Ternary data type is used in relation options that can be "true", "false" or
+ * "unset". Since relation options are used deep inside the PostgreSQL code,
+ * this type is declared globally.
+*/
+
+typedef enum ternary
+{
+ TERNARY_FALSE = 0,
+ TERNARY_TRUE = 1,
+ TERNARY_UNSET = -1
+} ternary;
+
/* ----------------------------------------------------------------
* Section 3: standard system types
diff --git a/src/include/utils/rel.h b/src/include/utils/rel.h
index b552359915..7346d618f9 100644
--- a/src/include/utils/rel.h
+++ b/src/include/utils/rel.h
@@ -346,8 +346,7 @@ typedef struct StdRdOptions
bool user_catalog_table; /* use as an additional catalog relation */
int parallel_workers; /* max number of parallel workers */
StdRdOptIndexCleanup vacuum_index_cleanup; /* controls index vacuuming */
- bool vacuum_truncate; /* enables vacuum to truncate a relation */
- bool vacuum_truncate_set; /* whether vacuum_truncate is set */
+ ternary vacuum_truncate; /* enables vacuum to truncate a relation */
/*
* Fraction of pages in a relation that vacuum can eagerly scan and fail
--
2.39.2
--nextPart5129932.5fSG56mABF
Content-Disposition: attachment;
filename="v2-0003-Add-alias-to-be-used-as-unset-state.patch"
Content-Transfer-Encoding: 7Bit
Content-Type: text/x-patch; charset="unicode-2-0-utf-8";
name="v2-0003-Add-alias-to-be-used-as-unset-state.patch"
^ permalink raw reply [nested|flat] 868+ messages in thread
* [PATCH v2a 2/4] Introduce ternary reloptions
@ 2025-09-04 14:41 Nikolay Shaplov <[email protected]>
0 siblings, 0 replies; 868+ messages in thread
From: Nikolay Shaplov @ 2025-09-04 14:41 UTC (permalink / raw)
Introduce ternary reloption as a replacement for current `vacuum_truncate`
implementation. Remove `vacuum_truncate_set` additional flag and using
`TERNARY_UNSET` value instead.
---
src/backend/access/common/reloptions.c | 129 ++++++++++++++++++++-----
src/backend/commands/vacuum.c | 4 +-
src/include/access/reloptions.h | 26 ++---
src/include/c.h | 16 +++
src/include/utils/rel.h | 3 +-
5 files changed, 135 insertions(+), 43 deletions(-)
diff --git a/src/backend/access/common/reloptions.c b/src/backend/access/common/reloptions.c
index 0af3fea68f..f543f11001 100644
--- a/src/backend/access/common/reloptions.c
+++ b/src/backend/access/common/reloptions.c
@@ -40,9 +40,9 @@
*
* To add an option:
*
- * (i) decide on a type (bool, integer, real, enum, string), name, default
- * value, upper and lower bounds (if applicable); for strings, consider a
- * validation routine.
+ * (i) decide on a type (bool, ternary, integer, real, enum, string), name,
+ * default value, upper and lower bounds (if applicable); for strings,
+ * consider a validation routine.
* (ii) add a record below (or use add_<type>_reloption).
* (iii) add it to the appropriate options struct (perhaps StdRdOptions)
* (iv) add it to the appropriate handling routine (perhaps
@@ -147,15 +147,6 @@ static relopt_bool boolRelOpts[] =
},
false
},
- {
- {
- "vacuum_truncate",
- "Enables vacuum to truncate empty pages at the end of this table",
- RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
- ShareUpdateExclusiveLock
- },
- true
- },
{
{
"deduplicate_items",
@@ -170,6 +161,21 @@ static relopt_bool boolRelOpts[] =
{{NULL}}
};
+static relopt_ternary ternaryRelOpts[] =
+{
+ {
+ {
+ "vacuum_truncate",
+ "Enables vacuum to truncate empty pages at the end of this table",
+ RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
+ ShareUpdateExclusiveLock
+ },
+ TERNARY_UNSET
+ },
+ /* list terminator */
+ {{NULL}}
+};
+
static relopt_int intRelOpts[] =
{
{
@@ -600,6 +606,13 @@ initialize_reloptions(void)
boolRelOpts[i].gen.lockmode));
j++;
}
+ for (i = 0; ternaryRelOpts[i].gen.name; i++)
+ {
+ Assert(DoLockModesConflict(ternaryRelOpts[i].gen.lockmode,
+ ternaryRelOpts[i].gen.lockmode));
+ j++;
+ }
+
for (i = 0; intRelOpts[i].gen.name; i++)
{
Assert(DoLockModesConflict(intRelOpts[i].gen.lockmode,
@@ -640,6 +653,14 @@ initialize_reloptions(void)
j++;
}
+ for (i = 0; ternaryRelOpts[i].gen.name; i++)
+ {
+ relOpts[j] = &ternaryRelOpts[i].gen;
+ relOpts[j]->type = RELOPT_TYPE_TERNARY;
+ relOpts[j]->namelen = strlen(relOpts[j]->name);
+ j++;
+ }
+
for (i = 0; intRelOpts[i].gen.name; i++)
{
relOpts[j] = &intRelOpts[i].gen;
@@ -800,6 +821,9 @@ allocate_reloption(bits32 kinds, int type, const char *name, const char *desc,
case RELOPT_TYPE_BOOL:
size = sizeof(relopt_bool);
break;
+ case RELOPT_TYPE_TERNARY:
+ size = sizeof(relopt_ternary);
+ break;
case RELOPT_TYPE_INT:
size = sizeof(relopt_int);
break;
@@ -883,6 +907,54 @@ add_local_bool_reloption(local_relopts *relopts, const char *name,
add_local_reloption(relopts, (relopt_gen *) newoption, offset);
}
+/*
+ * init_ternary_reloption
+ * Allocate and initialize a new ternary reloption
+ */
+static relopt_ternary *
+init_ternary_reloption(bits32 kinds, const char *name, const char *desc,
+ ternary default_val, LOCKMODE lockmode)
+{
+ relopt_ternary *newoption;
+
+ newoption = (relopt_ternary *) allocate_reloption(kinds,
+ RELOPT_TYPE_TERNARY, name, desc, lockmode);
+ newoption->default_val = default_val;
+
+ return newoption;
+}
+
+/*
+ * add_ternary_reloption
+ * Add a new ternary reloption
+ */
+void
+add_ternary_reloption(bits32 kinds, const char *name, const char *desc,
+ ternary default_val, LOCKMODE lockmode)
+{
+ relopt_ternary *newoption = init_ternary_reloption(kinds, name, desc,
+ default_val, lockmode);
+
+ add_reloption((relopt_gen *) newoption);
+}
+
+/*
+ * add_local_ternary_reloption
+ * Add a new ternary local reloption
+ *
+ * 'offset' is offset of ternary-typed field.
+ */
+void
+add_local_ternary_reloption(local_relopts *relopts, const char *name,
+ const char *desc, ternary default_val,
+ int offset)
+{
+ relopt_ternary *newoption = init_ternary_reloption(RELOPT_KIND_LOCAL,
+ name, desc,
+ default_val, 0);
+
+ add_local_reloption(relopts, (relopt_gen *) newoption, offset);
+}
/*
* init_real_reloption
@@ -1617,6 +1689,18 @@ parse_one_reloption(relopt_value *option, char *text_str, int text_len,
option->gen->name, value)));
}
break;
+ case RELOPT_TYPE_TERNARY:
+ {
+ bool b;
+ parsed = parse_bool(value, &b);
+ option->values.ternary_val = b ? TERNARY_TRUE : TERNARY_FALSE;
+ if (validate && !parsed)
+ ereport(ERROR,
+ (errcode(ERRCODE_INVALID_PARAMETER_VALUE),
+ errmsg("invalid value for ternary option \"%s\": %s",
+ option->gen->name, value)));
+ }
+ break;
case RELOPT_TYPE_INT:
{
relopt_int *optint = (relopt_int *) option->gen;
@@ -1780,17 +1864,6 @@ fillRelOptions(void *rdopts, Size basesize,
char *itempos = ((char *) rdopts) + elems[j].offset;
char *string_val;
- /*
- * If isset_offset is provided, store whether the reloption is
- * set there.
- */
- if (elems[j].isset_offset > 0)
- {
- char *setpos = ((char *) rdopts) + elems[j].isset_offset;
-
- *(bool *) setpos = options[i].isset;
- }
-
switch (options[i].gen->type)
{
case RELOPT_TYPE_BOOL:
@@ -1798,6 +1871,11 @@ fillRelOptions(void *rdopts, Size basesize,
options[i].values.bool_val :
((relopt_bool *) options[i].gen)->default_val;
break;
+ case RELOPT_TYPE_TERNARY:
+ *(ternary *) itempos = options[i].isset ?
+ options[i].values.ternary_val :
+ ((relopt_ternary *) options[i].gen)->default_val;
+ break;
case RELOPT_TYPE_INT:
*(int *) itempos = options[i].isset ?
options[i].values.int_val :
@@ -1912,8 +1990,8 @@ default_reloptions(Datum reloptions, bool validate, relopt_kind kind)
offsetof(StdRdOptions, parallel_workers)},
{"vacuum_index_cleanup", RELOPT_TYPE_ENUM,
offsetof(StdRdOptions, vacuum_index_cleanup)},
- {"vacuum_truncate", RELOPT_TYPE_BOOL,
- offsetof(StdRdOptions, vacuum_truncate), offsetof(StdRdOptions, vacuum_truncate_set)},
+ {"vacuum_truncate", RELOPT_TYPE_TERNARY,
+ offsetof(StdRdOptions, vacuum_truncate)},
{"vacuum_max_eager_freeze_failure_rate", RELOPT_TYPE_REAL,
offsetof(StdRdOptions, vacuum_max_eager_freeze_failure_rate)}
};
@@ -1993,7 +2071,6 @@ build_local_reloptions(local_relopts *relopts, Datum options, bool validate)
elems[i].optname = opt->option->name;
elems[i].opttype = opt->option->type;
elems[i].offset = opt->offset;
- elems[i].isset_offset = 0; /* not supported for local relopts yet */
i++;
}
diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c
index 733ef40ae7..977babff54 100644
--- a/src/backend/commands/vacuum.c
+++ b/src/backend/commands/vacuum.c
@@ -2221,9 +2221,9 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams params,
{
StdRdOptions *opts = (StdRdOptions *) rel->rd_options;
- if (opts && opts->vacuum_truncate_set)
+ if (opts && opts->vacuum_truncate != TERNARY_UNSET)
{
- if (opts->vacuum_truncate)
+ if (opts->vacuum_truncate == TERNARY_TRUE)
params.truncate = VACOPTVALUE_ENABLED;
else
params.truncate = VACOPTVALUE_DISABLED;
diff --git a/src/include/access/reloptions.h b/src/include/access/reloptions.h
index a604a4702c..a436697658 100644
--- a/src/include/access/reloptions.h
+++ b/src/include/access/reloptions.h
@@ -29,6 +29,7 @@
typedef enum relopt_type
{
RELOPT_TYPE_BOOL,
+ RELOPT_TYPE_TERNARY, /* on, off, unset */
RELOPT_TYPE_INT,
RELOPT_TYPE_REAL,
RELOPT_TYPE_ENUM,
@@ -80,6 +81,7 @@ typedef struct relopt_value
union
{
bool bool_val;
+ ternary ternary_val;
int int_val;
double real_val;
int enum_val;
@@ -94,6 +96,12 @@ typedef struct relopt_bool
bool default_val;
} relopt_bool;
+typedef struct relopt_rernary
+{
+ relopt_gen gen;
+ int default_val;
+} relopt_ternary;
+
typedef struct relopt_int
{
relopt_gen gen;
@@ -152,19 +160,6 @@ typedef struct
const char *optname; /* option's name */
relopt_type opttype; /* option's datatype */
int offset; /* offset of field in result struct */
-
- /*
- * isset_offset is an optional offset of a field in the result struct that
- * stores whether the option is explicitly set for the relation or if it
- * just picked up the default value. In most cases, this can be
- * accomplished by giving the reloption a special out-of-range default
- * value (e.g., some integer reloptions use -2), but this isn't always
- * possible. For example, a Boolean reloption cannot be given an
- * out-of-range default, so we need another way to discover the source of
- * its value. This offset is only used if given a value greater than
- * zero.
- */
- int isset_offset;
} relopt_parse_elt;
/* Local reloption definition */
@@ -195,6 +190,8 @@ typedef struct local_relopts
extern relopt_kind add_reloption_kind(void);
extern void add_bool_reloption(bits32 kinds, const char *name, const char *desc,
bool default_val, LOCKMODE lockmode);
+extern void add_ternary_reloption(bits32 kinds, const char *name,
+ const char *desc, int default_val, LOCKMODE lockmode);
extern void add_int_reloption(bits32 kinds, const char *name, const char *desc,
int default_val, int min_val, int max_val,
LOCKMODE lockmode);
@@ -214,6 +211,9 @@ extern void register_reloptions_validator(local_relopts *relopts,
extern void add_local_bool_reloption(local_relopts *relopts, const char *name,
const char *desc, bool default_val,
int offset);
+extern void add_local_ternary_reloption(local_relopts *relopts,
+ const char *name, const char *desc,
+ ternary default_val, int offset);
extern void add_local_int_reloption(local_relopts *relopts, const char *name,
const char *desc, int default_val,
int min_val, int max_val, int offset);
diff --git a/src/include/c.h b/src/include/c.h
index 39022f8a9d..9c59ccccf8 100644
--- a/src/include/c.h
+++ b/src/include/c.h
@@ -488,6 +488,22 @@ typedef void (*pg_funcptr_t) (void);
#include <stdbool.h>
+/*
+ * ternary
+ * Boolean value with an extrea "unset" option
+ *
+ * Ternary data type is used in relation options that can be "true", "false" or
+ * "unset". Since relation options are used deep inside the PostgreSQL code,
+ * this type is declared globally.
+*/
+
+typedef enum ternary
+{
+ TERNARY_FALSE = 0,
+ TERNARY_TRUE = 1,
+ TERNARY_UNSET = -1
+} ternary;
+
/* ----------------------------------------------------------------
* Section 3: standard system types
diff --git a/src/include/utils/rel.h b/src/include/utils/rel.h
index b552359915..7346d618f9 100644
--- a/src/include/utils/rel.h
+++ b/src/include/utils/rel.h
@@ -346,8 +346,7 @@ typedef struct StdRdOptions
bool user_catalog_table; /* use as an additional catalog relation */
int parallel_workers; /* max number of parallel workers */
StdRdOptIndexCleanup vacuum_index_cleanup; /* controls index vacuuming */
- bool vacuum_truncate; /* enables vacuum to truncate a relation */
- bool vacuum_truncate_set; /* whether vacuum_truncate is set */
+ ternary vacuum_truncate; /* enables vacuum to truncate a relation */
/*
* Fraction of pages in a relation that vacuum can eagerly scan and fail
--
2.39.2
--nextPart4155600.3ZeAukHxDK
Content-Disposition: attachment;
filename="v2a-0003-Add-alias-to-be-used-as-unset-state.patch"
Content-Transfer-Encoding: 7Bit
Content-Type: text/x-patch; charset="unicode-2-0-utf-8";
name="v2a-0003-Add-alias-to-be-used-as-unset-state.patch"
^ permalink raw reply [nested|flat] 868+ messages in thread
* [PATCH v2 2/4] Introduce ternary reloptions
@ 2025-09-04 14:41 Nikolay Shaplov <[email protected]>
0 siblings, 0 replies; 868+ messages in thread
From: Nikolay Shaplov @ 2025-09-04 14:41 UTC (permalink / raw)
Introduce ternary reloption as a replacement for current `vacuum_truncate`
implementation. Remove `vacuum_truncate_set` additional flag and using
`TERNARY_UNSET` value instead.
---
src/backend/access/common/reloptions.c | 129 ++++++++++++++++++++-----
src/backend/commands/vacuum.c | 4 +-
src/include/access/reloptions.h | 26 ++---
src/include/c.h | 16 +++
src/include/utils/rel.h | 3 +-
5 files changed, 135 insertions(+), 43 deletions(-)
diff --git a/src/backend/access/common/reloptions.c b/src/backend/access/common/reloptions.c
index 0af3fea68f..f543f11001 100644
--- a/src/backend/access/common/reloptions.c
+++ b/src/backend/access/common/reloptions.c
@@ -40,9 +40,9 @@
*
* To add an option:
*
- * (i) decide on a type (bool, integer, real, enum, string), name, default
- * value, upper and lower bounds (if applicable); for strings, consider a
- * validation routine.
+ * (i) decide on a type (bool, ternary, integer, real, enum, string), name,
+ * default value, upper and lower bounds (if applicable); for strings,
+ * consider a validation routine.
* (ii) add a record below (or use add_<type>_reloption).
* (iii) add it to the appropriate options struct (perhaps StdRdOptions)
* (iv) add it to the appropriate handling routine (perhaps
@@ -147,15 +147,6 @@ static relopt_bool boolRelOpts[] =
},
false
},
- {
- {
- "vacuum_truncate",
- "Enables vacuum to truncate empty pages at the end of this table",
- RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
- ShareUpdateExclusiveLock
- },
- true
- },
{
{
"deduplicate_items",
@@ -170,6 +161,21 @@ static relopt_bool boolRelOpts[] =
{{NULL}}
};
+static relopt_ternary ternaryRelOpts[] =
+{
+ {
+ {
+ "vacuum_truncate",
+ "Enables vacuum to truncate empty pages at the end of this table",
+ RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
+ ShareUpdateExclusiveLock
+ },
+ TERNARY_UNSET
+ },
+ /* list terminator */
+ {{NULL}}
+};
+
static relopt_int intRelOpts[] =
{
{
@@ -600,6 +606,13 @@ initialize_reloptions(void)
boolRelOpts[i].gen.lockmode));
j++;
}
+ for (i = 0; ternaryRelOpts[i].gen.name; i++)
+ {
+ Assert(DoLockModesConflict(ternaryRelOpts[i].gen.lockmode,
+ ternaryRelOpts[i].gen.lockmode));
+ j++;
+ }
+
for (i = 0; intRelOpts[i].gen.name; i++)
{
Assert(DoLockModesConflict(intRelOpts[i].gen.lockmode,
@@ -640,6 +653,14 @@ initialize_reloptions(void)
j++;
}
+ for (i = 0; ternaryRelOpts[i].gen.name; i++)
+ {
+ relOpts[j] = &ternaryRelOpts[i].gen;
+ relOpts[j]->type = RELOPT_TYPE_TERNARY;
+ relOpts[j]->namelen = strlen(relOpts[j]->name);
+ j++;
+ }
+
for (i = 0; intRelOpts[i].gen.name; i++)
{
relOpts[j] = &intRelOpts[i].gen;
@@ -800,6 +821,9 @@ allocate_reloption(bits32 kinds, int type, const char *name, const char *desc,
case RELOPT_TYPE_BOOL:
size = sizeof(relopt_bool);
break;
+ case RELOPT_TYPE_TERNARY:
+ size = sizeof(relopt_ternary);
+ break;
case RELOPT_TYPE_INT:
size = sizeof(relopt_int);
break;
@@ -883,6 +907,54 @@ add_local_bool_reloption(local_relopts *relopts, const char *name,
add_local_reloption(relopts, (relopt_gen *) newoption, offset);
}
+/*
+ * init_ternary_reloption
+ * Allocate and initialize a new ternary reloption
+ */
+static relopt_ternary *
+init_ternary_reloption(bits32 kinds, const char *name, const char *desc,
+ ternary default_val, LOCKMODE lockmode)
+{
+ relopt_ternary *newoption;
+
+ newoption = (relopt_ternary *) allocate_reloption(kinds,
+ RELOPT_TYPE_TERNARY, name, desc, lockmode);
+ newoption->default_val = default_val;
+
+ return newoption;
+}
+
+/*
+ * add_ternary_reloption
+ * Add a new ternary reloption
+ */
+void
+add_ternary_reloption(bits32 kinds, const char *name, const char *desc,
+ ternary default_val, LOCKMODE lockmode)
+{
+ relopt_ternary *newoption = init_ternary_reloption(kinds, name, desc,
+ default_val, lockmode);
+
+ add_reloption((relopt_gen *) newoption);
+}
+
+/*
+ * add_local_ternary_reloption
+ * Add a new ternary local reloption
+ *
+ * 'offset' is offset of ternary-typed field.
+ */
+void
+add_local_ternary_reloption(local_relopts *relopts, const char *name,
+ const char *desc, ternary default_val,
+ int offset)
+{
+ relopt_ternary *newoption = init_ternary_reloption(RELOPT_KIND_LOCAL,
+ name, desc,
+ default_val, 0);
+
+ add_local_reloption(relopts, (relopt_gen *) newoption, offset);
+}
/*
* init_real_reloption
@@ -1617,6 +1689,18 @@ parse_one_reloption(relopt_value *option, char *text_str, int text_len,
option->gen->name, value)));
}
break;
+ case RELOPT_TYPE_TERNARY:
+ {
+ bool b;
+ parsed = parse_bool(value, &b);
+ option->values.ternary_val = b ? TERNARY_TRUE : TERNARY_FALSE;
+ if (validate && !parsed)
+ ereport(ERROR,
+ (errcode(ERRCODE_INVALID_PARAMETER_VALUE),
+ errmsg("invalid value for ternary option \"%s\": %s",
+ option->gen->name, value)));
+ }
+ break;
case RELOPT_TYPE_INT:
{
relopt_int *optint = (relopt_int *) option->gen;
@@ -1780,17 +1864,6 @@ fillRelOptions(void *rdopts, Size basesize,
char *itempos = ((char *) rdopts) + elems[j].offset;
char *string_val;
- /*
- * If isset_offset is provided, store whether the reloption is
- * set there.
- */
- if (elems[j].isset_offset > 0)
- {
- char *setpos = ((char *) rdopts) + elems[j].isset_offset;
-
- *(bool *) setpos = options[i].isset;
- }
-
switch (options[i].gen->type)
{
case RELOPT_TYPE_BOOL:
@@ -1798,6 +1871,11 @@ fillRelOptions(void *rdopts, Size basesize,
options[i].values.bool_val :
((relopt_bool *) options[i].gen)->default_val;
break;
+ case RELOPT_TYPE_TERNARY:
+ *(ternary *) itempos = options[i].isset ?
+ options[i].values.ternary_val :
+ ((relopt_ternary *) options[i].gen)->default_val;
+ break;
case RELOPT_TYPE_INT:
*(int *) itempos = options[i].isset ?
options[i].values.int_val :
@@ -1912,8 +1990,8 @@ default_reloptions(Datum reloptions, bool validate, relopt_kind kind)
offsetof(StdRdOptions, parallel_workers)},
{"vacuum_index_cleanup", RELOPT_TYPE_ENUM,
offsetof(StdRdOptions, vacuum_index_cleanup)},
- {"vacuum_truncate", RELOPT_TYPE_BOOL,
- offsetof(StdRdOptions, vacuum_truncate), offsetof(StdRdOptions, vacuum_truncate_set)},
+ {"vacuum_truncate", RELOPT_TYPE_TERNARY,
+ offsetof(StdRdOptions, vacuum_truncate)},
{"vacuum_max_eager_freeze_failure_rate", RELOPT_TYPE_REAL,
offsetof(StdRdOptions, vacuum_max_eager_freeze_failure_rate)}
};
@@ -1993,7 +2071,6 @@ build_local_reloptions(local_relopts *relopts, Datum options, bool validate)
elems[i].optname = opt->option->name;
elems[i].opttype = opt->option->type;
elems[i].offset = opt->offset;
- elems[i].isset_offset = 0; /* not supported for local relopts yet */
i++;
}
diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c
index 733ef40ae7..977babff54 100644
--- a/src/backend/commands/vacuum.c
+++ b/src/backend/commands/vacuum.c
@@ -2221,9 +2221,9 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams params,
{
StdRdOptions *opts = (StdRdOptions *) rel->rd_options;
- if (opts && opts->vacuum_truncate_set)
+ if (opts && opts->vacuum_truncate != TERNARY_UNSET)
{
- if (opts->vacuum_truncate)
+ if (opts->vacuum_truncate == TERNARY_TRUE)
params.truncate = VACOPTVALUE_ENABLED;
else
params.truncate = VACOPTVALUE_DISABLED;
diff --git a/src/include/access/reloptions.h b/src/include/access/reloptions.h
index a604a4702c..a436697658 100644
--- a/src/include/access/reloptions.h
+++ b/src/include/access/reloptions.h
@@ -29,6 +29,7 @@
typedef enum relopt_type
{
RELOPT_TYPE_BOOL,
+ RELOPT_TYPE_TERNARY, /* on, off, unset */
RELOPT_TYPE_INT,
RELOPT_TYPE_REAL,
RELOPT_TYPE_ENUM,
@@ -80,6 +81,7 @@ typedef struct relopt_value
union
{
bool bool_val;
+ ternary ternary_val;
int int_val;
double real_val;
int enum_val;
@@ -94,6 +96,12 @@ typedef struct relopt_bool
bool default_val;
} relopt_bool;
+typedef struct relopt_rernary
+{
+ relopt_gen gen;
+ int default_val;
+} relopt_ternary;
+
typedef struct relopt_int
{
relopt_gen gen;
@@ -152,19 +160,6 @@ typedef struct
const char *optname; /* option's name */
relopt_type opttype; /* option's datatype */
int offset; /* offset of field in result struct */
-
- /*
- * isset_offset is an optional offset of a field in the result struct that
- * stores whether the option is explicitly set for the relation or if it
- * just picked up the default value. In most cases, this can be
- * accomplished by giving the reloption a special out-of-range default
- * value (e.g., some integer reloptions use -2), but this isn't always
- * possible. For example, a Boolean reloption cannot be given an
- * out-of-range default, so we need another way to discover the source of
- * its value. This offset is only used if given a value greater than
- * zero.
- */
- int isset_offset;
} relopt_parse_elt;
/* Local reloption definition */
@@ -195,6 +190,8 @@ typedef struct local_relopts
extern relopt_kind add_reloption_kind(void);
extern void add_bool_reloption(bits32 kinds, const char *name, const char *desc,
bool default_val, LOCKMODE lockmode);
+extern void add_ternary_reloption(bits32 kinds, const char *name,
+ const char *desc, int default_val, LOCKMODE lockmode);
extern void add_int_reloption(bits32 kinds, const char *name, const char *desc,
int default_val, int min_val, int max_val,
LOCKMODE lockmode);
@@ -214,6 +211,9 @@ extern void register_reloptions_validator(local_relopts *relopts,
extern void add_local_bool_reloption(local_relopts *relopts, const char *name,
const char *desc, bool default_val,
int offset);
+extern void add_local_ternary_reloption(local_relopts *relopts,
+ const char *name, const char *desc,
+ ternary default_val, int offset);
extern void add_local_int_reloption(local_relopts *relopts, const char *name,
const char *desc, int default_val,
int min_val, int max_val, int offset);
diff --git a/src/include/c.h b/src/include/c.h
index 39022f8a9d..9c59ccccf8 100644
--- a/src/include/c.h
+++ b/src/include/c.h
@@ -488,6 +488,22 @@ typedef void (*pg_funcptr_t) (void);
#include <stdbool.h>
+/*
+ * ternary
+ * Boolean value with an extrea "unset" option
+ *
+ * Ternary data type is used in relation options that can be "true", "false" or
+ * "unset". Since relation options are used deep inside the PostgreSQL code,
+ * this type is declared globally.
+*/
+
+typedef enum ternary
+{
+ TERNARY_FALSE = 0,
+ TERNARY_TRUE = 1,
+ TERNARY_UNSET = -1
+} ternary;
+
/* ----------------------------------------------------------------
* Section 3: standard system types
diff --git a/src/include/utils/rel.h b/src/include/utils/rel.h
index b552359915..7346d618f9 100644
--- a/src/include/utils/rel.h
+++ b/src/include/utils/rel.h
@@ -346,8 +346,7 @@ typedef struct StdRdOptions
bool user_catalog_table; /* use as an additional catalog relation */
int parallel_workers; /* max number of parallel workers */
StdRdOptIndexCleanup vacuum_index_cleanup; /* controls index vacuuming */
- bool vacuum_truncate; /* enables vacuum to truncate a relation */
- bool vacuum_truncate_set; /* whether vacuum_truncate is set */
+ ternary vacuum_truncate; /* enables vacuum to truncate a relation */
/*
* Fraction of pages in a relation that vacuum can eagerly scan and fail
--
2.39.2
--nextPart5129932.5fSG56mABF
Content-Disposition: attachment;
filename="v2-0003-Add-alias-to-be-used-as-unset-state.patch"
Content-Transfer-Encoding: 7Bit
Content-Type: text/x-patch; charset="unicode-2-0-utf-8";
name="v2-0003-Add-alias-to-be-used-as-unset-state.patch"
^ permalink raw reply [nested|flat] 868+ messages in thread
* [PATCH v2a 2/4] Introduce ternary reloptions
@ 2025-09-04 14:41 Nikolay Shaplov <[email protected]>
0 siblings, 0 replies; 868+ messages in thread
From: Nikolay Shaplov @ 2025-09-04 14:41 UTC (permalink / raw)
Introduce ternary reloption as a replacement for current `vacuum_truncate`
implementation. Remove `vacuum_truncate_set` additional flag and using
`TERNARY_UNSET` value instead.
---
src/backend/access/common/reloptions.c | 129 ++++++++++++++++++++-----
src/backend/commands/vacuum.c | 4 +-
src/include/access/reloptions.h | 26 ++---
src/include/c.h | 16 +++
src/include/utils/rel.h | 3 +-
5 files changed, 135 insertions(+), 43 deletions(-)
diff --git a/src/backend/access/common/reloptions.c b/src/backend/access/common/reloptions.c
index 0af3fea68f..f543f11001 100644
--- a/src/backend/access/common/reloptions.c
+++ b/src/backend/access/common/reloptions.c
@@ -40,9 +40,9 @@
*
* To add an option:
*
- * (i) decide on a type (bool, integer, real, enum, string), name, default
- * value, upper and lower bounds (if applicable); for strings, consider a
- * validation routine.
+ * (i) decide on a type (bool, ternary, integer, real, enum, string), name,
+ * default value, upper and lower bounds (if applicable); for strings,
+ * consider a validation routine.
* (ii) add a record below (or use add_<type>_reloption).
* (iii) add it to the appropriate options struct (perhaps StdRdOptions)
* (iv) add it to the appropriate handling routine (perhaps
@@ -147,15 +147,6 @@ static relopt_bool boolRelOpts[] =
},
false
},
- {
- {
- "vacuum_truncate",
- "Enables vacuum to truncate empty pages at the end of this table",
- RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
- ShareUpdateExclusiveLock
- },
- true
- },
{
{
"deduplicate_items",
@@ -170,6 +161,21 @@ static relopt_bool boolRelOpts[] =
{{NULL}}
};
+static relopt_ternary ternaryRelOpts[] =
+{
+ {
+ {
+ "vacuum_truncate",
+ "Enables vacuum to truncate empty pages at the end of this table",
+ RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
+ ShareUpdateExclusiveLock
+ },
+ TERNARY_UNSET
+ },
+ /* list terminator */
+ {{NULL}}
+};
+
static relopt_int intRelOpts[] =
{
{
@@ -600,6 +606,13 @@ initialize_reloptions(void)
boolRelOpts[i].gen.lockmode));
j++;
}
+ for (i = 0; ternaryRelOpts[i].gen.name; i++)
+ {
+ Assert(DoLockModesConflict(ternaryRelOpts[i].gen.lockmode,
+ ternaryRelOpts[i].gen.lockmode));
+ j++;
+ }
+
for (i = 0; intRelOpts[i].gen.name; i++)
{
Assert(DoLockModesConflict(intRelOpts[i].gen.lockmode,
@@ -640,6 +653,14 @@ initialize_reloptions(void)
j++;
}
+ for (i = 0; ternaryRelOpts[i].gen.name; i++)
+ {
+ relOpts[j] = &ternaryRelOpts[i].gen;
+ relOpts[j]->type = RELOPT_TYPE_TERNARY;
+ relOpts[j]->namelen = strlen(relOpts[j]->name);
+ j++;
+ }
+
for (i = 0; intRelOpts[i].gen.name; i++)
{
relOpts[j] = &intRelOpts[i].gen;
@@ -800,6 +821,9 @@ allocate_reloption(bits32 kinds, int type, const char *name, const char *desc,
case RELOPT_TYPE_BOOL:
size = sizeof(relopt_bool);
break;
+ case RELOPT_TYPE_TERNARY:
+ size = sizeof(relopt_ternary);
+ break;
case RELOPT_TYPE_INT:
size = sizeof(relopt_int);
break;
@@ -883,6 +907,54 @@ add_local_bool_reloption(local_relopts *relopts, const char *name,
add_local_reloption(relopts, (relopt_gen *) newoption, offset);
}
+/*
+ * init_ternary_reloption
+ * Allocate and initialize a new ternary reloption
+ */
+static relopt_ternary *
+init_ternary_reloption(bits32 kinds, const char *name, const char *desc,
+ ternary default_val, LOCKMODE lockmode)
+{
+ relopt_ternary *newoption;
+
+ newoption = (relopt_ternary *) allocate_reloption(kinds,
+ RELOPT_TYPE_TERNARY, name, desc, lockmode);
+ newoption->default_val = default_val;
+
+ return newoption;
+}
+
+/*
+ * add_ternary_reloption
+ * Add a new ternary reloption
+ */
+void
+add_ternary_reloption(bits32 kinds, const char *name, const char *desc,
+ ternary default_val, LOCKMODE lockmode)
+{
+ relopt_ternary *newoption = init_ternary_reloption(kinds, name, desc,
+ default_val, lockmode);
+
+ add_reloption((relopt_gen *) newoption);
+}
+
+/*
+ * add_local_ternary_reloption
+ * Add a new ternary local reloption
+ *
+ * 'offset' is offset of ternary-typed field.
+ */
+void
+add_local_ternary_reloption(local_relopts *relopts, const char *name,
+ const char *desc, ternary default_val,
+ int offset)
+{
+ relopt_ternary *newoption = init_ternary_reloption(RELOPT_KIND_LOCAL,
+ name, desc,
+ default_val, 0);
+
+ add_local_reloption(relopts, (relopt_gen *) newoption, offset);
+}
/*
* init_real_reloption
@@ -1617,6 +1689,18 @@ parse_one_reloption(relopt_value *option, char *text_str, int text_len,
option->gen->name, value)));
}
break;
+ case RELOPT_TYPE_TERNARY:
+ {
+ bool b;
+ parsed = parse_bool(value, &b);
+ option->values.ternary_val = b ? TERNARY_TRUE : TERNARY_FALSE;
+ if (validate && !parsed)
+ ereport(ERROR,
+ (errcode(ERRCODE_INVALID_PARAMETER_VALUE),
+ errmsg("invalid value for ternary option \"%s\": %s",
+ option->gen->name, value)));
+ }
+ break;
case RELOPT_TYPE_INT:
{
relopt_int *optint = (relopt_int *) option->gen;
@@ -1780,17 +1864,6 @@ fillRelOptions(void *rdopts, Size basesize,
char *itempos = ((char *) rdopts) + elems[j].offset;
char *string_val;
- /*
- * If isset_offset is provided, store whether the reloption is
- * set there.
- */
- if (elems[j].isset_offset > 0)
- {
- char *setpos = ((char *) rdopts) + elems[j].isset_offset;
-
- *(bool *) setpos = options[i].isset;
- }
-
switch (options[i].gen->type)
{
case RELOPT_TYPE_BOOL:
@@ -1798,6 +1871,11 @@ fillRelOptions(void *rdopts, Size basesize,
options[i].values.bool_val :
((relopt_bool *) options[i].gen)->default_val;
break;
+ case RELOPT_TYPE_TERNARY:
+ *(ternary *) itempos = options[i].isset ?
+ options[i].values.ternary_val :
+ ((relopt_ternary *) options[i].gen)->default_val;
+ break;
case RELOPT_TYPE_INT:
*(int *) itempos = options[i].isset ?
options[i].values.int_val :
@@ -1912,8 +1990,8 @@ default_reloptions(Datum reloptions, bool validate, relopt_kind kind)
offsetof(StdRdOptions, parallel_workers)},
{"vacuum_index_cleanup", RELOPT_TYPE_ENUM,
offsetof(StdRdOptions, vacuum_index_cleanup)},
- {"vacuum_truncate", RELOPT_TYPE_BOOL,
- offsetof(StdRdOptions, vacuum_truncate), offsetof(StdRdOptions, vacuum_truncate_set)},
+ {"vacuum_truncate", RELOPT_TYPE_TERNARY,
+ offsetof(StdRdOptions, vacuum_truncate)},
{"vacuum_max_eager_freeze_failure_rate", RELOPT_TYPE_REAL,
offsetof(StdRdOptions, vacuum_max_eager_freeze_failure_rate)}
};
@@ -1993,7 +2071,6 @@ build_local_reloptions(local_relopts *relopts, Datum options, bool validate)
elems[i].optname = opt->option->name;
elems[i].opttype = opt->option->type;
elems[i].offset = opt->offset;
- elems[i].isset_offset = 0; /* not supported for local relopts yet */
i++;
}
diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c
index 733ef40ae7..977babff54 100644
--- a/src/backend/commands/vacuum.c
+++ b/src/backend/commands/vacuum.c
@@ -2221,9 +2221,9 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams params,
{
StdRdOptions *opts = (StdRdOptions *) rel->rd_options;
- if (opts && opts->vacuum_truncate_set)
+ if (opts && opts->vacuum_truncate != TERNARY_UNSET)
{
- if (opts->vacuum_truncate)
+ if (opts->vacuum_truncate == TERNARY_TRUE)
params.truncate = VACOPTVALUE_ENABLED;
else
params.truncate = VACOPTVALUE_DISABLED;
diff --git a/src/include/access/reloptions.h b/src/include/access/reloptions.h
index a604a4702c..a436697658 100644
--- a/src/include/access/reloptions.h
+++ b/src/include/access/reloptions.h
@@ -29,6 +29,7 @@
typedef enum relopt_type
{
RELOPT_TYPE_BOOL,
+ RELOPT_TYPE_TERNARY, /* on, off, unset */
RELOPT_TYPE_INT,
RELOPT_TYPE_REAL,
RELOPT_TYPE_ENUM,
@@ -80,6 +81,7 @@ typedef struct relopt_value
union
{
bool bool_val;
+ ternary ternary_val;
int int_val;
double real_val;
int enum_val;
@@ -94,6 +96,12 @@ typedef struct relopt_bool
bool default_val;
} relopt_bool;
+typedef struct relopt_rernary
+{
+ relopt_gen gen;
+ int default_val;
+} relopt_ternary;
+
typedef struct relopt_int
{
relopt_gen gen;
@@ -152,19 +160,6 @@ typedef struct
const char *optname; /* option's name */
relopt_type opttype; /* option's datatype */
int offset; /* offset of field in result struct */
-
- /*
- * isset_offset is an optional offset of a field in the result struct that
- * stores whether the option is explicitly set for the relation or if it
- * just picked up the default value. In most cases, this can be
- * accomplished by giving the reloption a special out-of-range default
- * value (e.g., some integer reloptions use -2), but this isn't always
- * possible. For example, a Boolean reloption cannot be given an
- * out-of-range default, so we need another way to discover the source of
- * its value. This offset is only used if given a value greater than
- * zero.
- */
- int isset_offset;
} relopt_parse_elt;
/* Local reloption definition */
@@ -195,6 +190,8 @@ typedef struct local_relopts
extern relopt_kind add_reloption_kind(void);
extern void add_bool_reloption(bits32 kinds, const char *name, const char *desc,
bool default_val, LOCKMODE lockmode);
+extern void add_ternary_reloption(bits32 kinds, const char *name,
+ const char *desc, int default_val, LOCKMODE lockmode);
extern void add_int_reloption(bits32 kinds, const char *name, const char *desc,
int default_val, int min_val, int max_val,
LOCKMODE lockmode);
@@ -214,6 +211,9 @@ extern void register_reloptions_validator(local_relopts *relopts,
extern void add_local_bool_reloption(local_relopts *relopts, const char *name,
const char *desc, bool default_val,
int offset);
+extern void add_local_ternary_reloption(local_relopts *relopts,
+ const char *name, const char *desc,
+ ternary default_val, int offset);
extern void add_local_int_reloption(local_relopts *relopts, const char *name,
const char *desc, int default_val,
int min_val, int max_val, int offset);
diff --git a/src/include/c.h b/src/include/c.h
index 39022f8a9d..9c59ccccf8 100644
--- a/src/include/c.h
+++ b/src/include/c.h
@@ -488,6 +488,22 @@ typedef void (*pg_funcptr_t) (void);
#include <stdbool.h>
+/*
+ * ternary
+ * Boolean value with an extrea "unset" option
+ *
+ * Ternary data type is used in relation options that can be "true", "false" or
+ * "unset". Since relation options are used deep inside the PostgreSQL code,
+ * this type is declared globally.
+*/
+
+typedef enum ternary
+{
+ TERNARY_FALSE = 0,
+ TERNARY_TRUE = 1,
+ TERNARY_UNSET = -1
+} ternary;
+
/* ----------------------------------------------------------------
* Section 3: standard system types
diff --git a/src/include/utils/rel.h b/src/include/utils/rel.h
index b552359915..7346d618f9 100644
--- a/src/include/utils/rel.h
+++ b/src/include/utils/rel.h
@@ -346,8 +346,7 @@ typedef struct StdRdOptions
bool user_catalog_table; /* use as an additional catalog relation */
int parallel_workers; /* max number of parallel workers */
StdRdOptIndexCleanup vacuum_index_cleanup; /* controls index vacuuming */
- bool vacuum_truncate; /* enables vacuum to truncate a relation */
- bool vacuum_truncate_set; /* whether vacuum_truncate is set */
+ ternary vacuum_truncate; /* enables vacuum to truncate a relation */
/*
* Fraction of pages in a relation that vacuum can eagerly scan and fail
--
2.39.2
--nextPart4155600.3ZeAukHxDK
Content-Disposition: attachment;
filename="v2a-0003-Add-alias-to-be-used-as-unset-state.patch"
Content-Transfer-Encoding: 7Bit
Content-Type: text/x-patch; charset="unicode-2-0-utf-8";
name="v2a-0003-Add-alias-to-be-used-as-unset-state.patch"
^ permalink raw reply [nested|flat] 868+ messages in thread
* [PATCH v2a 2/4] Introduce ternary reloptions
@ 2025-09-04 14:41 Nikolay Shaplov <[email protected]>
0 siblings, 0 replies; 868+ messages in thread
From: Nikolay Shaplov @ 2025-09-04 14:41 UTC (permalink / raw)
Introduce ternary reloption as a replacement for current `vacuum_truncate`
implementation. Remove `vacuum_truncate_set` additional flag and using
`TERNARY_UNSET` value instead.
---
src/backend/access/common/reloptions.c | 129 ++++++++++++++++++++-----
src/backend/commands/vacuum.c | 4 +-
src/include/access/reloptions.h | 26 ++---
src/include/c.h | 16 +++
src/include/utils/rel.h | 3 +-
5 files changed, 135 insertions(+), 43 deletions(-)
diff --git a/src/backend/access/common/reloptions.c b/src/backend/access/common/reloptions.c
index 0af3fea68f..f543f11001 100644
--- a/src/backend/access/common/reloptions.c
+++ b/src/backend/access/common/reloptions.c
@@ -40,9 +40,9 @@
*
* To add an option:
*
- * (i) decide on a type (bool, integer, real, enum, string), name, default
- * value, upper and lower bounds (if applicable); for strings, consider a
- * validation routine.
+ * (i) decide on a type (bool, ternary, integer, real, enum, string), name,
+ * default value, upper and lower bounds (if applicable); for strings,
+ * consider a validation routine.
* (ii) add a record below (or use add_<type>_reloption).
* (iii) add it to the appropriate options struct (perhaps StdRdOptions)
* (iv) add it to the appropriate handling routine (perhaps
@@ -147,15 +147,6 @@ static relopt_bool boolRelOpts[] =
},
false
},
- {
- {
- "vacuum_truncate",
- "Enables vacuum to truncate empty pages at the end of this table",
- RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
- ShareUpdateExclusiveLock
- },
- true
- },
{
{
"deduplicate_items",
@@ -170,6 +161,21 @@ static relopt_bool boolRelOpts[] =
{{NULL}}
};
+static relopt_ternary ternaryRelOpts[] =
+{
+ {
+ {
+ "vacuum_truncate",
+ "Enables vacuum to truncate empty pages at the end of this table",
+ RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
+ ShareUpdateExclusiveLock
+ },
+ TERNARY_UNSET
+ },
+ /* list terminator */
+ {{NULL}}
+};
+
static relopt_int intRelOpts[] =
{
{
@@ -600,6 +606,13 @@ initialize_reloptions(void)
boolRelOpts[i].gen.lockmode));
j++;
}
+ for (i = 0; ternaryRelOpts[i].gen.name; i++)
+ {
+ Assert(DoLockModesConflict(ternaryRelOpts[i].gen.lockmode,
+ ternaryRelOpts[i].gen.lockmode));
+ j++;
+ }
+
for (i = 0; intRelOpts[i].gen.name; i++)
{
Assert(DoLockModesConflict(intRelOpts[i].gen.lockmode,
@@ -640,6 +653,14 @@ initialize_reloptions(void)
j++;
}
+ for (i = 0; ternaryRelOpts[i].gen.name; i++)
+ {
+ relOpts[j] = &ternaryRelOpts[i].gen;
+ relOpts[j]->type = RELOPT_TYPE_TERNARY;
+ relOpts[j]->namelen = strlen(relOpts[j]->name);
+ j++;
+ }
+
for (i = 0; intRelOpts[i].gen.name; i++)
{
relOpts[j] = &intRelOpts[i].gen;
@@ -800,6 +821,9 @@ allocate_reloption(bits32 kinds, int type, const char *name, const char *desc,
case RELOPT_TYPE_BOOL:
size = sizeof(relopt_bool);
break;
+ case RELOPT_TYPE_TERNARY:
+ size = sizeof(relopt_ternary);
+ break;
case RELOPT_TYPE_INT:
size = sizeof(relopt_int);
break;
@@ -883,6 +907,54 @@ add_local_bool_reloption(local_relopts *relopts, const char *name,
add_local_reloption(relopts, (relopt_gen *) newoption, offset);
}
+/*
+ * init_ternary_reloption
+ * Allocate and initialize a new ternary reloption
+ */
+static relopt_ternary *
+init_ternary_reloption(bits32 kinds, const char *name, const char *desc,
+ ternary default_val, LOCKMODE lockmode)
+{
+ relopt_ternary *newoption;
+
+ newoption = (relopt_ternary *) allocate_reloption(kinds,
+ RELOPT_TYPE_TERNARY, name, desc, lockmode);
+ newoption->default_val = default_val;
+
+ return newoption;
+}
+
+/*
+ * add_ternary_reloption
+ * Add a new ternary reloption
+ */
+void
+add_ternary_reloption(bits32 kinds, const char *name, const char *desc,
+ ternary default_val, LOCKMODE lockmode)
+{
+ relopt_ternary *newoption = init_ternary_reloption(kinds, name, desc,
+ default_val, lockmode);
+
+ add_reloption((relopt_gen *) newoption);
+}
+
+/*
+ * add_local_ternary_reloption
+ * Add a new ternary local reloption
+ *
+ * 'offset' is offset of ternary-typed field.
+ */
+void
+add_local_ternary_reloption(local_relopts *relopts, const char *name,
+ const char *desc, ternary default_val,
+ int offset)
+{
+ relopt_ternary *newoption = init_ternary_reloption(RELOPT_KIND_LOCAL,
+ name, desc,
+ default_val, 0);
+
+ add_local_reloption(relopts, (relopt_gen *) newoption, offset);
+}
/*
* init_real_reloption
@@ -1617,6 +1689,18 @@ parse_one_reloption(relopt_value *option, char *text_str, int text_len,
option->gen->name, value)));
}
break;
+ case RELOPT_TYPE_TERNARY:
+ {
+ bool b;
+ parsed = parse_bool(value, &b);
+ option->values.ternary_val = b ? TERNARY_TRUE : TERNARY_FALSE;
+ if (validate && !parsed)
+ ereport(ERROR,
+ (errcode(ERRCODE_INVALID_PARAMETER_VALUE),
+ errmsg("invalid value for ternary option \"%s\": %s",
+ option->gen->name, value)));
+ }
+ break;
case RELOPT_TYPE_INT:
{
relopt_int *optint = (relopt_int *) option->gen;
@@ -1780,17 +1864,6 @@ fillRelOptions(void *rdopts, Size basesize,
char *itempos = ((char *) rdopts) + elems[j].offset;
char *string_val;
- /*
- * If isset_offset is provided, store whether the reloption is
- * set there.
- */
- if (elems[j].isset_offset > 0)
- {
- char *setpos = ((char *) rdopts) + elems[j].isset_offset;
-
- *(bool *) setpos = options[i].isset;
- }
-
switch (options[i].gen->type)
{
case RELOPT_TYPE_BOOL:
@@ -1798,6 +1871,11 @@ fillRelOptions(void *rdopts, Size basesize,
options[i].values.bool_val :
((relopt_bool *) options[i].gen)->default_val;
break;
+ case RELOPT_TYPE_TERNARY:
+ *(ternary *) itempos = options[i].isset ?
+ options[i].values.ternary_val :
+ ((relopt_ternary *) options[i].gen)->default_val;
+ break;
case RELOPT_TYPE_INT:
*(int *) itempos = options[i].isset ?
options[i].values.int_val :
@@ -1912,8 +1990,8 @@ default_reloptions(Datum reloptions, bool validate, relopt_kind kind)
offsetof(StdRdOptions, parallel_workers)},
{"vacuum_index_cleanup", RELOPT_TYPE_ENUM,
offsetof(StdRdOptions, vacuum_index_cleanup)},
- {"vacuum_truncate", RELOPT_TYPE_BOOL,
- offsetof(StdRdOptions, vacuum_truncate), offsetof(StdRdOptions, vacuum_truncate_set)},
+ {"vacuum_truncate", RELOPT_TYPE_TERNARY,
+ offsetof(StdRdOptions, vacuum_truncate)},
{"vacuum_max_eager_freeze_failure_rate", RELOPT_TYPE_REAL,
offsetof(StdRdOptions, vacuum_max_eager_freeze_failure_rate)}
};
@@ -1993,7 +2071,6 @@ build_local_reloptions(local_relopts *relopts, Datum options, bool validate)
elems[i].optname = opt->option->name;
elems[i].opttype = opt->option->type;
elems[i].offset = opt->offset;
- elems[i].isset_offset = 0; /* not supported for local relopts yet */
i++;
}
diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c
index 733ef40ae7..977babff54 100644
--- a/src/backend/commands/vacuum.c
+++ b/src/backend/commands/vacuum.c
@@ -2221,9 +2221,9 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams params,
{
StdRdOptions *opts = (StdRdOptions *) rel->rd_options;
- if (opts && opts->vacuum_truncate_set)
+ if (opts && opts->vacuum_truncate != TERNARY_UNSET)
{
- if (opts->vacuum_truncate)
+ if (opts->vacuum_truncate == TERNARY_TRUE)
params.truncate = VACOPTVALUE_ENABLED;
else
params.truncate = VACOPTVALUE_DISABLED;
diff --git a/src/include/access/reloptions.h b/src/include/access/reloptions.h
index a604a4702c..a436697658 100644
--- a/src/include/access/reloptions.h
+++ b/src/include/access/reloptions.h
@@ -29,6 +29,7 @@
typedef enum relopt_type
{
RELOPT_TYPE_BOOL,
+ RELOPT_TYPE_TERNARY, /* on, off, unset */
RELOPT_TYPE_INT,
RELOPT_TYPE_REAL,
RELOPT_TYPE_ENUM,
@@ -80,6 +81,7 @@ typedef struct relopt_value
union
{
bool bool_val;
+ ternary ternary_val;
int int_val;
double real_val;
int enum_val;
@@ -94,6 +96,12 @@ typedef struct relopt_bool
bool default_val;
} relopt_bool;
+typedef struct relopt_rernary
+{
+ relopt_gen gen;
+ int default_val;
+} relopt_ternary;
+
typedef struct relopt_int
{
relopt_gen gen;
@@ -152,19 +160,6 @@ typedef struct
const char *optname; /* option's name */
relopt_type opttype; /* option's datatype */
int offset; /* offset of field in result struct */
-
- /*
- * isset_offset is an optional offset of a field in the result struct that
- * stores whether the option is explicitly set for the relation or if it
- * just picked up the default value. In most cases, this can be
- * accomplished by giving the reloption a special out-of-range default
- * value (e.g., some integer reloptions use -2), but this isn't always
- * possible. For example, a Boolean reloption cannot be given an
- * out-of-range default, so we need another way to discover the source of
- * its value. This offset is only used if given a value greater than
- * zero.
- */
- int isset_offset;
} relopt_parse_elt;
/* Local reloption definition */
@@ -195,6 +190,8 @@ typedef struct local_relopts
extern relopt_kind add_reloption_kind(void);
extern void add_bool_reloption(bits32 kinds, const char *name, const char *desc,
bool default_val, LOCKMODE lockmode);
+extern void add_ternary_reloption(bits32 kinds, const char *name,
+ const char *desc, int default_val, LOCKMODE lockmode);
extern void add_int_reloption(bits32 kinds, const char *name, const char *desc,
int default_val, int min_val, int max_val,
LOCKMODE lockmode);
@@ -214,6 +211,9 @@ extern void register_reloptions_validator(local_relopts *relopts,
extern void add_local_bool_reloption(local_relopts *relopts, const char *name,
const char *desc, bool default_val,
int offset);
+extern void add_local_ternary_reloption(local_relopts *relopts,
+ const char *name, const char *desc,
+ ternary default_val, int offset);
extern void add_local_int_reloption(local_relopts *relopts, const char *name,
const char *desc, int default_val,
int min_val, int max_val, int offset);
diff --git a/src/include/c.h b/src/include/c.h
index 39022f8a9d..9c59ccccf8 100644
--- a/src/include/c.h
+++ b/src/include/c.h
@@ -488,6 +488,22 @@ typedef void (*pg_funcptr_t) (void);
#include <stdbool.h>
+/*
+ * ternary
+ * Boolean value with an extrea "unset" option
+ *
+ * Ternary data type is used in relation options that can be "true", "false" or
+ * "unset". Since relation options are used deep inside the PostgreSQL code,
+ * this type is declared globally.
+*/
+
+typedef enum ternary
+{
+ TERNARY_FALSE = 0,
+ TERNARY_TRUE = 1,
+ TERNARY_UNSET = -1
+} ternary;
+
/* ----------------------------------------------------------------
* Section 3: standard system types
diff --git a/src/include/utils/rel.h b/src/include/utils/rel.h
index b552359915..7346d618f9 100644
--- a/src/include/utils/rel.h
+++ b/src/include/utils/rel.h
@@ -346,8 +346,7 @@ typedef struct StdRdOptions
bool user_catalog_table; /* use as an additional catalog relation */
int parallel_workers; /* max number of parallel workers */
StdRdOptIndexCleanup vacuum_index_cleanup; /* controls index vacuuming */
- bool vacuum_truncate; /* enables vacuum to truncate a relation */
- bool vacuum_truncate_set; /* whether vacuum_truncate is set */
+ ternary vacuum_truncate; /* enables vacuum to truncate a relation */
/*
* Fraction of pages in a relation that vacuum can eagerly scan and fail
--
2.39.2
--nextPart4155600.3ZeAukHxDK
Content-Disposition: attachment;
filename="v2a-0003-Add-alias-to-be-used-as-unset-state.patch"
Content-Transfer-Encoding: 7Bit
Content-Type: text/x-patch; charset="unicode-2-0-utf-8";
name="v2a-0003-Add-alias-to-be-used-as-unset-state.patch"
^ permalink raw reply [nested|flat] 868+ messages in thread
* [PATCH v2 2/4] Introduce ternary reloptions
@ 2025-09-04 14:41 Nikolay Shaplov <[email protected]>
0 siblings, 0 replies; 868+ messages in thread
From: Nikolay Shaplov @ 2025-09-04 14:41 UTC (permalink / raw)
Introduce ternary reloption as a replacement for current `vacuum_truncate`
implementation. Remove `vacuum_truncate_set` additional flag and using
`TERNARY_UNSET` value instead.
---
src/backend/access/common/reloptions.c | 129 ++++++++++++++++++++-----
src/backend/commands/vacuum.c | 4 +-
src/include/access/reloptions.h | 26 ++---
src/include/c.h | 16 +++
src/include/utils/rel.h | 3 +-
5 files changed, 135 insertions(+), 43 deletions(-)
diff --git a/src/backend/access/common/reloptions.c b/src/backend/access/common/reloptions.c
index 0af3fea68f..f543f11001 100644
--- a/src/backend/access/common/reloptions.c
+++ b/src/backend/access/common/reloptions.c
@@ -40,9 +40,9 @@
*
* To add an option:
*
- * (i) decide on a type (bool, integer, real, enum, string), name, default
- * value, upper and lower bounds (if applicable); for strings, consider a
- * validation routine.
+ * (i) decide on a type (bool, ternary, integer, real, enum, string), name,
+ * default value, upper and lower bounds (if applicable); for strings,
+ * consider a validation routine.
* (ii) add a record below (or use add_<type>_reloption).
* (iii) add it to the appropriate options struct (perhaps StdRdOptions)
* (iv) add it to the appropriate handling routine (perhaps
@@ -147,15 +147,6 @@ static relopt_bool boolRelOpts[] =
},
false
},
- {
- {
- "vacuum_truncate",
- "Enables vacuum to truncate empty pages at the end of this table",
- RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
- ShareUpdateExclusiveLock
- },
- true
- },
{
{
"deduplicate_items",
@@ -170,6 +161,21 @@ static relopt_bool boolRelOpts[] =
{{NULL}}
};
+static relopt_ternary ternaryRelOpts[] =
+{
+ {
+ {
+ "vacuum_truncate",
+ "Enables vacuum to truncate empty pages at the end of this table",
+ RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
+ ShareUpdateExclusiveLock
+ },
+ TERNARY_UNSET
+ },
+ /* list terminator */
+ {{NULL}}
+};
+
static relopt_int intRelOpts[] =
{
{
@@ -600,6 +606,13 @@ initialize_reloptions(void)
boolRelOpts[i].gen.lockmode));
j++;
}
+ for (i = 0; ternaryRelOpts[i].gen.name; i++)
+ {
+ Assert(DoLockModesConflict(ternaryRelOpts[i].gen.lockmode,
+ ternaryRelOpts[i].gen.lockmode));
+ j++;
+ }
+
for (i = 0; intRelOpts[i].gen.name; i++)
{
Assert(DoLockModesConflict(intRelOpts[i].gen.lockmode,
@@ -640,6 +653,14 @@ initialize_reloptions(void)
j++;
}
+ for (i = 0; ternaryRelOpts[i].gen.name; i++)
+ {
+ relOpts[j] = &ternaryRelOpts[i].gen;
+ relOpts[j]->type = RELOPT_TYPE_TERNARY;
+ relOpts[j]->namelen = strlen(relOpts[j]->name);
+ j++;
+ }
+
for (i = 0; intRelOpts[i].gen.name; i++)
{
relOpts[j] = &intRelOpts[i].gen;
@@ -800,6 +821,9 @@ allocate_reloption(bits32 kinds, int type, const char *name, const char *desc,
case RELOPT_TYPE_BOOL:
size = sizeof(relopt_bool);
break;
+ case RELOPT_TYPE_TERNARY:
+ size = sizeof(relopt_ternary);
+ break;
case RELOPT_TYPE_INT:
size = sizeof(relopt_int);
break;
@@ -883,6 +907,54 @@ add_local_bool_reloption(local_relopts *relopts, const char *name,
add_local_reloption(relopts, (relopt_gen *) newoption, offset);
}
+/*
+ * init_ternary_reloption
+ * Allocate and initialize a new ternary reloption
+ */
+static relopt_ternary *
+init_ternary_reloption(bits32 kinds, const char *name, const char *desc,
+ ternary default_val, LOCKMODE lockmode)
+{
+ relopt_ternary *newoption;
+
+ newoption = (relopt_ternary *) allocate_reloption(kinds,
+ RELOPT_TYPE_TERNARY, name, desc, lockmode);
+ newoption->default_val = default_val;
+
+ return newoption;
+}
+
+/*
+ * add_ternary_reloption
+ * Add a new ternary reloption
+ */
+void
+add_ternary_reloption(bits32 kinds, const char *name, const char *desc,
+ ternary default_val, LOCKMODE lockmode)
+{
+ relopt_ternary *newoption = init_ternary_reloption(kinds, name, desc,
+ default_val, lockmode);
+
+ add_reloption((relopt_gen *) newoption);
+}
+
+/*
+ * add_local_ternary_reloption
+ * Add a new ternary local reloption
+ *
+ * 'offset' is offset of ternary-typed field.
+ */
+void
+add_local_ternary_reloption(local_relopts *relopts, const char *name,
+ const char *desc, ternary default_val,
+ int offset)
+{
+ relopt_ternary *newoption = init_ternary_reloption(RELOPT_KIND_LOCAL,
+ name, desc,
+ default_val, 0);
+
+ add_local_reloption(relopts, (relopt_gen *) newoption, offset);
+}
/*
* init_real_reloption
@@ -1617,6 +1689,18 @@ parse_one_reloption(relopt_value *option, char *text_str, int text_len,
option->gen->name, value)));
}
break;
+ case RELOPT_TYPE_TERNARY:
+ {
+ bool b;
+ parsed = parse_bool(value, &b);
+ option->values.ternary_val = b ? TERNARY_TRUE : TERNARY_FALSE;
+ if (validate && !parsed)
+ ereport(ERROR,
+ (errcode(ERRCODE_INVALID_PARAMETER_VALUE),
+ errmsg("invalid value for ternary option \"%s\": %s",
+ option->gen->name, value)));
+ }
+ break;
case RELOPT_TYPE_INT:
{
relopt_int *optint = (relopt_int *) option->gen;
@@ -1780,17 +1864,6 @@ fillRelOptions(void *rdopts, Size basesize,
char *itempos = ((char *) rdopts) + elems[j].offset;
char *string_val;
- /*
- * If isset_offset is provided, store whether the reloption is
- * set there.
- */
- if (elems[j].isset_offset > 0)
- {
- char *setpos = ((char *) rdopts) + elems[j].isset_offset;
-
- *(bool *) setpos = options[i].isset;
- }
-
switch (options[i].gen->type)
{
case RELOPT_TYPE_BOOL:
@@ -1798,6 +1871,11 @@ fillRelOptions(void *rdopts, Size basesize,
options[i].values.bool_val :
((relopt_bool *) options[i].gen)->default_val;
break;
+ case RELOPT_TYPE_TERNARY:
+ *(ternary *) itempos = options[i].isset ?
+ options[i].values.ternary_val :
+ ((relopt_ternary *) options[i].gen)->default_val;
+ break;
case RELOPT_TYPE_INT:
*(int *) itempos = options[i].isset ?
options[i].values.int_val :
@@ -1912,8 +1990,8 @@ default_reloptions(Datum reloptions, bool validate, relopt_kind kind)
offsetof(StdRdOptions, parallel_workers)},
{"vacuum_index_cleanup", RELOPT_TYPE_ENUM,
offsetof(StdRdOptions, vacuum_index_cleanup)},
- {"vacuum_truncate", RELOPT_TYPE_BOOL,
- offsetof(StdRdOptions, vacuum_truncate), offsetof(StdRdOptions, vacuum_truncate_set)},
+ {"vacuum_truncate", RELOPT_TYPE_TERNARY,
+ offsetof(StdRdOptions, vacuum_truncate)},
{"vacuum_max_eager_freeze_failure_rate", RELOPT_TYPE_REAL,
offsetof(StdRdOptions, vacuum_max_eager_freeze_failure_rate)}
};
@@ -1993,7 +2071,6 @@ build_local_reloptions(local_relopts *relopts, Datum options, bool validate)
elems[i].optname = opt->option->name;
elems[i].opttype = opt->option->type;
elems[i].offset = opt->offset;
- elems[i].isset_offset = 0; /* not supported for local relopts yet */
i++;
}
diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c
index 733ef40ae7..977babff54 100644
--- a/src/backend/commands/vacuum.c
+++ b/src/backend/commands/vacuum.c
@@ -2221,9 +2221,9 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams params,
{
StdRdOptions *opts = (StdRdOptions *) rel->rd_options;
- if (opts && opts->vacuum_truncate_set)
+ if (opts && opts->vacuum_truncate != TERNARY_UNSET)
{
- if (opts->vacuum_truncate)
+ if (opts->vacuum_truncate == TERNARY_TRUE)
params.truncate = VACOPTVALUE_ENABLED;
else
params.truncate = VACOPTVALUE_DISABLED;
diff --git a/src/include/access/reloptions.h b/src/include/access/reloptions.h
index a604a4702c..a436697658 100644
--- a/src/include/access/reloptions.h
+++ b/src/include/access/reloptions.h
@@ -29,6 +29,7 @@
typedef enum relopt_type
{
RELOPT_TYPE_BOOL,
+ RELOPT_TYPE_TERNARY, /* on, off, unset */
RELOPT_TYPE_INT,
RELOPT_TYPE_REAL,
RELOPT_TYPE_ENUM,
@@ -80,6 +81,7 @@ typedef struct relopt_value
union
{
bool bool_val;
+ ternary ternary_val;
int int_val;
double real_val;
int enum_val;
@@ -94,6 +96,12 @@ typedef struct relopt_bool
bool default_val;
} relopt_bool;
+typedef struct relopt_rernary
+{
+ relopt_gen gen;
+ int default_val;
+} relopt_ternary;
+
typedef struct relopt_int
{
relopt_gen gen;
@@ -152,19 +160,6 @@ typedef struct
const char *optname; /* option's name */
relopt_type opttype; /* option's datatype */
int offset; /* offset of field in result struct */
-
- /*
- * isset_offset is an optional offset of a field in the result struct that
- * stores whether the option is explicitly set for the relation or if it
- * just picked up the default value. In most cases, this can be
- * accomplished by giving the reloption a special out-of-range default
- * value (e.g., some integer reloptions use -2), but this isn't always
- * possible. For example, a Boolean reloption cannot be given an
- * out-of-range default, so we need another way to discover the source of
- * its value. This offset is only used if given a value greater than
- * zero.
- */
- int isset_offset;
} relopt_parse_elt;
/* Local reloption definition */
@@ -195,6 +190,8 @@ typedef struct local_relopts
extern relopt_kind add_reloption_kind(void);
extern void add_bool_reloption(bits32 kinds, const char *name, const char *desc,
bool default_val, LOCKMODE lockmode);
+extern void add_ternary_reloption(bits32 kinds, const char *name,
+ const char *desc, int default_val, LOCKMODE lockmode);
extern void add_int_reloption(bits32 kinds, const char *name, const char *desc,
int default_val, int min_val, int max_val,
LOCKMODE lockmode);
@@ -214,6 +211,9 @@ extern void register_reloptions_validator(local_relopts *relopts,
extern void add_local_bool_reloption(local_relopts *relopts, const char *name,
const char *desc, bool default_val,
int offset);
+extern void add_local_ternary_reloption(local_relopts *relopts,
+ const char *name, const char *desc,
+ ternary default_val, int offset);
extern void add_local_int_reloption(local_relopts *relopts, const char *name,
const char *desc, int default_val,
int min_val, int max_val, int offset);
diff --git a/src/include/c.h b/src/include/c.h
index 39022f8a9d..9c59ccccf8 100644
--- a/src/include/c.h
+++ b/src/include/c.h
@@ -488,6 +488,22 @@ typedef void (*pg_funcptr_t) (void);
#include <stdbool.h>
+/*
+ * ternary
+ * Boolean value with an extrea "unset" option
+ *
+ * Ternary data type is used in relation options that can be "true", "false" or
+ * "unset". Since relation options are used deep inside the PostgreSQL code,
+ * this type is declared globally.
+*/
+
+typedef enum ternary
+{
+ TERNARY_FALSE = 0,
+ TERNARY_TRUE = 1,
+ TERNARY_UNSET = -1
+} ternary;
+
/* ----------------------------------------------------------------
* Section 3: standard system types
diff --git a/src/include/utils/rel.h b/src/include/utils/rel.h
index b552359915..7346d618f9 100644
--- a/src/include/utils/rel.h
+++ b/src/include/utils/rel.h
@@ -346,8 +346,7 @@ typedef struct StdRdOptions
bool user_catalog_table; /* use as an additional catalog relation */
int parallel_workers; /* max number of parallel workers */
StdRdOptIndexCleanup vacuum_index_cleanup; /* controls index vacuuming */
- bool vacuum_truncate; /* enables vacuum to truncate a relation */
- bool vacuum_truncate_set; /* whether vacuum_truncate is set */
+ ternary vacuum_truncate; /* enables vacuum to truncate a relation */
/*
* Fraction of pages in a relation that vacuum can eagerly scan and fail
--
2.39.2
--nextPart5129932.5fSG56mABF
Content-Disposition: attachment;
filename="v2-0003-Add-alias-to-be-used-as-unset-state.patch"
Content-Transfer-Encoding: 7Bit
Content-Type: text/x-patch; charset="unicode-2-0-utf-8";
name="v2-0003-Add-alias-to-be-used-as-unset-state.patch"
^ permalink raw reply [nested|flat] 868+ messages in thread
* [PATCH v2a 2/4] Introduce ternary reloptions
@ 2025-09-04 14:41 Nikolay Shaplov <[email protected]>
0 siblings, 0 replies; 868+ messages in thread
From: Nikolay Shaplov @ 2025-09-04 14:41 UTC (permalink / raw)
Introduce ternary reloption as a replacement for current `vacuum_truncate`
implementation. Remove `vacuum_truncate_set` additional flag and using
`TERNARY_UNSET` value instead.
---
src/backend/access/common/reloptions.c | 129 ++++++++++++++++++++-----
src/backend/commands/vacuum.c | 4 +-
src/include/access/reloptions.h | 26 ++---
src/include/c.h | 16 +++
src/include/utils/rel.h | 3 +-
5 files changed, 135 insertions(+), 43 deletions(-)
diff --git a/src/backend/access/common/reloptions.c b/src/backend/access/common/reloptions.c
index 0af3fea68f..f543f11001 100644
--- a/src/backend/access/common/reloptions.c
+++ b/src/backend/access/common/reloptions.c
@@ -40,9 +40,9 @@
*
* To add an option:
*
- * (i) decide on a type (bool, integer, real, enum, string), name, default
- * value, upper and lower bounds (if applicable); for strings, consider a
- * validation routine.
+ * (i) decide on a type (bool, ternary, integer, real, enum, string), name,
+ * default value, upper and lower bounds (if applicable); for strings,
+ * consider a validation routine.
* (ii) add a record below (or use add_<type>_reloption).
* (iii) add it to the appropriate options struct (perhaps StdRdOptions)
* (iv) add it to the appropriate handling routine (perhaps
@@ -147,15 +147,6 @@ static relopt_bool boolRelOpts[] =
},
false
},
- {
- {
- "vacuum_truncate",
- "Enables vacuum to truncate empty pages at the end of this table",
- RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
- ShareUpdateExclusiveLock
- },
- true
- },
{
{
"deduplicate_items",
@@ -170,6 +161,21 @@ static relopt_bool boolRelOpts[] =
{{NULL}}
};
+static relopt_ternary ternaryRelOpts[] =
+{
+ {
+ {
+ "vacuum_truncate",
+ "Enables vacuum to truncate empty pages at the end of this table",
+ RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
+ ShareUpdateExclusiveLock
+ },
+ TERNARY_UNSET
+ },
+ /* list terminator */
+ {{NULL}}
+};
+
static relopt_int intRelOpts[] =
{
{
@@ -600,6 +606,13 @@ initialize_reloptions(void)
boolRelOpts[i].gen.lockmode));
j++;
}
+ for (i = 0; ternaryRelOpts[i].gen.name; i++)
+ {
+ Assert(DoLockModesConflict(ternaryRelOpts[i].gen.lockmode,
+ ternaryRelOpts[i].gen.lockmode));
+ j++;
+ }
+
for (i = 0; intRelOpts[i].gen.name; i++)
{
Assert(DoLockModesConflict(intRelOpts[i].gen.lockmode,
@@ -640,6 +653,14 @@ initialize_reloptions(void)
j++;
}
+ for (i = 0; ternaryRelOpts[i].gen.name; i++)
+ {
+ relOpts[j] = &ternaryRelOpts[i].gen;
+ relOpts[j]->type = RELOPT_TYPE_TERNARY;
+ relOpts[j]->namelen = strlen(relOpts[j]->name);
+ j++;
+ }
+
for (i = 0; intRelOpts[i].gen.name; i++)
{
relOpts[j] = &intRelOpts[i].gen;
@@ -800,6 +821,9 @@ allocate_reloption(bits32 kinds, int type, const char *name, const char *desc,
case RELOPT_TYPE_BOOL:
size = sizeof(relopt_bool);
break;
+ case RELOPT_TYPE_TERNARY:
+ size = sizeof(relopt_ternary);
+ break;
case RELOPT_TYPE_INT:
size = sizeof(relopt_int);
break;
@@ -883,6 +907,54 @@ add_local_bool_reloption(local_relopts *relopts, const char *name,
add_local_reloption(relopts, (relopt_gen *) newoption, offset);
}
+/*
+ * init_ternary_reloption
+ * Allocate and initialize a new ternary reloption
+ */
+static relopt_ternary *
+init_ternary_reloption(bits32 kinds, const char *name, const char *desc,
+ ternary default_val, LOCKMODE lockmode)
+{
+ relopt_ternary *newoption;
+
+ newoption = (relopt_ternary *) allocate_reloption(kinds,
+ RELOPT_TYPE_TERNARY, name, desc, lockmode);
+ newoption->default_val = default_val;
+
+ return newoption;
+}
+
+/*
+ * add_ternary_reloption
+ * Add a new ternary reloption
+ */
+void
+add_ternary_reloption(bits32 kinds, const char *name, const char *desc,
+ ternary default_val, LOCKMODE lockmode)
+{
+ relopt_ternary *newoption = init_ternary_reloption(kinds, name, desc,
+ default_val, lockmode);
+
+ add_reloption((relopt_gen *) newoption);
+}
+
+/*
+ * add_local_ternary_reloption
+ * Add a new ternary local reloption
+ *
+ * 'offset' is offset of ternary-typed field.
+ */
+void
+add_local_ternary_reloption(local_relopts *relopts, const char *name,
+ const char *desc, ternary default_val,
+ int offset)
+{
+ relopt_ternary *newoption = init_ternary_reloption(RELOPT_KIND_LOCAL,
+ name, desc,
+ default_val, 0);
+
+ add_local_reloption(relopts, (relopt_gen *) newoption, offset);
+}
/*
* init_real_reloption
@@ -1617,6 +1689,18 @@ parse_one_reloption(relopt_value *option, char *text_str, int text_len,
option->gen->name, value)));
}
break;
+ case RELOPT_TYPE_TERNARY:
+ {
+ bool b;
+ parsed = parse_bool(value, &b);
+ option->values.ternary_val = b ? TERNARY_TRUE : TERNARY_FALSE;
+ if (validate && !parsed)
+ ereport(ERROR,
+ (errcode(ERRCODE_INVALID_PARAMETER_VALUE),
+ errmsg("invalid value for ternary option \"%s\": %s",
+ option->gen->name, value)));
+ }
+ break;
case RELOPT_TYPE_INT:
{
relopt_int *optint = (relopt_int *) option->gen;
@@ -1780,17 +1864,6 @@ fillRelOptions(void *rdopts, Size basesize,
char *itempos = ((char *) rdopts) + elems[j].offset;
char *string_val;
- /*
- * If isset_offset is provided, store whether the reloption is
- * set there.
- */
- if (elems[j].isset_offset > 0)
- {
- char *setpos = ((char *) rdopts) + elems[j].isset_offset;
-
- *(bool *) setpos = options[i].isset;
- }
-
switch (options[i].gen->type)
{
case RELOPT_TYPE_BOOL:
@@ -1798,6 +1871,11 @@ fillRelOptions(void *rdopts, Size basesize,
options[i].values.bool_val :
((relopt_bool *) options[i].gen)->default_val;
break;
+ case RELOPT_TYPE_TERNARY:
+ *(ternary *) itempos = options[i].isset ?
+ options[i].values.ternary_val :
+ ((relopt_ternary *) options[i].gen)->default_val;
+ break;
case RELOPT_TYPE_INT:
*(int *) itempos = options[i].isset ?
options[i].values.int_val :
@@ -1912,8 +1990,8 @@ default_reloptions(Datum reloptions, bool validate, relopt_kind kind)
offsetof(StdRdOptions, parallel_workers)},
{"vacuum_index_cleanup", RELOPT_TYPE_ENUM,
offsetof(StdRdOptions, vacuum_index_cleanup)},
- {"vacuum_truncate", RELOPT_TYPE_BOOL,
- offsetof(StdRdOptions, vacuum_truncate), offsetof(StdRdOptions, vacuum_truncate_set)},
+ {"vacuum_truncate", RELOPT_TYPE_TERNARY,
+ offsetof(StdRdOptions, vacuum_truncate)},
{"vacuum_max_eager_freeze_failure_rate", RELOPT_TYPE_REAL,
offsetof(StdRdOptions, vacuum_max_eager_freeze_failure_rate)}
};
@@ -1993,7 +2071,6 @@ build_local_reloptions(local_relopts *relopts, Datum options, bool validate)
elems[i].optname = opt->option->name;
elems[i].opttype = opt->option->type;
elems[i].offset = opt->offset;
- elems[i].isset_offset = 0; /* not supported for local relopts yet */
i++;
}
diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c
index 733ef40ae7..977babff54 100644
--- a/src/backend/commands/vacuum.c
+++ b/src/backend/commands/vacuum.c
@@ -2221,9 +2221,9 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams params,
{
StdRdOptions *opts = (StdRdOptions *) rel->rd_options;
- if (opts && opts->vacuum_truncate_set)
+ if (opts && opts->vacuum_truncate != TERNARY_UNSET)
{
- if (opts->vacuum_truncate)
+ if (opts->vacuum_truncate == TERNARY_TRUE)
params.truncate = VACOPTVALUE_ENABLED;
else
params.truncate = VACOPTVALUE_DISABLED;
diff --git a/src/include/access/reloptions.h b/src/include/access/reloptions.h
index a604a4702c..a436697658 100644
--- a/src/include/access/reloptions.h
+++ b/src/include/access/reloptions.h
@@ -29,6 +29,7 @@
typedef enum relopt_type
{
RELOPT_TYPE_BOOL,
+ RELOPT_TYPE_TERNARY, /* on, off, unset */
RELOPT_TYPE_INT,
RELOPT_TYPE_REAL,
RELOPT_TYPE_ENUM,
@@ -80,6 +81,7 @@ typedef struct relopt_value
union
{
bool bool_val;
+ ternary ternary_val;
int int_val;
double real_val;
int enum_val;
@@ -94,6 +96,12 @@ typedef struct relopt_bool
bool default_val;
} relopt_bool;
+typedef struct relopt_rernary
+{
+ relopt_gen gen;
+ int default_val;
+} relopt_ternary;
+
typedef struct relopt_int
{
relopt_gen gen;
@@ -152,19 +160,6 @@ typedef struct
const char *optname; /* option's name */
relopt_type opttype; /* option's datatype */
int offset; /* offset of field in result struct */
-
- /*
- * isset_offset is an optional offset of a field in the result struct that
- * stores whether the option is explicitly set for the relation or if it
- * just picked up the default value. In most cases, this can be
- * accomplished by giving the reloption a special out-of-range default
- * value (e.g., some integer reloptions use -2), but this isn't always
- * possible. For example, a Boolean reloption cannot be given an
- * out-of-range default, so we need another way to discover the source of
- * its value. This offset is only used if given a value greater than
- * zero.
- */
- int isset_offset;
} relopt_parse_elt;
/* Local reloption definition */
@@ -195,6 +190,8 @@ typedef struct local_relopts
extern relopt_kind add_reloption_kind(void);
extern void add_bool_reloption(bits32 kinds, const char *name, const char *desc,
bool default_val, LOCKMODE lockmode);
+extern void add_ternary_reloption(bits32 kinds, const char *name,
+ const char *desc, int default_val, LOCKMODE lockmode);
extern void add_int_reloption(bits32 kinds, const char *name, const char *desc,
int default_val, int min_val, int max_val,
LOCKMODE lockmode);
@@ -214,6 +211,9 @@ extern void register_reloptions_validator(local_relopts *relopts,
extern void add_local_bool_reloption(local_relopts *relopts, const char *name,
const char *desc, bool default_val,
int offset);
+extern void add_local_ternary_reloption(local_relopts *relopts,
+ const char *name, const char *desc,
+ ternary default_val, int offset);
extern void add_local_int_reloption(local_relopts *relopts, const char *name,
const char *desc, int default_val,
int min_val, int max_val, int offset);
diff --git a/src/include/c.h b/src/include/c.h
index 39022f8a9d..9c59ccccf8 100644
--- a/src/include/c.h
+++ b/src/include/c.h
@@ -488,6 +488,22 @@ typedef void (*pg_funcptr_t) (void);
#include <stdbool.h>
+/*
+ * ternary
+ * Boolean value with an extrea "unset" option
+ *
+ * Ternary data type is used in relation options that can be "true", "false" or
+ * "unset". Since relation options are used deep inside the PostgreSQL code,
+ * this type is declared globally.
+*/
+
+typedef enum ternary
+{
+ TERNARY_FALSE = 0,
+ TERNARY_TRUE = 1,
+ TERNARY_UNSET = -1
+} ternary;
+
/* ----------------------------------------------------------------
* Section 3: standard system types
diff --git a/src/include/utils/rel.h b/src/include/utils/rel.h
index b552359915..7346d618f9 100644
--- a/src/include/utils/rel.h
+++ b/src/include/utils/rel.h
@@ -346,8 +346,7 @@ typedef struct StdRdOptions
bool user_catalog_table; /* use as an additional catalog relation */
int parallel_workers; /* max number of parallel workers */
StdRdOptIndexCleanup vacuum_index_cleanup; /* controls index vacuuming */
- bool vacuum_truncate; /* enables vacuum to truncate a relation */
- bool vacuum_truncate_set; /* whether vacuum_truncate is set */
+ ternary vacuum_truncate; /* enables vacuum to truncate a relation */
/*
* Fraction of pages in a relation that vacuum can eagerly scan and fail
--
2.39.2
--nextPart4155600.3ZeAukHxDK
Content-Disposition: attachment;
filename="v2a-0003-Add-alias-to-be-used-as-unset-state.patch"
Content-Transfer-Encoding: 7Bit
Content-Type: text/x-patch; charset="unicode-2-0-utf-8";
name="v2a-0003-Add-alias-to-be-used-as-unset-state.patch"
^ permalink raw reply [nested|flat] 868+ messages in thread
* [PATCH v2 2/4] Introduce ternary reloptions
@ 2025-09-04 14:41 Nikolay Shaplov <[email protected]>
0 siblings, 0 replies; 868+ messages in thread
From: Nikolay Shaplov @ 2025-09-04 14:41 UTC (permalink / raw)
Introduce ternary reloption as a replacement for current `vacuum_truncate`
implementation. Remove `vacuum_truncate_set` additional flag and using
`TERNARY_UNSET` value instead.
---
src/backend/access/common/reloptions.c | 129 ++++++++++++++++++++-----
src/backend/commands/vacuum.c | 4 +-
src/include/access/reloptions.h | 26 ++---
src/include/c.h | 16 +++
src/include/utils/rel.h | 3 +-
5 files changed, 135 insertions(+), 43 deletions(-)
diff --git a/src/backend/access/common/reloptions.c b/src/backend/access/common/reloptions.c
index 0af3fea68f..f543f11001 100644
--- a/src/backend/access/common/reloptions.c
+++ b/src/backend/access/common/reloptions.c
@@ -40,9 +40,9 @@
*
* To add an option:
*
- * (i) decide on a type (bool, integer, real, enum, string), name, default
- * value, upper and lower bounds (if applicable); for strings, consider a
- * validation routine.
+ * (i) decide on a type (bool, ternary, integer, real, enum, string), name,
+ * default value, upper and lower bounds (if applicable); for strings,
+ * consider a validation routine.
* (ii) add a record below (or use add_<type>_reloption).
* (iii) add it to the appropriate options struct (perhaps StdRdOptions)
* (iv) add it to the appropriate handling routine (perhaps
@@ -147,15 +147,6 @@ static relopt_bool boolRelOpts[] =
},
false
},
- {
- {
- "vacuum_truncate",
- "Enables vacuum to truncate empty pages at the end of this table",
- RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
- ShareUpdateExclusiveLock
- },
- true
- },
{
{
"deduplicate_items",
@@ -170,6 +161,21 @@ static relopt_bool boolRelOpts[] =
{{NULL}}
};
+static relopt_ternary ternaryRelOpts[] =
+{
+ {
+ {
+ "vacuum_truncate",
+ "Enables vacuum to truncate empty pages at the end of this table",
+ RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
+ ShareUpdateExclusiveLock
+ },
+ TERNARY_UNSET
+ },
+ /* list terminator */
+ {{NULL}}
+};
+
static relopt_int intRelOpts[] =
{
{
@@ -600,6 +606,13 @@ initialize_reloptions(void)
boolRelOpts[i].gen.lockmode));
j++;
}
+ for (i = 0; ternaryRelOpts[i].gen.name; i++)
+ {
+ Assert(DoLockModesConflict(ternaryRelOpts[i].gen.lockmode,
+ ternaryRelOpts[i].gen.lockmode));
+ j++;
+ }
+
for (i = 0; intRelOpts[i].gen.name; i++)
{
Assert(DoLockModesConflict(intRelOpts[i].gen.lockmode,
@@ -640,6 +653,14 @@ initialize_reloptions(void)
j++;
}
+ for (i = 0; ternaryRelOpts[i].gen.name; i++)
+ {
+ relOpts[j] = &ternaryRelOpts[i].gen;
+ relOpts[j]->type = RELOPT_TYPE_TERNARY;
+ relOpts[j]->namelen = strlen(relOpts[j]->name);
+ j++;
+ }
+
for (i = 0; intRelOpts[i].gen.name; i++)
{
relOpts[j] = &intRelOpts[i].gen;
@@ -800,6 +821,9 @@ allocate_reloption(bits32 kinds, int type, const char *name, const char *desc,
case RELOPT_TYPE_BOOL:
size = sizeof(relopt_bool);
break;
+ case RELOPT_TYPE_TERNARY:
+ size = sizeof(relopt_ternary);
+ break;
case RELOPT_TYPE_INT:
size = sizeof(relopt_int);
break;
@@ -883,6 +907,54 @@ add_local_bool_reloption(local_relopts *relopts, const char *name,
add_local_reloption(relopts, (relopt_gen *) newoption, offset);
}
+/*
+ * init_ternary_reloption
+ * Allocate and initialize a new ternary reloption
+ */
+static relopt_ternary *
+init_ternary_reloption(bits32 kinds, const char *name, const char *desc,
+ ternary default_val, LOCKMODE lockmode)
+{
+ relopt_ternary *newoption;
+
+ newoption = (relopt_ternary *) allocate_reloption(kinds,
+ RELOPT_TYPE_TERNARY, name, desc, lockmode);
+ newoption->default_val = default_val;
+
+ return newoption;
+}
+
+/*
+ * add_ternary_reloption
+ * Add a new ternary reloption
+ */
+void
+add_ternary_reloption(bits32 kinds, const char *name, const char *desc,
+ ternary default_val, LOCKMODE lockmode)
+{
+ relopt_ternary *newoption = init_ternary_reloption(kinds, name, desc,
+ default_val, lockmode);
+
+ add_reloption((relopt_gen *) newoption);
+}
+
+/*
+ * add_local_ternary_reloption
+ * Add a new ternary local reloption
+ *
+ * 'offset' is offset of ternary-typed field.
+ */
+void
+add_local_ternary_reloption(local_relopts *relopts, const char *name,
+ const char *desc, ternary default_val,
+ int offset)
+{
+ relopt_ternary *newoption = init_ternary_reloption(RELOPT_KIND_LOCAL,
+ name, desc,
+ default_val, 0);
+
+ add_local_reloption(relopts, (relopt_gen *) newoption, offset);
+}
/*
* init_real_reloption
@@ -1617,6 +1689,18 @@ parse_one_reloption(relopt_value *option, char *text_str, int text_len,
option->gen->name, value)));
}
break;
+ case RELOPT_TYPE_TERNARY:
+ {
+ bool b;
+ parsed = parse_bool(value, &b);
+ option->values.ternary_val = b ? TERNARY_TRUE : TERNARY_FALSE;
+ if (validate && !parsed)
+ ereport(ERROR,
+ (errcode(ERRCODE_INVALID_PARAMETER_VALUE),
+ errmsg("invalid value for ternary option \"%s\": %s",
+ option->gen->name, value)));
+ }
+ break;
case RELOPT_TYPE_INT:
{
relopt_int *optint = (relopt_int *) option->gen;
@@ -1780,17 +1864,6 @@ fillRelOptions(void *rdopts, Size basesize,
char *itempos = ((char *) rdopts) + elems[j].offset;
char *string_val;
- /*
- * If isset_offset is provided, store whether the reloption is
- * set there.
- */
- if (elems[j].isset_offset > 0)
- {
- char *setpos = ((char *) rdopts) + elems[j].isset_offset;
-
- *(bool *) setpos = options[i].isset;
- }
-
switch (options[i].gen->type)
{
case RELOPT_TYPE_BOOL:
@@ -1798,6 +1871,11 @@ fillRelOptions(void *rdopts, Size basesize,
options[i].values.bool_val :
((relopt_bool *) options[i].gen)->default_val;
break;
+ case RELOPT_TYPE_TERNARY:
+ *(ternary *) itempos = options[i].isset ?
+ options[i].values.ternary_val :
+ ((relopt_ternary *) options[i].gen)->default_val;
+ break;
case RELOPT_TYPE_INT:
*(int *) itempos = options[i].isset ?
options[i].values.int_val :
@@ -1912,8 +1990,8 @@ default_reloptions(Datum reloptions, bool validate, relopt_kind kind)
offsetof(StdRdOptions, parallel_workers)},
{"vacuum_index_cleanup", RELOPT_TYPE_ENUM,
offsetof(StdRdOptions, vacuum_index_cleanup)},
- {"vacuum_truncate", RELOPT_TYPE_BOOL,
- offsetof(StdRdOptions, vacuum_truncate), offsetof(StdRdOptions, vacuum_truncate_set)},
+ {"vacuum_truncate", RELOPT_TYPE_TERNARY,
+ offsetof(StdRdOptions, vacuum_truncate)},
{"vacuum_max_eager_freeze_failure_rate", RELOPT_TYPE_REAL,
offsetof(StdRdOptions, vacuum_max_eager_freeze_failure_rate)}
};
@@ -1993,7 +2071,6 @@ build_local_reloptions(local_relopts *relopts, Datum options, bool validate)
elems[i].optname = opt->option->name;
elems[i].opttype = opt->option->type;
elems[i].offset = opt->offset;
- elems[i].isset_offset = 0; /* not supported for local relopts yet */
i++;
}
diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c
index 733ef40ae7..977babff54 100644
--- a/src/backend/commands/vacuum.c
+++ b/src/backend/commands/vacuum.c
@@ -2221,9 +2221,9 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams params,
{
StdRdOptions *opts = (StdRdOptions *) rel->rd_options;
- if (opts && opts->vacuum_truncate_set)
+ if (opts && opts->vacuum_truncate != TERNARY_UNSET)
{
- if (opts->vacuum_truncate)
+ if (opts->vacuum_truncate == TERNARY_TRUE)
params.truncate = VACOPTVALUE_ENABLED;
else
params.truncate = VACOPTVALUE_DISABLED;
diff --git a/src/include/access/reloptions.h b/src/include/access/reloptions.h
index a604a4702c..a436697658 100644
--- a/src/include/access/reloptions.h
+++ b/src/include/access/reloptions.h
@@ -29,6 +29,7 @@
typedef enum relopt_type
{
RELOPT_TYPE_BOOL,
+ RELOPT_TYPE_TERNARY, /* on, off, unset */
RELOPT_TYPE_INT,
RELOPT_TYPE_REAL,
RELOPT_TYPE_ENUM,
@@ -80,6 +81,7 @@ typedef struct relopt_value
union
{
bool bool_val;
+ ternary ternary_val;
int int_val;
double real_val;
int enum_val;
@@ -94,6 +96,12 @@ typedef struct relopt_bool
bool default_val;
} relopt_bool;
+typedef struct relopt_rernary
+{
+ relopt_gen gen;
+ int default_val;
+} relopt_ternary;
+
typedef struct relopt_int
{
relopt_gen gen;
@@ -152,19 +160,6 @@ typedef struct
const char *optname; /* option's name */
relopt_type opttype; /* option's datatype */
int offset; /* offset of field in result struct */
-
- /*
- * isset_offset is an optional offset of a field in the result struct that
- * stores whether the option is explicitly set for the relation or if it
- * just picked up the default value. In most cases, this can be
- * accomplished by giving the reloption a special out-of-range default
- * value (e.g., some integer reloptions use -2), but this isn't always
- * possible. For example, a Boolean reloption cannot be given an
- * out-of-range default, so we need another way to discover the source of
- * its value. This offset is only used if given a value greater than
- * zero.
- */
- int isset_offset;
} relopt_parse_elt;
/* Local reloption definition */
@@ -195,6 +190,8 @@ typedef struct local_relopts
extern relopt_kind add_reloption_kind(void);
extern void add_bool_reloption(bits32 kinds, const char *name, const char *desc,
bool default_val, LOCKMODE lockmode);
+extern void add_ternary_reloption(bits32 kinds, const char *name,
+ const char *desc, int default_val, LOCKMODE lockmode);
extern void add_int_reloption(bits32 kinds, const char *name, const char *desc,
int default_val, int min_val, int max_val,
LOCKMODE lockmode);
@@ -214,6 +211,9 @@ extern void register_reloptions_validator(local_relopts *relopts,
extern void add_local_bool_reloption(local_relopts *relopts, const char *name,
const char *desc, bool default_val,
int offset);
+extern void add_local_ternary_reloption(local_relopts *relopts,
+ const char *name, const char *desc,
+ ternary default_val, int offset);
extern void add_local_int_reloption(local_relopts *relopts, const char *name,
const char *desc, int default_val,
int min_val, int max_val, int offset);
diff --git a/src/include/c.h b/src/include/c.h
index 39022f8a9d..9c59ccccf8 100644
--- a/src/include/c.h
+++ b/src/include/c.h
@@ -488,6 +488,22 @@ typedef void (*pg_funcptr_t) (void);
#include <stdbool.h>
+/*
+ * ternary
+ * Boolean value with an extrea "unset" option
+ *
+ * Ternary data type is used in relation options that can be "true", "false" or
+ * "unset". Since relation options are used deep inside the PostgreSQL code,
+ * this type is declared globally.
+*/
+
+typedef enum ternary
+{
+ TERNARY_FALSE = 0,
+ TERNARY_TRUE = 1,
+ TERNARY_UNSET = -1
+} ternary;
+
/* ----------------------------------------------------------------
* Section 3: standard system types
diff --git a/src/include/utils/rel.h b/src/include/utils/rel.h
index b552359915..7346d618f9 100644
--- a/src/include/utils/rel.h
+++ b/src/include/utils/rel.h
@@ -346,8 +346,7 @@ typedef struct StdRdOptions
bool user_catalog_table; /* use as an additional catalog relation */
int parallel_workers; /* max number of parallel workers */
StdRdOptIndexCleanup vacuum_index_cleanup; /* controls index vacuuming */
- bool vacuum_truncate; /* enables vacuum to truncate a relation */
- bool vacuum_truncate_set; /* whether vacuum_truncate is set */
+ ternary vacuum_truncate; /* enables vacuum to truncate a relation */
/*
* Fraction of pages in a relation that vacuum can eagerly scan and fail
--
2.39.2
--nextPart5129932.5fSG56mABF
Content-Disposition: attachment;
filename="v2-0003-Add-alias-to-be-used-as-unset-state.patch"
Content-Transfer-Encoding: 7Bit
Content-Type: text/x-patch; charset="unicode-2-0-utf-8";
name="v2-0003-Add-alias-to-be-used-as-unset-state.patch"
^ permalink raw reply [nested|flat] 868+ messages in thread
* [PATCH v2 2/4] Introduce ternary reloptions
@ 2025-09-04 14:41 Nikolay Shaplov <[email protected]>
0 siblings, 0 replies; 868+ messages in thread
From: Nikolay Shaplov @ 2025-09-04 14:41 UTC (permalink / raw)
Introduce ternary reloption as a replacement for current `vacuum_truncate`
implementation. Remove `vacuum_truncate_set` additional flag and using
`TERNARY_UNSET` value instead.
---
src/backend/access/common/reloptions.c | 129 ++++++++++++++++++++-----
src/backend/commands/vacuum.c | 4 +-
src/include/access/reloptions.h | 26 ++---
src/include/c.h | 16 +++
src/include/utils/rel.h | 3 +-
5 files changed, 135 insertions(+), 43 deletions(-)
diff --git a/src/backend/access/common/reloptions.c b/src/backend/access/common/reloptions.c
index 0af3fea68f..f543f11001 100644
--- a/src/backend/access/common/reloptions.c
+++ b/src/backend/access/common/reloptions.c
@@ -40,9 +40,9 @@
*
* To add an option:
*
- * (i) decide on a type (bool, integer, real, enum, string), name, default
- * value, upper and lower bounds (if applicable); for strings, consider a
- * validation routine.
+ * (i) decide on a type (bool, ternary, integer, real, enum, string), name,
+ * default value, upper and lower bounds (if applicable); for strings,
+ * consider a validation routine.
* (ii) add a record below (or use add_<type>_reloption).
* (iii) add it to the appropriate options struct (perhaps StdRdOptions)
* (iv) add it to the appropriate handling routine (perhaps
@@ -147,15 +147,6 @@ static relopt_bool boolRelOpts[] =
},
false
},
- {
- {
- "vacuum_truncate",
- "Enables vacuum to truncate empty pages at the end of this table",
- RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
- ShareUpdateExclusiveLock
- },
- true
- },
{
{
"deduplicate_items",
@@ -170,6 +161,21 @@ static relopt_bool boolRelOpts[] =
{{NULL}}
};
+static relopt_ternary ternaryRelOpts[] =
+{
+ {
+ {
+ "vacuum_truncate",
+ "Enables vacuum to truncate empty pages at the end of this table",
+ RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
+ ShareUpdateExclusiveLock
+ },
+ TERNARY_UNSET
+ },
+ /* list terminator */
+ {{NULL}}
+};
+
static relopt_int intRelOpts[] =
{
{
@@ -600,6 +606,13 @@ initialize_reloptions(void)
boolRelOpts[i].gen.lockmode));
j++;
}
+ for (i = 0; ternaryRelOpts[i].gen.name; i++)
+ {
+ Assert(DoLockModesConflict(ternaryRelOpts[i].gen.lockmode,
+ ternaryRelOpts[i].gen.lockmode));
+ j++;
+ }
+
for (i = 0; intRelOpts[i].gen.name; i++)
{
Assert(DoLockModesConflict(intRelOpts[i].gen.lockmode,
@@ -640,6 +653,14 @@ initialize_reloptions(void)
j++;
}
+ for (i = 0; ternaryRelOpts[i].gen.name; i++)
+ {
+ relOpts[j] = &ternaryRelOpts[i].gen;
+ relOpts[j]->type = RELOPT_TYPE_TERNARY;
+ relOpts[j]->namelen = strlen(relOpts[j]->name);
+ j++;
+ }
+
for (i = 0; intRelOpts[i].gen.name; i++)
{
relOpts[j] = &intRelOpts[i].gen;
@@ -800,6 +821,9 @@ allocate_reloption(bits32 kinds, int type, const char *name, const char *desc,
case RELOPT_TYPE_BOOL:
size = sizeof(relopt_bool);
break;
+ case RELOPT_TYPE_TERNARY:
+ size = sizeof(relopt_ternary);
+ break;
case RELOPT_TYPE_INT:
size = sizeof(relopt_int);
break;
@@ -883,6 +907,54 @@ add_local_bool_reloption(local_relopts *relopts, const char *name,
add_local_reloption(relopts, (relopt_gen *) newoption, offset);
}
+/*
+ * init_ternary_reloption
+ * Allocate and initialize a new ternary reloption
+ */
+static relopt_ternary *
+init_ternary_reloption(bits32 kinds, const char *name, const char *desc,
+ ternary default_val, LOCKMODE lockmode)
+{
+ relopt_ternary *newoption;
+
+ newoption = (relopt_ternary *) allocate_reloption(kinds,
+ RELOPT_TYPE_TERNARY, name, desc, lockmode);
+ newoption->default_val = default_val;
+
+ return newoption;
+}
+
+/*
+ * add_ternary_reloption
+ * Add a new ternary reloption
+ */
+void
+add_ternary_reloption(bits32 kinds, const char *name, const char *desc,
+ ternary default_val, LOCKMODE lockmode)
+{
+ relopt_ternary *newoption = init_ternary_reloption(kinds, name, desc,
+ default_val, lockmode);
+
+ add_reloption((relopt_gen *) newoption);
+}
+
+/*
+ * add_local_ternary_reloption
+ * Add a new ternary local reloption
+ *
+ * 'offset' is offset of ternary-typed field.
+ */
+void
+add_local_ternary_reloption(local_relopts *relopts, const char *name,
+ const char *desc, ternary default_val,
+ int offset)
+{
+ relopt_ternary *newoption = init_ternary_reloption(RELOPT_KIND_LOCAL,
+ name, desc,
+ default_val, 0);
+
+ add_local_reloption(relopts, (relopt_gen *) newoption, offset);
+}
/*
* init_real_reloption
@@ -1617,6 +1689,18 @@ parse_one_reloption(relopt_value *option, char *text_str, int text_len,
option->gen->name, value)));
}
break;
+ case RELOPT_TYPE_TERNARY:
+ {
+ bool b;
+ parsed = parse_bool(value, &b);
+ option->values.ternary_val = b ? TERNARY_TRUE : TERNARY_FALSE;
+ if (validate && !parsed)
+ ereport(ERROR,
+ (errcode(ERRCODE_INVALID_PARAMETER_VALUE),
+ errmsg("invalid value for ternary option \"%s\": %s",
+ option->gen->name, value)));
+ }
+ break;
case RELOPT_TYPE_INT:
{
relopt_int *optint = (relopt_int *) option->gen;
@@ -1780,17 +1864,6 @@ fillRelOptions(void *rdopts, Size basesize,
char *itempos = ((char *) rdopts) + elems[j].offset;
char *string_val;
- /*
- * If isset_offset is provided, store whether the reloption is
- * set there.
- */
- if (elems[j].isset_offset > 0)
- {
- char *setpos = ((char *) rdopts) + elems[j].isset_offset;
-
- *(bool *) setpos = options[i].isset;
- }
-
switch (options[i].gen->type)
{
case RELOPT_TYPE_BOOL:
@@ -1798,6 +1871,11 @@ fillRelOptions(void *rdopts, Size basesize,
options[i].values.bool_val :
((relopt_bool *) options[i].gen)->default_val;
break;
+ case RELOPT_TYPE_TERNARY:
+ *(ternary *) itempos = options[i].isset ?
+ options[i].values.ternary_val :
+ ((relopt_ternary *) options[i].gen)->default_val;
+ break;
case RELOPT_TYPE_INT:
*(int *) itempos = options[i].isset ?
options[i].values.int_val :
@@ -1912,8 +1990,8 @@ default_reloptions(Datum reloptions, bool validate, relopt_kind kind)
offsetof(StdRdOptions, parallel_workers)},
{"vacuum_index_cleanup", RELOPT_TYPE_ENUM,
offsetof(StdRdOptions, vacuum_index_cleanup)},
- {"vacuum_truncate", RELOPT_TYPE_BOOL,
- offsetof(StdRdOptions, vacuum_truncate), offsetof(StdRdOptions, vacuum_truncate_set)},
+ {"vacuum_truncate", RELOPT_TYPE_TERNARY,
+ offsetof(StdRdOptions, vacuum_truncate)},
{"vacuum_max_eager_freeze_failure_rate", RELOPT_TYPE_REAL,
offsetof(StdRdOptions, vacuum_max_eager_freeze_failure_rate)}
};
@@ -1993,7 +2071,6 @@ build_local_reloptions(local_relopts *relopts, Datum options, bool validate)
elems[i].optname = opt->option->name;
elems[i].opttype = opt->option->type;
elems[i].offset = opt->offset;
- elems[i].isset_offset = 0; /* not supported for local relopts yet */
i++;
}
diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c
index 733ef40ae7..977babff54 100644
--- a/src/backend/commands/vacuum.c
+++ b/src/backend/commands/vacuum.c
@@ -2221,9 +2221,9 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams params,
{
StdRdOptions *opts = (StdRdOptions *) rel->rd_options;
- if (opts && opts->vacuum_truncate_set)
+ if (opts && opts->vacuum_truncate != TERNARY_UNSET)
{
- if (opts->vacuum_truncate)
+ if (opts->vacuum_truncate == TERNARY_TRUE)
params.truncate = VACOPTVALUE_ENABLED;
else
params.truncate = VACOPTVALUE_DISABLED;
diff --git a/src/include/access/reloptions.h b/src/include/access/reloptions.h
index a604a4702c..a436697658 100644
--- a/src/include/access/reloptions.h
+++ b/src/include/access/reloptions.h
@@ -29,6 +29,7 @@
typedef enum relopt_type
{
RELOPT_TYPE_BOOL,
+ RELOPT_TYPE_TERNARY, /* on, off, unset */
RELOPT_TYPE_INT,
RELOPT_TYPE_REAL,
RELOPT_TYPE_ENUM,
@@ -80,6 +81,7 @@ typedef struct relopt_value
union
{
bool bool_val;
+ ternary ternary_val;
int int_val;
double real_val;
int enum_val;
@@ -94,6 +96,12 @@ typedef struct relopt_bool
bool default_val;
} relopt_bool;
+typedef struct relopt_rernary
+{
+ relopt_gen gen;
+ int default_val;
+} relopt_ternary;
+
typedef struct relopt_int
{
relopt_gen gen;
@@ -152,19 +160,6 @@ typedef struct
const char *optname; /* option's name */
relopt_type opttype; /* option's datatype */
int offset; /* offset of field in result struct */
-
- /*
- * isset_offset is an optional offset of a field in the result struct that
- * stores whether the option is explicitly set for the relation or if it
- * just picked up the default value. In most cases, this can be
- * accomplished by giving the reloption a special out-of-range default
- * value (e.g., some integer reloptions use -2), but this isn't always
- * possible. For example, a Boolean reloption cannot be given an
- * out-of-range default, so we need another way to discover the source of
- * its value. This offset is only used if given a value greater than
- * zero.
- */
- int isset_offset;
} relopt_parse_elt;
/* Local reloption definition */
@@ -195,6 +190,8 @@ typedef struct local_relopts
extern relopt_kind add_reloption_kind(void);
extern void add_bool_reloption(bits32 kinds, const char *name, const char *desc,
bool default_val, LOCKMODE lockmode);
+extern void add_ternary_reloption(bits32 kinds, const char *name,
+ const char *desc, int default_val, LOCKMODE lockmode);
extern void add_int_reloption(bits32 kinds, const char *name, const char *desc,
int default_val, int min_val, int max_val,
LOCKMODE lockmode);
@@ -214,6 +211,9 @@ extern void register_reloptions_validator(local_relopts *relopts,
extern void add_local_bool_reloption(local_relopts *relopts, const char *name,
const char *desc, bool default_val,
int offset);
+extern void add_local_ternary_reloption(local_relopts *relopts,
+ const char *name, const char *desc,
+ ternary default_val, int offset);
extern void add_local_int_reloption(local_relopts *relopts, const char *name,
const char *desc, int default_val,
int min_val, int max_val, int offset);
diff --git a/src/include/c.h b/src/include/c.h
index 39022f8a9d..9c59ccccf8 100644
--- a/src/include/c.h
+++ b/src/include/c.h
@@ -488,6 +488,22 @@ typedef void (*pg_funcptr_t) (void);
#include <stdbool.h>
+/*
+ * ternary
+ * Boolean value with an extrea "unset" option
+ *
+ * Ternary data type is used in relation options that can be "true", "false" or
+ * "unset". Since relation options are used deep inside the PostgreSQL code,
+ * this type is declared globally.
+*/
+
+typedef enum ternary
+{
+ TERNARY_FALSE = 0,
+ TERNARY_TRUE = 1,
+ TERNARY_UNSET = -1
+} ternary;
+
/* ----------------------------------------------------------------
* Section 3: standard system types
diff --git a/src/include/utils/rel.h b/src/include/utils/rel.h
index b552359915..7346d618f9 100644
--- a/src/include/utils/rel.h
+++ b/src/include/utils/rel.h
@@ -346,8 +346,7 @@ typedef struct StdRdOptions
bool user_catalog_table; /* use as an additional catalog relation */
int parallel_workers; /* max number of parallel workers */
StdRdOptIndexCleanup vacuum_index_cleanup; /* controls index vacuuming */
- bool vacuum_truncate; /* enables vacuum to truncate a relation */
- bool vacuum_truncate_set; /* whether vacuum_truncate is set */
+ ternary vacuum_truncate; /* enables vacuum to truncate a relation */
/*
* Fraction of pages in a relation that vacuum can eagerly scan and fail
--
2.39.2
--nextPart5129932.5fSG56mABF
Content-Disposition: attachment;
filename="v2-0003-Add-alias-to-be-used-as-unset-state.patch"
Content-Transfer-Encoding: 7Bit
Content-Type: text/x-patch; charset="unicode-2-0-utf-8";
name="v2-0003-Add-alias-to-be-used-as-unset-state.patch"
^ permalink raw reply [nested|flat] 868+ messages in thread
* [PATCH v2 2/4] Introduce ternary reloptions
@ 2025-09-04 14:41 Nikolay Shaplov <[email protected]>
0 siblings, 0 replies; 868+ messages in thread
From: Nikolay Shaplov @ 2025-09-04 14:41 UTC (permalink / raw)
Introduce ternary reloption as a replacement for current `vacuum_truncate`
implementation. Remove `vacuum_truncate_set` additional flag and using
`TERNARY_UNSET` value instead.
---
src/backend/access/common/reloptions.c | 129 ++++++++++++++++++++-----
src/backend/commands/vacuum.c | 4 +-
src/include/access/reloptions.h | 26 ++---
src/include/c.h | 16 +++
src/include/utils/rel.h | 3 +-
5 files changed, 135 insertions(+), 43 deletions(-)
diff --git a/src/backend/access/common/reloptions.c b/src/backend/access/common/reloptions.c
index 0af3fea68f..f543f11001 100644
--- a/src/backend/access/common/reloptions.c
+++ b/src/backend/access/common/reloptions.c
@@ -40,9 +40,9 @@
*
* To add an option:
*
- * (i) decide on a type (bool, integer, real, enum, string), name, default
- * value, upper and lower bounds (if applicable); for strings, consider a
- * validation routine.
+ * (i) decide on a type (bool, ternary, integer, real, enum, string), name,
+ * default value, upper and lower bounds (if applicable); for strings,
+ * consider a validation routine.
* (ii) add a record below (or use add_<type>_reloption).
* (iii) add it to the appropriate options struct (perhaps StdRdOptions)
* (iv) add it to the appropriate handling routine (perhaps
@@ -147,15 +147,6 @@ static relopt_bool boolRelOpts[] =
},
false
},
- {
- {
- "vacuum_truncate",
- "Enables vacuum to truncate empty pages at the end of this table",
- RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
- ShareUpdateExclusiveLock
- },
- true
- },
{
{
"deduplicate_items",
@@ -170,6 +161,21 @@ static relopt_bool boolRelOpts[] =
{{NULL}}
};
+static relopt_ternary ternaryRelOpts[] =
+{
+ {
+ {
+ "vacuum_truncate",
+ "Enables vacuum to truncate empty pages at the end of this table",
+ RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
+ ShareUpdateExclusiveLock
+ },
+ TERNARY_UNSET
+ },
+ /* list terminator */
+ {{NULL}}
+};
+
static relopt_int intRelOpts[] =
{
{
@@ -600,6 +606,13 @@ initialize_reloptions(void)
boolRelOpts[i].gen.lockmode));
j++;
}
+ for (i = 0; ternaryRelOpts[i].gen.name; i++)
+ {
+ Assert(DoLockModesConflict(ternaryRelOpts[i].gen.lockmode,
+ ternaryRelOpts[i].gen.lockmode));
+ j++;
+ }
+
for (i = 0; intRelOpts[i].gen.name; i++)
{
Assert(DoLockModesConflict(intRelOpts[i].gen.lockmode,
@@ -640,6 +653,14 @@ initialize_reloptions(void)
j++;
}
+ for (i = 0; ternaryRelOpts[i].gen.name; i++)
+ {
+ relOpts[j] = &ternaryRelOpts[i].gen;
+ relOpts[j]->type = RELOPT_TYPE_TERNARY;
+ relOpts[j]->namelen = strlen(relOpts[j]->name);
+ j++;
+ }
+
for (i = 0; intRelOpts[i].gen.name; i++)
{
relOpts[j] = &intRelOpts[i].gen;
@@ -800,6 +821,9 @@ allocate_reloption(bits32 kinds, int type, const char *name, const char *desc,
case RELOPT_TYPE_BOOL:
size = sizeof(relopt_bool);
break;
+ case RELOPT_TYPE_TERNARY:
+ size = sizeof(relopt_ternary);
+ break;
case RELOPT_TYPE_INT:
size = sizeof(relopt_int);
break;
@@ -883,6 +907,54 @@ add_local_bool_reloption(local_relopts *relopts, const char *name,
add_local_reloption(relopts, (relopt_gen *) newoption, offset);
}
+/*
+ * init_ternary_reloption
+ * Allocate and initialize a new ternary reloption
+ */
+static relopt_ternary *
+init_ternary_reloption(bits32 kinds, const char *name, const char *desc,
+ ternary default_val, LOCKMODE lockmode)
+{
+ relopt_ternary *newoption;
+
+ newoption = (relopt_ternary *) allocate_reloption(kinds,
+ RELOPT_TYPE_TERNARY, name, desc, lockmode);
+ newoption->default_val = default_val;
+
+ return newoption;
+}
+
+/*
+ * add_ternary_reloption
+ * Add a new ternary reloption
+ */
+void
+add_ternary_reloption(bits32 kinds, const char *name, const char *desc,
+ ternary default_val, LOCKMODE lockmode)
+{
+ relopt_ternary *newoption = init_ternary_reloption(kinds, name, desc,
+ default_val, lockmode);
+
+ add_reloption((relopt_gen *) newoption);
+}
+
+/*
+ * add_local_ternary_reloption
+ * Add a new ternary local reloption
+ *
+ * 'offset' is offset of ternary-typed field.
+ */
+void
+add_local_ternary_reloption(local_relopts *relopts, const char *name,
+ const char *desc, ternary default_val,
+ int offset)
+{
+ relopt_ternary *newoption = init_ternary_reloption(RELOPT_KIND_LOCAL,
+ name, desc,
+ default_val, 0);
+
+ add_local_reloption(relopts, (relopt_gen *) newoption, offset);
+}
/*
* init_real_reloption
@@ -1617,6 +1689,18 @@ parse_one_reloption(relopt_value *option, char *text_str, int text_len,
option->gen->name, value)));
}
break;
+ case RELOPT_TYPE_TERNARY:
+ {
+ bool b;
+ parsed = parse_bool(value, &b);
+ option->values.ternary_val = b ? TERNARY_TRUE : TERNARY_FALSE;
+ if (validate && !parsed)
+ ereport(ERROR,
+ (errcode(ERRCODE_INVALID_PARAMETER_VALUE),
+ errmsg("invalid value for ternary option \"%s\": %s",
+ option->gen->name, value)));
+ }
+ break;
case RELOPT_TYPE_INT:
{
relopt_int *optint = (relopt_int *) option->gen;
@@ -1780,17 +1864,6 @@ fillRelOptions(void *rdopts, Size basesize,
char *itempos = ((char *) rdopts) + elems[j].offset;
char *string_val;
- /*
- * If isset_offset is provided, store whether the reloption is
- * set there.
- */
- if (elems[j].isset_offset > 0)
- {
- char *setpos = ((char *) rdopts) + elems[j].isset_offset;
-
- *(bool *) setpos = options[i].isset;
- }
-
switch (options[i].gen->type)
{
case RELOPT_TYPE_BOOL:
@@ -1798,6 +1871,11 @@ fillRelOptions(void *rdopts, Size basesize,
options[i].values.bool_val :
((relopt_bool *) options[i].gen)->default_val;
break;
+ case RELOPT_TYPE_TERNARY:
+ *(ternary *) itempos = options[i].isset ?
+ options[i].values.ternary_val :
+ ((relopt_ternary *) options[i].gen)->default_val;
+ break;
case RELOPT_TYPE_INT:
*(int *) itempos = options[i].isset ?
options[i].values.int_val :
@@ -1912,8 +1990,8 @@ default_reloptions(Datum reloptions, bool validate, relopt_kind kind)
offsetof(StdRdOptions, parallel_workers)},
{"vacuum_index_cleanup", RELOPT_TYPE_ENUM,
offsetof(StdRdOptions, vacuum_index_cleanup)},
- {"vacuum_truncate", RELOPT_TYPE_BOOL,
- offsetof(StdRdOptions, vacuum_truncate), offsetof(StdRdOptions, vacuum_truncate_set)},
+ {"vacuum_truncate", RELOPT_TYPE_TERNARY,
+ offsetof(StdRdOptions, vacuum_truncate)},
{"vacuum_max_eager_freeze_failure_rate", RELOPT_TYPE_REAL,
offsetof(StdRdOptions, vacuum_max_eager_freeze_failure_rate)}
};
@@ -1993,7 +2071,6 @@ build_local_reloptions(local_relopts *relopts, Datum options, bool validate)
elems[i].optname = opt->option->name;
elems[i].opttype = opt->option->type;
elems[i].offset = opt->offset;
- elems[i].isset_offset = 0; /* not supported for local relopts yet */
i++;
}
diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c
index 733ef40ae7..977babff54 100644
--- a/src/backend/commands/vacuum.c
+++ b/src/backend/commands/vacuum.c
@@ -2221,9 +2221,9 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams params,
{
StdRdOptions *opts = (StdRdOptions *) rel->rd_options;
- if (opts && opts->vacuum_truncate_set)
+ if (opts && opts->vacuum_truncate != TERNARY_UNSET)
{
- if (opts->vacuum_truncate)
+ if (opts->vacuum_truncate == TERNARY_TRUE)
params.truncate = VACOPTVALUE_ENABLED;
else
params.truncate = VACOPTVALUE_DISABLED;
diff --git a/src/include/access/reloptions.h b/src/include/access/reloptions.h
index a604a4702c..a436697658 100644
--- a/src/include/access/reloptions.h
+++ b/src/include/access/reloptions.h
@@ -29,6 +29,7 @@
typedef enum relopt_type
{
RELOPT_TYPE_BOOL,
+ RELOPT_TYPE_TERNARY, /* on, off, unset */
RELOPT_TYPE_INT,
RELOPT_TYPE_REAL,
RELOPT_TYPE_ENUM,
@@ -80,6 +81,7 @@ typedef struct relopt_value
union
{
bool bool_val;
+ ternary ternary_val;
int int_val;
double real_val;
int enum_val;
@@ -94,6 +96,12 @@ typedef struct relopt_bool
bool default_val;
} relopt_bool;
+typedef struct relopt_rernary
+{
+ relopt_gen gen;
+ int default_val;
+} relopt_ternary;
+
typedef struct relopt_int
{
relopt_gen gen;
@@ -152,19 +160,6 @@ typedef struct
const char *optname; /* option's name */
relopt_type opttype; /* option's datatype */
int offset; /* offset of field in result struct */
-
- /*
- * isset_offset is an optional offset of a field in the result struct that
- * stores whether the option is explicitly set for the relation or if it
- * just picked up the default value. In most cases, this can be
- * accomplished by giving the reloption a special out-of-range default
- * value (e.g., some integer reloptions use -2), but this isn't always
- * possible. For example, a Boolean reloption cannot be given an
- * out-of-range default, so we need another way to discover the source of
- * its value. This offset is only used if given a value greater than
- * zero.
- */
- int isset_offset;
} relopt_parse_elt;
/* Local reloption definition */
@@ -195,6 +190,8 @@ typedef struct local_relopts
extern relopt_kind add_reloption_kind(void);
extern void add_bool_reloption(bits32 kinds, const char *name, const char *desc,
bool default_val, LOCKMODE lockmode);
+extern void add_ternary_reloption(bits32 kinds, const char *name,
+ const char *desc, int default_val, LOCKMODE lockmode);
extern void add_int_reloption(bits32 kinds, const char *name, const char *desc,
int default_val, int min_val, int max_val,
LOCKMODE lockmode);
@@ -214,6 +211,9 @@ extern void register_reloptions_validator(local_relopts *relopts,
extern void add_local_bool_reloption(local_relopts *relopts, const char *name,
const char *desc, bool default_val,
int offset);
+extern void add_local_ternary_reloption(local_relopts *relopts,
+ const char *name, const char *desc,
+ ternary default_val, int offset);
extern void add_local_int_reloption(local_relopts *relopts, const char *name,
const char *desc, int default_val,
int min_val, int max_val, int offset);
diff --git a/src/include/c.h b/src/include/c.h
index 39022f8a9d..9c59ccccf8 100644
--- a/src/include/c.h
+++ b/src/include/c.h
@@ -488,6 +488,22 @@ typedef void (*pg_funcptr_t) (void);
#include <stdbool.h>
+/*
+ * ternary
+ * Boolean value with an extrea "unset" option
+ *
+ * Ternary data type is used in relation options that can be "true", "false" or
+ * "unset". Since relation options are used deep inside the PostgreSQL code,
+ * this type is declared globally.
+*/
+
+typedef enum ternary
+{
+ TERNARY_FALSE = 0,
+ TERNARY_TRUE = 1,
+ TERNARY_UNSET = -1
+} ternary;
+
/* ----------------------------------------------------------------
* Section 3: standard system types
diff --git a/src/include/utils/rel.h b/src/include/utils/rel.h
index b552359915..7346d618f9 100644
--- a/src/include/utils/rel.h
+++ b/src/include/utils/rel.h
@@ -346,8 +346,7 @@ typedef struct StdRdOptions
bool user_catalog_table; /* use as an additional catalog relation */
int parallel_workers; /* max number of parallel workers */
StdRdOptIndexCleanup vacuum_index_cleanup; /* controls index vacuuming */
- bool vacuum_truncate; /* enables vacuum to truncate a relation */
- bool vacuum_truncate_set; /* whether vacuum_truncate is set */
+ ternary vacuum_truncate; /* enables vacuum to truncate a relation */
/*
* Fraction of pages in a relation that vacuum can eagerly scan and fail
--
2.39.2
--nextPart5129932.5fSG56mABF
Content-Disposition: attachment;
filename="v2-0003-Add-alias-to-be-used-as-unset-state.patch"
Content-Transfer-Encoding: 7Bit
Content-Type: text/x-patch; charset="unicode-2-0-utf-8";
name="v2-0003-Add-alias-to-be-used-as-unset-state.patch"
^ permalink raw reply [nested|flat] 868+ messages in thread
* [PATCH v2a 2/4] Introduce ternary reloptions
@ 2025-09-04 14:41 Nikolay Shaplov <[email protected]>
0 siblings, 0 replies; 868+ messages in thread
From: Nikolay Shaplov @ 2025-09-04 14:41 UTC (permalink / raw)
Introduce ternary reloption as a replacement for current `vacuum_truncate`
implementation. Remove `vacuum_truncate_set` additional flag and using
`TERNARY_UNSET` value instead.
---
src/backend/access/common/reloptions.c | 129 ++++++++++++++++++++-----
src/backend/commands/vacuum.c | 4 +-
src/include/access/reloptions.h | 26 ++---
src/include/c.h | 16 +++
src/include/utils/rel.h | 3 +-
5 files changed, 135 insertions(+), 43 deletions(-)
diff --git a/src/backend/access/common/reloptions.c b/src/backend/access/common/reloptions.c
index 0af3fea68f..f543f11001 100644
--- a/src/backend/access/common/reloptions.c
+++ b/src/backend/access/common/reloptions.c
@@ -40,9 +40,9 @@
*
* To add an option:
*
- * (i) decide on a type (bool, integer, real, enum, string), name, default
- * value, upper and lower bounds (if applicable); for strings, consider a
- * validation routine.
+ * (i) decide on a type (bool, ternary, integer, real, enum, string), name,
+ * default value, upper and lower bounds (if applicable); for strings,
+ * consider a validation routine.
* (ii) add a record below (or use add_<type>_reloption).
* (iii) add it to the appropriate options struct (perhaps StdRdOptions)
* (iv) add it to the appropriate handling routine (perhaps
@@ -147,15 +147,6 @@ static relopt_bool boolRelOpts[] =
},
false
},
- {
- {
- "vacuum_truncate",
- "Enables vacuum to truncate empty pages at the end of this table",
- RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
- ShareUpdateExclusiveLock
- },
- true
- },
{
{
"deduplicate_items",
@@ -170,6 +161,21 @@ static relopt_bool boolRelOpts[] =
{{NULL}}
};
+static relopt_ternary ternaryRelOpts[] =
+{
+ {
+ {
+ "vacuum_truncate",
+ "Enables vacuum to truncate empty pages at the end of this table",
+ RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
+ ShareUpdateExclusiveLock
+ },
+ TERNARY_UNSET
+ },
+ /* list terminator */
+ {{NULL}}
+};
+
static relopt_int intRelOpts[] =
{
{
@@ -600,6 +606,13 @@ initialize_reloptions(void)
boolRelOpts[i].gen.lockmode));
j++;
}
+ for (i = 0; ternaryRelOpts[i].gen.name; i++)
+ {
+ Assert(DoLockModesConflict(ternaryRelOpts[i].gen.lockmode,
+ ternaryRelOpts[i].gen.lockmode));
+ j++;
+ }
+
for (i = 0; intRelOpts[i].gen.name; i++)
{
Assert(DoLockModesConflict(intRelOpts[i].gen.lockmode,
@@ -640,6 +653,14 @@ initialize_reloptions(void)
j++;
}
+ for (i = 0; ternaryRelOpts[i].gen.name; i++)
+ {
+ relOpts[j] = &ternaryRelOpts[i].gen;
+ relOpts[j]->type = RELOPT_TYPE_TERNARY;
+ relOpts[j]->namelen = strlen(relOpts[j]->name);
+ j++;
+ }
+
for (i = 0; intRelOpts[i].gen.name; i++)
{
relOpts[j] = &intRelOpts[i].gen;
@@ -800,6 +821,9 @@ allocate_reloption(bits32 kinds, int type, const char *name, const char *desc,
case RELOPT_TYPE_BOOL:
size = sizeof(relopt_bool);
break;
+ case RELOPT_TYPE_TERNARY:
+ size = sizeof(relopt_ternary);
+ break;
case RELOPT_TYPE_INT:
size = sizeof(relopt_int);
break;
@@ -883,6 +907,54 @@ add_local_bool_reloption(local_relopts *relopts, const char *name,
add_local_reloption(relopts, (relopt_gen *) newoption, offset);
}
+/*
+ * init_ternary_reloption
+ * Allocate and initialize a new ternary reloption
+ */
+static relopt_ternary *
+init_ternary_reloption(bits32 kinds, const char *name, const char *desc,
+ ternary default_val, LOCKMODE lockmode)
+{
+ relopt_ternary *newoption;
+
+ newoption = (relopt_ternary *) allocate_reloption(kinds,
+ RELOPT_TYPE_TERNARY, name, desc, lockmode);
+ newoption->default_val = default_val;
+
+ return newoption;
+}
+
+/*
+ * add_ternary_reloption
+ * Add a new ternary reloption
+ */
+void
+add_ternary_reloption(bits32 kinds, const char *name, const char *desc,
+ ternary default_val, LOCKMODE lockmode)
+{
+ relopt_ternary *newoption = init_ternary_reloption(kinds, name, desc,
+ default_val, lockmode);
+
+ add_reloption((relopt_gen *) newoption);
+}
+
+/*
+ * add_local_ternary_reloption
+ * Add a new ternary local reloption
+ *
+ * 'offset' is offset of ternary-typed field.
+ */
+void
+add_local_ternary_reloption(local_relopts *relopts, const char *name,
+ const char *desc, ternary default_val,
+ int offset)
+{
+ relopt_ternary *newoption = init_ternary_reloption(RELOPT_KIND_LOCAL,
+ name, desc,
+ default_val, 0);
+
+ add_local_reloption(relopts, (relopt_gen *) newoption, offset);
+}
/*
* init_real_reloption
@@ -1617,6 +1689,18 @@ parse_one_reloption(relopt_value *option, char *text_str, int text_len,
option->gen->name, value)));
}
break;
+ case RELOPT_TYPE_TERNARY:
+ {
+ bool b;
+ parsed = parse_bool(value, &b);
+ option->values.ternary_val = b ? TERNARY_TRUE : TERNARY_FALSE;
+ if (validate && !parsed)
+ ereport(ERROR,
+ (errcode(ERRCODE_INVALID_PARAMETER_VALUE),
+ errmsg("invalid value for ternary option \"%s\": %s",
+ option->gen->name, value)));
+ }
+ break;
case RELOPT_TYPE_INT:
{
relopt_int *optint = (relopt_int *) option->gen;
@@ -1780,17 +1864,6 @@ fillRelOptions(void *rdopts, Size basesize,
char *itempos = ((char *) rdopts) + elems[j].offset;
char *string_val;
- /*
- * If isset_offset is provided, store whether the reloption is
- * set there.
- */
- if (elems[j].isset_offset > 0)
- {
- char *setpos = ((char *) rdopts) + elems[j].isset_offset;
-
- *(bool *) setpos = options[i].isset;
- }
-
switch (options[i].gen->type)
{
case RELOPT_TYPE_BOOL:
@@ -1798,6 +1871,11 @@ fillRelOptions(void *rdopts, Size basesize,
options[i].values.bool_val :
((relopt_bool *) options[i].gen)->default_val;
break;
+ case RELOPT_TYPE_TERNARY:
+ *(ternary *) itempos = options[i].isset ?
+ options[i].values.ternary_val :
+ ((relopt_ternary *) options[i].gen)->default_val;
+ break;
case RELOPT_TYPE_INT:
*(int *) itempos = options[i].isset ?
options[i].values.int_val :
@@ -1912,8 +1990,8 @@ default_reloptions(Datum reloptions, bool validate, relopt_kind kind)
offsetof(StdRdOptions, parallel_workers)},
{"vacuum_index_cleanup", RELOPT_TYPE_ENUM,
offsetof(StdRdOptions, vacuum_index_cleanup)},
- {"vacuum_truncate", RELOPT_TYPE_BOOL,
- offsetof(StdRdOptions, vacuum_truncate), offsetof(StdRdOptions, vacuum_truncate_set)},
+ {"vacuum_truncate", RELOPT_TYPE_TERNARY,
+ offsetof(StdRdOptions, vacuum_truncate)},
{"vacuum_max_eager_freeze_failure_rate", RELOPT_TYPE_REAL,
offsetof(StdRdOptions, vacuum_max_eager_freeze_failure_rate)}
};
@@ -1993,7 +2071,6 @@ build_local_reloptions(local_relopts *relopts, Datum options, bool validate)
elems[i].optname = opt->option->name;
elems[i].opttype = opt->option->type;
elems[i].offset = opt->offset;
- elems[i].isset_offset = 0; /* not supported for local relopts yet */
i++;
}
diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c
index 733ef40ae7..977babff54 100644
--- a/src/backend/commands/vacuum.c
+++ b/src/backend/commands/vacuum.c
@@ -2221,9 +2221,9 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams params,
{
StdRdOptions *opts = (StdRdOptions *) rel->rd_options;
- if (opts && opts->vacuum_truncate_set)
+ if (opts && opts->vacuum_truncate != TERNARY_UNSET)
{
- if (opts->vacuum_truncate)
+ if (opts->vacuum_truncate == TERNARY_TRUE)
params.truncate = VACOPTVALUE_ENABLED;
else
params.truncate = VACOPTVALUE_DISABLED;
diff --git a/src/include/access/reloptions.h b/src/include/access/reloptions.h
index a604a4702c..a436697658 100644
--- a/src/include/access/reloptions.h
+++ b/src/include/access/reloptions.h
@@ -29,6 +29,7 @@
typedef enum relopt_type
{
RELOPT_TYPE_BOOL,
+ RELOPT_TYPE_TERNARY, /* on, off, unset */
RELOPT_TYPE_INT,
RELOPT_TYPE_REAL,
RELOPT_TYPE_ENUM,
@@ -80,6 +81,7 @@ typedef struct relopt_value
union
{
bool bool_val;
+ ternary ternary_val;
int int_val;
double real_val;
int enum_val;
@@ -94,6 +96,12 @@ typedef struct relopt_bool
bool default_val;
} relopt_bool;
+typedef struct relopt_rernary
+{
+ relopt_gen gen;
+ int default_val;
+} relopt_ternary;
+
typedef struct relopt_int
{
relopt_gen gen;
@@ -152,19 +160,6 @@ typedef struct
const char *optname; /* option's name */
relopt_type opttype; /* option's datatype */
int offset; /* offset of field in result struct */
-
- /*
- * isset_offset is an optional offset of a field in the result struct that
- * stores whether the option is explicitly set for the relation or if it
- * just picked up the default value. In most cases, this can be
- * accomplished by giving the reloption a special out-of-range default
- * value (e.g., some integer reloptions use -2), but this isn't always
- * possible. For example, a Boolean reloption cannot be given an
- * out-of-range default, so we need another way to discover the source of
- * its value. This offset is only used if given a value greater than
- * zero.
- */
- int isset_offset;
} relopt_parse_elt;
/* Local reloption definition */
@@ -195,6 +190,8 @@ typedef struct local_relopts
extern relopt_kind add_reloption_kind(void);
extern void add_bool_reloption(bits32 kinds, const char *name, const char *desc,
bool default_val, LOCKMODE lockmode);
+extern void add_ternary_reloption(bits32 kinds, const char *name,
+ const char *desc, int default_val, LOCKMODE lockmode);
extern void add_int_reloption(bits32 kinds, const char *name, const char *desc,
int default_val, int min_val, int max_val,
LOCKMODE lockmode);
@@ -214,6 +211,9 @@ extern void register_reloptions_validator(local_relopts *relopts,
extern void add_local_bool_reloption(local_relopts *relopts, const char *name,
const char *desc, bool default_val,
int offset);
+extern void add_local_ternary_reloption(local_relopts *relopts,
+ const char *name, const char *desc,
+ ternary default_val, int offset);
extern void add_local_int_reloption(local_relopts *relopts, const char *name,
const char *desc, int default_val,
int min_val, int max_val, int offset);
diff --git a/src/include/c.h b/src/include/c.h
index 39022f8a9d..9c59ccccf8 100644
--- a/src/include/c.h
+++ b/src/include/c.h
@@ -488,6 +488,22 @@ typedef void (*pg_funcptr_t) (void);
#include <stdbool.h>
+/*
+ * ternary
+ * Boolean value with an extrea "unset" option
+ *
+ * Ternary data type is used in relation options that can be "true", "false" or
+ * "unset". Since relation options are used deep inside the PostgreSQL code,
+ * this type is declared globally.
+*/
+
+typedef enum ternary
+{
+ TERNARY_FALSE = 0,
+ TERNARY_TRUE = 1,
+ TERNARY_UNSET = -1
+} ternary;
+
/* ----------------------------------------------------------------
* Section 3: standard system types
diff --git a/src/include/utils/rel.h b/src/include/utils/rel.h
index b552359915..7346d618f9 100644
--- a/src/include/utils/rel.h
+++ b/src/include/utils/rel.h
@@ -346,8 +346,7 @@ typedef struct StdRdOptions
bool user_catalog_table; /* use as an additional catalog relation */
int parallel_workers; /* max number of parallel workers */
StdRdOptIndexCleanup vacuum_index_cleanup; /* controls index vacuuming */
- bool vacuum_truncate; /* enables vacuum to truncate a relation */
- bool vacuum_truncate_set; /* whether vacuum_truncate is set */
+ ternary vacuum_truncate; /* enables vacuum to truncate a relation */
/*
* Fraction of pages in a relation that vacuum can eagerly scan and fail
--
2.39.2
--nextPart4155600.3ZeAukHxDK
Content-Disposition: attachment;
filename="v2a-0003-Add-alias-to-be-used-as-unset-state.patch"
Content-Transfer-Encoding: 7Bit
Content-Type: text/x-patch; charset="unicode-2-0-utf-8";
name="v2a-0003-Add-alias-to-be-used-as-unset-state.patch"
^ permalink raw reply [nested|flat] 868+ messages in thread
* [PATCH v2 2/4] Introduce ternary reloptions
@ 2025-09-04 14:41 Nikolay Shaplov <[email protected]>
0 siblings, 0 replies; 868+ messages in thread
From: Nikolay Shaplov @ 2025-09-04 14:41 UTC (permalink / raw)
Introduce ternary reloption as a replacement for current `vacuum_truncate`
implementation. Remove `vacuum_truncate_set` additional flag and using
`TERNARY_UNSET` value instead.
---
src/backend/access/common/reloptions.c | 129 ++++++++++++++++++++-----
src/backend/commands/vacuum.c | 4 +-
src/include/access/reloptions.h | 26 ++---
src/include/c.h | 16 +++
src/include/utils/rel.h | 3 +-
5 files changed, 135 insertions(+), 43 deletions(-)
diff --git a/src/backend/access/common/reloptions.c b/src/backend/access/common/reloptions.c
index 0af3fea68f..f543f11001 100644
--- a/src/backend/access/common/reloptions.c
+++ b/src/backend/access/common/reloptions.c
@@ -40,9 +40,9 @@
*
* To add an option:
*
- * (i) decide on a type (bool, integer, real, enum, string), name, default
- * value, upper and lower bounds (if applicable); for strings, consider a
- * validation routine.
+ * (i) decide on a type (bool, ternary, integer, real, enum, string), name,
+ * default value, upper and lower bounds (if applicable); for strings,
+ * consider a validation routine.
* (ii) add a record below (or use add_<type>_reloption).
* (iii) add it to the appropriate options struct (perhaps StdRdOptions)
* (iv) add it to the appropriate handling routine (perhaps
@@ -147,15 +147,6 @@ static relopt_bool boolRelOpts[] =
},
false
},
- {
- {
- "vacuum_truncate",
- "Enables vacuum to truncate empty pages at the end of this table",
- RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
- ShareUpdateExclusiveLock
- },
- true
- },
{
{
"deduplicate_items",
@@ -170,6 +161,21 @@ static relopt_bool boolRelOpts[] =
{{NULL}}
};
+static relopt_ternary ternaryRelOpts[] =
+{
+ {
+ {
+ "vacuum_truncate",
+ "Enables vacuum to truncate empty pages at the end of this table",
+ RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
+ ShareUpdateExclusiveLock
+ },
+ TERNARY_UNSET
+ },
+ /* list terminator */
+ {{NULL}}
+};
+
static relopt_int intRelOpts[] =
{
{
@@ -600,6 +606,13 @@ initialize_reloptions(void)
boolRelOpts[i].gen.lockmode));
j++;
}
+ for (i = 0; ternaryRelOpts[i].gen.name; i++)
+ {
+ Assert(DoLockModesConflict(ternaryRelOpts[i].gen.lockmode,
+ ternaryRelOpts[i].gen.lockmode));
+ j++;
+ }
+
for (i = 0; intRelOpts[i].gen.name; i++)
{
Assert(DoLockModesConflict(intRelOpts[i].gen.lockmode,
@@ -640,6 +653,14 @@ initialize_reloptions(void)
j++;
}
+ for (i = 0; ternaryRelOpts[i].gen.name; i++)
+ {
+ relOpts[j] = &ternaryRelOpts[i].gen;
+ relOpts[j]->type = RELOPT_TYPE_TERNARY;
+ relOpts[j]->namelen = strlen(relOpts[j]->name);
+ j++;
+ }
+
for (i = 0; intRelOpts[i].gen.name; i++)
{
relOpts[j] = &intRelOpts[i].gen;
@@ -800,6 +821,9 @@ allocate_reloption(bits32 kinds, int type, const char *name, const char *desc,
case RELOPT_TYPE_BOOL:
size = sizeof(relopt_bool);
break;
+ case RELOPT_TYPE_TERNARY:
+ size = sizeof(relopt_ternary);
+ break;
case RELOPT_TYPE_INT:
size = sizeof(relopt_int);
break;
@@ -883,6 +907,54 @@ add_local_bool_reloption(local_relopts *relopts, const char *name,
add_local_reloption(relopts, (relopt_gen *) newoption, offset);
}
+/*
+ * init_ternary_reloption
+ * Allocate and initialize a new ternary reloption
+ */
+static relopt_ternary *
+init_ternary_reloption(bits32 kinds, const char *name, const char *desc,
+ ternary default_val, LOCKMODE lockmode)
+{
+ relopt_ternary *newoption;
+
+ newoption = (relopt_ternary *) allocate_reloption(kinds,
+ RELOPT_TYPE_TERNARY, name, desc, lockmode);
+ newoption->default_val = default_val;
+
+ return newoption;
+}
+
+/*
+ * add_ternary_reloption
+ * Add a new ternary reloption
+ */
+void
+add_ternary_reloption(bits32 kinds, const char *name, const char *desc,
+ ternary default_val, LOCKMODE lockmode)
+{
+ relopt_ternary *newoption = init_ternary_reloption(kinds, name, desc,
+ default_val, lockmode);
+
+ add_reloption((relopt_gen *) newoption);
+}
+
+/*
+ * add_local_ternary_reloption
+ * Add a new ternary local reloption
+ *
+ * 'offset' is offset of ternary-typed field.
+ */
+void
+add_local_ternary_reloption(local_relopts *relopts, const char *name,
+ const char *desc, ternary default_val,
+ int offset)
+{
+ relopt_ternary *newoption = init_ternary_reloption(RELOPT_KIND_LOCAL,
+ name, desc,
+ default_val, 0);
+
+ add_local_reloption(relopts, (relopt_gen *) newoption, offset);
+}
/*
* init_real_reloption
@@ -1617,6 +1689,18 @@ parse_one_reloption(relopt_value *option, char *text_str, int text_len,
option->gen->name, value)));
}
break;
+ case RELOPT_TYPE_TERNARY:
+ {
+ bool b;
+ parsed = parse_bool(value, &b);
+ option->values.ternary_val = b ? TERNARY_TRUE : TERNARY_FALSE;
+ if (validate && !parsed)
+ ereport(ERROR,
+ (errcode(ERRCODE_INVALID_PARAMETER_VALUE),
+ errmsg("invalid value for ternary option \"%s\": %s",
+ option->gen->name, value)));
+ }
+ break;
case RELOPT_TYPE_INT:
{
relopt_int *optint = (relopt_int *) option->gen;
@@ -1780,17 +1864,6 @@ fillRelOptions(void *rdopts, Size basesize,
char *itempos = ((char *) rdopts) + elems[j].offset;
char *string_val;
- /*
- * If isset_offset is provided, store whether the reloption is
- * set there.
- */
- if (elems[j].isset_offset > 0)
- {
- char *setpos = ((char *) rdopts) + elems[j].isset_offset;
-
- *(bool *) setpos = options[i].isset;
- }
-
switch (options[i].gen->type)
{
case RELOPT_TYPE_BOOL:
@@ -1798,6 +1871,11 @@ fillRelOptions(void *rdopts, Size basesize,
options[i].values.bool_val :
((relopt_bool *) options[i].gen)->default_val;
break;
+ case RELOPT_TYPE_TERNARY:
+ *(ternary *) itempos = options[i].isset ?
+ options[i].values.ternary_val :
+ ((relopt_ternary *) options[i].gen)->default_val;
+ break;
case RELOPT_TYPE_INT:
*(int *) itempos = options[i].isset ?
options[i].values.int_val :
@@ -1912,8 +1990,8 @@ default_reloptions(Datum reloptions, bool validate, relopt_kind kind)
offsetof(StdRdOptions, parallel_workers)},
{"vacuum_index_cleanup", RELOPT_TYPE_ENUM,
offsetof(StdRdOptions, vacuum_index_cleanup)},
- {"vacuum_truncate", RELOPT_TYPE_BOOL,
- offsetof(StdRdOptions, vacuum_truncate), offsetof(StdRdOptions, vacuum_truncate_set)},
+ {"vacuum_truncate", RELOPT_TYPE_TERNARY,
+ offsetof(StdRdOptions, vacuum_truncate)},
{"vacuum_max_eager_freeze_failure_rate", RELOPT_TYPE_REAL,
offsetof(StdRdOptions, vacuum_max_eager_freeze_failure_rate)}
};
@@ -1993,7 +2071,6 @@ build_local_reloptions(local_relopts *relopts, Datum options, bool validate)
elems[i].optname = opt->option->name;
elems[i].opttype = opt->option->type;
elems[i].offset = opt->offset;
- elems[i].isset_offset = 0; /* not supported for local relopts yet */
i++;
}
diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c
index 733ef40ae7..977babff54 100644
--- a/src/backend/commands/vacuum.c
+++ b/src/backend/commands/vacuum.c
@@ -2221,9 +2221,9 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams params,
{
StdRdOptions *opts = (StdRdOptions *) rel->rd_options;
- if (opts && opts->vacuum_truncate_set)
+ if (opts && opts->vacuum_truncate != TERNARY_UNSET)
{
- if (opts->vacuum_truncate)
+ if (opts->vacuum_truncate == TERNARY_TRUE)
params.truncate = VACOPTVALUE_ENABLED;
else
params.truncate = VACOPTVALUE_DISABLED;
diff --git a/src/include/access/reloptions.h b/src/include/access/reloptions.h
index a604a4702c..a436697658 100644
--- a/src/include/access/reloptions.h
+++ b/src/include/access/reloptions.h
@@ -29,6 +29,7 @@
typedef enum relopt_type
{
RELOPT_TYPE_BOOL,
+ RELOPT_TYPE_TERNARY, /* on, off, unset */
RELOPT_TYPE_INT,
RELOPT_TYPE_REAL,
RELOPT_TYPE_ENUM,
@@ -80,6 +81,7 @@ typedef struct relopt_value
union
{
bool bool_val;
+ ternary ternary_val;
int int_val;
double real_val;
int enum_val;
@@ -94,6 +96,12 @@ typedef struct relopt_bool
bool default_val;
} relopt_bool;
+typedef struct relopt_rernary
+{
+ relopt_gen gen;
+ int default_val;
+} relopt_ternary;
+
typedef struct relopt_int
{
relopt_gen gen;
@@ -152,19 +160,6 @@ typedef struct
const char *optname; /* option's name */
relopt_type opttype; /* option's datatype */
int offset; /* offset of field in result struct */
-
- /*
- * isset_offset is an optional offset of a field in the result struct that
- * stores whether the option is explicitly set for the relation or if it
- * just picked up the default value. In most cases, this can be
- * accomplished by giving the reloption a special out-of-range default
- * value (e.g., some integer reloptions use -2), but this isn't always
- * possible. For example, a Boolean reloption cannot be given an
- * out-of-range default, so we need another way to discover the source of
- * its value. This offset is only used if given a value greater than
- * zero.
- */
- int isset_offset;
} relopt_parse_elt;
/* Local reloption definition */
@@ -195,6 +190,8 @@ typedef struct local_relopts
extern relopt_kind add_reloption_kind(void);
extern void add_bool_reloption(bits32 kinds, const char *name, const char *desc,
bool default_val, LOCKMODE lockmode);
+extern void add_ternary_reloption(bits32 kinds, const char *name,
+ const char *desc, int default_val, LOCKMODE lockmode);
extern void add_int_reloption(bits32 kinds, const char *name, const char *desc,
int default_val, int min_val, int max_val,
LOCKMODE lockmode);
@@ -214,6 +211,9 @@ extern void register_reloptions_validator(local_relopts *relopts,
extern void add_local_bool_reloption(local_relopts *relopts, const char *name,
const char *desc, bool default_val,
int offset);
+extern void add_local_ternary_reloption(local_relopts *relopts,
+ const char *name, const char *desc,
+ ternary default_val, int offset);
extern void add_local_int_reloption(local_relopts *relopts, const char *name,
const char *desc, int default_val,
int min_val, int max_val, int offset);
diff --git a/src/include/c.h b/src/include/c.h
index 39022f8a9d..9c59ccccf8 100644
--- a/src/include/c.h
+++ b/src/include/c.h
@@ -488,6 +488,22 @@ typedef void (*pg_funcptr_t) (void);
#include <stdbool.h>
+/*
+ * ternary
+ * Boolean value with an extrea "unset" option
+ *
+ * Ternary data type is used in relation options that can be "true", "false" or
+ * "unset". Since relation options are used deep inside the PostgreSQL code,
+ * this type is declared globally.
+*/
+
+typedef enum ternary
+{
+ TERNARY_FALSE = 0,
+ TERNARY_TRUE = 1,
+ TERNARY_UNSET = -1
+} ternary;
+
/* ----------------------------------------------------------------
* Section 3: standard system types
diff --git a/src/include/utils/rel.h b/src/include/utils/rel.h
index b552359915..7346d618f9 100644
--- a/src/include/utils/rel.h
+++ b/src/include/utils/rel.h
@@ -346,8 +346,7 @@ typedef struct StdRdOptions
bool user_catalog_table; /* use as an additional catalog relation */
int parallel_workers; /* max number of parallel workers */
StdRdOptIndexCleanup vacuum_index_cleanup; /* controls index vacuuming */
- bool vacuum_truncate; /* enables vacuum to truncate a relation */
- bool vacuum_truncate_set; /* whether vacuum_truncate is set */
+ ternary vacuum_truncate; /* enables vacuum to truncate a relation */
/*
* Fraction of pages in a relation that vacuum can eagerly scan and fail
--
2.39.2
--nextPart5129932.5fSG56mABF
Content-Disposition: attachment;
filename="v2-0003-Add-alias-to-be-used-as-unset-state.patch"
Content-Transfer-Encoding: 7Bit
Content-Type: text/x-patch; charset="unicode-2-0-utf-8";
name="v2-0003-Add-alias-to-be-used-as-unset-state.patch"
^ permalink raw reply [nested|flat] 868+ messages in thread
* [PATCH v2 2/4] Introduce ternary reloptions
@ 2025-09-04 14:41 Nikolay Shaplov <[email protected]>
0 siblings, 0 replies; 868+ messages in thread
From: Nikolay Shaplov @ 2025-09-04 14:41 UTC (permalink / raw)
Introduce ternary reloption as a replacement for current `vacuum_truncate`
implementation. Remove `vacuum_truncate_set` additional flag and using
`TERNARY_UNSET` value instead.
---
src/backend/access/common/reloptions.c | 129 ++++++++++++++++++++-----
src/backend/commands/vacuum.c | 4 +-
src/include/access/reloptions.h | 26 ++---
src/include/c.h | 16 +++
src/include/utils/rel.h | 3 +-
5 files changed, 135 insertions(+), 43 deletions(-)
diff --git a/src/backend/access/common/reloptions.c b/src/backend/access/common/reloptions.c
index 0af3fea68f..f543f11001 100644
--- a/src/backend/access/common/reloptions.c
+++ b/src/backend/access/common/reloptions.c
@@ -40,9 +40,9 @@
*
* To add an option:
*
- * (i) decide on a type (bool, integer, real, enum, string), name, default
- * value, upper and lower bounds (if applicable); for strings, consider a
- * validation routine.
+ * (i) decide on a type (bool, ternary, integer, real, enum, string), name,
+ * default value, upper and lower bounds (if applicable); for strings,
+ * consider a validation routine.
* (ii) add a record below (or use add_<type>_reloption).
* (iii) add it to the appropriate options struct (perhaps StdRdOptions)
* (iv) add it to the appropriate handling routine (perhaps
@@ -147,15 +147,6 @@ static relopt_bool boolRelOpts[] =
},
false
},
- {
- {
- "vacuum_truncate",
- "Enables vacuum to truncate empty pages at the end of this table",
- RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
- ShareUpdateExclusiveLock
- },
- true
- },
{
{
"deduplicate_items",
@@ -170,6 +161,21 @@ static relopt_bool boolRelOpts[] =
{{NULL}}
};
+static relopt_ternary ternaryRelOpts[] =
+{
+ {
+ {
+ "vacuum_truncate",
+ "Enables vacuum to truncate empty pages at the end of this table",
+ RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
+ ShareUpdateExclusiveLock
+ },
+ TERNARY_UNSET
+ },
+ /* list terminator */
+ {{NULL}}
+};
+
static relopt_int intRelOpts[] =
{
{
@@ -600,6 +606,13 @@ initialize_reloptions(void)
boolRelOpts[i].gen.lockmode));
j++;
}
+ for (i = 0; ternaryRelOpts[i].gen.name; i++)
+ {
+ Assert(DoLockModesConflict(ternaryRelOpts[i].gen.lockmode,
+ ternaryRelOpts[i].gen.lockmode));
+ j++;
+ }
+
for (i = 0; intRelOpts[i].gen.name; i++)
{
Assert(DoLockModesConflict(intRelOpts[i].gen.lockmode,
@@ -640,6 +653,14 @@ initialize_reloptions(void)
j++;
}
+ for (i = 0; ternaryRelOpts[i].gen.name; i++)
+ {
+ relOpts[j] = &ternaryRelOpts[i].gen;
+ relOpts[j]->type = RELOPT_TYPE_TERNARY;
+ relOpts[j]->namelen = strlen(relOpts[j]->name);
+ j++;
+ }
+
for (i = 0; intRelOpts[i].gen.name; i++)
{
relOpts[j] = &intRelOpts[i].gen;
@@ -800,6 +821,9 @@ allocate_reloption(bits32 kinds, int type, const char *name, const char *desc,
case RELOPT_TYPE_BOOL:
size = sizeof(relopt_bool);
break;
+ case RELOPT_TYPE_TERNARY:
+ size = sizeof(relopt_ternary);
+ break;
case RELOPT_TYPE_INT:
size = sizeof(relopt_int);
break;
@@ -883,6 +907,54 @@ add_local_bool_reloption(local_relopts *relopts, const char *name,
add_local_reloption(relopts, (relopt_gen *) newoption, offset);
}
+/*
+ * init_ternary_reloption
+ * Allocate and initialize a new ternary reloption
+ */
+static relopt_ternary *
+init_ternary_reloption(bits32 kinds, const char *name, const char *desc,
+ ternary default_val, LOCKMODE lockmode)
+{
+ relopt_ternary *newoption;
+
+ newoption = (relopt_ternary *) allocate_reloption(kinds,
+ RELOPT_TYPE_TERNARY, name, desc, lockmode);
+ newoption->default_val = default_val;
+
+ return newoption;
+}
+
+/*
+ * add_ternary_reloption
+ * Add a new ternary reloption
+ */
+void
+add_ternary_reloption(bits32 kinds, const char *name, const char *desc,
+ ternary default_val, LOCKMODE lockmode)
+{
+ relopt_ternary *newoption = init_ternary_reloption(kinds, name, desc,
+ default_val, lockmode);
+
+ add_reloption((relopt_gen *) newoption);
+}
+
+/*
+ * add_local_ternary_reloption
+ * Add a new ternary local reloption
+ *
+ * 'offset' is offset of ternary-typed field.
+ */
+void
+add_local_ternary_reloption(local_relopts *relopts, const char *name,
+ const char *desc, ternary default_val,
+ int offset)
+{
+ relopt_ternary *newoption = init_ternary_reloption(RELOPT_KIND_LOCAL,
+ name, desc,
+ default_val, 0);
+
+ add_local_reloption(relopts, (relopt_gen *) newoption, offset);
+}
/*
* init_real_reloption
@@ -1617,6 +1689,18 @@ parse_one_reloption(relopt_value *option, char *text_str, int text_len,
option->gen->name, value)));
}
break;
+ case RELOPT_TYPE_TERNARY:
+ {
+ bool b;
+ parsed = parse_bool(value, &b);
+ option->values.ternary_val = b ? TERNARY_TRUE : TERNARY_FALSE;
+ if (validate && !parsed)
+ ereport(ERROR,
+ (errcode(ERRCODE_INVALID_PARAMETER_VALUE),
+ errmsg("invalid value for ternary option \"%s\": %s",
+ option->gen->name, value)));
+ }
+ break;
case RELOPT_TYPE_INT:
{
relopt_int *optint = (relopt_int *) option->gen;
@@ -1780,17 +1864,6 @@ fillRelOptions(void *rdopts, Size basesize,
char *itempos = ((char *) rdopts) + elems[j].offset;
char *string_val;
- /*
- * If isset_offset is provided, store whether the reloption is
- * set there.
- */
- if (elems[j].isset_offset > 0)
- {
- char *setpos = ((char *) rdopts) + elems[j].isset_offset;
-
- *(bool *) setpos = options[i].isset;
- }
-
switch (options[i].gen->type)
{
case RELOPT_TYPE_BOOL:
@@ -1798,6 +1871,11 @@ fillRelOptions(void *rdopts, Size basesize,
options[i].values.bool_val :
((relopt_bool *) options[i].gen)->default_val;
break;
+ case RELOPT_TYPE_TERNARY:
+ *(ternary *) itempos = options[i].isset ?
+ options[i].values.ternary_val :
+ ((relopt_ternary *) options[i].gen)->default_val;
+ break;
case RELOPT_TYPE_INT:
*(int *) itempos = options[i].isset ?
options[i].values.int_val :
@@ -1912,8 +1990,8 @@ default_reloptions(Datum reloptions, bool validate, relopt_kind kind)
offsetof(StdRdOptions, parallel_workers)},
{"vacuum_index_cleanup", RELOPT_TYPE_ENUM,
offsetof(StdRdOptions, vacuum_index_cleanup)},
- {"vacuum_truncate", RELOPT_TYPE_BOOL,
- offsetof(StdRdOptions, vacuum_truncate), offsetof(StdRdOptions, vacuum_truncate_set)},
+ {"vacuum_truncate", RELOPT_TYPE_TERNARY,
+ offsetof(StdRdOptions, vacuum_truncate)},
{"vacuum_max_eager_freeze_failure_rate", RELOPT_TYPE_REAL,
offsetof(StdRdOptions, vacuum_max_eager_freeze_failure_rate)}
};
@@ -1993,7 +2071,6 @@ build_local_reloptions(local_relopts *relopts, Datum options, bool validate)
elems[i].optname = opt->option->name;
elems[i].opttype = opt->option->type;
elems[i].offset = opt->offset;
- elems[i].isset_offset = 0; /* not supported for local relopts yet */
i++;
}
diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c
index 733ef40ae7..977babff54 100644
--- a/src/backend/commands/vacuum.c
+++ b/src/backend/commands/vacuum.c
@@ -2221,9 +2221,9 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams params,
{
StdRdOptions *opts = (StdRdOptions *) rel->rd_options;
- if (opts && opts->vacuum_truncate_set)
+ if (opts && opts->vacuum_truncate != TERNARY_UNSET)
{
- if (opts->vacuum_truncate)
+ if (opts->vacuum_truncate == TERNARY_TRUE)
params.truncate = VACOPTVALUE_ENABLED;
else
params.truncate = VACOPTVALUE_DISABLED;
diff --git a/src/include/access/reloptions.h b/src/include/access/reloptions.h
index a604a4702c..a436697658 100644
--- a/src/include/access/reloptions.h
+++ b/src/include/access/reloptions.h
@@ -29,6 +29,7 @@
typedef enum relopt_type
{
RELOPT_TYPE_BOOL,
+ RELOPT_TYPE_TERNARY, /* on, off, unset */
RELOPT_TYPE_INT,
RELOPT_TYPE_REAL,
RELOPT_TYPE_ENUM,
@@ -80,6 +81,7 @@ typedef struct relopt_value
union
{
bool bool_val;
+ ternary ternary_val;
int int_val;
double real_val;
int enum_val;
@@ -94,6 +96,12 @@ typedef struct relopt_bool
bool default_val;
} relopt_bool;
+typedef struct relopt_rernary
+{
+ relopt_gen gen;
+ int default_val;
+} relopt_ternary;
+
typedef struct relopt_int
{
relopt_gen gen;
@@ -152,19 +160,6 @@ typedef struct
const char *optname; /* option's name */
relopt_type opttype; /* option's datatype */
int offset; /* offset of field in result struct */
-
- /*
- * isset_offset is an optional offset of a field in the result struct that
- * stores whether the option is explicitly set for the relation or if it
- * just picked up the default value. In most cases, this can be
- * accomplished by giving the reloption a special out-of-range default
- * value (e.g., some integer reloptions use -2), but this isn't always
- * possible. For example, a Boolean reloption cannot be given an
- * out-of-range default, so we need another way to discover the source of
- * its value. This offset is only used if given a value greater than
- * zero.
- */
- int isset_offset;
} relopt_parse_elt;
/* Local reloption definition */
@@ -195,6 +190,8 @@ typedef struct local_relopts
extern relopt_kind add_reloption_kind(void);
extern void add_bool_reloption(bits32 kinds, const char *name, const char *desc,
bool default_val, LOCKMODE lockmode);
+extern void add_ternary_reloption(bits32 kinds, const char *name,
+ const char *desc, int default_val, LOCKMODE lockmode);
extern void add_int_reloption(bits32 kinds, const char *name, const char *desc,
int default_val, int min_val, int max_val,
LOCKMODE lockmode);
@@ -214,6 +211,9 @@ extern void register_reloptions_validator(local_relopts *relopts,
extern void add_local_bool_reloption(local_relopts *relopts, const char *name,
const char *desc, bool default_val,
int offset);
+extern void add_local_ternary_reloption(local_relopts *relopts,
+ const char *name, const char *desc,
+ ternary default_val, int offset);
extern void add_local_int_reloption(local_relopts *relopts, const char *name,
const char *desc, int default_val,
int min_val, int max_val, int offset);
diff --git a/src/include/c.h b/src/include/c.h
index 39022f8a9d..9c59ccccf8 100644
--- a/src/include/c.h
+++ b/src/include/c.h
@@ -488,6 +488,22 @@ typedef void (*pg_funcptr_t) (void);
#include <stdbool.h>
+/*
+ * ternary
+ * Boolean value with an extrea "unset" option
+ *
+ * Ternary data type is used in relation options that can be "true", "false" or
+ * "unset". Since relation options are used deep inside the PostgreSQL code,
+ * this type is declared globally.
+*/
+
+typedef enum ternary
+{
+ TERNARY_FALSE = 0,
+ TERNARY_TRUE = 1,
+ TERNARY_UNSET = -1
+} ternary;
+
/* ----------------------------------------------------------------
* Section 3: standard system types
diff --git a/src/include/utils/rel.h b/src/include/utils/rel.h
index b552359915..7346d618f9 100644
--- a/src/include/utils/rel.h
+++ b/src/include/utils/rel.h
@@ -346,8 +346,7 @@ typedef struct StdRdOptions
bool user_catalog_table; /* use as an additional catalog relation */
int parallel_workers; /* max number of parallel workers */
StdRdOptIndexCleanup vacuum_index_cleanup; /* controls index vacuuming */
- bool vacuum_truncate; /* enables vacuum to truncate a relation */
- bool vacuum_truncate_set; /* whether vacuum_truncate is set */
+ ternary vacuum_truncate; /* enables vacuum to truncate a relation */
/*
* Fraction of pages in a relation that vacuum can eagerly scan and fail
--
2.39.2
--nextPart5129932.5fSG56mABF
Content-Disposition: attachment;
filename="v2-0003-Add-alias-to-be-used-as-unset-state.patch"
Content-Transfer-Encoding: 7Bit
Content-Type: text/x-patch; charset="unicode-2-0-utf-8";
name="v2-0003-Add-alias-to-be-used-as-unset-state.patch"
^ permalink raw reply [nested|flat] 868+ messages in thread
* [PATCH v2a 2/4] Introduce ternary reloptions
@ 2025-09-04 14:41 Nikolay Shaplov <[email protected]>
0 siblings, 0 replies; 868+ messages in thread
From: Nikolay Shaplov @ 2025-09-04 14:41 UTC (permalink / raw)
Introduce ternary reloption as a replacement for current `vacuum_truncate`
implementation. Remove `vacuum_truncate_set` additional flag and using
`TERNARY_UNSET` value instead.
---
src/backend/access/common/reloptions.c | 129 ++++++++++++++++++++-----
src/backend/commands/vacuum.c | 4 +-
src/include/access/reloptions.h | 26 ++---
src/include/c.h | 16 +++
src/include/utils/rel.h | 3 +-
5 files changed, 135 insertions(+), 43 deletions(-)
diff --git a/src/backend/access/common/reloptions.c b/src/backend/access/common/reloptions.c
index 0af3fea68f..f543f11001 100644
--- a/src/backend/access/common/reloptions.c
+++ b/src/backend/access/common/reloptions.c
@@ -40,9 +40,9 @@
*
* To add an option:
*
- * (i) decide on a type (bool, integer, real, enum, string), name, default
- * value, upper and lower bounds (if applicable); for strings, consider a
- * validation routine.
+ * (i) decide on a type (bool, ternary, integer, real, enum, string), name,
+ * default value, upper and lower bounds (if applicable); for strings,
+ * consider a validation routine.
* (ii) add a record below (or use add_<type>_reloption).
* (iii) add it to the appropriate options struct (perhaps StdRdOptions)
* (iv) add it to the appropriate handling routine (perhaps
@@ -147,15 +147,6 @@ static relopt_bool boolRelOpts[] =
},
false
},
- {
- {
- "vacuum_truncate",
- "Enables vacuum to truncate empty pages at the end of this table",
- RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
- ShareUpdateExclusiveLock
- },
- true
- },
{
{
"deduplicate_items",
@@ -170,6 +161,21 @@ static relopt_bool boolRelOpts[] =
{{NULL}}
};
+static relopt_ternary ternaryRelOpts[] =
+{
+ {
+ {
+ "vacuum_truncate",
+ "Enables vacuum to truncate empty pages at the end of this table",
+ RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
+ ShareUpdateExclusiveLock
+ },
+ TERNARY_UNSET
+ },
+ /* list terminator */
+ {{NULL}}
+};
+
static relopt_int intRelOpts[] =
{
{
@@ -600,6 +606,13 @@ initialize_reloptions(void)
boolRelOpts[i].gen.lockmode));
j++;
}
+ for (i = 0; ternaryRelOpts[i].gen.name; i++)
+ {
+ Assert(DoLockModesConflict(ternaryRelOpts[i].gen.lockmode,
+ ternaryRelOpts[i].gen.lockmode));
+ j++;
+ }
+
for (i = 0; intRelOpts[i].gen.name; i++)
{
Assert(DoLockModesConflict(intRelOpts[i].gen.lockmode,
@@ -640,6 +653,14 @@ initialize_reloptions(void)
j++;
}
+ for (i = 0; ternaryRelOpts[i].gen.name; i++)
+ {
+ relOpts[j] = &ternaryRelOpts[i].gen;
+ relOpts[j]->type = RELOPT_TYPE_TERNARY;
+ relOpts[j]->namelen = strlen(relOpts[j]->name);
+ j++;
+ }
+
for (i = 0; intRelOpts[i].gen.name; i++)
{
relOpts[j] = &intRelOpts[i].gen;
@@ -800,6 +821,9 @@ allocate_reloption(bits32 kinds, int type, const char *name, const char *desc,
case RELOPT_TYPE_BOOL:
size = sizeof(relopt_bool);
break;
+ case RELOPT_TYPE_TERNARY:
+ size = sizeof(relopt_ternary);
+ break;
case RELOPT_TYPE_INT:
size = sizeof(relopt_int);
break;
@@ -883,6 +907,54 @@ add_local_bool_reloption(local_relopts *relopts, const char *name,
add_local_reloption(relopts, (relopt_gen *) newoption, offset);
}
+/*
+ * init_ternary_reloption
+ * Allocate and initialize a new ternary reloption
+ */
+static relopt_ternary *
+init_ternary_reloption(bits32 kinds, const char *name, const char *desc,
+ ternary default_val, LOCKMODE lockmode)
+{
+ relopt_ternary *newoption;
+
+ newoption = (relopt_ternary *) allocate_reloption(kinds,
+ RELOPT_TYPE_TERNARY, name, desc, lockmode);
+ newoption->default_val = default_val;
+
+ return newoption;
+}
+
+/*
+ * add_ternary_reloption
+ * Add a new ternary reloption
+ */
+void
+add_ternary_reloption(bits32 kinds, const char *name, const char *desc,
+ ternary default_val, LOCKMODE lockmode)
+{
+ relopt_ternary *newoption = init_ternary_reloption(kinds, name, desc,
+ default_val, lockmode);
+
+ add_reloption((relopt_gen *) newoption);
+}
+
+/*
+ * add_local_ternary_reloption
+ * Add a new ternary local reloption
+ *
+ * 'offset' is offset of ternary-typed field.
+ */
+void
+add_local_ternary_reloption(local_relopts *relopts, const char *name,
+ const char *desc, ternary default_val,
+ int offset)
+{
+ relopt_ternary *newoption = init_ternary_reloption(RELOPT_KIND_LOCAL,
+ name, desc,
+ default_val, 0);
+
+ add_local_reloption(relopts, (relopt_gen *) newoption, offset);
+}
/*
* init_real_reloption
@@ -1617,6 +1689,18 @@ parse_one_reloption(relopt_value *option, char *text_str, int text_len,
option->gen->name, value)));
}
break;
+ case RELOPT_TYPE_TERNARY:
+ {
+ bool b;
+ parsed = parse_bool(value, &b);
+ option->values.ternary_val = b ? TERNARY_TRUE : TERNARY_FALSE;
+ if (validate && !parsed)
+ ereport(ERROR,
+ (errcode(ERRCODE_INVALID_PARAMETER_VALUE),
+ errmsg("invalid value for ternary option \"%s\": %s",
+ option->gen->name, value)));
+ }
+ break;
case RELOPT_TYPE_INT:
{
relopt_int *optint = (relopt_int *) option->gen;
@@ -1780,17 +1864,6 @@ fillRelOptions(void *rdopts, Size basesize,
char *itempos = ((char *) rdopts) + elems[j].offset;
char *string_val;
- /*
- * If isset_offset is provided, store whether the reloption is
- * set there.
- */
- if (elems[j].isset_offset > 0)
- {
- char *setpos = ((char *) rdopts) + elems[j].isset_offset;
-
- *(bool *) setpos = options[i].isset;
- }
-
switch (options[i].gen->type)
{
case RELOPT_TYPE_BOOL:
@@ -1798,6 +1871,11 @@ fillRelOptions(void *rdopts, Size basesize,
options[i].values.bool_val :
((relopt_bool *) options[i].gen)->default_val;
break;
+ case RELOPT_TYPE_TERNARY:
+ *(ternary *) itempos = options[i].isset ?
+ options[i].values.ternary_val :
+ ((relopt_ternary *) options[i].gen)->default_val;
+ break;
case RELOPT_TYPE_INT:
*(int *) itempos = options[i].isset ?
options[i].values.int_val :
@@ -1912,8 +1990,8 @@ default_reloptions(Datum reloptions, bool validate, relopt_kind kind)
offsetof(StdRdOptions, parallel_workers)},
{"vacuum_index_cleanup", RELOPT_TYPE_ENUM,
offsetof(StdRdOptions, vacuum_index_cleanup)},
- {"vacuum_truncate", RELOPT_TYPE_BOOL,
- offsetof(StdRdOptions, vacuum_truncate), offsetof(StdRdOptions, vacuum_truncate_set)},
+ {"vacuum_truncate", RELOPT_TYPE_TERNARY,
+ offsetof(StdRdOptions, vacuum_truncate)},
{"vacuum_max_eager_freeze_failure_rate", RELOPT_TYPE_REAL,
offsetof(StdRdOptions, vacuum_max_eager_freeze_failure_rate)}
};
@@ -1993,7 +2071,6 @@ build_local_reloptions(local_relopts *relopts, Datum options, bool validate)
elems[i].optname = opt->option->name;
elems[i].opttype = opt->option->type;
elems[i].offset = opt->offset;
- elems[i].isset_offset = 0; /* not supported for local relopts yet */
i++;
}
diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c
index 733ef40ae7..977babff54 100644
--- a/src/backend/commands/vacuum.c
+++ b/src/backend/commands/vacuum.c
@@ -2221,9 +2221,9 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams params,
{
StdRdOptions *opts = (StdRdOptions *) rel->rd_options;
- if (opts && opts->vacuum_truncate_set)
+ if (opts && opts->vacuum_truncate != TERNARY_UNSET)
{
- if (opts->vacuum_truncate)
+ if (opts->vacuum_truncate == TERNARY_TRUE)
params.truncate = VACOPTVALUE_ENABLED;
else
params.truncate = VACOPTVALUE_DISABLED;
diff --git a/src/include/access/reloptions.h b/src/include/access/reloptions.h
index a604a4702c..a436697658 100644
--- a/src/include/access/reloptions.h
+++ b/src/include/access/reloptions.h
@@ -29,6 +29,7 @@
typedef enum relopt_type
{
RELOPT_TYPE_BOOL,
+ RELOPT_TYPE_TERNARY, /* on, off, unset */
RELOPT_TYPE_INT,
RELOPT_TYPE_REAL,
RELOPT_TYPE_ENUM,
@@ -80,6 +81,7 @@ typedef struct relopt_value
union
{
bool bool_val;
+ ternary ternary_val;
int int_val;
double real_val;
int enum_val;
@@ -94,6 +96,12 @@ typedef struct relopt_bool
bool default_val;
} relopt_bool;
+typedef struct relopt_rernary
+{
+ relopt_gen gen;
+ int default_val;
+} relopt_ternary;
+
typedef struct relopt_int
{
relopt_gen gen;
@@ -152,19 +160,6 @@ typedef struct
const char *optname; /* option's name */
relopt_type opttype; /* option's datatype */
int offset; /* offset of field in result struct */
-
- /*
- * isset_offset is an optional offset of a field in the result struct that
- * stores whether the option is explicitly set for the relation or if it
- * just picked up the default value. In most cases, this can be
- * accomplished by giving the reloption a special out-of-range default
- * value (e.g., some integer reloptions use -2), but this isn't always
- * possible. For example, a Boolean reloption cannot be given an
- * out-of-range default, so we need another way to discover the source of
- * its value. This offset is only used if given a value greater than
- * zero.
- */
- int isset_offset;
} relopt_parse_elt;
/* Local reloption definition */
@@ -195,6 +190,8 @@ typedef struct local_relopts
extern relopt_kind add_reloption_kind(void);
extern void add_bool_reloption(bits32 kinds, const char *name, const char *desc,
bool default_val, LOCKMODE lockmode);
+extern void add_ternary_reloption(bits32 kinds, const char *name,
+ const char *desc, int default_val, LOCKMODE lockmode);
extern void add_int_reloption(bits32 kinds, const char *name, const char *desc,
int default_val, int min_val, int max_val,
LOCKMODE lockmode);
@@ -214,6 +211,9 @@ extern void register_reloptions_validator(local_relopts *relopts,
extern void add_local_bool_reloption(local_relopts *relopts, const char *name,
const char *desc, bool default_val,
int offset);
+extern void add_local_ternary_reloption(local_relopts *relopts,
+ const char *name, const char *desc,
+ ternary default_val, int offset);
extern void add_local_int_reloption(local_relopts *relopts, const char *name,
const char *desc, int default_val,
int min_val, int max_val, int offset);
diff --git a/src/include/c.h b/src/include/c.h
index 39022f8a9d..9c59ccccf8 100644
--- a/src/include/c.h
+++ b/src/include/c.h
@@ -488,6 +488,22 @@ typedef void (*pg_funcptr_t) (void);
#include <stdbool.h>
+/*
+ * ternary
+ * Boolean value with an extrea "unset" option
+ *
+ * Ternary data type is used in relation options that can be "true", "false" or
+ * "unset". Since relation options are used deep inside the PostgreSQL code,
+ * this type is declared globally.
+*/
+
+typedef enum ternary
+{
+ TERNARY_FALSE = 0,
+ TERNARY_TRUE = 1,
+ TERNARY_UNSET = -1
+} ternary;
+
/* ----------------------------------------------------------------
* Section 3: standard system types
diff --git a/src/include/utils/rel.h b/src/include/utils/rel.h
index b552359915..7346d618f9 100644
--- a/src/include/utils/rel.h
+++ b/src/include/utils/rel.h
@@ -346,8 +346,7 @@ typedef struct StdRdOptions
bool user_catalog_table; /* use as an additional catalog relation */
int parallel_workers; /* max number of parallel workers */
StdRdOptIndexCleanup vacuum_index_cleanup; /* controls index vacuuming */
- bool vacuum_truncate; /* enables vacuum to truncate a relation */
- bool vacuum_truncate_set; /* whether vacuum_truncate is set */
+ ternary vacuum_truncate; /* enables vacuum to truncate a relation */
/*
* Fraction of pages in a relation that vacuum can eagerly scan and fail
--
2.39.2
--nextPart4155600.3ZeAukHxDK
Content-Disposition: attachment;
filename="v2a-0003-Add-alias-to-be-used-as-unset-state.patch"
Content-Transfer-Encoding: 7Bit
Content-Type: text/x-patch; charset="unicode-2-0-utf-8";
name="v2a-0003-Add-alias-to-be-used-as-unset-state.patch"
^ permalink raw reply [nested|flat] 868+ messages in thread
* [PATCH v2a 2/4] Introduce ternary reloptions
@ 2025-09-04 14:41 Nikolay Shaplov <[email protected]>
0 siblings, 0 replies; 868+ messages in thread
From: Nikolay Shaplov @ 2025-09-04 14:41 UTC (permalink / raw)
Introduce ternary reloption as a replacement for current `vacuum_truncate`
implementation. Remove `vacuum_truncate_set` additional flag and using
`TERNARY_UNSET` value instead.
---
src/backend/access/common/reloptions.c | 129 ++++++++++++++++++++-----
src/backend/commands/vacuum.c | 4 +-
src/include/access/reloptions.h | 26 ++---
src/include/c.h | 16 +++
src/include/utils/rel.h | 3 +-
5 files changed, 135 insertions(+), 43 deletions(-)
diff --git a/src/backend/access/common/reloptions.c b/src/backend/access/common/reloptions.c
index 0af3fea68f..f543f11001 100644
--- a/src/backend/access/common/reloptions.c
+++ b/src/backend/access/common/reloptions.c
@@ -40,9 +40,9 @@
*
* To add an option:
*
- * (i) decide on a type (bool, integer, real, enum, string), name, default
- * value, upper and lower bounds (if applicable); for strings, consider a
- * validation routine.
+ * (i) decide on a type (bool, ternary, integer, real, enum, string), name,
+ * default value, upper and lower bounds (if applicable); for strings,
+ * consider a validation routine.
* (ii) add a record below (or use add_<type>_reloption).
* (iii) add it to the appropriate options struct (perhaps StdRdOptions)
* (iv) add it to the appropriate handling routine (perhaps
@@ -147,15 +147,6 @@ static relopt_bool boolRelOpts[] =
},
false
},
- {
- {
- "vacuum_truncate",
- "Enables vacuum to truncate empty pages at the end of this table",
- RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
- ShareUpdateExclusiveLock
- },
- true
- },
{
{
"deduplicate_items",
@@ -170,6 +161,21 @@ static relopt_bool boolRelOpts[] =
{{NULL}}
};
+static relopt_ternary ternaryRelOpts[] =
+{
+ {
+ {
+ "vacuum_truncate",
+ "Enables vacuum to truncate empty pages at the end of this table",
+ RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
+ ShareUpdateExclusiveLock
+ },
+ TERNARY_UNSET
+ },
+ /* list terminator */
+ {{NULL}}
+};
+
static relopt_int intRelOpts[] =
{
{
@@ -600,6 +606,13 @@ initialize_reloptions(void)
boolRelOpts[i].gen.lockmode));
j++;
}
+ for (i = 0; ternaryRelOpts[i].gen.name; i++)
+ {
+ Assert(DoLockModesConflict(ternaryRelOpts[i].gen.lockmode,
+ ternaryRelOpts[i].gen.lockmode));
+ j++;
+ }
+
for (i = 0; intRelOpts[i].gen.name; i++)
{
Assert(DoLockModesConflict(intRelOpts[i].gen.lockmode,
@@ -640,6 +653,14 @@ initialize_reloptions(void)
j++;
}
+ for (i = 0; ternaryRelOpts[i].gen.name; i++)
+ {
+ relOpts[j] = &ternaryRelOpts[i].gen;
+ relOpts[j]->type = RELOPT_TYPE_TERNARY;
+ relOpts[j]->namelen = strlen(relOpts[j]->name);
+ j++;
+ }
+
for (i = 0; intRelOpts[i].gen.name; i++)
{
relOpts[j] = &intRelOpts[i].gen;
@@ -800,6 +821,9 @@ allocate_reloption(bits32 kinds, int type, const char *name, const char *desc,
case RELOPT_TYPE_BOOL:
size = sizeof(relopt_bool);
break;
+ case RELOPT_TYPE_TERNARY:
+ size = sizeof(relopt_ternary);
+ break;
case RELOPT_TYPE_INT:
size = sizeof(relopt_int);
break;
@@ -883,6 +907,54 @@ add_local_bool_reloption(local_relopts *relopts, const char *name,
add_local_reloption(relopts, (relopt_gen *) newoption, offset);
}
+/*
+ * init_ternary_reloption
+ * Allocate and initialize a new ternary reloption
+ */
+static relopt_ternary *
+init_ternary_reloption(bits32 kinds, const char *name, const char *desc,
+ ternary default_val, LOCKMODE lockmode)
+{
+ relopt_ternary *newoption;
+
+ newoption = (relopt_ternary *) allocate_reloption(kinds,
+ RELOPT_TYPE_TERNARY, name, desc, lockmode);
+ newoption->default_val = default_val;
+
+ return newoption;
+}
+
+/*
+ * add_ternary_reloption
+ * Add a new ternary reloption
+ */
+void
+add_ternary_reloption(bits32 kinds, const char *name, const char *desc,
+ ternary default_val, LOCKMODE lockmode)
+{
+ relopt_ternary *newoption = init_ternary_reloption(kinds, name, desc,
+ default_val, lockmode);
+
+ add_reloption((relopt_gen *) newoption);
+}
+
+/*
+ * add_local_ternary_reloption
+ * Add a new ternary local reloption
+ *
+ * 'offset' is offset of ternary-typed field.
+ */
+void
+add_local_ternary_reloption(local_relopts *relopts, const char *name,
+ const char *desc, ternary default_val,
+ int offset)
+{
+ relopt_ternary *newoption = init_ternary_reloption(RELOPT_KIND_LOCAL,
+ name, desc,
+ default_val, 0);
+
+ add_local_reloption(relopts, (relopt_gen *) newoption, offset);
+}
/*
* init_real_reloption
@@ -1617,6 +1689,18 @@ parse_one_reloption(relopt_value *option, char *text_str, int text_len,
option->gen->name, value)));
}
break;
+ case RELOPT_TYPE_TERNARY:
+ {
+ bool b;
+ parsed = parse_bool(value, &b);
+ option->values.ternary_val = b ? TERNARY_TRUE : TERNARY_FALSE;
+ if (validate && !parsed)
+ ereport(ERROR,
+ (errcode(ERRCODE_INVALID_PARAMETER_VALUE),
+ errmsg("invalid value for ternary option \"%s\": %s",
+ option->gen->name, value)));
+ }
+ break;
case RELOPT_TYPE_INT:
{
relopt_int *optint = (relopt_int *) option->gen;
@@ -1780,17 +1864,6 @@ fillRelOptions(void *rdopts, Size basesize,
char *itempos = ((char *) rdopts) + elems[j].offset;
char *string_val;
- /*
- * If isset_offset is provided, store whether the reloption is
- * set there.
- */
- if (elems[j].isset_offset > 0)
- {
- char *setpos = ((char *) rdopts) + elems[j].isset_offset;
-
- *(bool *) setpos = options[i].isset;
- }
-
switch (options[i].gen->type)
{
case RELOPT_TYPE_BOOL:
@@ -1798,6 +1871,11 @@ fillRelOptions(void *rdopts, Size basesize,
options[i].values.bool_val :
((relopt_bool *) options[i].gen)->default_val;
break;
+ case RELOPT_TYPE_TERNARY:
+ *(ternary *) itempos = options[i].isset ?
+ options[i].values.ternary_val :
+ ((relopt_ternary *) options[i].gen)->default_val;
+ break;
case RELOPT_TYPE_INT:
*(int *) itempos = options[i].isset ?
options[i].values.int_val :
@@ -1912,8 +1990,8 @@ default_reloptions(Datum reloptions, bool validate, relopt_kind kind)
offsetof(StdRdOptions, parallel_workers)},
{"vacuum_index_cleanup", RELOPT_TYPE_ENUM,
offsetof(StdRdOptions, vacuum_index_cleanup)},
- {"vacuum_truncate", RELOPT_TYPE_BOOL,
- offsetof(StdRdOptions, vacuum_truncate), offsetof(StdRdOptions, vacuum_truncate_set)},
+ {"vacuum_truncate", RELOPT_TYPE_TERNARY,
+ offsetof(StdRdOptions, vacuum_truncate)},
{"vacuum_max_eager_freeze_failure_rate", RELOPT_TYPE_REAL,
offsetof(StdRdOptions, vacuum_max_eager_freeze_failure_rate)}
};
@@ -1993,7 +2071,6 @@ build_local_reloptions(local_relopts *relopts, Datum options, bool validate)
elems[i].optname = opt->option->name;
elems[i].opttype = opt->option->type;
elems[i].offset = opt->offset;
- elems[i].isset_offset = 0; /* not supported for local relopts yet */
i++;
}
diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c
index 733ef40ae7..977babff54 100644
--- a/src/backend/commands/vacuum.c
+++ b/src/backend/commands/vacuum.c
@@ -2221,9 +2221,9 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams params,
{
StdRdOptions *opts = (StdRdOptions *) rel->rd_options;
- if (opts && opts->vacuum_truncate_set)
+ if (opts && opts->vacuum_truncate != TERNARY_UNSET)
{
- if (opts->vacuum_truncate)
+ if (opts->vacuum_truncate == TERNARY_TRUE)
params.truncate = VACOPTVALUE_ENABLED;
else
params.truncate = VACOPTVALUE_DISABLED;
diff --git a/src/include/access/reloptions.h b/src/include/access/reloptions.h
index a604a4702c..a436697658 100644
--- a/src/include/access/reloptions.h
+++ b/src/include/access/reloptions.h
@@ -29,6 +29,7 @@
typedef enum relopt_type
{
RELOPT_TYPE_BOOL,
+ RELOPT_TYPE_TERNARY, /* on, off, unset */
RELOPT_TYPE_INT,
RELOPT_TYPE_REAL,
RELOPT_TYPE_ENUM,
@@ -80,6 +81,7 @@ typedef struct relopt_value
union
{
bool bool_val;
+ ternary ternary_val;
int int_val;
double real_val;
int enum_val;
@@ -94,6 +96,12 @@ typedef struct relopt_bool
bool default_val;
} relopt_bool;
+typedef struct relopt_rernary
+{
+ relopt_gen gen;
+ int default_val;
+} relopt_ternary;
+
typedef struct relopt_int
{
relopt_gen gen;
@@ -152,19 +160,6 @@ typedef struct
const char *optname; /* option's name */
relopt_type opttype; /* option's datatype */
int offset; /* offset of field in result struct */
-
- /*
- * isset_offset is an optional offset of a field in the result struct that
- * stores whether the option is explicitly set for the relation or if it
- * just picked up the default value. In most cases, this can be
- * accomplished by giving the reloption a special out-of-range default
- * value (e.g., some integer reloptions use -2), but this isn't always
- * possible. For example, a Boolean reloption cannot be given an
- * out-of-range default, so we need another way to discover the source of
- * its value. This offset is only used if given a value greater than
- * zero.
- */
- int isset_offset;
} relopt_parse_elt;
/* Local reloption definition */
@@ -195,6 +190,8 @@ typedef struct local_relopts
extern relopt_kind add_reloption_kind(void);
extern void add_bool_reloption(bits32 kinds, const char *name, const char *desc,
bool default_val, LOCKMODE lockmode);
+extern void add_ternary_reloption(bits32 kinds, const char *name,
+ const char *desc, int default_val, LOCKMODE lockmode);
extern void add_int_reloption(bits32 kinds, const char *name, const char *desc,
int default_val, int min_val, int max_val,
LOCKMODE lockmode);
@@ -214,6 +211,9 @@ extern void register_reloptions_validator(local_relopts *relopts,
extern void add_local_bool_reloption(local_relopts *relopts, const char *name,
const char *desc, bool default_val,
int offset);
+extern void add_local_ternary_reloption(local_relopts *relopts,
+ const char *name, const char *desc,
+ ternary default_val, int offset);
extern void add_local_int_reloption(local_relopts *relopts, const char *name,
const char *desc, int default_val,
int min_val, int max_val, int offset);
diff --git a/src/include/c.h b/src/include/c.h
index 39022f8a9d..9c59ccccf8 100644
--- a/src/include/c.h
+++ b/src/include/c.h
@@ -488,6 +488,22 @@ typedef void (*pg_funcptr_t) (void);
#include <stdbool.h>
+/*
+ * ternary
+ * Boolean value with an extrea "unset" option
+ *
+ * Ternary data type is used in relation options that can be "true", "false" or
+ * "unset". Since relation options are used deep inside the PostgreSQL code,
+ * this type is declared globally.
+*/
+
+typedef enum ternary
+{
+ TERNARY_FALSE = 0,
+ TERNARY_TRUE = 1,
+ TERNARY_UNSET = -1
+} ternary;
+
/* ----------------------------------------------------------------
* Section 3: standard system types
diff --git a/src/include/utils/rel.h b/src/include/utils/rel.h
index b552359915..7346d618f9 100644
--- a/src/include/utils/rel.h
+++ b/src/include/utils/rel.h
@@ -346,8 +346,7 @@ typedef struct StdRdOptions
bool user_catalog_table; /* use as an additional catalog relation */
int parallel_workers; /* max number of parallel workers */
StdRdOptIndexCleanup vacuum_index_cleanup; /* controls index vacuuming */
- bool vacuum_truncate; /* enables vacuum to truncate a relation */
- bool vacuum_truncate_set; /* whether vacuum_truncate is set */
+ ternary vacuum_truncate; /* enables vacuum to truncate a relation */
/*
* Fraction of pages in a relation that vacuum can eagerly scan and fail
--
2.39.2
--nextPart4155600.3ZeAukHxDK
Content-Disposition: attachment;
filename="v2a-0003-Add-alias-to-be-used-as-unset-state.patch"
Content-Transfer-Encoding: 7Bit
Content-Type: text/x-patch; charset="unicode-2-0-utf-8";
name="v2a-0003-Add-alias-to-be-used-as-unset-state.patch"
^ permalink raw reply [nested|flat] 868+ messages in thread
* [PATCH v2a 2/4] Introduce ternary reloptions
@ 2025-09-04 14:41 Nikolay Shaplov <[email protected]>
0 siblings, 0 replies; 868+ messages in thread
From: Nikolay Shaplov @ 2025-09-04 14:41 UTC (permalink / raw)
Introduce ternary reloption as a replacement for current `vacuum_truncate`
implementation. Remove `vacuum_truncate_set` additional flag and using
`TERNARY_UNSET` value instead.
---
src/backend/access/common/reloptions.c | 129 ++++++++++++++++++++-----
src/backend/commands/vacuum.c | 4 +-
src/include/access/reloptions.h | 26 ++---
src/include/c.h | 16 +++
src/include/utils/rel.h | 3 +-
5 files changed, 135 insertions(+), 43 deletions(-)
diff --git a/src/backend/access/common/reloptions.c b/src/backend/access/common/reloptions.c
index 0af3fea68f..f543f11001 100644
--- a/src/backend/access/common/reloptions.c
+++ b/src/backend/access/common/reloptions.c
@@ -40,9 +40,9 @@
*
* To add an option:
*
- * (i) decide on a type (bool, integer, real, enum, string), name, default
- * value, upper and lower bounds (if applicable); for strings, consider a
- * validation routine.
+ * (i) decide on a type (bool, ternary, integer, real, enum, string), name,
+ * default value, upper and lower bounds (if applicable); for strings,
+ * consider a validation routine.
* (ii) add a record below (or use add_<type>_reloption).
* (iii) add it to the appropriate options struct (perhaps StdRdOptions)
* (iv) add it to the appropriate handling routine (perhaps
@@ -147,15 +147,6 @@ static relopt_bool boolRelOpts[] =
},
false
},
- {
- {
- "vacuum_truncate",
- "Enables vacuum to truncate empty pages at the end of this table",
- RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
- ShareUpdateExclusiveLock
- },
- true
- },
{
{
"deduplicate_items",
@@ -170,6 +161,21 @@ static relopt_bool boolRelOpts[] =
{{NULL}}
};
+static relopt_ternary ternaryRelOpts[] =
+{
+ {
+ {
+ "vacuum_truncate",
+ "Enables vacuum to truncate empty pages at the end of this table",
+ RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
+ ShareUpdateExclusiveLock
+ },
+ TERNARY_UNSET
+ },
+ /* list terminator */
+ {{NULL}}
+};
+
static relopt_int intRelOpts[] =
{
{
@@ -600,6 +606,13 @@ initialize_reloptions(void)
boolRelOpts[i].gen.lockmode));
j++;
}
+ for (i = 0; ternaryRelOpts[i].gen.name; i++)
+ {
+ Assert(DoLockModesConflict(ternaryRelOpts[i].gen.lockmode,
+ ternaryRelOpts[i].gen.lockmode));
+ j++;
+ }
+
for (i = 0; intRelOpts[i].gen.name; i++)
{
Assert(DoLockModesConflict(intRelOpts[i].gen.lockmode,
@@ -640,6 +653,14 @@ initialize_reloptions(void)
j++;
}
+ for (i = 0; ternaryRelOpts[i].gen.name; i++)
+ {
+ relOpts[j] = &ternaryRelOpts[i].gen;
+ relOpts[j]->type = RELOPT_TYPE_TERNARY;
+ relOpts[j]->namelen = strlen(relOpts[j]->name);
+ j++;
+ }
+
for (i = 0; intRelOpts[i].gen.name; i++)
{
relOpts[j] = &intRelOpts[i].gen;
@@ -800,6 +821,9 @@ allocate_reloption(bits32 kinds, int type, const char *name, const char *desc,
case RELOPT_TYPE_BOOL:
size = sizeof(relopt_bool);
break;
+ case RELOPT_TYPE_TERNARY:
+ size = sizeof(relopt_ternary);
+ break;
case RELOPT_TYPE_INT:
size = sizeof(relopt_int);
break;
@@ -883,6 +907,54 @@ add_local_bool_reloption(local_relopts *relopts, const char *name,
add_local_reloption(relopts, (relopt_gen *) newoption, offset);
}
+/*
+ * init_ternary_reloption
+ * Allocate and initialize a new ternary reloption
+ */
+static relopt_ternary *
+init_ternary_reloption(bits32 kinds, const char *name, const char *desc,
+ ternary default_val, LOCKMODE lockmode)
+{
+ relopt_ternary *newoption;
+
+ newoption = (relopt_ternary *) allocate_reloption(kinds,
+ RELOPT_TYPE_TERNARY, name, desc, lockmode);
+ newoption->default_val = default_val;
+
+ return newoption;
+}
+
+/*
+ * add_ternary_reloption
+ * Add a new ternary reloption
+ */
+void
+add_ternary_reloption(bits32 kinds, const char *name, const char *desc,
+ ternary default_val, LOCKMODE lockmode)
+{
+ relopt_ternary *newoption = init_ternary_reloption(kinds, name, desc,
+ default_val, lockmode);
+
+ add_reloption((relopt_gen *) newoption);
+}
+
+/*
+ * add_local_ternary_reloption
+ * Add a new ternary local reloption
+ *
+ * 'offset' is offset of ternary-typed field.
+ */
+void
+add_local_ternary_reloption(local_relopts *relopts, const char *name,
+ const char *desc, ternary default_val,
+ int offset)
+{
+ relopt_ternary *newoption = init_ternary_reloption(RELOPT_KIND_LOCAL,
+ name, desc,
+ default_val, 0);
+
+ add_local_reloption(relopts, (relopt_gen *) newoption, offset);
+}
/*
* init_real_reloption
@@ -1617,6 +1689,18 @@ parse_one_reloption(relopt_value *option, char *text_str, int text_len,
option->gen->name, value)));
}
break;
+ case RELOPT_TYPE_TERNARY:
+ {
+ bool b;
+ parsed = parse_bool(value, &b);
+ option->values.ternary_val = b ? TERNARY_TRUE : TERNARY_FALSE;
+ if (validate && !parsed)
+ ereport(ERROR,
+ (errcode(ERRCODE_INVALID_PARAMETER_VALUE),
+ errmsg("invalid value for ternary option \"%s\": %s",
+ option->gen->name, value)));
+ }
+ break;
case RELOPT_TYPE_INT:
{
relopt_int *optint = (relopt_int *) option->gen;
@@ -1780,17 +1864,6 @@ fillRelOptions(void *rdopts, Size basesize,
char *itempos = ((char *) rdopts) + elems[j].offset;
char *string_val;
- /*
- * If isset_offset is provided, store whether the reloption is
- * set there.
- */
- if (elems[j].isset_offset > 0)
- {
- char *setpos = ((char *) rdopts) + elems[j].isset_offset;
-
- *(bool *) setpos = options[i].isset;
- }
-
switch (options[i].gen->type)
{
case RELOPT_TYPE_BOOL:
@@ -1798,6 +1871,11 @@ fillRelOptions(void *rdopts, Size basesize,
options[i].values.bool_val :
((relopt_bool *) options[i].gen)->default_val;
break;
+ case RELOPT_TYPE_TERNARY:
+ *(ternary *) itempos = options[i].isset ?
+ options[i].values.ternary_val :
+ ((relopt_ternary *) options[i].gen)->default_val;
+ break;
case RELOPT_TYPE_INT:
*(int *) itempos = options[i].isset ?
options[i].values.int_val :
@@ -1912,8 +1990,8 @@ default_reloptions(Datum reloptions, bool validate, relopt_kind kind)
offsetof(StdRdOptions, parallel_workers)},
{"vacuum_index_cleanup", RELOPT_TYPE_ENUM,
offsetof(StdRdOptions, vacuum_index_cleanup)},
- {"vacuum_truncate", RELOPT_TYPE_BOOL,
- offsetof(StdRdOptions, vacuum_truncate), offsetof(StdRdOptions, vacuum_truncate_set)},
+ {"vacuum_truncate", RELOPT_TYPE_TERNARY,
+ offsetof(StdRdOptions, vacuum_truncate)},
{"vacuum_max_eager_freeze_failure_rate", RELOPT_TYPE_REAL,
offsetof(StdRdOptions, vacuum_max_eager_freeze_failure_rate)}
};
@@ -1993,7 +2071,6 @@ build_local_reloptions(local_relopts *relopts, Datum options, bool validate)
elems[i].optname = opt->option->name;
elems[i].opttype = opt->option->type;
elems[i].offset = opt->offset;
- elems[i].isset_offset = 0; /* not supported for local relopts yet */
i++;
}
diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c
index 733ef40ae7..977babff54 100644
--- a/src/backend/commands/vacuum.c
+++ b/src/backend/commands/vacuum.c
@@ -2221,9 +2221,9 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams params,
{
StdRdOptions *opts = (StdRdOptions *) rel->rd_options;
- if (opts && opts->vacuum_truncate_set)
+ if (opts && opts->vacuum_truncate != TERNARY_UNSET)
{
- if (opts->vacuum_truncate)
+ if (opts->vacuum_truncate == TERNARY_TRUE)
params.truncate = VACOPTVALUE_ENABLED;
else
params.truncate = VACOPTVALUE_DISABLED;
diff --git a/src/include/access/reloptions.h b/src/include/access/reloptions.h
index a604a4702c..a436697658 100644
--- a/src/include/access/reloptions.h
+++ b/src/include/access/reloptions.h
@@ -29,6 +29,7 @@
typedef enum relopt_type
{
RELOPT_TYPE_BOOL,
+ RELOPT_TYPE_TERNARY, /* on, off, unset */
RELOPT_TYPE_INT,
RELOPT_TYPE_REAL,
RELOPT_TYPE_ENUM,
@@ -80,6 +81,7 @@ typedef struct relopt_value
union
{
bool bool_val;
+ ternary ternary_val;
int int_val;
double real_val;
int enum_val;
@@ -94,6 +96,12 @@ typedef struct relopt_bool
bool default_val;
} relopt_bool;
+typedef struct relopt_rernary
+{
+ relopt_gen gen;
+ int default_val;
+} relopt_ternary;
+
typedef struct relopt_int
{
relopt_gen gen;
@@ -152,19 +160,6 @@ typedef struct
const char *optname; /* option's name */
relopt_type opttype; /* option's datatype */
int offset; /* offset of field in result struct */
-
- /*
- * isset_offset is an optional offset of a field in the result struct that
- * stores whether the option is explicitly set for the relation or if it
- * just picked up the default value. In most cases, this can be
- * accomplished by giving the reloption a special out-of-range default
- * value (e.g., some integer reloptions use -2), but this isn't always
- * possible. For example, a Boolean reloption cannot be given an
- * out-of-range default, so we need another way to discover the source of
- * its value. This offset is only used if given a value greater than
- * zero.
- */
- int isset_offset;
} relopt_parse_elt;
/* Local reloption definition */
@@ -195,6 +190,8 @@ typedef struct local_relopts
extern relopt_kind add_reloption_kind(void);
extern void add_bool_reloption(bits32 kinds, const char *name, const char *desc,
bool default_val, LOCKMODE lockmode);
+extern void add_ternary_reloption(bits32 kinds, const char *name,
+ const char *desc, int default_val, LOCKMODE lockmode);
extern void add_int_reloption(bits32 kinds, const char *name, const char *desc,
int default_val, int min_val, int max_val,
LOCKMODE lockmode);
@@ -214,6 +211,9 @@ extern void register_reloptions_validator(local_relopts *relopts,
extern void add_local_bool_reloption(local_relopts *relopts, const char *name,
const char *desc, bool default_val,
int offset);
+extern void add_local_ternary_reloption(local_relopts *relopts,
+ const char *name, const char *desc,
+ ternary default_val, int offset);
extern void add_local_int_reloption(local_relopts *relopts, const char *name,
const char *desc, int default_val,
int min_val, int max_val, int offset);
diff --git a/src/include/c.h b/src/include/c.h
index 39022f8a9d..9c59ccccf8 100644
--- a/src/include/c.h
+++ b/src/include/c.h
@@ -488,6 +488,22 @@ typedef void (*pg_funcptr_t) (void);
#include <stdbool.h>
+/*
+ * ternary
+ * Boolean value with an extrea "unset" option
+ *
+ * Ternary data type is used in relation options that can be "true", "false" or
+ * "unset". Since relation options are used deep inside the PostgreSQL code,
+ * this type is declared globally.
+*/
+
+typedef enum ternary
+{
+ TERNARY_FALSE = 0,
+ TERNARY_TRUE = 1,
+ TERNARY_UNSET = -1
+} ternary;
+
/* ----------------------------------------------------------------
* Section 3: standard system types
diff --git a/src/include/utils/rel.h b/src/include/utils/rel.h
index b552359915..7346d618f9 100644
--- a/src/include/utils/rel.h
+++ b/src/include/utils/rel.h
@@ -346,8 +346,7 @@ typedef struct StdRdOptions
bool user_catalog_table; /* use as an additional catalog relation */
int parallel_workers; /* max number of parallel workers */
StdRdOptIndexCleanup vacuum_index_cleanup; /* controls index vacuuming */
- bool vacuum_truncate; /* enables vacuum to truncate a relation */
- bool vacuum_truncate_set; /* whether vacuum_truncate is set */
+ ternary vacuum_truncate; /* enables vacuum to truncate a relation */
/*
* Fraction of pages in a relation that vacuum can eagerly scan and fail
--
2.39.2
--nextPart4155600.3ZeAukHxDK
Content-Disposition: attachment;
filename="v2a-0003-Add-alias-to-be-used-as-unset-state.patch"
Content-Transfer-Encoding: 7Bit
Content-Type: text/x-patch; charset="unicode-2-0-utf-8";
name="v2a-0003-Add-alias-to-be-used-as-unset-state.patch"
^ permalink raw reply [nested|flat] 868+ messages in thread
* [PATCH v2 2/4] Introduce ternary reloptions
@ 2025-09-04 14:41 Nikolay Shaplov <[email protected]>
0 siblings, 0 replies; 868+ messages in thread
From: Nikolay Shaplov @ 2025-09-04 14:41 UTC (permalink / raw)
Introduce ternary reloption as a replacement for current `vacuum_truncate`
implementation. Remove `vacuum_truncate_set` additional flag and using
`TERNARY_UNSET` value instead.
---
src/backend/access/common/reloptions.c | 129 ++++++++++++++++++++-----
src/backend/commands/vacuum.c | 4 +-
src/include/access/reloptions.h | 26 ++---
src/include/c.h | 16 +++
src/include/utils/rel.h | 3 +-
5 files changed, 135 insertions(+), 43 deletions(-)
diff --git a/src/backend/access/common/reloptions.c b/src/backend/access/common/reloptions.c
index 0af3fea68f..f543f11001 100644
--- a/src/backend/access/common/reloptions.c
+++ b/src/backend/access/common/reloptions.c
@@ -40,9 +40,9 @@
*
* To add an option:
*
- * (i) decide on a type (bool, integer, real, enum, string), name, default
- * value, upper and lower bounds (if applicable); for strings, consider a
- * validation routine.
+ * (i) decide on a type (bool, ternary, integer, real, enum, string), name,
+ * default value, upper and lower bounds (if applicable); for strings,
+ * consider a validation routine.
* (ii) add a record below (or use add_<type>_reloption).
* (iii) add it to the appropriate options struct (perhaps StdRdOptions)
* (iv) add it to the appropriate handling routine (perhaps
@@ -147,15 +147,6 @@ static relopt_bool boolRelOpts[] =
},
false
},
- {
- {
- "vacuum_truncate",
- "Enables vacuum to truncate empty pages at the end of this table",
- RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
- ShareUpdateExclusiveLock
- },
- true
- },
{
{
"deduplicate_items",
@@ -170,6 +161,21 @@ static relopt_bool boolRelOpts[] =
{{NULL}}
};
+static relopt_ternary ternaryRelOpts[] =
+{
+ {
+ {
+ "vacuum_truncate",
+ "Enables vacuum to truncate empty pages at the end of this table",
+ RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
+ ShareUpdateExclusiveLock
+ },
+ TERNARY_UNSET
+ },
+ /* list terminator */
+ {{NULL}}
+};
+
static relopt_int intRelOpts[] =
{
{
@@ -600,6 +606,13 @@ initialize_reloptions(void)
boolRelOpts[i].gen.lockmode));
j++;
}
+ for (i = 0; ternaryRelOpts[i].gen.name; i++)
+ {
+ Assert(DoLockModesConflict(ternaryRelOpts[i].gen.lockmode,
+ ternaryRelOpts[i].gen.lockmode));
+ j++;
+ }
+
for (i = 0; intRelOpts[i].gen.name; i++)
{
Assert(DoLockModesConflict(intRelOpts[i].gen.lockmode,
@@ -640,6 +653,14 @@ initialize_reloptions(void)
j++;
}
+ for (i = 0; ternaryRelOpts[i].gen.name; i++)
+ {
+ relOpts[j] = &ternaryRelOpts[i].gen;
+ relOpts[j]->type = RELOPT_TYPE_TERNARY;
+ relOpts[j]->namelen = strlen(relOpts[j]->name);
+ j++;
+ }
+
for (i = 0; intRelOpts[i].gen.name; i++)
{
relOpts[j] = &intRelOpts[i].gen;
@@ -800,6 +821,9 @@ allocate_reloption(bits32 kinds, int type, const char *name, const char *desc,
case RELOPT_TYPE_BOOL:
size = sizeof(relopt_bool);
break;
+ case RELOPT_TYPE_TERNARY:
+ size = sizeof(relopt_ternary);
+ break;
case RELOPT_TYPE_INT:
size = sizeof(relopt_int);
break;
@@ -883,6 +907,54 @@ add_local_bool_reloption(local_relopts *relopts, const char *name,
add_local_reloption(relopts, (relopt_gen *) newoption, offset);
}
+/*
+ * init_ternary_reloption
+ * Allocate and initialize a new ternary reloption
+ */
+static relopt_ternary *
+init_ternary_reloption(bits32 kinds, const char *name, const char *desc,
+ ternary default_val, LOCKMODE lockmode)
+{
+ relopt_ternary *newoption;
+
+ newoption = (relopt_ternary *) allocate_reloption(kinds,
+ RELOPT_TYPE_TERNARY, name, desc, lockmode);
+ newoption->default_val = default_val;
+
+ return newoption;
+}
+
+/*
+ * add_ternary_reloption
+ * Add a new ternary reloption
+ */
+void
+add_ternary_reloption(bits32 kinds, const char *name, const char *desc,
+ ternary default_val, LOCKMODE lockmode)
+{
+ relopt_ternary *newoption = init_ternary_reloption(kinds, name, desc,
+ default_val, lockmode);
+
+ add_reloption((relopt_gen *) newoption);
+}
+
+/*
+ * add_local_ternary_reloption
+ * Add a new ternary local reloption
+ *
+ * 'offset' is offset of ternary-typed field.
+ */
+void
+add_local_ternary_reloption(local_relopts *relopts, const char *name,
+ const char *desc, ternary default_val,
+ int offset)
+{
+ relopt_ternary *newoption = init_ternary_reloption(RELOPT_KIND_LOCAL,
+ name, desc,
+ default_val, 0);
+
+ add_local_reloption(relopts, (relopt_gen *) newoption, offset);
+}
/*
* init_real_reloption
@@ -1617,6 +1689,18 @@ parse_one_reloption(relopt_value *option, char *text_str, int text_len,
option->gen->name, value)));
}
break;
+ case RELOPT_TYPE_TERNARY:
+ {
+ bool b;
+ parsed = parse_bool(value, &b);
+ option->values.ternary_val = b ? TERNARY_TRUE : TERNARY_FALSE;
+ if (validate && !parsed)
+ ereport(ERROR,
+ (errcode(ERRCODE_INVALID_PARAMETER_VALUE),
+ errmsg("invalid value for ternary option \"%s\": %s",
+ option->gen->name, value)));
+ }
+ break;
case RELOPT_TYPE_INT:
{
relopt_int *optint = (relopt_int *) option->gen;
@@ -1780,17 +1864,6 @@ fillRelOptions(void *rdopts, Size basesize,
char *itempos = ((char *) rdopts) + elems[j].offset;
char *string_val;
- /*
- * If isset_offset is provided, store whether the reloption is
- * set there.
- */
- if (elems[j].isset_offset > 0)
- {
- char *setpos = ((char *) rdopts) + elems[j].isset_offset;
-
- *(bool *) setpos = options[i].isset;
- }
-
switch (options[i].gen->type)
{
case RELOPT_TYPE_BOOL:
@@ -1798,6 +1871,11 @@ fillRelOptions(void *rdopts, Size basesize,
options[i].values.bool_val :
((relopt_bool *) options[i].gen)->default_val;
break;
+ case RELOPT_TYPE_TERNARY:
+ *(ternary *) itempos = options[i].isset ?
+ options[i].values.ternary_val :
+ ((relopt_ternary *) options[i].gen)->default_val;
+ break;
case RELOPT_TYPE_INT:
*(int *) itempos = options[i].isset ?
options[i].values.int_val :
@@ -1912,8 +1990,8 @@ default_reloptions(Datum reloptions, bool validate, relopt_kind kind)
offsetof(StdRdOptions, parallel_workers)},
{"vacuum_index_cleanup", RELOPT_TYPE_ENUM,
offsetof(StdRdOptions, vacuum_index_cleanup)},
- {"vacuum_truncate", RELOPT_TYPE_BOOL,
- offsetof(StdRdOptions, vacuum_truncate), offsetof(StdRdOptions, vacuum_truncate_set)},
+ {"vacuum_truncate", RELOPT_TYPE_TERNARY,
+ offsetof(StdRdOptions, vacuum_truncate)},
{"vacuum_max_eager_freeze_failure_rate", RELOPT_TYPE_REAL,
offsetof(StdRdOptions, vacuum_max_eager_freeze_failure_rate)}
};
@@ -1993,7 +2071,6 @@ build_local_reloptions(local_relopts *relopts, Datum options, bool validate)
elems[i].optname = opt->option->name;
elems[i].opttype = opt->option->type;
elems[i].offset = opt->offset;
- elems[i].isset_offset = 0; /* not supported for local relopts yet */
i++;
}
diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c
index 733ef40ae7..977babff54 100644
--- a/src/backend/commands/vacuum.c
+++ b/src/backend/commands/vacuum.c
@@ -2221,9 +2221,9 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams params,
{
StdRdOptions *opts = (StdRdOptions *) rel->rd_options;
- if (opts && opts->vacuum_truncate_set)
+ if (opts && opts->vacuum_truncate != TERNARY_UNSET)
{
- if (opts->vacuum_truncate)
+ if (opts->vacuum_truncate == TERNARY_TRUE)
params.truncate = VACOPTVALUE_ENABLED;
else
params.truncate = VACOPTVALUE_DISABLED;
diff --git a/src/include/access/reloptions.h b/src/include/access/reloptions.h
index a604a4702c..a436697658 100644
--- a/src/include/access/reloptions.h
+++ b/src/include/access/reloptions.h
@@ -29,6 +29,7 @@
typedef enum relopt_type
{
RELOPT_TYPE_BOOL,
+ RELOPT_TYPE_TERNARY, /* on, off, unset */
RELOPT_TYPE_INT,
RELOPT_TYPE_REAL,
RELOPT_TYPE_ENUM,
@@ -80,6 +81,7 @@ typedef struct relopt_value
union
{
bool bool_val;
+ ternary ternary_val;
int int_val;
double real_val;
int enum_val;
@@ -94,6 +96,12 @@ typedef struct relopt_bool
bool default_val;
} relopt_bool;
+typedef struct relopt_rernary
+{
+ relopt_gen gen;
+ int default_val;
+} relopt_ternary;
+
typedef struct relopt_int
{
relopt_gen gen;
@@ -152,19 +160,6 @@ typedef struct
const char *optname; /* option's name */
relopt_type opttype; /* option's datatype */
int offset; /* offset of field in result struct */
-
- /*
- * isset_offset is an optional offset of a field in the result struct that
- * stores whether the option is explicitly set for the relation or if it
- * just picked up the default value. In most cases, this can be
- * accomplished by giving the reloption a special out-of-range default
- * value (e.g., some integer reloptions use -2), but this isn't always
- * possible. For example, a Boolean reloption cannot be given an
- * out-of-range default, so we need another way to discover the source of
- * its value. This offset is only used if given a value greater than
- * zero.
- */
- int isset_offset;
} relopt_parse_elt;
/* Local reloption definition */
@@ -195,6 +190,8 @@ typedef struct local_relopts
extern relopt_kind add_reloption_kind(void);
extern void add_bool_reloption(bits32 kinds, const char *name, const char *desc,
bool default_val, LOCKMODE lockmode);
+extern void add_ternary_reloption(bits32 kinds, const char *name,
+ const char *desc, int default_val, LOCKMODE lockmode);
extern void add_int_reloption(bits32 kinds, const char *name, const char *desc,
int default_val, int min_val, int max_val,
LOCKMODE lockmode);
@@ -214,6 +211,9 @@ extern void register_reloptions_validator(local_relopts *relopts,
extern void add_local_bool_reloption(local_relopts *relopts, const char *name,
const char *desc, bool default_val,
int offset);
+extern void add_local_ternary_reloption(local_relopts *relopts,
+ const char *name, const char *desc,
+ ternary default_val, int offset);
extern void add_local_int_reloption(local_relopts *relopts, const char *name,
const char *desc, int default_val,
int min_val, int max_val, int offset);
diff --git a/src/include/c.h b/src/include/c.h
index 39022f8a9d..9c59ccccf8 100644
--- a/src/include/c.h
+++ b/src/include/c.h
@@ -488,6 +488,22 @@ typedef void (*pg_funcptr_t) (void);
#include <stdbool.h>
+/*
+ * ternary
+ * Boolean value with an extrea "unset" option
+ *
+ * Ternary data type is used in relation options that can be "true", "false" or
+ * "unset". Since relation options are used deep inside the PostgreSQL code,
+ * this type is declared globally.
+*/
+
+typedef enum ternary
+{
+ TERNARY_FALSE = 0,
+ TERNARY_TRUE = 1,
+ TERNARY_UNSET = -1
+} ternary;
+
/* ----------------------------------------------------------------
* Section 3: standard system types
diff --git a/src/include/utils/rel.h b/src/include/utils/rel.h
index b552359915..7346d618f9 100644
--- a/src/include/utils/rel.h
+++ b/src/include/utils/rel.h
@@ -346,8 +346,7 @@ typedef struct StdRdOptions
bool user_catalog_table; /* use as an additional catalog relation */
int parallel_workers; /* max number of parallel workers */
StdRdOptIndexCleanup vacuum_index_cleanup; /* controls index vacuuming */
- bool vacuum_truncate; /* enables vacuum to truncate a relation */
- bool vacuum_truncate_set; /* whether vacuum_truncate is set */
+ ternary vacuum_truncate; /* enables vacuum to truncate a relation */
/*
* Fraction of pages in a relation that vacuum can eagerly scan and fail
--
2.39.2
--nextPart5129932.5fSG56mABF
Content-Disposition: attachment;
filename="v2-0003-Add-alias-to-be-used-as-unset-state.patch"
Content-Transfer-Encoding: 7Bit
Content-Type: text/x-patch; charset="unicode-2-0-utf-8";
name="v2-0003-Add-alias-to-be-used-as-unset-state.patch"
^ permalink raw reply [nested|flat] 868+ messages in thread
* [PATCH v2a 2/4] Introduce ternary reloptions
@ 2025-09-04 14:41 Nikolay Shaplov <[email protected]>
0 siblings, 0 replies; 868+ messages in thread
From: Nikolay Shaplov @ 2025-09-04 14:41 UTC (permalink / raw)
Introduce ternary reloption as a replacement for current `vacuum_truncate`
implementation. Remove `vacuum_truncate_set` additional flag and using
`TERNARY_UNSET` value instead.
---
src/backend/access/common/reloptions.c | 129 ++++++++++++++++++++-----
src/backend/commands/vacuum.c | 4 +-
src/include/access/reloptions.h | 26 ++---
src/include/c.h | 16 +++
src/include/utils/rel.h | 3 +-
5 files changed, 135 insertions(+), 43 deletions(-)
diff --git a/src/backend/access/common/reloptions.c b/src/backend/access/common/reloptions.c
index 0af3fea68f..f543f11001 100644
--- a/src/backend/access/common/reloptions.c
+++ b/src/backend/access/common/reloptions.c
@@ -40,9 +40,9 @@
*
* To add an option:
*
- * (i) decide on a type (bool, integer, real, enum, string), name, default
- * value, upper and lower bounds (if applicable); for strings, consider a
- * validation routine.
+ * (i) decide on a type (bool, ternary, integer, real, enum, string), name,
+ * default value, upper and lower bounds (if applicable); for strings,
+ * consider a validation routine.
* (ii) add a record below (or use add_<type>_reloption).
* (iii) add it to the appropriate options struct (perhaps StdRdOptions)
* (iv) add it to the appropriate handling routine (perhaps
@@ -147,15 +147,6 @@ static relopt_bool boolRelOpts[] =
},
false
},
- {
- {
- "vacuum_truncate",
- "Enables vacuum to truncate empty pages at the end of this table",
- RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
- ShareUpdateExclusiveLock
- },
- true
- },
{
{
"deduplicate_items",
@@ -170,6 +161,21 @@ static relopt_bool boolRelOpts[] =
{{NULL}}
};
+static relopt_ternary ternaryRelOpts[] =
+{
+ {
+ {
+ "vacuum_truncate",
+ "Enables vacuum to truncate empty pages at the end of this table",
+ RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
+ ShareUpdateExclusiveLock
+ },
+ TERNARY_UNSET
+ },
+ /* list terminator */
+ {{NULL}}
+};
+
static relopt_int intRelOpts[] =
{
{
@@ -600,6 +606,13 @@ initialize_reloptions(void)
boolRelOpts[i].gen.lockmode));
j++;
}
+ for (i = 0; ternaryRelOpts[i].gen.name; i++)
+ {
+ Assert(DoLockModesConflict(ternaryRelOpts[i].gen.lockmode,
+ ternaryRelOpts[i].gen.lockmode));
+ j++;
+ }
+
for (i = 0; intRelOpts[i].gen.name; i++)
{
Assert(DoLockModesConflict(intRelOpts[i].gen.lockmode,
@@ -640,6 +653,14 @@ initialize_reloptions(void)
j++;
}
+ for (i = 0; ternaryRelOpts[i].gen.name; i++)
+ {
+ relOpts[j] = &ternaryRelOpts[i].gen;
+ relOpts[j]->type = RELOPT_TYPE_TERNARY;
+ relOpts[j]->namelen = strlen(relOpts[j]->name);
+ j++;
+ }
+
for (i = 0; intRelOpts[i].gen.name; i++)
{
relOpts[j] = &intRelOpts[i].gen;
@@ -800,6 +821,9 @@ allocate_reloption(bits32 kinds, int type, const char *name, const char *desc,
case RELOPT_TYPE_BOOL:
size = sizeof(relopt_bool);
break;
+ case RELOPT_TYPE_TERNARY:
+ size = sizeof(relopt_ternary);
+ break;
case RELOPT_TYPE_INT:
size = sizeof(relopt_int);
break;
@@ -883,6 +907,54 @@ add_local_bool_reloption(local_relopts *relopts, const char *name,
add_local_reloption(relopts, (relopt_gen *) newoption, offset);
}
+/*
+ * init_ternary_reloption
+ * Allocate and initialize a new ternary reloption
+ */
+static relopt_ternary *
+init_ternary_reloption(bits32 kinds, const char *name, const char *desc,
+ ternary default_val, LOCKMODE lockmode)
+{
+ relopt_ternary *newoption;
+
+ newoption = (relopt_ternary *) allocate_reloption(kinds,
+ RELOPT_TYPE_TERNARY, name, desc, lockmode);
+ newoption->default_val = default_val;
+
+ return newoption;
+}
+
+/*
+ * add_ternary_reloption
+ * Add a new ternary reloption
+ */
+void
+add_ternary_reloption(bits32 kinds, const char *name, const char *desc,
+ ternary default_val, LOCKMODE lockmode)
+{
+ relopt_ternary *newoption = init_ternary_reloption(kinds, name, desc,
+ default_val, lockmode);
+
+ add_reloption((relopt_gen *) newoption);
+}
+
+/*
+ * add_local_ternary_reloption
+ * Add a new ternary local reloption
+ *
+ * 'offset' is offset of ternary-typed field.
+ */
+void
+add_local_ternary_reloption(local_relopts *relopts, const char *name,
+ const char *desc, ternary default_val,
+ int offset)
+{
+ relopt_ternary *newoption = init_ternary_reloption(RELOPT_KIND_LOCAL,
+ name, desc,
+ default_val, 0);
+
+ add_local_reloption(relopts, (relopt_gen *) newoption, offset);
+}
/*
* init_real_reloption
@@ -1617,6 +1689,18 @@ parse_one_reloption(relopt_value *option, char *text_str, int text_len,
option->gen->name, value)));
}
break;
+ case RELOPT_TYPE_TERNARY:
+ {
+ bool b;
+ parsed = parse_bool(value, &b);
+ option->values.ternary_val = b ? TERNARY_TRUE : TERNARY_FALSE;
+ if (validate && !parsed)
+ ereport(ERROR,
+ (errcode(ERRCODE_INVALID_PARAMETER_VALUE),
+ errmsg("invalid value for ternary option \"%s\": %s",
+ option->gen->name, value)));
+ }
+ break;
case RELOPT_TYPE_INT:
{
relopt_int *optint = (relopt_int *) option->gen;
@@ -1780,17 +1864,6 @@ fillRelOptions(void *rdopts, Size basesize,
char *itempos = ((char *) rdopts) + elems[j].offset;
char *string_val;
- /*
- * If isset_offset is provided, store whether the reloption is
- * set there.
- */
- if (elems[j].isset_offset > 0)
- {
- char *setpos = ((char *) rdopts) + elems[j].isset_offset;
-
- *(bool *) setpos = options[i].isset;
- }
-
switch (options[i].gen->type)
{
case RELOPT_TYPE_BOOL:
@@ -1798,6 +1871,11 @@ fillRelOptions(void *rdopts, Size basesize,
options[i].values.bool_val :
((relopt_bool *) options[i].gen)->default_val;
break;
+ case RELOPT_TYPE_TERNARY:
+ *(ternary *) itempos = options[i].isset ?
+ options[i].values.ternary_val :
+ ((relopt_ternary *) options[i].gen)->default_val;
+ break;
case RELOPT_TYPE_INT:
*(int *) itempos = options[i].isset ?
options[i].values.int_val :
@@ -1912,8 +1990,8 @@ default_reloptions(Datum reloptions, bool validate, relopt_kind kind)
offsetof(StdRdOptions, parallel_workers)},
{"vacuum_index_cleanup", RELOPT_TYPE_ENUM,
offsetof(StdRdOptions, vacuum_index_cleanup)},
- {"vacuum_truncate", RELOPT_TYPE_BOOL,
- offsetof(StdRdOptions, vacuum_truncate), offsetof(StdRdOptions, vacuum_truncate_set)},
+ {"vacuum_truncate", RELOPT_TYPE_TERNARY,
+ offsetof(StdRdOptions, vacuum_truncate)},
{"vacuum_max_eager_freeze_failure_rate", RELOPT_TYPE_REAL,
offsetof(StdRdOptions, vacuum_max_eager_freeze_failure_rate)}
};
@@ -1993,7 +2071,6 @@ build_local_reloptions(local_relopts *relopts, Datum options, bool validate)
elems[i].optname = opt->option->name;
elems[i].opttype = opt->option->type;
elems[i].offset = opt->offset;
- elems[i].isset_offset = 0; /* not supported for local relopts yet */
i++;
}
diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c
index 733ef40ae7..977babff54 100644
--- a/src/backend/commands/vacuum.c
+++ b/src/backend/commands/vacuum.c
@@ -2221,9 +2221,9 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams params,
{
StdRdOptions *opts = (StdRdOptions *) rel->rd_options;
- if (opts && opts->vacuum_truncate_set)
+ if (opts && opts->vacuum_truncate != TERNARY_UNSET)
{
- if (opts->vacuum_truncate)
+ if (opts->vacuum_truncate == TERNARY_TRUE)
params.truncate = VACOPTVALUE_ENABLED;
else
params.truncate = VACOPTVALUE_DISABLED;
diff --git a/src/include/access/reloptions.h b/src/include/access/reloptions.h
index a604a4702c..a436697658 100644
--- a/src/include/access/reloptions.h
+++ b/src/include/access/reloptions.h
@@ -29,6 +29,7 @@
typedef enum relopt_type
{
RELOPT_TYPE_BOOL,
+ RELOPT_TYPE_TERNARY, /* on, off, unset */
RELOPT_TYPE_INT,
RELOPT_TYPE_REAL,
RELOPT_TYPE_ENUM,
@@ -80,6 +81,7 @@ typedef struct relopt_value
union
{
bool bool_val;
+ ternary ternary_val;
int int_val;
double real_val;
int enum_val;
@@ -94,6 +96,12 @@ typedef struct relopt_bool
bool default_val;
} relopt_bool;
+typedef struct relopt_rernary
+{
+ relopt_gen gen;
+ int default_val;
+} relopt_ternary;
+
typedef struct relopt_int
{
relopt_gen gen;
@@ -152,19 +160,6 @@ typedef struct
const char *optname; /* option's name */
relopt_type opttype; /* option's datatype */
int offset; /* offset of field in result struct */
-
- /*
- * isset_offset is an optional offset of a field in the result struct that
- * stores whether the option is explicitly set for the relation or if it
- * just picked up the default value. In most cases, this can be
- * accomplished by giving the reloption a special out-of-range default
- * value (e.g., some integer reloptions use -2), but this isn't always
- * possible. For example, a Boolean reloption cannot be given an
- * out-of-range default, so we need another way to discover the source of
- * its value. This offset is only used if given a value greater than
- * zero.
- */
- int isset_offset;
} relopt_parse_elt;
/* Local reloption definition */
@@ -195,6 +190,8 @@ typedef struct local_relopts
extern relopt_kind add_reloption_kind(void);
extern void add_bool_reloption(bits32 kinds, const char *name, const char *desc,
bool default_val, LOCKMODE lockmode);
+extern void add_ternary_reloption(bits32 kinds, const char *name,
+ const char *desc, int default_val, LOCKMODE lockmode);
extern void add_int_reloption(bits32 kinds, const char *name, const char *desc,
int default_val, int min_val, int max_val,
LOCKMODE lockmode);
@@ -214,6 +211,9 @@ extern void register_reloptions_validator(local_relopts *relopts,
extern void add_local_bool_reloption(local_relopts *relopts, const char *name,
const char *desc, bool default_val,
int offset);
+extern void add_local_ternary_reloption(local_relopts *relopts,
+ const char *name, const char *desc,
+ ternary default_val, int offset);
extern void add_local_int_reloption(local_relopts *relopts, const char *name,
const char *desc, int default_val,
int min_val, int max_val, int offset);
diff --git a/src/include/c.h b/src/include/c.h
index 39022f8a9d..9c59ccccf8 100644
--- a/src/include/c.h
+++ b/src/include/c.h
@@ -488,6 +488,22 @@ typedef void (*pg_funcptr_t) (void);
#include <stdbool.h>
+/*
+ * ternary
+ * Boolean value with an extrea "unset" option
+ *
+ * Ternary data type is used in relation options that can be "true", "false" or
+ * "unset". Since relation options are used deep inside the PostgreSQL code,
+ * this type is declared globally.
+*/
+
+typedef enum ternary
+{
+ TERNARY_FALSE = 0,
+ TERNARY_TRUE = 1,
+ TERNARY_UNSET = -1
+} ternary;
+
/* ----------------------------------------------------------------
* Section 3: standard system types
diff --git a/src/include/utils/rel.h b/src/include/utils/rel.h
index b552359915..7346d618f9 100644
--- a/src/include/utils/rel.h
+++ b/src/include/utils/rel.h
@@ -346,8 +346,7 @@ typedef struct StdRdOptions
bool user_catalog_table; /* use as an additional catalog relation */
int parallel_workers; /* max number of parallel workers */
StdRdOptIndexCleanup vacuum_index_cleanup; /* controls index vacuuming */
- bool vacuum_truncate; /* enables vacuum to truncate a relation */
- bool vacuum_truncate_set; /* whether vacuum_truncate is set */
+ ternary vacuum_truncate; /* enables vacuum to truncate a relation */
/*
* Fraction of pages in a relation that vacuum can eagerly scan and fail
--
2.39.2
--nextPart4155600.3ZeAukHxDK
Content-Disposition: attachment;
filename="v2a-0003-Add-alias-to-be-used-as-unset-state.patch"
Content-Transfer-Encoding: 7Bit
Content-Type: text/x-patch; charset="unicode-2-0-utf-8";
name="v2a-0003-Add-alias-to-be-used-as-unset-state.patch"
^ permalink raw reply [nested|flat] 868+ messages in thread
* [PATCH v2a 2/4] Introduce ternary reloptions
@ 2025-09-04 14:41 Nikolay Shaplov <[email protected]>
0 siblings, 0 replies; 868+ messages in thread
From: Nikolay Shaplov @ 2025-09-04 14:41 UTC (permalink / raw)
Introduce ternary reloption as a replacement for current `vacuum_truncate`
implementation. Remove `vacuum_truncate_set` additional flag and using
`TERNARY_UNSET` value instead.
---
src/backend/access/common/reloptions.c | 129 ++++++++++++++++++++-----
src/backend/commands/vacuum.c | 4 +-
src/include/access/reloptions.h | 26 ++---
src/include/c.h | 16 +++
src/include/utils/rel.h | 3 +-
5 files changed, 135 insertions(+), 43 deletions(-)
diff --git a/src/backend/access/common/reloptions.c b/src/backend/access/common/reloptions.c
index 0af3fea68f..f543f11001 100644
--- a/src/backend/access/common/reloptions.c
+++ b/src/backend/access/common/reloptions.c
@@ -40,9 +40,9 @@
*
* To add an option:
*
- * (i) decide on a type (bool, integer, real, enum, string), name, default
- * value, upper and lower bounds (if applicable); for strings, consider a
- * validation routine.
+ * (i) decide on a type (bool, ternary, integer, real, enum, string), name,
+ * default value, upper and lower bounds (if applicable); for strings,
+ * consider a validation routine.
* (ii) add a record below (or use add_<type>_reloption).
* (iii) add it to the appropriate options struct (perhaps StdRdOptions)
* (iv) add it to the appropriate handling routine (perhaps
@@ -147,15 +147,6 @@ static relopt_bool boolRelOpts[] =
},
false
},
- {
- {
- "vacuum_truncate",
- "Enables vacuum to truncate empty pages at the end of this table",
- RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
- ShareUpdateExclusiveLock
- },
- true
- },
{
{
"deduplicate_items",
@@ -170,6 +161,21 @@ static relopt_bool boolRelOpts[] =
{{NULL}}
};
+static relopt_ternary ternaryRelOpts[] =
+{
+ {
+ {
+ "vacuum_truncate",
+ "Enables vacuum to truncate empty pages at the end of this table",
+ RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
+ ShareUpdateExclusiveLock
+ },
+ TERNARY_UNSET
+ },
+ /* list terminator */
+ {{NULL}}
+};
+
static relopt_int intRelOpts[] =
{
{
@@ -600,6 +606,13 @@ initialize_reloptions(void)
boolRelOpts[i].gen.lockmode));
j++;
}
+ for (i = 0; ternaryRelOpts[i].gen.name; i++)
+ {
+ Assert(DoLockModesConflict(ternaryRelOpts[i].gen.lockmode,
+ ternaryRelOpts[i].gen.lockmode));
+ j++;
+ }
+
for (i = 0; intRelOpts[i].gen.name; i++)
{
Assert(DoLockModesConflict(intRelOpts[i].gen.lockmode,
@@ -640,6 +653,14 @@ initialize_reloptions(void)
j++;
}
+ for (i = 0; ternaryRelOpts[i].gen.name; i++)
+ {
+ relOpts[j] = &ternaryRelOpts[i].gen;
+ relOpts[j]->type = RELOPT_TYPE_TERNARY;
+ relOpts[j]->namelen = strlen(relOpts[j]->name);
+ j++;
+ }
+
for (i = 0; intRelOpts[i].gen.name; i++)
{
relOpts[j] = &intRelOpts[i].gen;
@@ -800,6 +821,9 @@ allocate_reloption(bits32 kinds, int type, const char *name, const char *desc,
case RELOPT_TYPE_BOOL:
size = sizeof(relopt_bool);
break;
+ case RELOPT_TYPE_TERNARY:
+ size = sizeof(relopt_ternary);
+ break;
case RELOPT_TYPE_INT:
size = sizeof(relopt_int);
break;
@@ -883,6 +907,54 @@ add_local_bool_reloption(local_relopts *relopts, const char *name,
add_local_reloption(relopts, (relopt_gen *) newoption, offset);
}
+/*
+ * init_ternary_reloption
+ * Allocate and initialize a new ternary reloption
+ */
+static relopt_ternary *
+init_ternary_reloption(bits32 kinds, const char *name, const char *desc,
+ ternary default_val, LOCKMODE lockmode)
+{
+ relopt_ternary *newoption;
+
+ newoption = (relopt_ternary *) allocate_reloption(kinds,
+ RELOPT_TYPE_TERNARY, name, desc, lockmode);
+ newoption->default_val = default_val;
+
+ return newoption;
+}
+
+/*
+ * add_ternary_reloption
+ * Add a new ternary reloption
+ */
+void
+add_ternary_reloption(bits32 kinds, const char *name, const char *desc,
+ ternary default_val, LOCKMODE lockmode)
+{
+ relopt_ternary *newoption = init_ternary_reloption(kinds, name, desc,
+ default_val, lockmode);
+
+ add_reloption((relopt_gen *) newoption);
+}
+
+/*
+ * add_local_ternary_reloption
+ * Add a new ternary local reloption
+ *
+ * 'offset' is offset of ternary-typed field.
+ */
+void
+add_local_ternary_reloption(local_relopts *relopts, const char *name,
+ const char *desc, ternary default_val,
+ int offset)
+{
+ relopt_ternary *newoption = init_ternary_reloption(RELOPT_KIND_LOCAL,
+ name, desc,
+ default_val, 0);
+
+ add_local_reloption(relopts, (relopt_gen *) newoption, offset);
+}
/*
* init_real_reloption
@@ -1617,6 +1689,18 @@ parse_one_reloption(relopt_value *option, char *text_str, int text_len,
option->gen->name, value)));
}
break;
+ case RELOPT_TYPE_TERNARY:
+ {
+ bool b;
+ parsed = parse_bool(value, &b);
+ option->values.ternary_val = b ? TERNARY_TRUE : TERNARY_FALSE;
+ if (validate && !parsed)
+ ereport(ERROR,
+ (errcode(ERRCODE_INVALID_PARAMETER_VALUE),
+ errmsg("invalid value for ternary option \"%s\": %s",
+ option->gen->name, value)));
+ }
+ break;
case RELOPT_TYPE_INT:
{
relopt_int *optint = (relopt_int *) option->gen;
@@ -1780,17 +1864,6 @@ fillRelOptions(void *rdopts, Size basesize,
char *itempos = ((char *) rdopts) + elems[j].offset;
char *string_val;
- /*
- * If isset_offset is provided, store whether the reloption is
- * set there.
- */
- if (elems[j].isset_offset > 0)
- {
- char *setpos = ((char *) rdopts) + elems[j].isset_offset;
-
- *(bool *) setpos = options[i].isset;
- }
-
switch (options[i].gen->type)
{
case RELOPT_TYPE_BOOL:
@@ -1798,6 +1871,11 @@ fillRelOptions(void *rdopts, Size basesize,
options[i].values.bool_val :
((relopt_bool *) options[i].gen)->default_val;
break;
+ case RELOPT_TYPE_TERNARY:
+ *(ternary *) itempos = options[i].isset ?
+ options[i].values.ternary_val :
+ ((relopt_ternary *) options[i].gen)->default_val;
+ break;
case RELOPT_TYPE_INT:
*(int *) itempos = options[i].isset ?
options[i].values.int_val :
@@ -1912,8 +1990,8 @@ default_reloptions(Datum reloptions, bool validate, relopt_kind kind)
offsetof(StdRdOptions, parallel_workers)},
{"vacuum_index_cleanup", RELOPT_TYPE_ENUM,
offsetof(StdRdOptions, vacuum_index_cleanup)},
- {"vacuum_truncate", RELOPT_TYPE_BOOL,
- offsetof(StdRdOptions, vacuum_truncate), offsetof(StdRdOptions, vacuum_truncate_set)},
+ {"vacuum_truncate", RELOPT_TYPE_TERNARY,
+ offsetof(StdRdOptions, vacuum_truncate)},
{"vacuum_max_eager_freeze_failure_rate", RELOPT_TYPE_REAL,
offsetof(StdRdOptions, vacuum_max_eager_freeze_failure_rate)}
};
@@ -1993,7 +2071,6 @@ build_local_reloptions(local_relopts *relopts, Datum options, bool validate)
elems[i].optname = opt->option->name;
elems[i].opttype = opt->option->type;
elems[i].offset = opt->offset;
- elems[i].isset_offset = 0; /* not supported for local relopts yet */
i++;
}
diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c
index 733ef40ae7..977babff54 100644
--- a/src/backend/commands/vacuum.c
+++ b/src/backend/commands/vacuum.c
@@ -2221,9 +2221,9 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams params,
{
StdRdOptions *opts = (StdRdOptions *) rel->rd_options;
- if (opts && opts->vacuum_truncate_set)
+ if (opts && opts->vacuum_truncate != TERNARY_UNSET)
{
- if (opts->vacuum_truncate)
+ if (opts->vacuum_truncate == TERNARY_TRUE)
params.truncate = VACOPTVALUE_ENABLED;
else
params.truncate = VACOPTVALUE_DISABLED;
diff --git a/src/include/access/reloptions.h b/src/include/access/reloptions.h
index a604a4702c..a436697658 100644
--- a/src/include/access/reloptions.h
+++ b/src/include/access/reloptions.h
@@ -29,6 +29,7 @@
typedef enum relopt_type
{
RELOPT_TYPE_BOOL,
+ RELOPT_TYPE_TERNARY, /* on, off, unset */
RELOPT_TYPE_INT,
RELOPT_TYPE_REAL,
RELOPT_TYPE_ENUM,
@@ -80,6 +81,7 @@ typedef struct relopt_value
union
{
bool bool_val;
+ ternary ternary_val;
int int_val;
double real_val;
int enum_val;
@@ -94,6 +96,12 @@ typedef struct relopt_bool
bool default_val;
} relopt_bool;
+typedef struct relopt_rernary
+{
+ relopt_gen gen;
+ int default_val;
+} relopt_ternary;
+
typedef struct relopt_int
{
relopt_gen gen;
@@ -152,19 +160,6 @@ typedef struct
const char *optname; /* option's name */
relopt_type opttype; /* option's datatype */
int offset; /* offset of field in result struct */
-
- /*
- * isset_offset is an optional offset of a field in the result struct that
- * stores whether the option is explicitly set for the relation or if it
- * just picked up the default value. In most cases, this can be
- * accomplished by giving the reloption a special out-of-range default
- * value (e.g., some integer reloptions use -2), but this isn't always
- * possible. For example, a Boolean reloption cannot be given an
- * out-of-range default, so we need another way to discover the source of
- * its value. This offset is only used if given a value greater than
- * zero.
- */
- int isset_offset;
} relopt_parse_elt;
/* Local reloption definition */
@@ -195,6 +190,8 @@ typedef struct local_relopts
extern relopt_kind add_reloption_kind(void);
extern void add_bool_reloption(bits32 kinds, const char *name, const char *desc,
bool default_val, LOCKMODE lockmode);
+extern void add_ternary_reloption(bits32 kinds, const char *name,
+ const char *desc, int default_val, LOCKMODE lockmode);
extern void add_int_reloption(bits32 kinds, const char *name, const char *desc,
int default_val, int min_val, int max_val,
LOCKMODE lockmode);
@@ -214,6 +211,9 @@ extern void register_reloptions_validator(local_relopts *relopts,
extern void add_local_bool_reloption(local_relopts *relopts, const char *name,
const char *desc, bool default_val,
int offset);
+extern void add_local_ternary_reloption(local_relopts *relopts,
+ const char *name, const char *desc,
+ ternary default_val, int offset);
extern void add_local_int_reloption(local_relopts *relopts, const char *name,
const char *desc, int default_val,
int min_val, int max_val, int offset);
diff --git a/src/include/c.h b/src/include/c.h
index 39022f8a9d..9c59ccccf8 100644
--- a/src/include/c.h
+++ b/src/include/c.h
@@ -488,6 +488,22 @@ typedef void (*pg_funcptr_t) (void);
#include <stdbool.h>
+/*
+ * ternary
+ * Boolean value with an extrea "unset" option
+ *
+ * Ternary data type is used in relation options that can be "true", "false" or
+ * "unset". Since relation options are used deep inside the PostgreSQL code,
+ * this type is declared globally.
+*/
+
+typedef enum ternary
+{
+ TERNARY_FALSE = 0,
+ TERNARY_TRUE = 1,
+ TERNARY_UNSET = -1
+} ternary;
+
/* ----------------------------------------------------------------
* Section 3: standard system types
diff --git a/src/include/utils/rel.h b/src/include/utils/rel.h
index b552359915..7346d618f9 100644
--- a/src/include/utils/rel.h
+++ b/src/include/utils/rel.h
@@ -346,8 +346,7 @@ typedef struct StdRdOptions
bool user_catalog_table; /* use as an additional catalog relation */
int parallel_workers; /* max number of parallel workers */
StdRdOptIndexCleanup vacuum_index_cleanup; /* controls index vacuuming */
- bool vacuum_truncate; /* enables vacuum to truncate a relation */
- bool vacuum_truncate_set; /* whether vacuum_truncate is set */
+ ternary vacuum_truncate; /* enables vacuum to truncate a relation */
/*
* Fraction of pages in a relation that vacuum can eagerly scan and fail
--
2.39.2
--nextPart4155600.3ZeAukHxDK
Content-Disposition: attachment;
filename="v2a-0003-Add-alias-to-be-used-as-unset-state.patch"
Content-Transfer-Encoding: 7Bit
Content-Type: text/x-patch; charset="unicode-2-0-utf-8";
name="v2a-0003-Add-alias-to-be-used-as-unset-state.patch"
^ permalink raw reply [nested|flat] 868+ messages in thread
* [PATCH v2a 2/4] Introduce ternary reloptions
@ 2025-09-04 14:41 Nikolay Shaplov <[email protected]>
0 siblings, 0 replies; 868+ messages in thread
From: Nikolay Shaplov @ 2025-09-04 14:41 UTC (permalink / raw)
Introduce ternary reloption as a replacement for current `vacuum_truncate`
implementation. Remove `vacuum_truncate_set` additional flag and using
`TERNARY_UNSET` value instead.
---
src/backend/access/common/reloptions.c | 129 ++++++++++++++++++++-----
src/backend/commands/vacuum.c | 4 +-
src/include/access/reloptions.h | 26 ++---
src/include/c.h | 16 +++
src/include/utils/rel.h | 3 +-
5 files changed, 135 insertions(+), 43 deletions(-)
diff --git a/src/backend/access/common/reloptions.c b/src/backend/access/common/reloptions.c
index 0af3fea68f..f543f11001 100644
--- a/src/backend/access/common/reloptions.c
+++ b/src/backend/access/common/reloptions.c
@@ -40,9 +40,9 @@
*
* To add an option:
*
- * (i) decide on a type (bool, integer, real, enum, string), name, default
- * value, upper and lower bounds (if applicable); for strings, consider a
- * validation routine.
+ * (i) decide on a type (bool, ternary, integer, real, enum, string), name,
+ * default value, upper and lower bounds (if applicable); for strings,
+ * consider a validation routine.
* (ii) add a record below (or use add_<type>_reloption).
* (iii) add it to the appropriate options struct (perhaps StdRdOptions)
* (iv) add it to the appropriate handling routine (perhaps
@@ -147,15 +147,6 @@ static relopt_bool boolRelOpts[] =
},
false
},
- {
- {
- "vacuum_truncate",
- "Enables vacuum to truncate empty pages at the end of this table",
- RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
- ShareUpdateExclusiveLock
- },
- true
- },
{
{
"deduplicate_items",
@@ -170,6 +161,21 @@ static relopt_bool boolRelOpts[] =
{{NULL}}
};
+static relopt_ternary ternaryRelOpts[] =
+{
+ {
+ {
+ "vacuum_truncate",
+ "Enables vacuum to truncate empty pages at the end of this table",
+ RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
+ ShareUpdateExclusiveLock
+ },
+ TERNARY_UNSET
+ },
+ /* list terminator */
+ {{NULL}}
+};
+
static relopt_int intRelOpts[] =
{
{
@@ -600,6 +606,13 @@ initialize_reloptions(void)
boolRelOpts[i].gen.lockmode));
j++;
}
+ for (i = 0; ternaryRelOpts[i].gen.name; i++)
+ {
+ Assert(DoLockModesConflict(ternaryRelOpts[i].gen.lockmode,
+ ternaryRelOpts[i].gen.lockmode));
+ j++;
+ }
+
for (i = 0; intRelOpts[i].gen.name; i++)
{
Assert(DoLockModesConflict(intRelOpts[i].gen.lockmode,
@@ -640,6 +653,14 @@ initialize_reloptions(void)
j++;
}
+ for (i = 0; ternaryRelOpts[i].gen.name; i++)
+ {
+ relOpts[j] = &ternaryRelOpts[i].gen;
+ relOpts[j]->type = RELOPT_TYPE_TERNARY;
+ relOpts[j]->namelen = strlen(relOpts[j]->name);
+ j++;
+ }
+
for (i = 0; intRelOpts[i].gen.name; i++)
{
relOpts[j] = &intRelOpts[i].gen;
@@ -800,6 +821,9 @@ allocate_reloption(bits32 kinds, int type, const char *name, const char *desc,
case RELOPT_TYPE_BOOL:
size = sizeof(relopt_bool);
break;
+ case RELOPT_TYPE_TERNARY:
+ size = sizeof(relopt_ternary);
+ break;
case RELOPT_TYPE_INT:
size = sizeof(relopt_int);
break;
@@ -883,6 +907,54 @@ add_local_bool_reloption(local_relopts *relopts, const char *name,
add_local_reloption(relopts, (relopt_gen *) newoption, offset);
}
+/*
+ * init_ternary_reloption
+ * Allocate and initialize a new ternary reloption
+ */
+static relopt_ternary *
+init_ternary_reloption(bits32 kinds, const char *name, const char *desc,
+ ternary default_val, LOCKMODE lockmode)
+{
+ relopt_ternary *newoption;
+
+ newoption = (relopt_ternary *) allocate_reloption(kinds,
+ RELOPT_TYPE_TERNARY, name, desc, lockmode);
+ newoption->default_val = default_val;
+
+ return newoption;
+}
+
+/*
+ * add_ternary_reloption
+ * Add a new ternary reloption
+ */
+void
+add_ternary_reloption(bits32 kinds, const char *name, const char *desc,
+ ternary default_val, LOCKMODE lockmode)
+{
+ relopt_ternary *newoption = init_ternary_reloption(kinds, name, desc,
+ default_val, lockmode);
+
+ add_reloption((relopt_gen *) newoption);
+}
+
+/*
+ * add_local_ternary_reloption
+ * Add a new ternary local reloption
+ *
+ * 'offset' is offset of ternary-typed field.
+ */
+void
+add_local_ternary_reloption(local_relopts *relopts, const char *name,
+ const char *desc, ternary default_val,
+ int offset)
+{
+ relopt_ternary *newoption = init_ternary_reloption(RELOPT_KIND_LOCAL,
+ name, desc,
+ default_val, 0);
+
+ add_local_reloption(relopts, (relopt_gen *) newoption, offset);
+}
/*
* init_real_reloption
@@ -1617,6 +1689,18 @@ parse_one_reloption(relopt_value *option, char *text_str, int text_len,
option->gen->name, value)));
}
break;
+ case RELOPT_TYPE_TERNARY:
+ {
+ bool b;
+ parsed = parse_bool(value, &b);
+ option->values.ternary_val = b ? TERNARY_TRUE : TERNARY_FALSE;
+ if (validate && !parsed)
+ ereport(ERROR,
+ (errcode(ERRCODE_INVALID_PARAMETER_VALUE),
+ errmsg("invalid value for ternary option \"%s\": %s",
+ option->gen->name, value)));
+ }
+ break;
case RELOPT_TYPE_INT:
{
relopt_int *optint = (relopt_int *) option->gen;
@@ -1780,17 +1864,6 @@ fillRelOptions(void *rdopts, Size basesize,
char *itempos = ((char *) rdopts) + elems[j].offset;
char *string_val;
- /*
- * If isset_offset is provided, store whether the reloption is
- * set there.
- */
- if (elems[j].isset_offset > 0)
- {
- char *setpos = ((char *) rdopts) + elems[j].isset_offset;
-
- *(bool *) setpos = options[i].isset;
- }
-
switch (options[i].gen->type)
{
case RELOPT_TYPE_BOOL:
@@ -1798,6 +1871,11 @@ fillRelOptions(void *rdopts, Size basesize,
options[i].values.bool_val :
((relopt_bool *) options[i].gen)->default_val;
break;
+ case RELOPT_TYPE_TERNARY:
+ *(ternary *) itempos = options[i].isset ?
+ options[i].values.ternary_val :
+ ((relopt_ternary *) options[i].gen)->default_val;
+ break;
case RELOPT_TYPE_INT:
*(int *) itempos = options[i].isset ?
options[i].values.int_val :
@@ -1912,8 +1990,8 @@ default_reloptions(Datum reloptions, bool validate, relopt_kind kind)
offsetof(StdRdOptions, parallel_workers)},
{"vacuum_index_cleanup", RELOPT_TYPE_ENUM,
offsetof(StdRdOptions, vacuum_index_cleanup)},
- {"vacuum_truncate", RELOPT_TYPE_BOOL,
- offsetof(StdRdOptions, vacuum_truncate), offsetof(StdRdOptions, vacuum_truncate_set)},
+ {"vacuum_truncate", RELOPT_TYPE_TERNARY,
+ offsetof(StdRdOptions, vacuum_truncate)},
{"vacuum_max_eager_freeze_failure_rate", RELOPT_TYPE_REAL,
offsetof(StdRdOptions, vacuum_max_eager_freeze_failure_rate)}
};
@@ -1993,7 +2071,6 @@ build_local_reloptions(local_relopts *relopts, Datum options, bool validate)
elems[i].optname = opt->option->name;
elems[i].opttype = opt->option->type;
elems[i].offset = opt->offset;
- elems[i].isset_offset = 0; /* not supported for local relopts yet */
i++;
}
diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c
index 733ef40ae7..977babff54 100644
--- a/src/backend/commands/vacuum.c
+++ b/src/backend/commands/vacuum.c
@@ -2221,9 +2221,9 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams params,
{
StdRdOptions *opts = (StdRdOptions *) rel->rd_options;
- if (opts && opts->vacuum_truncate_set)
+ if (opts && opts->vacuum_truncate != TERNARY_UNSET)
{
- if (opts->vacuum_truncate)
+ if (opts->vacuum_truncate == TERNARY_TRUE)
params.truncate = VACOPTVALUE_ENABLED;
else
params.truncate = VACOPTVALUE_DISABLED;
diff --git a/src/include/access/reloptions.h b/src/include/access/reloptions.h
index a604a4702c..a436697658 100644
--- a/src/include/access/reloptions.h
+++ b/src/include/access/reloptions.h
@@ -29,6 +29,7 @@
typedef enum relopt_type
{
RELOPT_TYPE_BOOL,
+ RELOPT_TYPE_TERNARY, /* on, off, unset */
RELOPT_TYPE_INT,
RELOPT_TYPE_REAL,
RELOPT_TYPE_ENUM,
@@ -80,6 +81,7 @@ typedef struct relopt_value
union
{
bool bool_val;
+ ternary ternary_val;
int int_val;
double real_val;
int enum_val;
@@ -94,6 +96,12 @@ typedef struct relopt_bool
bool default_val;
} relopt_bool;
+typedef struct relopt_rernary
+{
+ relopt_gen gen;
+ int default_val;
+} relopt_ternary;
+
typedef struct relopt_int
{
relopt_gen gen;
@@ -152,19 +160,6 @@ typedef struct
const char *optname; /* option's name */
relopt_type opttype; /* option's datatype */
int offset; /* offset of field in result struct */
-
- /*
- * isset_offset is an optional offset of a field in the result struct that
- * stores whether the option is explicitly set for the relation or if it
- * just picked up the default value. In most cases, this can be
- * accomplished by giving the reloption a special out-of-range default
- * value (e.g., some integer reloptions use -2), but this isn't always
- * possible. For example, a Boolean reloption cannot be given an
- * out-of-range default, so we need another way to discover the source of
- * its value. This offset is only used if given a value greater than
- * zero.
- */
- int isset_offset;
} relopt_parse_elt;
/* Local reloption definition */
@@ -195,6 +190,8 @@ typedef struct local_relopts
extern relopt_kind add_reloption_kind(void);
extern void add_bool_reloption(bits32 kinds, const char *name, const char *desc,
bool default_val, LOCKMODE lockmode);
+extern void add_ternary_reloption(bits32 kinds, const char *name,
+ const char *desc, int default_val, LOCKMODE lockmode);
extern void add_int_reloption(bits32 kinds, const char *name, const char *desc,
int default_val, int min_val, int max_val,
LOCKMODE lockmode);
@@ -214,6 +211,9 @@ extern void register_reloptions_validator(local_relopts *relopts,
extern void add_local_bool_reloption(local_relopts *relopts, const char *name,
const char *desc, bool default_val,
int offset);
+extern void add_local_ternary_reloption(local_relopts *relopts,
+ const char *name, const char *desc,
+ ternary default_val, int offset);
extern void add_local_int_reloption(local_relopts *relopts, const char *name,
const char *desc, int default_val,
int min_val, int max_val, int offset);
diff --git a/src/include/c.h b/src/include/c.h
index 39022f8a9d..9c59ccccf8 100644
--- a/src/include/c.h
+++ b/src/include/c.h
@@ -488,6 +488,22 @@ typedef void (*pg_funcptr_t) (void);
#include <stdbool.h>
+/*
+ * ternary
+ * Boolean value with an extrea "unset" option
+ *
+ * Ternary data type is used in relation options that can be "true", "false" or
+ * "unset". Since relation options are used deep inside the PostgreSQL code,
+ * this type is declared globally.
+*/
+
+typedef enum ternary
+{
+ TERNARY_FALSE = 0,
+ TERNARY_TRUE = 1,
+ TERNARY_UNSET = -1
+} ternary;
+
/* ----------------------------------------------------------------
* Section 3: standard system types
diff --git a/src/include/utils/rel.h b/src/include/utils/rel.h
index b552359915..7346d618f9 100644
--- a/src/include/utils/rel.h
+++ b/src/include/utils/rel.h
@@ -346,8 +346,7 @@ typedef struct StdRdOptions
bool user_catalog_table; /* use as an additional catalog relation */
int parallel_workers; /* max number of parallel workers */
StdRdOptIndexCleanup vacuum_index_cleanup; /* controls index vacuuming */
- bool vacuum_truncate; /* enables vacuum to truncate a relation */
- bool vacuum_truncate_set; /* whether vacuum_truncate is set */
+ ternary vacuum_truncate; /* enables vacuum to truncate a relation */
/*
* Fraction of pages in a relation that vacuum can eagerly scan and fail
--
2.39.2
--nextPart4155600.3ZeAukHxDK
Content-Disposition: attachment;
filename="v2a-0003-Add-alias-to-be-used-as-unset-state.patch"
Content-Transfer-Encoding: 7Bit
Content-Type: text/x-patch; charset="unicode-2-0-utf-8";
name="v2a-0003-Add-alias-to-be-used-as-unset-state.patch"
^ permalink raw reply [nested|flat] 868+ messages in thread
* [PATCH v2a 2/4] Introduce ternary reloptions
@ 2025-09-04 14:41 Nikolay Shaplov <[email protected]>
0 siblings, 0 replies; 868+ messages in thread
From: Nikolay Shaplov @ 2025-09-04 14:41 UTC (permalink / raw)
Introduce ternary reloption as a replacement for current `vacuum_truncate`
implementation. Remove `vacuum_truncate_set` additional flag and using
`TERNARY_UNSET` value instead.
---
src/backend/access/common/reloptions.c | 129 ++++++++++++++++++++-----
src/backend/commands/vacuum.c | 4 +-
src/include/access/reloptions.h | 26 ++---
src/include/c.h | 16 +++
src/include/utils/rel.h | 3 +-
5 files changed, 135 insertions(+), 43 deletions(-)
diff --git a/src/backend/access/common/reloptions.c b/src/backend/access/common/reloptions.c
index 0af3fea68f..f543f11001 100644
--- a/src/backend/access/common/reloptions.c
+++ b/src/backend/access/common/reloptions.c
@@ -40,9 +40,9 @@
*
* To add an option:
*
- * (i) decide on a type (bool, integer, real, enum, string), name, default
- * value, upper and lower bounds (if applicable); for strings, consider a
- * validation routine.
+ * (i) decide on a type (bool, ternary, integer, real, enum, string), name,
+ * default value, upper and lower bounds (if applicable); for strings,
+ * consider a validation routine.
* (ii) add a record below (or use add_<type>_reloption).
* (iii) add it to the appropriate options struct (perhaps StdRdOptions)
* (iv) add it to the appropriate handling routine (perhaps
@@ -147,15 +147,6 @@ static relopt_bool boolRelOpts[] =
},
false
},
- {
- {
- "vacuum_truncate",
- "Enables vacuum to truncate empty pages at the end of this table",
- RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
- ShareUpdateExclusiveLock
- },
- true
- },
{
{
"deduplicate_items",
@@ -170,6 +161,21 @@ static relopt_bool boolRelOpts[] =
{{NULL}}
};
+static relopt_ternary ternaryRelOpts[] =
+{
+ {
+ {
+ "vacuum_truncate",
+ "Enables vacuum to truncate empty pages at the end of this table",
+ RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
+ ShareUpdateExclusiveLock
+ },
+ TERNARY_UNSET
+ },
+ /* list terminator */
+ {{NULL}}
+};
+
static relopt_int intRelOpts[] =
{
{
@@ -600,6 +606,13 @@ initialize_reloptions(void)
boolRelOpts[i].gen.lockmode));
j++;
}
+ for (i = 0; ternaryRelOpts[i].gen.name; i++)
+ {
+ Assert(DoLockModesConflict(ternaryRelOpts[i].gen.lockmode,
+ ternaryRelOpts[i].gen.lockmode));
+ j++;
+ }
+
for (i = 0; intRelOpts[i].gen.name; i++)
{
Assert(DoLockModesConflict(intRelOpts[i].gen.lockmode,
@@ -640,6 +653,14 @@ initialize_reloptions(void)
j++;
}
+ for (i = 0; ternaryRelOpts[i].gen.name; i++)
+ {
+ relOpts[j] = &ternaryRelOpts[i].gen;
+ relOpts[j]->type = RELOPT_TYPE_TERNARY;
+ relOpts[j]->namelen = strlen(relOpts[j]->name);
+ j++;
+ }
+
for (i = 0; intRelOpts[i].gen.name; i++)
{
relOpts[j] = &intRelOpts[i].gen;
@@ -800,6 +821,9 @@ allocate_reloption(bits32 kinds, int type, const char *name, const char *desc,
case RELOPT_TYPE_BOOL:
size = sizeof(relopt_bool);
break;
+ case RELOPT_TYPE_TERNARY:
+ size = sizeof(relopt_ternary);
+ break;
case RELOPT_TYPE_INT:
size = sizeof(relopt_int);
break;
@@ -883,6 +907,54 @@ add_local_bool_reloption(local_relopts *relopts, const char *name,
add_local_reloption(relopts, (relopt_gen *) newoption, offset);
}
+/*
+ * init_ternary_reloption
+ * Allocate and initialize a new ternary reloption
+ */
+static relopt_ternary *
+init_ternary_reloption(bits32 kinds, const char *name, const char *desc,
+ ternary default_val, LOCKMODE lockmode)
+{
+ relopt_ternary *newoption;
+
+ newoption = (relopt_ternary *) allocate_reloption(kinds,
+ RELOPT_TYPE_TERNARY, name, desc, lockmode);
+ newoption->default_val = default_val;
+
+ return newoption;
+}
+
+/*
+ * add_ternary_reloption
+ * Add a new ternary reloption
+ */
+void
+add_ternary_reloption(bits32 kinds, const char *name, const char *desc,
+ ternary default_val, LOCKMODE lockmode)
+{
+ relopt_ternary *newoption = init_ternary_reloption(kinds, name, desc,
+ default_val, lockmode);
+
+ add_reloption((relopt_gen *) newoption);
+}
+
+/*
+ * add_local_ternary_reloption
+ * Add a new ternary local reloption
+ *
+ * 'offset' is offset of ternary-typed field.
+ */
+void
+add_local_ternary_reloption(local_relopts *relopts, const char *name,
+ const char *desc, ternary default_val,
+ int offset)
+{
+ relopt_ternary *newoption = init_ternary_reloption(RELOPT_KIND_LOCAL,
+ name, desc,
+ default_val, 0);
+
+ add_local_reloption(relopts, (relopt_gen *) newoption, offset);
+}
/*
* init_real_reloption
@@ -1617,6 +1689,18 @@ parse_one_reloption(relopt_value *option, char *text_str, int text_len,
option->gen->name, value)));
}
break;
+ case RELOPT_TYPE_TERNARY:
+ {
+ bool b;
+ parsed = parse_bool(value, &b);
+ option->values.ternary_val = b ? TERNARY_TRUE : TERNARY_FALSE;
+ if (validate && !parsed)
+ ereport(ERROR,
+ (errcode(ERRCODE_INVALID_PARAMETER_VALUE),
+ errmsg("invalid value for ternary option \"%s\": %s",
+ option->gen->name, value)));
+ }
+ break;
case RELOPT_TYPE_INT:
{
relopt_int *optint = (relopt_int *) option->gen;
@@ -1780,17 +1864,6 @@ fillRelOptions(void *rdopts, Size basesize,
char *itempos = ((char *) rdopts) + elems[j].offset;
char *string_val;
- /*
- * If isset_offset is provided, store whether the reloption is
- * set there.
- */
- if (elems[j].isset_offset > 0)
- {
- char *setpos = ((char *) rdopts) + elems[j].isset_offset;
-
- *(bool *) setpos = options[i].isset;
- }
-
switch (options[i].gen->type)
{
case RELOPT_TYPE_BOOL:
@@ -1798,6 +1871,11 @@ fillRelOptions(void *rdopts, Size basesize,
options[i].values.bool_val :
((relopt_bool *) options[i].gen)->default_val;
break;
+ case RELOPT_TYPE_TERNARY:
+ *(ternary *) itempos = options[i].isset ?
+ options[i].values.ternary_val :
+ ((relopt_ternary *) options[i].gen)->default_val;
+ break;
case RELOPT_TYPE_INT:
*(int *) itempos = options[i].isset ?
options[i].values.int_val :
@@ -1912,8 +1990,8 @@ default_reloptions(Datum reloptions, bool validate, relopt_kind kind)
offsetof(StdRdOptions, parallel_workers)},
{"vacuum_index_cleanup", RELOPT_TYPE_ENUM,
offsetof(StdRdOptions, vacuum_index_cleanup)},
- {"vacuum_truncate", RELOPT_TYPE_BOOL,
- offsetof(StdRdOptions, vacuum_truncate), offsetof(StdRdOptions, vacuum_truncate_set)},
+ {"vacuum_truncate", RELOPT_TYPE_TERNARY,
+ offsetof(StdRdOptions, vacuum_truncate)},
{"vacuum_max_eager_freeze_failure_rate", RELOPT_TYPE_REAL,
offsetof(StdRdOptions, vacuum_max_eager_freeze_failure_rate)}
};
@@ -1993,7 +2071,6 @@ build_local_reloptions(local_relopts *relopts, Datum options, bool validate)
elems[i].optname = opt->option->name;
elems[i].opttype = opt->option->type;
elems[i].offset = opt->offset;
- elems[i].isset_offset = 0; /* not supported for local relopts yet */
i++;
}
diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c
index 733ef40ae7..977babff54 100644
--- a/src/backend/commands/vacuum.c
+++ b/src/backend/commands/vacuum.c
@@ -2221,9 +2221,9 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams params,
{
StdRdOptions *opts = (StdRdOptions *) rel->rd_options;
- if (opts && opts->vacuum_truncate_set)
+ if (opts && opts->vacuum_truncate != TERNARY_UNSET)
{
- if (opts->vacuum_truncate)
+ if (opts->vacuum_truncate == TERNARY_TRUE)
params.truncate = VACOPTVALUE_ENABLED;
else
params.truncate = VACOPTVALUE_DISABLED;
diff --git a/src/include/access/reloptions.h b/src/include/access/reloptions.h
index a604a4702c..a436697658 100644
--- a/src/include/access/reloptions.h
+++ b/src/include/access/reloptions.h
@@ -29,6 +29,7 @@
typedef enum relopt_type
{
RELOPT_TYPE_BOOL,
+ RELOPT_TYPE_TERNARY, /* on, off, unset */
RELOPT_TYPE_INT,
RELOPT_TYPE_REAL,
RELOPT_TYPE_ENUM,
@@ -80,6 +81,7 @@ typedef struct relopt_value
union
{
bool bool_val;
+ ternary ternary_val;
int int_val;
double real_val;
int enum_val;
@@ -94,6 +96,12 @@ typedef struct relopt_bool
bool default_val;
} relopt_bool;
+typedef struct relopt_rernary
+{
+ relopt_gen gen;
+ int default_val;
+} relopt_ternary;
+
typedef struct relopt_int
{
relopt_gen gen;
@@ -152,19 +160,6 @@ typedef struct
const char *optname; /* option's name */
relopt_type opttype; /* option's datatype */
int offset; /* offset of field in result struct */
-
- /*
- * isset_offset is an optional offset of a field in the result struct that
- * stores whether the option is explicitly set for the relation or if it
- * just picked up the default value. In most cases, this can be
- * accomplished by giving the reloption a special out-of-range default
- * value (e.g., some integer reloptions use -2), but this isn't always
- * possible. For example, a Boolean reloption cannot be given an
- * out-of-range default, so we need another way to discover the source of
- * its value. This offset is only used if given a value greater than
- * zero.
- */
- int isset_offset;
} relopt_parse_elt;
/* Local reloption definition */
@@ -195,6 +190,8 @@ typedef struct local_relopts
extern relopt_kind add_reloption_kind(void);
extern void add_bool_reloption(bits32 kinds, const char *name, const char *desc,
bool default_val, LOCKMODE lockmode);
+extern void add_ternary_reloption(bits32 kinds, const char *name,
+ const char *desc, int default_val, LOCKMODE lockmode);
extern void add_int_reloption(bits32 kinds, const char *name, const char *desc,
int default_val, int min_val, int max_val,
LOCKMODE lockmode);
@@ -214,6 +211,9 @@ extern void register_reloptions_validator(local_relopts *relopts,
extern void add_local_bool_reloption(local_relopts *relopts, const char *name,
const char *desc, bool default_val,
int offset);
+extern void add_local_ternary_reloption(local_relopts *relopts,
+ const char *name, const char *desc,
+ ternary default_val, int offset);
extern void add_local_int_reloption(local_relopts *relopts, const char *name,
const char *desc, int default_val,
int min_val, int max_val, int offset);
diff --git a/src/include/c.h b/src/include/c.h
index 39022f8a9d..9c59ccccf8 100644
--- a/src/include/c.h
+++ b/src/include/c.h
@@ -488,6 +488,22 @@ typedef void (*pg_funcptr_t) (void);
#include <stdbool.h>
+/*
+ * ternary
+ * Boolean value with an extrea "unset" option
+ *
+ * Ternary data type is used in relation options that can be "true", "false" or
+ * "unset". Since relation options are used deep inside the PostgreSQL code,
+ * this type is declared globally.
+*/
+
+typedef enum ternary
+{
+ TERNARY_FALSE = 0,
+ TERNARY_TRUE = 1,
+ TERNARY_UNSET = -1
+} ternary;
+
/* ----------------------------------------------------------------
* Section 3: standard system types
diff --git a/src/include/utils/rel.h b/src/include/utils/rel.h
index b552359915..7346d618f9 100644
--- a/src/include/utils/rel.h
+++ b/src/include/utils/rel.h
@@ -346,8 +346,7 @@ typedef struct StdRdOptions
bool user_catalog_table; /* use as an additional catalog relation */
int parallel_workers; /* max number of parallel workers */
StdRdOptIndexCleanup vacuum_index_cleanup; /* controls index vacuuming */
- bool vacuum_truncate; /* enables vacuum to truncate a relation */
- bool vacuum_truncate_set; /* whether vacuum_truncate is set */
+ ternary vacuum_truncate; /* enables vacuum to truncate a relation */
/*
* Fraction of pages in a relation that vacuum can eagerly scan and fail
--
2.39.2
--nextPart4155600.3ZeAukHxDK
Content-Disposition: attachment;
filename="v2a-0003-Add-alias-to-be-used-as-unset-state.patch"
Content-Transfer-Encoding: 7Bit
Content-Type: text/x-patch; charset="unicode-2-0-utf-8";
name="v2a-0003-Add-alias-to-be-used-as-unset-state.patch"
^ permalink raw reply [nested|flat] 868+ messages in thread
* [PATCH v2 2/4] Introduce ternary reloptions
@ 2025-09-04 14:41 Nikolay Shaplov <[email protected]>
0 siblings, 0 replies; 868+ messages in thread
From: Nikolay Shaplov @ 2025-09-04 14:41 UTC (permalink / raw)
Introduce ternary reloption as a replacement for current `vacuum_truncate`
implementation. Remove `vacuum_truncate_set` additional flag and using
`TERNARY_UNSET` value instead.
---
src/backend/access/common/reloptions.c | 129 ++++++++++++++++++++-----
src/backend/commands/vacuum.c | 4 +-
src/include/access/reloptions.h | 26 ++---
src/include/c.h | 16 +++
src/include/utils/rel.h | 3 +-
5 files changed, 135 insertions(+), 43 deletions(-)
diff --git a/src/backend/access/common/reloptions.c b/src/backend/access/common/reloptions.c
index 0af3fea68f..f543f11001 100644
--- a/src/backend/access/common/reloptions.c
+++ b/src/backend/access/common/reloptions.c
@@ -40,9 +40,9 @@
*
* To add an option:
*
- * (i) decide on a type (bool, integer, real, enum, string), name, default
- * value, upper and lower bounds (if applicable); for strings, consider a
- * validation routine.
+ * (i) decide on a type (bool, ternary, integer, real, enum, string), name,
+ * default value, upper and lower bounds (if applicable); for strings,
+ * consider a validation routine.
* (ii) add a record below (or use add_<type>_reloption).
* (iii) add it to the appropriate options struct (perhaps StdRdOptions)
* (iv) add it to the appropriate handling routine (perhaps
@@ -147,15 +147,6 @@ static relopt_bool boolRelOpts[] =
},
false
},
- {
- {
- "vacuum_truncate",
- "Enables vacuum to truncate empty pages at the end of this table",
- RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
- ShareUpdateExclusiveLock
- },
- true
- },
{
{
"deduplicate_items",
@@ -170,6 +161,21 @@ static relopt_bool boolRelOpts[] =
{{NULL}}
};
+static relopt_ternary ternaryRelOpts[] =
+{
+ {
+ {
+ "vacuum_truncate",
+ "Enables vacuum to truncate empty pages at the end of this table",
+ RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
+ ShareUpdateExclusiveLock
+ },
+ TERNARY_UNSET
+ },
+ /* list terminator */
+ {{NULL}}
+};
+
static relopt_int intRelOpts[] =
{
{
@@ -600,6 +606,13 @@ initialize_reloptions(void)
boolRelOpts[i].gen.lockmode));
j++;
}
+ for (i = 0; ternaryRelOpts[i].gen.name; i++)
+ {
+ Assert(DoLockModesConflict(ternaryRelOpts[i].gen.lockmode,
+ ternaryRelOpts[i].gen.lockmode));
+ j++;
+ }
+
for (i = 0; intRelOpts[i].gen.name; i++)
{
Assert(DoLockModesConflict(intRelOpts[i].gen.lockmode,
@@ -640,6 +653,14 @@ initialize_reloptions(void)
j++;
}
+ for (i = 0; ternaryRelOpts[i].gen.name; i++)
+ {
+ relOpts[j] = &ternaryRelOpts[i].gen;
+ relOpts[j]->type = RELOPT_TYPE_TERNARY;
+ relOpts[j]->namelen = strlen(relOpts[j]->name);
+ j++;
+ }
+
for (i = 0; intRelOpts[i].gen.name; i++)
{
relOpts[j] = &intRelOpts[i].gen;
@@ -800,6 +821,9 @@ allocate_reloption(bits32 kinds, int type, const char *name, const char *desc,
case RELOPT_TYPE_BOOL:
size = sizeof(relopt_bool);
break;
+ case RELOPT_TYPE_TERNARY:
+ size = sizeof(relopt_ternary);
+ break;
case RELOPT_TYPE_INT:
size = sizeof(relopt_int);
break;
@@ -883,6 +907,54 @@ add_local_bool_reloption(local_relopts *relopts, const char *name,
add_local_reloption(relopts, (relopt_gen *) newoption, offset);
}
+/*
+ * init_ternary_reloption
+ * Allocate and initialize a new ternary reloption
+ */
+static relopt_ternary *
+init_ternary_reloption(bits32 kinds, const char *name, const char *desc,
+ ternary default_val, LOCKMODE lockmode)
+{
+ relopt_ternary *newoption;
+
+ newoption = (relopt_ternary *) allocate_reloption(kinds,
+ RELOPT_TYPE_TERNARY, name, desc, lockmode);
+ newoption->default_val = default_val;
+
+ return newoption;
+}
+
+/*
+ * add_ternary_reloption
+ * Add a new ternary reloption
+ */
+void
+add_ternary_reloption(bits32 kinds, const char *name, const char *desc,
+ ternary default_val, LOCKMODE lockmode)
+{
+ relopt_ternary *newoption = init_ternary_reloption(kinds, name, desc,
+ default_val, lockmode);
+
+ add_reloption((relopt_gen *) newoption);
+}
+
+/*
+ * add_local_ternary_reloption
+ * Add a new ternary local reloption
+ *
+ * 'offset' is offset of ternary-typed field.
+ */
+void
+add_local_ternary_reloption(local_relopts *relopts, const char *name,
+ const char *desc, ternary default_val,
+ int offset)
+{
+ relopt_ternary *newoption = init_ternary_reloption(RELOPT_KIND_LOCAL,
+ name, desc,
+ default_val, 0);
+
+ add_local_reloption(relopts, (relopt_gen *) newoption, offset);
+}
/*
* init_real_reloption
@@ -1617,6 +1689,18 @@ parse_one_reloption(relopt_value *option, char *text_str, int text_len,
option->gen->name, value)));
}
break;
+ case RELOPT_TYPE_TERNARY:
+ {
+ bool b;
+ parsed = parse_bool(value, &b);
+ option->values.ternary_val = b ? TERNARY_TRUE : TERNARY_FALSE;
+ if (validate && !parsed)
+ ereport(ERROR,
+ (errcode(ERRCODE_INVALID_PARAMETER_VALUE),
+ errmsg("invalid value for ternary option \"%s\": %s",
+ option->gen->name, value)));
+ }
+ break;
case RELOPT_TYPE_INT:
{
relopt_int *optint = (relopt_int *) option->gen;
@@ -1780,17 +1864,6 @@ fillRelOptions(void *rdopts, Size basesize,
char *itempos = ((char *) rdopts) + elems[j].offset;
char *string_val;
- /*
- * If isset_offset is provided, store whether the reloption is
- * set there.
- */
- if (elems[j].isset_offset > 0)
- {
- char *setpos = ((char *) rdopts) + elems[j].isset_offset;
-
- *(bool *) setpos = options[i].isset;
- }
-
switch (options[i].gen->type)
{
case RELOPT_TYPE_BOOL:
@@ -1798,6 +1871,11 @@ fillRelOptions(void *rdopts, Size basesize,
options[i].values.bool_val :
((relopt_bool *) options[i].gen)->default_val;
break;
+ case RELOPT_TYPE_TERNARY:
+ *(ternary *) itempos = options[i].isset ?
+ options[i].values.ternary_val :
+ ((relopt_ternary *) options[i].gen)->default_val;
+ break;
case RELOPT_TYPE_INT:
*(int *) itempos = options[i].isset ?
options[i].values.int_val :
@@ -1912,8 +1990,8 @@ default_reloptions(Datum reloptions, bool validate, relopt_kind kind)
offsetof(StdRdOptions, parallel_workers)},
{"vacuum_index_cleanup", RELOPT_TYPE_ENUM,
offsetof(StdRdOptions, vacuum_index_cleanup)},
- {"vacuum_truncate", RELOPT_TYPE_BOOL,
- offsetof(StdRdOptions, vacuum_truncate), offsetof(StdRdOptions, vacuum_truncate_set)},
+ {"vacuum_truncate", RELOPT_TYPE_TERNARY,
+ offsetof(StdRdOptions, vacuum_truncate)},
{"vacuum_max_eager_freeze_failure_rate", RELOPT_TYPE_REAL,
offsetof(StdRdOptions, vacuum_max_eager_freeze_failure_rate)}
};
@@ -1993,7 +2071,6 @@ build_local_reloptions(local_relopts *relopts, Datum options, bool validate)
elems[i].optname = opt->option->name;
elems[i].opttype = opt->option->type;
elems[i].offset = opt->offset;
- elems[i].isset_offset = 0; /* not supported for local relopts yet */
i++;
}
diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c
index 733ef40ae7..977babff54 100644
--- a/src/backend/commands/vacuum.c
+++ b/src/backend/commands/vacuum.c
@@ -2221,9 +2221,9 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams params,
{
StdRdOptions *opts = (StdRdOptions *) rel->rd_options;
- if (opts && opts->vacuum_truncate_set)
+ if (opts && opts->vacuum_truncate != TERNARY_UNSET)
{
- if (opts->vacuum_truncate)
+ if (opts->vacuum_truncate == TERNARY_TRUE)
params.truncate = VACOPTVALUE_ENABLED;
else
params.truncate = VACOPTVALUE_DISABLED;
diff --git a/src/include/access/reloptions.h b/src/include/access/reloptions.h
index a604a4702c..a436697658 100644
--- a/src/include/access/reloptions.h
+++ b/src/include/access/reloptions.h
@@ -29,6 +29,7 @@
typedef enum relopt_type
{
RELOPT_TYPE_BOOL,
+ RELOPT_TYPE_TERNARY, /* on, off, unset */
RELOPT_TYPE_INT,
RELOPT_TYPE_REAL,
RELOPT_TYPE_ENUM,
@@ -80,6 +81,7 @@ typedef struct relopt_value
union
{
bool bool_val;
+ ternary ternary_val;
int int_val;
double real_val;
int enum_val;
@@ -94,6 +96,12 @@ typedef struct relopt_bool
bool default_val;
} relopt_bool;
+typedef struct relopt_rernary
+{
+ relopt_gen gen;
+ int default_val;
+} relopt_ternary;
+
typedef struct relopt_int
{
relopt_gen gen;
@@ -152,19 +160,6 @@ typedef struct
const char *optname; /* option's name */
relopt_type opttype; /* option's datatype */
int offset; /* offset of field in result struct */
-
- /*
- * isset_offset is an optional offset of a field in the result struct that
- * stores whether the option is explicitly set for the relation or if it
- * just picked up the default value. In most cases, this can be
- * accomplished by giving the reloption a special out-of-range default
- * value (e.g., some integer reloptions use -2), but this isn't always
- * possible. For example, a Boolean reloption cannot be given an
- * out-of-range default, so we need another way to discover the source of
- * its value. This offset is only used if given a value greater than
- * zero.
- */
- int isset_offset;
} relopt_parse_elt;
/* Local reloption definition */
@@ -195,6 +190,8 @@ typedef struct local_relopts
extern relopt_kind add_reloption_kind(void);
extern void add_bool_reloption(bits32 kinds, const char *name, const char *desc,
bool default_val, LOCKMODE lockmode);
+extern void add_ternary_reloption(bits32 kinds, const char *name,
+ const char *desc, int default_val, LOCKMODE lockmode);
extern void add_int_reloption(bits32 kinds, const char *name, const char *desc,
int default_val, int min_val, int max_val,
LOCKMODE lockmode);
@@ -214,6 +211,9 @@ extern void register_reloptions_validator(local_relopts *relopts,
extern void add_local_bool_reloption(local_relopts *relopts, const char *name,
const char *desc, bool default_val,
int offset);
+extern void add_local_ternary_reloption(local_relopts *relopts,
+ const char *name, const char *desc,
+ ternary default_val, int offset);
extern void add_local_int_reloption(local_relopts *relopts, const char *name,
const char *desc, int default_val,
int min_val, int max_val, int offset);
diff --git a/src/include/c.h b/src/include/c.h
index 39022f8a9d..9c59ccccf8 100644
--- a/src/include/c.h
+++ b/src/include/c.h
@@ -488,6 +488,22 @@ typedef void (*pg_funcptr_t) (void);
#include <stdbool.h>
+/*
+ * ternary
+ * Boolean value with an extrea "unset" option
+ *
+ * Ternary data type is used in relation options that can be "true", "false" or
+ * "unset". Since relation options are used deep inside the PostgreSQL code,
+ * this type is declared globally.
+*/
+
+typedef enum ternary
+{
+ TERNARY_FALSE = 0,
+ TERNARY_TRUE = 1,
+ TERNARY_UNSET = -1
+} ternary;
+
/* ----------------------------------------------------------------
* Section 3: standard system types
diff --git a/src/include/utils/rel.h b/src/include/utils/rel.h
index b552359915..7346d618f9 100644
--- a/src/include/utils/rel.h
+++ b/src/include/utils/rel.h
@@ -346,8 +346,7 @@ typedef struct StdRdOptions
bool user_catalog_table; /* use as an additional catalog relation */
int parallel_workers; /* max number of parallel workers */
StdRdOptIndexCleanup vacuum_index_cleanup; /* controls index vacuuming */
- bool vacuum_truncate; /* enables vacuum to truncate a relation */
- bool vacuum_truncate_set; /* whether vacuum_truncate is set */
+ ternary vacuum_truncate; /* enables vacuum to truncate a relation */
/*
* Fraction of pages in a relation that vacuum can eagerly scan and fail
--
2.39.2
--nextPart5129932.5fSG56mABF
Content-Disposition: attachment;
filename="v2-0003-Add-alias-to-be-used-as-unset-state.patch"
Content-Transfer-Encoding: 7Bit
Content-Type: text/x-patch; charset="unicode-2-0-utf-8";
name="v2-0003-Add-alias-to-be-used-as-unset-state.patch"
^ permalink raw reply [nested|flat] 868+ messages in thread
* [PATCH v2 2/4] Introduce ternary reloptions
@ 2025-09-04 14:41 Nikolay Shaplov <[email protected]>
0 siblings, 0 replies; 868+ messages in thread
From: Nikolay Shaplov @ 2025-09-04 14:41 UTC (permalink / raw)
Introduce ternary reloption as a replacement for current `vacuum_truncate`
implementation. Remove `vacuum_truncate_set` additional flag and using
`TERNARY_UNSET` value instead.
---
src/backend/access/common/reloptions.c | 129 ++++++++++++++++++++-----
src/backend/commands/vacuum.c | 4 +-
src/include/access/reloptions.h | 26 ++---
src/include/c.h | 16 +++
src/include/utils/rel.h | 3 +-
5 files changed, 135 insertions(+), 43 deletions(-)
diff --git a/src/backend/access/common/reloptions.c b/src/backend/access/common/reloptions.c
index 0af3fea68f..f543f11001 100644
--- a/src/backend/access/common/reloptions.c
+++ b/src/backend/access/common/reloptions.c
@@ -40,9 +40,9 @@
*
* To add an option:
*
- * (i) decide on a type (bool, integer, real, enum, string), name, default
- * value, upper and lower bounds (if applicable); for strings, consider a
- * validation routine.
+ * (i) decide on a type (bool, ternary, integer, real, enum, string), name,
+ * default value, upper and lower bounds (if applicable); for strings,
+ * consider a validation routine.
* (ii) add a record below (or use add_<type>_reloption).
* (iii) add it to the appropriate options struct (perhaps StdRdOptions)
* (iv) add it to the appropriate handling routine (perhaps
@@ -147,15 +147,6 @@ static relopt_bool boolRelOpts[] =
},
false
},
- {
- {
- "vacuum_truncate",
- "Enables vacuum to truncate empty pages at the end of this table",
- RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
- ShareUpdateExclusiveLock
- },
- true
- },
{
{
"deduplicate_items",
@@ -170,6 +161,21 @@ static relopt_bool boolRelOpts[] =
{{NULL}}
};
+static relopt_ternary ternaryRelOpts[] =
+{
+ {
+ {
+ "vacuum_truncate",
+ "Enables vacuum to truncate empty pages at the end of this table",
+ RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
+ ShareUpdateExclusiveLock
+ },
+ TERNARY_UNSET
+ },
+ /* list terminator */
+ {{NULL}}
+};
+
static relopt_int intRelOpts[] =
{
{
@@ -600,6 +606,13 @@ initialize_reloptions(void)
boolRelOpts[i].gen.lockmode));
j++;
}
+ for (i = 0; ternaryRelOpts[i].gen.name; i++)
+ {
+ Assert(DoLockModesConflict(ternaryRelOpts[i].gen.lockmode,
+ ternaryRelOpts[i].gen.lockmode));
+ j++;
+ }
+
for (i = 0; intRelOpts[i].gen.name; i++)
{
Assert(DoLockModesConflict(intRelOpts[i].gen.lockmode,
@@ -640,6 +653,14 @@ initialize_reloptions(void)
j++;
}
+ for (i = 0; ternaryRelOpts[i].gen.name; i++)
+ {
+ relOpts[j] = &ternaryRelOpts[i].gen;
+ relOpts[j]->type = RELOPT_TYPE_TERNARY;
+ relOpts[j]->namelen = strlen(relOpts[j]->name);
+ j++;
+ }
+
for (i = 0; intRelOpts[i].gen.name; i++)
{
relOpts[j] = &intRelOpts[i].gen;
@@ -800,6 +821,9 @@ allocate_reloption(bits32 kinds, int type, const char *name, const char *desc,
case RELOPT_TYPE_BOOL:
size = sizeof(relopt_bool);
break;
+ case RELOPT_TYPE_TERNARY:
+ size = sizeof(relopt_ternary);
+ break;
case RELOPT_TYPE_INT:
size = sizeof(relopt_int);
break;
@@ -883,6 +907,54 @@ add_local_bool_reloption(local_relopts *relopts, const char *name,
add_local_reloption(relopts, (relopt_gen *) newoption, offset);
}
+/*
+ * init_ternary_reloption
+ * Allocate and initialize a new ternary reloption
+ */
+static relopt_ternary *
+init_ternary_reloption(bits32 kinds, const char *name, const char *desc,
+ ternary default_val, LOCKMODE lockmode)
+{
+ relopt_ternary *newoption;
+
+ newoption = (relopt_ternary *) allocate_reloption(kinds,
+ RELOPT_TYPE_TERNARY, name, desc, lockmode);
+ newoption->default_val = default_val;
+
+ return newoption;
+}
+
+/*
+ * add_ternary_reloption
+ * Add a new ternary reloption
+ */
+void
+add_ternary_reloption(bits32 kinds, const char *name, const char *desc,
+ ternary default_val, LOCKMODE lockmode)
+{
+ relopt_ternary *newoption = init_ternary_reloption(kinds, name, desc,
+ default_val, lockmode);
+
+ add_reloption((relopt_gen *) newoption);
+}
+
+/*
+ * add_local_ternary_reloption
+ * Add a new ternary local reloption
+ *
+ * 'offset' is offset of ternary-typed field.
+ */
+void
+add_local_ternary_reloption(local_relopts *relopts, const char *name,
+ const char *desc, ternary default_val,
+ int offset)
+{
+ relopt_ternary *newoption = init_ternary_reloption(RELOPT_KIND_LOCAL,
+ name, desc,
+ default_val, 0);
+
+ add_local_reloption(relopts, (relopt_gen *) newoption, offset);
+}
/*
* init_real_reloption
@@ -1617,6 +1689,18 @@ parse_one_reloption(relopt_value *option, char *text_str, int text_len,
option->gen->name, value)));
}
break;
+ case RELOPT_TYPE_TERNARY:
+ {
+ bool b;
+ parsed = parse_bool(value, &b);
+ option->values.ternary_val = b ? TERNARY_TRUE : TERNARY_FALSE;
+ if (validate && !parsed)
+ ereport(ERROR,
+ (errcode(ERRCODE_INVALID_PARAMETER_VALUE),
+ errmsg("invalid value for ternary option \"%s\": %s",
+ option->gen->name, value)));
+ }
+ break;
case RELOPT_TYPE_INT:
{
relopt_int *optint = (relopt_int *) option->gen;
@@ -1780,17 +1864,6 @@ fillRelOptions(void *rdopts, Size basesize,
char *itempos = ((char *) rdopts) + elems[j].offset;
char *string_val;
- /*
- * If isset_offset is provided, store whether the reloption is
- * set there.
- */
- if (elems[j].isset_offset > 0)
- {
- char *setpos = ((char *) rdopts) + elems[j].isset_offset;
-
- *(bool *) setpos = options[i].isset;
- }
-
switch (options[i].gen->type)
{
case RELOPT_TYPE_BOOL:
@@ -1798,6 +1871,11 @@ fillRelOptions(void *rdopts, Size basesize,
options[i].values.bool_val :
((relopt_bool *) options[i].gen)->default_val;
break;
+ case RELOPT_TYPE_TERNARY:
+ *(ternary *) itempos = options[i].isset ?
+ options[i].values.ternary_val :
+ ((relopt_ternary *) options[i].gen)->default_val;
+ break;
case RELOPT_TYPE_INT:
*(int *) itempos = options[i].isset ?
options[i].values.int_val :
@@ -1912,8 +1990,8 @@ default_reloptions(Datum reloptions, bool validate, relopt_kind kind)
offsetof(StdRdOptions, parallel_workers)},
{"vacuum_index_cleanup", RELOPT_TYPE_ENUM,
offsetof(StdRdOptions, vacuum_index_cleanup)},
- {"vacuum_truncate", RELOPT_TYPE_BOOL,
- offsetof(StdRdOptions, vacuum_truncate), offsetof(StdRdOptions, vacuum_truncate_set)},
+ {"vacuum_truncate", RELOPT_TYPE_TERNARY,
+ offsetof(StdRdOptions, vacuum_truncate)},
{"vacuum_max_eager_freeze_failure_rate", RELOPT_TYPE_REAL,
offsetof(StdRdOptions, vacuum_max_eager_freeze_failure_rate)}
};
@@ -1993,7 +2071,6 @@ build_local_reloptions(local_relopts *relopts, Datum options, bool validate)
elems[i].optname = opt->option->name;
elems[i].opttype = opt->option->type;
elems[i].offset = opt->offset;
- elems[i].isset_offset = 0; /* not supported for local relopts yet */
i++;
}
diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c
index 733ef40ae7..977babff54 100644
--- a/src/backend/commands/vacuum.c
+++ b/src/backend/commands/vacuum.c
@@ -2221,9 +2221,9 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams params,
{
StdRdOptions *opts = (StdRdOptions *) rel->rd_options;
- if (opts && opts->vacuum_truncate_set)
+ if (opts && opts->vacuum_truncate != TERNARY_UNSET)
{
- if (opts->vacuum_truncate)
+ if (opts->vacuum_truncate == TERNARY_TRUE)
params.truncate = VACOPTVALUE_ENABLED;
else
params.truncate = VACOPTVALUE_DISABLED;
diff --git a/src/include/access/reloptions.h b/src/include/access/reloptions.h
index a604a4702c..a436697658 100644
--- a/src/include/access/reloptions.h
+++ b/src/include/access/reloptions.h
@@ -29,6 +29,7 @@
typedef enum relopt_type
{
RELOPT_TYPE_BOOL,
+ RELOPT_TYPE_TERNARY, /* on, off, unset */
RELOPT_TYPE_INT,
RELOPT_TYPE_REAL,
RELOPT_TYPE_ENUM,
@@ -80,6 +81,7 @@ typedef struct relopt_value
union
{
bool bool_val;
+ ternary ternary_val;
int int_val;
double real_val;
int enum_val;
@@ -94,6 +96,12 @@ typedef struct relopt_bool
bool default_val;
} relopt_bool;
+typedef struct relopt_rernary
+{
+ relopt_gen gen;
+ int default_val;
+} relopt_ternary;
+
typedef struct relopt_int
{
relopt_gen gen;
@@ -152,19 +160,6 @@ typedef struct
const char *optname; /* option's name */
relopt_type opttype; /* option's datatype */
int offset; /* offset of field in result struct */
-
- /*
- * isset_offset is an optional offset of a field in the result struct that
- * stores whether the option is explicitly set for the relation or if it
- * just picked up the default value. In most cases, this can be
- * accomplished by giving the reloption a special out-of-range default
- * value (e.g., some integer reloptions use -2), but this isn't always
- * possible. For example, a Boolean reloption cannot be given an
- * out-of-range default, so we need another way to discover the source of
- * its value. This offset is only used if given a value greater than
- * zero.
- */
- int isset_offset;
} relopt_parse_elt;
/* Local reloption definition */
@@ -195,6 +190,8 @@ typedef struct local_relopts
extern relopt_kind add_reloption_kind(void);
extern void add_bool_reloption(bits32 kinds, const char *name, const char *desc,
bool default_val, LOCKMODE lockmode);
+extern void add_ternary_reloption(bits32 kinds, const char *name,
+ const char *desc, int default_val, LOCKMODE lockmode);
extern void add_int_reloption(bits32 kinds, const char *name, const char *desc,
int default_val, int min_val, int max_val,
LOCKMODE lockmode);
@@ -214,6 +211,9 @@ extern void register_reloptions_validator(local_relopts *relopts,
extern void add_local_bool_reloption(local_relopts *relopts, const char *name,
const char *desc, bool default_val,
int offset);
+extern void add_local_ternary_reloption(local_relopts *relopts,
+ const char *name, const char *desc,
+ ternary default_val, int offset);
extern void add_local_int_reloption(local_relopts *relopts, const char *name,
const char *desc, int default_val,
int min_val, int max_val, int offset);
diff --git a/src/include/c.h b/src/include/c.h
index 39022f8a9d..9c59ccccf8 100644
--- a/src/include/c.h
+++ b/src/include/c.h
@@ -488,6 +488,22 @@ typedef void (*pg_funcptr_t) (void);
#include <stdbool.h>
+/*
+ * ternary
+ * Boolean value with an extrea "unset" option
+ *
+ * Ternary data type is used in relation options that can be "true", "false" or
+ * "unset". Since relation options are used deep inside the PostgreSQL code,
+ * this type is declared globally.
+*/
+
+typedef enum ternary
+{
+ TERNARY_FALSE = 0,
+ TERNARY_TRUE = 1,
+ TERNARY_UNSET = -1
+} ternary;
+
/* ----------------------------------------------------------------
* Section 3: standard system types
diff --git a/src/include/utils/rel.h b/src/include/utils/rel.h
index b552359915..7346d618f9 100644
--- a/src/include/utils/rel.h
+++ b/src/include/utils/rel.h
@@ -346,8 +346,7 @@ typedef struct StdRdOptions
bool user_catalog_table; /* use as an additional catalog relation */
int parallel_workers; /* max number of parallel workers */
StdRdOptIndexCleanup vacuum_index_cleanup; /* controls index vacuuming */
- bool vacuum_truncate; /* enables vacuum to truncate a relation */
- bool vacuum_truncate_set; /* whether vacuum_truncate is set */
+ ternary vacuum_truncate; /* enables vacuum to truncate a relation */
/*
* Fraction of pages in a relation that vacuum can eagerly scan and fail
--
2.39.2
--nextPart5129932.5fSG56mABF
Content-Disposition: attachment;
filename="v2-0003-Add-alias-to-be-used-as-unset-state.patch"
Content-Transfer-Encoding: 7Bit
Content-Type: text/x-patch; charset="unicode-2-0-utf-8";
name="v2-0003-Add-alias-to-be-used-as-unset-state.patch"
^ permalink raw reply [nested|flat] 868+ messages in thread
* [PATCH v2a 2/4] Introduce ternary reloptions
@ 2025-09-04 14:41 Nikolay Shaplov <[email protected]>
0 siblings, 0 replies; 868+ messages in thread
From: Nikolay Shaplov @ 2025-09-04 14:41 UTC (permalink / raw)
Introduce ternary reloption as a replacement for current `vacuum_truncate`
implementation. Remove `vacuum_truncate_set` additional flag and using
`TERNARY_UNSET` value instead.
---
src/backend/access/common/reloptions.c | 129 ++++++++++++++++++++-----
src/backend/commands/vacuum.c | 4 +-
src/include/access/reloptions.h | 26 ++---
src/include/c.h | 16 +++
src/include/utils/rel.h | 3 +-
5 files changed, 135 insertions(+), 43 deletions(-)
diff --git a/src/backend/access/common/reloptions.c b/src/backend/access/common/reloptions.c
index 0af3fea68f..f543f11001 100644
--- a/src/backend/access/common/reloptions.c
+++ b/src/backend/access/common/reloptions.c
@@ -40,9 +40,9 @@
*
* To add an option:
*
- * (i) decide on a type (bool, integer, real, enum, string), name, default
- * value, upper and lower bounds (if applicable); for strings, consider a
- * validation routine.
+ * (i) decide on a type (bool, ternary, integer, real, enum, string), name,
+ * default value, upper and lower bounds (if applicable); for strings,
+ * consider a validation routine.
* (ii) add a record below (or use add_<type>_reloption).
* (iii) add it to the appropriate options struct (perhaps StdRdOptions)
* (iv) add it to the appropriate handling routine (perhaps
@@ -147,15 +147,6 @@ static relopt_bool boolRelOpts[] =
},
false
},
- {
- {
- "vacuum_truncate",
- "Enables vacuum to truncate empty pages at the end of this table",
- RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
- ShareUpdateExclusiveLock
- },
- true
- },
{
{
"deduplicate_items",
@@ -170,6 +161,21 @@ static relopt_bool boolRelOpts[] =
{{NULL}}
};
+static relopt_ternary ternaryRelOpts[] =
+{
+ {
+ {
+ "vacuum_truncate",
+ "Enables vacuum to truncate empty pages at the end of this table",
+ RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
+ ShareUpdateExclusiveLock
+ },
+ TERNARY_UNSET
+ },
+ /* list terminator */
+ {{NULL}}
+};
+
static relopt_int intRelOpts[] =
{
{
@@ -600,6 +606,13 @@ initialize_reloptions(void)
boolRelOpts[i].gen.lockmode));
j++;
}
+ for (i = 0; ternaryRelOpts[i].gen.name; i++)
+ {
+ Assert(DoLockModesConflict(ternaryRelOpts[i].gen.lockmode,
+ ternaryRelOpts[i].gen.lockmode));
+ j++;
+ }
+
for (i = 0; intRelOpts[i].gen.name; i++)
{
Assert(DoLockModesConflict(intRelOpts[i].gen.lockmode,
@@ -640,6 +653,14 @@ initialize_reloptions(void)
j++;
}
+ for (i = 0; ternaryRelOpts[i].gen.name; i++)
+ {
+ relOpts[j] = &ternaryRelOpts[i].gen;
+ relOpts[j]->type = RELOPT_TYPE_TERNARY;
+ relOpts[j]->namelen = strlen(relOpts[j]->name);
+ j++;
+ }
+
for (i = 0; intRelOpts[i].gen.name; i++)
{
relOpts[j] = &intRelOpts[i].gen;
@@ -800,6 +821,9 @@ allocate_reloption(bits32 kinds, int type, const char *name, const char *desc,
case RELOPT_TYPE_BOOL:
size = sizeof(relopt_bool);
break;
+ case RELOPT_TYPE_TERNARY:
+ size = sizeof(relopt_ternary);
+ break;
case RELOPT_TYPE_INT:
size = sizeof(relopt_int);
break;
@@ -883,6 +907,54 @@ add_local_bool_reloption(local_relopts *relopts, const char *name,
add_local_reloption(relopts, (relopt_gen *) newoption, offset);
}
+/*
+ * init_ternary_reloption
+ * Allocate and initialize a new ternary reloption
+ */
+static relopt_ternary *
+init_ternary_reloption(bits32 kinds, const char *name, const char *desc,
+ ternary default_val, LOCKMODE lockmode)
+{
+ relopt_ternary *newoption;
+
+ newoption = (relopt_ternary *) allocate_reloption(kinds,
+ RELOPT_TYPE_TERNARY, name, desc, lockmode);
+ newoption->default_val = default_val;
+
+ return newoption;
+}
+
+/*
+ * add_ternary_reloption
+ * Add a new ternary reloption
+ */
+void
+add_ternary_reloption(bits32 kinds, const char *name, const char *desc,
+ ternary default_val, LOCKMODE lockmode)
+{
+ relopt_ternary *newoption = init_ternary_reloption(kinds, name, desc,
+ default_val, lockmode);
+
+ add_reloption((relopt_gen *) newoption);
+}
+
+/*
+ * add_local_ternary_reloption
+ * Add a new ternary local reloption
+ *
+ * 'offset' is offset of ternary-typed field.
+ */
+void
+add_local_ternary_reloption(local_relopts *relopts, const char *name,
+ const char *desc, ternary default_val,
+ int offset)
+{
+ relopt_ternary *newoption = init_ternary_reloption(RELOPT_KIND_LOCAL,
+ name, desc,
+ default_val, 0);
+
+ add_local_reloption(relopts, (relopt_gen *) newoption, offset);
+}
/*
* init_real_reloption
@@ -1617,6 +1689,18 @@ parse_one_reloption(relopt_value *option, char *text_str, int text_len,
option->gen->name, value)));
}
break;
+ case RELOPT_TYPE_TERNARY:
+ {
+ bool b;
+ parsed = parse_bool(value, &b);
+ option->values.ternary_val = b ? TERNARY_TRUE : TERNARY_FALSE;
+ if (validate && !parsed)
+ ereport(ERROR,
+ (errcode(ERRCODE_INVALID_PARAMETER_VALUE),
+ errmsg("invalid value for ternary option \"%s\": %s",
+ option->gen->name, value)));
+ }
+ break;
case RELOPT_TYPE_INT:
{
relopt_int *optint = (relopt_int *) option->gen;
@@ -1780,17 +1864,6 @@ fillRelOptions(void *rdopts, Size basesize,
char *itempos = ((char *) rdopts) + elems[j].offset;
char *string_val;
- /*
- * If isset_offset is provided, store whether the reloption is
- * set there.
- */
- if (elems[j].isset_offset > 0)
- {
- char *setpos = ((char *) rdopts) + elems[j].isset_offset;
-
- *(bool *) setpos = options[i].isset;
- }
-
switch (options[i].gen->type)
{
case RELOPT_TYPE_BOOL:
@@ -1798,6 +1871,11 @@ fillRelOptions(void *rdopts, Size basesize,
options[i].values.bool_val :
((relopt_bool *) options[i].gen)->default_val;
break;
+ case RELOPT_TYPE_TERNARY:
+ *(ternary *) itempos = options[i].isset ?
+ options[i].values.ternary_val :
+ ((relopt_ternary *) options[i].gen)->default_val;
+ break;
case RELOPT_TYPE_INT:
*(int *) itempos = options[i].isset ?
options[i].values.int_val :
@@ -1912,8 +1990,8 @@ default_reloptions(Datum reloptions, bool validate, relopt_kind kind)
offsetof(StdRdOptions, parallel_workers)},
{"vacuum_index_cleanup", RELOPT_TYPE_ENUM,
offsetof(StdRdOptions, vacuum_index_cleanup)},
- {"vacuum_truncate", RELOPT_TYPE_BOOL,
- offsetof(StdRdOptions, vacuum_truncate), offsetof(StdRdOptions, vacuum_truncate_set)},
+ {"vacuum_truncate", RELOPT_TYPE_TERNARY,
+ offsetof(StdRdOptions, vacuum_truncate)},
{"vacuum_max_eager_freeze_failure_rate", RELOPT_TYPE_REAL,
offsetof(StdRdOptions, vacuum_max_eager_freeze_failure_rate)}
};
@@ -1993,7 +2071,6 @@ build_local_reloptions(local_relopts *relopts, Datum options, bool validate)
elems[i].optname = opt->option->name;
elems[i].opttype = opt->option->type;
elems[i].offset = opt->offset;
- elems[i].isset_offset = 0; /* not supported for local relopts yet */
i++;
}
diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c
index 733ef40ae7..977babff54 100644
--- a/src/backend/commands/vacuum.c
+++ b/src/backend/commands/vacuum.c
@@ -2221,9 +2221,9 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams params,
{
StdRdOptions *opts = (StdRdOptions *) rel->rd_options;
- if (opts && opts->vacuum_truncate_set)
+ if (opts && opts->vacuum_truncate != TERNARY_UNSET)
{
- if (opts->vacuum_truncate)
+ if (opts->vacuum_truncate == TERNARY_TRUE)
params.truncate = VACOPTVALUE_ENABLED;
else
params.truncate = VACOPTVALUE_DISABLED;
diff --git a/src/include/access/reloptions.h b/src/include/access/reloptions.h
index a604a4702c..a436697658 100644
--- a/src/include/access/reloptions.h
+++ b/src/include/access/reloptions.h
@@ -29,6 +29,7 @@
typedef enum relopt_type
{
RELOPT_TYPE_BOOL,
+ RELOPT_TYPE_TERNARY, /* on, off, unset */
RELOPT_TYPE_INT,
RELOPT_TYPE_REAL,
RELOPT_TYPE_ENUM,
@@ -80,6 +81,7 @@ typedef struct relopt_value
union
{
bool bool_val;
+ ternary ternary_val;
int int_val;
double real_val;
int enum_val;
@@ -94,6 +96,12 @@ typedef struct relopt_bool
bool default_val;
} relopt_bool;
+typedef struct relopt_rernary
+{
+ relopt_gen gen;
+ int default_val;
+} relopt_ternary;
+
typedef struct relopt_int
{
relopt_gen gen;
@@ -152,19 +160,6 @@ typedef struct
const char *optname; /* option's name */
relopt_type opttype; /* option's datatype */
int offset; /* offset of field in result struct */
-
- /*
- * isset_offset is an optional offset of a field in the result struct that
- * stores whether the option is explicitly set for the relation or if it
- * just picked up the default value. In most cases, this can be
- * accomplished by giving the reloption a special out-of-range default
- * value (e.g., some integer reloptions use -2), but this isn't always
- * possible. For example, a Boolean reloption cannot be given an
- * out-of-range default, so we need another way to discover the source of
- * its value. This offset is only used if given a value greater than
- * zero.
- */
- int isset_offset;
} relopt_parse_elt;
/* Local reloption definition */
@@ -195,6 +190,8 @@ typedef struct local_relopts
extern relopt_kind add_reloption_kind(void);
extern void add_bool_reloption(bits32 kinds, const char *name, const char *desc,
bool default_val, LOCKMODE lockmode);
+extern void add_ternary_reloption(bits32 kinds, const char *name,
+ const char *desc, int default_val, LOCKMODE lockmode);
extern void add_int_reloption(bits32 kinds, const char *name, const char *desc,
int default_val, int min_val, int max_val,
LOCKMODE lockmode);
@@ -214,6 +211,9 @@ extern void register_reloptions_validator(local_relopts *relopts,
extern void add_local_bool_reloption(local_relopts *relopts, const char *name,
const char *desc, bool default_val,
int offset);
+extern void add_local_ternary_reloption(local_relopts *relopts,
+ const char *name, const char *desc,
+ ternary default_val, int offset);
extern void add_local_int_reloption(local_relopts *relopts, const char *name,
const char *desc, int default_val,
int min_val, int max_val, int offset);
diff --git a/src/include/c.h b/src/include/c.h
index 39022f8a9d..9c59ccccf8 100644
--- a/src/include/c.h
+++ b/src/include/c.h
@@ -488,6 +488,22 @@ typedef void (*pg_funcptr_t) (void);
#include <stdbool.h>
+/*
+ * ternary
+ * Boolean value with an extrea "unset" option
+ *
+ * Ternary data type is used in relation options that can be "true", "false" or
+ * "unset". Since relation options are used deep inside the PostgreSQL code,
+ * this type is declared globally.
+*/
+
+typedef enum ternary
+{
+ TERNARY_FALSE = 0,
+ TERNARY_TRUE = 1,
+ TERNARY_UNSET = -1
+} ternary;
+
/* ----------------------------------------------------------------
* Section 3: standard system types
diff --git a/src/include/utils/rel.h b/src/include/utils/rel.h
index b552359915..7346d618f9 100644
--- a/src/include/utils/rel.h
+++ b/src/include/utils/rel.h
@@ -346,8 +346,7 @@ typedef struct StdRdOptions
bool user_catalog_table; /* use as an additional catalog relation */
int parallel_workers; /* max number of parallel workers */
StdRdOptIndexCleanup vacuum_index_cleanup; /* controls index vacuuming */
- bool vacuum_truncate; /* enables vacuum to truncate a relation */
- bool vacuum_truncate_set; /* whether vacuum_truncate is set */
+ ternary vacuum_truncate; /* enables vacuum to truncate a relation */
/*
* Fraction of pages in a relation that vacuum can eagerly scan and fail
--
2.39.2
--nextPart4155600.3ZeAukHxDK
Content-Disposition: attachment;
filename="v2a-0003-Add-alias-to-be-used-as-unset-state.patch"
Content-Transfer-Encoding: 7Bit
Content-Type: text/x-patch; charset="unicode-2-0-utf-8";
name="v2a-0003-Add-alias-to-be-used-as-unset-state.patch"
^ permalink raw reply [nested|flat] 868+ messages in thread
* [PATCH v2a 2/4] Introduce ternary reloptions
@ 2025-09-04 14:41 Nikolay Shaplov <[email protected]>
0 siblings, 0 replies; 868+ messages in thread
From: Nikolay Shaplov @ 2025-09-04 14:41 UTC (permalink / raw)
Introduce ternary reloption as a replacement for current `vacuum_truncate`
implementation. Remove `vacuum_truncate_set` additional flag and using
`TERNARY_UNSET` value instead.
---
src/backend/access/common/reloptions.c | 129 ++++++++++++++++++++-----
src/backend/commands/vacuum.c | 4 +-
src/include/access/reloptions.h | 26 ++---
src/include/c.h | 16 +++
src/include/utils/rel.h | 3 +-
5 files changed, 135 insertions(+), 43 deletions(-)
diff --git a/src/backend/access/common/reloptions.c b/src/backend/access/common/reloptions.c
index 0af3fea68f..f543f11001 100644
--- a/src/backend/access/common/reloptions.c
+++ b/src/backend/access/common/reloptions.c
@@ -40,9 +40,9 @@
*
* To add an option:
*
- * (i) decide on a type (bool, integer, real, enum, string), name, default
- * value, upper and lower bounds (if applicable); for strings, consider a
- * validation routine.
+ * (i) decide on a type (bool, ternary, integer, real, enum, string), name,
+ * default value, upper and lower bounds (if applicable); for strings,
+ * consider a validation routine.
* (ii) add a record below (or use add_<type>_reloption).
* (iii) add it to the appropriate options struct (perhaps StdRdOptions)
* (iv) add it to the appropriate handling routine (perhaps
@@ -147,15 +147,6 @@ static relopt_bool boolRelOpts[] =
},
false
},
- {
- {
- "vacuum_truncate",
- "Enables vacuum to truncate empty pages at the end of this table",
- RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
- ShareUpdateExclusiveLock
- },
- true
- },
{
{
"deduplicate_items",
@@ -170,6 +161,21 @@ static relopt_bool boolRelOpts[] =
{{NULL}}
};
+static relopt_ternary ternaryRelOpts[] =
+{
+ {
+ {
+ "vacuum_truncate",
+ "Enables vacuum to truncate empty pages at the end of this table",
+ RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
+ ShareUpdateExclusiveLock
+ },
+ TERNARY_UNSET
+ },
+ /* list terminator */
+ {{NULL}}
+};
+
static relopt_int intRelOpts[] =
{
{
@@ -600,6 +606,13 @@ initialize_reloptions(void)
boolRelOpts[i].gen.lockmode));
j++;
}
+ for (i = 0; ternaryRelOpts[i].gen.name; i++)
+ {
+ Assert(DoLockModesConflict(ternaryRelOpts[i].gen.lockmode,
+ ternaryRelOpts[i].gen.lockmode));
+ j++;
+ }
+
for (i = 0; intRelOpts[i].gen.name; i++)
{
Assert(DoLockModesConflict(intRelOpts[i].gen.lockmode,
@@ -640,6 +653,14 @@ initialize_reloptions(void)
j++;
}
+ for (i = 0; ternaryRelOpts[i].gen.name; i++)
+ {
+ relOpts[j] = &ternaryRelOpts[i].gen;
+ relOpts[j]->type = RELOPT_TYPE_TERNARY;
+ relOpts[j]->namelen = strlen(relOpts[j]->name);
+ j++;
+ }
+
for (i = 0; intRelOpts[i].gen.name; i++)
{
relOpts[j] = &intRelOpts[i].gen;
@@ -800,6 +821,9 @@ allocate_reloption(bits32 kinds, int type, const char *name, const char *desc,
case RELOPT_TYPE_BOOL:
size = sizeof(relopt_bool);
break;
+ case RELOPT_TYPE_TERNARY:
+ size = sizeof(relopt_ternary);
+ break;
case RELOPT_TYPE_INT:
size = sizeof(relopt_int);
break;
@@ -883,6 +907,54 @@ add_local_bool_reloption(local_relopts *relopts, const char *name,
add_local_reloption(relopts, (relopt_gen *) newoption, offset);
}
+/*
+ * init_ternary_reloption
+ * Allocate and initialize a new ternary reloption
+ */
+static relopt_ternary *
+init_ternary_reloption(bits32 kinds, const char *name, const char *desc,
+ ternary default_val, LOCKMODE lockmode)
+{
+ relopt_ternary *newoption;
+
+ newoption = (relopt_ternary *) allocate_reloption(kinds,
+ RELOPT_TYPE_TERNARY, name, desc, lockmode);
+ newoption->default_val = default_val;
+
+ return newoption;
+}
+
+/*
+ * add_ternary_reloption
+ * Add a new ternary reloption
+ */
+void
+add_ternary_reloption(bits32 kinds, const char *name, const char *desc,
+ ternary default_val, LOCKMODE lockmode)
+{
+ relopt_ternary *newoption = init_ternary_reloption(kinds, name, desc,
+ default_val, lockmode);
+
+ add_reloption((relopt_gen *) newoption);
+}
+
+/*
+ * add_local_ternary_reloption
+ * Add a new ternary local reloption
+ *
+ * 'offset' is offset of ternary-typed field.
+ */
+void
+add_local_ternary_reloption(local_relopts *relopts, const char *name,
+ const char *desc, ternary default_val,
+ int offset)
+{
+ relopt_ternary *newoption = init_ternary_reloption(RELOPT_KIND_LOCAL,
+ name, desc,
+ default_val, 0);
+
+ add_local_reloption(relopts, (relopt_gen *) newoption, offset);
+}
/*
* init_real_reloption
@@ -1617,6 +1689,18 @@ parse_one_reloption(relopt_value *option, char *text_str, int text_len,
option->gen->name, value)));
}
break;
+ case RELOPT_TYPE_TERNARY:
+ {
+ bool b;
+ parsed = parse_bool(value, &b);
+ option->values.ternary_val = b ? TERNARY_TRUE : TERNARY_FALSE;
+ if (validate && !parsed)
+ ereport(ERROR,
+ (errcode(ERRCODE_INVALID_PARAMETER_VALUE),
+ errmsg("invalid value for ternary option \"%s\": %s",
+ option->gen->name, value)));
+ }
+ break;
case RELOPT_TYPE_INT:
{
relopt_int *optint = (relopt_int *) option->gen;
@@ -1780,17 +1864,6 @@ fillRelOptions(void *rdopts, Size basesize,
char *itempos = ((char *) rdopts) + elems[j].offset;
char *string_val;
- /*
- * If isset_offset is provided, store whether the reloption is
- * set there.
- */
- if (elems[j].isset_offset > 0)
- {
- char *setpos = ((char *) rdopts) + elems[j].isset_offset;
-
- *(bool *) setpos = options[i].isset;
- }
-
switch (options[i].gen->type)
{
case RELOPT_TYPE_BOOL:
@@ -1798,6 +1871,11 @@ fillRelOptions(void *rdopts, Size basesize,
options[i].values.bool_val :
((relopt_bool *) options[i].gen)->default_val;
break;
+ case RELOPT_TYPE_TERNARY:
+ *(ternary *) itempos = options[i].isset ?
+ options[i].values.ternary_val :
+ ((relopt_ternary *) options[i].gen)->default_val;
+ break;
case RELOPT_TYPE_INT:
*(int *) itempos = options[i].isset ?
options[i].values.int_val :
@@ -1912,8 +1990,8 @@ default_reloptions(Datum reloptions, bool validate, relopt_kind kind)
offsetof(StdRdOptions, parallel_workers)},
{"vacuum_index_cleanup", RELOPT_TYPE_ENUM,
offsetof(StdRdOptions, vacuum_index_cleanup)},
- {"vacuum_truncate", RELOPT_TYPE_BOOL,
- offsetof(StdRdOptions, vacuum_truncate), offsetof(StdRdOptions, vacuum_truncate_set)},
+ {"vacuum_truncate", RELOPT_TYPE_TERNARY,
+ offsetof(StdRdOptions, vacuum_truncate)},
{"vacuum_max_eager_freeze_failure_rate", RELOPT_TYPE_REAL,
offsetof(StdRdOptions, vacuum_max_eager_freeze_failure_rate)}
};
@@ -1993,7 +2071,6 @@ build_local_reloptions(local_relopts *relopts, Datum options, bool validate)
elems[i].optname = opt->option->name;
elems[i].opttype = opt->option->type;
elems[i].offset = opt->offset;
- elems[i].isset_offset = 0; /* not supported for local relopts yet */
i++;
}
diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c
index 733ef40ae7..977babff54 100644
--- a/src/backend/commands/vacuum.c
+++ b/src/backend/commands/vacuum.c
@@ -2221,9 +2221,9 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams params,
{
StdRdOptions *opts = (StdRdOptions *) rel->rd_options;
- if (opts && opts->vacuum_truncate_set)
+ if (opts && opts->vacuum_truncate != TERNARY_UNSET)
{
- if (opts->vacuum_truncate)
+ if (opts->vacuum_truncate == TERNARY_TRUE)
params.truncate = VACOPTVALUE_ENABLED;
else
params.truncate = VACOPTVALUE_DISABLED;
diff --git a/src/include/access/reloptions.h b/src/include/access/reloptions.h
index a604a4702c..a436697658 100644
--- a/src/include/access/reloptions.h
+++ b/src/include/access/reloptions.h
@@ -29,6 +29,7 @@
typedef enum relopt_type
{
RELOPT_TYPE_BOOL,
+ RELOPT_TYPE_TERNARY, /* on, off, unset */
RELOPT_TYPE_INT,
RELOPT_TYPE_REAL,
RELOPT_TYPE_ENUM,
@@ -80,6 +81,7 @@ typedef struct relopt_value
union
{
bool bool_val;
+ ternary ternary_val;
int int_val;
double real_val;
int enum_val;
@@ -94,6 +96,12 @@ typedef struct relopt_bool
bool default_val;
} relopt_bool;
+typedef struct relopt_rernary
+{
+ relopt_gen gen;
+ int default_val;
+} relopt_ternary;
+
typedef struct relopt_int
{
relopt_gen gen;
@@ -152,19 +160,6 @@ typedef struct
const char *optname; /* option's name */
relopt_type opttype; /* option's datatype */
int offset; /* offset of field in result struct */
-
- /*
- * isset_offset is an optional offset of a field in the result struct that
- * stores whether the option is explicitly set for the relation or if it
- * just picked up the default value. In most cases, this can be
- * accomplished by giving the reloption a special out-of-range default
- * value (e.g., some integer reloptions use -2), but this isn't always
- * possible. For example, a Boolean reloption cannot be given an
- * out-of-range default, so we need another way to discover the source of
- * its value. This offset is only used if given a value greater than
- * zero.
- */
- int isset_offset;
} relopt_parse_elt;
/* Local reloption definition */
@@ -195,6 +190,8 @@ typedef struct local_relopts
extern relopt_kind add_reloption_kind(void);
extern void add_bool_reloption(bits32 kinds, const char *name, const char *desc,
bool default_val, LOCKMODE lockmode);
+extern void add_ternary_reloption(bits32 kinds, const char *name,
+ const char *desc, int default_val, LOCKMODE lockmode);
extern void add_int_reloption(bits32 kinds, const char *name, const char *desc,
int default_val, int min_val, int max_val,
LOCKMODE lockmode);
@@ -214,6 +211,9 @@ extern void register_reloptions_validator(local_relopts *relopts,
extern void add_local_bool_reloption(local_relopts *relopts, const char *name,
const char *desc, bool default_val,
int offset);
+extern void add_local_ternary_reloption(local_relopts *relopts,
+ const char *name, const char *desc,
+ ternary default_val, int offset);
extern void add_local_int_reloption(local_relopts *relopts, const char *name,
const char *desc, int default_val,
int min_val, int max_val, int offset);
diff --git a/src/include/c.h b/src/include/c.h
index 39022f8a9d..9c59ccccf8 100644
--- a/src/include/c.h
+++ b/src/include/c.h
@@ -488,6 +488,22 @@ typedef void (*pg_funcptr_t) (void);
#include <stdbool.h>
+/*
+ * ternary
+ * Boolean value with an extrea "unset" option
+ *
+ * Ternary data type is used in relation options that can be "true", "false" or
+ * "unset". Since relation options are used deep inside the PostgreSQL code,
+ * this type is declared globally.
+*/
+
+typedef enum ternary
+{
+ TERNARY_FALSE = 0,
+ TERNARY_TRUE = 1,
+ TERNARY_UNSET = -1
+} ternary;
+
/* ----------------------------------------------------------------
* Section 3: standard system types
diff --git a/src/include/utils/rel.h b/src/include/utils/rel.h
index b552359915..7346d618f9 100644
--- a/src/include/utils/rel.h
+++ b/src/include/utils/rel.h
@@ -346,8 +346,7 @@ typedef struct StdRdOptions
bool user_catalog_table; /* use as an additional catalog relation */
int parallel_workers; /* max number of parallel workers */
StdRdOptIndexCleanup vacuum_index_cleanup; /* controls index vacuuming */
- bool vacuum_truncate; /* enables vacuum to truncate a relation */
- bool vacuum_truncate_set; /* whether vacuum_truncate is set */
+ ternary vacuum_truncate; /* enables vacuum to truncate a relation */
/*
* Fraction of pages in a relation that vacuum can eagerly scan and fail
--
2.39.2
--nextPart4155600.3ZeAukHxDK
Content-Disposition: attachment;
filename="v2a-0003-Add-alias-to-be-used-as-unset-state.patch"
Content-Transfer-Encoding: 7Bit
Content-Type: text/x-patch; charset="unicode-2-0-utf-8";
name="v2a-0003-Add-alias-to-be-used-as-unset-state.patch"
^ permalink raw reply [nested|flat] 868+ messages in thread
* [PATCH v2a 2/4] Introduce ternary reloptions
@ 2025-09-04 14:41 Nikolay Shaplov <[email protected]>
0 siblings, 0 replies; 868+ messages in thread
From: Nikolay Shaplov @ 2025-09-04 14:41 UTC (permalink / raw)
Introduce ternary reloption as a replacement for current `vacuum_truncate`
implementation. Remove `vacuum_truncate_set` additional flag and using
`TERNARY_UNSET` value instead.
---
src/backend/access/common/reloptions.c | 129 ++++++++++++++++++++-----
src/backend/commands/vacuum.c | 4 +-
src/include/access/reloptions.h | 26 ++---
src/include/c.h | 16 +++
src/include/utils/rel.h | 3 +-
5 files changed, 135 insertions(+), 43 deletions(-)
diff --git a/src/backend/access/common/reloptions.c b/src/backend/access/common/reloptions.c
index 0af3fea68f..f543f11001 100644
--- a/src/backend/access/common/reloptions.c
+++ b/src/backend/access/common/reloptions.c
@@ -40,9 +40,9 @@
*
* To add an option:
*
- * (i) decide on a type (bool, integer, real, enum, string), name, default
- * value, upper and lower bounds (if applicable); for strings, consider a
- * validation routine.
+ * (i) decide on a type (bool, ternary, integer, real, enum, string), name,
+ * default value, upper and lower bounds (if applicable); for strings,
+ * consider a validation routine.
* (ii) add a record below (or use add_<type>_reloption).
* (iii) add it to the appropriate options struct (perhaps StdRdOptions)
* (iv) add it to the appropriate handling routine (perhaps
@@ -147,15 +147,6 @@ static relopt_bool boolRelOpts[] =
},
false
},
- {
- {
- "vacuum_truncate",
- "Enables vacuum to truncate empty pages at the end of this table",
- RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
- ShareUpdateExclusiveLock
- },
- true
- },
{
{
"deduplicate_items",
@@ -170,6 +161,21 @@ static relopt_bool boolRelOpts[] =
{{NULL}}
};
+static relopt_ternary ternaryRelOpts[] =
+{
+ {
+ {
+ "vacuum_truncate",
+ "Enables vacuum to truncate empty pages at the end of this table",
+ RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
+ ShareUpdateExclusiveLock
+ },
+ TERNARY_UNSET
+ },
+ /* list terminator */
+ {{NULL}}
+};
+
static relopt_int intRelOpts[] =
{
{
@@ -600,6 +606,13 @@ initialize_reloptions(void)
boolRelOpts[i].gen.lockmode));
j++;
}
+ for (i = 0; ternaryRelOpts[i].gen.name; i++)
+ {
+ Assert(DoLockModesConflict(ternaryRelOpts[i].gen.lockmode,
+ ternaryRelOpts[i].gen.lockmode));
+ j++;
+ }
+
for (i = 0; intRelOpts[i].gen.name; i++)
{
Assert(DoLockModesConflict(intRelOpts[i].gen.lockmode,
@@ -640,6 +653,14 @@ initialize_reloptions(void)
j++;
}
+ for (i = 0; ternaryRelOpts[i].gen.name; i++)
+ {
+ relOpts[j] = &ternaryRelOpts[i].gen;
+ relOpts[j]->type = RELOPT_TYPE_TERNARY;
+ relOpts[j]->namelen = strlen(relOpts[j]->name);
+ j++;
+ }
+
for (i = 0; intRelOpts[i].gen.name; i++)
{
relOpts[j] = &intRelOpts[i].gen;
@@ -800,6 +821,9 @@ allocate_reloption(bits32 kinds, int type, const char *name, const char *desc,
case RELOPT_TYPE_BOOL:
size = sizeof(relopt_bool);
break;
+ case RELOPT_TYPE_TERNARY:
+ size = sizeof(relopt_ternary);
+ break;
case RELOPT_TYPE_INT:
size = sizeof(relopt_int);
break;
@@ -883,6 +907,54 @@ add_local_bool_reloption(local_relopts *relopts, const char *name,
add_local_reloption(relopts, (relopt_gen *) newoption, offset);
}
+/*
+ * init_ternary_reloption
+ * Allocate and initialize a new ternary reloption
+ */
+static relopt_ternary *
+init_ternary_reloption(bits32 kinds, const char *name, const char *desc,
+ ternary default_val, LOCKMODE lockmode)
+{
+ relopt_ternary *newoption;
+
+ newoption = (relopt_ternary *) allocate_reloption(kinds,
+ RELOPT_TYPE_TERNARY, name, desc, lockmode);
+ newoption->default_val = default_val;
+
+ return newoption;
+}
+
+/*
+ * add_ternary_reloption
+ * Add a new ternary reloption
+ */
+void
+add_ternary_reloption(bits32 kinds, const char *name, const char *desc,
+ ternary default_val, LOCKMODE lockmode)
+{
+ relopt_ternary *newoption = init_ternary_reloption(kinds, name, desc,
+ default_val, lockmode);
+
+ add_reloption((relopt_gen *) newoption);
+}
+
+/*
+ * add_local_ternary_reloption
+ * Add a new ternary local reloption
+ *
+ * 'offset' is offset of ternary-typed field.
+ */
+void
+add_local_ternary_reloption(local_relopts *relopts, const char *name,
+ const char *desc, ternary default_val,
+ int offset)
+{
+ relopt_ternary *newoption = init_ternary_reloption(RELOPT_KIND_LOCAL,
+ name, desc,
+ default_val, 0);
+
+ add_local_reloption(relopts, (relopt_gen *) newoption, offset);
+}
/*
* init_real_reloption
@@ -1617,6 +1689,18 @@ parse_one_reloption(relopt_value *option, char *text_str, int text_len,
option->gen->name, value)));
}
break;
+ case RELOPT_TYPE_TERNARY:
+ {
+ bool b;
+ parsed = parse_bool(value, &b);
+ option->values.ternary_val = b ? TERNARY_TRUE : TERNARY_FALSE;
+ if (validate && !parsed)
+ ereport(ERROR,
+ (errcode(ERRCODE_INVALID_PARAMETER_VALUE),
+ errmsg("invalid value for ternary option \"%s\": %s",
+ option->gen->name, value)));
+ }
+ break;
case RELOPT_TYPE_INT:
{
relopt_int *optint = (relopt_int *) option->gen;
@@ -1780,17 +1864,6 @@ fillRelOptions(void *rdopts, Size basesize,
char *itempos = ((char *) rdopts) + elems[j].offset;
char *string_val;
- /*
- * If isset_offset is provided, store whether the reloption is
- * set there.
- */
- if (elems[j].isset_offset > 0)
- {
- char *setpos = ((char *) rdopts) + elems[j].isset_offset;
-
- *(bool *) setpos = options[i].isset;
- }
-
switch (options[i].gen->type)
{
case RELOPT_TYPE_BOOL:
@@ -1798,6 +1871,11 @@ fillRelOptions(void *rdopts, Size basesize,
options[i].values.bool_val :
((relopt_bool *) options[i].gen)->default_val;
break;
+ case RELOPT_TYPE_TERNARY:
+ *(ternary *) itempos = options[i].isset ?
+ options[i].values.ternary_val :
+ ((relopt_ternary *) options[i].gen)->default_val;
+ break;
case RELOPT_TYPE_INT:
*(int *) itempos = options[i].isset ?
options[i].values.int_val :
@@ -1912,8 +1990,8 @@ default_reloptions(Datum reloptions, bool validate, relopt_kind kind)
offsetof(StdRdOptions, parallel_workers)},
{"vacuum_index_cleanup", RELOPT_TYPE_ENUM,
offsetof(StdRdOptions, vacuum_index_cleanup)},
- {"vacuum_truncate", RELOPT_TYPE_BOOL,
- offsetof(StdRdOptions, vacuum_truncate), offsetof(StdRdOptions, vacuum_truncate_set)},
+ {"vacuum_truncate", RELOPT_TYPE_TERNARY,
+ offsetof(StdRdOptions, vacuum_truncate)},
{"vacuum_max_eager_freeze_failure_rate", RELOPT_TYPE_REAL,
offsetof(StdRdOptions, vacuum_max_eager_freeze_failure_rate)}
};
@@ -1993,7 +2071,6 @@ build_local_reloptions(local_relopts *relopts, Datum options, bool validate)
elems[i].optname = opt->option->name;
elems[i].opttype = opt->option->type;
elems[i].offset = opt->offset;
- elems[i].isset_offset = 0; /* not supported for local relopts yet */
i++;
}
diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c
index 733ef40ae7..977babff54 100644
--- a/src/backend/commands/vacuum.c
+++ b/src/backend/commands/vacuum.c
@@ -2221,9 +2221,9 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams params,
{
StdRdOptions *opts = (StdRdOptions *) rel->rd_options;
- if (opts && opts->vacuum_truncate_set)
+ if (opts && opts->vacuum_truncate != TERNARY_UNSET)
{
- if (opts->vacuum_truncate)
+ if (opts->vacuum_truncate == TERNARY_TRUE)
params.truncate = VACOPTVALUE_ENABLED;
else
params.truncate = VACOPTVALUE_DISABLED;
diff --git a/src/include/access/reloptions.h b/src/include/access/reloptions.h
index a604a4702c..a436697658 100644
--- a/src/include/access/reloptions.h
+++ b/src/include/access/reloptions.h
@@ -29,6 +29,7 @@
typedef enum relopt_type
{
RELOPT_TYPE_BOOL,
+ RELOPT_TYPE_TERNARY, /* on, off, unset */
RELOPT_TYPE_INT,
RELOPT_TYPE_REAL,
RELOPT_TYPE_ENUM,
@@ -80,6 +81,7 @@ typedef struct relopt_value
union
{
bool bool_val;
+ ternary ternary_val;
int int_val;
double real_val;
int enum_val;
@@ -94,6 +96,12 @@ typedef struct relopt_bool
bool default_val;
} relopt_bool;
+typedef struct relopt_rernary
+{
+ relopt_gen gen;
+ int default_val;
+} relopt_ternary;
+
typedef struct relopt_int
{
relopt_gen gen;
@@ -152,19 +160,6 @@ typedef struct
const char *optname; /* option's name */
relopt_type opttype; /* option's datatype */
int offset; /* offset of field in result struct */
-
- /*
- * isset_offset is an optional offset of a field in the result struct that
- * stores whether the option is explicitly set for the relation or if it
- * just picked up the default value. In most cases, this can be
- * accomplished by giving the reloption a special out-of-range default
- * value (e.g., some integer reloptions use -2), but this isn't always
- * possible. For example, a Boolean reloption cannot be given an
- * out-of-range default, so we need another way to discover the source of
- * its value. This offset is only used if given a value greater than
- * zero.
- */
- int isset_offset;
} relopt_parse_elt;
/* Local reloption definition */
@@ -195,6 +190,8 @@ typedef struct local_relopts
extern relopt_kind add_reloption_kind(void);
extern void add_bool_reloption(bits32 kinds, const char *name, const char *desc,
bool default_val, LOCKMODE lockmode);
+extern void add_ternary_reloption(bits32 kinds, const char *name,
+ const char *desc, int default_val, LOCKMODE lockmode);
extern void add_int_reloption(bits32 kinds, const char *name, const char *desc,
int default_val, int min_val, int max_val,
LOCKMODE lockmode);
@@ -214,6 +211,9 @@ extern void register_reloptions_validator(local_relopts *relopts,
extern void add_local_bool_reloption(local_relopts *relopts, const char *name,
const char *desc, bool default_val,
int offset);
+extern void add_local_ternary_reloption(local_relopts *relopts,
+ const char *name, const char *desc,
+ ternary default_val, int offset);
extern void add_local_int_reloption(local_relopts *relopts, const char *name,
const char *desc, int default_val,
int min_val, int max_val, int offset);
diff --git a/src/include/c.h b/src/include/c.h
index 39022f8a9d..9c59ccccf8 100644
--- a/src/include/c.h
+++ b/src/include/c.h
@@ -488,6 +488,22 @@ typedef void (*pg_funcptr_t) (void);
#include <stdbool.h>
+/*
+ * ternary
+ * Boolean value with an extrea "unset" option
+ *
+ * Ternary data type is used in relation options that can be "true", "false" or
+ * "unset". Since relation options are used deep inside the PostgreSQL code,
+ * this type is declared globally.
+*/
+
+typedef enum ternary
+{
+ TERNARY_FALSE = 0,
+ TERNARY_TRUE = 1,
+ TERNARY_UNSET = -1
+} ternary;
+
/* ----------------------------------------------------------------
* Section 3: standard system types
diff --git a/src/include/utils/rel.h b/src/include/utils/rel.h
index b552359915..7346d618f9 100644
--- a/src/include/utils/rel.h
+++ b/src/include/utils/rel.h
@@ -346,8 +346,7 @@ typedef struct StdRdOptions
bool user_catalog_table; /* use as an additional catalog relation */
int parallel_workers; /* max number of parallel workers */
StdRdOptIndexCleanup vacuum_index_cleanup; /* controls index vacuuming */
- bool vacuum_truncate; /* enables vacuum to truncate a relation */
- bool vacuum_truncate_set; /* whether vacuum_truncate is set */
+ ternary vacuum_truncate; /* enables vacuum to truncate a relation */
/*
* Fraction of pages in a relation that vacuum can eagerly scan and fail
--
2.39.2
--nextPart4155600.3ZeAukHxDK
Content-Disposition: attachment;
filename="v2a-0003-Add-alias-to-be-used-as-unset-state.patch"
Content-Transfer-Encoding: 7Bit
Content-Type: text/x-patch; charset="unicode-2-0-utf-8";
name="v2a-0003-Add-alias-to-be-used-as-unset-state.patch"
^ permalink raw reply [nested|flat] 868+ messages in thread
* [PATCH v2a 2/4] Introduce ternary reloptions
@ 2025-09-04 14:41 Nikolay Shaplov <[email protected]>
0 siblings, 0 replies; 868+ messages in thread
From: Nikolay Shaplov @ 2025-09-04 14:41 UTC (permalink / raw)
Introduce ternary reloption as a replacement for current `vacuum_truncate`
implementation. Remove `vacuum_truncate_set` additional flag and using
`TERNARY_UNSET` value instead.
---
src/backend/access/common/reloptions.c | 129 ++++++++++++++++++++-----
src/backend/commands/vacuum.c | 4 +-
src/include/access/reloptions.h | 26 ++---
src/include/c.h | 16 +++
src/include/utils/rel.h | 3 +-
5 files changed, 135 insertions(+), 43 deletions(-)
diff --git a/src/backend/access/common/reloptions.c b/src/backend/access/common/reloptions.c
index 0af3fea68f..f543f11001 100644
--- a/src/backend/access/common/reloptions.c
+++ b/src/backend/access/common/reloptions.c
@@ -40,9 +40,9 @@
*
* To add an option:
*
- * (i) decide on a type (bool, integer, real, enum, string), name, default
- * value, upper and lower bounds (if applicable); for strings, consider a
- * validation routine.
+ * (i) decide on a type (bool, ternary, integer, real, enum, string), name,
+ * default value, upper and lower bounds (if applicable); for strings,
+ * consider a validation routine.
* (ii) add a record below (or use add_<type>_reloption).
* (iii) add it to the appropriate options struct (perhaps StdRdOptions)
* (iv) add it to the appropriate handling routine (perhaps
@@ -147,15 +147,6 @@ static relopt_bool boolRelOpts[] =
},
false
},
- {
- {
- "vacuum_truncate",
- "Enables vacuum to truncate empty pages at the end of this table",
- RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
- ShareUpdateExclusiveLock
- },
- true
- },
{
{
"deduplicate_items",
@@ -170,6 +161,21 @@ static relopt_bool boolRelOpts[] =
{{NULL}}
};
+static relopt_ternary ternaryRelOpts[] =
+{
+ {
+ {
+ "vacuum_truncate",
+ "Enables vacuum to truncate empty pages at the end of this table",
+ RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
+ ShareUpdateExclusiveLock
+ },
+ TERNARY_UNSET
+ },
+ /* list terminator */
+ {{NULL}}
+};
+
static relopt_int intRelOpts[] =
{
{
@@ -600,6 +606,13 @@ initialize_reloptions(void)
boolRelOpts[i].gen.lockmode));
j++;
}
+ for (i = 0; ternaryRelOpts[i].gen.name; i++)
+ {
+ Assert(DoLockModesConflict(ternaryRelOpts[i].gen.lockmode,
+ ternaryRelOpts[i].gen.lockmode));
+ j++;
+ }
+
for (i = 0; intRelOpts[i].gen.name; i++)
{
Assert(DoLockModesConflict(intRelOpts[i].gen.lockmode,
@@ -640,6 +653,14 @@ initialize_reloptions(void)
j++;
}
+ for (i = 0; ternaryRelOpts[i].gen.name; i++)
+ {
+ relOpts[j] = &ternaryRelOpts[i].gen;
+ relOpts[j]->type = RELOPT_TYPE_TERNARY;
+ relOpts[j]->namelen = strlen(relOpts[j]->name);
+ j++;
+ }
+
for (i = 0; intRelOpts[i].gen.name; i++)
{
relOpts[j] = &intRelOpts[i].gen;
@@ -800,6 +821,9 @@ allocate_reloption(bits32 kinds, int type, const char *name, const char *desc,
case RELOPT_TYPE_BOOL:
size = sizeof(relopt_bool);
break;
+ case RELOPT_TYPE_TERNARY:
+ size = sizeof(relopt_ternary);
+ break;
case RELOPT_TYPE_INT:
size = sizeof(relopt_int);
break;
@@ -883,6 +907,54 @@ add_local_bool_reloption(local_relopts *relopts, const char *name,
add_local_reloption(relopts, (relopt_gen *) newoption, offset);
}
+/*
+ * init_ternary_reloption
+ * Allocate and initialize a new ternary reloption
+ */
+static relopt_ternary *
+init_ternary_reloption(bits32 kinds, const char *name, const char *desc,
+ ternary default_val, LOCKMODE lockmode)
+{
+ relopt_ternary *newoption;
+
+ newoption = (relopt_ternary *) allocate_reloption(kinds,
+ RELOPT_TYPE_TERNARY, name, desc, lockmode);
+ newoption->default_val = default_val;
+
+ return newoption;
+}
+
+/*
+ * add_ternary_reloption
+ * Add a new ternary reloption
+ */
+void
+add_ternary_reloption(bits32 kinds, const char *name, const char *desc,
+ ternary default_val, LOCKMODE lockmode)
+{
+ relopt_ternary *newoption = init_ternary_reloption(kinds, name, desc,
+ default_val, lockmode);
+
+ add_reloption((relopt_gen *) newoption);
+}
+
+/*
+ * add_local_ternary_reloption
+ * Add a new ternary local reloption
+ *
+ * 'offset' is offset of ternary-typed field.
+ */
+void
+add_local_ternary_reloption(local_relopts *relopts, const char *name,
+ const char *desc, ternary default_val,
+ int offset)
+{
+ relopt_ternary *newoption = init_ternary_reloption(RELOPT_KIND_LOCAL,
+ name, desc,
+ default_val, 0);
+
+ add_local_reloption(relopts, (relopt_gen *) newoption, offset);
+}
/*
* init_real_reloption
@@ -1617,6 +1689,18 @@ parse_one_reloption(relopt_value *option, char *text_str, int text_len,
option->gen->name, value)));
}
break;
+ case RELOPT_TYPE_TERNARY:
+ {
+ bool b;
+ parsed = parse_bool(value, &b);
+ option->values.ternary_val = b ? TERNARY_TRUE : TERNARY_FALSE;
+ if (validate && !parsed)
+ ereport(ERROR,
+ (errcode(ERRCODE_INVALID_PARAMETER_VALUE),
+ errmsg("invalid value for ternary option \"%s\": %s",
+ option->gen->name, value)));
+ }
+ break;
case RELOPT_TYPE_INT:
{
relopt_int *optint = (relopt_int *) option->gen;
@@ -1780,17 +1864,6 @@ fillRelOptions(void *rdopts, Size basesize,
char *itempos = ((char *) rdopts) + elems[j].offset;
char *string_val;
- /*
- * If isset_offset is provided, store whether the reloption is
- * set there.
- */
- if (elems[j].isset_offset > 0)
- {
- char *setpos = ((char *) rdopts) + elems[j].isset_offset;
-
- *(bool *) setpos = options[i].isset;
- }
-
switch (options[i].gen->type)
{
case RELOPT_TYPE_BOOL:
@@ -1798,6 +1871,11 @@ fillRelOptions(void *rdopts, Size basesize,
options[i].values.bool_val :
((relopt_bool *) options[i].gen)->default_val;
break;
+ case RELOPT_TYPE_TERNARY:
+ *(ternary *) itempos = options[i].isset ?
+ options[i].values.ternary_val :
+ ((relopt_ternary *) options[i].gen)->default_val;
+ break;
case RELOPT_TYPE_INT:
*(int *) itempos = options[i].isset ?
options[i].values.int_val :
@@ -1912,8 +1990,8 @@ default_reloptions(Datum reloptions, bool validate, relopt_kind kind)
offsetof(StdRdOptions, parallel_workers)},
{"vacuum_index_cleanup", RELOPT_TYPE_ENUM,
offsetof(StdRdOptions, vacuum_index_cleanup)},
- {"vacuum_truncate", RELOPT_TYPE_BOOL,
- offsetof(StdRdOptions, vacuum_truncate), offsetof(StdRdOptions, vacuum_truncate_set)},
+ {"vacuum_truncate", RELOPT_TYPE_TERNARY,
+ offsetof(StdRdOptions, vacuum_truncate)},
{"vacuum_max_eager_freeze_failure_rate", RELOPT_TYPE_REAL,
offsetof(StdRdOptions, vacuum_max_eager_freeze_failure_rate)}
};
@@ -1993,7 +2071,6 @@ build_local_reloptions(local_relopts *relopts, Datum options, bool validate)
elems[i].optname = opt->option->name;
elems[i].opttype = opt->option->type;
elems[i].offset = opt->offset;
- elems[i].isset_offset = 0; /* not supported for local relopts yet */
i++;
}
diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c
index 733ef40ae7..977babff54 100644
--- a/src/backend/commands/vacuum.c
+++ b/src/backend/commands/vacuum.c
@@ -2221,9 +2221,9 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams params,
{
StdRdOptions *opts = (StdRdOptions *) rel->rd_options;
- if (opts && opts->vacuum_truncate_set)
+ if (opts && opts->vacuum_truncate != TERNARY_UNSET)
{
- if (opts->vacuum_truncate)
+ if (opts->vacuum_truncate == TERNARY_TRUE)
params.truncate = VACOPTVALUE_ENABLED;
else
params.truncate = VACOPTVALUE_DISABLED;
diff --git a/src/include/access/reloptions.h b/src/include/access/reloptions.h
index a604a4702c..a436697658 100644
--- a/src/include/access/reloptions.h
+++ b/src/include/access/reloptions.h
@@ -29,6 +29,7 @@
typedef enum relopt_type
{
RELOPT_TYPE_BOOL,
+ RELOPT_TYPE_TERNARY, /* on, off, unset */
RELOPT_TYPE_INT,
RELOPT_TYPE_REAL,
RELOPT_TYPE_ENUM,
@@ -80,6 +81,7 @@ typedef struct relopt_value
union
{
bool bool_val;
+ ternary ternary_val;
int int_val;
double real_val;
int enum_val;
@@ -94,6 +96,12 @@ typedef struct relopt_bool
bool default_val;
} relopt_bool;
+typedef struct relopt_rernary
+{
+ relopt_gen gen;
+ int default_val;
+} relopt_ternary;
+
typedef struct relopt_int
{
relopt_gen gen;
@@ -152,19 +160,6 @@ typedef struct
const char *optname; /* option's name */
relopt_type opttype; /* option's datatype */
int offset; /* offset of field in result struct */
-
- /*
- * isset_offset is an optional offset of a field in the result struct that
- * stores whether the option is explicitly set for the relation or if it
- * just picked up the default value. In most cases, this can be
- * accomplished by giving the reloption a special out-of-range default
- * value (e.g., some integer reloptions use -2), but this isn't always
- * possible. For example, a Boolean reloption cannot be given an
- * out-of-range default, so we need another way to discover the source of
- * its value. This offset is only used if given a value greater than
- * zero.
- */
- int isset_offset;
} relopt_parse_elt;
/* Local reloption definition */
@@ -195,6 +190,8 @@ typedef struct local_relopts
extern relopt_kind add_reloption_kind(void);
extern void add_bool_reloption(bits32 kinds, const char *name, const char *desc,
bool default_val, LOCKMODE lockmode);
+extern void add_ternary_reloption(bits32 kinds, const char *name,
+ const char *desc, int default_val, LOCKMODE lockmode);
extern void add_int_reloption(bits32 kinds, const char *name, const char *desc,
int default_val, int min_val, int max_val,
LOCKMODE lockmode);
@@ -214,6 +211,9 @@ extern void register_reloptions_validator(local_relopts *relopts,
extern void add_local_bool_reloption(local_relopts *relopts, const char *name,
const char *desc, bool default_val,
int offset);
+extern void add_local_ternary_reloption(local_relopts *relopts,
+ const char *name, const char *desc,
+ ternary default_val, int offset);
extern void add_local_int_reloption(local_relopts *relopts, const char *name,
const char *desc, int default_val,
int min_val, int max_val, int offset);
diff --git a/src/include/c.h b/src/include/c.h
index 39022f8a9d..9c59ccccf8 100644
--- a/src/include/c.h
+++ b/src/include/c.h
@@ -488,6 +488,22 @@ typedef void (*pg_funcptr_t) (void);
#include <stdbool.h>
+/*
+ * ternary
+ * Boolean value with an extrea "unset" option
+ *
+ * Ternary data type is used in relation options that can be "true", "false" or
+ * "unset". Since relation options are used deep inside the PostgreSQL code,
+ * this type is declared globally.
+*/
+
+typedef enum ternary
+{
+ TERNARY_FALSE = 0,
+ TERNARY_TRUE = 1,
+ TERNARY_UNSET = -1
+} ternary;
+
/* ----------------------------------------------------------------
* Section 3: standard system types
diff --git a/src/include/utils/rel.h b/src/include/utils/rel.h
index b552359915..7346d618f9 100644
--- a/src/include/utils/rel.h
+++ b/src/include/utils/rel.h
@@ -346,8 +346,7 @@ typedef struct StdRdOptions
bool user_catalog_table; /* use as an additional catalog relation */
int parallel_workers; /* max number of parallel workers */
StdRdOptIndexCleanup vacuum_index_cleanup; /* controls index vacuuming */
- bool vacuum_truncate; /* enables vacuum to truncate a relation */
- bool vacuum_truncate_set; /* whether vacuum_truncate is set */
+ ternary vacuum_truncate; /* enables vacuum to truncate a relation */
/*
* Fraction of pages in a relation that vacuum can eagerly scan and fail
--
2.39.2
--nextPart4155600.3ZeAukHxDK
Content-Disposition: attachment;
filename="v2a-0003-Add-alias-to-be-used-as-unset-state.patch"
Content-Transfer-Encoding: 7Bit
Content-Type: text/x-patch; charset="unicode-2-0-utf-8";
name="v2a-0003-Add-alias-to-be-used-as-unset-state.patch"
^ permalink raw reply [nested|flat] 868+ messages in thread
* [PATCH v2a 2/4] Introduce ternary reloptions
@ 2025-09-04 14:41 Nikolay Shaplov <[email protected]>
0 siblings, 0 replies; 868+ messages in thread
From: Nikolay Shaplov @ 2025-09-04 14:41 UTC (permalink / raw)
Introduce ternary reloption as a replacement for current `vacuum_truncate`
implementation. Remove `vacuum_truncate_set` additional flag and using
`TERNARY_UNSET` value instead.
---
src/backend/access/common/reloptions.c | 129 ++++++++++++++++++++-----
src/backend/commands/vacuum.c | 4 +-
src/include/access/reloptions.h | 26 ++---
src/include/c.h | 16 +++
src/include/utils/rel.h | 3 +-
5 files changed, 135 insertions(+), 43 deletions(-)
diff --git a/src/backend/access/common/reloptions.c b/src/backend/access/common/reloptions.c
index 0af3fea68f..f543f11001 100644
--- a/src/backend/access/common/reloptions.c
+++ b/src/backend/access/common/reloptions.c
@@ -40,9 +40,9 @@
*
* To add an option:
*
- * (i) decide on a type (bool, integer, real, enum, string), name, default
- * value, upper and lower bounds (if applicable); for strings, consider a
- * validation routine.
+ * (i) decide on a type (bool, ternary, integer, real, enum, string), name,
+ * default value, upper and lower bounds (if applicable); for strings,
+ * consider a validation routine.
* (ii) add a record below (or use add_<type>_reloption).
* (iii) add it to the appropriate options struct (perhaps StdRdOptions)
* (iv) add it to the appropriate handling routine (perhaps
@@ -147,15 +147,6 @@ static relopt_bool boolRelOpts[] =
},
false
},
- {
- {
- "vacuum_truncate",
- "Enables vacuum to truncate empty pages at the end of this table",
- RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
- ShareUpdateExclusiveLock
- },
- true
- },
{
{
"deduplicate_items",
@@ -170,6 +161,21 @@ static relopt_bool boolRelOpts[] =
{{NULL}}
};
+static relopt_ternary ternaryRelOpts[] =
+{
+ {
+ {
+ "vacuum_truncate",
+ "Enables vacuum to truncate empty pages at the end of this table",
+ RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
+ ShareUpdateExclusiveLock
+ },
+ TERNARY_UNSET
+ },
+ /* list terminator */
+ {{NULL}}
+};
+
static relopt_int intRelOpts[] =
{
{
@@ -600,6 +606,13 @@ initialize_reloptions(void)
boolRelOpts[i].gen.lockmode));
j++;
}
+ for (i = 0; ternaryRelOpts[i].gen.name; i++)
+ {
+ Assert(DoLockModesConflict(ternaryRelOpts[i].gen.lockmode,
+ ternaryRelOpts[i].gen.lockmode));
+ j++;
+ }
+
for (i = 0; intRelOpts[i].gen.name; i++)
{
Assert(DoLockModesConflict(intRelOpts[i].gen.lockmode,
@@ -640,6 +653,14 @@ initialize_reloptions(void)
j++;
}
+ for (i = 0; ternaryRelOpts[i].gen.name; i++)
+ {
+ relOpts[j] = &ternaryRelOpts[i].gen;
+ relOpts[j]->type = RELOPT_TYPE_TERNARY;
+ relOpts[j]->namelen = strlen(relOpts[j]->name);
+ j++;
+ }
+
for (i = 0; intRelOpts[i].gen.name; i++)
{
relOpts[j] = &intRelOpts[i].gen;
@@ -800,6 +821,9 @@ allocate_reloption(bits32 kinds, int type, const char *name, const char *desc,
case RELOPT_TYPE_BOOL:
size = sizeof(relopt_bool);
break;
+ case RELOPT_TYPE_TERNARY:
+ size = sizeof(relopt_ternary);
+ break;
case RELOPT_TYPE_INT:
size = sizeof(relopt_int);
break;
@@ -883,6 +907,54 @@ add_local_bool_reloption(local_relopts *relopts, const char *name,
add_local_reloption(relopts, (relopt_gen *) newoption, offset);
}
+/*
+ * init_ternary_reloption
+ * Allocate and initialize a new ternary reloption
+ */
+static relopt_ternary *
+init_ternary_reloption(bits32 kinds, const char *name, const char *desc,
+ ternary default_val, LOCKMODE lockmode)
+{
+ relopt_ternary *newoption;
+
+ newoption = (relopt_ternary *) allocate_reloption(kinds,
+ RELOPT_TYPE_TERNARY, name, desc, lockmode);
+ newoption->default_val = default_val;
+
+ return newoption;
+}
+
+/*
+ * add_ternary_reloption
+ * Add a new ternary reloption
+ */
+void
+add_ternary_reloption(bits32 kinds, const char *name, const char *desc,
+ ternary default_val, LOCKMODE lockmode)
+{
+ relopt_ternary *newoption = init_ternary_reloption(kinds, name, desc,
+ default_val, lockmode);
+
+ add_reloption((relopt_gen *) newoption);
+}
+
+/*
+ * add_local_ternary_reloption
+ * Add a new ternary local reloption
+ *
+ * 'offset' is offset of ternary-typed field.
+ */
+void
+add_local_ternary_reloption(local_relopts *relopts, const char *name,
+ const char *desc, ternary default_val,
+ int offset)
+{
+ relopt_ternary *newoption = init_ternary_reloption(RELOPT_KIND_LOCAL,
+ name, desc,
+ default_val, 0);
+
+ add_local_reloption(relopts, (relopt_gen *) newoption, offset);
+}
/*
* init_real_reloption
@@ -1617,6 +1689,18 @@ parse_one_reloption(relopt_value *option, char *text_str, int text_len,
option->gen->name, value)));
}
break;
+ case RELOPT_TYPE_TERNARY:
+ {
+ bool b;
+ parsed = parse_bool(value, &b);
+ option->values.ternary_val = b ? TERNARY_TRUE : TERNARY_FALSE;
+ if (validate && !parsed)
+ ereport(ERROR,
+ (errcode(ERRCODE_INVALID_PARAMETER_VALUE),
+ errmsg("invalid value for ternary option \"%s\": %s",
+ option->gen->name, value)));
+ }
+ break;
case RELOPT_TYPE_INT:
{
relopt_int *optint = (relopt_int *) option->gen;
@@ -1780,17 +1864,6 @@ fillRelOptions(void *rdopts, Size basesize,
char *itempos = ((char *) rdopts) + elems[j].offset;
char *string_val;
- /*
- * If isset_offset is provided, store whether the reloption is
- * set there.
- */
- if (elems[j].isset_offset > 0)
- {
- char *setpos = ((char *) rdopts) + elems[j].isset_offset;
-
- *(bool *) setpos = options[i].isset;
- }
-
switch (options[i].gen->type)
{
case RELOPT_TYPE_BOOL:
@@ -1798,6 +1871,11 @@ fillRelOptions(void *rdopts, Size basesize,
options[i].values.bool_val :
((relopt_bool *) options[i].gen)->default_val;
break;
+ case RELOPT_TYPE_TERNARY:
+ *(ternary *) itempos = options[i].isset ?
+ options[i].values.ternary_val :
+ ((relopt_ternary *) options[i].gen)->default_val;
+ break;
case RELOPT_TYPE_INT:
*(int *) itempos = options[i].isset ?
options[i].values.int_val :
@@ -1912,8 +1990,8 @@ default_reloptions(Datum reloptions, bool validate, relopt_kind kind)
offsetof(StdRdOptions, parallel_workers)},
{"vacuum_index_cleanup", RELOPT_TYPE_ENUM,
offsetof(StdRdOptions, vacuum_index_cleanup)},
- {"vacuum_truncate", RELOPT_TYPE_BOOL,
- offsetof(StdRdOptions, vacuum_truncate), offsetof(StdRdOptions, vacuum_truncate_set)},
+ {"vacuum_truncate", RELOPT_TYPE_TERNARY,
+ offsetof(StdRdOptions, vacuum_truncate)},
{"vacuum_max_eager_freeze_failure_rate", RELOPT_TYPE_REAL,
offsetof(StdRdOptions, vacuum_max_eager_freeze_failure_rate)}
};
@@ -1993,7 +2071,6 @@ build_local_reloptions(local_relopts *relopts, Datum options, bool validate)
elems[i].optname = opt->option->name;
elems[i].opttype = opt->option->type;
elems[i].offset = opt->offset;
- elems[i].isset_offset = 0; /* not supported for local relopts yet */
i++;
}
diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c
index 733ef40ae7..977babff54 100644
--- a/src/backend/commands/vacuum.c
+++ b/src/backend/commands/vacuum.c
@@ -2221,9 +2221,9 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams params,
{
StdRdOptions *opts = (StdRdOptions *) rel->rd_options;
- if (opts && opts->vacuum_truncate_set)
+ if (opts && opts->vacuum_truncate != TERNARY_UNSET)
{
- if (opts->vacuum_truncate)
+ if (opts->vacuum_truncate == TERNARY_TRUE)
params.truncate = VACOPTVALUE_ENABLED;
else
params.truncate = VACOPTVALUE_DISABLED;
diff --git a/src/include/access/reloptions.h b/src/include/access/reloptions.h
index a604a4702c..a436697658 100644
--- a/src/include/access/reloptions.h
+++ b/src/include/access/reloptions.h
@@ -29,6 +29,7 @@
typedef enum relopt_type
{
RELOPT_TYPE_BOOL,
+ RELOPT_TYPE_TERNARY, /* on, off, unset */
RELOPT_TYPE_INT,
RELOPT_TYPE_REAL,
RELOPT_TYPE_ENUM,
@@ -80,6 +81,7 @@ typedef struct relopt_value
union
{
bool bool_val;
+ ternary ternary_val;
int int_val;
double real_val;
int enum_val;
@@ -94,6 +96,12 @@ typedef struct relopt_bool
bool default_val;
} relopt_bool;
+typedef struct relopt_rernary
+{
+ relopt_gen gen;
+ int default_val;
+} relopt_ternary;
+
typedef struct relopt_int
{
relopt_gen gen;
@@ -152,19 +160,6 @@ typedef struct
const char *optname; /* option's name */
relopt_type opttype; /* option's datatype */
int offset; /* offset of field in result struct */
-
- /*
- * isset_offset is an optional offset of a field in the result struct that
- * stores whether the option is explicitly set for the relation or if it
- * just picked up the default value. In most cases, this can be
- * accomplished by giving the reloption a special out-of-range default
- * value (e.g., some integer reloptions use -2), but this isn't always
- * possible. For example, a Boolean reloption cannot be given an
- * out-of-range default, so we need another way to discover the source of
- * its value. This offset is only used if given a value greater than
- * zero.
- */
- int isset_offset;
} relopt_parse_elt;
/* Local reloption definition */
@@ -195,6 +190,8 @@ typedef struct local_relopts
extern relopt_kind add_reloption_kind(void);
extern void add_bool_reloption(bits32 kinds, const char *name, const char *desc,
bool default_val, LOCKMODE lockmode);
+extern void add_ternary_reloption(bits32 kinds, const char *name,
+ const char *desc, int default_val, LOCKMODE lockmode);
extern void add_int_reloption(bits32 kinds, const char *name, const char *desc,
int default_val, int min_val, int max_val,
LOCKMODE lockmode);
@@ -214,6 +211,9 @@ extern void register_reloptions_validator(local_relopts *relopts,
extern void add_local_bool_reloption(local_relopts *relopts, const char *name,
const char *desc, bool default_val,
int offset);
+extern void add_local_ternary_reloption(local_relopts *relopts,
+ const char *name, const char *desc,
+ ternary default_val, int offset);
extern void add_local_int_reloption(local_relopts *relopts, const char *name,
const char *desc, int default_val,
int min_val, int max_val, int offset);
diff --git a/src/include/c.h b/src/include/c.h
index 39022f8a9d..9c59ccccf8 100644
--- a/src/include/c.h
+++ b/src/include/c.h
@@ -488,6 +488,22 @@ typedef void (*pg_funcptr_t) (void);
#include <stdbool.h>
+/*
+ * ternary
+ * Boolean value with an extrea "unset" option
+ *
+ * Ternary data type is used in relation options that can be "true", "false" or
+ * "unset". Since relation options are used deep inside the PostgreSQL code,
+ * this type is declared globally.
+*/
+
+typedef enum ternary
+{
+ TERNARY_FALSE = 0,
+ TERNARY_TRUE = 1,
+ TERNARY_UNSET = -1
+} ternary;
+
/* ----------------------------------------------------------------
* Section 3: standard system types
diff --git a/src/include/utils/rel.h b/src/include/utils/rel.h
index b552359915..7346d618f9 100644
--- a/src/include/utils/rel.h
+++ b/src/include/utils/rel.h
@@ -346,8 +346,7 @@ typedef struct StdRdOptions
bool user_catalog_table; /* use as an additional catalog relation */
int parallel_workers; /* max number of parallel workers */
StdRdOptIndexCleanup vacuum_index_cleanup; /* controls index vacuuming */
- bool vacuum_truncate; /* enables vacuum to truncate a relation */
- bool vacuum_truncate_set; /* whether vacuum_truncate is set */
+ ternary vacuum_truncate; /* enables vacuum to truncate a relation */
/*
* Fraction of pages in a relation that vacuum can eagerly scan and fail
--
2.39.2
--nextPart4155600.3ZeAukHxDK
Content-Disposition: attachment;
filename="v2a-0003-Add-alias-to-be-used-as-unset-state.patch"
Content-Transfer-Encoding: 7Bit
Content-Type: text/x-patch; charset="unicode-2-0-utf-8";
name="v2a-0003-Add-alias-to-be-used-as-unset-state.patch"
^ permalink raw reply [nested|flat] 868+ messages in thread
* [PATCH v2a 2/4] Introduce ternary reloptions
@ 2025-09-04 14:41 Nikolay Shaplov <[email protected]>
0 siblings, 0 replies; 868+ messages in thread
From: Nikolay Shaplov @ 2025-09-04 14:41 UTC (permalink / raw)
Introduce ternary reloption as a replacement for current `vacuum_truncate`
implementation. Remove `vacuum_truncate_set` additional flag and using
`TERNARY_UNSET` value instead.
---
src/backend/access/common/reloptions.c | 129 ++++++++++++++++++++-----
src/backend/commands/vacuum.c | 4 +-
src/include/access/reloptions.h | 26 ++---
src/include/c.h | 16 +++
src/include/utils/rel.h | 3 +-
5 files changed, 135 insertions(+), 43 deletions(-)
diff --git a/src/backend/access/common/reloptions.c b/src/backend/access/common/reloptions.c
index 0af3fea68f..f543f11001 100644
--- a/src/backend/access/common/reloptions.c
+++ b/src/backend/access/common/reloptions.c
@@ -40,9 +40,9 @@
*
* To add an option:
*
- * (i) decide on a type (bool, integer, real, enum, string), name, default
- * value, upper and lower bounds (if applicable); for strings, consider a
- * validation routine.
+ * (i) decide on a type (bool, ternary, integer, real, enum, string), name,
+ * default value, upper and lower bounds (if applicable); for strings,
+ * consider a validation routine.
* (ii) add a record below (or use add_<type>_reloption).
* (iii) add it to the appropriate options struct (perhaps StdRdOptions)
* (iv) add it to the appropriate handling routine (perhaps
@@ -147,15 +147,6 @@ static relopt_bool boolRelOpts[] =
},
false
},
- {
- {
- "vacuum_truncate",
- "Enables vacuum to truncate empty pages at the end of this table",
- RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
- ShareUpdateExclusiveLock
- },
- true
- },
{
{
"deduplicate_items",
@@ -170,6 +161,21 @@ static relopt_bool boolRelOpts[] =
{{NULL}}
};
+static relopt_ternary ternaryRelOpts[] =
+{
+ {
+ {
+ "vacuum_truncate",
+ "Enables vacuum to truncate empty pages at the end of this table",
+ RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
+ ShareUpdateExclusiveLock
+ },
+ TERNARY_UNSET
+ },
+ /* list terminator */
+ {{NULL}}
+};
+
static relopt_int intRelOpts[] =
{
{
@@ -600,6 +606,13 @@ initialize_reloptions(void)
boolRelOpts[i].gen.lockmode));
j++;
}
+ for (i = 0; ternaryRelOpts[i].gen.name; i++)
+ {
+ Assert(DoLockModesConflict(ternaryRelOpts[i].gen.lockmode,
+ ternaryRelOpts[i].gen.lockmode));
+ j++;
+ }
+
for (i = 0; intRelOpts[i].gen.name; i++)
{
Assert(DoLockModesConflict(intRelOpts[i].gen.lockmode,
@@ -640,6 +653,14 @@ initialize_reloptions(void)
j++;
}
+ for (i = 0; ternaryRelOpts[i].gen.name; i++)
+ {
+ relOpts[j] = &ternaryRelOpts[i].gen;
+ relOpts[j]->type = RELOPT_TYPE_TERNARY;
+ relOpts[j]->namelen = strlen(relOpts[j]->name);
+ j++;
+ }
+
for (i = 0; intRelOpts[i].gen.name; i++)
{
relOpts[j] = &intRelOpts[i].gen;
@@ -800,6 +821,9 @@ allocate_reloption(bits32 kinds, int type, const char *name, const char *desc,
case RELOPT_TYPE_BOOL:
size = sizeof(relopt_bool);
break;
+ case RELOPT_TYPE_TERNARY:
+ size = sizeof(relopt_ternary);
+ break;
case RELOPT_TYPE_INT:
size = sizeof(relopt_int);
break;
@@ -883,6 +907,54 @@ add_local_bool_reloption(local_relopts *relopts, const char *name,
add_local_reloption(relopts, (relopt_gen *) newoption, offset);
}
+/*
+ * init_ternary_reloption
+ * Allocate and initialize a new ternary reloption
+ */
+static relopt_ternary *
+init_ternary_reloption(bits32 kinds, const char *name, const char *desc,
+ ternary default_val, LOCKMODE lockmode)
+{
+ relopt_ternary *newoption;
+
+ newoption = (relopt_ternary *) allocate_reloption(kinds,
+ RELOPT_TYPE_TERNARY, name, desc, lockmode);
+ newoption->default_val = default_val;
+
+ return newoption;
+}
+
+/*
+ * add_ternary_reloption
+ * Add a new ternary reloption
+ */
+void
+add_ternary_reloption(bits32 kinds, const char *name, const char *desc,
+ ternary default_val, LOCKMODE lockmode)
+{
+ relopt_ternary *newoption = init_ternary_reloption(kinds, name, desc,
+ default_val, lockmode);
+
+ add_reloption((relopt_gen *) newoption);
+}
+
+/*
+ * add_local_ternary_reloption
+ * Add a new ternary local reloption
+ *
+ * 'offset' is offset of ternary-typed field.
+ */
+void
+add_local_ternary_reloption(local_relopts *relopts, const char *name,
+ const char *desc, ternary default_val,
+ int offset)
+{
+ relopt_ternary *newoption = init_ternary_reloption(RELOPT_KIND_LOCAL,
+ name, desc,
+ default_val, 0);
+
+ add_local_reloption(relopts, (relopt_gen *) newoption, offset);
+}
/*
* init_real_reloption
@@ -1617,6 +1689,18 @@ parse_one_reloption(relopt_value *option, char *text_str, int text_len,
option->gen->name, value)));
}
break;
+ case RELOPT_TYPE_TERNARY:
+ {
+ bool b;
+ parsed = parse_bool(value, &b);
+ option->values.ternary_val = b ? TERNARY_TRUE : TERNARY_FALSE;
+ if (validate && !parsed)
+ ereport(ERROR,
+ (errcode(ERRCODE_INVALID_PARAMETER_VALUE),
+ errmsg("invalid value for ternary option \"%s\": %s",
+ option->gen->name, value)));
+ }
+ break;
case RELOPT_TYPE_INT:
{
relopt_int *optint = (relopt_int *) option->gen;
@@ -1780,17 +1864,6 @@ fillRelOptions(void *rdopts, Size basesize,
char *itempos = ((char *) rdopts) + elems[j].offset;
char *string_val;
- /*
- * If isset_offset is provided, store whether the reloption is
- * set there.
- */
- if (elems[j].isset_offset > 0)
- {
- char *setpos = ((char *) rdopts) + elems[j].isset_offset;
-
- *(bool *) setpos = options[i].isset;
- }
-
switch (options[i].gen->type)
{
case RELOPT_TYPE_BOOL:
@@ -1798,6 +1871,11 @@ fillRelOptions(void *rdopts, Size basesize,
options[i].values.bool_val :
((relopt_bool *) options[i].gen)->default_val;
break;
+ case RELOPT_TYPE_TERNARY:
+ *(ternary *) itempos = options[i].isset ?
+ options[i].values.ternary_val :
+ ((relopt_ternary *) options[i].gen)->default_val;
+ break;
case RELOPT_TYPE_INT:
*(int *) itempos = options[i].isset ?
options[i].values.int_val :
@@ -1912,8 +1990,8 @@ default_reloptions(Datum reloptions, bool validate, relopt_kind kind)
offsetof(StdRdOptions, parallel_workers)},
{"vacuum_index_cleanup", RELOPT_TYPE_ENUM,
offsetof(StdRdOptions, vacuum_index_cleanup)},
- {"vacuum_truncate", RELOPT_TYPE_BOOL,
- offsetof(StdRdOptions, vacuum_truncate), offsetof(StdRdOptions, vacuum_truncate_set)},
+ {"vacuum_truncate", RELOPT_TYPE_TERNARY,
+ offsetof(StdRdOptions, vacuum_truncate)},
{"vacuum_max_eager_freeze_failure_rate", RELOPT_TYPE_REAL,
offsetof(StdRdOptions, vacuum_max_eager_freeze_failure_rate)}
};
@@ -1993,7 +2071,6 @@ build_local_reloptions(local_relopts *relopts, Datum options, bool validate)
elems[i].optname = opt->option->name;
elems[i].opttype = opt->option->type;
elems[i].offset = opt->offset;
- elems[i].isset_offset = 0; /* not supported for local relopts yet */
i++;
}
diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c
index 733ef40ae7..977babff54 100644
--- a/src/backend/commands/vacuum.c
+++ b/src/backend/commands/vacuum.c
@@ -2221,9 +2221,9 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams params,
{
StdRdOptions *opts = (StdRdOptions *) rel->rd_options;
- if (opts && opts->vacuum_truncate_set)
+ if (opts && opts->vacuum_truncate != TERNARY_UNSET)
{
- if (opts->vacuum_truncate)
+ if (opts->vacuum_truncate == TERNARY_TRUE)
params.truncate = VACOPTVALUE_ENABLED;
else
params.truncate = VACOPTVALUE_DISABLED;
diff --git a/src/include/access/reloptions.h b/src/include/access/reloptions.h
index a604a4702c..a436697658 100644
--- a/src/include/access/reloptions.h
+++ b/src/include/access/reloptions.h
@@ -29,6 +29,7 @@
typedef enum relopt_type
{
RELOPT_TYPE_BOOL,
+ RELOPT_TYPE_TERNARY, /* on, off, unset */
RELOPT_TYPE_INT,
RELOPT_TYPE_REAL,
RELOPT_TYPE_ENUM,
@@ -80,6 +81,7 @@ typedef struct relopt_value
union
{
bool bool_val;
+ ternary ternary_val;
int int_val;
double real_val;
int enum_val;
@@ -94,6 +96,12 @@ typedef struct relopt_bool
bool default_val;
} relopt_bool;
+typedef struct relopt_rernary
+{
+ relopt_gen gen;
+ int default_val;
+} relopt_ternary;
+
typedef struct relopt_int
{
relopt_gen gen;
@@ -152,19 +160,6 @@ typedef struct
const char *optname; /* option's name */
relopt_type opttype; /* option's datatype */
int offset; /* offset of field in result struct */
-
- /*
- * isset_offset is an optional offset of a field in the result struct that
- * stores whether the option is explicitly set for the relation or if it
- * just picked up the default value. In most cases, this can be
- * accomplished by giving the reloption a special out-of-range default
- * value (e.g., some integer reloptions use -2), but this isn't always
- * possible. For example, a Boolean reloption cannot be given an
- * out-of-range default, so we need another way to discover the source of
- * its value. This offset is only used if given a value greater than
- * zero.
- */
- int isset_offset;
} relopt_parse_elt;
/* Local reloption definition */
@@ -195,6 +190,8 @@ typedef struct local_relopts
extern relopt_kind add_reloption_kind(void);
extern void add_bool_reloption(bits32 kinds, const char *name, const char *desc,
bool default_val, LOCKMODE lockmode);
+extern void add_ternary_reloption(bits32 kinds, const char *name,
+ const char *desc, int default_val, LOCKMODE lockmode);
extern void add_int_reloption(bits32 kinds, const char *name, const char *desc,
int default_val, int min_val, int max_val,
LOCKMODE lockmode);
@@ -214,6 +211,9 @@ extern void register_reloptions_validator(local_relopts *relopts,
extern void add_local_bool_reloption(local_relopts *relopts, const char *name,
const char *desc, bool default_val,
int offset);
+extern void add_local_ternary_reloption(local_relopts *relopts,
+ const char *name, const char *desc,
+ ternary default_val, int offset);
extern void add_local_int_reloption(local_relopts *relopts, const char *name,
const char *desc, int default_val,
int min_val, int max_val, int offset);
diff --git a/src/include/c.h b/src/include/c.h
index 39022f8a9d..9c59ccccf8 100644
--- a/src/include/c.h
+++ b/src/include/c.h
@@ -488,6 +488,22 @@ typedef void (*pg_funcptr_t) (void);
#include <stdbool.h>
+/*
+ * ternary
+ * Boolean value with an extrea "unset" option
+ *
+ * Ternary data type is used in relation options that can be "true", "false" or
+ * "unset". Since relation options are used deep inside the PostgreSQL code,
+ * this type is declared globally.
+*/
+
+typedef enum ternary
+{
+ TERNARY_FALSE = 0,
+ TERNARY_TRUE = 1,
+ TERNARY_UNSET = -1
+} ternary;
+
/* ----------------------------------------------------------------
* Section 3: standard system types
diff --git a/src/include/utils/rel.h b/src/include/utils/rel.h
index b552359915..7346d618f9 100644
--- a/src/include/utils/rel.h
+++ b/src/include/utils/rel.h
@@ -346,8 +346,7 @@ typedef struct StdRdOptions
bool user_catalog_table; /* use as an additional catalog relation */
int parallel_workers; /* max number of parallel workers */
StdRdOptIndexCleanup vacuum_index_cleanup; /* controls index vacuuming */
- bool vacuum_truncate; /* enables vacuum to truncate a relation */
- bool vacuum_truncate_set; /* whether vacuum_truncate is set */
+ ternary vacuum_truncate; /* enables vacuum to truncate a relation */
/*
* Fraction of pages in a relation that vacuum can eagerly scan and fail
--
2.39.2
--nextPart4155600.3ZeAukHxDK
Content-Disposition: attachment;
filename="v2a-0003-Add-alias-to-be-used-as-unset-state.patch"
Content-Transfer-Encoding: 7Bit
Content-Type: text/x-patch; charset="unicode-2-0-utf-8";
name="v2a-0003-Add-alias-to-be-used-as-unset-state.patch"
^ permalink raw reply [nested|flat] 868+ messages in thread
* [PATCH v2 2/4] Introduce ternary reloptions
@ 2025-09-04 14:41 Nikolay Shaplov <[email protected]>
0 siblings, 0 replies; 868+ messages in thread
From: Nikolay Shaplov @ 2025-09-04 14:41 UTC (permalink / raw)
Introduce ternary reloption as a replacement for current `vacuum_truncate`
implementation. Remove `vacuum_truncate_set` additional flag and using
`TERNARY_UNSET` value instead.
---
src/backend/access/common/reloptions.c | 129 ++++++++++++++++++++-----
src/backend/commands/vacuum.c | 4 +-
src/include/access/reloptions.h | 26 ++---
src/include/c.h | 16 +++
src/include/utils/rel.h | 3 +-
5 files changed, 135 insertions(+), 43 deletions(-)
diff --git a/src/backend/access/common/reloptions.c b/src/backend/access/common/reloptions.c
index 0af3fea68f..f543f11001 100644
--- a/src/backend/access/common/reloptions.c
+++ b/src/backend/access/common/reloptions.c
@@ -40,9 +40,9 @@
*
* To add an option:
*
- * (i) decide on a type (bool, integer, real, enum, string), name, default
- * value, upper and lower bounds (if applicable); for strings, consider a
- * validation routine.
+ * (i) decide on a type (bool, ternary, integer, real, enum, string), name,
+ * default value, upper and lower bounds (if applicable); for strings,
+ * consider a validation routine.
* (ii) add a record below (or use add_<type>_reloption).
* (iii) add it to the appropriate options struct (perhaps StdRdOptions)
* (iv) add it to the appropriate handling routine (perhaps
@@ -147,15 +147,6 @@ static relopt_bool boolRelOpts[] =
},
false
},
- {
- {
- "vacuum_truncate",
- "Enables vacuum to truncate empty pages at the end of this table",
- RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
- ShareUpdateExclusiveLock
- },
- true
- },
{
{
"deduplicate_items",
@@ -170,6 +161,21 @@ static relopt_bool boolRelOpts[] =
{{NULL}}
};
+static relopt_ternary ternaryRelOpts[] =
+{
+ {
+ {
+ "vacuum_truncate",
+ "Enables vacuum to truncate empty pages at the end of this table",
+ RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
+ ShareUpdateExclusiveLock
+ },
+ TERNARY_UNSET
+ },
+ /* list terminator */
+ {{NULL}}
+};
+
static relopt_int intRelOpts[] =
{
{
@@ -600,6 +606,13 @@ initialize_reloptions(void)
boolRelOpts[i].gen.lockmode));
j++;
}
+ for (i = 0; ternaryRelOpts[i].gen.name; i++)
+ {
+ Assert(DoLockModesConflict(ternaryRelOpts[i].gen.lockmode,
+ ternaryRelOpts[i].gen.lockmode));
+ j++;
+ }
+
for (i = 0; intRelOpts[i].gen.name; i++)
{
Assert(DoLockModesConflict(intRelOpts[i].gen.lockmode,
@@ -640,6 +653,14 @@ initialize_reloptions(void)
j++;
}
+ for (i = 0; ternaryRelOpts[i].gen.name; i++)
+ {
+ relOpts[j] = &ternaryRelOpts[i].gen;
+ relOpts[j]->type = RELOPT_TYPE_TERNARY;
+ relOpts[j]->namelen = strlen(relOpts[j]->name);
+ j++;
+ }
+
for (i = 0; intRelOpts[i].gen.name; i++)
{
relOpts[j] = &intRelOpts[i].gen;
@@ -800,6 +821,9 @@ allocate_reloption(bits32 kinds, int type, const char *name, const char *desc,
case RELOPT_TYPE_BOOL:
size = sizeof(relopt_bool);
break;
+ case RELOPT_TYPE_TERNARY:
+ size = sizeof(relopt_ternary);
+ break;
case RELOPT_TYPE_INT:
size = sizeof(relopt_int);
break;
@@ -883,6 +907,54 @@ add_local_bool_reloption(local_relopts *relopts, const char *name,
add_local_reloption(relopts, (relopt_gen *) newoption, offset);
}
+/*
+ * init_ternary_reloption
+ * Allocate and initialize a new ternary reloption
+ */
+static relopt_ternary *
+init_ternary_reloption(bits32 kinds, const char *name, const char *desc,
+ ternary default_val, LOCKMODE lockmode)
+{
+ relopt_ternary *newoption;
+
+ newoption = (relopt_ternary *) allocate_reloption(kinds,
+ RELOPT_TYPE_TERNARY, name, desc, lockmode);
+ newoption->default_val = default_val;
+
+ return newoption;
+}
+
+/*
+ * add_ternary_reloption
+ * Add a new ternary reloption
+ */
+void
+add_ternary_reloption(bits32 kinds, const char *name, const char *desc,
+ ternary default_val, LOCKMODE lockmode)
+{
+ relopt_ternary *newoption = init_ternary_reloption(kinds, name, desc,
+ default_val, lockmode);
+
+ add_reloption((relopt_gen *) newoption);
+}
+
+/*
+ * add_local_ternary_reloption
+ * Add a new ternary local reloption
+ *
+ * 'offset' is offset of ternary-typed field.
+ */
+void
+add_local_ternary_reloption(local_relopts *relopts, const char *name,
+ const char *desc, ternary default_val,
+ int offset)
+{
+ relopt_ternary *newoption = init_ternary_reloption(RELOPT_KIND_LOCAL,
+ name, desc,
+ default_val, 0);
+
+ add_local_reloption(relopts, (relopt_gen *) newoption, offset);
+}
/*
* init_real_reloption
@@ -1617,6 +1689,18 @@ parse_one_reloption(relopt_value *option, char *text_str, int text_len,
option->gen->name, value)));
}
break;
+ case RELOPT_TYPE_TERNARY:
+ {
+ bool b;
+ parsed = parse_bool(value, &b);
+ option->values.ternary_val = b ? TERNARY_TRUE : TERNARY_FALSE;
+ if (validate && !parsed)
+ ereport(ERROR,
+ (errcode(ERRCODE_INVALID_PARAMETER_VALUE),
+ errmsg("invalid value for ternary option \"%s\": %s",
+ option->gen->name, value)));
+ }
+ break;
case RELOPT_TYPE_INT:
{
relopt_int *optint = (relopt_int *) option->gen;
@@ -1780,17 +1864,6 @@ fillRelOptions(void *rdopts, Size basesize,
char *itempos = ((char *) rdopts) + elems[j].offset;
char *string_val;
- /*
- * If isset_offset is provided, store whether the reloption is
- * set there.
- */
- if (elems[j].isset_offset > 0)
- {
- char *setpos = ((char *) rdopts) + elems[j].isset_offset;
-
- *(bool *) setpos = options[i].isset;
- }
-
switch (options[i].gen->type)
{
case RELOPT_TYPE_BOOL:
@@ -1798,6 +1871,11 @@ fillRelOptions(void *rdopts, Size basesize,
options[i].values.bool_val :
((relopt_bool *) options[i].gen)->default_val;
break;
+ case RELOPT_TYPE_TERNARY:
+ *(ternary *) itempos = options[i].isset ?
+ options[i].values.ternary_val :
+ ((relopt_ternary *) options[i].gen)->default_val;
+ break;
case RELOPT_TYPE_INT:
*(int *) itempos = options[i].isset ?
options[i].values.int_val :
@@ -1912,8 +1990,8 @@ default_reloptions(Datum reloptions, bool validate, relopt_kind kind)
offsetof(StdRdOptions, parallel_workers)},
{"vacuum_index_cleanup", RELOPT_TYPE_ENUM,
offsetof(StdRdOptions, vacuum_index_cleanup)},
- {"vacuum_truncate", RELOPT_TYPE_BOOL,
- offsetof(StdRdOptions, vacuum_truncate), offsetof(StdRdOptions, vacuum_truncate_set)},
+ {"vacuum_truncate", RELOPT_TYPE_TERNARY,
+ offsetof(StdRdOptions, vacuum_truncate)},
{"vacuum_max_eager_freeze_failure_rate", RELOPT_TYPE_REAL,
offsetof(StdRdOptions, vacuum_max_eager_freeze_failure_rate)}
};
@@ -1993,7 +2071,6 @@ build_local_reloptions(local_relopts *relopts, Datum options, bool validate)
elems[i].optname = opt->option->name;
elems[i].opttype = opt->option->type;
elems[i].offset = opt->offset;
- elems[i].isset_offset = 0; /* not supported for local relopts yet */
i++;
}
diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c
index 733ef40ae7..977babff54 100644
--- a/src/backend/commands/vacuum.c
+++ b/src/backend/commands/vacuum.c
@@ -2221,9 +2221,9 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams params,
{
StdRdOptions *opts = (StdRdOptions *) rel->rd_options;
- if (opts && opts->vacuum_truncate_set)
+ if (opts && opts->vacuum_truncate != TERNARY_UNSET)
{
- if (opts->vacuum_truncate)
+ if (opts->vacuum_truncate == TERNARY_TRUE)
params.truncate = VACOPTVALUE_ENABLED;
else
params.truncate = VACOPTVALUE_DISABLED;
diff --git a/src/include/access/reloptions.h b/src/include/access/reloptions.h
index a604a4702c..a436697658 100644
--- a/src/include/access/reloptions.h
+++ b/src/include/access/reloptions.h
@@ -29,6 +29,7 @@
typedef enum relopt_type
{
RELOPT_TYPE_BOOL,
+ RELOPT_TYPE_TERNARY, /* on, off, unset */
RELOPT_TYPE_INT,
RELOPT_TYPE_REAL,
RELOPT_TYPE_ENUM,
@@ -80,6 +81,7 @@ typedef struct relopt_value
union
{
bool bool_val;
+ ternary ternary_val;
int int_val;
double real_val;
int enum_val;
@@ -94,6 +96,12 @@ typedef struct relopt_bool
bool default_val;
} relopt_bool;
+typedef struct relopt_rernary
+{
+ relopt_gen gen;
+ int default_val;
+} relopt_ternary;
+
typedef struct relopt_int
{
relopt_gen gen;
@@ -152,19 +160,6 @@ typedef struct
const char *optname; /* option's name */
relopt_type opttype; /* option's datatype */
int offset; /* offset of field in result struct */
-
- /*
- * isset_offset is an optional offset of a field in the result struct that
- * stores whether the option is explicitly set for the relation or if it
- * just picked up the default value. In most cases, this can be
- * accomplished by giving the reloption a special out-of-range default
- * value (e.g., some integer reloptions use -2), but this isn't always
- * possible. For example, a Boolean reloption cannot be given an
- * out-of-range default, so we need another way to discover the source of
- * its value. This offset is only used if given a value greater than
- * zero.
- */
- int isset_offset;
} relopt_parse_elt;
/* Local reloption definition */
@@ -195,6 +190,8 @@ typedef struct local_relopts
extern relopt_kind add_reloption_kind(void);
extern void add_bool_reloption(bits32 kinds, const char *name, const char *desc,
bool default_val, LOCKMODE lockmode);
+extern void add_ternary_reloption(bits32 kinds, const char *name,
+ const char *desc, int default_val, LOCKMODE lockmode);
extern void add_int_reloption(bits32 kinds, const char *name, const char *desc,
int default_val, int min_val, int max_val,
LOCKMODE lockmode);
@@ -214,6 +211,9 @@ extern void register_reloptions_validator(local_relopts *relopts,
extern void add_local_bool_reloption(local_relopts *relopts, const char *name,
const char *desc, bool default_val,
int offset);
+extern void add_local_ternary_reloption(local_relopts *relopts,
+ const char *name, const char *desc,
+ ternary default_val, int offset);
extern void add_local_int_reloption(local_relopts *relopts, const char *name,
const char *desc, int default_val,
int min_val, int max_val, int offset);
diff --git a/src/include/c.h b/src/include/c.h
index 39022f8a9d..9c59ccccf8 100644
--- a/src/include/c.h
+++ b/src/include/c.h
@@ -488,6 +488,22 @@ typedef void (*pg_funcptr_t) (void);
#include <stdbool.h>
+/*
+ * ternary
+ * Boolean value with an extrea "unset" option
+ *
+ * Ternary data type is used in relation options that can be "true", "false" or
+ * "unset". Since relation options are used deep inside the PostgreSQL code,
+ * this type is declared globally.
+*/
+
+typedef enum ternary
+{
+ TERNARY_FALSE = 0,
+ TERNARY_TRUE = 1,
+ TERNARY_UNSET = -1
+} ternary;
+
/* ----------------------------------------------------------------
* Section 3: standard system types
diff --git a/src/include/utils/rel.h b/src/include/utils/rel.h
index b552359915..7346d618f9 100644
--- a/src/include/utils/rel.h
+++ b/src/include/utils/rel.h
@@ -346,8 +346,7 @@ typedef struct StdRdOptions
bool user_catalog_table; /* use as an additional catalog relation */
int parallel_workers; /* max number of parallel workers */
StdRdOptIndexCleanup vacuum_index_cleanup; /* controls index vacuuming */
- bool vacuum_truncate; /* enables vacuum to truncate a relation */
- bool vacuum_truncate_set; /* whether vacuum_truncate is set */
+ ternary vacuum_truncate; /* enables vacuum to truncate a relation */
/*
* Fraction of pages in a relation that vacuum can eagerly scan and fail
--
2.39.2
--nextPart5129932.5fSG56mABF
Content-Disposition: attachment;
filename="v2-0003-Add-alias-to-be-used-as-unset-state.patch"
Content-Transfer-Encoding: 7Bit
Content-Type: text/x-patch; charset="unicode-2-0-utf-8";
name="v2-0003-Add-alias-to-be-used-as-unset-state.patch"
^ permalink raw reply [nested|flat] 868+ messages in thread
* [PATCH v2a 2/4] Introduce ternary reloptions
@ 2025-09-04 14:41 Nikolay Shaplov <[email protected]>
0 siblings, 0 replies; 868+ messages in thread
From: Nikolay Shaplov @ 2025-09-04 14:41 UTC (permalink / raw)
Introduce ternary reloption as a replacement for current `vacuum_truncate`
implementation. Remove `vacuum_truncate_set` additional flag and using
`TERNARY_UNSET` value instead.
---
src/backend/access/common/reloptions.c | 129 ++++++++++++++++++++-----
src/backend/commands/vacuum.c | 4 +-
src/include/access/reloptions.h | 26 ++---
src/include/c.h | 16 +++
src/include/utils/rel.h | 3 +-
5 files changed, 135 insertions(+), 43 deletions(-)
diff --git a/src/backend/access/common/reloptions.c b/src/backend/access/common/reloptions.c
index 0af3fea68f..f543f11001 100644
--- a/src/backend/access/common/reloptions.c
+++ b/src/backend/access/common/reloptions.c
@@ -40,9 +40,9 @@
*
* To add an option:
*
- * (i) decide on a type (bool, integer, real, enum, string), name, default
- * value, upper and lower bounds (if applicable); for strings, consider a
- * validation routine.
+ * (i) decide on a type (bool, ternary, integer, real, enum, string), name,
+ * default value, upper and lower bounds (if applicable); for strings,
+ * consider a validation routine.
* (ii) add a record below (or use add_<type>_reloption).
* (iii) add it to the appropriate options struct (perhaps StdRdOptions)
* (iv) add it to the appropriate handling routine (perhaps
@@ -147,15 +147,6 @@ static relopt_bool boolRelOpts[] =
},
false
},
- {
- {
- "vacuum_truncate",
- "Enables vacuum to truncate empty pages at the end of this table",
- RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
- ShareUpdateExclusiveLock
- },
- true
- },
{
{
"deduplicate_items",
@@ -170,6 +161,21 @@ static relopt_bool boolRelOpts[] =
{{NULL}}
};
+static relopt_ternary ternaryRelOpts[] =
+{
+ {
+ {
+ "vacuum_truncate",
+ "Enables vacuum to truncate empty pages at the end of this table",
+ RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
+ ShareUpdateExclusiveLock
+ },
+ TERNARY_UNSET
+ },
+ /* list terminator */
+ {{NULL}}
+};
+
static relopt_int intRelOpts[] =
{
{
@@ -600,6 +606,13 @@ initialize_reloptions(void)
boolRelOpts[i].gen.lockmode));
j++;
}
+ for (i = 0; ternaryRelOpts[i].gen.name; i++)
+ {
+ Assert(DoLockModesConflict(ternaryRelOpts[i].gen.lockmode,
+ ternaryRelOpts[i].gen.lockmode));
+ j++;
+ }
+
for (i = 0; intRelOpts[i].gen.name; i++)
{
Assert(DoLockModesConflict(intRelOpts[i].gen.lockmode,
@@ -640,6 +653,14 @@ initialize_reloptions(void)
j++;
}
+ for (i = 0; ternaryRelOpts[i].gen.name; i++)
+ {
+ relOpts[j] = &ternaryRelOpts[i].gen;
+ relOpts[j]->type = RELOPT_TYPE_TERNARY;
+ relOpts[j]->namelen = strlen(relOpts[j]->name);
+ j++;
+ }
+
for (i = 0; intRelOpts[i].gen.name; i++)
{
relOpts[j] = &intRelOpts[i].gen;
@@ -800,6 +821,9 @@ allocate_reloption(bits32 kinds, int type, const char *name, const char *desc,
case RELOPT_TYPE_BOOL:
size = sizeof(relopt_bool);
break;
+ case RELOPT_TYPE_TERNARY:
+ size = sizeof(relopt_ternary);
+ break;
case RELOPT_TYPE_INT:
size = sizeof(relopt_int);
break;
@@ -883,6 +907,54 @@ add_local_bool_reloption(local_relopts *relopts, const char *name,
add_local_reloption(relopts, (relopt_gen *) newoption, offset);
}
+/*
+ * init_ternary_reloption
+ * Allocate and initialize a new ternary reloption
+ */
+static relopt_ternary *
+init_ternary_reloption(bits32 kinds, const char *name, const char *desc,
+ ternary default_val, LOCKMODE lockmode)
+{
+ relopt_ternary *newoption;
+
+ newoption = (relopt_ternary *) allocate_reloption(kinds,
+ RELOPT_TYPE_TERNARY, name, desc, lockmode);
+ newoption->default_val = default_val;
+
+ return newoption;
+}
+
+/*
+ * add_ternary_reloption
+ * Add a new ternary reloption
+ */
+void
+add_ternary_reloption(bits32 kinds, const char *name, const char *desc,
+ ternary default_val, LOCKMODE lockmode)
+{
+ relopt_ternary *newoption = init_ternary_reloption(kinds, name, desc,
+ default_val, lockmode);
+
+ add_reloption((relopt_gen *) newoption);
+}
+
+/*
+ * add_local_ternary_reloption
+ * Add a new ternary local reloption
+ *
+ * 'offset' is offset of ternary-typed field.
+ */
+void
+add_local_ternary_reloption(local_relopts *relopts, const char *name,
+ const char *desc, ternary default_val,
+ int offset)
+{
+ relopt_ternary *newoption = init_ternary_reloption(RELOPT_KIND_LOCAL,
+ name, desc,
+ default_val, 0);
+
+ add_local_reloption(relopts, (relopt_gen *) newoption, offset);
+}
/*
* init_real_reloption
@@ -1617,6 +1689,18 @@ parse_one_reloption(relopt_value *option, char *text_str, int text_len,
option->gen->name, value)));
}
break;
+ case RELOPT_TYPE_TERNARY:
+ {
+ bool b;
+ parsed = parse_bool(value, &b);
+ option->values.ternary_val = b ? TERNARY_TRUE : TERNARY_FALSE;
+ if (validate && !parsed)
+ ereport(ERROR,
+ (errcode(ERRCODE_INVALID_PARAMETER_VALUE),
+ errmsg("invalid value for ternary option \"%s\": %s",
+ option->gen->name, value)));
+ }
+ break;
case RELOPT_TYPE_INT:
{
relopt_int *optint = (relopt_int *) option->gen;
@@ -1780,17 +1864,6 @@ fillRelOptions(void *rdopts, Size basesize,
char *itempos = ((char *) rdopts) + elems[j].offset;
char *string_val;
- /*
- * If isset_offset is provided, store whether the reloption is
- * set there.
- */
- if (elems[j].isset_offset > 0)
- {
- char *setpos = ((char *) rdopts) + elems[j].isset_offset;
-
- *(bool *) setpos = options[i].isset;
- }
-
switch (options[i].gen->type)
{
case RELOPT_TYPE_BOOL:
@@ -1798,6 +1871,11 @@ fillRelOptions(void *rdopts, Size basesize,
options[i].values.bool_val :
((relopt_bool *) options[i].gen)->default_val;
break;
+ case RELOPT_TYPE_TERNARY:
+ *(ternary *) itempos = options[i].isset ?
+ options[i].values.ternary_val :
+ ((relopt_ternary *) options[i].gen)->default_val;
+ break;
case RELOPT_TYPE_INT:
*(int *) itempos = options[i].isset ?
options[i].values.int_val :
@@ -1912,8 +1990,8 @@ default_reloptions(Datum reloptions, bool validate, relopt_kind kind)
offsetof(StdRdOptions, parallel_workers)},
{"vacuum_index_cleanup", RELOPT_TYPE_ENUM,
offsetof(StdRdOptions, vacuum_index_cleanup)},
- {"vacuum_truncate", RELOPT_TYPE_BOOL,
- offsetof(StdRdOptions, vacuum_truncate), offsetof(StdRdOptions, vacuum_truncate_set)},
+ {"vacuum_truncate", RELOPT_TYPE_TERNARY,
+ offsetof(StdRdOptions, vacuum_truncate)},
{"vacuum_max_eager_freeze_failure_rate", RELOPT_TYPE_REAL,
offsetof(StdRdOptions, vacuum_max_eager_freeze_failure_rate)}
};
@@ -1993,7 +2071,6 @@ build_local_reloptions(local_relopts *relopts, Datum options, bool validate)
elems[i].optname = opt->option->name;
elems[i].opttype = opt->option->type;
elems[i].offset = opt->offset;
- elems[i].isset_offset = 0; /* not supported for local relopts yet */
i++;
}
diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c
index 733ef40ae7..977babff54 100644
--- a/src/backend/commands/vacuum.c
+++ b/src/backend/commands/vacuum.c
@@ -2221,9 +2221,9 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams params,
{
StdRdOptions *opts = (StdRdOptions *) rel->rd_options;
- if (opts && opts->vacuum_truncate_set)
+ if (opts && opts->vacuum_truncate != TERNARY_UNSET)
{
- if (opts->vacuum_truncate)
+ if (opts->vacuum_truncate == TERNARY_TRUE)
params.truncate = VACOPTVALUE_ENABLED;
else
params.truncate = VACOPTVALUE_DISABLED;
diff --git a/src/include/access/reloptions.h b/src/include/access/reloptions.h
index a604a4702c..a436697658 100644
--- a/src/include/access/reloptions.h
+++ b/src/include/access/reloptions.h
@@ -29,6 +29,7 @@
typedef enum relopt_type
{
RELOPT_TYPE_BOOL,
+ RELOPT_TYPE_TERNARY, /* on, off, unset */
RELOPT_TYPE_INT,
RELOPT_TYPE_REAL,
RELOPT_TYPE_ENUM,
@@ -80,6 +81,7 @@ typedef struct relopt_value
union
{
bool bool_val;
+ ternary ternary_val;
int int_val;
double real_val;
int enum_val;
@@ -94,6 +96,12 @@ typedef struct relopt_bool
bool default_val;
} relopt_bool;
+typedef struct relopt_rernary
+{
+ relopt_gen gen;
+ int default_val;
+} relopt_ternary;
+
typedef struct relopt_int
{
relopt_gen gen;
@@ -152,19 +160,6 @@ typedef struct
const char *optname; /* option's name */
relopt_type opttype; /* option's datatype */
int offset; /* offset of field in result struct */
-
- /*
- * isset_offset is an optional offset of a field in the result struct that
- * stores whether the option is explicitly set for the relation or if it
- * just picked up the default value. In most cases, this can be
- * accomplished by giving the reloption a special out-of-range default
- * value (e.g., some integer reloptions use -2), but this isn't always
- * possible. For example, a Boolean reloption cannot be given an
- * out-of-range default, so we need another way to discover the source of
- * its value. This offset is only used if given a value greater than
- * zero.
- */
- int isset_offset;
} relopt_parse_elt;
/* Local reloption definition */
@@ -195,6 +190,8 @@ typedef struct local_relopts
extern relopt_kind add_reloption_kind(void);
extern void add_bool_reloption(bits32 kinds, const char *name, const char *desc,
bool default_val, LOCKMODE lockmode);
+extern void add_ternary_reloption(bits32 kinds, const char *name,
+ const char *desc, int default_val, LOCKMODE lockmode);
extern void add_int_reloption(bits32 kinds, const char *name, const char *desc,
int default_val, int min_val, int max_val,
LOCKMODE lockmode);
@@ -214,6 +211,9 @@ extern void register_reloptions_validator(local_relopts *relopts,
extern void add_local_bool_reloption(local_relopts *relopts, const char *name,
const char *desc, bool default_val,
int offset);
+extern void add_local_ternary_reloption(local_relopts *relopts,
+ const char *name, const char *desc,
+ ternary default_val, int offset);
extern void add_local_int_reloption(local_relopts *relopts, const char *name,
const char *desc, int default_val,
int min_val, int max_val, int offset);
diff --git a/src/include/c.h b/src/include/c.h
index 39022f8a9d..9c59ccccf8 100644
--- a/src/include/c.h
+++ b/src/include/c.h
@@ -488,6 +488,22 @@ typedef void (*pg_funcptr_t) (void);
#include <stdbool.h>
+/*
+ * ternary
+ * Boolean value with an extrea "unset" option
+ *
+ * Ternary data type is used in relation options that can be "true", "false" or
+ * "unset". Since relation options are used deep inside the PostgreSQL code,
+ * this type is declared globally.
+*/
+
+typedef enum ternary
+{
+ TERNARY_FALSE = 0,
+ TERNARY_TRUE = 1,
+ TERNARY_UNSET = -1
+} ternary;
+
/* ----------------------------------------------------------------
* Section 3: standard system types
diff --git a/src/include/utils/rel.h b/src/include/utils/rel.h
index b552359915..7346d618f9 100644
--- a/src/include/utils/rel.h
+++ b/src/include/utils/rel.h
@@ -346,8 +346,7 @@ typedef struct StdRdOptions
bool user_catalog_table; /* use as an additional catalog relation */
int parallel_workers; /* max number of parallel workers */
StdRdOptIndexCleanup vacuum_index_cleanup; /* controls index vacuuming */
- bool vacuum_truncate; /* enables vacuum to truncate a relation */
- bool vacuum_truncate_set; /* whether vacuum_truncate is set */
+ ternary vacuum_truncate; /* enables vacuum to truncate a relation */
/*
* Fraction of pages in a relation that vacuum can eagerly scan and fail
--
2.39.2
--nextPart4155600.3ZeAukHxDK
Content-Disposition: attachment;
filename="v2a-0003-Add-alias-to-be-used-as-unset-state.patch"
Content-Transfer-Encoding: 7Bit
Content-Type: text/x-patch; charset="unicode-2-0-utf-8";
name="v2a-0003-Add-alias-to-be-used-as-unset-state.patch"
^ permalink raw reply [nested|flat] 868+ messages in thread
* [PATCH v2a 2/4] Introduce ternary reloptions
@ 2025-09-04 14:41 Nikolay Shaplov <[email protected]>
0 siblings, 0 replies; 868+ messages in thread
From: Nikolay Shaplov @ 2025-09-04 14:41 UTC (permalink / raw)
Introduce ternary reloption as a replacement for current `vacuum_truncate`
implementation. Remove `vacuum_truncate_set` additional flag and using
`TERNARY_UNSET` value instead.
---
src/backend/access/common/reloptions.c | 129 ++++++++++++++++++++-----
src/backend/commands/vacuum.c | 4 +-
src/include/access/reloptions.h | 26 ++---
src/include/c.h | 16 +++
src/include/utils/rel.h | 3 +-
5 files changed, 135 insertions(+), 43 deletions(-)
diff --git a/src/backend/access/common/reloptions.c b/src/backend/access/common/reloptions.c
index 0af3fea68f..f543f11001 100644
--- a/src/backend/access/common/reloptions.c
+++ b/src/backend/access/common/reloptions.c
@@ -40,9 +40,9 @@
*
* To add an option:
*
- * (i) decide on a type (bool, integer, real, enum, string), name, default
- * value, upper and lower bounds (if applicable); for strings, consider a
- * validation routine.
+ * (i) decide on a type (bool, ternary, integer, real, enum, string), name,
+ * default value, upper and lower bounds (if applicable); for strings,
+ * consider a validation routine.
* (ii) add a record below (or use add_<type>_reloption).
* (iii) add it to the appropriate options struct (perhaps StdRdOptions)
* (iv) add it to the appropriate handling routine (perhaps
@@ -147,15 +147,6 @@ static relopt_bool boolRelOpts[] =
},
false
},
- {
- {
- "vacuum_truncate",
- "Enables vacuum to truncate empty pages at the end of this table",
- RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
- ShareUpdateExclusiveLock
- },
- true
- },
{
{
"deduplicate_items",
@@ -170,6 +161,21 @@ static relopt_bool boolRelOpts[] =
{{NULL}}
};
+static relopt_ternary ternaryRelOpts[] =
+{
+ {
+ {
+ "vacuum_truncate",
+ "Enables vacuum to truncate empty pages at the end of this table",
+ RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
+ ShareUpdateExclusiveLock
+ },
+ TERNARY_UNSET
+ },
+ /* list terminator */
+ {{NULL}}
+};
+
static relopt_int intRelOpts[] =
{
{
@@ -600,6 +606,13 @@ initialize_reloptions(void)
boolRelOpts[i].gen.lockmode));
j++;
}
+ for (i = 0; ternaryRelOpts[i].gen.name; i++)
+ {
+ Assert(DoLockModesConflict(ternaryRelOpts[i].gen.lockmode,
+ ternaryRelOpts[i].gen.lockmode));
+ j++;
+ }
+
for (i = 0; intRelOpts[i].gen.name; i++)
{
Assert(DoLockModesConflict(intRelOpts[i].gen.lockmode,
@@ -640,6 +653,14 @@ initialize_reloptions(void)
j++;
}
+ for (i = 0; ternaryRelOpts[i].gen.name; i++)
+ {
+ relOpts[j] = &ternaryRelOpts[i].gen;
+ relOpts[j]->type = RELOPT_TYPE_TERNARY;
+ relOpts[j]->namelen = strlen(relOpts[j]->name);
+ j++;
+ }
+
for (i = 0; intRelOpts[i].gen.name; i++)
{
relOpts[j] = &intRelOpts[i].gen;
@@ -800,6 +821,9 @@ allocate_reloption(bits32 kinds, int type, const char *name, const char *desc,
case RELOPT_TYPE_BOOL:
size = sizeof(relopt_bool);
break;
+ case RELOPT_TYPE_TERNARY:
+ size = sizeof(relopt_ternary);
+ break;
case RELOPT_TYPE_INT:
size = sizeof(relopt_int);
break;
@@ -883,6 +907,54 @@ add_local_bool_reloption(local_relopts *relopts, const char *name,
add_local_reloption(relopts, (relopt_gen *) newoption, offset);
}
+/*
+ * init_ternary_reloption
+ * Allocate and initialize a new ternary reloption
+ */
+static relopt_ternary *
+init_ternary_reloption(bits32 kinds, const char *name, const char *desc,
+ ternary default_val, LOCKMODE lockmode)
+{
+ relopt_ternary *newoption;
+
+ newoption = (relopt_ternary *) allocate_reloption(kinds,
+ RELOPT_TYPE_TERNARY, name, desc, lockmode);
+ newoption->default_val = default_val;
+
+ return newoption;
+}
+
+/*
+ * add_ternary_reloption
+ * Add a new ternary reloption
+ */
+void
+add_ternary_reloption(bits32 kinds, const char *name, const char *desc,
+ ternary default_val, LOCKMODE lockmode)
+{
+ relopt_ternary *newoption = init_ternary_reloption(kinds, name, desc,
+ default_val, lockmode);
+
+ add_reloption((relopt_gen *) newoption);
+}
+
+/*
+ * add_local_ternary_reloption
+ * Add a new ternary local reloption
+ *
+ * 'offset' is offset of ternary-typed field.
+ */
+void
+add_local_ternary_reloption(local_relopts *relopts, const char *name,
+ const char *desc, ternary default_val,
+ int offset)
+{
+ relopt_ternary *newoption = init_ternary_reloption(RELOPT_KIND_LOCAL,
+ name, desc,
+ default_val, 0);
+
+ add_local_reloption(relopts, (relopt_gen *) newoption, offset);
+}
/*
* init_real_reloption
@@ -1617,6 +1689,18 @@ parse_one_reloption(relopt_value *option, char *text_str, int text_len,
option->gen->name, value)));
}
break;
+ case RELOPT_TYPE_TERNARY:
+ {
+ bool b;
+ parsed = parse_bool(value, &b);
+ option->values.ternary_val = b ? TERNARY_TRUE : TERNARY_FALSE;
+ if (validate && !parsed)
+ ereport(ERROR,
+ (errcode(ERRCODE_INVALID_PARAMETER_VALUE),
+ errmsg("invalid value for ternary option \"%s\": %s",
+ option->gen->name, value)));
+ }
+ break;
case RELOPT_TYPE_INT:
{
relopt_int *optint = (relopt_int *) option->gen;
@@ -1780,17 +1864,6 @@ fillRelOptions(void *rdopts, Size basesize,
char *itempos = ((char *) rdopts) + elems[j].offset;
char *string_val;
- /*
- * If isset_offset is provided, store whether the reloption is
- * set there.
- */
- if (elems[j].isset_offset > 0)
- {
- char *setpos = ((char *) rdopts) + elems[j].isset_offset;
-
- *(bool *) setpos = options[i].isset;
- }
-
switch (options[i].gen->type)
{
case RELOPT_TYPE_BOOL:
@@ -1798,6 +1871,11 @@ fillRelOptions(void *rdopts, Size basesize,
options[i].values.bool_val :
((relopt_bool *) options[i].gen)->default_val;
break;
+ case RELOPT_TYPE_TERNARY:
+ *(ternary *) itempos = options[i].isset ?
+ options[i].values.ternary_val :
+ ((relopt_ternary *) options[i].gen)->default_val;
+ break;
case RELOPT_TYPE_INT:
*(int *) itempos = options[i].isset ?
options[i].values.int_val :
@@ -1912,8 +1990,8 @@ default_reloptions(Datum reloptions, bool validate, relopt_kind kind)
offsetof(StdRdOptions, parallel_workers)},
{"vacuum_index_cleanup", RELOPT_TYPE_ENUM,
offsetof(StdRdOptions, vacuum_index_cleanup)},
- {"vacuum_truncate", RELOPT_TYPE_BOOL,
- offsetof(StdRdOptions, vacuum_truncate), offsetof(StdRdOptions, vacuum_truncate_set)},
+ {"vacuum_truncate", RELOPT_TYPE_TERNARY,
+ offsetof(StdRdOptions, vacuum_truncate)},
{"vacuum_max_eager_freeze_failure_rate", RELOPT_TYPE_REAL,
offsetof(StdRdOptions, vacuum_max_eager_freeze_failure_rate)}
};
@@ -1993,7 +2071,6 @@ build_local_reloptions(local_relopts *relopts, Datum options, bool validate)
elems[i].optname = opt->option->name;
elems[i].opttype = opt->option->type;
elems[i].offset = opt->offset;
- elems[i].isset_offset = 0; /* not supported for local relopts yet */
i++;
}
diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c
index 733ef40ae7..977babff54 100644
--- a/src/backend/commands/vacuum.c
+++ b/src/backend/commands/vacuum.c
@@ -2221,9 +2221,9 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams params,
{
StdRdOptions *opts = (StdRdOptions *) rel->rd_options;
- if (opts && opts->vacuum_truncate_set)
+ if (opts && opts->vacuum_truncate != TERNARY_UNSET)
{
- if (opts->vacuum_truncate)
+ if (opts->vacuum_truncate == TERNARY_TRUE)
params.truncate = VACOPTVALUE_ENABLED;
else
params.truncate = VACOPTVALUE_DISABLED;
diff --git a/src/include/access/reloptions.h b/src/include/access/reloptions.h
index a604a4702c..a436697658 100644
--- a/src/include/access/reloptions.h
+++ b/src/include/access/reloptions.h
@@ -29,6 +29,7 @@
typedef enum relopt_type
{
RELOPT_TYPE_BOOL,
+ RELOPT_TYPE_TERNARY, /* on, off, unset */
RELOPT_TYPE_INT,
RELOPT_TYPE_REAL,
RELOPT_TYPE_ENUM,
@@ -80,6 +81,7 @@ typedef struct relopt_value
union
{
bool bool_val;
+ ternary ternary_val;
int int_val;
double real_val;
int enum_val;
@@ -94,6 +96,12 @@ typedef struct relopt_bool
bool default_val;
} relopt_bool;
+typedef struct relopt_rernary
+{
+ relopt_gen gen;
+ int default_val;
+} relopt_ternary;
+
typedef struct relopt_int
{
relopt_gen gen;
@@ -152,19 +160,6 @@ typedef struct
const char *optname; /* option's name */
relopt_type opttype; /* option's datatype */
int offset; /* offset of field in result struct */
-
- /*
- * isset_offset is an optional offset of a field in the result struct that
- * stores whether the option is explicitly set for the relation or if it
- * just picked up the default value. In most cases, this can be
- * accomplished by giving the reloption a special out-of-range default
- * value (e.g., some integer reloptions use -2), but this isn't always
- * possible. For example, a Boolean reloption cannot be given an
- * out-of-range default, so we need another way to discover the source of
- * its value. This offset is only used if given a value greater than
- * zero.
- */
- int isset_offset;
} relopt_parse_elt;
/* Local reloption definition */
@@ -195,6 +190,8 @@ typedef struct local_relopts
extern relopt_kind add_reloption_kind(void);
extern void add_bool_reloption(bits32 kinds, const char *name, const char *desc,
bool default_val, LOCKMODE lockmode);
+extern void add_ternary_reloption(bits32 kinds, const char *name,
+ const char *desc, int default_val, LOCKMODE lockmode);
extern void add_int_reloption(bits32 kinds, const char *name, const char *desc,
int default_val, int min_val, int max_val,
LOCKMODE lockmode);
@@ -214,6 +211,9 @@ extern void register_reloptions_validator(local_relopts *relopts,
extern void add_local_bool_reloption(local_relopts *relopts, const char *name,
const char *desc, bool default_val,
int offset);
+extern void add_local_ternary_reloption(local_relopts *relopts,
+ const char *name, const char *desc,
+ ternary default_val, int offset);
extern void add_local_int_reloption(local_relopts *relopts, const char *name,
const char *desc, int default_val,
int min_val, int max_val, int offset);
diff --git a/src/include/c.h b/src/include/c.h
index 39022f8a9d..9c59ccccf8 100644
--- a/src/include/c.h
+++ b/src/include/c.h
@@ -488,6 +488,22 @@ typedef void (*pg_funcptr_t) (void);
#include <stdbool.h>
+/*
+ * ternary
+ * Boolean value with an extrea "unset" option
+ *
+ * Ternary data type is used in relation options that can be "true", "false" or
+ * "unset". Since relation options are used deep inside the PostgreSQL code,
+ * this type is declared globally.
+*/
+
+typedef enum ternary
+{
+ TERNARY_FALSE = 0,
+ TERNARY_TRUE = 1,
+ TERNARY_UNSET = -1
+} ternary;
+
/* ----------------------------------------------------------------
* Section 3: standard system types
diff --git a/src/include/utils/rel.h b/src/include/utils/rel.h
index b552359915..7346d618f9 100644
--- a/src/include/utils/rel.h
+++ b/src/include/utils/rel.h
@@ -346,8 +346,7 @@ typedef struct StdRdOptions
bool user_catalog_table; /* use as an additional catalog relation */
int parallel_workers; /* max number of parallel workers */
StdRdOptIndexCleanup vacuum_index_cleanup; /* controls index vacuuming */
- bool vacuum_truncate; /* enables vacuum to truncate a relation */
- bool vacuum_truncate_set; /* whether vacuum_truncate is set */
+ ternary vacuum_truncate; /* enables vacuum to truncate a relation */
/*
* Fraction of pages in a relation that vacuum can eagerly scan and fail
--
2.39.2
--nextPart4155600.3ZeAukHxDK
Content-Disposition: attachment;
filename="v2a-0003-Add-alias-to-be-used-as-unset-state.patch"
Content-Transfer-Encoding: 7Bit
Content-Type: text/x-patch; charset="unicode-2-0-utf-8";
name="v2a-0003-Add-alias-to-be-used-as-unset-state.patch"
^ permalink raw reply [nested|flat] 868+ messages in thread
* [PATCH v2a 2/4] Introduce ternary reloptions
@ 2025-09-04 14:41 Nikolay Shaplov <[email protected]>
0 siblings, 0 replies; 868+ messages in thread
From: Nikolay Shaplov @ 2025-09-04 14:41 UTC (permalink / raw)
Introduce ternary reloption as a replacement for current `vacuum_truncate`
implementation. Remove `vacuum_truncate_set` additional flag and using
`TERNARY_UNSET` value instead.
---
src/backend/access/common/reloptions.c | 129 ++++++++++++++++++++-----
src/backend/commands/vacuum.c | 4 +-
src/include/access/reloptions.h | 26 ++---
src/include/c.h | 16 +++
src/include/utils/rel.h | 3 +-
5 files changed, 135 insertions(+), 43 deletions(-)
diff --git a/src/backend/access/common/reloptions.c b/src/backend/access/common/reloptions.c
index 0af3fea68f..f543f11001 100644
--- a/src/backend/access/common/reloptions.c
+++ b/src/backend/access/common/reloptions.c
@@ -40,9 +40,9 @@
*
* To add an option:
*
- * (i) decide on a type (bool, integer, real, enum, string), name, default
- * value, upper and lower bounds (if applicable); for strings, consider a
- * validation routine.
+ * (i) decide on a type (bool, ternary, integer, real, enum, string), name,
+ * default value, upper and lower bounds (if applicable); for strings,
+ * consider a validation routine.
* (ii) add a record below (or use add_<type>_reloption).
* (iii) add it to the appropriate options struct (perhaps StdRdOptions)
* (iv) add it to the appropriate handling routine (perhaps
@@ -147,15 +147,6 @@ static relopt_bool boolRelOpts[] =
},
false
},
- {
- {
- "vacuum_truncate",
- "Enables vacuum to truncate empty pages at the end of this table",
- RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
- ShareUpdateExclusiveLock
- },
- true
- },
{
{
"deduplicate_items",
@@ -170,6 +161,21 @@ static relopt_bool boolRelOpts[] =
{{NULL}}
};
+static relopt_ternary ternaryRelOpts[] =
+{
+ {
+ {
+ "vacuum_truncate",
+ "Enables vacuum to truncate empty pages at the end of this table",
+ RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
+ ShareUpdateExclusiveLock
+ },
+ TERNARY_UNSET
+ },
+ /* list terminator */
+ {{NULL}}
+};
+
static relopt_int intRelOpts[] =
{
{
@@ -600,6 +606,13 @@ initialize_reloptions(void)
boolRelOpts[i].gen.lockmode));
j++;
}
+ for (i = 0; ternaryRelOpts[i].gen.name; i++)
+ {
+ Assert(DoLockModesConflict(ternaryRelOpts[i].gen.lockmode,
+ ternaryRelOpts[i].gen.lockmode));
+ j++;
+ }
+
for (i = 0; intRelOpts[i].gen.name; i++)
{
Assert(DoLockModesConflict(intRelOpts[i].gen.lockmode,
@@ -640,6 +653,14 @@ initialize_reloptions(void)
j++;
}
+ for (i = 0; ternaryRelOpts[i].gen.name; i++)
+ {
+ relOpts[j] = &ternaryRelOpts[i].gen;
+ relOpts[j]->type = RELOPT_TYPE_TERNARY;
+ relOpts[j]->namelen = strlen(relOpts[j]->name);
+ j++;
+ }
+
for (i = 0; intRelOpts[i].gen.name; i++)
{
relOpts[j] = &intRelOpts[i].gen;
@@ -800,6 +821,9 @@ allocate_reloption(bits32 kinds, int type, const char *name, const char *desc,
case RELOPT_TYPE_BOOL:
size = sizeof(relopt_bool);
break;
+ case RELOPT_TYPE_TERNARY:
+ size = sizeof(relopt_ternary);
+ break;
case RELOPT_TYPE_INT:
size = sizeof(relopt_int);
break;
@@ -883,6 +907,54 @@ add_local_bool_reloption(local_relopts *relopts, const char *name,
add_local_reloption(relopts, (relopt_gen *) newoption, offset);
}
+/*
+ * init_ternary_reloption
+ * Allocate and initialize a new ternary reloption
+ */
+static relopt_ternary *
+init_ternary_reloption(bits32 kinds, const char *name, const char *desc,
+ ternary default_val, LOCKMODE lockmode)
+{
+ relopt_ternary *newoption;
+
+ newoption = (relopt_ternary *) allocate_reloption(kinds,
+ RELOPT_TYPE_TERNARY, name, desc, lockmode);
+ newoption->default_val = default_val;
+
+ return newoption;
+}
+
+/*
+ * add_ternary_reloption
+ * Add a new ternary reloption
+ */
+void
+add_ternary_reloption(bits32 kinds, const char *name, const char *desc,
+ ternary default_val, LOCKMODE lockmode)
+{
+ relopt_ternary *newoption = init_ternary_reloption(kinds, name, desc,
+ default_val, lockmode);
+
+ add_reloption((relopt_gen *) newoption);
+}
+
+/*
+ * add_local_ternary_reloption
+ * Add a new ternary local reloption
+ *
+ * 'offset' is offset of ternary-typed field.
+ */
+void
+add_local_ternary_reloption(local_relopts *relopts, const char *name,
+ const char *desc, ternary default_val,
+ int offset)
+{
+ relopt_ternary *newoption = init_ternary_reloption(RELOPT_KIND_LOCAL,
+ name, desc,
+ default_val, 0);
+
+ add_local_reloption(relopts, (relopt_gen *) newoption, offset);
+}
/*
* init_real_reloption
@@ -1617,6 +1689,18 @@ parse_one_reloption(relopt_value *option, char *text_str, int text_len,
option->gen->name, value)));
}
break;
+ case RELOPT_TYPE_TERNARY:
+ {
+ bool b;
+ parsed = parse_bool(value, &b);
+ option->values.ternary_val = b ? TERNARY_TRUE : TERNARY_FALSE;
+ if (validate && !parsed)
+ ereport(ERROR,
+ (errcode(ERRCODE_INVALID_PARAMETER_VALUE),
+ errmsg("invalid value for ternary option \"%s\": %s",
+ option->gen->name, value)));
+ }
+ break;
case RELOPT_TYPE_INT:
{
relopt_int *optint = (relopt_int *) option->gen;
@@ -1780,17 +1864,6 @@ fillRelOptions(void *rdopts, Size basesize,
char *itempos = ((char *) rdopts) + elems[j].offset;
char *string_val;
- /*
- * If isset_offset is provided, store whether the reloption is
- * set there.
- */
- if (elems[j].isset_offset > 0)
- {
- char *setpos = ((char *) rdopts) + elems[j].isset_offset;
-
- *(bool *) setpos = options[i].isset;
- }
-
switch (options[i].gen->type)
{
case RELOPT_TYPE_BOOL:
@@ -1798,6 +1871,11 @@ fillRelOptions(void *rdopts, Size basesize,
options[i].values.bool_val :
((relopt_bool *) options[i].gen)->default_val;
break;
+ case RELOPT_TYPE_TERNARY:
+ *(ternary *) itempos = options[i].isset ?
+ options[i].values.ternary_val :
+ ((relopt_ternary *) options[i].gen)->default_val;
+ break;
case RELOPT_TYPE_INT:
*(int *) itempos = options[i].isset ?
options[i].values.int_val :
@@ -1912,8 +1990,8 @@ default_reloptions(Datum reloptions, bool validate, relopt_kind kind)
offsetof(StdRdOptions, parallel_workers)},
{"vacuum_index_cleanup", RELOPT_TYPE_ENUM,
offsetof(StdRdOptions, vacuum_index_cleanup)},
- {"vacuum_truncate", RELOPT_TYPE_BOOL,
- offsetof(StdRdOptions, vacuum_truncate), offsetof(StdRdOptions, vacuum_truncate_set)},
+ {"vacuum_truncate", RELOPT_TYPE_TERNARY,
+ offsetof(StdRdOptions, vacuum_truncate)},
{"vacuum_max_eager_freeze_failure_rate", RELOPT_TYPE_REAL,
offsetof(StdRdOptions, vacuum_max_eager_freeze_failure_rate)}
};
@@ -1993,7 +2071,6 @@ build_local_reloptions(local_relopts *relopts, Datum options, bool validate)
elems[i].optname = opt->option->name;
elems[i].opttype = opt->option->type;
elems[i].offset = opt->offset;
- elems[i].isset_offset = 0; /* not supported for local relopts yet */
i++;
}
diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c
index 733ef40ae7..977babff54 100644
--- a/src/backend/commands/vacuum.c
+++ b/src/backend/commands/vacuum.c
@@ -2221,9 +2221,9 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams params,
{
StdRdOptions *opts = (StdRdOptions *) rel->rd_options;
- if (opts && opts->vacuum_truncate_set)
+ if (opts && opts->vacuum_truncate != TERNARY_UNSET)
{
- if (opts->vacuum_truncate)
+ if (opts->vacuum_truncate == TERNARY_TRUE)
params.truncate = VACOPTVALUE_ENABLED;
else
params.truncate = VACOPTVALUE_DISABLED;
diff --git a/src/include/access/reloptions.h b/src/include/access/reloptions.h
index a604a4702c..a436697658 100644
--- a/src/include/access/reloptions.h
+++ b/src/include/access/reloptions.h
@@ -29,6 +29,7 @@
typedef enum relopt_type
{
RELOPT_TYPE_BOOL,
+ RELOPT_TYPE_TERNARY, /* on, off, unset */
RELOPT_TYPE_INT,
RELOPT_TYPE_REAL,
RELOPT_TYPE_ENUM,
@@ -80,6 +81,7 @@ typedef struct relopt_value
union
{
bool bool_val;
+ ternary ternary_val;
int int_val;
double real_val;
int enum_val;
@@ -94,6 +96,12 @@ typedef struct relopt_bool
bool default_val;
} relopt_bool;
+typedef struct relopt_rernary
+{
+ relopt_gen gen;
+ int default_val;
+} relopt_ternary;
+
typedef struct relopt_int
{
relopt_gen gen;
@@ -152,19 +160,6 @@ typedef struct
const char *optname; /* option's name */
relopt_type opttype; /* option's datatype */
int offset; /* offset of field in result struct */
-
- /*
- * isset_offset is an optional offset of a field in the result struct that
- * stores whether the option is explicitly set for the relation or if it
- * just picked up the default value. In most cases, this can be
- * accomplished by giving the reloption a special out-of-range default
- * value (e.g., some integer reloptions use -2), but this isn't always
- * possible. For example, a Boolean reloption cannot be given an
- * out-of-range default, so we need another way to discover the source of
- * its value. This offset is only used if given a value greater than
- * zero.
- */
- int isset_offset;
} relopt_parse_elt;
/* Local reloption definition */
@@ -195,6 +190,8 @@ typedef struct local_relopts
extern relopt_kind add_reloption_kind(void);
extern void add_bool_reloption(bits32 kinds, const char *name, const char *desc,
bool default_val, LOCKMODE lockmode);
+extern void add_ternary_reloption(bits32 kinds, const char *name,
+ const char *desc, int default_val, LOCKMODE lockmode);
extern void add_int_reloption(bits32 kinds, const char *name, const char *desc,
int default_val, int min_val, int max_val,
LOCKMODE lockmode);
@@ -214,6 +211,9 @@ extern void register_reloptions_validator(local_relopts *relopts,
extern void add_local_bool_reloption(local_relopts *relopts, const char *name,
const char *desc, bool default_val,
int offset);
+extern void add_local_ternary_reloption(local_relopts *relopts,
+ const char *name, const char *desc,
+ ternary default_val, int offset);
extern void add_local_int_reloption(local_relopts *relopts, const char *name,
const char *desc, int default_val,
int min_val, int max_val, int offset);
diff --git a/src/include/c.h b/src/include/c.h
index 39022f8a9d..9c59ccccf8 100644
--- a/src/include/c.h
+++ b/src/include/c.h
@@ -488,6 +488,22 @@ typedef void (*pg_funcptr_t) (void);
#include <stdbool.h>
+/*
+ * ternary
+ * Boolean value with an extrea "unset" option
+ *
+ * Ternary data type is used in relation options that can be "true", "false" or
+ * "unset". Since relation options are used deep inside the PostgreSQL code,
+ * this type is declared globally.
+*/
+
+typedef enum ternary
+{
+ TERNARY_FALSE = 0,
+ TERNARY_TRUE = 1,
+ TERNARY_UNSET = -1
+} ternary;
+
/* ----------------------------------------------------------------
* Section 3: standard system types
diff --git a/src/include/utils/rel.h b/src/include/utils/rel.h
index b552359915..7346d618f9 100644
--- a/src/include/utils/rel.h
+++ b/src/include/utils/rel.h
@@ -346,8 +346,7 @@ typedef struct StdRdOptions
bool user_catalog_table; /* use as an additional catalog relation */
int parallel_workers; /* max number of parallel workers */
StdRdOptIndexCleanup vacuum_index_cleanup; /* controls index vacuuming */
- bool vacuum_truncate; /* enables vacuum to truncate a relation */
- bool vacuum_truncate_set; /* whether vacuum_truncate is set */
+ ternary vacuum_truncate; /* enables vacuum to truncate a relation */
/*
* Fraction of pages in a relation that vacuum can eagerly scan and fail
--
2.39.2
--nextPart4155600.3ZeAukHxDK
Content-Disposition: attachment;
filename="v2a-0003-Add-alias-to-be-used-as-unset-state.patch"
Content-Transfer-Encoding: 7Bit
Content-Type: text/x-patch; charset="unicode-2-0-utf-8";
name="v2a-0003-Add-alias-to-be-used-as-unset-state.patch"
^ permalink raw reply [nested|flat] 868+ messages in thread
* [PATCH v2a 2/4] Introduce ternary reloptions
@ 2025-09-04 14:41 Nikolay Shaplov <[email protected]>
0 siblings, 0 replies; 868+ messages in thread
From: Nikolay Shaplov @ 2025-09-04 14:41 UTC (permalink / raw)
Introduce ternary reloption as a replacement for current `vacuum_truncate`
implementation. Remove `vacuum_truncate_set` additional flag and using
`TERNARY_UNSET` value instead.
---
src/backend/access/common/reloptions.c | 129 ++++++++++++++++++++-----
src/backend/commands/vacuum.c | 4 +-
src/include/access/reloptions.h | 26 ++---
src/include/c.h | 16 +++
src/include/utils/rel.h | 3 +-
5 files changed, 135 insertions(+), 43 deletions(-)
diff --git a/src/backend/access/common/reloptions.c b/src/backend/access/common/reloptions.c
index 0af3fea68f..f543f11001 100644
--- a/src/backend/access/common/reloptions.c
+++ b/src/backend/access/common/reloptions.c
@@ -40,9 +40,9 @@
*
* To add an option:
*
- * (i) decide on a type (bool, integer, real, enum, string), name, default
- * value, upper and lower bounds (if applicable); for strings, consider a
- * validation routine.
+ * (i) decide on a type (bool, ternary, integer, real, enum, string), name,
+ * default value, upper and lower bounds (if applicable); for strings,
+ * consider a validation routine.
* (ii) add a record below (or use add_<type>_reloption).
* (iii) add it to the appropriate options struct (perhaps StdRdOptions)
* (iv) add it to the appropriate handling routine (perhaps
@@ -147,15 +147,6 @@ static relopt_bool boolRelOpts[] =
},
false
},
- {
- {
- "vacuum_truncate",
- "Enables vacuum to truncate empty pages at the end of this table",
- RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
- ShareUpdateExclusiveLock
- },
- true
- },
{
{
"deduplicate_items",
@@ -170,6 +161,21 @@ static relopt_bool boolRelOpts[] =
{{NULL}}
};
+static relopt_ternary ternaryRelOpts[] =
+{
+ {
+ {
+ "vacuum_truncate",
+ "Enables vacuum to truncate empty pages at the end of this table",
+ RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
+ ShareUpdateExclusiveLock
+ },
+ TERNARY_UNSET
+ },
+ /* list terminator */
+ {{NULL}}
+};
+
static relopt_int intRelOpts[] =
{
{
@@ -600,6 +606,13 @@ initialize_reloptions(void)
boolRelOpts[i].gen.lockmode));
j++;
}
+ for (i = 0; ternaryRelOpts[i].gen.name; i++)
+ {
+ Assert(DoLockModesConflict(ternaryRelOpts[i].gen.lockmode,
+ ternaryRelOpts[i].gen.lockmode));
+ j++;
+ }
+
for (i = 0; intRelOpts[i].gen.name; i++)
{
Assert(DoLockModesConflict(intRelOpts[i].gen.lockmode,
@@ -640,6 +653,14 @@ initialize_reloptions(void)
j++;
}
+ for (i = 0; ternaryRelOpts[i].gen.name; i++)
+ {
+ relOpts[j] = &ternaryRelOpts[i].gen;
+ relOpts[j]->type = RELOPT_TYPE_TERNARY;
+ relOpts[j]->namelen = strlen(relOpts[j]->name);
+ j++;
+ }
+
for (i = 0; intRelOpts[i].gen.name; i++)
{
relOpts[j] = &intRelOpts[i].gen;
@@ -800,6 +821,9 @@ allocate_reloption(bits32 kinds, int type, const char *name, const char *desc,
case RELOPT_TYPE_BOOL:
size = sizeof(relopt_bool);
break;
+ case RELOPT_TYPE_TERNARY:
+ size = sizeof(relopt_ternary);
+ break;
case RELOPT_TYPE_INT:
size = sizeof(relopt_int);
break;
@@ -883,6 +907,54 @@ add_local_bool_reloption(local_relopts *relopts, const char *name,
add_local_reloption(relopts, (relopt_gen *) newoption, offset);
}
+/*
+ * init_ternary_reloption
+ * Allocate and initialize a new ternary reloption
+ */
+static relopt_ternary *
+init_ternary_reloption(bits32 kinds, const char *name, const char *desc,
+ ternary default_val, LOCKMODE lockmode)
+{
+ relopt_ternary *newoption;
+
+ newoption = (relopt_ternary *) allocate_reloption(kinds,
+ RELOPT_TYPE_TERNARY, name, desc, lockmode);
+ newoption->default_val = default_val;
+
+ return newoption;
+}
+
+/*
+ * add_ternary_reloption
+ * Add a new ternary reloption
+ */
+void
+add_ternary_reloption(bits32 kinds, const char *name, const char *desc,
+ ternary default_val, LOCKMODE lockmode)
+{
+ relopt_ternary *newoption = init_ternary_reloption(kinds, name, desc,
+ default_val, lockmode);
+
+ add_reloption((relopt_gen *) newoption);
+}
+
+/*
+ * add_local_ternary_reloption
+ * Add a new ternary local reloption
+ *
+ * 'offset' is offset of ternary-typed field.
+ */
+void
+add_local_ternary_reloption(local_relopts *relopts, const char *name,
+ const char *desc, ternary default_val,
+ int offset)
+{
+ relopt_ternary *newoption = init_ternary_reloption(RELOPT_KIND_LOCAL,
+ name, desc,
+ default_val, 0);
+
+ add_local_reloption(relopts, (relopt_gen *) newoption, offset);
+}
/*
* init_real_reloption
@@ -1617,6 +1689,18 @@ parse_one_reloption(relopt_value *option, char *text_str, int text_len,
option->gen->name, value)));
}
break;
+ case RELOPT_TYPE_TERNARY:
+ {
+ bool b;
+ parsed = parse_bool(value, &b);
+ option->values.ternary_val = b ? TERNARY_TRUE : TERNARY_FALSE;
+ if (validate && !parsed)
+ ereport(ERROR,
+ (errcode(ERRCODE_INVALID_PARAMETER_VALUE),
+ errmsg("invalid value for ternary option \"%s\": %s",
+ option->gen->name, value)));
+ }
+ break;
case RELOPT_TYPE_INT:
{
relopt_int *optint = (relopt_int *) option->gen;
@@ -1780,17 +1864,6 @@ fillRelOptions(void *rdopts, Size basesize,
char *itempos = ((char *) rdopts) + elems[j].offset;
char *string_val;
- /*
- * If isset_offset is provided, store whether the reloption is
- * set there.
- */
- if (elems[j].isset_offset > 0)
- {
- char *setpos = ((char *) rdopts) + elems[j].isset_offset;
-
- *(bool *) setpos = options[i].isset;
- }
-
switch (options[i].gen->type)
{
case RELOPT_TYPE_BOOL:
@@ -1798,6 +1871,11 @@ fillRelOptions(void *rdopts, Size basesize,
options[i].values.bool_val :
((relopt_bool *) options[i].gen)->default_val;
break;
+ case RELOPT_TYPE_TERNARY:
+ *(ternary *) itempos = options[i].isset ?
+ options[i].values.ternary_val :
+ ((relopt_ternary *) options[i].gen)->default_val;
+ break;
case RELOPT_TYPE_INT:
*(int *) itempos = options[i].isset ?
options[i].values.int_val :
@@ -1912,8 +1990,8 @@ default_reloptions(Datum reloptions, bool validate, relopt_kind kind)
offsetof(StdRdOptions, parallel_workers)},
{"vacuum_index_cleanup", RELOPT_TYPE_ENUM,
offsetof(StdRdOptions, vacuum_index_cleanup)},
- {"vacuum_truncate", RELOPT_TYPE_BOOL,
- offsetof(StdRdOptions, vacuum_truncate), offsetof(StdRdOptions, vacuum_truncate_set)},
+ {"vacuum_truncate", RELOPT_TYPE_TERNARY,
+ offsetof(StdRdOptions, vacuum_truncate)},
{"vacuum_max_eager_freeze_failure_rate", RELOPT_TYPE_REAL,
offsetof(StdRdOptions, vacuum_max_eager_freeze_failure_rate)}
};
@@ -1993,7 +2071,6 @@ build_local_reloptions(local_relopts *relopts, Datum options, bool validate)
elems[i].optname = opt->option->name;
elems[i].opttype = opt->option->type;
elems[i].offset = opt->offset;
- elems[i].isset_offset = 0; /* not supported for local relopts yet */
i++;
}
diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c
index 733ef40ae7..977babff54 100644
--- a/src/backend/commands/vacuum.c
+++ b/src/backend/commands/vacuum.c
@@ -2221,9 +2221,9 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams params,
{
StdRdOptions *opts = (StdRdOptions *) rel->rd_options;
- if (opts && opts->vacuum_truncate_set)
+ if (opts && opts->vacuum_truncate != TERNARY_UNSET)
{
- if (opts->vacuum_truncate)
+ if (opts->vacuum_truncate == TERNARY_TRUE)
params.truncate = VACOPTVALUE_ENABLED;
else
params.truncate = VACOPTVALUE_DISABLED;
diff --git a/src/include/access/reloptions.h b/src/include/access/reloptions.h
index a604a4702c..a436697658 100644
--- a/src/include/access/reloptions.h
+++ b/src/include/access/reloptions.h
@@ -29,6 +29,7 @@
typedef enum relopt_type
{
RELOPT_TYPE_BOOL,
+ RELOPT_TYPE_TERNARY, /* on, off, unset */
RELOPT_TYPE_INT,
RELOPT_TYPE_REAL,
RELOPT_TYPE_ENUM,
@@ -80,6 +81,7 @@ typedef struct relopt_value
union
{
bool bool_val;
+ ternary ternary_val;
int int_val;
double real_val;
int enum_val;
@@ -94,6 +96,12 @@ typedef struct relopt_bool
bool default_val;
} relopt_bool;
+typedef struct relopt_rernary
+{
+ relopt_gen gen;
+ int default_val;
+} relopt_ternary;
+
typedef struct relopt_int
{
relopt_gen gen;
@@ -152,19 +160,6 @@ typedef struct
const char *optname; /* option's name */
relopt_type opttype; /* option's datatype */
int offset; /* offset of field in result struct */
-
- /*
- * isset_offset is an optional offset of a field in the result struct that
- * stores whether the option is explicitly set for the relation or if it
- * just picked up the default value. In most cases, this can be
- * accomplished by giving the reloption a special out-of-range default
- * value (e.g., some integer reloptions use -2), but this isn't always
- * possible. For example, a Boolean reloption cannot be given an
- * out-of-range default, so we need another way to discover the source of
- * its value. This offset is only used if given a value greater than
- * zero.
- */
- int isset_offset;
} relopt_parse_elt;
/* Local reloption definition */
@@ -195,6 +190,8 @@ typedef struct local_relopts
extern relopt_kind add_reloption_kind(void);
extern void add_bool_reloption(bits32 kinds, const char *name, const char *desc,
bool default_val, LOCKMODE lockmode);
+extern void add_ternary_reloption(bits32 kinds, const char *name,
+ const char *desc, int default_val, LOCKMODE lockmode);
extern void add_int_reloption(bits32 kinds, const char *name, const char *desc,
int default_val, int min_val, int max_val,
LOCKMODE lockmode);
@@ -214,6 +211,9 @@ extern void register_reloptions_validator(local_relopts *relopts,
extern void add_local_bool_reloption(local_relopts *relopts, const char *name,
const char *desc, bool default_val,
int offset);
+extern void add_local_ternary_reloption(local_relopts *relopts,
+ const char *name, const char *desc,
+ ternary default_val, int offset);
extern void add_local_int_reloption(local_relopts *relopts, const char *name,
const char *desc, int default_val,
int min_val, int max_val, int offset);
diff --git a/src/include/c.h b/src/include/c.h
index 39022f8a9d..9c59ccccf8 100644
--- a/src/include/c.h
+++ b/src/include/c.h
@@ -488,6 +488,22 @@ typedef void (*pg_funcptr_t) (void);
#include <stdbool.h>
+/*
+ * ternary
+ * Boolean value with an extrea "unset" option
+ *
+ * Ternary data type is used in relation options that can be "true", "false" or
+ * "unset". Since relation options are used deep inside the PostgreSQL code,
+ * this type is declared globally.
+*/
+
+typedef enum ternary
+{
+ TERNARY_FALSE = 0,
+ TERNARY_TRUE = 1,
+ TERNARY_UNSET = -1
+} ternary;
+
/* ----------------------------------------------------------------
* Section 3: standard system types
diff --git a/src/include/utils/rel.h b/src/include/utils/rel.h
index b552359915..7346d618f9 100644
--- a/src/include/utils/rel.h
+++ b/src/include/utils/rel.h
@@ -346,8 +346,7 @@ typedef struct StdRdOptions
bool user_catalog_table; /* use as an additional catalog relation */
int parallel_workers; /* max number of parallel workers */
StdRdOptIndexCleanup vacuum_index_cleanup; /* controls index vacuuming */
- bool vacuum_truncate; /* enables vacuum to truncate a relation */
- bool vacuum_truncate_set; /* whether vacuum_truncate is set */
+ ternary vacuum_truncate; /* enables vacuum to truncate a relation */
/*
* Fraction of pages in a relation that vacuum can eagerly scan and fail
--
2.39.2
--nextPart4155600.3ZeAukHxDK
Content-Disposition: attachment;
filename="v2a-0003-Add-alias-to-be-used-as-unset-state.patch"
Content-Transfer-Encoding: 7Bit
Content-Type: text/x-patch; charset="unicode-2-0-utf-8";
name="v2a-0003-Add-alias-to-be-used-as-unset-state.patch"
^ permalink raw reply [nested|flat] 868+ messages in thread
* [PATCH v2a 2/4] Introduce ternary reloptions
@ 2025-09-04 14:41 Nikolay Shaplov <[email protected]>
0 siblings, 0 replies; 868+ messages in thread
From: Nikolay Shaplov @ 2025-09-04 14:41 UTC (permalink / raw)
Introduce ternary reloption as a replacement for current `vacuum_truncate`
implementation. Remove `vacuum_truncate_set` additional flag and using
`TERNARY_UNSET` value instead.
---
src/backend/access/common/reloptions.c | 129 ++++++++++++++++++++-----
src/backend/commands/vacuum.c | 4 +-
src/include/access/reloptions.h | 26 ++---
src/include/c.h | 16 +++
src/include/utils/rel.h | 3 +-
5 files changed, 135 insertions(+), 43 deletions(-)
diff --git a/src/backend/access/common/reloptions.c b/src/backend/access/common/reloptions.c
index 0af3fea68f..f543f11001 100644
--- a/src/backend/access/common/reloptions.c
+++ b/src/backend/access/common/reloptions.c
@@ -40,9 +40,9 @@
*
* To add an option:
*
- * (i) decide on a type (bool, integer, real, enum, string), name, default
- * value, upper and lower bounds (if applicable); for strings, consider a
- * validation routine.
+ * (i) decide on a type (bool, ternary, integer, real, enum, string), name,
+ * default value, upper and lower bounds (if applicable); for strings,
+ * consider a validation routine.
* (ii) add a record below (or use add_<type>_reloption).
* (iii) add it to the appropriate options struct (perhaps StdRdOptions)
* (iv) add it to the appropriate handling routine (perhaps
@@ -147,15 +147,6 @@ static relopt_bool boolRelOpts[] =
},
false
},
- {
- {
- "vacuum_truncate",
- "Enables vacuum to truncate empty pages at the end of this table",
- RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
- ShareUpdateExclusiveLock
- },
- true
- },
{
{
"deduplicate_items",
@@ -170,6 +161,21 @@ static relopt_bool boolRelOpts[] =
{{NULL}}
};
+static relopt_ternary ternaryRelOpts[] =
+{
+ {
+ {
+ "vacuum_truncate",
+ "Enables vacuum to truncate empty pages at the end of this table",
+ RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
+ ShareUpdateExclusiveLock
+ },
+ TERNARY_UNSET
+ },
+ /* list terminator */
+ {{NULL}}
+};
+
static relopt_int intRelOpts[] =
{
{
@@ -600,6 +606,13 @@ initialize_reloptions(void)
boolRelOpts[i].gen.lockmode));
j++;
}
+ for (i = 0; ternaryRelOpts[i].gen.name; i++)
+ {
+ Assert(DoLockModesConflict(ternaryRelOpts[i].gen.lockmode,
+ ternaryRelOpts[i].gen.lockmode));
+ j++;
+ }
+
for (i = 0; intRelOpts[i].gen.name; i++)
{
Assert(DoLockModesConflict(intRelOpts[i].gen.lockmode,
@@ -640,6 +653,14 @@ initialize_reloptions(void)
j++;
}
+ for (i = 0; ternaryRelOpts[i].gen.name; i++)
+ {
+ relOpts[j] = &ternaryRelOpts[i].gen;
+ relOpts[j]->type = RELOPT_TYPE_TERNARY;
+ relOpts[j]->namelen = strlen(relOpts[j]->name);
+ j++;
+ }
+
for (i = 0; intRelOpts[i].gen.name; i++)
{
relOpts[j] = &intRelOpts[i].gen;
@@ -800,6 +821,9 @@ allocate_reloption(bits32 kinds, int type, const char *name, const char *desc,
case RELOPT_TYPE_BOOL:
size = sizeof(relopt_bool);
break;
+ case RELOPT_TYPE_TERNARY:
+ size = sizeof(relopt_ternary);
+ break;
case RELOPT_TYPE_INT:
size = sizeof(relopt_int);
break;
@@ -883,6 +907,54 @@ add_local_bool_reloption(local_relopts *relopts, const char *name,
add_local_reloption(relopts, (relopt_gen *) newoption, offset);
}
+/*
+ * init_ternary_reloption
+ * Allocate and initialize a new ternary reloption
+ */
+static relopt_ternary *
+init_ternary_reloption(bits32 kinds, const char *name, const char *desc,
+ ternary default_val, LOCKMODE lockmode)
+{
+ relopt_ternary *newoption;
+
+ newoption = (relopt_ternary *) allocate_reloption(kinds,
+ RELOPT_TYPE_TERNARY, name, desc, lockmode);
+ newoption->default_val = default_val;
+
+ return newoption;
+}
+
+/*
+ * add_ternary_reloption
+ * Add a new ternary reloption
+ */
+void
+add_ternary_reloption(bits32 kinds, const char *name, const char *desc,
+ ternary default_val, LOCKMODE lockmode)
+{
+ relopt_ternary *newoption = init_ternary_reloption(kinds, name, desc,
+ default_val, lockmode);
+
+ add_reloption((relopt_gen *) newoption);
+}
+
+/*
+ * add_local_ternary_reloption
+ * Add a new ternary local reloption
+ *
+ * 'offset' is offset of ternary-typed field.
+ */
+void
+add_local_ternary_reloption(local_relopts *relopts, const char *name,
+ const char *desc, ternary default_val,
+ int offset)
+{
+ relopt_ternary *newoption = init_ternary_reloption(RELOPT_KIND_LOCAL,
+ name, desc,
+ default_val, 0);
+
+ add_local_reloption(relopts, (relopt_gen *) newoption, offset);
+}
/*
* init_real_reloption
@@ -1617,6 +1689,18 @@ parse_one_reloption(relopt_value *option, char *text_str, int text_len,
option->gen->name, value)));
}
break;
+ case RELOPT_TYPE_TERNARY:
+ {
+ bool b;
+ parsed = parse_bool(value, &b);
+ option->values.ternary_val = b ? TERNARY_TRUE : TERNARY_FALSE;
+ if (validate && !parsed)
+ ereport(ERROR,
+ (errcode(ERRCODE_INVALID_PARAMETER_VALUE),
+ errmsg("invalid value for ternary option \"%s\": %s",
+ option->gen->name, value)));
+ }
+ break;
case RELOPT_TYPE_INT:
{
relopt_int *optint = (relopt_int *) option->gen;
@@ -1780,17 +1864,6 @@ fillRelOptions(void *rdopts, Size basesize,
char *itempos = ((char *) rdopts) + elems[j].offset;
char *string_val;
- /*
- * If isset_offset is provided, store whether the reloption is
- * set there.
- */
- if (elems[j].isset_offset > 0)
- {
- char *setpos = ((char *) rdopts) + elems[j].isset_offset;
-
- *(bool *) setpos = options[i].isset;
- }
-
switch (options[i].gen->type)
{
case RELOPT_TYPE_BOOL:
@@ -1798,6 +1871,11 @@ fillRelOptions(void *rdopts, Size basesize,
options[i].values.bool_val :
((relopt_bool *) options[i].gen)->default_val;
break;
+ case RELOPT_TYPE_TERNARY:
+ *(ternary *) itempos = options[i].isset ?
+ options[i].values.ternary_val :
+ ((relopt_ternary *) options[i].gen)->default_val;
+ break;
case RELOPT_TYPE_INT:
*(int *) itempos = options[i].isset ?
options[i].values.int_val :
@@ -1912,8 +1990,8 @@ default_reloptions(Datum reloptions, bool validate, relopt_kind kind)
offsetof(StdRdOptions, parallel_workers)},
{"vacuum_index_cleanup", RELOPT_TYPE_ENUM,
offsetof(StdRdOptions, vacuum_index_cleanup)},
- {"vacuum_truncate", RELOPT_TYPE_BOOL,
- offsetof(StdRdOptions, vacuum_truncate), offsetof(StdRdOptions, vacuum_truncate_set)},
+ {"vacuum_truncate", RELOPT_TYPE_TERNARY,
+ offsetof(StdRdOptions, vacuum_truncate)},
{"vacuum_max_eager_freeze_failure_rate", RELOPT_TYPE_REAL,
offsetof(StdRdOptions, vacuum_max_eager_freeze_failure_rate)}
};
@@ -1993,7 +2071,6 @@ build_local_reloptions(local_relopts *relopts, Datum options, bool validate)
elems[i].optname = opt->option->name;
elems[i].opttype = opt->option->type;
elems[i].offset = opt->offset;
- elems[i].isset_offset = 0; /* not supported for local relopts yet */
i++;
}
diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c
index 733ef40ae7..977babff54 100644
--- a/src/backend/commands/vacuum.c
+++ b/src/backend/commands/vacuum.c
@@ -2221,9 +2221,9 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams params,
{
StdRdOptions *opts = (StdRdOptions *) rel->rd_options;
- if (opts && opts->vacuum_truncate_set)
+ if (opts && opts->vacuum_truncate != TERNARY_UNSET)
{
- if (opts->vacuum_truncate)
+ if (opts->vacuum_truncate == TERNARY_TRUE)
params.truncate = VACOPTVALUE_ENABLED;
else
params.truncate = VACOPTVALUE_DISABLED;
diff --git a/src/include/access/reloptions.h b/src/include/access/reloptions.h
index a604a4702c..a436697658 100644
--- a/src/include/access/reloptions.h
+++ b/src/include/access/reloptions.h
@@ -29,6 +29,7 @@
typedef enum relopt_type
{
RELOPT_TYPE_BOOL,
+ RELOPT_TYPE_TERNARY, /* on, off, unset */
RELOPT_TYPE_INT,
RELOPT_TYPE_REAL,
RELOPT_TYPE_ENUM,
@@ -80,6 +81,7 @@ typedef struct relopt_value
union
{
bool bool_val;
+ ternary ternary_val;
int int_val;
double real_val;
int enum_val;
@@ -94,6 +96,12 @@ typedef struct relopt_bool
bool default_val;
} relopt_bool;
+typedef struct relopt_rernary
+{
+ relopt_gen gen;
+ int default_val;
+} relopt_ternary;
+
typedef struct relopt_int
{
relopt_gen gen;
@@ -152,19 +160,6 @@ typedef struct
const char *optname; /* option's name */
relopt_type opttype; /* option's datatype */
int offset; /* offset of field in result struct */
-
- /*
- * isset_offset is an optional offset of a field in the result struct that
- * stores whether the option is explicitly set for the relation or if it
- * just picked up the default value. In most cases, this can be
- * accomplished by giving the reloption a special out-of-range default
- * value (e.g., some integer reloptions use -2), but this isn't always
- * possible. For example, a Boolean reloption cannot be given an
- * out-of-range default, so we need another way to discover the source of
- * its value. This offset is only used if given a value greater than
- * zero.
- */
- int isset_offset;
} relopt_parse_elt;
/* Local reloption definition */
@@ -195,6 +190,8 @@ typedef struct local_relopts
extern relopt_kind add_reloption_kind(void);
extern void add_bool_reloption(bits32 kinds, const char *name, const char *desc,
bool default_val, LOCKMODE lockmode);
+extern void add_ternary_reloption(bits32 kinds, const char *name,
+ const char *desc, int default_val, LOCKMODE lockmode);
extern void add_int_reloption(bits32 kinds, const char *name, const char *desc,
int default_val, int min_val, int max_val,
LOCKMODE lockmode);
@@ -214,6 +211,9 @@ extern void register_reloptions_validator(local_relopts *relopts,
extern void add_local_bool_reloption(local_relopts *relopts, const char *name,
const char *desc, bool default_val,
int offset);
+extern void add_local_ternary_reloption(local_relopts *relopts,
+ const char *name, const char *desc,
+ ternary default_val, int offset);
extern void add_local_int_reloption(local_relopts *relopts, const char *name,
const char *desc, int default_val,
int min_val, int max_val, int offset);
diff --git a/src/include/c.h b/src/include/c.h
index 39022f8a9d..9c59ccccf8 100644
--- a/src/include/c.h
+++ b/src/include/c.h
@@ -488,6 +488,22 @@ typedef void (*pg_funcptr_t) (void);
#include <stdbool.h>
+/*
+ * ternary
+ * Boolean value with an extrea "unset" option
+ *
+ * Ternary data type is used in relation options that can be "true", "false" or
+ * "unset". Since relation options are used deep inside the PostgreSQL code,
+ * this type is declared globally.
+*/
+
+typedef enum ternary
+{
+ TERNARY_FALSE = 0,
+ TERNARY_TRUE = 1,
+ TERNARY_UNSET = -1
+} ternary;
+
/* ----------------------------------------------------------------
* Section 3: standard system types
diff --git a/src/include/utils/rel.h b/src/include/utils/rel.h
index b552359915..7346d618f9 100644
--- a/src/include/utils/rel.h
+++ b/src/include/utils/rel.h
@@ -346,8 +346,7 @@ typedef struct StdRdOptions
bool user_catalog_table; /* use as an additional catalog relation */
int parallel_workers; /* max number of parallel workers */
StdRdOptIndexCleanup vacuum_index_cleanup; /* controls index vacuuming */
- bool vacuum_truncate; /* enables vacuum to truncate a relation */
- bool vacuum_truncate_set; /* whether vacuum_truncate is set */
+ ternary vacuum_truncate; /* enables vacuum to truncate a relation */
/*
* Fraction of pages in a relation that vacuum can eagerly scan and fail
--
2.39.2
--nextPart4155600.3ZeAukHxDK
Content-Disposition: attachment;
filename="v2a-0003-Add-alias-to-be-used-as-unset-state.patch"
Content-Transfer-Encoding: 7Bit
Content-Type: text/x-patch; charset="unicode-2-0-utf-8";
name="v2a-0003-Add-alias-to-be-used-as-unset-state.patch"
^ permalink raw reply [nested|flat] 868+ messages in thread
* [PATCH v2a 2/4] Introduce ternary reloptions
@ 2025-09-04 14:41 Nikolay Shaplov <[email protected]>
0 siblings, 0 replies; 868+ messages in thread
From: Nikolay Shaplov @ 2025-09-04 14:41 UTC (permalink / raw)
Introduce ternary reloption as a replacement for current `vacuum_truncate`
implementation. Remove `vacuum_truncate_set` additional flag and using
`TERNARY_UNSET` value instead.
---
src/backend/access/common/reloptions.c | 129 ++++++++++++++++++++-----
src/backend/commands/vacuum.c | 4 +-
src/include/access/reloptions.h | 26 ++---
src/include/c.h | 16 +++
src/include/utils/rel.h | 3 +-
5 files changed, 135 insertions(+), 43 deletions(-)
diff --git a/src/backend/access/common/reloptions.c b/src/backend/access/common/reloptions.c
index 0af3fea68f..f543f11001 100644
--- a/src/backend/access/common/reloptions.c
+++ b/src/backend/access/common/reloptions.c
@@ -40,9 +40,9 @@
*
* To add an option:
*
- * (i) decide on a type (bool, integer, real, enum, string), name, default
- * value, upper and lower bounds (if applicable); for strings, consider a
- * validation routine.
+ * (i) decide on a type (bool, ternary, integer, real, enum, string), name,
+ * default value, upper and lower bounds (if applicable); for strings,
+ * consider a validation routine.
* (ii) add a record below (or use add_<type>_reloption).
* (iii) add it to the appropriate options struct (perhaps StdRdOptions)
* (iv) add it to the appropriate handling routine (perhaps
@@ -147,15 +147,6 @@ static relopt_bool boolRelOpts[] =
},
false
},
- {
- {
- "vacuum_truncate",
- "Enables vacuum to truncate empty pages at the end of this table",
- RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
- ShareUpdateExclusiveLock
- },
- true
- },
{
{
"deduplicate_items",
@@ -170,6 +161,21 @@ static relopt_bool boolRelOpts[] =
{{NULL}}
};
+static relopt_ternary ternaryRelOpts[] =
+{
+ {
+ {
+ "vacuum_truncate",
+ "Enables vacuum to truncate empty pages at the end of this table",
+ RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
+ ShareUpdateExclusiveLock
+ },
+ TERNARY_UNSET
+ },
+ /* list terminator */
+ {{NULL}}
+};
+
static relopt_int intRelOpts[] =
{
{
@@ -600,6 +606,13 @@ initialize_reloptions(void)
boolRelOpts[i].gen.lockmode));
j++;
}
+ for (i = 0; ternaryRelOpts[i].gen.name; i++)
+ {
+ Assert(DoLockModesConflict(ternaryRelOpts[i].gen.lockmode,
+ ternaryRelOpts[i].gen.lockmode));
+ j++;
+ }
+
for (i = 0; intRelOpts[i].gen.name; i++)
{
Assert(DoLockModesConflict(intRelOpts[i].gen.lockmode,
@@ -640,6 +653,14 @@ initialize_reloptions(void)
j++;
}
+ for (i = 0; ternaryRelOpts[i].gen.name; i++)
+ {
+ relOpts[j] = &ternaryRelOpts[i].gen;
+ relOpts[j]->type = RELOPT_TYPE_TERNARY;
+ relOpts[j]->namelen = strlen(relOpts[j]->name);
+ j++;
+ }
+
for (i = 0; intRelOpts[i].gen.name; i++)
{
relOpts[j] = &intRelOpts[i].gen;
@@ -800,6 +821,9 @@ allocate_reloption(bits32 kinds, int type, const char *name, const char *desc,
case RELOPT_TYPE_BOOL:
size = sizeof(relopt_bool);
break;
+ case RELOPT_TYPE_TERNARY:
+ size = sizeof(relopt_ternary);
+ break;
case RELOPT_TYPE_INT:
size = sizeof(relopt_int);
break;
@@ -883,6 +907,54 @@ add_local_bool_reloption(local_relopts *relopts, const char *name,
add_local_reloption(relopts, (relopt_gen *) newoption, offset);
}
+/*
+ * init_ternary_reloption
+ * Allocate and initialize a new ternary reloption
+ */
+static relopt_ternary *
+init_ternary_reloption(bits32 kinds, const char *name, const char *desc,
+ ternary default_val, LOCKMODE lockmode)
+{
+ relopt_ternary *newoption;
+
+ newoption = (relopt_ternary *) allocate_reloption(kinds,
+ RELOPT_TYPE_TERNARY, name, desc, lockmode);
+ newoption->default_val = default_val;
+
+ return newoption;
+}
+
+/*
+ * add_ternary_reloption
+ * Add a new ternary reloption
+ */
+void
+add_ternary_reloption(bits32 kinds, const char *name, const char *desc,
+ ternary default_val, LOCKMODE lockmode)
+{
+ relopt_ternary *newoption = init_ternary_reloption(kinds, name, desc,
+ default_val, lockmode);
+
+ add_reloption((relopt_gen *) newoption);
+}
+
+/*
+ * add_local_ternary_reloption
+ * Add a new ternary local reloption
+ *
+ * 'offset' is offset of ternary-typed field.
+ */
+void
+add_local_ternary_reloption(local_relopts *relopts, const char *name,
+ const char *desc, ternary default_val,
+ int offset)
+{
+ relopt_ternary *newoption = init_ternary_reloption(RELOPT_KIND_LOCAL,
+ name, desc,
+ default_val, 0);
+
+ add_local_reloption(relopts, (relopt_gen *) newoption, offset);
+}
/*
* init_real_reloption
@@ -1617,6 +1689,18 @@ parse_one_reloption(relopt_value *option, char *text_str, int text_len,
option->gen->name, value)));
}
break;
+ case RELOPT_TYPE_TERNARY:
+ {
+ bool b;
+ parsed = parse_bool(value, &b);
+ option->values.ternary_val = b ? TERNARY_TRUE : TERNARY_FALSE;
+ if (validate && !parsed)
+ ereport(ERROR,
+ (errcode(ERRCODE_INVALID_PARAMETER_VALUE),
+ errmsg("invalid value for ternary option \"%s\": %s",
+ option->gen->name, value)));
+ }
+ break;
case RELOPT_TYPE_INT:
{
relopt_int *optint = (relopt_int *) option->gen;
@@ -1780,17 +1864,6 @@ fillRelOptions(void *rdopts, Size basesize,
char *itempos = ((char *) rdopts) + elems[j].offset;
char *string_val;
- /*
- * If isset_offset is provided, store whether the reloption is
- * set there.
- */
- if (elems[j].isset_offset > 0)
- {
- char *setpos = ((char *) rdopts) + elems[j].isset_offset;
-
- *(bool *) setpos = options[i].isset;
- }
-
switch (options[i].gen->type)
{
case RELOPT_TYPE_BOOL:
@@ -1798,6 +1871,11 @@ fillRelOptions(void *rdopts, Size basesize,
options[i].values.bool_val :
((relopt_bool *) options[i].gen)->default_val;
break;
+ case RELOPT_TYPE_TERNARY:
+ *(ternary *) itempos = options[i].isset ?
+ options[i].values.ternary_val :
+ ((relopt_ternary *) options[i].gen)->default_val;
+ break;
case RELOPT_TYPE_INT:
*(int *) itempos = options[i].isset ?
options[i].values.int_val :
@@ -1912,8 +1990,8 @@ default_reloptions(Datum reloptions, bool validate, relopt_kind kind)
offsetof(StdRdOptions, parallel_workers)},
{"vacuum_index_cleanup", RELOPT_TYPE_ENUM,
offsetof(StdRdOptions, vacuum_index_cleanup)},
- {"vacuum_truncate", RELOPT_TYPE_BOOL,
- offsetof(StdRdOptions, vacuum_truncate), offsetof(StdRdOptions, vacuum_truncate_set)},
+ {"vacuum_truncate", RELOPT_TYPE_TERNARY,
+ offsetof(StdRdOptions, vacuum_truncate)},
{"vacuum_max_eager_freeze_failure_rate", RELOPT_TYPE_REAL,
offsetof(StdRdOptions, vacuum_max_eager_freeze_failure_rate)}
};
@@ -1993,7 +2071,6 @@ build_local_reloptions(local_relopts *relopts, Datum options, bool validate)
elems[i].optname = opt->option->name;
elems[i].opttype = opt->option->type;
elems[i].offset = opt->offset;
- elems[i].isset_offset = 0; /* not supported for local relopts yet */
i++;
}
diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c
index 733ef40ae7..977babff54 100644
--- a/src/backend/commands/vacuum.c
+++ b/src/backend/commands/vacuum.c
@@ -2221,9 +2221,9 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams params,
{
StdRdOptions *opts = (StdRdOptions *) rel->rd_options;
- if (opts && opts->vacuum_truncate_set)
+ if (opts && opts->vacuum_truncate != TERNARY_UNSET)
{
- if (opts->vacuum_truncate)
+ if (opts->vacuum_truncate == TERNARY_TRUE)
params.truncate = VACOPTVALUE_ENABLED;
else
params.truncate = VACOPTVALUE_DISABLED;
diff --git a/src/include/access/reloptions.h b/src/include/access/reloptions.h
index a604a4702c..a436697658 100644
--- a/src/include/access/reloptions.h
+++ b/src/include/access/reloptions.h
@@ -29,6 +29,7 @@
typedef enum relopt_type
{
RELOPT_TYPE_BOOL,
+ RELOPT_TYPE_TERNARY, /* on, off, unset */
RELOPT_TYPE_INT,
RELOPT_TYPE_REAL,
RELOPT_TYPE_ENUM,
@@ -80,6 +81,7 @@ typedef struct relopt_value
union
{
bool bool_val;
+ ternary ternary_val;
int int_val;
double real_val;
int enum_val;
@@ -94,6 +96,12 @@ typedef struct relopt_bool
bool default_val;
} relopt_bool;
+typedef struct relopt_rernary
+{
+ relopt_gen gen;
+ int default_val;
+} relopt_ternary;
+
typedef struct relopt_int
{
relopt_gen gen;
@@ -152,19 +160,6 @@ typedef struct
const char *optname; /* option's name */
relopt_type opttype; /* option's datatype */
int offset; /* offset of field in result struct */
-
- /*
- * isset_offset is an optional offset of a field in the result struct that
- * stores whether the option is explicitly set for the relation or if it
- * just picked up the default value. In most cases, this can be
- * accomplished by giving the reloption a special out-of-range default
- * value (e.g., some integer reloptions use -2), but this isn't always
- * possible. For example, a Boolean reloption cannot be given an
- * out-of-range default, so we need another way to discover the source of
- * its value. This offset is only used if given a value greater than
- * zero.
- */
- int isset_offset;
} relopt_parse_elt;
/* Local reloption definition */
@@ -195,6 +190,8 @@ typedef struct local_relopts
extern relopt_kind add_reloption_kind(void);
extern void add_bool_reloption(bits32 kinds, const char *name, const char *desc,
bool default_val, LOCKMODE lockmode);
+extern void add_ternary_reloption(bits32 kinds, const char *name,
+ const char *desc, int default_val, LOCKMODE lockmode);
extern void add_int_reloption(bits32 kinds, const char *name, const char *desc,
int default_val, int min_val, int max_val,
LOCKMODE lockmode);
@@ -214,6 +211,9 @@ extern void register_reloptions_validator(local_relopts *relopts,
extern void add_local_bool_reloption(local_relopts *relopts, const char *name,
const char *desc, bool default_val,
int offset);
+extern void add_local_ternary_reloption(local_relopts *relopts,
+ const char *name, const char *desc,
+ ternary default_val, int offset);
extern void add_local_int_reloption(local_relopts *relopts, const char *name,
const char *desc, int default_val,
int min_val, int max_val, int offset);
diff --git a/src/include/c.h b/src/include/c.h
index 39022f8a9d..9c59ccccf8 100644
--- a/src/include/c.h
+++ b/src/include/c.h
@@ -488,6 +488,22 @@ typedef void (*pg_funcptr_t) (void);
#include <stdbool.h>
+/*
+ * ternary
+ * Boolean value with an extrea "unset" option
+ *
+ * Ternary data type is used in relation options that can be "true", "false" or
+ * "unset". Since relation options are used deep inside the PostgreSQL code,
+ * this type is declared globally.
+*/
+
+typedef enum ternary
+{
+ TERNARY_FALSE = 0,
+ TERNARY_TRUE = 1,
+ TERNARY_UNSET = -1
+} ternary;
+
/* ----------------------------------------------------------------
* Section 3: standard system types
diff --git a/src/include/utils/rel.h b/src/include/utils/rel.h
index b552359915..7346d618f9 100644
--- a/src/include/utils/rel.h
+++ b/src/include/utils/rel.h
@@ -346,8 +346,7 @@ typedef struct StdRdOptions
bool user_catalog_table; /* use as an additional catalog relation */
int parallel_workers; /* max number of parallel workers */
StdRdOptIndexCleanup vacuum_index_cleanup; /* controls index vacuuming */
- bool vacuum_truncate; /* enables vacuum to truncate a relation */
- bool vacuum_truncate_set; /* whether vacuum_truncate is set */
+ ternary vacuum_truncate; /* enables vacuum to truncate a relation */
/*
* Fraction of pages in a relation that vacuum can eagerly scan and fail
--
2.39.2
--nextPart4155600.3ZeAukHxDK
Content-Disposition: attachment;
filename="v2a-0003-Add-alias-to-be-used-as-unset-state.patch"
Content-Transfer-Encoding: 7Bit
Content-Type: text/x-patch; charset="unicode-2-0-utf-8";
name="v2a-0003-Add-alias-to-be-used-as-unset-state.patch"
^ permalink raw reply [nested|flat] 868+ messages in thread
* [PATCH v2 2/4] Introduce ternary reloptions
@ 2025-09-04 14:41 Nikolay Shaplov <[email protected]>
0 siblings, 0 replies; 868+ messages in thread
From: Nikolay Shaplov @ 2025-09-04 14:41 UTC (permalink / raw)
Introduce ternary reloption as a replacement for current `vacuum_truncate`
implementation. Remove `vacuum_truncate_set` additional flag and using
`TERNARY_UNSET` value instead.
---
src/backend/access/common/reloptions.c | 129 ++++++++++++++++++++-----
src/backend/commands/vacuum.c | 4 +-
src/include/access/reloptions.h | 26 ++---
src/include/c.h | 16 +++
src/include/utils/rel.h | 3 +-
5 files changed, 135 insertions(+), 43 deletions(-)
diff --git a/src/backend/access/common/reloptions.c b/src/backend/access/common/reloptions.c
index 0af3fea68f..f543f11001 100644
--- a/src/backend/access/common/reloptions.c
+++ b/src/backend/access/common/reloptions.c
@@ -40,9 +40,9 @@
*
* To add an option:
*
- * (i) decide on a type (bool, integer, real, enum, string), name, default
- * value, upper and lower bounds (if applicable); for strings, consider a
- * validation routine.
+ * (i) decide on a type (bool, ternary, integer, real, enum, string), name,
+ * default value, upper and lower bounds (if applicable); for strings,
+ * consider a validation routine.
* (ii) add a record below (or use add_<type>_reloption).
* (iii) add it to the appropriate options struct (perhaps StdRdOptions)
* (iv) add it to the appropriate handling routine (perhaps
@@ -147,15 +147,6 @@ static relopt_bool boolRelOpts[] =
},
false
},
- {
- {
- "vacuum_truncate",
- "Enables vacuum to truncate empty pages at the end of this table",
- RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
- ShareUpdateExclusiveLock
- },
- true
- },
{
{
"deduplicate_items",
@@ -170,6 +161,21 @@ static relopt_bool boolRelOpts[] =
{{NULL}}
};
+static relopt_ternary ternaryRelOpts[] =
+{
+ {
+ {
+ "vacuum_truncate",
+ "Enables vacuum to truncate empty pages at the end of this table",
+ RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
+ ShareUpdateExclusiveLock
+ },
+ TERNARY_UNSET
+ },
+ /* list terminator */
+ {{NULL}}
+};
+
static relopt_int intRelOpts[] =
{
{
@@ -600,6 +606,13 @@ initialize_reloptions(void)
boolRelOpts[i].gen.lockmode));
j++;
}
+ for (i = 0; ternaryRelOpts[i].gen.name; i++)
+ {
+ Assert(DoLockModesConflict(ternaryRelOpts[i].gen.lockmode,
+ ternaryRelOpts[i].gen.lockmode));
+ j++;
+ }
+
for (i = 0; intRelOpts[i].gen.name; i++)
{
Assert(DoLockModesConflict(intRelOpts[i].gen.lockmode,
@@ -640,6 +653,14 @@ initialize_reloptions(void)
j++;
}
+ for (i = 0; ternaryRelOpts[i].gen.name; i++)
+ {
+ relOpts[j] = &ternaryRelOpts[i].gen;
+ relOpts[j]->type = RELOPT_TYPE_TERNARY;
+ relOpts[j]->namelen = strlen(relOpts[j]->name);
+ j++;
+ }
+
for (i = 0; intRelOpts[i].gen.name; i++)
{
relOpts[j] = &intRelOpts[i].gen;
@@ -800,6 +821,9 @@ allocate_reloption(bits32 kinds, int type, const char *name, const char *desc,
case RELOPT_TYPE_BOOL:
size = sizeof(relopt_bool);
break;
+ case RELOPT_TYPE_TERNARY:
+ size = sizeof(relopt_ternary);
+ break;
case RELOPT_TYPE_INT:
size = sizeof(relopt_int);
break;
@@ -883,6 +907,54 @@ add_local_bool_reloption(local_relopts *relopts, const char *name,
add_local_reloption(relopts, (relopt_gen *) newoption, offset);
}
+/*
+ * init_ternary_reloption
+ * Allocate and initialize a new ternary reloption
+ */
+static relopt_ternary *
+init_ternary_reloption(bits32 kinds, const char *name, const char *desc,
+ ternary default_val, LOCKMODE lockmode)
+{
+ relopt_ternary *newoption;
+
+ newoption = (relopt_ternary *) allocate_reloption(kinds,
+ RELOPT_TYPE_TERNARY, name, desc, lockmode);
+ newoption->default_val = default_val;
+
+ return newoption;
+}
+
+/*
+ * add_ternary_reloption
+ * Add a new ternary reloption
+ */
+void
+add_ternary_reloption(bits32 kinds, const char *name, const char *desc,
+ ternary default_val, LOCKMODE lockmode)
+{
+ relopt_ternary *newoption = init_ternary_reloption(kinds, name, desc,
+ default_val, lockmode);
+
+ add_reloption((relopt_gen *) newoption);
+}
+
+/*
+ * add_local_ternary_reloption
+ * Add a new ternary local reloption
+ *
+ * 'offset' is offset of ternary-typed field.
+ */
+void
+add_local_ternary_reloption(local_relopts *relopts, const char *name,
+ const char *desc, ternary default_val,
+ int offset)
+{
+ relopt_ternary *newoption = init_ternary_reloption(RELOPT_KIND_LOCAL,
+ name, desc,
+ default_val, 0);
+
+ add_local_reloption(relopts, (relopt_gen *) newoption, offset);
+}
/*
* init_real_reloption
@@ -1617,6 +1689,18 @@ parse_one_reloption(relopt_value *option, char *text_str, int text_len,
option->gen->name, value)));
}
break;
+ case RELOPT_TYPE_TERNARY:
+ {
+ bool b;
+ parsed = parse_bool(value, &b);
+ option->values.ternary_val = b ? TERNARY_TRUE : TERNARY_FALSE;
+ if (validate && !parsed)
+ ereport(ERROR,
+ (errcode(ERRCODE_INVALID_PARAMETER_VALUE),
+ errmsg("invalid value for ternary option \"%s\": %s",
+ option->gen->name, value)));
+ }
+ break;
case RELOPT_TYPE_INT:
{
relopt_int *optint = (relopt_int *) option->gen;
@@ -1780,17 +1864,6 @@ fillRelOptions(void *rdopts, Size basesize,
char *itempos = ((char *) rdopts) + elems[j].offset;
char *string_val;
- /*
- * If isset_offset is provided, store whether the reloption is
- * set there.
- */
- if (elems[j].isset_offset > 0)
- {
- char *setpos = ((char *) rdopts) + elems[j].isset_offset;
-
- *(bool *) setpos = options[i].isset;
- }
-
switch (options[i].gen->type)
{
case RELOPT_TYPE_BOOL:
@@ -1798,6 +1871,11 @@ fillRelOptions(void *rdopts, Size basesize,
options[i].values.bool_val :
((relopt_bool *) options[i].gen)->default_val;
break;
+ case RELOPT_TYPE_TERNARY:
+ *(ternary *) itempos = options[i].isset ?
+ options[i].values.ternary_val :
+ ((relopt_ternary *) options[i].gen)->default_val;
+ break;
case RELOPT_TYPE_INT:
*(int *) itempos = options[i].isset ?
options[i].values.int_val :
@@ -1912,8 +1990,8 @@ default_reloptions(Datum reloptions, bool validate, relopt_kind kind)
offsetof(StdRdOptions, parallel_workers)},
{"vacuum_index_cleanup", RELOPT_TYPE_ENUM,
offsetof(StdRdOptions, vacuum_index_cleanup)},
- {"vacuum_truncate", RELOPT_TYPE_BOOL,
- offsetof(StdRdOptions, vacuum_truncate), offsetof(StdRdOptions, vacuum_truncate_set)},
+ {"vacuum_truncate", RELOPT_TYPE_TERNARY,
+ offsetof(StdRdOptions, vacuum_truncate)},
{"vacuum_max_eager_freeze_failure_rate", RELOPT_TYPE_REAL,
offsetof(StdRdOptions, vacuum_max_eager_freeze_failure_rate)}
};
@@ -1993,7 +2071,6 @@ build_local_reloptions(local_relopts *relopts, Datum options, bool validate)
elems[i].optname = opt->option->name;
elems[i].opttype = opt->option->type;
elems[i].offset = opt->offset;
- elems[i].isset_offset = 0; /* not supported for local relopts yet */
i++;
}
diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c
index 733ef40ae7..977babff54 100644
--- a/src/backend/commands/vacuum.c
+++ b/src/backend/commands/vacuum.c
@@ -2221,9 +2221,9 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams params,
{
StdRdOptions *opts = (StdRdOptions *) rel->rd_options;
- if (opts && opts->vacuum_truncate_set)
+ if (opts && opts->vacuum_truncate != TERNARY_UNSET)
{
- if (opts->vacuum_truncate)
+ if (opts->vacuum_truncate == TERNARY_TRUE)
params.truncate = VACOPTVALUE_ENABLED;
else
params.truncate = VACOPTVALUE_DISABLED;
diff --git a/src/include/access/reloptions.h b/src/include/access/reloptions.h
index a604a4702c..a436697658 100644
--- a/src/include/access/reloptions.h
+++ b/src/include/access/reloptions.h
@@ -29,6 +29,7 @@
typedef enum relopt_type
{
RELOPT_TYPE_BOOL,
+ RELOPT_TYPE_TERNARY, /* on, off, unset */
RELOPT_TYPE_INT,
RELOPT_TYPE_REAL,
RELOPT_TYPE_ENUM,
@@ -80,6 +81,7 @@ typedef struct relopt_value
union
{
bool bool_val;
+ ternary ternary_val;
int int_val;
double real_val;
int enum_val;
@@ -94,6 +96,12 @@ typedef struct relopt_bool
bool default_val;
} relopt_bool;
+typedef struct relopt_rernary
+{
+ relopt_gen gen;
+ int default_val;
+} relopt_ternary;
+
typedef struct relopt_int
{
relopt_gen gen;
@@ -152,19 +160,6 @@ typedef struct
const char *optname; /* option's name */
relopt_type opttype; /* option's datatype */
int offset; /* offset of field in result struct */
-
- /*
- * isset_offset is an optional offset of a field in the result struct that
- * stores whether the option is explicitly set for the relation or if it
- * just picked up the default value. In most cases, this can be
- * accomplished by giving the reloption a special out-of-range default
- * value (e.g., some integer reloptions use -2), but this isn't always
- * possible. For example, a Boolean reloption cannot be given an
- * out-of-range default, so we need another way to discover the source of
- * its value. This offset is only used if given a value greater than
- * zero.
- */
- int isset_offset;
} relopt_parse_elt;
/* Local reloption definition */
@@ -195,6 +190,8 @@ typedef struct local_relopts
extern relopt_kind add_reloption_kind(void);
extern void add_bool_reloption(bits32 kinds, const char *name, const char *desc,
bool default_val, LOCKMODE lockmode);
+extern void add_ternary_reloption(bits32 kinds, const char *name,
+ const char *desc, int default_val, LOCKMODE lockmode);
extern void add_int_reloption(bits32 kinds, const char *name, const char *desc,
int default_val, int min_val, int max_val,
LOCKMODE lockmode);
@@ -214,6 +211,9 @@ extern void register_reloptions_validator(local_relopts *relopts,
extern void add_local_bool_reloption(local_relopts *relopts, const char *name,
const char *desc, bool default_val,
int offset);
+extern void add_local_ternary_reloption(local_relopts *relopts,
+ const char *name, const char *desc,
+ ternary default_val, int offset);
extern void add_local_int_reloption(local_relopts *relopts, const char *name,
const char *desc, int default_val,
int min_val, int max_val, int offset);
diff --git a/src/include/c.h b/src/include/c.h
index 39022f8a9d..9c59ccccf8 100644
--- a/src/include/c.h
+++ b/src/include/c.h
@@ -488,6 +488,22 @@ typedef void (*pg_funcptr_t) (void);
#include <stdbool.h>
+/*
+ * ternary
+ * Boolean value with an extrea "unset" option
+ *
+ * Ternary data type is used in relation options that can be "true", "false" or
+ * "unset". Since relation options are used deep inside the PostgreSQL code,
+ * this type is declared globally.
+*/
+
+typedef enum ternary
+{
+ TERNARY_FALSE = 0,
+ TERNARY_TRUE = 1,
+ TERNARY_UNSET = -1
+} ternary;
+
/* ----------------------------------------------------------------
* Section 3: standard system types
diff --git a/src/include/utils/rel.h b/src/include/utils/rel.h
index b552359915..7346d618f9 100644
--- a/src/include/utils/rel.h
+++ b/src/include/utils/rel.h
@@ -346,8 +346,7 @@ typedef struct StdRdOptions
bool user_catalog_table; /* use as an additional catalog relation */
int parallel_workers; /* max number of parallel workers */
StdRdOptIndexCleanup vacuum_index_cleanup; /* controls index vacuuming */
- bool vacuum_truncate; /* enables vacuum to truncate a relation */
- bool vacuum_truncate_set; /* whether vacuum_truncate is set */
+ ternary vacuum_truncate; /* enables vacuum to truncate a relation */
/*
* Fraction of pages in a relation that vacuum can eagerly scan and fail
--
2.39.2
--nextPart5129932.5fSG56mABF
Content-Disposition: attachment;
filename="v2-0003-Add-alias-to-be-used-as-unset-state.patch"
Content-Transfer-Encoding: 7Bit
Content-Type: text/x-patch; charset="unicode-2-0-utf-8";
name="v2-0003-Add-alias-to-be-used-as-unset-state.patch"
^ permalink raw reply [nested|flat] 868+ messages in thread
* [PATCH v2 2/4] Introduce ternary reloptions
@ 2025-09-04 14:41 Nikolay Shaplov <[email protected]>
0 siblings, 0 replies; 868+ messages in thread
From: Nikolay Shaplov @ 2025-09-04 14:41 UTC (permalink / raw)
Introduce ternary reloption as a replacement for current `vacuum_truncate`
implementation. Remove `vacuum_truncate_set` additional flag and using
`TERNARY_UNSET` value instead.
---
src/backend/access/common/reloptions.c | 129 ++++++++++++++++++++-----
src/backend/commands/vacuum.c | 4 +-
src/include/access/reloptions.h | 26 ++---
src/include/c.h | 16 +++
src/include/utils/rel.h | 3 +-
5 files changed, 135 insertions(+), 43 deletions(-)
diff --git a/src/backend/access/common/reloptions.c b/src/backend/access/common/reloptions.c
index 0af3fea68f..f543f11001 100644
--- a/src/backend/access/common/reloptions.c
+++ b/src/backend/access/common/reloptions.c
@@ -40,9 +40,9 @@
*
* To add an option:
*
- * (i) decide on a type (bool, integer, real, enum, string), name, default
- * value, upper and lower bounds (if applicable); for strings, consider a
- * validation routine.
+ * (i) decide on a type (bool, ternary, integer, real, enum, string), name,
+ * default value, upper and lower bounds (if applicable); for strings,
+ * consider a validation routine.
* (ii) add a record below (or use add_<type>_reloption).
* (iii) add it to the appropriate options struct (perhaps StdRdOptions)
* (iv) add it to the appropriate handling routine (perhaps
@@ -147,15 +147,6 @@ static relopt_bool boolRelOpts[] =
},
false
},
- {
- {
- "vacuum_truncate",
- "Enables vacuum to truncate empty pages at the end of this table",
- RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
- ShareUpdateExclusiveLock
- },
- true
- },
{
{
"deduplicate_items",
@@ -170,6 +161,21 @@ static relopt_bool boolRelOpts[] =
{{NULL}}
};
+static relopt_ternary ternaryRelOpts[] =
+{
+ {
+ {
+ "vacuum_truncate",
+ "Enables vacuum to truncate empty pages at the end of this table",
+ RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
+ ShareUpdateExclusiveLock
+ },
+ TERNARY_UNSET
+ },
+ /* list terminator */
+ {{NULL}}
+};
+
static relopt_int intRelOpts[] =
{
{
@@ -600,6 +606,13 @@ initialize_reloptions(void)
boolRelOpts[i].gen.lockmode));
j++;
}
+ for (i = 0; ternaryRelOpts[i].gen.name; i++)
+ {
+ Assert(DoLockModesConflict(ternaryRelOpts[i].gen.lockmode,
+ ternaryRelOpts[i].gen.lockmode));
+ j++;
+ }
+
for (i = 0; intRelOpts[i].gen.name; i++)
{
Assert(DoLockModesConflict(intRelOpts[i].gen.lockmode,
@@ -640,6 +653,14 @@ initialize_reloptions(void)
j++;
}
+ for (i = 0; ternaryRelOpts[i].gen.name; i++)
+ {
+ relOpts[j] = &ternaryRelOpts[i].gen;
+ relOpts[j]->type = RELOPT_TYPE_TERNARY;
+ relOpts[j]->namelen = strlen(relOpts[j]->name);
+ j++;
+ }
+
for (i = 0; intRelOpts[i].gen.name; i++)
{
relOpts[j] = &intRelOpts[i].gen;
@@ -800,6 +821,9 @@ allocate_reloption(bits32 kinds, int type, const char *name, const char *desc,
case RELOPT_TYPE_BOOL:
size = sizeof(relopt_bool);
break;
+ case RELOPT_TYPE_TERNARY:
+ size = sizeof(relopt_ternary);
+ break;
case RELOPT_TYPE_INT:
size = sizeof(relopt_int);
break;
@@ -883,6 +907,54 @@ add_local_bool_reloption(local_relopts *relopts, const char *name,
add_local_reloption(relopts, (relopt_gen *) newoption, offset);
}
+/*
+ * init_ternary_reloption
+ * Allocate and initialize a new ternary reloption
+ */
+static relopt_ternary *
+init_ternary_reloption(bits32 kinds, const char *name, const char *desc,
+ ternary default_val, LOCKMODE lockmode)
+{
+ relopt_ternary *newoption;
+
+ newoption = (relopt_ternary *) allocate_reloption(kinds,
+ RELOPT_TYPE_TERNARY, name, desc, lockmode);
+ newoption->default_val = default_val;
+
+ return newoption;
+}
+
+/*
+ * add_ternary_reloption
+ * Add a new ternary reloption
+ */
+void
+add_ternary_reloption(bits32 kinds, const char *name, const char *desc,
+ ternary default_val, LOCKMODE lockmode)
+{
+ relopt_ternary *newoption = init_ternary_reloption(kinds, name, desc,
+ default_val, lockmode);
+
+ add_reloption((relopt_gen *) newoption);
+}
+
+/*
+ * add_local_ternary_reloption
+ * Add a new ternary local reloption
+ *
+ * 'offset' is offset of ternary-typed field.
+ */
+void
+add_local_ternary_reloption(local_relopts *relopts, const char *name,
+ const char *desc, ternary default_val,
+ int offset)
+{
+ relopt_ternary *newoption = init_ternary_reloption(RELOPT_KIND_LOCAL,
+ name, desc,
+ default_val, 0);
+
+ add_local_reloption(relopts, (relopt_gen *) newoption, offset);
+}
/*
* init_real_reloption
@@ -1617,6 +1689,18 @@ parse_one_reloption(relopt_value *option, char *text_str, int text_len,
option->gen->name, value)));
}
break;
+ case RELOPT_TYPE_TERNARY:
+ {
+ bool b;
+ parsed = parse_bool(value, &b);
+ option->values.ternary_val = b ? TERNARY_TRUE : TERNARY_FALSE;
+ if (validate && !parsed)
+ ereport(ERROR,
+ (errcode(ERRCODE_INVALID_PARAMETER_VALUE),
+ errmsg("invalid value for ternary option \"%s\": %s",
+ option->gen->name, value)));
+ }
+ break;
case RELOPT_TYPE_INT:
{
relopt_int *optint = (relopt_int *) option->gen;
@@ -1780,17 +1864,6 @@ fillRelOptions(void *rdopts, Size basesize,
char *itempos = ((char *) rdopts) + elems[j].offset;
char *string_val;
- /*
- * If isset_offset is provided, store whether the reloption is
- * set there.
- */
- if (elems[j].isset_offset > 0)
- {
- char *setpos = ((char *) rdopts) + elems[j].isset_offset;
-
- *(bool *) setpos = options[i].isset;
- }
-
switch (options[i].gen->type)
{
case RELOPT_TYPE_BOOL:
@@ -1798,6 +1871,11 @@ fillRelOptions(void *rdopts, Size basesize,
options[i].values.bool_val :
((relopt_bool *) options[i].gen)->default_val;
break;
+ case RELOPT_TYPE_TERNARY:
+ *(ternary *) itempos = options[i].isset ?
+ options[i].values.ternary_val :
+ ((relopt_ternary *) options[i].gen)->default_val;
+ break;
case RELOPT_TYPE_INT:
*(int *) itempos = options[i].isset ?
options[i].values.int_val :
@@ -1912,8 +1990,8 @@ default_reloptions(Datum reloptions, bool validate, relopt_kind kind)
offsetof(StdRdOptions, parallel_workers)},
{"vacuum_index_cleanup", RELOPT_TYPE_ENUM,
offsetof(StdRdOptions, vacuum_index_cleanup)},
- {"vacuum_truncate", RELOPT_TYPE_BOOL,
- offsetof(StdRdOptions, vacuum_truncate), offsetof(StdRdOptions, vacuum_truncate_set)},
+ {"vacuum_truncate", RELOPT_TYPE_TERNARY,
+ offsetof(StdRdOptions, vacuum_truncate)},
{"vacuum_max_eager_freeze_failure_rate", RELOPT_TYPE_REAL,
offsetof(StdRdOptions, vacuum_max_eager_freeze_failure_rate)}
};
@@ -1993,7 +2071,6 @@ build_local_reloptions(local_relopts *relopts, Datum options, bool validate)
elems[i].optname = opt->option->name;
elems[i].opttype = opt->option->type;
elems[i].offset = opt->offset;
- elems[i].isset_offset = 0; /* not supported for local relopts yet */
i++;
}
diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c
index 733ef40ae7..977babff54 100644
--- a/src/backend/commands/vacuum.c
+++ b/src/backend/commands/vacuum.c
@@ -2221,9 +2221,9 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams params,
{
StdRdOptions *opts = (StdRdOptions *) rel->rd_options;
- if (opts && opts->vacuum_truncate_set)
+ if (opts && opts->vacuum_truncate != TERNARY_UNSET)
{
- if (opts->vacuum_truncate)
+ if (opts->vacuum_truncate == TERNARY_TRUE)
params.truncate = VACOPTVALUE_ENABLED;
else
params.truncate = VACOPTVALUE_DISABLED;
diff --git a/src/include/access/reloptions.h b/src/include/access/reloptions.h
index a604a4702c..a436697658 100644
--- a/src/include/access/reloptions.h
+++ b/src/include/access/reloptions.h
@@ -29,6 +29,7 @@
typedef enum relopt_type
{
RELOPT_TYPE_BOOL,
+ RELOPT_TYPE_TERNARY, /* on, off, unset */
RELOPT_TYPE_INT,
RELOPT_TYPE_REAL,
RELOPT_TYPE_ENUM,
@@ -80,6 +81,7 @@ typedef struct relopt_value
union
{
bool bool_val;
+ ternary ternary_val;
int int_val;
double real_val;
int enum_val;
@@ -94,6 +96,12 @@ typedef struct relopt_bool
bool default_val;
} relopt_bool;
+typedef struct relopt_rernary
+{
+ relopt_gen gen;
+ int default_val;
+} relopt_ternary;
+
typedef struct relopt_int
{
relopt_gen gen;
@@ -152,19 +160,6 @@ typedef struct
const char *optname; /* option's name */
relopt_type opttype; /* option's datatype */
int offset; /* offset of field in result struct */
-
- /*
- * isset_offset is an optional offset of a field in the result struct that
- * stores whether the option is explicitly set for the relation or if it
- * just picked up the default value. In most cases, this can be
- * accomplished by giving the reloption a special out-of-range default
- * value (e.g., some integer reloptions use -2), but this isn't always
- * possible. For example, a Boolean reloption cannot be given an
- * out-of-range default, so we need another way to discover the source of
- * its value. This offset is only used if given a value greater than
- * zero.
- */
- int isset_offset;
} relopt_parse_elt;
/* Local reloption definition */
@@ -195,6 +190,8 @@ typedef struct local_relopts
extern relopt_kind add_reloption_kind(void);
extern void add_bool_reloption(bits32 kinds, const char *name, const char *desc,
bool default_val, LOCKMODE lockmode);
+extern void add_ternary_reloption(bits32 kinds, const char *name,
+ const char *desc, int default_val, LOCKMODE lockmode);
extern void add_int_reloption(bits32 kinds, const char *name, const char *desc,
int default_val, int min_val, int max_val,
LOCKMODE lockmode);
@@ -214,6 +211,9 @@ extern void register_reloptions_validator(local_relopts *relopts,
extern void add_local_bool_reloption(local_relopts *relopts, const char *name,
const char *desc, bool default_val,
int offset);
+extern void add_local_ternary_reloption(local_relopts *relopts,
+ const char *name, const char *desc,
+ ternary default_val, int offset);
extern void add_local_int_reloption(local_relopts *relopts, const char *name,
const char *desc, int default_val,
int min_val, int max_val, int offset);
diff --git a/src/include/c.h b/src/include/c.h
index 39022f8a9d..9c59ccccf8 100644
--- a/src/include/c.h
+++ b/src/include/c.h
@@ -488,6 +488,22 @@ typedef void (*pg_funcptr_t) (void);
#include <stdbool.h>
+/*
+ * ternary
+ * Boolean value with an extrea "unset" option
+ *
+ * Ternary data type is used in relation options that can be "true", "false" or
+ * "unset". Since relation options are used deep inside the PostgreSQL code,
+ * this type is declared globally.
+*/
+
+typedef enum ternary
+{
+ TERNARY_FALSE = 0,
+ TERNARY_TRUE = 1,
+ TERNARY_UNSET = -1
+} ternary;
+
/* ----------------------------------------------------------------
* Section 3: standard system types
diff --git a/src/include/utils/rel.h b/src/include/utils/rel.h
index b552359915..7346d618f9 100644
--- a/src/include/utils/rel.h
+++ b/src/include/utils/rel.h
@@ -346,8 +346,7 @@ typedef struct StdRdOptions
bool user_catalog_table; /* use as an additional catalog relation */
int parallel_workers; /* max number of parallel workers */
StdRdOptIndexCleanup vacuum_index_cleanup; /* controls index vacuuming */
- bool vacuum_truncate; /* enables vacuum to truncate a relation */
- bool vacuum_truncate_set; /* whether vacuum_truncate is set */
+ ternary vacuum_truncate; /* enables vacuum to truncate a relation */
/*
* Fraction of pages in a relation that vacuum can eagerly scan and fail
--
2.39.2
--nextPart5129932.5fSG56mABF
Content-Disposition: attachment;
filename="v2-0003-Add-alias-to-be-used-as-unset-state.patch"
Content-Transfer-Encoding: 7Bit
Content-Type: text/x-patch; charset="unicode-2-0-utf-8";
name="v2-0003-Add-alias-to-be-used-as-unset-state.patch"
^ permalink raw reply [nested|flat] 868+ messages in thread
* [PATCH v2 2/4] Introduce ternary reloptions
@ 2025-09-04 14:41 Nikolay Shaplov <[email protected]>
0 siblings, 0 replies; 868+ messages in thread
From: Nikolay Shaplov @ 2025-09-04 14:41 UTC (permalink / raw)
Introduce ternary reloption as a replacement for current `vacuum_truncate`
implementation. Remove `vacuum_truncate_set` additional flag and using
`TERNARY_UNSET` value instead.
---
src/backend/access/common/reloptions.c | 129 ++++++++++++++++++++-----
src/backend/commands/vacuum.c | 4 +-
src/include/access/reloptions.h | 26 ++---
src/include/c.h | 16 +++
src/include/utils/rel.h | 3 +-
5 files changed, 135 insertions(+), 43 deletions(-)
diff --git a/src/backend/access/common/reloptions.c b/src/backend/access/common/reloptions.c
index 0af3fea68f..f543f11001 100644
--- a/src/backend/access/common/reloptions.c
+++ b/src/backend/access/common/reloptions.c
@@ -40,9 +40,9 @@
*
* To add an option:
*
- * (i) decide on a type (bool, integer, real, enum, string), name, default
- * value, upper and lower bounds (if applicable); for strings, consider a
- * validation routine.
+ * (i) decide on a type (bool, ternary, integer, real, enum, string), name,
+ * default value, upper and lower bounds (if applicable); for strings,
+ * consider a validation routine.
* (ii) add a record below (or use add_<type>_reloption).
* (iii) add it to the appropriate options struct (perhaps StdRdOptions)
* (iv) add it to the appropriate handling routine (perhaps
@@ -147,15 +147,6 @@ static relopt_bool boolRelOpts[] =
},
false
},
- {
- {
- "vacuum_truncate",
- "Enables vacuum to truncate empty pages at the end of this table",
- RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
- ShareUpdateExclusiveLock
- },
- true
- },
{
{
"deduplicate_items",
@@ -170,6 +161,21 @@ static relopt_bool boolRelOpts[] =
{{NULL}}
};
+static relopt_ternary ternaryRelOpts[] =
+{
+ {
+ {
+ "vacuum_truncate",
+ "Enables vacuum to truncate empty pages at the end of this table",
+ RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
+ ShareUpdateExclusiveLock
+ },
+ TERNARY_UNSET
+ },
+ /* list terminator */
+ {{NULL}}
+};
+
static relopt_int intRelOpts[] =
{
{
@@ -600,6 +606,13 @@ initialize_reloptions(void)
boolRelOpts[i].gen.lockmode));
j++;
}
+ for (i = 0; ternaryRelOpts[i].gen.name; i++)
+ {
+ Assert(DoLockModesConflict(ternaryRelOpts[i].gen.lockmode,
+ ternaryRelOpts[i].gen.lockmode));
+ j++;
+ }
+
for (i = 0; intRelOpts[i].gen.name; i++)
{
Assert(DoLockModesConflict(intRelOpts[i].gen.lockmode,
@@ -640,6 +653,14 @@ initialize_reloptions(void)
j++;
}
+ for (i = 0; ternaryRelOpts[i].gen.name; i++)
+ {
+ relOpts[j] = &ternaryRelOpts[i].gen;
+ relOpts[j]->type = RELOPT_TYPE_TERNARY;
+ relOpts[j]->namelen = strlen(relOpts[j]->name);
+ j++;
+ }
+
for (i = 0; intRelOpts[i].gen.name; i++)
{
relOpts[j] = &intRelOpts[i].gen;
@@ -800,6 +821,9 @@ allocate_reloption(bits32 kinds, int type, const char *name, const char *desc,
case RELOPT_TYPE_BOOL:
size = sizeof(relopt_bool);
break;
+ case RELOPT_TYPE_TERNARY:
+ size = sizeof(relopt_ternary);
+ break;
case RELOPT_TYPE_INT:
size = sizeof(relopt_int);
break;
@@ -883,6 +907,54 @@ add_local_bool_reloption(local_relopts *relopts, const char *name,
add_local_reloption(relopts, (relopt_gen *) newoption, offset);
}
+/*
+ * init_ternary_reloption
+ * Allocate and initialize a new ternary reloption
+ */
+static relopt_ternary *
+init_ternary_reloption(bits32 kinds, const char *name, const char *desc,
+ ternary default_val, LOCKMODE lockmode)
+{
+ relopt_ternary *newoption;
+
+ newoption = (relopt_ternary *) allocate_reloption(kinds,
+ RELOPT_TYPE_TERNARY, name, desc, lockmode);
+ newoption->default_val = default_val;
+
+ return newoption;
+}
+
+/*
+ * add_ternary_reloption
+ * Add a new ternary reloption
+ */
+void
+add_ternary_reloption(bits32 kinds, const char *name, const char *desc,
+ ternary default_val, LOCKMODE lockmode)
+{
+ relopt_ternary *newoption = init_ternary_reloption(kinds, name, desc,
+ default_val, lockmode);
+
+ add_reloption((relopt_gen *) newoption);
+}
+
+/*
+ * add_local_ternary_reloption
+ * Add a new ternary local reloption
+ *
+ * 'offset' is offset of ternary-typed field.
+ */
+void
+add_local_ternary_reloption(local_relopts *relopts, const char *name,
+ const char *desc, ternary default_val,
+ int offset)
+{
+ relopt_ternary *newoption = init_ternary_reloption(RELOPT_KIND_LOCAL,
+ name, desc,
+ default_val, 0);
+
+ add_local_reloption(relopts, (relopt_gen *) newoption, offset);
+}
/*
* init_real_reloption
@@ -1617,6 +1689,18 @@ parse_one_reloption(relopt_value *option, char *text_str, int text_len,
option->gen->name, value)));
}
break;
+ case RELOPT_TYPE_TERNARY:
+ {
+ bool b;
+ parsed = parse_bool(value, &b);
+ option->values.ternary_val = b ? TERNARY_TRUE : TERNARY_FALSE;
+ if (validate && !parsed)
+ ereport(ERROR,
+ (errcode(ERRCODE_INVALID_PARAMETER_VALUE),
+ errmsg("invalid value for ternary option \"%s\": %s",
+ option->gen->name, value)));
+ }
+ break;
case RELOPT_TYPE_INT:
{
relopt_int *optint = (relopt_int *) option->gen;
@@ -1780,17 +1864,6 @@ fillRelOptions(void *rdopts, Size basesize,
char *itempos = ((char *) rdopts) + elems[j].offset;
char *string_val;
- /*
- * If isset_offset is provided, store whether the reloption is
- * set there.
- */
- if (elems[j].isset_offset > 0)
- {
- char *setpos = ((char *) rdopts) + elems[j].isset_offset;
-
- *(bool *) setpos = options[i].isset;
- }
-
switch (options[i].gen->type)
{
case RELOPT_TYPE_BOOL:
@@ -1798,6 +1871,11 @@ fillRelOptions(void *rdopts, Size basesize,
options[i].values.bool_val :
((relopt_bool *) options[i].gen)->default_val;
break;
+ case RELOPT_TYPE_TERNARY:
+ *(ternary *) itempos = options[i].isset ?
+ options[i].values.ternary_val :
+ ((relopt_ternary *) options[i].gen)->default_val;
+ break;
case RELOPT_TYPE_INT:
*(int *) itempos = options[i].isset ?
options[i].values.int_val :
@@ -1912,8 +1990,8 @@ default_reloptions(Datum reloptions, bool validate, relopt_kind kind)
offsetof(StdRdOptions, parallel_workers)},
{"vacuum_index_cleanup", RELOPT_TYPE_ENUM,
offsetof(StdRdOptions, vacuum_index_cleanup)},
- {"vacuum_truncate", RELOPT_TYPE_BOOL,
- offsetof(StdRdOptions, vacuum_truncate), offsetof(StdRdOptions, vacuum_truncate_set)},
+ {"vacuum_truncate", RELOPT_TYPE_TERNARY,
+ offsetof(StdRdOptions, vacuum_truncate)},
{"vacuum_max_eager_freeze_failure_rate", RELOPT_TYPE_REAL,
offsetof(StdRdOptions, vacuum_max_eager_freeze_failure_rate)}
};
@@ -1993,7 +2071,6 @@ build_local_reloptions(local_relopts *relopts, Datum options, bool validate)
elems[i].optname = opt->option->name;
elems[i].opttype = opt->option->type;
elems[i].offset = opt->offset;
- elems[i].isset_offset = 0; /* not supported for local relopts yet */
i++;
}
diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c
index 733ef40ae7..977babff54 100644
--- a/src/backend/commands/vacuum.c
+++ b/src/backend/commands/vacuum.c
@@ -2221,9 +2221,9 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams params,
{
StdRdOptions *opts = (StdRdOptions *) rel->rd_options;
- if (opts && opts->vacuum_truncate_set)
+ if (opts && opts->vacuum_truncate != TERNARY_UNSET)
{
- if (opts->vacuum_truncate)
+ if (opts->vacuum_truncate == TERNARY_TRUE)
params.truncate = VACOPTVALUE_ENABLED;
else
params.truncate = VACOPTVALUE_DISABLED;
diff --git a/src/include/access/reloptions.h b/src/include/access/reloptions.h
index a604a4702c..a436697658 100644
--- a/src/include/access/reloptions.h
+++ b/src/include/access/reloptions.h
@@ -29,6 +29,7 @@
typedef enum relopt_type
{
RELOPT_TYPE_BOOL,
+ RELOPT_TYPE_TERNARY, /* on, off, unset */
RELOPT_TYPE_INT,
RELOPT_TYPE_REAL,
RELOPT_TYPE_ENUM,
@@ -80,6 +81,7 @@ typedef struct relopt_value
union
{
bool bool_val;
+ ternary ternary_val;
int int_val;
double real_val;
int enum_val;
@@ -94,6 +96,12 @@ typedef struct relopt_bool
bool default_val;
} relopt_bool;
+typedef struct relopt_rernary
+{
+ relopt_gen gen;
+ int default_val;
+} relopt_ternary;
+
typedef struct relopt_int
{
relopt_gen gen;
@@ -152,19 +160,6 @@ typedef struct
const char *optname; /* option's name */
relopt_type opttype; /* option's datatype */
int offset; /* offset of field in result struct */
-
- /*
- * isset_offset is an optional offset of a field in the result struct that
- * stores whether the option is explicitly set for the relation or if it
- * just picked up the default value. In most cases, this can be
- * accomplished by giving the reloption a special out-of-range default
- * value (e.g., some integer reloptions use -2), but this isn't always
- * possible. For example, a Boolean reloption cannot be given an
- * out-of-range default, so we need another way to discover the source of
- * its value. This offset is only used if given a value greater than
- * zero.
- */
- int isset_offset;
} relopt_parse_elt;
/* Local reloption definition */
@@ -195,6 +190,8 @@ typedef struct local_relopts
extern relopt_kind add_reloption_kind(void);
extern void add_bool_reloption(bits32 kinds, const char *name, const char *desc,
bool default_val, LOCKMODE lockmode);
+extern void add_ternary_reloption(bits32 kinds, const char *name,
+ const char *desc, int default_val, LOCKMODE lockmode);
extern void add_int_reloption(bits32 kinds, const char *name, const char *desc,
int default_val, int min_val, int max_val,
LOCKMODE lockmode);
@@ -214,6 +211,9 @@ extern void register_reloptions_validator(local_relopts *relopts,
extern void add_local_bool_reloption(local_relopts *relopts, const char *name,
const char *desc, bool default_val,
int offset);
+extern void add_local_ternary_reloption(local_relopts *relopts,
+ const char *name, const char *desc,
+ ternary default_val, int offset);
extern void add_local_int_reloption(local_relopts *relopts, const char *name,
const char *desc, int default_val,
int min_val, int max_val, int offset);
diff --git a/src/include/c.h b/src/include/c.h
index 39022f8a9d..9c59ccccf8 100644
--- a/src/include/c.h
+++ b/src/include/c.h
@@ -488,6 +488,22 @@ typedef void (*pg_funcptr_t) (void);
#include <stdbool.h>
+/*
+ * ternary
+ * Boolean value with an extrea "unset" option
+ *
+ * Ternary data type is used in relation options that can be "true", "false" or
+ * "unset". Since relation options are used deep inside the PostgreSQL code,
+ * this type is declared globally.
+*/
+
+typedef enum ternary
+{
+ TERNARY_FALSE = 0,
+ TERNARY_TRUE = 1,
+ TERNARY_UNSET = -1
+} ternary;
+
/* ----------------------------------------------------------------
* Section 3: standard system types
diff --git a/src/include/utils/rel.h b/src/include/utils/rel.h
index b552359915..7346d618f9 100644
--- a/src/include/utils/rel.h
+++ b/src/include/utils/rel.h
@@ -346,8 +346,7 @@ typedef struct StdRdOptions
bool user_catalog_table; /* use as an additional catalog relation */
int parallel_workers; /* max number of parallel workers */
StdRdOptIndexCleanup vacuum_index_cleanup; /* controls index vacuuming */
- bool vacuum_truncate; /* enables vacuum to truncate a relation */
- bool vacuum_truncate_set; /* whether vacuum_truncate is set */
+ ternary vacuum_truncate; /* enables vacuum to truncate a relation */
/*
* Fraction of pages in a relation that vacuum can eagerly scan and fail
--
2.39.2
--nextPart5129932.5fSG56mABF
Content-Disposition: attachment;
filename="v2-0003-Add-alias-to-be-used-as-unset-state.patch"
Content-Transfer-Encoding: 7Bit
Content-Type: text/x-patch; charset="unicode-2-0-utf-8";
name="v2-0003-Add-alias-to-be-used-as-unset-state.patch"
^ permalink raw reply [nested|flat] 868+ messages in thread
* [PATCH v2a 2/4] Introduce ternary reloptions
@ 2025-09-04 14:41 Nikolay Shaplov <[email protected]>
0 siblings, 0 replies; 868+ messages in thread
From: Nikolay Shaplov @ 2025-09-04 14:41 UTC (permalink / raw)
Introduce ternary reloption as a replacement for current `vacuum_truncate`
implementation. Remove `vacuum_truncate_set` additional flag and using
`TERNARY_UNSET` value instead.
---
src/backend/access/common/reloptions.c | 129 ++++++++++++++++++++-----
src/backend/commands/vacuum.c | 4 +-
src/include/access/reloptions.h | 26 ++---
src/include/c.h | 16 +++
src/include/utils/rel.h | 3 +-
5 files changed, 135 insertions(+), 43 deletions(-)
diff --git a/src/backend/access/common/reloptions.c b/src/backend/access/common/reloptions.c
index 0af3fea68f..f543f11001 100644
--- a/src/backend/access/common/reloptions.c
+++ b/src/backend/access/common/reloptions.c
@@ -40,9 +40,9 @@
*
* To add an option:
*
- * (i) decide on a type (bool, integer, real, enum, string), name, default
- * value, upper and lower bounds (if applicable); for strings, consider a
- * validation routine.
+ * (i) decide on a type (bool, ternary, integer, real, enum, string), name,
+ * default value, upper and lower bounds (if applicable); for strings,
+ * consider a validation routine.
* (ii) add a record below (or use add_<type>_reloption).
* (iii) add it to the appropriate options struct (perhaps StdRdOptions)
* (iv) add it to the appropriate handling routine (perhaps
@@ -147,15 +147,6 @@ static relopt_bool boolRelOpts[] =
},
false
},
- {
- {
- "vacuum_truncate",
- "Enables vacuum to truncate empty pages at the end of this table",
- RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
- ShareUpdateExclusiveLock
- },
- true
- },
{
{
"deduplicate_items",
@@ -170,6 +161,21 @@ static relopt_bool boolRelOpts[] =
{{NULL}}
};
+static relopt_ternary ternaryRelOpts[] =
+{
+ {
+ {
+ "vacuum_truncate",
+ "Enables vacuum to truncate empty pages at the end of this table",
+ RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
+ ShareUpdateExclusiveLock
+ },
+ TERNARY_UNSET
+ },
+ /* list terminator */
+ {{NULL}}
+};
+
static relopt_int intRelOpts[] =
{
{
@@ -600,6 +606,13 @@ initialize_reloptions(void)
boolRelOpts[i].gen.lockmode));
j++;
}
+ for (i = 0; ternaryRelOpts[i].gen.name; i++)
+ {
+ Assert(DoLockModesConflict(ternaryRelOpts[i].gen.lockmode,
+ ternaryRelOpts[i].gen.lockmode));
+ j++;
+ }
+
for (i = 0; intRelOpts[i].gen.name; i++)
{
Assert(DoLockModesConflict(intRelOpts[i].gen.lockmode,
@@ -640,6 +653,14 @@ initialize_reloptions(void)
j++;
}
+ for (i = 0; ternaryRelOpts[i].gen.name; i++)
+ {
+ relOpts[j] = &ternaryRelOpts[i].gen;
+ relOpts[j]->type = RELOPT_TYPE_TERNARY;
+ relOpts[j]->namelen = strlen(relOpts[j]->name);
+ j++;
+ }
+
for (i = 0; intRelOpts[i].gen.name; i++)
{
relOpts[j] = &intRelOpts[i].gen;
@@ -800,6 +821,9 @@ allocate_reloption(bits32 kinds, int type, const char *name, const char *desc,
case RELOPT_TYPE_BOOL:
size = sizeof(relopt_bool);
break;
+ case RELOPT_TYPE_TERNARY:
+ size = sizeof(relopt_ternary);
+ break;
case RELOPT_TYPE_INT:
size = sizeof(relopt_int);
break;
@@ -883,6 +907,54 @@ add_local_bool_reloption(local_relopts *relopts, const char *name,
add_local_reloption(relopts, (relopt_gen *) newoption, offset);
}
+/*
+ * init_ternary_reloption
+ * Allocate and initialize a new ternary reloption
+ */
+static relopt_ternary *
+init_ternary_reloption(bits32 kinds, const char *name, const char *desc,
+ ternary default_val, LOCKMODE lockmode)
+{
+ relopt_ternary *newoption;
+
+ newoption = (relopt_ternary *) allocate_reloption(kinds,
+ RELOPT_TYPE_TERNARY, name, desc, lockmode);
+ newoption->default_val = default_val;
+
+ return newoption;
+}
+
+/*
+ * add_ternary_reloption
+ * Add a new ternary reloption
+ */
+void
+add_ternary_reloption(bits32 kinds, const char *name, const char *desc,
+ ternary default_val, LOCKMODE lockmode)
+{
+ relopt_ternary *newoption = init_ternary_reloption(kinds, name, desc,
+ default_val, lockmode);
+
+ add_reloption((relopt_gen *) newoption);
+}
+
+/*
+ * add_local_ternary_reloption
+ * Add a new ternary local reloption
+ *
+ * 'offset' is offset of ternary-typed field.
+ */
+void
+add_local_ternary_reloption(local_relopts *relopts, const char *name,
+ const char *desc, ternary default_val,
+ int offset)
+{
+ relopt_ternary *newoption = init_ternary_reloption(RELOPT_KIND_LOCAL,
+ name, desc,
+ default_val, 0);
+
+ add_local_reloption(relopts, (relopt_gen *) newoption, offset);
+}
/*
* init_real_reloption
@@ -1617,6 +1689,18 @@ parse_one_reloption(relopt_value *option, char *text_str, int text_len,
option->gen->name, value)));
}
break;
+ case RELOPT_TYPE_TERNARY:
+ {
+ bool b;
+ parsed = parse_bool(value, &b);
+ option->values.ternary_val = b ? TERNARY_TRUE : TERNARY_FALSE;
+ if (validate && !parsed)
+ ereport(ERROR,
+ (errcode(ERRCODE_INVALID_PARAMETER_VALUE),
+ errmsg("invalid value for ternary option \"%s\": %s",
+ option->gen->name, value)));
+ }
+ break;
case RELOPT_TYPE_INT:
{
relopt_int *optint = (relopt_int *) option->gen;
@@ -1780,17 +1864,6 @@ fillRelOptions(void *rdopts, Size basesize,
char *itempos = ((char *) rdopts) + elems[j].offset;
char *string_val;
- /*
- * If isset_offset is provided, store whether the reloption is
- * set there.
- */
- if (elems[j].isset_offset > 0)
- {
- char *setpos = ((char *) rdopts) + elems[j].isset_offset;
-
- *(bool *) setpos = options[i].isset;
- }
-
switch (options[i].gen->type)
{
case RELOPT_TYPE_BOOL:
@@ -1798,6 +1871,11 @@ fillRelOptions(void *rdopts, Size basesize,
options[i].values.bool_val :
((relopt_bool *) options[i].gen)->default_val;
break;
+ case RELOPT_TYPE_TERNARY:
+ *(ternary *) itempos = options[i].isset ?
+ options[i].values.ternary_val :
+ ((relopt_ternary *) options[i].gen)->default_val;
+ break;
case RELOPT_TYPE_INT:
*(int *) itempos = options[i].isset ?
options[i].values.int_val :
@@ -1912,8 +1990,8 @@ default_reloptions(Datum reloptions, bool validate, relopt_kind kind)
offsetof(StdRdOptions, parallel_workers)},
{"vacuum_index_cleanup", RELOPT_TYPE_ENUM,
offsetof(StdRdOptions, vacuum_index_cleanup)},
- {"vacuum_truncate", RELOPT_TYPE_BOOL,
- offsetof(StdRdOptions, vacuum_truncate), offsetof(StdRdOptions, vacuum_truncate_set)},
+ {"vacuum_truncate", RELOPT_TYPE_TERNARY,
+ offsetof(StdRdOptions, vacuum_truncate)},
{"vacuum_max_eager_freeze_failure_rate", RELOPT_TYPE_REAL,
offsetof(StdRdOptions, vacuum_max_eager_freeze_failure_rate)}
};
@@ -1993,7 +2071,6 @@ build_local_reloptions(local_relopts *relopts, Datum options, bool validate)
elems[i].optname = opt->option->name;
elems[i].opttype = opt->option->type;
elems[i].offset = opt->offset;
- elems[i].isset_offset = 0; /* not supported for local relopts yet */
i++;
}
diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c
index 733ef40ae7..977babff54 100644
--- a/src/backend/commands/vacuum.c
+++ b/src/backend/commands/vacuum.c
@@ -2221,9 +2221,9 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams params,
{
StdRdOptions *opts = (StdRdOptions *) rel->rd_options;
- if (opts && opts->vacuum_truncate_set)
+ if (opts && opts->vacuum_truncate != TERNARY_UNSET)
{
- if (opts->vacuum_truncate)
+ if (opts->vacuum_truncate == TERNARY_TRUE)
params.truncate = VACOPTVALUE_ENABLED;
else
params.truncate = VACOPTVALUE_DISABLED;
diff --git a/src/include/access/reloptions.h b/src/include/access/reloptions.h
index a604a4702c..a436697658 100644
--- a/src/include/access/reloptions.h
+++ b/src/include/access/reloptions.h
@@ -29,6 +29,7 @@
typedef enum relopt_type
{
RELOPT_TYPE_BOOL,
+ RELOPT_TYPE_TERNARY, /* on, off, unset */
RELOPT_TYPE_INT,
RELOPT_TYPE_REAL,
RELOPT_TYPE_ENUM,
@@ -80,6 +81,7 @@ typedef struct relopt_value
union
{
bool bool_val;
+ ternary ternary_val;
int int_val;
double real_val;
int enum_val;
@@ -94,6 +96,12 @@ typedef struct relopt_bool
bool default_val;
} relopt_bool;
+typedef struct relopt_rernary
+{
+ relopt_gen gen;
+ int default_val;
+} relopt_ternary;
+
typedef struct relopt_int
{
relopt_gen gen;
@@ -152,19 +160,6 @@ typedef struct
const char *optname; /* option's name */
relopt_type opttype; /* option's datatype */
int offset; /* offset of field in result struct */
-
- /*
- * isset_offset is an optional offset of a field in the result struct that
- * stores whether the option is explicitly set for the relation or if it
- * just picked up the default value. In most cases, this can be
- * accomplished by giving the reloption a special out-of-range default
- * value (e.g., some integer reloptions use -2), but this isn't always
- * possible. For example, a Boolean reloption cannot be given an
- * out-of-range default, so we need another way to discover the source of
- * its value. This offset is only used if given a value greater than
- * zero.
- */
- int isset_offset;
} relopt_parse_elt;
/* Local reloption definition */
@@ -195,6 +190,8 @@ typedef struct local_relopts
extern relopt_kind add_reloption_kind(void);
extern void add_bool_reloption(bits32 kinds, const char *name, const char *desc,
bool default_val, LOCKMODE lockmode);
+extern void add_ternary_reloption(bits32 kinds, const char *name,
+ const char *desc, int default_val, LOCKMODE lockmode);
extern void add_int_reloption(bits32 kinds, const char *name, const char *desc,
int default_val, int min_val, int max_val,
LOCKMODE lockmode);
@@ -214,6 +211,9 @@ extern void register_reloptions_validator(local_relopts *relopts,
extern void add_local_bool_reloption(local_relopts *relopts, const char *name,
const char *desc, bool default_val,
int offset);
+extern void add_local_ternary_reloption(local_relopts *relopts,
+ const char *name, const char *desc,
+ ternary default_val, int offset);
extern void add_local_int_reloption(local_relopts *relopts, const char *name,
const char *desc, int default_val,
int min_val, int max_val, int offset);
diff --git a/src/include/c.h b/src/include/c.h
index 39022f8a9d..9c59ccccf8 100644
--- a/src/include/c.h
+++ b/src/include/c.h
@@ -488,6 +488,22 @@ typedef void (*pg_funcptr_t) (void);
#include <stdbool.h>
+/*
+ * ternary
+ * Boolean value with an extrea "unset" option
+ *
+ * Ternary data type is used in relation options that can be "true", "false" or
+ * "unset". Since relation options are used deep inside the PostgreSQL code,
+ * this type is declared globally.
+*/
+
+typedef enum ternary
+{
+ TERNARY_FALSE = 0,
+ TERNARY_TRUE = 1,
+ TERNARY_UNSET = -1
+} ternary;
+
/* ----------------------------------------------------------------
* Section 3: standard system types
diff --git a/src/include/utils/rel.h b/src/include/utils/rel.h
index b552359915..7346d618f9 100644
--- a/src/include/utils/rel.h
+++ b/src/include/utils/rel.h
@@ -346,8 +346,7 @@ typedef struct StdRdOptions
bool user_catalog_table; /* use as an additional catalog relation */
int parallel_workers; /* max number of parallel workers */
StdRdOptIndexCleanup vacuum_index_cleanup; /* controls index vacuuming */
- bool vacuum_truncate; /* enables vacuum to truncate a relation */
- bool vacuum_truncate_set; /* whether vacuum_truncate is set */
+ ternary vacuum_truncate; /* enables vacuum to truncate a relation */
/*
* Fraction of pages in a relation that vacuum can eagerly scan and fail
--
2.39.2
--nextPart4155600.3ZeAukHxDK
Content-Disposition: attachment;
filename="v2a-0003-Add-alias-to-be-used-as-unset-state.patch"
Content-Transfer-Encoding: 7Bit
Content-Type: text/x-patch; charset="unicode-2-0-utf-8";
name="v2a-0003-Add-alias-to-be-used-as-unset-state.patch"
^ permalink raw reply [nested|flat] 868+ messages in thread
* [PATCH v2a 2/4] Introduce ternary reloptions
@ 2025-09-04 14:41 Nikolay Shaplov <[email protected]>
0 siblings, 0 replies; 868+ messages in thread
From: Nikolay Shaplov @ 2025-09-04 14:41 UTC (permalink / raw)
Introduce ternary reloption as a replacement for current `vacuum_truncate`
implementation. Remove `vacuum_truncate_set` additional flag and using
`TERNARY_UNSET` value instead.
---
src/backend/access/common/reloptions.c | 129 ++++++++++++++++++++-----
src/backend/commands/vacuum.c | 4 +-
src/include/access/reloptions.h | 26 ++---
src/include/c.h | 16 +++
src/include/utils/rel.h | 3 +-
5 files changed, 135 insertions(+), 43 deletions(-)
diff --git a/src/backend/access/common/reloptions.c b/src/backend/access/common/reloptions.c
index 0af3fea68f..f543f11001 100644
--- a/src/backend/access/common/reloptions.c
+++ b/src/backend/access/common/reloptions.c
@@ -40,9 +40,9 @@
*
* To add an option:
*
- * (i) decide on a type (bool, integer, real, enum, string), name, default
- * value, upper and lower bounds (if applicable); for strings, consider a
- * validation routine.
+ * (i) decide on a type (bool, ternary, integer, real, enum, string), name,
+ * default value, upper and lower bounds (if applicable); for strings,
+ * consider a validation routine.
* (ii) add a record below (or use add_<type>_reloption).
* (iii) add it to the appropriate options struct (perhaps StdRdOptions)
* (iv) add it to the appropriate handling routine (perhaps
@@ -147,15 +147,6 @@ static relopt_bool boolRelOpts[] =
},
false
},
- {
- {
- "vacuum_truncate",
- "Enables vacuum to truncate empty pages at the end of this table",
- RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
- ShareUpdateExclusiveLock
- },
- true
- },
{
{
"deduplicate_items",
@@ -170,6 +161,21 @@ static relopt_bool boolRelOpts[] =
{{NULL}}
};
+static relopt_ternary ternaryRelOpts[] =
+{
+ {
+ {
+ "vacuum_truncate",
+ "Enables vacuum to truncate empty pages at the end of this table",
+ RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
+ ShareUpdateExclusiveLock
+ },
+ TERNARY_UNSET
+ },
+ /* list terminator */
+ {{NULL}}
+};
+
static relopt_int intRelOpts[] =
{
{
@@ -600,6 +606,13 @@ initialize_reloptions(void)
boolRelOpts[i].gen.lockmode));
j++;
}
+ for (i = 0; ternaryRelOpts[i].gen.name; i++)
+ {
+ Assert(DoLockModesConflict(ternaryRelOpts[i].gen.lockmode,
+ ternaryRelOpts[i].gen.lockmode));
+ j++;
+ }
+
for (i = 0; intRelOpts[i].gen.name; i++)
{
Assert(DoLockModesConflict(intRelOpts[i].gen.lockmode,
@@ -640,6 +653,14 @@ initialize_reloptions(void)
j++;
}
+ for (i = 0; ternaryRelOpts[i].gen.name; i++)
+ {
+ relOpts[j] = &ternaryRelOpts[i].gen;
+ relOpts[j]->type = RELOPT_TYPE_TERNARY;
+ relOpts[j]->namelen = strlen(relOpts[j]->name);
+ j++;
+ }
+
for (i = 0; intRelOpts[i].gen.name; i++)
{
relOpts[j] = &intRelOpts[i].gen;
@@ -800,6 +821,9 @@ allocate_reloption(bits32 kinds, int type, const char *name, const char *desc,
case RELOPT_TYPE_BOOL:
size = sizeof(relopt_bool);
break;
+ case RELOPT_TYPE_TERNARY:
+ size = sizeof(relopt_ternary);
+ break;
case RELOPT_TYPE_INT:
size = sizeof(relopt_int);
break;
@@ -883,6 +907,54 @@ add_local_bool_reloption(local_relopts *relopts, const char *name,
add_local_reloption(relopts, (relopt_gen *) newoption, offset);
}
+/*
+ * init_ternary_reloption
+ * Allocate and initialize a new ternary reloption
+ */
+static relopt_ternary *
+init_ternary_reloption(bits32 kinds, const char *name, const char *desc,
+ ternary default_val, LOCKMODE lockmode)
+{
+ relopt_ternary *newoption;
+
+ newoption = (relopt_ternary *) allocate_reloption(kinds,
+ RELOPT_TYPE_TERNARY, name, desc, lockmode);
+ newoption->default_val = default_val;
+
+ return newoption;
+}
+
+/*
+ * add_ternary_reloption
+ * Add a new ternary reloption
+ */
+void
+add_ternary_reloption(bits32 kinds, const char *name, const char *desc,
+ ternary default_val, LOCKMODE lockmode)
+{
+ relopt_ternary *newoption = init_ternary_reloption(kinds, name, desc,
+ default_val, lockmode);
+
+ add_reloption((relopt_gen *) newoption);
+}
+
+/*
+ * add_local_ternary_reloption
+ * Add a new ternary local reloption
+ *
+ * 'offset' is offset of ternary-typed field.
+ */
+void
+add_local_ternary_reloption(local_relopts *relopts, const char *name,
+ const char *desc, ternary default_val,
+ int offset)
+{
+ relopt_ternary *newoption = init_ternary_reloption(RELOPT_KIND_LOCAL,
+ name, desc,
+ default_val, 0);
+
+ add_local_reloption(relopts, (relopt_gen *) newoption, offset);
+}
/*
* init_real_reloption
@@ -1617,6 +1689,18 @@ parse_one_reloption(relopt_value *option, char *text_str, int text_len,
option->gen->name, value)));
}
break;
+ case RELOPT_TYPE_TERNARY:
+ {
+ bool b;
+ parsed = parse_bool(value, &b);
+ option->values.ternary_val = b ? TERNARY_TRUE : TERNARY_FALSE;
+ if (validate && !parsed)
+ ereport(ERROR,
+ (errcode(ERRCODE_INVALID_PARAMETER_VALUE),
+ errmsg("invalid value for ternary option \"%s\": %s",
+ option->gen->name, value)));
+ }
+ break;
case RELOPT_TYPE_INT:
{
relopt_int *optint = (relopt_int *) option->gen;
@@ -1780,17 +1864,6 @@ fillRelOptions(void *rdopts, Size basesize,
char *itempos = ((char *) rdopts) + elems[j].offset;
char *string_val;
- /*
- * If isset_offset is provided, store whether the reloption is
- * set there.
- */
- if (elems[j].isset_offset > 0)
- {
- char *setpos = ((char *) rdopts) + elems[j].isset_offset;
-
- *(bool *) setpos = options[i].isset;
- }
-
switch (options[i].gen->type)
{
case RELOPT_TYPE_BOOL:
@@ -1798,6 +1871,11 @@ fillRelOptions(void *rdopts, Size basesize,
options[i].values.bool_val :
((relopt_bool *) options[i].gen)->default_val;
break;
+ case RELOPT_TYPE_TERNARY:
+ *(ternary *) itempos = options[i].isset ?
+ options[i].values.ternary_val :
+ ((relopt_ternary *) options[i].gen)->default_val;
+ break;
case RELOPT_TYPE_INT:
*(int *) itempos = options[i].isset ?
options[i].values.int_val :
@@ -1912,8 +1990,8 @@ default_reloptions(Datum reloptions, bool validate, relopt_kind kind)
offsetof(StdRdOptions, parallel_workers)},
{"vacuum_index_cleanup", RELOPT_TYPE_ENUM,
offsetof(StdRdOptions, vacuum_index_cleanup)},
- {"vacuum_truncate", RELOPT_TYPE_BOOL,
- offsetof(StdRdOptions, vacuum_truncate), offsetof(StdRdOptions, vacuum_truncate_set)},
+ {"vacuum_truncate", RELOPT_TYPE_TERNARY,
+ offsetof(StdRdOptions, vacuum_truncate)},
{"vacuum_max_eager_freeze_failure_rate", RELOPT_TYPE_REAL,
offsetof(StdRdOptions, vacuum_max_eager_freeze_failure_rate)}
};
@@ -1993,7 +2071,6 @@ build_local_reloptions(local_relopts *relopts, Datum options, bool validate)
elems[i].optname = opt->option->name;
elems[i].opttype = opt->option->type;
elems[i].offset = opt->offset;
- elems[i].isset_offset = 0; /* not supported for local relopts yet */
i++;
}
diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c
index 733ef40ae7..977babff54 100644
--- a/src/backend/commands/vacuum.c
+++ b/src/backend/commands/vacuum.c
@@ -2221,9 +2221,9 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams params,
{
StdRdOptions *opts = (StdRdOptions *) rel->rd_options;
- if (opts && opts->vacuum_truncate_set)
+ if (opts && opts->vacuum_truncate != TERNARY_UNSET)
{
- if (opts->vacuum_truncate)
+ if (opts->vacuum_truncate == TERNARY_TRUE)
params.truncate = VACOPTVALUE_ENABLED;
else
params.truncate = VACOPTVALUE_DISABLED;
diff --git a/src/include/access/reloptions.h b/src/include/access/reloptions.h
index a604a4702c..a436697658 100644
--- a/src/include/access/reloptions.h
+++ b/src/include/access/reloptions.h
@@ -29,6 +29,7 @@
typedef enum relopt_type
{
RELOPT_TYPE_BOOL,
+ RELOPT_TYPE_TERNARY, /* on, off, unset */
RELOPT_TYPE_INT,
RELOPT_TYPE_REAL,
RELOPT_TYPE_ENUM,
@@ -80,6 +81,7 @@ typedef struct relopt_value
union
{
bool bool_val;
+ ternary ternary_val;
int int_val;
double real_val;
int enum_val;
@@ -94,6 +96,12 @@ typedef struct relopt_bool
bool default_val;
} relopt_bool;
+typedef struct relopt_rernary
+{
+ relopt_gen gen;
+ int default_val;
+} relopt_ternary;
+
typedef struct relopt_int
{
relopt_gen gen;
@@ -152,19 +160,6 @@ typedef struct
const char *optname; /* option's name */
relopt_type opttype; /* option's datatype */
int offset; /* offset of field in result struct */
-
- /*
- * isset_offset is an optional offset of a field in the result struct that
- * stores whether the option is explicitly set for the relation or if it
- * just picked up the default value. In most cases, this can be
- * accomplished by giving the reloption a special out-of-range default
- * value (e.g., some integer reloptions use -2), but this isn't always
- * possible. For example, a Boolean reloption cannot be given an
- * out-of-range default, so we need another way to discover the source of
- * its value. This offset is only used if given a value greater than
- * zero.
- */
- int isset_offset;
} relopt_parse_elt;
/* Local reloption definition */
@@ -195,6 +190,8 @@ typedef struct local_relopts
extern relopt_kind add_reloption_kind(void);
extern void add_bool_reloption(bits32 kinds, const char *name, const char *desc,
bool default_val, LOCKMODE lockmode);
+extern void add_ternary_reloption(bits32 kinds, const char *name,
+ const char *desc, int default_val, LOCKMODE lockmode);
extern void add_int_reloption(bits32 kinds, const char *name, const char *desc,
int default_val, int min_val, int max_val,
LOCKMODE lockmode);
@@ -214,6 +211,9 @@ extern void register_reloptions_validator(local_relopts *relopts,
extern void add_local_bool_reloption(local_relopts *relopts, const char *name,
const char *desc, bool default_val,
int offset);
+extern void add_local_ternary_reloption(local_relopts *relopts,
+ const char *name, const char *desc,
+ ternary default_val, int offset);
extern void add_local_int_reloption(local_relopts *relopts, const char *name,
const char *desc, int default_val,
int min_val, int max_val, int offset);
diff --git a/src/include/c.h b/src/include/c.h
index 39022f8a9d..9c59ccccf8 100644
--- a/src/include/c.h
+++ b/src/include/c.h
@@ -488,6 +488,22 @@ typedef void (*pg_funcptr_t) (void);
#include <stdbool.h>
+/*
+ * ternary
+ * Boolean value with an extrea "unset" option
+ *
+ * Ternary data type is used in relation options that can be "true", "false" or
+ * "unset". Since relation options are used deep inside the PostgreSQL code,
+ * this type is declared globally.
+*/
+
+typedef enum ternary
+{
+ TERNARY_FALSE = 0,
+ TERNARY_TRUE = 1,
+ TERNARY_UNSET = -1
+} ternary;
+
/* ----------------------------------------------------------------
* Section 3: standard system types
diff --git a/src/include/utils/rel.h b/src/include/utils/rel.h
index b552359915..7346d618f9 100644
--- a/src/include/utils/rel.h
+++ b/src/include/utils/rel.h
@@ -346,8 +346,7 @@ typedef struct StdRdOptions
bool user_catalog_table; /* use as an additional catalog relation */
int parallel_workers; /* max number of parallel workers */
StdRdOptIndexCleanup vacuum_index_cleanup; /* controls index vacuuming */
- bool vacuum_truncate; /* enables vacuum to truncate a relation */
- bool vacuum_truncate_set; /* whether vacuum_truncate is set */
+ ternary vacuum_truncate; /* enables vacuum to truncate a relation */
/*
* Fraction of pages in a relation that vacuum can eagerly scan and fail
--
2.39.2
--nextPart4155600.3ZeAukHxDK
Content-Disposition: attachment;
filename="v2a-0003-Add-alias-to-be-used-as-unset-state.patch"
Content-Transfer-Encoding: 7Bit
Content-Type: text/x-patch; charset="unicode-2-0-utf-8";
name="v2a-0003-Add-alias-to-be-used-as-unset-state.patch"
^ permalink raw reply [nested|flat] 868+ messages in thread
* [PATCH v2a 2/4] Introduce ternary reloptions
@ 2025-09-04 14:41 Nikolay Shaplov <[email protected]>
0 siblings, 0 replies; 868+ messages in thread
From: Nikolay Shaplov @ 2025-09-04 14:41 UTC (permalink / raw)
Introduce ternary reloption as a replacement for current `vacuum_truncate`
implementation. Remove `vacuum_truncate_set` additional flag and using
`TERNARY_UNSET` value instead.
---
src/backend/access/common/reloptions.c | 129 ++++++++++++++++++++-----
src/backend/commands/vacuum.c | 4 +-
src/include/access/reloptions.h | 26 ++---
src/include/c.h | 16 +++
src/include/utils/rel.h | 3 +-
5 files changed, 135 insertions(+), 43 deletions(-)
diff --git a/src/backend/access/common/reloptions.c b/src/backend/access/common/reloptions.c
index 0af3fea68f..f543f11001 100644
--- a/src/backend/access/common/reloptions.c
+++ b/src/backend/access/common/reloptions.c
@@ -40,9 +40,9 @@
*
* To add an option:
*
- * (i) decide on a type (bool, integer, real, enum, string), name, default
- * value, upper and lower bounds (if applicable); for strings, consider a
- * validation routine.
+ * (i) decide on a type (bool, ternary, integer, real, enum, string), name,
+ * default value, upper and lower bounds (if applicable); for strings,
+ * consider a validation routine.
* (ii) add a record below (or use add_<type>_reloption).
* (iii) add it to the appropriate options struct (perhaps StdRdOptions)
* (iv) add it to the appropriate handling routine (perhaps
@@ -147,15 +147,6 @@ static relopt_bool boolRelOpts[] =
},
false
},
- {
- {
- "vacuum_truncate",
- "Enables vacuum to truncate empty pages at the end of this table",
- RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
- ShareUpdateExclusiveLock
- },
- true
- },
{
{
"deduplicate_items",
@@ -170,6 +161,21 @@ static relopt_bool boolRelOpts[] =
{{NULL}}
};
+static relopt_ternary ternaryRelOpts[] =
+{
+ {
+ {
+ "vacuum_truncate",
+ "Enables vacuum to truncate empty pages at the end of this table",
+ RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
+ ShareUpdateExclusiveLock
+ },
+ TERNARY_UNSET
+ },
+ /* list terminator */
+ {{NULL}}
+};
+
static relopt_int intRelOpts[] =
{
{
@@ -600,6 +606,13 @@ initialize_reloptions(void)
boolRelOpts[i].gen.lockmode));
j++;
}
+ for (i = 0; ternaryRelOpts[i].gen.name; i++)
+ {
+ Assert(DoLockModesConflict(ternaryRelOpts[i].gen.lockmode,
+ ternaryRelOpts[i].gen.lockmode));
+ j++;
+ }
+
for (i = 0; intRelOpts[i].gen.name; i++)
{
Assert(DoLockModesConflict(intRelOpts[i].gen.lockmode,
@@ -640,6 +653,14 @@ initialize_reloptions(void)
j++;
}
+ for (i = 0; ternaryRelOpts[i].gen.name; i++)
+ {
+ relOpts[j] = &ternaryRelOpts[i].gen;
+ relOpts[j]->type = RELOPT_TYPE_TERNARY;
+ relOpts[j]->namelen = strlen(relOpts[j]->name);
+ j++;
+ }
+
for (i = 0; intRelOpts[i].gen.name; i++)
{
relOpts[j] = &intRelOpts[i].gen;
@@ -800,6 +821,9 @@ allocate_reloption(bits32 kinds, int type, const char *name, const char *desc,
case RELOPT_TYPE_BOOL:
size = sizeof(relopt_bool);
break;
+ case RELOPT_TYPE_TERNARY:
+ size = sizeof(relopt_ternary);
+ break;
case RELOPT_TYPE_INT:
size = sizeof(relopt_int);
break;
@@ -883,6 +907,54 @@ add_local_bool_reloption(local_relopts *relopts, const char *name,
add_local_reloption(relopts, (relopt_gen *) newoption, offset);
}
+/*
+ * init_ternary_reloption
+ * Allocate and initialize a new ternary reloption
+ */
+static relopt_ternary *
+init_ternary_reloption(bits32 kinds, const char *name, const char *desc,
+ ternary default_val, LOCKMODE lockmode)
+{
+ relopt_ternary *newoption;
+
+ newoption = (relopt_ternary *) allocate_reloption(kinds,
+ RELOPT_TYPE_TERNARY, name, desc, lockmode);
+ newoption->default_val = default_val;
+
+ return newoption;
+}
+
+/*
+ * add_ternary_reloption
+ * Add a new ternary reloption
+ */
+void
+add_ternary_reloption(bits32 kinds, const char *name, const char *desc,
+ ternary default_val, LOCKMODE lockmode)
+{
+ relopt_ternary *newoption = init_ternary_reloption(kinds, name, desc,
+ default_val, lockmode);
+
+ add_reloption((relopt_gen *) newoption);
+}
+
+/*
+ * add_local_ternary_reloption
+ * Add a new ternary local reloption
+ *
+ * 'offset' is offset of ternary-typed field.
+ */
+void
+add_local_ternary_reloption(local_relopts *relopts, const char *name,
+ const char *desc, ternary default_val,
+ int offset)
+{
+ relopt_ternary *newoption = init_ternary_reloption(RELOPT_KIND_LOCAL,
+ name, desc,
+ default_val, 0);
+
+ add_local_reloption(relopts, (relopt_gen *) newoption, offset);
+}
/*
* init_real_reloption
@@ -1617,6 +1689,18 @@ parse_one_reloption(relopt_value *option, char *text_str, int text_len,
option->gen->name, value)));
}
break;
+ case RELOPT_TYPE_TERNARY:
+ {
+ bool b;
+ parsed = parse_bool(value, &b);
+ option->values.ternary_val = b ? TERNARY_TRUE : TERNARY_FALSE;
+ if (validate && !parsed)
+ ereport(ERROR,
+ (errcode(ERRCODE_INVALID_PARAMETER_VALUE),
+ errmsg("invalid value for ternary option \"%s\": %s",
+ option->gen->name, value)));
+ }
+ break;
case RELOPT_TYPE_INT:
{
relopt_int *optint = (relopt_int *) option->gen;
@@ -1780,17 +1864,6 @@ fillRelOptions(void *rdopts, Size basesize,
char *itempos = ((char *) rdopts) + elems[j].offset;
char *string_val;
- /*
- * If isset_offset is provided, store whether the reloption is
- * set there.
- */
- if (elems[j].isset_offset > 0)
- {
- char *setpos = ((char *) rdopts) + elems[j].isset_offset;
-
- *(bool *) setpos = options[i].isset;
- }
-
switch (options[i].gen->type)
{
case RELOPT_TYPE_BOOL:
@@ -1798,6 +1871,11 @@ fillRelOptions(void *rdopts, Size basesize,
options[i].values.bool_val :
((relopt_bool *) options[i].gen)->default_val;
break;
+ case RELOPT_TYPE_TERNARY:
+ *(ternary *) itempos = options[i].isset ?
+ options[i].values.ternary_val :
+ ((relopt_ternary *) options[i].gen)->default_val;
+ break;
case RELOPT_TYPE_INT:
*(int *) itempos = options[i].isset ?
options[i].values.int_val :
@@ -1912,8 +1990,8 @@ default_reloptions(Datum reloptions, bool validate, relopt_kind kind)
offsetof(StdRdOptions, parallel_workers)},
{"vacuum_index_cleanup", RELOPT_TYPE_ENUM,
offsetof(StdRdOptions, vacuum_index_cleanup)},
- {"vacuum_truncate", RELOPT_TYPE_BOOL,
- offsetof(StdRdOptions, vacuum_truncate), offsetof(StdRdOptions, vacuum_truncate_set)},
+ {"vacuum_truncate", RELOPT_TYPE_TERNARY,
+ offsetof(StdRdOptions, vacuum_truncate)},
{"vacuum_max_eager_freeze_failure_rate", RELOPT_TYPE_REAL,
offsetof(StdRdOptions, vacuum_max_eager_freeze_failure_rate)}
};
@@ -1993,7 +2071,6 @@ build_local_reloptions(local_relopts *relopts, Datum options, bool validate)
elems[i].optname = opt->option->name;
elems[i].opttype = opt->option->type;
elems[i].offset = opt->offset;
- elems[i].isset_offset = 0; /* not supported for local relopts yet */
i++;
}
diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c
index 733ef40ae7..977babff54 100644
--- a/src/backend/commands/vacuum.c
+++ b/src/backend/commands/vacuum.c
@@ -2221,9 +2221,9 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams params,
{
StdRdOptions *opts = (StdRdOptions *) rel->rd_options;
- if (opts && opts->vacuum_truncate_set)
+ if (opts && opts->vacuum_truncate != TERNARY_UNSET)
{
- if (opts->vacuum_truncate)
+ if (opts->vacuum_truncate == TERNARY_TRUE)
params.truncate = VACOPTVALUE_ENABLED;
else
params.truncate = VACOPTVALUE_DISABLED;
diff --git a/src/include/access/reloptions.h b/src/include/access/reloptions.h
index a604a4702c..a436697658 100644
--- a/src/include/access/reloptions.h
+++ b/src/include/access/reloptions.h
@@ -29,6 +29,7 @@
typedef enum relopt_type
{
RELOPT_TYPE_BOOL,
+ RELOPT_TYPE_TERNARY, /* on, off, unset */
RELOPT_TYPE_INT,
RELOPT_TYPE_REAL,
RELOPT_TYPE_ENUM,
@@ -80,6 +81,7 @@ typedef struct relopt_value
union
{
bool bool_val;
+ ternary ternary_val;
int int_val;
double real_val;
int enum_val;
@@ -94,6 +96,12 @@ typedef struct relopt_bool
bool default_val;
} relopt_bool;
+typedef struct relopt_rernary
+{
+ relopt_gen gen;
+ int default_val;
+} relopt_ternary;
+
typedef struct relopt_int
{
relopt_gen gen;
@@ -152,19 +160,6 @@ typedef struct
const char *optname; /* option's name */
relopt_type opttype; /* option's datatype */
int offset; /* offset of field in result struct */
-
- /*
- * isset_offset is an optional offset of a field in the result struct that
- * stores whether the option is explicitly set for the relation or if it
- * just picked up the default value. In most cases, this can be
- * accomplished by giving the reloption a special out-of-range default
- * value (e.g., some integer reloptions use -2), but this isn't always
- * possible. For example, a Boolean reloption cannot be given an
- * out-of-range default, so we need another way to discover the source of
- * its value. This offset is only used if given a value greater than
- * zero.
- */
- int isset_offset;
} relopt_parse_elt;
/* Local reloption definition */
@@ -195,6 +190,8 @@ typedef struct local_relopts
extern relopt_kind add_reloption_kind(void);
extern void add_bool_reloption(bits32 kinds, const char *name, const char *desc,
bool default_val, LOCKMODE lockmode);
+extern void add_ternary_reloption(bits32 kinds, const char *name,
+ const char *desc, int default_val, LOCKMODE lockmode);
extern void add_int_reloption(bits32 kinds, const char *name, const char *desc,
int default_val, int min_val, int max_val,
LOCKMODE lockmode);
@@ -214,6 +211,9 @@ extern void register_reloptions_validator(local_relopts *relopts,
extern void add_local_bool_reloption(local_relopts *relopts, const char *name,
const char *desc, bool default_val,
int offset);
+extern void add_local_ternary_reloption(local_relopts *relopts,
+ const char *name, const char *desc,
+ ternary default_val, int offset);
extern void add_local_int_reloption(local_relopts *relopts, const char *name,
const char *desc, int default_val,
int min_val, int max_val, int offset);
diff --git a/src/include/c.h b/src/include/c.h
index 39022f8a9d..9c59ccccf8 100644
--- a/src/include/c.h
+++ b/src/include/c.h
@@ -488,6 +488,22 @@ typedef void (*pg_funcptr_t) (void);
#include <stdbool.h>
+/*
+ * ternary
+ * Boolean value with an extrea "unset" option
+ *
+ * Ternary data type is used in relation options that can be "true", "false" or
+ * "unset". Since relation options are used deep inside the PostgreSQL code,
+ * this type is declared globally.
+*/
+
+typedef enum ternary
+{
+ TERNARY_FALSE = 0,
+ TERNARY_TRUE = 1,
+ TERNARY_UNSET = -1
+} ternary;
+
/* ----------------------------------------------------------------
* Section 3: standard system types
diff --git a/src/include/utils/rel.h b/src/include/utils/rel.h
index b552359915..7346d618f9 100644
--- a/src/include/utils/rel.h
+++ b/src/include/utils/rel.h
@@ -346,8 +346,7 @@ typedef struct StdRdOptions
bool user_catalog_table; /* use as an additional catalog relation */
int parallel_workers; /* max number of parallel workers */
StdRdOptIndexCleanup vacuum_index_cleanup; /* controls index vacuuming */
- bool vacuum_truncate; /* enables vacuum to truncate a relation */
- bool vacuum_truncate_set; /* whether vacuum_truncate is set */
+ ternary vacuum_truncate; /* enables vacuum to truncate a relation */
/*
* Fraction of pages in a relation that vacuum can eagerly scan and fail
--
2.39.2
--nextPart4155600.3ZeAukHxDK
Content-Disposition: attachment;
filename="v2a-0003-Add-alias-to-be-used-as-unset-state.patch"
Content-Transfer-Encoding: 7Bit
Content-Type: text/x-patch; charset="unicode-2-0-utf-8";
name="v2a-0003-Add-alias-to-be-used-as-unset-state.patch"
^ permalink raw reply [nested|flat] 868+ messages in thread
* [PATCH v2a 2/4] Introduce ternary reloptions
@ 2025-09-04 14:41 Nikolay Shaplov <[email protected]>
0 siblings, 0 replies; 868+ messages in thread
From: Nikolay Shaplov @ 2025-09-04 14:41 UTC (permalink / raw)
Introduce ternary reloption as a replacement for current `vacuum_truncate`
implementation. Remove `vacuum_truncate_set` additional flag and using
`TERNARY_UNSET` value instead.
---
src/backend/access/common/reloptions.c | 129 ++++++++++++++++++++-----
src/backend/commands/vacuum.c | 4 +-
src/include/access/reloptions.h | 26 ++---
src/include/c.h | 16 +++
src/include/utils/rel.h | 3 +-
5 files changed, 135 insertions(+), 43 deletions(-)
diff --git a/src/backend/access/common/reloptions.c b/src/backend/access/common/reloptions.c
index 0af3fea68f..f543f11001 100644
--- a/src/backend/access/common/reloptions.c
+++ b/src/backend/access/common/reloptions.c
@@ -40,9 +40,9 @@
*
* To add an option:
*
- * (i) decide on a type (bool, integer, real, enum, string), name, default
- * value, upper and lower bounds (if applicable); for strings, consider a
- * validation routine.
+ * (i) decide on a type (bool, ternary, integer, real, enum, string), name,
+ * default value, upper and lower bounds (if applicable); for strings,
+ * consider a validation routine.
* (ii) add a record below (or use add_<type>_reloption).
* (iii) add it to the appropriate options struct (perhaps StdRdOptions)
* (iv) add it to the appropriate handling routine (perhaps
@@ -147,15 +147,6 @@ static relopt_bool boolRelOpts[] =
},
false
},
- {
- {
- "vacuum_truncate",
- "Enables vacuum to truncate empty pages at the end of this table",
- RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
- ShareUpdateExclusiveLock
- },
- true
- },
{
{
"deduplicate_items",
@@ -170,6 +161,21 @@ static relopt_bool boolRelOpts[] =
{{NULL}}
};
+static relopt_ternary ternaryRelOpts[] =
+{
+ {
+ {
+ "vacuum_truncate",
+ "Enables vacuum to truncate empty pages at the end of this table",
+ RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
+ ShareUpdateExclusiveLock
+ },
+ TERNARY_UNSET
+ },
+ /* list terminator */
+ {{NULL}}
+};
+
static relopt_int intRelOpts[] =
{
{
@@ -600,6 +606,13 @@ initialize_reloptions(void)
boolRelOpts[i].gen.lockmode));
j++;
}
+ for (i = 0; ternaryRelOpts[i].gen.name; i++)
+ {
+ Assert(DoLockModesConflict(ternaryRelOpts[i].gen.lockmode,
+ ternaryRelOpts[i].gen.lockmode));
+ j++;
+ }
+
for (i = 0; intRelOpts[i].gen.name; i++)
{
Assert(DoLockModesConflict(intRelOpts[i].gen.lockmode,
@@ -640,6 +653,14 @@ initialize_reloptions(void)
j++;
}
+ for (i = 0; ternaryRelOpts[i].gen.name; i++)
+ {
+ relOpts[j] = &ternaryRelOpts[i].gen;
+ relOpts[j]->type = RELOPT_TYPE_TERNARY;
+ relOpts[j]->namelen = strlen(relOpts[j]->name);
+ j++;
+ }
+
for (i = 0; intRelOpts[i].gen.name; i++)
{
relOpts[j] = &intRelOpts[i].gen;
@@ -800,6 +821,9 @@ allocate_reloption(bits32 kinds, int type, const char *name, const char *desc,
case RELOPT_TYPE_BOOL:
size = sizeof(relopt_bool);
break;
+ case RELOPT_TYPE_TERNARY:
+ size = sizeof(relopt_ternary);
+ break;
case RELOPT_TYPE_INT:
size = sizeof(relopt_int);
break;
@@ -883,6 +907,54 @@ add_local_bool_reloption(local_relopts *relopts, const char *name,
add_local_reloption(relopts, (relopt_gen *) newoption, offset);
}
+/*
+ * init_ternary_reloption
+ * Allocate and initialize a new ternary reloption
+ */
+static relopt_ternary *
+init_ternary_reloption(bits32 kinds, const char *name, const char *desc,
+ ternary default_val, LOCKMODE lockmode)
+{
+ relopt_ternary *newoption;
+
+ newoption = (relopt_ternary *) allocate_reloption(kinds,
+ RELOPT_TYPE_TERNARY, name, desc, lockmode);
+ newoption->default_val = default_val;
+
+ return newoption;
+}
+
+/*
+ * add_ternary_reloption
+ * Add a new ternary reloption
+ */
+void
+add_ternary_reloption(bits32 kinds, const char *name, const char *desc,
+ ternary default_val, LOCKMODE lockmode)
+{
+ relopt_ternary *newoption = init_ternary_reloption(kinds, name, desc,
+ default_val, lockmode);
+
+ add_reloption((relopt_gen *) newoption);
+}
+
+/*
+ * add_local_ternary_reloption
+ * Add a new ternary local reloption
+ *
+ * 'offset' is offset of ternary-typed field.
+ */
+void
+add_local_ternary_reloption(local_relopts *relopts, const char *name,
+ const char *desc, ternary default_val,
+ int offset)
+{
+ relopt_ternary *newoption = init_ternary_reloption(RELOPT_KIND_LOCAL,
+ name, desc,
+ default_val, 0);
+
+ add_local_reloption(relopts, (relopt_gen *) newoption, offset);
+}
/*
* init_real_reloption
@@ -1617,6 +1689,18 @@ parse_one_reloption(relopt_value *option, char *text_str, int text_len,
option->gen->name, value)));
}
break;
+ case RELOPT_TYPE_TERNARY:
+ {
+ bool b;
+ parsed = parse_bool(value, &b);
+ option->values.ternary_val = b ? TERNARY_TRUE : TERNARY_FALSE;
+ if (validate && !parsed)
+ ereport(ERROR,
+ (errcode(ERRCODE_INVALID_PARAMETER_VALUE),
+ errmsg("invalid value for ternary option \"%s\": %s",
+ option->gen->name, value)));
+ }
+ break;
case RELOPT_TYPE_INT:
{
relopt_int *optint = (relopt_int *) option->gen;
@@ -1780,17 +1864,6 @@ fillRelOptions(void *rdopts, Size basesize,
char *itempos = ((char *) rdopts) + elems[j].offset;
char *string_val;
- /*
- * If isset_offset is provided, store whether the reloption is
- * set there.
- */
- if (elems[j].isset_offset > 0)
- {
- char *setpos = ((char *) rdopts) + elems[j].isset_offset;
-
- *(bool *) setpos = options[i].isset;
- }
-
switch (options[i].gen->type)
{
case RELOPT_TYPE_BOOL:
@@ -1798,6 +1871,11 @@ fillRelOptions(void *rdopts, Size basesize,
options[i].values.bool_val :
((relopt_bool *) options[i].gen)->default_val;
break;
+ case RELOPT_TYPE_TERNARY:
+ *(ternary *) itempos = options[i].isset ?
+ options[i].values.ternary_val :
+ ((relopt_ternary *) options[i].gen)->default_val;
+ break;
case RELOPT_TYPE_INT:
*(int *) itempos = options[i].isset ?
options[i].values.int_val :
@@ -1912,8 +1990,8 @@ default_reloptions(Datum reloptions, bool validate, relopt_kind kind)
offsetof(StdRdOptions, parallel_workers)},
{"vacuum_index_cleanup", RELOPT_TYPE_ENUM,
offsetof(StdRdOptions, vacuum_index_cleanup)},
- {"vacuum_truncate", RELOPT_TYPE_BOOL,
- offsetof(StdRdOptions, vacuum_truncate), offsetof(StdRdOptions, vacuum_truncate_set)},
+ {"vacuum_truncate", RELOPT_TYPE_TERNARY,
+ offsetof(StdRdOptions, vacuum_truncate)},
{"vacuum_max_eager_freeze_failure_rate", RELOPT_TYPE_REAL,
offsetof(StdRdOptions, vacuum_max_eager_freeze_failure_rate)}
};
@@ -1993,7 +2071,6 @@ build_local_reloptions(local_relopts *relopts, Datum options, bool validate)
elems[i].optname = opt->option->name;
elems[i].opttype = opt->option->type;
elems[i].offset = opt->offset;
- elems[i].isset_offset = 0; /* not supported for local relopts yet */
i++;
}
diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c
index 733ef40ae7..977babff54 100644
--- a/src/backend/commands/vacuum.c
+++ b/src/backend/commands/vacuum.c
@@ -2221,9 +2221,9 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams params,
{
StdRdOptions *opts = (StdRdOptions *) rel->rd_options;
- if (opts && opts->vacuum_truncate_set)
+ if (opts && opts->vacuum_truncate != TERNARY_UNSET)
{
- if (opts->vacuum_truncate)
+ if (opts->vacuum_truncate == TERNARY_TRUE)
params.truncate = VACOPTVALUE_ENABLED;
else
params.truncate = VACOPTVALUE_DISABLED;
diff --git a/src/include/access/reloptions.h b/src/include/access/reloptions.h
index a604a4702c..a436697658 100644
--- a/src/include/access/reloptions.h
+++ b/src/include/access/reloptions.h
@@ -29,6 +29,7 @@
typedef enum relopt_type
{
RELOPT_TYPE_BOOL,
+ RELOPT_TYPE_TERNARY, /* on, off, unset */
RELOPT_TYPE_INT,
RELOPT_TYPE_REAL,
RELOPT_TYPE_ENUM,
@@ -80,6 +81,7 @@ typedef struct relopt_value
union
{
bool bool_val;
+ ternary ternary_val;
int int_val;
double real_val;
int enum_val;
@@ -94,6 +96,12 @@ typedef struct relopt_bool
bool default_val;
} relopt_bool;
+typedef struct relopt_rernary
+{
+ relopt_gen gen;
+ int default_val;
+} relopt_ternary;
+
typedef struct relopt_int
{
relopt_gen gen;
@@ -152,19 +160,6 @@ typedef struct
const char *optname; /* option's name */
relopt_type opttype; /* option's datatype */
int offset; /* offset of field in result struct */
-
- /*
- * isset_offset is an optional offset of a field in the result struct that
- * stores whether the option is explicitly set for the relation or if it
- * just picked up the default value. In most cases, this can be
- * accomplished by giving the reloption a special out-of-range default
- * value (e.g., some integer reloptions use -2), but this isn't always
- * possible. For example, a Boolean reloption cannot be given an
- * out-of-range default, so we need another way to discover the source of
- * its value. This offset is only used if given a value greater than
- * zero.
- */
- int isset_offset;
} relopt_parse_elt;
/* Local reloption definition */
@@ -195,6 +190,8 @@ typedef struct local_relopts
extern relopt_kind add_reloption_kind(void);
extern void add_bool_reloption(bits32 kinds, const char *name, const char *desc,
bool default_val, LOCKMODE lockmode);
+extern void add_ternary_reloption(bits32 kinds, const char *name,
+ const char *desc, int default_val, LOCKMODE lockmode);
extern void add_int_reloption(bits32 kinds, const char *name, const char *desc,
int default_val, int min_val, int max_val,
LOCKMODE lockmode);
@@ -214,6 +211,9 @@ extern void register_reloptions_validator(local_relopts *relopts,
extern void add_local_bool_reloption(local_relopts *relopts, const char *name,
const char *desc, bool default_val,
int offset);
+extern void add_local_ternary_reloption(local_relopts *relopts,
+ const char *name, const char *desc,
+ ternary default_val, int offset);
extern void add_local_int_reloption(local_relopts *relopts, const char *name,
const char *desc, int default_val,
int min_val, int max_val, int offset);
diff --git a/src/include/c.h b/src/include/c.h
index 39022f8a9d..9c59ccccf8 100644
--- a/src/include/c.h
+++ b/src/include/c.h
@@ -488,6 +488,22 @@ typedef void (*pg_funcptr_t) (void);
#include <stdbool.h>
+/*
+ * ternary
+ * Boolean value with an extrea "unset" option
+ *
+ * Ternary data type is used in relation options that can be "true", "false" or
+ * "unset". Since relation options are used deep inside the PostgreSQL code,
+ * this type is declared globally.
+*/
+
+typedef enum ternary
+{
+ TERNARY_FALSE = 0,
+ TERNARY_TRUE = 1,
+ TERNARY_UNSET = -1
+} ternary;
+
/* ----------------------------------------------------------------
* Section 3: standard system types
diff --git a/src/include/utils/rel.h b/src/include/utils/rel.h
index b552359915..7346d618f9 100644
--- a/src/include/utils/rel.h
+++ b/src/include/utils/rel.h
@@ -346,8 +346,7 @@ typedef struct StdRdOptions
bool user_catalog_table; /* use as an additional catalog relation */
int parallel_workers; /* max number of parallel workers */
StdRdOptIndexCleanup vacuum_index_cleanup; /* controls index vacuuming */
- bool vacuum_truncate; /* enables vacuum to truncate a relation */
- bool vacuum_truncate_set; /* whether vacuum_truncate is set */
+ ternary vacuum_truncate; /* enables vacuum to truncate a relation */
/*
* Fraction of pages in a relation that vacuum can eagerly scan and fail
--
2.39.2
--nextPart4155600.3ZeAukHxDK
Content-Disposition: attachment;
filename="v2a-0003-Add-alias-to-be-used-as-unset-state.patch"
Content-Transfer-Encoding: 7Bit
Content-Type: text/x-patch; charset="unicode-2-0-utf-8";
name="v2a-0003-Add-alias-to-be-used-as-unset-state.patch"
^ permalink raw reply [nested|flat] 868+ messages in thread
* [PATCH v2a 2/4] Introduce ternary reloptions
@ 2025-09-04 14:41 Nikolay Shaplov <[email protected]>
0 siblings, 0 replies; 868+ messages in thread
From: Nikolay Shaplov @ 2025-09-04 14:41 UTC (permalink / raw)
Introduce ternary reloption as a replacement for current `vacuum_truncate`
implementation. Remove `vacuum_truncate_set` additional flag and using
`TERNARY_UNSET` value instead.
---
src/backend/access/common/reloptions.c | 129 ++++++++++++++++++++-----
src/backend/commands/vacuum.c | 4 +-
src/include/access/reloptions.h | 26 ++---
src/include/c.h | 16 +++
src/include/utils/rel.h | 3 +-
5 files changed, 135 insertions(+), 43 deletions(-)
diff --git a/src/backend/access/common/reloptions.c b/src/backend/access/common/reloptions.c
index 0af3fea68f..f543f11001 100644
--- a/src/backend/access/common/reloptions.c
+++ b/src/backend/access/common/reloptions.c
@@ -40,9 +40,9 @@
*
* To add an option:
*
- * (i) decide on a type (bool, integer, real, enum, string), name, default
- * value, upper and lower bounds (if applicable); for strings, consider a
- * validation routine.
+ * (i) decide on a type (bool, ternary, integer, real, enum, string), name,
+ * default value, upper and lower bounds (if applicable); for strings,
+ * consider a validation routine.
* (ii) add a record below (or use add_<type>_reloption).
* (iii) add it to the appropriate options struct (perhaps StdRdOptions)
* (iv) add it to the appropriate handling routine (perhaps
@@ -147,15 +147,6 @@ static relopt_bool boolRelOpts[] =
},
false
},
- {
- {
- "vacuum_truncate",
- "Enables vacuum to truncate empty pages at the end of this table",
- RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
- ShareUpdateExclusiveLock
- },
- true
- },
{
{
"deduplicate_items",
@@ -170,6 +161,21 @@ static relopt_bool boolRelOpts[] =
{{NULL}}
};
+static relopt_ternary ternaryRelOpts[] =
+{
+ {
+ {
+ "vacuum_truncate",
+ "Enables vacuum to truncate empty pages at the end of this table",
+ RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
+ ShareUpdateExclusiveLock
+ },
+ TERNARY_UNSET
+ },
+ /* list terminator */
+ {{NULL}}
+};
+
static relopt_int intRelOpts[] =
{
{
@@ -600,6 +606,13 @@ initialize_reloptions(void)
boolRelOpts[i].gen.lockmode));
j++;
}
+ for (i = 0; ternaryRelOpts[i].gen.name; i++)
+ {
+ Assert(DoLockModesConflict(ternaryRelOpts[i].gen.lockmode,
+ ternaryRelOpts[i].gen.lockmode));
+ j++;
+ }
+
for (i = 0; intRelOpts[i].gen.name; i++)
{
Assert(DoLockModesConflict(intRelOpts[i].gen.lockmode,
@@ -640,6 +653,14 @@ initialize_reloptions(void)
j++;
}
+ for (i = 0; ternaryRelOpts[i].gen.name; i++)
+ {
+ relOpts[j] = &ternaryRelOpts[i].gen;
+ relOpts[j]->type = RELOPT_TYPE_TERNARY;
+ relOpts[j]->namelen = strlen(relOpts[j]->name);
+ j++;
+ }
+
for (i = 0; intRelOpts[i].gen.name; i++)
{
relOpts[j] = &intRelOpts[i].gen;
@@ -800,6 +821,9 @@ allocate_reloption(bits32 kinds, int type, const char *name, const char *desc,
case RELOPT_TYPE_BOOL:
size = sizeof(relopt_bool);
break;
+ case RELOPT_TYPE_TERNARY:
+ size = sizeof(relopt_ternary);
+ break;
case RELOPT_TYPE_INT:
size = sizeof(relopt_int);
break;
@@ -883,6 +907,54 @@ add_local_bool_reloption(local_relopts *relopts, const char *name,
add_local_reloption(relopts, (relopt_gen *) newoption, offset);
}
+/*
+ * init_ternary_reloption
+ * Allocate and initialize a new ternary reloption
+ */
+static relopt_ternary *
+init_ternary_reloption(bits32 kinds, const char *name, const char *desc,
+ ternary default_val, LOCKMODE lockmode)
+{
+ relopt_ternary *newoption;
+
+ newoption = (relopt_ternary *) allocate_reloption(kinds,
+ RELOPT_TYPE_TERNARY, name, desc, lockmode);
+ newoption->default_val = default_val;
+
+ return newoption;
+}
+
+/*
+ * add_ternary_reloption
+ * Add a new ternary reloption
+ */
+void
+add_ternary_reloption(bits32 kinds, const char *name, const char *desc,
+ ternary default_val, LOCKMODE lockmode)
+{
+ relopt_ternary *newoption = init_ternary_reloption(kinds, name, desc,
+ default_val, lockmode);
+
+ add_reloption((relopt_gen *) newoption);
+}
+
+/*
+ * add_local_ternary_reloption
+ * Add a new ternary local reloption
+ *
+ * 'offset' is offset of ternary-typed field.
+ */
+void
+add_local_ternary_reloption(local_relopts *relopts, const char *name,
+ const char *desc, ternary default_val,
+ int offset)
+{
+ relopt_ternary *newoption = init_ternary_reloption(RELOPT_KIND_LOCAL,
+ name, desc,
+ default_val, 0);
+
+ add_local_reloption(relopts, (relopt_gen *) newoption, offset);
+}
/*
* init_real_reloption
@@ -1617,6 +1689,18 @@ parse_one_reloption(relopt_value *option, char *text_str, int text_len,
option->gen->name, value)));
}
break;
+ case RELOPT_TYPE_TERNARY:
+ {
+ bool b;
+ parsed = parse_bool(value, &b);
+ option->values.ternary_val = b ? TERNARY_TRUE : TERNARY_FALSE;
+ if (validate && !parsed)
+ ereport(ERROR,
+ (errcode(ERRCODE_INVALID_PARAMETER_VALUE),
+ errmsg("invalid value for ternary option \"%s\": %s",
+ option->gen->name, value)));
+ }
+ break;
case RELOPT_TYPE_INT:
{
relopt_int *optint = (relopt_int *) option->gen;
@@ -1780,17 +1864,6 @@ fillRelOptions(void *rdopts, Size basesize,
char *itempos = ((char *) rdopts) + elems[j].offset;
char *string_val;
- /*
- * If isset_offset is provided, store whether the reloption is
- * set there.
- */
- if (elems[j].isset_offset > 0)
- {
- char *setpos = ((char *) rdopts) + elems[j].isset_offset;
-
- *(bool *) setpos = options[i].isset;
- }
-
switch (options[i].gen->type)
{
case RELOPT_TYPE_BOOL:
@@ -1798,6 +1871,11 @@ fillRelOptions(void *rdopts, Size basesize,
options[i].values.bool_val :
((relopt_bool *) options[i].gen)->default_val;
break;
+ case RELOPT_TYPE_TERNARY:
+ *(ternary *) itempos = options[i].isset ?
+ options[i].values.ternary_val :
+ ((relopt_ternary *) options[i].gen)->default_val;
+ break;
case RELOPT_TYPE_INT:
*(int *) itempos = options[i].isset ?
options[i].values.int_val :
@@ -1912,8 +1990,8 @@ default_reloptions(Datum reloptions, bool validate, relopt_kind kind)
offsetof(StdRdOptions, parallel_workers)},
{"vacuum_index_cleanup", RELOPT_TYPE_ENUM,
offsetof(StdRdOptions, vacuum_index_cleanup)},
- {"vacuum_truncate", RELOPT_TYPE_BOOL,
- offsetof(StdRdOptions, vacuum_truncate), offsetof(StdRdOptions, vacuum_truncate_set)},
+ {"vacuum_truncate", RELOPT_TYPE_TERNARY,
+ offsetof(StdRdOptions, vacuum_truncate)},
{"vacuum_max_eager_freeze_failure_rate", RELOPT_TYPE_REAL,
offsetof(StdRdOptions, vacuum_max_eager_freeze_failure_rate)}
};
@@ -1993,7 +2071,6 @@ build_local_reloptions(local_relopts *relopts, Datum options, bool validate)
elems[i].optname = opt->option->name;
elems[i].opttype = opt->option->type;
elems[i].offset = opt->offset;
- elems[i].isset_offset = 0; /* not supported for local relopts yet */
i++;
}
diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c
index 733ef40ae7..977babff54 100644
--- a/src/backend/commands/vacuum.c
+++ b/src/backend/commands/vacuum.c
@@ -2221,9 +2221,9 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams params,
{
StdRdOptions *opts = (StdRdOptions *) rel->rd_options;
- if (opts && opts->vacuum_truncate_set)
+ if (opts && opts->vacuum_truncate != TERNARY_UNSET)
{
- if (opts->vacuum_truncate)
+ if (opts->vacuum_truncate == TERNARY_TRUE)
params.truncate = VACOPTVALUE_ENABLED;
else
params.truncate = VACOPTVALUE_DISABLED;
diff --git a/src/include/access/reloptions.h b/src/include/access/reloptions.h
index a604a4702c..a436697658 100644
--- a/src/include/access/reloptions.h
+++ b/src/include/access/reloptions.h
@@ -29,6 +29,7 @@
typedef enum relopt_type
{
RELOPT_TYPE_BOOL,
+ RELOPT_TYPE_TERNARY, /* on, off, unset */
RELOPT_TYPE_INT,
RELOPT_TYPE_REAL,
RELOPT_TYPE_ENUM,
@@ -80,6 +81,7 @@ typedef struct relopt_value
union
{
bool bool_val;
+ ternary ternary_val;
int int_val;
double real_val;
int enum_val;
@@ -94,6 +96,12 @@ typedef struct relopt_bool
bool default_val;
} relopt_bool;
+typedef struct relopt_rernary
+{
+ relopt_gen gen;
+ int default_val;
+} relopt_ternary;
+
typedef struct relopt_int
{
relopt_gen gen;
@@ -152,19 +160,6 @@ typedef struct
const char *optname; /* option's name */
relopt_type opttype; /* option's datatype */
int offset; /* offset of field in result struct */
-
- /*
- * isset_offset is an optional offset of a field in the result struct that
- * stores whether the option is explicitly set for the relation or if it
- * just picked up the default value. In most cases, this can be
- * accomplished by giving the reloption a special out-of-range default
- * value (e.g., some integer reloptions use -2), but this isn't always
- * possible. For example, a Boolean reloption cannot be given an
- * out-of-range default, so we need another way to discover the source of
- * its value. This offset is only used if given a value greater than
- * zero.
- */
- int isset_offset;
} relopt_parse_elt;
/* Local reloption definition */
@@ -195,6 +190,8 @@ typedef struct local_relopts
extern relopt_kind add_reloption_kind(void);
extern void add_bool_reloption(bits32 kinds, const char *name, const char *desc,
bool default_val, LOCKMODE lockmode);
+extern void add_ternary_reloption(bits32 kinds, const char *name,
+ const char *desc, int default_val, LOCKMODE lockmode);
extern void add_int_reloption(bits32 kinds, const char *name, const char *desc,
int default_val, int min_val, int max_val,
LOCKMODE lockmode);
@@ -214,6 +211,9 @@ extern void register_reloptions_validator(local_relopts *relopts,
extern void add_local_bool_reloption(local_relopts *relopts, const char *name,
const char *desc, bool default_val,
int offset);
+extern void add_local_ternary_reloption(local_relopts *relopts,
+ const char *name, const char *desc,
+ ternary default_val, int offset);
extern void add_local_int_reloption(local_relopts *relopts, const char *name,
const char *desc, int default_val,
int min_val, int max_val, int offset);
diff --git a/src/include/c.h b/src/include/c.h
index 39022f8a9d..9c59ccccf8 100644
--- a/src/include/c.h
+++ b/src/include/c.h
@@ -488,6 +488,22 @@ typedef void (*pg_funcptr_t) (void);
#include <stdbool.h>
+/*
+ * ternary
+ * Boolean value with an extrea "unset" option
+ *
+ * Ternary data type is used in relation options that can be "true", "false" or
+ * "unset". Since relation options are used deep inside the PostgreSQL code,
+ * this type is declared globally.
+*/
+
+typedef enum ternary
+{
+ TERNARY_FALSE = 0,
+ TERNARY_TRUE = 1,
+ TERNARY_UNSET = -1
+} ternary;
+
/* ----------------------------------------------------------------
* Section 3: standard system types
diff --git a/src/include/utils/rel.h b/src/include/utils/rel.h
index b552359915..7346d618f9 100644
--- a/src/include/utils/rel.h
+++ b/src/include/utils/rel.h
@@ -346,8 +346,7 @@ typedef struct StdRdOptions
bool user_catalog_table; /* use as an additional catalog relation */
int parallel_workers; /* max number of parallel workers */
StdRdOptIndexCleanup vacuum_index_cleanup; /* controls index vacuuming */
- bool vacuum_truncate; /* enables vacuum to truncate a relation */
- bool vacuum_truncate_set; /* whether vacuum_truncate is set */
+ ternary vacuum_truncate; /* enables vacuum to truncate a relation */
/*
* Fraction of pages in a relation that vacuum can eagerly scan and fail
--
2.39.2
--nextPart4155600.3ZeAukHxDK
Content-Disposition: attachment;
filename="v2a-0003-Add-alias-to-be-used-as-unset-state.patch"
Content-Transfer-Encoding: 7Bit
Content-Type: text/x-patch; charset="unicode-2-0-utf-8";
name="v2a-0003-Add-alias-to-be-used-as-unset-state.patch"
^ permalink raw reply [nested|flat] 868+ messages in thread
* [PATCH v2 2/4] Introduce ternary reloptions
@ 2025-09-04 14:41 Nikolay Shaplov <[email protected]>
0 siblings, 0 replies; 868+ messages in thread
From: Nikolay Shaplov @ 2025-09-04 14:41 UTC (permalink / raw)
Introduce ternary reloption as a replacement for current `vacuum_truncate`
implementation. Remove `vacuum_truncate_set` additional flag and using
`TERNARY_UNSET` value instead.
---
src/backend/access/common/reloptions.c | 129 ++++++++++++++++++++-----
src/backend/commands/vacuum.c | 4 +-
src/include/access/reloptions.h | 26 ++---
src/include/c.h | 16 +++
src/include/utils/rel.h | 3 +-
5 files changed, 135 insertions(+), 43 deletions(-)
diff --git a/src/backend/access/common/reloptions.c b/src/backend/access/common/reloptions.c
index 0af3fea68f..f543f11001 100644
--- a/src/backend/access/common/reloptions.c
+++ b/src/backend/access/common/reloptions.c
@@ -40,9 +40,9 @@
*
* To add an option:
*
- * (i) decide on a type (bool, integer, real, enum, string), name, default
- * value, upper and lower bounds (if applicable); for strings, consider a
- * validation routine.
+ * (i) decide on a type (bool, ternary, integer, real, enum, string), name,
+ * default value, upper and lower bounds (if applicable); for strings,
+ * consider a validation routine.
* (ii) add a record below (or use add_<type>_reloption).
* (iii) add it to the appropriate options struct (perhaps StdRdOptions)
* (iv) add it to the appropriate handling routine (perhaps
@@ -147,15 +147,6 @@ static relopt_bool boolRelOpts[] =
},
false
},
- {
- {
- "vacuum_truncate",
- "Enables vacuum to truncate empty pages at the end of this table",
- RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
- ShareUpdateExclusiveLock
- },
- true
- },
{
{
"deduplicate_items",
@@ -170,6 +161,21 @@ static relopt_bool boolRelOpts[] =
{{NULL}}
};
+static relopt_ternary ternaryRelOpts[] =
+{
+ {
+ {
+ "vacuum_truncate",
+ "Enables vacuum to truncate empty pages at the end of this table",
+ RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
+ ShareUpdateExclusiveLock
+ },
+ TERNARY_UNSET
+ },
+ /* list terminator */
+ {{NULL}}
+};
+
static relopt_int intRelOpts[] =
{
{
@@ -600,6 +606,13 @@ initialize_reloptions(void)
boolRelOpts[i].gen.lockmode));
j++;
}
+ for (i = 0; ternaryRelOpts[i].gen.name; i++)
+ {
+ Assert(DoLockModesConflict(ternaryRelOpts[i].gen.lockmode,
+ ternaryRelOpts[i].gen.lockmode));
+ j++;
+ }
+
for (i = 0; intRelOpts[i].gen.name; i++)
{
Assert(DoLockModesConflict(intRelOpts[i].gen.lockmode,
@@ -640,6 +653,14 @@ initialize_reloptions(void)
j++;
}
+ for (i = 0; ternaryRelOpts[i].gen.name; i++)
+ {
+ relOpts[j] = &ternaryRelOpts[i].gen;
+ relOpts[j]->type = RELOPT_TYPE_TERNARY;
+ relOpts[j]->namelen = strlen(relOpts[j]->name);
+ j++;
+ }
+
for (i = 0; intRelOpts[i].gen.name; i++)
{
relOpts[j] = &intRelOpts[i].gen;
@@ -800,6 +821,9 @@ allocate_reloption(bits32 kinds, int type, const char *name, const char *desc,
case RELOPT_TYPE_BOOL:
size = sizeof(relopt_bool);
break;
+ case RELOPT_TYPE_TERNARY:
+ size = sizeof(relopt_ternary);
+ break;
case RELOPT_TYPE_INT:
size = sizeof(relopt_int);
break;
@@ -883,6 +907,54 @@ add_local_bool_reloption(local_relopts *relopts, const char *name,
add_local_reloption(relopts, (relopt_gen *) newoption, offset);
}
+/*
+ * init_ternary_reloption
+ * Allocate and initialize a new ternary reloption
+ */
+static relopt_ternary *
+init_ternary_reloption(bits32 kinds, const char *name, const char *desc,
+ ternary default_val, LOCKMODE lockmode)
+{
+ relopt_ternary *newoption;
+
+ newoption = (relopt_ternary *) allocate_reloption(kinds,
+ RELOPT_TYPE_TERNARY, name, desc, lockmode);
+ newoption->default_val = default_val;
+
+ return newoption;
+}
+
+/*
+ * add_ternary_reloption
+ * Add a new ternary reloption
+ */
+void
+add_ternary_reloption(bits32 kinds, const char *name, const char *desc,
+ ternary default_val, LOCKMODE lockmode)
+{
+ relopt_ternary *newoption = init_ternary_reloption(kinds, name, desc,
+ default_val, lockmode);
+
+ add_reloption((relopt_gen *) newoption);
+}
+
+/*
+ * add_local_ternary_reloption
+ * Add a new ternary local reloption
+ *
+ * 'offset' is offset of ternary-typed field.
+ */
+void
+add_local_ternary_reloption(local_relopts *relopts, const char *name,
+ const char *desc, ternary default_val,
+ int offset)
+{
+ relopt_ternary *newoption = init_ternary_reloption(RELOPT_KIND_LOCAL,
+ name, desc,
+ default_val, 0);
+
+ add_local_reloption(relopts, (relopt_gen *) newoption, offset);
+}
/*
* init_real_reloption
@@ -1617,6 +1689,18 @@ parse_one_reloption(relopt_value *option, char *text_str, int text_len,
option->gen->name, value)));
}
break;
+ case RELOPT_TYPE_TERNARY:
+ {
+ bool b;
+ parsed = parse_bool(value, &b);
+ option->values.ternary_val = b ? TERNARY_TRUE : TERNARY_FALSE;
+ if (validate && !parsed)
+ ereport(ERROR,
+ (errcode(ERRCODE_INVALID_PARAMETER_VALUE),
+ errmsg("invalid value for ternary option \"%s\": %s",
+ option->gen->name, value)));
+ }
+ break;
case RELOPT_TYPE_INT:
{
relopt_int *optint = (relopt_int *) option->gen;
@@ -1780,17 +1864,6 @@ fillRelOptions(void *rdopts, Size basesize,
char *itempos = ((char *) rdopts) + elems[j].offset;
char *string_val;
- /*
- * If isset_offset is provided, store whether the reloption is
- * set there.
- */
- if (elems[j].isset_offset > 0)
- {
- char *setpos = ((char *) rdopts) + elems[j].isset_offset;
-
- *(bool *) setpos = options[i].isset;
- }
-
switch (options[i].gen->type)
{
case RELOPT_TYPE_BOOL:
@@ -1798,6 +1871,11 @@ fillRelOptions(void *rdopts, Size basesize,
options[i].values.bool_val :
((relopt_bool *) options[i].gen)->default_val;
break;
+ case RELOPT_TYPE_TERNARY:
+ *(ternary *) itempos = options[i].isset ?
+ options[i].values.ternary_val :
+ ((relopt_ternary *) options[i].gen)->default_val;
+ break;
case RELOPT_TYPE_INT:
*(int *) itempos = options[i].isset ?
options[i].values.int_val :
@@ -1912,8 +1990,8 @@ default_reloptions(Datum reloptions, bool validate, relopt_kind kind)
offsetof(StdRdOptions, parallel_workers)},
{"vacuum_index_cleanup", RELOPT_TYPE_ENUM,
offsetof(StdRdOptions, vacuum_index_cleanup)},
- {"vacuum_truncate", RELOPT_TYPE_BOOL,
- offsetof(StdRdOptions, vacuum_truncate), offsetof(StdRdOptions, vacuum_truncate_set)},
+ {"vacuum_truncate", RELOPT_TYPE_TERNARY,
+ offsetof(StdRdOptions, vacuum_truncate)},
{"vacuum_max_eager_freeze_failure_rate", RELOPT_TYPE_REAL,
offsetof(StdRdOptions, vacuum_max_eager_freeze_failure_rate)}
};
@@ -1993,7 +2071,6 @@ build_local_reloptions(local_relopts *relopts, Datum options, bool validate)
elems[i].optname = opt->option->name;
elems[i].opttype = opt->option->type;
elems[i].offset = opt->offset;
- elems[i].isset_offset = 0; /* not supported for local relopts yet */
i++;
}
diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c
index 733ef40ae7..977babff54 100644
--- a/src/backend/commands/vacuum.c
+++ b/src/backend/commands/vacuum.c
@@ -2221,9 +2221,9 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams params,
{
StdRdOptions *opts = (StdRdOptions *) rel->rd_options;
- if (opts && opts->vacuum_truncate_set)
+ if (opts && opts->vacuum_truncate != TERNARY_UNSET)
{
- if (opts->vacuum_truncate)
+ if (opts->vacuum_truncate == TERNARY_TRUE)
params.truncate = VACOPTVALUE_ENABLED;
else
params.truncate = VACOPTVALUE_DISABLED;
diff --git a/src/include/access/reloptions.h b/src/include/access/reloptions.h
index a604a4702c..a436697658 100644
--- a/src/include/access/reloptions.h
+++ b/src/include/access/reloptions.h
@@ -29,6 +29,7 @@
typedef enum relopt_type
{
RELOPT_TYPE_BOOL,
+ RELOPT_TYPE_TERNARY, /* on, off, unset */
RELOPT_TYPE_INT,
RELOPT_TYPE_REAL,
RELOPT_TYPE_ENUM,
@@ -80,6 +81,7 @@ typedef struct relopt_value
union
{
bool bool_val;
+ ternary ternary_val;
int int_val;
double real_val;
int enum_val;
@@ -94,6 +96,12 @@ typedef struct relopt_bool
bool default_val;
} relopt_bool;
+typedef struct relopt_rernary
+{
+ relopt_gen gen;
+ int default_val;
+} relopt_ternary;
+
typedef struct relopt_int
{
relopt_gen gen;
@@ -152,19 +160,6 @@ typedef struct
const char *optname; /* option's name */
relopt_type opttype; /* option's datatype */
int offset; /* offset of field in result struct */
-
- /*
- * isset_offset is an optional offset of a field in the result struct that
- * stores whether the option is explicitly set for the relation or if it
- * just picked up the default value. In most cases, this can be
- * accomplished by giving the reloption a special out-of-range default
- * value (e.g., some integer reloptions use -2), but this isn't always
- * possible. For example, a Boolean reloption cannot be given an
- * out-of-range default, so we need another way to discover the source of
- * its value. This offset is only used if given a value greater than
- * zero.
- */
- int isset_offset;
} relopt_parse_elt;
/* Local reloption definition */
@@ -195,6 +190,8 @@ typedef struct local_relopts
extern relopt_kind add_reloption_kind(void);
extern void add_bool_reloption(bits32 kinds, const char *name, const char *desc,
bool default_val, LOCKMODE lockmode);
+extern void add_ternary_reloption(bits32 kinds, const char *name,
+ const char *desc, int default_val, LOCKMODE lockmode);
extern void add_int_reloption(bits32 kinds, const char *name, const char *desc,
int default_val, int min_val, int max_val,
LOCKMODE lockmode);
@@ -214,6 +211,9 @@ extern void register_reloptions_validator(local_relopts *relopts,
extern void add_local_bool_reloption(local_relopts *relopts, const char *name,
const char *desc, bool default_val,
int offset);
+extern void add_local_ternary_reloption(local_relopts *relopts,
+ const char *name, const char *desc,
+ ternary default_val, int offset);
extern void add_local_int_reloption(local_relopts *relopts, const char *name,
const char *desc, int default_val,
int min_val, int max_val, int offset);
diff --git a/src/include/c.h b/src/include/c.h
index 39022f8a9d..9c59ccccf8 100644
--- a/src/include/c.h
+++ b/src/include/c.h
@@ -488,6 +488,22 @@ typedef void (*pg_funcptr_t) (void);
#include <stdbool.h>
+/*
+ * ternary
+ * Boolean value with an extrea "unset" option
+ *
+ * Ternary data type is used in relation options that can be "true", "false" or
+ * "unset". Since relation options are used deep inside the PostgreSQL code,
+ * this type is declared globally.
+*/
+
+typedef enum ternary
+{
+ TERNARY_FALSE = 0,
+ TERNARY_TRUE = 1,
+ TERNARY_UNSET = -1
+} ternary;
+
/* ----------------------------------------------------------------
* Section 3: standard system types
diff --git a/src/include/utils/rel.h b/src/include/utils/rel.h
index b552359915..7346d618f9 100644
--- a/src/include/utils/rel.h
+++ b/src/include/utils/rel.h
@@ -346,8 +346,7 @@ typedef struct StdRdOptions
bool user_catalog_table; /* use as an additional catalog relation */
int parallel_workers; /* max number of parallel workers */
StdRdOptIndexCleanup vacuum_index_cleanup; /* controls index vacuuming */
- bool vacuum_truncate; /* enables vacuum to truncate a relation */
- bool vacuum_truncate_set; /* whether vacuum_truncate is set */
+ ternary vacuum_truncate; /* enables vacuum to truncate a relation */
/*
* Fraction of pages in a relation that vacuum can eagerly scan and fail
--
2.39.2
--nextPart5129932.5fSG56mABF
Content-Disposition: attachment;
filename="v2-0003-Add-alias-to-be-used-as-unset-state.patch"
Content-Transfer-Encoding: 7Bit
Content-Type: text/x-patch; charset="unicode-2-0-utf-8";
name="v2-0003-Add-alias-to-be-used-as-unset-state.patch"
^ permalink raw reply [nested|flat] 868+ messages in thread
* [PATCH v2 2/4] Introduce ternary reloptions
@ 2025-09-04 14:41 Nikolay Shaplov <[email protected]>
0 siblings, 0 replies; 868+ messages in thread
From: Nikolay Shaplov @ 2025-09-04 14:41 UTC (permalink / raw)
Introduce ternary reloption as a replacement for current `vacuum_truncate`
implementation. Remove `vacuum_truncate_set` additional flag and using
`TERNARY_UNSET` value instead.
---
src/backend/access/common/reloptions.c | 129 ++++++++++++++++++++-----
src/backend/commands/vacuum.c | 4 +-
src/include/access/reloptions.h | 26 ++---
src/include/c.h | 16 +++
src/include/utils/rel.h | 3 +-
5 files changed, 135 insertions(+), 43 deletions(-)
diff --git a/src/backend/access/common/reloptions.c b/src/backend/access/common/reloptions.c
index 0af3fea68f..f543f11001 100644
--- a/src/backend/access/common/reloptions.c
+++ b/src/backend/access/common/reloptions.c
@@ -40,9 +40,9 @@
*
* To add an option:
*
- * (i) decide on a type (bool, integer, real, enum, string), name, default
- * value, upper and lower bounds (if applicable); for strings, consider a
- * validation routine.
+ * (i) decide on a type (bool, ternary, integer, real, enum, string), name,
+ * default value, upper and lower bounds (if applicable); for strings,
+ * consider a validation routine.
* (ii) add a record below (or use add_<type>_reloption).
* (iii) add it to the appropriate options struct (perhaps StdRdOptions)
* (iv) add it to the appropriate handling routine (perhaps
@@ -147,15 +147,6 @@ static relopt_bool boolRelOpts[] =
},
false
},
- {
- {
- "vacuum_truncate",
- "Enables vacuum to truncate empty pages at the end of this table",
- RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
- ShareUpdateExclusiveLock
- },
- true
- },
{
{
"deduplicate_items",
@@ -170,6 +161,21 @@ static relopt_bool boolRelOpts[] =
{{NULL}}
};
+static relopt_ternary ternaryRelOpts[] =
+{
+ {
+ {
+ "vacuum_truncate",
+ "Enables vacuum to truncate empty pages at the end of this table",
+ RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
+ ShareUpdateExclusiveLock
+ },
+ TERNARY_UNSET
+ },
+ /* list terminator */
+ {{NULL}}
+};
+
static relopt_int intRelOpts[] =
{
{
@@ -600,6 +606,13 @@ initialize_reloptions(void)
boolRelOpts[i].gen.lockmode));
j++;
}
+ for (i = 0; ternaryRelOpts[i].gen.name; i++)
+ {
+ Assert(DoLockModesConflict(ternaryRelOpts[i].gen.lockmode,
+ ternaryRelOpts[i].gen.lockmode));
+ j++;
+ }
+
for (i = 0; intRelOpts[i].gen.name; i++)
{
Assert(DoLockModesConflict(intRelOpts[i].gen.lockmode,
@@ -640,6 +653,14 @@ initialize_reloptions(void)
j++;
}
+ for (i = 0; ternaryRelOpts[i].gen.name; i++)
+ {
+ relOpts[j] = &ternaryRelOpts[i].gen;
+ relOpts[j]->type = RELOPT_TYPE_TERNARY;
+ relOpts[j]->namelen = strlen(relOpts[j]->name);
+ j++;
+ }
+
for (i = 0; intRelOpts[i].gen.name; i++)
{
relOpts[j] = &intRelOpts[i].gen;
@@ -800,6 +821,9 @@ allocate_reloption(bits32 kinds, int type, const char *name, const char *desc,
case RELOPT_TYPE_BOOL:
size = sizeof(relopt_bool);
break;
+ case RELOPT_TYPE_TERNARY:
+ size = sizeof(relopt_ternary);
+ break;
case RELOPT_TYPE_INT:
size = sizeof(relopt_int);
break;
@@ -883,6 +907,54 @@ add_local_bool_reloption(local_relopts *relopts, const char *name,
add_local_reloption(relopts, (relopt_gen *) newoption, offset);
}
+/*
+ * init_ternary_reloption
+ * Allocate and initialize a new ternary reloption
+ */
+static relopt_ternary *
+init_ternary_reloption(bits32 kinds, const char *name, const char *desc,
+ ternary default_val, LOCKMODE lockmode)
+{
+ relopt_ternary *newoption;
+
+ newoption = (relopt_ternary *) allocate_reloption(kinds,
+ RELOPT_TYPE_TERNARY, name, desc, lockmode);
+ newoption->default_val = default_val;
+
+ return newoption;
+}
+
+/*
+ * add_ternary_reloption
+ * Add a new ternary reloption
+ */
+void
+add_ternary_reloption(bits32 kinds, const char *name, const char *desc,
+ ternary default_val, LOCKMODE lockmode)
+{
+ relopt_ternary *newoption = init_ternary_reloption(kinds, name, desc,
+ default_val, lockmode);
+
+ add_reloption((relopt_gen *) newoption);
+}
+
+/*
+ * add_local_ternary_reloption
+ * Add a new ternary local reloption
+ *
+ * 'offset' is offset of ternary-typed field.
+ */
+void
+add_local_ternary_reloption(local_relopts *relopts, const char *name,
+ const char *desc, ternary default_val,
+ int offset)
+{
+ relopt_ternary *newoption = init_ternary_reloption(RELOPT_KIND_LOCAL,
+ name, desc,
+ default_val, 0);
+
+ add_local_reloption(relopts, (relopt_gen *) newoption, offset);
+}
/*
* init_real_reloption
@@ -1617,6 +1689,18 @@ parse_one_reloption(relopt_value *option, char *text_str, int text_len,
option->gen->name, value)));
}
break;
+ case RELOPT_TYPE_TERNARY:
+ {
+ bool b;
+ parsed = parse_bool(value, &b);
+ option->values.ternary_val = b ? TERNARY_TRUE : TERNARY_FALSE;
+ if (validate && !parsed)
+ ereport(ERROR,
+ (errcode(ERRCODE_INVALID_PARAMETER_VALUE),
+ errmsg("invalid value for ternary option \"%s\": %s",
+ option->gen->name, value)));
+ }
+ break;
case RELOPT_TYPE_INT:
{
relopt_int *optint = (relopt_int *) option->gen;
@@ -1780,17 +1864,6 @@ fillRelOptions(void *rdopts, Size basesize,
char *itempos = ((char *) rdopts) + elems[j].offset;
char *string_val;
- /*
- * If isset_offset is provided, store whether the reloption is
- * set there.
- */
- if (elems[j].isset_offset > 0)
- {
- char *setpos = ((char *) rdopts) + elems[j].isset_offset;
-
- *(bool *) setpos = options[i].isset;
- }
-
switch (options[i].gen->type)
{
case RELOPT_TYPE_BOOL:
@@ -1798,6 +1871,11 @@ fillRelOptions(void *rdopts, Size basesize,
options[i].values.bool_val :
((relopt_bool *) options[i].gen)->default_val;
break;
+ case RELOPT_TYPE_TERNARY:
+ *(ternary *) itempos = options[i].isset ?
+ options[i].values.ternary_val :
+ ((relopt_ternary *) options[i].gen)->default_val;
+ break;
case RELOPT_TYPE_INT:
*(int *) itempos = options[i].isset ?
options[i].values.int_val :
@@ -1912,8 +1990,8 @@ default_reloptions(Datum reloptions, bool validate, relopt_kind kind)
offsetof(StdRdOptions, parallel_workers)},
{"vacuum_index_cleanup", RELOPT_TYPE_ENUM,
offsetof(StdRdOptions, vacuum_index_cleanup)},
- {"vacuum_truncate", RELOPT_TYPE_BOOL,
- offsetof(StdRdOptions, vacuum_truncate), offsetof(StdRdOptions, vacuum_truncate_set)},
+ {"vacuum_truncate", RELOPT_TYPE_TERNARY,
+ offsetof(StdRdOptions, vacuum_truncate)},
{"vacuum_max_eager_freeze_failure_rate", RELOPT_TYPE_REAL,
offsetof(StdRdOptions, vacuum_max_eager_freeze_failure_rate)}
};
@@ -1993,7 +2071,6 @@ build_local_reloptions(local_relopts *relopts, Datum options, bool validate)
elems[i].optname = opt->option->name;
elems[i].opttype = opt->option->type;
elems[i].offset = opt->offset;
- elems[i].isset_offset = 0; /* not supported for local relopts yet */
i++;
}
diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c
index 733ef40ae7..977babff54 100644
--- a/src/backend/commands/vacuum.c
+++ b/src/backend/commands/vacuum.c
@@ -2221,9 +2221,9 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams params,
{
StdRdOptions *opts = (StdRdOptions *) rel->rd_options;
- if (opts && opts->vacuum_truncate_set)
+ if (opts && opts->vacuum_truncate != TERNARY_UNSET)
{
- if (opts->vacuum_truncate)
+ if (opts->vacuum_truncate == TERNARY_TRUE)
params.truncate = VACOPTVALUE_ENABLED;
else
params.truncate = VACOPTVALUE_DISABLED;
diff --git a/src/include/access/reloptions.h b/src/include/access/reloptions.h
index a604a4702c..a436697658 100644
--- a/src/include/access/reloptions.h
+++ b/src/include/access/reloptions.h
@@ -29,6 +29,7 @@
typedef enum relopt_type
{
RELOPT_TYPE_BOOL,
+ RELOPT_TYPE_TERNARY, /* on, off, unset */
RELOPT_TYPE_INT,
RELOPT_TYPE_REAL,
RELOPT_TYPE_ENUM,
@@ -80,6 +81,7 @@ typedef struct relopt_value
union
{
bool bool_val;
+ ternary ternary_val;
int int_val;
double real_val;
int enum_val;
@@ -94,6 +96,12 @@ typedef struct relopt_bool
bool default_val;
} relopt_bool;
+typedef struct relopt_rernary
+{
+ relopt_gen gen;
+ int default_val;
+} relopt_ternary;
+
typedef struct relopt_int
{
relopt_gen gen;
@@ -152,19 +160,6 @@ typedef struct
const char *optname; /* option's name */
relopt_type opttype; /* option's datatype */
int offset; /* offset of field in result struct */
-
- /*
- * isset_offset is an optional offset of a field in the result struct that
- * stores whether the option is explicitly set for the relation or if it
- * just picked up the default value. In most cases, this can be
- * accomplished by giving the reloption a special out-of-range default
- * value (e.g., some integer reloptions use -2), but this isn't always
- * possible. For example, a Boolean reloption cannot be given an
- * out-of-range default, so we need another way to discover the source of
- * its value. This offset is only used if given a value greater than
- * zero.
- */
- int isset_offset;
} relopt_parse_elt;
/* Local reloption definition */
@@ -195,6 +190,8 @@ typedef struct local_relopts
extern relopt_kind add_reloption_kind(void);
extern void add_bool_reloption(bits32 kinds, const char *name, const char *desc,
bool default_val, LOCKMODE lockmode);
+extern void add_ternary_reloption(bits32 kinds, const char *name,
+ const char *desc, int default_val, LOCKMODE lockmode);
extern void add_int_reloption(bits32 kinds, const char *name, const char *desc,
int default_val, int min_val, int max_val,
LOCKMODE lockmode);
@@ -214,6 +211,9 @@ extern void register_reloptions_validator(local_relopts *relopts,
extern void add_local_bool_reloption(local_relopts *relopts, const char *name,
const char *desc, bool default_val,
int offset);
+extern void add_local_ternary_reloption(local_relopts *relopts,
+ const char *name, const char *desc,
+ ternary default_val, int offset);
extern void add_local_int_reloption(local_relopts *relopts, const char *name,
const char *desc, int default_val,
int min_val, int max_val, int offset);
diff --git a/src/include/c.h b/src/include/c.h
index 39022f8a9d..9c59ccccf8 100644
--- a/src/include/c.h
+++ b/src/include/c.h
@@ -488,6 +488,22 @@ typedef void (*pg_funcptr_t) (void);
#include <stdbool.h>
+/*
+ * ternary
+ * Boolean value with an extrea "unset" option
+ *
+ * Ternary data type is used in relation options that can be "true", "false" or
+ * "unset". Since relation options are used deep inside the PostgreSQL code,
+ * this type is declared globally.
+*/
+
+typedef enum ternary
+{
+ TERNARY_FALSE = 0,
+ TERNARY_TRUE = 1,
+ TERNARY_UNSET = -1
+} ternary;
+
/* ----------------------------------------------------------------
* Section 3: standard system types
diff --git a/src/include/utils/rel.h b/src/include/utils/rel.h
index b552359915..7346d618f9 100644
--- a/src/include/utils/rel.h
+++ b/src/include/utils/rel.h
@@ -346,8 +346,7 @@ typedef struct StdRdOptions
bool user_catalog_table; /* use as an additional catalog relation */
int parallel_workers; /* max number of parallel workers */
StdRdOptIndexCleanup vacuum_index_cleanup; /* controls index vacuuming */
- bool vacuum_truncate; /* enables vacuum to truncate a relation */
- bool vacuum_truncate_set; /* whether vacuum_truncate is set */
+ ternary vacuum_truncate; /* enables vacuum to truncate a relation */
/*
* Fraction of pages in a relation that vacuum can eagerly scan and fail
--
2.39.2
--nextPart5129932.5fSG56mABF
Content-Disposition: attachment;
filename="v2-0003-Add-alias-to-be-used-as-unset-state.patch"
Content-Transfer-Encoding: 7Bit
Content-Type: text/x-patch; charset="unicode-2-0-utf-8";
name="v2-0003-Add-alias-to-be-used-as-unset-state.patch"
^ permalink raw reply [nested|flat] 868+ messages in thread
* [PATCH v2 2/4] Introduce ternary reloptions
@ 2025-09-04 14:41 Nikolay Shaplov <[email protected]>
0 siblings, 0 replies; 868+ messages in thread
From: Nikolay Shaplov @ 2025-09-04 14:41 UTC (permalink / raw)
Introduce ternary reloption as a replacement for current `vacuum_truncate`
implementation. Remove `vacuum_truncate_set` additional flag and using
`TERNARY_UNSET` value instead.
---
src/backend/access/common/reloptions.c | 129 ++++++++++++++++++++-----
src/backend/commands/vacuum.c | 4 +-
src/include/access/reloptions.h | 26 ++---
src/include/c.h | 16 +++
src/include/utils/rel.h | 3 +-
5 files changed, 135 insertions(+), 43 deletions(-)
diff --git a/src/backend/access/common/reloptions.c b/src/backend/access/common/reloptions.c
index 0af3fea68f..f543f11001 100644
--- a/src/backend/access/common/reloptions.c
+++ b/src/backend/access/common/reloptions.c
@@ -40,9 +40,9 @@
*
* To add an option:
*
- * (i) decide on a type (bool, integer, real, enum, string), name, default
- * value, upper and lower bounds (if applicable); for strings, consider a
- * validation routine.
+ * (i) decide on a type (bool, ternary, integer, real, enum, string), name,
+ * default value, upper and lower bounds (if applicable); for strings,
+ * consider a validation routine.
* (ii) add a record below (or use add_<type>_reloption).
* (iii) add it to the appropriate options struct (perhaps StdRdOptions)
* (iv) add it to the appropriate handling routine (perhaps
@@ -147,15 +147,6 @@ static relopt_bool boolRelOpts[] =
},
false
},
- {
- {
- "vacuum_truncate",
- "Enables vacuum to truncate empty pages at the end of this table",
- RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
- ShareUpdateExclusiveLock
- },
- true
- },
{
{
"deduplicate_items",
@@ -170,6 +161,21 @@ static relopt_bool boolRelOpts[] =
{{NULL}}
};
+static relopt_ternary ternaryRelOpts[] =
+{
+ {
+ {
+ "vacuum_truncate",
+ "Enables vacuum to truncate empty pages at the end of this table",
+ RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
+ ShareUpdateExclusiveLock
+ },
+ TERNARY_UNSET
+ },
+ /* list terminator */
+ {{NULL}}
+};
+
static relopt_int intRelOpts[] =
{
{
@@ -600,6 +606,13 @@ initialize_reloptions(void)
boolRelOpts[i].gen.lockmode));
j++;
}
+ for (i = 0; ternaryRelOpts[i].gen.name; i++)
+ {
+ Assert(DoLockModesConflict(ternaryRelOpts[i].gen.lockmode,
+ ternaryRelOpts[i].gen.lockmode));
+ j++;
+ }
+
for (i = 0; intRelOpts[i].gen.name; i++)
{
Assert(DoLockModesConflict(intRelOpts[i].gen.lockmode,
@@ -640,6 +653,14 @@ initialize_reloptions(void)
j++;
}
+ for (i = 0; ternaryRelOpts[i].gen.name; i++)
+ {
+ relOpts[j] = &ternaryRelOpts[i].gen;
+ relOpts[j]->type = RELOPT_TYPE_TERNARY;
+ relOpts[j]->namelen = strlen(relOpts[j]->name);
+ j++;
+ }
+
for (i = 0; intRelOpts[i].gen.name; i++)
{
relOpts[j] = &intRelOpts[i].gen;
@@ -800,6 +821,9 @@ allocate_reloption(bits32 kinds, int type, const char *name, const char *desc,
case RELOPT_TYPE_BOOL:
size = sizeof(relopt_bool);
break;
+ case RELOPT_TYPE_TERNARY:
+ size = sizeof(relopt_ternary);
+ break;
case RELOPT_TYPE_INT:
size = sizeof(relopt_int);
break;
@@ -883,6 +907,54 @@ add_local_bool_reloption(local_relopts *relopts, const char *name,
add_local_reloption(relopts, (relopt_gen *) newoption, offset);
}
+/*
+ * init_ternary_reloption
+ * Allocate and initialize a new ternary reloption
+ */
+static relopt_ternary *
+init_ternary_reloption(bits32 kinds, const char *name, const char *desc,
+ ternary default_val, LOCKMODE lockmode)
+{
+ relopt_ternary *newoption;
+
+ newoption = (relopt_ternary *) allocate_reloption(kinds,
+ RELOPT_TYPE_TERNARY, name, desc, lockmode);
+ newoption->default_val = default_val;
+
+ return newoption;
+}
+
+/*
+ * add_ternary_reloption
+ * Add a new ternary reloption
+ */
+void
+add_ternary_reloption(bits32 kinds, const char *name, const char *desc,
+ ternary default_val, LOCKMODE lockmode)
+{
+ relopt_ternary *newoption = init_ternary_reloption(kinds, name, desc,
+ default_val, lockmode);
+
+ add_reloption((relopt_gen *) newoption);
+}
+
+/*
+ * add_local_ternary_reloption
+ * Add a new ternary local reloption
+ *
+ * 'offset' is offset of ternary-typed field.
+ */
+void
+add_local_ternary_reloption(local_relopts *relopts, const char *name,
+ const char *desc, ternary default_val,
+ int offset)
+{
+ relopt_ternary *newoption = init_ternary_reloption(RELOPT_KIND_LOCAL,
+ name, desc,
+ default_val, 0);
+
+ add_local_reloption(relopts, (relopt_gen *) newoption, offset);
+}
/*
* init_real_reloption
@@ -1617,6 +1689,18 @@ parse_one_reloption(relopt_value *option, char *text_str, int text_len,
option->gen->name, value)));
}
break;
+ case RELOPT_TYPE_TERNARY:
+ {
+ bool b;
+ parsed = parse_bool(value, &b);
+ option->values.ternary_val = b ? TERNARY_TRUE : TERNARY_FALSE;
+ if (validate && !parsed)
+ ereport(ERROR,
+ (errcode(ERRCODE_INVALID_PARAMETER_VALUE),
+ errmsg("invalid value for ternary option \"%s\": %s",
+ option->gen->name, value)));
+ }
+ break;
case RELOPT_TYPE_INT:
{
relopt_int *optint = (relopt_int *) option->gen;
@@ -1780,17 +1864,6 @@ fillRelOptions(void *rdopts, Size basesize,
char *itempos = ((char *) rdopts) + elems[j].offset;
char *string_val;
- /*
- * If isset_offset is provided, store whether the reloption is
- * set there.
- */
- if (elems[j].isset_offset > 0)
- {
- char *setpos = ((char *) rdopts) + elems[j].isset_offset;
-
- *(bool *) setpos = options[i].isset;
- }
-
switch (options[i].gen->type)
{
case RELOPT_TYPE_BOOL:
@@ -1798,6 +1871,11 @@ fillRelOptions(void *rdopts, Size basesize,
options[i].values.bool_val :
((relopt_bool *) options[i].gen)->default_val;
break;
+ case RELOPT_TYPE_TERNARY:
+ *(ternary *) itempos = options[i].isset ?
+ options[i].values.ternary_val :
+ ((relopt_ternary *) options[i].gen)->default_val;
+ break;
case RELOPT_TYPE_INT:
*(int *) itempos = options[i].isset ?
options[i].values.int_val :
@@ -1912,8 +1990,8 @@ default_reloptions(Datum reloptions, bool validate, relopt_kind kind)
offsetof(StdRdOptions, parallel_workers)},
{"vacuum_index_cleanup", RELOPT_TYPE_ENUM,
offsetof(StdRdOptions, vacuum_index_cleanup)},
- {"vacuum_truncate", RELOPT_TYPE_BOOL,
- offsetof(StdRdOptions, vacuum_truncate), offsetof(StdRdOptions, vacuum_truncate_set)},
+ {"vacuum_truncate", RELOPT_TYPE_TERNARY,
+ offsetof(StdRdOptions, vacuum_truncate)},
{"vacuum_max_eager_freeze_failure_rate", RELOPT_TYPE_REAL,
offsetof(StdRdOptions, vacuum_max_eager_freeze_failure_rate)}
};
@@ -1993,7 +2071,6 @@ build_local_reloptions(local_relopts *relopts, Datum options, bool validate)
elems[i].optname = opt->option->name;
elems[i].opttype = opt->option->type;
elems[i].offset = opt->offset;
- elems[i].isset_offset = 0; /* not supported for local relopts yet */
i++;
}
diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c
index 733ef40ae7..977babff54 100644
--- a/src/backend/commands/vacuum.c
+++ b/src/backend/commands/vacuum.c
@@ -2221,9 +2221,9 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams params,
{
StdRdOptions *opts = (StdRdOptions *) rel->rd_options;
- if (opts && opts->vacuum_truncate_set)
+ if (opts && opts->vacuum_truncate != TERNARY_UNSET)
{
- if (opts->vacuum_truncate)
+ if (opts->vacuum_truncate == TERNARY_TRUE)
params.truncate = VACOPTVALUE_ENABLED;
else
params.truncate = VACOPTVALUE_DISABLED;
diff --git a/src/include/access/reloptions.h b/src/include/access/reloptions.h
index a604a4702c..a436697658 100644
--- a/src/include/access/reloptions.h
+++ b/src/include/access/reloptions.h
@@ -29,6 +29,7 @@
typedef enum relopt_type
{
RELOPT_TYPE_BOOL,
+ RELOPT_TYPE_TERNARY, /* on, off, unset */
RELOPT_TYPE_INT,
RELOPT_TYPE_REAL,
RELOPT_TYPE_ENUM,
@@ -80,6 +81,7 @@ typedef struct relopt_value
union
{
bool bool_val;
+ ternary ternary_val;
int int_val;
double real_val;
int enum_val;
@@ -94,6 +96,12 @@ typedef struct relopt_bool
bool default_val;
} relopt_bool;
+typedef struct relopt_rernary
+{
+ relopt_gen gen;
+ int default_val;
+} relopt_ternary;
+
typedef struct relopt_int
{
relopt_gen gen;
@@ -152,19 +160,6 @@ typedef struct
const char *optname; /* option's name */
relopt_type opttype; /* option's datatype */
int offset; /* offset of field in result struct */
-
- /*
- * isset_offset is an optional offset of a field in the result struct that
- * stores whether the option is explicitly set for the relation or if it
- * just picked up the default value. In most cases, this can be
- * accomplished by giving the reloption a special out-of-range default
- * value (e.g., some integer reloptions use -2), but this isn't always
- * possible. For example, a Boolean reloption cannot be given an
- * out-of-range default, so we need another way to discover the source of
- * its value. This offset is only used if given a value greater than
- * zero.
- */
- int isset_offset;
} relopt_parse_elt;
/* Local reloption definition */
@@ -195,6 +190,8 @@ typedef struct local_relopts
extern relopt_kind add_reloption_kind(void);
extern void add_bool_reloption(bits32 kinds, const char *name, const char *desc,
bool default_val, LOCKMODE lockmode);
+extern void add_ternary_reloption(bits32 kinds, const char *name,
+ const char *desc, int default_val, LOCKMODE lockmode);
extern void add_int_reloption(bits32 kinds, const char *name, const char *desc,
int default_val, int min_val, int max_val,
LOCKMODE lockmode);
@@ -214,6 +211,9 @@ extern void register_reloptions_validator(local_relopts *relopts,
extern void add_local_bool_reloption(local_relopts *relopts, const char *name,
const char *desc, bool default_val,
int offset);
+extern void add_local_ternary_reloption(local_relopts *relopts,
+ const char *name, const char *desc,
+ ternary default_val, int offset);
extern void add_local_int_reloption(local_relopts *relopts, const char *name,
const char *desc, int default_val,
int min_val, int max_val, int offset);
diff --git a/src/include/c.h b/src/include/c.h
index 39022f8a9d..9c59ccccf8 100644
--- a/src/include/c.h
+++ b/src/include/c.h
@@ -488,6 +488,22 @@ typedef void (*pg_funcptr_t) (void);
#include <stdbool.h>
+/*
+ * ternary
+ * Boolean value with an extrea "unset" option
+ *
+ * Ternary data type is used in relation options that can be "true", "false" or
+ * "unset". Since relation options are used deep inside the PostgreSQL code,
+ * this type is declared globally.
+*/
+
+typedef enum ternary
+{
+ TERNARY_FALSE = 0,
+ TERNARY_TRUE = 1,
+ TERNARY_UNSET = -1
+} ternary;
+
/* ----------------------------------------------------------------
* Section 3: standard system types
diff --git a/src/include/utils/rel.h b/src/include/utils/rel.h
index b552359915..7346d618f9 100644
--- a/src/include/utils/rel.h
+++ b/src/include/utils/rel.h
@@ -346,8 +346,7 @@ typedef struct StdRdOptions
bool user_catalog_table; /* use as an additional catalog relation */
int parallel_workers; /* max number of parallel workers */
StdRdOptIndexCleanup vacuum_index_cleanup; /* controls index vacuuming */
- bool vacuum_truncate; /* enables vacuum to truncate a relation */
- bool vacuum_truncate_set; /* whether vacuum_truncate is set */
+ ternary vacuum_truncate; /* enables vacuum to truncate a relation */
/*
* Fraction of pages in a relation that vacuum can eagerly scan and fail
--
2.39.2
--nextPart5129932.5fSG56mABF
Content-Disposition: attachment;
filename="v2-0003-Add-alias-to-be-used-as-unset-state.patch"
Content-Transfer-Encoding: 7Bit
Content-Type: text/x-patch; charset="unicode-2-0-utf-8";
name="v2-0003-Add-alias-to-be-used-as-unset-state.patch"
^ permalink raw reply [nested|flat] 868+ messages in thread
* [PATCH v2a 2/4] Introduce ternary reloptions
@ 2025-09-04 14:41 Nikolay Shaplov <[email protected]>
0 siblings, 0 replies; 868+ messages in thread
From: Nikolay Shaplov @ 2025-09-04 14:41 UTC (permalink / raw)
Introduce ternary reloption as a replacement for current `vacuum_truncate`
implementation. Remove `vacuum_truncate_set` additional flag and using
`TERNARY_UNSET` value instead.
---
src/backend/access/common/reloptions.c | 129 ++++++++++++++++++++-----
src/backend/commands/vacuum.c | 4 +-
src/include/access/reloptions.h | 26 ++---
src/include/c.h | 16 +++
src/include/utils/rel.h | 3 +-
5 files changed, 135 insertions(+), 43 deletions(-)
diff --git a/src/backend/access/common/reloptions.c b/src/backend/access/common/reloptions.c
index 0af3fea68f..f543f11001 100644
--- a/src/backend/access/common/reloptions.c
+++ b/src/backend/access/common/reloptions.c
@@ -40,9 +40,9 @@
*
* To add an option:
*
- * (i) decide on a type (bool, integer, real, enum, string), name, default
- * value, upper and lower bounds (if applicable); for strings, consider a
- * validation routine.
+ * (i) decide on a type (bool, ternary, integer, real, enum, string), name,
+ * default value, upper and lower bounds (if applicable); for strings,
+ * consider a validation routine.
* (ii) add a record below (or use add_<type>_reloption).
* (iii) add it to the appropriate options struct (perhaps StdRdOptions)
* (iv) add it to the appropriate handling routine (perhaps
@@ -147,15 +147,6 @@ static relopt_bool boolRelOpts[] =
},
false
},
- {
- {
- "vacuum_truncate",
- "Enables vacuum to truncate empty pages at the end of this table",
- RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
- ShareUpdateExclusiveLock
- },
- true
- },
{
{
"deduplicate_items",
@@ -170,6 +161,21 @@ static relopt_bool boolRelOpts[] =
{{NULL}}
};
+static relopt_ternary ternaryRelOpts[] =
+{
+ {
+ {
+ "vacuum_truncate",
+ "Enables vacuum to truncate empty pages at the end of this table",
+ RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
+ ShareUpdateExclusiveLock
+ },
+ TERNARY_UNSET
+ },
+ /* list terminator */
+ {{NULL}}
+};
+
static relopt_int intRelOpts[] =
{
{
@@ -600,6 +606,13 @@ initialize_reloptions(void)
boolRelOpts[i].gen.lockmode));
j++;
}
+ for (i = 0; ternaryRelOpts[i].gen.name; i++)
+ {
+ Assert(DoLockModesConflict(ternaryRelOpts[i].gen.lockmode,
+ ternaryRelOpts[i].gen.lockmode));
+ j++;
+ }
+
for (i = 0; intRelOpts[i].gen.name; i++)
{
Assert(DoLockModesConflict(intRelOpts[i].gen.lockmode,
@@ -640,6 +653,14 @@ initialize_reloptions(void)
j++;
}
+ for (i = 0; ternaryRelOpts[i].gen.name; i++)
+ {
+ relOpts[j] = &ternaryRelOpts[i].gen;
+ relOpts[j]->type = RELOPT_TYPE_TERNARY;
+ relOpts[j]->namelen = strlen(relOpts[j]->name);
+ j++;
+ }
+
for (i = 0; intRelOpts[i].gen.name; i++)
{
relOpts[j] = &intRelOpts[i].gen;
@@ -800,6 +821,9 @@ allocate_reloption(bits32 kinds, int type, const char *name, const char *desc,
case RELOPT_TYPE_BOOL:
size = sizeof(relopt_bool);
break;
+ case RELOPT_TYPE_TERNARY:
+ size = sizeof(relopt_ternary);
+ break;
case RELOPT_TYPE_INT:
size = sizeof(relopt_int);
break;
@@ -883,6 +907,54 @@ add_local_bool_reloption(local_relopts *relopts, const char *name,
add_local_reloption(relopts, (relopt_gen *) newoption, offset);
}
+/*
+ * init_ternary_reloption
+ * Allocate and initialize a new ternary reloption
+ */
+static relopt_ternary *
+init_ternary_reloption(bits32 kinds, const char *name, const char *desc,
+ ternary default_val, LOCKMODE lockmode)
+{
+ relopt_ternary *newoption;
+
+ newoption = (relopt_ternary *) allocate_reloption(kinds,
+ RELOPT_TYPE_TERNARY, name, desc, lockmode);
+ newoption->default_val = default_val;
+
+ return newoption;
+}
+
+/*
+ * add_ternary_reloption
+ * Add a new ternary reloption
+ */
+void
+add_ternary_reloption(bits32 kinds, const char *name, const char *desc,
+ ternary default_val, LOCKMODE lockmode)
+{
+ relopt_ternary *newoption = init_ternary_reloption(kinds, name, desc,
+ default_val, lockmode);
+
+ add_reloption((relopt_gen *) newoption);
+}
+
+/*
+ * add_local_ternary_reloption
+ * Add a new ternary local reloption
+ *
+ * 'offset' is offset of ternary-typed field.
+ */
+void
+add_local_ternary_reloption(local_relopts *relopts, const char *name,
+ const char *desc, ternary default_val,
+ int offset)
+{
+ relopt_ternary *newoption = init_ternary_reloption(RELOPT_KIND_LOCAL,
+ name, desc,
+ default_val, 0);
+
+ add_local_reloption(relopts, (relopt_gen *) newoption, offset);
+}
/*
* init_real_reloption
@@ -1617,6 +1689,18 @@ parse_one_reloption(relopt_value *option, char *text_str, int text_len,
option->gen->name, value)));
}
break;
+ case RELOPT_TYPE_TERNARY:
+ {
+ bool b;
+ parsed = parse_bool(value, &b);
+ option->values.ternary_val = b ? TERNARY_TRUE : TERNARY_FALSE;
+ if (validate && !parsed)
+ ereport(ERROR,
+ (errcode(ERRCODE_INVALID_PARAMETER_VALUE),
+ errmsg("invalid value for ternary option \"%s\": %s",
+ option->gen->name, value)));
+ }
+ break;
case RELOPT_TYPE_INT:
{
relopt_int *optint = (relopt_int *) option->gen;
@@ -1780,17 +1864,6 @@ fillRelOptions(void *rdopts, Size basesize,
char *itempos = ((char *) rdopts) + elems[j].offset;
char *string_val;
- /*
- * If isset_offset is provided, store whether the reloption is
- * set there.
- */
- if (elems[j].isset_offset > 0)
- {
- char *setpos = ((char *) rdopts) + elems[j].isset_offset;
-
- *(bool *) setpos = options[i].isset;
- }
-
switch (options[i].gen->type)
{
case RELOPT_TYPE_BOOL:
@@ -1798,6 +1871,11 @@ fillRelOptions(void *rdopts, Size basesize,
options[i].values.bool_val :
((relopt_bool *) options[i].gen)->default_val;
break;
+ case RELOPT_TYPE_TERNARY:
+ *(ternary *) itempos = options[i].isset ?
+ options[i].values.ternary_val :
+ ((relopt_ternary *) options[i].gen)->default_val;
+ break;
case RELOPT_TYPE_INT:
*(int *) itempos = options[i].isset ?
options[i].values.int_val :
@@ -1912,8 +1990,8 @@ default_reloptions(Datum reloptions, bool validate, relopt_kind kind)
offsetof(StdRdOptions, parallel_workers)},
{"vacuum_index_cleanup", RELOPT_TYPE_ENUM,
offsetof(StdRdOptions, vacuum_index_cleanup)},
- {"vacuum_truncate", RELOPT_TYPE_BOOL,
- offsetof(StdRdOptions, vacuum_truncate), offsetof(StdRdOptions, vacuum_truncate_set)},
+ {"vacuum_truncate", RELOPT_TYPE_TERNARY,
+ offsetof(StdRdOptions, vacuum_truncate)},
{"vacuum_max_eager_freeze_failure_rate", RELOPT_TYPE_REAL,
offsetof(StdRdOptions, vacuum_max_eager_freeze_failure_rate)}
};
@@ -1993,7 +2071,6 @@ build_local_reloptions(local_relopts *relopts, Datum options, bool validate)
elems[i].optname = opt->option->name;
elems[i].opttype = opt->option->type;
elems[i].offset = opt->offset;
- elems[i].isset_offset = 0; /* not supported for local relopts yet */
i++;
}
diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c
index 733ef40ae7..977babff54 100644
--- a/src/backend/commands/vacuum.c
+++ b/src/backend/commands/vacuum.c
@@ -2221,9 +2221,9 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams params,
{
StdRdOptions *opts = (StdRdOptions *) rel->rd_options;
- if (opts && opts->vacuum_truncate_set)
+ if (opts && opts->vacuum_truncate != TERNARY_UNSET)
{
- if (opts->vacuum_truncate)
+ if (opts->vacuum_truncate == TERNARY_TRUE)
params.truncate = VACOPTVALUE_ENABLED;
else
params.truncate = VACOPTVALUE_DISABLED;
diff --git a/src/include/access/reloptions.h b/src/include/access/reloptions.h
index a604a4702c..a436697658 100644
--- a/src/include/access/reloptions.h
+++ b/src/include/access/reloptions.h
@@ -29,6 +29,7 @@
typedef enum relopt_type
{
RELOPT_TYPE_BOOL,
+ RELOPT_TYPE_TERNARY, /* on, off, unset */
RELOPT_TYPE_INT,
RELOPT_TYPE_REAL,
RELOPT_TYPE_ENUM,
@@ -80,6 +81,7 @@ typedef struct relopt_value
union
{
bool bool_val;
+ ternary ternary_val;
int int_val;
double real_val;
int enum_val;
@@ -94,6 +96,12 @@ typedef struct relopt_bool
bool default_val;
} relopt_bool;
+typedef struct relopt_rernary
+{
+ relopt_gen gen;
+ int default_val;
+} relopt_ternary;
+
typedef struct relopt_int
{
relopt_gen gen;
@@ -152,19 +160,6 @@ typedef struct
const char *optname; /* option's name */
relopt_type opttype; /* option's datatype */
int offset; /* offset of field in result struct */
-
- /*
- * isset_offset is an optional offset of a field in the result struct that
- * stores whether the option is explicitly set for the relation or if it
- * just picked up the default value. In most cases, this can be
- * accomplished by giving the reloption a special out-of-range default
- * value (e.g., some integer reloptions use -2), but this isn't always
- * possible. For example, a Boolean reloption cannot be given an
- * out-of-range default, so we need another way to discover the source of
- * its value. This offset is only used if given a value greater than
- * zero.
- */
- int isset_offset;
} relopt_parse_elt;
/* Local reloption definition */
@@ -195,6 +190,8 @@ typedef struct local_relopts
extern relopt_kind add_reloption_kind(void);
extern void add_bool_reloption(bits32 kinds, const char *name, const char *desc,
bool default_val, LOCKMODE lockmode);
+extern void add_ternary_reloption(bits32 kinds, const char *name,
+ const char *desc, int default_val, LOCKMODE lockmode);
extern void add_int_reloption(bits32 kinds, const char *name, const char *desc,
int default_val, int min_val, int max_val,
LOCKMODE lockmode);
@@ -214,6 +211,9 @@ extern void register_reloptions_validator(local_relopts *relopts,
extern void add_local_bool_reloption(local_relopts *relopts, const char *name,
const char *desc, bool default_val,
int offset);
+extern void add_local_ternary_reloption(local_relopts *relopts,
+ const char *name, const char *desc,
+ ternary default_val, int offset);
extern void add_local_int_reloption(local_relopts *relopts, const char *name,
const char *desc, int default_val,
int min_val, int max_val, int offset);
diff --git a/src/include/c.h b/src/include/c.h
index 39022f8a9d..9c59ccccf8 100644
--- a/src/include/c.h
+++ b/src/include/c.h
@@ -488,6 +488,22 @@ typedef void (*pg_funcptr_t) (void);
#include <stdbool.h>
+/*
+ * ternary
+ * Boolean value with an extrea "unset" option
+ *
+ * Ternary data type is used in relation options that can be "true", "false" or
+ * "unset". Since relation options are used deep inside the PostgreSQL code,
+ * this type is declared globally.
+*/
+
+typedef enum ternary
+{
+ TERNARY_FALSE = 0,
+ TERNARY_TRUE = 1,
+ TERNARY_UNSET = -1
+} ternary;
+
/* ----------------------------------------------------------------
* Section 3: standard system types
diff --git a/src/include/utils/rel.h b/src/include/utils/rel.h
index b552359915..7346d618f9 100644
--- a/src/include/utils/rel.h
+++ b/src/include/utils/rel.h
@@ -346,8 +346,7 @@ typedef struct StdRdOptions
bool user_catalog_table; /* use as an additional catalog relation */
int parallel_workers; /* max number of parallel workers */
StdRdOptIndexCleanup vacuum_index_cleanup; /* controls index vacuuming */
- bool vacuum_truncate; /* enables vacuum to truncate a relation */
- bool vacuum_truncate_set; /* whether vacuum_truncate is set */
+ ternary vacuum_truncate; /* enables vacuum to truncate a relation */
/*
* Fraction of pages in a relation that vacuum can eagerly scan and fail
--
2.39.2
--nextPart4155600.3ZeAukHxDK
Content-Disposition: attachment;
filename="v2a-0003-Add-alias-to-be-used-as-unset-state.patch"
Content-Transfer-Encoding: 7Bit
Content-Type: text/x-patch; charset="unicode-2-0-utf-8";
name="v2a-0003-Add-alias-to-be-used-as-unset-state.patch"
^ permalink raw reply [nested|flat] 868+ messages in thread
* [PATCH v2 2/4] Introduce ternary reloptions
@ 2025-09-04 14:41 Nikolay Shaplov <[email protected]>
0 siblings, 0 replies; 868+ messages in thread
From: Nikolay Shaplov @ 2025-09-04 14:41 UTC (permalink / raw)
Introduce ternary reloption as a replacement for current `vacuum_truncate`
implementation. Remove `vacuum_truncate_set` additional flag and using
`TERNARY_UNSET` value instead.
---
src/backend/access/common/reloptions.c | 129 ++++++++++++++++++++-----
src/backend/commands/vacuum.c | 4 +-
src/include/access/reloptions.h | 26 ++---
src/include/c.h | 16 +++
src/include/utils/rel.h | 3 +-
5 files changed, 135 insertions(+), 43 deletions(-)
diff --git a/src/backend/access/common/reloptions.c b/src/backend/access/common/reloptions.c
index 0af3fea68f..f543f11001 100644
--- a/src/backend/access/common/reloptions.c
+++ b/src/backend/access/common/reloptions.c
@@ -40,9 +40,9 @@
*
* To add an option:
*
- * (i) decide on a type (bool, integer, real, enum, string), name, default
- * value, upper and lower bounds (if applicable); for strings, consider a
- * validation routine.
+ * (i) decide on a type (bool, ternary, integer, real, enum, string), name,
+ * default value, upper and lower bounds (if applicable); for strings,
+ * consider a validation routine.
* (ii) add a record below (or use add_<type>_reloption).
* (iii) add it to the appropriate options struct (perhaps StdRdOptions)
* (iv) add it to the appropriate handling routine (perhaps
@@ -147,15 +147,6 @@ static relopt_bool boolRelOpts[] =
},
false
},
- {
- {
- "vacuum_truncate",
- "Enables vacuum to truncate empty pages at the end of this table",
- RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
- ShareUpdateExclusiveLock
- },
- true
- },
{
{
"deduplicate_items",
@@ -170,6 +161,21 @@ static relopt_bool boolRelOpts[] =
{{NULL}}
};
+static relopt_ternary ternaryRelOpts[] =
+{
+ {
+ {
+ "vacuum_truncate",
+ "Enables vacuum to truncate empty pages at the end of this table",
+ RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
+ ShareUpdateExclusiveLock
+ },
+ TERNARY_UNSET
+ },
+ /* list terminator */
+ {{NULL}}
+};
+
static relopt_int intRelOpts[] =
{
{
@@ -600,6 +606,13 @@ initialize_reloptions(void)
boolRelOpts[i].gen.lockmode));
j++;
}
+ for (i = 0; ternaryRelOpts[i].gen.name; i++)
+ {
+ Assert(DoLockModesConflict(ternaryRelOpts[i].gen.lockmode,
+ ternaryRelOpts[i].gen.lockmode));
+ j++;
+ }
+
for (i = 0; intRelOpts[i].gen.name; i++)
{
Assert(DoLockModesConflict(intRelOpts[i].gen.lockmode,
@@ -640,6 +653,14 @@ initialize_reloptions(void)
j++;
}
+ for (i = 0; ternaryRelOpts[i].gen.name; i++)
+ {
+ relOpts[j] = &ternaryRelOpts[i].gen;
+ relOpts[j]->type = RELOPT_TYPE_TERNARY;
+ relOpts[j]->namelen = strlen(relOpts[j]->name);
+ j++;
+ }
+
for (i = 0; intRelOpts[i].gen.name; i++)
{
relOpts[j] = &intRelOpts[i].gen;
@@ -800,6 +821,9 @@ allocate_reloption(bits32 kinds, int type, const char *name, const char *desc,
case RELOPT_TYPE_BOOL:
size = sizeof(relopt_bool);
break;
+ case RELOPT_TYPE_TERNARY:
+ size = sizeof(relopt_ternary);
+ break;
case RELOPT_TYPE_INT:
size = sizeof(relopt_int);
break;
@@ -883,6 +907,54 @@ add_local_bool_reloption(local_relopts *relopts, const char *name,
add_local_reloption(relopts, (relopt_gen *) newoption, offset);
}
+/*
+ * init_ternary_reloption
+ * Allocate and initialize a new ternary reloption
+ */
+static relopt_ternary *
+init_ternary_reloption(bits32 kinds, const char *name, const char *desc,
+ ternary default_val, LOCKMODE lockmode)
+{
+ relopt_ternary *newoption;
+
+ newoption = (relopt_ternary *) allocate_reloption(kinds,
+ RELOPT_TYPE_TERNARY, name, desc, lockmode);
+ newoption->default_val = default_val;
+
+ return newoption;
+}
+
+/*
+ * add_ternary_reloption
+ * Add a new ternary reloption
+ */
+void
+add_ternary_reloption(bits32 kinds, const char *name, const char *desc,
+ ternary default_val, LOCKMODE lockmode)
+{
+ relopt_ternary *newoption = init_ternary_reloption(kinds, name, desc,
+ default_val, lockmode);
+
+ add_reloption((relopt_gen *) newoption);
+}
+
+/*
+ * add_local_ternary_reloption
+ * Add a new ternary local reloption
+ *
+ * 'offset' is offset of ternary-typed field.
+ */
+void
+add_local_ternary_reloption(local_relopts *relopts, const char *name,
+ const char *desc, ternary default_val,
+ int offset)
+{
+ relopt_ternary *newoption = init_ternary_reloption(RELOPT_KIND_LOCAL,
+ name, desc,
+ default_val, 0);
+
+ add_local_reloption(relopts, (relopt_gen *) newoption, offset);
+}
/*
* init_real_reloption
@@ -1617,6 +1689,18 @@ parse_one_reloption(relopt_value *option, char *text_str, int text_len,
option->gen->name, value)));
}
break;
+ case RELOPT_TYPE_TERNARY:
+ {
+ bool b;
+ parsed = parse_bool(value, &b);
+ option->values.ternary_val = b ? TERNARY_TRUE : TERNARY_FALSE;
+ if (validate && !parsed)
+ ereport(ERROR,
+ (errcode(ERRCODE_INVALID_PARAMETER_VALUE),
+ errmsg("invalid value for ternary option \"%s\": %s",
+ option->gen->name, value)));
+ }
+ break;
case RELOPT_TYPE_INT:
{
relopt_int *optint = (relopt_int *) option->gen;
@@ -1780,17 +1864,6 @@ fillRelOptions(void *rdopts, Size basesize,
char *itempos = ((char *) rdopts) + elems[j].offset;
char *string_val;
- /*
- * If isset_offset is provided, store whether the reloption is
- * set there.
- */
- if (elems[j].isset_offset > 0)
- {
- char *setpos = ((char *) rdopts) + elems[j].isset_offset;
-
- *(bool *) setpos = options[i].isset;
- }
-
switch (options[i].gen->type)
{
case RELOPT_TYPE_BOOL:
@@ -1798,6 +1871,11 @@ fillRelOptions(void *rdopts, Size basesize,
options[i].values.bool_val :
((relopt_bool *) options[i].gen)->default_val;
break;
+ case RELOPT_TYPE_TERNARY:
+ *(ternary *) itempos = options[i].isset ?
+ options[i].values.ternary_val :
+ ((relopt_ternary *) options[i].gen)->default_val;
+ break;
case RELOPT_TYPE_INT:
*(int *) itempos = options[i].isset ?
options[i].values.int_val :
@@ -1912,8 +1990,8 @@ default_reloptions(Datum reloptions, bool validate, relopt_kind kind)
offsetof(StdRdOptions, parallel_workers)},
{"vacuum_index_cleanup", RELOPT_TYPE_ENUM,
offsetof(StdRdOptions, vacuum_index_cleanup)},
- {"vacuum_truncate", RELOPT_TYPE_BOOL,
- offsetof(StdRdOptions, vacuum_truncate), offsetof(StdRdOptions, vacuum_truncate_set)},
+ {"vacuum_truncate", RELOPT_TYPE_TERNARY,
+ offsetof(StdRdOptions, vacuum_truncate)},
{"vacuum_max_eager_freeze_failure_rate", RELOPT_TYPE_REAL,
offsetof(StdRdOptions, vacuum_max_eager_freeze_failure_rate)}
};
@@ -1993,7 +2071,6 @@ build_local_reloptions(local_relopts *relopts, Datum options, bool validate)
elems[i].optname = opt->option->name;
elems[i].opttype = opt->option->type;
elems[i].offset = opt->offset;
- elems[i].isset_offset = 0; /* not supported for local relopts yet */
i++;
}
diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c
index 733ef40ae7..977babff54 100644
--- a/src/backend/commands/vacuum.c
+++ b/src/backend/commands/vacuum.c
@@ -2221,9 +2221,9 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams params,
{
StdRdOptions *opts = (StdRdOptions *) rel->rd_options;
- if (opts && opts->vacuum_truncate_set)
+ if (opts && opts->vacuum_truncate != TERNARY_UNSET)
{
- if (opts->vacuum_truncate)
+ if (opts->vacuum_truncate == TERNARY_TRUE)
params.truncate = VACOPTVALUE_ENABLED;
else
params.truncate = VACOPTVALUE_DISABLED;
diff --git a/src/include/access/reloptions.h b/src/include/access/reloptions.h
index a604a4702c..a436697658 100644
--- a/src/include/access/reloptions.h
+++ b/src/include/access/reloptions.h
@@ -29,6 +29,7 @@
typedef enum relopt_type
{
RELOPT_TYPE_BOOL,
+ RELOPT_TYPE_TERNARY, /* on, off, unset */
RELOPT_TYPE_INT,
RELOPT_TYPE_REAL,
RELOPT_TYPE_ENUM,
@@ -80,6 +81,7 @@ typedef struct relopt_value
union
{
bool bool_val;
+ ternary ternary_val;
int int_val;
double real_val;
int enum_val;
@@ -94,6 +96,12 @@ typedef struct relopt_bool
bool default_val;
} relopt_bool;
+typedef struct relopt_rernary
+{
+ relopt_gen gen;
+ int default_val;
+} relopt_ternary;
+
typedef struct relopt_int
{
relopt_gen gen;
@@ -152,19 +160,6 @@ typedef struct
const char *optname; /* option's name */
relopt_type opttype; /* option's datatype */
int offset; /* offset of field in result struct */
-
- /*
- * isset_offset is an optional offset of a field in the result struct that
- * stores whether the option is explicitly set for the relation or if it
- * just picked up the default value. In most cases, this can be
- * accomplished by giving the reloption a special out-of-range default
- * value (e.g., some integer reloptions use -2), but this isn't always
- * possible. For example, a Boolean reloption cannot be given an
- * out-of-range default, so we need another way to discover the source of
- * its value. This offset is only used if given a value greater than
- * zero.
- */
- int isset_offset;
} relopt_parse_elt;
/* Local reloption definition */
@@ -195,6 +190,8 @@ typedef struct local_relopts
extern relopt_kind add_reloption_kind(void);
extern void add_bool_reloption(bits32 kinds, const char *name, const char *desc,
bool default_val, LOCKMODE lockmode);
+extern void add_ternary_reloption(bits32 kinds, const char *name,
+ const char *desc, int default_val, LOCKMODE lockmode);
extern void add_int_reloption(bits32 kinds, const char *name, const char *desc,
int default_val, int min_val, int max_val,
LOCKMODE lockmode);
@@ -214,6 +211,9 @@ extern void register_reloptions_validator(local_relopts *relopts,
extern void add_local_bool_reloption(local_relopts *relopts, const char *name,
const char *desc, bool default_val,
int offset);
+extern void add_local_ternary_reloption(local_relopts *relopts,
+ const char *name, const char *desc,
+ ternary default_val, int offset);
extern void add_local_int_reloption(local_relopts *relopts, const char *name,
const char *desc, int default_val,
int min_val, int max_val, int offset);
diff --git a/src/include/c.h b/src/include/c.h
index 39022f8a9d..9c59ccccf8 100644
--- a/src/include/c.h
+++ b/src/include/c.h
@@ -488,6 +488,22 @@ typedef void (*pg_funcptr_t) (void);
#include <stdbool.h>
+/*
+ * ternary
+ * Boolean value with an extrea "unset" option
+ *
+ * Ternary data type is used in relation options that can be "true", "false" or
+ * "unset". Since relation options are used deep inside the PostgreSQL code,
+ * this type is declared globally.
+*/
+
+typedef enum ternary
+{
+ TERNARY_FALSE = 0,
+ TERNARY_TRUE = 1,
+ TERNARY_UNSET = -1
+} ternary;
+
/* ----------------------------------------------------------------
* Section 3: standard system types
diff --git a/src/include/utils/rel.h b/src/include/utils/rel.h
index b552359915..7346d618f9 100644
--- a/src/include/utils/rel.h
+++ b/src/include/utils/rel.h
@@ -346,8 +346,7 @@ typedef struct StdRdOptions
bool user_catalog_table; /* use as an additional catalog relation */
int parallel_workers; /* max number of parallel workers */
StdRdOptIndexCleanup vacuum_index_cleanup; /* controls index vacuuming */
- bool vacuum_truncate; /* enables vacuum to truncate a relation */
- bool vacuum_truncate_set; /* whether vacuum_truncate is set */
+ ternary vacuum_truncate; /* enables vacuum to truncate a relation */
/*
* Fraction of pages in a relation that vacuum can eagerly scan and fail
--
2.39.2
--nextPart5129932.5fSG56mABF
Content-Disposition: attachment;
filename="v2-0003-Add-alias-to-be-used-as-unset-state.patch"
Content-Transfer-Encoding: 7Bit
Content-Type: text/x-patch; charset="unicode-2-0-utf-8";
name="v2-0003-Add-alias-to-be-used-as-unset-state.patch"
^ permalink raw reply [nested|flat] 868+ messages in thread
* [PATCH v2a 2/4] Introduce ternary reloptions
@ 2025-09-04 14:41 Nikolay Shaplov <[email protected]>
0 siblings, 0 replies; 868+ messages in thread
From: Nikolay Shaplov @ 2025-09-04 14:41 UTC (permalink / raw)
Introduce ternary reloption as a replacement for current `vacuum_truncate`
implementation. Remove `vacuum_truncate_set` additional flag and using
`TERNARY_UNSET` value instead.
---
src/backend/access/common/reloptions.c | 129 ++++++++++++++++++++-----
src/backend/commands/vacuum.c | 4 +-
src/include/access/reloptions.h | 26 ++---
src/include/c.h | 16 +++
src/include/utils/rel.h | 3 +-
5 files changed, 135 insertions(+), 43 deletions(-)
diff --git a/src/backend/access/common/reloptions.c b/src/backend/access/common/reloptions.c
index 0af3fea68f..f543f11001 100644
--- a/src/backend/access/common/reloptions.c
+++ b/src/backend/access/common/reloptions.c
@@ -40,9 +40,9 @@
*
* To add an option:
*
- * (i) decide on a type (bool, integer, real, enum, string), name, default
- * value, upper and lower bounds (if applicable); for strings, consider a
- * validation routine.
+ * (i) decide on a type (bool, ternary, integer, real, enum, string), name,
+ * default value, upper and lower bounds (if applicable); for strings,
+ * consider a validation routine.
* (ii) add a record below (or use add_<type>_reloption).
* (iii) add it to the appropriate options struct (perhaps StdRdOptions)
* (iv) add it to the appropriate handling routine (perhaps
@@ -147,15 +147,6 @@ static relopt_bool boolRelOpts[] =
},
false
},
- {
- {
- "vacuum_truncate",
- "Enables vacuum to truncate empty pages at the end of this table",
- RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
- ShareUpdateExclusiveLock
- },
- true
- },
{
{
"deduplicate_items",
@@ -170,6 +161,21 @@ static relopt_bool boolRelOpts[] =
{{NULL}}
};
+static relopt_ternary ternaryRelOpts[] =
+{
+ {
+ {
+ "vacuum_truncate",
+ "Enables vacuum to truncate empty pages at the end of this table",
+ RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
+ ShareUpdateExclusiveLock
+ },
+ TERNARY_UNSET
+ },
+ /* list terminator */
+ {{NULL}}
+};
+
static relopt_int intRelOpts[] =
{
{
@@ -600,6 +606,13 @@ initialize_reloptions(void)
boolRelOpts[i].gen.lockmode));
j++;
}
+ for (i = 0; ternaryRelOpts[i].gen.name; i++)
+ {
+ Assert(DoLockModesConflict(ternaryRelOpts[i].gen.lockmode,
+ ternaryRelOpts[i].gen.lockmode));
+ j++;
+ }
+
for (i = 0; intRelOpts[i].gen.name; i++)
{
Assert(DoLockModesConflict(intRelOpts[i].gen.lockmode,
@@ -640,6 +653,14 @@ initialize_reloptions(void)
j++;
}
+ for (i = 0; ternaryRelOpts[i].gen.name; i++)
+ {
+ relOpts[j] = &ternaryRelOpts[i].gen;
+ relOpts[j]->type = RELOPT_TYPE_TERNARY;
+ relOpts[j]->namelen = strlen(relOpts[j]->name);
+ j++;
+ }
+
for (i = 0; intRelOpts[i].gen.name; i++)
{
relOpts[j] = &intRelOpts[i].gen;
@@ -800,6 +821,9 @@ allocate_reloption(bits32 kinds, int type, const char *name, const char *desc,
case RELOPT_TYPE_BOOL:
size = sizeof(relopt_bool);
break;
+ case RELOPT_TYPE_TERNARY:
+ size = sizeof(relopt_ternary);
+ break;
case RELOPT_TYPE_INT:
size = sizeof(relopt_int);
break;
@@ -883,6 +907,54 @@ add_local_bool_reloption(local_relopts *relopts, const char *name,
add_local_reloption(relopts, (relopt_gen *) newoption, offset);
}
+/*
+ * init_ternary_reloption
+ * Allocate and initialize a new ternary reloption
+ */
+static relopt_ternary *
+init_ternary_reloption(bits32 kinds, const char *name, const char *desc,
+ ternary default_val, LOCKMODE lockmode)
+{
+ relopt_ternary *newoption;
+
+ newoption = (relopt_ternary *) allocate_reloption(kinds,
+ RELOPT_TYPE_TERNARY, name, desc, lockmode);
+ newoption->default_val = default_val;
+
+ return newoption;
+}
+
+/*
+ * add_ternary_reloption
+ * Add a new ternary reloption
+ */
+void
+add_ternary_reloption(bits32 kinds, const char *name, const char *desc,
+ ternary default_val, LOCKMODE lockmode)
+{
+ relopt_ternary *newoption = init_ternary_reloption(kinds, name, desc,
+ default_val, lockmode);
+
+ add_reloption((relopt_gen *) newoption);
+}
+
+/*
+ * add_local_ternary_reloption
+ * Add a new ternary local reloption
+ *
+ * 'offset' is offset of ternary-typed field.
+ */
+void
+add_local_ternary_reloption(local_relopts *relopts, const char *name,
+ const char *desc, ternary default_val,
+ int offset)
+{
+ relopt_ternary *newoption = init_ternary_reloption(RELOPT_KIND_LOCAL,
+ name, desc,
+ default_val, 0);
+
+ add_local_reloption(relopts, (relopt_gen *) newoption, offset);
+}
/*
* init_real_reloption
@@ -1617,6 +1689,18 @@ parse_one_reloption(relopt_value *option, char *text_str, int text_len,
option->gen->name, value)));
}
break;
+ case RELOPT_TYPE_TERNARY:
+ {
+ bool b;
+ parsed = parse_bool(value, &b);
+ option->values.ternary_val = b ? TERNARY_TRUE : TERNARY_FALSE;
+ if (validate && !parsed)
+ ereport(ERROR,
+ (errcode(ERRCODE_INVALID_PARAMETER_VALUE),
+ errmsg("invalid value for ternary option \"%s\": %s",
+ option->gen->name, value)));
+ }
+ break;
case RELOPT_TYPE_INT:
{
relopt_int *optint = (relopt_int *) option->gen;
@@ -1780,17 +1864,6 @@ fillRelOptions(void *rdopts, Size basesize,
char *itempos = ((char *) rdopts) + elems[j].offset;
char *string_val;
- /*
- * If isset_offset is provided, store whether the reloption is
- * set there.
- */
- if (elems[j].isset_offset > 0)
- {
- char *setpos = ((char *) rdopts) + elems[j].isset_offset;
-
- *(bool *) setpos = options[i].isset;
- }
-
switch (options[i].gen->type)
{
case RELOPT_TYPE_BOOL:
@@ -1798,6 +1871,11 @@ fillRelOptions(void *rdopts, Size basesize,
options[i].values.bool_val :
((relopt_bool *) options[i].gen)->default_val;
break;
+ case RELOPT_TYPE_TERNARY:
+ *(ternary *) itempos = options[i].isset ?
+ options[i].values.ternary_val :
+ ((relopt_ternary *) options[i].gen)->default_val;
+ break;
case RELOPT_TYPE_INT:
*(int *) itempos = options[i].isset ?
options[i].values.int_val :
@@ -1912,8 +1990,8 @@ default_reloptions(Datum reloptions, bool validate, relopt_kind kind)
offsetof(StdRdOptions, parallel_workers)},
{"vacuum_index_cleanup", RELOPT_TYPE_ENUM,
offsetof(StdRdOptions, vacuum_index_cleanup)},
- {"vacuum_truncate", RELOPT_TYPE_BOOL,
- offsetof(StdRdOptions, vacuum_truncate), offsetof(StdRdOptions, vacuum_truncate_set)},
+ {"vacuum_truncate", RELOPT_TYPE_TERNARY,
+ offsetof(StdRdOptions, vacuum_truncate)},
{"vacuum_max_eager_freeze_failure_rate", RELOPT_TYPE_REAL,
offsetof(StdRdOptions, vacuum_max_eager_freeze_failure_rate)}
};
@@ -1993,7 +2071,6 @@ build_local_reloptions(local_relopts *relopts, Datum options, bool validate)
elems[i].optname = opt->option->name;
elems[i].opttype = opt->option->type;
elems[i].offset = opt->offset;
- elems[i].isset_offset = 0; /* not supported for local relopts yet */
i++;
}
diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c
index 733ef40ae7..977babff54 100644
--- a/src/backend/commands/vacuum.c
+++ b/src/backend/commands/vacuum.c
@@ -2221,9 +2221,9 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams params,
{
StdRdOptions *opts = (StdRdOptions *) rel->rd_options;
- if (opts && opts->vacuum_truncate_set)
+ if (opts && opts->vacuum_truncate != TERNARY_UNSET)
{
- if (opts->vacuum_truncate)
+ if (opts->vacuum_truncate == TERNARY_TRUE)
params.truncate = VACOPTVALUE_ENABLED;
else
params.truncate = VACOPTVALUE_DISABLED;
diff --git a/src/include/access/reloptions.h b/src/include/access/reloptions.h
index a604a4702c..a436697658 100644
--- a/src/include/access/reloptions.h
+++ b/src/include/access/reloptions.h
@@ -29,6 +29,7 @@
typedef enum relopt_type
{
RELOPT_TYPE_BOOL,
+ RELOPT_TYPE_TERNARY, /* on, off, unset */
RELOPT_TYPE_INT,
RELOPT_TYPE_REAL,
RELOPT_TYPE_ENUM,
@@ -80,6 +81,7 @@ typedef struct relopt_value
union
{
bool bool_val;
+ ternary ternary_val;
int int_val;
double real_val;
int enum_val;
@@ -94,6 +96,12 @@ typedef struct relopt_bool
bool default_val;
} relopt_bool;
+typedef struct relopt_rernary
+{
+ relopt_gen gen;
+ int default_val;
+} relopt_ternary;
+
typedef struct relopt_int
{
relopt_gen gen;
@@ -152,19 +160,6 @@ typedef struct
const char *optname; /* option's name */
relopt_type opttype; /* option's datatype */
int offset; /* offset of field in result struct */
-
- /*
- * isset_offset is an optional offset of a field in the result struct that
- * stores whether the option is explicitly set for the relation or if it
- * just picked up the default value. In most cases, this can be
- * accomplished by giving the reloption a special out-of-range default
- * value (e.g., some integer reloptions use -2), but this isn't always
- * possible. For example, a Boolean reloption cannot be given an
- * out-of-range default, so we need another way to discover the source of
- * its value. This offset is only used if given a value greater than
- * zero.
- */
- int isset_offset;
} relopt_parse_elt;
/* Local reloption definition */
@@ -195,6 +190,8 @@ typedef struct local_relopts
extern relopt_kind add_reloption_kind(void);
extern void add_bool_reloption(bits32 kinds, const char *name, const char *desc,
bool default_val, LOCKMODE lockmode);
+extern void add_ternary_reloption(bits32 kinds, const char *name,
+ const char *desc, int default_val, LOCKMODE lockmode);
extern void add_int_reloption(bits32 kinds, const char *name, const char *desc,
int default_val, int min_val, int max_val,
LOCKMODE lockmode);
@@ -214,6 +211,9 @@ extern void register_reloptions_validator(local_relopts *relopts,
extern void add_local_bool_reloption(local_relopts *relopts, const char *name,
const char *desc, bool default_val,
int offset);
+extern void add_local_ternary_reloption(local_relopts *relopts,
+ const char *name, const char *desc,
+ ternary default_val, int offset);
extern void add_local_int_reloption(local_relopts *relopts, const char *name,
const char *desc, int default_val,
int min_val, int max_val, int offset);
diff --git a/src/include/c.h b/src/include/c.h
index 39022f8a9d..9c59ccccf8 100644
--- a/src/include/c.h
+++ b/src/include/c.h
@@ -488,6 +488,22 @@ typedef void (*pg_funcptr_t) (void);
#include <stdbool.h>
+/*
+ * ternary
+ * Boolean value with an extrea "unset" option
+ *
+ * Ternary data type is used in relation options that can be "true", "false" or
+ * "unset". Since relation options are used deep inside the PostgreSQL code,
+ * this type is declared globally.
+*/
+
+typedef enum ternary
+{
+ TERNARY_FALSE = 0,
+ TERNARY_TRUE = 1,
+ TERNARY_UNSET = -1
+} ternary;
+
/* ----------------------------------------------------------------
* Section 3: standard system types
diff --git a/src/include/utils/rel.h b/src/include/utils/rel.h
index b552359915..7346d618f9 100644
--- a/src/include/utils/rel.h
+++ b/src/include/utils/rel.h
@@ -346,8 +346,7 @@ typedef struct StdRdOptions
bool user_catalog_table; /* use as an additional catalog relation */
int parallel_workers; /* max number of parallel workers */
StdRdOptIndexCleanup vacuum_index_cleanup; /* controls index vacuuming */
- bool vacuum_truncate; /* enables vacuum to truncate a relation */
- bool vacuum_truncate_set; /* whether vacuum_truncate is set */
+ ternary vacuum_truncate; /* enables vacuum to truncate a relation */
/*
* Fraction of pages in a relation that vacuum can eagerly scan and fail
--
2.39.2
--nextPart4155600.3ZeAukHxDK
Content-Disposition: attachment;
filename="v2a-0003-Add-alias-to-be-used-as-unset-state.patch"
Content-Transfer-Encoding: 7Bit
Content-Type: text/x-patch; charset="unicode-2-0-utf-8";
name="v2a-0003-Add-alias-to-be-used-as-unset-state.patch"
^ permalink raw reply [nested|flat] 868+ messages in thread
* [PATCH v2a 2/4] Introduce ternary reloptions
@ 2025-09-04 14:41 Nikolay Shaplov <[email protected]>
0 siblings, 0 replies; 868+ messages in thread
From: Nikolay Shaplov @ 2025-09-04 14:41 UTC (permalink / raw)
Introduce ternary reloption as a replacement for current `vacuum_truncate`
implementation. Remove `vacuum_truncate_set` additional flag and using
`TERNARY_UNSET` value instead.
---
src/backend/access/common/reloptions.c | 129 ++++++++++++++++++++-----
src/backend/commands/vacuum.c | 4 +-
src/include/access/reloptions.h | 26 ++---
src/include/c.h | 16 +++
src/include/utils/rel.h | 3 +-
5 files changed, 135 insertions(+), 43 deletions(-)
diff --git a/src/backend/access/common/reloptions.c b/src/backend/access/common/reloptions.c
index 0af3fea68f..f543f11001 100644
--- a/src/backend/access/common/reloptions.c
+++ b/src/backend/access/common/reloptions.c
@@ -40,9 +40,9 @@
*
* To add an option:
*
- * (i) decide on a type (bool, integer, real, enum, string), name, default
- * value, upper and lower bounds (if applicable); for strings, consider a
- * validation routine.
+ * (i) decide on a type (bool, ternary, integer, real, enum, string), name,
+ * default value, upper and lower bounds (if applicable); for strings,
+ * consider a validation routine.
* (ii) add a record below (or use add_<type>_reloption).
* (iii) add it to the appropriate options struct (perhaps StdRdOptions)
* (iv) add it to the appropriate handling routine (perhaps
@@ -147,15 +147,6 @@ static relopt_bool boolRelOpts[] =
},
false
},
- {
- {
- "vacuum_truncate",
- "Enables vacuum to truncate empty pages at the end of this table",
- RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
- ShareUpdateExclusiveLock
- },
- true
- },
{
{
"deduplicate_items",
@@ -170,6 +161,21 @@ static relopt_bool boolRelOpts[] =
{{NULL}}
};
+static relopt_ternary ternaryRelOpts[] =
+{
+ {
+ {
+ "vacuum_truncate",
+ "Enables vacuum to truncate empty pages at the end of this table",
+ RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
+ ShareUpdateExclusiveLock
+ },
+ TERNARY_UNSET
+ },
+ /* list terminator */
+ {{NULL}}
+};
+
static relopt_int intRelOpts[] =
{
{
@@ -600,6 +606,13 @@ initialize_reloptions(void)
boolRelOpts[i].gen.lockmode));
j++;
}
+ for (i = 0; ternaryRelOpts[i].gen.name; i++)
+ {
+ Assert(DoLockModesConflict(ternaryRelOpts[i].gen.lockmode,
+ ternaryRelOpts[i].gen.lockmode));
+ j++;
+ }
+
for (i = 0; intRelOpts[i].gen.name; i++)
{
Assert(DoLockModesConflict(intRelOpts[i].gen.lockmode,
@@ -640,6 +653,14 @@ initialize_reloptions(void)
j++;
}
+ for (i = 0; ternaryRelOpts[i].gen.name; i++)
+ {
+ relOpts[j] = &ternaryRelOpts[i].gen;
+ relOpts[j]->type = RELOPT_TYPE_TERNARY;
+ relOpts[j]->namelen = strlen(relOpts[j]->name);
+ j++;
+ }
+
for (i = 0; intRelOpts[i].gen.name; i++)
{
relOpts[j] = &intRelOpts[i].gen;
@@ -800,6 +821,9 @@ allocate_reloption(bits32 kinds, int type, const char *name, const char *desc,
case RELOPT_TYPE_BOOL:
size = sizeof(relopt_bool);
break;
+ case RELOPT_TYPE_TERNARY:
+ size = sizeof(relopt_ternary);
+ break;
case RELOPT_TYPE_INT:
size = sizeof(relopt_int);
break;
@@ -883,6 +907,54 @@ add_local_bool_reloption(local_relopts *relopts, const char *name,
add_local_reloption(relopts, (relopt_gen *) newoption, offset);
}
+/*
+ * init_ternary_reloption
+ * Allocate and initialize a new ternary reloption
+ */
+static relopt_ternary *
+init_ternary_reloption(bits32 kinds, const char *name, const char *desc,
+ ternary default_val, LOCKMODE lockmode)
+{
+ relopt_ternary *newoption;
+
+ newoption = (relopt_ternary *) allocate_reloption(kinds,
+ RELOPT_TYPE_TERNARY, name, desc, lockmode);
+ newoption->default_val = default_val;
+
+ return newoption;
+}
+
+/*
+ * add_ternary_reloption
+ * Add a new ternary reloption
+ */
+void
+add_ternary_reloption(bits32 kinds, const char *name, const char *desc,
+ ternary default_val, LOCKMODE lockmode)
+{
+ relopt_ternary *newoption = init_ternary_reloption(kinds, name, desc,
+ default_val, lockmode);
+
+ add_reloption((relopt_gen *) newoption);
+}
+
+/*
+ * add_local_ternary_reloption
+ * Add a new ternary local reloption
+ *
+ * 'offset' is offset of ternary-typed field.
+ */
+void
+add_local_ternary_reloption(local_relopts *relopts, const char *name,
+ const char *desc, ternary default_val,
+ int offset)
+{
+ relopt_ternary *newoption = init_ternary_reloption(RELOPT_KIND_LOCAL,
+ name, desc,
+ default_val, 0);
+
+ add_local_reloption(relopts, (relopt_gen *) newoption, offset);
+}
/*
* init_real_reloption
@@ -1617,6 +1689,18 @@ parse_one_reloption(relopt_value *option, char *text_str, int text_len,
option->gen->name, value)));
}
break;
+ case RELOPT_TYPE_TERNARY:
+ {
+ bool b;
+ parsed = parse_bool(value, &b);
+ option->values.ternary_val = b ? TERNARY_TRUE : TERNARY_FALSE;
+ if (validate && !parsed)
+ ereport(ERROR,
+ (errcode(ERRCODE_INVALID_PARAMETER_VALUE),
+ errmsg("invalid value for ternary option \"%s\": %s",
+ option->gen->name, value)));
+ }
+ break;
case RELOPT_TYPE_INT:
{
relopt_int *optint = (relopt_int *) option->gen;
@@ -1780,17 +1864,6 @@ fillRelOptions(void *rdopts, Size basesize,
char *itempos = ((char *) rdopts) + elems[j].offset;
char *string_val;
- /*
- * If isset_offset is provided, store whether the reloption is
- * set there.
- */
- if (elems[j].isset_offset > 0)
- {
- char *setpos = ((char *) rdopts) + elems[j].isset_offset;
-
- *(bool *) setpos = options[i].isset;
- }
-
switch (options[i].gen->type)
{
case RELOPT_TYPE_BOOL:
@@ -1798,6 +1871,11 @@ fillRelOptions(void *rdopts, Size basesize,
options[i].values.bool_val :
((relopt_bool *) options[i].gen)->default_val;
break;
+ case RELOPT_TYPE_TERNARY:
+ *(ternary *) itempos = options[i].isset ?
+ options[i].values.ternary_val :
+ ((relopt_ternary *) options[i].gen)->default_val;
+ break;
case RELOPT_TYPE_INT:
*(int *) itempos = options[i].isset ?
options[i].values.int_val :
@@ -1912,8 +1990,8 @@ default_reloptions(Datum reloptions, bool validate, relopt_kind kind)
offsetof(StdRdOptions, parallel_workers)},
{"vacuum_index_cleanup", RELOPT_TYPE_ENUM,
offsetof(StdRdOptions, vacuum_index_cleanup)},
- {"vacuum_truncate", RELOPT_TYPE_BOOL,
- offsetof(StdRdOptions, vacuum_truncate), offsetof(StdRdOptions, vacuum_truncate_set)},
+ {"vacuum_truncate", RELOPT_TYPE_TERNARY,
+ offsetof(StdRdOptions, vacuum_truncate)},
{"vacuum_max_eager_freeze_failure_rate", RELOPT_TYPE_REAL,
offsetof(StdRdOptions, vacuum_max_eager_freeze_failure_rate)}
};
@@ -1993,7 +2071,6 @@ build_local_reloptions(local_relopts *relopts, Datum options, bool validate)
elems[i].optname = opt->option->name;
elems[i].opttype = opt->option->type;
elems[i].offset = opt->offset;
- elems[i].isset_offset = 0; /* not supported for local relopts yet */
i++;
}
diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c
index 733ef40ae7..977babff54 100644
--- a/src/backend/commands/vacuum.c
+++ b/src/backend/commands/vacuum.c
@@ -2221,9 +2221,9 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams params,
{
StdRdOptions *opts = (StdRdOptions *) rel->rd_options;
- if (opts && opts->vacuum_truncate_set)
+ if (opts && opts->vacuum_truncate != TERNARY_UNSET)
{
- if (opts->vacuum_truncate)
+ if (opts->vacuum_truncate == TERNARY_TRUE)
params.truncate = VACOPTVALUE_ENABLED;
else
params.truncate = VACOPTVALUE_DISABLED;
diff --git a/src/include/access/reloptions.h b/src/include/access/reloptions.h
index a604a4702c..a436697658 100644
--- a/src/include/access/reloptions.h
+++ b/src/include/access/reloptions.h
@@ -29,6 +29,7 @@
typedef enum relopt_type
{
RELOPT_TYPE_BOOL,
+ RELOPT_TYPE_TERNARY, /* on, off, unset */
RELOPT_TYPE_INT,
RELOPT_TYPE_REAL,
RELOPT_TYPE_ENUM,
@@ -80,6 +81,7 @@ typedef struct relopt_value
union
{
bool bool_val;
+ ternary ternary_val;
int int_val;
double real_val;
int enum_val;
@@ -94,6 +96,12 @@ typedef struct relopt_bool
bool default_val;
} relopt_bool;
+typedef struct relopt_rernary
+{
+ relopt_gen gen;
+ int default_val;
+} relopt_ternary;
+
typedef struct relopt_int
{
relopt_gen gen;
@@ -152,19 +160,6 @@ typedef struct
const char *optname; /* option's name */
relopt_type opttype; /* option's datatype */
int offset; /* offset of field in result struct */
-
- /*
- * isset_offset is an optional offset of a field in the result struct that
- * stores whether the option is explicitly set for the relation or if it
- * just picked up the default value. In most cases, this can be
- * accomplished by giving the reloption a special out-of-range default
- * value (e.g., some integer reloptions use -2), but this isn't always
- * possible. For example, a Boolean reloption cannot be given an
- * out-of-range default, so we need another way to discover the source of
- * its value. This offset is only used if given a value greater than
- * zero.
- */
- int isset_offset;
} relopt_parse_elt;
/* Local reloption definition */
@@ -195,6 +190,8 @@ typedef struct local_relopts
extern relopt_kind add_reloption_kind(void);
extern void add_bool_reloption(bits32 kinds, const char *name, const char *desc,
bool default_val, LOCKMODE lockmode);
+extern void add_ternary_reloption(bits32 kinds, const char *name,
+ const char *desc, int default_val, LOCKMODE lockmode);
extern void add_int_reloption(bits32 kinds, const char *name, const char *desc,
int default_val, int min_val, int max_val,
LOCKMODE lockmode);
@@ -214,6 +211,9 @@ extern void register_reloptions_validator(local_relopts *relopts,
extern void add_local_bool_reloption(local_relopts *relopts, const char *name,
const char *desc, bool default_val,
int offset);
+extern void add_local_ternary_reloption(local_relopts *relopts,
+ const char *name, const char *desc,
+ ternary default_val, int offset);
extern void add_local_int_reloption(local_relopts *relopts, const char *name,
const char *desc, int default_val,
int min_val, int max_val, int offset);
diff --git a/src/include/c.h b/src/include/c.h
index 39022f8a9d..9c59ccccf8 100644
--- a/src/include/c.h
+++ b/src/include/c.h
@@ -488,6 +488,22 @@ typedef void (*pg_funcptr_t) (void);
#include <stdbool.h>
+/*
+ * ternary
+ * Boolean value with an extrea "unset" option
+ *
+ * Ternary data type is used in relation options that can be "true", "false" or
+ * "unset". Since relation options are used deep inside the PostgreSQL code,
+ * this type is declared globally.
+*/
+
+typedef enum ternary
+{
+ TERNARY_FALSE = 0,
+ TERNARY_TRUE = 1,
+ TERNARY_UNSET = -1
+} ternary;
+
/* ----------------------------------------------------------------
* Section 3: standard system types
diff --git a/src/include/utils/rel.h b/src/include/utils/rel.h
index b552359915..7346d618f9 100644
--- a/src/include/utils/rel.h
+++ b/src/include/utils/rel.h
@@ -346,8 +346,7 @@ typedef struct StdRdOptions
bool user_catalog_table; /* use as an additional catalog relation */
int parallel_workers; /* max number of parallel workers */
StdRdOptIndexCleanup vacuum_index_cleanup; /* controls index vacuuming */
- bool vacuum_truncate; /* enables vacuum to truncate a relation */
- bool vacuum_truncate_set; /* whether vacuum_truncate is set */
+ ternary vacuum_truncate; /* enables vacuum to truncate a relation */
/*
* Fraction of pages in a relation that vacuum can eagerly scan and fail
--
2.39.2
--nextPart4155600.3ZeAukHxDK
Content-Disposition: attachment;
filename="v2a-0003-Add-alias-to-be-used-as-unset-state.patch"
Content-Transfer-Encoding: 7Bit
Content-Type: text/x-patch; charset="unicode-2-0-utf-8";
name="v2a-0003-Add-alias-to-be-used-as-unset-state.patch"
^ permalink raw reply [nested|flat] 868+ messages in thread
* [PATCH v2 2/4] Introduce ternary reloptions
@ 2025-09-04 14:41 Nikolay Shaplov <[email protected]>
0 siblings, 0 replies; 868+ messages in thread
From: Nikolay Shaplov @ 2025-09-04 14:41 UTC (permalink / raw)
Introduce ternary reloption as a replacement for current `vacuum_truncate`
implementation. Remove `vacuum_truncate_set` additional flag and using
`TERNARY_UNSET` value instead.
---
src/backend/access/common/reloptions.c | 129 ++++++++++++++++++++-----
src/backend/commands/vacuum.c | 4 +-
src/include/access/reloptions.h | 26 ++---
src/include/c.h | 16 +++
src/include/utils/rel.h | 3 +-
5 files changed, 135 insertions(+), 43 deletions(-)
diff --git a/src/backend/access/common/reloptions.c b/src/backend/access/common/reloptions.c
index 0af3fea68f..f543f11001 100644
--- a/src/backend/access/common/reloptions.c
+++ b/src/backend/access/common/reloptions.c
@@ -40,9 +40,9 @@
*
* To add an option:
*
- * (i) decide on a type (bool, integer, real, enum, string), name, default
- * value, upper and lower bounds (if applicable); for strings, consider a
- * validation routine.
+ * (i) decide on a type (bool, ternary, integer, real, enum, string), name,
+ * default value, upper and lower bounds (if applicable); for strings,
+ * consider a validation routine.
* (ii) add a record below (or use add_<type>_reloption).
* (iii) add it to the appropriate options struct (perhaps StdRdOptions)
* (iv) add it to the appropriate handling routine (perhaps
@@ -147,15 +147,6 @@ static relopt_bool boolRelOpts[] =
},
false
},
- {
- {
- "vacuum_truncate",
- "Enables vacuum to truncate empty pages at the end of this table",
- RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
- ShareUpdateExclusiveLock
- },
- true
- },
{
{
"deduplicate_items",
@@ -170,6 +161,21 @@ static relopt_bool boolRelOpts[] =
{{NULL}}
};
+static relopt_ternary ternaryRelOpts[] =
+{
+ {
+ {
+ "vacuum_truncate",
+ "Enables vacuum to truncate empty pages at the end of this table",
+ RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
+ ShareUpdateExclusiveLock
+ },
+ TERNARY_UNSET
+ },
+ /* list terminator */
+ {{NULL}}
+};
+
static relopt_int intRelOpts[] =
{
{
@@ -600,6 +606,13 @@ initialize_reloptions(void)
boolRelOpts[i].gen.lockmode));
j++;
}
+ for (i = 0; ternaryRelOpts[i].gen.name; i++)
+ {
+ Assert(DoLockModesConflict(ternaryRelOpts[i].gen.lockmode,
+ ternaryRelOpts[i].gen.lockmode));
+ j++;
+ }
+
for (i = 0; intRelOpts[i].gen.name; i++)
{
Assert(DoLockModesConflict(intRelOpts[i].gen.lockmode,
@@ -640,6 +653,14 @@ initialize_reloptions(void)
j++;
}
+ for (i = 0; ternaryRelOpts[i].gen.name; i++)
+ {
+ relOpts[j] = &ternaryRelOpts[i].gen;
+ relOpts[j]->type = RELOPT_TYPE_TERNARY;
+ relOpts[j]->namelen = strlen(relOpts[j]->name);
+ j++;
+ }
+
for (i = 0; intRelOpts[i].gen.name; i++)
{
relOpts[j] = &intRelOpts[i].gen;
@@ -800,6 +821,9 @@ allocate_reloption(bits32 kinds, int type, const char *name, const char *desc,
case RELOPT_TYPE_BOOL:
size = sizeof(relopt_bool);
break;
+ case RELOPT_TYPE_TERNARY:
+ size = sizeof(relopt_ternary);
+ break;
case RELOPT_TYPE_INT:
size = sizeof(relopt_int);
break;
@@ -883,6 +907,54 @@ add_local_bool_reloption(local_relopts *relopts, const char *name,
add_local_reloption(relopts, (relopt_gen *) newoption, offset);
}
+/*
+ * init_ternary_reloption
+ * Allocate and initialize a new ternary reloption
+ */
+static relopt_ternary *
+init_ternary_reloption(bits32 kinds, const char *name, const char *desc,
+ ternary default_val, LOCKMODE lockmode)
+{
+ relopt_ternary *newoption;
+
+ newoption = (relopt_ternary *) allocate_reloption(kinds,
+ RELOPT_TYPE_TERNARY, name, desc, lockmode);
+ newoption->default_val = default_val;
+
+ return newoption;
+}
+
+/*
+ * add_ternary_reloption
+ * Add a new ternary reloption
+ */
+void
+add_ternary_reloption(bits32 kinds, const char *name, const char *desc,
+ ternary default_val, LOCKMODE lockmode)
+{
+ relopt_ternary *newoption = init_ternary_reloption(kinds, name, desc,
+ default_val, lockmode);
+
+ add_reloption((relopt_gen *) newoption);
+}
+
+/*
+ * add_local_ternary_reloption
+ * Add a new ternary local reloption
+ *
+ * 'offset' is offset of ternary-typed field.
+ */
+void
+add_local_ternary_reloption(local_relopts *relopts, const char *name,
+ const char *desc, ternary default_val,
+ int offset)
+{
+ relopt_ternary *newoption = init_ternary_reloption(RELOPT_KIND_LOCAL,
+ name, desc,
+ default_val, 0);
+
+ add_local_reloption(relopts, (relopt_gen *) newoption, offset);
+}
/*
* init_real_reloption
@@ -1617,6 +1689,18 @@ parse_one_reloption(relopt_value *option, char *text_str, int text_len,
option->gen->name, value)));
}
break;
+ case RELOPT_TYPE_TERNARY:
+ {
+ bool b;
+ parsed = parse_bool(value, &b);
+ option->values.ternary_val = b ? TERNARY_TRUE : TERNARY_FALSE;
+ if (validate && !parsed)
+ ereport(ERROR,
+ (errcode(ERRCODE_INVALID_PARAMETER_VALUE),
+ errmsg("invalid value for ternary option \"%s\": %s",
+ option->gen->name, value)));
+ }
+ break;
case RELOPT_TYPE_INT:
{
relopt_int *optint = (relopt_int *) option->gen;
@@ -1780,17 +1864,6 @@ fillRelOptions(void *rdopts, Size basesize,
char *itempos = ((char *) rdopts) + elems[j].offset;
char *string_val;
- /*
- * If isset_offset is provided, store whether the reloption is
- * set there.
- */
- if (elems[j].isset_offset > 0)
- {
- char *setpos = ((char *) rdopts) + elems[j].isset_offset;
-
- *(bool *) setpos = options[i].isset;
- }
-
switch (options[i].gen->type)
{
case RELOPT_TYPE_BOOL:
@@ -1798,6 +1871,11 @@ fillRelOptions(void *rdopts, Size basesize,
options[i].values.bool_val :
((relopt_bool *) options[i].gen)->default_val;
break;
+ case RELOPT_TYPE_TERNARY:
+ *(ternary *) itempos = options[i].isset ?
+ options[i].values.ternary_val :
+ ((relopt_ternary *) options[i].gen)->default_val;
+ break;
case RELOPT_TYPE_INT:
*(int *) itempos = options[i].isset ?
options[i].values.int_val :
@@ -1912,8 +1990,8 @@ default_reloptions(Datum reloptions, bool validate, relopt_kind kind)
offsetof(StdRdOptions, parallel_workers)},
{"vacuum_index_cleanup", RELOPT_TYPE_ENUM,
offsetof(StdRdOptions, vacuum_index_cleanup)},
- {"vacuum_truncate", RELOPT_TYPE_BOOL,
- offsetof(StdRdOptions, vacuum_truncate), offsetof(StdRdOptions, vacuum_truncate_set)},
+ {"vacuum_truncate", RELOPT_TYPE_TERNARY,
+ offsetof(StdRdOptions, vacuum_truncate)},
{"vacuum_max_eager_freeze_failure_rate", RELOPT_TYPE_REAL,
offsetof(StdRdOptions, vacuum_max_eager_freeze_failure_rate)}
};
@@ -1993,7 +2071,6 @@ build_local_reloptions(local_relopts *relopts, Datum options, bool validate)
elems[i].optname = opt->option->name;
elems[i].opttype = opt->option->type;
elems[i].offset = opt->offset;
- elems[i].isset_offset = 0; /* not supported for local relopts yet */
i++;
}
diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c
index 733ef40ae7..977babff54 100644
--- a/src/backend/commands/vacuum.c
+++ b/src/backend/commands/vacuum.c
@@ -2221,9 +2221,9 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams params,
{
StdRdOptions *opts = (StdRdOptions *) rel->rd_options;
- if (opts && opts->vacuum_truncate_set)
+ if (opts && opts->vacuum_truncate != TERNARY_UNSET)
{
- if (opts->vacuum_truncate)
+ if (opts->vacuum_truncate == TERNARY_TRUE)
params.truncate = VACOPTVALUE_ENABLED;
else
params.truncate = VACOPTVALUE_DISABLED;
diff --git a/src/include/access/reloptions.h b/src/include/access/reloptions.h
index a604a4702c..a436697658 100644
--- a/src/include/access/reloptions.h
+++ b/src/include/access/reloptions.h
@@ -29,6 +29,7 @@
typedef enum relopt_type
{
RELOPT_TYPE_BOOL,
+ RELOPT_TYPE_TERNARY, /* on, off, unset */
RELOPT_TYPE_INT,
RELOPT_TYPE_REAL,
RELOPT_TYPE_ENUM,
@@ -80,6 +81,7 @@ typedef struct relopt_value
union
{
bool bool_val;
+ ternary ternary_val;
int int_val;
double real_val;
int enum_val;
@@ -94,6 +96,12 @@ typedef struct relopt_bool
bool default_val;
} relopt_bool;
+typedef struct relopt_rernary
+{
+ relopt_gen gen;
+ int default_val;
+} relopt_ternary;
+
typedef struct relopt_int
{
relopt_gen gen;
@@ -152,19 +160,6 @@ typedef struct
const char *optname; /* option's name */
relopt_type opttype; /* option's datatype */
int offset; /* offset of field in result struct */
-
- /*
- * isset_offset is an optional offset of a field in the result struct that
- * stores whether the option is explicitly set for the relation or if it
- * just picked up the default value. In most cases, this can be
- * accomplished by giving the reloption a special out-of-range default
- * value (e.g., some integer reloptions use -2), but this isn't always
- * possible. For example, a Boolean reloption cannot be given an
- * out-of-range default, so we need another way to discover the source of
- * its value. This offset is only used if given a value greater than
- * zero.
- */
- int isset_offset;
} relopt_parse_elt;
/* Local reloption definition */
@@ -195,6 +190,8 @@ typedef struct local_relopts
extern relopt_kind add_reloption_kind(void);
extern void add_bool_reloption(bits32 kinds, const char *name, const char *desc,
bool default_val, LOCKMODE lockmode);
+extern void add_ternary_reloption(bits32 kinds, const char *name,
+ const char *desc, int default_val, LOCKMODE lockmode);
extern void add_int_reloption(bits32 kinds, const char *name, const char *desc,
int default_val, int min_val, int max_val,
LOCKMODE lockmode);
@@ -214,6 +211,9 @@ extern void register_reloptions_validator(local_relopts *relopts,
extern void add_local_bool_reloption(local_relopts *relopts, const char *name,
const char *desc, bool default_val,
int offset);
+extern void add_local_ternary_reloption(local_relopts *relopts,
+ const char *name, const char *desc,
+ ternary default_val, int offset);
extern void add_local_int_reloption(local_relopts *relopts, const char *name,
const char *desc, int default_val,
int min_val, int max_val, int offset);
diff --git a/src/include/c.h b/src/include/c.h
index 39022f8a9d..9c59ccccf8 100644
--- a/src/include/c.h
+++ b/src/include/c.h
@@ -488,6 +488,22 @@ typedef void (*pg_funcptr_t) (void);
#include <stdbool.h>
+/*
+ * ternary
+ * Boolean value with an extrea "unset" option
+ *
+ * Ternary data type is used in relation options that can be "true", "false" or
+ * "unset". Since relation options are used deep inside the PostgreSQL code,
+ * this type is declared globally.
+*/
+
+typedef enum ternary
+{
+ TERNARY_FALSE = 0,
+ TERNARY_TRUE = 1,
+ TERNARY_UNSET = -1
+} ternary;
+
/* ----------------------------------------------------------------
* Section 3: standard system types
diff --git a/src/include/utils/rel.h b/src/include/utils/rel.h
index b552359915..7346d618f9 100644
--- a/src/include/utils/rel.h
+++ b/src/include/utils/rel.h
@@ -346,8 +346,7 @@ typedef struct StdRdOptions
bool user_catalog_table; /* use as an additional catalog relation */
int parallel_workers; /* max number of parallel workers */
StdRdOptIndexCleanup vacuum_index_cleanup; /* controls index vacuuming */
- bool vacuum_truncate; /* enables vacuum to truncate a relation */
- bool vacuum_truncate_set; /* whether vacuum_truncate is set */
+ ternary vacuum_truncate; /* enables vacuum to truncate a relation */
/*
* Fraction of pages in a relation that vacuum can eagerly scan and fail
--
2.39.2
--nextPart5129932.5fSG56mABF
Content-Disposition: attachment;
filename="v2-0003-Add-alias-to-be-used-as-unset-state.patch"
Content-Transfer-Encoding: 7Bit
Content-Type: text/x-patch; charset="unicode-2-0-utf-8";
name="v2-0003-Add-alias-to-be-used-as-unset-state.patch"
^ permalink raw reply [nested|flat] 868+ messages in thread
* [PATCH v2a 2/4] Introduce ternary reloptions
@ 2025-09-04 14:41 Nikolay Shaplov <[email protected]>
0 siblings, 0 replies; 868+ messages in thread
From: Nikolay Shaplov @ 2025-09-04 14:41 UTC (permalink / raw)
Introduce ternary reloption as a replacement for current `vacuum_truncate`
implementation. Remove `vacuum_truncate_set` additional flag and using
`TERNARY_UNSET` value instead.
---
src/backend/access/common/reloptions.c | 129 ++++++++++++++++++++-----
src/backend/commands/vacuum.c | 4 +-
src/include/access/reloptions.h | 26 ++---
src/include/c.h | 16 +++
src/include/utils/rel.h | 3 +-
5 files changed, 135 insertions(+), 43 deletions(-)
diff --git a/src/backend/access/common/reloptions.c b/src/backend/access/common/reloptions.c
index 0af3fea68f..f543f11001 100644
--- a/src/backend/access/common/reloptions.c
+++ b/src/backend/access/common/reloptions.c
@@ -40,9 +40,9 @@
*
* To add an option:
*
- * (i) decide on a type (bool, integer, real, enum, string), name, default
- * value, upper and lower bounds (if applicable); for strings, consider a
- * validation routine.
+ * (i) decide on a type (bool, ternary, integer, real, enum, string), name,
+ * default value, upper and lower bounds (if applicable); for strings,
+ * consider a validation routine.
* (ii) add a record below (or use add_<type>_reloption).
* (iii) add it to the appropriate options struct (perhaps StdRdOptions)
* (iv) add it to the appropriate handling routine (perhaps
@@ -147,15 +147,6 @@ static relopt_bool boolRelOpts[] =
},
false
},
- {
- {
- "vacuum_truncate",
- "Enables vacuum to truncate empty pages at the end of this table",
- RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
- ShareUpdateExclusiveLock
- },
- true
- },
{
{
"deduplicate_items",
@@ -170,6 +161,21 @@ static relopt_bool boolRelOpts[] =
{{NULL}}
};
+static relopt_ternary ternaryRelOpts[] =
+{
+ {
+ {
+ "vacuum_truncate",
+ "Enables vacuum to truncate empty pages at the end of this table",
+ RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
+ ShareUpdateExclusiveLock
+ },
+ TERNARY_UNSET
+ },
+ /* list terminator */
+ {{NULL}}
+};
+
static relopt_int intRelOpts[] =
{
{
@@ -600,6 +606,13 @@ initialize_reloptions(void)
boolRelOpts[i].gen.lockmode));
j++;
}
+ for (i = 0; ternaryRelOpts[i].gen.name; i++)
+ {
+ Assert(DoLockModesConflict(ternaryRelOpts[i].gen.lockmode,
+ ternaryRelOpts[i].gen.lockmode));
+ j++;
+ }
+
for (i = 0; intRelOpts[i].gen.name; i++)
{
Assert(DoLockModesConflict(intRelOpts[i].gen.lockmode,
@@ -640,6 +653,14 @@ initialize_reloptions(void)
j++;
}
+ for (i = 0; ternaryRelOpts[i].gen.name; i++)
+ {
+ relOpts[j] = &ternaryRelOpts[i].gen;
+ relOpts[j]->type = RELOPT_TYPE_TERNARY;
+ relOpts[j]->namelen = strlen(relOpts[j]->name);
+ j++;
+ }
+
for (i = 0; intRelOpts[i].gen.name; i++)
{
relOpts[j] = &intRelOpts[i].gen;
@@ -800,6 +821,9 @@ allocate_reloption(bits32 kinds, int type, const char *name, const char *desc,
case RELOPT_TYPE_BOOL:
size = sizeof(relopt_bool);
break;
+ case RELOPT_TYPE_TERNARY:
+ size = sizeof(relopt_ternary);
+ break;
case RELOPT_TYPE_INT:
size = sizeof(relopt_int);
break;
@@ -883,6 +907,54 @@ add_local_bool_reloption(local_relopts *relopts, const char *name,
add_local_reloption(relopts, (relopt_gen *) newoption, offset);
}
+/*
+ * init_ternary_reloption
+ * Allocate and initialize a new ternary reloption
+ */
+static relopt_ternary *
+init_ternary_reloption(bits32 kinds, const char *name, const char *desc,
+ ternary default_val, LOCKMODE lockmode)
+{
+ relopt_ternary *newoption;
+
+ newoption = (relopt_ternary *) allocate_reloption(kinds,
+ RELOPT_TYPE_TERNARY, name, desc, lockmode);
+ newoption->default_val = default_val;
+
+ return newoption;
+}
+
+/*
+ * add_ternary_reloption
+ * Add a new ternary reloption
+ */
+void
+add_ternary_reloption(bits32 kinds, const char *name, const char *desc,
+ ternary default_val, LOCKMODE lockmode)
+{
+ relopt_ternary *newoption = init_ternary_reloption(kinds, name, desc,
+ default_val, lockmode);
+
+ add_reloption((relopt_gen *) newoption);
+}
+
+/*
+ * add_local_ternary_reloption
+ * Add a new ternary local reloption
+ *
+ * 'offset' is offset of ternary-typed field.
+ */
+void
+add_local_ternary_reloption(local_relopts *relopts, const char *name,
+ const char *desc, ternary default_val,
+ int offset)
+{
+ relopt_ternary *newoption = init_ternary_reloption(RELOPT_KIND_LOCAL,
+ name, desc,
+ default_val, 0);
+
+ add_local_reloption(relopts, (relopt_gen *) newoption, offset);
+}
/*
* init_real_reloption
@@ -1617,6 +1689,18 @@ parse_one_reloption(relopt_value *option, char *text_str, int text_len,
option->gen->name, value)));
}
break;
+ case RELOPT_TYPE_TERNARY:
+ {
+ bool b;
+ parsed = parse_bool(value, &b);
+ option->values.ternary_val = b ? TERNARY_TRUE : TERNARY_FALSE;
+ if (validate && !parsed)
+ ereport(ERROR,
+ (errcode(ERRCODE_INVALID_PARAMETER_VALUE),
+ errmsg("invalid value for ternary option \"%s\": %s",
+ option->gen->name, value)));
+ }
+ break;
case RELOPT_TYPE_INT:
{
relopt_int *optint = (relopt_int *) option->gen;
@@ -1780,17 +1864,6 @@ fillRelOptions(void *rdopts, Size basesize,
char *itempos = ((char *) rdopts) + elems[j].offset;
char *string_val;
- /*
- * If isset_offset is provided, store whether the reloption is
- * set there.
- */
- if (elems[j].isset_offset > 0)
- {
- char *setpos = ((char *) rdopts) + elems[j].isset_offset;
-
- *(bool *) setpos = options[i].isset;
- }
-
switch (options[i].gen->type)
{
case RELOPT_TYPE_BOOL:
@@ -1798,6 +1871,11 @@ fillRelOptions(void *rdopts, Size basesize,
options[i].values.bool_val :
((relopt_bool *) options[i].gen)->default_val;
break;
+ case RELOPT_TYPE_TERNARY:
+ *(ternary *) itempos = options[i].isset ?
+ options[i].values.ternary_val :
+ ((relopt_ternary *) options[i].gen)->default_val;
+ break;
case RELOPT_TYPE_INT:
*(int *) itempos = options[i].isset ?
options[i].values.int_val :
@@ -1912,8 +1990,8 @@ default_reloptions(Datum reloptions, bool validate, relopt_kind kind)
offsetof(StdRdOptions, parallel_workers)},
{"vacuum_index_cleanup", RELOPT_TYPE_ENUM,
offsetof(StdRdOptions, vacuum_index_cleanup)},
- {"vacuum_truncate", RELOPT_TYPE_BOOL,
- offsetof(StdRdOptions, vacuum_truncate), offsetof(StdRdOptions, vacuum_truncate_set)},
+ {"vacuum_truncate", RELOPT_TYPE_TERNARY,
+ offsetof(StdRdOptions, vacuum_truncate)},
{"vacuum_max_eager_freeze_failure_rate", RELOPT_TYPE_REAL,
offsetof(StdRdOptions, vacuum_max_eager_freeze_failure_rate)}
};
@@ -1993,7 +2071,6 @@ build_local_reloptions(local_relopts *relopts, Datum options, bool validate)
elems[i].optname = opt->option->name;
elems[i].opttype = opt->option->type;
elems[i].offset = opt->offset;
- elems[i].isset_offset = 0; /* not supported for local relopts yet */
i++;
}
diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c
index 733ef40ae7..977babff54 100644
--- a/src/backend/commands/vacuum.c
+++ b/src/backend/commands/vacuum.c
@@ -2221,9 +2221,9 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams params,
{
StdRdOptions *opts = (StdRdOptions *) rel->rd_options;
- if (opts && opts->vacuum_truncate_set)
+ if (opts && opts->vacuum_truncate != TERNARY_UNSET)
{
- if (opts->vacuum_truncate)
+ if (opts->vacuum_truncate == TERNARY_TRUE)
params.truncate = VACOPTVALUE_ENABLED;
else
params.truncate = VACOPTVALUE_DISABLED;
diff --git a/src/include/access/reloptions.h b/src/include/access/reloptions.h
index a604a4702c..a436697658 100644
--- a/src/include/access/reloptions.h
+++ b/src/include/access/reloptions.h
@@ -29,6 +29,7 @@
typedef enum relopt_type
{
RELOPT_TYPE_BOOL,
+ RELOPT_TYPE_TERNARY, /* on, off, unset */
RELOPT_TYPE_INT,
RELOPT_TYPE_REAL,
RELOPT_TYPE_ENUM,
@@ -80,6 +81,7 @@ typedef struct relopt_value
union
{
bool bool_val;
+ ternary ternary_val;
int int_val;
double real_val;
int enum_val;
@@ -94,6 +96,12 @@ typedef struct relopt_bool
bool default_val;
} relopt_bool;
+typedef struct relopt_rernary
+{
+ relopt_gen gen;
+ int default_val;
+} relopt_ternary;
+
typedef struct relopt_int
{
relopt_gen gen;
@@ -152,19 +160,6 @@ typedef struct
const char *optname; /* option's name */
relopt_type opttype; /* option's datatype */
int offset; /* offset of field in result struct */
-
- /*
- * isset_offset is an optional offset of a field in the result struct that
- * stores whether the option is explicitly set for the relation or if it
- * just picked up the default value. In most cases, this can be
- * accomplished by giving the reloption a special out-of-range default
- * value (e.g., some integer reloptions use -2), but this isn't always
- * possible. For example, a Boolean reloption cannot be given an
- * out-of-range default, so we need another way to discover the source of
- * its value. This offset is only used if given a value greater than
- * zero.
- */
- int isset_offset;
} relopt_parse_elt;
/* Local reloption definition */
@@ -195,6 +190,8 @@ typedef struct local_relopts
extern relopt_kind add_reloption_kind(void);
extern void add_bool_reloption(bits32 kinds, const char *name, const char *desc,
bool default_val, LOCKMODE lockmode);
+extern void add_ternary_reloption(bits32 kinds, const char *name,
+ const char *desc, int default_val, LOCKMODE lockmode);
extern void add_int_reloption(bits32 kinds, const char *name, const char *desc,
int default_val, int min_val, int max_val,
LOCKMODE lockmode);
@@ -214,6 +211,9 @@ extern void register_reloptions_validator(local_relopts *relopts,
extern void add_local_bool_reloption(local_relopts *relopts, const char *name,
const char *desc, bool default_val,
int offset);
+extern void add_local_ternary_reloption(local_relopts *relopts,
+ const char *name, const char *desc,
+ ternary default_val, int offset);
extern void add_local_int_reloption(local_relopts *relopts, const char *name,
const char *desc, int default_val,
int min_val, int max_val, int offset);
diff --git a/src/include/c.h b/src/include/c.h
index 39022f8a9d..9c59ccccf8 100644
--- a/src/include/c.h
+++ b/src/include/c.h
@@ -488,6 +488,22 @@ typedef void (*pg_funcptr_t) (void);
#include <stdbool.h>
+/*
+ * ternary
+ * Boolean value with an extrea "unset" option
+ *
+ * Ternary data type is used in relation options that can be "true", "false" or
+ * "unset". Since relation options are used deep inside the PostgreSQL code,
+ * this type is declared globally.
+*/
+
+typedef enum ternary
+{
+ TERNARY_FALSE = 0,
+ TERNARY_TRUE = 1,
+ TERNARY_UNSET = -1
+} ternary;
+
/* ----------------------------------------------------------------
* Section 3: standard system types
diff --git a/src/include/utils/rel.h b/src/include/utils/rel.h
index b552359915..7346d618f9 100644
--- a/src/include/utils/rel.h
+++ b/src/include/utils/rel.h
@@ -346,8 +346,7 @@ typedef struct StdRdOptions
bool user_catalog_table; /* use as an additional catalog relation */
int parallel_workers; /* max number of parallel workers */
StdRdOptIndexCleanup vacuum_index_cleanup; /* controls index vacuuming */
- bool vacuum_truncate; /* enables vacuum to truncate a relation */
- bool vacuum_truncate_set; /* whether vacuum_truncate is set */
+ ternary vacuum_truncate; /* enables vacuum to truncate a relation */
/*
* Fraction of pages in a relation that vacuum can eagerly scan and fail
--
2.39.2
--nextPart4155600.3ZeAukHxDK
Content-Disposition: attachment;
filename="v2a-0003-Add-alias-to-be-used-as-unset-state.patch"
Content-Transfer-Encoding: 7Bit
Content-Type: text/x-patch; charset="unicode-2-0-utf-8";
name="v2a-0003-Add-alias-to-be-used-as-unset-state.patch"
^ permalink raw reply [nested|flat] 868+ messages in thread
* [PATCH v2a 2/4] Introduce ternary reloptions
@ 2025-09-04 14:41 Nikolay Shaplov <[email protected]>
0 siblings, 0 replies; 868+ messages in thread
From: Nikolay Shaplov @ 2025-09-04 14:41 UTC (permalink / raw)
Introduce ternary reloption as a replacement for current `vacuum_truncate`
implementation. Remove `vacuum_truncate_set` additional flag and using
`TERNARY_UNSET` value instead.
---
src/backend/access/common/reloptions.c | 129 ++++++++++++++++++++-----
src/backend/commands/vacuum.c | 4 +-
src/include/access/reloptions.h | 26 ++---
src/include/c.h | 16 +++
src/include/utils/rel.h | 3 +-
5 files changed, 135 insertions(+), 43 deletions(-)
diff --git a/src/backend/access/common/reloptions.c b/src/backend/access/common/reloptions.c
index 0af3fea68f..f543f11001 100644
--- a/src/backend/access/common/reloptions.c
+++ b/src/backend/access/common/reloptions.c
@@ -40,9 +40,9 @@
*
* To add an option:
*
- * (i) decide on a type (bool, integer, real, enum, string), name, default
- * value, upper and lower bounds (if applicable); for strings, consider a
- * validation routine.
+ * (i) decide on a type (bool, ternary, integer, real, enum, string), name,
+ * default value, upper and lower bounds (if applicable); for strings,
+ * consider a validation routine.
* (ii) add a record below (or use add_<type>_reloption).
* (iii) add it to the appropriate options struct (perhaps StdRdOptions)
* (iv) add it to the appropriate handling routine (perhaps
@@ -147,15 +147,6 @@ static relopt_bool boolRelOpts[] =
},
false
},
- {
- {
- "vacuum_truncate",
- "Enables vacuum to truncate empty pages at the end of this table",
- RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
- ShareUpdateExclusiveLock
- },
- true
- },
{
{
"deduplicate_items",
@@ -170,6 +161,21 @@ static relopt_bool boolRelOpts[] =
{{NULL}}
};
+static relopt_ternary ternaryRelOpts[] =
+{
+ {
+ {
+ "vacuum_truncate",
+ "Enables vacuum to truncate empty pages at the end of this table",
+ RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
+ ShareUpdateExclusiveLock
+ },
+ TERNARY_UNSET
+ },
+ /* list terminator */
+ {{NULL}}
+};
+
static relopt_int intRelOpts[] =
{
{
@@ -600,6 +606,13 @@ initialize_reloptions(void)
boolRelOpts[i].gen.lockmode));
j++;
}
+ for (i = 0; ternaryRelOpts[i].gen.name; i++)
+ {
+ Assert(DoLockModesConflict(ternaryRelOpts[i].gen.lockmode,
+ ternaryRelOpts[i].gen.lockmode));
+ j++;
+ }
+
for (i = 0; intRelOpts[i].gen.name; i++)
{
Assert(DoLockModesConflict(intRelOpts[i].gen.lockmode,
@@ -640,6 +653,14 @@ initialize_reloptions(void)
j++;
}
+ for (i = 0; ternaryRelOpts[i].gen.name; i++)
+ {
+ relOpts[j] = &ternaryRelOpts[i].gen;
+ relOpts[j]->type = RELOPT_TYPE_TERNARY;
+ relOpts[j]->namelen = strlen(relOpts[j]->name);
+ j++;
+ }
+
for (i = 0; intRelOpts[i].gen.name; i++)
{
relOpts[j] = &intRelOpts[i].gen;
@@ -800,6 +821,9 @@ allocate_reloption(bits32 kinds, int type, const char *name, const char *desc,
case RELOPT_TYPE_BOOL:
size = sizeof(relopt_bool);
break;
+ case RELOPT_TYPE_TERNARY:
+ size = sizeof(relopt_ternary);
+ break;
case RELOPT_TYPE_INT:
size = sizeof(relopt_int);
break;
@@ -883,6 +907,54 @@ add_local_bool_reloption(local_relopts *relopts, const char *name,
add_local_reloption(relopts, (relopt_gen *) newoption, offset);
}
+/*
+ * init_ternary_reloption
+ * Allocate and initialize a new ternary reloption
+ */
+static relopt_ternary *
+init_ternary_reloption(bits32 kinds, const char *name, const char *desc,
+ ternary default_val, LOCKMODE lockmode)
+{
+ relopt_ternary *newoption;
+
+ newoption = (relopt_ternary *) allocate_reloption(kinds,
+ RELOPT_TYPE_TERNARY, name, desc, lockmode);
+ newoption->default_val = default_val;
+
+ return newoption;
+}
+
+/*
+ * add_ternary_reloption
+ * Add a new ternary reloption
+ */
+void
+add_ternary_reloption(bits32 kinds, const char *name, const char *desc,
+ ternary default_val, LOCKMODE lockmode)
+{
+ relopt_ternary *newoption = init_ternary_reloption(kinds, name, desc,
+ default_val, lockmode);
+
+ add_reloption((relopt_gen *) newoption);
+}
+
+/*
+ * add_local_ternary_reloption
+ * Add a new ternary local reloption
+ *
+ * 'offset' is offset of ternary-typed field.
+ */
+void
+add_local_ternary_reloption(local_relopts *relopts, const char *name,
+ const char *desc, ternary default_val,
+ int offset)
+{
+ relopt_ternary *newoption = init_ternary_reloption(RELOPT_KIND_LOCAL,
+ name, desc,
+ default_val, 0);
+
+ add_local_reloption(relopts, (relopt_gen *) newoption, offset);
+}
/*
* init_real_reloption
@@ -1617,6 +1689,18 @@ parse_one_reloption(relopt_value *option, char *text_str, int text_len,
option->gen->name, value)));
}
break;
+ case RELOPT_TYPE_TERNARY:
+ {
+ bool b;
+ parsed = parse_bool(value, &b);
+ option->values.ternary_val = b ? TERNARY_TRUE : TERNARY_FALSE;
+ if (validate && !parsed)
+ ereport(ERROR,
+ (errcode(ERRCODE_INVALID_PARAMETER_VALUE),
+ errmsg("invalid value for ternary option \"%s\": %s",
+ option->gen->name, value)));
+ }
+ break;
case RELOPT_TYPE_INT:
{
relopt_int *optint = (relopt_int *) option->gen;
@@ -1780,17 +1864,6 @@ fillRelOptions(void *rdopts, Size basesize,
char *itempos = ((char *) rdopts) + elems[j].offset;
char *string_val;
- /*
- * If isset_offset is provided, store whether the reloption is
- * set there.
- */
- if (elems[j].isset_offset > 0)
- {
- char *setpos = ((char *) rdopts) + elems[j].isset_offset;
-
- *(bool *) setpos = options[i].isset;
- }
-
switch (options[i].gen->type)
{
case RELOPT_TYPE_BOOL:
@@ -1798,6 +1871,11 @@ fillRelOptions(void *rdopts, Size basesize,
options[i].values.bool_val :
((relopt_bool *) options[i].gen)->default_val;
break;
+ case RELOPT_TYPE_TERNARY:
+ *(ternary *) itempos = options[i].isset ?
+ options[i].values.ternary_val :
+ ((relopt_ternary *) options[i].gen)->default_val;
+ break;
case RELOPT_TYPE_INT:
*(int *) itempos = options[i].isset ?
options[i].values.int_val :
@@ -1912,8 +1990,8 @@ default_reloptions(Datum reloptions, bool validate, relopt_kind kind)
offsetof(StdRdOptions, parallel_workers)},
{"vacuum_index_cleanup", RELOPT_TYPE_ENUM,
offsetof(StdRdOptions, vacuum_index_cleanup)},
- {"vacuum_truncate", RELOPT_TYPE_BOOL,
- offsetof(StdRdOptions, vacuum_truncate), offsetof(StdRdOptions, vacuum_truncate_set)},
+ {"vacuum_truncate", RELOPT_TYPE_TERNARY,
+ offsetof(StdRdOptions, vacuum_truncate)},
{"vacuum_max_eager_freeze_failure_rate", RELOPT_TYPE_REAL,
offsetof(StdRdOptions, vacuum_max_eager_freeze_failure_rate)}
};
@@ -1993,7 +2071,6 @@ build_local_reloptions(local_relopts *relopts, Datum options, bool validate)
elems[i].optname = opt->option->name;
elems[i].opttype = opt->option->type;
elems[i].offset = opt->offset;
- elems[i].isset_offset = 0; /* not supported for local relopts yet */
i++;
}
diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c
index 733ef40ae7..977babff54 100644
--- a/src/backend/commands/vacuum.c
+++ b/src/backend/commands/vacuum.c
@@ -2221,9 +2221,9 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams params,
{
StdRdOptions *opts = (StdRdOptions *) rel->rd_options;
- if (opts && opts->vacuum_truncate_set)
+ if (opts && opts->vacuum_truncate != TERNARY_UNSET)
{
- if (opts->vacuum_truncate)
+ if (opts->vacuum_truncate == TERNARY_TRUE)
params.truncate = VACOPTVALUE_ENABLED;
else
params.truncate = VACOPTVALUE_DISABLED;
diff --git a/src/include/access/reloptions.h b/src/include/access/reloptions.h
index a604a4702c..a436697658 100644
--- a/src/include/access/reloptions.h
+++ b/src/include/access/reloptions.h
@@ -29,6 +29,7 @@
typedef enum relopt_type
{
RELOPT_TYPE_BOOL,
+ RELOPT_TYPE_TERNARY, /* on, off, unset */
RELOPT_TYPE_INT,
RELOPT_TYPE_REAL,
RELOPT_TYPE_ENUM,
@@ -80,6 +81,7 @@ typedef struct relopt_value
union
{
bool bool_val;
+ ternary ternary_val;
int int_val;
double real_val;
int enum_val;
@@ -94,6 +96,12 @@ typedef struct relopt_bool
bool default_val;
} relopt_bool;
+typedef struct relopt_rernary
+{
+ relopt_gen gen;
+ int default_val;
+} relopt_ternary;
+
typedef struct relopt_int
{
relopt_gen gen;
@@ -152,19 +160,6 @@ typedef struct
const char *optname; /* option's name */
relopt_type opttype; /* option's datatype */
int offset; /* offset of field in result struct */
-
- /*
- * isset_offset is an optional offset of a field in the result struct that
- * stores whether the option is explicitly set for the relation or if it
- * just picked up the default value. In most cases, this can be
- * accomplished by giving the reloption a special out-of-range default
- * value (e.g., some integer reloptions use -2), but this isn't always
- * possible. For example, a Boolean reloption cannot be given an
- * out-of-range default, so we need another way to discover the source of
- * its value. This offset is only used if given a value greater than
- * zero.
- */
- int isset_offset;
} relopt_parse_elt;
/* Local reloption definition */
@@ -195,6 +190,8 @@ typedef struct local_relopts
extern relopt_kind add_reloption_kind(void);
extern void add_bool_reloption(bits32 kinds, const char *name, const char *desc,
bool default_val, LOCKMODE lockmode);
+extern void add_ternary_reloption(bits32 kinds, const char *name,
+ const char *desc, int default_val, LOCKMODE lockmode);
extern void add_int_reloption(bits32 kinds, const char *name, const char *desc,
int default_val, int min_val, int max_val,
LOCKMODE lockmode);
@@ -214,6 +211,9 @@ extern void register_reloptions_validator(local_relopts *relopts,
extern void add_local_bool_reloption(local_relopts *relopts, const char *name,
const char *desc, bool default_val,
int offset);
+extern void add_local_ternary_reloption(local_relopts *relopts,
+ const char *name, const char *desc,
+ ternary default_val, int offset);
extern void add_local_int_reloption(local_relopts *relopts, const char *name,
const char *desc, int default_val,
int min_val, int max_val, int offset);
diff --git a/src/include/c.h b/src/include/c.h
index 39022f8a9d..9c59ccccf8 100644
--- a/src/include/c.h
+++ b/src/include/c.h
@@ -488,6 +488,22 @@ typedef void (*pg_funcptr_t) (void);
#include <stdbool.h>
+/*
+ * ternary
+ * Boolean value with an extrea "unset" option
+ *
+ * Ternary data type is used in relation options that can be "true", "false" or
+ * "unset". Since relation options are used deep inside the PostgreSQL code,
+ * this type is declared globally.
+*/
+
+typedef enum ternary
+{
+ TERNARY_FALSE = 0,
+ TERNARY_TRUE = 1,
+ TERNARY_UNSET = -1
+} ternary;
+
/* ----------------------------------------------------------------
* Section 3: standard system types
diff --git a/src/include/utils/rel.h b/src/include/utils/rel.h
index b552359915..7346d618f9 100644
--- a/src/include/utils/rel.h
+++ b/src/include/utils/rel.h
@@ -346,8 +346,7 @@ typedef struct StdRdOptions
bool user_catalog_table; /* use as an additional catalog relation */
int parallel_workers; /* max number of parallel workers */
StdRdOptIndexCleanup vacuum_index_cleanup; /* controls index vacuuming */
- bool vacuum_truncate; /* enables vacuum to truncate a relation */
- bool vacuum_truncate_set; /* whether vacuum_truncate is set */
+ ternary vacuum_truncate; /* enables vacuum to truncate a relation */
/*
* Fraction of pages in a relation that vacuum can eagerly scan and fail
--
2.39.2
--nextPart4155600.3ZeAukHxDK
Content-Disposition: attachment;
filename="v2a-0003-Add-alias-to-be-used-as-unset-state.patch"
Content-Transfer-Encoding: 7Bit
Content-Type: text/x-patch; charset="unicode-2-0-utf-8";
name="v2a-0003-Add-alias-to-be-used-as-unset-state.patch"
^ permalink raw reply [nested|flat] 868+ messages in thread
* [PATCH v2a 2/4] Introduce ternary reloptions
@ 2025-09-04 14:41 Nikolay Shaplov <[email protected]>
0 siblings, 0 replies; 868+ messages in thread
From: Nikolay Shaplov @ 2025-09-04 14:41 UTC (permalink / raw)
Introduce ternary reloption as a replacement for current `vacuum_truncate`
implementation. Remove `vacuum_truncate_set` additional flag and using
`TERNARY_UNSET` value instead.
---
src/backend/access/common/reloptions.c | 129 ++++++++++++++++++++-----
src/backend/commands/vacuum.c | 4 +-
src/include/access/reloptions.h | 26 ++---
src/include/c.h | 16 +++
src/include/utils/rel.h | 3 +-
5 files changed, 135 insertions(+), 43 deletions(-)
diff --git a/src/backend/access/common/reloptions.c b/src/backend/access/common/reloptions.c
index 0af3fea68f..f543f11001 100644
--- a/src/backend/access/common/reloptions.c
+++ b/src/backend/access/common/reloptions.c
@@ -40,9 +40,9 @@
*
* To add an option:
*
- * (i) decide on a type (bool, integer, real, enum, string), name, default
- * value, upper and lower bounds (if applicable); for strings, consider a
- * validation routine.
+ * (i) decide on a type (bool, ternary, integer, real, enum, string), name,
+ * default value, upper and lower bounds (if applicable); for strings,
+ * consider a validation routine.
* (ii) add a record below (or use add_<type>_reloption).
* (iii) add it to the appropriate options struct (perhaps StdRdOptions)
* (iv) add it to the appropriate handling routine (perhaps
@@ -147,15 +147,6 @@ static relopt_bool boolRelOpts[] =
},
false
},
- {
- {
- "vacuum_truncate",
- "Enables vacuum to truncate empty pages at the end of this table",
- RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
- ShareUpdateExclusiveLock
- },
- true
- },
{
{
"deduplicate_items",
@@ -170,6 +161,21 @@ static relopt_bool boolRelOpts[] =
{{NULL}}
};
+static relopt_ternary ternaryRelOpts[] =
+{
+ {
+ {
+ "vacuum_truncate",
+ "Enables vacuum to truncate empty pages at the end of this table",
+ RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
+ ShareUpdateExclusiveLock
+ },
+ TERNARY_UNSET
+ },
+ /* list terminator */
+ {{NULL}}
+};
+
static relopt_int intRelOpts[] =
{
{
@@ -600,6 +606,13 @@ initialize_reloptions(void)
boolRelOpts[i].gen.lockmode));
j++;
}
+ for (i = 0; ternaryRelOpts[i].gen.name; i++)
+ {
+ Assert(DoLockModesConflict(ternaryRelOpts[i].gen.lockmode,
+ ternaryRelOpts[i].gen.lockmode));
+ j++;
+ }
+
for (i = 0; intRelOpts[i].gen.name; i++)
{
Assert(DoLockModesConflict(intRelOpts[i].gen.lockmode,
@@ -640,6 +653,14 @@ initialize_reloptions(void)
j++;
}
+ for (i = 0; ternaryRelOpts[i].gen.name; i++)
+ {
+ relOpts[j] = &ternaryRelOpts[i].gen;
+ relOpts[j]->type = RELOPT_TYPE_TERNARY;
+ relOpts[j]->namelen = strlen(relOpts[j]->name);
+ j++;
+ }
+
for (i = 0; intRelOpts[i].gen.name; i++)
{
relOpts[j] = &intRelOpts[i].gen;
@@ -800,6 +821,9 @@ allocate_reloption(bits32 kinds, int type, const char *name, const char *desc,
case RELOPT_TYPE_BOOL:
size = sizeof(relopt_bool);
break;
+ case RELOPT_TYPE_TERNARY:
+ size = sizeof(relopt_ternary);
+ break;
case RELOPT_TYPE_INT:
size = sizeof(relopt_int);
break;
@@ -883,6 +907,54 @@ add_local_bool_reloption(local_relopts *relopts, const char *name,
add_local_reloption(relopts, (relopt_gen *) newoption, offset);
}
+/*
+ * init_ternary_reloption
+ * Allocate and initialize a new ternary reloption
+ */
+static relopt_ternary *
+init_ternary_reloption(bits32 kinds, const char *name, const char *desc,
+ ternary default_val, LOCKMODE lockmode)
+{
+ relopt_ternary *newoption;
+
+ newoption = (relopt_ternary *) allocate_reloption(kinds,
+ RELOPT_TYPE_TERNARY, name, desc, lockmode);
+ newoption->default_val = default_val;
+
+ return newoption;
+}
+
+/*
+ * add_ternary_reloption
+ * Add a new ternary reloption
+ */
+void
+add_ternary_reloption(bits32 kinds, const char *name, const char *desc,
+ ternary default_val, LOCKMODE lockmode)
+{
+ relopt_ternary *newoption = init_ternary_reloption(kinds, name, desc,
+ default_val, lockmode);
+
+ add_reloption((relopt_gen *) newoption);
+}
+
+/*
+ * add_local_ternary_reloption
+ * Add a new ternary local reloption
+ *
+ * 'offset' is offset of ternary-typed field.
+ */
+void
+add_local_ternary_reloption(local_relopts *relopts, const char *name,
+ const char *desc, ternary default_val,
+ int offset)
+{
+ relopt_ternary *newoption = init_ternary_reloption(RELOPT_KIND_LOCAL,
+ name, desc,
+ default_val, 0);
+
+ add_local_reloption(relopts, (relopt_gen *) newoption, offset);
+}
/*
* init_real_reloption
@@ -1617,6 +1689,18 @@ parse_one_reloption(relopt_value *option, char *text_str, int text_len,
option->gen->name, value)));
}
break;
+ case RELOPT_TYPE_TERNARY:
+ {
+ bool b;
+ parsed = parse_bool(value, &b);
+ option->values.ternary_val = b ? TERNARY_TRUE : TERNARY_FALSE;
+ if (validate && !parsed)
+ ereport(ERROR,
+ (errcode(ERRCODE_INVALID_PARAMETER_VALUE),
+ errmsg("invalid value for ternary option \"%s\": %s",
+ option->gen->name, value)));
+ }
+ break;
case RELOPT_TYPE_INT:
{
relopt_int *optint = (relopt_int *) option->gen;
@@ -1780,17 +1864,6 @@ fillRelOptions(void *rdopts, Size basesize,
char *itempos = ((char *) rdopts) + elems[j].offset;
char *string_val;
- /*
- * If isset_offset is provided, store whether the reloption is
- * set there.
- */
- if (elems[j].isset_offset > 0)
- {
- char *setpos = ((char *) rdopts) + elems[j].isset_offset;
-
- *(bool *) setpos = options[i].isset;
- }
-
switch (options[i].gen->type)
{
case RELOPT_TYPE_BOOL:
@@ -1798,6 +1871,11 @@ fillRelOptions(void *rdopts, Size basesize,
options[i].values.bool_val :
((relopt_bool *) options[i].gen)->default_val;
break;
+ case RELOPT_TYPE_TERNARY:
+ *(ternary *) itempos = options[i].isset ?
+ options[i].values.ternary_val :
+ ((relopt_ternary *) options[i].gen)->default_val;
+ break;
case RELOPT_TYPE_INT:
*(int *) itempos = options[i].isset ?
options[i].values.int_val :
@@ -1912,8 +1990,8 @@ default_reloptions(Datum reloptions, bool validate, relopt_kind kind)
offsetof(StdRdOptions, parallel_workers)},
{"vacuum_index_cleanup", RELOPT_TYPE_ENUM,
offsetof(StdRdOptions, vacuum_index_cleanup)},
- {"vacuum_truncate", RELOPT_TYPE_BOOL,
- offsetof(StdRdOptions, vacuum_truncate), offsetof(StdRdOptions, vacuum_truncate_set)},
+ {"vacuum_truncate", RELOPT_TYPE_TERNARY,
+ offsetof(StdRdOptions, vacuum_truncate)},
{"vacuum_max_eager_freeze_failure_rate", RELOPT_TYPE_REAL,
offsetof(StdRdOptions, vacuum_max_eager_freeze_failure_rate)}
};
@@ -1993,7 +2071,6 @@ build_local_reloptions(local_relopts *relopts, Datum options, bool validate)
elems[i].optname = opt->option->name;
elems[i].opttype = opt->option->type;
elems[i].offset = opt->offset;
- elems[i].isset_offset = 0; /* not supported for local relopts yet */
i++;
}
diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c
index 733ef40ae7..977babff54 100644
--- a/src/backend/commands/vacuum.c
+++ b/src/backend/commands/vacuum.c
@@ -2221,9 +2221,9 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams params,
{
StdRdOptions *opts = (StdRdOptions *) rel->rd_options;
- if (opts && opts->vacuum_truncate_set)
+ if (opts && opts->vacuum_truncate != TERNARY_UNSET)
{
- if (opts->vacuum_truncate)
+ if (opts->vacuum_truncate == TERNARY_TRUE)
params.truncate = VACOPTVALUE_ENABLED;
else
params.truncate = VACOPTVALUE_DISABLED;
diff --git a/src/include/access/reloptions.h b/src/include/access/reloptions.h
index a604a4702c..a436697658 100644
--- a/src/include/access/reloptions.h
+++ b/src/include/access/reloptions.h
@@ -29,6 +29,7 @@
typedef enum relopt_type
{
RELOPT_TYPE_BOOL,
+ RELOPT_TYPE_TERNARY, /* on, off, unset */
RELOPT_TYPE_INT,
RELOPT_TYPE_REAL,
RELOPT_TYPE_ENUM,
@@ -80,6 +81,7 @@ typedef struct relopt_value
union
{
bool bool_val;
+ ternary ternary_val;
int int_val;
double real_val;
int enum_val;
@@ -94,6 +96,12 @@ typedef struct relopt_bool
bool default_val;
} relopt_bool;
+typedef struct relopt_rernary
+{
+ relopt_gen gen;
+ int default_val;
+} relopt_ternary;
+
typedef struct relopt_int
{
relopt_gen gen;
@@ -152,19 +160,6 @@ typedef struct
const char *optname; /* option's name */
relopt_type opttype; /* option's datatype */
int offset; /* offset of field in result struct */
-
- /*
- * isset_offset is an optional offset of a field in the result struct that
- * stores whether the option is explicitly set for the relation or if it
- * just picked up the default value. In most cases, this can be
- * accomplished by giving the reloption a special out-of-range default
- * value (e.g., some integer reloptions use -2), but this isn't always
- * possible. For example, a Boolean reloption cannot be given an
- * out-of-range default, so we need another way to discover the source of
- * its value. This offset is only used if given a value greater than
- * zero.
- */
- int isset_offset;
} relopt_parse_elt;
/* Local reloption definition */
@@ -195,6 +190,8 @@ typedef struct local_relopts
extern relopt_kind add_reloption_kind(void);
extern void add_bool_reloption(bits32 kinds, const char *name, const char *desc,
bool default_val, LOCKMODE lockmode);
+extern void add_ternary_reloption(bits32 kinds, const char *name,
+ const char *desc, int default_val, LOCKMODE lockmode);
extern void add_int_reloption(bits32 kinds, const char *name, const char *desc,
int default_val, int min_val, int max_val,
LOCKMODE lockmode);
@@ -214,6 +211,9 @@ extern void register_reloptions_validator(local_relopts *relopts,
extern void add_local_bool_reloption(local_relopts *relopts, const char *name,
const char *desc, bool default_val,
int offset);
+extern void add_local_ternary_reloption(local_relopts *relopts,
+ const char *name, const char *desc,
+ ternary default_val, int offset);
extern void add_local_int_reloption(local_relopts *relopts, const char *name,
const char *desc, int default_val,
int min_val, int max_val, int offset);
diff --git a/src/include/c.h b/src/include/c.h
index 39022f8a9d..9c59ccccf8 100644
--- a/src/include/c.h
+++ b/src/include/c.h
@@ -488,6 +488,22 @@ typedef void (*pg_funcptr_t) (void);
#include <stdbool.h>
+/*
+ * ternary
+ * Boolean value with an extrea "unset" option
+ *
+ * Ternary data type is used in relation options that can be "true", "false" or
+ * "unset". Since relation options are used deep inside the PostgreSQL code,
+ * this type is declared globally.
+*/
+
+typedef enum ternary
+{
+ TERNARY_FALSE = 0,
+ TERNARY_TRUE = 1,
+ TERNARY_UNSET = -1
+} ternary;
+
/* ----------------------------------------------------------------
* Section 3: standard system types
diff --git a/src/include/utils/rel.h b/src/include/utils/rel.h
index b552359915..7346d618f9 100644
--- a/src/include/utils/rel.h
+++ b/src/include/utils/rel.h
@@ -346,8 +346,7 @@ typedef struct StdRdOptions
bool user_catalog_table; /* use as an additional catalog relation */
int parallel_workers; /* max number of parallel workers */
StdRdOptIndexCleanup vacuum_index_cleanup; /* controls index vacuuming */
- bool vacuum_truncate; /* enables vacuum to truncate a relation */
- bool vacuum_truncate_set; /* whether vacuum_truncate is set */
+ ternary vacuum_truncate; /* enables vacuum to truncate a relation */
/*
* Fraction of pages in a relation that vacuum can eagerly scan and fail
--
2.39.2
--nextPart4155600.3ZeAukHxDK
Content-Disposition: attachment;
filename="v2a-0003-Add-alias-to-be-used-as-unset-state.patch"
Content-Transfer-Encoding: 7Bit
Content-Type: text/x-patch; charset="unicode-2-0-utf-8";
name="v2a-0003-Add-alias-to-be-used-as-unset-state.patch"
^ permalink raw reply [nested|flat] 868+ messages in thread
* [PATCH v2 2/4] Introduce ternary reloptions
@ 2025-09-04 14:41 Nikolay Shaplov <[email protected]>
0 siblings, 0 replies; 868+ messages in thread
From: Nikolay Shaplov @ 2025-09-04 14:41 UTC (permalink / raw)
Introduce ternary reloption as a replacement for current `vacuum_truncate`
implementation. Remove `vacuum_truncate_set` additional flag and using
`TERNARY_UNSET` value instead.
---
src/backend/access/common/reloptions.c | 129 ++++++++++++++++++++-----
src/backend/commands/vacuum.c | 4 +-
src/include/access/reloptions.h | 26 ++---
src/include/c.h | 16 +++
src/include/utils/rel.h | 3 +-
5 files changed, 135 insertions(+), 43 deletions(-)
diff --git a/src/backend/access/common/reloptions.c b/src/backend/access/common/reloptions.c
index 0af3fea68f..f543f11001 100644
--- a/src/backend/access/common/reloptions.c
+++ b/src/backend/access/common/reloptions.c
@@ -40,9 +40,9 @@
*
* To add an option:
*
- * (i) decide on a type (bool, integer, real, enum, string), name, default
- * value, upper and lower bounds (if applicable); for strings, consider a
- * validation routine.
+ * (i) decide on a type (bool, ternary, integer, real, enum, string), name,
+ * default value, upper and lower bounds (if applicable); for strings,
+ * consider a validation routine.
* (ii) add a record below (or use add_<type>_reloption).
* (iii) add it to the appropriate options struct (perhaps StdRdOptions)
* (iv) add it to the appropriate handling routine (perhaps
@@ -147,15 +147,6 @@ static relopt_bool boolRelOpts[] =
},
false
},
- {
- {
- "vacuum_truncate",
- "Enables vacuum to truncate empty pages at the end of this table",
- RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
- ShareUpdateExclusiveLock
- },
- true
- },
{
{
"deduplicate_items",
@@ -170,6 +161,21 @@ static relopt_bool boolRelOpts[] =
{{NULL}}
};
+static relopt_ternary ternaryRelOpts[] =
+{
+ {
+ {
+ "vacuum_truncate",
+ "Enables vacuum to truncate empty pages at the end of this table",
+ RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
+ ShareUpdateExclusiveLock
+ },
+ TERNARY_UNSET
+ },
+ /* list terminator */
+ {{NULL}}
+};
+
static relopt_int intRelOpts[] =
{
{
@@ -600,6 +606,13 @@ initialize_reloptions(void)
boolRelOpts[i].gen.lockmode));
j++;
}
+ for (i = 0; ternaryRelOpts[i].gen.name; i++)
+ {
+ Assert(DoLockModesConflict(ternaryRelOpts[i].gen.lockmode,
+ ternaryRelOpts[i].gen.lockmode));
+ j++;
+ }
+
for (i = 0; intRelOpts[i].gen.name; i++)
{
Assert(DoLockModesConflict(intRelOpts[i].gen.lockmode,
@@ -640,6 +653,14 @@ initialize_reloptions(void)
j++;
}
+ for (i = 0; ternaryRelOpts[i].gen.name; i++)
+ {
+ relOpts[j] = &ternaryRelOpts[i].gen;
+ relOpts[j]->type = RELOPT_TYPE_TERNARY;
+ relOpts[j]->namelen = strlen(relOpts[j]->name);
+ j++;
+ }
+
for (i = 0; intRelOpts[i].gen.name; i++)
{
relOpts[j] = &intRelOpts[i].gen;
@@ -800,6 +821,9 @@ allocate_reloption(bits32 kinds, int type, const char *name, const char *desc,
case RELOPT_TYPE_BOOL:
size = sizeof(relopt_bool);
break;
+ case RELOPT_TYPE_TERNARY:
+ size = sizeof(relopt_ternary);
+ break;
case RELOPT_TYPE_INT:
size = sizeof(relopt_int);
break;
@@ -883,6 +907,54 @@ add_local_bool_reloption(local_relopts *relopts, const char *name,
add_local_reloption(relopts, (relopt_gen *) newoption, offset);
}
+/*
+ * init_ternary_reloption
+ * Allocate and initialize a new ternary reloption
+ */
+static relopt_ternary *
+init_ternary_reloption(bits32 kinds, const char *name, const char *desc,
+ ternary default_val, LOCKMODE lockmode)
+{
+ relopt_ternary *newoption;
+
+ newoption = (relopt_ternary *) allocate_reloption(kinds,
+ RELOPT_TYPE_TERNARY, name, desc, lockmode);
+ newoption->default_val = default_val;
+
+ return newoption;
+}
+
+/*
+ * add_ternary_reloption
+ * Add a new ternary reloption
+ */
+void
+add_ternary_reloption(bits32 kinds, const char *name, const char *desc,
+ ternary default_val, LOCKMODE lockmode)
+{
+ relopt_ternary *newoption = init_ternary_reloption(kinds, name, desc,
+ default_val, lockmode);
+
+ add_reloption((relopt_gen *) newoption);
+}
+
+/*
+ * add_local_ternary_reloption
+ * Add a new ternary local reloption
+ *
+ * 'offset' is offset of ternary-typed field.
+ */
+void
+add_local_ternary_reloption(local_relopts *relopts, const char *name,
+ const char *desc, ternary default_val,
+ int offset)
+{
+ relopt_ternary *newoption = init_ternary_reloption(RELOPT_KIND_LOCAL,
+ name, desc,
+ default_val, 0);
+
+ add_local_reloption(relopts, (relopt_gen *) newoption, offset);
+}
/*
* init_real_reloption
@@ -1617,6 +1689,18 @@ parse_one_reloption(relopt_value *option, char *text_str, int text_len,
option->gen->name, value)));
}
break;
+ case RELOPT_TYPE_TERNARY:
+ {
+ bool b;
+ parsed = parse_bool(value, &b);
+ option->values.ternary_val = b ? TERNARY_TRUE : TERNARY_FALSE;
+ if (validate && !parsed)
+ ereport(ERROR,
+ (errcode(ERRCODE_INVALID_PARAMETER_VALUE),
+ errmsg("invalid value for ternary option \"%s\": %s",
+ option->gen->name, value)));
+ }
+ break;
case RELOPT_TYPE_INT:
{
relopt_int *optint = (relopt_int *) option->gen;
@@ -1780,17 +1864,6 @@ fillRelOptions(void *rdopts, Size basesize,
char *itempos = ((char *) rdopts) + elems[j].offset;
char *string_val;
- /*
- * If isset_offset is provided, store whether the reloption is
- * set there.
- */
- if (elems[j].isset_offset > 0)
- {
- char *setpos = ((char *) rdopts) + elems[j].isset_offset;
-
- *(bool *) setpos = options[i].isset;
- }
-
switch (options[i].gen->type)
{
case RELOPT_TYPE_BOOL:
@@ -1798,6 +1871,11 @@ fillRelOptions(void *rdopts, Size basesize,
options[i].values.bool_val :
((relopt_bool *) options[i].gen)->default_val;
break;
+ case RELOPT_TYPE_TERNARY:
+ *(ternary *) itempos = options[i].isset ?
+ options[i].values.ternary_val :
+ ((relopt_ternary *) options[i].gen)->default_val;
+ break;
case RELOPT_TYPE_INT:
*(int *) itempos = options[i].isset ?
options[i].values.int_val :
@@ -1912,8 +1990,8 @@ default_reloptions(Datum reloptions, bool validate, relopt_kind kind)
offsetof(StdRdOptions, parallel_workers)},
{"vacuum_index_cleanup", RELOPT_TYPE_ENUM,
offsetof(StdRdOptions, vacuum_index_cleanup)},
- {"vacuum_truncate", RELOPT_TYPE_BOOL,
- offsetof(StdRdOptions, vacuum_truncate), offsetof(StdRdOptions, vacuum_truncate_set)},
+ {"vacuum_truncate", RELOPT_TYPE_TERNARY,
+ offsetof(StdRdOptions, vacuum_truncate)},
{"vacuum_max_eager_freeze_failure_rate", RELOPT_TYPE_REAL,
offsetof(StdRdOptions, vacuum_max_eager_freeze_failure_rate)}
};
@@ -1993,7 +2071,6 @@ build_local_reloptions(local_relopts *relopts, Datum options, bool validate)
elems[i].optname = opt->option->name;
elems[i].opttype = opt->option->type;
elems[i].offset = opt->offset;
- elems[i].isset_offset = 0; /* not supported for local relopts yet */
i++;
}
diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c
index 733ef40ae7..977babff54 100644
--- a/src/backend/commands/vacuum.c
+++ b/src/backend/commands/vacuum.c
@@ -2221,9 +2221,9 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams params,
{
StdRdOptions *opts = (StdRdOptions *) rel->rd_options;
- if (opts && opts->vacuum_truncate_set)
+ if (opts && opts->vacuum_truncate != TERNARY_UNSET)
{
- if (opts->vacuum_truncate)
+ if (opts->vacuum_truncate == TERNARY_TRUE)
params.truncate = VACOPTVALUE_ENABLED;
else
params.truncate = VACOPTVALUE_DISABLED;
diff --git a/src/include/access/reloptions.h b/src/include/access/reloptions.h
index a604a4702c..a436697658 100644
--- a/src/include/access/reloptions.h
+++ b/src/include/access/reloptions.h
@@ -29,6 +29,7 @@
typedef enum relopt_type
{
RELOPT_TYPE_BOOL,
+ RELOPT_TYPE_TERNARY, /* on, off, unset */
RELOPT_TYPE_INT,
RELOPT_TYPE_REAL,
RELOPT_TYPE_ENUM,
@@ -80,6 +81,7 @@ typedef struct relopt_value
union
{
bool bool_val;
+ ternary ternary_val;
int int_val;
double real_val;
int enum_val;
@@ -94,6 +96,12 @@ typedef struct relopt_bool
bool default_val;
} relopt_bool;
+typedef struct relopt_rernary
+{
+ relopt_gen gen;
+ int default_val;
+} relopt_ternary;
+
typedef struct relopt_int
{
relopt_gen gen;
@@ -152,19 +160,6 @@ typedef struct
const char *optname; /* option's name */
relopt_type opttype; /* option's datatype */
int offset; /* offset of field in result struct */
-
- /*
- * isset_offset is an optional offset of a field in the result struct that
- * stores whether the option is explicitly set for the relation or if it
- * just picked up the default value. In most cases, this can be
- * accomplished by giving the reloption a special out-of-range default
- * value (e.g., some integer reloptions use -2), but this isn't always
- * possible. For example, a Boolean reloption cannot be given an
- * out-of-range default, so we need another way to discover the source of
- * its value. This offset is only used if given a value greater than
- * zero.
- */
- int isset_offset;
} relopt_parse_elt;
/* Local reloption definition */
@@ -195,6 +190,8 @@ typedef struct local_relopts
extern relopt_kind add_reloption_kind(void);
extern void add_bool_reloption(bits32 kinds, const char *name, const char *desc,
bool default_val, LOCKMODE lockmode);
+extern void add_ternary_reloption(bits32 kinds, const char *name,
+ const char *desc, int default_val, LOCKMODE lockmode);
extern void add_int_reloption(bits32 kinds, const char *name, const char *desc,
int default_val, int min_val, int max_val,
LOCKMODE lockmode);
@@ -214,6 +211,9 @@ extern void register_reloptions_validator(local_relopts *relopts,
extern void add_local_bool_reloption(local_relopts *relopts, const char *name,
const char *desc, bool default_val,
int offset);
+extern void add_local_ternary_reloption(local_relopts *relopts,
+ const char *name, const char *desc,
+ ternary default_val, int offset);
extern void add_local_int_reloption(local_relopts *relopts, const char *name,
const char *desc, int default_val,
int min_val, int max_val, int offset);
diff --git a/src/include/c.h b/src/include/c.h
index 39022f8a9d..9c59ccccf8 100644
--- a/src/include/c.h
+++ b/src/include/c.h
@@ -488,6 +488,22 @@ typedef void (*pg_funcptr_t) (void);
#include <stdbool.h>
+/*
+ * ternary
+ * Boolean value with an extrea "unset" option
+ *
+ * Ternary data type is used in relation options that can be "true", "false" or
+ * "unset". Since relation options are used deep inside the PostgreSQL code,
+ * this type is declared globally.
+*/
+
+typedef enum ternary
+{
+ TERNARY_FALSE = 0,
+ TERNARY_TRUE = 1,
+ TERNARY_UNSET = -1
+} ternary;
+
/* ----------------------------------------------------------------
* Section 3: standard system types
diff --git a/src/include/utils/rel.h b/src/include/utils/rel.h
index b552359915..7346d618f9 100644
--- a/src/include/utils/rel.h
+++ b/src/include/utils/rel.h
@@ -346,8 +346,7 @@ typedef struct StdRdOptions
bool user_catalog_table; /* use as an additional catalog relation */
int parallel_workers; /* max number of parallel workers */
StdRdOptIndexCleanup vacuum_index_cleanup; /* controls index vacuuming */
- bool vacuum_truncate; /* enables vacuum to truncate a relation */
- bool vacuum_truncate_set; /* whether vacuum_truncate is set */
+ ternary vacuum_truncate; /* enables vacuum to truncate a relation */
/*
* Fraction of pages in a relation that vacuum can eagerly scan and fail
--
2.39.2
--nextPart5129932.5fSG56mABF
Content-Disposition: attachment;
filename="v2-0003-Add-alias-to-be-used-as-unset-state.patch"
Content-Transfer-Encoding: 7Bit
Content-Type: text/x-patch; charset="unicode-2-0-utf-8";
name="v2-0003-Add-alias-to-be-used-as-unset-state.patch"
^ permalink raw reply [nested|flat] 868+ messages in thread
* [PATCH v2a 2/4] Introduce ternary reloptions
@ 2025-09-04 14:41 Nikolay Shaplov <[email protected]>
0 siblings, 0 replies; 868+ messages in thread
From: Nikolay Shaplov @ 2025-09-04 14:41 UTC (permalink / raw)
Introduce ternary reloption as a replacement for current `vacuum_truncate`
implementation. Remove `vacuum_truncate_set` additional flag and using
`TERNARY_UNSET` value instead.
---
src/backend/access/common/reloptions.c | 129 ++++++++++++++++++++-----
src/backend/commands/vacuum.c | 4 +-
src/include/access/reloptions.h | 26 ++---
src/include/c.h | 16 +++
src/include/utils/rel.h | 3 +-
5 files changed, 135 insertions(+), 43 deletions(-)
diff --git a/src/backend/access/common/reloptions.c b/src/backend/access/common/reloptions.c
index 0af3fea68f..f543f11001 100644
--- a/src/backend/access/common/reloptions.c
+++ b/src/backend/access/common/reloptions.c
@@ -40,9 +40,9 @@
*
* To add an option:
*
- * (i) decide on a type (bool, integer, real, enum, string), name, default
- * value, upper and lower bounds (if applicable); for strings, consider a
- * validation routine.
+ * (i) decide on a type (bool, ternary, integer, real, enum, string), name,
+ * default value, upper and lower bounds (if applicable); for strings,
+ * consider a validation routine.
* (ii) add a record below (or use add_<type>_reloption).
* (iii) add it to the appropriate options struct (perhaps StdRdOptions)
* (iv) add it to the appropriate handling routine (perhaps
@@ -147,15 +147,6 @@ static relopt_bool boolRelOpts[] =
},
false
},
- {
- {
- "vacuum_truncate",
- "Enables vacuum to truncate empty pages at the end of this table",
- RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
- ShareUpdateExclusiveLock
- },
- true
- },
{
{
"deduplicate_items",
@@ -170,6 +161,21 @@ static relopt_bool boolRelOpts[] =
{{NULL}}
};
+static relopt_ternary ternaryRelOpts[] =
+{
+ {
+ {
+ "vacuum_truncate",
+ "Enables vacuum to truncate empty pages at the end of this table",
+ RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
+ ShareUpdateExclusiveLock
+ },
+ TERNARY_UNSET
+ },
+ /* list terminator */
+ {{NULL}}
+};
+
static relopt_int intRelOpts[] =
{
{
@@ -600,6 +606,13 @@ initialize_reloptions(void)
boolRelOpts[i].gen.lockmode));
j++;
}
+ for (i = 0; ternaryRelOpts[i].gen.name; i++)
+ {
+ Assert(DoLockModesConflict(ternaryRelOpts[i].gen.lockmode,
+ ternaryRelOpts[i].gen.lockmode));
+ j++;
+ }
+
for (i = 0; intRelOpts[i].gen.name; i++)
{
Assert(DoLockModesConflict(intRelOpts[i].gen.lockmode,
@@ -640,6 +653,14 @@ initialize_reloptions(void)
j++;
}
+ for (i = 0; ternaryRelOpts[i].gen.name; i++)
+ {
+ relOpts[j] = &ternaryRelOpts[i].gen;
+ relOpts[j]->type = RELOPT_TYPE_TERNARY;
+ relOpts[j]->namelen = strlen(relOpts[j]->name);
+ j++;
+ }
+
for (i = 0; intRelOpts[i].gen.name; i++)
{
relOpts[j] = &intRelOpts[i].gen;
@@ -800,6 +821,9 @@ allocate_reloption(bits32 kinds, int type, const char *name, const char *desc,
case RELOPT_TYPE_BOOL:
size = sizeof(relopt_bool);
break;
+ case RELOPT_TYPE_TERNARY:
+ size = sizeof(relopt_ternary);
+ break;
case RELOPT_TYPE_INT:
size = sizeof(relopt_int);
break;
@@ -883,6 +907,54 @@ add_local_bool_reloption(local_relopts *relopts, const char *name,
add_local_reloption(relopts, (relopt_gen *) newoption, offset);
}
+/*
+ * init_ternary_reloption
+ * Allocate and initialize a new ternary reloption
+ */
+static relopt_ternary *
+init_ternary_reloption(bits32 kinds, const char *name, const char *desc,
+ ternary default_val, LOCKMODE lockmode)
+{
+ relopt_ternary *newoption;
+
+ newoption = (relopt_ternary *) allocate_reloption(kinds,
+ RELOPT_TYPE_TERNARY, name, desc, lockmode);
+ newoption->default_val = default_val;
+
+ return newoption;
+}
+
+/*
+ * add_ternary_reloption
+ * Add a new ternary reloption
+ */
+void
+add_ternary_reloption(bits32 kinds, const char *name, const char *desc,
+ ternary default_val, LOCKMODE lockmode)
+{
+ relopt_ternary *newoption = init_ternary_reloption(kinds, name, desc,
+ default_val, lockmode);
+
+ add_reloption((relopt_gen *) newoption);
+}
+
+/*
+ * add_local_ternary_reloption
+ * Add a new ternary local reloption
+ *
+ * 'offset' is offset of ternary-typed field.
+ */
+void
+add_local_ternary_reloption(local_relopts *relopts, const char *name,
+ const char *desc, ternary default_val,
+ int offset)
+{
+ relopt_ternary *newoption = init_ternary_reloption(RELOPT_KIND_LOCAL,
+ name, desc,
+ default_val, 0);
+
+ add_local_reloption(relopts, (relopt_gen *) newoption, offset);
+}
/*
* init_real_reloption
@@ -1617,6 +1689,18 @@ parse_one_reloption(relopt_value *option, char *text_str, int text_len,
option->gen->name, value)));
}
break;
+ case RELOPT_TYPE_TERNARY:
+ {
+ bool b;
+ parsed = parse_bool(value, &b);
+ option->values.ternary_val = b ? TERNARY_TRUE : TERNARY_FALSE;
+ if (validate && !parsed)
+ ereport(ERROR,
+ (errcode(ERRCODE_INVALID_PARAMETER_VALUE),
+ errmsg("invalid value for ternary option \"%s\": %s",
+ option->gen->name, value)));
+ }
+ break;
case RELOPT_TYPE_INT:
{
relopt_int *optint = (relopt_int *) option->gen;
@@ -1780,17 +1864,6 @@ fillRelOptions(void *rdopts, Size basesize,
char *itempos = ((char *) rdopts) + elems[j].offset;
char *string_val;
- /*
- * If isset_offset is provided, store whether the reloption is
- * set there.
- */
- if (elems[j].isset_offset > 0)
- {
- char *setpos = ((char *) rdopts) + elems[j].isset_offset;
-
- *(bool *) setpos = options[i].isset;
- }
-
switch (options[i].gen->type)
{
case RELOPT_TYPE_BOOL:
@@ -1798,6 +1871,11 @@ fillRelOptions(void *rdopts, Size basesize,
options[i].values.bool_val :
((relopt_bool *) options[i].gen)->default_val;
break;
+ case RELOPT_TYPE_TERNARY:
+ *(ternary *) itempos = options[i].isset ?
+ options[i].values.ternary_val :
+ ((relopt_ternary *) options[i].gen)->default_val;
+ break;
case RELOPT_TYPE_INT:
*(int *) itempos = options[i].isset ?
options[i].values.int_val :
@@ -1912,8 +1990,8 @@ default_reloptions(Datum reloptions, bool validate, relopt_kind kind)
offsetof(StdRdOptions, parallel_workers)},
{"vacuum_index_cleanup", RELOPT_TYPE_ENUM,
offsetof(StdRdOptions, vacuum_index_cleanup)},
- {"vacuum_truncate", RELOPT_TYPE_BOOL,
- offsetof(StdRdOptions, vacuum_truncate), offsetof(StdRdOptions, vacuum_truncate_set)},
+ {"vacuum_truncate", RELOPT_TYPE_TERNARY,
+ offsetof(StdRdOptions, vacuum_truncate)},
{"vacuum_max_eager_freeze_failure_rate", RELOPT_TYPE_REAL,
offsetof(StdRdOptions, vacuum_max_eager_freeze_failure_rate)}
};
@@ -1993,7 +2071,6 @@ build_local_reloptions(local_relopts *relopts, Datum options, bool validate)
elems[i].optname = opt->option->name;
elems[i].opttype = opt->option->type;
elems[i].offset = opt->offset;
- elems[i].isset_offset = 0; /* not supported for local relopts yet */
i++;
}
diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c
index 733ef40ae7..977babff54 100644
--- a/src/backend/commands/vacuum.c
+++ b/src/backend/commands/vacuum.c
@@ -2221,9 +2221,9 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams params,
{
StdRdOptions *opts = (StdRdOptions *) rel->rd_options;
- if (opts && opts->vacuum_truncate_set)
+ if (opts && opts->vacuum_truncate != TERNARY_UNSET)
{
- if (opts->vacuum_truncate)
+ if (opts->vacuum_truncate == TERNARY_TRUE)
params.truncate = VACOPTVALUE_ENABLED;
else
params.truncate = VACOPTVALUE_DISABLED;
diff --git a/src/include/access/reloptions.h b/src/include/access/reloptions.h
index a604a4702c..a436697658 100644
--- a/src/include/access/reloptions.h
+++ b/src/include/access/reloptions.h
@@ -29,6 +29,7 @@
typedef enum relopt_type
{
RELOPT_TYPE_BOOL,
+ RELOPT_TYPE_TERNARY, /* on, off, unset */
RELOPT_TYPE_INT,
RELOPT_TYPE_REAL,
RELOPT_TYPE_ENUM,
@@ -80,6 +81,7 @@ typedef struct relopt_value
union
{
bool bool_val;
+ ternary ternary_val;
int int_val;
double real_val;
int enum_val;
@@ -94,6 +96,12 @@ typedef struct relopt_bool
bool default_val;
} relopt_bool;
+typedef struct relopt_rernary
+{
+ relopt_gen gen;
+ int default_val;
+} relopt_ternary;
+
typedef struct relopt_int
{
relopt_gen gen;
@@ -152,19 +160,6 @@ typedef struct
const char *optname; /* option's name */
relopt_type opttype; /* option's datatype */
int offset; /* offset of field in result struct */
-
- /*
- * isset_offset is an optional offset of a field in the result struct that
- * stores whether the option is explicitly set for the relation or if it
- * just picked up the default value. In most cases, this can be
- * accomplished by giving the reloption a special out-of-range default
- * value (e.g., some integer reloptions use -2), but this isn't always
- * possible. For example, a Boolean reloption cannot be given an
- * out-of-range default, so we need another way to discover the source of
- * its value. This offset is only used if given a value greater than
- * zero.
- */
- int isset_offset;
} relopt_parse_elt;
/* Local reloption definition */
@@ -195,6 +190,8 @@ typedef struct local_relopts
extern relopt_kind add_reloption_kind(void);
extern void add_bool_reloption(bits32 kinds, const char *name, const char *desc,
bool default_val, LOCKMODE lockmode);
+extern void add_ternary_reloption(bits32 kinds, const char *name,
+ const char *desc, int default_val, LOCKMODE lockmode);
extern void add_int_reloption(bits32 kinds, const char *name, const char *desc,
int default_val, int min_val, int max_val,
LOCKMODE lockmode);
@@ -214,6 +211,9 @@ extern void register_reloptions_validator(local_relopts *relopts,
extern void add_local_bool_reloption(local_relopts *relopts, const char *name,
const char *desc, bool default_val,
int offset);
+extern void add_local_ternary_reloption(local_relopts *relopts,
+ const char *name, const char *desc,
+ ternary default_val, int offset);
extern void add_local_int_reloption(local_relopts *relopts, const char *name,
const char *desc, int default_val,
int min_val, int max_val, int offset);
diff --git a/src/include/c.h b/src/include/c.h
index 39022f8a9d..9c59ccccf8 100644
--- a/src/include/c.h
+++ b/src/include/c.h
@@ -488,6 +488,22 @@ typedef void (*pg_funcptr_t) (void);
#include <stdbool.h>
+/*
+ * ternary
+ * Boolean value with an extrea "unset" option
+ *
+ * Ternary data type is used in relation options that can be "true", "false" or
+ * "unset". Since relation options are used deep inside the PostgreSQL code,
+ * this type is declared globally.
+*/
+
+typedef enum ternary
+{
+ TERNARY_FALSE = 0,
+ TERNARY_TRUE = 1,
+ TERNARY_UNSET = -1
+} ternary;
+
/* ----------------------------------------------------------------
* Section 3: standard system types
diff --git a/src/include/utils/rel.h b/src/include/utils/rel.h
index b552359915..7346d618f9 100644
--- a/src/include/utils/rel.h
+++ b/src/include/utils/rel.h
@@ -346,8 +346,7 @@ typedef struct StdRdOptions
bool user_catalog_table; /* use as an additional catalog relation */
int parallel_workers; /* max number of parallel workers */
StdRdOptIndexCleanup vacuum_index_cleanup; /* controls index vacuuming */
- bool vacuum_truncate; /* enables vacuum to truncate a relation */
- bool vacuum_truncate_set; /* whether vacuum_truncate is set */
+ ternary vacuum_truncate; /* enables vacuum to truncate a relation */
/*
* Fraction of pages in a relation that vacuum can eagerly scan and fail
--
2.39.2
--nextPart4155600.3ZeAukHxDK
Content-Disposition: attachment;
filename="v2a-0003-Add-alias-to-be-used-as-unset-state.patch"
Content-Transfer-Encoding: 7Bit
Content-Type: text/x-patch; charset="unicode-2-0-utf-8";
name="v2a-0003-Add-alias-to-be-used-as-unset-state.patch"
^ permalink raw reply [nested|flat] 868+ messages in thread
* [PATCH v2a 2/4] Introduce ternary reloptions
@ 2025-09-04 14:41 Nikolay Shaplov <[email protected]>
0 siblings, 0 replies; 868+ messages in thread
From: Nikolay Shaplov @ 2025-09-04 14:41 UTC (permalink / raw)
Introduce ternary reloption as a replacement for current `vacuum_truncate`
implementation. Remove `vacuum_truncate_set` additional flag and using
`TERNARY_UNSET` value instead.
---
src/backend/access/common/reloptions.c | 129 ++++++++++++++++++++-----
src/backend/commands/vacuum.c | 4 +-
src/include/access/reloptions.h | 26 ++---
src/include/c.h | 16 +++
src/include/utils/rel.h | 3 +-
5 files changed, 135 insertions(+), 43 deletions(-)
diff --git a/src/backend/access/common/reloptions.c b/src/backend/access/common/reloptions.c
index 0af3fea68f..f543f11001 100644
--- a/src/backend/access/common/reloptions.c
+++ b/src/backend/access/common/reloptions.c
@@ -40,9 +40,9 @@
*
* To add an option:
*
- * (i) decide on a type (bool, integer, real, enum, string), name, default
- * value, upper and lower bounds (if applicable); for strings, consider a
- * validation routine.
+ * (i) decide on a type (bool, ternary, integer, real, enum, string), name,
+ * default value, upper and lower bounds (if applicable); for strings,
+ * consider a validation routine.
* (ii) add a record below (or use add_<type>_reloption).
* (iii) add it to the appropriate options struct (perhaps StdRdOptions)
* (iv) add it to the appropriate handling routine (perhaps
@@ -147,15 +147,6 @@ static relopt_bool boolRelOpts[] =
},
false
},
- {
- {
- "vacuum_truncate",
- "Enables vacuum to truncate empty pages at the end of this table",
- RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
- ShareUpdateExclusiveLock
- },
- true
- },
{
{
"deduplicate_items",
@@ -170,6 +161,21 @@ static relopt_bool boolRelOpts[] =
{{NULL}}
};
+static relopt_ternary ternaryRelOpts[] =
+{
+ {
+ {
+ "vacuum_truncate",
+ "Enables vacuum to truncate empty pages at the end of this table",
+ RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
+ ShareUpdateExclusiveLock
+ },
+ TERNARY_UNSET
+ },
+ /* list terminator */
+ {{NULL}}
+};
+
static relopt_int intRelOpts[] =
{
{
@@ -600,6 +606,13 @@ initialize_reloptions(void)
boolRelOpts[i].gen.lockmode));
j++;
}
+ for (i = 0; ternaryRelOpts[i].gen.name; i++)
+ {
+ Assert(DoLockModesConflict(ternaryRelOpts[i].gen.lockmode,
+ ternaryRelOpts[i].gen.lockmode));
+ j++;
+ }
+
for (i = 0; intRelOpts[i].gen.name; i++)
{
Assert(DoLockModesConflict(intRelOpts[i].gen.lockmode,
@@ -640,6 +653,14 @@ initialize_reloptions(void)
j++;
}
+ for (i = 0; ternaryRelOpts[i].gen.name; i++)
+ {
+ relOpts[j] = &ternaryRelOpts[i].gen;
+ relOpts[j]->type = RELOPT_TYPE_TERNARY;
+ relOpts[j]->namelen = strlen(relOpts[j]->name);
+ j++;
+ }
+
for (i = 0; intRelOpts[i].gen.name; i++)
{
relOpts[j] = &intRelOpts[i].gen;
@@ -800,6 +821,9 @@ allocate_reloption(bits32 kinds, int type, const char *name, const char *desc,
case RELOPT_TYPE_BOOL:
size = sizeof(relopt_bool);
break;
+ case RELOPT_TYPE_TERNARY:
+ size = sizeof(relopt_ternary);
+ break;
case RELOPT_TYPE_INT:
size = sizeof(relopt_int);
break;
@@ -883,6 +907,54 @@ add_local_bool_reloption(local_relopts *relopts, const char *name,
add_local_reloption(relopts, (relopt_gen *) newoption, offset);
}
+/*
+ * init_ternary_reloption
+ * Allocate and initialize a new ternary reloption
+ */
+static relopt_ternary *
+init_ternary_reloption(bits32 kinds, const char *name, const char *desc,
+ ternary default_val, LOCKMODE lockmode)
+{
+ relopt_ternary *newoption;
+
+ newoption = (relopt_ternary *) allocate_reloption(kinds,
+ RELOPT_TYPE_TERNARY, name, desc, lockmode);
+ newoption->default_val = default_val;
+
+ return newoption;
+}
+
+/*
+ * add_ternary_reloption
+ * Add a new ternary reloption
+ */
+void
+add_ternary_reloption(bits32 kinds, const char *name, const char *desc,
+ ternary default_val, LOCKMODE lockmode)
+{
+ relopt_ternary *newoption = init_ternary_reloption(kinds, name, desc,
+ default_val, lockmode);
+
+ add_reloption((relopt_gen *) newoption);
+}
+
+/*
+ * add_local_ternary_reloption
+ * Add a new ternary local reloption
+ *
+ * 'offset' is offset of ternary-typed field.
+ */
+void
+add_local_ternary_reloption(local_relopts *relopts, const char *name,
+ const char *desc, ternary default_val,
+ int offset)
+{
+ relopt_ternary *newoption = init_ternary_reloption(RELOPT_KIND_LOCAL,
+ name, desc,
+ default_val, 0);
+
+ add_local_reloption(relopts, (relopt_gen *) newoption, offset);
+}
/*
* init_real_reloption
@@ -1617,6 +1689,18 @@ parse_one_reloption(relopt_value *option, char *text_str, int text_len,
option->gen->name, value)));
}
break;
+ case RELOPT_TYPE_TERNARY:
+ {
+ bool b;
+ parsed = parse_bool(value, &b);
+ option->values.ternary_val = b ? TERNARY_TRUE : TERNARY_FALSE;
+ if (validate && !parsed)
+ ereport(ERROR,
+ (errcode(ERRCODE_INVALID_PARAMETER_VALUE),
+ errmsg("invalid value for ternary option \"%s\": %s",
+ option->gen->name, value)));
+ }
+ break;
case RELOPT_TYPE_INT:
{
relopt_int *optint = (relopt_int *) option->gen;
@@ -1780,17 +1864,6 @@ fillRelOptions(void *rdopts, Size basesize,
char *itempos = ((char *) rdopts) + elems[j].offset;
char *string_val;
- /*
- * If isset_offset is provided, store whether the reloption is
- * set there.
- */
- if (elems[j].isset_offset > 0)
- {
- char *setpos = ((char *) rdopts) + elems[j].isset_offset;
-
- *(bool *) setpos = options[i].isset;
- }
-
switch (options[i].gen->type)
{
case RELOPT_TYPE_BOOL:
@@ -1798,6 +1871,11 @@ fillRelOptions(void *rdopts, Size basesize,
options[i].values.bool_val :
((relopt_bool *) options[i].gen)->default_val;
break;
+ case RELOPT_TYPE_TERNARY:
+ *(ternary *) itempos = options[i].isset ?
+ options[i].values.ternary_val :
+ ((relopt_ternary *) options[i].gen)->default_val;
+ break;
case RELOPT_TYPE_INT:
*(int *) itempos = options[i].isset ?
options[i].values.int_val :
@@ -1912,8 +1990,8 @@ default_reloptions(Datum reloptions, bool validate, relopt_kind kind)
offsetof(StdRdOptions, parallel_workers)},
{"vacuum_index_cleanup", RELOPT_TYPE_ENUM,
offsetof(StdRdOptions, vacuum_index_cleanup)},
- {"vacuum_truncate", RELOPT_TYPE_BOOL,
- offsetof(StdRdOptions, vacuum_truncate), offsetof(StdRdOptions, vacuum_truncate_set)},
+ {"vacuum_truncate", RELOPT_TYPE_TERNARY,
+ offsetof(StdRdOptions, vacuum_truncate)},
{"vacuum_max_eager_freeze_failure_rate", RELOPT_TYPE_REAL,
offsetof(StdRdOptions, vacuum_max_eager_freeze_failure_rate)}
};
@@ -1993,7 +2071,6 @@ build_local_reloptions(local_relopts *relopts, Datum options, bool validate)
elems[i].optname = opt->option->name;
elems[i].opttype = opt->option->type;
elems[i].offset = opt->offset;
- elems[i].isset_offset = 0; /* not supported for local relopts yet */
i++;
}
diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c
index 733ef40ae7..977babff54 100644
--- a/src/backend/commands/vacuum.c
+++ b/src/backend/commands/vacuum.c
@@ -2221,9 +2221,9 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams params,
{
StdRdOptions *opts = (StdRdOptions *) rel->rd_options;
- if (opts && opts->vacuum_truncate_set)
+ if (opts && opts->vacuum_truncate != TERNARY_UNSET)
{
- if (opts->vacuum_truncate)
+ if (opts->vacuum_truncate == TERNARY_TRUE)
params.truncate = VACOPTVALUE_ENABLED;
else
params.truncate = VACOPTVALUE_DISABLED;
diff --git a/src/include/access/reloptions.h b/src/include/access/reloptions.h
index a604a4702c..a436697658 100644
--- a/src/include/access/reloptions.h
+++ b/src/include/access/reloptions.h
@@ -29,6 +29,7 @@
typedef enum relopt_type
{
RELOPT_TYPE_BOOL,
+ RELOPT_TYPE_TERNARY, /* on, off, unset */
RELOPT_TYPE_INT,
RELOPT_TYPE_REAL,
RELOPT_TYPE_ENUM,
@@ -80,6 +81,7 @@ typedef struct relopt_value
union
{
bool bool_val;
+ ternary ternary_val;
int int_val;
double real_val;
int enum_val;
@@ -94,6 +96,12 @@ typedef struct relopt_bool
bool default_val;
} relopt_bool;
+typedef struct relopt_rernary
+{
+ relopt_gen gen;
+ int default_val;
+} relopt_ternary;
+
typedef struct relopt_int
{
relopt_gen gen;
@@ -152,19 +160,6 @@ typedef struct
const char *optname; /* option's name */
relopt_type opttype; /* option's datatype */
int offset; /* offset of field in result struct */
-
- /*
- * isset_offset is an optional offset of a field in the result struct that
- * stores whether the option is explicitly set for the relation or if it
- * just picked up the default value. In most cases, this can be
- * accomplished by giving the reloption a special out-of-range default
- * value (e.g., some integer reloptions use -2), but this isn't always
- * possible. For example, a Boolean reloption cannot be given an
- * out-of-range default, so we need another way to discover the source of
- * its value. This offset is only used if given a value greater than
- * zero.
- */
- int isset_offset;
} relopt_parse_elt;
/* Local reloption definition */
@@ -195,6 +190,8 @@ typedef struct local_relopts
extern relopt_kind add_reloption_kind(void);
extern void add_bool_reloption(bits32 kinds, const char *name, const char *desc,
bool default_val, LOCKMODE lockmode);
+extern void add_ternary_reloption(bits32 kinds, const char *name,
+ const char *desc, int default_val, LOCKMODE lockmode);
extern void add_int_reloption(bits32 kinds, const char *name, const char *desc,
int default_val, int min_val, int max_val,
LOCKMODE lockmode);
@@ -214,6 +211,9 @@ extern void register_reloptions_validator(local_relopts *relopts,
extern void add_local_bool_reloption(local_relopts *relopts, const char *name,
const char *desc, bool default_val,
int offset);
+extern void add_local_ternary_reloption(local_relopts *relopts,
+ const char *name, const char *desc,
+ ternary default_val, int offset);
extern void add_local_int_reloption(local_relopts *relopts, const char *name,
const char *desc, int default_val,
int min_val, int max_val, int offset);
diff --git a/src/include/c.h b/src/include/c.h
index 39022f8a9d..9c59ccccf8 100644
--- a/src/include/c.h
+++ b/src/include/c.h
@@ -488,6 +488,22 @@ typedef void (*pg_funcptr_t) (void);
#include <stdbool.h>
+/*
+ * ternary
+ * Boolean value with an extrea "unset" option
+ *
+ * Ternary data type is used in relation options that can be "true", "false" or
+ * "unset". Since relation options are used deep inside the PostgreSQL code,
+ * this type is declared globally.
+*/
+
+typedef enum ternary
+{
+ TERNARY_FALSE = 0,
+ TERNARY_TRUE = 1,
+ TERNARY_UNSET = -1
+} ternary;
+
/* ----------------------------------------------------------------
* Section 3: standard system types
diff --git a/src/include/utils/rel.h b/src/include/utils/rel.h
index b552359915..7346d618f9 100644
--- a/src/include/utils/rel.h
+++ b/src/include/utils/rel.h
@@ -346,8 +346,7 @@ typedef struct StdRdOptions
bool user_catalog_table; /* use as an additional catalog relation */
int parallel_workers; /* max number of parallel workers */
StdRdOptIndexCleanup vacuum_index_cleanup; /* controls index vacuuming */
- bool vacuum_truncate; /* enables vacuum to truncate a relation */
- bool vacuum_truncate_set; /* whether vacuum_truncate is set */
+ ternary vacuum_truncate; /* enables vacuum to truncate a relation */
/*
* Fraction of pages in a relation that vacuum can eagerly scan and fail
--
2.39.2
--nextPart4155600.3ZeAukHxDK
Content-Disposition: attachment;
filename="v2a-0003-Add-alias-to-be-used-as-unset-state.patch"
Content-Transfer-Encoding: 7Bit
Content-Type: text/x-patch; charset="unicode-2-0-utf-8";
name="v2a-0003-Add-alias-to-be-used-as-unset-state.patch"
^ permalink raw reply [nested|flat] 868+ messages in thread
* [PATCH v2a 2/4] Introduce ternary reloptions
@ 2025-09-04 14:41 Nikolay Shaplov <[email protected]>
0 siblings, 0 replies; 868+ messages in thread
From: Nikolay Shaplov @ 2025-09-04 14:41 UTC (permalink / raw)
Introduce ternary reloption as a replacement for current `vacuum_truncate`
implementation. Remove `vacuum_truncate_set` additional flag and using
`TERNARY_UNSET` value instead.
---
src/backend/access/common/reloptions.c | 129 ++++++++++++++++++++-----
src/backend/commands/vacuum.c | 4 +-
src/include/access/reloptions.h | 26 ++---
src/include/c.h | 16 +++
src/include/utils/rel.h | 3 +-
5 files changed, 135 insertions(+), 43 deletions(-)
diff --git a/src/backend/access/common/reloptions.c b/src/backend/access/common/reloptions.c
index 0af3fea68f..f543f11001 100644
--- a/src/backend/access/common/reloptions.c
+++ b/src/backend/access/common/reloptions.c
@@ -40,9 +40,9 @@
*
* To add an option:
*
- * (i) decide on a type (bool, integer, real, enum, string), name, default
- * value, upper and lower bounds (if applicable); for strings, consider a
- * validation routine.
+ * (i) decide on a type (bool, ternary, integer, real, enum, string), name,
+ * default value, upper and lower bounds (if applicable); for strings,
+ * consider a validation routine.
* (ii) add a record below (or use add_<type>_reloption).
* (iii) add it to the appropriate options struct (perhaps StdRdOptions)
* (iv) add it to the appropriate handling routine (perhaps
@@ -147,15 +147,6 @@ static relopt_bool boolRelOpts[] =
},
false
},
- {
- {
- "vacuum_truncate",
- "Enables vacuum to truncate empty pages at the end of this table",
- RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
- ShareUpdateExclusiveLock
- },
- true
- },
{
{
"deduplicate_items",
@@ -170,6 +161,21 @@ static relopt_bool boolRelOpts[] =
{{NULL}}
};
+static relopt_ternary ternaryRelOpts[] =
+{
+ {
+ {
+ "vacuum_truncate",
+ "Enables vacuum to truncate empty pages at the end of this table",
+ RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
+ ShareUpdateExclusiveLock
+ },
+ TERNARY_UNSET
+ },
+ /* list terminator */
+ {{NULL}}
+};
+
static relopt_int intRelOpts[] =
{
{
@@ -600,6 +606,13 @@ initialize_reloptions(void)
boolRelOpts[i].gen.lockmode));
j++;
}
+ for (i = 0; ternaryRelOpts[i].gen.name; i++)
+ {
+ Assert(DoLockModesConflict(ternaryRelOpts[i].gen.lockmode,
+ ternaryRelOpts[i].gen.lockmode));
+ j++;
+ }
+
for (i = 0; intRelOpts[i].gen.name; i++)
{
Assert(DoLockModesConflict(intRelOpts[i].gen.lockmode,
@@ -640,6 +653,14 @@ initialize_reloptions(void)
j++;
}
+ for (i = 0; ternaryRelOpts[i].gen.name; i++)
+ {
+ relOpts[j] = &ternaryRelOpts[i].gen;
+ relOpts[j]->type = RELOPT_TYPE_TERNARY;
+ relOpts[j]->namelen = strlen(relOpts[j]->name);
+ j++;
+ }
+
for (i = 0; intRelOpts[i].gen.name; i++)
{
relOpts[j] = &intRelOpts[i].gen;
@@ -800,6 +821,9 @@ allocate_reloption(bits32 kinds, int type, const char *name, const char *desc,
case RELOPT_TYPE_BOOL:
size = sizeof(relopt_bool);
break;
+ case RELOPT_TYPE_TERNARY:
+ size = sizeof(relopt_ternary);
+ break;
case RELOPT_TYPE_INT:
size = sizeof(relopt_int);
break;
@@ -883,6 +907,54 @@ add_local_bool_reloption(local_relopts *relopts, const char *name,
add_local_reloption(relopts, (relopt_gen *) newoption, offset);
}
+/*
+ * init_ternary_reloption
+ * Allocate and initialize a new ternary reloption
+ */
+static relopt_ternary *
+init_ternary_reloption(bits32 kinds, const char *name, const char *desc,
+ ternary default_val, LOCKMODE lockmode)
+{
+ relopt_ternary *newoption;
+
+ newoption = (relopt_ternary *) allocate_reloption(kinds,
+ RELOPT_TYPE_TERNARY, name, desc, lockmode);
+ newoption->default_val = default_val;
+
+ return newoption;
+}
+
+/*
+ * add_ternary_reloption
+ * Add a new ternary reloption
+ */
+void
+add_ternary_reloption(bits32 kinds, const char *name, const char *desc,
+ ternary default_val, LOCKMODE lockmode)
+{
+ relopt_ternary *newoption = init_ternary_reloption(kinds, name, desc,
+ default_val, lockmode);
+
+ add_reloption((relopt_gen *) newoption);
+}
+
+/*
+ * add_local_ternary_reloption
+ * Add a new ternary local reloption
+ *
+ * 'offset' is offset of ternary-typed field.
+ */
+void
+add_local_ternary_reloption(local_relopts *relopts, const char *name,
+ const char *desc, ternary default_val,
+ int offset)
+{
+ relopt_ternary *newoption = init_ternary_reloption(RELOPT_KIND_LOCAL,
+ name, desc,
+ default_val, 0);
+
+ add_local_reloption(relopts, (relopt_gen *) newoption, offset);
+}
/*
* init_real_reloption
@@ -1617,6 +1689,18 @@ parse_one_reloption(relopt_value *option, char *text_str, int text_len,
option->gen->name, value)));
}
break;
+ case RELOPT_TYPE_TERNARY:
+ {
+ bool b;
+ parsed = parse_bool(value, &b);
+ option->values.ternary_val = b ? TERNARY_TRUE : TERNARY_FALSE;
+ if (validate && !parsed)
+ ereport(ERROR,
+ (errcode(ERRCODE_INVALID_PARAMETER_VALUE),
+ errmsg("invalid value for ternary option \"%s\": %s",
+ option->gen->name, value)));
+ }
+ break;
case RELOPT_TYPE_INT:
{
relopt_int *optint = (relopt_int *) option->gen;
@@ -1780,17 +1864,6 @@ fillRelOptions(void *rdopts, Size basesize,
char *itempos = ((char *) rdopts) + elems[j].offset;
char *string_val;
- /*
- * If isset_offset is provided, store whether the reloption is
- * set there.
- */
- if (elems[j].isset_offset > 0)
- {
- char *setpos = ((char *) rdopts) + elems[j].isset_offset;
-
- *(bool *) setpos = options[i].isset;
- }
-
switch (options[i].gen->type)
{
case RELOPT_TYPE_BOOL:
@@ -1798,6 +1871,11 @@ fillRelOptions(void *rdopts, Size basesize,
options[i].values.bool_val :
((relopt_bool *) options[i].gen)->default_val;
break;
+ case RELOPT_TYPE_TERNARY:
+ *(ternary *) itempos = options[i].isset ?
+ options[i].values.ternary_val :
+ ((relopt_ternary *) options[i].gen)->default_val;
+ break;
case RELOPT_TYPE_INT:
*(int *) itempos = options[i].isset ?
options[i].values.int_val :
@@ -1912,8 +1990,8 @@ default_reloptions(Datum reloptions, bool validate, relopt_kind kind)
offsetof(StdRdOptions, parallel_workers)},
{"vacuum_index_cleanup", RELOPT_TYPE_ENUM,
offsetof(StdRdOptions, vacuum_index_cleanup)},
- {"vacuum_truncate", RELOPT_TYPE_BOOL,
- offsetof(StdRdOptions, vacuum_truncate), offsetof(StdRdOptions, vacuum_truncate_set)},
+ {"vacuum_truncate", RELOPT_TYPE_TERNARY,
+ offsetof(StdRdOptions, vacuum_truncate)},
{"vacuum_max_eager_freeze_failure_rate", RELOPT_TYPE_REAL,
offsetof(StdRdOptions, vacuum_max_eager_freeze_failure_rate)}
};
@@ -1993,7 +2071,6 @@ build_local_reloptions(local_relopts *relopts, Datum options, bool validate)
elems[i].optname = opt->option->name;
elems[i].opttype = opt->option->type;
elems[i].offset = opt->offset;
- elems[i].isset_offset = 0; /* not supported for local relopts yet */
i++;
}
diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c
index 733ef40ae7..977babff54 100644
--- a/src/backend/commands/vacuum.c
+++ b/src/backend/commands/vacuum.c
@@ -2221,9 +2221,9 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams params,
{
StdRdOptions *opts = (StdRdOptions *) rel->rd_options;
- if (opts && opts->vacuum_truncate_set)
+ if (opts && opts->vacuum_truncate != TERNARY_UNSET)
{
- if (opts->vacuum_truncate)
+ if (opts->vacuum_truncate == TERNARY_TRUE)
params.truncate = VACOPTVALUE_ENABLED;
else
params.truncate = VACOPTVALUE_DISABLED;
diff --git a/src/include/access/reloptions.h b/src/include/access/reloptions.h
index a604a4702c..a436697658 100644
--- a/src/include/access/reloptions.h
+++ b/src/include/access/reloptions.h
@@ -29,6 +29,7 @@
typedef enum relopt_type
{
RELOPT_TYPE_BOOL,
+ RELOPT_TYPE_TERNARY, /* on, off, unset */
RELOPT_TYPE_INT,
RELOPT_TYPE_REAL,
RELOPT_TYPE_ENUM,
@@ -80,6 +81,7 @@ typedef struct relopt_value
union
{
bool bool_val;
+ ternary ternary_val;
int int_val;
double real_val;
int enum_val;
@@ -94,6 +96,12 @@ typedef struct relopt_bool
bool default_val;
} relopt_bool;
+typedef struct relopt_rernary
+{
+ relopt_gen gen;
+ int default_val;
+} relopt_ternary;
+
typedef struct relopt_int
{
relopt_gen gen;
@@ -152,19 +160,6 @@ typedef struct
const char *optname; /* option's name */
relopt_type opttype; /* option's datatype */
int offset; /* offset of field in result struct */
-
- /*
- * isset_offset is an optional offset of a field in the result struct that
- * stores whether the option is explicitly set for the relation or if it
- * just picked up the default value. In most cases, this can be
- * accomplished by giving the reloption a special out-of-range default
- * value (e.g., some integer reloptions use -2), but this isn't always
- * possible. For example, a Boolean reloption cannot be given an
- * out-of-range default, so we need another way to discover the source of
- * its value. This offset is only used if given a value greater than
- * zero.
- */
- int isset_offset;
} relopt_parse_elt;
/* Local reloption definition */
@@ -195,6 +190,8 @@ typedef struct local_relopts
extern relopt_kind add_reloption_kind(void);
extern void add_bool_reloption(bits32 kinds, const char *name, const char *desc,
bool default_val, LOCKMODE lockmode);
+extern void add_ternary_reloption(bits32 kinds, const char *name,
+ const char *desc, int default_val, LOCKMODE lockmode);
extern void add_int_reloption(bits32 kinds, const char *name, const char *desc,
int default_val, int min_val, int max_val,
LOCKMODE lockmode);
@@ -214,6 +211,9 @@ extern void register_reloptions_validator(local_relopts *relopts,
extern void add_local_bool_reloption(local_relopts *relopts, const char *name,
const char *desc, bool default_val,
int offset);
+extern void add_local_ternary_reloption(local_relopts *relopts,
+ const char *name, const char *desc,
+ ternary default_val, int offset);
extern void add_local_int_reloption(local_relopts *relopts, const char *name,
const char *desc, int default_val,
int min_val, int max_val, int offset);
diff --git a/src/include/c.h b/src/include/c.h
index 39022f8a9d..9c59ccccf8 100644
--- a/src/include/c.h
+++ b/src/include/c.h
@@ -488,6 +488,22 @@ typedef void (*pg_funcptr_t) (void);
#include <stdbool.h>
+/*
+ * ternary
+ * Boolean value with an extrea "unset" option
+ *
+ * Ternary data type is used in relation options that can be "true", "false" or
+ * "unset". Since relation options are used deep inside the PostgreSQL code,
+ * this type is declared globally.
+*/
+
+typedef enum ternary
+{
+ TERNARY_FALSE = 0,
+ TERNARY_TRUE = 1,
+ TERNARY_UNSET = -1
+} ternary;
+
/* ----------------------------------------------------------------
* Section 3: standard system types
diff --git a/src/include/utils/rel.h b/src/include/utils/rel.h
index b552359915..7346d618f9 100644
--- a/src/include/utils/rel.h
+++ b/src/include/utils/rel.h
@@ -346,8 +346,7 @@ typedef struct StdRdOptions
bool user_catalog_table; /* use as an additional catalog relation */
int parallel_workers; /* max number of parallel workers */
StdRdOptIndexCleanup vacuum_index_cleanup; /* controls index vacuuming */
- bool vacuum_truncate; /* enables vacuum to truncate a relation */
- bool vacuum_truncate_set; /* whether vacuum_truncate is set */
+ ternary vacuum_truncate; /* enables vacuum to truncate a relation */
/*
* Fraction of pages in a relation that vacuum can eagerly scan and fail
--
2.39.2
--nextPart4155600.3ZeAukHxDK
Content-Disposition: attachment;
filename="v2a-0003-Add-alias-to-be-used-as-unset-state.patch"
Content-Transfer-Encoding: 7Bit
Content-Type: text/x-patch; charset="unicode-2-0-utf-8";
name="v2a-0003-Add-alias-to-be-used-as-unset-state.patch"
^ permalink raw reply [nested|flat] 868+ messages in thread
* [PATCH v2a 2/4] Introduce ternary reloptions
@ 2025-09-04 14:41 Nikolay Shaplov <[email protected]>
0 siblings, 0 replies; 868+ messages in thread
From: Nikolay Shaplov @ 2025-09-04 14:41 UTC (permalink / raw)
Introduce ternary reloption as a replacement for current `vacuum_truncate`
implementation. Remove `vacuum_truncate_set` additional flag and using
`TERNARY_UNSET` value instead.
---
src/backend/access/common/reloptions.c | 129 ++++++++++++++++++++-----
src/backend/commands/vacuum.c | 4 +-
src/include/access/reloptions.h | 26 ++---
src/include/c.h | 16 +++
src/include/utils/rel.h | 3 +-
5 files changed, 135 insertions(+), 43 deletions(-)
diff --git a/src/backend/access/common/reloptions.c b/src/backend/access/common/reloptions.c
index 0af3fea68f..f543f11001 100644
--- a/src/backend/access/common/reloptions.c
+++ b/src/backend/access/common/reloptions.c
@@ -40,9 +40,9 @@
*
* To add an option:
*
- * (i) decide on a type (bool, integer, real, enum, string), name, default
- * value, upper and lower bounds (if applicable); for strings, consider a
- * validation routine.
+ * (i) decide on a type (bool, ternary, integer, real, enum, string), name,
+ * default value, upper and lower bounds (if applicable); for strings,
+ * consider a validation routine.
* (ii) add a record below (or use add_<type>_reloption).
* (iii) add it to the appropriate options struct (perhaps StdRdOptions)
* (iv) add it to the appropriate handling routine (perhaps
@@ -147,15 +147,6 @@ static relopt_bool boolRelOpts[] =
},
false
},
- {
- {
- "vacuum_truncate",
- "Enables vacuum to truncate empty pages at the end of this table",
- RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
- ShareUpdateExclusiveLock
- },
- true
- },
{
{
"deduplicate_items",
@@ -170,6 +161,21 @@ static relopt_bool boolRelOpts[] =
{{NULL}}
};
+static relopt_ternary ternaryRelOpts[] =
+{
+ {
+ {
+ "vacuum_truncate",
+ "Enables vacuum to truncate empty pages at the end of this table",
+ RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
+ ShareUpdateExclusiveLock
+ },
+ TERNARY_UNSET
+ },
+ /* list terminator */
+ {{NULL}}
+};
+
static relopt_int intRelOpts[] =
{
{
@@ -600,6 +606,13 @@ initialize_reloptions(void)
boolRelOpts[i].gen.lockmode));
j++;
}
+ for (i = 0; ternaryRelOpts[i].gen.name; i++)
+ {
+ Assert(DoLockModesConflict(ternaryRelOpts[i].gen.lockmode,
+ ternaryRelOpts[i].gen.lockmode));
+ j++;
+ }
+
for (i = 0; intRelOpts[i].gen.name; i++)
{
Assert(DoLockModesConflict(intRelOpts[i].gen.lockmode,
@@ -640,6 +653,14 @@ initialize_reloptions(void)
j++;
}
+ for (i = 0; ternaryRelOpts[i].gen.name; i++)
+ {
+ relOpts[j] = &ternaryRelOpts[i].gen;
+ relOpts[j]->type = RELOPT_TYPE_TERNARY;
+ relOpts[j]->namelen = strlen(relOpts[j]->name);
+ j++;
+ }
+
for (i = 0; intRelOpts[i].gen.name; i++)
{
relOpts[j] = &intRelOpts[i].gen;
@@ -800,6 +821,9 @@ allocate_reloption(bits32 kinds, int type, const char *name, const char *desc,
case RELOPT_TYPE_BOOL:
size = sizeof(relopt_bool);
break;
+ case RELOPT_TYPE_TERNARY:
+ size = sizeof(relopt_ternary);
+ break;
case RELOPT_TYPE_INT:
size = sizeof(relopt_int);
break;
@@ -883,6 +907,54 @@ add_local_bool_reloption(local_relopts *relopts, const char *name,
add_local_reloption(relopts, (relopt_gen *) newoption, offset);
}
+/*
+ * init_ternary_reloption
+ * Allocate and initialize a new ternary reloption
+ */
+static relopt_ternary *
+init_ternary_reloption(bits32 kinds, const char *name, const char *desc,
+ ternary default_val, LOCKMODE lockmode)
+{
+ relopt_ternary *newoption;
+
+ newoption = (relopt_ternary *) allocate_reloption(kinds,
+ RELOPT_TYPE_TERNARY, name, desc, lockmode);
+ newoption->default_val = default_val;
+
+ return newoption;
+}
+
+/*
+ * add_ternary_reloption
+ * Add a new ternary reloption
+ */
+void
+add_ternary_reloption(bits32 kinds, const char *name, const char *desc,
+ ternary default_val, LOCKMODE lockmode)
+{
+ relopt_ternary *newoption = init_ternary_reloption(kinds, name, desc,
+ default_val, lockmode);
+
+ add_reloption((relopt_gen *) newoption);
+}
+
+/*
+ * add_local_ternary_reloption
+ * Add a new ternary local reloption
+ *
+ * 'offset' is offset of ternary-typed field.
+ */
+void
+add_local_ternary_reloption(local_relopts *relopts, const char *name,
+ const char *desc, ternary default_val,
+ int offset)
+{
+ relopt_ternary *newoption = init_ternary_reloption(RELOPT_KIND_LOCAL,
+ name, desc,
+ default_val, 0);
+
+ add_local_reloption(relopts, (relopt_gen *) newoption, offset);
+}
/*
* init_real_reloption
@@ -1617,6 +1689,18 @@ parse_one_reloption(relopt_value *option, char *text_str, int text_len,
option->gen->name, value)));
}
break;
+ case RELOPT_TYPE_TERNARY:
+ {
+ bool b;
+ parsed = parse_bool(value, &b);
+ option->values.ternary_val = b ? TERNARY_TRUE : TERNARY_FALSE;
+ if (validate && !parsed)
+ ereport(ERROR,
+ (errcode(ERRCODE_INVALID_PARAMETER_VALUE),
+ errmsg("invalid value for ternary option \"%s\": %s",
+ option->gen->name, value)));
+ }
+ break;
case RELOPT_TYPE_INT:
{
relopt_int *optint = (relopt_int *) option->gen;
@@ -1780,17 +1864,6 @@ fillRelOptions(void *rdopts, Size basesize,
char *itempos = ((char *) rdopts) + elems[j].offset;
char *string_val;
- /*
- * If isset_offset is provided, store whether the reloption is
- * set there.
- */
- if (elems[j].isset_offset > 0)
- {
- char *setpos = ((char *) rdopts) + elems[j].isset_offset;
-
- *(bool *) setpos = options[i].isset;
- }
-
switch (options[i].gen->type)
{
case RELOPT_TYPE_BOOL:
@@ -1798,6 +1871,11 @@ fillRelOptions(void *rdopts, Size basesize,
options[i].values.bool_val :
((relopt_bool *) options[i].gen)->default_val;
break;
+ case RELOPT_TYPE_TERNARY:
+ *(ternary *) itempos = options[i].isset ?
+ options[i].values.ternary_val :
+ ((relopt_ternary *) options[i].gen)->default_val;
+ break;
case RELOPT_TYPE_INT:
*(int *) itempos = options[i].isset ?
options[i].values.int_val :
@@ -1912,8 +1990,8 @@ default_reloptions(Datum reloptions, bool validate, relopt_kind kind)
offsetof(StdRdOptions, parallel_workers)},
{"vacuum_index_cleanup", RELOPT_TYPE_ENUM,
offsetof(StdRdOptions, vacuum_index_cleanup)},
- {"vacuum_truncate", RELOPT_TYPE_BOOL,
- offsetof(StdRdOptions, vacuum_truncate), offsetof(StdRdOptions, vacuum_truncate_set)},
+ {"vacuum_truncate", RELOPT_TYPE_TERNARY,
+ offsetof(StdRdOptions, vacuum_truncate)},
{"vacuum_max_eager_freeze_failure_rate", RELOPT_TYPE_REAL,
offsetof(StdRdOptions, vacuum_max_eager_freeze_failure_rate)}
};
@@ -1993,7 +2071,6 @@ build_local_reloptions(local_relopts *relopts, Datum options, bool validate)
elems[i].optname = opt->option->name;
elems[i].opttype = opt->option->type;
elems[i].offset = opt->offset;
- elems[i].isset_offset = 0; /* not supported for local relopts yet */
i++;
}
diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c
index 733ef40ae7..977babff54 100644
--- a/src/backend/commands/vacuum.c
+++ b/src/backend/commands/vacuum.c
@@ -2221,9 +2221,9 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams params,
{
StdRdOptions *opts = (StdRdOptions *) rel->rd_options;
- if (opts && opts->vacuum_truncate_set)
+ if (opts && opts->vacuum_truncate != TERNARY_UNSET)
{
- if (opts->vacuum_truncate)
+ if (opts->vacuum_truncate == TERNARY_TRUE)
params.truncate = VACOPTVALUE_ENABLED;
else
params.truncate = VACOPTVALUE_DISABLED;
diff --git a/src/include/access/reloptions.h b/src/include/access/reloptions.h
index a604a4702c..a436697658 100644
--- a/src/include/access/reloptions.h
+++ b/src/include/access/reloptions.h
@@ -29,6 +29,7 @@
typedef enum relopt_type
{
RELOPT_TYPE_BOOL,
+ RELOPT_TYPE_TERNARY, /* on, off, unset */
RELOPT_TYPE_INT,
RELOPT_TYPE_REAL,
RELOPT_TYPE_ENUM,
@@ -80,6 +81,7 @@ typedef struct relopt_value
union
{
bool bool_val;
+ ternary ternary_val;
int int_val;
double real_val;
int enum_val;
@@ -94,6 +96,12 @@ typedef struct relopt_bool
bool default_val;
} relopt_bool;
+typedef struct relopt_rernary
+{
+ relopt_gen gen;
+ int default_val;
+} relopt_ternary;
+
typedef struct relopt_int
{
relopt_gen gen;
@@ -152,19 +160,6 @@ typedef struct
const char *optname; /* option's name */
relopt_type opttype; /* option's datatype */
int offset; /* offset of field in result struct */
-
- /*
- * isset_offset is an optional offset of a field in the result struct that
- * stores whether the option is explicitly set for the relation or if it
- * just picked up the default value. In most cases, this can be
- * accomplished by giving the reloption a special out-of-range default
- * value (e.g., some integer reloptions use -2), but this isn't always
- * possible. For example, a Boolean reloption cannot be given an
- * out-of-range default, so we need another way to discover the source of
- * its value. This offset is only used if given a value greater than
- * zero.
- */
- int isset_offset;
} relopt_parse_elt;
/* Local reloption definition */
@@ -195,6 +190,8 @@ typedef struct local_relopts
extern relopt_kind add_reloption_kind(void);
extern void add_bool_reloption(bits32 kinds, const char *name, const char *desc,
bool default_val, LOCKMODE lockmode);
+extern void add_ternary_reloption(bits32 kinds, const char *name,
+ const char *desc, int default_val, LOCKMODE lockmode);
extern void add_int_reloption(bits32 kinds, const char *name, const char *desc,
int default_val, int min_val, int max_val,
LOCKMODE lockmode);
@@ -214,6 +211,9 @@ extern void register_reloptions_validator(local_relopts *relopts,
extern void add_local_bool_reloption(local_relopts *relopts, const char *name,
const char *desc, bool default_val,
int offset);
+extern void add_local_ternary_reloption(local_relopts *relopts,
+ const char *name, const char *desc,
+ ternary default_val, int offset);
extern void add_local_int_reloption(local_relopts *relopts, const char *name,
const char *desc, int default_val,
int min_val, int max_val, int offset);
diff --git a/src/include/c.h b/src/include/c.h
index 39022f8a9d..9c59ccccf8 100644
--- a/src/include/c.h
+++ b/src/include/c.h
@@ -488,6 +488,22 @@ typedef void (*pg_funcptr_t) (void);
#include <stdbool.h>
+/*
+ * ternary
+ * Boolean value with an extrea "unset" option
+ *
+ * Ternary data type is used in relation options that can be "true", "false" or
+ * "unset". Since relation options are used deep inside the PostgreSQL code,
+ * this type is declared globally.
+*/
+
+typedef enum ternary
+{
+ TERNARY_FALSE = 0,
+ TERNARY_TRUE = 1,
+ TERNARY_UNSET = -1
+} ternary;
+
/* ----------------------------------------------------------------
* Section 3: standard system types
diff --git a/src/include/utils/rel.h b/src/include/utils/rel.h
index b552359915..7346d618f9 100644
--- a/src/include/utils/rel.h
+++ b/src/include/utils/rel.h
@@ -346,8 +346,7 @@ typedef struct StdRdOptions
bool user_catalog_table; /* use as an additional catalog relation */
int parallel_workers; /* max number of parallel workers */
StdRdOptIndexCleanup vacuum_index_cleanup; /* controls index vacuuming */
- bool vacuum_truncate; /* enables vacuum to truncate a relation */
- bool vacuum_truncate_set; /* whether vacuum_truncate is set */
+ ternary vacuum_truncate; /* enables vacuum to truncate a relation */
/*
* Fraction of pages in a relation that vacuum can eagerly scan and fail
--
2.39.2
--nextPart4155600.3ZeAukHxDK
Content-Disposition: attachment;
filename="v2a-0003-Add-alias-to-be-used-as-unset-state.patch"
Content-Transfer-Encoding: 7Bit
Content-Type: text/x-patch; charset="unicode-2-0-utf-8";
name="v2a-0003-Add-alias-to-be-used-as-unset-state.patch"
^ permalink raw reply [nested|flat] 868+ messages in thread
* [PATCH v2 2/4] Introduce ternary reloptions
@ 2025-09-04 14:41 Nikolay Shaplov <[email protected]>
0 siblings, 0 replies; 868+ messages in thread
From: Nikolay Shaplov @ 2025-09-04 14:41 UTC (permalink / raw)
Introduce ternary reloption as a replacement for current `vacuum_truncate`
implementation. Remove `vacuum_truncate_set` additional flag and using
`TERNARY_UNSET` value instead.
---
src/backend/access/common/reloptions.c | 129 ++++++++++++++++++++-----
src/backend/commands/vacuum.c | 4 +-
src/include/access/reloptions.h | 26 ++---
src/include/c.h | 16 +++
src/include/utils/rel.h | 3 +-
5 files changed, 135 insertions(+), 43 deletions(-)
diff --git a/src/backend/access/common/reloptions.c b/src/backend/access/common/reloptions.c
index 0af3fea68f..f543f11001 100644
--- a/src/backend/access/common/reloptions.c
+++ b/src/backend/access/common/reloptions.c
@@ -40,9 +40,9 @@
*
* To add an option:
*
- * (i) decide on a type (bool, integer, real, enum, string), name, default
- * value, upper and lower bounds (if applicable); for strings, consider a
- * validation routine.
+ * (i) decide on a type (bool, ternary, integer, real, enum, string), name,
+ * default value, upper and lower bounds (if applicable); for strings,
+ * consider a validation routine.
* (ii) add a record below (or use add_<type>_reloption).
* (iii) add it to the appropriate options struct (perhaps StdRdOptions)
* (iv) add it to the appropriate handling routine (perhaps
@@ -147,15 +147,6 @@ static relopt_bool boolRelOpts[] =
},
false
},
- {
- {
- "vacuum_truncate",
- "Enables vacuum to truncate empty pages at the end of this table",
- RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
- ShareUpdateExclusiveLock
- },
- true
- },
{
{
"deduplicate_items",
@@ -170,6 +161,21 @@ static relopt_bool boolRelOpts[] =
{{NULL}}
};
+static relopt_ternary ternaryRelOpts[] =
+{
+ {
+ {
+ "vacuum_truncate",
+ "Enables vacuum to truncate empty pages at the end of this table",
+ RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
+ ShareUpdateExclusiveLock
+ },
+ TERNARY_UNSET
+ },
+ /* list terminator */
+ {{NULL}}
+};
+
static relopt_int intRelOpts[] =
{
{
@@ -600,6 +606,13 @@ initialize_reloptions(void)
boolRelOpts[i].gen.lockmode));
j++;
}
+ for (i = 0; ternaryRelOpts[i].gen.name; i++)
+ {
+ Assert(DoLockModesConflict(ternaryRelOpts[i].gen.lockmode,
+ ternaryRelOpts[i].gen.lockmode));
+ j++;
+ }
+
for (i = 0; intRelOpts[i].gen.name; i++)
{
Assert(DoLockModesConflict(intRelOpts[i].gen.lockmode,
@@ -640,6 +653,14 @@ initialize_reloptions(void)
j++;
}
+ for (i = 0; ternaryRelOpts[i].gen.name; i++)
+ {
+ relOpts[j] = &ternaryRelOpts[i].gen;
+ relOpts[j]->type = RELOPT_TYPE_TERNARY;
+ relOpts[j]->namelen = strlen(relOpts[j]->name);
+ j++;
+ }
+
for (i = 0; intRelOpts[i].gen.name; i++)
{
relOpts[j] = &intRelOpts[i].gen;
@@ -800,6 +821,9 @@ allocate_reloption(bits32 kinds, int type, const char *name, const char *desc,
case RELOPT_TYPE_BOOL:
size = sizeof(relopt_bool);
break;
+ case RELOPT_TYPE_TERNARY:
+ size = sizeof(relopt_ternary);
+ break;
case RELOPT_TYPE_INT:
size = sizeof(relopt_int);
break;
@@ -883,6 +907,54 @@ add_local_bool_reloption(local_relopts *relopts, const char *name,
add_local_reloption(relopts, (relopt_gen *) newoption, offset);
}
+/*
+ * init_ternary_reloption
+ * Allocate and initialize a new ternary reloption
+ */
+static relopt_ternary *
+init_ternary_reloption(bits32 kinds, const char *name, const char *desc,
+ ternary default_val, LOCKMODE lockmode)
+{
+ relopt_ternary *newoption;
+
+ newoption = (relopt_ternary *) allocate_reloption(kinds,
+ RELOPT_TYPE_TERNARY, name, desc, lockmode);
+ newoption->default_val = default_val;
+
+ return newoption;
+}
+
+/*
+ * add_ternary_reloption
+ * Add a new ternary reloption
+ */
+void
+add_ternary_reloption(bits32 kinds, const char *name, const char *desc,
+ ternary default_val, LOCKMODE lockmode)
+{
+ relopt_ternary *newoption = init_ternary_reloption(kinds, name, desc,
+ default_val, lockmode);
+
+ add_reloption((relopt_gen *) newoption);
+}
+
+/*
+ * add_local_ternary_reloption
+ * Add a new ternary local reloption
+ *
+ * 'offset' is offset of ternary-typed field.
+ */
+void
+add_local_ternary_reloption(local_relopts *relopts, const char *name,
+ const char *desc, ternary default_val,
+ int offset)
+{
+ relopt_ternary *newoption = init_ternary_reloption(RELOPT_KIND_LOCAL,
+ name, desc,
+ default_val, 0);
+
+ add_local_reloption(relopts, (relopt_gen *) newoption, offset);
+}
/*
* init_real_reloption
@@ -1617,6 +1689,18 @@ parse_one_reloption(relopt_value *option, char *text_str, int text_len,
option->gen->name, value)));
}
break;
+ case RELOPT_TYPE_TERNARY:
+ {
+ bool b;
+ parsed = parse_bool(value, &b);
+ option->values.ternary_val = b ? TERNARY_TRUE : TERNARY_FALSE;
+ if (validate && !parsed)
+ ereport(ERROR,
+ (errcode(ERRCODE_INVALID_PARAMETER_VALUE),
+ errmsg("invalid value for ternary option \"%s\": %s",
+ option->gen->name, value)));
+ }
+ break;
case RELOPT_TYPE_INT:
{
relopt_int *optint = (relopt_int *) option->gen;
@@ -1780,17 +1864,6 @@ fillRelOptions(void *rdopts, Size basesize,
char *itempos = ((char *) rdopts) + elems[j].offset;
char *string_val;
- /*
- * If isset_offset is provided, store whether the reloption is
- * set there.
- */
- if (elems[j].isset_offset > 0)
- {
- char *setpos = ((char *) rdopts) + elems[j].isset_offset;
-
- *(bool *) setpos = options[i].isset;
- }
-
switch (options[i].gen->type)
{
case RELOPT_TYPE_BOOL:
@@ -1798,6 +1871,11 @@ fillRelOptions(void *rdopts, Size basesize,
options[i].values.bool_val :
((relopt_bool *) options[i].gen)->default_val;
break;
+ case RELOPT_TYPE_TERNARY:
+ *(ternary *) itempos = options[i].isset ?
+ options[i].values.ternary_val :
+ ((relopt_ternary *) options[i].gen)->default_val;
+ break;
case RELOPT_TYPE_INT:
*(int *) itempos = options[i].isset ?
options[i].values.int_val :
@@ -1912,8 +1990,8 @@ default_reloptions(Datum reloptions, bool validate, relopt_kind kind)
offsetof(StdRdOptions, parallel_workers)},
{"vacuum_index_cleanup", RELOPT_TYPE_ENUM,
offsetof(StdRdOptions, vacuum_index_cleanup)},
- {"vacuum_truncate", RELOPT_TYPE_BOOL,
- offsetof(StdRdOptions, vacuum_truncate), offsetof(StdRdOptions, vacuum_truncate_set)},
+ {"vacuum_truncate", RELOPT_TYPE_TERNARY,
+ offsetof(StdRdOptions, vacuum_truncate)},
{"vacuum_max_eager_freeze_failure_rate", RELOPT_TYPE_REAL,
offsetof(StdRdOptions, vacuum_max_eager_freeze_failure_rate)}
};
@@ -1993,7 +2071,6 @@ build_local_reloptions(local_relopts *relopts, Datum options, bool validate)
elems[i].optname = opt->option->name;
elems[i].opttype = opt->option->type;
elems[i].offset = opt->offset;
- elems[i].isset_offset = 0; /* not supported for local relopts yet */
i++;
}
diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c
index 733ef40ae7..977babff54 100644
--- a/src/backend/commands/vacuum.c
+++ b/src/backend/commands/vacuum.c
@@ -2221,9 +2221,9 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams params,
{
StdRdOptions *opts = (StdRdOptions *) rel->rd_options;
- if (opts && opts->vacuum_truncate_set)
+ if (opts && opts->vacuum_truncate != TERNARY_UNSET)
{
- if (opts->vacuum_truncate)
+ if (opts->vacuum_truncate == TERNARY_TRUE)
params.truncate = VACOPTVALUE_ENABLED;
else
params.truncate = VACOPTVALUE_DISABLED;
diff --git a/src/include/access/reloptions.h b/src/include/access/reloptions.h
index a604a4702c..a436697658 100644
--- a/src/include/access/reloptions.h
+++ b/src/include/access/reloptions.h
@@ -29,6 +29,7 @@
typedef enum relopt_type
{
RELOPT_TYPE_BOOL,
+ RELOPT_TYPE_TERNARY, /* on, off, unset */
RELOPT_TYPE_INT,
RELOPT_TYPE_REAL,
RELOPT_TYPE_ENUM,
@@ -80,6 +81,7 @@ typedef struct relopt_value
union
{
bool bool_val;
+ ternary ternary_val;
int int_val;
double real_val;
int enum_val;
@@ -94,6 +96,12 @@ typedef struct relopt_bool
bool default_val;
} relopt_bool;
+typedef struct relopt_rernary
+{
+ relopt_gen gen;
+ int default_val;
+} relopt_ternary;
+
typedef struct relopt_int
{
relopt_gen gen;
@@ -152,19 +160,6 @@ typedef struct
const char *optname; /* option's name */
relopt_type opttype; /* option's datatype */
int offset; /* offset of field in result struct */
-
- /*
- * isset_offset is an optional offset of a field in the result struct that
- * stores whether the option is explicitly set for the relation or if it
- * just picked up the default value. In most cases, this can be
- * accomplished by giving the reloption a special out-of-range default
- * value (e.g., some integer reloptions use -2), but this isn't always
- * possible. For example, a Boolean reloption cannot be given an
- * out-of-range default, so we need another way to discover the source of
- * its value. This offset is only used if given a value greater than
- * zero.
- */
- int isset_offset;
} relopt_parse_elt;
/* Local reloption definition */
@@ -195,6 +190,8 @@ typedef struct local_relopts
extern relopt_kind add_reloption_kind(void);
extern void add_bool_reloption(bits32 kinds, const char *name, const char *desc,
bool default_val, LOCKMODE lockmode);
+extern void add_ternary_reloption(bits32 kinds, const char *name,
+ const char *desc, int default_val, LOCKMODE lockmode);
extern void add_int_reloption(bits32 kinds, const char *name, const char *desc,
int default_val, int min_val, int max_val,
LOCKMODE lockmode);
@@ -214,6 +211,9 @@ extern void register_reloptions_validator(local_relopts *relopts,
extern void add_local_bool_reloption(local_relopts *relopts, const char *name,
const char *desc, bool default_val,
int offset);
+extern void add_local_ternary_reloption(local_relopts *relopts,
+ const char *name, const char *desc,
+ ternary default_val, int offset);
extern void add_local_int_reloption(local_relopts *relopts, const char *name,
const char *desc, int default_val,
int min_val, int max_val, int offset);
diff --git a/src/include/c.h b/src/include/c.h
index 39022f8a9d..9c59ccccf8 100644
--- a/src/include/c.h
+++ b/src/include/c.h
@@ -488,6 +488,22 @@ typedef void (*pg_funcptr_t) (void);
#include <stdbool.h>
+/*
+ * ternary
+ * Boolean value with an extrea "unset" option
+ *
+ * Ternary data type is used in relation options that can be "true", "false" or
+ * "unset". Since relation options are used deep inside the PostgreSQL code,
+ * this type is declared globally.
+*/
+
+typedef enum ternary
+{
+ TERNARY_FALSE = 0,
+ TERNARY_TRUE = 1,
+ TERNARY_UNSET = -1
+} ternary;
+
/* ----------------------------------------------------------------
* Section 3: standard system types
diff --git a/src/include/utils/rel.h b/src/include/utils/rel.h
index b552359915..7346d618f9 100644
--- a/src/include/utils/rel.h
+++ b/src/include/utils/rel.h
@@ -346,8 +346,7 @@ typedef struct StdRdOptions
bool user_catalog_table; /* use as an additional catalog relation */
int parallel_workers; /* max number of parallel workers */
StdRdOptIndexCleanup vacuum_index_cleanup; /* controls index vacuuming */
- bool vacuum_truncate; /* enables vacuum to truncate a relation */
- bool vacuum_truncate_set; /* whether vacuum_truncate is set */
+ ternary vacuum_truncate; /* enables vacuum to truncate a relation */
/*
* Fraction of pages in a relation that vacuum can eagerly scan and fail
--
2.39.2
--nextPart5129932.5fSG56mABF
Content-Disposition: attachment;
filename="v2-0003-Add-alias-to-be-used-as-unset-state.patch"
Content-Transfer-Encoding: 7Bit
Content-Type: text/x-patch; charset="unicode-2-0-utf-8";
name="v2-0003-Add-alias-to-be-used-as-unset-state.patch"
^ permalink raw reply [nested|flat] 868+ messages in thread
* [PATCH v2 2/4] Introduce ternary reloptions
@ 2025-09-04 14:41 Nikolay Shaplov <[email protected]>
0 siblings, 0 replies; 868+ messages in thread
From: Nikolay Shaplov @ 2025-09-04 14:41 UTC (permalink / raw)
Introduce ternary reloption as a replacement for current `vacuum_truncate`
implementation. Remove `vacuum_truncate_set` additional flag and using
`TERNARY_UNSET` value instead.
---
src/backend/access/common/reloptions.c | 129 ++++++++++++++++++++-----
src/backend/commands/vacuum.c | 4 +-
src/include/access/reloptions.h | 26 ++---
src/include/c.h | 16 +++
src/include/utils/rel.h | 3 +-
5 files changed, 135 insertions(+), 43 deletions(-)
diff --git a/src/backend/access/common/reloptions.c b/src/backend/access/common/reloptions.c
index 0af3fea68f..f543f11001 100644
--- a/src/backend/access/common/reloptions.c
+++ b/src/backend/access/common/reloptions.c
@@ -40,9 +40,9 @@
*
* To add an option:
*
- * (i) decide on a type (bool, integer, real, enum, string), name, default
- * value, upper and lower bounds (if applicable); for strings, consider a
- * validation routine.
+ * (i) decide on a type (bool, ternary, integer, real, enum, string), name,
+ * default value, upper and lower bounds (if applicable); for strings,
+ * consider a validation routine.
* (ii) add a record below (or use add_<type>_reloption).
* (iii) add it to the appropriate options struct (perhaps StdRdOptions)
* (iv) add it to the appropriate handling routine (perhaps
@@ -147,15 +147,6 @@ static relopt_bool boolRelOpts[] =
},
false
},
- {
- {
- "vacuum_truncate",
- "Enables vacuum to truncate empty pages at the end of this table",
- RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
- ShareUpdateExclusiveLock
- },
- true
- },
{
{
"deduplicate_items",
@@ -170,6 +161,21 @@ static relopt_bool boolRelOpts[] =
{{NULL}}
};
+static relopt_ternary ternaryRelOpts[] =
+{
+ {
+ {
+ "vacuum_truncate",
+ "Enables vacuum to truncate empty pages at the end of this table",
+ RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
+ ShareUpdateExclusiveLock
+ },
+ TERNARY_UNSET
+ },
+ /* list terminator */
+ {{NULL}}
+};
+
static relopt_int intRelOpts[] =
{
{
@@ -600,6 +606,13 @@ initialize_reloptions(void)
boolRelOpts[i].gen.lockmode));
j++;
}
+ for (i = 0; ternaryRelOpts[i].gen.name; i++)
+ {
+ Assert(DoLockModesConflict(ternaryRelOpts[i].gen.lockmode,
+ ternaryRelOpts[i].gen.lockmode));
+ j++;
+ }
+
for (i = 0; intRelOpts[i].gen.name; i++)
{
Assert(DoLockModesConflict(intRelOpts[i].gen.lockmode,
@@ -640,6 +653,14 @@ initialize_reloptions(void)
j++;
}
+ for (i = 0; ternaryRelOpts[i].gen.name; i++)
+ {
+ relOpts[j] = &ternaryRelOpts[i].gen;
+ relOpts[j]->type = RELOPT_TYPE_TERNARY;
+ relOpts[j]->namelen = strlen(relOpts[j]->name);
+ j++;
+ }
+
for (i = 0; intRelOpts[i].gen.name; i++)
{
relOpts[j] = &intRelOpts[i].gen;
@@ -800,6 +821,9 @@ allocate_reloption(bits32 kinds, int type, const char *name, const char *desc,
case RELOPT_TYPE_BOOL:
size = sizeof(relopt_bool);
break;
+ case RELOPT_TYPE_TERNARY:
+ size = sizeof(relopt_ternary);
+ break;
case RELOPT_TYPE_INT:
size = sizeof(relopt_int);
break;
@@ -883,6 +907,54 @@ add_local_bool_reloption(local_relopts *relopts, const char *name,
add_local_reloption(relopts, (relopt_gen *) newoption, offset);
}
+/*
+ * init_ternary_reloption
+ * Allocate and initialize a new ternary reloption
+ */
+static relopt_ternary *
+init_ternary_reloption(bits32 kinds, const char *name, const char *desc,
+ ternary default_val, LOCKMODE lockmode)
+{
+ relopt_ternary *newoption;
+
+ newoption = (relopt_ternary *) allocate_reloption(kinds,
+ RELOPT_TYPE_TERNARY, name, desc, lockmode);
+ newoption->default_val = default_val;
+
+ return newoption;
+}
+
+/*
+ * add_ternary_reloption
+ * Add a new ternary reloption
+ */
+void
+add_ternary_reloption(bits32 kinds, const char *name, const char *desc,
+ ternary default_val, LOCKMODE lockmode)
+{
+ relopt_ternary *newoption = init_ternary_reloption(kinds, name, desc,
+ default_val, lockmode);
+
+ add_reloption((relopt_gen *) newoption);
+}
+
+/*
+ * add_local_ternary_reloption
+ * Add a new ternary local reloption
+ *
+ * 'offset' is offset of ternary-typed field.
+ */
+void
+add_local_ternary_reloption(local_relopts *relopts, const char *name,
+ const char *desc, ternary default_val,
+ int offset)
+{
+ relopt_ternary *newoption = init_ternary_reloption(RELOPT_KIND_LOCAL,
+ name, desc,
+ default_val, 0);
+
+ add_local_reloption(relopts, (relopt_gen *) newoption, offset);
+}
/*
* init_real_reloption
@@ -1617,6 +1689,18 @@ parse_one_reloption(relopt_value *option, char *text_str, int text_len,
option->gen->name, value)));
}
break;
+ case RELOPT_TYPE_TERNARY:
+ {
+ bool b;
+ parsed = parse_bool(value, &b);
+ option->values.ternary_val = b ? TERNARY_TRUE : TERNARY_FALSE;
+ if (validate && !parsed)
+ ereport(ERROR,
+ (errcode(ERRCODE_INVALID_PARAMETER_VALUE),
+ errmsg("invalid value for ternary option \"%s\": %s",
+ option->gen->name, value)));
+ }
+ break;
case RELOPT_TYPE_INT:
{
relopt_int *optint = (relopt_int *) option->gen;
@@ -1780,17 +1864,6 @@ fillRelOptions(void *rdopts, Size basesize,
char *itempos = ((char *) rdopts) + elems[j].offset;
char *string_val;
- /*
- * If isset_offset is provided, store whether the reloption is
- * set there.
- */
- if (elems[j].isset_offset > 0)
- {
- char *setpos = ((char *) rdopts) + elems[j].isset_offset;
-
- *(bool *) setpos = options[i].isset;
- }
-
switch (options[i].gen->type)
{
case RELOPT_TYPE_BOOL:
@@ -1798,6 +1871,11 @@ fillRelOptions(void *rdopts, Size basesize,
options[i].values.bool_val :
((relopt_bool *) options[i].gen)->default_val;
break;
+ case RELOPT_TYPE_TERNARY:
+ *(ternary *) itempos = options[i].isset ?
+ options[i].values.ternary_val :
+ ((relopt_ternary *) options[i].gen)->default_val;
+ break;
case RELOPT_TYPE_INT:
*(int *) itempos = options[i].isset ?
options[i].values.int_val :
@@ -1912,8 +1990,8 @@ default_reloptions(Datum reloptions, bool validate, relopt_kind kind)
offsetof(StdRdOptions, parallel_workers)},
{"vacuum_index_cleanup", RELOPT_TYPE_ENUM,
offsetof(StdRdOptions, vacuum_index_cleanup)},
- {"vacuum_truncate", RELOPT_TYPE_BOOL,
- offsetof(StdRdOptions, vacuum_truncate), offsetof(StdRdOptions, vacuum_truncate_set)},
+ {"vacuum_truncate", RELOPT_TYPE_TERNARY,
+ offsetof(StdRdOptions, vacuum_truncate)},
{"vacuum_max_eager_freeze_failure_rate", RELOPT_TYPE_REAL,
offsetof(StdRdOptions, vacuum_max_eager_freeze_failure_rate)}
};
@@ -1993,7 +2071,6 @@ build_local_reloptions(local_relopts *relopts, Datum options, bool validate)
elems[i].optname = opt->option->name;
elems[i].opttype = opt->option->type;
elems[i].offset = opt->offset;
- elems[i].isset_offset = 0; /* not supported for local relopts yet */
i++;
}
diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c
index 733ef40ae7..977babff54 100644
--- a/src/backend/commands/vacuum.c
+++ b/src/backend/commands/vacuum.c
@@ -2221,9 +2221,9 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams params,
{
StdRdOptions *opts = (StdRdOptions *) rel->rd_options;
- if (opts && opts->vacuum_truncate_set)
+ if (opts && opts->vacuum_truncate != TERNARY_UNSET)
{
- if (opts->vacuum_truncate)
+ if (opts->vacuum_truncate == TERNARY_TRUE)
params.truncate = VACOPTVALUE_ENABLED;
else
params.truncate = VACOPTVALUE_DISABLED;
diff --git a/src/include/access/reloptions.h b/src/include/access/reloptions.h
index a604a4702c..a436697658 100644
--- a/src/include/access/reloptions.h
+++ b/src/include/access/reloptions.h
@@ -29,6 +29,7 @@
typedef enum relopt_type
{
RELOPT_TYPE_BOOL,
+ RELOPT_TYPE_TERNARY, /* on, off, unset */
RELOPT_TYPE_INT,
RELOPT_TYPE_REAL,
RELOPT_TYPE_ENUM,
@@ -80,6 +81,7 @@ typedef struct relopt_value
union
{
bool bool_val;
+ ternary ternary_val;
int int_val;
double real_val;
int enum_val;
@@ -94,6 +96,12 @@ typedef struct relopt_bool
bool default_val;
} relopt_bool;
+typedef struct relopt_rernary
+{
+ relopt_gen gen;
+ int default_val;
+} relopt_ternary;
+
typedef struct relopt_int
{
relopt_gen gen;
@@ -152,19 +160,6 @@ typedef struct
const char *optname; /* option's name */
relopt_type opttype; /* option's datatype */
int offset; /* offset of field in result struct */
-
- /*
- * isset_offset is an optional offset of a field in the result struct that
- * stores whether the option is explicitly set for the relation or if it
- * just picked up the default value. In most cases, this can be
- * accomplished by giving the reloption a special out-of-range default
- * value (e.g., some integer reloptions use -2), but this isn't always
- * possible. For example, a Boolean reloption cannot be given an
- * out-of-range default, so we need another way to discover the source of
- * its value. This offset is only used if given a value greater than
- * zero.
- */
- int isset_offset;
} relopt_parse_elt;
/* Local reloption definition */
@@ -195,6 +190,8 @@ typedef struct local_relopts
extern relopt_kind add_reloption_kind(void);
extern void add_bool_reloption(bits32 kinds, const char *name, const char *desc,
bool default_val, LOCKMODE lockmode);
+extern void add_ternary_reloption(bits32 kinds, const char *name,
+ const char *desc, int default_val, LOCKMODE lockmode);
extern void add_int_reloption(bits32 kinds, const char *name, const char *desc,
int default_val, int min_val, int max_val,
LOCKMODE lockmode);
@@ -214,6 +211,9 @@ extern void register_reloptions_validator(local_relopts *relopts,
extern void add_local_bool_reloption(local_relopts *relopts, const char *name,
const char *desc, bool default_val,
int offset);
+extern void add_local_ternary_reloption(local_relopts *relopts,
+ const char *name, const char *desc,
+ ternary default_val, int offset);
extern void add_local_int_reloption(local_relopts *relopts, const char *name,
const char *desc, int default_val,
int min_val, int max_val, int offset);
diff --git a/src/include/c.h b/src/include/c.h
index 39022f8a9d..9c59ccccf8 100644
--- a/src/include/c.h
+++ b/src/include/c.h
@@ -488,6 +488,22 @@ typedef void (*pg_funcptr_t) (void);
#include <stdbool.h>
+/*
+ * ternary
+ * Boolean value with an extrea "unset" option
+ *
+ * Ternary data type is used in relation options that can be "true", "false" or
+ * "unset". Since relation options are used deep inside the PostgreSQL code,
+ * this type is declared globally.
+*/
+
+typedef enum ternary
+{
+ TERNARY_FALSE = 0,
+ TERNARY_TRUE = 1,
+ TERNARY_UNSET = -1
+} ternary;
+
/* ----------------------------------------------------------------
* Section 3: standard system types
diff --git a/src/include/utils/rel.h b/src/include/utils/rel.h
index b552359915..7346d618f9 100644
--- a/src/include/utils/rel.h
+++ b/src/include/utils/rel.h
@@ -346,8 +346,7 @@ typedef struct StdRdOptions
bool user_catalog_table; /* use as an additional catalog relation */
int parallel_workers; /* max number of parallel workers */
StdRdOptIndexCleanup vacuum_index_cleanup; /* controls index vacuuming */
- bool vacuum_truncate; /* enables vacuum to truncate a relation */
- bool vacuum_truncate_set; /* whether vacuum_truncate is set */
+ ternary vacuum_truncate; /* enables vacuum to truncate a relation */
/*
* Fraction of pages in a relation that vacuum can eagerly scan and fail
--
2.39.2
--nextPart5129932.5fSG56mABF
Content-Disposition: attachment;
filename="v2-0003-Add-alias-to-be-used-as-unset-state.patch"
Content-Transfer-Encoding: 7Bit
Content-Type: text/x-patch; charset="unicode-2-0-utf-8";
name="v2-0003-Add-alias-to-be-used-as-unset-state.patch"
^ permalink raw reply [nested|flat] 868+ messages in thread
* [PATCH v2a 2/4] Introduce ternary reloptions
@ 2025-09-04 14:41 Nikolay Shaplov <[email protected]>
0 siblings, 0 replies; 868+ messages in thread
From: Nikolay Shaplov @ 2025-09-04 14:41 UTC (permalink / raw)
Introduce ternary reloption as a replacement for current `vacuum_truncate`
implementation. Remove `vacuum_truncate_set` additional flag and using
`TERNARY_UNSET` value instead.
---
src/backend/access/common/reloptions.c | 129 ++++++++++++++++++++-----
src/backend/commands/vacuum.c | 4 +-
src/include/access/reloptions.h | 26 ++---
src/include/c.h | 16 +++
src/include/utils/rel.h | 3 +-
5 files changed, 135 insertions(+), 43 deletions(-)
diff --git a/src/backend/access/common/reloptions.c b/src/backend/access/common/reloptions.c
index 0af3fea68f..f543f11001 100644
--- a/src/backend/access/common/reloptions.c
+++ b/src/backend/access/common/reloptions.c
@@ -40,9 +40,9 @@
*
* To add an option:
*
- * (i) decide on a type (bool, integer, real, enum, string), name, default
- * value, upper and lower bounds (if applicable); for strings, consider a
- * validation routine.
+ * (i) decide on a type (bool, ternary, integer, real, enum, string), name,
+ * default value, upper and lower bounds (if applicable); for strings,
+ * consider a validation routine.
* (ii) add a record below (or use add_<type>_reloption).
* (iii) add it to the appropriate options struct (perhaps StdRdOptions)
* (iv) add it to the appropriate handling routine (perhaps
@@ -147,15 +147,6 @@ static relopt_bool boolRelOpts[] =
},
false
},
- {
- {
- "vacuum_truncate",
- "Enables vacuum to truncate empty pages at the end of this table",
- RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
- ShareUpdateExclusiveLock
- },
- true
- },
{
{
"deduplicate_items",
@@ -170,6 +161,21 @@ static relopt_bool boolRelOpts[] =
{{NULL}}
};
+static relopt_ternary ternaryRelOpts[] =
+{
+ {
+ {
+ "vacuum_truncate",
+ "Enables vacuum to truncate empty pages at the end of this table",
+ RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
+ ShareUpdateExclusiveLock
+ },
+ TERNARY_UNSET
+ },
+ /* list terminator */
+ {{NULL}}
+};
+
static relopt_int intRelOpts[] =
{
{
@@ -600,6 +606,13 @@ initialize_reloptions(void)
boolRelOpts[i].gen.lockmode));
j++;
}
+ for (i = 0; ternaryRelOpts[i].gen.name; i++)
+ {
+ Assert(DoLockModesConflict(ternaryRelOpts[i].gen.lockmode,
+ ternaryRelOpts[i].gen.lockmode));
+ j++;
+ }
+
for (i = 0; intRelOpts[i].gen.name; i++)
{
Assert(DoLockModesConflict(intRelOpts[i].gen.lockmode,
@@ -640,6 +653,14 @@ initialize_reloptions(void)
j++;
}
+ for (i = 0; ternaryRelOpts[i].gen.name; i++)
+ {
+ relOpts[j] = &ternaryRelOpts[i].gen;
+ relOpts[j]->type = RELOPT_TYPE_TERNARY;
+ relOpts[j]->namelen = strlen(relOpts[j]->name);
+ j++;
+ }
+
for (i = 0; intRelOpts[i].gen.name; i++)
{
relOpts[j] = &intRelOpts[i].gen;
@@ -800,6 +821,9 @@ allocate_reloption(bits32 kinds, int type, const char *name, const char *desc,
case RELOPT_TYPE_BOOL:
size = sizeof(relopt_bool);
break;
+ case RELOPT_TYPE_TERNARY:
+ size = sizeof(relopt_ternary);
+ break;
case RELOPT_TYPE_INT:
size = sizeof(relopt_int);
break;
@@ -883,6 +907,54 @@ add_local_bool_reloption(local_relopts *relopts, const char *name,
add_local_reloption(relopts, (relopt_gen *) newoption, offset);
}
+/*
+ * init_ternary_reloption
+ * Allocate and initialize a new ternary reloption
+ */
+static relopt_ternary *
+init_ternary_reloption(bits32 kinds, const char *name, const char *desc,
+ ternary default_val, LOCKMODE lockmode)
+{
+ relopt_ternary *newoption;
+
+ newoption = (relopt_ternary *) allocate_reloption(kinds,
+ RELOPT_TYPE_TERNARY, name, desc, lockmode);
+ newoption->default_val = default_val;
+
+ return newoption;
+}
+
+/*
+ * add_ternary_reloption
+ * Add a new ternary reloption
+ */
+void
+add_ternary_reloption(bits32 kinds, const char *name, const char *desc,
+ ternary default_val, LOCKMODE lockmode)
+{
+ relopt_ternary *newoption = init_ternary_reloption(kinds, name, desc,
+ default_val, lockmode);
+
+ add_reloption((relopt_gen *) newoption);
+}
+
+/*
+ * add_local_ternary_reloption
+ * Add a new ternary local reloption
+ *
+ * 'offset' is offset of ternary-typed field.
+ */
+void
+add_local_ternary_reloption(local_relopts *relopts, const char *name,
+ const char *desc, ternary default_val,
+ int offset)
+{
+ relopt_ternary *newoption = init_ternary_reloption(RELOPT_KIND_LOCAL,
+ name, desc,
+ default_val, 0);
+
+ add_local_reloption(relopts, (relopt_gen *) newoption, offset);
+}
/*
* init_real_reloption
@@ -1617,6 +1689,18 @@ parse_one_reloption(relopt_value *option, char *text_str, int text_len,
option->gen->name, value)));
}
break;
+ case RELOPT_TYPE_TERNARY:
+ {
+ bool b;
+ parsed = parse_bool(value, &b);
+ option->values.ternary_val = b ? TERNARY_TRUE : TERNARY_FALSE;
+ if (validate && !parsed)
+ ereport(ERROR,
+ (errcode(ERRCODE_INVALID_PARAMETER_VALUE),
+ errmsg("invalid value for ternary option \"%s\": %s",
+ option->gen->name, value)));
+ }
+ break;
case RELOPT_TYPE_INT:
{
relopt_int *optint = (relopt_int *) option->gen;
@@ -1780,17 +1864,6 @@ fillRelOptions(void *rdopts, Size basesize,
char *itempos = ((char *) rdopts) + elems[j].offset;
char *string_val;
- /*
- * If isset_offset is provided, store whether the reloption is
- * set there.
- */
- if (elems[j].isset_offset > 0)
- {
- char *setpos = ((char *) rdopts) + elems[j].isset_offset;
-
- *(bool *) setpos = options[i].isset;
- }
-
switch (options[i].gen->type)
{
case RELOPT_TYPE_BOOL:
@@ -1798,6 +1871,11 @@ fillRelOptions(void *rdopts, Size basesize,
options[i].values.bool_val :
((relopt_bool *) options[i].gen)->default_val;
break;
+ case RELOPT_TYPE_TERNARY:
+ *(ternary *) itempos = options[i].isset ?
+ options[i].values.ternary_val :
+ ((relopt_ternary *) options[i].gen)->default_val;
+ break;
case RELOPT_TYPE_INT:
*(int *) itempos = options[i].isset ?
options[i].values.int_val :
@@ -1912,8 +1990,8 @@ default_reloptions(Datum reloptions, bool validate, relopt_kind kind)
offsetof(StdRdOptions, parallel_workers)},
{"vacuum_index_cleanup", RELOPT_TYPE_ENUM,
offsetof(StdRdOptions, vacuum_index_cleanup)},
- {"vacuum_truncate", RELOPT_TYPE_BOOL,
- offsetof(StdRdOptions, vacuum_truncate), offsetof(StdRdOptions, vacuum_truncate_set)},
+ {"vacuum_truncate", RELOPT_TYPE_TERNARY,
+ offsetof(StdRdOptions, vacuum_truncate)},
{"vacuum_max_eager_freeze_failure_rate", RELOPT_TYPE_REAL,
offsetof(StdRdOptions, vacuum_max_eager_freeze_failure_rate)}
};
@@ -1993,7 +2071,6 @@ build_local_reloptions(local_relopts *relopts, Datum options, bool validate)
elems[i].optname = opt->option->name;
elems[i].opttype = opt->option->type;
elems[i].offset = opt->offset;
- elems[i].isset_offset = 0; /* not supported for local relopts yet */
i++;
}
diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c
index 733ef40ae7..977babff54 100644
--- a/src/backend/commands/vacuum.c
+++ b/src/backend/commands/vacuum.c
@@ -2221,9 +2221,9 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams params,
{
StdRdOptions *opts = (StdRdOptions *) rel->rd_options;
- if (opts && opts->vacuum_truncate_set)
+ if (opts && opts->vacuum_truncate != TERNARY_UNSET)
{
- if (opts->vacuum_truncate)
+ if (opts->vacuum_truncate == TERNARY_TRUE)
params.truncate = VACOPTVALUE_ENABLED;
else
params.truncate = VACOPTVALUE_DISABLED;
diff --git a/src/include/access/reloptions.h b/src/include/access/reloptions.h
index a604a4702c..a436697658 100644
--- a/src/include/access/reloptions.h
+++ b/src/include/access/reloptions.h
@@ -29,6 +29,7 @@
typedef enum relopt_type
{
RELOPT_TYPE_BOOL,
+ RELOPT_TYPE_TERNARY, /* on, off, unset */
RELOPT_TYPE_INT,
RELOPT_TYPE_REAL,
RELOPT_TYPE_ENUM,
@@ -80,6 +81,7 @@ typedef struct relopt_value
union
{
bool bool_val;
+ ternary ternary_val;
int int_val;
double real_val;
int enum_val;
@@ -94,6 +96,12 @@ typedef struct relopt_bool
bool default_val;
} relopt_bool;
+typedef struct relopt_rernary
+{
+ relopt_gen gen;
+ int default_val;
+} relopt_ternary;
+
typedef struct relopt_int
{
relopt_gen gen;
@@ -152,19 +160,6 @@ typedef struct
const char *optname; /* option's name */
relopt_type opttype; /* option's datatype */
int offset; /* offset of field in result struct */
-
- /*
- * isset_offset is an optional offset of a field in the result struct that
- * stores whether the option is explicitly set for the relation or if it
- * just picked up the default value. In most cases, this can be
- * accomplished by giving the reloption a special out-of-range default
- * value (e.g., some integer reloptions use -2), but this isn't always
- * possible. For example, a Boolean reloption cannot be given an
- * out-of-range default, so we need another way to discover the source of
- * its value. This offset is only used if given a value greater than
- * zero.
- */
- int isset_offset;
} relopt_parse_elt;
/* Local reloption definition */
@@ -195,6 +190,8 @@ typedef struct local_relopts
extern relopt_kind add_reloption_kind(void);
extern void add_bool_reloption(bits32 kinds, const char *name, const char *desc,
bool default_val, LOCKMODE lockmode);
+extern void add_ternary_reloption(bits32 kinds, const char *name,
+ const char *desc, int default_val, LOCKMODE lockmode);
extern void add_int_reloption(bits32 kinds, const char *name, const char *desc,
int default_val, int min_val, int max_val,
LOCKMODE lockmode);
@@ -214,6 +211,9 @@ extern void register_reloptions_validator(local_relopts *relopts,
extern void add_local_bool_reloption(local_relopts *relopts, const char *name,
const char *desc, bool default_val,
int offset);
+extern void add_local_ternary_reloption(local_relopts *relopts,
+ const char *name, const char *desc,
+ ternary default_val, int offset);
extern void add_local_int_reloption(local_relopts *relopts, const char *name,
const char *desc, int default_val,
int min_val, int max_val, int offset);
diff --git a/src/include/c.h b/src/include/c.h
index 39022f8a9d..9c59ccccf8 100644
--- a/src/include/c.h
+++ b/src/include/c.h
@@ -488,6 +488,22 @@ typedef void (*pg_funcptr_t) (void);
#include <stdbool.h>
+/*
+ * ternary
+ * Boolean value with an extrea "unset" option
+ *
+ * Ternary data type is used in relation options that can be "true", "false" or
+ * "unset". Since relation options are used deep inside the PostgreSQL code,
+ * this type is declared globally.
+*/
+
+typedef enum ternary
+{
+ TERNARY_FALSE = 0,
+ TERNARY_TRUE = 1,
+ TERNARY_UNSET = -1
+} ternary;
+
/* ----------------------------------------------------------------
* Section 3: standard system types
diff --git a/src/include/utils/rel.h b/src/include/utils/rel.h
index b552359915..7346d618f9 100644
--- a/src/include/utils/rel.h
+++ b/src/include/utils/rel.h
@@ -346,8 +346,7 @@ typedef struct StdRdOptions
bool user_catalog_table; /* use as an additional catalog relation */
int parallel_workers; /* max number of parallel workers */
StdRdOptIndexCleanup vacuum_index_cleanup; /* controls index vacuuming */
- bool vacuum_truncate; /* enables vacuum to truncate a relation */
- bool vacuum_truncate_set; /* whether vacuum_truncate is set */
+ ternary vacuum_truncate; /* enables vacuum to truncate a relation */
/*
* Fraction of pages in a relation that vacuum can eagerly scan and fail
--
2.39.2
--nextPart4155600.3ZeAukHxDK
Content-Disposition: attachment;
filename="v2a-0003-Add-alias-to-be-used-as-unset-state.patch"
Content-Transfer-Encoding: 7Bit
Content-Type: text/x-patch; charset="unicode-2-0-utf-8";
name="v2a-0003-Add-alias-to-be-used-as-unset-state.patch"
^ permalink raw reply [nested|flat] 868+ messages in thread
* [PATCH v2 2/4] Introduce ternary reloptions
@ 2025-09-04 14:41 Nikolay Shaplov <[email protected]>
0 siblings, 0 replies; 868+ messages in thread
From: Nikolay Shaplov @ 2025-09-04 14:41 UTC (permalink / raw)
Introduce ternary reloption as a replacement for current `vacuum_truncate`
implementation. Remove `vacuum_truncate_set` additional flag and using
`TERNARY_UNSET` value instead.
---
src/backend/access/common/reloptions.c | 129 ++++++++++++++++++++-----
src/backend/commands/vacuum.c | 4 +-
src/include/access/reloptions.h | 26 ++---
src/include/c.h | 16 +++
src/include/utils/rel.h | 3 +-
5 files changed, 135 insertions(+), 43 deletions(-)
diff --git a/src/backend/access/common/reloptions.c b/src/backend/access/common/reloptions.c
index 0af3fea68f..f543f11001 100644
--- a/src/backend/access/common/reloptions.c
+++ b/src/backend/access/common/reloptions.c
@@ -40,9 +40,9 @@
*
* To add an option:
*
- * (i) decide on a type (bool, integer, real, enum, string), name, default
- * value, upper and lower bounds (if applicable); for strings, consider a
- * validation routine.
+ * (i) decide on a type (bool, ternary, integer, real, enum, string), name,
+ * default value, upper and lower bounds (if applicable); for strings,
+ * consider a validation routine.
* (ii) add a record below (or use add_<type>_reloption).
* (iii) add it to the appropriate options struct (perhaps StdRdOptions)
* (iv) add it to the appropriate handling routine (perhaps
@@ -147,15 +147,6 @@ static relopt_bool boolRelOpts[] =
},
false
},
- {
- {
- "vacuum_truncate",
- "Enables vacuum to truncate empty pages at the end of this table",
- RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
- ShareUpdateExclusiveLock
- },
- true
- },
{
{
"deduplicate_items",
@@ -170,6 +161,21 @@ static relopt_bool boolRelOpts[] =
{{NULL}}
};
+static relopt_ternary ternaryRelOpts[] =
+{
+ {
+ {
+ "vacuum_truncate",
+ "Enables vacuum to truncate empty pages at the end of this table",
+ RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
+ ShareUpdateExclusiveLock
+ },
+ TERNARY_UNSET
+ },
+ /* list terminator */
+ {{NULL}}
+};
+
static relopt_int intRelOpts[] =
{
{
@@ -600,6 +606,13 @@ initialize_reloptions(void)
boolRelOpts[i].gen.lockmode));
j++;
}
+ for (i = 0; ternaryRelOpts[i].gen.name; i++)
+ {
+ Assert(DoLockModesConflict(ternaryRelOpts[i].gen.lockmode,
+ ternaryRelOpts[i].gen.lockmode));
+ j++;
+ }
+
for (i = 0; intRelOpts[i].gen.name; i++)
{
Assert(DoLockModesConflict(intRelOpts[i].gen.lockmode,
@@ -640,6 +653,14 @@ initialize_reloptions(void)
j++;
}
+ for (i = 0; ternaryRelOpts[i].gen.name; i++)
+ {
+ relOpts[j] = &ternaryRelOpts[i].gen;
+ relOpts[j]->type = RELOPT_TYPE_TERNARY;
+ relOpts[j]->namelen = strlen(relOpts[j]->name);
+ j++;
+ }
+
for (i = 0; intRelOpts[i].gen.name; i++)
{
relOpts[j] = &intRelOpts[i].gen;
@@ -800,6 +821,9 @@ allocate_reloption(bits32 kinds, int type, const char *name, const char *desc,
case RELOPT_TYPE_BOOL:
size = sizeof(relopt_bool);
break;
+ case RELOPT_TYPE_TERNARY:
+ size = sizeof(relopt_ternary);
+ break;
case RELOPT_TYPE_INT:
size = sizeof(relopt_int);
break;
@@ -883,6 +907,54 @@ add_local_bool_reloption(local_relopts *relopts, const char *name,
add_local_reloption(relopts, (relopt_gen *) newoption, offset);
}
+/*
+ * init_ternary_reloption
+ * Allocate and initialize a new ternary reloption
+ */
+static relopt_ternary *
+init_ternary_reloption(bits32 kinds, const char *name, const char *desc,
+ ternary default_val, LOCKMODE lockmode)
+{
+ relopt_ternary *newoption;
+
+ newoption = (relopt_ternary *) allocate_reloption(kinds,
+ RELOPT_TYPE_TERNARY, name, desc, lockmode);
+ newoption->default_val = default_val;
+
+ return newoption;
+}
+
+/*
+ * add_ternary_reloption
+ * Add a new ternary reloption
+ */
+void
+add_ternary_reloption(bits32 kinds, const char *name, const char *desc,
+ ternary default_val, LOCKMODE lockmode)
+{
+ relopt_ternary *newoption = init_ternary_reloption(kinds, name, desc,
+ default_val, lockmode);
+
+ add_reloption((relopt_gen *) newoption);
+}
+
+/*
+ * add_local_ternary_reloption
+ * Add a new ternary local reloption
+ *
+ * 'offset' is offset of ternary-typed field.
+ */
+void
+add_local_ternary_reloption(local_relopts *relopts, const char *name,
+ const char *desc, ternary default_val,
+ int offset)
+{
+ relopt_ternary *newoption = init_ternary_reloption(RELOPT_KIND_LOCAL,
+ name, desc,
+ default_val, 0);
+
+ add_local_reloption(relopts, (relopt_gen *) newoption, offset);
+}
/*
* init_real_reloption
@@ -1617,6 +1689,18 @@ parse_one_reloption(relopt_value *option, char *text_str, int text_len,
option->gen->name, value)));
}
break;
+ case RELOPT_TYPE_TERNARY:
+ {
+ bool b;
+ parsed = parse_bool(value, &b);
+ option->values.ternary_val = b ? TERNARY_TRUE : TERNARY_FALSE;
+ if (validate && !parsed)
+ ereport(ERROR,
+ (errcode(ERRCODE_INVALID_PARAMETER_VALUE),
+ errmsg("invalid value for ternary option \"%s\": %s",
+ option->gen->name, value)));
+ }
+ break;
case RELOPT_TYPE_INT:
{
relopt_int *optint = (relopt_int *) option->gen;
@@ -1780,17 +1864,6 @@ fillRelOptions(void *rdopts, Size basesize,
char *itempos = ((char *) rdopts) + elems[j].offset;
char *string_val;
- /*
- * If isset_offset is provided, store whether the reloption is
- * set there.
- */
- if (elems[j].isset_offset > 0)
- {
- char *setpos = ((char *) rdopts) + elems[j].isset_offset;
-
- *(bool *) setpos = options[i].isset;
- }
-
switch (options[i].gen->type)
{
case RELOPT_TYPE_BOOL:
@@ -1798,6 +1871,11 @@ fillRelOptions(void *rdopts, Size basesize,
options[i].values.bool_val :
((relopt_bool *) options[i].gen)->default_val;
break;
+ case RELOPT_TYPE_TERNARY:
+ *(ternary *) itempos = options[i].isset ?
+ options[i].values.ternary_val :
+ ((relopt_ternary *) options[i].gen)->default_val;
+ break;
case RELOPT_TYPE_INT:
*(int *) itempos = options[i].isset ?
options[i].values.int_val :
@@ -1912,8 +1990,8 @@ default_reloptions(Datum reloptions, bool validate, relopt_kind kind)
offsetof(StdRdOptions, parallel_workers)},
{"vacuum_index_cleanup", RELOPT_TYPE_ENUM,
offsetof(StdRdOptions, vacuum_index_cleanup)},
- {"vacuum_truncate", RELOPT_TYPE_BOOL,
- offsetof(StdRdOptions, vacuum_truncate), offsetof(StdRdOptions, vacuum_truncate_set)},
+ {"vacuum_truncate", RELOPT_TYPE_TERNARY,
+ offsetof(StdRdOptions, vacuum_truncate)},
{"vacuum_max_eager_freeze_failure_rate", RELOPT_TYPE_REAL,
offsetof(StdRdOptions, vacuum_max_eager_freeze_failure_rate)}
};
@@ -1993,7 +2071,6 @@ build_local_reloptions(local_relopts *relopts, Datum options, bool validate)
elems[i].optname = opt->option->name;
elems[i].opttype = opt->option->type;
elems[i].offset = opt->offset;
- elems[i].isset_offset = 0; /* not supported for local relopts yet */
i++;
}
diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c
index 733ef40ae7..977babff54 100644
--- a/src/backend/commands/vacuum.c
+++ b/src/backend/commands/vacuum.c
@@ -2221,9 +2221,9 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams params,
{
StdRdOptions *opts = (StdRdOptions *) rel->rd_options;
- if (opts && opts->vacuum_truncate_set)
+ if (opts && opts->vacuum_truncate != TERNARY_UNSET)
{
- if (opts->vacuum_truncate)
+ if (opts->vacuum_truncate == TERNARY_TRUE)
params.truncate = VACOPTVALUE_ENABLED;
else
params.truncate = VACOPTVALUE_DISABLED;
diff --git a/src/include/access/reloptions.h b/src/include/access/reloptions.h
index a604a4702c..a436697658 100644
--- a/src/include/access/reloptions.h
+++ b/src/include/access/reloptions.h
@@ -29,6 +29,7 @@
typedef enum relopt_type
{
RELOPT_TYPE_BOOL,
+ RELOPT_TYPE_TERNARY, /* on, off, unset */
RELOPT_TYPE_INT,
RELOPT_TYPE_REAL,
RELOPT_TYPE_ENUM,
@@ -80,6 +81,7 @@ typedef struct relopt_value
union
{
bool bool_val;
+ ternary ternary_val;
int int_val;
double real_val;
int enum_val;
@@ -94,6 +96,12 @@ typedef struct relopt_bool
bool default_val;
} relopt_bool;
+typedef struct relopt_rernary
+{
+ relopt_gen gen;
+ int default_val;
+} relopt_ternary;
+
typedef struct relopt_int
{
relopt_gen gen;
@@ -152,19 +160,6 @@ typedef struct
const char *optname; /* option's name */
relopt_type opttype; /* option's datatype */
int offset; /* offset of field in result struct */
-
- /*
- * isset_offset is an optional offset of a field in the result struct that
- * stores whether the option is explicitly set for the relation or if it
- * just picked up the default value. In most cases, this can be
- * accomplished by giving the reloption a special out-of-range default
- * value (e.g., some integer reloptions use -2), but this isn't always
- * possible. For example, a Boolean reloption cannot be given an
- * out-of-range default, so we need another way to discover the source of
- * its value. This offset is only used if given a value greater than
- * zero.
- */
- int isset_offset;
} relopt_parse_elt;
/* Local reloption definition */
@@ -195,6 +190,8 @@ typedef struct local_relopts
extern relopt_kind add_reloption_kind(void);
extern void add_bool_reloption(bits32 kinds, const char *name, const char *desc,
bool default_val, LOCKMODE lockmode);
+extern void add_ternary_reloption(bits32 kinds, const char *name,
+ const char *desc, int default_val, LOCKMODE lockmode);
extern void add_int_reloption(bits32 kinds, const char *name, const char *desc,
int default_val, int min_val, int max_val,
LOCKMODE lockmode);
@@ -214,6 +211,9 @@ extern void register_reloptions_validator(local_relopts *relopts,
extern void add_local_bool_reloption(local_relopts *relopts, const char *name,
const char *desc, bool default_val,
int offset);
+extern void add_local_ternary_reloption(local_relopts *relopts,
+ const char *name, const char *desc,
+ ternary default_val, int offset);
extern void add_local_int_reloption(local_relopts *relopts, const char *name,
const char *desc, int default_val,
int min_val, int max_val, int offset);
diff --git a/src/include/c.h b/src/include/c.h
index 39022f8a9d..9c59ccccf8 100644
--- a/src/include/c.h
+++ b/src/include/c.h
@@ -488,6 +488,22 @@ typedef void (*pg_funcptr_t) (void);
#include <stdbool.h>
+/*
+ * ternary
+ * Boolean value with an extrea "unset" option
+ *
+ * Ternary data type is used in relation options that can be "true", "false" or
+ * "unset". Since relation options are used deep inside the PostgreSQL code,
+ * this type is declared globally.
+*/
+
+typedef enum ternary
+{
+ TERNARY_FALSE = 0,
+ TERNARY_TRUE = 1,
+ TERNARY_UNSET = -1
+} ternary;
+
/* ----------------------------------------------------------------
* Section 3: standard system types
diff --git a/src/include/utils/rel.h b/src/include/utils/rel.h
index b552359915..7346d618f9 100644
--- a/src/include/utils/rel.h
+++ b/src/include/utils/rel.h
@@ -346,8 +346,7 @@ typedef struct StdRdOptions
bool user_catalog_table; /* use as an additional catalog relation */
int parallel_workers; /* max number of parallel workers */
StdRdOptIndexCleanup vacuum_index_cleanup; /* controls index vacuuming */
- bool vacuum_truncate; /* enables vacuum to truncate a relation */
- bool vacuum_truncate_set; /* whether vacuum_truncate is set */
+ ternary vacuum_truncate; /* enables vacuum to truncate a relation */
/*
* Fraction of pages in a relation that vacuum can eagerly scan and fail
--
2.39.2
--nextPart5129932.5fSG56mABF
Content-Disposition: attachment;
filename="v2-0003-Add-alias-to-be-used-as-unset-state.patch"
Content-Transfer-Encoding: 7Bit
Content-Type: text/x-patch; charset="unicode-2-0-utf-8";
name="v2-0003-Add-alias-to-be-used-as-unset-state.patch"
^ permalink raw reply [nested|flat] 868+ messages in thread
* [PATCH v2a 2/4] Introduce ternary reloptions
@ 2025-09-04 14:41 Nikolay Shaplov <[email protected]>
0 siblings, 0 replies; 868+ messages in thread
From: Nikolay Shaplov @ 2025-09-04 14:41 UTC (permalink / raw)
Introduce ternary reloption as a replacement for current `vacuum_truncate`
implementation. Remove `vacuum_truncate_set` additional flag and using
`TERNARY_UNSET` value instead.
---
src/backend/access/common/reloptions.c | 129 ++++++++++++++++++++-----
src/backend/commands/vacuum.c | 4 +-
src/include/access/reloptions.h | 26 ++---
src/include/c.h | 16 +++
src/include/utils/rel.h | 3 +-
5 files changed, 135 insertions(+), 43 deletions(-)
diff --git a/src/backend/access/common/reloptions.c b/src/backend/access/common/reloptions.c
index 0af3fea68f..f543f11001 100644
--- a/src/backend/access/common/reloptions.c
+++ b/src/backend/access/common/reloptions.c
@@ -40,9 +40,9 @@
*
* To add an option:
*
- * (i) decide on a type (bool, integer, real, enum, string), name, default
- * value, upper and lower bounds (if applicable); for strings, consider a
- * validation routine.
+ * (i) decide on a type (bool, ternary, integer, real, enum, string), name,
+ * default value, upper and lower bounds (if applicable); for strings,
+ * consider a validation routine.
* (ii) add a record below (or use add_<type>_reloption).
* (iii) add it to the appropriate options struct (perhaps StdRdOptions)
* (iv) add it to the appropriate handling routine (perhaps
@@ -147,15 +147,6 @@ static relopt_bool boolRelOpts[] =
},
false
},
- {
- {
- "vacuum_truncate",
- "Enables vacuum to truncate empty pages at the end of this table",
- RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
- ShareUpdateExclusiveLock
- },
- true
- },
{
{
"deduplicate_items",
@@ -170,6 +161,21 @@ static relopt_bool boolRelOpts[] =
{{NULL}}
};
+static relopt_ternary ternaryRelOpts[] =
+{
+ {
+ {
+ "vacuum_truncate",
+ "Enables vacuum to truncate empty pages at the end of this table",
+ RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
+ ShareUpdateExclusiveLock
+ },
+ TERNARY_UNSET
+ },
+ /* list terminator */
+ {{NULL}}
+};
+
static relopt_int intRelOpts[] =
{
{
@@ -600,6 +606,13 @@ initialize_reloptions(void)
boolRelOpts[i].gen.lockmode));
j++;
}
+ for (i = 0; ternaryRelOpts[i].gen.name; i++)
+ {
+ Assert(DoLockModesConflict(ternaryRelOpts[i].gen.lockmode,
+ ternaryRelOpts[i].gen.lockmode));
+ j++;
+ }
+
for (i = 0; intRelOpts[i].gen.name; i++)
{
Assert(DoLockModesConflict(intRelOpts[i].gen.lockmode,
@@ -640,6 +653,14 @@ initialize_reloptions(void)
j++;
}
+ for (i = 0; ternaryRelOpts[i].gen.name; i++)
+ {
+ relOpts[j] = &ternaryRelOpts[i].gen;
+ relOpts[j]->type = RELOPT_TYPE_TERNARY;
+ relOpts[j]->namelen = strlen(relOpts[j]->name);
+ j++;
+ }
+
for (i = 0; intRelOpts[i].gen.name; i++)
{
relOpts[j] = &intRelOpts[i].gen;
@@ -800,6 +821,9 @@ allocate_reloption(bits32 kinds, int type, const char *name, const char *desc,
case RELOPT_TYPE_BOOL:
size = sizeof(relopt_bool);
break;
+ case RELOPT_TYPE_TERNARY:
+ size = sizeof(relopt_ternary);
+ break;
case RELOPT_TYPE_INT:
size = sizeof(relopt_int);
break;
@@ -883,6 +907,54 @@ add_local_bool_reloption(local_relopts *relopts, const char *name,
add_local_reloption(relopts, (relopt_gen *) newoption, offset);
}
+/*
+ * init_ternary_reloption
+ * Allocate and initialize a new ternary reloption
+ */
+static relopt_ternary *
+init_ternary_reloption(bits32 kinds, const char *name, const char *desc,
+ ternary default_val, LOCKMODE lockmode)
+{
+ relopt_ternary *newoption;
+
+ newoption = (relopt_ternary *) allocate_reloption(kinds,
+ RELOPT_TYPE_TERNARY, name, desc, lockmode);
+ newoption->default_val = default_val;
+
+ return newoption;
+}
+
+/*
+ * add_ternary_reloption
+ * Add a new ternary reloption
+ */
+void
+add_ternary_reloption(bits32 kinds, const char *name, const char *desc,
+ ternary default_val, LOCKMODE lockmode)
+{
+ relopt_ternary *newoption = init_ternary_reloption(kinds, name, desc,
+ default_val, lockmode);
+
+ add_reloption((relopt_gen *) newoption);
+}
+
+/*
+ * add_local_ternary_reloption
+ * Add a new ternary local reloption
+ *
+ * 'offset' is offset of ternary-typed field.
+ */
+void
+add_local_ternary_reloption(local_relopts *relopts, const char *name,
+ const char *desc, ternary default_val,
+ int offset)
+{
+ relopt_ternary *newoption = init_ternary_reloption(RELOPT_KIND_LOCAL,
+ name, desc,
+ default_val, 0);
+
+ add_local_reloption(relopts, (relopt_gen *) newoption, offset);
+}
/*
* init_real_reloption
@@ -1617,6 +1689,18 @@ parse_one_reloption(relopt_value *option, char *text_str, int text_len,
option->gen->name, value)));
}
break;
+ case RELOPT_TYPE_TERNARY:
+ {
+ bool b;
+ parsed = parse_bool(value, &b);
+ option->values.ternary_val = b ? TERNARY_TRUE : TERNARY_FALSE;
+ if (validate && !parsed)
+ ereport(ERROR,
+ (errcode(ERRCODE_INVALID_PARAMETER_VALUE),
+ errmsg("invalid value for ternary option \"%s\": %s",
+ option->gen->name, value)));
+ }
+ break;
case RELOPT_TYPE_INT:
{
relopt_int *optint = (relopt_int *) option->gen;
@@ -1780,17 +1864,6 @@ fillRelOptions(void *rdopts, Size basesize,
char *itempos = ((char *) rdopts) + elems[j].offset;
char *string_val;
- /*
- * If isset_offset is provided, store whether the reloption is
- * set there.
- */
- if (elems[j].isset_offset > 0)
- {
- char *setpos = ((char *) rdopts) + elems[j].isset_offset;
-
- *(bool *) setpos = options[i].isset;
- }
-
switch (options[i].gen->type)
{
case RELOPT_TYPE_BOOL:
@@ -1798,6 +1871,11 @@ fillRelOptions(void *rdopts, Size basesize,
options[i].values.bool_val :
((relopt_bool *) options[i].gen)->default_val;
break;
+ case RELOPT_TYPE_TERNARY:
+ *(ternary *) itempos = options[i].isset ?
+ options[i].values.ternary_val :
+ ((relopt_ternary *) options[i].gen)->default_val;
+ break;
case RELOPT_TYPE_INT:
*(int *) itempos = options[i].isset ?
options[i].values.int_val :
@@ -1912,8 +1990,8 @@ default_reloptions(Datum reloptions, bool validate, relopt_kind kind)
offsetof(StdRdOptions, parallel_workers)},
{"vacuum_index_cleanup", RELOPT_TYPE_ENUM,
offsetof(StdRdOptions, vacuum_index_cleanup)},
- {"vacuum_truncate", RELOPT_TYPE_BOOL,
- offsetof(StdRdOptions, vacuum_truncate), offsetof(StdRdOptions, vacuum_truncate_set)},
+ {"vacuum_truncate", RELOPT_TYPE_TERNARY,
+ offsetof(StdRdOptions, vacuum_truncate)},
{"vacuum_max_eager_freeze_failure_rate", RELOPT_TYPE_REAL,
offsetof(StdRdOptions, vacuum_max_eager_freeze_failure_rate)}
};
@@ -1993,7 +2071,6 @@ build_local_reloptions(local_relopts *relopts, Datum options, bool validate)
elems[i].optname = opt->option->name;
elems[i].opttype = opt->option->type;
elems[i].offset = opt->offset;
- elems[i].isset_offset = 0; /* not supported for local relopts yet */
i++;
}
diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c
index 733ef40ae7..977babff54 100644
--- a/src/backend/commands/vacuum.c
+++ b/src/backend/commands/vacuum.c
@@ -2221,9 +2221,9 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams params,
{
StdRdOptions *opts = (StdRdOptions *) rel->rd_options;
- if (opts && opts->vacuum_truncate_set)
+ if (opts && opts->vacuum_truncate != TERNARY_UNSET)
{
- if (opts->vacuum_truncate)
+ if (opts->vacuum_truncate == TERNARY_TRUE)
params.truncate = VACOPTVALUE_ENABLED;
else
params.truncate = VACOPTVALUE_DISABLED;
diff --git a/src/include/access/reloptions.h b/src/include/access/reloptions.h
index a604a4702c..a436697658 100644
--- a/src/include/access/reloptions.h
+++ b/src/include/access/reloptions.h
@@ -29,6 +29,7 @@
typedef enum relopt_type
{
RELOPT_TYPE_BOOL,
+ RELOPT_TYPE_TERNARY, /* on, off, unset */
RELOPT_TYPE_INT,
RELOPT_TYPE_REAL,
RELOPT_TYPE_ENUM,
@@ -80,6 +81,7 @@ typedef struct relopt_value
union
{
bool bool_val;
+ ternary ternary_val;
int int_val;
double real_val;
int enum_val;
@@ -94,6 +96,12 @@ typedef struct relopt_bool
bool default_val;
} relopt_bool;
+typedef struct relopt_rernary
+{
+ relopt_gen gen;
+ int default_val;
+} relopt_ternary;
+
typedef struct relopt_int
{
relopt_gen gen;
@@ -152,19 +160,6 @@ typedef struct
const char *optname; /* option's name */
relopt_type opttype; /* option's datatype */
int offset; /* offset of field in result struct */
-
- /*
- * isset_offset is an optional offset of a field in the result struct that
- * stores whether the option is explicitly set for the relation or if it
- * just picked up the default value. In most cases, this can be
- * accomplished by giving the reloption a special out-of-range default
- * value (e.g., some integer reloptions use -2), but this isn't always
- * possible. For example, a Boolean reloption cannot be given an
- * out-of-range default, so we need another way to discover the source of
- * its value. This offset is only used if given a value greater than
- * zero.
- */
- int isset_offset;
} relopt_parse_elt;
/* Local reloption definition */
@@ -195,6 +190,8 @@ typedef struct local_relopts
extern relopt_kind add_reloption_kind(void);
extern void add_bool_reloption(bits32 kinds, const char *name, const char *desc,
bool default_val, LOCKMODE lockmode);
+extern void add_ternary_reloption(bits32 kinds, const char *name,
+ const char *desc, int default_val, LOCKMODE lockmode);
extern void add_int_reloption(bits32 kinds, const char *name, const char *desc,
int default_val, int min_val, int max_val,
LOCKMODE lockmode);
@@ -214,6 +211,9 @@ extern void register_reloptions_validator(local_relopts *relopts,
extern void add_local_bool_reloption(local_relopts *relopts, const char *name,
const char *desc, bool default_val,
int offset);
+extern void add_local_ternary_reloption(local_relopts *relopts,
+ const char *name, const char *desc,
+ ternary default_val, int offset);
extern void add_local_int_reloption(local_relopts *relopts, const char *name,
const char *desc, int default_val,
int min_val, int max_val, int offset);
diff --git a/src/include/c.h b/src/include/c.h
index 39022f8a9d..9c59ccccf8 100644
--- a/src/include/c.h
+++ b/src/include/c.h
@@ -488,6 +488,22 @@ typedef void (*pg_funcptr_t) (void);
#include <stdbool.h>
+/*
+ * ternary
+ * Boolean value with an extrea "unset" option
+ *
+ * Ternary data type is used in relation options that can be "true", "false" or
+ * "unset". Since relation options are used deep inside the PostgreSQL code,
+ * this type is declared globally.
+*/
+
+typedef enum ternary
+{
+ TERNARY_FALSE = 0,
+ TERNARY_TRUE = 1,
+ TERNARY_UNSET = -1
+} ternary;
+
/* ----------------------------------------------------------------
* Section 3: standard system types
diff --git a/src/include/utils/rel.h b/src/include/utils/rel.h
index b552359915..7346d618f9 100644
--- a/src/include/utils/rel.h
+++ b/src/include/utils/rel.h
@@ -346,8 +346,7 @@ typedef struct StdRdOptions
bool user_catalog_table; /* use as an additional catalog relation */
int parallel_workers; /* max number of parallel workers */
StdRdOptIndexCleanup vacuum_index_cleanup; /* controls index vacuuming */
- bool vacuum_truncate; /* enables vacuum to truncate a relation */
- bool vacuum_truncate_set; /* whether vacuum_truncate is set */
+ ternary vacuum_truncate; /* enables vacuum to truncate a relation */
/*
* Fraction of pages in a relation that vacuum can eagerly scan and fail
--
2.39.2
--nextPart4155600.3ZeAukHxDK
Content-Disposition: attachment;
filename="v2a-0003-Add-alias-to-be-used-as-unset-state.patch"
Content-Transfer-Encoding: 7Bit
Content-Type: text/x-patch; charset="unicode-2-0-utf-8";
name="v2a-0003-Add-alias-to-be-used-as-unset-state.patch"
^ permalink raw reply [nested|flat] 868+ messages in thread
* [PATCH v2 2/4] Introduce ternary reloptions
@ 2025-09-04 14:41 Nikolay Shaplov <[email protected]>
0 siblings, 0 replies; 868+ messages in thread
From: Nikolay Shaplov @ 2025-09-04 14:41 UTC (permalink / raw)
Introduce ternary reloption as a replacement for current `vacuum_truncate`
implementation. Remove `vacuum_truncate_set` additional flag and using
`TERNARY_UNSET` value instead.
---
src/backend/access/common/reloptions.c | 129 ++++++++++++++++++++-----
src/backend/commands/vacuum.c | 4 +-
src/include/access/reloptions.h | 26 ++---
src/include/c.h | 16 +++
src/include/utils/rel.h | 3 +-
5 files changed, 135 insertions(+), 43 deletions(-)
diff --git a/src/backend/access/common/reloptions.c b/src/backend/access/common/reloptions.c
index 0af3fea68f..f543f11001 100644
--- a/src/backend/access/common/reloptions.c
+++ b/src/backend/access/common/reloptions.c
@@ -40,9 +40,9 @@
*
* To add an option:
*
- * (i) decide on a type (bool, integer, real, enum, string), name, default
- * value, upper and lower bounds (if applicable); for strings, consider a
- * validation routine.
+ * (i) decide on a type (bool, ternary, integer, real, enum, string), name,
+ * default value, upper and lower bounds (if applicable); for strings,
+ * consider a validation routine.
* (ii) add a record below (or use add_<type>_reloption).
* (iii) add it to the appropriate options struct (perhaps StdRdOptions)
* (iv) add it to the appropriate handling routine (perhaps
@@ -147,15 +147,6 @@ static relopt_bool boolRelOpts[] =
},
false
},
- {
- {
- "vacuum_truncate",
- "Enables vacuum to truncate empty pages at the end of this table",
- RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
- ShareUpdateExclusiveLock
- },
- true
- },
{
{
"deduplicate_items",
@@ -170,6 +161,21 @@ static relopt_bool boolRelOpts[] =
{{NULL}}
};
+static relopt_ternary ternaryRelOpts[] =
+{
+ {
+ {
+ "vacuum_truncate",
+ "Enables vacuum to truncate empty pages at the end of this table",
+ RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
+ ShareUpdateExclusiveLock
+ },
+ TERNARY_UNSET
+ },
+ /* list terminator */
+ {{NULL}}
+};
+
static relopt_int intRelOpts[] =
{
{
@@ -600,6 +606,13 @@ initialize_reloptions(void)
boolRelOpts[i].gen.lockmode));
j++;
}
+ for (i = 0; ternaryRelOpts[i].gen.name; i++)
+ {
+ Assert(DoLockModesConflict(ternaryRelOpts[i].gen.lockmode,
+ ternaryRelOpts[i].gen.lockmode));
+ j++;
+ }
+
for (i = 0; intRelOpts[i].gen.name; i++)
{
Assert(DoLockModesConflict(intRelOpts[i].gen.lockmode,
@@ -640,6 +653,14 @@ initialize_reloptions(void)
j++;
}
+ for (i = 0; ternaryRelOpts[i].gen.name; i++)
+ {
+ relOpts[j] = &ternaryRelOpts[i].gen;
+ relOpts[j]->type = RELOPT_TYPE_TERNARY;
+ relOpts[j]->namelen = strlen(relOpts[j]->name);
+ j++;
+ }
+
for (i = 0; intRelOpts[i].gen.name; i++)
{
relOpts[j] = &intRelOpts[i].gen;
@@ -800,6 +821,9 @@ allocate_reloption(bits32 kinds, int type, const char *name, const char *desc,
case RELOPT_TYPE_BOOL:
size = sizeof(relopt_bool);
break;
+ case RELOPT_TYPE_TERNARY:
+ size = sizeof(relopt_ternary);
+ break;
case RELOPT_TYPE_INT:
size = sizeof(relopt_int);
break;
@@ -883,6 +907,54 @@ add_local_bool_reloption(local_relopts *relopts, const char *name,
add_local_reloption(relopts, (relopt_gen *) newoption, offset);
}
+/*
+ * init_ternary_reloption
+ * Allocate and initialize a new ternary reloption
+ */
+static relopt_ternary *
+init_ternary_reloption(bits32 kinds, const char *name, const char *desc,
+ ternary default_val, LOCKMODE lockmode)
+{
+ relopt_ternary *newoption;
+
+ newoption = (relopt_ternary *) allocate_reloption(kinds,
+ RELOPT_TYPE_TERNARY, name, desc, lockmode);
+ newoption->default_val = default_val;
+
+ return newoption;
+}
+
+/*
+ * add_ternary_reloption
+ * Add a new ternary reloption
+ */
+void
+add_ternary_reloption(bits32 kinds, const char *name, const char *desc,
+ ternary default_val, LOCKMODE lockmode)
+{
+ relopt_ternary *newoption = init_ternary_reloption(kinds, name, desc,
+ default_val, lockmode);
+
+ add_reloption((relopt_gen *) newoption);
+}
+
+/*
+ * add_local_ternary_reloption
+ * Add a new ternary local reloption
+ *
+ * 'offset' is offset of ternary-typed field.
+ */
+void
+add_local_ternary_reloption(local_relopts *relopts, const char *name,
+ const char *desc, ternary default_val,
+ int offset)
+{
+ relopt_ternary *newoption = init_ternary_reloption(RELOPT_KIND_LOCAL,
+ name, desc,
+ default_val, 0);
+
+ add_local_reloption(relopts, (relopt_gen *) newoption, offset);
+}
/*
* init_real_reloption
@@ -1617,6 +1689,18 @@ parse_one_reloption(relopt_value *option, char *text_str, int text_len,
option->gen->name, value)));
}
break;
+ case RELOPT_TYPE_TERNARY:
+ {
+ bool b;
+ parsed = parse_bool(value, &b);
+ option->values.ternary_val = b ? TERNARY_TRUE : TERNARY_FALSE;
+ if (validate && !parsed)
+ ereport(ERROR,
+ (errcode(ERRCODE_INVALID_PARAMETER_VALUE),
+ errmsg("invalid value for ternary option \"%s\": %s",
+ option->gen->name, value)));
+ }
+ break;
case RELOPT_TYPE_INT:
{
relopt_int *optint = (relopt_int *) option->gen;
@@ -1780,17 +1864,6 @@ fillRelOptions(void *rdopts, Size basesize,
char *itempos = ((char *) rdopts) + elems[j].offset;
char *string_val;
- /*
- * If isset_offset is provided, store whether the reloption is
- * set there.
- */
- if (elems[j].isset_offset > 0)
- {
- char *setpos = ((char *) rdopts) + elems[j].isset_offset;
-
- *(bool *) setpos = options[i].isset;
- }
-
switch (options[i].gen->type)
{
case RELOPT_TYPE_BOOL:
@@ -1798,6 +1871,11 @@ fillRelOptions(void *rdopts, Size basesize,
options[i].values.bool_val :
((relopt_bool *) options[i].gen)->default_val;
break;
+ case RELOPT_TYPE_TERNARY:
+ *(ternary *) itempos = options[i].isset ?
+ options[i].values.ternary_val :
+ ((relopt_ternary *) options[i].gen)->default_val;
+ break;
case RELOPT_TYPE_INT:
*(int *) itempos = options[i].isset ?
options[i].values.int_val :
@@ -1912,8 +1990,8 @@ default_reloptions(Datum reloptions, bool validate, relopt_kind kind)
offsetof(StdRdOptions, parallel_workers)},
{"vacuum_index_cleanup", RELOPT_TYPE_ENUM,
offsetof(StdRdOptions, vacuum_index_cleanup)},
- {"vacuum_truncate", RELOPT_TYPE_BOOL,
- offsetof(StdRdOptions, vacuum_truncate), offsetof(StdRdOptions, vacuum_truncate_set)},
+ {"vacuum_truncate", RELOPT_TYPE_TERNARY,
+ offsetof(StdRdOptions, vacuum_truncate)},
{"vacuum_max_eager_freeze_failure_rate", RELOPT_TYPE_REAL,
offsetof(StdRdOptions, vacuum_max_eager_freeze_failure_rate)}
};
@@ -1993,7 +2071,6 @@ build_local_reloptions(local_relopts *relopts, Datum options, bool validate)
elems[i].optname = opt->option->name;
elems[i].opttype = opt->option->type;
elems[i].offset = opt->offset;
- elems[i].isset_offset = 0; /* not supported for local relopts yet */
i++;
}
diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c
index 733ef40ae7..977babff54 100644
--- a/src/backend/commands/vacuum.c
+++ b/src/backend/commands/vacuum.c
@@ -2221,9 +2221,9 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams params,
{
StdRdOptions *opts = (StdRdOptions *) rel->rd_options;
- if (opts && opts->vacuum_truncate_set)
+ if (opts && opts->vacuum_truncate != TERNARY_UNSET)
{
- if (opts->vacuum_truncate)
+ if (opts->vacuum_truncate == TERNARY_TRUE)
params.truncate = VACOPTVALUE_ENABLED;
else
params.truncate = VACOPTVALUE_DISABLED;
diff --git a/src/include/access/reloptions.h b/src/include/access/reloptions.h
index a604a4702c..a436697658 100644
--- a/src/include/access/reloptions.h
+++ b/src/include/access/reloptions.h
@@ -29,6 +29,7 @@
typedef enum relopt_type
{
RELOPT_TYPE_BOOL,
+ RELOPT_TYPE_TERNARY, /* on, off, unset */
RELOPT_TYPE_INT,
RELOPT_TYPE_REAL,
RELOPT_TYPE_ENUM,
@@ -80,6 +81,7 @@ typedef struct relopt_value
union
{
bool bool_val;
+ ternary ternary_val;
int int_val;
double real_val;
int enum_val;
@@ -94,6 +96,12 @@ typedef struct relopt_bool
bool default_val;
} relopt_bool;
+typedef struct relopt_rernary
+{
+ relopt_gen gen;
+ int default_val;
+} relopt_ternary;
+
typedef struct relopt_int
{
relopt_gen gen;
@@ -152,19 +160,6 @@ typedef struct
const char *optname; /* option's name */
relopt_type opttype; /* option's datatype */
int offset; /* offset of field in result struct */
-
- /*
- * isset_offset is an optional offset of a field in the result struct that
- * stores whether the option is explicitly set for the relation or if it
- * just picked up the default value. In most cases, this can be
- * accomplished by giving the reloption a special out-of-range default
- * value (e.g., some integer reloptions use -2), but this isn't always
- * possible. For example, a Boolean reloption cannot be given an
- * out-of-range default, so we need another way to discover the source of
- * its value. This offset is only used if given a value greater than
- * zero.
- */
- int isset_offset;
} relopt_parse_elt;
/* Local reloption definition */
@@ -195,6 +190,8 @@ typedef struct local_relopts
extern relopt_kind add_reloption_kind(void);
extern void add_bool_reloption(bits32 kinds, const char *name, const char *desc,
bool default_val, LOCKMODE lockmode);
+extern void add_ternary_reloption(bits32 kinds, const char *name,
+ const char *desc, int default_val, LOCKMODE lockmode);
extern void add_int_reloption(bits32 kinds, const char *name, const char *desc,
int default_val, int min_val, int max_val,
LOCKMODE lockmode);
@@ -214,6 +211,9 @@ extern void register_reloptions_validator(local_relopts *relopts,
extern void add_local_bool_reloption(local_relopts *relopts, const char *name,
const char *desc, bool default_val,
int offset);
+extern void add_local_ternary_reloption(local_relopts *relopts,
+ const char *name, const char *desc,
+ ternary default_val, int offset);
extern void add_local_int_reloption(local_relopts *relopts, const char *name,
const char *desc, int default_val,
int min_val, int max_val, int offset);
diff --git a/src/include/c.h b/src/include/c.h
index 39022f8a9d..9c59ccccf8 100644
--- a/src/include/c.h
+++ b/src/include/c.h
@@ -488,6 +488,22 @@ typedef void (*pg_funcptr_t) (void);
#include <stdbool.h>
+/*
+ * ternary
+ * Boolean value with an extrea "unset" option
+ *
+ * Ternary data type is used in relation options that can be "true", "false" or
+ * "unset". Since relation options are used deep inside the PostgreSQL code,
+ * this type is declared globally.
+*/
+
+typedef enum ternary
+{
+ TERNARY_FALSE = 0,
+ TERNARY_TRUE = 1,
+ TERNARY_UNSET = -1
+} ternary;
+
/* ----------------------------------------------------------------
* Section 3: standard system types
diff --git a/src/include/utils/rel.h b/src/include/utils/rel.h
index b552359915..7346d618f9 100644
--- a/src/include/utils/rel.h
+++ b/src/include/utils/rel.h
@@ -346,8 +346,7 @@ typedef struct StdRdOptions
bool user_catalog_table; /* use as an additional catalog relation */
int parallel_workers; /* max number of parallel workers */
StdRdOptIndexCleanup vacuum_index_cleanup; /* controls index vacuuming */
- bool vacuum_truncate; /* enables vacuum to truncate a relation */
- bool vacuum_truncate_set; /* whether vacuum_truncate is set */
+ ternary vacuum_truncate; /* enables vacuum to truncate a relation */
/*
* Fraction of pages in a relation that vacuum can eagerly scan and fail
--
2.39.2
--nextPart5129932.5fSG56mABF
Content-Disposition: attachment;
filename="v2-0003-Add-alias-to-be-used-as-unset-state.patch"
Content-Transfer-Encoding: 7Bit
Content-Type: text/x-patch; charset="unicode-2-0-utf-8";
name="v2-0003-Add-alias-to-be-used-as-unset-state.patch"
^ permalink raw reply [nested|flat] 868+ messages in thread
* [PATCH v2 2/4] Introduce ternary reloptions
@ 2025-09-04 14:41 Nikolay Shaplov <[email protected]>
0 siblings, 0 replies; 868+ messages in thread
From: Nikolay Shaplov @ 2025-09-04 14:41 UTC (permalink / raw)
Introduce ternary reloption as a replacement for current `vacuum_truncate`
implementation. Remove `vacuum_truncate_set` additional flag and using
`TERNARY_UNSET` value instead.
---
src/backend/access/common/reloptions.c | 129 ++++++++++++++++++++-----
src/backend/commands/vacuum.c | 4 +-
src/include/access/reloptions.h | 26 ++---
src/include/c.h | 16 +++
src/include/utils/rel.h | 3 +-
5 files changed, 135 insertions(+), 43 deletions(-)
diff --git a/src/backend/access/common/reloptions.c b/src/backend/access/common/reloptions.c
index 0af3fea68f..f543f11001 100644
--- a/src/backend/access/common/reloptions.c
+++ b/src/backend/access/common/reloptions.c
@@ -40,9 +40,9 @@
*
* To add an option:
*
- * (i) decide on a type (bool, integer, real, enum, string), name, default
- * value, upper and lower bounds (if applicable); for strings, consider a
- * validation routine.
+ * (i) decide on a type (bool, ternary, integer, real, enum, string), name,
+ * default value, upper and lower bounds (if applicable); for strings,
+ * consider a validation routine.
* (ii) add a record below (or use add_<type>_reloption).
* (iii) add it to the appropriate options struct (perhaps StdRdOptions)
* (iv) add it to the appropriate handling routine (perhaps
@@ -147,15 +147,6 @@ static relopt_bool boolRelOpts[] =
},
false
},
- {
- {
- "vacuum_truncate",
- "Enables vacuum to truncate empty pages at the end of this table",
- RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
- ShareUpdateExclusiveLock
- },
- true
- },
{
{
"deduplicate_items",
@@ -170,6 +161,21 @@ static relopt_bool boolRelOpts[] =
{{NULL}}
};
+static relopt_ternary ternaryRelOpts[] =
+{
+ {
+ {
+ "vacuum_truncate",
+ "Enables vacuum to truncate empty pages at the end of this table",
+ RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
+ ShareUpdateExclusiveLock
+ },
+ TERNARY_UNSET
+ },
+ /* list terminator */
+ {{NULL}}
+};
+
static relopt_int intRelOpts[] =
{
{
@@ -600,6 +606,13 @@ initialize_reloptions(void)
boolRelOpts[i].gen.lockmode));
j++;
}
+ for (i = 0; ternaryRelOpts[i].gen.name; i++)
+ {
+ Assert(DoLockModesConflict(ternaryRelOpts[i].gen.lockmode,
+ ternaryRelOpts[i].gen.lockmode));
+ j++;
+ }
+
for (i = 0; intRelOpts[i].gen.name; i++)
{
Assert(DoLockModesConflict(intRelOpts[i].gen.lockmode,
@@ -640,6 +653,14 @@ initialize_reloptions(void)
j++;
}
+ for (i = 0; ternaryRelOpts[i].gen.name; i++)
+ {
+ relOpts[j] = &ternaryRelOpts[i].gen;
+ relOpts[j]->type = RELOPT_TYPE_TERNARY;
+ relOpts[j]->namelen = strlen(relOpts[j]->name);
+ j++;
+ }
+
for (i = 0; intRelOpts[i].gen.name; i++)
{
relOpts[j] = &intRelOpts[i].gen;
@@ -800,6 +821,9 @@ allocate_reloption(bits32 kinds, int type, const char *name, const char *desc,
case RELOPT_TYPE_BOOL:
size = sizeof(relopt_bool);
break;
+ case RELOPT_TYPE_TERNARY:
+ size = sizeof(relopt_ternary);
+ break;
case RELOPT_TYPE_INT:
size = sizeof(relopt_int);
break;
@@ -883,6 +907,54 @@ add_local_bool_reloption(local_relopts *relopts, const char *name,
add_local_reloption(relopts, (relopt_gen *) newoption, offset);
}
+/*
+ * init_ternary_reloption
+ * Allocate and initialize a new ternary reloption
+ */
+static relopt_ternary *
+init_ternary_reloption(bits32 kinds, const char *name, const char *desc,
+ ternary default_val, LOCKMODE lockmode)
+{
+ relopt_ternary *newoption;
+
+ newoption = (relopt_ternary *) allocate_reloption(kinds,
+ RELOPT_TYPE_TERNARY, name, desc, lockmode);
+ newoption->default_val = default_val;
+
+ return newoption;
+}
+
+/*
+ * add_ternary_reloption
+ * Add a new ternary reloption
+ */
+void
+add_ternary_reloption(bits32 kinds, const char *name, const char *desc,
+ ternary default_val, LOCKMODE lockmode)
+{
+ relopt_ternary *newoption = init_ternary_reloption(kinds, name, desc,
+ default_val, lockmode);
+
+ add_reloption((relopt_gen *) newoption);
+}
+
+/*
+ * add_local_ternary_reloption
+ * Add a new ternary local reloption
+ *
+ * 'offset' is offset of ternary-typed field.
+ */
+void
+add_local_ternary_reloption(local_relopts *relopts, const char *name,
+ const char *desc, ternary default_val,
+ int offset)
+{
+ relopt_ternary *newoption = init_ternary_reloption(RELOPT_KIND_LOCAL,
+ name, desc,
+ default_val, 0);
+
+ add_local_reloption(relopts, (relopt_gen *) newoption, offset);
+}
/*
* init_real_reloption
@@ -1617,6 +1689,18 @@ parse_one_reloption(relopt_value *option, char *text_str, int text_len,
option->gen->name, value)));
}
break;
+ case RELOPT_TYPE_TERNARY:
+ {
+ bool b;
+ parsed = parse_bool(value, &b);
+ option->values.ternary_val = b ? TERNARY_TRUE : TERNARY_FALSE;
+ if (validate && !parsed)
+ ereport(ERROR,
+ (errcode(ERRCODE_INVALID_PARAMETER_VALUE),
+ errmsg("invalid value for ternary option \"%s\": %s",
+ option->gen->name, value)));
+ }
+ break;
case RELOPT_TYPE_INT:
{
relopt_int *optint = (relopt_int *) option->gen;
@@ -1780,17 +1864,6 @@ fillRelOptions(void *rdopts, Size basesize,
char *itempos = ((char *) rdopts) + elems[j].offset;
char *string_val;
- /*
- * If isset_offset is provided, store whether the reloption is
- * set there.
- */
- if (elems[j].isset_offset > 0)
- {
- char *setpos = ((char *) rdopts) + elems[j].isset_offset;
-
- *(bool *) setpos = options[i].isset;
- }
-
switch (options[i].gen->type)
{
case RELOPT_TYPE_BOOL:
@@ -1798,6 +1871,11 @@ fillRelOptions(void *rdopts, Size basesize,
options[i].values.bool_val :
((relopt_bool *) options[i].gen)->default_val;
break;
+ case RELOPT_TYPE_TERNARY:
+ *(ternary *) itempos = options[i].isset ?
+ options[i].values.ternary_val :
+ ((relopt_ternary *) options[i].gen)->default_val;
+ break;
case RELOPT_TYPE_INT:
*(int *) itempos = options[i].isset ?
options[i].values.int_val :
@@ -1912,8 +1990,8 @@ default_reloptions(Datum reloptions, bool validate, relopt_kind kind)
offsetof(StdRdOptions, parallel_workers)},
{"vacuum_index_cleanup", RELOPT_TYPE_ENUM,
offsetof(StdRdOptions, vacuum_index_cleanup)},
- {"vacuum_truncate", RELOPT_TYPE_BOOL,
- offsetof(StdRdOptions, vacuum_truncate), offsetof(StdRdOptions, vacuum_truncate_set)},
+ {"vacuum_truncate", RELOPT_TYPE_TERNARY,
+ offsetof(StdRdOptions, vacuum_truncate)},
{"vacuum_max_eager_freeze_failure_rate", RELOPT_TYPE_REAL,
offsetof(StdRdOptions, vacuum_max_eager_freeze_failure_rate)}
};
@@ -1993,7 +2071,6 @@ build_local_reloptions(local_relopts *relopts, Datum options, bool validate)
elems[i].optname = opt->option->name;
elems[i].opttype = opt->option->type;
elems[i].offset = opt->offset;
- elems[i].isset_offset = 0; /* not supported for local relopts yet */
i++;
}
diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c
index 733ef40ae7..977babff54 100644
--- a/src/backend/commands/vacuum.c
+++ b/src/backend/commands/vacuum.c
@@ -2221,9 +2221,9 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams params,
{
StdRdOptions *opts = (StdRdOptions *) rel->rd_options;
- if (opts && opts->vacuum_truncate_set)
+ if (opts && opts->vacuum_truncate != TERNARY_UNSET)
{
- if (opts->vacuum_truncate)
+ if (opts->vacuum_truncate == TERNARY_TRUE)
params.truncate = VACOPTVALUE_ENABLED;
else
params.truncate = VACOPTVALUE_DISABLED;
diff --git a/src/include/access/reloptions.h b/src/include/access/reloptions.h
index a604a4702c..a436697658 100644
--- a/src/include/access/reloptions.h
+++ b/src/include/access/reloptions.h
@@ -29,6 +29,7 @@
typedef enum relopt_type
{
RELOPT_TYPE_BOOL,
+ RELOPT_TYPE_TERNARY, /* on, off, unset */
RELOPT_TYPE_INT,
RELOPT_TYPE_REAL,
RELOPT_TYPE_ENUM,
@@ -80,6 +81,7 @@ typedef struct relopt_value
union
{
bool bool_val;
+ ternary ternary_val;
int int_val;
double real_val;
int enum_val;
@@ -94,6 +96,12 @@ typedef struct relopt_bool
bool default_val;
} relopt_bool;
+typedef struct relopt_rernary
+{
+ relopt_gen gen;
+ int default_val;
+} relopt_ternary;
+
typedef struct relopt_int
{
relopt_gen gen;
@@ -152,19 +160,6 @@ typedef struct
const char *optname; /* option's name */
relopt_type opttype; /* option's datatype */
int offset; /* offset of field in result struct */
-
- /*
- * isset_offset is an optional offset of a field in the result struct that
- * stores whether the option is explicitly set for the relation or if it
- * just picked up the default value. In most cases, this can be
- * accomplished by giving the reloption a special out-of-range default
- * value (e.g., some integer reloptions use -2), but this isn't always
- * possible. For example, a Boolean reloption cannot be given an
- * out-of-range default, so we need another way to discover the source of
- * its value. This offset is only used if given a value greater than
- * zero.
- */
- int isset_offset;
} relopt_parse_elt;
/* Local reloption definition */
@@ -195,6 +190,8 @@ typedef struct local_relopts
extern relopt_kind add_reloption_kind(void);
extern void add_bool_reloption(bits32 kinds, const char *name, const char *desc,
bool default_val, LOCKMODE lockmode);
+extern void add_ternary_reloption(bits32 kinds, const char *name,
+ const char *desc, int default_val, LOCKMODE lockmode);
extern void add_int_reloption(bits32 kinds, const char *name, const char *desc,
int default_val, int min_val, int max_val,
LOCKMODE lockmode);
@@ -214,6 +211,9 @@ extern void register_reloptions_validator(local_relopts *relopts,
extern void add_local_bool_reloption(local_relopts *relopts, const char *name,
const char *desc, bool default_val,
int offset);
+extern void add_local_ternary_reloption(local_relopts *relopts,
+ const char *name, const char *desc,
+ ternary default_val, int offset);
extern void add_local_int_reloption(local_relopts *relopts, const char *name,
const char *desc, int default_val,
int min_val, int max_val, int offset);
diff --git a/src/include/c.h b/src/include/c.h
index 39022f8a9d..9c59ccccf8 100644
--- a/src/include/c.h
+++ b/src/include/c.h
@@ -488,6 +488,22 @@ typedef void (*pg_funcptr_t) (void);
#include <stdbool.h>
+/*
+ * ternary
+ * Boolean value with an extrea "unset" option
+ *
+ * Ternary data type is used in relation options that can be "true", "false" or
+ * "unset". Since relation options are used deep inside the PostgreSQL code,
+ * this type is declared globally.
+*/
+
+typedef enum ternary
+{
+ TERNARY_FALSE = 0,
+ TERNARY_TRUE = 1,
+ TERNARY_UNSET = -1
+} ternary;
+
/* ----------------------------------------------------------------
* Section 3: standard system types
diff --git a/src/include/utils/rel.h b/src/include/utils/rel.h
index b552359915..7346d618f9 100644
--- a/src/include/utils/rel.h
+++ b/src/include/utils/rel.h
@@ -346,8 +346,7 @@ typedef struct StdRdOptions
bool user_catalog_table; /* use as an additional catalog relation */
int parallel_workers; /* max number of parallel workers */
StdRdOptIndexCleanup vacuum_index_cleanup; /* controls index vacuuming */
- bool vacuum_truncate; /* enables vacuum to truncate a relation */
- bool vacuum_truncate_set; /* whether vacuum_truncate is set */
+ ternary vacuum_truncate; /* enables vacuum to truncate a relation */
/*
* Fraction of pages in a relation that vacuum can eagerly scan and fail
--
2.39.2
--nextPart5129932.5fSG56mABF
Content-Disposition: attachment;
filename="v2-0003-Add-alias-to-be-used-as-unset-state.patch"
Content-Transfer-Encoding: 7Bit
Content-Type: text/x-patch; charset="unicode-2-0-utf-8";
name="v2-0003-Add-alias-to-be-used-as-unset-state.patch"
^ permalink raw reply [nested|flat] 868+ messages in thread
* [PATCH v2 2/4] Introduce ternary reloptions
@ 2025-09-04 14:41 Nikolay Shaplov <[email protected]>
0 siblings, 0 replies; 868+ messages in thread
From: Nikolay Shaplov @ 2025-09-04 14:41 UTC (permalink / raw)
Introduce ternary reloption as a replacement for current `vacuum_truncate`
implementation. Remove `vacuum_truncate_set` additional flag and using
`TERNARY_UNSET` value instead.
---
src/backend/access/common/reloptions.c | 129 ++++++++++++++++++++-----
src/backend/commands/vacuum.c | 4 +-
src/include/access/reloptions.h | 26 ++---
src/include/c.h | 16 +++
src/include/utils/rel.h | 3 +-
5 files changed, 135 insertions(+), 43 deletions(-)
diff --git a/src/backend/access/common/reloptions.c b/src/backend/access/common/reloptions.c
index 0af3fea68f..f543f11001 100644
--- a/src/backend/access/common/reloptions.c
+++ b/src/backend/access/common/reloptions.c
@@ -40,9 +40,9 @@
*
* To add an option:
*
- * (i) decide on a type (bool, integer, real, enum, string), name, default
- * value, upper and lower bounds (if applicable); for strings, consider a
- * validation routine.
+ * (i) decide on a type (bool, ternary, integer, real, enum, string), name,
+ * default value, upper and lower bounds (if applicable); for strings,
+ * consider a validation routine.
* (ii) add a record below (or use add_<type>_reloption).
* (iii) add it to the appropriate options struct (perhaps StdRdOptions)
* (iv) add it to the appropriate handling routine (perhaps
@@ -147,15 +147,6 @@ static relopt_bool boolRelOpts[] =
},
false
},
- {
- {
- "vacuum_truncate",
- "Enables vacuum to truncate empty pages at the end of this table",
- RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
- ShareUpdateExclusiveLock
- },
- true
- },
{
{
"deduplicate_items",
@@ -170,6 +161,21 @@ static relopt_bool boolRelOpts[] =
{{NULL}}
};
+static relopt_ternary ternaryRelOpts[] =
+{
+ {
+ {
+ "vacuum_truncate",
+ "Enables vacuum to truncate empty pages at the end of this table",
+ RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
+ ShareUpdateExclusiveLock
+ },
+ TERNARY_UNSET
+ },
+ /* list terminator */
+ {{NULL}}
+};
+
static relopt_int intRelOpts[] =
{
{
@@ -600,6 +606,13 @@ initialize_reloptions(void)
boolRelOpts[i].gen.lockmode));
j++;
}
+ for (i = 0; ternaryRelOpts[i].gen.name; i++)
+ {
+ Assert(DoLockModesConflict(ternaryRelOpts[i].gen.lockmode,
+ ternaryRelOpts[i].gen.lockmode));
+ j++;
+ }
+
for (i = 0; intRelOpts[i].gen.name; i++)
{
Assert(DoLockModesConflict(intRelOpts[i].gen.lockmode,
@@ -640,6 +653,14 @@ initialize_reloptions(void)
j++;
}
+ for (i = 0; ternaryRelOpts[i].gen.name; i++)
+ {
+ relOpts[j] = &ternaryRelOpts[i].gen;
+ relOpts[j]->type = RELOPT_TYPE_TERNARY;
+ relOpts[j]->namelen = strlen(relOpts[j]->name);
+ j++;
+ }
+
for (i = 0; intRelOpts[i].gen.name; i++)
{
relOpts[j] = &intRelOpts[i].gen;
@@ -800,6 +821,9 @@ allocate_reloption(bits32 kinds, int type, const char *name, const char *desc,
case RELOPT_TYPE_BOOL:
size = sizeof(relopt_bool);
break;
+ case RELOPT_TYPE_TERNARY:
+ size = sizeof(relopt_ternary);
+ break;
case RELOPT_TYPE_INT:
size = sizeof(relopt_int);
break;
@@ -883,6 +907,54 @@ add_local_bool_reloption(local_relopts *relopts, const char *name,
add_local_reloption(relopts, (relopt_gen *) newoption, offset);
}
+/*
+ * init_ternary_reloption
+ * Allocate and initialize a new ternary reloption
+ */
+static relopt_ternary *
+init_ternary_reloption(bits32 kinds, const char *name, const char *desc,
+ ternary default_val, LOCKMODE lockmode)
+{
+ relopt_ternary *newoption;
+
+ newoption = (relopt_ternary *) allocate_reloption(kinds,
+ RELOPT_TYPE_TERNARY, name, desc, lockmode);
+ newoption->default_val = default_val;
+
+ return newoption;
+}
+
+/*
+ * add_ternary_reloption
+ * Add a new ternary reloption
+ */
+void
+add_ternary_reloption(bits32 kinds, const char *name, const char *desc,
+ ternary default_val, LOCKMODE lockmode)
+{
+ relopt_ternary *newoption = init_ternary_reloption(kinds, name, desc,
+ default_val, lockmode);
+
+ add_reloption((relopt_gen *) newoption);
+}
+
+/*
+ * add_local_ternary_reloption
+ * Add a new ternary local reloption
+ *
+ * 'offset' is offset of ternary-typed field.
+ */
+void
+add_local_ternary_reloption(local_relopts *relopts, const char *name,
+ const char *desc, ternary default_val,
+ int offset)
+{
+ relopt_ternary *newoption = init_ternary_reloption(RELOPT_KIND_LOCAL,
+ name, desc,
+ default_val, 0);
+
+ add_local_reloption(relopts, (relopt_gen *) newoption, offset);
+}
/*
* init_real_reloption
@@ -1617,6 +1689,18 @@ parse_one_reloption(relopt_value *option, char *text_str, int text_len,
option->gen->name, value)));
}
break;
+ case RELOPT_TYPE_TERNARY:
+ {
+ bool b;
+ parsed = parse_bool(value, &b);
+ option->values.ternary_val = b ? TERNARY_TRUE : TERNARY_FALSE;
+ if (validate && !parsed)
+ ereport(ERROR,
+ (errcode(ERRCODE_INVALID_PARAMETER_VALUE),
+ errmsg("invalid value for ternary option \"%s\": %s",
+ option->gen->name, value)));
+ }
+ break;
case RELOPT_TYPE_INT:
{
relopt_int *optint = (relopt_int *) option->gen;
@@ -1780,17 +1864,6 @@ fillRelOptions(void *rdopts, Size basesize,
char *itempos = ((char *) rdopts) + elems[j].offset;
char *string_val;
- /*
- * If isset_offset is provided, store whether the reloption is
- * set there.
- */
- if (elems[j].isset_offset > 0)
- {
- char *setpos = ((char *) rdopts) + elems[j].isset_offset;
-
- *(bool *) setpos = options[i].isset;
- }
-
switch (options[i].gen->type)
{
case RELOPT_TYPE_BOOL:
@@ -1798,6 +1871,11 @@ fillRelOptions(void *rdopts, Size basesize,
options[i].values.bool_val :
((relopt_bool *) options[i].gen)->default_val;
break;
+ case RELOPT_TYPE_TERNARY:
+ *(ternary *) itempos = options[i].isset ?
+ options[i].values.ternary_val :
+ ((relopt_ternary *) options[i].gen)->default_val;
+ break;
case RELOPT_TYPE_INT:
*(int *) itempos = options[i].isset ?
options[i].values.int_val :
@@ -1912,8 +1990,8 @@ default_reloptions(Datum reloptions, bool validate, relopt_kind kind)
offsetof(StdRdOptions, parallel_workers)},
{"vacuum_index_cleanup", RELOPT_TYPE_ENUM,
offsetof(StdRdOptions, vacuum_index_cleanup)},
- {"vacuum_truncate", RELOPT_TYPE_BOOL,
- offsetof(StdRdOptions, vacuum_truncate), offsetof(StdRdOptions, vacuum_truncate_set)},
+ {"vacuum_truncate", RELOPT_TYPE_TERNARY,
+ offsetof(StdRdOptions, vacuum_truncate)},
{"vacuum_max_eager_freeze_failure_rate", RELOPT_TYPE_REAL,
offsetof(StdRdOptions, vacuum_max_eager_freeze_failure_rate)}
};
@@ -1993,7 +2071,6 @@ build_local_reloptions(local_relopts *relopts, Datum options, bool validate)
elems[i].optname = opt->option->name;
elems[i].opttype = opt->option->type;
elems[i].offset = opt->offset;
- elems[i].isset_offset = 0; /* not supported for local relopts yet */
i++;
}
diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c
index 733ef40ae7..977babff54 100644
--- a/src/backend/commands/vacuum.c
+++ b/src/backend/commands/vacuum.c
@@ -2221,9 +2221,9 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams params,
{
StdRdOptions *opts = (StdRdOptions *) rel->rd_options;
- if (opts && opts->vacuum_truncate_set)
+ if (opts && opts->vacuum_truncate != TERNARY_UNSET)
{
- if (opts->vacuum_truncate)
+ if (opts->vacuum_truncate == TERNARY_TRUE)
params.truncate = VACOPTVALUE_ENABLED;
else
params.truncate = VACOPTVALUE_DISABLED;
diff --git a/src/include/access/reloptions.h b/src/include/access/reloptions.h
index a604a4702c..a436697658 100644
--- a/src/include/access/reloptions.h
+++ b/src/include/access/reloptions.h
@@ -29,6 +29,7 @@
typedef enum relopt_type
{
RELOPT_TYPE_BOOL,
+ RELOPT_TYPE_TERNARY, /* on, off, unset */
RELOPT_TYPE_INT,
RELOPT_TYPE_REAL,
RELOPT_TYPE_ENUM,
@@ -80,6 +81,7 @@ typedef struct relopt_value
union
{
bool bool_val;
+ ternary ternary_val;
int int_val;
double real_val;
int enum_val;
@@ -94,6 +96,12 @@ typedef struct relopt_bool
bool default_val;
} relopt_bool;
+typedef struct relopt_rernary
+{
+ relopt_gen gen;
+ int default_val;
+} relopt_ternary;
+
typedef struct relopt_int
{
relopt_gen gen;
@@ -152,19 +160,6 @@ typedef struct
const char *optname; /* option's name */
relopt_type opttype; /* option's datatype */
int offset; /* offset of field in result struct */
-
- /*
- * isset_offset is an optional offset of a field in the result struct that
- * stores whether the option is explicitly set for the relation or if it
- * just picked up the default value. In most cases, this can be
- * accomplished by giving the reloption a special out-of-range default
- * value (e.g., some integer reloptions use -2), but this isn't always
- * possible. For example, a Boolean reloption cannot be given an
- * out-of-range default, so we need another way to discover the source of
- * its value. This offset is only used if given a value greater than
- * zero.
- */
- int isset_offset;
} relopt_parse_elt;
/* Local reloption definition */
@@ -195,6 +190,8 @@ typedef struct local_relopts
extern relopt_kind add_reloption_kind(void);
extern void add_bool_reloption(bits32 kinds, const char *name, const char *desc,
bool default_val, LOCKMODE lockmode);
+extern void add_ternary_reloption(bits32 kinds, const char *name,
+ const char *desc, int default_val, LOCKMODE lockmode);
extern void add_int_reloption(bits32 kinds, const char *name, const char *desc,
int default_val, int min_val, int max_val,
LOCKMODE lockmode);
@@ -214,6 +211,9 @@ extern void register_reloptions_validator(local_relopts *relopts,
extern void add_local_bool_reloption(local_relopts *relopts, const char *name,
const char *desc, bool default_val,
int offset);
+extern void add_local_ternary_reloption(local_relopts *relopts,
+ const char *name, const char *desc,
+ ternary default_val, int offset);
extern void add_local_int_reloption(local_relopts *relopts, const char *name,
const char *desc, int default_val,
int min_val, int max_val, int offset);
diff --git a/src/include/c.h b/src/include/c.h
index 39022f8a9d..9c59ccccf8 100644
--- a/src/include/c.h
+++ b/src/include/c.h
@@ -488,6 +488,22 @@ typedef void (*pg_funcptr_t) (void);
#include <stdbool.h>
+/*
+ * ternary
+ * Boolean value with an extrea "unset" option
+ *
+ * Ternary data type is used in relation options that can be "true", "false" or
+ * "unset". Since relation options are used deep inside the PostgreSQL code,
+ * this type is declared globally.
+*/
+
+typedef enum ternary
+{
+ TERNARY_FALSE = 0,
+ TERNARY_TRUE = 1,
+ TERNARY_UNSET = -1
+} ternary;
+
/* ----------------------------------------------------------------
* Section 3: standard system types
diff --git a/src/include/utils/rel.h b/src/include/utils/rel.h
index b552359915..7346d618f9 100644
--- a/src/include/utils/rel.h
+++ b/src/include/utils/rel.h
@@ -346,8 +346,7 @@ typedef struct StdRdOptions
bool user_catalog_table; /* use as an additional catalog relation */
int parallel_workers; /* max number of parallel workers */
StdRdOptIndexCleanup vacuum_index_cleanup; /* controls index vacuuming */
- bool vacuum_truncate; /* enables vacuum to truncate a relation */
- bool vacuum_truncate_set; /* whether vacuum_truncate is set */
+ ternary vacuum_truncate; /* enables vacuum to truncate a relation */
/*
* Fraction of pages in a relation that vacuum can eagerly scan and fail
--
2.39.2
--nextPart5129932.5fSG56mABF
Content-Disposition: attachment;
filename="v2-0003-Add-alias-to-be-used-as-unset-state.patch"
Content-Transfer-Encoding: 7Bit
Content-Type: text/x-patch; charset="unicode-2-0-utf-8";
name="v2-0003-Add-alias-to-be-used-as-unset-state.patch"
^ permalink raw reply [nested|flat] 868+ messages in thread
* [PATCH v2 2/4] Introduce ternary reloptions
@ 2025-09-04 14:41 Nikolay Shaplov <[email protected]>
0 siblings, 0 replies; 868+ messages in thread
From: Nikolay Shaplov @ 2025-09-04 14:41 UTC (permalink / raw)
Introduce ternary reloption as a replacement for current `vacuum_truncate`
implementation. Remove `vacuum_truncate_set` additional flag and using
`TERNARY_UNSET` value instead.
---
src/backend/access/common/reloptions.c | 129 ++++++++++++++++++++-----
src/backend/commands/vacuum.c | 4 +-
src/include/access/reloptions.h | 26 ++---
src/include/c.h | 16 +++
src/include/utils/rel.h | 3 +-
5 files changed, 135 insertions(+), 43 deletions(-)
diff --git a/src/backend/access/common/reloptions.c b/src/backend/access/common/reloptions.c
index 0af3fea68f..f543f11001 100644
--- a/src/backend/access/common/reloptions.c
+++ b/src/backend/access/common/reloptions.c
@@ -40,9 +40,9 @@
*
* To add an option:
*
- * (i) decide on a type (bool, integer, real, enum, string), name, default
- * value, upper and lower bounds (if applicable); for strings, consider a
- * validation routine.
+ * (i) decide on a type (bool, ternary, integer, real, enum, string), name,
+ * default value, upper and lower bounds (if applicable); for strings,
+ * consider a validation routine.
* (ii) add a record below (or use add_<type>_reloption).
* (iii) add it to the appropriate options struct (perhaps StdRdOptions)
* (iv) add it to the appropriate handling routine (perhaps
@@ -147,15 +147,6 @@ static relopt_bool boolRelOpts[] =
},
false
},
- {
- {
- "vacuum_truncate",
- "Enables vacuum to truncate empty pages at the end of this table",
- RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
- ShareUpdateExclusiveLock
- },
- true
- },
{
{
"deduplicate_items",
@@ -170,6 +161,21 @@ static relopt_bool boolRelOpts[] =
{{NULL}}
};
+static relopt_ternary ternaryRelOpts[] =
+{
+ {
+ {
+ "vacuum_truncate",
+ "Enables vacuum to truncate empty pages at the end of this table",
+ RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
+ ShareUpdateExclusiveLock
+ },
+ TERNARY_UNSET
+ },
+ /* list terminator */
+ {{NULL}}
+};
+
static relopt_int intRelOpts[] =
{
{
@@ -600,6 +606,13 @@ initialize_reloptions(void)
boolRelOpts[i].gen.lockmode));
j++;
}
+ for (i = 0; ternaryRelOpts[i].gen.name; i++)
+ {
+ Assert(DoLockModesConflict(ternaryRelOpts[i].gen.lockmode,
+ ternaryRelOpts[i].gen.lockmode));
+ j++;
+ }
+
for (i = 0; intRelOpts[i].gen.name; i++)
{
Assert(DoLockModesConflict(intRelOpts[i].gen.lockmode,
@@ -640,6 +653,14 @@ initialize_reloptions(void)
j++;
}
+ for (i = 0; ternaryRelOpts[i].gen.name; i++)
+ {
+ relOpts[j] = &ternaryRelOpts[i].gen;
+ relOpts[j]->type = RELOPT_TYPE_TERNARY;
+ relOpts[j]->namelen = strlen(relOpts[j]->name);
+ j++;
+ }
+
for (i = 0; intRelOpts[i].gen.name; i++)
{
relOpts[j] = &intRelOpts[i].gen;
@@ -800,6 +821,9 @@ allocate_reloption(bits32 kinds, int type, const char *name, const char *desc,
case RELOPT_TYPE_BOOL:
size = sizeof(relopt_bool);
break;
+ case RELOPT_TYPE_TERNARY:
+ size = sizeof(relopt_ternary);
+ break;
case RELOPT_TYPE_INT:
size = sizeof(relopt_int);
break;
@@ -883,6 +907,54 @@ add_local_bool_reloption(local_relopts *relopts, const char *name,
add_local_reloption(relopts, (relopt_gen *) newoption, offset);
}
+/*
+ * init_ternary_reloption
+ * Allocate and initialize a new ternary reloption
+ */
+static relopt_ternary *
+init_ternary_reloption(bits32 kinds, const char *name, const char *desc,
+ ternary default_val, LOCKMODE lockmode)
+{
+ relopt_ternary *newoption;
+
+ newoption = (relopt_ternary *) allocate_reloption(kinds,
+ RELOPT_TYPE_TERNARY, name, desc, lockmode);
+ newoption->default_val = default_val;
+
+ return newoption;
+}
+
+/*
+ * add_ternary_reloption
+ * Add a new ternary reloption
+ */
+void
+add_ternary_reloption(bits32 kinds, const char *name, const char *desc,
+ ternary default_val, LOCKMODE lockmode)
+{
+ relopt_ternary *newoption = init_ternary_reloption(kinds, name, desc,
+ default_val, lockmode);
+
+ add_reloption((relopt_gen *) newoption);
+}
+
+/*
+ * add_local_ternary_reloption
+ * Add a new ternary local reloption
+ *
+ * 'offset' is offset of ternary-typed field.
+ */
+void
+add_local_ternary_reloption(local_relopts *relopts, const char *name,
+ const char *desc, ternary default_val,
+ int offset)
+{
+ relopt_ternary *newoption = init_ternary_reloption(RELOPT_KIND_LOCAL,
+ name, desc,
+ default_val, 0);
+
+ add_local_reloption(relopts, (relopt_gen *) newoption, offset);
+}
/*
* init_real_reloption
@@ -1617,6 +1689,18 @@ parse_one_reloption(relopt_value *option, char *text_str, int text_len,
option->gen->name, value)));
}
break;
+ case RELOPT_TYPE_TERNARY:
+ {
+ bool b;
+ parsed = parse_bool(value, &b);
+ option->values.ternary_val = b ? TERNARY_TRUE : TERNARY_FALSE;
+ if (validate && !parsed)
+ ereport(ERROR,
+ (errcode(ERRCODE_INVALID_PARAMETER_VALUE),
+ errmsg("invalid value for ternary option \"%s\": %s",
+ option->gen->name, value)));
+ }
+ break;
case RELOPT_TYPE_INT:
{
relopt_int *optint = (relopt_int *) option->gen;
@@ -1780,17 +1864,6 @@ fillRelOptions(void *rdopts, Size basesize,
char *itempos = ((char *) rdopts) + elems[j].offset;
char *string_val;
- /*
- * If isset_offset is provided, store whether the reloption is
- * set there.
- */
- if (elems[j].isset_offset > 0)
- {
- char *setpos = ((char *) rdopts) + elems[j].isset_offset;
-
- *(bool *) setpos = options[i].isset;
- }
-
switch (options[i].gen->type)
{
case RELOPT_TYPE_BOOL:
@@ -1798,6 +1871,11 @@ fillRelOptions(void *rdopts, Size basesize,
options[i].values.bool_val :
((relopt_bool *) options[i].gen)->default_val;
break;
+ case RELOPT_TYPE_TERNARY:
+ *(ternary *) itempos = options[i].isset ?
+ options[i].values.ternary_val :
+ ((relopt_ternary *) options[i].gen)->default_val;
+ break;
case RELOPT_TYPE_INT:
*(int *) itempos = options[i].isset ?
options[i].values.int_val :
@@ -1912,8 +1990,8 @@ default_reloptions(Datum reloptions, bool validate, relopt_kind kind)
offsetof(StdRdOptions, parallel_workers)},
{"vacuum_index_cleanup", RELOPT_TYPE_ENUM,
offsetof(StdRdOptions, vacuum_index_cleanup)},
- {"vacuum_truncate", RELOPT_TYPE_BOOL,
- offsetof(StdRdOptions, vacuum_truncate), offsetof(StdRdOptions, vacuum_truncate_set)},
+ {"vacuum_truncate", RELOPT_TYPE_TERNARY,
+ offsetof(StdRdOptions, vacuum_truncate)},
{"vacuum_max_eager_freeze_failure_rate", RELOPT_TYPE_REAL,
offsetof(StdRdOptions, vacuum_max_eager_freeze_failure_rate)}
};
@@ -1993,7 +2071,6 @@ build_local_reloptions(local_relopts *relopts, Datum options, bool validate)
elems[i].optname = opt->option->name;
elems[i].opttype = opt->option->type;
elems[i].offset = opt->offset;
- elems[i].isset_offset = 0; /* not supported for local relopts yet */
i++;
}
diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c
index 733ef40ae7..977babff54 100644
--- a/src/backend/commands/vacuum.c
+++ b/src/backend/commands/vacuum.c
@@ -2221,9 +2221,9 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams params,
{
StdRdOptions *opts = (StdRdOptions *) rel->rd_options;
- if (opts && opts->vacuum_truncate_set)
+ if (opts && opts->vacuum_truncate != TERNARY_UNSET)
{
- if (opts->vacuum_truncate)
+ if (opts->vacuum_truncate == TERNARY_TRUE)
params.truncate = VACOPTVALUE_ENABLED;
else
params.truncate = VACOPTVALUE_DISABLED;
diff --git a/src/include/access/reloptions.h b/src/include/access/reloptions.h
index a604a4702c..a436697658 100644
--- a/src/include/access/reloptions.h
+++ b/src/include/access/reloptions.h
@@ -29,6 +29,7 @@
typedef enum relopt_type
{
RELOPT_TYPE_BOOL,
+ RELOPT_TYPE_TERNARY, /* on, off, unset */
RELOPT_TYPE_INT,
RELOPT_TYPE_REAL,
RELOPT_TYPE_ENUM,
@@ -80,6 +81,7 @@ typedef struct relopt_value
union
{
bool bool_val;
+ ternary ternary_val;
int int_val;
double real_val;
int enum_val;
@@ -94,6 +96,12 @@ typedef struct relopt_bool
bool default_val;
} relopt_bool;
+typedef struct relopt_rernary
+{
+ relopt_gen gen;
+ int default_val;
+} relopt_ternary;
+
typedef struct relopt_int
{
relopt_gen gen;
@@ -152,19 +160,6 @@ typedef struct
const char *optname; /* option's name */
relopt_type opttype; /* option's datatype */
int offset; /* offset of field in result struct */
-
- /*
- * isset_offset is an optional offset of a field in the result struct that
- * stores whether the option is explicitly set for the relation or if it
- * just picked up the default value. In most cases, this can be
- * accomplished by giving the reloption a special out-of-range default
- * value (e.g., some integer reloptions use -2), but this isn't always
- * possible. For example, a Boolean reloption cannot be given an
- * out-of-range default, so we need another way to discover the source of
- * its value. This offset is only used if given a value greater than
- * zero.
- */
- int isset_offset;
} relopt_parse_elt;
/* Local reloption definition */
@@ -195,6 +190,8 @@ typedef struct local_relopts
extern relopt_kind add_reloption_kind(void);
extern void add_bool_reloption(bits32 kinds, const char *name, const char *desc,
bool default_val, LOCKMODE lockmode);
+extern void add_ternary_reloption(bits32 kinds, const char *name,
+ const char *desc, int default_val, LOCKMODE lockmode);
extern void add_int_reloption(bits32 kinds, const char *name, const char *desc,
int default_val, int min_val, int max_val,
LOCKMODE lockmode);
@@ -214,6 +211,9 @@ extern void register_reloptions_validator(local_relopts *relopts,
extern void add_local_bool_reloption(local_relopts *relopts, const char *name,
const char *desc, bool default_val,
int offset);
+extern void add_local_ternary_reloption(local_relopts *relopts,
+ const char *name, const char *desc,
+ ternary default_val, int offset);
extern void add_local_int_reloption(local_relopts *relopts, const char *name,
const char *desc, int default_val,
int min_val, int max_val, int offset);
diff --git a/src/include/c.h b/src/include/c.h
index 39022f8a9d..9c59ccccf8 100644
--- a/src/include/c.h
+++ b/src/include/c.h
@@ -488,6 +488,22 @@ typedef void (*pg_funcptr_t) (void);
#include <stdbool.h>
+/*
+ * ternary
+ * Boolean value with an extrea "unset" option
+ *
+ * Ternary data type is used in relation options that can be "true", "false" or
+ * "unset". Since relation options are used deep inside the PostgreSQL code,
+ * this type is declared globally.
+*/
+
+typedef enum ternary
+{
+ TERNARY_FALSE = 0,
+ TERNARY_TRUE = 1,
+ TERNARY_UNSET = -1
+} ternary;
+
/* ----------------------------------------------------------------
* Section 3: standard system types
diff --git a/src/include/utils/rel.h b/src/include/utils/rel.h
index b552359915..7346d618f9 100644
--- a/src/include/utils/rel.h
+++ b/src/include/utils/rel.h
@@ -346,8 +346,7 @@ typedef struct StdRdOptions
bool user_catalog_table; /* use as an additional catalog relation */
int parallel_workers; /* max number of parallel workers */
StdRdOptIndexCleanup vacuum_index_cleanup; /* controls index vacuuming */
- bool vacuum_truncate; /* enables vacuum to truncate a relation */
- bool vacuum_truncate_set; /* whether vacuum_truncate is set */
+ ternary vacuum_truncate; /* enables vacuum to truncate a relation */
/*
* Fraction of pages in a relation that vacuum can eagerly scan and fail
--
2.39.2
--nextPart5129932.5fSG56mABF
Content-Disposition: attachment;
filename="v2-0003-Add-alias-to-be-used-as-unset-state.patch"
Content-Transfer-Encoding: 7Bit
Content-Type: text/x-patch; charset="unicode-2-0-utf-8";
name="v2-0003-Add-alias-to-be-used-as-unset-state.patch"
^ permalink raw reply [nested|flat] 868+ messages in thread
* [PATCH v2 2/4] Introduce ternary reloptions
@ 2025-09-04 14:41 Nikolay Shaplov <[email protected]>
0 siblings, 0 replies; 868+ messages in thread
From: Nikolay Shaplov @ 2025-09-04 14:41 UTC (permalink / raw)
Introduce ternary reloption as a replacement for current `vacuum_truncate`
implementation. Remove `vacuum_truncate_set` additional flag and using
`TERNARY_UNSET` value instead.
---
src/backend/access/common/reloptions.c | 129 ++++++++++++++++++++-----
src/backend/commands/vacuum.c | 4 +-
src/include/access/reloptions.h | 26 ++---
src/include/c.h | 16 +++
src/include/utils/rel.h | 3 +-
5 files changed, 135 insertions(+), 43 deletions(-)
diff --git a/src/backend/access/common/reloptions.c b/src/backend/access/common/reloptions.c
index 0af3fea68f..f543f11001 100644
--- a/src/backend/access/common/reloptions.c
+++ b/src/backend/access/common/reloptions.c
@@ -40,9 +40,9 @@
*
* To add an option:
*
- * (i) decide on a type (bool, integer, real, enum, string), name, default
- * value, upper and lower bounds (if applicable); for strings, consider a
- * validation routine.
+ * (i) decide on a type (bool, ternary, integer, real, enum, string), name,
+ * default value, upper and lower bounds (if applicable); for strings,
+ * consider a validation routine.
* (ii) add a record below (or use add_<type>_reloption).
* (iii) add it to the appropriate options struct (perhaps StdRdOptions)
* (iv) add it to the appropriate handling routine (perhaps
@@ -147,15 +147,6 @@ static relopt_bool boolRelOpts[] =
},
false
},
- {
- {
- "vacuum_truncate",
- "Enables vacuum to truncate empty pages at the end of this table",
- RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
- ShareUpdateExclusiveLock
- },
- true
- },
{
{
"deduplicate_items",
@@ -170,6 +161,21 @@ static relopt_bool boolRelOpts[] =
{{NULL}}
};
+static relopt_ternary ternaryRelOpts[] =
+{
+ {
+ {
+ "vacuum_truncate",
+ "Enables vacuum to truncate empty pages at the end of this table",
+ RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
+ ShareUpdateExclusiveLock
+ },
+ TERNARY_UNSET
+ },
+ /* list terminator */
+ {{NULL}}
+};
+
static relopt_int intRelOpts[] =
{
{
@@ -600,6 +606,13 @@ initialize_reloptions(void)
boolRelOpts[i].gen.lockmode));
j++;
}
+ for (i = 0; ternaryRelOpts[i].gen.name; i++)
+ {
+ Assert(DoLockModesConflict(ternaryRelOpts[i].gen.lockmode,
+ ternaryRelOpts[i].gen.lockmode));
+ j++;
+ }
+
for (i = 0; intRelOpts[i].gen.name; i++)
{
Assert(DoLockModesConflict(intRelOpts[i].gen.lockmode,
@@ -640,6 +653,14 @@ initialize_reloptions(void)
j++;
}
+ for (i = 0; ternaryRelOpts[i].gen.name; i++)
+ {
+ relOpts[j] = &ternaryRelOpts[i].gen;
+ relOpts[j]->type = RELOPT_TYPE_TERNARY;
+ relOpts[j]->namelen = strlen(relOpts[j]->name);
+ j++;
+ }
+
for (i = 0; intRelOpts[i].gen.name; i++)
{
relOpts[j] = &intRelOpts[i].gen;
@@ -800,6 +821,9 @@ allocate_reloption(bits32 kinds, int type, const char *name, const char *desc,
case RELOPT_TYPE_BOOL:
size = sizeof(relopt_bool);
break;
+ case RELOPT_TYPE_TERNARY:
+ size = sizeof(relopt_ternary);
+ break;
case RELOPT_TYPE_INT:
size = sizeof(relopt_int);
break;
@@ -883,6 +907,54 @@ add_local_bool_reloption(local_relopts *relopts, const char *name,
add_local_reloption(relopts, (relopt_gen *) newoption, offset);
}
+/*
+ * init_ternary_reloption
+ * Allocate and initialize a new ternary reloption
+ */
+static relopt_ternary *
+init_ternary_reloption(bits32 kinds, const char *name, const char *desc,
+ ternary default_val, LOCKMODE lockmode)
+{
+ relopt_ternary *newoption;
+
+ newoption = (relopt_ternary *) allocate_reloption(kinds,
+ RELOPT_TYPE_TERNARY, name, desc, lockmode);
+ newoption->default_val = default_val;
+
+ return newoption;
+}
+
+/*
+ * add_ternary_reloption
+ * Add a new ternary reloption
+ */
+void
+add_ternary_reloption(bits32 kinds, const char *name, const char *desc,
+ ternary default_val, LOCKMODE lockmode)
+{
+ relopt_ternary *newoption = init_ternary_reloption(kinds, name, desc,
+ default_val, lockmode);
+
+ add_reloption((relopt_gen *) newoption);
+}
+
+/*
+ * add_local_ternary_reloption
+ * Add a new ternary local reloption
+ *
+ * 'offset' is offset of ternary-typed field.
+ */
+void
+add_local_ternary_reloption(local_relopts *relopts, const char *name,
+ const char *desc, ternary default_val,
+ int offset)
+{
+ relopt_ternary *newoption = init_ternary_reloption(RELOPT_KIND_LOCAL,
+ name, desc,
+ default_val, 0);
+
+ add_local_reloption(relopts, (relopt_gen *) newoption, offset);
+}
/*
* init_real_reloption
@@ -1617,6 +1689,18 @@ parse_one_reloption(relopt_value *option, char *text_str, int text_len,
option->gen->name, value)));
}
break;
+ case RELOPT_TYPE_TERNARY:
+ {
+ bool b;
+ parsed = parse_bool(value, &b);
+ option->values.ternary_val = b ? TERNARY_TRUE : TERNARY_FALSE;
+ if (validate && !parsed)
+ ereport(ERROR,
+ (errcode(ERRCODE_INVALID_PARAMETER_VALUE),
+ errmsg("invalid value for ternary option \"%s\": %s",
+ option->gen->name, value)));
+ }
+ break;
case RELOPT_TYPE_INT:
{
relopt_int *optint = (relopt_int *) option->gen;
@@ -1780,17 +1864,6 @@ fillRelOptions(void *rdopts, Size basesize,
char *itempos = ((char *) rdopts) + elems[j].offset;
char *string_val;
- /*
- * If isset_offset is provided, store whether the reloption is
- * set there.
- */
- if (elems[j].isset_offset > 0)
- {
- char *setpos = ((char *) rdopts) + elems[j].isset_offset;
-
- *(bool *) setpos = options[i].isset;
- }
-
switch (options[i].gen->type)
{
case RELOPT_TYPE_BOOL:
@@ -1798,6 +1871,11 @@ fillRelOptions(void *rdopts, Size basesize,
options[i].values.bool_val :
((relopt_bool *) options[i].gen)->default_val;
break;
+ case RELOPT_TYPE_TERNARY:
+ *(ternary *) itempos = options[i].isset ?
+ options[i].values.ternary_val :
+ ((relopt_ternary *) options[i].gen)->default_val;
+ break;
case RELOPT_TYPE_INT:
*(int *) itempos = options[i].isset ?
options[i].values.int_val :
@@ -1912,8 +1990,8 @@ default_reloptions(Datum reloptions, bool validate, relopt_kind kind)
offsetof(StdRdOptions, parallel_workers)},
{"vacuum_index_cleanup", RELOPT_TYPE_ENUM,
offsetof(StdRdOptions, vacuum_index_cleanup)},
- {"vacuum_truncate", RELOPT_TYPE_BOOL,
- offsetof(StdRdOptions, vacuum_truncate), offsetof(StdRdOptions, vacuum_truncate_set)},
+ {"vacuum_truncate", RELOPT_TYPE_TERNARY,
+ offsetof(StdRdOptions, vacuum_truncate)},
{"vacuum_max_eager_freeze_failure_rate", RELOPT_TYPE_REAL,
offsetof(StdRdOptions, vacuum_max_eager_freeze_failure_rate)}
};
@@ -1993,7 +2071,6 @@ build_local_reloptions(local_relopts *relopts, Datum options, bool validate)
elems[i].optname = opt->option->name;
elems[i].opttype = opt->option->type;
elems[i].offset = opt->offset;
- elems[i].isset_offset = 0; /* not supported for local relopts yet */
i++;
}
diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c
index 733ef40ae7..977babff54 100644
--- a/src/backend/commands/vacuum.c
+++ b/src/backend/commands/vacuum.c
@@ -2221,9 +2221,9 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams params,
{
StdRdOptions *opts = (StdRdOptions *) rel->rd_options;
- if (opts && opts->vacuum_truncate_set)
+ if (opts && opts->vacuum_truncate != TERNARY_UNSET)
{
- if (opts->vacuum_truncate)
+ if (opts->vacuum_truncate == TERNARY_TRUE)
params.truncate = VACOPTVALUE_ENABLED;
else
params.truncate = VACOPTVALUE_DISABLED;
diff --git a/src/include/access/reloptions.h b/src/include/access/reloptions.h
index a604a4702c..a436697658 100644
--- a/src/include/access/reloptions.h
+++ b/src/include/access/reloptions.h
@@ -29,6 +29,7 @@
typedef enum relopt_type
{
RELOPT_TYPE_BOOL,
+ RELOPT_TYPE_TERNARY, /* on, off, unset */
RELOPT_TYPE_INT,
RELOPT_TYPE_REAL,
RELOPT_TYPE_ENUM,
@@ -80,6 +81,7 @@ typedef struct relopt_value
union
{
bool bool_val;
+ ternary ternary_val;
int int_val;
double real_val;
int enum_val;
@@ -94,6 +96,12 @@ typedef struct relopt_bool
bool default_val;
} relopt_bool;
+typedef struct relopt_rernary
+{
+ relopt_gen gen;
+ int default_val;
+} relopt_ternary;
+
typedef struct relopt_int
{
relopt_gen gen;
@@ -152,19 +160,6 @@ typedef struct
const char *optname; /* option's name */
relopt_type opttype; /* option's datatype */
int offset; /* offset of field in result struct */
-
- /*
- * isset_offset is an optional offset of a field in the result struct that
- * stores whether the option is explicitly set for the relation or if it
- * just picked up the default value. In most cases, this can be
- * accomplished by giving the reloption a special out-of-range default
- * value (e.g., some integer reloptions use -2), but this isn't always
- * possible. For example, a Boolean reloption cannot be given an
- * out-of-range default, so we need another way to discover the source of
- * its value. This offset is only used if given a value greater than
- * zero.
- */
- int isset_offset;
} relopt_parse_elt;
/* Local reloption definition */
@@ -195,6 +190,8 @@ typedef struct local_relopts
extern relopt_kind add_reloption_kind(void);
extern void add_bool_reloption(bits32 kinds, const char *name, const char *desc,
bool default_val, LOCKMODE lockmode);
+extern void add_ternary_reloption(bits32 kinds, const char *name,
+ const char *desc, int default_val, LOCKMODE lockmode);
extern void add_int_reloption(bits32 kinds, const char *name, const char *desc,
int default_val, int min_val, int max_val,
LOCKMODE lockmode);
@@ -214,6 +211,9 @@ extern void register_reloptions_validator(local_relopts *relopts,
extern void add_local_bool_reloption(local_relopts *relopts, const char *name,
const char *desc, bool default_val,
int offset);
+extern void add_local_ternary_reloption(local_relopts *relopts,
+ const char *name, const char *desc,
+ ternary default_val, int offset);
extern void add_local_int_reloption(local_relopts *relopts, const char *name,
const char *desc, int default_val,
int min_val, int max_val, int offset);
diff --git a/src/include/c.h b/src/include/c.h
index 39022f8a9d..9c59ccccf8 100644
--- a/src/include/c.h
+++ b/src/include/c.h
@@ -488,6 +488,22 @@ typedef void (*pg_funcptr_t) (void);
#include <stdbool.h>
+/*
+ * ternary
+ * Boolean value with an extrea "unset" option
+ *
+ * Ternary data type is used in relation options that can be "true", "false" or
+ * "unset". Since relation options are used deep inside the PostgreSQL code,
+ * this type is declared globally.
+*/
+
+typedef enum ternary
+{
+ TERNARY_FALSE = 0,
+ TERNARY_TRUE = 1,
+ TERNARY_UNSET = -1
+} ternary;
+
/* ----------------------------------------------------------------
* Section 3: standard system types
diff --git a/src/include/utils/rel.h b/src/include/utils/rel.h
index b552359915..7346d618f9 100644
--- a/src/include/utils/rel.h
+++ b/src/include/utils/rel.h
@@ -346,8 +346,7 @@ typedef struct StdRdOptions
bool user_catalog_table; /* use as an additional catalog relation */
int parallel_workers; /* max number of parallel workers */
StdRdOptIndexCleanup vacuum_index_cleanup; /* controls index vacuuming */
- bool vacuum_truncate; /* enables vacuum to truncate a relation */
- bool vacuum_truncate_set; /* whether vacuum_truncate is set */
+ ternary vacuum_truncate; /* enables vacuum to truncate a relation */
/*
* Fraction of pages in a relation that vacuum can eagerly scan and fail
--
2.39.2
--nextPart5129932.5fSG56mABF
Content-Disposition: attachment;
filename="v2-0003-Add-alias-to-be-used-as-unset-state.patch"
Content-Transfer-Encoding: 7Bit
Content-Type: text/x-patch; charset="unicode-2-0-utf-8";
name="v2-0003-Add-alias-to-be-used-as-unset-state.patch"
^ permalink raw reply [nested|flat] 868+ messages in thread
* [PATCH v2 2/4] Introduce ternary reloptions
@ 2025-09-04 14:41 Nikolay Shaplov <[email protected]>
0 siblings, 0 replies; 868+ messages in thread
From: Nikolay Shaplov @ 2025-09-04 14:41 UTC (permalink / raw)
Introduce ternary reloption as a replacement for current `vacuum_truncate`
implementation. Remove `vacuum_truncate_set` additional flag and using
`TERNARY_UNSET` value instead.
---
src/backend/access/common/reloptions.c | 129 ++++++++++++++++++++-----
src/backend/commands/vacuum.c | 4 +-
src/include/access/reloptions.h | 26 ++---
src/include/c.h | 16 +++
src/include/utils/rel.h | 3 +-
5 files changed, 135 insertions(+), 43 deletions(-)
diff --git a/src/backend/access/common/reloptions.c b/src/backend/access/common/reloptions.c
index 0af3fea68f..f543f11001 100644
--- a/src/backend/access/common/reloptions.c
+++ b/src/backend/access/common/reloptions.c
@@ -40,9 +40,9 @@
*
* To add an option:
*
- * (i) decide on a type (bool, integer, real, enum, string), name, default
- * value, upper and lower bounds (if applicable); for strings, consider a
- * validation routine.
+ * (i) decide on a type (bool, ternary, integer, real, enum, string), name,
+ * default value, upper and lower bounds (if applicable); for strings,
+ * consider a validation routine.
* (ii) add a record below (or use add_<type>_reloption).
* (iii) add it to the appropriate options struct (perhaps StdRdOptions)
* (iv) add it to the appropriate handling routine (perhaps
@@ -147,15 +147,6 @@ static relopt_bool boolRelOpts[] =
},
false
},
- {
- {
- "vacuum_truncate",
- "Enables vacuum to truncate empty pages at the end of this table",
- RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
- ShareUpdateExclusiveLock
- },
- true
- },
{
{
"deduplicate_items",
@@ -170,6 +161,21 @@ static relopt_bool boolRelOpts[] =
{{NULL}}
};
+static relopt_ternary ternaryRelOpts[] =
+{
+ {
+ {
+ "vacuum_truncate",
+ "Enables vacuum to truncate empty pages at the end of this table",
+ RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
+ ShareUpdateExclusiveLock
+ },
+ TERNARY_UNSET
+ },
+ /* list terminator */
+ {{NULL}}
+};
+
static relopt_int intRelOpts[] =
{
{
@@ -600,6 +606,13 @@ initialize_reloptions(void)
boolRelOpts[i].gen.lockmode));
j++;
}
+ for (i = 0; ternaryRelOpts[i].gen.name; i++)
+ {
+ Assert(DoLockModesConflict(ternaryRelOpts[i].gen.lockmode,
+ ternaryRelOpts[i].gen.lockmode));
+ j++;
+ }
+
for (i = 0; intRelOpts[i].gen.name; i++)
{
Assert(DoLockModesConflict(intRelOpts[i].gen.lockmode,
@@ -640,6 +653,14 @@ initialize_reloptions(void)
j++;
}
+ for (i = 0; ternaryRelOpts[i].gen.name; i++)
+ {
+ relOpts[j] = &ternaryRelOpts[i].gen;
+ relOpts[j]->type = RELOPT_TYPE_TERNARY;
+ relOpts[j]->namelen = strlen(relOpts[j]->name);
+ j++;
+ }
+
for (i = 0; intRelOpts[i].gen.name; i++)
{
relOpts[j] = &intRelOpts[i].gen;
@@ -800,6 +821,9 @@ allocate_reloption(bits32 kinds, int type, const char *name, const char *desc,
case RELOPT_TYPE_BOOL:
size = sizeof(relopt_bool);
break;
+ case RELOPT_TYPE_TERNARY:
+ size = sizeof(relopt_ternary);
+ break;
case RELOPT_TYPE_INT:
size = sizeof(relopt_int);
break;
@@ -883,6 +907,54 @@ add_local_bool_reloption(local_relopts *relopts, const char *name,
add_local_reloption(relopts, (relopt_gen *) newoption, offset);
}
+/*
+ * init_ternary_reloption
+ * Allocate and initialize a new ternary reloption
+ */
+static relopt_ternary *
+init_ternary_reloption(bits32 kinds, const char *name, const char *desc,
+ ternary default_val, LOCKMODE lockmode)
+{
+ relopt_ternary *newoption;
+
+ newoption = (relopt_ternary *) allocate_reloption(kinds,
+ RELOPT_TYPE_TERNARY, name, desc, lockmode);
+ newoption->default_val = default_val;
+
+ return newoption;
+}
+
+/*
+ * add_ternary_reloption
+ * Add a new ternary reloption
+ */
+void
+add_ternary_reloption(bits32 kinds, const char *name, const char *desc,
+ ternary default_val, LOCKMODE lockmode)
+{
+ relopt_ternary *newoption = init_ternary_reloption(kinds, name, desc,
+ default_val, lockmode);
+
+ add_reloption((relopt_gen *) newoption);
+}
+
+/*
+ * add_local_ternary_reloption
+ * Add a new ternary local reloption
+ *
+ * 'offset' is offset of ternary-typed field.
+ */
+void
+add_local_ternary_reloption(local_relopts *relopts, const char *name,
+ const char *desc, ternary default_val,
+ int offset)
+{
+ relopt_ternary *newoption = init_ternary_reloption(RELOPT_KIND_LOCAL,
+ name, desc,
+ default_val, 0);
+
+ add_local_reloption(relopts, (relopt_gen *) newoption, offset);
+}
/*
* init_real_reloption
@@ -1617,6 +1689,18 @@ parse_one_reloption(relopt_value *option, char *text_str, int text_len,
option->gen->name, value)));
}
break;
+ case RELOPT_TYPE_TERNARY:
+ {
+ bool b;
+ parsed = parse_bool(value, &b);
+ option->values.ternary_val = b ? TERNARY_TRUE : TERNARY_FALSE;
+ if (validate && !parsed)
+ ereport(ERROR,
+ (errcode(ERRCODE_INVALID_PARAMETER_VALUE),
+ errmsg("invalid value for ternary option \"%s\": %s",
+ option->gen->name, value)));
+ }
+ break;
case RELOPT_TYPE_INT:
{
relopt_int *optint = (relopt_int *) option->gen;
@@ -1780,17 +1864,6 @@ fillRelOptions(void *rdopts, Size basesize,
char *itempos = ((char *) rdopts) + elems[j].offset;
char *string_val;
- /*
- * If isset_offset is provided, store whether the reloption is
- * set there.
- */
- if (elems[j].isset_offset > 0)
- {
- char *setpos = ((char *) rdopts) + elems[j].isset_offset;
-
- *(bool *) setpos = options[i].isset;
- }
-
switch (options[i].gen->type)
{
case RELOPT_TYPE_BOOL:
@@ -1798,6 +1871,11 @@ fillRelOptions(void *rdopts, Size basesize,
options[i].values.bool_val :
((relopt_bool *) options[i].gen)->default_val;
break;
+ case RELOPT_TYPE_TERNARY:
+ *(ternary *) itempos = options[i].isset ?
+ options[i].values.ternary_val :
+ ((relopt_ternary *) options[i].gen)->default_val;
+ break;
case RELOPT_TYPE_INT:
*(int *) itempos = options[i].isset ?
options[i].values.int_val :
@@ -1912,8 +1990,8 @@ default_reloptions(Datum reloptions, bool validate, relopt_kind kind)
offsetof(StdRdOptions, parallel_workers)},
{"vacuum_index_cleanup", RELOPT_TYPE_ENUM,
offsetof(StdRdOptions, vacuum_index_cleanup)},
- {"vacuum_truncate", RELOPT_TYPE_BOOL,
- offsetof(StdRdOptions, vacuum_truncate), offsetof(StdRdOptions, vacuum_truncate_set)},
+ {"vacuum_truncate", RELOPT_TYPE_TERNARY,
+ offsetof(StdRdOptions, vacuum_truncate)},
{"vacuum_max_eager_freeze_failure_rate", RELOPT_TYPE_REAL,
offsetof(StdRdOptions, vacuum_max_eager_freeze_failure_rate)}
};
@@ -1993,7 +2071,6 @@ build_local_reloptions(local_relopts *relopts, Datum options, bool validate)
elems[i].optname = opt->option->name;
elems[i].opttype = opt->option->type;
elems[i].offset = opt->offset;
- elems[i].isset_offset = 0; /* not supported for local relopts yet */
i++;
}
diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c
index 733ef40ae7..977babff54 100644
--- a/src/backend/commands/vacuum.c
+++ b/src/backend/commands/vacuum.c
@@ -2221,9 +2221,9 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams params,
{
StdRdOptions *opts = (StdRdOptions *) rel->rd_options;
- if (opts && opts->vacuum_truncate_set)
+ if (opts && opts->vacuum_truncate != TERNARY_UNSET)
{
- if (opts->vacuum_truncate)
+ if (opts->vacuum_truncate == TERNARY_TRUE)
params.truncate = VACOPTVALUE_ENABLED;
else
params.truncate = VACOPTVALUE_DISABLED;
diff --git a/src/include/access/reloptions.h b/src/include/access/reloptions.h
index a604a4702c..a436697658 100644
--- a/src/include/access/reloptions.h
+++ b/src/include/access/reloptions.h
@@ -29,6 +29,7 @@
typedef enum relopt_type
{
RELOPT_TYPE_BOOL,
+ RELOPT_TYPE_TERNARY, /* on, off, unset */
RELOPT_TYPE_INT,
RELOPT_TYPE_REAL,
RELOPT_TYPE_ENUM,
@@ -80,6 +81,7 @@ typedef struct relopt_value
union
{
bool bool_val;
+ ternary ternary_val;
int int_val;
double real_val;
int enum_val;
@@ -94,6 +96,12 @@ typedef struct relopt_bool
bool default_val;
} relopt_bool;
+typedef struct relopt_rernary
+{
+ relopt_gen gen;
+ int default_val;
+} relopt_ternary;
+
typedef struct relopt_int
{
relopt_gen gen;
@@ -152,19 +160,6 @@ typedef struct
const char *optname; /* option's name */
relopt_type opttype; /* option's datatype */
int offset; /* offset of field in result struct */
-
- /*
- * isset_offset is an optional offset of a field in the result struct that
- * stores whether the option is explicitly set for the relation or if it
- * just picked up the default value. In most cases, this can be
- * accomplished by giving the reloption a special out-of-range default
- * value (e.g., some integer reloptions use -2), but this isn't always
- * possible. For example, a Boolean reloption cannot be given an
- * out-of-range default, so we need another way to discover the source of
- * its value. This offset is only used if given a value greater than
- * zero.
- */
- int isset_offset;
} relopt_parse_elt;
/* Local reloption definition */
@@ -195,6 +190,8 @@ typedef struct local_relopts
extern relopt_kind add_reloption_kind(void);
extern void add_bool_reloption(bits32 kinds, const char *name, const char *desc,
bool default_val, LOCKMODE lockmode);
+extern void add_ternary_reloption(bits32 kinds, const char *name,
+ const char *desc, int default_val, LOCKMODE lockmode);
extern void add_int_reloption(bits32 kinds, const char *name, const char *desc,
int default_val, int min_val, int max_val,
LOCKMODE lockmode);
@@ -214,6 +211,9 @@ extern void register_reloptions_validator(local_relopts *relopts,
extern void add_local_bool_reloption(local_relopts *relopts, const char *name,
const char *desc, bool default_val,
int offset);
+extern void add_local_ternary_reloption(local_relopts *relopts,
+ const char *name, const char *desc,
+ ternary default_val, int offset);
extern void add_local_int_reloption(local_relopts *relopts, const char *name,
const char *desc, int default_val,
int min_val, int max_val, int offset);
diff --git a/src/include/c.h b/src/include/c.h
index 39022f8a9d..9c59ccccf8 100644
--- a/src/include/c.h
+++ b/src/include/c.h
@@ -488,6 +488,22 @@ typedef void (*pg_funcptr_t) (void);
#include <stdbool.h>
+/*
+ * ternary
+ * Boolean value with an extrea "unset" option
+ *
+ * Ternary data type is used in relation options that can be "true", "false" or
+ * "unset". Since relation options are used deep inside the PostgreSQL code,
+ * this type is declared globally.
+*/
+
+typedef enum ternary
+{
+ TERNARY_FALSE = 0,
+ TERNARY_TRUE = 1,
+ TERNARY_UNSET = -1
+} ternary;
+
/* ----------------------------------------------------------------
* Section 3: standard system types
diff --git a/src/include/utils/rel.h b/src/include/utils/rel.h
index b552359915..7346d618f9 100644
--- a/src/include/utils/rel.h
+++ b/src/include/utils/rel.h
@@ -346,8 +346,7 @@ typedef struct StdRdOptions
bool user_catalog_table; /* use as an additional catalog relation */
int parallel_workers; /* max number of parallel workers */
StdRdOptIndexCleanup vacuum_index_cleanup; /* controls index vacuuming */
- bool vacuum_truncate; /* enables vacuum to truncate a relation */
- bool vacuum_truncate_set; /* whether vacuum_truncate is set */
+ ternary vacuum_truncate; /* enables vacuum to truncate a relation */
/*
* Fraction of pages in a relation that vacuum can eagerly scan and fail
--
2.39.2
--nextPart5129932.5fSG56mABF
Content-Disposition: attachment;
filename="v2-0003-Add-alias-to-be-used-as-unset-state.patch"
Content-Transfer-Encoding: 7Bit
Content-Type: text/x-patch; charset="unicode-2-0-utf-8";
name="v2-0003-Add-alias-to-be-used-as-unset-state.patch"
^ permalink raw reply [nested|flat] 868+ messages in thread
* [PATCH v2a 2/4] Introduce ternary reloptions
@ 2025-09-04 14:41 Nikolay Shaplov <[email protected]>
0 siblings, 0 replies; 868+ messages in thread
From: Nikolay Shaplov @ 2025-09-04 14:41 UTC (permalink / raw)
Introduce ternary reloption as a replacement for current `vacuum_truncate`
implementation. Remove `vacuum_truncate_set` additional flag and using
`TERNARY_UNSET` value instead.
---
src/backend/access/common/reloptions.c | 129 ++++++++++++++++++++-----
src/backend/commands/vacuum.c | 4 +-
src/include/access/reloptions.h | 26 ++---
src/include/c.h | 16 +++
src/include/utils/rel.h | 3 +-
5 files changed, 135 insertions(+), 43 deletions(-)
diff --git a/src/backend/access/common/reloptions.c b/src/backend/access/common/reloptions.c
index 0af3fea68f..f543f11001 100644
--- a/src/backend/access/common/reloptions.c
+++ b/src/backend/access/common/reloptions.c
@@ -40,9 +40,9 @@
*
* To add an option:
*
- * (i) decide on a type (bool, integer, real, enum, string), name, default
- * value, upper and lower bounds (if applicable); for strings, consider a
- * validation routine.
+ * (i) decide on a type (bool, ternary, integer, real, enum, string), name,
+ * default value, upper and lower bounds (if applicable); for strings,
+ * consider a validation routine.
* (ii) add a record below (or use add_<type>_reloption).
* (iii) add it to the appropriate options struct (perhaps StdRdOptions)
* (iv) add it to the appropriate handling routine (perhaps
@@ -147,15 +147,6 @@ static relopt_bool boolRelOpts[] =
},
false
},
- {
- {
- "vacuum_truncate",
- "Enables vacuum to truncate empty pages at the end of this table",
- RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
- ShareUpdateExclusiveLock
- },
- true
- },
{
{
"deduplicate_items",
@@ -170,6 +161,21 @@ static relopt_bool boolRelOpts[] =
{{NULL}}
};
+static relopt_ternary ternaryRelOpts[] =
+{
+ {
+ {
+ "vacuum_truncate",
+ "Enables vacuum to truncate empty pages at the end of this table",
+ RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
+ ShareUpdateExclusiveLock
+ },
+ TERNARY_UNSET
+ },
+ /* list terminator */
+ {{NULL}}
+};
+
static relopt_int intRelOpts[] =
{
{
@@ -600,6 +606,13 @@ initialize_reloptions(void)
boolRelOpts[i].gen.lockmode));
j++;
}
+ for (i = 0; ternaryRelOpts[i].gen.name; i++)
+ {
+ Assert(DoLockModesConflict(ternaryRelOpts[i].gen.lockmode,
+ ternaryRelOpts[i].gen.lockmode));
+ j++;
+ }
+
for (i = 0; intRelOpts[i].gen.name; i++)
{
Assert(DoLockModesConflict(intRelOpts[i].gen.lockmode,
@@ -640,6 +653,14 @@ initialize_reloptions(void)
j++;
}
+ for (i = 0; ternaryRelOpts[i].gen.name; i++)
+ {
+ relOpts[j] = &ternaryRelOpts[i].gen;
+ relOpts[j]->type = RELOPT_TYPE_TERNARY;
+ relOpts[j]->namelen = strlen(relOpts[j]->name);
+ j++;
+ }
+
for (i = 0; intRelOpts[i].gen.name; i++)
{
relOpts[j] = &intRelOpts[i].gen;
@@ -800,6 +821,9 @@ allocate_reloption(bits32 kinds, int type, const char *name, const char *desc,
case RELOPT_TYPE_BOOL:
size = sizeof(relopt_bool);
break;
+ case RELOPT_TYPE_TERNARY:
+ size = sizeof(relopt_ternary);
+ break;
case RELOPT_TYPE_INT:
size = sizeof(relopt_int);
break;
@@ -883,6 +907,54 @@ add_local_bool_reloption(local_relopts *relopts, const char *name,
add_local_reloption(relopts, (relopt_gen *) newoption, offset);
}
+/*
+ * init_ternary_reloption
+ * Allocate and initialize a new ternary reloption
+ */
+static relopt_ternary *
+init_ternary_reloption(bits32 kinds, const char *name, const char *desc,
+ ternary default_val, LOCKMODE lockmode)
+{
+ relopt_ternary *newoption;
+
+ newoption = (relopt_ternary *) allocate_reloption(kinds,
+ RELOPT_TYPE_TERNARY, name, desc, lockmode);
+ newoption->default_val = default_val;
+
+ return newoption;
+}
+
+/*
+ * add_ternary_reloption
+ * Add a new ternary reloption
+ */
+void
+add_ternary_reloption(bits32 kinds, const char *name, const char *desc,
+ ternary default_val, LOCKMODE lockmode)
+{
+ relopt_ternary *newoption = init_ternary_reloption(kinds, name, desc,
+ default_val, lockmode);
+
+ add_reloption((relopt_gen *) newoption);
+}
+
+/*
+ * add_local_ternary_reloption
+ * Add a new ternary local reloption
+ *
+ * 'offset' is offset of ternary-typed field.
+ */
+void
+add_local_ternary_reloption(local_relopts *relopts, const char *name,
+ const char *desc, ternary default_val,
+ int offset)
+{
+ relopt_ternary *newoption = init_ternary_reloption(RELOPT_KIND_LOCAL,
+ name, desc,
+ default_val, 0);
+
+ add_local_reloption(relopts, (relopt_gen *) newoption, offset);
+}
/*
* init_real_reloption
@@ -1617,6 +1689,18 @@ parse_one_reloption(relopt_value *option, char *text_str, int text_len,
option->gen->name, value)));
}
break;
+ case RELOPT_TYPE_TERNARY:
+ {
+ bool b;
+ parsed = parse_bool(value, &b);
+ option->values.ternary_val = b ? TERNARY_TRUE : TERNARY_FALSE;
+ if (validate && !parsed)
+ ereport(ERROR,
+ (errcode(ERRCODE_INVALID_PARAMETER_VALUE),
+ errmsg("invalid value for ternary option \"%s\": %s",
+ option->gen->name, value)));
+ }
+ break;
case RELOPT_TYPE_INT:
{
relopt_int *optint = (relopt_int *) option->gen;
@@ -1780,17 +1864,6 @@ fillRelOptions(void *rdopts, Size basesize,
char *itempos = ((char *) rdopts) + elems[j].offset;
char *string_val;
- /*
- * If isset_offset is provided, store whether the reloption is
- * set there.
- */
- if (elems[j].isset_offset > 0)
- {
- char *setpos = ((char *) rdopts) + elems[j].isset_offset;
-
- *(bool *) setpos = options[i].isset;
- }
-
switch (options[i].gen->type)
{
case RELOPT_TYPE_BOOL:
@@ -1798,6 +1871,11 @@ fillRelOptions(void *rdopts, Size basesize,
options[i].values.bool_val :
((relopt_bool *) options[i].gen)->default_val;
break;
+ case RELOPT_TYPE_TERNARY:
+ *(ternary *) itempos = options[i].isset ?
+ options[i].values.ternary_val :
+ ((relopt_ternary *) options[i].gen)->default_val;
+ break;
case RELOPT_TYPE_INT:
*(int *) itempos = options[i].isset ?
options[i].values.int_val :
@@ -1912,8 +1990,8 @@ default_reloptions(Datum reloptions, bool validate, relopt_kind kind)
offsetof(StdRdOptions, parallel_workers)},
{"vacuum_index_cleanup", RELOPT_TYPE_ENUM,
offsetof(StdRdOptions, vacuum_index_cleanup)},
- {"vacuum_truncate", RELOPT_TYPE_BOOL,
- offsetof(StdRdOptions, vacuum_truncate), offsetof(StdRdOptions, vacuum_truncate_set)},
+ {"vacuum_truncate", RELOPT_TYPE_TERNARY,
+ offsetof(StdRdOptions, vacuum_truncate)},
{"vacuum_max_eager_freeze_failure_rate", RELOPT_TYPE_REAL,
offsetof(StdRdOptions, vacuum_max_eager_freeze_failure_rate)}
};
@@ -1993,7 +2071,6 @@ build_local_reloptions(local_relopts *relopts, Datum options, bool validate)
elems[i].optname = opt->option->name;
elems[i].opttype = opt->option->type;
elems[i].offset = opt->offset;
- elems[i].isset_offset = 0; /* not supported for local relopts yet */
i++;
}
diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c
index 733ef40ae7..977babff54 100644
--- a/src/backend/commands/vacuum.c
+++ b/src/backend/commands/vacuum.c
@@ -2221,9 +2221,9 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams params,
{
StdRdOptions *opts = (StdRdOptions *) rel->rd_options;
- if (opts && opts->vacuum_truncate_set)
+ if (opts && opts->vacuum_truncate != TERNARY_UNSET)
{
- if (opts->vacuum_truncate)
+ if (opts->vacuum_truncate == TERNARY_TRUE)
params.truncate = VACOPTVALUE_ENABLED;
else
params.truncate = VACOPTVALUE_DISABLED;
diff --git a/src/include/access/reloptions.h b/src/include/access/reloptions.h
index a604a4702c..a436697658 100644
--- a/src/include/access/reloptions.h
+++ b/src/include/access/reloptions.h
@@ -29,6 +29,7 @@
typedef enum relopt_type
{
RELOPT_TYPE_BOOL,
+ RELOPT_TYPE_TERNARY, /* on, off, unset */
RELOPT_TYPE_INT,
RELOPT_TYPE_REAL,
RELOPT_TYPE_ENUM,
@@ -80,6 +81,7 @@ typedef struct relopt_value
union
{
bool bool_val;
+ ternary ternary_val;
int int_val;
double real_val;
int enum_val;
@@ -94,6 +96,12 @@ typedef struct relopt_bool
bool default_val;
} relopt_bool;
+typedef struct relopt_rernary
+{
+ relopt_gen gen;
+ int default_val;
+} relopt_ternary;
+
typedef struct relopt_int
{
relopt_gen gen;
@@ -152,19 +160,6 @@ typedef struct
const char *optname; /* option's name */
relopt_type opttype; /* option's datatype */
int offset; /* offset of field in result struct */
-
- /*
- * isset_offset is an optional offset of a field in the result struct that
- * stores whether the option is explicitly set for the relation or if it
- * just picked up the default value. In most cases, this can be
- * accomplished by giving the reloption a special out-of-range default
- * value (e.g., some integer reloptions use -2), but this isn't always
- * possible. For example, a Boolean reloption cannot be given an
- * out-of-range default, so we need another way to discover the source of
- * its value. This offset is only used if given a value greater than
- * zero.
- */
- int isset_offset;
} relopt_parse_elt;
/* Local reloption definition */
@@ -195,6 +190,8 @@ typedef struct local_relopts
extern relopt_kind add_reloption_kind(void);
extern void add_bool_reloption(bits32 kinds, const char *name, const char *desc,
bool default_val, LOCKMODE lockmode);
+extern void add_ternary_reloption(bits32 kinds, const char *name,
+ const char *desc, int default_val, LOCKMODE lockmode);
extern void add_int_reloption(bits32 kinds, const char *name, const char *desc,
int default_val, int min_val, int max_val,
LOCKMODE lockmode);
@@ -214,6 +211,9 @@ extern void register_reloptions_validator(local_relopts *relopts,
extern void add_local_bool_reloption(local_relopts *relopts, const char *name,
const char *desc, bool default_val,
int offset);
+extern void add_local_ternary_reloption(local_relopts *relopts,
+ const char *name, const char *desc,
+ ternary default_val, int offset);
extern void add_local_int_reloption(local_relopts *relopts, const char *name,
const char *desc, int default_val,
int min_val, int max_val, int offset);
diff --git a/src/include/c.h b/src/include/c.h
index 39022f8a9d..9c59ccccf8 100644
--- a/src/include/c.h
+++ b/src/include/c.h
@@ -488,6 +488,22 @@ typedef void (*pg_funcptr_t) (void);
#include <stdbool.h>
+/*
+ * ternary
+ * Boolean value with an extrea "unset" option
+ *
+ * Ternary data type is used in relation options that can be "true", "false" or
+ * "unset". Since relation options are used deep inside the PostgreSQL code,
+ * this type is declared globally.
+*/
+
+typedef enum ternary
+{
+ TERNARY_FALSE = 0,
+ TERNARY_TRUE = 1,
+ TERNARY_UNSET = -1
+} ternary;
+
/* ----------------------------------------------------------------
* Section 3: standard system types
diff --git a/src/include/utils/rel.h b/src/include/utils/rel.h
index b552359915..7346d618f9 100644
--- a/src/include/utils/rel.h
+++ b/src/include/utils/rel.h
@@ -346,8 +346,7 @@ typedef struct StdRdOptions
bool user_catalog_table; /* use as an additional catalog relation */
int parallel_workers; /* max number of parallel workers */
StdRdOptIndexCleanup vacuum_index_cleanup; /* controls index vacuuming */
- bool vacuum_truncate; /* enables vacuum to truncate a relation */
- bool vacuum_truncate_set; /* whether vacuum_truncate is set */
+ ternary vacuum_truncate; /* enables vacuum to truncate a relation */
/*
* Fraction of pages in a relation that vacuum can eagerly scan and fail
--
2.39.2
--nextPart4155600.3ZeAukHxDK
Content-Disposition: attachment;
filename="v2a-0003-Add-alias-to-be-used-as-unset-state.patch"
Content-Transfer-Encoding: 7Bit
Content-Type: text/x-patch; charset="unicode-2-0-utf-8";
name="v2a-0003-Add-alias-to-be-used-as-unset-state.patch"
^ permalink raw reply [nested|flat] 868+ messages in thread
* [PATCH v2a 2/4] Introduce ternary reloptions
@ 2025-09-04 14:41 Nikolay Shaplov <[email protected]>
0 siblings, 0 replies; 868+ messages in thread
From: Nikolay Shaplov @ 2025-09-04 14:41 UTC (permalink / raw)
Introduce ternary reloption as a replacement for current `vacuum_truncate`
implementation. Remove `vacuum_truncate_set` additional flag and using
`TERNARY_UNSET` value instead.
---
src/backend/access/common/reloptions.c | 129 ++++++++++++++++++++-----
src/backend/commands/vacuum.c | 4 +-
src/include/access/reloptions.h | 26 ++---
src/include/c.h | 16 +++
src/include/utils/rel.h | 3 +-
5 files changed, 135 insertions(+), 43 deletions(-)
diff --git a/src/backend/access/common/reloptions.c b/src/backend/access/common/reloptions.c
index 0af3fea68f..f543f11001 100644
--- a/src/backend/access/common/reloptions.c
+++ b/src/backend/access/common/reloptions.c
@@ -40,9 +40,9 @@
*
* To add an option:
*
- * (i) decide on a type (bool, integer, real, enum, string), name, default
- * value, upper and lower bounds (if applicable); for strings, consider a
- * validation routine.
+ * (i) decide on a type (bool, ternary, integer, real, enum, string), name,
+ * default value, upper and lower bounds (if applicable); for strings,
+ * consider a validation routine.
* (ii) add a record below (or use add_<type>_reloption).
* (iii) add it to the appropriate options struct (perhaps StdRdOptions)
* (iv) add it to the appropriate handling routine (perhaps
@@ -147,15 +147,6 @@ static relopt_bool boolRelOpts[] =
},
false
},
- {
- {
- "vacuum_truncate",
- "Enables vacuum to truncate empty pages at the end of this table",
- RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
- ShareUpdateExclusiveLock
- },
- true
- },
{
{
"deduplicate_items",
@@ -170,6 +161,21 @@ static relopt_bool boolRelOpts[] =
{{NULL}}
};
+static relopt_ternary ternaryRelOpts[] =
+{
+ {
+ {
+ "vacuum_truncate",
+ "Enables vacuum to truncate empty pages at the end of this table",
+ RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
+ ShareUpdateExclusiveLock
+ },
+ TERNARY_UNSET
+ },
+ /* list terminator */
+ {{NULL}}
+};
+
static relopt_int intRelOpts[] =
{
{
@@ -600,6 +606,13 @@ initialize_reloptions(void)
boolRelOpts[i].gen.lockmode));
j++;
}
+ for (i = 0; ternaryRelOpts[i].gen.name; i++)
+ {
+ Assert(DoLockModesConflict(ternaryRelOpts[i].gen.lockmode,
+ ternaryRelOpts[i].gen.lockmode));
+ j++;
+ }
+
for (i = 0; intRelOpts[i].gen.name; i++)
{
Assert(DoLockModesConflict(intRelOpts[i].gen.lockmode,
@@ -640,6 +653,14 @@ initialize_reloptions(void)
j++;
}
+ for (i = 0; ternaryRelOpts[i].gen.name; i++)
+ {
+ relOpts[j] = &ternaryRelOpts[i].gen;
+ relOpts[j]->type = RELOPT_TYPE_TERNARY;
+ relOpts[j]->namelen = strlen(relOpts[j]->name);
+ j++;
+ }
+
for (i = 0; intRelOpts[i].gen.name; i++)
{
relOpts[j] = &intRelOpts[i].gen;
@@ -800,6 +821,9 @@ allocate_reloption(bits32 kinds, int type, const char *name, const char *desc,
case RELOPT_TYPE_BOOL:
size = sizeof(relopt_bool);
break;
+ case RELOPT_TYPE_TERNARY:
+ size = sizeof(relopt_ternary);
+ break;
case RELOPT_TYPE_INT:
size = sizeof(relopt_int);
break;
@@ -883,6 +907,54 @@ add_local_bool_reloption(local_relopts *relopts, const char *name,
add_local_reloption(relopts, (relopt_gen *) newoption, offset);
}
+/*
+ * init_ternary_reloption
+ * Allocate and initialize a new ternary reloption
+ */
+static relopt_ternary *
+init_ternary_reloption(bits32 kinds, const char *name, const char *desc,
+ ternary default_val, LOCKMODE lockmode)
+{
+ relopt_ternary *newoption;
+
+ newoption = (relopt_ternary *) allocate_reloption(kinds,
+ RELOPT_TYPE_TERNARY, name, desc, lockmode);
+ newoption->default_val = default_val;
+
+ return newoption;
+}
+
+/*
+ * add_ternary_reloption
+ * Add a new ternary reloption
+ */
+void
+add_ternary_reloption(bits32 kinds, const char *name, const char *desc,
+ ternary default_val, LOCKMODE lockmode)
+{
+ relopt_ternary *newoption = init_ternary_reloption(kinds, name, desc,
+ default_val, lockmode);
+
+ add_reloption((relopt_gen *) newoption);
+}
+
+/*
+ * add_local_ternary_reloption
+ * Add a new ternary local reloption
+ *
+ * 'offset' is offset of ternary-typed field.
+ */
+void
+add_local_ternary_reloption(local_relopts *relopts, const char *name,
+ const char *desc, ternary default_val,
+ int offset)
+{
+ relopt_ternary *newoption = init_ternary_reloption(RELOPT_KIND_LOCAL,
+ name, desc,
+ default_val, 0);
+
+ add_local_reloption(relopts, (relopt_gen *) newoption, offset);
+}
/*
* init_real_reloption
@@ -1617,6 +1689,18 @@ parse_one_reloption(relopt_value *option, char *text_str, int text_len,
option->gen->name, value)));
}
break;
+ case RELOPT_TYPE_TERNARY:
+ {
+ bool b;
+ parsed = parse_bool(value, &b);
+ option->values.ternary_val = b ? TERNARY_TRUE : TERNARY_FALSE;
+ if (validate && !parsed)
+ ereport(ERROR,
+ (errcode(ERRCODE_INVALID_PARAMETER_VALUE),
+ errmsg("invalid value for ternary option \"%s\": %s",
+ option->gen->name, value)));
+ }
+ break;
case RELOPT_TYPE_INT:
{
relopt_int *optint = (relopt_int *) option->gen;
@@ -1780,17 +1864,6 @@ fillRelOptions(void *rdopts, Size basesize,
char *itempos = ((char *) rdopts) + elems[j].offset;
char *string_val;
- /*
- * If isset_offset is provided, store whether the reloption is
- * set there.
- */
- if (elems[j].isset_offset > 0)
- {
- char *setpos = ((char *) rdopts) + elems[j].isset_offset;
-
- *(bool *) setpos = options[i].isset;
- }
-
switch (options[i].gen->type)
{
case RELOPT_TYPE_BOOL:
@@ -1798,6 +1871,11 @@ fillRelOptions(void *rdopts, Size basesize,
options[i].values.bool_val :
((relopt_bool *) options[i].gen)->default_val;
break;
+ case RELOPT_TYPE_TERNARY:
+ *(ternary *) itempos = options[i].isset ?
+ options[i].values.ternary_val :
+ ((relopt_ternary *) options[i].gen)->default_val;
+ break;
case RELOPT_TYPE_INT:
*(int *) itempos = options[i].isset ?
options[i].values.int_val :
@@ -1912,8 +1990,8 @@ default_reloptions(Datum reloptions, bool validate, relopt_kind kind)
offsetof(StdRdOptions, parallel_workers)},
{"vacuum_index_cleanup", RELOPT_TYPE_ENUM,
offsetof(StdRdOptions, vacuum_index_cleanup)},
- {"vacuum_truncate", RELOPT_TYPE_BOOL,
- offsetof(StdRdOptions, vacuum_truncate), offsetof(StdRdOptions, vacuum_truncate_set)},
+ {"vacuum_truncate", RELOPT_TYPE_TERNARY,
+ offsetof(StdRdOptions, vacuum_truncate)},
{"vacuum_max_eager_freeze_failure_rate", RELOPT_TYPE_REAL,
offsetof(StdRdOptions, vacuum_max_eager_freeze_failure_rate)}
};
@@ -1993,7 +2071,6 @@ build_local_reloptions(local_relopts *relopts, Datum options, bool validate)
elems[i].optname = opt->option->name;
elems[i].opttype = opt->option->type;
elems[i].offset = opt->offset;
- elems[i].isset_offset = 0; /* not supported for local relopts yet */
i++;
}
diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c
index 733ef40ae7..977babff54 100644
--- a/src/backend/commands/vacuum.c
+++ b/src/backend/commands/vacuum.c
@@ -2221,9 +2221,9 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams params,
{
StdRdOptions *opts = (StdRdOptions *) rel->rd_options;
- if (opts && opts->vacuum_truncate_set)
+ if (opts && opts->vacuum_truncate != TERNARY_UNSET)
{
- if (opts->vacuum_truncate)
+ if (opts->vacuum_truncate == TERNARY_TRUE)
params.truncate = VACOPTVALUE_ENABLED;
else
params.truncate = VACOPTVALUE_DISABLED;
diff --git a/src/include/access/reloptions.h b/src/include/access/reloptions.h
index a604a4702c..a436697658 100644
--- a/src/include/access/reloptions.h
+++ b/src/include/access/reloptions.h
@@ -29,6 +29,7 @@
typedef enum relopt_type
{
RELOPT_TYPE_BOOL,
+ RELOPT_TYPE_TERNARY, /* on, off, unset */
RELOPT_TYPE_INT,
RELOPT_TYPE_REAL,
RELOPT_TYPE_ENUM,
@@ -80,6 +81,7 @@ typedef struct relopt_value
union
{
bool bool_val;
+ ternary ternary_val;
int int_val;
double real_val;
int enum_val;
@@ -94,6 +96,12 @@ typedef struct relopt_bool
bool default_val;
} relopt_bool;
+typedef struct relopt_rernary
+{
+ relopt_gen gen;
+ int default_val;
+} relopt_ternary;
+
typedef struct relopt_int
{
relopt_gen gen;
@@ -152,19 +160,6 @@ typedef struct
const char *optname; /* option's name */
relopt_type opttype; /* option's datatype */
int offset; /* offset of field in result struct */
-
- /*
- * isset_offset is an optional offset of a field in the result struct that
- * stores whether the option is explicitly set for the relation or if it
- * just picked up the default value. In most cases, this can be
- * accomplished by giving the reloption a special out-of-range default
- * value (e.g., some integer reloptions use -2), but this isn't always
- * possible. For example, a Boolean reloption cannot be given an
- * out-of-range default, so we need another way to discover the source of
- * its value. This offset is only used if given a value greater than
- * zero.
- */
- int isset_offset;
} relopt_parse_elt;
/* Local reloption definition */
@@ -195,6 +190,8 @@ typedef struct local_relopts
extern relopt_kind add_reloption_kind(void);
extern void add_bool_reloption(bits32 kinds, const char *name, const char *desc,
bool default_val, LOCKMODE lockmode);
+extern void add_ternary_reloption(bits32 kinds, const char *name,
+ const char *desc, int default_val, LOCKMODE lockmode);
extern void add_int_reloption(bits32 kinds, const char *name, const char *desc,
int default_val, int min_val, int max_val,
LOCKMODE lockmode);
@@ -214,6 +211,9 @@ extern void register_reloptions_validator(local_relopts *relopts,
extern void add_local_bool_reloption(local_relopts *relopts, const char *name,
const char *desc, bool default_val,
int offset);
+extern void add_local_ternary_reloption(local_relopts *relopts,
+ const char *name, const char *desc,
+ ternary default_val, int offset);
extern void add_local_int_reloption(local_relopts *relopts, const char *name,
const char *desc, int default_val,
int min_val, int max_val, int offset);
diff --git a/src/include/c.h b/src/include/c.h
index 39022f8a9d..9c59ccccf8 100644
--- a/src/include/c.h
+++ b/src/include/c.h
@@ -488,6 +488,22 @@ typedef void (*pg_funcptr_t) (void);
#include <stdbool.h>
+/*
+ * ternary
+ * Boolean value with an extrea "unset" option
+ *
+ * Ternary data type is used in relation options that can be "true", "false" or
+ * "unset". Since relation options are used deep inside the PostgreSQL code,
+ * this type is declared globally.
+*/
+
+typedef enum ternary
+{
+ TERNARY_FALSE = 0,
+ TERNARY_TRUE = 1,
+ TERNARY_UNSET = -1
+} ternary;
+
/* ----------------------------------------------------------------
* Section 3: standard system types
diff --git a/src/include/utils/rel.h b/src/include/utils/rel.h
index b552359915..7346d618f9 100644
--- a/src/include/utils/rel.h
+++ b/src/include/utils/rel.h
@@ -346,8 +346,7 @@ typedef struct StdRdOptions
bool user_catalog_table; /* use as an additional catalog relation */
int parallel_workers; /* max number of parallel workers */
StdRdOptIndexCleanup vacuum_index_cleanup; /* controls index vacuuming */
- bool vacuum_truncate; /* enables vacuum to truncate a relation */
- bool vacuum_truncate_set; /* whether vacuum_truncate is set */
+ ternary vacuum_truncate; /* enables vacuum to truncate a relation */
/*
* Fraction of pages in a relation that vacuum can eagerly scan and fail
--
2.39.2
--nextPart4155600.3ZeAukHxDK
Content-Disposition: attachment;
filename="v2a-0003-Add-alias-to-be-used-as-unset-state.patch"
Content-Transfer-Encoding: 7Bit
Content-Type: text/x-patch; charset="unicode-2-0-utf-8";
name="v2a-0003-Add-alias-to-be-used-as-unset-state.patch"
^ permalink raw reply [nested|flat] 868+ messages in thread
* [PATCH v2a 2/4] Introduce ternary reloptions
@ 2025-09-04 14:41 Nikolay Shaplov <[email protected]>
0 siblings, 0 replies; 868+ messages in thread
From: Nikolay Shaplov @ 2025-09-04 14:41 UTC (permalink / raw)
Introduce ternary reloption as a replacement for current `vacuum_truncate`
implementation. Remove `vacuum_truncate_set` additional flag and using
`TERNARY_UNSET` value instead.
---
src/backend/access/common/reloptions.c | 129 ++++++++++++++++++++-----
src/backend/commands/vacuum.c | 4 +-
src/include/access/reloptions.h | 26 ++---
src/include/c.h | 16 +++
src/include/utils/rel.h | 3 +-
5 files changed, 135 insertions(+), 43 deletions(-)
diff --git a/src/backend/access/common/reloptions.c b/src/backend/access/common/reloptions.c
index 0af3fea68f..f543f11001 100644
--- a/src/backend/access/common/reloptions.c
+++ b/src/backend/access/common/reloptions.c
@@ -40,9 +40,9 @@
*
* To add an option:
*
- * (i) decide on a type (bool, integer, real, enum, string), name, default
- * value, upper and lower bounds (if applicable); for strings, consider a
- * validation routine.
+ * (i) decide on a type (bool, ternary, integer, real, enum, string), name,
+ * default value, upper and lower bounds (if applicable); for strings,
+ * consider a validation routine.
* (ii) add a record below (or use add_<type>_reloption).
* (iii) add it to the appropriate options struct (perhaps StdRdOptions)
* (iv) add it to the appropriate handling routine (perhaps
@@ -147,15 +147,6 @@ static relopt_bool boolRelOpts[] =
},
false
},
- {
- {
- "vacuum_truncate",
- "Enables vacuum to truncate empty pages at the end of this table",
- RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
- ShareUpdateExclusiveLock
- },
- true
- },
{
{
"deduplicate_items",
@@ -170,6 +161,21 @@ static relopt_bool boolRelOpts[] =
{{NULL}}
};
+static relopt_ternary ternaryRelOpts[] =
+{
+ {
+ {
+ "vacuum_truncate",
+ "Enables vacuum to truncate empty pages at the end of this table",
+ RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
+ ShareUpdateExclusiveLock
+ },
+ TERNARY_UNSET
+ },
+ /* list terminator */
+ {{NULL}}
+};
+
static relopt_int intRelOpts[] =
{
{
@@ -600,6 +606,13 @@ initialize_reloptions(void)
boolRelOpts[i].gen.lockmode));
j++;
}
+ for (i = 0; ternaryRelOpts[i].gen.name; i++)
+ {
+ Assert(DoLockModesConflict(ternaryRelOpts[i].gen.lockmode,
+ ternaryRelOpts[i].gen.lockmode));
+ j++;
+ }
+
for (i = 0; intRelOpts[i].gen.name; i++)
{
Assert(DoLockModesConflict(intRelOpts[i].gen.lockmode,
@@ -640,6 +653,14 @@ initialize_reloptions(void)
j++;
}
+ for (i = 0; ternaryRelOpts[i].gen.name; i++)
+ {
+ relOpts[j] = &ternaryRelOpts[i].gen;
+ relOpts[j]->type = RELOPT_TYPE_TERNARY;
+ relOpts[j]->namelen = strlen(relOpts[j]->name);
+ j++;
+ }
+
for (i = 0; intRelOpts[i].gen.name; i++)
{
relOpts[j] = &intRelOpts[i].gen;
@@ -800,6 +821,9 @@ allocate_reloption(bits32 kinds, int type, const char *name, const char *desc,
case RELOPT_TYPE_BOOL:
size = sizeof(relopt_bool);
break;
+ case RELOPT_TYPE_TERNARY:
+ size = sizeof(relopt_ternary);
+ break;
case RELOPT_TYPE_INT:
size = sizeof(relopt_int);
break;
@@ -883,6 +907,54 @@ add_local_bool_reloption(local_relopts *relopts, const char *name,
add_local_reloption(relopts, (relopt_gen *) newoption, offset);
}
+/*
+ * init_ternary_reloption
+ * Allocate and initialize a new ternary reloption
+ */
+static relopt_ternary *
+init_ternary_reloption(bits32 kinds, const char *name, const char *desc,
+ ternary default_val, LOCKMODE lockmode)
+{
+ relopt_ternary *newoption;
+
+ newoption = (relopt_ternary *) allocate_reloption(kinds,
+ RELOPT_TYPE_TERNARY, name, desc, lockmode);
+ newoption->default_val = default_val;
+
+ return newoption;
+}
+
+/*
+ * add_ternary_reloption
+ * Add a new ternary reloption
+ */
+void
+add_ternary_reloption(bits32 kinds, const char *name, const char *desc,
+ ternary default_val, LOCKMODE lockmode)
+{
+ relopt_ternary *newoption = init_ternary_reloption(kinds, name, desc,
+ default_val, lockmode);
+
+ add_reloption((relopt_gen *) newoption);
+}
+
+/*
+ * add_local_ternary_reloption
+ * Add a new ternary local reloption
+ *
+ * 'offset' is offset of ternary-typed field.
+ */
+void
+add_local_ternary_reloption(local_relopts *relopts, const char *name,
+ const char *desc, ternary default_val,
+ int offset)
+{
+ relopt_ternary *newoption = init_ternary_reloption(RELOPT_KIND_LOCAL,
+ name, desc,
+ default_val, 0);
+
+ add_local_reloption(relopts, (relopt_gen *) newoption, offset);
+}
/*
* init_real_reloption
@@ -1617,6 +1689,18 @@ parse_one_reloption(relopt_value *option, char *text_str, int text_len,
option->gen->name, value)));
}
break;
+ case RELOPT_TYPE_TERNARY:
+ {
+ bool b;
+ parsed = parse_bool(value, &b);
+ option->values.ternary_val = b ? TERNARY_TRUE : TERNARY_FALSE;
+ if (validate && !parsed)
+ ereport(ERROR,
+ (errcode(ERRCODE_INVALID_PARAMETER_VALUE),
+ errmsg("invalid value for ternary option \"%s\": %s",
+ option->gen->name, value)));
+ }
+ break;
case RELOPT_TYPE_INT:
{
relopt_int *optint = (relopt_int *) option->gen;
@@ -1780,17 +1864,6 @@ fillRelOptions(void *rdopts, Size basesize,
char *itempos = ((char *) rdopts) + elems[j].offset;
char *string_val;
- /*
- * If isset_offset is provided, store whether the reloption is
- * set there.
- */
- if (elems[j].isset_offset > 0)
- {
- char *setpos = ((char *) rdopts) + elems[j].isset_offset;
-
- *(bool *) setpos = options[i].isset;
- }
-
switch (options[i].gen->type)
{
case RELOPT_TYPE_BOOL:
@@ -1798,6 +1871,11 @@ fillRelOptions(void *rdopts, Size basesize,
options[i].values.bool_val :
((relopt_bool *) options[i].gen)->default_val;
break;
+ case RELOPT_TYPE_TERNARY:
+ *(ternary *) itempos = options[i].isset ?
+ options[i].values.ternary_val :
+ ((relopt_ternary *) options[i].gen)->default_val;
+ break;
case RELOPT_TYPE_INT:
*(int *) itempos = options[i].isset ?
options[i].values.int_val :
@@ -1912,8 +1990,8 @@ default_reloptions(Datum reloptions, bool validate, relopt_kind kind)
offsetof(StdRdOptions, parallel_workers)},
{"vacuum_index_cleanup", RELOPT_TYPE_ENUM,
offsetof(StdRdOptions, vacuum_index_cleanup)},
- {"vacuum_truncate", RELOPT_TYPE_BOOL,
- offsetof(StdRdOptions, vacuum_truncate), offsetof(StdRdOptions, vacuum_truncate_set)},
+ {"vacuum_truncate", RELOPT_TYPE_TERNARY,
+ offsetof(StdRdOptions, vacuum_truncate)},
{"vacuum_max_eager_freeze_failure_rate", RELOPT_TYPE_REAL,
offsetof(StdRdOptions, vacuum_max_eager_freeze_failure_rate)}
};
@@ -1993,7 +2071,6 @@ build_local_reloptions(local_relopts *relopts, Datum options, bool validate)
elems[i].optname = opt->option->name;
elems[i].opttype = opt->option->type;
elems[i].offset = opt->offset;
- elems[i].isset_offset = 0; /* not supported for local relopts yet */
i++;
}
diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c
index 733ef40ae7..977babff54 100644
--- a/src/backend/commands/vacuum.c
+++ b/src/backend/commands/vacuum.c
@@ -2221,9 +2221,9 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams params,
{
StdRdOptions *opts = (StdRdOptions *) rel->rd_options;
- if (opts && opts->vacuum_truncate_set)
+ if (opts && opts->vacuum_truncate != TERNARY_UNSET)
{
- if (opts->vacuum_truncate)
+ if (opts->vacuum_truncate == TERNARY_TRUE)
params.truncate = VACOPTVALUE_ENABLED;
else
params.truncate = VACOPTVALUE_DISABLED;
diff --git a/src/include/access/reloptions.h b/src/include/access/reloptions.h
index a604a4702c..a436697658 100644
--- a/src/include/access/reloptions.h
+++ b/src/include/access/reloptions.h
@@ -29,6 +29,7 @@
typedef enum relopt_type
{
RELOPT_TYPE_BOOL,
+ RELOPT_TYPE_TERNARY, /* on, off, unset */
RELOPT_TYPE_INT,
RELOPT_TYPE_REAL,
RELOPT_TYPE_ENUM,
@@ -80,6 +81,7 @@ typedef struct relopt_value
union
{
bool bool_val;
+ ternary ternary_val;
int int_val;
double real_val;
int enum_val;
@@ -94,6 +96,12 @@ typedef struct relopt_bool
bool default_val;
} relopt_bool;
+typedef struct relopt_rernary
+{
+ relopt_gen gen;
+ int default_val;
+} relopt_ternary;
+
typedef struct relopt_int
{
relopt_gen gen;
@@ -152,19 +160,6 @@ typedef struct
const char *optname; /* option's name */
relopt_type opttype; /* option's datatype */
int offset; /* offset of field in result struct */
-
- /*
- * isset_offset is an optional offset of a field in the result struct that
- * stores whether the option is explicitly set for the relation or if it
- * just picked up the default value. In most cases, this can be
- * accomplished by giving the reloption a special out-of-range default
- * value (e.g., some integer reloptions use -2), but this isn't always
- * possible. For example, a Boolean reloption cannot be given an
- * out-of-range default, so we need another way to discover the source of
- * its value. This offset is only used if given a value greater than
- * zero.
- */
- int isset_offset;
} relopt_parse_elt;
/* Local reloption definition */
@@ -195,6 +190,8 @@ typedef struct local_relopts
extern relopt_kind add_reloption_kind(void);
extern void add_bool_reloption(bits32 kinds, const char *name, const char *desc,
bool default_val, LOCKMODE lockmode);
+extern void add_ternary_reloption(bits32 kinds, const char *name,
+ const char *desc, int default_val, LOCKMODE lockmode);
extern void add_int_reloption(bits32 kinds, const char *name, const char *desc,
int default_val, int min_val, int max_val,
LOCKMODE lockmode);
@@ -214,6 +211,9 @@ extern void register_reloptions_validator(local_relopts *relopts,
extern void add_local_bool_reloption(local_relopts *relopts, const char *name,
const char *desc, bool default_val,
int offset);
+extern void add_local_ternary_reloption(local_relopts *relopts,
+ const char *name, const char *desc,
+ ternary default_val, int offset);
extern void add_local_int_reloption(local_relopts *relopts, const char *name,
const char *desc, int default_val,
int min_val, int max_val, int offset);
diff --git a/src/include/c.h b/src/include/c.h
index 39022f8a9d..9c59ccccf8 100644
--- a/src/include/c.h
+++ b/src/include/c.h
@@ -488,6 +488,22 @@ typedef void (*pg_funcptr_t) (void);
#include <stdbool.h>
+/*
+ * ternary
+ * Boolean value with an extrea "unset" option
+ *
+ * Ternary data type is used in relation options that can be "true", "false" or
+ * "unset". Since relation options are used deep inside the PostgreSQL code,
+ * this type is declared globally.
+*/
+
+typedef enum ternary
+{
+ TERNARY_FALSE = 0,
+ TERNARY_TRUE = 1,
+ TERNARY_UNSET = -1
+} ternary;
+
/* ----------------------------------------------------------------
* Section 3: standard system types
diff --git a/src/include/utils/rel.h b/src/include/utils/rel.h
index b552359915..7346d618f9 100644
--- a/src/include/utils/rel.h
+++ b/src/include/utils/rel.h
@@ -346,8 +346,7 @@ typedef struct StdRdOptions
bool user_catalog_table; /* use as an additional catalog relation */
int parallel_workers; /* max number of parallel workers */
StdRdOptIndexCleanup vacuum_index_cleanup; /* controls index vacuuming */
- bool vacuum_truncate; /* enables vacuum to truncate a relation */
- bool vacuum_truncate_set; /* whether vacuum_truncate is set */
+ ternary vacuum_truncate; /* enables vacuum to truncate a relation */
/*
* Fraction of pages in a relation that vacuum can eagerly scan and fail
--
2.39.2
--nextPart4155600.3ZeAukHxDK
Content-Disposition: attachment;
filename="v2a-0003-Add-alias-to-be-used-as-unset-state.patch"
Content-Transfer-Encoding: 7Bit
Content-Type: text/x-patch; charset="unicode-2-0-utf-8";
name="v2a-0003-Add-alias-to-be-used-as-unset-state.patch"
^ permalink raw reply [nested|flat] 868+ messages in thread
* [PATCH v2 2/4] Introduce ternary reloptions
@ 2025-09-04 14:41 Nikolay Shaplov <[email protected]>
0 siblings, 0 replies; 868+ messages in thread
From: Nikolay Shaplov @ 2025-09-04 14:41 UTC (permalink / raw)
Introduce ternary reloption as a replacement for current `vacuum_truncate`
implementation. Remove `vacuum_truncate_set` additional flag and using
`TERNARY_UNSET` value instead.
---
src/backend/access/common/reloptions.c | 129 ++++++++++++++++++++-----
src/backend/commands/vacuum.c | 4 +-
src/include/access/reloptions.h | 26 ++---
src/include/c.h | 16 +++
src/include/utils/rel.h | 3 +-
5 files changed, 135 insertions(+), 43 deletions(-)
diff --git a/src/backend/access/common/reloptions.c b/src/backend/access/common/reloptions.c
index 0af3fea68f..f543f11001 100644
--- a/src/backend/access/common/reloptions.c
+++ b/src/backend/access/common/reloptions.c
@@ -40,9 +40,9 @@
*
* To add an option:
*
- * (i) decide on a type (bool, integer, real, enum, string), name, default
- * value, upper and lower bounds (if applicable); for strings, consider a
- * validation routine.
+ * (i) decide on a type (bool, ternary, integer, real, enum, string), name,
+ * default value, upper and lower bounds (if applicable); for strings,
+ * consider a validation routine.
* (ii) add a record below (or use add_<type>_reloption).
* (iii) add it to the appropriate options struct (perhaps StdRdOptions)
* (iv) add it to the appropriate handling routine (perhaps
@@ -147,15 +147,6 @@ static relopt_bool boolRelOpts[] =
},
false
},
- {
- {
- "vacuum_truncate",
- "Enables vacuum to truncate empty pages at the end of this table",
- RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
- ShareUpdateExclusiveLock
- },
- true
- },
{
{
"deduplicate_items",
@@ -170,6 +161,21 @@ static relopt_bool boolRelOpts[] =
{{NULL}}
};
+static relopt_ternary ternaryRelOpts[] =
+{
+ {
+ {
+ "vacuum_truncate",
+ "Enables vacuum to truncate empty pages at the end of this table",
+ RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
+ ShareUpdateExclusiveLock
+ },
+ TERNARY_UNSET
+ },
+ /* list terminator */
+ {{NULL}}
+};
+
static relopt_int intRelOpts[] =
{
{
@@ -600,6 +606,13 @@ initialize_reloptions(void)
boolRelOpts[i].gen.lockmode));
j++;
}
+ for (i = 0; ternaryRelOpts[i].gen.name; i++)
+ {
+ Assert(DoLockModesConflict(ternaryRelOpts[i].gen.lockmode,
+ ternaryRelOpts[i].gen.lockmode));
+ j++;
+ }
+
for (i = 0; intRelOpts[i].gen.name; i++)
{
Assert(DoLockModesConflict(intRelOpts[i].gen.lockmode,
@@ -640,6 +653,14 @@ initialize_reloptions(void)
j++;
}
+ for (i = 0; ternaryRelOpts[i].gen.name; i++)
+ {
+ relOpts[j] = &ternaryRelOpts[i].gen;
+ relOpts[j]->type = RELOPT_TYPE_TERNARY;
+ relOpts[j]->namelen = strlen(relOpts[j]->name);
+ j++;
+ }
+
for (i = 0; intRelOpts[i].gen.name; i++)
{
relOpts[j] = &intRelOpts[i].gen;
@@ -800,6 +821,9 @@ allocate_reloption(bits32 kinds, int type, const char *name, const char *desc,
case RELOPT_TYPE_BOOL:
size = sizeof(relopt_bool);
break;
+ case RELOPT_TYPE_TERNARY:
+ size = sizeof(relopt_ternary);
+ break;
case RELOPT_TYPE_INT:
size = sizeof(relopt_int);
break;
@@ -883,6 +907,54 @@ add_local_bool_reloption(local_relopts *relopts, const char *name,
add_local_reloption(relopts, (relopt_gen *) newoption, offset);
}
+/*
+ * init_ternary_reloption
+ * Allocate and initialize a new ternary reloption
+ */
+static relopt_ternary *
+init_ternary_reloption(bits32 kinds, const char *name, const char *desc,
+ ternary default_val, LOCKMODE lockmode)
+{
+ relopt_ternary *newoption;
+
+ newoption = (relopt_ternary *) allocate_reloption(kinds,
+ RELOPT_TYPE_TERNARY, name, desc, lockmode);
+ newoption->default_val = default_val;
+
+ return newoption;
+}
+
+/*
+ * add_ternary_reloption
+ * Add a new ternary reloption
+ */
+void
+add_ternary_reloption(bits32 kinds, const char *name, const char *desc,
+ ternary default_val, LOCKMODE lockmode)
+{
+ relopt_ternary *newoption = init_ternary_reloption(kinds, name, desc,
+ default_val, lockmode);
+
+ add_reloption((relopt_gen *) newoption);
+}
+
+/*
+ * add_local_ternary_reloption
+ * Add a new ternary local reloption
+ *
+ * 'offset' is offset of ternary-typed field.
+ */
+void
+add_local_ternary_reloption(local_relopts *relopts, const char *name,
+ const char *desc, ternary default_val,
+ int offset)
+{
+ relopt_ternary *newoption = init_ternary_reloption(RELOPT_KIND_LOCAL,
+ name, desc,
+ default_val, 0);
+
+ add_local_reloption(relopts, (relopt_gen *) newoption, offset);
+}
/*
* init_real_reloption
@@ -1617,6 +1689,18 @@ parse_one_reloption(relopt_value *option, char *text_str, int text_len,
option->gen->name, value)));
}
break;
+ case RELOPT_TYPE_TERNARY:
+ {
+ bool b;
+ parsed = parse_bool(value, &b);
+ option->values.ternary_val = b ? TERNARY_TRUE : TERNARY_FALSE;
+ if (validate && !parsed)
+ ereport(ERROR,
+ (errcode(ERRCODE_INVALID_PARAMETER_VALUE),
+ errmsg("invalid value for ternary option \"%s\": %s",
+ option->gen->name, value)));
+ }
+ break;
case RELOPT_TYPE_INT:
{
relopt_int *optint = (relopt_int *) option->gen;
@@ -1780,17 +1864,6 @@ fillRelOptions(void *rdopts, Size basesize,
char *itempos = ((char *) rdopts) + elems[j].offset;
char *string_val;
- /*
- * If isset_offset is provided, store whether the reloption is
- * set there.
- */
- if (elems[j].isset_offset > 0)
- {
- char *setpos = ((char *) rdopts) + elems[j].isset_offset;
-
- *(bool *) setpos = options[i].isset;
- }
-
switch (options[i].gen->type)
{
case RELOPT_TYPE_BOOL:
@@ -1798,6 +1871,11 @@ fillRelOptions(void *rdopts, Size basesize,
options[i].values.bool_val :
((relopt_bool *) options[i].gen)->default_val;
break;
+ case RELOPT_TYPE_TERNARY:
+ *(ternary *) itempos = options[i].isset ?
+ options[i].values.ternary_val :
+ ((relopt_ternary *) options[i].gen)->default_val;
+ break;
case RELOPT_TYPE_INT:
*(int *) itempos = options[i].isset ?
options[i].values.int_val :
@@ -1912,8 +1990,8 @@ default_reloptions(Datum reloptions, bool validate, relopt_kind kind)
offsetof(StdRdOptions, parallel_workers)},
{"vacuum_index_cleanup", RELOPT_TYPE_ENUM,
offsetof(StdRdOptions, vacuum_index_cleanup)},
- {"vacuum_truncate", RELOPT_TYPE_BOOL,
- offsetof(StdRdOptions, vacuum_truncate), offsetof(StdRdOptions, vacuum_truncate_set)},
+ {"vacuum_truncate", RELOPT_TYPE_TERNARY,
+ offsetof(StdRdOptions, vacuum_truncate)},
{"vacuum_max_eager_freeze_failure_rate", RELOPT_TYPE_REAL,
offsetof(StdRdOptions, vacuum_max_eager_freeze_failure_rate)}
};
@@ -1993,7 +2071,6 @@ build_local_reloptions(local_relopts *relopts, Datum options, bool validate)
elems[i].optname = opt->option->name;
elems[i].opttype = opt->option->type;
elems[i].offset = opt->offset;
- elems[i].isset_offset = 0; /* not supported for local relopts yet */
i++;
}
diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c
index 733ef40ae7..977babff54 100644
--- a/src/backend/commands/vacuum.c
+++ b/src/backend/commands/vacuum.c
@@ -2221,9 +2221,9 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams params,
{
StdRdOptions *opts = (StdRdOptions *) rel->rd_options;
- if (opts && opts->vacuum_truncate_set)
+ if (opts && opts->vacuum_truncate != TERNARY_UNSET)
{
- if (opts->vacuum_truncate)
+ if (opts->vacuum_truncate == TERNARY_TRUE)
params.truncate = VACOPTVALUE_ENABLED;
else
params.truncate = VACOPTVALUE_DISABLED;
diff --git a/src/include/access/reloptions.h b/src/include/access/reloptions.h
index a604a4702c..a436697658 100644
--- a/src/include/access/reloptions.h
+++ b/src/include/access/reloptions.h
@@ -29,6 +29,7 @@
typedef enum relopt_type
{
RELOPT_TYPE_BOOL,
+ RELOPT_TYPE_TERNARY, /* on, off, unset */
RELOPT_TYPE_INT,
RELOPT_TYPE_REAL,
RELOPT_TYPE_ENUM,
@@ -80,6 +81,7 @@ typedef struct relopt_value
union
{
bool bool_val;
+ ternary ternary_val;
int int_val;
double real_val;
int enum_val;
@@ -94,6 +96,12 @@ typedef struct relopt_bool
bool default_val;
} relopt_bool;
+typedef struct relopt_rernary
+{
+ relopt_gen gen;
+ int default_val;
+} relopt_ternary;
+
typedef struct relopt_int
{
relopt_gen gen;
@@ -152,19 +160,6 @@ typedef struct
const char *optname; /* option's name */
relopt_type opttype; /* option's datatype */
int offset; /* offset of field in result struct */
-
- /*
- * isset_offset is an optional offset of a field in the result struct that
- * stores whether the option is explicitly set for the relation or if it
- * just picked up the default value. In most cases, this can be
- * accomplished by giving the reloption a special out-of-range default
- * value (e.g., some integer reloptions use -2), but this isn't always
- * possible. For example, a Boolean reloption cannot be given an
- * out-of-range default, so we need another way to discover the source of
- * its value. This offset is only used if given a value greater than
- * zero.
- */
- int isset_offset;
} relopt_parse_elt;
/* Local reloption definition */
@@ -195,6 +190,8 @@ typedef struct local_relopts
extern relopt_kind add_reloption_kind(void);
extern void add_bool_reloption(bits32 kinds, const char *name, const char *desc,
bool default_val, LOCKMODE lockmode);
+extern void add_ternary_reloption(bits32 kinds, const char *name,
+ const char *desc, int default_val, LOCKMODE lockmode);
extern void add_int_reloption(bits32 kinds, const char *name, const char *desc,
int default_val, int min_val, int max_val,
LOCKMODE lockmode);
@@ -214,6 +211,9 @@ extern void register_reloptions_validator(local_relopts *relopts,
extern void add_local_bool_reloption(local_relopts *relopts, const char *name,
const char *desc, bool default_val,
int offset);
+extern void add_local_ternary_reloption(local_relopts *relopts,
+ const char *name, const char *desc,
+ ternary default_val, int offset);
extern void add_local_int_reloption(local_relopts *relopts, const char *name,
const char *desc, int default_val,
int min_val, int max_val, int offset);
diff --git a/src/include/c.h b/src/include/c.h
index 39022f8a9d..9c59ccccf8 100644
--- a/src/include/c.h
+++ b/src/include/c.h
@@ -488,6 +488,22 @@ typedef void (*pg_funcptr_t) (void);
#include <stdbool.h>
+/*
+ * ternary
+ * Boolean value with an extrea "unset" option
+ *
+ * Ternary data type is used in relation options that can be "true", "false" or
+ * "unset". Since relation options are used deep inside the PostgreSQL code,
+ * this type is declared globally.
+*/
+
+typedef enum ternary
+{
+ TERNARY_FALSE = 0,
+ TERNARY_TRUE = 1,
+ TERNARY_UNSET = -1
+} ternary;
+
/* ----------------------------------------------------------------
* Section 3: standard system types
diff --git a/src/include/utils/rel.h b/src/include/utils/rel.h
index b552359915..7346d618f9 100644
--- a/src/include/utils/rel.h
+++ b/src/include/utils/rel.h
@@ -346,8 +346,7 @@ typedef struct StdRdOptions
bool user_catalog_table; /* use as an additional catalog relation */
int parallel_workers; /* max number of parallel workers */
StdRdOptIndexCleanup vacuum_index_cleanup; /* controls index vacuuming */
- bool vacuum_truncate; /* enables vacuum to truncate a relation */
- bool vacuum_truncate_set; /* whether vacuum_truncate is set */
+ ternary vacuum_truncate; /* enables vacuum to truncate a relation */
/*
* Fraction of pages in a relation that vacuum can eagerly scan and fail
--
2.39.2
--nextPart5129932.5fSG56mABF
Content-Disposition: attachment;
filename="v2-0003-Add-alias-to-be-used-as-unset-state.patch"
Content-Transfer-Encoding: 7Bit
Content-Type: text/x-patch; charset="unicode-2-0-utf-8";
name="v2-0003-Add-alias-to-be-used-as-unset-state.patch"
^ permalink raw reply [nested|flat] 868+ messages in thread
* [PATCH v2a 2/4] Introduce ternary reloptions
@ 2025-09-04 14:41 Nikolay Shaplov <[email protected]>
0 siblings, 0 replies; 868+ messages in thread
From: Nikolay Shaplov @ 2025-09-04 14:41 UTC (permalink / raw)
Introduce ternary reloption as a replacement for current `vacuum_truncate`
implementation. Remove `vacuum_truncate_set` additional flag and using
`TERNARY_UNSET` value instead.
---
src/backend/access/common/reloptions.c | 129 ++++++++++++++++++++-----
src/backend/commands/vacuum.c | 4 +-
src/include/access/reloptions.h | 26 ++---
src/include/c.h | 16 +++
src/include/utils/rel.h | 3 +-
5 files changed, 135 insertions(+), 43 deletions(-)
diff --git a/src/backend/access/common/reloptions.c b/src/backend/access/common/reloptions.c
index 0af3fea68f..f543f11001 100644
--- a/src/backend/access/common/reloptions.c
+++ b/src/backend/access/common/reloptions.c
@@ -40,9 +40,9 @@
*
* To add an option:
*
- * (i) decide on a type (bool, integer, real, enum, string), name, default
- * value, upper and lower bounds (if applicable); for strings, consider a
- * validation routine.
+ * (i) decide on a type (bool, ternary, integer, real, enum, string), name,
+ * default value, upper and lower bounds (if applicable); for strings,
+ * consider a validation routine.
* (ii) add a record below (or use add_<type>_reloption).
* (iii) add it to the appropriate options struct (perhaps StdRdOptions)
* (iv) add it to the appropriate handling routine (perhaps
@@ -147,15 +147,6 @@ static relopt_bool boolRelOpts[] =
},
false
},
- {
- {
- "vacuum_truncate",
- "Enables vacuum to truncate empty pages at the end of this table",
- RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
- ShareUpdateExclusiveLock
- },
- true
- },
{
{
"deduplicate_items",
@@ -170,6 +161,21 @@ static relopt_bool boolRelOpts[] =
{{NULL}}
};
+static relopt_ternary ternaryRelOpts[] =
+{
+ {
+ {
+ "vacuum_truncate",
+ "Enables vacuum to truncate empty pages at the end of this table",
+ RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
+ ShareUpdateExclusiveLock
+ },
+ TERNARY_UNSET
+ },
+ /* list terminator */
+ {{NULL}}
+};
+
static relopt_int intRelOpts[] =
{
{
@@ -600,6 +606,13 @@ initialize_reloptions(void)
boolRelOpts[i].gen.lockmode));
j++;
}
+ for (i = 0; ternaryRelOpts[i].gen.name; i++)
+ {
+ Assert(DoLockModesConflict(ternaryRelOpts[i].gen.lockmode,
+ ternaryRelOpts[i].gen.lockmode));
+ j++;
+ }
+
for (i = 0; intRelOpts[i].gen.name; i++)
{
Assert(DoLockModesConflict(intRelOpts[i].gen.lockmode,
@@ -640,6 +653,14 @@ initialize_reloptions(void)
j++;
}
+ for (i = 0; ternaryRelOpts[i].gen.name; i++)
+ {
+ relOpts[j] = &ternaryRelOpts[i].gen;
+ relOpts[j]->type = RELOPT_TYPE_TERNARY;
+ relOpts[j]->namelen = strlen(relOpts[j]->name);
+ j++;
+ }
+
for (i = 0; intRelOpts[i].gen.name; i++)
{
relOpts[j] = &intRelOpts[i].gen;
@@ -800,6 +821,9 @@ allocate_reloption(bits32 kinds, int type, const char *name, const char *desc,
case RELOPT_TYPE_BOOL:
size = sizeof(relopt_bool);
break;
+ case RELOPT_TYPE_TERNARY:
+ size = sizeof(relopt_ternary);
+ break;
case RELOPT_TYPE_INT:
size = sizeof(relopt_int);
break;
@@ -883,6 +907,54 @@ add_local_bool_reloption(local_relopts *relopts, const char *name,
add_local_reloption(relopts, (relopt_gen *) newoption, offset);
}
+/*
+ * init_ternary_reloption
+ * Allocate and initialize a new ternary reloption
+ */
+static relopt_ternary *
+init_ternary_reloption(bits32 kinds, const char *name, const char *desc,
+ ternary default_val, LOCKMODE lockmode)
+{
+ relopt_ternary *newoption;
+
+ newoption = (relopt_ternary *) allocate_reloption(kinds,
+ RELOPT_TYPE_TERNARY, name, desc, lockmode);
+ newoption->default_val = default_val;
+
+ return newoption;
+}
+
+/*
+ * add_ternary_reloption
+ * Add a new ternary reloption
+ */
+void
+add_ternary_reloption(bits32 kinds, const char *name, const char *desc,
+ ternary default_val, LOCKMODE lockmode)
+{
+ relopt_ternary *newoption = init_ternary_reloption(kinds, name, desc,
+ default_val, lockmode);
+
+ add_reloption((relopt_gen *) newoption);
+}
+
+/*
+ * add_local_ternary_reloption
+ * Add a new ternary local reloption
+ *
+ * 'offset' is offset of ternary-typed field.
+ */
+void
+add_local_ternary_reloption(local_relopts *relopts, const char *name,
+ const char *desc, ternary default_val,
+ int offset)
+{
+ relopt_ternary *newoption = init_ternary_reloption(RELOPT_KIND_LOCAL,
+ name, desc,
+ default_val, 0);
+
+ add_local_reloption(relopts, (relopt_gen *) newoption, offset);
+}
/*
* init_real_reloption
@@ -1617,6 +1689,18 @@ parse_one_reloption(relopt_value *option, char *text_str, int text_len,
option->gen->name, value)));
}
break;
+ case RELOPT_TYPE_TERNARY:
+ {
+ bool b;
+ parsed = parse_bool(value, &b);
+ option->values.ternary_val = b ? TERNARY_TRUE : TERNARY_FALSE;
+ if (validate && !parsed)
+ ereport(ERROR,
+ (errcode(ERRCODE_INVALID_PARAMETER_VALUE),
+ errmsg("invalid value for ternary option \"%s\": %s",
+ option->gen->name, value)));
+ }
+ break;
case RELOPT_TYPE_INT:
{
relopt_int *optint = (relopt_int *) option->gen;
@@ -1780,17 +1864,6 @@ fillRelOptions(void *rdopts, Size basesize,
char *itempos = ((char *) rdopts) + elems[j].offset;
char *string_val;
- /*
- * If isset_offset is provided, store whether the reloption is
- * set there.
- */
- if (elems[j].isset_offset > 0)
- {
- char *setpos = ((char *) rdopts) + elems[j].isset_offset;
-
- *(bool *) setpos = options[i].isset;
- }
-
switch (options[i].gen->type)
{
case RELOPT_TYPE_BOOL:
@@ -1798,6 +1871,11 @@ fillRelOptions(void *rdopts, Size basesize,
options[i].values.bool_val :
((relopt_bool *) options[i].gen)->default_val;
break;
+ case RELOPT_TYPE_TERNARY:
+ *(ternary *) itempos = options[i].isset ?
+ options[i].values.ternary_val :
+ ((relopt_ternary *) options[i].gen)->default_val;
+ break;
case RELOPT_TYPE_INT:
*(int *) itempos = options[i].isset ?
options[i].values.int_val :
@@ -1912,8 +1990,8 @@ default_reloptions(Datum reloptions, bool validate, relopt_kind kind)
offsetof(StdRdOptions, parallel_workers)},
{"vacuum_index_cleanup", RELOPT_TYPE_ENUM,
offsetof(StdRdOptions, vacuum_index_cleanup)},
- {"vacuum_truncate", RELOPT_TYPE_BOOL,
- offsetof(StdRdOptions, vacuum_truncate), offsetof(StdRdOptions, vacuum_truncate_set)},
+ {"vacuum_truncate", RELOPT_TYPE_TERNARY,
+ offsetof(StdRdOptions, vacuum_truncate)},
{"vacuum_max_eager_freeze_failure_rate", RELOPT_TYPE_REAL,
offsetof(StdRdOptions, vacuum_max_eager_freeze_failure_rate)}
};
@@ -1993,7 +2071,6 @@ build_local_reloptions(local_relopts *relopts, Datum options, bool validate)
elems[i].optname = opt->option->name;
elems[i].opttype = opt->option->type;
elems[i].offset = opt->offset;
- elems[i].isset_offset = 0; /* not supported for local relopts yet */
i++;
}
diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c
index 733ef40ae7..977babff54 100644
--- a/src/backend/commands/vacuum.c
+++ b/src/backend/commands/vacuum.c
@@ -2221,9 +2221,9 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams params,
{
StdRdOptions *opts = (StdRdOptions *) rel->rd_options;
- if (opts && opts->vacuum_truncate_set)
+ if (opts && opts->vacuum_truncate != TERNARY_UNSET)
{
- if (opts->vacuum_truncate)
+ if (opts->vacuum_truncate == TERNARY_TRUE)
params.truncate = VACOPTVALUE_ENABLED;
else
params.truncate = VACOPTVALUE_DISABLED;
diff --git a/src/include/access/reloptions.h b/src/include/access/reloptions.h
index a604a4702c..a436697658 100644
--- a/src/include/access/reloptions.h
+++ b/src/include/access/reloptions.h
@@ -29,6 +29,7 @@
typedef enum relopt_type
{
RELOPT_TYPE_BOOL,
+ RELOPT_TYPE_TERNARY, /* on, off, unset */
RELOPT_TYPE_INT,
RELOPT_TYPE_REAL,
RELOPT_TYPE_ENUM,
@@ -80,6 +81,7 @@ typedef struct relopt_value
union
{
bool bool_val;
+ ternary ternary_val;
int int_val;
double real_val;
int enum_val;
@@ -94,6 +96,12 @@ typedef struct relopt_bool
bool default_val;
} relopt_bool;
+typedef struct relopt_rernary
+{
+ relopt_gen gen;
+ int default_val;
+} relopt_ternary;
+
typedef struct relopt_int
{
relopt_gen gen;
@@ -152,19 +160,6 @@ typedef struct
const char *optname; /* option's name */
relopt_type opttype; /* option's datatype */
int offset; /* offset of field in result struct */
-
- /*
- * isset_offset is an optional offset of a field in the result struct that
- * stores whether the option is explicitly set for the relation or if it
- * just picked up the default value. In most cases, this can be
- * accomplished by giving the reloption a special out-of-range default
- * value (e.g., some integer reloptions use -2), but this isn't always
- * possible. For example, a Boolean reloption cannot be given an
- * out-of-range default, so we need another way to discover the source of
- * its value. This offset is only used if given a value greater than
- * zero.
- */
- int isset_offset;
} relopt_parse_elt;
/* Local reloption definition */
@@ -195,6 +190,8 @@ typedef struct local_relopts
extern relopt_kind add_reloption_kind(void);
extern void add_bool_reloption(bits32 kinds, const char *name, const char *desc,
bool default_val, LOCKMODE lockmode);
+extern void add_ternary_reloption(bits32 kinds, const char *name,
+ const char *desc, int default_val, LOCKMODE lockmode);
extern void add_int_reloption(bits32 kinds, const char *name, const char *desc,
int default_val, int min_val, int max_val,
LOCKMODE lockmode);
@@ -214,6 +211,9 @@ extern void register_reloptions_validator(local_relopts *relopts,
extern void add_local_bool_reloption(local_relopts *relopts, const char *name,
const char *desc, bool default_val,
int offset);
+extern void add_local_ternary_reloption(local_relopts *relopts,
+ const char *name, const char *desc,
+ ternary default_val, int offset);
extern void add_local_int_reloption(local_relopts *relopts, const char *name,
const char *desc, int default_val,
int min_val, int max_val, int offset);
diff --git a/src/include/c.h b/src/include/c.h
index 39022f8a9d..9c59ccccf8 100644
--- a/src/include/c.h
+++ b/src/include/c.h
@@ -488,6 +488,22 @@ typedef void (*pg_funcptr_t) (void);
#include <stdbool.h>
+/*
+ * ternary
+ * Boolean value with an extrea "unset" option
+ *
+ * Ternary data type is used in relation options that can be "true", "false" or
+ * "unset". Since relation options are used deep inside the PostgreSQL code,
+ * this type is declared globally.
+*/
+
+typedef enum ternary
+{
+ TERNARY_FALSE = 0,
+ TERNARY_TRUE = 1,
+ TERNARY_UNSET = -1
+} ternary;
+
/* ----------------------------------------------------------------
* Section 3: standard system types
diff --git a/src/include/utils/rel.h b/src/include/utils/rel.h
index b552359915..7346d618f9 100644
--- a/src/include/utils/rel.h
+++ b/src/include/utils/rel.h
@@ -346,8 +346,7 @@ typedef struct StdRdOptions
bool user_catalog_table; /* use as an additional catalog relation */
int parallel_workers; /* max number of parallel workers */
StdRdOptIndexCleanup vacuum_index_cleanup; /* controls index vacuuming */
- bool vacuum_truncate; /* enables vacuum to truncate a relation */
- bool vacuum_truncate_set; /* whether vacuum_truncate is set */
+ ternary vacuum_truncate; /* enables vacuum to truncate a relation */
/*
* Fraction of pages in a relation that vacuum can eagerly scan and fail
--
2.39.2
--nextPart4155600.3ZeAukHxDK
Content-Disposition: attachment;
filename="v2a-0003-Add-alias-to-be-used-as-unset-state.patch"
Content-Transfer-Encoding: 7Bit
Content-Type: text/x-patch; charset="unicode-2-0-utf-8";
name="v2a-0003-Add-alias-to-be-used-as-unset-state.patch"
^ permalink raw reply [nested|flat] 868+ messages in thread
* [PATCH v2a 2/4] Introduce ternary reloptions
@ 2025-09-04 14:41 Nikolay Shaplov <[email protected]>
0 siblings, 0 replies; 868+ messages in thread
From: Nikolay Shaplov @ 2025-09-04 14:41 UTC (permalink / raw)
Introduce ternary reloption as a replacement for current `vacuum_truncate`
implementation. Remove `vacuum_truncate_set` additional flag and using
`TERNARY_UNSET` value instead.
---
src/backend/access/common/reloptions.c | 129 ++++++++++++++++++++-----
src/backend/commands/vacuum.c | 4 +-
src/include/access/reloptions.h | 26 ++---
src/include/c.h | 16 +++
src/include/utils/rel.h | 3 +-
5 files changed, 135 insertions(+), 43 deletions(-)
diff --git a/src/backend/access/common/reloptions.c b/src/backend/access/common/reloptions.c
index 0af3fea68f..f543f11001 100644
--- a/src/backend/access/common/reloptions.c
+++ b/src/backend/access/common/reloptions.c
@@ -40,9 +40,9 @@
*
* To add an option:
*
- * (i) decide on a type (bool, integer, real, enum, string), name, default
- * value, upper and lower bounds (if applicable); for strings, consider a
- * validation routine.
+ * (i) decide on a type (bool, ternary, integer, real, enum, string), name,
+ * default value, upper and lower bounds (if applicable); for strings,
+ * consider a validation routine.
* (ii) add a record below (or use add_<type>_reloption).
* (iii) add it to the appropriate options struct (perhaps StdRdOptions)
* (iv) add it to the appropriate handling routine (perhaps
@@ -147,15 +147,6 @@ static relopt_bool boolRelOpts[] =
},
false
},
- {
- {
- "vacuum_truncate",
- "Enables vacuum to truncate empty pages at the end of this table",
- RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
- ShareUpdateExclusiveLock
- },
- true
- },
{
{
"deduplicate_items",
@@ -170,6 +161,21 @@ static relopt_bool boolRelOpts[] =
{{NULL}}
};
+static relopt_ternary ternaryRelOpts[] =
+{
+ {
+ {
+ "vacuum_truncate",
+ "Enables vacuum to truncate empty pages at the end of this table",
+ RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
+ ShareUpdateExclusiveLock
+ },
+ TERNARY_UNSET
+ },
+ /* list terminator */
+ {{NULL}}
+};
+
static relopt_int intRelOpts[] =
{
{
@@ -600,6 +606,13 @@ initialize_reloptions(void)
boolRelOpts[i].gen.lockmode));
j++;
}
+ for (i = 0; ternaryRelOpts[i].gen.name; i++)
+ {
+ Assert(DoLockModesConflict(ternaryRelOpts[i].gen.lockmode,
+ ternaryRelOpts[i].gen.lockmode));
+ j++;
+ }
+
for (i = 0; intRelOpts[i].gen.name; i++)
{
Assert(DoLockModesConflict(intRelOpts[i].gen.lockmode,
@@ -640,6 +653,14 @@ initialize_reloptions(void)
j++;
}
+ for (i = 0; ternaryRelOpts[i].gen.name; i++)
+ {
+ relOpts[j] = &ternaryRelOpts[i].gen;
+ relOpts[j]->type = RELOPT_TYPE_TERNARY;
+ relOpts[j]->namelen = strlen(relOpts[j]->name);
+ j++;
+ }
+
for (i = 0; intRelOpts[i].gen.name; i++)
{
relOpts[j] = &intRelOpts[i].gen;
@@ -800,6 +821,9 @@ allocate_reloption(bits32 kinds, int type, const char *name, const char *desc,
case RELOPT_TYPE_BOOL:
size = sizeof(relopt_bool);
break;
+ case RELOPT_TYPE_TERNARY:
+ size = sizeof(relopt_ternary);
+ break;
case RELOPT_TYPE_INT:
size = sizeof(relopt_int);
break;
@@ -883,6 +907,54 @@ add_local_bool_reloption(local_relopts *relopts, const char *name,
add_local_reloption(relopts, (relopt_gen *) newoption, offset);
}
+/*
+ * init_ternary_reloption
+ * Allocate and initialize a new ternary reloption
+ */
+static relopt_ternary *
+init_ternary_reloption(bits32 kinds, const char *name, const char *desc,
+ ternary default_val, LOCKMODE lockmode)
+{
+ relopt_ternary *newoption;
+
+ newoption = (relopt_ternary *) allocate_reloption(kinds,
+ RELOPT_TYPE_TERNARY, name, desc, lockmode);
+ newoption->default_val = default_val;
+
+ return newoption;
+}
+
+/*
+ * add_ternary_reloption
+ * Add a new ternary reloption
+ */
+void
+add_ternary_reloption(bits32 kinds, const char *name, const char *desc,
+ ternary default_val, LOCKMODE lockmode)
+{
+ relopt_ternary *newoption = init_ternary_reloption(kinds, name, desc,
+ default_val, lockmode);
+
+ add_reloption((relopt_gen *) newoption);
+}
+
+/*
+ * add_local_ternary_reloption
+ * Add a new ternary local reloption
+ *
+ * 'offset' is offset of ternary-typed field.
+ */
+void
+add_local_ternary_reloption(local_relopts *relopts, const char *name,
+ const char *desc, ternary default_val,
+ int offset)
+{
+ relopt_ternary *newoption = init_ternary_reloption(RELOPT_KIND_LOCAL,
+ name, desc,
+ default_val, 0);
+
+ add_local_reloption(relopts, (relopt_gen *) newoption, offset);
+}
/*
* init_real_reloption
@@ -1617,6 +1689,18 @@ parse_one_reloption(relopt_value *option, char *text_str, int text_len,
option->gen->name, value)));
}
break;
+ case RELOPT_TYPE_TERNARY:
+ {
+ bool b;
+ parsed = parse_bool(value, &b);
+ option->values.ternary_val = b ? TERNARY_TRUE : TERNARY_FALSE;
+ if (validate && !parsed)
+ ereport(ERROR,
+ (errcode(ERRCODE_INVALID_PARAMETER_VALUE),
+ errmsg("invalid value for ternary option \"%s\": %s",
+ option->gen->name, value)));
+ }
+ break;
case RELOPT_TYPE_INT:
{
relopt_int *optint = (relopt_int *) option->gen;
@@ -1780,17 +1864,6 @@ fillRelOptions(void *rdopts, Size basesize,
char *itempos = ((char *) rdopts) + elems[j].offset;
char *string_val;
- /*
- * If isset_offset is provided, store whether the reloption is
- * set there.
- */
- if (elems[j].isset_offset > 0)
- {
- char *setpos = ((char *) rdopts) + elems[j].isset_offset;
-
- *(bool *) setpos = options[i].isset;
- }
-
switch (options[i].gen->type)
{
case RELOPT_TYPE_BOOL:
@@ -1798,6 +1871,11 @@ fillRelOptions(void *rdopts, Size basesize,
options[i].values.bool_val :
((relopt_bool *) options[i].gen)->default_val;
break;
+ case RELOPT_TYPE_TERNARY:
+ *(ternary *) itempos = options[i].isset ?
+ options[i].values.ternary_val :
+ ((relopt_ternary *) options[i].gen)->default_val;
+ break;
case RELOPT_TYPE_INT:
*(int *) itempos = options[i].isset ?
options[i].values.int_val :
@@ -1912,8 +1990,8 @@ default_reloptions(Datum reloptions, bool validate, relopt_kind kind)
offsetof(StdRdOptions, parallel_workers)},
{"vacuum_index_cleanup", RELOPT_TYPE_ENUM,
offsetof(StdRdOptions, vacuum_index_cleanup)},
- {"vacuum_truncate", RELOPT_TYPE_BOOL,
- offsetof(StdRdOptions, vacuum_truncate), offsetof(StdRdOptions, vacuum_truncate_set)},
+ {"vacuum_truncate", RELOPT_TYPE_TERNARY,
+ offsetof(StdRdOptions, vacuum_truncate)},
{"vacuum_max_eager_freeze_failure_rate", RELOPT_TYPE_REAL,
offsetof(StdRdOptions, vacuum_max_eager_freeze_failure_rate)}
};
@@ -1993,7 +2071,6 @@ build_local_reloptions(local_relopts *relopts, Datum options, bool validate)
elems[i].optname = opt->option->name;
elems[i].opttype = opt->option->type;
elems[i].offset = opt->offset;
- elems[i].isset_offset = 0; /* not supported for local relopts yet */
i++;
}
diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c
index 733ef40ae7..977babff54 100644
--- a/src/backend/commands/vacuum.c
+++ b/src/backend/commands/vacuum.c
@@ -2221,9 +2221,9 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams params,
{
StdRdOptions *opts = (StdRdOptions *) rel->rd_options;
- if (opts && opts->vacuum_truncate_set)
+ if (opts && opts->vacuum_truncate != TERNARY_UNSET)
{
- if (opts->vacuum_truncate)
+ if (opts->vacuum_truncate == TERNARY_TRUE)
params.truncate = VACOPTVALUE_ENABLED;
else
params.truncate = VACOPTVALUE_DISABLED;
diff --git a/src/include/access/reloptions.h b/src/include/access/reloptions.h
index a604a4702c..a436697658 100644
--- a/src/include/access/reloptions.h
+++ b/src/include/access/reloptions.h
@@ -29,6 +29,7 @@
typedef enum relopt_type
{
RELOPT_TYPE_BOOL,
+ RELOPT_TYPE_TERNARY, /* on, off, unset */
RELOPT_TYPE_INT,
RELOPT_TYPE_REAL,
RELOPT_TYPE_ENUM,
@@ -80,6 +81,7 @@ typedef struct relopt_value
union
{
bool bool_val;
+ ternary ternary_val;
int int_val;
double real_val;
int enum_val;
@@ -94,6 +96,12 @@ typedef struct relopt_bool
bool default_val;
} relopt_bool;
+typedef struct relopt_rernary
+{
+ relopt_gen gen;
+ int default_val;
+} relopt_ternary;
+
typedef struct relopt_int
{
relopt_gen gen;
@@ -152,19 +160,6 @@ typedef struct
const char *optname; /* option's name */
relopt_type opttype; /* option's datatype */
int offset; /* offset of field in result struct */
-
- /*
- * isset_offset is an optional offset of a field in the result struct that
- * stores whether the option is explicitly set for the relation or if it
- * just picked up the default value. In most cases, this can be
- * accomplished by giving the reloption a special out-of-range default
- * value (e.g., some integer reloptions use -2), but this isn't always
- * possible. For example, a Boolean reloption cannot be given an
- * out-of-range default, so we need another way to discover the source of
- * its value. This offset is only used if given a value greater than
- * zero.
- */
- int isset_offset;
} relopt_parse_elt;
/* Local reloption definition */
@@ -195,6 +190,8 @@ typedef struct local_relopts
extern relopt_kind add_reloption_kind(void);
extern void add_bool_reloption(bits32 kinds, const char *name, const char *desc,
bool default_val, LOCKMODE lockmode);
+extern void add_ternary_reloption(bits32 kinds, const char *name,
+ const char *desc, int default_val, LOCKMODE lockmode);
extern void add_int_reloption(bits32 kinds, const char *name, const char *desc,
int default_val, int min_val, int max_val,
LOCKMODE lockmode);
@@ -214,6 +211,9 @@ extern void register_reloptions_validator(local_relopts *relopts,
extern void add_local_bool_reloption(local_relopts *relopts, const char *name,
const char *desc, bool default_val,
int offset);
+extern void add_local_ternary_reloption(local_relopts *relopts,
+ const char *name, const char *desc,
+ ternary default_val, int offset);
extern void add_local_int_reloption(local_relopts *relopts, const char *name,
const char *desc, int default_val,
int min_val, int max_val, int offset);
diff --git a/src/include/c.h b/src/include/c.h
index 39022f8a9d..9c59ccccf8 100644
--- a/src/include/c.h
+++ b/src/include/c.h
@@ -488,6 +488,22 @@ typedef void (*pg_funcptr_t) (void);
#include <stdbool.h>
+/*
+ * ternary
+ * Boolean value with an extrea "unset" option
+ *
+ * Ternary data type is used in relation options that can be "true", "false" or
+ * "unset". Since relation options are used deep inside the PostgreSQL code,
+ * this type is declared globally.
+*/
+
+typedef enum ternary
+{
+ TERNARY_FALSE = 0,
+ TERNARY_TRUE = 1,
+ TERNARY_UNSET = -1
+} ternary;
+
/* ----------------------------------------------------------------
* Section 3: standard system types
diff --git a/src/include/utils/rel.h b/src/include/utils/rel.h
index b552359915..7346d618f9 100644
--- a/src/include/utils/rel.h
+++ b/src/include/utils/rel.h
@@ -346,8 +346,7 @@ typedef struct StdRdOptions
bool user_catalog_table; /* use as an additional catalog relation */
int parallel_workers; /* max number of parallel workers */
StdRdOptIndexCleanup vacuum_index_cleanup; /* controls index vacuuming */
- bool vacuum_truncate; /* enables vacuum to truncate a relation */
- bool vacuum_truncate_set; /* whether vacuum_truncate is set */
+ ternary vacuum_truncate; /* enables vacuum to truncate a relation */
/*
* Fraction of pages in a relation that vacuum can eagerly scan and fail
--
2.39.2
--nextPart4155600.3ZeAukHxDK
Content-Disposition: attachment;
filename="v2a-0003-Add-alias-to-be-used-as-unset-state.patch"
Content-Transfer-Encoding: 7Bit
Content-Type: text/x-patch; charset="unicode-2-0-utf-8";
name="v2a-0003-Add-alias-to-be-used-as-unset-state.patch"
^ permalink raw reply [nested|flat] 868+ messages in thread
* [PATCH v2a 2/4] Introduce ternary reloptions
@ 2025-09-04 14:41 Nikolay Shaplov <[email protected]>
0 siblings, 0 replies; 868+ messages in thread
From: Nikolay Shaplov @ 2025-09-04 14:41 UTC (permalink / raw)
Introduce ternary reloption as a replacement for current `vacuum_truncate`
implementation. Remove `vacuum_truncate_set` additional flag and using
`TERNARY_UNSET` value instead.
---
src/backend/access/common/reloptions.c | 129 ++++++++++++++++++++-----
src/backend/commands/vacuum.c | 4 +-
src/include/access/reloptions.h | 26 ++---
src/include/c.h | 16 +++
src/include/utils/rel.h | 3 +-
5 files changed, 135 insertions(+), 43 deletions(-)
diff --git a/src/backend/access/common/reloptions.c b/src/backend/access/common/reloptions.c
index 0af3fea68f..f543f11001 100644
--- a/src/backend/access/common/reloptions.c
+++ b/src/backend/access/common/reloptions.c
@@ -40,9 +40,9 @@
*
* To add an option:
*
- * (i) decide on a type (bool, integer, real, enum, string), name, default
- * value, upper and lower bounds (if applicable); for strings, consider a
- * validation routine.
+ * (i) decide on a type (bool, ternary, integer, real, enum, string), name,
+ * default value, upper and lower bounds (if applicable); for strings,
+ * consider a validation routine.
* (ii) add a record below (or use add_<type>_reloption).
* (iii) add it to the appropriate options struct (perhaps StdRdOptions)
* (iv) add it to the appropriate handling routine (perhaps
@@ -147,15 +147,6 @@ static relopt_bool boolRelOpts[] =
},
false
},
- {
- {
- "vacuum_truncate",
- "Enables vacuum to truncate empty pages at the end of this table",
- RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
- ShareUpdateExclusiveLock
- },
- true
- },
{
{
"deduplicate_items",
@@ -170,6 +161,21 @@ static relopt_bool boolRelOpts[] =
{{NULL}}
};
+static relopt_ternary ternaryRelOpts[] =
+{
+ {
+ {
+ "vacuum_truncate",
+ "Enables vacuum to truncate empty pages at the end of this table",
+ RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
+ ShareUpdateExclusiveLock
+ },
+ TERNARY_UNSET
+ },
+ /* list terminator */
+ {{NULL}}
+};
+
static relopt_int intRelOpts[] =
{
{
@@ -600,6 +606,13 @@ initialize_reloptions(void)
boolRelOpts[i].gen.lockmode));
j++;
}
+ for (i = 0; ternaryRelOpts[i].gen.name; i++)
+ {
+ Assert(DoLockModesConflict(ternaryRelOpts[i].gen.lockmode,
+ ternaryRelOpts[i].gen.lockmode));
+ j++;
+ }
+
for (i = 0; intRelOpts[i].gen.name; i++)
{
Assert(DoLockModesConflict(intRelOpts[i].gen.lockmode,
@@ -640,6 +653,14 @@ initialize_reloptions(void)
j++;
}
+ for (i = 0; ternaryRelOpts[i].gen.name; i++)
+ {
+ relOpts[j] = &ternaryRelOpts[i].gen;
+ relOpts[j]->type = RELOPT_TYPE_TERNARY;
+ relOpts[j]->namelen = strlen(relOpts[j]->name);
+ j++;
+ }
+
for (i = 0; intRelOpts[i].gen.name; i++)
{
relOpts[j] = &intRelOpts[i].gen;
@@ -800,6 +821,9 @@ allocate_reloption(bits32 kinds, int type, const char *name, const char *desc,
case RELOPT_TYPE_BOOL:
size = sizeof(relopt_bool);
break;
+ case RELOPT_TYPE_TERNARY:
+ size = sizeof(relopt_ternary);
+ break;
case RELOPT_TYPE_INT:
size = sizeof(relopt_int);
break;
@@ -883,6 +907,54 @@ add_local_bool_reloption(local_relopts *relopts, const char *name,
add_local_reloption(relopts, (relopt_gen *) newoption, offset);
}
+/*
+ * init_ternary_reloption
+ * Allocate and initialize a new ternary reloption
+ */
+static relopt_ternary *
+init_ternary_reloption(bits32 kinds, const char *name, const char *desc,
+ ternary default_val, LOCKMODE lockmode)
+{
+ relopt_ternary *newoption;
+
+ newoption = (relopt_ternary *) allocate_reloption(kinds,
+ RELOPT_TYPE_TERNARY, name, desc, lockmode);
+ newoption->default_val = default_val;
+
+ return newoption;
+}
+
+/*
+ * add_ternary_reloption
+ * Add a new ternary reloption
+ */
+void
+add_ternary_reloption(bits32 kinds, const char *name, const char *desc,
+ ternary default_val, LOCKMODE lockmode)
+{
+ relopt_ternary *newoption = init_ternary_reloption(kinds, name, desc,
+ default_val, lockmode);
+
+ add_reloption((relopt_gen *) newoption);
+}
+
+/*
+ * add_local_ternary_reloption
+ * Add a new ternary local reloption
+ *
+ * 'offset' is offset of ternary-typed field.
+ */
+void
+add_local_ternary_reloption(local_relopts *relopts, const char *name,
+ const char *desc, ternary default_val,
+ int offset)
+{
+ relopt_ternary *newoption = init_ternary_reloption(RELOPT_KIND_LOCAL,
+ name, desc,
+ default_val, 0);
+
+ add_local_reloption(relopts, (relopt_gen *) newoption, offset);
+}
/*
* init_real_reloption
@@ -1617,6 +1689,18 @@ parse_one_reloption(relopt_value *option, char *text_str, int text_len,
option->gen->name, value)));
}
break;
+ case RELOPT_TYPE_TERNARY:
+ {
+ bool b;
+ parsed = parse_bool(value, &b);
+ option->values.ternary_val = b ? TERNARY_TRUE : TERNARY_FALSE;
+ if (validate && !parsed)
+ ereport(ERROR,
+ (errcode(ERRCODE_INVALID_PARAMETER_VALUE),
+ errmsg("invalid value for ternary option \"%s\": %s",
+ option->gen->name, value)));
+ }
+ break;
case RELOPT_TYPE_INT:
{
relopt_int *optint = (relopt_int *) option->gen;
@@ -1780,17 +1864,6 @@ fillRelOptions(void *rdopts, Size basesize,
char *itempos = ((char *) rdopts) + elems[j].offset;
char *string_val;
- /*
- * If isset_offset is provided, store whether the reloption is
- * set there.
- */
- if (elems[j].isset_offset > 0)
- {
- char *setpos = ((char *) rdopts) + elems[j].isset_offset;
-
- *(bool *) setpos = options[i].isset;
- }
-
switch (options[i].gen->type)
{
case RELOPT_TYPE_BOOL:
@@ -1798,6 +1871,11 @@ fillRelOptions(void *rdopts, Size basesize,
options[i].values.bool_val :
((relopt_bool *) options[i].gen)->default_val;
break;
+ case RELOPT_TYPE_TERNARY:
+ *(ternary *) itempos = options[i].isset ?
+ options[i].values.ternary_val :
+ ((relopt_ternary *) options[i].gen)->default_val;
+ break;
case RELOPT_TYPE_INT:
*(int *) itempos = options[i].isset ?
options[i].values.int_val :
@@ -1912,8 +1990,8 @@ default_reloptions(Datum reloptions, bool validate, relopt_kind kind)
offsetof(StdRdOptions, parallel_workers)},
{"vacuum_index_cleanup", RELOPT_TYPE_ENUM,
offsetof(StdRdOptions, vacuum_index_cleanup)},
- {"vacuum_truncate", RELOPT_TYPE_BOOL,
- offsetof(StdRdOptions, vacuum_truncate), offsetof(StdRdOptions, vacuum_truncate_set)},
+ {"vacuum_truncate", RELOPT_TYPE_TERNARY,
+ offsetof(StdRdOptions, vacuum_truncate)},
{"vacuum_max_eager_freeze_failure_rate", RELOPT_TYPE_REAL,
offsetof(StdRdOptions, vacuum_max_eager_freeze_failure_rate)}
};
@@ -1993,7 +2071,6 @@ build_local_reloptions(local_relopts *relopts, Datum options, bool validate)
elems[i].optname = opt->option->name;
elems[i].opttype = opt->option->type;
elems[i].offset = opt->offset;
- elems[i].isset_offset = 0; /* not supported for local relopts yet */
i++;
}
diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c
index 733ef40ae7..977babff54 100644
--- a/src/backend/commands/vacuum.c
+++ b/src/backend/commands/vacuum.c
@@ -2221,9 +2221,9 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams params,
{
StdRdOptions *opts = (StdRdOptions *) rel->rd_options;
- if (opts && opts->vacuum_truncate_set)
+ if (opts && opts->vacuum_truncate != TERNARY_UNSET)
{
- if (opts->vacuum_truncate)
+ if (opts->vacuum_truncate == TERNARY_TRUE)
params.truncate = VACOPTVALUE_ENABLED;
else
params.truncate = VACOPTVALUE_DISABLED;
diff --git a/src/include/access/reloptions.h b/src/include/access/reloptions.h
index a604a4702c..a436697658 100644
--- a/src/include/access/reloptions.h
+++ b/src/include/access/reloptions.h
@@ -29,6 +29,7 @@
typedef enum relopt_type
{
RELOPT_TYPE_BOOL,
+ RELOPT_TYPE_TERNARY, /* on, off, unset */
RELOPT_TYPE_INT,
RELOPT_TYPE_REAL,
RELOPT_TYPE_ENUM,
@@ -80,6 +81,7 @@ typedef struct relopt_value
union
{
bool bool_val;
+ ternary ternary_val;
int int_val;
double real_val;
int enum_val;
@@ -94,6 +96,12 @@ typedef struct relopt_bool
bool default_val;
} relopt_bool;
+typedef struct relopt_rernary
+{
+ relopt_gen gen;
+ int default_val;
+} relopt_ternary;
+
typedef struct relopt_int
{
relopt_gen gen;
@@ -152,19 +160,6 @@ typedef struct
const char *optname; /* option's name */
relopt_type opttype; /* option's datatype */
int offset; /* offset of field in result struct */
-
- /*
- * isset_offset is an optional offset of a field in the result struct that
- * stores whether the option is explicitly set for the relation or if it
- * just picked up the default value. In most cases, this can be
- * accomplished by giving the reloption a special out-of-range default
- * value (e.g., some integer reloptions use -2), but this isn't always
- * possible. For example, a Boolean reloption cannot be given an
- * out-of-range default, so we need another way to discover the source of
- * its value. This offset is only used if given a value greater than
- * zero.
- */
- int isset_offset;
} relopt_parse_elt;
/* Local reloption definition */
@@ -195,6 +190,8 @@ typedef struct local_relopts
extern relopt_kind add_reloption_kind(void);
extern void add_bool_reloption(bits32 kinds, const char *name, const char *desc,
bool default_val, LOCKMODE lockmode);
+extern void add_ternary_reloption(bits32 kinds, const char *name,
+ const char *desc, int default_val, LOCKMODE lockmode);
extern void add_int_reloption(bits32 kinds, const char *name, const char *desc,
int default_val, int min_val, int max_val,
LOCKMODE lockmode);
@@ -214,6 +211,9 @@ extern void register_reloptions_validator(local_relopts *relopts,
extern void add_local_bool_reloption(local_relopts *relopts, const char *name,
const char *desc, bool default_val,
int offset);
+extern void add_local_ternary_reloption(local_relopts *relopts,
+ const char *name, const char *desc,
+ ternary default_val, int offset);
extern void add_local_int_reloption(local_relopts *relopts, const char *name,
const char *desc, int default_val,
int min_val, int max_val, int offset);
diff --git a/src/include/c.h b/src/include/c.h
index 39022f8a9d..9c59ccccf8 100644
--- a/src/include/c.h
+++ b/src/include/c.h
@@ -488,6 +488,22 @@ typedef void (*pg_funcptr_t) (void);
#include <stdbool.h>
+/*
+ * ternary
+ * Boolean value with an extrea "unset" option
+ *
+ * Ternary data type is used in relation options that can be "true", "false" or
+ * "unset". Since relation options are used deep inside the PostgreSQL code,
+ * this type is declared globally.
+*/
+
+typedef enum ternary
+{
+ TERNARY_FALSE = 0,
+ TERNARY_TRUE = 1,
+ TERNARY_UNSET = -1
+} ternary;
+
/* ----------------------------------------------------------------
* Section 3: standard system types
diff --git a/src/include/utils/rel.h b/src/include/utils/rel.h
index b552359915..7346d618f9 100644
--- a/src/include/utils/rel.h
+++ b/src/include/utils/rel.h
@@ -346,8 +346,7 @@ typedef struct StdRdOptions
bool user_catalog_table; /* use as an additional catalog relation */
int parallel_workers; /* max number of parallel workers */
StdRdOptIndexCleanup vacuum_index_cleanup; /* controls index vacuuming */
- bool vacuum_truncate; /* enables vacuum to truncate a relation */
- bool vacuum_truncate_set; /* whether vacuum_truncate is set */
+ ternary vacuum_truncate; /* enables vacuum to truncate a relation */
/*
* Fraction of pages in a relation that vacuum can eagerly scan and fail
--
2.39.2
--nextPart4155600.3ZeAukHxDK
Content-Disposition: attachment;
filename="v2a-0003-Add-alias-to-be-used-as-unset-state.patch"
Content-Transfer-Encoding: 7Bit
Content-Type: text/x-patch; charset="unicode-2-0-utf-8";
name="v2a-0003-Add-alias-to-be-used-as-unset-state.patch"
^ permalink raw reply [nested|flat] 868+ messages in thread
* [PATCH v2 2/4] Introduce ternary reloptions
@ 2025-09-04 14:41 Nikolay Shaplov <[email protected]>
0 siblings, 0 replies; 868+ messages in thread
From: Nikolay Shaplov @ 2025-09-04 14:41 UTC (permalink / raw)
Introduce ternary reloption as a replacement for current `vacuum_truncate`
implementation. Remove `vacuum_truncate_set` additional flag and using
`TERNARY_UNSET` value instead.
---
src/backend/access/common/reloptions.c | 129 ++++++++++++++++++++-----
src/backend/commands/vacuum.c | 4 +-
src/include/access/reloptions.h | 26 ++---
src/include/c.h | 16 +++
src/include/utils/rel.h | 3 +-
5 files changed, 135 insertions(+), 43 deletions(-)
diff --git a/src/backend/access/common/reloptions.c b/src/backend/access/common/reloptions.c
index 0af3fea68f..f543f11001 100644
--- a/src/backend/access/common/reloptions.c
+++ b/src/backend/access/common/reloptions.c
@@ -40,9 +40,9 @@
*
* To add an option:
*
- * (i) decide on a type (bool, integer, real, enum, string), name, default
- * value, upper and lower bounds (if applicable); for strings, consider a
- * validation routine.
+ * (i) decide on a type (bool, ternary, integer, real, enum, string), name,
+ * default value, upper and lower bounds (if applicable); for strings,
+ * consider a validation routine.
* (ii) add a record below (or use add_<type>_reloption).
* (iii) add it to the appropriate options struct (perhaps StdRdOptions)
* (iv) add it to the appropriate handling routine (perhaps
@@ -147,15 +147,6 @@ static relopt_bool boolRelOpts[] =
},
false
},
- {
- {
- "vacuum_truncate",
- "Enables vacuum to truncate empty pages at the end of this table",
- RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
- ShareUpdateExclusiveLock
- },
- true
- },
{
{
"deduplicate_items",
@@ -170,6 +161,21 @@ static relopt_bool boolRelOpts[] =
{{NULL}}
};
+static relopt_ternary ternaryRelOpts[] =
+{
+ {
+ {
+ "vacuum_truncate",
+ "Enables vacuum to truncate empty pages at the end of this table",
+ RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
+ ShareUpdateExclusiveLock
+ },
+ TERNARY_UNSET
+ },
+ /* list terminator */
+ {{NULL}}
+};
+
static relopt_int intRelOpts[] =
{
{
@@ -600,6 +606,13 @@ initialize_reloptions(void)
boolRelOpts[i].gen.lockmode));
j++;
}
+ for (i = 0; ternaryRelOpts[i].gen.name; i++)
+ {
+ Assert(DoLockModesConflict(ternaryRelOpts[i].gen.lockmode,
+ ternaryRelOpts[i].gen.lockmode));
+ j++;
+ }
+
for (i = 0; intRelOpts[i].gen.name; i++)
{
Assert(DoLockModesConflict(intRelOpts[i].gen.lockmode,
@@ -640,6 +653,14 @@ initialize_reloptions(void)
j++;
}
+ for (i = 0; ternaryRelOpts[i].gen.name; i++)
+ {
+ relOpts[j] = &ternaryRelOpts[i].gen;
+ relOpts[j]->type = RELOPT_TYPE_TERNARY;
+ relOpts[j]->namelen = strlen(relOpts[j]->name);
+ j++;
+ }
+
for (i = 0; intRelOpts[i].gen.name; i++)
{
relOpts[j] = &intRelOpts[i].gen;
@@ -800,6 +821,9 @@ allocate_reloption(bits32 kinds, int type, const char *name, const char *desc,
case RELOPT_TYPE_BOOL:
size = sizeof(relopt_bool);
break;
+ case RELOPT_TYPE_TERNARY:
+ size = sizeof(relopt_ternary);
+ break;
case RELOPT_TYPE_INT:
size = sizeof(relopt_int);
break;
@@ -883,6 +907,54 @@ add_local_bool_reloption(local_relopts *relopts, const char *name,
add_local_reloption(relopts, (relopt_gen *) newoption, offset);
}
+/*
+ * init_ternary_reloption
+ * Allocate and initialize a new ternary reloption
+ */
+static relopt_ternary *
+init_ternary_reloption(bits32 kinds, const char *name, const char *desc,
+ ternary default_val, LOCKMODE lockmode)
+{
+ relopt_ternary *newoption;
+
+ newoption = (relopt_ternary *) allocate_reloption(kinds,
+ RELOPT_TYPE_TERNARY, name, desc, lockmode);
+ newoption->default_val = default_val;
+
+ return newoption;
+}
+
+/*
+ * add_ternary_reloption
+ * Add a new ternary reloption
+ */
+void
+add_ternary_reloption(bits32 kinds, const char *name, const char *desc,
+ ternary default_val, LOCKMODE lockmode)
+{
+ relopt_ternary *newoption = init_ternary_reloption(kinds, name, desc,
+ default_val, lockmode);
+
+ add_reloption((relopt_gen *) newoption);
+}
+
+/*
+ * add_local_ternary_reloption
+ * Add a new ternary local reloption
+ *
+ * 'offset' is offset of ternary-typed field.
+ */
+void
+add_local_ternary_reloption(local_relopts *relopts, const char *name,
+ const char *desc, ternary default_val,
+ int offset)
+{
+ relopt_ternary *newoption = init_ternary_reloption(RELOPT_KIND_LOCAL,
+ name, desc,
+ default_val, 0);
+
+ add_local_reloption(relopts, (relopt_gen *) newoption, offset);
+}
/*
* init_real_reloption
@@ -1617,6 +1689,18 @@ parse_one_reloption(relopt_value *option, char *text_str, int text_len,
option->gen->name, value)));
}
break;
+ case RELOPT_TYPE_TERNARY:
+ {
+ bool b;
+ parsed = parse_bool(value, &b);
+ option->values.ternary_val = b ? TERNARY_TRUE : TERNARY_FALSE;
+ if (validate && !parsed)
+ ereport(ERROR,
+ (errcode(ERRCODE_INVALID_PARAMETER_VALUE),
+ errmsg("invalid value for ternary option \"%s\": %s",
+ option->gen->name, value)));
+ }
+ break;
case RELOPT_TYPE_INT:
{
relopt_int *optint = (relopt_int *) option->gen;
@@ -1780,17 +1864,6 @@ fillRelOptions(void *rdopts, Size basesize,
char *itempos = ((char *) rdopts) + elems[j].offset;
char *string_val;
- /*
- * If isset_offset is provided, store whether the reloption is
- * set there.
- */
- if (elems[j].isset_offset > 0)
- {
- char *setpos = ((char *) rdopts) + elems[j].isset_offset;
-
- *(bool *) setpos = options[i].isset;
- }
-
switch (options[i].gen->type)
{
case RELOPT_TYPE_BOOL:
@@ -1798,6 +1871,11 @@ fillRelOptions(void *rdopts, Size basesize,
options[i].values.bool_val :
((relopt_bool *) options[i].gen)->default_val;
break;
+ case RELOPT_TYPE_TERNARY:
+ *(ternary *) itempos = options[i].isset ?
+ options[i].values.ternary_val :
+ ((relopt_ternary *) options[i].gen)->default_val;
+ break;
case RELOPT_TYPE_INT:
*(int *) itempos = options[i].isset ?
options[i].values.int_val :
@@ -1912,8 +1990,8 @@ default_reloptions(Datum reloptions, bool validate, relopt_kind kind)
offsetof(StdRdOptions, parallel_workers)},
{"vacuum_index_cleanup", RELOPT_TYPE_ENUM,
offsetof(StdRdOptions, vacuum_index_cleanup)},
- {"vacuum_truncate", RELOPT_TYPE_BOOL,
- offsetof(StdRdOptions, vacuum_truncate), offsetof(StdRdOptions, vacuum_truncate_set)},
+ {"vacuum_truncate", RELOPT_TYPE_TERNARY,
+ offsetof(StdRdOptions, vacuum_truncate)},
{"vacuum_max_eager_freeze_failure_rate", RELOPT_TYPE_REAL,
offsetof(StdRdOptions, vacuum_max_eager_freeze_failure_rate)}
};
@@ -1993,7 +2071,6 @@ build_local_reloptions(local_relopts *relopts, Datum options, bool validate)
elems[i].optname = opt->option->name;
elems[i].opttype = opt->option->type;
elems[i].offset = opt->offset;
- elems[i].isset_offset = 0; /* not supported for local relopts yet */
i++;
}
diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c
index 733ef40ae7..977babff54 100644
--- a/src/backend/commands/vacuum.c
+++ b/src/backend/commands/vacuum.c
@@ -2221,9 +2221,9 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams params,
{
StdRdOptions *opts = (StdRdOptions *) rel->rd_options;
- if (opts && opts->vacuum_truncate_set)
+ if (opts && opts->vacuum_truncate != TERNARY_UNSET)
{
- if (opts->vacuum_truncate)
+ if (opts->vacuum_truncate == TERNARY_TRUE)
params.truncate = VACOPTVALUE_ENABLED;
else
params.truncate = VACOPTVALUE_DISABLED;
diff --git a/src/include/access/reloptions.h b/src/include/access/reloptions.h
index a604a4702c..a436697658 100644
--- a/src/include/access/reloptions.h
+++ b/src/include/access/reloptions.h
@@ -29,6 +29,7 @@
typedef enum relopt_type
{
RELOPT_TYPE_BOOL,
+ RELOPT_TYPE_TERNARY, /* on, off, unset */
RELOPT_TYPE_INT,
RELOPT_TYPE_REAL,
RELOPT_TYPE_ENUM,
@@ -80,6 +81,7 @@ typedef struct relopt_value
union
{
bool bool_val;
+ ternary ternary_val;
int int_val;
double real_val;
int enum_val;
@@ -94,6 +96,12 @@ typedef struct relopt_bool
bool default_val;
} relopt_bool;
+typedef struct relopt_rernary
+{
+ relopt_gen gen;
+ int default_val;
+} relopt_ternary;
+
typedef struct relopt_int
{
relopt_gen gen;
@@ -152,19 +160,6 @@ typedef struct
const char *optname; /* option's name */
relopt_type opttype; /* option's datatype */
int offset; /* offset of field in result struct */
-
- /*
- * isset_offset is an optional offset of a field in the result struct that
- * stores whether the option is explicitly set for the relation or if it
- * just picked up the default value. In most cases, this can be
- * accomplished by giving the reloption a special out-of-range default
- * value (e.g., some integer reloptions use -2), but this isn't always
- * possible. For example, a Boolean reloption cannot be given an
- * out-of-range default, so we need another way to discover the source of
- * its value. This offset is only used if given a value greater than
- * zero.
- */
- int isset_offset;
} relopt_parse_elt;
/* Local reloption definition */
@@ -195,6 +190,8 @@ typedef struct local_relopts
extern relopt_kind add_reloption_kind(void);
extern void add_bool_reloption(bits32 kinds, const char *name, const char *desc,
bool default_val, LOCKMODE lockmode);
+extern void add_ternary_reloption(bits32 kinds, const char *name,
+ const char *desc, int default_val, LOCKMODE lockmode);
extern void add_int_reloption(bits32 kinds, const char *name, const char *desc,
int default_val, int min_val, int max_val,
LOCKMODE lockmode);
@@ -214,6 +211,9 @@ extern void register_reloptions_validator(local_relopts *relopts,
extern void add_local_bool_reloption(local_relopts *relopts, const char *name,
const char *desc, bool default_val,
int offset);
+extern void add_local_ternary_reloption(local_relopts *relopts,
+ const char *name, const char *desc,
+ ternary default_val, int offset);
extern void add_local_int_reloption(local_relopts *relopts, const char *name,
const char *desc, int default_val,
int min_val, int max_val, int offset);
diff --git a/src/include/c.h b/src/include/c.h
index 39022f8a9d..9c59ccccf8 100644
--- a/src/include/c.h
+++ b/src/include/c.h
@@ -488,6 +488,22 @@ typedef void (*pg_funcptr_t) (void);
#include <stdbool.h>
+/*
+ * ternary
+ * Boolean value with an extrea "unset" option
+ *
+ * Ternary data type is used in relation options that can be "true", "false" or
+ * "unset". Since relation options are used deep inside the PostgreSQL code,
+ * this type is declared globally.
+*/
+
+typedef enum ternary
+{
+ TERNARY_FALSE = 0,
+ TERNARY_TRUE = 1,
+ TERNARY_UNSET = -1
+} ternary;
+
/* ----------------------------------------------------------------
* Section 3: standard system types
diff --git a/src/include/utils/rel.h b/src/include/utils/rel.h
index b552359915..7346d618f9 100644
--- a/src/include/utils/rel.h
+++ b/src/include/utils/rel.h
@@ -346,8 +346,7 @@ typedef struct StdRdOptions
bool user_catalog_table; /* use as an additional catalog relation */
int parallel_workers; /* max number of parallel workers */
StdRdOptIndexCleanup vacuum_index_cleanup; /* controls index vacuuming */
- bool vacuum_truncate; /* enables vacuum to truncate a relation */
- bool vacuum_truncate_set; /* whether vacuum_truncate is set */
+ ternary vacuum_truncate; /* enables vacuum to truncate a relation */
/*
* Fraction of pages in a relation that vacuum can eagerly scan and fail
--
2.39.2
--nextPart5129932.5fSG56mABF
Content-Disposition: attachment;
filename="v2-0003-Add-alias-to-be-used-as-unset-state.patch"
Content-Transfer-Encoding: 7Bit
Content-Type: text/x-patch; charset="unicode-2-0-utf-8";
name="v2-0003-Add-alias-to-be-used-as-unset-state.patch"
^ permalink raw reply [nested|flat] 868+ messages in thread
* [PATCH v2a 2/4] Introduce ternary reloptions
@ 2025-09-04 14:41 Nikolay Shaplov <[email protected]>
0 siblings, 0 replies; 868+ messages in thread
From: Nikolay Shaplov @ 2025-09-04 14:41 UTC (permalink / raw)
Introduce ternary reloption as a replacement for current `vacuum_truncate`
implementation. Remove `vacuum_truncate_set` additional flag and using
`TERNARY_UNSET` value instead.
---
src/backend/access/common/reloptions.c | 129 ++++++++++++++++++++-----
src/backend/commands/vacuum.c | 4 +-
src/include/access/reloptions.h | 26 ++---
src/include/c.h | 16 +++
src/include/utils/rel.h | 3 +-
5 files changed, 135 insertions(+), 43 deletions(-)
diff --git a/src/backend/access/common/reloptions.c b/src/backend/access/common/reloptions.c
index 0af3fea68f..f543f11001 100644
--- a/src/backend/access/common/reloptions.c
+++ b/src/backend/access/common/reloptions.c
@@ -40,9 +40,9 @@
*
* To add an option:
*
- * (i) decide on a type (bool, integer, real, enum, string), name, default
- * value, upper and lower bounds (if applicable); for strings, consider a
- * validation routine.
+ * (i) decide on a type (bool, ternary, integer, real, enum, string), name,
+ * default value, upper and lower bounds (if applicable); for strings,
+ * consider a validation routine.
* (ii) add a record below (or use add_<type>_reloption).
* (iii) add it to the appropriate options struct (perhaps StdRdOptions)
* (iv) add it to the appropriate handling routine (perhaps
@@ -147,15 +147,6 @@ static relopt_bool boolRelOpts[] =
},
false
},
- {
- {
- "vacuum_truncate",
- "Enables vacuum to truncate empty pages at the end of this table",
- RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
- ShareUpdateExclusiveLock
- },
- true
- },
{
{
"deduplicate_items",
@@ -170,6 +161,21 @@ static relopt_bool boolRelOpts[] =
{{NULL}}
};
+static relopt_ternary ternaryRelOpts[] =
+{
+ {
+ {
+ "vacuum_truncate",
+ "Enables vacuum to truncate empty pages at the end of this table",
+ RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
+ ShareUpdateExclusiveLock
+ },
+ TERNARY_UNSET
+ },
+ /* list terminator */
+ {{NULL}}
+};
+
static relopt_int intRelOpts[] =
{
{
@@ -600,6 +606,13 @@ initialize_reloptions(void)
boolRelOpts[i].gen.lockmode));
j++;
}
+ for (i = 0; ternaryRelOpts[i].gen.name; i++)
+ {
+ Assert(DoLockModesConflict(ternaryRelOpts[i].gen.lockmode,
+ ternaryRelOpts[i].gen.lockmode));
+ j++;
+ }
+
for (i = 0; intRelOpts[i].gen.name; i++)
{
Assert(DoLockModesConflict(intRelOpts[i].gen.lockmode,
@@ -640,6 +653,14 @@ initialize_reloptions(void)
j++;
}
+ for (i = 0; ternaryRelOpts[i].gen.name; i++)
+ {
+ relOpts[j] = &ternaryRelOpts[i].gen;
+ relOpts[j]->type = RELOPT_TYPE_TERNARY;
+ relOpts[j]->namelen = strlen(relOpts[j]->name);
+ j++;
+ }
+
for (i = 0; intRelOpts[i].gen.name; i++)
{
relOpts[j] = &intRelOpts[i].gen;
@@ -800,6 +821,9 @@ allocate_reloption(bits32 kinds, int type, const char *name, const char *desc,
case RELOPT_TYPE_BOOL:
size = sizeof(relopt_bool);
break;
+ case RELOPT_TYPE_TERNARY:
+ size = sizeof(relopt_ternary);
+ break;
case RELOPT_TYPE_INT:
size = sizeof(relopt_int);
break;
@@ -883,6 +907,54 @@ add_local_bool_reloption(local_relopts *relopts, const char *name,
add_local_reloption(relopts, (relopt_gen *) newoption, offset);
}
+/*
+ * init_ternary_reloption
+ * Allocate and initialize a new ternary reloption
+ */
+static relopt_ternary *
+init_ternary_reloption(bits32 kinds, const char *name, const char *desc,
+ ternary default_val, LOCKMODE lockmode)
+{
+ relopt_ternary *newoption;
+
+ newoption = (relopt_ternary *) allocate_reloption(kinds,
+ RELOPT_TYPE_TERNARY, name, desc, lockmode);
+ newoption->default_val = default_val;
+
+ return newoption;
+}
+
+/*
+ * add_ternary_reloption
+ * Add a new ternary reloption
+ */
+void
+add_ternary_reloption(bits32 kinds, const char *name, const char *desc,
+ ternary default_val, LOCKMODE lockmode)
+{
+ relopt_ternary *newoption = init_ternary_reloption(kinds, name, desc,
+ default_val, lockmode);
+
+ add_reloption((relopt_gen *) newoption);
+}
+
+/*
+ * add_local_ternary_reloption
+ * Add a new ternary local reloption
+ *
+ * 'offset' is offset of ternary-typed field.
+ */
+void
+add_local_ternary_reloption(local_relopts *relopts, const char *name,
+ const char *desc, ternary default_val,
+ int offset)
+{
+ relopt_ternary *newoption = init_ternary_reloption(RELOPT_KIND_LOCAL,
+ name, desc,
+ default_val, 0);
+
+ add_local_reloption(relopts, (relopt_gen *) newoption, offset);
+}
/*
* init_real_reloption
@@ -1617,6 +1689,18 @@ parse_one_reloption(relopt_value *option, char *text_str, int text_len,
option->gen->name, value)));
}
break;
+ case RELOPT_TYPE_TERNARY:
+ {
+ bool b;
+ parsed = parse_bool(value, &b);
+ option->values.ternary_val = b ? TERNARY_TRUE : TERNARY_FALSE;
+ if (validate && !parsed)
+ ereport(ERROR,
+ (errcode(ERRCODE_INVALID_PARAMETER_VALUE),
+ errmsg("invalid value for ternary option \"%s\": %s",
+ option->gen->name, value)));
+ }
+ break;
case RELOPT_TYPE_INT:
{
relopt_int *optint = (relopt_int *) option->gen;
@@ -1780,17 +1864,6 @@ fillRelOptions(void *rdopts, Size basesize,
char *itempos = ((char *) rdopts) + elems[j].offset;
char *string_val;
- /*
- * If isset_offset is provided, store whether the reloption is
- * set there.
- */
- if (elems[j].isset_offset > 0)
- {
- char *setpos = ((char *) rdopts) + elems[j].isset_offset;
-
- *(bool *) setpos = options[i].isset;
- }
-
switch (options[i].gen->type)
{
case RELOPT_TYPE_BOOL:
@@ -1798,6 +1871,11 @@ fillRelOptions(void *rdopts, Size basesize,
options[i].values.bool_val :
((relopt_bool *) options[i].gen)->default_val;
break;
+ case RELOPT_TYPE_TERNARY:
+ *(ternary *) itempos = options[i].isset ?
+ options[i].values.ternary_val :
+ ((relopt_ternary *) options[i].gen)->default_val;
+ break;
case RELOPT_TYPE_INT:
*(int *) itempos = options[i].isset ?
options[i].values.int_val :
@@ -1912,8 +1990,8 @@ default_reloptions(Datum reloptions, bool validate, relopt_kind kind)
offsetof(StdRdOptions, parallel_workers)},
{"vacuum_index_cleanup", RELOPT_TYPE_ENUM,
offsetof(StdRdOptions, vacuum_index_cleanup)},
- {"vacuum_truncate", RELOPT_TYPE_BOOL,
- offsetof(StdRdOptions, vacuum_truncate), offsetof(StdRdOptions, vacuum_truncate_set)},
+ {"vacuum_truncate", RELOPT_TYPE_TERNARY,
+ offsetof(StdRdOptions, vacuum_truncate)},
{"vacuum_max_eager_freeze_failure_rate", RELOPT_TYPE_REAL,
offsetof(StdRdOptions, vacuum_max_eager_freeze_failure_rate)}
};
@@ -1993,7 +2071,6 @@ build_local_reloptions(local_relopts *relopts, Datum options, bool validate)
elems[i].optname = opt->option->name;
elems[i].opttype = opt->option->type;
elems[i].offset = opt->offset;
- elems[i].isset_offset = 0; /* not supported for local relopts yet */
i++;
}
diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c
index 733ef40ae7..977babff54 100644
--- a/src/backend/commands/vacuum.c
+++ b/src/backend/commands/vacuum.c
@@ -2221,9 +2221,9 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams params,
{
StdRdOptions *opts = (StdRdOptions *) rel->rd_options;
- if (opts && opts->vacuum_truncate_set)
+ if (opts && opts->vacuum_truncate != TERNARY_UNSET)
{
- if (opts->vacuum_truncate)
+ if (opts->vacuum_truncate == TERNARY_TRUE)
params.truncate = VACOPTVALUE_ENABLED;
else
params.truncate = VACOPTVALUE_DISABLED;
diff --git a/src/include/access/reloptions.h b/src/include/access/reloptions.h
index a604a4702c..a436697658 100644
--- a/src/include/access/reloptions.h
+++ b/src/include/access/reloptions.h
@@ -29,6 +29,7 @@
typedef enum relopt_type
{
RELOPT_TYPE_BOOL,
+ RELOPT_TYPE_TERNARY, /* on, off, unset */
RELOPT_TYPE_INT,
RELOPT_TYPE_REAL,
RELOPT_TYPE_ENUM,
@@ -80,6 +81,7 @@ typedef struct relopt_value
union
{
bool bool_val;
+ ternary ternary_val;
int int_val;
double real_val;
int enum_val;
@@ -94,6 +96,12 @@ typedef struct relopt_bool
bool default_val;
} relopt_bool;
+typedef struct relopt_rernary
+{
+ relopt_gen gen;
+ int default_val;
+} relopt_ternary;
+
typedef struct relopt_int
{
relopt_gen gen;
@@ -152,19 +160,6 @@ typedef struct
const char *optname; /* option's name */
relopt_type opttype; /* option's datatype */
int offset; /* offset of field in result struct */
-
- /*
- * isset_offset is an optional offset of a field in the result struct that
- * stores whether the option is explicitly set for the relation or if it
- * just picked up the default value. In most cases, this can be
- * accomplished by giving the reloption a special out-of-range default
- * value (e.g., some integer reloptions use -2), but this isn't always
- * possible. For example, a Boolean reloption cannot be given an
- * out-of-range default, so we need another way to discover the source of
- * its value. This offset is only used if given a value greater than
- * zero.
- */
- int isset_offset;
} relopt_parse_elt;
/* Local reloption definition */
@@ -195,6 +190,8 @@ typedef struct local_relopts
extern relopt_kind add_reloption_kind(void);
extern void add_bool_reloption(bits32 kinds, const char *name, const char *desc,
bool default_val, LOCKMODE lockmode);
+extern void add_ternary_reloption(bits32 kinds, const char *name,
+ const char *desc, int default_val, LOCKMODE lockmode);
extern void add_int_reloption(bits32 kinds, const char *name, const char *desc,
int default_val, int min_val, int max_val,
LOCKMODE lockmode);
@@ -214,6 +211,9 @@ extern void register_reloptions_validator(local_relopts *relopts,
extern void add_local_bool_reloption(local_relopts *relopts, const char *name,
const char *desc, bool default_val,
int offset);
+extern void add_local_ternary_reloption(local_relopts *relopts,
+ const char *name, const char *desc,
+ ternary default_val, int offset);
extern void add_local_int_reloption(local_relopts *relopts, const char *name,
const char *desc, int default_val,
int min_val, int max_val, int offset);
diff --git a/src/include/c.h b/src/include/c.h
index 39022f8a9d..9c59ccccf8 100644
--- a/src/include/c.h
+++ b/src/include/c.h
@@ -488,6 +488,22 @@ typedef void (*pg_funcptr_t) (void);
#include <stdbool.h>
+/*
+ * ternary
+ * Boolean value with an extrea "unset" option
+ *
+ * Ternary data type is used in relation options that can be "true", "false" or
+ * "unset". Since relation options are used deep inside the PostgreSQL code,
+ * this type is declared globally.
+*/
+
+typedef enum ternary
+{
+ TERNARY_FALSE = 0,
+ TERNARY_TRUE = 1,
+ TERNARY_UNSET = -1
+} ternary;
+
/* ----------------------------------------------------------------
* Section 3: standard system types
diff --git a/src/include/utils/rel.h b/src/include/utils/rel.h
index b552359915..7346d618f9 100644
--- a/src/include/utils/rel.h
+++ b/src/include/utils/rel.h
@@ -346,8 +346,7 @@ typedef struct StdRdOptions
bool user_catalog_table; /* use as an additional catalog relation */
int parallel_workers; /* max number of parallel workers */
StdRdOptIndexCleanup vacuum_index_cleanup; /* controls index vacuuming */
- bool vacuum_truncate; /* enables vacuum to truncate a relation */
- bool vacuum_truncate_set; /* whether vacuum_truncate is set */
+ ternary vacuum_truncate; /* enables vacuum to truncate a relation */
/*
* Fraction of pages in a relation that vacuum can eagerly scan and fail
--
2.39.2
--nextPart4155600.3ZeAukHxDK
Content-Disposition: attachment;
filename="v2a-0003-Add-alias-to-be-used-as-unset-state.patch"
Content-Transfer-Encoding: 7Bit
Content-Type: text/x-patch; charset="unicode-2-0-utf-8";
name="v2a-0003-Add-alias-to-be-used-as-unset-state.patch"
^ permalink raw reply [nested|flat] 868+ messages in thread
* [PATCH v2 2/4] Introduce ternary reloptions
@ 2025-09-04 14:41 Nikolay Shaplov <[email protected]>
0 siblings, 0 replies; 868+ messages in thread
From: Nikolay Shaplov @ 2025-09-04 14:41 UTC (permalink / raw)
Introduce ternary reloption as a replacement for current `vacuum_truncate`
implementation. Remove `vacuum_truncate_set` additional flag and using
`TERNARY_UNSET` value instead.
---
src/backend/access/common/reloptions.c | 129 ++++++++++++++++++++-----
src/backend/commands/vacuum.c | 4 +-
src/include/access/reloptions.h | 26 ++---
src/include/c.h | 16 +++
src/include/utils/rel.h | 3 +-
5 files changed, 135 insertions(+), 43 deletions(-)
diff --git a/src/backend/access/common/reloptions.c b/src/backend/access/common/reloptions.c
index 0af3fea68f..f543f11001 100644
--- a/src/backend/access/common/reloptions.c
+++ b/src/backend/access/common/reloptions.c
@@ -40,9 +40,9 @@
*
* To add an option:
*
- * (i) decide on a type (bool, integer, real, enum, string), name, default
- * value, upper and lower bounds (if applicable); for strings, consider a
- * validation routine.
+ * (i) decide on a type (bool, ternary, integer, real, enum, string), name,
+ * default value, upper and lower bounds (if applicable); for strings,
+ * consider a validation routine.
* (ii) add a record below (or use add_<type>_reloption).
* (iii) add it to the appropriate options struct (perhaps StdRdOptions)
* (iv) add it to the appropriate handling routine (perhaps
@@ -147,15 +147,6 @@ static relopt_bool boolRelOpts[] =
},
false
},
- {
- {
- "vacuum_truncate",
- "Enables vacuum to truncate empty pages at the end of this table",
- RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
- ShareUpdateExclusiveLock
- },
- true
- },
{
{
"deduplicate_items",
@@ -170,6 +161,21 @@ static relopt_bool boolRelOpts[] =
{{NULL}}
};
+static relopt_ternary ternaryRelOpts[] =
+{
+ {
+ {
+ "vacuum_truncate",
+ "Enables vacuum to truncate empty pages at the end of this table",
+ RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
+ ShareUpdateExclusiveLock
+ },
+ TERNARY_UNSET
+ },
+ /* list terminator */
+ {{NULL}}
+};
+
static relopt_int intRelOpts[] =
{
{
@@ -600,6 +606,13 @@ initialize_reloptions(void)
boolRelOpts[i].gen.lockmode));
j++;
}
+ for (i = 0; ternaryRelOpts[i].gen.name; i++)
+ {
+ Assert(DoLockModesConflict(ternaryRelOpts[i].gen.lockmode,
+ ternaryRelOpts[i].gen.lockmode));
+ j++;
+ }
+
for (i = 0; intRelOpts[i].gen.name; i++)
{
Assert(DoLockModesConflict(intRelOpts[i].gen.lockmode,
@@ -640,6 +653,14 @@ initialize_reloptions(void)
j++;
}
+ for (i = 0; ternaryRelOpts[i].gen.name; i++)
+ {
+ relOpts[j] = &ternaryRelOpts[i].gen;
+ relOpts[j]->type = RELOPT_TYPE_TERNARY;
+ relOpts[j]->namelen = strlen(relOpts[j]->name);
+ j++;
+ }
+
for (i = 0; intRelOpts[i].gen.name; i++)
{
relOpts[j] = &intRelOpts[i].gen;
@@ -800,6 +821,9 @@ allocate_reloption(bits32 kinds, int type, const char *name, const char *desc,
case RELOPT_TYPE_BOOL:
size = sizeof(relopt_bool);
break;
+ case RELOPT_TYPE_TERNARY:
+ size = sizeof(relopt_ternary);
+ break;
case RELOPT_TYPE_INT:
size = sizeof(relopt_int);
break;
@@ -883,6 +907,54 @@ add_local_bool_reloption(local_relopts *relopts, const char *name,
add_local_reloption(relopts, (relopt_gen *) newoption, offset);
}
+/*
+ * init_ternary_reloption
+ * Allocate and initialize a new ternary reloption
+ */
+static relopt_ternary *
+init_ternary_reloption(bits32 kinds, const char *name, const char *desc,
+ ternary default_val, LOCKMODE lockmode)
+{
+ relopt_ternary *newoption;
+
+ newoption = (relopt_ternary *) allocate_reloption(kinds,
+ RELOPT_TYPE_TERNARY, name, desc, lockmode);
+ newoption->default_val = default_val;
+
+ return newoption;
+}
+
+/*
+ * add_ternary_reloption
+ * Add a new ternary reloption
+ */
+void
+add_ternary_reloption(bits32 kinds, const char *name, const char *desc,
+ ternary default_val, LOCKMODE lockmode)
+{
+ relopt_ternary *newoption = init_ternary_reloption(kinds, name, desc,
+ default_val, lockmode);
+
+ add_reloption((relopt_gen *) newoption);
+}
+
+/*
+ * add_local_ternary_reloption
+ * Add a new ternary local reloption
+ *
+ * 'offset' is offset of ternary-typed field.
+ */
+void
+add_local_ternary_reloption(local_relopts *relopts, const char *name,
+ const char *desc, ternary default_val,
+ int offset)
+{
+ relopt_ternary *newoption = init_ternary_reloption(RELOPT_KIND_LOCAL,
+ name, desc,
+ default_val, 0);
+
+ add_local_reloption(relopts, (relopt_gen *) newoption, offset);
+}
/*
* init_real_reloption
@@ -1617,6 +1689,18 @@ parse_one_reloption(relopt_value *option, char *text_str, int text_len,
option->gen->name, value)));
}
break;
+ case RELOPT_TYPE_TERNARY:
+ {
+ bool b;
+ parsed = parse_bool(value, &b);
+ option->values.ternary_val = b ? TERNARY_TRUE : TERNARY_FALSE;
+ if (validate && !parsed)
+ ereport(ERROR,
+ (errcode(ERRCODE_INVALID_PARAMETER_VALUE),
+ errmsg("invalid value for ternary option \"%s\": %s",
+ option->gen->name, value)));
+ }
+ break;
case RELOPT_TYPE_INT:
{
relopt_int *optint = (relopt_int *) option->gen;
@@ -1780,17 +1864,6 @@ fillRelOptions(void *rdopts, Size basesize,
char *itempos = ((char *) rdopts) + elems[j].offset;
char *string_val;
- /*
- * If isset_offset is provided, store whether the reloption is
- * set there.
- */
- if (elems[j].isset_offset > 0)
- {
- char *setpos = ((char *) rdopts) + elems[j].isset_offset;
-
- *(bool *) setpos = options[i].isset;
- }
-
switch (options[i].gen->type)
{
case RELOPT_TYPE_BOOL:
@@ -1798,6 +1871,11 @@ fillRelOptions(void *rdopts, Size basesize,
options[i].values.bool_val :
((relopt_bool *) options[i].gen)->default_val;
break;
+ case RELOPT_TYPE_TERNARY:
+ *(ternary *) itempos = options[i].isset ?
+ options[i].values.ternary_val :
+ ((relopt_ternary *) options[i].gen)->default_val;
+ break;
case RELOPT_TYPE_INT:
*(int *) itempos = options[i].isset ?
options[i].values.int_val :
@@ -1912,8 +1990,8 @@ default_reloptions(Datum reloptions, bool validate, relopt_kind kind)
offsetof(StdRdOptions, parallel_workers)},
{"vacuum_index_cleanup", RELOPT_TYPE_ENUM,
offsetof(StdRdOptions, vacuum_index_cleanup)},
- {"vacuum_truncate", RELOPT_TYPE_BOOL,
- offsetof(StdRdOptions, vacuum_truncate), offsetof(StdRdOptions, vacuum_truncate_set)},
+ {"vacuum_truncate", RELOPT_TYPE_TERNARY,
+ offsetof(StdRdOptions, vacuum_truncate)},
{"vacuum_max_eager_freeze_failure_rate", RELOPT_TYPE_REAL,
offsetof(StdRdOptions, vacuum_max_eager_freeze_failure_rate)}
};
@@ -1993,7 +2071,6 @@ build_local_reloptions(local_relopts *relopts, Datum options, bool validate)
elems[i].optname = opt->option->name;
elems[i].opttype = opt->option->type;
elems[i].offset = opt->offset;
- elems[i].isset_offset = 0; /* not supported for local relopts yet */
i++;
}
diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c
index 733ef40ae7..977babff54 100644
--- a/src/backend/commands/vacuum.c
+++ b/src/backend/commands/vacuum.c
@@ -2221,9 +2221,9 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams params,
{
StdRdOptions *opts = (StdRdOptions *) rel->rd_options;
- if (opts && opts->vacuum_truncate_set)
+ if (opts && opts->vacuum_truncate != TERNARY_UNSET)
{
- if (opts->vacuum_truncate)
+ if (opts->vacuum_truncate == TERNARY_TRUE)
params.truncate = VACOPTVALUE_ENABLED;
else
params.truncate = VACOPTVALUE_DISABLED;
diff --git a/src/include/access/reloptions.h b/src/include/access/reloptions.h
index a604a4702c..a436697658 100644
--- a/src/include/access/reloptions.h
+++ b/src/include/access/reloptions.h
@@ -29,6 +29,7 @@
typedef enum relopt_type
{
RELOPT_TYPE_BOOL,
+ RELOPT_TYPE_TERNARY, /* on, off, unset */
RELOPT_TYPE_INT,
RELOPT_TYPE_REAL,
RELOPT_TYPE_ENUM,
@@ -80,6 +81,7 @@ typedef struct relopt_value
union
{
bool bool_val;
+ ternary ternary_val;
int int_val;
double real_val;
int enum_val;
@@ -94,6 +96,12 @@ typedef struct relopt_bool
bool default_val;
} relopt_bool;
+typedef struct relopt_rernary
+{
+ relopt_gen gen;
+ int default_val;
+} relopt_ternary;
+
typedef struct relopt_int
{
relopt_gen gen;
@@ -152,19 +160,6 @@ typedef struct
const char *optname; /* option's name */
relopt_type opttype; /* option's datatype */
int offset; /* offset of field in result struct */
-
- /*
- * isset_offset is an optional offset of a field in the result struct that
- * stores whether the option is explicitly set for the relation or if it
- * just picked up the default value. In most cases, this can be
- * accomplished by giving the reloption a special out-of-range default
- * value (e.g., some integer reloptions use -2), but this isn't always
- * possible. For example, a Boolean reloption cannot be given an
- * out-of-range default, so we need another way to discover the source of
- * its value. This offset is only used if given a value greater than
- * zero.
- */
- int isset_offset;
} relopt_parse_elt;
/* Local reloption definition */
@@ -195,6 +190,8 @@ typedef struct local_relopts
extern relopt_kind add_reloption_kind(void);
extern void add_bool_reloption(bits32 kinds, const char *name, const char *desc,
bool default_val, LOCKMODE lockmode);
+extern void add_ternary_reloption(bits32 kinds, const char *name,
+ const char *desc, int default_val, LOCKMODE lockmode);
extern void add_int_reloption(bits32 kinds, const char *name, const char *desc,
int default_val, int min_val, int max_val,
LOCKMODE lockmode);
@@ -214,6 +211,9 @@ extern void register_reloptions_validator(local_relopts *relopts,
extern void add_local_bool_reloption(local_relopts *relopts, const char *name,
const char *desc, bool default_val,
int offset);
+extern void add_local_ternary_reloption(local_relopts *relopts,
+ const char *name, const char *desc,
+ ternary default_val, int offset);
extern void add_local_int_reloption(local_relopts *relopts, const char *name,
const char *desc, int default_val,
int min_val, int max_val, int offset);
diff --git a/src/include/c.h b/src/include/c.h
index 39022f8a9d..9c59ccccf8 100644
--- a/src/include/c.h
+++ b/src/include/c.h
@@ -488,6 +488,22 @@ typedef void (*pg_funcptr_t) (void);
#include <stdbool.h>
+/*
+ * ternary
+ * Boolean value with an extrea "unset" option
+ *
+ * Ternary data type is used in relation options that can be "true", "false" or
+ * "unset". Since relation options are used deep inside the PostgreSQL code,
+ * this type is declared globally.
+*/
+
+typedef enum ternary
+{
+ TERNARY_FALSE = 0,
+ TERNARY_TRUE = 1,
+ TERNARY_UNSET = -1
+} ternary;
+
/* ----------------------------------------------------------------
* Section 3: standard system types
diff --git a/src/include/utils/rel.h b/src/include/utils/rel.h
index b552359915..7346d618f9 100644
--- a/src/include/utils/rel.h
+++ b/src/include/utils/rel.h
@@ -346,8 +346,7 @@ typedef struct StdRdOptions
bool user_catalog_table; /* use as an additional catalog relation */
int parallel_workers; /* max number of parallel workers */
StdRdOptIndexCleanup vacuum_index_cleanup; /* controls index vacuuming */
- bool vacuum_truncate; /* enables vacuum to truncate a relation */
- bool vacuum_truncate_set; /* whether vacuum_truncate is set */
+ ternary vacuum_truncate; /* enables vacuum to truncate a relation */
/*
* Fraction of pages in a relation that vacuum can eagerly scan and fail
--
2.39.2
--nextPart5129932.5fSG56mABF
Content-Disposition: attachment;
filename="v2-0003-Add-alias-to-be-used-as-unset-state.patch"
Content-Transfer-Encoding: 7Bit
Content-Type: text/x-patch; charset="unicode-2-0-utf-8";
name="v2-0003-Add-alias-to-be-used-as-unset-state.patch"
^ permalink raw reply [nested|flat] 868+ messages in thread
* [PATCH v2a 2/4] Introduce ternary reloptions
@ 2025-09-04 14:41 Nikolay Shaplov <[email protected]>
0 siblings, 0 replies; 868+ messages in thread
From: Nikolay Shaplov @ 2025-09-04 14:41 UTC (permalink / raw)
Introduce ternary reloption as a replacement for current `vacuum_truncate`
implementation. Remove `vacuum_truncate_set` additional flag and using
`TERNARY_UNSET` value instead.
---
src/backend/access/common/reloptions.c | 129 ++++++++++++++++++++-----
src/backend/commands/vacuum.c | 4 +-
src/include/access/reloptions.h | 26 ++---
src/include/c.h | 16 +++
src/include/utils/rel.h | 3 +-
5 files changed, 135 insertions(+), 43 deletions(-)
diff --git a/src/backend/access/common/reloptions.c b/src/backend/access/common/reloptions.c
index 0af3fea68f..f543f11001 100644
--- a/src/backend/access/common/reloptions.c
+++ b/src/backend/access/common/reloptions.c
@@ -40,9 +40,9 @@
*
* To add an option:
*
- * (i) decide on a type (bool, integer, real, enum, string), name, default
- * value, upper and lower bounds (if applicable); for strings, consider a
- * validation routine.
+ * (i) decide on a type (bool, ternary, integer, real, enum, string), name,
+ * default value, upper and lower bounds (if applicable); for strings,
+ * consider a validation routine.
* (ii) add a record below (or use add_<type>_reloption).
* (iii) add it to the appropriate options struct (perhaps StdRdOptions)
* (iv) add it to the appropriate handling routine (perhaps
@@ -147,15 +147,6 @@ static relopt_bool boolRelOpts[] =
},
false
},
- {
- {
- "vacuum_truncate",
- "Enables vacuum to truncate empty pages at the end of this table",
- RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
- ShareUpdateExclusiveLock
- },
- true
- },
{
{
"deduplicate_items",
@@ -170,6 +161,21 @@ static relopt_bool boolRelOpts[] =
{{NULL}}
};
+static relopt_ternary ternaryRelOpts[] =
+{
+ {
+ {
+ "vacuum_truncate",
+ "Enables vacuum to truncate empty pages at the end of this table",
+ RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
+ ShareUpdateExclusiveLock
+ },
+ TERNARY_UNSET
+ },
+ /* list terminator */
+ {{NULL}}
+};
+
static relopt_int intRelOpts[] =
{
{
@@ -600,6 +606,13 @@ initialize_reloptions(void)
boolRelOpts[i].gen.lockmode));
j++;
}
+ for (i = 0; ternaryRelOpts[i].gen.name; i++)
+ {
+ Assert(DoLockModesConflict(ternaryRelOpts[i].gen.lockmode,
+ ternaryRelOpts[i].gen.lockmode));
+ j++;
+ }
+
for (i = 0; intRelOpts[i].gen.name; i++)
{
Assert(DoLockModesConflict(intRelOpts[i].gen.lockmode,
@@ -640,6 +653,14 @@ initialize_reloptions(void)
j++;
}
+ for (i = 0; ternaryRelOpts[i].gen.name; i++)
+ {
+ relOpts[j] = &ternaryRelOpts[i].gen;
+ relOpts[j]->type = RELOPT_TYPE_TERNARY;
+ relOpts[j]->namelen = strlen(relOpts[j]->name);
+ j++;
+ }
+
for (i = 0; intRelOpts[i].gen.name; i++)
{
relOpts[j] = &intRelOpts[i].gen;
@@ -800,6 +821,9 @@ allocate_reloption(bits32 kinds, int type, const char *name, const char *desc,
case RELOPT_TYPE_BOOL:
size = sizeof(relopt_bool);
break;
+ case RELOPT_TYPE_TERNARY:
+ size = sizeof(relopt_ternary);
+ break;
case RELOPT_TYPE_INT:
size = sizeof(relopt_int);
break;
@@ -883,6 +907,54 @@ add_local_bool_reloption(local_relopts *relopts, const char *name,
add_local_reloption(relopts, (relopt_gen *) newoption, offset);
}
+/*
+ * init_ternary_reloption
+ * Allocate and initialize a new ternary reloption
+ */
+static relopt_ternary *
+init_ternary_reloption(bits32 kinds, const char *name, const char *desc,
+ ternary default_val, LOCKMODE lockmode)
+{
+ relopt_ternary *newoption;
+
+ newoption = (relopt_ternary *) allocate_reloption(kinds,
+ RELOPT_TYPE_TERNARY, name, desc, lockmode);
+ newoption->default_val = default_val;
+
+ return newoption;
+}
+
+/*
+ * add_ternary_reloption
+ * Add a new ternary reloption
+ */
+void
+add_ternary_reloption(bits32 kinds, const char *name, const char *desc,
+ ternary default_val, LOCKMODE lockmode)
+{
+ relopt_ternary *newoption = init_ternary_reloption(kinds, name, desc,
+ default_val, lockmode);
+
+ add_reloption((relopt_gen *) newoption);
+}
+
+/*
+ * add_local_ternary_reloption
+ * Add a new ternary local reloption
+ *
+ * 'offset' is offset of ternary-typed field.
+ */
+void
+add_local_ternary_reloption(local_relopts *relopts, const char *name,
+ const char *desc, ternary default_val,
+ int offset)
+{
+ relopt_ternary *newoption = init_ternary_reloption(RELOPT_KIND_LOCAL,
+ name, desc,
+ default_val, 0);
+
+ add_local_reloption(relopts, (relopt_gen *) newoption, offset);
+}
/*
* init_real_reloption
@@ -1617,6 +1689,18 @@ parse_one_reloption(relopt_value *option, char *text_str, int text_len,
option->gen->name, value)));
}
break;
+ case RELOPT_TYPE_TERNARY:
+ {
+ bool b;
+ parsed = parse_bool(value, &b);
+ option->values.ternary_val = b ? TERNARY_TRUE : TERNARY_FALSE;
+ if (validate && !parsed)
+ ereport(ERROR,
+ (errcode(ERRCODE_INVALID_PARAMETER_VALUE),
+ errmsg("invalid value for ternary option \"%s\": %s",
+ option->gen->name, value)));
+ }
+ break;
case RELOPT_TYPE_INT:
{
relopt_int *optint = (relopt_int *) option->gen;
@@ -1780,17 +1864,6 @@ fillRelOptions(void *rdopts, Size basesize,
char *itempos = ((char *) rdopts) + elems[j].offset;
char *string_val;
- /*
- * If isset_offset is provided, store whether the reloption is
- * set there.
- */
- if (elems[j].isset_offset > 0)
- {
- char *setpos = ((char *) rdopts) + elems[j].isset_offset;
-
- *(bool *) setpos = options[i].isset;
- }
-
switch (options[i].gen->type)
{
case RELOPT_TYPE_BOOL:
@@ -1798,6 +1871,11 @@ fillRelOptions(void *rdopts, Size basesize,
options[i].values.bool_val :
((relopt_bool *) options[i].gen)->default_val;
break;
+ case RELOPT_TYPE_TERNARY:
+ *(ternary *) itempos = options[i].isset ?
+ options[i].values.ternary_val :
+ ((relopt_ternary *) options[i].gen)->default_val;
+ break;
case RELOPT_TYPE_INT:
*(int *) itempos = options[i].isset ?
options[i].values.int_val :
@@ -1912,8 +1990,8 @@ default_reloptions(Datum reloptions, bool validate, relopt_kind kind)
offsetof(StdRdOptions, parallel_workers)},
{"vacuum_index_cleanup", RELOPT_TYPE_ENUM,
offsetof(StdRdOptions, vacuum_index_cleanup)},
- {"vacuum_truncate", RELOPT_TYPE_BOOL,
- offsetof(StdRdOptions, vacuum_truncate), offsetof(StdRdOptions, vacuum_truncate_set)},
+ {"vacuum_truncate", RELOPT_TYPE_TERNARY,
+ offsetof(StdRdOptions, vacuum_truncate)},
{"vacuum_max_eager_freeze_failure_rate", RELOPT_TYPE_REAL,
offsetof(StdRdOptions, vacuum_max_eager_freeze_failure_rate)}
};
@@ -1993,7 +2071,6 @@ build_local_reloptions(local_relopts *relopts, Datum options, bool validate)
elems[i].optname = opt->option->name;
elems[i].opttype = opt->option->type;
elems[i].offset = opt->offset;
- elems[i].isset_offset = 0; /* not supported for local relopts yet */
i++;
}
diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c
index 733ef40ae7..977babff54 100644
--- a/src/backend/commands/vacuum.c
+++ b/src/backend/commands/vacuum.c
@@ -2221,9 +2221,9 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams params,
{
StdRdOptions *opts = (StdRdOptions *) rel->rd_options;
- if (opts && opts->vacuum_truncate_set)
+ if (opts && opts->vacuum_truncate != TERNARY_UNSET)
{
- if (opts->vacuum_truncate)
+ if (opts->vacuum_truncate == TERNARY_TRUE)
params.truncate = VACOPTVALUE_ENABLED;
else
params.truncate = VACOPTVALUE_DISABLED;
diff --git a/src/include/access/reloptions.h b/src/include/access/reloptions.h
index a604a4702c..a436697658 100644
--- a/src/include/access/reloptions.h
+++ b/src/include/access/reloptions.h
@@ -29,6 +29,7 @@
typedef enum relopt_type
{
RELOPT_TYPE_BOOL,
+ RELOPT_TYPE_TERNARY, /* on, off, unset */
RELOPT_TYPE_INT,
RELOPT_TYPE_REAL,
RELOPT_TYPE_ENUM,
@@ -80,6 +81,7 @@ typedef struct relopt_value
union
{
bool bool_val;
+ ternary ternary_val;
int int_val;
double real_val;
int enum_val;
@@ -94,6 +96,12 @@ typedef struct relopt_bool
bool default_val;
} relopt_bool;
+typedef struct relopt_rernary
+{
+ relopt_gen gen;
+ int default_val;
+} relopt_ternary;
+
typedef struct relopt_int
{
relopt_gen gen;
@@ -152,19 +160,6 @@ typedef struct
const char *optname; /* option's name */
relopt_type opttype; /* option's datatype */
int offset; /* offset of field in result struct */
-
- /*
- * isset_offset is an optional offset of a field in the result struct that
- * stores whether the option is explicitly set for the relation or if it
- * just picked up the default value. In most cases, this can be
- * accomplished by giving the reloption a special out-of-range default
- * value (e.g., some integer reloptions use -2), but this isn't always
- * possible. For example, a Boolean reloption cannot be given an
- * out-of-range default, so we need another way to discover the source of
- * its value. This offset is only used if given a value greater than
- * zero.
- */
- int isset_offset;
} relopt_parse_elt;
/* Local reloption definition */
@@ -195,6 +190,8 @@ typedef struct local_relopts
extern relopt_kind add_reloption_kind(void);
extern void add_bool_reloption(bits32 kinds, const char *name, const char *desc,
bool default_val, LOCKMODE lockmode);
+extern void add_ternary_reloption(bits32 kinds, const char *name,
+ const char *desc, int default_val, LOCKMODE lockmode);
extern void add_int_reloption(bits32 kinds, const char *name, const char *desc,
int default_val, int min_val, int max_val,
LOCKMODE lockmode);
@@ -214,6 +211,9 @@ extern void register_reloptions_validator(local_relopts *relopts,
extern void add_local_bool_reloption(local_relopts *relopts, const char *name,
const char *desc, bool default_val,
int offset);
+extern void add_local_ternary_reloption(local_relopts *relopts,
+ const char *name, const char *desc,
+ ternary default_val, int offset);
extern void add_local_int_reloption(local_relopts *relopts, const char *name,
const char *desc, int default_val,
int min_val, int max_val, int offset);
diff --git a/src/include/c.h b/src/include/c.h
index 39022f8a9d..9c59ccccf8 100644
--- a/src/include/c.h
+++ b/src/include/c.h
@@ -488,6 +488,22 @@ typedef void (*pg_funcptr_t) (void);
#include <stdbool.h>
+/*
+ * ternary
+ * Boolean value with an extrea "unset" option
+ *
+ * Ternary data type is used in relation options that can be "true", "false" or
+ * "unset". Since relation options are used deep inside the PostgreSQL code,
+ * this type is declared globally.
+*/
+
+typedef enum ternary
+{
+ TERNARY_FALSE = 0,
+ TERNARY_TRUE = 1,
+ TERNARY_UNSET = -1
+} ternary;
+
/* ----------------------------------------------------------------
* Section 3: standard system types
diff --git a/src/include/utils/rel.h b/src/include/utils/rel.h
index b552359915..7346d618f9 100644
--- a/src/include/utils/rel.h
+++ b/src/include/utils/rel.h
@@ -346,8 +346,7 @@ typedef struct StdRdOptions
bool user_catalog_table; /* use as an additional catalog relation */
int parallel_workers; /* max number of parallel workers */
StdRdOptIndexCleanup vacuum_index_cleanup; /* controls index vacuuming */
- bool vacuum_truncate; /* enables vacuum to truncate a relation */
- bool vacuum_truncate_set; /* whether vacuum_truncate is set */
+ ternary vacuum_truncate; /* enables vacuum to truncate a relation */
/*
* Fraction of pages in a relation that vacuum can eagerly scan and fail
--
2.39.2
--nextPart4155600.3ZeAukHxDK
Content-Disposition: attachment;
filename="v2a-0003-Add-alias-to-be-used-as-unset-state.patch"
Content-Transfer-Encoding: 7Bit
Content-Type: text/x-patch; charset="unicode-2-0-utf-8";
name="v2a-0003-Add-alias-to-be-used-as-unset-state.patch"
^ permalink raw reply [nested|flat] 868+ messages in thread
* [PATCH v2a 2/4] Introduce ternary reloptions
@ 2025-09-04 14:41 Nikolay Shaplov <[email protected]>
0 siblings, 0 replies; 868+ messages in thread
From: Nikolay Shaplov @ 2025-09-04 14:41 UTC (permalink / raw)
Introduce ternary reloption as a replacement for current `vacuum_truncate`
implementation. Remove `vacuum_truncate_set` additional flag and using
`TERNARY_UNSET` value instead.
---
src/backend/access/common/reloptions.c | 129 ++++++++++++++++++++-----
src/backend/commands/vacuum.c | 4 +-
src/include/access/reloptions.h | 26 ++---
src/include/c.h | 16 +++
src/include/utils/rel.h | 3 +-
5 files changed, 135 insertions(+), 43 deletions(-)
diff --git a/src/backend/access/common/reloptions.c b/src/backend/access/common/reloptions.c
index 0af3fea68f..f543f11001 100644
--- a/src/backend/access/common/reloptions.c
+++ b/src/backend/access/common/reloptions.c
@@ -40,9 +40,9 @@
*
* To add an option:
*
- * (i) decide on a type (bool, integer, real, enum, string), name, default
- * value, upper and lower bounds (if applicable); for strings, consider a
- * validation routine.
+ * (i) decide on a type (bool, ternary, integer, real, enum, string), name,
+ * default value, upper and lower bounds (if applicable); for strings,
+ * consider a validation routine.
* (ii) add a record below (or use add_<type>_reloption).
* (iii) add it to the appropriate options struct (perhaps StdRdOptions)
* (iv) add it to the appropriate handling routine (perhaps
@@ -147,15 +147,6 @@ static relopt_bool boolRelOpts[] =
},
false
},
- {
- {
- "vacuum_truncate",
- "Enables vacuum to truncate empty pages at the end of this table",
- RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
- ShareUpdateExclusiveLock
- },
- true
- },
{
{
"deduplicate_items",
@@ -170,6 +161,21 @@ static relopt_bool boolRelOpts[] =
{{NULL}}
};
+static relopt_ternary ternaryRelOpts[] =
+{
+ {
+ {
+ "vacuum_truncate",
+ "Enables vacuum to truncate empty pages at the end of this table",
+ RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
+ ShareUpdateExclusiveLock
+ },
+ TERNARY_UNSET
+ },
+ /* list terminator */
+ {{NULL}}
+};
+
static relopt_int intRelOpts[] =
{
{
@@ -600,6 +606,13 @@ initialize_reloptions(void)
boolRelOpts[i].gen.lockmode));
j++;
}
+ for (i = 0; ternaryRelOpts[i].gen.name; i++)
+ {
+ Assert(DoLockModesConflict(ternaryRelOpts[i].gen.lockmode,
+ ternaryRelOpts[i].gen.lockmode));
+ j++;
+ }
+
for (i = 0; intRelOpts[i].gen.name; i++)
{
Assert(DoLockModesConflict(intRelOpts[i].gen.lockmode,
@@ -640,6 +653,14 @@ initialize_reloptions(void)
j++;
}
+ for (i = 0; ternaryRelOpts[i].gen.name; i++)
+ {
+ relOpts[j] = &ternaryRelOpts[i].gen;
+ relOpts[j]->type = RELOPT_TYPE_TERNARY;
+ relOpts[j]->namelen = strlen(relOpts[j]->name);
+ j++;
+ }
+
for (i = 0; intRelOpts[i].gen.name; i++)
{
relOpts[j] = &intRelOpts[i].gen;
@@ -800,6 +821,9 @@ allocate_reloption(bits32 kinds, int type, const char *name, const char *desc,
case RELOPT_TYPE_BOOL:
size = sizeof(relopt_bool);
break;
+ case RELOPT_TYPE_TERNARY:
+ size = sizeof(relopt_ternary);
+ break;
case RELOPT_TYPE_INT:
size = sizeof(relopt_int);
break;
@@ -883,6 +907,54 @@ add_local_bool_reloption(local_relopts *relopts, const char *name,
add_local_reloption(relopts, (relopt_gen *) newoption, offset);
}
+/*
+ * init_ternary_reloption
+ * Allocate and initialize a new ternary reloption
+ */
+static relopt_ternary *
+init_ternary_reloption(bits32 kinds, const char *name, const char *desc,
+ ternary default_val, LOCKMODE lockmode)
+{
+ relopt_ternary *newoption;
+
+ newoption = (relopt_ternary *) allocate_reloption(kinds,
+ RELOPT_TYPE_TERNARY, name, desc, lockmode);
+ newoption->default_val = default_val;
+
+ return newoption;
+}
+
+/*
+ * add_ternary_reloption
+ * Add a new ternary reloption
+ */
+void
+add_ternary_reloption(bits32 kinds, const char *name, const char *desc,
+ ternary default_val, LOCKMODE lockmode)
+{
+ relopt_ternary *newoption = init_ternary_reloption(kinds, name, desc,
+ default_val, lockmode);
+
+ add_reloption((relopt_gen *) newoption);
+}
+
+/*
+ * add_local_ternary_reloption
+ * Add a new ternary local reloption
+ *
+ * 'offset' is offset of ternary-typed field.
+ */
+void
+add_local_ternary_reloption(local_relopts *relopts, const char *name,
+ const char *desc, ternary default_val,
+ int offset)
+{
+ relopt_ternary *newoption = init_ternary_reloption(RELOPT_KIND_LOCAL,
+ name, desc,
+ default_val, 0);
+
+ add_local_reloption(relopts, (relopt_gen *) newoption, offset);
+}
/*
* init_real_reloption
@@ -1617,6 +1689,18 @@ parse_one_reloption(relopt_value *option, char *text_str, int text_len,
option->gen->name, value)));
}
break;
+ case RELOPT_TYPE_TERNARY:
+ {
+ bool b;
+ parsed = parse_bool(value, &b);
+ option->values.ternary_val = b ? TERNARY_TRUE : TERNARY_FALSE;
+ if (validate && !parsed)
+ ereport(ERROR,
+ (errcode(ERRCODE_INVALID_PARAMETER_VALUE),
+ errmsg("invalid value for ternary option \"%s\": %s",
+ option->gen->name, value)));
+ }
+ break;
case RELOPT_TYPE_INT:
{
relopt_int *optint = (relopt_int *) option->gen;
@@ -1780,17 +1864,6 @@ fillRelOptions(void *rdopts, Size basesize,
char *itempos = ((char *) rdopts) + elems[j].offset;
char *string_val;
- /*
- * If isset_offset is provided, store whether the reloption is
- * set there.
- */
- if (elems[j].isset_offset > 0)
- {
- char *setpos = ((char *) rdopts) + elems[j].isset_offset;
-
- *(bool *) setpos = options[i].isset;
- }
-
switch (options[i].gen->type)
{
case RELOPT_TYPE_BOOL:
@@ -1798,6 +1871,11 @@ fillRelOptions(void *rdopts, Size basesize,
options[i].values.bool_val :
((relopt_bool *) options[i].gen)->default_val;
break;
+ case RELOPT_TYPE_TERNARY:
+ *(ternary *) itempos = options[i].isset ?
+ options[i].values.ternary_val :
+ ((relopt_ternary *) options[i].gen)->default_val;
+ break;
case RELOPT_TYPE_INT:
*(int *) itempos = options[i].isset ?
options[i].values.int_val :
@@ -1912,8 +1990,8 @@ default_reloptions(Datum reloptions, bool validate, relopt_kind kind)
offsetof(StdRdOptions, parallel_workers)},
{"vacuum_index_cleanup", RELOPT_TYPE_ENUM,
offsetof(StdRdOptions, vacuum_index_cleanup)},
- {"vacuum_truncate", RELOPT_TYPE_BOOL,
- offsetof(StdRdOptions, vacuum_truncate), offsetof(StdRdOptions, vacuum_truncate_set)},
+ {"vacuum_truncate", RELOPT_TYPE_TERNARY,
+ offsetof(StdRdOptions, vacuum_truncate)},
{"vacuum_max_eager_freeze_failure_rate", RELOPT_TYPE_REAL,
offsetof(StdRdOptions, vacuum_max_eager_freeze_failure_rate)}
};
@@ -1993,7 +2071,6 @@ build_local_reloptions(local_relopts *relopts, Datum options, bool validate)
elems[i].optname = opt->option->name;
elems[i].opttype = opt->option->type;
elems[i].offset = opt->offset;
- elems[i].isset_offset = 0; /* not supported for local relopts yet */
i++;
}
diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c
index 733ef40ae7..977babff54 100644
--- a/src/backend/commands/vacuum.c
+++ b/src/backend/commands/vacuum.c
@@ -2221,9 +2221,9 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams params,
{
StdRdOptions *opts = (StdRdOptions *) rel->rd_options;
- if (opts && opts->vacuum_truncate_set)
+ if (opts && opts->vacuum_truncate != TERNARY_UNSET)
{
- if (opts->vacuum_truncate)
+ if (opts->vacuum_truncate == TERNARY_TRUE)
params.truncate = VACOPTVALUE_ENABLED;
else
params.truncate = VACOPTVALUE_DISABLED;
diff --git a/src/include/access/reloptions.h b/src/include/access/reloptions.h
index a604a4702c..a436697658 100644
--- a/src/include/access/reloptions.h
+++ b/src/include/access/reloptions.h
@@ -29,6 +29,7 @@
typedef enum relopt_type
{
RELOPT_TYPE_BOOL,
+ RELOPT_TYPE_TERNARY, /* on, off, unset */
RELOPT_TYPE_INT,
RELOPT_TYPE_REAL,
RELOPT_TYPE_ENUM,
@@ -80,6 +81,7 @@ typedef struct relopt_value
union
{
bool bool_val;
+ ternary ternary_val;
int int_val;
double real_val;
int enum_val;
@@ -94,6 +96,12 @@ typedef struct relopt_bool
bool default_val;
} relopt_bool;
+typedef struct relopt_rernary
+{
+ relopt_gen gen;
+ int default_val;
+} relopt_ternary;
+
typedef struct relopt_int
{
relopt_gen gen;
@@ -152,19 +160,6 @@ typedef struct
const char *optname; /* option's name */
relopt_type opttype; /* option's datatype */
int offset; /* offset of field in result struct */
-
- /*
- * isset_offset is an optional offset of a field in the result struct that
- * stores whether the option is explicitly set for the relation or if it
- * just picked up the default value. In most cases, this can be
- * accomplished by giving the reloption a special out-of-range default
- * value (e.g., some integer reloptions use -2), but this isn't always
- * possible. For example, a Boolean reloption cannot be given an
- * out-of-range default, so we need another way to discover the source of
- * its value. This offset is only used if given a value greater than
- * zero.
- */
- int isset_offset;
} relopt_parse_elt;
/* Local reloption definition */
@@ -195,6 +190,8 @@ typedef struct local_relopts
extern relopt_kind add_reloption_kind(void);
extern void add_bool_reloption(bits32 kinds, const char *name, const char *desc,
bool default_val, LOCKMODE lockmode);
+extern void add_ternary_reloption(bits32 kinds, const char *name,
+ const char *desc, int default_val, LOCKMODE lockmode);
extern void add_int_reloption(bits32 kinds, const char *name, const char *desc,
int default_val, int min_val, int max_val,
LOCKMODE lockmode);
@@ -214,6 +211,9 @@ extern void register_reloptions_validator(local_relopts *relopts,
extern void add_local_bool_reloption(local_relopts *relopts, const char *name,
const char *desc, bool default_val,
int offset);
+extern void add_local_ternary_reloption(local_relopts *relopts,
+ const char *name, const char *desc,
+ ternary default_val, int offset);
extern void add_local_int_reloption(local_relopts *relopts, const char *name,
const char *desc, int default_val,
int min_val, int max_val, int offset);
diff --git a/src/include/c.h b/src/include/c.h
index 39022f8a9d..9c59ccccf8 100644
--- a/src/include/c.h
+++ b/src/include/c.h
@@ -488,6 +488,22 @@ typedef void (*pg_funcptr_t) (void);
#include <stdbool.h>
+/*
+ * ternary
+ * Boolean value with an extrea "unset" option
+ *
+ * Ternary data type is used in relation options that can be "true", "false" or
+ * "unset". Since relation options are used deep inside the PostgreSQL code,
+ * this type is declared globally.
+*/
+
+typedef enum ternary
+{
+ TERNARY_FALSE = 0,
+ TERNARY_TRUE = 1,
+ TERNARY_UNSET = -1
+} ternary;
+
/* ----------------------------------------------------------------
* Section 3: standard system types
diff --git a/src/include/utils/rel.h b/src/include/utils/rel.h
index b552359915..7346d618f9 100644
--- a/src/include/utils/rel.h
+++ b/src/include/utils/rel.h
@@ -346,8 +346,7 @@ typedef struct StdRdOptions
bool user_catalog_table; /* use as an additional catalog relation */
int parallel_workers; /* max number of parallel workers */
StdRdOptIndexCleanup vacuum_index_cleanup; /* controls index vacuuming */
- bool vacuum_truncate; /* enables vacuum to truncate a relation */
- bool vacuum_truncate_set; /* whether vacuum_truncate is set */
+ ternary vacuum_truncate; /* enables vacuum to truncate a relation */
/*
* Fraction of pages in a relation that vacuum can eagerly scan and fail
--
2.39.2
--nextPart4155600.3ZeAukHxDK
Content-Disposition: attachment;
filename="v2a-0003-Add-alias-to-be-used-as-unset-state.patch"
Content-Transfer-Encoding: 7Bit
Content-Type: text/x-patch; charset="unicode-2-0-utf-8";
name="v2a-0003-Add-alias-to-be-used-as-unset-state.patch"
^ permalink raw reply [nested|flat] 868+ messages in thread
* [PATCH v2a 2/4] Introduce ternary reloptions
@ 2025-09-04 14:41 Nikolay Shaplov <[email protected]>
0 siblings, 0 replies; 868+ messages in thread
From: Nikolay Shaplov @ 2025-09-04 14:41 UTC (permalink / raw)
Introduce ternary reloption as a replacement for current `vacuum_truncate`
implementation. Remove `vacuum_truncate_set` additional flag and using
`TERNARY_UNSET` value instead.
---
src/backend/access/common/reloptions.c | 129 ++++++++++++++++++++-----
src/backend/commands/vacuum.c | 4 +-
src/include/access/reloptions.h | 26 ++---
src/include/c.h | 16 +++
src/include/utils/rel.h | 3 +-
5 files changed, 135 insertions(+), 43 deletions(-)
diff --git a/src/backend/access/common/reloptions.c b/src/backend/access/common/reloptions.c
index 0af3fea68f..f543f11001 100644
--- a/src/backend/access/common/reloptions.c
+++ b/src/backend/access/common/reloptions.c
@@ -40,9 +40,9 @@
*
* To add an option:
*
- * (i) decide on a type (bool, integer, real, enum, string), name, default
- * value, upper and lower bounds (if applicable); for strings, consider a
- * validation routine.
+ * (i) decide on a type (bool, ternary, integer, real, enum, string), name,
+ * default value, upper and lower bounds (if applicable); for strings,
+ * consider a validation routine.
* (ii) add a record below (or use add_<type>_reloption).
* (iii) add it to the appropriate options struct (perhaps StdRdOptions)
* (iv) add it to the appropriate handling routine (perhaps
@@ -147,15 +147,6 @@ static relopt_bool boolRelOpts[] =
},
false
},
- {
- {
- "vacuum_truncate",
- "Enables vacuum to truncate empty pages at the end of this table",
- RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
- ShareUpdateExclusiveLock
- },
- true
- },
{
{
"deduplicate_items",
@@ -170,6 +161,21 @@ static relopt_bool boolRelOpts[] =
{{NULL}}
};
+static relopt_ternary ternaryRelOpts[] =
+{
+ {
+ {
+ "vacuum_truncate",
+ "Enables vacuum to truncate empty pages at the end of this table",
+ RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
+ ShareUpdateExclusiveLock
+ },
+ TERNARY_UNSET
+ },
+ /* list terminator */
+ {{NULL}}
+};
+
static relopt_int intRelOpts[] =
{
{
@@ -600,6 +606,13 @@ initialize_reloptions(void)
boolRelOpts[i].gen.lockmode));
j++;
}
+ for (i = 0; ternaryRelOpts[i].gen.name; i++)
+ {
+ Assert(DoLockModesConflict(ternaryRelOpts[i].gen.lockmode,
+ ternaryRelOpts[i].gen.lockmode));
+ j++;
+ }
+
for (i = 0; intRelOpts[i].gen.name; i++)
{
Assert(DoLockModesConflict(intRelOpts[i].gen.lockmode,
@@ -640,6 +653,14 @@ initialize_reloptions(void)
j++;
}
+ for (i = 0; ternaryRelOpts[i].gen.name; i++)
+ {
+ relOpts[j] = &ternaryRelOpts[i].gen;
+ relOpts[j]->type = RELOPT_TYPE_TERNARY;
+ relOpts[j]->namelen = strlen(relOpts[j]->name);
+ j++;
+ }
+
for (i = 0; intRelOpts[i].gen.name; i++)
{
relOpts[j] = &intRelOpts[i].gen;
@@ -800,6 +821,9 @@ allocate_reloption(bits32 kinds, int type, const char *name, const char *desc,
case RELOPT_TYPE_BOOL:
size = sizeof(relopt_bool);
break;
+ case RELOPT_TYPE_TERNARY:
+ size = sizeof(relopt_ternary);
+ break;
case RELOPT_TYPE_INT:
size = sizeof(relopt_int);
break;
@@ -883,6 +907,54 @@ add_local_bool_reloption(local_relopts *relopts, const char *name,
add_local_reloption(relopts, (relopt_gen *) newoption, offset);
}
+/*
+ * init_ternary_reloption
+ * Allocate and initialize a new ternary reloption
+ */
+static relopt_ternary *
+init_ternary_reloption(bits32 kinds, const char *name, const char *desc,
+ ternary default_val, LOCKMODE lockmode)
+{
+ relopt_ternary *newoption;
+
+ newoption = (relopt_ternary *) allocate_reloption(kinds,
+ RELOPT_TYPE_TERNARY, name, desc, lockmode);
+ newoption->default_val = default_val;
+
+ return newoption;
+}
+
+/*
+ * add_ternary_reloption
+ * Add a new ternary reloption
+ */
+void
+add_ternary_reloption(bits32 kinds, const char *name, const char *desc,
+ ternary default_val, LOCKMODE lockmode)
+{
+ relopt_ternary *newoption = init_ternary_reloption(kinds, name, desc,
+ default_val, lockmode);
+
+ add_reloption((relopt_gen *) newoption);
+}
+
+/*
+ * add_local_ternary_reloption
+ * Add a new ternary local reloption
+ *
+ * 'offset' is offset of ternary-typed field.
+ */
+void
+add_local_ternary_reloption(local_relopts *relopts, const char *name,
+ const char *desc, ternary default_val,
+ int offset)
+{
+ relopt_ternary *newoption = init_ternary_reloption(RELOPT_KIND_LOCAL,
+ name, desc,
+ default_val, 0);
+
+ add_local_reloption(relopts, (relopt_gen *) newoption, offset);
+}
/*
* init_real_reloption
@@ -1617,6 +1689,18 @@ parse_one_reloption(relopt_value *option, char *text_str, int text_len,
option->gen->name, value)));
}
break;
+ case RELOPT_TYPE_TERNARY:
+ {
+ bool b;
+ parsed = parse_bool(value, &b);
+ option->values.ternary_val = b ? TERNARY_TRUE : TERNARY_FALSE;
+ if (validate && !parsed)
+ ereport(ERROR,
+ (errcode(ERRCODE_INVALID_PARAMETER_VALUE),
+ errmsg("invalid value for ternary option \"%s\": %s",
+ option->gen->name, value)));
+ }
+ break;
case RELOPT_TYPE_INT:
{
relopt_int *optint = (relopt_int *) option->gen;
@@ -1780,17 +1864,6 @@ fillRelOptions(void *rdopts, Size basesize,
char *itempos = ((char *) rdopts) + elems[j].offset;
char *string_val;
- /*
- * If isset_offset is provided, store whether the reloption is
- * set there.
- */
- if (elems[j].isset_offset > 0)
- {
- char *setpos = ((char *) rdopts) + elems[j].isset_offset;
-
- *(bool *) setpos = options[i].isset;
- }
-
switch (options[i].gen->type)
{
case RELOPT_TYPE_BOOL:
@@ -1798,6 +1871,11 @@ fillRelOptions(void *rdopts, Size basesize,
options[i].values.bool_val :
((relopt_bool *) options[i].gen)->default_val;
break;
+ case RELOPT_TYPE_TERNARY:
+ *(ternary *) itempos = options[i].isset ?
+ options[i].values.ternary_val :
+ ((relopt_ternary *) options[i].gen)->default_val;
+ break;
case RELOPT_TYPE_INT:
*(int *) itempos = options[i].isset ?
options[i].values.int_val :
@@ -1912,8 +1990,8 @@ default_reloptions(Datum reloptions, bool validate, relopt_kind kind)
offsetof(StdRdOptions, parallel_workers)},
{"vacuum_index_cleanup", RELOPT_TYPE_ENUM,
offsetof(StdRdOptions, vacuum_index_cleanup)},
- {"vacuum_truncate", RELOPT_TYPE_BOOL,
- offsetof(StdRdOptions, vacuum_truncate), offsetof(StdRdOptions, vacuum_truncate_set)},
+ {"vacuum_truncate", RELOPT_TYPE_TERNARY,
+ offsetof(StdRdOptions, vacuum_truncate)},
{"vacuum_max_eager_freeze_failure_rate", RELOPT_TYPE_REAL,
offsetof(StdRdOptions, vacuum_max_eager_freeze_failure_rate)}
};
@@ -1993,7 +2071,6 @@ build_local_reloptions(local_relopts *relopts, Datum options, bool validate)
elems[i].optname = opt->option->name;
elems[i].opttype = opt->option->type;
elems[i].offset = opt->offset;
- elems[i].isset_offset = 0; /* not supported for local relopts yet */
i++;
}
diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c
index 733ef40ae7..977babff54 100644
--- a/src/backend/commands/vacuum.c
+++ b/src/backend/commands/vacuum.c
@@ -2221,9 +2221,9 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams params,
{
StdRdOptions *opts = (StdRdOptions *) rel->rd_options;
- if (opts && opts->vacuum_truncate_set)
+ if (opts && opts->vacuum_truncate != TERNARY_UNSET)
{
- if (opts->vacuum_truncate)
+ if (opts->vacuum_truncate == TERNARY_TRUE)
params.truncate = VACOPTVALUE_ENABLED;
else
params.truncate = VACOPTVALUE_DISABLED;
diff --git a/src/include/access/reloptions.h b/src/include/access/reloptions.h
index a604a4702c..a436697658 100644
--- a/src/include/access/reloptions.h
+++ b/src/include/access/reloptions.h
@@ -29,6 +29,7 @@
typedef enum relopt_type
{
RELOPT_TYPE_BOOL,
+ RELOPT_TYPE_TERNARY, /* on, off, unset */
RELOPT_TYPE_INT,
RELOPT_TYPE_REAL,
RELOPT_TYPE_ENUM,
@@ -80,6 +81,7 @@ typedef struct relopt_value
union
{
bool bool_val;
+ ternary ternary_val;
int int_val;
double real_val;
int enum_val;
@@ -94,6 +96,12 @@ typedef struct relopt_bool
bool default_val;
} relopt_bool;
+typedef struct relopt_rernary
+{
+ relopt_gen gen;
+ int default_val;
+} relopt_ternary;
+
typedef struct relopt_int
{
relopt_gen gen;
@@ -152,19 +160,6 @@ typedef struct
const char *optname; /* option's name */
relopt_type opttype; /* option's datatype */
int offset; /* offset of field in result struct */
-
- /*
- * isset_offset is an optional offset of a field in the result struct that
- * stores whether the option is explicitly set for the relation or if it
- * just picked up the default value. In most cases, this can be
- * accomplished by giving the reloption a special out-of-range default
- * value (e.g., some integer reloptions use -2), but this isn't always
- * possible. For example, a Boolean reloption cannot be given an
- * out-of-range default, so we need another way to discover the source of
- * its value. This offset is only used if given a value greater than
- * zero.
- */
- int isset_offset;
} relopt_parse_elt;
/* Local reloption definition */
@@ -195,6 +190,8 @@ typedef struct local_relopts
extern relopt_kind add_reloption_kind(void);
extern void add_bool_reloption(bits32 kinds, const char *name, const char *desc,
bool default_val, LOCKMODE lockmode);
+extern void add_ternary_reloption(bits32 kinds, const char *name,
+ const char *desc, int default_val, LOCKMODE lockmode);
extern void add_int_reloption(bits32 kinds, const char *name, const char *desc,
int default_val, int min_val, int max_val,
LOCKMODE lockmode);
@@ -214,6 +211,9 @@ extern void register_reloptions_validator(local_relopts *relopts,
extern void add_local_bool_reloption(local_relopts *relopts, const char *name,
const char *desc, bool default_val,
int offset);
+extern void add_local_ternary_reloption(local_relopts *relopts,
+ const char *name, const char *desc,
+ ternary default_val, int offset);
extern void add_local_int_reloption(local_relopts *relopts, const char *name,
const char *desc, int default_val,
int min_val, int max_val, int offset);
diff --git a/src/include/c.h b/src/include/c.h
index 39022f8a9d..9c59ccccf8 100644
--- a/src/include/c.h
+++ b/src/include/c.h
@@ -488,6 +488,22 @@ typedef void (*pg_funcptr_t) (void);
#include <stdbool.h>
+/*
+ * ternary
+ * Boolean value with an extrea "unset" option
+ *
+ * Ternary data type is used in relation options that can be "true", "false" or
+ * "unset". Since relation options are used deep inside the PostgreSQL code,
+ * this type is declared globally.
+*/
+
+typedef enum ternary
+{
+ TERNARY_FALSE = 0,
+ TERNARY_TRUE = 1,
+ TERNARY_UNSET = -1
+} ternary;
+
/* ----------------------------------------------------------------
* Section 3: standard system types
diff --git a/src/include/utils/rel.h b/src/include/utils/rel.h
index b552359915..7346d618f9 100644
--- a/src/include/utils/rel.h
+++ b/src/include/utils/rel.h
@@ -346,8 +346,7 @@ typedef struct StdRdOptions
bool user_catalog_table; /* use as an additional catalog relation */
int parallel_workers; /* max number of parallel workers */
StdRdOptIndexCleanup vacuum_index_cleanup; /* controls index vacuuming */
- bool vacuum_truncate; /* enables vacuum to truncate a relation */
- bool vacuum_truncate_set; /* whether vacuum_truncate is set */
+ ternary vacuum_truncate; /* enables vacuum to truncate a relation */
/*
* Fraction of pages in a relation that vacuum can eagerly scan and fail
--
2.39.2
--nextPart4155600.3ZeAukHxDK
Content-Disposition: attachment;
filename="v2a-0003-Add-alias-to-be-used-as-unset-state.patch"
Content-Transfer-Encoding: 7Bit
Content-Type: text/x-patch; charset="unicode-2-0-utf-8";
name="v2a-0003-Add-alias-to-be-used-as-unset-state.patch"
^ permalink raw reply [nested|flat] 868+ messages in thread
* [PATCH v2a 2/4] Introduce ternary reloptions
@ 2025-09-04 14:41 Nikolay Shaplov <[email protected]>
0 siblings, 0 replies; 868+ messages in thread
From: Nikolay Shaplov @ 2025-09-04 14:41 UTC (permalink / raw)
Introduce ternary reloption as a replacement for current `vacuum_truncate`
implementation. Remove `vacuum_truncate_set` additional flag and using
`TERNARY_UNSET` value instead.
---
src/backend/access/common/reloptions.c | 129 ++++++++++++++++++++-----
src/backend/commands/vacuum.c | 4 +-
src/include/access/reloptions.h | 26 ++---
src/include/c.h | 16 +++
src/include/utils/rel.h | 3 +-
5 files changed, 135 insertions(+), 43 deletions(-)
diff --git a/src/backend/access/common/reloptions.c b/src/backend/access/common/reloptions.c
index 0af3fea68f..f543f11001 100644
--- a/src/backend/access/common/reloptions.c
+++ b/src/backend/access/common/reloptions.c
@@ -40,9 +40,9 @@
*
* To add an option:
*
- * (i) decide on a type (bool, integer, real, enum, string), name, default
- * value, upper and lower bounds (if applicable); for strings, consider a
- * validation routine.
+ * (i) decide on a type (bool, ternary, integer, real, enum, string), name,
+ * default value, upper and lower bounds (if applicable); for strings,
+ * consider a validation routine.
* (ii) add a record below (or use add_<type>_reloption).
* (iii) add it to the appropriate options struct (perhaps StdRdOptions)
* (iv) add it to the appropriate handling routine (perhaps
@@ -147,15 +147,6 @@ static relopt_bool boolRelOpts[] =
},
false
},
- {
- {
- "vacuum_truncate",
- "Enables vacuum to truncate empty pages at the end of this table",
- RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
- ShareUpdateExclusiveLock
- },
- true
- },
{
{
"deduplicate_items",
@@ -170,6 +161,21 @@ static relopt_bool boolRelOpts[] =
{{NULL}}
};
+static relopt_ternary ternaryRelOpts[] =
+{
+ {
+ {
+ "vacuum_truncate",
+ "Enables vacuum to truncate empty pages at the end of this table",
+ RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
+ ShareUpdateExclusiveLock
+ },
+ TERNARY_UNSET
+ },
+ /* list terminator */
+ {{NULL}}
+};
+
static relopt_int intRelOpts[] =
{
{
@@ -600,6 +606,13 @@ initialize_reloptions(void)
boolRelOpts[i].gen.lockmode));
j++;
}
+ for (i = 0; ternaryRelOpts[i].gen.name; i++)
+ {
+ Assert(DoLockModesConflict(ternaryRelOpts[i].gen.lockmode,
+ ternaryRelOpts[i].gen.lockmode));
+ j++;
+ }
+
for (i = 0; intRelOpts[i].gen.name; i++)
{
Assert(DoLockModesConflict(intRelOpts[i].gen.lockmode,
@@ -640,6 +653,14 @@ initialize_reloptions(void)
j++;
}
+ for (i = 0; ternaryRelOpts[i].gen.name; i++)
+ {
+ relOpts[j] = &ternaryRelOpts[i].gen;
+ relOpts[j]->type = RELOPT_TYPE_TERNARY;
+ relOpts[j]->namelen = strlen(relOpts[j]->name);
+ j++;
+ }
+
for (i = 0; intRelOpts[i].gen.name; i++)
{
relOpts[j] = &intRelOpts[i].gen;
@@ -800,6 +821,9 @@ allocate_reloption(bits32 kinds, int type, const char *name, const char *desc,
case RELOPT_TYPE_BOOL:
size = sizeof(relopt_bool);
break;
+ case RELOPT_TYPE_TERNARY:
+ size = sizeof(relopt_ternary);
+ break;
case RELOPT_TYPE_INT:
size = sizeof(relopt_int);
break;
@@ -883,6 +907,54 @@ add_local_bool_reloption(local_relopts *relopts, const char *name,
add_local_reloption(relopts, (relopt_gen *) newoption, offset);
}
+/*
+ * init_ternary_reloption
+ * Allocate and initialize a new ternary reloption
+ */
+static relopt_ternary *
+init_ternary_reloption(bits32 kinds, const char *name, const char *desc,
+ ternary default_val, LOCKMODE lockmode)
+{
+ relopt_ternary *newoption;
+
+ newoption = (relopt_ternary *) allocate_reloption(kinds,
+ RELOPT_TYPE_TERNARY, name, desc, lockmode);
+ newoption->default_val = default_val;
+
+ return newoption;
+}
+
+/*
+ * add_ternary_reloption
+ * Add a new ternary reloption
+ */
+void
+add_ternary_reloption(bits32 kinds, const char *name, const char *desc,
+ ternary default_val, LOCKMODE lockmode)
+{
+ relopt_ternary *newoption = init_ternary_reloption(kinds, name, desc,
+ default_val, lockmode);
+
+ add_reloption((relopt_gen *) newoption);
+}
+
+/*
+ * add_local_ternary_reloption
+ * Add a new ternary local reloption
+ *
+ * 'offset' is offset of ternary-typed field.
+ */
+void
+add_local_ternary_reloption(local_relopts *relopts, const char *name,
+ const char *desc, ternary default_val,
+ int offset)
+{
+ relopt_ternary *newoption = init_ternary_reloption(RELOPT_KIND_LOCAL,
+ name, desc,
+ default_val, 0);
+
+ add_local_reloption(relopts, (relopt_gen *) newoption, offset);
+}
/*
* init_real_reloption
@@ -1617,6 +1689,18 @@ parse_one_reloption(relopt_value *option, char *text_str, int text_len,
option->gen->name, value)));
}
break;
+ case RELOPT_TYPE_TERNARY:
+ {
+ bool b;
+ parsed = parse_bool(value, &b);
+ option->values.ternary_val = b ? TERNARY_TRUE : TERNARY_FALSE;
+ if (validate && !parsed)
+ ereport(ERROR,
+ (errcode(ERRCODE_INVALID_PARAMETER_VALUE),
+ errmsg("invalid value for ternary option \"%s\": %s",
+ option->gen->name, value)));
+ }
+ break;
case RELOPT_TYPE_INT:
{
relopt_int *optint = (relopt_int *) option->gen;
@@ -1780,17 +1864,6 @@ fillRelOptions(void *rdopts, Size basesize,
char *itempos = ((char *) rdopts) + elems[j].offset;
char *string_val;
- /*
- * If isset_offset is provided, store whether the reloption is
- * set there.
- */
- if (elems[j].isset_offset > 0)
- {
- char *setpos = ((char *) rdopts) + elems[j].isset_offset;
-
- *(bool *) setpos = options[i].isset;
- }
-
switch (options[i].gen->type)
{
case RELOPT_TYPE_BOOL:
@@ -1798,6 +1871,11 @@ fillRelOptions(void *rdopts, Size basesize,
options[i].values.bool_val :
((relopt_bool *) options[i].gen)->default_val;
break;
+ case RELOPT_TYPE_TERNARY:
+ *(ternary *) itempos = options[i].isset ?
+ options[i].values.ternary_val :
+ ((relopt_ternary *) options[i].gen)->default_val;
+ break;
case RELOPT_TYPE_INT:
*(int *) itempos = options[i].isset ?
options[i].values.int_val :
@@ -1912,8 +1990,8 @@ default_reloptions(Datum reloptions, bool validate, relopt_kind kind)
offsetof(StdRdOptions, parallel_workers)},
{"vacuum_index_cleanup", RELOPT_TYPE_ENUM,
offsetof(StdRdOptions, vacuum_index_cleanup)},
- {"vacuum_truncate", RELOPT_TYPE_BOOL,
- offsetof(StdRdOptions, vacuum_truncate), offsetof(StdRdOptions, vacuum_truncate_set)},
+ {"vacuum_truncate", RELOPT_TYPE_TERNARY,
+ offsetof(StdRdOptions, vacuum_truncate)},
{"vacuum_max_eager_freeze_failure_rate", RELOPT_TYPE_REAL,
offsetof(StdRdOptions, vacuum_max_eager_freeze_failure_rate)}
};
@@ -1993,7 +2071,6 @@ build_local_reloptions(local_relopts *relopts, Datum options, bool validate)
elems[i].optname = opt->option->name;
elems[i].opttype = opt->option->type;
elems[i].offset = opt->offset;
- elems[i].isset_offset = 0; /* not supported for local relopts yet */
i++;
}
diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c
index 733ef40ae7..977babff54 100644
--- a/src/backend/commands/vacuum.c
+++ b/src/backend/commands/vacuum.c
@@ -2221,9 +2221,9 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams params,
{
StdRdOptions *opts = (StdRdOptions *) rel->rd_options;
- if (opts && opts->vacuum_truncate_set)
+ if (opts && opts->vacuum_truncate != TERNARY_UNSET)
{
- if (opts->vacuum_truncate)
+ if (opts->vacuum_truncate == TERNARY_TRUE)
params.truncate = VACOPTVALUE_ENABLED;
else
params.truncate = VACOPTVALUE_DISABLED;
diff --git a/src/include/access/reloptions.h b/src/include/access/reloptions.h
index a604a4702c..a436697658 100644
--- a/src/include/access/reloptions.h
+++ b/src/include/access/reloptions.h
@@ -29,6 +29,7 @@
typedef enum relopt_type
{
RELOPT_TYPE_BOOL,
+ RELOPT_TYPE_TERNARY, /* on, off, unset */
RELOPT_TYPE_INT,
RELOPT_TYPE_REAL,
RELOPT_TYPE_ENUM,
@@ -80,6 +81,7 @@ typedef struct relopt_value
union
{
bool bool_val;
+ ternary ternary_val;
int int_val;
double real_val;
int enum_val;
@@ -94,6 +96,12 @@ typedef struct relopt_bool
bool default_val;
} relopt_bool;
+typedef struct relopt_rernary
+{
+ relopt_gen gen;
+ int default_val;
+} relopt_ternary;
+
typedef struct relopt_int
{
relopt_gen gen;
@@ -152,19 +160,6 @@ typedef struct
const char *optname; /* option's name */
relopt_type opttype; /* option's datatype */
int offset; /* offset of field in result struct */
-
- /*
- * isset_offset is an optional offset of a field in the result struct that
- * stores whether the option is explicitly set for the relation or if it
- * just picked up the default value. In most cases, this can be
- * accomplished by giving the reloption a special out-of-range default
- * value (e.g., some integer reloptions use -2), but this isn't always
- * possible. For example, a Boolean reloption cannot be given an
- * out-of-range default, so we need another way to discover the source of
- * its value. This offset is only used if given a value greater than
- * zero.
- */
- int isset_offset;
} relopt_parse_elt;
/* Local reloption definition */
@@ -195,6 +190,8 @@ typedef struct local_relopts
extern relopt_kind add_reloption_kind(void);
extern void add_bool_reloption(bits32 kinds, const char *name, const char *desc,
bool default_val, LOCKMODE lockmode);
+extern void add_ternary_reloption(bits32 kinds, const char *name,
+ const char *desc, int default_val, LOCKMODE lockmode);
extern void add_int_reloption(bits32 kinds, const char *name, const char *desc,
int default_val, int min_val, int max_val,
LOCKMODE lockmode);
@@ -214,6 +211,9 @@ extern void register_reloptions_validator(local_relopts *relopts,
extern void add_local_bool_reloption(local_relopts *relopts, const char *name,
const char *desc, bool default_val,
int offset);
+extern void add_local_ternary_reloption(local_relopts *relopts,
+ const char *name, const char *desc,
+ ternary default_val, int offset);
extern void add_local_int_reloption(local_relopts *relopts, const char *name,
const char *desc, int default_val,
int min_val, int max_val, int offset);
diff --git a/src/include/c.h b/src/include/c.h
index 39022f8a9d..9c59ccccf8 100644
--- a/src/include/c.h
+++ b/src/include/c.h
@@ -488,6 +488,22 @@ typedef void (*pg_funcptr_t) (void);
#include <stdbool.h>
+/*
+ * ternary
+ * Boolean value with an extrea "unset" option
+ *
+ * Ternary data type is used in relation options that can be "true", "false" or
+ * "unset". Since relation options are used deep inside the PostgreSQL code,
+ * this type is declared globally.
+*/
+
+typedef enum ternary
+{
+ TERNARY_FALSE = 0,
+ TERNARY_TRUE = 1,
+ TERNARY_UNSET = -1
+} ternary;
+
/* ----------------------------------------------------------------
* Section 3: standard system types
diff --git a/src/include/utils/rel.h b/src/include/utils/rel.h
index b552359915..7346d618f9 100644
--- a/src/include/utils/rel.h
+++ b/src/include/utils/rel.h
@@ -346,8 +346,7 @@ typedef struct StdRdOptions
bool user_catalog_table; /* use as an additional catalog relation */
int parallel_workers; /* max number of parallel workers */
StdRdOptIndexCleanup vacuum_index_cleanup; /* controls index vacuuming */
- bool vacuum_truncate; /* enables vacuum to truncate a relation */
- bool vacuum_truncate_set; /* whether vacuum_truncate is set */
+ ternary vacuum_truncate; /* enables vacuum to truncate a relation */
/*
* Fraction of pages in a relation that vacuum can eagerly scan and fail
--
2.39.2
--nextPart4155600.3ZeAukHxDK
Content-Disposition: attachment;
filename="v2a-0003-Add-alias-to-be-used-as-unset-state.patch"
Content-Transfer-Encoding: 7Bit
Content-Type: text/x-patch; charset="unicode-2-0-utf-8";
name="v2a-0003-Add-alias-to-be-used-as-unset-state.patch"
^ permalink raw reply [nested|flat] 868+ messages in thread
* [PATCH v2 2/4] Introduce ternary reloptions
@ 2025-09-04 14:41 Nikolay Shaplov <[email protected]>
0 siblings, 0 replies; 868+ messages in thread
From: Nikolay Shaplov @ 2025-09-04 14:41 UTC (permalink / raw)
Introduce ternary reloption as a replacement for current `vacuum_truncate`
implementation. Remove `vacuum_truncate_set` additional flag and using
`TERNARY_UNSET` value instead.
---
src/backend/access/common/reloptions.c | 129 ++++++++++++++++++++-----
src/backend/commands/vacuum.c | 4 +-
src/include/access/reloptions.h | 26 ++---
src/include/c.h | 16 +++
src/include/utils/rel.h | 3 +-
5 files changed, 135 insertions(+), 43 deletions(-)
diff --git a/src/backend/access/common/reloptions.c b/src/backend/access/common/reloptions.c
index 0af3fea68f..f543f11001 100644
--- a/src/backend/access/common/reloptions.c
+++ b/src/backend/access/common/reloptions.c
@@ -40,9 +40,9 @@
*
* To add an option:
*
- * (i) decide on a type (bool, integer, real, enum, string), name, default
- * value, upper and lower bounds (if applicable); for strings, consider a
- * validation routine.
+ * (i) decide on a type (bool, ternary, integer, real, enum, string), name,
+ * default value, upper and lower bounds (if applicable); for strings,
+ * consider a validation routine.
* (ii) add a record below (or use add_<type>_reloption).
* (iii) add it to the appropriate options struct (perhaps StdRdOptions)
* (iv) add it to the appropriate handling routine (perhaps
@@ -147,15 +147,6 @@ static relopt_bool boolRelOpts[] =
},
false
},
- {
- {
- "vacuum_truncate",
- "Enables vacuum to truncate empty pages at the end of this table",
- RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
- ShareUpdateExclusiveLock
- },
- true
- },
{
{
"deduplicate_items",
@@ -170,6 +161,21 @@ static relopt_bool boolRelOpts[] =
{{NULL}}
};
+static relopt_ternary ternaryRelOpts[] =
+{
+ {
+ {
+ "vacuum_truncate",
+ "Enables vacuum to truncate empty pages at the end of this table",
+ RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
+ ShareUpdateExclusiveLock
+ },
+ TERNARY_UNSET
+ },
+ /* list terminator */
+ {{NULL}}
+};
+
static relopt_int intRelOpts[] =
{
{
@@ -600,6 +606,13 @@ initialize_reloptions(void)
boolRelOpts[i].gen.lockmode));
j++;
}
+ for (i = 0; ternaryRelOpts[i].gen.name; i++)
+ {
+ Assert(DoLockModesConflict(ternaryRelOpts[i].gen.lockmode,
+ ternaryRelOpts[i].gen.lockmode));
+ j++;
+ }
+
for (i = 0; intRelOpts[i].gen.name; i++)
{
Assert(DoLockModesConflict(intRelOpts[i].gen.lockmode,
@@ -640,6 +653,14 @@ initialize_reloptions(void)
j++;
}
+ for (i = 0; ternaryRelOpts[i].gen.name; i++)
+ {
+ relOpts[j] = &ternaryRelOpts[i].gen;
+ relOpts[j]->type = RELOPT_TYPE_TERNARY;
+ relOpts[j]->namelen = strlen(relOpts[j]->name);
+ j++;
+ }
+
for (i = 0; intRelOpts[i].gen.name; i++)
{
relOpts[j] = &intRelOpts[i].gen;
@@ -800,6 +821,9 @@ allocate_reloption(bits32 kinds, int type, const char *name, const char *desc,
case RELOPT_TYPE_BOOL:
size = sizeof(relopt_bool);
break;
+ case RELOPT_TYPE_TERNARY:
+ size = sizeof(relopt_ternary);
+ break;
case RELOPT_TYPE_INT:
size = sizeof(relopt_int);
break;
@@ -883,6 +907,54 @@ add_local_bool_reloption(local_relopts *relopts, const char *name,
add_local_reloption(relopts, (relopt_gen *) newoption, offset);
}
+/*
+ * init_ternary_reloption
+ * Allocate and initialize a new ternary reloption
+ */
+static relopt_ternary *
+init_ternary_reloption(bits32 kinds, const char *name, const char *desc,
+ ternary default_val, LOCKMODE lockmode)
+{
+ relopt_ternary *newoption;
+
+ newoption = (relopt_ternary *) allocate_reloption(kinds,
+ RELOPT_TYPE_TERNARY, name, desc, lockmode);
+ newoption->default_val = default_val;
+
+ return newoption;
+}
+
+/*
+ * add_ternary_reloption
+ * Add a new ternary reloption
+ */
+void
+add_ternary_reloption(bits32 kinds, const char *name, const char *desc,
+ ternary default_val, LOCKMODE lockmode)
+{
+ relopt_ternary *newoption = init_ternary_reloption(kinds, name, desc,
+ default_val, lockmode);
+
+ add_reloption((relopt_gen *) newoption);
+}
+
+/*
+ * add_local_ternary_reloption
+ * Add a new ternary local reloption
+ *
+ * 'offset' is offset of ternary-typed field.
+ */
+void
+add_local_ternary_reloption(local_relopts *relopts, const char *name,
+ const char *desc, ternary default_val,
+ int offset)
+{
+ relopt_ternary *newoption = init_ternary_reloption(RELOPT_KIND_LOCAL,
+ name, desc,
+ default_val, 0);
+
+ add_local_reloption(relopts, (relopt_gen *) newoption, offset);
+}
/*
* init_real_reloption
@@ -1617,6 +1689,18 @@ parse_one_reloption(relopt_value *option, char *text_str, int text_len,
option->gen->name, value)));
}
break;
+ case RELOPT_TYPE_TERNARY:
+ {
+ bool b;
+ parsed = parse_bool(value, &b);
+ option->values.ternary_val = b ? TERNARY_TRUE : TERNARY_FALSE;
+ if (validate && !parsed)
+ ereport(ERROR,
+ (errcode(ERRCODE_INVALID_PARAMETER_VALUE),
+ errmsg("invalid value for ternary option \"%s\": %s",
+ option->gen->name, value)));
+ }
+ break;
case RELOPT_TYPE_INT:
{
relopt_int *optint = (relopt_int *) option->gen;
@@ -1780,17 +1864,6 @@ fillRelOptions(void *rdopts, Size basesize,
char *itempos = ((char *) rdopts) + elems[j].offset;
char *string_val;
- /*
- * If isset_offset is provided, store whether the reloption is
- * set there.
- */
- if (elems[j].isset_offset > 0)
- {
- char *setpos = ((char *) rdopts) + elems[j].isset_offset;
-
- *(bool *) setpos = options[i].isset;
- }
-
switch (options[i].gen->type)
{
case RELOPT_TYPE_BOOL:
@@ -1798,6 +1871,11 @@ fillRelOptions(void *rdopts, Size basesize,
options[i].values.bool_val :
((relopt_bool *) options[i].gen)->default_val;
break;
+ case RELOPT_TYPE_TERNARY:
+ *(ternary *) itempos = options[i].isset ?
+ options[i].values.ternary_val :
+ ((relopt_ternary *) options[i].gen)->default_val;
+ break;
case RELOPT_TYPE_INT:
*(int *) itempos = options[i].isset ?
options[i].values.int_val :
@@ -1912,8 +1990,8 @@ default_reloptions(Datum reloptions, bool validate, relopt_kind kind)
offsetof(StdRdOptions, parallel_workers)},
{"vacuum_index_cleanup", RELOPT_TYPE_ENUM,
offsetof(StdRdOptions, vacuum_index_cleanup)},
- {"vacuum_truncate", RELOPT_TYPE_BOOL,
- offsetof(StdRdOptions, vacuum_truncate), offsetof(StdRdOptions, vacuum_truncate_set)},
+ {"vacuum_truncate", RELOPT_TYPE_TERNARY,
+ offsetof(StdRdOptions, vacuum_truncate)},
{"vacuum_max_eager_freeze_failure_rate", RELOPT_TYPE_REAL,
offsetof(StdRdOptions, vacuum_max_eager_freeze_failure_rate)}
};
@@ -1993,7 +2071,6 @@ build_local_reloptions(local_relopts *relopts, Datum options, bool validate)
elems[i].optname = opt->option->name;
elems[i].opttype = opt->option->type;
elems[i].offset = opt->offset;
- elems[i].isset_offset = 0; /* not supported for local relopts yet */
i++;
}
diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c
index 733ef40ae7..977babff54 100644
--- a/src/backend/commands/vacuum.c
+++ b/src/backend/commands/vacuum.c
@@ -2221,9 +2221,9 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams params,
{
StdRdOptions *opts = (StdRdOptions *) rel->rd_options;
- if (opts && opts->vacuum_truncate_set)
+ if (opts && opts->vacuum_truncate != TERNARY_UNSET)
{
- if (opts->vacuum_truncate)
+ if (opts->vacuum_truncate == TERNARY_TRUE)
params.truncate = VACOPTVALUE_ENABLED;
else
params.truncate = VACOPTVALUE_DISABLED;
diff --git a/src/include/access/reloptions.h b/src/include/access/reloptions.h
index a604a4702c..a436697658 100644
--- a/src/include/access/reloptions.h
+++ b/src/include/access/reloptions.h
@@ -29,6 +29,7 @@
typedef enum relopt_type
{
RELOPT_TYPE_BOOL,
+ RELOPT_TYPE_TERNARY, /* on, off, unset */
RELOPT_TYPE_INT,
RELOPT_TYPE_REAL,
RELOPT_TYPE_ENUM,
@@ -80,6 +81,7 @@ typedef struct relopt_value
union
{
bool bool_val;
+ ternary ternary_val;
int int_val;
double real_val;
int enum_val;
@@ -94,6 +96,12 @@ typedef struct relopt_bool
bool default_val;
} relopt_bool;
+typedef struct relopt_rernary
+{
+ relopt_gen gen;
+ int default_val;
+} relopt_ternary;
+
typedef struct relopt_int
{
relopt_gen gen;
@@ -152,19 +160,6 @@ typedef struct
const char *optname; /* option's name */
relopt_type opttype; /* option's datatype */
int offset; /* offset of field in result struct */
-
- /*
- * isset_offset is an optional offset of a field in the result struct that
- * stores whether the option is explicitly set for the relation or if it
- * just picked up the default value. In most cases, this can be
- * accomplished by giving the reloption a special out-of-range default
- * value (e.g., some integer reloptions use -2), but this isn't always
- * possible. For example, a Boolean reloption cannot be given an
- * out-of-range default, so we need another way to discover the source of
- * its value. This offset is only used if given a value greater than
- * zero.
- */
- int isset_offset;
} relopt_parse_elt;
/* Local reloption definition */
@@ -195,6 +190,8 @@ typedef struct local_relopts
extern relopt_kind add_reloption_kind(void);
extern void add_bool_reloption(bits32 kinds, const char *name, const char *desc,
bool default_val, LOCKMODE lockmode);
+extern void add_ternary_reloption(bits32 kinds, const char *name,
+ const char *desc, int default_val, LOCKMODE lockmode);
extern void add_int_reloption(bits32 kinds, const char *name, const char *desc,
int default_val, int min_val, int max_val,
LOCKMODE lockmode);
@@ -214,6 +211,9 @@ extern void register_reloptions_validator(local_relopts *relopts,
extern void add_local_bool_reloption(local_relopts *relopts, const char *name,
const char *desc, bool default_val,
int offset);
+extern void add_local_ternary_reloption(local_relopts *relopts,
+ const char *name, const char *desc,
+ ternary default_val, int offset);
extern void add_local_int_reloption(local_relopts *relopts, const char *name,
const char *desc, int default_val,
int min_val, int max_val, int offset);
diff --git a/src/include/c.h b/src/include/c.h
index 39022f8a9d..9c59ccccf8 100644
--- a/src/include/c.h
+++ b/src/include/c.h
@@ -488,6 +488,22 @@ typedef void (*pg_funcptr_t) (void);
#include <stdbool.h>
+/*
+ * ternary
+ * Boolean value with an extrea "unset" option
+ *
+ * Ternary data type is used in relation options that can be "true", "false" or
+ * "unset". Since relation options are used deep inside the PostgreSQL code,
+ * this type is declared globally.
+*/
+
+typedef enum ternary
+{
+ TERNARY_FALSE = 0,
+ TERNARY_TRUE = 1,
+ TERNARY_UNSET = -1
+} ternary;
+
/* ----------------------------------------------------------------
* Section 3: standard system types
diff --git a/src/include/utils/rel.h b/src/include/utils/rel.h
index b552359915..7346d618f9 100644
--- a/src/include/utils/rel.h
+++ b/src/include/utils/rel.h
@@ -346,8 +346,7 @@ typedef struct StdRdOptions
bool user_catalog_table; /* use as an additional catalog relation */
int parallel_workers; /* max number of parallel workers */
StdRdOptIndexCleanup vacuum_index_cleanup; /* controls index vacuuming */
- bool vacuum_truncate; /* enables vacuum to truncate a relation */
- bool vacuum_truncate_set; /* whether vacuum_truncate is set */
+ ternary vacuum_truncate; /* enables vacuum to truncate a relation */
/*
* Fraction of pages in a relation that vacuum can eagerly scan and fail
--
2.39.2
--nextPart5129932.5fSG56mABF
Content-Disposition: attachment;
filename="v2-0003-Add-alias-to-be-used-as-unset-state.patch"
Content-Transfer-Encoding: 7Bit
Content-Type: text/x-patch; charset="unicode-2-0-utf-8";
name="v2-0003-Add-alias-to-be-used-as-unset-state.patch"
^ permalink raw reply [nested|flat] 868+ messages in thread
* [PATCH v2 2/4] Introduce ternary reloptions
@ 2025-09-04 14:41 Nikolay Shaplov <[email protected]>
0 siblings, 0 replies; 868+ messages in thread
From: Nikolay Shaplov @ 2025-09-04 14:41 UTC (permalink / raw)
Introduce ternary reloption as a replacement for current `vacuum_truncate`
implementation. Remove `vacuum_truncate_set` additional flag and using
`TERNARY_UNSET` value instead.
---
src/backend/access/common/reloptions.c | 129 ++++++++++++++++++++-----
src/backend/commands/vacuum.c | 4 +-
src/include/access/reloptions.h | 26 ++---
src/include/c.h | 16 +++
src/include/utils/rel.h | 3 +-
5 files changed, 135 insertions(+), 43 deletions(-)
diff --git a/src/backend/access/common/reloptions.c b/src/backend/access/common/reloptions.c
index 0af3fea68f..f543f11001 100644
--- a/src/backend/access/common/reloptions.c
+++ b/src/backend/access/common/reloptions.c
@@ -40,9 +40,9 @@
*
* To add an option:
*
- * (i) decide on a type (bool, integer, real, enum, string), name, default
- * value, upper and lower bounds (if applicable); for strings, consider a
- * validation routine.
+ * (i) decide on a type (bool, ternary, integer, real, enum, string), name,
+ * default value, upper and lower bounds (if applicable); for strings,
+ * consider a validation routine.
* (ii) add a record below (or use add_<type>_reloption).
* (iii) add it to the appropriate options struct (perhaps StdRdOptions)
* (iv) add it to the appropriate handling routine (perhaps
@@ -147,15 +147,6 @@ static relopt_bool boolRelOpts[] =
},
false
},
- {
- {
- "vacuum_truncate",
- "Enables vacuum to truncate empty pages at the end of this table",
- RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
- ShareUpdateExclusiveLock
- },
- true
- },
{
{
"deduplicate_items",
@@ -170,6 +161,21 @@ static relopt_bool boolRelOpts[] =
{{NULL}}
};
+static relopt_ternary ternaryRelOpts[] =
+{
+ {
+ {
+ "vacuum_truncate",
+ "Enables vacuum to truncate empty pages at the end of this table",
+ RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
+ ShareUpdateExclusiveLock
+ },
+ TERNARY_UNSET
+ },
+ /* list terminator */
+ {{NULL}}
+};
+
static relopt_int intRelOpts[] =
{
{
@@ -600,6 +606,13 @@ initialize_reloptions(void)
boolRelOpts[i].gen.lockmode));
j++;
}
+ for (i = 0; ternaryRelOpts[i].gen.name; i++)
+ {
+ Assert(DoLockModesConflict(ternaryRelOpts[i].gen.lockmode,
+ ternaryRelOpts[i].gen.lockmode));
+ j++;
+ }
+
for (i = 0; intRelOpts[i].gen.name; i++)
{
Assert(DoLockModesConflict(intRelOpts[i].gen.lockmode,
@@ -640,6 +653,14 @@ initialize_reloptions(void)
j++;
}
+ for (i = 0; ternaryRelOpts[i].gen.name; i++)
+ {
+ relOpts[j] = &ternaryRelOpts[i].gen;
+ relOpts[j]->type = RELOPT_TYPE_TERNARY;
+ relOpts[j]->namelen = strlen(relOpts[j]->name);
+ j++;
+ }
+
for (i = 0; intRelOpts[i].gen.name; i++)
{
relOpts[j] = &intRelOpts[i].gen;
@@ -800,6 +821,9 @@ allocate_reloption(bits32 kinds, int type, const char *name, const char *desc,
case RELOPT_TYPE_BOOL:
size = sizeof(relopt_bool);
break;
+ case RELOPT_TYPE_TERNARY:
+ size = sizeof(relopt_ternary);
+ break;
case RELOPT_TYPE_INT:
size = sizeof(relopt_int);
break;
@@ -883,6 +907,54 @@ add_local_bool_reloption(local_relopts *relopts, const char *name,
add_local_reloption(relopts, (relopt_gen *) newoption, offset);
}
+/*
+ * init_ternary_reloption
+ * Allocate and initialize a new ternary reloption
+ */
+static relopt_ternary *
+init_ternary_reloption(bits32 kinds, const char *name, const char *desc,
+ ternary default_val, LOCKMODE lockmode)
+{
+ relopt_ternary *newoption;
+
+ newoption = (relopt_ternary *) allocate_reloption(kinds,
+ RELOPT_TYPE_TERNARY, name, desc, lockmode);
+ newoption->default_val = default_val;
+
+ return newoption;
+}
+
+/*
+ * add_ternary_reloption
+ * Add a new ternary reloption
+ */
+void
+add_ternary_reloption(bits32 kinds, const char *name, const char *desc,
+ ternary default_val, LOCKMODE lockmode)
+{
+ relopt_ternary *newoption = init_ternary_reloption(kinds, name, desc,
+ default_val, lockmode);
+
+ add_reloption((relopt_gen *) newoption);
+}
+
+/*
+ * add_local_ternary_reloption
+ * Add a new ternary local reloption
+ *
+ * 'offset' is offset of ternary-typed field.
+ */
+void
+add_local_ternary_reloption(local_relopts *relopts, const char *name,
+ const char *desc, ternary default_val,
+ int offset)
+{
+ relopt_ternary *newoption = init_ternary_reloption(RELOPT_KIND_LOCAL,
+ name, desc,
+ default_val, 0);
+
+ add_local_reloption(relopts, (relopt_gen *) newoption, offset);
+}
/*
* init_real_reloption
@@ -1617,6 +1689,18 @@ parse_one_reloption(relopt_value *option, char *text_str, int text_len,
option->gen->name, value)));
}
break;
+ case RELOPT_TYPE_TERNARY:
+ {
+ bool b;
+ parsed = parse_bool(value, &b);
+ option->values.ternary_val = b ? TERNARY_TRUE : TERNARY_FALSE;
+ if (validate && !parsed)
+ ereport(ERROR,
+ (errcode(ERRCODE_INVALID_PARAMETER_VALUE),
+ errmsg("invalid value for ternary option \"%s\": %s",
+ option->gen->name, value)));
+ }
+ break;
case RELOPT_TYPE_INT:
{
relopt_int *optint = (relopt_int *) option->gen;
@@ -1780,17 +1864,6 @@ fillRelOptions(void *rdopts, Size basesize,
char *itempos = ((char *) rdopts) + elems[j].offset;
char *string_val;
- /*
- * If isset_offset is provided, store whether the reloption is
- * set there.
- */
- if (elems[j].isset_offset > 0)
- {
- char *setpos = ((char *) rdopts) + elems[j].isset_offset;
-
- *(bool *) setpos = options[i].isset;
- }
-
switch (options[i].gen->type)
{
case RELOPT_TYPE_BOOL:
@@ -1798,6 +1871,11 @@ fillRelOptions(void *rdopts, Size basesize,
options[i].values.bool_val :
((relopt_bool *) options[i].gen)->default_val;
break;
+ case RELOPT_TYPE_TERNARY:
+ *(ternary *) itempos = options[i].isset ?
+ options[i].values.ternary_val :
+ ((relopt_ternary *) options[i].gen)->default_val;
+ break;
case RELOPT_TYPE_INT:
*(int *) itempos = options[i].isset ?
options[i].values.int_val :
@@ -1912,8 +1990,8 @@ default_reloptions(Datum reloptions, bool validate, relopt_kind kind)
offsetof(StdRdOptions, parallel_workers)},
{"vacuum_index_cleanup", RELOPT_TYPE_ENUM,
offsetof(StdRdOptions, vacuum_index_cleanup)},
- {"vacuum_truncate", RELOPT_TYPE_BOOL,
- offsetof(StdRdOptions, vacuum_truncate), offsetof(StdRdOptions, vacuum_truncate_set)},
+ {"vacuum_truncate", RELOPT_TYPE_TERNARY,
+ offsetof(StdRdOptions, vacuum_truncate)},
{"vacuum_max_eager_freeze_failure_rate", RELOPT_TYPE_REAL,
offsetof(StdRdOptions, vacuum_max_eager_freeze_failure_rate)}
};
@@ -1993,7 +2071,6 @@ build_local_reloptions(local_relopts *relopts, Datum options, bool validate)
elems[i].optname = opt->option->name;
elems[i].opttype = opt->option->type;
elems[i].offset = opt->offset;
- elems[i].isset_offset = 0; /* not supported for local relopts yet */
i++;
}
diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c
index 733ef40ae7..977babff54 100644
--- a/src/backend/commands/vacuum.c
+++ b/src/backend/commands/vacuum.c
@@ -2221,9 +2221,9 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams params,
{
StdRdOptions *opts = (StdRdOptions *) rel->rd_options;
- if (opts && opts->vacuum_truncate_set)
+ if (opts && opts->vacuum_truncate != TERNARY_UNSET)
{
- if (opts->vacuum_truncate)
+ if (opts->vacuum_truncate == TERNARY_TRUE)
params.truncate = VACOPTVALUE_ENABLED;
else
params.truncate = VACOPTVALUE_DISABLED;
diff --git a/src/include/access/reloptions.h b/src/include/access/reloptions.h
index a604a4702c..a436697658 100644
--- a/src/include/access/reloptions.h
+++ b/src/include/access/reloptions.h
@@ -29,6 +29,7 @@
typedef enum relopt_type
{
RELOPT_TYPE_BOOL,
+ RELOPT_TYPE_TERNARY, /* on, off, unset */
RELOPT_TYPE_INT,
RELOPT_TYPE_REAL,
RELOPT_TYPE_ENUM,
@@ -80,6 +81,7 @@ typedef struct relopt_value
union
{
bool bool_val;
+ ternary ternary_val;
int int_val;
double real_val;
int enum_val;
@@ -94,6 +96,12 @@ typedef struct relopt_bool
bool default_val;
} relopt_bool;
+typedef struct relopt_rernary
+{
+ relopt_gen gen;
+ int default_val;
+} relopt_ternary;
+
typedef struct relopt_int
{
relopt_gen gen;
@@ -152,19 +160,6 @@ typedef struct
const char *optname; /* option's name */
relopt_type opttype; /* option's datatype */
int offset; /* offset of field in result struct */
-
- /*
- * isset_offset is an optional offset of a field in the result struct that
- * stores whether the option is explicitly set for the relation or if it
- * just picked up the default value. In most cases, this can be
- * accomplished by giving the reloption a special out-of-range default
- * value (e.g., some integer reloptions use -2), but this isn't always
- * possible. For example, a Boolean reloption cannot be given an
- * out-of-range default, so we need another way to discover the source of
- * its value. This offset is only used if given a value greater than
- * zero.
- */
- int isset_offset;
} relopt_parse_elt;
/* Local reloption definition */
@@ -195,6 +190,8 @@ typedef struct local_relopts
extern relopt_kind add_reloption_kind(void);
extern void add_bool_reloption(bits32 kinds, const char *name, const char *desc,
bool default_val, LOCKMODE lockmode);
+extern void add_ternary_reloption(bits32 kinds, const char *name,
+ const char *desc, int default_val, LOCKMODE lockmode);
extern void add_int_reloption(bits32 kinds, const char *name, const char *desc,
int default_val, int min_val, int max_val,
LOCKMODE lockmode);
@@ -214,6 +211,9 @@ extern void register_reloptions_validator(local_relopts *relopts,
extern void add_local_bool_reloption(local_relopts *relopts, const char *name,
const char *desc, bool default_val,
int offset);
+extern void add_local_ternary_reloption(local_relopts *relopts,
+ const char *name, const char *desc,
+ ternary default_val, int offset);
extern void add_local_int_reloption(local_relopts *relopts, const char *name,
const char *desc, int default_val,
int min_val, int max_val, int offset);
diff --git a/src/include/c.h b/src/include/c.h
index 39022f8a9d..9c59ccccf8 100644
--- a/src/include/c.h
+++ b/src/include/c.h
@@ -488,6 +488,22 @@ typedef void (*pg_funcptr_t) (void);
#include <stdbool.h>
+/*
+ * ternary
+ * Boolean value with an extrea "unset" option
+ *
+ * Ternary data type is used in relation options that can be "true", "false" or
+ * "unset". Since relation options are used deep inside the PostgreSQL code,
+ * this type is declared globally.
+*/
+
+typedef enum ternary
+{
+ TERNARY_FALSE = 0,
+ TERNARY_TRUE = 1,
+ TERNARY_UNSET = -1
+} ternary;
+
/* ----------------------------------------------------------------
* Section 3: standard system types
diff --git a/src/include/utils/rel.h b/src/include/utils/rel.h
index b552359915..7346d618f9 100644
--- a/src/include/utils/rel.h
+++ b/src/include/utils/rel.h
@@ -346,8 +346,7 @@ typedef struct StdRdOptions
bool user_catalog_table; /* use as an additional catalog relation */
int parallel_workers; /* max number of parallel workers */
StdRdOptIndexCleanup vacuum_index_cleanup; /* controls index vacuuming */
- bool vacuum_truncate; /* enables vacuum to truncate a relation */
- bool vacuum_truncate_set; /* whether vacuum_truncate is set */
+ ternary vacuum_truncate; /* enables vacuum to truncate a relation */
/*
* Fraction of pages in a relation that vacuum can eagerly scan and fail
--
2.39.2
--nextPart5129932.5fSG56mABF
Content-Disposition: attachment;
filename="v2-0003-Add-alias-to-be-used-as-unset-state.patch"
Content-Transfer-Encoding: 7Bit
Content-Type: text/x-patch; charset="unicode-2-0-utf-8";
name="v2-0003-Add-alias-to-be-used-as-unset-state.patch"
^ permalink raw reply [nested|flat] 868+ messages in thread
* [PATCH v2a 2/4] Introduce ternary reloptions
@ 2025-09-04 14:41 Nikolay Shaplov <[email protected]>
0 siblings, 0 replies; 868+ messages in thread
From: Nikolay Shaplov @ 2025-09-04 14:41 UTC (permalink / raw)
Introduce ternary reloption as a replacement for current `vacuum_truncate`
implementation. Remove `vacuum_truncate_set` additional flag and using
`TERNARY_UNSET` value instead.
---
src/backend/access/common/reloptions.c | 129 ++++++++++++++++++++-----
src/backend/commands/vacuum.c | 4 +-
src/include/access/reloptions.h | 26 ++---
src/include/c.h | 16 +++
src/include/utils/rel.h | 3 +-
5 files changed, 135 insertions(+), 43 deletions(-)
diff --git a/src/backend/access/common/reloptions.c b/src/backend/access/common/reloptions.c
index 0af3fea68f..f543f11001 100644
--- a/src/backend/access/common/reloptions.c
+++ b/src/backend/access/common/reloptions.c
@@ -40,9 +40,9 @@
*
* To add an option:
*
- * (i) decide on a type (bool, integer, real, enum, string), name, default
- * value, upper and lower bounds (if applicable); for strings, consider a
- * validation routine.
+ * (i) decide on a type (bool, ternary, integer, real, enum, string), name,
+ * default value, upper and lower bounds (if applicable); for strings,
+ * consider a validation routine.
* (ii) add a record below (or use add_<type>_reloption).
* (iii) add it to the appropriate options struct (perhaps StdRdOptions)
* (iv) add it to the appropriate handling routine (perhaps
@@ -147,15 +147,6 @@ static relopt_bool boolRelOpts[] =
},
false
},
- {
- {
- "vacuum_truncate",
- "Enables vacuum to truncate empty pages at the end of this table",
- RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
- ShareUpdateExclusiveLock
- },
- true
- },
{
{
"deduplicate_items",
@@ -170,6 +161,21 @@ static relopt_bool boolRelOpts[] =
{{NULL}}
};
+static relopt_ternary ternaryRelOpts[] =
+{
+ {
+ {
+ "vacuum_truncate",
+ "Enables vacuum to truncate empty pages at the end of this table",
+ RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
+ ShareUpdateExclusiveLock
+ },
+ TERNARY_UNSET
+ },
+ /* list terminator */
+ {{NULL}}
+};
+
static relopt_int intRelOpts[] =
{
{
@@ -600,6 +606,13 @@ initialize_reloptions(void)
boolRelOpts[i].gen.lockmode));
j++;
}
+ for (i = 0; ternaryRelOpts[i].gen.name; i++)
+ {
+ Assert(DoLockModesConflict(ternaryRelOpts[i].gen.lockmode,
+ ternaryRelOpts[i].gen.lockmode));
+ j++;
+ }
+
for (i = 0; intRelOpts[i].gen.name; i++)
{
Assert(DoLockModesConflict(intRelOpts[i].gen.lockmode,
@@ -640,6 +653,14 @@ initialize_reloptions(void)
j++;
}
+ for (i = 0; ternaryRelOpts[i].gen.name; i++)
+ {
+ relOpts[j] = &ternaryRelOpts[i].gen;
+ relOpts[j]->type = RELOPT_TYPE_TERNARY;
+ relOpts[j]->namelen = strlen(relOpts[j]->name);
+ j++;
+ }
+
for (i = 0; intRelOpts[i].gen.name; i++)
{
relOpts[j] = &intRelOpts[i].gen;
@@ -800,6 +821,9 @@ allocate_reloption(bits32 kinds, int type, const char *name, const char *desc,
case RELOPT_TYPE_BOOL:
size = sizeof(relopt_bool);
break;
+ case RELOPT_TYPE_TERNARY:
+ size = sizeof(relopt_ternary);
+ break;
case RELOPT_TYPE_INT:
size = sizeof(relopt_int);
break;
@@ -883,6 +907,54 @@ add_local_bool_reloption(local_relopts *relopts, const char *name,
add_local_reloption(relopts, (relopt_gen *) newoption, offset);
}
+/*
+ * init_ternary_reloption
+ * Allocate and initialize a new ternary reloption
+ */
+static relopt_ternary *
+init_ternary_reloption(bits32 kinds, const char *name, const char *desc,
+ ternary default_val, LOCKMODE lockmode)
+{
+ relopt_ternary *newoption;
+
+ newoption = (relopt_ternary *) allocate_reloption(kinds,
+ RELOPT_TYPE_TERNARY, name, desc, lockmode);
+ newoption->default_val = default_val;
+
+ return newoption;
+}
+
+/*
+ * add_ternary_reloption
+ * Add a new ternary reloption
+ */
+void
+add_ternary_reloption(bits32 kinds, const char *name, const char *desc,
+ ternary default_val, LOCKMODE lockmode)
+{
+ relopt_ternary *newoption = init_ternary_reloption(kinds, name, desc,
+ default_val, lockmode);
+
+ add_reloption((relopt_gen *) newoption);
+}
+
+/*
+ * add_local_ternary_reloption
+ * Add a new ternary local reloption
+ *
+ * 'offset' is offset of ternary-typed field.
+ */
+void
+add_local_ternary_reloption(local_relopts *relopts, const char *name,
+ const char *desc, ternary default_val,
+ int offset)
+{
+ relopt_ternary *newoption = init_ternary_reloption(RELOPT_KIND_LOCAL,
+ name, desc,
+ default_val, 0);
+
+ add_local_reloption(relopts, (relopt_gen *) newoption, offset);
+}
/*
* init_real_reloption
@@ -1617,6 +1689,18 @@ parse_one_reloption(relopt_value *option, char *text_str, int text_len,
option->gen->name, value)));
}
break;
+ case RELOPT_TYPE_TERNARY:
+ {
+ bool b;
+ parsed = parse_bool(value, &b);
+ option->values.ternary_val = b ? TERNARY_TRUE : TERNARY_FALSE;
+ if (validate && !parsed)
+ ereport(ERROR,
+ (errcode(ERRCODE_INVALID_PARAMETER_VALUE),
+ errmsg("invalid value for ternary option \"%s\": %s",
+ option->gen->name, value)));
+ }
+ break;
case RELOPT_TYPE_INT:
{
relopt_int *optint = (relopt_int *) option->gen;
@@ -1780,17 +1864,6 @@ fillRelOptions(void *rdopts, Size basesize,
char *itempos = ((char *) rdopts) + elems[j].offset;
char *string_val;
- /*
- * If isset_offset is provided, store whether the reloption is
- * set there.
- */
- if (elems[j].isset_offset > 0)
- {
- char *setpos = ((char *) rdopts) + elems[j].isset_offset;
-
- *(bool *) setpos = options[i].isset;
- }
-
switch (options[i].gen->type)
{
case RELOPT_TYPE_BOOL:
@@ -1798,6 +1871,11 @@ fillRelOptions(void *rdopts, Size basesize,
options[i].values.bool_val :
((relopt_bool *) options[i].gen)->default_val;
break;
+ case RELOPT_TYPE_TERNARY:
+ *(ternary *) itempos = options[i].isset ?
+ options[i].values.ternary_val :
+ ((relopt_ternary *) options[i].gen)->default_val;
+ break;
case RELOPT_TYPE_INT:
*(int *) itempos = options[i].isset ?
options[i].values.int_val :
@@ -1912,8 +1990,8 @@ default_reloptions(Datum reloptions, bool validate, relopt_kind kind)
offsetof(StdRdOptions, parallel_workers)},
{"vacuum_index_cleanup", RELOPT_TYPE_ENUM,
offsetof(StdRdOptions, vacuum_index_cleanup)},
- {"vacuum_truncate", RELOPT_TYPE_BOOL,
- offsetof(StdRdOptions, vacuum_truncate), offsetof(StdRdOptions, vacuum_truncate_set)},
+ {"vacuum_truncate", RELOPT_TYPE_TERNARY,
+ offsetof(StdRdOptions, vacuum_truncate)},
{"vacuum_max_eager_freeze_failure_rate", RELOPT_TYPE_REAL,
offsetof(StdRdOptions, vacuum_max_eager_freeze_failure_rate)}
};
@@ -1993,7 +2071,6 @@ build_local_reloptions(local_relopts *relopts, Datum options, bool validate)
elems[i].optname = opt->option->name;
elems[i].opttype = opt->option->type;
elems[i].offset = opt->offset;
- elems[i].isset_offset = 0; /* not supported for local relopts yet */
i++;
}
diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c
index 733ef40ae7..977babff54 100644
--- a/src/backend/commands/vacuum.c
+++ b/src/backend/commands/vacuum.c
@@ -2221,9 +2221,9 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams params,
{
StdRdOptions *opts = (StdRdOptions *) rel->rd_options;
- if (opts && opts->vacuum_truncate_set)
+ if (opts && opts->vacuum_truncate != TERNARY_UNSET)
{
- if (opts->vacuum_truncate)
+ if (opts->vacuum_truncate == TERNARY_TRUE)
params.truncate = VACOPTVALUE_ENABLED;
else
params.truncate = VACOPTVALUE_DISABLED;
diff --git a/src/include/access/reloptions.h b/src/include/access/reloptions.h
index a604a4702c..a436697658 100644
--- a/src/include/access/reloptions.h
+++ b/src/include/access/reloptions.h
@@ -29,6 +29,7 @@
typedef enum relopt_type
{
RELOPT_TYPE_BOOL,
+ RELOPT_TYPE_TERNARY, /* on, off, unset */
RELOPT_TYPE_INT,
RELOPT_TYPE_REAL,
RELOPT_TYPE_ENUM,
@@ -80,6 +81,7 @@ typedef struct relopt_value
union
{
bool bool_val;
+ ternary ternary_val;
int int_val;
double real_val;
int enum_val;
@@ -94,6 +96,12 @@ typedef struct relopt_bool
bool default_val;
} relopt_bool;
+typedef struct relopt_rernary
+{
+ relopt_gen gen;
+ int default_val;
+} relopt_ternary;
+
typedef struct relopt_int
{
relopt_gen gen;
@@ -152,19 +160,6 @@ typedef struct
const char *optname; /* option's name */
relopt_type opttype; /* option's datatype */
int offset; /* offset of field in result struct */
-
- /*
- * isset_offset is an optional offset of a field in the result struct that
- * stores whether the option is explicitly set for the relation or if it
- * just picked up the default value. In most cases, this can be
- * accomplished by giving the reloption a special out-of-range default
- * value (e.g., some integer reloptions use -2), but this isn't always
- * possible. For example, a Boolean reloption cannot be given an
- * out-of-range default, so we need another way to discover the source of
- * its value. This offset is only used if given a value greater than
- * zero.
- */
- int isset_offset;
} relopt_parse_elt;
/* Local reloption definition */
@@ -195,6 +190,8 @@ typedef struct local_relopts
extern relopt_kind add_reloption_kind(void);
extern void add_bool_reloption(bits32 kinds, const char *name, const char *desc,
bool default_val, LOCKMODE lockmode);
+extern void add_ternary_reloption(bits32 kinds, const char *name,
+ const char *desc, int default_val, LOCKMODE lockmode);
extern void add_int_reloption(bits32 kinds, const char *name, const char *desc,
int default_val, int min_val, int max_val,
LOCKMODE lockmode);
@@ -214,6 +211,9 @@ extern void register_reloptions_validator(local_relopts *relopts,
extern void add_local_bool_reloption(local_relopts *relopts, const char *name,
const char *desc, bool default_val,
int offset);
+extern void add_local_ternary_reloption(local_relopts *relopts,
+ const char *name, const char *desc,
+ ternary default_val, int offset);
extern void add_local_int_reloption(local_relopts *relopts, const char *name,
const char *desc, int default_val,
int min_val, int max_val, int offset);
diff --git a/src/include/c.h b/src/include/c.h
index 39022f8a9d..9c59ccccf8 100644
--- a/src/include/c.h
+++ b/src/include/c.h
@@ -488,6 +488,22 @@ typedef void (*pg_funcptr_t) (void);
#include <stdbool.h>
+/*
+ * ternary
+ * Boolean value with an extrea "unset" option
+ *
+ * Ternary data type is used in relation options that can be "true", "false" or
+ * "unset". Since relation options are used deep inside the PostgreSQL code,
+ * this type is declared globally.
+*/
+
+typedef enum ternary
+{
+ TERNARY_FALSE = 0,
+ TERNARY_TRUE = 1,
+ TERNARY_UNSET = -1
+} ternary;
+
/* ----------------------------------------------------------------
* Section 3: standard system types
diff --git a/src/include/utils/rel.h b/src/include/utils/rel.h
index b552359915..7346d618f9 100644
--- a/src/include/utils/rel.h
+++ b/src/include/utils/rel.h
@@ -346,8 +346,7 @@ typedef struct StdRdOptions
bool user_catalog_table; /* use as an additional catalog relation */
int parallel_workers; /* max number of parallel workers */
StdRdOptIndexCleanup vacuum_index_cleanup; /* controls index vacuuming */
- bool vacuum_truncate; /* enables vacuum to truncate a relation */
- bool vacuum_truncate_set; /* whether vacuum_truncate is set */
+ ternary vacuum_truncate; /* enables vacuum to truncate a relation */
/*
* Fraction of pages in a relation that vacuum can eagerly scan and fail
--
2.39.2
--nextPart4155600.3ZeAukHxDK
Content-Disposition: attachment;
filename="v2a-0003-Add-alias-to-be-used-as-unset-state.patch"
Content-Transfer-Encoding: 7Bit
Content-Type: text/x-patch; charset="unicode-2-0-utf-8";
name="v2a-0003-Add-alias-to-be-used-as-unset-state.patch"
^ permalink raw reply [nested|flat] 868+ messages in thread
* [PATCH v2 2/4] Introduce ternary reloptions
@ 2025-09-04 14:41 Nikolay Shaplov <[email protected]>
0 siblings, 0 replies; 868+ messages in thread
From: Nikolay Shaplov @ 2025-09-04 14:41 UTC (permalink / raw)
Introduce ternary reloption as a replacement for current `vacuum_truncate`
implementation. Remove `vacuum_truncate_set` additional flag and using
`TERNARY_UNSET` value instead.
---
src/backend/access/common/reloptions.c | 129 ++++++++++++++++++++-----
src/backend/commands/vacuum.c | 4 +-
src/include/access/reloptions.h | 26 ++---
src/include/c.h | 16 +++
src/include/utils/rel.h | 3 +-
5 files changed, 135 insertions(+), 43 deletions(-)
diff --git a/src/backend/access/common/reloptions.c b/src/backend/access/common/reloptions.c
index 0af3fea68f..f543f11001 100644
--- a/src/backend/access/common/reloptions.c
+++ b/src/backend/access/common/reloptions.c
@@ -40,9 +40,9 @@
*
* To add an option:
*
- * (i) decide on a type (bool, integer, real, enum, string), name, default
- * value, upper and lower bounds (if applicable); for strings, consider a
- * validation routine.
+ * (i) decide on a type (bool, ternary, integer, real, enum, string), name,
+ * default value, upper and lower bounds (if applicable); for strings,
+ * consider a validation routine.
* (ii) add a record below (or use add_<type>_reloption).
* (iii) add it to the appropriate options struct (perhaps StdRdOptions)
* (iv) add it to the appropriate handling routine (perhaps
@@ -147,15 +147,6 @@ static relopt_bool boolRelOpts[] =
},
false
},
- {
- {
- "vacuum_truncate",
- "Enables vacuum to truncate empty pages at the end of this table",
- RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
- ShareUpdateExclusiveLock
- },
- true
- },
{
{
"deduplicate_items",
@@ -170,6 +161,21 @@ static relopt_bool boolRelOpts[] =
{{NULL}}
};
+static relopt_ternary ternaryRelOpts[] =
+{
+ {
+ {
+ "vacuum_truncate",
+ "Enables vacuum to truncate empty pages at the end of this table",
+ RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
+ ShareUpdateExclusiveLock
+ },
+ TERNARY_UNSET
+ },
+ /* list terminator */
+ {{NULL}}
+};
+
static relopt_int intRelOpts[] =
{
{
@@ -600,6 +606,13 @@ initialize_reloptions(void)
boolRelOpts[i].gen.lockmode));
j++;
}
+ for (i = 0; ternaryRelOpts[i].gen.name; i++)
+ {
+ Assert(DoLockModesConflict(ternaryRelOpts[i].gen.lockmode,
+ ternaryRelOpts[i].gen.lockmode));
+ j++;
+ }
+
for (i = 0; intRelOpts[i].gen.name; i++)
{
Assert(DoLockModesConflict(intRelOpts[i].gen.lockmode,
@@ -640,6 +653,14 @@ initialize_reloptions(void)
j++;
}
+ for (i = 0; ternaryRelOpts[i].gen.name; i++)
+ {
+ relOpts[j] = &ternaryRelOpts[i].gen;
+ relOpts[j]->type = RELOPT_TYPE_TERNARY;
+ relOpts[j]->namelen = strlen(relOpts[j]->name);
+ j++;
+ }
+
for (i = 0; intRelOpts[i].gen.name; i++)
{
relOpts[j] = &intRelOpts[i].gen;
@@ -800,6 +821,9 @@ allocate_reloption(bits32 kinds, int type, const char *name, const char *desc,
case RELOPT_TYPE_BOOL:
size = sizeof(relopt_bool);
break;
+ case RELOPT_TYPE_TERNARY:
+ size = sizeof(relopt_ternary);
+ break;
case RELOPT_TYPE_INT:
size = sizeof(relopt_int);
break;
@@ -883,6 +907,54 @@ add_local_bool_reloption(local_relopts *relopts, const char *name,
add_local_reloption(relopts, (relopt_gen *) newoption, offset);
}
+/*
+ * init_ternary_reloption
+ * Allocate and initialize a new ternary reloption
+ */
+static relopt_ternary *
+init_ternary_reloption(bits32 kinds, const char *name, const char *desc,
+ ternary default_val, LOCKMODE lockmode)
+{
+ relopt_ternary *newoption;
+
+ newoption = (relopt_ternary *) allocate_reloption(kinds,
+ RELOPT_TYPE_TERNARY, name, desc, lockmode);
+ newoption->default_val = default_val;
+
+ return newoption;
+}
+
+/*
+ * add_ternary_reloption
+ * Add a new ternary reloption
+ */
+void
+add_ternary_reloption(bits32 kinds, const char *name, const char *desc,
+ ternary default_val, LOCKMODE lockmode)
+{
+ relopt_ternary *newoption = init_ternary_reloption(kinds, name, desc,
+ default_val, lockmode);
+
+ add_reloption((relopt_gen *) newoption);
+}
+
+/*
+ * add_local_ternary_reloption
+ * Add a new ternary local reloption
+ *
+ * 'offset' is offset of ternary-typed field.
+ */
+void
+add_local_ternary_reloption(local_relopts *relopts, const char *name,
+ const char *desc, ternary default_val,
+ int offset)
+{
+ relopt_ternary *newoption = init_ternary_reloption(RELOPT_KIND_LOCAL,
+ name, desc,
+ default_val, 0);
+
+ add_local_reloption(relopts, (relopt_gen *) newoption, offset);
+}
/*
* init_real_reloption
@@ -1617,6 +1689,18 @@ parse_one_reloption(relopt_value *option, char *text_str, int text_len,
option->gen->name, value)));
}
break;
+ case RELOPT_TYPE_TERNARY:
+ {
+ bool b;
+ parsed = parse_bool(value, &b);
+ option->values.ternary_val = b ? TERNARY_TRUE : TERNARY_FALSE;
+ if (validate && !parsed)
+ ereport(ERROR,
+ (errcode(ERRCODE_INVALID_PARAMETER_VALUE),
+ errmsg("invalid value for ternary option \"%s\": %s",
+ option->gen->name, value)));
+ }
+ break;
case RELOPT_TYPE_INT:
{
relopt_int *optint = (relopt_int *) option->gen;
@@ -1780,17 +1864,6 @@ fillRelOptions(void *rdopts, Size basesize,
char *itempos = ((char *) rdopts) + elems[j].offset;
char *string_val;
- /*
- * If isset_offset is provided, store whether the reloption is
- * set there.
- */
- if (elems[j].isset_offset > 0)
- {
- char *setpos = ((char *) rdopts) + elems[j].isset_offset;
-
- *(bool *) setpos = options[i].isset;
- }
-
switch (options[i].gen->type)
{
case RELOPT_TYPE_BOOL:
@@ -1798,6 +1871,11 @@ fillRelOptions(void *rdopts, Size basesize,
options[i].values.bool_val :
((relopt_bool *) options[i].gen)->default_val;
break;
+ case RELOPT_TYPE_TERNARY:
+ *(ternary *) itempos = options[i].isset ?
+ options[i].values.ternary_val :
+ ((relopt_ternary *) options[i].gen)->default_val;
+ break;
case RELOPT_TYPE_INT:
*(int *) itempos = options[i].isset ?
options[i].values.int_val :
@@ -1912,8 +1990,8 @@ default_reloptions(Datum reloptions, bool validate, relopt_kind kind)
offsetof(StdRdOptions, parallel_workers)},
{"vacuum_index_cleanup", RELOPT_TYPE_ENUM,
offsetof(StdRdOptions, vacuum_index_cleanup)},
- {"vacuum_truncate", RELOPT_TYPE_BOOL,
- offsetof(StdRdOptions, vacuum_truncate), offsetof(StdRdOptions, vacuum_truncate_set)},
+ {"vacuum_truncate", RELOPT_TYPE_TERNARY,
+ offsetof(StdRdOptions, vacuum_truncate)},
{"vacuum_max_eager_freeze_failure_rate", RELOPT_TYPE_REAL,
offsetof(StdRdOptions, vacuum_max_eager_freeze_failure_rate)}
};
@@ -1993,7 +2071,6 @@ build_local_reloptions(local_relopts *relopts, Datum options, bool validate)
elems[i].optname = opt->option->name;
elems[i].opttype = opt->option->type;
elems[i].offset = opt->offset;
- elems[i].isset_offset = 0; /* not supported for local relopts yet */
i++;
}
diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c
index 733ef40ae7..977babff54 100644
--- a/src/backend/commands/vacuum.c
+++ b/src/backend/commands/vacuum.c
@@ -2221,9 +2221,9 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams params,
{
StdRdOptions *opts = (StdRdOptions *) rel->rd_options;
- if (opts && opts->vacuum_truncate_set)
+ if (opts && opts->vacuum_truncate != TERNARY_UNSET)
{
- if (opts->vacuum_truncate)
+ if (opts->vacuum_truncate == TERNARY_TRUE)
params.truncate = VACOPTVALUE_ENABLED;
else
params.truncate = VACOPTVALUE_DISABLED;
diff --git a/src/include/access/reloptions.h b/src/include/access/reloptions.h
index a604a4702c..a436697658 100644
--- a/src/include/access/reloptions.h
+++ b/src/include/access/reloptions.h
@@ -29,6 +29,7 @@
typedef enum relopt_type
{
RELOPT_TYPE_BOOL,
+ RELOPT_TYPE_TERNARY, /* on, off, unset */
RELOPT_TYPE_INT,
RELOPT_TYPE_REAL,
RELOPT_TYPE_ENUM,
@@ -80,6 +81,7 @@ typedef struct relopt_value
union
{
bool bool_val;
+ ternary ternary_val;
int int_val;
double real_val;
int enum_val;
@@ -94,6 +96,12 @@ typedef struct relopt_bool
bool default_val;
} relopt_bool;
+typedef struct relopt_rernary
+{
+ relopt_gen gen;
+ int default_val;
+} relopt_ternary;
+
typedef struct relopt_int
{
relopt_gen gen;
@@ -152,19 +160,6 @@ typedef struct
const char *optname; /* option's name */
relopt_type opttype; /* option's datatype */
int offset; /* offset of field in result struct */
-
- /*
- * isset_offset is an optional offset of a field in the result struct that
- * stores whether the option is explicitly set for the relation or if it
- * just picked up the default value. In most cases, this can be
- * accomplished by giving the reloption a special out-of-range default
- * value (e.g., some integer reloptions use -2), but this isn't always
- * possible. For example, a Boolean reloption cannot be given an
- * out-of-range default, so we need another way to discover the source of
- * its value. This offset is only used if given a value greater than
- * zero.
- */
- int isset_offset;
} relopt_parse_elt;
/* Local reloption definition */
@@ -195,6 +190,8 @@ typedef struct local_relopts
extern relopt_kind add_reloption_kind(void);
extern void add_bool_reloption(bits32 kinds, const char *name, const char *desc,
bool default_val, LOCKMODE lockmode);
+extern void add_ternary_reloption(bits32 kinds, const char *name,
+ const char *desc, int default_val, LOCKMODE lockmode);
extern void add_int_reloption(bits32 kinds, const char *name, const char *desc,
int default_val, int min_val, int max_val,
LOCKMODE lockmode);
@@ -214,6 +211,9 @@ extern void register_reloptions_validator(local_relopts *relopts,
extern void add_local_bool_reloption(local_relopts *relopts, const char *name,
const char *desc, bool default_val,
int offset);
+extern void add_local_ternary_reloption(local_relopts *relopts,
+ const char *name, const char *desc,
+ ternary default_val, int offset);
extern void add_local_int_reloption(local_relopts *relopts, const char *name,
const char *desc, int default_val,
int min_val, int max_val, int offset);
diff --git a/src/include/c.h b/src/include/c.h
index 39022f8a9d..9c59ccccf8 100644
--- a/src/include/c.h
+++ b/src/include/c.h
@@ -488,6 +488,22 @@ typedef void (*pg_funcptr_t) (void);
#include <stdbool.h>
+/*
+ * ternary
+ * Boolean value with an extrea "unset" option
+ *
+ * Ternary data type is used in relation options that can be "true", "false" or
+ * "unset". Since relation options are used deep inside the PostgreSQL code,
+ * this type is declared globally.
+*/
+
+typedef enum ternary
+{
+ TERNARY_FALSE = 0,
+ TERNARY_TRUE = 1,
+ TERNARY_UNSET = -1
+} ternary;
+
/* ----------------------------------------------------------------
* Section 3: standard system types
diff --git a/src/include/utils/rel.h b/src/include/utils/rel.h
index b552359915..7346d618f9 100644
--- a/src/include/utils/rel.h
+++ b/src/include/utils/rel.h
@@ -346,8 +346,7 @@ typedef struct StdRdOptions
bool user_catalog_table; /* use as an additional catalog relation */
int parallel_workers; /* max number of parallel workers */
StdRdOptIndexCleanup vacuum_index_cleanup; /* controls index vacuuming */
- bool vacuum_truncate; /* enables vacuum to truncate a relation */
- bool vacuum_truncate_set; /* whether vacuum_truncate is set */
+ ternary vacuum_truncate; /* enables vacuum to truncate a relation */
/*
* Fraction of pages in a relation that vacuum can eagerly scan and fail
--
2.39.2
--nextPart5129932.5fSG56mABF
Content-Disposition: attachment;
filename="v2-0003-Add-alias-to-be-used-as-unset-state.patch"
Content-Transfer-Encoding: 7Bit
Content-Type: text/x-patch; charset="unicode-2-0-utf-8";
name="v2-0003-Add-alias-to-be-used-as-unset-state.patch"
^ permalink raw reply [nested|flat] 868+ messages in thread
* [PATCH v2a 2/4] Introduce ternary reloptions
@ 2025-09-04 14:41 Nikolay Shaplov <[email protected]>
0 siblings, 0 replies; 868+ messages in thread
From: Nikolay Shaplov @ 2025-09-04 14:41 UTC (permalink / raw)
Introduce ternary reloption as a replacement for current `vacuum_truncate`
implementation. Remove `vacuum_truncate_set` additional flag and using
`TERNARY_UNSET` value instead.
---
src/backend/access/common/reloptions.c | 129 ++++++++++++++++++++-----
src/backend/commands/vacuum.c | 4 +-
src/include/access/reloptions.h | 26 ++---
src/include/c.h | 16 +++
src/include/utils/rel.h | 3 +-
5 files changed, 135 insertions(+), 43 deletions(-)
diff --git a/src/backend/access/common/reloptions.c b/src/backend/access/common/reloptions.c
index 0af3fea68f..f543f11001 100644
--- a/src/backend/access/common/reloptions.c
+++ b/src/backend/access/common/reloptions.c
@@ -40,9 +40,9 @@
*
* To add an option:
*
- * (i) decide on a type (bool, integer, real, enum, string), name, default
- * value, upper and lower bounds (if applicable); for strings, consider a
- * validation routine.
+ * (i) decide on a type (bool, ternary, integer, real, enum, string), name,
+ * default value, upper and lower bounds (if applicable); for strings,
+ * consider a validation routine.
* (ii) add a record below (or use add_<type>_reloption).
* (iii) add it to the appropriate options struct (perhaps StdRdOptions)
* (iv) add it to the appropriate handling routine (perhaps
@@ -147,15 +147,6 @@ static relopt_bool boolRelOpts[] =
},
false
},
- {
- {
- "vacuum_truncate",
- "Enables vacuum to truncate empty pages at the end of this table",
- RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
- ShareUpdateExclusiveLock
- },
- true
- },
{
{
"deduplicate_items",
@@ -170,6 +161,21 @@ static relopt_bool boolRelOpts[] =
{{NULL}}
};
+static relopt_ternary ternaryRelOpts[] =
+{
+ {
+ {
+ "vacuum_truncate",
+ "Enables vacuum to truncate empty pages at the end of this table",
+ RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
+ ShareUpdateExclusiveLock
+ },
+ TERNARY_UNSET
+ },
+ /* list terminator */
+ {{NULL}}
+};
+
static relopt_int intRelOpts[] =
{
{
@@ -600,6 +606,13 @@ initialize_reloptions(void)
boolRelOpts[i].gen.lockmode));
j++;
}
+ for (i = 0; ternaryRelOpts[i].gen.name; i++)
+ {
+ Assert(DoLockModesConflict(ternaryRelOpts[i].gen.lockmode,
+ ternaryRelOpts[i].gen.lockmode));
+ j++;
+ }
+
for (i = 0; intRelOpts[i].gen.name; i++)
{
Assert(DoLockModesConflict(intRelOpts[i].gen.lockmode,
@@ -640,6 +653,14 @@ initialize_reloptions(void)
j++;
}
+ for (i = 0; ternaryRelOpts[i].gen.name; i++)
+ {
+ relOpts[j] = &ternaryRelOpts[i].gen;
+ relOpts[j]->type = RELOPT_TYPE_TERNARY;
+ relOpts[j]->namelen = strlen(relOpts[j]->name);
+ j++;
+ }
+
for (i = 0; intRelOpts[i].gen.name; i++)
{
relOpts[j] = &intRelOpts[i].gen;
@@ -800,6 +821,9 @@ allocate_reloption(bits32 kinds, int type, const char *name, const char *desc,
case RELOPT_TYPE_BOOL:
size = sizeof(relopt_bool);
break;
+ case RELOPT_TYPE_TERNARY:
+ size = sizeof(relopt_ternary);
+ break;
case RELOPT_TYPE_INT:
size = sizeof(relopt_int);
break;
@@ -883,6 +907,54 @@ add_local_bool_reloption(local_relopts *relopts, const char *name,
add_local_reloption(relopts, (relopt_gen *) newoption, offset);
}
+/*
+ * init_ternary_reloption
+ * Allocate and initialize a new ternary reloption
+ */
+static relopt_ternary *
+init_ternary_reloption(bits32 kinds, const char *name, const char *desc,
+ ternary default_val, LOCKMODE lockmode)
+{
+ relopt_ternary *newoption;
+
+ newoption = (relopt_ternary *) allocate_reloption(kinds,
+ RELOPT_TYPE_TERNARY, name, desc, lockmode);
+ newoption->default_val = default_val;
+
+ return newoption;
+}
+
+/*
+ * add_ternary_reloption
+ * Add a new ternary reloption
+ */
+void
+add_ternary_reloption(bits32 kinds, const char *name, const char *desc,
+ ternary default_val, LOCKMODE lockmode)
+{
+ relopt_ternary *newoption = init_ternary_reloption(kinds, name, desc,
+ default_val, lockmode);
+
+ add_reloption((relopt_gen *) newoption);
+}
+
+/*
+ * add_local_ternary_reloption
+ * Add a new ternary local reloption
+ *
+ * 'offset' is offset of ternary-typed field.
+ */
+void
+add_local_ternary_reloption(local_relopts *relopts, const char *name,
+ const char *desc, ternary default_val,
+ int offset)
+{
+ relopt_ternary *newoption = init_ternary_reloption(RELOPT_KIND_LOCAL,
+ name, desc,
+ default_val, 0);
+
+ add_local_reloption(relopts, (relopt_gen *) newoption, offset);
+}
/*
* init_real_reloption
@@ -1617,6 +1689,18 @@ parse_one_reloption(relopt_value *option, char *text_str, int text_len,
option->gen->name, value)));
}
break;
+ case RELOPT_TYPE_TERNARY:
+ {
+ bool b;
+ parsed = parse_bool(value, &b);
+ option->values.ternary_val = b ? TERNARY_TRUE : TERNARY_FALSE;
+ if (validate && !parsed)
+ ereport(ERROR,
+ (errcode(ERRCODE_INVALID_PARAMETER_VALUE),
+ errmsg("invalid value for ternary option \"%s\": %s",
+ option->gen->name, value)));
+ }
+ break;
case RELOPT_TYPE_INT:
{
relopt_int *optint = (relopt_int *) option->gen;
@@ -1780,17 +1864,6 @@ fillRelOptions(void *rdopts, Size basesize,
char *itempos = ((char *) rdopts) + elems[j].offset;
char *string_val;
- /*
- * If isset_offset is provided, store whether the reloption is
- * set there.
- */
- if (elems[j].isset_offset > 0)
- {
- char *setpos = ((char *) rdopts) + elems[j].isset_offset;
-
- *(bool *) setpos = options[i].isset;
- }
-
switch (options[i].gen->type)
{
case RELOPT_TYPE_BOOL:
@@ -1798,6 +1871,11 @@ fillRelOptions(void *rdopts, Size basesize,
options[i].values.bool_val :
((relopt_bool *) options[i].gen)->default_val;
break;
+ case RELOPT_TYPE_TERNARY:
+ *(ternary *) itempos = options[i].isset ?
+ options[i].values.ternary_val :
+ ((relopt_ternary *) options[i].gen)->default_val;
+ break;
case RELOPT_TYPE_INT:
*(int *) itempos = options[i].isset ?
options[i].values.int_val :
@@ -1912,8 +1990,8 @@ default_reloptions(Datum reloptions, bool validate, relopt_kind kind)
offsetof(StdRdOptions, parallel_workers)},
{"vacuum_index_cleanup", RELOPT_TYPE_ENUM,
offsetof(StdRdOptions, vacuum_index_cleanup)},
- {"vacuum_truncate", RELOPT_TYPE_BOOL,
- offsetof(StdRdOptions, vacuum_truncate), offsetof(StdRdOptions, vacuum_truncate_set)},
+ {"vacuum_truncate", RELOPT_TYPE_TERNARY,
+ offsetof(StdRdOptions, vacuum_truncate)},
{"vacuum_max_eager_freeze_failure_rate", RELOPT_TYPE_REAL,
offsetof(StdRdOptions, vacuum_max_eager_freeze_failure_rate)}
};
@@ -1993,7 +2071,6 @@ build_local_reloptions(local_relopts *relopts, Datum options, bool validate)
elems[i].optname = opt->option->name;
elems[i].opttype = opt->option->type;
elems[i].offset = opt->offset;
- elems[i].isset_offset = 0; /* not supported for local relopts yet */
i++;
}
diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c
index 733ef40ae7..977babff54 100644
--- a/src/backend/commands/vacuum.c
+++ b/src/backend/commands/vacuum.c
@@ -2221,9 +2221,9 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams params,
{
StdRdOptions *opts = (StdRdOptions *) rel->rd_options;
- if (opts && opts->vacuum_truncate_set)
+ if (opts && opts->vacuum_truncate != TERNARY_UNSET)
{
- if (opts->vacuum_truncate)
+ if (opts->vacuum_truncate == TERNARY_TRUE)
params.truncate = VACOPTVALUE_ENABLED;
else
params.truncate = VACOPTVALUE_DISABLED;
diff --git a/src/include/access/reloptions.h b/src/include/access/reloptions.h
index a604a4702c..a436697658 100644
--- a/src/include/access/reloptions.h
+++ b/src/include/access/reloptions.h
@@ -29,6 +29,7 @@
typedef enum relopt_type
{
RELOPT_TYPE_BOOL,
+ RELOPT_TYPE_TERNARY, /* on, off, unset */
RELOPT_TYPE_INT,
RELOPT_TYPE_REAL,
RELOPT_TYPE_ENUM,
@@ -80,6 +81,7 @@ typedef struct relopt_value
union
{
bool bool_val;
+ ternary ternary_val;
int int_val;
double real_val;
int enum_val;
@@ -94,6 +96,12 @@ typedef struct relopt_bool
bool default_val;
} relopt_bool;
+typedef struct relopt_rernary
+{
+ relopt_gen gen;
+ int default_val;
+} relopt_ternary;
+
typedef struct relopt_int
{
relopt_gen gen;
@@ -152,19 +160,6 @@ typedef struct
const char *optname; /* option's name */
relopt_type opttype; /* option's datatype */
int offset; /* offset of field in result struct */
-
- /*
- * isset_offset is an optional offset of a field in the result struct that
- * stores whether the option is explicitly set for the relation or if it
- * just picked up the default value. In most cases, this can be
- * accomplished by giving the reloption a special out-of-range default
- * value (e.g., some integer reloptions use -2), but this isn't always
- * possible. For example, a Boolean reloption cannot be given an
- * out-of-range default, so we need another way to discover the source of
- * its value. This offset is only used if given a value greater than
- * zero.
- */
- int isset_offset;
} relopt_parse_elt;
/* Local reloption definition */
@@ -195,6 +190,8 @@ typedef struct local_relopts
extern relopt_kind add_reloption_kind(void);
extern void add_bool_reloption(bits32 kinds, const char *name, const char *desc,
bool default_val, LOCKMODE lockmode);
+extern void add_ternary_reloption(bits32 kinds, const char *name,
+ const char *desc, int default_val, LOCKMODE lockmode);
extern void add_int_reloption(bits32 kinds, const char *name, const char *desc,
int default_val, int min_val, int max_val,
LOCKMODE lockmode);
@@ -214,6 +211,9 @@ extern void register_reloptions_validator(local_relopts *relopts,
extern void add_local_bool_reloption(local_relopts *relopts, const char *name,
const char *desc, bool default_val,
int offset);
+extern void add_local_ternary_reloption(local_relopts *relopts,
+ const char *name, const char *desc,
+ ternary default_val, int offset);
extern void add_local_int_reloption(local_relopts *relopts, const char *name,
const char *desc, int default_val,
int min_val, int max_val, int offset);
diff --git a/src/include/c.h b/src/include/c.h
index 39022f8a9d..9c59ccccf8 100644
--- a/src/include/c.h
+++ b/src/include/c.h
@@ -488,6 +488,22 @@ typedef void (*pg_funcptr_t) (void);
#include <stdbool.h>
+/*
+ * ternary
+ * Boolean value with an extrea "unset" option
+ *
+ * Ternary data type is used in relation options that can be "true", "false" or
+ * "unset". Since relation options are used deep inside the PostgreSQL code,
+ * this type is declared globally.
+*/
+
+typedef enum ternary
+{
+ TERNARY_FALSE = 0,
+ TERNARY_TRUE = 1,
+ TERNARY_UNSET = -1
+} ternary;
+
/* ----------------------------------------------------------------
* Section 3: standard system types
diff --git a/src/include/utils/rel.h b/src/include/utils/rel.h
index b552359915..7346d618f9 100644
--- a/src/include/utils/rel.h
+++ b/src/include/utils/rel.h
@@ -346,8 +346,7 @@ typedef struct StdRdOptions
bool user_catalog_table; /* use as an additional catalog relation */
int parallel_workers; /* max number of parallel workers */
StdRdOptIndexCleanup vacuum_index_cleanup; /* controls index vacuuming */
- bool vacuum_truncate; /* enables vacuum to truncate a relation */
- bool vacuum_truncate_set; /* whether vacuum_truncate is set */
+ ternary vacuum_truncate; /* enables vacuum to truncate a relation */
/*
* Fraction of pages in a relation that vacuum can eagerly scan and fail
--
2.39.2
--nextPart4155600.3ZeAukHxDK
Content-Disposition: attachment;
filename="v2a-0003-Add-alias-to-be-used-as-unset-state.patch"
Content-Transfer-Encoding: 7Bit
Content-Type: text/x-patch; charset="unicode-2-0-utf-8";
name="v2a-0003-Add-alias-to-be-used-as-unset-state.patch"
^ permalink raw reply [nested|flat] 868+ messages in thread
* [PATCH v2 2/4] Introduce ternary reloptions
@ 2025-09-04 14:41 Nikolay Shaplov <[email protected]>
0 siblings, 0 replies; 868+ messages in thread
From: Nikolay Shaplov @ 2025-09-04 14:41 UTC (permalink / raw)
Introduce ternary reloption as a replacement for current `vacuum_truncate`
implementation. Remove `vacuum_truncate_set` additional flag and using
`TERNARY_UNSET` value instead.
---
src/backend/access/common/reloptions.c | 129 ++++++++++++++++++++-----
src/backend/commands/vacuum.c | 4 +-
src/include/access/reloptions.h | 26 ++---
src/include/c.h | 16 +++
src/include/utils/rel.h | 3 +-
5 files changed, 135 insertions(+), 43 deletions(-)
diff --git a/src/backend/access/common/reloptions.c b/src/backend/access/common/reloptions.c
index 0af3fea68f..f543f11001 100644
--- a/src/backend/access/common/reloptions.c
+++ b/src/backend/access/common/reloptions.c
@@ -40,9 +40,9 @@
*
* To add an option:
*
- * (i) decide on a type (bool, integer, real, enum, string), name, default
- * value, upper and lower bounds (if applicable); for strings, consider a
- * validation routine.
+ * (i) decide on a type (bool, ternary, integer, real, enum, string), name,
+ * default value, upper and lower bounds (if applicable); for strings,
+ * consider a validation routine.
* (ii) add a record below (or use add_<type>_reloption).
* (iii) add it to the appropriate options struct (perhaps StdRdOptions)
* (iv) add it to the appropriate handling routine (perhaps
@@ -147,15 +147,6 @@ static relopt_bool boolRelOpts[] =
},
false
},
- {
- {
- "vacuum_truncate",
- "Enables vacuum to truncate empty pages at the end of this table",
- RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
- ShareUpdateExclusiveLock
- },
- true
- },
{
{
"deduplicate_items",
@@ -170,6 +161,21 @@ static relopt_bool boolRelOpts[] =
{{NULL}}
};
+static relopt_ternary ternaryRelOpts[] =
+{
+ {
+ {
+ "vacuum_truncate",
+ "Enables vacuum to truncate empty pages at the end of this table",
+ RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
+ ShareUpdateExclusiveLock
+ },
+ TERNARY_UNSET
+ },
+ /* list terminator */
+ {{NULL}}
+};
+
static relopt_int intRelOpts[] =
{
{
@@ -600,6 +606,13 @@ initialize_reloptions(void)
boolRelOpts[i].gen.lockmode));
j++;
}
+ for (i = 0; ternaryRelOpts[i].gen.name; i++)
+ {
+ Assert(DoLockModesConflict(ternaryRelOpts[i].gen.lockmode,
+ ternaryRelOpts[i].gen.lockmode));
+ j++;
+ }
+
for (i = 0; intRelOpts[i].gen.name; i++)
{
Assert(DoLockModesConflict(intRelOpts[i].gen.lockmode,
@@ -640,6 +653,14 @@ initialize_reloptions(void)
j++;
}
+ for (i = 0; ternaryRelOpts[i].gen.name; i++)
+ {
+ relOpts[j] = &ternaryRelOpts[i].gen;
+ relOpts[j]->type = RELOPT_TYPE_TERNARY;
+ relOpts[j]->namelen = strlen(relOpts[j]->name);
+ j++;
+ }
+
for (i = 0; intRelOpts[i].gen.name; i++)
{
relOpts[j] = &intRelOpts[i].gen;
@@ -800,6 +821,9 @@ allocate_reloption(bits32 kinds, int type, const char *name, const char *desc,
case RELOPT_TYPE_BOOL:
size = sizeof(relopt_bool);
break;
+ case RELOPT_TYPE_TERNARY:
+ size = sizeof(relopt_ternary);
+ break;
case RELOPT_TYPE_INT:
size = sizeof(relopt_int);
break;
@@ -883,6 +907,54 @@ add_local_bool_reloption(local_relopts *relopts, const char *name,
add_local_reloption(relopts, (relopt_gen *) newoption, offset);
}
+/*
+ * init_ternary_reloption
+ * Allocate and initialize a new ternary reloption
+ */
+static relopt_ternary *
+init_ternary_reloption(bits32 kinds, const char *name, const char *desc,
+ ternary default_val, LOCKMODE lockmode)
+{
+ relopt_ternary *newoption;
+
+ newoption = (relopt_ternary *) allocate_reloption(kinds,
+ RELOPT_TYPE_TERNARY, name, desc, lockmode);
+ newoption->default_val = default_val;
+
+ return newoption;
+}
+
+/*
+ * add_ternary_reloption
+ * Add a new ternary reloption
+ */
+void
+add_ternary_reloption(bits32 kinds, const char *name, const char *desc,
+ ternary default_val, LOCKMODE lockmode)
+{
+ relopt_ternary *newoption = init_ternary_reloption(kinds, name, desc,
+ default_val, lockmode);
+
+ add_reloption((relopt_gen *) newoption);
+}
+
+/*
+ * add_local_ternary_reloption
+ * Add a new ternary local reloption
+ *
+ * 'offset' is offset of ternary-typed field.
+ */
+void
+add_local_ternary_reloption(local_relopts *relopts, const char *name,
+ const char *desc, ternary default_val,
+ int offset)
+{
+ relopt_ternary *newoption = init_ternary_reloption(RELOPT_KIND_LOCAL,
+ name, desc,
+ default_val, 0);
+
+ add_local_reloption(relopts, (relopt_gen *) newoption, offset);
+}
/*
* init_real_reloption
@@ -1617,6 +1689,18 @@ parse_one_reloption(relopt_value *option, char *text_str, int text_len,
option->gen->name, value)));
}
break;
+ case RELOPT_TYPE_TERNARY:
+ {
+ bool b;
+ parsed = parse_bool(value, &b);
+ option->values.ternary_val = b ? TERNARY_TRUE : TERNARY_FALSE;
+ if (validate && !parsed)
+ ereport(ERROR,
+ (errcode(ERRCODE_INVALID_PARAMETER_VALUE),
+ errmsg("invalid value for ternary option \"%s\": %s",
+ option->gen->name, value)));
+ }
+ break;
case RELOPT_TYPE_INT:
{
relopt_int *optint = (relopt_int *) option->gen;
@@ -1780,17 +1864,6 @@ fillRelOptions(void *rdopts, Size basesize,
char *itempos = ((char *) rdopts) + elems[j].offset;
char *string_val;
- /*
- * If isset_offset is provided, store whether the reloption is
- * set there.
- */
- if (elems[j].isset_offset > 0)
- {
- char *setpos = ((char *) rdopts) + elems[j].isset_offset;
-
- *(bool *) setpos = options[i].isset;
- }
-
switch (options[i].gen->type)
{
case RELOPT_TYPE_BOOL:
@@ -1798,6 +1871,11 @@ fillRelOptions(void *rdopts, Size basesize,
options[i].values.bool_val :
((relopt_bool *) options[i].gen)->default_val;
break;
+ case RELOPT_TYPE_TERNARY:
+ *(ternary *) itempos = options[i].isset ?
+ options[i].values.ternary_val :
+ ((relopt_ternary *) options[i].gen)->default_val;
+ break;
case RELOPT_TYPE_INT:
*(int *) itempos = options[i].isset ?
options[i].values.int_val :
@@ -1912,8 +1990,8 @@ default_reloptions(Datum reloptions, bool validate, relopt_kind kind)
offsetof(StdRdOptions, parallel_workers)},
{"vacuum_index_cleanup", RELOPT_TYPE_ENUM,
offsetof(StdRdOptions, vacuum_index_cleanup)},
- {"vacuum_truncate", RELOPT_TYPE_BOOL,
- offsetof(StdRdOptions, vacuum_truncate), offsetof(StdRdOptions, vacuum_truncate_set)},
+ {"vacuum_truncate", RELOPT_TYPE_TERNARY,
+ offsetof(StdRdOptions, vacuum_truncate)},
{"vacuum_max_eager_freeze_failure_rate", RELOPT_TYPE_REAL,
offsetof(StdRdOptions, vacuum_max_eager_freeze_failure_rate)}
};
@@ -1993,7 +2071,6 @@ build_local_reloptions(local_relopts *relopts, Datum options, bool validate)
elems[i].optname = opt->option->name;
elems[i].opttype = opt->option->type;
elems[i].offset = opt->offset;
- elems[i].isset_offset = 0; /* not supported for local relopts yet */
i++;
}
diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c
index 733ef40ae7..977babff54 100644
--- a/src/backend/commands/vacuum.c
+++ b/src/backend/commands/vacuum.c
@@ -2221,9 +2221,9 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams params,
{
StdRdOptions *opts = (StdRdOptions *) rel->rd_options;
- if (opts && opts->vacuum_truncate_set)
+ if (opts && opts->vacuum_truncate != TERNARY_UNSET)
{
- if (opts->vacuum_truncate)
+ if (opts->vacuum_truncate == TERNARY_TRUE)
params.truncate = VACOPTVALUE_ENABLED;
else
params.truncate = VACOPTVALUE_DISABLED;
diff --git a/src/include/access/reloptions.h b/src/include/access/reloptions.h
index a604a4702c..a436697658 100644
--- a/src/include/access/reloptions.h
+++ b/src/include/access/reloptions.h
@@ -29,6 +29,7 @@
typedef enum relopt_type
{
RELOPT_TYPE_BOOL,
+ RELOPT_TYPE_TERNARY, /* on, off, unset */
RELOPT_TYPE_INT,
RELOPT_TYPE_REAL,
RELOPT_TYPE_ENUM,
@@ -80,6 +81,7 @@ typedef struct relopt_value
union
{
bool bool_val;
+ ternary ternary_val;
int int_val;
double real_val;
int enum_val;
@@ -94,6 +96,12 @@ typedef struct relopt_bool
bool default_val;
} relopt_bool;
+typedef struct relopt_rernary
+{
+ relopt_gen gen;
+ int default_val;
+} relopt_ternary;
+
typedef struct relopt_int
{
relopt_gen gen;
@@ -152,19 +160,6 @@ typedef struct
const char *optname; /* option's name */
relopt_type opttype; /* option's datatype */
int offset; /* offset of field in result struct */
-
- /*
- * isset_offset is an optional offset of a field in the result struct that
- * stores whether the option is explicitly set for the relation or if it
- * just picked up the default value. In most cases, this can be
- * accomplished by giving the reloption a special out-of-range default
- * value (e.g., some integer reloptions use -2), but this isn't always
- * possible. For example, a Boolean reloption cannot be given an
- * out-of-range default, so we need another way to discover the source of
- * its value. This offset is only used if given a value greater than
- * zero.
- */
- int isset_offset;
} relopt_parse_elt;
/* Local reloption definition */
@@ -195,6 +190,8 @@ typedef struct local_relopts
extern relopt_kind add_reloption_kind(void);
extern void add_bool_reloption(bits32 kinds, const char *name, const char *desc,
bool default_val, LOCKMODE lockmode);
+extern void add_ternary_reloption(bits32 kinds, const char *name,
+ const char *desc, int default_val, LOCKMODE lockmode);
extern void add_int_reloption(bits32 kinds, const char *name, const char *desc,
int default_val, int min_val, int max_val,
LOCKMODE lockmode);
@@ -214,6 +211,9 @@ extern void register_reloptions_validator(local_relopts *relopts,
extern void add_local_bool_reloption(local_relopts *relopts, const char *name,
const char *desc, bool default_val,
int offset);
+extern void add_local_ternary_reloption(local_relopts *relopts,
+ const char *name, const char *desc,
+ ternary default_val, int offset);
extern void add_local_int_reloption(local_relopts *relopts, const char *name,
const char *desc, int default_val,
int min_val, int max_val, int offset);
diff --git a/src/include/c.h b/src/include/c.h
index 39022f8a9d..9c59ccccf8 100644
--- a/src/include/c.h
+++ b/src/include/c.h
@@ -488,6 +488,22 @@ typedef void (*pg_funcptr_t) (void);
#include <stdbool.h>
+/*
+ * ternary
+ * Boolean value with an extrea "unset" option
+ *
+ * Ternary data type is used in relation options that can be "true", "false" or
+ * "unset". Since relation options are used deep inside the PostgreSQL code,
+ * this type is declared globally.
+*/
+
+typedef enum ternary
+{
+ TERNARY_FALSE = 0,
+ TERNARY_TRUE = 1,
+ TERNARY_UNSET = -1
+} ternary;
+
/* ----------------------------------------------------------------
* Section 3: standard system types
diff --git a/src/include/utils/rel.h b/src/include/utils/rel.h
index b552359915..7346d618f9 100644
--- a/src/include/utils/rel.h
+++ b/src/include/utils/rel.h
@@ -346,8 +346,7 @@ typedef struct StdRdOptions
bool user_catalog_table; /* use as an additional catalog relation */
int parallel_workers; /* max number of parallel workers */
StdRdOptIndexCleanup vacuum_index_cleanup; /* controls index vacuuming */
- bool vacuum_truncate; /* enables vacuum to truncate a relation */
- bool vacuum_truncate_set; /* whether vacuum_truncate is set */
+ ternary vacuum_truncate; /* enables vacuum to truncate a relation */
/*
* Fraction of pages in a relation that vacuum can eagerly scan and fail
--
2.39.2
--nextPart5129932.5fSG56mABF
Content-Disposition: attachment;
filename="v2-0003-Add-alias-to-be-used-as-unset-state.patch"
Content-Transfer-Encoding: 7Bit
Content-Type: text/x-patch; charset="unicode-2-0-utf-8";
name="v2-0003-Add-alias-to-be-used-as-unset-state.patch"
^ permalink raw reply [nested|flat] 868+ messages in thread
* [PATCH v2a 2/4] Introduce ternary reloptions
@ 2025-09-04 14:41 Nikolay Shaplov <[email protected]>
0 siblings, 0 replies; 868+ messages in thread
From: Nikolay Shaplov @ 2025-09-04 14:41 UTC (permalink / raw)
Introduce ternary reloption as a replacement for current `vacuum_truncate`
implementation. Remove `vacuum_truncate_set` additional flag and using
`TERNARY_UNSET` value instead.
---
src/backend/access/common/reloptions.c | 129 ++++++++++++++++++++-----
src/backend/commands/vacuum.c | 4 +-
src/include/access/reloptions.h | 26 ++---
src/include/c.h | 16 +++
src/include/utils/rel.h | 3 +-
5 files changed, 135 insertions(+), 43 deletions(-)
diff --git a/src/backend/access/common/reloptions.c b/src/backend/access/common/reloptions.c
index 0af3fea68f..f543f11001 100644
--- a/src/backend/access/common/reloptions.c
+++ b/src/backend/access/common/reloptions.c
@@ -40,9 +40,9 @@
*
* To add an option:
*
- * (i) decide on a type (bool, integer, real, enum, string), name, default
- * value, upper and lower bounds (if applicable); for strings, consider a
- * validation routine.
+ * (i) decide on a type (bool, ternary, integer, real, enum, string), name,
+ * default value, upper and lower bounds (if applicable); for strings,
+ * consider a validation routine.
* (ii) add a record below (or use add_<type>_reloption).
* (iii) add it to the appropriate options struct (perhaps StdRdOptions)
* (iv) add it to the appropriate handling routine (perhaps
@@ -147,15 +147,6 @@ static relopt_bool boolRelOpts[] =
},
false
},
- {
- {
- "vacuum_truncate",
- "Enables vacuum to truncate empty pages at the end of this table",
- RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
- ShareUpdateExclusiveLock
- },
- true
- },
{
{
"deduplicate_items",
@@ -170,6 +161,21 @@ static relopt_bool boolRelOpts[] =
{{NULL}}
};
+static relopt_ternary ternaryRelOpts[] =
+{
+ {
+ {
+ "vacuum_truncate",
+ "Enables vacuum to truncate empty pages at the end of this table",
+ RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
+ ShareUpdateExclusiveLock
+ },
+ TERNARY_UNSET
+ },
+ /* list terminator */
+ {{NULL}}
+};
+
static relopt_int intRelOpts[] =
{
{
@@ -600,6 +606,13 @@ initialize_reloptions(void)
boolRelOpts[i].gen.lockmode));
j++;
}
+ for (i = 0; ternaryRelOpts[i].gen.name; i++)
+ {
+ Assert(DoLockModesConflict(ternaryRelOpts[i].gen.lockmode,
+ ternaryRelOpts[i].gen.lockmode));
+ j++;
+ }
+
for (i = 0; intRelOpts[i].gen.name; i++)
{
Assert(DoLockModesConflict(intRelOpts[i].gen.lockmode,
@@ -640,6 +653,14 @@ initialize_reloptions(void)
j++;
}
+ for (i = 0; ternaryRelOpts[i].gen.name; i++)
+ {
+ relOpts[j] = &ternaryRelOpts[i].gen;
+ relOpts[j]->type = RELOPT_TYPE_TERNARY;
+ relOpts[j]->namelen = strlen(relOpts[j]->name);
+ j++;
+ }
+
for (i = 0; intRelOpts[i].gen.name; i++)
{
relOpts[j] = &intRelOpts[i].gen;
@@ -800,6 +821,9 @@ allocate_reloption(bits32 kinds, int type, const char *name, const char *desc,
case RELOPT_TYPE_BOOL:
size = sizeof(relopt_bool);
break;
+ case RELOPT_TYPE_TERNARY:
+ size = sizeof(relopt_ternary);
+ break;
case RELOPT_TYPE_INT:
size = sizeof(relopt_int);
break;
@@ -883,6 +907,54 @@ add_local_bool_reloption(local_relopts *relopts, const char *name,
add_local_reloption(relopts, (relopt_gen *) newoption, offset);
}
+/*
+ * init_ternary_reloption
+ * Allocate and initialize a new ternary reloption
+ */
+static relopt_ternary *
+init_ternary_reloption(bits32 kinds, const char *name, const char *desc,
+ ternary default_val, LOCKMODE lockmode)
+{
+ relopt_ternary *newoption;
+
+ newoption = (relopt_ternary *) allocate_reloption(kinds,
+ RELOPT_TYPE_TERNARY, name, desc, lockmode);
+ newoption->default_val = default_val;
+
+ return newoption;
+}
+
+/*
+ * add_ternary_reloption
+ * Add a new ternary reloption
+ */
+void
+add_ternary_reloption(bits32 kinds, const char *name, const char *desc,
+ ternary default_val, LOCKMODE lockmode)
+{
+ relopt_ternary *newoption = init_ternary_reloption(kinds, name, desc,
+ default_val, lockmode);
+
+ add_reloption((relopt_gen *) newoption);
+}
+
+/*
+ * add_local_ternary_reloption
+ * Add a new ternary local reloption
+ *
+ * 'offset' is offset of ternary-typed field.
+ */
+void
+add_local_ternary_reloption(local_relopts *relopts, const char *name,
+ const char *desc, ternary default_val,
+ int offset)
+{
+ relopt_ternary *newoption = init_ternary_reloption(RELOPT_KIND_LOCAL,
+ name, desc,
+ default_val, 0);
+
+ add_local_reloption(relopts, (relopt_gen *) newoption, offset);
+}
/*
* init_real_reloption
@@ -1617,6 +1689,18 @@ parse_one_reloption(relopt_value *option, char *text_str, int text_len,
option->gen->name, value)));
}
break;
+ case RELOPT_TYPE_TERNARY:
+ {
+ bool b;
+ parsed = parse_bool(value, &b);
+ option->values.ternary_val = b ? TERNARY_TRUE : TERNARY_FALSE;
+ if (validate && !parsed)
+ ereport(ERROR,
+ (errcode(ERRCODE_INVALID_PARAMETER_VALUE),
+ errmsg("invalid value for ternary option \"%s\": %s",
+ option->gen->name, value)));
+ }
+ break;
case RELOPT_TYPE_INT:
{
relopt_int *optint = (relopt_int *) option->gen;
@@ -1780,17 +1864,6 @@ fillRelOptions(void *rdopts, Size basesize,
char *itempos = ((char *) rdopts) + elems[j].offset;
char *string_val;
- /*
- * If isset_offset is provided, store whether the reloption is
- * set there.
- */
- if (elems[j].isset_offset > 0)
- {
- char *setpos = ((char *) rdopts) + elems[j].isset_offset;
-
- *(bool *) setpos = options[i].isset;
- }
-
switch (options[i].gen->type)
{
case RELOPT_TYPE_BOOL:
@@ -1798,6 +1871,11 @@ fillRelOptions(void *rdopts, Size basesize,
options[i].values.bool_val :
((relopt_bool *) options[i].gen)->default_val;
break;
+ case RELOPT_TYPE_TERNARY:
+ *(ternary *) itempos = options[i].isset ?
+ options[i].values.ternary_val :
+ ((relopt_ternary *) options[i].gen)->default_val;
+ break;
case RELOPT_TYPE_INT:
*(int *) itempos = options[i].isset ?
options[i].values.int_val :
@@ -1912,8 +1990,8 @@ default_reloptions(Datum reloptions, bool validate, relopt_kind kind)
offsetof(StdRdOptions, parallel_workers)},
{"vacuum_index_cleanup", RELOPT_TYPE_ENUM,
offsetof(StdRdOptions, vacuum_index_cleanup)},
- {"vacuum_truncate", RELOPT_TYPE_BOOL,
- offsetof(StdRdOptions, vacuum_truncate), offsetof(StdRdOptions, vacuum_truncate_set)},
+ {"vacuum_truncate", RELOPT_TYPE_TERNARY,
+ offsetof(StdRdOptions, vacuum_truncate)},
{"vacuum_max_eager_freeze_failure_rate", RELOPT_TYPE_REAL,
offsetof(StdRdOptions, vacuum_max_eager_freeze_failure_rate)}
};
@@ -1993,7 +2071,6 @@ build_local_reloptions(local_relopts *relopts, Datum options, bool validate)
elems[i].optname = opt->option->name;
elems[i].opttype = opt->option->type;
elems[i].offset = opt->offset;
- elems[i].isset_offset = 0; /* not supported for local relopts yet */
i++;
}
diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c
index 733ef40ae7..977babff54 100644
--- a/src/backend/commands/vacuum.c
+++ b/src/backend/commands/vacuum.c
@@ -2221,9 +2221,9 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams params,
{
StdRdOptions *opts = (StdRdOptions *) rel->rd_options;
- if (opts && opts->vacuum_truncate_set)
+ if (opts && opts->vacuum_truncate != TERNARY_UNSET)
{
- if (opts->vacuum_truncate)
+ if (opts->vacuum_truncate == TERNARY_TRUE)
params.truncate = VACOPTVALUE_ENABLED;
else
params.truncate = VACOPTVALUE_DISABLED;
diff --git a/src/include/access/reloptions.h b/src/include/access/reloptions.h
index a604a4702c..a436697658 100644
--- a/src/include/access/reloptions.h
+++ b/src/include/access/reloptions.h
@@ -29,6 +29,7 @@
typedef enum relopt_type
{
RELOPT_TYPE_BOOL,
+ RELOPT_TYPE_TERNARY, /* on, off, unset */
RELOPT_TYPE_INT,
RELOPT_TYPE_REAL,
RELOPT_TYPE_ENUM,
@@ -80,6 +81,7 @@ typedef struct relopt_value
union
{
bool bool_val;
+ ternary ternary_val;
int int_val;
double real_val;
int enum_val;
@@ -94,6 +96,12 @@ typedef struct relopt_bool
bool default_val;
} relopt_bool;
+typedef struct relopt_rernary
+{
+ relopt_gen gen;
+ int default_val;
+} relopt_ternary;
+
typedef struct relopt_int
{
relopt_gen gen;
@@ -152,19 +160,6 @@ typedef struct
const char *optname; /* option's name */
relopt_type opttype; /* option's datatype */
int offset; /* offset of field in result struct */
-
- /*
- * isset_offset is an optional offset of a field in the result struct that
- * stores whether the option is explicitly set for the relation or if it
- * just picked up the default value. In most cases, this can be
- * accomplished by giving the reloption a special out-of-range default
- * value (e.g., some integer reloptions use -2), but this isn't always
- * possible. For example, a Boolean reloption cannot be given an
- * out-of-range default, so we need another way to discover the source of
- * its value. This offset is only used if given a value greater than
- * zero.
- */
- int isset_offset;
} relopt_parse_elt;
/* Local reloption definition */
@@ -195,6 +190,8 @@ typedef struct local_relopts
extern relopt_kind add_reloption_kind(void);
extern void add_bool_reloption(bits32 kinds, const char *name, const char *desc,
bool default_val, LOCKMODE lockmode);
+extern void add_ternary_reloption(bits32 kinds, const char *name,
+ const char *desc, int default_val, LOCKMODE lockmode);
extern void add_int_reloption(bits32 kinds, const char *name, const char *desc,
int default_val, int min_val, int max_val,
LOCKMODE lockmode);
@@ -214,6 +211,9 @@ extern void register_reloptions_validator(local_relopts *relopts,
extern void add_local_bool_reloption(local_relopts *relopts, const char *name,
const char *desc, bool default_val,
int offset);
+extern void add_local_ternary_reloption(local_relopts *relopts,
+ const char *name, const char *desc,
+ ternary default_val, int offset);
extern void add_local_int_reloption(local_relopts *relopts, const char *name,
const char *desc, int default_val,
int min_val, int max_val, int offset);
diff --git a/src/include/c.h b/src/include/c.h
index 39022f8a9d..9c59ccccf8 100644
--- a/src/include/c.h
+++ b/src/include/c.h
@@ -488,6 +488,22 @@ typedef void (*pg_funcptr_t) (void);
#include <stdbool.h>
+/*
+ * ternary
+ * Boolean value with an extrea "unset" option
+ *
+ * Ternary data type is used in relation options that can be "true", "false" or
+ * "unset". Since relation options are used deep inside the PostgreSQL code,
+ * this type is declared globally.
+*/
+
+typedef enum ternary
+{
+ TERNARY_FALSE = 0,
+ TERNARY_TRUE = 1,
+ TERNARY_UNSET = -1
+} ternary;
+
/* ----------------------------------------------------------------
* Section 3: standard system types
diff --git a/src/include/utils/rel.h b/src/include/utils/rel.h
index b552359915..7346d618f9 100644
--- a/src/include/utils/rel.h
+++ b/src/include/utils/rel.h
@@ -346,8 +346,7 @@ typedef struct StdRdOptions
bool user_catalog_table; /* use as an additional catalog relation */
int parallel_workers; /* max number of parallel workers */
StdRdOptIndexCleanup vacuum_index_cleanup; /* controls index vacuuming */
- bool vacuum_truncate; /* enables vacuum to truncate a relation */
- bool vacuum_truncate_set; /* whether vacuum_truncate is set */
+ ternary vacuum_truncate; /* enables vacuum to truncate a relation */
/*
* Fraction of pages in a relation that vacuum can eagerly scan and fail
--
2.39.2
--nextPart4155600.3ZeAukHxDK
Content-Disposition: attachment;
filename="v2a-0003-Add-alias-to-be-used-as-unset-state.patch"
Content-Transfer-Encoding: 7Bit
Content-Type: text/x-patch; charset="unicode-2-0-utf-8";
name="v2a-0003-Add-alias-to-be-used-as-unset-state.patch"
^ permalink raw reply [nested|flat] 868+ messages in thread
* [PATCH v2 2/4] Introduce ternary reloptions
@ 2025-09-04 14:41 Nikolay Shaplov <[email protected]>
0 siblings, 0 replies; 868+ messages in thread
From: Nikolay Shaplov @ 2025-09-04 14:41 UTC (permalink / raw)
Introduce ternary reloption as a replacement for current `vacuum_truncate`
implementation. Remove `vacuum_truncate_set` additional flag and using
`TERNARY_UNSET` value instead.
---
src/backend/access/common/reloptions.c | 129 ++++++++++++++++++++-----
src/backend/commands/vacuum.c | 4 +-
src/include/access/reloptions.h | 26 ++---
src/include/c.h | 16 +++
src/include/utils/rel.h | 3 +-
5 files changed, 135 insertions(+), 43 deletions(-)
diff --git a/src/backend/access/common/reloptions.c b/src/backend/access/common/reloptions.c
index 0af3fea68f..f543f11001 100644
--- a/src/backend/access/common/reloptions.c
+++ b/src/backend/access/common/reloptions.c
@@ -40,9 +40,9 @@
*
* To add an option:
*
- * (i) decide on a type (bool, integer, real, enum, string), name, default
- * value, upper and lower bounds (if applicable); for strings, consider a
- * validation routine.
+ * (i) decide on a type (bool, ternary, integer, real, enum, string), name,
+ * default value, upper and lower bounds (if applicable); for strings,
+ * consider a validation routine.
* (ii) add a record below (or use add_<type>_reloption).
* (iii) add it to the appropriate options struct (perhaps StdRdOptions)
* (iv) add it to the appropriate handling routine (perhaps
@@ -147,15 +147,6 @@ static relopt_bool boolRelOpts[] =
},
false
},
- {
- {
- "vacuum_truncate",
- "Enables vacuum to truncate empty pages at the end of this table",
- RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
- ShareUpdateExclusiveLock
- },
- true
- },
{
{
"deduplicate_items",
@@ -170,6 +161,21 @@ static relopt_bool boolRelOpts[] =
{{NULL}}
};
+static relopt_ternary ternaryRelOpts[] =
+{
+ {
+ {
+ "vacuum_truncate",
+ "Enables vacuum to truncate empty pages at the end of this table",
+ RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
+ ShareUpdateExclusiveLock
+ },
+ TERNARY_UNSET
+ },
+ /* list terminator */
+ {{NULL}}
+};
+
static relopt_int intRelOpts[] =
{
{
@@ -600,6 +606,13 @@ initialize_reloptions(void)
boolRelOpts[i].gen.lockmode));
j++;
}
+ for (i = 0; ternaryRelOpts[i].gen.name; i++)
+ {
+ Assert(DoLockModesConflict(ternaryRelOpts[i].gen.lockmode,
+ ternaryRelOpts[i].gen.lockmode));
+ j++;
+ }
+
for (i = 0; intRelOpts[i].gen.name; i++)
{
Assert(DoLockModesConflict(intRelOpts[i].gen.lockmode,
@@ -640,6 +653,14 @@ initialize_reloptions(void)
j++;
}
+ for (i = 0; ternaryRelOpts[i].gen.name; i++)
+ {
+ relOpts[j] = &ternaryRelOpts[i].gen;
+ relOpts[j]->type = RELOPT_TYPE_TERNARY;
+ relOpts[j]->namelen = strlen(relOpts[j]->name);
+ j++;
+ }
+
for (i = 0; intRelOpts[i].gen.name; i++)
{
relOpts[j] = &intRelOpts[i].gen;
@@ -800,6 +821,9 @@ allocate_reloption(bits32 kinds, int type, const char *name, const char *desc,
case RELOPT_TYPE_BOOL:
size = sizeof(relopt_bool);
break;
+ case RELOPT_TYPE_TERNARY:
+ size = sizeof(relopt_ternary);
+ break;
case RELOPT_TYPE_INT:
size = sizeof(relopt_int);
break;
@@ -883,6 +907,54 @@ add_local_bool_reloption(local_relopts *relopts, const char *name,
add_local_reloption(relopts, (relopt_gen *) newoption, offset);
}
+/*
+ * init_ternary_reloption
+ * Allocate and initialize a new ternary reloption
+ */
+static relopt_ternary *
+init_ternary_reloption(bits32 kinds, const char *name, const char *desc,
+ ternary default_val, LOCKMODE lockmode)
+{
+ relopt_ternary *newoption;
+
+ newoption = (relopt_ternary *) allocate_reloption(kinds,
+ RELOPT_TYPE_TERNARY, name, desc, lockmode);
+ newoption->default_val = default_val;
+
+ return newoption;
+}
+
+/*
+ * add_ternary_reloption
+ * Add a new ternary reloption
+ */
+void
+add_ternary_reloption(bits32 kinds, const char *name, const char *desc,
+ ternary default_val, LOCKMODE lockmode)
+{
+ relopt_ternary *newoption = init_ternary_reloption(kinds, name, desc,
+ default_val, lockmode);
+
+ add_reloption((relopt_gen *) newoption);
+}
+
+/*
+ * add_local_ternary_reloption
+ * Add a new ternary local reloption
+ *
+ * 'offset' is offset of ternary-typed field.
+ */
+void
+add_local_ternary_reloption(local_relopts *relopts, const char *name,
+ const char *desc, ternary default_val,
+ int offset)
+{
+ relopt_ternary *newoption = init_ternary_reloption(RELOPT_KIND_LOCAL,
+ name, desc,
+ default_val, 0);
+
+ add_local_reloption(relopts, (relopt_gen *) newoption, offset);
+}
/*
* init_real_reloption
@@ -1617,6 +1689,18 @@ parse_one_reloption(relopt_value *option, char *text_str, int text_len,
option->gen->name, value)));
}
break;
+ case RELOPT_TYPE_TERNARY:
+ {
+ bool b;
+ parsed = parse_bool(value, &b);
+ option->values.ternary_val = b ? TERNARY_TRUE : TERNARY_FALSE;
+ if (validate && !parsed)
+ ereport(ERROR,
+ (errcode(ERRCODE_INVALID_PARAMETER_VALUE),
+ errmsg("invalid value for ternary option \"%s\": %s",
+ option->gen->name, value)));
+ }
+ break;
case RELOPT_TYPE_INT:
{
relopt_int *optint = (relopt_int *) option->gen;
@@ -1780,17 +1864,6 @@ fillRelOptions(void *rdopts, Size basesize,
char *itempos = ((char *) rdopts) + elems[j].offset;
char *string_val;
- /*
- * If isset_offset is provided, store whether the reloption is
- * set there.
- */
- if (elems[j].isset_offset > 0)
- {
- char *setpos = ((char *) rdopts) + elems[j].isset_offset;
-
- *(bool *) setpos = options[i].isset;
- }
-
switch (options[i].gen->type)
{
case RELOPT_TYPE_BOOL:
@@ -1798,6 +1871,11 @@ fillRelOptions(void *rdopts, Size basesize,
options[i].values.bool_val :
((relopt_bool *) options[i].gen)->default_val;
break;
+ case RELOPT_TYPE_TERNARY:
+ *(ternary *) itempos = options[i].isset ?
+ options[i].values.ternary_val :
+ ((relopt_ternary *) options[i].gen)->default_val;
+ break;
case RELOPT_TYPE_INT:
*(int *) itempos = options[i].isset ?
options[i].values.int_val :
@@ -1912,8 +1990,8 @@ default_reloptions(Datum reloptions, bool validate, relopt_kind kind)
offsetof(StdRdOptions, parallel_workers)},
{"vacuum_index_cleanup", RELOPT_TYPE_ENUM,
offsetof(StdRdOptions, vacuum_index_cleanup)},
- {"vacuum_truncate", RELOPT_TYPE_BOOL,
- offsetof(StdRdOptions, vacuum_truncate), offsetof(StdRdOptions, vacuum_truncate_set)},
+ {"vacuum_truncate", RELOPT_TYPE_TERNARY,
+ offsetof(StdRdOptions, vacuum_truncate)},
{"vacuum_max_eager_freeze_failure_rate", RELOPT_TYPE_REAL,
offsetof(StdRdOptions, vacuum_max_eager_freeze_failure_rate)}
};
@@ -1993,7 +2071,6 @@ build_local_reloptions(local_relopts *relopts, Datum options, bool validate)
elems[i].optname = opt->option->name;
elems[i].opttype = opt->option->type;
elems[i].offset = opt->offset;
- elems[i].isset_offset = 0; /* not supported for local relopts yet */
i++;
}
diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c
index 733ef40ae7..977babff54 100644
--- a/src/backend/commands/vacuum.c
+++ b/src/backend/commands/vacuum.c
@@ -2221,9 +2221,9 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams params,
{
StdRdOptions *opts = (StdRdOptions *) rel->rd_options;
- if (opts && opts->vacuum_truncate_set)
+ if (opts && opts->vacuum_truncate != TERNARY_UNSET)
{
- if (opts->vacuum_truncate)
+ if (opts->vacuum_truncate == TERNARY_TRUE)
params.truncate = VACOPTVALUE_ENABLED;
else
params.truncate = VACOPTVALUE_DISABLED;
diff --git a/src/include/access/reloptions.h b/src/include/access/reloptions.h
index a604a4702c..a436697658 100644
--- a/src/include/access/reloptions.h
+++ b/src/include/access/reloptions.h
@@ -29,6 +29,7 @@
typedef enum relopt_type
{
RELOPT_TYPE_BOOL,
+ RELOPT_TYPE_TERNARY, /* on, off, unset */
RELOPT_TYPE_INT,
RELOPT_TYPE_REAL,
RELOPT_TYPE_ENUM,
@@ -80,6 +81,7 @@ typedef struct relopt_value
union
{
bool bool_val;
+ ternary ternary_val;
int int_val;
double real_val;
int enum_val;
@@ -94,6 +96,12 @@ typedef struct relopt_bool
bool default_val;
} relopt_bool;
+typedef struct relopt_rernary
+{
+ relopt_gen gen;
+ int default_val;
+} relopt_ternary;
+
typedef struct relopt_int
{
relopt_gen gen;
@@ -152,19 +160,6 @@ typedef struct
const char *optname; /* option's name */
relopt_type opttype; /* option's datatype */
int offset; /* offset of field in result struct */
-
- /*
- * isset_offset is an optional offset of a field in the result struct that
- * stores whether the option is explicitly set for the relation or if it
- * just picked up the default value. In most cases, this can be
- * accomplished by giving the reloption a special out-of-range default
- * value (e.g., some integer reloptions use -2), but this isn't always
- * possible. For example, a Boolean reloption cannot be given an
- * out-of-range default, so we need another way to discover the source of
- * its value. This offset is only used if given a value greater than
- * zero.
- */
- int isset_offset;
} relopt_parse_elt;
/* Local reloption definition */
@@ -195,6 +190,8 @@ typedef struct local_relopts
extern relopt_kind add_reloption_kind(void);
extern void add_bool_reloption(bits32 kinds, const char *name, const char *desc,
bool default_val, LOCKMODE lockmode);
+extern void add_ternary_reloption(bits32 kinds, const char *name,
+ const char *desc, int default_val, LOCKMODE lockmode);
extern void add_int_reloption(bits32 kinds, const char *name, const char *desc,
int default_val, int min_val, int max_val,
LOCKMODE lockmode);
@@ -214,6 +211,9 @@ extern void register_reloptions_validator(local_relopts *relopts,
extern void add_local_bool_reloption(local_relopts *relopts, const char *name,
const char *desc, bool default_val,
int offset);
+extern void add_local_ternary_reloption(local_relopts *relopts,
+ const char *name, const char *desc,
+ ternary default_val, int offset);
extern void add_local_int_reloption(local_relopts *relopts, const char *name,
const char *desc, int default_val,
int min_val, int max_val, int offset);
diff --git a/src/include/c.h b/src/include/c.h
index 39022f8a9d..9c59ccccf8 100644
--- a/src/include/c.h
+++ b/src/include/c.h
@@ -488,6 +488,22 @@ typedef void (*pg_funcptr_t) (void);
#include <stdbool.h>
+/*
+ * ternary
+ * Boolean value with an extrea "unset" option
+ *
+ * Ternary data type is used in relation options that can be "true", "false" or
+ * "unset". Since relation options are used deep inside the PostgreSQL code,
+ * this type is declared globally.
+*/
+
+typedef enum ternary
+{
+ TERNARY_FALSE = 0,
+ TERNARY_TRUE = 1,
+ TERNARY_UNSET = -1
+} ternary;
+
/* ----------------------------------------------------------------
* Section 3: standard system types
diff --git a/src/include/utils/rel.h b/src/include/utils/rel.h
index b552359915..7346d618f9 100644
--- a/src/include/utils/rel.h
+++ b/src/include/utils/rel.h
@@ -346,8 +346,7 @@ typedef struct StdRdOptions
bool user_catalog_table; /* use as an additional catalog relation */
int parallel_workers; /* max number of parallel workers */
StdRdOptIndexCleanup vacuum_index_cleanup; /* controls index vacuuming */
- bool vacuum_truncate; /* enables vacuum to truncate a relation */
- bool vacuum_truncate_set; /* whether vacuum_truncate is set */
+ ternary vacuum_truncate; /* enables vacuum to truncate a relation */
/*
* Fraction of pages in a relation that vacuum can eagerly scan and fail
--
2.39.2
--nextPart5129932.5fSG56mABF
Content-Disposition: attachment;
filename="v2-0003-Add-alias-to-be-used-as-unset-state.patch"
Content-Transfer-Encoding: 7Bit
Content-Type: text/x-patch; charset="unicode-2-0-utf-8";
name="v2-0003-Add-alias-to-be-used-as-unset-state.patch"
^ permalink raw reply [nested|flat] 868+ messages in thread
* [PATCH v2a 2/4] Introduce ternary reloptions
@ 2025-09-04 14:41 Nikolay Shaplov <[email protected]>
0 siblings, 0 replies; 868+ messages in thread
From: Nikolay Shaplov @ 2025-09-04 14:41 UTC (permalink / raw)
Introduce ternary reloption as a replacement for current `vacuum_truncate`
implementation. Remove `vacuum_truncate_set` additional flag and using
`TERNARY_UNSET` value instead.
---
src/backend/access/common/reloptions.c | 129 ++++++++++++++++++++-----
src/backend/commands/vacuum.c | 4 +-
src/include/access/reloptions.h | 26 ++---
src/include/c.h | 16 +++
src/include/utils/rel.h | 3 +-
5 files changed, 135 insertions(+), 43 deletions(-)
diff --git a/src/backend/access/common/reloptions.c b/src/backend/access/common/reloptions.c
index 0af3fea68f..f543f11001 100644
--- a/src/backend/access/common/reloptions.c
+++ b/src/backend/access/common/reloptions.c
@@ -40,9 +40,9 @@
*
* To add an option:
*
- * (i) decide on a type (bool, integer, real, enum, string), name, default
- * value, upper and lower bounds (if applicable); for strings, consider a
- * validation routine.
+ * (i) decide on a type (bool, ternary, integer, real, enum, string), name,
+ * default value, upper and lower bounds (if applicable); for strings,
+ * consider a validation routine.
* (ii) add a record below (or use add_<type>_reloption).
* (iii) add it to the appropriate options struct (perhaps StdRdOptions)
* (iv) add it to the appropriate handling routine (perhaps
@@ -147,15 +147,6 @@ static relopt_bool boolRelOpts[] =
},
false
},
- {
- {
- "vacuum_truncate",
- "Enables vacuum to truncate empty pages at the end of this table",
- RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
- ShareUpdateExclusiveLock
- },
- true
- },
{
{
"deduplicate_items",
@@ -170,6 +161,21 @@ static relopt_bool boolRelOpts[] =
{{NULL}}
};
+static relopt_ternary ternaryRelOpts[] =
+{
+ {
+ {
+ "vacuum_truncate",
+ "Enables vacuum to truncate empty pages at the end of this table",
+ RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
+ ShareUpdateExclusiveLock
+ },
+ TERNARY_UNSET
+ },
+ /* list terminator */
+ {{NULL}}
+};
+
static relopt_int intRelOpts[] =
{
{
@@ -600,6 +606,13 @@ initialize_reloptions(void)
boolRelOpts[i].gen.lockmode));
j++;
}
+ for (i = 0; ternaryRelOpts[i].gen.name; i++)
+ {
+ Assert(DoLockModesConflict(ternaryRelOpts[i].gen.lockmode,
+ ternaryRelOpts[i].gen.lockmode));
+ j++;
+ }
+
for (i = 0; intRelOpts[i].gen.name; i++)
{
Assert(DoLockModesConflict(intRelOpts[i].gen.lockmode,
@@ -640,6 +653,14 @@ initialize_reloptions(void)
j++;
}
+ for (i = 0; ternaryRelOpts[i].gen.name; i++)
+ {
+ relOpts[j] = &ternaryRelOpts[i].gen;
+ relOpts[j]->type = RELOPT_TYPE_TERNARY;
+ relOpts[j]->namelen = strlen(relOpts[j]->name);
+ j++;
+ }
+
for (i = 0; intRelOpts[i].gen.name; i++)
{
relOpts[j] = &intRelOpts[i].gen;
@@ -800,6 +821,9 @@ allocate_reloption(bits32 kinds, int type, const char *name, const char *desc,
case RELOPT_TYPE_BOOL:
size = sizeof(relopt_bool);
break;
+ case RELOPT_TYPE_TERNARY:
+ size = sizeof(relopt_ternary);
+ break;
case RELOPT_TYPE_INT:
size = sizeof(relopt_int);
break;
@@ -883,6 +907,54 @@ add_local_bool_reloption(local_relopts *relopts, const char *name,
add_local_reloption(relopts, (relopt_gen *) newoption, offset);
}
+/*
+ * init_ternary_reloption
+ * Allocate and initialize a new ternary reloption
+ */
+static relopt_ternary *
+init_ternary_reloption(bits32 kinds, const char *name, const char *desc,
+ ternary default_val, LOCKMODE lockmode)
+{
+ relopt_ternary *newoption;
+
+ newoption = (relopt_ternary *) allocate_reloption(kinds,
+ RELOPT_TYPE_TERNARY, name, desc, lockmode);
+ newoption->default_val = default_val;
+
+ return newoption;
+}
+
+/*
+ * add_ternary_reloption
+ * Add a new ternary reloption
+ */
+void
+add_ternary_reloption(bits32 kinds, const char *name, const char *desc,
+ ternary default_val, LOCKMODE lockmode)
+{
+ relopt_ternary *newoption = init_ternary_reloption(kinds, name, desc,
+ default_val, lockmode);
+
+ add_reloption((relopt_gen *) newoption);
+}
+
+/*
+ * add_local_ternary_reloption
+ * Add a new ternary local reloption
+ *
+ * 'offset' is offset of ternary-typed field.
+ */
+void
+add_local_ternary_reloption(local_relopts *relopts, const char *name,
+ const char *desc, ternary default_val,
+ int offset)
+{
+ relopt_ternary *newoption = init_ternary_reloption(RELOPT_KIND_LOCAL,
+ name, desc,
+ default_val, 0);
+
+ add_local_reloption(relopts, (relopt_gen *) newoption, offset);
+}
/*
* init_real_reloption
@@ -1617,6 +1689,18 @@ parse_one_reloption(relopt_value *option, char *text_str, int text_len,
option->gen->name, value)));
}
break;
+ case RELOPT_TYPE_TERNARY:
+ {
+ bool b;
+ parsed = parse_bool(value, &b);
+ option->values.ternary_val = b ? TERNARY_TRUE : TERNARY_FALSE;
+ if (validate && !parsed)
+ ereport(ERROR,
+ (errcode(ERRCODE_INVALID_PARAMETER_VALUE),
+ errmsg("invalid value for ternary option \"%s\": %s",
+ option->gen->name, value)));
+ }
+ break;
case RELOPT_TYPE_INT:
{
relopt_int *optint = (relopt_int *) option->gen;
@@ -1780,17 +1864,6 @@ fillRelOptions(void *rdopts, Size basesize,
char *itempos = ((char *) rdopts) + elems[j].offset;
char *string_val;
- /*
- * If isset_offset is provided, store whether the reloption is
- * set there.
- */
- if (elems[j].isset_offset > 0)
- {
- char *setpos = ((char *) rdopts) + elems[j].isset_offset;
-
- *(bool *) setpos = options[i].isset;
- }
-
switch (options[i].gen->type)
{
case RELOPT_TYPE_BOOL:
@@ -1798,6 +1871,11 @@ fillRelOptions(void *rdopts, Size basesize,
options[i].values.bool_val :
((relopt_bool *) options[i].gen)->default_val;
break;
+ case RELOPT_TYPE_TERNARY:
+ *(ternary *) itempos = options[i].isset ?
+ options[i].values.ternary_val :
+ ((relopt_ternary *) options[i].gen)->default_val;
+ break;
case RELOPT_TYPE_INT:
*(int *) itempos = options[i].isset ?
options[i].values.int_val :
@@ -1912,8 +1990,8 @@ default_reloptions(Datum reloptions, bool validate, relopt_kind kind)
offsetof(StdRdOptions, parallel_workers)},
{"vacuum_index_cleanup", RELOPT_TYPE_ENUM,
offsetof(StdRdOptions, vacuum_index_cleanup)},
- {"vacuum_truncate", RELOPT_TYPE_BOOL,
- offsetof(StdRdOptions, vacuum_truncate), offsetof(StdRdOptions, vacuum_truncate_set)},
+ {"vacuum_truncate", RELOPT_TYPE_TERNARY,
+ offsetof(StdRdOptions, vacuum_truncate)},
{"vacuum_max_eager_freeze_failure_rate", RELOPT_TYPE_REAL,
offsetof(StdRdOptions, vacuum_max_eager_freeze_failure_rate)}
};
@@ -1993,7 +2071,6 @@ build_local_reloptions(local_relopts *relopts, Datum options, bool validate)
elems[i].optname = opt->option->name;
elems[i].opttype = opt->option->type;
elems[i].offset = opt->offset;
- elems[i].isset_offset = 0; /* not supported for local relopts yet */
i++;
}
diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c
index 733ef40ae7..977babff54 100644
--- a/src/backend/commands/vacuum.c
+++ b/src/backend/commands/vacuum.c
@@ -2221,9 +2221,9 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams params,
{
StdRdOptions *opts = (StdRdOptions *) rel->rd_options;
- if (opts && opts->vacuum_truncate_set)
+ if (opts && opts->vacuum_truncate != TERNARY_UNSET)
{
- if (opts->vacuum_truncate)
+ if (opts->vacuum_truncate == TERNARY_TRUE)
params.truncate = VACOPTVALUE_ENABLED;
else
params.truncate = VACOPTVALUE_DISABLED;
diff --git a/src/include/access/reloptions.h b/src/include/access/reloptions.h
index a604a4702c..a436697658 100644
--- a/src/include/access/reloptions.h
+++ b/src/include/access/reloptions.h
@@ -29,6 +29,7 @@
typedef enum relopt_type
{
RELOPT_TYPE_BOOL,
+ RELOPT_TYPE_TERNARY, /* on, off, unset */
RELOPT_TYPE_INT,
RELOPT_TYPE_REAL,
RELOPT_TYPE_ENUM,
@@ -80,6 +81,7 @@ typedef struct relopt_value
union
{
bool bool_val;
+ ternary ternary_val;
int int_val;
double real_val;
int enum_val;
@@ -94,6 +96,12 @@ typedef struct relopt_bool
bool default_val;
} relopt_bool;
+typedef struct relopt_rernary
+{
+ relopt_gen gen;
+ int default_val;
+} relopt_ternary;
+
typedef struct relopt_int
{
relopt_gen gen;
@@ -152,19 +160,6 @@ typedef struct
const char *optname; /* option's name */
relopt_type opttype; /* option's datatype */
int offset; /* offset of field in result struct */
-
- /*
- * isset_offset is an optional offset of a field in the result struct that
- * stores whether the option is explicitly set for the relation or if it
- * just picked up the default value. In most cases, this can be
- * accomplished by giving the reloption a special out-of-range default
- * value (e.g., some integer reloptions use -2), but this isn't always
- * possible. For example, a Boolean reloption cannot be given an
- * out-of-range default, so we need another way to discover the source of
- * its value. This offset is only used if given a value greater than
- * zero.
- */
- int isset_offset;
} relopt_parse_elt;
/* Local reloption definition */
@@ -195,6 +190,8 @@ typedef struct local_relopts
extern relopt_kind add_reloption_kind(void);
extern void add_bool_reloption(bits32 kinds, const char *name, const char *desc,
bool default_val, LOCKMODE lockmode);
+extern void add_ternary_reloption(bits32 kinds, const char *name,
+ const char *desc, int default_val, LOCKMODE lockmode);
extern void add_int_reloption(bits32 kinds, const char *name, const char *desc,
int default_val, int min_val, int max_val,
LOCKMODE lockmode);
@@ -214,6 +211,9 @@ extern void register_reloptions_validator(local_relopts *relopts,
extern void add_local_bool_reloption(local_relopts *relopts, const char *name,
const char *desc, bool default_val,
int offset);
+extern void add_local_ternary_reloption(local_relopts *relopts,
+ const char *name, const char *desc,
+ ternary default_val, int offset);
extern void add_local_int_reloption(local_relopts *relopts, const char *name,
const char *desc, int default_val,
int min_val, int max_val, int offset);
diff --git a/src/include/c.h b/src/include/c.h
index 39022f8a9d..9c59ccccf8 100644
--- a/src/include/c.h
+++ b/src/include/c.h
@@ -488,6 +488,22 @@ typedef void (*pg_funcptr_t) (void);
#include <stdbool.h>
+/*
+ * ternary
+ * Boolean value with an extrea "unset" option
+ *
+ * Ternary data type is used in relation options that can be "true", "false" or
+ * "unset". Since relation options are used deep inside the PostgreSQL code,
+ * this type is declared globally.
+*/
+
+typedef enum ternary
+{
+ TERNARY_FALSE = 0,
+ TERNARY_TRUE = 1,
+ TERNARY_UNSET = -1
+} ternary;
+
/* ----------------------------------------------------------------
* Section 3: standard system types
diff --git a/src/include/utils/rel.h b/src/include/utils/rel.h
index b552359915..7346d618f9 100644
--- a/src/include/utils/rel.h
+++ b/src/include/utils/rel.h
@@ -346,8 +346,7 @@ typedef struct StdRdOptions
bool user_catalog_table; /* use as an additional catalog relation */
int parallel_workers; /* max number of parallel workers */
StdRdOptIndexCleanup vacuum_index_cleanup; /* controls index vacuuming */
- bool vacuum_truncate; /* enables vacuum to truncate a relation */
- bool vacuum_truncate_set; /* whether vacuum_truncate is set */
+ ternary vacuum_truncate; /* enables vacuum to truncate a relation */
/*
* Fraction of pages in a relation that vacuum can eagerly scan and fail
--
2.39.2
--nextPart4155600.3ZeAukHxDK
Content-Disposition: attachment;
filename="v2a-0003-Add-alias-to-be-used-as-unset-state.patch"
Content-Transfer-Encoding: 7Bit
Content-Type: text/x-patch; charset="unicode-2-0-utf-8";
name="v2a-0003-Add-alias-to-be-used-as-unset-state.patch"
^ permalink raw reply [nested|flat] 868+ messages in thread
* [PATCH v2 2/4] Introduce ternary reloptions
@ 2025-09-04 14:41 Nikolay Shaplov <[email protected]>
0 siblings, 0 replies; 868+ messages in thread
From: Nikolay Shaplov @ 2025-09-04 14:41 UTC (permalink / raw)
Introduce ternary reloption as a replacement for current `vacuum_truncate`
implementation. Remove `vacuum_truncate_set` additional flag and using
`TERNARY_UNSET` value instead.
---
src/backend/access/common/reloptions.c | 129 ++++++++++++++++++++-----
src/backend/commands/vacuum.c | 4 +-
src/include/access/reloptions.h | 26 ++---
src/include/c.h | 16 +++
src/include/utils/rel.h | 3 +-
5 files changed, 135 insertions(+), 43 deletions(-)
diff --git a/src/backend/access/common/reloptions.c b/src/backend/access/common/reloptions.c
index 0af3fea68f..f543f11001 100644
--- a/src/backend/access/common/reloptions.c
+++ b/src/backend/access/common/reloptions.c
@@ -40,9 +40,9 @@
*
* To add an option:
*
- * (i) decide on a type (bool, integer, real, enum, string), name, default
- * value, upper and lower bounds (if applicable); for strings, consider a
- * validation routine.
+ * (i) decide on a type (bool, ternary, integer, real, enum, string), name,
+ * default value, upper and lower bounds (if applicable); for strings,
+ * consider a validation routine.
* (ii) add a record below (or use add_<type>_reloption).
* (iii) add it to the appropriate options struct (perhaps StdRdOptions)
* (iv) add it to the appropriate handling routine (perhaps
@@ -147,15 +147,6 @@ static relopt_bool boolRelOpts[] =
},
false
},
- {
- {
- "vacuum_truncate",
- "Enables vacuum to truncate empty pages at the end of this table",
- RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
- ShareUpdateExclusiveLock
- },
- true
- },
{
{
"deduplicate_items",
@@ -170,6 +161,21 @@ static relopt_bool boolRelOpts[] =
{{NULL}}
};
+static relopt_ternary ternaryRelOpts[] =
+{
+ {
+ {
+ "vacuum_truncate",
+ "Enables vacuum to truncate empty pages at the end of this table",
+ RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
+ ShareUpdateExclusiveLock
+ },
+ TERNARY_UNSET
+ },
+ /* list terminator */
+ {{NULL}}
+};
+
static relopt_int intRelOpts[] =
{
{
@@ -600,6 +606,13 @@ initialize_reloptions(void)
boolRelOpts[i].gen.lockmode));
j++;
}
+ for (i = 0; ternaryRelOpts[i].gen.name; i++)
+ {
+ Assert(DoLockModesConflict(ternaryRelOpts[i].gen.lockmode,
+ ternaryRelOpts[i].gen.lockmode));
+ j++;
+ }
+
for (i = 0; intRelOpts[i].gen.name; i++)
{
Assert(DoLockModesConflict(intRelOpts[i].gen.lockmode,
@@ -640,6 +653,14 @@ initialize_reloptions(void)
j++;
}
+ for (i = 0; ternaryRelOpts[i].gen.name; i++)
+ {
+ relOpts[j] = &ternaryRelOpts[i].gen;
+ relOpts[j]->type = RELOPT_TYPE_TERNARY;
+ relOpts[j]->namelen = strlen(relOpts[j]->name);
+ j++;
+ }
+
for (i = 0; intRelOpts[i].gen.name; i++)
{
relOpts[j] = &intRelOpts[i].gen;
@@ -800,6 +821,9 @@ allocate_reloption(bits32 kinds, int type, const char *name, const char *desc,
case RELOPT_TYPE_BOOL:
size = sizeof(relopt_bool);
break;
+ case RELOPT_TYPE_TERNARY:
+ size = sizeof(relopt_ternary);
+ break;
case RELOPT_TYPE_INT:
size = sizeof(relopt_int);
break;
@@ -883,6 +907,54 @@ add_local_bool_reloption(local_relopts *relopts, const char *name,
add_local_reloption(relopts, (relopt_gen *) newoption, offset);
}
+/*
+ * init_ternary_reloption
+ * Allocate and initialize a new ternary reloption
+ */
+static relopt_ternary *
+init_ternary_reloption(bits32 kinds, const char *name, const char *desc,
+ ternary default_val, LOCKMODE lockmode)
+{
+ relopt_ternary *newoption;
+
+ newoption = (relopt_ternary *) allocate_reloption(kinds,
+ RELOPT_TYPE_TERNARY, name, desc, lockmode);
+ newoption->default_val = default_val;
+
+ return newoption;
+}
+
+/*
+ * add_ternary_reloption
+ * Add a new ternary reloption
+ */
+void
+add_ternary_reloption(bits32 kinds, const char *name, const char *desc,
+ ternary default_val, LOCKMODE lockmode)
+{
+ relopt_ternary *newoption = init_ternary_reloption(kinds, name, desc,
+ default_val, lockmode);
+
+ add_reloption((relopt_gen *) newoption);
+}
+
+/*
+ * add_local_ternary_reloption
+ * Add a new ternary local reloption
+ *
+ * 'offset' is offset of ternary-typed field.
+ */
+void
+add_local_ternary_reloption(local_relopts *relopts, const char *name,
+ const char *desc, ternary default_val,
+ int offset)
+{
+ relopt_ternary *newoption = init_ternary_reloption(RELOPT_KIND_LOCAL,
+ name, desc,
+ default_val, 0);
+
+ add_local_reloption(relopts, (relopt_gen *) newoption, offset);
+}
/*
* init_real_reloption
@@ -1617,6 +1689,18 @@ parse_one_reloption(relopt_value *option, char *text_str, int text_len,
option->gen->name, value)));
}
break;
+ case RELOPT_TYPE_TERNARY:
+ {
+ bool b;
+ parsed = parse_bool(value, &b);
+ option->values.ternary_val = b ? TERNARY_TRUE : TERNARY_FALSE;
+ if (validate && !parsed)
+ ereport(ERROR,
+ (errcode(ERRCODE_INVALID_PARAMETER_VALUE),
+ errmsg("invalid value for ternary option \"%s\": %s",
+ option->gen->name, value)));
+ }
+ break;
case RELOPT_TYPE_INT:
{
relopt_int *optint = (relopt_int *) option->gen;
@@ -1780,17 +1864,6 @@ fillRelOptions(void *rdopts, Size basesize,
char *itempos = ((char *) rdopts) + elems[j].offset;
char *string_val;
- /*
- * If isset_offset is provided, store whether the reloption is
- * set there.
- */
- if (elems[j].isset_offset > 0)
- {
- char *setpos = ((char *) rdopts) + elems[j].isset_offset;
-
- *(bool *) setpos = options[i].isset;
- }
-
switch (options[i].gen->type)
{
case RELOPT_TYPE_BOOL:
@@ -1798,6 +1871,11 @@ fillRelOptions(void *rdopts, Size basesize,
options[i].values.bool_val :
((relopt_bool *) options[i].gen)->default_val;
break;
+ case RELOPT_TYPE_TERNARY:
+ *(ternary *) itempos = options[i].isset ?
+ options[i].values.ternary_val :
+ ((relopt_ternary *) options[i].gen)->default_val;
+ break;
case RELOPT_TYPE_INT:
*(int *) itempos = options[i].isset ?
options[i].values.int_val :
@@ -1912,8 +1990,8 @@ default_reloptions(Datum reloptions, bool validate, relopt_kind kind)
offsetof(StdRdOptions, parallel_workers)},
{"vacuum_index_cleanup", RELOPT_TYPE_ENUM,
offsetof(StdRdOptions, vacuum_index_cleanup)},
- {"vacuum_truncate", RELOPT_TYPE_BOOL,
- offsetof(StdRdOptions, vacuum_truncate), offsetof(StdRdOptions, vacuum_truncate_set)},
+ {"vacuum_truncate", RELOPT_TYPE_TERNARY,
+ offsetof(StdRdOptions, vacuum_truncate)},
{"vacuum_max_eager_freeze_failure_rate", RELOPT_TYPE_REAL,
offsetof(StdRdOptions, vacuum_max_eager_freeze_failure_rate)}
};
@@ -1993,7 +2071,6 @@ build_local_reloptions(local_relopts *relopts, Datum options, bool validate)
elems[i].optname = opt->option->name;
elems[i].opttype = opt->option->type;
elems[i].offset = opt->offset;
- elems[i].isset_offset = 0; /* not supported for local relopts yet */
i++;
}
diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c
index 733ef40ae7..977babff54 100644
--- a/src/backend/commands/vacuum.c
+++ b/src/backend/commands/vacuum.c
@@ -2221,9 +2221,9 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams params,
{
StdRdOptions *opts = (StdRdOptions *) rel->rd_options;
- if (opts && opts->vacuum_truncate_set)
+ if (opts && opts->vacuum_truncate != TERNARY_UNSET)
{
- if (opts->vacuum_truncate)
+ if (opts->vacuum_truncate == TERNARY_TRUE)
params.truncate = VACOPTVALUE_ENABLED;
else
params.truncate = VACOPTVALUE_DISABLED;
diff --git a/src/include/access/reloptions.h b/src/include/access/reloptions.h
index a604a4702c..a436697658 100644
--- a/src/include/access/reloptions.h
+++ b/src/include/access/reloptions.h
@@ -29,6 +29,7 @@
typedef enum relopt_type
{
RELOPT_TYPE_BOOL,
+ RELOPT_TYPE_TERNARY, /* on, off, unset */
RELOPT_TYPE_INT,
RELOPT_TYPE_REAL,
RELOPT_TYPE_ENUM,
@@ -80,6 +81,7 @@ typedef struct relopt_value
union
{
bool bool_val;
+ ternary ternary_val;
int int_val;
double real_val;
int enum_val;
@@ -94,6 +96,12 @@ typedef struct relopt_bool
bool default_val;
} relopt_bool;
+typedef struct relopt_rernary
+{
+ relopt_gen gen;
+ int default_val;
+} relopt_ternary;
+
typedef struct relopt_int
{
relopt_gen gen;
@@ -152,19 +160,6 @@ typedef struct
const char *optname; /* option's name */
relopt_type opttype; /* option's datatype */
int offset; /* offset of field in result struct */
-
- /*
- * isset_offset is an optional offset of a field in the result struct that
- * stores whether the option is explicitly set for the relation or if it
- * just picked up the default value. In most cases, this can be
- * accomplished by giving the reloption a special out-of-range default
- * value (e.g., some integer reloptions use -2), but this isn't always
- * possible. For example, a Boolean reloption cannot be given an
- * out-of-range default, so we need another way to discover the source of
- * its value. This offset is only used if given a value greater than
- * zero.
- */
- int isset_offset;
} relopt_parse_elt;
/* Local reloption definition */
@@ -195,6 +190,8 @@ typedef struct local_relopts
extern relopt_kind add_reloption_kind(void);
extern void add_bool_reloption(bits32 kinds, const char *name, const char *desc,
bool default_val, LOCKMODE lockmode);
+extern void add_ternary_reloption(bits32 kinds, const char *name,
+ const char *desc, int default_val, LOCKMODE lockmode);
extern void add_int_reloption(bits32 kinds, const char *name, const char *desc,
int default_val, int min_val, int max_val,
LOCKMODE lockmode);
@@ -214,6 +211,9 @@ extern void register_reloptions_validator(local_relopts *relopts,
extern void add_local_bool_reloption(local_relopts *relopts, const char *name,
const char *desc, bool default_val,
int offset);
+extern void add_local_ternary_reloption(local_relopts *relopts,
+ const char *name, const char *desc,
+ ternary default_val, int offset);
extern void add_local_int_reloption(local_relopts *relopts, const char *name,
const char *desc, int default_val,
int min_val, int max_val, int offset);
diff --git a/src/include/c.h b/src/include/c.h
index 39022f8a9d..9c59ccccf8 100644
--- a/src/include/c.h
+++ b/src/include/c.h
@@ -488,6 +488,22 @@ typedef void (*pg_funcptr_t) (void);
#include <stdbool.h>
+/*
+ * ternary
+ * Boolean value with an extrea "unset" option
+ *
+ * Ternary data type is used in relation options that can be "true", "false" or
+ * "unset". Since relation options are used deep inside the PostgreSQL code,
+ * this type is declared globally.
+*/
+
+typedef enum ternary
+{
+ TERNARY_FALSE = 0,
+ TERNARY_TRUE = 1,
+ TERNARY_UNSET = -1
+} ternary;
+
/* ----------------------------------------------------------------
* Section 3: standard system types
diff --git a/src/include/utils/rel.h b/src/include/utils/rel.h
index b552359915..7346d618f9 100644
--- a/src/include/utils/rel.h
+++ b/src/include/utils/rel.h
@@ -346,8 +346,7 @@ typedef struct StdRdOptions
bool user_catalog_table; /* use as an additional catalog relation */
int parallel_workers; /* max number of parallel workers */
StdRdOptIndexCleanup vacuum_index_cleanup; /* controls index vacuuming */
- bool vacuum_truncate; /* enables vacuum to truncate a relation */
- bool vacuum_truncate_set; /* whether vacuum_truncate is set */
+ ternary vacuum_truncate; /* enables vacuum to truncate a relation */
/*
* Fraction of pages in a relation that vacuum can eagerly scan and fail
--
2.39.2
--nextPart5129932.5fSG56mABF
Content-Disposition: attachment;
filename="v2-0003-Add-alias-to-be-used-as-unset-state.patch"
Content-Transfer-Encoding: 7Bit
Content-Type: text/x-patch; charset="unicode-2-0-utf-8";
name="v2-0003-Add-alias-to-be-used-as-unset-state.patch"
^ permalink raw reply [nested|flat] 868+ messages in thread
* [PATCH v2a 2/4] Introduce ternary reloptions
@ 2025-09-04 14:41 Nikolay Shaplov <[email protected]>
0 siblings, 0 replies; 868+ messages in thread
From: Nikolay Shaplov @ 2025-09-04 14:41 UTC (permalink / raw)
Introduce ternary reloption as a replacement for current `vacuum_truncate`
implementation. Remove `vacuum_truncate_set` additional flag and using
`TERNARY_UNSET` value instead.
---
src/backend/access/common/reloptions.c | 129 ++++++++++++++++++++-----
src/backend/commands/vacuum.c | 4 +-
src/include/access/reloptions.h | 26 ++---
src/include/c.h | 16 +++
src/include/utils/rel.h | 3 +-
5 files changed, 135 insertions(+), 43 deletions(-)
diff --git a/src/backend/access/common/reloptions.c b/src/backend/access/common/reloptions.c
index 0af3fea68f..f543f11001 100644
--- a/src/backend/access/common/reloptions.c
+++ b/src/backend/access/common/reloptions.c
@@ -40,9 +40,9 @@
*
* To add an option:
*
- * (i) decide on a type (bool, integer, real, enum, string), name, default
- * value, upper and lower bounds (if applicable); for strings, consider a
- * validation routine.
+ * (i) decide on a type (bool, ternary, integer, real, enum, string), name,
+ * default value, upper and lower bounds (if applicable); for strings,
+ * consider a validation routine.
* (ii) add a record below (or use add_<type>_reloption).
* (iii) add it to the appropriate options struct (perhaps StdRdOptions)
* (iv) add it to the appropriate handling routine (perhaps
@@ -147,15 +147,6 @@ static relopt_bool boolRelOpts[] =
},
false
},
- {
- {
- "vacuum_truncate",
- "Enables vacuum to truncate empty pages at the end of this table",
- RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
- ShareUpdateExclusiveLock
- },
- true
- },
{
{
"deduplicate_items",
@@ -170,6 +161,21 @@ static relopt_bool boolRelOpts[] =
{{NULL}}
};
+static relopt_ternary ternaryRelOpts[] =
+{
+ {
+ {
+ "vacuum_truncate",
+ "Enables vacuum to truncate empty pages at the end of this table",
+ RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
+ ShareUpdateExclusiveLock
+ },
+ TERNARY_UNSET
+ },
+ /* list terminator */
+ {{NULL}}
+};
+
static relopt_int intRelOpts[] =
{
{
@@ -600,6 +606,13 @@ initialize_reloptions(void)
boolRelOpts[i].gen.lockmode));
j++;
}
+ for (i = 0; ternaryRelOpts[i].gen.name; i++)
+ {
+ Assert(DoLockModesConflict(ternaryRelOpts[i].gen.lockmode,
+ ternaryRelOpts[i].gen.lockmode));
+ j++;
+ }
+
for (i = 0; intRelOpts[i].gen.name; i++)
{
Assert(DoLockModesConflict(intRelOpts[i].gen.lockmode,
@@ -640,6 +653,14 @@ initialize_reloptions(void)
j++;
}
+ for (i = 0; ternaryRelOpts[i].gen.name; i++)
+ {
+ relOpts[j] = &ternaryRelOpts[i].gen;
+ relOpts[j]->type = RELOPT_TYPE_TERNARY;
+ relOpts[j]->namelen = strlen(relOpts[j]->name);
+ j++;
+ }
+
for (i = 0; intRelOpts[i].gen.name; i++)
{
relOpts[j] = &intRelOpts[i].gen;
@@ -800,6 +821,9 @@ allocate_reloption(bits32 kinds, int type, const char *name, const char *desc,
case RELOPT_TYPE_BOOL:
size = sizeof(relopt_bool);
break;
+ case RELOPT_TYPE_TERNARY:
+ size = sizeof(relopt_ternary);
+ break;
case RELOPT_TYPE_INT:
size = sizeof(relopt_int);
break;
@@ -883,6 +907,54 @@ add_local_bool_reloption(local_relopts *relopts, const char *name,
add_local_reloption(relopts, (relopt_gen *) newoption, offset);
}
+/*
+ * init_ternary_reloption
+ * Allocate and initialize a new ternary reloption
+ */
+static relopt_ternary *
+init_ternary_reloption(bits32 kinds, const char *name, const char *desc,
+ ternary default_val, LOCKMODE lockmode)
+{
+ relopt_ternary *newoption;
+
+ newoption = (relopt_ternary *) allocate_reloption(kinds,
+ RELOPT_TYPE_TERNARY, name, desc, lockmode);
+ newoption->default_val = default_val;
+
+ return newoption;
+}
+
+/*
+ * add_ternary_reloption
+ * Add a new ternary reloption
+ */
+void
+add_ternary_reloption(bits32 kinds, const char *name, const char *desc,
+ ternary default_val, LOCKMODE lockmode)
+{
+ relopt_ternary *newoption = init_ternary_reloption(kinds, name, desc,
+ default_val, lockmode);
+
+ add_reloption((relopt_gen *) newoption);
+}
+
+/*
+ * add_local_ternary_reloption
+ * Add a new ternary local reloption
+ *
+ * 'offset' is offset of ternary-typed field.
+ */
+void
+add_local_ternary_reloption(local_relopts *relopts, const char *name,
+ const char *desc, ternary default_val,
+ int offset)
+{
+ relopt_ternary *newoption = init_ternary_reloption(RELOPT_KIND_LOCAL,
+ name, desc,
+ default_val, 0);
+
+ add_local_reloption(relopts, (relopt_gen *) newoption, offset);
+}
/*
* init_real_reloption
@@ -1617,6 +1689,18 @@ parse_one_reloption(relopt_value *option, char *text_str, int text_len,
option->gen->name, value)));
}
break;
+ case RELOPT_TYPE_TERNARY:
+ {
+ bool b;
+ parsed = parse_bool(value, &b);
+ option->values.ternary_val = b ? TERNARY_TRUE : TERNARY_FALSE;
+ if (validate && !parsed)
+ ereport(ERROR,
+ (errcode(ERRCODE_INVALID_PARAMETER_VALUE),
+ errmsg("invalid value for ternary option \"%s\": %s",
+ option->gen->name, value)));
+ }
+ break;
case RELOPT_TYPE_INT:
{
relopt_int *optint = (relopt_int *) option->gen;
@@ -1780,17 +1864,6 @@ fillRelOptions(void *rdopts, Size basesize,
char *itempos = ((char *) rdopts) + elems[j].offset;
char *string_val;
- /*
- * If isset_offset is provided, store whether the reloption is
- * set there.
- */
- if (elems[j].isset_offset > 0)
- {
- char *setpos = ((char *) rdopts) + elems[j].isset_offset;
-
- *(bool *) setpos = options[i].isset;
- }
-
switch (options[i].gen->type)
{
case RELOPT_TYPE_BOOL:
@@ -1798,6 +1871,11 @@ fillRelOptions(void *rdopts, Size basesize,
options[i].values.bool_val :
((relopt_bool *) options[i].gen)->default_val;
break;
+ case RELOPT_TYPE_TERNARY:
+ *(ternary *) itempos = options[i].isset ?
+ options[i].values.ternary_val :
+ ((relopt_ternary *) options[i].gen)->default_val;
+ break;
case RELOPT_TYPE_INT:
*(int *) itempos = options[i].isset ?
options[i].values.int_val :
@@ -1912,8 +1990,8 @@ default_reloptions(Datum reloptions, bool validate, relopt_kind kind)
offsetof(StdRdOptions, parallel_workers)},
{"vacuum_index_cleanup", RELOPT_TYPE_ENUM,
offsetof(StdRdOptions, vacuum_index_cleanup)},
- {"vacuum_truncate", RELOPT_TYPE_BOOL,
- offsetof(StdRdOptions, vacuum_truncate), offsetof(StdRdOptions, vacuum_truncate_set)},
+ {"vacuum_truncate", RELOPT_TYPE_TERNARY,
+ offsetof(StdRdOptions, vacuum_truncate)},
{"vacuum_max_eager_freeze_failure_rate", RELOPT_TYPE_REAL,
offsetof(StdRdOptions, vacuum_max_eager_freeze_failure_rate)}
};
@@ -1993,7 +2071,6 @@ build_local_reloptions(local_relopts *relopts, Datum options, bool validate)
elems[i].optname = opt->option->name;
elems[i].opttype = opt->option->type;
elems[i].offset = opt->offset;
- elems[i].isset_offset = 0; /* not supported for local relopts yet */
i++;
}
diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c
index 733ef40ae7..977babff54 100644
--- a/src/backend/commands/vacuum.c
+++ b/src/backend/commands/vacuum.c
@@ -2221,9 +2221,9 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams params,
{
StdRdOptions *opts = (StdRdOptions *) rel->rd_options;
- if (opts && opts->vacuum_truncate_set)
+ if (opts && opts->vacuum_truncate != TERNARY_UNSET)
{
- if (opts->vacuum_truncate)
+ if (opts->vacuum_truncate == TERNARY_TRUE)
params.truncate = VACOPTVALUE_ENABLED;
else
params.truncate = VACOPTVALUE_DISABLED;
diff --git a/src/include/access/reloptions.h b/src/include/access/reloptions.h
index a604a4702c..a436697658 100644
--- a/src/include/access/reloptions.h
+++ b/src/include/access/reloptions.h
@@ -29,6 +29,7 @@
typedef enum relopt_type
{
RELOPT_TYPE_BOOL,
+ RELOPT_TYPE_TERNARY, /* on, off, unset */
RELOPT_TYPE_INT,
RELOPT_TYPE_REAL,
RELOPT_TYPE_ENUM,
@@ -80,6 +81,7 @@ typedef struct relopt_value
union
{
bool bool_val;
+ ternary ternary_val;
int int_val;
double real_val;
int enum_val;
@@ -94,6 +96,12 @@ typedef struct relopt_bool
bool default_val;
} relopt_bool;
+typedef struct relopt_rernary
+{
+ relopt_gen gen;
+ int default_val;
+} relopt_ternary;
+
typedef struct relopt_int
{
relopt_gen gen;
@@ -152,19 +160,6 @@ typedef struct
const char *optname; /* option's name */
relopt_type opttype; /* option's datatype */
int offset; /* offset of field in result struct */
-
- /*
- * isset_offset is an optional offset of a field in the result struct that
- * stores whether the option is explicitly set for the relation or if it
- * just picked up the default value. In most cases, this can be
- * accomplished by giving the reloption a special out-of-range default
- * value (e.g., some integer reloptions use -2), but this isn't always
- * possible. For example, a Boolean reloption cannot be given an
- * out-of-range default, so we need another way to discover the source of
- * its value. This offset is only used if given a value greater than
- * zero.
- */
- int isset_offset;
} relopt_parse_elt;
/* Local reloption definition */
@@ -195,6 +190,8 @@ typedef struct local_relopts
extern relopt_kind add_reloption_kind(void);
extern void add_bool_reloption(bits32 kinds, const char *name, const char *desc,
bool default_val, LOCKMODE lockmode);
+extern void add_ternary_reloption(bits32 kinds, const char *name,
+ const char *desc, int default_val, LOCKMODE lockmode);
extern void add_int_reloption(bits32 kinds, const char *name, const char *desc,
int default_val, int min_val, int max_val,
LOCKMODE lockmode);
@@ -214,6 +211,9 @@ extern void register_reloptions_validator(local_relopts *relopts,
extern void add_local_bool_reloption(local_relopts *relopts, const char *name,
const char *desc, bool default_val,
int offset);
+extern void add_local_ternary_reloption(local_relopts *relopts,
+ const char *name, const char *desc,
+ ternary default_val, int offset);
extern void add_local_int_reloption(local_relopts *relopts, const char *name,
const char *desc, int default_val,
int min_val, int max_val, int offset);
diff --git a/src/include/c.h b/src/include/c.h
index 39022f8a9d..9c59ccccf8 100644
--- a/src/include/c.h
+++ b/src/include/c.h
@@ -488,6 +488,22 @@ typedef void (*pg_funcptr_t) (void);
#include <stdbool.h>
+/*
+ * ternary
+ * Boolean value with an extrea "unset" option
+ *
+ * Ternary data type is used in relation options that can be "true", "false" or
+ * "unset". Since relation options are used deep inside the PostgreSQL code,
+ * this type is declared globally.
+*/
+
+typedef enum ternary
+{
+ TERNARY_FALSE = 0,
+ TERNARY_TRUE = 1,
+ TERNARY_UNSET = -1
+} ternary;
+
/* ----------------------------------------------------------------
* Section 3: standard system types
diff --git a/src/include/utils/rel.h b/src/include/utils/rel.h
index b552359915..7346d618f9 100644
--- a/src/include/utils/rel.h
+++ b/src/include/utils/rel.h
@@ -346,8 +346,7 @@ typedef struct StdRdOptions
bool user_catalog_table; /* use as an additional catalog relation */
int parallel_workers; /* max number of parallel workers */
StdRdOptIndexCleanup vacuum_index_cleanup; /* controls index vacuuming */
- bool vacuum_truncate; /* enables vacuum to truncate a relation */
- bool vacuum_truncate_set; /* whether vacuum_truncate is set */
+ ternary vacuum_truncate; /* enables vacuum to truncate a relation */
/*
* Fraction of pages in a relation that vacuum can eagerly scan and fail
--
2.39.2
--nextPart4155600.3ZeAukHxDK
Content-Disposition: attachment;
filename="v2a-0003-Add-alias-to-be-used-as-unset-state.patch"
Content-Transfer-Encoding: 7Bit
Content-Type: text/x-patch; charset="unicode-2-0-utf-8";
name="v2a-0003-Add-alias-to-be-used-as-unset-state.patch"
^ permalink raw reply [nested|flat] 868+ messages in thread
* [PATCH v2a 2/4] Introduce ternary reloptions
@ 2025-09-04 14:41 Nikolay Shaplov <[email protected]>
0 siblings, 0 replies; 868+ messages in thread
From: Nikolay Shaplov @ 2025-09-04 14:41 UTC (permalink / raw)
Introduce ternary reloption as a replacement for current `vacuum_truncate`
implementation. Remove `vacuum_truncate_set` additional flag and using
`TERNARY_UNSET` value instead.
---
src/backend/access/common/reloptions.c | 129 ++++++++++++++++++++-----
src/backend/commands/vacuum.c | 4 +-
src/include/access/reloptions.h | 26 ++---
src/include/c.h | 16 +++
src/include/utils/rel.h | 3 +-
5 files changed, 135 insertions(+), 43 deletions(-)
diff --git a/src/backend/access/common/reloptions.c b/src/backend/access/common/reloptions.c
index 0af3fea68f..f543f11001 100644
--- a/src/backend/access/common/reloptions.c
+++ b/src/backend/access/common/reloptions.c
@@ -40,9 +40,9 @@
*
* To add an option:
*
- * (i) decide on a type (bool, integer, real, enum, string), name, default
- * value, upper and lower bounds (if applicable); for strings, consider a
- * validation routine.
+ * (i) decide on a type (bool, ternary, integer, real, enum, string), name,
+ * default value, upper and lower bounds (if applicable); for strings,
+ * consider a validation routine.
* (ii) add a record below (or use add_<type>_reloption).
* (iii) add it to the appropriate options struct (perhaps StdRdOptions)
* (iv) add it to the appropriate handling routine (perhaps
@@ -147,15 +147,6 @@ static relopt_bool boolRelOpts[] =
},
false
},
- {
- {
- "vacuum_truncate",
- "Enables vacuum to truncate empty pages at the end of this table",
- RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
- ShareUpdateExclusiveLock
- },
- true
- },
{
{
"deduplicate_items",
@@ -170,6 +161,21 @@ static relopt_bool boolRelOpts[] =
{{NULL}}
};
+static relopt_ternary ternaryRelOpts[] =
+{
+ {
+ {
+ "vacuum_truncate",
+ "Enables vacuum to truncate empty pages at the end of this table",
+ RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
+ ShareUpdateExclusiveLock
+ },
+ TERNARY_UNSET
+ },
+ /* list terminator */
+ {{NULL}}
+};
+
static relopt_int intRelOpts[] =
{
{
@@ -600,6 +606,13 @@ initialize_reloptions(void)
boolRelOpts[i].gen.lockmode));
j++;
}
+ for (i = 0; ternaryRelOpts[i].gen.name; i++)
+ {
+ Assert(DoLockModesConflict(ternaryRelOpts[i].gen.lockmode,
+ ternaryRelOpts[i].gen.lockmode));
+ j++;
+ }
+
for (i = 0; intRelOpts[i].gen.name; i++)
{
Assert(DoLockModesConflict(intRelOpts[i].gen.lockmode,
@@ -640,6 +653,14 @@ initialize_reloptions(void)
j++;
}
+ for (i = 0; ternaryRelOpts[i].gen.name; i++)
+ {
+ relOpts[j] = &ternaryRelOpts[i].gen;
+ relOpts[j]->type = RELOPT_TYPE_TERNARY;
+ relOpts[j]->namelen = strlen(relOpts[j]->name);
+ j++;
+ }
+
for (i = 0; intRelOpts[i].gen.name; i++)
{
relOpts[j] = &intRelOpts[i].gen;
@@ -800,6 +821,9 @@ allocate_reloption(bits32 kinds, int type, const char *name, const char *desc,
case RELOPT_TYPE_BOOL:
size = sizeof(relopt_bool);
break;
+ case RELOPT_TYPE_TERNARY:
+ size = sizeof(relopt_ternary);
+ break;
case RELOPT_TYPE_INT:
size = sizeof(relopt_int);
break;
@@ -883,6 +907,54 @@ add_local_bool_reloption(local_relopts *relopts, const char *name,
add_local_reloption(relopts, (relopt_gen *) newoption, offset);
}
+/*
+ * init_ternary_reloption
+ * Allocate and initialize a new ternary reloption
+ */
+static relopt_ternary *
+init_ternary_reloption(bits32 kinds, const char *name, const char *desc,
+ ternary default_val, LOCKMODE lockmode)
+{
+ relopt_ternary *newoption;
+
+ newoption = (relopt_ternary *) allocate_reloption(kinds,
+ RELOPT_TYPE_TERNARY, name, desc, lockmode);
+ newoption->default_val = default_val;
+
+ return newoption;
+}
+
+/*
+ * add_ternary_reloption
+ * Add a new ternary reloption
+ */
+void
+add_ternary_reloption(bits32 kinds, const char *name, const char *desc,
+ ternary default_val, LOCKMODE lockmode)
+{
+ relopt_ternary *newoption = init_ternary_reloption(kinds, name, desc,
+ default_val, lockmode);
+
+ add_reloption((relopt_gen *) newoption);
+}
+
+/*
+ * add_local_ternary_reloption
+ * Add a new ternary local reloption
+ *
+ * 'offset' is offset of ternary-typed field.
+ */
+void
+add_local_ternary_reloption(local_relopts *relopts, const char *name,
+ const char *desc, ternary default_val,
+ int offset)
+{
+ relopt_ternary *newoption = init_ternary_reloption(RELOPT_KIND_LOCAL,
+ name, desc,
+ default_val, 0);
+
+ add_local_reloption(relopts, (relopt_gen *) newoption, offset);
+}
/*
* init_real_reloption
@@ -1617,6 +1689,18 @@ parse_one_reloption(relopt_value *option, char *text_str, int text_len,
option->gen->name, value)));
}
break;
+ case RELOPT_TYPE_TERNARY:
+ {
+ bool b;
+ parsed = parse_bool(value, &b);
+ option->values.ternary_val = b ? TERNARY_TRUE : TERNARY_FALSE;
+ if (validate && !parsed)
+ ereport(ERROR,
+ (errcode(ERRCODE_INVALID_PARAMETER_VALUE),
+ errmsg("invalid value for ternary option \"%s\": %s",
+ option->gen->name, value)));
+ }
+ break;
case RELOPT_TYPE_INT:
{
relopt_int *optint = (relopt_int *) option->gen;
@@ -1780,17 +1864,6 @@ fillRelOptions(void *rdopts, Size basesize,
char *itempos = ((char *) rdopts) + elems[j].offset;
char *string_val;
- /*
- * If isset_offset is provided, store whether the reloption is
- * set there.
- */
- if (elems[j].isset_offset > 0)
- {
- char *setpos = ((char *) rdopts) + elems[j].isset_offset;
-
- *(bool *) setpos = options[i].isset;
- }
-
switch (options[i].gen->type)
{
case RELOPT_TYPE_BOOL:
@@ -1798,6 +1871,11 @@ fillRelOptions(void *rdopts, Size basesize,
options[i].values.bool_val :
((relopt_bool *) options[i].gen)->default_val;
break;
+ case RELOPT_TYPE_TERNARY:
+ *(ternary *) itempos = options[i].isset ?
+ options[i].values.ternary_val :
+ ((relopt_ternary *) options[i].gen)->default_val;
+ break;
case RELOPT_TYPE_INT:
*(int *) itempos = options[i].isset ?
options[i].values.int_val :
@@ -1912,8 +1990,8 @@ default_reloptions(Datum reloptions, bool validate, relopt_kind kind)
offsetof(StdRdOptions, parallel_workers)},
{"vacuum_index_cleanup", RELOPT_TYPE_ENUM,
offsetof(StdRdOptions, vacuum_index_cleanup)},
- {"vacuum_truncate", RELOPT_TYPE_BOOL,
- offsetof(StdRdOptions, vacuum_truncate), offsetof(StdRdOptions, vacuum_truncate_set)},
+ {"vacuum_truncate", RELOPT_TYPE_TERNARY,
+ offsetof(StdRdOptions, vacuum_truncate)},
{"vacuum_max_eager_freeze_failure_rate", RELOPT_TYPE_REAL,
offsetof(StdRdOptions, vacuum_max_eager_freeze_failure_rate)}
};
@@ -1993,7 +2071,6 @@ build_local_reloptions(local_relopts *relopts, Datum options, bool validate)
elems[i].optname = opt->option->name;
elems[i].opttype = opt->option->type;
elems[i].offset = opt->offset;
- elems[i].isset_offset = 0; /* not supported for local relopts yet */
i++;
}
diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c
index 733ef40ae7..977babff54 100644
--- a/src/backend/commands/vacuum.c
+++ b/src/backend/commands/vacuum.c
@@ -2221,9 +2221,9 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams params,
{
StdRdOptions *opts = (StdRdOptions *) rel->rd_options;
- if (opts && opts->vacuum_truncate_set)
+ if (opts && opts->vacuum_truncate != TERNARY_UNSET)
{
- if (opts->vacuum_truncate)
+ if (opts->vacuum_truncate == TERNARY_TRUE)
params.truncate = VACOPTVALUE_ENABLED;
else
params.truncate = VACOPTVALUE_DISABLED;
diff --git a/src/include/access/reloptions.h b/src/include/access/reloptions.h
index a604a4702c..a436697658 100644
--- a/src/include/access/reloptions.h
+++ b/src/include/access/reloptions.h
@@ -29,6 +29,7 @@
typedef enum relopt_type
{
RELOPT_TYPE_BOOL,
+ RELOPT_TYPE_TERNARY, /* on, off, unset */
RELOPT_TYPE_INT,
RELOPT_TYPE_REAL,
RELOPT_TYPE_ENUM,
@@ -80,6 +81,7 @@ typedef struct relopt_value
union
{
bool bool_val;
+ ternary ternary_val;
int int_val;
double real_val;
int enum_val;
@@ -94,6 +96,12 @@ typedef struct relopt_bool
bool default_val;
} relopt_bool;
+typedef struct relopt_rernary
+{
+ relopt_gen gen;
+ int default_val;
+} relopt_ternary;
+
typedef struct relopt_int
{
relopt_gen gen;
@@ -152,19 +160,6 @@ typedef struct
const char *optname; /* option's name */
relopt_type opttype; /* option's datatype */
int offset; /* offset of field in result struct */
-
- /*
- * isset_offset is an optional offset of a field in the result struct that
- * stores whether the option is explicitly set for the relation or if it
- * just picked up the default value. In most cases, this can be
- * accomplished by giving the reloption a special out-of-range default
- * value (e.g., some integer reloptions use -2), but this isn't always
- * possible. For example, a Boolean reloption cannot be given an
- * out-of-range default, so we need another way to discover the source of
- * its value. This offset is only used if given a value greater than
- * zero.
- */
- int isset_offset;
} relopt_parse_elt;
/* Local reloption definition */
@@ -195,6 +190,8 @@ typedef struct local_relopts
extern relopt_kind add_reloption_kind(void);
extern void add_bool_reloption(bits32 kinds, const char *name, const char *desc,
bool default_val, LOCKMODE lockmode);
+extern void add_ternary_reloption(bits32 kinds, const char *name,
+ const char *desc, int default_val, LOCKMODE lockmode);
extern void add_int_reloption(bits32 kinds, const char *name, const char *desc,
int default_val, int min_val, int max_val,
LOCKMODE lockmode);
@@ -214,6 +211,9 @@ extern void register_reloptions_validator(local_relopts *relopts,
extern void add_local_bool_reloption(local_relopts *relopts, const char *name,
const char *desc, bool default_val,
int offset);
+extern void add_local_ternary_reloption(local_relopts *relopts,
+ const char *name, const char *desc,
+ ternary default_val, int offset);
extern void add_local_int_reloption(local_relopts *relopts, const char *name,
const char *desc, int default_val,
int min_val, int max_val, int offset);
diff --git a/src/include/c.h b/src/include/c.h
index 39022f8a9d..9c59ccccf8 100644
--- a/src/include/c.h
+++ b/src/include/c.h
@@ -488,6 +488,22 @@ typedef void (*pg_funcptr_t) (void);
#include <stdbool.h>
+/*
+ * ternary
+ * Boolean value with an extrea "unset" option
+ *
+ * Ternary data type is used in relation options that can be "true", "false" or
+ * "unset". Since relation options are used deep inside the PostgreSQL code,
+ * this type is declared globally.
+*/
+
+typedef enum ternary
+{
+ TERNARY_FALSE = 0,
+ TERNARY_TRUE = 1,
+ TERNARY_UNSET = -1
+} ternary;
+
/* ----------------------------------------------------------------
* Section 3: standard system types
diff --git a/src/include/utils/rel.h b/src/include/utils/rel.h
index b552359915..7346d618f9 100644
--- a/src/include/utils/rel.h
+++ b/src/include/utils/rel.h
@@ -346,8 +346,7 @@ typedef struct StdRdOptions
bool user_catalog_table; /* use as an additional catalog relation */
int parallel_workers; /* max number of parallel workers */
StdRdOptIndexCleanup vacuum_index_cleanup; /* controls index vacuuming */
- bool vacuum_truncate; /* enables vacuum to truncate a relation */
- bool vacuum_truncate_set; /* whether vacuum_truncate is set */
+ ternary vacuum_truncate; /* enables vacuum to truncate a relation */
/*
* Fraction of pages in a relation that vacuum can eagerly scan and fail
--
2.39.2
--nextPart4155600.3ZeAukHxDK
Content-Disposition: attachment;
filename="v2a-0003-Add-alias-to-be-used-as-unset-state.patch"
Content-Transfer-Encoding: 7Bit
Content-Type: text/x-patch; charset="unicode-2-0-utf-8";
name="v2a-0003-Add-alias-to-be-used-as-unset-state.patch"
^ permalink raw reply [nested|flat] 868+ messages in thread
* [PATCH v2 2/4] Introduce ternary reloptions
@ 2025-09-04 14:41 Nikolay Shaplov <[email protected]>
0 siblings, 0 replies; 868+ messages in thread
From: Nikolay Shaplov @ 2025-09-04 14:41 UTC (permalink / raw)
Introduce ternary reloption as a replacement for current `vacuum_truncate`
implementation. Remove `vacuum_truncate_set` additional flag and using
`TERNARY_UNSET` value instead.
---
src/backend/access/common/reloptions.c | 129 ++++++++++++++++++++-----
src/backend/commands/vacuum.c | 4 +-
src/include/access/reloptions.h | 26 ++---
src/include/c.h | 16 +++
src/include/utils/rel.h | 3 +-
5 files changed, 135 insertions(+), 43 deletions(-)
diff --git a/src/backend/access/common/reloptions.c b/src/backend/access/common/reloptions.c
index 0af3fea68f..f543f11001 100644
--- a/src/backend/access/common/reloptions.c
+++ b/src/backend/access/common/reloptions.c
@@ -40,9 +40,9 @@
*
* To add an option:
*
- * (i) decide on a type (bool, integer, real, enum, string), name, default
- * value, upper and lower bounds (if applicable); for strings, consider a
- * validation routine.
+ * (i) decide on a type (bool, ternary, integer, real, enum, string), name,
+ * default value, upper and lower bounds (if applicable); for strings,
+ * consider a validation routine.
* (ii) add a record below (or use add_<type>_reloption).
* (iii) add it to the appropriate options struct (perhaps StdRdOptions)
* (iv) add it to the appropriate handling routine (perhaps
@@ -147,15 +147,6 @@ static relopt_bool boolRelOpts[] =
},
false
},
- {
- {
- "vacuum_truncate",
- "Enables vacuum to truncate empty pages at the end of this table",
- RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
- ShareUpdateExclusiveLock
- },
- true
- },
{
{
"deduplicate_items",
@@ -170,6 +161,21 @@ static relopt_bool boolRelOpts[] =
{{NULL}}
};
+static relopt_ternary ternaryRelOpts[] =
+{
+ {
+ {
+ "vacuum_truncate",
+ "Enables vacuum to truncate empty pages at the end of this table",
+ RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
+ ShareUpdateExclusiveLock
+ },
+ TERNARY_UNSET
+ },
+ /* list terminator */
+ {{NULL}}
+};
+
static relopt_int intRelOpts[] =
{
{
@@ -600,6 +606,13 @@ initialize_reloptions(void)
boolRelOpts[i].gen.lockmode));
j++;
}
+ for (i = 0; ternaryRelOpts[i].gen.name; i++)
+ {
+ Assert(DoLockModesConflict(ternaryRelOpts[i].gen.lockmode,
+ ternaryRelOpts[i].gen.lockmode));
+ j++;
+ }
+
for (i = 0; intRelOpts[i].gen.name; i++)
{
Assert(DoLockModesConflict(intRelOpts[i].gen.lockmode,
@@ -640,6 +653,14 @@ initialize_reloptions(void)
j++;
}
+ for (i = 0; ternaryRelOpts[i].gen.name; i++)
+ {
+ relOpts[j] = &ternaryRelOpts[i].gen;
+ relOpts[j]->type = RELOPT_TYPE_TERNARY;
+ relOpts[j]->namelen = strlen(relOpts[j]->name);
+ j++;
+ }
+
for (i = 0; intRelOpts[i].gen.name; i++)
{
relOpts[j] = &intRelOpts[i].gen;
@@ -800,6 +821,9 @@ allocate_reloption(bits32 kinds, int type, const char *name, const char *desc,
case RELOPT_TYPE_BOOL:
size = sizeof(relopt_bool);
break;
+ case RELOPT_TYPE_TERNARY:
+ size = sizeof(relopt_ternary);
+ break;
case RELOPT_TYPE_INT:
size = sizeof(relopt_int);
break;
@@ -883,6 +907,54 @@ add_local_bool_reloption(local_relopts *relopts, const char *name,
add_local_reloption(relopts, (relopt_gen *) newoption, offset);
}
+/*
+ * init_ternary_reloption
+ * Allocate and initialize a new ternary reloption
+ */
+static relopt_ternary *
+init_ternary_reloption(bits32 kinds, const char *name, const char *desc,
+ ternary default_val, LOCKMODE lockmode)
+{
+ relopt_ternary *newoption;
+
+ newoption = (relopt_ternary *) allocate_reloption(kinds,
+ RELOPT_TYPE_TERNARY, name, desc, lockmode);
+ newoption->default_val = default_val;
+
+ return newoption;
+}
+
+/*
+ * add_ternary_reloption
+ * Add a new ternary reloption
+ */
+void
+add_ternary_reloption(bits32 kinds, const char *name, const char *desc,
+ ternary default_val, LOCKMODE lockmode)
+{
+ relopt_ternary *newoption = init_ternary_reloption(kinds, name, desc,
+ default_val, lockmode);
+
+ add_reloption((relopt_gen *) newoption);
+}
+
+/*
+ * add_local_ternary_reloption
+ * Add a new ternary local reloption
+ *
+ * 'offset' is offset of ternary-typed field.
+ */
+void
+add_local_ternary_reloption(local_relopts *relopts, const char *name,
+ const char *desc, ternary default_val,
+ int offset)
+{
+ relopt_ternary *newoption = init_ternary_reloption(RELOPT_KIND_LOCAL,
+ name, desc,
+ default_val, 0);
+
+ add_local_reloption(relopts, (relopt_gen *) newoption, offset);
+}
/*
* init_real_reloption
@@ -1617,6 +1689,18 @@ parse_one_reloption(relopt_value *option, char *text_str, int text_len,
option->gen->name, value)));
}
break;
+ case RELOPT_TYPE_TERNARY:
+ {
+ bool b;
+ parsed = parse_bool(value, &b);
+ option->values.ternary_val = b ? TERNARY_TRUE : TERNARY_FALSE;
+ if (validate && !parsed)
+ ereport(ERROR,
+ (errcode(ERRCODE_INVALID_PARAMETER_VALUE),
+ errmsg("invalid value for ternary option \"%s\": %s",
+ option->gen->name, value)));
+ }
+ break;
case RELOPT_TYPE_INT:
{
relopt_int *optint = (relopt_int *) option->gen;
@@ -1780,17 +1864,6 @@ fillRelOptions(void *rdopts, Size basesize,
char *itempos = ((char *) rdopts) + elems[j].offset;
char *string_val;
- /*
- * If isset_offset is provided, store whether the reloption is
- * set there.
- */
- if (elems[j].isset_offset > 0)
- {
- char *setpos = ((char *) rdopts) + elems[j].isset_offset;
-
- *(bool *) setpos = options[i].isset;
- }
-
switch (options[i].gen->type)
{
case RELOPT_TYPE_BOOL:
@@ -1798,6 +1871,11 @@ fillRelOptions(void *rdopts, Size basesize,
options[i].values.bool_val :
((relopt_bool *) options[i].gen)->default_val;
break;
+ case RELOPT_TYPE_TERNARY:
+ *(ternary *) itempos = options[i].isset ?
+ options[i].values.ternary_val :
+ ((relopt_ternary *) options[i].gen)->default_val;
+ break;
case RELOPT_TYPE_INT:
*(int *) itempos = options[i].isset ?
options[i].values.int_val :
@@ -1912,8 +1990,8 @@ default_reloptions(Datum reloptions, bool validate, relopt_kind kind)
offsetof(StdRdOptions, parallel_workers)},
{"vacuum_index_cleanup", RELOPT_TYPE_ENUM,
offsetof(StdRdOptions, vacuum_index_cleanup)},
- {"vacuum_truncate", RELOPT_TYPE_BOOL,
- offsetof(StdRdOptions, vacuum_truncate), offsetof(StdRdOptions, vacuum_truncate_set)},
+ {"vacuum_truncate", RELOPT_TYPE_TERNARY,
+ offsetof(StdRdOptions, vacuum_truncate)},
{"vacuum_max_eager_freeze_failure_rate", RELOPT_TYPE_REAL,
offsetof(StdRdOptions, vacuum_max_eager_freeze_failure_rate)}
};
@@ -1993,7 +2071,6 @@ build_local_reloptions(local_relopts *relopts, Datum options, bool validate)
elems[i].optname = opt->option->name;
elems[i].opttype = opt->option->type;
elems[i].offset = opt->offset;
- elems[i].isset_offset = 0; /* not supported for local relopts yet */
i++;
}
diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c
index 733ef40ae7..977babff54 100644
--- a/src/backend/commands/vacuum.c
+++ b/src/backend/commands/vacuum.c
@@ -2221,9 +2221,9 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams params,
{
StdRdOptions *opts = (StdRdOptions *) rel->rd_options;
- if (opts && opts->vacuum_truncate_set)
+ if (opts && opts->vacuum_truncate != TERNARY_UNSET)
{
- if (opts->vacuum_truncate)
+ if (opts->vacuum_truncate == TERNARY_TRUE)
params.truncate = VACOPTVALUE_ENABLED;
else
params.truncate = VACOPTVALUE_DISABLED;
diff --git a/src/include/access/reloptions.h b/src/include/access/reloptions.h
index a604a4702c..a436697658 100644
--- a/src/include/access/reloptions.h
+++ b/src/include/access/reloptions.h
@@ -29,6 +29,7 @@
typedef enum relopt_type
{
RELOPT_TYPE_BOOL,
+ RELOPT_TYPE_TERNARY, /* on, off, unset */
RELOPT_TYPE_INT,
RELOPT_TYPE_REAL,
RELOPT_TYPE_ENUM,
@@ -80,6 +81,7 @@ typedef struct relopt_value
union
{
bool bool_val;
+ ternary ternary_val;
int int_val;
double real_val;
int enum_val;
@@ -94,6 +96,12 @@ typedef struct relopt_bool
bool default_val;
} relopt_bool;
+typedef struct relopt_rernary
+{
+ relopt_gen gen;
+ int default_val;
+} relopt_ternary;
+
typedef struct relopt_int
{
relopt_gen gen;
@@ -152,19 +160,6 @@ typedef struct
const char *optname; /* option's name */
relopt_type opttype; /* option's datatype */
int offset; /* offset of field in result struct */
-
- /*
- * isset_offset is an optional offset of a field in the result struct that
- * stores whether the option is explicitly set for the relation or if it
- * just picked up the default value. In most cases, this can be
- * accomplished by giving the reloption a special out-of-range default
- * value (e.g., some integer reloptions use -2), but this isn't always
- * possible. For example, a Boolean reloption cannot be given an
- * out-of-range default, so we need another way to discover the source of
- * its value. This offset is only used if given a value greater than
- * zero.
- */
- int isset_offset;
} relopt_parse_elt;
/* Local reloption definition */
@@ -195,6 +190,8 @@ typedef struct local_relopts
extern relopt_kind add_reloption_kind(void);
extern void add_bool_reloption(bits32 kinds, const char *name, const char *desc,
bool default_val, LOCKMODE lockmode);
+extern void add_ternary_reloption(bits32 kinds, const char *name,
+ const char *desc, int default_val, LOCKMODE lockmode);
extern void add_int_reloption(bits32 kinds, const char *name, const char *desc,
int default_val, int min_val, int max_val,
LOCKMODE lockmode);
@@ -214,6 +211,9 @@ extern void register_reloptions_validator(local_relopts *relopts,
extern void add_local_bool_reloption(local_relopts *relopts, const char *name,
const char *desc, bool default_val,
int offset);
+extern void add_local_ternary_reloption(local_relopts *relopts,
+ const char *name, const char *desc,
+ ternary default_val, int offset);
extern void add_local_int_reloption(local_relopts *relopts, const char *name,
const char *desc, int default_val,
int min_val, int max_val, int offset);
diff --git a/src/include/c.h b/src/include/c.h
index 39022f8a9d..9c59ccccf8 100644
--- a/src/include/c.h
+++ b/src/include/c.h
@@ -488,6 +488,22 @@ typedef void (*pg_funcptr_t) (void);
#include <stdbool.h>
+/*
+ * ternary
+ * Boolean value with an extrea "unset" option
+ *
+ * Ternary data type is used in relation options that can be "true", "false" or
+ * "unset". Since relation options are used deep inside the PostgreSQL code,
+ * this type is declared globally.
+*/
+
+typedef enum ternary
+{
+ TERNARY_FALSE = 0,
+ TERNARY_TRUE = 1,
+ TERNARY_UNSET = -1
+} ternary;
+
/* ----------------------------------------------------------------
* Section 3: standard system types
diff --git a/src/include/utils/rel.h b/src/include/utils/rel.h
index b552359915..7346d618f9 100644
--- a/src/include/utils/rel.h
+++ b/src/include/utils/rel.h
@@ -346,8 +346,7 @@ typedef struct StdRdOptions
bool user_catalog_table; /* use as an additional catalog relation */
int parallel_workers; /* max number of parallel workers */
StdRdOptIndexCleanup vacuum_index_cleanup; /* controls index vacuuming */
- bool vacuum_truncate; /* enables vacuum to truncate a relation */
- bool vacuum_truncate_set; /* whether vacuum_truncate is set */
+ ternary vacuum_truncate; /* enables vacuum to truncate a relation */
/*
* Fraction of pages in a relation that vacuum can eagerly scan and fail
--
2.39.2
--nextPart5129932.5fSG56mABF
Content-Disposition: attachment;
filename="v2-0003-Add-alias-to-be-used-as-unset-state.patch"
Content-Transfer-Encoding: 7Bit
Content-Type: text/x-patch; charset="unicode-2-0-utf-8";
name="v2-0003-Add-alias-to-be-used-as-unset-state.patch"
^ permalink raw reply [nested|flat] 868+ messages in thread
* [PATCH v2 2/4] Introduce ternary reloptions
@ 2025-09-04 14:41 Nikolay Shaplov <[email protected]>
0 siblings, 0 replies; 868+ messages in thread
From: Nikolay Shaplov @ 2025-09-04 14:41 UTC (permalink / raw)
Introduce ternary reloption as a replacement for current `vacuum_truncate`
implementation. Remove `vacuum_truncate_set` additional flag and using
`TERNARY_UNSET` value instead.
---
src/backend/access/common/reloptions.c | 129 ++++++++++++++++++++-----
src/backend/commands/vacuum.c | 4 +-
src/include/access/reloptions.h | 26 ++---
src/include/c.h | 16 +++
src/include/utils/rel.h | 3 +-
5 files changed, 135 insertions(+), 43 deletions(-)
diff --git a/src/backend/access/common/reloptions.c b/src/backend/access/common/reloptions.c
index 0af3fea68f..f543f11001 100644
--- a/src/backend/access/common/reloptions.c
+++ b/src/backend/access/common/reloptions.c
@@ -40,9 +40,9 @@
*
* To add an option:
*
- * (i) decide on a type (bool, integer, real, enum, string), name, default
- * value, upper and lower bounds (if applicable); for strings, consider a
- * validation routine.
+ * (i) decide on a type (bool, ternary, integer, real, enum, string), name,
+ * default value, upper and lower bounds (if applicable); for strings,
+ * consider a validation routine.
* (ii) add a record below (or use add_<type>_reloption).
* (iii) add it to the appropriate options struct (perhaps StdRdOptions)
* (iv) add it to the appropriate handling routine (perhaps
@@ -147,15 +147,6 @@ static relopt_bool boolRelOpts[] =
},
false
},
- {
- {
- "vacuum_truncate",
- "Enables vacuum to truncate empty pages at the end of this table",
- RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
- ShareUpdateExclusiveLock
- },
- true
- },
{
{
"deduplicate_items",
@@ -170,6 +161,21 @@ static relopt_bool boolRelOpts[] =
{{NULL}}
};
+static relopt_ternary ternaryRelOpts[] =
+{
+ {
+ {
+ "vacuum_truncate",
+ "Enables vacuum to truncate empty pages at the end of this table",
+ RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
+ ShareUpdateExclusiveLock
+ },
+ TERNARY_UNSET
+ },
+ /* list terminator */
+ {{NULL}}
+};
+
static relopt_int intRelOpts[] =
{
{
@@ -600,6 +606,13 @@ initialize_reloptions(void)
boolRelOpts[i].gen.lockmode));
j++;
}
+ for (i = 0; ternaryRelOpts[i].gen.name; i++)
+ {
+ Assert(DoLockModesConflict(ternaryRelOpts[i].gen.lockmode,
+ ternaryRelOpts[i].gen.lockmode));
+ j++;
+ }
+
for (i = 0; intRelOpts[i].gen.name; i++)
{
Assert(DoLockModesConflict(intRelOpts[i].gen.lockmode,
@@ -640,6 +653,14 @@ initialize_reloptions(void)
j++;
}
+ for (i = 0; ternaryRelOpts[i].gen.name; i++)
+ {
+ relOpts[j] = &ternaryRelOpts[i].gen;
+ relOpts[j]->type = RELOPT_TYPE_TERNARY;
+ relOpts[j]->namelen = strlen(relOpts[j]->name);
+ j++;
+ }
+
for (i = 0; intRelOpts[i].gen.name; i++)
{
relOpts[j] = &intRelOpts[i].gen;
@@ -800,6 +821,9 @@ allocate_reloption(bits32 kinds, int type, const char *name, const char *desc,
case RELOPT_TYPE_BOOL:
size = sizeof(relopt_bool);
break;
+ case RELOPT_TYPE_TERNARY:
+ size = sizeof(relopt_ternary);
+ break;
case RELOPT_TYPE_INT:
size = sizeof(relopt_int);
break;
@@ -883,6 +907,54 @@ add_local_bool_reloption(local_relopts *relopts, const char *name,
add_local_reloption(relopts, (relopt_gen *) newoption, offset);
}
+/*
+ * init_ternary_reloption
+ * Allocate and initialize a new ternary reloption
+ */
+static relopt_ternary *
+init_ternary_reloption(bits32 kinds, const char *name, const char *desc,
+ ternary default_val, LOCKMODE lockmode)
+{
+ relopt_ternary *newoption;
+
+ newoption = (relopt_ternary *) allocate_reloption(kinds,
+ RELOPT_TYPE_TERNARY, name, desc, lockmode);
+ newoption->default_val = default_val;
+
+ return newoption;
+}
+
+/*
+ * add_ternary_reloption
+ * Add a new ternary reloption
+ */
+void
+add_ternary_reloption(bits32 kinds, const char *name, const char *desc,
+ ternary default_val, LOCKMODE lockmode)
+{
+ relopt_ternary *newoption = init_ternary_reloption(kinds, name, desc,
+ default_val, lockmode);
+
+ add_reloption((relopt_gen *) newoption);
+}
+
+/*
+ * add_local_ternary_reloption
+ * Add a new ternary local reloption
+ *
+ * 'offset' is offset of ternary-typed field.
+ */
+void
+add_local_ternary_reloption(local_relopts *relopts, const char *name,
+ const char *desc, ternary default_val,
+ int offset)
+{
+ relopt_ternary *newoption = init_ternary_reloption(RELOPT_KIND_LOCAL,
+ name, desc,
+ default_val, 0);
+
+ add_local_reloption(relopts, (relopt_gen *) newoption, offset);
+}
/*
* init_real_reloption
@@ -1617,6 +1689,18 @@ parse_one_reloption(relopt_value *option, char *text_str, int text_len,
option->gen->name, value)));
}
break;
+ case RELOPT_TYPE_TERNARY:
+ {
+ bool b;
+ parsed = parse_bool(value, &b);
+ option->values.ternary_val = b ? TERNARY_TRUE : TERNARY_FALSE;
+ if (validate && !parsed)
+ ereport(ERROR,
+ (errcode(ERRCODE_INVALID_PARAMETER_VALUE),
+ errmsg("invalid value for ternary option \"%s\": %s",
+ option->gen->name, value)));
+ }
+ break;
case RELOPT_TYPE_INT:
{
relopt_int *optint = (relopt_int *) option->gen;
@@ -1780,17 +1864,6 @@ fillRelOptions(void *rdopts, Size basesize,
char *itempos = ((char *) rdopts) + elems[j].offset;
char *string_val;
- /*
- * If isset_offset is provided, store whether the reloption is
- * set there.
- */
- if (elems[j].isset_offset > 0)
- {
- char *setpos = ((char *) rdopts) + elems[j].isset_offset;
-
- *(bool *) setpos = options[i].isset;
- }
-
switch (options[i].gen->type)
{
case RELOPT_TYPE_BOOL:
@@ -1798,6 +1871,11 @@ fillRelOptions(void *rdopts, Size basesize,
options[i].values.bool_val :
((relopt_bool *) options[i].gen)->default_val;
break;
+ case RELOPT_TYPE_TERNARY:
+ *(ternary *) itempos = options[i].isset ?
+ options[i].values.ternary_val :
+ ((relopt_ternary *) options[i].gen)->default_val;
+ break;
case RELOPT_TYPE_INT:
*(int *) itempos = options[i].isset ?
options[i].values.int_val :
@@ -1912,8 +1990,8 @@ default_reloptions(Datum reloptions, bool validate, relopt_kind kind)
offsetof(StdRdOptions, parallel_workers)},
{"vacuum_index_cleanup", RELOPT_TYPE_ENUM,
offsetof(StdRdOptions, vacuum_index_cleanup)},
- {"vacuum_truncate", RELOPT_TYPE_BOOL,
- offsetof(StdRdOptions, vacuum_truncate), offsetof(StdRdOptions, vacuum_truncate_set)},
+ {"vacuum_truncate", RELOPT_TYPE_TERNARY,
+ offsetof(StdRdOptions, vacuum_truncate)},
{"vacuum_max_eager_freeze_failure_rate", RELOPT_TYPE_REAL,
offsetof(StdRdOptions, vacuum_max_eager_freeze_failure_rate)}
};
@@ -1993,7 +2071,6 @@ build_local_reloptions(local_relopts *relopts, Datum options, bool validate)
elems[i].optname = opt->option->name;
elems[i].opttype = opt->option->type;
elems[i].offset = opt->offset;
- elems[i].isset_offset = 0; /* not supported for local relopts yet */
i++;
}
diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c
index 733ef40ae7..977babff54 100644
--- a/src/backend/commands/vacuum.c
+++ b/src/backend/commands/vacuum.c
@@ -2221,9 +2221,9 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams params,
{
StdRdOptions *opts = (StdRdOptions *) rel->rd_options;
- if (opts && opts->vacuum_truncate_set)
+ if (opts && opts->vacuum_truncate != TERNARY_UNSET)
{
- if (opts->vacuum_truncate)
+ if (opts->vacuum_truncate == TERNARY_TRUE)
params.truncate = VACOPTVALUE_ENABLED;
else
params.truncate = VACOPTVALUE_DISABLED;
diff --git a/src/include/access/reloptions.h b/src/include/access/reloptions.h
index a604a4702c..a436697658 100644
--- a/src/include/access/reloptions.h
+++ b/src/include/access/reloptions.h
@@ -29,6 +29,7 @@
typedef enum relopt_type
{
RELOPT_TYPE_BOOL,
+ RELOPT_TYPE_TERNARY, /* on, off, unset */
RELOPT_TYPE_INT,
RELOPT_TYPE_REAL,
RELOPT_TYPE_ENUM,
@@ -80,6 +81,7 @@ typedef struct relopt_value
union
{
bool bool_val;
+ ternary ternary_val;
int int_val;
double real_val;
int enum_val;
@@ -94,6 +96,12 @@ typedef struct relopt_bool
bool default_val;
} relopt_bool;
+typedef struct relopt_rernary
+{
+ relopt_gen gen;
+ int default_val;
+} relopt_ternary;
+
typedef struct relopt_int
{
relopt_gen gen;
@@ -152,19 +160,6 @@ typedef struct
const char *optname; /* option's name */
relopt_type opttype; /* option's datatype */
int offset; /* offset of field in result struct */
-
- /*
- * isset_offset is an optional offset of a field in the result struct that
- * stores whether the option is explicitly set for the relation or if it
- * just picked up the default value. In most cases, this can be
- * accomplished by giving the reloption a special out-of-range default
- * value (e.g., some integer reloptions use -2), but this isn't always
- * possible. For example, a Boolean reloption cannot be given an
- * out-of-range default, so we need another way to discover the source of
- * its value. This offset is only used if given a value greater than
- * zero.
- */
- int isset_offset;
} relopt_parse_elt;
/* Local reloption definition */
@@ -195,6 +190,8 @@ typedef struct local_relopts
extern relopt_kind add_reloption_kind(void);
extern void add_bool_reloption(bits32 kinds, const char *name, const char *desc,
bool default_val, LOCKMODE lockmode);
+extern void add_ternary_reloption(bits32 kinds, const char *name,
+ const char *desc, int default_val, LOCKMODE lockmode);
extern void add_int_reloption(bits32 kinds, const char *name, const char *desc,
int default_val, int min_val, int max_val,
LOCKMODE lockmode);
@@ -214,6 +211,9 @@ extern void register_reloptions_validator(local_relopts *relopts,
extern void add_local_bool_reloption(local_relopts *relopts, const char *name,
const char *desc, bool default_val,
int offset);
+extern void add_local_ternary_reloption(local_relopts *relopts,
+ const char *name, const char *desc,
+ ternary default_val, int offset);
extern void add_local_int_reloption(local_relopts *relopts, const char *name,
const char *desc, int default_val,
int min_val, int max_val, int offset);
diff --git a/src/include/c.h b/src/include/c.h
index 39022f8a9d..9c59ccccf8 100644
--- a/src/include/c.h
+++ b/src/include/c.h
@@ -488,6 +488,22 @@ typedef void (*pg_funcptr_t) (void);
#include <stdbool.h>
+/*
+ * ternary
+ * Boolean value with an extrea "unset" option
+ *
+ * Ternary data type is used in relation options that can be "true", "false" or
+ * "unset". Since relation options are used deep inside the PostgreSQL code,
+ * this type is declared globally.
+*/
+
+typedef enum ternary
+{
+ TERNARY_FALSE = 0,
+ TERNARY_TRUE = 1,
+ TERNARY_UNSET = -1
+} ternary;
+
/* ----------------------------------------------------------------
* Section 3: standard system types
diff --git a/src/include/utils/rel.h b/src/include/utils/rel.h
index b552359915..7346d618f9 100644
--- a/src/include/utils/rel.h
+++ b/src/include/utils/rel.h
@@ -346,8 +346,7 @@ typedef struct StdRdOptions
bool user_catalog_table; /* use as an additional catalog relation */
int parallel_workers; /* max number of parallel workers */
StdRdOptIndexCleanup vacuum_index_cleanup; /* controls index vacuuming */
- bool vacuum_truncate; /* enables vacuum to truncate a relation */
- bool vacuum_truncate_set; /* whether vacuum_truncate is set */
+ ternary vacuum_truncate; /* enables vacuum to truncate a relation */
/*
* Fraction of pages in a relation that vacuum can eagerly scan and fail
--
2.39.2
--nextPart5129932.5fSG56mABF
Content-Disposition: attachment;
filename="v2-0003-Add-alias-to-be-used-as-unset-state.patch"
Content-Transfer-Encoding: 7Bit
Content-Type: text/x-patch; charset="unicode-2-0-utf-8";
name="v2-0003-Add-alias-to-be-used-as-unset-state.patch"
^ permalink raw reply [nested|flat] 868+ messages in thread
* [PATCH v2 2/4] Introduce ternary reloptions
@ 2025-09-04 14:41 Nikolay Shaplov <[email protected]>
0 siblings, 0 replies; 868+ messages in thread
From: Nikolay Shaplov @ 2025-09-04 14:41 UTC (permalink / raw)
Introduce ternary reloption as a replacement for current `vacuum_truncate`
implementation. Remove `vacuum_truncate_set` additional flag and using
`TERNARY_UNSET` value instead.
---
src/backend/access/common/reloptions.c | 129 ++++++++++++++++++++-----
src/backend/commands/vacuum.c | 4 +-
src/include/access/reloptions.h | 26 ++---
src/include/c.h | 16 +++
src/include/utils/rel.h | 3 +-
5 files changed, 135 insertions(+), 43 deletions(-)
diff --git a/src/backend/access/common/reloptions.c b/src/backend/access/common/reloptions.c
index 0af3fea68f..f543f11001 100644
--- a/src/backend/access/common/reloptions.c
+++ b/src/backend/access/common/reloptions.c
@@ -40,9 +40,9 @@
*
* To add an option:
*
- * (i) decide on a type (bool, integer, real, enum, string), name, default
- * value, upper and lower bounds (if applicable); for strings, consider a
- * validation routine.
+ * (i) decide on a type (bool, ternary, integer, real, enum, string), name,
+ * default value, upper and lower bounds (if applicable); for strings,
+ * consider a validation routine.
* (ii) add a record below (or use add_<type>_reloption).
* (iii) add it to the appropriate options struct (perhaps StdRdOptions)
* (iv) add it to the appropriate handling routine (perhaps
@@ -147,15 +147,6 @@ static relopt_bool boolRelOpts[] =
},
false
},
- {
- {
- "vacuum_truncate",
- "Enables vacuum to truncate empty pages at the end of this table",
- RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
- ShareUpdateExclusiveLock
- },
- true
- },
{
{
"deduplicate_items",
@@ -170,6 +161,21 @@ static relopt_bool boolRelOpts[] =
{{NULL}}
};
+static relopt_ternary ternaryRelOpts[] =
+{
+ {
+ {
+ "vacuum_truncate",
+ "Enables vacuum to truncate empty pages at the end of this table",
+ RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
+ ShareUpdateExclusiveLock
+ },
+ TERNARY_UNSET
+ },
+ /* list terminator */
+ {{NULL}}
+};
+
static relopt_int intRelOpts[] =
{
{
@@ -600,6 +606,13 @@ initialize_reloptions(void)
boolRelOpts[i].gen.lockmode));
j++;
}
+ for (i = 0; ternaryRelOpts[i].gen.name; i++)
+ {
+ Assert(DoLockModesConflict(ternaryRelOpts[i].gen.lockmode,
+ ternaryRelOpts[i].gen.lockmode));
+ j++;
+ }
+
for (i = 0; intRelOpts[i].gen.name; i++)
{
Assert(DoLockModesConflict(intRelOpts[i].gen.lockmode,
@@ -640,6 +653,14 @@ initialize_reloptions(void)
j++;
}
+ for (i = 0; ternaryRelOpts[i].gen.name; i++)
+ {
+ relOpts[j] = &ternaryRelOpts[i].gen;
+ relOpts[j]->type = RELOPT_TYPE_TERNARY;
+ relOpts[j]->namelen = strlen(relOpts[j]->name);
+ j++;
+ }
+
for (i = 0; intRelOpts[i].gen.name; i++)
{
relOpts[j] = &intRelOpts[i].gen;
@@ -800,6 +821,9 @@ allocate_reloption(bits32 kinds, int type, const char *name, const char *desc,
case RELOPT_TYPE_BOOL:
size = sizeof(relopt_bool);
break;
+ case RELOPT_TYPE_TERNARY:
+ size = sizeof(relopt_ternary);
+ break;
case RELOPT_TYPE_INT:
size = sizeof(relopt_int);
break;
@@ -883,6 +907,54 @@ add_local_bool_reloption(local_relopts *relopts, const char *name,
add_local_reloption(relopts, (relopt_gen *) newoption, offset);
}
+/*
+ * init_ternary_reloption
+ * Allocate and initialize a new ternary reloption
+ */
+static relopt_ternary *
+init_ternary_reloption(bits32 kinds, const char *name, const char *desc,
+ ternary default_val, LOCKMODE lockmode)
+{
+ relopt_ternary *newoption;
+
+ newoption = (relopt_ternary *) allocate_reloption(kinds,
+ RELOPT_TYPE_TERNARY, name, desc, lockmode);
+ newoption->default_val = default_val;
+
+ return newoption;
+}
+
+/*
+ * add_ternary_reloption
+ * Add a new ternary reloption
+ */
+void
+add_ternary_reloption(bits32 kinds, const char *name, const char *desc,
+ ternary default_val, LOCKMODE lockmode)
+{
+ relopt_ternary *newoption = init_ternary_reloption(kinds, name, desc,
+ default_val, lockmode);
+
+ add_reloption((relopt_gen *) newoption);
+}
+
+/*
+ * add_local_ternary_reloption
+ * Add a new ternary local reloption
+ *
+ * 'offset' is offset of ternary-typed field.
+ */
+void
+add_local_ternary_reloption(local_relopts *relopts, const char *name,
+ const char *desc, ternary default_val,
+ int offset)
+{
+ relopt_ternary *newoption = init_ternary_reloption(RELOPT_KIND_LOCAL,
+ name, desc,
+ default_val, 0);
+
+ add_local_reloption(relopts, (relopt_gen *) newoption, offset);
+}
/*
* init_real_reloption
@@ -1617,6 +1689,18 @@ parse_one_reloption(relopt_value *option, char *text_str, int text_len,
option->gen->name, value)));
}
break;
+ case RELOPT_TYPE_TERNARY:
+ {
+ bool b;
+ parsed = parse_bool(value, &b);
+ option->values.ternary_val = b ? TERNARY_TRUE : TERNARY_FALSE;
+ if (validate && !parsed)
+ ereport(ERROR,
+ (errcode(ERRCODE_INVALID_PARAMETER_VALUE),
+ errmsg("invalid value for ternary option \"%s\": %s",
+ option->gen->name, value)));
+ }
+ break;
case RELOPT_TYPE_INT:
{
relopt_int *optint = (relopt_int *) option->gen;
@@ -1780,17 +1864,6 @@ fillRelOptions(void *rdopts, Size basesize,
char *itempos = ((char *) rdopts) + elems[j].offset;
char *string_val;
- /*
- * If isset_offset is provided, store whether the reloption is
- * set there.
- */
- if (elems[j].isset_offset > 0)
- {
- char *setpos = ((char *) rdopts) + elems[j].isset_offset;
-
- *(bool *) setpos = options[i].isset;
- }
-
switch (options[i].gen->type)
{
case RELOPT_TYPE_BOOL:
@@ -1798,6 +1871,11 @@ fillRelOptions(void *rdopts, Size basesize,
options[i].values.bool_val :
((relopt_bool *) options[i].gen)->default_val;
break;
+ case RELOPT_TYPE_TERNARY:
+ *(ternary *) itempos = options[i].isset ?
+ options[i].values.ternary_val :
+ ((relopt_ternary *) options[i].gen)->default_val;
+ break;
case RELOPT_TYPE_INT:
*(int *) itempos = options[i].isset ?
options[i].values.int_val :
@@ -1912,8 +1990,8 @@ default_reloptions(Datum reloptions, bool validate, relopt_kind kind)
offsetof(StdRdOptions, parallel_workers)},
{"vacuum_index_cleanup", RELOPT_TYPE_ENUM,
offsetof(StdRdOptions, vacuum_index_cleanup)},
- {"vacuum_truncate", RELOPT_TYPE_BOOL,
- offsetof(StdRdOptions, vacuum_truncate), offsetof(StdRdOptions, vacuum_truncate_set)},
+ {"vacuum_truncate", RELOPT_TYPE_TERNARY,
+ offsetof(StdRdOptions, vacuum_truncate)},
{"vacuum_max_eager_freeze_failure_rate", RELOPT_TYPE_REAL,
offsetof(StdRdOptions, vacuum_max_eager_freeze_failure_rate)}
};
@@ -1993,7 +2071,6 @@ build_local_reloptions(local_relopts *relopts, Datum options, bool validate)
elems[i].optname = opt->option->name;
elems[i].opttype = opt->option->type;
elems[i].offset = opt->offset;
- elems[i].isset_offset = 0; /* not supported for local relopts yet */
i++;
}
diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c
index 733ef40ae7..977babff54 100644
--- a/src/backend/commands/vacuum.c
+++ b/src/backend/commands/vacuum.c
@@ -2221,9 +2221,9 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams params,
{
StdRdOptions *opts = (StdRdOptions *) rel->rd_options;
- if (opts && opts->vacuum_truncate_set)
+ if (opts && opts->vacuum_truncate != TERNARY_UNSET)
{
- if (opts->vacuum_truncate)
+ if (opts->vacuum_truncate == TERNARY_TRUE)
params.truncate = VACOPTVALUE_ENABLED;
else
params.truncate = VACOPTVALUE_DISABLED;
diff --git a/src/include/access/reloptions.h b/src/include/access/reloptions.h
index a604a4702c..a436697658 100644
--- a/src/include/access/reloptions.h
+++ b/src/include/access/reloptions.h
@@ -29,6 +29,7 @@
typedef enum relopt_type
{
RELOPT_TYPE_BOOL,
+ RELOPT_TYPE_TERNARY, /* on, off, unset */
RELOPT_TYPE_INT,
RELOPT_TYPE_REAL,
RELOPT_TYPE_ENUM,
@@ -80,6 +81,7 @@ typedef struct relopt_value
union
{
bool bool_val;
+ ternary ternary_val;
int int_val;
double real_val;
int enum_val;
@@ -94,6 +96,12 @@ typedef struct relopt_bool
bool default_val;
} relopt_bool;
+typedef struct relopt_rernary
+{
+ relopt_gen gen;
+ int default_val;
+} relopt_ternary;
+
typedef struct relopt_int
{
relopt_gen gen;
@@ -152,19 +160,6 @@ typedef struct
const char *optname; /* option's name */
relopt_type opttype; /* option's datatype */
int offset; /* offset of field in result struct */
-
- /*
- * isset_offset is an optional offset of a field in the result struct that
- * stores whether the option is explicitly set for the relation or if it
- * just picked up the default value. In most cases, this can be
- * accomplished by giving the reloption a special out-of-range default
- * value (e.g., some integer reloptions use -2), but this isn't always
- * possible. For example, a Boolean reloption cannot be given an
- * out-of-range default, so we need another way to discover the source of
- * its value. This offset is only used if given a value greater than
- * zero.
- */
- int isset_offset;
} relopt_parse_elt;
/* Local reloption definition */
@@ -195,6 +190,8 @@ typedef struct local_relopts
extern relopt_kind add_reloption_kind(void);
extern void add_bool_reloption(bits32 kinds, const char *name, const char *desc,
bool default_val, LOCKMODE lockmode);
+extern void add_ternary_reloption(bits32 kinds, const char *name,
+ const char *desc, int default_val, LOCKMODE lockmode);
extern void add_int_reloption(bits32 kinds, const char *name, const char *desc,
int default_val, int min_val, int max_val,
LOCKMODE lockmode);
@@ -214,6 +211,9 @@ extern void register_reloptions_validator(local_relopts *relopts,
extern void add_local_bool_reloption(local_relopts *relopts, const char *name,
const char *desc, bool default_val,
int offset);
+extern void add_local_ternary_reloption(local_relopts *relopts,
+ const char *name, const char *desc,
+ ternary default_val, int offset);
extern void add_local_int_reloption(local_relopts *relopts, const char *name,
const char *desc, int default_val,
int min_val, int max_val, int offset);
diff --git a/src/include/c.h b/src/include/c.h
index 39022f8a9d..9c59ccccf8 100644
--- a/src/include/c.h
+++ b/src/include/c.h
@@ -488,6 +488,22 @@ typedef void (*pg_funcptr_t) (void);
#include <stdbool.h>
+/*
+ * ternary
+ * Boolean value with an extrea "unset" option
+ *
+ * Ternary data type is used in relation options that can be "true", "false" or
+ * "unset". Since relation options are used deep inside the PostgreSQL code,
+ * this type is declared globally.
+*/
+
+typedef enum ternary
+{
+ TERNARY_FALSE = 0,
+ TERNARY_TRUE = 1,
+ TERNARY_UNSET = -1
+} ternary;
+
/* ----------------------------------------------------------------
* Section 3: standard system types
diff --git a/src/include/utils/rel.h b/src/include/utils/rel.h
index b552359915..7346d618f9 100644
--- a/src/include/utils/rel.h
+++ b/src/include/utils/rel.h
@@ -346,8 +346,7 @@ typedef struct StdRdOptions
bool user_catalog_table; /* use as an additional catalog relation */
int parallel_workers; /* max number of parallel workers */
StdRdOptIndexCleanup vacuum_index_cleanup; /* controls index vacuuming */
- bool vacuum_truncate; /* enables vacuum to truncate a relation */
- bool vacuum_truncate_set; /* whether vacuum_truncate is set */
+ ternary vacuum_truncate; /* enables vacuum to truncate a relation */
/*
* Fraction of pages in a relation that vacuum can eagerly scan and fail
--
2.39.2
--nextPart5129932.5fSG56mABF
Content-Disposition: attachment;
filename="v2-0003-Add-alias-to-be-used-as-unset-state.patch"
Content-Transfer-Encoding: 7Bit
Content-Type: text/x-patch; charset="unicode-2-0-utf-8";
name="v2-0003-Add-alias-to-be-used-as-unset-state.patch"
^ permalink raw reply [nested|flat] 868+ messages in thread
* [PATCH v2 2/4] Introduce ternary reloptions
@ 2025-09-04 14:41 Nikolay Shaplov <[email protected]>
0 siblings, 0 replies; 868+ messages in thread
From: Nikolay Shaplov @ 2025-09-04 14:41 UTC (permalink / raw)
Introduce ternary reloption as a replacement for current `vacuum_truncate`
implementation. Remove `vacuum_truncate_set` additional flag and using
`TERNARY_UNSET` value instead.
---
src/backend/access/common/reloptions.c | 129 ++++++++++++++++++++-----
src/backend/commands/vacuum.c | 4 +-
src/include/access/reloptions.h | 26 ++---
src/include/c.h | 16 +++
src/include/utils/rel.h | 3 +-
5 files changed, 135 insertions(+), 43 deletions(-)
diff --git a/src/backend/access/common/reloptions.c b/src/backend/access/common/reloptions.c
index 0af3fea68f..f543f11001 100644
--- a/src/backend/access/common/reloptions.c
+++ b/src/backend/access/common/reloptions.c
@@ -40,9 +40,9 @@
*
* To add an option:
*
- * (i) decide on a type (bool, integer, real, enum, string), name, default
- * value, upper and lower bounds (if applicable); for strings, consider a
- * validation routine.
+ * (i) decide on a type (bool, ternary, integer, real, enum, string), name,
+ * default value, upper and lower bounds (if applicable); for strings,
+ * consider a validation routine.
* (ii) add a record below (or use add_<type>_reloption).
* (iii) add it to the appropriate options struct (perhaps StdRdOptions)
* (iv) add it to the appropriate handling routine (perhaps
@@ -147,15 +147,6 @@ static relopt_bool boolRelOpts[] =
},
false
},
- {
- {
- "vacuum_truncate",
- "Enables vacuum to truncate empty pages at the end of this table",
- RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
- ShareUpdateExclusiveLock
- },
- true
- },
{
{
"deduplicate_items",
@@ -170,6 +161,21 @@ static relopt_bool boolRelOpts[] =
{{NULL}}
};
+static relopt_ternary ternaryRelOpts[] =
+{
+ {
+ {
+ "vacuum_truncate",
+ "Enables vacuum to truncate empty pages at the end of this table",
+ RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
+ ShareUpdateExclusiveLock
+ },
+ TERNARY_UNSET
+ },
+ /* list terminator */
+ {{NULL}}
+};
+
static relopt_int intRelOpts[] =
{
{
@@ -600,6 +606,13 @@ initialize_reloptions(void)
boolRelOpts[i].gen.lockmode));
j++;
}
+ for (i = 0; ternaryRelOpts[i].gen.name; i++)
+ {
+ Assert(DoLockModesConflict(ternaryRelOpts[i].gen.lockmode,
+ ternaryRelOpts[i].gen.lockmode));
+ j++;
+ }
+
for (i = 0; intRelOpts[i].gen.name; i++)
{
Assert(DoLockModesConflict(intRelOpts[i].gen.lockmode,
@@ -640,6 +653,14 @@ initialize_reloptions(void)
j++;
}
+ for (i = 0; ternaryRelOpts[i].gen.name; i++)
+ {
+ relOpts[j] = &ternaryRelOpts[i].gen;
+ relOpts[j]->type = RELOPT_TYPE_TERNARY;
+ relOpts[j]->namelen = strlen(relOpts[j]->name);
+ j++;
+ }
+
for (i = 0; intRelOpts[i].gen.name; i++)
{
relOpts[j] = &intRelOpts[i].gen;
@@ -800,6 +821,9 @@ allocate_reloption(bits32 kinds, int type, const char *name, const char *desc,
case RELOPT_TYPE_BOOL:
size = sizeof(relopt_bool);
break;
+ case RELOPT_TYPE_TERNARY:
+ size = sizeof(relopt_ternary);
+ break;
case RELOPT_TYPE_INT:
size = sizeof(relopt_int);
break;
@@ -883,6 +907,54 @@ add_local_bool_reloption(local_relopts *relopts, const char *name,
add_local_reloption(relopts, (relopt_gen *) newoption, offset);
}
+/*
+ * init_ternary_reloption
+ * Allocate and initialize a new ternary reloption
+ */
+static relopt_ternary *
+init_ternary_reloption(bits32 kinds, const char *name, const char *desc,
+ ternary default_val, LOCKMODE lockmode)
+{
+ relopt_ternary *newoption;
+
+ newoption = (relopt_ternary *) allocate_reloption(kinds,
+ RELOPT_TYPE_TERNARY, name, desc, lockmode);
+ newoption->default_val = default_val;
+
+ return newoption;
+}
+
+/*
+ * add_ternary_reloption
+ * Add a new ternary reloption
+ */
+void
+add_ternary_reloption(bits32 kinds, const char *name, const char *desc,
+ ternary default_val, LOCKMODE lockmode)
+{
+ relopt_ternary *newoption = init_ternary_reloption(kinds, name, desc,
+ default_val, lockmode);
+
+ add_reloption((relopt_gen *) newoption);
+}
+
+/*
+ * add_local_ternary_reloption
+ * Add a new ternary local reloption
+ *
+ * 'offset' is offset of ternary-typed field.
+ */
+void
+add_local_ternary_reloption(local_relopts *relopts, const char *name,
+ const char *desc, ternary default_val,
+ int offset)
+{
+ relopt_ternary *newoption = init_ternary_reloption(RELOPT_KIND_LOCAL,
+ name, desc,
+ default_val, 0);
+
+ add_local_reloption(relopts, (relopt_gen *) newoption, offset);
+}
/*
* init_real_reloption
@@ -1617,6 +1689,18 @@ parse_one_reloption(relopt_value *option, char *text_str, int text_len,
option->gen->name, value)));
}
break;
+ case RELOPT_TYPE_TERNARY:
+ {
+ bool b;
+ parsed = parse_bool(value, &b);
+ option->values.ternary_val = b ? TERNARY_TRUE : TERNARY_FALSE;
+ if (validate && !parsed)
+ ereport(ERROR,
+ (errcode(ERRCODE_INVALID_PARAMETER_VALUE),
+ errmsg("invalid value for ternary option \"%s\": %s",
+ option->gen->name, value)));
+ }
+ break;
case RELOPT_TYPE_INT:
{
relopt_int *optint = (relopt_int *) option->gen;
@@ -1780,17 +1864,6 @@ fillRelOptions(void *rdopts, Size basesize,
char *itempos = ((char *) rdopts) + elems[j].offset;
char *string_val;
- /*
- * If isset_offset is provided, store whether the reloption is
- * set there.
- */
- if (elems[j].isset_offset > 0)
- {
- char *setpos = ((char *) rdopts) + elems[j].isset_offset;
-
- *(bool *) setpos = options[i].isset;
- }
-
switch (options[i].gen->type)
{
case RELOPT_TYPE_BOOL:
@@ -1798,6 +1871,11 @@ fillRelOptions(void *rdopts, Size basesize,
options[i].values.bool_val :
((relopt_bool *) options[i].gen)->default_val;
break;
+ case RELOPT_TYPE_TERNARY:
+ *(ternary *) itempos = options[i].isset ?
+ options[i].values.ternary_val :
+ ((relopt_ternary *) options[i].gen)->default_val;
+ break;
case RELOPT_TYPE_INT:
*(int *) itempos = options[i].isset ?
options[i].values.int_val :
@@ -1912,8 +1990,8 @@ default_reloptions(Datum reloptions, bool validate, relopt_kind kind)
offsetof(StdRdOptions, parallel_workers)},
{"vacuum_index_cleanup", RELOPT_TYPE_ENUM,
offsetof(StdRdOptions, vacuum_index_cleanup)},
- {"vacuum_truncate", RELOPT_TYPE_BOOL,
- offsetof(StdRdOptions, vacuum_truncate), offsetof(StdRdOptions, vacuum_truncate_set)},
+ {"vacuum_truncate", RELOPT_TYPE_TERNARY,
+ offsetof(StdRdOptions, vacuum_truncate)},
{"vacuum_max_eager_freeze_failure_rate", RELOPT_TYPE_REAL,
offsetof(StdRdOptions, vacuum_max_eager_freeze_failure_rate)}
};
@@ -1993,7 +2071,6 @@ build_local_reloptions(local_relopts *relopts, Datum options, bool validate)
elems[i].optname = opt->option->name;
elems[i].opttype = opt->option->type;
elems[i].offset = opt->offset;
- elems[i].isset_offset = 0; /* not supported for local relopts yet */
i++;
}
diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c
index 733ef40ae7..977babff54 100644
--- a/src/backend/commands/vacuum.c
+++ b/src/backend/commands/vacuum.c
@@ -2221,9 +2221,9 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams params,
{
StdRdOptions *opts = (StdRdOptions *) rel->rd_options;
- if (opts && opts->vacuum_truncate_set)
+ if (opts && opts->vacuum_truncate != TERNARY_UNSET)
{
- if (opts->vacuum_truncate)
+ if (opts->vacuum_truncate == TERNARY_TRUE)
params.truncate = VACOPTVALUE_ENABLED;
else
params.truncate = VACOPTVALUE_DISABLED;
diff --git a/src/include/access/reloptions.h b/src/include/access/reloptions.h
index a604a4702c..a436697658 100644
--- a/src/include/access/reloptions.h
+++ b/src/include/access/reloptions.h
@@ -29,6 +29,7 @@
typedef enum relopt_type
{
RELOPT_TYPE_BOOL,
+ RELOPT_TYPE_TERNARY, /* on, off, unset */
RELOPT_TYPE_INT,
RELOPT_TYPE_REAL,
RELOPT_TYPE_ENUM,
@@ -80,6 +81,7 @@ typedef struct relopt_value
union
{
bool bool_val;
+ ternary ternary_val;
int int_val;
double real_val;
int enum_val;
@@ -94,6 +96,12 @@ typedef struct relopt_bool
bool default_val;
} relopt_bool;
+typedef struct relopt_rernary
+{
+ relopt_gen gen;
+ int default_val;
+} relopt_ternary;
+
typedef struct relopt_int
{
relopt_gen gen;
@@ -152,19 +160,6 @@ typedef struct
const char *optname; /* option's name */
relopt_type opttype; /* option's datatype */
int offset; /* offset of field in result struct */
-
- /*
- * isset_offset is an optional offset of a field in the result struct that
- * stores whether the option is explicitly set for the relation or if it
- * just picked up the default value. In most cases, this can be
- * accomplished by giving the reloption a special out-of-range default
- * value (e.g., some integer reloptions use -2), but this isn't always
- * possible. For example, a Boolean reloption cannot be given an
- * out-of-range default, so we need another way to discover the source of
- * its value. This offset is only used if given a value greater than
- * zero.
- */
- int isset_offset;
} relopt_parse_elt;
/* Local reloption definition */
@@ -195,6 +190,8 @@ typedef struct local_relopts
extern relopt_kind add_reloption_kind(void);
extern void add_bool_reloption(bits32 kinds, const char *name, const char *desc,
bool default_val, LOCKMODE lockmode);
+extern void add_ternary_reloption(bits32 kinds, const char *name,
+ const char *desc, int default_val, LOCKMODE lockmode);
extern void add_int_reloption(bits32 kinds, const char *name, const char *desc,
int default_val, int min_val, int max_val,
LOCKMODE lockmode);
@@ -214,6 +211,9 @@ extern void register_reloptions_validator(local_relopts *relopts,
extern void add_local_bool_reloption(local_relopts *relopts, const char *name,
const char *desc, bool default_val,
int offset);
+extern void add_local_ternary_reloption(local_relopts *relopts,
+ const char *name, const char *desc,
+ ternary default_val, int offset);
extern void add_local_int_reloption(local_relopts *relopts, const char *name,
const char *desc, int default_val,
int min_val, int max_val, int offset);
diff --git a/src/include/c.h b/src/include/c.h
index 39022f8a9d..9c59ccccf8 100644
--- a/src/include/c.h
+++ b/src/include/c.h
@@ -488,6 +488,22 @@ typedef void (*pg_funcptr_t) (void);
#include <stdbool.h>
+/*
+ * ternary
+ * Boolean value with an extrea "unset" option
+ *
+ * Ternary data type is used in relation options that can be "true", "false" or
+ * "unset". Since relation options are used deep inside the PostgreSQL code,
+ * this type is declared globally.
+*/
+
+typedef enum ternary
+{
+ TERNARY_FALSE = 0,
+ TERNARY_TRUE = 1,
+ TERNARY_UNSET = -1
+} ternary;
+
/* ----------------------------------------------------------------
* Section 3: standard system types
diff --git a/src/include/utils/rel.h b/src/include/utils/rel.h
index b552359915..7346d618f9 100644
--- a/src/include/utils/rel.h
+++ b/src/include/utils/rel.h
@@ -346,8 +346,7 @@ typedef struct StdRdOptions
bool user_catalog_table; /* use as an additional catalog relation */
int parallel_workers; /* max number of parallel workers */
StdRdOptIndexCleanup vacuum_index_cleanup; /* controls index vacuuming */
- bool vacuum_truncate; /* enables vacuum to truncate a relation */
- bool vacuum_truncate_set; /* whether vacuum_truncate is set */
+ ternary vacuum_truncate; /* enables vacuum to truncate a relation */
/*
* Fraction of pages in a relation that vacuum can eagerly scan and fail
--
2.39.2
--nextPart5129932.5fSG56mABF
Content-Disposition: attachment;
filename="v2-0003-Add-alias-to-be-used-as-unset-state.patch"
Content-Transfer-Encoding: 7Bit
Content-Type: text/x-patch; charset="unicode-2-0-utf-8";
name="v2-0003-Add-alias-to-be-used-as-unset-state.patch"
^ permalink raw reply [nested|flat] 868+ messages in thread
* [PATCH v2a 2/4] Introduce ternary reloptions
@ 2025-09-04 14:41 Nikolay Shaplov <[email protected]>
0 siblings, 0 replies; 868+ messages in thread
From: Nikolay Shaplov @ 2025-09-04 14:41 UTC (permalink / raw)
Introduce ternary reloption as a replacement for current `vacuum_truncate`
implementation. Remove `vacuum_truncate_set` additional flag and using
`TERNARY_UNSET` value instead.
---
src/backend/access/common/reloptions.c | 129 ++++++++++++++++++++-----
src/backend/commands/vacuum.c | 4 +-
src/include/access/reloptions.h | 26 ++---
src/include/c.h | 16 +++
src/include/utils/rel.h | 3 +-
5 files changed, 135 insertions(+), 43 deletions(-)
diff --git a/src/backend/access/common/reloptions.c b/src/backend/access/common/reloptions.c
index 0af3fea68f..f543f11001 100644
--- a/src/backend/access/common/reloptions.c
+++ b/src/backend/access/common/reloptions.c
@@ -40,9 +40,9 @@
*
* To add an option:
*
- * (i) decide on a type (bool, integer, real, enum, string), name, default
- * value, upper and lower bounds (if applicable); for strings, consider a
- * validation routine.
+ * (i) decide on a type (bool, ternary, integer, real, enum, string), name,
+ * default value, upper and lower bounds (if applicable); for strings,
+ * consider a validation routine.
* (ii) add a record below (or use add_<type>_reloption).
* (iii) add it to the appropriate options struct (perhaps StdRdOptions)
* (iv) add it to the appropriate handling routine (perhaps
@@ -147,15 +147,6 @@ static relopt_bool boolRelOpts[] =
},
false
},
- {
- {
- "vacuum_truncate",
- "Enables vacuum to truncate empty pages at the end of this table",
- RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
- ShareUpdateExclusiveLock
- },
- true
- },
{
{
"deduplicate_items",
@@ -170,6 +161,21 @@ static relopt_bool boolRelOpts[] =
{{NULL}}
};
+static relopt_ternary ternaryRelOpts[] =
+{
+ {
+ {
+ "vacuum_truncate",
+ "Enables vacuum to truncate empty pages at the end of this table",
+ RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
+ ShareUpdateExclusiveLock
+ },
+ TERNARY_UNSET
+ },
+ /* list terminator */
+ {{NULL}}
+};
+
static relopt_int intRelOpts[] =
{
{
@@ -600,6 +606,13 @@ initialize_reloptions(void)
boolRelOpts[i].gen.lockmode));
j++;
}
+ for (i = 0; ternaryRelOpts[i].gen.name; i++)
+ {
+ Assert(DoLockModesConflict(ternaryRelOpts[i].gen.lockmode,
+ ternaryRelOpts[i].gen.lockmode));
+ j++;
+ }
+
for (i = 0; intRelOpts[i].gen.name; i++)
{
Assert(DoLockModesConflict(intRelOpts[i].gen.lockmode,
@@ -640,6 +653,14 @@ initialize_reloptions(void)
j++;
}
+ for (i = 0; ternaryRelOpts[i].gen.name; i++)
+ {
+ relOpts[j] = &ternaryRelOpts[i].gen;
+ relOpts[j]->type = RELOPT_TYPE_TERNARY;
+ relOpts[j]->namelen = strlen(relOpts[j]->name);
+ j++;
+ }
+
for (i = 0; intRelOpts[i].gen.name; i++)
{
relOpts[j] = &intRelOpts[i].gen;
@@ -800,6 +821,9 @@ allocate_reloption(bits32 kinds, int type, const char *name, const char *desc,
case RELOPT_TYPE_BOOL:
size = sizeof(relopt_bool);
break;
+ case RELOPT_TYPE_TERNARY:
+ size = sizeof(relopt_ternary);
+ break;
case RELOPT_TYPE_INT:
size = sizeof(relopt_int);
break;
@@ -883,6 +907,54 @@ add_local_bool_reloption(local_relopts *relopts, const char *name,
add_local_reloption(relopts, (relopt_gen *) newoption, offset);
}
+/*
+ * init_ternary_reloption
+ * Allocate and initialize a new ternary reloption
+ */
+static relopt_ternary *
+init_ternary_reloption(bits32 kinds, const char *name, const char *desc,
+ ternary default_val, LOCKMODE lockmode)
+{
+ relopt_ternary *newoption;
+
+ newoption = (relopt_ternary *) allocate_reloption(kinds,
+ RELOPT_TYPE_TERNARY, name, desc, lockmode);
+ newoption->default_val = default_val;
+
+ return newoption;
+}
+
+/*
+ * add_ternary_reloption
+ * Add a new ternary reloption
+ */
+void
+add_ternary_reloption(bits32 kinds, const char *name, const char *desc,
+ ternary default_val, LOCKMODE lockmode)
+{
+ relopt_ternary *newoption = init_ternary_reloption(kinds, name, desc,
+ default_val, lockmode);
+
+ add_reloption((relopt_gen *) newoption);
+}
+
+/*
+ * add_local_ternary_reloption
+ * Add a new ternary local reloption
+ *
+ * 'offset' is offset of ternary-typed field.
+ */
+void
+add_local_ternary_reloption(local_relopts *relopts, const char *name,
+ const char *desc, ternary default_val,
+ int offset)
+{
+ relopt_ternary *newoption = init_ternary_reloption(RELOPT_KIND_LOCAL,
+ name, desc,
+ default_val, 0);
+
+ add_local_reloption(relopts, (relopt_gen *) newoption, offset);
+}
/*
* init_real_reloption
@@ -1617,6 +1689,18 @@ parse_one_reloption(relopt_value *option, char *text_str, int text_len,
option->gen->name, value)));
}
break;
+ case RELOPT_TYPE_TERNARY:
+ {
+ bool b;
+ parsed = parse_bool(value, &b);
+ option->values.ternary_val = b ? TERNARY_TRUE : TERNARY_FALSE;
+ if (validate && !parsed)
+ ereport(ERROR,
+ (errcode(ERRCODE_INVALID_PARAMETER_VALUE),
+ errmsg("invalid value for ternary option \"%s\": %s",
+ option->gen->name, value)));
+ }
+ break;
case RELOPT_TYPE_INT:
{
relopt_int *optint = (relopt_int *) option->gen;
@@ -1780,17 +1864,6 @@ fillRelOptions(void *rdopts, Size basesize,
char *itempos = ((char *) rdopts) + elems[j].offset;
char *string_val;
- /*
- * If isset_offset is provided, store whether the reloption is
- * set there.
- */
- if (elems[j].isset_offset > 0)
- {
- char *setpos = ((char *) rdopts) + elems[j].isset_offset;
-
- *(bool *) setpos = options[i].isset;
- }
-
switch (options[i].gen->type)
{
case RELOPT_TYPE_BOOL:
@@ -1798,6 +1871,11 @@ fillRelOptions(void *rdopts, Size basesize,
options[i].values.bool_val :
((relopt_bool *) options[i].gen)->default_val;
break;
+ case RELOPT_TYPE_TERNARY:
+ *(ternary *) itempos = options[i].isset ?
+ options[i].values.ternary_val :
+ ((relopt_ternary *) options[i].gen)->default_val;
+ break;
case RELOPT_TYPE_INT:
*(int *) itempos = options[i].isset ?
options[i].values.int_val :
@@ -1912,8 +1990,8 @@ default_reloptions(Datum reloptions, bool validate, relopt_kind kind)
offsetof(StdRdOptions, parallel_workers)},
{"vacuum_index_cleanup", RELOPT_TYPE_ENUM,
offsetof(StdRdOptions, vacuum_index_cleanup)},
- {"vacuum_truncate", RELOPT_TYPE_BOOL,
- offsetof(StdRdOptions, vacuum_truncate), offsetof(StdRdOptions, vacuum_truncate_set)},
+ {"vacuum_truncate", RELOPT_TYPE_TERNARY,
+ offsetof(StdRdOptions, vacuum_truncate)},
{"vacuum_max_eager_freeze_failure_rate", RELOPT_TYPE_REAL,
offsetof(StdRdOptions, vacuum_max_eager_freeze_failure_rate)}
};
@@ -1993,7 +2071,6 @@ build_local_reloptions(local_relopts *relopts, Datum options, bool validate)
elems[i].optname = opt->option->name;
elems[i].opttype = opt->option->type;
elems[i].offset = opt->offset;
- elems[i].isset_offset = 0; /* not supported for local relopts yet */
i++;
}
diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c
index 733ef40ae7..977babff54 100644
--- a/src/backend/commands/vacuum.c
+++ b/src/backend/commands/vacuum.c
@@ -2221,9 +2221,9 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams params,
{
StdRdOptions *opts = (StdRdOptions *) rel->rd_options;
- if (opts && opts->vacuum_truncate_set)
+ if (opts && opts->vacuum_truncate != TERNARY_UNSET)
{
- if (opts->vacuum_truncate)
+ if (opts->vacuum_truncate == TERNARY_TRUE)
params.truncate = VACOPTVALUE_ENABLED;
else
params.truncate = VACOPTVALUE_DISABLED;
diff --git a/src/include/access/reloptions.h b/src/include/access/reloptions.h
index a604a4702c..a436697658 100644
--- a/src/include/access/reloptions.h
+++ b/src/include/access/reloptions.h
@@ -29,6 +29,7 @@
typedef enum relopt_type
{
RELOPT_TYPE_BOOL,
+ RELOPT_TYPE_TERNARY, /* on, off, unset */
RELOPT_TYPE_INT,
RELOPT_TYPE_REAL,
RELOPT_TYPE_ENUM,
@@ -80,6 +81,7 @@ typedef struct relopt_value
union
{
bool bool_val;
+ ternary ternary_val;
int int_val;
double real_val;
int enum_val;
@@ -94,6 +96,12 @@ typedef struct relopt_bool
bool default_val;
} relopt_bool;
+typedef struct relopt_rernary
+{
+ relopt_gen gen;
+ int default_val;
+} relopt_ternary;
+
typedef struct relopt_int
{
relopt_gen gen;
@@ -152,19 +160,6 @@ typedef struct
const char *optname; /* option's name */
relopt_type opttype; /* option's datatype */
int offset; /* offset of field in result struct */
-
- /*
- * isset_offset is an optional offset of a field in the result struct that
- * stores whether the option is explicitly set for the relation or if it
- * just picked up the default value. In most cases, this can be
- * accomplished by giving the reloption a special out-of-range default
- * value (e.g., some integer reloptions use -2), but this isn't always
- * possible. For example, a Boolean reloption cannot be given an
- * out-of-range default, so we need another way to discover the source of
- * its value. This offset is only used if given a value greater than
- * zero.
- */
- int isset_offset;
} relopt_parse_elt;
/* Local reloption definition */
@@ -195,6 +190,8 @@ typedef struct local_relopts
extern relopt_kind add_reloption_kind(void);
extern void add_bool_reloption(bits32 kinds, const char *name, const char *desc,
bool default_val, LOCKMODE lockmode);
+extern void add_ternary_reloption(bits32 kinds, const char *name,
+ const char *desc, int default_val, LOCKMODE lockmode);
extern void add_int_reloption(bits32 kinds, const char *name, const char *desc,
int default_val, int min_val, int max_val,
LOCKMODE lockmode);
@@ -214,6 +211,9 @@ extern void register_reloptions_validator(local_relopts *relopts,
extern void add_local_bool_reloption(local_relopts *relopts, const char *name,
const char *desc, bool default_val,
int offset);
+extern void add_local_ternary_reloption(local_relopts *relopts,
+ const char *name, const char *desc,
+ ternary default_val, int offset);
extern void add_local_int_reloption(local_relopts *relopts, const char *name,
const char *desc, int default_val,
int min_val, int max_val, int offset);
diff --git a/src/include/c.h b/src/include/c.h
index 39022f8a9d..9c59ccccf8 100644
--- a/src/include/c.h
+++ b/src/include/c.h
@@ -488,6 +488,22 @@ typedef void (*pg_funcptr_t) (void);
#include <stdbool.h>
+/*
+ * ternary
+ * Boolean value with an extrea "unset" option
+ *
+ * Ternary data type is used in relation options that can be "true", "false" or
+ * "unset". Since relation options are used deep inside the PostgreSQL code,
+ * this type is declared globally.
+*/
+
+typedef enum ternary
+{
+ TERNARY_FALSE = 0,
+ TERNARY_TRUE = 1,
+ TERNARY_UNSET = -1
+} ternary;
+
/* ----------------------------------------------------------------
* Section 3: standard system types
diff --git a/src/include/utils/rel.h b/src/include/utils/rel.h
index b552359915..7346d618f9 100644
--- a/src/include/utils/rel.h
+++ b/src/include/utils/rel.h
@@ -346,8 +346,7 @@ typedef struct StdRdOptions
bool user_catalog_table; /* use as an additional catalog relation */
int parallel_workers; /* max number of parallel workers */
StdRdOptIndexCleanup vacuum_index_cleanup; /* controls index vacuuming */
- bool vacuum_truncate; /* enables vacuum to truncate a relation */
- bool vacuum_truncate_set; /* whether vacuum_truncate is set */
+ ternary vacuum_truncate; /* enables vacuum to truncate a relation */
/*
* Fraction of pages in a relation that vacuum can eagerly scan and fail
--
2.39.2
--nextPart4155600.3ZeAukHxDK
Content-Disposition: attachment;
filename="v2a-0003-Add-alias-to-be-used-as-unset-state.patch"
Content-Transfer-Encoding: 7Bit
Content-Type: text/x-patch; charset="unicode-2-0-utf-8";
name="v2a-0003-Add-alias-to-be-used-as-unset-state.patch"
^ permalink raw reply [nested|flat] 868+ messages in thread
* [PATCH v2 2/4] Introduce ternary reloptions
@ 2025-09-04 14:41 Nikolay Shaplov <[email protected]>
0 siblings, 0 replies; 868+ messages in thread
From: Nikolay Shaplov @ 2025-09-04 14:41 UTC (permalink / raw)
Introduce ternary reloption as a replacement for current `vacuum_truncate`
implementation. Remove `vacuum_truncate_set` additional flag and using
`TERNARY_UNSET` value instead.
---
src/backend/access/common/reloptions.c | 129 ++++++++++++++++++++-----
src/backend/commands/vacuum.c | 4 +-
src/include/access/reloptions.h | 26 ++---
src/include/c.h | 16 +++
src/include/utils/rel.h | 3 +-
5 files changed, 135 insertions(+), 43 deletions(-)
diff --git a/src/backend/access/common/reloptions.c b/src/backend/access/common/reloptions.c
index 0af3fea68f..f543f11001 100644
--- a/src/backend/access/common/reloptions.c
+++ b/src/backend/access/common/reloptions.c
@@ -40,9 +40,9 @@
*
* To add an option:
*
- * (i) decide on a type (bool, integer, real, enum, string), name, default
- * value, upper and lower bounds (if applicable); for strings, consider a
- * validation routine.
+ * (i) decide on a type (bool, ternary, integer, real, enum, string), name,
+ * default value, upper and lower bounds (if applicable); for strings,
+ * consider a validation routine.
* (ii) add a record below (or use add_<type>_reloption).
* (iii) add it to the appropriate options struct (perhaps StdRdOptions)
* (iv) add it to the appropriate handling routine (perhaps
@@ -147,15 +147,6 @@ static relopt_bool boolRelOpts[] =
},
false
},
- {
- {
- "vacuum_truncate",
- "Enables vacuum to truncate empty pages at the end of this table",
- RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
- ShareUpdateExclusiveLock
- },
- true
- },
{
{
"deduplicate_items",
@@ -170,6 +161,21 @@ static relopt_bool boolRelOpts[] =
{{NULL}}
};
+static relopt_ternary ternaryRelOpts[] =
+{
+ {
+ {
+ "vacuum_truncate",
+ "Enables vacuum to truncate empty pages at the end of this table",
+ RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
+ ShareUpdateExclusiveLock
+ },
+ TERNARY_UNSET
+ },
+ /* list terminator */
+ {{NULL}}
+};
+
static relopt_int intRelOpts[] =
{
{
@@ -600,6 +606,13 @@ initialize_reloptions(void)
boolRelOpts[i].gen.lockmode));
j++;
}
+ for (i = 0; ternaryRelOpts[i].gen.name; i++)
+ {
+ Assert(DoLockModesConflict(ternaryRelOpts[i].gen.lockmode,
+ ternaryRelOpts[i].gen.lockmode));
+ j++;
+ }
+
for (i = 0; intRelOpts[i].gen.name; i++)
{
Assert(DoLockModesConflict(intRelOpts[i].gen.lockmode,
@@ -640,6 +653,14 @@ initialize_reloptions(void)
j++;
}
+ for (i = 0; ternaryRelOpts[i].gen.name; i++)
+ {
+ relOpts[j] = &ternaryRelOpts[i].gen;
+ relOpts[j]->type = RELOPT_TYPE_TERNARY;
+ relOpts[j]->namelen = strlen(relOpts[j]->name);
+ j++;
+ }
+
for (i = 0; intRelOpts[i].gen.name; i++)
{
relOpts[j] = &intRelOpts[i].gen;
@@ -800,6 +821,9 @@ allocate_reloption(bits32 kinds, int type, const char *name, const char *desc,
case RELOPT_TYPE_BOOL:
size = sizeof(relopt_bool);
break;
+ case RELOPT_TYPE_TERNARY:
+ size = sizeof(relopt_ternary);
+ break;
case RELOPT_TYPE_INT:
size = sizeof(relopt_int);
break;
@@ -883,6 +907,54 @@ add_local_bool_reloption(local_relopts *relopts, const char *name,
add_local_reloption(relopts, (relopt_gen *) newoption, offset);
}
+/*
+ * init_ternary_reloption
+ * Allocate and initialize a new ternary reloption
+ */
+static relopt_ternary *
+init_ternary_reloption(bits32 kinds, const char *name, const char *desc,
+ ternary default_val, LOCKMODE lockmode)
+{
+ relopt_ternary *newoption;
+
+ newoption = (relopt_ternary *) allocate_reloption(kinds,
+ RELOPT_TYPE_TERNARY, name, desc, lockmode);
+ newoption->default_val = default_val;
+
+ return newoption;
+}
+
+/*
+ * add_ternary_reloption
+ * Add a new ternary reloption
+ */
+void
+add_ternary_reloption(bits32 kinds, const char *name, const char *desc,
+ ternary default_val, LOCKMODE lockmode)
+{
+ relopt_ternary *newoption = init_ternary_reloption(kinds, name, desc,
+ default_val, lockmode);
+
+ add_reloption((relopt_gen *) newoption);
+}
+
+/*
+ * add_local_ternary_reloption
+ * Add a new ternary local reloption
+ *
+ * 'offset' is offset of ternary-typed field.
+ */
+void
+add_local_ternary_reloption(local_relopts *relopts, const char *name,
+ const char *desc, ternary default_val,
+ int offset)
+{
+ relopt_ternary *newoption = init_ternary_reloption(RELOPT_KIND_LOCAL,
+ name, desc,
+ default_val, 0);
+
+ add_local_reloption(relopts, (relopt_gen *) newoption, offset);
+}
/*
* init_real_reloption
@@ -1617,6 +1689,18 @@ parse_one_reloption(relopt_value *option, char *text_str, int text_len,
option->gen->name, value)));
}
break;
+ case RELOPT_TYPE_TERNARY:
+ {
+ bool b;
+ parsed = parse_bool(value, &b);
+ option->values.ternary_val = b ? TERNARY_TRUE : TERNARY_FALSE;
+ if (validate && !parsed)
+ ereport(ERROR,
+ (errcode(ERRCODE_INVALID_PARAMETER_VALUE),
+ errmsg("invalid value for ternary option \"%s\": %s",
+ option->gen->name, value)));
+ }
+ break;
case RELOPT_TYPE_INT:
{
relopt_int *optint = (relopt_int *) option->gen;
@@ -1780,17 +1864,6 @@ fillRelOptions(void *rdopts, Size basesize,
char *itempos = ((char *) rdopts) + elems[j].offset;
char *string_val;
- /*
- * If isset_offset is provided, store whether the reloption is
- * set there.
- */
- if (elems[j].isset_offset > 0)
- {
- char *setpos = ((char *) rdopts) + elems[j].isset_offset;
-
- *(bool *) setpos = options[i].isset;
- }
-
switch (options[i].gen->type)
{
case RELOPT_TYPE_BOOL:
@@ -1798,6 +1871,11 @@ fillRelOptions(void *rdopts, Size basesize,
options[i].values.bool_val :
((relopt_bool *) options[i].gen)->default_val;
break;
+ case RELOPT_TYPE_TERNARY:
+ *(ternary *) itempos = options[i].isset ?
+ options[i].values.ternary_val :
+ ((relopt_ternary *) options[i].gen)->default_val;
+ break;
case RELOPT_TYPE_INT:
*(int *) itempos = options[i].isset ?
options[i].values.int_val :
@@ -1912,8 +1990,8 @@ default_reloptions(Datum reloptions, bool validate, relopt_kind kind)
offsetof(StdRdOptions, parallel_workers)},
{"vacuum_index_cleanup", RELOPT_TYPE_ENUM,
offsetof(StdRdOptions, vacuum_index_cleanup)},
- {"vacuum_truncate", RELOPT_TYPE_BOOL,
- offsetof(StdRdOptions, vacuum_truncate), offsetof(StdRdOptions, vacuum_truncate_set)},
+ {"vacuum_truncate", RELOPT_TYPE_TERNARY,
+ offsetof(StdRdOptions, vacuum_truncate)},
{"vacuum_max_eager_freeze_failure_rate", RELOPT_TYPE_REAL,
offsetof(StdRdOptions, vacuum_max_eager_freeze_failure_rate)}
};
@@ -1993,7 +2071,6 @@ build_local_reloptions(local_relopts *relopts, Datum options, bool validate)
elems[i].optname = opt->option->name;
elems[i].opttype = opt->option->type;
elems[i].offset = opt->offset;
- elems[i].isset_offset = 0; /* not supported for local relopts yet */
i++;
}
diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c
index 733ef40ae7..977babff54 100644
--- a/src/backend/commands/vacuum.c
+++ b/src/backend/commands/vacuum.c
@@ -2221,9 +2221,9 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams params,
{
StdRdOptions *opts = (StdRdOptions *) rel->rd_options;
- if (opts && opts->vacuum_truncate_set)
+ if (opts && opts->vacuum_truncate != TERNARY_UNSET)
{
- if (opts->vacuum_truncate)
+ if (opts->vacuum_truncate == TERNARY_TRUE)
params.truncate = VACOPTVALUE_ENABLED;
else
params.truncate = VACOPTVALUE_DISABLED;
diff --git a/src/include/access/reloptions.h b/src/include/access/reloptions.h
index a604a4702c..a436697658 100644
--- a/src/include/access/reloptions.h
+++ b/src/include/access/reloptions.h
@@ -29,6 +29,7 @@
typedef enum relopt_type
{
RELOPT_TYPE_BOOL,
+ RELOPT_TYPE_TERNARY, /* on, off, unset */
RELOPT_TYPE_INT,
RELOPT_TYPE_REAL,
RELOPT_TYPE_ENUM,
@@ -80,6 +81,7 @@ typedef struct relopt_value
union
{
bool bool_val;
+ ternary ternary_val;
int int_val;
double real_val;
int enum_val;
@@ -94,6 +96,12 @@ typedef struct relopt_bool
bool default_val;
} relopt_bool;
+typedef struct relopt_rernary
+{
+ relopt_gen gen;
+ int default_val;
+} relopt_ternary;
+
typedef struct relopt_int
{
relopt_gen gen;
@@ -152,19 +160,6 @@ typedef struct
const char *optname; /* option's name */
relopt_type opttype; /* option's datatype */
int offset; /* offset of field in result struct */
-
- /*
- * isset_offset is an optional offset of a field in the result struct that
- * stores whether the option is explicitly set for the relation or if it
- * just picked up the default value. In most cases, this can be
- * accomplished by giving the reloption a special out-of-range default
- * value (e.g., some integer reloptions use -2), but this isn't always
- * possible. For example, a Boolean reloption cannot be given an
- * out-of-range default, so we need another way to discover the source of
- * its value. This offset is only used if given a value greater than
- * zero.
- */
- int isset_offset;
} relopt_parse_elt;
/* Local reloption definition */
@@ -195,6 +190,8 @@ typedef struct local_relopts
extern relopt_kind add_reloption_kind(void);
extern void add_bool_reloption(bits32 kinds, const char *name, const char *desc,
bool default_val, LOCKMODE lockmode);
+extern void add_ternary_reloption(bits32 kinds, const char *name,
+ const char *desc, int default_val, LOCKMODE lockmode);
extern void add_int_reloption(bits32 kinds, const char *name, const char *desc,
int default_val, int min_val, int max_val,
LOCKMODE lockmode);
@@ -214,6 +211,9 @@ extern void register_reloptions_validator(local_relopts *relopts,
extern void add_local_bool_reloption(local_relopts *relopts, const char *name,
const char *desc, bool default_val,
int offset);
+extern void add_local_ternary_reloption(local_relopts *relopts,
+ const char *name, const char *desc,
+ ternary default_val, int offset);
extern void add_local_int_reloption(local_relopts *relopts, const char *name,
const char *desc, int default_val,
int min_val, int max_val, int offset);
diff --git a/src/include/c.h b/src/include/c.h
index 39022f8a9d..9c59ccccf8 100644
--- a/src/include/c.h
+++ b/src/include/c.h
@@ -488,6 +488,22 @@ typedef void (*pg_funcptr_t) (void);
#include <stdbool.h>
+/*
+ * ternary
+ * Boolean value with an extrea "unset" option
+ *
+ * Ternary data type is used in relation options that can be "true", "false" or
+ * "unset". Since relation options are used deep inside the PostgreSQL code,
+ * this type is declared globally.
+*/
+
+typedef enum ternary
+{
+ TERNARY_FALSE = 0,
+ TERNARY_TRUE = 1,
+ TERNARY_UNSET = -1
+} ternary;
+
/* ----------------------------------------------------------------
* Section 3: standard system types
diff --git a/src/include/utils/rel.h b/src/include/utils/rel.h
index b552359915..7346d618f9 100644
--- a/src/include/utils/rel.h
+++ b/src/include/utils/rel.h
@@ -346,8 +346,7 @@ typedef struct StdRdOptions
bool user_catalog_table; /* use as an additional catalog relation */
int parallel_workers; /* max number of parallel workers */
StdRdOptIndexCleanup vacuum_index_cleanup; /* controls index vacuuming */
- bool vacuum_truncate; /* enables vacuum to truncate a relation */
- bool vacuum_truncate_set; /* whether vacuum_truncate is set */
+ ternary vacuum_truncate; /* enables vacuum to truncate a relation */
/*
* Fraction of pages in a relation that vacuum can eagerly scan and fail
--
2.39.2
--nextPart5129932.5fSG56mABF
Content-Disposition: attachment;
filename="v2-0003-Add-alias-to-be-used-as-unset-state.patch"
Content-Transfer-Encoding: 7Bit
Content-Type: text/x-patch; charset="unicode-2-0-utf-8";
name="v2-0003-Add-alias-to-be-used-as-unset-state.patch"
^ permalink raw reply [nested|flat] 868+ messages in thread
* [PATCH v2 2/4] Introduce ternary reloptions
@ 2025-09-04 14:41 Nikolay Shaplov <[email protected]>
0 siblings, 0 replies; 868+ messages in thread
From: Nikolay Shaplov @ 2025-09-04 14:41 UTC (permalink / raw)
Introduce ternary reloption as a replacement for current `vacuum_truncate`
implementation. Remove `vacuum_truncate_set` additional flag and using
`TERNARY_UNSET` value instead.
---
src/backend/access/common/reloptions.c | 129 ++++++++++++++++++++-----
src/backend/commands/vacuum.c | 4 +-
src/include/access/reloptions.h | 26 ++---
src/include/c.h | 16 +++
src/include/utils/rel.h | 3 +-
5 files changed, 135 insertions(+), 43 deletions(-)
diff --git a/src/backend/access/common/reloptions.c b/src/backend/access/common/reloptions.c
index 0af3fea68f..f543f11001 100644
--- a/src/backend/access/common/reloptions.c
+++ b/src/backend/access/common/reloptions.c
@@ -40,9 +40,9 @@
*
* To add an option:
*
- * (i) decide on a type (bool, integer, real, enum, string), name, default
- * value, upper and lower bounds (if applicable); for strings, consider a
- * validation routine.
+ * (i) decide on a type (bool, ternary, integer, real, enum, string), name,
+ * default value, upper and lower bounds (if applicable); for strings,
+ * consider a validation routine.
* (ii) add a record below (or use add_<type>_reloption).
* (iii) add it to the appropriate options struct (perhaps StdRdOptions)
* (iv) add it to the appropriate handling routine (perhaps
@@ -147,15 +147,6 @@ static relopt_bool boolRelOpts[] =
},
false
},
- {
- {
- "vacuum_truncate",
- "Enables vacuum to truncate empty pages at the end of this table",
- RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
- ShareUpdateExclusiveLock
- },
- true
- },
{
{
"deduplicate_items",
@@ -170,6 +161,21 @@ static relopt_bool boolRelOpts[] =
{{NULL}}
};
+static relopt_ternary ternaryRelOpts[] =
+{
+ {
+ {
+ "vacuum_truncate",
+ "Enables vacuum to truncate empty pages at the end of this table",
+ RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
+ ShareUpdateExclusiveLock
+ },
+ TERNARY_UNSET
+ },
+ /* list terminator */
+ {{NULL}}
+};
+
static relopt_int intRelOpts[] =
{
{
@@ -600,6 +606,13 @@ initialize_reloptions(void)
boolRelOpts[i].gen.lockmode));
j++;
}
+ for (i = 0; ternaryRelOpts[i].gen.name; i++)
+ {
+ Assert(DoLockModesConflict(ternaryRelOpts[i].gen.lockmode,
+ ternaryRelOpts[i].gen.lockmode));
+ j++;
+ }
+
for (i = 0; intRelOpts[i].gen.name; i++)
{
Assert(DoLockModesConflict(intRelOpts[i].gen.lockmode,
@@ -640,6 +653,14 @@ initialize_reloptions(void)
j++;
}
+ for (i = 0; ternaryRelOpts[i].gen.name; i++)
+ {
+ relOpts[j] = &ternaryRelOpts[i].gen;
+ relOpts[j]->type = RELOPT_TYPE_TERNARY;
+ relOpts[j]->namelen = strlen(relOpts[j]->name);
+ j++;
+ }
+
for (i = 0; intRelOpts[i].gen.name; i++)
{
relOpts[j] = &intRelOpts[i].gen;
@@ -800,6 +821,9 @@ allocate_reloption(bits32 kinds, int type, const char *name, const char *desc,
case RELOPT_TYPE_BOOL:
size = sizeof(relopt_bool);
break;
+ case RELOPT_TYPE_TERNARY:
+ size = sizeof(relopt_ternary);
+ break;
case RELOPT_TYPE_INT:
size = sizeof(relopt_int);
break;
@@ -883,6 +907,54 @@ add_local_bool_reloption(local_relopts *relopts, const char *name,
add_local_reloption(relopts, (relopt_gen *) newoption, offset);
}
+/*
+ * init_ternary_reloption
+ * Allocate and initialize a new ternary reloption
+ */
+static relopt_ternary *
+init_ternary_reloption(bits32 kinds, const char *name, const char *desc,
+ ternary default_val, LOCKMODE lockmode)
+{
+ relopt_ternary *newoption;
+
+ newoption = (relopt_ternary *) allocate_reloption(kinds,
+ RELOPT_TYPE_TERNARY, name, desc, lockmode);
+ newoption->default_val = default_val;
+
+ return newoption;
+}
+
+/*
+ * add_ternary_reloption
+ * Add a new ternary reloption
+ */
+void
+add_ternary_reloption(bits32 kinds, const char *name, const char *desc,
+ ternary default_val, LOCKMODE lockmode)
+{
+ relopt_ternary *newoption = init_ternary_reloption(kinds, name, desc,
+ default_val, lockmode);
+
+ add_reloption((relopt_gen *) newoption);
+}
+
+/*
+ * add_local_ternary_reloption
+ * Add a new ternary local reloption
+ *
+ * 'offset' is offset of ternary-typed field.
+ */
+void
+add_local_ternary_reloption(local_relopts *relopts, const char *name,
+ const char *desc, ternary default_val,
+ int offset)
+{
+ relopt_ternary *newoption = init_ternary_reloption(RELOPT_KIND_LOCAL,
+ name, desc,
+ default_val, 0);
+
+ add_local_reloption(relopts, (relopt_gen *) newoption, offset);
+}
/*
* init_real_reloption
@@ -1617,6 +1689,18 @@ parse_one_reloption(relopt_value *option, char *text_str, int text_len,
option->gen->name, value)));
}
break;
+ case RELOPT_TYPE_TERNARY:
+ {
+ bool b;
+ parsed = parse_bool(value, &b);
+ option->values.ternary_val = b ? TERNARY_TRUE : TERNARY_FALSE;
+ if (validate && !parsed)
+ ereport(ERROR,
+ (errcode(ERRCODE_INVALID_PARAMETER_VALUE),
+ errmsg("invalid value for ternary option \"%s\": %s",
+ option->gen->name, value)));
+ }
+ break;
case RELOPT_TYPE_INT:
{
relopt_int *optint = (relopt_int *) option->gen;
@@ -1780,17 +1864,6 @@ fillRelOptions(void *rdopts, Size basesize,
char *itempos = ((char *) rdopts) + elems[j].offset;
char *string_val;
- /*
- * If isset_offset is provided, store whether the reloption is
- * set there.
- */
- if (elems[j].isset_offset > 0)
- {
- char *setpos = ((char *) rdopts) + elems[j].isset_offset;
-
- *(bool *) setpos = options[i].isset;
- }
-
switch (options[i].gen->type)
{
case RELOPT_TYPE_BOOL:
@@ -1798,6 +1871,11 @@ fillRelOptions(void *rdopts, Size basesize,
options[i].values.bool_val :
((relopt_bool *) options[i].gen)->default_val;
break;
+ case RELOPT_TYPE_TERNARY:
+ *(ternary *) itempos = options[i].isset ?
+ options[i].values.ternary_val :
+ ((relopt_ternary *) options[i].gen)->default_val;
+ break;
case RELOPT_TYPE_INT:
*(int *) itempos = options[i].isset ?
options[i].values.int_val :
@@ -1912,8 +1990,8 @@ default_reloptions(Datum reloptions, bool validate, relopt_kind kind)
offsetof(StdRdOptions, parallel_workers)},
{"vacuum_index_cleanup", RELOPT_TYPE_ENUM,
offsetof(StdRdOptions, vacuum_index_cleanup)},
- {"vacuum_truncate", RELOPT_TYPE_BOOL,
- offsetof(StdRdOptions, vacuum_truncate), offsetof(StdRdOptions, vacuum_truncate_set)},
+ {"vacuum_truncate", RELOPT_TYPE_TERNARY,
+ offsetof(StdRdOptions, vacuum_truncate)},
{"vacuum_max_eager_freeze_failure_rate", RELOPT_TYPE_REAL,
offsetof(StdRdOptions, vacuum_max_eager_freeze_failure_rate)}
};
@@ -1993,7 +2071,6 @@ build_local_reloptions(local_relopts *relopts, Datum options, bool validate)
elems[i].optname = opt->option->name;
elems[i].opttype = opt->option->type;
elems[i].offset = opt->offset;
- elems[i].isset_offset = 0; /* not supported for local relopts yet */
i++;
}
diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c
index 733ef40ae7..977babff54 100644
--- a/src/backend/commands/vacuum.c
+++ b/src/backend/commands/vacuum.c
@@ -2221,9 +2221,9 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams params,
{
StdRdOptions *opts = (StdRdOptions *) rel->rd_options;
- if (opts && opts->vacuum_truncate_set)
+ if (opts && opts->vacuum_truncate != TERNARY_UNSET)
{
- if (opts->vacuum_truncate)
+ if (opts->vacuum_truncate == TERNARY_TRUE)
params.truncate = VACOPTVALUE_ENABLED;
else
params.truncate = VACOPTVALUE_DISABLED;
diff --git a/src/include/access/reloptions.h b/src/include/access/reloptions.h
index a604a4702c..a436697658 100644
--- a/src/include/access/reloptions.h
+++ b/src/include/access/reloptions.h
@@ -29,6 +29,7 @@
typedef enum relopt_type
{
RELOPT_TYPE_BOOL,
+ RELOPT_TYPE_TERNARY, /* on, off, unset */
RELOPT_TYPE_INT,
RELOPT_TYPE_REAL,
RELOPT_TYPE_ENUM,
@@ -80,6 +81,7 @@ typedef struct relopt_value
union
{
bool bool_val;
+ ternary ternary_val;
int int_val;
double real_val;
int enum_val;
@@ -94,6 +96,12 @@ typedef struct relopt_bool
bool default_val;
} relopt_bool;
+typedef struct relopt_rernary
+{
+ relopt_gen gen;
+ int default_val;
+} relopt_ternary;
+
typedef struct relopt_int
{
relopt_gen gen;
@@ -152,19 +160,6 @@ typedef struct
const char *optname; /* option's name */
relopt_type opttype; /* option's datatype */
int offset; /* offset of field in result struct */
-
- /*
- * isset_offset is an optional offset of a field in the result struct that
- * stores whether the option is explicitly set for the relation or if it
- * just picked up the default value. In most cases, this can be
- * accomplished by giving the reloption a special out-of-range default
- * value (e.g., some integer reloptions use -2), but this isn't always
- * possible. For example, a Boolean reloption cannot be given an
- * out-of-range default, so we need another way to discover the source of
- * its value. This offset is only used if given a value greater than
- * zero.
- */
- int isset_offset;
} relopt_parse_elt;
/* Local reloption definition */
@@ -195,6 +190,8 @@ typedef struct local_relopts
extern relopt_kind add_reloption_kind(void);
extern void add_bool_reloption(bits32 kinds, const char *name, const char *desc,
bool default_val, LOCKMODE lockmode);
+extern void add_ternary_reloption(bits32 kinds, const char *name,
+ const char *desc, int default_val, LOCKMODE lockmode);
extern void add_int_reloption(bits32 kinds, const char *name, const char *desc,
int default_val, int min_val, int max_val,
LOCKMODE lockmode);
@@ -214,6 +211,9 @@ extern void register_reloptions_validator(local_relopts *relopts,
extern void add_local_bool_reloption(local_relopts *relopts, const char *name,
const char *desc, bool default_val,
int offset);
+extern void add_local_ternary_reloption(local_relopts *relopts,
+ const char *name, const char *desc,
+ ternary default_val, int offset);
extern void add_local_int_reloption(local_relopts *relopts, const char *name,
const char *desc, int default_val,
int min_val, int max_val, int offset);
diff --git a/src/include/c.h b/src/include/c.h
index 39022f8a9d..9c59ccccf8 100644
--- a/src/include/c.h
+++ b/src/include/c.h
@@ -488,6 +488,22 @@ typedef void (*pg_funcptr_t) (void);
#include <stdbool.h>
+/*
+ * ternary
+ * Boolean value with an extrea "unset" option
+ *
+ * Ternary data type is used in relation options that can be "true", "false" or
+ * "unset". Since relation options are used deep inside the PostgreSQL code,
+ * this type is declared globally.
+*/
+
+typedef enum ternary
+{
+ TERNARY_FALSE = 0,
+ TERNARY_TRUE = 1,
+ TERNARY_UNSET = -1
+} ternary;
+
/* ----------------------------------------------------------------
* Section 3: standard system types
diff --git a/src/include/utils/rel.h b/src/include/utils/rel.h
index b552359915..7346d618f9 100644
--- a/src/include/utils/rel.h
+++ b/src/include/utils/rel.h
@@ -346,8 +346,7 @@ typedef struct StdRdOptions
bool user_catalog_table; /* use as an additional catalog relation */
int parallel_workers; /* max number of parallel workers */
StdRdOptIndexCleanup vacuum_index_cleanup; /* controls index vacuuming */
- bool vacuum_truncate; /* enables vacuum to truncate a relation */
- bool vacuum_truncate_set; /* whether vacuum_truncate is set */
+ ternary vacuum_truncate; /* enables vacuum to truncate a relation */
/*
* Fraction of pages in a relation that vacuum can eagerly scan and fail
--
2.39.2
--nextPart5129932.5fSG56mABF
Content-Disposition: attachment;
filename="v2-0003-Add-alias-to-be-used-as-unset-state.patch"
Content-Transfer-Encoding: 7Bit
Content-Type: text/x-patch; charset="unicode-2-0-utf-8";
name="v2-0003-Add-alias-to-be-used-as-unset-state.patch"
^ permalink raw reply [nested|flat] 868+ messages in thread
* [PATCH v2 2/4] Introduce ternary reloptions
@ 2025-09-04 14:41 Nikolay Shaplov <[email protected]>
0 siblings, 0 replies; 868+ messages in thread
From: Nikolay Shaplov @ 2025-09-04 14:41 UTC (permalink / raw)
Introduce ternary reloption as a replacement for current `vacuum_truncate`
implementation. Remove `vacuum_truncate_set` additional flag and using
`TERNARY_UNSET` value instead.
---
src/backend/access/common/reloptions.c | 129 ++++++++++++++++++++-----
src/backend/commands/vacuum.c | 4 +-
src/include/access/reloptions.h | 26 ++---
src/include/c.h | 16 +++
src/include/utils/rel.h | 3 +-
5 files changed, 135 insertions(+), 43 deletions(-)
diff --git a/src/backend/access/common/reloptions.c b/src/backend/access/common/reloptions.c
index 0af3fea68f..f543f11001 100644
--- a/src/backend/access/common/reloptions.c
+++ b/src/backend/access/common/reloptions.c
@@ -40,9 +40,9 @@
*
* To add an option:
*
- * (i) decide on a type (bool, integer, real, enum, string), name, default
- * value, upper and lower bounds (if applicable); for strings, consider a
- * validation routine.
+ * (i) decide on a type (bool, ternary, integer, real, enum, string), name,
+ * default value, upper and lower bounds (if applicable); for strings,
+ * consider a validation routine.
* (ii) add a record below (or use add_<type>_reloption).
* (iii) add it to the appropriate options struct (perhaps StdRdOptions)
* (iv) add it to the appropriate handling routine (perhaps
@@ -147,15 +147,6 @@ static relopt_bool boolRelOpts[] =
},
false
},
- {
- {
- "vacuum_truncate",
- "Enables vacuum to truncate empty pages at the end of this table",
- RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
- ShareUpdateExclusiveLock
- },
- true
- },
{
{
"deduplicate_items",
@@ -170,6 +161,21 @@ static relopt_bool boolRelOpts[] =
{{NULL}}
};
+static relopt_ternary ternaryRelOpts[] =
+{
+ {
+ {
+ "vacuum_truncate",
+ "Enables vacuum to truncate empty pages at the end of this table",
+ RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
+ ShareUpdateExclusiveLock
+ },
+ TERNARY_UNSET
+ },
+ /* list terminator */
+ {{NULL}}
+};
+
static relopt_int intRelOpts[] =
{
{
@@ -600,6 +606,13 @@ initialize_reloptions(void)
boolRelOpts[i].gen.lockmode));
j++;
}
+ for (i = 0; ternaryRelOpts[i].gen.name; i++)
+ {
+ Assert(DoLockModesConflict(ternaryRelOpts[i].gen.lockmode,
+ ternaryRelOpts[i].gen.lockmode));
+ j++;
+ }
+
for (i = 0; intRelOpts[i].gen.name; i++)
{
Assert(DoLockModesConflict(intRelOpts[i].gen.lockmode,
@@ -640,6 +653,14 @@ initialize_reloptions(void)
j++;
}
+ for (i = 0; ternaryRelOpts[i].gen.name; i++)
+ {
+ relOpts[j] = &ternaryRelOpts[i].gen;
+ relOpts[j]->type = RELOPT_TYPE_TERNARY;
+ relOpts[j]->namelen = strlen(relOpts[j]->name);
+ j++;
+ }
+
for (i = 0; intRelOpts[i].gen.name; i++)
{
relOpts[j] = &intRelOpts[i].gen;
@@ -800,6 +821,9 @@ allocate_reloption(bits32 kinds, int type, const char *name, const char *desc,
case RELOPT_TYPE_BOOL:
size = sizeof(relopt_bool);
break;
+ case RELOPT_TYPE_TERNARY:
+ size = sizeof(relopt_ternary);
+ break;
case RELOPT_TYPE_INT:
size = sizeof(relopt_int);
break;
@@ -883,6 +907,54 @@ add_local_bool_reloption(local_relopts *relopts, const char *name,
add_local_reloption(relopts, (relopt_gen *) newoption, offset);
}
+/*
+ * init_ternary_reloption
+ * Allocate and initialize a new ternary reloption
+ */
+static relopt_ternary *
+init_ternary_reloption(bits32 kinds, const char *name, const char *desc,
+ ternary default_val, LOCKMODE lockmode)
+{
+ relopt_ternary *newoption;
+
+ newoption = (relopt_ternary *) allocate_reloption(kinds,
+ RELOPT_TYPE_TERNARY, name, desc, lockmode);
+ newoption->default_val = default_val;
+
+ return newoption;
+}
+
+/*
+ * add_ternary_reloption
+ * Add a new ternary reloption
+ */
+void
+add_ternary_reloption(bits32 kinds, const char *name, const char *desc,
+ ternary default_val, LOCKMODE lockmode)
+{
+ relopt_ternary *newoption = init_ternary_reloption(kinds, name, desc,
+ default_val, lockmode);
+
+ add_reloption((relopt_gen *) newoption);
+}
+
+/*
+ * add_local_ternary_reloption
+ * Add a new ternary local reloption
+ *
+ * 'offset' is offset of ternary-typed field.
+ */
+void
+add_local_ternary_reloption(local_relopts *relopts, const char *name,
+ const char *desc, ternary default_val,
+ int offset)
+{
+ relopt_ternary *newoption = init_ternary_reloption(RELOPT_KIND_LOCAL,
+ name, desc,
+ default_val, 0);
+
+ add_local_reloption(relopts, (relopt_gen *) newoption, offset);
+}
/*
* init_real_reloption
@@ -1617,6 +1689,18 @@ parse_one_reloption(relopt_value *option, char *text_str, int text_len,
option->gen->name, value)));
}
break;
+ case RELOPT_TYPE_TERNARY:
+ {
+ bool b;
+ parsed = parse_bool(value, &b);
+ option->values.ternary_val = b ? TERNARY_TRUE : TERNARY_FALSE;
+ if (validate && !parsed)
+ ereport(ERROR,
+ (errcode(ERRCODE_INVALID_PARAMETER_VALUE),
+ errmsg("invalid value for ternary option \"%s\": %s",
+ option->gen->name, value)));
+ }
+ break;
case RELOPT_TYPE_INT:
{
relopt_int *optint = (relopt_int *) option->gen;
@@ -1780,17 +1864,6 @@ fillRelOptions(void *rdopts, Size basesize,
char *itempos = ((char *) rdopts) + elems[j].offset;
char *string_val;
- /*
- * If isset_offset is provided, store whether the reloption is
- * set there.
- */
- if (elems[j].isset_offset > 0)
- {
- char *setpos = ((char *) rdopts) + elems[j].isset_offset;
-
- *(bool *) setpos = options[i].isset;
- }
-
switch (options[i].gen->type)
{
case RELOPT_TYPE_BOOL:
@@ -1798,6 +1871,11 @@ fillRelOptions(void *rdopts, Size basesize,
options[i].values.bool_val :
((relopt_bool *) options[i].gen)->default_val;
break;
+ case RELOPT_TYPE_TERNARY:
+ *(ternary *) itempos = options[i].isset ?
+ options[i].values.ternary_val :
+ ((relopt_ternary *) options[i].gen)->default_val;
+ break;
case RELOPT_TYPE_INT:
*(int *) itempos = options[i].isset ?
options[i].values.int_val :
@@ -1912,8 +1990,8 @@ default_reloptions(Datum reloptions, bool validate, relopt_kind kind)
offsetof(StdRdOptions, parallel_workers)},
{"vacuum_index_cleanup", RELOPT_TYPE_ENUM,
offsetof(StdRdOptions, vacuum_index_cleanup)},
- {"vacuum_truncate", RELOPT_TYPE_BOOL,
- offsetof(StdRdOptions, vacuum_truncate), offsetof(StdRdOptions, vacuum_truncate_set)},
+ {"vacuum_truncate", RELOPT_TYPE_TERNARY,
+ offsetof(StdRdOptions, vacuum_truncate)},
{"vacuum_max_eager_freeze_failure_rate", RELOPT_TYPE_REAL,
offsetof(StdRdOptions, vacuum_max_eager_freeze_failure_rate)}
};
@@ -1993,7 +2071,6 @@ build_local_reloptions(local_relopts *relopts, Datum options, bool validate)
elems[i].optname = opt->option->name;
elems[i].opttype = opt->option->type;
elems[i].offset = opt->offset;
- elems[i].isset_offset = 0; /* not supported for local relopts yet */
i++;
}
diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c
index 733ef40ae7..977babff54 100644
--- a/src/backend/commands/vacuum.c
+++ b/src/backend/commands/vacuum.c
@@ -2221,9 +2221,9 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams params,
{
StdRdOptions *opts = (StdRdOptions *) rel->rd_options;
- if (opts && opts->vacuum_truncate_set)
+ if (opts && opts->vacuum_truncate != TERNARY_UNSET)
{
- if (opts->vacuum_truncate)
+ if (opts->vacuum_truncate == TERNARY_TRUE)
params.truncate = VACOPTVALUE_ENABLED;
else
params.truncate = VACOPTVALUE_DISABLED;
diff --git a/src/include/access/reloptions.h b/src/include/access/reloptions.h
index a604a4702c..a436697658 100644
--- a/src/include/access/reloptions.h
+++ b/src/include/access/reloptions.h
@@ -29,6 +29,7 @@
typedef enum relopt_type
{
RELOPT_TYPE_BOOL,
+ RELOPT_TYPE_TERNARY, /* on, off, unset */
RELOPT_TYPE_INT,
RELOPT_TYPE_REAL,
RELOPT_TYPE_ENUM,
@@ -80,6 +81,7 @@ typedef struct relopt_value
union
{
bool bool_val;
+ ternary ternary_val;
int int_val;
double real_val;
int enum_val;
@@ -94,6 +96,12 @@ typedef struct relopt_bool
bool default_val;
} relopt_bool;
+typedef struct relopt_rernary
+{
+ relopt_gen gen;
+ int default_val;
+} relopt_ternary;
+
typedef struct relopt_int
{
relopt_gen gen;
@@ -152,19 +160,6 @@ typedef struct
const char *optname; /* option's name */
relopt_type opttype; /* option's datatype */
int offset; /* offset of field in result struct */
-
- /*
- * isset_offset is an optional offset of a field in the result struct that
- * stores whether the option is explicitly set for the relation or if it
- * just picked up the default value. In most cases, this can be
- * accomplished by giving the reloption a special out-of-range default
- * value (e.g., some integer reloptions use -2), but this isn't always
- * possible. For example, a Boolean reloption cannot be given an
- * out-of-range default, so we need another way to discover the source of
- * its value. This offset is only used if given a value greater than
- * zero.
- */
- int isset_offset;
} relopt_parse_elt;
/* Local reloption definition */
@@ -195,6 +190,8 @@ typedef struct local_relopts
extern relopt_kind add_reloption_kind(void);
extern void add_bool_reloption(bits32 kinds, const char *name, const char *desc,
bool default_val, LOCKMODE lockmode);
+extern void add_ternary_reloption(bits32 kinds, const char *name,
+ const char *desc, int default_val, LOCKMODE lockmode);
extern void add_int_reloption(bits32 kinds, const char *name, const char *desc,
int default_val, int min_val, int max_val,
LOCKMODE lockmode);
@@ -214,6 +211,9 @@ extern void register_reloptions_validator(local_relopts *relopts,
extern void add_local_bool_reloption(local_relopts *relopts, const char *name,
const char *desc, bool default_val,
int offset);
+extern void add_local_ternary_reloption(local_relopts *relopts,
+ const char *name, const char *desc,
+ ternary default_val, int offset);
extern void add_local_int_reloption(local_relopts *relopts, const char *name,
const char *desc, int default_val,
int min_val, int max_val, int offset);
diff --git a/src/include/c.h b/src/include/c.h
index 39022f8a9d..9c59ccccf8 100644
--- a/src/include/c.h
+++ b/src/include/c.h
@@ -488,6 +488,22 @@ typedef void (*pg_funcptr_t) (void);
#include <stdbool.h>
+/*
+ * ternary
+ * Boolean value with an extrea "unset" option
+ *
+ * Ternary data type is used in relation options that can be "true", "false" or
+ * "unset". Since relation options are used deep inside the PostgreSQL code,
+ * this type is declared globally.
+*/
+
+typedef enum ternary
+{
+ TERNARY_FALSE = 0,
+ TERNARY_TRUE = 1,
+ TERNARY_UNSET = -1
+} ternary;
+
/* ----------------------------------------------------------------
* Section 3: standard system types
diff --git a/src/include/utils/rel.h b/src/include/utils/rel.h
index b552359915..7346d618f9 100644
--- a/src/include/utils/rel.h
+++ b/src/include/utils/rel.h
@@ -346,8 +346,7 @@ typedef struct StdRdOptions
bool user_catalog_table; /* use as an additional catalog relation */
int parallel_workers; /* max number of parallel workers */
StdRdOptIndexCleanup vacuum_index_cleanup; /* controls index vacuuming */
- bool vacuum_truncate; /* enables vacuum to truncate a relation */
- bool vacuum_truncate_set; /* whether vacuum_truncate is set */
+ ternary vacuum_truncate; /* enables vacuum to truncate a relation */
/*
* Fraction of pages in a relation that vacuum can eagerly scan and fail
--
2.39.2
--nextPart5129932.5fSG56mABF
Content-Disposition: attachment;
filename="v2-0003-Add-alias-to-be-used-as-unset-state.patch"
Content-Transfer-Encoding: 7Bit
Content-Type: text/x-patch; charset="unicode-2-0-utf-8";
name="v2-0003-Add-alias-to-be-used-as-unset-state.patch"
^ permalink raw reply [nested|flat] 868+ messages in thread
* [PATCH v2 2/4] Introduce ternary reloptions
@ 2025-09-04 14:41 Nikolay Shaplov <[email protected]>
0 siblings, 0 replies; 868+ messages in thread
From: Nikolay Shaplov @ 2025-09-04 14:41 UTC (permalink / raw)
Introduce ternary reloption as a replacement for current `vacuum_truncate`
implementation. Remove `vacuum_truncate_set` additional flag and using
`TERNARY_UNSET` value instead.
---
src/backend/access/common/reloptions.c | 129 ++++++++++++++++++++-----
src/backend/commands/vacuum.c | 4 +-
src/include/access/reloptions.h | 26 ++---
src/include/c.h | 16 +++
src/include/utils/rel.h | 3 +-
5 files changed, 135 insertions(+), 43 deletions(-)
diff --git a/src/backend/access/common/reloptions.c b/src/backend/access/common/reloptions.c
index 0af3fea68f..f543f11001 100644
--- a/src/backend/access/common/reloptions.c
+++ b/src/backend/access/common/reloptions.c
@@ -40,9 +40,9 @@
*
* To add an option:
*
- * (i) decide on a type (bool, integer, real, enum, string), name, default
- * value, upper and lower bounds (if applicable); for strings, consider a
- * validation routine.
+ * (i) decide on a type (bool, ternary, integer, real, enum, string), name,
+ * default value, upper and lower bounds (if applicable); for strings,
+ * consider a validation routine.
* (ii) add a record below (or use add_<type>_reloption).
* (iii) add it to the appropriate options struct (perhaps StdRdOptions)
* (iv) add it to the appropriate handling routine (perhaps
@@ -147,15 +147,6 @@ static relopt_bool boolRelOpts[] =
},
false
},
- {
- {
- "vacuum_truncate",
- "Enables vacuum to truncate empty pages at the end of this table",
- RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
- ShareUpdateExclusiveLock
- },
- true
- },
{
{
"deduplicate_items",
@@ -170,6 +161,21 @@ static relopt_bool boolRelOpts[] =
{{NULL}}
};
+static relopt_ternary ternaryRelOpts[] =
+{
+ {
+ {
+ "vacuum_truncate",
+ "Enables vacuum to truncate empty pages at the end of this table",
+ RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
+ ShareUpdateExclusiveLock
+ },
+ TERNARY_UNSET
+ },
+ /* list terminator */
+ {{NULL}}
+};
+
static relopt_int intRelOpts[] =
{
{
@@ -600,6 +606,13 @@ initialize_reloptions(void)
boolRelOpts[i].gen.lockmode));
j++;
}
+ for (i = 0; ternaryRelOpts[i].gen.name; i++)
+ {
+ Assert(DoLockModesConflict(ternaryRelOpts[i].gen.lockmode,
+ ternaryRelOpts[i].gen.lockmode));
+ j++;
+ }
+
for (i = 0; intRelOpts[i].gen.name; i++)
{
Assert(DoLockModesConflict(intRelOpts[i].gen.lockmode,
@@ -640,6 +653,14 @@ initialize_reloptions(void)
j++;
}
+ for (i = 0; ternaryRelOpts[i].gen.name; i++)
+ {
+ relOpts[j] = &ternaryRelOpts[i].gen;
+ relOpts[j]->type = RELOPT_TYPE_TERNARY;
+ relOpts[j]->namelen = strlen(relOpts[j]->name);
+ j++;
+ }
+
for (i = 0; intRelOpts[i].gen.name; i++)
{
relOpts[j] = &intRelOpts[i].gen;
@@ -800,6 +821,9 @@ allocate_reloption(bits32 kinds, int type, const char *name, const char *desc,
case RELOPT_TYPE_BOOL:
size = sizeof(relopt_bool);
break;
+ case RELOPT_TYPE_TERNARY:
+ size = sizeof(relopt_ternary);
+ break;
case RELOPT_TYPE_INT:
size = sizeof(relopt_int);
break;
@@ -883,6 +907,54 @@ add_local_bool_reloption(local_relopts *relopts, const char *name,
add_local_reloption(relopts, (relopt_gen *) newoption, offset);
}
+/*
+ * init_ternary_reloption
+ * Allocate and initialize a new ternary reloption
+ */
+static relopt_ternary *
+init_ternary_reloption(bits32 kinds, const char *name, const char *desc,
+ ternary default_val, LOCKMODE lockmode)
+{
+ relopt_ternary *newoption;
+
+ newoption = (relopt_ternary *) allocate_reloption(kinds,
+ RELOPT_TYPE_TERNARY, name, desc, lockmode);
+ newoption->default_val = default_val;
+
+ return newoption;
+}
+
+/*
+ * add_ternary_reloption
+ * Add a new ternary reloption
+ */
+void
+add_ternary_reloption(bits32 kinds, const char *name, const char *desc,
+ ternary default_val, LOCKMODE lockmode)
+{
+ relopt_ternary *newoption = init_ternary_reloption(kinds, name, desc,
+ default_val, lockmode);
+
+ add_reloption((relopt_gen *) newoption);
+}
+
+/*
+ * add_local_ternary_reloption
+ * Add a new ternary local reloption
+ *
+ * 'offset' is offset of ternary-typed field.
+ */
+void
+add_local_ternary_reloption(local_relopts *relopts, const char *name,
+ const char *desc, ternary default_val,
+ int offset)
+{
+ relopt_ternary *newoption = init_ternary_reloption(RELOPT_KIND_LOCAL,
+ name, desc,
+ default_val, 0);
+
+ add_local_reloption(relopts, (relopt_gen *) newoption, offset);
+}
/*
* init_real_reloption
@@ -1617,6 +1689,18 @@ parse_one_reloption(relopt_value *option, char *text_str, int text_len,
option->gen->name, value)));
}
break;
+ case RELOPT_TYPE_TERNARY:
+ {
+ bool b;
+ parsed = parse_bool(value, &b);
+ option->values.ternary_val = b ? TERNARY_TRUE : TERNARY_FALSE;
+ if (validate && !parsed)
+ ereport(ERROR,
+ (errcode(ERRCODE_INVALID_PARAMETER_VALUE),
+ errmsg("invalid value for ternary option \"%s\": %s",
+ option->gen->name, value)));
+ }
+ break;
case RELOPT_TYPE_INT:
{
relopt_int *optint = (relopt_int *) option->gen;
@@ -1780,17 +1864,6 @@ fillRelOptions(void *rdopts, Size basesize,
char *itempos = ((char *) rdopts) + elems[j].offset;
char *string_val;
- /*
- * If isset_offset is provided, store whether the reloption is
- * set there.
- */
- if (elems[j].isset_offset > 0)
- {
- char *setpos = ((char *) rdopts) + elems[j].isset_offset;
-
- *(bool *) setpos = options[i].isset;
- }
-
switch (options[i].gen->type)
{
case RELOPT_TYPE_BOOL:
@@ -1798,6 +1871,11 @@ fillRelOptions(void *rdopts, Size basesize,
options[i].values.bool_val :
((relopt_bool *) options[i].gen)->default_val;
break;
+ case RELOPT_TYPE_TERNARY:
+ *(ternary *) itempos = options[i].isset ?
+ options[i].values.ternary_val :
+ ((relopt_ternary *) options[i].gen)->default_val;
+ break;
case RELOPT_TYPE_INT:
*(int *) itempos = options[i].isset ?
options[i].values.int_val :
@@ -1912,8 +1990,8 @@ default_reloptions(Datum reloptions, bool validate, relopt_kind kind)
offsetof(StdRdOptions, parallel_workers)},
{"vacuum_index_cleanup", RELOPT_TYPE_ENUM,
offsetof(StdRdOptions, vacuum_index_cleanup)},
- {"vacuum_truncate", RELOPT_TYPE_BOOL,
- offsetof(StdRdOptions, vacuum_truncate), offsetof(StdRdOptions, vacuum_truncate_set)},
+ {"vacuum_truncate", RELOPT_TYPE_TERNARY,
+ offsetof(StdRdOptions, vacuum_truncate)},
{"vacuum_max_eager_freeze_failure_rate", RELOPT_TYPE_REAL,
offsetof(StdRdOptions, vacuum_max_eager_freeze_failure_rate)}
};
@@ -1993,7 +2071,6 @@ build_local_reloptions(local_relopts *relopts, Datum options, bool validate)
elems[i].optname = opt->option->name;
elems[i].opttype = opt->option->type;
elems[i].offset = opt->offset;
- elems[i].isset_offset = 0; /* not supported for local relopts yet */
i++;
}
diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c
index 733ef40ae7..977babff54 100644
--- a/src/backend/commands/vacuum.c
+++ b/src/backend/commands/vacuum.c
@@ -2221,9 +2221,9 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams params,
{
StdRdOptions *opts = (StdRdOptions *) rel->rd_options;
- if (opts && opts->vacuum_truncate_set)
+ if (opts && opts->vacuum_truncate != TERNARY_UNSET)
{
- if (opts->vacuum_truncate)
+ if (opts->vacuum_truncate == TERNARY_TRUE)
params.truncate = VACOPTVALUE_ENABLED;
else
params.truncate = VACOPTVALUE_DISABLED;
diff --git a/src/include/access/reloptions.h b/src/include/access/reloptions.h
index a604a4702c..a436697658 100644
--- a/src/include/access/reloptions.h
+++ b/src/include/access/reloptions.h
@@ -29,6 +29,7 @@
typedef enum relopt_type
{
RELOPT_TYPE_BOOL,
+ RELOPT_TYPE_TERNARY, /* on, off, unset */
RELOPT_TYPE_INT,
RELOPT_TYPE_REAL,
RELOPT_TYPE_ENUM,
@@ -80,6 +81,7 @@ typedef struct relopt_value
union
{
bool bool_val;
+ ternary ternary_val;
int int_val;
double real_val;
int enum_val;
@@ -94,6 +96,12 @@ typedef struct relopt_bool
bool default_val;
} relopt_bool;
+typedef struct relopt_rernary
+{
+ relopt_gen gen;
+ int default_val;
+} relopt_ternary;
+
typedef struct relopt_int
{
relopt_gen gen;
@@ -152,19 +160,6 @@ typedef struct
const char *optname; /* option's name */
relopt_type opttype; /* option's datatype */
int offset; /* offset of field in result struct */
-
- /*
- * isset_offset is an optional offset of a field in the result struct that
- * stores whether the option is explicitly set for the relation or if it
- * just picked up the default value. In most cases, this can be
- * accomplished by giving the reloption a special out-of-range default
- * value (e.g., some integer reloptions use -2), but this isn't always
- * possible. For example, a Boolean reloption cannot be given an
- * out-of-range default, so we need another way to discover the source of
- * its value. This offset is only used if given a value greater than
- * zero.
- */
- int isset_offset;
} relopt_parse_elt;
/* Local reloption definition */
@@ -195,6 +190,8 @@ typedef struct local_relopts
extern relopt_kind add_reloption_kind(void);
extern void add_bool_reloption(bits32 kinds, const char *name, const char *desc,
bool default_val, LOCKMODE lockmode);
+extern void add_ternary_reloption(bits32 kinds, const char *name,
+ const char *desc, int default_val, LOCKMODE lockmode);
extern void add_int_reloption(bits32 kinds, const char *name, const char *desc,
int default_val, int min_val, int max_val,
LOCKMODE lockmode);
@@ -214,6 +211,9 @@ extern void register_reloptions_validator(local_relopts *relopts,
extern void add_local_bool_reloption(local_relopts *relopts, const char *name,
const char *desc, bool default_val,
int offset);
+extern void add_local_ternary_reloption(local_relopts *relopts,
+ const char *name, const char *desc,
+ ternary default_val, int offset);
extern void add_local_int_reloption(local_relopts *relopts, const char *name,
const char *desc, int default_val,
int min_val, int max_val, int offset);
diff --git a/src/include/c.h b/src/include/c.h
index 39022f8a9d..9c59ccccf8 100644
--- a/src/include/c.h
+++ b/src/include/c.h
@@ -488,6 +488,22 @@ typedef void (*pg_funcptr_t) (void);
#include <stdbool.h>
+/*
+ * ternary
+ * Boolean value with an extrea "unset" option
+ *
+ * Ternary data type is used in relation options that can be "true", "false" or
+ * "unset". Since relation options are used deep inside the PostgreSQL code,
+ * this type is declared globally.
+*/
+
+typedef enum ternary
+{
+ TERNARY_FALSE = 0,
+ TERNARY_TRUE = 1,
+ TERNARY_UNSET = -1
+} ternary;
+
/* ----------------------------------------------------------------
* Section 3: standard system types
diff --git a/src/include/utils/rel.h b/src/include/utils/rel.h
index b552359915..7346d618f9 100644
--- a/src/include/utils/rel.h
+++ b/src/include/utils/rel.h
@@ -346,8 +346,7 @@ typedef struct StdRdOptions
bool user_catalog_table; /* use as an additional catalog relation */
int parallel_workers; /* max number of parallel workers */
StdRdOptIndexCleanup vacuum_index_cleanup; /* controls index vacuuming */
- bool vacuum_truncate; /* enables vacuum to truncate a relation */
- bool vacuum_truncate_set; /* whether vacuum_truncate is set */
+ ternary vacuum_truncate; /* enables vacuum to truncate a relation */
/*
* Fraction of pages in a relation that vacuum can eagerly scan and fail
--
2.39.2
--nextPart5129932.5fSG56mABF
Content-Disposition: attachment;
filename="v2-0003-Add-alias-to-be-used-as-unset-state.patch"
Content-Transfer-Encoding: 7Bit
Content-Type: text/x-patch; charset="unicode-2-0-utf-8";
name="v2-0003-Add-alias-to-be-used-as-unset-state.patch"
^ permalink raw reply [nested|flat] 868+ messages in thread
* [PATCH v2a 2/4] Introduce ternary reloptions
@ 2025-09-04 14:41 Nikolay Shaplov <[email protected]>
0 siblings, 0 replies; 868+ messages in thread
From: Nikolay Shaplov @ 2025-09-04 14:41 UTC (permalink / raw)
Introduce ternary reloption as a replacement for current `vacuum_truncate`
implementation. Remove `vacuum_truncate_set` additional flag and using
`TERNARY_UNSET` value instead.
---
src/backend/access/common/reloptions.c | 129 ++++++++++++++++++++-----
src/backend/commands/vacuum.c | 4 +-
src/include/access/reloptions.h | 26 ++---
src/include/c.h | 16 +++
src/include/utils/rel.h | 3 +-
5 files changed, 135 insertions(+), 43 deletions(-)
diff --git a/src/backend/access/common/reloptions.c b/src/backend/access/common/reloptions.c
index 0af3fea68f..f543f11001 100644
--- a/src/backend/access/common/reloptions.c
+++ b/src/backend/access/common/reloptions.c
@@ -40,9 +40,9 @@
*
* To add an option:
*
- * (i) decide on a type (bool, integer, real, enum, string), name, default
- * value, upper and lower bounds (if applicable); for strings, consider a
- * validation routine.
+ * (i) decide on a type (bool, ternary, integer, real, enum, string), name,
+ * default value, upper and lower bounds (if applicable); for strings,
+ * consider a validation routine.
* (ii) add a record below (or use add_<type>_reloption).
* (iii) add it to the appropriate options struct (perhaps StdRdOptions)
* (iv) add it to the appropriate handling routine (perhaps
@@ -147,15 +147,6 @@ static relopt_bool boolRelOpts[] =
},
false
},
- {
- {
- "vacuum_truncate",
- "Enables vacuum to truncate empty pages at the end of this table",
- RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
- ShareUpdateExclusiveLock
- },
- true
- },
{
{
"deduplicate_items",
@@ -170,6 +161,21 @@ static relopt_bool boolRelOpts[] =
{{NULL}}
};
+static relopt_ternary ternaryRelOpts[] =
+{
+ {
+ {
+ "vacuum_truncate",
+ "Enables vacuum to truncate empty pages at the end of this table",
+ RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
+ ShareUpdateExclusiveLock
+ },
+ TERNARY_UNSET
+ },
+ /* list terminator */
+ {{NULL}}
+};
+
static relopt_int intRelOpts[] =
{
{
@@ -600,6 +606,13 @@ initialize_reloptions(void)
boolRelOpts[i].gen.lockmode));
j++;
}
+ for (i = 0; ternaryRelOpts[i].gen.name; i++)
+ {
+ Assert(DoLockModesConflict(ternaryRelOpts[i].gen.lockmode,
+ ternaryRelOpts[i].gen.lockmode));
+ j++;
+ }
+
for (i = 0; intRelOpts[i].gen.name; i++)
{
Assert(DoLockModesConflict(intRelOpts[i].gen.lockmode,
@@ -640,6 +653,14 @@ initialize_reloptions(void)
j++;
}
+ for (i = 0; ternaryRelOpts[i].gen.name; i++)
+ {
+ relOpts[j] = &ternaryRelOpts[i].gen;
+ relOpts[j]->type = RELOPT_TYPE_TERNARY;
+ relOpts[j]->namelen = strlen(relOpts[j]->name);
+ j++;
+ }
+
for (i = 0; intRelOpts[i].gen.name; i++)
{
relOpts[j] = &intRelOpts[i].gen;
@@ -800,6 +821,9 @@ allocate_reloption(bits32 kinds, int type, const char *name, const char *desc,
case RELOPT_TYPE_BOOL:
size = sizeof(relopt_bool);
break;
+ case RELOPT_TYPE_TERNARY:
+ size = sizeof(relopt_ternary);
+ break;
case RELOPT_TYPE_INT:
size = sizeof(relopt_int);
break;
@@ -883,6 +907,54 @@ add_local_bool_reloption(local_relopts *relopts, const char *name,
add_local_reloption(relopts, (relopt_gen *) newoption, offset);
}
+/*
+ * init_ternary_reloption
+ * Allocate and initialize a new ternary reloption
+ */
+static relopt_ternary *
+init_ternary_reloption(bits32 kinds, const char *name, const char *desc,
+ ternary default_val, LOCKMODE lockmode)
+{
+ relopt_ternary *newoption;
+
+ newoption = (relopt_ternary *) allocate_reloption(kinds,
+ RELOPT_TYPE_TERNARY, name, desc, lockmode);
+ newoption->default_val = default_val;
+
+ return newoption;
+}
+
+/*
+ * add_ternary_reloption
+ * Add a new ternary reloption
+ */
+void
+add_ternary_reloption(bits32 kinds, const char *name, const char *desc,
+ ternary default_val, LOCKMODE lockmode)
+{
+ relopt_ternary *newoption = init_ternary_reloption(kinds, name, desc,
+ default_val, lockmode);
+
+ add_reloption((relopt_gen *) newoption);
+}
+
+/*
+ * add_local_ternary_reloption
+ * Add a new ternary local reloption
+ *
+ * 'offset' is offset of ternary-typed field.
+ */
+void
+add_local_ternary_reloption(local_relopts *relopts, const char *name,
+ const char *desc, ternary default_val,
+ int offset)
+{
+ relopt_ternary *newoption = init_ternary_reloption(RELOPT_KIND_LOCAL,
+ name, desc,
+ default_val, 0);
+
+ add_local_reloption(relopts, (relopt_gen *) newoption, offset);
+}
/*
* init_real_reloption
@@ -1617,6 +1689,18 @@ parse_one_reloption(relopt_value *option, char *text_str, int text_len,
option->gen->name, value)));
}
break;
+ case RELOPT_TYPE_TERNARY:
+ {
+ bool b;
+ parsed = parse_bool(value, &b);
+ option->values.ternary_val = b ? TERNARY_TRUE : TERNARY_FALSE;
+ if (validate && !parsed)
+ ereport(ERROR,
+ (errcode(ERRCODE_INVALID_PARAMETER_VALUE),
+ errmsg("invalid value for ternary option \"%s\": %s",
+ option->gen->name, value)));
+ }
+ break;
case RELOPT_TYPE_INT:
{
relopt_int *optint = (relopt_int *) option->gen;
@@ -1780,17 +1864,6 @@ fillRelOptions(void *rdopts, Size basesize,
char *itempos = ((char *) rdopts) + elems[j].offset;
char *string_val;
- /*
- * If isset_offset is provided, store whether the reloption is
- * set there.
- */
- if (elems[j].isset_offset > 0)
- {
- char *setpos = ((char *) rdopts) + elems[j].isset_offset;
-
- *(bool *) setpos = options[i].isset;
- }
-
switch (options[i].gen->type)
{
case RELOPT_TYPE_BOOL:
@@ -1798,6 +1871,11 @@ fillRelOptions(void *rdopts, Size basesize,
options[i].values.bool_val :
((relopt_bool *) options[i].gen)->default_val;
break;
+ case RELOPT_TYPE_TERNARY:
+ *(ternary *) itempos = options[i].isset ?
+ options[i].values.ternary_val :
+ ((relopt_ternary *) options[i].gen)->default_val;
+ break;
case RELOPT_TYPE_INT:
*(int *) itempos = options[i].isset ?
options[i].values.int_val :
@@ -1912,8 +1990,8 @@ default_reloptions(Datum reloptions, bool validate, relopt_kind kind)
offsetof(StdRdOptions, parallel_workers)},
{"vacuum_index_cleanup", RELOPT_TYPE_ENUM,
offsetof(StdRdOptions, vacuum_index_cleanup)},
- {"vacuum_truncate", RELOPT_TYPE_BOOL,
- offsetof(StdRdOptions, vacuum_truncate), offsetof(StdRdOptions, vacuum_truncate_set)},
+ {"vacuum_truncate", RELOPT_TYPE_TERNARY,
+ offsetof(StdRdOptions, vacuum_truncate)},
{"vacuum_max_eager_freeze_failure_rate", RELOPT_TYPE_REAL,
offsetof(StdRdOptions, vacuum_max_eager_freeze_failure_rate)}
};
@@ -1993,7 +2071,6 @@ build_local_reloptions(local_relopts *relopts, Datum options, bool validate)
elems[i].optname = opt->option->name;
elems[i].opttype = opt->option->type;
elems[i].offset = opt->offset;
- elems[i].isset_offset = 0; /* not supported for local relopts yet */
i++;
}
diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c
index 733ef40ae7..977babff54 100644
--- a/src/backend/commands/vacuum.c
+++ b/src/backend/commands/vacuum.c
@@ -2221,9 +2221,9 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams params,
{
StdRdOptions *opts = (StdRdOptions *) rel->rd_options;
- if (opts && opts->vacuum_truncate_set)
+ if (opts && opts->vacuum_truncate != TERNARY_UNSET)
{
- if (opts->vacuum_truncate)
+ if (opts->vacuum_truncate == TERNARY_TRUE)
params.truncate = VACOPTVALUE_ENABLED;
else
params.truncate = VACOPTVALUE_DISABLED;
diff --git a/src/include/access/reloptions.h b/src/include/access/reloptions.h
index a604a4702c..a436697658 100644
--- a/src/include/access/reloptions.h
+++ b/src/include/access/reloptions.h
@@ -29,6 +29,7 @@
typedef enum relopt_type
{
RELOPT_TYPE_BOOL,
+ RELOPT_TYPE_TERNARY, /* on, off, unset */
RELOPT_TYPE_INT,
RELOPT_TYPE_REAL,
RELOPT_TYPE_ENUM,
@@ -80,6 +81,7 @@ typedef struct relopt_value
union
{
bool bool_val;
+ ternary ternary_val;
int int_val;
double real_val;
int enum_val;
@@ -94,6 +96,12 @@ typedef struct relopt_bool
bool default_val;
} relopt_bool;
+typedef struct relopt_rernary
+{
+ relopt_gen gen;
+ int default_val;
+} relopt_ternary;
+
typedef struct relopt_int
{
relopt_gen gen;
@@ -152,19 +160,6 @@ typedef struct
const char *optname; /* option's name */
relopt_type opttype; /* option's datatype */
int offset; /* offset of field in result struct */
-
- /*
- * isset_offset is an optional offset of a field in the result struct that
- * stores whether the option is explicitly set for the relation or if it
- * just picked up the default value. In most cases, this can be
- * accomplished by giving the reloption a special out-of-range default
- * value (e.g., some integer reloptions use -2), but this isn't always
- * possible. For example, a Boolean reloption cannot be given an
- * out-of-range default, so we need another way to discover the source of
- * its value. This offset is only used if given a value greater than
- * zero.
- */
- int isset_offset;
} relopt_parse_elt;
/* Local reloption definition */
@@ -195,6 +190,8 @@ typedef struct local_relopts
extern relopt_kind add_reloption_kind(void);
extern void add_bool_reloption(bits32 kinds, const char *name, const char *desc,
bool default_val, LOCKMODE lockmode);
+extern void add_ternary_reloption(bits32 kinds, const char *name,
+ const char *desc, int default_val, LOCKMODE lockmode);
extern void add_int_reloption(bits32 kinds, const char *name, const char *desc,
int default_val, int min_val, int max_val,
LOCKMODE lockmode);
@@ -214,6 +211,9 @@ extern void register_reloptions_validator(local_relopts *relopts,
extern void add_local_bool_reloption(local_relopts *relopts, const char *name,
const char *desc, bool default_val,
int offset);
+extern void add_local_ternary_reloption(local_relopts *relopts,
+ const char *name, const char *desc,
+ ternary default_val, int offset);
extern void add_local_int_reloption(local_relopts *relopts, const char *name,
const char *desc, int default_val,
int min_val, int max_val, int offset);
diff --git a/src/include/c.h b/src/include/c.h
index 39022f8a9d..9c59ccccf8 100644
--- a/src/include/c.h
+++ b/src/include/c.h
@@ -488,6 +488,22 @@ typedef void (*pg_funcptr_t) (void);
#include <stdbool.h>
+/*
+ * ternary
+ * Boolean value with an extrea "unset" option
+ *
+ * Ternary data type is used in relation options that can be "true", "false" or
+ * "unset". Since relation options are used deep inside the PostgreSQL code,
+ * this type is declared globally.
+*/
+
+typedef enum ternary
+{
+ TERNARY_FALSE = 0,
+ TERNARY_TRUE = 1,
+ TERNARY_UNSET = -1
+} ternary;
+
/* ----------------------------------------------------------------
* Section 3: standard system types
diff --git a/src/include/utils/rel.h b/src/include/utils/rel.h
index b552359915..7346d618f9 100644
--- a/src/include/utils/rel.h
+++ b/src/include/utils/rel.h
@@ -346,8 +346,7 @@ typedef struct StdRdOptions
bool user_catalog_table; /* use as an additional catalog relation */
int parallel_workers; /* max number of parallel workers */
StdRdOptIndexCleanup vacuum_index_cleanup; /* controls index vacuuming */
- bool vacuum_truncate; /* enables vacuum to truncate a relation */
- bool vacuum_truncate_set; /* whether vacuum_truncate is set */
+ ternary vacuum_truncate; /* enables vacuum to truncate a relation */
/*
* Fraction of pages in a relation that vacuum can eagerly scan and fail
--
2.39.2
--nextPart4155600.3ZeAukHxDK
Content-Disposition: attachment;
filename="v2a-0003-Add-alias-to-be-used-as-unset-state.patch"
Content-Transfer-Encoding: 7Bit
Content-Type: text/x-patch; charset="unicode-2-0-utf-8";
name="v2a-0003-Add-alias-to-be-used-as-unset-state.patch"
^ permalink raw reply [nested|flat] 868+ messages in thread
* [PATCH v2a 2/4] Introduce ternary reloptions
@ 2025-09-04 14:41 Nikolay Shaplov <[email protected]>
0 siblings, 0 replies; 868+ messages in thread
From: Nikolay Shaplov @ 2025-09-04 14:41 UTC (permalink / raw)
Introduce ternary reloption as a replacement for current `vacuum_truncate`
implementation. Remove `vacuum_truncate_set` additional flag and using
`TERNARY_UNSET` value instead.
---
src/backend/access/common/reloptions.c | 129 ++++++++++++++++++++-----
src/backend/commands/vacuum.c | 4 +-
src/include/access/reloptions.h | 26 ++---
src/include/c.h | 16 +++
src/include/utils/rel.h | 3 +-
5 files changed, 135 insertions(+), 43 deletions(-)
diff --git a/src/backend/access/common/reloptions.c b/src/backend/access/common/reloptions.c
index 0af3fea68f..f543f11001 100644
--- a/src/backend/access/common/reloptions.c
+++ b/src/backend/access/common/reloptions.c
@@ -40,9 +40,9 @@
*
* To add an option:
*
- * (i) decide on a type (bool, integer, real, enum, string), name, default
- * value, upper and lower bounds (if applicable); for strings, consider a
- * validation routine.
+ * (i) decide on a type (bool, ternary, integer, real, enum, string), name,
+ * default value, upper and lower bounds (if applicable); for strings,
+ * consider a validation routine.
* (ii) add a record below (or use add_<type>_reloption).
* (iii) add it to the appropriate options struct (perhaps StdRdOptions)
* (iv) add it to the appropriate handling routine (perhaps
@@ -147,15 +147,6 @@ static relopt_bool boolRelOpts[] =
},
false
},
- {
- {
- "vacuum_truncate",
- "Enables vacuum to truncate empty pages at the end of this table",
- RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
- ShareUpdateExclusiveLock
- },
- true
- },
{
{
"deduplicate_items",
@@ -170,6 +161,21 @@ static relopt_bool boolRelOpts[] =
{{NULL}}
};
+static relopt_ternary ternaryRelOpts[] =
+{
+ {
+ {
+ "vacuum_truncate",
+ "Enables vacuum to truncate empty pages at the end of this table",
+ RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
+ ShareUpdateExclusiveLock
+ },
+ TERNARY_UNSET
+ },
+ /* list terminator */
+ {{NULL}}
+};
+
static relopt_int intRelOpts[] =
{
{
@@ -600,6 +606,13 @@ initialize_reloptions(void)
boolRelOpts[i].gen.lockmode));
j++;
}
+ for (i = 0; ternaryRelOpts[i].gen.name; i++)
+ {
+ Assert(DoLockModesConflict(ternaryRelOpts[i].gen.lockmode,
+ ternaryRelOpts[i].gen.lockmode));
+ j++;
+ }
+
for (i = 0; intRelOpts[i].gen.name; i++)
{
Assert(DoLockModesConflict(intRelOpts[i].gen.lockmode,
@@ -640,6 +653,14 @@ initialize_reloptions(void)
j++;
}
+ for (i = 0; ternaryRelOpts[i].gen.name; i++)
+ {
+ relOpts[j] = &ternaryRelOpts[i].gen;
+ relOpts[j]->type = RELOPT_TYPE_TERNARY;
+ relOpts[j]->namelen = strlen(relOpts[j]->name);
+ j++;
+ }
+
for (i = 0; intRelOpts[i].gen.name; i++)
{
relOpts[j] = &intRelOpts[i].gen;
@@ -800,6 +821,9 @@ allocate_reloption(bits32 kinds, int type, const char *name, const char *desc,
case RELOPT_TYPE_BOOL:
size = sizeof(relopt_bool);
break;
+ case RELOPT_TYPE_TERNARY:
+ size = sizeof(relopt_ternary);
+ break;
case RELOPT_TYPE_INT:
size = sizeof(relopt_int);
break;
@@ -883,6 +907,54 @@ add_local_bool_reloption(local_relopts *relopts, const char *name,
add_local_reloption(relopts, (relopt_gen *) newoption, offset);
}
+/*
+ * init_ternary_reloption
+ * Allocate and initialize a new ternary reloption
+ */
+static relopt_ternary *
+init_ternary_reloption(bits32 kinds, const char *name, const char *desc,
+ ternary default_val, LOCKMODE lockmode)
+{
+ relopt_ternary *newoption;
+
+ newoption = (relopt_ternary *) allocate_reloption(kinds,
+ RELOPT_TYPE_TERNARY, name, desc, lockmode);
+ newoption->default_val = default_val;
+
+ return newoption;
+}
+
+/*
+ * add_ternary_reloption
+ * Add a new ternary reloption
+ */
+void
+add_ternary_reloption(bits32 kinds, const char *name, const char *desc,
+ ternary default_val, LOCKMODE lockmode)
+{
+ relopt_ternary *newoption = init_ternary_reloption(kinds, name, desc,
+ default_val, lockmode);
+
+ add_reloption((relopt_gen *) newoption);
+}
+
+/*
+ * add_local_ternary_reloption
+ * Add a new ternary local reloption
+ *
+ * 'offset' is offset of ternary-typed field.
+ */
+void
+add_local_ternary_reloption(local_relopts *relopts, const char *name,
+ const char *desc, ternary default_val,
+ int offset)
+{
+ relopt_ternary *newoption = init_ternary_reloption(RELOPT_KIND_LOCAL,
+ name, desc,
+ default_val, 0);
+
+ add_local_reloption(relopts, (relopt_gen *) newoption, offset);
+}
/*
* init_real_reloption
@@ -1617,6 +1689,18 @@ parse_one_reloption(relopt_value *option, char *text_str, int text_len,
option->gen->name, value)));
}
break;
+ case RELOPT_TYPE_TERNARY:
+ {
+ bool b;
+ parsed = parse_bool(value, &b);
+ option->values.ternary_val = b ? TERNARY_TRUE : TERNARY_FALSE;
+ if (validate && !parsed)
+ ereport(ERROR,
+ (errcode(ERRCODE_INVALID_PARAMETER_VALUE),
+ errmsg("invalid value for ternary option \"%s\": %s",
+ option->gen->name, value)));
+ }
+ break;
case RELOPT_TYPE_INT:
{
relopt_int *optint = (relopt_int *) option->gen;
@@ -1780,17 +1864,6 @@ fillRelOptions(void *rdopts, Size basesize,
char *itempos = ((char *) rdopts) + elems[j].offset;
char *string_val;
- /*
- * If isset_offset is provided, store whether the reloption is
- * set there.
- */
- if (elems[j].isset_offset > 0)
- {
- char *setpos = ((char *) rdopts) + elems[j].isset_offset;
-
- *(bool *) setpos = options[i].isset;
- }
-
switch (options[i].gen->type)
{
case RELOPT_TYPE_BOOL:
@@ -1798,6 +1871,11 @@ fillRelOptions(void *rdopts, Size basesize,
options[i].values.bool_val :
((relopt_bool *) options[i].gen)->default_val;
break;
+ case RELOPT_TYPE_TERNARY:
+ *(ternary *) itempos = options[i].isset ?
+ options[i].values.ternary_val :
+ ((relopt_ternary *) options[i].gen)->default_val;
+ break;
case RELOPT_TYPE_INT:
*(int *) itempos = options[i].isset ?
options[i].values.int_val :
@@ -1912,8 +1990,8 @@ default_reloptions(Datum reloptions, bool validate, relopt_kind kind)
offsetof(StdRdOptions, parallel_workers)},
{"vacuum_index_cleanup", RELOPT_TYPE_ENUM,
offsetof(StdRdOptions, vacuum_index_cleanup)},
- {"vacuum_truncate", RELOPT_TYPE_BOOL,
- offsetof(StdRdOptions, vacuum_truncate), offsetof(StdRdOptions, vacuum_truncate_set)},
+ {"vacuum_truncate", RELOPT_TYPE_TERNARY,
+ offsetof(StdRdOptions, vacuum_truncate)},
{"vacuum_max_eager_freeze_failure_rate", RELOPT_TYPE_REAL,
offsetof(StdRdOptions, vacuum_max_eager_freeze_failure_rate)}
};
@@ -1993,7 +2071,6 @@ build_local_reloptions(local_relopts *relopts, Datum options, bool validate)
elems[i].optname = opt->option->name;
elems[i].opttype = opt->option->type;
elems[i].offset = opt->offset;
- elems[i].isset_offset = 0; /* not supported for local relopts yet */
i++;
}
diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c
index 733ef40ae7..977babff54 100644
--- a/src/backend/commands/vacuum.c
+++ b/src/backend/commands/vacuum.c
@@ -2221,9 +2221,9 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams params,
{
StdRdOptions *opts = (StdRdOptions *) rel->rd_options;
- if (opts && opts->vacuum_truncate_set)
+ if (opts && opts->vacuum_truncate != TERNARY_UNSET)
{
- if (opts->vacuum_truncate)
+ if (opts->vacuum_truncate == TERNARY_TRUE)
params.truncate = VACOPTVALUE_ENABLED;
else
params.truncate = VACOPTVALUE_DISABLED;
diff --git a/src/include/access/reloptions.h b/src/include/access/reloptions.h
index a604a4702c..a436697658 100644
--- a/src/include/access/reloptions.h
+++ b/src/include/access/reloptions.h
@@ -29,6 +29,7 @@
typedef enum relopt_type
{
RELOPT_TYPE_BOOL,
+ RELOPT_TYPE_TERNARY, /* on, off, unset */
RELOPT_TYPE_INT,
RELOPT_TYPE_REAL,
RELOPT_TYPE_ENUM,
@@ -80,6 +81,7 @@ typedef struct relopt_value
union
{
bool bool_val;
+ ternary ternary_val;
int int_val;
double real_val;
int enum_val;
@@ -94,6 +96,12 @@ typedef struct relopt_bool
bool default_val;
} relopt_bool;
+typedef struct relopt_rernary
+{
+ relopt_gen gen;
+ int default_val;
+} relopt_ternary;
+
typedef struct relopt_int
{
relopt_gen gen;
@@ -152,19 +160,6 @@ typedef struct
const char *optname; /* option's name */
relopt_type opttype; /* option's datatype */
int offset; /* offset of field in result struct */
-
- /*
- * isset_offset is an optional offset of a field in the result struct that
- * stores whether the option is explicitly set for the relation or if it
- * just picked up the default value. In most cases, this can be
- * accomplished by giving the reloption a special out-of-range default
- * value (e.g., some integer reloptions use -2), but this isn't always
- * possible. For example, a Boolean reloption cannot be given an
- * out-of-range default, so we need another way to discover the source of
- * its value. This offset is only used if given a value greater than
- * zero.
- */
- int isset_offset;
} relopt_parse_elt;
/* Local reloption definition */
@@ -195,6 +190,8 @@ typedef struct local_relopts
extern relopt_kind add_reloption_kind(void);
extern void add_bool_reloption(bits32 kinds, const char *name, const char *desc,
bool default_val, LOCKMODE lockmode);
+extern void add_ternary_reloption(bits32 kinds, const char *name,
+ const char *desc, int default_val, LOCKMODE lockmode);
extern void add_int_reloption(bits32 kinds, const char *name, const char *desc,
int default_val, int min_val, int max_val,
LOCKMODE lockmode);
@@ -214,6 +211,9 @@ extern void register_reloptions_validator(local_relopts *relopts,
extern void add_local_bool_reloption(local_relopts *relopts, const char *name,
const char *desc, bool default_val,
int offset);
+extern void add_local_ternary_reloption(local_relopts *relopts,
+ const char *name, const char *desc,
+ ternary default_val, int offset);
extern void add_local_int_reloption(local_relopts *relopts, const char *name,
const char *desc, int default_val,
int min_val, int max_val, int offset);
diff --git a/src/include/c.h b/src/include/c.h
index 39022f8a9d..9c59ccccf8 100644
--- a/src/include/c.h
+++ b/src/include/c.h
@@ -488,6 +488,22 @@ typedef void (*pg_funcptr_t) (void);
#include <stdbool.h>
+/*
+ * ternary
+ * Boolean value with an extrea "unset" option
+ *
+ * Ternary data type is used in relation options that can be "true", "false" or
+ * "unset". Since relation options are used deep inside the PostgreSQL code,
+ * this type is declared globally.
+*/
+
+typedef enum ternary
+{
+ TERNARY_FALSE = 0,
+ TERNARY_TRUE = 1,
+ TERNARY_UNSET = -1
+} ternary;
+
/* ----------------------------------------------------------------
* Section 3: standard system types
diff --git a/src/include/utils/rel.h b/src/include/utils/rel.h
index b552359915..7346d618f9 100644
--- a/src/include/utils/rel.h
+++ b/src/include/utils/rel.h
@@ -346,8 +346,7 @@ typedef struct StdRdOptions
bool user_catalog_table; /* use as an additional catalog relation */
int parallel_workers; /* max number of parallel workers */
StdRdOptIndexCleanup vacuum_index_cleanup; /* controls index vacuuming */
- bool vacuum_truncate; /* enables vacuum to truncate a relation */
- bool vacuum_truncate_set; /* whether vacuum_truncate is set */
+ ternary vacuum_truncate; /* enables vacuum to truncate a relation */
/*
* Fraction of pages in a relation that vacuum can eagerly scan and fail
--
2.39.2
--nextPart4155600.3ZeAukHxDK
Content-Disposition: attachment;
filename="v2a-0003-Add-alias-to-be-used-as-unset-state.patch"
Content-Transfer-Encoding: 7Bit
Content-Type: text/x-patch; charset="unicode-2-0-utf-8";
name="v2a-0003-Add-alias-to-be-used-as-unset-state.patch"
^ permalink raw reply [nested|flat] 868+ messages in thread
* [PATCH v2 2/4] Introduce ternary reloptions
@ 2025-09-04 14:41 Nikolay Shaplov <[email protected]>
0 siblings, 0 replies; 868+ messages in thread
From: Nikolay Shaplov @ 2025-09-04 14:41 UTC (permalink / raw)
Introduce ternary reloption as a replacement for current `vacuum_truncate`
implementation. Remove `vacuum_truncate_set` additional flag and using
`TERNARY_UNSET` value instead.
---
src/backend/access/common/reloptions.c | 129 ++++++++++++++++++++-----
src/backend/commands/vacuum.c | 4 +-
src/include/access/reloptions.h | 26 ++---
src/include/c.h | 16 +++
src/include/utils/rel.h | 3 +-
5 files changed, 135 insertions(+), 43 deletions(-)
diff --git a/src/backend/access/common/reloptions.c b/src/backend/access/common/reloptions.c
index 0af3fea68f..f543f11001 100644
--- a/src/backend/access/common/reloptions.c
+++ b/src/backend/access/common/reloptions.c
@@ -40,9 +40,9 @@
*
* To add an option:
*
- * (i) decide on a type (bool, integer, real, enum, string), name, default
- * value, upper and lower bounds (if applicable); for strings, consider a
- * validation routine.
+ * (i) decide on a type (bool, ternary, integer, real, enum, string), name,
+ * default value, upper and lower bounds (if applicable); for strings,
+ * consider a validation routine.
* (ii) add a record below (or use add_<type>_reloption).
* (iii) add it to the appropriate options struct (perhaps StdRdOptions)
* (iv) add it to the appropriate handling routine (perhaps
@@ -147,15 +147,6 @@ static relopt_bool boolRelOpts[] =
},
false
},
- {
- {
- "vacuum_truncate",
- "Enables vacuum to truncate empty pages at the end of this table",
- RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
- ShareUpdateExclusiveLock
- },
- true
- },
{
{
"deduplicate_items",
@@ -170,6 +161,21 @@ static relopt_bool boolRelOpts[] =
{{NULL}}
};
+static relopt_ternary ternaryRelOpts[] =
+{
+ {
+ {
+ "vacuum_truncate",
+ "Enables vacuum to truncate empty pages at the end of this table",
+ RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
+ ShareUpdateExclusiveLock
+ },
+ TERNARY_UNSET
+ },
+ /* list terminator */
+ {{NULL}}
+};
+
static relopt_int intRelOpts[] =
{
{
@@ -600,6 +606,13 @@ initialize_reloptions(void)
boolRelOpts[i].gen.lockmode));
j++;
}
+ for (i = 0; ternaryRelOpts[i].gen.name; i++)
+ {
+ Assert(DoLockModesConflict(ternaryRelOpts[i].gen.lockmode,
+ ternaryRelOpts[i].gen.lockmode));
+ j++;
+ }
+
for (i = 0; intRelOpts[i].gen.name; i++)
{
Assert(DoLockModesConflict(intRelOpts[i].gen.lockmode,
@@ -640,6 +653,14 @@ initialize_reloptions(void)
j++;
}
+ for (i = 0; ternaryRelOpts[i].gen.name; i++)
+ {
+ relOpts[j] = &ternaryRelOpts[i].gen;
+ relOpts[j]->type = RELOPT_TYPE_TERNARY;
+ relOpts[j]->namelen = strlen(relOpts[j]->name);
+ j++;
+ }
+
for (i = 0; intRelOpts[i].gen.name; i++)
{
relOpts[j] = &intRelOpts[i].gen;
@@ -800,6 +821,9 @@ allocate_reloption(bits32 kinds, int type, const char *name, const char *desc,
case RELOPT_TYPE_BOOL:
size = sizeof(relopt_bool);
break;
+ case RELOPT_TYPE_TERNARY:
+ size = sizeof(relopt_ternary);
+ break;
case RELOPT_TYPE_INT:
size = sizeof(relopt_int);
break;
@@ -883,6 +907,54 @@ add_local_bool_reloption(local_relopts *relopts, const char *name,
add_local_reloption(relopts, (relopt_gen *) newoption, offset);
}
+/*
+ * init_ternary_reloption
+ * Allocate and initialize a new ternary reloption
+ */
+static relopt_ternary *
+init_ternary_reloption(bits32 kinds, const char *name, const char *desc,
+ ternary default_val, LOCKMODE lockmode)
+{
+ relopt_ternary *newoption;
+
+ newoption = (relopt_ternary *) allocate_reloption(kinds,
+ RELOPT_TYPE_TERNARY, name, desc, lockmode);
+ newoption->default_val = default_val;
+
+ return newoption;
+}
+
+/*
+ * add_ternary_reloption
+ * Add a new ternary reloption
+ */
+void
+add_ternary_reloption(bits32 kinds, const char *name, const char *desc,
+ ternary default_val, LOCKMODE lockmode)
+{
+ relopt_ternary *newoption = init_ternary_reloption(kinds, name, desc,
+ default_val, lockmode);
+
+ add_reloption((relopt_gen *) newoption);
+}
+
+/*
+ * add_local_ternary_reloption
+ * Add a new ternary local reloption
+ *
+ * 'offset' is offset of ternary-typed field.
+ */
+void
+add_local_ternary_reloption(local_relopts *relopts, const char *name,
+ const char *desc, ternary default_val,
+ int offset)
+{
+ relopt_ternary *newoption = init_ternary_reloption(RELOPT_KIND_LOCAL,
+ name, desc,
+ default_val, 0);
+
+ add_local_reloption(relopts, (relopt_gen *) newoption, offset);
+}
/*
* init_real_reloption
@@ -1617,6 +1689,18 @@ parse_one_reloption(relopt_value *option, char *text_str, int text_len,
option->gen->name, value)));
}
break;
+ case RELOPT_TYPE_TERNARY:
+ {
+ bool b;
+ parsed = parse_bool(value, &b);
+ option->values.ternary_val = b ? TERNARY_TRUE : TERNARY_FALSE;
+ if (validate && !parsed)
+ ereport(ERROR,
+ (errcode(ERRCODE_INVALID_PARAMETER_VALUE),
+ errmsg("invalid value for ternary option \"%s\": %s",
+ option->gen->name, value)));
+ }
+ break;
case RELOPT_TYPE_INT:
{
relopt_int *optint = (relopt_int *) option->gen;
@@ -1780,17 +1864,6 @@ fillRelOptions(void *rdopts, Size basesize,
char *itempos = ((char *) rdopts) + elems[j].offset;
char *string_val;
- /*
- * If isset_offset is provided, store whether the reloption is
- * set there.
- */
- if (elems[j].isset_offset > 0)
- {
- char *setpos = ((char *) rdopts) + elems[j].isset_offset;
-
- *(bool *) setpos = options[i].isset;
- }
-
switch (options[i].gen->type)
{
case RELOPT_TYPE_BOOL:
@@ -1798,6 +1871,11 @@ fillRelOptions(void *rdopts, Size basesize,
options[i].values.bool_val :
((relopt_bool *) options[i].gen)->default_val;
break;
+ case RELOPT_TYPE_TERNARY:
+ *(ternary *) itempos = options[i].isset ?
+ options[i].values.ternary_val :
+ ((relopt_ternary *) options[i].gen)->default_val;
+ break;
case RELOPT_TYPE_INT:
*(int *) itempos = options[i].isset ?
options[i].values.int_val :
@@ -1912,8 +1990,8 @@ default_reloptions(Datum reloptions, bool validate, relopt_kind kind)
offsetof(StdRdOptions, parallel_workers)},
{"vacuum_index_cleanup", RELOPT_TYPE_ENUM,
offsetof(StdRdOptions, vacuum_index_cleanup)},
- {"vacuum_truncate", RELOPT_TYPE_BOOL,
- offsetof(StdRdOptions, vacuum_truncate), offsetof(StdRdOptions, vacuum_truncate_set)},
+ {"vacuum_truncate", RELOPT_TYPE_TERNARY,
+ offsetof(StdRdOptions, vacuum_truncate)},
{"vacuum_max_eager_freeze_failure_rate", RELOPT_TYPE_REAL,
offsetof(StdRdOptions, vacuum_max_eager_freeze_failure_rate)}
};
@@ -1993,7 +2071,6 @@ build_local_reloptions(local_relopts *relopts, Datum options, bool validate)
elems[i].optname = opt->option->name;
elems[i].opttype = opt->option->type;
elems[i].offset = opt->offset;
- elems[i].isset_offset = 0; /* not supported for local relopts yet */
i++;
}
diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c
index 733ef40ae7..977babff54 100644
--- a/src/backend/commands/vacuum.c
+++ b/src/backend/commands/vacuum.c
@@ -2221,9 +2221,9 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams params,
{
StdRdOptions *opts = (StdRdOptions *) rel->rd_options;
- if (opts && opts->vacuum_truncate_set)
+ if (opts && opts->vacuum_truncate != TERNARY_UNSET)
{
- if (opts->vacuum_truncate)
+ if (opts->vacuum_truncate == TERNARY_TRUE)
params.truncate = VACOPTVALUE_ENABLED;
else
params.truncate = VACOPTVALUE_DISABLED;
diff --git a/src/include/access/reloptions.h b/src/include/access/reloptions.h
index a604a4702c..a436697658 100644
--- a/src/include/access/reloptions.h
+++ b/src/include/access/reloptions.h
@@ -29,6 +29,7 @@
typedef enum relopt_type
{
RELOPT_TYPE_BOOL,
+ RELOPT_TYPE_TERNARY, /* on, off, unset */
RELOPT_TYPE_INT,
RELOPT_TYPE_REAL,
RELOPT_TYPE_ENUM,
@@ -80,6 +81,7 @@ typedef struct relopt_value
union
{
bool bool_val;
+ ternary ternary_val;
int int_val;
double real_val;
int enum_val;
@@ -94,6 +96,12 @@ typedef struct relopt_bool
bool default_val;
} relopt_bool;
+typedef struct relopt_rernary
+{
+ relopt_gen gen;
+ int default_val;
+} relopt_ternary;
+
typedef struct relopt_int
{
relopt_gen gen;
@@ -152,19 +160,6 @@ typedef struct
const char *optname; /* option's name */
relopt_type opttype; /* option's datatype */
int offset; /* offset of field in result struct */
-
- /*
- * isset_offset is an optional offset of a field in the result struct that
- * stores whether the option is explicitly set for the relation or if it
- * just picked up the default value. In most cases, this can be
- * accomplished by giving the reloption a special out-of-range default
- * value (e.g., some integer reloptions use -2), but this isn't always
- * possible. For example, a Boolean reloption cannot be given an
- * out-of-range default, so we need another way to discover the source of
- * its value. This offset is only used if given a value greater than
- * zero.
- */
- int isset_offset;
} relopt_parse_elt;
/* Local reloption definition */
@@ -195,6 +190,8 @@ typedef struct local_relopts
extern relopt_kind add_reloption_kind(void);
extern void add_bool_reloption(bits32 kinds, const char *name, const char *desc,
bool default_val, LOCKMODE lockmode);
+extern void add_ternary_reloption(bits32 kinds, const char *name,
+ const char *desc, int default_val, LOCKMODE lockmode);
extern void add_int_reloption(bits32 kinds, const char *name, const char *desc,
int default_val, int min_val, int max_val,
LOCKMODE lockmode);
@@ -214,6 +211,9 @@ extern void register_reloptions_validator(local_relopts *relopts,
extern void add_local_bool_reloption(local_relopts *relopts, const char *name,
const char *desc, bool default_val,
int offset);
+extern void add_local_ternary_reloption(local_relopts *relopts,
+ const char *name, const char *desc,
+ ternary default_val, int offset);
extern void add_local_int_reloption(local_relopts *relopts, const char *name,
const char *desc, int default_val,
int min_val, int max_val, int offset);
diff --git a/src/include/c.h b/src/include/c.h
index 39022f8a9d..9c59ccccf8 100644
--- a/src/include/c.h
+++ b/src/include/c.h
@@ -488,6 +488,22 @@ typedef void (*pg_funcptr_t) (void);
#include <stdbool.h>
+/*
+ * ternary
+ * Boolean value with an extrea "unset" option
+ *
+ * Ternary data type is used in relation options that can be "true", "false" or
+ * "unset". Since relation options are used deep inside the PostgreSQL code,
+ * this type is declared globally.
+*/
+
+typedef enum ternary
+{
+ TERNARY_FALSE = 0,
+ TERNARY_TRUE = 1,
+ TERNARY_UNSET = -1
+} ternary;
+
/* ----------------------------------------------------------------
* Section 3: standard system types
diff --git a/src/include/utils/rel.h b/src/include/utils/rel.h
index b552359915..7346d618f9 100644
--- a/src/include/utils/rel.h
+++ b/src/include/utils/rel.h
@@ -346,8 +346,7 @@ typedef struct StdRdOptions
bool user_catalog_table; /* use as an additional catalog relation */
int parallel_workers; /* max number of parallel workers */
StdRdOptIndexCleanup vacuum_index_cleanup; /* controls index vacuuming */
- bool vacuum_truncate; /* enables vacuum to truncate a relation */
- bool vacuum_truncate_set; /* whether vacuum_truncate is set */
+ ternary vacuum_truncate; /* enables vacuum to truncate a relation */
/*
* Fraction of pages in a relation that vacuum can eagerly scan and fail
--
2.39.2
--nextPart5129932.5fSG56mABF
Content-Disposition: attachment;
filename="v2-0003-Add-alias-to-be-used-as-unset-state.patch"
Content-Transfer-Encoding: 7Bit
Content-Type: text/x-patch; charset="unicode-2-0-utf-8";
name="v2-0003-Add-alias-to-be-used-as-unset-state.patch"
^ permalink raw reply [nested|flat] 868+ messages in thread
* [PATCH v2a 2/4] Introduce ternary reloptions
@ 2025-09-04 14:41 Nikolay Shaplov <[email protected]>
0 siblings, 0 replies; 868+ messages in thread
From: Nikolay Shaplov @ 2025-09-04 14:41 UTC (permalink / raw)
Introduce ternary reloption as a replacement for current `vacuum_truncate`
implementation. Remove `vacuum_truncate_set` additional flag and using
`TERNARY_UNSET` value instead.
---
src/backend/access/common/reloptions.c | 129 ++++++++++++++++++++-----
src/backend/commands/vacuum.c | 4 +-
src/include/access/reloptions.h | 26 ++---
src/include/c.h | 16 +++
src/include/utils/rel.h | 3 +-
5 files changed, 135 insertions(+), 43 deletions(-)
diff --git a/src/backend/access/common/reloptions.c b/src/backend/access/common/reloptions.c
index 0af3fea68f..f543f11001 100644
--- a/src/backend/access/common/reloptions.c
+++ b/src/backend/access/common/reloptions.c
@@ -40,9 +40,9 @@
*
* To add an option:
*
- * (i) decide on a type (bool, integer, real, enum, string), name, default
- * value, upper and lower bounds (if applicable); for strings, consider a
- * validation routine.
+ * (i) decide on a type (bool, ternary, integer, real, enum, string), name,
+ * default value, upper and lower bounds (if applicable); for strings,
+ * consider a validation routine.
* (ii) add a record below (or use add_<type>_reloption).
* (iii) add it to the appropriate options struct (perhaps StdRdOptions)
* (iv) add it to the appropriate handling routine (perhaps
@@ -147,15 +147,6 @@ static relopt_bool boolRelOpts[] =
},
false
},
- {
- {
- "vacuum_truncate",
- "Enables vacuum to truncate empty pages at the end of this table",
- RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
- ShareUpdateExclusiveLock
- },
- true
- },
{
{
"deduplicate_items",
@@ -170,6 +161,21 @@ static relopt_bool boolRelOpts[] =
{{NULL}}
};
+static relopt_ternary ternaryRelOpts[] =
+{
+ {
+ {
+ "vacuum_truncate",
+ "Enables vacuum to truncate empty pages at the end of this table",
+ RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
+ ShareUpdateExclusiveLock
+ },
+ TERNARY_UNSET
+ },
+ /* list terminator */
+ {{NULL}}
+};
+
static relopt_int intRelOpts[] =
{
{
@@ -600,6 +606,13 @@ initialize_reloptions(void)
boolRelOpts[i].gen.lockmode));
j++;
}
+ for (i = 0; ternaryRelOpts[i].gen.name; i++)
+ {
+ Assert(DoLockModesConflict(ternaryRelOpts[i].gen.lockmode,
+ ternaryRelOpts[i].gen.lockmode));
+ j++;
+ }
+
for (i = 0; intRelOpts[i].gen.name; i++)
{
Assert(DoLockModesConflict(intRelOpts[i].gen.lockmode,
@@ -640,6 +653,14 @@ initialize_reloptions(void)
j++;
}
+ for (i = 0; ternaryRelOpts[i].gen.name; i++)
+ {
+ relOpts[j] = &ternaryRelOpts[i].gen;
+ relOpts[j]->type = RELOPT_TYPE_TERNARY;
+ relOpts[j]->namelen = strlen(relOpts[j]->name);
+ j++;
+ }
+
for (i = 0; intRelOpts[i].gen.name; i++)
{
relOpts[j] = &intRelOpts[i].gen;
@@ -800,6 +821,9 @@ allocate_reloption(bits32 kinds, int type, const char *name, const char *desc,
case RELOPT_TYPE_BOOL:
size = sizeof(relopt_bool);
break;
+ case RELOPT_TYPE_TERNARY:
+ size = sizeof(relopt_ternary);
+ break;
case RELOPT_TYPE_INT:
size = sizeof(relopt_int);
break;
@@ -883,6 +907,54 @@ add_local_bool_reloption(local_relopts *relopts, const char *name,
add_local_reloption(relopts, (relopt_gen *) newoption, offset);
}
+/*
+ * init_ternary_reloption
+ * Allocate and initialize a new ternary reloption
+ */
+static relopt_ternary *
+init_ternary_reloption(bits32 kinds, const char *name, const char *desc,
+ ternary default_val, LOCKMODE lockmode)
+{
+ relopt_ternary *newoption;
+
+ newoption = (relopt_ternary *) allocate_reloption(kinds,
+ RELOPT_TYPE_TERNARY, name, desc, lockmode);
+ newoption->default_val = default_val;
+
+ return newoption;
+}
+
+/*
+ * add_ternary_reloption
+ * Add a new ternary reloption
+ */
+void
+add_ternary_reloption(bits32 kinds, const char *name, const char *desc,
+ ternary default_val, LOCKMODE lockmode)
+{
+ relopt_ternary *newoption = init_ternary_reloption(kinds, name, desc,
+ default_val, lockmode);
+
+ add_reloption((relopt_gen *) newoption);
+}
+
+/*
+ * add_local_ternary_reloption
+ * Add a new ternary local reloption
+ *
+ * 'offset' is offset of ternary-typed field.
+ */
+void
+add_local_ternary_reloption(local_relopts *relopts, const char *name,
+ const char *desc, ternary default_val,
+ int offset)
+{
+ relopt_ternary *newoption = init_ternary_reloption(RELOPT_KIND_LOCAL,
+ name, desc,
+ default_val, 0);
+
+ add_local_reloption(relopts, (relopt_gen *) newoption, offset);
+}
/*
* init_real_reloption
@@ -1617,6 +1689,18 @@ parse_one_reloption(relopt_value *option, char *text_str, int text_len,
option->gen->name, value)));
}
break;
+ case RELOPT_TYPE_TERNARY:
+ {
+ bool b;
+ parsed = parse_bool(value, &b);
+ option->values.ternary_val = b ? TERNARY_TRUE : TERNARY_FALSE;
+ if (validate && !parsed)
+ ereport(ERROR,
+ (errcode(ERRCODE_INVALID_PARAMETER_VALUE),
+ errmsg("invalid value for ternary option \"%s\": %s",
+ option->gen->name, value)));
+ }
+ break;
case RELOPT_TYPE_INT:
{
relopt_int *optint = (relopt_int *) option->gen;
@@ -1780,17 +1864,6 @@ fillRelOptions(void *rdopts, Size basesize,
char *itempos = ((char *) rdopts) + elems[j].offset;
char *string_val;
- /*
- * If isset_offset is provided, store whether the reloption is
- * set there.
- */
- if (elems[j].isset_offset > 0)
- {
- char *setpos = ((char *) rdopts) + elems[j].isset_offset;
-
- *(bool *) setpos = options[i].isset;
- }
-
switch (options[i].gen->type)
{
case RELOPT_TYPE_BOOL:
@@ -1798,6 +1871,11 @@ fillRelOptions(void *rdopts, Size basesize,
options[i].values.bool_val :
((relopt_bool *) options[i].gen)->default_val;
break;
+ case RELOPT_TYPE_TERNARY:
+ *(ternary *) itempos = options[i].isset ?
+ options[i].values.ternary_val :
+ ((relopt_ternary *) options[i].gen)->default_val;
+ break;
case RELOPT_TYPE_INT:
*(int *) itempos = options[i].isset ?
options[i].values.int_val :
@@ -1912,8 +1990,8 @@ default_reloptions(Datum reloptions, bool validate, relopt_kind kind)
offsetof(StdRdOptions, parallel_workers)},
{"vacuum_index_cleanup", RELOPT_TYPE_ENUM,
offsetof(StdRdOptions, vacuum_index_cleanup)},
- {"vacuum_truncate", RELOPT_TYPE_BOOL,
- offsetof(StdRdOptions, vacuum_truncate), offsetof(StdRdOptions, vacuum_truncate_set)},
+ {"vacuum_truncate", RELOPT_TYPE_TERNARY,
+ offsetof(StdRdOptions, vacuum_truncate)},
{"vacuum_max_eager_freeze_failure_rate", RELOPT_TYPE_REAL,
offsetof(StdRdOptions, vacuum_max_eager_freeze_failure_rate)}
};
@@ -1993,7 +2071,6 @@ build_local_reloptions(local_relopts *relopts, Datum options, bool validate)
elems[i].optname = opt->option->name;
elems[i].opttype = opt->option->type;
elems[i].offset = opt->offset;
- elems[i].isset_offset = 0; /* not supported for local relopts yet */
i++;
}
diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c
index 733ef40ae7..977babff54 100644
--- a/src/backend/commands/vacuum.c
+++ b/src/backend/commands/vacuum.c
@@ -2221,9 +2221,9 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams params,
{
StdRdOptions *opts = (StdRdOptions *) rel->rd_options;
- if (opts && opts->vacuum_truncate_set)
+ if (opts && opts->vacuum_truncate != TERNARY_UNSET)
{
- if (opts->vacuum_truncate)
+ if (opts->vacuum_truncate == TERNARY_TRUE)
params.truncate = VACOPTVALUE_ENABLED;
else
params.truncate = VACOPTVALUE_DISABLED;
diff --git a/src/include/access/reloptions.h b/src/include/access/reloptions.h
index a604a4702c..a436697658 100644
--- a/src/include/access/reloptions.h
+++ b/src/include/access/reloptions.h
@@ -29,6 +29,7 @@
typedef enum relopt_type
{
RELOPT_TYPE_BOOL,
+ RELOPT_TYPE_TERNARY, /* on, off, unset */
RELOPT_TYPE_INT,
RELOPT_TYPE_REAL,
RELOPT_TYPE_ENUM,
@@ -80,6 +81,7 @@ typedef struct relopt_value
union
{
bool bool_val;
+ ternary ternary_val;
int int_val;
double real_val;
int enum_val;
@@ -94,6 +96,12 @@ typedef struct relopt_bool
bool default_val;
} relopt_bool;
+typedef struct relopt_rernary
+{
+ relopt_gen gen;
+ int default_val;
+} relopt_ternary;
+
typedef struct relopt_int
{
relopt_gen gen;
@@ -152,19 +160,6 @@ typedef struct
const char *optname; /* option's name */
relopt_type opttype; /* option's datatype */
int offset; /* offset of field in result struct */
-
- /*
- * isset_offset is an optional offset of a field in the result struct that
- * stores whether the option is explicitly set for the relation or if it
- * just picked up the default value. In most cases, this can be
- * accomplished by giving the reloption a special out-of-range default
- * value (e.g., some integer reloptions use -2), but this isn't always
- * possible. For example, a Boolean reloption cannot be given an
- * out-of-range default, so we need another way to discover the source of
- * its value. This offset is only used if given a value greater than
- * zero.
- */
- int isset_offset;
} relopt_parse_elt;
/* Local reloption definition */
@@ -195,6 +190,8 @@ typedef struct local_relopts
extern relopt_kind add_reloption_kind(void);
extern void add_bool_reloption(bits32 kinds, const char *name, const char *desc,
bool default_val, LOCKMODE lockmode);
+extern void add_ternary_reloption(bits32 kinds, const char *name,
+ const char *desc, int default_val, LOCKMODE lockmode);
extern void add_int_reloption(bits32 kinds, const char *name, const char *desc,
int default_val, int min_val, int max_val,
LOCKMODE lockmode);
@@ -214,6 +211,9 @@ extern void register_reloptions_validator(local_relopts *relopts,
extern void add_local_bool_reloption(local_relopts *relopts, const char *name,
const char *desc, bool default_val,
int offset);
+extern void add_local_ternary_reloption(local_relopts *relopts,
+ const char *name, const char *desc,
+ ternary default_val, int offset);
extern void add_local_int_reloption(local_relopts *relopts, const char *name,
const char *desc, int default_val,
int min_val, int max_val, int offset);
diff --git a/src/include/c.h b/src/include/c.h
index 39022f8a9d..9c59ccccf8 100644
--- a/src/include/c.h
+++ b/src/include/c.h
@@ -488,6 +488,22 @@ typedef void (*pg_funcptr_t) (void);
#include <stdbool.h>
+/*
+ * ternary
+ * Boolean value with an extrea "unset" option
+ *
+ * Ternary data type is used in relation options that can be "true", "false" or
+ * "unset". Since relation options are used deep inside the PostgreSQL code,
+ * this type is declared globally.
+*/
+
+typedef enum ternary
+{
+ TERNARY_FALSE = 0,
+ TERNARY_TRUE = 1,
+ TERNARY_UNSET = -1
+} ternary;
+
/* ----------------------------------------------------------------
* Section 3: standard system types
diff --git a/src/include/utils/rel.h b/src/include/utils/rel.h
index b552359915..7346d618f9 100644
--- a/src/include/utils/rel.h
+++ b/src/include/utils/rel.h
@@ -346,8 +346,7 @@ typedef struct StdRdOptions
bool user_catalog_table; /* use as an additional catalog relation */
int parallel_workers; /* max number of parallel workers */
StdRdOptIndexCleanup vacuum_index_cleanup; /* controls index vacuuming */
- bool vacuum_truncate; /* enables vacuum to truncate a relation */
- bool vacuum_truncate_set; /* whether vacuum_truncate is set */
+ ternary vacuum_truncate; /* enables vacuum to truncate a relation */
/*
* Fraction of pages in a relation that vacuum can eagerly scan and fail
--
2.39.2
--nextPart4155600.3ZeAukHxDK
Content-Disposition: attachment;
filename="v2a-0003-Add-alias-to-be-used-as-unset-state.patch"
Content-Transfer-Encoding: 7Bit
Content-Type: text/x-patch; charset="unicode-2-0-utf-8";
name="v2a-0003-Add-alias-to-be-used-as-unset-state.patch"
^ permalink raw reply [nested|flat] 868+ messages in thread
* [PATCH v2 2/4] Introduce ternary reloptions
@ 2025-09-04 14:41 Nikolay Shaplov <[email protected]>
0 siblings, 0 replies; 868+ messages in thread
From: Nikolay Shaplov @ 2025-09-04 14:41 UTC (permalink / raw)
Introduce ternary reloption as a replacement for current `vacuum_truncate`
implementation. Remove `vacuum_truncate_set` additional flag and using
`TERNARY_UNSET` value instead.
---
src/backend/access/common/reloptions.c | 129 ++++++++++++++++++++-----
src/backend/commands/vacuum.c | 4 +-
src/include/access/reloptions.h | 26 ++---
src/include/c.h | 16 +++
src/include/utils/rel.h | 3 +-
5 files changed, 135 insertions(+), 43 deletions(-)
diff --git a/src/backend/access/common/reloptions.c b/src/backend/access/common/reloptions.c
index 0af3fea68f..f543f11001 100644
--- a/src/backend/access/common/reloptions.c
+++ b/src/backend/access/common/reloptions.c
@@ -40,9 +40,9 @@
*
* To add an option:
*
- * (i) decide on a type (bool, integer, real, enum, string), name, default
- * value, upper and lower bounds (if applicable); for strings, consider a
- * validation routine.
+ * (i) decide on a type (bool, ternary, integer, real, enum, string), name,
+ * default value, upper and lower bounds (if applicable); for strings,
+ * consider a validation routine.
* (ii) add a record below (or use add_<type>_reloption).
* (iii) add it to the appropriate options struct (perhaps StdRdOptions)
* (iv) add it to the appropriate handling routine (perhaps
@@ -147,15 +147,6 @@ static relopt_bool boolRelOpts[] =
},
false
},
- {
- {
- "vacuum_truncate",
- "Enables vacuum to truncate empty pages at the end of this table",
- RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
- ShareUpdateExclusiveLock
- },
- true
- },
{
{
"deduplicate_items",
@@ -170,6 +161,21 @@ static relopt_bool boolRelOpts[] =
{{NULL}}
};
+static relopt_ternary ternaryRelOpts[] =
+{
+ {
+ {
+ "vacuum_truncate",
+ "Enables vacuum to truncate empty pages at the end of this table",
+ RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
+ ShareUpdateExclusiveLock
+ },
+ TERNARY_UNSET
+ },
+ /* list terminator */
+ {{NULL}}
+};
+
static relopt_int intRelOpts[] =
{
{
@@ -600,6 +606,13 @@ initialize_reloptions(void)
boolRelOpts[i].gen.lockmode));
j++;
}
+ for (i = 0; ternaryRelOpts[i].gen.name; i++)
+ {
+ Assert(DoLockModesConflict(ternaryRelOpts[i].gen.lockmode,
+ ternaryRelOpts[i].gen.lockmode));
+ j++;
+ }
+
for (i = 0; intRelOpts[i].gen.name; i++)
{
Assert(DoLockModesConflict(intRelOpts[i].gen.lockmode,
@@ -640,6 +653,14 @@ initialize_reloptions(void)
j++;
}
+ for (i = 0; ternaryRelOpts[i].gen.name; i++)
+ {
+ relOpts[j] = &ternaryRelOpts[i].gen;
+ relOpts[j]->type = RELOPT_TYPE_TERNARY;
+ relOpts[j]->namelen = strlen(relOpts[j]->name);
+ j++;
+ }
+
for (i = 0; intRelOpts[i].gen.name; i++)
{
relOpts[j] = &intRelOpts[i].gen;
@@ -800,6 +821,9 @@ allocate_reloption(bits32 kinds, int type, const char *name, const char *desc,
case RELOPT_TYPE_BOOL:
size = sizeof(relopt_bool);
break;
+ case RELOPT_TYPE_TERNARY:
+ size = sizeof(relopt_ternary);
+ break;
case RELOPT_TYPE_INT:
size = sizeof(relopt_int);
break;
@@ -883,6 +907,54 @@ add_local_bool_reloption(local_relopts *relopts, const char *name,
add_local_reloption(relopts, (relopt_gen *) newoption, offset);
}
+/*
+ * init_ternary_reloption
+ * Allocate and initialize a new ternary reloption
+ */
+static relopt_ternary *
+init_ternary_reloption(bits32 kinds, const char *name, const char *desc,
+ ternary default_val, LOCKMODE lockmode)
+{
+ relopt_ternary *newoption;
+
+ newoption = (relopt_ternary *) allocate_reloption(kinds,
+ RELOPT_TYPE_TERNARY, name, desc, lockmode);
+ newoption->default_val = default_val;
+
+ return newoption;
+}
+
+/*
+ * add_ternary_reloption
+ * Add a new ternary reloption
+ */
+void
+add_ternary_reloption(bits32 kinds, const char *name, const char *desc,
+ ternary default_val, LOCKMODE lockmode)
+{
+ relopt_ternary *newoption = init_ternary_reloption(kinds, name, desc,
+ default_val, lockmode);
+
+ add_reloption((relopt_gen *) newoption);
+}
+
+/*
+ * add_local_ternary_reloption
+ * Add a new ternary local reloption
+ *
+ * 'offset' is offset of ternary-typed field.
+ */
+void
+add_local_ternary_reloption(local_relopts *relopts, const char *name,
+ const char *desc, ternary default_val,
+ int offset)
+{
+ relopt_ternary *newoption = init_ternary_reloption(RELOPT_KIND_LOCAL,
+ name, desc,
+ default_val, 0);
+
+ add_local_reloption(relopts, (relopt_gen *) newoption, offset);
+}
/*
* init_real_reloption
@@ -1617,6 +1689,18 @@ parse_one_reloption(relopt_value *option, char *text_str, int text_len,
option->gen->name, value)));
}
break;
+ case RELOPT_TYPE_TERNARY:
+ {
+ bool b;
+ parsed = parse_bool(value, &b);
+ option->values.ternary_val = b ? TERNARY_TRUE : TERNARY_FALSE;
+ if (validate && !parsed)
+ ereport(ERROR,
+ (errcode(ERRCODE_INVALID_PARAMETER_VALUE),
+ errmsg("invalid value for ternary option \"%s\": %s",
+ option->gen->name, value)));
+ }
+ break;
case RELOPT_TYPE_INT:
{
relopt_int *optint = (relopt_int *) option->gen;
@@ -1780,17 +1864,6 @@ fillRelOptions(void *rdopts, Size basesize,
char *itempos = ((char *) rdopts) + elems[j].offset;
char *string_val;
- /*
- * If isset_offset is provided, store whether the reloption is
- * set there.
- */
- if (elems[j].isset_offset > 0)
- {
- char *setpos = ((char *) rdopts) + elems[j].isset_offset;
-
- *(bool *) setpos = options[i].isset;
- }
-
switch (options[i].gen->type)
{
case RELOPT_TYPE_BOOL:
@@ -1798,6 +1871,11 @@ fillRelOptions(void *rdopts, Size basesize,
options[i].values.bool_val :
((relopt_bool *) options[i].gen)->default_val;
break;
+ case RELOPT_TYPE_TERNARY:
+ *(ternary *) itempos = options[i].isset ?
+ options[i].values.ternary_val :
+ ((relopt_ternary *) options[i].gen)->default_val;
+ break;
case RELOPT_TYPE_INT:
*(int *) itempos = options[i].isset ?
options[i].values.int_val :
@@ -1912,8 +1990,8 @@ default_reloptions(Datum reloptions, bool validate, relopt_kind kind)
offsetof(StdRdOptions, parallel_workers)},
{"vacuum_index_cleanup", RELOPT_TYPE_ENUM,
offsetof(StdRdOptions, vacuum_index_cleanup)},
- {"vacuum_truncate", RELOPT_TYPE_BOOL,
- offsetof(StdRdOptions, vacuum_truncate), offsetof(StdRdOptions, vacuum_truncate_set)},
+ {"vacuum_truncate", RELOPT_TYPE_TERNARY,
+ offsetof(StdRdOptions, vacuum_truncate)},
{"vacuum_max_eager_freeze_failure_rate", RELOPT_TYPE_REAL,
offsetof(StdRdOptions, vacuum_max_eager_freeze_failure_rate)}
};
@@ -1993,7 +2071,6 @@ build_local_reloptions(local_relopts *relopts, Datum options, bool validate)
elems[i].optname = opt->option->name;
elems[i].opttype = opt->option->type;
elems[i].offset = opt->offset;
- elems[i].isset_offset = 0; /* not supported for local relopts yet */
i++;
}
diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c
index 733ef40ae7..977babff54 100644
--- a/src/backend/commands/vacuum.c
+++ b/src/backend/commands/vacuum.c
@@ -2221,9 +2221,9 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams params,
{
StdRdOptions *opts = (StdRdOptions *) rel->rd_options;
- if (opts && opts->vacuum_truncate_set)
+ if (opts && opts->vacuum_truncate != TERNARY_UNSET)
{
- if (opts->vacuum_truncate)
+ if (opts->vacuum_truncate == TERNARY_TRUE)
params.truncate = VACOPTVALUE_ENABLED;
else
params.truncate = VACOPTVALUE_DISABLED;
diff --git a/src/include/access/reloptions.h b/src/include/access/reloptions.h
index a604a4702c..a436697658 100644
--- a/src/include/access/reloptions.h
+++ b/src/include/access/reloptions.h
@@ -29,6 +29,7 @@
typedef enum relopt_type
{
RELOPT_TYPE_BOOL,
+ RELOPT_TYPE_TERNARY, /* on, off, unset */
RELOPT_TYPE_INT,
RELOPT_TYPE_REAL,
RELOPT_TYPE_ENUM,
@@ -80,6 +81,7 @@ typedef struct relopt_value
union
{
bool bool_val;
+ ternary ternary_val;
int int_val;
double real_val;
int enum_val;
@@ -94,6 +96,12 @@ typedef struct relopt_bool
bool default_val;
} relopt_bool;
+typedef struct relopt_rernary
+{
+ relopt_gen gen;
+ int default_val;
+} relopt_ternary;
+
typedef struct relopt_int
{
relopt_gen gen;
@@ -152,19 +160,6 @@ typedef struct
const char *optname; /* option's name */
relopt_type opttype; /* option's datatype */
int offset; /* offset of field in result struct */
-
- /*
- * isset_offset is an optional offset of a field in the result struct that
- * stores whether the option is explicitly set for the relation or if it
- * just picked up the default value. In most cases, this can be
- * accomplished by giving the reloption a special out-of-range default
- * value (e.g., some integer reloptions use -2), but this isn't always
- * possible. For example, a Boolean reloption cannot be given an
- * out-of-range default, so we need another way to discover the source of
- * its value. This offset is only used if given a value greater than
- * zero.
- */
- int isset_offset;
} relopt_parse_elt;
/* Local reloption definition */
@@ -195,6 +190,8 @@ typedef struct local_relopts
extern relopt_kind add_reloption_kind(void);
extern void add_bool_reloption(bits32 kinds, const char *name, const char *desc,
bool default_val, LOCKMODE lockmode);
+extern void add_ternary_reloption(bits32 kinds, const char *name,
+ const char *desc, int default_val, LOCKMODE lockmode);
extern void add_int_reloption(bits32 kinds, const char *name, const char *desc,
int default_val, int min_val, int max_val,
LOCKMODE lockmode);
@@ -214,6 +211,9 @@ extern void register_reloptions_validator(local_relopts *relopts,
extern void add_local_bool_reloption(local_relopts *relopts, const char *name,
const char *desc, bool default_val,
int offset);
+extern void add_local_ternary_reloption(local_relopts *relopts,
+ const char *name, const char *desc,
+ ternary default_val, int offset);
extern void add_local_int_reloption(local_relopts *relopts, const char *name,
const char *desc, int default_val,
int min_val, int max_val, int offset);
diff --git a/src/include/c.h b/src/include/c.h
index 39022f8a9d..9c59ccccf8 100644
--- a/src/include/c.h
+++ b/src/include/c.h
@@ -488,6 +488,22 @@ typedef void (*pg_funcptr_t) (void);
#include <stdbool.h>
+/*
+ * ternary
+ * Boolean value with an extrea "unset" option
+ *
+ * Ternary data type is used in relation options that can be "true", "false" or
+ * "unset". Since relation options are used deep inside the PostgreSQL code,
+ * this type is declared globally.
+*/
+
+typedef enum ternary
+{
+ TERNARY_FALSE = 0,
+ TERNARY_TRUE = 1,
+ TERNARY_UNSET = -1
+} ternary;
+
/* ----------------------------------------------------------------
* Section 3: standard system types
diff --git a/src/include/utils/rel.h b/src/include/utils/rel.h
index b552359915..7346d618f9 100644
--- a/src/include/utils/rel.h
+++ b/src/include/utils/rel.h
@@ -346,8 +346,7 @@ typedef struct StdRdOptions
bool user_catalog_table; /* use as an additional catalog relation */
int parallel_workers; /* max number of parallel workers */
StdRdOptIndexCleanup vacuum_index_cleanup; /* controls index vacuuming */
- bool vacuum_truncate; /* enables vacuum to truncate a relation */
- bool vacuum_truncate_set; /* whether vacuum_truncate is set */
+ ternary vacuum_truncate; /* enables vacuum to truncate a relation */
/*
* Fraction of pages in a relation that vacuum can eagerly scan and fail
--
2.39.2
--nextPart5129932.5fSG56mABF
Content-Disposition: attachment;
filename="v2-0003-Add-alias-to-be-used-as-unset-state.patch"
Content-Transfer-Encoding: 7Bit
Content-Type: text/x-patch; charset="unicode-2-0-utf-8";
name="v2-0003-Add-alias-to-be-used-as-unset-state.patch"
^ permalink raw reply [nested|flat] 868+ messages in thread
* [PATCH v2a 2/4] Introduce ternary reloptions
@ 2025-09-04 14:41 Nikolay Shaplov <[email protected]>
0 siblings, 0 replies; 868+ messages in thread
From: Nikolay Shaplov @ 2025-09-04 14:41 UTC (permalink / raw)
Introduce ternary reloption as a replacement for current `vacuum_truncate`
implementation. Remove `vacuum_truncate_set` additional flag and using
`TERNARY_UNSET` value instead.
---
src/backend/access/common/reloptions.c | 129 ++++++++++++++++++++-----
src/backend/commands/vacuum.c | 4 +-
src/include/access/reloptions.h | 26 ++---
src/include/c.h | 16 +++
src/include/utils/rel.h | 3 +-
5 files changed, 135 insertions(+), 43 deletions(-)
diff --git a/src/backend/access/common/reloptions.c b/src/backend/access/common/reloptions.c
index 0af3fea68f..f543f11001 100644
--- a/src/backend/access/common/reloptions.c
+++ b/src/backend/access/common/reloptions.c
@@ -40,9 +40,9 @@
*
* To add an option:
*
- * (i) decide on a type (bool, integer, real, enum, string), name, default
- * value, upper and lower bounds (if applicable); for strings, consider a
- * validation routine.
+ * (i) decide on a type (bool, ternary, integer, real, enum, string), name,
+ * default value, upper and lower bounds (if applicable); for strings,
+ * consider a validation routine.
* (ii) add a record below (or use add_<type>_reloption).
* (iii) add it to the appropriate options struct (perhaps StdRdOptions)
* (iv) add it to the appropriate handling routine (perhaps
@@ -147,15 +147,6 @@ static relopt_bool boolRelOpts[] =
},
false
},
- {
- {
- "vacuum_truncate",
- "Enables vacuum to truncate empty pages at the end of this table",
- RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
- ShareUpdateExclusiveLock
- },
- true
- },
{
{
"deduplicate_items",
@@ -170,6 +161,21 @@ static relopt_bool boolRelOpts[] =
{{NULL}}
};
+static relopt_ternary ternaryRelOpts[] =
+{
+ {
+ {
+ "vacuum_truncate",
+ "Enables vacuum to truncate empty pages at the end of this table",
+ RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
+ ShareUpdateExclusiveLock
+ },
+ TERNARY_UNSET
+ },
+ /* list terminator */
+ {{NULL}}
+};
+
static relopt_int intRelOpts[] =
{
{
@@ -600,6 +606,13 @@ initialize_reloptions(void)
boolRelOpts[i].gen.lockmode));
j++;
}
+ for (i = 0; ternaryRelOpts[i].gen.name; i++)
+ {
+ Assert(DoLockModesConflict(ternaryRelOpts[i].gen.lockmode,
+ ternaryRelOpts[i].gen.lockmode));
+ j++;
+ }
+
for (i = 0; intRelOpts[i].gen.name; i++)
{
Assert(DoLockModesConflict(intRelOpts[i].gen.lockmode,
@@ -640,6 +653,14 @@ initialize_reloptions(void)
j++;
}
+ for (i = 0; ternaryRelOpts[i].gen.name; i++)
+ {
+ relOpts[j] = &ternaryRelOpts[i].gen;
+ relOpts[j]->type = RELOPT_TYPE_TERNARY;
+ relOpts[j]->namelen = strlen(relOpts[j]->name);
+ j++;
+ }
+
for (i = 0; intRelOpts[i].gen.name; i++)
{
relOpts[j] = &intRelOpts[i].gen;
@@ -800,6 +821,9 @@ allocate_reloption(bits32 kinds, int type, const char *name, const char *desc,
case RELOPT_TYPE_BOOL:
size = sizeof(relopt_bool);
break;
+ case RELOPT_TYPE_TERNARY:
+ size = sizeof(relopt_ternary);
+ break;
case RELOPT_TYPE_INT:
size = sizeof(relopt_int);
break;
@@ -883,6 +907,54 @@ add_local_bool_reloption(local_relopts *relopts, const char *name,
add_local_reloption(relopts, (relopt_gen *) newoption, offset);
}
+/*
+ * init_ternary_reloption
+ * Allocate and initialize a new ternary reloption
+ */
+static relopt_ternary *
+init_ternary_reloption(bits32 kinds, const char *name, const char *desc,
+ ternary default_val, LOCKMODE lockmode)
+{
+ relopt_ternary *newoption;
+
+ newoption = (relopt_ternary *) allocate_reloption(kinds,
+ RELOPT_TYPE_TERNARY, name, desc, lockmode);
+ newoption->default_val = default_val;
+
+ return newoption;
+}
+
+/*
+ * add_ternary_reloption
+ * Add a new ternary reloption
+ */
+void
+add_ternary_reloption(bits32 kinds, const char *name, const char *desc,
+ ternary default_val, LOCKMODE lockmode)
+{
+ relopt_ternary *newoption = init_ternary_reloption(kinds, name, desc,
+ default_val, lockmode);
+
+ add_reloption((relopt_gen *) newoption);
+}
+
+/*
+ * add_local_ternary_reloption
+ * Add a new ternary local reloption
+ *
+ * 'offset' is offset of ternary-typed field.
+ */
+void
+add_local_ternary_reloption(local_relopts *relopts, const char *name,
+ const char *desc, ternary default_val,
+ int offset)
+{
+ relopt_ternary *newoption = init_ternary_reloption(RELOPT_KIND_LOCAL,
+ name, desc,
+ default_val, 0);
+
+ add_local_reloption(relopts, (relopt_gen *) newoption, offset);
+}
/*
* init_real_reloption
@@ -1617,6 +1689,18 @@ parse_one_reloption(relopt_value *option, char *text_str, int text_len,
option->gen->name, value)));
}
break;
+ case RELOPT_TYPE_TERNARY:
+ {
+ bool b;
+ parsed = parse_bool(value, &b);
+ option->values.ternary_val = b ? TERNARY_TRUE : TERNARY_FALSE;
+ if (validate && !parsed)
+ ereport(ERROR,
+ (errcode(ERRCODE_INVALID_PARAMETER_VALUE),
+ errmsg("invalid value for ternary option \"%s\": %s",
+ option->gen->name, value)));
+ }
+ break;
case RELOPT_TYPE_INT:
{
relopt_int *optint = (relopt_int *) option->gen;
@@ -1780,17 +1864,6 @@ fillRelOptions(void *rdopts, Size basesize,
char *itempos = ((char *) rdopts) + elems[j].offset;
char *string_val;
- /*
- * If isset_offset is provided, store whether the reloption is
- * set there.
- */
- if (elems[j].isset_offset > 0)
- {
- char *setpos = ((char *) rdopts) + elems[j].isset_offset;
-
- *(bool *) setpos = options[i].isset;
- }
-
switch (options[i].gen->type)
{
case RELOPT_TYPE_BOOL:
@@ -1798,6 +1871,11 @@ fillRelOptions(void *rdopts, Size basesize,
options[i].values.bool_val :
((relopt_bool *) options[i].gen)->default_val;
break;
+ case RELOPT_TYPE_TERNARY:
+ *(ternary *) itempos = options[i].isset ?
+ options[i].values.ternary_val :
+ ((relopt_ternary *) options[i].gen)->default_val;
+ break;
case RELOPT_TYPE_INT:
*(int *) itempos = options[i].isset ?
options[i].values.int_val :
@@ -1912,8 +1990,8 @@ default_reloptions(Datum reloptions, bool validate, relopt_kind kind)
offsetof(StdRdOptions, parallel_workers)},
{"vacuum_index_cleanup", RELOPT_TYPE_ENUM,
offsetof(StdRdOptions, vacuum_index_cleanup)},
- {"vacuum_truncate", RELOPT_TYPE_BOOL,
- offsetof(StdRdOptions, vacuum_truncate), offsetof(StdRdOptions, vacuum_truncate_set)},
+ {"vacuum_truncate", RELOPT_TYPE_TERNARY,
+ offsetof(StdRdOptions, vacuum_truncate)},
{"vacuum_max_eager_freeze_failure_rate", RELOPT_TYPE_REAL,
offsetof(StdRdOptions, vacuum_max_eager_freeze_failure_rate)}
};
@@ -1993,7 +2071,6 @@ build_local_reloptions(local_relopts *relopts, Datum options, bool validate)
elems[i].optname = opt->option->name;
elems[i].opttype = opt->option->type;
elems[i].offset = opt->offset;
- elems[i].isset_offset = 0; /* not supported for local relopts yet */
i++;
}
diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c
index 733ef40ae7..977babff54 100644
--- a/src/backend/commands/vacuum.c
+++ b/src/backend/commands/vacuum.c
@@ -2221,9 +2221,9 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams params,
{
StdRdOptions *opts = (StdRdOptions *) rel->rd_options;
- if (opts && opts->vacuum_truncate_set)
+ if (opts && opts->vacuum_truncate != TERNARY_UNSET)
{
- if (opts->vacuum_truncate)
+ if (opts->vacuum_truncate == TERNARY_TRUE)
params.truncate = VACOPTVALUE_ENABLED;
else
params.truncate = VACOPTVALUE_DISABLED;
diff --git a/src/include/access/reloptions.h b/src/include/access/reloptions.h
index a604a4702c..a436697658 100644
--- a/src/include/access/reloptions.h
+++ b/src/include/access/reloptions.h
@@ -29,6 +29,7 @@
typedef enum relopt_type
{
RELOPT_TYPE_BOOL,
+ RELOPT_TYPE_TERNARY, /* on, off, unset */
RELOPT_TYPE_INT,
RELOPT_TYPE_REAL,
RELOPT_TYPE_ENUM,
@@ -80,6 +81,7 @@ typedef struct relopt_value
union
{
bool bool_val;
+ ternary ternary_val;
int int_val;
double real_val;
int enum_val;
@@ -94,6 +96,12 @@ typedef struct relopt_bool
bool default_val;
} relopt_bool;
+typedef struct relopt_rernary
+{
+ relopt_gen gen;
+ int default_val;
+} relopt_ternary;
+
typedef struct relopt_int
{
relopt_gen gen;
@@ -152,19 +160,6 @@ typedef struct
const char *optname; /* option's name */
relopt_type opttype; /* option's datatype */
int offset; /* offset of field in result struct */
-
- /*
- * isset_offset is an optional offset of a field in the result struct that
- * stores whether the option is explicitly set for the relation or if it
- * just picked up the default value. In most cases, this can be
- * accomplished by giving the reloption a special out-of-range default
- * value (e.g., some integer reloptions use -2), but this isn't always
- * possible. For example, a Boolean reloption cannot be given an
- * out-of-range default, so we need another way to discover the source of
- * its value. This offset is only used if given a value greater than
- * zero.
- */
- int isset_offset;
} relopt_parse_elt;
/* Local reloption definition */
@@ -195,6 +190,8 @@ typedef struct local_relopts
extern relopt_kind add_reloption_kind(void);
extern void add_bool_reloption(bits32 kinds, const char *name, const char *desc,
bool default_val, LOCKMODE lockmode);
+extern void add_ternary_reloption(bits32 kinds, const char *name,
+ const char *desc, int default_val, LOCKMODE lockmode);
extern void add_int_reloption(bits32 kinds, const char *name, const char *desc,
int default_val, int min_val, int max_val,
LOCKMODE lockmode);
@@ -214,6 +211,9 @@ extern void register_reloptions_validator(local_relopts *relopts,
extern void add_local_bool_reloption(local_relopts *relopts, const char *name,
const char *desc, bool default_val,
int offset);
+extern void add_local_ternary_reloption(local_relopts *relopts,
+ const char *name, const char *desc,
+ ternary default_val, int offset);
extern void add_local_int_reloption(local_relopts *relopts, const char *name,
const char *desc, int default_val,
int min_val, int max_val, int offset);
diff --git a/src/include/c.h b/src/include/c.h
index 39022f8a9d..9c59ccccf8 100644
--- a/src/include/c.h
+++ b/src/include/c.h
@@ -488,6 +488,22 @@ typedef void (*pg_funcptr_t) (void);
#include <stdbool.h>
+/*
+ * ternary
+ * Boolean value with an extrea "unset" option
+ *
+ * Ternary data type is used in relation options that can be "true", "false" or
+ * "unset". Since relation options are used deep inside the PostgreSQL code,
+ * this type is declared globally.
+*/
+
+typedef enum ternary
+{
+ TERNARY_FALSE = 0,
+ TERNARY_TRUE = 1,
+ TERNARY_UNSET = -1
+} ternary;
+
/* ----------------------------------------------------------------
* Section 3: standard system types
diff --git a/src/include/utils/rel.h b/src/include/utils/rel.h
index b552359915..7346d618f9 100644
--- a/src/include/utils/rel.h
+++ b/src/include/utils/rel.h
@@ -346,8 +346,7 @@ typedef struct StdRdOptions
bool user_catalog_table; /* use as an additional catalog relation */
int parallel_workers; /* max number of parallel workers */
StdRdOptIndexCleanup vacuum_index_cleanup; /* controls index vacuuming */
- bool vacuum_truncate; /* enables vacuum to truncate a relation */
- bool vacuum_truncate_set; /* whether vacuum_truncate is set */
+ ternary vacuum_truncate; /* enables vacuum to truncate a relation */
/*
* Fraction of pages in a relation that vacuum can eagerly scan and fail
--
2.39.2
--nextPart4155600.3ZeAukHxDK
Content-Disposition: attachment;
filename="v2a-0003-Add-alias-to-be-used-as-unset-state.patch"
Content-Transfer-Encoding: 7Bit
Content-Type: text/x-patch; charset="unicode-2-0-utf-8";
name="v2a-0003-Add-alias-to-be-used-as-unset-state.patch"
^ permalink raw reply [nested|flat] 868+ messages in thread
* [PATCH v2 2/4] Introduce ternary reloptions
@ 2025-09-04 14:41 Nikolay Shaplov <[email protected]>
0 siblings, 0 replies; 868+ messages in thread
From: Nikolay Shaplov @ 2025-09-04 14:41 UTC (permalink / raw)
Introduce ternary reloption as a replacement for current `vacuum_truncate`
implementation. Remove `vacuum_truncate_set` additional flag and using
`TERNARY_UNSET` value instead.
---
src/backend/access/common/reloptions.c | 129 ++++++++++++++++++++-----
src/backend/commands/vacuum.c | 4 +-
src/include/access/reloptions.h | 26 ++---
src/include/c.h | 16 +++
src/include/utils/rel.h | 3 +-
5 files changed, 135 insertions(+), 43 deletions(-)
diff --git a/src/backend/access/common/reloptions.c b/src/backend/access/common/reloptions.c
index 0af3fea68f..f543f11001 100644
--- a/src/backend/access/common/reloptions.c
+++ b/src/backend/access/common/reloptions.c
@@ -40,9 +40,9 @@
*
* To add an option:
*
- * (i) decide on a type (bool, integer, real, enum, string), name, default
- * value, upper and lower bounds (if applicable); for strings, consider a
- * validation routine.
+ * (i) decide on a type (bool, ternary, integer, real, enum, string), name,
+ * default value, upper and lower bounds (if applicable); for strings,
+ * consider a validation routine.
* (ii) add a record below (or use add_<type>_reloption).
* (iii) add it to the appropriate options struct (perhaps StdRdOptions)
* (iv) add it to the appropriate handling routine (perhaps
@@ -147,15 +147,6 @@ static relopt_bool boolRelOpts[] =
},
false
},
- {
- {
- "vacuum_truncate",
- "Enables vacuum to truncate empty pages at the end of this table",
- RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
- ShareUpdateExclusiveLock
- },
- true
- },
{
{
"deduplicate_items",
@@ -170,6 +161,21 @@ static relopt_bool boolRelOpts[] =
{{NULL}}
};
+static relopt_ternary ternaryRelOpts[] =
+{
+ {
+ {
+ "vacuum_truncate",
+ "Enables vacuum to truncate empty pages at the end of this table",
+ RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
+ ShareUpdateExclusiveLock
+ },
+ TERNARY_UNSET
+ },
+ /* list terminator */
+ {{NULL}}
+};
+
static relopt_int intRelOpts[] =
{
{
@@ -600,6 +606,13 @@ initialize_reloptions(void)
boolRelOpts[i].gen.lockmode));
j++;
}
+ for (i = 0; ternaryRelOpts[i].gen.name; i++)
+ {
+ Assert(DoLockModesConflict(ternaryRelOpts[i].gen.lockmode,
+ ternaryRelOpts[i].gen.lockmode));
+ j++;
+ }
+
for (i = 0; intRelOpts[i].gen.name; i++)
{
Assert(DoLockModesConflict(intRelOpts[i].gen.lockmode,
@@ -640,6 +653,14 @@ initialize_reloptions(void)
j++;
}
+ for (i = 0; ternaryRelOpts[i].gen.name; i++)
+ {
+ relOpts[j] = &ternaryRelOpts[i].gen;
+ relOpts[j]->type = RELOPT_TYPE_TERNARY;
+ relOpts[j]->namelen = strlen(relOpts[j]->name);
+ j++;
+ }
+
for (i = 0; intRelOpts[i].gen.name; i++)
{
relOpts[j] = &intRelOpts[i].gen;
@@ -800,6 +821,9 @@ allocate_reloption(bits32 kinds, int type, const char *name, const char *desc,
case RELOPT_TYPE_BOOL:
size = sizeof(relopt_bool);
break;
+ case RELOPT_TYPE_TERNARY:
+ size = sizeof(relopt_ternary);
+ break;
case RELOPT_TYPE_INT:
size = sizeof(relopt_int);
break;
@@ -883,6 +907,54 @@ add_local_bool_reloption(local_relopts *relopts, const char *name,
add_local_reloption(relopts, (relopt_gen *) newoption, offset);
}
+/*
+ * init_ternary_reloption
+ * Allocate and initialize a new ternary reloption
+ */
+static relopt_ternary *
+init_ternary_reloption(bits32 kinds, const char *name, const char *desc,
+ ternary default_val, LOCKMODE lockmode)
+{
+ relopt_ternary *newoption;
+
+ newoption = (relopt_ternary *) allocate_reloption(kinds,
+ RELOPT_TYPE_TERNARY, name, desc, lockmode);
+ newoption->default_val = default_val;
+
+ return newoption;
+}
+
+/*
+ * add_ternary_reloption
+ * Add a new ternary reloption
+ */
+void
+add_ternary_reloption(bits32 kinds, const char *name, const char *desc,
+ ternary default_val, LOCKMODE lockmode)
+{
+ relopt_ternary *newoption = init_ternary_reloption(kinds, name, desc,
+ default_val, lockmode);
+
+ add_reloption((relopt_gen *) newoption);
+}
+
+/*
+ * add_local_ternary_reloption
+ * Add a new ternary local reloption
+ *
+ * 'offset' is offset of ternary-typed field.
+ */
+void
+add_local_ternary_reloption(local_relopts *relopts, const char *name,
+ const char *desc, ternary default_val,
+ int offset)
+{
+ relopt_ternary *newoption = init_ternary_reloption(RELOPT_KIND_LOCAL,
+ name, desc,
+ default_val, 0);
+
+ add_local_reloption(relopts, (relopt_gen *) newoption, offset);
+}
/*
* init_real_reloption
@@ -1617,6 +1689,18 @@ parse_one_reloption(relopt_value *option, char *text_str, int text_len,
option->gen->name, value)));
}
break;
+ case RELOPT_TYPE_TERNARY:
+ {
+ bool b;
+ parsed = parse_bool(value, &b);
+ option->values.ternary_val = b ? TERNARY_TRUE : TERNARY_FALSE;
+ if (validate && !parsed)
+ ereport(ERROR,
+ (errcode(ERRCODE_INVALID_PARAMETER_VALUE),
+ errmsg("invalid value for ternary option \"%s\": %s",
+ option->gen->name, value)));
+ }
+ break;
case RELOPT_TYPE_INT:
{
relopt_int *optint = (relopt_int *) option->gen;
@@ -1780,17 +1864,6 @@ fillRelOptions(void *rdopts, Size basesize,
char *itempos = ((char *) rdopts) + elems[j].offset;
char *string_val;
- /*
- * If isset_offset is provided, store whether the reloption is
- * set there.
- */
- if (elems[j].isset_offset > 0)
- {
- char *setpos = ((char *) rdopts) + elems[j].isset_offset;
-
- *(bool *) setpos = options[i].isset;
- }
-
switch (options[i].gen->type)
{
case RELOPT_TYPE_BOOL:
@@ -1798,6 +1871,11 @@ fillRelOptions(void *rdopts, Size basesize,
options[i].values.bool_val :
((relopt_bool *) options[i].gen)->default_val;
break;
+ case RELOPT_TYPE_TERNARY:
+ *(ternary *) itempos = options[i].isset ?
+ options[i].values.ternary_val :
+ ((relopt_ternary *) options[i].gen)->default_val;
+ break;
case RELOPT_TYPE_INT:
*(int *) itempos = options[i].isset ?
options[i].values.int_val :
@@ -1912,8 +1990,8 @@ default_reloptions(Datum reloptions, bool validate, relopt_kind kind)
offsetof(StdRdOptions, parallel_workers)},
{"vacuum_index_cleanup", RELOPT_TYPE_ENUM,
offsetof(StdRdOptions, vacuum_index_cleanup)},
- {"vacuum_truncate", RELOPT_TYPE_BOOL,
- offsetof(StdRdOptions, vacuum_truncate), offsetof(StdRdOptions, vacuum_truncate_set)},
+ {"vacuum_truncate", RELOPT_TYPE_TERNARY,
+ offsetof(StdRdOptions, vacuum_truncate)},
{"vacuum_max_eager_freeze_failure_rate", RELOPT_TYPE_REAL,
offsetof(StdRdOptions, vacuum_max_eager_freeze_failure_rate)}
};
@@ -1993,7 +2071,6 @@ build_local_reloptions(local_relopts *relopts, Datum options, bool validate)
elems[i].optname = opt->option->name;
elems[i].opttype = opt->option->type;
elems[i].offset = opt->offset;
- elems[i].isset_offset = 0; /* not supported for local relopts yet */
i++;
}
diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c
index 733ef40ae7..977babff54 100644
--- a/src/backend/commands/vacuum.c
+++ b/src/backend/commands/vacuum.c
@@ -2221,9 +2221,9 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams params,
{
StdRdOptions *opts = (StdRdOptions *) rel->rd_options;
- if (opts && opts->vacuum_truncate_set)
+ if (opts && opts->vacuum_truncate != TERNARY_UNSET)
{
- if (opts->vacuum_truncate)
+ if (opts->vacuum_truncate == TERNARY_TRUE)
params.truncate = VACOPTVALUE_ENABLED;
else
params.truncate = VACOPTVALUE_DISABLED;
diff --git a/src/include/access/reloptions.h b/src/include/access/reloptions.h
index a604a4702c..a436697658 100644
--- a/src/include/access/reloptions.h
+++ b/src/include/access/reloptions.h
@@ -29,6 +29,7 @@
typedef enum relopt_type
{
RELOPT_TYPE_BOOL,
+ RELOPT_TYPE_TERNARY, /* on, off, unset */
RELOPT_TYPE_INT,
RELOPT_TYPE_REAL,
RELOPT_TYPE_ENUM,
@@ -80,6 +81,7 @@ typedef struct relopt_value
union
{
bool bool_val;
+ ternary ternary_val;
int int_val;
double real_val;
int enum_val;
@@ -94,6 +96,12 @@ typedef struct relopt_bool
bool default_val;
} relopt_bool;
+typedef struct relopt_rernary
+{
+ relopt_gen gen;
+ int default_val;
+} relopt_ternary;
+
typedef struct relopt_int
{
relopt_gen gen;
@@ -152,19 +160,6 @@ typedef struct
const char *optname; /* option's name */
relopt_type opttype; /* option's datatype */
int offset; /* offset of field in result struct */
-
- /*
- * isset_offset is an optional offset of a field in the result struct that
- * stores whether the option is explicitly set for the relation or if it
- * just picked up the default value. In most cases, this can be
- * accomplished by giving the reloption a special out-of-range default
- * value (e.g., some integer reloptions use -2), but this isn't always
- * possible. For example, a Boolean reloption cannot be given an
- * out-of-range default, so we need another way to discover the source of
- * its value. This offset is only used if given a value greater than
- * zero.
- */
- int isset_offset;
} relopt_parse_elt;
/* Local reloption definition */
@@ -195,6 +190,8 @@ typedef struct local_relopts
extern relopt_kind add_reloption_kind(void);
extern void add_bool_reloption(bits32 kinds, const char *name, const char *desc,
bool default_val, LOCKMODE lockmode);
+extern void add_ternary_reloption(bits32 kinds, const char *name,
+ const char *desc, int default_val, LOCKMODE lockmode);
extern void add_int_reloption(bits32 kinds, const char *name, const char *desc,
int default_val, int min_val, int max_val,
LOCKMODE lockmode);
@@ -214,6 +211,9 @@ extern void register_reloptions_validator(local_relopts *relopts,
extern void add_local_bool_reloption(local_relopts *relopts, const char *name,
const char *desc, bool default_val,
int offset);
+extern void add_local_ternary_reloption(local_relopts *relopts,
+ const char *name, const char *desc,
+ ternary default_val, int offset);
extern void add_local_int_reloption(local_relopts *relopts, const char *name,
const char *desc, int default_val,
int min_val, int max_val, int offset);
diff --git a/src/include/c.h b/src/include/c.h
index 39022f8a9d..9c59ccccf8 100644
--- a/src/include/c.h
+++ b/src/include/c.h
@@ -488,6 +488,22 @@ typedef void (*pg_funcptr_t) (void);
#include <stdbool.h>
+/*
+ * ternary
+ * Boolean value with an extrea "unset" option
+ *
+ * Ternary data type is used in relation options that can be "true", "false" or
+ * "unset". Since relation options are used deep inside the PostgreSQL code,
+ * this type is declared globally.
+*/
+
+typedef enum ternary
+{
+ TERNARY_FALSE = 0,
+ TERNARY_TRUE = 1,
+ TERNARY_UNSET = -1
+} ternary;
+
/* ----------------------------------------------------------------
* Section 3: standard system types
diff --git a/src/include/utils/rel.h b/src/include/utils/rel.h
index b552359915..7346d618f9 100644
--- a/src/include/utils/rel.h
+++ b/src/include/utils/rel.h
@@ -346,8 +346,7 @@ typedef struct StdRdOptions
bool user_catalog_table; /* use as an additional catalog relation */
int parallel_workers; /* max number of parallel workers */
StdRdOptIndexCleanup vacuum_index_cleanup; /* controls index vacuuming */
- bool vacuum_truncate; /* enables vacuum to truncate a relation */
- bool vacuum_truncate_set; /* whether vacuum_truncate is set */
+ ternary vacuum_truncate; /* enables vacuum to truncate a relation */
/*
* Fraction of pages in a relation that vacuum can eagerly scan and fail
--
2.39.2
--nextPart5129932.5fSG56mABF
Content-Disposition: attachment;
filename="v2-0003-Add-alias-to-be-used-as-unset-state.patch"
Content-Transfer-Encoding: 7Bit
Content-Type: text/x-patch; charset="unicode-2-0-utf-8";
name="v2-0003-Add-alias-to-be-used-as-unset-state.patch"
^ permalink raw reply [nested|flat] 868+ messages in thread
* [PATCH v2a 2/4] Introduce ternary reloptions
@ 2025-09-04 14:41 Nikolay Shaplov <[email protected]>
0 siblings, 0 replies; 868+ messages in thread
From: Nikolay Shaplov @ 2025-09-04 14:41 UTC (permalink / raw)
Introduce ternary reloption as a replacement for current `vacuum_truncate`
implementation. Remove `vacuum_truncate_set` additional flag and using
`TERNARY_UNSET` value instead.
---
src/backend/access/common/reloptions.c | 129 ++++++++++++++++++++-----
src/backend/commands/vacuum.c | 4 +-
src/include/access/reloptions.h | 26 ++---
src/include/c.h | 16 +++
src/include/utils/rel.h | 3 +-
5 files changed, 135 insertions(+), 43 deletions(-)
diff --git a/src/backend/access/common/reloptions.c b/src/backend/access/common/reloptions.c
index 0af3fea68f..f543f11001 100644
--- a/src/backend/access/common/reloptions.c
+++ b/src/backend/access/common/reloptions.c
@@ -40,9 +40,9 @@
*
* To add an option:
*
- * (i) decide on a type (bool, integer, real, enum, string), name, default
- * value, upper and lower bounds (if applicable); for strings, consider a
- * validation routine.
+ * (i) decide on a type (bool, ternary, integer, real, enum, string), name,
+ * default value, upper and lower bounds (if applicable); for strings,
+ * consider a validation routine.
* (ii) add a record below (or use add_<type>_reloption).
* (iii) add it to the appropriate options struct (perhaps StdRdOptions)
* (iv) add it to the appropriate handling routine (perhaps
@@ -147,15 +147,6 @@ static relopt_bool boolRelOpts[] =
},
false
},
- {
- {
- "vacuum_truncate",
- "Enables vacuum to truncate empty pages at the end of this table",
- RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
- ShareUpdateExclusiveLock
- },
- true
- },
{
{
"deduplicate_items",
@@ -170,6 +161,21 @@ static relopt_bool boolRelOpts[] =
{{NULL}}
};
+static relopt_ternary ternaryRelOpts[] =
+{
+ {
+ {
+ "vacuum_truncate",
+ "Enables vacuum to truncate empty pages at the end of this table",
+ RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
+ ShareUpdateExclusiveLock
+ },
+ TERNARY_UNSET
+ },
+ /* list terminator */
+ {{NULL}}
+};
+
static relopt_int intRelOpts[] =
{
{
@@ -600,6 +606,13 @@ initialize_reloptions(void)
boolRelOpts[i].gen.lockmode));
j++;
}
+ for (i = 0; ternaryRelOpts[i].gen.name; i++)
+ {
+ Assert(DoLockModesConflict(ternaryRelOpts[i].gen.lockmode,
+ ternaryRelOpts[i].gen.lockmode));
+ j++;
+ }
+
for (i = 0; intRelOpts[i].gen.name; i++)
{
Assert(DoLockModesConflict(intRelOpts[i].gen.lockmode,
@@ -640,6 +653,14 @@ initialize_reloptions(void)
j++;
}
+ for (i = 0; ternaryRelOpts[i].gen.name; i++)
+ {
+ relOpts[j] = &ternaryRelOpts[i].gen;
+ relOpts[j]->type = RELOPT_TYPE_TERNARY;
+ relOpts[j]->namelen = strlen(relOpts[j]->name);
+ j++;
+ }
+
for (i = 0; intRelOpts[i].gen.name; i++)
{
relOpts[j] = &intRelOpts[i].gen;
@@ -800,6 +821,9 @@ allocate_reloption(bits32 kinds, int type, const char *name, const char *desc,
case RELOPT_TYPE_BOOL:
size = sizeof(relopt_bool);
break;
+ case RELOPT_TYPE_TERNARY:
+ size = sizeof(relopt_ternary);
+ break;
case RELOPT_TYPE_INT:
size = sizeof(relopt_int);
break;
@@ -883,6 +907,54 @@ add_local_bool_reloption(local_relopts *relopts, const char *name,
add_local_reloption(relopts, (relopt_gen *) newoption, offset);
}
+/*
+ * init_ternary_reloption
+ * Allocate and initialize a new ternary reloption
+ */
+static relopt_ternary *
+init_ternary_reloption(bits32 kinds, const char *name, const char *desc,
+ ternary default_val, LOCKMODE lockmode)
+{
+ relopt_ternary *newoption;
+
+ newoption = (relopt_ternary *) allocate_reloption(kinds,
+ RELOPT_TYPE_TERNARY, name, desc, lockmode);
+ newoption->default_val = default_val;
+
+ return newoption;
+}
+
+/*
+ * add_ternary_reloption
+ * Add a new ternary reloption
+ */
+void
+add_ternary_reloption(bits32 kinds, const char *name, const char *desc,
+ ternary default_val, LOCKMODE lockmode)
+{
+ relopt_ternary *newoption = init_ternary_reloption(kinds, name, desc,
+ default_val, lockmode);
+
+ add_reloption((relopt_gen *) newoption);
+}
+
+/*
+ * add_local_ternary_reloption
+ * Add a new ternary local reloption
+ *
+ * 'offset' is offset of ternary-typed field.
+ */
+void
+add_local_ternary_reloption(local_relopts *relopts, const char *name,
+ const char *desc, ternary default_val,
+ int offset)
+{
+ relopt_ternary *newoption = init_ternary_reloption(RELOPT_KIND_LOCAL,
+ name, desc,
+ default_val, 0);
+
+ add_local_reloption(relopts, (relopt_gen *) newoption, offset);
+}
/*
* init_real_reloption
@@ -1617,6 +1689,18 @@ parse_one_reloption(relopt_value *option, char *text_str, int text_len,
option->gen->name, value)));
}
break;
+ case RELOPT_TYPE_TERNARY:
+ {
+ bool b;
+ parsed = parse_bool(value, &b);
+ option->values.ternary_val = b ? TERNARY_TRUE : TERNARY_FALSE;
+ if (validate && !parsed)
+ ereport(ERROR,
+ (errcode(ERRCODE_INVALID_PARAMETER_VALUE),
+ errmsg("invalid value for ternary option \"%s\": %s",
+ option->gen->name, value)));
+ }
+ break;
case RELOPT_TYPE_INT:
{
relopt_int *optint = (relopt_int *) option->gen;
@@ -1780,17 +1864,6 @@ fillRelOptions(void *rdopts, Size basesize,
char *itempos = ((char *) rdopts) + elems[j].offset;
char *string_val;
- /*
- * If isset_offset is provided, store whether the reloption is
- * set there.
- */
- if (elems[j].isset_offset > 0)
- {
- char *setpos = ((char *) rdopts) + elems[j].isset_offset;
-
- *(bool *) setpos = options[i].isset;
- }
-
switch (options[i].gen->type)
{
case RELOPT_TYPE_BOOL:
@@ -1798,6 +1871,11 @@ fillRelOptions(void *rdopts, Size basesize,
options[i].values.bool_val :
((relopt_bool *) options[i].gen)->default_val;
break;
+ case RELOPT_TYPE_TERNARY:
+ *(ternary *) itempos = options[i].isset ?
+ options[i].values.ternary_val :
+ ((relopt_ternary *) options[i].gen)->default_val;
+ break;
case RELOPT_TYPE_INT:
*(int *) itempos = options[i].isset ?
options[i].values.int_val :
@@ -1912,8 +1990,8 @@ default_reloptions(Datum reloptions, bool validate, relopt_kind kind)
offsetof(StdRdOptions, parallel_workers)},
{"vacuum_index_cleanup", RELOPT_TYPE_ENUM,
offsetof(StdRdOptions, vacuum_index_cleanup)},
- {"vacuum_truncate", RELOPT_TYPE_BOOL,
- offsetof(StdRdOptions, vacuum_truncate), offsetof(StdRdOptions, vacuum_truncate_set)},
+ {"vacuum_truncate", RELOPT_TYPE_TERNARY,
+ offsetof(StdRdOptions, vacuum_truncate)},
{"vacuum_max_eager_freeze_failure_rate", RELOPT_TYPE_REAL,
offsetof(StdRdOptions, vacuum_max_eager_freeze_failure_rate)}
};
@@ -1993,7 +2071,6 @@ build_local_reloptions(local_relopts *relopts, Datum options, bool validate)
elems[i].optname = opt->option->name;
elems[i].opttype = opt->option->type;
elems[i].offset = opt->offset;
- elems[i].isset_offset = 0; /* not supported for local relopts yet */
i++;
}
diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c
index 733ef40ae7..977babff54 100644
--- a/src/backend/commands/vacuum.c
+++ b/src/backend/commands/vacuum.c
@@ -2221,9 +2221,9 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams params,
{
StdRdOptions *opts = (StdRdOptions *) rel->rd_options;
- if (opts && opts->vacuum_truncate_set)
+ if (opts && opts->vacuum_truncate != TERNARY_UNSET)
{
- if (opts->vacuum_truncate)
+ if (opts->vacuum_truncate == TERNARY_TRUE)
params.truncate = VACOPTVALUE_ENABLED;
else
params.truncate = VACOPTVALUE_DISABLED;
diff --git a/src/include/access/reloptions.h b/src/include/access/reloptions.h
index a604a4702c..a436697658 100644
--- a/src/include/access/reloptions.h
+++ b/src/include/access/reloptions.h
@@ -29,6 +29,7 @@
typedef enum relopt_type
{
RELOPT_TYPE_BOOL,
+ RELOPT_TYPE_TERNARY, /* on, off, unset */
RELOPT_TYPE_INT,
RELOPT_TYPE_REAL,
RELOPT_TYPE_ENUM,
@@ -80,6 +81,7 @@ typedef struct relopt_value
union
{
bool bool_val;
+ ternary ternary_val;
int int_val;
double real_val;
int enum_val;
@@ -94,6 +96,12 @@ typedef struct relopt_bool
bool default_val;
} relopt_bool;
+typedef struct relopt_rernary
+{
+ relopt_gen gen;
+ int default_val;
+} relopt_ternary;
+
typedef struct relopt_int
{
relopt_gen gen;
@@ -152,19 +160,6 @@ typedef struct
const char *optname; /* option's name */
relopt_type opttype; /* option's datatype */
int offset; /* offset of field in result struct */
-
- /*
- * isset_offset is an optional offset of a field in the result struct that
- * stores whether the option is explicitly set for the relation or if it
- * just picked up the default value. In most cases, this can be
- * accomplished by giving the reloption a special out-of-range default
- * value (e.g., some integer reloptions use -2), but this isn't always
- * possible. For example, a Boolean reloption cannot be given an
- * out-of-range default, so we need another way to discover the source of
- * its value. This offset is only used if given a value greater than
- * zero.
- */
- int isset_offset;
} relopt_parse_elt;
/* Local reloption definition */
@@ -195,6 +190,8 @@ typedef struct local_relopts
extern relopt_kind add_reloption_kind(void);
extern void add_bool_reloption(bits32 kinds, const char *name, const char *desc,
bool default_val, LOCKMODE lockmode);
+extern void add_ternary_reloption(bits32 kinds, const char *name,
+ const char *desc, int default_val, LOCKMODE lockmode);
extern void add_int_reloption(bits32 kinds, const char *name, const char *desc,
int default_val, int min_val, int max_val,
LOCKMODE lockmode);
@@ -214,6 +211,9 @@ extern void register_reloptions_validator(local_relopts *relopts,
extern void add_local_bool_reloption(local_relopts *relopts, const char *name,
const char *desc, bool default_val,
int offset);
+extern void add_local_ternary_reloption(local_relopts *relopts,
+ const char *name, const char *desc,
+ ternary default_val, int offset);
extern void add_local_int_reloption(local_relopts *relopts, const char *name,
const char *desc, int default_val,
int min_val, int max_val, int offset);
diff --git a/src/include/c.h b/src/include/c.h
index 39022f8a9d..9c59ccccf8 100644
--- a/src/include/c.h
+++ b/src/include/c.h
@@ -488,6 +488,22 @@ typedef void (*pg_funcptr_t) (void);
#include <stdbool.h>
+/*
+ * ternary
+ * Boolean value with an extrea "unset" option
+ *
+ * Ternary data type is used in relation options that can be "true", "false" or
+ * "unset". Since relation options are used deep inside the PostgreSQL code,
+ * this type is declared globally.
+*/
+
+typedef enum ternary
+{
+ TERNARY_FALSE = 0,
+ TERNARY_TRUE = 1,
+ TERNARY_UNSET = -1
+} ternary;
+
/* ----------------------------------------------------------------
* Section 3: standard system types
diff --git a/src/include/utils/rel.h b/src/include/utils/rel.h
index b552359915..7346d618f9 100644
--- a/src/include/utils/rel.h
+++ b/src/include/utils/rel.h
@@ -346,8 +346,7 @@ typedef struct StdRdOptions
bool user_catalog_table; /* use as an additional catalog relation */
int parallel_workers; /* max number of parallel workers */
StdRdOptIndexCleanup vacuum_index_cleanup; /* controls index vacuuming */
- bool vacuum_truncate; /* enables vacuum to truncate a relation */
- bool vacuum_truncate_set; /* whether vacuum_truncate is set */
+ ternary vacuum_truncate; /* enables vacuum to truncate a relation */
/*
* Fraction of pages in a relation that vacuum can eagerly scan and fail
--
2.39.2
--nextPart4155600.3ZeAukHxDK
Content-Disposition: attachment;
filename="v2a-0003-Add-alias-to-be-used-as-unset-state.patch"
Content-Transfer-Encoding: 7Bit
Content-Type: text/x-patch; charset="unicode-2-0-utf-8";
name="v2a-0003-Add-alias-to-be-used-as-unset-state.patch"
^ permalink raw reply [nested|flat] 868+ messages in thread
* [PATCH v2 2/4] Introduce ternary reloptions
@ 2025-09-04 14:41 Nikolay Shaplov <[email protected]>
0 siblings, 0 replies; 868+ messages in thread
From: Nikolay Shaplov @ 2025-09-04 14:41 UTC (permalink / raw)
Introduce ternary reloption as a replacement for current `vacuum_truncate`
implementation. Remove `vacuum_truncate_set` additional flag and using
`TERNARY_UNSET` value instead.
---
src/backend/access/common/reloptions.c | 129 ++++++++++++++++++++-----
src/backend/commands/vacuum.c | 4 +-
src/include/access/reloptions.h | 26 ++---
src/include/c.h | 16 +++
src/include/utils/rel.h | 3 +-
5 files changed, 135 insertions(+), 43 deletions(-)
diff --git a/src/backend/access/common/reloptions.c b/src/backend/access/common/reloptions.c
index 0af3fea68f..f543f11001 100644
--- a/src/backend/access/common/reloptions.c
+++ b/src/backend/access/common/reloptions.c
@@ -40,9 +40,9 @@
*
* To add an option:
*
- * (i) decide on a type (bool, integer, real, enum, string), name, default
- * value, upper and lower bounds (if applicable); for strings, consider a
- * validation routine.
+ * (i) decide on a type (bool, ternary, integer, real, enum, string), name,
+ * default value, upper and lower bounds (if applicable); for strings,
+ * consider a validation routine.
* (ii) add a record below (or use add_<type>_reloption).
* (iii) add it to the appropriate options struct (perhaps StdRdOptions)
* (iv) add it to the appropriate handling routine (perhaps
@@ -147,15 +147,6 @@ static relopt_bool boolRelOpts[] =
},
false
},
- {
- {
- "vacuum_truncate",
- "Enables vacuum to truncate empty pages at the end of this table",
- RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
- ShareUpdateExclusiveLock
- },
- true
- },
{
{
"deduplicate_items",
@@ -170,6 +161,21 @@ static relopt_bool boolRelOpts[] =
{{NULL}}
};
+static relopt_ternary ternaryRelOpts[] =
+{
+ {
+ {
+ "vacuum_truncate",
+ "Enables vacuum to truncate empty pages at the end of this table",
+ RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
+ ShareUpdateExclusiveLock
+ },
+ TERNARY_UNSET
+ },
+ /* list terminator */
+ {{NULL}}
+};
+
static relopt_int intRelOpts[] =
{
{
@@ -600,6 +606,13 @@ initialize_reloptions(void)
boolRelOpts[i].gen.lockmode));
j++;
}
+ for (i = 0; ternaryRelOpts[i].gen.name; i++)
+ {
+ Assert(DoLockModesConflict(ternaryRelOpts[i].gen.lockmode,
+ ternaryRelOpts[i].gen.lockmode));
+ j++;
+ }
+
for (i = 0; intRelOpts[i].gen.name; i++)
{
Assert(DoLockModesConflict(intRelOpts[i].gen.lockmode,
@@ -640,6 +653,14 @@ initialize_reloptions(void)
j++;
}
+ for (i = 0; ternaryRelOpts[i].gen.name; i++)
+ {
+ relOpts[j] = &ternaryRelOpts[i].gen;
+ relOpts[j]->type = RELOPT_TYPE_TERNARY;
+ relOpts[j]->namelen = strlen(relOpts[j]->name);
+ j++;
+ }
+
for (i = 0; intRelOpts[i].gen.name; i++)
{
relOpts[j] = &intRelOpts[i].gen;
@@ -800,6 +821,9 @@ allocate_reloption(bits32 kinds, int type, const char *name, const char *desc,
case RELOPT_TYPE_BOOL:
size = sizeof(relopt_bool);
break;
+ case RELOPT_TYPE_TERNARY:
+ size = sizeof(relopt_ternary);
+ break;
case RELOPT_TYPE_INT:
size = sizeof(relopt_int);
break;
@@ -883,6 +907,54 @@ add_local_bool_reloption(local_relopts *relopts, const char *name,
add_local_reloption(relopts, (relopt_gen *) newoption, offset);
}
+/*
+ * init_ternary_reloption
+ * Allocate and initialize a new ternary reloption
+ */
+static relopt_ternary *
+init_ternary_reloption(bits32 kinds, const char *name, const char *desc,
+ ternary default_val, LOCKMODE lockmode)
+{
+ relopt_ternary *newoption;
+
+ newoption = (relopt_ternary *) allocate_reloption(kinds,
+ RELOPT_TYPE_TERNARY, name, desc, lockmode);
+ newoption->default_val = default_val;
+
+ return newoption;
+}
+
+/*
+ * add_ternary_reloption
+ * Add a new ternary reloption
+ */
+void
+add_ternary_reloption(bits32 kinds, const char *name, const char *desc,
+ ternary default_val, LOCKMODE lockmode)
+{
+ relopt_ternary *newoption = init_ternary_reloption(kinds, name, desc,
+ default_val, lockmode);
+
+ add_reloption((relopt_gen *) newoption);
+}
+
+/*
+ * add_local_ternary_reloption
+ * Add a new ternary local reloption
+ *
+ * 'offset' is offset of ternary-typed field.
+ */
+void
+add_local_ternary_reloption(local_relopts *relopts, const char *name,
+ const char *desc, ternary default_val,
+ int offset)
+{
+ relopt_ternary *newoption = init_ternary_reloption(RELOPT_KIND_LOCAL,
+ name, desc,
+ default_val, 0);
+
+ add_local_reloption(relopts, (relopt_gen *) newoption, offset);
+}
/*
* init_real_reloption
@@ -1617,6 +1689,18 @@ parse_one_reloption(relopt_value *option, char *text_str, int text_len,
option->gen->name, value)));
}
break;
+ case RELOPT_TYPE_TERNARY:
+ {
+ bool b;
+ parsed = parse_bool(value, &b);
+ option->values.ternary_val = b ? TERNARY_TRUE : TERNARY_FALSE;
+ if (validate && !parsed)
+ ereport(ERROR,
+ (errcode(ERRCODE_INVALID_PARAMETER_VALUE),
+ errmsg("invalid value for ternary option \"%s\": %s",
+ option->gen->name, value)));
+ }
+ break;
case RELOPT_TYPE_INT:
{
relopt_int *optint = (relopt_int *) option->gen;
@@ -1780,17 +1864,6 @@ fillRelOptions(void *rdopts, Size basesize,
char *itempos = ((char *) rdopts) + elems[j].offset;
char *string_val;
- /*
- * If isset_offset is provided, store whether the reloption is
- * set there.
- */
- if (elems[j].isset_offset > 0)
- {
- char *setpos = ((char *) rdopts) + elems[j].isset_offset;
-
- *(bool *) setpos = options[i].isset;
- }
-
switch (options[i].gen->type)
{
case RELOPT_TYPE_BOOL:
@@ -1798,6 +1871,11 @@ fillRelOptions(void *rdopts, Size basesize,
options[i].values.bool_val :
((relopt_bool *) options[i].gen)->default_val;
break;
+ case RELOPT_TYPE_TERNARY:
+ *(ternary *) itempos = options[i].isset ?
+ options[i].values.ternary_val :
+ ((relopt_ternary *) options[i].gen)->default_val;
+ break;
case RELOPT_TYPE_INT:
*(int *) itempos = options[i].isset ?
options[i].values.int_val :
@@ -1912,8 +1990,8 @@ default_reloptions(Datum reloptions, bool validate, relopt_kind kind)
offsetof(StdRdOptions, parallel_workers)},
{"vacuum_index_cleanup", RELOPT_TYPE_ENUM,
offsetof(StdRdOptions, vacuum_index_cleanup)},
- {"vacuum_truncate", RELOPT_TYPE_BOOL,
- offsetof(StdRdOptions, vacuum_truncate), offsetof(StdRdOptions, vacuum_truncate_set)},
+ {"vacuum_truncate", RELOPT_TYPE_TERNARY,
+ offsetof(StdRdOptions, vacuum_truncate)},
{"vacuum_max_eager_freeze_failure_rate", RELOPT_TYPE_REAL,
offsetof(StdRdOptions, vacuum_max_eager_freeze_failure_rate)}
};
@@ -1993,7 +2071,6 @@ build_local_reloptions(local_relopts *relopts, Datum options, bool validate)
elems[i].optname = opt->option->name;
elems[i].opttype = opt->option->type;
elems[i].offset = opt->offset;
- elems[i].isset_offset = 0; /* not supported for local relopts yet */
i++;
}
diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c
index 733ef40ae7..977babff54 100644
--- a/src/backend/commands/vacuum.c
+++ b/src/backend/commands/vacuum.c
@@ -2221,9 +2221,9 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams params,
{
StdRdOptions *opts = (StdRdOptions *) rel->rd_options;
- if (opts && opts->vacuum_truncate_set)
+ if (opts && opts->vacuum_truncate != TERNARY_UNSET)
{
- if (opts->vacuum_truncate)
+ if (opts->vacuum_truncate == TERNARY_TRUE)
params.truncate = VACOPTVALUE_ENABLED;
else
params.truncate = VACOPTVALUE_DISABLED;
diff --git a/src/include/access/reloptions.h b/src/include/access/reloptions.h
index a604a4702c..a436697658 100644
--- a/src/include/access/reloptions.h
+++ b/src/include/access/reloptions.h
@@ -29,6 +29,7 @@
typedef enum relopt_type
{
RELOPT_TYPE_BOOL,
+ RELOPT_TYPE_TERNARY, /* on, off, unset */
RELOPT_TYPE_INT,
RELOPT_TYPE_REAL,
RELOPT_TYPE_ENUM,
@@ -80,6 +81,7 @@ typedef struct relopt_value
union
{
bool bool_val;
+ ternary ternary_val;
int int_val;
double real_val;
int enum_val;
@@ -94,6 +96,12 @@ typedef struct relopt_bool
bool default_val;
} relopt_bool;
+typedef struct relopt_rernary
+{
+ relopt_gen gen;
+ int default_val;
+} relopt_ternary;
+
typedef struct relopt_int
{
relopt_gen gen;
@@ -152,19 +160,6 @@ typedef struct
const char *optname; /* option's name */
relopt_type opttype; /* option's datatype */
int offset; /* offset of field in result struct */
-
- /*
- * isset_offset is an optional offset of a field in the result struct that
- * stores whether the option is explicitly set for the relation or if it
- * just picked up the default value. In most cases, this can be
- * accomplished by giving the reloption a special out-of-range default
- * value (e.g., some integer reloptions use -2), but this isn't always
- * possible. For example, a Boolean reloption cannot be given an
- * out-of-range default, so we need another way to discover the source of
- * its value. This offset is only used if given a value greater than
- * zero.
- */
- int isset_offset;
} relopt_parse_elt;
/* Local reloption definition */
@@ -195,6 +190,8 @@ typedef struct local_relopts
extern relopt_kind add_reloption_kind(void);
extern void add_bool_reloption(bits32 kinds, const char *name, const char *desc,
bool default_val, LOCKMODE lockmode);
+extern void add_ternary_reloption(bits32 kinds, const char *name,
+ const char *desc, int default_val, LOCKMODE lockmode);
extern void add_int_reloption(bits32 kinds, const char *name, const char *desc,
int default_val, int min_val, int max_val,
LOCKMODE lockmode);
@@ -214,6 +211,9 @@ extern void register_reloptions_validator(local_relopts *relopts,
extern void add_local_bool_reloption(local_relopts *relopts, const char *name,
const char *desc, bool default_val,
int offset);
+extern void add_local_ternary_reloption(local_relopts *relopts,
+ const char *name, const char *desc,
+ ternary default_val, int offset);
extern void add_local_int_reloption(local_relopts *relopts, const char *name,
const char *desc, int default_val,
int min_val, int max_val, int offset);
diff --git a/src/include/c.h b/src/include/c.h
index 39022f8a9d..9c59ccccf8 100644
--- a/src/include/c.h
+++ b/src/include/c.h
@@ -488,6 +488,22 @@ typedef void (*pg_funcptr_t) (void);
#include <stdbool.h>
+/*
+ * ternary
+ * Boolean value with an extrea "unset" option
+ *
+ * Ternary data type is used in relation options that can be "true", "false" or
+ * "unset". Since relation options are used deep inside the PostgreSQL code,
+ * this type is declared globally.
+*/
+
+typedef enum ternary
+{
+ TERNARY_FALSE = 0,
+ TERNARY_TRUE = 1,
+ TERNARY_UNSET = -1
+} ternary;
+
/* ----------------------------------------------------------------
* Section 3: standard system types
diff --git a/src/include/utils/rel.h b/src/include/utils/rel.h
index b552359915..7346d618f9 100644
--- a/src/include/utils/rel.h
+++ b/src/include/utils/rel.h
@@ -346,8 +346,7 @@ typedef struct StdRdOptions
bool user_catalog_table; /* use as an additional catalog relation */
int parallel_workers; /* max number of parallel workers */
StdRdOptIndexCleanup vacuum_index_cleanup; /* controls index vacuuming */
- bool vacuum_truncate; /* enables vacuum to truncate a relation */
- bool vacuum_truncate_set; /* whether vacuum_truncate is set */
+ ternary vacuum_truncate; /* enables vacuum to truncate a relation */
/*
* Fraction of pages in a relation that vacuum can eagerly scan and fail
--
2.39.2
--nextPart5129932.5fSG56mABF
Content-Disposition: attachment;
filename="v2-0003-Add-alias-to-be-used-as-unset-state.patch"
Content-Transfer-Encoding: 7Bit
Content-Type: text/x-patch; charset="unicode-2-0-utf-8";
name="v2-0003-Add-alias-to-be-used-as-unset-state.patch"
^ permalink raw reply [nested|flat] 868+ messages in thread
* [PATCH v2a 2/4] Introduce ternary reloptions
@ 2025-09-04 14:41 Nikolay Shaplov <[email protected]>
0 siblings, 0 replies; 868+ messages in thread
From: Nikolay Shaplov @ 2025-09-04 14:41 UTC (permalink / raw)
Introduce ternary reloption as a replacement for current `vacuum_truncate`
implementation. Remove `vacuum_truncate_set` additional flag and using
`TERNARY_UNSET` value instead.
---
src/backend/access/common/reloptions.c | 129 ++++++++++++++++++++-----
src/backend/commands/vacuum.c | 4 +-
src/include/access/reloptions.h | 26 ++---
src/include/c.h | 16 +++
src/include/utils/rel.h | 3 +-
5 files changed, 135 insertions(+), 43 deletions(-)
diff --git a/src/backend/access/common/reloptions.c b/src/backend/access/common/reloptions.c
index 0af3fea68f..f543f11001 100644
--- a/src/backend/access/common/reloptions.c
+++ b/src/backend/access/common/reloptions.c
@@ -40,9 +40,9 @@
*
* To add an option:
*
- * (i) decide on a type (bool, integer, real, enum, string), name, default
- * value, upper and lower bounds (if applicable); for strings, consider a
- * validation routine.
+ * (i) decide on a type (bool, ternary, integer, real, enum, string), name,
+ * default value, upper and lower bounds (if applicable); for strings,
+ * consider a validation routine.
* (ii) add a record below (or use add_<type>_reloption).
* (iii) add it to the appropriate options struct (perhaps StdRdOptions)
* (iv) add it to the appropriate handling routine (perhaps
@@ -147,15 +147,6 @@ static relopt_bool boolRelOpts[] =
},
false
},
- {
- {
- "vacuum_truncate",
- "Enables vacuum to truncate empty pages at the end of this table",
- RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
- ShareUpdateExclusiveLock
- },
- true
- },
{
{
"deduplicate_items",
@@ -170,6 +161,21 @@ static relopt_bool boolRelOpts[] =
{{NULL}}
};
+static relopt_ternary ternaryRelOpts[] =
+{
+ {
+ {
+ "vacuum_truncate",
+ "Enables vacuum to truncate empty pages at the end of this table",
+ RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
+ ShareUpdateExclusiveLock
+ },
+ TERNARY_UNSET
+ },
+ /* list terminator */
+ {{NULL}}
+};
+
static relopt_int intRelOpts[] =
{
{
@@ -600,6 +606,13 @@ initialize_reloptions(void)
boolRelOpts[i].gen.lockmode));
j++;
}
+ for (i = 0; ternaryRelOpts[i].gen.name; i++)
+ {
+ Assert(DoLockModesConflict(ternaryRelOpts[i].gen.lockmode,
+ ternaryRelOpts[i].gen.lockmode));
+ j++;
+ }
+
for (i = 0; intRelOpts[i].gen.name; i++)
{
Assert(DoLockModesConflict(intRelOpts[i].gen.lockmode,
@@ -640,6 +653,14 @@ initialize_reloptions(void)
j++;
}
+ for (i = 0; ternaryRelOpts[i].gen.name; i++)
+ {
+ relOpts[j] = &ternaryRelOpts[i].gen;
+ relOpts[j]->type = RELOPT_TYPE_TERNARY;
+ relOpts[j]->namelen = strlen(relOpts[j]->name);
+ j++;
+ }
+
for (i = 0; intRelOpts[i].gen.name; i++)
{
relOpts[j] = &intRelOpts[i].gen;
@@ -800,6 +821,9 @@ allocate_reloption(bits32 kinds, int type, const char *name, const char *desc,
case RELOPT_TYPE_BOOL:
size = sizeof(relopt_bool);
break;
+ case RELOPT_TYPE_TERNARY:
+ size = sizeof(relopt_ternary);
+ break;
case RELOPT_TYPE_INT:
size = sizeof(relopt_int);
break;
@@ -883,6 +907,54 @@ add_local_bool_reloption(local_relopts *relopts, const char *name,
add_local_reloption(relopts, (relopt_gen *) newoption, offset);
}
+/*
+ * init_ternary_reloption
+ * Allocate and initialize a new ternary reloption
+ */
+static relopt_ternary *
+init_ternary_reloption(bits32 kinds, const char *name, const char *desc,
+ ternary default_val, LOCKMODE lockmode)
+{
+ relopt_ternary *newoption;
+
+ newoption = (relopt_ternary *) allocate_reloption(kinds,
+ RELOPT_TYPE_TERNARY, name, desc, lockmode);
+ newoption->default_val = default_val;
+
+ return newoption;
+}
+
+/*
+ * add_ternary_reloption
+ * Add a new ternary reloption
+ */
+void
+add_ternary_reloption(bits32 kinds, const char *name, const char *desc,
+ ternary default_val, LOCKMODE lockmode)
+{
+ relopt_ternary *newoption = init_ternary_reloption(kinds, name, desc,
+ default_val, lockmode);
+
+ add_reloption((relopt_gen *) newoption);
+}
+
+/*
+ * add_local_ternary_reloption
+ * Add a new ternary local reloption
+ *
+ * 'offset' is offset of ternary-typed field.
+ */
+void
+add_local_ternary_reloption(local_relopts *relopts, const char *name,
+ const char *desc, ternary default_val,
+ int offset)
+{
+ relopt_ternary *newoption = init_ternary_reloption(RELOPT_KIND_LOCAL,
+ name, desc,
+ default_val, 0);
+
+ add_local_reloption(relopts, (relopt_gen *) newoption, offset);
+}
/*
* init_real_reloption
@@ -1617,6 +1689,18 @@ parse_one_reloption(relopt_value *option, char *text_str, int text_len,
option->gen->name, value)));
}
break;
+ case RELOPT_TYPE_TERNARY:
+ {
+ bool b;
+ parsed = parse_bool(value, &b);
+ option->values.ternary_val = b ? TERNARY_TRUE : TERNARY_FALSE;
+ if (validate && !parsed)
+ ereport(ERROR,
+ (errcode(ERRCODE_INVALID_PARAMETER_VALUE),
+ errmsg("invalid value for ternary option \"%s\": %s",
+ option->gen->name, value)));
+ }
+ break;
case RELOPT_TYPE_INT:
{
relopt_int *optint = (relopt_int *) option->gen;
@@ -1780,17 +1864,6 @@ fillRelOptions(void *rdopts, Size basesize,
char *itempos = ((char *) rdopts) + elems[j].offset;
char *string_val;
- /*
- * If isset_offset is provided, store whether the reloption is
- * set there.
- */
- if (elems[j].isset_offset > 0)
- {
- char *setpos = ((char *) rdopts) + elems[j].isset_offset;
-
- *(bool *) setpos = options[i].isset;
- }
-
switch (options[i].gen->type)
{
case RELOPT_TYPE_BOOL:
@@ -1798,6 +1871,11 @@ fillRelOptions(void *rdopts, Size basesize,
options[i].values.bool_val :
((relopt_bool *) options[i].gen)->default_val;
break;
+ case RELOPT_TYPE_TERNARY:
+ *(ternary *) itempos = options[i].isset ?
+ options[i].values.ternary_val :
+ ((relopt_ternary *) options[i].gen)->default_val;
+ break;
case RELOPT_TYPE_INT:
*(int *) itempos = options[i].isset ?
options[i].values.int_val :
@@ -1912,8 +1990,8 @@ default_reloptions(Datum reloptions, bool validate, relopt_kind kind)
offsetof(StdRdOptions, parallel_workers)},
{"vacuum_index_cleanup", RELOPT_TYPE_ENUM,
offsetof(StdRdOptions, vacuum_index_cleanup)},
- {"vacuum_truncate", RELOPT_TYPE_BOOL,
- offsetof(StdRdOptions, vacuum_truncate), offsetof(StdRdOptions, vacuum_truncate_set)},
+ {"vacuum_truncate", RELOPT_TYPE_TERNARY,
+ offsetof(StdRdOptions, vacuum_truncate)},
{"vacuum_max_eager_freeze_failure_rate", RELOPT_TYPE_REAL,
offsetof(StdRdOptions, vacuum_max_eager_freeze_failure_rate)}
};
@@ -1993,7 +2071,6 @@ build_local_reloptions(local_relopts *relopts, Datum options, bool validate)
elems[i].optname = opt->option->name;
elems[i].opttype = opt->option->type;
elems[i].offset = opt->offset;
- elems[i].isset_offset = 0; /* not supported for local relopts yet */
i++;
}
diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c
index 733ef40ae7..977babff54 100644
--- a/src/backend/commands/vacuum.c
+++ b/src/backend/commands/vacuum.c
@@ -2221,9 +2221,9 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams params,
{
StdRdOptions *opts = (StdRdOptions *) rel->rd_options;
- if (opts && opts->vacuum_truncate_set)
+ if (opts && opts->vacuum_truncate != TERNARY_UNSET)
{
- if (opts->vacuum_truncate)
+ if (opts->vacuum_truncate == TERNARY_TRUE)
params.truncate = VACOPTVALUE_ENABLED;
else
params.truncate = VACOPTVALUE_DISABLED;
diff --git a/src/include/access/reloptions.h b/src/include/access/reloptions.h
index a604a4702c..a436697658 100644
--- a/src/include/access/reloptions.h
+++ b/src/include/access/reloptions.h
@@ -29,6 +29,7 @@
typedef enum relopt_type
{
RELOPT_TYPE_BOOL,
+ RELOPT_TYPE_TERNARY, /* on, off, unset */
RELOPT_TYPE_INT,
RELOPT_TYPE_REAL,
RELOPT_TYPE_ENUM,
@@ -80,6 +81,7 @@ typedef struct relopt_value
union
{
bool bool_val;
+ ternary ternary_val;
int int_val;
double real_val;
int enum_val;
@@ -94,6 +96,12 @@ typedef struct relopt_bool
bool default_val;
} relopt_bool;
+typedef struct relopt_rernary
+{
+ relopt_gen gen;
+ int default_val;
+} relopt_ternary;
+
typedef struct relopt_int
{
relopt_gen gen;
@@ -152,19 +160,6 @@ typedef struct
const char *optname; /* option's name */
relopt_type opttype; /* option's datatype */
int offset; /* offset of field in result struct */
-
- /*
- * isset_offset is an optional offset of a field in the result struct that
- * stores whether the option is explicitly set for the relation or if it
- * just picked up the default value. In most cases, this can be
- * accomplished by giving the reloption a special out-of-range default
- * value (e.g., some integer reloptions use -2), but this isn't always
- * possible. For example, a Boolean reloption cannot be given an
- * out-of-range default, so we need another way to discover the source of
- * its value. This offset is only used if given a value greater than
- * zero.
- */
- int isset_offset;
} relopt_parse_elt;
/* Local reloption definition */
@@ -195,6 +190,8 @@ typedef struct local_relopts
extern relopt_kind add_reloption_kind(void);
extern void add_bool_reloption(bits32 kinds, const char *name, const char *desc,
bool default_val, LOCKMODE lockmode);
+extern void add_ternary_reloption(bits32 kinds, const char *name,
+ const char *desc, int default_val, LOCKMODE lockmode);
extern void add_int_reloption(bits32 kinds, const char *name, const char *desc,
int default_val, int min_val, int max_val,
LOCKMODE lockmode);
@@ -214,6 +211,9 @@ extern void register_reloptions_validator(local_relopts *relopts,
extern void add_local_bool_reloption(local_relopts *relopts, const char *name,
const char *desc, bool default_val,
int offset);
+extern void add_local_ternary_reloption(local_relopts *relopts,
+ const char *name, const char *desc,
+ ternary default_val, int offset);
extern void add_local_int_reloption(local_relopts *relopts, const char *name,
const char *desc, int default_val,
int min_val, int max_val, int offset);
diff --git a/src/include/c.h b/src/include/c.h
index 39022f8a9d..9c59ccccf8 100644
--- a/src/include/c.h
+++ b/src/include/c.h
@@ -488,6 +488,22 @@ typedef void (*pg_funcptr_t) (void);
#include <stdbool.h>
+/*
+ * ternary
+ * Boolean value with an extrea "unset" option
+ *
+ * Ternary data type is used in relation options that can be "true", "false" or
+ * "unset". Since relation options are used deep inside the PostgreSQL code,
+ * this type is declared globally.
+*/
+
+typedef enum ternary
+{
+ TERNARY_FALSE = 0,
+ TERNARY_TRUE = 1,
+ TERNARY_UNSET = -1
+} ternary;
+
/* ----------------------------------------------------------------
* Section 3: standard system types
diff --git a/src/include/utils/rel.h b/src/include/utils/rel.h
index b552359915..7346d618f9 100644
--- a/src/include/utils/rel.h
+++ b/src/include/utils/rel.h
@@ -346,8 +346,7 @@ typedef struct StdRdOptions
bool user_catalog_table; /* use as an additional catalog relation */
int parallel_workers; /* max number of parallel workers */
StdRdOptIndexCleanup vacuum_index_cleanup; /* controls index vacuuming */
- bool vacuum_truncate; /* enables vacuum to truncate a relation */
- bool vacuum_truncate_set; /* whether vacuum_truncate is set */
+ ternary vacuum_truncate; /* enables vacuum to truncate a relation */
/*
* Fraction of pages in a relation that vacuum can eagerly scan and fail
--
2.39.2
--nextPart4155600.3ZeAukHxDK
Content-Disposition: attachment;
filename="v2a-0003-Add-alias-to-be-used-as-unset-state.patch"
Content-Transfer-Encoding: 7Bit
Content-Type: text/x-patch; charset="unicode-2-0-utf-8";
name="v2a-0003-Add-alias-to-be-used-as-unset-state.patch"
^ permalink raw reply [nested|flat] 868+ messages in thread
* [PATCH v2 2/4] Introduce ternary reloptions
@ 2025-09-04 14:41 Nikolay Shaplov <[email protected]>
0 siblings, 0 replies; 868+ messages in thread
From: Nikolay Shaplov @ 2025-09-04 14:41 UTC (permalink / raw)
Introduce ternary reloption as a replacement for current `vacuum_truncate`
implementation. Remove `vacuum_truncate_set` additional flag and using
`TERNARY_UNSET` value instead.
---
src/backend/access/common/reloptions.c | 129 ++++++++++++++++++++-----
src/backend/commands/vacuum.c | 4 +-
src/include/access/reloptions.h | 26 ++---
src/include/c.h | 16 +++
src/include/utils/rel.h | 3 +-
5 files changed, 135 insertions(+), 43 deletions(-)
diff --git a/src/backend/access/common/reloptions.c b/src/backend/access/common/reloptions.c
index 0af3fea68f..f543f11001 100644
--- a/src/backend/access/common/reloptions.c
+++ b/src/backend/access/common/reloptions.c
@@ -40,9 +40,9 @@
*
* To add an option:
*
- * (i) decide on a type (bool, integer, real, enum, string), name, default
- * value, upper and lower bounds (if applicable); for strings, consider a
- * validation routine.
+ * (i) decide on a type (bool, ternary, integer, real, enum, string), name,
+ * default value, upper and lower bounds (if applicable); for strings,
+ * consider a validation routine.
* (ii) add a record below (or use add_<type>_reloption).
* (iii) add it to the appropriate options struct (perhaps StdRdOptions)
* (iv) add it to the appropriate handling routine (perhaps
@@ -147,15 +147,6 @@ static relopt_bool boolRelOpts[] =
},
false
},
- {
- {
- "vacuum_truncate",
- "Enables vacuum to truncate empty pages at the end of this table",
- RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
- ShareUpdateExclusiveLock
- },
- true
- },
{
{
"deduplicate_items",
@@ -170,6 +161,21 @@ static relopt_bool boolRelOpts[] =
{{NULL}}
};
+static relopt_ternary ternaryRelOpts[] =
+{
+ {
+ {
+ "vacuum_truncate",
+ "Enables vacuum to truncate empty pages at the end of this table",
+ RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
+ ShareUpdateExclusiveLock
+ },
+ TERNARY_UNSET
+ },
+ /* list terminator */
+ {{NULL}}
+};
+
static relopt_int intRelOpts[] =
{
{
@@ -600,6 +606,13 @@ initialize_reloptions(void)
boolRelOpts[i].gen.lockmode));
j++;
}
+ for (i = 0; ternaryRelOpts[i].gen.name; i++)
+ {
+ Assert(DoLockModesConflict(ternaryRelOpts[i].gen.lockmode,
+ ternaryRelOpts[i].gen.lockmode));
+ j++;
+ }
+
for (i = 0; intRelOpts[i].gen.name; i++)
{
Assert(DoLockModesConflict(intRelOpts[i].gen.lockmode,
@@ -640,6 +653,14 @@ initialize_reloptions(void)
j++;
}
+ for (i = 0; ternaryRelOpts[i].gen.name; i++)
+ {
+ relOpts[j] = &ternaryRelOpts[i].gen;
+ relOpts[j]->type = RELOPT_TYPE_TERNARY;
+ relOpts[j]->namelen = strlen(relOpts[j]->name);
+ j++;
+ }
+
for (i = 0; intRelOpts[i].gen.name; i++)
{
relOpts[j] = &intRelOpts[i].gen;
@@ -800,6 +821,9 @@ allocate_reloption(bits32 kinds, int type, const char *name, const char *desc,
case RELOPT_TYPE_BOOL:
size = sizeof(relopt_bool);
break;
+ case RELOPT_TYPE_TERNARY:
+ size = sizeof(relopt_ternary);
+ break;
case RELOPT_TYPE_INT:
size = sizeof(relopt_int);
break;
@@ -883,6 +907,54 @@ add_local_bool_reloption(local_relopts *relopts, const char *name,
add_local_reloption(relopts, (relopt_gen *) newoption, offset);
}
+/*
+ * init_ternary_reloption
+ * Allocate and initialize a new ternary reloption
+ */
+static relopt_ternary *
+init_ternary_reloption(bits32 kinds, const char *name, const char *desc,
+ ternary default_val, LOCKMODE lockmode)
+{
+ relopt_ternary *newoption;
+
+ newoption = (relopt_ternary *) allocate_reloption(kinds,
+ RELOPT_TYPE_TERNARY, name, desc, lockmode);
+ newoption->default_val = default_val;
+
+ return newoption;
+}
+
+/*
+ * add_ternary_reloption
+ * Add a new ternary reloption
+ */
+void
+add_ternary_reloption(bits32 kinds, const char *name, const char *desc,
+ ternary default_val, LOCKMODE lockmode)
+{
+ relopt_ternary *newoption = init_ternary_reloption(kinds, name, desc,
+ default_val, lockmode);
+
+ add_reloption((relopt_gen *) newoption);
+}
+
+/*
+ * add_local_ternary_reloption
+ * Add a new ternary local reloption
+ *
+ * 'offset' is offset of ternary-typed field.
+ */
+void
+add_local_ternary_reloption(local_relopts *relopts, const char *name,
+ const char *desc, ternary default_val,
+ int offset)
+{
+ relopt_ternary *newoption = init_ternary_reloption(RELOPT_KIND_LOCAL,
+ name, desc,
+ default_val, 0);
+
+ add_local_reloption(relopts, (relopt_gen *) newoption, offset);
+}
/*
* init_real_reloption
@@ -1617,6 +1689,18 @@ parse_one_reloption(relopt_value *option, char *text_str, int text_len,
option->gen->name, value)));
}
break;
+ case RELOPT_TYPE_TERNARY:
+ {
+ bool b;
+ parsed = parse_bool(value, &b);
+ option->values.ternary_val = b ? TERNARY_TRUE : TERNARY_FALSE;
+ if (validate && !parsed)
+ ereport(ERROR,
+ (errcode(ERRCODE_INVALID_PARAMETER_VALUE),
+ errmsg("invalid value for ternary option \"%s\": %s",
+ option->gen->name, value)));
+ }
+ break;
case RELOPT_TYPE_INT:
{
relopt_int *optint = (relopt_int *) option->gen;
@@ -1780,17 +1864,6 @@ fillRelOptions(void *rdopts, Size basesize,
char *itempos = ((char *) rdopts) + elems[j].offset;
char *string_val;
- /*
- * If isset_offset is provided, store whether the reloption is
- * set there.
- */
- if (elems[j].isset_offset > 0)
- {
- char *setpos = ((char *) rdopts) + elems[j].isset_offset;
-
- *(bool *) setpos = options[i].isset;
- }
-
switch (options[i].gen->type)
{
case RELOPT_TYPE_BOOL:
@@ -1798,6 +1871,11 @@ fillRelOptions(void *rdopts, Size basesize,
options[i].values.bool_val :
((relopt_bool *) options[i].gen)->default_val;
break;
+ case RELOPT_TYPE_TERNARY:
+ *(ternary *) itempos = options[i].isset ?
+ options[i].values.ternary_val :
+ ((relopt_ternary *) options[i].gen)->default_val;
+ break;
case RELOPT_TYPE_INT:
*(int *) itempos = options[i].isset ?
options[i].values.int_val :
@@ -1912,8 +1990,8 @@ default_reloptions(Datum reloptions, bool validate, relopt_kind kind)
offsetof(StdRdOptions, parallel_workers)},
{"vacuum_index_cleanup", RELOPT_TYPE_ENUM,
offsetof(StdRdOptions, vacuum_index_cleanup)},
- {"vacuum_truncate", RELOPT_TYPE_BOOL,
- offsetof(StdRdOptions, vacuum_truncate), offsetof(StdRdOptions, vacuum_truncate_set)},
+ {"vacuum_truncate", RELOPT_TYPE_TERNARY,
+ offsetof(StdRdOptions, vacuum_truncate)},
{"vacuum_max_eager_freeze_failure_rate", RELOPT_TYPE_REAL,
offsetof(StdRdOptions, vacuum_max_eager_freeze_failure_rate)}
};
@@ -1993,7 +2071,6 @@ build_local_reloptions(local_relopts *relopts, Datum options, bool validate)
elems[i].optname = opt->option->name;
elems[i].opttype = opt->option->type;
elems[i].offset = opt->offset;
- elems[i].isset_offset = 0; /* not supported for local relopts yet */
i++;
}
diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c
index 733ef40ae7..977babff54 100644
--- a/src/backend/commands/vacuum.c
+++ b/src/backend/commands/vacuum.c
@@ -2221,9 +2221,9 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams params,
{
StdRdOptions *opts = (StdRdOptions *) rel->rd_options;
- if (opts && opts->vacuum_truncate_set)
+ if (opts && opts->vacuum_truncate != TERNARY_UNSET)
{
- if (opts->vacuum_truncate)
+ if (opts->vacuum_truncate == TERNARY_TRUE)
params.truncate = VACOPTVALUE_ENABLED;
else
params.truncate = VACOPTVALUE_DISABLED;
diff --git a/src/include/access/reloptions.h b/src/include/access/reloptions.h
index a604a4702c..a436697658 100644
--- a/src/include/access/reloptions.h
+++ b/src/include/access/reloptions.h
@@ -29,6 +29,7 @@
typedef enum relopt_type
{
RELOPT_TYPE_BOOL,
+ RELOPT_TYPE_TERNARY, /* on, off, unset */
RELOPT_TYPE_INT,
RELOPT_TYPE_REAL,
RELOPT_TYPE_ENUM,
@@ -80,6 +81,7 @@ typedef struct relopt_value
union
{
bool bool_val;
+ ternary ternary_val;
int int_val;
double real_val;
int enum_val;
@@ -94,6 +96,12 @@ typedef struct relopt_bool
bool default_val;
} relopt_bool;
+typedef struct relopt_rernary
+{
+ relopt_gen gen;
+ int default_val;
+} relopt_ternary;
+
typedef struct relopt_int
{
relopt_gen gen;
@@ -152,19 +160,6 @@ typedef struct
const char *optname; /* option's name */
relopt_type opttype; /* option's datatype */
int offset; /* offset of field in result struct */
-
- /*
- * isset_offset is an optional offset of a field in the result struct that
- * stores whether the option is explicitly set for the relation or if it
- * just picked up the default value. In most cases, this can be
- * accomplished by giving the reloption a special out-of-range default
- * value (e.g., some integer reloptions use -2), but this isn't always
- * possible. For example, a Boolean reloption cannot be given an
- * out-of-range default, so we need another way to discover the source of
- * its value. This offset is only used if given a value greater than
- * zero.
- */
- int isset_offset;
} relopt_parse_elt;
/* Local reloption definition */
@@ -195,6 +190,8 @@ typedef struct local_relopts
extern relopt_kind add_reloption_kind(void);
extern void add_bool_reloption(bits32 kinds, const char *name, const char *desc,
bool default_val, LOCKMODE lockmode);
+extern void add_ternary_reloption(bits32 kinds, const char *name,
+ const char *desc, int default_val, LOCKMODE lockmode);
extern void add_int_reloption(bits32 kinds, const char *name, const char *desc,
int default_val, int min_val, int max_val,
LOCKMODE lockmode);
@@ -214,6 +211,9 @@ extern void register_reloptions_validator(local_relopts *relopts,
extern void add_local_bool_reloption(local_relopts *relopts, const char *name,
const char *desc, bool default_val,
int offset);
+extern void add_local_ternary_reloption(local_relopts *relopts,
+ const char *name, const char *desc,
+ ternary default_val, int offset);
extern void add_local_int_reloption(local_relopts *relopts, const char *name,
const char *desc, int default_val,
int min_val, int max_val, int offset);
diff --git a/src/include/c.h b/src/include/c.h
index 39022f8a9d..9c59ccccf8 100644
--- a/src/include/c.h
+++ b/src/include/c.h
@@ -488,6 +488,22 @@ typedef void (*pg_funcptr_t) (void);
#include <stdbool.h>
+/*
+ * ternary
+ * Boolean value with an extrea "unset" option
+ *
+ * Ternary data type is used in relation options that can be "true", "false" or
+ * "unset". Since relation options are used deep inside the PostgreSQL code,
+ * this type is declared globally.
+*/
+
+typedef enum ternary
+{
+ TERNARY_FALSE = 0,
+ TERNARY_TRUE = 1,
+ TERNARY_UNSET = -1
+} ternary;
+
/* ----------------------------------------------------------------
* Section 3: standard system types
diff --git a/src/include/utils/rel.h b/src/include/utils/rel.h
index b552359915..7346d618f9 100644
--- a/src/include/utils/rel.h
+++ b/src/include/utils/rel.h
@@ -346,8 +346,7 @@ typedef struct StdRdOptions
bool user_catalog_table; /* use as an additional catalog relation */
int parallel_workers; /* max number of parallel workers */
StdRdOptIndexCleanup vacuum_index_cleanup; /* controls index vacuuming */
- bool vacuum_truncate; /* enables vacuum to truncate a relation */
- bool vacuum_truncate_set; /* whether vacuum_truncate is set */
+ ternary vacuum_truncate; /* enables vacuum to truncate a relation */
/*
* Fraction of pages in a relation that vacuum can eagerly scan and fail
--
2.39.2
--nextPart5129932.5fSG56mABF
Content-Disposition: attachment;
filename="v2-0003-Add-alias-to-be-used-as-unset-state.patch"
Content-Transfer-Encoding: 7Bit
Content-Type: text/x-patch; charset="unicode-2-0-utf-8";
name="v2-0003-Add-alias-to-be-used-as-unset-state.patch"
^ permalink raw reply [nested|flat] 868+ messages in thread
* [PATCH v2 2/4] Introduce ternary reloptions
@ 2025-09-04 14:41 Nikolay Shaplov <[email protected]>
0 siblings, 0 replies; 868+ messages in thread
From: Nikolay Shaplov @ 2025-09-04 14:41 UTC (permalink / raw)
Introduce ternary reloption as a replacement for current `vacuum_truncate`
implementation. Remove `vacuum_truncate_set` additional flag and using
`TERNARY_UNSET` value instead.
---
src/backend/access/common/reloptions.c | 129 ++++++++++++++++++++-----
src/backend/commands/vacuum.c | 4 +-
src/include/access/reloptions.h | 26 ++---
src/include/c.h | 16 +++
src/include/utils/rel.h | 3 +-
5 files changed, 135 insertions(+), 43 deletions(-)
diff --git a/src/backend/access/common/reloptions.c b/src/backend/access/common/reloptions.c
index 0af3fea68f..f543f11001 100644
--- a/src/backend/access/common/reloptions.c
+++ b/src/backend/access/common/reloptions.c
@@ -40,9 +40,9 @@
*
* To add an option:
*
- * (i) decide on a type (bool, integer, real, enum, string), name, default
- * value, upper and lower bounds (if applicable); for strings, consider a
- * validation routine.
+ * (i) decide on a type (bool, ternary, integer, real, enum, string), name,
+ * default value, upper and lower bounds (if applicable); for strings,
+ * consider a validation routine.
* (ii) add a record below (or use add_<type>_reloption).
* (iii) add it to the appropriate options struct (perhaps StdRdOptions)
* (iv) add it to the appropriate handling routine (perhaps
@@ -147,15 +147,6 @@ static relopt_bool boolRelOpts[] =
},
false
},
- {
- {
- "vacuum_truncate",
- "Enables vacuum to truncate empty pages at the end of this table",
- RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
- ShareUpdateExclusiveLock
- },
- true
- },
{
{
"deduplicate_items",
@@ -170,6 +161,21 @@ static relopt_bool boolRelOpts[] =
{{NULL}}
};
+static relopt_ternary ternaryRelOpts[] =
+{
+ {
+ {
+ "vacuum_truncate",
+ "Enables vacuum to truncate empty pages at the end of this table",
+ RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
+ ShareUpdateExclusiveLock
+ },
+ TERNARY_UNSET
+ },
+ /* list terminator */
+ {{NULL}}
+};
+
static relopt_int intRelOpts[] =
{
{
@@ -600,6 +606,13 @@ initialize_reloptions(void)
boolRelOpts[i].gen.lockmode));
j++;
}
+ for (i = 0; ternaryRelOpts[i].gen.name; i++)
+ {
+ Assert(DoLockModesConflict(ternaryRelOpts[i].gen.lockmode,
+ ternaryRelOpts[i].gen.lockmode));
+ j++;
+ }
+
for (i = 0; intRelOpts[i].gen.name; i++)
{
Assert(DoLockModesConflict(intRelOpts[i].gen.lockmode,
@@ -640,6 +653,14 @@ initialize_reloptions(void)
j++;
}
+ for (i = 0; ternaryRelOpts[i].gen.name; i++)
+ {
+ relOpts[j] = &ternaryRelOpts[i].gen;
+ relOpts[j]->type = RELOPT_TYPE_TERNARY;
+ relOpts[j]->namelen = strlen(relOpts[j]->name);
+ j++;
+ }
+
for (i = 0; intRelOpts[i].gen.name; i++)
{
relOpts[j] = &intRelOpts[i].gen;
@@ -800,6 +821,9 @@ allocate_reloption(bits32 kinds, int type, const char *name, const char *desc,
case RELOPT_TYPE_BOOL:
size = sizeof(relopt_bool);
break;
+ case RELOPT_TYPE_TERNARY:
+ size = sizeof(relopt_ternary);
+ break;
case RELOPT_TYPE_INT:
size = sizeof(relopt_int);
break;
@@ -883,6 +907,54 @@ add_local_bool_reloption(local_relopts *relopts, const char *name,
add_local_reloption(relopts, (relopt_gen *) newoption, offset);
}
+/*
+ * init_ternary_reloption
+ * Allocate and initialize a new ternary reloption
+ */
+static relopt_ternary *
+init_ternary_reloption(bits32 kinds, const char *name, const char *desc,
+ ternary default_val, LOCKMODE lockmode)
+{
+ relopt_ternary *newoption;
+
+ newoption = (relopt_ternary *) allocate_reloption(kinds,
+ RELOPT_TYPE_TERNARY, name, desc, lockmode);
+ newoption->default_val = default_val;
+
+ return newoption;
+}
+
+/*
+ * add_ternary_reloption
+ * Add a new ternary reloption
+ */
+void
+add_ternary_reloption(bits32 kinds, const char *name, const char *desc,
+ ternary default_val, LOCKMODE lockmode)
+{
+ relopt_ternary *newoption = init_ternary_reloption(kinds, name, desc,
+ default_val, lockmode);
+
+ add_reloption((relopt_gen *) newoption);
+}
+
+/*
+ * add_local_ternary_reloption
+ * Add a new ternary local reloption
+ *
+ * 'offset' is offset of ternary-typed field.
+ */
+void
+add_local_ternary_reloption(local_relopts *relopts, const char *name,
+ const char *desc, ternary default_val,
+ int offset)
+{
+ relopt_ternary *newoption = init_ternary_reloption(RELOPT_KIND_LOCAL,
+ name, desc,
+ default_val, 0);
+
+ add_local_reloption(relopts, (relopt_gen *) newoption, offset);
+}
/*
* init_real_reloption
@@ -1617,6 +1689,18 @@ parse_one_reloption(relopt_value *option, char *text_str, int text_len,
option->gen->name, value)));
}
break;
+ case RELOPT_TYPE_TERNARY:
+ {
+ bool b;
+ parsed = parse_bool(value, &b);
+ option->values.ternary_val = b ? TERNARY_TRUE : TERNARY_FALSE;
+ if (validate && !parsed)
+ ereport(ERROR,
+ (errcode(ERRCODE_INVALID_PARAMETER_VALUE),
+ errmsg("invalid value for ternary option \"%s\": %s",
+ option->gen->name, value)));
+ }
+ break;
case RELOPT_TYPE_INT:
{
relopt_int *optint = (relopt_int *) option->gen;
@@ -1780,17 +1864,6 @@ fillRelOptions(void *rdopts, Size basesize,
char *itempos = ((char *) rdopts) + elems[j].offset;
char *string_val;
- /*
- * If isset_offset is provided, store whether the reloption is
- * set there.
- */
- if (elems[j].isset_offset > 0)
- {
- char *setpos = ((char *) rdopts) + elems[j].isset_offset;
-
- *(bool *) setpos = options[i].isset;
- }
-
switch (options[i].gen->type)
{
case RELOPT_TYPE_BOOL:
@@ -1798,6 +1871,11 @@ fillRelOptions(void *rdopts, Size basesize,
options[i].values.bool_val :
((relopt_bool *) options[i].gen)->default_val;
break;
+ case RELOPT_TYPE_TERNARY:
+ *(ternary *) itempos = options[i].isset ?
+ options[i].values.ternary_val :
+ ((relopt_ternary *) options[i].gen)->default_val;
+ break;
case RELOPT_TYPE_INT:
*(int *) itempos = options[i].isset ?
options[i].values.int_val :
@@ -1912,8 +1990,8 @@ default_reloptions(Datum reloptions, bool validate, relopt_kind kind)
offsetof(StdRdOptions, parallel_workers)},
{"vacuum_index_cleanup", RELOPT_TYPE_ENUM,
offsetof(StdRdOptions, vacuum_index_cleanup)},
- {"vacuum_truncate", RELOPT_TYPE_BOOL,
- offsetof(StdRdOptions, vacuum_truncate), offsetof(StdRdOptions, vacuum_truncate_set)},
+ {"vacuum_truncate", RELOPT_TYPE_TERNARY,
+ offsetof(StdRdOptions, vacuum_truncate)},
{"vacuum_max_eager_freeze_failure_rate", RELOPT_TYPE_REAL,
offsetof(StdRdOptions, vacuum_max_eager_freeze_failure_rate)}
};
@@ -1993,7 +2071,6 @@ build_local_reloptions(local_relopts *relopts, Datum options, bool validate)
elems[i].optname = opt->option->name;
elems[i].opttype = opt->option->type;
elems[i].offset = opt->offset;
- elems[i].isset_offset = 0; /* not supported for local relopts yet */
i++;
}
diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c
index 733ef40ae7..977babff54 100644
--- a/src/backend/commands/vacuum.c
+++ b/src/backend/commands/vacuum.c
@@ -2221,9 +2221,9 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams params,
{
StdRdOptions *opts = (StdRdOptions *) rel->rd_options;
- if (opts && opts->vacuum_truncate_set)
+ if (opts && opts->vacuum_truncate != TERNARY_UNSET)
{
- if (opts->vacuum_truncate)
+ if (opts->vacuum_truncate == TERNARY_TRUE)
params.truncate = VACOPTVALUE_ENABLED;
else
params.truncate = VACOPTVALUE_DISABLED;
diff --git a/src/include/access/reloptions.h b/src/include/access/reloptions.h
index a604a4702c..a436697658 100644
--- a/src/include/access/reloptions.h
+++ b/src/include/access/reloptions.h
@@ -29,6 +29,7 @@
typedef enum relopt_type
{
RELOPT_TYPE_BOOL,
+ RELOPT_TYPE_TERNARY, /* on, off, unset */
RELOPT_TYPE_INT,
RELOPT_TYPE_REAL,
RELOPT_TYPE_ENUM,
@@ -80,6 +81,7 @@ typedef struct relopt_value
union
{
bool bool_val;
+ ternary ternary_val;
int int_val;
double real_val;
int enum_val;
@@ -94,6 +96,12 @@ typedef struct relopt_bool
bool default_val;
} relopt_bool;
+typedef struct relopt_rernary
+{
+ relopt_gen gen;
+ int default_val;
+} relopt_ternary;
+
typedef struct relopt_int
{
relopt_gen gen;
@@ -152,19 +160,6 @@ typedef struct
const char *optname; /* option's name */
relopt_type opttype; /* option's datatype */
int offset; /* offset of field in result struct */
-
- /*
- * isset_offset is an optional offset of a field in the result struct that
- * stores whether the option is explicitly set for the relation or if it
- * just picked up the default value. In most cases, this can be
- * accomplished by giving the reloption a special out-of-range default
- * value (e.g., some integer reloptions use -2), but this isn't always
- * possible. For example, a Boolean reloption cannot be given an
- * out-of-range default, so we need another way to discover the source of
- * its value. This offset is only used if given a value greater than
- * zero.
- */
- int isset_offset;
} relopt_parse_elt;
/* Local reloption definition */
@@ -195,6 +190,8 @@ typedef struct local_relopts
extern relopt_kind add_reloption_kind(void);
extern void add_bool_reloption(bits32 kinds, const char *name, const char *desc,
bool default_val, LOCKMODE lockmode);
+extern void add_ternary_reloption(bits32 kinds, const char *name,
+ const char *desc, int default_val, LOCKMODE lockmode);
extern void add_int_reloption(bits32 kinds, const char *name, const char *desc,
int default_val, int min_val, int max_val,
LOCKMODE lockmode);
@@ -214,6 +211,9 @@ extern void register_reloptions_validator(local_relopts *relopts,
extern void add_local_bool_reloption(local_relopts *relopts, const char *name,
const char *desc, bool default_val,
int offset);
+extern void add_local_ternary_reloption(local_relopts *relopts,
+ const char *name, const char *desc,
+ ternary default_val, int offset);
extern void add_local_int_reloption(local_relopts *relopts, const char *name,
const char *desc, int default_val,
int min_val, int max_val, int offset);
diff --git a/src/include/c.h b/src/include/c.h
index 39022f8a9d..9c59ccccf8 100644
--- a/src/include/c.h
+++ b/src/include/c.h
@@ -488,6 +488,22 @@ typedef void (*pg_funcptr_t) (void);
#include <stdbool.h>
+/*
+ * ternary
+ * Boolean value with an extrea "unset" option
+ *
+ * Ternary data type is used in relation options that can be "true", "false" or
+ * "unset". Since relation options are used deep inside the PostgreSQL code,
+ * this type is declared globally.
+*/
+
+typedef enum ternary
+{
+ TERNARY_FALSE = 0,
+ TERNARY_TRUE = 1,
+ TERNARY_UNSET = -1
+} ternary;
+
/* ----------------------------------------------------------------
* Section 3: standard system types
diff --git a/src/include/utils/rel.h b/src/include/utils/rel.h
index b552359915..7346d618f9 100644
--- a/src/include/utils/rel.h
+++ b/src/include/utils/rel.h
@@ -346,8 +346,7 @@ typedef struct StdRdOptions
bool user_catalog_table; /* use as an additional catalog relation */
int parallel_workers; /* max number of parallel workers */
StdRdOptIndexCleanup vacuum_index_cleanup; /* controls index vacuuming */
- bool vacuum_truncate; /* enables vacuum to truncate a relation */
- bool vacuum_truncate_set; /* whether vacuum_truncate is set */
+ ternary vacuum_truncate; /* enables vacuum to truncate a relation */
/*
* Fraction of pages in a relation that vacuum can eagerly scan and fail
--
2.39.2
--nextPart5129932.5fSG56mABF
Content-Disposition: attachment;
filename="v2-0003-Add-alias-to-be-used-as-unset-state.patch"
Content-Transfer-Encoding: 7Bit
Content-Type: text/x-patch; charset="unicode-2-0-utf-8";
name="v2-0003-Add-alias-to-be-used-as-unset-state.patch"
^ permalink raw reply [nested|flat] 868+ messages in thread
* [PATCH v2a 2/4] Introduce ternary reloptions
@ 2025-09-04 14:41 Nikolay Shaplov <[email protected]>
0 siblings, 0 replies; 868+ messages in thread
From: Nikolay Shaplov @ 2025-09-04 14:41 UTC (permalink / raw)
Introduce ternary reloption as a replacement for current `vacuum_truncate`
implementation. Remove `vacuum_truncate_set` additional flag and using
`TERNARY_UNSET` value instead.
---
src/backend/access/common/reloptions.c | 129 ++++++++++++++++++++-----
src/backend/commands/vacuum.c | 4 +-
src/include/access/reloptions.h | 26 ++---
src/include/c.h | 16 +++
src/include/utils/rel.h | 3 +-
5 files changed, 135 insertions(+), 43 deletions(-)
diff --git a/src/backend/access/common/reloptions.c b/src/backend/access/common/reloptions.c
index 0af3fea68f..f543f11001 100644
--- a/src/backend/access/common/reloptions.c
+++ b/src/backend/access/common/reloptions.c
@@ -40,9 +40,9 @@
*
* To add an option:
*
- * (i) decide on a type (bool, integer, real, enum, string), name, default
- * value, upper and lower bounds (if applicable); for strings, consider a
- * validation routine.
+ * (i) decide on a type (bool, ternary, integer, real, enum, string), name,
+ * default value, upper and lower bounds (if applicable); for strings,
+ * consider a validation routine.
* (ii) add a record below (or use add_<type>_reloption).
* (iii) add it to the appropriate options struct (perhaps StdRdOptions)
* (iv) add it to the appropriate handling routine (perhaps
@@ -147,15 +147,6 @@ static relopt_bool boolRelOpts[] =
},
false
},
- {
- {
- "vacuum_truncate",
- "Enables vacuum to truncate empty pages at the end of this table",
- RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
- ShareUpdateExclusiveLock
- },
- true
- },
{
{
"deduplicate_items",
@@ -170,6 +161,21 @@ static relopt_bool boolRelOpts[] =
{{NULL}}
};
+static relopt_ternary ternaryRelOpts[] =
+{
+ {
+ {
+ "vacuum_truncate",
+ "Enables vacuum to truncate empty pages at the end of this table",
+ RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
+ ShareUpdateExclusiveLock
+ },
+ TERNARY_UNSET
+ },
+ /* list terminator */
+ {{NULL}}
+};
+
static relopt_int intRelOpts[] =
{
{
@@ -600,6 +606,13 @@ initialize_reloptions(void)
boolRelOpts[i].gen.lockmode));
j++;
}
+ for (i = 0; ternaryRelOpts[i].gen.name; i++)
+ {
+ Assert(DoLockModesConflict(ternaryRelOpts[i].gen.lockmode,
+ ternaryRelOpts[i].gen.lockmode));
+ j++;
+ }
+
for (i = 0; intRelOpts[i].gen.name; i++)
{
Assert(DoLockModesConflict(intRelOpts[i].gen.lockmode,
@@ -640,6 +653,14 @@ initialize_reloptions(void)
j++;
}
+ for (i = 0; ternaryRelOpts[i].gen.name; i++)
+ {
+ relOpts[j] = &ternaryRelOpts[i].gen;
+ relOpts[j]->type = RELOPT_TYPE_TERNARY;
+ relOpts[j]->namelen = strlen(relOpts[j]->name);
+ j++;
+ }
+
for (i = 0; intRelOpts[i].gen.name; i++)
{
relOpts[j] = &intRelOpts[i].gen;
@@ -800,6 +821,9 @@ allocate_reloption(bits32 kinds, int type, const char *name, const char *desc,
case RELOPT_TYPE_BOOL:
size = sizeof(relopt_bool);
break;
+ case RELOPT_TYPE_TERNARY:
+ size = sizeof(relopt_ternary);
+ break;
case RELOPT_TYPE_INT:
size = sizeof(relopt_int);
break;
@@ -883,6 +907,54 @@ add_local_bool_reloption(local_relopts *relopts, const char *name,
add_local_reloption(relopts, (relopt_gen *) newoption, offset);
}
+/*
+ * init_ternary_reloption
+ * Allocate and initialize a new ternary reloption
+ */
+static relopt_ternary *
+init_ternary_reloption(bits32 kinds, const char *name, const char *desc,
+ ternary default_val, LOCKMODE lockmode)
+{
+ relopt_ternary *newoption;
+
+ newoption = (relopt_ternary *) allocate_reloption(kinds,
+ RELOPT_TYPE_TERNARY, name, desc, lockmode);
+ newoption->default_val = default_val;
+
+ return newoption;
+}
+
+/*
+ * add_ternary_reloption
+ * Add a new ternary reloption
+ */
+void
+add_ternary_reloption(bits32 kinds, const char *name, const char *desc,
+ ternary default_val, LOCKMODE lockmode)
+{
+ relopt_ternary *newoption = init_ternary_reloption(kinds, name, desc,
+ default_val, lockmode);
+
+ add_reloption((relopt_gen *) newoption);
+}
+
+/*
+ * add_local_ternary_reloption
+ * Add a new ternary local reloption
+ *
+ * 'offset' is offset of ternary-typed field.
+ */
+void
+add_local_ternary_reloption(local_relopts *relopts, const char *name,
+ const char *desc, ternary default_val,
+ int offset)
+{
+ relopt_ternary *newoption = init_ternary_reloption(RELOPT_KIND_LOCAL,
+ name, desc,
+ default_val, 0);
+
+ add_local_reloption(relopts, (relopt_gen *) newoption, offset);
+}
/*
* init_real_reloption
@@ -1617,6 +1689,18 @@ parse_one_reloption(relopt_value *option, char *text_str, int text_len,
option->gen->name, value)));
}
break;
+ case RELOPT_TYPE_TERNARY:
+ {
+ bool b;
+ parsed = parse_bool(value, &b);
+ option->values.ternary_val = b ? TERNARY_TRUE : TERNARY_FALSE;
+ if (validate && !parsed)
+ ereport(ERROR,
+ (errcode(ERRCODE_INVALID_PARAMETER_VALUE),
+ errmsg("invalid value for ternary option \"%s\": %s",
+ option->gen->name, value)));
+ }
+ break;
case RELOPT_TYPE_INT:
{
relopt_int *optint = (relopt_int *) option->gen;
@@ -1780,17 +1864,6 @@ fillRelOptions(void *rdopts, Size basesize,
char *itempos = ((char *) rdopts) + elems[j].offset;
char *string_val;
- /*
- * If isset_offset is provided, store whether the reloption is
- * set there.
- */
- if (elems[j].isset_offset > 0)
- {
- char *setpos = ((char *) rdopts) + elems[j].isset_offset;
-
- *(bool *) setpos = options[i].isset;
- }
-
switch (options[i].gen->type)
{
case RELOPT_TYPE_BOOL:
@@ -1798,6 +1871,11 @@ fillRelOptions(void *rdopts, Size basesize,
options[i].values.bool_val :
((relopt_bool *) options[i].gen)->default_val;
break;
+ case RELOPT_TYPE_TERNARY:
+ *(ternary *) itempos = options[i].isset ?
+ options[i].values.ternary_val :
+ ((relopt_ternary *) options[i].gen)->default_val;
+ break;
case RELOPT_TYPE_INT:
*(int *) itempos = options[i].isset ?
options[i].values.int_val :
@@ -1912,8 +1990,8 @@ default_reloptions(Datum reloptions, bool validate, relopt_kind kind)
offsetof(StdRdOptions, parallel_workers)},
{"vacuum_index_cleanup", RELOPT_TYPE_ENUM,
offsetof(StdRdOptions, vacuum_index_cleanup)},
- {"vacuum_truncate", RELOPT_TYPE_BOOL,
- offsetof(StdRdOptions, vacuum_truncate), offsetof(StdRdOptions, vacuum_truncate_set)},
+ {"vacuum_truncate", RELOPT_TYPE_TERNARY,
+ offsetof(StdRdOptions, vacuum_truncate)},
{"vacuum_max_eager_freeze_failure_rate", RELOPT_TYPE_REAL,
offsetof(StdRdOptions, vacuum_max_eager_freeze_failure_rate)}
};
@@ -1993,7 +2071,6 @@ build_local_reloptions(local_relopts *relopts, Datum options, bool validate)
elems[i].optname = opt->option->name;
elems[i].opttype = opt->option->type;
elems[i].offset = opt->offset;
- elems[i].isset_offset = 0; /* not supported for local relopts yet */
i++;
}
diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c
index 733ef40ae7..977babff54 100644
--- a/src/backend/commands/vacuum.c
+++ b/src/backend/commands/vacuum.c
@@ -2221,9 +2221,9 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams params,
{
StdRdOptions *opts = (StdRdOptions *) rel->rd_options;
- if (opts && opts->vacuum_truncate_set)
+ if (opts && opts->vacuum_truncate != TERNARY_UNSET)
{
- if (opts->vacuum_truncate)
+ if (opts->vacuum_truncate == TERNARY_TRUE)
params.truncate = VACOPTVALUE_ENABLED;
else
params.truncate = VACOPTVALUE_DISABLED;
diff --git a/src/include/access/reloptions.h b/src/include/access/reloptions.h
index a604a4702c..a436697658 100644
--- a/src/include/access/reloptions.h
+++ b/src/include/access/reloptions.h
@@ -29,6 +29,7 @@
typedef enum relopt_type
{
RELOPT_TYPE_BOOL,
+ RELOPT_TYPE_TERNARY, /* on, off, unset */
RELOPT_TYPE_INT,
RELOPT_TYPE_REAL,
RELOPT_TYPE_ENUM,
@@ -80,6 +81,7 @@ typedef struct relopt_value
union
{
bool bool_val;
+ ternary ternary_val;
int int_val;
double real_val;
int enum_val;
@@ -94,6 +96,12 @@ typedef struct relopt_bool
bool default_val;
} relopt_bool;
+typedef struct relopt_rernary
+{
+ relopt_gen gen;
+ int default_val;
+} relopt_ternary;
+
typedef struct relopt_int
{
relopt_gen gen;
@@ -152,19 +160,6 @@ typedef struct
const char *optname; /* option's name */
relopt_type opttype; /* option's datatype */
int offset; /* offset of field in result struct */
-
- /*
- * isset_offset is an optional offset of a field in the result struct that
- * stores whether the option is explicitly set for the relation or if it
- * just picked up the default value. In most cases, this can be
- * accomplished by giving the reloption a special out-of-range default
- * value (e.g., some integer reloptions use -2), but this isn't always
- * possible. For example, a Boolean reloption cannot be given an
- * out-of-range default, so we need another way to discover the source of
- * its value. This offset is only used if given a value greater than
- * zero.
- */
- int isset_offset;
} relopt_parse_elt;
/* Local reloption definition */
@@ -195,6 +190,8 @@ typedef struct local_relopts
extern relopt_kind add_reloption_kind(void);
extern void add_bool_reloption(bits32 kinds, const char *name, const char *desc,
bool default_val, LOCKMODE lockmode);
+extern void add_ternary_reloption(bits32 kinds, const char *name,
+ const char *desc, int default_val, LOCKMODE lockmode);
extern void add_int_reloption(bits32 kinds, const char *name, const char *desc,
int default_val, int min_val, int max_val,
LOCKMODE lockmode);
@@ -214,6 +211,9 @@ extern void register_reloptions_validator(local_relopts *relopts,
extern void add_local_bool_reloption(local_relopts *relopts, const char *name,
const char *desc, bool default_val,
int offset);
+extern void add_local_ternary_reloption(local_relopts *relopts,
+ const char *name, const char *desc,
+ ternary default_val, int offset);
extern void add_local_int_reloption(local_relopts *relopts, const char *name,
const char *desc, int default_val,
int min_val, int max_val, int offset);
diff --git a/src/include/c.h b/src/include/c.h
index 39022f8a9d..9c59ccccf8 100644
--- a/src/include/c.h
+++ b/src/include/c.h
@@ -488,6 +488,22 @@ typedef void (*pg_funcptr_t) (void);
#include <stdbool.h>
+/*
+ * ternary
+ * Boolean value with an extrea "unset" option
+ *
+ * Ternary data type is used in relation options that can be "true", "false" or
+ * "unset". Since relation options are used deep inside the PostgreSQL code,
+ * this type is declared globally.
+*/
+
+typedef enum ternary
+{
+ TERNARY_FALSE = 0,
+ TERNARY_TRUE = 1,
+ TERNARY_UNSET = -1
+} ternary;
+
/* ----------------------------------------------------------------
* Section 3: standard system types
diff --git a/src/include/utils/rel.h b/src/include/utils/rel.h
index b552359915..7346d618f9 100644
--- a/src/include/utils/rel.h
+++ b/src/include/utils/rel.h
@@ -346,8 +346,7 @@ typedef struct StdRdOptions
bool user_catalog_table; /* use as an additional catalog relation */
int parallel_workers; /* max number of parallel workers */
StdRdOptIndexCleanup vacuum_index_cleanup; /* controls index vacuuming */
- bool vacuum_truncate; /* enables vacuum to truncate a relation */
- bool vacuum_truncate_set; /* whether vacuum_truncate is set */
+ ternary vacuum_truncate; /* enables vacuum to truncate a relation */
/*
* Fraction of pages in a relation that vacuum can eagerly scan and fail
--
2.39.2
--nextPart4155600.3ZeAukHxDK
Content-Disposition: attachment;
filename="v2a-0003-Add-alias-to-be-used-as-unset-state.patch"
Content-Transfer-Encoding: 7Bit
Content-Type: text/x-patch; charset="unicode-2-0-utf-8";
name="v2a-0003-Add-alias-to-be-used-as-unset-state.patch"
^ permalink raw reply [nested|flat] 868+ messages in thread
* [PATCH v2a 2/4] Introduce ternary reloptions
@ 2025-09-04 14:41 Nikolay Shaplov <[email protected]>
0 siblings, 0 replies; 868+ messages in thread
From: Nikolay Shaplov @ 2025-09-04 14:41 UTC (permalink / raw)
Introduce ternary reloption as a replacement for current `vacuum_truncate`
implementation. Remove `vacuum_truncate_set` additional flag and using
`TERNARY_UNSET` value instead.
---
src/backend/access/common/reloptions.c | 129 ++++++++++++++++++++-----
src/backend/commands/vacuum.c | 4 +-
src/include/access/reloptions.h | 26 ++---
src/include/c.h | 16 +++
src/include/utils/rel.h | 3 +-
5 files changed, 135 insertions(+), 43 deletions(-)
diff --git a/src/backend/access/common/reloptions.c b/src/backend/access/common/reloptions.c
index 0af3fea68f..f543f11001 100644
--- a/src/backend/access/common/reloptions.c
+++ b/src/backend/access/common/reloptions.c
@@ -40,9 +40,9 @@
*
* To add an option:
*
- * (i) decide on a type (bool, integer, real, enum, string), name, default
- * value, upper and lower bounds (if applicable); for strings, consider a
- * validation routine.
+ * (i) decide on a type (bool, ternary, integer, real, enum, string), name,
+ * default value, upper and lower bounds (if applicable); for strings,
+ * consider a validation routine.
* (ii) add a record below (or use add_<type>_reloption).
* (iii) add it to the appropriate options struct (perhaps StdRdOptions)
* (iv) add it to the appropriate handling routine (perhaps
@@ -147,15 +147,6 @@ static relopt_bool boolRelOpts[] =
},
false
},
- {
- {
- "vacuum_truncate",
- "Enables vacuum to truncate empty pages at the end of this table",
- RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
- ShareUpdateExclusiveLock
- },
- true
- },
{
{
"deduplicate_items",
@@ -170,6 +161,21 @@ static relopt_bool boolRelOpts[] =
{{NULL}}
};
+static relopt_ternary ternaryRelOpts[] =
+{
+ {
+ {
+ "vacuum_truncate",
+ "Enables vacuum to truncate empty pages at the end of this table",
+ RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
+ ShareUpdateExclusiveLock
+ },
+ TERNARY_UNSET
+ },
+ /* list terminator */
+ {{NULL}}
+};
+
static relopt_int intRelOpts[] =
{
{
@@ -600,6 +606,13 @@ initialize_reloptions(void)
boolRelOpts[i].gen.lockmode));
j++;
}
+ for (i = 0; ternaryRelOpts[i].gen.name; i++)
+ {
+ Assert(DoLockModesConflict(ternaryRelOpts[i].gen.lockmode,
+ ternaryRelOpts[i].gen.lockmode));
+ j++;
+ }
+
for (i = 0; intRelOpts[i].gen.name; i++)
{
Assert(DoLockModesConflict(intRelOpts[i].gen.lockmode,
@@ -640,6 +653,14 @@ initialize_reloptions(void)
j++;
}
+ for (i = 0; ternaryRelOpts[i].gen.name; i++)
+ {
+ relOpts[j] = &ternaryRelOpts[i].gen;
+ relOpts[j]->type = RELOPT_TYPE_TERNARY;
+ relOpts[j]->namelen = strlen(relOpts[j]->name);
+ j++;
+ }
+
for (i = 0; intRelOpts[i].gen.name; i++)
{
relOpts[j] = &intRelOpts[i].gen;
@@ -800,6 +821,9 @@ allocate_reloption(bits32 kinds, int type, const char *name, const char *desc,
case RELOPT_TYPE_BOOL:
size = sizeof(relopt_bool);
break;
+ case RELOPT_TYPE_TERNARY:
+ size = sizeof(relopt_ternary);
+ break;
case RELOPT_TYPE_INT:
size = sizeof(relopt_int);
break;
@@ -883,6 +907,54 @@ add_local_bool_reloption(local_relopts *relopts, const char *name,
add_local_reloption(relopts, (relopt_gen *) newoption, offset);
}
+/*
+ * init_ternary_reloption
+ * Allocate and initialize a new ternary reloption
+ */
+static relopt_ternary *
+init_ternary_reloption(bits32 kinds, const char *name, const char *desc,
+ ternary default_val, LOCKMODE lockmode)
+{
+ relopt_ternary *newoption;
+
+ newoption = (relopt_ternary *) allocate_reloption(kinds,
+ RELOPT_TYPE_TERNARY, name, desc, lockmode);
+ newoption->default_val = default_val;
+
+ return newoption;
+}
+
+/*
+ * add_ternary_reloption
+ * Add a new ternary reloption
+ */
+void
+add_ternary_reloption(bits32 kinds, const char *name, const char *desc,
+ ternary default_val, LOCKMODE lockmode)
+{
+ relopt_ternary *newoption = init_ternary_reloption(kinds, name, desc,
+ default_val, lockmode);
+
+ add_reloption((relopt_gen *) newoption);
+}
+
+/*
+ * add_local_ternary_reloption
+ * Add a new ternary local reloption
+ *
+ * 'offset' is offset of ternary-typed field.
+ */
+void
+add_local_ternary_reloption(local_relopts *relopts, const char *name,
+ const char *desc, ternary default_val,
+ int offset)
+{
+ relopt_ternary *newoption = init_ternary_reloption(RELOPT_KIND_LOCAL,
+ name, desc,
+ default_val, 0);
+
+ add_local_reloption(relopts, (relopt_gen *) newoption, offset);
+}
/*
* init_real_reloption
@@ -1617,6 +1689,18 @@ parse_one_reloption(relopt_value *option, char *text_str, int text_len,
option->gen->name, value)));
}
break;
+ case RELOPT_TYPE_TERNARY:
+ {
+ bool b;
+ parsed = parse_bool(value, &b);
+ option->values.ternary_val = b ? TERNARY_TRUE : TERNARY_FALSE;
+ if (validate && !parsed)
+ ereport(ERROR,
+ (errcode(ERRCODE_INVALID_PARAMETER_VALUE),
+ errmsg("invalid value for ternary option \"%s\": %s",
+ option->gen->name, value)));
+ }
+ break;
case RELOPT_TYPE_INT:
{
relopt_int *optint = (relopt_int *) option->gen;
@@ -1780,17 +1864,6 @@ fillRelOptions(void *rdopts, Size basesize,
char *itempos = ((char *) rdopts) + elems[j].offset;
char *string_val;
- /*
- * If isset_offset is provided, store whether the reloption is
- * set there.
- */
- if (elems[j].isset_offset > 0)
- {
- char *setpos = ((char *) rdopts) + elems[j].isset_offset;
-
- *(bool *) setpos = options[i].isset;
- }
-
switch (options[i].gen->type)
{
case RELOPT_TYPE_BOOL:
@@ -1798,6 +1871,11 @@ fillRelOptions(void *rdopts, Size basesize,
options[i].values.bool_val :
((relopt_bool *) options[i].gen)->default_val;
break;
+ case RELOPT_TYPE_TERNARY:
+ *(ternary *) itempos = options[i].isset ?
+ options[i].values.ternary_val :
+ ((relopt_ternary *) options[i].gen)->default_val;
+ break;
case RELOPT_TYPE_INT:
*(int *) itempos = options[i].isset ?
options[i].values.int_val :
@@ -1912,8 +1990,8 @@ default_reloptions(Datum reloptions, bool validate, relopt_kind kind)
offsetof(StdRdOptions, parallel_workers)},
{"vacuum_index_cleanup", RELOPT_TYPE_ENUM,
offsetof(StdRdOptions, vacuum_index_cleanup)},
- {"vacuum_truncate", RELOPT_TYPE_BOOL,
- offsetof(StdRdOptions, vacuum_truncate), offsetof(StdRdOptions, vacuum_truncate_set)},
+ {"vacuum_truncate", RELOPT_TYPE_TERNARY,
+ offsetof(StdRdOptions, vacuum_truncate)},
{"vacuum_max_eager_freeze_failure_rate", RELOPT_TYPE_REAL,
offsetof(StdRdOptions, vacuum_max_eager_freeze_failure_rate)}
};
@@ -1993,7 +2071,6 @@ build_local_reloptions(local_relopts *relopts, Datum options, bool validate)
elems[i].optname = opt->option->name;
elems[i].opttype = opt->option->type;
elems[i].offset = opt->offset;
- elems[i].isset_offset = 0; /* not supported for local relopts yet */
i++;
}
diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c
index 733ef40ae7..977babff54 100644
--- a/src/backend/commands/vacuum.c
+++ b/src/backend/commands/vacuum.c
@@ -2221,9 +2221,9 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams params,
{
StdRdOptions *opts = (StdRdOptions *) rel->rd_options;
- if (opts && opts->vacuum_truncate_set)
+ if (opts && opts->vacuum_truncate != TERNARY_UNSET)
{
- if (opts->vacuum_truncate)
+ if (opts->vacuum_truncate == TERNARY_TRUE)
params.truncate = VACOPTVALUE_ENABLED;
else
params.truncate = VACOPTVALUE_DISABLED;
diff --git a/src/include/access/reloptions.h b/src/include/access/reloptions.h
index a604a4702c..a436697658 100644
--- a/src/include/access/reloptions.h
+++ b/src/include/access/reloptions.h
@@ -29,6 +29,7 @@
typedef enum relopt_type
{
RELOPT_TYPE_BOOL,
+ RELOPT_TYPE_TERNARY, /* on, off, unset */
RELOPT_TYPE_INT,
RELOPT_TYPE_REAL,
RELOPT_TYPE_ENUM,
@@ -80,6 +81,7 @@ typedef struct relopt_value
union
{
bool bool_val;
+ ternary ternary_val;
int int_val;
double real_val;
int enum_val;
@@ -94,6 +96,12 @@ typedef struct relopt_bool
bool default_val;
} relopt_bool;
+typedef struct relopt_rernary
+{
+ relopt_gen gen;
+ int default_val;
+} relopt_ternary;
+
typedef struct relopt_int
{
relopt_gen gen;
@@ -152,19 +160,6 @@ typedef struct
const char *optname; /* option's name */
relopt_type opttype; /* option's datatype */
int offset; /* offset of field in result struct */
-
- /*
- * isset_offset is an optional offset of a field in the result struct that
- * stores whether the option is explicitly set for the relation or if it
- * just picked up the default value. In most cases, this can be
- * accomplished by giving the reloption a special out-of-range default
- * value (e.g., some integer reloptions use -2), but this isn't always
- * possible. For example, a Boolean reloption cannot be given an
- * out-of-range default, so we need another way to discover the source of
- * its value. This offset is only used if given a value greater than
- * zero.
- */
- int isset_offset;
} relopt_parse_elt;
/* Local reloption definition */
@@ -195,6 +190,8 @@ typedef struct local_relopts
extern relopt_kind add_reloption_kind(void);
extern void add_bool_reloption(bits32 kinds, const char *name, const char *desc,
bool default_val, LOCKMODE lockmode);
+extern void add_ternary_reloption(bits32 kinds, const char *name,
+ const char *desc, int default_val, LOCKMODE lockmode);
extern void add_int_reloption(bits32 kinds, const char *name, const char *desc,
int default_val, int min_val, int max_val,
LOCKMODE lockmode);
@@ -214,6 +211,9 @@ extern void register_reloptions_validator(local_relopts *relopts,
extern void add_local_bool_reloption(local_relopts *relopts, const char *name,
const char *desc, bool default_val,
int offset);
+extern void add_local_ternary_reloption(local_relopts *relopts,
+ const char *name, const char *desc,
+ ternary default_val, int offset);
extern void add_local_int_reloption(local_relopts *relopts, const char *name,
const char *desc, int default_val,
int min_val, int max_val, int offset);
diff --git a/src/include/c.h b/src/include/c.h
index 39022f8a9d..9c59ccccf8 100644
--- a/src/include/c.h
+++ b/src/include/c.h
@@ -488,6 +488,22 @@ typedef void (*pg_funcptr_t) (void);
#include <stdbool.h>
+/*
+ * ternary
+ * Boolean value with an extrea "unset" option
+ *
+ * Ternary data type is used in relation options that can be "true", "false" or
+ * "unset". Since relation options are used deep inside the PostgreSQL code,
+ * this type is declared globally.
+*/
+
+typedef enum ternary
+{
+ TERNARY_FALSE = 0,
+ TERNARY_TRUE = 1,
+ TERNARY_UNSET = -1
+} ternary;
+
/* ----------------------------------------------------------------
* Section 3: standard system types
diff --git a/src/include/utils/rel.h b/src/include/utils/rel.h
index b552359915..7346d618f9 100644
--- a/src/include/utils/rel.h
+++ b/src/include/utils/rel.h
@@ -346,8 +346,7 @@ typedef struct StdRdOptions
bool user_catalog_table; /* use as an additional catalog relation */
int parallel_workers; /* max number of parallel workers */
StdRdOptIndexCleanup vacuum_index_cleanup; /* controls index vacuuming */
- bool vacuum_truncate; /* enables vacuum to truncate a relation */
- bool vacuum_truncate_set; /* whether vacuum_truncate is set */
+ ternary vacuum_truncate; /* enables vacuum to truncate a relation */
/*
* Fraction of pages in a relation that vacuum can eagerly scan and fail
--
2.39.2
--nextPart4155600.3ZeAukHxDK
Content-Disposition: attachment;
filename="v2a-0003-Add-alias-to-be-used-as-unset-state.patch"
Content-Transfer-Encoding: 7Bit
Content-Type: text/x-patch; charset="unicode-2-0-utf-8";
name="v2a-0003-Add-alias-to-be-used-as-unset-state.patch"
^ permalink raw reply [nested|flat] 868+ messages in thread
* [PATCH v2a 2/4] Introduce ternary reloptions
@ 2025-09-04 14:41 Nikolay Shaplov <[email protected]>
0 siblings, 0 replies; 868+ messages in thread
From: Nikolay Shaplov @ 2025-09-04 14:41 UTC (permalink / raw)
Introduce ternary reloption as a replacement for current `vacuum_truncate`
implementation. Remove `vacuum_truncate_set` additional flag and using
`TERNARY_UNSET` value instead.
---
src/backend/access/common/reloptions.c | 129 ++++++++++++++++++++-----
src/backend/commands/vacuum.c | 4 +-
src/include/access/reloptions.h | 26 ++---
src/include/c.h | 16 +++
src/include/utils/rel.h | 3 +-
5 files changed, 135 insertions(+), 43 deletions(-)
diff --git a/src/backend/access/common/reloptions.c b/src/backend/access/common/reloptions.c
index 0af3fea68f..f543f11001 100644
--- a/src/backend/access/common/reloptions.c
+++ b/src/backend/access/common/reloptions.c
@@ -40,9 +40,9 @@
*
* To add an option:
*
- * (i) decide on a type (bool, integer, real, enum, string), name, default
- * value, upper and lower bounds (if applicable); for strings, consider a
- * validation routine.
+ * (i) decide on a type (bool, ternary, integer, real, enum, string), name,
+ * default value, upper and lower bounds (if applicable); for strings,
+ * consider a validation routine.
* (ii) add a record below (or use add_<type>_reloption).
* (iii) add it to the appropriate options struct (perhaps StdRdOptions)
* (iv) add it to the appropriate handling routine (perhaps
@@ -147,15 +147,6 @@ static relopt_bool boolRelOpts[] =
},
false
},
- {
- {
- "vacuum_truncate",
- "Enables vacuum to truncate empty pages at the end of this table",
- RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
- ShareUpdateExclusiveLock
- },
- true
- },
{
{
"deduplicate_items",
@@ -170,6 +161,21 @@ static relopt_bool boolRelOpts[] =
{{NULL}}
};
+static relopt_ternary ternaryRelOpts[] =
+{
+ {
+ {
+ "vacuum_truncate",
+ "Enables vacuum to truncate empty pages at the end of this table",
+ RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
+ ShareUpdateExclusiveLock
+ },
+ TERNARY_UNSET
+ },
+ /* list terminator */
+ {{NULL}}
+};
+
static relopt_int intRelOpts[] =
{
{
@@ -600,6 +606,13 @@ initialize_reloptions(void)
boolRelOpts[i].gen.lockmode));
j++;
}
+ for (i = 0; ternaryRelOpts[i].gen.name; i++)
+ {
+ Assert(DoLockModesConflict(ternaryRelOpts[i].gen.lockmode,
+ ternaryRelOpts[i].gen.lockmode));
+ j++;
+ }
+
for (i = 0; intRelOpts[i].gen.name; i++)
{
Assert(DoLockModesConflict(intRelOpts[i].gen.lockmode,
@@ -640,6 +653,14 @@ initialize_reloptions(void)
j++;
}
+ for (i = 0; ternaryRelOpts[i].gen.name; i++)
+ {
+ relOpts[j] = &ternaryRelOpts[i].gen;
+ relOpts[j]->type = RELOPT_TYPE_TERNARY;
+ relOpts[j]->namelen = strlen(relOpts[j]->name);
+ j++;
+ }
+
for (i = 0; intRelOpts[i].gen.name; i++)
{
relOpts[j] = &intRelOpts[i].gen;
@@ -800,6 +821,9 @@ allocate_reloption(bits32 kinds, int type, const char *name, const char *desc,
case RELOPT_TYPE_BOOL:
size = sizeof(relopt_bool);
break;
+ case RELOPT_TYPE_TERNARY:
+ size = sizeof(relopt_ternary);
+ break;
case RELOPT_TYPE_INT:
size = sizeof(relopt_int);
break;
@@ -883,6 +907,54 @@ add_local_bool_reloption(local_relopts *relopts, const char *name,
add_local_reloption(relopts, (relopt_gen *) newoption, offset);
}
+/*
+ * init_ternary_reloption
+ * Allocate and initialize a new ternary reloption
+ */
+static relopt_ternary *
+init_ternary_reloption(bits32 kinds, const char *name, const char *desc,
+ ternary default_val, LOCKMODE lockmode)
+{
+ relopt_ternary *newoption;
+
+ newoption = (relopt_ternary *) allocate_reloption(kinds,
+ RELOPT_TYPE_TERNARY, name, desc, lockmode);
+ newoption->default_val = default_val;
+
+ return newoption;
+}
+
+/*
+ * add_ternary_reloption
+ * Add a new ternary reloption
+ */
+void
+add_ternary_reloption(bits32 kinds, const char *name, const char *desc,
+ ternary default_val, LOCKMODE lockmode)
+{
+ relopt_ternary *newoption = init_ternary_reloption(kinds, name, desc,
+ default_val, lockmode);
+
+ add_reloption((relopt_gen *) newoption);
+}
+
+/*
+ * add_local_ternary_reloption
+ * Add a new ternary local reloption
+ *
+ * 'offset' is offset of ternary-typed field.
+ */
+void
+add_local_ternary_reloption(local_relopts *relopts, const char *name,
+ const char *desc, ternary default_val,
+ int offset)
+{
+ relopt_ternary *newoption = init_ternary_reloption(RELOPT_KIND_LOCAL,
+ name, desc,
+ default_val, 0);
+
+ add_local_reloption(relopts, (relopt_gen *) newoption, offset);
+}
/*
* init_real_reloption
@@ -1617,6 +1689,18 @@ parse_one_reloption(relopt_value *option, char *text_str, int text_len,
option->gen->name, value)));
}
break;
+ case RELOPT_TYPE_TERNARY:
+ {
+ bool b;
+ parsed = parse_bool(value, &b);
+ option->values.ternary_val = b ? TERNARY_TRUE : TERNARY_FALSE;
+ if (validate && !parsed)
+ ereport(ERROR,
+ (errcode(ERRCODE_INVALID_PARAMETER_VALUE),
+ errmsg("invalid value for ternary option \"%s\": %s",
+ option->gen->name, value)));
+ }
+ break;
case RELOPT_TYPE_INT:
{
relopt_int *optint = (relopt_int *) option->gen;
@@ -1780,17 +1864,6 @@ fillRelOptions(void *rdopts, Size basesize,
char *itempos = ((char *) rdopts) + elems[j].offset;
char *string_val;
- /*
- * If isset_offset is provided, store whether the reloption is
- * set there.
- */
- if (elems[j].isset_offset > 0)
- {
- char *setpos = ((char *) rdopts) + elems[j].isset_offset;
-
- *(bool *) setpos = options[i].isset;
- }
-
switch (options[i].gen->type)
{
case RELOPT_TYPE_BOOL:
@@ -1798,6 +1871,11 @@ fillRelOptions(void *rdopts, Size basesize,
options[i].values.bool_val :
((relopt_bool *) options[i].gen)->default_val;
break;
+ case RELOPT_TYPE_TERNARY:
+ *(ternary *) itempos = options[i].isset ?
+ options[i].values.ternary_val :
+ ((relopt_ternary *) options[i].gen)->default_val;
+ break;
case RELOPT_TYPE_INT:
*(int *) itempos = options[i].isset ?
options[i].values.int_val :
@@ -1912,8 +1990,8 @@ default_reloptions(Datum reloptions, bool validate, relopt_kind kind)
offsetof(StdRdOptions, parallel_workers)},
{"vacuum_index_cleanup", RELOPT_TYPE_ENUM,
offsetof(StdRdOptions, vacuum_index_cleanup)},
- {"vacuum_truncate", RELOPT_TYPE_BOOL,
- offsetof(StdRdOptions, vacuum_truncate), offsetof(StdRdOptions, vacuum_truncate_set)},
+ {"vacuum_truncate", RELOPT_TYPE_TERNARY,
+ offsetof(StdRdOptions, vacuum_truncate)},
{"vacuum_max_eager_freeze_failure_rate", RELOPT_TYPE_REAL,
offsetof(StdRdOptions, vacuum_max_eager_freeze_failure_rate)}
};
@@ -1993,7 +2071,6 @@ build_local_reloptions(local_relopts *relopts, Datum options, bool validate)
elems[i].optname = opt->option->name;
elems[i].opttype = opt->option->type;
elems[i].offset = opt->offset;
- elems[i].isset_offset = 0; /* not supported for local relopts yet */
i++;
}
diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c
index 733ef40ae7..977babff54 100644
--- a/src/backend/commands/vacuum.c
+++ b/src/backend/commands/vacuum.c
@@ -2221,9 +2221,9 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams params,
{
StdRdOptions *opts = (StdRdOptions *) rel->rd_options;
- if (opts && opts->vacuum_truncate_set)
+ if (opts && opts->vacuum_truncate != TERNARY_UNSET)
{
- if (opts->vacuum_truncate)
+ if (opts->vacuum_truncate == TERNARY_TRUE)
params.truncate = VACOPTVALUE_ENABLED;
else
params.truncate = VACOPTVALUE_DISABLED;
diff --git a/src/include/access/reloptions.h b/src/include/access/reloptions.h
index a604a4702c..a436697658 100644
--- a/src/include/access/reloptions.h
+++ b/src/include/access/reloptions.h
@@ -29,6 +29,7 @@
typedef enum relopt_type
{
RELOPT_TYPE_BOOL,
+ RELOPT_TYPE_TERNARY, /* on, off, unset */
RELOPT_TYPE_INT,
RELOPT_TYPE_REAL,
RELOPT_TYPE_ENUM,
@@ -80,6 +81,7 @@ typedef struct relopt_value
union
{
bool bool_val;
+ ternary ternary_val;
int int_val;
double real_val;
int enum_val;
@@ -94,6 +96,12 @@ typedef struct relopt_bool
bool default_val;
} relopt_bool;
+typedef struct relopt_rernary
+{
+ relopt_gen gen;
+ int default_val;
+} relopt_ternary;
+
typedef struct relopt_int
{
relopt_gen gen;
@@ -152,19 +160,6 @@ typedef struct
const char *optname; /* option's name */
relopt_type opttype; /* option's datatype */
int offset; /* offset of field in result struct */
-
- /*
- * isset_offset is an optional offset of a field in the result struct that
- * stores whether the option is explicitly set for the relation or if it
- * just picked up the default value. In most cases, this can be
- * accomplished by giving the reloption a special out-of-range default
- * value (e.g., some integer reloptions use -2), but this isn't always
- * possible. For example, a Boolean reloption cannot be given an
- * out-of-range default, so we need another way to discover the source of
- * its value. This offset is only used if given a value greater than
- * zero.
- */
- int isset_offset;
} relopt_parse_elt;
/* Local reloption definition */
@@ -195,6 +190,8 @@ typedef struct local_relopts
extern relopt_kind add_reloption_kind(void);
extern void add_bool_reloption(bits32 kinds, const char *name, const char *desc,
bool default_val, LOCKMODE lockmode);
+extern void add_ternary_reloption(bits32 kinds, const char *name,
+ const char *desc, int default_val, LOCKMODE lockmode);
extern void add_int_reloption(bits32 kinds, const char *name, const char *desc,
int default_val, int min_val, int max_val,
LOCKMODE lockmode);
@@ -214,6 +211,9 @@ extern void register_reloptions_validator(local_relopts *relopts,
extern void add_local_bool_reloption(local_relopts *relopts, const char *name,
const char *desc, bool default_val,
int offset);
+extern void add_local_ternary_reloption(local_relopts *relopts,
+ const char *name, const char *desc,
+ ternary default_val, int offset);
extern void add_local_int_reloption(local_relopts *relopts, const char *name,
const char *desc, int default_val,
int min_val, int max_val, int offset);
diff --git a/src/include/c.h b/src/include/c.h
index 39022f8a9d..9c59ccccf8 100644
--- a/src/include/c.h
+++ b/src/include/c.h
@@ -488,6 +488,22 @@ typedef void (*pg_funcptr_t) (void);
#include <stdbool.h>
+/*
+ * ternary
+ * Boolean value with an extrea "unset" option
+ *
+ * Ternary data type is used in relation options that can be "true", "false" or
+ * "unset". Since relation options are used deep inside the PostgreSQL code,
+ * this type is declared globally.
+*/
+
+typedef enum ternary
+{
+ TERNARY_FALSE = 0,
+ TERNARY_TRUE = 1,
+ TERNARY_UNSET = -1
+} ternary;
+
/* ----------------------------------------------------------------
* Section 3: standard system types
diff --git a/src/include/utils/rel.h b/src/include/utils/rel.h
index b552359915..7346d618f9 100644
--- a/src/include/utils/rel.h
+++ b/src/include/utils/rel.h
@@ -346,8 +346,7 @@ typedef struct StdRdOptions
bool user_catalog_table; /* use as an additional catalog relation */
int parallel_workers; /* max number of parallel workers */
StdRdOptIndexCleanup vacuum_index_cleanup; /* controls index vacuuming */
- bool vacuum_truncate; /* enables vacuum to truncate a relation */
- bool vacuum_truncate_set; /* whether vacuum_truncate is set */
+ ternary vacuum_truncate; /* enables vacuum to truncate a relation */
/*
* Fraction of pages in a relation that vacuum can eagerly scan and fail
--
2.39.2
--nextPart4155600.3ZeAukHxDK
Content-Disposition: attachment;
filename="v2a-0003-Add-alias-to-be-used-as-unset-state.patch"
Content-Transfer-Encoding: 7Bit
Content-Type: text/x-patch; charset="unicode-2-0-utf-8";
name="v2a-0003-Add-alias-to-be-used-as-unset-state.patch"
^ permalink raw reply [nested|flat] 868+ messages in thread
* [PATCH v2 2/4] Introduce ternary reloptions
@ 2025-09-04 14:41 Nikolay Shaplov <[email protected]>
0 siblings, 0 replies; 868+ messages in thread
From: Nikolay Shaplov @ 2025-09-04 14:41 UTC (permalink / raw)
Introduce ternary reloption as a replacement for current `vacuum_truncate`
implementation. Remove `vacuum_truncate_set` additional flag and using
`TERNARY_UNSET` value instead.
---
src/backend/access/common/reloptions.c | 129 ++++++++++++++++++++-----
src/backend/commands/vacuum.c | 4 +-
src/include/access/reloptions.h | 26 ++---
src/include/c.h | 16 +++
src/include/utils/rel.h | 3 +-
5 files changed, 135 insertions(+), 43 deletions(-)
diff --git a/src/backend/access/common/reloptions.c b/src/backend/access/common/reloptions.c
index 0af3fea68f..f543f11001 100644
--- a/src/backend/access/common/reloptions.c
+++ b/src/backend/access/common/reloptions.c
@@ -40,9 +40,9 @@
*
* To add an option:
*
- * (i) decide on a type (bool, integer, real, enum, string), name, default
- * value, upper and lower bounds (if applicable); for strings, consider a
- * validation routine.
+ * (i) decide on a type (bool, ternary, integer, real, enum, string), name,
+ * default value, upper and lower bounds (if applicable); for strings,
+ * consider a validation routine.
* (ii) add a record below (or use add_<type>_reloption).
* (iii) add it to the appropriate options struct (perhaps StdRdOptions)
* (iv) add it to the appropriate handling routine (perhaps
@@ -147,15 +147,6 @@ static relopt_bool boolRelOpts[] =
},
false
},
- {
- {
- "vacuum_truncate",
- "Enables vacuum to truncate empty pages at the end of this table",
- RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
- ShareUpdateExclusiveLock
- },
- true
- },
{
{
"deduplicate_items",
@@ -170,6 +161,21 @@ static relopt_bool boolRelOpts[] =
{{NULL}}
};
+static relopt_ternary ternaryRelOpts[] =
+{
+ {
+ {
+ "vacuum_truncate",
+ "Enables vacuum to truncate empty pages at the end of this table",
+ RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
+ ShareUpdateExclusiveLock
+ },
+ TERNARY_UNSET
+ },
+ /* list terminator */
+ {{NULL}}
+};
+
static relopt_int intRelOpts[] =
{
{
@@ -600,6 +606,13 @@ initialize_reloptions(void)
boolRelOpts[i].gen.lockmode));
j++;
}
+ for (i = 0; ternaryRelOpts[i].gen.name; i++)
+ {
+ Assert(DoLockModesConflict(ternaryRelOpts[i].gen.lockmode,
+ ternaryRelOpts[i].gen.lockmode));
+ j++;
+ }
+
for (i = 0; intRelOpts[i].gen.name; i++)
{
Assert(DoLockModesConflict(intRelOpts[i].gen.lockmode,
@@ -640,6 +653,14 @@ initialize_reloptions(void)
j++;
}
+ for (i = 0; ternaryRelOpts[i].gen.name; i++)
+ {
+ relOpts[j] = &ternaryRelOpts[i].gen;
+ relOpts[j]->type = RELOPT_TYPE_TERNARY;
+ relOpts[j]->namelen = strlen(relOpts[j]->name);
+ j++;
+ }
+
for (i = 0; intRelOpts[i].gen.name; i++)
{
relOpts[j] = &intRelOpts[i].gen;
@@ -800,6 +821,9 @@ allocate_reloption(bits32 kinds, int type, const char *name, const char *desc,
case RELOPT_TYPE_BOOL:
size = sizeof(relopt_bool);
break;
+ case RELOPT_TYPE_TERNARY:
+ size = sizeof(relopt_ternary);
+ break;
case RELOPT_TYPE_INT:
size = sizeof(relopt_int);
break;
@@ -883,6 +907,54 @@ add_local_bool_reloption(local_relopts *relopts, const char *name,
add_local_reloption(relopts, (relopt_gen *) newoption, offset);
}
+/*
+ * init_ternary_reloption
+ * Allocate and initialize a new ternary reloption
+ */
+static relopt_ternary *
+init_ternary_reloption(bits32 kinds, const char *name, const char *desc,
+ ternary default_val, LOCKMODE lockmode)
+{
+ relopt_ternary *newoption;
+
+ newoption = (relopt_ternary *) allocate_reloption(kinds,
+ RELOPT_TYPE_TERNARY, name, desc, lockmode);
+ newoption->default_val = default_val;
+
+ return newoption;
+}
+
+/*
+ * add_ternary_reloption
+ * Add a new ternary reloption
+ */
+void
+add_ternary_reloption(bits32 kinds, const char *name, const char *desc,
+ ternary default_val, LOCKMODE lockmode)
+{
+ relopt_ternary *newoption = init_ternary_reloption(kinds, name, desc,
+ default_val, lockmode);
+
+ add_reloption((relopt_gen *) newoption);
+}
+
+/*
+ * add_local_ternary_reloption
+ * Add a new ternary local reloption
+ *
+ * 'offset' is offset of ternary-typed field.
+ */
+void
+add_local_ternary_reloption(local_relopts *relopts, const char *name,
+ const char *desc, ternary default_val,
+ int offset)
+{
+ relopt_ternary *newoption = init_ternary_reloption(RELOPT_KIND_LOCAL,
+ name, desc,
+ default_val, 0);
+
+ add_local_reloption(relopts, (relopt_gen *) newoption, offset);
+}
/*
* init_real_reloption
@@ -1617,6 +1689,18 @@ parse_one_reloption(relopt_value *option, char *text_str, int text_len,
option->gen->name, value)));
}
break;
+ case RELOPT_TYPE_TERNARY:
+ {
+ bool b;
+ parsed = parse_bool(value, &b);
+ option->values.ternary_val = b ? TERNARY_TRUE : TERNARY_FALSE;
+ if (validate && !parsed)
+ ereport(ERROR,
+ (errcode(ERRCODE_INVALID_PARAMETER_VALUE),
+ errmsg("invalid value for ternary option \"%s\": %s",
+ option->gen->name, value)));
+ }
+ break;
case RELOPT_TYPE_INT:
{
relopt_int *optint = (relopt_int *) option->gen;
@@ -1780,17 +1864,6 @@ fillRelOptions(void *rdopts, Size basesize,
char *itempos = ((char *) rdopts) + elems[j].offset;
char *string_val;
- /*
- * If isset_offset is provided, store whether the reloption is
- * set there.
- */
- if (elems[j].isset_offset > 0)
- {
- char *setpos = ((char *) rdopts) + elems[j].isset_offset;
-
- *(bool *) setpos = options[i].isset;
- }
-
switch (options[i].gen->type)
{
case RELOPT_TYPE_BOOL:
@@ -1798,6 +1871,11 @@ fillRelOptions(void *rdopts, Size basesize,
options[i].values.bool_val :
((relopt_bool *) options[i].gen)->default_val;
break;
+ case RELOPT_TYPE_TERNARY:
+ *(ternary *) itempos = options[i].isset ?
+ options[i].values.ternary_val :
+ ((relopt_ternary *) options[i].gen)->default_val;
+ break;
case RELOPT_TYPE_INT:
*(int *) itempos = options[i].isset ?
options[i].values.int_val :
@@ -1912,8 +1990,8 @@ default_reloptions(Datum reloptions, bool validate, relopt_kind kind)
offsetof(StdRdOptions, parallel_workers)},
{"vacuum_index_cleanup", RELOPT_TYPE_ENUM,
offsetof(StdRdOptions, vacuum_index_cleanup)},
- {"vacuum_truncate", RELOPT_TYPE_BOOL,
- offsetof(StdRdOptions, vacuum_truncate), offsetof(StdRdOptions, vacuum_truncate_set)},
+ {"vacuum_truncate", RELOPT_TYPE_TERNARY,
+ offsetof(StdRdOptions, vacuum_truncate)},
{"vacuum_max_eager_freeze_failure_rate", RELOPT_TYPE_REAL,
offsetof(StdRdOptions, vacuum_max_eager_freeze_failure_rate)}
};
@@ -1993,7 +2071,6 @@ build_local_reloptions(local_relopts *relopts, Datum options, bool validate)
elems[i].optname = opt->option->name;
elems[i].opttype = opt->option->type;
elems[i].offset = opt->offset;
- elems[i].isset_offset = 0; /* not supported for local relopts yet */
i++;
}
diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c
index 733ef40ae7..977babff54 100644
--- a/src/backend/commands/vacuum.c
+++ b/src/backend/commands/vacuum.c
@@ -2221,9 +2221,9 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams params,
{
StdRdOptions *opts = (StdRdOptions *) rel->rd_options;
- if (opts && opts->vacuum_truncate_set)
+ if (opts && opts->vacuum_truncate != TERNARY_UNSET)
{
- if (opts->vacuum_truncate)
+ if (opts->vacuum_truncate == TERNARY_TRUE)
params.truncate = VACOPTVALUE_ENABLED;
else
params.truncate = VACOPTVALUE_DISABLED;
diff --git a/src/include/access/reloptions.h b/src/include/access/reloptions.h
index a604a4702c..a436697658 100644
--- a/src/include/access/reloptions.h
+++ b/src/include/access/reloptions.h
@@ -29,6 +29,7 @@
typedef enum relopt_type
{
RELOPT_TYPE_BOOL,
+ RELOPT_TYPE_TERNARY, /* on, off, unset */
RELOPT_TYPE_INT,
RELOPT_TYPE_REAL,
RELOPT_TYPE_ENUM,
@@ -80,6 +81,7 @@ typedef struct relopt_value
union
{
bool bool_val;
+ ternary ternary_val;
int int_val;
double real_val;
int enum_val;
@@ -94,6 +96,12 @@ typedef struct relopt_bool
bool default_val;
} relopt_bool;
+typedef struct relopt_rernary
+{
+ relopt_gen gen;
+ int default_val;
+} relopt_ternary;
+
typedef struct relopt_int
{
relopt_gen gen;
@@ -152,19 +160,6 @@ typedef struct
const char *optname; /* option's name */
relopt_type opttype; /* option's datatype */
int offset; /* offset of field in result struct */
-
- /*
- * isset_offset is an optional offset of a field in the result struct that
- * stores whether the option is explicitly set for the relation or if it
- * just picked up the default value. In most cases, this can be
- * accomplished by giving the reloption a special out-of-range default
- * value (e.g., some integer reloptions use -2), but this isn't always
- * possible. For example, a Boolean reloption cannot be given an
- * out-of-range default, so we need another way to discover the source of
- * its value. This offset is only used if given a value greater than
- * zero.
- */
- int isset_offset;
} relopt_parse_elt;
/* Local reloption definition */
@@ -195,6 +190,8 @@ typedef struct local_relopts
extern relopt_kind add_reloption_kind(void);
extern void add_bool_reloption(bits32 kinds, const char *name, const char *desc,
bool default_val, LOCKMODE lockmode);
+extern void add_ternary_reloption(bits32 kinds, const char *name,
+ const char *desc, int default_val, LOCKMODE lockmode);
extern void add_int_reloption(bits32 kinds, const char *name, const char *desc,
int default_val, int min_val, int max_val,
LOCKMODE lockmode);
@@ -214,6 +211,9 @@ extern void register_reloptions_validator(local_relopts *relopts,
extern void add_local_bool_reloption(local_relopts *relopts, const char *name,
const char *desc, bool default_val,
int offset);
+extern void add_local_ternary_reloption(local_relopts *relopts,
+ const char *name, const char *desc,
+ ternary default_val, int offset);
extern void add_local_int_reloption(local_relopts *relopts, const char *name,
const char *desc, int default_val,
int min_val, int max_val, int offset);
diff --git a/src/include/c.h b/src/include/c.h
index 39022f8a9d..9c59ccccf8 100644
--- a/src/include/c.h
+++ b/src/include/c.h
@@ -488,6 +488,22 @@ typedef void (*pg_funcptr_t) (void);
#include <stdbool.h>
+/*
+ * ternary
+ * Boolean value with an extrea "unset" option
+ *
+ * Ternary data type is used in relation options that can be "true", "false" or
+ * "unset". Since relation options are used deep inside the PostgreSQL code,
+ * this type is declared globally.
+*/
+
+typedef enum ternary
+{
+ TERNARY_FALSE = 0,
+ TERNARY_TRUE = 1,
+ TERNARY_UNSET = -1
+} ternary;
+
/* ----------------------------------------------------------------
* Section 3: standard system types
diff --git a/src/include/utils/rel.h b/src/include/utils/rel.h
index b552359915..7346d618f9 100644
--- a/src/include/utils/rel.h
+++ b/src/include/utils/rel.h
@@ -346,8 +346,7 @@ typedef struct StdRdOptions
bool user_catalog_table; /* use as an additional catalog relation */
int parallel_workers; /* max number of parallel workers */
StdRdOptIndexCleanup vacuum_index_cleanup; /* controls index vacuuming */
- bool vacuum_truncate; /* enables vacuum to truncate a relation */
- bool vacuum_truncate_set; /* whether vacuum_truncate is set */
+ ternary vacuum_truncate; /* enables vacuum to truncate a relation */
/*
* Fraction of pages in a relation that vacuum can eagerly scan and fail
--
2.39.2
--nextPart5129932.5fSG56mABF
Content-Disposition: attachment;
filename="v2-0003-Add-alias-to-be-used-as-unset-state.patch"
Content-Transfer-Encoding: 7Bit
Content-Type: text/x-patch; charset="unicode-2-0-utf-8";
name="v2-0003-Add-alias-to-be-used-as-unset-state.patch"
^ permalink raw reply [nested|flat] 868+ messages in thread
* [PATCH v2a 2/4] Introduce ternary reloptions
@ 2025-09-04 14:41 Nikolay Shaplov <[email protected]>
0 siblings, 0 replies; 868+ messages in thread
From: Nikolay Shaplov @ 2025-09-04 14:41 UTC (permalink / raw)
Introduce ternary reloption as a replacement for current `vacuum_truncate`
implementation. Remove `vacuum_truncate_set` additional flag and using
`TERNARY_UNSET` value instead.
---
src/backend/access/common/reloptions.c | 129 ++++++++++++++++++++-----
src/backend/commands/vacuum.c | 4 +-
src/include/access/reloptions.h | 26 ++---
src/include/c.h | 16 +++
src/include/utils/rel.h | 3 +-
5 files changed, 135 insertions(+), 43 deletions(-)
diff --git a/src/backend/access/common/reloptions.c b/src/backend/access/common/reloptions.c
index 0af3fea68f..f543f11001 100644
--- a/src/backend/access/common/reloptions.c
+++ b/src/backend/access/common/reloptions.c
@@ -40,9 +40,9 @@
*
* To add an option:
*
- * (i) decide on a type (bool, integer, real, enum, string), name, default
- * value, upper and lower bounds (if applicable); for strings, consider a
- * validation routine.
+ * (i) decide on a type (bool, ternary, integer, real, enum, string), name,
+ * default value, upper and lower bounds (if applicable); for strings,
+ * consider a validation routine.
* (ii) add a record below (or use add_<type>_reloption).
* (iii) add it to the appropriate options struct (perhaps StdRdOptions)
* (iv) add it to the appropriate handling routine (perhaps
@@ -147,15 +147,6 @@ static relopt_bool boolRelOpts[] =
},
false
},
- {
- {
- "vacuum_truncate",
- "Enables vacuum to truncate empty pages at the end of this table",
- RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
- ShareUpdateExclusiveLock
- },
- true
- },
{
{
"deduplicate_items",
@@ -170,6 +161,21 @@ static relopt_bool boolRelOpts[] =
{{NULL}}
};
+static relopt_ternary ternaryRelOpts[] =
+{
+ {
+ {
+ "vacuum_truncate",
+ "Enables vacuum to truncate empty pages at the end of this table",
+ RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
+ ShareUpdateExclusiveLock
+ },
+ TERNARY_UNSET
+ },
+ /* list terminator */
+ {{NULL}}
+};
+
static relopt_int intRelOpts[] =
{
{
@@ -600,6 +606,13 @@ initialize_reloptions(void)
boolRelOpts[i].gen.lockmode));
j++;
}
+ for (i = 0; ternaryRelOpts[i].gen.name; i++)
+ {
+ Assert(DoLockModesConflict(ternaryRelOpts[i].gen.lockmode,
+ ternaryRelOpts[i].gen.lockmode));
+ j++;
+ }
+
for (i = 0; intRelOpts[i].gen.name; i++)
{
Assert(DoLockModesConflict(intRelOpts[i].gen.lockmode,
@@ -640,6 +653,14 @@ initialize_reloptions(void)
j++;
}
+ for (i = 0; ternaryRelOpts[i].gen.name; i++)
+ {
+ relOpts[j] = &ternaryRelOpts[i].gen;
+ relOpts[j]->type = RELOPT_TYPE_TERNARY;
+ relOpts[j]->namelen = strlen(relOpts[j]->name);
+ j++;
+ }
+
for (i = 0; intRelOpts[i].gen.name; i++)
{
relOpts[j] = &intRelOpts[i].gen;
@@ -800,6 +821,9 @@ allocate_reloption(bits32 kinds, int type, const char *name, const char *desc,
case RELOPT_TYPE_BOOL:
size = sizeof(relopt_bool);
break;
+ case RELOPT_TYPE_TERNARY:
+ size = sizeof(relopt_ternary);
+ break;
case RELOPT_TYPE_INT:
size = sizeof(relopt_int);
break;
@@ -883,6 +907,54 @@ add_local_bool_reloption(local_relopts *relopts, const char *name,
add_local_reloption(relopts, (relopt_gen *) newoption, offset);
}
+/*
+ * init_ternary_reloption
+ * Allocate and initialize a new ternary reloption
+ */
+static relopt_ternary *
+init_ternary_reloption(bits32 kinds, const char *name, const char *desc,
+ ternary default_val, LOCKMODE lockmode)
+{
+ relopt_ternary *newoption;
+
+ newoption = (relopt_ternary *) allocate_reloption(kinds,
+ RELOPT_TYPE_TERNARY, name, desc, lockmode);
+ newoption->default_val = default_val;
+
+ return newoption;
+}
+
+/*
+ * add_ternary_reloption
+ * Add a new ternary reloption
+ */
+void
+add_ternary_reloption(bits32 kinds, const char *name, const char *desc,
+ ternary default_val, LOCKMODE lockmode)
+{
+ relopt_ternary *newoption = init_ternary_reloption(kinds, name, desc,
+ default_val, lockmode);
+
+ add_reloption((relopt_gen *) newoption);
+}
+
+/*
+ * add_local_ternary_reloption
+ * Add a new ternary local reloption
+ *
+ * 'offset' is offset of ternary-typed field.
+ */
+void
+add_local_ternary_reloption(local_relopts *relopts, const char *name,
+ const char *desc, ternary default_val,
+ int offset)
+{
+ relopt_ternary *newoption = init_ternary_reloption(RELOPT_KIND_LOCAL,
+ name, desc,
+ default_val, 0);
+
+ add_local_reloption(relopts, (relopt_gen *) newoption, offset);
+}
/*
* init_real_reloption
@@ -1617,6 +1689,18 @@ parse_one_reloption(relopt_value *option, char *text_str, int text_len,
option->gen->name, value)));
}
break;
+ case RELOPT_TYPE_TERNARY:
+ {
+ bool b;
+ parsed = parse_bool(value, &b);
+ option->values.ternary_val = b ? TERNARY_TRUE : TERNARY_FALSE;
+ if (validate && !parsed)
+ ereport(ERROR,
+ (errcode(ERRCODE_INVALID_PARAMETER_VALUE),
+ errmsg("invalid value for ternary option \"%s\": %s",
+ option->gen->name, value)));
+ }
+ break;
case RELOPT_TYPE_INT:
{
relopt_int *optint = (relopt_int *) option->gen;
@@ -1780,17 +1864,6 @@ fillRelOptions(void *rdopts, Size basesize,
char *itempos = ((char *) rdopts) + elems[j].offset;
char *string_val;
- /*
- * If isset_offset is provided, store whether the reloption is
- * set there.
- */
- if (elems[j].isset_offset > 0)
- {
- char *setpos = ((char *) rdopts) + elems[j].isset_offset;
-
- *(bool *) setpos = options[i].isset;
- }
-
switch (options[i].gen->type)
{
case RELOPT_TYPE_BOOL:
@@ -1798,6 +1871,11 @@ fillRelOptions(void *rdopts, Size basesize,
options[i].values.bool_val :
((relopt_bool *) options[i].gen)->default_val;
break;
+ case RELOPT_TYPE_TERNARY:
+ *(ternary *) itempos = options[i].isset ?
+ options[i].values.ternary_val :
+ ((relopt_ternary *) options[i].gen)->default_val;
+ break;
case RELOPT_TYPE_INT:
*(int *) itempos = options[i].isset ?
options[i].values.int_val :
@@ -1912,8 +1990,8 @@ default_reloptions(Datum reloptions, bool validate, relopt_kind kind)
offsetof(StdRdOptions, parallel_workers)},
{"vacuum_index_cleanup", RELOPT_TYPE_ENUM,
offsetof(StdRdOptions, vacuum_index_cleanup)},
- {"vacuum_truncate", RELOPT_TYPE_BOOL,
- offsetof(StdRdOptions, vacuum_truncate), offsetof(StdRdOptions, vacuum_truncate_set)},
+ {"vacuum_truncate", RELOPT_TYPE_TERNARY,
+ offsetof(StdRdOptions, vacuum_truncate)},
{"vacuum_max_eager_freeze_failure_rate", RELOPT_TYPE_REAL,
offsetof(StdRdOptions, vacuum_max_eager_freeze_failure_rate)}
};
@@ -1993,7 +2071,6 @@ build_local_reloptions(local_relopts *relopts, Datum options, bool validate)
elems[i].optname = opt->option->name;
elems[i].opttype = opt->option->type;
elems[i].offset = opt->offset;
- elems[i].isset_offset = 0; /* not supported for local relopts yet */
i++;
}
diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c
index 733ef40ae7..977babff54 100644
--- a/src/backend/commands/vacuum.c
+++ b/src/backend/commands/vacuum.c
@@ -2221,9 +2221,9 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams params,
{
StdRdOptions *opts = (StdRdOptions *) rel->rd_options;
- if (opts && opts->vacuum_truncate_set)
+ if (opts && opts->vacuum_truncate != TERNARY_UNSET)
{
- if (opts->vacuum_truncate)
+ if (opts->vacuum_truncate == TERNARY_TRUE)
params.truncate = VACOPTVALUE_ENABLED;
else
params.truncate = VACOPTVALUE_DISABLED;
diff --git a/src/include/access/reloptions.h b/src/include/access/reloptions.h
index a604a4702c..a436697658 100644
--- a/src/include/access/reloptions.h
+++ b/src/include/access/reloptions.h
@@ -29,6 +29,7 @@
typedef enum relopt_type
{
RELOPT_TYPE_BOOL,
+ RELOPT_TYPE_TERNARY, /* on, off, unset */
RELOPT_TYPE_INT,
RELOPT_TYPE_REAL,
RELOPT_TYPE_ENUM,
@@ -80,6 +81,7 @@ typedef struct relopt_value
union
{
bool bool_val;
+ ternary ternary_val;
int int_val;
double real_val;
int enum_val;
@@ -94,6 +96,12 @@ typedef struct relopt_bool
bool default_val;
} relopt_bool;
+typedef struct relopt_rernary
+{
+ relopt_gen gen;
+ int default_val;
+} relopt_ternary;
+
typedef struct relopt_int
{
relopt_gen gen;
@@ -152,19 +160,6 @@ typedef struct
const char *optname; /* option's name */
relopt_type opttype; /* option's datatype */
int offset; /* offset of field in result struct */
-
- /*
- * isset_offset is an optional offset of a field in the result struct that
- * stores whether the option is explicitly set for the relation or if it
- * just picked up the default value. In most cases, this can be
- * accomplished by giving the reloption a special out-of-range default
- * value (e.g., some integer reloptions use -2), but this isn't always
- * possible. For example, a Boolean reloption cannot be given an
- * out-of-range default, so we need another way to discover the source of
- * its value. This offset is only used if given a value greater than
- * zero.
- */
- int isset_offset;
} relopt_parse_elt;
/* Local reloption definition */
@@ -195,6 +190,8 @@ typedef struct local_relopts
extern relopt_kind add_reloption_kind(void);
extern void add_bool_reloption(bits32 kinds, const char *name, const char *desc,
bool default_val, LOCKMODE lockmode);
+extern void add_ternary_reloption(bits32 kinds, const char *name,
+ const char *desc, int default_val, LOCKMODE lockmode);
extern void add_int_reloption(bits32 kinds, const char *name, const char *desc,
int default_val, int min_val, int max_val,
LOCKMODE lockmode);
@@ -214,6 +211,9 @@ extern void register_reloptions_validator(local_relopts *relopts,
extern void add_local_bool_reloption(local_relopts *relopts, const char *name,
const char *desc, bool default_val,
int offset);
+extern void add_local_ternary_reloption(local_relopts *relopts,
+ const char *name, const char *desc,
+ ternary default_val, int offset);
extern void add_local_int_reloption(local_relopts *relopts, const char *name,
const char *desc, int default_val,
int min_val, int max_val, int offset);
diff --git a/src/include/c.h b/src/include/c.h
index 39022f8a9d..9c59ccccf8 100644
--- a/src/include/c.h
+++ b/src/include/c.h
@@ -488,6 +488,22 @@ typedef void (*pg_funcptr_t) (void);
#include <stdbool.h>
+/*
+ * ternary
+ * Boolean value with an extrea "unset" option
+ *
+ * Ternary data type is used in relation options that can be "true", "false" or
+ * "unset". Since relation options are used deep inside the PostgreSQL code,
+ * this type is declared globally.
+*/
+
+typedef enum ternary
+{
+ TERNARY_FALSE = 0,
+ TERNARY_TRUE = 1,
+ TERNARY_UNSET = -1
+} ternary;
+
/* ----------------------------------------------------------------
* Section 3: standard system types
diff --git a/src/include/utils/rel.h b/src/include/utils/rel.h
index b552359915..7346d618f9 100644
--- a/src/include/utils/rel.h
+++ b/src/include/utils/rel.h
@@ -346,8 +346,7 @@ typedef struct StdRdOptions
bool user_catalog_table; /* use as an additional catalog relation */
int parallel_workers; /* max number of parallel workers */
StdRdOptIndexCleanup vacuum_index_cleanup; /* controls index vacuuming */
- bool vacuum_truncate; /* enables vacuum to truncate a relation */
- bool vacuum_truncate_set; /* whether vacuum_truncate is set */
+ ternary vacuum_truncate; /* enables vacuum to truncate a relation */
/*
* Fraction of pages in a relation that vacuum can eagerly scan and fail
--
2.39.2
--nextPart4155600.3ZeAukHxDK
Content-Disposition: attachment;
filename="v2a-0003-Add-alias-to-be-used-as-unset-state.patch"
Content-Transfer-Encoding: 7Bit
Content-Type: text/x-patch; charset="unicode-2-0-utf-8";
name="v2a-0003-Add-alias-to-be-used-as-unset-state.patch"
^ permalink raw reply [nested|flat] 868+ messages in thread
* [PATCH v2a 2/4] Introduce ternary reloptions
@ 2025-09-04 14:41 Nikolay Shaplov <[email protected]>
0 siblings, 0 replies; 868+ messages in thread
From: Nikolay Shaplov @ 2025-09-04 14:41 UTC (permalink / raw)
Introduce ternary reloption as a replacement for current `vacuum_truncate`
implementation. Remove `vacuum_truncate_set` additional flag and using
`TERNARY_UNSET` value instead.
---
src/backend/access/common/reloptions.c | 129 ++++++++++++++++++++-----
src/backend/commands/vacuum.c | 4 +-
src/include/access/reloptions.h | 26 ++---
src/include/c.h | 16 +++
src/include/utils/rel.h | 3 +-
5 files changed, 135 insertions(+), 43 deletions(-)
diff --git a/src/backend/access/common/reloptions.c b/src/backend/access/common/reloptions.c
index 0af3fea68f..f543f11001 100644
--- a/src/backend/access/common/reloptions.c
+++ b/src/backend/access/common/reloptions.c
@@ -40,9 +40,9 @@
*
* To add an option:
*
- * (i) decide on a type (bool, integer, real, enum, string), name, default
- * value, upper and lower bounds (if applicable); for strings, consider a
- * validation routine.
+ * (i) decide on a type (bool, ternary, integer, real, enum, string), name,
+ * default value, upper and lower bounds (if applicable); for strings,
+ * consider a validation routine.
* (ii) add a record below (or use add_<type>_reloption).
* (iii) add it to the appropriate options struct (perhaps StdRdOptions)
* (iv) add it to the appropriate handling routine (perhaps
@@ -147,15 +147,6 @@ static relopt_bool boolRelOpts[] =
},
false
},
- {
- {
- "vacuum_truncate",
- "Enables vacuum to truncate empty pages at the end of this table",
- RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
- ShareUpdateExclusiveLock
- },
- true
- },
{
{
"deduplicate_items",
@@ -170,6 +161,21 @@ static relopt_bool boolRelOpts[] =
{{NULL}}
};
+static relopt_ternary ternaryRelOpts[] =
+{
+ {
+ {
+ "vacuum_truncate",
+ "Enables vacuum to truncate empty pages at the end of this table",
+ RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
+ ShareUpdateExclusiveLock
+ },
+ TERNARY_UNSET
+ },
+ /* list terminator */
+ {{NULL}}
+};
+
static relopt_int intRelOpts[] =
{
{
@@ -600,6 +606,13 @@ initialize_reloptions(void)
boolRelOpts[i].gen.lockmode));
j++;
}
+ for (i = 0; ternaryRelOpts[i].gen.name; i++)
+ {
+ Assert(DoLockModesConflict(ternaryRelOpts[i].gen.lockmode,
+ ternaryRelOpts[i].gen.lockmode));
+ j++;
+ }
+
for (i = 0; intRelOpts[i].gen.name; i++)
{
Assert(DoLockModesConflict(intRelOpts[i].gen.lockmode,
@@ -640,6 +653,14 @@ initialize_reloptions(void)
j++;
}
+ for (i = 0; ternaryRelOpts[i].gen.name; i++)
+ {
+ relOpts[j] = &ternaryRelOpts[i].gen;
+ relOpts[j]->type = RELOPT_TYPE_TERNARY;
+ relOpts[j]->namelen = strlen(relOpts[j]->name);
+ j++;
+ }
+
for (i = 0; intRelOpts[i].gen.name; i++)
{
relOpts[j] = &intRelOpts[i].gen;
@@ -800,6 +821,9 @@ allocate_reloption(bits32 kinds, int type, const char *name, const char *desc,
case RELOPT_TYPE_BOOL:
size = sizeof(relopt_bool);
break;
+ case RELOPT_TYPE_TERNARY:
+ size = sizeof(relopt_ternary);
+ break;
case RELOPT_TYPE_INT:
size = sizeof(relopt_int);
break;
@@ -883,6 +907,54 @@ add_local_bool_reloption(local_relopts *relopts, const char *name,
add_local_reloption(relopts, (relopt_gen *) newoption, offset);
}
+/*
+ * init_ternary_reloption
+ * Allocate and initialize a new ternary reloption
+ */
+static relopt_ternary *
+init_ternary_reloption(bits32 kinds, const char *name, const char *desc,
+ ternary default_val, LOCKMODE lockmode)
+{
+ relopt_ternary *newoption;
+
+ newoption = (relopt_ternary *) allocate_reloption(kinds,
+ RELOPT_TYPE_TERNARY, name, desc, lockmode);
+ newoption->default_val = default_val;
+
+ return newoption;
+}
+
+/*
+ * add_ternary_reloption
+ * Add a new ternary reloption
+ */
+void
+add_ternary_reloption(bits32 kinds, const char *name, const char *desc,
+ ternary default_val, LOCKMODE lockmode)
+{
+ relopt_ternary *newoption = init_ternary_reloption(kinds, name, desc,
+ default_val, lockmode);
+
+ add_reloption((relopt_gen *) newoption);
+}
+
+/*
+ * add_local_ternary_reloption
+ * Add a new ternary local reloption
+ *
+ * 'offset' is offset of ternary-typed field.
+ */
+void
+add_local_ternary_reloption(local_relopts *relopts, const char *name,
+ const char *desc, ternary default_val,
+ int offset)
+{
+ relopt_ternary *newoption = init_ternary_reloption(RELOPT_KIND_LOCAL,
+ name, desc,
+ default_val, 0);
+
+ add_local_reloption(relopts, (relopt_gen *) newoption, offset);
+}
/*
* init_real_reloption
@@ -1617,6 +1689,18 @@ parse_one_reloption(relopt_value *option, char *text_str, int text_len,
option->gen->name, value)));
}
break;
+ case RELOPT_TYPE_TERNARY:
+ {
+ bool b;
+ parsed = parse_bool(value, &b);
+ option->values.ternary_val = b ? TERNARY_TRUE : TERNARY_FALSE;
+ if (validate && !parsed)
+ ereport(ERROR,
+ (errcode(ERRCODE_INVALID_PARAMETER_VALUE),
+ errmsg("invalid value for ternary option \"%s\": %s",
+ option->gen->name, value)));
+ }
+ break;
case RELOPT_TYPE_INT:
{
relopt_int *optint = (relopt_int *) option->gen;
@@ -1780,17 +1864,6 @@ fillRelOptions(void *rdopts, Size basesize,
char *itempos = ((char *) rdopts) + elems[j].offset;
char *string_val;
- /*
- * If isset_offset is provided, store whether the reloption is
- * set there.
- */
- if (elems[j].isset_offset > 0)
- {
- char *setpos = ((char *) rdopts) + elems[j].isset_offset;
-
- *(bool *) setpos = options[i].isset;
- }
-
switch (options[i].gen->type)
{
case RELOPT_TYPE_BOOL:
@@ -1798,6 +1871,11 @@ fillRelOptions(void *rdopts, Size basesize,
options[i].values.bool_val :
((relopt_bool *) options[i].gen)->default_val;
break;
+ case RELOPT_TYPE_TERNARY:
+ *(ternary *) itempos = options[i].isset ?
+ options[i].values.ternary_val :
+ ((relopt_ternary *) options[i].gen)->default_val;
+ break;
case RELOPT_TYPE_INT:
*(int *) itempos = options[i].isset ?
options[i].values.int_val :
@@ -1912,8 +1990,8 @@ default_reloptions(Datum reloptions, bool validate, relopt_kind kind)
offsetof(StdRdOptions, parallel_workers)},
{"vacuum_index_cleanup", RELOPT_TYPE_ENUM,
offsetof(StdRdOptions, vacuum_index_cleanup)},
- {"vacuum_truncate", RELOPT_TYPE_BOOL,
- offsetof(StdRdOptions, vacuum_truncate), offsetof(StdRdOptions, vacuum_truncate_set)},
+ {"vacuum_truncate", RELOPT_TYPE_TERNARY,
+ offsetof(StdRdOptions, vacuum_truncate)},
{"vacuum_max_eager_freeze_failure_rate", RELOPT_TYPE_REAL,
offsetof(StdRdOptions, vacuum_max_eager_freeze_failure_rate)}
};
@@ -1993,7 +2071,6 @@ build_local_reloptions(local_relopts *relopts, Datum options, bool validate)
elems[i].optname = opt->option->name;
elems[i].opttype = opt->option->type;
elems[i].offset = opt->offset;
- elems[i].isset_offset = 0; /* not supported for local relopts yet */
i++;
}
diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c
index 733ef40ae7..977babff54 100644
--- a/src/backend/commands/vacuum.c
+++ b/src/backend/commands/vacuum.c
@@ -2221,9 +2221,9 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams params,
{
StdRdOptions *opts = (StdRdOptions *) rel->rd_options;
- if (opts && opts->vacuum_truncate_set)
+ if (opts && opts->vacuum_truncate != TERNARY_UNSET)
{
- if (opts->vacuum_truncate)
+ if (opts->vacuum_truncate == TERNARY_TRUE)
params.truncate = VACOPTVALUE_ENABLED;
else
params.truncate = VACOPTVALUE_DISABLED;
diff --git a/src/include/access/reloptions.h b/src/include/access/reloptions.h
index a604a4702c..a436697658 100644
--- a/src/include/access/reloptions.h
+++ b/src/include/access/reloptions.h
@@ -29,6 +29,7 @@
typedef enum relopt_type
{
RELOPT_TYPE_BOOL,
+ RELOPT_TYPE_TERNARY, /* on, off, unset */
RELOPT_TYPE_INT,
RELOPT_TYPE_REAL,
RELOPT_TYPE_ENUM,
@@ -80,6 +81,7 @@ typedef struct relopt_value
union
{
bool bool_val;
+ ternary ternary_val;
int int_val;
double real_val;
int enum_val;
@@ -94,6 +96,12 @@ typedef struct relopt_bool
bool default_val;
} relopt_bool;
+typedef struct relopt_rernary
+{
+ relopt_gen gen;
+ int default_val;
+} relopt_ternary;
+
typedef struct relopt_int
{
relopt_gen gen;
@@ -152,19 +160,6 @@ typedef struct
const char *optname; /* option's name */
relopt_type opttype; /* option's datatype */
int offset; /* offset of field in result struct */
-
- /*
- * isset_offset is an optional offset of a field in the result struct that
- * stores whether the option is explicitly set for the relation or if it
- * just picked up the default value. In most cases, this can be
- * accomplished by giving the reloption a special out-of-range default
- * value (e.g., some integer reloptions use -2), but this isn't always
- * possible. For example, a Boolean reloption cannot be given an
- * out-of-range default, so we need another way to discover the source of
- * its value. This offset is only used if given a value greater than
- * zero.
- */
- int isset_offset;
} relopt_parse_elt;
/* Local reloption definition */
@@ -195,6 +190,8 @@ typedef struct local_relopts
extern relopt_kind add_reloption_kind(void);
extern void add_bool_reloption(bits32 kinds, const char *name, const char *desc,
bool default_val, LOCKMODE lockmode);
+extern void add_ternary_reloption(bits32 kinds, const char *name,
+ const char *desc, int default_val, LOCKMODE lockmode);
extern void add_int_reloption(bits32 kinds, const char *name, const char *desc,
int default_val, int min_val, int max_val,
LOCKMODE lockmode);
@@ -214,6 +211,9 @@ extern void register_reloptions_validator(local_relopts *relopts,
extern void add_local_bool_reloption(local_relopts *relopts, const char *name,
const char *desc, bool default_val,
int offset);
+extern void add_local_ternary_reloption(local_relopts *relopts,
+ const char *name, const char *desc,
+ ternary default_val, int offset);
extern void add_local_int_reloption(local_relopts *relopts, const char *name,
const char *desc, int default_val,
int min_val, int max_val, int offset);
diff --git a/src/include/c.h b/src/include/c.h
index 39022f8a9d..9c59ccccf8 100644
--- a/src/include/c.h
+++ b/src/include/c.h
@@ -488,6 +488,22 @@ typedef void (*pg_funcptr_t) (void);
#include <stdbool.h>
+/*
+ * ternary
+ * Boolean value with an extrea "unset" option
+ *
+ * Ternary data type is used in relation options that can be "true", "false" or
+ * "unset". Since relation options are used deep inside the PostgreSQL code,
+ * this type is declared globally.
+*/
+
+typedef enum ternary
+{
+ TERNARY_FALSE = 0,
+ TERNARY_TRUE = 1,
+ TERNARY_UNSET = -1
+} ternary;
+
/* ----------------------------------------------------------------
* Section 3: standard system types
diff --git a/src/include/utils/rel.h b/src/include/utils/rel.h
index b552359915..7346d618f9 100644
--- a/src/include/utils/rel.h
+++ b/src/include/utils/rel.h
@@ -346,8 +346,7 @@ typedef struct StdRdOptions
bool user_catalog_table; /* use as an additional catalog relation */
int parallel_workers; /* max number of parallel workers */
StdRdOptIndexCleanup vacuum_index_cleanup; /* controls index vacuuming */
- bool vacuum_truncate; /* enables vacuum to truncate a relation */
- bool vacuum_truncate_set; /* whether vacuum_truncate is set */
+ ternary vacuum_truncate; /* enables vacuum to truncate a relation */
/*
* Fraction of pages in a relation that vacuum can eagerly scan and fail
--
2.39.2
--nextPart4155600.3ZeAukHxDK
Content-Disposition: attachment;
filename="v2a-0003-Add-alias-to-be-used-as-unset-state.patch"
Content-Transfer-Encoding: 7Bit
Content-Type: text/x-patch; charset="unicode-2-0-utf-8";
name="v2a-0003-Add-alias-to-be-used-as-unset-state.patch"
^ permalink raw reply [nested|flat] 868+ messages in thread
* [PATCH v2 2/4] Introduce ternary reloptions
@ 2025-09-04 14:41 Nikolay Shaplov <[email protected]>
0 siblings, 0 replies; 868+ messages in thread
From: Nikolay Shaplov @ 2025-09-04 14:41 UTC (permalink / raw)
Introduce ternary reloption as a replacement for current `vacuum_truncate`
implementation. Remove `vacuum_truncate_set` additional flag and using
`TERNARY_UNSET` value instead.
---
src/backend/access/common/reloptions.c | 129 ++++++++++++++++++++-----
src/backend/commands/vacuum.c | 4 +-
src/include/access/reloptions.h | 26 ++---
src/include/c.h | 16 +++
src/include/utils/rel.h | 3 +-
5 files changed, 135 insertions(+), 43 deletions(-)
diff --git a/src/backend/access/common/reloptions.c b/src/backend/access/common/reloptions.c
index 0af3fea68f..f543f11001 100644
--- a/src/backend/access/common/reloptions.c
+++ b/src/backend/access/common/reloptions.c
@@ -40,9 +40,9 @@
*
* To add an option:
*
- * (i) decide on a type (bool, integer, real, enum, string), name, default
- * value, upper and lower bounds (if applicable); for strings, consider a
- * validation routine.
+ * (i) decide on a type (bool, ternary, integer, real, enum, string), name,
+ * default value, upper and lower bounds (if applicable); for strings,
+ * consider a validation routine.
* (ii) add a record below (or use add_<type>_reloption).
* (iii) add it to the appropriate options struct (perhaps StdRdOptions)
* (iv) add it to the appropriate handling routine (perhaps
@@ -147,15 +147,6 @@ static relopt_bool boolRelOpts[] =
},
false
},
- {
- {
- "vacuum_truncate",
- "Enables vacuum to truncate empty pages at the end of this table",
- RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
- ShareUpdateExclusiveLock
- },
- true
- },
{
{
"deduplicate_items",
@@ -170,6 +161,21 @@ static relopt_bool boolRelOpts[] =
{{NULL}}
};
+static relopt_ternary ternaryRelOpts[] =
+{
+ {
+ {
+ "vacuum_truncate",
+ "Enables vacuum to truncate empty pages at the end of this table",
+ RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
+ ShareUpdateExclusiveLock
+ },
+ TERNARY_UNSET
+ },
+ /* list terminator */
+ {{NULL}}
+};
+
static relopt_int intRelOpts[] =
{
{
@@ -600,6 +606,13 @@ initialize_reloptions(void)
boolRelOpts[i].gen.lockmode));
j++;
}
+ for (i = 0; ternaryRelOpts[i].gen.name; i++)
+ {
+ Assert(DoLockModesConflict(ternaryRelOpts[i].gen.lockmode,
+ ternaryRelOpts[i].gen.lockmode));
+ j++;
+ }
+
for (i = 0; intRelOpts[i].gen.name; i++)
{
Assert(DoLockModesConflict(intRelOpts[i].gen.lockmode,
@@ -640,6 +653,14 @@ initialize_reloptions(void)
j++;
}
+ for (i = 0; ternaryRelOpts[i].gen.name; i++)
+ {
+ relOpts[j] = &ternaryRelOpts[i].gen;
+ relOpts[j]->type = RELOPT_TYPE_TERNARY;
+ relOpts[j]->namelen = strlen(relOpts[j]->name);
+ j++;
+ }
+
for (i = 0; intRelOpts[i].gen.name; i++)
{
relOpts[j] = &intRelOpts[i].gen;
@@ -800,6 +821,9 @@ allocate_reloption(bits32 kinds, int type, const char *name, const char *desc,
case RELOPT_TYPE_BOOL:
size = sizeof(relopt_bool);
break;
+ case RELOPT_TYPE_TERNARY:
+ size = sizeof(relopt_ternary);
+ break;
case RELOPT_TYPE_INT:
size = sizeof(relopt_int);
break;
@@ -883,6 +907,54 @@ add_local_bool_reloption(local_relopts *relopts, const char *name,
add_local_reloption(relopts, (relopt_gen *) newoption, offset);
}
+/*
+ * init_ternary_reloption
+ * Allocate and initialize a new ternary reloption
+ */
+static relopt_ternary *
+init_ternary_reloption(bits32 kinds, const char *name, const char *desc,
+ ternary default_val, LOCKMODE lockmode)
+{
+ relopt_ternary *newoption;
+
+ newoption = (relopt_ternary *) allocate_reloption(kinds,
+ RELOPT_TYPE_TERNARY, name, desc, lockmode);
+ newoption->default_val = default_val;
+
+ return newoption;
+}
+
+/*
+ * add_ternary_reloption
+ * Add a new ternary reloption
+ */
+void
+add_ternary_reloption(bits32 kinds, const char *name, const char *desc,
+ ternary default_val, LOCKMODE lockmode)
+{
+ relopt_ternary *newoption = init_ternary_reloption(kinds, name, desc,
+ default_val, lockmode);
+
+ add_reloption((relopt_gen *) newoption);
+}
+
+/*
+ * add_local_ternary_reloption
+ * Add a new ternary local reloption
+ *
+ * 'offset' is offset of ternary-typed field.
+ */
+void
+add_local_ternary_reloption(local_relopts *relopts, const char *name,
+ const char *desc, ternary default_val,
+ int offset)
+{
+ relopt_ternary *newoption = init_ternary_reloption(RELOPT_KIND_LOCAL,
+ name, desc,
+ default_val, 0);
+
+ add_local_reloption(relopts, (relopt_gen *) newoption, offset);
+}
/*
* init_real_reloption
@@ -1617,6 +1689,18 @@ parse_one_reloption(relopt_value *option, char *text_str, int text_len,
option->gen->name, value)));
}
break;
+ case RELOPT_TYPE_TERNARY:
+ {
+ bool b;
+ parsed = parse_bool(value, &b);
+ option->values.ternary_val = b ? TERNARY_TRUE : TERNARY_FALSE;
+ if (validate && !parsed)
+ ereport(ERROR,
+ (errcode(ERRCODE_INVALID_PARAMETER_VALUE),
+ errmsg("invalid value for ternary option \"%s\": %s",
+ option->gen->name, value)));
+ }
+ break;
case RELOPT_TYPE_INT:
{
relopt_int *optint = (relopt_int *) option->gen;
@@ -1780,17 +1864,6 @@ fillRelOptions(void *rdopts, Size basesize,
char *itempos = ((char *) rdopts) + elems[j].offset;
char *string_val;
- /*
- * If isset_offset is provided, store whether the reloption is
- * set there.
- */
- if (elems[j].isset_offset > 0)
- {
- char *setpos = ((char *) rdopts) + elems[j].isset_offset;
-
- *(bool *) setpos = options[i].isset;
- }
-
switch (options[i].gen->type)
{
case RELOPT_TYPE_BOOL:
@@ -1798,6 +1871,11 @@ fillRelOptions(void *rdopts, Size basesize,
options[i].values.bool_val :
((relopt_bool *) options[i].gen)->default_val;
break;
+ case RELOPT_TYPE_TERNARY:
+ *(ternary *) itempos = options[i].isset ?
+ options[i].values.ternary_val :
+ ((relopt_ternary *) options[i].gen)->default_val;
+ break;
case RELOPT_TYPE_INT:
*(int *) itempos = options[i].isset ?
options[i].values.int_val :
@@ -1912,8 +1990,8 @@ default_reloptions(Datum reloptions, bool validate, relopt_kind kind)
offsetof(StdRdOptions, parallel_workers)},
{"vacuum_index_cleanup", RELOPT_TYPE_ENUM,
offsetof(StdRdOptions, vacuum_index_cleanup)},
- {"vacuum_truncate", RELOPT_TYPE_BOOL,
- offsetof(StdRdOptions, vacuum_truncate), offsetof(StdRdOptions, vacuum_truncate_set)},
+ {"vacuum_truncate", RELOPT_TYPE_TERNARY,
+ offsetof(StdRdOptions, vacuum_truncate)},
{"vacuum_max_eager_freeze_failure_rate", RELOPT_TYPE_REAL,
offsetof(StdRdOptions, vacuum_max_eager_freeze_failure_rate)}
};
@@ -1993,7 +2071,6 @@ build_local_reloptions(local_relopts *relopts, Datum options, bool validate)
elems[i].optname = opt->option->name;
elems[i].opttype = opt->option->type;
elems[i].offset = opt->offset;
- elems[i].isset_offset = 0; /* not supported for local relopts yet */
i++;
}
diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c
index 733ef40ae7..977babff54 100644
--- a/src/backend/commands/vacuum.c
+++ b/src/backend/commands/vacuum.c
@@ -2221,9 +2221,9 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams params,
{
StdRdOptions *opts = (StdRdOptions *) rel->rd_options;
- if (opts && opts->vacuum_truncate_set)
+ if (opts && opts->vacuum_truncate != TERNARY_UNSET)
{
- if (opts->vacuum_truncate)
+ if (opts->vacuum_truncate == TERNARY_TRUE)
params.truncate = VACOPTVALUE_ENABLED;
else
params.truncate = VACOPTVALUE_DISABLED;
diff --git a/src/include/access/reloptions.h b/src/include/access/reloptions.h
index a604a4702c..a436697658 100644
--- a/src/include/access/reloptions.h
+++ b/src/include/access/reloptions.h
@@ -29,6 +29,7 @@
typedef enum relopt_type
{
RELOPT_TYPE_BOOL,
+ RELOPT_TYPE_TERNARY, /* on, off, unset */
RELOPT_TYPE_INT,
RELOPT_TYPE_REAL,
RELOPT_TYPE_ENUM,
@@ -80,6 +81,7 @@ typedef struct relopt_value
union
{
bool bool_val;
+ ternary ternary_val;
int int_val;
double real_val;
int enum_val;
@@ -94,6 +96,12 @@ typedef struct relopt_bool
bool default_val;
} relopt_bool;
+typedef struct relopt_rernary
+{
+ relopt_gen gen;
+ int default_val;
+} relopt_ternary;
+
typedef struct relopt_int
{
relopt_gen gen;
@@ -152,19 +160,6 @@ typedef struct
const char *optname; /* option's name */
relopt_type opttype; /* option's datatype */
int offset; /* offset of field in result struct */
-
- /*
- * isset_offset is an optional offset of a field in the result struct that
- * stores whether the option is explicitly set for the relation or if it
- * just picked up the default value. In most cases, this can be
- * accomplished by giving the reloption a special out-of-range default
- * value (e.g., some integer reloptions use -2), but this isn't always
- * possible. For example, a Boolean reloption cannot be given an
- * out-of-range default, so we need another way to discover the source of
- * its value. This offset is only used if given a value greater than
- * zero.
- */
- int isset_offset;
} relopt_parse_elt;
/* Local reloption definition */
@@ -195,6 +190,8 @@ typedef struct local_relopts
extern relopt_kind add_reloption_kind(void);
extern void add_bool_reloption(bits32 kinds, const char *name, const char *desc,
bool default_val, LOCKMODE lockmode);
+extern void add_ternary_reloption(bits32 kinds, const char *name,
+ const char *desc, int default_val, LOCKMODE lockmode);
extern void add_int_reloption(bits32 kinds, const char *name, const char *desc,
int default_val, int min_val, int max_val,
LOCKMODE lockmode);
@@ -214,6 +211,9 @@ extern void register_reloptions_validator(local_relopts *relopts,
extern void add_local_bool_reloption(local_relopts *relopts, const char *name,
const char *desc, bool default_val,
int offset);
+extern void add_local_ternary_reloption(local_relopts *relopts,
+ const char *name, const char *desc,
+ ternary default_val, int offset);
extern void add_local_int_reloption(local_relopts *relopts, const char *name,
const char *desc, int default_val,
int min_val, int max_val, int offset);
diff --git a/src/include/c.h b/src/include/c.h
index 39022f8a9d..9c59ccccf8 100644
--- a/src/include/c.h
+++ b/src/include/c.h
@@ -488,6 +488,22 @@ typedef void (*pg_funcptr_t) (void);
#include <stdbool.h>
+/*
+ * ternary
+ * Boolean value with an extrea "unset" option
+ *
+ * Ternary data type is used in relation options that can be "true", "false" or
+ * "unset". Since relation options are used deep inside the PostgreSQL code,
+ * this type is declared globally.
+*/
+
+typedef enum ternary
+{
+ TERNARY_FALSE = 0,
+ TERNARY_TRUE = 1,
+ TERNARY_UNSET = -1
+} ternary;
+
/* ----------------------------------------------------------------
* Section 3: standard system types
diff --git a/src/include/utils/rel.h b/src/include/utils/rel.h
index b552359915..7346d618f9 100644
--- a/src/include/utils/rel.h
+++ b/src/include/utils/rel.h
@@ -346,8 +346,7 @@ typedef struct StdRdOptions
bool user_catalog_table; /* use as an additional catalog relation */
int parallel_workers; /* max number of parallel workers */
StdRdOptIndexCleanup vacuum_index_cleanup; /* controls index vacuuming */
- bool vacuum_truncate; /* enables vacuum to truncate a relation */
- bool vacuum_truncate_set; /* whether vacuum_truncate is set */
+ ternary vacuum_truncate; /* enables vacuum to truncate a relation */
/*
* Fraction of pages in a relation that vacuum can eagerly scan and fail
--
2.39.2
--nextPart5129932.5fSG56mABF
Content-Disposition: attachment;
filename="v2-0003-Add-alias-to-be-used-as-unset-state.patch"
Content-Transfer-Encoding: 7Bit
Content-Type: text/x-patch; charset="unicode-2-0-utf-8";
name="v2-0003-Add-alias-to-be-used-as-unset-state.patch"
^ permalink raw reply [nested|flat] 868+ messages in thread
* [PATCH v2 2/4] Introduce ternary reloptions
@ 2025-09-04 14:41 Nikolay Shaplov <[email protected]>
0 siblings, 0 replies; 868+ messages in thread
From: Nikolay Shaplov @ 2025-09-04 14:41 UTC (permalink / raw)
Introduce ternary reloption as a replacement for current `vacuum_truncate`
implementation. Remove `vacuum_truncate_set` additional flag and using
`TERNARY_UNSET` value instead.
---
src/backend/access/common/reloptions.c | 129 ++++++++++++++++++++-----
src/backend/commands/vacuum.c | 4 +-
src/include/access/reloptions.h | 26 ++---
src/include/c.h | 16 +++
src/include/utils/rel.h | 3 +-
5 files changed, 135 insertions(+), 43 deletions(-)
diff --git a/src/backend/access/common/reloptions.c b/src/backend/access/common/reloptions.c
index 0af3fea68f..f543f11001 100644
--- a/src/backend/access/common/reloptions.c
+++ b/src/backend/access/common/reloptions.c
@@ -40,9 +40,9 @@
*
* To add an option:
*
- * (i) decide on a type (bool, integer, real, enum, string), name, default
- * value, upper and lower bounds (if applicable); for strings, consider a
- * validation routine.
+ * (i) decide on a type (bool, ternary, integer, real, enum, string), name,
+ * default value, upper and lower bounds (if applicable); for strings,
+ * consider a validation routine.
* (ii) add a record below (or use add_<type>_reloption).
* (iii) add it to the appropriate options struct (perhaps StdRdOptions)
* (iv) add it to the appropriate handling routine (perhaps
@@ -147,15 +147,6 @@ static relopt_bool boolRelOpts[] =
},
false
},
- {
- {
- "vacuum_truncate",
- "Enables vacuum to truncate empty pages at the end of this table",
- RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
- ShareUpdateExclusiveLock
- },
- true
- },
{
{
"deduplicate_items",
@@ -170,6 +161,21 @@ static relopt_bool boolRelOpts[] =
{{NULL}}
};
+static relopt_ternary ternaryRelOpts[] =
+{
+ {
+ {
+ "vacuum_truncate",
+ "Enables vacuum to truncate empty pages at the end of this table",
+ RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
+ ShareUpdateExclusiveLock
+ },
+ TERNARY_UNSET
+ },
+ /* list terminator */
+ {{NULL}}
+};
+
static relopt_int intRelOpts[] =
{
{
@@ -600,6 +606,13 @@ initialize_reloptions(void)
boolRelOpts[i].gen.lockmode));
j++;
}
+ for (i = 0; ternaryRelOpts[i].gen.name; i++)
+ {
+ Assert(DoLockModesConflict(ternaryRelOpts[i].gen.lockmode,
+ ternaryRelOpts[i].gen.lockmode));
+ j++;
+ }
+
for (i = 0; intRelOpts[i].gen.name; i++)
{
Assert(DoLockModesConflict(intRelOpts[i].gen.lockmode,
@@ -640,6 +653,14 @@ initialize_reloptions(void)
j++;
}
+ for (i = 0; ternaryRelOpts[i].gen.name; i++)
+ {
+ relOpts[j] = &ternaryRelOpts[i].gen;
+ relOpts[j]->type = RELOPT_TYPE_TERNARY;
+ relOpts[j]->namelen = strlen(relOpts[j]->name);
+ j++;
+ }
+
for (i = 0; intRelOpts[i].gen.name; i++)
{
relOpts[j] = &intRelOpts[i].gen;
@@ -800,6 +821,9 @@ allocate_reloption(bits32 kinds, int type, const char *name, const char *desc,
case RELOPT_TYPE_BOOL:
size = sizeof(relopt_bool);
break;
+ case RELOPT_TYPE_TERNARY:
+ size = sizeof(relopt_ternary);
+ break;
case RELOPT_TYPE_INT:
size = sizeof(relopt_int);
break;
@@ -883,6 +907,54 @@ add_local_bool_reloption(local_relopts *relopts, const char *name,
add_local_reloption(relopts, (relopt_gen *) newoption, offset);
}
+/*
+ * init_ternary_reloption
+ * Allocate and initialize a new ternary reloption
+ */
+static relopt_ternary *
+init_ternary_reloption(bits32 kinds, const char *name, const char *desc,
+ ternary default_val, LOCKMODE lockmode)
+{
+ relopt_ternary *newoption;
+
+ newoption = (relopt_ternary *) allocate_reloption(kinds,
+ RELOPT_TYPE_TERNARY, name, desc, lockmode);
+ newoption->default_val = default_val;
+
+ return newoption;
+}
+
+/*
+ * add_ternary_reloption
+ * Add a new ternary reloption
+ */
+void
+add_ternary_reloption(bits32 kinds, const char *name, const char *desc,
+ ternary default_val, LOCKMODE lockmode)
+{
+ relopt_ternary *newoption = init_ternary_reloption(kinds, name, desc,
+ default_val, lockmode);
+
+ add_reloption((relopt_gen *) newoption);
+}
+
+/*
+ * add_local_ternary_reloption
+ * Add a new ternary local reloption
+ *
+ * 'offset' is offset of ternary-typed field.
+ */
+void
+add_local_ternary_reloption(local_relopts *relopts, const char *name,
+ const char *desc, ternary default_val,
+ int offset)
+{
+ relopt_ternary *newoption = init_ternary_reloption(RELOPT_KIND_LOCAL,
+ name, desc,
+ default_val, 0);
+
+ add_local_reloption(relopts, (relopt_gen *) newoption, offset);
+}
/*
* init_real_reloption
@@ -1617,6 +1689,18 @@ parse_one_reloption(relopt_value *option, char *text_str, int text_len,
option->gen->name, value)));
}
break;
+ case RELOPT_TYPE_TERNARY:
+ {
+ bool b;
+ parsed = parse_bool(value, &b);
+ option->values.ternary_val = b ? TERNARY_TRUE : TERNARY_FALSE;
+ if (validate && !parsed)
+ ereport(ERROR,
+ (errcode(ERRCODE_INVALID_PARAMETER_VALUE),
+ errmsg("invalid value for ternary option \"%s\": %s",
+ option->gen->name, value)));
+ }
+ break;
case RELOPT_TYPE_INT:
{
relopt_int *optint = (relopt_int *) option->gen;
@@ -1780,17 +1864,6 @@ fillRelOptions(void *rdopts, Size basesize,
char *itempos = ((char *) rdopts) + elems[j].offset;
char *string_val;
- /*
- * If isset_offset is provided, store whether the reloption is
- * set there.
- */
- if (elems[j].isset_offset > 0)
- {
- char *setpos = ((char *) rdopts) + elems[j].isset_offset;
-
- *(bool *) setpos = options[i].isset;
- }
-
switch (options[i].gen->type)
{
case RELOPT_TYPE_BOOL:
@@ -1798,6 +1871,11 @@ fillRelOptions(void *rdopts, Size basesize,
options[i].values.bool_val :
((relopt_bool *) options[i].gen)->default_val;
break;
+ case RELOPT_TYPE_TERNARY:
+ *(ternary *) itempos = options[i].isset ?
+ options[i].values.ternary_val :
+ ((relopt_ternary *) options[i].gen)->default_val;
+ break;
case RELOPT_TYPE_INT:
*(int *) itempos = options[i].isset ?
options[i].values.int_val :
@@ -1912,8 +1990,8 @@ default_reloptions(Datum reloptions, bool validate, relopt_kind kind)
offsetof(StdRdOptions, parallel_workers)},
{"vacuum_index_cleanup", RELOPT_TYPE_ENUM,
offsetof(StdRdOptions, vacuum_index_cleanup)},
- {"vacuum_truncate", RELOPT_TYPE_BOOL,
- offsetof(StdRdOptions, vacuum_truncate), offsetof(StdRdOptions, vacuum_truncate_set)},
+ {"vacuum_truncate", RELOPT_TYPE_TERNARY,
+ offsetof(StdRdOptions, vacuum_truncate)},
{"vacuum_max_eager_freeze_failure_rate", RELOPT_TYPE_REAL,
offsetof(StdRdOptions, vacuum_max_eager_freeze_failure_rate)}
};
@@ -1993,7 +2071,6 @@ build_local_reloptions(local_relopts *relopts, Datum options, bool validate)
elems[i].optname = opt->option->name;
elems[i].opttype = opt->option->type;
elems[i].offset = opt->offset;
- elems[i].isset_offset = 0; /* not supported for local relopts yet */
i++;
}
diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c
index 733ef40ae7..977babff54 100644
--- a/src/backend/commands/vacuum.c
+++ b/src/backend/commands/vacuum.c
@@ -2221,9 +2221,9 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams params,
{
StdRdOptions *opts = (StdRdOptions *) rel->rd_options;
- if (opts && opts->vacuum_truncate_set)
+ if (opts && opts->vacuum_truncate != TERNARY_UNSET)
{
- if (opts->vacuum_truncate)
+ if (opts->vacuum_truncate == TERNARY_TRUE)
params.truncate = VACOPTVALUE_ENABLED;
else
params.truncate = VACOPTVALUE_DISABLED;
diff --git a/src/include/access/reloptions.h b/src/include/access/reloptions.h
index a604a4702c..a436697658 100644
--- a/src/include/access/reloptions.h
+++ b/src/include/access/reloptions.h
@@ -29,6 +29,7 @@
typedef enum relopt_type
{
RELOPT_TYPE_BOOL,
+ RELOPT_TYPE_TERNARY, /* on, off, unset */
RELOPT_TYPE_INT,
RELOPT_TYPE_REAL,
RELOPT_TYPE_ENUM,
@@ -80,6 +81,7 @@ typedef struct relopt_value
union
{
bool bool_val;
+ ternary ternary_val;
int int_val;
double real_val;
int enum_val;
@@ -94,6 +96,12 @@ typedef struct relopt_bool
bool default_val;
} relopt_bool;
+typedef struct relopt_rernary
+{
+ relopt_gen gen;
+ int default_val;
+} relopt_ternary;
+
typedef struct relopt_int
{
relopt_gen gen;
@@ -152,19 +160,6 @@ typedef struct
const char *optname; /* option's name */
relopt_type opttype; /* option's datatype */
int offset; /* offset of field in result struct */
-
- /*
- * isset_offset is an optional offset of a field in the result struct that
- * stores whether the option is explicitly set for the relation or if it
- * just picked up the default value. In most cases, this can be
- * accomplished by giving the reloption a special out-of-range default
- * value (e.g., some integer reloptions use -2), but this isn't always
- * possible. For example, a Boolean reloption cannot be given an
- * out-of-range default, so we need another way to discover the source of
- * its value. This offset is only used if given a value greater than
- * zero.
- */
- int isset_offset;
} relopt_parse_elt;
/* Local reloption definition */
@@ -195,6 +190,8 @@ typedef struct local_relopts
extern relopt_kind add_reloption_kind(void);
extern void add_bool_reloption(bits32 kinds, const char *name, const char *desc,
bool default_val, LOCKMODE lockmode);
+extern void add_ternary_reloption(bits32 kinds, const char *name,
+ const char *desc, int default_val, LOCKMODE lockmode);
extern void add_int_reloption(bits32 kinds, const char *name, const char *desc,
int default_val, int min_val, int max_val,
LOCKMODE lockmode);
@@ -214,6 +211,9 @@ extern void register_reloptions_validator(local_relopts *relopts,
extern void add_local_bool_reloption(local_relopts *relopts, const char *name,
const char *desc, bool default_val,
int offset);
+extern void add_local_ternary_reloption(local_relopts *relopts,
+ const char *name, const char *desc,
+ ternary default_val, int offset);
extern void add_local_int_reloption(local_relopts *relopts, const char *name,
const char *desc, int default_val,
int min_val, int max_val, int offset);
diff --git a/src/include/c.h b/src/include/c.h
index 39022f8a9d..9c59ccccf8 100644
--- a/src/include/c.h
+++ b/src/include/c.h
@@ -488,6 +488,22 @@ typedef void (*pg_funcptr_t) (void);
#include <stdbool.h>
+/*
+ * ternary
+ * Boolean value with an extrea "unset" option
+ *
+ * Ternary data type is used in relation options that can be "true", "false" or
+ * "unset". Since relation options are used deep inside the PostgreSQL code,
+ * this type is declared globally.
+*/
+
+typedef enum ternary
+{
+ TERNARY_FALSE = 0,
+ TERNARY_TRUE = 1,
+ TERNARY_UNSET = -1
+} ternary;
+
/* ----------------------------------------------------------------
* Section 3: standard system types
diff --git a/src/include/utils/rel.h b/src/include/utils/rel.h
index b552359915..7346d618f9 100644
--- a/src/include/utils/rel.h
+++ b/src/include/utils/rel.h
@@ -346,8 +346,7 @@ typedef struct StdRdOptions
bool user_catalog_table; /* use as an additional catalog relation */
int parallel_workers; /* max number of parallel workers */
StdRdOptIndexCleanup vacuum_index_cleanup; /* controls index vacuuming */
- bool vacuum_truncate; /* enables vacuum to truncate a relation */
- bool vacuum_truncate_set; /* whether vacuum_truncate is set */
+ ternary vacuum_truncate; /* enables vacuum to truncate a relation */
/*
* Fraction of pages in a relation that vacuum can eagerly scan and fail
--
2.39.2
--nextPart5129932.5fSG56mABF
Content-Disposition: attachment;
filename="v2-0003-Add-alias-to-be-used-as-unset-state.patch"
Content-Transfer-Encoding: 7Bit
Content-Type: text/x-patch; charset="unicode-2-0-utf-8";
name="v2-0003-Add-alias-to-be-used-as-unset-state.patch"
^ permalink raw reply [nested|flat] 868+ messages in thread
* [PATCH v2 2/4] Introduce ternary reloptions
@ 2025-09-04 14:41 Nikolay Shaplov <[email protected]>
0 siblings, 0 replies; 868+ messages in thread
From: Nikolay Shaplov @ 2025-09-04 14:41 UTC (permalink / raw)
Introduce ternary reloption as a replacement for current `vacuum_truncate`
implementation. Remove `vacuum_truncate_set` additional flag and using
`TERNARY_UNSET` value instead.
---
src/backend/access/common/reloptions.c | 129 ++++++++++++++++++++-----
src/backend/commands/vacuum.c | 4 +-
src/include/access/reloptions.h | 26 ++---
src/include/c.h | 16 +++
src/include/utils/rel.h | 3 +-
5 files changed, 135 insertions(+), 43 deletions(-)
diff --git a/src/backend/access/common/reloptions.c b/src/backend/access/common/reloptions.c
index 0af3fea68f..f543f11001 100644
--- a/src/backend/access/common/reloptions.c
+++ b/src/backend/access/common/reloptions.c
@@ -40,9 +40,9 @@
*
* To add an option:
*
- * (i) decide on a type (bool, integer, real, enum, string), name, default
- * value, upper and lower bounds (if applicable); for strings, consider a
- * validation routine.
+ * (i) decide on a type (bool, ternary, integer, real, enum, string), name,
+ * default value, upper and lower bounds (if applicable); for strings,
+ * consider a validation routine.
* (ii) add a record below (or use add_<type>_reloption).
* (iii) add it to the appropriate options struct (perhaps StdRdOptions)
* (iv) add it to the appropriate handling routine (perhaps
@@ -147,15 +147,6 @@ static relopt_bool boolRelOpts[] =
},
false
},
- {
- {
- "vacuum_truncate",
- "Enables vacuum to truncate empty pages at the end of this table",
- RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
- ShareUpdateExclusiveLock
- },
- true
- },
{
{
"deduplicate_items",
@@ -170,6 +161,21 @@ static relopt_bool boolRelOpts[] =
{{NULL}}
};
+static relopt_ternary ternaryRelOpts[] =
+{
+ {
+ {
+ "vacuum_truncate",
+ "Enables vacuum to truncate empty pages at the end of this table",
+ RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
+ ShareUpdateExclusiveLock
+ },
+ TERNARY_UNSET
+ },
+ /* list terminator */
+ {{NULL}}
+};
+
static relopt_int intRelOpts[] =
{
{
@@ -600,6 +606,13 @@ initialize_reloptions(void)
boolRelOpts[i].gen.lockmode));
j++;
}
+ for (i = 0; ternaryRelOpts[i].gen.name; i++)
+ {
+ Assert(DoLockModesConflict(ternaryRelOpts[i].gen.lockmode,
+ ternaryRelOpts[i].gen.lockmode));
+ j++;
+ }
+
for (i = 0; intRelOpts[i].gen.name; i++)
{
Assert(DoLockModesConflict(intRelOpts[i].gen.lockmode,
@@ -640,6 +653,14 @@ initialize_reloptions(void)
j++;
}
+ for (i = 0; ternaryRelOpts[i].gen.name; i++)
+ {
+ relOpts[j] = &ternaryRelOpts[i].gen;
+ relOpts[j]->type = RELOPT_TYPE_TERNARY;
+ relOpts[j]->namelen = strlen(relOpts[j]->name);
+ j++;
+ }
+
for (i = 0; intRelOpts[i].gen.name; i++)
{
relOpts[j] = &intRelOpts[i].gen;
@@ -800,6 +821,9 @@ allocate_reloption(bits32 kinds, int type, const char *name, const char *desc,
case RELOPT_TYPE_BOOL:
size = sizeof(relopt_bool);
break;
+ case RELOPT_TYPE_TERNARY:
+ size = sizeof(relopt_ternary);
+ break;
case RELOPT_TYPE_INT:
size = sizeof(relopt_int);
break;
@@ -883,6 +907,54 @@ add_local_bool_reloption(local_relopts *relopts, const char *name,
add_local_reloption(relopts, (relopt_gen *) newoption, offset);
}
+/*
+ * init_ternary_reloption
+ * Allocate and initialize a new ternary reloption
+ */
+static relopt_ternary *
+init_ternary_reloption(bits32 kinds, const char *name, const char *desc,
+ ternary default_val, LOCKMODE lockmode)
+{
+ relopt_ternary *newoption;
+
+ newoption = (relopt_ternary *) allocate_reloption(kinds,
+ RELOPT_TYPE_TERNARY, name, desc, lockmode);
+ newoption->default_val = default_val;
+
+ return newoption;
+}
+
+/*
+ * add_ternary_reloption
+ * Add a new ternary reloption
+ */
+void
+add_ternary_reloption(bits32 kinds, const char *name, const char *desc,
+ ternary default_val, LOCKMODE lockmode)
+{
+ relopt_ternary *newoption = init_ternary_reloption(kinds, name, desc,
+ default_val, lockmode);
+
+ add_reloption((relopt_gen *) newoption);
+}
+
+/*
+ * add_local_ternary_reloption
+ * Add a new ternary local reloption
+ *
+ * 'offset' is offset of ternary-typed field.
+ */
+void
+add_local_ternary_reloption(local_relopts *relopts, const char *name,
+ const char *desc, ternary default_val,
+ int offset)
+{
+ relopt_ternary *newoption = init_ternary_reloption(RELOPT_KIND_LOCAL,
+ name, desc,
+ default_val, 0);
+
+ add_local_reloption(relopts, (relopt_gen *) newoption, offset);
+}
/*
* init_real_reloption
@@ -1617,6 +1689,18 @@ parse_one_reloption(relopt_value *option, char *text_str, int text_len,
option->gen->name, value)));
}
break;
+ case RELOPT_TYPE_TERNARY:
+ {
+ bool b;
+ parsed = parse_bool(value, &b);
+ option->values.ternary_val = b ? TERNARY_TRUE : TERNARY_FALSE;
+ if (validate && !parsed)
+ ereport(ERROR,
+ (errcode(ERRCODE_INVALID_PARAMETER_VALUE),
+ errmsg("invalid value for ternary option \"%s\": %s",
+ option->gen->name, value)));
+ }
+ break;
case RELOPT_TYPE_INT:
{
relopt_int *optint = (relopt_int *) option->gen;
@@ -1780,17 +1864,6 @@ fillRelOptions(void *rdopts, Size basesize,
char *itempos = ((char *) rdopts) + elems[j].offset;
char *string_val;
- /*
- * If isset_offset is provided, store whether the reloption is
- * set there.
- */
- if (elems[j].isset_offset > 0)
- {
- char *setpos = ((char *) rdopts) + elems[j].isset_offset;
-
- *(bool *) setpos = options[i].isset;
- }
-
switch (options[i].gen->type)
{
case RELOPT_TYPE_BOOL:
@@ -1798,6 +1871,11 @@ fillRelOptions(void *rdopts, Size basesize,
options[i].values.bool_val :
((relopt_bool *) options[i].gen)->default_val;
break;
+ case RELOPT_TYPE_TERNARY:
+ *(ternary *) itempos = options[i].isset ?
+ options[i].values.ternary_val :
+ ((relopt_ternary *) options[i].gen)->default_val;
+ break;
case RELOPT_TYPE_INT:
*(int *) itempos = options[i].isset ?
options[i].values.int_val :
@@ -1912,8 +1990,8 @@ default_reloptions(Datum reloptions, bool validate, relopt_kind kind)
offsetof(StdRdOptions, parallel_workers)},
{"vacuum_index_cleanup", RELOPT_TYPE_ENUM,
offsetof(StdRdOptions, vacuum_index_cleanup)},
- {"vacuum_truncate", RELOPT_TYPE_BOOL,
- offsetof(StdRdOptions, vacuum_truncate), offsetof(StdRdOptions, vacuum_truncate_set)},
+ {"vacuum_truncate", RELOPT_TYPE_TERNARY,
+ offsetof(StdRdOptions, vacuum_truncate)},
{"vacuum_max_eager_freeze_failure_rate", RELOPT_TYPE_REAL,
offsetof(StdRdOptions, vacuum_max_eager_freeze_failure_rate)}
};
@@ -1993,7 +2071,6 @@ build_local_reloptions(local_relopts *relopts, Datum options, bool validate)
elems[i].optname = opt->option->name;
elems[i].opttype = opt->option->type;
elems[i].offset = opt->offset;
- elems[i].isset_offset = 0; /* not supported for local relopts yet */
i++;
}
diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c
index 733ef40ae7..977babff54 100644
--- a/src/backend/commands/vacuum.c
+++ b/src/backend/commands/vacuum.c
@@ -2221,9 +2221,9 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams params,
{
StdRdOptions *opts = (StdRdOptions *) rel->rd_options;
- if (opts && opts->vacuum_truncate_set)
+ if (opts && opts->vacuum_truncate != TERNARY_UNSET)
{
- if (opts->vacuum_truncate)
+ if (opts->vacuum_truncate == TERNARY_TRUE)
params.truncate = VACOPTVALUE_ENABLED;
else
params.truncate = VACOPTVALUE_DISABLED;
diff --git a/src/include/access/reloptions.h b/src/include/access/reloptions.h
index a604a4702c..a436697658 100644
--- a/src/include/access/reloptions.h
+++ b/src/include/access/reloptions.h
@@ -29,6 +29,7 @@
typedef enum relopt_type
{
RELOPT_TYPE_BOOL,
+ RELOPT_TYPE_TERNARY, /* on, off, unset */
RELOPT_TYPE_INT,
RELOPT_TYPE_REAL,
RELOPT_TYPE_ENUM,
@@ -80,6 +81,7 @@ typedef struct relopt_value
union
{
bool bool_val;
+ ternary ternary_val;
int int_val;
double real_val;
int enum_val;
@@ -94,6 +96,12 @@ typedef struct relopt_bool
bool default_val;
} relopt_bool;
+typedef struct relopt_rernary
+{
+ relopt_gen gen;
+ int default_val;
+} relopt_ternary;
+
typedef struct relopt_int
{
relopt_gen gen;
@@ -152,19 +160,6 @@ typedef struct
const char *optname; /* option's name */
relopt_type opttype; /* option's datatype */
int offset; /* offset of field in result struct */
-
- /*
- * isset_offset is an optional offset of a field in the result struct that
- * stores whether the option is explicitly set for the relation or if it
- * just picked up the default value. In most cases, this can be
- * accomplished by giving the reloption a special out-of-range default
- * value (e.g., some integer reloptions use -2), but this isn't always
- * possible. For example, a Boolean reloption cannot be given an
- * out-of-range default, so we need another way to discover the source of
- * its value. This offset is only used if given a value greater than
- * zero.
- */
- int isset_offset;
} relopt_parse_elt;
/* Local reloption definition */
@@ -195,6 +190,8 @@ typedef struct local_relopts
extern relopt_kind add_reloption_kind(void);
extern void add_bool_reloption(bits32 kinds, const char *name, const char *desc,
bool default_val, LOCKMODE lockmode);
+extern void add_ternary_reloption(bits32 kinds, const char *name,
+ const char *desc, int default_val, LOCKMODE lockmode);
extern void add_int_reloption(bits32 kinds, const char *name, const char *desc,
int default_val, int min_val, int max_val,
LOCKMODE lockmode);
@@ -214,6 +211,9 @@ extern void register_reloptions_validator(local_relopts *relopts,
extern void add_local_bool_reloption(local_relopts *relopts, const char *name,
const char *desc, bool default_val,
int offset);
+extern void add_local_ternary_reloption(local_relopts *relopts,
+ const char *name, const char *desc,
+ ternary default_val, int offset);
extern void add_local_int_reloption(local_relopts *relopts, const char *name,
const char *desc, int default_val,
int min_val, int max_val, int offset);
diff --git a/src/include/c.h b/src/include/c.h
index 39022f8a9d..9c59ccccf8 100644
--- a/src/include/c.h
+++ b/src/include/c.h
@@ -488,6 +488,22 @@ typedef void (*pg_funcptr_t) (void);
#include <stdbool.h>
+/*
+ * ternary
+ * Boolean value with an extrea "unset" option
+ *
+ * Ternary data type is used in relation options that can be "true", "false" or
+ * "unset". Since relation options are used deep inside the PostgreSQL code,
+ * this type is declared globally.
+*/
+
+typedef enum ternary
+{
+ TERNARY_FALSE = 0,
+ TERNARY_TRUE = 1,
+ TERNARY_UNSET = -1
+} ternary;
+
/* ----------------------------------------------------------------
* Section 3: standard system types
diff --git a/src/include/utils/rel.h b/src/include/utils/rel.h
index b552359915..7346d618f9 100644
--- a/src/include/utils/rel.h
+++ b/src/include/utils/rel.h
@@ -346,8 +346,7 @@ typedef struct StdRdOptions
bool user_catalog_table; /* use as an additional catalog relation */
int parallel_workers; /* max number of parallel workers */
StdRdOptIndexCleanup vacuum_index_cleanup; /* controls index vacuuming */
- bool vacuum_truncate; /* enables vacuum to truncate a relation */
- bool vacuum_truncate_set; /* whether vacuum_truncate is set */
+ ternary vacuum_truncate; /* enables vacuum to truncate a relation */
/*
* Fraction of pages in a relation that vacuum can eagerly scan and fail
--
2.39.2
--nextPart5129932.5fSG56mABF
Content-Disposition: attachment;
filename="v2-0003-Add-alias-to-be-used-as-unset-state.patch"
Content-Transfer-Encoding: 7Bit
Content-Type: text/x-patch; charset="unicode-2-0-utf-8";
name="v2-0003-Add-alias-to-be-used-as-unset-state.patch"
^ permalink raw reply [nested|flat] 868+ messages in thread
* [PATCH v2 2/4] Introduce ternary reloptions
@ 2025-09-04 14:41 Nikolay Shaplov <[email protected]>
0 siblings, 0 replies; 868+ messages in thread
From: Nikolay Shaplov @ 2025-09-04 14:41 UTC (permalink / raw)
Introduce ternary reloption as a replacement for current `vacuum_truncate`
implementation. Remove `vacuum_truncate_set` additional flag and using
`TERNARY_UNSET` value instead.
---
src/backend/access/common/reloptions.c | 129 ++++++++++++++++++++-----
src/backend/commands/vacuum.c | 4 +-
src/include/access/reloptions.h | 26 ++---
src/include/c.h | 16 +++
src/include/utils/rel.h | 3 +-
5 files changed, 135 insertions(+), 43 deletions(-)
diff --git a/src/backend/access/common/reloptions.c b/src/backend/access/common/reloptions.c
index 0af3fea68f..f543f11001 100644
--- a/src/backend/access/common/reloptions.c
+++ b/src/backend/access/common/reloptions.c
@@ -40,9 +40,9 @@
*
* To add an option:
*
- * (i) decide on a type (bool, integer, real, enum, string), name, default
- * value, upper and lower bounds (if applicable); for strings, consider a
- * validation routine.
+ * (i) decide on a type (bool, ternary, integer, real, enum, string), name,
+ * default value, upper and lower bounds (if applicable); for strings,
+ * consider a validation routine.
* (ii) add a record below (or use add_<type>_reloption).
* (iii) add it to the appropriate options struct (perhaps StdRdOptions)
* (iv) add it to the appropriate handling routine (perhaps
@@ -147,15 +147,6 @@ static relopt_bool boolRelOpts[] =
},
false
},
- {
- {
- "vacuum_truncate",
- "Enables vacuum to truncate empty pages at the end of this table",
- RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
- ShareUpdateExclusiveLock
- },
- true
- },
{
{
"deduplicate_items",
@@ -170,6 +161,21 @@ static relopt_bool boolRelOpts[] =
{{NULL}}
};
+static relopt_ternary ternaryRelOpts[] =
+{
+ {
+ {
+ "vacuum_truncate",
+ "Enables vacuum to truncate empty pages at the end of this table",
+ RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
+ ShareUpdateExclusiveLock
+ },
+ TERNARY_UNSET
+ },
+ /* list terminator */
+ {{NULL}}
+};
+
static relopt_int intRelOpts[] =
{
{
@@ -600,6 +606,13 @@ initialize_reloptions(void)
boolRelOpts[i].gen.lockmode));
j++;
}
+ for (i = 0; ternaryRelOpts[i].gen.name; i++)
+ {
+ Assert(DoLockModesConflict(ternaryRelOpts[i].gen.lockmode,
+ ternaryRelOpts[i].gen.lockmode));
+ j++;
+ }
+
for (i = 0; intRelOpts[i].gen.name; i++)
{
Assert(DoLockModesConflict(intRelOpts[i].gen.lockmode,
@@ -640,6 +653,14 @@ initialize_reloptions(void)
j++;
}
+ for (i = 0; ternaryRelOpts[i].gen.name; i++)
+ {
+ relOpts[j] = &ternaryRelOpts[i].gen;
+ relOpts[j]->type = RELOPT_TYPE_TERNARY;
+ relOpts[j]->namelen = strlen(relOpts[j]->name);
+ j++;
+ }
+
for (i = 0; intRelOpts[i].gen.name; i++)
{
relOpts[j] = &intRelOpts[i].gen;
@@ -800,6 +821,9 @@ allocate_reloption(bits32 kinds, int type, const char *name, const char *desc,
case RELOPT_TYPE_BOOL:
size = sizeof(relopt_bool);
break;
+ case RELOPT_TYPE_TERNARY:
+ size = sizeof(relopt_ternary);
+ break;
case RELOPT_TYPE_INT:
size = sizeof(relopt_int);
break;
@@ -883,6 +907,54 @@ add_local_bool_reloption(local_relopts *relopts, const char *name,
add_local_reloption(relopts, (relopt_gen *) newoption, offset);
}
+/*
+ * init_ternary_reloption
+ * Allocate and initialize a new ternary reloption
+ */
+static relopt_ternary *
+init_ternary_reloption(bits32 kinds, const char *name, const char *desc,
+ ternary default_val, LOCKMODE lockmode)
+{
+ relopt_ternary *newoption;
+
+ newoption = (relopt_ternary *) allocate_reloption(kinds,
+ RELOPT_TYPE_TERNARY, name, desc, lockmode);
+ newoption->default_val = default_val;
+
+ return newoption;
+}
+
+/*
+ * add_ternary_reloption
+ * Add a new ternary reloption
+ */
+void
+add_ternary_reloption(bits32 kinds, const char *name, const char *desc,
+ ternary default_val, LOCKMODE lockmode)
+{
+ relopt_ternary *newoption = init_ternary_reloption(kinds, name, desc,
+ default_val, lockmode);
+
+ add_reloption((relopt_gen *) newoption);
+}
+
+/*
+ * add_local_ternary_reloption
+ * Add a new ternary local reloption
+ *
+ * 'offset' is offset of ternary-typed field.
+ */
+void
+add_local_ternary_reloption(local_relopts *relopts, const char *name,
+ const char *desc, ternary default_val,
+ int offset)
+{
+ relopt_ternary *newoption = init_ternary_reloption(RELOPT_KIND_LOCAL,
+ name, desc,
+ default_val, 0);
+
+ add_local_reloption(relopts, (relopt_gen *) newoption, offset);
+}
/*
* init_real_reloption
@@ -1617,6 +1689,18 @@ parse_one_reloption(relopt_value *option, char *text_str, int text_len,
option->gen->name, value)));
}
break;
+ case RELOPT_TYPE_TERNARY:
+ {
+ bool b;
+ parsed = parse_bool(value, &b);
+ option->values.ternary_val = b ? TERNARY_TRUE : TERNARY_FALSE;
+ if (validate && !parsed)
+ ereport(ERROR,
+ (errcode(ERRCODE_INVALID_PARAMETER_VALUE),
+ errmsg("invalid value for ternary option \"%s\": %s",
+ option->gen->name, value)));
+ }
+ break;
case RELOPT_TYPE_INT:
{
relopt_int *optint = (relopt_int *) option->gen;
@@ -1780,17 +1864,6 @@ fillRelOptions(void *rdopts, Size basesize,
char *itempos = ((char *) rdopts) + elems[j].offset;
char *string_val;
- /*
- * If isset_offset is provided, store whether the reloption is
- * set there.
- */
- if (elems[j].isset_offset > 0)
- {
- char *setpos = ((char *) rdopts) + elems[j].isset_offset;
-
- *(bool *) setpos = options[i].isset;
- }
-
switch (options[i].gen->type)
{
case RELOPT_TYPE_BOOL:
@@ -1798,6 +1871,11 @@ fillRelOptions(void *rdopts, Size basesize,
options[i].values.bool_val :
((relopt_bool *) options[i].gen)->default_val;
break;
+ case RELOPT_TYPE_TERNARY:
+ *(ternary *) itempos = options[i].isset ?
+ options[i].values.ternary_val :
+ ((relopt_ternary *) options[i].gen)->default_val;
+ break;
case RELOPT_TYPE_INT:
*(int *) itempos = options[i].isset ?
options[i].values.int_val :
@@ -1912,8 +1990,8 @@ default_reloptions(Datum reloptions, bool validate, relopt_kind kind)
offsetof(StdRdOptions, parallel_workers)},
{"vacuum_index_cleanup", RELOPT_TYPE_ENUM,
offsetof(StdRdOptions, vacuum_index_cleanup)},
- {"vacuum_truncate", RELOPT_TYPE_BOOL,
- offsetof(StdRdOptions, vacuum_truncate), offsetof(StdRdOptions, vacuum_truncate_set)},
+ {"vacuum_truncate", RELOPT_TYPE_TERNARY,
+ offsetof(StdRdOptions, vacuum_truncate)},
{"vacuum_max_eager_freeze_failure_rate", RELOPT_TYPE_REAL,
offsetof(StdRdOptions, vacuum_max_eager_freeze_failure_rate)}
};
@@ -1993,7 +2071,6 @@ build_local_reloptions(local_relopts *relopts, Datum options, bool validate)
elems[i].optname = opt->option->name;
elems[i].opttype = opt->option->type;
elems[i].offset = opt->offset;
- elems[i].isset_offset = 0; /* not supported for local relopts yet */
i++;
}
diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c
index 733ef40ae7..977babff54 100644
--- a/src/backend/commands/vacuum.c
+++ b/src/backend/commands/vacuum.c
@@ -2221,9 +2221,9 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams params,
{
StdRdOptions *opts = (StdRdOptions *) rel->rd_options;
- if (opts && opts->vacuum_truncate_set)
+ if (opts && opts->vacuum_truncate != TERNARY_UNSET)
{
- if (opts->vacuum_truncate)
+ if (opts->vacuum_truncate == TERNARY_TRUE)
params.truncate = VACOPTVALUE_ENABLED;
else
params.truncate = VACOPTVALUE_DISABLED;
diff --git a/src/include/access/reloptions.h b/src/include/access/reloptions.h
index a604a4702c..a436697658 100644
--- a/src/include/access/reloptions.h
+++ b/src/include/access/reloptions.h
@@ -29,6 +29,7 @@
typedef enum relopt_type
{
RELOPT_TYPE_BOOL,
+ RELOPT_TYPE_TERNARY, /* on, off, unset */
RELOPT_TYPE_INT,
RELOPT_TYPE_REAL,
RELOPT_TYPE_ENUM,
@@ -80,6 +81,7 @@ typedef struct relopt_value
union
{
bool bool_val;
+ ternary ternary_val;
int int_val;
double real_val;
int enum_val;
@@ -94,6 +96,12 @@ typedef struct relopt_bool
bool default_val;
} relopt_bool;
+typedef struct relopt_rernary
+{
+ relopt_gen gen;
+ int default_val;
+} relopt_ternary;
+
typedef struct relopt_int
{
relopt_gen gen;
@@ -152,19 +160,6 @@ typedef struct
const char *optname; /* option's name */
relopt_type opttype; /* option's datatype */
int offset; /* offset of field in result struct */
-
- /*
- * isset_offset is an optional offset of a field in the result struct that
- * stores whether the option is explicitly set for the relation or if it
- * just picked up the default value. In most cases, this can be
- * accomplished by giving the reloption a special out-of-range default
- * value (e.g., some integer reloptions use -2), but this isn't always
- * possible. For example, a Boolean reloption cannot be given an
- * out-of-range default, so we need another way to discover the source of
- * its value. This offset is only used if given a value greater than
- * zero.
- */
- int isset_offset;
} relopt_parse_elt;
/* Local reloption definition */
@@ -195,6 +190,8 @@ typedef struct local_relopts
extern relopt_kind add_reloption_kind(void);
extern void add_bool_reloption(bits32 kinds, const char *name, const char *desc,
bool default_val, LOCKMODE lockmode);
+extern void add_ternary_reloption(bits32 kinds, const char *name,
+ const char *desc, int default_val, LOCKMODE lockmode);
extern void add_int_reloption(bits32 kinds, const char *name, const char *desc,
int default_val, int min_val, int max_val,
LOCKMODE lockmode);
@@ -214,6 +211,9 @@ extern void register_reloptions_validator(local_relopts *relopts,
extern void add_local_bool_reloption(local_relopts *relopts, const char *name,
const char *desc, bool default_val,
int offset);
+extern void add_local_ternary_reloption(local_relopts *relopts,
+ const char *name, const char *desc,
+ ternary default_val, int offset);
extern void add_local_int_reloption(local_relopts *relopts, const char *name,
const char *desc, int default_val,
int min_val, int max_val, int offset);
diff --git a/src/include/c.h b/src/include/c.h
index 39022f8a9d..9c59ccccf8 100644
--- a/src/include/c.h
+++ b/src/include/c.h
@@ -488,6 +488,22 @@ typedef void (*pg_funcptr_t) (void);
#include <stdbool.h>
+/*
+ * ternary
+ * Boolean value with an extrea "unset" option
+ *
+ * Ternary data type is used in relation options that can be "true", "false" or
+ * "unset". Since relation options are used deep inside the PostgreSQL code,
+ * this type is declared globally.
+*/
+
+typedef enum ternary
+{
+ TERNARY_FALSE = 0,
+ TERNARY_TRUE = 1,
+ TERNARY_UNSET = -1
+} ternary;
+
/* ----------------------------------------------------------------
* Section 3: standard system types
diff --git a/src/include/utils/rel.h b/src/include/utils/rel.h
index b552359915..7346d618f9 100644
--- a/src/include/utils/rel.h
+++ b/src/include/utils/rel.h
@@ -346,8 +346,7 @@ typedef struct StdRdOptions
bool user_catalog_table; /* use as an additional catalog relation */
int parallel_workers; /* max number of parallel workers */
StdRdOptIndexCleanup vacuum_index_cleanup; /* controls index vacuuming */
- bool vacuum_truncate; /* enables vacuum to truncate a relation */
- bool vacuum_truncate_set; /* whether vacuum_truncate is set */
+ ternary vacuum_truncate; /* enables vacuum to truncate a relation */
/*
* Fraction of pages in a relation that vacuum can eagerly scan and fail
--
2.39.2
--nextPart5129932.5fSG56mABF
Content-Disposition: attachment;
filename="v2-0003-Add-alias-to-be-used-as-unset-state.patch"
Content-Transfer-Encoding: 7Bit
Content-Type: text/x-patch; charset="unicode-2-0-utf-8";
name="v2-0003-Add-alias-to-be-used-as-unset-state.patch"
^ permalink raw reply [nested|flat] 868+ messages in thread
* [PATCH v2 2/4] Introduce ternary reloptions
@ 2025-09-04 14:41 Nikolay Shaplov <[email protected]>
0 siblings, 0 replies; 868+ messages in thread
From: Nikolay Shaplov @ 2025-09-04 14:41 UTC (permalink / raw)
Introduce ternary reloption as a replacement for current `vacuum_truncate`
implementation. Remove `vacuum_truncate_set` additional flag and using
`TERNARY_UNSET` value instead.
---
src/backend/access/common/reloptions.c | 129 ++++++++++++++++++++-----
src/backend/commands/vacuum.c | 4 +-
src/include/access/reloptions.h | 26 ++---
src/include/c.h | 16 +++
src/include/utils/rel.h | 3 +-
5 files changed, 135 insertions(+), 43 deletions(-)
diff --git a/src/backend/access/common/reloptions.c b/src/backend/access/common/reloptions.c
index 0af3fea68f..f543f11001 100644
--- a/src/backend/access/common/reloptions.c
+++ b/src/backend/access/common/reloptions.c
@@ -40,9 +40,9 @@
*
* To add an option:
*
- * (i) decide on a type (bool, integer, real, enum, string), name, default
- * value, upper and lower bounds (if applicable); for strings, consider a
- * validation routine.
+ * (i) decide on a type (bool, ternary, integer, real, enum, string), name,
+ * default value, upper and lower bounds (if applicable); for strings,
+ * consider a validation routine.
* (ii) add a record below (or use add_<type>_reloption).
* (iii) add it to the appropriate options struct (perhaps StdRdOptions)
* (iv) add it to the appropriate handling routine (perhaps
@@ -147,15 +147,6 @@ static relopt_bool boolRelOpts[] =
},
false
},
- {
- {
- "vacuum_truncate",
- "Enables vacuum to truncate empty pages at the end of this table",
- RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
- ShareUpdateExclusiveLock
- },
- true
- },
{
{
"deduplicate_items",
@@ -170,6 +161,21 @@ static relopt_bool boolRelOpts[] =
{{NULL}}
};
+static relopt_ternary ternaryRelOpts[] =
+{
+ {
+ {
+ "vacuum_truncate",
+ "Enables vacuum to truncate empty pages at the end of this table",
+ RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
+ ShareUpdateExclusiveLock
+ },
+ TERNARY_UNSET
+ },
+ /* list terminator */
+ {{NULL}}
+};
+
static relopt_int intRelOpts[] =
{
{
@@ -600,6 +606,13 @@ initialize_reloptions(void)
boolRelOpts[i].gen.lockmode));
j++;
}
+ for (i = 0; ternaryRelOpts[i].gen.name; i++)
+ {
+ Assert(DoLockModesConflict(ternaryRelOpts[i].gen.lockmode,
+ ternaryRelOpts[i].gen.lockmode));
+ j++;
+ }
+
for (i = 0; intRelOpts[i].gen.name; i++)
{
Assert(DoLockModesConflict(intRelOpts[i].gen.lockmode,
@@ -640,6 +653,14 @@ initialize_reloptions(void)
j++;
}
+ for (i = 0; ternaryRelOpts[i].gen.name; i++)
+ {
+ relOpts[j] = &ternaryRelOpts[i].gen;
+ relOpts[j]->type = RELOPT_TYPE_TERNARY;
+ relOpts[j]->namelen = strlen(relOpts[j]->name);
+ j++;
+ }
+
for (i = 0; intRelOpts[i].gen.name; i++)
{
relOpts[j] = &intRelOpts[i].gen;
@@ -800,6 +821,9 @@ allocate_reloption(bits32 kinds, int type, const char *name, const char *desc,
case RELOPT_TYPE_BOOL:
size = sizeof(relopt_bool);
break;
+ case RELOPT_TYPE_TERNARY:
+ size = sizeof(relopt_ternary);
+ break;
case RELOPT_TYPE_INT:
size = sizeof(relopt_int);
break;
@@ -883,6 +907,54 @@ add_local_bool_reloption(local_relopts *relopts, const char *name,
add_local_reloption(relopts, (relopt_gen *) newoption, offset);
}
+/*
+ * init_ternary_reloption
+ * Allocate and initialize a new ternary reloption
+ */
+static relopt_ternary *
+init_ternary_reloption(bits32 kinds, const char *name, const char *desc,
+ ternary default_val, LOCKMODE lockmode)
+{
+ relopt_ternary *newoption;
+
+ newoption = (relopt_ternary *) allocate_reloption(kinds,
+ RELOPT_TYPE_TERNARY, name, desc, lockmode);
+ newoption->default_val = default_val;
+
+ return newoption;
+}
+
+/*
+ * add_ternary_reloption
+ * Add a new ternary reloption
+ */
+void
+add_ternary_reloption(bits32 kinds, const char *name, const char *desc,
+ ternary default_val, LOCKMODE lockmode)
+{
+ relopt_ternary *newoption = init_ternary_reloption(kinds, name, desc,
+ default_val, lockmode);
+
+ add_reloption((relopt_gen *) newoption);
+}
+
+/*
+ * add_local_ternary_reloption
+ * Add a new ternary local reloption
+ *
+ * 'offset' is offset of ternary-typed field.
+ */
+void
+add_local_ternary_reloption(local_relopts *relopts, const char *name,
+ const char *desc, ternary default_val,
+ int offset)
+{
+ relopt_ternary *newoption = init_ternary_reloption(RELOPT_KIND_LOCAL,
+ name, desc,
+ default_val, 0);
+
+ add_local_reloption(relopts, (relopt_gen *) newoption, offset);
+}
/*
* init_real_reloption
@@ -1617,6 +1689,18 @@ parse_one_reloption(relopt_value *option, char *text_str, int text_len,
option->gen->name, value)));
}
break;
+ case RELOPT_TYPE_TERNARY:
+ {
+ bool b;
+ parsed = parse_bool(value, &b);
+ option->values.ternary_val = b ? TERNARY_TRUE : TERNARY_FALSE;
+ if (validate && !parsed)
+ ereport(ERROR,
+ (errcode(ERRCODE_INVALID_PARAMETER_VALUE),
+ errmsg("invalid value for ternary option \"%s\": %s",
+ option->gen->name, value)));
+ }
+ break;
case RELOPT_TYPE_INT:
{
relopt_int *optint = (relopt_int *) option->gen;
@@ -1780,17 +1864,6 @@ fillRelOptions(void *rdopts, Size basesize,
char *itempos = ((char *) rdopts) + elems[j].offset;
char *string_val;
- /*
- * If isset_offset is provided, store whether the reloption is
- * set there.
- */
- if (elems[j].isset_offset > 0)
- {
- char *setpos = ((char *) rdopts) + elems[j].isset_offset;
-
- *(bool *) setpos = options[i].isset;
- }
-
switch (options[i].gen->type)
{
case RELOPT_TYPE_BOOL:
@@ -1798,6 +1871,11 @@ fillRelOptions(void *rdopts, Size basesize,
options[i].values.bool_val :
((relopt_bool *) options[i].gen)->default_val;
break;
+ case RELOPT_TYPE_TERNARY:
+ *(ternary *) itempos = options[i].isset ?
+ options[i].values.ternary_val :
+ ((relopt_ternary *) options[i].gen)->default_val;
+ break;
case RELOPT_TYPE_INT:
*(int *) itempos = options[i].isset ?
options[i].values.int_val :
@@ -1912,8 +1990,8 @@ default_reloptions(Datum reloptions, bool validate, relopt_kind kind)
offsetof(StdRdOptions, parallel_workers)},
{"vacuum_index_cleanup", RELOPT_TYPE_ENUM,
offsetof(StdRdOptions, vacuum_index_cleanup)},
- {"vacuum_truncate", RELOPT_TYPE_BOOL,
- offsetof(StdRdOptions, vacuum_truncate), offsetof(StdRdOptions, vacuum_truncate_set)},
+ {"vacuum_truncate", RELOPT_TYPE_TERNARY,
+ offsetof(StdRdOptions, vacuum_truncate)},
{"vacuum_max_eager_freeze_failure_rate", RELOPT_TYPE_REAL,
offsetof(StdRdOptions, vacuum_max_eager_freeze_failure_rate)}
};
@@ -1993,7 +2071,6 @@ build_local_reloptions(local_relopts *relopts, Datum options, bool validate)
elems[i].optname = opt->option->name;
elems[i].opttype = opt->option->type;
elems[i].offset = opt->offset;
- elems[i].isset_offset = 0; /* not supported for local relopts yet */
i++;
}
diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c
index 733ef40ae7..977babff54 100644
--- a/src/backend/commands/vacuum.c
+++ b/src/backend/commands/vacuum.c
@@ -2221,9 +2221,9 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams params,
{
StdRdOptions *opts = (StdRdOptions *) rel->rd_options;
- if (opts && opts->vacuum_truncate_set)
+ if (opts && opts->vacuum_truncate != TERNARY_UNSET)
{
- if (opts->vacuum_truncate)
+ if (opts->vacuum_truncate == TERNARY_TRUE)
params.truncate = VACOPTVALUE_ENABLED;
else
params.truncate = VACOPTVALUE_DISABLED;
diff --git a/src/include/access/reloptions.h b/src/include/access/reloptions.h
index a604a4702c..a436697658 100644
--- a/src/include/access/reloptions.h
+++ b/src/include/access/reloptions.h
@@ -29,6 +29,7 @@
typedef enum relopt_type
{
RELOPT_TYPE_BOOL,
+ RELOPT_TYPE_TERNARY, /* on, off, unset */
RELOPT_TYPE_INT,
RELOPT_TYPE_REAL,
RELOPT_TYPE_ENUM,
@@ -80,6 +81,7 @@ typedef struct relopt_value
union
{
bool bool_val;
+ ternary ternary_val;
int int_val;
double real_val;
int enum_val;
@@ -94,6 +96,12 @@ typedef struct relopt_bool
bool default_val;
} relopt_bool;
+typedef struct relopt_rernary
+{
+ relopt_gen gen;
+ int default_val;
+} relopt_ternary;
+
typedef struct relopt_int
{
relopt_gen gen;
@@ -152,19 +160,6 @@ typedef struct
const char *optname; /* option's name */
relopt_type opttype; /* option's datatype */
int offset; /* offset of field in result struct */
-
- /*
- * isset_offset is an optional offset of a field in the result struct that
- * stores whether the option is explicitly set for the relation or if it
- * just picked up the default value. In most cases, this can be
- * accomplished by giving the reloption a special out-of-range default
- * value (e.g., some integer reloptions use -2), but this isn't always
- * possible. For example, a Boolean reloption cannot be given an
- * out-of-range default, so we need another way to discover the source of
- * its value. This offset is only used if given a value greater than
- * zero.
- */
- int isset_offset;
} relopt_parse_elt;
/* Local reloption definition */
@@ -195,6 +190,8 @@ typedef struct local_relopts
extern relopt_kind add_reloption_kind(void);
extern void add_bool_reloption(bits32 kinds, const char *name, const char *desc,
bool default_val, LOCKMODE lockmode);
+extern void add_ternary_reloption(bits32 kinds, const char *name,
+ const char *desc, int default_val, LOCKMODE lockmode);
extern void add_int_reloption(bits32 kinds, const char *name, const char *desc,
int default_val, int min_val, int max_val,
LOCKMODE lockmode);
@@ -214,6 +211,9 @@ extern void register_reloptions_validator(local_relopts *relopts,
extern void add_local_bool_reloption(local_relopts *relopts, const char *name,
const char *desc, bool default_val,
int offset);
+extern void add_local_ternary_reloption(local_relopts *relopts,
+ const char *name, const char *desc,
+ ternary default_val, int offset);
extern void add_local_int_reloption(local_relopts *relopts, const char *name,
const char *desc, int default_val,
int min_val, int max_val, int offset);
diff --git a/src/include/c.h b/src/include/c.h
index 39022f8a9d..9c59ccccf8 100644
--- a/src/include/c.h
+++ b/src/include/c.h
@@ -488,6 +488,22 @@ typedef void (*pg_funcptr_t) (void);
#include <stdbool.h>
+/*
+ * ternary
+ * Boolean value with an extrea "unset" option
+ *
+ * Ternary data type is used in relation options that can be "true", "false" or
+ * "unset". Since relation options are used deep inside the PostgreSQL code,
+ * this type is declared globally.
+*/
+
+typedef enum ternary
+{
+ TERNARY_FALSE = 0,
+ TERNARY_TRUE = 1,
+ TERNARY_UNSET = -1
+} ternary;
+
/* ----------------------------------------------------------------
* Section 3: standard system types
diff --git a/src/include/utils/rel.h b/src/include/utils/rel.h
index b552359915..7346d618f9 100644
--- a/src/include/utils/rel.h
+++ b/src/include/utils/rel.h
@@ -346,8 +346,7 @@ typedef struct StdRdOptions
bool user_catalog_table; /* use as an additional catalog relation */
int parallel_workers; /* max number of parallel workers */
StdRdOptIndexCleanup vacuum_index_cleanup; /* controls index vacuuming */
- bool vacuum_truncate; /* enables vacuum to truncate a relation */
- bool vacuum_truncate_set; /* whether vacuum_truncate is set */
+ ternary vacuum_truncate; /* enables vacuum to truncate a relation */
/*
* Fraction of pages in a relation that vacuum can eagerly scan and fail
--
2.39.2
--nextPart5129932.5fSG56mABF
Content-Disposition: attachment;
filename="v2-0003-Add-alias-to-be-used-as-unset-state.patch"
Content-Transfer-Encoding: 7Bit
Content-Type: text/x-patch; charset="unicode-2-0-utf-8";
name="v2-0003-Add-alias-to-be-used-as-unset-state.patch"
^ permalink raw reply [nested|flat] 868+ messages in thread
* [PATCH v2 2/4] Introduce ternary reloptions
@ 2025-09-04 14:41 Nikolay Shaplov <[email protected]>
0 siblings, 0 replies; 868+ messages in thread
From: Nikolay Shaplov @ 2025-09-04 14:41 UTC (permalink / raw)
Introduce ternary reloption as a replacement for current `vacuum_truncate`
implementation. Remove `vacuum_truncate_set` additional flag and using
`TERNARY_UNSET` value instead.
---
src/backend/access/common/reloptions.c | 129 ++++++++++++++++++++-----
src/backend/commands/vacuum.c | 4 +-
src/include/access/reloptions.h | 26 ++---
src/include/c.h | 16 +++
src/include/utils/rel.h | 3 +-
5 files changed, 135 insertions(+), 43 deletions(-)
diff --git a/src/backend/access/common/reloptions.c b/src/backend/access/common/reloptions.c
index 0af3fea68f..f543f11001 100644
--- a/src/backend/access/common/reloptions.c
+++ b/src/backend/access/common/reloptions.c
@@ -40,9 +40,9 @@
*
* To add an option:
*
- * (i) decide on a type (bool, integer, real, enum, string), name, default
- * value, upper and lower bounds (if applicable); for strings, consider a
- * validation routine.
+ * (i) decide on a type (bool, ternary, integer, real, enum, string), name,
+ * default value, upper and lower bounds (if applicable); for strings,
+ * consider a validation routine.
* (ii) add a record below (or use add_<type>_reloption).
* (iii) add it to the appropriate options struct (perhaps StdRdOptions)
* (iv) add it to the appropriate handling routine (perhaps
@@ -147,15 +147,6 @@ static relopt_bool boolRelOpts[] =
},
false
},
- {
- {
- "vacuum_truncate",
- "Enables vacuum to truncate empty pages at the end of this table",
- RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
- ShareUpdateExclusiveLock
- },
- true
- },
{
{
"deduplicate_items",
@@ -170,6 +161,21 @@ static relopt_bool boolRelOpts[] =
{{NULL}}
};
+static relopt_ternary ternaryRelOpts[] =
+{
+ {
+ {
+ "vacuum_truncate",
+ "Enables vacuum to truncate empty pages at the end of this table",
+ RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
+ ShareUpdateExclusiveLock
+ },
+ TERNARY_UNSET
+ },
+ /* list terminator */
+ {{NULL}}
+};
+
static relopt_int intRelOpts[] =
{
{
@@ -600,6 +606,13 @@ initialize_reloptions(void)
boolRelOpts[i].gen.lockmode));
j++;
}
+ for (i = 0; ternaryRelOpts[i].gen.name; i++)
+ {
+ Assert(DoLockModesConflict(ternaryRelOpts[i].gen.lockmode,
+ ternaryRelOpts[i].gen.lockmode));
+ j++;
+ }
+
for (i = 0; intRelOpts[i].gen.name; i++)
{
Assert(DoLockModesConflict(intRelOpts[i].gen.lockmode,
@@ -640,6 +653,14 @@ initialize_reloptions(void)
j++;
}
+ for (i = 0; ternaryRelOpts[i].gen.name; i++)
+ {
+ relOpts[j] = &ternaryRelOpts[i].gen;
+ relOpts[j]->type = RELOPT_TYPE_TERNARY;
+ relOpts[j]->namelen = strlen(relOpts[j]->name);
+ j++;
+ }
+
for (i = 0; intRelOpts[i].gen.name; i++)
{
relOpts[j] = &intRelOpts[i].gen;
@@ -800,6 +821,9 @@ allocate_reloption(bits32 kinds, int type, const char *name, const char *desc,
case RELOPT_TYPE_BOOL:
size = sizeof(relopt_bool);
break;
+ case RELOPT_TYPE_TERNARY:
+ size = sizeof(relopt_ternary);
+ break;
case RELOPT_TYPE_INT:
size = sizeof(relopt_int);
break;
@@ -883,6 +907,54 @@ add_local_bool_reloption(local_relopts *relopts, const char *name,
add_local_reloption(relopts, (relopt_gen *) newoption, offset);
}
+/*
+ * init_ternary_reloption
+ * Allocate and initialize a new ternary reloption
+ */
+static relopt_ternary *
+init_ternary_reloption(bits32 kinds, const char *name, const char *desc,
+ ternary default_val, LOCKMODE lockmode)
+{
+ relopt_ternary *newoption;
+
+ newoption = (relopt_ternary *) allocate_reloption(kinds,
+ RELOPT_TYPE_TERNARY, name, desc, lockmode);
+ newoption->default_val = default_val;
+
+ return newoption;
+}
+
+/*
+ * add_ternary_reloption
+ * Add a new ternary reloption
+ */
+void
+add_ternary_reloption(bits32 kinds, const char *name, const char *desc,
+ ternary default_val, LOCKMODE lockmode)
+{
+ relopt_ternary *newoption = init_ternary_reloption(kinds, name, desc,
+ default_val, lockmode);
+
+ add_reloption((relopt_gen *) newoption);
+}
+
+/*
+ * add_local_ternary_reloption
+ * Add a new ternary local reloption
+ *
+ * 'offset' is offset of ternary-typed field.
+ */
+void
+add_local_ternary_reloption(local_relopts *relopts, const char *name,
+ const char *desc, ternary default_val,
+ int offset)
+{
+ relopt_ternary *newoption = init_ternary_reloption(RELOPT_KIND_LOCAL,
+ name, desc,
+ default_val, 0);
+
+ add_local_reloption(relopts, (relopt_gen *) newoption, offset);
+}
/*
* init_real_reloption
@@ -1617,6 +1689,18 @@ parse_one_reloption(relopt_value *option, char *text_str, int text_len,
option->gen->name, value)));
}
break;
+ case RELOPT_TYPE_TERNARY:
+ {
+ bool b;
+ parsed = parse_bool(value, &b);
+ option->values.ternary_val = b ? TERNARY_TRUE : TERNARY_FALSE;
+ if (validate && !parsed)
+ ereport(ERROR,
+ (errcode(ERRCODE_INVALID_PARAMETER_VALUE),
+ errmsg("invalid value for ternary option \"%s\": %s",
+ option->gen->name, value)));
+ }
+ break;
case RELOPT_TYPE_INT:
{
relopt_int *optint = (relopt_int *) option->gen;
@@ -1780,17 +1864,6 @@ fillRelOptions(void *rdopts, Size basesize,
char *itempos = ((char *) rdopts) + elems[j].offset;
char *string_val;
- /*
- * If isset_offset is provided, store whether the reloption is
- * set there.
- */
- if (elems[j].isset_offset > 0)
- {
- char *setpos = ((char *) rdopts) + elems[j].isset_offset;
-
- *(bool *) setpos = options[i].isset;
- }
-
switch (options[i].gen->type)
{
case RELOPT_TYPE_BOOL:
@@ -1798,6 +1871,11 @@ fillRelOptions(void *rdopts, Size basesize,
options[i].values.bool_val :
((relopt_bool *) options[i].gen)->default_val;
break;
+ case RELOPT_TYPE_TERNARY:
+ *(ternary *) itempos = options[i].isset ?
+ options[i].values.ternary_val :
+ ((relopt_ternary *) options[i].gen)->default_val;
+ break;
case RELOPT_TYPE_INT:
*(int *) itempos = options[i].isset ?
options[i].values.int_val :
@@ -1912,8 +1990,8 @@ default_reloptions(Datum reloptions, bool validate, relopt_kind kind)
offsetof(StdRdOptions, parallel_workers)},
{"vacuum_index_cleanup", RELOPT_TYPE_ENUM,
offsetof(StdRdOptions, vacuum_index_cleanup)},
- {"vacuum_truncate", RELOPT_TYPE_BOOL,
- offsetof(StdRdOptions, vacuum_truncate), offsetof(StdRdOptions, vacuum_truncate_set)},
+ {"vacuum_truncate", RELOPT_TYPE_TERNARY,
+ offsetof(StdRdOptions, vacuum_truncate)},
{"vacuum_max_eager_freeze_failure_rate", RELOPT_TYPE_REAL,
offsetof(StdRdOptions, vacuum_max_eager_freeze_failure_rate)}
};
@@ -1993,7 +2071,6 @@ build_local_reloptions(local_relopts *relopts, Datum options, bool validate)
elems[i].optname = opt->option->name;
elems[i].opttype = opt->option->type;
elems[i].offset = opt->offset;
- elems[i].isset_offset = 0; /* not supported for local relopts yet */
i++;
}
diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c
index 733ef40ae7..977babff54 100644
--- a/src/backend/commands/vacuum.c
+++ b/src/backend/commands/vacuum.c
@@ -2221,9 +2221,9 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams params,
{
StdRdOptions *opts = (StdRdOptions *) rel->rd_options;
- if (opts && opts->vacuum_truncate_set)
+ if (opts && opts->vacuum_truncate != TERNARY_UNSET)
{
- if (opts->vacuum_truncate)
+ if (opts->vacuum_truncate == TERNARY_TRUE)
params.truncate = VACOPTVALUE_ENABLED;
else
params.truncate = VACOPTVALUE_DISABLED;
diff --git a/src/include/access/reloptions.h b/src/include/access/reloptions.h
index a604a4702c..a436697658 100644
--- a/src/include/access/reloptions.h
+++ b/src/include/access/reloptions.h
@@ -29,6 +29,7 @@
typedef enum relopt_type
{
RELOPT_TYPE_BOOL,
+ RELOPT_TYPE_TERNARY, /* on, off, unset */
RELOPT_TYPE_INT,
RELOPT_TYPE_REAL,
RELOPT_TYPE_ENUM,
@@ -80,6 +81,7 @@ typedef struct relopt_value
union
{
bool bool_val;
+ ternary ternary_val;
int int_val;
double real_val;
int enum_val;
@@ -94,6 +96,12 @@ typedef struct relopt_bool
bool default_val;
} relopt_bool;
+typedef struct relopt_rernary
+{
+ relopt_gen gen;
+ int default_val;
+} relopt_ternary;
+
typedef struct relopt_int
{
relopt_gen gen;
@@ -152,19 +160,6 @@ typedef struct
const char *optname; /* option's name */
relopt_type opttype; /* option's datatype */
int offset; /* offset of field in result struct */
-
- /*
- * isset_offset is an optional offset of a field in the result struct that
- * stores whether the option is explicitly set for the relation or if it
- * just picked up the default value. In most cases, this can be
- * accomplished by giving the reloption a special out-of-range default
- * value (e.g., some integer reloptions use -2), but this isn't always
- * possible. For example, a Boolean reloption cannot be given an
- * out-of-range default, so we need another way to discover the source of
- * its value. This offset is only used if given a value greater than
- * zero.
- */
- int isset_offset;
} relopt_parse_elt;
/* Local reloption definition */
@@ -195,6 +190,8 @@ typedef struct local_relopts
extern relopt_kind add_reloption_kind(void);
extern void add_bool_reloption(bits32 kinds, const char *name, const char *desc,
bool default_val, LOCKMODE lockmode);
+extern void add_ternary_reloption(bits32 kinds, const char *name,
+ const char *desc, int default_val, LOCKMODE lockmode);
extern void add_int_reloption(bits32 kinds, const char *name, const char *desc,
int default_val, int min_val, int max_val,
LOCKMODE lockmode);
@@ -214,6 +211,9 @@ extern void register_reloptions_validator(local_relopts *relopts,
extern void add_local_bool_reloption(local_relopts *relopts, const char *name,
const char *desc, bool default_val,
int offset);
+extern void add_local_ternary_reloption(local_relopts *relopts,
+ const char *name, const char *desc,
+ ternary default_val, int offset);
extern void add_local_int_reloption(local_relopts *relopts, const char *name,
const char *desc, int default_val,
int min_val, int max_val, int offset);
diff --git a/src/include/c.h b/src/include/c.h
index 39022f8a9d..9c59ccccf8 100644
--- a/src/include/c.h
+++ b/src/include/c.h
@@ -488,6 +488,22 @@ typedef void (*pg_funcptr_t) (void);
#include <stdbool.h>
+/*
+ * ternary
+ * Boolean value with an extrea "unset" option
+ *
+ * Ternary data type is used in relation options that can be "true", "false" or
+ * "unset". Since relation options are used deep inside the PostgreSQL code,
+ * this type is declared globally.
+*/
+
+typedef enum ternary
+{
+ TERNARY_FALSE = 0,
+ TERNARY_TRUE = 1,
+ TERNARY_UNSET = -1
+} ternary;
+
/* ----------------------------------------------------------------
* Section 3: standard system types
diff --git a/src/include/utils/rel.h b/src/include/utils/rel.h
index b552359915..7346d618f9 100644
--- a/src/include/utils/rel.h
+++ b/src/include/utils/rel.h
@@ -346,8 +346,7 @@ typedef struct StdRdOptions
bool user_catalog_table; /* use as an additional catalog relation */
int parallel_workers; /* max number of parallel workers */
StdRdOptIndexCleanup vacuum_index_cleanup; /* controls index vacuuming */
- bool vacuum_truncate; /* enables vacuum to truncate a relation */
- bool vacuum_truncate_set; /* whether vacuum_truncate is set */
+ ternary vacuum_truncate; /* enables vacuum to truncate a relation */
/*
* Fraction of pages in a relation that vacuum can eagerly scan and fail
--
2.39.2
--nextPart5129932.5fSG56mABF
Content-Disposition: attachment;
filename="v2-0003-Add-alias-to-be-used-as-unset-state.patch"
Content-Transfer-Encoding: 7Bit
Content-Type: text/x-patch; charset="unicode-2-0-utf-8";
name="v2-0003-Add-alias-to-be-used-as-unset-state.patch"
^ permalink raw reply [nested|flat] 868+ messages in thread
* [PATCH v2 2/4] Introduce ternary reloptions
@ 2025-09-04 14:41 Nikolay Shaplov <[email protected]>
0 siblings, 0 replies; 868+ messages in thread
From: Nikolay Shaplov @ 2025-09-04 14:41 UTC (permalink / raw)
Introduce ternary reloption as a replacement for current `vacuum_truncate`
implementation. Remove `vacuum_truncate_set` additional flag and using
`TERNARY_UNSET` value instead.
---
src/backend/access/common/reloptions.c | 129 ++++++++++++++++++++-----
src/backend/commands/vacuum.c | 4 +-
src/include/access/reloptions.h | 26 ++---
src/include/c.h | 16 +++
src/include/utils/rel.h | 3 +-
5 files changed, 135 insertions(+), 43 deletions(-)
diff --git a/src/backend/access/common/reloptions.c b/src/backend/access/common/reloptions.c
index 0af3fea68f..f543f11001 100644
--- a/src/backend/access/common/reloptions.c
+++ b/src/backend/access/common/reloptions.c
@@ -40,9 +40,9 @@
*
* To add an option:
*
- * (i) decide on a type (bool, integer, real, enum, string), name, default
- * value, upper and lower bounds (if applicable); for strings, consider a
- * validation routine.
+ * (i) decide on a type (bool, ternary, integer, real, enum, string), name,
+ * default value, upper and lower bounds (if applicable); for strings,
+ * consider a validation routine.
* (ii) add a record below (or use add_<type>_reloption).
* (iii) add it to the appropriate options struct (perhaps StdRdOptions)
* (iv) add it to the appropriate handling routine (perhaps
@@ -147,15 +147,6 @@ static relopt_bool boolRelOpts[] =
},
false
},
- {
- {
- "vacuum_truncate",
- "Enables vacuum to truncate empty pages at the end of this table",
- RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
- ShareUpdateExclusiveLock
- },
- true
- },
{
{
"deduplicate_items",
@@ -170,6 +161,21 @@ static relopt_bool boolRelOpts[] =
{{NULL}}
};
+static relopt_ternary ternaryRelOpts[] =
+{
+ {
+ {
+ "vacuum_truncate",
+ "Enables vacuum to truncate empty pages at the end of this table",
+ RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
+ ShareUpdateExclusiveLock
+ },
+ TERNARY_UNSET
+ },
+ /* list terminator */
+ {{NULL}}
+};
+
static relopt_int intRelOpts[] =
{
{
@@ -600,6 +606,13 @@ initialize_reloptions(void)
boolRelOpts[i].gen.lockmode));
j++;
}
+ for (i = 0; ternaryRelOpts[i].gen.name; i++)
+ {
+ Assert(DoLockModesConflict(ternaryRelOpts[i].gen.lockmode,
+ ternaryRelOpts[i].gen.lockmode));
+ j++;
+ }
+
for (i = 0; intRelOpts[i].gen.name; i++)
{
Assert(DoLockModesConflict(intRelOpts[i].gen.lockmode,
@@ -640,6 +653,14 @@ initialize_reloptions(void)
j++;
}
+ for (i = 0; ternaryRelOpts[i].gen.name; i++)
+ {
+ relOpts[j] = &ternaryRelOpts[i].gen;
+ relOpts[j]->type = RELOPT_TYPE_TERNARY;
+ relOpts[j]->namelen = strlen(relOpts[j]->name);
+ j++;
+ }
+
for (i = 0; intRelOpts[i].gen.name; i++)
{
relOpts[j] = &intRelOpts[i].gen;
@@ -800,6 +821,9 @@ allocate_reloption(bits32 kinds, int type, const char *name, const char *desc,
case RELOPT_TYPE_BOOL:
size = sizeof(relopt_bool);
break;
+ case RELOPT_TYPE_TERNARY:
+ size = sizeof(relopt_ternary);
+ break;
case RELOPT_TYPE_INT:
size = sizeof(relopt_int);
break;
@@ -883,6 +907,54 @@ add_local_bool_reloption(local_relopts *relopts, const char *name,
add_local_reloption(relopts, (relopt_gen *) newoption, offset);
}
+/*
+ * init_ternary_reloption
+ * Allocate and initialize a new ternary reloption
+ */
+static relopt_ternary *
+init_ternary_reloption(bits32 kinds, const char *name, const char *desc,
+ ternary default_val, LOCKMODE lockmode)
+{
+ relopt_ternary *newoption;
+
+ newoption = (relopt_ternary *) allocate_reloption(kinds,
+ RELOPT_TYPE_TERNARY, name, desc, lockmode);
+ newoption->default_val = default_val;
+
+ return newoption;
+}
+
+/*
+ * add_ternary_reloption
+ * Add a new ternary reloption
+ */
+void
+add_ternary_reloption(bits32 kinds, const char *name, const char *desc,
+ ternary default_val, LOCKMODE lockmode)
+{
+ relopt_ternary *newoption = init_ternary_reloption(kinds, name, desc,
+ default_val, lockmode);
+
+ add_reloption((relopt_gen *) newoption);
+}
+
+/*
+ * add_local_ternary_reloption
+ * Add a new ternary local reloption
+ *
+ * 'offset' is offset of ternary-typed field.
+ */
+void
+add_local_ternary_reloption(local_relopts *relopts, const char *name,
+ const char *desc, ternary default_val,
+ int offset)
+{
+ relopt_ternary *newoption = init_ternary_reloption(RELOPT_KIND_LOCAL,
+ name, desc,
+ default_val, 0);
+
+ add_local_reloption(relopts, (relopt_gen *) newoption, offset);
+}
/*
* init_real_reloption
@@ -1617,6 +1689,18 @@ parse_one_reloption(relopt_value *option, char *text_str, int text_len,
option->gen->name, value)));
}
break;
+ case RELOPT_TYPE_TERNARY:
+ {
+ bool b;
+ parsed = parse_bool(value, &b);
+ option->values.ternary_val = b ? TERNARY_TRUE : TERNARY_FALSE;
+ if (validate && !parsed)
+ ereport(ERROR,
+ (errcode(ERRCODE_INVALID_PARAMETER_VALUE),
+ errmsg("invalid value for ternary option \"%s\": %s",
+ option->gen->name, value)));
+ }
+ break;
case RELOPT_TYPE_INT:
{
relopt_int *optint = (relopt_int *) option->gen;
@@ -1780,17 +1864,6 @@ fillRelOptions(void *rdopts, Size basesize,
char *itempos = ((char *) rdopts) + elems[j].offset;
char *string_val;
- /*
- * If isset_offset is provided, store whether the reloption is
- * set there.
- */
- if (elems[j].isset_offset > 0)
- {
- char *setpos = ((char *) rdopts) + elems[j].isset_offset;
-
- *(bool *) setpos = options[i].isset;
- }
-
switch (options[i].gen->type)
{
case RELOPT_TYPE_BOOL:
@@ -1798,6 +1871,11 @@ fillRelOptions(void *rdopts, Size basesize,
options[i].values.bool_val :
((relopt_bool *) options[i].gen)->default_val;
break;
+ case RELOPT_TYPE_TERNARY:
+ *(ternary *) itempos = options[i].isset ?
+ options[i].values.ternary_val :
+ ((relopt_ternary *) options[i].gen)->default_val;
+ break;
case RELOPT_TYPE_INT:
*(int *) itempos = options[i].isset ?
options[i].values.int_val :
@@ -1912,8 +1990,8 @@ default_reloptions(Datum reloptions, bool validate, relopt_kind kind)
offsetof(StdRdOptions, parallel_workers)},
{"vacuum_index_cleanup", RELOPT_TYPE_ENUM,
offsetof(StdRdOptions, vacuum_index_cleanup)},
- {"vacuum_truncate", RELOPT_TYPE_BOOL,
- offsetof(StdRdOptions, vacuum_truncate), offsetof(StdRdOptions, vacuum_truncate_set)},
+ {"vacuum_truncate", RELOPT_TYPE_TERNARY,
+ offsetof(StdRdOptions, vacuum_truncate)},
{"vacuum_max_eager_freeze_failure_rate", RELOPT_TYPE_REAL,
offsetof(StdRdOptions, vacuum_max_eager_freeze_failure_rate)}
};
@@ -1993,7 +2071,6 @@ build_local_reloptions(local_relopts *relopts, Datum options, bool validate)
elems[i].optname = opt->option->name;
elems[i].opttype = opt->option->type;
elems[i].offset = opt->offset;
- elems[i].isset_offset = 0; /* not supported for local relopts yet */
i++;
}
diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c
index 733ef40ae7..977babff54 100644
--- a/src/backend/commands/vacuum.c
+++ b/src/backend/commands/vacuum.c
@@ -2221,9 +2221,9 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams params,
{
StdRdOptions *opts = (StdRdOptions *) rel->rd_options;
- if (opts && opts->vacuum_truncate_set)
+ if (opts && opts->vacuum_truncate != TERNARY_UNSET)
{
- if (opts->vacuum_truncate)
+ if (opts->vacuum_truncate == TERNARY_TRUE)
params.truncate = VACOPTVALUE_ENABLED;
else
params.truncate = VACOPTVALUE_DISABLED;
diff --git a/src/include/access/reloptions.h b/src/include/access/reloptions.h
index a604a4702c..a436697658 100644
--- a/src/include/access/reloptions.h
+++ b/src/include/access/reloptions.h
@@ -29,6 +29,7 @@
typedef enum relopt_type
{
RELOPT_TYPE_BOOL,
+ RELOPT_TYPE_TERNARY, /* on, off, unset */
RELOPT_TYPE_INT,
RELOPT_TYPE_REAL,
RELOPT_TYPE_ENUM,
@@ -80,6 +81,7 @@ typedef struct relopt_value
union
{
bool bool_val;
+ ternary ternary_val;
int int_val;
double real_val;
int enum_val;
@@ -94,6 +96,12 @@ typedef struct relopt_bool
bool default_val;
} relopt_bool;
+typedef struct relopt_rernary
+{
+ relopt_gen gen;
+ int default_val;
+} relopt_ternary;
+
typedef struct relopt_int
{
relopt_gen gen;
@@ -152,19 +160,6 @@ typedef struct
const char *optname; /* option's name */
relopt_type opttype; /* option's datatype */
int offset; /* offset of field in result struct */
-
- /*
- * isset_offset is an optional offset of a field in the result struct that
- * stores whether the option is explicitly set for the relation or if it
- * just picked up the default value. In most cases, this can be
- * accomplished by giving the reloption a special out-of-range default
- * value (e.g., some integer reloptions use -2), but this isn't always
- * possible. For example, a Boolean reloption cannot be given an
- * out-of-range default, so we need another way to discover the source of
- * its value. This offset is only used if given a value greater than
- * zero.
- */
- int isset_offset;
} relopt_parse_elt;
/* Local reloption definition */
@@ -195,6 +190,8 @@ typedef struct local_relopts
extern relopt_kind add_reloption_kind(void);
extern void add_bool_reloption(bits32 kinds, const char *name, const char *desc,
bool default_val, LOCKMODE lockmode);
+extern void add_ternary_reloption(bits32 kinds, const char *name,
+ const char *desc, int default_val, LOCKMODE lockmode);
extern void add_int_reloption(bits32 kinds, const char *name, const char *desc,
int default_val, int min_val, int max_val,
LOCKMODE lockmode);
@@ -214,6 +211,9 @@ extern void register_reloptions_validator(local_relopts *relopts,
extern void add_local_bool_reloption(local_relopts *relopts, const char *name,
const char *desc, bool default_val,
int offset);
+extern void add_local_ternary_reloption(local_relopts *relopts,
+ const char *name, const char *desc,
+ ternary default_val, int offset);
extern void add_local_int_reloption(local_relopts *relopts, const char *name,
const char *desc, int default_val,
int min_val, int max_val, int offset);
diff --git a/src/include/c.h b/src/include/c.h
index 39022f8a9d..9c59ccccf8 100644
--- a/src/include/c.h
+++ b/src/include/c.h
@@ -488,6 +488,22 @@ typedef void (*pg_funcptr_t) (void);
#include <stdbool.h>
+/*
+ * ternary
+ * Boolean value with an extrea "unset" option
+ *
+ * Ternary data type is used in relation options that can be "true", "false" or
+ * "unset". Since relation options are used deep inside the PostgreSQL code,
+ * this type is declared globally.
+*/
+
+typedef enum ternary
+{
+ TERNARY_FALSE = 0,
+ TERNARY_TRUE = 1,
+ TERNARY_UNSET = -1
+} ternary;
+
/* ----------------------------------------------------------------
* Section 3: standard system types
diff --git a/src/include/utils/rel.h b/src/include/utils/rel.h
index b552359915..7346d618f9 100644
--- a/src/include/utils/rel.h
+++ b/src/include/utils/rel.h
@@ -346,8 +346,7 @@ typedef struct StdRdOptions
bool user_catalog_table; /* use as an additional catalog relation */
int parallel_workers; /* max number of parallel workers */
StdRdOptIndexCleanup vacuum_index_cleanup; /* controls index vacuuming */
- bool vacuum_truncate; /* enables vacuum to truncate a relation */
- bool vacuum_truncate_set; /* whether vacuum_truncate is set */
+ ternary vacuum_truncate; /* enables vacuum to truncate a relation */
/*
* Fraction of pages in a relation that vacuum can eagerly scan and fail
--
2.39.2
--nextPart5129932.5fSG56mABF
Content-Disposition: attachment;
filename="v2-0003-Add-alias-to-be-used-as-unset-state.patch"
Content-Transfer-Encoding: 7Bit
Content-Type: text/x-patch; charset="unicode-2-0-utf-8";
name="v2-0003-Add-alias-to-be-used-as-unset-state.patch"
^ permalink raw reply [nested|flat] 868+ messages in thread
* [PATCH v2 2/4] Introduce ternary reloptions
@ 2025-09-04 14:41 Nikolay Shaplov <[email protected]>
0 siblings, 0 replies; 868+ messages in thread
From: Nikolay Shaplov @ 2025-09-04 14:41 UTC (permalink / raw)
Introduce ternary reloption as a replacement for current `vacuum_truncate`
implementation. Remove `vacuum_truncate_set` additional flag and using
`TERNARY_UNSET` value instead.
---
src/backend/access/common/reloptions.c | 129 ++++++++++++++++++++-----
src/backend/commands/vacuum.c | 4 +-
src/include/access/reloptions.h | 26 ++---
src/include/c.h | 16 +++
src/include/utils/rel.h | 3 +-
5 files changed, 135 insertions(+), 43 deletions(-)
diff --git a/src/backend/access/common/reloptions.c b/src/backend/access/common/reloptions.c
index 0af3fea68f..f543f11001 100644
--- a/src/backend/access/common/reloptions.c
+++ b/src/backend/access/common/reloptions.c
@@ -40,9 +40,9 @@
*
* To add an option:
*
- * (i) decide on a type (bool, integer, real, enum, string), name, default
- * value, upper and lower bounds (if applicable); for strings, consider a
- * validation routine.
+ * (i) decide on a type (bool, ternary, integer, real, enum, string), name,
+ * default value, upper and lower bounds (if applicable); for strings,
+ * consider a validation routine.
* (ii) add a record below (or use add_<type>_reloption).
* (iii) add it to the appropriate options struct (perhaps StdRdOptions)
* (iv) add it to the appropriate handling routine (perhaps
@@ -147,15 +147,6 @@ static relopt_bool boolRelOpts[] =
},
false
},
- {
- {
- "vacuum_truncate",
- "Enables vacuum to truncate empty pages at the end of this table",
- RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
- ShareUpdateExclusiveLock
- },
- true
- },
{
{
"deduplicate_items",
@@ -170,6 +161,21 @@ static relopt_bool boolRelOpts[] =
{{NULL}}
};
+static relopt_ternary ternaryRelOpts[] =
+{
+ {
+ {
+ "vacuum_truncate",
+ "Enables vacuum to truncate empty pages at the end of this table",
+ RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
+ ShareUpdateExclusiveLock
+ },
+ TERNARY_UNSET
+ },
+ /* list terminator */
+ {{NULL}}
+};
+
static relopt_int intRelOpts[] =
{
{
@@ -600,6 +606,13 @@ initialize_reloptions(void)
boolRelOpts[i].gen.lockmode));
j++;
}
+ for (i = 0; ternaryRelOpts[i].gen.name; i++)
+ {
+ Assert(DoLockModesConflict(ternaryRelOpts[i].gen.lockmode,
+ ternaryRelOpts[i].gen.lockmode));
+ j++;
+ }
+
for (i = 0; intRelOpts[i].gen.name; i++)
{
Assert(DoLockModesConflict(intRelOpts[i].gen.lockmode,
@@ -640,6 +653,14 @@ initialize_reloptions(void)
j++;
}
+ for (i = 0; ternaryRelOpts[i].gen.name; i++)
+ {
+ relOpts[j] = &ternaryRelOpts[i].gen;
+ relOpts[j]->type = RELOPT_TYPE_TERNARY;
+ relOpts[j]->namelen = strlen(relOpts[j]->name);
+ j++;
+ }
+
for (i = 0; intRelOpts[i].gen.name; i++)
{
relOpts[j] = &intRelOpts[i].gen;
@@ -800,6 +821,9 @@ allocate_reloption(bits32 kinds, int type, const char *name, const char *desc,
case RELOPT_TYPE_BOOL:
size = sizeof(relopt_bool);
break;
+ case RELOPT_TYPE_TERNARY:
+ size = sizeof(relopt_ternary);
+ break;
case RELOPT_TYPE_INT:
size = sizeof(relopt_int);
break;
@@ -883,6 +907,54 @@ add_local_bool_reloption(local_relopts *relopts, const char *name,
add_local_reloption(relopts, (relopt_gen *) newoption, offset);
}
+/*
+ * init_ternary_reloption
+ * Allocate and initialize a new ternary reloption
+ */
+static relopt_ternary *
+init_ternary_reloption(bits32 kinds, const char *name, const char *desc,
+ ternary default_val, LOCKMODE lockmode)
+{
+ relopt_ternary *newoption;
+
+ newoption = (relopt_ternary *) allocate_reloption(kinds,
+ RELOPT_TYPE_TERNARY, name, desc, lockmode);
+ newoption->default_val = default_val;
+
+ return newoption;
+}
+
+/*
+ * add_ternary_reloption
+ * Add a new ternary reloption
+ */
+void
+add_ternary_reloption(bits32 kinds, const char *name, const char *desc,
+ ternary default_val, LOCKMODE lockmode)
+{
+ relopt_ternary *newoption = init_ternary_reloption(kinds, name, desc,
+ default_val, lockmode);
+
+ add_reloption((relopt_gen *) newoption);
+}
+
+/*
+ * add_local_ternary_reloption
+ * Add a new ternary local reloption
+ *
+ * 'offset' is offset of ternary-typed field.
+ */
+void
+add_local_ternary_reloption(local_relopts *relopts, const char *name,
+ const char *desc, ternary default_val,
+ int offset)
+{
+ relopt_ternary *newoption = init_ternary_reloption(RELOPT_KIND_LOCAL,
+ name, desc,
+ default_val, 0);
+
+ add_local_reloption(relopts, (relopt_gen *) newoption, offset);
+}
/*
* init_real_reloption
@@ -1617,6 +1689,18 @@ parse_one_reloption(relopt_value *option, char *text_str, int text_len,
option->gen->name, value)));
}
break;
+ case RELOPT_TYPE_TERNARY:
+ {
+ bool b;
+ parsed = parse_bool(value, &b);
+ option->values.ternary_val = b ? TERNARY_TRUE : TERNARY_FALSE;
+ if (validate && !parsed)
+ ereport(ERROR,
+ (errcode(ERRCODE_INVALID_PARAMETER_VALUE),
+ errmsg("invalid value for ternary option \"%s\": %s",
+ option->gen->name, value)));
+ }
+ break;
case RELOPT_TYPE_INT:
{
relopt_int *optint = (relopt_int *) option->gen;
@@ -1780,17 +1864,6 @@ fillRelOptions(void *rdopts, Size basesize,
char *itempos = ((char *) rdopts) + elems[j].offset;
char *string_val;
- /*
- * If isset_offset is provided, store whether the reloption is
- * set there.
- */
- if (elems[j].isset_offset > 0)
- {
- char *setpos = ((char *) rdopts) + elems[j].isset_offset;
-
- *(bool *) setpos = options[i].isset;
- }
-
switch (options[i].gen->type)
{
case RELOPT_TYPE_BOOL:
@@ -1798,6 +1871,11 @@ fillRelOptions(void *rdopts, Size basesize,
options[i].values.bool_val :
((relopt_bool *) options[i].gen)->default_val;
break;
+ case RELOPT_TYPE_TERNARY:
+ *(ternary *) itempos = options[i].isset ?
+ options[i].values.ternary_val :
+ ((relopt_ternary *) options[i].gen)->default_val;
+ break;
case RELOPT_TYPE_INT:
*(int *) itempos = options[i].isset ?
options[i].values.int_val :
@@ -1912,8 +1990,8 @@ default_reloptions(Datum reloptions, bool validate, relopt_kind kind)
offsetof(StdRdOptions, parallel_workers)},
{"vacuum_index_cleanup", RELOPT_TYPE_ENUM,
offsetof(StdRdOptions, vacuum_index_cleanup)},
- {"vacuum_truncate", RELOPT_TYPE_BOOL,
- offsetof(StdRdOptions, vacuum_truncate), offsetof(StdRdOptions, vacuum_truncate_set)},
+ {"vacuum_truncate", RELOPT_TYPE_TERNARY,
+ offsetof(StdRdOptions, vacuum_truncate)},
{"vacuum_max_eager_freeze_failure_rate", RELOPT_TYPE_REAL,
offsetof(StdRdOptions, vacuum_max_eager_freeze_failure_rate)}
};
@@ -1993,7 +2071,6 @@ build_local_reloptions(local_relopts *relopts, Datum options, bool validate)
elems[i].optname = opt->option->name;
elems[i].opttype = opt->option->type;
elems[i].offset = opt->offset;
- elems[i].isset_offset = 0; /* not supported for local relopts yet */
i++;
}
diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c
index 733ef40ae7..977babff54 100644
--- a/src/backend/commands/vacuum.c
+++ b/src/backend/commands/vacuum.c
@@ -2221,9 +2221,9 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams params,
{
StdRdOptions *opts = (StdRdOptions *) rel->rd_options;
- if (opts && opts->vacuum_truncate_set)
+ if (opts && opts->vacuum_truncate != TERNARY_UNSET)
{
- if (opts->vacuum_truncate)
+ if (opts->vacuum_truncate == TERNARY_TRUE)
params.truncate = VACOPTVALUE_ENABLED;
else
params.truncate = VACOPTVALUE_DISABLED;
diff --git a/src/include/access/reloptions.h b/src/include/access/reloptions.h
index a604a4702c..a436697658 100644
--- a/src/include/access/reloptions.h
+++ b/src/include/access/reloptions.h
@@ -29,6 +29,7 @@
typedef enum relopt_type
{
RELOPT_TYPE_BOOL,
+ RELOPT_TYPE_TERNARY, /* on, off, unset */
RELOPT_TYPE_INT,
RELOPT_TYPE_REAL,
RELOPT_TYPE_ENUM,
@@ -80,6 +81,7 @@ typedef struct relopt_value
union
{
bool bool_val;
+ ternary ternary_val;
int int_val;
double real_val;
int enum_val;
@@ -94,6 +96,12 @@ typedef struct relopt_bool
bool default_val;
} relopt_bool;
+typedef struct relopt_rernary
+{
+ relopt_gen gen;
+ int default_val;
+} relopt_ternary;
+
typedef struct relopt_int
{
relopt_gen gen;
@@ -152,19 +160,6 @@ typedef struct
const char *optname; /* option's name */
relopt_type opttype; /* option's datatype */
int offset; /* offset of field in result struct */
-
- /*
- * isset_offset is an optional offset of a field in the result struct that
- * stores whether the option is explicitly set for the relation or if it
- * just picked up the default value. In most cases, this can be
- * accomplished by giving the reloption a special out-of-range default
- * value (e.g., some integer reloptions use -2), but this isn't always
- * possible. For example, a Boolean reloption cannot be given an
- * out-of-range default, so we need another way to discover the source of
- * its value. This offset is only used if given a value greater than
- * zero.
- */
- int isset_offset;
} relopt_parse_elt;
/* Local reloption definition */
@@ -195,6 +190,8 @@ typedef struct local_relopts
extern relopt_kind add_reloption_kind(void);
extern void add_bool_reloption(bits32 kinds, const char *name, const char *desc,
bool default_val, LOCKMODE lockmode);
+extern void add_ternary_reloption(bits32 kinds, const char *name,
+ const char *desc, int default_val, LOCKMODE lockmode);
extern void add_int_reloption(bits32 kinds, const char *name, const char *desc,
int default_val, int min_val, int max_val,
LOCKMODE lockmode);
@@ -214,6 +211,9 @@ extern void register_reloptions_validator(local_relopts *relopts,
extern void add_local_bool_reloption(local_relopts *relopts, const char *name,
const char *desc, bool default_val,
int offset);
+extern void add_local_ternary_reloption(local_relopts *relopts,
+ const char *name, const char *desc,
+ ternary default_val, int offset);
extern void add_local_int_reloption(local_relopts *relopts, const char *name,
const char *desc, int default_val,
int min_val, int max_val, int offset);
diff --git a/src/include/c.h b/src/include/c.h
index 39022f8a9d..9c59ccccf8 100644
--- a/src/include/c.h
+++ b/src/include/c.h
@@ -488,6 +488,22 @@ typedef void (*pg_funcptr_t) (void);
#include <stdbool.h>
+/*
+ * ternary
+ * Boolean value with an extrea "unset" option
+ *
+ * Ternary data type is used in relation options that can be "true", "false" or
+ * "unset". Since relation options are used deep inside the PostgreSQL code,
+ * this type is declared globally.
+*/
+
+typedef enum ternary
+{
+ TERNARY_FALSE = 0,
+ TERNARY_TRUE = 1,
+ TERNARY_UNSET = -1
+} ternary;
+
/* ----------------------------------------------------------------
* Section 3: standard system types
diff --git a/src/include/utils/rel.h b/src/include/utils/rel.h
index b552359915..7346d618f9 100644
--- a/src/include/utils/rel.h
+++ b/src/include/utils/rel.h
@@ -346,8 +346,7 @@ typedef struct StdRdOptions
bool user_catalog_table; /* use as an additional catalog relation */
int parallel_workers; /* max number of parallel workers */
StdRdOptIndexCleanup vacuum_index_cleanup; /* controls index vacuuming */
- bool vacuum_truncate; /* enables vacuum to truncate a relation */
- bool vacuum_truncate_set; /* whether vacuum_truncate is set */
+ ternary vacuum_truncate; /* enables vacuum to truncate a relation */
/*
* Fraction of pages in a relation that vacuum can eagerly scan and fail
--
2.39.2
--nextPart5129932.5fSG56mABF
Content-Disposition: attachment;
filename="v2-0003-Add-alias-to-be-used-as-unset-state.patch"
Content-Transfer-Encoding: 7Bit
Content-Type: text/x-patch; charset="unicode-2-0-utf-8";
name="v2-0003-Add-alias-to-be-used-as-unset-state.patch"
^ permalink raw reply [nested|flat] 868+ messages in thread
* [PATCH v2 2/4] Introduce ternary reloptions
@ 2025-09-04 14:41 Nikolay Shaplov <[email protected]>
0 siblings, 0 replies; 868+ messages in thread
From: Nikolay Shaplov @ 2025-09-04 14:41 UTC (permalink / raw)
Introduce ternary reloption as a replacement for current `vacuum_truncate`
implementation. Remove `vacuum_truncate_set` additional flag and using
`TERNARY_UNSET` value instead.
---
src/backend/access/common/reloptions.c | 129 ++++++++++++++++++++-----
src/backend/commands/vacuum.c | 4 +-
src/include/access/reloptions.h | 26 ++---
src/include/c.h | 16 +++
src/include/utils/rel.h | 3 +-
5 files changed, 135 insertions(+), 43 deletions(-)
diff --git a/src/backend/access/common/reloptions.c b/src/backend/access/common/reloptions.c
index 0af3fea68f..f543f11001 100644
--- a/src/backend/access/common/reloptions.c
+++ b/src/backend/access/common/reloptions.c
@@ -40,9 +40,9 @@
*
* To add an option:
*
- * (i) decide on a type (bool, integer, real, enum, string), name, default
- * value, upper and lower bounds (if applicable); for strings, consider a
- * validation routine.
+ * (i) decide on a type (bool, ternary, integer, real, enum, string), name,
+ * default value, upper and lower bounds (if applicable); for strings,
+ * consider a validation routine.
* (ii) add a record below (or use add_<type>_reloption).
* (iii) add it to the appropriate options struct (perhaps StdRdOptions)
* (iv) add it to the appropriate handling routine (perhaps
@@ -147,15 +147,6 @@ static relopt_bool boolRelOpts[] =
},
false
},
- {
- {
- "vacuum_truncate",
- "Enables vacuum to truncate empty pages at the end of this table",
- RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
- ShareUpdateExclusiveLock
- },
- true
- },
{
{
"deduplicate_items",
@@ -170,6 +161,21 @@ static relopt_bool boolRelOpts[] =
{{NULL}}
};
+static relopt_ternary ternaryRelOpts[] =
+{
+ {
+ {
+ "vacuum_truncate",
+ "Enables vacuum to truncate empty pages at the end of this table",
+ RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
+ ShareUpdateExclusiveLock
+ },
+ TERNARY_UNSET
+ },
+ /* list terminator */
+ {{NULL}}
+};
+
static relopt_int intRelOpts[] =
{
{
@@ -600,6 +606,13 @@ initialize_reloptions(void)
boolRelOpts[i].gen.lockmode));
j++;
}
+ for (i = 0; ternaryRelOpts[i].gen.name; i++)
+ {
+ Assert(DoLockModesConflict(ternaryRelOpts[i].gen.lockmode,
+ ternaryRelOpts[i].gen.lockmode));
+ j++;
+ }
+
for (i = 0; intRelOpts[i].gen.name; i++)
{
Assert(DoLockModesConflict(intRelOpts[i].gen.lockmode,
@@ -640,6 +653,14 @@ initialize_reloptions(void)
j++;
}
+ for (i = 0; ternaryRelOpts[i].gen.name; i++)
+ {
+ relOpts[j] = &ternaryRelOpts[i].gen;
+ relOpts[j]->type = RELOPT_TYPE_TERNARY;
+ relOpts[j]->namelen = strlen(relOpts[j]->name);
+ j++;
+ }
+
for (i = 0; intRelOpts[i].gen.name; i++)
{
relOpts[j] = &intRelOpts[i].gen;
@@ -800,6 +821,9 @@ allocate_reloption(bits32 kinds, int type, const char *name, const char *desc,
case RELOPT_TYPE_BOOL:
size = sizeof(relopt_bool);
break;
+ case RELOPT_TYPE_TERNARY:
+ size = sizeof(relopt_ternary);
+ break;
case RELOPT_TYPE_INT:
size = sizeof(relopt_int);
break;
@@ -883,6 +907,54 @@ add_local_bool_reloption(local_relopts *relopts, const char *name,
add_local_reloption(relopts, (relopt_gen *) newoption, offset);
}
+/*
+ * init_ternary_reloption
+ * Allocate and initialize a new ternary reloption
+ */
+static relopt_ternary *
+init_ternary_reloption(bits32 kinds, const char *name, const char *desc,
+ ternary default_val, LOCKMODE lockmode)
+{
+ relopt_ternary *newoption;
+
+ newoption = (relopt_ternary *) allocate_reloption(kinds,
+ RELOPT_TYPE_TERNARY, name, desc, lockmode);
+ newoption->default_val = default_val;
+
+ return newoption;
+}
+
+/*
+ * add_ternary_reloption
+ * Add a new ternary reloption
+ */
+void
+add_ternary_reloption(bits32 kinds, const char *name, const char *desc,
+ ternary default_val, LOCKMODE lockmode)
+{
+ relopt_ternary *newoption = init_ternary_reloption(kinds, name, desc,
+ default_val, lockmode);
+
+ add_reloption((relopt_gen *) newoption);
+}
+
+/*
+ * add_local_ternary_reloption
+ * Add a new ternary local reloption
+ *
+ * 'offset' is offset of ternary-typed field.
+ */
+void
+add_local_ternary_reloption(local_relopts *relopts, const char *name,
+ const char *desc, ternary default_val,
+ int offset)
+{
+ relopt_ternary *newoption = init_ternary_reloption(RELOPT_KIND_LOCAL,
+ name, desc,
+ default_val, 0);
+
+ add_local_reloption(relopts, (relopt_gen *) newoption, offset);
+}
/*
* init_real_reloption
@@ -1617,6 +1689,18 @@ parse_one_reloption(relopt_value *option, char *text_str, int text_len,
option->gen->name, value)));
}
break;
+ case RELOPT_TYPE_TERNARY:
+ {
+ bool b;
+ parsed = parse_bool(value, &b);
+ option->values.ternary_val = b ? TERNARY_TRUE : TERNARY_FALSE;
+ if (validate && !parsed)
+ ereport(ERROR,
+ (errcode(ERRCODE_INVALID_PARAMETER_VALUE),
+ errmsg("invalid value for ternary option \"%s\": %s",
+ option->gen->name, value)));
+ }
+ break;
case RELOPT_TYPE_INT:
{
relopt_int *optint = (relopt_int *) option->gen;
@@ -1780,17 +1864,6 @@ fillRelOptions(void *rdopts, Size basesize,
char *itempos = ((char *) rdopts) + elems[j].offset;
char *string_val;
- /*
- * If isset_offset is provided, store whether the reloption is
- * set there.
- */
- if (elems[j].isset_offset > 0)
- {
- char *setpos = ((char *) rdopts) + elems[j].isset_offset;
-
- *(bool *) setpos = options[i].isset;
- }
-
switch (options[i].gen->type)
{
case RELOPT_TYPE_BOOL:
@@ -1798,6 +1871,11 @@ fillRelOptions(void *rdopts, Size basesize,
options[i].values.bool_val :
((relopt_bool *) options[i].gen)->default_val;
break;
+ case RELOPT_TYPE_TERNARY:
+ *(ternary *) itempos = options[i].isset ?
+ options[i].values.ternary_val :
+ ((relopt_ternary *) options[i].gen)->default_val;
+ break;
case RELOPT_TYPE_INT:
*(int *) itempos = options[i].isset ?
options[i].values.int_val :
@@ -1912,8 +1990,8 @@ default_reloptions(Datum reloptions, bool validate, relopt_kind kind)
offsetof(StdRdOptions, parallel_workers)},
{"vacuum_index_cleanup", RELOPT_TYPE_ENUM,
offsetof(StdRdOptions, vacuum_index_cleanup)},
- {"vacuum_truncate", RELOPT_TYPE_BOOL,
- offsetof(StdRdOptions, vacuum_truncate), offsetof(StdRdOptions, vacuum_truncate_set)},
+ {"vacuum_truncate", RELOPT_TYPE_TERNARY,
+ offsetof(StdRdOptions, vacuum_truncate)},
{"vacuum_max_eager_freeze_failure_rate", RELOPT_TYPE_REAL,
offsetof(StdRdOptions, vacuum_max_eager_freeze_failure_rate)}
};
@@ -1993,7 +2071,6 @@ build_local_reloptions(local_relopts *relopts, Datum options, bool validate)
elems[i].optname = opt->option->name;
elems[i].opttype = opt->option->type;
elems[i].offset = opt->offset;
- elems[i].isset_offset = 0; /* not supported for local relopts yet */
i++;
}
diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c
index 733ef40ae7..977babff54 100644
--- a/src/backend/commands/vacuum.c
+++ b/src/backend/commands/vacuum.c
@@ -2221,9 +2221,9 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams params,
{
StdRdOptions *opts = (StdRdOptions *) rel->rd_options;
- if (opts && opts->vacuum_truncate_set)
+ if (opts && opts->vacuum_truncate != TERNARY_UNSET)
{
- if (opts->vacuum_truncate)
+ if (opts->vacuum_truncate == TERNARY_TRUE)
params.truncate = VACOPTVALUE_ENABLED;
else
params.truncate = VACOPTVALUE_DISABLED;
diff --git a/src/include/access/reloptions.h b/src/include/access/reloptions.h
index a604a4702c..a436697658 100644
--- a/src/include/access/reloptions.h
+++ b/src/include/access/reloptions.h
@@ -29,6 +29,7 @@
typedef enum relopt_type
{
RELOPT_TYPE_BOOL,
+ RELOPT_TYPE_TERNARY, /* on, off, unset */
RELOPT_TYPE_INT,
RELOPT_TYPE_REAL,
RELOPT_TYPE_ENUM,
@@ -80,6 +81,7 @@ typedef struct relopt_value
union
{
bool bool_val;
+ ternary ternary_val;
int int_val;
double real_val;
int enum_val;
@@ -94,6 +96,12 @@ typedef struct relopt_bool
bool default_val;
} relopt_bool;
+typedef struct relopt_rernary
+{
+ relopt_gen gen;
+ int default_val;
+} relopt_ternary;
+
typedef struct relopt_int
{
relopt_gen gen;
@@ -152,19 +160,6 @@ typedef struct
const char *optname; /* option's name */
relopt_type opttype; /* option's datatype */
int offset; /* offset of field in result struct */
-
- /*
- * isset_offset is an optional offset of a field in the result struct that
- * stores whether the option is explicitly set for the relation or if it
- * just picked up the default value. In most cases, this can be
- * accomplished by giving the reloption a special out-of-range default
- * value (e.g., some integer reloptions use -2), but this isn't always
- * possible. For example, a Boolean reloption cannot be given an
- * out-of-range default, so we need another way to discover the source of
- * its value. This offset is only used if given a value greater than
- * zero.
- */
- int isset_offset;
} relopt_parse_elt;
/* Local reloption definition */
@@ -195,6 +190,8 @@ typedef struct local_relopts
extern relopt_kind add_reloption_kind(void);
extern void add_bool_reloption(bits32 kinds, const char *name, const char *desc,
bool default_val, LOCKMODE lockmode);
+extern void add_ternary_reloption(bits32 kinds, const char *name,
+ const char *desc, int default_val, LOCKMODE lockmode);
extern void add_int_reloption(bits32 kinds, const char *name, const char *desc,
int default_val, int min_val, int max_val,
LOCKMODE lockmode);
@@ -214,6 +211,9 @@ extern void register_reloptions_validator(local_relopts *relopts,
extern void add_local_bool_reloption(local_relopts *relopts, const char *name,
const char *desc, bool default_val,
int offset);
+extern void add_local_ternary_reloption(local_relopts *relopts,
+ const char *name, const char *desc,
+ ternary default_val, int offset);
extern void add_local_int_reloption(local_relopts *relopts, const char *name,
const char *desc, int default_val,
int min_val, int max_val, int offset);
diff --git a/src/include/c.h b/src/include/c.h
index 39022f8a9d..9c59ccccf8 100644
--- a/src/include/c.h
+++ b/src/include/c.h
@@ -488,6 +488,22 @@ typedef void (*pg_funcptr_t) (void);
#include <stdbool.h>
+/*
+ * ternary
+ * Boolean value with an extrea "unset" option
+ *
+ * Ternary data type is used in relation options that can be "true", "false" or
+ * "unset". Since relation options are used deep inside the PostgreSQL code,
+ * this type is declared globally.
+*/
+
+typedef enum ternary
+{
+ TERNARY_FALSE = 0,
+ TERNARY_TRUE = 1,
+ TERNARY_UNSET = -1
+} ternary;
+
/* ----------------------------------------------------------------
* Section 3: standard system types
diff --git a/src/include/utils/rel.h b/src/include/utils/rel.h
index b552359915..7346d618f9 100644
--- a/src/include/utils/rel.h
+++ b/src/include/utils/rel.h
@@ -346,8 +346,7 @@ typedef struct StdRdOptions
bool user_catalog_table; /* use as an additional catalog relation */
int parallel_workers; /* max number of parallel workers */
StdRdOptIndexCleanup vacuum_index_cleanup; /* controls index vacuuming */
- bool vacuum_truncate; /* enables vacuum to truncate a relation */
- bool vacuum_truncate_set; /* whether vacuum_truncate is set */
+ ternary vacuum_truncate; /* enables vacuum to truncate a relation */
/*
* Fraction of pages in a relation that vacuum can eagerly scan and fail
--
2.39.2
--nextPart5129932.5fSG56mABF
Content-Disposition: attachment;
filename="v2-0003-Add-alias-to-be-used-as-unset-state.patch"
Content-Transfer-Encoding: 7Bit
Content-Type: text/x-patch; charset="unicode-2-0-utf-8";
name="v2-0003-Add-alias-to-be-used-as-unset-state.patch"
^ permalink raw reply [nested|flat] 868+ messages in thread
* [PATCH v2 2/4] Introduce ternary reloptions
@ 2025-09-04 14:41 Nikolay Shaplov <[email protected]>
0 siblings, 0 replies; 868+ messages in thread
From: Nikolay Shaplov @ 2025-09-04 14:41 UTC (permalink / raw)
Introduce ternary reloption as a replacement for current `vacuum_truncate`
implementation. Remove `vacuum_truncate_set` additional flag and using
`TERNARY_UNSET` value instead.
---
src/backend/access/common/reloptions.c | 129 ++++++++++++++++++++-----
src/backend/commands/vacuum.c | 4 +-
src/include/access/reloptions.h | 26 ++---
src/include/c.h | 16 +++
src/include/utils/rel.h | 3 +-
5 files changed, 135 insertions(+), 43 deletions(-)
diff --git a/src/backend/access/common/reloptions.c b/src/backend/access/common/reloptions.c
index 0af3fea68f..f543f11001 100644
--- a/src/backend/access/common/reloptions.c
+++ b/src/backend/access/common/reloptions.c
@@ -40,9 +40,9 @@
*
* To add an option:
*
- * (i) decide on a type (bool, integer, real, enum, string), name, default
- * value, upper and lower bounds (if applicable); for strings, consider a
- * validation routine.
+ * (i) decide on a type (bool, ternary, integer, real, enum, string), name,
+ * default value, upper and lower bounds (if applicable); for strings,
+ * consider a validation routine.
* (ii) add a record below (or use add_<type>_reloption).
* (iii) add it to the appropriate options struct (perhaps StdRdOptions)
* (iv) add it to the appropriate handling routine (perhaps
@@ -147,15 +147,6 @@ static relopt_bool boolRelOpts[] =
},
false
},
- {
- {
- "vacuum_truncate",
- "Enables vacuum to truncate empty pages at the end of this table",
- RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
- ShareUpdateExclusiveLock
- },
- true
- },
{
{
"deduplicate_items",
@@ -170,6 +161,21 @@ static relopt_bool boolRelOpts[] =
{{NULL}}
};
+static relopt_ternary ternaryRelOpts[] =
+{
+ {
+ {
+ "vacuum_truncate",
+ "Enables vacuum to truncate empty pages at the end of this table",
+ RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
+ ShareUpdateExclusiveLock
+ },
+ TERNARY_UNSET
+ },
+ /* list terminator */
+ {{NULL}}
+};
+
static relopt_int intRelOpts[] =
{
{
@@ -600,6 +606,13 @@ initialize_reloptions(void)
boolRelOpts[i].gen.lockmode));
j++;
}
+ for (i = 0; ternaryRelOpts[i].gen.name; i++)
+ {
+ Assert(DoLockModesConflict(ternaryRelOpts[i].gen.lockmode,
+ ternaryRelOpts[i].gen.lockmode));
+ j++;
+ }
+
for (i = 0; intRelOpts[i].gen.name; i++)
{
Assert(DoLockModesConflict(intRelOpts[i].gen.lockmode,
@@ -640,6 +653,14 @@ initialize_reloptions(void)
j++;
}
+ for (i = 0; ternaryRelOpts[i].gen.name; i++)
+ {
+ relOpts[j] = &ternaryRelOpts[i].gen;
+ relOpts[j]->type = RELOPT_TYPE_TERNARY;
+ relOpts[j]->namelen = strlen(relOpts[j]->name);
+ j++;
+ }
+
for (i = 0; intRelOpts[i].gen.name; i++)
{
relOpts[j] = &intRelOpts[i].gen;
@@ -800,6 +821,9 @@ allocate_reloption(bits32 kinds, int type, const char *name, const char *desc,
case RELOPT_TYPE_BOOL:
size = sizeof(relopt_bool);
break;
+ case RELOPT_TYPE_TERNARY:
+ size = sizeof(relopt_ternary);
+ break;
case RELOPT_TYPE_INT:
size = sizeof(relopt_int);
break;
@@ -883,6 +907,54 @@ add_local_bool_reloption(local_relopts *relopts, const char *name,
add_local_reloption(relopts, (relopt_gen *) newoption, offset);
}
+/*
+ * init_ternary_reloption
+ * Allocate and initialize a new ternary reloption
+ */
+static relopt_ternary *
+init_ternary_reloption(bits32 kinds, const char *name, const char *desc,
+ ternary default_val, LOCKMODE lockmode)
+{
+ relopt_ternary *newoption;
+
+ newoption = (relopt_ternary *) allocate_reloption(kinds,
+ RELOPT_TYPE_TERNARY, name, desc, lockmode);
+ newoption->default_val = default_val;
+
+ return newoption;
+}
+
+/*
+ * add_ternary_reloption
+ * Add a new ternary reloption
+ */
+void
+add_ternary_reloption(bits32 kinds, const char *name, const char *desc,
+ ternary default_val, LOCKMODE lockmode)
+{
+ relopt_ternary *newoption = init_ternary_reloption(kinds, name, desc,
+ default_val, lockmode);
+
+ add_reloption((relopt_gen *) newoption);
+}
+
+/*
+ * add_local_ternary_reloption
+ * Add a new ternary local reloption
+ *
+ * 'offset' is offset of ternary-typed field.
+ */
+void
+add_local_ternary_reloption(local_relopts *relopts, const char *name,
+ const char *desc, ternary default_val,
+ int offset)
+{
+ relopt_ternary *newoption = init_ternary_reloption(RELOPT_KIND_LOCAL,
+ name, desc,
+ default_val, 0);
+
+ add_local_reloption(relopts, (relopt_gen *) newoption, offset);
+}
/*
* init_real_reloption
@@ -1617,6 +1689,18 @@ parse_one_reloption(relopt_value *option, char *text_str, int text_len,
option->gen->name, value)));
}
break;
+ case RELOPT_TYPE_TERNARY:
+ {
+ bool b;
+ parsed = parse_bool(value, &b);
+ option->values.ternary_val = b ? TERNARY_TRUE : TERNARY_FALSE;
+ if (validate && !parsed)
+ ereport(ERROR,
+ (errcode(ERRCODE_INVALID_PARAMETER_VALUE),
+ errmsg("invalid value for ternary option \"%s\": %s",
+ option->gen->name, value)));
+ }
+ break;
case RELOPT_TYPE_INT:
{
relopt_int *optint = (relopt_int *) option->gen;
@@ -1780,17 +1864,6 @@ fillRelOptions(void *rdopts, Size basesize,
char *itempos = ((char *) rdopts) + elems[j].offset;
char *string_val;
- /*
- * If isset_offset is provided, store whether the reloption is
- * set there.
- */
- if (elems[j].isset_offset > 0)
- {
- char *setpos = ((char *) rdopts) + elems[j].isset_offset;
-
- *(bool *) setpos = options[i].isset;
- }
-
switch (options[i].gen->type)
{
case RELOPT_TYPE_BOOL:
@@ -1798,6 +1871,11 @@ fillRelOptions(void *rdopts, Size basesize,
options[i].values.bool_val :
((relopt_bool *) options[i].gen)->default_val;
break;
+ case RELOPT_TYPE_TERNARY:
+ *(ternary *) itempos = options[i].isset ?
+ options[i].values.ternary_val :
+ ((relopt_ternary *) options[i].gen)->default_val;
+ break;
case RELOPT_TYPE_INT:
*(int *) itempos = options[i].isset ?
options[i].values.int_val :
@@ -1912,8 +1990,8 @@ default_reloptions(Datum reloptions, bool validate, relopt_kind kind)
offsetof(StdRdOptions, parallel_workers)},
{"vacuum_index_cleanup", RELOPT_TYPE_ENUM,
offsetof(StdRdOptions, vacuum_index_cleanup)},
- {"vacuum_truncate", RELOPT_TYPE_BOOL,
- offsetof(StdRdOptions, vacuum_truncate), offsetof(StdRdOptions, vacuum_truncate_set)},
+ {"vacuum_truncate", RELOPT_TYPE_TERNARY,
+ offsetof(StdRdOptions, vacuum_truncate)},
{"vacuum_max_eager_freeze_failure_rate", RELOPT_TYPE_REAL,
offsetof(StdRdOptions, vacuum_max_eager_freeze_failure_rate)}
};
@@ -1993,7 +2071,6 @@ build_local_reloptions(local_relopts *relopts, Datum options, bool validate)
elems[i].optname = opt->option->name;
elems[i].opttype = opt->option->type;
elems[i].offset = opt->offset;
- elems[i].isset_offset = 0; /* not supported for local relopts yet */
i++;
}
diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c
index 733ef40ae7..977babff54 100644
--- a/src/backend/commands/vacuum.c
+++ b/src/backend/commands/vacuum.c
@@ -2221,9 +2221,9 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams params,
{
StdRdOptions *opts = (StdRdOptions *) rel->rd_options;
- if (opts && opts->vacuum_truncate_set)
+ if (opts && opts->vacuum_truncate != TERNARY_UNSET)
{
- if (opts->vacuum_truncate)
+ if (opts->vacuum_truncate == TERNARY_TRUE)
params.truncate = VACOPTVALUE_ENABLED;
else
params.truncate = VACOPTVALUE_DISABLED;
diff --git a/src/include/access/reloptions.h b/src/include/access/reloptions.h
index a604a4702c..a436697658 100644
--- a/src/include/access/reloptions.h
+++ b/src/include/access/reloptions.h
@@ -29,6 +29,7 @@
typedef enum relopt_type
{
RELOPT_TYPE_BOOL,
+ RELOPT_TYPE_TERNARY, /* on, off, unset */
RELOPT_TYPE_INT,
RELOPT_TYPE_REAL,
RELOPT_TYPE_ENUM,
@@ -80,6 +81,7 @@ typedef struct relopt_value
union
{
bool bool_val;
+ ternary ternary_val;
int int_val;
double real_val;
int enum_val;
@@ -94,6 +96,12 @@ typedef struct relopt_bool
bool default_val;
} relopt_bool;
+typedef struct relopt_rernary
+{
+ relopt_gen gen;
+ int default_val;
+} relopt_ternary;
+
typedef struct relopt_int
{
relopt_gen gen;
@@ -152,19 +160,6 @@ typedef struct
const char *optname; /* option's name */
relopt_type opttype; /* option's datatype */
int offset; /* offset of field in result struct */
-
- /*
- * isset_offset is an optional offset of a field in the result struct that
- * stores whether the option is explicitly set for the relation or if it
- * just picked up the default value. In most cases, this can be
- * accomplished by giving the reloption a special out-of-range default
- * value (e.g., some integer reloptions use -2), but this isn't always
- * possible. For example, a Boolean reloption cannot be given an
- * out-of-range default, so we need another way to discover the source of
- * its value. This offset is only used if given a value greater than
- * zero.
- */
- int isset_offset;
} relopt_parse_elt;
/* Local reloption definition */
@@ -195,6 +190,8 @@ typedef struct local_relopts
extern relopt_kind add_reloption_kind(void);
extern void add_bool_reloption(bits32 kinds, const char *name, const char *desc,
bool default_val, LOCKMODE lockmode);
+extern void add_ternary_reloption(bits32 kinds, const char *name,
+ const char *desc, int default_val, LOCKMODE lockmode);
extern void add_int_reloption(bits32 kinds, const char *name, const char *desc,
int default_val, int min_val, int max_val,
LOCKMODE lockmode);
@@ -214,6 +211,9 @@ extern void register_reloptions_validator(local_relopts *relopts,
extern void add_local_bool_reloption(local_relopts *relopts, const char *name,
const char *desc, bool default_val,
int offset);
+extern void add_local_ternary_reloption(local_relopts *relopts,
+ const char *name, const char *desc,
+ ternary default_val, int offset);
extern void add_local_int_reloption(local_relopts *relopts, const char *name,
const char *desc, int default_val,
int min_val, int max_val, int offset);
diff --git a/src/include/c.h b/src/include/c.h
index 39022f8a9d..9c59ccccf8 100644
--- a/src/include/c.h
+++ b/src/include/c.h
@@ -488,6 +488,22 @@ typedef void (*pg_funcptr_t) (void);
#include <stdbool.h>
+/*
+ * ternary
+ * Boolean value with an extrea "unset" option
+ *
+ * Ternary data type is used in relation options that can be "true", "false" or
+ * "unset". Since relation options are used deep inside the PostgreSQL code,
+ * this type is declared globally.
+*/
+
+typedef enum ternary
+{
+ TERNARY_FALSE = 0,
+ TERNARY_TRUE = 1,
+ TERNARY_UNSET = -1
+} ternary;
+
/* ----------------------------------------------------------------
* Section 3: standard system types
diff --git a/src/include/utils/rel.h b/src/include/utils/rel.h
index b552359915..7346d618f9 100644
--- a/src/include/utils/rel.h
+++ b/src/include/utils/rel.h
@@ -346,8 +346,7 @@ typedef struct StdRdOptions
bool user_catalog_table; /* use as an additional catalog relation */
int parallel_workers; /* max number of parallel workers */
StdRdOptIndexCleanup vacuum_index_cleanup; /* controls index vacuuming */
- bool vacuum_truncate; /* enables vacuum to truncate a relation */
- bool vacuum_truncate_set; /* whether vacuum_truncate is set */
+ ternary vacuum_truncate; /* enables vacuum to truncate a relation */
/*
* Fraction of pages in a relation that vacuum can eagerly scan and fail
--
2.39.2
--nextPart5129932.5fSG56mABF
Content-Disposition: attachment;
filename="v2-0003-Add-alias-to-be-used-as-unset-state.patch"
Content-Transfer-Encoding: 7Bit
Content-Type: text/x-patch; charset="unicode-2-0-utf-8";
name="v2-0003-Add-alias-to-be-used-as-unset-state.patch"
^ permalink raw reply [nested|flat] 868+ messages in thread
* [PATCH v2 2/4] Introduce ternary reloptions
@ 2025-09-04 14:41 Nikolay Shaplov <[email protected]>
0 siblings, 0 replies; 868+ messages in thread
From: Nikolay Shaplov @ 2025-09-04 14:41 UTC (permalink / raw)
Introduce ternary reloption as a replacement for current `vacuum_truncate`
implementation. Remove `vacuum_truncate_set` additional flag and using
`TERNARY_UNSET` value instead.
---
src/backend/access/common/reloptions.c | 129 ++++++++++++++++++++-----
src/backend/commands/vacuum.c | 4 +-
src/include/access/reloptions.h | 26 ++---
src/include/c.h | 16 +++
src/include/utils/rel.h | 3 +-
5 files changed, 135 insertions(+), 43 deletions(-)
diff --git a/src/backend/access/common/reloptions.c b/src/backend/access/common/reloptions.c
index 0af3fea68f..f543f11001 100644
--- a/src/backend/access/common/reloptions.c
+++ b/src/backend/access/common/reloptions.c
@@ -40,9 +40,9 @@
*
* To add an option:
*
- * (i) decide on a type (bool, integer, real, enum, string), name, default
- * value, upper and lower bounds (if applicable); for strings, consider a
- * validation routine.
+ * (i) decide on a type (bool, ternary, integer, real, enum, string), name,
+ * default value, upper and lower bounds (if applicable); for strings,
+ * consider a validation routine.
* (ii) add a record below (or use add_<type>_reloption).
* (iii) add it to the appropriate options struct (perhaps StdRdOptions)
* (iv) add it to the appropriate handling routine (perhaps
@@ -147,15 +147,6 @@ static relopt_bool boolRelOpts[] =
},
false
},
- {
- {
- "vacuum_truncate",
- "Enables vacuum to truncate empty pages at the end of this table",
- RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
- ShareUpdateExclusiveLock
- },
- true
- },
{
{
"deduplicate_items",
@@ -170,6 +161,21 @@ static relopt_bool boolRelOpts[] =
{{NULL}}
};
+static relopt_ternary ternaryRelOpts[] =
+{
+ {
+ {
+ "vacuum_truncate",
+ "Enables vacuum to truncate empty pages at the end of this table",
+ RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
+ ShareUpdateExclusiveLock
+ },
+ TERNARY_UNSET
+ },
+ /* list terminator */
+ {{NULL}}
+};
+
static relopt_int intRelOpts[] =
{
{
@@ -600,6 +606,13 @@ initialize_reloptions(void)
boolRelOpts[i].gen.lockmode));
j++;
}
+ for (i = 0; ternaryRelOpts[i].gen.name; i++)
+ {
+ Assert(DoLockModesConflict(ternaryRelOpts[i].gen.lockmode,
+ ternaryRelOpts[i].gen.lockmode));
+ j++;
+ }
+
for (i = 0; intRelOpts[i].gen.name; i++)
{
Assert(DoLockModesConflict(intRelOpts[i].gen.lockmode,
@@ -640,6 +653,14 @@ initialize_reloptions(void)
j++;
}
+ for (i = 0; ternaryRelOpts[i].gen.name; i++)
+ {
+ relOpts[j] = &ternaryRelOpts[i].gen;
+ relOpts[j]->type = RELOPT_TYPE_TERNARY;
+ relOpts[j]->namelen = strlen(relOpts[j]->name);
+ j++;
+ }
+
for (i = 0; intRelOpts[i].gen.name; i++)
{
relOpts[j] = &intRelOpts[i].gen;
@@ -800,6 +821,9 @@ allocate_reloption(bits32 kinds, int type, const char *name, const char *desc,
case RELOPT_TYPE_BOOL:
size = sizeof(relopt_bool);
break;
+ case RELOPT_TYPE_TERNARY:
+ size = sizeof(relopt_ternary);
+ break;
case RELOPT_TYPE_INT:
size = sizeof(relopt_int);
break;
@@ -883,6 +907,54 @@ add_local_bool_reloption(local_relopts *relopts, const char *name,
add_local_reloption(relopts, (relopt_gen *) newoption, offset);
}
+/*
+ * init_ternary_reloption
+ * Allocate and initialize a new ternary reloption
+ */
+static relopt_ternary *
+init_ternary_reloption(bits32 kinds, const char *name, const char *desc,
+ ternary default_val, LOCKMODE lockmode)
+{
+ relopt_ternary *newoption;
+
+ newoption = (relopt_ternary *) allocate_reloption(kinds,
+ RELOPT_TYPE_TERNARY, name, desc, lockmode);
+ newoption->default_val = default_val;
+
+ return newoption;
+}
+
+/*
+ * add_ternary_reloption
+ * Add a new ternary reloption
+ */
+void
+add_ternary_reloption(bits32 kinds, const char *name, const char *desc,
+ ternary default_val, LOCKMODE lockmode)
+{
+ relopt_ternary *newoption = init_ternary_reloption(kinds, name, desc,
+ default_val, lockmode);
+
+ add_reloption((relopt_gen *) newoption);
+}
+
+/*
+ * add_local_ternary_reloption
+ * Add a new ternary local reloption
+ *
+ * 'offset' is offset of ternary-typed field.
+ */
+void
+add_local_ternary_reloption(local_relopts *relopts, const char *name,
+ const char *desc, ternary default_val,
+ int offset)
+{
+ relopt_ternary *newoption = init_ternary_reloption(RELOPT_KIND_LOCAL,
+ name, desc,
+ default_val, 0);
+
+ add_local_reloption(relopts, (relopt_gen *) newoption, offset);
+}
/*
* init_real_reloption
@@ -1617,6 +1689,18 @@ parse_one_reloption(relopt_value *option, char *text_str, int text_len,
option->gen->name, value)));
}
break;
+ case RELOPT_TYPE_TERNARY:
+ {
+ bool b;
+ parsed = parse_bool(value, &b);
+ option->values.ternary_val = b ? TERNARY_TRUE : TERNARY_FALSE;
+ if (validate && !parsed)
+ ereport(ERROR,
+ (errcode(ERRCODE_INVALID_PARAMETER_VALUE),
+ errmsg("invalid value for ternary option \"%s\": %s",
+ option->gen->name, value)));
+ }
+ break;
case RELOPT_TYPE_INT:
{
relopt_int *optint = (relopt_int *) option->gen;
@@ -1780,17 +1864,6 @@ fillRelOptions(void *rdopts, Size basesize,
char *itempos = ((char *) rdopts) + elems[j].offset;
char *string_val;
- /*
- * If isset_offset is provided, store whether the reloption is
- * set there.
- */
- if (elems[j].isset_offset > 0)
- {
- char *setpos = ((char *) rdopts) + elems[j].isset_offset;
-
- *(bool *) setpos = options[i].isset;
- }
-
switch (options[i].gen->type)
{
case RELOPT_TYPE_BOOL:
@@ -1798,6 +1871,11 @@ fillRelOptions(void *rdopts, Size basesize,
options[i].values.bool_val :
((relopt_bool *) options[i].gen)->default_val;
break;
+ case RELOPT_TYPE_TERNARY:
+ *(ternary *) itempos = options[i].isset ?
+ options[i].values.ternary_val :
+ ((relopt_ternary *) options[i].gen)->default_val;
+ break;
case RELOPT_TYPE_INT:
*(int *) itempos = options[i].isset ?
options[i].values.int_val :
@@ -1912,8 +1990,8 @@ default_reloptions(Datum reloptions, bool validate, relopt_kind kind)
offsetof(StdRdOptions, parallel_workers)},
{"vacuum_index_cleanup", RELOPT_TYPE_ENUM,
offsetof(StdRdOptions, vacuum_index_cleanup)},
- {"vacuum_truncate", RELOPT_TYPE_BOOL,
- offsetof(StdRdOptions, vacuum_truncate), offsetof(StdRdOptions, vacuum_truncate_set)},
+ {"vacuum_truncate", RELOPT_TYPE_TERNARY,
+ offsetof(StdRdOptions, vacuum_truncate)},
{"vacuum_max_eager_freeze_failure_rate", RELOPT_TYPE_REAL,
offsetof(StdRdOptions, vacuum_max_eager_freeze_failure_rate)}
};
@@ -1993,7 +2071,6 @@ build_local_reloptions(local_relopts *relopts, Datum options, bool validate)
elems[i].optname = opt->option->name;
elems[i].opttype = opt->option->type;
elems[i].offset = opt->offset;
- elems[i].isset_offset = 0; /* not supported for local relopts yet */
i++;
}
diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c
index 733ef40ae7..977babff54 100644
--- a/src/backend/commands/vacuum.c
+++ b/src/backend/commands/vacuum.c
@@ -2221,9 +2221,9 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams params,
{
StdRdOptions *opts = (StdRdOptions *) rel->rd_options;
- if (opts && opts->vacuum_truncate_set)
+ if (opts && opts->vacuum_truncate != TERNARY_UNSET)
{
- if (opts->vacuum_truncate)
+ if (opts->vacuum_truncate == TERNARY_TRUE)
params.truncate = VACOPTVALUE_ENABLED;
else
params.truncate = VACOPTVALUE_DISABLED;
diff --git a/src/include/access/reloptions.h b/src/include/access/reloptions.h
index a604a4702c..a436697658 100644
--- a/src/include/access/reloptions.h
+++ b/src/include/access/reloptions.h
@@ -29,6 +29,7 @@
typedef enum relopt_type
{
RELOPT_TYPE_BOOL,
+ RELOPT_TYPE_TERNARY, /* on, off, unset */
RELOPT_TYPE_INT,
RELOPT_TYPE_REAL,
RELOPT_TYPE_ENUM,
@@ -80,6 +81,7 @@ typedef struct relopt_value
union
{
bool bool_val;
+ ternary ternary_val;
int int_val;
double real_val;
int enum_val;
@@ -94,6 +96,12 @@ typedef struct relopt_bool
bool default_val;
} relopt_bool;
+typedef struct relopt_rernary
+{
+ relopt_gen gen;
+ int default_val;
+} relopt_ternary;
+
typedef struct relopt_int
{
relopt_gen gen;
@@ -152,19 +160,6 @@ typedef struct
const char *optname; /* option's name */
relopt_type opttype; /* option's datatype */
int offset; /* offset of field in result struct */
-
- /*
- * isset_offset is an optional offset of a field in the result struct that
- * stores whether the option is explicitly set for the relation or if it
- * just picked up the default value. In most cases, this can be
- * accomplished by giving the reloption a special out-of-range default
- * value (e.g., some integer reloptions use -2), but this isn't always
- * possible. For example, a Boolean reloption cannot be given an
- * out-of-range default, so we need another way to discover the source of
- * its value. This offset is only used if given a value greater than
- * zero.
- */
- int isset_offset;
} relopt_parse_elt;
/* Local reloption definition */
@@ -195,6 +190,8 @@ typedef struct local_relopts
extern relopt_kind add_reloption_kind(void);
extern void add_bool_reloption(bits32 kinds, const char *name, const char *desc,
bool default_val, LOCKMODE lockmode);
+extern void add_ternary_reloption(bits32 kinds, const char *name,
+ const char *desc, int default_val, LOCKMODE lockmode);
extern void add_int_reloption(bits32 kinds, const char *name, const char *desc,
int default_val, int min_val, int max_val,
LOCKMODE lockmode);
@@ -214,6 +211,9 @@ extern void register_reloptions_validator(local_relopts *relopts,
extern void add_local_bool_reloption(local_relopts *relopts, const char *name,
const char *desc, bool default_val,
int offset);
+extern void add_local_ternary_reloption(local_relopts *relopts,
+ const char *name, const char *desc,
+ ternary default_val, int offset);
extern void add_local_int_reloption(local_relopts *relopts, const char *name,
const char *desc, int default_val,
int min_val, int max_val, int offset);
diff --git a/src/include/c.h b/src/include/c.h
index 39022f8a9d..9c59ccccf8 100644
--- a/src/include/c.h
+++ b/src/include/c.h
@@ -488,6 +488,22 @@ typedef void (*pg_funcptr_t) (void);
#include <stdbool.h>
+/*
+ * ternary
+ * Boolean value with an extrea "unset" option
+ *
+ * Ternary data type is used in relation options that can be "true", "false" or
+ * "unset". Since relation options are used deep inside the PostgreSQL code,
+ * this type is declared globally.
+*/
+
+typedef enum ternary
+{
+ TERNARY_FALSE = 0,
+ TERNARY_TRUE = 1,
+ TERNARY_UNSET = -1
+} ternary;
+
/* ----------------------------------------------------------------
* Section 3: standard system types
diff --git a/src/include/utils/rel.h b/src/include/utils/rel.h
index b552359915..7346d618f9 100644
--- a/src/include/utils/rel.h
+++ b/src/include/utils/rel.h
@@ -346,8 +346,7 @@ typedef struct StdRdOptions
bool user_catalog_table; /* use as an additional catalog relation */
int parallel_workers; /* max number of parallel workers */
StdRdOptIndexCleanup vacuum_index_cleanup; /* controls index vacuuming */
- bool vacuum_truncate; /* enables vacuum to truncate a relation */
- bool vacuum_truncate_set; /* whether vacuum_truncate is set */
+ ternary vacuum_truncate; /* enables vacuum to truncate a relation */
/*
* Fraction of pages in a relation that vacuum can eagerly scan and fail
--
2.39.2
--nextPart5129932.5fSG56mABF
Content-Disposition: attachment;
filename="v2-0003-Add-alias-to-be-used-as-unset-state.patch"
Content-Transfer-Encoding: 7Bit
Content-Type: text/x-patch; charset="unicode-2-0-utf-8";
name="v2-0003-Add-alias-to-be-used-as-unset-state.patch"
^ permalink raw reply [nested|flat] 868+ messages in thread
* [PATCH v2a 2/4] Introduce ternary reloptions
@ 2025-09-04 14:41 Nikolay Shaplov <[email protected]>
0 siblings, 0 replies; 868+ messages in thread
From: Nikolay Shaplov @ 2025-09-04 14:41 UTC (permalink / raw)
Introduce ternary reloption as a replacement for current `vacuum_truncate`
implementation. Remove `vacuum_truncate_set` additional flag and using
`TERNARY_UNSET` value instead.
---
src/backend/access/common/reloptions.c | 129 ++++++++++++++++++++-----
src/backend/commands/vacuum.c | 4 +-
src/include/access/reloptions.h | 26 ++---
src/include/c.h | 16 +++
src/include/utils/rel.h | 3 +-
5 files changed, 135 insertions(+), 43 deletions(-)
diff --git a/src/backend/access/common/reloptions.c b/src/backend/access/common/reloptions.c
index 0af3fea68f..f543f11001 100644
--- a/src/backend/access/common/reloptions.c
+++ b/src/backend/access/common/reloptions.c
@@ -40,9 +40,9 @@
*
* To add an option:
*
- * (i) decide on a type (bool, integer, real, enum, string), name, default
- * value, upper and lower bounds (if applicable); for strings, consider a
- * validation routine.
+ * (i) decide on a type (bool, ternary, integer, real, enum, string), name,
+ * default value, upper and lower bounds (if applicable); for strings,
+ * consider a validation routine.
* (ii) add a record below (or use add_<type>_reloption).
* (iii) add it to the appropriate options struct (perhaps StdRdOptions)
* (iv) add it to the appropriate handling routine (perhaps
@@ -147,15 +147,6 @@ static relopt_bool boolRelOpts[] =
},
false
},
- {
- {
- "vacuum_truncate",
- "Enables vacuum to truncate empty pages at the end of this table",
- RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
- ShareUpdateExclusiveLock
- },
- true
- },
{
{
"deduplicate_items",
@@ -170,6 +161,21 @@ static relopt_bool boolRelOpts[] =
{{NULL}}
};
+static relopt_ternary ternaryRelOpts[] =
+{
+ {
+ {
+ "vacuum_truncate",
+ "Enables vacuum to truncate empty pages at the end of this table",
+ RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
+ ShareUpdateExclusiveLock
+ },
+ TERNARY_UNSET
+ },
+ /* list terminator */
+ {{NULL}}
+};
+
static relopt_int intRelOpts[] =
{
{
@@ -600,6 +606,13 @@ initialize_reloptions(void)
boolRelOpts[i].gen.lockmode));
j++;
}
+ for (i = 0; ternaryRelOpts[i].gen.name; i++)
+ {
+ Assert(DoLockModesConflict(ternaryRelOpts[i].gen.lockmode,
+ ternaryRelOpts[i].gen.lockmode));
+ j++;
+ }
+
for (i = 0; intRelOpts[i].gen.name; i++)
{
Assert(DoLockModesConflict(intRelOpts[i].gen.lockmode,
@@ -640,6 +653,14 @@ initialize_reloptions(void)
j++;
}
+ for (i = 0; ternaryRelOpts[i].gen.name; i++)
+ {
+ relOpts[j] = &ternaryRelOpts[i].gen;
+ relOpts[j]->type = RELOPT_TYPE_TERNARY;
+ relOpts[j]->namelen = strlen(relOpts[j]->name);
+ j++;
+ }
+
for (i = 0; intRelOpts[i].gen.name; i++)
{
relOpts[j] = &intRelOpts[i].gen;
@@ -800,6 +821,9 @@ allocate_reloption(bits32 kinds, int type, const char *name, const char *desc,
case RELOPT_TYPE_BOOL:
size = sizeof(relopt_bool);
break;
+ case RELOPT_TYPE_TERNARY:
+ size = sizeof(relopt_ternary);
+ break;
case RELOPT_TYPE_INT:
size = sizeof(relopt_int);
break;
@@ -883,6 +907,54 @@ add_local_bool_reloption(local_relopts *relopts, const char *name,
add_local_reloption(relopts, (relopt_gen *) newoption, offset);
}
+/*
+ * init_ternary_reloption
+ * Allocate and initialize a new ternary reloption
+ */
+static relopt_ternary *
+init_ternary_reloption(bits32 kinds, const char *name, const char *desc,
+ ternary default_val, LOCKMODE lockmode)
+{
+ relopt_ternary *newoption;
+
+ newoption = (relopt_ternary *) allocate_reloption(kinds,
+ RELOPT_TYPE_TERNARY, name, desc, lockmode);
+ newoption->default_val = default_val;
+
+ return newoption;
+}
+
+/*
+ * add_ternary_reloption
+ * Add a new ternary reloption
+ */
+void
+add_ternary_reloption(bits32 kinds, const char *name, const char *desc,
+ ternary default_val, LOCKMODE lockmode)
+{
+ relopt_ternary *newoption = init_ternary_reloption(kinds, name, desc,
+ default_val, lockmode);
+
+ add_reloption((relopt_gen *) newoption);
+}
+
+/*
+ * add_local_ternary_reloption
+ * Add a new ternary local reloption
+ *
+ * 'offset' is offset of ternary-typed field.
+ */
+void
+add_local_ternary_reloption(local_relopts *relopts, const char *name,
+ const char *desc, ternary default_val,
+ int offset)
+{
+ relopt_ternary *newoption = init_ternary_reloption(RELOPT_KIND_LOCAL,
+ name, desc,
+ default_val, 0);
+
+ add_local_reloption(relopts, (relopt_gen *) newoption, offset);
+}
/*
* init_real_reloption
@@ -1617,6 +1689,18 @@ parse_one_reloption(relopt_value *option, char *text_str, int text_len,
option->gen->name, value)));
}
break;
+ case RELOPT_TYPE_TERNARY:
+ {
+ bool b;
+ parsed = parse_bool(value, &b);
+ option->values.ternary_val = b ? TERNARY_TRUE : TERNARY_FALSE;
+ if (validate && !parsed)
+ ereport(ERROR,
+ (errcode(ERRCODE_INVALID_PARAMETER_VALUE),
+ errmsg("invalid value for ternary option \"%s\": %s",
+ option->gen->name, value)));
+ }
+ break;
case RELOPT_TYPE_INT:
{
relopt_int *optint = (relopt_int *) option->gen;
@@ -1780,17 +1864,6 @@ fillRelOptions(void *rdopts, Size basesize,
char *itempos = ((char *) rdopts) + elems[j].offset;
char *string_val;
- /*
- * If isset_offset is provided, store whether the reloption is
- * set there.
- */
- if (elems[j].isset_offset > 0)
- {
- char *setpos = ((char *) rdopts) + elems[j].isset_offset;
-
- *(bool *) setpos = options[i].isset;
- }
-
switch (options[i].gen->type)
{
case RELOPT_TYPE_BOOL:
@@ -1798,6 +1871,11 @@ fillRelOptions(void *rdopts, Size basesize,
options[i].values.bool_val :
((relopt_bool *) options[i].gen)->default_val;
break;
+ case RELOPT_TYPE_TERNARY:
+ *(ternary *) itempos = options[i].isset ?
+ options[i].values.ternary_val :
+ ((relopt_ternary *) options[i].gen)->default_val;
+ break;
case RELOPT_TYPE_INT:
*(int *) itempos = options[i].isset ?
options[i].values.int_val :
@@ -1912,8 +1990,8 @@ default_reloptions(Datum reloptions, bool validate, relopt_kind kind)
offsetof(StdRdOptions, parallel_workers)},
{"vacuum_index_cleanup", RELOPT_TYPE_ENUM,
offsetof(StdRdOptions, vacuum_index_cleanup)},
- {"vacuum_truncate", RELOPT_TYPE_BOOL,
- offsetof(StdRdOptions, vacuum_truncate), offsetof(StdRdOptions, vacuum_truncate_set)},
+ {"vacuum_truncate", RELOPT_TYPE_TERNARY,
+ offsetof(StdRdOptions, vacuum_truncate)},
{"vacuum_max_eager_freeze_failure_rate", RELOPT_TYPE_REAL,
offsetof(StdRdOptions, vacuum_max_eager_freeze_failure_rate)}
};
@@ -1993,7 +2071,6 @@ build_local_reloptions(local_relopts *relopts, Datum options, bool validate)
elems[i].optname = opt->option->name;
elems[i].opttype = opt->option->type;
elems[i].offset = opt->offset;
- elems[i].isset_offset = 0; /* not supported for local relopts yet */
i++;
}
diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c
index 733ef40ae7..977babff54 100644
--- a/src/backend/commands/vacuum.c
+++ b/src/backend/commands/vacuum.c
@@ -2221,9 +2221,9 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams params,
{
StdRdOptions *opts = (StdRdOptions *) rel->rd_options;
- if (opts && opts->vacuum_truncate_set)
+ if (opts && opts->vacuum_truncate != TERNARY_UNSET)
{
- if (opts->vacuum_truncate)
+ if (opts->vacuum_truncate == TERNARY_TRUE)
params.truncate = VACOPTVALUE_ENABLED;
else
params.truncate = VACOPTVALUE_DISABLED;
diff --git a/src/include/access/reloptions.h b/src/include/access/reloptions.h
index a604a4702c..a436697658 100644
--- a/src/include/access/reloptions.h
+++ b/src/include/access/reloptions.h
@@ -29,6 +29,7 @@
typedef enum relopt_type
{
RELOPT_TYPE_BOOL,
+ RELOPT_TYPE_TERNARY, /* on, off, unset */
RELOPT_TYPE_INT,
RELOPT_TYPE_REAL,
RELOPT_TYPE_ENUM,
@@ -80,6 +81,7 @@ typedef struct relopt_value
union
{
bool bool_val;
+ ternary ternary_val;
int int_val;
double real_val;
int enum_val;
@@ -94,6 +96,12 @@ typedef struct relopt_bool
bool default_val;
} relopt_bool;
+typedef struct relopt_rernary
+{
+ relopt_gen gen;
+ int default_val;
+} relopt_ternary;
+
typedef struct relopt_int
{
relopt_gen gen;
@@ -152,19 +160,6 @@ typedef struct
const char *optname; /* option's name */
relopt_type opttype; /* option's datatype */
int offset; /* offset of field in result struct */
-
- /*
- * isset_offset is an optional offset of a field in the result struct that
- * stores whether the option is explicitly set for the relation or if it
- * just picked up the default value. In most cases, this can be
- * accomplished by giving the reloption a special out-of-range default
- * value (e.g., some integer reloptions use -2), but this isn't always
- * possible. For example, a Boolean reloption cannot be given an
- * out-of-range default, so we need another way to discover the source of
- * its value. This offset is only used if given a value greater than
- * zero.
- */
- int isset_offset;
} relopt_parse_elt;
/* Local reloption definition */
@@ -195,6 +190,8 @@ typedef struct local_relopts
extern relopt_kind add_reloption_kind(void);
extern void add_bool_reloption(bits32 kinds, const char *name, const char *desc,
bool default_val, LOCKMODE lockmode);
+extern void add_ternary_reloption(bits32 kinds, const char *name,
+ const char *desc, int default_val, LOCKMODE lockmode);
extern void add_int_reloption(bits32 kinds, const char *name, const char *desc,
int default_val, int min_val, int max_val,
LOCKMODE lockmode);
@@ -214,6 +211,9 @@ extern void register_reloptions_validator(local_relopts *relopts,
extern void add_local_bool_reloption(local_relopts *relopts, const char *name,
const char *desc, bool default_val,
int offset);
+extern void add_local_ternary_reloption(local_relopts *relopts,
+ const char *name, const char *desc,
+ ternary default_val, int offset);
extern void add_local_int_reloption(local_relopts *relopts, const char *name,
const char *desc, int default_val,
int min_val, int max_val, int offset);
diff --git a/src/include/c.h b/src/include/c.h
index 39022f8a9d..9c59ccccf8 100644
--- a/src/include/c.h
+++ b/src/include/c.h
@@ -488,6 +488,22 @@ typedef void (*pg_funcptr_t) (void);
#include <stdbool.h>
+/*
+ * ternary
+ * Boolean value with an extrea "unset" option
+ *
+ * Ternary data type is used in relation options that can be "true", "false" or
+ * "unset". Since relation options are used deep inside the PostgreSQL code,
+ * this type is declared globally.
+*/
+
+typedef enum ternary
+{
+ TERNARY_FALSE = 0,
+ TERNARY_TRUE = 1,
+ TERNARY_UNSET = -1
+} ternary;
+
/* ----------------------------------------------------------------
* Section 3: standard system types
diff --git a/src/include/utils/rel.h b/src/include/utils/rel.h
index b552359915..7346d618f9 100644
--- a/src/include/utils/rel.h
+++ b/src/include/utils/rel.h
@@ -346,8 +346,7 @@ typedef struct StdRdOptions
bool user_catalog_table; /* use as an additional catalog relation */
int parallel_workers; /* max number of parallel workers */
StdRdOptIndexCleanup vacuum_index_cleanup; /* controls index vacuuming */
- bool vacuum_truncate; /* enables vacuum to truncate a relation */
- bool vacuum_truncate_set; /* whether vacuum_truncate is set */
+ ternary vacuum_truncate; /* enables vacuum to truncate a relation */
/*
* Fraction of pages in a relation that vacuum can eagerly scan and fail
--
2.39.2
--nextPart4155600.3ZeAukHxDK
Content-Disposition: attachment;
filename="v2a-0003-Add-alias-to-be-used-as-unset-state.patch"
Content-Transfer-Encoding: 7Bit
Content-Type: text/x-patch; charset="unicode-2-0-utf-8";
name="v2a-0003-Add-alias-to-be-used-as-unset-state.patch"
^ permalink raw reply [nested|flat] 868+ messages in thread
* [PATCH v2 2/4] Introduce ternary reloptions
@ 2025-09-04 14:41 Nikolay Shaplov <[email protected]>
0 siblings, 0 replies; 868+ messages in thread
From: Nikolay Shaplov @ 2025-09-04 14:41 UTC (permalink / raw)
Introduce ternary reloption as a replacement for current `vacuum_truncate`
implementation. Remove `vacuum_truncate_set` additional flag and using
`TERNARY_UNSET` value instead.
---
src/backend/access/common/reloptions.c | 129 ++++++++++++++++++++-----
src/backend/commands/vacuum.c | 4 +-
src/include/access/reloptions.h | 26 ++---
src/include/c.h | 16 +++
src/include/utils/rel.h | 3 +-
5 files changed, 135 insertions(+), 43 deletions(-)
diff --git a/src/backend/access/common/reloptions.c b/src/backend/access/common/reloptions.c
index 0af3fea68f..f543f11001 100644
--- a/src/backend/access/common/reloptions.c
+++ b/src/backend/access/common/reloptions.c
@@ -40,9 +40,9 @@
*
* To add an option:
*
- * (i) decide on a type (bool, integer, real, enum, string), name, default
- * value, upper and lower bounds (if applicable); for strings, consider a
- * validation routine.
+ * (i) decide on a type (bool, ternary, integer, real, enum, string), name,
+ * default value, upper and lower bounds (if applicable); for strings,
+ * consider a validation routine.
* (ii) add a record below (or use add_<type>_reloption).
* (iii) add it to the appropriate options struct (perhaps StdRdOptions)
* (iv) add it to the appropriate handling routine (perhaps
@@ -147,15 +147,6 @@ static relopt_bool boolRelOpts[] =
},
false
},
- {
- {
- "vacuum_truncate",
- "Enables vacuum to truncate empty pages at the end of this table",
- RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
- ShareUpdateExclusiveLock
- },
- true
- },
{
{
"deduplicate_items",
@@ -170,6 +161,21 @@ static relopt_bool boolRelOpts[] =
{{NULL}}
};
+static relopt_ternary ternaryRelOpts[] =
+{
+ {
+ {
+ "vacuum_truncate",
+ "Enables vacuum to truncate empty pages at the end of this table",
+ RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
+ ShareUpdateExclusiveLock
+ },
+ TERNARY_UNSET
+ },
+ /* list terminator */
+ {{NULL}}
+};
+
static relopt_int intRelOpts[] =
{
{
@@ -600,6 +606,13 @@ initialize_reloptions(void)
boolRelOpts[i].gen.lockmode));
j++;
}
+ for (i = 0; ternaryRelOpts[i].gen.name; i++)
+ {
+ Assert(DoLockModesConflict(ternaryRelOpts[i].gen.lockmode,
+ ternaryRelOpts[i].gen.lockmode));
+ j++;
+ }
+
for (i = 0; intRelOpts[i].gen.name; i++)
{
Assert(DoLockModesConflict(intRelOpts[i].gen.lockmode,
@@ -640,6 +653,14 @@ initialize_reloptions(void)
j++;
}
+ for (i = 0; ternaryRelOpts[i].gen.name; i++)
+ {
+ relOpts[j] = &ternaryRelOpts[i].gen;
+ relOpts[j]->type = RELOPT_TYPE_TERNARY;
+ relOpts[j]->namelen = strlen(relOpts[j]->name);
+ j++;
+ }
+
for (i = 0; intRelOpts[i].gen.name; i++)
{
relOpts[j] = &intRelOpts[i].gen;
@@ -800,6 +821,9 @@ allocate_reloption(bits32 kinds, int type, const char *name, const char *desc,
case RELOPT_TYPE_BOOL:
size = sizeof(relopt_bool);
break;
+ case RELOPT_TYPE_TERNARY:
+ size = sizeof(relopt_ternary);
+ break;
case RELOPT_TYPE_INT:
size = sizeof(relopt_int);
break;
@@ -883,6 +907,54 @@ add_local_bool_reloption(local_relopts *relopts, const char *name,
add_local_reloption(relopts, (relopt_gen *) newoption, offset);
}
+/*
+ * init_ternary_reloption
+ * Allocate and initialize a new ternary reloption
+ */
+static relopt_ternary *
+init_ternary_reloption(bits32 kinds, const char *name, const char *desc,
+ ternary default_val, LOCKMODE lockmode)
+{
+ relopt_ternary *newoption;
+
+ newoption = (relopt_ternary *) allocate_reloption(kinds,
+ RELOPT_TYPE_TERNARY, name, desc, lockmode);
+ newoption->default_val = default_val;
+
+ return newoption;
+}
+
+/*
+ * add_ternary_reloption
+ * Add a new ternary reloption
+ */
+void
+add_ternary_reloption(bits32 kinds, const char *name, const char *desc,
+ ternary default_val, LOCKMODE lockmode)
+{
+ relopt_ternary *newoption = init_ternary_reloption(kinds, name, desc,
+ default_val, lockmode);
+
+ add_reloption((relopt_gen *) newoption);
+}
+
+/*
+ * add_local_ternary_reloption
+ * Add a new ternary local reloption
+ *
+ * 'offset' is offset of ternary-typed field.
+ */
+void
+add_local_ternary_reloption(local_relopts *relopts, const char *name,
+ const char *desc, ternary default_val,
+ int offset)
+{
+ relopt_ternary *newoption = init_ternary_reloption(RELOPT_KIND_LOCAL,
+ name, desc,
+ default_val, 0);
+
+ add_local_reloption(relopts, (relopt_gen *) newoption, offset);
+}
/*
* init_real_reloption
@@ -1617,6 +1689,18 @@ parse_one_reloption(relopt_value *option, char *text_str, int text_len,
option->gen->name, value)));
}
break;
+ case RELOPT_TYPE_TERNARY:
+ {
+ bool b;
+ parsed = parse_bool(value, &b);
+ option->values.ternary_val = b ? TERNARY_TRUE : TERNARY_FALSE;
+ if (validate && !parsed)
+ ereport(ERROR,
+ (errcode(ERRCODE_INVALID_PARAMETER_VALUE),
+ errmsg("invalid value for ternary option \"%s\": %s",
+ option->gen->name, value)));
+ }
+ break;
case RELOPT_TYPE_INT:
{
relopt_int *optint = (relopt_int *) option->gen;
@@ -1780,17 +1864,6 @@ fillRelOptions(void *rdopts, Size basesize,
char *itempos = ((char *) rdopts) + elems[j].offset;
char *string_val;
- /*
- * If isset_offset is provided, store whether the reloption is
- * set there.
- */
- if (elems[j].isset_offset > 0)
- {
- char *setpos = ((char *) rdopts) + elems[j].isset_offset;
-
- *(bool *) setpos = options[i].isset;
- }
-
switch (options[i].gen->type)
{
case RELOPT_TYPE_BOOL:
@@ -1798,6 +1871,11 @@ fillRelOptions(void *rdopts, Size basesize,
options[i].values.bool_val :
((relopt_bool *) options[i].gen)->default_val;
break;
+ case RELOPT_TYPE_TERNARY:
+ *(ternary *) itempos = options[i].isset ?
+ options[i].values.ternary_val :
+ ((relopt_ternary *) options[i].gen)->default_val;
+ break;
case RELOPT_TYPE_INT:
*(int *) itempos = options[i].isset ?
options[i].values.int_val :
@@ -1912,8 +1990,8 @@ default_reloptions(Datum reloptions, bool validate, relopt_kind kind)
offsetof(StdRdOptions, parallel_workers)},
{"vacuum_index_cleanup", RELOPT_TYPE_ENUM,
offsetof(StdRdOptions, vacuum_index_cleanup)},
- {"vacuum_truncate", RELOPT_TYPE_BOOL,
- offsetof(StdRdOptions, vacuum_truncate), offsetof(StdRdOptions, vacuum_truncate_set)},
+ {"vacuum_truncate", RELOPT_TYPE_TERNARY,
+ offsetof(StdRdOptions, vacuum_truncate)},
{"vacuum_max_eager_freeze_failure_rate", RELOPT_TYPE_REAL,
offsetof(StdRdOptions, vacuum_max_eager_freeze_failure_rate)}
};
@@ -1993,7 +2071,6 @@ build_local_reloptions(local_relopts *relopts, Datum options, bool validate)
elems[i].optname = opt->option->name;
elems[i].opttype = opt->option->type;
elems[i].offset = opt->offset;
- elems[i].isset_offset = 0; /* not supported for local relopts yet */
i++;
}
diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c
index 733ef40ae7..977babff54 100644
--- a/src/backend/commands/vacuum.c
+++ b/src/backend/commands/vacuum.c
@@ -2221,9 +2221,9 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams params,
{
StdRdOptions *opts = (StdRdOptions *) rel->rd_options;
- if (opts && opts->vacuum_truncate_set)
+ if (opts && opts->vacuum_truncate != TERNARY_UNSET)
{
- if (opts->vacuum_truncate)
+ if (opts->vacuum_truncate == TERNARY_TRUE)
params.truncate = VACOPTVALUE_ENABLED;
else
params.truncate = VACOPTVALUE_DISABLED;
diff --git a/src/include/access/reloptions.h b/src/include/access/reloptions.h
index a604a4702c..a436697658 100644
--- a/src/include/access/reloptions.h
+++ b/src/include/access/reloptions.h
@@ -29,6 +29,7 @@
typedef enum relopt_type
{
RELOPT_TYPE_BOOL,
+ RELOPT_TYPE_TERNARY, /* on, off, unset */
RELOPT_TYPE_INT,
RELOPT_TYPE_REAL,
RELOPT_TYPE_ENUM,
@@ -80,6 +81,7 @@ typedef struct relopt_value
union
{
bool bool_val;
+ ternary ternary_val;
int int_val;
double real_val;
int enum_val;
@@ -94,6 +96,12 @@ typedef struct relopt_bool
bool default_val;
} relopt_bool;
+typedef struct relopt_rernary
+{
+ relopt_gen gen;
+ int default_val;
+} relopt_ternary;
+
typedef struct relopt_int
{
relopt_gen gen;
@@ -152,19 +160,6 @@ typedef struct
const char *optname; /* option's name */
relopt_type opttype; /* option's datatype */
int offset; /* offset of field in result struct */
-
- /*
- * isset_offset is an optional offset of a field in the result struct that
- * stores whether the option is explicitly set for the relation or if it
- * just picked up the default value. In most cases, this can be
- * accomplished by giving the reloption a special out-of-range default
- * value (e.g., some integer reloptions use -2), but this isn't always
- * possible. For example, a Boolean reloption cannot be given an
- * out-of-range default, so we need another way to discover the source of
- * its value. This offset is only used if given a value greater than
- * zero.
- */
- int isset_offset;
} relopt_parse_elt;
/* Local reloption definition */
@@ -195,6 +190,8 @@ typedef struct local_relopts
extern relopt_kind add_reloption_kind(void);
extern void add_bool_reloption(bits32 kinds, const char *name, const char *desc,
bool default_val, LOCKMODE lockmode);
+extern void add_ternary_reloption(bits32 kinds, const char *name,
+ const char *desc, int default_val, LOCKMODE lockmode);
extern void add_int_reloption(bits32 kinds, const char *name, const char *desc,
int default_val, int min_val, int max_val,
LOCKMODE lockmode);
@@ -214,6 +211,9 @@ extern void register_reloptions_validator(local_relopts *relopts,
extern void add_local_bool_reloption(local_relopts *relopts, const char *name,
const char *desc, bool default_val,
int offset);
+extern void add_local_ternary_reloption(local_relopts *relopts,
+ const char *name, const char *desc,
+ ternary default_val, int offset);
extern void add_local_int_reloption(local_relopts *relopts, const char *name,
const char *desc, int default_val,
int min_val, int max_val, int offset);
diff --git a/src/include/c.h b/src/include/c.h
index 39022f8a9d..9c59ccccf8 100644
--- a/src/include/c.h
+++ b/src/include/c.h
@@ -488,6 +488,22 @@ typedef void (*pg_funcptr_t) (void);
#include <stdbool.h>
+/*
+ * ternary
+ * Boolean value with an extrea "unset" option
+ *
+ * Ternary data type is used in relation options that can be "true", "false" or
+ * "unset". Since relation options are used deep inside the PostgreSQL code,
+ * this type is declared globally.
+*/
+
+typedef enum ternary
+{
+ TERNARY_FALSE = 0,
+ TERNARY_TRUE = 1,
+ TERNARY_UNSET = -1
+} ternary;
+
/* ----------------------------------------------------------------
* Section 3: standard system types
diff --git a/src/include/utils/rel.h b/src/include/utils/rel.h
index b552359915..7346d618f9 100644
--- a/src/include/utils/rel.h
+++ b/src/include/utils/rel.h
@@ -346,8 +346,7 @@ typedef struct StdRdOptions
bool user_catalog_table; /* use as an additional catalog relation */
int parallel_workers; /* max number of parallel workers */
StdRdOptIndexCleanup vacuum_index_cleanup; /* controls index vacuuming */
- bool vacuum_truncate; /* enables vacuum to truncate a relation */
- bool vacuum_truncate_set; /* whether vacuum_truncate is set */
+ ternary vacuum_truncate; /* enables vacuum to truncate a relation */
/*
* Fraction of pages in a relation that vacuum can eagerly scan and fail
--
2.39.2
--nextPart5129932.5fSG56mABF
Content-Disposition: attachment;
filename="v2-0003-Add-alias-to-be-used-as-unset-state.patch"
Content-Transfer-Encoding: 7Bit
Content-Type: text/x-patch; charset="unicode-2-0-utf-8";
name="v2-0003-Add-alias-to-be-used-as-unset-state.patch"
^ permalink raw reply [nested|flat] 868+ messages in thread
* [PATCH v2a 2/4] Introduce ternary reloptions
@ 2025-09-04 14:41 Nikolay Shaplov <[email protected]>
0 siblings, 0 replies; 868+ messages in thread
From: Nikolay Shaplov @ 2025-09-04 14:41 UTC (permalink / raw)
Introduce ternary reloption as a replacement for current `vacuum_truncate`
implementation. Remove `vacuum_truncate_set` additional flag and using
`TERNARY_UNSET` value instead.
---
src/backend/access/common/reloptions.c | 129 ++++++++++++++++++++-----
src/backend/commands/vacuum.c | 4 +-
src/include/access/reloptions.h | 26 ++---
src/include/c.h | 16 +++
src/include/utils/rel.h | 3 +-
5 files changed, 135 insertions(+), 43 deletions(-)
diff --git a/src/backend/access/common/reloptions.c b/src/backend/access/common/reloptions.c
index 0af3fea68f..f543f11001 100644
--- a/src/backend/access/common/reloptions.c
+++ b/src/backend/access/common/reloptions.c
@@ -40,9 +40,9 @@
*
* To add an option:
*
- * (i) decide on a type (bool, integer, real, enum, string), name, default
- * value, upper and lower bounds (if applicable); for strings, consider a
- * validation routine.
+ * (i) decide on a type (bool, ternary, integer, real, enum, string), name,
+ * default value, upper and lower bounds (if applicable); for strings,
+ * consider a validation routine.
* (ii) add a record below (or use add_<type>_reloption).
* (iii) add it to the appropriate options struct (perhaps StdRdOptions)
* (iv) add it to the appropriate handling routine (perhaps
@@ -147,15 +147,6 @@ static relopt_bool boolRelOpts[] =
},
false
},
- {
- {
- "vacuum_truncate",
- "Enables vacuum to truncate empty pages at the end of this table",
- RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
- ShareUpdateExclusiveLock
- },
- true
- },
{
{
"deduplicate_items",
@@ -170,6 +161,21 @@ static relopt_bool boolRelOpts[] =
{{NULL}}
};
+static relopt_ternary ternaryRelOpts[] =
+{
+ {
+ {
+ "vacuum_truncate",
+ "Enables vacuum to truncate empty pages at the end of this table",
+ RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
+ ShareUpdateExclusiveLock
+ },
+ TERNARY_UNSET
+ },
+ /* list terminator */
+ {{NULL}}
+};
+
static relopt_int intRelOpts[] =
{
{
@@ -600,6 +606,13 @@ initialize_reloptions(void)
boolRelOpts[i].gen.lockmode));
j++;
}
+ for (i = 0; ternaryRelOpts[i].gen.name; i++)
+ {
+ Assert(DoLockModesConflict(ternaryRelOpts[i].gen.lockmode,
+ ternaryRelOpts[i].gen.lockmode));
+ j++;
+ }
+
for (i = 0; intRelOpts[i].gen.name; i++)
{
Assert(DoLockModesConflict(intRelOpts[i].gen.lockmode,
@@ -640,6 +653,14 @@ initialize_reloptions(void)
j++;
}
+ for (i = 0; ternaryRelOpts[i].gen.name; i++)
+ {
+ relOpts[j] = &ternaryRelOpts[i].gen;
+ relOpts[j]->type = RELOPT_TYPE_TERNARY;
+ relOpts[j]->namelen = strlen(relOpts[j]->name);
+ j++;
+ }
+
for (i = 0; intRelOpts[i].gen.name; i++)
{
relOpts[j] = &intRelOpts[i].gen;
@@ -800,6 +821,9 @@ allocate_reloption(bits32 kinds, int type, const char *name, const char *desc,
case RELOPT_TYPE_BOOL:
size = sizeof(relopt_bool);
break;
+ case RELOPT_TYPE_TERNARY:
+ size = sizeof(relopt_ternary);
+ break;
case RELOPT_TYPE_INT:
size = sizeof(relopt_int);
break;
@@ -883,6 +907,54 @@ add_local_bool_reloption(local_relopts *relopts, const char *name,
add_local_reloption(relopts, (relopt_gen *) newoption, offset);
}
+/*
+ * init_ternary_reloption
+ * Allocate and initialize a new ternary reloption
+ */
+static relopt_ternary *
+init_ternary_reloption(bits32 kinds, const char *name, const char *desc,
+ ternary default_val, LOCKMODE lockmode)
+{
+ relopt_ternary *newoption;
+
+ newoption = (relopt_ternary *) allocate_reloption(kinds,
+ RELOPT_TYPE_TERNARY, name, desc, lockmode);
+ newoption->default_val = default_val;
+
+ return newoption;
+}
+
+/*
+ * add_ternary_reloption
+ * Add a new ternary reloption
+ */
+void
+add_ternary_reloption(bits32 kinds, const char *name, const char *desc,
+ ternary default_val, LOCKMODE lockmode)
+{
+ relopt_ternary *newoption = init_ternary_reloption(kinds, name, desc,
+ default_val, lockmode);
+
+ add_reloption((relopt_gen *) newoption);
+}
+
+/*
+ * add_local_ternary_reloption
+ * Add a new ternary local reloption
+ *
+ * 'offset' is offset of ternary-typed field.
+ */
+void
+add_local_ternary_reloption(local_relopts *relopts, const char *name,
+ const char *desc, ternary default_val,
+ int offset)
+{
+ relopt_ternary *newoption = init_ternary_reloption(RELOPT_KIND_LOCAL,
+ name, desc,
+ default_val, 0);
+
+ add_local_reloption(relopts, (relopt_gen *) newoption, offset);
+}
/*
* init_real_reloption
@@ -1617,6 +1689,18 @@ parse_one_reloption(relopt_value *option, char *text_str, int text_len,
option->gen->name, value)));
}
break;
+ case RELOPT_TYPE_TERNARY:
+ {
+ bool b;
+ parsed = parse_bool(value, &b);
+ option->values.ternary_val = b ? TERNARY_TRUE : TERNARY_FALSE;
+ if (validate && !parsed)
+ ereport(ERROR,
+ (errcode(ERRCODE_INVALID_PARAMETER_VALUE),
+ errmsg("invalid value for ternary option \"%s\": %s",
+ option->gen->name, value)));
+ }
+ break;
case RELOPT_TYPE_INT:
{
relopt_int *optint = (relopt_int *) option->gen;
@@ -1780,17 +1864,6 @@ fillRelOptions(void *rdopts, Size basesize,
char *itempos = ((char *) rdopts) + elems[j].offset;
char *string_val;
- /*
- * If isset_offset is provided, store whether the reloption is
- * set there.
- */
- if (elems[j].isset_offset > 0)
- {
- char *setpos = ((char *) rdopts) + elems[j].isset_offset;
-
- *(bool *) setpos = options[i].isset;
- }
-
switch (options[i].gen->type)
{
case RELOPT_TYPE_BOOL:
@@ -1798,6 +1871,11 @@ fillRelOptions(void *rdopts, Size basesize,
options[i].values.bool_val :
((relopt_bool *) options[i].gen)->default_val;
break;
+ case RELOPT_TYPE_TERNARY:
+ *(ternary *) itempos = options[i].isset ?
+ options[i].values.ternary_val :
+ ((relopt_ternary *) options[i].gen)->default_val;
+ break;
case RELOPT_TYPE_INT:
*(int *) itempos = options[i].isset ?
options[i].values.int_val :
@@ -1912,8 +1990,8 @@ default_reloptions(Datum reloptions, bool validate, relopt_kind kind)
offsetof(StdRdOptions, parallel_workers)},
{"vacuum_index_cleanup", RELOPT_TYPE_ENUM,
offsetof(StdRdOptions, vacuum_index_cleanup)},
- {"vacuum_truncate", RELOPT_TYPE_BOOL,
- offsetof(StdRdOptions, vacuum_truncate), offsetof(StdRdOptions, vacuum_truncate_set)},
+ {"vacuum_truncate", RELOPT_TYPE_TERNARY,
+ offsetof(StdRdOptions, vacuum_truncate)},
{"vacuum_max_eager_freeze_failure_rate", RELOPT_TYPE_REAL,
offsetof(StdRdOptions, vacuum_max_eager_freeze_failure_rate)}
};
@@ -1993,7 +2071,6 @@ build_local_reloptions(local_relopts *relopts, Datum options, bool validate)
elems[i].optname = opt->option->name;
elems[i].opttype = opt->option->type;
elems[i].offset = opt->offset;
- elems[i].isset_offset = 0; /* not supported for local relopts yet */
i++;
}
diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c
index 733ef40ae7..977babff54 100644
--- a/src/backend/commands/vacuum.c
+++ b/src/backend/commands/vacuum.c
@@ -2221,9 +2221,9 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams params,
{
StdRdOptions *opts = (StdRdOptions *) rel->rd_options;
- if (opts && opts->vacuum_truncate_set)
+ if (opts && opts->vacuum_truncate != TERNARY_UNSET)
{
- if (opts->vacuum_truncate)
+ if (opts->vacuum_truncate == TERNARY_TRUE)
params.truncate = VACOPTVALUE_ENABLED;
else
params.truncate = VACOPTVALUE_DISABLED;
diff --git a/src/include/access/reloptions.h b/src/include/access/reloptions.h
index a604a4702c..a436697658 100644
--- a/src/include/access/reloptions.h
+++ b/src/include/access/reloptions.h
@@ -29,6 +29,7 @@
typedef enum relopt_type
{
RELOPT_TYPE_BOOL,
+ RELOPT_TYPE_TERNARY, /* on, off, unset */
RELOPT_TYPE_INT,
RELOPT_TYPE_REAL,
RELOPT_TYPE_ENUM,
@@ -80,6 +81,7 @@ typedef struct relopt_value
union
{
bool bool_val;
+ ternary ternary_val;
int int_val;
double real_val;
int enum_val;
@@ -94,6 +96,12 @@ typedef struct relopt_bool
bool default_val;
} relopt_bool;
+typedef struct relopt_rernary
+{
+ relopt_gen gen;
+ int default_val;
+} relopt_ternary;
+
typedef struct relopt_int
{
relopt_gen gen;
@@ -152,19 +160,6 @@ typedef struct
const char *optname; /* option's name */
relopt_type opttype; /* option's datatype */
int offset; /* offset of field in result struct */
-
- /*
- * isset_offset is an optional offset of a field in the result struct that
- * stores whether the option is explicitly set for the relation or if it
- * just picked up the default value. In most cases, this can be
- * accomplished by giving the reloption a special out-of-range default
- * value (e.g., some integer reloptions use -2), but this isn't always
- * possible. For example, a Boolean reloption cannot be given an
- * out-of-range default, so we need another way to discover the source of
- * its value. This offset is only used if given a value greater than
- * zero.
- */
- int isset_offset;
} relopt_parse_elt;
/* Local reloption definition */
@@ -195,6 +190,8 @@ typedef struct local_relopts
extern relopt_kind add_reloption_kind(void);
extern void add_bool_reloption(bits32 kinds, const char *name, const char *desc,
bool default_val, LOCKMODE lockmode);
+extern void add_ternary_reloption(bits32 kinds, const char *name,
+ const char *desc, int default_val, LOCKMODE lockmode);
extern void add_int_reloption(bits32 kinds, const char *name, const char *desc,
int default_val, int min_val, int max_val,
LOCKMODE lockmode);
@@ -214,6 +211,9 @@ extern void register_reloptions_validator(local_relopts *relopts,
extern void add_local_bool_reloption(local_relopts *relopts, const char *name,
const char *desc, bool default_val,
int offset);
+extern void add_local_ternary_reloption(local_relopts *relopts,
+ const char *name, const char *desc,
+ ternary default_val, int offset);
extern void add_local_int_reloption(local_relopts *relopts, const char *name,
const char *desc, int default_val,
int min_val, int max_val, int offset);
diff --git a/src/include/c.h b/src/include/c.h
index 39022f8a9d..9c59ccccf8 100644
--- a/src/include/c.h
+++ b/src/include/c.h
@@ -488,6 +488,22 @@ typedef void (*pg_funcptr_t) (void);
#include <stdbool.h>
+/*
+ * ternary
+ * Boolean value with an extrea "unset" option
+ *
+ * Ternary data type is used in relation options that can be "true", "false" or
+ * "unset". Since relation options are used deep inside the PostgreSQL code,
+ * this type is declared globally.
+*/
+
+typedef enum ternary
+{
+ TERNARY_FALSE = 0,
+ TERNARY_TRUE = 1,
+ TERNARY_UNSET = -1
+} ternary;
+
/* ----------------------------------------------------------------
* Section 3: standard system types
diff --git a/src/include/utils/rel.h b/src/include/utils/rel.h
index b552359915..7346d618f9 100644
--- a/src/include/utils/rel.h
+++ b/src/include/utils/rel.h
@@ -346,8 +346,7 @@ typedef struct StdRdOptions
bool user_catalog_table; /* use as an additional catalog relation */
int parallel_workers; /* max number of parallel workers */
StdRdOptIndexCleanup vacuum_index_cleanup; /* controls index vacuuming */
- bool vacuum_truncate; /* enables vacuum to truncate a relation */
- bool vacuum_truncate_set; /* whether vacuum_truncate is set */
+ ternary vacuum_truncate; /* enables vacuum to truncate a relation */
/*
* Fraction of pages in a relation that vacuum can eagerly scan and fail
--
2.39.2
--nextPart4155600.3ZeAukHxDK
Content-Disposition: attachment;
filename="v2a-0003-Add-alias-to-be-used-as-unset-state.patch"
Content-Transfer-Encoding: 7Bit
Content-Type: text/x-patch; charset="unicode-2-0-utf-8";
name="v2a-0003-Add-alias-to-be-used-as-unset-state.patch"
^ permalink raw reply [nested|flat] 868+ messages in thread
* [PATCH v2 2/4] Introduce ternary reloptions
@ 2025-09-04 14:41 Nikolay Shaplov <[email protected]>
0 siblings, 0 replies; 868+ messages in thread
From: Nikolay Shaplov @ 2025-09-04 14:41 UTC (permalink / raw)
Introduce ternary reloption as a replacement for current `vacuum_truncate`
implementation. Remove `vacuum_truncate_set` additional flag and using
`TERNARY_UNSET` value instead.
---
src/backend/access/common/reloptions.c | 129 ++++++++++++++++++++-----
src/backend/commands/vacuum.c | 4 +-
src/include/access/reloptions.h | 26 ++---
src/include/c.h | 16 +++
src/include/utils/rel.h | 3 +-
5 files changed, 135 insertions(+), 43 deletions(-)
diff --git a/src/backend/access/common/reloptions.c b/src/backend/access/common/reloptions.c
index 0af3fea68f..f543f11001 100644
--- a/src/backend/access/common/reloptions.c
+++ b/src/backend/access/common/reloptions.c
@@ -40,9 +40,9 @@
*
* To add an option:
*
- * (i) decide on a type (bool, integer, real, enum, string), name, default
- * value, upper and lower bounds (if applicable); for strings, consider a
- * validation routine.
+ * (i) decide on a type (bool, ternary, integer, real, enum, string), name,
+ * default value, upper and lower bounds (if applicable); for strings,
+ * consider a validation routine.
* (ii) add a record below (or use add_<type>_reloption).
* (iii) add it to the appropriate options struct (perhaps StdRdOptions)
* (iv) add it to the appropriate handling routine (perhaps
@@ -147,15 +147,6 @@ static relopt_bool boolRelOpts[] =
},
false
},
- {
- {
- "vacuum_truncate",
- "Enables vacuum to truncate empty pages at the end of this table",
- RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
- ShareUpdateExclusiveLock
- },
- true
- },
{
{
"deduplicate_items",
@@ -170,6 +161,21 @@ static relopt_bool boolRelOpts[] =
{{NULL}}
};
+static relopt_ternary ternaryRelOpts[] =
+{
+ {
+ {
+ "vacuum_truncate",
+ "Enables vacuum to truncate empty pages at the end of this table",
+ RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
+ ShareUpdateExclusiveLock
+ },
+ TERNARY_UNSET
+ },
+ /* list terminator */
+ {{NULL}}
+};
+
static relopt_int intRelOpts[] =
{
{
@@ -600,6 +606,13 @@ initialize_reloptions(void)
boolRelOpts[i].gen.lockmode));
j++;
}
+ for (i = 0; ternaryRelOpts[i].gen.name; i++)
+ {
+ Assert(DoLockModesConflict(ternaryRelOpts[i].gen.lockmode,
+ ternaryRelOpts[i].gen.lockmode));
+ j++;
+ }
+
for (i = 0; intRelOpts[i].gen.name; i++)
{
Assert(DoLockModesConflict(intRelOpts[i].gen.lockmode,
@@ -640,6 +653,14 @@ initialize_reloptions(void)
j++;
}
+ for (i = 0; ternaryRelOpts[i].gen.name; i++)
+ {
+ relOpts[j] = &ternaryRelOpts[i].gen;
+ relOpts[j]->type = RELOPT_TYPE_TERNARY;
+ relOpts[j]->namelen = strlen(relOpts[j]->name);
+ j++;
+ }
+
for (i = 0; intRelOpts[i].gen.name; i++)
{
relOpts[j] = &intRelOpts[i].gen;
@@ -800,6 +821,9 @@ allocate_reloption(bits32 kinds, int type, const char *name, const char *desc,
case RELOPT_TYPE_BOOL:
size = sizeof(relopt_bool);
break;
+ case RELOPT_TYPE_TERNARY:
+ size = sizeof(relopt_ternary);
+ break;
case RELOPT_TYPE_INT:
size = sizeof(relopt_int);
break;
@@ -883,6 +907,54 @@ add_local_bool_reloption(local_relopts *relopts, const char *name,
add_local_reloption(relopts, (relopt_gen *) newoption, offset);
}
+/*
+ * init_ternary_reloption
+ * Allocate and initialize a new ternary reloption
+ */
+static relopt_ternary *
+init_ternary_reloption(bits32 kinds, const char *name, const char *desc,
+ ternary default_val, LOCKMODE lockmode)
+{
+ relopt_ternary *newoption;
+
+ newoption = (relopt_ternary *) allocate_reloption(kinds,
+ RELOPT_TYPE_TERNARY, name, desc, lockmode);
+ newoption->default_val = default_val;
+
+ return newoption;
+}
+
+/*
+ * add_ternary_reloption
+ * Add a new ternary reloption
+ */
+void
+add_ternary_reloption(bits32 kinds, const char *name, const char *desc,
+ ternary default_val, LOCKMODE lockmode)
+{
+ relopt_ternary *newoption = init_ternary_reloption(kinds, name, desc,
+ default_val, lockmode);
+
+ add_reloption((relopt_gen *) newoption);
+}
+
+/*
+ * add_local_ternary_reloption
+ * Add a new ternary local reloption
+ *
+ * 'offset' is offset of ternary-typed field.
+ */
+void
+add_local_ternary_reloption(local_relopts *relopts, const char *name,
+ const char *desc, ternary default_val,
+ int offset)
+{
+ relopt_ternary *newoption = init_ternary_reloption(RELOPT_KIND_LOCAL,
+ name, desc,
+ default_val, 0);
+
+ add_local_reloption(relopts, (relopt_gen *) newoption, offset);
+}
/*
* init_real_reloption
@@ -1617,6 +1689,18 @@ parse_one_reloption(relopt_value *option, char *text_str, int text_len,
option->gen->name, value)));
}
break;
+ case RELOPT_TYPE_TERNARY:
+ {
+ bool b;
+ parsed = parse_bool(value, &b);
+ option->values.ternary_val = b ? TERNARY_TRUE : TERNARY_FALSE;
+ if (validate && !parsed)
+ ereport(ERROR,
+ (errcode(ERRCODE_INVALID_PARAMETER_VALUE),
+ errmsg("invalid value for ternary option \"%s\": %s",
+ option->gen->name, value)));
+ }
+ break;
case RELOPT_TYPE_INT:
{
relopt_int *optint = (relopt_int *) option->gen;
@@ -1780,17 +1864,6 @@ fillRelOptions(void *rdopts, Size basesize,
char *itempos = ((char *) rdopts) + elems[j].offset;
char *string_val;
- /*
- * If isset_offset is provided, store whether the reloption is
- * set there.
- */
- if (elems[j].isset_offset > 0)
- {
- char *setpos = ((char *) rdopts) + elems[j].isset_offset;
-
- *(bool *) setpos = options[i].isset;
- }
-
switch (options[i].gen->type)
{
case RELOPT_TYPE_BOOL:
@@ -1798,6 +1871,11 @@ fillRelOptions(void *rdopts, Size basesize,
options[i].values.bool_val :
((relopt_bool *) options[i].gen)->default_val;
break;
+ case RELOPT_TYPE_TERNARY:
+ *(ternary *) itempos = options[i].isset ?
+ options[i].values.ternary_val :
+ ((relopt_ternary *) options[i].gen)->default_val;
+ break;
case RELOPT_TYPE_INT:
*(int *) itempos = options[i].isset ?
options[i].values.int_val :
@@ -1912,8 +1990,8 @@ default_reloptions(Datum reloptions, bool validate, relopt_kind kind)
offsetof(StdRdOptions, parallel_workers)},
{"vacuum_index_cleanup", RELOPT_TYPE_ENUM,
offsetof(StdRdOptions, vacuum_index_cleanup)},
- {"vacuum_truncate", RELOPT_TYPE_BOOL,
- offsetof(StdRdOptions, vacuum_truncate), offsetof(StdRdOptions, vacuum_truncate_set)},
+ {"vacuum_truncate", RELOPT_TYPE_TERNARY,
+ offsetof(StdRdOptions, vacuum_truncate)},
{"vacuum_max_eager_freeze_failure_rate", RELOPT_TYPE_REAL,
offsetof(StdRdOptions, vacuum_max_eager_freeze_failure_rate)}
};
@@ -1993,7 +2071,6 @@ build_local_reloptions(local_relopts *relopts, Datum options, bool validate)
elems[i].optname = opt->option->name;
elems[i].opttype = opt->option->type;
elems[i].offset = opt->offset;
- elems[i].isset_offset = 0; /* not supported for local relopts yet */
i++;
}
diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c
index 733ef40ae7..977babff54 100644
--- a/src/backend/commands/vacuum.c
+++ b/src/backend/commands/vacuum.c
@@ -2221,9 +2221,9 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams params,
{
StdRdOptions *opts = (StdRdOptions *) rel->rd_options;
- if (opts && opts->vacuum_truncate_set)
+ if (opts && opts->vacuum_truncate != TERNARY_UNSET)
{
- if (opts->vacuum_truncate)
+ if (opts->vacuum_truncate == TERNARY_TRUE)
params.truncate = VACOPTVALUE_ENABLED;
else
params.truncate = VACOPTVALUE_DISABLED;
diff --git a/src/include/access/reloptions.h b/src/include/access/reloptions.h
index a604a4702c..a436697658 100644
--- a/src/include/access/reloptions.h
+++ b/src/include/access/reloptions.h
@@ -29,6 +29,7 @@
typedef enum relopt_type
{
RELOPT_TYPE_BOOL,
+ RELOPT_TYPE_TERNARY, /* on, off, unset */
RELOPT_TYPE_INT,
RELOPT_TYPE_REAL,
RELOPT_TYPE_ENUM,
@@ -80,6 +81,7 @@ typedef struct relopt_value
union
{
bool bool_val;
+ ternary ternary_val;
int int_val;
double real_val;
int enum_val;
@@ -94,6 +96,12 @@ typedef struct relopt_bool
bool default_val;
} relopt_bool;
+typedef struct relopt_rernary
+{
+ relopt_gen gen;
+ int default_val;
+} relopt_ternary;
+
typedef struct relopt_int
{
relopt_gen gen;
@@ -152,19 +160,6 @@ typedef struct
const char *optname; /* option's name */
relopt_type opttype; /* option's datatype */
int offset; /* offset of field in result struct */
-
- /*
- * isset_offset is an optional offset of a field in the result struct that
- * stores whether the option is explicitly set for the relation or if it
- * just picked up the default value. In most cases, this can be
- * accomplished by giving the reloption a special out-of-range default
- * value (e.g., some integer reloptions use -2), but this isn't always
- * possible. For example, a Boolean reloption cannot be given an
- * out-of-range default, so we need another way to discover the source of
- * its value. This offset is only used if given a value greater than
- * zero.
- */
- int isset_offset;
} relopt_parse_elt;
/* Local reloption definition */
@@ -195,6 +190,8 @@ typedef struct local_relopts
extern relopt_kind add_reloption_kind(void);
extern void add_bool_reloption(bits32 kinds, const char *name, const char *desc,
bool default_val, LOCKMODE lockmode);
+extern void add_ternary_reloption(bits32 kinds, const char *name,
+ const char *desc, int default_val, LOCKMODE lockmode);
extern void add_int_reloption(bits32 kinds, const char *name, const char *desc,
int default_val, int min_val, int max_val,
LOCKMODE lockmode);
@@ -214,6 +211,9 @@ extern void register_reloptions_validator(local_relopts *relopts,
extern void add_local_bool_reloption(local_relopts *relopts, const char *name,
const char *desc, bool default_val,
int offset);
+extern void add_local_ternary_reloption(local_relopts *relopts,
+ const char *name, const char *desc,
+ ternary default_val, int offset);
extern void add_local_int_reloption(local_relopts *relopts, const char *name,
const char *desc, int default_val,
int min_val, int max_val, int offset);
diff --git a/src/include/c.h b/src/include/c.h
index 39022f8a9d..9c59ccccf8 100644
--- a/src/include/c.h
+++ b/src/include/c.h
@@ -488,6 +488,22 @@ typedef void (*pg_funcptr_t) (void);
#include <stdbool.h>
+/*
+ * ternary
+ * Boolean value with an extrea "unset" option
+ *
+ * Ternary data type is used in relation options that can be "true", "false" or
+ * "unset". Since relation options are used deep inside the PostgreSQL code,
+ * this type is declared globally.
+*/
+
+typedef enum ternary
+{
+ TERNARY_FALSE = 0,
+ TERNARY_TRUE = 1,
+ TERNARY_UNSET = -1
+} ternary;
+
/* ----------------------------------------------------------------
* Section 3: standard system types
diff --git a/src/include/utils/rel.h b/src/include/utils/rel.h
index b552359915..7346d618f9 100644
--- a/src/include/utils/rel.h
+++ b/src/include/utils/rel.h
@@ -346,8 +346,7 @@ typedef struct StdRdOptions
bool user_catalog_table; /* use as an additional catalog relation */
int parallel_workers; /* max number of parallel workers */
StdRdOptIndexCleanup vacuum_index_cleanup; /* controls index vacuuming */
- bool vacuum_truncate; /* enables vacuum to truncate a relation */
- bool vacuum_truncate_set; /* whether vacuum_truncate is set */
+ ternary vacuum_truncate; /* enables vacuum to truncate a relation */
/*
* Fraction of pages in a relation that vacuum can eagerly scan and fail
--
2.39.2
--nextPart5129932.5fSG56mABF
Content-Disposition: attachment;
filename="v2-0003-Add-alias-to-be-used-as-unset-state.patch"
Content-Transfer-Encoding: 7Bit
Content-Type: text/x-patch; charset="unicode-2-0-utf-8";
name="v2-0003-Add-alias-to-be-used-as-unset-state.patch"
^ permalink raw reply [nested|flat] 868+ messages in thread
* [PATCH v2a 2/4] Introduce ternary reloptions
@ 2025-09-04 14:41 Nikolay Shaplov <[email protected]>
0 siblings, 0 replies; 868+ messages in thread
From: Nikolay Shaplov @ 2025-09-04 14:41 UTC (permalink / raw)
Introduce ternary reloption as a replacement for current `vacuum_truncate`
implementation. Remove `vacuum_truncate_set` additional flag and using
`TERNARY_UNSET` value instead.
---
src/backend/access/common/reloptions.c | 129 ++++++++++++++++++++-----
src/backend/commands/vacuum.c | 4 +-
src/include/access/reloptions.h | 26 ++---
src/include/c.h | 16 +++
src/include/utils/rel.h | 3 +-
5 files changed, 135 insertions(+), 43 deletions(-)
diff --git a/src/backend/access/common/reloptions.c b/src/backend/access/common/reloptions.c
index 0af3fea68f..f543f11001 100644
--- a/src/backend/access/common/reloptions.c
+++ b/src/backend/access/common/reloptions.c
@@ -40,9 +40,9 @@
*
* To add an option:
*
- * (i) decide on a type (bool, integer, real, enum, string), name, default
- * value, upper and lower bounds (if applicable); for strings, consider a
- * validation routine.
+ * (i) decide on a type (bool, ternary, integer, real, enum, string), name,
+ * default value, upper and lower bounds (if applicable); for strings,
+ * consider a validation routine.
* (ii) add a record below (or use add_<type>_reloption).
* (iii) add it to the appropriate options struct (perhaps StdRdOptions)
* (iv) add it to the appropriate handling routine (perhaps
@@ -147,15 +147,6 @@ static relopt_bool boolRelOpts[] =
},
false
},
- {
- {
- "vacuum_truncate",
- "Enables vacuum to truncate empty pages at the end of this table",
- RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
- ShareUpdateExclusiveLock
- },
- true
- },
{
{
"deduplicate_items",
@@ -170,6 +161,21 @@ static relopt_bool boolRelOpts[] =
{{NULL}}
};
+static relopt_ternary ternaryRelOpts[] =
+{
+ {
+ {
+ "vacuum_truncate",
+ "Enables vacuum to truncate empty pages at the end of this table",
+ RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
+ ShareUpdateExclusiveLock
+ },
+ TERNARY_UNSET
+ },
+ /* list terminator */
+ {{NULL}}
+};
+
static relopt_int intRelOpts[] =
{
{
@@ -600,6 +606,13 @@ initialize_reloptions(void)
boolRelOpts[i].gen.lockmode));
j++;
}
+ for (i = 0; ternaryRelOpts[i].gen.name; i++)
+ {
+ Assert(DoLockModesConflict(ternaryRelOpts[i].gen.lockmode,
+ ternaryRelOpts[i].gen.lockmode));
+ j++;
+ }
+
for (i = 0; intRelOpts[i].gen.name; i++)
{
Assert(DoLockModesConflict(intRelOpts[i].gen.lockmode,
@@ -640,6 +653,14 @@ initialize_reloptions(void)
j++;
}
+ for (i = 0; ternaryRelOpts[i].gen.name; i++)
+ {
+ relOpts[j] = &ternaryRelOpts[i].gen;
+ relOpts[j]->type = RELOPT_TYPE_TERNARY;
+ relOpts[j]->namelen = strlen(relOpts[j]->name);
+ j++;
+ }
+
for (i = 0; intRelOpts[i].gen.name; i++)
{
relOpts[j] = &intRelOpts[i].gen;
@@ -800,6 +821,9 @@ allocate_reloption(bits32 kinds, int type, const char *name, const char *desc,
case RELOPT_TYPE_BOOL:
size = sizeof(relopt_bool);
break;
+ case RELOPT_TYPE_TERNARY:
+ size = sizeof(relopt_ternary);
+ break;
case RELOPT_TYPE_INT:
size = sizeof(relopt_int);
break;
@@ -883,6 +907,54 @@ add_local_bool_reloption(local_relopts *relopts, const char *name,
add_local_reloption(relopts, (relopt_gen *) newoption, offset);
}
+/*
+ * init_ternary_reloption
+ * Allocate and initialize a new ternary reloption
+ */
+static relopt_ternary *
+init_ternary_reloption(bits32 kinds, const char *name, const char *desc,
+ ternary default_val, LOCKMODE lockmode)
+{
+ relopt_ternary *newoption;
+
+ newoption = (relopt_ternary *) allocate_reloption(kinds,
+ RELOPT_TYPE_TERNARY, name, desc, lockmode);
+ newoption->default_val = default_val;
+
+ return newoption;
+}
+
+/*
+ * add_ternary_reloption
+ * Add a new ternary reloption
+ */
+void
+add_ternary_reloption(bits32 kinds, const char *name, const char *desc,
+ ternary default_val, LOCKMODE lockmode)
+{
+ relopt_ternary *newoption = init_ternary_reloption(kinds, name, desc,
+ default_val, lockmode);
+
+ add_reloption((relopt_gen *) newoption);
+}
+
+/*
+ * add_local_ternary_reloption
+ * Add a new ternary local reloption
+ *
+ * 'offset' is offset of ternary-typed field.
+ */
+void
+add_local_ternary_reloption(local_relopts *relopts, const char *name,
+ const char *desc, ternary default_val,
+ int offset)
+{
+ relopt_ternary *newoption = init_ternary_reloption(RELOPT_KIND_LOCAL,
+ name, desc,
+ default_val, 0);
+
+ add_local_reloption(relopts, (relopt_gen *) newoption, offset);
+}
/*
* init_real_reloption
@@ -1617,6 +1689,18 @@ parse_one_reloption(relopt_value *option, char *text_str, int text_len,
option->gen->name, value)));
}
break;
+ case RELOPT_TYPE_TERNARY:
+ {
+ bool b;
+ parsed = parse_bool(value, &b);
+ option->values.ternary_val = b ? TERNARY_TRUE : TERNARY_FALSE;
+ if (validate && !parsed)
+ ereport(ERROR,
+ (errcode(ERRCODE_INVALID_PARAMETER_VALUE),
+ errmsg("invalid value for ternary option \"%s\": %s",
+ option->gen->name, value)));
+ }
+ break;
case RELOPT_TYPE_INT:
{
relopt_int *optint = (relopt_int *) option->gen;
@@ -1780,17 +1864,6 @@ fillRelOptions(void *rdopts, Size basesize,
char *itempos = ((char *) rdopts) + elems[j].offset;
char *string_val;
- /*
- * If isset_offset is provided, store whether the reloption is
- * set there.
- */
- if (elems[j].isset_offset > 0)
- {
- char *setpos = ((char *) rdopts) + elems[j].isset_offset;
-
- *(bool *) setpos = options[i].isset;
- }
-
switch (options[i].gen->type)
{
case RELOPT_TYPE_BOOL:
@@ -1798,6 +1871,11 @@ fillRelOptions(void *rdopts, Size basesize,
options[i].values.bool_val :
((relopt_bool *) options[i].gen)->default_val;
break;
+ case RELOPT_TYPE_TERNARY:
+ *(ternary *) itempos = options[i].isset ?
+ options[i].values.ternary_val :
+ ((relopt_ternary *) options[i].gen)->default_val;
+ break;
case RELOPT_TYPE_INT:
*(int *) itempos = options[i].isset ?
options[i].values.int_val :
@@ -1912,8 +1990,8 @@ default_reloptions(Datum reloptions, bool validate, relopt_kind kind)
offsetof(StdRdOptions, parallel_workers)},
{"vacuum_index_cleanup", RELOPT_TYPE_ENUM,
offsetof(StdRdOptions, vacuum_index_cleanup)},
- {"vacuum_truncate", RELOPT_TYPE_BOOL,
- offsetof(StdRdOptions, vacuum_truncate), offsetof(StdRdOptions, vacuum_truncate_set)},
+ {"vacuum_truncate", RELOPT_TYPE_TERNARY,
+ offsetof(StdRdOptions, vacuum_truncate)},
{"vacuum_max_eager_freeze_failure_rate", RELOPT_TYPE_REAL,
offsetof(StdRdOptions, vacuum_max_eager_freeze_failure_rate)}
};
@@ -1993,7 +2071,6 @@ build_local_reloptions(local_relopts *relopts, Datum options, bool validate)
elems[i].optname = opt->option->name;
elems[i].opttype = opt->option->type;
elems[i].offset = opt->offset;
- elems[i].isset_offset = 0; /* not supported for local relopts yet */
i++;
}
diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c
index 733ef40ae7..977babff54 100644
--- a/src/backend/commands/vacuum.c
+++ b/src/backend/commands/vacuum.c
@@ -2221,9 +2221,9 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams params,
{
StdRdOptions *opts = (StdRdOptions *) rel->rd_options;
- if (opts && opts->vacuum_truncate_set)
+ if (opts && opts->vacuum_truncate != TERNARY_UNSET)
{
- if (opts->vacuum_truncate)
+ if (opts->vacuum_truncate == TERNARY_TRUE)
params.truncate = VACOPTVALUE_ENABLED;
else
params.truncate = VACOPTVALUE_DISABLED;
diff --git a/src/include/access/reloptions.h b/src/include/access/reloptions.h
index a604a4702c..a436697658 100644
--- a/src/include/access/reloptions.h
+++ b/src/include/access/reloptions.h
@@ -29,6 +29,7 @@
typedef enum relopt_type
{
RELOPT_TYPE_BOOL,
+ RELOPT_TYPE_TERNARY, /* on, off, unset */
RELOPT_TYPE_INT,
RELOPT_TYPE_REAL,
RELOPT_TYPE_ENUM,
@@ -80,6 +81,7 @@ typedef struct relopt_value
union
{
bool bool_val;
+ ternary ternary_val;
int int_val;
double real_val;
int enum_val;
@@ -94,6 +96,12 @@ typedef struct relopt_bool
bool default_val;
} relopt_bool;
+typedef struct relopt_rernary
+{
+ relopt_gen gen;
+ int default_val;
+} relopt_ternary;
+
typedef struct relopt_int
{
relopt_gen gen;
@@ -152,19 +160,6 @@ typedef struct
const char *optname; /* option's name */
relopt_type opttype; /* option's datatype */
int offset; /* offset of field in result struct */
-
- /*
- * isset_offset is an optional offset of a field in the result struct that
- * stores whether the option is explicitly set for the relation or if it
- * just picked up the default value. In most cases, this can be
- * accomplished by giving the reloption a special out-of-range default
- * value (e.g., some integer reloptions use -2), but this isn't always
- * possible. For example, a Boolean reloption cannot be given an
- * out-of-range default, so we need another way to discover the source of
- * its value. This offset is only used if given a value greater than
- * zero.
- */
- int isset_offset;
} relopt_parse_elt;
/* Local reloption definition */
@@ -195,6 +190,8 @@ typedef struct local_relopts
extern relopt_kind add_reloption_kind(void);
extern void add_bool_reloption(bits32 kinds, const char *name, const char *desc,
bool default_val, LOCKMODE lockmode);
+extern void add_ternary_reloption(bits32 kinds, const char *name,
+ const char *desc, int default_val, LOCKMODE lockmode);
extern void add_int_reloption(bits32 kinds, const char *name, const char *desc,
int default_val, int min_val, int max_val,
LOCKMODE lockmode);
@@ -214,6 +211,9 @@ extern void register_reloptions_validator(local_relopts *relopts,
extern void add_local_bool_reloption(local_relopts *relopts, const char *name,
const char *desc, bool default_val,
int offset);
+extern void add_local_ternary_reloption(local_relopts *relopts,
+ const char *name, const char *desc,
+ ternary default_val, int offset);
extern void add_local_int_reloption(local_relopts *relopts, const char *name,
const char *desc, int default_val,
int min_val, int max_val, int offset);
diff --git a/src/include/c.h b/src/include/c.h
index 39022f8a9d..9c59ccccf8 100644
--- a/src/include/c.h
+++ b/src/include/c.h
@@ -488,6 +488,22 @@ typedef void (*pg_funcptr_t) (void);
#include <stdbool.h>
+/*
+ * ternary
+ * Boolean value with an extrea "unset" option
+ *
+ * Ternary data type is used in relation options that can be "true", "false" or
+ * "unset". Since relation options are used deep inside the PostgreSQL code,
+ * this type is declared globally.
+*/
+
+typedef enum ternary
+{
+ TERNARY_FALSE = 0,
+ TERNARY_TRUE = 1,
+ TERNARY_UNSET = -1
+} ternary;
+
/* ----------------------------------------------------------------
* Section 3: standard system types
diff --git a/src/include/utils/rel.h b/src/include/utils/rel.h
index b552359915..7346d618f9 100644
--- a/src/include/utils/rel.h
+++ b/src/include/utils/rel.h
@@ -346,8 +346,7 @@ typedef struct StdRdOptions
bool user_catalog_table; /* use as an additional catalog relation */
int parallel_workers; /* max number of parallel workers */
StdRdOptIndexCleanup vacuum_index_cleanup; /* controls index vacuuming */
- bool vacuum_truncate; /* enables vacuum to truncate a relation */
- bool vacuum_truncate_set; /* whether vacuum_truncate is set */
+ ternary vacuum_truncate; /* enables vacuum to truncate a relation */
/*
* Fraction of pages in a relation that vacuum can eagerly scan and fail
--
2.39.2
--nextPart4155600.3ZeAukHxDK
Content-Disposition: attachment;
filename="v2a-0003-Add-alias-to-be-used-as-unset-state.patch"
Content-Transfer-Encoding: 7Bit
Content-Type: text/x-patch; charset="unicode-2-0-utf-8";
name="v2a-0003-Add-alias-to-be-used-as-unset-state.patch"
^ permalink raw reply [nested|flat] 868+ messages in thread
* [PATCH v2 2/4] Introduce ternary reloptions
@ 2025-09-04 14:41 Nikolay Shaplov <[email protected]>
0 siblings, 0 replies; 868+ messages in thread
From: Nikolay Shaplov @ 2025-09-04 14:41 UTC (permalink / raw)
Introduce ternary reloption as a replacement for current `vacuum_truncate`
implementation. Remove `vacuum_truncate_set` additional flag and using
`TERNARY_UNSET` value instead.
---
src/backend/access/common/reloptions.c | 129 ++++++++++++++++++++-----
src/backend/commands/vacuum.c | 4 +-
src/include/access/reloptions.h | 26 ++---
src/include/c.h | 16 +++
src/include/utils/rel.h | 3 +-
5 files changed, 135 insertions(+), 43 deletions(-)
diff --git a/src/backend/access/common/reloptions.c b/src/backend/access/common/reloptions.c
index 0af3fea68f..f543f11001 100644
--- a/src/backend/access/common/reloptions.c
+++ b/src/backend/access/common/reloptions.c
@@ -40,9 +40,9 @@
*
* To add an option:
*
- * (i) decide on a type (bool, integer, real, enum, string), name, default
- * value, upper and lower bounds (if applicable); for strings, consider a
- * validation routine.
+ * (i) decide on a type (bool, ternary, integer, real, enum, string), name,
+ * default value, upper and lower bounds (if applicable); for strings,
+ * consider a validation routine.
* (ii) add a record below (or use add_<type>_reloption).
* (iii) add it to the appropriate options struct (perhaps StdRdOptions)
* (iv) add it to the appropriate handling routine (perhaps
@@ -147,15 +147,6 @@ static relopt_bool boolRelOpts[] =
},
false
},
- {
- {
- "vacuum_truncate",
- "Enables vacuum to truncate empty pages at the end of this table",
- RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
- ShareUpdateExclusiveLock
- },
- true
- },
{
{
"deduplicate_items",
@@ -170,6 +161,21 @@ static relopt_bool boolRelOpts[] =
{{NULL}}
};
+static relopt_ternary ternaryRelOpts[] =
+{
+ {
+ {
+ "vacuum_truncate",
+ "Enables vacuum to truncate empty pages at the end of this table",
+ RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
+ ShareUpdateExclusiveLock
+ },
+ TERNARY_UNSET
+ },
+ /* list terminator */
+ {{NULL}}
+};
+
static relopt_int intRelOpts[] =
{
{
@@ -600,6 +606,13 @@ initialize_reloptions(void)
boolRelOpts[i].gen.lockmode));
j++;
}
+ for (i = 0; ternaryRelOpts[i].gen.name; i++)
+ {
+ Assert(DoLockModesConflict(ternaryRelOpts[i].gen.lockmode,
+ ternaryRelOpts[i].gen.lockmode));
+ j++;
+ }
+
for (i = 0; intRelOpts[i].gen.name; i++)
{
Assert(DoLockModesConflict(intRelOpts[i].gen.lockmode,
@@ -640,6 +653,14 @@ initialize_reloptions(void)
j++;
}
+ for (i = 0; ternaryRelOpts[i].gen.name; i++)
+ {
+ relOpts[j] = &ternaryRelOpts[i].gen;
+ relOpts[j]->type = RELOPT_TYPE_TERNARY;
+ relOpts[j]->namelen = strlen(relOpts[j]->name);
+ j++;
+ }
+
for (i = 0; intRelOpts[i].gen.name; i++)
{
relOpts[j] = &intRelOpts[i].gen;
@@ -800,6 +821,9 @@ allocate_reloption(bits32 kinds, int type, const char *name, const char *desc,
case RELOPT_TYPE_BOOL:
size = sizeof(relopt_bool);
break;
+ case RELOPT_TYPE_TERNARY:
+ size = sizeof(relopt_ternary);
+ break;
case RELOPT_TYPE_INT:
size = sizeof(relopt_int);
break;
@@ -883,6 +907,54 @@ add_local_bool_reloption(local_relopts *relopts, const char *name,
add_local_reloption(relopts, (relopt_gen *) newoption, offset);
}
+/*
+ * init_ternary_reloption
+ * Allocate and initialize a new ternary reloption
+ */
+static relopt_ternary *
+init_ternary_reloption(bits32 kinds, const char *name, const char *desc,
+ ternary default_val, LOCKMODE lockmode)
+{
+ relopt_ternary *newoption;
+
+ newoption = (relopt_ternary *) allocate_reloption(kinds,
+ RELOPT_TYPE_TERNARY, name, desc, lockmode);
+ newoption->default_val = default_val;
+
+ return newoption;
+}
+
+/*
+ * add_ternary_reloption
+ * Add a new ternary reloption
+ */
+void
+add_ternary_reloption(bits32 kinds, const char *name, const char *desc,
+ ternary default_val, LOCKMODE lockmode)
+{
+ relopt_ternary *newoption = init_ternary_reloption(kinds, name, desc,
+ default_val, lockmode);
+
+ add_reloption((relopt_gen *) newoption);
+}
+
+/*
+ * add_local_ternary_reloption
+ * Add a new ternary local reloption
+ *
+ * 'offset' is offset of ternary-typed field.
+ */
+void
+add_local_ternary_reloption(local_relopts *relopts, const char *name,
+ const char *desc, ternary default_val,
+ int offset)
+{
+ relopt_ternary *newoption = init_ternary_reloption(RELOPT_KIND_LOCAL,
+ name, desc,
+ default_val, 0);
+
+ add_local_reloption(relopts, (relopt_gen *) newoption, offset);
+}
/*
* init_real_reloption
@@ -1617,6 +1689,18 @@ parse_one_reloption(relopt_value *option, char *text_str, int text_len,
option->gen->name, value)));
}
break;
+ case RELOPT_TYPE_TERNARY:
+ {
+ bool b;
+ parsed = parse_bool(value, &b);
+ option->values.ternary_val = b ? TERNARY_TRUE : TERNARY_FALSE;
+ if (validate && !parsed)
+ ereport(ERROR,
+ (errcode(ERRCODE_INVALID_PARAMETER_VALUE),
+ errmsg("invalid value for ternary option \"%s\": %s",
+ option->gen->name, value)));
+ }
+ break;
case RELOPT_TYPE_INT:
{
relopt_int *optint = (relopt_int *) option->gen;
@@ -1780,17 +1864,6 @@ fillRelOptions(void *rdopts, Size basesize,
char *itempos = ((char *) rdopts) + elems[j].offset;
char *string_val;
- /*
- * If isset_offset is provided, store whether the reloption is
- * set there.
- */
- if (elems[j].isset_offset > 0)
- {
- char *setpos = ((char *) rdopts) + elems[j].isset_offset;
-
- *(bool *) setpos = options[i].isset;
- }
-
switch (options[i].gen->type)
{
case RELOPT_TYPE_BOOL:
@@ -1798,6 +1871,11 @@ fillRelOptions(void *rdopts, Size basesize,
options[i].values.bool_val :
((relopt_bool *) options[i].gen)->default_val;
break;
+ case RELOPT_TYPE_TERNARY:
+ *(ternary *) itempos = options[i].isset ?
+ options[i].values.ternary_val :
+ ((relopt_ternary *) options[i].gen)->default_val;
+ break;
case RELOPT_TYPE_INT:
*(int *) itempos = options[i].isset ?
options[i].values.int_val :
@@ -1912,8 +1990,8 @@ default_reloptions(Datum reloptions, bool validate, relopt_kind kind)
offsetof(StdRdOptions, parallel_workers)},
{"vacuum_index_cleanup", RELOPT_TYPE_ENUM,
offsetof(StdRdOptions, vacuum_index_cleanup)},
- {"vacuum_truncate", RELOPT_TYPE_BOOL,
- offsetof(StdRdOptions, vacuum_truncate), offsetof(StdRdOptions, vacuum_truncate_set)},
+ {"vacuum_truncate", RELOPT_TYPE_TERNARY,
+ offsetof(StdRdOptions, vacuum_truncate)},
{"vacuum_max_eager_freeze_failure_rate", RELOPT_TYPE_REAL,
offsetof(StdRdOptions, vacuum_max_eager_freeze_failure_rate)}
};
@@ -1993,7 +2071,6 @@ build_local_reloptions(local_relopts *relopts, Datum options, bool validate)
elems[i].optname = opt->option->name;
elems[i].opttype = opt->option->type;
elems[i].offset = opt->offset;
- elems[i].isset_offset = 0; /* not supported for local relopts yet */
i++;
}
diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c
index 733ef40ae7..977babff54 100644
--- a/src/backend/commands/vacuum.c
+++ b/src/backend/commands/vacuum.c
@@ -2221,9 +2221,9 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams params,
{
StdRdOptions *opts = (StdRdOptions *) rel->rd_options;
- if (opts && opts->vacuum_truncate_set)
+ if (opts && opts->vacuum_truncate != TERNARY_UNSET)
{
- if (opts->vacuum_truncate)
+ if (opts->vacuum_truncate == TERNARY_TRUE)
params.truncate = VACOPTVALUE_ENABLED;
else
params.truncate = VACOPTVALUE_DISABLED;
diff --git a/src/include/access/reloptions.h b/src/include/access/reloptions.h
index a604a4702c..a436697658 100644
--- a/src/include/access/reloptions.h
+++ b/src/include/access/reloptions.h
@@ -29,6 +29,7 @@
typedef enum relopt_type
{
RELOPT_TYPE_BOOL,
+ RELOPT_TYPE_TERNARY, /* on, off, unset */
RELOPT_TYPE_INT,
RELOPT_TYPE_REAL,
RELOPT_TYPE_ENUM,
@@ -80,6 +81,7 @@ typedef struct relopt_value
union
{
bool bool_val;
+ ternary ternary_val;
int int_val;
double real_val;
int enum_val;
@@ -94,6 +96,12 @@ typedef struct relopt_bool
bool default_val;
} relopt_bool;
+typedef struct relopt_rernary
+{
+ relopt_gen gen;
+ int default_val;
+} relopt_ternary;
+
typedef struct relopt_int
{
relopt_gen gen;
@@ -152,19 +160,6 @@ typedef struct
const char *optname; /* option's name */
relopt_type opttype; /* option's datatype */
int offset; /* offset of field in result struct */
-
- /*
- * isset_offset is an optional offset of a field in the result struct that
- * stores whether the option is explicitly set for the relation or if it
- * just picked up the default value. In most cases, this can be
- * accomplished by giving the reloption a special out-of-range default
- * value (e.g., some integer reloptions use -2), but this isn't always
- * possible. For example, a Boolean reloption cannot be given an
- * out-of-range default, so we need another way to discover the source of
- * its value. This offset is only used if given a value greater than
- * zero.
- */
- int isset_offset;
} relopt_parse_elt;
/* Local reloption definition */
@@ -195,6 +190,8 @@ typedef struct local_relopts
extern relopt_kind add_reloption_kind(void);
extern void add_bool_reloption(bits32 kinds, const char *name, const char *desc,
bool default_val, LOCKMODE lockmode);
+extern void add_ternary_reloption(bits32 kinds, const char *name,
+ const char *desc, int default_val, LOCKMODE lockmode);
extern void add_int_reloption(bits32 kinds, const char *name, const char *desc,
int default_val, int min_val, int max_val,
LOCKMODE lockmode);
@@ -214,6 +211,9 @@ extern void register_reloptions_validator(local_relopts *relopts,
extern void add_local_bool_reloption(local_relopts *relopts, const char *name,
const char *desc, bool default_val,
int offset);
+extern void add_local_ternary_reloption(local_relopts *relopts,
+ const char *name, const char *desc,
+ ternary default_val, int offset);
extern void add_local_int_reloption(local_relopts *relopts, const char *name,
const char *desc, int default_val,
int min_val, int max_val, int offset);
diff --git a/src/include/c.h b/src/include/c.h
index 39022f8a9d..9c59ccccf8 100644
--- a/src/include/c.h
+++ b/src/include/c.h
@@ -488,6 +488,22 @@ typedef void (*pg_funcptr_t) (void);
#include <stdbool.h>
+/*
+ * ternary
+ * Boolean value with an extrea "unset" option
+ *
+ * Ternary data type is used in relation options that can be "true", "false" or
+ * "unset". Since relation options are used deep inside the PostgreSQL code,
+ * this type is declared globally.
+*/
+
+typedef enum ternary
+{
+ TERNARY_FALSE = 0,
+ TERNARY_TRUE = 1,
+ TERNARY_UNSET = -1
+} ternary;
+
/* ----------------------------------------------------------------
* Section 3: standard system types
diff --git a/src/include/utils/rel.h b/src/include/utils/rel.h
index b552359915..7346d618f9 100644
--- a/src/include/utils/rel.h
+++ b/src/include/utils/rel.h
@@ -346,8 +346,7 @@ typedef struct StdRdOptions
bool user_catalog_table; /* use as an additional catalog relation */
int parallel_workers; /* max number of parallel workers */
StdRdOptIndexCleanup vacuum_index_cleanup; /* controls index vacuuming */
- bool vacuum_truncate; /* enables vacuum to truncate a relation */
- bool vacuum_truncate_set; /* whether vacuum_truncate is set */
+ ternary vacuum_truncate; /* enables vacuum to truncate a relation */
/*
* Fraction of pages in a relation that vacuum can eagerly scan and fail
--
2.39.2
--nextPart5129932.5fSG56mABF
Content-Disposition: attachment;
filename="v2-0003-Add-alias-to-be-used-as-unset-state.patch"
Content-Transfer-Encoding: 7Bit
Content-Type: text/x-patch; charset="unicode-2-0-utf-8";
name="v2-0003-Add-alias-to-be-used-as-unset-state.patch"
^ permalink raw reply [nested|flat] 868+ messages in thread
* [PATCH v2a 2/4] Introduce ternary reloptions
@ 2025-09-04 14:41 Nikolay Shaplov <[email protected]>
0 siblings, 0 replies; 868+ messages in thread
From: Nikolay Shaplov @ 2025-09-04 14:41 UTC (permalink / raw)
Introduce ternary reloption as a replacement for current `vacuum_truncate`
implementation. Remove `vacuum_truncate_set` additional flag and using
`TERNARY_UNSET` value instead.
---
src/backend/access/common/reloptions.c | 129 ++++++++++++++++++++-----
src/backend/commands/vacuum.c | 4 +-
src/include/access/reloptions.h | 26 ++---
src/include/c.h | 16 +++
src/include/utils/rel.h | 3 +-
5 files changed, 135 insertions(+), 43 deletions(-)
diff --git a/src/backend/access/common/reloptions.c b/src/backend/access/common/reloptions.c
index 0af3fea68f..f543f11001 100644
--- a/src/backend/access/common/reloptions.c
+++ b/src/backend/access/common/reloptions.c
@@ -40,9 +40,9 @@
*
* To add an option:
*
- * (i) decide on a type (bool, integer, real, enum, string), name, default
- * value, upper and lower bounds (if applicable); for strings, consider a
- * validation routine.
+ * (i) decide on a type (bool, ternary, integer, real, enum, string), name,
+ * default value, upper and lower bounds (if applicable); for strings,
+ * consider a validation routine.
* (ii) add a record below (or use add_<type>_reloption).
* (iii) add it to the appropriate options struct (perhaps StdRdOptions)
* (iv) add it to the appropriate handling routine (perhaps
@@ -147,15 +147,6 @@ static relopt_bool boolRelOpts[] =
},
false
},
- {
- {
- "vacuum_truncate",
- "Enables vacuum to truncate empty pages at the end of this table",
- RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
- ShareUpdateExclusiveLock
- },
- true
- },
{
{
"deduplicate_items",
@@ -170,6 +161,21 @@ static relopt_bool boolRelOpts[] =
{{NULL}}
};
+static relopt_ternary ternaryRelOpts[] =
+{
+ {
+ {
+ "vacuum_truncate",
+ "Enables vacuum to truncate empty pages at the end of this table",
+ RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
+ ShareUpdateExclusiveLock
+ },
+ TERNARY_UNSET
+ },
+ /* list terminator */
+ {{NULL}}
+};
+
static relopt_int intRelOpts[] =
{
{
@@ -600,6 +606,13 @@ initialize_reloptions(void)
boolRelOpts[i].gen.lockmode));
j++;
}
+ for (i = 0; ternaryRelOpts[i].gen.name; i++)
+ {
+ Assert(DoLockModesConflict(ternaryRelOpts[i].gen.lockmode,
+ ternaryRelOpts[i].gen.lockmode));
+ j++;
+ }
+
for (i = 0; intRelOpts[i].gen.name; i++)
{
Assert(DoLockModesConflict(intRelOpts[i].gen.lockmode,
@@ -640,6 +653,14 @@ initialize_reloptions(void)
j++;
}
+ for (i = 0; ternaryRelOpts[i].gen.name; i++)
+ {
+ relOpts[j] = &ternaryRelOpts[i].gen;
+ relOpts[j]->type = RELOPT_TYPE_TERNARY;
+ relOpts[j]->namelen = strlen(relOpts[j]->name);
+ j++;
+ }
+
for (i = 0; intRelOpts[i].gen.name; i++)
{
relOpts[j] = &intRelOpts[i].gen;
@@ -800,6 +821,9 @@ allocate_reloption(bits32 kinds, int type, const char *name, const char *desc,
case RELOPT_TYPE_BOOL:
size = sizeof(relopt_bool);
break;
+ case RELOPT_TYPE_TERNARY:
+ size = sizeof(relopt_ternary);
+ break;
case RELOPT_TYPE_INT:
size = sizeof(relopt_int);
break;
@@ -883,6 +907,54 @@ add_local_bool_reloption(local_relopts *relopts, const char *name,
add_local_reloption(relopts, (relopt_gen *) newoption, offset);
}
+/*
+ * init_ternary_reloption
+ * Allocate and initialize a new ternary reloption
+ */
+static relopt_ternary *
+init_ternary_reloption(bits32 kinds, const char *name, const char *desc,
+ ternary default_val, LOCKMODE lockmode)
+{
+ relopt_ternary *newoption;
+
+ newoption = (relopt_ternary *) allocate_reloption(kinds,
+ RELOPT_TYPE_TERNARY, name, desc, lockmode);
+ newoption->default_val = default_val;
+
+ return newoption;
+}
+
+/*
+ * add_ternary_reloption
+ * Add a new ternary reloption
+ */
+void
+add_ternary_reloption(bits32 kinds, const char *name, const char *desc,
+ ternary default_val, LOCKMODE lockmode)
+{
+ relopt_ternary *newoption = init_ternary_reloption(kinds, name, desc,
+ default_val, lockmode);
+
+ add_reloption((relopt_gen *) newoption);
+}
+
+/*
+ * add_local_ternary_reloption
+ * Add a new ternary local reloption
+ *
+ * 'offset' is offset of ternary-typed field.
+ */
+void
+add_local_ternary_reloption(local_relopts *relopts, const char *name,
+ const char *desc, ternary default_val,
+ int offset)
+{
+ relopt_ternary *newoption = init_ternary_reloption(RELOPT_KIND_LOCAL,
+ name, desc,
+ default_val, 0);
+
+ add_local_reloption(relopts, (relopt_gen *) newoption, offset);
+}
/*
* init_real_reloption
@@ -1617,6 +1689,18 @@ parse_one_reloption(relopt_value *option, char *text_str, int text_len,
option->gen->name, value)));
}
break;
+ case RELOPT_TYPE_TERNARY:
+ {
+ bool b;
+ parsed = parse_bool(value, &b);
+ option->values.ternary_val = b ? TERNARY_TRUE : TERNARY_FALSE;
+ if (validate && !parsed)
+ ereport(ERROR,
+ (errcode(ERRCODE_INVALID_PARAMETER_VALUE),
+ errmsg("invalid value for ternary option \"%s\": %s",
+ option->gen->name, value)));
+ }
+ break;
case RELOPT_TYPE_INT:
{
relopt_int *optint = (relopt_int *) option->gen;
@@ -1780,17 +1864,6 @@ fillRelOptions(void *rdopts, Size basesize,
char *itempos = ((char *) rdopts) + elems[j].offset;
char *string_val;
- /*
- * If isset_offset is provided, store whether the reloption is
- * set there.
- */
- if (elems[j].isset_offset > 0)
- {
- char *setpos = ((char *) rdopts) + elems[j].isset_offset;
-
- *(bool *) setpos = options[i].isset;
- }
-
switch (options[i].gen->type)
{
case RELOPT_TYPE_BOOL:
@@ -1798,6 +1871,11 @@ fillRelOptions(void *rdopts, Size basesize,
options[i].values.bool_val :
((relopt_bool *) options[i].gen)->default_val;
break;
+ case RELOPT_TYPE_TERNARY:
+ *(ternary *) itempos = options[i].isset ?
+ options[i].values.ternary_val :
+ ((relopt_ternary *) options[i].gen)->default_val;
+ break;
case RELOPT_TYPE_INT:
*(int *) itempos = options[i].isset ?
options[i].values.int_val :
@@ -1912,8 +1990,8 @@ default_reloptions(Datum reloptions, bool validate, relopt_kind kind)
offsetof(StdRdOptions, parallel_workers)},
{"vacuum_index_cleanup", RELOPT_TYPE_ENUM,
offsetof(StdRdOptions, vacuum_index_cleanup)},
- {"vacuum_truncate", RELOPT_TYPE_BOOL,
- offsetof(StdRdOptions, vacuum_truncate), offsetof(StdRdOptions, vacuum_truncate_set)},
+ {"vacuum_truncate", RELOPT_TYPE_TERNARY,
+ offsetof(StdRdOptions, vacuum_truncate)},
{"vacuum_max_eager_freeze_failure_rate", RELOPT_TYPE_REAL,
offsetof(StdRdOptions, vacuum_max_eager_freeze_failure_rate)}
};
@@ -1993,7 +2071,6 @@ build_local_reloptions(local_relopts *relopts, Datum options, bool validate)
elems[i].optname = opt->option->name;
elems[i].opttype = opt->option->type;
elems[i].offset = opt->offset;
- elems[i].isset_offset = 0; /* not supported for local relopts yet */
i++;
}
diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c
index 733ef40ae7..977babff54 100644
--- a/src/backend/commands/vacuum.c
+++ b/src/backend/commands/vacuum.c
@@ -2221,9 +2221,9 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams params,
{
StdRdOptions *opts = (StdRdOptions *) rel->rd_options;
- if (opts && opts->vacuum_truncate_set)
+ if (opts && opts->vacuum_truncate != TERNARY_UNSET)
{
- if (opts->vacuum_truncate)
+ if (opts->vacuum_truncate == TERNARY_TRUE)
params.truncate = VACOPTVALUE_ENABLED;
else
params.truncate = VACOPTVALUE_DISABLED;
diff --git a/src/include/access/reloptions.h b/src/include/access/reloptions.h
index a604a4702c..a436697658 100644
--- a/src/include/access/reloptions.h
+++ b/src/include/access/reloptions.h
@@ -29,6 +29,7 @@
typedef enum relopt_type
{
RELOPT_TYPE_BOOL,
+ RELOPT_TYPE_TERNARY, /* on, off, unset */
RELOPT_TYPE_INT,
RELOPT_TYPE_REAL,
RELOPT_TYPE_ENUM,
@@ -80,6 +81,7 @@ typedef struct relopt_value
union
{
bool bool_val;
+ ternary ternary_val;
int int_val;
double real_val;
int enum_val;
@@ -94,6 +96,12 @@ typedef struct relopt_bool
bool default_val;
} relopt_bool;
+typedef struct relopt_rernary
+{
+ relopt_gen gen;
+ int default_val;
+} relopt_ternary;
+
typedef struct relopt_int
{
relopt_gen gen;
@@ -152,19 +160,6 @@ typedef struct
const char *optname; /* option's name */
relopt_type opttype; /* option's datatype */
int offset; /* offset of field in result struct */
-
- /*
- * isset_offset is an optional offset of a field in the result struct that
- * stores whether the option is explicitly set for the relation or if it
- * just picked up the default value. In most cases, this can be
- * accomplished by giving the reloption a special out-of-range default
- * value (e.g., some integer reloptions use -2), but this isn't always
- * possible. For example, a Boolean reloption cannot be given an
- * out-of-range default, so we need another way to discover the source of
- * its value. This offset is only used if given a value greater than
- * zero.
- */
- int isset_offset;
} relopt_parse_elt;
/* Local reloption definition */
@@ -195,6 +190,8 @@ typedef struct local_relopts
extern relopt_kind add_reloption_kind(void);
extern void add_bool_reloption(bits32 kinds, const char *name, const char *desc,
bool default_val, LOCKMODE lockmode);
+extern void add_ternary_reloption(bits32 kinds, const char *name,
+ const char *desc, int default_val, LOCKMODE lockmode);
extern void add_int_reloption(bits32 kinds, const char *name, const char *desc,
int default_val, int min_val, int max_val,
LOCKMODE lockmode);
@@ -214,6 +211,9 @@ extern void register_reloptions_validator(local_relopts *relopts,
extern void add_local_bool_reloption(local_relopts *relopts, const char *name,
const char *desc, bool default_val,
int offset);
+extern void add_local_ternary_reloption(local_relopts *relopts,
+ const char *name, const char *desc,
+ ternary default_val, int offset);
extern void add_local_int_reloption(local_relopts *relopts, const char *name,
const char *desc, int default_val,
int min_val, int max_val, int offset);
diff --git a/src/include/c.h b/src/include/c.h
index 39022f8a9d..9c59ccccf8 100644
--- a/src/include/c.h
+++ b/src/include/c.h
@@ -488,6 +488,22 @@ typedef void (*pg_funcptr_t) (void);
#include <stdbool.h>
+/*
+ * ternary
+ * Boolean value with an extrea "unset" option
+ *
+ * Ternary data type is used in relation options that can be "true", "false" or
+ * "unset". Since relation options are used deep inside the PostgreSQL code,
+ * this type is declared globally.
+*/
+
+typedef enum ternary
+{
+ TERNARY_FALSE = 0,
+ TERNARY_TRUE = 1,
+ TERNARY_UNSET = -1
+} ternary;
+
/* ----------------------------------------------------------------
* Section 3: standard system types
diff --git a/src/include/utils/rel.h b/src/include/utils/rel.h
index b552359915..7346d618f9 100644
--- a/src/include/utils/rel.h
+++ b/src/include/utils/rel.h
@@ -346,8 +346,7 @@ typedef struct StdRdOptions
bool user_catalog_table; /* use as an additional catalog relation */
int parallel_workers; /* max number of parallel workers */
StdRdOptIndexCleanup vacuum_index_cleanup; /* controls index vacuuming */
- bool vacuum_truncate; /* enables vacuum to truncate a relation */
- bool vacuum_truncate_set; /* whether vacuum_truncate is set */
+ ternary vacuum_truncate; /* enables vacuum to truncate a relation */
/*
* Fraction of pages in a relation that vacuum can eagerly scan and fail
--
2.39.2
--nextPart4155600.3ZeAukHxDK
Content-Disposition: attachment;
filename="v2a-0003-Add-alias-to-be-used-as-unset-state.patch"
Content-Transfer-Encoding: 7Bit
Content-Type: text/x-patch; charset="unicode-2-0-utf-8";
name="v2a-0003-Add-alias-to-be-used-as-unset-state.patch"
^ permalink raw reply [nested|flat] 868+ messages in thread
* [PATCH v2a 2/4] Introduce ternary reloptions
@ 2025-09-04 14:41 Nikolay Shaplov <[email protected]>
0 siblings, 0 replies; 868+ messages in thread
From: Nikolay Shaplov @ 2025-09-04 14:41 UTC (permalink / raw)
Introduce ternary reloption as a replacement for current `vacuum_truncate`
implementation. Remove `vacuum_truncate_set` additional flag and using
`TERNARY_UNSET` value instead.
---
src/backend/access/common/reloptions.c | 129 ++++++++++++++++++++-----
src/backend/commands/vacuum.c | 4 +-
src/include/access/reloptions.h | 26 ++---
src/include/c.h | 16 +++
src/include/utils/rel.h | 3 +-
5 files changed, 135 insertions(+), 43 deletions(-)
diff --git a/src/backend/access/common/reloptions.c b/src/backend/access/common/reloptions.c
index 0af3fea68f..f543f11001 100644
--- a/src/backend/access/common/reloptions.c
+++ b/src/backend/access/common/reloptions.c
@@ -40,9 +40,9 @@
*
* To add an option:
*
- * (i) decide on a type (bool, integer, real, enum, string), name, default
- * value, upper and lower bounds (if applicable); for strings, consider a
- * validation routine.
+ * (i) decide on a type (bool, ternary, integer, real, enum, string), name,
+ * default value, upper and lower bounds (if applicable); for strings,
+ * consider a validation routine.
* (ii) add a record below (or use add_<type>_reloption).
* (iii) add it to the appropriate options struct (perhaps StdRdOptions)
* (iv) add it to the appropriate handling routine (perhaps
@@ -147,15 +147,6 @@ static relopt_bool boolRelOpts[] =
},
false
},
- {
- {
- "vacuum_truncate",
- "Enables vacuum to truncate empty pages at the end of this table",
- RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
- ShareUpdateExclusiveLock
- },
- true
- },
{
{
"deduplicate_items",
@@ -170,6 +161,21 @@ static relopt_bool boolRelOpts[] =
{{NULL}}
};
+static relopt_ternary ternaryRelOpts[] =
+{
+ {
+ {
+ "vacuum_truncate",
+ "Enables vacuum to truncate empty pages at the end of this table",
+ RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
+ ShareUpdateExclusiveLock
+ },
+ TERNARY_UNSET
+ },
+ /* list terminator */
+ {{NULL}}
+};
+
static relopt_int intRelOpts[] =
{
{
@@ -600,6 +606,13 @@ initialize_reloptions(void)
boolRelOpts[i].gen.lockmode));
j++;
}
+ for (i = 0; ternaryRelOpts[i].gen.name; i++)
+ {
+ Assert(DoLockModesConflict(ternaryRelOpts[i].gen.lockmode,
+ ternaryRelOpts[i].gen.lockmode));
+ j++;
+ }
+
for (i = 0; intRelOpts[i].gen.name; i++)
{
Assert(DoLockModesConflict(intRelOpts[i].gen.lockmode,
@@ -640,6 +653,14 @@ initialize_reloptions(void)
j++;
}
+ for (i = 0; ternaryRelOpts[i].gen.name; i++)
+ {
+ relOpts[j] = &ternaryRelOpts[i].gen;
+ relOpts[j]->type = RELOPT_TYPE_TERNARY;
+ relOpts[j]->namelen = strlen(relOpts[j]->name);
+ j++;
+ }
+
for (i = 0; intRelOpts[i].gen.name; i++)
{
relOpts[j] = &intRelOpts[i].gen;
@@ -800,6 +821,9 @@ allocate_reloption(bits32 kinds, int type, const char *name, const char *desc,
case RELOPT_TYPE_BOOL:
size = sizeof(relopt_bool);
break;
+ case RELOPT_TYPE_TERNARY:
+ size = sizeof(relopt_ternary);
+ break;
case RELOPT_TYPE_INT:
size = sizeof(relopt_int);
break;
@@ -883,6 +907,54 @@ add_local_bool_reloption(local_relopts *relopts, const char *name,
add_local_reloption(relopts, (relopt_gen *) newoption, offset);
}
+/*
+ * init_ternary_reloption
+ * Allocate and initialize a new ternary reloption
+ */
+static relopt_ternary *
+init_ternary_reloption(bits32 kinds, const char *name, const char *desc,
+ ternary default_val, LOCKMODE lockmode)
+{
+ relopt_ternary *newoption;
+
+ newoption = (relopt_ternary *) allocate_reloption(kinds,
+ RELOPT_TYPE_TERNARY, name, desc, lockmode);
+ newoption->default_val = default_val;
+
+ return newoption;
+}
+
+/*
+ * add_ternary_reloption
+ * Add a new ternary reloption
+ */
+void
+add_ternary_reloption(bits32 kinds, const char *name, const char *desc,
+ ternary default_val, LOCKMODE lockmode)
+{
+ relopt_ternary *newoption = init_ternary_reloption(kinds, name, desc,
+ default_val, lockmode);
+
+ add_reloption((relopt_gen *) newoption);
+}
+
+/*
+ * add_local_ternary_reloption
+ * Add a new ternary local reloption
+ *
+ * 'offset' is offset of ternary-typed field.
+ */
+void
+add_local_ternary_reloption(local_relopts *relopts, const char *name,
+ const char *desc, ternary default_val,
+ int offset)
+{
+ relopt_ternary *newoption = init_ternary_reloption(RELOPT_KIND_LOCAL,
+ name, desc,
+ default_val, 0);
+
+ add_local_reloption(relopts, (relopt_gen *) newoption, offset);
+}
/*
* init_real_reloption
@@ -1617,6 +1689,18 @@ parse_one_reloption(relopt_value *option, char *text_str, int text_len,
option->gen->name, value)));
}
break;
+ case RELOPT_TYPE_TERNARY:
+ {
+ bool b;
+ parsed = parse_bool(value, &b);
+ option->values.ternary_val = b ? TERNARY_TRUE : TERNARY_FALSE;
+ if (validate && !parsed)
+ ereport(ERROR,
+ (errcode(ERRCODE_INVALID_PARAMETER_VALUE),
+ errmsg("invalid value for ternary option \"%s\": %s",
+ option->gen->name, value)));
+ }
+ break;
case RELOPT_TYPE_INT:
{
relopt_int *optint = (relopt_int *) option->gen;
@@ -1780,17 +1864,6 @@ fillRelOptions(void *rdopts, Size basesize,
char *itempos = ((char *) rdopts) + elems[j].offset;
char *string_val;
- /*
- * If isset_offset is provided, store whether the reloption is
- * set there.
- */
- if (elems[j].isset_offset > 0)
- {
- char *setpos = ((char *) rdopts) + elems[j].isset_offset;
-
- *(bool *) setpos = options[i].isset;
- }
-
switch (options[i].gen->type)
{
case RELOPT_TYPE_BOOL:
@@ -1798,6 +1871,11 @@ fillRelOptions(void *rdopts, Size basesize,
options[i].values.bool_val :
((relopt_bool *) options[i].gen)->default_val;
break;
+ case RELOPT_TYPE_TERNARY:
+ *(ternary *) itempos = options[i].isset ?
+ options[i].values.ternary_val :
+ ((relopt_ternary *) options[i].gen)->default_val;
+ break;
case RELOPT_TYPE_INT:
*(int *) itempos = options[i].isset ?
options[i].values.int_val :
@@ -1912,8 +1990,8 @@ default_reloptions(Datum reloptions, bool validate, relopt_kind kind)
offsetof(StdRdOptions, parallel_workers)},
{"vacuum_index_cleanup", RELOPT_TYPE_ENUM,
offsetof(StdRdOptions, vacuum_index_cleanup)},
- {"vacuum_truncate", RELOPT_TYPE_BOOL,
- offsetof(StdRdOptions, vacuum_truncate), offsetof(StdRdOptions, vacuum_truncate_set)},
+ {"vacuum_truncate", RELOPT_TYPE_TERNARY,
+ offsetof(StdRdOptions, vacuum_truncate)},
{"vacuum_max_eager_freeze_failure_rate", RELOPT_TYPE_REAL,
offsetof(StdRdOptions, vacuum_max_eager_freeze_failure_rate)}
};
@@ -1993,7 +2071,6 @@ build_local_reloptions(local_relopts *relopts, Datum options, bool validate)
elems[i].optname = opt->option->name;
elems[i].opttype = opt->option->type;
elems[i].offset = opt->offset;
- elems[i].isset_offset = 0; /* not supported for local relopts yet */
i++;
}
diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c
index 733ef40ae7..977babff54 100644
--- a/src/backend/commands/vacuum.c
+++ b/src/backend/commands/vacuum.c
@@ -2221,9 +2221,9 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams params,
{
StdRdOptions *opts = (StdRdOptions *) rel->rd_options;
- if (opts && opts->vacuum_truncate_set)
+ if (opts && opts->vacuum_truncate != TERNARY_UNSET)
{
- if (opts->vacuum_truncate)
+ if (opts->vacuum_truncate == TERNARY_TRUE)
params.truncate = VACOPTVALUE_ENABLED;
else
params.truncate = VACOPTVALUE_DISABLED;
diff --git a/src/include/access/reloptions.h b/src/include/access/reloptions.h
index a604a4702c..a436697658 100644
--- a/src/include/access/reloptions.h
+++ b/src/include/access/reloptions.h
@@ -29,6 +29,7 @@
typedef enum relopt_type
{
RELOPT_TYPE_BOOL,
+ RELOPT_TYPE_TERNARY, /* on, off, unset */
RELOPT_TYPE_INT,
RELOPT_TYPE_REAL,
RELOPT_TYPE_ENUM,
@@ -80,6 +81,7 @@ typedef struct relopt_value
union
{
bool bool_val;
+ ternary ternary_val;
int int_val;
double real_val;
int enum_val;
@@ -94,6 +96,12 @@ typedef struct relopt_bool
bool default_val;
} relopt_bool;
+typedef struct relopt_rernary
+{
+ relopt_gen gen;
+ int default_val;
+} relopt_ternary;
+
typedef struct relopt_int
{
relopt_gen gen;
@@ -152,19 +160,6 @@ typedef struct
const char *optname; /* option's name */
relopt_type opttype; /* option's datatype */
int offset; /* offset of field in result struct */
-
- /*
- * isset_offset is an optional offset of a field in the result struct that
- * stores whether the option is explicitly set for the relation or if it
- * just picked up the default value. In most cases, this can be
- * accomplished by giving the reloption a special out-of-range default
- * value (e.g., some integer reloptions use -2), but this isn't always
- * possible. For example, a Boolean reloption cannot be given an
- * out-of-range default, so we need another way to discover the source of
- * its value. This offset is only used if given a value greater than
- * zero.
- */
- int isset_offset;
} relopt_parse_elt;
/* Local reloption definition */
@@ -195,6 +190,8 @@ typedef struct local_relopts
extern relopt_kind add_reloption_kind(void);
extern void add_bool_reloption(bits32 kinds, const char *name, const char *desc,
bool default_val, LOCKMODE lockmode);
+extern void add_ternary_reloption(bits32 kinds, const char *name,
+ const char *desc, int default_val, LOCKMODE lockmode);
extern void add_int_reloption(bits32 kinds, const char *name, const char *desc,
int default_val, int min_val, int max_val,
LOCKMODE lockmode);
@@ -214,6 +211,9 @@ extern void register_reloptions_validator(local_relopts *relopts,
extern void add_local_bool_reloption(local_relopts *relopts, const char *name,
const char *desc, bool default_val,
int offset);
+extern void add_local_ternary_reloption(local_relopts *relopts,
+ const char *name, const char *desc,
+ ternary default_val, int offset);
extern void add_local_int_reloption(local_relopts *relopts, const char *name,
const char *desc, int default_val,
int min_val, int max_val, int offset);
diff --git a/src/include/c.h b/src/include/c.h
index 39022f8a9d..9c59ccccf8 100644
--- a/src/include/c.h
+++ b/src/include/c.h
@@ -488,6 +488,22 @@ typedef void (*pg_funcptr_t) (void);
#include <stdbool.h>
+/*
+ * ternary
+ * Boolean value with an extrea "unset" option
+ *
+ * Ternary data type is used in relation options that can be "true", "false" or
+ * "unset". Since relation options are used deep inside the PostgreSQL code,
+ * this type is declared globally.
+*/
+
+typedef enum ternary
+{
+ TERNARY_FALSE = 0,
+ TERNARY_TRUE = 1,
+ TERNARY_UNSET = -1
+} ternary;
+
/* ----------------------------------------------------------------
* Section 3: standard system types
diff --git a/src/include/utils/rel.h b/src/include/utils/rel.h
index b552359915..7346d618f9 100644
--- a/src/include/utils/rel.h
+++ b/src/include/utils/rel.h
@@ -346,8 +346,7 @@ typedef struct StdRdOptions
bool user_catalog_table; /* use as an additional catalog relation */
int parallel_workers; /* max number of parallel workers */
StdRdOptIndexCleanup vacuum_index_cleanup; /* controls index vacuuming */
- bool vacuum_truncate; /* enables vacuum to truncate a relation */
- bool vacuum_truncate_set; /* whether vacuum_truncate is set */
+ ternary vacuum_truncate; /* enables vacuum to truncate a relation */
/*
* Fraction of pages in a relation that vacuum can eagerly scan and fail
--
2.39.2
--nextPart4155600.3ZeAukHxDK
Content-Disposition: attachment;
filename="v2a-0003-Add-alias-to-be-used-as-unset-state.patch"
Content-Transfer-Encoding: 7Bit
Content-Type: text/x-patch; charset="unicode-2-0-utf-8";
name="v2a-0003-Add-alias-to-be-used-as-unset-state.patch"
^ permalink raw reply [nested|flat] 868+ messages in thread
* [PATCH v2 2/4] Introduce ternary reloptions
@ 2025-09-04 14:41 Nikolay Shaplov <[email protected]>
0 siblings, 0 replies; 868+ messages in thread
From: Nikolay Shaplov @ 2025-09-04 14:41 UTC (permalink / raw)
Introduce ternary reloption as a replacement for current `vacuum_truncate`
implementation. Remove `vacuum_truncate_set` additional flag and using
`TERNARY_UNSET` value instead.
---
src/backend/access/common/reloptions.c | 129 ++++++++++++++++++++-----
src/backend/commands/vacuum.c | 4 +-
src/include/access/reloptions.h | 26 ++---
src/include/c.h | 16 +++
src/include/utils/rel.h | 3 +-
5 files changed, 135 insertions(+), 43 deletions(-)
diff --git a/src/backend/access/common/reloptions.c b/src/backend/access/common/reloptions.c
index 0af3fea68f..f543f11001 100644
--- a/src/backend/access/common/reloptions.c
+++ b/src/backend/access/common/reloptions.c
@@ -40,9 +40,9 @@
*
* To add an option:
*
- * (i) decide on a type (bool, integer, real, enum, string), name, default
- * value, upper and lower bounds (if applicable); for strings, consider a
- * validation routine.
+ * (i) decide on a type (bool, ternary, integer, real, enum, string), name,
+ * default value, upper and lower bounds (if applicable); for strings,
+ * consider a validation routine.
* (ii) add a record below (or use add_<type>_reloption).
* (iii) add it to the appropriate options struct (perhaps StdRdOptions)
* (iv) add it to the appropriate handling routine (perhaps
@@ -147,15 +147,6 @@ static relopt_bool boolRelOpts[] =
},
false
},
- {
- {
- "vacuum_truncate",
- "Enables vacuum to truncate empty pages at the end of this table",
- RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
- ShareUpdateExclusiveLock
- },
- true
- },
{
{
"deduplicate_items",
@@ -170,6 +161,21 @@ static relopt_bool boolRelOpts[] =
{{NULL}}
};
+static relopt_ternary ternaryRelOpts[] =
+{
+ {
+ {
+ "vacuum_truncate",
+ "Enables vacuum to truncate empty pages at the end of this table",
+ RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
+ ShareUpdateExclusiveLock
+ },
+ TERNARY_UNSET
+ },
+ /* list terminator */
+ {{NULL}}
+};
+
static relopt_int intRelOpts[] =
{
{
@@ -600,6 +606,13 @@ initialize_reloptions(void)
boolRelOpts[i].gen.lockmode));
j++;
}
+ for (i = 0; ternaryRelOpts[i].gen.name; i++)
+ {
+ Assert(DoLockModesConflict(ternaryRelOpts[i].gen.lockmode,
+ ternaryRelOpts[i].gen.lockmode));
+ j++;
+ }
+
for (i = 0; intRelOpts[i].gen.name; i++)
{
Assert(DoLockModesConflict(intRelOpts[i].gen.lockmode,
@@ -640,6 +653,14 @@ initialize_reloptions(void)
j++;
}
+ for (i = 0; ternaryRelOpts[i].gen.name; i++)
+ {
+ relOpts[j] = &ternaryRelOpts[i].gen;
+ relOpts[j]->type = RELOPT_TYPE_TERNARY;
+ relOpts[j]->namelen = strlen(relOpts[j]->name);
+ j++;
+ }
+
for (i = 0; intRelOpts[i].gen.name; i++)
{
relOpts[j] = &intRelOpts[i].gen;
@@ -800,6 +821,9 @@ allocate_reloption(bits32 kinds, int type, const char *name, const char *desc,
case RELOPT_TYPE_BOOL:
size = sizeof(relopt_bool);
break;
+ case RELOPT_TYPE_TERNARY:
+ size = sizeof(relopt_ternary);
+ break;
case RELOPT_TYPE_INT:
size = sizeof(relopt_int);
break;
@@ -883,6 +907,54 @@ add_local_bool_reloption(local_relopts *relopts, const char *name,
add_local_reloption(relopts, (relopt_gen *) newoption, offset);
}
+/*
+ * init_ternary_reloption
+ * Allocate and initialize a new ternary reloption
+ */
+static relopt_ternary *
+init_ternary_reloption(bits32 kinds, const char *name, const char *desc,
+ ternary default_val, LOCKMODE lockmode)
+{
+ relopt_ternary *newoption;
+
+ newoption = (relopt_ternary *) allocate_reloption(kinds,
+ RELOPT_TYPE_TERNARY, name, desc, lockmode);
+ newoption->default_val = default_val;
+
+ return newoption;
+}
+
+/*
+ * add_ternary_reloption
+ * Add a new ternary reloption
+ */
+void
+add_ternary_reloption(bits32 kinds, const char *name, const char *desc,
+ ternary default_val, LOCKMODE lockmode)
+{
+ relopt_ternary *newoption = init_ternary_reloption(kinds, name, desc,
+ default_val, lockmode);
+
+ add_reloption((relopt_gen *) newoption);
+}
+
+/*
+ * add_local_ternary_reloption
+ * Add a new ternary local reloption
+ *
+ * 'offset' is offset of ternary-typed field.
+ */
+void
+add_local_ternary_reloption(local_relopts *relopts, const char *name,
+ const char *desc, ternary default_val,
+ int offset)
+{
+ relopt_ternary *newoption = init_ternary_reloption(RELOPT_KIND_LOCAL,
+ name, desc,
+ default_val, 0);
+
+ add_local_reloption(relopts, (relopt_gen *) newoption, offset);
+}
/*
* init_real_reloption
@@ -1617,6 +1689,18 @@ parse_one_reloption(relopt_value *option, char *text_str, int text_len,
option->gen->name, value)));
}
break;
+ case RELOPT_TYPE_TERNARY:
+ {
+ bool b;
+ parsed = parse_bool(value, &b);
+ option->values.ternary_val = b ? TERNARY_TRUE : TERNARY_FALSE;
+ if (validate && !parsed)
+ ereport(ERROR,
+ (errcode(ERRCODE_INVALID_PARAMETER_VALUE),
+ errmsg("invalid value for ternary option \"%s\": %s",
+ option->gen->name, value)));
+ }
+ break;
case RELOPT_TYPE_INT:
{
relopt_int *optint = (relopt_int *) option->gen;
@@ -1780,17 +1864,6 @@ fillRelOptions(void *rdopts, Size basesize,
char *itempos = ((char *) rdopts) + elems[j].offset;
char *string_val;
- /*
- * If isset_offset is provided, store whether the reloption is
- * set there.
- */
- if (elems[j].isset_offset > 0)
- {
- char *setpos = ((char *) rdopts) + elems[j].isset_offset;
-
- *(bool *) setpos = options[i].isset;
- }
-
switch (options[i].gen->type)
{
case RELOPT_TYPE_BOOL:
@@ -1798,6 +1871,11 @@ fillRelOptions(void *rdopts, Size basesize,
options[i].values.bool_val :
((relopt_bool *) options[i].gen)->default_val;
break;
+ case RELOPT_TYPE_TERNARY:
+ *(ternary *) itempos = options[i].isset ?
+ options[i].values.ternary_val :
+ ((relopt_ternary *) options[i].gen)->default_val;
+ break;
case RELOPT_TYPE_INT:
*(int *) itempos = options[i].isset ?
options[i].values.int_val :
@@ -1912,8 +1990,8 @@ default_reloptions(Datum reloptions, bool validate, relopt_kind kind)
offsetof(StdRdOptions, parallel_workers)},
{"vacuum_index_cleanup", RELOPT_TYPE_ENUM,
offsetof(StdRdOptions, vacuum_index_cleanup)},
- {"vacuum_truncate", RELOPT_TYPE_BOOL,
- offsetof(StdRdOptions, vacuum_truncate), offsetof(StdRdOptions, vacuum_truncate_set)},
+ {"vacuum_truncate", RELOPT_TYPE_TERNARY,
+ offsetof(StdRdOptions, vacuum_truncate)},
{"vacuum_max_eager_freeze_failure_rate", RELOPT_TYPE_REAL,
offsetof(StdRdOptions, vacuum_max_eager_freeze_failure_rate)}
};
@@ -1993,7 +2071,6 @@ build_local_reloptions(local_relopts *relopts, Datum options, bool validate)
elems[i].optname = opt->option->name;
elems[i].opttype = opt->option->type;
elems[i].offset = opt->offset;
- elems[i].isset_offset = 0; /* not supported for local relopts yet */
i++;
}
diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c
index 733ef40ae7..977babff54 100644
--- a/src/backend/commands/vacuum.c
+++ b/src/backend/commands/vacuum.c
@@ -2221,9 +2221,9 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams params,
{
StdRdOptions *opts = (StdRdOptions *) rel->rd_options;
- if (opts && opts->vacuum_truncate_set)
+ if (opts && opts->vacuum_truncate != TERNARY_UNSET)
{
- if (opts->vacuum_truncate)
+ if (opts->vacuum_truncate == TERNARY_TRUE)
params.truncate = VACOPTVALUE_ENABLED;
else
params.truncate = VACOPTVALUE_DISABLED;
diff --git a/src/include/access/reloptions.h b/src/include/access/reloptions.h
index a604a4702c..a436697658 100644
--- a/src/include/access/reloptions.h
+++ b/src/include/access/reloptions.h
@@ -29,6 +29,7 @@
typedef enum relopt_type
{
RELOPT_TYPE_BOOL,
+ RELOPT_TYPE_TERNARY, /* on, off, unset */
RELOPT_TYPE_INT,
RELOPT_TYPE_REAL,
RELOPT_TYPE_ENUM,
@@ -80,6 +81,7 @@ typedef struct relopt_value
union
{
bool bool_val;
+ ternary ternary_val;
int int_val;
double real_val;
int enum_val;
@@ -94,6 +96,12 @@ typedef struct relopt_bool
bool default_val;
} relopt_bool;
+typedef struct relopt_rernary
+{
+ relopt_gen gen;
+ int default_val;
+} relopt_ternary;
+
typedef struct relopt_int
{
relopt_gen gen;
@@ -152,19 +160,6 @@ typedef struct
const char *optname; /* option's name */
relopt_type opttype; /* option's datatype */
int offset; /* offset of field in result struct */
-
- /*
- * isset_offset is an optional offset of a field in the result struct that
- * stores whether the option is explicitly set for the relation or if it
- * just picked up the default value. In most cases, this can be
- * accomplished by giving the reloption a special out-of-range default
- * value (e.g., some integer reloptions use -2), but this isn't always
- * possible. For example, a Boolean reloption cannot be given an
- * out-of-range default, so we need another way to discover the source of
- * its value. This offset is only used if given a value greater than
- * zero.
- */
- int isset_offset;
} relopt_parse_elt;
/* Local reloption definition */
@@ -195,6 +190,8 @@ typedef struct local_relopts
extern relopt_kind add_reloption_kind(void);
extern void add_bool_reloption(bits32 kinds, const char *name, const char *desc,
bool default_val, LOCKMODE lockmode);
+extern void add_ternary_reloption(bits32 kinds, const char *name,
+ const char *desc, int default_val, LOCKMODE lockmode);
extern void add_int_reloption(bits32 kinds, const char *name, const char *desc,
int default_val, int min_val, int max_val,
LOCKMODE lockmode);
@@ -214,6 +211,9 @@ extern void register_reloptions_validator(local_relopts *relopts,
extern void add_local_bool_reloption(local_relopts *relopts, const char *name,
const char *desc, bool default_val,
int offset);
+extern void add_local_ternary_reloption(local_relopts *relopts,
+ const char *name, const char *desc,
+ ternary default_val, int offset);
extern void add_local_int_reloption(local_relopts *relopts, const char *name,
const char *desc, int default_val,
int min_val, int max_val, int offset);
diff --git a/src/include/c.h b/src/include/c.h
index 39022f8a9d..9c59ccccf8 100644
--- a/src/include/c.h
+++ b/src/include/c.h
@@ -488,6 +488,22 @@ typedef void (*pg_funcptr_t) (void);
#include <stdbool.h>
+/*
+ * ternary
+ * Boolean value with an extrea "unset" option
+ *
+ * Ternary data type is used in relation options that can be "true", "false" or
+ * "unset". Since relation options are used deep inside the PostgreSQL code,
+ * this type is declared globally.
+*/
+
+typedef enum ternary
+{
+ TERNARY_FALSE = 0,
+ TERNARY_TRUE = 1,
+ TERNARY_UNSET = -1
+} ternary;
+
/* ----------------------------------------------------------------
* Section 3: standard system types
diff --git a/src/include/utils/rel.h b/src/include/utils/rel.h
index b552359915..7346d618f9 100644
--- a/src/include/utils/rel.h
+++ b/src/include/utils/rel.h
@@ -346,8 +346,7 @@ typedef struct StdRdOptions
bool user_catalog_table; /* use as an additional catalog relation */
int parallel_workers; /* max number of parallel workers */
StdRdOptIndexCleanup vacuum_index_cleanup; /* controls index vacuuming */
- bool vacuum_truncate; /* enables vacuum to truncate a relation */
- bool vacuum_truncate_set; /* whether vacuum_truncate is set */
+ ternary vacuum_truncate; /* enables vacuum to truncate a relation */
/*
* Fraction of pages in a relation that vacuum can eagerly scan and fail
--
2.39.2
--nextPart5129932.5fSG56mABF
Content-Disposition: attachment;
filename="v2-0003-Add-alias-to-be-used-as-unset-state.patch"
Content-Transfer-Encoding: 7Bit
Content-Type: text/x-patch; charset="unicode-2-0-utf-8";
name="v2-0003-Add-alias-to-be-used-as-unset-state.patch"
^ permalink raw reply [nested|flat] 868+ messages in thread
* [PATCH v2 2/4] Introduce ternary reloptions
@ 2025-09-04 14:41 Nikolay Shaplov <[email protected]>
0 siblings, 0 replies; 868+ messages in thread
From: Nikolay Shaplov @ 2025-09-04 14:41 UTC (permalink / raw)
Introduce ternary reloption as a replacement for current `vacuum_truncate`
implementation. Remove `vacuum_truncate_set` additional flag and using
`TERNARY_UNSET` value instead.
---
src/backend/access/common/reloptions.c | 129 ++++++++++++++++++++-----
src/backend/commands/vacuum.c | 4 +-
src/include/access/reloptions.h | 26 ++---
src/include/c.h | 16 +++
src/include/utils/rel.h | 3 +-
5 files changed, 135 insertions(+), 43 deletions(-)
diff --git a/src/backend/access/common/reloptions.c b/src/backend/access/common/reloptions.c
index 0af3fea68f..f543f11001 100644
--- a/src/backend/access/common/reloptions.c
+++ b/src/backend/access/common/reloptions.c
@@ -40,9 +40,9 @@
*
* To add an option:
*
- * (i) decide on a type (bool, integer, real, enum, string), name, default
- * value, upper and lower bounds (if applicable); for strings, consider a
- * validation routine.
+ * (i) decide on a type (bool, ternary, integer, real, enum, string), name,
+ * default value, upper and lower bounds (if applicable); for strings,
+ * consider a validation routine.
* (ii) add a record below (or use add_<type>_reloption).
* (iii) add it to the appropriate options struct (perhaps StdRdOptions)
* (iv) add it to the appropriate handling routine (perhaps
@@ -147,15 +147,6 @@ static relopt_bool boolRelOpts[] =
},
false
},
- {
- {
- "vacuum_truncate",
- "Enables vacuum to truncate empty pages at the end of this table",
- RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
- ShareUpdateExclusiveLock
- },
- true
- },
{
{
"deduplicate_items",
@@ -170,6 +161,21 @@ static relopt_bool boolRelOpts[] =
{{NULL}}
};
+static relopt_ternary ternaryRelOpts[] =
+{
+ {
+ {
+ "vacuum_truncate",
+ "Enables vacuum to truncate empty pages at the end of this table",
+ RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
+ ShareUpdateExclusiveLock
+ },
+ TERNARY_UNSET
+ },
+ /* list terminator */
+ {{NULL}}
+};
+
static relopt_int intRelOpts[] =
{
{
@@ -600,6 +606,13 @@ initialize_reloptions(void)
boolRelOpts[i].gen.lockmode));
j++;
}
+ for (i = 0; ternaryRelOpts[i].gen.name; i++)
+ {
+ Assert(DoLockModesConflict(ternaryRelOpts[i].gen.lockmode,
+ ternaryRelOpts[i].gen.lockmode));
+ j++;
+ }
+
for (i = 0; intRelOpts[i].gen.name; i++)
{
Assert(DoLockModesConflict(intRelOpts[i].gen.lockmode,
@@ -640,6 +653,14 @@ initialize_reloptions(void)
j++;
}
+ for (i = 0; ternaryRelOpts[i].gen.name; i++)
+ {
+ relOpts[j] = &ternaryRelOpts[i].gen;
+ relOpts[j]->type = RELOPT_TYPE_TERNARY;
+ relOpts[j]->namelen = strlen(relOpts[j]->name);
+ j++;
+ }
+
for (i = 0; intRelOpts[i].gen.name; i++)
{
relOpts[j] = &intRelOpts[i].gen;
@@ -800,6 +821,9 @@ allocate_reloption(bits32 kinds, int type, const char *name, const char *desc,
case RELOPT_TYPE_BOOL:
size = sizeof(relopt_bool);
break;
+ case RELOPT_TYPE_TERNARY:
+ size = sizeof(relopt_ternary);
+ break;
case RELOPT_TYPE_INT:
size = sizeof(relopt_int);
break;
@@ -883,6 +907,54 @@ add_local_bool_reloption(local_relopts *relopts, const char *name,
add_local_reloption(relopts, (relopt_gen *) newoption, offset);
}
+/*
+ * init_ternary_reloption
+ * Allocate and initialize a new ternary reloption
+ */
+static relopt_ternary *
+init_ternary_reloption(bits32 kinds, const char *name, const char *desc,
+ ternary default_val, LOCKMODE lockmode)
+{
+ relopt_ternary *newoption;
+
+ newoption = (relopt_ternary *) allocate_reloption(kinds,
+ RELOPT_TYPE_TERNARY, name, desc, lockmode);
+ newoption->default_val = default_val;
+
+ return newoption;
+}
+
+/*
+ * add_ternary_reloption
+ * Add a new ternary reloption
+ */
+void
+add_ternary_reloption(bits32 kinds, const char *name, const char *desc,
+ ternary default_val, LOCKMODE lockmode)
+{
+ relopt_ternary *newoption = init_ternary_reloption(kinds, name, desc,
+ default_val, lockmode);
+
+ add_reloption((relopt_gen *) newoption);
+}
+
+/*
+ * add_local_ternary_reloption
+ * Add a new ternary local reloption
+ *
+ * 'offset' is offset of ternary-typed field.
+ */
+void
+add_local_ternary_reloption(local_relopts *relopts, const char *name,
+ const char *desc, ternary default_val,
+ int offset)
+{
+ relopt_ternary *newoption = init_ternary_reloption(RELOPT_KIND_LOCAL,
+ name, desc,
+ default_val, 0);
+
+ add_local_reloption(relopts, (relopt_gen *) newoption, offset);
+}
/*
* init_real_reloption
@@ -1617,6 +1689,18 @@ parse_one_reloption(relopt_value *option, char *text_str, int text_len,
option->gen->name, value)));
}
break;
+ case RELOPT_TYPE_TERNARY:
+ {
+ bool b;
+ parsed = parse_bool(value, &b);
+ option->values.ternary_val = b ? TERNARY_TRUE : TERNARY_FALSE;
+ if (validate && !parsed)
+ ereport(ERROR,
+ (errcode(ERRCODE_INVALID_PARAMETER_VALUE),
+ errmsg("invalid value for ternary option \"%s\": %s",
+ option->gen->name, value)));
+ }
+ break;
case RELOPT_TYPE_INT:
{
relopt_int *optint = (relopt_int *) option->gen;
@@ -1780,17 +1864,6 @@ fillRelOptions(void *rdopts, Size basesize,
char *itempos = ((char *) rdopts) + elems[j].offset;
char *string_val;
- /*
- * If isset_offset is provided, store whether the reloption is
- * set there.
- */
- if (elems[j].isset_offset > 0)
- {
- char *setpos = ((char *) rdopts) + elems[j].isset_offset;
-
- *(bool *) setpos = options[i].isset;
- }
-
switch (options[i].gen->type)
{
case RELOPT_TYPE_BOOL:
@@ -1798,6 +1871,11 @@ fillRelOptions(void *rdopts, Size basesize,
options[i].values.bool_val :
((relopt_bool *) options[i].gen)->default_val;
break;
+ case RELOPT_TYPE_TERNARY:
+ *(ternary *) itempos = options[i].isset ?
+ options[i].values.ternary_val :
+ ((relopt_ternary *) options[i].gen)->default_val;
+ break;
case RELOPT_TYPE_INT:
*(int *) itempos = options[i].isset ?
options[i].values.int_val :
@@ -1912,8 +1990,8 @@ default_reloptions(Datum reloptions, bool validate, relopt_kind kind)
offsetof(StdRdOptions, parallel_workers)},
{"vacuum_index_cleanup", RELOPT_TYPE_ENUM,
offsetof(StdRdOptions, vacuum_index_cleanup)},
- {"vacuum_truncate", RELOPT_TYPE_BOOL,
- offsetof(StdRdOptions, vacuum_truncate), offsetof(StdRdOptions, vacuum_truncate_set)},
+ {"vacuum_truncate", RELOPT_TYPE_TERNARY,
+ offsetof(StdRdOptions, vacuum_truncate)},
{"vacuum_max_eager_freeze_failure_rate", RELOPT_TYPE_REAL,
offsetof(StdRdOptions, vacuum_max_eager_freeze_failure_rate)}
};
@@ -1993,7 +2071,6 @@ build_local_reloptions(local_relopts *relopts, Datum options, bool validate)
elems[i].optname = opt->option->name;
elems[i].opttype = opt->option->type;
elems[i].offset = opt->offset;
- elems[i].isset_offset = 0; /* not supported for local relopts yet */
i++;
}
diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c
index 733ef40ae7..977babff54 100644
--- a/src/backend/commands/vacuum.c
+++ b/src/backend/commands/vacuum.c
@@ -2221,9 +2221,9 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams params,
{
StdRdOptions *opts = (StdRdOptions *) rel->rd_options;
- if (opts && opts->vacuum_truncate_set)
+ if (opts && opts->vacuum_truncate != TERNARY_UNSET)
{
- if (opts->vacuum_truncate)
+ if (opts->vacuum_truncate == TERNARY_TRUE)
params.truncate = VACOPTVALUE_ENABLED;
else
params.truncate = VACOPTVALUE_DISABLED;
diff --git a/src/include/access/reloptions.h b/src/include/access/reloptions.h
index a604a4702c..a436697658 100644
--- a/src/include/access/reloptions.h
+++ b/src/include/access/reloptions.h
@@ -29,6 +29,7 @@
typedef enum relopt_type
{
RELOPT_TYPE_BOOL,
+ RELOPT_TYPE_TERNARY, /* on, off, unset */
RELOPT_TYPE_INT,
RELOPT_TYPE_REAL,
RELOPT_TYPE_ENUM,
@@ -80,6 +81,7 @@ typedef struct relopt_value
union
{
bool bool_val;
+ ternary ternary_val;
int int_val;
double real_val;
int enum_val;
@@ -94,6 +96,12 @@ typedef struct relopt_bool
bool default_val;
} relopt_bool;
+typedef struct relopt_rernary
+{
+ relopt_gen gen;
+ int default_val;
+} relopt_ternary;
+
typedef struct relopt_int
{
relopt_gen gen;
@@ -152,19 +160,6 @@ typedef struct
const char *optname; /* option's name */
relopt_type opttype; /* option's datatype */
int offset; /* offset of field in result struct */
-
- /*
- * isset_offset is an optional offset of a field in the result struct that
- * stores whether the option is explicitly set for the relation or if it
- * just picked up the default value. In most cases, this can be
- * accomplished by giving the reloption a special out-of-range default
- * value (e.g., some integer reloptions use -2), but this isn't always
- * possible. For example, a Boolean reloption cannot be given an
- * out-of-range default, so we need another way to discover the source of
- * its value. This offset is only used if given a value greater than
- * zero.
- */
- int isset_offset;
} relopt_parse_elt;
/* Local reloption definition */
@@ -195,6 +190,8 @@ typedef struct local_relopts
extern relopt_kind add_reloption_kind(void);
extern void add_bool_reloption(bits32 kinds, const char *name, const char *desc,
bool default_val, LOCKMODE lockmode);
+extern void add_ternary_reloption(bits32 kinds, const char *name,
+ const char *desc, int default_val, LOCKMODE lockmode);
extern void add_int_reloption(bits32 kinds, const char *name, const char *desc,
int default_val, int min_val, int max_val,
LOCKMODE lockmode);
@@ -214,6 +211,9 @@ extern void register_reloptions_validator(local_relopts *relopts,
extern void add_local_bool_reloption(local_relopts *relopts, const char *name,
const char *desc, bool default_val,
int offset);
+extern void add_local_ternary_reloption(local_relopts *relopts,
+ const char *name, const char *desc,
+ ternary default_val, int offset);
extern void add_local_int_reloption(local_relopts *relopts, const char *name,
const char *desc, int default_val,
int min_val, int max_val, int offset);
diff --git a/src/include/c.h b/src/include/c.h
index 39022f8a9d..9c59ccccf8 100644
--- a/src/include/c.h
+++ b/src/include/c.h
@@ -488,6 +488,22 @@ typedef void (*pg_funcptr_t) (void);
#include <stdbool.h>
+/*
+ * ternary
+ * Boolean value with an extrea "unset" option
+ *
+ * Ternary data type is used in relation options that can be "true", "false" or
+ * "unset". Since relation options are used deep inside the PostgreSQL code,
+ * this type is declared globally.
+*/
+
+typedef enum ternary
+{
+ TERNARY_FALSE = 0,
+ TERNARY_TRUE = 1,
+ TERNARY_UNSET = -1
+} ternary;
+
/* ----------------------------------------------------------------
* Section 3: standard system types
diff --git a/src/include/utils/rel.h b/src/include/utils/rel.h
index b552359915..7346d618f9 100644
--- a/src/include/utils/rel.h
+++ b/src/include/utils/rel.h
@@ -346,8 +346,7 @@ typedef struct StdRdOptions
bool user_catalog_table; /* use as an additional catalog relation */
int parallel_workers; /* max number of parallel workers */
StdRdOptIndexCleanup vacuum_index_cleanup; /* controls index vacuuming */
- bool vacuum_truncate; /* enables vacuum to truncate a relation */
- bool vacuum_truncate_set; /* whether vacuum_truncate is set */
+ ternary vacuum_truncate; /* enables vacuum to truncate a relation */
/*
* Fraction of pages in a relation that vacuum can eagerly scan and fail
--
2.39.2
--nextPart5129932.5fSG56mABF
Content-Disposition: attachment;
filename="v2-0003-Add-alias-to-be-used-as-unset-state.patch"
Content-Transfer-Encoding: 7Bit
Content-Type: text/x-patch; charset="unicode-2-0-utf-8";
name="v2-0003-Add-alias-to-be-used-as-unset-state.patch"
^ permalink raw reply [nested|flat] 868+ messages in thread
* [PATCH v2a 2/4] Introduce ternary reloptions
@ 2025-09-04 14:41 Nikolay Shaplov <[email protected]>
0 siblings, 0 replies; 868+ messages in thread
From: Nikolay Shaplov @ 2025-09-04 14:41 UTC (permalink / raw)
Introduce ternary reloption as a replacement for current `vacuum_truncate`
implementation. Remove `vacuum_truncate_set` additional flag and using
`TERNARY_UNSET` value instead.
---
src/backend/access/common/reloptions.c | 129 ++++++++++++++++++++-----
src/backend/commands/vacuum.c | 4 +-
src/include/access/reloptions.h | 26 ++---
src/include/c.h | 16 +++
src/include/utils/rel.h | 3 +-
5 files changed, 135 insertions(+), 43 deletions(-)
diff --git a/src/backend/access/common/reloptions.c b/src/backend/access/common/reloptions.c
index 0af3fea68f..f543f11001 100644
--- a/src/backend/access/common/reloptions.c
+++ b/src/backend/access/common/reloptions.c
@@ -40,9 +40,9 @@
*
* To add an option:
*
- * (i) decide on a type (bool, integer, real, enum, string), name, default
- * value, upper and lower bounds (if applicable); for strings, consider a
- * validation routine.
+ * (i) decide on a type (bool, ternary, integer, real, enum, string), name,
+ * default value, upper and lower bounds (if applicable); for strings,
+ * consider a validation routine.
* (ii) add a record below (or use add_<type>_reloption).
* (iii) add it to the appropriate options struct (perhaps StdRdOptions)
* (iv) add it to the appropriate handling routine (perhaps
@@ -147,15 +147,6 @@ static relopt_bool boolRelOpts[] =
},
false
},
- {
- {
- "vacuum_truncate",
- "Enables vacuum to truncate empty pages at the end of this table",
- RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
- ShareUpdateExclusiveLock
- },
- true
- },
{
{
"deduplicate_items",
@@ -170,6 +161,21 @@ static relopt_bool boolRelOpts[] =
{{NULL}}
};
+static relopt_ternary ternaryRelOpts[] =
+{
+ {
+ {
+ "vacuum_truncate",
+ "Enables vacuum to truncate empty pages at the end of this table",
+ RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
+ ShareUpdateExclusiveLock
+ },
+ TERNARY_UNSET
+ },
+ /* list terminator */
+ {{NULL}}
+};
+
static relopt_int intRelOpts[] =
{
{
@@ -600,6 +606,13 @@ initialize_reloptions(void)
boolRelOpts[i].gen.lockmode));
j++;
}
+ for (i = 0; ternaryRelOpts[i].gen.name; i++)
+ {
+ Assert(DoLockModesConflict(ternaryRelOpts[i].gen.lockmode,
+ ternaryRelOpts[i].gen.lockmode));
+ j++;
+ }
+
for (i = 0; intRelOpts[i].gen.name; i++)
{
Assert(DoLockModesConflict(intRelOpts[i].gen.lockmode,
@@ -640,6 +653,14 @@ initialize_reloptions(void)
j++;
}
+ for (i = 0; ternaryRelOpts[i].gen.name; i++)
+ {
+ relOpts[j] = &ternaryRelOpts[i].gen;
+ relOpts[j]->type = RELOPT_TYPE_TERNARY;
+ relOpts[j]->namelen = strlen(relOpts[j]->name);
+ j++;
+ }
+
for (i = 0; intRelOpts[i].gen.name; i++)
{
relOpts[j] = &intRelOpts[i].gen;
@@ -800,6 +821,9 @@ allocate_reloption(bits32 kinds, int type, const char *name, const char *desc,
case RELOPT_TYPE_BOOL:
size = sizeof(relopt_bool);
break;
+ case RELOPT_TYPE_TERNARY:
+ size = sizeof(relopt_ternary);
+ break;
case RELOPT_TYPE_INT:
size = sizeof(relopt_int);
break;
@@ -883,6 +907,54 @@ add_local_bool_reloption(local_relopts *relopts, const char *name,
add_local_reloption(relopts, (relopt_gen *) newoption, offset);
}
+/*
+ * init_ternary_reloption
+ * Allocate and initialize a new ternary reloption
+ */
+static relopt_ternary *
+init_ternary_reloption(bits32 kinds, const char *name, const char *desc,
+ ternary default_val, LOCKMODE lockmode)
+{
+ relopt_ternary *newoption;
+
+ newoption = (relopt_ternary *) allocate_reloption(kinds,
+ RELOPT_TYPE_TERNARY, name, desc, lockmode);
+ newoption->default_val = default_val;
+
+ return newoption;
+}
+
+/*
+ * add_ternary_reloption
+ * Add a new ternary reloption
+ */
+void
+add_ternary_reloption(bits32 kinds, const char *name, const char *desc,
+ ternary default_val, LOCKMODE lockmode)
+{
+ relopt_ternary *newoption = init_ternary_reloption(kinds, name, desc,
+ default_val, lockmode);
+
+ add_reloption((relopt_gen *) newoption);
+}
+
+/*
+ * add_local_ternary_reloption
+ * Add a new ternary local reloption
+ *
+ * 'offset' is offset of ternary-typed field.
+ */
+void
+add_local_ternary_reloption(local_relopts *relopts, const char *name,
+ const char *desc, ternary default_val,
+ int offset)
+{
+ relopt_ternary *newoption = init_ternary_reloption(RELOPT_KIND_LOCAL,
+ name, desc,
+ default_val, 0);
+
+ add_local_reloption(relopts, (relopt_gen *) newoption, offset);
+}
/*
* init_real_reloption
@@ -1617,6 +1689,18 @@ parse_one_reloption(relopt_value *option, char *text_str, int text_len,
option->gen->name, value)));
}
break;
+ case RELOPT_TYPE_TERNARY:
+ {
+ bool b;
+ parsed = parse_bool(value, &b);
+ option->values.ternary_val = b ? TERNARY_TRUE : TERNARY_FALSE;
+ if (validate && !parsed)
+ ereport(ERROR,
+ (errcode(ERRCODE_INVALID_PARAMETER_VALUE),
+ errmsg("invalid value for ternary option \"%s\": %s",
+ option->gen->name, value)));
+ }
+ break;
case RELOPT_TYPE_INT:
{
relopt_int *optint = (relopt_int *) option->gen;
@@ -1780,17 +1864,6 @@ fillRelOptions(void *rdopts, Size basesize,
char *itempos = ((char *) rdopts) + elems[j].offset;
char *string_val;
- /*
- * If isset_offset is provided, store whether the reloption is
- * set there.
- */
- if (elems[j].isset_offset > 0)
- {
- char *setpos = ((char *) rdopts) + elems[j].isset_offset;
-
- *(bool *) setpos = options[i].isset;
- }
-
switch (options[i].gen->type)
{
case RELOPT_TYPE_BOOL:
@@ -1798,6 +1871,11 @@ fillRelOptions(void *rdopts, Size basesize,
options[i].values.bool_val :
((relopt_bool *) options[i].gen)->default_val;
break;
+ case RELOPT_TYPE_TERNARY:
+ *(ternary *) itempos = options[i].isset ?
+ options[i].values.ternary_val :
+ ((relopt_ternary *) options[i].gen)->default_val;
+ break;
case RELOPT_TYPE_INT:
*(int *) itempos = options[i].isset ?
options[i].values.int_val :
@@ -1912,8 +1990,8 @@ default_reloptions(Datum reloptions, bool validate, relopt_kind kind)
offsetof(StdRdOptions, parallel_workers)},
{"vacuum_index_cleanup", RELOPT_TYPE_ENUM,
offsetof(StdRdOptions, vacuum_index_cleanup)},
- {"vacuum_truncate", RELOPT_TYPE_BOOL,
- offsetof(StdRdOptions, vacuum_truncate), offsetof(StdRdOptions, vacuum_truncate_set)},
+ {"vacuum_truncate", RELOPT_TYPE_TERNARY,
+ offsetof(StdRdOptions, vacuum_truncate)},
{"vacuum_max_eager_freeze_failure_rate", RELOPT_TYPE_REAL,
offsetof(StdRdOptions, vacuum_max_eager_freeze_failure_rate)}
};
@@ -1993,7 +2071,6 @@ build_local_reloptions(local_relopts *relopts, Datum options, bool validate)
elems[i].optname = opt->option->name;
elems[i].opttype = opt->option->type;
elems[i].offset = opt->offset;
- elems[i].isset_offset = 0; /* not supported for local relopts yet */
i++;
}
diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c
index 733ef40ae7..977babff54 100644
--- a/src/backend/commands/vacuum.c
+++ b/src/backend/commands/vacuum.c
@@ -2221,9 +2221,9 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams params,
{
StdRdOptions *opts = (StdRdOptions *) rel->rd_options;
- if (opts && opts->vacuum_truncate_set)
+ if (opts && opts->vacuum_truncate != TERNARY_UNSET)
{
- if (opts->vacuum_truncate)
+ if (opts->vacuum_truncate == TERNARY_TRUE)
params.truncate = VACOPTVALUE_ENABLED;
else
params.truncate = VACOPTVALUE_DISABLED;
diff --git a/src/include/access/reloptions.h b/src/include/access/reloptions.h
index a604a4702c..a436697658 100644
--- a/src/include/access/reloptions.h
+++ b/src/include/access/reloptions.h
@@ -29,6 +29,7 @@
typedef enum relopt_type
{
RELOPT_TYPE_BOOL,
+ RELOPT_TYPE_TERNARY, /* on, off, unset */
RELOPT_TYPE_INT,
RELOPT_TYPE_REAL,
RELOPT_TYPE_ENUM,
@@ -80,6 +81,7 @@ typedef struct relopt_value
union
{
bool bool_val;
+ ternary ternary_val;
int int_val;
double real_val;
int enum_val;
@@ -94,6 +96,12 @@ typedef struct relopt_bool
bool default_val;
} relopt_bool;
+typedef struct relopt_rernary
+{
+ relopt_gen gen;
+ int default_val;
+} relopt_ternary;
+
typedef struct relopt_int
{
relopt_gen gen;
@@ -152,19 +160,6 @@ typedef struct
const char *optname; /* option's name */
relopt_type opttype; /* option's datatype */
int offset; /* offset of field in result struct */
-
- /*
- * isset_offset is an optional offset of a field in the result struct that
- * stores whether the option is explicitly set for the relation or if it
- * just picked up the default value. In most cases, this can be
- * accomplished by giving the reloption a special out-of-range default
- * value (e.g., some integer reloptions use -2), but this isn't always
- * possible. For example, a Boolean reloption cannot be given an
- * out-of-range default, so we need another way to discover the source of
- * its value. This offset is only used if given a value greater than
- * zero.
- */
- int isset_offset;
} relopt_parse_elt;
/* Local reloption definition */
@@ -195,6 +190,8 @@ typedef struct local_relopts
extern relopt_kind add_reloption_kind(void);
extern void add_bool_reloption(bits32 kinds, const char *name, const char *desc,
bool default_val, LOCKMODE lockmode);
+extern void add_ternary_reloption(bits32 kinds, const char *name,
+ const char *desc, int default_val, LOCKMODE lockmode);
extern void add_int_reloption(bits32 kinds, const char *name, const char *desc,
int default_val, int min_val, int max_val,
LOCKMODE lockmode);
@@ -214,6 +211,9 @@ extern void register_reloptions_validator(local_relopts *relopts,
extern void add_local_bool_reloption(local_relopts *relopts, const char *name,
const char *desc, bool default_val,
int offset);
+extern void add_local_ternary_reloption(local_relopts *relopts,
+ const char *name, const char *desc,
+ ternary default_val, int offset);
extern void add_local_int_reloption(local_relopts *relopts, const char *name,
const char *desc, int default_val,
int min_val, int max_val, int offset);
diff --git a/src/include/c.h b/src/include/c.h
index 39022f8a9d..9c59ccccf8 100644
--- a/src/include/c.h
+++ b/src/include/c.h
@@ -488,6 +488,22 @@ typedef void (*pg_funcptr_t) (void);
#include <stdbool.h>
+/*
+ * ternary
+ * Boolean value with an extrea "unset" option
+ *
+ * Ternary data type is used in relation options that can be "true", "false" or
+ * "unset". Since relation options are used deep inside the PostgreSQL code,
+ * this type is declared globally.
+*/
+
+typedef enum ternary
+{
+ TERNARY_FALSE = 0,
+ TERNARY_TRUE = 1,
+ TERNARY_UNSET = -1
+} ternary;
+
/* ----------------------------------------------------------------
* Section 3: standard system types
diff --git a/src/include/utils/rel.h b/src/include/utils/rel.h
index b552359915..7346d618f9 100644
--- a/src/include/utils/rel.h
+++ b/src/include/utils/rel.h
@@ -346,8 +346,7 @@ typedef struct StdRdOptions
bool user_catalog_table; /* use as an additional catalog relation */
int parallel_workers; /* max number of parallel workers */
StdRdOptIndexCleanup vacuum_index_cleanup; /* controls index vacuuming */
- bool vacuum_truncate; /* enables vacuum to truncate a relation */
- bool vacuum_truncate_set; /* whether vacuum_truncate is set */
+ ternary vacuum_truncate; /* enables vacuum to truncate a relation */
/*
* Fraction of pages in a relation that vacuum can eagerly scan and fail
--
2.39.2
--nextPart4155600.3ZeAukHxDK
Content-Disposition: attachment;
filename="v2a-0003-Add-alias-to-be-used-as-unset-state.patch"
Content-Transfer-Encoding: 7Bit
Content-Type: text/x-patch; charset="unicode-2-0-utf-8";
name="v2a-0003-Add-alias-to-be-used-as-unset-state.patch"
^ permalink raw reply [nested|flat] 868+ messages in thread
* [PATCH v2 2/4] Introduce ternary reloptions
@ 2025-09-04 14:41 Nikolay Shaplov <[email protected]>
0 siblings, 0 replies; 868+ messages in thread
From: Nikolay Shaplov @ 2025-09-04 14:41 UTC (permalink / raw)
Introduce ternary reloption as a replacement for current `vacuum_truncate`
implementation. Remove `vacuum_truncate_set` additional flag and using
`TERNARY_UNSET` value instead.
---
src/backend/access/common/reloptions.c | 129 ++++++++++++++++++++-----
src/backend/commands/vacuum.c | 4 +-
src/include/access/reloptions.h | 26 ++---
src/include/c.h | 16 +++
src/include/utils/rel.h | 3 +-
5 files changed, 135 insertions(+), 43 deletions(-)
diff --git a/src/backend/access/common/reloptions.c b/src/backend/access/common/reloptions.c
index 0af3fea68f..f543f11001 100644
--- a/src/backend/access/common/reloptions.c
+++ b/src/backend/access/common/reloptions.c
@@ -40,9 +40,9 @@
*
* To add an option:
*
- * (i) decide on a type (bool, integer, real, enum, string), name, default
- * value, upper and lower bounds (if applicable); for strings, consider a
- * validation routine.
+ * (i) decide on a type (bool, ternary, integer, real, enum, string), name,
+ * default value, upper and lower bounds (if applicable); for strings,
+ * consider a validation routine.
* (ii) add a record below (or use add_<type>_reloption).
* (iii) add it to the appropriate options struct (perhaps StdRdOptions)
* (iv) add it to the appropriate handling routine (perhaps
@@ -147,15 +147,6 @@ static relopt_bool boolRelOpts[] =
},
false
},
- {
- {
- "vacuum_truncate",
- "Enables vacuum to truncate empty pages at the end of this table",
- RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
- ShareUpdateExclusiveLock
- },
- true
- },
{
{
"deduplicate_items",
@@ -170,6 +161,21 @@ static relopt_bool boolRelOpts[] =
{{NULL}}
};
+static relopt_ternary ternaryRelOpts[] =
+{
+ {
+ {
+ "vacuum_truncate",
+ "Enables vacuum to truncate empty pages at the end of this table",
+ RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
+ ShareUpdateExclusiveLock
+ },
+ TERNARY_UNSET
+ },
+ /* list terminator */
+ {{NULL}}
+};
+
static relopt_int intRelOpts[] =
{
{
@@ -600,6 +606,13 @@ initialize_reloptions(void)
boolRelOpts[i].gen.lockmode));
j++;
}
+ for (i = 0; ternaryRelOpts[i].gen.name; i++)
+ {
+ Assert(DoLockModesConflict(ternaryRelOpts[i].gen.lockmode,
+ ternaryRelOpts[i].gen.lockmode));
+ j++;
+ }
+
for (i = 0; intRelOpts[i].gen.name; i++)
{
Assert(DoLockModesConflict(intRelOpts[i].gen.lockmode,
@@ -640,6 +653,14 @@ initialize_reloptions(void)
j++;
}
+ for (i = 0; ternaryRelOpts[i].gen.name; i++)
+ {
+ relOpts[j] = &ternaryRelOpts[i].gen;
+ relOpts[j]->type = RELOPT_TYPE_TERNARY;
+ relOpts[j]->namelen = strlen(relOpts[j]->name);
+ j++;
+ }
+
for (i = 0; intRelOpts[i].gen.name; i++)
{
relOpts[j] = &intRelOpts[i].gen;
@@ -800,6 +821,9 @@ allocate_reloption(bits32 kinds, int type, const char *name, const char *desc,
case RELOPT_TYPE_BOOL:
size = sizeof(relopt_bool);
break;
+ case RELOPT_TYPE_TERNARY:
+ size = sizeof(relopt_ternary);
+ break;
case RELOPT_TYPE_INT:
size = sizeof(relopt_int);
break;
@@ -883,6 +907,54 @@ add_local_bool_reloption(local_relopts *relopts, const char *name,
add_local_reloption(relopts, (relopt_gen *) newoption, offset);
}
+/*
+ * init_ternary_reloption
+ * Allocate and initialize a new ternary reloption
+ */
+static relopt_ternary *
+init_ternary_reloption(bits32 kinds, const char *name, const char *desc,
+ ternary default_val, LOCKMODE lockmode)
+{
+ relopt_ternary *newoption;
+
+ newoption = (relopt_ternary *) allocate_reloption(kinds,
+ RELOPT_TYPE_TERNARY, name, desc, lockmode);
+ newoption->default_val = default_val;
+
+ return newoption;
+}
+
+/*
+ * add_ternary_reloption
+ * Add a new ternary reloption
+ */
+void
+add_ternary_reloption(bits32 kinds, const char *name, const char *desc,
+ ternary default_val, LOCKMODE lockmode)
+{
+ relopt_ternary *newoption = init_ternary_reloption(kinds, name, desc,
+ default_val, lockmode);
+
+ add_reloption((relopt_gen *) newoption);
+}
+
+/*
+ * add_local_ternary_reloption
+ * Add a new ternary local reloption
+ *
+ * 'offset' is offset of ternary-typed field.
+ */
+void
+add_local_ternary_reloption(local_relopts *relopts, const char *name,
+ const char *desc, ternary default_val,
+ int offset)
+{
+ relopt_ternary *newoption = init_ternary_reloption(RELOPT_KIND_LOCAL,
+ name, desc,
+ default_val, 0);
+
+ add_local_reloption(relopts, (relopt_gen *) newoption, offset);
+}
/*
* init_real_reloption
@@ -1617,6 +1689,18 @@ parse_one_reloption(relopt_value *option, char *text_str, int text_len,
option->gen->name, value)));
}
break;
+ case RELOPT_TYPE_TERNARY:
+ {
+ bool b;
+ parsed = parse_bool(value, &b);
+ option->values.ternary_val = b ? TERNARY_TRUE : TERNARY_FALSE;
+ if (validate && !parsed)
+ ereport(ERROR,
+ (errcode(ERRCODE_INVALID_PARAMETER_VALUE),
+ errmsg("invalid value for ternary option \"%s\": %s",
+ option->gen->name, value)));
+ }
+ break;
case RELOPT_TYPE_INT:
{
relopt_int *optint = (relopt_int *) option->gen;
@@ -1780,17 +1864,6 @@ fillRelOptions(void *rdopts, Size basesize,
char *itempos = ((char *) rdopts) + elems[j].offset;
char *string_val;
- /*
- * If isset_offset is provided, store whether the reloption is
- * set there.
- */
- if (elems[j].isset_offset > 0)
- {
- char *setpos = ((char *) rdopts) + elems[j].isset_offset;
-
- *(bool *) setpos = options[i].isset;
- }
-
switch (options[i].gen->type)
{
case RELOPT_TYPE_BOOL:
@@ -1798,6 +1871,11 @@ fillRelOptions(void *rdopts, Size basesize,
options[i].values.bool_val :
((relopt_bool *) options[i].gen)->default_val;
break;
+ case RELOPT_TYPE_TERNARY:
+ *(ternary *) itempos = options[i].isset ?
+ options[i].values.ternary_val :
+ ((relopt_ternary *) options[i].gen)->default_val;
+ break;
case RELOPT_TYPE_INT:
*(int *) itempos = options[i].isset ?
options[i].values.int_val :
@@ -1912,8 +1990,8 @@ default_reloptions(Datum reloptions, bool validate, relopt_kind kind)
offsetof(StdRdOptions, parallel_workers)},
{"vacuum_index_cleanup", RELOPT_TYPE_ENUM,
offsetof(StdRdOptions, vacuum_index_cleanup)},
- {"vacuum_truncate", RELOPT_TYPE_BOOL,
- offsetof(StdRdOptions, vacuum_truncate), offsetof(StdRdOptions, vacuum_truncate_set)},
+ {"vacuum_truncate", RELOPT_TYPE_TERNARY,
+ offsetof(StdRdOptions, vacuum_truncate)},
{"vacuum_max_eager_freeze_failure_rate", RELOPT_TYPE_REAL,
offsetof(StdRdOptions, vacuum_max_eager_freeze_failure_rate)}
};
@@ -1993,7 +2071,6 @@ build_local_reloptions(local_relopts *relopts, Datum options, bool validate)
elems[i].optname = opt->option->name;
elems[i].opttype = opt->option->type;
elems[i].offset = opt->offset;
- elems[i].isset_offset = 0; /* not supported for local relopts yet */
i++;
}
diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c
index 733ef40ae7..977babff54 100644
--- a/src/backend/commands/vacuum.c
+++ b/src/backend/commands/vacuum.c
@@ -2221,9 +2221,9 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams params,
{
StdRdOptions *opts = (StdRdOptions *) rel->rd_options;
- if (opts && opts->vacuum_truncate_set)
+ if (opts && opts->vacuum_truncate != TERNARY_UNSET)
{
- if (opts->vacuum_truncate)
+ if (opts->vacuum_truncate == TERNARY_TRUE)
params.truncate = VACOPTVALUE_ENABLED;
else
params.truncate = VACOPTVALUE_DISABLED;
diff --git a/src/include/access/reloptions.h b/src/include/access/reloptions.h
index a604a4702c..a436697658 100644
--- a/src/include/access/reloptions.h
+++ b/src/include/access/reloptions.h
@@ -29,6 +29,7 @@
typedef enum relopt_type
{
RELOPT_TYPE_BOOL,
+ RELOPT_TYPE_TERNARY, /* on, off, unset */
RELOPT_TYPE_INT,
RELOPT_TYPE_REAL,
RELOPT_TYPE_ENUM,
@@ -80,6 +81,7 @@ typedef struct relopt_value
union
{
bool bool_val;
+ ternary ternary_val;
int int_val;
double real_val;
int enum_val;
@@ -94,6 +96,12 @@ typedef struct relopt_bool
bool default_val;
} relopt_bool;
+typedef struct relopt_rernary
+{
+ relopt_gen gen;
+ int default_val;
+} relopt_ternary;
+
typedef struct relopt_int
{
relopt_gen gen;
@@ -152,19 +160,6 @@ typedef struct
const char *optname; /* option's name */
relopt_type opttype; /* option's datatype */
int offset; /* offset of field in result struct */
-
- /*
- * isset_offset is an optional offset of a field in the result struct that
- * stores whether the option is explicitly set for the relation or if it
- * just picked up the default value. In most cases, this can be
- * accomplished by giving the reloption a special out-of-range default
- * value (e.g., some integer reloptions use -2), but this isn't always
- * possible. For example, a Boolean reloption cannot be given an
- * out-of-range default, so we need another way to discover the source of
- * its value. This offset is only used if given a value greater than
- * zero.
- */
- int isset_offset;
} relopt_parse_elt;
/* Local reloption definition */
@@ -195,6 +190,8 @@ typedef struct local_relopts
extern relopt_kind add_reloption_kind(void);
extern void add_bool_reloption(bits32 kinds, const char *name, const char *desc,
bool default_val, LOCKMODE lockmode);
+extern void add_ternary_reloption(bits32 kinds, const char *name,
+ const char *desc, int default_val, LOCKMODE lockmode);
extern void add_int_reloption(bits32 kinds, const char *name, const char *desc,
int default_val, int min_val, int max_val,
LOCKMODE lockmode);
@@ -214,6 +211,9 @@ extern void register_reloptions_validator(local_relopts *relopts,
extern void add_local_bool_reloption(local_relopts *relopts, const char *name,
const char *desc, bool default_val,
int offset);
+extern void add_local_ternary_reloption(local_relopts *relopts,
+ const char *name, const char *desc,
+ ternary default_val, int offset);
extern void add_local_int_reloption(local_relopts *relopts, const char *name,
const char *desc, int default_val,
int min_val, int max_val, int offset);
diff --git a/src/include/c.h b/src/include/c.h
index 39022f8a9d..9c59ccccf8 100644
--- a/src/include/c.h
+++ b/src/include/c.h
@@ -488,6 +488,22 @@ typedef void (*pg_funcptr_t) (void);
#include <stdbool.h>
+/*
+ * ternary
+ * Boolean value with an extrea "unset" option
+ *
+ * Ternary data type is used in relation options that can be "true", "false" or
+ * "unset". Since relation options are used deep inside the PostgreSQL code,
+ * this type is declared globally.
+*/
+
+typedef enum ternary
+{
+ TERNARY_FALSE = 0,
+ TERNARY_TRUE = 1,
+ TERNARY_UNSET = -1
+} ternary;
+
/* ----------------------------------------------------------------
* Section 3: standard system types
diff --git a/src/include/utils/rel.h b/src/include/utils/rel.h
index b552359915..7346d618f9 100644
--- a/src/include/utils/rel.h
+++ b/src/include/utils/rel.h
@@ -346,8 +346,7 @@ typedef struct StdRdOptions
bool user_catalog_table; /* use as an additional catalog relation */
int parallel_workers; /* max number of parallel workers */
StdRdOptIndexCleanup vacuum_index_cleanup; /* controls index vacuuming */
- bool vacuum_truncate; /* enables vacuum to truncate a relation */
- bool vacuum_truncate_set; /* whether vacuum_truncate is set */
+ ternary vacuum_truncate; /* enables vacuum to truncate a relation */
/*
* Fraction of pages in a relation that vacuum can eagerly scan and fail
--
2.39.2
--nextPart5129932.5fSG56mABF
Content-Disposition: attachment;
filename="v2-0003-Add-alias-to-be-used-as-unset-state.patch"
Content-Transfer-Encoding: 7Bit
Content-Type: text/x-patch; charset="unicode-2-0-utf-8";
name="v2-0003-Add-alias-to-be-used-as-unset-state.patch"
^ permalink raw reply [nested|flat] 868+ messages in thread
* [PATCH v2a 2/4] Introduce ternary reloptions
@ 2025-09-04 14:41 Nikolay Shaplov <[email protected]>
0 siblings, 0 replies; 868+ messages in thread
From: Nikolay Shaplov @ 2025-09-04 14:41 UTC (permalink / raw)
Introduce ternary reloption as a replacement for current `vacuum_truncate`
implementation. Remove `vacuum_truncate_set` additional flag and using
`TERNARY_UNSET` value instead.
---
src/backend/access/common/reloptions.c | 129 ++++++++++++++++++++-----
src/backend/commands/vacuum.c | 4 +-
src/include/access/reloptions.h | 26 ++---
src/include/c.h | 16 +++
src/include/utils/rel.h | 3 +-
5 files changed, 135 insertions(+), 43 deletions(-)
diff --git a/src/backend/access/common/reloptions.c b/src/backend/access/common/reloptions.c
index 0af3fea68f..f543f11001 100644
--- a/src/backend/access/common/reloptions.c
+++ b/src/backend/access/common/reloptions.c
@@ -40,9 +40,9 @@
*
* To add an option:
*
- * (i) decide on a type (bool, integer, real, enum, string), name, default
- * value, upper and lower bounds (if applicable); for strings, consider a
- * validation routine.
+ * (i) decide on a type (bool, ternary, integer, real, enum, string), name,
+ * default value, upper and lower bounds (if applicable); for strings,
+ * consider a validation routine.
* (ii) add a record below (or use add_<type>_reloption).
* (iii) add it to the appropriate options struct (perhaps StdRdOptions)
* (iv) add it to the appropriate handling routine (perhaps
@@ -147,15 +147,6 @@ static relopt_bool boolRelOpts[] =
},
false
},
- {
- {
- "vacuum_truncate",
- "Enables vacuum to truncate empty pages at the end of this table",
- RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
- ShareUpdateExclusiveLock
- },
- true
- },
{
{
"deduplicate_items",
@@ -170,6 +161,21 @@ static relopt_bool boolRelOpts[] =
{{NULL}}
};
+static relopt_ternary ternaryRelOpts[] =
+{
+ {
+ {
+ "vacuum_truncate",
+ "Enables vacuum to truncate empty pages at the end of this table",
+ RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
+ ShareUpdateExclusiveLock
+ },
+ TERNARY_UNSET
+ },
+ /* list terminator */
+ {{NULL}}
+};
+
static relopt_int intRelOpts[] =
{
{
@@ -600,6 +606,13 @@ initialize_reloptions(void)
boolRelOpts[i].gen.lockmode));
j++;
}
+ for (i = 0; ternaryRelOpts[i].gen.name; i++)
+ {
+ Assert(DoLockModesConflict(ternaryRelOpts[i].gen.lockmode,
+ ternaryRelOpts[i].gen.lockmode));
+ j++;
+ }
+
for (i = 0; intRelOpts[i].gen.name; i++)
{
Assert(DoLockModesConflict(intRelOpts[i].gen.lockmode,
@@ -640,6 +653,14 @@ initialize_reloptions(void)
j++;
}
+ for (i = 0; ternaryRelOpts[i].gen.name; i++)
+ {
+ relOpts[j] = &ternaryRelOpts[i].gen;
+ relOpts[j]->type = RELOPT_TYPE_TERNARY;
+ relOpts[j]->namelen = strlen(relOpts[j]->name);
+ j++;
+ }
+
for (i = 0; intRelOpts[i].gen.name; i++)
{
relOpts[j] = &intRelOpts[i].gen;
@@ -800,6 +821,9 @@ allocate_reloption(bits32 kinds, int type, const char *name, const char *desc,
case RELOPT_TYPE_BOOL:
size = sizeof(relopt_bool);
break;
+ case RELOPT_TYPE_TERNARY:
+ size = sizeof(relopt_ternary);
+ break;
case RELOPT_TYPE_INT:
size = sizeof(relopt_int);
break;
@@ -883,6 +907,54 @@ add_local_bool_reloption(local_relopts *relopts, const char *name,
add_local_reloption(relopts, (relopt_gen *) newoption, offset);
}
+/*
+ * init_ternary_reloption
+ * Allocate and initialize a new ternary reloption
+ */
+static relopt_ternary *
+init_ternary_reloption(bits32 kinds, const char *name, const char *desc,
+ ternary default_val, LOCKMODE lockmode)
+{
+ relopt_ternary *newoption;
+
+ newoption = (relopt_ternary *) allocate_reloption(kinds,
+ RELOPT_TYPE_TERNARY, name, desc, lockmode);
+ newoption->default_val = default_val;
+
+ return newoption;
+}
+
+/*
+ * add_ternary_reloption
+ * Add a new ternary reloption
+ */
+void
+add_ternary_reloption(bits32 kinds, const char *name, const char *desc,
+ ternary default_val, LOCKMODE lockmode)
+{
+ relopt_ternary *newoption = init_ternary_reloption(kinds, name, desc,
+ default_val, lockmode);
+
+ add_reloption((relopt_gen *) newoption);
+}
+
+/*
+ * add_local_ternary_reloption
+ * Add a new ternary local reloption
+ *
+ * 'offset' is offset of ternary-typed field.
+ */
+void
+add_local_ternary_reloption(local_relopts *relopts, const char *name,
+ const char *desc, ternary default_val,
+ int offset)
+{
+ relopt_ternary *newoption = init_ternary_reloption(RELOPT_KIND_LOCAL,
+ name, desc,
+ default_val, 0);
+
+ add_local_reloption(relopts, (relopt_gen *) newoption, offset);
+}
/*
* init_real_reloption
@@ -1617,6 +1689,18 @@ parse_one_reloption(relopt_value *option, char *text_str, int text_len,
option->gen->name, value)));
}
break;
+ case RELOPT_TYPE_TERNARY:
+ {
+ bool b;
+ parsed = parse_bool(value, &b);
+ option->values.ternary_val = b ? TERNARY_TRUE : TERNARY_FALSE;
+ if (validate && !parsed)
+ ereport(ERROR,
+ (errcode(ERRCODE_INVALID_PARAMETER_VALUE),
+ errmsg("invalid value for ternary option \"%s\": %s",
+ option->gen->name, value)));
+ }
+ break;
case RELOPT_TYPE_INT:
{
relopt_int *optint = (relopt_int *) option->gen;
@@ -1780,17 +1864,6 @@ fillRelOptions(void *rdopts, Size basesize,
char *itempos = ((char *) rdopts) + elems[j].offset;
char *string_val;
- /*
- * If isset_offset is provided, store whether the reloption is
- * set there.
- */
- if (elems[j].isset_offset > 0)
- {
- char *setpos = ((char *) rdopts) + elems[j].isset_offset;
-
- *(bool *) setpos = options[i].isset;
- }
-
switch (options[i].gen->type)
{
case RELOPT_TYPE_BOOL:
@@ -1798,6 +1871,11 @@ fillRelOptions(void *rdopts, Size basesize,
options[i].values.bool_val :
((relopt_bool *) options[i].gen)->default_val;
break;
+ case RELOPT_TYPE_TERNARY:
+ *(ternary *) itempos = options[i].isset ?
+ options[i].values.ternary_val :
+ ((relopt_ternary *) options[i].gen)->default_val;
+ break;
case RELOPT_TYPE_INT:
*(int *) itempos = options[i].isset ?
options[i].values.int_val :
@@ -1912,8 +1990,8 @@ default_reloptions(Datum reloptions, bool validate, relopt_kind kind)
offsetof(StdRdOptions, parallel_workers)},
{"vacuum_index_cleanup", RELOPT_TYPE_ENUM,
offsetof(StdRdOptions, vacuum_index_cleanup)},
- {"vacuum_truncate", RELOPT_TYPE_BOOL,
- offsetof(StdRdOptions, vacuum_truncate), offsetof(StdRdOptions, vacuum_truncate_set)},
+ {"vacuum_truncate", RELOPT_TYPE_TERNARY,
+ offsetof(StdRdOptions, vacuum_truncate)},
{"vacuum_max_eager_freeze_failure_rate", RELOPT_TYPE_REAL,
offsetof(StdRdOptions, vacuum_max_eager_freeze_failure_rate)}
};
@@ -1993,7 +2071,6 @@ build_local_reloptions(local_relopts *relopts, Datum options, bool validate)
elems[i].optname = opt->option->name;
elems[i].opttype = opt->option->type;
elems[i].offset = opt->offset;
- elems[i].isset_offset = 0; /* not supported for local relopts yet */
i++;
}
diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c
index 733ef40ae7..977babff54 100644
--- a/src/backend/commands/vacuum.c
+++ b/src/backend/commands/vacuum.c
@@ -2221,9 +2221,9 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams params,
{
StdRdOptions *opts = (StdRdOptions *) rel->rd_options;
- if (opts && opts->vacuum_truncate_set)
+ if (opts && opts->vacuum_truncate != TERNARY_UNSET)
{
- if (opts->vacuum_truncate)
+ if (opts->vacuum_truncate == TERNARY_TRUE)
params.truncate = VACOPTVALUE_ENABLED;
else
params.truncate = VACOPTVALUE_DISABLED;
diff --git a/src/include/access/reloptions.h b/src/include/access/reloptions.h
index a604a4702c..a436697658 100644
--- a/src/include/access/reloptions.h
+++ b/src/include/access/reloptions.h
@@ -29,6 +29,7 @@
typedef enum relopt_type
{
RELOPT_TYPE_BOOL,
+ RELOPT_TYPE_TERNARY, /* on, off, unset */
RELOPT_TYPE_INT,
RELOPT_TYPE_REAL,
RELOPT_TYPE_ENUM,
@@ -80,6 +81,7 @@ typedef struct relopt_value
union
{
bool bool_val;
+ ternary ternary_val;
int int_val;
double real_val;
int enum_val;
@@ -94,6 +96,12 @@ typedef struct relopt_bool
bool default_val;
} relopt_bool;
+typedef struct relopt_rernary
+{
+ relopt_gen gen;
+ int default_val;
+} relopt_ternary;
+
typedef struct relopt_int
{
relopt_gen gen;
@@ -152,19 +160,6 @@ typedef struct
const char *optname; /* option's name */
relopt_type opttype; /* option's datatype */
int offset; /* offset of field in result struct */
-
- /*
- * isset_offset is an optional offset of a field in the result struct that
- * stores whether the option is explicitly set for the relation or if it
- * just picked up the default value. In most cases, this can be
- * accomplished by giving the reloption a special out-of-range default
- * value (e.g., some integer reloptions use -2), but this isn't always
- * possible. For example, a Boolean reloption cannot be given an
- * out-of-range default, so we need another way to discover the source of
- * its value. This offset is only used if given a value greater than
- * zero.
- */
- int isset_offset;
} relopt_parse_elt;
/* Local reloption definition */
@@ -195,6 +190,8 @@ typedef struct local_relopts
extern relopt_kind add_reloption_kind(void);
extern void add_bool_reloption(bits32 kinds, const char *name, const char *desc,
bool default_val, LOCKMODE lockmode);
+extern void add_ternary_reloption(bits32 kinds, const char *name,
+ const char *desc, int default_val, LOCKMODE lockmode);
extern void add_int_reloption(bits32 kinds, const char *name, const char *desc,
int default_val, int min_val, int max_val,
LOCKMODE lockmode);
@@ -214,6 +211,9 @@ extern void register_reloptions_validator(local_relopts *relopts,
extern void add_local_bool_reloption(local_relopts *relopts, const char *name,
const char *desc, bool default_val,
int offset);
+extern void add_local_ternary_reloption(local_relopts *relopts,
+ const char *name, const char *desc,
+ ternary default_val, int offset);
extern void add_local_int_reloption(local_relopts *relopts, const char *name,
const char *desc, int default_val,
int min_val, int max_val, int offset);
diff --git a/src/include/c.h b/src/include/c.h
index 39022f8a9d..9c59ccccf8 100644
--- a/src/include/c.h
+++ b/src/include/c.h
@@ -488,6 +488,22 @@ typedef void (*pg_funcptr_t) (void);
#include <stdbool.h>
+/*
+ * ternary
+ * Boolean value with an extrea "unset" option
+ *
+ * Ternary data type is used in relation options that can be "true", "false" or
+ * "unset". Since relation options are used deep inside the PostgreSQL code,
+ * this type is declared globally.
+*/
+
+typedef enum ternary
+{
+ TERNARY_FALSE = 0,
+ TERNARY_TRUE = 1,
+ TERNARY_UNSET = -1
+} ternary;
+
/* ----------------------------------------------------------------
* Section 3: standard system types
diff --git a/src/include/utils/rel.h b/src/include/utils/rel.h
index b552359915..7346d618f9 100644
--- a/src/include/utils/rel.h
+++ b/src/include/utils/rel.h
@@ -346,8 +346,7 @@ typedef struct StdRdOptions
bool user_catalog_table; /* use as an additional catalog relation */
int parallel_workers; /* max number of parallel workers */
StdRdOptIndexCleanup vacuum_index_cleanup; /* controls index vacuuming */
- bool vacuum_truncate; /* enables vacuum to truncate a relation */
- bool vacuum_truncate_set; /* whether vacuum_truncate is set */
+ ternary vacuum_truncate; /* enables vacuum to truncate a relation */
/*
* Fraction of pages in a relation that vacuum can eagerly scan and fail
--
2.39.2
--nextPart4155600.3ZeAukHxDK
Content-Disposition: attachment;
filename="v2a-0003-Add-alias-to-be-used-as-unset-state.patch"
Content-Transfer-Encoding: 7Bit
Content-Type: text/x-patch; charset="unicode-2-0-utf-8";
name="v2a-0003-Add-alias-to-be-used-as-unset-state.patch"
^ permalink raw reply [nested|flat] 868+ messages in thread
* [PATCH v2 2/4] Introduce ternary reloptions
@ 2025-09-04 14:41 Nikolay Shaplov <[email protected]>
0 siblings, 0 replies; 868+ messages in thread
From: Nikolay Shaplov @ 2025-09-04 14:41 UTC (permalink / raw)
Introduce ternary reloption as a replacement for current `vacuum_truncate`
implementation. Remove `vacuum_truncate_set` additional flag and using
`TERNARY_UNSET` value instead.
---
src/backend/access/common/reloptions.c | 129 ++++++++++++++++++++-----
src/backend/commands/vacuum.c | 4 +-
src/include/access/reloptions.h | 26 ++---
src/include/c.h | 16 +++
src/include/utils/rel.h | 3 +-
5 files changed, 135 insertions(+), 43 deletions(-)
diff --git a/src/backend/access/common/reloptions.c b/src/backend/access/common/reloptions.c
index 0af3fea68f..f543f11001 100644
--- a/src/backend/access/common/reloptions.c
+++ b/src/backend/access/common/reloptions.c
@@ -40,9 +40,9 @@
*
* To add an option:
*
- * (i) decide on a type (bool, integer, real, enum, string), name, default
- * value, upper and lower bounds (if applicable); for strings, consider a
- * validation routine.
+ * (i) decide on a type (bool, ternary, integer, real, enum, string), name,
+ * default value, upper and lower bounds (if applicable); for strings,
+ * consider a validation routine.
* (ii) add a record below (or use add_<type>_reloption).
* (iii) add it to the appropriate options struct (perhaps StdRdOptions)
* (iv) add it to the appropriate handling routine (perhaps
@@ -147,15 +147,6 @@ static relopt_bool boolRelOpts[] =
},
false
},
- {
- {
- "vacuum_truncate",
- "Enables vacuum to truncate empty pages at the end of this table",
- RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
- ShareUpdateExclusiveLock
- },
- true
- },
{
{
"deduplicate_items",
@@ -170,6 +161,21 @@ static relopt_bool boolRelOpts[] =
{{NULL}}
};
+static relopt_ternary ternaryRelOpts[] =
+{
+ {
+ {
+ "vacuum_truncate",
+ "Enables vacuum to truncate empty pages at the end of this table",
+ RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
+ ShareUpdateExclusiveLock
+ },
+ TERNARY_UNSET
+ },
+ /* list terminator */
+ {{NULL}}
+};
+
static relopt_int intRelOpts[] =
{
{
@@ -600,6 +606,13 @@ initialize_reloptions(void)
boolRelOpts[i].gen.lockmode));
j++;
}
+ for (i = 0; ternaryRelOpts[i].gen.name; i++)
+ {
+ Assert(DoLockModesConflict(ternaryRelOpts[i].gen.lockmode,
+ ternaryRelOpts[i].gen.lockmode));
+ j++;
+ }
+
for (i = 0; intRelOpts[i].gen.name; i++)
{
Assert(DoLockModesConflict(intRelOpts[i].gen.lockmode,
@@ -640,6 +653,14 @@ initialize_reloptions(void)
j++;
}
+ for (i = 0; ternaryRelOpts[i].gen.name; i++)
+ {
+ relOpts[j] = &ternaryRelOpts[i].gen;
+ relOpts[j]->type = RELOPT_TYPE_TERNARY;
+ relOpts[j]->namelen = strlen(relOpts[j]->name);
+ j++;
+ }
+
for (i = 0; intRelOpts[i].gen.name; i++)
{
relOpts[j] = &intRelOpts[i].gen;
@@ -800,6 +821,9 @@ allocate_reloption(bits32 kinds, int type, const char *name, const char *desc,
case RELOPT_TYPE_BOOL:
size = sizeof(relopt_bool);
break;
+ case RELOPT_TYPE_TERNARY:
+ size = sizeof(relopt_ternary);
+ break;
case RELOPT_TYPE_INT:
size = sizeof(relopt_int);
break;
@@ -883,6 +907,54 @@ add_local_bool_reloption(local_relopts *relopts, const char *name,
add_local_reloption(relopts, (relopt_gen *) newoption, offset);
}
+/*
+ * init_ternary_reloption
+ * Allocate and initialize a new ternary reloption
+ */
+static relopt_ternary *
+init_ternary_reloption(bits32 kinds, const char *name, const char *desc,
+ ternary default_val, LOCKMODE lockmode)
+{
+ relopt_ternary *newoption;
+
+ newoption = (relopt_ternary *) allocate_reloption(kinds,
+ RELOPT_TYPE_TERNARY, name, desc, lockmode);
+ newoption->default_val = default_val;
+
+ return newoption;
+}
+
+/*
+ * add_ternary_reloption
+ * Add a new ternary reloption
+ */
+void
+add_ternary_reloption(bits32 kinds, const char *name, const char *desc,
+ ternary default_val, LOCKMODE lockmode)
+{
+ relopt_ternary *newoption = init_ternary_reloption(kinds, name, desc,
+ default_val, lockmode);
+
+ add_reloption((relopt_gen *) newoption);
+}
+
+/*
+ * add_local_ternary_reloption
+ * Add a new ternary local reloption
+ *
+ * 'offset' is offset of ternary-typed field.
+ */
+void
+add_local_ternary_reloption(local_relopts *relopts, const char *name,
+ const char *desc, ternary default_val,
+ int offset)
+{
+ relopt_ternary *newoption = init_ternary_reloption(RELOPT_KIND_LOCAL,
+ name, desc,
+ default_val, 0);
+
+ add_local_reloption(relopts, (relopt_gen *) newoption, offset);
+}
/*
* init_real_reloption
@@ -1617,6 +1689,18 @@ parse_one_reloption(relopt_value *option, char *text_str, int text_len,
option->gen->name, value)));
}
break;
+ case RELOPT_TYPE_TERNARY:
+ {
+ bool b;
+ parsed = parse_bool(value, &b);
+ option->values.ternary_val = b ? TERNARY_TRUE : TERNARY_FALSE;
+ if (validate && !parsed)
+ ereport(ERROR,
+ (errcode(ERRCODE_INVALID_PARAMETER_VALUE),
+ errmsg("invalid value for ternary option \"%s\": %s",
+ option->gen->name, value)));
+ }
+ break;
case RELOPT_TYPE_INT:
{
relopt_int *optint = (relopt_int *) option->gen;
@@ -1780,17 +1864,6 @@ fillRelOptions(void *rdopts, Size basesize,
char *itempos = ((char *) rdopts) + elems[j].offset;
char *string_val;
- /*
- * If isset_offset is provided, store whether the reloption is
- * set there.
- */
- if (elems[j].isset_offset > 0)
- {
- char *setpos = ((char *) rdopts) + elems[j].isset_offset;
-
- *(bool *) setpos = options[i].isset;
- }
-
switch (options[i].gen->type)
{
case RELOPT_TYPE_BOOL:
@@ -1798,6 +1871,11 @@ fillRelOptions(void *rdopts, Size basesize,
options[i].values.bool_val :
((relopt_bool *) options[i].gen)->default_val;
break;
+ case RELOPT_TYPE_TERNARY:
+ *(ternary *) itempos = options[i].isset ?
+ options[i].values.ternary_val :
+ ((relopt_ternary *) options[i].gen)->default_val;
+ break;
case RELOPT_TYPE_INT:
*(int *) itempos = options[i].isset ?
options[i].values.int_val :
@@ -1912,8 +1990,8 @@ default_reloptions(Datum reloptions, bool validate, relopt_kind kind)
offsetof(StdRdOptions, parallel_workers)},
{"vacuum_index_cleanup", RELOPT_TYPE_ENUM,
offsetof(StdRdOptions, vacuum_index_cleanup)},
- {"vacuum_truncate", RELOPT_TYPE_BOOL,
- offsetof(StdRdOptions, vacuum_truncate), offsetof(StdRdOptions, vacuum_truncate_set)},
+ {"vacuum_truncate", RELOPT_TYPE_TERNARY,
+ offsetof(StdRdOptions, vacuum_truncate)},
{"vacuum_max_eager_freeze_failure_rate", RELOPT_TYPE_REAL,
offsetof(StdRdOptions, vacuum_max_eager_freeze_failure_rate)}
};
@@ -1993,7 +2071,6 @@ build_local_reloptions(local_relopts *relopts, Datum options, bool validate)
elems[i].optname = opt->option->name;
elems[i].opttype = opt->option->type;
elems[i].offset = opt->offset;
- elems[i].isset_offset = 0; /* not supported for local relopts yet */
i++;
}
diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c
index 733ef40ae7..977babff54 100644
--- a/src/backend/commands/vacuum.c
+++ b/src/backend/commands/vacuum.c
@@ -2221,9 +2221,9 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams params,
{
StdRdOptions *opts = (StdRdOptions *) rel->rd_options;
- if (opts && opts->vacuum_truncate_set)
+ if (opts && opts->vacuum_truncate != TERNARY_UNSET)
{
- if (opts->vacuum_truncate)
+ if (opts->vacuum_truncate == TERNARY_TRUE)
params.truncate = VACOPTVALUE_ENABLED;
else
params.truncate = VACOPTVALUE_DISABLED;
diff --git a/src/include/access/reloptions.h b/src/include/access/reloptions.h
index a604a4702c..a436697658 100644
--- a/src/include/access/reloptions.h
+++ b/src/include/access/reloptions.h
@@ -29,6 +29,7 @@
typedef enum relopt_type
{
RELOPT_TYPE_BOOL,
+ RELOPT_TYPE_TERNARY, /* on, off, unset */
RELOPT_TYPE_INT,
RELOPT_TYPE_REAL,
RELOPT_TYPE_ENUM,
@@ -80,6 +81,7 @@ typedef struct relopt_value
union
{
bool bool_val;
+ ternary ternary_val;
int int_val;
double real_val;
int enum_val;
@@ -94,6 +96,12 @@ typedef struct relopt_bool
bool default_val;
} relopt_bool;
+typedef struct relopt_rernary
+{
+ relopt_gen gen;
+ int default_val;
+} relopt_ternary;
+
typedef struct relopt_int
{
relopt_gen gen;
@@ -152,19 +160,6 @@ typedef struct
const char *optname; /* option's name */
relopt_type opttype; /* option's datatype */
int offset; /* offset of field in result struct */
-
- /*
- * isset_offset is an optional offset of a field in the result struct that
- * stores whether the option is explicitly set for the relation or if it
- * just picked up the default value. In most cases, this can be
- * accomplished by giving the reloption a special out-of-range default
- * value (e.g., some integer reloptions use -2), but this isn't always
- * possible. For example, a Boolean reloption cannot be given an
- * out-of-range default, so we need another way to discover the source of
- * its value. This offset is only used if given a value greater than
- * zero.
- */
- int isset_offset;
} relopt_parse_elt;
/* Local reloption definition */
@@ -195,6 +190,8 @@ typedef struct local_relopts
extern relopt_kind add_reloption_kind(void);
extern void add_bool_reloption(bits32 kinds, const char *name, const char *desc,
bool default_val, LOCKMODE lockmode);
+extern void add_ternary_reloption(bits32 kinds, const char *name,
+ const char *desc, int default_val, LOCKMODE lockmode);
extern void add_int_reloption(bits32 kinds, const char *name, const char *desc,
int default_val, int min_val, int max_val,
LOCKMODE lockmode);
@@ -214,6 +211,9 @@ extern void register_reloptions_validator(local_relopts *relopts,
extern void add_local_bool_reloption(local_relopts *relopts, const char *name,
const char *desc, bool default_val,
int offset);
+extern void add_local_ternary_reloption(local_relopts *relopts,
+ const char *name, const char *desc,
+ ternary default_val, int offset);
extern void add_local_int_reloption(local_relopts *relopts, const char *name,
const char *desc, int default_val,
int min_val, int max_val, int offset);
diff --git a/src/include/c.h b/src/include/c.h
index 39022f8a9d..9c59ccccf8 100644
--- a/src/include/c.h
+++ b/src/include/c.h
@@ -488,6 +488,22 @@ typedef void (*pg_funcptr_t) (void);
#include <stdbool.h>
+/*
+ * ternary
+ * Boolean value with an extrea "unset" option
+ *
+ * Ternary data type is used in relation options that can be "true", "false" or
+ * "unset". Since relation options are used deep inside the PostgreSQL code,
+ * this type is declared globally.
+*/
+
+typedef enum ternary
+{
+ TERNARY_FALSE = 0,
+ TERNARY_TRUE = 1,
+ TERNARY_UNSET = -1
+} ternary;
+
/* ----------------------------------------------------------------
* Section 3: standard system types
diff --git a/src/include/utils/rel.h b/src/include/utils/rel.h
index b552359915..7346d618f9 100644
--- a/src/include/utils/rel.h
+++ b/src/include/utils/rel.h
@@ -346,8 +346,7 @@ typedef struct StdRdOptions
bool user_catalog_table; /* use as an additional catalog relation */
int parallel_workers; /* max number of parallel workers */
StdRdOptIndexCleanup vacuum_index_cleanup; /* controls index vacuuming */
- bool vacuum_truncate; /* enables vacuum to truncate a relation */
- bool vacuum_truncate_set; /* whether vacuum_truncate is set */
+ ternary vacuum_truncate; /* enables vacuum to truncate a relation */
/*
* Fraction of pages in a relation that vacuum can eagerly scan and fail
--
2.39.2
--nextPart5129932.5fSG56mABF
Content-Disposition: attachment;
filename="v2-0003-Add-alias-to-be-used-as-unset-state.patch"
Content-Transfer-Encoding: 7Bit
Content-Type: text/x-patch; charset="unicode-2-0-utf-8";
name="v2-0003-Add-alias-to-be-used-as-unset-state.patch"
^ permalink raw reply [nested|flat] 868+ messages in thread
* [PATCH v2a 2/4] Introduce ternary reloptions
@ 2025-09-04 14:41 Nikolay Shaplov <[email protected]>
0 siblings, 0 replies; 868+ messages in thread
From: Nikolay Shaplov @ 2025-09-04 14:41 UTC (permalink / raw)
Introduce ternary reloption as a replacement for current `vacuum_truncate`
implementation. Remove `vacuum_truncate_set` additional flag and using
`TERNARY_UNSET` value instead.
---
src/backend/access/common/reloptions.c | 129 ++++++++++++++++++++-----
src/backend/commands/vacuum.c | 4 +-
src/include/access/reloptions.h | 26 ++---
src/include/c.h | 16 +++
src/include/utils/rel.h | 3 +-
5 files changed, 135 insertions(+), 43 deletions(-)
diff --git a/src/backend/access/common/reloptions.c b/src/backend/access/common/reloptions.c
index 0af3fea68f..f543f11001 100644
--- a/src/backend/access/common/reloptions.c
+++ b/src/backend/access/common/reloptions.c
@@ -40,9 +40,9 @@
*
* To add an option:
*
- * (i) decide on a type (bool, integer, real, enum, string), name, default
- * value, upper and lower bounds (if applicable); for strings, consider a
- * validation routine.
+ * (i) decide on a type (bool, ternary, integer, real, enum, string), name,
+ * default value, upper and lower bounds (if applicable); for strings,
+ * consider a validation routine.
* (ii) add a record below (or use add_<type>_reloption).
* (iii) add it to the appropriate options struct (perhaps StdRdOptions)
* (iv) add it to the appropriate handling routine (perhaps
@@ -147,15 +147,6 @@ static relopt_bool boolRelOpts[] =
},
false
},
- {
- {
- "vacuum_truncate",
- "Enables vacuum to truncate empty pages at the end of this table",
- RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
- ShareUpdateExclusiveLock
- },
- true
- },
{
{
"deduplicate_items",
@@ -170,6 +161,21 @@ static relopt_bool boolRelOpts[] =
{{NULL}}
};
+static relopt_ternary ternaryRelOpts[] =
+{
+ {
+ {
+ "vacuum_truncate",
+ "Enables vacuum to truncate empty pages at the end of this table",
+ RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
+ ShareUpdateExclusiveLock
+ },
+ TERNARY_UNSET
+ },
+ /* list terminator */
+ {{NULL}}
+};
+
static relopt_int intRelOpts[] =
{
{
@@ -600,6 +606,13 @@ initialize_reloptions(void)
boolRelOpts[i].gen.lockmode));
j++;
}
+ for (i = 0; ternaryRelOpts[i].gen.name; i++)
+ {
+ Assert(DoLockModesConflict(ternaryRelOpts[i].gen.lockmode,
+ ternaryRelOpts[i].gen.lockmode));
+ j++;
+ }
+
for (i = 0; intRelOpts[i].gen.name; i++)
{
Assert(DoLockModesConflict(intRelOpts[i].gen.lockmode,
@@ -640,6 +653,14 @@ initialize_reloptions(void)
j++;
}
+ for (i = 0; ternaryRelOpts[i].gen.name; i++)
+ {
+ relOpts[j] = &ternaryRelOpts[i].gen;
+ relOpts[j]->type = RELOPT_TYPE_TERNARY;
+ relOpts[j]->namelen = strlen(relOpts[j]->name);
+ j++;
+ }
+
for (i = 0; intRelOpts[i].gen.name; i++)
{
relOpts[j] = &intRelOpts[i].gen;
@@ -800,6 +821,9 @@ allocate_reloption(bits32 kinds, int type, const char *name, const char *desc,
case RELOPT_TYPE_BOOL:
size = sizeof(relopt_bool);
break;
+ case RELOPT_TYPE_TERNARY:
+ size = sizeof(relopt_ternary);
+ break;
case RELOPT_TYPE_INT:
size = sizeof(relopt_int);
break;
@@ -883,6 +907,54 @@ add_local_bool_reloption(local_relopts *relopts, const char *name,
add_local_reloption(relopts, (relopt_gen *) newoption, offset);
}
+/*
+ * init_ternary_reloption
+ * Allocate and initialize a new ternary reloption
+ */
+static relopt_ternary *
+init_ternary_reloption(bits32 kinds, const char *name, const char *desc,
+ ternary default_val, LOCKMODE lockmode)
+{
+ relopt_ternary *newoption;
+
+ newoption = (relopt_ternary *) allocate_reloption(kinds,
+ RELOPT_TYPE_TERNARY, name, desc, lockmode);
+ newoption->default_val = default_val;
+
+ return newoption;
+}
+
+/*
+ * add_ternary_reloption
+ * Add a new ternary reloption
+ */
+void
+add_ternary_reloption(bits32 kinds, const char *name, const char *desc,
+ ternary default_val, LOCKMODE lockmode)
+{
+ relopt_ternary *newoption = init_ternary_reloption(kinds, name, desc,
+ default_val, lockmode);
+
+ add_reloption((relopt_gen *) newoption);
+}
+
+/*
+ * add_local_ternary_reloption
+ * Add a new ternary local reloption
+ *
+ * 'offset' is offset of ternary-typed field.
+ */
+void
+add_local_ternary_reloption(local_relopts *relopts, const char *name,
+ const char *desc, ternary default_val,
+ int offset)
+{
+ relopt_ternary *newoption = init_ternary_reloption(RELOPT_KIND_LOCAL,
+ name, desc,
+ default_val, 0);
+
+ add_local_reloption(relopts, (relopt_gen *) newoption, offset);
+}
/*
* init_real_reloption
@@ -1617,6 +1689,18 @@ parse_one_reloption(relopt_value *option, char *text_str, int text_len,
option->gen->name, value)));
}
break;
+ case RELOPT_TYPE_TERNARY:
+ {
+ bool b;
+ parsed = parse_bool(value, &b);
+ option->values.ternary_val = b ? TERNARY_TRUE : TERNARY_FALSE;
+ if (validate && !parsed)
+ ereport(ERROR,
+ (errcode(ERRCODE_INVALID_PARAMETER_VALUE),
+ errmsg("invalid value for ternary option \"%s\": %s",
+ option->gen->name, value)));
+ }
+ break;
case RELOPT_TYPE_INT:
{
relopt_int *optint = (relopt_int *) option->gen;
@@ -1780,17 +1864,6 @@ fillRelOptions(void *rdopts, Size basesize,
char *itempos = ((char *) rdopts) + elems[j].offset;
char *string_val;
- /*
- * If isset_offset is provided, store whether the reloption is
- * set there.
- */
- if (elems[j].isset_offset > 0)
- {
- char *setpos = ((char *) rdopts) + elems[j].isset_offset;
-
- *(bool *) setpos = options[i].isset;
- }
-
switch (options[i].gen->type)
{
case RELOPT_TYPE_BOOL:
@@ -1798,6 +1871,11 @@ fillRelOptions(void *rdopts, Size basesize,
options[i].values.bool_val :
((relopt_bool *) options[i].gen)->default_val;
break;
+ case RELOPT_TYPE_TERNARY:
+ *(ternary *) itempos = options[i].isset ?
+ options[i].values.ternary_val :
+ ((relopt_ternary *) options[i].gen)->default_val;
+ break;
case RELOPT_TYPE_INT:
*(int *) itempos = options[i].isset ?
options[i].values.int_val :
@@ -1912,8 +1990,8 @@ default_reloptions(Datum reloptions, bool validate, relopt_kind kind)
offsetof(StdRdOptions, parallel_workers)},
{"vacuum_index_cleanup", RELOPT_TYPE_ENUM,
offsetof(StdRdOptions, vacuum_index_cleanup)},
- {"vacuum_truncate", RELOPT_TYPE_BOOL,
- offsetof(StdRdOptions, vacuum_truncate), offsetof(StdRdOptions, vacuum_truncate_set)},
+ {"vacuum_truncate", RELOPT_TYPE_TERNARY,
+ offsetof(StdRdOptions, vacuum_truncate)},
{"vacuum_max_eager_freeze_failure_rate", RELOPT_TYPE_REAL,
offsetof(StdRdOptions, vacuum_max_eager_freeze_failure_rate)}
};
@@ -1993,7 +2071,6 @@ build_local_reloptions(local_relopts *relopts, Datum options, bool validate)
elems[i].optname = opt->option->name;
elems[i].opttype = opt->option->type;
elems[i].offset = opt->offset;
- elems[i].isset_offset = 0; /* not supported for local relopts yet */
i++;
}
diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c
index 733ef40ae7..977babff54 100644
--- a/src/backend/commands/vacuum.c
+++ b/src/backend/commands/vacuum.c
@@ -2221,9 +2221,9 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams params,
{
StdRdOptions *opts = (StdRdOptions *) rel->rd_options;
- if (opts && opts->vacuum_truncate_set)
+ if (opts && opts->vacuum_truncate != TERNARY_UNSET)
{
- if (opts->vacuum_truncate)
+ if (opts->vacuum_truncate == TERNARY_TRUE)
params.truncate = VACOPTVALUE_ENABLED;
else
params.truncate = VACOPTVALUE_DISABLED;
diff --git a/src/include/access/reloptions.h b/src/include/access/reloptions.h
index a604a4702c..a436697658 100644
--- a/src/include/access/reloptions.h
+++ b/src/include/access/reloptions.h
@@ -29,6 +29,7 @@
typedef enum relopt_type
{
RELOPT_TYPE_BOOL,
+ RELOPT_TYPE_TERNARY, /* on, off, unset */
RELOPT_TYPE_INT,
RELOPT_TYPE_REAL,
RELOPT_TYPE_ENUM,
@@ -80,6 +81,7 @@ typedef struct relopt_value
union
{
bool bool_val;
+ ternary ternary_val;
int int_val;
double real_val;
int enum_val;
@@ -94,6 +96,12 @@ typedef struct relopt_bool
bool default_val;
} relopt_bool;
+typedef struct relopt_rernary
+{
+ relopt_gen gen;
+ int default_val;
+} relopt_ternary;
+
typedef struct relopt_int
{
relopt_gen gen;
@@ -152,19 +160,6 @@ typedef struct
const char *optname; /* option's name */
relopt_type opttype; /* option's datatype */
int offset; /* offset of field in result struct */
-
- /*
- * isset_offset is an optional offset of a field in the result struct that
- * stores whether the option is explicitly set for the relation or if it
- * just picked up the default value. In most cases, this can be
- * accomplished by giving the reloption a special out-of-range default
- * value (e.g., some integer reloptions use -2), but this isn't always
- * possible. For example, a Boolean reloption cannot be given an
- * out-of-range default, so we need another way to discover the source of
- * its value. This offset is only used if given a value greater than
- * zero.
- */
- int isset_offset;
} relopt_parse_elt;
/* Local reloption definition */
@@ -195,6 +190,8 @@ typedef struct local_relopts
extern relopt_kind add_reloption_kind(void);
extern void add_bool_reloption(bits32 kinds, const char *name, const char *desc,
bool default_val, LOCKMODE lockmode);
+extern void add_ternary_reloption(bits32 kinds, const char *name,
+ const char *desc, int default_val, LOCKMODE lockmode);
extern void add_int_reloption(bits32 kinds, const char *name, const char *desc,
int default_val, int min_val, int max_val,
LOCKMODE lockmode);
@@ -214,6 +211,9 @@ extern void register_reloptions_validator(local_relopts *relopts,
extern void add_local_bool_reloption(local_relopts *relopts, const char *name,
const char *desc, bool default_val,
int offset);
+extern void add_local_ternary_reloption(local_relopts *relopts,
+ const char *name, const char *desc,
+ ternary default_val, int offset);
extern void add_local_int_reloption(local_relopts *relopts, const char *name,
const char *desc, int default_val,
int min_val, int max_val, int offset);
diff --git a/src/include/c.h b/src/include/c.h
index 39022f8a9d..9c59ccccf8 100644
--- a/src/include/c.h
+++ b/src/include/c.h
@@ -488,6 +488,22 @@ typedef void (*pg_funcptr_t) (void);
#include <stdbool.h>
+/*
+ * ternary
+ * Boolean value with an extrea "unset" option
+ *
+ * Ternary data type is used in relation options that can be "true", "false" or
+ * "unset". Since relation options are used deep inside the PostgreSQL code,
+ * this type is declared globally.
+*/
+
+typedef enum ternary
+{
+ TERNARY_FALSE = 0,
+ TERNARY_TRUE = 1,
+ TERNARY_UNSET = -1
+} ternary;
+
/* ----------------------------------------------------------------
* Section 3: standard system types
diff --git a/src/include/utils/rel.h b/src/include/utils/rel.h
index b552359915..7346d618f9 100644
--- a/src/include/utils/rel.h
+++ b/src/include/utils/rel.h
@@ -346,8 +346,7 @@ typedef struct StdRdOptions
bool user_catalog_table; /* use as an additional catalog relation */
int parallel_workers; /* max number of parallel workers */
StdRdOptIndexCleanup vacuum_index_cleanup; /* controls index vacuuming */
- bool vacuum_truncate; /* enables vacuum to truncate a relation */
- bool vacuum_truncate_set; /* whether vacuum_truncate is set */
+ ternary vacuum_truncate; /* enables vacuum to truncate a relation */
/*
* Fraction of pages in a relation that vacuum can eagerly scan and fail
--
2.39.2
--nextPart4155600.3ZeAukHxDK
Content-Disposition: attachment;
filename="v2a-0003-Add-alias-to-be-used-as-unset-state.patch"
Content-Transfer-Encoding: 7Bit
Content-Type: text/x-patch; charset="unicode-2-0-utf-8";
name="v2a-0003-Add-alias-to-be-used-as-unset-state.patch"
^ permalink raw reply [nested|flat] 868+ messages in thread
* [PATCH v2a 2/4] Introduce ternary reloptions
@ 2025-09-04 14:41 Nikolay Shaplov <[email protected]>
0 siblings, 0 replies; 868+ messages in thread
From: Nikolay Shaplov @ 2025-09-04 14:41 UTC (permalink / raw)
Introduce ternary reloption as a replacement for current `vacuum_truncate`
implementation. Remove `vacuum_truncate_set` additional flag and using
`TERNARY_UNSET` value instead.
---
src/backend/access/common/reloptions.c | 129 ++++++++++++++++++++-----
src/backend/commands/vacuum.c | 4 +-
src/include/access/reloptions.h | 26 ++---
src/include/c.h | 16 +++
src/include/utils/rel.h | 3 +-
5 files changed, 135 insertions(+), 43 deletions(-)
diff --git a/src/backend/access/common/reloptions.c b/src/backend/access/common/reloptions.c
index 0af3fea68f..f543f11001 100644
--- a/src/backend/access/common/reloptions.c
+++ b/src/backend/access/common/reloptions.c
@@ -40,9 +40,9 @@
*
* To add an option:
*
- * (i) decide on a type (bool, integer, real, enum, string), name, default
- * value, upper and lower bounds (if applicable); for strings, consider a
- * validation routine.
+ * (i) decide on a type (bool, ternary, integer, real, enum, string), name,
+ * default value, upper and lower bounds (if applicable); for strings,
+ * consider a validation routine.
* (ii) add a record below (or use add_<type>_reloption).
* (iii) add it to the appropriate options struct (perhaps StdRdOptions)
* (iv) add it to the appropriate handling routine (perhaps
@@ -147,15 +147,6 @@ static relopt_bool boolRelOpts[] =
},
false
},
- {
- {
- "vacuum_truncate",
- "Enables vacuum to truncate empty pages at the end of this table",
- RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
- ShareUpdateExclusiveLock
- },
- true
- },
{
{
"deduplicate_items",
@@ -170,6 +161,21 @@ static relopt_bool boolRelOpts[] =
{{NULL}}
};
+static relopt_ternary ternaryRelOpts[] =
+{
+ {
+ {
+ "vacuum_truncate",
+ "Enables vacuum to truncate empty pages at the end of this table",
+ RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
+ ShareUpdateExclusiveLock
+ },
+ TERNARY_UNSET
+ },
+ /* list terminator */
+ {{NULL}}
+};
+
static relopt_int intRelOpts[] =
{
{
@@ -600,6 +606,13 @@ initialize_reloptions(void)
boolRelOpts[i].gen.lockmode));
j++;
}
+ for (i = 0; ternaryRelOpts[i].gen.name; i++)
+ {
+ Assert(DoLockModesConflict(ternaryRelOpts[i].gen.lockmode,
+ ternaryRelOpts[i].gen.lockmode));
+ j++;
+ }
+
for (i = 0; intRelOpts[i].gen.name; i++)
{
Assert(DoLockModesConflict(intRelOpts[i].gen.lockmode,
@@ -640,6 +653,14 @@ initialize_reloptions(void)
j++;
}
+ for (i = 0; ternaryRelOpts[i].gen.name; i++)
+ {
+ relOpts[j] = &ternaryRelOpts[i].gen;
+ relOpts[j]->type = RELOPT_TYPE_TERNARY;
+ relOpts[j]->namelen = strlen(relOpts[j]->name);
+ j++;
+ }
+
for (i = 0; intRelOpts[i].gen.name; i++)
{
relOpts[j] = &intRelOpts[i].gen;
@@ -800,6 +821,9 @@ allocate_reloption(bits32 kinds, int type, const char *name, const char *desc,
case RELOPT_TYPE_BOOL:
size = sizeof(relopt_bool);
break;
+ case RELOPT_TYPE_TERNARY:
+ size = sizeof(relopt_ternary);
+ break;
case RELOPT_TYPE_INT:
size = sizeof(relopt_int);
break;
@@ -883,6 +907,54 @@ add_local_bool_reloption(local_relopts *relopts, const char *name,
add_local_reloption(relopts, (relopt_gen *) newoption, offset);
}
+/*
+ * init_ternary_reloption
+ * Allocate and initialize a new ternary reloption
+ */
+static relopt_ternary *
+init_ternary_reloption(bits32 kinds, const char *name, const char *desc,
+ ternary default_val, LOCKMODE lockmode)
+{
+ relopt_ternary *newoption;
+
+ newoption = (relopt_ternary *) allocate_reloption(kinds,
+ RELOPT_TYPE_TERNARY, name, desc, lockmode);
+ newoption->default_val = default_val;
+
+ return newoption;
+}
+
+/*
+ * add_ternary_reloption
+ * Add a new ternary reloption
+ */
+void
+add_ternary_reloption(bits32 kinds, const char *name, const char *desc,
+ ternary default_val, LOCKMODE lockmode)
+{
+ relopt_ternary *newoption = init_ternary_reloption(kinds, name, desc,
+ default_val, lockmode);
+
+ add_reloption((relopt_gen *) newoption);
+}
+
+/*
+ * add_local_ternary_reloption
+ * Add a new ternary local reloption
+ *
+ * 'offset' is offset of ternary-typed field.
+ */
+void
+add_local_ternary_reloption(local_relopts *relopts, const char *name,
+ const char *desc, ternary default_val,
+ int offset)
+{
+ relopt_ternary *newoption = init_ternary_reloption(RELOPT_KIND_LOCAL,
+ name, desc,
+ default_val, 0);
+
+ add_local_reloption(relopts, (relopt_gen *) newoption, offset);
+}
/*
* init_real_reloption
@@ -1617,6 +1689,18 @@ parse_one_reloption(relopt_value *option, char *text_str, int text_len,
option->gen->name, value)));
}
break;
+ case RELOPT_TYPE_TERNARY:
+ {
+ bool b;
+ parsed = parse_bool(value, &b);
+ option->values.ternary_val = b ? TERNARY_TRUE : TERNARY_FALSE;
+ if (validate && !parsed)
+ ereport(ERROR,
+ (errcode(ERRCODE_INVALID_PARAMETER_VALUE),
+ errmsg("invalid value for ternary option \"%s\": %s",
+ option->gen->name, value)));
+ }
+ break;
case RELOPT_TYPE_INT:
{
relopt_int *optint = (relopt_int *) option->gen;
@@ -1780,17 +1864,6 @@ fillRelOptions(void *rdopts, Size basesize,
char *itempos = ((char *) rdopts) + elems[j].offset;
char *string_val;
- /*
- * If isset_offset is provided, store whether the reloption is
- * set there.
- */
- if (elems[j].isset_offset > 0)
- {
- char *setpos = ((char *) rdopts) + elems[j].isset_offset;
-
- *(bool *) setpos = options[i].isset;
- }
-
switch (options[i].gen->type)
{
case RELOPT_TYPE_BOOL:
@@ -1798,6 +1871,11 @@ fillRelOptions(void *rdopts, Size basesize,
options[i].values.bool_val :
((relopt_bool *) options[i].gen)->default_val;
break;
+ case RELOPT_TYPE_TERNARY:
+ *(ternary *) itempos = options[i].isset ?
+ options[i].values.ternary_val :
+ ((relopt_ternary *) options[i].gen)->default_val;
+ break;
case RELOPT_TYPE_INT:
*(int *) itempos = options[i].isset ?
options[i].values.int_val :
@@ -1912,8 +1990,8 @@ default_reloptions(Datum reloptions, bool validate, relopt_kind kind)
offsetof(StdRdOptions, parallel_workers)},
{"vacuum_index_cleanup", RELOPT_TYPE_ENUM,
offsetof(StdRdOptions, vacuum_index_cleanup)},
- {"vacuum_truncate", RELOPT_TYPE_BOOL,
- offsetof(StdRdOptions, vacuum_truncate), offsetof(StdRdOptions, vacuum_truncate_set)},
+ {"vacuum_truncate", RELOPT_TYPE_TERNARY,
+ offsetof(StdRdOptions, vacuum_truncate)},
{"vacuum_max_eager_freeze_failure_rate", RELOPT_TYPE_REAL,
offsetof(StdRdOptions, vacuum_max_eager_freeze_failure_rate)}
};
@@ -1993,7 +2071,6 @@ build_local_reloptions(local_relopts *relopts, Datum options, bool validate)
elems[i].optname = opt->option->name;
elems[i].opttype = opt->option->type;
elems[i].offset = opt->offset;
- elems[i].isset_offset = 0; /* not supported for local relopts yet */
i++;
}
diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c
index 733ef40ae7..977babff54 100644
--- a/src/backend/commands/vacuum.c
+++ b/src/backend/commands/vacuum.c
@@ -2221,9 +2221,9 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams params,
{
StdRdOptions *opts = (StdRdOptions *) rel->rd_options;
- if (opts && opts->vacuum_truncate_set)
+ if (opts && opts->vacuum_truncate != TERNARY_UNSET)
{
- if (opts->vacuum_truncate)
+ if (opts->vacuum_truncate == TERNARY_TRUE)
params.truncate = VACOPTVALUE_ENABLED;
else
params.truncate = VACOPTVALUE_DISABLED;
diff --git a/src/include/access/reloptions.h b/src/include/access/reloptions.h
index a604a4702c..a436697658 100644
--- a/src/include/access/reloptions.h
+++ b/src/include/access/reloptions.h
@@ -29,6 +29,7 @@
typedef enum relopt_type
{
RELOPT_TYPE_BOOL,
+ RELOPT_TYPE_TERNARY, /* on, off, unset */
RELOPT_TYPE_INT,
RELOPT_TYPE_REAL,
RELOPT_TYPE_ENUM,
@@ -80,6 +81,7 @@ typedef struct relopt_value
union
{
bool bool_val;
+ ternary ternary_val;
int int_val;
double real_val;
int enum_val;
@@ -94,6 +96,12 @@ typedef struct relopt_bool
bool default_val;
} relopt_bool;
+typedef struct relopt_rernary
+{
+ relopt_gen gen;
+ int default_val;
+} relopt_ternary;
+
typedef struct relopt_int
{
relopt_gen gen;
@@ -152,19 +160,6 @@ typedef struct
const char *optname; /* option's name */
relopt_type opttype; /* option's datatype */
int offset; /* offset of field in result struct */
-
- /*
- * isset_offset is an optional offset of a field in the result struct that
- * stores whether the option is explicitly set for the relation or if it
- * just picked up the default value. In most cases, this can be
- * accomplished by giving the reloption a special out-of-range default
- * value (e.g., some integer reloptions use -2), but this isn't always
- * possible. For example, a Boolean reloption cannot be given an
- * out-of-range default, so we need another way to discover the source of
- * its value. This offset is only used if given a value greater than
- * zero.
- */
- int isset_offset;
} relopt_parse_elt;
/* Local reloption definition */
@@ -195,6 +190,8 @@ typedef struct local_relopts
extern relopt_kind add_reloption_kind(void);
extern void add_bool_reloption(bits32 kinds, const char *name, const char *desc,
bool default_val, LOCKMODE lockmode);
+extern void add_ternary_reloption(bits32 kinds, const char *name,
+ const char *desc, int default_val, LOCKMODE lockmode);
extern void add_int_reloption(bits32 kinds, const char *name, const char *desc,
int default_val, int min_val, int max_val,
LOCKMODE lockmode);
@@ -214,6 +211,9 @@ extern void register_reloptions_validator(local_relopts *relopts,
extern void add_local_bool_reloption(local_relopts *relopts, const char *name,
const char *desc, bool default_val,
int offset);
+extern void add_local_ternary_reloption(local_relopts *relopts,
+ const char *name, const char *desc,
+ ternary default_val, int offset);
extern void add_local_int_reloption(local_relopts *relopts, const char *name,
const char *desc, int default_val,
int min_val, int max_val, int offset);
diff --git a/src/include/c.h b/src/include/c.h
index 39022f8a9d..9c59ccccf8 100644
--- a/src/include/c.h
+++ b/src/include/c.h
@@ -488,6 +488,22 @@ typedef void (*pg_funcptr_t) (void);
#include <stdbool.h>
+/*
+ * ternary
+ * Boolean value with an extrea "unset" option
+ *
+ * Ternary data type is used in relation options that can be "true", "false" or
+ * "unset". Since relation options are used deep inside the PostgreSQL code,
+ * this type is declared globally.
+*/
+
+typedef enum ternary
+{
+ TERNARY_FALSE = 0,
+ TERNARY_TRUE = 1,
+ TERNARY_UNSET = -1
+} ternary;
+
/* ----------------------------------------------------------------
* Section 3: standard system types
diff --git a/src/include/utils/rel.h b/src/include/utils/rel.h
index b552359915..7346d618f9 100644
--- a/src/include/utils/rel.h
+++ b/src/include/utils/rel.h
@@ -346,8 +346,7 @@ typedef struct StdRdOptions
bool user_catalog_table; /* use as an additional catalog relation */
int parallel_workers; /* max number of parallel workers */
StdRdOptIndexCleanup vacuum_index_cleanup; /* controls index vacuuming */
- bool vacuum_truncate; /* enables vacuum to truncate a relation */
- bool vacuum_truncate_set; /* whether vacuum_truncate is set */
+ ternary vacuum_truncate; /* enables vacuum to truncate a relation */
/*
* Fraction of pages in a relation that vacuum can eagerly scan and fail
--
2.39.2
--nextPart4155600.3ZeAukHxDK
Content-Disposition: attachment;
filename="v2a-0003-Add-alias-to-be-used-as-unset-state.patch"
Content-Transfer-Encoding: 7Bit
Content-Type: text/x-patch; charset="unicode-2-0-utf-8";
name="v2a-0003-Add-alias-to-be-used-as-unset-state.patch"
^ permalink raw reply [nested|flat] 868+ messages in thread
* [PATCH v2 2/4] Introduce ternary reloptions
@ 2025-09-04 14:41 Nikolay Shaplov <[email protected]>
0 siblings, 0 replies; 868+ messages in thread
From: Nikolay Shaplov @ 2025-09-04 14:41 UTC (permalink / raw)
Introduce ternary reloption as a replacement for current `vacuum_truncate`
implementation. Remove `vacuum_truncate_set` additional flag and using
`TERNARY_UNSET` value instead.
---
src/backend/access/common/reloptions.c | 129 ++++++++++++++++++++-----
src/backend/commands/vacuum.c | 4 +-
src/include/access/reloptions.h | 26 ++---
src/include/c.h | 16 +++
src/include/utils/rel.h | 3 +-
5 files changed, 135 insertions(+), 43 deletions(-)
diff --git a/src/backend/access/common/reloptions.c b/src/backend/access/common/reloptions.c
index 0af3fea68f..f543f11001 100644
--- a/src/backend/access/common/reloptions.c
+++ b/src/backend/access/common/reloptions.c
@@ -40,9 +40,9 @@
*
* To add an option:
*
- * (i) decide on a type (bool, integer, real, enum, string), name, default
- * value, upper and lower bounds (if applicable); for strings, consider a
- * validation routine.
+ * (i) decide on a type (bool, ternary, integer, real, enum, string), name,
+ * default value, upper and lower bounds (if applicable); for strings,
+ * consider a validation routine.
* (ii) add a record below (or use add_<type>_reloption).
* (iii) add it to the appropriate options struct (perhaps StdRdOptions)
* (iv) add it to the appropriate handling routine (perhaps
@@ -147,15 +147,6 @@ static relopt_bool boolRelOpts[] =
},
false
},
- {
- {
- "vacuum_truncate",
- "Enables vacuum to truncate empty pages at the end of this table",
- RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
- ShareUpdateExclusiveLock
- },
- true
- },
{
{
"deduplicate_items",
@@ -170,6 +161,21 @@ static relopt_bool boolRelOpts[] =
{{NULL}}
};
+static relopt_ternary ternaryRelOpts[] =
+{
+ {
+ {
+ "vacuum_truncate",
+ "Enables vacuum to truncate empty pages at the end of this table",
+ RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
+ ShareUpdateExclusiveLock
+ },
+ TERNARY_UNSET
+ },
+ /* list terminator */
+ {{NULL}}
+};
+
static relopt_int intRelOpts[] =
{
{
@@ -600,6 +606,13 @@ initialize_reloptions(void)
boolRelOpts[i].gen.lockmode));
j++;
}
+ for (i = 0; ternaryRelOpts[i].gen.name; i++)
+ {
+ Assert(DoLockModesConflict(ternaryRelOpts[i].gen.lockmode,
+ ternaryRelOpts[i].gen.lockmode));
+ j++;
+ }
+
for (i = 0; intRelOpts[i].gen.name; i++)
{
Assert(DoLockModesConflict(intRelOpts[i].gen.lockmode,
@@ -640,6 +653,14 @@ initialize_reloptions(void)
j++;
}
+ for (i = 0; ternaryRelOpts[i].gen.name; i++)
+ {
+ relOpts[j] = &ternaryRelOpts[i].gen;
+ relOpts[j]->type = RELOPT_TYPE_TERNARY;
+ relOpts[j]->namelen = strlen(relOpts[j]->name);
+ j++;
+ }
+
for (i = 0; intRelOpts[i].gen.name; i++)
{
relOpts[j] = &intRelOpts[i].gen;
@@ -800,6 +821,9 @@ allocate_reloption(bits32 kinds, int type, const char *name, const char *desc,
case RELOPT_TYPE_BOOL:
size = sizeof(relopt_bool);
break;
+ case RELOPT_TYPE_TERNARY:
+ size = sizeof(relopt_ternary);
+ break;
case RELOPT_TYPE_INT:
size = sizeof(relopt_int);
break;
@@ -883,6 +907,54 @@ add_local_bool_reloption(local_relopts *relopts, const char *name,
add_local_reloption(relopts, (relopt_gen *) newoption, offset);
}
+/*
+ * init_ternary_reloption
+ * Allocate and initialize a new ternary reloption
+ */
+static relopt_ternary *
+init_ternary_reloption(bits32 kinds, const char *name, const char *desc,
+ ternary default_val, LOCKMODE lockmode)
+{
+ relopt_ternary *newoption;
+
+ newoption = (relopt_ternary *) allocate_reloption(kinds,
+ RELOPT_TYPE_TERNARY, name, desc, lockmode);
+ newoption->default_val = default_val;
+
+ return newoption;
+}
+
+/*
+ * add_ternary_reloption
+ * Add a new ternary reloption
+ */
+void
+add_ternary_reloption(bits32 kinds, const char *name, const char *desc,
+ ternary default_val, LOCKMODE lockmode)
+{
+ relopt_ternary *newoption = init_ternary_reloption(kinds, name, desc,
+ default_val, lockmode);
+
+ add_reloption((relopt_gen *) newoption);
+}
+
+/*
+ * add_local_ternary_reloption
+ * Add a new ternary local reloption
+ *
+ * 'offset' is offset of ternary-typed field.
+ */
+void
+add_local_ternary_reloption(local_relopts *relopts, const char *name,
+ const char *desc, ternary default_val,
+ int offset)
+{
+ relopt_ternary *newoption = init_ternary_reloption(RELOPT_KIND_LOCAL,
+ name, desc,
+ default_val, 0);
+
+ add_local_reloption(relopts, (relopt_gen *) newoption, offset);
+}
/*
* init_real_reloption
@@ -1617,6 +1689,18 @@ parse_one_reloption(relopt_value *option, char *text_str, int text_len,
option->gen->name, value)));
}
break;
+ case RELOPT_TYPE_TERNARY:
+ {
+ bool b;
+ parsed = parse_bool(value, &b);
+ option->values.ternary_val = b ? TERNARY_TRUE : TERNARY_FALSE;
+ if (validate && !parsed)
+ ereport(ERROR,
+ (errcode(ERRCODE_INVALID_PARAMETER_VALUE),
+ errmsg("invalid value for ternary option \"%s\": %s",
+ option->gen->name, value)));
+ }
+ break;
case RELOPT_TYPE_INT:
{
relopt_int *optint = (relopt_int *) option->gen;
@@ -1780,17 +1864,6 @@ fillRelOptions(void *rdopts, Size basesize,
char *itempos = ((char *) rdopts) + elems[j].offset;
char *string_val;
- /*
- * If isset_offset is provided, store whether the reloption is
- * set there.
- */
- if (elems[j].isset_offset > 0)
- {
- char *setpos = ((char *) rdopts) + elems[j].isset_offset;
-
- *(bool *) setpos = options[i].isset;
- }
-
switch (options[i].gen->type)
{
case RELOPT_TYPE_BOOL:
@@ -1798,6 +1871,11 @@ fillRelOptions(void *rdopts, Size basesize,
options[i].values.bool_val :
((relopt_bool *) options[i].gen)->default_val;
break;
+ case RELOPT_TYPE_TERNARY:
+ *(ternary *) itempos = options[i].isset ?
+ options[i].values.ternary_val :
+ ((relopt_ternary *) options[i].gen)->default_val;
+ break;
case RELOPT_TYPE_INT:
*(int *) itempos = options[i].isset ?
options[i].values.int_val :
@@ -1912,8 +1990,8 @@ default_reloptions(Datum reloptions, bool validate, relopt_kind kind)
offsetof(StdRdOptions, parallel_workers)},
{"vacuum_index_cleanup", RELOPT_TYPE_ENUM,
offsetof(StdRdOptions, vacuum_index_cleanup)},
- {"vacuum_truncate", RELOPT_TYPE_BOOL,
- offsetof(StdRdOptions, vacuum_truncate), offsetof(StdRdOptions, vacuum_truncate_set)},
+ {"vacuum_truncate", RELOPT_TYPE_TERNARY,
+ offsetof(StdRdOptions, vacuum_truncate)},
{"vacuum_max_eager_freeze_failure_rate", RELOPT_TYPE_REAL,
offsetof(StdRdOptions, vacuum_max_eager_freeze_failure_rate)}
};
@@ -1993,7 +2071,6 @@ build_local_reloptions(local_relopts *relopts, Datum options, bool validate)
elems[i].optname = opt->option->name;
elems[i].opttype = opt->option->type;
elems[i].offset = opt->offset;
- elems[i].isset_offset = 0; /* not supported for local relopts yet */
i++;
}
diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c
index 733ef40ae7..977babff54 100644
--- a/src/backend/commands/vacuum.c
+++ b/src/backend/commands/vacuum.c
@@ -2221,9 +2221,9 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams params,
{
StdRdOptions *opts = (StdRdOptions *) rel->rd_options;
- if (opts && opts->vacuum_truncate_set)
+ if (opts && opts->vacuum_truncate != TERNARY_UNSET)
{
- if (opts->vacuum_truncate)
+ if (opts->vacuum_truncate == TERNARY_TRUE)
params.truncate = VACOPTVALUE_ENABLED;
else
params.truncate = VACOPTVALUE_DISABLED;
diff --git a/src/include/access/reloptions.h b/src/include/access/reloptions.h
index a604a4702c..a436697658 100644
--- a/src/include/access/reloptions.h
+++ b/src/include/access/reloptions.h
@@ -29,6 +29,7 @@
typedef enum relopt_type
{
RELOPT_TYPE_BOOL,
+ RELOPT_TYPE_TERNARY, /* on, off, unset */
RELOPT_TYPE_INT,
RELOPT_TYPE_REAL,
RELOPT_TYPE_ENUM,
@@ -80,6 +81,7 @@ typedef struct relopt_value
union
{
bool bool_val;
+ ternary ternary_val;
int int_val;
double real_val;
int enum_val;
@@ -94,6 +96,12 @@ typedef struct relopt_bool
bool default_val;
} relopt_bool;
+typedef struct relopt_rernary
+{
+ relopt_gen gen;
+ int default_val;
+} relopt_ternary;
+
typedef struct relopt_int
{
relopt_gen gen;
@@ -152,19 +160,6 @@ typedef struct
const char *optname; /* option's name */
relopt_type opttype; /* option's datatype */
int offset; /* offset of field in result struct */
-
- /*
- * isset_offset is an optional offset of a field in the result struct that
- * stores whether the option is explicitly set for the relation or if it
- * just picked up the default value. In most cases, this can be
- * accomplished by giving the reloption a special out-of-range default
- * value (e.g., some integer reloptions use -2), but this isn't always
- * possible. For example, a Boolean reloption cannot be given an
- * out-of-range default, so we need another way to discover the source of
- * its value. This offset is only used if given a value greater than
- * zero.
- */
- int isset_offset;
} relopt_parse_elt;
/* Local reloption definition */
@@ -195,6 +190,8 @@ typedef struct local_relopts
extern relopt_kind add_reloption_kind(void);
extern void add_bool_reloption(bits32 kinds, const char *name, const char *desc,
bool default_val, LOCKMODE lockmode);
+extern void add_ternary_reloption(bits32 kinds, const char *name,
+ const char *desc, int default_val, LOCKMODE lockmode);
extern void add_int_reloption(bits32 kinds, const char *name, const char *desc,
int default_val, int min_val, int max_val,
LOCKMODE lockmode);
@@ -214,6 +211,9 @@ extern void register_reloptions_validator(local_relopts *relopts,
extern void add_local_bool_reloption(local_relopts *relopts, const char *name,
const char *desc, bool default_val,
int offset);
+extern void add_local_ternary_reloption(local_relopts *relopts,
+ const char *name, const char *desc,
+ ternary default_val, int offset);
extern void add_local_int_reloption(local_relopts *relopts, const char *name,
const char *desc, int default_val,
int min_val, int max_val, int offset);
diff --git a/src/include/c.h b/src/include/c.h
index 39022f8a9d..9c59ccccf8 100644
--- a/src/include/c.h
+++ b/src/include/c.h
@@ -488,6 +488,22 @@ typedef void (*pg_funcptr_t) (void);
#include <stdbool.h>
+/*
+ * ternary
+ * Boolean value with an extrea "unset" option
+ *
+ * Ternary data type is used in relation options that can be "true", "false" or
+ * "unset". Since relation options are used deep inside the PostgreSQL code,
+ * this type is declared globally.
+*/
+
+typedef enum ternary
+{
+ TERNARY_FALSE = 0,
+ TERNARY_TRUE = 1,
+ TERNARY_UNSET = -1
+} ternary;
+
/* ----------------------------------------------------------------
* Section 3: standard system types
diff --git a/src/include/utils/rel.h b/src/include/utils/rel.h
index b552359915..7346d618f9 100644
--- a/src/include/utils/rel.h
+++ b/src/include/utils/rel.h
@@ -346,8 +346,7 @@ typedef struct StdRdOptions
bool user_catalog_table; /* use as an additional catalog relation */
int parallel_workers; /* max number of parallel workers */
StdRdOptIndexCleanup vacuum_index_cleanup; /* controls index vacuuming */
- bool vacuum_truncate; /* enables vacuum to truncate a relation */
- bool vacuum_truncate_set; /* whether vacuum_truncate is set */
+ ternary vacuum_truncate; /* enables vacuum to truncate a relation */
/*
* Fraction of pages in a relation that vacuum can eagerly scan and fail
--
2.39.2
--nextPart5129932.5fSG56mABF
Content-Disposition: attachment;
filename="v2-0003-Add-alias-to-be-used-as-unset-state.patch"
Content-Transfer-Encoding: 7Bit
Content-Type: text/x-patch; charset="unicode-2-0-utf-8";
name="v2-0003-Add-alias-to-be-used-as-unset-state.patch"
^ permalink raw reply [nested|flat] 868+ messages in thread
* [PATCH v2a 2/4] Introduce ternary reloptions
@ 2025-09-04 14:41 Nikolay Shaplov <[email protected]>
0 siblings, 0 replies; 868+ messages in thread
From: Nikolay Shaplov @ 2025-09-04 14:41 UTC (permalink / raw)
Introduce ternary reloption as a replacement for current `vacuum_truncate`
implementation. Remove `vacuum_truncate_set` additional flag and using
`TERNARY_UNSET` value instead.
---
src/backend/access/common/reloptions.c | 129 ++++++++++++++++++++-----
src/backend/commands/vacuum.c | 4 +-
src/include/access/reloptions.h | 26 ++---
src/include/c.h | 16 +++
src/include/utils/rel.h | 3 +-
5 files changed, 135 insertions(+), 43 deletions(-)
diff --git a/src/backend/access/common/reloptions.c b/src/backend/access/common/reloptions.c
index 0af3fea68f..f543f11001 100644
--- a/src/backend/access/common/reloptions.c
+++ b/src/backend/access/common/reloptions.c
@@ -40,9 +40,9 @@
*
* To add an option:
*
- * (i) decide on a type (bool, integer, real, enum, string), name, default
- * value, upper and lower bounds (if applicable); for strings, consider a
- * validation routine.
+ * (i) decide on a type (bool, ternary, integer, real, enum, string), name,
+ * default value, upper and lower bounds (if applicable); for strings,
+ * consider a validation routine.
* (ii) add a record below (or use add_<type>_reloption).
* (iii) add it to the appropriate options struct (perhaps StdRdOptions)
* (iv) add it to the appropriate handling routine (perhaps
@@ -147,15 +147,6 @@ static relopt_bool boolRelOpts[] =
},
false
},
- {
- {
- "vacuum_truncate",
- "Enables vacuum to truncate empty pages at the end of this table",
- RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
- ShareUpdateExclusiveLock
- },
- true
- },
{
{
"deduplicate_items",
@@ -170,6 +161,21 @@ static relopt_bool boolRelOpts[] =
{{NULL}}
};
+static relopt_ternary ternaryRelOpts[] =
+{
+ {
+ {
+ "vacuum_truncate",
+ "Enables vacuum to truncate empty pages at the end of this table",
+ RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
+ ShareUpdateExclusiveLock
+ },
+ TERNARY_UNSET
+ },
+ /* list terminator */
+ {{NULL}}
+};
+
static relopt_int intRelOpts[] =
{
{
@@ -600,6 +606,13 @@ initialize_reloptions(void)
boolRelOpts[i].gen.lockmode));
j++;
}
+ for (i = 0; ternaryRelOpts[i].gen.name; i++)
+ {
+ Assert(DoLockModesConflict(ternaryRelOpts[i].gen.lockmode,
+ ternaryRelOpts[i].gen.lockmode));
+ j++;
+ }
+
for (i = 0; intRelOpts[i].gen.name; i++)
{
Assert(DoLockModesConflict(intRelOpts[i].gen.lockmode,
@@ -640,6 +653,14 @@ initialize_reloptions(void)
j++;
}
+ for (i = 0; ternaryRelOpts[i].gen.name; i++)
+ {
+ relOpts[j] = &ternaryRelOpts[i].gen;
+ relOpts[j]->type = RELOPT_TYPE_TERNARY;
+ relOpts[j]->namelen = strlen(relOpts[j]->name);
+ j++;
+ }
+
for (i = 0; intRelOpts[i].gen.name; i++)
{
relOpts[j] = &intRelOpts[i].gen;
@@ -800,6 +821,9 @@ allocate_reloption(bits32 kinds, int type, const char *name, const char *desc,
case RELOPT_TYPE_BOOL:
size = sizeof(relopt_bool);
break;
+ case RELOPT_TYPE_TERNARY:
+ size = sizeof(relopt_ternary);
+ break;
case RELOPT_TYPE_INT:
size = sizeof(relopt_int);
break;
@@ -883,6 +907,54 @@ add_local_bool_reloption(local_relopts *relopts, const char *name,
add_local_reloption(relopts, (relopt_gen *) newoption, offset);
}
+/*
+ * init_ternary_reloption
+ * Allocate and initialize a new ternary reloption
+ */
+static relopt_ternary *
+init_ternary_reloption(bits32 kinds, const char *name, const char *desc,
+ ternary default_val, LOCKMODE lockmode)
+{
+ relopt_ternary *newoption;
+
+ newoption = (relopt_ternary *) allocate_reloption(kinds,
+ RELOPT_TYPE_TERNARY, name, desc, lockmode);
+ newoption->default_val = default_val;
+
+ return newoption;
+}
+
+/*
+ * add_ternary_reloption
+ * Add a new ternary reloption
+ */
+void
+add_ternary_reloption(bits32 kinds, const char *name, const char *desc,
+ ternary default_val, LOCKMODE lockmode)
+{
+ relopt_ternary *newoption = init_ternary_reloption(kinds, name, desc,
+ default_val, lockmode);
+
+ add_reloption((relopt_gen *) newoption);
+}
+
+/*
+ * add_local_ternary_reloption
+ * Add a new ternary local reloption
+ *
+ * 'offset' is offset of ternary-typed field.
+ */
+void
+add_local_ternary_reloption(local_relopts *relopts, const char *name,
+ const char *desc, ternary default_val,
+ int offset)
+{
+ relopt_ternary *newoption = init_ternary_reloption(RELOPT_KIND_LOCAL,
+ name, desc,
+ default_val, 0);
+
+ add_local_reloption(relopts, (relopt_gen *) newoption, offset);
+}
/*
* init_real_reloption
@@ -1617,6 +1689,18 @@ parse_one_reloption(relopt_value *option, char *text_str, int text_len,
option->gen->name, value)));
}
break;
+ case RELOPT_TYPE_TERNARY:
+ {
+ bool b;
+ parsed = parse_bool(value, &b);
+ option->values.ternary_val = b ? TERNARY_TRUE : TERNARY_FALSE;
+ if (validate && !parsed)
+ ereport(ERROR,
+ (errcode(ERRCODE_INVALID_PARAMETER_VALUE),
+ errmsg("invalid value for ternary option \"%s\": %s",
+ option->gen->name, value)));
+ }
+ break;
case RELOPT_TYPE_INT:
{
relopt_int *optint = (relopt_int *) option->gen;
@@ -1780,17 +1864,6 @@ fillRelOptions(void *rdopts, Size basesize,
char *itempos = ((char *) rdopts) + elems[j].offset;
char *string_val;
- /*
- * If isset_offset is provided, store whether the reloption is
- * set there.
- */
- if (elems[j].isset_offset > 0)
- {
- char *setpos = ((char *) rdopts) + elems[j].isset_offset;
-
- *(bool *) setpos = options[i].isset;
- }
-
switch (options[i].gen->type)
{
case RELOPT_TYPE_BOOL:
@@ -1798,6 +1871,11 @@ fillRelOptions(void *rdopts, Size basesize,
options[i].values.bool_val :
((relopt_bool *) options[i].gen)->default_val;
break;
+ case RELOPT_TYPE_TERNARY:
+ *(ternary *) itempos = options[i].isset ?
+ options[i].values.ternary_val :
+ ((relopt_ternary *) options[i].gen)->default_val;
+ break;
case RELOPT_TYPE_INT:
*(int *) itempos = options[i].isset ?
options[i].values.int_val :
@@ -1912,8 +1990,8 @@ default_reloptions(Datum reloptions, bool validate, relopt_kind kind)
offsetof(StdRdOptions, parallel_workers)},
{"vacuum_index_cleanup", RELOPT_TYPE_ENUM,
offsetof(StdRdOptions, vacuum_index_cleanup)},
- {"vacuum_truncate", RELOPT_TYPE_BOOL,
- offsetof(StdRdOptions, vacuum_truncate), offsetof(StdRdOptions, vacuum_truncate_set)},
+ {"vacuum_truncate", RELOPT_TYPE_TERNARY,
+ offsetof(StdRdOptions, vacuum_truncate)},
{"vacuum_max_eager_freeze_failure_rate", RELOPT_TYPE_REAL,
offsetof(StdRdOptions, vacuum_max_eager_freeze_failure_rate)}
};
@@ -1993,7 +2071,6 @@ build_local_reloptions(local_relopts *relopts, Datum options, bool validate)
elems[i].optname = opt->option->name;
elems[i].opttype = opt->option->type;
elems[i].offset = opt->offset;
- elems[i].isset_offset = 0; /* not supported for local relopts yet */
i++;
}
diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c
index 733ef40ae7..977babff54 100644
--- a/src/backend/commands/vacuum.c
+++ b/src/backend/commands/vacuum.c
@@ -2221,9 +2221,9 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams params,
{
StdRdOptions *opts = (StdRdOptions *) rel->rd_options;
- if (opts && opts->vacuum_truncate_set)
+ if (opts && opts->vacuum_truncate != TERNARY_UNSET)
{
- if (opts->vacuum_truncate)
+ if (opts->vacuum_truncate == TERNARY_TRUE)
params.truncate = VACOPTVALUE_ENABLED;
else
params.truncate = VACOPTVALUE_DISABLED;
diff --git a/src/include/access/reloptions.h b/src/include/access/reloptions.h
index a604a4702c..a436697658 100644
--- a/src/include/access/reloptions.h
+++ b/src/include/access/reloptions.h
@@ -29,6 +29,7 @@
typedef enum relopt_type
{
RELOPT_TYPE_BOOL,
+ RELOPT_TYPE_TERNARY, /* on, off, unset */
RELOPT_TYPE_INT,
RELOPT_TYPE_REAL,
RELOPT_TYPE_ENUM,
@@ -80,6 +81,7 @@ typedef struct relopt_value
union
{
bool bool_val;
+ ternary ternary_val;
int int_val;
double real_val;
int enum_val;
@@ -94,6 +96,12 @@ typedef struct relopt_bool
bool default_val;
} relopt_bool;
+typedef struct relopt_rernary
+{
+ relopt_gen gen;
+ int default_val;
+} relopt_ternary;
+
typedef struct relopt_int
{
relopt_gen gen;
@@ -152,19 +160,6 @@ typedef struct
const char *optname; /* option's name */
relopt_type opttype; /* option's datatype */
int offset; /* offset of field in result struct */
-
- /*
- * isset_offset is an optional offset of a field in the result struct that
- * stores whether the option is explicitly set for the relation or if it
- * just picked up the default value. In most cases, this can be
- * accomplished by giving the reloption a special out-of-range default
- * value (e.g., some integer reloptions use -2), but this isn't always
- * possible. For example, a Boolean reloption cannot be given an
- * out-of-range default, so we need another way to discover the source of
- * its value. This offset is only used if given a value greater than
- * zero.
- */
- int isset_offset;
} relopt_parse_elt;
/* Local reloption definition */
@@ -195,6 +190,8 @@ typedef struct local_relopts
extern relopt_kind add_reloption_kind(void);
extern void add_bool_reloption(bits32 kinds, const char *name, const char *desc,
bool default_val, LOCKMODE lockmode);
+extern void add_ternary_reloption(bits32 kinds, const char *name,
+ const char *desc, int default_val, LOCKMODE lockmode);
extern void add_int_reloption(bits32 kinds, const char *name, const char *desc,
int default_val, int min_val, int max_val,
LOCKMODE lockmode);
@@ -214,6 +211,9 @@ extern void register_reloptions_validator(local_relopts *relopts,
extern void add_local_bool_reloption(local_relopts *relopts, const char *name,
const char *desc, bool default_val,
int offset);
+extern void add_local_ternary_reloption(local_relopts *relopts,
+ const char *name, const char *desc,
+ ternary default_val, int offset);
extern void add_local_int_reloption(local_relopts *relopts, const char *name,
const char *desc, int default_val,
int min_val, int max_val, int offset);
diff --git a/src/include/c.h b/src/include/c.h
index 39022f8a9d..9c59ccccf8 100644
--- a/src/include/c.h
+++ b/src/include/c.h
@@ -488,6 +488,22 @@ typedef void (*pg_funcptr_t) (void);
#include <stdbool.h>
+/*
+ * ternary
+ * Boolean value with an extrea "unset" option
+ *
+ * Ternary data type is used in relation options that can be "true", "false" or
+ * "unset". Since relation options are used deep inside the PostgreSQL code,
+ * this type is declared globally.
+*/
+
+typedef enum ternary
+{
+ TERNARY_FALSE = 0,
+ TERNARY_TRUE = 1,
+ TERNARY_UNSET = -1
+} ternary;
+
/* ----------------------------------------------------------------
* Section 3: standard system types
diff --git a/src/include/utils/rel.h b/src/include/utils/rel.h
index b552359915..7346d618f9 100644
--- a/src/include/utils/rel.h
+++ b/src/include/utils/rel.h
@@ -346,8 +346,7 @@ typedef struct StdRdOptions
bool user_catalog_table; /* use as an additional catalog relation */
int parallel_workers; /* max number of parallel workers */
StdRdOptIndexCleanup vacuum_index_cleanup; /* controls index vacuuming */
- bool vacuum_truncate; /* enables vacuum to truncate a relation */
- bool vacuum_truncate_set; /* whether vacuum_truncate is set */
+ ternary vacuum_truncate; /* enables vacuum to truncate a relation */
/*
* Fraction of pages in a relation that vacuum can eagerly scan and fail
--
2.39.2
--nextPart4155600.3ZeAukHxDK
Content-Disposition: attachment;
filename="v2a-0003-Add-alias-to-be-used-as-unset-state.patch"
Content-Transfer-Encoding: 7Bit
Content-Type: text/x-patch; charset="unicode-2-0-utf-8";
name="v2a-0003-Add-alias-to-be-used-as-unset-state.patch"
^ permalink raw reply [nested|flat] 868+ messages in thread
* [PATCH v2 2/4] Introduce ternary reloptions
@ 2025-09-04 14:41 Nikolay Shaplov <[email protected]>
0 siblings, 0 replies; 868+ messages in thread
From: Nikolay Shaplov @ 2025-09-04 14:41 UTC (permalink / raw)
Introduce ternary reloption as a replacement for current `vacuum_truncate`
implementation. Remove `vacuum_truncate_set` additional flag and using
`TERNARY_UNSET` value instead.
---
src/backend/access/common/reloptions.c | 129 ++++++++++++++++++++-----
src/backend/commands/vacuum.c | 4 +-
src/include/access/reloptions.h | 26 ++---
src/include/c.h | 16 +++
src/include/utils/rel.h | 3 +-
5 files changed, 135 insertions(+), 43 deletions(-)
diff --git a/src/backend/access/common/reloptions.c b/src/backend/access/common/reloptions.c
index 0af3fea68f..f543f11001 100644
--- a/src/backend/access/common/reloptions.c
+++ b/src/backend/access/common/reloptions.c
@@ -40,9 +40,9 @@
*
* To add an option:
*
- * (i) decide on a type (bool, integer, real, enum, string), name, default
- * value, upper and lower bounds (if applicable); for strings, consider a
- * validation routine.
+ * (i) decide on a type (bool, ternary, integer, real, enum, string), name,
+ * default value, upper and lower bounds (if applicable); for strings,
+ * consider a validation routine.
* (ii) add a record below (or use add_<type>_reloption).
* (iii) add it to the appropriate options struct (perhaps StdRdOptions)
* (iv) add it to the appropriate handling routine (perhaps
@@ -147,15 +147,6 @@ static relopt_bool boolRelOpts[] =
},
false
},
- {
- {
- "vacuum_truncate",
- "Enables vacuum to truncate empty pages at the end of this table",
- RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
- ShareUpdateExclusiveLock
- },
- true
- },
{
{
"deduplicate_items",
@@ -170,6 +161,21 @@ static relopt_bool boolRelOpts[] =
{{NULL}}
};
+static relopt_ternary ternaryRelOpts[] =
+{
+ {
+ {
+ "vacuum_truncate",
+ "Enables vacuum to truncate empty pages at the end of this table",
+ RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
+ ShareUpdateExclusiveLock
+ },
+ TERNARY_UNSET
+ },
+ /* list terminator */
+ {{NULL}}
+};
+
static relopt_int intRelOpts[] =
{
{
@@ -600,6 +606,13 @@ initialize_reloptions(void)
boolRelOpts[i].gen.lockmode));
j++;
}
+ for (i = 0; ternaryRelOpts[i].gen.name; i++)
+ {
+ Assert(DoLockModesConflict(ternaryRelOpts[i].gen.lockmode,
+ ternaryRelOpts[i].gen.lockmode));
+ j++;
+ }
+
for (i = 0; intRelOpts[i].gen.name; i++)
{
Assert(DoLockModesConflict(intRelOpts[i].gen.lockmode,
@@ -640,6 +653,14 @@ initialize_reloptions(void)
j++;
}
+ for (i = 0; ternaryRelOpts[i].gen.name; i++)
+ {
+ relOpts[j] = &ternaryRelOpts[i].gen;
+ relOpts[j]->type = RELOPT_TYPE_TERNARY;
+ relOpts[j]->namelen = strlen(relOpts[j]->name);
+ j++;
+ }
+
for (i = 0; intRelOpts[i].gen.name; i++)
{
relOpts[j] = &intRelOpts[i].gen;
@@ -800,6 +821,9 @@ allocate_reloption(bits32 kinds, int type, const char *name, const char *desc,
case RELOPT_TYPE_BOOL:
size = sizeof(relopt_bool);
break;
+ case RELOPT_TYPE_TERNARY:
+ size = sizeof(relopt_ternary);
+ break;
case RELOPT_TYPE_INT:
size = sizeof(relopt_int);
break;
@@ -883,6 +907,54 @@ add_local_bool_reloption(local_relopts *relopts, const char *name,
add_local_reloption(relopts, (relopt_gen *) newoption, offset);
}
+/*
+ * init_ternary_reloption
+ * Allocate and initialize a new ternary reloption
+ */
+static relopt_ternary *
+init_ternary_reloption(bits32 kinds, const char *name, const char *desc,
+ ternary default_val, LOCKMODE lockmode)
+{
+ relopt_ternary *newoption;
+
+ newoption = (relopt_ternary *) allocate_reloption(kinds,
+ RELOPT_TYPE_TERNARY, name, desc, lockmode);
+ newoption->default_val = default_val;
+
+ return newoption;
+}
+
+/*
+ * add_ternary_reloption
+ * Add a new ternary reloption
+ */
+void
+add_ternary_reloption(bits32 kinds, const char *name, const char *desc,
+ ternary default_val, LOCKMODE lockmode)
+{
+ relopt_ternary *newoption = init_ternary_reloption(kinds, name, desc,
+ default_val, lockmode);
+
+ add_reloption((relopt_gen *) newoption);
+}
+
+/*
+ * add_local_ternary_reloption
+ * Add a new ternary local reloption
+ *
+ * 'offset' is offset of ternary-typed field.
+ */
+void
+add_local_ternary_reloption(local_relopts *relopts, const char *name,
+ const char *desc, ternary default_val,
+ int offset)
+{
+ relopt_ternary *newoption = init_ternary_reloption(RELOPT_KIND_LOCAL,
+ name, desc,
+ default_val, 0);
+
+ add_local_reloption(relopts, (relopt_gen *) newoption, offset);
+}
/*
* init_real_reloption
@@ -1617,6 +1689,18 @@ parse_one_reloption(relopt_value *option, char *text_str, int text_len,
option->gen->name, value)));
}
break;
+ case RELOPT_TYPE_TERNARY:
+ {
+ bool b;
+ parsed = parse_bool(value, &b);
+ option->values.ternary_val = b ? TERNARY_TRUE : TERNARY_FALSE;
+ if (validate && !parsed)
+ ereport(ERROR,
+ (errcode(ERRCODE_INVALID_PARAMETER_VALUE),
+ errmsg("invalid value for ternary option \"%s\": %s",
+ option->gen->name, value)));
+ }
+ break;
case RELOPT_TYPE_INT:
{
relopt_int *optint = (relopt_int *) option->gen;
@@ -1780,17 +1864,6 @@ fillRelOptions(void *rdopts, Size basesize,
char *itempos = ((char *) rdopts) + elems[j].offset;
char *string_val;
- /*
- * If isset_offset is provided, store whether the reloption is
- * set there.
- */
- if (elems[j].isset_offset > 0)
- {
- char *setpos = ((char *) rdopts) + elems[j].isset_offset;
-
- *(bool *) setpos = options[i].isset;
- }
-
switch (options[i].gen->type)
{
case RELOPT_TYPE_BOOL:
@@ -1798,6 +1871,11 @@ fillRelOptions(void *rdopts, Size basesize,
options[i].values.bool_val :
((relopt_bool *) options[i].gen)->default_val;
break;
+ case RELOPT_TYPE_TERNARY:
+ *(ternary *) itempos = options[i].isset ?
+ options[i].values.ternary_val :
+ ((relopt_ternary *) options[i].gen)->default_val;
+ break;
case RELOPT_TYPE_INT:
*(int *) itempos = options[i].isset ?
options[i].values.int_val :
@@ -1912,8 +1990,8 @@ default_reloptions(Datum reloptions, bool validate, relopt_kind kind)
offsetof(StdRdOptions, parallel_workers)},
{"vacuum_index_cleanup", RELOPT_TYPE_ENUM,
offsetof(StdRdOptions, vacuum_index_cleanup)},
- {"vacuum_truncate", RELOPT_TYPE_BOOL,
- offsetof(StdRdOptions, vacuum_truncate), offsetof(StdRdOptions, vacuum_truncate_set)},
+ {"vacuum_truncate", RELOPT_TYPE_TERNARY,
+ offsetof(StdRdOptions, vacuum_truncate)},
{"vacuum_max_eager_freeze_failure_rate", RELOPT_TYPE_REAL,
offsetof(StdRdOptions, vacuum_max_eager_freeze_failure_rate)}
};
@@ -1993,7 +2071,6 @@ build_local_reloptions(local_relopts *relopts, Datum options, bool validate)
elems[i].optname = opt->option->name;
elems[i].opttype = opt->option->type;
elems[i].offset = opt->offset;
- elems[i].isset_offset = 0; /* not supported for local relopts yet */
i++;
}
diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c
index 733ef40ae7..977babff54 100644
--- a/src/backend/commands/vacuum.c
+++ b/src/backend/commands/vacuum.c
@@ -2221,9 +2221,9 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams params,
{
StdRdOptions *opts = (StdRdOptions *) rel->rd_options;
- if (opts && opts->vacuum_truncate_set)
+ if (opts && opts->vacuum_truncate != TERNARY_UNSET)
{
- if (opts->vacuum_truncate)
+ if (opts->vacuum_truncate == TERNARY_TRUE)
params.truncate = VACOPTVALUE_ENABLED;
else
params.truncate = VACOPTVALUE_DISABLED;
diff --git a/src/include/access/reloptions.h b/src/include/access/reloptions.h
index a604a4702c..a436697658 100644
--- a/src/include/access/reloptions.h
+++ b/src/include/access/reloptions.h
@@ -29,6 +29,7 @@
typedef enum relopt_type
{
RELOPT_TYPE_BOOL,
+ RELOPT_TYPE_TERNARY, /* on, off, unset */
RELOPT_TYPE_INT,
RELOPT_TYPE_REAL,
RELOPT_TYPE_ENUM,
@@ -80,6 +81,7 @@ typedef struct relopt_value
union
{
bool bool_val;
+ ternary ternary_val;
int int_val;
double real_val;
int enum_val;
@@ -94,6 +96,12 @@ typedef struct relopt_bool
bool default_val;
} relopt_bool;
+typedef struct relopt_rernary
+{
+ relopt_gen gen;
+ int default_val;
+} relopt_ternary;
+
typedef struct relopt_int
{
relopt_gen gen;
@@ -152,19 +160,6 @@ typedef struct
const char *optname; /* option's name */
relopt_type opttype; /* option's datatype */
int offset; /* offset of field in result struct */
-
- /*
- * isset_offset is an optional offset of a field in the result struct that
- * stores whether the option is explicitly set for the relation or if it
- * just picked up the default value. In most cases, this can be
- * accomplished by giving the reloption a special out-of-range default
- * value (e.g., some integer reloptions use -2), but this isn't always
- * possible. For example, a Boolean reloption cannot be given an
- * out-of-range default, so we need another way to discover the source of
- * its value. This offset is only used if given a value greater than
- * zero.
- */
- int isset_offset;
} relopt_parse_elt;
/* Local reloption definition */
@@ -195,6 +190,8 @@ typedef struct local_relopts
extern relopt_kind add_reloption_kind(void);
extern void add_bool_reloption(bits32 kinds, const char *name, const char *desc,
bool default_val, LOCKMODE lockmode);
+extern void add_ternary_reloption(bits32 kinds, const char *name,
+ const char *desc, int default_val, LOCKMODE lockmode);
extern void add_int_reloption(bits32 kinds, const char *name, const char *desc,
int default_val, int min_val, int max_val,
LOCKMODE lockmode);
@@ -214,6 +211,9 @@ extern void register_reloptions_validator(local_relopts *relopts,
extern void add_local_bool_reloption(local_relopts *relopts, const char *name,
const char *desc, bool default_val,
int offset);
+extern void add_local_ternary_reloption(local_relopts *relopts,
+ const char *name, const char *desc,
+ ternary default_val, int offset);
extern void add_local_int_reloption(local_relopts *relopts, const char *name,
const char *desc, int default_val,
int min_val, int max_val, int offset);
diff --git a/src/include/c.h b/src/include/c.h
index 39022f8a9d..9c59ccccf8 100644
--- a/src/include/c.h
+++ b/src/include/c.h
@@ -488,6 +488,22 @@ typedef void (*pg_funcptr_t) (void);
#include <stdbool.h>
+/*
+ * ternary
+ * Boolean value with an extrea "unset" option
+ *
+ * Ternary data type is used in relation options that can be "true", "false" or
+ * "unset". Since relation options are used deep inside the PostgreSQL code,
+ * this type is declared globally.
+*/
+
+typedef enum ternary
+{
+ TERNARY_FALSE = 0,
+ TERNARY_TRUE = 1,
+ TERNARY_UNSET = -1
+} ternary;
+
/* ----------------------------------------------------------------
* Section 3: standard system types
diff --git a/src/include/utils/rel.h b/src/include/utils/rel.h
index b552359915..7346d618f9 100644
--- a/src/include/utils/rel.h
+++ b/src/include/utils/rel.h
@@ -346,8 +346,7 @@ typedef struct StdRdOptions
bool user_catalog_table; /* use as an additional catalog relation */
int parallel_workers; /* max number of parallel workers */
StdRdOptIndexCleanup vacuum_index_cleanup; /* controls index vacuuming */
- bool vacuum_truncate; /* enables vacuum to truncate a relation */
- bool vacuum_truncate_set; /* whether vacuum_truncate is set */
+ ternary vacuum_truncate; /* enables vacuum to truncate a relation */
/*
* Fraction of pages in a relation that vacuum can eagerly scan and fail
--
2.39.2
--nextPart5129932.5fSG56mABF
Content-Disposition: attachment;
filename="v2-0003-Add-alias-to-be-used-as-unset-state.patch"
Content-Transfer-Encoding: 7Bit
Content-Type: text/x-patch; charset="unicode-2-0-utf-8";
name="v2-0003-Add-alias-to-be-used-as-unset-state.patch"
^ permalink raw reply [nested|flat] 868+ messages in thread
* [PATCH v2a 2/4] Introduce ternary reloptions
@ 2025-09-04 14:41 Nikolay Shaplov <[email protected]>
0 siblings, 0 replies; 868+ messages in thread
From: Nikolay Shaplov @ 2025-09-04 14:41 UTC (permalink / raw)
Introduce ternary reloption as a replacement for current `vacuum_truncate`
implementation. Remove `vacuum_truncate_set` additional flag and using
`TERNARY_UNSET` value instead.
---
src/backend/access/common/reloptions.c | 129 ++++++++++++++++++++-----
src/backend/commands/vacuum.c | 4 +-
src/include/access/reloptions.h | 26 ++---
src/include/c.h | 16 +++
src/include/utils/rel.h | 3 +-
5 files changed, 135 insertions(+), 43 deletions(-)
diff --git a/src/backend/access/common/reloptions.c b/src/backend/access/common/reloptions.c
index 0af3fea68f..f543f11001 100644
--- a/src/backend/access/common/reloptions.c
+++ b/src/backend/access/common/reloptions.c
@@ -40,9 +40,9 @@
*
* To add an option:
*
- * (i) decide on a type (bool, integer, real, enum, string), name, default
- * value, upper and lower bounds (if applicable); for strings, consider a
- * validation routine.
+ * (i) decide on a type (bool, ternary, integer, real, enum, string), name,
+ * default value, upper and lower bounds (if applicable); for strings,
+ * consider a validation routine.
* (ii) add a record below (or use add_<type>_reloption).
* (iii) add it to the appropriate options struct (perhaps StdRdOptions)
* (iv) add it to the appropriate handling routine (perhaps
@@ -147,15 +147,6 @@ static relopt_bool boolRelOpts[] =
},
false
},
- {
- {
- "vacuum_truncate",
- "Enables vacuum to truncate empty pages at the end of this table",
- RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
- ShareUpdateExclusiveLock
- },
- true
- },
{
{
"deduplicate_items",
@@ -170,6 +161,21 @@ static relopt_bool boolRelOpts[] =
{{NULL}}
};
+static relopt_ternary ternaryRelOpts[] =
+{
+ {
+ {
+ "vacuum_truncate",
+ "Enables vacuum to truncate empty pages at the end of this table",
+ RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
+ ShareUpdateExclusiveLock
+ },
+ TERNARY_UNSET
+ },
+ /* list terminator */
+ {{NULL}}
+};
+
static relopt_int intRelOpts[] =
{
{
@@ -600,6 +606,13 @@ initialize_reloptions(void)
boolRelOpts[i].gen.lockmode));
j++;
}
+ for (i = 0; ternaryRelOpts[i].gen.name; i++)
+ {
+ Assert(DoLockModesConflict(ternaryRelOpts[i].gen.lockmode,
+ ternaryRelOpts[i].gen.lockmode));
+ j++;
+ }
+
for (i = 0; intRelOpts[i].gen.name; i++)
{
Assert(DoLockModesConflict(intRelOpts[i].gen.lockmode,
@@ -640,6 +653,14 @@ initialize_reloptions(void)
j++;
}
+ for (i = 0; ternaryRelOpts[i].gen.name; i++)
+ {
+ relOpts[j] = &ternaryRelOpts[i].gen;
+ relOpts[j]->type = RELOPT_TYPE_TERNARY;
+ relOpts[j]->namelen = strlen(relOpts[j]->name);
+ j++;
+ }
+
for (i = 0; intRelOpts[i].gen.name; i++)
{
relOpts[j] = &intRelOpts[i].gen;
@@ -800,6 +821,9 @@ allocate_reloption(bits32 kinds, int type, const char *name, const char *desc,
case RELOPT_TYPE_BOOL:
size = sizeof(relopt_bool);
break;
+ case RELOPT_TYPE_TERNARY:
+ size = sizeof(relopt_ternary);
+ break;
case RELOPT_TYPE_INT:
size = sizeof(relopt_int);
break;
@@ -883,6 +907,54 @@ add_local_bool_reloption(local_relopts *relopts, const char *name,
add_local_reloption(relopts, (relopt_gen *) newoption, offset);
}
+/*
+ * init_ternary_reloption
+ * Allocate and initialize a new ternary reloption
+ */
+static relopt_ternary *
+init_ternary_reloption(bits32 kinds, const char *name, const char *desc,
+ ternary default_val, LOCKMODE lockmode)
+{
+ relopt_ternary *newoption;
+
+ newoption = (relopt_ternary *) allocate_reloption(kinds,
+ RELOPT_TYPE_TERNARY, name, desc, lockmode);
+ newoption->default_val = default_val;
+
+ return newoption;
+}
+
+/*
+ * add_ternary_reloption
+ * Add a new ternary reloption
+ */
+void
+add_ternary_reloption(bits32 kinds, const char *name, const char *desc,
+ ternary default_val, LOCKMODE lockmode)
+{
+ relopt_ternary *newoption = init_ternary_reloption(kinds, name, desc,
+ default_val, lockmode);
+
+ add_reloption((relopt_gen *) newoption);
+}
+
+/*
+ * add_local_ternary_reloption
+ * Add a new ternary local reloption
+ *
+ * 'offset' is offset of ternary-typed field.
+ */
+void
+add_local_ternary_reloption(local_relopts *relopts, const char *name,
+ const char *desc, ternary default_val,
+ int offset)
+{
+ relopt_ternary *newoption = init_ternary_reloption(RELOPT_KIND_LOCAL,
+ name, desc,
+ default_val, 0);
+
+ add_local_reloption(relopts, (relopt_gen *) newoption, offset);
+}
/*
* init_real_reloption
@@ -1617,6 +1689,18 @@ parse_one_reloption(relopt_value *option, char *text_str, int text_len,
option->gen->name, value)));
}
break;
+ case RELOPT_TYPE_TERNARY:
+ {
+ bool b;
+ parsed = parse_bool(value, &b);
+ option->values.ternary_val = b ? TERNARY_TRUE : TERNARY_FALSE;
+ if (validate && !parsed)
+ ereport(ERROR,
+ (errcode(ERRCODE_INVALID_PARAMETER_VALUE),
+ errmsg("invalid value for ternary option \"%s\": %s",
+ option->gen->name, value)));
+ }
+ break;
case RELOPT_TYPE_INT:
{
relopt_int *optint = (relopt_int *) option->gen;
@@ -1780,17 +1864,6 @@ fillRelOptions(void *rdopts, Size basesize,
char *itempos = ((char *) rdopts) + elems[j].offset;
char *string_val;
- /*
- * If isset_offset is provided, store whether the reloption is
- * set there.
- */
- if (elems[j].isset_offset > 0)
- {
- char *setpos = ((char *) rdopts) + elems[j].isset_offset;
-
- *(bool *) setpos = options[i].isset;
- }
-
switch (options[i].gen->type)
{
case RELOPT_TYPE_BOOL:
@@ -1798,6 +1871,11 @@ fillRelOptions(void *rdopts, Size basesize,
options[i].values.bool_val :
((relopt_bool *) options[i].gen)->default_val;
break;
+ case RELOPT_TYPE_TERNARY:
+ *(ternary *) itempos = options[i].isset ?
+ options[i].values.ternary_val :
+ ((relopt_ternary *) options[i].gen)->default_val;
+ break;
case RELOPT_TYPE_INT:
*(int *) itempos = options[i].isset ?
options[i].values.int_val :
@@ -1912,8 +1990,8 @@ default_reloptions(Datum reloptions, bool validate, relopt_kind kind)
offsetof(StdRdOptions, parallel_workers)},
{"vacuum_index_cleanup", RELOPT_TYPE_ENUM,
offsetof(StdRdOptions, vacuum_index_cleanup)},
- {"vacuum_truncate", RELOPT_TYPE_BOOL,
- offsetof(StdRdOptions, vacuum_truncate), offsetof(StdRdOptions, vacuum_truncate_set)},
+ {"vacuum_truncate", RELOPT_TYPE_TERNARY,
+ offsetof(StdRdOptions, vacuum_truncate)},
{"vacuum_max_eager_freeze_failure_rate", RELOPT_TYPE_REAL,
offsetof(StdRdOptions, vacuum_max_eager_freeze_failure_rate)}
};
@@ -1993,7 +2071,6 @@ build_local_reloptions(local_relopts *relopts, Datum options, bool validate)
elems[i].optname = opt->option->name;
elems[i].opttype = opt->option->type;
elems[i].offset = opt->offset;
- elems[i].isset_offset = 0; /* not supported for local relopts yet */
i++;
}
diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c
index 733ef40ae7..977babff54 100644
--- a/src/backend/commands/vacuum.c
+++ b/src/backend/commands/vacuum.c
@@ -2221,9 +2221,9 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams params,
{
StdRdOptions *opts = (StdRdOptions *) rel->rd_options;
- if (opts && opts->vacuum_truncate_set)
+ if (opts && opts->vacuum_truncate != TERNARY_UNSET)
{
- if (opts->vacuum_truncate)
+ if (opts->vacuum_truncate == TERNARY_TRUE)
params.truncate = VACOPTVALUE_ENABLED;
else
params.truncate = VACOPTVALUE_DISABLED;
diff --git a/src/include/access/reloptions.h b/src/include/access/reloptions.h
index a604a4702c..a436697658 100644
--- a/src/include/access/reloptions.h
+++ b/src/include/access/reloptions.h
@@ -29,6 +29,7 @@
typedef enum relopt_type
{
RELOPT_TYPE_BOOL,
+ RELOPT_TYPE_TERNARY, /* on, off, unset */
RELOPT_TYPE_INT,
RELOPT_TYPE_REAL,
RELOPT_TYPE_ENUM,
@@ -80,6 +81,7 @@ typedef struct relopt_value
union
{
bool bool_val;
+ ternary ternary_val;
int int_val;
double real_val;
int enum_val;
@@ -94,6 +96,12 @@ typedef struct relopt_bool
bool default_val;
} relopt_bool;
+typedef struct relopt_rernary
+{
+ relopt_gen gen;
+ int default_val;
+} relopt_ternary;
+
typedef struct relopt_int
{
relopt_gen gen;
@@ -152,19 +160,6 @@ typedef struct
const char *optname; /* option's name */
relopt_type opttype; /* option's datatype */
int offset; /* offset of field in result struct */
-
- /*
- * isset_offset is an optional offset of a field in the result struct that
- * stores whether the option is explicitly set for the relation or if it
- * just picked up the default value. In most cases, this can be
- * accomplished by giving the reloption a special out-of-range default
- * value (e.g., some integer reloptions use -2), but this isn't always
- * possible. For example, a Boolean reloption cannot be given an
- * out-of-range default, so we need another way to discover the source of
- * its value. This offset is only used if given a value greater than
- * zero.
- */
- int isset_offset;
} relopt_parse_elt;
/* Local reloption definition */
@@ -195,6 +190,8 @@ typedef struct local_relopts
extern relopt_kind add_reloption_kind(void);
extern void add_bool_reloption(bits32 kinds, const char *name, const char *desc,
bool default_val, LOCKMODE lockmode);
+extern void add_ternary_reloption(bits32 kinds, const char *name,
+ const char *desc, int default_val, LOCKMODE lockmode);
extern void add_int_reloption(bits32 kinds, const char *name, const char *desc,
int default_val, int min_val, int max_val,
LOCKMODE lockmode);
@@ -214,6 +211,9 @@ extern void register_reloptions_validator(local_relopts *relopts,
extern void add_local_bool_reloption(local_relopts *relopts, const char *name,
const char *desc, bool default_val,
int offset);
+extern void add_local_ternary_reloption(local_relopts *relopts,
+ const char *name, const char *desc,
+ ternary default_val, int offset);
extern void add_local_int_reloption(local_relopts *relopts, const char *name,
const char *desc, int default_val,
int min_val, int max_val, int offset);
diff --git a/src/include/c.h b/src/include/c.h
index 39022f8a9d..9c59ccccf8 100644
--- a/src/include/c.h
+++ b/src/include/c.h
@@ -488,6 +488,22 @@ typedef void (*pg_funcptr_t) (void);
#include <stdbool.h>
+/*
+ * ternary
+ * Boolean value with an extrea "unset" option
+ *
+ * Ternary data type is used in relation options that can be "true", "false" or
+ * "unset". Since relation options are used deep inside the PostgreSQL code,
+ * this type is declared globally.
+*/
+
+typedef enum ternary
+{
+ TERNARY_FALSE = 0,
+ TERNARY_TRUE = 1,
+ TERNARY_UNSET = -1
+} ternary;
+
/* ----------------------------------------------------------------
* Section 3: standard system types
diff --git a/src/include/utils/rel.h b/src/include/utils/rel.h
index b552359915..7346d618f9 100644
--- a/src/include/utils/rel.h
+++ b/src/include/utils/rel.h
@@ -346,8 +346,7 @@ typedef struct StdRdOptions
bool user_catalog_table; /* use as an additional catalog relation */
int parallel_workers; /* max number of parallel workers */
StdRdOptIndexCleanup vacuum_index_cleanup; /* controls index vacuuming */
- bool vacuum_truncate; /* enables vacuum to truncate a relation */
- bool vacuum_truncate_set; /* whether vacuum_truncate is set */
+ ternary vacuum_truncate; /* enables vacuum to truncate a relation */
/*
* Fraction of pages in a relation that vacuum can eagerly scan and fail
--
2.39.2
--nextPart4155600.3ZeAukHxDK
Content-Disposition: attachment;
filename="v2a-0003-Add-alias-to-be-used-as-unset-state.patch"
Content-Transfer-Encoding: 7Bit
Content-Type: text/x-patch; charset="unicode-2-0-utf-8";
name="v2a-0003-Add-alias-to-be-used-as-unset-state.patch"
^ permalink raw reply [nested|flat] 868+ messages in thread
* [PATCH v2 2/4] Introduce ternary reloptions
@ 2025-09-04 14:41 Nikolay Shaplov <[email protected]>
0 siblings, 0 replies; 868+ messages in thread
From: Nikolay Shaplov @ 2025-09-04 14:41 UTC (permalink / raw)
Introduce ternary reloption as a replacement for current `vacuum_truncate`
implementation. Remove `vacuum_truncate_set` additional flag and using
`TERNARY_UNSET` value instead.
---
src/backend/access/common/reloptions.c | 129 ++++++++++++++++++++-----
src/backend/commands/vacuum.c | 4 +-
src/include/access/reloptions.h | 26 ++---
src/include/c.h | 16 +++
src/include/utils/rel.h | 3 +-
5 files changed, 135 insertions(+), 43 deletions(-)
diff --git a/src/backend/access/common/reloptions.c b/src/backend/access/common/reloptions.c
index 0af3fea68f..f543f11001 100644
--- a/src/backend/access/common/reloptions.c
+++ b/src/backend/access/common/reloptions.c
@@ -40,9 +40,9 @@
*
* To add an option:
*
- * (i) decide on a type (bool, integer, real, enum, string), name, default
- * value, upper and lower bounds (if applicable); for strings, consider a
- * validation routine.
+ * (i) decide on a type (bool, ternary, integer, real, enum, string), name,
+ * default value, upper and lower bounds (if applicable); for strings,
+ * consider a validation routine.
* (ii) add a record below (or use add_<type>_reloption).
* (iii) add it to the appropriate options struct (perhaps StdRdOptions)
* (iv) add it to the appropriate handling routine (perhaps
@@ -147,15 +147,6 @@ static relopt_bool boolRelOpts[] =
},
false
},
- {
- {
- "vacuum_truncate",
- "Enables vacuum to truncate empty pages at the end of this table",
- RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
- ShareUpdateExclusiveLock
- },
- true
- },
{
{
"deduplicate_items",
@@ -170,6 +161,21 @@ static relopt_bool boolRelOpts[] =
{{NULL}}
};
+static relopt_ternary ternaryRelOpts[] =
+{
+ {
+ {
+ "vacuum_truncate",
+ "Enables vacuum to truncate empty pages at the end of this table",
+ RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
+ ShareUpdateExclusiveLock
+ },
+ TERNARY_UNSET
+ },
+ /* list terminator */
+ {{NULL}}
+};
+
static relopt_int intRelOpts[] =
{
{
@@ -600,6 +606,13 @@ initialize_reloptions(void)
boolRelOpts[i].gen.lockmode));
j++;
}
+ for (i = 0; ternaryRelOpts[i].gen.name; i++)
+ {
+ Assert(DoLockModesConflict(ternaryRelOpts[i].gen.lockmode,
+ ternaryRelOpts[i].gen.lockmode));
+ j++;
+ }
+
for (i = 0; intRelOpts[i].gen.name; i++)
{
Assert(DoLockModesConflict(intRelOpts[i].gen.lockmode,
@@ -640,6 +653,14 @@ initialize_reloptions(void)
j++;
}
+ for (i = 0; ternaryRelOpts[i].gen.name; i++)
+ {
+ relOpts[j] = &ternaryRelOpts[i].gen;
+ relOpts[j]->type = RELOPT_TYPE_TERNARY;
+ relOpts[j]->namelen = strlen(relOpts[j]->name);
+ j++;
+ }
+
for (i = 0; intRelOpts[i].gen.name; i++)
{
relOpts[j] = &intRelOpts[i].gen;
@@ -800,6 +821,9 @@ allocate_reloption(bits32 kinds, int type, const char *name, const char *desc,
case RELOPT_TYPE_BOOL:
size = sizeof(relopt_bool);
break;
+ case RELOPT_TYPE_TERNARY:
+ size = sizeof(relopt_ternary);
+ break;
case RELOPT_TYPE_INT:
size = sizeof(relopt_int);
break;
@@ -883,6 +907,54 @@ add_local_bool_reloption(local_relopts *relopts, const char *name,
add_local_reloption(relopts, (relopt_gen *) newoption, offset);
}
+/*
+ * init_ternary_reloption
+ * Allocate and initialize a new ternary reloption
+ */
+static relopt_ternary *
+init_ternary_reloption(bits32 kinds, const char *name, const char *desc,
+ ternary default_val, LOCKMODE lockmode)
+{
+ relopt_ternary *newoption;
+
+ newoption = (relopt_ternary *) allocate_reloption(kinds,
+ RELOPT_TYPE_TERNARY, name, desc, lockmode);
+ newoption->default_val = default_val;
+
+ return newoption;
+}
+
+/*
+ * add_ternary_reloption
+ * Add a new ternary reloption
+ */
+void
+add_ternary_reloption(bits32 kinds, const char *name, const char *desc,
+ ternary default_val, LOCKMODE lockmode)
+{
+ relopt_ternary *newoption = init_ternary_reloption(kinds, name, desc,
+ default_val, lockmode);
+
+ add_reloption((relopt_gen *) newoption);
+}
+
+/*
+ * add_local_ternary_reloption
+ * Add a new ternary local reloption
+ *
+ * 'offset' is offset of ternary-typed field.
+ */
+void
+add_local_ternary_reloption(local_relopts *relopts, const char *name,
+ const char *desc, ternary default_val,
+ int offset)
+{
+ relopt_ternary *newoption = init_ternary_reloption(RELOPT_KIND_LOCAL,
+ name, desc,
+ default_val, 0);
+
+ add_local_reloption(relopts, (relopt_gen *) newoption, offset);
+}
/*
* init_real_reloption
@@ -1617,6 +1689,18 @@ parse_one_reloption(relopt_value *option, char *text_str, int text_len,
option->gen->name, value)));
}
break;
+ case RELOPT_TYPE_TERNARY:
+ {
+ bool b;
+ parsed = parse_bool(value, &b);
+ option->values.ternary_val = b ? TERNARY_TRUE : TERNARY_FALSE;
+ if (validate && !parsed)
+ ereport(ERROR,
+ (errcode(ERRCODE_INVALID_PARAMETER_VALUE),
+ errmsg("invalid value for ternary option \"%s\": %s",
+ option->gen->name, value)));
+ }
+ break;
case RELOPT_TYPE_INT:
{
relopt_int *optint = (relopt_int *) option->gen;
@@ -1780,17 +1864,6 @@ fillRelOptions(void *rdopts, Size basesize,
char *itempos = ((char *) rdopts) + elems[j].offset;
char *string_val;
- /*
- * If isset_offset is provided, store whether the reloption is
- * set there.
- */
- if (elems[j].isset_offset > 0)
- {
- char *setpos = ((char *) rdopts) + elems[j].isset_offset;
-
- *(bool *) setpos = options[i].isset;
- }
-
switch (options[i].gen->type)
{
case RELOPT_TYPE_BOOL:
@@ -1798,6 +1871,11 @@ fillRelOptions(void *rdopts, Size basesize,
options[i].values.bool_val :
((relopt_bool *) options[i].gen)->default_val;
break;
+ case RELOPT_TYPE_TERNARY:
+ *(ternary *) itempos = options[i].isset ?
+ options[i].values.ternary_val :
+ ((relopt_ternary *) options[i].gen)->default_val;
+ break;
case RELOPT_TYPE_INT:
*(int *) itempos = options[i].isset ?
options[i].values.int_val :
@@ -1912,8 +1990,8 @@ default_reloptions(Datum reloptions, bool validate, relopt_kind kind)
offsetof(StdRdOptions, parallel_workers)},
{"vacuum_index_cleanup", RELOPT_TYPE_ENUM,
offsetof(StdRdOptions, vacuum_index_cleanup)},
- {"vacuum_truncate", RELOPT_TYPE_BOOL,
- offsetof(StdRdOptions, vacuum_truncate), offsetof(StdRdOptions, vacuum_truncate_set)},
+ {"vacuum_truncate", RELOPT_TYPE_TERNARY,
+ offsetof(StdRdOptions, vacuum_truncate)},
{"vacuum_max_eager_freeze_failure_rate", RELOPT_TYPE_REAL,
offsetof(StdRdOptions, vacuum_max_eager_freeze_failure_rate)}
};
@@ -1993,7 +2071,6 @@ build_local_reloptions(local_relopts *relopts, Datum options, bool validate)
elems[i].optname = opt->option->name;
elems[i].opttype = opt->option->type;
elems[i].offset = opt->offset;
- elems[i].isset_offset = 0; /* not supported for local relopts yet */
i++;
}
diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c
index 733ef40ae7..977babff54 100644
--- a/src/backend/commands/vacuum.c
+++ b/src/backend/commands/vacuum.c
@@ -2221,9 +2221,9 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams params,
{
StdRdOptions *opts = (StdRdOptions *) rel->rd_options;
- if (opts && opts->vacuum_truncate_set)
+ if (opts && opts->vacuum_truncate != TERNARY_UNSET)
{
- if (opts->vacuum_truncate)
+ if (opts->vacuum_truncate == TERNARY_TRUE)
params.truncate = VACOPTVALUE_ENABLED;
else
params.truncate = VACOPTVALUE_DISABLED;
diff --git a/src/include/access/reloptions.h b/src/include/access/reloptions.h
index a604a4702c..a436697658 100644
--- a/src/include/access/reloptions.h
+++ b/src/include/access/reloptions.h
@@ -29,6 +29,7 @@
typedef enum relopt_type
{
RELOPT_TYPE_BOOL,
+ RELOPT_TYPE_TERNARY, /* on, off, unset */
RELOPT_TYPE_INT,
RELOPT_TYPE_REAL,
RELOPT_TYPE_ENUM,
@@ -80,6 +81,7 @@ typedef struct relopt_value
union
{
bool bool_val;
+ ternary ternary_val;
int int_val;
double real_val;
int enum_val;
@@ -94,6 +96,12 @@ typedef struct relopt_bool
bool default_val;
} relopt_bool;
+typedef struct relopt_rernary
+{
+ relopt_gen gen;
+ int default_val;
+} relopt_ternary;
+
typedef struct relopt_int
{
relopt_gen gen;
@@ -152,19 +160,6 @@ typedef struct
const char *optname; /* option's name */
relopt_type opttype; /* option's datatype */
int offset; /* offset of field in result struct */
-
- /*
- * isset_offset is an optional offset of a field in the result struct that
- * stores whether the option is explicitly set for the relation or if it
- * just picked up the default value. In most cases, this can be
- * accomplished by giving the reloption a special out-of-range default
- * value (e.g., some integer reloptions use -2), but this isn't always
- * possible. For example, a Boolean reloption cannot be given an
- * out-of-range default, so we need another way to discover the source of
- * its value. This offset is only used if given a value greater than
- * zero.
- */
- int isset_offset;
} relopt_parse_elt;
/* Local reloption definition */
@@ -195,6 +190,8 @@ typedef struct local_relopts
extern relopt_kind add_reloption_kind(void);
extern void add_bool_reloption(bits32 kinds, const char *name, const char *desc,
bool default_val, LOCKMODE lockmode);
+extern void add_ternary_reloption(bits32 kinds, const char *name,
+ const char *desc, int default_val, LOCKMODE lockmode);
extern void add_int_reloption(bits32 kinds, const char *name, const char *desc,
int default_val, int min_val, int max_val,
LOCKMODE lockmode);
@@ -214,6 +211,9 @@ extern void register_reloptions_validator(local_relopts *relopts,
extern void add_local_bool_reloption(local_relopts *relopts, const char *name,
const char *desc, bool default_val,
int offset);
+extern void add_local_ternary_reloption(local_relopts *relopts,
+ const char *name, const char *desc,
+ ternary default_val, int offset);
extern void add_local_int_reloption(local_relopts *relopts, const char *name,
const char *desc, int default_val,
int min_val, int max_val, int offset);
diff --git a/src/include/c.h b/src/include/c.h
index 39022f8a9d..9c59ccccf8 100644
--- a/src/include/c.h
+++ b/src/include/c.h
@@ -488,6 +488,22 @@ typedef void (*pg_funcptr_t) (void);
#include <stdbool.h>
+/*
+ * ternary
+ * Boolean value with an extrea "unset" option
+ *
+ * Ternary data type is used in relation options that can be "true", "false" or
+ * "unset". Since relation options are used deep inside the PostgreSQL code,
+ * this type is declared globally.
+*/
+
+typedef enum ternary
+{
+ TERNARY_FALSE = 0,
+ TERNARY_TRUE = 1,
+ TERNARY_UNSET = -1
+} ternary;
+
/* ----------------------------------------------------------------
* Section 3: standard system types
diff --git a/src/include/utils/rel.h b/src/include/utils/rel.h
index b552359915..7346d618f9 100644
--- a/src/include/utils/rel.h
+++ b/src/include/utils/rel.h
@@ -346,8 +346,7 @@ typedef struct StdRdOptions
bool user_catalog_table; /* use as an additional catalog relation */
int parallel_workers; /* max number of parallel workers */
StdRdOptIndexCleanup vacuum_index_cleanup; /* controls index vacuuming */
- bool vacuum_truncate; /* enables vacuum to truncate a relation */
- bool vacuum_truncate_set; /* whether vacuum_truncate is set */
+ ternary vacuum_truncate; /* enables vacuum to truncate a relation */
/*
* Fraction of pages in a relation that vacuum can eagerly scan and fail
--
2.39.2
--nextPart5129932.5fSG56mABF
Content-Disposition: attachment;
filename="v2-0003-Add-alias-to-be-used-as-unset-state.patch"
Content-Transfer-Encoding: 7Bit
Content-Type: text/x-patch; charset="unicode-2-0-utf-8";
name="v2-0003-Add-alias-to-be-used-as-unset-state.patch"
^ permalink raw reply [nested|flat] 868+ messages in thread
* [PATCH v2 2/4] Introduce ternary reloptions
@ 2025-09-04 14:41 Nikolay Shaplov <[email protected]>
0 siblings, 0 replies; 868+ messages in thread
From: Nikolay Shaplov @ 2025-09-04 14:41 UTC (permalink / raw)
Introduce ternary reloption as a replacement for current `vacuum_truncate`
implementation. Remove `vacuum_truncate_set` additional flag and using
`TERNARY_UNSET` value instead.
---
src/backend/access/common/reloptions.c | 129 ++++++++++++++++++++-----
src/backend/commands/vacuum.c | 4 +-
src/include/access/reloptions.h | 26 ++---
src/include/c.h | 16 +++
src/include/utils/rel.h | 3 +-
5 files changed, 135 insertions(+), 43 deletions(-)
diff --git a/src/backend/access/common/reloptions.c b/src/backend/access/common/reloptions.c
index 0af3fea68f..f543f11001 100644
--- a/src/backend/access/common/reloptions.c
+++ b/src/backend/access/common/reloptions.c
@@ -40,9 +40,9 @@
*
* To add an option:
*
- * (i) decide on a type (bool, integer, real, enum, string), name, default
- * value, upper and lower bounds (if applicable); for strings, consider a
- * validation routine.
+ * (i) decide on a type (bool, ternary, integer, real, enum, string), name,
+ * default value, upper and lower bounds (if applicable); for strings,
+ * consider a validation routine.
* (ii) add a record below (or use add_<type>_reloption).
* (iii) add it to the appropriate options struct (perhaps StdRdOptions)
* (iv) add it to the appropriate handling routine (perhaps
@@ -147,15 +147,6 @@ static relopt_bool boolRelOpts[] =
},
false
},
- {
- {
- "vacuum_truncate",
- "Enables vacuum to truncate empty pages at the end of this table",
- RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
- ShareUpdateExclusiveLock
- },
- true
- },
{
{
"deduplicate_items",
@@ -170,6 +161,21 @@ static relopt_bool boolRelOpts[] =
{{NULL}}
};
+static relopt_ternary ternaryRelOpts[] =
+{
+ {
+ {
+ "vacuum_truncate",
+ "Enables vacuum to truncate empty pages at the end of this table",
+ RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
+ ShareUpdateExclusiveLock
+ },
+ TERNARY_UNSET
+ },
+ /* list terminator */
+ {{NULL}}
+};
+
static relopt_int intRelOpts[] =
{
{
@@ -600,6 +606,13 @@ initialize_reloptions(void)
boolRelOpts[i].gen.lockmode));
j++;
}
+ for (i = 0; ternaryRelOpts[i].gen.name; i++)
+ {
+ Assert(DoLockModesConflict(ternaryRelOpts[i].gen.lockmode,
+ ternaryRelOpts[i].gen.lockmode));
+ j++;
+ }
+
for (i = 0; intRelOpts[i].gen.name; i++)
{
Assert(DoLockModesConflict(intRelOpts[i].gen.lockmode,
@@ -640,6 +653,14 @@ initialize_reloptions(void)
j++;
}
+ for (i = 0; ternaryRelOpts[i].gen.name; i++)
+ {
+ relOpts[j] = &ternaryRelOpts[i].gen;
+ relOpts[j]->type = RELOPT_TYPE_TERNARY;
+ relOpts[j]->namelen = strlen(relOpts[j]->name);
+ j++;
+ }
+
for (i = 0; intRelOpts[i].gen.name; i++)
{
relOpts[j] = &intRelOpts[i].gen;
@@ -800,6 +821,9 @@ allocate_reloption(bits32 kinds, int type, const char *name, const char *desc,
case RELOPT_TYPE_BOOL:
size = sizeof(relopt_bool);
break;
+ case RELOPT_TYPE_TERNARY:
+ size = sizeof(relopt_ternary);
+ break;
case RELOPT_TYPE_INT:
size = sizeof(relopt_int);
break;
@@ -883,6 +907,54 @@ add_local_bool_reloption(local_relopts *relopts, const char *name,
add_local_reloption(relopts, (relopt_gen *) newoption, offset);
}
+/*
+ * init_ternary_reloption
+ * Allocate and initialize a new ternary reloption
+ */
+static relopt_ternary *
+init_ternary_reloption(bits32 kinds, const char *name, const char *desc,
+ ternary default_val, LOCKMODE lockmode)
+{
+ relopt_ternary *newoption;
+
+ newoption = (relopt_ternary *) allocate_reloption(kinds,
+ RELOPT_TYPE_TERNARY, name, desc, lockmode);
+ newoption->default_val = default_val;
+
+ return newoption;
+}
+
+/*
+ * add_ternary_reloption
+ * Add a new ternary reloption
+ */
+void
+add_ternary_reloption(bits32 kinds, const char *name, const char *desc,
+ ternary default_val, LOCKMODE lockmode)
+{
+ relopt_ternary *newoption = init_ternary_reloption(kinds, name, desc,
+ default_val, lockmode);
+
+ add_reloption((relopt_gen *) newoption);
+}
+
+/*
+ * add_local_ternary_reloption
+ * Add a new ternary local reloption
+ *
+ * 'offset' is offset of ternary-typed field.
+ */
+void
+add_local_ternary_reloption(local_relopts *relopts, const char *name,
+ const char *desc, ternary default_val,
+ int offset)
+{
+ relopt_ternary *newoption = init_ternary_reloption(RELOPT_KIND_LOCAL,
+ name, desc,
+ default_val, 0);
+
+ add_local_reloption(relopts, (relopt_gen *) newoption, offset);
+}
/*
* init_real_reloption
@@ -1617,6 +1689,18 @@ parse_one_reloption(relopt_value *option, char *text_str, int text_len,
option->gen->name, value)));
}
break;
+ case RELOPT_TYPE_TERNARY:
+ {
+ bool b;
+ parsed = parse_bool(value, &b);
+ option->values.ternary_val = b ? TERNARY_TRUE : TERNARY_FALSE;
+ if (validate && !parsed)
+ ereport(ERROR,
+ (errcode(ERRCODE_INVALID_PARAMETER_VALUE),
+ errmsg("invalid value for ternary option \"%s\": %s",
+ option->gen->name, value)));
+ }
+ break;
case RELOPT_TYPE_INT:
{
relopt_int *optint = (relopt_int *) option->gen;
@@ -1780,17 +1864,6 @@ fillRelOptions(void *rdopts, Size basesize,
char *itempos = ((char *) rdopts) + elems[j].offset;
char *string_val;
- /*
- * If isset_offset is provided, store whether the reloption is
- * set there.
- */
- if (elems[j].isset_offset > 0)
- {
- char *setpos = ((char *) rdopts) + elems[j].isset_offset;
-
- *(bool *) setpos = options[i].isset;
- }
-
switch (options[i].gen->type)
{
case RELOPT_TYPE_BOOL:
@@ -1798,6 +1871,11 @@ fillRelOptions(void *rdopts, Size basesize,
options[i].values.bool_val :
((relopt_bool *) options[i].gen)->default_val;
break;
+ case RELOPT_TYPE_TERNARY:
+ *(ternary *) itempos = options[i].isset ?
+ options[i].values.ternary_val :
+ ((relopt_ternary *) options[i].gen)->default_val;
+ break;
case RELOPT_TYPE_INT:
*(int *) itempos = options[i].isset ?
options[i].values.int_val :
@@ -1912,8 +1990,8 @@ default_reloptions(Datum reloptions, bool validate, relopt_kind kind)
offsetof(StdRdOptions, parallel_workers)},
{"vacuum_index_cleanup", RELOPT_TYPE_ENUM,
offsetof(StdRdOptions, vacuum_index_cleanup)},
- {"vacuum_truncate", RELOPT_TYPE_BOOL,
- offsetof(StdRdOptions, vacuum_truncate), offsetof(StdRdOptions, vacuum_truncate_set)},
+ {"vacuum_truncate", RELOPT_TYPE_TERNARY,
+ offsetof(StdRdOptions, vacuum_truncate)},
{"vacuum_max_eager_freeze_failure_rate", RELOPT_TYPE_REAL,
offsetof(StdRdOptions, vacuum_max_eager_freeze_failure_rate)}
};
@@ -1993,7 +2071,6 @@ build_local_reloptions(local_relopts *relopts, Datum options, bool validate)
elems[i].optname = opt->option->name;
elems[i].opttype = opt->option->type;
elems[i].offset = opt->offset;
- elems[i].isset_offset = 0; /* not supported for local relopts yet */
i++;
}
diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c
index 733ef40ae7..977babff54 100644
--- a/src/backend/commands/vacuum.c
+++ b/src/backend/commands/vacuum.c
@@ -2221,9 +2221,9 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams params,
{
StdRdOptions *opts = (StdRdOptions *) rel->rd_options;
- if (opts && opts->vacuum_truncate_set)
+ if (opts && opts->vacuum_truncate != TERNARY_UNSET)
{
- if (opts->vacuum_truncate)
+ if (opts->vacuum_truncate == TERNARY_TRUE)
params.truncate = VACOPTVALUE_ENABLED;
else
params.truncate = VACOPTVALUE_DISABLED;
diff --git a/src/include/access/reloptions.h b/src/include/access/reloptions.h
index a604a4702c..a436697658 100644
--- a/src/include/access/reloptions.h
+++ b/src/include/access/reloptions.h
@@ -29,6 +29,7 @@
typedef enum relopt_type
{
RELOPT_TYPE_BOOL,
+ RELOPT_TYPE_TERNARY, /* on, off, unset */
RELOPT_TYPE_INT,
RELOPT_TYPE_REAL,
RELOPT_TYPE_ENUM,
@@ -80,6 +81,7 @@ typedef struct relopt_value
union
{
bool bool_val;
+ ternary ternary_val;
int int_val;
double real_val;
int enum_val;
@@ -94,6 +96,12 @@ typedef struct relopt_bool
bool default_val;
} relopt_bool;
+typedef struct relopt_rernary
+{
+ relopt_gen gen;
+ int default_val;
+} relopt_ternary;
+
typedef struct relopt_int
{
relopt_gen gen;
@@ -152,19 +160,6 @@ typedef struct
const char *optname; /* option's name */
relopt_type opttype; /* option's datatype */
int offset; /* offset of field in result struct */
-
- /*
- * isset_offset is an optional offset of a field in the result struct that
- * stores whether the option is explicitly set for the relation or if it
- * just picked up the default value. In most cases, this can be
- * accomplished by giving the reloption a special out-of-range default
- * value (e.g., some integer reloptions use -2), but this isn't always
- * possible. For example, a Boolean reloption cannot be given an
- * out-of-range default, so we need another way to discover the source of
- * its value. This offset is only used if given a value greater than
- * zero.
- */
- int isset_offset;
} relopt_parse_elt;
/* Local reloption definition */
@@ -195,6 +190,8 @@ typedef struct local_relopts
extern relopt_kind add_reloption_kind(void);
extern void add_bool_reloption(bits32 kinds, const char *name, const char *desc,
bool default_val, LOCKMODE lockmode);
+extern void add_ternary_reloption(bits32 kinds, const char *name,
+ const char *desc, int default_val, LOCKMODE lockmode);
extern void add_int_reloption(bits32 kinds, const char *name, const char *desc,
int default_val, int min_val, int max_val,
LOCKMODE lockmode);
@@ -214,6 +211,9 @@ extern void register_reloptions_validator(local_relopts *relopts,
extern void add_local_bool_reloption(local_relopts *relopts, const char *name,
const char *desc, bool default_val,
int offset);
+extern void add_local_ternary_reloption(local_relopts *relopts,
+ const char *name, const char *desc,
+ ternary default_val, int offset);
extern void add_local_int_reloption(local_relopts *relopts, const char *name,
const char *desc, int default_val,
int min_val, int max_val, int offset);
diff --git a/src/include/c.h b/src/include/c.h
index 39022f8a9d..9c59ccccf8 100644
--- a/src/include/c.h
+++ b/src/include/c.h
@@ -488,6 +488,22 @@ typedef void (*pg_funcptr_t) (void);
#include <stdbool.h>
+/*
+ * ternary
+ * Boolean value with an extrea "unset" option
+ *
+ * Ternary data type is used in relation options that can be "true", "false" or
+ * "unset". Since relation options are used deep inside the PostgreSQL code,
+ * this type is declared globally.
+*/
+
+typedef enum ternary
+{
+ TERNARY_FALSE = 0,
+ TERNARY_TRUE = 1,
+ TERNARY_UNSET = -1
+} ternary;
+
/* ----------------------------------------------------------------
* Section 3: standard system types
diff --git a/src/include/utils/rel.h b/src/include/utils/rel.h
index b552359915..7346d618f9 100644
--- a/src/include/utils/rel.h
+++ b/src/include/utils/rel.h
@@ -346,8 +346,7 @@ typedef struct StdRdOptions
bool user_catalog_table; /* use as an additional catalog relation */
int parallel_workers; /* max number of parallel workers */
StdRdOptIndexCleanup vacuum_index_cleanup; /* controls index vacuuming */
- bool vacuum_truncate; /* enables vacuum to truncate a relation */
- bool vacuum_truncate_set; /* whether vacuum_truncate is set */
+ ternary vacuum_truncate; /* enables vacuum to truncate a relation */
/*
* Fraction of pages in a relation that vacuum can eagerly scan and fail
--
2.39.2
--nextPart5129932.5fSG56mABF
Content-Disposition: attachment;
filename="v2-0003-Add-alias-to-be-used-as-unset-state.patch"
Content-Transfer-Encoding: 7Bit
Content-Type: text/x-patch; charset="unicode-2-0-utf-8";
name="v2-0003-Add-alias-to-be-used-as-unset-state.patch"
^ permalink raw reply [nested|flat] 868+ messages in thread
* [PATCH v2 2/4] Introduce ternary reloptions
@ 2025-09-04 14:41 Nikolay Shaplov <[email protected]>
0 siblings, 0 replies; 868+ messages in thread
From: Nikolay Shaplov @ 2025-09-04 14:41 UTC (permalink / raw)
Introduce ternary reloption as a replacement for current `vacuum_truncate`
implementation. Remove `vacuum_truncate_set` additional flag and using
`TERNARY_UNSET` value instead.
---
src/backend/access/common/reloptions.c | 129 ++++++++++++++++++++-----
src/backend/commands/vacuum.c | 4 +-
src/include/access/reloptions.h | 26 ++---
src/include/c.h | 16 +++
src/include/utils/rel.h | 3 +-
5 files changed, 135 insertions(+), 43 deletions(-)
diff --git a/src/backend/access/common/reloptions.c b/src/backend/access/common/reloptions.c
index 0af3fea68f..f543f11001 100644
--- a/src/backend/access/common/reloptions.c
+++ b/src/backend/access/common/reloptions.c
@@ -40,9 +40,9 @@
*
* To add an option:
*
- * (i) decide on a type (bool, integer, real, enum, string), name, default
- * value, upper and lower bounds (if applicable); for strings, consider a
- * validation routine.
+ * (i) decide on a type (bool, ternary, integer, real, enum, string), name,
+ * default value, upper and lower bounds (if applicable); for strings,
+ * consider a validation routine.
* (ii) add a record below (or use add_<type>_reloption).
* (iii) add it to the appropriate options struct (perhaps StdRdOptions)
* (iv) add it to the appropriate handling routine (perhaps
@@ -147,15 +147,6 @@ static relopt_bool boolRelOpts[] =
},
false
},
- {
- {
- "vacuum_truncate",
- "Enables vacuum to truncate empty pages at the end of this table",
- RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
- ShareUpdateExclusiveLock
- },
- true
- },
{
{
"deduplicate_items",
@@ -170,6 +161,21 @@ static relopt_bool boolRelOpts[] =
{{NULL}}
};
+static relopt_ternary ternaryRelOpts[] =
+{
+ {
+ {
+ "vacuum_truncate",
+ "Enables vacuum to truncate empty pages at the end of this table",
+ RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
+ ShareUpdateExclusiveLock
+ },
+ TERNARY_UNSET
+ },
+ /* list terminator */
+ {{NULL}}
+};
+
static relopt_int intRelOpts[] =
{
{
@@ -600,6 +606,13 @@ initialize_reloptions(void)
boolRelOpts[i].gen.lockmode));
j++;
}
+ for (i = 0; ternaryRelOpts[i].gen.name; i++)
+ {
+ Assert(DoLockModesConflict(ternaryRelOpts[i].gen.lockmode,
+ ternaryRelOpts[i].gen.lockmode));
+ j++;
+ }
+
for (i = 0; intRelOpts[i].gen.name; i++)
{
Assert(DoLockModesConflict(intRelOpts[i].gen.lockmode,
@@ -640,6 +653,14 @@ initialize_reloptions(void)
j++;
}
+ for (i = 0; ternaryRelOpts[i].gen.name; i++)
+ {
+ relOpts[j] = &ternaryRelOpts[i].gen;
+ relOpts[j]->type = RELOPT_TYPE_TERNARY;
+ relOpts[j]->namelen = strlen(relOpts[j]->name);
+ j++;
+ }
+
for (i = 0; intRelOpts[i].gen.name; i++)
{
relOpts[j] = &intRelOpts[i].gen;
@@ -800,6 +821,9 @@ allocate_reloption(bits32 kinds, int type, const char *name, const char *desc,
case RELOPT_TYPE_BOOL:
size = sizeof(relopt_bool);
break;
+ case RELOPT_TYPE_TERNARY:
+ size = sizeof(relopt_ternary);
+ break;
case RELOPT_TYPE_INT:
size = sizeof(relopt_int);
break;
@@ -883,6 +907,54 @@ add_local_bool_reloption(local_relopts *relopts, const char *name,
add_local_reloption(relopts, (relopt_gen *) newoption, offset);
}
+/*
+ * init_ternary_reloption
+ * Allocate and initialize a new ternary reloption
+ */
+static relopt_ternary *
+init_ternary_reloption(bits32 kinds, const char *name, const char *desc,
+ ternary default_val, LOCKMODE lockmode)
+{
+ relopt_ternary *newoption;
+
+ newoption = (relopt_ternary *) allocate_reloption(kinds,
+ RELOPT_TYPE_TERNARY, name, desc, lockmode);
+ newoption->default_val = default_val;
+
+ return newoption;
+}
+
+/*
+ * add_ternary_reloption
+ * Add a new ternary reloption
+ */
+void
+add_ternary_reloption(bits32 kinds, const char *name, const char *desc,
+ ternary default_val, LOCKMODE lockmode)
+{
+ relopt_ternary *newoption = init_ternary_reloption(kinds, name, desc,
+ default_val, lockmode);
+
+ add_reloption((relopt_gen *) newoption);
+}
+
+/*
+ * add_local_ternary_reloption
+ * Add a new ternary local reloption
+ *
+ * 'offset' is offset of ternary-typed field.
+ */
+void
+add_local_ternary_reloption(local_relopts *relopts, const char *name,
+ const char *desc, ternary default_val,
+ int offset)
+{
+ relopt_ternary *newoption = init_ternary_reloption(RELOPT_KIND_LOCAL,
+ name, desc,
+ default_val, 0);
+
+ add_local_reloption(relopts, (relopt_gen *) newoption, offset);
+}
/*
* init_real_reloption
@@ -1617,6 +1689,18 @@ parse_one_reloption(relopt_value *option, char *text_str, int text_len,
option->gen->name, value)));
}
break;
+ case RELOPT_TYPE_TERNARY:
+ {
+ bool b;
+ parsed = parse_bool(value, &b);
+ option->values.ternary_val = b ? TERNARY_TRUE : TERNARY_FALSE;
+ if (validate && !parsed)
+ ereport(ERROR,
+ (errcode(ERRCODE_INVALID_PARAMETER_VALUE),
+ errmsg("invalid value for ternary option \"%s\": %s",
+ option->gen->name, value)));
+ }
+ break;
case RELOPT_TYPE_INT:
{
relopt_int *optint = (relopt_int *) option->gen;
@@ -1780,17 +1864,6 @@ fillRelOptions(void *rdopts, Size basesize,
char *itempos = ((char *) rdopts) + elems[j].offset;
char *string_val;
- /*
- * If isset_offset is provided, store whether the reloption is
- * set there.
- */
- if (elems[j].isset_offset > 0)
- {
- char *setpos = ((char *) rdopts) + elems[j].isset_offset;
-
- *(bool *) setpos = options[i].isset;
- }
-
switch (options[i].gen->type)
{
case RELOPT_TYPE_BOOL:
@@ -1798,6 +1871,11 @@ fillRelOptions(void *rdopts, Size basesize,
options[i].values.bool_val :
((relopt_bool *) options[i].gen)->default_val;
break;
+ case RELOPT_TYPE_TERNARY:
+ *(ternary *) itempos = options[i].isset ?
+ options[i].values.ternary_val :
+ ((relopt_ternary *) options[i].gen)->default_val;
+ break;
case RELOPT_TYPE_INT:
*(int *) itempos = options[i].isset ?
options[i].values.int_val :
@@ -1912,8 +1990,8 @@ default_reloptions(Datum reloptions, bool validate, relopt_kind kind)
offsetof(StdRdOptions, parallel_workers)},
{"vacuum_index_cleanup", RELOPT_TYPE_ENUM,
offsetof(StdRdOptions, vacuum_index_cleanup)},
- {"vacuum_truncate", RELOPT_TYPE_BOOL,
- offsetof(StdRdOptions, vacuum_truncate), offsetof(StdRdOptions, vacuum_truncate_set)},
+ {"vacuum_truncate", RELOPT_TYPE_TERNARY,
+ offsetof(StdRdOptions, vacuum_truncate)},
{"vacuum_max_eager_freeze_failure_rate", RELOPT_TYPE_REAL,
offsetof(StdRdOptions, vacuum_max_eager_freeze_failure_rate)}
};
@@ -1993,7 +2071,6 @@ build_local_reloptions(local_relopts *relopts, Datum options, bool validate)
elems[i].optname = opt->option->name;
elems[i].opttype = opt->option->type;
elems[i].offset = opt->offset;
- elems[i].isset_offset = 0; /* not supported for local relopts yet */
i++;
}
diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c
index 733ef40ae7..977babff54 100644
--- a/src/backend/commands/vacuum.c
+++ b/src/backend/commands/vacuum.c
@@ -2221,9 +2221,9 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams params,
{
StdRdOptions *opts = (StdRdOptions *) rel->rd_options;
- if (opts && opts->vacuum_truncate_set)
+ if (opts && opts->vacuum_truncate != TERNARY_UNSET)
{
- if (opts->vacuum_truncate)
+ if (opts->vacuum_truncate == TERNARY_TRUE)
params.truncate = VACOPTVALUE_ENABLED;
else
params.truncate = VACOPTVALUE_DISABLED;
diff --git a/src/include/access/reloptions.h b/src/include/access/reloptions.h
index a604a4702c..a436697658 100644
--- a/src/include/access/reloptions.h
+++ b/src/include/access/reloptions.h
@@ -29,6 +29,7 @@
typedef enum relopt_type
{
RELOPT_TYPE_BOOL,
+ RELOPT_TYPE_TERNARY, /* on, off, unset */
RELOPT_TYPE_INT,
RELOPT_TYPE_REAL,
RELOPT_TYPE_ENUM,
@@ -80,6 +81,7 @@ typedef struct relopt_value
union
{
bool bool_val;
+ ternary ternary_val;
int int_val;
double real_val;
int enum_val;
@@ -94,6 +96,12 @@ typedef struct relopt_bool
bool default_val;
} relopt_bool;
+typedef struct relopt_rernary
+{
+ relopt_gen gen;
+ int default_val;
+} relopt_ternary;
+
typedef struct relopt_int
{
relopt_gen gen;
@@ -152,19 +160,6 @@ typedef struct
const char *optname; /* option's name */
relopt_type opttype; /* option's datatype */
int offset; /* offset of field in result struct */
-
- /*
- * isset_offset is an optional offset of a field in the result struct that
- * stores whether the option is explicitly set for the relation or if it
- * just picked up the default value. In most cases, this can be
- * accomplished by giving the reloption a special out-of-range default
- * value (e.g., some integer reloptions use -2), but this isn't always
- * possible. For example, a Boolean reloption cannot be given an
- * out-of-range default, so we need another way to discover the source of
- * its value. This offset is only used if given a value greater than
- * zero.
- */
- int isset_offset;
} relopt_parse_elt;
/* Local reloption definition */
@@ -195,6 +190,8 @@ typedef struct local_relopts
extern relopt_kind add_reloption_kind(void);
extern void add_bool_reloption(bits32 kinds, const char *name, const char *desc,
bool default_val, LOCKMODE lockmode);
+extern void add_ternary_reloption(bits32 kinds, const char *name,
+ const char *desc, int default_val, LOCKMODE lockmode);
extern void add_int_reloption(bits32 kinds, const char *name, const char *desc,
int default_val, int min_val, int max_val,
LOCKMODE lockmode);
@@ -214,6 +211,9 @@ extern void register_reloptions_validator(local_relopts *relopts,
extern void add_local_bool_reloption(local_relopts *relopts, const char *name,
const char *desc, bool default_val,
int offset);
+extern void add_local_ternary_reloption(local_relopts *relopts,
+ const char *name, const char *desc,
+ ternary default_val, int offset);
extern void add_local_int_reloption(local_relopts *relopts, const char *name,
const char *desc, int default_val,
int min_val, int max_val, int offset);
diff --git a/src/include/c.h b/src/include/c.h
index 39022f8a9d..9c59ccccf8 100644
--- a/src/include/c.h
+++ b/src/include/c.h
@@ -488,6 +488,22 @@ typedef void (*pg_funcptr_t) (void);
#include <stdbool.h>
+/*
+ * ternary
+ * Boolean value with an extrea "unset" option
+ *
+ * Ternary data type is used in relation options that can be "true", "false" or
+ * "unset". Since relation options are used deep inside the PostgreSQL code,
+ * this type is declared globally.
+*/
+
+typedef enum ternary
+{
+ TERNARY_FALSE = 0,
+ TERNARY_TRUE = 1,
+ TERNARY_UNSET = -1
+} ternary;
+
/* ----------------------------------------------------------------
* Section 3: standard system types
diff --git a/src/include/utils/rel.h b/src/include/utils/rel.h
index b552359915..7346d618f9 100644
--- a/src/include/utils/rel.h
+++ b/src/include/utils/rel.h
@@ -346,8 +346,7 @@ typedef struct StdRdOptions
bool user_catalog_table; /* use as an additional catalog relation */
int parallel_workers; /* max number of parallel workers */
StdRdOptIndexCleanup vacuum_index_cleanup; /* controls index vacuuming */
- bool vacuum_truncate; /* enables vacuum to truncate a relation */
- bool vacuum_truncate_set; /* whether vacuum_truncate is set */
+ ternary vacuum_truncate; /* enables vacuum to truncate a relation */
/*
* Fraction of pages in a relation that vacuum can eagerly scan and fail
--
2.39.2
--nextPart5129932.5fSG56mABF
Content-Disposition: attachment;
filename="v2-0003-Add-alias-to-be-used-as-unset-state.patch"
Content-Transfer-Encoding: 7Bit
Content-Type: text/x-patch; charset="unicode-2-0-utf-8";
name="v2-0003-Add-alias-to-be-used-as-unset-state.patch"
^ permalink raw reply [nested|flat] 868+ messages in thread
* [PATCH v2a 2/4] Introduce ternary reloptions
@ 2025-09-04 14:41 Nikolay Shaplov <[email protected]>
0 siblings, 0 replies; 868+ messages in thread
From: Nikolay Shaplov @ 2025-09-04 14:41 UTC (permalink / raw)
Introduce ternary reloption as a replacement for current `vacuum_truncate`
implementation. Remove `vacuum_truncate_set` additional flag and using
`TERNARY_UNSET` value instead.
---
src/backend/access/common/reloptions.c | 129 ++++++++++++++++++++-----
src/backend/commands/vacuum.c | 4 +-
src/include/access/reloptions.h | 26 ++---
src/include/c.h | 16 +++
src/include/utils/rel.h | 3 +-
5 files changed, 135 insertions(+), 43 deletions(-)
diff --git a/src/backend/access/common/reloptions.c b/src/backend/access/common/reloptions.c
index 0af3fea68f..f543f11001 100644
--- a/src/backend/access/common/reloptions.c
+++ b/src/backend/access/common/reloptions.c
@@ -40,9 +40,9 @@
*
* To add an option:
*
- * (i) decide on a type (bool, integer, real, enum, string), name, default
- * value, upper and lower bounds (if applicable); for strings, consider a
- * validation routine.
+ * (i) decide on a type (bool, ternary, integer, real, enum, string), name,
+ * default value, upper and lower bounds (if applicable); for strings,
+ * consider a validation routine.
* (ii) add a record below (or use add_<type>_reloption).
* (iii) add it to the appropriate options struct (perhaps StdRdOptions)
* (iv) add it to the appropriate handling routine (perhaps
@@ -147,15 +147,6 @@ static relopt_bool boolRelOpts[] =
},
false
},
- {
- {
- "vacuum_truncate",
- "Enables vacuum to truncate empty pages at the end of this table",
- RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
- ShareUpdateExclusiveLock
- },
- true
- },
{
{
"deduplicate_items",
@@ -170,6 +161,21 @@ static relopt_bool boolRelOpts[] =
{{NULL}}
};
+static relopt_ternary ternaryRelOpts[] =
+{
+ {
+ {
+ "vacuum_truncate",
+ "Enables vacuum to truncate empty pages at the end of this table",
+ RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
+ ShareUpdateExclusiveLock
+ },
+ TERNARY_UNSET
+ },
+ /* list terminator */
+ {{NULL}}
+};
+
static relopt_int intRelOpts[] =
{
{
@@ -600,6 +606,13 @@ initialize_reloptions(void)
boolRelOpts[i].gen.lockmode));
j++;
}
+ for (i = 0; ternaryRelOpts[i].gen.name; i++)
+ {
+ Assert(DoLockModesConflict(ternaryRelOpts[i].gen.lockmode,
+ ternaryRelOpts[i].gen.lockmode));
+ j++;
+ }
+
for (i = 0; intRelOpts[i].gen.name; i++)
{
Assert(DoLockModesConflict(intRelOpts[i].gen.lockmode,
@@ -640,6 +653,14 @@ initialize_reloptions(void)
j++;
}
+ for (i = 0; ternaryRelOpts[i].gen.name; i++)
+ {
+ relOpts[j] = &ternaryRelOpts[i].gen;
+ relOpts[j]->type = RELOPT_TYPE_TERNARY;
+ relOpts[j]->namelen = strlen(relOpts[j]->name);
+ j++;
+ }
+
for (i = 0; intRelOpts[i].gen.name; i++)
{
relOpts[j] = &intRelOpts[i].gen;
@@ -800,6 +821,9 @@ allocate_reloption(bits32 kinds, int type, const char *name, const char *desc,
case RELOPT_TYPE_BOOL:
size = sizeof(relopt_bool);
break;
+ case RELOPT_TYPE_TERNARY:
+ size = sizeof(relopt_ternary);
+ break;
case RELOPT_TYPE_INT:
size = sizeof(relopt_int);
break;
@@ -883,6 +907,54 @@ add_local_bool_reloption(local_relopts *relopts, const char *name,
add_local_reloption(relopts, (relopt_gen *) newoption, offset);
}
+/*
+ * init_ternary_reloption
+ * Allocate and initialize a new ternary reloption
+ */
+static relopt_ternary *
+init_ternary_reloption(bits32 kinds, const char *name, const char *desc,
+ ternary default_val, LOCKMODE lockmode)
+{
+ relopt_ternary *newoption;
+
+ newoption = (relopt_ternary *) allocate_reloption(kinds,
+ RELOPT_TYPE_TERNARY, name, desc, lockmode);
+ newoption->default_val = default_val;
+
+ return newoption;
+}
+
+/*
+ * add_ternary_reloption
+ * Add a new ternary reloption
+ */
+void
+add_ternary_reloption(bits32 kinds, const char *name, const char *desc,
+ ternary default_val, LOCKMODE lockmode)
+{
+ relopt_ternary *newoption = init_ternary_reloption(kinds, name, desc,
+ default_val, lockmode);
+
+ add_reloption((relopt_gen *) newoption);
+}
+
+/*
+ * add_local_ternary_reloption
+ * Add a new ternary local reloption
+ *
+ * 'offset' is offset of ternary-typed field.
+ */
+void
+add_local_ternary_reloption(local_relopts *relopts, const char *name,
+ const char *desc, ternary default_val,
+ int offset)
+{
+ relopt_ternary *newoption = init_ternary_reloption(RELOPT_KIND_LOCAL,
+ name, desc,
+ default_val, 0);
+
+ add_local_reloption(relopts, (relopt_gen *) newoption, offset);
+}
/*
* init_real_reloption
@@ -1617,6 +1689,18 @@ parse_one_reloption(relopt_value *option, char *text_str, int text_len,
option->gen->name, value)));
}
break;
+ case RELOPT_TYPE_TERNARY:
+ {
+ bool b;
+ parsed = parse_bool(value, &b);
+ option->values.ternary_val = b ? TERNARY_TRUE : TERNARY_FALSE;
+ if (validate && !parsed)
+ ereport(ERROR,
+ (errcode(ERRCODE_INVALID_PARAMETER_VALUE),
+ errmsg("invalid value for ternary option \"%s\": %s",
+ option->gen->name, value)));
+ }
+ break;
case RELOPT_TYPE_INT:
{
relopt_int *optint = (relopt_int *) option->gen;
@@ -1780,17 +1864,6 @@ fillRelOptions(void *rdopts, Size basesize,
char *itempos = ((char *) rdopts) + elems[j].offset;
char *string_val;
- /*
- * If isset_offset is provided, store whether the reloption is
- * set there.
- */
- if (elems[j].isset_offset > 0)
- {
- char *setpos = ((char *) rdopts) + elems[j].isset_offset;
-
- *(bool *) setpos = options[i].isset;
- }
-
switch (options[i].gen->type)
{
case RELOPT_TYPE_BOOL:
@@ -1798,6 +1871,11 @@ fillRelOptions(void *rdopts, Size basesize,
options[i].values.bool_val :
((relopt_bool *) options[i].gen)->default_val;
break;
+ case RELOPT_TYPE_TERNARY:
+ *(ternary *) itempos = options[i].isset ?
+ options[i].values.ternary_val :
+ ((relopt_ternary *) options[i].gen)->default_val;
+ break;
case RELOPT_TYPE_INT:
*(int *) itempos = options[i].isset ?
options[i].values.int_val :
@@ -1912,8 +1990,8 @@ default_reloptions(Datum reloptions, bool validate, relopt_kind kind)
offsetof(StdRdOptions, parallel_workers)},
{"vacuum_index_cleanup", RELOPT_TYPE_ENUM,
offsetof(StdRdOptions, vacuum_index_cleanup)},
- {"vacuum_truncate", RELOPT_TYPE_BOOL,
- offsetof(StdRdOptions, vacuum_truncate), offsetof(StdRdOptions, vacuum_truncate_set)},
+ {"vacuum_truncate", RELOPT_TYPE_TERNARY,
+ offsetof(StdRdOptions, vacuum_truncate)},
{"vacuum_max_eager_freeze_failure_rate", RELOPT_TYPE_REAL,
offsetof(StdRdOptions, vacuum_max_eager_freeze_failure_rate)}
};
@@ -1993,7 +2071,6 @@ build_local_reloptions(local_relopts *relopts, Datum options, bool validate)
elems[i].optname = opt->option->name;
elems[i].opttype = opt->option->type;
elems[i].offset = opt->offset;
- elems[i].isset_offset = 0; /* not supported for local relopts yet */
i++;
}
diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c
index 733ef40ae7..977babff54 100644
--- a/src/backend/commands/vacuum.c
+++ b/src/backend/commands/vacuum.c
@@ -2221,9 +2221,9 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams params,
{
StdRdOptions *opts = (StdRdOptions *) rel->rd_options;
- if (opts && opts->vacuum_truncate_set)
+ if (opts && opts->vacuum_truncate != TERNARY_UNSET)
{
- if (opts->vacuum_truncate)
+ if (opts->vacuum_truncate == TERNARY_TRUE)
params.truncate = VACOPTVALUE_ENABLED;
else
params.truncate = VACOPTVALUE_DISABLED;
diff --git a/src/include/access/reloptions.h b/src/include/access/reloptions.h
index a604a4702c..a436697658 100644
--- a/src/include/access/reloptions.h
+++ b/src/include/access/reloptions.h
@@ -29,6 +29,7 @@
typedef enum relopt_type
{
RELOPT_TYPE_BOOL,
+ RELOPT_TYPE_TERNARY, /* on, off, unset */
RELOPT_TYPE_INT,
RELOPT_TYPE_REAL,
RELOPT_TYPE_ENUM,
@@ -80,6 +81,7 @@ typedef struct relopt_value
union
{
bool bool_val;
+ ternary ternary_val;
int int_val;
double real_val;
int enum_val;
@@ -94,6 +96,12 @@ typedef struct relopt_bool
bool default_val;
} relopt_bool;
+typedef struct relopt_rernary
+{
+ relopt_gen gen;
+ int default_val;
+} relopt_ternary;
+
typedef struct relopt_int
{
relopt_gen gen;
@@ -152,19 +160,6 @@ typedef struct
const char *optname; /* option's name */
relopt_type opttype; /* option's datatype */
int offset; /* offset of field in result struct */
-
- /*
- * isset_offset is an optional offset of a field in the result struct that
- * stores whether the option is explicitly set for the relation or if it
- * just picked up the default value. In most cases, this can be
- * accomplished by giving the reloption a special out-of-range default
- * value (e.g., some integer reloptions use -2), but this isn't always
- * possible. For example, a Boolean reloption cannot be given an
- * out-of-range default, so we need another way to discover the source of
- * its value. This offset is only used if given a value greater than
- * zero.
- */
- int isset_offset;
} relopt_parse_elt;
/* Local reloption definition */
@@ -195,6 +190,8 @@ typedef struct local_relopts
extern relopt_kind add_reloption_kind(void);
extern void add_bool_reloption(bits32 kinds, const char *name, const char *desc,
bool default_val, LOCKMODE lockmode);
+extern void add_ternary_reloption(bits32 kinds, const char *name,
+ const char *desc, int default_val, LOCKMODE lockmode);
extern void add_int_reloption(bits32 kinds, const char *name, const char *desc,
int default_val, int min_val, int max_val,
LOCKMODE lockmode);
@@ -214,6 +211,9 @@ extern void register_reloptions_validator(local_relopts *relopts,
extern void add_local_bool_reloption(local_relopts *relopts, const char *name,
const char *desc, bool default_val,
int offset);
+extern void add_local_ternary_reloption(local_relopts *relopts,
+ const char *name, const char *desc,
+ ternary default_val, int offset);
extern void add_local_int_reloption(local_relopts *relopts, const char *name,
const char *desc, int default_val,
int min_val, int max_val, int offset);
diff --git a/src/include/c.h b/src/include/c.h
index 39022f8a9d..9c59ccccf8 100644
--- a/src/include/c.h
+++ b/src/include/c.h
@@ -488,6 +488,22 @@ typedef void (*pg_funcptr_t) (void);
#include <stdbool.h>
+/*
+ * ternary
+ * Boolean value with an extrea "unset" option
+ *
+ * Ternary data type is used in relation options that can be "true", "false" or
+ * "unset". Since relation options are used deep inside the PostgreSQL code,
+ * this type is declared globally.
+*/
+
+typedef enum ternary
+{
+ TERNARY_FALSE = 0,
+ TERNARY_TRUE = 1,
+ TERNARY_UNSET = -1
+} ternary;
+
/* ----------------------------------------------------------------
* Section 3: standard system types
diff --git a/src/include/utils/rel.h b/src/include/utils/rel.h
index b552359915..7346d618f9 100644
--- a/src/include/utils/rel.h
+++ b/src/include/utils/rel.h
@@ -346,8 +346,7 @@ typedef struct StdRdOptions
bool user_catalog_table; /* use as an additional catalog relation */
int parallel_workers; /* max number of parallel workers */
StdRdOptIndexCleanup vacuum_index_cleanup; /* controls index vacuuming */
- bool vacuum_truncate; /* enables vacuum to truncate a relation */
- bool vacuum_truncate_set; /* whether vacuum_truncate is set */
+ ternary vacuum_truncate; /* enables vacuum to truncate a relation */
/*
* Fraction of pages in a relation that vacuum can eagerly scan and fail
--
2.39.2
--nextPart4155600.3ZeAukHxDK
Content-Disposition: attachment;
filename="v2a-0003-Add-alias-to-be-used-as-unset-state.patch"
Content-Transfer-Encoding: 7Bit
Content-Type: text/x-patch; charset="unicode-2-0-utf-8";
name="v2a-0003-Add-alias-to-be-used-as-unset-state.patch"
^ permalink raw reply [nested|flat] 868+ messages in thread
* [PATCH v2a 2/4] Introduce ternary reloptions
@ 2025-09-04 14:41 Nikolay Shaplov <[email protected]>
0 siblings, 0 replies; 868+ messages in thread
From: Nikolay Shaplov @ 2025-09-04 14:41 UTC (permalink / raw)
Introduce ternary reloption as a replacement for current `vacuum_truncate`
implementation. Remove `vacuum_truncate_set` additional flag and using
`TERNARY_UNSET` value instead.
---
src/backend/access/common/reloptions.c | 129 ++++++++++++++++++++-----
src/backend/commands/vacuum.c | 4 +-
src/include/access/reloptions.h | 26 ++---
src/include/c.h | 16 +++
src/include/utils/rel.h | 3 +-
5 files changed, 135 insertions(+), 43 deletions(-)
diff --git a/src/backend/access/common/reloptions.c b/src/backend/access/common/reloptions.c
index 0af3fea68f..f543f11001 100644
--- a/src/backend/access/common/reloptions.c
+++ b/src/backend/access/common/reloptions.c
@@ -40,9 +40,9 @@
*
* To add an option:
*
- * (i) decide on a type (bool, integer, real, enum, string), name, default
- * value, upper and lower bounds (if applicable); for strings, consider a
- * validation routine.
+ * (i) decide on a type (bool, ternary, integer, real, enum, string), name,
+ * default value, upper and lower bounds (if applicable); for strings,
+ * consider a validation routine.
* (ii) add a record below (or use add_<type>_reloption).
* (iii) add it to the appropriate options struct (perhaps StdRdOptions)
* (iv) add it to the appropriate handling routine (perhaps
@@ -147,15 +147,6 @@ static relopt_bool boolRelOpts[] =
},
false
},
- {
- {
- "vacuum_truncate",
- "Enables vacuum to truncate empty pages at the end of this table",
- RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
- ShareUpdateExclusiveLock
- },
- true
- },
{
{
"deduplicate_items",
@@ -170,6 +161,21 @@ static relopt_bool boolRelOpts[] =
{{NULL}}
};
+static relopt_ternary ternaryRelOpts[] =
+{
+ {
+ {
+ "vacuum_truncate",
+ "Enables vacuum to truncate empty pages at the end of this table",
+ RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
+ ShareUpdateExclusiveLock
+ },
+ TERNARY_UNSET
+ },
+ /* list terminator */
+ {{NULL}}
+};
+
static relopt_int intRelOpts[] =
{
{
@@ -600,6 +606,13 @@ initialize_reloptions(void)
boolRelOpts[i].gen.lockmode));
j++;
}
+ for (i = 0; ternaryRelOpts[i].gen.name; i++)
+ {
+ Assert(DoLockModesConflict(ternaryRelOpts[i].gen.lockmode,
+ ternaryRelOpts[i].gen.lockmode));
+ j++;
+ }
+
for (i = 0; intRelOpts[i].gen.name; i++)
{
Assert(DoLockModesConflict(intRelOpts[i].gen.lockmode,
@@ -640,6 +653,14 @@ initialize_reloptions(void)
j++;
}
+ for (i = 0; ternaryRelOpts[i].gen.name; i++)
+ {
+ relOpts[j] = &ternaryRelOpts[i].gen;
+ relOpts[j]->type = RELOPT_TYPE_TERNARY;
+ relOpts[j]->namelen = strlen(relOpts[j]->name);
+ j++;
+ }
+
for (i = 0; intRelOpts[i].gen.name; i++)
{
relOpts[j] = &intRelOpts[i].gen;
@@ -800,6 +821,9 @@ allocate_reloption(bits32 kinds, int type, const char *name, const char *desc,
case RELOPT_TYPE_BOOL:
size = sizeof(relopt_bool);
break;
+ case RELOPT_TYPE_TERNARY:
+ size = sizeof(relopt_ternary);
+ break;
case RELOPT_TYPE_INT:
size = sizeof(relopt_int);
break;
@@ -883,6 +907,54 @@ add_local_bool_reloption(local_relopts *relopts, const char *name,
add_local_reloption(relopts, (relopt_gen *) newoption, offset);
}
+/*
+ * init_ternary_reloption
+ * Allocate and initialize a new ternary reloption
+ */
+static relopt_ternary *
+init_ternary_reloption(bits32 kinds, const char *name, const char *desc,
+ ternary default_val, LOCKMODE lockmode)
+{
+ relopt_ternary *newoption;
+
+ newoption = (relopt_ternary *) allocate_reloption(kinds,
+ RELOPT_TYPE_TERNARY, name, desc, lockmode);
+ newoption->default_val = default_val;
+
+ return newoption;
+}
+
+/*
+ * add_ternary_reloption
+ * Add a new ternary reloption
+ */
+void
+add_ternary_reloption(bits32 kinds, const char *name, const char *desc,
+ ternary default_val, LOCKMODE lockmode)
+{
+ relopt_ternary *newoption = init_ternary_reloption(kinds, name, desc,
+ default_val, lockmode);
+
+ add_reloption((relopt_gen *) newoption);
+}
+
+/*
+ * add_local_ternary_reloption
+ * Add a new ternary local reloption
+ *
+ * 'offset' is offset of ternary-typed field.
+ */
+void
+add_local_ternary_reloption(local_relopts *relopts, const char *name,
+ const char *desc, ternary default_val,
+ int offset)
+{
+ relopt_ternary *newoption = init_ternary_reloption(RELOPT_KIND_LOCAL,
+ name, desc,
+ default_val, 0);
+
+ add_local_reloption(relopts, (relopt_gen *) newoption, offset);
+}
/*
* init_real_reloption
@@ -1617,6 +1689,18 @@ parse_one_reloption(relopt_value *option, char *text_str, int text_len,
option->gen->name, value)));
}
break;
+ case RELOPT_TYPE_TERNARY:
+ {
+ bool b;
+ parsed = parse_bool(value, &b);
+ option->values.ternary_val = b ? TERNARY_TRUE : TERNARY_FALSE;
+ if (validate && !parsed)
+ ereport(ERROR,
+ (errcode(ERRCODE_INVALID_PARAMETER_VALUE),
+ errmsg("invalid value for ternary option \"%s\": %s",
+ option->gen->name, value)));
+ }
+ break;
case RELOPT_TYPE_INT:
{
relopt_int *optint = (relopt_int *) option->gen;
@@ -1780,17 +1864,6 @@ fillRelOptions(void *rdopts, Size basesize,
char *itempos = ((char *) rdopts) + elems[j].offset;
char *string_val;
- /*
- * If isset_offset is provided, store whether the reloption is
- * set there.
- */
- if (elems[j].isset_offset > 0)
- {
- char *setpos = ((char *) rdopts) + elems[j].isset_offset;
-
- *(bool *) setpos = options[i].isset;
- }
-
switch (options[i].gen->type)
{
case RELOPT_TYPE_BOOL:
@@ -1798,6 +1871,11 @@ fillRelOptions(void *rdopts, Size basesize,
options[i].values.bool_val :
((relopt_bool *) options[i].gen)->default_val;
break;
+ case RELOPT_TYPE_TERNARY:
+ *(ternary *) itempos = options[i].isset ?
+ options[i].values.ternary_val :
+ ((relopt_ternary *) options[i].gen)->default_val;
+ break;
case RELOPT_TYPE_INT:
*(int *) itempos = options[i].isset ?
options[i].values.int_val :
@@ -1912,8 +1990,8 @@ default_reloptions(Datum reloptions, bool validate, relopt_kind kind)
offsetof(StdRdOptions, parallel_workers)},
{"vacuum_index_cleanup", RELOPT_TYPE_ENUM,
offsetof(StdRdOptions, vacuum_index_cleanup)},
- {"vacuum_truncate", RELOPT_TYPE_BOOL,
- offsetof(StdRdOptions, vacuum_truncate), offsetof(StdRdOptions, vacuum_truncate_set)},
+ {"vacuum_truncate", RELOPT_TYPE_TERNARY,
+ offsetof(StdRdOptions, vacuum_truncate)},
{"vacuum_max_eager_freeze_failure_rate", RELOPT_TYPE_REAL,
offsetof(StdRdOptions, vacuum_max_eager_freeze_failure_rate)}
};
@@ -1993,7 +2071,6 @@ build_local_reloptions(local_relopts *relopts, Datum options, bool validate)
elems[i].optname = opt->option->name;
elems[i].opttype = opt->option->type;
elems[i].offset = opt->offset;
- elems[i].isset_offset = 0; /* not supported for local relopts yet */
i++;
}
diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c
index 733ef40ae7..977babff54 100644
--- a/src/backend/commands/vacuum.c
+++ b/src/backend/commands/vacuum.c
@@ -2221,9 +2221,9 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams params,
{
StdRdOptions *opts = (StdRdOptions *) rel->rd_options;
- if (opts && opts->vacuum_truncate_set)
+ if (opts && opts->vacuum_truncate != TERNARY_UNSET)
{
- if (opts->vacuum_truncate)
+ if (opts->vacuum_truncate == TERNARY_TRUE)
params.truncate = VACOPTVALUE_ENABLED;
else
params.truncate = VACOPTVALUE_DISABLED;
diff --git a/src/include/access/reloptions.h b/src/include/access/reloptions.h
index a604a4702c..a436697658 100644
--- a/src/include/access/reloptions.h
+++ b/src/include/access/reloptions.h
@@ -29,6 +29,7 @@
typedef enum relopt_type
{
RELOPT_TYPE_BOOL,
+ RELOPT_TYPE_TERNARY, /* on, off, unset */
RELOPT_TYPE_INT,
RELOPT_TYPE_REAL,
RELOPT_TYPE_ENUM,
@@ -80,6 +81,7 @@ typedef struct relopt_value
union
{
bool bool_val;
+ ternary ternary_val;
int int_val;
double real_val;
int enum_val;
@@ -94,6 +96,12 @@ typedef struct relopt_bool
bool default_val;
} relopt_bool;
+typedef struct relopt_rernary
+{
+ relopt_gen gen;
+ int default_val;
+} relopt_ternary;
+
typedef struct relopt_int
{
relopt_gen gen;
@@ -152,19 +160,6 @@ typedef struct
const char *optname; /* option's name */
relopt_type opttype; /* option's datatype */
int offset; /* offset of field in result struct */
-
- /*
- * isset_offset is an optional offset of a field in the result struct that
- * stores whether the option is explicitly set for the relation or if it
- * just picked up the default value. In most cases, this can be
- * accomplished by giving the reloption a special out-of-range default
- * value (e.g., some integer reloptions use -2), but this isn't always
- * possible. For example, a Boolean reloption cannot be given an
- * out-of-range default, so we need another way to discover the source of
- * its value. This offset is only used if given a value greater than
- * zero.
- */
- int isset_offset;
} relopt_parse_elt;
/* Local reloption definition */
@@ -195,6 +190,8 @@ typedef struct local_relopts
extern relopt_kind add_reloption_kind(void);
extern void add_bool_reloption(bits32 kinds, const char *name, const char *desc,
bool default_val, LOCKMODE lockmode);
+extern void add_ternary_reloption(bits32 kinds, const char *name,
+ const char *desc, int default_val, LOCKMODE lockmode);
extern void add_int_reloption(bits32 kinds, const char *name, const char *desc,
int default_val, int min_val, int max_val,
LOCKMODE lockmode);
@@ -214,6 +211,9 @@ extern void register_reloptions_validator(local_relopts *relopts,
extern void add_local_bool_reloption(local_relopts *relopts, const char *name,
const char *desc, bool default_val,
int offset);
+extern void add_local_ternary_reloption(local_relopts *relopts,
+ const char *name, const char *desc,
+ ternary default_val, int offset);
extern void add_local_int_reloption(local_relopts *relopts, const char *name,
const char *desc, int default_val,
int min_val, int max_val, int offset);
diff --git a/src/include/c.h b/src/include/c.h
index 39022f8a9d..9c59ccccf8 100644
--- a/src/include/c.h
+++ b/src/include/c.h
@@ -488,6 +488,22 @@ typedef void (*pg_funcptr_t) (void);
#include <stdbool.h>
+/*
+ * ternary
+ * Boolean value with an extrea "unset" option
+ *
+ * Ternary data type is used in relation options that can be "true", "false" or
+ * "unset". Since relation options are used deep inside the PostgreSQL code,
+ * this type is declared globally.
+*/
+
+typedef enum ternary
+{
+ TERNARY_FALSE = 0,
+ TERNARY_TRUE = 1,
+ TERNARY_UNSET = -1
+} ternary;
+
/* ----------------------------------------------------------------
* Section 3: standard system types
diff --git a/src/include/utils/rel.h b/src/include/utils/rel.h
index b552359915..7346d618f9 100644
--- a/src/include/utils/rel.h
+++ b/src/include/utils/rel.h
@@ -346,8 +346,7 @@ typedef struct StdRdOptions
bool user_catalog_table; /* use as an additional catalog relation */
int parallel_workers; /* max number of parallel workers */
StdRdOptIndexCleanup vacuum_index_cleanup; /* controls index vacuuming */
- bool vacuum_truncate; /* enables vacuum to truncate a relation */
- bool vacuum_truncate_set; /* whether vacuum_truncate is set */
+ ternary vacuum_truncate; /* enables vacuum to truncate a relation */
/*
* Fraction of pages in a relation that vacuum can eagerly scan and fail
--
2.39.2
--nextPart4155600.3ZeAukHxDK
Content-Disposition: attachment;
filename="v2a-0003-Add-alias-to-be-used-as-unset-state.patch"
Content-Transfer-Encoding: 7Bit
Content-Type: text/x-patch; charset="unicode-2-0-utf-8";
name="v2a-0003-Add-alias-to-be-used-as-unset-state.patch"
^ permalink raw reply [nested|flat] 868+ messages in thread
* [PATCH v2 2/4] Introduce ternary reloptions
@ 2025-09-04 14:41 Nikolay Shaplov <[email protected]>
0 siblings, 0 replies; 868+ messages in thread
From: Nikolay Shaplov @ 2025-09-04 14:41 UTC (permalink / raw)
Introduce ternary reloption as a replacement for current `vacuum_truncate`
implementation. Remove `vacuum_truncate_set` additional flag and using
`TERNARY_UNSET` value instead.
---
src/backend/access/common/reloptions.c | 129 ++++++++++++++++++++-----
src/backend/commands/vacuum.c | 4 +-
src/include/access/reloptions.h | 26 ++---
src/include/c.h | 16 +++
src/include/utils/rel.h | 3 +-
5 files changed, 135 insertions(+), 43 deletions(-)
diff --git a/src/backend/access/common/reloptions.c b/src/backend/access/common/reloptions.c
index 0af3fea68f..f543f11001 100644
--- a/src/backend/access/common/reloptions.c
+++ b/src/backend/access/common/reloptions.c
@@ -40,9 +40,9 @@
*
* To add an option:
*
- * (i) decide on a type (bool, integer, real, enum, string), name, default
- * value, upper and lower bounds (if applicable); for strings, consider a
- * validation routine.
+ * (i) decide on a type (bool, ternary, integer, real, enum, string), name,
+ * default value, upper and lower bounds (if applicable); for strings,
+ * consider a validation routine.
* (ii) add a record below (or use add_<type>_reloption).
* (iii) add it to the appropriate options struct (perhaps StdRdOptions)
* (iv) add it to the appropriate handling routine (perhaps
@@ -147,15 +147,6 @@ static relopt_bool boolRelOpts[] =
},
false
},
- {
- {
- "vacuum_truncate",
- "Enables vacuum to truncate empty pages at the end of this table",
- RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
- ShareUpdateExclusiveLock
- },
- true
- },
{
{
"deduplicate_items",
@@ -170,6 +161,21 @@ static relopt_bool boolRelOpts[] =
{{NULL}}
};
+static relopt_ternary ternaryRelOpts[] =
+{
+ {
+ {
+ "vacuum_truncate",
+ "Enables vacuum to truncate empty pages at the end of this table",
+ RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
+ ShareUpdateExclusiveLock
+ },
+ TERNARY_UNSET
+ },
+ /* list terminator */
+ {{NULL}}
+};
+
static relopt_int intRelOpts[] =
{
{
@@ -600,6 +606,13 @@ initialize_reloptions(void)
boolRelOpts[i].gen.lockmode));
j++;
}
+ for (i = 0; ternaryRelOpts[i].gen.name; i++)
+ {
+ Assert(DoLockModesConflict(ternaryRelOpts[i].gen.lockmode,
+ ternaryRelOpts[i].gen.lockmode));
+ j++;
+ }
+
for (i = 0; intRelOpts[i].gen.name; i++)
{
Assert(DoLockModesConflict(intRelOpts[i].gen.lockmode,
@@ -640,6 +653,14 @@ initialize_reloptions(void)
j++;
}
+ for (i = 0; ternaryRelOpts[i].gen.name; i++)
+ {
+ relOpts[j] = &ternaryRelOpts[i].gen;
+ relOpts[j]->type = RELOPT_TYPE_TERNARY;
+ relOpts[j]->namelen = strlen(relOpts[j]->name);
+ j++;
+ }
+
for (i = 0; intRelOpts[i].gen.name; i++)
{
relOpts[j] = &intRelOpts[i].gen;
@@ -800,6 +821,9 @@ allocate_reloption(bits32 kinds, int type, const char *name, const char *desc,
case RELOPT_TYPE_BOOL:
size = sizeof(relopt_bool);
break;
+ case RELOPT_TYPE_TERNARY:
+ size = sizeof(relopt_ternary);
+ break;
case RELOPT_TYPE_INT:
size = sizeof(relopt_int);
break;
@@ -883,6 +907,54 @@ add_local_bool_reloption(local_relopts *relopts, const char *name,
add_local_reloption(relopts, (relopt_gen *) newoption, offset);
}
+/*
+ * init_ternary_reloption
+ * Allocate and initialize a new ternary reloption
+ */
+static relopt_ternary *
+init_ternary_reloption(bits32 kinds, const char *name, const char *desc,
+ ternary default_val, LOCKMODE lockmode)
+{
+ relopt_ternary *newoption;
+
+ newoption = (relopt_ternary *) allocate_reloption(kinds,
+ RELOPT_TYPE_TERNARY, name, desc, lockmode);
+ newoption->default_val = default_val;
+
+ return newoption;
+}
+
+/*
+ * add_ternary_reloption
+ * Add a new ternary reloption
+ */
+void
+add_ternary_reloption(bits32 kinds, const char *name, const char *desc,
+ ternary default_val, LOCKMODE lockmode)
+{
+ relopt_ternary *newoption = init_ternary_reloption(kinds, name, desc,
+ default_val, lockmode);
+
+ add_reloption((relopt_gen *) newoption);
+}
+
+/*
+ * add_local_ternary_reloption
+ * Add a new ternary local reloption
+ *
+ * 'offset' is offset of ternary-typed field.
+ */
+void
+add_local_ternary_reloption(local_relopts *relopts, const char *name,
+ const char *desc, ternary default_val,
+ int offset)
+{
+ relopt_ternary *newoption = init_ternary_reloption(RELOPT_KIND_LOCAL,
+ name, desc,
+ default_val, 0);
+
+ add_local_reloption(relopts, (relopt_gen *) newoption, offset);
+}
/*
* init_real_reloption
@@ -1617,6 +1689,18 @@ parse_one_reloption(relopt_value *option, char *text_str, int text_len,
option->gen->name, value)));
}
break;
+ case RELOPT_TYPE_TERNARY:
+ {
+ bool b;
+ parsed = parse_bool(value, &b);
+ option->values.ternary_val = b ? TERNARY_TRUE : TERNARY_FALSE;
+ if (validate && !parsed)
+ ereport(ERROR,
+ (errcode(ERRCODE_INVALID_PARAMETER_VALUE),
+ errmsg("invalid value for ternary option \"%s\": %s",
+ option->gen->name, value)));
+ }
+ break;
case RELOPT_TYPE_INT:
{
relopt_int *optint = (relopt_int *) option->gen;
@@ -1780,17 +1864,6 @@ fillRelOptions(void *rdopts, Size basesize,
char *itempos = ((char *) rdopts) + elems[j].offset;
char *string_val;
- /*
- * If isset_offset is provided, store whether the reloption is
- * set there.
- */
- if (elems[j].isset_offset > 0)
- {
- char *setpos = ((char *) rdopts) + elems[j].isset_offset;
-
- *(bool *) setpos = options[i].isset;
- }
-
switch (options[i].gen->type)
{
case RELOPT_TYPE_BOOL:
@@ -1798,6 +1871,11 @@ fillRelOptions(void *rdopts, Size basesize,
options[i].values.bool_val :
((relopt_bool *) options[i].gen)->default_val;
break;
+ case RELOPT_TYPE_TERNARY:
+ *(ternary *) itempos = options[i].isset ?
+ options[i].values.ternary_val :
+ ((relopt_ternary *) options[i].gen)->default_val;
+ break;
case RELOPT_TYPE_INT:
*(int *) itempos = options[i].isset ?
options[i].values.int_val :
@@ -1912,8 +1990,8 @@ default_reloptions(Datum reloptions, bool validate, relopt_kind kind)
offsetof(StdRdOptions, parallel_workers)},
{"vacuum_index_cleanup", RELOPT_TYPE_ENUM,
offsetof(StdRdOptions, vacuum_index_cleanup)},
- {"vacuum_truncate", RELOPT_TYPE_BOOL,
- offsetof(StdRdOptions, vacuum_truncate), offsetof(StdRdOptions, vacuum_truncate_set)},
+ {"vacuum_truncate", RELOPT_TYPE_TERNARY,
+ offsetof(StdRdOptions, vacuum_truncate)},
{"vacuum_max_eager_freeze_failure_rate", RELOPT_TYPE_REAL,
offsetof(StdRdOptions, vacuum_max_eager_freeze_failure_rate)}
};
@@ -1993,7 +2071,6 @@ build_local_reloptions(local_relopts *relopts, Datum options, bool validate)
elems[i].optname = opt->option->name;
elems[i].opttype = opt->option->type;
elems[i].offset = opt->offset;
- elems[i].isset_offset = 0; /* not supported for local relopts yet */
i++;
}
diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c
index 733ef40ae7..977babff54 100644
--- a/src/backend/commands/vacuum.c
+++ b/src/backend/commands/vacuum.c
@@ -2221,9 +2221,9 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams params,
{
StdRdOptions *opts = (StdRdOptions *) rel->rd_options;
- if (opts && opts->vacuum_truncate_set)
+ if (opts && opts->vacuum_truncate != TERNARY_UNSET)
{
- if (opts->vacuum_truncate)
+ if (opts->vacuum_truncate == TERNARY_TRUE)
params.truncate = VACOPTVALUE_ENABLED;
else
params.truncate = VACOPTVALUE_DISABLED;
diff --git a/src/include/access/reloptions.h b/src/include/access/reloptions.h
index a604a4702c..a436697658 100644
--- a/src/include/access/reloptions.h
+++ b/src/include/access/reloptions.h
@@ -29,6 +29,7 @@
typedef enum relopt_type
{
RELOPT_TYPE_BOOL,
+ RELOPT_TYPE_TERNARY, /* on, off, unset */
RELOPT_TYPE_INT,
RELOPT_TYPE_REAL,
RELOPT_TYPE_ENUM,
@@ -80,6 +81,7 @@ typedef struct relopt_value
union
{
bool bool_val;
+ ternary ternary_val;
int int_val;
double real_val;
int enum_val;
@@ -94,6 +96,12 @@ typedef struct relopt_bool
bool default_val;
} relopt_bool;
+typedef struct relopt_rernary
+{
+ relopt_gen gen;
+ int default_val;
+} relopt_ternary;
+
typedef struct relopt_int
{
relopt_gen gen;
@@ -152,19 +160,6 @@ typedef struct
const char *optname; /* option's name */
relopt_type opttype; /* option's datatype */
int offset; /* offset of field in result struct */
-
- /*
- * isset_offset is an optional offset of a field in the result struct that
- * stores whether the option is explicitly set for the relation or if it
- * just picked up the default value. In most cases, this can be
- * accomplished by giving the reloption a special out-of-range default
- * value (e.g., some integer reloptions use -2), but this isn't always
- * possible. For example, a Boolean reloption cannot be given an
- * out-of-range default, so we need another way to discover the source of
- * its value. This offset is only used if given a value greater than
- * zero.
- */
- int isset_offset;
} relopt_parse_elt;
/* Local reloption definition */
@@ -195,6 +190,8 @@ typedef struct local_relopts
extern relopt_kind add_reloption_kind(void);
extern void add_bool_reloption(bits32 kinds, const char *name, const char *desc,
bool default_val, LOCKMODE lockmode);
+extern void add_ternary_reloption(bits32 kinds, const char *name,
+ const char *desc, int default_val, LOCKMODE lockmode);
extern void add_int_reloption(bits32 kinds, const char *name, const char *desc,
int default_val, int min_val, int max_val,
LOCKMODE lockmode);
@@ -214,6 +211,9 @@ extern void register_reloptions_validator(local_relopts *relopts,
extern void add_local_bool_reloption(local_relopts *relopts, const char *name,
const char *desc, bool default_val,
int offset);
+extern void add_local_ternary_reloption(local_relopts *relopts,
+ const char *name, const char *desc,
+ ternary default_val, int offset);
extern void add_local_int_reloption(local_relopts *relopts, const char *name,
const char *desc, int default_val,
int min_val, int max_val, int offset);
diff --git a/src/include/c.h b/src/include/c.h
index 39022f8a9d..9c59ccccf8 100644
--- a/src/include/c.h
+++ b/src/include/c.h
@@ -488,6 +488,22 @@ typedef void (*pg_funcptr_t) (void);
#include <stdbool.h>
+/*
+ * ternary
+ * Boolean value with an extrea "unset" option
+ *
+ * Ternary data type is used in relation options that can be "true", "false" or
+ * "unset". Since relation options are used deep inside the PostgreSQL code,
+ * this type is declared globally.
+*/
+
+typedef enum ternary
+{
+ TERNARY_FALSE = 0,
+ TERNARY_TRUE = 1,
+ TERNARY_UNSET = -1
+} ternary;
+
/* ----------------------------------------------------------------
* Section 3: standard system types
diff --git a/src/include/utils/rel.h b/src/include/utils/rel.h
index b552359915..7346d618f9 100644
--- a/src/include/utils/rel.h
+++ b/src/include/utils/rel.h
@@ -346,8 +346,7 @@ typedef struct StdRdOptions
bool user_catalog_table; /* use as an additional catalog relation */
int parallel_workers; /* max number of parallel workers */
StdRdOptIndexCleanup vacuum_index_cleanup; /* controls index vacuuming */
- bool vacuum_truncate; /* enables vacuum to truncate a relation */
- bool vacuum_truncate_set; /* whether vacuum_truncate is set */
+ ternary vacuum_truncate; /* enables vacuum to truncate a relation */
/*
* Fraction of pages in a relation that vacuum can eagerly scan and fail
--
2.39.2
--nextPart5129932.5fSG56mABF
Content-Disposition: attachment;
filename="v2-0003-Add-alias-to-be-used-as-unset-state.patch"
Content-Transfer-Encoding: 7Bit
Content-Type: text/x-patch; charset="unicode-2-0-utf-8";
name="v2-0003-Add-alias-to-be-used-as-unset-state.patch"
^ permalink raw reply [nested|flat] 868+ messages in thread
* [PATCH v2 2/4] Introduce ternary reloptions
@ 2025-09-04 14:41 Nikolay Shaplov <[email protected]>
0 siblings, 0 replies; 868+ messages in thread
From: Nikolay Shaplov @ 2025-09-04 14:41 UTC (permalink / raw)
Introduce ternary reloption as a replacement for current `vacuum_truncate`
implementation. Remove `vacuum_truncate_set` additional flag and using
`TERNARY_UNSET` value instead.
---
src/backend/access/common/reloptions.c | 129 ++++++++++++++++++++-----
src/backend/commands/vacuum.c | 4 +-
src/include/access/reloptions.h | 26 ++---
src/include/c.h | 16 +++
src/include/utils/rel.h | 3 +-
5 files changed, 135 insertions(+), 43 deletions(-)
diff --git a/src/backend/access/common/reloptions.c b/src/backend/access/common/reloptions.c
index 0af3fea68f..f543f11001 100644
--- a/src/backend/access/common/reloptions.c
+++ b/src/backend/access/common/reloptions.c
@@ -40,9 +40,9 @@
*
* To add an option:
*
- * (i) decide on a type (bool, integer, real, enum, string), name, default
- * value, upper and lower bounds (if applicable); for strings, consider a
- * validation routine.
+ * (i) decide on a type (bool, ternary, integer, real, enum, string), name,
+ * default value, upper and lower bounds (if applicable); for strings,
+ * consider a validation routine.
* (ii) add a record below (or use add_<type>_reloption).
* (iii) add it to the appropriate options struct (perhaps StdRdOptions)
* (iv) add it to the appropriate handling routine (perhaps
@@ -147,15 +147,6 @@ static relopt_bool boolRelOpts[] =
},
false
},
- {
- {
- "vacuum_truncate",
- "Enables vacuum to truncate empty pages at the end of this table",
- RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
- ShareUpdateExclusiveLock
- },
- true
- },
{
{
"deduplicate_items",
@@ -170,6 +161,21 @@ static relopt_bool boolRelOpts[] =
{{NULL}}
};
+static relopt_ternary ternaryRelOpts[] =
+{
+ {
+ {
+ "vacuum_truncate",
+ "Enables vacuum to truncate empty pages at the end of this table",
+ RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
+ ShareUpdateExclusiveLock
+ },
+ TERNARY_UNSET
+ },
+ /* list terminator */
+ {{NULL}}
+};
+
static relopt_int intRelOpts[] =
{
{
@@ -600,6 +606,13 @@ initialize_reloptions(void)
boolRelOpts[i].gen.lockmode));
j++;
}
+ for (i = 0; ternaryRelOpts[i].gen.name; i++)
+ {
+ Assert(DoLockModesConflict(ternaryRelOpts[i].gen.lockmode,
+ ternaryRelOpts[i].gen.lockmode));
+ j++;
+ }
+
for (i = 0; intRelOpts[i].gen.name; i++)
{
Assert(DoLockModesConflict(intRelOpts[i].gen.lockmode,
@@ -640,6 +653,14 @@ initialize_reloptions(void)
j++;
}
+ for (i = 0; ternaryRelOpts[i].gen.name; i++)
+ {
+ relOpts[j] = &ternaryRelOpts[i].gen;
+ relOpts[j]->type = RELOPT_TYPE_TERNARY;
+ relOpts[j]->namelen = strlen(relOpts[j]->name);
+ j++;
+ }
+
for (i = 0; intRelOpts[i].gen.name; i++)
{
relOpts[j] = &intRelOpts[i].gen;
@@ -800,6 +821,9 @@ allocate_reloption(bits32 kinds, int type, const char *name, const char *desc,
case RELOPT_TYPE_BOOL:
size = sizeof(relopt_bool);
break;
+ case RELOPT_TYPE_TERNARY:
+ size = sizeof(relopt_ternary);
+ break;
case RELOPT_TYPE_INT:
size = sizeof(relopt_int);
break;
@@ -883,6 +907,54 @@ add_local_bool_reloption(local_relopts *relopts, const char *name,
add_local_reloption(relopts, (relopt_gen *) newoption, offset);
}
+/*
+ * init_ternary_reloption
+ * Allocate and initialize a new ternary reloption
+ */
+static relopt_ternary *
+init_ternary_reloption(bits32 kinds, const char *name, const char *desc,
+ ternary default_val, LOCKMODE lockmode)
+{
+ relopt_ternary *newoption;
+
+ newoption = (relopt_ternary *) allocate_reloption(kinds,
+ RELOPT_TYPE_TERNARY, name, desc, lockmode);
+ newoption->default_val = default_val;
+
+ return newoption;
+}
+
+/*
+ * add_ternary_reloption
+ * Add a new ternary reloption
+ */
+void
+add_ternary_reloption(bits32 kinds, const char *name, const char *desc,
+ ternary default_val, LOCKMODE lockmode)
+{
+ relopt_ternary *newoption = init_ternary_reloption(kinds, name, desc,
+ default_val, lockmode);
+
+ add_reloption((relopt_gen *) newoption);
+}
+
+/*
+ * add_local_ternary_reloption
+ * Add a new ternary local reloption
+ *
+ * 'offset' is offset of ternary-typed field.
+ */
+void
+add_local_ternary_reloption(local_relopts *relopts, const char *name,
+ const char *desc, ternary default_val,
+ int offset)
+{
+ relopt_ternary *newoption = init_ternary_reloption(RELOPT_KIND_LOCAL,
+ name, desc,
+ default_val, 0);
+
+ add_local_reloption(relopts, (relopt_gen *) newoption, offset);
+}
/*
* init_real_reloption
@@ -1617,6 +1689,18 @@ parse_one_reloption(relopt_value *option, char *text_str, int text_len,
option->gen->name, value)));
}
break;
+ case RELOPT_TYPE_TERNARY:
+ {
+ bool b;
+ parsed = parse_bool(value, &b);
+ option->values.ternary_val = b ? TERNARY_TRUE : TERNARY_FALSE;
+ if (validate && !parsed)
+ ereport(ERROR,
+ (errcode(ERRCODE_INVALID_PARAMETER_VALUE),
+ errmsg("invalid value for ternary option \"%s\": %s",
+ option->gen->name, value)));
+ }
+ break;
case RELOPT_TYPE_INT:
{
relopt_int *optint = (relopt_int *) option->gen;
@@ -1780,17 +1864,6 @@ fillRelOptions(void *rdopts, Size basesize,
char *itempos = ((char *) rdopts) + elems[j].offset;
char *string_val;
- /*
- * If isset_offset is provided, store whether the reloption is
- * set there.
- */
- if (elems[j].isset_offset > 0)
- {
- char *setpos = ((char *) rdopts) + elems[j].isset_offset;
-
- *(bool *) setpos = options[i].isset;
- }
-
switch (options[i].gen->type)
{
case RELOPT_TYPE_BOOL:
@@ -1798,6 +1871,11 @@ fillRelOptions(void *rdopts, Size basesize,
options[i].values.bool_val :
((relopt_bool *) options[i].gen)->default_val;
break;
+ case RELOPT_TYPE_TERNARY:
+ *(ternary *) itempos = options[i].isset ?
+ options[i].values.ternary_val :
+ ((relopt_ternary *) options[i].gen)->default_val;
+ break;
case RELOPT_TYPE_INT:
*(int *) itempos = options[i].isset ?
options[i].values.int_val :
@@ -1912,8 +1990,8 @@ default_reloptions(Datum reloptions, bool validate, relopt_kind kind)
offsetof(StdRdOptions, parallel_workers)},
{"vacuum_index_cleanup", RELOPT_TYPE_ENUM,
offsetof(StdRdOptions, vacuum_index_cleanup)},
- {"vacuum_truncate", RELOPT_TYPE_BOOL,
- offsetof(StdRdOptions, vacuum_truncate), offsetof(StdRdOptions, vacuum_truncate_set)},
+ {"vacuum_truncate", RELOPT_TYPE_TERNARY,
+ offsetof(StdRdOptions, vacuum_truncate)},
{"vacuum_max_eager_freeze_failure_rate", RELOPT_TYPE_REAL,
offsetof(StdRdOptions, vacuum_max_eager_freeze_failure_rate)}
};
@@ -1993,7 +2071,6 @@ build_local_reloptions(local_relopts *relopts, Datum options, bool validate)
elems[i].optname = opt->option->name;
elems[i].opttype = opt->option->type;
elems[i].offset = opt->offset;
- elems[i].isset_offset = 0; /* not supported for local relopts yet */
i++;
}
diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c
index 733ef40ae7..977babff54 100644
--- a/src/backend/commands/vacuum.c
+++ b/src/backend/commands/vacuum.c
@@ -2221,9 +2221,9 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams params,
{
StdRdOptions *opts = (StdRdOptions *) rel->rd_options;
- if (opts && opts->vacuum_truncate_set)
+ if (opts && opts->vacuum_truncate != TERNARY_UNSET)
{
- if (opts->vacuum_truncate)
+ if (opts->vacuum_truncate == TERNARY_TRUE)
params.truncate = VACOPTVALUE_ENABLED;
else
params.truncate = VACOPTVALUE_DISABLED;
diff --git a/src/include/access/reloptions.h b/src/include/access/reloptions.h
index a604a4702c..a436697658 100644
--- a/src/include/access/reloptions.h
+++ b/src/include/access/reloptions.h
@@ -29,6 +29,7 @@
typedef enum relopt_type
{
RELOPT_TYPE_BOOL,
+ RELOPT_TYPE_TERNARY, /* on, off, unset */
RELOPT_TYPE_INT,
RELOPT_TYPE_REAL,
RELOPT_TYPE_ENUM,
@@ -80,6 +81,7 @@ typedef struct relopt_value
union
{
bool bool_val;
+ ternary ternary_val;
int int_val;
double real_val;
int enum_val;
@@ -94,6 +96,12 @@ typedef struct relopt_bool
bool default_val;
} relopt_bool;
+typedef struct relopt_rernary
+{
+ relopt_gen gen;
+ int default_val;
+} relopt_ternary;
+
typedef struct relopt_int
{
relopt_gen gen;
@@ -152,19 +160,6 @@ typedef struct
const char *optname; /* option's name */
relopt_type opttype; /* option's datatype */
int offset; /* offset of field in result struct */
-
- /*
- * isset_offset is an optional offset of a field in the result struct that
- * stores whether the option is explicitly set for the relation or if it
- * just picked up the default value. In most cases, this can be
- * accomplished by giving the reloption a special out-of-range default
- * value (e.g., some integer reloptions use -2), but this isn't always
- * possible. For example, a Boolean reloption cannot be given an
- * out-of-range default, so we need another way to discover the source of
- * its value. This offset is only used if given a value greater than
- * zero.
- */
- int isset_offset;
} relopt_parse_elt;
/* Local reloption definition */
@@ -195,6 +190,8 @@ typedef struct local_relopts
extern relopt_kind add_reloption_kind(void);
extern void add_bool_reloption(bits32 kinds, const char *name, const char *desc,
bool default_val, LOCKMODE lockmode);
+extern void add_ternary_reloption(bits32 kinds, const char *name,
+ const char *desc, int default_val, LOCKMODE lockmode);
extern void add_int_reloption(bits32 kinds, const char *name, const char *desc,
int default_val, int min_val, int max_val,
LOCKMODE lockmode);
@@ -214,6 +211,9 @@ extern void register_reloptions_validator(local_relopts *relopts,
extern void add_local_bool_reloption(local_relopts *relopts, const char *name,
const char *desc, bool default_val,
int offset);
+extern void add_local_ternary_reloption(local_relopts *relopts,
+ const char *name, const char *desc,
+ ternary default_val, int offset);
extern void add_local_int_reloption(local_relopts *relopts, const char *name,
const char *desc, int default_val,
int min_val, int max_val, int offset);
diff --git a/src/include/c.h b/src/include/c.h
index 39022f8a9d..9c59ccccf8 100644
--- a/src/include/c.h
+++ b/src/include/c.h
@@ -488,6 +488,22 @@ typedef void (*pg_funcptr_t) (void);
#include <stdbool.h>
+/*
+ * ternary
+ * Boolean value with an extrea "unset" option
+ *
+ * Ternary data type is used in relation options that can be "true", "false" or
+ * "unset". Since relation options are used deep inside the PostgreSQL code,
+ * this type is declared globally.
+*/
+
+typedef enum ternary
+{
+ TERNARY_FALSE = 0,
+ TERNARY_TRUE = 1,
+ TERNARY_UNSET = -1
+} ternary;
+
/* ----------------------------------------------------------------
* Section 3: standard system types
diff --git a/src/include/utils/rel.h b/src/include/utils/rel.h
index b552359915..7346d618f9 100644
--- a/src/include/utils/rel.h
+++ b/src/include/utils/rel.h
@@ -346,8 +346,7 @@ typedef struct StdRdOptions
bool user_catalog_table; /* use as an additional catalog relation */
int parallel_workers; /* max number of parallel workers */
StdRdOptIndexCleanup vacuum_index_cleanup; /* controls index vacuuming */
- bool vacuum_truncate; /* enables vacuum to truncate a relation */
- bool vacuum_truncate_set; /* whether vacuum_truncate is set */
+ ternary vacuum_truncate; /* enables vacuum to truncate a relation */
/*
* Fraction of pages in a relation that vacuum can eagerly scan and fail
--
2.39.2
--nextPart5129932.5fSG56mABF
Content-Disposition: attachment;
filename="v2-0003-Add-alias-to-be-used-as-unset-state.patch"
Content-Transfer-Encoding: 7Bit
Content-Type: text/x-patch; charset="unicode-2-0-utf-8";
name="v2-0003-Add-alias-to-be-used-as-unset-state.patch"
^ permalink raw reply [nested|flat] 868+ messages in thread
* [PATCH v2a 2/4] Introduce ternary reloptions
@ 2025-09-04 14:41 Nikolay Shaplov <[email protected]>
0 siblings, 0 replies; 868+ messages in thread
From: Nikolay Shaplov @ 2025-09-04 14:41 UTC (permalink / raw)
Introduce ternary reloption as a replacement for current `vacuum_truncate`
implementation. Remove `vacuum_truncate_set` additional flag and using
`TERNARY_UNSET` value instead.
---
src/backend/access/common/reloptions.c | 129 ++++++++++++++++++++-----
src/backend/commands/vacuum.c | 4 +-
src/include/access/reloptions.h | 26 ++---
src/include/c.h | 16 +++
src/include/utils/rel.h | 3 +-
5 files changed, 135 insertions(+), 43 deletions(-)
diff --git a/src/backend/access/common/reloptions.c b/src/backend/access/common/reloptions.c
index 0af3fea68f..f543f11001 100644
--- a/src/backend/access/common/reloptions.c
+++ b/src/backend/access/common/reloptions.c
@@ -40,9 +40,9 @@
*
* To add an option:
*
- * (i) decide on a type (bool, integer, real, enum, string), name, default
- * value, upper and lower bounds (if applicable); for strings, consider a
- * validation routine.
+ * (i) decide on a type (bool, ternary, integer, real, enum, string), name,
+ * default value, upper and lower bounds (if applicable); for strings,
+ * consider a validation routine.
* (ii) add a record below (or use add_<type>_reloption).
* (iii) add it to the appropriate options struct (perhaps StdRdOptions)
* (iv) add it to the appropriate handling routine (perhaps
@@ -147,15 +147,6 @@ static relopt_bool boolRelOpts[] =
},
false
},
- {
- {
- "vacuum_truncate",
- "Enables vacuum to truncate empty pages at the end of this table",
- RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
- ShareUpdateExclusiveLock
- },
- true
- },
{
{
"deduplicate_items",
@@ -170,6 +161,21 @@ static relopt_bool boolRelOpts[] =
{{NULL}}
};
+static relopt_ternary ternaryRelOpts[] =
+{
+ {
+ {
+ "vacuum_truncate",
+ "Enables vacuum to truncate empty pages at the end of this table",
+ RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
+ ShareUpdateExclusiveLock
+ },
+ TERNARY_UNSET
+ },
+ /* list terminator */
+ {{NULL}}
+};
+
static relopt_int intRelOpts[] =
{
{
@@ -600,6 +606,13 @@ initialize_reloptions(void)
boolRelOpts[i].gen.lockmode));
j++;
}
+ for (i = 0; ternaryRelOpts[i].gen.name; i++)
+ {
+ Assert(DoLockModesConflict(ternaryRelOpts[i].gen.lockmode,
+ ternaryRelOpts[i].gen.lockmode));
+ j++;
+ }
+
for (i = 0; intRelOpts[i].gen.name; i++)
{
Assert(DoLockModesConflict(intRelOpts[i].gen.lockmode,
@@ -640,6 +653,14 @@ initialize_reloptions(void)
j++;
}
+ for (i = 0; ternaryRelOpts[i].gen.name; i++)
+ {
+ relOpts[j] = &ternaryRelOpts[i].gen;
+ relOpts[j]->type = RELOPT_TYPE_TERNARY;
+ relOpts[j]->namelen = strlen(relOpts[j]->name);
+ j++;
+ }
+
for (i = 0; intRelOpts[i].gen.name; i++)
{
relOpts[j] = &intRelOpts[i].gen;
@@ -800,6 +821,9 @@ allocate_reloption(bits32 kinds, int type, const char *name, const char *desc,
case RELOPT_TYPE_BOOL:
size = sizeof(relopt_bool);
break;
+ case RELOPT_TYPE_TERNARY:
+ size = sizeof(relopt_ternary);
+ break;
case RELOPT_TYPE_INT:
size = sizeof(relopt_int);
break;
@@ -883,6 +907,54 @@ add_local_bool_reloption(local_relopts *relopts, const char *name,
add_local_reloption(relopts, (relopt_gen *) newoption, offset);
}
+/*
+ * init_ternary_reloption
+ * Allocate and initialize a new ternary reloption
+ */
+static relopt_ternary *
+init_ternary_reloption(bits32 kinds, const char *name, const char *desc,
+ ternary default_val, LOCKMODE lockmode)
+{
+ relopt_ternary *newoption;
+
+ newoption = (relopt_ternary *) allocate_reloption(kinds,
+ RELOPT_TYPE_TERNARY, name, desc, lockmode);
+ newoption->default_val = default_val;
+
+ return newoption;
+}
+
+/*
+ * add_ternary_reloption
+ * Add a new ternary reloption
+ */
+void
+add_ternary_reloption(bits32 kinds, const char *name, const char *desc,
+ ternary default_val, LOCKMODE lockmode)
+{
+ relopt_ternary *newoption = init_ternary_reloption(kinds, name, desc,
+ default_val, lockmode);
+
+ add_reloption((relopt_gen *) newoption);
+}
+
+/*
+ * add_local_ternary_reloption
+ * Add a new ternary local reloption
+ *
+ * 'offset' is offset of ternary-typed field.
+ */
+void
+add_local_ternary_reloption(local_relopts *relopts, const char *name,
+ const char *desc, ternary default_val,
+ int offset)
+{
+ relopt_ternary *newoption = init_ternary_reloption(RELOPT_KIND_LOCAL,
+ name, desc,
+ default_val, 0);
+
+ add_local_reloption(relopts, (relopt_gen *) newoption, offset);
+}
/*
* init_real_reloption
@@ -1617,6 +1689,18 @@ parse_one_reloption(relopt_value *option, char *text_str, int text_len,
option->gen->name, value)));
}
break;
+ case RELOPT_TYPE_TERNARY:
+ {
+ bool b;
+ parsed = parse_bool(value, &b);
+ option->values.ternary_val = b ? TERNARY_TRUE : TERNARY_FALSE;
+ if (validate && !parsed)
+ ereport(ERROR,
+ (errcode(ERRCODE_INVALID_PARAMETER_VALUE),
+ errmsg("invalid value for ternary option \"%s\": %s",
+ option->gen->name, value)));
+ }
+ break;
case RELOPT_TYPE_INT:
{
relopt_int *optint = (relopt_int *) option->gen;
@@ -1780,17 +1864,6 @@ fillRelOptions(void *rdopts, Size basesize,
char *itempos = ((char *) rdopts) + elems[j].offset;
char *string_val;
- /*
- * If isset_offset is provided, store whether the reloption is
- * set there.
- */
- if (elems[j].isset_offset > 0)
- {
- char *setpos = ((char *) rdopts) + elems[j].isset_offset;
-
- *(bool *) setpos = options[i].isset;
- }
-
switch (options[i].gen->type)
{
case RELOPT_TYPE_BOOL:
@@ -1798,6 +1871,11 @@ fillRelOptions(void *rdopts, Size basesize,
options[i].values.bool_val :
((relopt_bool *) options[i].gen)->default_val;
break;
+ case RELOPT_TYPE_TERNARY:
+ *(ternary *) itempos = options[i].isset ?
+ options[i].values.ternary_val :
+ ((relopt_ternary *) options[i].gen)->default_val;
+ break;
case RELOPT_TYPE_INT:
*(int *) itempos = options[i].isset ?
options[i].values.int_val :
@@ -1912,8 +1990,8 @@ default_reloptions(Datum reloptions, bool validate, relopt_kind kind)
offsetof(StdRdOptions, parallel_workers)},
{"vacuum_index_cleanup", RELOPT_TYPE_ENUM,
offsetof(StdRdOptions, vacuum_index_cleanup)},
- {"vacuum_truncate", RELOPT_TYPE_BOOL,
- offsetof(StdRdOptions, vacuum_truncate), offsetof(StdRdOptions, vacuum_truncate_set)},
+ {"vacuum_truncate", RELOPT_TYPE_TERNARY,
+ offsetof(StdRdOptions, vacuum_truncate)},
{"vacuum_max_eager_freeze_failure_rate", RELOPT_TYPE_REAL,
offsetof(StdRdOptions, vacuum_max_eager_freeze_failure_rate)}
};
@@ -1993,7 +2071,6 @@ build_local_reloptions(local_relopts *relopts, Datum options, bool validate)
elems[i].optname = opt->option->name;
elems[i].opttype = opt->option->type;
elems[i].offset = opt->offset;
- elems[i].isset_offset = 0; /* not supported for local relopts yet */
i++;
}
diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c
index 733ef40ae7..977babff54 100644
--- a/src/backend/commands/vacuum.c
+++ b/src/backend/commands/vacuum.c
@@ -2221,9 +2221,9 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams params,
{
StdRdOptions *opts = (StdRdOptions *) rel->rd_options;
- if (opts && opts->vacuum_truncate_set)
+ if (opts && opts->vacuum_truncate != TERNARY_UNSET)
{
- if (opts->vacuum_truncate)
+ if (opts->vacuum_truncate == TERNARY_TRUE)
params.truncate = VACOPTVALUE_ENABLED;
else
params.truncate = VACOPTVALUE_DISABLED;
diff --git a/src/include/access/reloptions.h b/src/include/access/reloptions.h
index a604a4702c..a436697658 100644
--- a/src/include/access/reloptions.h
+++ b/src/include/access/reloptions.h
@@ -29,6 +29,7 @@
typedef enum relopt_type
{
RELOPT_TYPE_BOOL,
+ RELOPT_TYPE_TERNARY, /* on, off, unset */
RELOPT_TYPE_INT,
RELOPT_TYPE_REAL,
RELOPT_TYPE_ENUM,
@@ -80,6 +81,7 @@ typedef struct relopt_value
union
{
bool bool_val;
+ ternary ternary_val;
int int_val;
double real_val;
int enum_val;
@@ -94,6 +96,12 @@ typedef struct relopt_bool
bool default_val;
} relopt_bool;
+typedef struct relopt_rernary
+{
+ relopt_gen gen;
+ int default_val;
+} relopt_ternary;
+
typedef struct relopt_int
{
relopt_gen gen;
@@ -152,19 +160,6 @@ typedef struct
const char *optname; /* option's name */
relopt_type opttype; /* option's datatype */
int offset; /* offset of field in result struct */
-
- /*
- * isset_offset is an optional offset of a field in the result struct that
- * stores whether the option is explicitly set for the relation or if it
- * just picked up the default value. In most cases, this can be
- * accomplished by giving the reloption a special out-of-range default
- * value (e.g., some integer reloptions use -2), but this isn't always
- * possible. For example, a Boolean reloption cannot be given an
- * out-of-range default, so we need another way to discover the source of
- * its value. This offset is only used if given a value greater than
- * zero.
- */
- int isset_offset;
} relopt_parse_elt;
/* Local reloption definition */
@@ -195,6 +190,8 @@ typedef struct local_relopts
extern relopt_kind add_reloption_kind(void);
extern void add_bool_reloption(bits32 kinds, const char *name, const char *desc,
bool default_val, LOCKMODE lockmode);
+extern void add_ternary_reloption(bits32 kinds, const char *name,
+ const char *desc, int default_val, LOCKMODE lockmode);
extern void add_int_reloption(bits32 kinds, const char *name, const char *desc,
int default_val, int min_val, int max_val,
LOCKMODE lockmode);
@@ -214,6 +211,9 @@ extern void register_reloptions_validator(local_relopts *relopts,
extern void add_local_bool_reloption(local_relopts *relopts, const char *name,
const char *desc, bool default_val,
int offset);
+extern void add_local_ternary_reloption(local_relopts *relopts,
+ const char *name, const char *desc,
+ ternary default_val, int offset);
extern void add_local_int_reloption(local_relopts *relopts, const char *name,
const char *desc, int default_val,
int min_val, int max_val, int offset);
diff --git a/src/include/c.h b/src/include/c.h
index 39022f8a9d..9c59ccccf8 100644
--- a/src/include/c.h
+++ b/src/include/c.h
@@ -488,6 +488,22 @@ typedef void (*pg_funcptr_t) (void);
#include <stdbool.h>
+/*
+ * ternary
+ * Boolean value with an extrea "unset" option
+ *
+ * Ternary data type is used in relation options that can be "true", "false" or
+ * "unset". Since relation options are used deep inside the PostgreSQL code,
+ * this type is declared globally.
+*/
+
+typedef enum ternary
+{
+ TERNARY_FALSE = 0,
+ TERNARY_TRUE = 1,
+ TERNARY_UNSET = -1
+} ternary;
+
/* ----------------------------------------------------------------
* Section 3: standard system types
diff --git a/src/include/utils/rel.h b/src/include/utils/rel.h
index b552359915..7346d618f9 100644
--- a/src/include/utils/rel.h
+++ b/src/include/utils/rel.h
@@ -346,8 +346,7 @@ typedef struct StdRdOptions
bool user_catalog_table; /* use as an additional catalog relation */
int parallel_workers; /* max number of parallel workers */
StdRdOptIndexCleanup vacuum_index_cleanup; /* controls index vacuuming */
- bool vacuum_truncate; /* enables vacuum to truncate a relation */
- bool vacuum_truncate_set; /* whether vacuum_truncate is set */
+ ternary vacuum_truncate; /* enables vacuum to truncate a relation */
/*
* Fraction of pages in a relation that vacuum can eagerly scan and fail
--
2.39.2
--nextPart4155600.3ZeAukHxDK
Content-Disposition: attachment;
filename="v2a-0003-Add-alias-to-be-used-as-unset-state.patch"
Content-Transfer-Encoding: 7Bit
Content-Type: text/x-patch; charset="unicode-2-0-utf-8";
name="v2a-0003-Add-alias-to-be-used-as-unset-state.patch"
^ permalink raw reply [nested|flat] 868+ messages in thread
* [PATCH v2 2/4] Introduce ternary reloptions
@ 2025-09-04 14:41 Nikolay Shaplov <[email protected]>
0 siblings, 0 replies; 868+ messages in thread
From: Nikolay Shaplov @ 2025-09-04 14:41 UTC (permalink / raw)
Introduce ternary reloption as a replacement for current `vacuum_truncate`
implementation. Remove `vacuum_truncate_set` additional flag and using
`TERNARY_UNSET` value instead.
---
src/backend/access/common/reloptions.c | 129 ++++++++++++++++++++-----
src/backend/commands/vacuum.c | 4 +-
src/include/access/reloptions.h | 26 ++---
src/include/c.h | 16 +++
src/include/utils/rel.h | 3 +-
5 files changed, 135 insertions(+), 43 deletions(-)
diff --git a/src/backend/access/common/reloptions.c b/src/backend/access/common/reloptions.c
index 0af3fea68f..f543f11001 100644
--- a/src/backend/access/common/reloptions.c
+++ b/src/backend/access/common/reloptions.c
@@ -40,9 +40,9 @@
*
* To add an option:
*
- * (i) decide on a type (bool, integer, real, enum, string), name, default
- * value, upper and lower bounds (if applicable); for strings, consider a
- * validation routine.
+ * (i) decide on a type (bool, ternary, integer, real, enum, string), name,
+ * default value, upper and lower bounds (if applicable); for strings,
+ * consider a validation routine.
* (ii) add a record below (or use add_<type>_reloption).
* (iii) add it to the appropriate options struct (perhaps StdRdOptions)
* (iv) add it to the appropriate handling routine (perhaps
@@ -147,15 +147,6 @@ static relopt_bool boolRelOpts[] =
},
false
},
- {
- {
- "vacuum_truncate",
- "Enables vacuum to truncate empty pages at the end of this table",
- RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
- ShareUpdateExclusiveLock
- },
- true
- },
{
{
"deduplicate_items",
@@ -170,6 +161,21 @@ static relopt_bool boolRelOpts[] =
{{NULL}}
};
+static relopt_ternary ternaryRelOpts[] =
+{
+ {
+ {
+ "vacuum_truncate",
+ "Enables vacuum to truncate empty pages at the end of this table",
+ RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
+ ShareUpdateExclusiveLock
+ },
+ TERNARY_UNSET
+ },
+ /* list terminator */
+ {{NULL}}
+};
+
static relopt_int intRelOpts[] =
{
{
@@ -600,6 +606,13 @@ initialize_reloptions(void)
boolRelOpts[i].gen.lockmode));
j++;
}
+ for (i = 0; ternaryRelOpts[i].gen.name; i++)
+ {
+ Assert(DoLockModesConflict(ternaryRelOpts[i].gen.lockmode,
+ ternaryRelOpts[i].gen.lockmode));
+ j++;
+ }
+
for (i = 0; intRelOpts[i].gen.name; i++)
{
Assert(DoLockModesConflict(intRelOpts[i].gen.lockmode,
@@ -640,6 +653,14 @@ initialize_reloptions(void)
j++;
}
+ for (i = 0; ternaryRelOpts[i].gen.name; i++)
+ {
+ relOpts[j] = &ternaryRelOpts[i].gen;
+ relOpts[j]->type = RELOPT_TYPE_TERNARY;
+ relOpts[j]->namelen = strlen(relOpts[j]->name);
+ j++;
+ }
+
for (i = 0; intRelOpts[i].gen.name; i++)
{
relOpts[j] = &intRelOpts[i].gen;
@@ -800,6 +821,9 @@ allocate_reloption(bits32 kinds, int type, const char *name, const char *desc,
case RELOPT_TYPE_BOOL:
size = sizeof(relopt_bool);
break;
+ case RELOPT_TYPE_TERNARY:
+ size = sizeof(relopt_ternary);
+ break;
case RELOPT_TYPE_INT:
size = sizeof(relopt_int);
break;
@@ -883,6 +907,54 @@ add_local_bool_reloption(local_relopts *relopts, const char *name,
add_local_reloption(relopts, (relopt_gen *) newoption, offset);
}
+/*
+ * init_ternary_reloption
+ * Allocate and initialize a new ternary reloption
+ */
+static relopt_ternary *
+init_ternary_reloption(bits32 kinds, const char *name, const char *desc,
+ ternary default_val, LOCKMODE lockmode)
+{
+ relopt_ternary *newoption;
+
+ newoption = (relopt_ternary *) allocate_reloption(kinds,
+ RELOPT_TYPE_TERNARY, name, desc, lockmode);
+ newoption->default_val = default_val;
+
+ return newoption;
+}
+
+/*
+ * add_ternary_reloption
+ * Add a new ternary reloption
+ */
+void
+add_ternary_reloption(bits32 kinds, const char *name, const char *desc,
+ ternary default_val, LOCKMODE lockmode)
+{
+ relopt_ternary *newoption = init_ternary_reloption(kinds, name, desc,
+ default_val, lockmode);
+
+ add_reloption((relopt_gen *) newoption);
+}
+
+/*
+ * add_local_ternary_reloption
+ * Add a new ternary local reloption
+ *
+ * 'offset' is offset of ternary-typed field.
+ */
+void
+add_local_ternary_reloption(local_relopts *relopts, const char *name,
+ const char *desc, ternary default_val,
+ int offset)
+{
+ relopt_ternary *newoption = init_ternary_reloption(RELOPT_KIND_LOCAL,
+ name, desc,
+ default_val, 0);
+
+ add_local_reloption(relopts, (relopt_gen *) newoption, offset);
+}
/*
* init_real_reloption
@@ -1617,6 +1689,18 @@ parse_one_reloption(relopt_value *option, char *text_str, int text_len,
option->gen->name, value)));
}
break;
+ case RELOPT_TYPE_TERNARY:
+ {
+ bool b;
+ parsed = parse_bool(value, &b);
+ option->values.ternary_val = b ? TERNARY_TRUE : TERNARY_FALSE;
+ if (validate && !parsed)
+ ereport(ERROR,
+ (errcode(ERRCODE_INVALID_PARAMETER_VALUE),
+ errmsg("invalid value for ternary option \"%s\": %s",
+ option->gen->name, value)));
+ }
+ break;
case RELOPT_TYPE_INT:
{
relopt_int *optint = (relopt_int *) option->gen;
@@ -1780,17 +1864,6 @@ fillRelOptions(void *rdopts, Size basesize,
char *itempos = ((char *) rdopts) + elems[j].offset;
char *string_val;
- /*
- * If isset_offset is provided, store whether the reloption is
- * set there.
- */
- if (elems[j].isset_offset > 0)
- {
- char *setpos = ((char *) rdopts) + elems[j].isset_offset;
-
- *(bool *) setpos = options[i].isset;
- }
-
switch (options[i].gen->type)
{
case RELOPT_TYPE_BOOL:
@@ -1798,6 +1871,11 @@ fillRelOptions(void *rdopts, Size basesize,
options[i].values.bool_val :
((relopt_bool *) options[i].gen)->default_val;
break;
+ case RELOPT_TYPE_TERNARY:
+ *(ternary *) itempos = options[i].isset ?
+ options[i].values.ternary_val :
+ ((relopt_ternary *) options[i].gen)->default_val;
+ break;
case RELOPT_TYPE_INT:
*(int *) itempos = options[i].isset ?
options[i].values.int_val :
@@ -1912,8 +1990,8 @@ default_reloptions(Datum reloptions, bool validate, relopt_kind kind)
offsetof(StdRdOptions, parallel_workers)},
{"vacuum_index_cleanup", RELOPT_TYPE_ENUM,
offsetof(StdRdOptions, vacuum_index_cleanup)},
- {"vacuum_truncate", RELOPT_TYPE_BOOL,
- offsetof(StdRdOptions, vacuum_truncate), offsetof(StdRdOptions, vacuum_truncate_set)},
+ {"vacuum_truncate", RELOPT_TYPE_TERNARY,
+ offsetof(StdRdOptions, vacuum_truncate)},
{"vacuum_max_eager_freeze_failure_rate", RELOPT_TYPE_REAL,
offsetof(StdRdOptions, vacuum_max_eager_freeze_failure_rate)}
};
@@ -1993,7 +2071,6 @@ build_local_reloptions(local_relopts *relopts, Datum options, bool validate)
elems[i].optname = opt->option->name;
elems[i].opttype = opt->option->type;
elems[i].offset = opt->offset;
- elems[i].isset_offset = 0; /* not supported for local relopts yet */
i++;
}
diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c
index 733ef40ae7..977babff54 100644
--- a/src/backend/commands/vacuum.c
+++ b/src/backend/commands/vacuum.c
@@ -2221,9 +2221,9 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams params,
{
StdRdOptions *opts = (StdRdOptions *) rel->rd_options;
- if (opts && opts->vacuum_truncate_set)
+ if (opts && opts->vacuum_truncate != TERNARY_UNSET)
{
- if (opts->vacuum_truncate)
+ if (opts->vacuum_truncate == TERNARY_TRUE)
params.truncate = VACOPTVALUE_ENABLED;
else
params.truncate = VACOPTVALUE_DISABLED;
diff --git a/src/include/access/reloptions.h b/src/include/access/reloptions.h
index a604a4702c..a436697658 100644
--- a/src/include/access/reloptions.h
+++ b/src/include/access/reloptions.h
@@ -29,6 +29,7 @@
typedef enum relopt_type
{
RELOPT_TYPE_BOOL,
+ RELOPT_TYPE_TERNARY, /* on, off, unset */
RELOPT_TYPE_INT,
RELOPT_TYPE_REAL,
RELOPT_TYPE_ENUM,
@@ -80,6 +81,7 @@ typedef struct relopt_value
union
{
bool bool_val;
+ ternary ternary_val;
int int_val;
double real_val;
int enum_val;
@@ -94,6 +96,12 @@ typedef struct relopt_bool
bool default_val;
} relopt_bool;
+typedef struct relopt_rernary
+{
+ relopt_gen gen;
+ int default_val;
+} relopt_ternary;
+
typedef struct relopt_int
{
relopt_gen gen;
@@ -152,19 +160,6 @@ typedef struct
const char *optname; /* option's name */
relopt_type opttype; /* option's datatype */
int offset; /* offset of field in result struct */
-
- /*
- * isset_offset is an optional offset of a field in the result struct that
- * stores whether the option is explicitly set for the relation or if it
- * just picked up the default value. In most cases, this can be
- * accomplished by giving the reloption a special out-of-range default
- * value (e.g., some integer reloptions use -2), but this isn't always
- * possible. For example, a Boolean reloption cannot be given an
- * out-of-range default, so we need another way to discover the source of
- * its value. This offset is only used if given a value greater than
- * zero.
- */
- int isset_offset;
} relopt_parse_elt;
/* Local reloption definition */
@@ -195,6 +190,8 @@ typedef struct local_relopts
extern relopt_kind add_reloption_kind(void);
extern void add_bool_reloption(bits32 kinds, const char *name, const char *desc,
bool default_val, LOCKMODE lockmode);
+extern void add_ternary_reloption(bits32 kinds, const char *name,
+ const char *desc, int default_val, LOCKMODE lockmode);
extern void add_int_reloption(bits32 kinds, const char *name, const char *desc,
int default_val, int min_val, int max_val,
LOCKMODE lockmode);
@@ -214,6 +211,9 @@ extern void register_reloptions_validator(local_relopts *relopts,
extern void add_local_bool_reloption(local_relopts *relopts, const char *name,
const char *desc, bool default_val,
int offset);
+extern void add_local_ternary_reloption(local_relopts *relopts,
+ const char *name, const char *desc,
+ ternary default_val, int offset);
extern void add_local_int_reloption(local_relopts *relopts, const char *name,
const char *desc, int default_val,
int min_val, int max_val, int offset);
diff --git a/src/include/c.h b/src/include/c.h
index 39022f8a9d..9c59ccccf8 100644
--- a/src/include/c.h
+++ b/src/include/c.h
@@ -488,6 +488,22 @@ typedef void (*pg_funcptr_t) (void);
#include <stdbool.h>
+/*
+ * ternary
+ * Boolean value with an extrea "unset" option
+ *
+ * Ternary data type is used in relation options that can be "true", "false" or
+ * "unset". Since relation options are used deep inside the PostgreSQL code,
+ * this type is declared globally.
+*/
+
+typedef enum ternary
+{
+ TERNARY_FALSE = 0,
+ TERNARY_TRUE = 1,
+ TERNARY_UNSET = -1
+} ternary;
+
/* ----------------------------------------------------------------
* Section 3: standard system types
diff --git a/src/include/utils/rel.h b/src/include/utils/rel.h
index b552359915..7346d618f9 100644
--- a/src/include/utils/rel.h
+++ b/src/include/utils/rel.h
@@ -346,8 +346,7 @@ typedef struct StdRdOptions
bool user_catalog_table; /* use as an additional catalog relation */
int parallel_workers; /* max number of parallel workers */
StdRdOptIndexCleanup vacuum_index_cleanup; /* controls index vacuuming */
- bool vacuum_truncate; /* enables vacuum to truncate a relation */
- bool vacuum_truncate_set; /* whether vacuum_truncate is set */
+ ternary vacuum_truncate; /* enables vacuum to truncate a relation */
/*
* Fraction of pages in a relation that vacuum can eagerly scan and fail
--
2.39.2
--nextPart5129932.5fSG56mABF
Content-Disposition: attachment;
filename="v2-0003-Add-alias-to-be-used-as-unset-state.patch"
Content-Transfer-Encoding: 7Bit
Content-Type: text/x-patch; charset="unicode-2-0-utf-8";
name="v2-0003-Add-alias-to-be-used-as-unset-state.patch"
^ permalink raw reply [nested|flat] 868+ messages in thread
* [PATCH v2a 2/4] Introduce ternary reloptions
@ 2025-09-04 14:41 Nikolay Shaplov <[email protected]>
0 siblings, 0 replies; 868+ messages in thread
From: Nikolay Shaplov @ 2025-09-04 14:41 UTC (permalink / raw)
Introduce ternary reloption as a replacement for current `vacuum_truncate`
implementation. Remove `vacuum_truncate_set` additional flag and using
`TERNARY_UNSET` value instead.
---
src/backend/access/common/reloptions.c | 129 ++++++++++++++++++++-----
src/backend/commands/vacuum.c | 4 +-
src/include/access/reloptions.h | 26 ++---
src/include/c.h | 16 +++
src/include/utils/rel.h | 3 +-
5 files changed, 135 insertions(+), 43 deletions(-)
diff --git a/src/backend/access/common/reloptions.c b/src/backend/access/common/reloptions.c
index 0af3fea68f..f543f11001 100644
--- a/src/backend/access/common/reloptions.c
+++ b/src/backend/access/common/reloptions.c
@@ -40,9 +40,9 @@
*
* To add an option:
*
- * (i) decide on a type (bool, integer, real, enum, string), name, default
- * value, upper and lower bounds (if applicable); for strings, consider a
- * validation routine.
+ * (i) decide on a type (bool, ternary, integer, real, enum, string), name,
+ * default value, upper and lower bounds (if applicable); for strings,
+ * consider a validation routine.
* (ii) add a record below (or use add_<type>_reloption).
* (iii) add it to the appropriate options struct (perhaps StdRdOptions)
* (iv) add it to the appropriate handling routine (perhaps
@@ -147,15 +147,6 @@ static relopt_bool boolRelOpts[] =
},
false
},
- {
- {
- "vacuum_truncate",
- "Enables vacuum to truncate empty pages at the end of this table",
- RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
- ShareUpdateExclusiveLock
- },
- true
- },
{
{
"deduplicate_items",
@@ -170,6 +161,21 @@ static relopt_bool boolRelOpts[] =
{{NULL}}
};
+static relopt_ternary ternaryRelOpts[] =
+{
+ {
+ {
+ "vacuum_truncate",
+ "Enables vacuum to truncate empty pages at the end of this table",
+ RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
+ ShareUpdateExclusiveLock
+ },
+ TERNARY_UNSET
+ },
+ /* list terminator */
+ {{NULL}}
+};
+
static relopt_int intRelOpts[] =
{
{
@@ -600,6 +606,13 @@ initialize_reloptions(void)
boolRelOpts[i].gen.lockmode));
j++;
}
+ for (i = 0; ternaryRelOpts[i].gen.name; i++)
+ {
+ Assert(DoLockModesConflict(ternaryRelOpts[i].gen.lockmode,
+ ternaryRelOpts[i].gen.lockmode));
+ j++;
+ }
+
for (i = 0; intRelOpts[i].gen.name; i++)
{
Assert(DoLockModesConflict(intRelOpts[i].gen.lockmode,
@@ -640,6 +653,14 @@ initialize_reloptions(void)
j++;
}
+ for (i = 0; ternaryRelOpts[i].gen.name; i++)
+ {
+ relOpts[j] = &ternaryRelOpts[i].gen;
+ relOpts[j]->type = RELOPT_TYPE_TERNARY;
+ relOpts[j]->namelen = strlen(relOpts[j]->name);
+ j++;
+ }
+
for (i = 0; intRelOpts[i].gen.name; i++)
{
relOpts[j] = &intRelOpts[i].gen;
@@ -800,6 +821,9 @@ allocate_reloption(bits32 kinds, int type, const char *name, const char *desc,
case RELOPT_TYPE_BOOL:
size = sizeof(relopt_bool);
break;
+ case RELOPT_TYPE_TERNARY:
+ size = sizeof(relopt_ternary);
+ break;
case RELOPT_TYPE_INT:
size = sizeof(relopt_int);
break;
@@ -883,6 +907,54 @@ add_local_bool_reloption(local_relopts *relopts, const char *name,
add_local_reloption(relopts, (relopt_gen *) newoption, offset);
}
+/*
+ * init_ternary_reloption
+ * Allocate and initialize a new ternary reloption
+ */
+static relopt_ternary *
+init_ternary_reloption(bits32 kinds, const char *name, const char *desc,
+ ternary default_val, LOCKMODE lockmode)
+{
+ relopt_ternary *newoption;
+
+ newoption = (relopt_ternary *) allocate_reloption(kinds,
+ RELOPT_TYPE_TERNARY, name, desc, lockmode);
+ newoption->default_val = default_val;
+
+ return newoption;
+}
+
+/*
+ * add_ternary_reloption
+ * Add a new ternary reloption
+ */
+void
+add_ternary_reloption(bits32 kinds, const char *name, const char *desc,
+ ternary default_val, LOCKMODE lockmode)
+{
+ relopt_ternary *newoption = init_ternary_reloption(kinds, name, desc,
+ default_val, lockmode);
+
+ add_reloption((relopt_gen *) newoption);
+}
+
+/*
+ * add_local_ternary_reloption
+ * Add a new ternary local reloption
+ *
+ * 'offset' is offset of ternary-typed field.
+ */
+void
+add_local_ternary_reloption(local_relopts *relopts, const char *name,
+ const char *desc, ternary default_val,
+ int offset)
+{
+ relopt_ternary *newoption = init_ternary_reloption(RELOPT_KIND_LOCAL,
+ name, desc,
+ default_val, 0);
+
+ add_local_reloption(relopts, (relopt_gen *) newoption, offset);
+}
/*
* init_real_reloption
@@ -1617,6 +1689,18 @@ parse_one_reloption(relopt_value *option, char *text_str, int text_len,
option->gen->name, value)));
}
break;
+ case RELOPT_TYPE_TERNARY:
+ {
+ bool b;
+ parsed = parse_bool(value, &b);
+ option->values.ternary_val = b ? TERNARY_TRUE : TERNARY_FALSE;
+ if (validate && !parsed)
+ ereport(ERROR,
+ (errcode(ERRCODE_INVALID_PARAMETER_VALUE),
+ errmsg("invalid value for ternary option \"%s\": %s",
+ option->gen->name, value)));
+ }
+ break;
case RELOPT_TYPE_INT:
{
relopt_int *optint = (relopt_int *) option->gen;
@@ -1780,17 +1864,6 @@ fillRelOptions(void *rdopts, Size basesize,
char *itempos = ((char *) rdopts) + elems[j].offset;
char *string_val;
- /*
- * If isset_offset is provided, store whether the reloption is
- * set there.
- */
- if (elems[j].isset_offset > 0)
- {
- char *setpos = ((char *) rdopts) + elems[j].isset_offset;
-
- *(bool *) setpos = options[i].isset;
- }
-
switch (options[i].gen->type)
{
case RELOPT_TYPE_BOOL:
@@ -1798,6 +1871,11 @@ fillRelOptions(void *rdopts, Size basesize,
options[i].values.bool_val :
((relopt_bool *) options[i].gen)->default_val;
break;
+ case RELOPT_TYPE_TERNARY:
+ *(ternary *) itempos = options[i].isset ?
+ options[i].values.ternary_val :
+ ((relopt_ternary *) options[i].gen)->default_val;
+ break;
case RELOPT_TYPE_INT:
*(int *) itempos = options[i].isset ?
options[i].values.int_val :
@@ -1912,8 +1990,8 @@ default_reloptions(Datum reloptions, bool validate, relopt_kind kind)
offsetof(StdRdOptions, parallel_workers)},
{"vacuum_index_cleanup", RELOPT_TYPE_ENUM,
offsetof(StdRdOptions, vacuum_index_cleanup)},
- {"vacuum_truncate", RELOPT_TYPE_BOOL,
- offsetof(StdRdOptions, vacuum_truncate), offsetof(StdRdOptions, vacuum_truncate_set)},
+ {"vacuum_truncate", RELOPT_TYPE_TERNARY,
+ offsetof(StdRdOptions, vacuum_truncate)},
{"vacuum_max_eager_freeze_failure_rate", RELOPT_TYPE_REAL,
offsetof(StdRdOptions, vacuum_max_eager_freeze_failure_rate)}
};
@@ -1993,7 +2071,6 @@ build_local_reloptions(local_relopts *relopts, Datum options, bool validate)
elems[i].optname = opt->option->name;
elems[i].opttype = opt->option->type;
elems[i].offset = opt->offset;
- elems[i].isset_offset = 0; /* not supported for local relopts yet */
i++;
}
diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c
index 733ef40ae7..977babff54 100644
--- a/src/backend/commands/vacuum.c
+++ b/src/backend/commands/vacuum.c
@@ -2221,9 +2221,9 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams params,
{
StdRdOptions *opts = (StdRdOptions *) rel->rd_options;
- if (opts && opts->vacuum_truncate_set)
+ if (opts && opts->vacuum_truncate != TERNARY_UNSET)
{
- if (opts->vacuum_truncate)
+ if (opts->vacuum_truncate == TERNARY_TRUE)
params.truncate = VACOPTVALUE_ENABLED;
else
params.truncate = VACOPTVALUE_DISABLED;
diff --git a/src/include/access/reloptions.h b/src/include/access/reloptions.h
index a604a4702c..a436697658 100644
--- a/src/include/access/reloptions.h
+++ b/src/include/access/reloptions.h
@@ -29,6 +29,7 @@
typedef enum relopt_type
{
RELOPT_TYPE_BOOL,
+ RELOPT_TYPE_TERNARY, /* on, off, unset */
RELOPT_TYPE_INT,
RELOPT_TYPE_REAL,
RELOPT_TYPE_ENUM,
@@ -80,6 +81,7 @@ typedef struct relopt_value
union
{
bool bool_val;
+ ternary ternary_val;
int int_val;
double real_val;
int enum_val;
@@ -94,6 +96,12 @@ typedef struct relopt_bool
bool default_val;
} relopt_bool;
+typedef struct relopt_rernary
+{
+ relopt_gen gen;
+ int default_val;
+} relopt_ternary;
+
typedef struct relopt_int
{
relopt_gen gen;
@@ -152,19 +160,6 @@ typedef struct
const char *optname; /* option's name */
relopt_type opttype; /* option's datatype */
int offset; /* offset of field in result struct */
-
- /*
- * isset_offset is an optional offset of a field in the result struct that
- * stores whether the option is explicitly set for the relation or if it
- * just picked up the default value. In most cases, this can be
- * accomplished by giving the reloption a special out-of-range default
- * value (e.g., some integer reloptions use -2), but this isn't always
- * possible. For example, a Boolean reloption cannot be given an
- * out-of-range default, so we need another way to discover the source of
- * its value. This offset is only used if given a value greater than
- * zero.
- */
- int isset_offset;
} relopt_parse_elt;
/* Local reloption definition */
@@ -195,6 +190,8 @@ typedef struct local_relopts
extern relopt_kind add_reloption_kind(void);
extern void add_bool_reloption(bits32 kinds, const char *name, const char *desc,
bool default_val, LOCKMODE lockmode);
+extern void add_ternary_reloption(bits32 kinds, const char *name,
+ const char *desc, int default_val, LOCKMODE lockmode);
extern void add_int_reloption(bits32 kinds, const char *name, const char *desc,
int default_val, int min_val, int max_val,
LOCKMODE lockmode);
@@ -214,6 +211,9 @@ extern void register_reloptions_validator(local_relopts *relopts,
extern void add_local_bool_reloption(local_relopts *relopts, const char *name,
const char *desc, bool default_val,
int offset);
+extern void add_local_ternary_reloption(local_relopts *relopts,
+ const char *name, const char *desc,
+ ternary default_val, int offset);
extern void add_local_int_reloption(local_relopts *relopts, const char *name,
const char *desc, int default_val,
int min_val, int max_val, int offset);
diff --git a/src/include/c.h b/src/include/c.h
index 39022f8a9d..9c59ccccf8 100644
--- a/src/include/c.h
+++ b/src/include/c.h
@@ -488,6 +488,22 @@ typedef void (*pg_funcptr_t) (void);
#include <stdbool.h>
+/*
+ * ternary
+ * Boolean value with an extrea "unset" option
+ *
+ * Ternary data type is used in relation options that can be "true", "false" or
+ * "unset". Since relation options are used deep inside the PostgreSQL code,
+ * this type is declared globally.
+*/
+
+typedef enum ternary
+{
+ TERNARY_FALSE = 0,
+ TERNARY_TRUE = 1,
+ TERNARY_UNSET = -1
+} ternary;
+
/* ----------------------------------------------------------------
* Section 3: standard system types
diff --git a/src/include/utils/rel.h b/src/include/utils/rel.h
index b552359915..7346d618f9 100644
--- a/src/include/utils/rel.h
+++ b/src/include/utils/rel.h
@@ -346,8 +346,7 @@ typedef struct StdRdOptions
bool user_catalog_table; /* use as an additional catalog relation */
int parallel_workers; /* max number of parallel workers */
StdRdOptIndexCleanup vacuum_index_cleanup; /* controls index vacuuming */
- bool vacuum_truncate; /* enables vacuum to truncate a relation */
- bool vacuum_truncate_set; /* whether vacuum_truncate is set */
+ ternary vacuum_truncate; /* enables vacuum to truncate a relation */
/*
* Fraction of pages in a relation that vacuum can eagerly scan and fail
--
2.39.2
--nextPart4155600.3ZeAukHxDK
Content-Disposition: attachment;
filename="v2a-0003-Add-alias-to-be-used-as-unset-state.patch"
Content-Transfer-Encoding: 7Bit
Content-Type: text/x-patch; charset="unicode-2-0-utf-8";
name="v2a-0003-Add-alias-to-be-used-as-unset-state.patch"
^ permalink raw reply [nested|flat] 868+ messages in thread
* [PATCH v2 2/4] Introduce ternary reloptions
@ 2025-09-04 14:41 Nikolay Shaplov <[email protected]>
0 siblings, 0 replies; 868+ messages in thread
From: Nikolay Shaplov @ 2025-09-04 14:41 UTC (permalink / raw)
Introduce ternary reloption as a replacement for current `vacuum_truncate`
implementation. Remove `vacuum_truncate_set` additional flag and using
`TERNARY_UNSET` value instead.
---
src/backend/access/common/reloptions.c | 129 ++++++++++++++++++++-----
src/backend/commands/vacuum.c | 4 +-
src/include/access/reloptions.h | 26 ++---
src/include/c.h | 16 +++
src/include/utils/rel.h | 3 +-
5 files changed, 135 insertions(+), 43 deletions(-)
diff --git a/src/backend/access/common/reloptions.c b/src/backend/access/common/reloptions.c
index 0af3fea68f..f543f11001 100644
--- a/src/backend/access/common/reloptions.c
+++ b/src/backend/access/common/reloptions.c
@@ -40,9 +40,9 @@
*
* To add an option:
*
- * (i) decide on a type (bool, integer, real, enum, string), name, default
- * value, upper and lower bounds (if applicable); for strings, consider a
- * validation routine.
+ * (i) decide on a type (bool, ternary, integer, real, enum, string), name,
+ * default value, upper and lower bounds (if applicable); for strings,
+ * consider a validation routine.
* (ii) add a record below (or use add_<type>_reloption).
* (iii) add it to the appropriate options struct (perhaps StdRdOptions)
* (iv) add it to the appropriate handling routine (perhaps
@@ -147,15 +147,6 @@ static relopt_bool boolRelOpts[] =
},
false
},
- {
- {
- "vacuum_truncate",
- "Enables vacuum to truncate empty pages at the end of this table",
- RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
- ShareUpdateExclusiveLock
- },
- true
- },
{
{
"deduplicate_items",
@@ -170,6 +161,21 @@ static relopt_bool boolRelOpts[] =
{{NULL}}
};
+static relopt_ternary ternaryRelOpts[] =
+{
+ {
+ {
+ "vacuum_truncate",
+ "Enables vacuum to truncate empty pages at the end of this table",
+ RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
+ ShareUpdateExclusiveLock
+ },
+ TERNARY_UNSET
+ },
+ /* list terminator */
+ {{NULL}}
+};
+
static relopt_int intRelOpts[] =
{
{
@@ -600,6 +606,13 @@ initialize_reloptions(void)
boolRelOpts[i].gen.lockmode));
j++;
}
+ for (i = 0; ternaryRelOpts[i].gen.name; i++)
+ {
+ Assert(DoLockModesConflict(ternaryRelOpts[i].gen.lockmode,
+ ternaryRelOpts[i].gen.lockmode));
+ j++;
+ }
+
for (i = 0; intRelOpts[i].gen.name; i++)
{
Assert(DoLockModesConflict(intRelOpts[i].gen.lockmode,
@@ -640,6 +653,14 @@ initialize_reloptions(void)
j++;
}
+ for (i = 0; ternaryRelOpts[i].gen.name; i++)
+ {
+ relOpts[j] = &ternaryRelOpts[i].gen;
+ relOpts[j]->type = RELOPT_TYPE_TERNARY;
+ relOpts[j]->namelen = strlen(relOpts[j]->name);
+ j++;
+ }
+
for (i = 0; intRelOpts[i].gen.name; i++)
{
relOpts[j] = &intRelOpts[i].gen;
@@ -800,6 +821,9 @@ allocate_reloption(bits32 kinds, int type, const char *name, const char *desc,
case RELOPT_TYPE_BOOL:
size = sizeof(relopt_bool);
break;
+ case RELOPT_TYPE_TERNARY:
+ size = sizeof(relopt_ternary);
+ break;
case RELOPT_TYPE_INT:
size = sizeof(relopt_int);
break;
@@ -883,6 +907,54 @@ add_local_bool_reloption(local_relopts *relopts, const char *name,
add_local_reloption(relopts, (relopt_gen *) newoption, offset);
}
+/*
+ * init_ternary_reloption
+ * Allocate and initialize a new ternary reloption
+ */
+static relopt_ternary *
+init_ternary_reloption(bits32 kinds, const char *name, const char *desc,
+ ternary default_val, LOCKMODE lockmode)
+{
+ relopt_ternary *newoption;
+
+ newoption = (relopt_ternary *) allocate_reloption(kinds,
+ RELOPT_TYPE_TERNARY, name, desc, lockmode);
+ newoption->default_val = default_val;
+
+ return newoption;
+}
+
+/*
+ * add_ternary_reloption
+ * Add a new ternary reloption
+ */
+void
+add_ternary_reloption(bits32 kinds, const char *name, const char *desc,
+ ternary default_val, LOCKMODE lockmode)
+{
+ relopt_ternary *newoption = init_ternary_reloption(kinds, name, desc,
+ default_val, lockmode);
+
+ add_reloption((relopt_gen *) newoption);
+}
+
+/*
+ * add_local_ternary_reloption
+ * Add a new ternary local reloption
+ *
+ * 'offset' is offset of ternary-typed field.
+ */
+void
+add_local_ternary_reloption(local_relopts *relopts, const char *name,
+ const char *desc, ternary default_val,
+ int offset)
+{
+ relopt_ternary *newoption = init_ternary_reloption(RELOPT_KIND_LOCAL,
+ name, desc,
+ default_val, 0);
+
+ add_local_reloption(relopts, (relopt_gen *) newoption, offset);
+}
/*
* init_real_reloption
@@ -1617,6 +1689,18 @@ parse_one_reloption(relopt_value *option, char *text_str, int text_len,
option->gen->name, value)));
}
break;
+ case RELOPT_TYPE_TERNARY:
+ {
+ bool b;
+ parsed = parse_bool(value, &b);
+ option->values.ternary_val = b ? TERNARY_TRUE : TERNARY_FALSE;
+ if (validate && !parsed)
+ ereport(ERROR,
+ (errcode(ERRCODE_INVALID_PARAMETER_VALUE),
+ errmsg("invalid value for ternary option \"%s\": %s",
+ option->gen->name, value)));
+ }
+ break;
case RELOPT_TYPE_INT:
{
relopt_int *optint = (relopt_int *) option->gen;
@@ -1780,17 +1864,6 @@ fillRelOptions(void *rdopts, Size basesize,
char *itempos = ((char *) rdopts) + elems[j].offset;
char *string_val;
- /*
- * If isset_offset is provided, store whether the reloption is
- * set there.
- */
- if (elems[j].isset_offset > 0)
- {
- char *setpos = ((char *) rdopts) + elems[j].isset_offset;
-
- *(bool *) setpos = options[i].isset;
- }
-
switch (options[i].gen->type)
{
case RELOPT_TYPE_BOOL:
@@ -1798,6 +1871,11 @@ fillRelOptions(void *rdopts, Size basesize,
options[i].values.bool_val :
((relopt_bool *) options[i].gen)->default_val;
break;
+ case RELOPT_TYPE_TERNARY:
+ *(ternary *) itempos = options[i].isset ?
+ options[i].values.ternary_val :
+ ((relopt_ternary *) options[i].gen)->default_val;
+ break;
case RELOPT_TYPE_INT:
*(int *) itempos = options[i].isset ?
options[i].values.int_val :
@@ -1912,8 +1990,8 @@ default_reloptions(Datum reloptions, bool validate, relopt_kind kind)
offsetof(StdRdOptions, parallel_workers)},
{"vacuum_index_cleanup", RELOPT_TYPE_ENUM,
offsetof(StdRdOptions, vacuum_index_cleanup)},
- {"vacuum_truncate", RELOPT_TYPE_BOOL,
- offsetof(StdRdOptions, vacuum_truncate), offsetof(StdRdOptions, vacuum_truncate_set)},
+ {"vacuum_truncate", RELOPT_TYPE_TERNARY,
+ offsetof(StdRdOptions, vacuum_truncate)},
{"vacuum_max_eager_freeze_failure_rate", RELOPT_TYPE_REAL,
offsetof(StdRdOptions, vacuum_max_eager_freeze_failure_rate)}
};
@@ -1993,7 +2071,6 @@ build_local_reloptions(local_relopts *relopts, Datum options, bool validate)
elems[i].optname = opt->option->name;
elems[i].opttype = opt->option->type;
elems[i].offset = opt->offset;
- elems[i].isset_offset = 0; /* not supported for local relopts yet */
i++;
}
diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c
index 733ef40ae7..977babff54 100644
--- a/src/backend/commands/vacuum.c
+++ b/src/backend/commands/vacuum.c
@@ -2221,9 +2221,9 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams params,
{
StdRdOptions *opts = (StdRdOptions *) rel->rd_options;
- if (opts && opts->vacuum_truncate_set)
+ if (opts && opts->vacuum_truncate != TERNARY_UNSET)
{
- if (opts->vacuum_truncate)
+ if (opts->vacuum_truncate == TERNARY_TRUE)
params.truncate = VACOPTVALUE_ENABLED;
else
params.truncate = VACOPTVALUE_DISABLED;
diff --git a/src/include/access/reloptions.h b/src/include/access/reloptions.h
index a604a4702c..a436697658 100644
--- a/src/include/access/reloptions.h
+++ b/src/include/access/reloptions.h
@@ -29,6 +29,7 @@
typedef enum relopt_type
{
RELOPT_TYPE_BOOL,
+ RELOPT_TYPE_TERNARY, /* on, off, unset */
RELOPT_TYPE_INT,
RELOPT_TYPE_REAL,
RELOPT_TYPE_ENUM,
@@ -80,6 +81,7 @@ typedef struct relopt_value
union
{
bool bool_val;
+ ternary ternary_val;
int int_val;
double real_val;
int enum_val;
@@ -94,6 +96,12 @@ typedef struct relopt_bool
bool default_val;
} relopt_bool;
+typedef struct relopt_rernary
+{
+ relopt_gen gen;
+ int default_val;
+} relopt_ternary;
+
typedef struct relopt_int
{
relopt_gen gen;
@@ -152,19 +160,6 @@ typedef struct
const char *optname; /* option's name */
relopt_type opttype; /* option's datatype */
int offset; /* offset of field in result struct */
-
- /*
- * isset_offset is an optional offset of a field in the result struct that
- * stores whether the option is explicitly set for the relation or if it
- * just picked up the default value. In most cases, this can be
- * accomplished by giving the reloption a special out-of-range default
- * value (e.g., some integer reloptions use -2), but this isn't always
- * possible. For example, a Boolean reloption cannot be given an
- * out-of-range default, so we need another way to discover the source of
- * its value. This offset is only used if given a value greater than
- * zero.
- */
- int isset_offset;
} relopt_parse_elt;
/* Local reloption definition */
@@ -195,6 +190,8 @@ typedef struct local_relopts
extern relopt_kind add_reloption_kind(void);
extern void add_bool_reloption(bits32 kinds, const char *name, const char *desc,
bool default_val, LOCKMODE lockmode);
+extern void add_ternary_reloption(bits32 kinds, const char *name,
+ const char *desc, int default_val, LOCKMODE lockmode);
extern void add_int_reloption(bits32 kinds, const char *name, const char *desc,
int default_val, int min_val, int max_val,
LOCKMODE lockmode);
@@ -214,6 +211,9 @@ extern void register_reloptions_validator(local_relopts *relopts,
extern void add_local_bool_reloption(local_relopts *relopts, const char *name,
const char *desc, bool default_val,
int offset);
+extern void add_local_ternary_reloption(local_relopts *relopts,
+ const char *name, const char *desc,
+ ternary default_val, int offset);
extern void add_local_int_reloption(local_relopts *relopts, const char *name,
const char *desc, int default_val,
int min_val, int max_val, int offset);
diff --git a/src/include/c.h b/src/include/c.h
index 39022f8a9d..9c59ccccf8 100644
--- a/src/include/c.h
+++ b/src/include/c.h
@@ -488,6 +488,22 @@ typedef void (*pg_funcptr_t) (void);
#include <stdbool.h>
+/*
+ * ternary
+ * Boolean value with an extrea "unset" option
+ *
+ * Ternary data type is used in relation options that can be "true", "false" or
+ * "unset". Since relation options are used deep inside the PostgreSQL code,
+ * this type is declared globally.
+*/
+
+typedef enum ternary
+{
+ TERNARY_FALSE = 0,
+ TERNARY_TRUE = 1,
+ TERNARY_UNSET = -1
+} ternary;
+
/* ----------------------------------------------------------------
* Section 3: standard system types
diff --git a/src/include/utils/rel.h b/src/include/utils/rel.h
index b552359915..7346d618f9 100644
--- a/src/include/utils/rel.h
+++ b/src/include/utils/rel.h
@@ -346,8 +346,7 @@ typedef struct StdRdOptions
bool user_catalog_table; /* use as an additional catalog relation */
int parallel_workers; /* max number of parallel workers */
StdRdOptIndexCleanup vacuum_index_cleanup; /* controls index vacuuming */
- bool vacuum_truncate; /* enables vacuum to truncate a relation */
- bool vacuum_truncate_set; /* whether vacuum_truncate is set */
+ ternary vacuum_truncate; /* enables vacuum to truncate a relation */
/*
* Fraction of pages in a relation that vacuum can eagerly scan and fail
--
2.39.2
--nextPart5129932.5fSG56mABF
Content-Disposition: attachment;
filename="v2-0003-Add-alias-to-be-used-as-unset-state.patch"
Content-Transfer-Encoding: 7Bit
Content-Type: text/x-patch; charset="unicode-2-0-utf-8";
name="v2-0003-Add-alias-to-be-used-as-unset-state.patch"
^ permalink raw reply [nested|flat] 868+ messages in thread
* [PATCH v2 2/4] Introduce ternary reloptions
@ 2025-09-04 14:41 Nikolay Shaplov <[email protected]>
0 siblings, 0 replies; 868+ messages in thread
From: Nikolay Shaplov @ 2025-09-04 14:41 UTC (permalink / raw)
Introduce ternary reloption as a replacement for current `vacuum_truncate`
implementation. Remove `vacuum_truncate_set` additional flag and using
`TERNARY_UNSET` value instead.
---
src/backend/access/common/reloptions.c | 129 ++++++++++++++++++++-----
src/backend/commands/vacuum.c | 4 +-
src/include/access/reloptions.h | 26 ++---
src/include/c.h | 16 +++
src/include/utils/rel.h | 3 +-
5 files changed, 135 insertions(+), 43 deletions(-)
diff --git a/src/backend/access/common/reloptions.c b/src/backend/access/common/reloptions.c
index 0af3fea68f..f543f11001 100644
--- a/src/backend/access/common/reloptions.c
+++ b/src/backend/access/common/reloptions.c
@@ -40,9 +40,9 @@
*
* To add an option:
*
- * (i) decide on a type (bool, integer, real, enum, string), name, default
- * value, upper and lower bounds (if applicable); for strings, consider a
- * validation routine.
+ * (i) decide on a type (bool, ternary, integer, real, enum, string), name,
+ * default value, upper and lower bounds (if applicable); for strings,
+ * consider a validation routine.
* (ii) add a record below (or use add_<type>_reloption).
* (iii) add it to the appropriate options struct (perhaps StdRdOptions)
* (iv) add it to the appropriate handling routine (perhaps
@@ -147,15 +147,6 @@ static relopt_bool boolRelOpts[] =
},
false
},
- {
- {
- "vacuum_truncate",
- "Enables vacuum to truncate empty pages at the end of this table",
- RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
- ShareUpdateExclusiveLock
- },
- true
- },
{
{
"deduplicate_items",
@@ -170,6 +161,21 @@ static relopt_bool boolRelOpts[] =
{{NULL}}
};
+static relopt_ternary ternaryRelOpts[] =
+{
+ {
+ {
+ "vacuum_truncate",
+ "Enables vacuum to truncate empty pages at the end of this table",
+ RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
+ ShareUpdateExclusiveLock
+ },
+ TERNARY_UNSET
+ },
+ /* list terminator */
+ {{NULL}}
+};
+
static relopt_int intRelOpts[] =
{
{
@@ -600,6 +606,13 @@ initialize_reloptions(void)
boolRelOpts[i].gen.lockmode));
j++;
}
+ for (i = 0; ternaryRelOpts[i].gen.name; i++)
+ {
+ Assert(DoLockModesConflict(ternaryRelOpts[i].gen.lockmode,
+ ternaryRelOpts[i].gen.lockmode));
+ j++;
+ }
+
for (i = 0; intRelOpts[i].gen.name; i++)
{
Assert(DoLockModesConflict(intRelOpts[i].gen.lockmode,
@@ -640,6 +653,14 @@ initialize_reloptions(void)
j++;
}
+ for (i = 0; ternaryRelOpts[i].gen.name; i++)
+ {
+ relOpts[j] = &ternaryRelOpts[i].gen;
+ relOpts[j]->type = RELOPT_TYPE_TERNARY;
+ relOpts[j]->namelen = strlen(relOpts[j]->name);
+ j++;
+ }
+
for (i = 0; intRelOpts[i].gen.name; i++)
{
relOpts[j] = &intRelOpts[i].gen;
@@ -800,6 +821,9 @@ allocate_reloption(bits32 kinds, int type, const char *name, const char *desc,
case RELOPT_TYPE_BOOL:
size = sizeof(relopt_bool);
break;
+ case RELOPT_TYPE_TERNARY:
+ size = sizeof(relopt_ternary);
+ break;
case RELOPT_TYPE_INT:
size = sizeof(relopt_int);
break;
@@ -883,6 +907,54 @@ add_local_bool_reloption(local_relopts *relopts, const char *name,
add_local_reloption(relopts, (relopt_gen *) newoption, offset);
}
+/*
+ * init_ternary_reloption
+ * Allocate and initialize a new ternary reloption
+ */
+static relopt_ternary *
+init_ternary_reloption(bits32 kinds, const char *name, const char *desc,
+ ternary default_val, LOCKMODE lockmode)
+{
+ relopt_ternary *newoption;
+
+ newoption = (relopt_ternary *) allocate_reloption(kinds,
+ RELOPT_TYPE_TERNARY, name, desc, lockmode);
+ newoption->default_val = default_val;
+
+ return newoption;
+}
+
+/*
+ * add_ternary_reloption
+ * Add a new ternary reloption
+ */
+void
+add_ternary_reloption(bits32 kinds, const char *name, const char *desc,
+ ternary default_val, LOCKMODE lockmode)
+{
+ relopt_ternary *newoption = init_ternary_reloption(kinds, name, desc,
+ default_val, lockmode);
+
+ add_reloption((relopt_gen *) newoption);
+}
+
+/*
+ * add_local_ternary_reloption
+ * Add a new ternary local reloption
+ *
+ * 'offset' is offset of ternary-typed field.
+ */
+void
+add_local_ternary_reloption(local_relopts *relopts, const char *name,
+ const char *desc, ternary default_val,
+ int offset)
+{
+ relopt_ternary *newoption = init_ternary_reloption(RELOPT_KIND_LOCAL,
+ name, desc,
+ default_val, 0);
+
+ add_local_reloption(relopts, (relopt_gen *) newoption, offset);
+}
/*
* init_real_reloption
@@ -1617,6 +1689,18 @@ parse_one_reloption(relopt_value *option, char *text_str, int text_len,
option->gen->name, value)));
}
break;
+ case RELOPT_TYPE_TERNARY:
+ {
+ bool b;
+ parsed = parse_bool(value, &b);
+ option->values.ternary_val = b ? TERNARY_TRUE : TERNARY_FALSE;
+ if (validate && !parsed)
+ ereport(ERROR,
+ (errcode(ERRCODE_INVALID_PARAMETER_VALUE),
+ errmsg("invalid value for ternary option \"%s\": %s",
+ option->gen->name, value)));
+ }
+ break;
case RELOPT_TYPE_INT:
{
relopt_int *optint = (relopt_int *) option->gen;
@@ -1780,17 +1864,6 @@ fillRelOptions(void *rdopts, Size basesize,
char *itempos = ((char *) rdopts) + elems[j].offset;
char *string_val;
- /*
- * If isset_offset is provided, store whether the reloption is
- * set there.
- */
- if (elems[j].isset_offset > 0)
- {
- char *setpos = ((char *) rdopts) + elems[j].isset_offset;
-
- *(bool *) setpos = options[i].isset;
- }
-
switch (options[i].gen->type)
{
case RELOPT_TYPE_BOOL:
@@ -1798,6 +1871,11 @@ fillRelOptions(void *rdopts, Size basesize,
options[i].values.bool_val :
((relopt_bool *) options[i].gen)->default_val;
break;
+ case RELOPT_TYPE_TERNARY:
+ *(ternary *) itempos = options[i].isset ?
+ options[i].values.ternary_val :
+ ((relopt_ternary *) options[i].gen)->default_val;
+ break;
case RELOPT_TYPE_INT:
*(int *) itempos = options[i].isset ?
options[i].values.int_val :
@@ -1912,8 +1990,8 @@ default_reloptions(Datum reloptions, bool validate, relopt_kind kind)
offsetof(StdRdOptions, parallel_workers)},
{"vacuum_index_cleanup", RELOPT_TYPE_ENUM,
offsetof(StdRdOptions, vacuum_index_cleanup)},
- {"vacuum_truncate", RELOPT_TYPE_BOOL,
- offsetof(StdRdOptions, vacuum_truncate), offsetof(StdRdOptions, vacuum_truncate_set)},
+ {"vacuum_truncate", RELOPT_TYPE_TERNARY,
+ offsetof(StdRdOptions, vacuum_truncate)},
{"vacuum_max_eager_freeze_failure_rate", RELOPT_TYPE_REAL,
offsetof(StdRdOptions, vacuum_max_eager_freeze_failure_rate)}
};
@@ -1993,7 +2071,6 @@ build_local_reloptions(local_relopts *relopts, Datum options, bool validate)
elems[i].optname = opt->option->name;
elems[i].opttype = opt->option->type;
elems[i].offset = opt->offset;
- elems[i].isset_offset = 0; /* not supported for local relopts yet */
i++;
}
diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c
index 733ef40ae7..977babff54 100644
--- a/src/backend/commands/vacuum.c
+++ b/src/backend/commands/vacuum.c
@@ -2221,9 +2221,9 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams params,
{
StdRdOptions *opts = (StdRdOptions *) rel->rd_options;
- if (opts && opts->vacuum_truncate_set)
+ if (opts && opts->vacuum_truncate != TERNARY_UNSET)
{
- if (opts->vacuum_truncate)
+ if (opts->vacuum_truncate == TERNARY_TRUE)
params.truncate = VACOPTVALUE_ENABLED;
else
params.truncate = VACOPTVALUE_DISABLED;
diff --git a/src/include/access/reloptions.h b/src/include/access/reloptions.h
index a604a4702c..a436697658 100644
--- a/src/include/access/reloptions.h
+++ b/src/include/access/reloptions.h
@@ -29,6 +29,7 @@
typedef enum relopt_type
{
RELOPT_TYPE_BOOL,
+ RELOPT_TYPE_TERNARY, /* on, off, unset */
RELOPT_TYPE_INT,
RELOPT_TYPE_REAL,
RELOPT_TYPE_ENUM,
@@ -80,6 +81,7 @@ typedef struct relopt_value
union
{
bool bool_val;
+ ternary ternary_val;
int int_val;
double real_val;
int enum_val;
@@ -94,6 +96,12 @@ typedef struct relopt_bool
bool default_val;
} relopt_bool;
+typedef struct relopt_rernary
+{
+ relopt_gen gen;
+ int default_val;
+} relopt_ternary;
+
typedef struct relopt_int
{
relopt_gen gen;
@@ -152,19 +160,6 @@ typedef struct
const char *optname; /* option's name */
relopt_type opttype; /* option's datatype */
int offset; /* offset of field in result struct */
-
- /*
- * isset_offset is an optional offset of a field in the result struct that
- * stores whether the option is explicitly set for the relation or if it
- * just picked up the default value. In most cases, this can be
- * accomplished by giving the reloption a special out-of-range default
- * value (e.g., some integer reloptions use -2), but this isn't always
- * possible. For example, a Boolean reloption cannot be given an
- * out-of-range default, so we need another way to discover the source of
- * its value. This offset is only used if given a value greater than
- * zero.
- */
- int isset_offset;
} relopt_parse_elt;
/* Local reloption definition */
@@ -195,6 +190,8 @@ typedef struct local_relopts
extern relopt_kind add_reloption_kind(void);
extern void add_bool_reloption(bits32 kinds, const char *name, const char *desc,
bool default_val, LOCKMODE lockmode);
+extern void add_ternary_reloption(bits32 kinds, const char *name,
+ const char *desc, int default_val, LOCKMODE lockmode);
extern void add_int_reloption(bits32 kinds, const char *name, const char *desc,
int default_val, int min_val, int max_val,
LOCKMODE lockmode);
@@ -214,6 +211,9 @@ extern void register_reloptions_validator(local_relopts *relopts,
extern void add_local_bool_reloption(local_relopts *relopts, const char *name,
const char *desc, bool default_val,
int offset);
+extern void add_local_ternary_reloption(local_relopts *relopts,
+ const char *name, const char *desc,
+ ternary default_val, int offset);
extern void add_local_int_reloption(local_relopts *relopts, const char *name,
const char *desc, int default_val,
int min_val, int max_val, int offset);
diff --git a/src/include/c.h b/src/include/c.h
index 39022f8a9d..9c59ccccf8 100644
--- a/src/include/c.h
+++ b/src/include/c.h
@@ -488,6 +488,22 @@ typedef void (*pg_funcptr_t) (void);
#include <stdbool.h>
+/*
+ * ternary
+ * Boolean value with an extrea "unset" option
+ *
+ * Ternary data type is used in relation options that can be "true", "false" or
+ * "unset". Since relation options are used deep inside the PostgreSQL code,
+ * this type is declared globally.
+*/
+
+typedef enum ternary
+{
+ TERNARY_FALSE = 0,
+ TERNARY_TRUE = 1,
+ TERNARY_UNSET = -1
+} ternary;
+
/* ----------------------------------------------------------------
* Section 3: standard system types
diff --git a/src/include/utils/rel.h b/src/include/utils/rel.h
index b552359915..7346d618f9 100644
--- a/src/include/utils/rel.h
+++ b/src/include/utils/rel.h
@@ -346,8 +346,7 @@ typedef struct StdRdOptions
bool user_catalog_table; /* use as an additional catalog relation */
int parallel_workers; /* max number of parallel workers */
StdRdOptIndexCleanup vacuum_index_cleanup; /* controls index vacuuming */
- bool vacuum_truncate; /* enables vacuum to truncate a relation */
- bool vacuum_truncate_set; /* whether vacuum_truncate is set */
+ ternary vacuum_truncate; /* enables vacuum to truncate a relation */
/*
* Fraction of pages in a relation that vacuum can eagerly scan and fail
--
2.39.2
--nextPart5129932.5fSG56mABF
Content-Disposition: attachment;
filename="v2-0003-Add-alias-to-be-used-as-unset-state.patch"
Content-Transfer-Encoding: 7Bit
Content-Type: text/x-patch; charset="unicode-2-0-utf-8";
name="v2-0003-Add-alias-to-be-used-as-unset-state.patch"
^ permalink raw reply [nested|flat] 868+ messages in thread
* [PATCH v2 2/4] Introduce ternary reloptions
@ 2025-09-04 14:41 Nikolay Shaplov <[email protected]>
0 siblings, 0 replies; 868+ messages in thread
From: Nikolay Shaplov @ 2025-09-04 14:41 UTC (permalink / raw)
Introduce ternary reloption as a replacement for current `vacuum_truncate`
implementation. Remove `vacuum_truncate_set` additional flag and using
`TERNARY_UNSET` value instead.
---
src/backend/access/common/reloptions.c | 129 ++++++++++++++++++++-----
src/backend/commands/vacuum.c | 4 +-
src/include/access/reloptions.h | 26 ++---
src/include/c.h | 16 +++
src/include/utils/rel.h | 3 +-
5 files changed, 135 insertions(+), 43 deletions(-)
diff --git a/src/backend/access/common/reloptions.c b/src/backend/access/common/reloptions.c
index 0af3fea68f..f543f11001 100644
--- a/src/backend/access/common/reloptions.c
+++ b/src/backend/access/common/reloptions.c
@@ -40,9 +40,9 @@
*
* To add an option:
*
- * (i) decide on a type (bool, integer, real, enum, string), name, default
- * value, upper and lower bounds (if applicable); for strings, consider a
- * validation routine.
+ * (i) decide on a type (bool, ternary, integer, real, enum, string), name,
+ * default value, upper and lower bounds (if applicable); for strings,
+ * consider a validation routine.
* (ii) add a record below (or use add_<type>_reloption).
* (iii) add it to the appropriate options struct (perhaps StdRdOptions)
* (iv) add it to the appropriate handling routine (perhaps
@@ -147,15 +147,6 @@ static relopt_bool boolRelOpts[] =
},
false
},
- {
- {
- "vacuum_truncate",
- "Enables vacuum to truncate empty pages at the end of this table",
- RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
- ShareUpdateExclusiveLock
- },
- true
- },
{
{
"deduplicate_items",
@@ -170,6 +161,21 @@ static relopt_bool boolRelOpts[] =
{{NULL}}
};
+static relopt_ternary ternaryRelOpts[] =
+{
+ {
+ {
+ "vacuum_truncate",
+ "Enables vacuum to truncate empty pages at the end of this table",
+ RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
+ ShareUpdateExclusiveLock
+ },
+ TERNARY_UNSET
+ },
+ /* list terminator */
+ {{NULL}}
+};
+
static relopt_int intRelOpts[] =
{
{
@@ -600,6 +606,13 @@ initialize_reloptions(void)
boolRelOpts[i].gen.lockmode));
j++;
}
+ for (i = 0; ternaryRelOpts[i].gen.name; i++)
+ {
+ Assert(DoLockModesConflict(ternaryRelOpts[i].gen.lockmode,
+ ternaryRelOpts[i].gen.lockmode));
+ j++;
+ }
+
for (i = 0; intRelOpts[i].gen.name; i++)
{
Assert(DoLockModesConflict(intRelOpts[i].gen.lockmode,
@@ -640,6 +653,14 @@ initialize_reloptions(void)
j++;
}
+ for (i = 0; ternaryRelOpts[i].gen.name; i++)
+ {
+ relOpts[j] = &ternaryRelOpts[i].gen;
+ relOpts[j]->type = RELOPT_TYPE_TERNARY;
+ relOpts[j]->namelen = strlen(relOpts[j]->name);
+ j++;
+ }
+
for (i = 0; intRelOpts[i].gen.name; i++)
{
relOpts[j] = &intRelOpts[i].gen;
@@ -800,6 +821,9 @@ allocate_reloption(bits32 kinds, int type, const char *name, const char *desc,
case RELOPT_TYPE_BOOL:
size = sizeof(relopt_bool);
break;
+ case RELOPT_TYPE_TERNARY:
+ size = sizeof(relopt_ternary);
+ break;
case RELOPT_TYPE_INT:
size = sizeof(relopt_int);
break;
@@ -883,6 +907,54 @@ add_local_bool_reloption(local_relopts *relopts, const char *name,
add_local_reloption(relopts, (relopt_gen *) newoption, offset);
}
+/*
+ * init_ternary_reloption
+ * Allocate and initialize a new ternary reloption
+ */
+static relopt_ternary *
+init_ternary_reloption(bits32 kinds, const char *name, const char *desc,
+ ternary default_val, LOCKMODE lockmode)
+{
+ relopt_ternary *newoption;
+
+ newoption = (relopt_ternary *) allocate_reloption(kinds,
+ RELOPT_TYPE_TERNARY, name, desc, lockmode);
+ newoption->default_val = default_val;
+
+ return newoption;
+}
+
+/*
+ * add_ternary_reloption
+ * Add a new ternary reloption
+ */
+void
+add_ternary_reloption(bits32 kinds, const char *name, const char *desc,
+ ternary default_val, LOCKMODE lockmode)
+{
+ relopt_ternary *newoption = init_ternary_reloption(kinds, name, desc,
+ default_val, lockmode);
+
+ add_reloption((relopt_gen *) newoption);
+}
+
+/*
+ * add_local_ternary_reloption
+ * Add a new ternary local reloption
+ *
+ * 'offset' is offset of ternary-typed field.
+ */
+void
+add_local_ternary_reloption(local_relopts *relopts, const char *name,
+ const char *desc, ternary default_val,
+ int offset)
+{
+ relopt_ternary *newoption = init_ternary_reloption(RELOPT_KIND_LOCAL,
+ name, desc,
+ default_val, 0);
+
+ add_local_reloption(relopts, (relopt_gen *) newoption, offset);
+}
/*
* init_real_reloption
@@ -1617,6 +1689,18 @@ parse_one_reloption(relopt_value *option, char *text_str, int text_len,
option->gen->name, value)));
}
break;
+ case RELOPT_TYPE_TERNARY:
+ {
+ bool b;
+ parsed = parse_bool(value, &b);
+ option->values.ternary_val = b ? TERNARY_TRUE : TERNARY_FALSE;
+ if (validate && !parsed)
+ ereport(ERROR,
+ (errcode(ERRCODE_INVALID_PARAMETER_VALUE),
+ errmsg("invalid value for ternary option \"%s\": %s",
+ option->gen->name, value)));
+ }
+ break;
case RELOPT_TYPE_INT:
{
relopt_int *optint = (relopt_int *) option->gen;
@@ -1780,17 +1864,6 @@ fillRelOptions(void *rdopts, Size basesize,
char *itempos = ((char *) rdopts) + elems[j].offset;
char *string_val;
- /*
- * If isset_offset is provided, store whether the reloption is
- * set there.
- */
- if (elems[j].isset_offset > 0)
- {
- char *setpos = ((char *) rdopts) + elems[j].isset_offset;
-
- *(bool *) setpos = options[i].isset;
- }
-
switch (options[i].gen->type)
{
case RELOPT_TYPE_BOOL:
@@ -1798,6 +1871,11 @@ fillRelOptions(void *rdopts, Size basesize,
options[i].values.bool_val :
((relopt_bool *) options[i].gen)->default_val;
break;
+ case RELOPT_TYPE_TERNARY:
+ *(ternary *) itempos = options[i].isset ?
+ options[i].values.ternary_val :
+ ((relopt_ternary *) options[i].gen)->default_val;
+ break;
case RELOPT_TYPE_INT:
*(int *) itempos = options[i].isset ?
options[i].values.int_val :
@@ -1912,8 +1990,8 @@ default_reloptions(Datum reloptions, bool validate, relopt_kind kind)
offsetof(StdRdOptions, parallel_workers)},
{"vacuum_index_cleanup", RELOPT_TYPE_ENUM,
offsetof(StdRdOptions, vacuum_index_cleanup)},
- {"vacuum_truncate", RELOPT_TYPE_BOOL,
- offsetof(StdRdOptions, vacuum_truncate), offsetof(StdRdOptions, vacuum_truncate_set)},
+ {"vacuum_truncate", RELOPT_TYPE_TERNARY,
+ offsetof(StdRdOptions, vacuum_truncate)},
{"vacuum_max_eager_freeze_failure_rate", RELOPT_TYPE_REAL,
offsetof(StdRdOptions, vacuum_max_eager_freeze_failure_rate)}
};
@@ -1993,7 +2071,6 @@ build_local_reloptions(local_relopts *relopts, Datum options, bool validate)
elems[i].optname = opt->option->name;
elems[i].opttype = opt->option->type;
elems[i].offset = opt->offset;
- elems[i].isset_offset = 0; /* not supported for local relopts yet */
i++;
}
diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c
index 733ef40ae7..977babff54 100644
--- a/src/backend/commands/vacuum.c
+++ b/src/backend/commands/vacuum.c
@@ -2221,9 +2221,9 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams params,
{
StdRdOptions *opts = (StdRdOptions *) rel->rd_options;
- if (opts && opts->vacuum_truncate_set)
+ if (opts && opts->vacuum_truncate != TERNARY_UNSET)
{
- if (opts->vacuum_truncate)
+ if (opts->vacuum_truncate == TERNARY_TRUE)
params.truncate = VACOPTVALUE_ENABLED;
else
params.truncate = VACOPTVALUE_DISABLED;
diff --git a/src/include/access/reloptions.h b/src/include/access/reloptions.h
index a604a4702c..a436697658 100644
--- a/src/include/access/reloptions.h
+++ b/src/include/access/reloptions.h
@@ -29,6 +29,7 @@
typedef enum relopt_type
{
RELOPT_TYPE_BOOL,
+ RELOPT_TYPE_TERNARY, /* on, off, unset */
RELOPT_TYPE_INT,
RELOPT_TYPE_REAL,
RELOPT_TYPE_ENUM,
@@ -80,6 +81,7 @@ typedef struct relopt_value
union
{
bool bool_val;
+ ternary ternary_val;
int int_val;
double real_val;
int enum_val;
@@ -94,6 +96,12 @@ typedef struct relopt_bool
bool default_val;
} relopt_bool;
+typedef struct relopt_rernary
+{
+ relopt_gen gen;
+ int default_val;
+} relopt_ternary;
+
typedef struct relopt_int
{
relopt_gen gen;
@@ -152,19 +160,6 @@ typedef struct
const char *optname; /* option's name */
relopt_type opttype; /* option's datatype */
int offset; /* offset of field in result struct */
-
- /*
- * isset_offset is an optional offset of a field in the result struct that
- * stores whether the option is explicitly set for the relation or if it
- * just picked up the default value. In most cases, this can be
- * accomplished by giving the reloption a special out-of-range default
- * value (e.g., some integer reloptions use -2), but this isn't always
- * possible. For example, a Boolean reloption cannot be given an
- * out-of-range default, so we need another way to discover the source of
- * its value. This offset is only used if given a value greater than
- * zero.
- */
- int isset_offset;
} relopt_parse_elt;
/* Local reloption definition */
@@ -195,6 +190,8 @@ typedef struct local_relopts
extern relopt_kind add_reloption_kind(void);
extern void add_bool_reloption(bits32 kinds, const char *name, const char *desc,
bool default_val, LOCKMODE lockmode);
+extern void add_ternary_reloption(bits32 kinds, const char *name,
+ const char *desc, int default_val, LOCKMODE lockmode);
extern void add_int_reloption(bits32 kinds, const char *name, const char *desc,
int default_val, int min_val, int max_val,
LOCKMODE lockmode);
@@ -214,6 +211,9 @@ extern void register_reloptions_validator(local_relopts *relopts,
extern void add_local_bool_reloption(local_relopts *relopts, const char *name,
const char *desc, bool default_val,
int offset);
+extern void add_local_ternary_reloption(local_relopts *relopts,
+ const char *name, const char *desc,
+ ternary default_val, int offset);
extern void add_local_int_reloption(local_relopts *relopts, const char *name,
const char *desc, int default_val,
int min_val, int max_val, int offset);
diff --git a/src/include/c.h b/src/include/c.h
index 39022f8a9d..9c59ccccf8 100644
--- a/src/include/c.h
+++ b/src/include/c.h
@@ -488,6 +488,22 @@ typedef void (*pg_funcptr_t) (void);
#include <stdbool.h>
+/*
+ * ternary
+ * Boolean value with an extrea "unset" option
+ *
+ * Ternary data type is used in relation options that can be "true", "false" or
+ * "unset". Since relation options are used deep inside the PostgreSQL code,
+ * this type is declared globally.
+*/
+
+typedef enum ternary
+{
+ TERNARY_FALSE = 0,
+ TERNARY_TRUE = 1,
+ TERNARY_UNSET = -1
+} ternary;
+
/* ----------------------------------------------------------------
* Section 3: standard system types
diff --git a/src/include/utils/rel.h b/src/include/utils/rel.h
index b552359915..7346d618f9 100644
--- a/src/include/utils/rel.h
+++ b/src/include/utils/rel.h
@@ -346,8 +346,7 @@ typedef struct StdRdOptions
bool user_catalog_table; /* use as an additional catalog relation */
int parallel_workers; /* max number of parallel workers */
StdRdOptIndexCleanup vacuum_index_cleanup; /* controls index vacuuming */
- bool vacuum_truncate; /* enables vacuum to truncate a relation */
- bool vacuum_truncate_set; /* whether vacuum_truncate is set */
+ ternary vacuum_truncate; /* enables vacuum to truncate a relation */
/*
* Fraction of pages in a relation that vacuum can eagerly scan and fail
--
2.39.2
--nextPart5129932.5fSG56mABF
Content-Disposition: attachment;
filename="v2-0003-Add-alias-to-be-used-as-unset-state.patch"
Content-Transfer-Encoding: 7Bit
Content-Type: text/x-patch; charset="unicode-2-0-utf-8";
name="v2-0003-Add-alias-to-be-used-as-unset-state.patch"
^ permalink raw reply [nested|flat] 868+ messages in thread
* [PATCH v2 2/4] Introduce ternary reloptions
@ 2025-09-04 14:41 Nikolay Shaplov <[email protected]>
0 siblings, 0 replies; 868+ messages in thread
From: Nikolay Shaplov @ 2025-09-04 14:41 UTC (permalink / raw)
Introduce ternary reloption as a replacement for current `vacuum_truncate`
implementation. Remove `vacuum_truncate_set` additional flag and using
`TERNARY_UNSET` value instead.
---
src/backend/access/common/reloptions.c | 129 ++++++++++++++++++++-----
src/backend/commands/vacuum.c | 4 +-
src/include/access/reloptions.h | 26 ++---
src/include/c.h | 16 +++
src/include/utils/rel.h | 3 +-
5 files changed, 135 insertions(+), 43 deletions(-)
diff --git a/src/backend/access/common/reloptions.c b/src/backend/access/common/reloptions.c
index 0af3fea68f..f543f11001 100644
--- a/src/backend/access/common/reloptions.c
+++ b/src/backend/access/common/reloptions.c
@@ -40,9 +40,9 @@
*
* To add an option:
*
- * (i) decide on a type (bool, integer, real, enum, string), name, default
- * value, upper and lower bounds (if applicable); for strings, consider a
- * validation routine.
+ * (i) decide on a type (bool, ternary, integer, real, enum, string), name,
+ * default value, upper and lower bounds (if applicable); for strings,
+ * consider a validation routine.
* (ii) add a record below (or use add_<type>_reloption).
* (iii) add it to the appropriate options struct (perhaps StdRdOptions)
* (iv) add it to the appropriate handling routine (perhaps
@@ -147,15 +147,6 @@ static relopt_bool boolRelOpts[] =
},
false
},
- {
- {
- "vacuum_truncate",
- "Enables vacuum to truncate empty pages at the end of this table",
- RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
- ShareUpdateExclusiveLock
- },
- true
- },
{
{
"deduplicate_items",
@@ -170,6 +161,21 @@ static relopt_bool boolRelOpts[] =
{{NULL}}
};
+static relopt_ternary ternaryRelOpts[] =
+{
+ {
+ {
+ "vacuum_truncate",
+ "Enables vacuum to truncate empty pages at the end of this table",
+ RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
+ ShareUpdateExclusiveLock
+ },
+ TERNARY_UNSET
+ },
+ /* list terminator */
+ {{NULL}}
+};
+
static relopt_int intRelOpts[] =
{
{
@@ -600,6 +606,13 @@ initialize_reloptions(void)
boolRelOpts[i].gen.lockmode));
j++;
}
+ for (i = 0; ternaryRelOpts[i].gen.name; i++)
+ {
+ Assert(DoLockModesConflict(ternaryRelOpts[i].gen.lockmode,
+ ternaryRelOpts[i].gen.lockmode));
+ j++;
+ }
+
for (i = 0; intRelOpts[i].gen.name; i++)
{
Assert(DoLockModesConflict(intRelOpts[i].gen.lockmode,
@@ -640,6 +653,14 @@ initialize_reloptions(void)
j++;
}
+ for (i = 0; ternaryRelOpts[i].gen.name; i++)
+ {
+ relOpts[j] = &ternaryRelOpts[i].gen;
+ relOpts[j]->type = RELOPT_TYPE_TERNARY;
+ relOpts[j]->namelen = strlen(relOpts[j]->name);
+ j++;
+ }
+
for (i = 0; intRelOpts[i].gen.name; i++)
{
relOpts[j] = &intRelOpts[i].gen;
@@ -800,6 +821,9 @@ allocate_reloption(bits32 kinds, int type, const char *name, const char *desc,
case RELOPT_TYPE_BOOL:
size = sizeof(relopt_bool);
break;
+ case RELOPT_TYPE_TERNARY:
+ size = sizeof(relopt_ternary);
+ break;
case RELOPT_TYPE_INT:
size = sizeof(relopt_int);
break;
@@ -883,6 +907,54 @@ add_local_bool_reloption(local_relopts *relopts, const char *name,
add_local_reloption(relopts, (relopt_gen *) newoption, offset);
}
+/*
+ * init_ternary_reloption
+ * Allocate and initialize a new ternary reloption
+ */
+static relopt_ternary *
+init_ternary_reloption(bits32 kinds, const char *name, const char *desc,
+ ternary default_val, LOCKMODE lockmode)
+{
+ relopt_ternary *newoption;
+
+ newoption = (relopt_ternary *) allocate_reloption(kinds,
+ RELOPT_TYPE_TERNARY, name, desc, lockmode);
+ newoption->default_val = default_val;
+
+ return newoption;
+}
+
+/*
+ * add_ternary_reloption
+ * Add a new ternary reloption
+ */
+void
+add_ternary_reloption(bits32 kinds, const char *name, const char *desc,
+ ternary default_val, LOCKMODE lockmode)
+{
+ relopt_ternary *newoption = init_ternary_reloption(kinds, name, desc,
+ default_val, lockmode);
+
+ add_reloption((relopt_gen *) newoption);
+}
+
+/*
+ * add_local_ternary_reloption
+ * Add a new ternary local reloption
+ *
+ * 'offset' is offset of ternary-typed field.
+ */
+void
+add_local_ternary_reloption(local_relopts *relopts, const char *name,
+ const char *desc, ternary default_val,
+ int offset)
+{
+ relopt_ternary *newoption = init_ternary_reloption(RELOPT_KIND_LOCAL,
+ name, desc,
+ default_val, 0);
+
+ add_local_reloption(relopts, (relopt_gen *) newoption, offset);
+}
/*
* init_real_reloption
@@ -1617,6 +1689,18 @@ parse_one_reloption(relopt_value *option, char *text_str, int text_len,
option->gen->name, value)));
}
break;
+ case RELOPT_TYPE_TERNARY:
+ {
+ bool b;
+ parsed = parse_bool(value, &b);
+ option->values.ternary_val = b ? TERNARY_TRUE : TERNARY_FALSE;
+ if (validate && !parsed)
+ ereport(ERROR,
+ (errcode(ERRCODE_INVALID_PARAMETER_VALUE),
+ errmsg("invalid value for ternary option \"%s\": %s",
+ option->gen->name, value)));
+ }
+ break;
case RELOPT_TYPE_INT:
{
relopt_int *optint = (relopt_int *) option->gen;
@@ -1780,17 +1864,6 @@ fillRelOptions(void *rdopts, Size basesize,
char *itempos = ((char *) rdopts) + elems[j].offset;
char *string_val;
- /*
- * If isset_offset is provided, store whether the reloption is
- * set there.
- */
- if (elems[j].isset_offset > 0)
- {
- char *setpos = ((char *) rdopts) + elems[j].isset_offset;
-
- *(bool *) setpos = options[i].isset;
- }
-
switch (options[i].gen->type)
{
case RELOPT_TYPE_BOOL:
@@ -1798,6 +1871,11 @@ fillRelOptions(void *rdopts, Size basesize,
options[i].values.bool_val :
((relopt_bool *) options[i].gen)->default_val;
break;
+ case RELOPT_TYPE_TERNARY:
+ *(ternary *) itempos = options[i].isset ?
+ options[i].values.ternary_val :
+ ((relopt_ternary *) options[i].gen)->default_val;
+ break;
case RELOPT_TYPE_INT:
*(int *) itempos = options[i].isset ?
options[i].values.int_val :
@@ -1912,8 +1990,8 @@ default_reloptions(Datum reloptions, bool validate, relopt_kind kind)
offsetof(StdRdOptions, parallel_workers)},
{"vacuum_index_cleanup", RELOPT_TYPE_ENUM,
offsetof(StdRdOptions, vacuum_index_cleanup)},
- {"vacuum_truncate", RELOPT_TYPE_BOOL,
- offsetof(StdRdOptions, vacuum_truncate), offsetof(StdRdOptions, vacuum_truncate_set)},
+ {"vacuum_truncate", RELOPT_TYPE_TERNARY,
+ offsetof(StdRdOptions, vacuum_truncate)},
{"vacuum_max_eager_freeze_failure_rate", RELOPT_TYPE_REAL,
offsetof(StdRdOptions, vacuum_max_eager_freeze_failure_rate)}
};
@@ -1993,7 +2071,6 @@ build_local_reloptions(local_relopts *relopts, Datum options, bool validate)
elems[i].optname = opt->option->name;
elems[i].opttype = opt->option->type;
elems[i].offset = opt->offset;
- elems[i].isset_offset = 0; /* not supported for local relopts yet */
i++;
}
diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c
index 733ef40ae7..977babff54 100644
--- a/src/backend/commands/vacuum.c
+++ b/src/backend/commands/vacuum.c
@@ -2221,9 +2221,9 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams params,
{
StdRdOptions *opts = (StdRdOptions *) rel->rd_options;
- if (opts && opts->vacuum_truncate_set)
+ if (opts && opts->vacuum_truncate != TERNARY_UNSET)
{
- if (opts->vacuum_truncate)
+ if (opts->vacuum_truncate == TERNARY_TRUE)
params.truncate = VACOPTVALUE_ENABLED;
else
params.truncate = VACOPTVALUE_DISABLED;
diff --git a/src/include/access/reloptions.h b/src/include/access/reloptions.h
index a604a4702c..a436697658 100644
--- a/src/include/access/reloptions.h
+++ b/src/include/access/reloptions.h
@@ -29,6 +29,7 @@
typedef enum relopt_type
{
RELOPT_TYPE_BOOL,
+ RELOPT_TYPE_TERNARY, /* on, off, unset */
RELOPT_TYPE_INT,
RELOPT_TYPE_REAL,
RELOPT_TYPE_ENUM,
@@ -80,6 +81,7 @@ typedef struct relopt_value
union
{
bool bool_val;
+ ternary ternary_val;
int int_val;
double real_val;
int enum_val;
@@ -94,6 +96,12 @@ typedef struct relopt_bool
bool default_val;
} relopt_bool;
+typedef struct relopt_rernary
+{
+ relopt_gen gen;
+ int default_val;
+} relopt_ternary;
+
typedef struct relopt_int
{
relopt_gen gen;
@@ -152,19 +160,6 @@ typedef struct
const char *optname; /* option's name */
relopt_type opttype; /* option's datatype */
int offset; /* offset of field in result struct */
-
- /*
- * isset_offset is an optional offset of a field in the result struct that
- * stores whether the option is explicitly set for the relation or if it
- * just picked up the default value. In most cases, this can be
- * accomplished by giving the reloption a special out-of-range default
- * value (e.g., some integer reloptions use -2), but this isn't always
- * possible. For example, a Boolean reloption cannot be given an
- * out-of-range default, so we need another way to discover the source of
- * its value. This offset is only used if given a value greater than
- * zero.
- */
- int isset_offset;
} relopt_parse_elt;
/* Local reloption definition */
@@ -195,6 +190,8 @@ typedef struct local_relopts
extern relopt_kind add_reloption_kind(void);
extern void add_bool_reloption(bits32 kinds, const char *name, const char *desc,
bool default_val, LOCKMODE lockmode);
+extern void add_ternary_reloption(bits32 kinds, const char *name,
+ const char *desc, int default_val, LOCKMODE lockmode);
extern void add_int_reloption(bits32 kinds, const char *name, const char *desc,
int default_val, int min_val, int max_val,
LOCKMODE lockmode);
@@ -214,6 +211,9 @@ extern void register_reloptions_validator(local_relopts *relopts,
extern void add_local_bool_reloption(local_relopts *relopts, const char *name,
const char *desc, bool default_val,
int offset);
+extern void add_local_ternary_reloption(local_relopts *relopts,
+ const char *name, const char *desc,
+ ternary default_val, int offset);
extern void add_local_int_reloption(local_relopts *relopts, const char *name,
const char *desc, int default_val,
int min_val, int max_val, int offset);
diff --git a/src/include/c.h b/src/include/c.h
index 39022f8a9d..9c59ccccf8 100644
--- a/src/include/c.h
+++ b/src/include/c.h
@@ -488,6 +488,22 @@ typedef void (*pg_funcptr_t) (void);
#include <stdbool.h>
+/*
+ * ternary
+ * Boolean value with an extrea "unset" option
+ *
+ * Ternary data type is used in relation options that can be "true", "false" or
+ * "unset". Since relation options are used deep inside the PostgreSQL code,
+ * this type is declared globally.
+*/
+
+typedef enum ternary
+{
+ TERNARY_FALSE = 0,
+ TERNARY_TRUE = 1,
+ TERNARY_UNSET = -1
+} ternary;
+
/* ----------------------------------------------------------------
* Section 3: standard system types
diff --git a/src/include/utils/rel.h b/src/include/utils/rel.h
index b552359915..7346d618f9 100644
--- a/src/include/utils/rel.h
+++ b/src/include/utils/rel.h
@@ -346,8 +346,7 @@ typedef struct StdRdOptions
bool user_catalog_table; /* use as an additional catalog relation */
int parallel_workers; /* max number of parallel workers */
StdRdOptIndexCleanup vacuum_index_cleanup; /* controls index vacuuming */
- bool vacuum_truncate; /* enables vacuum to truncate a relation */
- bool vacuum_truncate_set; /* whether vacuum_truncate is set */
+ ternary vacuum_truncate; /* enables vacuum to truncate a relation */
/*
* Fraction of pages in a relation that vacuum can eagerly scan and fail
--
2.39.2
--nextPart5129932.5fSG56mABF
Content-Disposition: attachment;
filename="v2-0003-Add-alias-to-be-used-as-unset-state.patch"
Content-Transfer-Encoding: 7Bit
Content-Type: text/x-patch; charset="unicode-2-0-utf-8";
name="v2-0003-Add-alias-to-be-used-as-unset-state.patch"
^ permalink raw reply [nested|flat] 868+ messages in thread
* [PATCH v2a 2/4] Introduce ternary reloptions
@ 2025-09-04 14:41 Nikolay Shaplov <[email protected]>
0 siblings, 0 replies; 868+ messages in thread
From: Nikolay Shaplov @ 2025-09-04 14:41 UTC (permalink / raw)
Introduce ternary reloption as a replacement for current `vacuum_truncate`
implementation. Remove `vacuum_truncate_set` additional flag and using
`TERNARY_UNSET` value instead.
---
src/backend/access/common/reloptions.c | 129 ++++++++++++++++++++-----
src/backend/commands/vacuum.c | 4 +-
src/include/access/reloptions.h | 26 ++---
src/include/c.h | 16 +++
src/include/utils/rel.h | 3 +-
5 files changed, 135 insertions(+), 43 deletions(-)
diff --git a/src/backend/access/common/reloptions.c b/src/backend/access/common/reloptions.c
index 0af3fea68f..f543f11001 100644
--- a/src/backend/access/common/reloptions.c
+++ b/src/backend/access/common/reloptions.c
@@ -40,9 +40,9 @@
*
* To add an option:
*
- * (i) decide on a type (bool, integer, real, enum, string), name, default
- * value, upper and lower bounds (if applicable); for strings, consider a
- * validation routine.
+ * (i) decide on a type (bool, ternary, integer, real, enum, string), name,
+ * default value, upper and lower bounds (if applicable); for strings,
+ * consider a validation routine.
* (ii) add a record below (or use add_<type>_reloption).
* (iii) add it to the appropriate options struct (perhaps StdRdOptions)
* (iv) add it to the appropriate handling routine (perhaps
@@ -147,15 +147,6 @@ static relopt_bool boolRelOpts[] =
},
false
},
- {
- {
- "vacuum_truncate",
- "Enables vacuum to truncate empty pages at the end of this table",
- RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
- ShareUpdateExclusiveLock
- },
- true
- },
{
{
"deduplicate_items",
@@ -170,6 +161,21 @@ static relopt_bool boolRelOpts[] =
{{NULL}}
};
+static relopt_ternary ternaryRelOpts[] =
+{
+ {
+ {
+ "vacuum_truncate",
+ "Enables vacuum to truncate empty pages at the end of this table",
+ RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
+ ShareUpdateExclusiveLock
+ },
+ TERNARY_UNSET
+ },
+ /* list terminator */
+ {{NULL}}
+};
+
static relopt_int intRelOpts[] =
{
{
@@ -600,6 +606,13 @@ initialize_reloptions(void)
boolRelOpts[i].gen.lockmode));
j++;
}
+ for (i = 0; ternaryRelOpts[i].gen.name; i++)
+ {
+ Assert(DoLockModesConflict(ternaryRelOpts[i].gen.lockmode,
+ ternaryRelOpts[i].gen.lockmode));
+ j++;
+ }
+
for (i = 0; intRelOpts[i].gen.name; i++)
{
Assert(DoLockModesConflict(intRelOpts[i].gen.lockmode,
@@ -640,6 +653,14 @@ initialize_reloptions(void)
j++;
}
+ for (i = 0; ternaryRelOpts[i].gen.name; i++)
+ {
+ relOpts[j] = &ternaryRelOpts[i].gen;
+ relOpts[j]->type = RELOPT_TYPE_TERNARY;
+ relOpts[j]->namelen = strlen(relOpts[j]->name);
+ j++;
+ }
+
for (i = 0; intRelOpts[i].gen.name; i++)
{
relOpts[j] = &intRelOpts[i].gen;
@@ -800,6 +821,9 @@ allocate_reloption(bits32 kinds, int type, const char *name, const char *desc,
case RELOPT_TYPE_BOOL:
size = sizeof(relopt_bool);
break;
+ case RELOPT_TYPE_TERNARY:
+ size = sizeof(relopt_ternary);
+ break;
case RELOPT_TYPE_INT:
size = sizeof(relopt_int);
break;
@@ -883,6 +907,54 @@ add_local_bool_reloption(local_relopts *relopts, const char *name,
add_local_reloption(relopts, (relopt_gen *) newoption, offset);
}
+/*
+ * init_ternary_reloption
+ * Allocate and initialize a new ternary reloption
+ */
+static relopt_ternary *
+init_ternary_reloption(bits32 kinds, const char *name, const char *desc,
+ ternary default_val, LOCKMODE lockmode)
+{
+ relopt_ternary *newoption;
+
+ newoption = (relopt_ternary *) allocate_reloption(kinds,
+ RELOPT_TYPE_TERNARY, name, desc, lockmode);
+ newoption->default_val = default_val;
+
+ return newoption;
+}
+
+/*
+ * add_ternary_reloption
+ * Add a new ternary reloption
+ */
+void
+add_ternary_reloption(bits32 kinds, const char *name, const char *desc,
+ ternary default_val, LOCKMODE lockmode)
+{
+ relopt_ternary *newoption = init_ternary_reloption(kinds, name, desc,
+ default_val, lockmode);
+
+ add_reloption((relopt_gen *) newoption);
+}
+
+/*
+ * add_local_ternary_reloption
+ * Add a new ternary local reloption
+ *
+ * 'offset' is offset of ternary-typed field.
+ */
+void
+add_local_ternary_reloption(local_relopts *relopts, const char *name,
+ const char *desc, ternary default_val,
+ int offset)
+{
+ relopt_ternary *newoption = init_ternary_reloption(RELOPT_KIND_LOCAL,
+ name, desc,
+ default_val, 0);
+
+ add_local_reloption(relopts, (relopt_gen *) newoption, offset);
+}
/*
* init_real_reloption
@@ -1617,6 +1689,18 @@ parse_one_reloption(relopt_value *option, char *text_str, int text_len,
option->gen->name, value)));
}
break;
+ case RELOPT_TYPE_TERNARY:
+ {
+ bool b;
+ parsed = parse_bool(value, &b);
+ option->values.ternary_val = b ? TERNARY_TRUE : TERNARY_FALSE;
+ if (validate && !parsed)
+ ereport(ERROR,
+ (errcode(ERRCODE_INVALID_PARAMETER_VALUE),
+ errmsg("invalid value for ternary option \"%s\": %s",
+ option->gen->name, value)));
+ }
+ break;
case RELOPT_TYPE_INT:
{
relopt_int *optint = (relopt_int *) option->gen;
@@ -1780,17 +1864,6 @@ fillRelOptions(void *rdopts, Size basesize,
char *itempos = ((char *) rdopts) + elems[j].offset;
char *string_val;
- /*
- * If isset_offset is provided, store whether the reloption is
- * set there.
- */
- if (elems[j].isset_offset > 0)
- {
- char *setpos = ((char *) rdopts) + elems[j].isset_offset;
-
- *(bool *) setpos = options[i].isset;
- }
-
switch (options[i].gen->type)
{
case RELOPT_TYPE_BOOL:
@@ -1798,6 +1871,11 @@ fillRelOptions(void *rdopts, Size basesize,
options[i].values.bool_val :
((relopt_bool *) options[i].gen)->default_val;
break;
+ case RELOPT_TYPE_TERNARY:
+ *(ternary *) itempos = options[i].isset ?
+ options[i].values.ternary_val :
+ ((relopt_ternary *) options[i].gen)->default_val;
+ break;
case RELOPT_TYPE_INT:
*(int *) itempos = options[i].isset ?
options[i].values.int_val :
@@ -1912,8 +1990,8 @@ default_reloptions(Datum reloptions, bool validate, relopt_kind kind)
offsetof(StdRdOptions, parallel_workers)},
{"vacuum_index_cleanup", RELOPT_TYPE_ENUM,
offsetof(StdRdOptions, vacuum_index_cleanup)},
- {"vacuum_truncate", RELOPT_TYPE_BOOL,
- offsetof(StdRdOptions, vacuum_truncate), offsetof(StdRdOptions, vacuum_truncate_set)},
+ {"vacuum_truncate", RELOPT_TYPE_TERNARY,
+ offsetof(StdRdOptions, vacuum_truncate)},
{"vacuum_max_eager_freeze_failure_rate", RELOPT_TYPE_REAL,
offsetof(StdRdOptions, vacuum_max_eager_freeze_failure_rate)}
};
@@ -1993,7 +2071,6 @@ build_local_reloptions(local_relopts *relopts, Datum options, bool validate)
elems[i].optname = opt->option->name;
elems[i].opttype = opt->option->type;
elems[i].offset = opt->offset;
- elems[i].isset_offset = 0; /* not supported for local relopts yet */
i++;
}
diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c
index 733ef40ae7..977babff54 100644
--- a/src/backend/commands/vacuum.c
+++ b/src/backend/commands/vacuum.c
@@ -2221,9 +2221,9 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams params,
{
StdRdOptions *opts = (StdRdOptions *) rel->rd_options;
- if (opts && opts->vacuum_truncate_set)
+ if (opts && opts->vacuum_truncate != TERNARY_UNSET)
{
- if (opts->vacuum_truncate)
+ if (opts->vacuum_truncate == TERNARY_TRUE)
params.truncate = VACOPTVALUE_ENABLED;
else
params.truncate = VACOPTVALUE_DISABLED;
diff --git a/src/include/access/reloptions.h b/src/include/access/reloptions.h
index a604a4702c..a436697658 100644
--- a/src/include/access/reloptions.h
+++ b/src/include/access/reloptions.h
@@ -29,6 +29,7 @@
typedef enum relopt_type
{
RELOPT_TYPE_BOOL,
+ RELOPT_TYPE_TERNARY, /* on, off, unset */
RELOPT_TYPE_INT,
RELOPT_TYPE_REAL,
RELOPT_TYPE_ENUM,
@@ -80,6 +81,7 @@ typedef struct relopt_value
union
{
bool bool_val;
+ ternary ternary_val;
int int_val;
double real_val;
int enum_val;
@@ -94,6 +96,12 @@ typedef struct relopt_bool
bool default_val;
} relopt_bool;
+typedef struct relopt_rernary
+{
+ relopt_gen gen;
+ int default_val;
+} relopt_ternary;
+
typedef struct relopt_int
{
relopt_gen gen;
@@ -152,19 +160,6 @@ typedef struct
const char *optname; /* option's name */
relopt_type opttype; /* option's datatype */
int offset; /* offset of field in result struct */
-
- /*
- * isset_offset is an optional offset of a field in the result struct that
- * stores whether the option is explicitly set for the relation or if it
- * just picked up the default value. In most cases, this can be
- * accomplished by giving the reloption a special out-of-range default
- * value (e.g., some integer reloptions use -2), but this isn't always
- * possible. For example, a Boolean reloption cannot be given an
- * out-of-range default, so we need another way to discover the source of
- * its value. This offset is only used if given a value greater than
- * zero.
- */
- int isset_offset;
} relopt_parse_elt;
/* Local reloption definition */
@@ -195,6 +190,8 @@ typedef struct local_relopts
extern relopt_kind add_reloption_kind(void);
extern void add_bool_reloption(bits32 kinds, const char *name, const char *desc,
bool default_val, LOCKMODE lockmode);
+extern void add_ternary_reloption(bits32 kinds, const char *name,
+ const char *desc, int default_val, LOCKMODE lockmode);
extern void add_int_reloption(bits32 kinds, const char *name, const char *desc,
int default_val, int min_val, int max_val,
LOCKMODE lockmode);
@@ -214,6 +211,9 @@ extern void register_reloptions_validator(local_relopts *relopts,
extern void add_local_bool_reloption(local_relopts *relopts, const char *name,
const char *desc, bool default_val,
int offset);
+extern void add_local_ternary_reloption(local_relopts *relopts,
+ const char *name, const char *desc,
+ ternary default_val, int offset);
extern void add_local_int_reloption(local_relopts *relopts, const char *name,
const char *desc, int default_val,
int min_val, int max_val, int offset);
diff --git a/src/include/c.h b/src/include/c.h
index 39022f8a9d..9c59ccccf8 100644
--- a/src/include/c.h
+++ b/src/include/c.h
@@ -488,6 +488,22 @@ typedef void (*pg_funcptr_t) (void);
#include <stdbool.h>
+/*
+ * ternary
+ * Boolean value with an extrea "unset" option
+ *
+ * Ternary data type is used in relation options that can be "true", "false" or
+ * "unset". Since relation options are used deep inside the PostgreSQL code,
+ * this type is declared globally.
+*/
+
+typedef enum ternary
+{
+ TERNARY_FALSE = 0,
+ TERNARY_TRUE = 1,
+ TERNARY_UNSET = -1
+} ternary;
+
/* ----------------------------------------------------------------
* Section 3: standard system types
diff --git a/src/include/utils/rel.h b/src/include/utils/rel.h
index b552359915..7346d618f9 100644
--- a/src/include/utils/rel.h
+++ b/src/include/utils/rel.h
@@ -346,8 +346,7 @@ typedef struct StdRdOptions
bool user_catalog_table; /* use as an additional catalog relation */
int parallel_workers; /* max number of parallel workers */
StdRdOptIndexCleanup vacuum_index_cleanup; /* controls index vacuuming */
- bool vacuum_truncate; /* enables vacuum to truncate a relation */
- bool vacuum_truncate_set; /* whether vacuum_truncate is set */
+ ternary vacuum_truncate; /* enables vacuum to truncate a relation */
/*
* Fraction of pages in a relation that vacuum can eagerly scan and fail
--
2.39.2
--nextPart4155600.3ZeAukHxDK
Content-Disposition: attachment;
filename="v2a-0003-Add-alias-to-be-used-as-unset-state.patch"
Content-Transfer-Encoding: 7Bit
Content-Type: text/x-patch; charset="unicode-2-0-utf-8";
name="v2a-0003-Add-alias-to-be-used-as-unset-state.patch"
^ permalink raw reply [nested|flat] 868+ messages in thread
* [PATCH v2 2/4] Introduce ternary reloptions
@ 2025-09-04 14:41 Nikolay Shaplov <[email protected]>
0 siblings, 0 replies; 868+ messages in thread
From: Nikolay Shaplov @ 2025-09-04 14:41 UTC (permalink / raw)
Introduce ternary reloption as a replacement for current `vacuum_truncate`
implementation. Remove `vacuum_truncate_set` additional flag and using
`TERNARY_UNSET` value instead.
---
src/backend/access/common/reloptions.c | 129 ++++++++++++++++++++-----
src/backend/commands/vacuum.c | 4 +-
src/include/access/reloptions.h | 26 ++---
src/include/c.h | 16 +++
src/include/utils/rel.h | 3 +-
5 files changed, 135 insertions(+), 43 deletions(-)
diff --git a/src/backend/access/common/reloptions.c b/src/backend/access/common/reloptions.c
index 0af3fea68f..f543f11001 100644
--- a/src/backend/access/common/reloptions.c
+++ b/src/backend/access/common/reloptions.c
@@ -40,9 +40,9 @@
*
* To add an option:
*
- * (i) decide on a type (bool, integer, real, enum, string), name, default
- * value, upper and lower bounds (if applicable); for strings, consider a
- * validation routine.
+ * (i) decide on a type (bool, ternary, integer, real, enum, string), name,
+ * default value, upper and lower bounds (if applicable); for strings,
+ * consider a validation routine.
* (ii) add a record below (or use add_<type>_reloption).
* (iii) add it to the appropriate options struct (perhaps StdRdOptions)
* (iv) add it to the appropriate handling routine (perhaps
@@ -147,15 +147,6 @@ static relopt_bool boolRelOpts[] =
},
false
},
- {
- {
- "vacuum_truncate",
- "Enables vacuum to truncate empty pages at the end of this table",
- RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
- ShareUpdateExclusiveLock
- },
- true
- },
{
{
"deduplicate_items",
@@ -170,6 +161,21 @@ static relopt_bool boolRelOpts[] =
{{NULL}}
};
+static relopt_ternary ternaryRelOpts[] =
+{
+ {
+ {
+ "vacuum_truncate",
+ "Enables vacuum to truncate empty pages at the end of this table",
+ RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
+ ShareUpdateExclusiveLock
+ },
+ TERNARY_UNSET
+ },
+ /* list terminator */
+ {{NULL}}
+};
+
static relopt_int intRelOpts[] =
{
{
@@ -600,6 +606,13 @@ initialize_reloptions(void)
boolRelOpts[i].gen.lockmode));
j++;
}
+ for (i = 0; ternaryRelOpts[i].gen.name; i++)
+ {
+ Assert(DoLockModesConflict(ternaryRelOpts[i].gen.lockmode,
+ ternaryRelOpts[i].gen.lockmode));
+ j++;
+ }
+
for (i = 0; intRelOpts[i].gen.name; i++)
{
Assert(DoLockModesConflict(intRelOpts[i].gen.lockmode,
@@ -640,6 +653,14 @@ initialize_reloptions(void)
j++;
}
+ for (i = 0; ternaryRelOpts[i].gen.name; i++)
+ {
+ relOpts[j] = &ternaryRelOpts[i].gen;
+ relOpts[j]->type = RELOPT_TYPE_TERNARY;
+ relOpts[j]->namelen = strlen(relOpts[j]->name);
+ j++;
+ }
+
for (i = 0; intRelOpts[i].gen.name; i++)
{
relOpts[j] = &intRelOpts[i].gen;
@@ -800,6 +821,9 @@ allocate_reloption(bits32 kinds, int type, const char *name, const char *desc,
case RELOPT_TYPE_BOOL:
size = sizeof(relopt_bool);
break;
+ case RELOPT_TYPE_TERNARY:
+ size = sizeof(relopt_ternary);
+ break;
case RELOPT_TYPE_INT:
size = sizeof(relopt_int);
break;
@@ -883,6 +907,54 @@ add_local_bool_reloption(local_relopts *relopts, const char *name,
add_local_reloption(relopts, (relopt_gen *) newoption, offset);
}
+/*
+ * init_ternary_reloption
+ * Allocate and initialize a new ternary reloption
+ */
+static relopt_ternary *
+init_ternary_reloption(bits32 kinds, const char *name, const char *desc,
+ ternary default_val, LOCKMODE lockmode)
+{
+ relopt_ternary *newoption;
+
+ newoption = (relopt_ternary *) allocate_reloption(kinds,
+ RELOPT_TYPE_TERNARY, name, desc, lockmode);
+ newoption->default_val = default_val;
+
+ return newoption;
+}
+
+/*
+ * add_ternary_reloption
+ * Add a new ternary reloption
+ */
+void
+add_ternary_reloption(bits32 kinds, const char *name, const char *desc,
+ ternary default_val, LOCKMODE lockmode)
+{
+ relopt_ternary *newoption = init_ternary_reloption(kinds, name, desc,
+ default_val, lockmode);
+
+ add_reloption((relopt_gen *) newoption);
+}
+
+/*
+ * add_local_ternary_reloption
+ * Add a new ternary local reloption
+ *
+ * 'offset' is offset of ternary-typed field.
+ */
+void
+add_local_ternary_reloption(local_relopts *relopts, const char *name,
+ const char *desc, ternary default_val,
+ int offset)
+{
+ relopt_ternary *newoption = init_ternary_reloption(RELOPT_KIND_LOCAL,
+ name, desc,
+ default_val, 0);
+
+ add_local_reloption(relopts, (relopt_gen *) newoption, offset);
+}
/*
* init_real_reloption
@@ -1617,6 +1689,18 @@ parse_one_reloption(relopt_value *option, char *text_str, int text_len,
option->gen->name, value)));
}
break;
+ case RELOPT_TYPE_TERNARY:
+ {
+ bool b;
+ parsed = parse_bool(value, &b);
+ option->values.ternary_val = b ? TERNARY_TRUE : TERNARY_FALSE;
+ if (validate && !parsed)
+ ereport(ERROR,
+ (errcode(ERRCODE_INVALID_PARAMETER_VALUE),
+ errmsg("invalid value for ternary option \"%s\": %s",
+ option->gen->name, value)));
+ }
+ break;
case RELOPT_TYPE_INT:
{
relopt_int *optint = (relopt_int *) option->gen;
@@ -1780,17 +1864,6 @@ fillRelOptions(void *rdopts, Size basesize,
char *itempos = ((char *) rdopts) + elems[j].offset;
char *string_val;
- /*
- * If isset_offset is provided, store whether the reloption is
- * set there.
- */
- if (elems[j].isset_offset > 0)
- {
- char *setpos = ((char *) rdopts) + elems[j].isset_offset;
-
- *(bool *) setpos = options[i].isset;
- }
-
switch (options[i].gen->type)
{
case RELOPT_TYPE_BOOL:
@@ -1798,6 +1871,11 @@ fillRelOptions(void *rdopts, Size basesize,
options[i].values.bool_val :
((relopt_bool *) options[i].gen)->default_val;
break;
+ case RELOPT_TYPE_TERNARY:
+ *(ternary *) itempos = options[i].isset ?
+ options[i].values.ternary_val :
+ ((relopt_ternary *) options[i].gen)->default_val;
+ break;
case RELOPT_TYPE_INT:
*(int *) itempos = options[i].isset ?
options[i].values.int_val :
@@ -1912,8 +1990,8 @@ default_reloptions(Datum reloptions, bool validate, relopt_kind kind)
offsetof(StdRdOptions, parallel_workers)},
{"vacuum_index_cleanup", RELOPT_TYPE_ENUM,
offsetof(StdRdOptions, vacuum_index_cleanup)},
- {"vacuum_truncate", RELOPT_TYPE_BOOL,
- offsetof(StdRdOptions, vacuum_truncate), offsetof(StdRdOptions, vacuum_truncate_set)},
+ {"vacuum_truncate", RELOPT_TYPE_TERNARY,
+ offsetof(StdRdOptions, vacuum_truncate)},
{"vacuum_max_eager_freeze_failure_rate", RELOPT_TYPE_REAL,
offsetof(StdRdOptions, vacuum_max_eager_freeze_failure_rate)}
};
@@ -1993,7 +2071,6 @@ build_local_reloptions(local_relopts *relopts, Datum options, bool validate)
elems[i].optname = opt->option->name;
elems[i].opttype = opt->option->type;
elems[i].offset = opt->offset;
- elems[i].isset_offset = 0; /* not supported for local relopts yet */
i++;
}
diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c
index 733ef40ae7..977babff54 100644
--- a/src/backend/commands/vacuum.c
+++ b/src/backend/commands/vacuum.c
@@ -2221,9 +2221,9 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams params,
{
StdRdOptions *opts = (StdRdOptions *) rel->rd_options;
- if (opts && opts->vacuum_truncate_set)
+ if (opts && opts->vacuum_truncate != TERNARY_UNSET)
{
- if (opts->vacuum_truncate)
+ if (opts->vacuum_truncate == TERNARY_TRUE)
params.truncate = VACOPTVALUE_ENABLED;
else
params.truncate = VACOPTVALUE_DISABLED;
diff --git a/src/include/access/reloptions.h b/src/include/access/reloptions.h
index a604a4702c..a436697658 100644
--- a/src/include/access/reloptions.h
+++ b/src/include/access/reloptions.h
@@ -29,6 +29,7 @@
typedef enum relopt_type
{
RELOPT_TYPE_BOOL,
+ RELOPT_TYPE_TERNARY, /* on, off, unset */
RELOPT_TYPE_INT,
RELOPT_TYPE_REAL,
RELOPT_TYPE_ENUM,
@@ -80,6 +81,7 @@ typedef struct relopt_value
union
{
bool bool_val;
+ ternary ternary_val;
int int_val;
double real_val;
int enum_val;
@@ -94,6 +96,12 @@ typedef struct relopt_bool
bool default_val;
} relopt_bool;
+typedef struct relopt_rernary
+{
+ relopt_gen gen;
+ int default_val;
+} relopt_ternary;
+
typedef struct relopt_int
{
relopt_gen gen;
@@ -152,19 +160,6 @@ typedef struct
const char *optname; /* option's name */
relopt_type opttype; /* option's datatype */
int offset; /* offset of field in result struct */
-
- /*
- * isset_offset is an optional offset of a field in the result struct that
- * stores whether the option is explicitly set for the relation or if it
- * just picked up the default value. In most cases, this can be
- * accomplished by giving the reloption a special out-of-range default
- * value (e.g., some integer reloptions use -2), but this isn't always
- * possible. For example, a Boolean reloption cannot be given an
- * out-of-range default, so we need another way to discover the source of
- * its value. This offset is only used if given a value greater than
- * zero.
- */
- int isset_offset;
} relopt_parse_elt;
/* Local reloption definition */
@@ -195,6 +190,8 @@ typedef struct local_relopts
extern relopt_kind add_reloption_kind(void);
extern void add_bool_reloption(bits32 kinds, const char *name, const char *desc,
bool default_val, LOCKMODE lockmode);
+extern void add_ternary_reloption(bits32 kinds, const char *name,
+ const char *desc, int default_val, LOCKMODE lockmode);
extern void add_int_reloption(bits32 kinds, const char *name, const char *desc,
int default_val, int min_val, int max_val,
LOCKMODE lockmode);
@@ -214,6 +211,9 @@ extern void register_reloptions_validator(local_relopts *relopts,
extern void add_local_bool_reloption(local_relopts *relopts, const char *name,
const char *desc, bool default_val,
int offset);
+extern void add_local_ternary_reloption(local_relopts *relopts,
+ const char *name, const char *desc,
+ ternary default_val, int offset);
extern void add_local_int_reloption(local_relopts *relopts, const char *name,
const char *desc, int default_val,
int min_val, int max_val, int offset);
diff --git a/src/include/c.h b/src/include/c.h
index 39022f8a9d..9c59ccccf8 100644
--- a/src/include/c.h
+++ b/src/include/c.h
@@ -488,6 +488,22 @@ typedef void (*pg_funcptr_t) (void);
#include <stdbool.h>
+/*
+ * ternary
+ * Boolean value with an extrea "unset" option
+ *
+ * Ternary data type is used in relation options that can be "true", "false" or
+ * "unset". Since relation options are used deep inside the PostgreSQL code,
+ * this type is declared globally.
+*/
+
+typedef enum ternary
+{
+ TERNARY_FALSE = 0,
+ TERNARY_TRUE = 1,
+ TERNARY_UNSET = -1
+} ternary;
+
/* ----------------------------------------------------------------
* Section 3: standard system types
diff --git a/src/include/utils/rel.h b/src/include/utils/rel.h
index b552359915..7346d618f9 100644
--- a/src/include/utils/rel.h
+++ b/src/include/utils/rel.h
@@ -346,8 +346,7 @@ typedef struct StdRdOptions
bool user_catalog_table; /* use as an additional catalog relation */
int parallel_workers; /* max number of parallel workers */
StdRdOptIndexCleanup vacuum_index_cleanup; /* controls index vacuuming */
- bool vacuum_truncate; /* enables vacuum to truncate a relation */
- bool vacuum_truncate_set; /* whether vacuum_truncate is set */
+ ternary vacuum_truncate; /* enables vacuum to truncate a relation */
/*
* Fraction of pages in a relation that vacuum can eagerly scan and fail
--
2.39.2
--nextPart5129932.5fSG56mABF
Content-Disposition: attachment;
filename="v2-0003-Add-alias-to-be-used-as-unset-state.patch"
Content-Transfer-Encoding: 7Bit
Content-Type: text/x-patch; charset="unicode-2-0-utf-8";
name="v2-0003-Add-alias-to-be-used-as-unset-state.patch"
^ permalink raw reply [nested|flat] 868+ messages in thread
* [PATCH v2 2/4] Introduce ternary reloptions
@ 2025-09-04 14:41 Nikolay Shaplov <[email protected]>
0 siblings, 0 replies; 868+ messages in thread
From: Nikolay Shaplov @ 2025-09-04 14:41 UTC (permalink / raw)
Introduce ternary reloption as a replacement for current `vacuum_truncate`
implementation. Remove `vacuum_truncate_set` additional flag and using
`TERNARY_UNSET` value instead.
---
src/backend/access/common/reloptions.c | 129 ++++++++++++++++++++-----
src/backend/commands/vacuum.c | 4 +-
src/include/access/reloptions.h | 26 ++---
src/include/c.h | 16 +++
src/include/utils/rel.h | 3 +-
5 files changed, 135 insertions(+), 43 deletions(-)
diff --git a/src/backend/access/common/reloptions.c b/src/backend/access/common/reloptions.c
index 0af3fea68f..f543f11001 100644
--- a/src/backend/access/common/reloptions.c
+++ b/src/backend/access/common/reloptions.c
@@ -40,9 +40,9 @@
*
* To add an option:
*
- * (i) decide on a type (bool, integer, real, enum, string), name, default
- * value, upper and lower bounds (if applicable); for strings, consider a
- * validation routine.
+ * (i) decide on a type (bool, ternary, integer, real, enum, string), name,
+ * default value, upper and lower bounds (if applicable); for strings,
+ * consider a validation routine.
* (ii) add a record below (or use add_<type>_reloption).
* (iii) add it to the appropriate options struct (perhaps StdRdOptions)
* (iv) add it to the appropriate handling routine (perhaps
@@ -147,15 +147,6 @@ static relopt_bool boolRelOpts[] =
},
false
},
- {
- {
- "vacuum_truncate",
- "Enables vacuum to truncate empty pages at the end of this table",
- RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
- ShareUpdateExclusiveLock
- },
- true
- },
{
{
"deduplicate_items",
@@ -170,6 +161,21 @@ static relopt_bool boolRelOpts[] =
{{NULL}}
};
+static relopt_ternary ternaryRelOpts[] =
+{
+ {
+ {
+ "vacuum_truncate",
+ "Enables vacuum to truncate empty pages at the end of this table",
+ RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
+ ShareUpdateExclusiveLock
+ },
+ TERNARY_UNSET
+ },
+ /* list terminator */
+ {{NULL}}
+};
+
static relopt_int intRelOpts[] =
{
{
@@ -600,6 +606,13 @@ initialize_reloptions(void)
boolRelOpts[i].gen.lockmode));
j++;
}
+ for (i = 0; ternaryRelOpts[i].gen.name; i++)
+ {
+ Assert(DoLockModesConflict(ternaryRelOpts[i].gen.lockmode,
+ ternaryRelOpts[i].gen.lockmode));
+ j++;
+ }
+
for (i = 0; intRelOpts[i].gen.name; i++)
{
Assert(DoLockModesConflict(intRelOpts[i].gen.lockmode,
@@ -640,6 +653,14 @@ initialize_reloptions(void)
j++;
}
+ for (i = 0; ternaryRelOpts[i].gen.name; i++)
+ {
+ relOpts[j] = &ternaryRelOpts[i].gen;
+ relOpts[j]->type = RELOPT_TYPE_TERNARY;
+ relOpts[j]->namelen = strlen(relOpts[j]->name);
+ j++;
+ }
+
for (i = 0; intRelOpts[i].gen.name; i++)
{
relOpts[j] = &intRelOpts[i].gen;
@@ -800,6 +821,9 @@ allocate_reloption(bits32 kinds, int type, const char *name, const char *desc,
case RELOPT_TYPE_BOOL:
size = sizeof(relopt_bool);
break;
+ case RELOPT_TYPE_TERNARY:
+ size = sizeof(relopt_ternary);
+ break;
case RELOPT_TYPE_INT:
size = sizeof(relopt_int);
break;
@@ -883,6 +907,54 @@ add_local_bool_reloption(local_relopts *relopts, const char *name,
add_local_reloption(relopts, (relopt_gen *) newoption, offset);
}
+/*
+ * init_ternary_reloption
+ * Allocate and initialize a new ternary reloption
+ */
+static relopt_ternary *
+init_ternary_reloption(bits32 kinds, const char *name, const char *desc,
+ ternary default_val, LOCKMODE lockmode)
+{
+ relopt_ternary *newoption;
+
+ newoption = (relopt_ternary *) allocate_reloption(kinds,
+ RELOPT_TYPE_TERNARY, name, desc, lockmode);
+ newoption->default_val = default_val;
+
+ return newoption;
+}
+
+/*
+ * add_ternary_reloption
+ * Add a new ternary reloption
+ */
+void
+add_ternary_reloption(bits32 kinds, const char *name, const char *desc,
+ ternary default_val, LOCKMODE lockmode)
+{
+ relopt_ternary *newoption = init_ternary_reloption(kinds, name, desc,
+ default_val, lockmode);
+
+ add_reloption((relopt_gen *) newoption);
+}
+
+/*
+ * add_local_ternary_reloption
+ * Add a new ternary local reloption
+ *
+ * 'offset' is offset of ternary-typed field.
+ */
+void
+add_local_ternary_reloption(local_relopts *relopts, const char *name,
+ const char *desc, ternary default_val,
+ int offset)
+{
+ relopt_ternary *newoption = init_ternary_reloption(RELOPT_KIND_LOCAL,
+ name, desc,
+ default_val, 0);
+
+ add_local_reloption(relopts, (relopt_gen *) newoption, offset);
+}
/*
* init_real_reloption
@@ -1617,6 +1689,18 @@ parse_one_reloption(relopt_value *option, char *text_str, int text_len,
option->gen->name, value)));
}
break;
+ case RELOPT_TYPE_TERNARY:
+ {
+ bool b;
+ parsed = parse_bool(value, &b);
+ option->values.ternary_val = b ? TERNARY_TRUE : TERNARY_FALSE;
+ if (validate && !parsed)
+ ereport(ERROR,
+ (errcode(ERRCODE_INVALID_PARAMETER_VALUE),
+ errmsg("invalid value for ternary option \"%s\": %s",
+ option->gen->name, value)));
+ }
+ break;
case RELOPT_TYPE_INT:
{
relopt_int *optint = (relopt_int *) option->gen;
@@ -1780,17 +1864,6 @@ fillRelOptions(void *rdopts, Size basesize,
char *itempos = ((char *) rdopts) + elems[j].offset;
char *string_val;
- /*
- * If isset_offset is provided, store whether the reloption is
- * set there.
- */
- if (elems[j].isset_offset > 0)
- {
- char *setpos = ((char *) rdopts) + elems[j].isset_offset;
-
- *(bool *) setpos = options[i].isset;
- }
-
switch (options[i].gen->type)
{
case RELOPT_TYPE_BOOL:
@@ -1798,6 +1871,11 @@ fillRelOptions(void *rdopts, Size basesize,
options[i].values.bool_val :
((relopt_bool *) options[i].gen)->default_val;
break;
+ case RELOPT_TYPE_TERNARY:
+ *(ternary *) itempos = options[i].isset ?
+ options[i].values.ternary_val :
+ ((relopt_ternary *) options[i].gen)->default_val;
+ break;
case RELOPT_TYPE_INT:
*(int *) itempos = options[i].isset ?
options[i].values.int_val :
@@ -1912,8 +1990,8 @@ default_reloptions(Datum reloptions, bool validate, relopt_kind kind)
offsetof(StdRdOptions, parallel_workers)},
{"vacuum_index_cleanup", RELOPT_TYPE_ENUM,
offsetof(StdRdOptions, vacuum_index_cleanup)},
- {"vacuum_truncate", RELOPT_TYPE_BOOL,
- offsetof(StdRdOptions, vacuum_truncate), offsetof(StdRdOptions, vacuum_truncate_set)},
+ {"vacuum_truncate", RELOPT_TYPE_TERNARY,
+ offsetof(StdRdOptions, vacuum_truncate)},
{"vacuum_max_eager_freeze_failure_rate", RELOPT_TYPE_REAL,
offsetof(StdRdOptions, vacuum_max_eager_freeze_failure_rate)}
};
@@ -1993,7 +2071,6 @@ build_local_reloptions(local_relopts *relopts, Datum options, bool validate)
elems[i].optname = opt->option->name;
elems[i].opttype = opt->option->type;
elems[i].offset = opt->offset;
- elems[i].isset_offset = 0; /* not supported for local relopts yet */
i++;
}
diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c
index 733ef40ae7..977babff54 100644
--- a/src/backend/commands/vacuum.c
+++ b/src/backend/commands/vacuum.c
@@ -2221,9 +2221,9 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams params,
{
StdRdOptions *opts = (StdRdOptions *) rel->rd_options;
- if (opts && opts->vacuum_truncate_set)
+ if (opts && opts->vacuum_truncate != TERNARY_UNSET)
{
- if (opts->vacuum_truncate)
+ if (opts->vacuum_truncate == TERNARY_TRUE)
params.truncate = VACOPTVALUE_ENABLED;
else
params.truncate = VACOPTVALUE_DISABLED;
diff --git a/src/include/access/reloptions.h b/src/include/access/reloptions.h
index a604a4702c..a436697658 100644
--- a/src/include/access/reloptions.h
+++ b/src/include/access/reloptions.h
@@ -29,6 +29,7 @@
typedef enum relopt_type
{
RELOPT_TYPE_BOOL,
+ RELOPT_TYPE_TERNARY, /* on, off, unset */
RELOPT_TYPE_INT,
RELOPT_TYPE_REAL,
RELOPT_TYPE_ENUM,
@@ -80,6 +81,7 @@ typedef struct relopt_value
union
{
bool bool_val;
+ ternary ternary_val;
int int_val;
double real_val;
int enum_val;
@@ -94,6 +96,12 @@ typedef struct relopt_bool
bool default_val;
} relopt_bool;
+typedef struct relopt_rernary
+{
+ relopt_gen gen;
+ int default_val;
+} relopt_ternary;
+
typedef struct relopt_int
{
relopt_gen gen;
@@ -152,19 +160,6 @@ typedef struct
const char *optname; /* option's name */
relopt_type opttype; /* option's datatype */
int offset; /* offset of field in result struct */
-
- /*
- * isset_offset is an optional offset of a field in the result struct that
- * stores whether the option is explicitly set for the relation or if it
- * just picked up the default value. In most cases, this can be
- * accomplished by giving the reloption a special out-of-range default
- * value (e.g., some integer reloptions use -2), but this isn't always
- * possible. For example, a Boolean reloption cannot be given an
- * out-of-range default, so we need another way to discover the source of
- * its value. This offset is only used if given a value greater than
- * zero.
- */
- int isset_offset;
} relopt_parse_elt;
/* Local reloption definition */
@@ -195,6 +190,8 @@ typedef struct local_relopts
extern relopt_kind add_reloption_kind(void);
extern void add_bool_reloption(bits32 kinds, const char *name, const char *desc,
bool default_val, LOCKMODE lockmode);
+extern void add_ternary_reloption(bits32 kinds, const char *name,
+ const char *desc, int default_val, LOCKMODE lockmode);
extern void add_int_reloption(bits32 kinds, const char *name, const char *desc,
int default_val, int min_val, int max_val,
LOCKMODE lockmode);
@@ -214,6 +211,9 @@ extern void register_reloptions_validator(local_relopts *relopts,
extern void add_local_bool_reloption(local_relopts *relopts, const char *name,
const char *desc, bool default_val,
int offset);
+extern void add_local_ternary_reloption(local_relopts *relopts,
+ const char *name, const char *desc,
+ ternary default_val, int offset);
extern void add_local_int_reloption(local_relopts *relopts, const char *name,
const char *desc, int default_val,
int min_val, int max_val, int offset);
diff --git a/src/include/c.h b/src/include/c.h
index 39022f8a9d..9c59ccccf8 100644
--- a/src/include/c.h
+++ b/src/include/c.h
@@ -488,6 +488,22 @@ typedef void (*pg_funcptr_t) (void);
#include <stdbool.h>
+/*
+ * ternary
+ * Boolean value with an extrea "unset" option
+ *
+ * Ternary data type is used in relation options that can be "true", "false" or
+ * "unset". Since relation options are used deep inside the PostgreSQL code,
+ * this type is declared globally.
+*/
+
+typedef enum ternary
+{
+ TERNARY_FALSE = 0,
+ TERNARY_TRUE = 1,
+ TERNARY_UNSET = -1
+} ternary;
+
/* ----------------------------------------------------------------
* Section 3: standard system types
diff --git a/src/include/utils/rel.h b/src/include/utils/rel.h
index b552359915..7346d618f9 100644
--- a/src/include/utils/rel.h
+++ b/src/include/utils/rel.h
@@ -346,8 +346,7 @@ typedef struct StdRdOptions
bool user_catalog_table; /* use as an additional catalog relation */
int parallel_workers; /* max number of parallel workers */
StdRdOptIndexCleanup vacuum_index_cleanup; /* controls index vacuuming */
- bool vacuum_truncate; /* enables vacuum to truncate a relation */
- bool vacuum_truncate_set; /* whether vacuum_truncate is set */
+ ternary vacuum_truncate; /* enables vacuum to truncate a relation */
/*
* Fraction of pages in a relation that vacuum can eagerly scan and fail
--
2.39.2
--nextPart5129932.5fSG56mABF
Content-Disposition: attachment;
filename="v2-0003-Add-alias-to-be-used-as-unset-state.patch"
Content-Transfer-Encoding: 7Bit
Content-Type: text/x-patch; charset="unicode-2-0-utf-8";
name="v2-0003-Add-alias-to-be-used-as-unset-state.patch"
^ permalink raw reply [nested|flat] 868+ messages in thread
* [PATCH v2 2/4] Introduce ternary reloptions
@ 2025-09-04 14:41 Nikolay Shaplov <[email protected]>
0 siblings, 0 replies; 868+ messages in thread
From: Nikolay Shaplov @ 2025-09-04 14:41 UTC (permalink / raw)
Introduce ternary reloption as a replacement for current `vacuum_truncate`
implementation. Remove `vacuum_truncate_set` additional flag and using
`TERNARY_UNSET` value instead.
---
src/backend/access/common/reloptions.c | 129 ++++++++++++++++++++-----
src/backend/commands/vacuum.c | 4 +-
src/include/access/reloptions.h | 26 ++---
src/include/c.h | 16 +++
src/include/utils/rel.h | 3 +-
5 files changed, 135 insertions(+), 43 deletions(-)
diff --git a/src/backend/access/common/reloptions.c b/src/backend/access/common/reloptions.c
index 0af3fea68f..f543f11001 100644
--- a/src/backend/access/common/reloptions.c
+++ b/src/backend/access/common/reloptions.c
@@ -40,9 +40,9 @@
*
* To add an option:
*
- * (i) decide on a type (bool, integer, real, enum, string), name, default
- * value, upper and lower bounds (if applicable); for strings, consider a
- * validation routine.
+ * (i) decide on a type (bool, ternary, integer, real, enum, string), name,
+ * default value, upper and lower bounds (if applicable); for strings,
+ * consider a validation routine.
* (ii) add a record below (or use add_<type>_reloption).
* (iii) add it to the appropriate options struct (perhaps StdRdOptions)
* (iv) add it to the appropriate handling routine (perhaps
@@ -147,15 +147,6 @@ static relopt_bool boolRelOpts[] =
},
false
},
- {
- {
- "vacuum_truncate",
- "Enables vacuum to truncate empty pages at the end of this table",
- RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
- ShareUpdateExclusiveLock
- },
- true
- },
{
{
"deduplicate_items",
@@ -170,6 +161,21 @@ static relopt_bool boolRelOpts[] =
{{NULL}}
};
+static relopt_ternary ternaryRelOpts[] =
+{
+ {
+ {
+ "vacuum_truncate",
+ "Enables vacuum to truncate empty pages at the end of this table",
+ RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
+ ShareUpdateExclusiveLock
+ },
+ TERNARY_UNSET
+ },
+ /* list terminator */
+ {{NULL}}
+};
+
static relopt_int intRelOpts[] =
{
{
@@ -600,6 +606,13 @@ initialize_reloptions(void)
boolRelOpts[i].gen.lockmode));
j++;
}
+ for (i = 0; ternaryRelOpts[i].gen.name; i++)
+ {
+ Assert(DoLockModesConflict(ternaryRelOpts[i].gen.lockmode,
+ ternaryRelOpts[i].gen.lockmode));
+ j++;
+ }
+
for (i = 0; intRelOpts[i].gen.name; i++)
{
Assert(DoLockModesConflict(intRelOpts[i].gen.lockmode,
@@ -640,6 +653,14 @@ initialize_reloptions(void)
j++;
}
+ for (i = 0; ternaryRelOpts[i].gen.name; i++)
+ {
+ relOpts[j] = &ternaryRelOpts[i].gen;
+ relOpts[j]->type = RELOPT_TYPE_TERNARY;
+ relOpts[j]->namelen = strlen(relOpts[j]->name);
+ j++;
+ }
+
for (i = 0; intRelOpts[i].gen.name; i++)
{
relOpts[j] = &intRelOpts[i].gen;
@@ -800,6 +821,9 @@ allocate_reloption(bits32 kinds, int type, const char *name, const char *desc,
case RELOPT_TYPE_BOOL:
size = sizeof(relopt_bool);
break;
+ case RELOPT_TYPE_TERNARY:
+ size = sizeof(relopt_ternary);
+ break;
case RELOPT_TYPE_INT:
size = sizeof(relopt_int);
break;
@@ -883,6 +907,54 @@ add_local_bool_reloption(local_relopts *relopts, const char *name,
add_local_reloption(relopts, (relopt_gen *) newoption, offset);
}
+/*
+ * init_ternary_reloption
+ * Allocate and initialize a new ternary reloption
+ */
+static relopt_ternary *
+init_ternary_reloption(bits32 kinds, const char *name, const char *desc,
+ ternary default_val, LOCKMODE lockmode)
+{
+ relopt_ternary *newoption;
+
+ newoption = (relopt_ternary *) allocate_reloption(kinds,
+ RELOPT_TYPE_TERNARY, name, desc, lockmode);
+ newoption->default_val = default_val;
+
+ return newoption;
+}
+
+/*
+ * add_ternary_reloption
+ * Add a new ternary reloption
+ */
+void
+add_ternary_reloption(bits32 kinds, const char *name, const char *desc,
+ ternary default_val, LOCKMODE lockmode)
+{
+ relopt_ternary *newoption = init_ternary_reloption(kinds, name, desc,
+ default_val, lockmode);
+
+ add_reloption((relopt_gen *) newoption);
+}
+
+/*
+ * add_local_ternary_reloption
+ * Add a new ternary local reloption
+ *
+ * 'offset' is offset of ternary-typed field.
+ */
+void
+add_local_ternary_reloption(local_relopts *relopts, const char *name,
+ const char *desc, ternary default_val,
+ int offset)
+{
+ relopt_ternary *newoption = init_ternary_reloption(RELOPT_KIND_LOCAL,
+ name, desc,
+ default_val, 0);
+
+ add_local_reloption(relopts, (relopt_gen *) newoption, offset);
+}
/*
* init_real_reloption
@@ -1617,6 +1689,18 @@ parse_one_reloption(relopt_value *option, char *text_str, int text_len,
option->gen->name, value)));
}
break;
+ case RELOPT_TYPE_TERNARY:
+ {
+ bool b;
+ parsed = parse_bool(value, &b);
+ option->values.ternary_val = b ? TERNARY_TRUE : TERNARY_FALSE;
+ if (validate && !parsed)
+ ereport(ERROR,
+ (errcode(ERRCODE_INVALID_PARAMETER_VALUE),
+ errmsg("invalid value for ternary option \"%s\": %s",
+ option->gen->name, value)));
+ }
+ break;
case RELOPT_TYPE_INT:
{
relopt_int *optint = (relopt_int *) option->gen;
@@ -1780,17 +1864,6 @@ fillRelOptions(void *rdopts, Size basesize,
char *itempos = ((char *) rdopts) + elems[j].offset;
char *string_val;
- /*
- * If isset_offset is provided, store whether the reloption is
- * set there.
- */
- if (elems[j].isset_offset > 0)
- {
- char *setpos = ((char *) rdopts) + elems[j].isset_offset;
-
- *(bool *) setpos = options[i].isset;
- }
-
switch (options[i].gen->type)
{
case RELOPT_TYPE_BOOL:
@@ -1798,6 +1871,11 @@ fillRelOptions(void *rdopts, Size basesize,
options[i].values.bool_val :
((relopt_bool *) options[i].gen)->default_val;
break;
+ case RELOPT_TYPE_TERNARY:
+ *(ternary *) itempos = options[i].isset ?
+ options[i].values.ternary_val :
+ ((relopt_ternary *) options[i].gen)->default_val;
+ break;
case RELOPT_TYPE_INT:
*(int *) itempos = options[i].isset ?
options[i].values.int_val :
@@ -1912,8 +1990,8 @@ default_reloptions(Datum reloptions, bool validate, relopt_kind kind)
offsetof(StdRdOptions, parallel_workers)},
{"vacuum_index_cleanup", RELOPT_TYPE_ENUM,
offsetof(StdRdOptions, vacuum_index_cleanup)},
- {"vacuum_truncate", RELOPT_TYPE_BOOL,
- offsetof(StdRdOptions, vacuum_truncate), offsetof(StdRdOptions, vacuum_truncate_set)},
+ {"vacuum_truncate", RELOPT_TYPE_TERNARY,
+ offsetof(StdRdOptions, vacuum_truncate)},
{"vacuum_max_eager_freeze_failure_rate", RELOPT_TYPE_REAL,
offsetof(StdRdOptions, vacuum_max_eager_freeze_failure_rate)}
};
@@ -1993,7 +2071,6 @@ build_local_reloptions(local_relopts *relopts, Datum options, bool validate)
elems[i].optname = opt->option->name;
elems[i].opttype = opt->option->type;
elems[i].offset = opt->offset;
- elems[i].isset_offset = 0; /* not supported for local relopts yet */
i++;
}
diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c
index 733ef40ae7..977babff54 100644
--- a/src/backend/commands/vacuum.c
+++ b/src/backend/commands/vacuum.c
@@ -2221,9 +2221,9 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams params,
{
StdRdOptions *opts = (StdRdOptions *) rel->rd_options;
- if (opts && opts->vacuum_truncate_set)
+ if (opts && opts->vacuum_truncate != TERNARY_UNSET)
{
- if (opts->vacuum_truncate)
+ if (opts->vacuum_truncate == TERNARY_TRUE)
params.truncate = VACOPTVALUE_ENABLED;
else
params.truncate = VACOPTVALUE_DISABLED;
diff --git a/src/include/access/reloptions.h b/src/include/access/reloptions.h
index a604a4702c..a436697658 100644
--- a/src/include/access/reloptions.h
+++ b/src/include/access/reloptions.h
@@ -29,6 +29,7 @@
typedef enum relopt_type
{
RELOPT_TYPE_BOOL,
+ RELOPT_TYPE_TERNARY, /* on, off, unset */
RELOPT_TYPE_INT,
RELOPT_TYPE_REAL,
RELOPT_TYPE_ENUM,
@@ -80,6 +81,7 @@ typedef struct relopt_value
union
{
bool bool_val;
+ ternary ternary_val;
int int_val;
double real_val;
int enum_val;
@@ -94,6 +96,12 @@ typedef struct relopt_bool
bool default_val;
} relopt_bool;
+typedef struct relopt_rernary
+{
+ relopt_gen gen;
+ int default_val;
+} relopt_ternary;
+
typedef struct relopt_int
{
relopt_gen gen;
@@ -152,19 +160,6 @@ typedef struct
const char *optname; /* option's name */
relopt_type opttype; /* option's datatype */
int offset; /* offset of field in result struct */
-
- /*
- * isset_offset is an optional offset of a field in the result struct that
- * stores whether the option is explicitly set for the relation or if it
- * just picked up the default value. In most cases, this can be
- * accomplished by giving the reloption a special out-of-range default
- * value (e.g., some integer reloptions use -2), but this isn't always
- * possible. For example, a Boolean reloption cannot be given an
- * out-of-range default, so we need another way to discover the source of
- * its value. This offset is only used if given a value greater than
- * zero.
- */
- int isset_offset;
} relopt_parse_elt;
/* Local reloption definition */
@@ -195,6 +190,8 @@ typedef struct local_relopts
extern relopt_kind add_reloption_kind(void);
extern void add_bool_reloption(bits32 kinds, const char *name, const char *desc,
bool default_val, LOCKMODE lockmode);
+extern void add_ternary_reloption(bits32 kinds, const char *name,
+ const char *desc, int default_val, LOCKMODE lockmode);
extern void add_int_reloption(bits32 kinds, const char *name, const char *desc,
int default_val, int min_val, int max_val,
LOCKMODE lockmode);
@@ -214,6 +211,9 @@ extern void register_reloptions_validator(local_relopts *relopts,
extern void add_local_bool_reloption(local_relopts *relopts, const char *name,
const char *desc, bool default_val,
int offset);
+extern void add_local_ternary_reloption(local_relopts *relopts,
+ const char *name, const char *desc,
+ ternary default_val, int offset);
extern void add_local_int_reloption(local_relopts *relopts, const char *name,
const char *desc, int default_val,
int min_val, int max_val, int offset);
diff --git a/src/include/c.h b/src/include/c.h
index 39022f8a9d..9c59ccccf8 100644
--- a/src/include/c.h
+++ b/src/include/c.h
@@ -488,6 +488,22 @@ typedef void (*pg_funcptr_t) (void);
#include <stdbool.h>
+/*
+ * ternary
+ * Boolean value with an extrea "unset" option
+ *
+ * Ternary data type is used in relation options that can be "true", "false" or
+ * "unset". Since relation options are used deep inside the PostgreSQL code,
+ * this type is declared globally.
+*/
+
+typedef enum ternary
+{
+ TERNARY_FALSE = 0,
+ TERNARY_TRUE = 1,
+ TERNARY_UNSET = -1
+} ternary;
+
/* ----------------------------------------------------------------
* Section 3: standard system types
diff --git a/src/include/utils/rel.h b/src/include/utils/rel.h
index b552359915..7346d618f9 100644
--- a/src/include/utils/rel.h
+++ b/src/include/utils/rel.h
@@ -346,8 +346,7 @@ typedef struct StdRdOptions
bool user_catalog_table; /* use as an additional catalog relation */
int parallel_workers; /* max number of parallel workers */
StdRdOptIndexCleanup vacuum_index_cleanup; /* controls index vacuuming */
- bool vacuum_truncate; /* enables vacuum to truncate a relation */
- bool vacuum_truncate_set; /* whether vacuum_truncate is set */
+ ternary vacuum_truncate; /* enables vacuum to truncate a relation */
/*
* Fraction of pages in a relation that vacuum can eagerly scan and fail
--
2.39.2
--nextPart5129932.5fSG56mABF
Content-Disposition: attachment;
filename="v2-0003-Add-alias-to-be-used-as-unset-state.patch"
Content-Transfer-Encoding: 7Bit
Content-Type: text/x-patch; charset="unicode-2-0-utf-8";
name="v2-0003-Add-alias-to-be-used-as-unset-state.patch"
^ permalink raw reply [nested|flat] 868+ messages in thread
* [PATCH v2a 2/4] Introduce ternary reloptions
@ 2025-09-04 14:41 Nikolay Shaplov <[email protected]>
0 siblings, 0 replies; 868+ messages in thread
From: Nikolay Shaplov @ 2025-09-04 14:41 UTC (permalink / raw)
Introduce ternary reloption as a replacement for current `vacuum_truncate`
implementation. Remove `vacuum_truncate_set` additional flag and using
`TERNARY_UNSET` value instead.
---
src/backend/access/common/reloptions.c | 129 ++++++++++++++++++++-----
src/backend/commands/vacuum.c | 4 +-
src/include/access/reloptions.h | 26 ++---
src/include/c.h | 16 +++
src/include/utils/rel.h | 3 +-
5 files changed, 135 insertions(+), 43 deletions(-)
diff --git a/src/backend/access/common/reloptions.c b/src/backend/access/common/reloptions.c
index 0af3fea68f..f543f11001 100644
--- a/src/backend/access/common/reloptions.c
+++ b/src/backend/access/common/reloptions.c
@@ -40,9 +40,9 @@
*
* To add an option:
*
- * (i) decide on a type (bool, integer, real, enum, string), name, default
- * value, upper and lower bounds (if applicable); for strings, consider a
- * validation routine.
+ * (i) decide on a type (bool, ternary, integer, real, enum, string), name,
+ * default value, upper and lower bounds (if applicable); for strings,
+ * consider a validation routine.
* (ii) add a record below (or use add_<type>_reloption).
* (iii) add it to the appropriate options struct (perhaps StdRdOptions)
* (iv) add it to the appropriate handling routine (perhaps
@@ -147,15 +147,6 @@ static relopt_bool boolRelOpts[] =
},
false
},
- {
- {
- "vacuum_truncate",
- "Enables vacuum to truncate empty pages at the end of this table",
- RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
- ShareUpdateExclusiveLock
- },
- true
- },
{
{
"deduplicate_items",
@@ -170,6 +161,21 @@ static relopt_bool boolRelOpts[] =
{{NULL}}
};
+static relopt_ternary ternaryRelOpts[] =
+{
+ {
+ {
+ "vacuum_truncate",
+ "Enables vacuum to truncate empty pages at the end of this table",
+ RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
+ ShareUpdateExclusiveLock
+ },
+ TERNARY_UNSET
+ },
+ /* list terminator */
+ {{NULL}}
+};
+
static relopt_int intRelOpts[] =
{
{
@@ -600,6 +606,13 @@ initialize_reloptions(void)
boolRelOpts[i].gen.lockmode));
j++;
}
+ for (i = 0; ternaryRelOpts[i].gen.name; i++)
+ {
+ Assert(DoLockModesConflict(ternaryRelOpts[i].gen.lockmode,
+ ternaryRelOpts[i].gen.lockmode));
+ j++;
+ }
+
for (i = 0; intRelOpts[i].gen.name; i++)
{
Assert(DoLockModesConflict(intRelOpts[i].gen.lockmode,
@@ -640,6 +653,14 @@ initialize_reloptions(void)
j++;
}
+ for (i = 0; ternaryRelOpts[i].gen.name; i++)
+ {
+ relOpts[j] = &ternaryRelOpts[i].gen;
+ relOpts[j]->type = RELOPT_TYPE_TERNARY;
+ relOpts[j]->namelen = strlen(relOpts[j]->name);
+ j++;
+ }
+
for (i = 0; intRelOpts[i].gen.name; i++)
{
relOpts[j] = &intRelOpts[i].gen;
@@ -800,6 +821,9 @@ allocate_reloption(bits32 kinds, int type, const char *name, const char *desc,
case RELOPT_TYPE_BOOL:
size = sizeof(relopt_bool);
break;
+ case RELOPT_TYPE_TERNARY:
+ size = sizeof(relopt_ternary);
+ break;
case RELOPT_TYPE_INT:
size = sizeof(relopt_int);
break;
@@ -883,6 +907,54 @@ add_local_bool_reloption(local_relopts *relopts, const char *name,
add_local_reloption(relopts, (relopt_gen *) newoption, offset);
}
+/*
+ * init_ternary_reloption
+ * Allocate and initialize a new ternary reloption
+ */
+static relopt_ternary *
+init_ternary_reloption(bits32 kinds, const char *name, const char *desc,
+ ternary default_val, LOCKMODE lockmode)
+{
+ relopt_ternary *newoption;
+
+ newoption = (relopt_ternary *) allocate_reloption(kinds,
+ RELOPT_TYPE_TERNARY, name, desc, lockmode);
+ newoption->default_val = default_val;
+
+ return newoption;
+}
+
+/*
+ * add_ternary_reloption
+ * Add a new ternary reloption
+ */
+void
+add_ternary_reloption(bits32 kinds, const char *name, const char *desc,
+ ternary default_val, LOCKMODE lockmode)
+{
+ relopt_ternary *newoption = init_ternary_reloption(kinds, name, desc,
+ default_val, lockmode);
+
+ add_reloption((relopt_gen *) newoption);
+}
+
+/*
+ * add_local_ternary_reloption
+ * Add a new ternary local reloption
+ *
+ * 'offset' is offset of ternary-typed field.
+ */
+void
+add_local_ternary_reloption(local_relopts *relopts, const char *name,
+ const char *desc, ternary default_val,
+ int offset)
+{
+ relopt_ternary *newoption = init_ternary_reloption(RELOPT_KIND_LOCAL,
+ name, desc,
+ default_val, 0);
+
+ add_local_reloption(relopts, (relopt_gen *) newoption, offset);
+}
/*
* init_real_reloption
@@ -1617,6 +1689,18 @@ parse_one_reloption(relopt_value *option, char *text_str, int text_len,
option->gen->name, value)));
}
break;
+ case RELOPT_TYPE_TERNARY:
+ {
+ bool b;
+ parsed = parse_bool(value, &b);
+ option->values.ternary_val = b ? TERNARY_TRUE : TERNARY_FALSE;
+ if (validate && !parsed)
+ ereport(ERROR,
+ (errcode(ERRCODE_INVALID_PARAMETER_VALUE),
+ errmsg("invalid value for ternary option \"%s\": %s",
+ option->gen->name, value)));
+ }
+ break;
case RELOPT_TYPE_INT:
{
relopt_int *optint = (relopt_int *) option->gen;
@@ -1780,17 +1864,6 @@ fillRelOptions(void *rdopts, Size basesize,
char *itempos = ((char *) rdopts) + elems[j].offset;
char *string_val;
- /*
- * If isset_offset is provided, store whether the reloption is
- * set there.
- */
- if (elems[j].isset_offset > 0)
- {
- char *setpos = ((char *) rdopts) + elems[j].isset_offset;
-
- *(bool *) setpos = options[i].isset;
- }
-
switch (options[i].gen->type)
{
case RELOPT_TYPE_BOOL:
@@ -1798,6 +1871,11 @@ fillRelOptions(void *rdopts, Size basesize,
options[i].values.bool_val :
((relopt_bool *) options[i].gen)->default_val;
break;
+ case RELOPT_TYPE_TERNARY:
+ *(ternary *) itempos = options[i].isset ?
+ options[i].values.ternary_val :
+ ((relopt_ternary *) options[i].gen)->default_val;
+ break;
case RELOPT_TYPE_INT:
*(int *) itempos = options[i].isset ?
options[i].values.int_val :
@@ -1912,8 +1990,8 @@ default_reloptions(Datum reloptions, bool validate, relopt_kind kind)
offsetof(StdRdOptions, parallel_workers)},
{"vacuum_index_cleanup", RELOPT_TYPE_ENUM,
offsetof(StdRdOptions, vacuum_index_cleanup)},
- {"vacuum_truncate", RELOPT_TYPE_BOOL,
- offsetof(StdRdOptions, vacuum_truncate), offsetof(StdRdOptions, vacuum_truncate_set)},
+ {"vacuum_truncate", RELOPT_TYPE_TERNARY,
+ offsetof(StdRdOptions, vacuum_truncate)},
{"vacuum_max_eager_freeze_failure_rate", RELOPT_TYPE_REAL,
offsetof(StdRdOptions, vacuum_max_eager_freeze_failure_rate)}
};
@@ -1993,7 +2071,6 @@ build_local_reloptions(local_relopts *relopts, Datum options, bool validate)
elems[i].optname = opt->option->name;
elems[i].opttype = opt->option->type;
elems[i].offset = opt->offset;
- elems[i].isset_offset = 0; /* not supported for local relopts yet */
i++;
}
diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c
index 733ef40ae7..977babff54 100644
--- a/src/backend/commands/vacuum.c
+++ b/src/backend/commands/vacuum.c
@@ -2221,9 +2221,9 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams params,
{
StdRdOptions *opts = (StdRdOptions *) rel->rd_options;
- if (opts && opts->vacuum_truncate_set)
+ if (opts && opts->vacuum_truncate != TERNARY_UNSET)
{
- if (opts->vacuum_truncate)
+ if (opts->vacuum_truncate == TERNARY_TRUE)
params.truncate = VACOPTVALUE_ENABLED;
else
params.truncate = VACOPTVALUE_DISABLED;
diff --git a/src/include/access/reloptions.h b/src/include/access/reloptions.h
index a604a4702c..a436697658 100644
--- a/src/include/access/reloptions.h
+++ b/src/include/access/reloptions.h
@@ -29,6 +29,7 @@
typedef enum relopt_type
{
RELOPT_TYPE_BOOL,
+ RELOPT_TYPE_TERNARY, /* on, off, unset */
RELOPT_TYPE_INT,
RELOPT_TYPE_REAL,
RELOPT_TYPE_ENUM,
@@ -80,6 +81,7 @@ typedef struct relopt_value
union
{
bool bool_val;
+ ternary ternary_val;
int int_val;
double real_val;
int enum_val;
@@ -94,6 +96,12 @@ typedef struct relopt_bool
bool default_val;
} relopt_bool;
+typedef struct relopt_rernary
+{
+ relopt_gen gen;
+ int default_val;
+} relopt_ternary;
+
typedef struct relopt_int
{
relopt_gen gen;
@@ -152,19 +160,6 @@ typedef struct
const char *optname; /* option's name */
relopt_type opttype; /* option's datatype */
int offset; /* offset of field in result struct */
-
- /*
- * isset_offset is an optional offset of a field in the result struct that
- * stores whether the option is explicitly set for the relation or if it
- * just picked up the default value. In most cases, this can be
- * accomplished by giving the reloption a special out-of-range default
- * value (e.g., some integer reloptions use -2), but this isn't always
- * possible. For example, a Boolean reloption cannot be given an
- * out-of-range default, so we need another way to discover the source of
- * its value. This offset is only used if given a value greater than
- * zero.
- */
- int isset_offset;
} relopt_parse_elt;
/* Local reloption definition */
@@ -195,6 +190,8 @@ typedef struct local_relopts
extern relopt_kind add_reloption_kind(void);
extern void add_bool_reloption(bits32 kinds, const char *name, const char *desc,
bool default_val, LOCKMODE lockmode);
+extern void add_ternary_reloption(bits32 kinds, const char *name,
+ const char *desc, int default_val, LOCKMODE lockmode);
extern void add_int_reloption(bits32 kinds, const char *name, const char *desc,
int default_val, int min_val, int max_val,
LOCKMODE lockmode);
@@ -214,6 +211,9 @@ extern void register_reloptions_validator(local_relopts *relopts,
extern void add_local_bool_reloption(local_relopts *relopts, const char *name,
const char *desc, bool default_val,
int offset);
+extern void add_local_ternary_reloption(local_relopts *relopts,
+ const char *name, const char *desc,
+ ternary default_val, int offset);
extern void add_local_int_reloption(local_relopts *relopts, const char *name,
const char *desc, int default_val,
int min_val, int max_val, int offset);
diff --git a/src/include/c.h b/src/include/c.h
index 39022f8a9d..9c59ccccf8 100644
--- a/src/include/c.h
+++ b/src/include/c.h
@@ -488,6 +488,22 @@ typedef void (*pg_funcptr_t) (void);
#include <stdbool.h>
+/*
+ * ternary
+ * Boolean value with an extrea "unset" option
+ *
+ * Ternary data type is used in relation options that can be "true", "false" or
+ * "unset". Since relation options are used deep inside the PostgreSQL code,
+ * this type is declared globally.
+*/
+
+typedef enum ternary
+{
+ TERNARY_FALSE = 0,
+ TERNARY_TRUE = 1,
+ TERNARY_UNSET = -1
+} ternary;
+
/* ----------------------------------------------------------------
* Section 3: standard system types
diff --git a/src/include/utils/rel.h b/src/include/utils/rel.h
index b552359915..7346d618f9 100644
--- a/src/include/utils/rel.h
+++ b/src/include/utils/rel.h
@@ -346,8 +346,7 @@ typedef struct StdRdOptions
bool user_catalog_table; /* use as an additional catalog relation */
int parallel_workers; /* max number of parallel workers */
StdRdOptIndexCleanup vacuum_index_cleanup; /* controls index vacuuming */
- bool vacuum_truncate; /* enables vacuum to truncate a relation */
- bool vacuum_truncate_set; /* whether vacuum_truncate is set */
+ ternary vacuum_truncate; /* enables vacuum to truncate a relation */
/*
* Fraction of pages in a relation that vacuum can eagerly scan and fail
--
2.39.2
--nextPart4155600.3ZeAukHxDK
Content-Disposition: attachment;
filename="v2a-0003-Add-alias-to-be-used-as-unset-state.patch"
Content-Transfer-Encoding: 7Bit
Content-Type: text/x-patch; charset="unicode-2-0-utf-8";
name="v2a-0003-Add-alias-to-be-used-as-unset-state.patch"
^ permalink raw reply [nested|flat] 868+ messages in thread
* [PATCH v2 2/4] Introduce ternary reloptions
@ 2025-09-04 14:41 Nikolay Shaplov <[email protected]>
0 siblings, 0 replies; 868+ messages in thread
From: Nikolay Shaplov @ 2025-09-04 14:41 UTC (permalink / raw)
Introduce ternary reloption as a replacement for current `vacuum_truncate`
implementation. Remove `vacuum_truncate_set` additional flag and using
`TERNARY_UNSET` value instead.
---
src/backend/access/common/reloptions.c | 129 ++++++++++++++++++++-----
src/backend/commands/vacuum.c | 4 +-
src/include/access/reloptions.h | 26 ++---
src/include/c.h | 16 +++
src/include/utils/rel.h | 3 +-
5 files changed, 135 insertions(+), 43 deletions(-)
diff --git a/src/backend/access/common/reloptions.c b/src/backend/access/common/reloptions.c
index 0af3fea68f..f543f11001 100644
--- a/src/backend/access/common/reloptions.c
+++ b/src/backend/access/common/reloptions.c
@@ -40,9 +40,9 @@
*
* To add an option:
*
- * (i) decide on a type (bool, integer, real, enum, string), name, default
- * value, upper and lower bounds (if applicable); for strings, consider a
- * validation routine.
+ * (i) decide on a type (bool, ternary, integer, real, enum, string), name,
+ * default value, upper and lower bounds (if applicable); for strings,
+ * consider a validation routine.
* (ii) add a record below (or use add_<type>_reloption).
* (iii) add it to the appropriate options struct (perhaps StdRdOptions)
* (iv) add it to the appropriate handling routine (perhaps
@@ -147,15 +147,6 @@ static relopt_bool boolRelOpts[] =
},
false
},
- {
- {
- "vacuum_truncate",
- "Enables vacuum to truncate empty pages at the end of this table",
- RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
- ShareUpdateExclusiveLock
- },
- true
- },
{
{
"deduplicate_items",
@@ -170,6 +161,21 @@ static relopt_bool boolRelOpts[] =
{{NULL}}
};
+static relopt_ternary ternaryRelOpts[] =
+{
+ {
+ {
+ "vacuum_truncate",
+ "Enables vacuum to truncate empty pages at the end of this table",
+ RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
+ ShareUpdateExclusiveLock
+ },
+ TERNARY_UNSET
+ },
+ /* list terminator */
+ {{NULL}}
+};
+
static relopt_int intRelOpts[] =
{
{
@@ -600,6 +606,13 @@ initialize_reloptions(void)
boolRelOpts[i].gen.lockmode));
j++;
}
+ for (i = 0; ternaryRelOpts[i].gen.name; i++)
+ {
+ Assert(DoLockModesConflict(ternaryRelOpts[i].gen.lockmode,
+ ternaryRelOpts[i].gen.lockmode));
+ j++;
+ }
+
for (i = 0; intRelOpts[i].gen.name; i++)
{
Assert(DoLockModesConflict(intRelOpts[i].gen.lockmode,
@@ -640,6 +653,14 @@ initialize_reloptions(void)
j++;
}
+ for (i = 0; ternaryRelOpts[i].gen.name; i++)
+ {
+ relOpts[j] = &ternaryRelOpts[i].gen;
+ relOpts[j]->type = RELOPT_TYPE_TERNARY;
+ relOpts[j]->namelen = strlen(relOpts[j]->name);
+ j++;
+ }
+
for (i = 0; intRelOpts[i].gen.name; i++)
{
relOpts[j] = &intRelOpts[i].gen;
@@ -800,6 +821,9 @@ allocate_reloption(bits32 kinds, int type, const char *name, const char *desc,
case RELOPT_TYPE_BOOL:
size = sizeof(relopt_bool);
break;
+ case RELOPT_TYPE_TERNARY:
+ size = sizeof(relopt_ternary);
+ break;
case RELOPT_TYPE_INT:
size = sizeof(relopt_int);
break;
@@ -883,6 +907,54 @@ add_local_bool_reloption(local_relopts *relopts, const char *name,
add_local_reloption(relopts, (relopt_gen *) newoption, offset);
}
+/*
+ * init_ternary_reloption
+ * Allocate and initialize a new ternary reloption
+ */
+static relopt_ternary *
+init_ternary_reloption(bits32 kinds, const char *name, const char *desc,
+ ternary default_val, LOCKMODE lockmode)
+{
+ relopt_ternary *newoption;
+
+ newoption = (relopt_ternary *) allocate_reloption(kinds,
+ RELOPT_TYPE_TERNARY, name, desc, lockmode);
+ newoption->default_val = default_val;
+
+ return newoption;
+}
+
+/*
+ * add_ternary_reloption
+ * Add a new ternary reloption
+ */
+void
+add_ternary_reloption(bits32 kinds, const char *name, const char *desc,
+ ternary default_val, LOCKMODE lockmode)
+{
+ relopt_ternary *newoption = init_ternary_reloption(kinds, name, desc,
+ default_val, lockmode);
+
+ add_reloption((relopt_gen *) newoption);
+}
+
+/*
+ * add_local_ternary_reloption
+ * Add a new ternary local reloption
+ *
+ * 'offset' is offset of ternary-typed field.
+ */
+void
+add_local_ternary_reloption(local_relopts *relopts, const char *name,
+ const char *desc, ternary default_val,
+ int offset)
+{
+ relopt_ternary *newoption = init_ternary_reloption(RELOPT_KIND_LOCAL,
+ name, desc,
+ default_val, 0);
+
+ add_local_reloption(relopts, (relopt_gen *) newoption, offset);
+}
/*
* init_real_reloption
@@ -1617,6 +1689,18 @@ parse_one_reloption(relopt_value *option, char *text_str, int text_len,
option->gen->name, value)));
}
break;
+ case RELOPT_TYPE_TERNARY:
+ {
+ bool b;
+ parsed = parse_bool(value, &b);
+ option->values.ternary_val = b ? TERNARY_TRUE : TERNARY_FALSE;
+ if (validate && !parsed)
+ ereport(ERROR,
+ (errcode(ERRCODE_INVALID_PARAMETER_VALUE),
+ errmsg("invalid value for ternary option \"%s\": %s",
+ option->gen->name, value)));
+ }
+ break;
case RELOPT_TYPE_INT:
{
relopt_int *optint = (relopt_int *) option->gen;
@@ -1780,17 +1864,6 @@ fillRelOptions(void *rdopts, Size basesize,
char *itempos = ((char *) rdopts) + elems[j].offset;
char *string_val;
- /*
- * If isset_offset is provided, store whether the reloption is
- * set there.
- */
- if (elems[j].isset_offset > 0)
- {
- char *setpos = ((char *) rdopts) + elems[j].isset_offset;
-
- *(bool *) setpos = options[i].isset;
- }
-
switch (options[i].gen->type)
{
case RELOPT_TYPE_BOOL:
@@ -1798,6 +1871,11 @@ fillRelOptions(void *rdopts, Size basesize,
options[i].values.bool_val :
((relopt_bool *) options[i].gen)->default_val;
break;
+ case RELOPT_TYPE_TERNARY:
+ *(ternary *) itempos = options[i].isset ?
+ options[i].values.ternary_val :
+ ((relopt_ternary *) options[i].gen)->default_val;
+ break;
case RELOPT_TYPE_INT:
*(int *) itempos = options[i].isset ?
options[i].values.int_val :
@@ -1912,8 +1990,8 @@ default_reloptions(Datum reloptions, bool validate, relopt_kind kind)
offsetof(StdRdOptions, parallel_workers)},
{"vacuum_index_cleanup", RELOPT_TYPE_ENUM,
offsetof(StdRdOptions, vacuum_index_cleanup)},
- {"vacuum_truncate", RELOPT_TYPE_BOOL,
- offsetof(StdRdOptions, vacuum_truncate), offsetof(StdRdOptions, vacuum_truncate_set)},
+ {"vacuum_truncate", RELOPT_TYPE_TERNARY,
+ offsetof(StdRdOptions, vacuum_truncate)},
{"vacuum_max_eager_freeze_failure_rate", RELOPT_TYPE_REAL,
offsetof(StdRdOptions, vacuum_max_eager_freeze_failure_rate)}
};
@@ -1993,7 +2071,6 @@ build_local_reloptions(local_relopts *relopts, Datum options, bool validate)
elems[i].optname = opt->option->name;
elems[i].opttype = opt->option->type;
elems[i].offset = opt->offset;
- elems[i].isset_offset = 0; /* not supported for local relopts yet */
i++;
}
diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c
index 733ef40ae7..977babff54 100644
--- a/src/backend/commands/vacuum.c
+++ b/src/backend/commands/vacuum.c
@@ -2221,9 +2221,9 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams params,
{
StdRdOptions *opts = (StdRdOptions *) rel->rd_options;
- if (opts && opts->vacuum_truncate_set)
+ if (opts && opts->vacuum_truncate != TERNARY_UNSET)
{
- if (opts->vacuum_truncate)
+ if (opts->vacuum_truncate == TERNARY_TRUE)
params.truncate = VACOPTVALUE_ENABLED;
else
params.truncate = VACOPTVALUE_DISABLED;
diff --git a/src/include/access/reloptions.h b/src/include/access/reloptions.h
index a604a4702c..a436697658 100644
--- a/src/include/access/reloptions.h
+++ b/src/include/access/reloptions.h
@@ -29,6 +29,7 @@
typedef enum relopt_type
{
RELOPT_TYPE_BOOL,
+ RELOPT_TYPE_TERNARY, /* on, off, unset */
RELOPT_TYPE_INT,
RELOPT_TYPE_REAL,
RELOPT_TYPE_ENUM,
@@ -80,6 +81,7 @@ typedef struct relopt_value
union
{
bool bool_val;
+ ternary ternary_val;
int int_val;
double real_val;
int enum_val;
@@ -94,6 +96,12 @@ typedef struct relopt_bool
bool default_val;
} relopt_bool;
+typedef struct relopt_rernary
+{
+ relopt_gen gen;
+ int default_val;
+} relopt_ternary;
+
typedef struct relopt_int
{
relopt_gen gen;
@@ -152,19 +160,6 @@ typedef struct
const char *optname; /* option's name */
relopt_type opttype; /* option's datatype */
int offset; /* offset of field in result struct */
-
- /*
- * isset_offset is an optional offset of a field in the result struct that
- * stores whether the option is explicitly set for the relation or if it
- * just picked up the default value. In most cases, this can be
- * accomplished by giving the reloption a special out-of-range default
- * value (e.g., some integer reloptions use -2), but this isn't always
- * possible. For example, a Boolean reloption cannot be given an
- * out-of-range default, so we need another way to discover the source of
- * its value. This offset is only used if given a value greater than
- * zero.
- */
- int isset_offset;
} relopt_parse_elt;
/* Local reloption definition */
@@ -195,6 +190,8 @@ typedef struct local_relopts
extern relopt_kind add_reloption_kind(void);
extern void add_bool_reloption(bits32 kinds, const char *name, const char *desc,
bool default_val, LOCKMODE lockmode);
+extern void add_ternary_reloption(bits32 kinds, const char *name,
+ const char *desc, int default_val, LOCKMODE lockmode);
extern void add_int_reloption(bits32 kinds, const char *name, const char *desc,
int default_val, int min_val, int max_val,
LOCKMODE lockmode);
@@ -214,6 +211,9 @@ extern void register_reloptions_validator(local_relopts *relopts,
extern void add_local_bool_reloption(local_relopts *relopts, const char *name,
const char *desc, bool default_val,
int offset);
+extern void add_local_ternary_reloption(local_relopts *relopts,
+ const char *name, const char *desc,
+ ternary default_val, int offset);
extern void add_local_int_reloption(local_relopts *relopts, const char *name,
const char *desc, int default_val,
int min_val, int max_val, int offset);
diff --git a/src/include/c.h b/src/include/c.h
index 39022f8a9d..9c59ccccf8 100644
--- a/src/include/c.h
+++ b/src/include/c.h
@@ -488,6 +488,22 @@ typedef void (*pg_funcptr_t) (void);
#include <stdbool.h>
+/*
+ * ternary
+ * Boolean value with an extrea "unset" option
+ *
+ * Ternary data type is used in relation options that can be "true", "false" or
+ * "unset". Since relation options are used deep inside the PostgreSQL code,
+ * this type is declared globally.
+*/
+
+typedef enum ternary
+{
+ TERNARY_FALSE = 0,
+ TERNARY_TRUE = 1,
+ TERNARY_UNSET = -1
+} ternary;
+
/* ----------------------------------------------------------------
* Section 3: standard system types
diff --git a/src/include/utils/rel.h b/src/include/utils/rel.h
index b552359915..7346d618f9 100644
--- a/src/include/utils/rel.h
+++ b/src/include/utils/rel.h
@@ -346,8 +346,7 @@ typedef struct StdRdOptions
bool user_catalog_table; /* use as an additional catalog relation */
int parallel_workers; /* max number of parallel workers */
StdRdOptIndexCleanup vacuum_index_cleanup; /* controls index vacuuming */
- bool vacuum_truncate; /* enables vacuum to truncate a relation */
- bool vacuum_truncate_set; /* whether vacuum_truncate is set */
+ ternary vacuum_truncate; /* enables vacuum to truncate a relation */
/*
* Fraction of pages in a relation that vacuum can eagerly scan and fail
--
2.39.2
--nextPart5129932.5fSG56mABF
Content-Disposition: attachment;
filename="v2-0003-Add-alias-to-be-used-as-unset-state.patch"
Content-Transfer-Encoding: 7Bit
Content-Type: text/x-patch; charset="unicode-2-0-utf-8";
name="v2-0003-Add-alias-to-be-used-as-unset-state.patch"
^ permalink raw reply [nested|flat] 868+ messages in thread
* [PATCH v2 2/4] Introduce ternary reloptions
@ 2025-09-04 14:41 Nikolay Shaplov <[email protected]>
0 siblings, 0 replies; 868+ messages in thread
From: Nikolay Shaplov @ 2025-09-04 14:41 UTC (permalink / raw)
Introduce ternary reloption as a replacement for current `vacuum_truncate`
implementation. Remove `vacuum_truncate_set` additional flag and using
`TERNARY_UNSET` value instead.
---
src/backend/access/common/reloptions.c | 129 ++++++++++++++++++++-----
src/backend/commands/vacuum.c | 4 +-
src/include/access/reloptions.h | 26 ++---
src/include/c.h | 16 +++
src/include/utils/rel.h | 3 +-
5 files changed, 135 insertions(+), 43 deletions(-)
diff --git a/src/backend/access/common/reloptions.c b/src/backend/access/common/reloptions.c
index 0af3fea68f..f543f11001 100644
--- a/src/backend/access/common/reloptions.c
+++ b/src/backend/access/common/reloptions.c
@@ -40,9 +40,9 @@
*
* To add an option:
*
- * (i) decide on a type (bool, integer, real, enum, string), name, default
- * value, upper and lower bounds (if applicable); for strings, consider a
- * validation routine.
+ * (i) decide on a type (bool, ternary, integer, real, enum, string), name,
+ * default value, upper and lower bounds (if applicable); for strings,
+ * consider a validation routine.
* (ii) add a record below (or use add_<type>_reloption).
* (iii) add it to the appropriate options struct (perhaps StdRdOptions)
* (iv) add it to the appropriate handling routine (perhaps
@@ -147,15 +147,6 @@ static relopt_bool boolRelOpts[] =
},
false
},
- {
- {
- "vacuum_truncate",
- "Enables vacuum to truncate empty pages at the end of this table",
- RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
- ShareUpdateExclusiveLock
- },
- true
- },
{
{
"deduplicate_items",
@@ -170,6 +161,21 @@ static relopt_bool boolRelOpts[] =
{{NULL}}
};
+static relopt_ternary ternaryRelOpts[] =
+{
+ {
+ {
+ "vacuum_truncate",
+ "Enables vacuum to truncate empty pages at the end of this table",
+ RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
+ ShareUpdateExclusiveLock
+ },
+ TERNARY_UNSET
+ },
+ /* list terminator */
+ {{NULL}}
+};
+
static relopt_int intRelOpts[] =
{
{
@@ -600,6 +606,13 @@ initialize_reloptions(void)
boolRelOpts[i].gen.lockmode));
j++;
}
+ for (i = 0; ternaryRelOpts[i].gen.name; i++)
+ {
+ Assert(DoLockModesConflict(ternaryRelOpts[i].gen.lockmode,
+ ternaryRelOpts[i].gen.lockmode));
+ j++;
+ }
+
for (i = 0; intRelOpts[i].gen.name; i++)
{
Assert(DoLockModesConflict(intRelOpts[i].gen.lockmode,
@@ -640,6 +653,14 @@ initialize_reloptions(void)
j++;
}
+ for (i = 0; ternaryRelOpts[i].gen.name; i++)
+ {
+ relOpts[j] = &ternaryRelOpts[i].gen;
+ relOpts[j]->type = RELOPT_TYPE_TERNARY;
+ relOpts[j]->namelen = strlen(relOpts[j]->name);
+ j++;
+ }
+
for (i = 0; intRelOpts[i].gen.name; i++)
{
relOpts[j] = &intRelOpts[i].gen;
@@ -800,6 +821,9 @@ allocate_reloption(bits32 kinds, int type, const char *name, const char *desc,
case RELOPT_TYPE_BOOL:
size = sizeof(relopt_bool);
break;
+ case RELOPT_TYPE_TERNARY:
+ size = sizeof(relopt_ternary);
+ break;
case RELOPT_TYPE_INT:
size = sizeof(relopt_int);
break;
@@ -883,6 +907,54 @@ add_local_bool_reloption(local_relopts *relopts, const char *name,
add_local_reloption(relopts, (relopt_gen *) newoption, offset);
}
+/*
+ * init_ternary_reloption
+ * Allocate and initialize a new ternary reloption
+ */
+static relopt_ternary *
+init_ternary_reloption(bits32 kinds, const char *name, const char *desc,
+ ternary default_val, LOCKMODE lockmode)
+{
+ relopt_ternary *newoption;
+
+ newoption = (relopt_ternary *) allocate_reloption(kinds,
+ RELOPT_TYPE_TERNARY, name, desc, lockmode);
+ newoption->default_val = default_val;
+
+ return newoption;
+}
+
+/*
+ * add_ternary_reloption
+ * Add a new ternary reloption
+ */
+void
+add_ternary_reloption(bits32 kinds, const char *name, const char *desc,
+ ternary default_val, LOCKMODE lockmode)
+{
+ relopt_ternary *newoption = init_ternary_reloption(kinds, name, desc,
+ default_val, lockmode);
+
+ add_reloption((relopt_gen *) newoption);
+}
+
+/*
+ * add_local_ternary_reloption
+ * Add a new ternary local reloption
+ *
+ * 'offset' is offset of ternary-typed field.
+ */
+void
+add_local_ternary_reloption(local_relopts *relopts, const char *name,
+ const char *desc, ternary default_val,
+ int offset)
+{
+ relopt_ternary *newoption = init_ternary_reloption(RELOPT_KIND_LOCAL,
+ name, desc,
+ default_val, 0);
+
+ add_local_reloption(relopts, (relopt_gen *) newoption, offset);
+}
/*
* init_real_reloption
@@ -1617,6 +1689,18 @@ parse_one_reloption(relopt_value *option, char *text_str, int text_len,
option->gen->name, value)));
}
break;
+ case RELOPT_TYPE_TERNARY:
+ {
+ bool b;
+ parsed = parse_bool(value, &b);
+ option->values.ternary_val = b ? TERNARY_TRUE : TERNARY_FALSE;
+ if (validate && !parsed)
+ ereport(ERROR,
+ (errcode(ERRCODE_INVALID_PARAMETER_VALUE),
+ errmsg("invalid value for ternary option \"%s\": %s",
+ option->gen->name, value)));
+ }
+ break;
case RELOPT_TYPE_INT:
{
relopt_int *optint = (relopt_int *) option->gen;
@@ -1780,17 +1864,6 @@ fillRelOptions(void *rdopts, Size basesize,
char *itempos = ((char *) rdopts) + elems[j].offset;
char *string_val;
- /*
- * If isset_offset is provided, store whether the reloption is
- * set there.
- */
- if (elems[j].isset_offset > 0)
- {
- char *setpos = ((char *) rdopts) + elems[j].isset_offset;
-
- *(bool *) setpos = options[i].isset;
- }
-
switch (options[i].gen->type)
{
case RELOPT_TYPE_BOOL:
@@ -1798,6 +1871,11 @@ fillRelOptions(void *rdopts, Size basesize,
options[i].values.bool_val :
((relopt_bool *) options[i].gen)->default_val;
break;
+ case RELOPT_TYPE_TERNARY:
+ *(ternary *) itempos = options[i].isset ?
+ options[i].values.ternary_val :
+ ((relopt_ternary *) options[i].gen)->default_val;
+ break;
case RELOPT_TYPE_INT:
*(int *) itempos = options[i].isset ?
options[i].values.int_val :
@@ -1912,8 +1990,8 @@ default_reloptions(Datum reloptions, bool validate, relopt_kind kind)
offsetof(StdRdOptions, parallel_workers)},
{"vacuum_index_cleanup", RELOPT_TYPE_ENUM,
offsetof(StdRdOptions, vacuum_index_cleanup)},
- {"vacuum_truncate", RELOPT_TYPE_BOOL,
- offsetof(StdRdOptions, vacuum_truncate), offsetof(StdRdOptions, vacuum_truncate_set)},
+ {"vacuum_truncate", RELOPT_TYPE_TERNARY,
+ offsetof(StdRdOptions, vacuum_truncate)},
{"vacuum_max_eager_freeze_failure_rate", RELOPT_TYPE_REAL,
offsetof(StdRdOptions, vacuum_max_eager_freeze_failure_rate)}
};
@@ -1993,7 +2071,6 @@ build_local_reloptions(local_relopts *relopts, Datum options, bool validate)
elems[i].optname = opt->option->name;
elems[i].opttype = opt->option->type;
elems[i].offset = opt->offset;
- elems[i].isset_offset = 0; /* not supported for local relopts yet */
i++;
}
diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c
index 733ef40ae7..977babff54 100644
--- a/src/backend/commands/vacuum.c
+++ b/src/backend/commands/vacuum.c
@@ -2221,9 +2221,9 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams params,
{
StdRdOptions *opts = (StdRdOptions *) rel->rd_options;
- if (opts && opts->vacuum_truncate_set)
+ if (opts && opts->vacuum_truncate != TERNARY_UNSET)
{
- if (opts->vacuum_truncate)
+ if (opts->vacuum_truncate == TERNARY_TRUE)
params.truncate = VACOPTVALUE_ENABLED;
else
params.truncate = VACOPTVALUE_DISABLED;
diff --git a/src/include/access/reloptions.h b/src/include/access/reloptions.h
index a604a4702c..a436697658 100644
--- a/src/include/access/reloptions.h
+++ b/src/include/access/reloptions.h
@@ -29,6 +29,7 @@
typedef enum relopt_type
{
RELOPT_TYPE_BOOL,
+ RELOPT_TYPE_TERNARY, /* on, off, unset */
RELOPT_TYPE_INT,
RELOPT_TYPE_REAL,
RELOPT_TYPE_ENUM,
@@ -80,6 +81,7 @@ typedef struct relopt_value
union
{
bool bool_val;
+ ternary ternary_val;
int int_val;
double real_val;
int enum_val;
@@ -94,6 +96,12 @@ typedef struct relopt_bool
bool default_val;
} relopt_bool;
+typedef struct relopt_rernary
+{
+ relopt_gen gen;
+ int default_val;
+} relopt_ternary;
+
typedef struct relopt_int
{
relopt_gen gen;
@@ -152,19 +160,6 @@ typedef struct
const char *optname; /* option's name */
relopt_type opttype; /* option's datatype */
int offset; /* offset of field in result struct */
-
- /*
- * isset_offset is an optional offset of a field in the result struct that
- * stores whether the option is explicitly set for the relation or if it
- * just picked up the default value. In most cases, this can be
- * accomplished by giving the reloption a special out-of-range default
- * value (e.g., some integer reloptions use -2), but this isn't always
- * possible. For example, a Boolean reloption cannot be given an
- * out-of-range default, so we need another way to discover the source of
- * its value. This offset is only used if given a value greater than
- * zero.
- */
- int isset_offset;
} relopt_parse_elt;
/* Local reloption definition */
@@ -195,6 +190,8 @@ typedef struct local_relopts
extern relopt_kind add_reloption_kind(void);
extern void add_bool_reloption(bits32 kinds, const char *name, const char *desc,
bool default_val, LOCKMODE lockmode);
+extern void add_ternary_reloption(bits32 kinds, const char *name,
+ const char *desc, int default_val, LOCKMODE lockmode);
extern void add_int_reloption(bits32 kinds, const char *name, const char *desc,
int default_val, int min_val, int max_val,
LOCKMODE lockmode);
@@ -214,6 +211,9 @@ extern void register_reloptions_validator(local_relopts *relopts,
extern void add_local_bool_reloption(local_relopts *relopts, const char *name,
const char *desc, bool default_val,
int offset);
+extern void add_local_ternary_reloption(local_relopts *relopts,
+ const char *name, const char *desc,
+ ternary default_val, int offset);
extern void add_local_int_reloption(local_relopts *relopts, const char *name,
const char *desc, int default_val,
int min_val, int max_val, int offset);
diff --git a/src/include/c.h b/src/include/c.h
index 39022f8a9d..9c59ccccf8 100644
--- a/src/include/c.h
+++ b/src/include/c.h
@@ -488,6 +488,22 @@ typedef void (*pg_funcptr_t) (void);
#include <stdbool.h>
+/*
+ * ternary
+ * Boolean value with an extrea "unset" option
+ *
+ * Ternary data type is used in relation options that can be "true", "false" or
+ * "unset". Since relation options are used deep inside the PostgreSQL code,
+ * this type is declared globally.
+*/
+
+typedef enum ternary
+{
+ TERNARY_FALSE = 0,
+ TERNARY_TRUE = 1,
+ TERNARY_UNSET = -1
+} ternary;
+
/* ----------------------------------------------------------------
* Section 3: standard system types
diff --git a/src/include/utils/rel.h b/src/include/utils/rel.h
index b552359915..7346d618f9 100644
--- a/src/include/utils/rel.h
+++ b/src/include/utils/rel.h
@@ -346,8 +346,7 @@ typedef struct StdRdOptions
bool user_catalog_table; /* use as an additional catalog relation */
int parallel_workers; /* max number of parallel workers */
StdRdOptIndexCleanup vacuum_index_cleanup; /* controls index vacuuming */
- bool vacuum_truncate; /* enables vacuum to truncate a relation */
- bool vacuum_truncate_set; /* whether vacuum_truncate is set */
+ ternary vacuum_truncate; /* enables vacuum to truncate a relation */
/*
* Fraction of pages in a relation that vacuum can eagerly scan and fail
--
2.39.2
--nextPart5129932.5fSG56mABF
Content-Disposition: attachment;
filename="v2-0003-Add-alias-to-be-used-as-unset-state.patch"
Content-Transfer-Encoding: 7Bit
Content-Type: text/x-patch; charset="unicode-2-0-utf-8";
name="v2-0003-Add-alias-to-be-used-as-unset-state.patch"
^ permalink raw reply [nested|flat] 868+ messages in thread
* [PATCH v2a 2/4] Introduce ternary reloptions
@ 2025-09-04 14:41 Nikolay Shaplov <[email protected]>
0 siblings, 0 replies; 868+ messages in thread
From: Nikolay Shaplov @ 2025-09-04 14:41 UTC (permalink / raw)
Introduce ternary reloption as a replacement for current `vacuum_truncate`
implementation. Remove `vacuum_truncate_set` additional flag and using
`TERNARY_UNSET` value instead.
---
src/backend/access/common/reloptions.c | 129 ++++++++++++++++++++-----
src/backend/commands/vacuum.c | 4 +-
src/include/access/reloptions.h | 26 ++---
src/include/c.h | 16 +++
src/include/utils/rel.h | 3 +-
5 files changed, 135 insertions(+), 43 deletions(-)
diff --git a/src/backend/access/common/reloptions.c b/src/backend/access/common/reloptions.c
index 0af3fea68f..f543f11001 100644
--- a/src/backend/access/common/reloptions.c
+++ b/src/backend/access/common/reloptions.c
@@ -40,9 +40,9 @@
*
* To add an option:
*
- * (i) decide on a type (bool, integer, real, enum, string), name, default
- * value, upper and lower bounds (if applicable); for strings, consider a
- * validation routine.
+ * (i) decide on a type (bool, ternary, integer, real, enum, string), name,
+ * default value, upper and lower bounds (if applicable); for strings,
+ * consider a validation routine.
* (ii) add a record below (or use add_<type>_reloption).
* (iii) add it to the appropriate options struct (perhaps StdRdOptions)
* (iv) add it to the appropriate handling routine (perhaps
@@ -147,15 +147,6 @@ static relopt_bool boolRelOpts[] =
},
false
},
- {
- {
- "vacuum_truncate",
- "Enables vacuum to truncate empty pages at the end of this table",
- RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
- ShareUpdateExclusiveLock
- },
- true
- },
{
{
"deduplicate_items",
@@ -170,6 +161,21 @@ static relopt_bool boolRelOpts[] =
{{NULL}}
};
+static relopt_ternary ternaryRelOpts[] =
+{
+ {
+ {
+ "vacuum_truncate",
+ "Enables vacuum to truncate empty pages at the end of this table",
+ RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
+ ShareUpdateExclusiveLock
+ },
+ TERNARY_UNSET
+ },
+ /* list terminator */
+ {{NULL}}
+};
+
static relopt_int intRelOpts[] =
{
{
@@ -600,6 +606,13 @@ initialize_reloptions(void)
boolRelOpts[i].gen.lockmode));
j++;
}
+ for (i = 0; ternaryRelOpts[i].gen.name; i++)
+ {
+ Assert(DoLockModesConflict(ternaryRelOpts[i].gen.lockmode,
+ ternaryRelOpts[i].gen.lockmode));
+ j++;
+ }
+
for (i = 0; intRelOpts[i].gen.name; i++)
{
Assert(DoLockModesConflict(intRelOpts[i].gen.lockmode,
@@ -640,6 +653,14 @@ initialize_reloptions(void)
j++;
}
+ for (i = 0; ternaryRelOpts[i].gen.name; i++)
+ {
+ relOpts[j] = &ternaryRelOpts[i].gen;
+ relOpts[j]->type = RELOPT_TYPE_TERNARY;
+ relOpts[j]->namelen = strlen(relOpts[j]->name);
+ j++;
+ }
+
for (i = 0; intRelOpts[i].gen.name; i++)
{
relOpts[j] = &intRelOpts[i].gen;
@@ -800,6 +821,9 @@ allocate_reloption(bits32 kinds, int type, const char *name, const char *desc,
case RELOPT_TYPE_BOOL:
size = sizeof(relopt_bool);
break;
+ case RELOPT_TYPE_TERNARY:
+ size = sizeof(relopt_ternary);
+ break;
case RELOPT_TYPE_INT:
size = sizeof(relopt_int);
break;
@@ -883,6 +907,54 @@ add_local_bool_reloption(local_relopts *relopts, const char *name,
add_local_reloption(relopts, (relopt_gen *) newoption, offset);
}
+/*
+ * init_ternary_reloption
+ * Allocate and initialize a new ternary reloption
+ */
+static relopt_ternary *
+init_ternary_reloption(bits32 kinds, const char *name, const char *desc,
+ ternary default_val, LOCKMODE lockmode)
+{
+ relopt_ternary *newoption;
+
+ newoption = (relopt_ternary *) allocate_reloption(kinds,
+ RELOPT_TYPE_TERNARY, name, desc, lockmode);
+ newoption->default_val = default_val;
+
+ return newoption;
+}
+
+/*
+ * add_ternary_reloption
+ * Add a new ternary reloption
+ */
+void
+add_ternary_reloption(bits32 kinds, const char *name, const char *desc,
+ ternary default_val, LOCKMODE lockmode)
+{
+ relopt_ternary *newoption = init_ternary_reloption(kinds, name, desc,
+ default_val, lockmode);
+
+ add_reloption((relopt_gen *) newoption);
+}
+
+/*
+ * add_local_ternary_reloption
+ * Add a new ternary local reloption
+ *
+ * 'offset' is offset of ternary-typed field.
+ */
+void
+add_local_ternary_reloption(local_relopts *relopts, const char *name,
+ const char *desc, ternary default_val,
+ int offset)
+{
+ relopt_ternary *newoption = init_ternary_reloption(RELOPT_KIND_LOCAL,
+ name, desc,
+ default_val, 0);
+
+ add_local_reloption(relopts, (relopt_gen *) newoption, offset);
+}
/*
* init_real_reloption
@@ -1617,6 +1689,18 @@ parse_one_reloption(relopt_value *option, char *text_str, int text_len,
option->gen->name, value)));
}
break;
+ case RELOPT_TYPE_TERNARY:
+ {
+ bool b;
+ parsed = parse_bool(value, &b);
+ option->values.ternary_val = b ? TERNARY_TRUE : TERNARY_FALSE;
+ if (validate && !parsed)
+ ereport(ERROR,
+ (errcode(ERRCODE_INVALID_PARAMETER_VALUE),
+ errmsg("invalid value for ternary option \"%s\": %s",
+ option->gen->name, value)));
+ }
+ break;
case RELOPT_TYPE_INT:
{
relopt_int *optint = (relopt_int *) option->gen;
@@ -1780,17 +1864,6 @@ fillRelOptions(void *rdopts, Size basesize,
char *itempos = ((char *) rdopts) + elems[j].offset;
char *string_val;
- /*
- * If isset_offset is provided, store whether the reloption is
- * set there.
- */
- if (elems[j].isset_offset > 0)
- {
- char *setpos = ((char *) rdopts) + elems[j].isset_offset;
-
- *(bool *) setpos = options[i].isset;
- }
-
switch (options[i].gen->type)
{
case RELOPT_TYPE_BOOL:
@@ -1798,6 +1871,11 @@ fillRelOptions(void *rdopts, Size basesize,
options[i].values.bool_val :
((relopt_bool *) options[i].gen)->default_val;
break;
+ case RELOPT_TYPE_TERNARY:
+ *(ternary *) itempos = options[i].isset ?
+ options[i].values.ternary_val :
+ ((relopt_ternary *) options[i].gen)->default_val;
+ break;
case RELOPT_TYPE_INT:
*(int *) itempos = options[i].isset ?
options[i].values.int_val :
@@ -1912,8 +1990,8 @@ default_reloptions(Datum reloptions, bool validate, relopt_kind kind)
offsetof(StdRdOptions, parallel_workers)},
{"vacuum_index_cleanup", RELOPT_TYPE_ENUM,
offsetof(StdRdOptions, vacuum_index_cleanup)},
- {"vacuum_truncate", RELOPT_TYPE_BOOL,
- offsetof(StdRdOptions, vacuum_truncate), offsetof(StdRdOptions, vacuum_truncate_set)},
+ {"vacuum_truncate", RELOPT_TYPE_TERNARY,
+ offsetof(StdRdOptions, vacuum_truncate)},
{"vacuum_max_eager_freeze_failure_rate", RELOPT_TYPE_REAL,
offsetof(StdRdOptions, vacuum_max_eager_freeze_failure_rate)}
};
@@ -1993,7 +2071,6 @@ build_local_reloptions(local_relopts *relopts, Datum options, bool validate)
elems[i].optname = opt->option->name;
elems[i].opttype = opt->option->type;
elems[i].offset = opt->offset;
- elems[i].isset_offset = 0; /* not supported for local relopts yet */
i++;
}
diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c
index 733ef40ae7..977babff54 100644
--- a/src/backend/commands/vacuum.c
+++ b/src/backend/commands/vacuum.c
@@ -2221,9 +2221,9 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams params,
{
StdRdOptions *opts = (StdRdOptions *) rel->rd_options;
- if (opts && opts->vacuum_truncate_set)
+ if (opts && opts->vacuum_truncate != TERNARY_UNSET)
{
- if (opts->vacuum_truncate)
+ if (opts->vacuum_truncate == TERNARY_TRUE)
params.truncate = VACOPTVALUE_ENABLED;
else
params.truncate = VACOPTVALUE_DISABLED;
diff --git a/src/include/access/reloptions.h b/src/include/access/reloptions.h
index a604a4702c..a436697658 100644
--- a/src/include/access/reloptions.h
+++ b/src/include/access/reloptions.h
@@ -29,6 +29,7 @@
typedef enum relopt_type
{
RELOPT_TYPE_BOOL,
+ RELOPT_TYPE_TERNARY, /* on, off, unset */
RELOPT_TYPE_INT,
RELOPT_TYPE_REAL,
RELOPT_TYPE_ENUM,
@@ -80,6 +81,7 @@ typedef struct relopt_value
union
{
bool bool_val;
+ ternary ternary_val;
int int_val;
double real_val;
int enum_val;
@@ -94,6 +96,12 @@ typedef struct relopt_bool
bool default_val;
} relopt_bool;
+typedef struct relopt_rernary
+{
+ relopt_gen gen;
+ int default_val;
+} relopt_ternary;
+
typedef struct relopt_int
{
relopt_gen gen;
@@ -152,19 +160,6 @@ typedef struct
const char *optname; /* option's name */
relopt_type opttype; /* option's datatype */
int offset; /* offset of field in result struct */
-
- /*
- * isset_offset is an optional offset of a field in the result struct that
- * stores whether the option is explicitly set for the relation or if it
- * just picked up the default value. In most cases, this can be
- * accomplished by giving the reloption a special out-of-range default
- * value (e.g., some integer reloptions use -2), but this isn't always
- * possible. For example, a Boolean reloption cannot be given an
- * out-of-range default, so we need another way to discover the source of
- * its value. This offset is only used if given a value greater than
- * zero.
- */
- int isset_offset;
} relopt_parse_elt;
/* Local reloption definition */
@@ -195,6 +190,8 @@ typedef struct local_relopts
extern relopt_kind add_reloption_kind(void);
extern void add_bool_reloption(bits32 kinds, const char *name, const char *desc,
bool default_val, LOCKMODE lockmode);
+extern void add_ternary_reloption(bits32 kinds, const char *name,
+ const char *desc, int default_val, LOCKMODE lockmode);
extern void add_int_reloption(bits32 kinds, const char *name, const char *desc,
int default_val, int min_val, int max_val,
LOCKMODE lockmode);
@@ -214,6 +211,9 @@ extern void register_reloptions_validator(local_relopts *relopts,
extern void add_local_bool_reloption(local_relopts *relopts, const char *name,
const char *desc, bool default_val,
int offset);
+extern void add_local_ternary_reloption(local_relopts *relopts,
+ const char *name, const char *desc,
+ ternary default_val, int offset);
extern void add_local_int_reloption(local_relopts *relopts, const char *name,
const char *desc, int default_val,
int min_val, int max_val, int offset);
diff --git a/src/include/c.h b/src/include/c.h
index 39022f8a9d..9c59ccccf8 100644
--- a/src/include/c.h
+++ b/src/include/c.h
@@ -488,6 +488,22 @@ typedef void (*pg_funcptr_t) (void);
#include <stdbool.h>
+/*
+ * ternary
+ * Boolean value with an extrea "unset" option
+ *
+ * Ternary data type is used in relation options that can be "true", "false" or
+ * "unset". Since relation options are used deep inside the PostgreSQL code,
+ * this type is declared globally.
+*/
+
+typedef enum ternary
+{
+ TERNARY_FALSE = 0,
+ TERNARY_TRUE = 1,
+ TERNARY_UNSET = -1
+} ternary;
+
/* ----------------------------------------------------------------
* Section 3: standard system types
diff --git a/src/include/utils/rel.h b/src/include/utils/rel.h
index b552359915..7346d618f9 100644
--- a/src/include/utils/rel.h
+++ b/src/include/utils/rel.h
@@ -346,8 +346,7 @@ typedef struct StdRdOptions
bool user_catalog_table; /* use as an additional catalog relation */
int parallel_workers; /* max number of parallel workers */
StdRdOptIndexCleanup vacuum_index_cleanup; /* controls index vacuuming */
- bool vacuum_truncate; /* enables vacuum to truncate a relation */
- bool vacuum_truncate_set; /* whether vacuum_truncate is set */
+ ternary vacuum_truncate; /* enables vacuum to truncate a relation */
/*
* Fraction of pages in a relation that vacuum can eagerly scan and fail
--
2.39.2
--nextPart4155600.3ZeAukHxDK
Content-Disposition: attachment;
filename="v2a-0003-Add-alias-to-be-used-as-unset-state.patch"
Content-Transfer-Encoding: 7Bit
Content-Type: text/x-patch; charset="unicode-2-0-utf-8";
name="v2a-0003-Add-alias-to-be-used-as-unset-state.patch"
^ permalink raw reply [nested|flat] 868+ messages in thread
* [PATCH v2 2/4] Introduce ternary reloptions
@ 2025-09-04 14:41 Nikolay Shaplov <[email protected]>
0 siblings, 0 replies; 868+ messages in thread
From: Nikolay Shaplov @ 2025-09-04 14:41 UTC (permalink / raw)
Introduce ternary reloption as a replacement for current `vacuum_truncate`
implementation. Remove `vacuum_truncate_set` additional flag and using
`TERNARY_UNSET` value instead.
---
src/backend/access/common/reloptions.c | 129 ++++++++++++++++++++-----
src/backend/commands/vacuum.c | 4 +-
src/include/access/reloptions.h | 26 ++---
src/include/c.h | 16 +++
src/include/utils/rel.h | 3 +-
5 files changed, 135 insertions(+), 43 deletions(-)
diff --git a/src/backend/access/common/reloptions.c b/src/backend/access/common/reloptions.c
index 0af3fea68f..f543f11001 100644
--- a/src/backend/access/common/reloptions.c
+++ b/src/backend/access/common/reloptions.c
@@ -40,9 +40,9 @@
*
* To add an option:
*
- * (i) decide on a type (bool, integer, real, enum, string), name, default
- * value, upper and lower bounds (if applicable); for strings, consider a
- * validation routine.
+ * (i) decide on a type (bool, ternary, integer, real, enum, string), name,
+ * default value, upper and lower bounds (if applicable); for strings,
+ * consider a validation routine.
* (ii) add a record below (or use add_<type>_reloption).
* (iii) add it to the appropriate options struct (perhaps StdRdOptions)
* (iv) add it to the appropriate handling routine (perhaps
@@ -147,15 +147,6 @@ static relopt_bool boolRelOpts[] =
},
false
},
- {
- {
- "vacuum_truncate",
- "Enables vacuum to truncate empty pages at the end of this table",
- RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
- ShareUpdateExclusiveLock
- },
- true
- },
{
{
"deduplicate_items",
@@ -170,6 +161,21 @@ static relopt_bool boolRelOpts[] =
{{NULL}}
};
+static relopt_ternary ternaryRelOpts[] =
+{
+ {
+ {
+ "vacuum_truncate",
+ "Enables vacuum to truncate empty pages at the end of this table",
+ RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
+ ShareUpdateExclusiveLock
+ },
+ TERNARY_UNSET
+ },
+ /* list terminator */
+ {{NULL}}
+};
+
static relopt_int intRelOpts[] =
{
{
@@ -600,6 +606,13 @@ initialize_reloptions(void)
boolRelOpts[i].gen.lockmode));
j++;
}
+ for (i = 0; ternaryRelOpts[i].gen.name; i++)
+ {
+ Assert(DoLockModesConflict(ternaryRelOpts[i].gen.lockmode,
+ ternaryRelOpts[i].gen.lockmode));
+ j++;
+ }
+
for (i = 0; intRelOpts[i].gen.name; i++)
{
Assert(DoLockModesConflict(intRelOpts[i].gen.lockmode,
@@ -640,6 +653,14 @@ initialize_reloptions(void)
j++;
}
+ for (i = 0; ternaryRelOpts[i].gen.name; i++)
+ {
+ relOpts[j] = &ternaryRelOpts[i].gen;
+ relOpts[j]->type = RELOPT_TYPE_TERNARY;
+ relOpts[j]->namelen = strlen(relOpts[j]->name);
+ j++;
+ }
+
for (i = 0; intRelOpts[i].gen.name; i++)
{
relOpts[j] = &intRelOpts[i].gen;
@@ -800,6 +821,9 @@ allocate_reloption(bits32 kinds, int type, const char *name, const char *desc,
case RELOPT_TYPE_BOOL:
size = sizeof(relopt_bool);
break;
+ case RELOPT_TYPE_TERNARY:
+ size = sizeof(relopt_ternary);
+ break;
case RELOPT_TYPE_INT:
size = sizeof(relopt_int);
break;
@@ -883,6 +907,54 @@ add_local_bool_reloption(local_relopts *relopts, const char *name,
add_local_reloption(relopts, (relopt_gen *) newoption, offset);
}
+/*
+ * init_ternary_reloption
+ * Allocate and initialize a new ternary reloption
+ */
+static relopt_ternary *
+init_ternary_reloption(bits32 kinds, const char *name, const char *desc,
+ ternary default_val, LOCKMODE lockmode)
+{
+ relopt_ternary *newoption;
+
+ newoption = (relopt_ternary *) allocate_reloption(kinds,
+ RELOPT_TYPE_TERNARY, name, desc, lockmode);
+ newoption->default_val = default_val;
+
+ return newoption;
+}
+
+/*
+ * add_ternary_reloption
+ * Add a new ternary reloption
+ */
+void
+add_ternary_reloption(bits32 kinds, const char *name, const char *desc,
+ ternary default_val, LOCKMODE lockmode)
+{
+ relopt_ternary *newoption = init_ternary_reloption(kinds, name, desc,
+ default_val, lockmode);
+
+ add_reloption((relopt_gen *) newoption);
+}
+
+/*
+ * add_local_ternary_reloption
+ * Add a new ternary local reloption
+ *
+ * 'offset' is offset of ternary-typed field.
+ */
+void
+add_local_ternary_reloption(local_relopts *relopts, const char *name,
+ const char *desc, ternary default_val,
+ int offset)
+{
+ relopt_ternary *newoption = init_ternary_reloption(RELOPT_KIND_LOCAL,
+ name, desc,
+ default_val, 0);
+
+ add_local_reloption(relopts, (relopt_gen *) newoption, offset);
+}
/*
* init_real_reloption
@@ -1617,6 +1689,18 @@ parse_one_reloption(relopt_value *option, char *text_str, int text_len,
option->gen->name, value)));
}
break;
+ case RELOPT_TYPE_TERNARY:
+ {
+ bool b;
+ parsed = parse_bool(value, &b);
+ option->values.ternary_val = b ? TERNARY_TRUE : TERNARY_FALSE;
+ if (validate && !parsed)
+ ereport(ERROR,
+ (errcode(ERRCODE_INVALID_PARAMETER_VALUE),
+ errmsg("invalid value for ternary option \"%s\": %s",
+ option->gen->name, value)));
+ }
+ break;
case RELOPT_TYPE_INT:
{
relopt_int *optint = (relopt_int *) option->gen;
@@ -1780,17 +1864,6 @@ fillRelOptions(void *rdopts, Size basesize,
char *itempos = ((char *) rdopts) + elems[j].offset;
char *string_val;
- /*
- * If isset_offset is provided, store whether the reloption is
- * set there.
- */
- if (elems[j].isset_offset > 0)
- {
- char *setpos = ((char *) rdopts) + elems[j].isset_offset;
-
- *(bool *) setpos = options[i].isset;
- }
-
switch (options[i].gen->type)
{
case RELOPT_TYPE_BOOL:
@@ -1798,6 +1871,11 @@ fillRelOptions(void *rdopts, Size basesize,
options[i].values.bool_val :
((relopt_bool *) options[i].gen)->default_val;
break;
+ case RELOPT_TYPE_TERNARY:
+ *(ternary *) itempos = options[i].isset ?
+ options[i].values.ternary_val :
+ ((relopt_ternary *) options[i].gen)->default_val;
+ break;
case RELOPT_TYPE_INT:
*(int *) itempos = options[i].isset ?
options[i].values.int_val :
@@ -1912,8 +1990,8 @@ default_reloptions(Datum reloptions, bool validate, relopt_kind kind)
offsetof(StdRdOptions, parallel_workers)},
{"vacuum_index_cleanup", RELOPT_TYPE_ENUM,
offsetof(StdRdOptions, vacuum_index_cleanup)},
- {"vacuum_truncate", RELOPT_TYPE_BOOL,
- offsetof(StdRdOptions, vacuum_truncate), offsetof(StdRdOptions, vacuum_truncate_set)},
+ {"vacuum_truncate", RELOPT_TYPE_TERNARY,
+ offsetof(StdRdOptions, vacuum_truncate)},
{"vacuum_max_eager_freeze_failure_rate", RELOPT_TYPE_REAL,
offsetof(StdRdOptions, vacuum_max_eager_freeze_failure_rate)}
};
@@ -1993,7 +2071,6 @@ build_local_reloptions(local_relopts *relopts, Datum options, bool validate)
elems[i].optname = opt->option->name;
elems[i].opttype = opt->option->type;
elems[i].offset = opt->offset;
- elems[i].isset_offset = 0; /* not supported for local relopts yet */
i++;
}
diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c
index 733ef40ae7..977babff54 100644
--- a/src/backend/commands/vacuum.c
+++ b/src/backend/commands/vacuum.c
@@ -2221,9 +2221,9 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams params,
{
StdRdOptions *opts = (StdRdOptions *) rel->rd_options;
- if (opts && opts->vacuum_truncate_set)
+ if (opts && opts->vacuum_truncate != TERNARY_UNSET)
{
- if (opts->vacuum_truncate)
+ if (opts->vacuum_truncate == TERNARY_TRUE)
params.truncate = VACOPTVALUE_ENABLED;
else
params.truncate = VACOPTVALUE_DISABLED;
diff --git a/src/include/access/reloptions.h b/src/include/access/reloptions.h
index a604a4702c..a436697658 100644
--- a/src/include/access/reloptions.h
+++ b/src/include/access/reloptions.h
@@ -29,6 +29,7 @@
typedef enum relopt_type
{
RELOPT_TYPE_BOOL,
+ RELOPT_TYPE_TERNARY, /* on, off, unset */
RELOPT_TYPE_INT,
RELOPT_TYPE_REAL,
RELOPT_TYPE_ENUM,
@@ -80,6 +81,7 @@ typedef struct relopt_value
union
{
bool bool_val;
+ ternary ternary_val;
int int_val;
double real_val;
int enum_val;
@@ -94,6 +96,12 @@ typedef struct relopt_bool
bool default_val;
} relopt_bool;
+typedef struct relopt_rernary
+{
+ relopt_gen gen;
+ int default_val;
+} relopt_ternary;
+
typedef struct relopt_int
{
relopt_gen gen;
@@ -152,19 +160,6 @@ typedef struct
const char *optname; /* option's name */
relopt_type opttype; /* option's datatype */
int offset; /* offset of field in result struct */
-
- /*
- * isset_offset is an optional offset of a field in the result struct that
- * stores whether the option is explicitly set for the relation or if it
- * just picked up the default value. In most cases, this can be
- * accomplished by giving the reloption a special out-of-range default
- * value (e.g., some integer reloptions use -2), but this isn't always
- * possible. For example, a Boolean reloption cannot be given an
- * out-of-range default, so we need another way to discover the source of
- * its value. This offset is only used if given a value greater than
- * zero.
- */
- int isset_offset;
} relopt_parse_elt;
/* Local reloption definition */
@@ -195,6 +190,8 @@ typedef struct local_relopts
extern relopt_kind add_reloption_kind(void);
extern void add_bool_reloption(bits32 kinds, const char *name, const char *desc,
bool default_val, LOCKMODE lockmode);
+extern void add_ternary_reloption(bits32 kinds, const char *name,
+ const char *desc, int default_val, LOCKMODE lockmode);
extern void add_int_reloption(bits32 kinds, const char *name, const char *desc,
int default_val, int min_val, int max_val,
LOCKMODE lockmode);
@@ -214,6 +211,9 @@ extern void register_reloptions_validator(local_relopts *relopts,
extern void add_local_bool_reloption(local_relopts *relopts, const char *name,
const char *desc, bool default_val,
int offset);
+extern void add_local_ternary_reloption(local_relopts *relopts,
+ const char *name, const char *desc,
+ ternary default_val, int offset);
extern void add_local_int_reloption(local_relopts *relopts, const char *name,
const char *desc, int default_val,
int min_val, int max_val, int offset);
diff --git a/src/include/c.h b/src/include/c.h
index 39022f8a9d..9c59ccccf8 100644
--- a/src/include/c.h
+++ b/src/include/c.h
@@ -488,6 +488,22 @@ typedef void (*pg_funcptr_t) (void);
#include <stdbool.h>
+/*
+ * ternary
+ * Boolean value with an extrea "unset" option
+ *
+ * Ternary data type is used in relation options that can be "true", "false" or
+ * "unset". Since relation options are used deep inside the PostgreSQL code,
+ * this type is declared globally.
+*/
+
+typedef enum ternary
+{
+ TERNARY_FALSE = 0,
+ TERNARY_TRUE = 1,
+ TERNARY_UNSET = -1
+} ternary;
+
/* ----------------------------------------------------------------
* Section 3: standard system types
diff --git a/src/include/utils/rel.h b/src/include/utils/rel.h
index b552359915..7346d618f9 100644
--- a/src/include/utils/rel.h
+++ b/src/include/utils/rel.h
@@ -346,8 +346,7 @@ typedef struct StdRdOptions
bool user_catalog_table; /* use as an additional catalog relation */
int parallel_workers; /* max number of parallel workers */
StdRdOptIndexCleanup vacuum_index_cleanup; /* controls index vacuuming */
- bool vacuum_truncate; /* enables vacuum to truncate a relation */
- bool vacuum_truncate_set; /* whether vacuum_truncate is set */
+ ternary vacuum_truncate; /* enables vacuum to truncate a relation */
/*
* Fraction of pages in a relation that vacuum can eagerly scan and fail
--
2.39.2
--nextPart5129932.5fSG56mABF
Content-Disposition: attachment;
filename="v2-0003-Add-alias-to-be-used-as-unset-state.patch"
Content-Transfer-Encoding: 7Bit
Content-Type: text/x-patch; charset="unicode-2-0-utf-8";
name="v2-0003-Add-alias-to-be-used-as-unset-state.patch"
^ permalink raw reply [nested|flat] 868+ messages in thread
* [PATCH v2a 2/4] Introduce ternary reloptions
@ 2025-09-04 14:41 Nikolay Shaplov <[email protected]>
0 siblings, 0 replies; 868+ messages in thread
From: Nikolay Shaplov @ 2025-09-04 14:41 UTC (permalink / raw)
Introduce ternary reloption as a replacement for current `vacuum_truncate`
implementation. Remove `vacuum_truncate_set` additional flag and using
`TERNARY_UNSET` value instead.
---
src/backend/access/common/reloptions.c | 129 ++++++++++++++++++++-----
src/backend/commands/vacuum.c | 4 +-
src/include/access/reloptions.h | 26 ++---
src/include/c.h | 16 +++
src/include/utils/rel.h | 3 +-
5 files changed, 135 insertions(+), 43 deletions(-)
diff --git a/src/backend/access/common/reloptions.c b/src/backend/access/common/reloptions.c
index 0af3fea68f..f543f11001 100644
--- a/src/backend/access/common/reloptions.c
+++ b/src/backend/access/common/reloptions.c
@@ -40,9 +40,9 @@
*
* To add an option:
*
- * (i) decide on a type (bool, integer, real, enum, string), name, default
- * value, upper and lower bounds (if applicable); for strings, consider a
- * validation routine.
+ * (i) decide on a type (bool, ternary, integer, real, enum, string), name,
+ * default value, upper and lower bounds (if applicable); for strings,
+ * consider a validation routine.
* (ii) add a record below (or use add_<type>_reloption).
* (iii) add it to the appropriate options struct (perhaps StdRdOptions)
* (iv) add it to the appropriate handling routine (perhaps
@@ -147,15 +147,6 @@ static relopt_bool boolRelOpts[] =
},
false
},
- {
- {
- "vacuum_truncate",
- "Enables vacuum to truncate empty pages at the end of this table",
- RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
- ShareUpdateExclusiveLock
- },
- true
- },
{
{
"deduplicate_items",
@@ -170,6 +161,21 @@ static relopt_bool boolRelOpts[] =
{{NULL}}
};
+static relopt_ternary ternaryRelOpts[] =
+{
+ {
+ {
+ "vacuum_truncate",
+ "Enables vacuum to truncate empty pages at the end of this table",
+ RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
+ ShareUpdateExclusiveLock
+ },
+ TERNARY_UNSET
+ },
+ /* list terminator */
+ {{NULL}}
+};
+
static relopt_int intRelOpts[] =
{
{
@@ -600,6 +606,13 @@ initialize_reloptions(void)
boolRelOpts[i].gen.lockmode));
j++;
}
+ for (i = 0; ternaryRelOpts[i].gen.name; i++)
+ {
+ Assert(DoLockModesConflict(ternaryRelOpts[i].gen.lockmode,
+ ternaryRelOpts[i].gen.lockmode));
+ j++;
+ }
+
for (i = 0; intRelOpts[i].gen.name; i++)
{
Assert(DoLockModesConflict(intRelOpts[i].gen.lockmode,
@@ -640,6 +653,14 @@ initialize_reloptions(void)
j++;
}
+ for (i = 0; ternaryRelOpts[i].gen.name; i++)
+ {
+ relOpts[j] = &ternaryRelOpts[i].gen;
+ relOpts[j]->type = RELOPT_TYPE_TERNARY;
+ relOpts[j]->namelen = strlen(relOpts[j]->name);
+ j++;
+ }
+
for (i = 0; intRelOpts[i].gen.name; i++)
{
relOpts[j] = &intRelOpts[i].gen;
@@ -800,6 +821,9 @@ allocate_reloption(bits32 kinds, int type, const char *name, const char *desc,
case RELOPT_TYPE_BOOL:
size = sizeof(relopt_bool);
break;
+ case RELOPT_TYPE_TERNARY:
+ size = sizeof(relopt_ternary);
+ break;
case RELOPT_TYPE_INT:
size = sizeof(relopt_int);
break;
@@ -883,6 +907,54 @@ add_local_bool_reloption(local_relopts *relopts, const char *name,
add_local_reloption(relopts, (relopt_gen *) newoption, offset);
}
+/*
+ * init_ternary_reloption
+ * Allocate and initialize a new ternary reloption
+ */
+static relopt_ternary *
+init_ternary_reloption(bits32 kinds, const char *name, const char *desc,
+ ternary default_val, LOCKMODE lockmode)
+{
+ relopt_ternary *newoption;
+
+ newoption = (relopt_ternary *) allocate_reloption(kinds,
+ RELOPT_TYPE_TERNARY, name, desc, lockmode);
+ newoption->default_val = default_val;
+
+ return newoption;
+}
+
+/*
+ * add_ternary_reloption
+ * Add a new ternary reloption
+ */
+void
+add_ternary_reloption(bits32 kinds, const char *name, const char *desc,
+ ternary default_val, LOCKMODE lockmode)
+{
+ relopt_ternary *newoption = init_ternary_reloption(kinds, name, desc,
+ default_val, lockmode);
+
+ add_reloption((relopt_gen *) newoption);
+}
+
+/*
+ * add_local_ternary_reloption
+ * Add a new ternary local reloption
+ *
+ * 'offset' is offset of ternary-typed field.
+ */
+void
+add_local_ternary_reloption(local_relopts *relopts, const char *name,
+ const char *desc, ternary default_val,
+ int offset)
+{
+ relopt_ternary *newoption = init_ternary_reloption(RELOPT_KIND_LOCAL,
+ name, desc,
+ default_val, 0);
+
+ add_local_reloption(relopts, (relopt_gen *) newoption, offset);
+}
/*
* init_real_reloption
@@ -1617,6 +1689,18 @@ parse_one_reloption(relopt_value *option, char *text_str, int text_len,
option->gen->name, value)));
}
break;
+ case RELOPT_TYPE_TERNARY:
+ {
+ bool b;
+ parsed = parse_bool(value, &b);
+ option->values.ternary_val = b ? TERNARY_TRUE : TERNARY_FALSE;
+ if (validate && !parsed)
+ ereport(ERROR,
+ (errcode(ERRCODE_INVALID_PARAMETER_VALUE),
+ errmsg("invalid value for ternary option \"%s\": %s",
+ option->gen->name, value)));
+ }
+ break;
case RELOPT_TYPE_INT:
{
relopt_int *optint = (relopt_int *) option->gen;
@@ -1780,17 +1864,6 @@ fillRelOptions(void *rdopts, Size basesize,
char *itempos = ((char *) rdopts) + elems[j].offset;
char *string_val;
- /*
- * If isset_offset is provided, store whether the reloption is
- * set there.
- */
- if (elems[j].isset_offset > 0)
- {
- char *setpos = ((char *) rdopts) + elems[j].isset_offset;
-
- *(bool *) setpos = options[i].isset;
- }
-
switch (options[i].gen->type)
{
case RELOPT_TYPE_BOOL:
@@ -1798,6 +1871,11 @@ fillRelOptions(void *rdopts, Size basesize,
options[i].values.bool_val :
((relopt_bool *) options[i].gen)->default_val;
break;
+ case RELOPT_TYPE_TERNARY:
+ *(ternary *) itempos = options[i].isset ?
+ options[i].values.ternary_val :
+ ((relopt_ternary *) options[i].gen)->default_val;
+ break;
case RELOPT_TYPE_INT:
*(int *) itempos = options[i].isset ?
options[i].values.int_val :
@@ -1912,8 +1990,8 @@ default_reloptions(Datum reloptions, bool validate, relopt_kind kind)
offsetof(StdRdOptions, parallel_workers)},
{"vacuum_index_cleanup", RELOPT_TYPE_ENUM,
offsetof(StdRdOptions, vacuum_index_cleanup)},
- {"vacuum_truncate", RELOPT_TYPE_BOOL,
- offsetof(StdRdOptions, vacuum_truncate), offsetof(StdRdOptions, vacuum_truncate_set)},
+ {"vacuum_truncate", RELOPT_TYPE_TERNARY,
+ offsetof(StdRdOptions, vacuum_truncate)},
{"vacuum_max_eager_freeze_failure_rate", RELOPT_TYPE_REAL,
offsetof(StdRdOptions, vacuum_max_eager_freeze_failure_rate)}
};
@@ -1993,7 +2071,6 @@ build_local_reloptions(local_relopts *relopts, Datum options, bool validate)
elems[i].optname = opt->option->name;
elems[i].opttype = opt->option->type;
elems[i].offset = opt->offset;
- elems[i].isset_offset = 0; /* not supported for local relopts yet */
i++;
}
diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c
index 733ef40ae7..977babff54 100644
--- a/src/backend/commands/vacuum.c
+++ b/src/backend/commands/vacuum.c
@@ -2221,9 +2221,9 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams params,
{
StdRdOptions *opts = (StdRdOptions *) rel->rd_options;
- if (opts && opts->vacuum_truncate_set)
+ if (opts && opts->vacuum_truncate != TERNARY_UNSET)
{
- if (opts->vacuum_truncate)
+ if (opts->vacuum_truncate == TERNARY_TRUE)
params.truncate = VACOPTVALUE_ENABLED;
else
params.truncate = VACOPTVALUE_DISABLED;
diff --git a/src/include/access/reloptions.h b/src/include/access/reloptions.h
index a604a4702c..a436697658 100644
--- a/src/include/access/reloptions.h
+++ b/src/include/access/reloptions.h
@@ -29,6 +29,7 @@
typedef enum relopt_type
{
RELOPT_TYPE_BOOL,
+ RELOPT_TYPE_TERNARY, /* on, off, unset */
RELOPT_TYPE_INT,
RELOPT_TYPE_REAL,
RELOPT_TYPE_ENUM,
@@ -80,6 +81,7 @@ typedef struct relopt_value
union
{
bool bool_val;
+ ternary ternary_val;
int int_val;
double real_val;
int enum_val;
@@ -94,6 +96,12 @@ typedef struct relopt_bool
bool default_val;
} relopt_bool;
+typedef struct relopt_rernary
+{
+ relopt_gen gen;
+ int default_val;
+} relopt_ternary;
+
typedef struct relopt_int
{
relopt_gen gen;
@@ -152,19 +160,6 @@ typedef struct
const char *optname; /* option's name */
relopt_type opttype; /* option's datatype */
int offset; /* offset of field in result struct */
-
- /*
- * isset_offset is an optional offset of a field in the result struct that
- * stores whether the option is explicitly set for the relation or if it
- * just picked up the default value. In most cases, this can be
- * accomplished by giving the reloption a special out-of-range default
- * value (e.g., some integer reloptions use -2), but this isn't always
- * possible. For example, a Boolean reloption cannot be given an
- * out-of-range default, so we need another way to discover the source of
- * its value. This offset is only used if given a value greater than
- * zero.
- */
- int isset_offset;
} relopt_parse_elt;
/* Local reloption definition */
@@ -195,6 +190,8 @@ typedef struct local_relopts
extern relopt_kind add_reloption_kind(void);
extern void add_bool_reloption(bits32 kinds, const char *name, const char *desc,
bool default_val, LOCKMODE lockmode);
+extern void add_ternary_reloption(bits32 kinds, const char *name,
+ const char *desc, int default_val, LOCKMODE lockmode);
extern void add_int_reloption(bits32 kinds, const char *name, const char *desc,
int default_val, int min_val, int max_val,
LOCKMODE lockmode);
@@ -214,6 +211,9 @@ extern void register_reloptions_validator(local_relopts *relopts,
extern void add_local_bool_reloption(local_relopts *relopts, const char *name,
const char *desc, bool default_val,
int offset);
+extern void add_local_ternary_reloption(local_relopts *relopts,
+ const char *name, const char *desc,
+ ternary default_val, int offset);
extern void add_local_int_reloption(local_relopts *relopts, const char *name,
const char *desc, int default_val,
int min_val, int max_val, int offset);
diff --git a/src/include/c.h b/src/include/c.h
index 39022f8a9d..9c59ccccf8 100644
--- a/src/include/c.h
+++ b/src/include/c.h
@@ -488,6 +488,22 @@ typedef void (*pg_funcptr_t) (void);
#include <stdbool.h>
+/*
+ * ternary
+ * Boolean value with an extrea "unset" option
+ *
+ * Ternary data type is used in relation options that can be "true", "false" or
+ * "unset". Since relation options are used deep inside the PostgreSQL code,
+ * this type is declared globally.
+*/
+
+typedef enum ternary
+{
+ TERNARY_FALSE = 0,
+ TERNARY_TRUE = 1,
+ TERNARY_UNSET = -1
+} ternary;
+
/* ----------------------------------------------------------------
* Section 3: standard system types
diff --git a/src/include/utils/rel.h b/src/include/utils/rel.h
index b552359915..7346d618f9 100644
--- a/src/include/utils/rel.h
+++ b/src/include/utils/rel.h
@@ -346,8 +346,7 @@ typedef struct StdRdOptions
bool user_catalog_table; /* use as an additional catalog relation */
int parallel_workers; /* max number of parallel workers */
StdRdOptIndexCleanup vacuum_index_cleanup; /* controls index vacuuming */
- bool vacuum_truncate; /* enables vacuum to truncate a relation */
- bool vacuum_truncate_set; /* whether vacuum_truncate is set */
+ ternary vacuum_truncate; /* enables vacuum to truncate a relation */
/*
* Fraction of pages in a relation that vacuum can eagerly scan and fail
--
2.39.2
--nextPart4155600.3ZeAukHxDK
Content-Disposition: attachment;
filename="v2a-0003-Add-alias-to-be-used-as-unset-state.patch"
Content-Transfer-Encoding: 7Bit
Content-Type: text/x-patch; charset="unicode-2-0-utf-8";
name="v2a-0003-Add-alias-to-be-used-as-unset-state.patch"
^ permalink raw reply [nested|flat] 868+ messages in thread
* [PATCH v2a 2/4] Introduce ternary reloptions
@ 2025-09-04 14:41 Nikolay Shaplov <[email protected]>
0 siblings, 0 replies; 868+ messages in thread
From: Nikolay Shaplov @ 2025-09-04 14:41 UTC (permalink / raw)
Introduce ternary reloption as a replacement for current `vacuum_truncate`
implementation. Remove `vacuum_truncate_set` additional flag and using
`TERNARY_UNSET` value instead.
---
src/backend/access/common/reloptions.c | 129 ++++++++++++++++++++-----
src/backend/commands/vacuum.c | 4 +-
src/include/access/reloptions.h | 26 ++---
src/include/c.h | 16 +++
src/include/utils/rel.h | 3 +-
5 files changed, 135 insertions(+), 43 deletions(-)
diff --git a/src/backend/access/common/reloptions.c b/src/backend/access/common/reloptions.c
index 0af3fea68f..f543f11001 100644
--- a/src/backend/access/common/reloptions.c
+++ b/src/backend/access/common/reloptions.c
@@ -40,9 +40,9 @@
*
* To add an option:
*
- * (i) decide on a type (bool, integer, real, enum, string), name, default
- * value, upper and lower bounds (if applicable); for strings, consider a
- * validation routine.
+ * (i) decide on a type (bool, ternary, integer, real, enum, string), name,
+ * default value, upper and lower bounds (if applicable); for strings,
+ * consider a validation routine.
* (ii) add a record below (or use add_<type>_reloption).
* (iii) add it to the appropriate options struct (perhaps StdRdOptions)
* (iv) add it to the appropriate handling routine (perhaps
@@ -147,15 +147,6 @@ static relopt_bool boolRelOpts[] =
},
false
},
- {
- {
- "vacuum_truncate",
- "Enables vacuum to truncate empty pages at the end of this table",
- RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
- ShareUpdateExclusiveLock
- },
- true
- },
{
{
"deduplicate_items",
@@ -170,6 +161,21 @@ static relopt_bool boolRelOpts[] =
{{NULL}}
};
+static relopt_ternary ternaryRelOpts[] =
+{
+ {
+ {
+ "vacuum_truncate",
+ "Enables vacuum to truncate empty pages at the end of this table",
+ RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
+ ShareUpdateExclusiveLock
+ },
+ TERNARY_UNSET
+ },
+ /* list terminator */
+ {{NULL}}
+};
+
static relopt_int intRelOpts[] =
{
{
@@ -600,6 +606,13 @@ initialize_reloptions(void)
boolRelOpts[i].gen.lockmode));
j++;
}
+ for (i = 0; ternaryRelOpts[i].gen.name; i++)
+ {
+ Assert(DoLockModesConflict(ternaryRelOpts[i].gen.lockmode,
+ ternaryRelOpts[i].gen.lockmode));
+ j++;
+ }
+
for (i = 0; intRelOpts[i].gen.name; i++)
{
Assert(DoLockModesConflict(intRelOpts[i].gen.lockmode,
@@ -640,6 +653,14 @@ initialize_reloptions(void)
j++;
}
+ for (i = 0; ternaryRelOpts[i].gen.name; i++)
+ {
+ relOpts[j] = &ternaryRelOpts[i].gen;
+ relOpts[j]->type = RELOPT_TYPE_TERNARY;
+ relOpts[j]->namelen = strlen(relOpts[j]->name);
+ j++;
+ }
+
for (i = 0; intRelOpts[i].gen.name; i++)
{
relOpts[j] = &intRelOpts[i].gen;
@@ -800,6 +821,9 @@ allocate_reloption(bits32 kinds, int type, const char *name, const char *desc,
case RELOPT_TYPE_BOOL:
size = sizeof(relopt_bool);
break;
+ case RELOPT_TYPE_TERNARY:
+ size = sizeof(relopt_ternary);
+ break;
case RELOPT_TYPE_INT:
size = sizeof(relopt_int);
break;
@@ -883,6 +907,54 @@ add_local_bool_reloption(local_relopts *relopts, const char *name,
add_local_reloption(relopts, (relopt_gen *) newoption, offset);
}
+/*
+ * init_ternary_reloption
+ * Allocate and initialize a new ternary reloption
+ */
+static relopt_ternary *
+init_ternary_reloption(bits32 kinds, const char *name, const char *desc,
+ ternary default_val, LOCKMODE lockmode)
+{
+ relopt_ternary *newoption;
+
+ newoption = (relopt_ternary *) allocate_reloption(kinds,
+ RELOPT_TYPE_TERNARY, name, desc, lockmode);
+ newoption->default_val = default_val;
+
+ return newoption;
+}
+
+/*
+ * add_ternary_reloption
+ * Add a new ternary reloption
+ */
+void
+add_ternary_reloption(bits32 kinds, const char *name, const char *desc,
+ ternary default_val, LOCKMODE lockmode)
+{
+ relopt_ternary *newoption = init_ternary_reloption(kinds, name, desc,
+ default_val, lockmode);
+
+ add_reloption((relopt_gen *) newoption);
+}
+
+/*
+ * add_local_ternary_reloption
+ * Add a new ternary local reloption
+ *
+ * 'offset' is offset of ternary-typed field.
+ */
+void
+add_local_ternary_reloption(local_relopts *relopts, const char *name,
+ const char *desc, ternary default_val,
+ int offset)
+{
+ relopt_ternary *newoption = init_ternary_reloption(RELOPT_KIND_LOCAL,
+ name, desc,
+ default_val, 0);
+
+ add_local_reloption(relopts, (relopt_gen *) newoption, offset);
+}
/*
* init_real_reloption
@@ -1617,6 +1689,18 @@ parse_one_reloption(relopt_value *option, char *text_str, int text_len,
option->gen->name, value)));
}
break;
+ case RELOPT_TYPE_TERNARY:
+ {
+ bool b;
+ parsed = parse_bool(value, &b);
+ option->values.ternary_val = b ? TERNARY_TRUE : TERNARY_FALSE;
+ if (validate && !parsed)
+ ereport(ERROR,
+ (errcode(ERRCODE_INVALID_PARAMETER_VALUE),
+ errmsg("invalid value for ternary option \"%s\": %s",
+ option->gen->name, value)));
+ }
+ break;
case RELOPT_TYPE_INT:
{
relopt_int *optint = (relopt_int *) option->gen;
@@ -1780,17 +1864,6 @@ fillRelOptions(void *rdopts, Size basesize,
char *itempos = ((char *) rdopts) + elems[j].offset;
char *string_val;
- /*
- * If isset_offset is provided, store whether the reloption is
- * set there.
- */
- if (elems[j].isset_offset > 0)
- {
- char *setpos = ((char *) rdopts) + elems[j].isset_offset;
-
- *(bool *) setpos = options[i].isset;
- }
-
switch (options[i].gen->type)
{
case RELOPT_TYPE_BOOL:
@@ -1798,6 +1871,11 @@ fillRelOptions(void *rdopts, Size basesize,
options[i].values.bool_val :
((relopt_bool *) options[i].gen)->default_val;
break;
+ case RELOPT_TYPE_TERNARY:
+ *(ternary *) itempos = options[i].isset ?
+ options[i].values.ternary_val :
+ ((relopt_ternary *) options[i].gen)->default_val;
+ break;
case RELOPT_TYPE_INT:
*(int *) itempos = options[i].isset ?
options[i].values.int_val :
@@ -1912,8 +1990,8 @@ default_reloptions(Datum reloptions, bool validate, relopt_kind kind)
offsetof(StdRdOptions, parallel_workers)},
{"vacuum_index_cleanup", RELOPT_TYPE_ENUM,
offsetof(StdRdOptions, vacuum_index_cleanup)},
- {"vacuum_truncate", RELOPT_TYPE_BOOL,
- offsetof(StdRdOptions, vacuum_truncate), offsetof(StdRdOptions, vacuum_truncate_set)},
+ {"vacuum_truncate", RELOPT_TYPE_TERNARY,
+ offsetof(StdRdOptions, vacuum_truncate)},
{"vacuum_max_eager_freeze_failure_rate", RELOPT_TYPE_REAL,
offsetof(StdRdOptions, vacuum_max_eager_freeze_failure_rate)}
};
@@ -1993,7 +2071,6 @@ build_local_reloptions(local_relopts *relopts, Datum options, bool validate)
elems[i].optname = opt->option->name;
elems[i].opttype = opt->option->type;
elems[i].offset = opt->offset;
- elems[i].isset_offset = 0; /* not supported for local relopts yet */
i++;
}
diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c
index 733ef40ae7..977babff54 100644
--- a/src/backend/commands/vacuum.c
+++ b/src/backend/commands/vacuum.c
@@ -2221,9 +2221,9 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams params,
{
StdRdOptions *opts = (StdRdOptions *) rel->rd_options;
- if (opts && opts->vacuum_truncate_set)
+ if (opts && opts->vacuum_truncate != TERNARY_UNSET)
{
- if (opts->vacuum_truncate)
+ if (opts->vacuum_truncate == TERNARY_TRUE)
params.truncate = VACOPTVALUE_ENABLED;
else
params.truncate = VACOPTVALUE_DISABLED;
diff --git a/src/include/access/reloptions.h b/src/include/access/reloptions.h
index a604a4702c..a436697658 100644
--- a/src/include/access/reloptions.h
+++ b/src/include/access/reloptions.h
@@ -29,6 +29,7 @@
typedef enum relopt_type
{
RELOPT_TYPE_BOOL,
+ RELOPT_TYPE_TERNARY, /* on, off, unset */
RELOPT_TYPE_INT,
RELOPT_TYPE_REAL,
RELOPT_TYPE_ENUM,
@@ -80,6 +81,7 @@ typedef struct relopt_value
union
{
bool bool_val;
+ ternary ternary_val;
int int_val;
double real_val;
int enum_val;
@@ -94,6 +96,12 @@ typedef struct relopt_bool
bool default_val;
} relopt_bool;
+typedef struct relopt_rernary
+{
+ relopt_gen gen;
+ int default_val;
+} relopt_ternary;
+
typedef struct relopt_int
{
relopt_gen gen;
@@ -152,19 +160,6 @@ typedef struct
const char *optname; /* option's name */
relopt_type opttype; /* option's datatype */
int offset; /* offset of field in result struct */
-
- /*
- * isset_offset is an optional offset of a field in the result struct that
- * stores whether the option is explicitly set for the relation or if it
- * just picked up the default value. In most cases, this can be
- * accomplished by giving the reloption a special out-of-range default
- * value (e.g., some integer reloptions use -2), but this isn't always
- * possible. For example, a Boolean reloption cannot be given an
- * out-of-range default, so we need another way to discover the source of
- * its value. This offset is only used if given a value greater than
- * zero.
- */
- int isset_offset;
} relopt_parse_elt;
/* Local reloption definition */
@@ -195,6 +190,8 @@ typedef struct local_relopts
extern relopt_kind add_reloption_kind(void);
extern void add_bool_reloption(bits32 kinds, const char *name, const char *desc,
bool default_val, LOCKMODE lockmode);
+extern void add_ternary_reloption(bits32 kinds, const char *name,
+ const char *desc, int default_val, LOCKMODE lockmode);
extern void add_int_reloption(bits32 kinds, const char *name, const char *desc,
int default_val, int min_val, int max_val,
LOCKMODE lockmode);
@@ -214,6 +211,9 @@ extern void register_reloptions_validator(local_relopts *relopts,
extern void add_local_bool_reloption(local_relopts *relopts, const char *name,
const char *desc, bool default_val,
int offset);
+extern void add_local_ternary_reloption(local_relopts *relopts,
+ const char *name, const char *desc,
+ ternary default_val, int offset);
extern void add_local_int_reloption(local_relopts *relopts, const char *name,
const char *desc, int default_val,
int min_val, int max_val, int offset);
diff --git a/src/include/c.h b/src/include/c.h
index 39022f8a9d..9c59ccccf8 100644
--- a/src/include/c.h
+++ b/src/include/c.h
@@ -488,6 +488,22 @@ typedef void (*pg_funcptr_t) (void);
#include <stdbool.h>
+/*
+ * ternary
+ * Boolean value with an extrea "unset" option
+ *
+ * Ternary data type is used in relation options that can be "true", "false" or
+ * "unset". Since relation options are used deep inside the PostgreSQL code,
+ * this type is declared globally.
+*/
+
+typedef enum ternary
+{
+ TERNARY_FALSE = 0,
+ TERNARY_TRUE = 1,
+ TERNARY_UNSET = -1
+} ternary;
+
/* ----------------------------------------------------------------
* Section 3: standard system types
diff --git a/src/include/utils/rel.h b/src/include/utils/rel.h
index b552359915..7346d618f9 100644
--- a/src/include/utils/rel.h
+++ b/src/include/utils/rel.h
@@ -346,8 +346,7 @@ typedef struct StdRdOptions
bool user_catalog_table; /* use as an additional catalog relation */
int parallel_workers; /* max number of parallel workers */
StdRdOptIndexCleanup vacuum_index_cleanup; /* controls index vacuuming */
- bool vacuum_truncate; /* enables vacuum to truncate a relation */
- bool vacuum_truncate_set; /* whether vacuum_truncate is set */
+ ternary vacuum_truncate; /* enables vacuum to truncate a relation */
/*
* Fraction of pages in a relation that vacuum can eagerly scan and fail
--
2.39.2
--nextPart4155600.3ZeAukHxDK
Content-Disposition: attachment;
filename="v2a-0003-Add-alias-to-be-used-as-unset-state.patch"
Content-Transfer-Encoding: 7Bit
Content-Type: text/x-patch; charset="unicode-2-0-utf-8";
name="v2a-0003-Add-alias-to-be-used-as-unset-state.patch"
^ permalink raw reply [nested|flat] 868+ messages in thread
* [PATCH v2a 2/4] Introduce ternary reloptions
@ 2025-09-04 14:41 Nikolay Shaplov <[email protected]>
0 siblings, 0 replies; 868+ messages in thread
From: Nikolay Shaplov @ 2025-09-04 14:41 UTC (permalink / raw)
Introduce ternary reloption as a replacement for current `vacuum_truncate`
implementation. Remove `vacuum_truncate_set` additional flag and using
`TERNARY_UNSET` value instead.
---
src/backend/access/common/reloptions.c | 129 ++++++++++++++++++++-----
src/backend/commands/vacuum.c | 4 +-
src/include/access/reloptions.h | 26 ++---
src/include/c.h | 16 +++
src/include/utils/rel.h | 3 +-
5 files changed, 135 insertions(+), 43 deletions(-)
diff --git a/src/backend/access/common/reloptions.c b/src/backend/access/common/reloptions.c
index 0af3fea68f..f543f11001 100644
--- a/src/backend/access/common/reloptions.c
+++ b/src/backend/access/common/reloptions.c
@@ -40,9 +40,9 @@
*
* To add an option:
*
- * (i) decide on a type (bool, integer, real, enum, string), name, default
- * value, upper and lower bounds (if applicable); for strings, consider a
- * validation routine.
+ * (i) decide on a type (bool, ternary, integer, real, enum, string), name,
+ * default value, upper and lower bounds (if applicable); for strings,
+ * consider a validation routine.
* (ii) add a record below (or use add_<type>_reloption).
* (iii) add it to the appropriate options struct (perhaps StdRdOptions)
* (iv) add it to the appropriate handling routine (perhaps
@@ -147,15 +147,6 @@ static relopt_bool boolRelOpts[] =
},
false
},
- {
- {
- "vacuum_truncate",
- "Enables vacuum to truncate empty pages at the end of this table",
- RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
- ShareUpdateExclusiveLock
- },
- true
- },
{
{
"deduplicate_items",
@@ -170,6 +161,21 @@ static relopt_bool boolRelOpts[] =
{{NULL}}
};
+static relopt_ternary ternaryRelOpts[] =
+{
+ {
+ {
+ "vacuum_truncate",
+ "Enables vacuum to truncate empty pages at the end of this table",
+ RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
+ ShareUpdateExclusiveLock
+ },
+ TERNARY_UNSET
+ },
+ /* list terminator */
+ {{NULL}}
+};
+
static relopt_int intRelOpts[] =
{
{
@@ -600,6 +606,13 @@ initialize_reloptions(void)
boolRelOpts[i].gen.lockmode));
j++;
}
+ for (i = 0; ternaryRelOpts[i].gen.name; i++)
+ {
+ Assert(DoLockModesConflict(ternaryRelOpts[i].gen.lockmode,
+ ternaryRelOpts[i].gen.lockmode));
+ j++;
+ }
+
for (i = 0; intRelOpts[i].gen.name; i++)
{
Assert(DoLockModesConflict(intRelOpts[i].gen.lockmode,
@@ -640,6 +653,14 @@ initialize_reloptions(void)
j++;
}
+ for (i = 0; ternaryRelOpts[i].gen.name; i++)
+ {
+ relOpts[j] = &ternaryRelOpts[i].gen;
+ relOpts[j]->type = RELOPT_TYPE_TERNARY;
+ relOpts[j]->namelen = strlen(relOpts[j]->name);
+ j++;
+ }
+
for (i = 0; intRelOpts[i].gen.name; i++)
{
relOpts[j] = &intRelOpts[i].gen;
@@ -800,6 +821,9 @@ allocate_reloption(bits32 kinds, int type, const char *name, const char *desc,
case RELOPT_TYPE_BOOL:
size = sizeof(relopt_bool);
break;
+ case RELOPT_TYPE_TERNARY:
+ size = sizeof(relopt_ternary);
+ break;
case RELOPT_TYPE_INT:
size = sizeof(relopt_int);
break;
@@ -883,6 +907,54 @@ add_local_bool_reloption(local_relopts *relopts, const char *name,
add_local_reloption(relopts, (relopt_gen *) newoption, offset);
}
+/*
+ * init_ternary_reloption
+ * Allocate and initialize a new ternary reloption
+ */
+static relopt_ternary *
+init_ternary_reloption(bits32 kinds, const char *name, const char *desc,
+ ternary default_val, LOCKMODE lockmode)
+{
+ relopt_ternary *newoption;
+
+ newoption = (relopt_ternary *) allocate_reloption(kinds,
+ RELOPT_TYPE_TERNARY, name, desc, lockmode);
+ newoption->default_val = default_val;
+
+ return newoption;
+}
+
+/*
+ * add_ternary_reloption
+ * Add a new ternary reloption
+ */
+void
+add_ternary_reloption(bits32 kinds, const char *name, const char *desc,
+ ternary default_val, LOCKMODE lockmode)
+{
+ relopt_ternary *newoption = init_ternary_reloption(kinds, name, desc,
+ default_val, lockmode);
+
+ add_reloption((relopt_gen *) newoption);
+}
+
+/*
+ * add_local_ternary_reloption
+ * Add a new ternary local reloption
+ *
+ * 'offset' is offset of ternary-typed field.
+ */
+void
+add_local_ternary_reloption(local_relopts *relopts, const char *name,
+ const char *desc, ternary default_val,
+ int offset)
+{
+ relopt_ternary *newoption = init_ternary_reloption(RELOPT_KIND_LOCAL,
+ name, desc,
+ default_val, 0);
+
+ add_local_reloption(relopts, (relopt_gen *) newoption, offset);
+}
/*
* init_real_reloption
@@ -1617,6 +1689,18 @@ parse_one_reloption(relopt_value *option, char *text_str, int text_len,
option->gen->name, value)));
}
break;
+ case RELOPT_TYPE_TERNARY:
+ {
+ bool b;
+ parsed = parse_bool(value, &b);
+ option->values.ternary_val = b ? TERNARY_TRUE : TERNARY_FALSE;
+ if (validate && !parsed)
+ ereport(ERROR,
+ (errcode(ERRCODE_INVALID_PARAMETER_VALUE),
+ errmsg("invalid value for ternary option \"%s\": %s",
+ option->gen->name, value)));
+ }
+ break;
case RELOPT_TYPE_INT:
{
relopt_int *optint = (relopt_int *) option->gen;
@@ -1780,17 +1864,6 @@ fillRelOptions(void *rdopts, Size basesize,
char *itempos = ((char *) rdopts) + elems[j].offset;
char *string_val;
- /*
- * If isset_offset is provided, store whether the reloption is
- * set there.
- */
- if (elems[j].isset_offset > 0)
- {
- char *setpos = ((char *) rdopts) + elems[j].isset_offset;
-
- *(bool *) setpos = options[i].isset;
- }
-
switch (options[i].gen->type)
{
case RELOPT_TYPE_BOOL:
@@ -1798,6 +1871,11 @@ fillRelOptions(void *rdopts, Size basesize,
options[i].values.bool_val :
((relopt_bool *) options[i].gen)->default_val;
break;
+ case RELOPT_TYPE_TERNARY:
+ *(ternary *) itempos = options[i].isset ?
+ options[i].values.ternary_val :
+ ((relopt_ternary *) options[i].gen)->default_val;
+ break;
case RELOPT_TYPE_INT:
*(int *) itempos = options[i].isset ?
options[i].values.int_val :
@@ -1912,8 +1990,8 @@ default_reloptions(Datum reloptions, bool validate, relopt_kind kind)
offsetof(StdRdOptions, parallel_workers)},
{"vacuum_index_cleanup", RELOPT_TYPE_ENUM,
offsetof(StdRdOptions, vacuum_index_cleanup)},
- {"vacuum_truncate", RELOPT_TYPE_BOOL,
- offsetof(StdRdOptions, vacuum_truncate), offsetof(StdRdOptions, vacuum_truncate_set)},
+ {"vacuum_truncate", RELOPT_TYPE_TERNARY,
+ offsetof(StdRdOptions, vacuum_truncate)},
{"vacuum_max_eager_freeze_failure_rate", RELOPT_TYPE_REAL,
offsetof(StdRdOptions, vacuum_max_eager_freeze_failure_rate)}
};
@@ -1993,7 +2071,6 @@ build_local_reloptions(local_relopts *relopts, Datum options, bool validate)
elems[i].optname = opt->option->name;
elems[i].opttype = opt->option->type;
elems[i].offset = opt->offset;
- elems[i].isset_offset = 0; /* not supported for local relopts yet */
i++;
}
diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c
index 733ef40ae7..977babff54 100644
--- a/src/backend/commands/vacuum.c
+++ b/src/backend/commands/vacuum.c
@@ -2221,9 +2221,9 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams params,
{
StdRdOptions *opts = (StdRdOptions *) rel->rd_options;
- if (opts && opts->vacuum_truncate_set)
+ if (opts && opts->vacuum_truncate != TERNARY_UNSET)
{
- if (opts->vacuum_truncate)
+ if (opts->vacuum_truncate == TERNARY_TRUE)
params.truncate = VACOPTVALUE_ENABLED;
else
params.truncate = VACOPTVALUE_DISABLED;
diff --git a/src/include/access/reloptions.h b/src/include/access/reloptions.h
index a604a4702c..a436697658 100644
--- a/src/include/access/reloptions.h
+++ b/src/include/access/reloptions.h
@@ -29,6 +29,7 @@
typedef enum relopt_type
{
RELOPT_TYPE_BOOL,
+ RELOPT_TYPE_TERNARY, /* on, off, unset */
RELOPT_TYPE_INT,
RELOPT_TYPE_REAL,
RELOPT_TYPE_ENUM,
@@ -80,6 +81,7 @@ typedef struct relopt_value
union
{
bool bool_val;
+ ternary ternary_val;
int int_val;
double real_val;
int enum_val;
@@ -94,6 +96,12 @@ typedef struct relopt_bool
bool default_val;
} relopt_bool;
+typedef struct relopt_rernary
+{
+ relopt_gen gen;
+ int default_val;
+} relopt_ternary;
+
typedef struct relopt_int
{
relopt_gen gen;
@@ -152,19 +160,6 @@ typedef struct
const char *optname; /* option's name */
relopt_type opttype; /* option's datatype */
int offset; /* offset of field in result struct */
-
- /*
- * isset_offset is an optional offset of a field in the result struct that
- * stores whether the option is explicitly set for the relation or if it
- * just picked up the default value. In most cases, this can be
- * accomplished by giving the reloption a special out-of-range default
- * value (e.g., some integer reloptions use -2), but this isn't always
- * possible. For example, a Boolean reloption cannot be given an
- * out-of-range default, so we need another way to discover the source of
- * its value. This offset is only used if given a value greater than
- * zero.
- */
- int isset_offset;
} relopt_parse_elt;
/* Local reloption definition */
@@ -195,6 +190,8 @@ typedef struct local_relopts
extern relopt_kind add_reloption_kind(void);
extern void add_bool_reloption(bits32 kinds, const char *name, const char *desc,
bool default_val, LOCKMODE lockmode);
+extern void add_ternary_reloption(bits32 kinds, const char *name,
+ const char *desc, int default_val, LOCKMODE lockmode);
extern void add_int_reloption(bits32 kinds, const char *name, const char *desc,
int default_val, int min_val, int max_val,
LOCKMODE lockmode);
@@ -214,6 +211,9 @@ extern void register_reloptions_validator(local_relopts *relopts,
extern void add_local_bool_reloption(local_relopts *relopts, const char *name,
const char *desc, bool default_val,
int offset);
+extern void add_local_ternary_reloption(local_relopts *relopts,
+ const char *name, const char *desc,
+ ternary default_val, int offset);
extern void add_local_int_reloption(local_relopts *relopts, const char *name,
const char *desc, int default_val,
int min_val, int max_val, int offset);
diff --git a/src/include/c.h b/src/include/c.h
index 39022f8a9d..9c59ccccf8 100644
--- a/src/include/c.h
+++ b/src/include/c.h
@@ -488,6 +488,22 @@ typedef void (*pg_funcptr_t) (void);
#include <stdbool.h>
+/*
+ * ternary
+ * Boolean value with an extrea "unset" option
+ *
+ * Ternary data type is used in relation options that can be "true", "false" or
+ * "unset". Since relation options are used deep inside the PostgreSQL code,
+ * this type is declared globally.
+*/
+
+typedef enum ternary
+{
+ TERNARY_FALSE = 0,
+ TERNARY_TRUE = 1,
+ TERNARY_UNSET = -1
+} ternary;
+
/* ----------------------------------------------------------------
* Section 3: standard system types
diff --git a/src/include/utils/rel.h b/src/include/utils/rel.h
index b552359915..7346d618f9 100644
--- a/src/include/utils/rel.h
+++ b/src/include/utils/rel.h
@@ -346,8 +346,7 @@ typedef struct StdRdOptions
bool user_catalog_table; /* use as an additional catalog relation */
int parallel_workers; /* max number of parallel workers */
StdRdOptIndexCleanup vacuum_index_cleanup; /* controls index vacuuming */
- bool vacuum_truncate; /* enables vacuum to truncate a relation */
- bool vacuum_truncate_set; /* whether vacuum_truncate is set */
+ ternary vacuum_truncate; /* enables vacuum to truncate a relation */
/*
* Fraction of pages in a relation that vacuum can eagerly scan and fail
--
2.39.2
--nextPart4155600.3ZeAukHxDK
Content-Disposition: attachment;
filename="v2a-0003-Add-alias-to-be-used-as-unset-state.patch"
Content-Transfer-Encoding: 7Bit
Content-Type: text/x-patch; charset="unicode-2-0-utf-8";
name="v2a-0003-Add-alias-to-be-used-as-unset-state.patch"
^ permalink raw reply [nested|flat] 868+ messages in thread
* [PATCH v2 2/4] Introduce ternary reloptions
@ 2025-09-04 14:41 Nikolay Shaplov <[email protected]>
0 siblings, 0 replies; 868+ messages in thread
From: Nikolay Shaplov @ 2025-09-04 14:41 UTC (permalink / raw)
Introduce ternary reloption as a replacement for current `vacuum_truncate`
implementation. Remove `vacuum_truncate_set` additional flag and using
`TERNARY_UNSET` value instead.
---
src/backend/access/common/reloptions.c | 129 ++++++++++++++++++++-----
src/backend/commands/vacuum.c | 4 +-
src/include/access/reloptions.h | 26 ++---
src/include/c.h | 16 +++
src/include/utils/rel.h | 3 +-
5 files changed, 135 insertions(+), 43 deletions(-)
diff --git a/src/backend/access/common/reloptions.c b/src/backend/access/common/reloptions.c
index 0af3fea68f..f543f11001 100644
--- a/src/backend/access/common/reloptions.c
+++ b/src/backend/access/common/reloptions.c
@@ -40,9 +40,9 @@
*
* To add an option:
*
- * (i) decide on a type (bool, integer, real, enum, string), name, default
- * value, upper and lower bounds (if applicable); for strings, consider a
- * validation routine.
+ * (i) decide on a type (bool, ternary, integer, real, enum, string), name,
+ * default value, upper and lower bounds (if applicable); for strings,
+ * consider a validation routine.
* (ii) add a record below (or use add_<type>_reloption).
* (iii) add it to the appropriate options struct (perhaps StdRdOptions)
* (iv) add it to the appropriate handling routine (perhaps
@@ -147,15 +147,6 @@ static relopt_bool boolRelOpts[] =
},
false
},
- {
- {
- "vacuum_truncate",
- "Enables vacuum to truncate empty pages at the end of this table",
- RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
- ShareUpdateExclusiveLock
- },
- true
- },
{
{
"deduplicate_items",
@@ -170,6 +161,21 @@ static relopt_bool boolRelOpts[] =
{{NULL}}
};
+static relopt_ternary ternaryRelOpts[] =
+{
+ {
+ {
+ "vacuum_truncate",
+ "Enables vacuum to truncate empty pages at the end of this table",
+ RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
+ ShareUpdateExclusiveLock
+ },
+ TERNARY_UNSET
+ },
+ /* list terminator */
+ {{NULL}}
+};
+
static relopt_int intRelOpts[] =
{
{
@@ -600,6 +606,13 @@ initialize_reloptions(void)
boolRelOpts[i].gen.lockmode));
j++;
}
+ for (i = 0; ternaryRelOpts[i].gen.name; i++)
+ {
+ Assert(DoLockModesConflict(ternaryRelOpts[i].gen.lockmode,
+ ternaryRelOpts[i].gen.lockmode));
+ j++;
+ }
+
for (i = 0; intRelOpts[i].gen.name; i++)
{
Assert(DoLockModesConflict(intRelOpts[i].gen.lockmode,
@@ -640,6 +653,14 @@ initialize_reloptions(void)
j++;
}
+ for (i = 0; ternaryRelOpts[i].gen.name; i++)
+ {
+ relOpts[j] = &ternaryRelOpts[i].gen;
+ relOpts[j]->type = RELOPT_TYPE_TERNARY;
+ relOpts[j]->namelen = strlen(relOpts[j]->name);
+ j++;
+ }
+
for (i = 0; intRelOpts[i].gen.name; i++)
{
relOpts[j] = &intRelOpts[i].gen;
@@ -800,6 +821,9 @@ allocate_reloption(bits32 kinds, int type, const char *name, const char *desc,
case RELOPT_TYPE_BOOL:
size = sizeof(relopt_bool);
break;
+ case RELOPT_TYPE_TERNARY:
+ size = sizeof(relopt_ternary);
+ break;
case RELOPT_TYPE_INT:
size = sizeof(relopt_int);
break;
@@ -883,6 +907,54 @@ add_local_bool_reloption(local_relopts *relopts, const char *name,
add_local_reloption(relopts, (relopt_gen *) newoption, offset);
}
+/*
+ * init_ternary_reloption
+ * Allocate and initialize a new ternary reloption
+ */
+static relopt_ternary *
+init_ternary_reloption(bits32 kinds, const char *name, const char *desc,
+ ternary default_val, LOCKMODE lockmode)
+{
+ relopt_ternary *newoption;
+
+ newoption = (relopt_ternary *) allocate_reloption(kinds,
+ RELOPT_TYPE_TERNARY, name, desc, lockmode);
+ newoption->default_val = default_val;
+
+ return newoption;
+}
+
+/*
+ * add_ternary_reloption
+ * Add a new ternary reloption
+ */
+void
+add_ternary_reloption(bits32 kinds, const char *name, const char *desc,
+ ternary default_val, LOCKMODE lockmode)
+{
+ relopt_ternary *newoption = init_ternary_reloption(kinds, name, desc,
+ default_val, lockmode);
+
+ add_reloption((relopt_gen *) newoption);
+}
+
+/*
+ * add_local_ternary_reloption
+ * Add a new ternary local reloption
+ *
+ * 'offset' is offset of ternary-typed field.
+ */
+void
+add_local_ternary_reloption(local_relopts *relopts, const char *name,
+ const char *desc, ternary default_val,
+ int offset)
+{
+ relopt_ternary *newoption = init_ternary_reloption(RELOPT_KIND_LOCAL,
+ name, desc,
+ default_val, 0);
+
+ add_local_reloption(relopts, (relopt_gen *) newoption, offset);
+}
/*
* init_real_reloption
@@ -1617,6 +1689,18 @@ parse_one_reloption(relopt_value *option, char *text_str, int text_len,
option->gen->name, value)));
}
break;
+ case RELOPT_TYPE_TERNARY:
+ {
+ bool b;
+ parsed = parse_bool(value, &b);
+ option->values.ternary_val = b ? TERNARY_TRUE : TERNARY_FALSE;
+ if (validate && !parsed)
+ ereport(ERROR,
+ (errcode(ERRCODE_INVALID_PARAMETER_VALUE),
+ errmsg("invalid value for ternary option \"%s\": %s",
+ option->gen->name, value)));
+ }
+ break;
case RELOPT_TYPE_INT:
{
relopt_int *optint = (relopt_int *) option->gen;
@@ -1780,17 +1864,6 @@ fillRelOptions(void *rdopts, Size basesize,
char *itempos = ((char *) rdopts) + elems[j].offset;
char *string_val;
- /*
- * If isset_offset is provided, store whether the reloption is
- * set there.
- */
- if (elems[j].isset_offset > 0)
- {
- char *setpos = ((char *) rdopts) + elems[j].isset_offset;
-
- *(bool *) setpos = options[i].isset;
- }
-
switch (options[i].gen->type)
{
case RELOPT_TYPE_BOOL:
@@ -1798,6 +1871,11 @@ fillRelOptions(void *rdopts, Size basesize,
options[i].values.bool_val :
((relopt_bool *) options[i].gen)->default_val;
break;
+ case RELOPT_TYPE_TERNARY:
+ *(ternary *) itempos = options[i].isset ?
+ options[i].values.ternary_val :
+ ((relopt_ternary *) options[i].gen)->default_val;
+ break;
case RELOPT_TYPE_INT:
*(int *) itempos = options[i].isset ?
options[i].values.int_val :
@@ -1912,8 +1990,8 @@ default_reloptions(Datum reloptions, bool validate, relopt_kind kind)
offsetof(StdRdOptions, parallel_workers)},
{"vacuum_index_cleanup", RELOPT_TYPE_ENUM,
offsetof(StdRdOptions, vacuum_index_cleanup)},
- {"vacuum_truncate", RELOPT_TYPE_BOOL,
- offsetof(StdRdOptions, vacuum_truncate), offsetof(StdRdOptions, vacuum_truncate_set)},
+ {"vacuum_truncate", RELOPT_TYPE_TERNARY,
+ offsetof(StdRdOptions, vacuum_truncate)},
{"vacuum_max_eager_freeze_failure_rate", RELOPT_TYPE_REAL,
offsetof(StdRdOptions, vacuum_max_eager_freeze_failure_rate)}
};
@@ -1993,7 +2071,6 @@ build_local_reloptions(local_relopts *relopts, Datum options, bool validate)
elems[i].optname = opt->option->name;
elems[i].opttype = opt->option->type;
elems[i].offset = opt->offset;
- elems[i].isset_offset = 0; /* not supported for local relopts yet */
i++;
}
diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c
index 733ef40ae7..977babff54 100644
--- a/src/backend/commands/vacuum.c
+++ b/src/backend/commands/vacuum.c
@@ -2221,9 +2221,9 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams params,
{
StdRdOptions *opts = (StdRdOptions *) rel->rd_options;
- if (opts && opts->vacuum_truncate_set)
+ if (opts && opts->vacuum_truncate != TERNARY_UNSET)
{
- if (opts->vacuum_truncate)
+ if (opts->vacuum_truncate == TERNARY_TRUE)
params.truncate = VACOPTVALUE_ENABLED;
else
params.truncate = VACOPTVALUE_DISABLED;
diff --git a/src/include/access/reloptions.h b/src/include/access/reloptions.h
index a604a4702c..a436697658 100644
--- a/src/include/access/reloptions.h
+++ b/src/include/access/reloptions.h
@@ -29,6 +29,7 @@
typedef enum relopt_type
{
RELOPT_TYPE_BOOL,
+ RELOPT_TYPE_TERNARY, /* on, off, unset */
RELOPT_TYPE_INT,
RELOPT_TYPE_REAL,
RELOPT_TYPE_ENUM,
@@ -80,6 +81,7 @@ typedef struct relopt_value
union
{
bool bool_val;
+ ternary ternary_val;
int int_val;
double real_val;
int enum_val;
@@ -94,6 +96,12 @@ typedef struct relopt_bool
bool default_val;
} relopt_bool;
+typedef struct relopt_rernary
+{
+ relopt_gen gen;
+ int default_val;
+} relopt_ternary;
+
typedef struct relopt_int
{
relopt_gen gen;
@@ -152,19 +160,6 @@ typedef struct
const char *optname; /* option's name */
relopt_type opttype; /* option's datatype */
int offset; /* offset of field in result struct */
-
- /*
- * isset_offset is an optional offset of a field in the result struct that
- * stores whether the option is explicitly set for the relation or if it
- * just picked up the default value. In most cases, this can be
- * accomplished by giving the reloption a special out-of-range default
- * value (e.g., some integer reloptions use -2), but this isn't always
- * possible. For example, a Boolean reloption cannot be given an
- * out-of-range default, so we need another way to discover the source of
- * its value. This offset is only used if given a value greater than
- * zero.
- */
- int isset_offset;
} relopt_parse_elt;
/* Local reloption definition */
@@ -195,6 +190,8 @@ typedef struct local_relopts
extern relopt_kind add_reloption_kind(void);
extern void add_bool_reloption(bits32 kinds, const char *name, const char *desc,
bool default_val, LOCKMODE lockmode);
+extern void add_ternary_reloption(bits32 kinds, const char *name,
+ const char *desc, int default_val, LOCKMODE lockmode);
extern void add_int_reloption(bits32 kinds, const char *name, const char *desc,
int default_val, int min_val, int max_val,
LOCKMODE lockmode);
@@ -214,6 +211,9 @@ extern void register_reloptions_validator(local_relopts *relopts,
extern void add_local_bool_reloption(local_relopts *relopts, const char *name,
const char *desc, bool default_val,
int offset);
+extern void add_local_ternary_reloption(local_relopts *relopts,
+ const char *name, const char *desc,
+ ternary default_val, int offset);
extern void add_local_int_reloption(local_relopts *relopts, const char *name,
const char *desc, int default_val,
int min_val, int max_val, int offset);
diff --git a/src/include/c.h b/src/include/c.h
index 39022f8a9d..9c59ccccf8 100644
--- a/src/include/c.h
+++ b/src/include/c.h
@@ -488,6 +488,22 @@ typedef void (*pg_funcptr_t) (void);
#include <stdbool.h>
+/*
+ * ternary
+ * Boolean value with an extrea "unset" option
+ *
+ * Ternary data type is used in relation options that can be "true", "false" or
+ * "unset". Since relation options are used deep inside the PostgreSQL code,
+ * this type is declared globally.
+*/
+
+typedef enum ternary
+{
+ TERNARY_FALSE = 0,
+ TERNARY_TRUE = 1,
+ TERNARY_UNSET = -1
+} ternary;
+
/* ----------------------------------------------------------------
* Section 3: standard system types
diff --git a/src/include/utils/rel.h b/src/include/utils/rel.h
index b552359915..7346d618f9 100644
--- a/src/include/utils/rel.h
+++ b/src/include/utils/rel.h
@@ -346,8 +346,7 @@ typedef struct StdRdOptions
bool user_catalog_table; /* use as an additional catalog relation */
int parallel_workers; /* max number of parallel workers */
StdRdOptIndexCleanup vacuum_index_cleanup; /* controls index vacuuming */
- bool vacuum_truncate; /* enables vacuum to truncate a relation */
- bool vacuum_truncate_set; /* whether vacuum_truncate is set */
+ ternary vacuum_truncate; /* enables vacuum to truncate a relation */
/*
* Fraction of pages in a relation that vacuum can eagerly scan and fail
--
2.39.2
--nextPart5129932.5fSG56mABF
Content-Disposition: attachment;
filename="v2-0003-Add-alias-to-be-used-as-unset-state.patch"
Content-Transfer-Encoding: 7Bit
Content-Type: text/x-patch; charset="unicode-2-0-utf-8";
name="v2-0003-Add-alias-to-be-used-as-unset-state.patch"
^ permalink raw reply [nested|flat] 868+ messages in thread
* [PATCH v2 2/4] Introduce ternary reloptions
@ 2025-09-04 14:41 Nikolay Shaplov <[email protected]>
0 siblings, 0 replies; 868+ messages in thread
From: Nikolay Shaplov @ 2025-09-04 14:41 UTC (permalink / raw)
Introduce ternary reloption as a replacement for current `vacuum_truncate`
implementation. Remove `vacuum_truncate_set` additional flag and using
`TERNARY_UNSET` value instead.
---
src/backend/access/common/reloptions.c | 129 ++++++++++++++++++++-----
src/backend/commands/vacuum.c | 4 +-
src/include/access/reloptions.h | 26 ++---
src/include/c.h | 16 +++
src/include/utils/rel.h | 3 +-
5 files changed, 135 insertions(+), 43 deletions(-)
diff --git a/src/backend/access/common/reloptions.c b/src/backend/access/common/reloptions.c
index 0af3fea68f..f543f11001 100644
--- a/src/backend/access/common/reloptions.c
+++ b/src/backend/access/common/reloptions.c
@@ -40,9 +40,9 @@
*
* To add an option:
*
- * (i) decide on a type (bool, integer, real, enum, string), name, default
- * value, upper and lower bounds (if applicable); for strings, consider a
- * validation routine.
+ * (i) decide on a type (bool, ternary, integer, real, enum, string), name,
+ * default value, upper and lower bounds (if applicable); for strings,
+ * consider a validation routine.
* (ii) add a record below (or use add_<type>_reloption).
* (iii) add it to the appropriate options struct (perhaps StdRdOptions)
* (iv) add it to the appropriate handling routine (perhaps
@@ -147,15 +147,6 @@ static relopt_bool boolRelOpts[] =
},
false
},
- {
- {
- "vacuum_truncate",
- "Enables vacuum to truncate empty pages at the end of this table",
- RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
- ShareUpdateExclusiveLock
- },
- true
- },
{
{
"deduplicate_items",
@@ -170,6 +161,21 @@ static relopt_bool boolRelOpts[] =
{{NULL}}
};
+static relopt_ternary ternaryRelOpts[] =
+{
+ {
+ {
+ "vacuum_truncate",
+ "Enables vacuum to truncate empty pages at the end of this table",
+ RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
+ ShareUpdateExclusiveLock
+ },
+ TERNARY_UNSET
+ },
+ /* list terminator */
+ {{NULL}}
+};
+
static relopt_int intRelOpts[] =
{
{
@@ -600,6 +606,13 @@ initialize_reloptions(void)
boolRelOpts[i].gen.lockmode));
j++;
}
+ for (i = 0; ternaryRelOpts[i].gen.name; i++)
+ {
+ Assert(DoLockModesConflict(ternaryRelOpts[i].gen.lockmode,
+ ternaryRelOpts[i].gen.lockmode));
+ j++;
+ }
+
for (i = 0; intRelOpts[i].gen.name; i++)
{
Assert(DoLockModesConflict(intRelOpts[i].gen.lockmode,
@@ -640,6 +653,14 @@ initialize_reloptions(void)
j++;
}
+ for (i = 0; ternaryRelOpts[i].gen.name; i++)
+ {
+ relOpts[j] = &ternaryRelOpts[i].gen;
+ relOpts[j]->type = RELOPT_TYPE_TERNARY;
+ relOpts[j]->namelen = strlen(relOpts[j]->name);
+ j++;
+ }
+
for (i = 0; intRelOpts[i].gen.name; i++)
{
relOpts[j] = &intRelOpts[i].gen;
@@ -800,6 +821,9 @@ allocate_reloption(bits32 kinds, int type, const char *name, const char *desc,
case RELOPT_TYPE_BOOL:
size = sizeof(relopt_bool);
break;
+ case RELOPT_TYPE_TERNARY:
+ size = sizeof(relopt_ternary);
+ break;
case RELOPT_TYPE_INT:
size = sizeof(relopt_int);
break;
@@ -883,6 +907,54 @@ add_local_bool_reloption(local_relopts *relopts, const char *name,
add_local_reloption(relopts, (relopt_gen *) newoption, offset);
}
+/*
+ * init_ternary_reloption
+ * Allocate and initialize a new ternary reloption
+ */
+static relopt_ternary *
+init_ternary_reloption(bits32 kinds, const char *name, const char *desc,
+ ternary default_val, LOCKMODE lockmode)
+{
+ relopt_ternary *newoption;
+
+ newoption = (relopt_ternary *) allocate_reloption(kinds,
+ RELOPT_TYPE_TERNARY, name, desc, lockmode);
+ newoption->default_val = default_val;
+
+ return newoption;
+}
+
+/*
+ * add_ternary_reloption
+ * Add a new ternary reloption
+ */
+void
+add_ternary_reloption(bits32 kinds, const char *name, const char *desc,
+ ternary default_val, LOCKMODE lockmode)
+{
+ relopt_ternary *newoption = init_ternary_reloption(kinds, name, desc,
+ default_val, lockmode);
+
+ add_reloption((relopt_gen *) newoption);
+}
+
+/*
+ * add_local_ternary_reloption
+ * Add a new ternary local reloption
+ *
+ * 'offset' is offset of ternary-typed field.
+ */
+void
+add_local_ternary_reloption(local_relopts *relopts, const char *name,
+ const char *desc, ternary default_val,
+ int offset)
+{
+ relopt_ternary *newoption = init_ternary_reloption(RELOPT_KIND_LOCAL,
+ name, desc,
+ default_val, 0);
+
+ add_local_reloption(relopts, (relopt_gen *) newoption, offset);
+}
/*
* init_real_reloption
@@ -1617,6 +1689,18 @@ parse_one_reloption(relopt_value *option, char *text_str, int text_len,
option->gen->name, value)));
}
break;
+ case RELOPT_TYPE_TERNARY:
+ {
+ bool b;
+ parsed = parse_bool(value, &b);
+ option->values.ternary_val = b ? TERNARY_TRUE : TERNARY_FALSE;
+ if (validate && !parsed)
+ ereport(ERROR,
+ (errcode(ERRCODE_INVALID_PARAMETER_VALUE),
+ errmsg("invalid value for ternary option \"%s\": %s",
+ option->gen->name, value)));
+ }
+ break;
case RELOPT_TYPE_INT:
{
relopt_int *optint = (relopt_int *) option->gen;
@@ -1780,17 +1864,6 @@ fillRelOptions(void *rdopts, Size basesize,
char *itempos = ((char *) rdopts) + elems[j].offset;
char *string_val;
- /*
- * If isset_offset is provided, store whether the reloption is
- * set there.
- */
- if (elems[j].isset_offset > 0)
- {
- char *setpos = ((char *) rdopts) + elems[j].isset_offset;
-
- *(bool *) setpos = options[i].isset;
- }
-
switch (options[i].gen->type)
{
case RELOPT_TYPE_BOOL:
@@ -1798,6 +1871,11 @@ fillRelOptions(void *rdopts, Size basesize,
options[i].values.bool_val :
((relopt_bool *) options[i].gen)->default_val;
break;
+ case RELOPT_TYPE_TERNARY:
+ *(ternary *) itempos = options[i].isset ?
+ options[i].values.ternary_val :
+ ((relopt_ternary *) options[i].gen)->default_val;
+ break;
case RELOPT_TYPE_INT:
*(int *) itempos = options[i].isset ?
options[i].values.int_val :
@@ -1912,8 +1990,8 @@ default_reloptions(Datum reloptions, bool validate, relopt_kind kind)
offsetof(StdRdOptions, parallel_workers)},
{"vacuum_index_cleanup", RELOPT_TYPE_ENUM,
offsetof(StdRdOptions, vacuum_index_cleanup)},
- {"vacuum_truncate", RELOPT_TYPE_BOOL,
- offsetof(StdRdOptions, vacuum_truncate), offsetof(StdRdOptions, vacuum_truncate_set)},
+ {"vacuum_truncate", RELOPT_TYPE_TERNARY,
+ offsetof(StdRdOptions, vacuum_truncate)},
{"vacuum_max_eager_freeze_failure_rate", RELOPT_TYPE_REAL,
offsetof(StdRdOptions, vacuum_max_eager_freeze_failure_rate)}
};
@@ -1993,7 +2071,6 @@ build_local_reloptions(local_relopts *relopts, Datum options, bool validate)
elems[i].optname = opt->option->name;
elems[i].opttype = opt->option->type;
elems[i].offset = opt->offset;
- elems[i].isset_offset = 0; /* not supported for local relopts yet */
i++;
}
diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c
index 733ef40ae7..977babff54 100644
--- a/src/backend/commands/vacuum.c
+++ b/src/backend/commands/vacuum.c
@@ -2221,9 +2221,9 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams params,
{
StdRdOptions *opts = (StdRdOptions *) rel->rd_options;
- if (opts && opts->vacuum_truncate_set)
+ if (opts && opts->vacuum_truncate != TERNARY_UNSET)
{
- if (opts->vacuum_truncate)
+ if (opts->vacuum_truncate == TERNARY_TRUE)
params.truncate = VACOPTVALUE_ENABLED;
else
params.truncate = VACOPTVALUE_DISABLED;
diff --git a/src/include/access/reloptions.h b/src/include/access/reloptions.h
index a604a4702c..a436697658 100644
--- a/src/include/access/reloptions.h
+++ b/src/include/access/reloptions.h
@@ -29,6 +29,7 @@
typedef enum relopt_type
{
RELOPT_TYPE_BOOL,
+ RELOPT_TYPE_TERNARY, /* on, off, unset */
RELOPT_TYPE_INT,
RELOPT_TYPE_REAL,
RELOPT_TYPE_ENUM,
@@ -80,6 +81,7 @@ typedef struct relopt_value
union
{
bool bool_val;
+ ternary ternary_val;
int int_val;
double real_val;
int enum_val;
@@ -94,6 +96,12 @@ typedef struct relopt_bool
bool default_val;
} relopt_bool;
+typedef struct relopt_rernary
+{
+ relopt_gen gen;
+ int default_val;
+} relopt_ternary;
+
typedef struct relopt_int
{
relopt_gen gen;
@@ -152,19 +160,6 @@ typedef struct
const char *optname; /* option's name */
relopt_type opttype; /* option's datatype */
int offset; /* offset of field in result struct */
-
- /*
- * isset_offset is an optional offset of a field in the result struct that
- * stores whether the option is explicitly set for the relation or if it
- * just picked up the default value. In most cases, this can be
- * accomplished by giving the reloption a special out-of-range default
- * value (e.g., some integer reloptions use -2), but this isn't always
- * possible. For example, a Boolean reloption cannot be given an
- * out-of-range default, so we need another way to discover the source of
- * its value. This offset is only used if given a value greater than
- * zero.
- */
- int isset_offset;
} relopt_parse_elt;
/* Local reloption definition */
@@ -195,6 +190,8 @@ typedef struct local_relopts
extern relopt_kind add_reloption_kind(void);
extern void add_bool_reloption(bits32 kinds, const char *name, const char *desc,
bool default_val, LOCKMODE lockmode);
+extern void add_ternary_reloption(bits32 kinds, const char *name,
+ const char *desc, int default_val, LOCKMODE lockmode);
extern void add_int_reloption(bits32 kinds, const char *name, const char *desc,
int default_val, int min_val, int max_val,
LOCKMODE lockmode);
@@ -214,6 +211,9 @@ extern void register_reloptions_validator(local_relopts *relopts,
extern void add_local_bool_reloption(local_relopts *relopts, const char *name,
const char *desc, bool default_val,
int offset);
+extern void add_local_ternary_reloption(local_relopts *relopts,
+ const char *name, const char *desc,
+ ternary default_val, int offset);
extern void add_local_int_reloption(local_relopts *relopts, const char *name,
const char *desc, int default_val,
int min_val, int max_val, int offset);
diff --git a/src/include/c.h b/src/include/c.h
index 39022f8a9d..9c59ccccf8 100644
--- a/src/include/c.h
+++ b/src/include/c.h
@@ -488,6 +488,22 @@ typedef void (*pg_funcptr_t) (void);
#include <stdbool.h>
+/*
+ * ternary
+ * Boolean value with an extrea "unset" option
+ *
+ * Ternary data type is used in relation options that can be "true", "false" or
+ * "unset". Since relation options are used deep inside the PostgreSQL code,
+ * this type is declared globally.
+*/
+
+typedef enum ternary
+{
+ TERNARY_FALSE = 0,
+ TERNARY_TRUE = 1,
+ TERNARY_UNSET = -1
+} ternary;
+
/* ----------------------------------------------------------------
* Section 3: standard system types
diff --git a/src/include/utils/rel.h b/src/include/utils/rel.h
index b552359915..7346d618f9 100644
--- a/src/include/utils/rel.h
+++ b/src/include/utils/rel.h
@@ -346,8 +346,7 @@ typedef struct StdRdOptions
bool user_catalog_table; /* use as an additional catalog relation */
int parallel_workers; /* max number of parallel workers */
StdRdOptIndexCleanup vacuum_index_cleanup; /* controls index vacuuming */
- bool vacuum_truncate; /* enables vacuum to truncate a relation */
- bool vacuum_truncate_set; /* whether vacuum_truncate is set */
+ ternary vacuum_truncate; /* enables vacuum to truncate a relation */
/*
* Fraction of pages in a relation that vacuum can eagerly scan and fail
--
2.39.2
--nextPart5129932.5fSG56mABF
Content-Disposition: attachment;
filename="v2-0003-Add-alias-to-be-used-as-unset-state.patch"
Content-Transfer-Encoding: 7Bit
Content-Type: text/x-patch; charset="unicode-2-0-utf-8";
name="v2-0003-Add-alias-to-be-used-as-unset-state.patch"
^ permalink raw reply [nested|flat] 868+ messages in thread
* [PATCH v2a 2/4] Introduce ternary reloptions
@ 2025-09-04 14:41 Nikolay Shaplov <[email protected]>
0 siblings, 0 replies; 868+ messages in thread
From: Nikolay Shaplov @ 2025-09-04 14:41 UTC (permalink / raw)
Introduce ternary reloption as a replacement for current `vacuum_truncate`
implementation. Remove `vacuum_truncate_set` additional flag and using
`TERNARY_UNSET` value instead.
---
src/backend/access/common/reloptions.c | 129 ++++++++++++++++++++-----
src/backend/commands/vacuum.c | 4 +-
src/include/access/reloptions.h | 26 ++---
src/include/c.h | 16 +++
src/include/utils/rel.h | 3 +-
5 files changed, 135 insertions(+), 43 deletions(-)
diff --git a/src/backend/access/common/reloptions.c b/src/backend/access/common/reloptions.c
index 0af3fea68f..f543f11001 100644
--- a/src/backend/access/common/reloptions.c
+++ b/src/backend/access/common/reloptions.c
@@ -40,9 +40,9 @@
*
* To add an option:
*
- * (i) decide on a type (bool, integer, real, enum, string), name, default
- * value, upper and lower bounds (if applicable); for strings, consider a
- * validation routine.
+ * (i) decide on a type (bool, ternary, integer, real, enum, string), name,
+ * default value, upper and lower bounds (if applicable); for strings,
+ * consider a validation routine.
* (ii) add a record below (or use add_<type>_reloption).
* (iii) add it to the appropriate options struct (perhaps StdRdOptions)
* (iv) add it to the appropriate handling routine (perhaps
@@ -147,15 +147,6 @@ static relopt_bool boolRelOpts[] =
},
false
},
- {
- {
- "vacuum_truncate",
- "Enables vacuum to truncate empty pages at the end of this table",
- RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
- ShareUpdateExclusiveLock
- },
- true
- },
{
{
"deduplicate_items",
@@ -170,6 +161,21 @@ static relopt_bool boolRelOpts[] =
{{NULL}}
};
+static relopt_ternary ternaryRelOpts[] =
+{
+ {
+ {
+ "vacuum_truncate",
+ "Enables vacuum to truncate empty pages at the end of this table",
+ RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
+ ShareUpdateExclusiveLock
+ },
+ TERNARY_UNSET
+ },
+ /* list terminator */
+ {{NULL}}
+};
+
static relopt_int intRelOpts[] =
{
{
@@ -600,6 +606,13 @@ initialize_reloptions(void)
boolRelOpts[i].gen.lockmode));
j++;
}
+ for (i = 0; ternaryRelOpts[i].gen.name; i++)
+ {
+ Assert(DoLockModesConflict(ternaryRelOpts[i].gen.lockmode,
+ ternaryRelOpts[i].gen.lockmode));
+ j++;
+ }
+
for (i = 0; intRelOpts[i].gen.name; i++)
{
Assert(DoLockModesConflict(intRelOpts[i].gen.lockmode,
@@ -640,6 +653,14 @@ initialize_reloptions(void)
j++;
}
+ for (i = 0; ternaryRelOpts[i].gen.name; i++)
+ {
+ relOpts[j] = &ternaryRelOpts[i].gen;
+ relOpts[j]->type = RELOPT_TYPE_TERNARY;
+ relOpts[j]->namelen = strlen(relOpts[j]->name);
+ j++;
+ }
+
for (i = 0; intRelOpts[i].gen.name; i++)
{
relOpts[j] = &intRelOpts[i].gen;
@@ -800,6 +821,9 @@ allocate_reloption(bits32 kinds, int type, const char *name, const char *desc,
case RELOPT_TYPE_BOOL:
size = sizeof(relopt_bool);
break;
+ case RELOPT_TYPE_TERNARY:
+ size = sizeof(relopt_ternary);
+ break;
case RELOPT_TYPE_INT:
size = sizeof(relopt_int);
break;
@@ -883,6 +907,54 @@ add_local_bool_reloption(local_relopts *relopts, const char *name,
add_local_reloption(relopts, (relopt_gen *) newoption, offset);
}
+/*
+ * init_ternary_reloption
+ * Allocate and initialize a new ternary reloption
+ */
+static relopt_ternary *
+init_ternary_reloption(bits32 kinds, const char *name, const char *desc,
+ ternary default_val, LOCKMODE lockmode)
+{
+ relopt_ternary *newoption;
+
+ newoption = (relopt_ternary *) allocate_reloption(kinds,
+ RELOPT_TYPE_TERNARY, name, desc, lockmode);
+ newoption->default_val = default_val;
+
+ return newoption;
+}
+
+/*
+ * add_ternary_reloption
+ * Add a new ternary reloption
+ */
+void
+add_ternary_reloption(bits32 kinds, const char *name, const char *desc,
+ ternary default_val, LOCKMODE lockmode)
+{
+ relopt_ternary *newoption = init_ternary_reloption(kinds, name, desc,
+ default_val, lockmode);
+
+ add_reloption((relopt_gen *) newoption);
+}
+
+/*
+ * add_local_ternary_reloption
+ * Add a new ternary local reloption
+ *
+ * 'offset' is offset of ternary-typed field.
+ */
+void
+add_local_ternary_reloption(local_relopts *relopts, const char *name,
+ const char *desc, ternary default_val,
+ int offset)
+{
+ relopt_ternary *newoption = init_ternary_reloption(RELOPT_KIND_LOCAL,
+ name, desc,
+ default_val, 0);
+
+ add_local_reloption(relopts, (relopt_gen *) newoption, offset);
+}
/*
* init_real_reloption
@@ -1617,6 +1689,18 @@ parse_one_reloption(relopt_value *option, char *text_str, int text_len,
option->gen->name, value)));
}
break;
+ case RELOPT_TYPE_TERNARY:
+ {
+ bool b;
+ parsed = parse_bool(value, &b);
+ option->values.ternary_val = b ? TERNARY_TRUE : TERNARY_FALSE;
+ if (validate && !parsed)
+ ereport(ERROR,
+ (errcode(ERRCODE_INVALID_PARAMETER_VALUE),
+ errmsg("invalid value for ternary option \"%s\": %s",
+ option->gen->name, value)));
+ }
+ break;
case RELOPT_TYPE_INT:
{
relopt_int *optint = (relopt_int *) option->gen;
@@ -1780,17 +1864,6 @@ fillRelOptions(void *rdopts, Size basesize,
char *itempos = ((char *) rdopts) + elems[j].offset;
char *string_val;
- /*
- * If isset_offset is provided, store whether the reloption is
- * set there.
- */
- if (elems[j].isset_offset > 0)
- {
- char *setpos = ((char *) rdopts) + elems[j].isset_offset;
-
- *(bool *) setpos = options[i].isset;
- }
-
switch (options[i].gen->type)
{
case RELOPT_TYPE_BOOL:
@@ -1798,6 +1871,11 @@ fillRelOptions(void *rdopts, Size basesize,
options[i].values.bool_val :
((relopt_bool *) options[i].gen)->default_val;
break;
+ case RELOPT_TYPE_TERNARY:
+ *(ternary *) itempos = options[i].isset ?
+ options[i].values.ternary_val :
+ ((relopt_ternary *) options[i].gen)->default_val;
+ break;
case RELOPT_TYPE_INT:
*(int *) itempos = options[i].isset ?
options[i].values.int_val :
@@ -1912,8 +1990,8 @@ default_reloptions(Datum reloptions, bool validate, relopt_kind kind)
offsetof(StdRdOptions, parallel_workers)},
{"vacuum_index_cleanup", RELOPT_TYPE_ENUM,
offsetof(StdRdOptions, vacuum_index_cleanup)},
- {"vacuum_truncate", RELOPT_TYPE_BOOL,
- offsetof(StdRdOptions, vacuum_truncate), offsetof(StdRdOptions, vacuum_truncate_set)},
+ {"vacuum_truncate", RELOPT_TYPE_TERNARY,
+ offsetof(StdRdOptions, vacuum_truncate)},
{"vacuum_max_eager_freeze_failure_rate", RELOPT_TYPE_REAL,
offsetof(StdRdOptions, vacuum_max_eager_freeze_failure_rate)}
};
@@ -1993,7 +2071,6 @@ build_local_reloptions(local_relopts *relopts, Datum options, bool validate)
elems[i].optname = opt->option->name;
elems[i].opttype = opt->option->type;
elems[i].offset = opt->offset;
- elems[i].isset_offset = 0; /* not supported for local relopts yet */
i++;
}
diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c
index 733ef40ae7..977babff54 100644
--- a/src/backend/commands/vacuum.c
+++ b/src/backend/commands/vacuum.c
@@ -2221,9 +2221,9 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams params,
{
StdRdOptions *opts = (StdRdOptions *) rel->rd_options;
- if (opts && opts->vacuum_truncate_set)
+ if (opts && opts->vacuum_truncate != TERNARY_UNSET)
{
- if (opts->vacuum_truncate)
+ if (opts->vacuum_truncate == TERNARY_TRUE)
params.truncate = VACOPTVALUE_ENABLED;
else
params.truncate = VACOPTVALUE_DISABLED;
diff --git a/src/include/access/reloptions.h b/src/include/access/reloptions.h
index a604a4702c..a436697658 100644
--- a/src/include/access/reloptions.h
+++ b/src/include/access/reloptions.h
@@ -29,6 +29,7 @@
typedef enum relopt_type
{
RELOPT_TYPE_BOOL,
+ RELOPT_TYPE_TERNARY, /* on, off, unset */
RELOPT_TYPE_INT,
RELOPT_TYPE_REAL,
RELOPT_TYPE_ENUM,
@@ -80,6 +81,7 @@ typedef struct relopt_value
union
{
bool bool_val;
+ ternary ternary_val;
int int_val;
double real_val;
int enum_val;
@@ -94,6 +96,12 @@ typedef struct relopt_bool
bool default_val;
} relopt_bool;
+typedef struct relopt_rernary
+{
+ relopt_gen gen;
+ int default_val;
+} relopt_ternary;
+
typedef struct relopt_int
{
relopt_gen gen;
@@ -152,19 +160,6 @@ typedef struct
const char *optname; /* option's name */
relopt_type opttype; /* option's datatype */
int offset; /* offset of field in result struct */
-
- /*
- * isset_offset is an optional offset of a field in the result struct that
- * stores whether the option is explicitly set for the relation or if it
- * just picked up the default value. In most cases, this can be
- * accomplished by giving the reloption a special out-of-range default
- * value (e.g., some integer reloptions use -2), but this isn't always
- * possible. For example, a Boolean reloption cannot be given an
- * out-of-range default, so we need another way to discover the source of
- * its value. This offset is only used if given a value greater than
- * zero.
- */
- int isset_offset;
} relopt_parse_elt;
/* Local reloption definition */
@@ -195,6 +190,8 @@ typedef struct local_relopts
extern relopt_kind add_reloption_kind(void);
extern void add_bool_reloption(bits32 kinds, const char *name, const char *desc,
bool default_val, LOCKMODE lockmode);
+extern void add_ternary_reloption(bits32 kinds, const char *name,
+ const char *desc, int default_val, LOCKMODE lockmode);
extern void add_int_reloption(bits32 kinds, const char *name, const char *desc,
int default_val, int min_val, int max_val,
LOCKMODE lockmode);
@@ -214,6 +211,9 @@ extern void register_reloptions_validator(local_relopts *relopts,
extern void add_local_bool_reloption(local_relopts *relopts, const char *name,
const char *desc, bool default_val,
int offset);
+extern void add_local_ternary_reloption(local_relopts *relopts,
+ const char *name, const char *desc,
+ ternary default_val, int offset);
extern void add_local_int_reloption(local_relopts *relopts, const char *name,
const char *desc, int default_val,
int min_val, int max_val, int offset);
diff --git a/src/include/c.h b/src/include/c.h
index 39022f8a9d..9c59ccccf8 100644
--- a/src/include/c.h
+++ b/src/include/c.h
@@ -488,6 +488,22 @@ typedef void (*pg_funcptr_t) (void);
#include <stdbool.h>
+/*
+ * ternary
+ * Boolean value with an extrea "unset" option
+ *
+ * Ternary data type is used in relation options that can be "true", "false" or
+ * "unset". Since relation options are used deep inside the PostgreSQL code,
+ * this type is declared globally.
+*/
+
+typedef enum ternary
+{
+ TERNARY_FALSE = 0,
+ TERNARY_TRUE = 1,
+ TERNARY_UNSET = -1
+} ternary;
+
/* ----------------------------------------------------------------
* Section 3: standard system types
diff --git a/src/include/utils/rel.h b/src/include/utils/rel.h
index b552359915..7346d618f9 100644
--- a/src/include/utils/rel.h
+++ b/src/include/utils/rel.h
@@ -346,8 +346,7 @@ typedef struct StdRdOptions
bool user_catalog_table; /* use as an additional catalog relation */
int parallel_workers; /* max number of parallel workers */
StdRdOptIndexCleanup vacuum_index_cleanup; /* controls index vacuuming */
- bool vacuum_truncate; /* enables vacuum to truncate a relation */
- bool vacuum_truncate_set; /* whether vacuum_truncate is set */
+ ternary vacuum_truncate; /* enables vacuum to truncate a relation */
/*
* Fraction of pages in a relation that vacuum can eagerly scan and fail
--
2.39.2
--nextPart4155600.3ZeAukHxDK
Content-Disposition: attachment;
filename="v2a-0003-Add-alias-to-be-used-as-unset-state.patch"
Content-Transfer-Encoding: 7Bit
Content-Type: text/x-patch; charset="unicode-2-0-utf-8";
name="v2a-0003-Add-alias-to-be-used-as-unset-state.patch"
^ permalink raw reply [nested|flat] 868+ messages in thread
* [PATCH v2 2/4] Introduce ternary reloptions
@ 2025-09-04 14:41 Nikolay Shaplov <[email protected]>
0 siblings, 0 replies; 868+ messages in thread
From: Nikolay Shaplov @ 2025-09-04 14:41 UTC (permalink / raw)
Introduce ternary reloption as a replacement for current `vacuum_truncate`
implementation. Remove `vacuum_truncate_set` additional flag and using
`TERNARY_UNSET` value instead.
---
src/backend/access/common/reloptions.c | 129 ++++++++++++++++++++-----
src/backend/commands/vacuum.c | 4 +-
src/include/access/reloptions.h | 26 ++---
src/include/c.h | 16 +++
src/include/utils/rel.h | 3 +-
5 files changed, 135 insertions(+), 43 deletions(-)
diff --git a/src/backend/access/common/reloptions.c b/src/backend/access/common/reloptions.c
index 0af3fea68f..f543f11001 100644
--- a/src/backend/access/common/reloptions.c
+++ b/src/backend/access/common/reloptions.c
@@ -40,9 +40,9 @@
*
* To add an option:
*
- * (i) decide on a type (bool, integer, real, enum, string), name, default
- * value, upper and lower bounds (if applicable); for strings, consider a
- * validation routine.
+ * (i) decide on a type (bool, ternary, integer, real, enum, string), name,
+ * default value, upper and lower bounds (if applicable); for strings,
+ * consider a validation routine.
* (ii) add a record below (or use add_<type>_reloption).
* (iii) add it to the appropriate options struct (perhaps StdRdOptions)
* (iv) add it to the appropriate handling routine (perhaps
@@ -147,15 +147,6 @@ static relopt_bool boolRelOpts[] =
},
false
},
- {
- {
- "vacuum_truncate",
- "Enables vacuum to truncate empty pages at the end of this table",
- RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
- ShareUpdateExclusiveLock
- },
- true
- },
{
{
"deduplicate_items",
@@ -170,6 +161,21 @@ static relopt_bool boolRelOpts[] =
{{NULL}}
};
+static relopt_ternary ternaryRelOpts[] =
+{
+ {
+ {
+ "vacuum_truncate",
+ "Enables vacuum to truncate empty pages at the end of this table",
+ RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
+ ShareUpdateExclusiveLock
+ },
+ TERNARY_UNSET
+ },
+ /* list terminator */
+ {{NULL}}
+};
+
static relopt_int intRelOpts[] =
{
{
@@ -600,6 +606,13 @@ initialize_reloptions(void)
boolRelOpts[i].gen.lockmode));
j++;
}
+ for (i = 0; ternaryRelOpts[i].gen.name; i++)
+ {
+ Assert(DoLockModesConflict(ternaryRelOpts[i].gen.lockmode,
+ ternaryRelOpts[i].gen.lockmode));
+ j++;
+ }
+
for (i = 0; intRelOpts[i].gen.name; i++)
{
Assert(DoLockModesConflict(intRelOpts[i].gen.lockmode,
@@ -640,6 +653,14 @@ initialize_reloptions(void)
j++;
}
+ for (i = 0; ternaryRelOpts[i].gen.name; i++)
+ {
+ relOpts[j] = &ternaryRelOpts[i].gen;
+ relOpts[j]->type = RELOPT_TYPE_TERNARY;
+ relOpts[j]->namelen = strlen(relOpts[j]->name);
+ j++;
+ }
+
for (i = 0; intRelOpts[i].gen.name; i++)
{
relOpts[j] = &intRelOpts[i].gen;
@@ -800,6 +821,9 @@ allocate_reloption(bits32 kinds, int type, const char *name, const char *desc,
case RELOPT_TYPE_BOOL:
size = sizeof(relopt_bool);
break;
+ case RELOPT_TYPE_TERNARY:
+ size = sizeof(relopt_ternary);
+ break;
case RELOPT_TYPE_INT:
size = sizeof(relopt_int);
break;
@@ -883,6 +907,54 @@ add_local_bool_reloption(local_relopts *relopts, const char *name,
add_local_reloption(relopts, (relopt_gen *) newoption, offset);
}
+/*
+ * init_ternary_reloption
+ * Allocate and initialize a new ternary reloption
+ */
+static relopt_ternary *
+init_ternary_reloption(bits32 kinds, const char *name, const char *desc,
+ ternary default_val, LOCKMODE lockmode)
+{
+ relopt_ternary *newoption;
+
+ newoption = (relopt_ternary *) allocate_reloption(kinds,
+ RELOPT_TYPE_TERNARY, name, desc, lockmode);
+ newoption->default_val = default_val;
+
+ return newoption;
+}
+
+/*
+ * add_ternary_reloption
+ * Add a new ternary reloption
+ */
+void
+add_ternary_reloption(bits32 kinds, const char *name, const char *desc,
+ ternary default_val, LOCKMODE lockmode)
+{
+ relopt_ternary *newoption = init_ternary_reloption(kinds, name, desc,
+ default_val, lockmode);
+
+ add_reloption((relopt_gen *) newoption);
+}
+
+/*
+ * add_local_ternary_reloption
+ * Add a new ternary local reloption
+ *
+ * 'offset' is offset of ternary-typed field.
+ */
+void
+add_local_ternary_reloption(local_relopts *relopts, const char *name,
+ const char *desc, ternary default_val,
+ int offset)
+{
+ relopt_ternary *newoption = init_ternary_reloption(RELOPT_KIND_LOCAL,
+ name, desc,
+ default_val, 0);
+
+ add_local_reloption(relopts, (relopt_gen *) newoption, offset);
+}
/*
* init_real_reloption
@@ -1617,6 +1689,18 @@ parse_one_reloption(relopt_value *option, char *text_str, int text_len,
option->gen->name, value)));
}
break;
+ case RELOPT_TYPE_TERNARY:
+ {
+ bool b;
+ parsed = parse_bool(value, &b);
+ option->values.ternary_val = b ? TERNARY_TRUE : TERNARY_FALSE;
+ if (validate && !parsed)
+ ereport(ERROR,
+ (errcode(ERRCODE_INVALID_PARAMETER_VALUE),
+ errmsg("invalid value for ternary option \"%s\": %s",
+ option->gen->name, value)));
+ }
+ break;
case RELOPT_TYPE_INT:
{
relopt_int *optint = (relopt_int *) option->gen;
@@ -1780,17 +1864,6 @@ fillRelOptions(void *rdopts, Size basesize,
char *itempos = ((char *) rdopts) + elems[j].offset;
char *string_val;
- /*
- * If isset_offset is provided, store whether the reloption is
- * set there.
- */
- if (elems[j].isset_offset > 0)
- {
- char *setpos = ((char *) rdopts) + elems[j].isset_offset;
-
- *(bool *) setpos = options[i].isset;
- }
-
switch (options[i].gen->type)
{
case RELOPT_TYPE_BOOL:
@@ -1798,6 +1871,11 @@ fillRelOptions(void *rdopts, Size basesize,
options[i].values.bool_val :
((relopt_bool *) options[i].gen)->default_val;
break;
+ case RELOPT_TYPE_TERNARY:
+ *(ternary *) itempos = options[i].isset ?
+ options[i].values.ternary_val :
+ ((relopt_ternary *) options[i].gen)->default_val;
+ break;
case RELOPT_TYPE_INT:
*(int *) itempos = options[i].isset ?
options[i].values.int_val :
@@ -1912,8 +1990,8 @@ default_reloptions(Datum reloptions, bool validate, relopt_kind kind)
offsetof(StdRdOptions, parallel_workers)},
{"vacuum_index_cleanup", RELOPT_TYPE_ENUM,
offsetof(StdRdOptions, vacuum_index_cleanup)},
- {"vacuum_truncate", RELOPT_TYPE_BOOL,
- offsetof(StdRdOptions, vacuum_truncate), offsetof(StdRdOptions, vacuum_truncate_set)},
+ {"vacuum_truncate", RELOPT_TYPE_TERNARY,
+ offsetof(StdRdOptions, vacuum_truncate)},
{"vacuum_max_eager_freeze_failure_rate", RELOPT_TYPE_REAL,
offsetof(StdRdOptions, vacuum_max_eager_freeze_failure_rate)}
};
@@ -1993,7 +2071,6 @@ build_local_reloptions(local_relopts *relopts, Datum options, bool validate)
elems[i].optname = opt->option->name;
elems[i].opttype = opt->option->type;
elems[i].offset = opt->offset;
- elems[i].isset_offset = 0; /* not supported for local relopts yet */
i++;
}
diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c
index 733ef40ae7..977babff54 100644
--- a/src/backend/commands/vacuum.c
+++ b/src/backend/commands/vacuum.c
@@ -2221,9 +2221,9 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams params,
{
StdRdOptions *opts = (StdRdOptions *) rel->rd_options;
- if (opts && opts->vacuum_truncate_set)
+ if (opts && opts->vacuum_truncate != TERNARY_UNSET)
{
- if (opts->vacuum_truncate)
+ if (opts->vacuum_truncate == TERNARY_TRUE)
params.truncate = VACOPTVALUE_ENABLED;
else
params.truncate = VACOPTVALUE_DISABLED;
diff --git a/src/include/access/reloptions.h b/src/include/access/reloptions.h
index a604a4702c..a436697658 100644
--- a/src/include/access/reloptions.h
+++ b/src/include/access/reloptions.h
@@ -29,6 +29,7 @@
typedef enum relopt_type
{
RELOPT_TYPE_BOOL,
+ RELOPT_TYPE_TERNARY, /* on, off, unset */
RELOPT_TYPE_INT,
RELOPT_TYPE_REAL,
RELOPT_TYPE_ENUM,
@@ -80,6 +81,7 @@ typedef struct relopt_value
union
{
bool bool_val;
+ ternary ternary_val;
int int_val;
double real_val;
int enum_val;
@@ -94,6 +96,12 @@ typedef struct relopt_bool
bool default_val;
} relopt_bool;
+typedef struct relopt_rernary
+{
+ relopt_gen gen;
+ int default_val;
+} relopt_ternary;
+
typedef struct relopt_int
{
relopt_gen gen;
@@ -152,19 +160,6 @@ typedef struct
const char *optname; /* option's name */
relopt_type opttype; /* option's datatype */
int offset; /* offset of field in result struct */
-
- /*
- * isset_offset is an optional offset of a field in the result struct that
- * stores whether the option is explicitly set for the relation or if it
- * just picked up the default value. In most cases, this can be
- * accomplished by giving the reloption a special out-of-range default
- * value (e.g., some integer reloptions use -2), but this isn't always
- * possible. For example, a Boolean reloption cannot be given an
- * out-of-range default, so we need another way to discover the source of
- * its value. This offset is only used if given a value greater than
- * zero.
- */
- int isset_offset;
} relopt_parse_elt;
/* Local reloption definition */
@@ -195,6 +190,8 @@ typedef struct local_relopts
extern relopt_kind add_reloption_kind(void);
extern void add_bool_reloption(bits32 kinds, const char *name, const char *desc,
bool default_val, LOCKMODE lockmode);
+extern void add_ternary_reloption(bits32 kinds, const char *name,
+ const char *desc, int default_val, LOCKMODE lockmode);
extern void add_int_reloption(bits32 kinds, const char *name, const char *desc,
int default_val, int min_val, int max_val,
LOCKMODE lockmode);
@@ -214,6 +211,9 @@ extern void register_reloptions_validator(local_relopts *relopts,
extern void add_local_bool_reloption(local_relopts *relopts, const char *name,
const char *desc, bool default_val,
int offset);
+extern void add_local_ternary_reloption(local_relopts *relopts,
+ const char *name, const char *desc,
+ ternary default_val, int offset);
extern void add_local_int_reloption(local_relopts *relopts, const char *name,
const char *desc, int default_val,
int min_val, int max_val, int offset);
diff --git a/src/include/c.h b/src/include/c.h
index 39022f8a9d..9c59ccccf8 100644
--- a/src/include/c.h
+++ b/src/include/c.h
@@ -488,6 +488,22 @@ typedef void (*pg_funcptr_t) (void);
#include <stdbool.h>
+/*
+ * ternary
+ * Boolean value with an extrea "unset" option
+ *
+ * Ternary data type is used in relation options that can be "true", "false" or
+ * "unset". Since relation options are used deep inside the PostgreSQL code,
+ * this type is declared globally.
+*/
+
+typedef enum ternary
+{
+ TERNARY_FALSE = 0,
+ TERNARY_TRUE = 1,
+ TERNARY_UNSET = -1
+} ternary;
+
/* ----------------------------------------------------------------
* Section 3: standard system types
diff --git a/src/include/utils/rel.h b/src/include/utils/rel.h
index b552359915..7346d618f9 100644
--- a/src/include/utils/rel.h
+++ b/src/include/utils/rel.h
@@ -346,8 +346,7 @@ typedef struct StdRdOptions
bool user_catalog_table; /* use as an additional catalog relation */
int parallel_workers; /* max number of parallel workers */
StdRdOptIndexCleanup vacuum_index_cleanup; /* controls index vacuuming */
- bool vacuum_truncate; /* enables vacuum to truncate a relation */
- bool vacuum_truncate_set; /* whether vacuum_truncate is set */
+ ternary vacuum_truncate; /* enables vacuum to truncate a relation */
/*
* Fraction of pages in a relation that vacuum can eagerly scan and fail
--
2.39.2
--nextPart5129932.5fSG56mABF
Content-Disposition: attachment;
filename="v2-0003-Add-alias-to-be-used-as-unset-state.patch"
Content-Transfer-Encoding: 7Bit
Content-Type: text/x-patch; charset="unicode-2-0-utf-8";
name="v2-0003-Add-alias-to-be-used-as-unset-state.patch"
^ permalink raw reply [nested|flat] 868+ messages in thread
* [PATCH v2a 2/4] Introduce ternary reloptions
@ 2025-09-04 14:41 Nikolay Shaplov <[email protected]>
0 siblings, 0 replies; 868+ messages in thread
From: Nikolay Shaplov @ 2025-09-04 14:41 UTC (permalink / raw)
Introduce ternary reloption as a replacement for current `vacuum_truncate`
implementation. Remove `vacuum_truncate_set` additional flag and using
`TERNARY_UNSET` value instead.
---
src/backend/access/common/reloptions.c | 129 ++++++++++++++++++++-----
src/backend/commands/vacuum.c | 4 +-
src/include/access/reloptions.h | 26 ++---
src/include/c.h | 16 +++
src/include/utils/rel.h | 3 +-
5 files changed, 135 insertions(+), 43 deletions(-)
diff --git a/src/backend/access/common/reloptions.c b/src/backend/access/common/reloptions.c
index 0af3fea68f..f543f11001 100644
--- a/src/backend/access/common/reloptions.c
+++ b/src/backend/access/common/reloptions.c
@@ -40,9 +40,9 @@
*
* To add an option:
*
- * (i) decide on a type (bool, integer, real, enum, string), name, default
- * value, upper and lower bounds (if applicable); for strings, consider a
- * validation routine.
+ * (i) decide on a type (bool, ternary, integer, real, enum, string), name,
+ * default value, upper and lower bounds (if applicable); for strings,
+ * consider a validation routine.
* (ii) add a record below (or use add_<type>_reloption).
* (iii) add it to the appropriate options struct (perhaps StdRdOptions)
* (iv) add it to the appropriate handling routine (perhaps
@@ -147,15 +147,6 @@ static relopt_bool boolRelOpts[] =
},
false
},
- {
- {
- "vacuum_truncate",
- "Enables vacuum to truncate empty pages at the end of this table",
- RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
- ShareUpdateExclusiveLock
- },
- true
- },
{
{
"deduplicate_items",
@@ -170,6 +161,21 @@ static relopt_bool boolRelOpts[] =
{{NULL}}
};
+static relopt_ternary ternaryRelOpts[] =
+{
+ {
+ {
+ "vacuum_truncate",
+ "Enables vacuum to truncate empty pages at the end of this table",
+ RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
+ ShareUpdateExclusiveLock
+ },
+ TERNARY_UNSET
+ },
+ /* list terminator */
+ {{NULL}}
+};
+
static relopt_int intRelOpts[] =
{
{
@@ -600,6 +606,13 @@ initialize_reloptions(void)
boolRelOpts[i].gen.lockmode));
j++;
}
+ for (i = 0; ternaryRelOpts[i].gen.name; i++)
+ {
+ Assert(DoLockModesConflict(ternaryRelOpts[i].gen.lockmode,
+ ternaryRelOpts[i].gen.lockmode));
+ j++;
+ }
+
for (i = 0; intRelOpts[i].gen.name; i++)
{
Assert(DoLockModesConflict(intRelOpts[i].gen.lockmode,
@@ -640,6 +653,14 @@ initialize_reloptions(void)
j++;
}
+ for (i = 0; ternaryRelOpts[i].gen.name; i++)
+ {
+ relOpts[j] = &ternaryRelOpts[i].gen;
+ relOpts[j]->type = RELOPT_TYPE_TERNARY;
+ relOpts[j]->namelen = strlen(relOpts[j]->name);
+ j++;
+ }
+
for (i = 0; intRelOpts[i].gen.name; i++)
{
relOpts[j] = &intRelOpts[i].gen;
@@ -800,6 +821,9 @@ allocate_reloption(bits32 kinds, int type, const char *name, const char *desc,
case RELOPT_TYPE_BOOL:
size = sizeof(relopt_bool);
break;
+ case RELOPT_TYPE_TERNARY:
+ size = sizeof(relopt_ternary);
+ break;
case RELOPT_TYPE_INT:
size = sizeof(relopt_int);
break;
@@ -883,6 +907,54 @@ add_local_bool_reloption(local_relopts *relopts, const char *name,
add_local_reloption(relopts, (relopt_gen *) newoption, offset);
}
+/*
+ * init_ternary_reloption
+ * Allocate and initialize a new ternary reloption
+ */
+static relopt_ternary *
+init_ternary_reloption(bits32 kinds, const char *name, const char *desc,
+ ternary default_val, LOCKMODE lockmode)
+{
+ relopt_ternary *newoption;
+
+ newoption = (relopt_ternary *) allocate_reloption(kinds,
+ RELOPT_TYPE_TERNARY, name, desc, lockmode);
+ newoption->default_val = default_val;
+
+ return newoption;
+}
+
+/*
+ * add_ternary_reloption
+ * Add a new ternary reloption
+ */
+void
+add_ternary_reloption(bits32 kinds, const char *name, const char *desc,
+ ternary default_val, LOCKMODE lockmode)
+{
+ relopt_ternary *newoption = init_ternary_reloption(kinds, name, desc,
+ default_val, lockmode);
+
+ add_reloption((relopt_gen *) newoption);
+}
+
+/*
+ * add_local_ternary_reloption
+ * Add a new ternary local reloption
+ *
+ * 'offset' is offset of ternary-typed field.
+ */
+void
+add_local_ternary_reloption(local_relopts *relopts, const char *name,
+ const char *desc, ternary default_val,
+ int offset)
+{
+ relopt_ternary *newoption = init_ternary_reloption(RELOPT_KIND_LOCAL,
+ name, desc,
+ default_val, 0);
+
+ add_local_reloption(relopts, (relopt_gen *) newoption, offset);
+}
/*
* init_real_reloption
@@ -1617,6 +1689,18 @@ parse_one_reloption(relopt_value *option, char *text_str, int text_len,
option->gen->name, value)));
}
break;
+ case RELOPT_TYPE_TERNARY:
+ {
+ bool b;
+ parsed = parse_bool(value, &b);
+ option->values.ternary_val = b ? TERNARY_TRUE : TERNARY_FALSE;
+ if (validate && !parsed)
+ ereport(ERROR,
+ (errcode(ERRCODE_INVALID_PARAMETER_VALUE),
+ errmsg("invalid value for ternary option \"%s\": %s",
+ option->gen->name, value)));
+ }
+ break;
case RELOPT_TYPE_INT:
{
relopt_int *optint = (relopt_int *) option->gen;
@@ -1780,17 +1864,6 @@ fillRelOptions(void *rdopts, Size basesize,
char *itempos = ((char *) rdopts) + elems[j].offset;
char *string_val;
- /*
- * If isset_offset is provided, store whether the reloption is
- * set there.
- */
- if (elems[j].isset_offset > 0)
- {
- char *setpos = ((char *) rdopts) + elems[j].isset_offset;
-
- *(bool *) setpos = options[i].isset;
- }
-
switch (options[i].gen->type)
{
case RELOPT_TYPE_BOOL:
@@ -1798,6 +1871,11 @@ fillRelOptions(void *rdopts, Size basesize,
options[i].values.bool_val :
((relopt_bool *) options[i].gen)->default_val;
break;
+ case RELOPT_TYPE_TERNARY:
+ *(ternary *) itempos = options[i].isset ?
+ options[i].values.ternary_val :
+ ((relopt_ternary *) options[i].gen)->default_val;
+ break;
case RELOPT_TYPE_INT:
*(int *) itempos = options[i].isset ?
options[i].values.int_val :
@@ -1912,8 +1990,8 @@ default_reloptions(Datum reloptions, bool validate, relopt_kind kind)
offsetof(StdRdOptions, parallel_workers)},
{"vacuum_index_cleanup", RELOPT_TYPE_ENUM,
offsetof(StdRdOptions, vacuum_index_cleanup)},
- {"vacuum_truncate", RELOPT_TYPE_BOOL,
- offsetof(StdRdOptions, vacuum_truncate), offsetof(StdRdOptions, vacuum_truncate_set)},
+ {"vacuum_truncate", RELOPT_TYPE_TERNARY,
+ offsetof(StdRdOptions, vacuum_truncate)},
{"vacuum_max_eager_freeze_failure_rate", RELOPT_TYPE_REAL,
offsetof(StdRdOptions, vacuum_max_eager_freeze_failure_rate)}
};
@@ -1993,7 +2071,6 @@ build_local_reloptions(local_relopts *relopts, Datum options, bool validate)
elems[i].optname = opt->option->name;
elems[i].opttype = opt->option->type;
elems[i].offset = opt->offset;
- elems[i].isset_offset = 0; /* not supported for local relopts yet */
i++;
}
diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c
index 733ef40ae7..977babff54 100644
--- a/src/backend/commands/vacuum.c
+++ b/src/backend/commands/vacuum.c
@@ -2221,9 +2221,9 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams params,
{
StdRdOptions *opts = (StdRdOptions *) rel->rd_options;
- if (opts && opts->vacuum_truncate_set)
+ if (opts && opts->vacuum_truncate != TERNARY_UNSET)
{
- if (opts->vacuum_truncate)
+ if (opts->vacuum_truncate == TERNARY_TRUE)
params.truncate = VACOPTVALUE_ENABLED;
else
params.truncate = VACOPTVALUE_DISABLED;
diff --git a/src/include/access/reloptions.h b/src/include/access/reloptions.h
index a604a4702c..a436697658 100644
--- a/src/include/access/reloptions.h
+++ b/src/include/access/reloptions.h
@@ -29,6 +29,7 @@
typedef enum relopt_type
{
RELOPT_TYPE_BOOL,
+ RELOPT_TYPE_TERNARY, /* on, off, unset */
RELOPT_TYPE_INT,
RELOPT_TYPE_REAL,
RELOPT_TYPE_ENUM,
@@ -80,6 +81,7 @@ typedef struct relopt_value
union
{
bool bool_val;
+ ternary ternary_val;
int int_val;
double real_val;
int enum_val;
@@ -94,6 +96,12 @@ typedef struct relopt_bool
bool default_val;
} relopt_bool;
+typedef struct relopt_rernary
+{
+ relopt_gen gen;
+ int default_val;
+} relopt_ternary;
+
typedef struct relopt_int
{
relopt_gen gen;
@@ -152,19 +160,6 @@ typedef struct
const char *optname; /* option's name */
relopt_type opttype; /* option's datatype */
int offset; /* offset of field in result struct */
-
- /*
- * isset_offset is an optional offset of a field in the result struct that
- * stores whether the option is explicitly set for the relation or if it
- * just picked up the default value. In most cases, this can be
- * accomplished by giving the reloption a special out-of-range default
- * value (e.g., some integer reloptions use -2), but this isn't always
- * possible. For example, a Boolean reloption cannot be given an
- * out-of-range default, so we need another way to discover the source of
- * its value. This offset is only used if given a value greater than
- * zero.
- */
- int isset_offset;
} relopt_parse_elt;
/* Local reloption definition */
@@ -195,6 +190,8 @@ typedef struct local_relopts
extern relopt_kind add_reloption_kind(void);
extern void add_bool_reloption(bits32 kinds, const char *name, const char *desc,
bool default_val, LOCKMODE lockmode);
+extern void add_ternary_reloption(bits32 kinds, const char *name,
+ const char *desc, int default_val, LOCKMODE lockmode);
extern void add_int_reloption(bits32 kinds, const char *name, const char *desc,
int default_val, int min_val, int max_val,
LOCKMODE lockmode);
@@ -214,6 +211,9 @@ extern void register_reloptions_validator(local_relopts *relopts,
extern void add_local_bool_reloption(local_relopts *relopts, const char *name,
const char *desc, bool default_val,
int offset);
+extern void add_local_ternary_reloption(local_relopts *relopts,
+ const char *name, const char *desc,
+ ternary default_val, int offset);
extern void add_local_int_reloption(local_relopts *relopts, const char *name,
const char *desc, int default_val,
int min_val, int max_val, int offset);
diff --git a/src/include/c.h b/src/include/c.h
index 39022f8a9d..9c59ccccf8 100644
--- a/src/include/c.h
+++ b/src/include/c.h
@@ -488,6 +488,22 @@ typedef void (*pg_funcptr_t) (void);
#include <stdbool.h>
+/*
+ * ternary
+ * Boolean value with an extrea "unset" option
+ *
+ * Ternary data type is used in relation options that can be "true", "false" or
+ * "unset". Since relation options are used deep inside the PostgreSQL code,
+ * this type is declared globally.
+*/
+
+typedef enum ternary
+{
+ TERNARY_FALSE = 0,
+ TERNARY_TRUE = 1,
+ TERNARY_UNSET = -1
+} ternary;
+
/* ----------------------------------------------------------------
* Section 3: standard system types
diff --git a/src/include/utils/rel.h b/src/include/utils/rel.h
index b552359915..7346d618f9 100644
--- a/src/include/utils/rel.h
+++ b/src/include/utils/rel.h
@@ -346,8 +346,7 @@ typedef struct StdRdOptions
bool user_catalog_table; /* use as an additional catalog relation */
int parallel_workers; /* max number of parallel workers */
StdRdOptIndexCleanup vacuum_index_cleanup; /* controls index vacuuming */
- bool vacuum_truncate; /* enables vacuum to truncate a relation */
- bool vacuum_truncate_set; /* whether vacuum_truncate is set */
+ ternary vacuum_truncate; /* enables vacuum to truncate a relation */
/*
* Fraction of pages in a relation that vacuum can eagerly scan and fail
--
2.39.2
--nextPart4155600.3ZeAukHxDK
Content-Disposition: attachment;
filename="v2a-0003-Add-alias-to-be-used-as-unset-state.patch"
Content-Transfer-Encoding: 7Bit
Content-Type: text/x-patch; charset="unicode-2-0-utf-8";
name="v2a-0003-Add-alias-to-be-used-as-unset-state.patch"
^ permalink raw reply [nested|flat] 868+ messages in thread
* [PATCH v2a 2/4] Introduce ternary reloptions
@ 2025-09-04 14:41 Nikolay Shaplov <[email protected]>
0 siblings, 0 replies; 868+ messages in thread
From: Nikolay Shaplov @ 2025-09-04 14:41 UTC (permalink / raw)
Introduce ternary reloption as a replacement for current `vacuum_truncate`
implementation. Remove `vacuum_truncate_set` additional flag and using
`TERNARY_UNSET` value instead.
---
src/backend/access/common/reloptions.c | 129 ++++++++++++++++++++-----
src/backend/commands/vacuum.c | 4 +-
src/include/access/reloptions.h | 26 ++---
src/include/c.h | 16 +++
src/include/utils/rel.h | 3 +-
5 files changed, 135 insertions(+), 43 deletions(-)
diff --git a/src/backend/access/common/reloptions.c b/src/backend/access/common/reloptions.c
index 0af3fea68f..f543f11001 100644
--- a/src/backend/access/common/reloptions.c
+++ b/src/backend/access/common/reloptions.c
@@ -40,9 +40,9 @@
*
* To add an option:
*
- * (i) decide on a type (bool, integer, real, enum, string), name, default
- * value, upper and lower bounds (if applicable); for strings, consider a
- * validation routine.
+ * (i) decide on a type (bool, ternary, integer, real, enum, string), name,
+ * default value, upper and lower bounds (if applicable); for strings,
+ * consider a validation routine.
* (ii) add a record below (or use add_<type>_reloption).
* (iii) add it to the appropriate options struct (perhaps StdRdOptions)
* (iv) add it to the appropriate handling routine (perhaps
@@ -147,15 +147,6 @@ static relopt_bool boolRelOpts[] =
},
false
},
- {
- {
- "vacuum_truncate",
- "Enables vacuum to truncate empty pages at the end of this table",
- RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
- ShareUpdateExclusiveLock
- },
- true
- },
{
{
"deduplicate_items",
@@ -170,6 +161,21 @@ static relopt_bool boolRelOpts[] =
{{NULL}}
};
+static relopt_ternary ternaryRelOpts[] =
+{
+ {
+ {
+ "vacuum_truncate",
+ "Enables vacuum to truncate empty pages at the end of this table",
+ RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
+ ShareUpdateExclusiveLock
+ },
+ TERNARY_UNSET
+ },
+ /* list terminator */
+ {{NULL}}
+};
+
static relopt_int intRelOpts[] =
{
{
@@ -600,6 +606,13 @@ initialize_reloptions(void)
boolRelOpts[i].gen.lockmode));
j++;
}
+ for (i = 0; ternaryRelOpts[i].gen.name; i++)
+ {
+ Assert(DoLockModesConflict(ternaryRelOpts[i].gen.lockmode,
+ ternaryRelOpts[i].gen.lockmode));
+ j++;
+ }
+
for (i = 0; intRelOpts[i].gen.name; i++)
{
Assert(DoLockModesConflict(intRelOpts[i].gen.lockmode,
@@ -640,6 +653,14 @@ initialize_reloptions(void)
j++;
}
+ for (i = 0; ternaryRelOpts[i].gen.name; i++)
+ {
+ relOpts[j] = &ternaryRelOpts[i].gen;
+ relOpts[j]->type = RELOPT_TYPE_TERNARY;
+ relOpts[j]->namelen = strlen(relOpts[j]->name);
+ j++;
+ }
+
for (i = 0; intRelOpts[i].gen.name; i++)
{
relOpts[j] = &intRelOpts[i].gen;
@@ -800,6 +821,9 @@ allocate_reloption(bits32 kinds, int type, const char *name, const char *desc,
case RELOPT_TYPE_BOOL:
size = sizeof(relopt_bool);
break;
+ case RELOPT_TYPE_TERNARY:
+ size = sizeof(relopt_ternary);
+ break;
case RELOPT_TYPE_INT:
size = sizeof(relopt_int);
break;
@@ -883,6 +907,54 @@ add_local_bool_reloption(local_relopts *relopts, const char *name,
add_local_reloption(relopts, (relopt_gen *) newoption, offset);
}
+/*
+ * init_ternary_reloption
+ * Allocate and initialize a new ternary reloption
+ */
+static relopt_ternary *
+init_ternary_reloption(bits32 kinds, const char *name, const char *desc,
+ ternary default_val, LOCKMODE lockmode)
+{
+ relopt_ternary *newoption;
+
+ newoption = (relopt_ternary *) allocate_reloption(kinds,
+ RELOPT_TYPE_TERNARY, name, desc, lockmode);
+ newoption->default_val = default_val;
+
+ return newoption;
+}
+
+/*
+ * add_ternary_reloption
+ * Add a new ternary reloption
+ */
+void
+add_ternary_reloption(bits32 kinds, const char *name, const char *desc,
+ ternary default_val, LOCKMODE lockmode)
+{
+ relopt_ternary *newoption = init_ternary_reloption(kinds, name, desc,
+ default_val, lockmode);
+
+ add_reloption((relopt_gen *) newoption);
+}
+
+/*
+ * add_local_ternary_reloption
+ * Add a new ternary local reloption
+ *
+ * 'offset' is offset of ternary-typed field.
+ */
+void
+add_local_ternary_reloption(local_relopts *relopts, const char *name,
+ const char *desc, ternary default_val,
+ int offset)
+{
+ relopt_ternary *newoption = init_ternary_reloption(RELOPT_KIND_LOCAL,
+ name, desc,
+ default_val, 0);
+
+ add_local_reloption(relopts, (relopt_gen *) newoption, offset);
+}
/*
* init_real_reloption
@@ -1617,6 +1689,18 @@ parse_one_reloption(relopt_value *option, char *text_str, int text_len,
option->gen->name, value)));
}
break;
+ case RELOPT_TYPE_TERNARY:
+ {
+ bool b;
+ parsed = parse_bool(value, &b);
+ option->values.ternary_val = b ? TERNARY_TRUE : TERNARY_FALSE;
+ if (validate && !parsed)
+ ereport(ERROR,
+ (errcode(ERRCODE_INVALID_PARAMETER_VALUE),
+ errmsg("invalid value for ternary option \"%s\": %s",
+ option->gen->name, value)));
+ }
+ break;
case RELOPT_TYPE_INT:
{
relopt_int *optint = (relopt_int *) option->gen;
@@ -1780,17 +1864,6 @@ fillRelOptions(void *rdopts, Size basesize,
char *itempos = ((char *) rdopts) + elems[j].offset;
char *string_val;
- /*
- * If isset_offset is provided, store whether the reloption is
- * set there.
- */
- if (elems[j].isset_offset > 0)
- {
- char *setpos = ((char *) rdopts) + elems[j].isset_offset;
-
- *(bool *) setpos = options[i].isset;
- }
-
switch (options[i].gen->type)
{
case RELOPT_TYPE_BOOL:
@@ -1798,6 +1871,11 @@ fillRelOptions(void *rdopts, Size basesize,
options[i].values.bool_val :
((relopt_bool *) options[i].gen)->default_val;
break;
+ case RELOPT_TYPE_TERNARY:
+ *(ternary *) itempos = options[i].isset ?
+ options[i].values.ternary_val :
+ ((relopt_ternary *) options[i].gen)->default_val;
+ break;
case RELOPT_TYPE_INT:
*(int *) itempos = options[i].isset ?
options[i].values.int_val :
@@ -1912,8 +1990,8 @@ default_reloptions(Datum reloptions, bool validate, relopt_kind kind)
offsetof(StdRdOptions, parallel_workers)},
{"vacuum_index_cleanup", RELOPT_TYPE_ENUM,
offsetof(StdRdOptions, vacuum_index_cleanup)},
- {"vacuum_truncate", RELOPT_TYPE_BOOL,
- offsetof(StdRdOptions, vacuum_truncate), offsetof(StdRdOptions, vacuum_truncate_set)},
+ {"vacuum_truncate", RELOPT_TYPE_TERNARY,
+ offsetof(StdRdOptions, vacuum_truncate)},
{"vacuum_max_eager_freeze_failure_rate", RELOPT_TYPE_REAL,
offsetof(StdRdOptions, vacuum_max_eager_freeze_failure_rate)}
};
@@ -1993,7 +2071,6 @@ build_local_reloptions(local_relopts *relopts, Datum options, bool validate)
elems[i].optname = opt->option->name;
elems[i].opttype = opt->option->type;
elems[i].offset = opt->offset;
- elems[i].isset_offset = 0; /* not supported for local relopts yet */
i++;
}
diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c
index 733ef40ae7..977babff54 100644
--- a/src/backend/commands/vacuum.c
+++ b/src/backend/commands/vacuum.c
@@ -2221,9 +2221,9 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams params,
{
StdRdOptions *opts = (StdRdOptions *) rel->rd_options;
- if (opts && opts->vacuum_truncate_set)
+ if (opts && opts->vacuum_truncate != TERNARY_UNSET)
{
- if (opts->vacuum_truncate)
+ if (opts->vacuum_truncate == TERNARY_TRUE)
params.truncate = VACOPTVALUE_ENABLED;
else
params.truncate = VACOPTVALUE_DISABLED;
diff --git a/src/include/access/reloptions.h b/src/include/access/reloptions.h
index a604a4702c..a436697658 100644
--- a/src/include/access/reloptions.h
+++ b/src/include/access/reloptions.h
@@ -29,6 +29,7 @@
typedef enum relopt_type
{
RELOPT_TYPE_BOOL,
+ RELOPT_TYPE_TERNARY, /* on, off, unset */
RELOPT_TYPE_INT,
RELOPT_TYPE_REAL,
RELOPT_TYPE_ENUM,
@@ -80,6 +81,7 @@ typedef struct relopt_value
union
{
bool bool_val;
+ ternary ternary_val;
int int_val;
double real_val;
int enum_val;
@@ -94,6 +96,12 @@ typedef struct relopt_bool
bool default_val;
} relopt_bool;
+typedef struct relopt_rernary
+{
+ relopt_gen gen;
+ int default_val;
+} relopt_ternary;
+
typedef struct relopt_int
{
relopt_gen gen;
@@ -152,19 +160,6 @@ typedef struct
const char *optname; /* option's name */
relopt_type opttype; /* option's datatype */
int offset; /* offset of field in result struct */
-
- /*
- * isset_offset is an optional offset of a field in the result struct that
- * stores whether the option is explicitly set for the relation or if it
- * just picked up the default value. In most cases, this can be
- * accomplished by giving the reloption a special out-of-range default
- * value (e.g., some integer reloptions use -2), but this isn't always
- * possible. For example, a Boolean reloption cannot be given an
- * out-of-range default, so we need another way to discover the source of
- * its value. This offset is only used if given a value greater than
- * zero.
- */
- int isset_offset;
} relopt_parse_elt;
/* Local reloption definition */
@@ -195,6 +190,8 @@ typedef struct local_relopts
extern relopt_kind add_reloption_kind(void);
extern void add_bool_reloption(bits32 kinds, const char *name, const char *desc,
bool default_val, LOCKMODE lockmode);
+extern void add_ternary_reloption(bits32 kinds, const char *name,
+ const char *desc, int default_val, LOCKMODE lockmode);
extern void add_int_reloption(bits32 kinds, const char *name, const char *desc,
int default_val, int min_val, int max_val,
LOCKMODE lockmode);
@@ -214,6 +211,9 @@ extern void register_reloptions_validator(local_relopts *relopts,
extern void add_local_bool_reloption(local_relopts *relopts, const char *name,
const char *desc, bool default_val,
int offset);
+extern void add_local_ternary_reloption(local_relopts *relopts,
+ const char *name, const char *desc,
+ ternary default_val, int offset);
extern void add_local_int_reloption(local_relopts *relopts, const char *name,
const char *desc, int default_val,
int min_val, int max_val, int offset);
diff --git a/src/include/c.h b/src/include/c.h
index 39022f8a9d..9c59ccccf8 100644
--- a/src/include/c.h
+++ b/src/include/c.h
@@ -488,6 +488,22 @@ typedef void (*pg_funcptr_t) (void);
#include <stdbool.h>
+/*
+ * ternary
+ * Boolean value with an extrea "unset" option
+ *
+ * Ternary data type is used in relation options that can be "true", "false" or
+ * "unset". Since relation options are used deep inside the PostgreSQL code,
+ * this type is declared globally.
+*/
+
+typedef enum ternary
+{
+ TERNARY_FALSE = 0,
+ TERNARY_TRUE = 1,
+ TERNARY_UNSET = -1
+} ternary;
+
/* ----------------------------------------------------------------
* Section 3: standard system types
diff --git a/src/include/utils/rel.h b/src/include/utils/rel.h
index b552359915..7346d618f9 100644
--- a/src/include/utils/rel.h
+++ b/src/include/utils/rel.h
@@ -346,8 +346,7 @@ typedef struct StdRdOptions
bool user_catalog_table; /* use as an additional catalog relation */
int parallel_workers; /* max number of parallel workers */
StdRdOptIndexCleanup vacuum_index_cleanup; /* controls index vacuuming */
- bool vacuum_truncate; /* enables vacuum to truncate a relation */
- bool vacuum_truncate_set; /* whether vacuum_truncate is set */
+ ternary vacuum_truncate; /* enables vacuum to truncate a relation */
/*
* Fraction of pages in a relation that vacuum can eagerly scan and fail
--
2.39.2
--nextPart4155600.3ZeAukHxDK
Content-Disposition: attachment;
filename="v2a-0003-Add-alias-to-be-used-as-unset-state.patch"
Content-Transfer-Encoding: 7Bit
Content-Type: text/x-patch; charset="unicode-2-0-utf-8";
name="v2a-0003-Add-alias-to-be-used-as-unset-state.patch"
^ permalink raw reply [nested|flat] 868+ messages in thread
* [PATCH v2a 2/4] Introduce ternary reloptions
@ 2025-09-04 14:41 Nikolay Shaplov <[email protected]>
0 siblings, 0 replies; 868+ messages in thread
From: Nikolay Shaplov @ 2025-09-04 14:41 UTC (permalink / raw)
Introduce ternary reloption as a replacement for current `vacuum_truncate`
implementation. Remove `vacuum_truncate_set` additional flag and using
`TERNARY_UNSET` value instead.
---
src/backend/access/common/reloptions.c | 129 ++++++++++++++++++++-----
src/backend/commands/vacuum.c | 4 +-
src/include/access/reloptions.h | 26 ++---
src/include/c.h | 16 +++
src/include/utils/rel.h | 3 +-
5 files changed, 135 insertions(+), 43 deletions(-)
diff --git a/src/backend/access/common/reloptions.c b/src/backend/access/common/reloptions.c
index 0af3fea68f..f543f11001 100644
--- a/src/backend/access/common/reloptions.c
+++ b/src/backend/access/common/reloptions.c
@@ -40,9 +40,9 @@
*
* To add an option:
*
- * (i) decide on a type (bool, integer, real, enum, string), name, default
- * value, upper and lower bounds (if applicable); for strings, consider a
- * validation routine.
+ * (i) decide on a type (bool, ternary, integer, real, enum, string), name,
+ * default value, upper and lower bounds (if applicable); for strings,
+ * consider a validation routine.
* (ii) add a record below (or use add_<type>_reloption).
* (iii) add it to the appropriate options struct (perhaps StdRdOptions)
* (iv) add it to the appropriate handling routine (perhaps
@@ -147,15 +147,6 @@ static relopt_bool boolRelOpts[] =
},
false
},
- {
- {
- "vacuum_truncate",
- "Enables vacuum to truncate empty pages at the end of this table",
- RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
- ShareUpdateExclusiveLock
- },
- true
- },
{
{
"deduplicate_items",
@@ -170,6 +161,21 @@ static relopt_bool boolRelOpts[] =
{{NULL}}
};
+static relopt_ternary ternaryRelOpts[] =
+{
+ {
+ {
+ "vacuum_truncate",
+ "Enables vacuum to truncate empty pages at the end of this table",
+ RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
+ ShareUpdateExclusiveLock
+ },
+ TERNARY_UNSET
+ },
+ /* list terminator */
+ {{NULL}}
+};
+
static relopt_int intRelOpts[] =
{
{
@@ -600,6 +606,13 @@ initialize_reloptions(void)
boolRelOpts[i].gen.lockmode));
j++;
}
+ for (i = 0; ternaryRelOpts[i].gen.name; i++)
+ {
+ Assert(DoLockModesConflict(ternaryRelOpts[i].gen.lockmode,
+ ternaryRelOpts[i].gen.lockmode));
+ j++;
+ }
+
for (i = 0; intRelOpts[i].gen.name; i++)
{
Assert(DoLockModesConflict(intRelOpts[i].gen.lockmode,
@@ -640,6 +653,14 @@ initialize_reloptions(void)
j++;
}
+ for (i = 0; ternaryRelOpts[i].gen.name; i++)
+ {
+ relOpts[j] = &ternaryRelOpts[i].gen;
+ relOpts[j]->type = RELOPT_TYPE_TERNARY;
+ relOpts[j]->namelen = strlen(relOpts[j]->name);
+ j++;
+ }
+
for (i = 0; intRelOpts[i].gen.name; i++)
{
relOpts[j] = &intRelOpts[i].gen;
@@ -800,6 +821,9 @@ allocate_reloption(bits32 kinds, int type, const char *name, const char *desc,
case RELOPT_TYPE_BOOL:
size = sizeof(relopt_bool);
break;
+ case RELOPT_TYPE_TERNARY:
+ size = sizeof(relopt_ternary);
+ break;
case RELOPT_TYPE_INT:
size = sizeof(relopt_int);
break;
@@ -883,6 +907,54 @@ add_local_bool_reloption(local_relopts *relopts, const char *name,
add_local_reloption(relopts, (relopt_gen *) newoption, offset);
}
+/*
+ * init_ternary_reloption
+ * Allocate and initialize a new ternary reloption
+ */
+static relopt_ternary *
+init_ternary_reloption(bits32 kinds, const char *name, const char *desc,
+ ternary default_val, LOCKMODE lockmode)
+{
+ relopt_ternary *newoption;
+
+ newoption = (relopt_ternary *) allocate_reloption(kinds,
+ RELOPT_TYPE_TERNARY, name, desc, lockmode);
+ newoption->default_val = default_val;
+
+ return newoption;
+}
+
+/*
+ * add_ternary_reloption
+ * Add a new ternary reloption
+ */
+void
+add_ternary_reloption(bits32 kinds, const char *name, const char *desc,
+ ternary default_val, LOCKMODE lockmode)
+{
+ relopt_ternary *newoption = init_ternary_reloption(kinds, name, desc,
+ default_val, lockmode);
+
+ add_reloption((relopt_gen *) newoption);
+}
+
+/*
+ * add_local_ternary_reloption
+ * Add a new ternary local reloption
+ *
+ * 'offset' is offset of ternary-typed field.
+ */
+void
+add_local_ternary_reloption(local_relopts *relopts, const char *name,
+ const char *desc, ternary default_val,
+ int offset)
+{
+ relopt_ternary *newoption = init_ternary_reloption(RELOPT_KIND_LOCAL,
+ name, desc,
+ default_val, 0);
+
+ add_local_reloption(relopts, (relopt_gen *) newoption, offset);
+}
/*
* init_real_reloption
@@ -1617,6 +1689,18 @@ parse_one_reloption(relopt_value *option, char *text_str, int text_len,
option->gen->name, value)));
}
break;
+ case RELOPT_TYPE_TERNARY:
+ {
+ bool b;
+ parsed = parse_bool(value, &b);
+ option->values.ternary_val = b ? TERNARY_TRUE : TERNARY_FALSE;
+ if (validate && !parsed)
+ ereport(ERROR,
+ (errcode(ERRCODE_INVALID_PARAMETER_VALUE),
+ errmsg("invalid value for ternary option \"%s\": %s",
+ option->gen->name, value)));
+ }
+ break;
case RELOPT_TYPE_INT:
{
relopt_int *optint = (relopt_int *) option->gen;
@@ -1780,17 +1864,6 @@ fillRelOptions(void *rdopts, Size basesize,
char *itempos = ((char *) rdopts) + elems[j].offset;
char *string_val;
- /*
- * If isset_offset is provided, store whether the reloption is
- * set there.
- */
- if (elems[j].isset_offset > 0)
- {
- char *setpos = ((char *) rdopts) + elems[j].isset_offset;
-
- *(bool *) setpos = options[i].isset;
- }
-
switch (options[i].gen->type)
{
case RELOPT_TYPE_BOOL:
@@ -1798,6 +1871,11 @@ fillRelOptions(void *rdopts, Size basesize,
options[i].values.bool_val :
((relopt_bool *) options[i].gen)->default_val;
break;
+ case RELOPT_TYPE_TERNARY:
+ *(ternary *) itempos = options[i].isset ?
+ options[i].values.ternary_val :
+ ((relopt_ternary *) options[i].gen)->default_val;
+ break;
case RELOPT_TYPE_INT:
*(int *) itempos = options[i].isset ?
options[i].values.int_val :
@@ -1912,8 +1990,8 @@ default_reloptions(Datum reloptions, bool validate, relopt_kind kind)
offsetof(StdRdOptions, parallel_workers)},
{"vacuum_index_cleanup", RELOPT_TYPE_ENUM,
offsetof(StdRdOptions, vacuum_index_cleanup)},
- {"vacuum_truncate", RELOPT_TYPE_BOOL,
- offsetof(StdRdOptions, vacuum_truncate), offsetof(StdRdOptions, vacuum_truncate_set)},
+ {"vacuum_truncate", RELOPT_TYPE_TERNARY,
+ offsetof(StdRdOptions, vacuum_truncate)},
{"vacuum_max_eager_freeze_failure_rate", RELOPT_TYPE_REAL,
offsetof(StdRdOptions, vacuum_max_eager_freeze_failure_rate)}
};
@@ -1993,7 +2071,6 @@ build_local_reloptions(local_relopts *relopts, Datum options, bool validate)
elems[i].optname = opt->option->name;
elems[i].opttype = opt->option->type;
elems[i].offset = opt->offset;
- elems[i].isset_offset = 0; /* not supported for local relopts yet */
i++;
}
diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c
index 733ef40ae7..977babff54 100644
--- a/src/backend/commands/vacuum.c
+++ b/src/backend/commands/vacuum.c
@@ -2221,9 +2221,9 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams params,
{
StdRdOptions *opts = (StdRdOptions *) rel->rd_options;
- if (opts && opts->vacuum_truncate_set)
+ if (opts && opts->vacuum_truncate != TERNARY_UNSET)
{
- if (opts->vacuum_truncate)
+ if (opts->vacuum_truncate == TERNARY_TRUE)
params.truncate = VACOPTVALUE_ENABLED;
else
params.truncate = VACOPTVALUE_DISABLED;
diff --git a/src/include/access/reloptions.h b/src/include/access/reloptions.h
index a604a4702c..a436697658 100644
--- a/src/include/access/reloptions.h
+++ b/src/include/access/reloptions.h
@@ -29,6 +29,7 @@
typedef enum relopt_type
{
RELOPT_TYPE_BOOL,
+ RELOPT_TYPE_TERNARY, /* on, off, unset */
RELOPT_TYPE_INT,
RELOPT_TYPE_REAL,
RELOPT_TYPE_ENUM,
@@ -80,6 +81,7 @@ typedef struct relopt_value
union
{
bool bool_val;
+ ternary ternary_val;
int int_val;
double real_val;
int enum_val;
@@ -94,6 +96,12 @@ typedef struct relopt_bool
bool default_val;
} relopt_bool;
+typedef struct relopt_rernary
+{
+ relopt_gen gen;
+ int default_val;
+} relopt_ternary;
+
typedef struct relopt_int
{
relopt_gen gen;
@@ -152,19 +160,6 @@ typedef struct
const char *optname; /* option's name */
relopt_type opttype; /* option's datatype */
int offset; /* offset of field in result struct */
-
- /*
- * isset_offset is an optional offset of a field in the result struct that
- * stores whether the option is explicitly set for the relation or if it
- * just picked up the default value. In most cases, this can be
- * accomplished by giving the reloption a special out-of-range default
- * value (e.g., some integer reloptions use -2), but this isn't always
- * possible. For example, a Boolean reloption cannot be given an
- * out-of-range default, so we need another way to discover the source of
- * its value. This offset is only used if given a value greater than
- * zero.
- */
- int isset_offset;
} relopt_parse_elt;
/* Local reloption definition */
@@ -195,6 +190,8 @@ typedef struct local_relopts
extern relopt_kind add_reloption_kind(void);
extern void add_bool_reloption(bits32 kinds, const char *name, const char *desc,
bool default_val, LOCKMODE lockmode);
+extern void add_ternary_reloption(bits32 kinds, const char *name,
+ const char *desc, int default_val, LOCKMODE lockmode);
extern void add_int_reloption(bits32 kinds, const char *name, const char *desc,
int default_val, int min_val, int max_val,
LOCKMODE lockmode);
@@ -214,6 +211,9 @@ extern void register_reloptions_validator(local_relopts *relopts,
extern void add_local_bool_reloption(local_relopts *relopts, const char *name,
const char *desc, bool default_val,
int offset);
+extern void add_local_ternary_reloption(local_relopts *relopts,
+ const char *name, const char *desc,
+ ternary default_val, int offset);
extern void add_local_int_reloption(local_relopts *relopts, const char *name,
const char *desc, int default_val,
int min_val, int max_val, int offset);
diff --git a/src/include/c.h b/src/include/c.h
index 39022f8a9d..9c59ccccf8 100644
--- a/src/include/c.h
+++ b/src/include/c.h
@@ -488,6 +488,22 @@ typedef void (*pg_funcptr_t) (void);
#include <stdbool.h>
+/*
+ * ternary
+ * Boolean value with an extrea "unset" option
+ *
+ * Ternary data type is used in relation options that can be "true", "false" or
+ * "unset". Since relation options are used deep inside the PostgreSQL code,
+ * this type is declared globally.
+*/
+
+typedef enum ternary
+{
+ TERNARY_FALSE = 0,
+ TERNARY_TRUE = 1,
+ TERNARY_UNSET = -1
+} ternary;
+
/* ----------------------------------------------------------------
* Section 3: standard system types
diff --git a/src/include/utils/rel.h b/src/include/utils/rel.h
index b552359915..7346d618f9 100644
--- a/src/include/utils/rel.h
+++ b/src/include/utils/rel.h
@@ -346,8 +346,7 @@ typedef struct StdRdOptions
bool user_catalog_table; /* use as an additional catalog relation */
int parallel_workers; /* max number of parallel workers */
StdRdOptIndexCleanup vacuum_index_cleanup; /* controls index vacuuming */
- bool vacuum_truncate; /* enables vacuum to truncate a relation */
- bool vacuum_truncate_set; /* whether vacuum_truncate is set */
+ ternary vacuum_truncate; /* enables vacuum to truncate a relation */
/*
* Fraction of pages in a relation that vacuum can eagerly scan and fail
--
2.39.2
--nextPart4155600.3ZeAukHxDK
Content-Disposition: attachment;
filename="v2a-0003-Add-alias-to-be-used-as-unset-state.patch"
Content-Transfer-Encoding: 7Bit
Content-Type: text/x-patch; charset="unicode-2-0-utf-8";
name="v2a-0003-Add-alias-to-be-used-as-unset-state.patch"
^ permalink raw reply [nested|flat] 868+ messages in thread
* [PATCH v2 2/4] Introduce ternary reloptions
@ 2025-09-04 14:41 Nikolay Shaplov <[email protected]>
0 siblings, 0 replies; 868+ messages in thread
From: Nikolay Shaplov @ 2025-09-04 14:41 UTC (permalink / raw)
Introduce ternary reloption as a replacement for current `vacuum_truncate`
implementation. Remove `vacuum_truncate_set` additional flag and using
`TERNARY_UNSET` value instead.
---
src/backend/access/common/reloptions.c | 129 ++++++++++++++++++++-----
src/backend/commands/vacuum.c | 4 +-
src/include/access/reloptions.h | 26 ++---
src/include/c.h | 16 +++
src/include/utils/rel.h | 3 +-
5 files changed, 135 insertions(+), 43 deletions(-)
diff --git a/src/backend/access/common/reloptions.c b/src/backend/access/common/reloptions.c
index 0af3fea68f..f543f11001 100644
--- a/src/backend/access/common/reloptions.c
+++ b/src/backend/access/common/reloptions.c
@@ -40,9 +40,9 @@
*
* To add an option:
*
- * (i) decide on a type (bool, integer, real, enum, string), name, default
- * value, upper and lower bounds (if applicable); for strings, consider a
- * validation routine.
+ * (i) decide on a type (bool, ternary, integer, real, enum, string), name,
+ * default value, upper and lower bounds (if applicable); for strings,
+ * consider a validation routine.
* (ii) add a record below (or use add_<type>_reloption).
* (iii) add it to the appropriate options struct (perhaps StdRdOptions)
* (iv) add it to the appropriate handling routine (perhaps
@@ -147,15 +147,6 @@ static relopt_bool boolRelOpts[] =
},
false
},
- {
- {
- "vacuum_truncate",
- "Enables vacuum to truncate empty pages at the end of this table",
- RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
- ShareUpdateExclusiveLock
- },
- true
- },
{
{
"deduplicate_items",
@@ -170,6 +161,21 @@ static relopt_bool boolRelOpts[] =
{{NULL}}
};
+static relopt_ternary ternaryRelOpts[] =
+{
+ {
+ {
+ "vacuum_truncate",
+ "Enables vacuum to truncate empty pages at the end of this table",
+ RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
+ ShareUpdateExclusiveLock
+ },
+ TERNARY_UNSET
+ },
+ /* list terminator */
+ {{NULL}}
+};
+
static relopt_int intRelOpts[] =
{
{
@@ -600,6 +606,13 @@ initialize_reloptions(void)
boolRelOpts[i].gen.lockmode));
j++;
}
+ for (i = 0; ternaryRelOpts[i].gen.name; i++)
+ {
+ Assert(DoLockModesConflict(ternaryRelOpts[i].gen.lockmode,
+ ternaryRelOpts[i].gen.lockmode));
+ j++;
+ }
+
for (i = 0; intRelOpts[i].gen.name; i++)
{
Assert(DoLockModesConflict(intRelOpts[i].gen.lockmode,
@@ -640,6 +653,14 @@ initialize_reloptions(void)
j++;
}
+ for (i = 0; ternaryRelOpts[i].gen.name; i++)
+ {
+ relOpts[j] = &ternaryRelOpts[i].gen;
+ relOpts[j]->type = RELOPT_TYPE_TERNARY;
+ relOpts[j]->namelen = strlen(relOpts[j]->name);
+ j++;
+ }
+
for (i = 0; intRelOpts[i].gen.name; i++)
{
relOpts[j] = &intRelOpts[i].gen;
@@ -800,6 +821,9 @@ allocate_reloption(bits32 kinds, int type, const char *name, const char *desc,
case RELOPT_TYPE_BOOL:
size = sizeof(relopt_bool);
break;
+ case RELOPT_TYPE_TERNARY:
+ size = sizeof(relopt_ternary);
+ break;
case RELOPT_TYPE_INT:
size = sizeof(relopt_int);
break;
@@ -883,6 +907,54 @@ add_local_bool_reloption(local_relopts *relopts, const char *name,
add_local_reloption(relopts, (relopt_gen *) newoption, offset);
}
+/*
+ * init_ternary_reloption
+ * Allocate and initialize a new ternary reloption
+ */
+static relopt_ternary *
+init_ternary_reloption(bits32 kinds, const char *name, const char *desc,
+ ternary default_val, LOCKMODE lockmode)
+{
+ relopt_ternary *newoption;
+
+ newoption = (relopt_ternary *) allocate_reloption(kinds,
+ RELOPT_TYPE_TERNARY, name, desc, lockmode);
+ newoption->default_val = default_val;
+
+ return newoption;
+}
+
+/*
+ * add_ternary_reloption
+ * Add a new ternary reloption
+ */
+void
+add_ternary_reloption(bits32 kinds, const char *name, const char *desc,
+ ternary default_val, LOCKMODE lockmode)
+{
+ relopt_ternary *newoption = init_ternary_reloption(kinds, name, desc,
+ default_val, lockmode);
+
+ add_reloption((relopt_gen *) newoption);
+}
+
+/*
+ * add_local_ternary_reloption
+ * Add a new ternary local reloption
+ *
+ * 'offset' is offset of ternary-typed field.
+ */
+void
+add_local_ternary_reloption(local_relopts *relopts, const char *name,
+ const char *desc, ternary default_val,
+ int offset)
+{
+ relopt_ternary *newoption = init_ternary_reloption(RELOPT_KIND_LOCAL,
+ name, desc,
+ default_val, 0);
+
+ add_local_reloption(relopts, (relopt_gen *) newoption, offset);
+}
/*
* init_real_reloption
@@ -1617,6 +1689,18 @@ parse_one_reloption(relopt_value *option, char *text_str, int text_len,
option->gen->name, value)));
}
break;
+ case RELOPT_TYPE_TERNARY:
+ {
+ bool b;
+ parsed = parse_bool(value, &b);
+ option->values.ternary_val = b ? TERNARY_TRUE : TERNARY_FALSE;
+ if (validate && !parsed)
+ ereport(ERROR,
+ (errcode(ERRCODE_INVALID_PARAMETER_VALUE),
+ errmsg("invalid value for ternary option \"%s\": %s",
+ option->gen->name, value)));
+ }
+ break;
case RELOPT_TYPE_INT:
{
relopt_int *optint = (relopt_int *) option->gen;
@@ -1780,17 +1864,6 @@ fillRelOptions(void *rdopts, Size basesize,
char *itempos = ((char *) rdopts) + elems[j].offset;
char *string_val;
- /*
- * If isset_offset is provided, store whether the reloption is
- * set there.
- */
- if (elems[j].isset_offset > 0)
- {
- char *setpos = ((char *) rdopts) + elems[j].isset_offset;
-
- *(bool *) setpos = options[i].isset;
- }
-
switch (options[i].gen->type)
{
case RELOPT_TYPE_BOOL:
@@ -1798,6 +1871,11 @@ fillRelOptions(void *rdopts, Size basesize,
options[i].values.bool_val :
((relopt_bool *) options[i].gen)->default_val;
break;
+ case RELOPT_TYPE_TERNARY:
+ *(ternary *) itempos = options[i].isset ?
+ options[i].values.ternary_val :
+ ((relopt_ternary *) options[i].gen)->default_val;
+ break;
case RELOPT_TYPE_INT:
*(int *) itempos = options[i].isset ?
options[i].values.int_val :
@@ -1912,8 +1990,8 @@ default_reloptions(Datum reloptions, bool validate, relopt_kind kind)
offsetof(StdRdOptions, parallel_workers)},
{"vacuum_index_cleanup", RELOPT_TYPE_ENUM,
offsetof(StdRdOptions, vacuum_index_cleanup)},
- {"vacuum_truncate", RELOPT_TYPE_BOOL,
- offsetof(StdRdOptions, vacuum_truncate), offsetof(StdRdOptions, vacuum_truncate_set)},
+ {"vacuum_truncate", RELOPT_TYPE_TERNARY,
+ offsetof(StdRdOptions, vacuum_truncate)},
{"vacuum_max_eager_freeze_failure_rate", RELOPT_TYPE_REAL,
offsetof(StdRdOptions, vacuum_max_eager_freeze_failure_rate)}
};
@@ -1993,7 +2071,6 @@ build_local_reloptions(local_relopts *relopts, Datum options, bool validate)
elems[i].optname = opt->option->name;
elems[i].opttype = opt->option->type;
elems[i].offset = opt->offset;
- elems[i].isset_offset = 0; /* not supported for local relopts yet */
i++;
}
diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c
index 733ef40ae7..977babff54 100644
--- a/src/backend/commands/vacuum.c
+++ b/src/backend/commands/vacuum.c
@@ -2221,9 +2221,9 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams params,
{
StdRdOptions *opts = (StdRdOptions *) rel->rd_options;
- if (opts && opts->vacuum_truncate_set)
+ if (opts && opts->vacuum_truncate != TERNARY_UNSET)
{
- if (opts->vacuum_truncate)
+ if (opts->vacuum_truncate == TERNARY_TRUE)
params.truncate = VACOPTVALUE_ENABLED;
else
params.truncate = VACOPTVALUE_DISABLED;
diff --git a/src/include/access/reloptions.h b/src/include/access/reloptions.h
index a604a4702c..a436697658 100644
--- a/src/include/access/reloptions.h
+++ b/src/include/access/reloptions.h
@@ -29,6 +29,7 @@
typedef enum relopt_type
{
RELOPT_TYPE_BOOL,
+ RELOPT_TYPE_TERNARY, /* on, off, unset */
RELOPT_TYPE_INT,
RELOPT_TYPE_REAL,
RELOPT_TYPE_ENUM,
@@ -80,6 +81,7 @@ typedef struct relopt_value
union
{
bool bool_val;
+ ternary ternary_val;
int int_val;
double real_val;
int enum_val;
@@ -94,6 +96,12 @@ typedef struct relopt_bool
bool default_val;
} relopt_bool;
+typedef struct relopt_rernary
+{
+ relopt_gen gen;
+ int default_val;
+} relopt_ternary;
+
typedef struct relopt_int
{
relopt_gen gen;
@@ -152,19 +160,6 @@ typedef struct
const char *optname; /* option's name */
relopt_type opttype; /* option's datatype */
int offset; /* offset of field in result struct */
-
- /*
- * isset_offset is an optional offset of a field in the result struct that
- * stores whether the option is explicitly set for the relation or if it
- * just picked up the default value. In most cases, this can be
- * accomplished by giving the reloption a special out-of-range default
- * value (e.g., some integer reloptions use -2), but this isn't always
- * possible. For example, a Boolean reloption cannot be given an
- * out-of-range default, so we need another way to discover the source of
- * its value. This offset is only used if given a value greater than
- * zero.
- */
- int isset_offset;
} relopt_parse_elt;
/* Local reloption definition */
@@ -195,6 +190,8 @@ typedef struct local_relopts
extern relopt_kind add_reloption_kind(void);
extern void add_bool_reloption(bits32 kinds, const char *name, const char *desc,
bool default_val, LOCKMODE lockmode);
+extern void add_ternary_reloption(bits32 kinds, const char *name,
+ const char *desc, int default_val, LOCKMODE lockmode);
extern void add_int_reloption(bits32 kinds, const char *name, const char *desc,
int default_val, int min_val, int max_val,
LOCKMODE lockmode);
@@ -214,6 +211,9 @@ extern void register_reloptions_validator(local_relopts *relopts,
extern void add_local_bool_reloption(local_relopts *relopts, const char *name,
const char *desc, bool default_val,
int offset);
+extern void add_local_ternary_reloption(local_relopts *relopts,
+ const char *name, const char *desc,
+ ternary default_val, int offset);
extern void add_local_int_reloption(local_relopts *relopts, const char *name,
const char *desc, int default_val,
int min_val, int max_val, int offset);
diff --git a/src/include/c.h b/src/include/c.h
index 39022f8a9d..9c59ccccf8 100644
--- a/src/include/c.h
+++ b/src/include/c.h
@@ -488,6 +488,22 @@ typedef void (*pg_funcptr_t) (void);
#include <stdbool.h>
+/*
+ * ternary
+ * Boolean value with an extrea "unset" option
+ *
+ * Ternary data type is used in relation options that can be "true", "false" or
+ * "unset". Since relation options are used deep inside the PostgreSQL code,
+ * this type is declared globally.
+*/
+
+typedef enum ternary
+{
+ TERNARY_FALSE = 0,
+ TERNARY_TRUE = 1,
+ TERNARY_UNSET = -1
+} ternary;
+
/* ----------------------------------------------------------------
* Section 3: standard system types
diff --git a/src/include/utils/rel.h b/src/include/utils/rel.h
index b552359915..7346d618f9 100644
--- a/src/include/utils/rel.h
+++ b/src/include/utils/rel.h
@@ -346,8 +346,7 @@ typedef struct StdRdOptions
bool user_catalog_table; /* use as an additional catalog relation */
int parallel_workers; /* max number of parallel workers */
StdRdOptIndexCleanup vacuum_index_cleanup; /* controls index vacuuming */
- bool vacuum_truncate; /* enables vacuum to truncate a relation */
- bool vacuum_truncate_set; /* whether vacuum_truncate is set */
+ ternary vacuum_truncate; /* enables vacuum to truncate a relation */
/*
* Fraction of pages in a relation that vacuum can eagerly scan and fail
--
2.39.2
--nextPart5129932.5fSG56mABF
Content-Disposition: attachment;
filename="v2-0003-Add-alias-to-be-used-as-unset-state.patch"
Content-Transfer-Encoding: 7Bit
Content-Type: text/x-patch; charset="unicode-2-0-utf-8";
name="v2-0003-Add-alias-to-be-used-as-unset-state.patch"
^ permalink raw reply [nested|flat] 868+ messages in thread
* [PATCH v2a 2/4] Introduce ternary reloptions
@ 2025-09-04 14:41 Nikolay Shaplov <[email protected]>
0 siblings, 0 replies; 868+ messages in thread
From: Nikolay Shaplov @ 2025-09-04 14:41 UTC (permalink / raw)
Introduce ternary reloption as a replacement for current `vacuum_truncate`
implementation. Remove `vacuum_truncate_set` additional flag and using
`TERNARY_UNSET` value instead.
---
src/backend/access/common/reloptions.c | 129 ++++++++++++++++++++-----
src/backend/commands/vacuum.c | 4 +-
src/include/access/reloptions.h | 26 ++---
src/include/c.h | 16 +++
src/include/utils/rel.h | 3 +-
5 files changed, 135 insertions(+), 43 deletions(-)
diff --git a/src/backend/access/common/reloptions.c b/src/backend/access/common/reloptions.c
index 0af3fea68f..f543f11001 100644
--- a/src/backend/access/common/reloptions.c
+++ b/src/backend/access/common/reloptions.c
@@ -40,9 +40,9 @@
*
* To add an option:
*
- * (i) decide on a type (bool, integer, real, enum, string), name, default
- * value, upper and lower bounds (if applicable); for strings, consider a
- * validation routine.
+ * (i) decide on a type (bool, ternary, integer, real, enum, string), name,
+ * default value, upper and lower bounds (if applicable); for strings,
+ * consider a validation routine.
* (ii) add a record below (or use add_<type>_reloption).
* (iii) add it to the appropriate options struct (perhaps StdRdOptions)
* (iv) add it to the appropriate handling routine (perhaps
@@ -147,15 +147,6 @@ static relopt_bool boolRelOpts[] =
},
false
},
- {
- {
- "vacuum_truncate",
- "Enables vacuum to truncate empty pages at the end of this table",
- RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
- ShareUpdateExclusiveLock
- },
- true
- },
{
{
"deduplicate_items",
@@ -170,6 +161,21 @@ static relopt_bool boolRelOpts[] =
{{NULL}}
};
+static relopt_ternary ternaryRelOpts[] =
+{
+ {
+ {
+ "vacuum_truncate",
+ "Enables vacuum to truncate empty pages at the end of this table",
+ RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
+ ShareUpdateExclusiveLock
+ },
+ TERNARY_UNSET
+ },
+ /* list terminator */
+ {{NULL}}
+};
+
static relopt_int intRelOpts[] =
{
{
@@ -600,6 +606,13 @@ initialize_reloptions(void)
boolRelOpts[i].gen.lockmode));
j++;
}
+ for (i = 0; ternaryRelOpts[i].gen.name; i++)
+ {
+ Assert(DoLockModesConflict(ternaryRelOpts[i].gen.lockmode,
+ ternaryRelOpts[i].gen.lockmode));
+ j++;
+ }
+
for (i = 0; intRelOpts[i].gen.name; i++)
{
Assert(DoLockModesConflict(intRelOpts[i].gen.lockmode,
@@ -640,6 +653,14 @@ initialize_reloptions(void)
j++;
}
+ for (i = 0; ternaryRelOpts[i].gen.name; i++)
+ {
+ relOpts[j] = &ternaryRelOpts[i].gen;
+ relOpts[j]->type = RELOPT_TYPE_TERNARY;
+ relOpts[j]->namelen = strlen(relOpts[j]->name);
+ j++;
+ }
+
for (i = 0; intRelOpts[i].gen.name; i++)
{
relOpts[j] = &intRelOpts[i].gen;
@@ -800,6 +821,9 @@ allocate_reloption(bits32 kinds, int type, const char *name, const char *desc,
case RELOPT_TYPE_BOOL:
size = sizeof(relopt_bool);
break;
+ case RELOPT_TYPE_TERNARY:
+ size = sizeof(relopt_ternary);
+ break;
case RELOPT_TYPE_INT:
size = sizeof(relopt_int);
break;
@@ -883,6 +907,54 @@ add_local_bool_reloption(local_relopts *relopts, const char *name,
add_local_reloption(relopts, (relopt_gen *) newoption, offset);
}
+/*
+ * init_ternary_reloption
+ * Allocate and initialize a new ternary reloption
+ */
+static relopt_ternary *
+init_ternary_reloption(bits32 kinds, const char *name, const char *desc,
+ ternary default_val, LOCKMODE lockmode)
+{
+ relopt_ternary *newoption;
+
+ newoption = (relopt_ternary *) allocate_reloption(kinds,
+ RELOPT_TYPE_TERNARY, name, desc, lockmode);
+ newoption->default_val = default_val;
+
+ return newoption;
+}
+
+/*
+ * add_ternary_reloption
+ * Add a new ternary reloption
+ */
+void
+add_ternary_reloption(bits32 kinds, const char *name, const char *desc,
+ ternary default_val, LOCKMODE lockmode)
+{
+ relopt_ternary *newoption = init_ternary_reloption(kinds, name, desc,
+ default_val, lockmode);
+
+ add_reloption((relopt_gen *) newoption);
+}
+
+/*
+ * add_local_ternary_reloption
+ * Add a new ternary local reloption
+ *
+ * 'offset' is offset of ternary-typed field.
+ */
+void
+add_local_ternary_reloption(local_relopts *relopts, const char *name,
+ const char *desc, ternary default_val,
+ int offset)
+{
+ relopt_ternary *newoption = init_ternary_reloption(RELOPT_KIND_LOCAL,
+ name, desc,
+ default_val, 0);
+
+ add_local_reloption(relopts, (relopt_gen *) newoption, offset);
+}
/*
* init_real_reloption
@@ -1617,6 +1689,18 @@ parse_one_reloption(relopt_value *option, char *text_str, int text_len,
option->gen->name, value)));
}
break;
+ case RELOPT_TYPE_TERNARY:
+ {
+ bool b;
+ parsed = parse_bool(value, &b);
+ option->values.ternary_val = b ? TERNARY_TRUE : TERNARY_FALSE;
+ if (validate && !parsed)
+ ereport(ERROR,
+ (errcode(ERRCODE_INVALID_PARAMETER_VALUE),
+ errmsg("invalid value for ternary option \"%s\": %s",
+ option->gen->name, value)));
+ }
+ break;
case RELOPT_TYPE_INT:
{
relopt_int *optint = (relopt_int *) option->gen;
@@ -1780,17 +1864,6 @@ fillRelOptions(void *rdopts, Size basesize,
char *itempos = ((char *) rdopts) + elems[j].offset;
char *string_val;
- /*
- * If isset_offset is provided, store whether the reloption is
- * set there.
- */
- if (elems[j].isset_offset > 0)
- {
- char *setpos = ((char *) rdopts) + elems[j].isset_offset;
-
- *(bool *) setpos = options[i].isset;
- }
-
switch (options[i].gen->type)
{
case RELOPT_TYPE_BOOL:
@@ -1798,6 +1871,11 @@ fillRelOptions(void *rdopts, Size basesize,
options[i].values.bool_val :
((relopt_bool *) options[i].gen)->default_val;
break;
+ case RELOPT_TYPE_TERNARY:
+ *(ternary *) itempos = options[i].isset ?
+ options[i].values.ternary_val :
+ ((relopt_ternary *) options[i].gen)->default_val;
+ break;
case RELOPT_TYPE_INT:
*(int *) itempos = options[i].isset ?
options[i].values.int_val :
@@ -1912,8 +1990,8 @@ default_reloptions(Datum reloptions, bool validate, relopt_kind kind)
offsetof(StdRdOptions, parallel_workers)},
{"vacuum_index_cleanup", RELOPT_TYPE_ENUM,
offsetof(StdRdOptions, vacuum_index_cleanup)},
- {"vacuum_truncate", RELOPT_TYPE_BOOL,
- offsetof(StdRdOptions, vacuum_truncate), offsetof(StdRdOptions, vacuum_truncate_set)},
+ {"vacuum_truncate", RELOPT_TYPE_TERNARY,
+ offsetof(StdRdOptions, vacuum_truncate)},
{"vacuum_max_eager_freeze_failure_rate", RELOPT_TYPE_REAL,
offsetof(StdRdOptions, vacuum_max_eager_freeze_failure_rate)}
};
@@ -1993,7 +2071,6 @@ build_local_reloptions(local_relopts *relopts, Datum options, bool validate)
elems[i].optname = opt->option->name;
elems[i].opttype = opt->option->type;
elems[i].offset = opt->offset;
- elems[i].isset_offset = 0; /* not supported for local relopts yet */
i++;
}
diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c
index 733ef40ae7..977babff54 100644
--- a/src/backend/commands/vacuum.c
+++ b/src/backend/commands/vacuum.c
@@ -2221,9 +2221,9 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams params,
{
StdRdOptions *opts = (StdRdOptions *) rel->rd_options;
- if (opts && opts->vacuum_truncate_set)
+ if (opts && opts->vacuum_truncate != TERNARY_UNSET)
{
- if (opts->vacuum_truncate)
+ if (opts->vacuum_truncate == TERNARY_TRUE)
params.truncate = VACOPTVALUE_ENABLED;
else
params.truncate = VACOPTVALUE_DISABLED;
diff --git a/src/include/access/reloptions.h b/src/include/access/reloptions.h
index a604a4702c..a436697658 100644
--- a/src/include/access/reloptions.h
+++ b/src/include/access/reloptions.h
@@ -29,6 +29,7 @@
typedef enum relopt_type
{
RELOPT_TYPE_BOOL,
+ RELOPT_TYPE_TERNARY, /* on, off, unset */
RELOPT_TYPE_INT,
RELOPT_TYPE_REAL,
RELOPT_TYPE_ENUM,
@@ -80,6 +81,7 @@ typedef struct relopt_value
union
{
bool bool_val;
+ ternary ternary_val;
int int_val;
double real_val;
int enum_val;
@@ -94,6 +96,12 @@ typedef struct relopt_bool
bool default_val;
} relopt_bool;
+typedef struct relopt_rernary
+{
+ relopt_gen gen;
+ int default_val;
+} relopt_ternary;
+
typedef struct relopt_int
{
relopt_gen gen;
@@ -152,19 +160,6 @@ typedef struct
const char *optname; /* option's name */
relopt_type opttype; /* option's datatype */
int offset; /* offset of field in result struct */
-
- /*
- * isset_offset is an optional offset of a field in the result struct that
- * stores whether the option is explicitly set for the relation or if it
- * just picked up the default value. In most cases, this can be
- * accomplished by giving the reloption a special out-of-range default
- * value (e.g., some integer reloptions use -2), but this isn't always
- * possible. For example, a Boolean reloption cannot be given an
- * out-of-range default, so we need another way to discover the source of
- * its value. This offset is only used if given a value greater than
- * zero.
- */
- int isset_offset;
} relopt_parse_elt;
/* Local reloption definition */
@@ -195,6 +190,8 @@ typedef struct local_relopts
extern relopt_kind add_reloption_kind(void);
extern void add_bool_reloption(bits32 kinds, const char *name, const char *desc,
bool default_val, LOCKMODE lockmode);
+extern void add_ternary_reloption(bits32 kinds, const char *name,
+ const char *desc, int default_val, LOCKMODE lockmode);
extern void add_int_reloption(bits32 kinds, const char *name, const char *desc,
int default_val, int min_val, int max_val,
LOCKMODE lockmode);
@@ -214,6 +211,9 @@ extern void register_reloptions_validator(local_relopts *relopts,
extern void add_local_bool_reloption(local_relopts *relopts, const char *name,
const char *desc, bool default_val,
int offset);
+extern void add_local_ternary_reloption(local_relopts *relopts,
+ const char *name, const char *desc,
+ ternary default_val, int offset);
extern void add_local_int_reloption(local_relopts *relopts, const char *name,
const char *desc, int default_val,
int min_val, int max_val, int offset);
diff --git a/src/include/c.h b/src/include/c.h
index 39022f8a9d..9c59ccccf8 100644
--- a/src/include/c.h
+++ b/src/include/c.h
@@ -488,6 +488,22 @@ typedef void (*pg_funcptr_t) (void);
#include <stdbool.h>
+/*
+ * ternary
+ * Boolean value with an extrea "unset" option
+ *
+ * Ternary data type is used in relation options that can be "true", "false" or
+ * "unset". Since relation options are used deep inside the PostgreSQL code,
+ * this type is declared globally.
+*/
+
+typedef enum ternary
+{
+ TERNARY_FALSE = 0,
+ TERNARY_TRUE = 1,
+ TERNARY_UNSET = -1
+} ternary;
+
/* ----------------------------------------------------------------
* Section 3: standard system types
diff --git a/src/include/utils/rel.h b/src/include/utils/rel.h
index b552359915..7346d618f9 100644
--- a/src/include/utils/rel.h
+++ b/src/include/utils/rel.h
@@ -346,8 +346,7 @@ typedef struct StdRdOptions
bool user_catalog_table; /* use as an additional catalog relation */
int parallel_workers; /* max number of parallel workers */
StdRdOptIndexCleanup vacuum_index_cleanup; /* controls index vacuuming */
- bool vacuum_truncate; /* enables vacuum to truncate a relation */
- bool vacuum_truncate_set; /* whether vacuum_truncate is set */
+ ternary vacuum_truncate; /* enables vacuum to truncate a relation */
/*
* Fraction of pages in a relation that vacuum can eagerly scan and fail
--
2.39.2
--nextPart4155600.3ZeAukHxDK
Content-Disposition: attachment;
filename="v2a-0003-Add-alias-to-be-used-as-unset-state.patch"
Content-Transfer-Encoding: 7Bit
Content-Type: text/x-patch; charset="unicode-2-0-utf-8";
name="v2a-0003-Add-alias-to-be-used-as-unset-state.patch"
^ permalink raw reply [nested|flat] 868+ messages in thread
* [PATCH v2a 2/4] Introduce ternary reloptions
@ 2025-09-04 14:41 Nikolay Shaplov <[email protected]>
0 siblings, 0 replies; 868+ messages in thread
From: Nikolay Shaplov @ 2025-09-04 14:41 UTC (permalink / raw)
Introduce ternary reloption as a replacement for current `vacuum_truncate`
implementation. Remove `vacuum_truncate_set` additional flag and using
`TERNARY_UNSET` value instead.
---
src/backend/access/common/reloptions.c | 129 ++++++++++++++++++++-----
src/backend/commands/vacuum.c | 4 +-
src/include/access/reloptions.h | 26 ++---
src/include/c.h | 16 +++
src/include/utils/rel.h | 3 +-
5 files changed, 135 insertions(+), 43 deletions(-)
diff --git a/src/backend/access/common/reloptions.c b/src/backend/access/common/reloptions.c
index 0af3fea68f..f543f11001 100644
--- a/src/backend/access/common/reloptions.c
+++ b/src/backend/access/common/reloptions.c
@@ -40,9 +40,9 @@
*
* To add an option:
*
- * (i) decide on a type (bool, integer, real, enum, string), name, default
- * value, upper and lower bounds (if applicable); for strings, consider a
- * validation routine.
+ * (i) decide on a type (bool, ternary, integer, real, enum, string), name,
+ * default value, upper and lower bounds (if applicable); for strings,
+ * consider a validation routine.
* (ii) add a record below (or use add_<type>_reloption).
* (iii) add it to the appropriate options struct (perhaps StdRdOptions)
* (iv) add it to the appropriate handling routine (perhaps
@@ -147,15 +147,6 @@ static relopt_bool boolRelOpts[] =
},
false
},
- {
- {
- "vacuum_truncate",
- "Enables vacuum to truncate empty pages at the end of this table",
- RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
- ShareUpdateExclusiveLock
- },
- true
- },
{
{
"deduplicate_items",
@@ -170,6 +161,21 @@ static relopt_bool boolRelOpts[] =
{{NULL}}
};
+static relopt_ternary ternaryRelOpts[] =
+{
+ {
+ {
+ "vacuum_truncate",
+ "Enables vacuum to truncate empty pages at the end of this table",
+ RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
+ ShareUpdateExclusiveLock
+ },
+ TERNARY_UNSET
+ },
+ /* list terminator */
+ {{NULL}}
+};
+
static relopt_int intRelOpts[] =
{
{
@@ -600,6 +606,13 @@ initialize_reloptions(void)
boolRelOpts[i].gen.lockmode));
j++;
}
+ for (i = 0; ternaryRelOpts[i].gen.name; i++)
+ {
+ Assert(DoLockModesConflict(ternaryRelOpts[i].gen.lockmode,
+ ternaryRelOpts[i].gen.lockmode));
+ j++;
+ }
+
for (i = 0; intRelOpts[i].gen.name; i++)
{
Assert(DoLockModesConflict(intRelOpts[i].gen.lockmode,
@@ -640,6 +653,14 @@ initialize_reloptions(void)
j++;
}
+ for (i = 0; ternaryRelOpts[i].gen.name; i++)
+ {
+ relOpts[j] = &ternaryRelOpts[i].gen;
+ relOpts[j]->type = RELOPT_TYPE_TERNARY;
+ relOpts[j]->namelen = strlen(relOpts[j]->name);
+ j++;
+ }
+
for (i = 0; intRelOpts[i].gen.name; i++)
{
relOpts[j] = &intRelOpts[i].gen;
@@ -800,6 +821,9 @@ allocate_reloption(bits32 kinds, int type, const char *name, const char *desc,
case RELOPT_TYPE_BOOL:
size = sizeof(relopt_bool);
break;
+ case RELOPT_TYPE_TERNARY:
+ size = sizeof(relopt_ternary);
+ break;
case RELOPT_TYPE_INT:
size = sizeof(relopt_int);
break;
@@ -883,6 +907,54 @@ add_local_bool_reloption(local_relopts *relopts, const char *name,
add_local_reloption(relopts, (relopt_gen *) newoption, offset);
}
+/*
+ * init_ternary_reloption
+ * Allocate and initialize a new ternary reloption
+ */
+static relopt_ternary *
+init_ternary_reloption(bits32 kinds, const char *name, const char *desc,
+ ternary default_val, LOCKMODE lockmode)
+{
+ relopt_ternary *newoption;
+
+ newoption = (relopt_ternary *) allocate_reloption(kinds,
+ RELOPT_TYPE_TERNARY, name, desc, lockmode);
+ newoption->default_val = default_val;
+
+ return newoption;
+}
+
+/*
+ * add_ternary_reloption
+ * Add a new ternary reloption
+ */
+void
+add_ternary_reloption(bits32 kinds, const char *name, const char *desc,
+ ternary default_val, LOCKMODE lockmode)
+{
+ relopt_ternary *newoption = init_ternary_reloption(kinds, name, desc,
+ default_val, lockmode);
+
+ add_reloption((relopt_gen *) newoption);
+}
+
+/*
+ * add_local_ternary_reloption
+ * Add a new ternary local reloption
+ *
+ * 'offset' is offset of ternary-typed field.
+ */
+void
+add_local_ternary_reloption(local_relopts *relopts, const char *name,
+ const char *desc, ternary default_val,
+ int offset)
+{
+ relopt_ternary *newoption = init_ternary_reloption(RELOPT_KIND_LOCAL,
+ name, desc,
+ default_val, 0);
+
+ add_local_reloption(relopts, (relopt_gen *) newoption, offset);
+}
/*
* init_real_reloption
@@ -1617,6 +1689,18 @@ parse_one_reloption(relopt_value *option, char *text_str, int text_len,
option->gen->name, value)));
}
break;
+ case RELOPT_TYPE_TERNARY:
+ {
+ bool b;
+ parsed = parse_bool(value, &b);
+ option->values.ternary_val = b ? TERNARY_TRUE : TERNARY_FALSE;
+ if (validate && !parsed)
+ ereport(ERROR,
+ (errcode(ERRCODE_INVALID_PARAMETER_VALUE),
+ errmsg("invalid value for ternary option \"%s\": %s",
+ option->gen->name, value)));
+ }
+ break;
case RELOPT_TYPE_INT:
{
relopt_int *optint = (relopt_int *) option->gen;
@@ -1780,17 +1864,6 @@ fillRelOptions(void *rdopts, Size basesize,
char *itempos = ((char *) rdopts) + elems[j].offset;
char *string_val;
- /*
- * If isset_offset is provided, store whether the reloption is
- * set there.
- */
- if (elems[j].isset_offset > 0)
- {
- char *setpos = ((char *) rdopts) + elems[j].isset_offset;
-
- *(bool *) setpos = options[i].isset;
- }
-
switch (options[i].gen->type)
{
case RELOPT_TYPE_BOOL:
@@ -1798,6 +1871,11 @@ fillRelOptions(void *rdopts, Size basesize,
options[i].values.bool_val :
((relopt_bool *) options[i].gen)->default_val;
break;
+ case RELOPT_TYPE_TERNARY:
+ *(ternary *) itempos = options[i].isset ?
+ options[i].values.ternary_val :
+ ((relopt_ternary *) options[i].gen)->default_val;
+ break;
case RELOPT_TYPE_INT:
*(int *) itempos = options[i].isset ?
options[i].values.int_val :
@@ -1912,8 +1990,8 @@ default_reloptions(Datum reloptions, bool validate, relopt_kind kind)
offsetof(StdRdOptions, parallel_workers)},
{"vacuum_index_cleanup", RELOPT_TYPE_ENUM,
offsetof(StdRdOptions, vacuum_index_cleanup)},
- {"vacuum_truncate", RELOPT_TYPE_BOOL,
- offsetof(StdRdOptions, vacuum_truncate), offsetof(StdRdOptions, vacuum_truncate_set)},
+ {"vacuum_truncate", RELOPT_TYPE_TERNARY,
+ offsetof(StdRdOptions, vacuum_truncate)},
{"vacuum_max_eager_freeze_failure_rate", RELOPT_TYPE_REAL,
offsetof(StdRdOptions, vacuum_max_eager_freeze_failure_rate)}
};
@@ -1993,7 +2071,6 @@ build_local_reloptions(local_relopts *relopts, Datum options, bool validate)
elems[i].optname = opt->option->name;
elems[i].opttype = opt->option->type;
elems[i].offset = opt->offset;
- elems[i].isset_offset = 0; /* not supported for local relopts yet */
i++;
}
diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c
index 733ef40ae7..977babff54 100644
--- a/src/backend/commands/vacuum.c
+++ b/src/backend/commands/vacuum.c
@@ -2221,9 +2221,9 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams params,
{
StdRdOptions *opts = (StdRdOptions *) rel->rd_options;
- if (opts && opts->vacuum_truncate_set)
+ if (opts && opts->vacuum_truncate != TERNARY_UNSET)
{
- if (opts->vacuum_truncate)
+ if (opts->vacuum_truncate == TERNARY_TRUE)
params.truncate = VACOPTVALUE_ENABLED;
else
params.truncate = VACOPTVALUE_DISABLED;
diff --git a/src/include/access/reloptions.h b/src/include/access/reloptions.h
index a604a4702c..a436697658 100644
--- a/src/include/access/reloptions.h
+++ b/src/include/access/reloptions.h
@@ -29,6 +29,7 @@
typedef enum relopt_type
{
RELOPT_TYPE_BOOL,
+ RELOPT_TYPE_TERNARY, /* on, off, unset */
RELOPT_TYPE_INT,
RELOPT_TYPE_REAL,
RELOPT_TYPE_ENUM,
@@ -80,6 +81,7 @@ typedef struct relopt_value
union
{
bool bool_val;
+ ternary ternary_val;
int int_val;
double real_val;
int enum_val;
@@ -94,6 +96,12 @@ typedef struct relopt_bool
bool default_val;
} relopt_bool;
+typedef struct relopt_rernary
+{
+ relopt_gen gen;
+ int default_val;
+} relopt_ternary;
+
typedef struct relopt_int
{
relopt_gen gen;
@@ -152,19 +160,6 @@ typedef struct
const char *optname; /* option's name */
relopt_type opttype; /* option's datatype */
int offset; /* offset of field in result struct */
-
- /*
- * isset_offset is an optional offset of a field in the result struct that
- * stores whether the option is explicitly set for the relation or if it
- * just picked up the default value. In most cases, this can be
- * accomplished by giving the reloption a special out-of-range default
- * value (e.g., some integer reloptions use -2), but this isn't always
- * possible. For example, a Boolean reloption cannot be given an
- * out-of-range default, so we need another way to discover the source of
- * its value. This offset is only used if given a value greater than
- * zero.
- */
- int isset_offset;
} relopt_parse_elt;
/* Local reloption definition */
@@ -195,6 +190,8 @@ typedef struct local_relopts
extern relopt_kind add_reloption_kind(void);
extern void add_bool_reloption(bits32 kinds, const char *name, const char *desc,
bool default_val, LOCKMODE lockmode);
+extern void add_ternary_reloption(bits32 kinds, const char *name,
+ const char *desc, int default_val, LOCKMODE lockmode);
extern void add_int_reloption(bits32 kinds, const char *name, const char *desc,
int default_val, int min_val, int max_val,
LOCKMODE lockmode);
@@ -214,6 +211,9 @@ extern void register_reloptions_validator(local_relopts *relopts,
extern void add_local_bool_reloption(local_relopts *relopts, const char *name,
const char *desc, bool default_val,
int offset);
+extern void add_local_ternary_reloption(local_relopts *relopts,
+ const char *name, const char *desc,
+ ternary default_val, int offset);
extern void add_local_int_reloption(local_relopts *relopts, const char *name,
const char *desc, int default_val,
int min_val, int max_val, int offset);
diff --git a/src/include/c.h b/src/include/c.h
index 39022f8a9d..9c59ccccf8 100644
--- a/src/include/c.h
+++ b/src/include/c.h
@@ -488,6 +488,22 @@ typedef void (*pg_funcptr_t) (void);
#include <stdbool.h>
+/*
+ * ternary
+ * Boolean value with an extrea "unset" option
+ *
+ * Ternary data type is used in relation options that can be "true", "false" or
+ * "unset". Since relation options are used deep inside the PostgreSQL code,
+ * this type is declared globally.
+*/
+
+typedef enum ternary
+{
+ TERNARY_FALSE = 0,
+ TERNARY_TRUE = 1,
+ TERNARY_UNSET = -1
+} ternary;
+
/* ----------------------------------------------------------------
* Section 3: standard system types
diff --git a/src/include/utils/rel.h b/src/include/utils/rel.h
index b552359915..7346d618f9 100644
--- a/src/include/utils/rel.h
+++ b/src/include/utils/rel.h
@@ -346,8 +346,7 @@ typedef struct StdRdOptions
bool user_catalog_table; /* use as an additional catalog relation */
int parallel_workers; /* max number of parallel workers */
StdRdOptIndexCleanup vacuum_index_cleanup; /* controls index vacuuming */
- bool vacuum_truncate; /* enables vacuum to truncate a relation */
- bool vacuum_truncate_set; /* whether vacuum_truncate is set */
+ ternary vacuum_truncate; /* enables vacuum to truncate a relation */
/*
* Fraction of pages in a relation that vacuum can eagerly scan and fail
--
2.39.2
--nextPart4155600.3ZeAukHxDK
Content-Disposition: attachment;
filename="v2a-0003-Add-alias-to-be-used-as-unset-state.patch"
Content-Transfer-Encoding: 7Bit
Content-Type: text/x-patch; charset="unicode-2-0-utf-8";
name="v2a-0003-Add-alias-to-be-used-as-unset-state.patch"
^ permalink raw reply [nested|flat] 868+ messages in thread
* [PATCH v2a 2/4] Introduce ternary reloptions
@ 2025-09-04 14:41 Nikolay Shaplov <[email protected]>
0 siblings, 0 replies; 868+ messages in thread
From: Nikolay Shaplov @ 2025-09-04 14:41 UTC (permalink / raw)
Introduce ternary reloption as a replacement for current `vacuum_truncate`
implementation. Remove `vacuum_truncate_set` additional flag and using
`TERNARY_UNSET` value instead.
---
src/backend/access/common/reloptions.c | 129 ++++++++++++++++++++-----
src/backend/commands/vacuum.c | 4 +-
src/include/access/reloptions.h | 26 ++---
src/include/c.h | 16 +++
src/include/utils/rel.h | 3 +-
5 files changed, 135 insertions(+), 43 deletions(-)
diff --git a/src/backend/access/common/reloptions.c b/src/backend/access/common/reloptions.c
index 0af3fea68f..f543f11001 100644
--- a/src/backend/access/common/reloptions.c
+++ b/src/backend/access/common/reloptions.c
@@ -40,9 +40,9 @@
*
* To add an option:
*
- * (i) decide on a type (bool, integer, real, enum, string), name, default
- * value, upper and lower bounds (if applicable); for strings, consider a
- * validation routine.
+ * (i) decide on a type (bool, ternary, integer, real, enum, string), name,
+ * default value, upper and lower bounds (if applicable); for strings,
+ * consider a validation routine.
* (ii) add a record below (or use add_<type>_reloption).
* (iii) add it to the appropriate options struct (perhaps StdRdOptions)
* (iv) add it to the appropriate handling routine (perhaps
@@ -147,15 +147,6 @@ static relopt_bool boolRelOpts[] =
},
false
},
- {
- {
- "vacuum_truncate",
- "Enables vacuum to truncate empty pages at the end of this table",
- RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
- ShareUpdateExclusiveLock
- },
- true
- },
{
{
"deduplicate_items",
@@ -170,6 +161,21 @@ static relopt_bool boolRelOpts[] =
{{NULL}}
};
+static relopt_ternary ternaryRelOpts[] =
+{
+ {
+ {
+ "vacuum_truncate",
+ "Enables vacuum to truncate empty pages at the end of this table",
+ RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
+ ShareUpdateExclusiveLock
+ },
+ TERNARY_UNSET
+ },
+ /* list terminator */
+ {{NULL}}
+};
+
static relopt_int intRelOpts[] =
{
{
@@ -600,6 +606,13 @@ initialize_reloptions(void)
boolRelOpts[i].gen.lockmode));
j++;
}
+ for (i = 0; ternaryRelOpts[i].gen.name; i++)
+ {
+ Assert(DoLockModesConflict(ternaryRelOpts[i].gen.lockmode,
+ ternaryRelOpts[i].gen.lockmode));
+ j++;
+ }
+
for (i = 0; intRelOpts[i].gen.name; i++)
{
Assert(DoLockModesConflict(intRelOpts[i].gen.lockmode,
@@ -640,6 +653,14 @@ initialize_reloptions(void)
j++;
}
+ for (i = 0; ternaryRelOpts[i].gen.name; i++)
+ {
+ relOpts[j] = &ternaryRelOpts[i].gen;
+ relOpts[j]->type = RELOPT_TYPE_TERNARY;
+ relOpts[j]->namelen = strlen(relOpts[j]->name);
+ j++;
+ }
+
for (i = 0; intRelOpts[i].gen.name; i++)
{
relOpts[j] = &intRelOpts[i].gen;
@@ -800,6 +821,9 @@ allocate_reloption(bits32 kinds, int type, const char *name, const char *desc,
case RELOPT_TYPE_BOOL:
size = sizeof(relopt_bool);
break;
+ case RELOPT_TYPE_TERNARY:
+ size = sizeof(relopt_ternary);
+ break;
case RELOPT_TYPE_INT:
size = sizeof(relopt_int);
break;
@@ -883,6 +907,54 @@ add_local_bool_reloption(local_relopts *relopts, const char *name,
add_local_reloption(relopts, (relopt_gen *) newoption, offset);
}
+/*
+ * init_ternary_reloption
+ * Allocate and initialize a new ternary reloption
+ */
+static relopt_ternary *
+init_ternary_reloption(bits32 kinds, const char *name, const char *desc,
+ ternary default_val, LOCKMODE lockmode)
+{
+ relopt_ternary *newoption;
+
+ newoption = (relopt_ternary *) allocate_reloption(kinds,
+ RELOPT_TYPE_TERNARY, name, desc, lockmode);
+ newoption->default_val = default_val;
+
+ return newoption;
+}
+
+/*
+ * add_ternary_reloption
+ * Add a new ternary reloption
+ */
+void
+add_ternary_reloption(bits32 kinds, const char *name, const char *desc,
+ ternary default_val, LOCKMODE lockmode)
+{
+ relopt_ternary *newoption = init_ternary_reloption(kinds, name, desc,
+ default_val, lockmode);
+
+ add_reloption((relopt_gen *) newoption);
+}
+
+/*
+ * add_local_ternary_reloption
+ * Add a new ternary local reloption
+ *
+ * 'offset' is offset of ternary-typed field.
+ */
+void
+add_local_ternary_reloption(local_relopts *relopts, const char *name,
+ const char *desc, ternary default_val,
+ int offset)
+{
+ relopt_ternary *newoption = init_ternary_reloption(RELOPT_KIND_LOCAL,
+ name, desc,
+ default_val, 0);
+
+ add_local_reloption(relopts, (relopt_gen *) newoption, offset);
+}
/*
* init_real_reloption
@@ -1617,6 +1689,18 @@ parse_one_reloption(relopt_value *option, char *text_str, int text_len,
option->gen->name, value)));
}
break;
+ case RELOPT_TYPE_TERNARY:
+ {
+ bool b;
+ parsed = parse_bool(value, &b);
+ option->values.ternary_val = b ? TERNARY_TRUE : TERNARY_FALSE;
+ if (validate && !parsed)
+ ereport(ERROR,
+ (errcode(ERRCODE_INVALID_PARAMETER_VALUE),
+ errmsg("invalid value for ternary option \"%s\": %s",
+ option->gen->name, value)));
+ }
+ break;
case RELOPT_TYPE_INT:
{
relopt_int *optint = (relopt_int *) option->gen;
@@ -1780,17 +1864,6 @@ fillRelOptions(void *rdopts, Size basesize,
char *itempos = ((char *) rdopts) + elems[j].offset;
char *string_val;
- /*
- * If isset_offset is provided, store whether the reloption is
- * set there.
- */
- if (elems[j].isset_offset > 0)
- {
- char *setpos = ((char *) rdopts) + elems[j].isset_offset;
-
- *(bool *) setpos = options[i].isset;
- }
-
switch (options[i].gen->type)
{
case RELOPT_TYPE_BOOL:
@@ -1798,6 +1871,11 @@ fillRelOptions(void *rdopts, Size basesize,
options[i].values.bool_val :
((relopt_bool *) options[i].gen)->default_val;
break;
+ case RELOPT_TYPE_TERNARY:
+ *(ternary *) itempos = options[i].isset ?
+ options[i].values.ternary_val :
+ ((relopt_ternary *) options[i].gen)->default_val;
+ break;
case RELOPT_TYPE_INT:
*(int *) itempos = options[i].isset ?
options[i].values.int_val :
@@ -1912,8 +1990,8 @@ default_reloptions(Datum reloptions, bool validate, relopt_kind kind)
offsetof(StdRdOptions, parallel_workers)},
{"vacuum_index_cleanup", RELOPT_TYPE_ENUM,
offsetof(StdRdOptions, vacuum_index_cleanup)},
- {"vacuum_truncate", RELOPT_TYPE_BOOL,
- offsetof(StdRdOptions, vacuum_truncate), offsetof(StdRdOptions, vacuum_truncate_set)},
+ {"vacuum_truncate", RELOPT_TYPE_TERNARY,
+ offsetof(StdRdOptions, vacuum_truncate)},
{"vacuum_max_eager_freeze_failure_rate", RELOPT_TYPE_REAL,
offsetof(StdRdOptions, vacuum_max_eager_freeze_failure_rate)}
};
@@ -1993,7 +2071,6 @@ build_local_reloptions(local_relopts *relopts, Datum options, bool validate)
elems[i].optname = opt->option->name;
elems[i].opttype = opt->option->type;
elems[i].offset = opt->offset;
- elems[i].isset_offset = 0; /* not supported for local relopts yet */
i++;
}
diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c
index 733ef40ae7..977babff54 100644
--- a/src/backend/commands/vacuum.c
+++ b/src/backend/commands/vacuum.c
@@ -2221,9 +2221,9 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams params,
{
StdRdOptions *opts = (StdRdOptions *) rel->rd_options;
- if (opts && opts->vacuum_truncate_set)
+ if (opts && opts->vacuum_truncate != TERNARY_UNSET)
{
- if (opts->vacuum_truncate)
+ if (opts->vacuum_truncate == TERNARY_TRUE)
params.truncate = VACOPTVALUE_ENABLED;
else
params.truncate = VACOPTVALUE_DISABLED;
diff --git a/src/include/access/reloptions.h b/src/include/access/reloptions.h
index a604a4702c..a436697658 100644
--- a/src/include/access/reloptions.h
+++ b/src/include/access/reloptions.h
@@ -29,6 +29,7 @@
typedef enum relopt_type
{
RELOPT_TYPE_BOOL,
+ RELOPT_TYPE_TERNARY, /* on, off, unset */
RELOPT_TYPE_INT,
RELOPT_TYPE_REAL,
RELOPT_TYPE_ENUM,
@@ -80,6 +81,7 @@ typedef struct relopt_value
union
{
bool bool_val;
+ ternary ternary_val;
int int_val;
double real_val;
int enum_val;
@@ -94,6 +96,12 @@ typedef struct relopt_bool
bool default_val;
} relopt_bool;
+typedef struct relopt_rernary
+{
+ relopt_gen gen;
+ int default_val;
+} relopt_ternary;
+
typedef struct relopt_int
{
relopt_gen gen;
@@ -152,19 +160,6 @@ typedef struct
const char *optname; /* option's name */
relopt_type opttype; /* option's datatype */
int offset; /* offset of field in result struct */
-
- /*
- * isset_offset is an optional offset of a field in the result struct that
- * stores whether the option is explicitly set for the relation or if it
- * just picked up the default value. In most cases, this can be
- * accomplished by giving the reloption a special out-of-range default
- * value (e.g., some integer reloptions use -2), but this isn't always
- * possible. For example, a Boolean reloption cannot be given an
- * out-of-range default, so we need another way to discover the source of
- * its value. This offset is only used if given a value greater than
- * zero.
- */
- int isset_offset;
} relopt_parse_elt;
/* Local reloption definition */
@@ -195,6 +190,8 @@ typedef struct local_relopts
extern relopt_kind add_reloption_kind(void);
extern void add_bool_reloption(bits32 kinds, const char *name, const char *desc,
bool default_val, LOCKMODE lockmode);
+extern void add_ternary_reloption(bits32 kinds, const char *name,
+ const char *desc, int default_val, LOCKMODE lockmode);
extern void add_int_reloption(bits32 kinds, const char *name, const char *desc,
int default_val, int min_val, int max_val,
LOCKMODE lockmode);
@@ -214,6 +211,9 @@ extern void register_reloptions_validator(local_relopts *relopts,
extern void add_local_bool_reloption(local_relopts *relopts, const char *name,
const char *desc, bool default_val,
int offset);
+extern void add_local_ternary_reloption(local_relopts *relopts,
+ const char *name, const char *desc,
+ ternary default_val, int offset);
extern void add_local_int_reloption(local_relopts *relopts, const char *name,
const char *desc, int default_val,
int min_val, int max_val, int offset);
diff --git a/src/include/c.h b/src/include/c.h
index 39022f8a9d..9c59ccccf8 100644
--- a/src/include/c.h
+++ b/src/include/c.h
@@ -488,6 +488,22 @@ typedef void (*pg_funcptr_t) (void);
#include <stdbool.h>
+/*
+ * ternary
+ * Boolean value with an extrea "unset" option
+ *
+ * Ternary data type is used in relation options that can be "true", "false" or
+ * "unset". Since relation options are used deep inside the PostgreSQL code,
+ * this type is declared globally.
+*/
+
+typedef enum ternary
+{
+ TERNARY_FALSE = 0,
+ TERNARY_TRUE = 1,
+ TERNARY_UNSET = -1
+} ternary;
+
/* ----------------------------------------------------------------
* Section 3: standard system types
diff --git a/src/include/utils/rel.h b/src/include/utils/rel.h
index b552359915..7346d618f9 100644
--- a/src/include/utils/rel.h
+++ b/src/include/utils/rel.h
@@ -346,8 +346,7 @@ typedef struct StdRdOptions
bool user_catalog_table; /* use as an additional catalog relation */
int parallel_workers; /* max number of parallel workers */
StdRdOptIndexCleanup vacuum_index_cleanup; /* controls index vacuuming */
- bool vacuum_truncate; /* enables vacuum to truncate a relation */
- bool vacuum_truncate_set; /* whether vacuum_truncate is set */
+ ternary vacuum_truncate; /* enables vacuum to truncate a relation */
/*
* Fraction of pages in a relation that vacuum can eagerly scan and fail
--
2.39.2
--nextPart4155600.3ZeAukHxDK
Content-Disposition: attachment;
filename="v2a-0003-Add-alias-to-be-used-as-unset-state.patch"
Content-Transfer-Encoding: 7Bit
Content-Type: text/x-patch; charset="unicode-2-0-utf-8";
name="v2a-0003-Add-alias-to-be-used-as-unset-state.patch"
^ permalink raw reply [nested|flat] 868+ messages in thread
* [PATCH v2 2/4] Introduce ternary reloptions
@ 2025-09-04 14:41 Nikolay Shaplov <[email protected]>
0 siblings, 0 replies; 868+ messages in thread
From: Nikolay Shaplov @ 2025-09-04 14:41 UTC (permalink / raw)
Introduce ternary reloption as a replacement for current `vacuum_truncate`
implementation. Remove `vacuum_truncate_set` additional flag and using
`TERNARY_UNSET` value instead.
---
src/backend/access/common/reloptions.c | 129 ++++++++++++++++++++-----
src/backend/commands/vacuum.c | 4 +-
src/include/access/reloptions.h | 26 ++---
src/include/c.h | 16 +++
src/include/utils/rel.h | 3 +-
5 files changed, 135 insertions(+), 43 deletions(-)
diff --git a/src/backend/access/common/reloptions.c b/src/backend/access/common/reloptions.c
index 0af3fea68f..f543f11001 100644
--- a/src/backend/access/common/reloptions.c
+++ b/src/backend/access/common/reloptions.c
@@ -40,9 +40,9 @@
*
* To add an option:
*
- * (i) decide on a type (bool, integer, real, enum, string), name, default
- * value, upper and lower bounds (if applicable); for strings, consider a
- * validation routine.
+ * (i) decide on a type (bool, ternary, integer, real, enum, string), name,
+ * default value, upper and lower bounds (if applicable); for strings,
+ * consider a validation routine.
* (ii) add a record below (or use add_<type>_reloption).
* (iii) add it to the appropriate options struct (perhaps StdRdOptions)
* (iv) add it to the appropriate handling routine (perhaps
@@ -147,15 +147,6 @@ static relopt_bool boolRelOpts[] =
},
false
},
- {
- {
- "vacuum_truncate",
- "Enables vacuum to truncate empty pages at the end of this table",
- RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
- ShareUpdateExclusiveLock
- },
- true
- },
{
{
"deduplicate_items",
@@ -170,6 +161,21 @@ static relopt_bool boolRelOpts[] =
{{NULL}}
};
+static relopt_ternary ternaryRelOpts[] =
+{
+ {
+ {
+ "vacuum_truncate",
+ "Enables vacuum to truncate empty pages at the end of this table",
+ RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
+ ShareUpdateExclusiveLock
+ },
+ TERNARY_UNSET
+ },
+ /* list terminator */
+ {{NULL}}
+};
+
static relopt_int intRelOpts[] =
{
{
@@ -600,6 +606,13 @@ initialize_reloptions(void)
boolRelOpts[i].gen.lockmode));
j++;
}
+ for (i = 0; ternaryRelOpts[i].gen.name; i++)
+ {
+ Assert(DoLockModesConflict(ternaryRelOpts[i].gen.lockmode,
+ ternaryRelOpts[i].gen.lockmode));
+ j++;
+ }
+
for (i = 0; intRelOpts[i].gen.name; i++)
{
Assert(DoLockModesConflict(intRelOpts[i].gen.lockmode,
@@ -640,6 +653,14 @@ initialize_reloptions(void)
j++;
}
+ for (i = 0; ternaryRelOpts[i].gen.name; i++)
+ {
+ relOpts[j] = &ternaryRelOpts[i].gen;
+ relOpts[j]->type = RELOPT_TYPE_TERNARY;
+ relOpts[j]->namelen = strlen(relOpts[j]->name);
+ j++;
+ }
+
for (i = 0; intRelOpts[i].gen.name; i++)
{
relOpts[j] = &intRelOpts[i].gen;
@@ -800,6 +821,9 @@ allocate_reloption(bits32 kinds, int type, const char *name, const char *desc,
case RELOPT_TYPE_BOOL:
size = sizeof(relopt_bool);
break;
+ case RELOPT_TYPE_TERNARY:
+ size = sizeof(relopt_ternary);
+ break;
case RELOPT_TYPE_INT:
size = sizeof(relopt_int);
break;
@@ -883,6 +907,54 @@ add_local_bool_reloption(local_relopts *relopts, const char *name,
add_local_reloption(relopts, (relopt_gen *) newoption, offset);
}
+/*
+ * init_ternary_reloption
+ * Allocate and initialize a new ternary reloption
+ */
+static relopt_ternary *
+init_ternary_reloption(bits32 kinds, const char *name, const char *desc,
+ ternary default_val, LOCKMODE lockmode)
+{
+ relopt_ternary *newoption;
+
+ newoption = (relopt_ternary *) allocate_reloption(kinds,
+ RELOPT_TYPE_TERNARY, name, desc, lockmode);
+ newoption->default_val = default_val;
+
+ return newoption;
+}
+
+/*
+ * add_ternary_reloption
+ * Add a new ternary reloption
+ */
+void
+add_ternary_reloption(bits32 kinds, const char *name, const char *desc,
+ ternary default_val, LOCKMODE lockmode)
+{
+ relopt_ternary *newoption = init_ternary_reloption(kinds, name, desc,
+ default_val, lockmode);
+
+ add_reloption((relopt_gen *) newoption);
+}
+
+/*
+ * add_local_ternary_reloption
+ * Add a new ternary local reloption
+ *
+ * 'offset' is offset of ternary-typed field.
+ */
+void
+add_local_ternary_reloption(local_relopts *relopts, const char *name,
+ const char *desc, ternary default_val,
+ int offset)
+{
+ relopt_ternary *newoption = init_ternary_reloption(RELOPT_KIND_LOCAL,
+ name, desc,
+ default_val, 0);
+
+ add_local_reloption(relopts, (relopt_gen *) newoption, offset);
+}
/*
* init_real_reloption
@@ -1617,6 +1689,18 @@ parse_one_reloption(relopt_value *option, char *text_str, int text_len,
option->gen->name, value)));
}
break;
+ case RELOPT_TYPE_TERNARY:
+ {
+ bool b;
+ parsed = parse_bool(value, &b);
+ option->values.ternary_val = b ? TERNARY_TRUE : TERNARY_FALSE;
+ if (validate && !parsed)
+ ereport(ERROR,
+ (errcode(ERRCODE_INVALID_PARAMETER_VALUE),
+ errmsg("invalid value for ternary option \"%s\": %s",
+ option->gen->name, value)));
+ }
+ break;
case RELOPT_TYPE_INT:
{
relopt_int *optint = (relopt_int *) option->gen;
@@ -1780,17 +1864,6 @@ fillRelOptions(void *rdopts, Size basesize,
char *itempos = ((char *) rdopts) + elems[j].offset;
char *string_val;
- /*
- * If isset_offset is provided, store whether the reloption is
- * set there.
- */
- if (elems[j].isset_offset > 0)
- {
- char *setpos = ((char *) rdopts) + elems[j].isset_offset;
-
- *(bool *) setpos = options[i].isset;
- }
-
switch (options[i].gen->type)
{
case RELOPT_TYPE_BOOL:
@@ -1798,6 +1871,11 @@ fillRelOptions(void *rdopts, Size basesize,
options[i].values.bool_val :
((relopt_bool *) options[i].gen)->default_val;
break;
+ case RELOPT_TYPE_TERNARY:
+ *(ternary *) itempos = options[i].isset ?
+ options[i].values.ternary_val :
+ ((relopt_ternary *) options[i].gen)->default_val;
+ break;
case RELOPT_TYPE_INT:
*(int *) itempos = options[i].isset ?
options[i].values.int_val :
@@ -1912,8 +1990,8 @@ default_reloptions(Datum reloptions, bool validate, relopt_kind kind)
offsetof(StdRdOptions, parallel_workers)},
{"vacuum_index_cleanup", RELOPT_TYPE_ENUM,
offsetof(StdRdOptions, vacuum_index_cleanup)},
- {"vacuum_truncate", RELOPT_TYPE_BOOL,
- offsetof(StdRdOptions, vacuum_truncate), offsetof(StdRdOptions, vacuum_truncate_set)},
+ {"vacuum_truncate", RELOPT_TYPE_TERNARY,
+ offsetof(StdRdOptions, vacuum_truncate)},
{"vacuum_max_eager_freeze_failure_rate", RELOPT_TYPE_REAL,
offsetof(StdRdOptions, vacuum_max_eager_freeze_failure_rate)}
};
@@ -1993,7 +2071,6 @@ build_local_reloptions(local_relopts *relopts, Datum options, bool validate)
elems[i].optname = opt->option->name;
elems[i].opttype = opt->option->type;
elems[i].offset = opt->offset;
- elems[i].isset_offset = 0; /* not supported for local relopts yet */
i++;
}
diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c
index 733ef40ae7..977babff54 100644
--- a/src/backend/commands/vacuum.c
+++ b/src/backend/commands/vacuum.c
@@ -2221,9 +2221,9 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams params,
{
StdRdOptions *opts = (StdRdOptions *) rel->rd_options;
- if (opts && opts->vacuum_truncate_set)
+ if (opts && opts->vacuum_truncate != TERNARY_UNSET)
{
- if (opts->vacuum_truncate)
+ if (opts->vacuum_truncate == TERNARY_TRUE)
params.truncate = VACOPTVALUE_ENABLED;
else
params.truncate = VACOPTVALUE_DISABLED;
diff --git a/src/include/access/reloptions.h b/src/include/access/reloptions.h
index a604a4702c..a436697658 100644
--- a/src/include/access/reloptions.h
+++ b/src/include/access/reloptions.h
@@ -29,6 +29,7 @@
typedef enum relopt_type
{
RELOPT_TYPE_BOOL,
+ RELOPT_TYPE_TERNARY, /* on, off, unset */
RELOPT_TYPE_INT,
RELOPT_TYPE_REAL,
RELOPT_TYPE_ENUM,
@@ -80,6 +81,7 @@ typedef struct relopt_value
union
{
bool bool_val;
+ ternary ternary_val;
int int_val;
double real_val;
int enum_val;
@@ -94,6 +96,12 @@ typedef struct relopt_bool
bool default_val;
} relopt_bool;
+typedef struct relopt_rernary
+{
+ relopt_gen gen;
+ int default_val;
+} relopt_ternary;
+
typedef struct relopt_int
{
relopt_gen gen;
@@ -152,19 +160,6 @@ typedef struct
const char *optname; /* option's name */
relopt_type opttype; /* option's datatype */
int offset; /* offset of field in result struct */
-
- /*
- * isset_offset is an optional offset of a field in the result struct that
- * stores whether the option is explicitly set for the relation or if it
- * just picked up the default value. In most cases, this can be
- * accomplished by giving the reloption a special out-of-range default
- * value (e.g., some integer reloptions use -2), but this isn't always
- * possible. For example, a Boolean reloption cannot be given an
- * out-of-range default, so we need another way to discover the source of
- * its value. This offset is only used if given a value greater than
- * zero.
- */
- int isset_offset;
} relopt_parse_elt;
/* Local reloption definition */
@@ -195,6 +190,8 @@ typedef struct local_relopts
extern relopt_kind add_reloption_kind(void);
extern void add_bool_reloption(bits32 kinds, const char *name, const char *desc,
bool default_val, LOCKMODE lockmode);
+extern void add_ternary_reloption(bits32 kinds, const char *name,
+ const char *desc, int default_val, LOCKMODE lockmode);
extern void add_int_reloption(bits32 kinds, const char *name, const char *desc,
int default_val, int min_val, int max_val,
LOCKMODE lockmode);
@@ -214,6 +211,9 @@ extern void register_reloptions_validator(local_relopts *relopts,
extern void add_local_bool_reloption(local_relopts *relopts, const char *name,
const char *desc, bool default_val,
int offset);
+extern void add_local_ternary_reloption(local_relopts *relopts,
+ const char *name, const char *desc,
+ ternary default_val, int offset);
extern void add_local_int_reloption(local_relopts *relopts, const char *name,
const char *desc, int default_val,
int min_val, int max_val, int offset);
diff --git a/src/include/c.h b/src/include/c.h
index 39022f8a9d..9c59ccccf8 100644
--- a/src/include/c.h
+++ b/src/include/c.h
@@ -488,6 +488,22 @@ typedef void (*pg_funcptr_t) (void);
#include <stdbool.h>
+/*
+ * ternary
+ * Boolean value with an extrea "unset" option
+ *
+ * Ternary data type is used in relation options that can be "true", "false" or
+ * "unset". Since relation options are used deep inside the PostgreSQL code,
+ * this type is declared globally.
+*/
+
+typedef enum ternary
+{
+ TERNARY_FALSE = 0,
+ TERNARY_TRUE = 1,
+ TERNARY_UNSET = -1
+} ternary;
+
/* ----------------------------------------------------------------
* Section 3: standard system types
diff --git a/src/include/utils/rel.h b/src/include/utils/rel.h
index b552359915..7346d618f9 100644
--- a/src/include/utils/rel.h
+++ b/src/include/utils/rel.h
@@ -346,8 +346,7 @@ typedef struct StdRdOptions
bool user_catalog_table; /* use as an additional catalog relation */
int parallel_workers; /* max number of parallel workers */
StdRdOptIndexCleanup vacuum_index_cleanup; /* controls index vacuuming */
- bool vacuum_truncate; /* enables vacuum to truncate a relation */
- bool vacuum_truncate_set; /* whether vacuum_truncate is set */
+ ternary vacuum_truncate; /* enables vacuum to truncate a relation */
/*
* Fraction of pages in a relation that vacuum can eagerly scan and fail
--
2.39.2
--nextPart5129932.5fSG56mABF
Content-Disposition: attachment;
filename="v2-0003-Add-alias-to-be-used-as-unset-state.patch"
Content-Transfer-Encoding: 7Bit
Content-Type: text/x-patch; charset="unicode-2-0-utf-8";
name="v2-0003-Add-alias-to-be-used-as-unset-state.patch"
^ permalink raw reply [nested|flat] 868+ messages in thread
* [PATCH v2a 2/4] Introduce ternary reloptions
@ 2025-09-04 14:41 Nikolay Shaplov <[email protected]>
0 siblings, 0 replies; 868+ messages in thread
From: Nikolay Shaplov @ 2025-09-04 14:41 UTC (permalink / raw)
Introduce ternary reloption as a replacement for current `vacuum_truncate`
implementation. Remove `vacuum_truncate_set` additional flag and using
`TERNARY_UNSET` value instead.
---
src/backend/access/common/reloptions.c | 129 ++++++++++++++++++++-----
src/backend/commands/vacuum.c | 4 +-
src/include/access/reloptions.h | 26 ++---
src/include/c.h | 16 +++
src/include/utils/rel.h | 3 +-
5 files changed, 135 insertions(+), 43 deletions(-)
diff --git a/src/backend/access/common/reloptions.c b/src/backend/access/common/reloptions.c
index 0af3fea68f..f543f11001 100644
--- a/src/backend/access/common/reloptions.c
+++ b/src/backend/access/common/reloptions.c
@@ -40,9 +40,9 @@
*
* To add an option:
*
- * (i) decide on a type (bool, integer, real, enum, string), name, default
- * value, upper and lower bounds (if applicable); for strings, consider a
- * validation routine.
+ * (i) decide on a type (bool, ternary, integer, real, enum, string), name,
+ * default value, upper and lower bounds (if applicable); for strings,
+ * consider a validation routine.
* (ii) add a record below (or use add_<type>_reloption).
* (iii) add it to the appropriate options struct (perhaps StdRdOptions)
* (iv) add it to the appropriate handling routine (perhaps
@@ -147,15 +147,6 @@ static relopt_bool boolRelOpts[] =
},
false
},
- {
- {
- "vacuum_truncate",
- "Enables vacuum to truncate empty pages at the end of this table",
- RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
- ShareUpdateExclusiveLock
- },
- true
- },
{
{
"deduplicate_items",
@@ -170,6 +161,21 @@ static relopt_bool boolRelOpts[] =
{{NULL}}
};
+static relopt_ternary ternaryRelOpts[] =
+{
+ {
+ {
+ "vacuum_truncate",
+ "Enables vacuum to truncate empty pages at the end of this table",
+ RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
+ ShareUpdateExclusiveLock
+ },
+ TERNARY_UNSET
+ },
+ /* list terminator */
+ {{NULL}}
+};
+
static relopt_int intRelOpts[] =
{
{
@@ -600,6 +606,13 @@ initialize_reloptions(void)
boolRelOpts[i].gen.lockmode));
j++;
}
+ for (i = 0; ternaryRelOpts[i].gen.name; i++)
+ {
+ Assert(DoLockModesConflict(ternaryRelOpts[i].gen.lockmode,
+ ternaryRelOpts[i].gen.lockmode));
+ j++;
+ }
+
for (i = 0; intRelOpts[i].gen.name; i++)
{
Assert(DoLockModesConflict(intRelOpts[i].gen.lockmode,
@@ -640,6 +653,14 @@ initialize_reloptions(void)
j++;
}
+ for (i = 0; ternaryRelOpts[i].gen.name; i++)
+ {
+ relOpts[j] = &ternaryRelOpts[i].gen;
+ relOpts[j]->type = RELOPT_TYPE_TERNARY;
+ relOpts[j]->namelen = strlen(relOpts[j]->name);
+ j++;
+ }
+
for (i = 0; intRelOpts[i].gen.name; i++)
{
relOpts[j] = &intRelOpts[i].gen;
@@ -800,6 +821,9 @@ allocate_reloption(bits32 kinds, int type, const char *name, const char *desc,
case RELOPT_TYPE_BOOL:
size = sizeof(relopt_bool);
break;
+ case RELOPT_TYPE_TERNARY:
+ size = sizeof(relopt_ternary);
+ break;
case RELOPT_TYPE_INT:
size = sizeof(relopt_int);
break;
@@ -883,6 +907,54 @@ add_local_bool_reloption(local_relopts *relopts, const char *name,
add_local_reloption(relopts, (relopt_gen *) newoption, offset);
}
+/*
+ * init_ternary_reloption
+ * Allocate and initialize a new ternary reloption
+ */
+static relopt_ternary *
+init_ternary_reloption(bits32 kinds, const char *name, const char *desc,
+ ternary default_val, LOCKMODE lockmode)
+{
+ relopt_ternary *newoption;
+
+ newoption = (relopt_ternary *) allocate_reloption(kinds,
+ RELOPT_TYPE_TERNARY, name, desc, lockmode);
+ newoption->default_val = default_val;
+
+ return newoption;
+}
+
+/*
+ * add_ternary_reloption
+ * Add a new ternary reloption
+ */
+void
+add_ternary_reloption(bits32 kinds, const char *name, const char *desc,
+ ternary default_val, LOCKMODE lockmode)
+{
+ relopt_ternary *newoption = init_ternary_reloption(kinds, name, desc,
+ default_val, lockmode);
+
+ add_reloption((relopt_gen *) newoption);
+}
+
+/*
+ * add_local_ternary_reloption
+ * Add a new ternary local reloption
+ *
+ * 'offset' is offset of ternary-typed field.
+ */
+void
+add_local_ternary_reloption(local_relopts *relopts, const char *name,
+ const char *desc, ternary default_val,
+ int offset)
+{
+ relopt_ternary *newoption = init_ternary_reloption(RELOPT_KIND_LOCAL,
+ name, desc,
+ default_val, 0);
+
+ add_local_reloption(relopts, (relopt_gen *) newoption, offset);
+}
/*
* init_real_reloption
@@ -1617,6 +1689,18 @@ parse_one_reloption(relopt_value *option, char *text_str, int text_len,
option->gen->name, value)));
}
break;
+ case RELOPT_TYPE_TERNARY:
+ {
+ bool b;
+ parsed = parse_bool(value, &b);
+ option->values.ternary_val = b ? TERNARY_TRUE : TERNARY_FALSE;
+ if (validate && !parsed)
+ ereport(ERROR,
+ (errcode(ERRCODE_INVALID_PARAMETER_VALUE),
+ errmsg("invalid value for ternary option \"%s\": %s",
+ option->gen->name, value)));
+ }
+ break;
case RELOPT_TYPE_INT:
{
relopt_int *optint = (relopt_int *) option->gen;
@@ -1780,17 +1864,6 @@ fillRelOptions(void *rdopts, Size basesize,
char *itempos = ((char *) rdopts) + elems[j].offset;
char *string_val;
- /*
- * If isset_offset is provided, store whether the reloption is
- * set there.
- */
- if (elems[j].isset_offset > 0)
- {
- char *setpos = ((char *) rdopts) + elems[j].isset_offset;
-
- *(bool *) setpos = options[i].isset;
- }
-
switch (options[i].gen->type)
{
case RELOPT_TYPE_BOOL:
@@ -1798,6 +1871,11 @@ fillRelOptions(void *rdopts, Size basesize,
options[i].values.bool_val :
((relopt_bool *) options[i].gen)->default_val;
break;
+ case RELOPT_TYPE_TERNARY:
+ *(ternary *) itempos = options[i].isset ?
+ options[i].values.ternary_val :
+ ((relopt_ternary *) options[i].gen)->default_val;
+ break;
case RELOPT_TYPE_INT:
*(int *) itempos = options[i].isset ?
options[i].values.int_val :
@@ -1912,8 +1990,8 @@ default_reloptions(Datum reloptions, bool validate, relopt_kind kind)
offsetof(StdRdOptions, parallel_workers)},
{"vacuum_index_cleanup", RELOPT_TYPE_ENUM,
offsetof(StdRdOptions, vacuum_index_cleanup)},
- {"vacuum_truncate", RELOPT_TYPE_BOOL,
- offsetof(StdRdOptions, vacuum_truncate), offsetof(StdRdOptions, vacuum_truncate_set)},
+ {"vacuum_truncate", RELOPT_TYPE_TERNARY,
+ offsetof(StdRdOptions, vacuum_truncate)},
{"vacuum_max_eager_freeze_failure_rate", RELOPT_TYPE_REAL,
offsetof(StdRdOptions, vacuum_max_eager_freeze_failure_rate)}
};
@@ -1993,7 +2071,6 @@ build_local_reloptions(local_relopts *relopts, Datum options, bool validate)
elems[i].optname = opt->option->name;
elems[i].opttype = opt->option->type;
elems[i].offset = opt->offset;
- elems[i].isset_offset = 0; /* not supported for local relopts yet */
i++;
}
diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c
index 733ef40ae7..977babff54 100644
--- a/src/backend/commands/vacuum.c
+++ b/src/backend/commands/vacuum.c
@@ -2221,9 +2221,9 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams params,
{
StdRdOptions *opts = (StdRdOptions *) rel->rd_options;
- if (opts && opts->vacuum_truncate_set)
+ if (opts && opts->vacuum_truncate != TERNARY_UNSET)
{
- if (opts->vacuum_truncate)
+ if (opts->vacuum_truncate == TERNARY_TRUE)
params.truncate = VACOPTVALUE_ENABLED;
else
params.truncate = VACOPTVALUE_DISABLED;
diff --git a/src/include/access/reloptions.h b/src/include/access/reloptions.h
index a604a4702c..a436697658 100644
--- a/src/include/access/reloptions.h
+++ b/src/include/access/reloptions.h
@@ -29,6 +29,7 @@
typedef enum relopt_type
{
RELOPT_TYPE_BOOL,
+ RELOPT_TYPE_TERNARY, /* on, off, unset */
RELOPT_TYPE_INT,
RELOPT_TYPE_REAL,
RELOPT_TYPE_ENUM,
@@ -80,6 +81,7 @@ typedef struct relopt_value
union
{
bool bool_val;
+ ternary ternary_val;
int int_val;
double real_val;
int enum_val;
@@ -94,6 +96,12 @@ typedef struct relopt_bool
bool default_val;
} relopt_bool;
+typedef struct relopt_rernary
+{
+ relopt_gen gen;
+ int default_val;
+} relopt_ternary;
+
typedef struct relopt_int
{
relopt_gen gen;
@@ -152,19 +160,6 @@ typedef struct
const char *optname; /* option's name */
relopt_type opttype; /* option's datatype */
int offset; /* offset of field in result struct */
-
- /*
- * isset_offset is an optional offset of a field in the result struct that
- * stores whether the option is explicitly set for the relation or if it
- * just picked up the default value. In most cases, this can be
- * accomplished by giving the reloption a special out-of-range default
- * value (e.g., some integer reloptions use -2), but this isn't always
- * possible. For example, a Boolean reloption cannot be given an
- * out-of-range default, so we need another way to discover the source of
- * its value. This offset is only used if given a value greater than
- * zero.
- */
- int isset_offset;
} relopt_parse_elt;
/* Local reloption definition */
@@ -195,6 +190,8 @@ typedef struct local_relopts
extern relopt_kind add_reloption_kind(void);
extern void add_bool_reloption(bits32 kinds, const char *name, const char *desc,
bool default_val, LOCKMODE lockmode);
+extern void add_ternary_reloption(bits32 kinds, const char *name,
+ const char *desc, int default_val, LOCKMODE lockmode);
extern void add_int_reloption(bits32 kinds, const char *name, const char *desc,
int default_val, int min_val, int max_val,
LOCKMODE lockmode);
@@ -214,6 +211,9 @@ extern void register_reloptions_validator(local_relopts *relopts,
extern void add_local_bool_reloption(local_relopts *relopts, const char *name,
const char *desc, bool default_val,
int offset);
+extern void add_local_ternary_reloption(local_relopts *relopts,
+ const char *name, const char *desc,
+ ternary default_val, int offset);
extern void add_local_int_reloption(local_relopts *relopts, const char *name,
const char *desc, int default_val,
int min_val, int max_val, int offset);
diff --git a/src/include/c.h b/src/include/c.h
index 39022f8a9d..9c59ccccf8 100644
--- a/src/include/c.h
+++ b/src/include/c.h
@@ -488,6 +488,22 @@ typedef void (*pg_funcptr_t) (void);
#include <stdbool.h>
+/*
+ * ternary
+ * Boolean value with an extrea "unset" option
+ *
+ * Ternary data type is used in relation options that can be "true", "false" or
+ * "unset". Since relation options are used deep inside the PostgreSQL code,
+ * this type is declared globally.
+*/
+
+typedef enum ternary
+{
+ TERNARY_FALSE = 0,
+ TERNARY_TRUE = 1,
+ TERNARY_UNSET = -1
+} ternary;
+
/* ----------------------------------------------------------------
* Section 3: standard system types
diff --git a/src/include/utils/rel.h b/src/include/utils/rel.h
index b552359915..7346d618f9 100644
--- a/src/include/utils/rel.h
+++ b/src/include/utils/rel.h
@@ -346,8 +346,7 @@ typedef struct StdRdOptions
bool user_catalog_table; /* use as an additional catalog relation */
int parallel_workers; /* max number of parallel workers */
StdRdOptIndexCleanup vacuum_index_cleanup; /* controls index vacuuming */
- bool vacuum_truncate; /* enables vacuum to truncate a relation */
- bool vacuum_truncate_set; /* whether vacuum_truncate is set */
+ ternary vacuum_truncate; /* enables vacuum to truncate a relation */
/*
* Fraction of pages in a relation that vacuum can eagerly scan and fail
--
2.39.2
--nextPart4155600.3ZeAukHxDK
Content-Disposition: attachment;
filename="v2a-0003-Add-alias-to-be-used-as-unset-state.patch"
Content-Transfer-Encoding: 7Bit
Content-Type: text/x-patch; charset="unicode-2-0-utf-8";
name="v2a-0003-Add-alias-to-be-used-as-unset-state.patch"
^ permalink raw reply [nested|flat] 868+ messages in thread
* [PATCH v2 2/4] Introduce ternary reloptions
@ 2025-09-04 14:41 Nikolay Shaplov <[email protected]>
0 siblings, 0 replies; 868+ messages in thread
From: Nikolay Shaplov @ 2025-09-04 14:41 UTC (permalink / raw)
Introduce ternary reloption as a replacement for current `vacuum_truncate`
implementation. Remove `vacuum_truncate_set` additional flag and using
`TERNARY_UNSET` value instead.
---
src/backend/access/common/reloptions.c | 129 ++++++++++++++++++++-----
src/backend/commands/vacuum.c | 4 +-
src/include/access/reloptions.h | 26 ++---
src/include/c.h | 16 +++
src/include/utils/rel.h | 3 +-
5 files changed, 135 insertions(+), 43 deletions(-)
diff --git a/src/backend/access/common/reloptions.c b/src/backend/access/common/reloptions.c
index 0af3fea68f..f543f11001 100644
--- a/src/backend/access/common/reloptions.c
+++ b/src/backend/access/common/reloptions.c
@@ -40,9 +40,9 @@
*
* To add an option:
*
- * (i) decide on a type (bool, integer, real, enum, string), name, default
- * value, upper and lower bounds (if applicable); for strings, consider a
- * validation routine.
+ * (i) decide on a type (bool, ternary, integer, real, enum, string), name,
+ * default value, upper and lower bounds (if applicable); for strings,
+ * consider a validation routine.
* (ii) add a record below (or use add_<type>_reloption).
* (iii) add it to the appropriate options struct (perhaps StdRdOptions)
* (iv) add it to the appropriate handling routine (perhaps
@@ -147,15 +147,6 @@ static relopt_bool boolRelOpts[] =
},
false
},
- {
- {
- "vacuum_truncate",
- "Enables vacuum to truncate empty pages at the end of this table",
- RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
- ShareUpdateExclusiveLock
- },
- true
- },
{
{
"deduplicate_items",
@@ -170,6 +161,21 @@ static relopt_bool boolRelOpts[] =
{{NULL}}
};
+static relopt_ternary ternaryRelOpts[] =
+{
+ {
+ {
+ "vacuum_truncate",
+ "Enables vacuum to truncate empty pages at the end of this table",
+ RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
+ ShareUpdateExclusiveLock
+ },
+ TERNARY_UNSET
+ },
+ /* list terminator */
+ {{NULL}}
+};
+
static relopt_int intRelOpts[] =
{
{
@@ -600,6 +606,13 @@ initialize_reloptions(void)
boolRelOpts[i].gen.lockmode));
j++;
}
+ for (i = 0; ternaryRelOpts[i].gen.name; i++)
+ {
+ Assert(DoLockModesConflict(ternaryRelOpts[i].gen.lockmode,
+ ternaryRelOpts[i].gen.lockmode));
+ j++;
+ }
+
for (i = 0; intRelOpts[i].gen.name; i++)
{
Assert(DoLockModesConflict(intRelOpts[i].gen.lockmode,
@@ -640,6 +653,14 @@ initialize_reloptions(void)
j++;
}
+ for (i = 0; ternaryRelOpts[i].gen.name; i++)
+ {
+ relOpts[j] = &ternaryRelOpts[i].gen;
+ relOpts[j]->type = RELOPT_TYPE_TERNARY;
+ relOpts[j]->namelen = strlen(relOpts[j]->name);
+ j++;
+ }
+
for (i = 0; intRelOpts[i].gen.name; i++)
{
relOpts[j] = &intRelOpts[i].gen;
@@ -800,6 +821,9 @@ allocate_reloption(bits32 kinds, int type, const char *name, const char *desc,
case RELOPT_TYPE_BOOL:
size = sizeof(relopt_bool);
break;
+ case RELOPT_TYPE_TERNARY:
+ size = sizeof(relopt_ternary);
+ break;
case RELOPT_TYPE_INT:
size = sizeof(relopt_int);
break;
@@ -883,6 +907,54 @@ add_local_bool_reloption(local_relopts *relopts, const char *name,
add_local_reloption(relopts, (relopt_gen *) newoption, offset);
}
+/*
+ * init_ternary_reloption
+ * Allocate and initialize a new ternary reloption
+ */
+static relopt_ternary *
+init_ternary_reloption(bits32 kinds, const char *name, const char *desc,
+ ternary default_val, LOCKMODE lockmode)
+{
+ relopt_ternary *newoption;
+
+ newoption = (relopt_ternary *) allocate_reloption(kinds,
+ RELOPT_TYPE_TERNARY, name, desc, lockmode);
+ newoption->default_val = default_val;
+
+ return newoption;
+}
+
+/*
+ * add_ternary_reloption
+ * Add a new ternary reloption
+ */
+void
+add_ternary_reloption(bits32 kinds, const char *name, const char *desc,
+ ternary default_val, LOCKMODE lockmode)
+{
+ relopt_ternary *newoption = init_ternary_reloption(kinds, name, desc,
+ default_val, lockmode);
+
+ add_reloption((relopt_gen *) newoption);
+}
+
+/*
+ * add_local_ternary_reloption
+ * Add a new ternary local reloption
+ *
+ * 'offset' is offset of ternary-typed field.
+ */
+void
+add_local_ternary_reloption(local_relopts *relopts, const char *name,
+ const char *desc, ternary default_val,
+ int offset)
+{
+ relopt_ternary *newoption = init_ternary_reloption(RELOPT_KIND_LOCAL,
+ name, desc,
+ default_val, 0);
+
+ add_local_reloption(relopts, (relopt_gen *) newoption, offset);
+}
/*
* init_real_reloption
@@ -1617,6 +1689,18 @@ parse_one_reloption(relopt_value *option, char *text_str, int text_len,
option->gen->name, value)));
}
break;
+ case RELOPT_TYPE_TERNARY:
+ {
+ bool b;
+ parsed = parse_bool(value, &b);
+ option->values.ternary_val = b ? TERNARY_TRUE : TERNARY_FALSE;
+ if (validate && !parsed)
+ ereport(ERROR,
+ (errcode(ERRCODE_INVALID_PARAMETER_VALUE),
+ errmsg("invalid value for ternary option \"%s\": %s",
+ option->gen->name, value)));
+ }
+ break;
case RELOPT_TYPE_INT:
{
relopt_int *optint = (relopt_int *) option->gen;
@@ -1780,17 +1864,6 @@ fillRelOptions(void *rdopts, Size basesize,
char *itempos = ((char *) rdopts) + elems[j].offset;
char *string_val;
- /*
- * If isset_offset is provided, store whether the reloption is
- * set there.
- */
- if (elems[j].isset_offset > 0)
- {
- char *setpos = ((char *) rdopts) + elems[j].isset_offset;
-
- *(bool *) setpos = options[i].isset;
- }
-
switch (options[i].gen->type)
{
case RELOPT_TYPE_BOOL:
@@ -1798,6 +1871,11 @@ fillRelOptions(void *rdopts, Size basesize,
options[i].values.bool_val :
((relopt_bool *) options[i].gen)->default_val;
break;
+ case RELOPT_TYPE_TERNARY:
+ *(ternary *) itempos = options[i].isset ?
+ options[i].values.ternary_val :
+ ((relopt_ternary *) options[i].gen)->default_val;
+ break;
case RELOPT_TYPE_INT:
*(int *) itempos = options[i].isset ?
options[i].values.int_val :
@@ -1912,8 +1990,8 @@ default_reloptions(Datum reloptions, bool validate, relopt_kind kind)
offsetof(StdRdOptions, parallel_workers)},
{"vacuum_index_cleanup", RELOPT_TYPE_ENUM,
offsetof(StdRdOptions, vacuum_index_cleanup)},
- {"vacuum_truncate", RELOPT_TYPE_BOOL,
- offsetof(StdRdOptions, vacuum_truncate), offsetof(StdRdOptions, vacuum_truncate_set)},
+ {"vacuum_truncate", RELOPT_TYPE_TERNARY,
+ offsetof(StdRdOptions, vacuum_truncate)},
{"vacuum_max_eager_freeze_failure_rate", RELOPT_TYPE_REAL,
offsetof(StdRdOptions, vacuum_max_eager_freeze_failure_rate)}
};
@@ -1993,7 +2071,6 @@ build_local_reloptions(local_relopts *relopts, Datum options, bool validate)
elems[i].optname = opt->option->name;
elems[i].opttype = opt->option->type;
elems[i].offset = opt->offset;
- elems[i].isset_offset = 0; /* not supported for local relopts yet */
i++;
}
diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c
index 733ef40ae7..977babff54 100644
--- a/src/backend/commands/vacuum.c
+++ b/src/backend/commands/vacuum.c
@@ -2221,9 +2221,9 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams params,
{
StdRdOptions *opts = (StdRdOptions *) rel->rd_options;
- if (opts && opts->vacuum_truncate_set)
+ if (opts && opts->vacuum_truncate != TERNARY_UNSET)
{
- if (opts->vacuum_truncate)
+ if (opts->vacuum_truncate == TERNARY_TRUE)
params.truncate = VACOPTVALUE_ENABLED;
else
params.truncate = VACOPTVALUE_DISABLED;
diff --git a/src/include/access/reloptions.h b/src/include/access/reloptions.h
index a604a4702c..a436697658 100644
--- a/src/include/access/reloptions.h
+++ b/src/include/access/reloptions.h
@@ -29,6 +29,7 @@
typedef enum relopt_type
{
RELOPT_TYPE_BOOL,
+ RELOPT_TYPE_TERNARY, /* on, off, unset */
RELOPT_TYPE_INT,
RELOPT_TYPE_REAL,
RELOPT_TYPE_ENUM,
@@ -80,6 +81,7 @@ typedef struct relopt_value
union
{
bool bool_val;
+ ternary ternary_val;
int int_val;
double real_val;
int enum_val;
@@ -94,6 +96,12 @@ typedef struct relopt_bool
bool default_val;
} relopt_bool;
+typedef struct relopt_rernary
+{
+ relopt_gen gen;
+ int default_val;
+} relopt_ternary;
+
typedef struct relopt_int
{
relopt_gen gen;
@@ -152,19 +160,6 @@ typedef struct
const char *optname; /* option's name */
relopt_type opttype; /* option's datatype */
int offset; /* offset of field in result struct */
-
- /*
- * isset_offset is an optional offset of a field in the result struct that
- * stores whether the option is explicitly set for the relation or if it
- * just picked up the default value. In most cases, this can be
- * accomplished by giving the reloption a special out-of-range default
- * value (e.g., some integer reloptions use -2), but this isn't always
- * possible. For example, a Boolean reloption cannot be given an
- * out-of-range default, so we need another way to discover the source of
- * its value. This offset is only used if given a value greater than
- * zero.
- */
- int isset_offset;
} relopt_parse_elt;
/* Local reloption definition */
@@ -195,6 +190,8 @@ typedef struct local_relopts
extern relopt_kind add_reloption_kind(void);
extern void add_bool_reloption(bits32 kinds, const char *name, const char *desc,
bool default_val, LOCKMODE lockmode);
+extern void add_ternary_reloption(bits32 kinds, const char *name,
+ const char *desc, int default_val, LOCKMODE lockmode);
extern void add_int_reloption(bits32 kinds, const char *name, const char *desc,
int default_val, int min_val, int max_val,
LOCKMODE lockmode);
@@ -214,6 +211,9 @@ extern void register_reloptions_validator(local_relopts *relopts,
extern void add_local_bool_reloption(local_relopts *relopts, const char *name,
const char *desc, bool default_val,
int offset);
+extern void add_local_ternary_reloption(local_relopts *relopts,
+ const char *name, const char *desc,
+ ternary default_val, int offset);
extern void add_local_int_reloption(local_relopts *relopts, const char *name,
const char *desc, int default_val,
int min_val, int max_val, int offset);
diff --git a/src/include/c.h b/src/include/c.h
index 39022f8a9d..9c59ccccf8 100644
--- a/src/include/c.h
+++ b/src/include/c.h
@@ -488,6 +488,22 @@ typedef void (*pg_funcptr_t) (void);
#include <stdbool.h>
+/*
+ * ternary
+ * Boolean value with an extrea "unset" option
+ *
+ * Ternary data type is used in relation options that can be "true", "false" or
+ * "unset". Since relation options are used deep inside the PostgreSQL code,
+ * this type is declared globally.
+*/
+
+typedef enum ternary
+{
+ TERNARY_FALSE = 0,
+ TERNARY_TRUE = 1,
+ TERNARY_UNSET = -1
+} ternary;
+
/* ----------------------------------------------------------------
* Section 3: standard system types
diff --git a/src/include/utils/rel.h b/src/include/utils/rel.h
index b552359915..7346d618f9 100644
--- a/src/include/utils/rel.h
+++ b/src/include/utils/rel.h
@@ -346,8 +346,7 @@ typedef struct StdRdOptions
bool user_catalog_table; /* use as an additional catalog relation */
int parallel_workers; /* max number of parallel workers */
StdRdOptIndexCleanup vacuum_index_cleanup; /* controls index vacuuming */
- bool vacuum_truncate; /* enables vacuum to truncate a relation */
- bool vacuum_truncate_set; /* whether vacuum_truncate is set */
+ ternary vacuum_truncate; /* enables vacuum to truncate a relation */
/*
* Fraction of pages in a relation that vacuum can eagerly scan and fail
--
2.39.2
--nextPart5129932.5fSG56mABF
Content-Disposition: attachment;
filename="v2-0003-Add-alias-to-be-used-as-unset-state.patch"
Content-Transfer-Encoding: 7Bit
Content-Type: text/x-patch; charset="unicode-2-0-utf-8";
name="v2-0003-Add-alias-to-be-used-as-unset-state.patch"
^ permalink raw reply [nested|flat] 868+ messages in thread
* [PATCH v2 2/4] Introduce ternary reloptions
@ 2025-09-04 14:41 Nikolay Shaplov <[email protected]>
0 siblings, 0 replies; 868+ messages in thread
From: Nikolay Shaplov @ 2025-09-04 14:41 UTC (permalink / raw)
Introduce ternary reloption as a replacement for current `vacuum_truncate`
implementation. Remove `vacuum_truncate_set` additional flag and using
`TERNARY_UNSET` value instead.
---
src/backend/access/common/reloptions.c | 129 ++++++++++++++++++++-----
src/backend/commands/vacuum.c | 4 +-
src/include/access/reloptions.h | 26 ++---
src/include/c.h | 16 +++
src/include/utils/rel.h | 3 +-
5 files changed, 135 insertions(+), 43 deletions(-)
diff --git a/src/backend/access/common/reloptions.c b/src/backend/access/common/reloptions.c
index 0af3fea68f..f543f11001 100644
--- a/src/backend/access/common/reloptions.c
+++ b/src/backend/access/common/reloptions.c
@@ -40,9 +40,9 @@
*
* To add an option:
*
- * (i) decide on a type (bool, integer, real, enum, string), name, default
- * value, upper and lower bounds (if applicable); for strings, consider a
- * validation routine.
+ * (i) decide on a type (bool, ternary, integer, real, enum, string), name,
+ * default value, upper and lower bounds (if applicable); for strings,
+ * consider a validation routine.
* (ii) add a record below (or use add_<type>_reloption).
* (iii) add it to the appropriate options struct (perhaps StdRdOptions)
* (iv) add it to the appropriate handling routine (perhaps
@@ -147,15 +147,6 @@ static relopt_bool boolRelOpts[] =
},
false
},
- {
- {
- "vacuum_truncate",
- "Enables vacuum to truncate empty pages at the end of this table",
- RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
- ShareUpdateExclusiveLock
- },
- true
- },
{
{
"deduplicate_items",
@@ -170,6 +161,21 @@ static relopt_bool boolRelOpts[] =
{{NULL}}
};
+static relopt_ternary ternaryRelOpts[] =
+{
+ {
+ {
+ "vacuum_truncate",
+ "Enables vacuum to truncate empty pages at the end of this table",
+ RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
+ ShareUpdateExclusiveLock
+ },
+ TERNARY_UNSET
+ },
+ /* list terminator */
+ {{NULL}}
+};
+
static relopt_int intRelOpts[] =
{
{
@@ -600,6 +606,13 @@ initialize_reloptions(void)
boolRelOpts[i].gen.lockmode));
j++;
}
+ for (i = 0; ternaryRelOpts[i].gen.name; i++)
+ {
+ Assert(DoLockModesConflict(ternaryRelOpts[i].gen.lockmode,
+ ternaryRelOpts[i].gen.lockmode));
+ j++;
+ }
+
for (i = 0; intRelOpts[i].gen.name; i++)
{
Assert(DoLockModesConflict(intRelOpts[i].gen.lockmode,
@@ -640,6 +653,14 @@ initialize_reloptions(void)
j++;
}
+ for (i = 0; ternaryRelOpts[i].gen.name; i++)
+ {
+ relOpts[j] = &ternaryRelOpts[i].gen;
+ relOpts[j]->type = RELOPT_TYPE_TERNARY;
+ relOpts[j]->namelen = strlen(relOpts[j]->name);
+ j++;
+ }
+
for (i = 0; intRelOpts[i].gen.name; i++)
{
relOpts[j] = &intRelOpts[i].gen;
@@ -800,6 +821,9 @@ allocate_reloption(bits32 kinds, int type, const char *name, const char *desc,
case RELOPT_TYPE_BOOL:
size = sizeof(relopt_bool);
break;
+ case RELOPT_TYPE_TERNARY:
+ size = sizeof(relopt_ternary);
+ break;
case RELOPT_TYPE_INT:
size = sizeof(relopt_int);
break;
@@ -883,6 +907,54 @@ add_local_bool_reloption(local_relopts *relopts, const char *name,
add_local_reloption(relopts, (relopt_gen *) newoption, offset);
}
+/*
+ * init_ternary_reloption
+ * Allocate and initialize a new ternary reloption
+ */
+static relopt_ternary *
+init_ternary_reloption(bits32 kinds, const char *name, const char *desc,
+ ternary default_val, LOCKMODE lockmode)
+{
+ relopt_ternary *newoption;
+
+ newoption = (relopt_ternary *) allocate_reloption(kinds,
+ RELOPT_TYPE_TERNARY, name, desc, lockmode);
+ newoption->default_val = default_val;
+
+ return newoption;
+}
+
+/*
+ * add_ternary_reloption
+ * Add a new ternary reloption
+ */
+void
+add_ternary_reloption(bits32 kinds, const char *name, const char *desc,
+ ternary default_val, LOCKMODE lockmode)
+{
+ relopt_ternary *newoption = init_ternary_reloption(kinds, name, desc,
+ default_val, lockmode);
+
+ add_reloption((relopt_gen *) newoption);
+}
+
+/*
+ * add_local_ternary_reloption
+ * Add a new ternary local reloption
+ *
+ * 'offset' is offset of ternary-typed field.
+ */
+void
+add_local_ternary_reloption(local_relopts *relopts, const char *name,
+ const char *desc, ternary default_val,
+ int offset)
+{
+ relopt_ternary *newoption = init_ternary_reloption(RELOPT_KIND_LOCAL,
+ name, desc,
+ default_val, 0);
+
+ add_local_reloption(relopts, (relopt_gen *) newoption, offset);
+}
/*
* init_real_reloption
@@ -1617,6 +1689,18 @@ parse_one_reloption(relopt_value *option, char *text_str, int text_len,
option->gen->name, value)));
}
break;
+ case RELOPT_TYPE_TERNARY:
+ {
+ bool b;
+ parsed = parse_bool(value, &b);
+ option->values.ternary_val = b ? TERNARY_TRUE : TERNARY_FALSE;
+ if (validate && !parsed)
+ ereport(ERROR,
+ (errcode(ERRCODE_INVALID_PARAMETER_VALUE),
+ errmsg("invalid value for ternary option \"%s\": %s",
+ option->gen->name, value)));
+ }
+ break;
case RELOPT_TYPE_INT:
{
relopt_int *optint = (relopt_int *) option->gen;
@@ -1780,17 +1864,6 @@ fillRelOptions(void *rdopts, Size basesize,
char *itempos = ((char *) rdopts) + elems[j].offset;
char *string_val;
- /*
- * If isset_offset is provided, store whether the reloption is
- * set there.
- */
- if (elems[j].isset_offset > 0)
- {
- char *setpos = ((char *) rdopts) + elems[j].isset_offset;
-
- *(bool *) setpos = options[i].isset;
- }
-
switch (options[i].gen->type)
{
case RELOPT_TYPE_BOOL:
@@ -1798,6 +1871,11 @@ fillRelOptions(void *rdopts, Size basesize,
options[i].values.bool_val :
((relopt_bool *) options[i].gen)->default_val;
break;
+ case RELOPT_TYPE_TERNARY:
+ *(ternary *) itempos = options[i].isset ?
+ options[i].values.ternary_val :
+ ((relopt_ternary *) options[i].gen)->default_val;
+ break;
case RELOPT_TYPE_INT:
*(int *) itempos = options[i].isset ?
options[i].values.int_val :
@@ -1912,8 +1990,8 @@ default_reloptions(Datum reloptions, bool validate, relopt_kind kind)
offsetof(StdRdOptions, parallel_workers)},
{"vacuum_index_cleanup", RELOPT_TYPE_ENUM,
offsetof(StdRdOptions, vacuum_index_cleanup)},
- {"vacuum_truncate", RELOPT_TYPE_BOOL,
- offsetof(StdRdOptions, vacuum_truncate), offsetof(StdRdOptions, vacuum_truncate_set)},
+ {"vacuum_truncate", RELOPT_TYPE_TERNARY,
+ offsetof(StdRdOptions, vacuum_truncate)},
{"vacuum_max_eager_freeze_failure_rate", RELOPT_TYPE_REAL,
offsetof(StdRdOptions, vacuum_max_eager_freeze_failure_rate)}
};
@@ -1993,7 +2071,6 @@ build_local_reloptions(local_relopts *relopts, Datum options, bool validate)
elems[i].optname = opt->option->name;
elems[i].opttype = opt->option->type;
elems[i].offset = opt->offset;
- elems[i].isset_offset = 0; /* not supported for local relopts yet */
i++;
}
diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c
index 733ef40ae7..977babff54 100644
--- a/src/backend/commands/vacuum.c
+++ b/src/backend/commands/vacuum.c
@@ -2221,9 +2221,9 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams params,
{
StdRdOptions *opts = (StdRdOptions *) rel->rd_options;
- if (opts && opts->vacuum_truncate_set)
+ if (opts && opts->vacuum_truncate != TERNARY_UNSET)
{
- if (opts->vacuum_truncate)
+ if (opts->vacuum_truncate == TERNARY_TRUE)
params.truncate = VACOPTVALUE_ENABLED;
else
params.truncate = VACOPTVALUE_DISABLED;
diff --git a/src/include/access/reloptions.h b/src/include/access/reloptions.h
index a604a4702c..a436697658 100644
--- a/src/include/access/reloptions.h
+++ b/src/include/access/reloptions.h
@@ -29,6 +29,7 @@
typedef enum relopt_type
{
RELOPT_TYPE_BOOL,
+ RELOPT_TYPE_TERNARY, /* on, off, unset */
RELOPT_TYPE_INT,
RELOPT_TYPE_REAL,
RELOPT_TYPE_ENUM,
@@ -80,6 +81,7 @@ typedef struct relopt_value
union
{
bool bool_val;
+ ternary ternary_val;
int int_val;
double real_val;
int enum_val;
@@ -94,6 +96,12 @@ typedef struct relopt_bool
bool default_val;
} relopt_bool;
+typedef struct relopt_rernary
+{
+ relopt_gen gen;
+ int default_val;
+} relopt_ternary;
+
typedef struct relopt_int
{
relopt_gen gen;
@@ -152,19 +160,6 @@ typedef struct
const char *optname; /* option's name */
relopt_type opttype; /* option's datatype */
int offset; /* offset of field in result struct */
-
- /*
- * isset_offset is an optional offset of a field in the result struct that
- * stores whether the option is explicitly set for the relation or if it
- * just picked up the default value. In most cases, this can be
- * accomplished by giving the reloption a special out-of-range default
- * value (e.g., some integer reloptions use -2), but this isn't always
- * possible. For example, a Boolean reloption cannot be given an
- * out-of-range default, so we need another way to discover the source of
- * its value. This offset is only used if given a value greater than
- * zero.
- */
- int isset_offset;
} relopt_parse_elt;
/* Local reloption definition */
@@ -195,6 +190,8 @@ typedef struct local_relopts
extern relopt_kind add_reloption_kind(void);
extern void add_bool_reloption(bits32 kinds, const char *name, const char *desc,
bool default_val, LOCKMODE lockmode);
+extern void add_ternary_reloption(bits32 kinds, const char *name,
+ const char *desc, int default_val, LOCKMODE lockmode);
extern void add_int_reloption(bits32 kinds, const char *name, const char *desc,
int default_val, int min_val, int max_val,
LOCKMODE lockmode);
@@ -214,6 +211,9 @@ extern void register_reloptions_validator(local_relopts *relopts,
extern void add_local_bool_reloption(local_relopts *relopts, const char *name,
const char *desc, bool default_val,
int offset);
+extern void add_local_ternary_reloption(local_relopts *relopts,
+ const char *name, const char *desc,
+ ternary default_val, int offset);
extern void add_local_int_reloption(local_relopts *relopts, const char *name,
const char *desc, int default_val,
int min_val, int max_val, int offset);
diff --git a/src/include/c.h b/src/include/c.h
index 39022f8a9d..9c59ccccf8 100644
--- a/src/include/c.h
+++ b/src/include/c.h
@@ -488,6 +488,22 @@ typedef void (*pg_funcptr_t) (void);
#include <stdbool.h>
+/*
+ * ternary
+ * Boolean value with an extrea "unset" option
+ *
+ * Ternary data type is used in relation options that can be "true", "false" or
+ * "unset". Since relation options are used deep inside the PostgreSQL code,
+ * this type is declared globally.
+*/
+
+typedef enum ternary
+{
+ TERNARY_FALSE = 0,
+ TERNARY_TRUE = 1,
+ TERNARY_UNSET = -1
+} ternary;
+
/* ----------------------------------------------------------------
* Section 3: standard system types
diff --git a/src/include/utils/rel.h b/src/include/utils/rel.h
index b552359915..7346d618f9 100644
--- a/src/include/utils/rel.h
+++ b/src/include/utils/rel.h
@@ -346,8 +346,7 @@ typedef struct StdRdOptions
bool user_catalog_table; /* use as an additional catalog relation */
int parallel_workers; /* max number of parallel workers */
StdRdOptIndexCleanup vacuum_index_cleanup; /* controls index vacuuming */
- bool vacuum_truncate; /* enables vacuum to truncate a relation */
- bool vacuum_truncate_set; /* whether vacuum_truncate is set */
+ ternary vacuum_truncate; /* enables vacuum to truncate a relation */
/*
* Fraction of pages in a relation that vacuum can eagerly scan and fail
--
2.39.2
--nextPart5129932.5fSG56mABF
Content-Disposition: attachment;
filename="v2-0003-Add-alias-to-be-used-as-unset-state.patch"
Content-Transfer-Encoding: 7Bit
Content-Type: text/x-patch; charset="unicode-2-0-utf-8";
name="v2-0003-Add-alias-to-be-used-as-unset-state.patch"
^ permalink raw reply [nested|flat] 868+ messages in thread
* [PATCH v2 2/4] Introduce ternary reloptions
@ 2025-09-04 14:41 Nikolay Shaplov <[email protected]>
0 siblings, 0 replies; 868+ messages in thread
From: Nikolay Shaplov @ 2025-09-04 14:41 UTC (permalink / raw)
Introduce ternary reloption as a replacement for current `vacuum_truncate`
implementation. Remove `vacuum_truncate_set` additional flag and using
`TERNARY_UNSET` value instead.
---
src/backend/access/common/reloptions.c | 129 ++++++++++++++++++++-----
src/backend/commands/vacuum.c | 4 +-
src/include/access/reloptions.h | 26 ++---
src/include/c.h | 16 +++
src/include/utils/rel.h | 3 +-
5 files changed, 135 insertions(+), 43 deletions(-)
diff --git a/src/backend/access/common/reloptions.c b/src/backend/access/common/reloptions.c
index 0af3fea68f..f543f11001 100644
--- a/src/backend/access/common/reloptions.c
+++ b/src/backend/access/common/reloptions.c
@@ -40,9 +40,9 @@
*
* To add an option:
*
- * (i) decide on a type (bool, integer, real, enum, string), name, default
- * value, upper and lower bounds (if applicable); for strings, consider a
- * validation routine.
+ * (i) decide on a type (bool, ternary, integer, real, enum, string), name,
+ * default value, upper and lower bounds (if applicable); for strings,
+ * consider a validation routine.
* (ii) add a record below (or use add_<type>_reloption).
* (iii) add it to the appropriate options struct (perhaps StdRdOptions)
* (iv) add it to the appropriate handling routine (perhaps
@@ -147,15 +147,6 @@ static relopt_bool boolRelOpts[] =
},
false
},
- {
- {
- "vacuum_truncate",
- "Enables vacuum to truncate empty pages at the end of this table",
- RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
- ShareUpdateExclusiveLock
- },
- true
- },
{
{
"deduplicate_items",
@@ -170,6 +161,21 @@ static relopt_bool boolRelOpts[] =
{{NULL}}
};
+static relopt_ternary ternaryRelOpts[] =
+{
+ {
+ {
+ "vacuum_truncate",
+ "Enables vacuum to truncate empty pages at the end of this table",
+ RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
+ ShareUpdateExclusiveLock
+ },
+ TERNARY_UNSET
+ },
+ /* list terminator */
+ {{NULL}}
+};
+
static relopt_int intRelOpts[] =
{
{
@@ -600,6 +606,13 @@ initialize_reloptions(void)
boolRelOpts[i].gen.lockmode));
j++;
}
+ for (i = 0; ternaryRelOpts[i].gen.name; i++)
+ {
+ Assert(DoLockModesConflict(ternaryRelOpts[i].gen.lockmode,
+ ternaryRelOpts[i].gen.lockmode));
+ j++;
+ }
+
for (i = 0; intRelOpts[i].gen.name; i++)
{
Assert(DoLockModesConflict(intRelOpts[i].gen.lockmode,
@@ -640,6 +653,14 @@ initialize_reloptions(void)
j++;
}
+ for (i = 0; ternaryRelOpts[i].gen.name; i++)
+ {
+ relOpts[j] = &ternaryRelOpts[i].gen;
+ relOpts[j]->type = RELOPT_TYPE_TERNARY;
+ relOpts[j]->namelen = strlen(relOpts[j]->name);
+ j++;
+ }
+
for (i = 0; intRelOpts[i].gen.name; i++)
{
relOpts[j] = &intRelOpts[i].gen;
@@ -800,6 +821,9 @@ allocate_reloption(bits32 kinds, int type, const char *name, const char *desc,
case RELOPT_TYPE_BOOL:
size = sizeof(relopt_bool);
break;
+ case RELOPT_TYPE_TERNARY:
+ size = sizeof(relopt_ternary);
+ break;
case RELOPT_TYPE_INT:
size = sizeof(relopt_int);
break;
@@ -883,6 +907,54 @@ add_local_bool_reloption(local_relopts *relopts, const char *name,
add_local_reloption(relopts, (relopt_gen *) newoption, offset);
}
+/*
+ * init_ternary_reloption
+ * Allocate and initialize a new ternary reloption
+ */
+static relopt_ternary *
+init_ternary_reloption(bits32 kinds, const char *name, const char *desc,
+ ternary default_val, LOCKMODE lockmode)
+{
+ relopt_ternary *newoption;
+
+ newoption = (relopt_ternary *) allocate_reloption(kinds,
+ RELOPT_TYPE_TERNARY, name, desc, lockmode);
+ newoption->default_val = default_val;
+
+ return newoption;
+}
+
+/*
+ * add_ternary_reloption
+ * Add a new ternary reloption
+ */
+void
+add_ternary_reloption(bits32 kinds, const char *name, const char *desc,
+ ternary default_val, LOCKMODE lockmode)
+{
+ relopt_ternary *newoption = init_ternary_reloption(kinds, name, desc,
+ default_val, lockmode);
+
+ add_reloption((relopt_gen *) newoption);
+}
+
+/*
+ * add_local_ternary_reloption
+ * Add a new ternary local reloption
+ *
+ * 'offset' is offset of ternary-typed field.
+ */
+void
+add_local_ternary_reloption(local_relopts *relopts, const char *name,
+ const char *desc, ternary default_val,
+ int offset)
+{
+ relopt_ternary *newoption = init_ternary_reloption(RELOPT_KIND_LOCAL,
+ name, desc,
+ default_val, 0);
+
+ add_local_reloption(relopts, (relopt_gen *) newoption, offset);
+}
/*
* init_real_reloption
@@ -1617,6 +1689,18 @@ parse_one_reloption(relopt_value *option, char *text_str, int text_len,
option->gen->name, value)));
}
break;
+ case RELOPT_TYPE_TERNARY:
+ {
+ bool b;
+ parsed = parse_bool(value, &b);
+ option->values.ternary_val = b ? TERNARY_TRUE : TERNARY_FALSE;
+ if (validate && !parsed)
+ ereport(ERROR,
+ (errcode(ERRCODE_INVALID_PARAMETER_VALUE),
+ errmsg("invalid value for ternary option \"%s\": %s",
+ option->gen->name, value)));
+ }
+ break;
case RELOPT_TYPE_INT:
{
relopt_int *optint = (relopt_int *) option->gen;
@@ -1780,17 +1864,6 @@ fillRelOptions(void *rdopts, Size basesize,
char *itempos = ((char *) rdopts) + elems[j].offset;
char *string_val;
- /*
- * If isset_offset is provided, store whether the reloption is
- * set there.
- */
- if (elems[j].isset_offset > 0)
- {
- char *setpos = ((char *) rdopts) + elems[j].isset_offset;
-
- *(bool *) setpos = options[i].isset;
- }
-
switch (options[i].gen->type)
{
case RELOPT_TYPE_BOOL:
@@ -1798,6 +1871,11 @@ fillRelOptions(void *rdopts, Size basesize,
options[i].values.bool_val :
((relopt_bool *) options[i].gen)->default_val;
break;
+ case RELOPT_TYPE_TERNARY:
+ *(ternary *) itempos = options[i].isset ?
+ options[i].values.ternary_val :
+ ((relopt_ternary *) options[i].gen)->default_val;
+ break;
case RELOPT_TYPE_INT:
*(int *) itempos = options[i].isset ?
options[i].values.int_val :
@@ -1912,8 +1990,8 @@ default_reloptions(Datum reloptions, bool validate, relopt_kind kind)
offsetof(StdRdOptions, parallel_workers)},
{"vacuum_index_cleanup", RELOPT_TYPE_ENUM,
offsetof(StdRdOptions, vacuum_index_cleanup)},
- {"vacuum_truncate", RELOPT_TYPE_BOOL,
- offsetof(StdRdOptions, vacuum_truncate), offsetof(StdRdOptions, vacuum_truncate_set)},
+ {"vacuum_truncate", RELOPT_TYPE_TERNARY,
+ offsetof(StdRdOptions, vacuum_truncate)},
{"vacuum_max_eager_freeze_failure_rate", RELOPT_TYPE_REAL,
offsetof(StdRdOptions, vacuum_max_eager_freeze_failure_rate)}
};
@@ -1993,7 +2071,6 @@ build_local_reloptions(local_relopts *relopts, Datum options, bool validate)
elems[i].optname = opt->option->name;
elems[i].opttype = opt->option->type;
elems[i].offset = opt->offset;
- elems[i].isset_offset = 0; /* not supported for local relopts yet */
i++;
}
diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c
index 733ef40ae7..977babff54 100644
--- a/src/backend/commands/vacuum.c
+++ b/src/backend/commands/vacuum.c
@@ -2221,9 +2221,9 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams params,
{
StdRdOptions *opts = (StdRdOptions *) rel->rd_options;
- if (opts && opts->vacuum_truncate_set)
+ if (opts && opts->vacuum_truncate != TERNARY_UNSET)
{
- if (opts->vacuum_truncate)
+ if (opts->vacuum_truncate == TERNARY_TRUE)
params.truncate = VACOPTVALUE_ENABLED;
else
params.truncate = VACOPTVALUE_DISABLED;
diff --git a/src/include/access/reloptions.h b/src/include/access/reloptions.h
index a604a4702c..a436697658 100644
--- a/src/include/access/reloptions.h
+++ b/src/include/access/reloptions.h
@@ -29,6 +29,7 @@
typedef enum relopt_type
{
RELOPT_TYPE_BOOL,
+ RELOPT_TYPE_TERNARY, /* on, off, unset */
RELOPT_TYPE_INT,
RELOPT_TYPE_REAL,
RELOPT_TYPE_ENUM,
@@ -80,6 +81,7 @@ typedef struct relopt_value
union
{
bool bool_val;
+ ternary ternary_val;
int int_val;
double real_val;
int enum_val;
@@ -94,6 +96,12 @@ typedef struct relopt_bool
bool default_val;
} relopt_bool;
+typedef struct relopt_rernary
+{
+ relopt_gen gen;
+ int default_val;
+} relopt_ternary;
+
typedef struct relopt_int
{
relopt_gen gen;
@@ -152,19 +160,6 @@ typedef struct
const char *optname; /* option's name */
relopt_type opttype; /* option's datatype */
int offset; /* offset of field in result struct */
-
- /*
- * isset_offset is an optional offset of a field in the result struct that
- * stores whether the option is explicitly set for the relation or if it
- * just picked up the default value. In most cases, this can be
- * accomplished by giving the reloption a special out-of-range default
- * value (e.g., some integer reloptions use -2), but this isn't always
- * possible. For example, a Boolean reloption cannot be given an
- * out-of-range default, so we need another way to discover the source of
- * its value. This offset is only used if given a value greater than
- * zero.
- */
- int isset_offset;
} relopt_parse_elt;
/* Local reloption definition */
@@ -195,6 +190,8 @@ typedef struct local_relopts
extern relopt_kind add_reloption_kind(void);
extern void add_bool_reloption(bits32 kinds, const char *name, const char *desc,
bool default_val, LOCKMODE lockmode);
+extern void add_ternary_reloption(bits32 kinds, const char *name,
+ const char *desc, int default_val, LOCKMODE lockmode);
extern void add_int_reloption(bits32 kinds, const char *name, const char *desc,
int default_val, int min_val, int max_val,
LOCKMODE lockmode);
@@ -214,6 +211,9 @@ extern void register_reloptions_validator(local_relopts *relopts,
extern void add_local_bool_reloption(local_relopts *relopts, const char *name,
const char *desc, bool default_val,
int offset);
+extern void add_local_ternary_reloption(local_relopts *relopts,
+ const char *name, const char *desc,
+ ternary default_val, int offset);
extern void add_local_int_reloption(local_relopts *relopts, const char *name,
const char *desc, int default_val,
int min_val, int max_val, int offset);
diff --git a/src/include/c.h b/src/include/c.h
index 39022f8a9d..9c59ccccf8 100644
--- a/src/include/c.h
+++ b/src/include/c.h
@@ -488,6 +488,22 @@ typedef void (*pg_funcptr_t) (void);
#include <stdbool.h>
+/*
+ * ternary
+ * Boolean value with an extrea "unset" option
+ *
+ * Ternary data type is used in relation options that can be "true", "false" or
+ * "unset". Since relation options are used deep inside the PostgreSQL code,
+ * this type is declared globally.
+*/
+
+typedef enum ternary
+{
+ TERNARY_FALSE = 0,
+ TERNARY_TRUE = 1,
+ TERNARY_UNSET = -1
+} ternary;
+
/* ----------------------------------------------------------------
* Section 3: standard system types
diff --git a/src/include/utils/rel.h b/src/include/utils/rel.h
index b552359915..7346d618f9 100644
--- a/src/include/utils/rel.h
+++ b/src/include/utils/rel.h
@@ -346,8 +346,7 @@ typedef struct StdRdOptions
bool user_catalog_table; /* use as an additional catalog relation */
int parallel_workers; /* max number of parallel workers */
StdRdOptIndexCleanup vacuum_index_cleanup; /* controls index vacuuming */
- bool vacuum_truncate; /* enables vacuum to truncate a relation */
- bool vacuum_truncate_set; /* whether vacuum_truncate is set */
+ ternary vacuum_truncate; /* enables vacuum to truncate a relation */
/*
* Fraction of pages in a relation that vacuum can eagerly scan and fail
--
2.39.2
--nextPart5129932.5fSG56mABF
Content-Disposition: attachment;
filename="v2-0003-Add-alias-to-be-used-as-unset-state.patch"
Content-Transfer-Encoding: 7Bit
Content-Type: text/x-patch; charset="unicode-2-0-utf-8";
name="v2-0003-Add-alias-to-be-used-as-unset-state.patch"
^ permalink raw reply [nested|flat] 868+ messages in thread
* [PATCH v2 2/4] Introduce ternary reloptions
@ 2025-09-04 14:41 Nikolay Shaplov <[email protected]>
0 siblings, 0 replies; 868+ messages in thread
From: Nikolay Shaplov @ 2025-09-04 14:41 UTC (permalink / raw)
Introduce ternary reloption as a replacement for current `vacuum_truncate`
implementation. Remove `vacuum_truncate_set` additional flag and using
`TERNARY_UNSET` value instead.
---
src/backend/access/common/reloptions.c | 129 ++++++++++++++++++++-----
src/backend/commands/vacuum.c | 4 +-
src/include/access/reloptions.h | 26 ++---
src/include/c.h | 16 +++
src/include/utils/rel.h | 3 +-
5 files changed, 135 insertions(+), 43 deletions(-)
diff --git a/src/backend/access/common/reloptions.c b/src/backend/access/common/reloptions.c
index 0af3fea68f..f543f11001 100644
--- a/src/backend/access/common/reloptions.c
+++ b/src/backend/access/common/reloptions.c
@@ -40,9 +40,9 @@
*
* To add an option:
*
- * (i) decide on a type (bool, integer, real, enum, string), name, default
- * value, upper and lower bounds (if applicable); for strings, consider a
- * validation routine.
+ * (i) decide on a type (bool, ternary, integer, real, enum, string), name,
+ * default value, upper and lower bounds (if applicable); for strings,
+ * consider a validation routine.
* (ii) add a record below (or use add_<type>_reloption).
* (iii) add it to the appropriate options struct (perhaps StdRdOptions)
* (iv) add it to the appropriate handling routine (perhaps
@@ -147,15 +147,6 @@ static relopt_bool boolRelOpts[] =
},
false
},
- {
- {
- "vacuum_truncate",
- "Enables vacuum to truncate empty pages at the end of this table",
- RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
- ShareUpdateExclusiveLock
- },
- true
- },
{
{
"deduplicate_items",
@@ -170,6 +161,21 @@ static relopt_bool boolRelOpts[] =
{{NULL}}
};
+static relopt_ternary ternaryRelOpts[] =
+{
+ {
+ {
+ "vacuum_truncate",
+ "Enables vacuum to truncate empty pages at the end of this table",
+ RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
+ ShareUpdateExclusiveLock
+ },
+ TERNARY_UNSET
+ },
+ /* list terminator */
+ {{NULL}}
+};
+
static relopt_int intRelOpts[] =
{
{
@@ -600,6 +606,13 @@ initialize_reloptions(void)
boolRelOpts[i].gen.lockmode));
j++;
}
+ for (i = 0; ternaryRelOpts[i].gen.name; i++)
+ {
+ Assert(DoLockModesConflict(ternaryRelOpts[i].gen.lockmode,
+ ternaryRelOpts[i].gen.lockmode));
+ j++;
+ }
+
for (i = 0; intRelOpts[i].gen.name; i++)
{
Assert(DoLockModesConflict(intRelOpts[i].gen.lockmode,
@@ -640,6 +653,14 @@ initialize_reloptions(void)
j++;
}
+ for (i = 0; ternaryRelOpts[i].gen.name; i++)
+ {
+ relOpts[j] = &ternaryRelOpts[i].gen;
+ relOpts[j]->type = RELOPT_TYPE_TERNARY;
+ relOpts[j]->namelen = strlen(relOpts[j]->name);
+ j++;
+ }
+
for (i = 0; intRelOpts[i].gen.name; i++)
{
relOpts[j] = &intRelOpts[i].gen;
@@ -800,6 +821,9 @@ allocate_reloption(bits32 kinds, int type, const char *name, const char *desc,
case RELOPT_TYPE_BOOL:
size = sizeof(relopt_bool);
break;
+ case RELOPT_TYPE_TERNARY:
+ size = sizeof(relopt_ternary);
+ break;
case RELOPT_TYPE_INT:
size = sizeof(relopt_int);
break;
@@ -883,6 +907,54 @@ add_local_bool_reloption(local_relopts *relopts, const char *name,
add_local_reloption(relopts, (relopt_gen *) newoption, offset);
}
+/*
+ * init_ternary_reloption
+ * Allocate and initialize a new ternary reloption
+ */
+static relopt_ternary *
+init_ternary_reloption(bits32 kinds, const char *name, const char *desc,
+ ternary default_val, LOCKMODE lockmode)
+{
+ relopt_ternary *newoption;
+
+ newoption = (relopt_ternary *) allocate_reloption(kinds,
+ RELOPT_TYPE_TERNARY, name, desc, lockmode);
+ newoption->default_val = default_val;
+
+ return newoption;
+}
+
+/*
+ * add_ternary_reloption
+ * Add a new ternary reloption
+ */
+void
+add_ternary_reloption(bits32 kinds, const char *name, const char *desc,
+ ternary default_val, LOCKMODE lockmode)
+{
+ relopt_ternary *newoption = init_ternary_reloption(kinds, name, desc,
+ default_val, lockmode);
+
+ add_reloption((relopt_gen *) newoption);
+}
+
+/*
+ * add_local_ternary_reloption
+ * Add a new ternary local reloption
+ *
+ * 'offset' is offset of ternary-typed field.
+ */
+void
+add_local_ternary_reloption(local_relopts *relopts, const char *name,
+ const char *desc, ternary default_val,
+ int offset)
+{
+ relopt_ternary *newoption = init_ternary_reloption(RELOPT_KIND_LOCAL,
+ name, desc,
+ default_val, 0);
+
+ add_local_reloption(relopts, (relopt_gen *) newoption, offset);
+}
/*
* init_real_reloption
@@ -1617,6 +1689,18 @@ parse_one_reloption(relopt_value *option, char *text_str, int text_len,
option->gen->name, value)));
}
break;
+ case RELOPT_TYPE_TERNARY:
+ {
+ bool b;
+ parsed = parse_bool(value, &b);
+ option->values.ternary_val = b ? TERNARY_TRUE : TERNARY_FALSE;
+ if (validate && !parsed)
+ ereport(ERROR,
+ (errcode(ERRCODE_INVALID_PARAMETER_VALUE),
+ errmsg("invalid value for ternary option \"%s\": %s",
+ option->gen->name, value)));
+ }
+ break;
case RELOPT_TYPE_INT:
{
relopt_int *optint = (relopt_int *) option->gen;
@@ -1780,17 +1864,6 @@ fillRelOptions(void *rdopts, Size basesize,
char *itempos = ((char *) rdopts) + elems[j].offset;
char *string_val;
- /*
- * If isset_offset is provided, store whether the reloption is
- * set there.
- */
- if (elems[j].isset_offset > 0)
- {
- char *setpos = ((char *) rdopts) + elems[j].isset_offset;
-
- *(bool *) setpos = options[i].isset;
- }
-
switch (options[i].gen->type)
{
case RELOPT_TYPE_BOOL:
@@ -1798,6 +1871,11 @@ fillRelOptions(void *rdopts, Size basesize,
options[i].values.bool_val :
((relopt_bool *) options[i].gen)->default_val;
break;
+ case RELOPT_TYPE_TERNARY:
+ *(ternary *) itempos = options[i].isset ?
+ options[i].values.ternary_val :
+ ((relopt_ternary *) options[i].gen)->default_val;
+ break;
case RELOPT_TYPE_INT:
*(int *) itempos = options[i].isset ?
options[i].values.int_val :
@@ -1912,8 +1990,8 @@ default_reloptions(Datum reloptions, bool validate, relopt_kind kind)
offsetof(StdRdOptions, parallel_workers)},
{"vacuum_index_cleanup", RELOPT_TYPE_ENUM,
offsetof(StdRdOptions, vacuum_index_cleanup)},
- {"vacuum_truncate", RELOPT_TYPE_BOOL,
- offsetof(StdRdOptions, vacuum_truncate), offsetof(StdRdOptions, vacuum_truncate_set)},
+ {"vacuum_truncate", RELOPT_TYPE_TERNARY,
+ offsetof(StdRdOptions, vacuum_truncate)},
{"vacuum_max_eager_freeze_failure_rate", RELOPT_TYPE_REAL,
offsetof(StdRdOptions, vacuum_max_eager_freeze_failure_rate)}
};
@@ -1993,7 +2071,6 @@ build_local_reloptions(local_relopts *relopts, Datum options, bool validate)
elems[i].optname = opt->option->name;
elems[i].opttype = opt->option->type;
elems[i].offset = opt->offset;
- elems[i].isset_offset = 0; /* not supported for local relopts yet */
i++;
}
diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c
index 733ef40ae7..977babff54 100644
--- a/src/backend/commands/vacuum.c
+++ b/src/backend/commands/vacuum.c
@@ -2221,9 +2221,9 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams params,
{
StdRdOptions *opts = (StdRdOptions *) rel->rd_options;
- if (opts && opts->vacuum_truncate_set)
+ if (opts && opts->vacuum_truncate != TERNARY_UNSET)
{
- if (opts->vacuum_truncate)
+ if (opts->vacuum_truncate == TERNARY_TRUE)
params.truncate = VACOPTVALUE_ENABLED;
else
params.truncate = VACOPTVALUE_DISABLED;
diff --git a/src/include/access/reloptions.h b/src/include/access/reloptions.h
index a604a4702c..a436697658 100644
--- a/src/include/access/reloptions.h
+++ b/src/include/access/reloptions.h
@@ -29,6 +29,7 @@
typedef enum relopt_type
{
RELOPT_TYPE_BOOL,
+ RELOPT_TYPE_TERNARY, /* on, off, unset */
RELOPT_TYPE_INT,
RELOPT_TYPE_REAL,
RELOPT_TYPE_ENUM,
@@ -80,6 +81,7 @@ typedef struct relopt_value
union
{
bool bool_val;
+ ternary ternary_val;
int int_val;
double real_val;
int enum_val;
@@ -94,6 +96,12 @@ typedef struct relopt_bool
bool default_val;
} relopt_bool;
+typedef struct relopt_rernary
+{
+ relopt_gen gen;
+ int default_val;
+} relopt_ternary;
+
typedef struct relopt_int
{
relopt_gen gen;
@@ -152,19 +160,6 @@ typedef struct
const char *optname; /* option's name */
relopt_type opttype; /* option's datatype */
int offset; /* offset of field in result struct */
-
- /*
- * isset_offset is an optional offset of a field in the result struct that
- * stores whether the option is explicitly set for the relation or if it
- * just picked up the default value. In most cases, this can be
- * accomplished by giving the reloption a special out-of-range default
- * value (e.g., some integer reloptions use -2), but this isn't always
- * possible. For example, a Boolean reloption cannot be given an
- * out-of-range default, so we need another way to discover the source of
- * its value. This offset is only used if given a value greater than
- * zero.
- */
- int isset_offset;
} relopt_parse_elt;
/* Local reloption definition */
@@ -195,6 +190,8 @@ typedef struct local_relopts
extern relopt_kind add_reloption_kind(void);
extern void add_bool_reloption(bits32 kinds, const char *name, const char *desc,
bool default_val, LOCKMODE lockmode);
+extern void add_ternary_reloption(bits32 kinds, const char *name,
+ const char *desc, int default_val, LOCKMODE lockmode);
extern void add_int_reloption(bits32 kinds, const char *name, const char *desc,
int default_val, int min_val, int max_val,
LOCKMODE lockmode);
@@ -214,6 +211,9 @@ extern void register_reloptions_validator(local_relopts *relopts,
extern void add_local_bool_reloption(local_relopts *relopts, const char *name,
const char *desc, bool default_val,
int offset);
+extern void add_local_ternary_reloption(local_relopts *relopts,
+ const char *name, const char *desc,
+ ternary default_val, int offset);
extern void add_local_int_reloption(local_relopts *relopts, const char *name,
const char *desc, int default_val,
int min_val, int max_val, int offset);
diff --git a/src/include/c.h b/src/include/c.h
index 39022f8a9d..9c59ccccf8 100644
--- a/src/include/c.h
+++ b/src/include/c.h
@@ -488,6 +488,22 @@ typedef void (*pg_funcptr_t) (void);
#include <stdbool.h>
+/*
+ * ternary
+ * Boolean value with an extrea "unset" option
+ *
+ * Ternary data type is used in relation options that can be "true", "false" or
+ * "unset". Since relation options are used deep inside the PostgreSQL code,
+ * this type is declared globally.
+*/
+
+typedef enum ternary
+{
+ TERNARY_FALSE = 0,
+ TERNARY_TRUE = 1,
+ TERNARY_UNSET = -1
+} ternary;
+
/* ----------------------------------------------------------------
* Section 3: standard system types
diff --git a/src/include/utils/rel.h b/src/include/utils/rel.h
index b552359915..7346d618f9 100644
--- a/src/include/utils/rel.h
+++ b/src/include/utils/rel.h
@@ -346,8 +346,7 @@ typedef struct StdRdOptions
bool user_catalog_table; /* use as an additional catalog relation */
int parallel_workers; /* max number of parallel workers */
StdRdOptIndexCleanup vacuum_index_cleanup; /* controls index vacuuming */
- bool vacuum_truncate; /* enables vacuum to truncate a relation */
- bool vacuum_truncate_set; /* whether vacuum_truncate is set */
+ ternary vacuum_truncate; /* enables vacuum to truncate a relation */
/*
* Fraction of pages in a relation that vacuum can eagerly scan and fail
--
2.39.2
--nextPart5129932.5fSG56mABF
Content-Disposition: attachment;
filename="v2-0003-Add-alias-to-be-used-as-unset-state.patch"
Content-Transfer-Encoding: 7Bit
Content-Type: text/x-patch; charset="unicode-2-0-utf-8";
name="v2-0003-Add-alias-to-be-used-as-unset-state.patch"
^ permalink raw reply [nested|flat] 868+ messages in thread
* [PATCH v2a 2/4] Introduce ternary reloptions
@ 2025-09-04 14:41 Nikolay Shaplov <[email protected]>
0 siblings, 0 replies; 868+ messages in thread
From: Nikolay Shaplov @ 2025-09-04 14:41 UTC (permalink / raw)
Introduce ternary reloption as a replacement for current `vacuum_truncate`
implementation. Remove `vacuum_truncate_set` additional flag and using
`TERNARY_UNSET` value instead.
---
src/backend/access/common/reloptions.c | 129 ++++++++++++++++++++-----
src/backend/commands/vacuum.c | 4 +-
src/include/access/reloptions.h | 26 ++---
src/include/c.h | 16 +++
src/include/utils/rel.h | 3 +-
5 files changed, 135 insertions(+), 43 deletions(-)
diff --git a/src/backend/access/common/reloptions.c b/src/backend/access/common/reloptions.c
index 0af3fea68f..f543f11001 100644
--- a/src/backend/access/common/reloptions.c
+++ b/src/backend/access/common/reloptions.c
@@ -40,9 +40,9 @@
*
* To add an option:
*
- * (i) decide on a type (bool, integer, real, enum, string), name, default
- * value, upper and lower bounds (if applicable); for strings, consider a
- * validation routine.
+ * (i) decide on a type (bool, ternary, integer, real, enum, string), name,
+ * default value, upper and lower bounds (if applicable); for strings,
+ * consider a validation routine.
* (ii) add a record below (or use add_<type>_reloption).
* (iii) add it to the appropriate options struct (perhaps StdRdOptions)
* (iv) add it to the appropriate handling routine (perhaps
@@ -147,15 +147,6 @@ static relopt_bool boolRelOpts[] =
},
false
},
- {
- {
- "vacuum_truncate",
- "Enables vacuum to truncate empty pages at the end of this table",
- RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
- ShareUpdateExclusiveLock
- },
- true
- },
{
{
"deduplicate_items",
@@ -170,6 +161,21 @@ static relopt_bool boolRelOpts[] =
{{NULL}}
};
+static relopt_ternary ternaryRelOpts[] =
+{
+ {
+ {
+ "vacuum_truncate",
+ "Enables vacuum to truncate empty pages at the end of this table",
+ RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
+ ShareUpdateExclusiveLock
+ },
+ TERNARY_UNSET
+ },
+ /* list terminator */
+ {{NULL}}
+};
+
static relopt_int intRelOpts[] =
{
{
@@ -600,6 +606,13 @@ initialize_reloptions(void)
boolRelOpts[i].gen.lockmode));
j++;
}
+ for (i = 0; ternaryRelOpts[i].gen.name; i++)
+ {
+ Assert(DoLockModesConflict(ternaryRelOpts[i].gen.lockmode,
+ ternaryRelOpts[i].gen.lockmode));
+ j++;
+ }
+
for (i = 0; intRelOpts[i].gen.name; i++)
{
Assert(DoLockModesConflict(intRelOpts[i].gen.lockmode,
@@ -640,6 +653,14 @@ initialize_reloptions(void)
j++;
}
+ for (i = 0; ternaryRelOpts[i].gen.name; i++)
+ {
+ relOpts[j] = &ternaryRelOpts[i].gen;
+ relOpts[j]->type = RELOPT_TYPE_TERNARY;
+ relOpts[j]->namelen = strlen(relOpts[j]->name);
+ j++;
+ }
+
for (i = 0; intRelOpts[i].gen.name; i++)
{
relOpts[j] = &intRelOpts[i].gen;
@@ -800,6 +821,9 @@ allocate_reloption(bits32 kinds, int type, const char *name, const char *desc,
case RELOPT_TYPE_BOOL:
size = sizeof(relopt_bool);
break;
+ case RELOPT_TYPE_TERNARY:
+ size = sizeof(relopt_ternary);
+ break;
case RELOPT_TYPE_INT:
size = sizeof(relopt_int);
break;
@@ -883,6 +907,54 @@ add_local_bool_reloption(local_relopts *relopts, const char *name,
add_local_reloption(relopts, (relopt_gen *) newoption, offset);
}
+/*
+ * init_ternary_reloption
+ * Allocate and initialize a new ternary reloption
+ */
+static relopt_ternary *
+init_ternary_reloption(bits32 kinds, const char *name, const char *desc,
+ ternary default_val, LOCKMODE lockmode)
+{
+ relopt_ternary *newoption;
+
+ newoption = (relopt_ternary *) allocate_reloption(kinds,
+ RELOPT_TYPE_TERNARY, name, desc, lockmode);
+ newoption->default_val = default_val;
+
+ return newoption;
+}
+
+/*
+ * add_ternary_reloption
+ * Add a new ternary reloption
+ */
+void
+add_ternary_reloption(bits32 kinds, const char *name, const char *desc,
+ ternary default_val, LOCKMODE lockmode)
+{
+ relopt_ternary *newoption = init_ternary_reloption(kinds, name, desc,
+ default_val, lockmode);
+
+ add_reloption((relopt_gen *) newoption);
+}
+
+/*
+ * add_local_ternary_reloption
+ * Add a new ternary local reloption
+ *
+ * 'offset' is offset of ternary-typed field.
+ */
+void
+add_local_ternary_reloption(local_relopts *relopts, const char *name,
+ const char *desc, ternary default_val,
+ int offset)
+{
+ relopt_ternary *newoption = init_ternary_reloption(RELOPT_KIND_LOCAL,
+ name, desc,
+ default_val, 0);
+
+ add_local_reloption(relopts, (relopt_gen *) newoption, offset);
+}
/*
* init_real_reloption
@@ -1617,6 +1689,18 @@ parse_one_reloption(relopt_value *option, char *text_str, int text_len,
option->gen->name, value)));
}
break;
+ case RELOPT_TYPE_TERNARY:
+ {
+ bool b;
+ parsed = parse_bool(value, &b);
+ option->values.ternary_val = b ? TERNARY_TRUE : TERNARY_FALSE;
+ if (validate && !parsed)
+ ereport(ERROR,
+ (errcode(ERRCODE_INVALID_PARAMETER_VALUE),
+ errmsg("invalid value for ternary option \"%s\": %s",
+ option->gen->name, value)));
+ }
+ break;
case RELOPT_TYPE_INT:
{
relopt_int *optint = (relopt_int *) option->gen;
@@ -1780,17 +1864,6 @@ fillRelOptions(void *rdopts, Size basesize,
char *itempos = ((char *) rdopts) + elems[j].offset;
char *string_val;
- /*
- * If isset_offset is provided, store whether the reloption is
- * set there.
- */
- if (elems[j].isset_offset > 0)
- {
- char *setpos = ((char *) rdopts) + elems[j].isset_offset;
-
- *(bool *) setpos = options[i].isset;
- }
-
switch (options[i].gen->type)
{
case RELOPT_TYPE_BOOL:
@@ -1798,6 +1871,11 @@ fillRelOptions(void *rdopts, Size basesize,
options[i].values.bool_val :
((relopt_bool *) options[i].gen)->default_val;
break;
+ case RELOPT_TYPE_TERNARY:
+ *(ternary *) itempos = options[i].isset ?
+ options[i].values.ternary_val :
+ ((relopt_ternary *) options[i].gen)->default_val;
+ break;
case RELOPT_TYPE_INT:
*(int *) itempos = options[i].isset ?
options[i].values.int_val :
@@ -1912,8 +1990,8 @@ default_reloptions(Datum reloptions, bool validate, relopt_kind kind)
offsetof(StdRdOptions, parallel_workers)},
{"vacuum_index_cleanup", RELOPT_TYPE_ENUM,
offsetof(StdRdOptions, vacuum_index_cleanup)},
- {"vacuum_truncate", RELOPT_TYPE_BOOL,
- offsetof(StdRdOptions, vacuum_truncate), offsetof(StdRdOptions, vacuum_truncate_set)},
+ {"vacuum_truncate", RELOPT_TYPE_TERNARY,
+ offsetof(StdRdOptions, vacuum_truncate)},
{"vacuum_max_eager_freeze_failure_rate", RELOPT_TYPE_REAL,
offsetof(StdRdOptions, vacuum_max_eager_freeze_failure_rate)}
};
@@ -1993,7 +2071,6 @@ build_local_reloptions(local_relopts *relopts, Datum options, bool validate)
elems[i].optname = opt->option->name;
elems[i].opttype = opt->option->type;
elems[i].offset = opt->offset;
- elems[i].isset_offset = 0; /* not supported for local relopts yet */
i++;
}
diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c
index 733ef40ae7..977babff54 100644
--- a/src/backend/commands/vacuum.c
+++ b/src/backend/commands/vacuum.c
@@ -2221,9 +2221,9 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams params,
{
StdRdOptions *opts = (StdRdOptions *) rel->rd_options;
- if (opts && opts->vacuum_truncate_set)
+ if (opts && opts->vacuum_truncate != TERNARY_UNSET)
{
- if (opts->vacuum_truncate)
+ if (opts->vacuum_truncate == TERNARY_TRUE)
params.truncate = VACOPTVALUE_ENABLED;
else
params.truncate = VACOPTVALUE_DISABLED;
diff --git a/src/include/access/reloptions.h b/src/include/access/reloptions.h
index a604a4702c..a436697658 100644
--- a/src/include/access/reloptions.h
+++ b/src/include/access/reloptions.h
@@ -29,6 +29,7 @@
typedef enum relopt_type
{
RELOPT_TYPE_BOOL,
+ RELOPT_TYPE_TERNARY, /* on, off, unset */
RELOPT_TYPE_INT,
RELOPT_TYPE_REAL,
RELOPT_TYPE_ENUM,
@@ -80,6 +81,7 @@ typedef struct relopt_value
union
{
bool bool_val;
+ ternary ternary_val;
int int_val;
double real_val;
int enum_val;
@@ -94,6 +96,12 @@ typedef struct relopt_bool
bool default_val;
} relopt_bool;
+typedef struct relopt_rernary
+{
+ relopt_gen gen;
+ int default_val;
+} relopt_ternary;
+
typedef struct relopt_int
{
relopt_gen gen;
@@ -152,19 +160,6 @@ typedef struct
const char *optname; /* option's name */
relopt_type opttype; /* option's datatype */
int offset; /* offset of field in result struct */
-
- /*
- * isset_offset is an optional offset of a field in the result struct that
- * stores whether the option is explicitly set for the relation or if it
- * just picked up the default value. In most cases, this can be
- * accomplished by giving the reloption a special out-of-range default
- * value (e.g., some integer reloptions use -2), but this isn't always
- * possible. For example, a Boolean reloption cannot be given an
- * out-of-range default, so we need another way to discover the source of
- * its value. This offset is only used if given a value greater than
- * zero.
- */
- int isset_offset;
} relopt_parse_elt;
/* Local reloption definition */
@@ -195,6 +190,8 @@ typedef struct local_relopts
extern relopt_kind add_reloption_kind(void);
extern void add_bool_reloption(bits32 kinds, const char *name, const char *desc,
bool default_val, LOCKMODE lockmode);
+extern void add_ternary_reloption(bits32 kinds, const char *name,
+ const char *desc, int default_val, LOCKMODE lockmode);
extern void add_int_reloption(bits32 kinds, const char *name, const char *desc,
int default_val, int min_val, int max_val,
LOCKMODE lockmode);
@@ -214,6 +211,9 @@ extern void register_reloptions_validator(local_relopts *relopts,
extern void add_local_bool_reloption(local_relopts *relopts, const char *name,
const char *desc, bool default_val,
int offset);
+extern void add_local_ternary_reloption(local_relopts *relopts,
+ const char *name, const char *desc,
+ ternary default_val, int offset);
extern void add_local_int_reloption(local_relopts *relopts, const char *name,
const char *desc, int default_val,
int min_val, int max_val, int offset);
diff --git a/src/include/c.h b/src/include/c.h
index 39022f8a9d..9c59ccccf8 100644
--- a/src/include/c.h
+++ b/src/include/c.h
@@ -488,6 +488,22 @@ typedef void (*pg_funcptr_t) (void);
#include <stdbool.h>
+/*
+ * ternary
+ * Boolean value with an extrea "unset" option
+ *
+ * Ternary data type is used in relation options that can be "true", "false" or
+ * "unset". Since relation options are used deep inside the PostgreSQL code,
+ * this type is declared globally.
+*/
+
+typedef enum ternary
+{
+ TERNARY_FALSE = 0,
+ TERNARY_TRUE = 1,
+ TERNARY_UNSET = -1
+} ternary;
+
/* ----------------------------------------------------------------
* Section 3: standard system types
diff --git a/src/include/utils/rel.h b/src/include/utils/rel.h
index b552359915..7346d618f9 100644
--- a/src/include/utils/rel.h
+++ b/src/include/utils/rel.h
@@ -346,8 +346,7 @@ typedef struct StdRdOptions
bool user_catalog_table; /* use as an additional catalog relation */
int parallel_workers; /* max number of parallel workers */
StdRdOptIndexCleanup vacuum_index_cleanup; /* controls index vacuuming */
- bool vacuum_truncate; /* enables vacuum to truncate a relation */
- bool vacuum_truncate_set; /* whether vacuum_truncate is set */
+ ternary vacuum_truncate; /* enables vacuum to truncate a relation */
/*
* Fraction of pages in a relation that vacuum can eagerly scan and fail
--
2.39.2
--nextPart4155600.3ZeAukHxDK
Content-Disposition: attachment;
filename="v2a-0003-Add-alias-to-be-used-as-unset-state.patch"
Content-Transfer-Encoding: 7Bit
Content-Type: text/x-patch; charset="unicode-2-0-utf-8";
name="v2a-0003-Add-alias-to-be-used-as-unset-state.patch"
^ permalink raw reply [nested|flat] 868+ messages in thread
* [PATCH v2a 2/4] Introduce ternary reloptions
@ 2025-09-04 14:41 Nikolay Shaplov <[email protected]>
0 siblings, 0 replies; 868+ messages in thread
From: Nikolay Shaplov @ 2025-09-04 14:41 UTC (permalink / raw)
Introduce ternary reloption as a replacement for current `vacuum_truncate`
implementation. Remove `vacuum_truncate_set` additional flag and using
`TERNARY_UNSET` value instead.
---
src/backend/access/common/reloptions.c | 129 ++++++++++++++++++++-----
src/backend/commands/vacuum.c | 4 +-
src/include/access/reloptions.h | 26 ++---
src/include/c.h | 16 +++
src/include/utils/rel.h | 3 +-
5 files changed, 135 insertions(+), 43 deletions(-)
diff --git a/src/backend/access/common/reloptions.c b/src/backend/access/common/reloptions.c
index 0af3fea68f..f543f11001 100644
--- a/src/backend/access/common/reloptions.c
+++ b/src/backend/access/common/reloptions.c
@@ -40,9 +40,9 @@
*
* To add an option:
*
- * (i) decide on a type (bool, integer, real, enum, string), name, default
- * value, upper and lower bounds (if applicable); for strings, consider a
- * validation routine.
+ * (i) decide on a type (bool, ternary, integer, real, enum, string), name,
+ * default value, upper and lower bounds (if applicable); for strings,
+ * consider a validation routine.
* (ii) add a record below (or use add_<type>_reloption).
* (iii) add it to the appropriate options struct (perhaps StdRdOptions)
* (iv) add it to the appropriate handling routine (perhaps
@@ -147,15 +147,6 @@ static relopt_bool boolRelOpts[] =
},
false
},
- {
- {
- "vacuum_truncate",
- "Enables vacuum to truncate empty pages at the end of this table",
- RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
- ShareUpdateExclusiveLock
- },
- true
- },
{
{
"deduplicate_items",
@@ -170,6 +161,21 @@ static relopt_bool boolRelOpts[] =
{{NULL}}
};
+static relopt_ternary ternaryRelOpts[] =
+{
+ {
+ {
+ "vacuum_truncate",
+ "Enables vacuum to truncate empty pages at the end of this table",
+ RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
+ ShareUpdateExclusiveLock
+ },
+ TERNARY_UNSET
+ },
+ /* list terminator */
+ {{NULL}}
+};
+
static relopt_int intRelOpts[] =
{
{
@@ -600,6 +606,13 @@ initialize_reloptions(void)
boolRelOpts[i].gen.lockmode));
j++;
}
+ for (i = 0; ternaryRelOpts[i].gen.name; i++)
+ {
+ Assert(DoLockModesConflict(ternaryRelOpts[i].gen.lockmode,
+ ternaryRelOpts[i].gen.lockmode));
+ j++;
+ }
+
for (i = 0; intRelOpts[i].gen.name; i++)
{
Assert(DoLockModesConflict(intRelOpts[i].gen.lockmode,
@@ -640,6 +653,14 @@ initialize_reloptions(void)
j++;
}
+ for (i = 0; ternaryRelOpts[i].gen.name; i++)
+ {
+ relOpts[j] = &ternaryRelOpts[i].gen;
+ relOpts[j]->type = RELOPT_TYPE_TERNARY;
+ relOpts[j]->namelen = strlen(relOpts[j]->name);
+ j++;
+ }
+
for (i = 0; intRelOpts[i].gen.name; i++)
{
relOpts[j] = &intRelOpts[i].gen;
@@ -800,6 +821,9 @@ allocate_reloption(bits32 kinds, int type, const char *name, const char *desc,
case RELOPT_TYPE_BOOL:
size = sizeof(relopt_bool);
break;
+ case RELOPT_TYPE_TERNARY:
+ size = sizeof(relopt_ternary);
+ break;
case RELOPT_TYPE_INT:
size = sizeof(relopt_int);
break;
@@ -883,6 +907,54 @@ add_local_bool_reloption(local_relopts *relopts, const char *name,
add_local_reloption(relopts, (relopt_gen *) newoption, offset);
}
+/*
+ * init_ternary_reloption
+ * Allocate and initialize a new ternary reloption
+ */
+static relopt_ternary *
+init_ternary_reloption(bits32 kinds, const char *name, const char *desc,
+ ternary default_val, LOCKMODE lockmode)
+{
+ relopt_ternary *newoption;
+
+ newoption = (relopt_ternary *) allocate_reloption(kinds,
+ RELOPT_TYPE_TERNARY, name, desc, lockmode);
+ newoption->default_val = default_val;
+
+ return newoption;
+}
+
+/*
+ * add_ternary_reloption
+ * Add a new ternary reloption
+ */
+void
+add_ternary_reloption(bits32 kinds, const char *name, const char *desc,
+ ternary default_val, LOCKMODE lockmode)
+{
+ relopt_ternary *newoption = init_ternary_reloption(kinds, name, desc,
+ default_val, lockmode);
+
+ add_reloption((relopt_gen *) newoption);
+}
+
+/*
+ * add_local_ternary_reloption
+ * Add a new ternary local reloption
+ *
+ * 'offset' is offset of ternary-typed field.
+ */
+void
+add_local_ternary_reloption(local_relopts *relopts, const char *name,
+ const char *desc, ternary default_val,
+ int offset)
+{
+ relopt_ternary *newoption = init_ternary_reloption(RELOPT_KIND_LOCAL,
+ name, desc,
+ default_val, 0);
+
+ add_local_reloption(relopts, (relopt_gen *) newoption, offset);
+}
/*
* init_real_reloption
@@ -1617,6 +1689,18 @@ parse_one_reloption(relopt_value *option, char *text_str, int text_len,
option->gen->name, value)));
}
break;
+ case RELOPT_TYPE_TERNARY:
+ {
+ bool b;
+ parsed = parse_bool(value, &b);
+ option->values.ternary_val = b ? TERNARY_TRUE : TERNARY_FALSE;
+ if (validate && !parsed)
+ ereport(ERROR,
+ (errcode(ERRCODE_INVALID_PARAMETER_VALUE),
+ errmsg("invalid value for ternary option \"%s\": %s",
+ option->gen->name, value)));
+ }
+ break;
case RELOPT_TYPE_INT:
{
relopt_int *optint = (relopt_int *) option->gen;
@@ -1780,17 +1864,6 @@ fillRelOptions(void *rdopts, Size basesize,
char *itempos = ((char *) rdopts) + elems[j].offset;
char *string_val;
- /*
- * If isset_offset is provided, store whether the reloption is
- * set there.
- */
- if (elems[j].isset_offset > 0)
- {
- char *setpos = ((char *) rdopts) + elems[j].isset_offset;
-
- *(bool *) setpos = options[i].isset;
- }
-
switch (options[i].gen->type)
{
case RELOPT_TYPE_BOOL:
@@ -1798,6 +1871,11 @@ fillRelOptions(void *rdopts, Size basesize,
options[i].values.bool_val :
((relopt_bool *) options[i].gen)->default_val;
break;
+ case RELOPT_TYPE_TERNARY:
+ *(ternary *) itempos = options[i].isset ?
+ options[i].values.ternary_val :
+ ((relopt_ternary *) options[i].gen)->default_val;
+ break;
case RELOPT_TYPE_INT:
*(int *) itempos = options[i].isset ?
options[i].values.int_val :
@@ -1912,8 +1990,8 @@ default_reloptions(Datum reloptions, bool validate, relopt_kind kind)
offsetof(StdRdOptions, parallel_workers)},
{"vacuum_index_cleanup", RELOPT_TYPE_ENUM,
offsetof(StdRdOptions, vacuum_index_cleanup)},
- {"vacuum_truncate", RELOPT_TYPE_BOOL,
- offsetof(StdRdOptions, vacuum_truncate), offsetof(StdRdOptions, vacuum_truncate_set)},
+ {"vacuum_truncate", RELOPT_TYPE_TERNARY,
+ offsetof(StdRdOptions, vacuum_truncate)},
{"vacuum_max_eager_freeze_failure_rate", RELOPT_TYPE_REAL,
offsetof(StdRdOptions, vacuum_max_eager_freeze_failure_rate)}
};
@@ -1993,7 +2071,6 @@ build_local_reloptions(local_relopts *relopts, Datum options, bool validate)
elems[i].optname = opt->option->name;
elems[i].opttype = opt->option->type;
elems[i].offset = opt->offset;
- elems[i].isset_offset = 0; /* not supported for local relopts yet */
i++;
}
diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c
index 733ef40ae7..977babff54 100644
--- a/src/backend/commands/vacuum.c
+++ b/src/backend/commands/vacuum.c
@@ -2221,9 +2221,9 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams params,
{
StdRdOptions *opts = (StdRdOptions *) rel->rd_options;
- if (opts && opts->vacuum_truncate_set)
+ if (opts && opts->vacuum_truncate != TERNARY_UNSET)
{
- if (opts->vacuum_truncate)
+ if (opts->vacuum_truncate == TERNARY_TRUE)
params.truncate = VACOPTVALUE_ENABLED;
else
params.truncate = VACOPTVALUE_DISABLED;
diff --git a/src/include/access/reloptions.h b/src/include/access/reloptions.h
index a604a4702c..a436697658 100644
--- a/src/include/access/reloptions.h
+++ b/src/include/access/reloptions.h
@@ -29,6 +29,7 @@
typedef enum relopt_type
{
RELOPT_TYPE_BOOL,
+ RELOPT_TYPE_TERNARY, /* on, off, unset */
RELOPT_TYPE_INT,
RELOPT_TYPE_REAL,
RELOPT_TYPE_ENUM,
@@ -80,6 +81,7 @@ typedef struct relopt_value
union
{
bool bool_val;
+ ternary ternary_val;
int int_val;
double real_val;
int enum_val;
@@ -94,6 +96,12 @@ typedef struct relopt_bool
bool default_val;
} relopt_bool;
+typedef struct relopt_rernary
+{
+ relopt_gen gen;
+ int default_val;
+} relopt_ternary;
+
typedef struct relopt_int
{
relopt_gen gen;
@@ -152,19 +160,6 @@ typedef struct
const char *optname; /* option's name */
relopt_type opttype; /* option's datatype */
int offset; /* offset of field in result struct */
-
- /*
- * isset_offset is an optional offset of a field in the result struct that
- * stores whether the option is explicitly set for the relation or if it
- * just picked up the default value. In most cases, this can be
- * accomplished by giving the reloption a special out-of-range default
- * value (e.g., some integer reloptions use -2), but this isn't always
- * possible. For example, a Boolean reloption cannot be given an
- * out-of-range default, so we need another way to discover the source of
- * its value. This offset is only used if given a value greater than
- * zero.
- */
- int isset_offset;
} relopt_parse_elt;
/* Local reloption definition */
@@ -195,6 +190,8 @@ typedef struct local_relopts
extern relopt_kind add_reloption_kind(void);
extern void add_bool_reloption(bits32 kinds, const char *name, const char *desc,
bool default_val, LOCKMODE lockmode);
+extern void add_ternary_reloption(bits32 kinds, const char *name,
+ const char *desc, int default_val, LOCKMODE lockmode);
extern void add_int_reloption(bits32 kinds, const char *name, const char *desc,
int default_val, int min_val, int max_val,
LOCKMODE lockmode);
@@ -214,6 +211,9 @@ extern void register_reloptions_validator(local_relopts *relopts,
extern void add_local_bool_reloption(local_relopts *relopts, const char *name,
const char *desc, bool default_val,
int offset);
+extern void add_local_ternary_reloption(local_relopts *relopts,
+ const char *name, const char *desc,
+ ternary default_val, int offset);
extern void add_local_int_reloption(local_relopts *relopts, const char *name,
const char *desc, int default_val,
int min_val, int max_val, int offset);
diff --git a/src/include/c.h b/src/include/c.h
index 39022f8a9d..9c59ccccf8 100644
--- a/src/include/c.h
+++ b/src/include/c.h
@@ -488,6 +488,22 @@ typedef void (*pg_funcptr_t) (void);
#include <stdbool.h>
+/*
+ * ternary
+ * Boolean value with an extrea "unset" option
+ *
+ * Ternary data type is used in relation options that can be "true", "false" or
+ * "unset". Since relation options are used deep inside the PostgreSQL code,
+ * this type is declared globally.
+*/
+
+typedef enum ternary
+{
+ TERNARY_FALSE = 0,
+ TERNARY_TRUE = 1,
+ TERNARY_UNSET = -1
+} ternary;
+
/* ----------------------------------------------------------------
* Section 3: standard system types
diff --git a/src/include/utils/rel.h b/src/include/utils/rel.h
index b552359915..7346d618f9 100644
--- a/src/include/utils/rel.h
+++ b/src/include/utils/rel.h
@@ -346,8 +346,7 @@ typedef struct StdRdOptions
bool user_catalog_table; /* use as an additional catalog relation */
int parallel_workers; /* max number of parallel workers */
StdRdOptIndexCleanup vacuum_index_cleanup; /* controls index vacuuming */
- bool vacuum_truncate; /* enables vacuum to truncate a relation */
- bool vacuum_truncate_set; /* whether vacuum_truncate is set */
+ ternary vacuum_truncate; /* enables vacuum to truncate a relation */
/*
* Fraction of pages in a relation that vacuum can eagerly scan and fail
--
2.39.2
--nextPart4155600.3ZeAukHxDK
Content-Disposition: attachment;
filename="v2a-0003-Add-alias-to-be-used-as-unset-state.patch"
Content-Transfer-Encoding: 7Bit
Content-Type: text/x-patch; charset="unicode-2-0-utf-8";
name="v2a-0003-Add-alias-to-be-used-as-unset-state.patch"
^ permalink raw reply [nested|flat] 868+ messages in thread
* [PATCH v2a 2/4] Introduce ternary reloptions
@ 2025-09-04 14:41 Nikolay Shaplov <[email protected]>
0 siblings, 0 replies; 868+ messages in thread
From: Nikolay Shaplov @ 2025-09-04 14:41 UTC (permalink / raw)
Introduce ternary reloption as a replacement for current `vacuum_truncate`
implementation. Remove `vacuum_truncate_set` additional flag and using
`TERNARY_UNSET` value instead.
---
src/backend/access/common/reloptions.c | 129 ++++++++++++++++++++-----
src/backend/commands/vacuum.c | 4 +-
src/include/access/reloptions.h | 26 ++---
src/include/c.h | 16 +++
src/include/utils/rel.h | 3 +-
5 files changed, 135 insertions(+), 43 deletions(-)
diff --git a/src/backend/access/common/reloptions.c b/src/backend/access/common/reloptions.c
index 0af3fea68f..f543f11001 100644
--- a/src/backend/access/common/reloptions.c
+++ b/src/backend/access/common/reloptions.c
@@ -40,9 +40,9 @@
*
* To add an option:
*
- * (i) decide on a type (bool, integer, real, enum, string), name, default
- * value, upper and lower bounds (if applicable); for strings, consider a
- * validation routine.
+ * (i) decide on a type (bool, ternary, integer, real, enum, string), name,
+ * default value, upper and lower bounds (if applicable); for strings,
+ * consider a validation routine.
* (ii) add a record below (or use add_<type>_reloption).
* (iii) add it to the appropriate options struct (perhaps StdRdOptions)
* (iv) add it to the appropriate handling routine (perhaps
@@ -147,15 +147,6 @@ static relopt_bool boolRelOpts[] =
},
false
},
- {
- {
- "vacuum_truncate",
- "Enables vacuum to truncate empty pages at the end of this table",
- RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
- ShareUpdateExclusiveLock
- },
- true
- },
{
{
"deduplicate_items",
@@ -170,6 +161,21 @@ static relopt_bool boolRelOpts[] =
{{NULL}}
};
+static relopt_ternary ternaryRelOpts[] =
+{
+ {
+ {
+ "vacuum_truncate",
+ "Enables vacuum to truncate empty pages at the end of this table",
+ RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
+ ShareUpdateExclusiveLock
+ },
+ TERNARY_UNSET
+ },
+ /* list terminator */
+ {{NULL}}
+};
+
static relopt_int intRelOpts[] =
{
{
@@ -600,6 +606,13 @@ initialize_reloptions(void)
boolRelOpts[i].gen.lockmode));
j++;
}
+ for (i = 0; ternaryRelOpts[i].gen.name; i++)
+ {
+ Assert(DoLockModesConflict(ternaryRelOpts[i].gen.lockmode,
+ ternaryRelOpts[i].gen.lockmode));
+ j++;
+ }
+
for (i = 0; intRelOpts[i].gen.name; i++)
{
Assert(DoLockModesConflict(intRelOpts[i].gen.lockmode,
@@ -640,6 +653,14 @@ initialize_reloptions(void)
j++;
}
+ for (i = 0; ternaryRelOpts[i].gen.name; i++)
+ {
+ relOpts[j] = &ternaryRelOpts[i].gen;
+ relOpts[j]->type = RELOPT_TYPE_TERNARY;
+ relOpts[j]->namelen = strlen(relOpts[j]->name);
+ j++;
+ }
+
for (i = 0; intRelOpts[i].gen.name; i++)
{
relOpts[j] = &intRelOpts[i].gen;
@@ -800,6 +821,9 @@ allocate_reloption(bits32 kinds, int type, const char *name, const char *desc,
case RELOPT_TYPE_BOOL:
size = sizeof(relopt_bool);
break;
+ case RELOPT_TYPE_TERNARY:
+ size = sizeof(relopt_ternary);
+ break;
case RELOPT_TYPE_INT:
size = sizeof(relopt_int);
break;
@@ -883,6 +907,54 @@ add_local_bool_reloption(local_relopts *relopts, const char *name,
add_local_reloption(relopts, (relopt_gen *) newoption, offset);
}
+/*
+ * init_ternary_reloption
+ * Allocate and initialize a new ternary reloption
+ */
+static relopt_ternary *
+init_ternary_reloption(bits32 kinds, const char *name, const char *desc,
+ ternary default_val, LOCKMODE lockmode)
+{
+ relopt_ternary *newoption;
+
+ newoption = (relopt_ternary *) allocate_reloption(kinds,
+ RELOPT_TYPE_TERNARY, name, desc, lockmode);
+ newoption->default_val = default_val;
+
+ return newoption;
+}
+
+/*
+ * add_ternary_reloption
+ * Add a new ternary reloption
+ */
+void
+add_ternary_reloption(bits32 kinds, const char *name, const char *desc,
+ ternary default_val, LOCKMODE lockmode)
+{
+ relopt_ternary *newoption = init_ternary_reloption(kinds, name, desc,
+ default_val, lockmode);
+
+ add_reloption((relopt_gen *) newoption);
+}
+
+/*
+ * add_local_ternary_reloption
+ * Add a new ternary local reloption
+ *
+ * 'offset' is offset of ternary-typed field.
+ */
+void
+add_local_ternary_reloption(local_relopts *relopts, const char *name,
+ const char *desc, ternary default_val,
+ int offset)
+{
+ relopt_ternary *newoption = init_ternary_reloption(RELOPT_KIND_LOCAL,
+ name, desc,
+ default_val, 0);
+
+ add_local_reloption(relopts, (relopt_gen *) newoption, offset);
+}
/*
* init_real_reloption
@@ -1617,6 +1689,18 @@ parse_one_reloption(relopt_value *option, char *text_str, int text_len,
option->gen->name, value)));
}
break;
+ case RELOPT_TYPE_TERNARY:
+ {
+ bool b;
+ parsed = parse_bool(value, &b);
+ option->values.ternary_val = b ? TERNARY_TRUE : TERNARY_FALSE;
+ if (validate && !parsed)
+ ereport(ERROR,
+ (errcode(ERRCODE_INVALID_PARAMETER_VALUE),
+ errmsg("invalid value for ternary option \"%s\": %s",
+ option->gen->name, value)));
+ }
+ break;
case RELOPT_TYPE_INT:
{
relopt_int *optint = (relopt_int *) option->gen;
@@ -1780,17 +1864,6 @@ fillRelOptions(void *rdopts, Size basesize,
char *itempos = ((char *) rdopts) + elems[j].offset;
char *string_val;
- /*
- * If isset_offset is provided, store whether the reloption is
- * set there.
- */
- if (elems[j].isset_offset > 0)
- {
- char *setpos = ((char *) rdopts) + elems[j].isset_offset;
-
- *(bool *) setpos = options[i].isset;
- }
-
switch (options[i].gen->type)
{
case RELOPT_TYPE_BOOL:
@@ -1798,6 +1871,11 @@ fillRelOptions(void *rdopts, Size basesize,
options[i].values.bool_val :
((relopt_bool *) options[i].gen)->default_val;
break;
+ case RELOPT_TYPE_TERNARY:
+ *(ternary *) itempos = options[i].isset ?
+ options[i].values.ternary_val :
+ ((relopt_ternary *) options[i].gen)->default_val;
+ break;
case RELOPT_TYPE_INT:
*(int *) itempos = options[i].isset ?
options[i].values.int_val :
@@ -1912,8 +1990,8 @@ default_reloptions(Datum reloptions, bool validate, relopt_kind kind)
offsetof(StdRdOptions, parallel_workers)},
{"vacuum_index_cleanup", RELOPT_TYPE_ENUM,
offsetof(StdRdOptions, vacuum_index_cleanup)},
- {"vacuum_truncate", RELOPT_TYPE_BOOL,
- offsetof(StdRdOptions, vacuum_truncate), offsetof(StdRdOptions, vacuum_truncate_set)},
+ {"vacuum_truncate", RELOPT_TYPE_TERNARY,
+ offsetof(StdRdOptions, vacuum_truncate)},
{"vacuum_max_eager_freeze_failure_rate", RELOPT_TYPE_REAL,
offsetof(StdRdOptions, vacuum_max_eager_freeze_failure_rate)}
};
@@ -1993,7 +2071,6 @@ build_local_reloptions(local_relopts *relopts, Datum options, bool validate)
elems[i].optname = opt->option->name;
elems[i].opttype = opt->option->type;
elems[i].offset = opt->offset;
- elems[i].isset_offset = 0; /* not supported for local relopts yet */
i++;
}
diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c
index 733ef40ae7..977babff54 100644
--- a/src/backend/commands/vacuum.c
+++ b/src/backend/commands/vacuum.c
@@ -2221,9 +2221,9 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams params,
{
StdRdOptions *opts = (StdRdOptions *) rel->rd_options;
- if (opts && opts->vacuum_truncate_set)
+ if (opts && opts->vacuum_truncate != TERNARY_UNSET)
{
- if (opts->vacuum_truncate)
+ if (opts->vacuum_truncate == TERNARY_TRUE)
params.truncate = VACOPTVALUE_ENABLED;
else
params.truncate = VACOPTVALUE_DISABLED;
diff --git a/src/include/access/reloptions.h b/src/include/access/reloptions.h
index a604a4702c..a436697658 100644
--- a/src/include/access/reloptions.h
+++ b/src/include/access/reloptions.h
@@ -29,6 +29,7 @@
typedef enum relopt_type
{
RELOPT_TYPE_BOOL,
+ RELOPT_TYPE_TERNARY, /* on, off, unset */
RELOPT_TYPE_INT,
RELOPT_TYPE_REAL,
RELOPT_TYPE_ENUM,
@@ -80,6 +81,7 @@ typedef struct relopt_value
union
{
bool bool_val;
+ ternary ternary_val;
int int_val;
double real_val;
int enum_val;
@@ -94,6 +96,12 @@ typedef struct relopt_bool
bool default_val;
} relopt_bool;
+typedef struct relopt_rernary
+{
+ relopt_gen gen;
+ int default_val;
+} relopt_ternary;
+
typedef struct relopt_int
{
relopt_gen gen;
@@ -152,19 +160,6 @@ typedef struct
const char *optname; /* option's name */
relopt_type opttype; /* option's datatype */
int offset; /* offset of field in result struct */
-
- /*
- * isset_offset is an optional offset of a field in the result struct that
- * stores whether the option is explicitly set for the relation or if it
- * just picked up the default value. In most cases, this can be
- * accomplished by giving the reloption a special out-of-range default
- * value (e.g., some integer reloptions use -2), but this isn't always
- * possible. For example, a Boolean reloption cannot be given an
- * out-of-range default, so we need another way to discover the source of
- * its value. This offset is only used if given a value greater than
- * zero.
- */
- int isset_offset;
} relopt_parse_elt;
/* Local reloption definition */
@@ -195,6 +190,8 @@ typedef struct local_relopts
extern relopt_kind add_reloption_kind(void);
extern void add_bool_reloption(bits32 kinds, const char *name, const char *desc,
bool default_val, LOCKMODE lockmode);
+extern void add_ternary_reloption(bits32 kinds, const char *name,
+ const char *desc, int default_val, LOCKMODE lockmode);
extern void add_int_reloption(bits32 kinds, const char *name, const char *desc,
int default_val, int min_val, int max_val,
LOCKMODE lockmode);
@@ -214,6 +211,9 @@ extern void register_reloptions_validator(local_relopts *relopts,
extern void add_local_bool_reloption(local_relopts *relopts, const char *name,
const char *desc, bool default_val,
int offset);
+extern void add_local_ternary_reloption(local_relopts *relopts,
+ const char *name, const char *desc,
+ ternary default_val, int offset);
extern void add_local_int_reloption(local_relopts *relopts, const char *name,
const char *desc, int default_val,
int min_val, int max_val, int offset);
diff --git a/src/include/c.h b/src/include/c.h
index 39022f8a9d..9c59ccccf8 100644
--- a/src/include/c.h
+++ b/src/include/c.h
@@ -488,6 +488,22 @@ typedef void (*pg_funcptr_t) (void);
#include <stdbool.h>
+/*
+ * ternary
+ * Boolean value with an extrea "unset" option
+ *
+ * Ternary data type is used in relation options that can be "true", "false" or
+ * "unset". Since relation options are used deep inside the PostgreSQL code,
+ * this type is declared globally.
+*/
+
+typedef enum ternary
+{
+ TERNARY_FALSE = 0,
+ TERNARY_TRUE = 1,
+ TERNARY_UNSET = -1
+} ternary;
+
/* ----------------------------------------------------------------
* Section 3: standard system types
diff --git a/src/include/utils/rel.h b/src/include/utils/rel.h
index b552359915..7346d618f9 100644
--- a/src/include/utils/rel.h
+++ b/src/include/utils/rel.h
@@ -346,8 +346,7 @@ typedef struct StdRdOptions
bool user_catalog_table; /* use as an additional catalog relation */
int parallel_workers; /* max number of parallel workers */
StdRdOptIndexCleanup vacuum_index_cleanup; /* controls index vacuuming */
- bool vacuum_truncate; /* enables vacuum to truncate a relation */
- bool vacuum_truncate_set; /* whether vacuum_truncate is set */
+ ternary vacuum_truncate; /* enables vacuum to truncate a relation */
/*
* Fraction of pages in a relation that vacuum can eagerly scan and fail
--
2.39.2
--nextPart4155600.3ZeAukHxDK
Content-Disposition: attachment;
filename="v2a-0003-Add-alias-to-be-used-as-unset-state.patch"
Content-Transfer-Encoding: 7Bit
Content-Type: text/x-patch; charset="unicode-2-0-utf-8";
name="v2a-0003-Add-alias-to-be-used-as-unset-state.patch"
^ permalink raw reply [nested|flat] 868+ messages in thread
* [PATCH v2a 2/4] Introduce ternary reloptions
@ 2025-09-04 14:41 Nikolay Shaplov <[email protected]>
0 siblings, 0 replies; 868+ messages in thread
From: Nikolay Shaplov @ 2025-09-04 14:41 UTC (permalink / raw)
Introduce ternary reloption as a replacement for current `vacuum_truncate`
implementation. Remove `vacuum_truncate_set` additional flag and using
`TERNARY_UNSET` value instead.
---
src/backend/access/common/reloptions.c | 129 ++++++++++++++++++++-----
src/backend/commands/vacuum.c | 4 +-
src/include/access/reloptions.h | 26 ++---
src/include/c.h | 16 +++
src/include/utils/rel.h | 3 +-
5 files changed, 135 insertions(+), 43 deletions(-)
diff --git a/src/backend/access/common/reloptions.c b/src/backend/access/common/reloptions.c
index 0af3fea68f..f543f11001 100644
--- a/src/backend/access/common/reloptions.c
+++ b/src/backend/access/common/reloptions.c
@@ -40,9 +40,9 @@
*
* To add an option:
*
- * (i) decide on a type (bool, integer, real, enum, string), name, default
- * value, upper and lower bounds (if applicable); for strings, consider a
- * validation routine.
+ * (i) decide on a type (bool, ternary, integer, real, enum, string), name,
+ * default value, upper and lower bounds (if applicable); for strings,
+ * consider a validation routine.
* (ii) add a record below (or use add_<type>_reloption).
* (iii) add it to the appropriate options struct (perhaps StdRdOptions)
* (iv) add it to the appropriate handling routine (perhaps
@@ -147,15 +147,6 @@ static relopt_bool boolRelOpts[] =
},
false
},
- {
- {
- "vacuum_truncate",
- "Enables vacuum to truncate empty pages at the end of this table",
- RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
- ShareUpdateExclusiveLock
- },
- true
- },
{
{
"deduplicate_items",
@@ -170,6 +161,21 @@ static relopt_bool boolRelOpts[] =
{{NULL}}
};
+static relopt_ternary ternaryRelOpts[] =
+{
+ {
+ {
+ "vacuum_truncate",
+ "Enables vacuum to truncate empty pages at the end of this table",
+ RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
+ ShareUpdateExclusiveLock
+ },
+ TERNARY_UNSET
+ },
+ /* list terminator */
+ {{NULL}}
+};
+
static relopt_int intRelOpts[] =
{
{
@@ -600,6 +606,13 @@ initialize_reloptions(void)
boolRelOpts[i].gen.lockmode));
j++;
}
+ for (i = 0; ternaryRelOpts[i].gen.name; i++)
+ {
+ Assert(DoLockModesConflict(ternaryRelOpts[i].gen.lockmode,
+ ternaryRelOpts[i].gen.lockmode));
+ j++;
+ }
+
for (i = 0; intRelOpts[i].gen.name; i++)
{
Assert(DoLockModesConflict(intRelOpts[i].gen.lockmode,
@@ -640,6 +653,14 @@ initialize_reloptions(void)
j++;
}
+ for (i = 0; ternaryRelOpts[i].gen.name; i++)
+ {
+ relOpts[j] = &ternaryRelOpts[i].gen;
+ relOpts[j]->type = RELOPT_TYPE_TERNARY;
+ relOpts[j]->namelen = strlen(relOpts[j]->name);
+ j++;
+ }
+
for (i = 0; intRelOpts[i].gen.name; i++)
{
relOpts[j] = &intRelOpts[i].gen;
@@ -800,6 +821,9 @@ allocate_reloption(bits32 kinds, int type, const char *name, const char *desc,
case RELOPT_TYPE_BOOL:
size = sizeof(relopt_bool);
break;
+ case RELOPT_TYPE_TERNARY:
+ size = sizeof(relopt_ternary);
+ break;
case RELOPT_TYPE_INT:
size = sizeof(relopt_int);
break;
@@ -883,6 +907,54 @@ add_local_bool_reloption(local_relopts *relopts, const char *name,
add_local_reloption(relopts, (relopt_gen *) newoption, offset);
}
+/*
+ * init_ternary_reloption
+ * Allocate and initialize a new ternary reloption
+ */
+static relopt_ternary *
+init_ternary_reloption(bits32 kinds, const char *name, const char *desc,
+ ternary default_val, LOCKMODE lockmode)
+{
+ relopt_ternary *newoption;
+
+ newoption = (relopt_ternary *) allocate_reloption(kinds,
+ RELOPT_TYPE_TERNARY, name, desc, lockmode);
+ newoption->default_val = default_val;
+
+ return newoption;
+}
+
+/*
+ * add_ternary_reloption
+ * Add a new ternary reloption
+ */
+void
+add_ternary_reloption(bits32 kinds, const char *name, const char *desc,
+ ternary default_val, LOCKMODE lockmode)
+{
+ relopt_ternary *newoption = init_ternary_reloption(kinds, name, desc,
+ default_val, lockmode);
+
+ add_reloption((relopt_gen *) newoption);
+}
+
+/*
+ * add_local_ternary_reloption
+ * Add a new ternary local reloption
+ *
+ * 'offset' is offset of ternary-typed field.
+ */
+void
+add_local_ternary_reloption(local_relopts *relopts, const char *name,
+ const char *desc, ternary default_val,
+ int offset)
+{
+ relopt_ternary *newoption = init_ternary_reloption(RELOPT_KIND_LOCAL,
+ name, desc,
+ default_val, 0);
+
+ add_local_reloption(relopts, (relopt_gen *) newoption, offset);
+}
/*
* init_real_reloption
@@ -1617,6 +1689,18 @@ parse_one_reloption(relopt_value *option, char *text_str, int text_len,
option->gen->name, value)));
}
break;
+ case RELOPT_TYPE_TERNARY:
+ {
+ bool b;
+ parsed = parse_bool(value, &b);
+ option->values.ternary_val = b ? TERNARY_TRUE : TERNARY_FALSE;
+ if (validate && !parsed)
+ ereport(ERROR,
+ (errcode(ERRCODE_INVALID_PARAMETER_VALUE),
+ errmsg("invalid value for ternary option \"%s\": %s",
+ option->gen->name, value)));
+ }
+ break;
case RELOPT_TYPE_INT:
{
relopt_int *optint = (relopt_int *) option->gen;
@@ -1780,17 +1864,6 @@ fillRelOptions(void *rdopts, Size basesize,
char *itempos = ((char *) rdopts) + elems[j].offset;
char *string_val;
- /*
- * If isset_offset is provided, store whether the reloption is
- * set there.
- */
- if (elems[j].isset_offset > 0)
- {
- char *setpos = ((char *) rdopts) + elems[j].isset_offset;
-
- *(bool *) setpos = options[i].isset;
- }
-
switch (options[i].gen->type)
{
case RELOPT_TYPE_BOOL:
@@ -1798,6 +1871,11 @@ fillRelOptions(void *rdopts, Size basesize,
options[i].values.bool_val :
((relopt_bool *) options[i].gen)->default_val;
break;
+ case RELOPT_TYPE_TERNARY:
+ *(ternary *) itempos = options[i].isset ?
+ options[i].values.ternary_val :
+ ((relopt_ternary *) options[i].gen)->default_val;
+ break;
case RELOPT_TYPE_INT:
*(int *) itempos = options[i].isset ?
options[i].values.int_val :
@@ -1912,8 +1990,8 @@ default_reloptions(Datum reloptions, bool validate, relopt_kind kind)
offsetof(StdRdOptions, parallel_workers)},
{"vacuum_index_cleanup", RELOPT_TYPE_ENUM,
offsetof(StdRdOptions, vacuum_index_cleanup)},
- {"vacuum_truncate", RELOPT_TYPE_BOOL,
- offsetof(StdRdOptions, vacuum_truncate), offsetof(StdRdOptions, vacuum_truncate_set)},
+ {"vacuum_truncate", RELOPT_TYPE_TERNARY,
+ offsetof(StdRdOptions, vacuum_truncate)},
{"vacuum_max_eager_freeze_failure_rate", RELOPT_TYPE_REAL,
offsetof(StdRdOptions, vacuum_max_eager_freeze_failure_rate)}
};
@@ -1993,7 +2071,6 @@ build_local_reloptions(local_relopts *relopts, Datum options, bool validate)
elems[i].optname = opt->option->name;
elems[i].opttype = opt->option->type;
elems[i].offset = opt->offset;
- elems[i].isset_offset = 0; /* not supported for local relopts yet */
i++;
}
diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c
index 733ef40ae7..977babff54 100644
--- a/src/backend/commands/vacuum.c
+++ b/src/backend/commands/vacuum.c
@@ -2221,9 +2221,9 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams params,
{
StdRdOptions *opts = (StdRdOptions *) rel->rd_options;
- if (opts && opts->vacuum_truncate_set)
+ if (opts && opts->vacuum_truncate != TERNARY_UNSET)
{
- if (opts->vacuum_truncate)
+ if (opts->vacuum_truncate == TERNARY_TRUE)
params.truncate = VACOPTVALUE_ENABLED;
else
params.truncate = VACOPTVALUE_DISABLED;
diff --git a/src/include/access/reloptions.h b/src/include/access/reloptions.h
index a604a4702c..a436697658 100644
--- a/src/include/access/reloptions.h
+++ b/src/include/access/reloptions.h
@@ -29,6 +29,7 @@
typedef enum relopt_type
{
RELOPT_TYPE_BOOL,
+ RELOPT_TYPE_TERNARY, /* on, off, unset */
RELOPT_TYPE_INT,
RELOPT_TYPE_REAL,
RELOPT_TYPE_ENUM,
@@ -80,6 +81,7 @@ typedef struct relopt_value
union
{
bool bool_val;
+ ternary ternary_val;
int int_val;
double real_val;
int enum_val;
@@ -94,6 +96,12 @@ typedef struct relopt_bool
bool default_val;
} relopt_bool;
+typedef struct relopt_rernary
+{
+ relopt_gen gen;
+ int default_val;
+} relopt_ternary;
+
typedef struct relopt_int
{
relopt_gen gen;
@@ -152,19 +160,6 @@ typedef struct
const char *optname; /* option's name */
relopt_type opttype; /* option's datatype */
int offset; /* offset of field in result struct */
-
- /*
- * isset_offset is an optional offset of a field in the result struct that
- * stores whether the option is explicitly set for the relation or if it
- * just picked up the default value. In most cases, this can be
- * accomplished by giving the reloption a special out-of-range default
- * value (e.g., some integer reloptions use -2), but this isn't always
- * possible. For example, a Boolean reloption cannot be given an
- * out-of-range default, so we need another way to discover the source of
- * its value. This offset is only used if given a value greater than
- * zero.
- */
- int isset_offset;
} relopt_parse_elt;
/* Local reloption definition */
@@ -195,6 +190,8 @@ typedef struct local_relopts
extern relopt_kind add_reloption_kind(void);
extern void add_bool_reloption(bits32 kinds, const char *name, const char *desc,
bool default_val, LOCKMODE lockmode);
+extern void add_ternary_reloption(bits32 kinds, const char *name,
+ const char *desc, int default_val, LOCKMODE lockmode);
extern void add_int_reloption(bits32 kinds, const char *name, const char *desc,
int default_val, int min_val, int max_val,
LOCKMODE lockmode);
@@ -214,6 +211,9 @@ extern void register_reloptions_validator(local_relopts *relopts,
extern void add_local_bool_reloption(local_relopts *relopts, const char *name,
const char *desc, bool default_val,
int offset);
+extern void add_local_ternary_reloption(local_relopts *relopts,
+ const char *name, const char *desc,
+ ternary default_val, int offset);
extern void add_local_int_reloption(local_relopts *relopts, const char *name,
const char *desc, int default_val,
int min_val, int max_val, int offset);
diff --git a/src/include/c.h b/src/include/c.h
index 39022f8a9d..9c59ccccf8 100644
--- a/src/include/c.h
+++ b/src/include/c.h
@@ -488,6 +488,22 @@ typedef void (*pg_funcptr_t) (void);
#include <stdbool.h>
+/*
+ * ternary
+ * Boolean value with an extrea "unset" option
+ *
+ * Ternary data type is used in relation options that can be "true", "false" or
+ * "unset". Since relation options are used deep inside the PostgreSQL code,
+ * this type is declared globally.
+*/
+
+typedef enum ternary
+{
+ TERNARY_FALSE = 0,
+ TERNARY_TRUE = 1,
+ TERNARY_UNSET = -1
+} ternary;
+
/* ----------------------------------------------------------------
* Section 3: standard system types
diff --git a/src/include/utils/rel.h b/src/include/utils/rel.h
index b552359915..7346d618f9 100644
--- a/src/include/utils/rel.h
+++ b/src/include/utils/rel.h
@@ -346,8 +346,7 @@ typedef struct StdRdOptions
bool user_catalog_table; /* use as an additional catalog relation */
int parallel_workers; /* max number of parallel workers */
StdRdOptIndexCleanup vacuum_index_cleanup; /* controls index vacuuming */
- bool vacuum_truncate; /* enables vacuum to truncate a relation */
- bool vacuum_truncate_set; /* whether vacuum_truncate is set */
+ ternary vacuum_truncate; /* enables vacuum to truncate a relation */
/*
* Fraction of pages in a relation that vacuum can eagerly scan and fail
--
2.39.2
--nextPart4155600.3ZeAukHxDK
Content-Disposition: attachment;
filename="v2a-0003-Add-alias-to-be-used-as-unset-state.patch"
Content-Transfer-Encoding: 7Bit
Content-Type: text/x-patch; charset="unicode-2-0-utf-8";
name="v2a-0003-Add-alias-to-be-used-as-unset-state.patch"
^ permalink raw reply [nested|flat] 868+ messages in thread
* [PATCH v2a 2/4] Introduce ternary reloptions
@ 2025-09-04 14:41 Nikolay Shaplov <[email protected]>
0 siblings, 0 replies; 868+ messages in thread
From: Nikolay Shaplov @ 2025-09-04 14:41 UTC (permalink / raw)
Introduce ternary reloption as a replacement for current `vacuum_truncate`
implementation. Remove `vacuum_truncate_set` additional flag and using
`TERNARY_UNSET` value instead.
---
src/backend/access/common/reloptions.c | 129 ++++++++++++++++++++-----
src/backend/commands/vacuum.c | 4 +-
src/include/access/reloptions.h | 26 ++---
src/include/c.h | 16 +++
src/include/utils/rel.h | 3 +-
5 files changed, 135 insertions(+), 43 deletions(-)
diff --git a/src/backend/access/common/reloptions.c b/src/backend/access/common/reloptions.c
index 0af3fea68f..f543f11001 100644
--- a/src/backend/access/common/reloptions.c
+++ b/src/backend/access/common/reloptions.c
@@ -40,9 +40,9 @@
*
* To add an option:
*
- * (i) decide on a type (bool, integer, real, enum, string), name, default
- * value, upper and lower bounds (if applicable); for strings, consider a
- * validation routine.
+ * (i) decide on a type (bool, ternary, integer, real, enum, string), name,
+ * default value, upper and lower bounds (if applicable); for strings,
+ * consider a validation routine.
* (ii) add a record below (or use add_<type>_reloption).
* (iii) add it to the appropriate options struct (perhaps StdRdOptions)
* (iv) add it to the appropriate handling routine (perhaps
@@ -147,15 +147,6 @@ static relopt_bool boolRelOpts[] =
},
false
},
- {
- {
- "vacuum_truncate",
- "Enables vacuum to truncate empty pages at the end of this table",
- RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
- ShareUpdateExclusiveLock
- },
- true
- },
{
{
"deduplicate_items",
@@ -170,6 +161,21 @@ static relopt_bool boolRelOpts[] =
{{NULL}}
};
+static relopt_ternary ternaryRelOpts[] =
+{
+ {
+ {
+ "vacuum_truncate",
+ "Enables vacuum to truncate empty pages at the end of this table",
+ RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
+ ShareUpdateExclusiveLock
+ },
+ TERNARY_UNSET
+ },
+ /* list terminator */
+ {{NULL}}
+};
+
static relopt_int intRelOpts[] =
{
{
@@ -600,6 +606,13 @@ initialize_reloptions(void)
boolRelOpts[i].gen.lockmode));
j++;
}
+ for (i = 0; ternaryRelOpts[i].gen.name; i++)
+ {
+ Assert(DoLockModesConflict(ternaryRelOpts[i].gen.lockmode,
+ ternaryRelOpts[i].gen.lockmode));
+ j++;
+ }
+
for (i = 0; intRelOpts[i].gen.name; i++)
{
Assert(DoLockModesConflict(intRelOpts[i].gen.lockmode,
@@ -640,6 +653,14 @@ initialize_reloptions(void)
j++;
}
+ for (i = 0; ternaryRelOpts[i].gen.name; i++)
+ {
+ relOpts[j] = &ternaryRelOpts[i].gen;
+ relOpts[j]->type = RELOPT_TYPE_TERNARY;
+ relOpts[j]->namelen = strlen(relOpts[j]->name);
+ j++;
+ }
+
for (i = 0; intRelOpts[i].gen.name; i++)
{
relOpts[j] = &intRelOpts[i].gen;
@@ -800,6 +821,9 @@ allocate_reloption(bits32 kinds, int type, const char *name, const char *desc,
case RELOPT_TYPE_BOOL:
size = sizeof(relopt_bool);
break;
+ case RELOPT_TYPE_TERNARY:
+ size = sizeof(relopt_ternary);
+ break;
case RELOPT_TYPE_INT:
size = sizeof(relopt_int);
break;
@@ -883,6 +907,54 @@ add_local_bool_reloption(local_relopts *relopts, const char *name,
add_local_reloption(relopts, (relopt_gen *) newoption, offset);
}
+/*
+ * init_ternary_reloption
+ * Allocate and initialize a new ternary reloption
+ */
+static relopt_ternary *
+init_ternary_reloption(bits32 kinds, const char *name, const char *desc,
+ ternary default_val, LOCKMODE lockmode)
+{
+ relopt_ternary *newoption;
+
+ newoption = (relopt_ternary *) allocate_reloption(kinds,
+ RELOPT_TYPE_TERNARY, name, desc, lockmode);
+ newoption->default_val = default_val;
+
+ return newoption;
+}
+
+/*
+ * add_ternary_reloption
+ * Add a new ternary reloption
+ */
+void
+add_ternary_reloption(bits32 kinds, const char *name, const char *desc,
+ ternary default_val, LOCKMODE lockmode)
+{
+ relopt_ternary *newoption = init_ternary_reloption(kinds, name, desc,
+ default_val, lockmode);
+
+ add_reloption((relopt_gen *) newoption);
+}
+
+/*
+ * add_local_ternary_reloption
+ * Add a new ternary local reloption
+ *
+ * 'offset' is offset of ternary-typed field.
+ */
+void
+add_local_ternary_reloption(local_relopts *relopts, const char *name,
+ const char *desc, ternary default_val,
+ int offset)
+{
+ relopt_ternary *newoption = init_ternary_reloption(RELOPT_KIND_LOCAL,
+ name, desc,
+ default_val, 0);
+
+ add_local_reloption(relopts, (relopt_gen *) newoption, offset);
+}
/*
* init_real_reloption
@@ -1617,6 +1689,18 @@ parse_one_reloption(relopt_value *option, char *text_str, int text_len,
option->gen->name, value)));
}
break;
+ case RELOPT_TYPE_TERNARY:
+ {
+ bool b;
+ parsed = parse_bool(value, &b);
+ option->values.ternary_val = b ? TERNARY_TRUE : TERNARY_FALSE;
+ if (validate && !parsed)
+ ereport(ERROR,
+ (errcode(ERRCODE_INVALID_PARAMETER_VALUE),
+ errmsg("invalid value for ternary option \"%s\": %s",
+ option->gen->name, value)));
+ }
+ break;
case RELOPT_TYPE_INT:
{
relopt_int *optint = (relopt_int *) option->gen;
@@ -1780,17 +1864,6 @@ fillRelOptions(void *rdopts, Size basesize,
char *itempos = ((char *) rdopts) + elems[j].offset;
char *string_val;
- /*
- * If isset_offset is provided, store whether the reloption is
- * set there.
- */
- if (elems[j].isset_offset > 0)
- {
- char *setpos = ((char *) rdopts) + elems[j].isset_offset;
-
- *(bool *) setpos = options[i].isset;
- }
-
switch (options[i].gen->type)
{
case RELOPT_TYPE_BOOL:
@@ -1798,6 +1871,11 @@ fillRelOptions(void *rdopts, Size basesize,
options[i].values.bool_val :
((relopt_bool *) options[i].gen)->default_val;
break;
+ case RELOPT_TYPE_TERNARY:
+ *(ternary *) itempos = options[i].isset ?
+ options[i].values.ternary_val :
+ ((relopt_ternary *) options[i].gen)->default_val;
+ break;
case RELOPT_TYPE_INT:
*(int *) itempos = options[i].isset ?
options[i].values.int_val :
@@ -1912,8 +1990,8 @@ default_reloptions(Datum reloptions, bool validate, relopt_kind kind)
offsetof(StdRdOptions, parallel_workers)},
{"vacuum_index_cleanup", RELOPT_TYPE_ENUM,
offsetof(StdRdOptions, vacuum_index_cleanup)},
- {"vacuum_truncate", RELOPT_TYPE_BOOL,
- offsetof(StdRdOptions, vacuum_truncate), offsetof(StdRdOptions, vacuum_truncate_set)},
+ {"vacuum_truncate", RELOPT_TYPE_TERNARY,
+ offsetof(StdRdOptions, vacuum_truncate)},
{"vacuum_max_eager_freeze_failure_rate", RELOPT_TYPE_REAL,
offsetof(StdRdOptions, vacuum_max_eager_freeze_failure_rate)}
};
@@ -1993,7 +2071,6 @@ build_local_reloptions(local_relopts *relopts, Datum options, bool validate)
elems[i].optname = opt->option->name;
elems[i].opttype = opt->option->type;
elems[i].offset = opt->offset;
- elems[i].isset_offset = 0; /* not supported for local relopts yet */
i++;
}
diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c
index 733ef40ae7..977babff54 100644
--- a/src/backend/commands/vacuum.c
+++ b/src/backend/commands/vacuum.c
@@ -2221,9 +2221,9 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams params,
{
StdRdOptions *opts = (StdRdOptions *) rel->rd_options;
- if (opts && opts->vacuum_truncate_set)
+ if (opts && opts->vacuum_truncate != TERNARY_UNSET)
{
- if (opts->vacuum_truncate)
+ if (opts->vacuum_truncate == TERNARY_TRUE)
params.truncate = VACOPTVALUE_ENABLED;
else
params.truncate = VACOPTVALUE_DISABLED;
diff --git a/src/include/access/reloptions.h b/src/include/access/reloptions.h
index a604a4702c..a436697658 100644
--- a/src/include/access/reloptions.h
+++ b/src/include/access/reloptions.h
@@ -29,6 +29,7 @@
typedef enum relopt_type
{
RELOPT_TYPE_BOOL,
+ RELOPT_TYPE_TERNARY, /* on, off, unset */
RELOPT_TYPE_INT,
RELOPT_TYPE_REAL,
RELOPT_TYPE_ENUM,
@@ -80,6 +81,7 @@ typedef struct relopt_value
union
{
bool bool_val;
+ ternary ternary_val;
int int_val;
double real_val;
int enum_val;
@@ -94,6 +96,12 @@ typedef struct relopt_bool
bool default_val;
} relopt_bool;
+typedef struct relopt_rernary
+{
+ relopt_gen gen;
+ int default_val;
+} relopt_ternary;
+
typedef struct relopt_int
{
relopt_gen gen;
@@ -152,19 +160,6 @@ typedef struct
const char *optname; /* option's name */
relopt_type opttype; /* option's datatype */
int offset; /* offset of field in result struct */
-
- /*
- * isset_offset is an optional offset of a field in the result struct that
- * stores whether the option is explicitly set for the relation or if it
- * just picked up the default value. In most cases, this can be
- * accomplished by giving the reloption a special out-of-range default
- * value (e.g., some integer reloptions use -2), but this isn't always
- * possible. For example, a Boolean reloption cannot be given an
- * out-of-range default, so we need another way to discover the source of
- * its value. This offset is only used if given a value greater than
- * zero.
- */
- int isset_offset;
} relopt_parse_elt;
/* Local reloption definition */
@@ -195,6 +190,8 @@ typedef struct local_relopts
extern relopt_kind add_reloption_kind(void);
extern void add_bool_reloption(bits32 kinds, const char *name, const char *desc,
bool default_val, LOCKMODE lockmode);
+extern void add_ternary_reloption(bits32 kinds, const char *name,
+ const char *desc, int default_val, LOCKMODE lockmode);
extern void add_int_reloption(bits32 kinds, const char *name, const char *desc,
int default_val, int min_val, int max_val,
LOCKMODE lockmode);
@@ -214,6 +211,9 @@ extern void register_reloptions_validator(local_relopts *relopts,
extern void add_local_bool_reloption(local_relopts *relopts, const char *name,
const char *desc, bool default_val,
int offset);
+extern void add_local_ternary_reloption(local_relopts *relopts,
+ const char *name, const char *desc,
+ ternary default_val, int offset);
extern void add_local_int_reloption(local_relopts *relopts, const char *name,
const char *desc, int default_val,
int min_val, int max_val, int offset);
diff --git a/src/include/c.h b/src/include/c.h
index 39022f8a9d..9c59ccccf8 100644
--- a/src/include/c.h
+++ b/src/include/c.h
@@ -488,6 +488,22 @@ typedef void (*pg_funcptr_t) (void);
#include <stdbool.h>
+/*
+ * ternary
+ * Boolean value with an extrea "unset" option
+ *
+ * Ternary data type is used in relation options that can be "true", "false" or
+ * "unset". Since relation options are used deep inside the PostgreSQL code,
+ * this type is declared globally.
+*/
+
+typedef enum ternary
+{
+ TERNARY_FALSE = 0,
+ TERNARY_TRUE = 1,
+ TERNARY_UNSET = -1
+} ternary;
+
/* ----------------------------------------------------------------
* Section 3: standard system types
diff --git a/src/include/utils/rel.h b/src/include/utils/rel.h
index b552359915..7346d618f9 100644
--- a/src/include/utils/rel.h
+++ b/src/include/utils/rel.h
@@ -346,8 +346,7 @@ typedef struct StdRdOptions
bool user_catalog_table; /* use as an additional catalog relation */
int parallel_workers; /* max number of parallel workers */
StdRdOptIndexCleanup vacuum_index_cleanup; /* controls index vacuuming */
- bool vacuum_truncate; /* enables vacuum to truncate a relation */
- bool vacuum_truncate_set; /* whether vacuum_truncate is set */
+ ternary vacuum_truncate; /* enables vacuum to truncate a relation */
/*
* Fraction of pages in a relation that vacuum can eagerly scan and fail
--
2.39.2
--nextPart4155600.3ZeAukHxDK
Content-Disposition: attachment;
filename="v2a-0003-Add-alias-to-be-used-as-unset-state.patch"
Content-Transfer-Encoding: 7Bit
Content-Type: text/x-patch; charset="unicode-2-0-utf-8";
name="v2a-0003-Add-alias-to-be-used-as-unset-state.patch"
^ permalink raw reply [nested|flat] 868+ messages in thread
* [PATCH v2a 2/4] Introduce ternary reloptions
@ 2025-09-04 14:41 Nikolay Shaplov <[email protected]>
0 siblings, 0 replies; 868+ messages in thread
From: Nikolay Shaplov @ 2025-09-04 14:41 UTC (permalink / raw)
Introduce ternary reloption as a replacement for current `vacuum_truncate`
implementation. Remove `vacuum_truncate_set` additional flag and using
`TERNARY_UNSET` value instead.
---
src/backend/access/common/reloptions.c | 129 ++++++++++++++++++++-----
src/backend/commands/vacuum.c | 4 +-
src/include/access/reloptions.h | 26 ++---
src/include/c.h | 16 +++
src/include/utils/rel.h | 3 +-
5 files changed, 135 insertions(+), 43 deletions(-)
diff --git a/src/backend/access/common/reloptions.c b/src/backend/access/common/reloptions.c
index 0af3fea68f..f543f11001 100644
--- a/src/backend/access/common/reloptions.c
+++ b/src/backend/access/common/reloptions.c
@@ -40,9 +40,9 @@
*
* To add an option:
*
- * (i) decide on a type (bool, integer, real, enum, string), name, default
- * value, upper and lower bounds (if applicable); for strings, consider a
- * validation routine.
+ * (i) decide on a type (bool, ternary, integer, real, enum, string), name,
+ * default value, upper and lower bounds (if applicable); for strings,
+ * consider a validation routine.
* (ii) add a record below (or use add_<type>_reloption).
* (iii) add it to the appropriate options struct (perhaps StdRdOptions)
* (iv) add it to the appropriate handling routine (perhaps
@@ -147,15 +147,6 @@ static relopt_bool boolRelOpts[] =
},
false
},
- {
- {
- "vacuum_truncate",
- "Enables vacuum to truncate empty pages at the end of this table",
- RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
- ShareUpdateExclusiveLock
- },
- true
- },
{
{
"deduplicate_items",
@@ -170,6 +161,21 @@ static relopt_bool boolRelOpts[] =
{{NULL}}
};
+static relopt_ternary ternaryRelOpts[] =
+{
+ {
+ {
+ "vacuum_truncate",
+ "Enables vacuum to truncate empty pages at the end of this table",
+ RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
+ ShareUpdateExclusiveLock
+ },
+ TERNARY_UNSET
+ },
+ /* list terminator */
+ {{NULL}}
+};
+
static relopt_int intRelOpts[] =
{
{
@@ -600,6 +606,13 @@ initialize_reloptions(void)
boolRelOpts[i].gen.lockmode));
j++;
}
+ for (i = 0; ternaryRelOpts[i].gen.name; i++)
+ {
+ Assert(DoLockModesConflict(ternaryRelOpts[i].gen.lockmode,
+ ternaryRelOpts[i].gen.lockmode));
+ j++;
+ }
+
for (i = 0; intRelOpts[i].gen.name; i++)
{
Assert(DoLockModesConflict(intRelOpts[i].gen.lockmode,
@@ -640,6 +653,14 @@ initialize_reloptions(void)
j++;
}
+ for (i = 0; ternaryRelOpts[i].gen.name; i++)
+ {
+ relOpts[j] = &ternaryRelOpts[i].gen;
+ relOpts[j]->type = RELOPT_TYPE_TERNARY;
+ relOpts[j]->namelen = strlen(relOpts[j]->name);
+ j++;
+ }
+
for (i = 0; intRelOpts[i].gen.name; i++)
{
relOpts[j] = &intRelOpts[i].gen;
@@ -800,6 +821,9 @@ allocate_reloption(bits32 kinds, int type, const char *name, const char *desc,
case RELOPT_TYPE_BOOL:
size = sizeof(relopt_bool);
break;
+ case RELOPT_TYPE_TERNARY:
+ size = sizeof(relopt_ternary);
+ break;
case RELOPT_TYPE_INT:
size = sizeof(relopt_int);
break;
@@ -883,6 +907,54 @@ add_local_bool_reloption(local_relopts *relopts, const char *name,
add_local_reloption(relopts, (relopt_gen *) newoption, offset);
}
+/*
+ * init_ternary_reloption
+ * Allocate and initialize a new ternary reloption
+ */
+static relopt_ternary *
+init_ternary_reloption(bits32 kinds, const char *name, const char *desc,
+ ternary default_val, LOCKMODE lockmode)
+{
+ relopt_ternary *newoption;
+
+ newoption = (relopt_ternary *) allocate_reloption(kinds,
+ RELOPT_TYPE_TERNARY, name, desc, lockmode);
+ newoption->default_val = default_val;
+
+ return newoption;
+}
+
+/*
+ * add_ternary_reloption
+ * Add a new ternary reloption
+ */
+void
+add_ternary_reloption(bits32 kinds, const char *name, const char *desc,
+ ternary default_val, LOCKMODE lockmode)
+{
+ relopt_ternary *newoption = init_ternary_reloption(kinds, name, desc,
+ default_val, lockmode);
+
+ add_reloption((relopt_gen *) newoption);
+}
+
+/*
+ * add_local_ternary_reloption
+ * Add a new ternary local reloption
+ *
+ * 'offset' is offset of ternary-typed field.
+ */
+void
+add_local_ternary_reloption(local_relopts *relopts, const char *name,
+ const char *desc, ternary default_val,
+ int offset)
+{
+ relopt_ternary *newoption = init_ternary_reloption(RELOPT_KIND_LOCAL,
+ name, desc,
+ default_val, 0);
+
+ add_local_reloption(relopts, (relopt_gen *) newoption, offset);
+}
/*
* init_real_reloption
@@ -1617,6 +1689,18 @@ parse_one_reloption(relopt_value *option, char *text_str, int text_len,
option->gen->name, value)));
}
break;
+ case RELOPT_TYPE_TERNARY:
+ {
+ bool b;
+ parsed = parse_bool(value, &b);
+ option->values.ternary_val = b ? TERNARY_TRUE : TERNARY_FALSE;
+ if (validate && !parsed)
+ ereport(ERROR,
+ (errcode(ERRCODE_INVALID_PARAMETER_VALUE),
+ errmsg("invalid value for ternary option \"%s\": %s",
+ option->gen->name, value)));
+ }
+ break;
case RELOPT_TYPE_INT:
{
relopt_int *optint = (relopt_int *) option->gen;
@@ -1780,17 +1864,6 @@ fillRelOptions(void *rdopts, Size basesize,
char *itempos = ((char *) rdopts) + elems[j].offset;
char *string_val;
- /*
- * If isset_offset is provided, store whether the reloption is
- * set there.
- */
- if (elems[j].isset_offset > 0)
- {
- char *setpos = ((char *) rdopts) + elems[j].isset_offset;
-
- *(bool *) setpos = options[i].isset;
- }
-
switch (options[i].gen->type)
{
case RELOPT_TYPE_BOOL:
@@ -1798,6 +1871,11 @@ fillRelOptions(void *rdopts, Size basesize,
options[i].values.bool_val :
((relopt_bool *) options[i].gen)->default_val;
break;
+ case RELOPT_TYPE_TERNARY:
+ *(ternary *) itempos = options[i].isset ?
+ options[i].values.ternary_val :
+ ((relopt_ternary *) options[i].gen)->default_val;
+ break;
case RELOPT_TYPE_INT:
*(int *) itempos = options[i].isset ?
options[i].values.int_val :
@@ -1912,8 +1990,8 @@ default_reloptions(Datum reloptions, bool validate, relopt_kind kind)
offsetof(StdRdOptions, parallel_workers)},
{"vacuum_index_cleanup", RELOPT_TYPE_ENUM,
offsetof(StdRdOptions, vacuum_index_cleanup)},
- {"vacuum_truncate", RELOPT_TYPE_BOOL,
- offsetof(StdRdOptions, vacuum_truncate), offsetof(StdRdOptions, vacuum_truncate_set)},
+ {"vacuum_truncate", RELOPT_TYPE_TERNARY,
+ offsetof(StdRdOptions, vacuum_truncate)},
{"vacuum_max_eager_freeze_failure_rate", RELOPT_TYPE_REAL,
offsetof(StdRdOptions, vacuum_max_eager_freeze_failure_rate)}
};
@@ -1993,7 +2071,6 @@ build_local_reloptions(local_relopts *relopts, Datum options, bool validate)
elems[i].optname = opt->option->name;
elems[i].opttype = opt->option->type;
elems[i].offset = opt->offset;
- elems[i].isset_offset = 0; /* not supported for local relopts yet */
i++;
}
diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c
index 733ef40ae7..977babff54 100644
--- a/src/backend/commands/vacuum.c
+++ b/src/backend/commands/vacuum.c
@@ -2221,9 +2221,9 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams params,
{
StdRdOptions *opts = (StdRdOptions *) rel->rd_options;
- if (opts && opts->vacuum_truncate_set)
+ if (opts && opts->vacuum_truncate != TERNARY_UNSET)
{
- if (opts->vacuum_truncate)
+ if (opts->vacuum_truncate == TERNARY_TRUE)
params.truncate = VACOPTVALUE_ENABLED;
else
params.truncate = VACOPTVALUE_DISABLED;
diff --git a/src/include/access/reloptions.h b/src/include/access/reloptions.h
index a604a4702c..a436697658 100644
--- a/src/include/access/reloptions.h
+++ b/src/include/access/reloptions.h
@@ -29,6 +29,7 @@
typedef enum relopt_type
{
RELOPT_TYPE_BOOL,
+ RELOPT_TYPE_TERNARY, /* on, off, unset */
RELOPT_TYPE_INT,
RELOPT_TYPE_REAL,
RELOPT_TYPE_ENUM,
@@ -80,6 +81,7 @@ typedef struct relopt_value
union
{
bool bool_val;
+ ternary ternary_val;
int int_val;
double real_val;
int enum_val;
@@ -94,6 +96,12 @@ typedef struct relopt_bool
bool default_val;
} relopt_bool;
+typedef struct relopt_rernary
+{
+ relopt_gen gen;
+ int default_val;
+} relopt_ternary;
+
typedef struct relopt_int
{
relopt_gen gen;
@@ -152,19 +160,6 @@ typedef struct
const char *optname; /* option's name */
relopt_type opttype; /* option's datatype */
int offset; /* offset of field in result struct */
-
- /*
- * isset_offset is an optional offset of a field in the result struct that
- * stores whether the option is explicitly set for the relation or if it
- * just picked up the default value. In most cases, this can be
- * accomplished by giving the reloption a special out-of-range default
- * value (e.g., some integer reloptions use -2), but this isn't always
- * possible. For example, a Boolean reloption cannot be given an
- * out-of-range default, so we need another way to discover the source of
- * its value. This offset is only used if given a value greater than
- * zero.
- */
- int isset_offset;
} relopt_parse_elt;
/* Local reloption definition */
@@ -195,6 +190,8 @@ typedef struct local_relopts
extern relopt_kind add_reloption_kind(void);
extern void add_bool_reloption(bits32 kinds, const char *name, const char *desc,
bool default_val, LOCKMODE lockmode);
+extern void add_ternary_reloption(bits32 kinds, const char *name,
+ const char *desc, int default_val, LOCKMODE lockmode);
extern void add_int_reloption(bits32 kinds, const char *name, const char *desc,
int default_val, int min_val, int max_val,
LOCKMODE lockmode);
@@ -214,6 +211,9 @@ extern void register_reloptions_validator(local_relopts *relopts,
extern void add_local_bool_reloption(local_relopts *relopts, const char *name,
const char *desc, bool default_val,
int offset);
+extern void add_local_ternary_reloption(local_relopts *relopts,
+ const char *name, const char *desc,
+ ternary default_val, int offset);
extern void add_local_int_reloption(local_relopts *relopts, const char *name,
const char *desc, int default_val,
int min_val, int max_val, int offset);
diff --git a/src/include/c.h b/src/include/c.h
index 39022f8a9d..9c59ccccf8 100644
--- a/src/include/c.h
+++ b/src/include/c.h
@@ -488,6 +488,22 @@ typedef void (*pg_funcptr_t) (void);
#include <stdbool.h>
+/*
+ * ternary
+ * Boolean value with an extrea "unset" option
+ *
+ * Ternary data type is used in relation options that can be "true", "false" or
+ * "unset". Since relation options are used deep inside the PostgreSQL code,
+ * this type is declared globally.
+*/
+
+typedef enum ternary
+{
+ TERNARY_FALSE = 0,
+ TERNARY_TRUE = 1,
+ TERNARY_UNSET = -1
+} ternary;
+
/* ----------------------------------------------------------------
* Section 3: standard system types
diff --git a/src/include/utils/rel.h b/src/include/utils/rel.h
index b552359915..7346d618f9 100644
--- a/src/include/utils/rel.h
+++ b/src/include/utils/rel.h
@@ -346,8 +346,7 @@ typedef struct StdRdOptions
bool user_catalog_table; /* use as an additional catalog relation */
int parallel_workers; /* max number of parallel workers */
StdRdOptIndexCleanup vacuum_index_cleanup; /* controls index vacuuming */
- bool vacuum_truncate; /* enables vacuum to truncate a relation */
- bool vacuum_truncate_set; /* whether vacuum_truncate is set */
+ ternary vacuum_truncate; /* enables vacuum to truncate a relation */
/*
* Fraction of pages in a relation that vacuum can eagerly scan and fail
--
2.39.2
--nextPart4155600.3ZeAukHxDK
Content-Disposition: attachment;
filename="v2a-0003-Add-alias-to-be-used-as-unset-state.patch"
Content-Transfer-Encoding: 7Bit
Content-Type: text/x-patch; charset="unicode-2-0-utf-8";
name="v2a-0003-Add-alias-to-be-used-as-unset-state.patch"
^ permalink raw reply [nested|flat] 868+ messages in thread
* [PATCH v2a 2/4] Introduce ternary reloptions
@ 2025-09-04 14:41 Nikolay Shaplov <[email protected]>
0 siblings, 0 replies; 868+ messages in thread
From: Nikolay Shaplov @ 2025-09-04 14:41 UTC (permalink / raw)
Introduce ternary reloption as a replacement for current `vacuum_truncate`
implementation. Remove `vacuum_truncate_set` additional flag and using
`TERNARY_UNSET` value instead.
---
src/backend/access/common/reloptions.c | 129 ++++++++++++++++++++-----
src/backend/commands/vacuum.c | 4 +-
src/include/access/reloptions.h | 26 ++---
src/include/c.h | 16 +++
src/include/utils/rel.h | 3 +-
5 files changed, 135 insertions(+), 43 deletions(-)
diff --git a/src/backend/access/common/reloptions.c b/src/backend/access/common/reloptions.c
index 0af3fea68f..f543f11001 100644
--- a/src/backend/access/common/reloptions.c
+++ b/src/backend/access/common/reloptions.c
@@ -40,9 +40,9 @@
*
* To add an option:
*
- * (i) decide on a type (bool, integer, real, enum, string), name, default
- * value, upper and lower bounds (if applicable); for strings, consider a
- * validation routine.
+ * (i) decide on a type (bool, ternary, integer, real, enum, string), name,
+ * default value, upper and lower bounds (if applicable); for strings,
+ * consider a validation routine.
* (ii) add a record below (or use add_<type>_reloption).
* (iii) add it to the appropriate options struct (perhaps StdRdOptions)
* (iv) add it to the appropriate handling routine (perhaps
@@ -147,15 +147,6 @@ static relopt_bool boolRelOpts[] =
},
false
},
- {
- {
- "vacuum_truncate",
- "Enables vacuum to truncate empty pages at the end of this table",
- RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
- ShareUpdateExclusiveLock
- },
- true
- },
{
{
"deduplicate_items",
@@ -170,6 +161,21 @@ static relopt_bool boolRelOpts[] =
{{NULL}}
};
+static relopt_ternary ternaryRelOpts[] =
+{
+ {
+ {
+ "vacuum_truncate",
+ "Enables vacuum to truncate empty pages at the end of this table",
+ RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
+ ShareUpdateExclusiveLock
+ },
+ TERNARY_UNSET
+ },
+ /* list terminator */
+ {{NULL}}
+};
+
static relopt_int intRelOpts[] =
{
{
@@ -600,6 +606,13 @@ initialize_reloptions(void)
boolRelOpts[i].gen.lockmode));
j++;
}
+ for (i = 0; ternaryRelOpts[i].gen.name; i++)
+ {
+ Assert(DoLockModesConflict(ternaryRelOpts[i].gen.lockmode,
+ ternaryRelOpts[i].gen.lockmode));
+ j++;
+ }
+
for (i = 0; intRelOpts[i].gen.name; i++)
{
Assert(DoLockModesConflict(intRelOpts[i].gen.lockmode,
@@ -640,6 +653,14 @@ initialize_reloptions(void)
j++;
}
+ for (i = 0; ternaryRelOpts[i].gen.name; i++)
+ {
+ relOpts[j] = &ternaryRelOpts[i].gen;
+ relOpts[j]->type = RELOPT_TYPE_TERNARY;
+ relOpts[j]->namelen = strlen(relOpts[j]->name);
+ j++;
+ }
+
for (i = 0; intRelOpts[i].gen.name; i++)
{
relOpts[j] = &intRelOpts[i].gen;
@@ -800,6 +821,9 @@ allocate_reloption(bits32 kinds, int type, const char *name, const char *desc,
case RELOPT_TYPE_BOOL:
size = sizeof(relopt_bool);
break;
+ case RELOPT_TYPE_TERNARY:
+ size = sizeof(relopt_ternary);
+ break;
case RELOPT_TYPE_INT:
size = sizeof(relopt_int);
break;
@@ -883,6 +907,54 @@ add_local_bool_reloption(local_relopts *relopts, const char *name,
add_local_reloption(relopts, (relopt_gen *) newoption, offset);
}
+/*
+ * init_ternary_reloption
+ * Allocate and initialize a new ternary reloption
+ */
+static relopt_ternary *
+init_ternary_reloption(bits32 kinds, const char *name, const char *desc,
+ ternary default_val, LOCKMODE lockmode)
+{
+ relopt_ternary *newoption;
+
+ newoption = (relopt_ternary *) allocate_reloption(kinds,
+ RELOPT_TYPE_TERNARY, name, desc, lockmode);
+ newoption->default_val = default_val;
+
+ return newoption;
+}
+
+/*
+ * add_ternary_reloption
+ * Add a new ternary reloption
+ */
+void
+add_ternary_reloption(bits32 kinds, const char *name, const char *desc,
+ ternary default_val, LOCKMODE lockmode)
+{
+ relopt_ternary *newoption = init_ternary_reloption(kinds, name, desc,
+ default_val, lockmode);
+
+ add_reloption((relopt_gen *) newoption);
+}
+
+/*
+ * add_local_ternary_reloption
+ * Add a new ternary local reloption
+ *
+ * 'offset' is offset of ternary-typed field.
+ */
+void
+add_local_ternary_reloption(local_relopts *relopts, const char *name,
+ const char *desc, ternary default_val,
+ int offset)
+{
+ relopt_ternary *newoption = init_ternary_reloption(RELOPT_KIND_LOCAL,
+ name, desc,
+ default_val, 0);
+
+ add_local_reloption(relopts, (relopt_gen *) newoption, offset);
+}
/*
* init_real_reloption
@@ -1617,6 +1689,18 @@ parse_one_reloption(relopt_value *option, char *text_str, int text_len,
option->gen->name, value)));
}
break;
+ case RELOPT_TYPE_TERNARY:
+ {
+ bool b;
+ parsed = parse_bool(value, &b);
+ option->values.ternary_val = b ? TERNARY_TRUE : TERNARY_FALSE;
+ if (validate && !parsed)
+ ereport(ERROR,
+ (errcode(ERRCODE_INVALID_PARAMETER_VALUE),
+ errmsg("invalid value for ternary option \"%s\": %s",
+ option->gen->name, value)));
+ }
+ break;
case RELOPT_TYPE_INT:
{
relopt_int *optint = (relopt_int *) option->gen;
@@ -1780,17 +1864,6 @@ fillRelOptions(void *rdopts, Size basesize,
char *itempos = ((char *) rdopts) + elems[j].offset;
char *string_val;
- /*
- * If isset_offset is provided, store whether the reloption is
- * set there.
- */
- if (elems[j].isset_offset > 0)
- {
- char *setpos = ((char *) rdopts) + elems[j].isset_offset;
-
- *(bool *) setpos = options[i].isset;
- }
-
switch (options[i].gen->type)
{
case RELOPT_TYPE_BOOL:
@@ -1798,6 +1871,11 @@ fillRelOptions(void *rdopts, Size basesize,
options[i].values.bool_val :
((relopt_bool *) options[i].gen)->default_val;
break;
+ case RELOPT_TYPE_TERNARY:
+ *(ternary *) itempos = options[i].isset ?
+ options[i].values.ternary_val :
+ ((relopt_ternary *) options[i].gen)->default_val;
+ break;
case RELOPT_TYPE_INT:
*(int *) itempos = options[i].isset ?
options[i].values.int_val :
@@ -1912,8 +1990,8 @@ default_reloptions(Datum reloptions, bool validate, relopt_kind kind)
offsetof(StdRdOptions, parallel_workers)},
{"vacuum_index_cleanup", RELOPT_TYPE_ENUM,
offsetof(StdRdOptions, vacuum_index_cleanup)},
- {"vacuum_truncate", RELOPT_TYPE_BOOL,
- offsetof(StdRdOptions, vacuum_truncate), offsetof(StdRdOptions, vacuum_truncate_set)},
+ {"vacuum_truncate", RELOPT_TYPE_TERNARY,
+ offsetof(StdRdOptions, vacuum_truncate)},
{"vacuum_max_eager_freeze_failure_rate", RELOPT_TYPE_REAL,
offsetof(StdRdOptions, vacuum_max_eager_freeze_failure_rate)}
};
@@ -1993,7 +2071,6 @@ build_local_reloptions(local_relopts *relopts, Datum options, bool validate)
elems[i].optname = opt->option->name;
elems[i].opttype = opt->option->type;
elems[i].offset = opt->offset;
- elems[i].isset_offset = 0; /* not supported for local relopts yet */
i++;
}
diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c
index 733ef40ae7..977babff54 100644
--- a/src/backend/commands/vacuum.c
+++ b/src/backend/commands/vacuum.c
@@ -2221,9 +2221,9 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams params,
{
StdRdOptions *opts = (StdRdOptions *) rel->rd_options;
- if (opts && opts->vacuum_truncate_set)
+ if (opts && opts->vacuum_truncate != TERNARY_UNSET)
{
- if (opts->vacuum_truncate)
+ if (opts->vacuum_truncate == TERNARY_TRUE)
params.truncate = VACOPTVALUE_ENABLED;
else
params.truncate = VACOPTVALUE_DISABLED;
diff --git a/src/include/access/reloptions.h b/src/include/access/reloptions.h
index a604a4702c..a436697658 100644
--- a/src/include/access/reloptions.h
+++ b/src/include/access/reloptions.h
@@ -29,6 +29,7 @@
typedef enum relopt_type
{
RELOPT_TYPE_BOOL,
+ RELOPT_TYPE_TERNARY, /* on, off, unset */
RELOPT_TYPE_INT,
RELOPT_TYPE_REAL,
RELOPT_TYPE_ENUM,
@@ -80,6 +81,7 @@ typedef struct relopt_value
union
{
bool bool_val;
+ ternary ternary_val;
int int_val;
double real_val;
int enum_val;
@@ -94,6 +96,12 @@ typedef struct relopt_bool
bool default_val;
} relopt_bool;
+typedef struct relopt_rernary
+{
+ relopt_gen gen;
+ int default_val;
+} relopt_ternary;
+
typedef struct relopt_int
{
relopt_gen gen;
@@ -152,19 +160,6 @@ typedef struct
const char *optname; /* option's name */
relopt_type opttype; /* option's datatype */
int offset; /* offset of field in result struct */
-
- /*
- * isset_offset is an optional offset of a field in the result struct that
- * stores whether the option is explicitly set for the relation or if it
- * just picked up the default value. In most cases, this can be
- * accomplished by giving the reloption a special out-of-range default
- * value (e.g., some integer reloptions use -2), but this isn't always
- * possible. For example, a Boolean reloption cannot be given an
- * out-of-range default, so we need another way to discover the source of
- * its value. This offset is only used if given a value greater than
- * zero.
- */
- int isset_offset;
} relopt_parse_elt;
/* Local reloption definition */
@@ -195,6 +190,8 @@ typedef struct local_relopts
extern relopt_kind add_reloption_kind(void);
extern void add_bool_reloption(bits32 kinds, const char *name, const char *desc,
bool default_val, LOCKMODE lockmode);
+extern void add_ternary_reloption(bits32 kinds, const char *name,
+ const char *desc, int default_val, LOCKMODE lockmode);
extern void add_int_reloption(bits32 kinds, const char *name, const char *desc,
int default_val, int min_val, int max_val,
LOCKMODE lockmode);
@@ -214,6 +211,9 @@ extern void register_reloptions_validator(local_relopts *relopts,
extern void add_local_bool_reloption(local_relopts *relopts, const char *name,
const char *desc, bool default_val,
int offset);
+extern void add_local_ternary_reloption(local_relopts *relopts,
+ const char *name, const char *desc,
+ ternary default_val, int offset);
extern void add_local_int_reloption(local_relopts *relopts, const char *name,
const char *desc, int default_val,
int min_val, int max_val, int offset);
diff --git a/src/include/c.h b/src/include/c.h
index 39022f8a9d..9c59ccccf8 100644
--- a/src/include/c.h
+++ b/src/include/c.h
@@ -488,6 +488,22 @@ typedef void (*pg_funcptr_t) (void);
#include <stdbool.h>
+/*
+ * ternary
+ * Boolean value with an extrea "unset" option
+ *
+ * Ternary data type is used in relation options that can be "true", "false" or
+ * "unset". Since relation options are used deep inside the PostgreSQL code,
+ * this type is declared globally.
+*/
+
+typedef enum ternary
+{
+ TERNARY_FALSE = 0,
+ TERNARY_TRUE = 1,
+ TERNARY_UNSET = -1
+} ternary;
+
/* ----------------------------------------------------------------
* Section 3: standard system types
diff --git a/src/include/utils/rel.h b/src/include/utils/rel.h
index b552359915..7346d618f9 100644
--- a/src/include/utils/rel.h
+++ b/src/include/utils/rel.h
@@ -346,8 +346,7 @@ typedef struct StdRdOptions
bool user_catalog_table; /* use as an additional catalog relation */
int parallel_workers; /* max number of parallel workers */
StdRdOptIndexCleanup vacuum_index_cleanup; /* controls index vacuuming */
- bool vacuum_truncate; /* enables vacuum to truncate a relation */
- bool vacuum_truncate_set; /* whether vacuum_truncate is set */
+ ternary vacuum_truncate; /* enables vacuum to truncate a relation */
/*
* Fraction of pages in a relation that vacuum can eagerly scan and fail
--
2.39.2
--nextPart4155600.3ZeAukHxDK
Content-Disposition: attachment;
filename="v2a-0003-Add-alias-to-be-used-as-unset-state.patch"
Content-Transfer-Encoding: 7Bit
Content-Type: text/x-patch; charset="unicode-2-0-utf-8";
name="v2a-0003-Add-alias-to-be-used-as-unset-state.patch"
^ permalink raw reply [nested|flat] 868+ messages in thread
* [PATCH v2 2/4] Introduce ternary reloptions
@ 2025-09-04 14:41 Nikolay Shaplov <[email protected]>
0 siblings, 0 replies; 868+ messages in thread
From: Nikolay Shaplov @ 2025-09-04 14:41 UTC (permalink / raw)
Introduce ternary reloption as a replacement for current `vacuum_truncate`
implementation. Remove `vacuum_truncate_set` additional flag and using
`TERNARY_UNSET` value instead.
---
src/backend/access/common/reloptions.c | 129 ++++++++++++++++++++-----
src/backend/commands/vacuum.c | 4 +-
src/include/access/reloptions.h | 26 ++---
src/include/c.h | 16 +++
src/include/utils/rel.h | 3 +-
5 files changed, 135 insertions(+), 43 deletions(-)
diff --git a/src/backend/access/common/reloptions.c b/src/backend/access/common/reloptions.c
index 0af3fea68f..f543f11001 100644
--- a/src/backend/access/common/reloptions.c
+++ b/src/backend/access/common/reloptions.c
@@ -40,9 +40,9 @@
*
* To add an option:
*
- * (i) decide on a type (bool, integer, real, enum, string), name, default
- * value, upper and lower bounds (if applicable); for strings, consider a
- * validation routine.
+ * (i) decide on a type (bool, ternary, integer, real, enum, string), name,
+ * default value, upper and lower bounds (if applicable); for strings,
+ * consider a validation routine.
* (ii) add a record below (or use add_<type>_reloption).
* (iii) add it to the appropriate options struct (perhaps StdRdOptions)
* (iv) add it to the appropriate handling routine (perhaps
@@ -147,15 +147,6 @@ static relopt_bool boolRelOpts[] =
},
false
},
- {
- {
- "vacuum_truncate",
- "Enables vacuum to truncate empty pages at the end of this table",
- RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
- ShareUpdateExclusiveLock
- },
- true
- },
{
{
"deduplicate_items",
@@ -170,6 +161,21 @@ static relopt_bool boolRelOpts[] =
{{NULL}}
};
+static relopt_ternary ternaryRelOpts[] =
+{
+ {
+ {
+ "vacuum_truncate",
+ "Enables vacuum to truncate empty pages at the end of this table",
+ RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
+ ShareUpdateExclusiveLock
+ },
+ TERNARY_UNSET
+ },
+ /* list terminator */
+ {{NULL}}
+};
+
static relopt_int intRelOpts[] =
{
{
@@ -600,6 +606,13 @@ initialize_reloptions(void)
boolRelOpts[i].gen.lockmode));
j++;
}
+ for (i = 0; ternaryRelOpts[i].gen.name; i++)
+ {
+ Assert(DoLockModesConflict(ternaryRelOpts[i].gen.lockmode,
+ ternaryRelOpts[i].gen.lockmode));
+ j++;
+ }
+
for (i = 0; intRelOpts[i].gen.name; i++)
{
Assert(DoLockModesConflict(intRelOpts[i].gen.lockmode,
@@ -640,6 +653,14 @@ initialize_reloptions(void)
j++;
}
+ for (i = 0; ternaryRelOpts[i].gen.name; i++)
+ {
+ relOpts[j] = &ternaryRelOpts[i].gen;
+ relOpts[j]->type = RELOPT_TYPE_TERNARY;
+ relOpts[j]->namelen = strlen(relOpts[j]->name);
+ j++;
+ }
+
for (i = 0; intRelOpts[i].gen.name; i++)
{
relOpts[j] = &intRelOpts[i].gen;
@@ -800,6 +821,9 @@ allocate_reloption(bits32 kinds, int type, const char *name, const char *desc,
case RELOPT_TYPE_BOOL:
size = sizeof(relopt_bool);
break;
+ case RELOPT_TYPE_TERNARY:
+ size = sizeof(relopt_ternary);
+ break;
case RELOPT_TYPE_INT:
size = sizeof(relopt_int);
break;
@@ -883,6 +907,54 @@ add_local_bool_reloption(local_relopts *relopts, const char *name,
add_local_reloption(relopts, (relopt_gen *) newoption, offset);
}
+/*
+ * init_ternary_reloption
+ * Allocate and initialize a new ternary reloption
+ */
+static relopt_ternary *
+init_ternary_reloption(bits32 kinds, const char *name, const char *desc,
+ ternary default_val, LOCKMODE lockmode)
+{
+ relopt_ternary *newoption;
+
+ newoption = (relopt_ternary *) allocate_reloption(kinds,
+ RELOPT_TYPE_TERNARY, name, desc, lockmode);
+ newoption->default_val = default_val;
+
+ return newoption;
+}
+
+/*
+ * add_ternary_reloption
+ * Add a new ternary reloption
+ */
+void
+add_ternary_reloption(bits32 kinds, const char *name, const char *desc,
+ ternary default_val, LOCKMODE lockmode)
+{
+ relopt_ternary *newoption = init_ternary_reloption(kinds, name, desc,
+ default_val, lockmode);
+
+ add_reloption((relopt_gen *) newoption);
+}
+
+/*
+ * add_local_ternary_reloption
+ * Add a new ternary local reloption
+ *
+ * 'offset' is offset of ternary-typed field.
+ */
+void
+add_local_ternary_reloption(local_relopts *relopts, const char *name,
+ const char *desc, ternary default_val,
+ int offset)
+{
+ relopt_ternary *newoption = init_ternary_reloption(RELOPT_KIND_LOCAL,
+ name, desc,
+ default_val, 0);
+
+ add_local_reloption(relopts, (relopt_gen *) newoption, offset);
+}
/*
* init_real_reloption
@@ -1617,6 +1689,18 @@ parse_one_reloption(relopt_value *option, char *text_str, int text_len,
option->gen->name, value)));
}
break;
+ case RELOPT_TYPE_TERNARY:
+ {
+ bool b;
+ parsed = parse_bool(value, &b);
+ option->values.ternary_val = b ? TERNARY_TRUE : TERNARY_FALSE;
+ if (validate && !parsed)
+ ereport(ERROR,
+ (errcode(ERRCODE_INVALID_PARAMETER_VALUE),
+ errmsg("invalid value for ternary option \"%s\": %s",
+ option->gen->name, value)));
+ }
+ break;
case RELOPT_TYPE_INT:
{
relopt_int *optint = (relopt_int *) option->gen;
@@ -1780,17 +1864,6 @@ fillRelOptions(void *rdopts, Size basesize,
char *itempos = ((char *) rdopts) + elems[j].offset;
char *string_val;
- /*
- * If isset_offset is provided, store whether the reloption is
- * set there.
- */
- if (elems[j].isset_offset > 0)
- {
- char *setpos = ((char *) rdopts) + elems[j].isset_offset;
-
- *(bool *) setpos = options[i].isset;
- }
-
switch (options[i].gen->type)
{
case RELOPT_TYPE_BOOL:
@@ -1798,6 +1871,11 @@ fillRelOptions(void *rdopts, Size basesize,
options[i].values.bool_val :
((relopt_bool *) options[i].gen)->default_val;
break;
+ case RELOPT_TYPE_TERNARY:
+ *(ternary *) itempos = options[i].isset ?
+ options[i].values.ternary_val :
+ ((relopt_ternary *) options[i].gen)->default_val;
+ break;
case RELOPT_TYPE_INT:
*(int *) itempos = options[i].isset ?
options[i].values.int_val :
@@ -1912,8 +1990,8 @@ default_reloptions(Datum reloptions, bool validate, relopt_kind kind)
offsetof(StdRdOptions, parallel_workers)},
{"vacuum_index_cleanup", RELOPT_TYPE_ENUM,
offsetof(StdRdOptions, vacuum_index_cleanup)},
- {"vacuum_truncate", RELOPT_TYPE_BOOL,
- offsetof(StdRdOptions, vacuum_truncate), offsetof(StdRdOptions, vacuum_truncate_set)},
+ {"vacuum_truncate", RELOPT_TYPE_TERNARY,
+ offsetof(StdRdOptions, vacuum_truncate)},
{"vacuum_max_eager_freeze_failure_rate", RELOPT_TYPE_REAL,
offsetof(StdRdOptions, vacuum_max_eager_freeze_failure_rate)}
};
@@ -1993,7 +2071,6 @@ build_local_reloptions(local_relopts *relopts, Datum options, bool validate)
elems[i].optname = opt->option->name;
elems[i].opttype = opt->option->type;
elems[i].offset = opt->offset;
- elems[i].isset_offset = 0; /* not supported for local relopts yet */
i++;
}
diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c
index 733ef40ae7..977babff54 100644
--- a/src/backend/commands/vacuum.c
+++ b/src/backend/commands/vacuum.c
@@ -2221,9 +2221,9 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams params,
{
StdRdOptions *opts = (StdRdOptions *) rel->rd_options;
- if (opts && opts->vacuum_truncate_set)
+ if (opts && opts->vacuum_truncate != TERNARY_UNSET)
{
- if (opts->vacuum_truncate)
+ if (opts->vacuum_truncate == TERNARY_TRUE)
params.truncate = VACOPTVALUE_ENABLED;
else
params.truncate = VACOPTVALUE_DISABLED;
diff --git a/src/include/access/reloptions.h b/src/include/access/reloptions.h
index a604a4702c..a436697658 100644
--- a/src/include/access/reloptions.h
+++ b/src/include/access/reloptions.h
@@ -29,6 +29,7 @@
typedef enum relopt_type
{
RELOPT_TYPE_BOOL,
+ RELOPT_TYPE_TERNARY, /* on, off, unset */
RELOPT_TYPE_INT,
RELOPT_TYPE_REAL,
RELOPT_TYPE_ENUM,
@@ -80,6 +81,7 @@ typedef struct relopt_value
union
{
bool bool_val;
+ ternary ternary_val;
int int_val;
double real_val;
int enum_val;
@@ -94,6 +96,12 @@ typedef struct relopt_bool
bool default_val;
} relopt_bool;
+typedef struct relopt_rernary
+{
+ relopt_gen gen;
+ int default_val;
+} relopt_ternary;
+
typedef struct relopt_int
{
relopt_gen gen;
@@ -152,19 +160,6 @@ typedef struct
const char *optname; /* option's name */
relopt_type opttype; /* option's datatype */
int offset; /* offset of field in result struct */
-
- /*
- * isset_offset is an optional offset of a field in the result struct that
- * stores whether the option is explicitly set for the relation or if it
- * just picked up the default value. In most cases, this can be
- * accomplished by giving the reloption a special out-of-range default
- * value (e.g., some integer reloptions use -2), but this isn't always
- * possible. For example, a Boolean reloption cannot be given an
- * out-of-range default, so we need another way to discover the source of
- * its value. This offset is only used if given a value greater than
- * zero.
- */
- int isset_offset;
} relopt_parse_elt;
/* Local reloption definition */
@@ -195,6 +190,8 @@ typedef struct local_relopts
extern relopt_kind add_reloption_kind(void);
extern void add_bool_reloption(bits32 kinds, const char *name, const char *desc,
bool default_val, LOCKMODE lockmode);
+extern void add_ternary_reloption(bits32 kinds, const char *name,
+ const char *desc, int default_val, LOCKMODE lockmode);
extern void add_int_reloption(bits32 kinds, const char *name, const char *desc,
int default_val, int min_val, int max_val,
LOCKMODE lockmode);
@@ -214,6 +211,9 @@ extern void register_reloptions_validator(local_relopts *relopts,
extern void add_local_bool_reloption(local_relopts *relopts, const char *name,
const char *desc, bool default_val,
int offset);
+extern void add_local_ternary_reloption(local_relopts *relopts,
+ const char *name, const char *desc,
+ ternary default_val, int offset);
extern void add_local_int_reloption(local_relopts *relopts, const char *name,
const char *desc, int default_val,
int min_val, int max_val, int offset);
diff --git a/src/include/c.h b/src/include/c.h
index 39022f8a9d..9c59ccccf8 100644
--- a/src/include/c.h
+++ b/src/include/c.h
@@ -488,6 +488,22 @@ typedef void (*pg_funcptr_t) (void);
#include <stdbool.h>
+/*
+ * ternary
+ * Boolean value with an extrea "unset" option
+ *
+ * Ternary data type is used in relation options that can be "true", "false" or
+ * "unset". Since relation options are used deep inside the PostgreSQL code,
+ * this type is declared globally.
+*/
+
+typedef enum ternary
+{
+ TERNARY_FALSE = 0,
+ TERNARY_TRUE = 1,
+ TERNARY_UNSET = -1
+} ternary;
+
/* ----------------------------------------------------------------
* Section 3: standard system types
diff --git a/src/include/utils/rel.h b/src/include/utils/rel.h
index b552359915..7346d618f9 100644
--- a/src/include/utils/rel.h
+++ b/src/include/utils/rel.h
@@ -346,8 +346,7 @@ typedef struct StdRdOptions
bool user_catalog_table; /* use as an additional catalog relation */
int parallel_workers; /* max number of parallel workers */
StdRdOptIndexCleanup vacuum_index_cleanup; /* controls index vacuuming */
- bool vacuum_truncate; /* enables vacuum to truncate a relation */
- bool vacuum_truncate_set; /* whether vacuum_truncate is set */
+ ternary vacuum_truncate; /* enables vacuum to truncate a relation */
/*
* Fraction of pages in a relation that vacuum can eagerly scan and fail
--
2.39.2
--nextPart5129932.5fSG56mABF
Content-Disposition: attachment;
filename="v2-0003-Add-alias-to-be-used-as-unset-state.patch"
Content-Transfer-Encoding: 7Bit
Content-Type: text/x-patch; charset="unicode-2-0-utf-8";
name="v2-0003-Add-alias-to-be-used-as-unset-state.patch"
^ permalink raw reply [nested|flat] 868+ messages in thread
* [PATCH v2 2/4] Introduce ternary reloptions
@ 2025-09-04 14:41 Nikolay Shaplov <[email protected]>
0 siblings, 0 replies; 868+ messages in thread
From: Nikolay Shaplov @ 2025-09-04 14:41 UTC (permalink / raw)
Introduce ternary reloption as a replacement for current `vacuum_truncate`
implementation. Remove `vacuum_truncate_set` additional flag and using
`TERNARY_UNSET` value instead.
---
src/backend/access/common/reloptions.c | 129 ++++++++++++++++++++-----
src/backend/commands/vacuum.c | 4 +-
src/include/access/reloptions.h | 26 ++---
src/include/c.h | 16 +++
src/include/utils/rel.h | 3 +-
5 files changed, 135 insertions(+), 43 deletions(-)
diff --git a/src/backend/access/common/reloptions.c b/src/backend/access/common/reloptions.c
index 0af3fea68f..f543f11001 100644
--- a/src/backend/access/common/reloptions.c
+++ b/src/backend/access/common/reloptions.c
@@ -40,9 +40,9 @@
*
* To add an option:
*
- * (i) decide on a type (bool, integer, real, enum, string), name, default
- * value, upper and lower bounds (if applicable); for strings, consider a
- * validation routine.
+ * (i) decide on a type (bool, ternary, integer, real, enum, string), name,
+ * default value, upper and lower bounds (if applicable); for strings,
+ * consider a validation routine.
* (ii) add a record below (or use add_<type>_reloption).
* (iii) add it to the appropriate options struct (perhaps StdRdOptions)
* (iv) add it to the appropriate handling routine (perhaps
@@ -147,15 +147,6 @@ static relopt_bool boolRelOpts[] =
},
false
},
- {
- {
- "vacuum_truncate",
- "Enables vacuum to truncate empty pages at the end of this table",
- RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
- ShareUpdateExclusiveLock
- },
- true
- },
{
{
"deduplicate_items",
@@ -170,6 +161,21 @@ static relopt_bool boolRelOpts[] =
{{NULL}}
};
+static relopt_ternary ternaryRelOpts[] =
+{
+ {
+ {
+ "vacuum_truncate",
+ "Enables vacuum to truncate empty pages at the end of this table",
+ RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
+ ShareUpdateExclusiveLock
+ },
+ TERNARY_UNSET
+ },
+ /* list terminator */
+ {{NULL}}
+};
+
static relopt_int intRelOpts[] =
{
{
@@ -600,6 +606,13 @@ initialize_reloptions(void)
boolRelOpts[i].gen.lockmode));
j++;
}
+ for (i = 0; ternaryRelOpts[i].gen.name; i++)
+ {
+ Assert(DoLockModesConflict(ternaryRelOpts[i].gen.lockmode,
+ ternaryRelOpts[i].gen.lockmode));
+ j++;
+ }
+
for (i = 0; intRelOpts[i].gen.name; i++)
{
Assert(DoLockModesConflict(intRelOpts[i].gen.lockmode,
@@ -640,6 +653,14 @@ initialize_reloptions(void)
j++;
}
+ for (i = 0; ternaryRelOpts[i].gen.name; i++)
+ {
+ relOpts[j] = &ternaryRelOpts[i].gen;
+ relOpts[j]->type = RELOPT_TYPE_TERNARY;
+ relOpts[j]->namelen = strlen(relOpts[j]->name);
+ j++;
+ }
+
for (i = 0; intRelOpts[i].gen.name; i++)
{
relOpts[j] = &intRelOpts[i].gen;
@@ -800,6 +821,9 @@ allocate_reloption(bits32 kinds, int type, const char *name, const char *desc,
case RELOPT_TYPE_BOOL:
size = sizeof(relopt_bool);
break;
+ case RELOPT_TYPE_TERNARY:
+ size = sizeof(relopt_ternary);
+ break;
case RELOPT_TYPE_INT:
size = sizeof(relopt_int);
break;
@@ -883,6 +907,54 @@ add_local_bool_reloption(local_relopts *relopts, const char *name,
add_local_reloption(relopts, (relopt_gen *) newoption, offset);
}
+/*
+ * init_ternary_reloption
+ * Allocate and initialize a new ternary reloption
+ */
+static relopt_ternary *
+init_ternary_reloption(bits32 kinds, const char *name, const char *desc,
+ ternary default_val, LOCKMODE lockmode)
+{
+ relopt_ternary *newoption;
+
+ newoption = (relopt_ternary *) allocate_reloption(kinds,
+ RELOPT_TYPE_TERNARY, name, desc, lockmode);
+ newoption->default_val = default_val;
+
+ return newoption;
+}
+
+/*
+ * add_ternary_reloption
+ * Add a new ternary reloption
+ */
+void
+add_ternary_reloption(bits32 kinds, const char *name, const char *desc,
+ ternary default_val, LOCKMODE lockmode)
+{
+ relopt_ternary *newoption = init_ternary_reloption(kinds, name, desc,
+ default_val, lockmode);
+
+ add_reloption((relopt_gen *) newoption);
+}
+
+/*
+ * add_local_ternary_reloption
+ * Add a new ternary local reloption
+ *
+ * 'offset' is offset of ternary-typed field.
+ */
+void
+add_local_ternary_reloption(local_relopts *relopts, const char *name,
+ const char *desc, ternary default_val,
+ int offset)
+{
+ relopt_ternary *newoption = init_ternary_reloption(RELOPT_KIND_LOCAL,
+ name, desc,
+ default_val, 0);
+
+ add_local_reloption(relopts, (relopt_gen *) newoption, offset);
+}
/*
* init_real_reloption
@@ -1617,6 +1689,18 @@ parse_one_reloption(relopt_value *option, char *text_str, int text_len,
option->gen->name, value)));
}
break;
+ case RELOPT_TYPE_TERNARY:
+ {
+ bool b;
+ parsed = parse_bool(value, &b);
+ option->values.ternary_val = b ? TERNARY_TRUE : TERNARY_FALSE;
+ if (validate && !parsed)
+ ereport(ERROR,
+ (errcode(ERRCODE_INVALID_PARAMETER_VALUE),
+ errmsg("invalid value for ternary option \"%s\": %s",
+ option->gen->name, value)));
+ }
+ break;
case RELOPT_TYPE_INT:
{
relopt_int *optint = (relopt_int *) option->gen;
@@ -1780,17 +1864,6 @@ fillRelOptions(void *rdopts, Size basesize,
char *itempos = ((char *) rdopts) + elems[j].offset;
char *string_val;
- /*
- * If isset_offset is provided, store whether the reloption is
- * set there.
- */
- if (elems[j].isset_offset > 0)
- {
- char *setpos = ((char *) rdopts) + elems[j].isset_offset;
-
- *(bool *) setpos = options[i].isset;
- }
-
switch (options[i].gen->type)
{
case RELOPT_TYPE_BOOL:
@@ -1798,6 +1871,11 @@ fillRelOptions(void *rdopts, Size basesize,
options[i].values.bool_val :
((relopt_bool *) options[i].gen)->default_val;
break;
+ case RELOPT_TYPE_TERNARY:
+ *(ternary *) itempos = options[i].isset ?
+ options[i].values.ternary_val :
+ ((relopt_ternary *) options[i].gen)->default_val;
+ break;
case RELOPT_TYPE_INT:
*(int *) itempos = options[i].isset ?
options[i].values.int_val :
@@ -1912,8 +1990,8 @@ default_reloptions(Datum reloptions, bool validate, relopt_kind kind)
offsetof(StdRdOptions, parallel_workers)},
{"vacuum_index_cleanup", RELOPT_TYPE_ENUM,
offsetof(StdRdOptions, vacuum_index_cleanup)},
- {"vacuum_truncate", RELOPT_TYPE_BOOL,
- offsetof(StdRdOptions, vacuum_truncate), offsetof(StdRdOptions, vacuum_truncate_set)},
+ {"vacuum_truncate", RELOPT_TYPE_TERNARY,
+ offsetof(StdRdOptions, vacuum_truncate)},
{"vacuum_max_eager_freeze_failure_rate", RELOPT_TYPE_REAL,
offsetof(StdRdOptions, vacuum_max_eager_freeze_failure_rate)}
};
@@ -1993,7 +2071,6 @@ build_local_reloptions(local_relopts *relopts, Datum options, bool validate)
elems[i].optname = opt->option->name;
elems[i].opttype = opt->option->type;
elems[i].offset = opt->offset;
- elems[i].isset_offset = 0; /* not supported for local relopts yet */
i++;
}
diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c
index 733ef40ae7..977babff54 100644
--- a/src/backend/commands/vacuum.c
+++ b/src/backend/commands/vacuum.c
@@ -2221,9 +2221,9 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams params,
{
StdRdOptions *opts = (StdRdOptions *) rel->rd_options;
- if (opts && opts->vacuum_truncate_set)
+ if (opts && opts->vacuum_truncate != TERNARY_UNSET)
{
- if (opts->vacuum_truncate)
+ if (opts->vacuum_truncate == TERNARY_TRUE)
params.truncate = VACOPTVALUE_ENABLED;
else
params.truncate = VACOPTVALUE_DISABLED;
diff --git a/src/include/access/reloptions.h b/src/include/access/reloptions.h
index a604a4702c..a436697658 100644
--- a/src/include/access/reloptions.h
+++ b/src/include/access/reloptions.h
@@ -29,6 +29,7 @@
typedef enum relopt_type
{
RELOPT_TYPE_BOOL,
+ RELOPT_TYPE_TERNARY, /* on, off, unset */
RELOPT_TYPE_INT,
RELOPT_TYPE_REAL,
RELOPT_TYPE_ENUM,
@@ -80,6 +81,7 @@ typedef struct relopt_value
union
{
bool bool_val;
+ ternary ternary_val;
int int_val;
double real_val;
int enum_val;
@@ -94,6 +96,12 @@ typedef struct relopt_bool
bool default_val;
} relopt_bool;
+typedef struct relopt_rernary
+{
+ relopt_gen gen;
+ int default_val;
+} relopt_ternary;
+
typedef struct relopt_int
{
relopt_gen gen;
@@ -152,19 +160,6 @@ typedef struct
const char *optname; /* option's name */
relopt_type opttype; /* option's datatype */
int offset; /* offset of field in result struct */
-
- /*
- * isset_offset is an optional offset of a field in the result struct that
- * stores whether the option is explicitly set for the relation or if it
- * just picked up the default value. In most cases, this can be
- * accomplished by giving the reloption a special out-of-range default
- * value (e.g., some integer reloptions use -2), but this isn't always
- * possible. For example, a Boolean reloption cannot be given an
- * out-of-range default, so we need another way to discover the source of
- * its value. This offset is only used if given a value greater than
- * zero.
- */
- int isset_offset;
} relopt_parse_elt;
/* Local reloption definition */
@@ -195,6 +190,8 @@ typedef struct local_relopts
extern relopt_kind add_reloption_kind(void);
extern void add_bool_reloption(bits32 kinds, const char *name, const char *desc,
bool default_val, LOCKMODE lockmode);
+extern void add_ternary_reloption(bits32 kinds, const char *name,
+ const char *desc, int default_val, LOCKMODE lockmode);
extern void add_int_reloption(bits32 kinds, const char *name, const char *desc,
int default_val, int min_val, int max_val,
LOCKMODE lockmode);
@@ -214,6 +211,9 @@ extern void register_reloptions_validator(local_relopts *relopts,
extern void add_local_bool_reloption(local_relopts *relopts, const char *name,
const char *desc, bool default_val,
int offset);
+extern void add_local_ternary_reloption(local_relopts *relopts,
+ const char *name, const char *desc,
+ ternary default_val, int offset);
extern void add_local_int_reloption(local_relopts *relopts, const char *name,
const char *desc, int default_val,
int min_val, int max_val, int offset);
diff --git a/src/include/c.h b/src/include/c.h
index 39022f8a9d..9c59ccccf8 100644
--- a/src/include/c.h
+++ b/src/include/c.h
@@ -488,6 +488,22 @@ typedef void (*pg_funcptr_t) (void);
#include <stdbool.h>
+/*
+ * ternary
+ * Boolean value with an extrea "unset" option
+ *
+ * Ternary data type is used in relation options that can be "true", "false" or
+ * "unset". Since relation options are used deep inside the PostgreSQL code,
+ * this type is declared globally.
+*/
+
+typedef enum ternary
+{
+ TERNARY_FALSE = 0,
+ TERNARY_TRUE = 1,
+ TERNARY_UNSET = -1
+} ternary;
+
/* ----------------------------------------------------------------
* Section 3: standard system types
diff --git a/src/include/utils/rel.h b/src/include/utils/rel.h
index b552359915..7346d618f9 100644
--- a/src/include/utils/rel.h
+++ b/src/include/utils/rel.h
@@ -346,8 +346,7 @@ typedef struct StdRdOptions
bool user_catalog_table; /* use as an additional catalog relation */
int parallel_workers; /* max number of parallel workers */
StdRdOptIndexCleanup vacuum_index_cleanup; /* controls index vacuuming */
- bool vacuum_truncate; /* enables vacuum to truncate a relation */
- bool vacuum_truncate_set; /* whether vacuum_truncate is set */
+ ternary vacuum_truncate; /* enables vacuum to truncate a relation */
/*
* Fraction of pages in a relation that vacuum can eagerly scan and fail
--
2.39.2
--nextPart5129932.5fSG56mABF
Content-Disposition: attachment;
filename="v2-0003-Add-alias-to-be-used-as-unset-state.patch"
Content-Transfer-Encoding: 7Bit
Content-Type: text/x-patch; charset="unicode-2-0-utf-8";
name="v2-0003-Add-alias-to-be-used-as-unset-state.patch"
^ permalink raw reply [nested|flat] 868+ messages in thread
* [PATCH v2a 2/4] Introduce ternary reloptions
@ 2025-09-04 14:41 Nikolay Shaplov <[email protected]>
0 siblings, 0 replies; 868+ messages in thread
From: Nikolay Shaplov @ 2025-09-04 14:41 UTC (permalink / raw)
Introduce ternary reloption as a replacement for current `vacuum_truncate`
implementation. Remove `vacuum_truncate_set` additional flag and using
`TERNARY_UNSET` value instead.
---
src/backend/access/common/reloptions.c | 129 ++++++++++++++++++++-----
src/backend/commands/vacuum.c | 4 +-
src/include/access/reloptions.h | 26 ++---
src/include/c.h | 16 +++
src/include/utils/rel.h | 3 +-
5 files changed, 135 insertions(+), 43 deletions(-)
diff --git a/src/backend/access/common/reloptions.c b/src/backend/access/common/reloptions.c
index 0af3fea68f..f543f11001 100644
--- a/src/backend/access/common/reloptions.c
+++ b/src/backend/access/common/reloptions.c
@@ -40,9 +40,9 @@
*
* To add an option:
*
- * (i) decide on a type (bool, integer, real, enum, string), name, default
- * value, upper and lower bounds (if applicable); for strings, consider a
- * validation routine.
+ * (i) decide on a type (bool, ternary, integer, real, enum, string), name,
+ * default value, upper and lower bounds (if applicable); for strings,
+ * consider a validation routine.
* (ii) add a record below (or use add_<type>_reloption).
* (iii) add it to the appropriate options struct (perhaps StdRdOptions)
* (iv) add it to the appropriate handling routine (perhaps
@@ -147,15 +147,6 @@ static relopt_bool boolRelOpts[] =
},
false
},
- {
- {
- "vacuum_truncate",
- "Enables vacuum to truncate empty pages at the end of this table",
- RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
- ShareUpdateExclusiveLock
- },
- true
- },
{
{
"deduplicate_items",
@@ -170,6 +161,21 @@ static relopt_bool boolRelOpts[] =
{{NULL}}
};
+static relopt_ternary ternaryRelOpts[] =
+{
+ {
+ {
+ "vacuum_truncate",
+ "Enables vacuum to truncate empty pages at the end of this table",
+ RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
+ ShareUpdateExclusiveLock
+ },
+ TERNARY_UNSET
+ },
+ /* list terminator */
+ {{NULL}}
+};
+
static relopt_int intRelOpts[] =
{
{
@@ -600,6 +606,13 @@ initialize_reloptions(void)
boolRelOpts[i].gen.lockmode));
j++;
}
+ for (i = 0; ternaryRelOpts[i].gen.name; i++)
+ {
+ Assert(DoLockModesConflict(ternaryRelOpts[i].gen.lockmode,
+ ternaryRelOpts[i].gen.lockmode));
+ j++;
+ }
+
for (i = 0; intRelOpts[i].gen.name; i++)
{
Assert(DoLockModesConflict(intRelOpts[i].gen.lockmode,
@@ -640,6 +653,14 @@ initialize_reloptions(void)
j++;
}
+ for (i = 0; ternaryRelOpts[i].gen.name; i++)
+ {
+ relOpts[j] = &ternaryRelOpts[i].gen;
+ relOpts[j]->type = RELOPT_TYPE_TERNARY;
+ relOpts[j]->namelen = strlen(relOpts[j]->name);
+ j++;
+ }
+
for (i = 0; intRelOpts[i].gen.name; i++)
{
relOpts[j] = &intRelOpts[i].gen;
@@ -800,6 +821,9 @@ allocate_reloption(bits32 kinds, int type, const char *name, const char *desc,
case RELOPT_TYPE_BOOL:
size = sizeof(relopt_bool);
break;
+ case RELOPT_TYPE_TERNARY:
+ size = sizeof(relopt_ternary);
+ break;
case RELOPT_TYPE_INT:
size = sizeof(relopt_int);
break;
@@ -883,6 +907,54 @@ add_local_bool_reloption(local_relopts *relopts, const char *name,
add_local_reloption(relopts, (relopt_gen *) newoption, offset);
}
+/*
+ * init_ternary_reloption
+ * Allocate and initialize a new ternary reloption
+ */
+static relopt_ternary *
+init_ternary_reloption(bits32 kinds, const char *name, const char *desc,
+ ternary default_val, LOCKMODE lockmode)
+{
+ relopt_ternary *newoption;
+
+ newoption = (relopt_ternary *) allocate_reloption(kinds,
+ RELOPT_TYPE_TERNARY, name, desc, lockmode);
+ newoption->default_val = default_val;
+
+ return newoption;
+}
+
+/*
+ * add_ternary_reloption
+ * Add a new ternary reloption
+ */
+void
+add_ternary_reloption(bits32 kinds, const char *name, const char *desc,
+ ternary default_val, LOCKMODE lockmode)
+{
+ relopt_ternary *newoption = init_ternary_reloption(kinds, name, desc,
+ default_val, lockmode);
+
+ add_reloption((relopt_gen *) newoption);
+}
+
+/*
+ * add_local_ternary_reloption
+ * Add a new ternary local reloption
+ *
+ * 'offset' is offset of ternary-typed field.
+ */
+void
+add_local_ternary_reloption(local_relopts *relopts, const char *name,
+ const char *desc, ternary default_val,
+ int offset)
+{
+ relopt_ternary *newoption = init_ternary_reloption(RELOPT_KIND_LOCAL,
+ name, desc,
+ default_val, 0);
+
+ add_local_reloption(relopts, (relopt_gen *) newoption, offset);
+}
/*
* init_real_reloption
@@ -1617,6 +1689,18 @@ parse_one_reloption(relopt_value *option, char *text_str, int text_len,
option->gen->name, value)));
}
break;
+ case RELOPT_TYPE_TERNARY:
+ {
+ bool b;
+ parsed = parse_bool(value, &b);
+ option->values.ternary_val = b ? TERNARY_TRUE : TERNARY_FALSE;
+ if (validate && !parsed)
+ ereport(ERROR,
+ (errcode(ERRCODE_INVALID_PARAMETER_VALUE),
+ errmsg("invalid value for ternary option \"%s\": %s",
+ option->gen->name, value)));
+ }
+ break;
case RELOPT_TYPE_INT:
{
relopt_int *optint = (relopt_int *) option->gen;
@@ -1780,17 +1864,6 @@ fillRelOptions(void *rdopts, Size basesize,
char *itempos = ((char *) rdopts) + elems[j].offset;
char *string_val;
- /*
- * If isset_offset is provided, store whether the reloption is
- * set there.
- */
- if (elems[j].isset_offset > 0)
- {
- char *setpos = ((char *) rdopts) + elems[j].isset_offset;
-
- *(bool *) setpos = options[i].isset;
- }
-
switch (options[i].gen->type)
{
case RELOPT_TYPE_BOOL:
@@ -1798,6 +1871,11 @@ fillRelOptions(void *rdopts, Size basesize,
options[i].values.bool_val :
((relopt_bool *) options[i].gen)->default_val;
break;
+ case RELOPT_TYPE_TERNARY:
+ *(ternary *) itempos = options[i].isset ?
+ options[i].values.ternary_val :
+ ((relopt_ternary *) options[i].gen)->default_val;
+ break;
case RELOPT_TYPE_INT:
*(int *) itempos = options[i].isset ?
options[i].values.int_val :
@@ -1912,8 +1990,8 @@ default_reloptions(Datum reloptions, bool validate, relopt_kind kind)
offsetof(StdRdOptions, parallel_workers)},
{"vacuum_index_cleanup", RELOPT_TYPE_ENUM,
offsetof(StdRdOptions, vacuum_index_cleanup)},
- {"vacuum_truncate", RELOPT_TYPE_BOOL,
- offsetof(StdRdOptions, vacuum_truncate), offsetof(StdRdOptions, vacuum_truncate_set)},
+ {"vacuum_truncate", RELOPT_TYPE_TERNARY,
+ offsetof(StdRdOptions, vacuum_truncate)},
{"vacuum_max_eager_freeze_failure_rate", RELOPT_TYPE_REAL,
offsetof(StdRdOptions, vacuum_max_eager_freeze_failure_rate)}
};
@@ -1993,7 +2071,6 @@ build_local_reloptions(local_relopts *relopts, Datum options, bool validate)
elems[i].optname = opt->option->name;
elems[i].opttype = opt->option->type;
elems[i].offset = opt->offset;
- elems[i].isset_offset = 0; /* not supported for local relopts yet */
i++;
}
diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c
index 733ef40ae7..977babff54 100644
--- a/src/backend/commands/vacuum.c
+++ b/src/backend/commands/vacuum.c
@@ -2221,9 +2221,9 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams params,
{
StdRdOptions *opts = (StdRdOptions *) rel->rd_options;
- if (opts && opts->vacuum_truncate_set)
+ if (opts && opts->vacuum_truncate != TERNARY_UNSET)
{
- if (opts->vacuum_truncate)
+ if (opts->vacuum_truncate == TERNARY_TRUE)
params.truncate = VACOPTVALUE_ENABLED;
else
params.truncate = VACOPTVALUE_DISABLED;
diff --git a/src/include/access/reloptions.h b/src/include/access/reloptions.h
index a604a4702c..a436697658 100644
--- a/src/include/access/reloptions.h
+++ b/src/include/access/reloptions.h
@@ -29,6 +29,7 @@
typedef enum relopt_type
{
RELOPT_TYPE_BOOL,
+ RELOPT_TYPE_TERNARY, /* on, off, unset */
RELOPT_TYPE_INT,
RELOPT_TYPE_REAL,
RELOPT_TYPE_ENUM,
@@ -80,6 +81,7 @@ typedef struct relopt_value
union
{
bool bool_val;
+ ternary ternary_val;
int int_val;
double real_val;
int enum_val;
@@ -94,6 +96,12 @@ typedef struct relopt_bool
bool default_val;
} relopt_bool;
+typedef struct relopt_rernary
+{
+ relopt_gen gen;
+ int default_val;
+} relopt_ternary;
+
typedef struct relopt_int
{
relopt_gen gen;
@@ -152,19 +160,6 @@ typedef struct
const char *optname; /* option's name */
relopt_type opttype; /* option's datatype */
int offset; /* offset of field in result struct */
-
- /*
- * isset_offset is an optional offset of a field in the result struct that
- * stores whether the option is explicitly set for the relation or if it
- * just picked up the default value. In most cases, this can be
- * accomplished by giving the reloption a special out-of-range default
- * value (e.g., some integer reloptions use -2), but this isn't always
- * possible. For example, a Boolean reloption cannot be given an
- * out-of-range default, so we need another way to discover the source of
- * its value. This offset is only used if given a value greater than
- * zero.
- */
- int isset_offset;
} relopt_parse_elt;
/* Local reloption definition */
@@ -195,6 +190,8 @@ typedef struct local_relopts
extern relopt_kind add_reloption_kind(void);
extern void add_bool_reloption(bits32 kinds, const char *name, const char *desc,
bool default_val, LOCKMODE lockmode);
+extern void add_ternary_reloption(bits32 kinds, const char *name,
+ const char *desc, int default_val, LOCKMODE lockmode);
extern void add_int_reloption(bits32 kinds, const char *name, const char *desc,
int default_val, int min_val, int max_val,
LOCKMODE lockmode);
@@ -214,6 +211,9 @@ extern void register_reloptions_validator(local_relopts *relopts,
extern void add_local_bool_reloption(local_relopts *relopts, const char *name,
const char *desc, bool default_val,
int offset);
+extern void add_local_ternary_reloption(local_relopts *relopts,
+ const char *name, const char *desc,
+ ternary default_val, int offset);
extern void add_local_int_reloption(local_relopts *relopts, const char *name,
const char *desc, int default_val,
int min_val, int max_val, int offset);
diff --git a/src/include/c.h b/src/include/c.h
index 39022f8a9d..9c59ccccf8 100644
--- a/src/include/c.h
+++ b/src/include/c.h
@@ -488,6 +488,22 @@ typedef void (*pg_funcptr_t) (void);
#include <stdbool.h>
+/*
+ * ternary
+ * Boolean value with an extrea "unset" option
+ *
+ * Ternary data type is used in relation options that can be "true", "false" or
+ * "unset". Since relation options are used deep inside the PostgreSQL code,
+ * this type is declared globally.
+*/
+
+typedef enum ternary
+{
+ TERNARY_FALSE = 0,
+ TERNARY_TRUE = 1,
+ TERNARY_UNSET = -1
+} ternary;
+
/* ----------------------------------------------------------------
* Section 3: standard system types
diff --git a/src/include/utils/rel.h b/src/include/utils/rel.h
index b552359915..7346d618f9 100644
--- a/src/include/utils/rel.h
+++ b/src/include/utils/rel.h
@@ -346,8 +346,7 @@ typedef struct StdRdOptions
bool user_catalog_table; /* use as an additional catalog relation */
int parallel_workers; /* max number of parallel workers */
StdRdOptIndexCleanup vacuum_index_cleanup; /* controls index vacuuming */
- bool vacuum_truncate; /* enables vacuum to truncate a relation */
- bool vacuum_truncate_set; /* whether vacuum_truncate is set */
+ ternary vacuum_truncate; /* enables vacuum to truncate a relation */
/*
* Fraction of pages in a relation that vacuum can eagerly scan and fail
--
2.39.2
--nextPart4155600.3ZeAukHxDK
Content-Disposition: attachment;
filename="v2a-0003-Add-alias-to-be-used-as-unset-state.patch"
Content-Transfer-Encoding: 7Bit
Content-Type: text/x-patch; charset="unicode-2-0-utf-8";
name="v2a-0003-Add-alias-to-be-used-as-unset-state.patch"
^ permalink raw reply [nested|flat] 868+ messages in thread
* [PATCH v2a 2/4] Introduce ternary reloptions
@ 2025-09-04 14:41 Nikolay Shaplov <[email protected]>
0 siblings, 0 replies; 868+ messages in thread
From: Nikolay Shaplov @ 2025-09-04 14:41 UTC (permalink / raw)
Introduce ternary reloption as a replacement for current `vacuum_truncate`
implementation. Remove `vacuum_truncate_set` additional flag and using
`TERNARY_UNSET` value instead.
---
src/backend/access/common/reloptions.c | 129 ++++++++++++++++++++-----
src/backend/commands/vacuum.c | 4 +-
src/include/access/reloptions.h | 26 ++---
src/include/c.h | 16 +++
src/include/utils/rel.h | 3 +-
5 files changed, 135 insertions(+), 43 deletions(-)
diff --git a/src/backend/access/common/reloptions.c b/src/backend/access/common/reloptions.c
index 0af3fea68f..f543f11001 100644
--- a/src/backend/access/common/reloptions.c
+++ b/src/backend/access/common/reloptions.c
@@ -40,9 +40,9 @@
*
* To add an option:
*
- * (i) decide on a type (bool, integer, real, enum, string), name, default
- * value, upper and lower bounds (if applicable); for strings, consider a
- * validation routine.
+ * (i) decide on a type (bool, ternary, integer, real, enum, string), name,
+ * default value, upper and lower bounds (if applicable); for strings,
+ * consider a validation routine.
* (ii) add a record below (or use add_<type>_reloption).
* (iii) add it to the appropriate options struct (perhaps StdRdOptions)
* (iv) add it to the appropriate handling routine (perhaps
@@ -147,15 +147,6 @@ static relopt_bool boolRelOpts[] =
},
false
},
- {
- {
- "vacuum_truncate",
- "Enables vacuum to truncate empty pages at the end of this table",
- RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
- ShareUpdateExclusiveLock
- },
- true
- },
{
{
"deduplicate_items",
@@ -170,6 +161,21 @@ static relopt_bool boolRelOpts[] =
{{NULL}}
};
+static relopt_ternary ternaryRelOpts[] =
+{
+ {
+ {
+ "vacuum_truncate",
+ "Enables vacuum to truncate empty pages at the end of this table",
+ RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
+ ShareUpdateExclusiveLock
+ },
+ TERNARY_UNSET
+ },
+ /* list terminator */
+ {{NULL}}
+};
+
static relopt_int intRelOpts[] =
{
{
@@ -600,6 +606,13 @@ initialize_reloptions(void)
boolRelOpts[i].gen.lockmode));
j++;
}
+ for (i = 0; ternaryRelOpts[i].gen.name; i++)
+ {
+ Assert(DoLockModesConflict(ternaryRelOpts[i].gen.lockmode,
+ ternaryRelOpts[i].gen.lockmode));
+ j++;
+ }
+
for (i = 0; intRelOpts[i].gen.name; i++)
{
Assert(DoLockModesConflict(intRelOpts[i].gen.lockmode,
@@ -640,6 +653,14 @@ initialize_reloptions(void)
j++;
}
+ for (i = 0; ternaryRelOpts[i].gen.name; i++)
+ {
+ relOpts[j] = &ternaryRelOpts[i].gen;
+ relOpts[j]->type = RELOPT_TYPE_TERNARY;
+ relOpts[j]->namelen = strlen(relOpts[j]->name);
+ j++;
+ }
+
for (i = 0; intRelOpts[i].gen.name; i++)
{
relOpts[j] = &intRelOpts[i].gen;
@@ -800,6 +821,9 @@ allocate_reloption(bits32 kinds, int type, const char *name, const char *desc,
case RELOPT_TYPE_BOOL:
size = sizeof(relopt_bool);
break;
+ case RELOPT_TYPE_TERNARY:
+ size = sizeof(relopt_ternary);
+ break;
case RELOPT_TYPE_INT:
size = sizeof(relopt_int);
break;
@@ -883,6 +907,54 @@ add_local_bool_reloption(local_relopts *relopts, const char *name,
add_local_reloption(relopts, (relopt_gen *) newoption, offset);
}
+/*
+ * init_ternary_reloption
+ * Allocate and initialize a new ternary reloption
+ */
+static relopt_ternary *
+init_ternary_reloption(bits32 kinds, const char *name, const char *desc,
+ ternary default_val, LOCKMODE lockmode)
+{
+ relopt_ternary *newoption;
+
+ newoption = (relopt_ternary *) allocate_reloption(kinds,
+ RELOPT_TYPE_TERNARY, name, desc, lockmode);
+ newoption->default_val = default_val;
+
+ return newoption;
+}
+
+/*
+ * add_ternary_reloption
+ * Add a new ternary reloption
+ */
+void
+add_ternary_reloption(bits32 kinds, const char *name, const char *desc,
+ ternary default_val, LOCKMODE lockmode)
+{
+ relopt_ternary *newoption = init_ternary_reloption(kinds, name, desc,
+ default_val, lockmode);
+
+ add_reloption((relopt_gen *) newoption);
+}
+
+/*
+ * add_local_ternary_reloption
+ * Add a new ternary local reloption
+ *
+ * 'offset' is offset of ternary-typed field.
+ */
+void
+add_local_ternary_reloption(local_relopts *relopts, const char *name,
+ const char *desc, ternary default_val,
+ int offset)
+{
+ relopt_ternary *newoption = init_ternary_reloption(RELOPT_KIND_LOCAL,
+ name, desc,
+ default_val, 0);
+
+ add_local_reloption(relopts, (relopt_gen *) newoption, offset);
+}
/*
* init_real_reloption
@@ -1617,6 +1689,18 @@ parse_one_reloption(relopt_value *option, char *text_str, int text_len,
option->gen->name, value)));
}
break;
+ case RELOPT_TYPE_TERNARY:
+ {
+ bool b;
+ parsed = parse_bool(value, &b);
+ option->values.ternary_val = b ? TERNARY_TRUE : TERNARY_FALSE;
+ if (validate && !parsed)
+ ereport(ERROR,
+ (errcode(ERRCODE_INVALID_PARAMETER_VALUE),
+ errmsg("invalid value for ternary option \"%s\": %s",
+ option->gen->name, value)));
+ }
+ break;
case RELOPT_TYPE_INT:
{
relopt_int *optint = (relopt_int *) option->gen;
@@ -1780,17 +1864,6 @@ fillRelOptions(void *rdopts, Size basesize,
char *itempos = ((char *) rdopts) + elems[j].offset;
char *string_val;
- /*
- * If isset_offset is provided, store whether the reloption is
- * set there.
- */
- if (elems[j].isset_offset > 0)
- {
- char *setpos = ((char *) rdopts) + elems[j].isset_offset;
-
- *(bool *) setpos = options[i].isset;
- }
-
switch (options[i].gen->type)
{
case RELOPT_TYPE_BOOL:
@@ -1798,6 +1871,11 @@ fillRelOptions(void *rdopts, Size basesize,
options[i].values.bool_val :
((relopt_bool *) options[i].gen)->default_val;
break;
+ case RELOPT_TYPE_TERNARY:
+ *(ternary *) itempos = options[i].isset ?
+ options[i].values.ternary_val :
+ ((relopt_ternary *) options[i].gen)->default_val;
+ break;
case RELOPT_TYPE_INT:
*(int *) itempos = options[i].isset ?
options[i].values.int_val :
@@ -1912,8 +1990,8 @@ default_reloptions(Datum reloptions, bool validate, relopt_kind kind)
offsetof(StdRdOptions, parallel_workers)},
{"vacuum_index_cleanup", RELOPT_TYPE_ENUM,
offsetof(StdRdOptions, vacuum_index_cleanup)},
- {"vacuum_truncate", RELOPT_TYPE_BOOL,
- offsetof(StdRdOptions, vacuum_truncate), offsetof(StdRdOptions, vacuum_truncate_set)},
+ {"vacuum_truncate", RELOPT_TYPE_TERNARY,
+ offsetof(StdRdOptions, vacuum_truncate)},
{"vacuum_max_eager_freeze_failure_rate", RELOPT_TYPE_REAL,
offsetof(StdRdOptions, vacuum_max_eager_freeze_failure_rate)}
};
@@ -1993,7 +2071,6 @@ build_local_reloptions(local_relopts *relopts, Datum options, bool validate)
elems[i].optname = opt->option->name;
elems[i].opttype = opt->option->type;
elems[i].offset = opt->offset;
- elems[i].isset_offset = 0; /* not supported for local relopts yet */
i++;
}
diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c
index 733ef40ae7..977babff54 100644
--- a/src/backend/commands/vacuum.c
+++ b/src/backend/commands/vacuum.c
@@ -2221,9 +2221,9 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams params,
{
StdRdOptions *opts = (StdRdOptions *) rel->rd_options;
- if (opts && opts->vacuum_truncate_set)
+ if (opts && opts->vacuum_truncate != TERNARY_UNSET)
{
- if (opts->vacuum_truncate)
+ if (opts->vacuum_truncate == TERNARY_TRUE)
params.truncate = VACOPTVALUE_ENABLED;
else
params.truncate = VACOPTVALUE_DISABLED;
diff --git a/src/include/access/reloptions.h b/src/include/access/reloptions.h
index a604a4702c..a436697658 100644
--- a/src/include/access/reloptions.h
+++ b/src/include/access/reloptions.h
@@ -29,6 +29,7 @@
typedef enum relopt_type
{
RELOPT_TYPE_BOOL,
+ RELOPT_TYPE_TERNARY, /* on, off, unset */
RELOPT_TYPE_INT,
RELOPT_TYPE_REAL,
RELOPT_TYPE_ENUM,
@@ -80,6 +81,7 @@ typedef struct relopt_value
union
{
bool bool_val;
+ ternary ternary_val;
int int_val;
double real_val;
int enum_val;
@@ -94,6 +96,12 @@ typedef struct relopt_bool
bool default_val;
} relopt_bool;
+typedef struct relopt_rernary
+{
+ relopt_gen gen;
+ int default_val;
+} relopt_ternary;
+
typedef struct relopt_int
{
relopt_gen gen;
@@ -152,19 +160,6 @@ typedef struct
const char *optname; /* option's name */
relopt_type opttype; /* option's datatype */
int offset; /* offset of field in result struct */
-
- /*
- * isset_offset is an optional offset of a field in the result struct that
- * stores whether the option is explicitly set for the relation or if it
- * just picked up the default value. In most cases, this can be
- * accomplished by giving the reloption a special out-of-range default
- * value (e.g., some integer reloptions use -2), but this isn't always
- * possible. For example, a Boolean reloption cannot be given an
- * out-of-range default, so we need another way to discover the source of
- * its value. This offset is only used if given a value greater than
- * zero.
- */
- int isset_offset;
} relopt_parse_elt;
/* Local reloption definition */
@@ -195,6 +190,8 @@ typedef struct local_relopts
extern relopt_kind add_reloption_kind(void);
extern void add_bool_reloption(bits32 kinds, const char *name, const char *desc,
bool default_val, LOCKMODE lockmode);
+extern void add_ternary_reloption(bits32 kinds, const char *name,
+ const char *desc, int default_val, LOCKMODE lockmode);
extern void add_int_reloption(bits32 kinds, const char *name, const char *desc,
int default_val, int min_val, int max_val,
LOCKMODE lockmode);
@@ -214,6 +211,9 @@ extern void register_reloptions_validator(local_relopts *relopts,
extern void add_local_bool_reloption(local_relopts *relopts, const char *name,
const char *desc, bool default_val,
int offset);
+extern void add_local_ternary_reloption(local_relopts *relopts,
+ const char *name, const char *desc,
+ ternary default_val, int offset);
extern void add_local_int_reloption(local_relopts *relopts, const char *name,
const char *desc, int default_val,
int min_val, int max_val, int offset);
diff --git a/src/include/c.h b/src/include/c.h
index 39022f8a9d..9c59ccccf8 100644
--- a/src/include/c.h
+++ b/src/include/c.h
@@ -488,6 +488,22 @@ typedef void (*pg_funcptr_t) (void);
#include <stdbool.h>
+/*
+ * ternary
+ * Boolean value with an extrea "unset" option
+ *
+ * Ternary data type is used in relation options that can be "true", "false" or
+ * "unset". Since relation options are used deep inside the PostgreSQL code,
+ * this type is declared globally.
+*/
+
+typedef enum ternary
+{
+ TERNARY_FALSE = 0,
+ TERNARY_TRUE = 1,
+ TERNARY_UNSET = -1
+} ternary;
+
/* ----------------------------------------------------------------
* Section 3: standard system types
diff --git a/src/include/utils/rel.h b/src/include/utils/rel.h
index b552359915..7346d618f9 100644
--- a/src/include/utils/rel.h
+++ b/src/include/utils/rel.h
@@ -346,8 +346,7 @@ typedef struct StdRdOptions
bool user_catalog_table; /* use as an additional catalog relation */
int parallel_workers; /* max number of parallel workers */
StdRdOptIndexCleanup vacuum_index_cleanup; /* controls index vacuuming */
- bool vacuum_truncate; /* enables vacuum to truncate a relation */
- bool vacuum_truncate_set; /* whether vacuum_truncate is set */
+ ternary vacuum_truncate; /* enables vacuum to truncate a relation */
/*
* Fraction of pages in a relation that vacuum can eagerly scan and fail
--
2.39.2
--nextPart4155600.3ZeAukHxDK
Content-Disposition: attachment;
filename="v2a-0003-Add-alias-to-be-used-as-unset-state.patch"
Content-Transfer-Encoding: 7Bit
Content-Type: text/x-patch; charset="unicode-2-0-utf-8";
name="v2a-0003-Add-alias-to-be-used-as-unset-state.patch"
^ permalink raw reply [nested|flat] 868+ messages in thread
* [PATCH v2 2/4] Introduce ternary reloptions
@ 2025-09-04 14:41 Nikolay Shaplov <[email protected]>
0 siblings, 0 replies; 868+ messages in thread
From: Nikolay Shaplov @ 2025-09-04 14:41 UTC (permalink / raw)
Introduce ternary reloption as a replacement for current `vacuum_truncate`
implementation. Remove `vacuum_truncate_set` additional flag and using
`TERNARY_UNSET` value instead.
---
src/backend/access/common/reloptions.c | 129 ++++++++++++++++++++-----
src/backend/commands/vacuum.c | 4 +-
src/include/access/reloptions.h | 26 ++---
src/include/c.h | 16 +++
src/include/utils/rel.h | 3 +-
5 files changed, 135 insertions(+), 43 deletions(-)
diff --git a/src/backend/access/common/reloptions.c b/src/backend/access/common/reloptions.c
index 0af3fea68f..f543f11001 100644
--- a/src/backend/access/common/reloptions.c
+++ b/src/backend/access/common/reloptions.c
@@ -40,9 +40,9 @@
*
* To add an option:
*
- * (i) decide on a type (bool, integer, real, enum, string), name, default
- * value, upper and lower bounds (if applicable); for strings, consider a
- * validation routine.
+ * (i) decide on a type (bool, ternary, integer, real, enum, string), name,
+ * default value, upper and lower bounds (if applicable); for strings,
+ * consider a validation routine.
* (ii) add a record below (or use add_<type>_reloption).
* (iii) add it to the appropriate options struct (perhaps StdRdOptions)
* (iv) add it to the appropriate handling routine (perhaps
@@ -147,15 +147,6 @@ static relopt_bool boolRelOpts[] =
},
false
},
- {
- {
- "vacuum_truncate",
- "Enables vacuum to truncate empty pages at the end of this table",
- RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
- ShareUpdateExclusiveLock
- },
- true
- },
{
{
"deduplicate_items",
@@ -170,6 +161,21 @@ static relopt_bool boolRelOpts[] =
{{NULL}}
};
+static relopt_ternary ternaryRelOpts[] =
+{
+ {
+ {
+ "vacuum_truncate",
+ "Enables vacuum to truncate empty pages at the end of this table",
+ RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
+ ShareUpdateExclusiveLock
+ },
+ TERNARY_UNSET
+ },
+ /* list terminator */
+ {{NULL}}
+};
+
static relopt_int intRelOpts[] =
{
{
@@ -600,6 +606,13 @@ initialize_reloptions(void)
boolRelOpts[i].gen.lockmode));
j++;
}
+ for (i = 0; ternaryRelOpts[i].gen.name; i++)
+ {
+ Assert(DoLockModesConflict(ternaryRelOpts[i].gen.lockmode,
+ ternaryRelOpts[i].gen.lockmode));
+ j++;
+ }
+
for (i = 0; intRelOpts[i].gen.name; i++)
{
Assert(DoLockModesConflict(intRelOpts[i].gen.lockmode,
@@ -640,6 +653,14 @@ initialize_reloptions(void)
j++;
}
+ for (i = 0; ternaryRelOpts[i].gen.name; i++)
+ {
+ relOpts[j] = &ternaryRelOpts[i].gen;
+ relOpts[j]->type = RELOPT_TYPE_TERNARY;
+ relOpts[j]->namelen = strlen(relOpts[j]->name);
+ j++;
+ }
+
for (i = 0; intRelOpts[i].gen.name; i++)
{
relOpts[j] = &intRelOpts[i].gen;
@@ -800,6 +821,9 @@ allocate_reloption(bits32 kinds, int type, const char *name, const char *desc,
case RELOPT_TYPE_BOOL:
size = sizeof(relopt_bool);
break;
+ case RELOPT_TYPE_TERNARY:
+ size = sizeof(relopt_ternary);
+ break;
case RELOPT_TYPE_INT:
size = sizeof(relopt_int);
break;
@@ -883,6 +907,54 @@ add_local_bool_reloption(local_relopts *relopts, const char *name,
add_local_reloption(relopts, (relopt_gen *) newoption, offset);
}
+/*
+ * init_ternary_reloption
+ * Allocate and initialize a new ternary reloption
+ */
+static relopt_ternary *
+init_ternary_reloption(bits32 kinds, const char *name, const char *desc,
+ ternary default_val, LOCKMODE lockmode)
+{
+ relopt_ternary *newoption;
+
+ newoption = (relopt_ternary *) allocate_reloption(kinds,
+ RELOPT_TYPE_TERNARY, name, desc, lockmode);
+ newoption->default_val = default_val;
+
+ return newoption;
+}
+
+/*
+ * add_ternary_reloption
+ * Add a new ternary reloption
+ */
+void
+add_ternary_reloption(bits32 kinds, const char *name, const char *desc,
+ ternary default_val, LOCKMODE lockmode)
+{
+ relopt_ternary *newoption = init_ternary_reloption(kinds, name, desc,
+ default_val, lockmode);
+
+ add_reloption((relopt_gen *) newoption);
+}
+
+/*
+ * add_local_ternary_reloption
+ * Add a new ternary local reloption
+ *
+ * 'offset' is offset of ternary-typed field.
+ */
+void
+add_local_ternary_reloption(local_relopts *relopts, const char *name,
+ const char *desc, ternary default_val,
+ int offset)
+{
+ relopt_ternary *newoption = init_ternary_reloption(RELOPT_KIND_LOCAL,
+ name, desc,
+ default_val, 0);
+
+ add_local_reloption(relopts, (relopt_gen *) newoption, offset);
+}
/*
* init_real_reloption
@@ -1617,6 +1689,18 @@ parse_one_reloption(relopt_value *option, char *text_str, int text_len,
option->gen->name, value)));
}
break;
+ case RELOPT_TYPE_TERNARY:
+ {
+ bool b;
+ parsed = parse_bool(value, &b);
+ option->values.ternary_val = b ? TERNARY_TRUE : TERNARY_FALSE;
+ if (validate && !parsed)
+ ereport(ERROR,
+ (errcode(ERRCODE_INVALID_PARAMETER_VALUE),
+ errmsg("invalid value for ternary option \"%s\": %s",
+ option->gen->name, value)));
+ }
+ break;
case RELOPT_TYPE_INT:
{
relopt_int *optint = (relopt_int *) option->gen;
@@ -1780,17 +1864,6 @@ fillRelOptions(void *rdopts, Size basesize,
char *itempos = ((char *) rdopts) + elems[j].offset;
char *string_val;
- /*
- * If isset_offset is provided, store whether the reloption is
- * set there.
- */
- if (elems[j].isset_offset > 0)
- {
- char *setpos = ((char *) rdopts) + elems[j].isset_offset;
-
- *(bool *) setpos = options[i].isset;
- }
-
switch (options[i].gen->type)
{
case RELOPT_TYPE_BOOL:
@@ -1798,6 +1871,11 @@ fillRelOptions(void *rdopts, Size basesize,
options[i].values.bool_val :
((relopt_bool *) options[i].gen)->default_val;
break;
+ case RELOPT_TYPE_TERNARY:
+ *(ternary *) itempos = options[i].isset ?
+ options[i].values.ternary_val :
+ ((relopt_ternary *) options[i].gen)->default_val;
+ break;
case RELOPT_TYPE_INT:
*(int *) itempos = options[i].isset ?
options[i].values.int_val :
@@ -1912,8 +1990,8 @@ default_reloptions(Datum reloptions, bool validate, relopt_kind kind)
offsetof(StdRdOptions, parallel_workers)},
{"vacuum_index_cleanup", RELOPT_TYPE_ENUM,
offsetof(StdRdOptions, vacuum_index_cleanup)},
- {"vacuum_truncate", RELOPT_TYPE_BOOL,
- offsetof(StdRdOptions, vacuum_truncate), offsetof(StdRdOptions, vacuum_truncate_set)},
+ {"vacuum_truncate", RELOPT_TYPE_TERNARY,
+ offsetof(StdRdOptions, vacuum_truncate)},
{"vacuum_max_eager_freeze_failure_rate", RELOPT_TYPE_REAL,
offsetof(StdRdOptions, vacuum_max_eager_freeze_failure_rate)}
};
@@ -1993,7 +2071,6 @@ build_local_reloptions(local_relopts *relopts, Datum options, bool validate)
elems[i].optname = opt->option->name;
elems[i].opttype = opt->option->type;
elems[i].offset = opt->offset;
- elems[i].isset_offset = 0; /* not supported for local relopts yet */
i++;
}
diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c
index 733ef40ae7..977babff54 100644
--- a/src/backend/commands/vacuum.c
+++ b/src/backend/commands/vacuum.c
@@ -2221,9 +2221,9 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams params,
{
StdRdOptions *opts = (StdRdOptions *) rel->rd_options;
- if (opts && opts->vacuum_truncate_set)
+ if (opts && opts->vacuum_truncate != TERNARY_UNSET)
{
- if (opts->vacuum_truncate)
+ if (opts->vacuum_truncate == TERNARY_TRUE)
params.truncate = VACOPTVALUE_ENABLED;
else
params.truncate = VACOPTVALUE_DISABLED;
diff --git a/src/include/access/reloptions.h b/src/include/access/reloptions.h
index a604a4702c..a436697658 100644
--- a/src/include/access/reloptions.h
+++ b/src/include/access/reloptions.h
@@ -29,6 +29,7 @@
typedef enum relopt_type
{
RELOPT_TYPE_BOOL,
+ RELOPT_TYPE_TERNARY, /* on, off, unset */
RELOPT_TYPE_INT,
RELOPT_TYPE_REAL,
RELOPT_TYPE_ENUM,
@@ -80,6 +81,7 @@ typedef struct relopt_value
union
{
bool bool_val;
+ ternary ternary_val;
int int_val;
double real_val;
int enum_val;
@@ -94,6 +96,12 @@ typedef struct relopt_bool
bool default_val;
} relopt_bool;
+typedef struct relopt_rernary
+{
+ relopt_gen gen;
+ int default_val;
+} relopt_ternary;
+
typedef struct relopt_int
{
relopt_gen gen;
@@ -152,19 +160,6 @@ typedef struct
const char *optname; /* option's name */
relopt_type opttype; /* option's datatype */
int offset; /* offset of field in result struct */
-
- /*
- * isset_offset is an optional offset of a field in the result struct that
- * stores whether the option is explicitly set for the relation or if it
- * just picked up the default value. In most cases, this can be
- * accomplished by giving the reloption a special out-of-range default
- * value (e.g., some integer reloptions use -2), but this isn't always
- * possible. For example, a Boolean reloption cannot be given an
- * out-of-range default, so we need another way to discover the source of
- * its value. This offset is only used if given a value greater than
- * zero.
- */
- int isset_offset;
} relopt_parse_elt;
/* Local reloption definition */
@@ -195,6 +190,8 @@ typedef struct local_relopts
extern relopt_kind add_reloption_kind(void);
extern void add_bool_reloption(bits32 kinds, const char *name, const char *desc,
bool default_val, LOCKMODE lockmode);
+extern void add_ternary_reloption(bits32 kinds, const char *name,
+ const char *desc, int default_val, LOCKMODE lockmode);
extern void add_int_reloption(bits32 kinds, const char *name, const char *desc,
int default_val, int min_val, int max_val,
LOCKMODE lockmode);
@@ -214,6 +211,9 @@ extern void register_reloptions_validator(local_relopts *relopts,
extern void add_local_bool_reloption(local_relopts *relopts, const char *name,
const char *desc, bool default_val,
int offset);
+extern void add_local_ternary_reloption(local_relopts *relopts,
+ const char *name, const char *desc,
+ ternary default_val, int offset);
extern void add_local_int_reloption(local_relopts *relopts, const char *name,
const char *desc, int default_val,
int min_val, int max_val, int offset);
diff --git a/src/include/c.h b/src/include/c.h
index 39022f8a9d..9c59ccccf8 100644
--- a/src/include/c.h
+++ b/src/include/c.h
@@ -488,6 +488,22 @@ typedef void (*pg_funcptr_t) (void);
#include <stdbool.h>
+/*
+ * ternary
+ * Boolean value with an extrea "unset" option
+ *
+ * Ternary data type is used in relation options that can be "true", "false" or
+ * "unset". Since relation options are used deep inside the PostgreSQL code,
+ * this type is declared globally.
+*/
+
+typedef enum ternary
+{
+ TERNARY_FALSE = 0,
+ TERNARY_TRUE = 1,
+ TERNARY_UNSET = -1
+} ternary;
+
/* ----------------------------------------------------------------
* Section 3: standard system types
diff --git a/src/include/utils/rel.h b/src/include/utils/rel.h
index b552359915..7346d618f9 100644
--- a/src/include/utils/rel.h
+++ b/src/include/utils/rel.h
@@ -346,8 +346,7 @@ typedef struct StdRdOptions
bool user_catalog_table; /* use as an additional catalog relation */
int parallel_workers; /* max number of parallel workers */
StdRdOptIndexCleanup vacuum_index_cleanup; /* controls index vacuuming */
- bool vacuum_truncate; /* enables vacuum to truncate a relation */
- bool vacuum_truncate_set; /* whether vacuum_truncate is set */
+ ternary vacuum_truncate; /* enables vacuum to truncate a relation */
/*
* Fraction of pages in a relation that vacuum can eagerly scan and fail
--
2.39.2
--nextPart5129932.5fSG56mABF
Content-Disposition: attachment;
filename="v2-0003-Add-alias-to-be-used-as-unset-state.patch"
Content-Transfer-Encoding: 7Bit
Content-Type: text/x-patch; charset="unicode-2-0-utf-8";
name="v2-0003-Add-alias-to-be-used-as-unset-state.patch"
^ permalink raw reply [nested|flat] 868+ messages in thread
* [PATCH v2a 2/4] Introduce ternary reloptions
@ 2025-09-04 14:41 Nikolay Shaplov <[email protected]>
0 siblings, 0 replies; 868+ messages in thread
From: Nikolay Shaplov @ 2025-09-04 14:41 UTC (permalink / raw)
Introduce ternary reloption as a replacement for current `vacuum_truncate`
implementation. Remove `vacuum_truncate_set` additional flag and using
`TERNARY_UNSET` value instead.
---
src/backend/access/common/reloptions.c | 129 ++++++++++++++++++++-----
src/backend/commands/vacuum.c | 4 +-
src/include/access/reloptions.h | 26 ++---
src/include/c.h | 16 +++
src/include/utils/rel.h | 3 +-
5 files changed, 135 insertions(+), 43 deletions(-)
diff --git a/src/backend/access/common/reloptions.c b/src/backend/access/common/reloptions.c
index 0af3fea68f..f543f11001 100644
--- a/src/backend/access/common/reloptions.c
+++ b/src/backend/access/common/reloptions.c
@@ -40,9 +40,9 @@
*
* To add an option:
*
- * (i) decide on a type (bool, integer, real, enum, string), name, default
- * value, upper and lower bounds (if applicable); for strings, consider a
- * validation routine.
+ * (i) decide on a type (bool, ternary, integer, real, enum, string), name,
+ * default value, upper and lower bounds (if applicable); for strings,
+ * consider a validation routine.
* (ii) add a record below (or use add_<type>_reloption).
* (iii) add it to the appropriate options struct (perhaps StdRdOptions)
* (iv) add it to the appropriate handling routine (perhaps
@@ -147,15 +147,6 @@ static relopt_bool boolRelOpts[] =
},
false
},
- {
- {
- "vacuum_truncate",
- "Enables vacuum to truncate empty pages at the end of this table",
- RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
- ShareUpdateExclusiveLock
- },
- true
- },
{
{
"deduplicate_items",
@@ -170,6 +161,21 @@ static relopt_bool boolRelOpts[] =
{{NULL}}
};
+static relopt_ternary ternaryRelOpts[] =
+{
+ {
+ {
+ "vacuum_truncate",
+ "Enables vacuum to truncate empty pages at the end of this table",
+ RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
+ ShareUpdateExclusiveLock
+ },
+ TERNARY_UNSET
+ },
+ /* list terminator */
+ {{NULL}}
+};
+
static relopt_int intRelOpts[] =
{
{
@@ -600,6 +606,13 @@ initialize_reloptions(void)
boolRelOpts[i].gen.lockmode));
j++;
}
+ for (i = 0; ternaryRelOpts[i].gen.name; i++)
+ {
+ Assert(DoLockModesConflict(ternaryRelOpts[i].gen.lockmode,
+ ternaryRelOpts[i].gen.lockmode));
+ j++;
+ }
+
for (i = 0; intRelOpts[i].gen.name; i++)
{
Assert(DoLockModesConflict(intRelOpts[i].gen.lockmode,
@@ -640,6 +653,14 @@ initialize_reloptions(void)
j++;
}
+ for (i = 0; ternaryRelOpts[i].gen.name; i++)
+ {
+ relOpts[j] = &ternaryRelOpts[i].gen;
+ relOpts[j]->type = RELOPT_TYPE_TERNARY;
+ relOpts[j]->namelen = strlen(relOpts[j]->name);
+ j++;
+ }
+
for (i = 0; intRelOpts[i].gen.name; i++)
{
relOpts[j] = &intRelOpts[i].gen;
@@ -800,6 +821,9 @@ allocate_reloption(bits32 kinds, int type, const char *name, const char *desc,
case RELOPT_TYPE_BOOL:
size = sizeof(relopt_bool);
break;
+ case RELOPT_TYPE_TERNARY:
+ size = sizeof(relopt_ternary);
+ break;
case RELOPT_TYPE_INT:
size = sizeof(relopt_int);
break;
@@ -883,6 +907,54 @@ add_local_bool_reloption(local_relopts *relopts, const char *name,
add_local_reloption(relopts, (relopt_gen *) newoption, offset);
}
+/*
+ * init_ternary_reloption
+ * Allocate and initialize a new ternary reloption
+ */
+static relopt_ternary *
+init_ternary_reloption(bits32 kinds, const char *name, const char *desc,
+ ternary default_val, LOCKMODE lockmode)
+{
+ relopt_ternary *newoption;
+
+ newoption = (relopt_ternary *) allocate_reloption(kinds,
+ RELOPT_TYPE_TERNARY, name, desc, lockmode);
+ newoption->default_val = default_val;
+
+ return newoption;
+}
+
+/*
+ * add_ternary_reloption
+ * Add a new ternary reloption
+ */
+void
+add_ternary_reloption(bits32 kinds, const char *name, const char *desc,
+ ternary default_val, LOCKMODE lockmode)
+{
+ relopt_ternary *newoption = init_ternary_reloption(kinds, name, desc,
+ default_val, lockmode);
+
+ add_reloption((relopt_gen *) newoption);
+}
+
+/*
+ * add_local_ternary_reloption
+ * Add a new ternary local reloption
+ *
+ * 'offset' is offset of ternary-typed field.
+ */
+void
+add_local_ternary_reloption(local_relopts *relopts, const char *name,
+ const char *desc, ternary default_val,
+ int offset)
+{
+ relopt_ternary *newoption = init_ternary_reloption(RELOPT_KIND_LOCAL,
+ name, desc,
+ default_val, 0);
+
+ add_local_reloption(relopts, (relopt_gen *) newoption, offset);
+}
/*
* init_real_reloption
@@ -1617,6 +1689,18 @@ parse_one_reloption(relopt_value *option, char *text_str, int text_len,
option->gen->name, value)));
}
break;
+ case RELOPT_TYPE_TERNARY:
+ {
+ bool b;
+ parsed = parse_bool(value, &b);
+ option->values.ternary_val = b ? TERNARY_TRUE : TERNARY_FALSE;
+ if (validate && !parsed)
+ ereport(ERROR,
+ (errcode(ERRCODE_INVALID_PARAMETER_VALUE),
+ errmsg("invalid value for ternary option \"%s\": %s",
+ option->gen->name, value)));
+ }
+ break;
case RELOPT_TYPE_INT:
{
relopt_int *optint = (relopt_int *) option->gen;
@@ -1780,17 +1864,6 @@ fillRelOptions(void *rdopts, Size basesize,
char *itempos = ((char *) rdopts) + elems[j].offset;
char *string_val;
- /*
- * If isset_offset is provided, store whether the reloption is
- * set there.
- */
- if (elems[j].isset_offset > 0)
- {
- char *setpos = ((char *) rdopts) + elems[j].isset_offset;
-
- *(bool *) setpos = options[i].isset;
- }
-
switch (options[i].gen->type)
{
case RELOPT_TYPE_BOOL:
@@ -1798,6 +1871,11 @@ fillRelOptions(void *rdopts, Size basesize,
options[i].values.bool_val :
((relopt_bool *) options[i].gen)->default_val;
break;
+ case RELOPT_TYPE_TERNARY:
+ *(ternary *) itempos = options[i].isset ?
+ options[i].values.ternary_val :
+ ((relopt_ternary *) options[i].gen)->default_val;
+ break;
case RELOPT_TYPE_INT:
*(int *) itempos = options[i].isset ?
options[i].values.int_val :
@@ -1912,8 +1990,8 @@ default_reloptions(Datum reloptions, bool validate, relopt_kind kind)
offsetof(StdRdOptions, parallel_workers)},
{"vacuum_index_cleanup", RELOPT_TYPE_ENUM,
offsetof(StdRdOptions, vacuum_index_cleanup)},
- {"vacuum_truncate", RELOPT_TYPE_BOOL,
- offsetof(StdRdOptions, vacuum_truncate), offsetof(StdRdOptions, vacuum_truncate_set)},
+ {"vacuum_truncate", RELOPT_TYPE_TERNARY,
+ offsetof(StdRdOptions, vacuum_truncate)},
{"vacuum_max_eager_freeze_failure_rate", RELOPT_TYPE_REAL,
offsetof(StdRdOptions, vacuum_max_eager_freeze_failure_rate)}
};
@@ -1993,7 +2071,6 @@ build_local_reloptions(local_relopts *relopts, Datum options, bool validate)
elems[i].optname = opt->option->name;
elems[i].opttype = opt->option->type;
elems[i].offset = opt->offset;
- elems[i].isset_offset = 0; /* not supported for local relopts yet */
i++;
}
diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c
index 733ef40ae7..977babff54 100644
--- a/src/backend/commands/vacuum.c
+++ b/src/backend/commands/vacuum.c
@@ -2221,9 +2221,9 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams params,
{
StdRdOptions *opts = (StdRdOptions *) rel->rd_options;
- if (opts && opts->vacuum_truncate_set)
+ if (opts && opts->vacuum_truncate != TERNARY_UNSET)
{
- if (opts->vacuum_truncate)
+ if (opts->vacuum_truncate == TERNARY_TRUE)
params.truncate = VACOPTVALUE_ENABLED;
else
params.truncate = VACOPTVALUE_DISABLED;
diff --git a/src/include/access/reloptions.h b/src/include/access/reloptions.h
index a604a4702c..a436697658 100644
--- a/src/include/access/reloptions.h
+++ b/src/include/access/reloptions.h
@@ -29,6 +29,7 @@
typedef enum relopt_type
{
RELOPT_TYPE_BOOL,
+ RELOPT_TYPE_TERNARY, /* on, off, unset */
RELOPT_TYPE_INT,
RELOPT_TYPE_REAL,
RELOPT_TYPE_ENUM,
@@ -80,6 +81,7 @@ typedef struct relopt_value
union
{
bool bool_val;
+ ternary ternary_val;
int int_val;
double real_val;
int enum_val;
@@ -94,6 +96,12 @@ typedef struct relopt_bool
bool default_val;
} relopt_bool;
+typedef struct relopt_rernary
+{
+ relopt_gen gen;
+ int default_val;
+} relopt_ternary;
+
typedef struct relopt_int
{
relopt_gen gen;
@@ -152,19 +160,6 @@ typedef struct
const char *optname; /* option's name */
relopt_type opttype; /* option's datatype */
int offset; /* offset of field in result struct */
-
- /*
- * isset_offset is an optional offset of a field in the result struct that
- * stores whether the option is explicitly set for the relation or if it
- * just picked up the default value. In most cases, this can be
- * accomplished by giving the reloption a special out-of-range default
- * value (e.g., some integer reloptions use -2), but this isn't always
- * possible. For example, a Boolean reloption cannot be given an
- * out-of-range default, so we need another way to discover the source of
- * its value. This offset is only used if given a value greater than
- * zero.
- */
- int isset_offset;
} relopt_parse_elt;
/* Local reloption definition */
@@ -195,6 +190,8 @@ typedef struct local_relopts
extern relopt_kind add_reloption_kind(void);
extern void add_bool_reloption(bits32 kinds, const char *name, const char *desc,
bool default_val, LOCKMODE lockmode);
+extern void add_ternary_reloption(bits32 kinds, const char *name,
+ const char *desc, int default_val, LOCKMODE lockmode);
extern void add_int_reloption(bits32 kinds, const char *name, const char *desc,
int default_val, int min_val, int max_val,
LOCKMODE lockmode);
@@ -214,6 +211,9 @@ extern void register_reloptions_validator(local_relopts *relopts,
extern void add_local_bool_reloption(local_relopts *relopts, const char *name,
const char *desc, bool default_val,
int offset);
+extern void add_local_ternary_reloption(local_relopts *relopts,
+ const char *name, const char *desc,
+ ternary default_val, int offset);
extern void add_local_int_reloption(local_relopts *relopts, const char *name,
const char *desc, int default_val,
int min_val, int max_val, int offset);
diff --git a/src/include/c.h b/src/include/c.h
index 39022f8a9d..9c59ccccf8 100644
--- a/src/include/c.h
+++ b/src/include/c.h
@@ -488,6 +488,22 @@ typedef void (*pg_funcptr_t) (void);
#include <stdbool.h>
+/*
+ * ternary
+ * Boolean value with an extrea "unset" option
+ *
+ * Ternary data type is used in relation options that can be "true", "false" or
+ * "unset". Since relation options are used deep inside the PostgreSQL code,
+ * this type is declared globally.
+*/
+
+typedef enum ternary
+{
+ TERNARY_FALSE = 0,
+ TERNARY_TRUE = 1,
+ TERNARY_UNSET = -1
+} ternary;
+
/* ----------------------------------------------------------------
* Section 3: standard system types
diff --git a/src/include/utils/rel.h b/src/include/utils/rel.h
index b552359915..7346d618f9 100644
--- a/src/include/utils/rel.h
+++ b/src/include/utils/rel.h
@@ -346,8 +346,7 @@ typedef struct StdRdOptions
bool user_catalog_table; /* use as an additional catalog relation */
int parallel_workers; /* max number of parallel workers */
StdRdOptIndexCleanup vacuum_index_cleanup; /* controls index vacuuming */
- bool vacuum_truncate; /* enables vacuum to truncate a relation */
- bool vacuum_truncate_set; /* whether vacuum_truncate is set */
+ ternary vacuum_truncate; /* enables vacuum to truncate a relation */
/*
* Fraction of pages in a relation that vacuum can eagerly scan and fail
--
2.39.2
--nextPart4155600.3ZeAukHxDK
Content-Disposition: attachment;
filename="v2a-0003-Add-alias-to-be-used-as-unset-state.patch"
Content-Transfer-Encoding: 7Bit
Content-Type: text/x-patch; charset="unicode-2-0-utf-8";
name="v2a-0003-Add-alias-to-be-used-as-unset-state.patch"
^ permalink raw reply [nested|flat] 868+ messages in thread
* [PATCH v2a 2/4] Introduce ternary reloptions
@ 2025-09-04 14:41 Nikolay Shaplov <[email protected]>
0 siblings, 0 replies; 868+ messages in thread
From: Nikolay Shaplov @ 2025-09-04 14:41 UTC (permalink / raw)
Introduce ternary reloption as a replacement for current `vacuum_truncate`
implementation. Remove `vacuum_truncate_set` additional flag and using
`TERNARY_UNSET` value instead.
---
src/backend/access/common/reloptions.c | 129 ++++++++++++++++++++-----
src/backend/commands/vacuum.c | 4 +-
src/include/access/reloptions.h | 26 ++---
src/include/c.h | 16 +++
src/include/utils/rel.h | 3 +-
5 files changed, 135 insertions(+), 43 deletions(-)
diff --git a/src/backend/access/common/reloptions.c b/src/backend/access/common/reloptions.c
index 0af3fea68f..f543f11001 100644
--- a/src/backend/access/common/reloptions.c
+++ b/src/backend/access/common/reloptions.c
@@ -40,9 +40,9 @@
*
* To add an option:
*
- * (i) decide on a type (bool, integer, real, enum, string), name, default
- * value, upper and lower bounds (if applicable); for strings, consider a
- * validation routine.
+ * (i) decide on a type (bool, ternary, integer, real, enum, string), name,
+ * default value, upper and lower bounds (if applicable); for strings,
+ * consider a validation routine.
* (ii) add a record below (or use add_<type>_reloption).
* (iii) add it to the appropriate options struct (perhaps StdRdOptions)
* (iv) add it to the appropriate handling routine (perhaps
@@ -147,15 +147,6 @@ static relopt_bool boolRelOpts[] =
},
false
},
- {
- {
- "vacuum_truncate",
- "Enables vacuum to truncate empty pages at the end of this table",
- RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
- ShareUpdateExclusiveLock
- },
- true
- },
{
{
"deduplicate_items",
@@ -170,6 +161,21 @@ static relopt_bool boolRelOpts[] =
{{NULL}}
};
+static relopt_ternary ternaryRelOpts[] =
+{
+ {
+ {
+ "vacuum_truncate",
+ "Enables vacuum to truncate empty pages at the end of this table",
+ RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
+ ShareUpdateExclusiveLock
+ },
+ TERNARY_UNSET
+ },
+ /* list terminator */
+ {{NULL}}
+};
+
static relopt_int intRelOpts[] =
{
{
@@ -600,6 +606,13 @@ initialize_reloptions(void)
boolRelOpts[i].gen.lockmode));
j++;
}
+ for (i = 0; ternaryRelOpts[i].gen.name; i++)
+ {
+ Assert(DoLockModesConflict(ternaryRelOpts[i].gen.lockmode,
+ ternaryRelOpts[i].gen.lockmode));
+ j++;
+ }
+
for (i = 0; intRelOpts[i].gen.name; i++)
{
Assert(DoLockModesConflict(intRelOpts[i].gen.lockmode,
@@ -640,6 +653,14 @@ initialize_reloptions(void)
j++;
}
+ for (i = 0; ternaryRelOpts[i].gen.name; i++)
+ {
+ relOpts[j] = &ternaryRelOpts[i].gen;
+ relOpts[j]->type = RELOPT_TYPE_TERNARY;
+ relOpts[j]->namelen = strlen(relOpts[j]->name);
+ j++;
+ }
+
for (i = 0; intRelOpts[i].gen.name; i++)
{
relOpts[j] = &intRelOpts[i].gen;
@@ -800,6 +821,9 @@ allocate_reloption(bits32 kinds, int type, const char *name, const char *desc,
case RELOPT_TYPE_BOOL:
size = sizeof(relopt_bool);
break;
+ case RELOPT_TYPE_TERNARY:
+ size = sizeof(relopt_ternary);
+ break;
case RELOPT_TYPE_INT:
size = sizeof(relopt_int);
break;
@@ -883,6 +907,54 @@ add_local_bool_reloption(local_relopts *relopts, const char *name,
add_local_reloption(relopts, (relopt_gen *) newoption, offset);
}
+/*
+ * init_ternary_reloption
+ * Allocate and initialize a new ternary reloption
+ */
+static relopt_ternary *
+init_ternary_reloption(bits32 kinds, const char *name, const char *desc,
+ ternary default_val, LOCKMODE lockmode)
+{
+ relopt_ternary *newoption;
+
+ newoption = (relopt_ternary *) allocate_reloption(kinds,
+ RELOPT_TYPE_TERNARY, name, desc, lockmode);
+ newoption->default_val = default_val;
+
+ return newoption;
+}
+
+/*
+ * add_ternary_reloption
+ * Add a new ternary reloption
+ */
+void
+add_ternary_reloption(bits32 kinds, const char *name, const char *desc,
+ ternary default_val, LOCKMODE lockmode)
+{
+ relopt_ternary *newoption = init_ternary_reloption(kinds, name, desc,
+ default_val, lockmode);
+
+ add_reloption((relopt_gen *) newoption);
+}
+
+/*
+ * add_local_ternary_reloption
+ * Add a new ternary local reloption
+ *
+ * 'offset' is offset of ternary-typed field.
+ */
+void
+add_local_ternary_reloption(local_relopts *relopts, const char *name,
+ const char *desc, ternary default_val,
+ int offset)
+{
+ relopt_ternary *newoption = init_ternary_reloption(RELOPT_KIND_LOCAL,
+ name, desc,
+ default_val, 0);
+
+ add_local_reloption(relopts, (relopt_gen *) newoption, offset);
+}
/*
* init_real_reloption
@@ -1617,6 +1689,18 @@ parse_one_reloption(relopt_value *option, char *text_str, int text_len,
option->gen->name, value)));
}
break;
+ case RELOPT_TYPE_TERNARY:
+ {
+ bool b;
+ parsed = parse_bool(value, &b);
+ option->values.ternary_val = b ? TERNARY_TRUE : TERNARY_FALSE;
+ if (validate && !parsed)
+ ereport(ERROR,
+ (errcode(ERRCODE_INVALID_PARAMETER_VALUE),
+ errmsg("invalid value for ternary option \"%s\": %s",
+ option->gen->name, value)));
+ }
+ break;
case RELOPT_TYPE_INT:
{
relopt_int *optint = (relopt_int *) option->gen;
@@ -1780,17 +1864,6 @@ fillRelOptions(void *rdopts, Size basesize,
char *itempos = ((char *) rdopts) + elems[j].offset;
char *string_val;
- /*
- * If isset_offset is provided, store whether the reloption is
- * set there.
- */
- if (elems[j].isset_offset > 0)
- {
- char *setpos = ((char *) rdopts) + elems[j].isset_offset;
-
- *(bool *) setpos = options[i].isset;
- }
-
switch (options[i].gen->type)
{
case RELOPT_TYPE_BOOL:
@@ -1798,6 +1871,11 @@ fillRelOptions(void *rdopts, Size basesize,
options[i].values.bool_val :
((relopt_bool *) options[i].gen)->default_val;
break;
+ case RELOPT_TYPE_TERNARY:
+ *(ternary *) itempos = options[i].isset ?
+ options[i].values.ternary_val :
+ ((relopt_ternary *) options[i].gen)->default_val;
+ break;
case RELOPT_TYPE_INT:
*(int *) itempos = options[i].isset ?
options[i].values.int_val :
@@ -1912,8 +1990,8 @@ default_reloptions(Datum reloptions, bool validate, relopt_kind kind)
offsetof(StdRdOptions, parallel_workers)},
{"vacuum_index_cleanup", RELOPT_TYPE_ENUM,
offsetof(StdRdOptions, vacuum_index_cleanup)},
- {"vacuum_truncate", RELOPT_TYPE_BOOL,
- offsetof(StdRdOptions, vacuum_truncate), offsetof(StdRdOptions, vacuum_truncate_set)},
+ {"vacuum_truncate", RELOPT_TYPE_TERNARY,
+ offsetof(StdRdOptions, vacuum_truncate)},
{"vacuum_max_eager_freeze_failure_rate", RELOPT_TYPE_REAL,
offsetof(StdRdOptions, vacuum_max_eager_freeze_failure_rate)}
};
@@ -1993,7 +2071,6 @@ build_local_reloptions(local_relopts *relopts, Datum options, bool validate)
elems[i].optname = opt->option->name;
elems[i].opttype = opt->option->type;
elems[i].offset = opt->offset;
- elems[i].isset_offset = 0; /* not supported for local relopts yet */
i++;
}
diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c
index 733ef40ae7..977babff54 100644
--- a/src/backend/commands/vacuum.c
+++ b/src/backend/commands/vacuum.c
@@ -2221,9 +2221,9 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams params,
{
StdRdOptions *opts = (StdRdOptions *) rel->rd_options;
- if (opts && opts->vacuum_truncate_set)
+ if (opts && opts->vacuum_truncate != TERNARY_UNSET)
{
- if (opts->vacuum_truncate)
+ if (opts->vacuum_truncate == TERNARY_TRUE)
params.truncate = VACOPTVALUE_ENABLED;
else
params.truncate = VACOPTVALUE_DISABLED;
diff --git a/src/include/access/reloptions.h b/src/include/access/reloptions.h
index a604a4702c..a436697658 100644
--- a/src/include/access/reloptions.h
+++ b/src/include/access/reloptions.h
@@ -29,6 +29,7 @@
typedef enum relopt_type
{
RELOPT_TYPE_BOOL,
+ RELOPT_TYPE_TERNARY, /* on, off, unset */
RELOPT_TYPE_INT,
RELOPT_TYPE_REAL,
RELOPT_TYPE_ENUM,
@@ -80,6 +81,7 @@ typedef struct relopt_value
union
{
bool bool_val;
+ ternary ternary_val;
int int_val;
double real_val;
int enum_val;
@@ -94,6 +96,12 @@ typedef struct relopt_bool
bool default_val;
} relopt_bool;
+typedef struct relopt_rernary
+{
+ relopt_gen gen;
+ int default_val;
+} relopt_ternary;
+
typedef struct relopt_int
{
relopt_gen gen;
@@ -152,19 +160,6 @@ typedef struct
const char *optname; /* option's name */
relopt_type opttype; /* option's datatype */
int offset; /* offset of field in result struct */
-
- /*
- * isset_offset is an optional offset of a field in the result struct that
- * stores whether the option is explicitly set for the relation or if it
- * just picked up the default value. In most cases, this can be
- * accomplished by giving the reloption a special out-of-range default
- * value (e.g., some integer reloptions use -2), but this isn't always
- * possible. For example, a Boolean reloption cannot be given an
- * out-of-range default, so we need another way to discover the source of
- * its value. This offset is only used if given a value greater than
- * zero.
- */
- int isset_offset;
} relopt_parse_elt;
/* Local reloption definition */
@@ -195,6 +190,8 @@ typedef struct local_relopts
extern relopt_kind add_reloption_kind(void);
extern void add_bool_reloption(bits32 kinds, const char *name, const char *desc,
bool default_val, LOCKMODE lockmode);
+extern void add_ternary_reloption(bits32 kinds, const char *name,
+ const char *desc, int default_val, LOCKMODE lockmode);
extern void add_int_reloption(bits32 kinds, const char *name, const char *desc,
int default_val, int min_val, int max_val,
LOCKMODE lockmode);
@@ -214,6 +211,9 @@ extern void register_reloptions_validator(local_relopts *relopts,
extern void add_local_bool_reloption(local_relopts *relopts, const char *name,
const char *desc, bool default_val,
int offset);
+extern void add_local_ternary_reloption(local_relopts *relopts,
+ const char *name, const char *desc,
+ ternary default_val, int offset);
extern void add_local_int_reloption(local_relopts *relopts, const char *name,
const char *desc, int default_val,
int min_val, int max_val, int offset);
diff --git a/src/include/c.h b/src/include/c.h
index 39022f8a9d..9c59ccccf8 100644
--- a/src/include/c.h
+++ b/src/include/c.h
@@ -488,6 +488,22 @@ typedef void (*pg_funcptr_t) (void);
#include <stdbool.h>
+/*
+ * ternary
+ * Boolean value with an extrea "unset" option
+ *
+ * Ternary data type is used in relation options that can be "true", "false" or
+ * "unset". Since relation options are used deep inside the PostgreSQL code,
+ * this type is declared globally.
+*/
+
+typedef enum ternary
+{
+ TERNARY_FALSE = 0,
+ TERNARY_TRUE = 1,
+ TERNARY_UNSET = -1
+} ternary;
+
/* ----------------------------------------------------------------
* Section 3: standard system types
diff --git a/src/include/utils/rel.h b/src/include/utils/rel.h
index b552359915..7346d618f9 100644
--- a/src/include/utils/rel.h
+++ b/src/include/utils/rel.h
@@ -346,8 +346,7 @@ typedef struct StdRdOptions
bool user_catalog_table; /* use as an additional catalog relation */
int parallel_workers; /* max number of parallel workers */
StdRdOptIndexCleanup vacuum_index_cleanup; /* controls index vacuuming */
- bool vacuum_truncate; /* enables vacuum to truncate a relation */
- bool vacuum_truncate_set; /* whether vacuum_truncate is set */
+ ternary vacuum_truncate; /* enables vacuum to truncate a relation */
/*
* Fraction of pages in a relation that vacuum can eagerly scan and fail
--
2.39.2
--nextPart4155600.3ZeAukHxDK
Content-Disposition: attachment;
filename="v2a-0003-Add-alias-to-be-used-as-unset-state.patch"
Content-Transfer-Encoding: 7Bit
Content-Type: text/x-patch; charset="unicode-2-0-utf-8";
name="v2a-0003-Add-alias-to-be-used-as-unset-state.patch"
^ permalink raw reply [nested|flat] 868+ messages in thread
* [PATCH v2 2/4] Introduce ternary reloptions
@ 2025-09-04 14:41 Nikolay Shaplov <[email protected]>
0 siblings, 0 replies; 868+ messages in thread
From: Nikolay Shaplov @ 2025-09-04 14:41 UTC (permalink / raw)
Introduce ternary reloption as a replacement for current `vacuum_truncate`
implementation. Remove `vacuum_truncate_set` additional flag and using
`TERNARY_UNSET` value instead.
---
src/backend/access/common/reloptions.c | 129 ++++++++++++++++++++-----
src/backend/commands/vacuum.c | 4 +-
src/include/access/reloptions.h | 26 ++---
src/include/c.h | 16 +++
src/include/utils/rel.h | 3 +-
5 files changed, 135 insertions(+), 43 deletions(-)
diff --git a/src/backend/access/common/reloptions.c b/src/backend/access/common/reloptions.c
index 0af3fea68f..f543f11001 100644
--- a/src/backend/access/common/reloptions.c
+++ b/src/backend/access/common/reloptions.c
@@ -40,9 +40,9 @@
*
* To add an option:
*
- * (i) decide on a type (bool, integer, real, enum, string), name, default
- * value, upper and lower bounds (if applicable); for strings, consider a
- * validation routine.
+ * (i) decide on a type (bool, ternary, integer, real, enum, string), name,
+ * default value, upper and lower bounds (if applicable); for strings,
+ * consider a validation routine.
* (ii) add a record below (or use add_<type>_reloption).
* (iii) add it to the appropriate options struct (perhaps StdRdOptions)
* (iv) add it to the appropriate handling routine (perhaps
@@ -147,15 +147,6 @@ static relopt_bool boolRelOpts[] =
},
false
},
- {
- {
- "vacuum_truncate",
- "Enables vacuum to truncate empty pages at the end of this table",
- RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
- ShareUpdateExclusiveLock
- },
- true
- },
{
{
"deduplicate_items",
@@ -170,6 +161,21 @@ static relopt_bool boolRelOpts[] =
{{NULL}}
};
+static relopt_ternary ternaryRelOpts[] =
+{
+ {
+ {
+ "vacuum_truncate",
+ "Enables vacuum to truncate empty pages at the end of this table",
+ RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
+ ShareUpdateExclusiveLock
+ },
+ TERNARY_UNSET
+ },
+ /* list terminator */
+ {{NULL}}
+};
+
static relopt_int intRelOpts[] =
{
{
@@ -600,6 +606,13 @@ initialize_reloptions(void)
boolRelOpts[i].gen.lockmode));
j++;
}
+ for (i = 0; ternaryRelOpts[i].gen.name; i++)
+ {
+ Assert(DoLockModesConflict(ternaryRelOpts[i].gen.lockmode,
+ ternaryRelOpts[i].gen.lockmode));
+ j++;
+ }
+
for (i = 0; intRelOpts[i].gen.name; i++)
{
Assert(DoLockModesConflict(intRelOpts[i].gen.lockmode,
@@ -640,6 +653,14 @@ initialize_reloptions(void)
j++;
}
+ for (i = 0; ternaryRelOpts[i].gen.name; i++)
+ {
+ relOpts[j] = &ternaryRelOpts[i].gen;
+ relOpts[j]->type = RELOPT_TYPE_TERNARY;
+ relOpts[j]->namelen = strlen(relOpts[j]->name);
+ j++;
+ }
+
for (i = 0; intRelOpts[i].gen.name; i++)
{
relOpts[j] = &intRelOpts[i].gen;
@@ -800,6 +821,9 @@ allocate_reloption(bits32 kinds, int type, const char *name, const char *desc,
case RELOPT_TYPE_BOOL:
size = sizeof(relopt_bool);
break;
+ case RELOPT_TYPE_TERNARY:
+ size = sizeof(relopt_ternary);
+ break;
case RELOPT_TYPE_INT:
size = sizeof(relopt_int);
break;
@@ -883,6 +907,54 @@ add_local_bool_reloption(local_relopts *relopts, const char *name,
add_local_reloption(relopts, (relopt_gen *) newoption, offset);
}
+/*
+ * init_ternary_reloption
+ * Allocate and initialize a new ternary reloption
+ */
+static relopt_ternary *
+init_ternary_reloption(bits32 kinds, const char *name, const char *desc,
+ ternary default_val, LOCKMODE lockmode)
+{
+ relopt_ternary *newoption;
+
+ newoption = (relopt_ternary *) allocate_reloption(kinds,
+ RELOPT_TYPE_TERNARY, name, desc, lockmode);
+ newoption->default_val = default_val;
+
+ return newoption;
+}
+
+/*
+ * add_ternary_reloption
+ * Add a new ternary reloption
+ */
+void
+add_ternary_reloption(bits32 kinds, const char *name, const char *desc,
+ ternary default_val, LOCKMODE lockmode)
+{
+ relopt_ternary *newoption = init_ternary_reloption(kinds, name, desc,
+ default_val, lockmode);
+
+ add_reloption((relopt_gen *) newoption);
+}
+
+/*
+ * add_local_ternary_reloption
+ * Add a new ternary local reloption
+ *
+ * 'offset' is offset of ternary-typed field.
+ */
+void
+add_local_ternary_reloption(local_relopts *relopts, const char *name,
+ const char *desc, ternary default_val,
+ int offset)
+{
+ relopt_ternary *newoption = init_ternary_reloption(RELOPT_KIND_LOCAL,
+ name, desc,
+ default_val, 0);
+
+ add_local_reloption(relopts, (relopt_gen *) newoption, offset);
+}
/*
* init_real_reloption
@@ -1617,6 +1689,18 @@ parse_one_reloption(relopt_value *option, char *text_str, int text_len,
option->gen->name, value)));
}
break;
+ case RELOPT_TYPE_TERNARY:
+ {
+ bool b;
+ parsed = parse_bool(value, &b);
+ option->values.ternary_val = b ? TERNARY_TRUE : TERNARY_FALSE;
+ if (validate && !parsed)
+ ereport(ERROR,
+ (errcode(ERRCODE_INVALID_PARAMETER_VALUE),
+ errmsg("invalid value for ternary option \"%s\": %s",
+ option->gen->name, value)));
+ }
+ break;
case RELOPT_TYPE_INT:
{
relopt_int *optint = (relopt_int *) option->gen;
@@ -1780,17 +1864,6 @@ fillRelOptions(void *rdopts, Size basesize,
char *itempos = ((char *) rdopts) + elems[j].offset;
char *string_val;
- /*
- * If isset_offset is provided, store whether the reloption is
- * set there.
- */
- if (elems[j].isset_offset > 0)
- {
- char *setpos = ((char *) rdopts) + elems[j].isset_offset;
-
- *(bool *) setpos = options[i].isset;
- }
-
switch (options[i].gen->type)
{
case RELOPT_TYPE_BOOL:
@@ -1798,6 +1871,11 @@ fillRelOptions(void *rdopts, Size basesize,
options[i].values.bool_val :
((relopt_bool *) options[i].gen)->default_val;
break;
+ case RELOPT_TYPE_TERNARY:
+ *(ternary *) itempos = options[i].isset ?
+ options[i].values.ternary_val :
+ ((relopt_ternary *) options[i].gen)->default_val;
+ break;
case RELOPT_TYPE_INT:
*(int *) itempos = options[i].isset ?
options[i].values.int_val :
@@ -1912,8 +1990,8 @@ default_reloptions(Datum reloptions, bool validate, relopt_kind kind)
offsetof(StdRdOptions, parallel_workers)},
{"vacuum_index_cleanup", RELOPT_TYPE_ENUM,
offsetof(StdRdOptions, vacuum_index_cleanup)},
- {"vacuum_truncate", RELOPT_TYPE_BOOL,
- offsetof(StdRdOptions, vacuum_truncate), offsetof(StdRdOptions, vacuum_truncate_set)},
+ {"vacuum_truncate", RELOPT_TYPE_TERNARY,
+ offsetof(StdRdOptions, vacuum_truncate)},
{"vacuum_max_eager_freeze_failure_rate", RELOPT_TYPE_REAL,
offsetof(StdRdOptions, vacuum_max_eager_freeze_failure_rate)}
};
@@ -1993,7 +2071,6 @@ build_local_reloptions(local_relopts *relopts, Datum options, bool validate)
elems[i].optname = opt->option->name;
elems[i].opttype = opt->option->type;
elems[i].offset = opt->offset;
- elems[i].isset_offset = 0; /* not supported for local relopts yet */
i++;
}
diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c
index 733ef40ae7..977babff54 100644
--- a/src/backend/commands/vacuum.c
+++ b/src/backend/commands/vacuum.c
@@ -2221,9 +2221,9 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams params,
{
StdRdOptions *opts = (StdRdOptions *) rel->rd_options;
- if (opts && opts->vacuum_truncate_set)
+ if (opts && opts->vacuum_truncate != TERNARY_UNSET)
{
- if (opts->vacuum_truncate)
+ if (opts->vacuum_truncate == TERNARY_TRUE)
params.truncate = VACOPTVALUE_ENABLED;
else
params.truncate = VACOPTVALUE_DISABLED;
diff --git a/src/include/access/reloptions.h b/src/include/access/reloptions.h
index a604a4702c..a436697658 100644
--- a/src/include/access/reloptions.h
+++ b/src/include/access/reloptions.h
@@ -29,6 +29,7 @@
typedef enum relopt_type
{
RELOPT_TYPE_BOOL,
+ RELOPT_TYPE_TERNARY, /* on, off, unset */
RELOPT_TYPE_INT,
RELOPT_TYPE_REAL,
RELOPT_TYPE_ENUM,
@@ -80,6 +81,7 @@ typedef struct relopt_value
union
{
bool bool_val;
+ ternary ternary_val;
int int_val;
double real_val;
int enum_val;
@@ -94,6 +96,12 @@ typedef struct relopt_bool
bool default_val;
} relopt_bool;
+typedef struct relopt_rernary
+{
+ relopt_gen gen;
+ int default_val;
+} relopt_ternary;
+
typedef struct relopt_int
{
relopt_gen gen;
@@ -152,19 +160,6 @@ typedef struct
const char *optname; /* option's name */
relopt_type opttype; /* option's datatype */
int offset; /* offset of field in result struct */
-
- /*
- * isset_offset is an optional offset of a field in the result struct that
- * stores whether the option is explicitly set for the relation or if it
- * just picked up the default value. In most cases, this can be
- * accomplished by giving the reloption a special out-of-range default
- * value (e.g., some integer reloptions use -2), but this isn't always
- * possible. For example, a Boolean reloption cannot be given an
- * out-of-range default, so we need another way to discover the source of
- * its value. This offset is only used if given a value greater than
- * zero.
- */
- int isset_offset;
} relopt_parse_elt;
/* Local reloption definition */
@@ -195,6 +190,8 @@ typedef struct local_relopts
extern relopt_kind add_reloption_kind(void);
extern void add_bool_reloption(bits32 kinds, const char *name, const char *desc,
bool default_val, LOCKMODE lockmode);
+extern void add_ternary_reloption(bits32 kinds, const char *name,
+ const char *desc, int default_val, LOCKMODE lockmode);
extern void add_int_reloption(bits32 kinds, const char *name, const char *desc,
int default_val, int min_val, int max_val,
LOCKMODE lockmode);
@@ -214,6 +211,9 @@ extern void register_reloptions_validator(local_relopts *relopts,
extern void add_local_bool_reloption(local_relopts *relopts, const char *name,
const char *desc, bool default_val,
int offset);
+extern void add_local_ternary_reloption(local_relopts *relopts,
+ const char *name, const char *desc,
+ ternary default_val, int offset);
extern void add_local_int_reloption(local_relopts *relopts, const char *name,
const char *desc, int default_val,
int min_val, int max_val, int offset);
diff --git a/src/include/c.h b/src/include/c.h
index 39022f8a9d..9c59ccccf8 100644
--- a/src/include/c.h
+++ b/src/include/c.h
@@ -488,6 +488,22 @@ typedef void (*pg_funcptr_t) (void);
#include <stdbool.h>
+/*
+ * ternary
+ * Boolean value with an extrea "unset" option
+ *
+ * Ternary data type is used in relation options that can be "true", "false" or
+ * "unset". Since relation options are used deep inside the PostgreSQL code,
+ * this type is declared globally.
+*/
+
+typedef enum ternary
+{
+ TERNARY_FALSE = 0,
+ TERNARY_TRUE = 1,
+ TERNARY_UNSET = -1
+} ternary;
+
/* ----------------------------------------------------------------
* Section 3: standard system types
diff --git a/src/include/utils/rel.h b/src/include/utils/rel.h
index b552359915..7346d618f9 100644
--- a/src/include/utils/rel.h
+++ b/src/include/utils/rel.h
@@ -346,8 +346,7 @@ typedef struct StdRdOptions
bool user_catalog_table; /* use as an additional catalog relation */
int parallel_workers; /* max number of parallel workers */
StdRdOptIndexCleanup vacuum_index_cleanup; /* controls index vacuuming */
- bool vacuum_truncate; /* enables vacuum to truncate a relation */
- bool vacuum_truncate_set; /* whether vacuum_truncate is set */
+ ternary vacuum_truncate; /* enables vacuum to truncate a relation */
/*
* Fraction of pages in a relation that vacuum can eagerly scan and fail
--
2.39.2
--nextPart5129932.5fSG56mABF
Content-Disposition: attachment;
filename="v2-0003-Add-alias-to-be-used-as-unset-state.patch"
Content-Transfer-Encoding: 7Bit
Content-Type: text/x-patch; charset="unicode-2-0-utf-8";
name="v2-0003-Add-alias-to-be-used-as-unset-state.patch"
^ permalink raw reply [nested|flat] 868+ messages in thread
* [PATCH v2 2/4] Introduce ternary reloptions
@ 2025-09-04 14:41 Nikolay Shaplov <[email protected]>
0 siblings, 0 replies; 868+ messages in thread
From: Nikolay Shaplov @ 2025-09-04 14:41 UTC (permalink / raw)
Introduce ternary reloption as a replacement for current `vacuum_truncate`
implementation. Remove `vacuum_truncate_set` additional flag and using
`TERNARY_UNSET` value instead.
---
src/backend/access/common/reloptions.c | 129 ++++++++++++++++++++-----
src/backend/commands/vacuum.c | 4 +-
src/include/access/reloptions.h | 26 ++---
src/include/c.h | 16 +++
src/include/utils/rel.h | 3 +-
5 files changed, 135 insertions(+), 43 deletions(-)
diff --git a/src/backend/access/common/reloptions.c b/src/backend/access/common/reloptions.c
index 0af3fea68f..f543f11001 100644
--- a/src/backend/access/common/reloptions.c
+++ b/src/backend/access/common/reloptions.c
@@ -40,9 +40,9 @@
*
* To add an option:
*
- * (i) decide on a type (bool, integer, real, enum, string), name, default
- * value, upper and lower bounds (if applicable); for strings, consider a
- * validation routine.
+ * (i) decide on a type (bool, ternary, integer, real, enum, string), name,
+ * default value, upper and lower bounds (if applicable); for strings,
+ * consider a validation routine.
* (ii) add a record below (or use add_<type>_reloption).
* (iii) add it to the appropriate options struct (perhaps StdRdOptions)
* (iv) add it to the appropriate handling routine (perhaps
@@ -147,15 +147,6 @@ static relopt_bool boolRelOpts[] =
},
false
},
- {
- {
- "vacuum_truncate",
- "Enables vacuum to truncate empty pages at the end of this table",
- RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
- ShareUpdateExclusiveLock
- },
- true
- },
{
{
"deduplicate_items",
@@ -170,6 +161,21 @@ static relopt_bool boolRelOpts[] =
{{NULL}}
};
+static relopt_ternary ternaryRelOpts[] =
+{
+ {
+ {
+ "vacuum_truncate",
+ "Enables vacuum to truncate empty pages at the end of this table",
+ RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
+ ShareUpdateExclusiveLock
+ },
+ TERNARY_UNSET
+ },
+ /* list terminator */
+ {{NULL}}
+};
+
static relopt_int intRelOpts[] =
{
{
@@ -600,6 +606,13 @@ initialize_reloptions(void)
boolRelOpts[i].gen.lockmode));
j++;
}
+ for (i = 0; ternaryRelOpts[i].gen.name; i++)
+ {
+ Assert(DoLockModesConflict(ternaryRelOpts[i].gen.lockmode,
+ ternaryRelOpts[i].gen.lockmode));
+ j++;
+ }
+
for (i = 0; intRelOpts[i].gen.name; i++)
{
Assert(DoLockModesConflict(intRelOpts[i].gen.lockmode,
@@ -640,6 +653,14 @@ initialize_reloptions(void)
j++;
}
+ for (i = 0; ternaryRelOpts[i].gen.name; i++)
+ {
+ relOpts[j] = &ternaryRelOpts[i].gen;
+ relOpts[j]->type = RELOPT_TYPE_TERNARY;
+ relOpts[j]->namelen = strlen(relOpts[j]->name);
+ j++;
+ }
+
for (i = 0; intRelOpts[i].gen.name; i++)
{
relOpts[j] = &intRelOpts[i].gen;
@@ -800,6 +821,9 @@ allocate_reloption(bits32 kinds, int type, const char *name, const char *desc,
case RELOPT_TYPE_BOOL:
size = sizeof(relopt_bool);
break;
+ case RELOPT_TYPE_TERNARY:
+ size = sizeof(relopt_ternary);
+ break;
case RELOPT_TYPE_INT:
size = sizeof(relopt_int);
break;
@@ -883,6 +907,54 @@ add_local_bool_reloption(local_relopts *relopts, const char *name,
add_local_reloption(relopts, (relopt_gen *) newoption, offset);
}
+/*
+ * init_ternary_reloption
+ * Allocate and initialize a new ternary reloption
+ */
+static relopt_ternary *
+init_ternary_reloption(bits32 kinds, const char *name, const char *desc,
+ ternary default_val, LOCKMODE lockmode)
+{
+ relopt_ternary *newoption;
+
+ newoption = (relopt_ternary *) allocate_reloption(kinds,
+ RELOPT_TYPE_TERNARY, name, desc, lockmode);
+ newoption->default_val = default_val;
+
+ return newoption;
+}
+
+/*
+ * add_ternary_reloption
+ * Add a new ternary reloption
+ */
+void
+add_ternary_reloption(bits32 kinds, const char *name, const char *desc,
+ ternary default_val, LOCKMODE lockmode)
+{
+ relopt_ternary *newoption = init_ternary_reloption(kinds, name, desc,
+ default_val, lockmode);
+
+ add_reloption((relopt_gen *) newoption);
+}
+
+/*
+ * add_local_ternary_reloption
+ * Add a new ternary local reloption
+ *
+ * 'offset' is offset of ternary-typed field.
+ */
+void
+add_local_ternary_reloption(local_relopts *relopts, const char *name,
+ const char *desc, ternary default_val,
+ int offset)
+{
+ relopt_ternary *newoption = init_ternary_reloption(RELOPT_KIND_LOCAL,
+ name, desc,
+ default_val, 0);
+
+ add_local_reloption(relopts, (relopt_gen *) newoption, offset);
+}
/*
* init_real_reloption
@@ -1617,6 +1689,18 @@ parse_one_reloption(relopt_value *option, char *text_str, int text_len,
option->gen->name, value)));
}
break;
+ case RELOPT_TYPE_TERNARY:
+ {
+ bool b;
+ parsed = parse_bool(value, &b);
+ option->values.ternary_val = b ? TERNARY_TRUE : TERNARY_FALSE;
+ if (validate && !parsed)
+ ereport(ERROR,
+ (errcode(ERRCODE_INVALID_PARAMETER_VALUE),
+ errmsg("invalid value for ternary option \"%s\": %s",
+ option->gen->name, value)));
+ }
+ break;
case RELOPT_TYPE_INT:
{
relopt_int *optint = (relopt_int *) option->gen;
@@ -1780,17 +1864,6 @@ fillRelOptions(void *rdopts, Size basesize,
char *itempos = ((char *) rdopts) + elems[j].offset;
char *string_val;
- /*
- * If isset_offset is provided, store whether the reloption is
- * set there.
- */
- if (elems[j].isset_offset > 0)
- {
- char *setpos = ((char *) rdopts) + elems[j].isset_offset;
-
- *(bool *) setpos = options[i].isset;
- }
-
switch (options[i].gen->type)
{
case RELOPT_TYPE_BOOL:
@@ -1798,6 +1871,11 @@ fillRelOptions(void *rdopts, Size basesize,
options[i].values.bool_val :
((relopt_bool *) options[i].gen)->default_val;
break;
+ case RELOPT_TYPE_TERNARY:
+ *(ternary *) itempos = options[i].isset ?
+ options[i].values.ternary_val :
+ ((relopt_ternary *) options[i].gen)->default_val;
+ break;
case RELOPT_TYPE_INT:
*(int *) itempos = options[i].isset ?
options[i].values.int_val :
@@ -1912,8 +1990,8 @@ default_reloptions(Datum reloptions, bool validate, relopt_kind kind)
offsetof(StdRdOptions, parallel_workers)},
{"vacuum_index_cleanup", RELOPT_TYPE_ENUM,
offsetof(StdRdOptions, vacuum_index_cleanup)},
- {"vacuum_truncate", RELOPT_TYPE_BOOL,
- offsetof(StdRdOptions, vacuum_truncate), offsetof(StdRdOptions, vacuum_truncate_set)},
+ {"vacuum_truncate", RELOPT_TYPE_TERNARY,
+ offsetof(StdRdOptions, vacuum_truncate)},
{"vacuum_max_eager_freeze_failure_rate", RELOPT_TYPE_REAL,
offsetof(StdRdOptions, vacuum_max_eager_freeze_failure_rate)}
};
@@ -1993,7 +2071,6 @@ build_local_reloptions(local_relopts *relopts, Datum options, bool validate)
elems[i].optname = opt->option->name;
elems[i].opttype = opt->option->type;
elems[i].offset = opt->offset;
- elems[i].isset_offset = 0; /* not supported for local relopts yet */
i++;
}
diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c
index 733ef40ae7..977babff54 100644
--- a/src/backend/commands/vacuum.c
+++ b/src/backend/commands/vacuum.c
@@ -2221,9 +2221,9 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams params,
{
StdRdOptions *opts = (StdRdOptions *) rel->rd_options;
- if (opts && opts->vacuum_truncate_set)
+ if (opts && opts->vacuum_truncate != TERNARY_UNSET)
{
- if (opts->vacuum_truncate)
+ if (opts->vacuum_truncate == TERNARY_TRUE)
params.truncate = VACOPTVALUE_ENABLED;
else
params.truncate = VACOPTVALUE_DISABLED;
diff --git a/src/include/access/reloptions.h b/src/include/access/reloptions.h
index a604a4702c..a436697658 100644
--- a/src/include/access/reloptions.h
+++ b/src/include/access/reloptions.h
@@ -29,6 +29,7 @@
typedef enum relopt_type
{
RELOPT_TYPE_BOOL,
+ RELOPT_TYPE_TERNARY, /* on, off, unset */
RELOPT_TYPE_INT,
RELOPT_TYPE_REAL,
RELOPT_TYPE_ENUM,
@@ -80,6 +81,7 @@ typedef struct relopt_value
union
{
bool bool_val;
+ ternary ternary_val;
int int_val;
double real_val;
int enum_val;
@@ -94,6 +96,12 @@ typedef struct relopt_bool
bool default_val;
} relopt_bool;
+typedef struct relopt_rernary
+{
+ relopt_gen gen;
+ int default_val;
+} relopt_ternary;
+
typedef struct relopt_int
{
relopt_gen gen;
@@ -152,19 +160,6 @@ typedef struct
const char *optname; /* option's name */
relopt_type opttype; /* option's datatype */
int offset; /* offset of field in result struct */
-
- /*
- * isset_offset is an optional offset of a field in the result struct that
- * stores whether the option is explicitly set for the relation or if it
- * just picked up the default value. In most cases, this can be
- * accomplished by giving the reloption a special out-of-range default
- * value (e.g., some integer reloptions use -2), but this isn't always
- * possible. For example, a Boolean reloption cannot be given an
- * out-of-range default, so we need another way to discover the source of
- * its value. This offset is only used if given a value greater than
- * zero.
- */
- int isset_offset;
} relopt_parse_elt;
/* Local reloption definition */
@@ -195,6 +190,8 @@ typedef struct local_relopts
extern relopt_kind add_reloption_kind(void);
extern void add_bool_reloption(bits32 kinds, const char *name, const char *desc,
bool default_val, LOCKMODE lockmode);
+extern void add_ternary_reloption(bits32 kinds, const char *name,
+ const char *desc, int default_val, LOCKMODE lockmode);
extern void add_int_reloption(bits32 kinds, const char *name, const char *desc,
int default_val, int min_val, int max_val,
LOCKMODE lockmode);
@@ -214,6 +211,9 @@ extern void register_reloptions_validator(local_relopts *relopts,
extern void add_local_bool_reloption(local_relopts *relopts, const char *name,
const char *desc, bool default_val,
int offset);
+extern void add_local_ternary_reloption(local_relopts *relopts,
+ const char *name, const char *desc,
+ ternary default_val, int offset);
extern void add_local_int_reloption(local_relopts *relopts, const char *name,
const char *desc, int default_val,
int min_val, int max_val, int offset);
diff --git a/src/include/c.h b/src/include/c.h
index 39022f8a9d..9c59ccccf8 100644
--- a/src/include/c.h
+++ b/src/include/c.h
@@ -488,6 +488,22 @@ typedef void (*pg_funcptr_t) (void);
#include <stdbool.h>
+/*
+ * ternary
+ * Boolean value with an extrea "unset" option
+ *
+ * Ternary data type is used in relation options that can be "true", "false" or
+ * "unset". Since relation options are used deep inside the PostgreSQL code,
+ * this type is declared globally.
+*/
+
+typedef enum ternary
+{
+ TERNARY_FALSE = 0,
+ TERNARY_TRUE = 1,
+ TERNARY_UNSET = -1
+} ternary;
+
/* ----------------------------------------------------------------
* Section 3: standard system types
diff --git a/src/include/utils/rel.h b/src/include/utils/rel.h
index b552359915..7346d618f9 100644
--- a/src/include/utils/rel.h
+++ b/src/include/utils/rel.h
@@ -346,8 +346,7 @@ typedef struct StdRdOptions
bool user_catalog_table; /* use as an additional catalog relation */
int parallel_workers; /* max number of parallel workers */
StdRdOptIndexCleanup vacuum_index_cleanup; /* controls index vacuuming */
- bool vacuum_truncate; /* enables vacuum to truncate a relation */
- bool vacuum_truncate_set; /* whether vacuum_truncate is set */
+ ternary vacuum_truncate; /* enables vacuum to truncate a relation */
/*
* Fraction of pages in a relation that vacuum can eagerly scan and fail
--
2.39.2
--nextPart5129932.5fSG56mABF
Content-Disposition: attachment;
filename="v2-0003-Add-alias-to-be-used-as-unset-state.patch"
Content-Transfer-Encoding: 7Bit
Content-Type: text/x-patch; charset="unicode-2-0-utf-8";
name="v2-0003-Add-alias-to-be-used-as-unset-state.patch"
^ permalink raw reply [nested|flat] 868+ messages in thread
* [PATCH v2a 2/4] Introduce ternary reloptions
@ 2025-09-04 14:41 Nikolay Shaplov <[email protected]>
0 siblings, 0 replies; 868+ messages in thread
From: Nikolay Shaplov @ 2025-09-04 14:41 UTC (permalink / raw)
Introduce ternary reloption as a replacement for current `vacuum_truncate`
implementation. Remove `vacuum_truncate_set` additional flag and using
`TERNARY_UNSET` value instead.
---
src/backend/access/common/reloptions.c | 129 ++++++++++++++++++++-----
src/backend/commands/vacuum.c | 4 +-
src/include/access/reloptions.h | 26 ++---
src/include/c.h | 16 +++
src/include/utils/rel.h | 3 +-
5 files changed, 135 insertions(+), 43 deletions(-)
diff --git a/src/backend/access/common/reloptions.c b/src/backend/access/common/reloptions.c
index 0af3fea68f..f543f11001 100644
--- a/src/backend/access/common/reloptions.c
+++ b/src/backend/access/common/reloptions.c
@@ -40,9 +40,9 @@
*
* To add an option:
*
- * (i) decide on a type (bool, integer, real, enum, string), name, default
- * value, upper and lower bounds (if applicable); for strings, consider a
- * validation routine.
+ * (i) decide on a type (bool, ternary, integer, real, enum, string), name,
+ * default value, upper and lower bounds (if applicable); for strings,
+ * consider a validation routine.
* (ii) add a record below (or use add_<type>_reloption).
* (iii) add it to the appropriate options struct (perhaps StdRdOptions)
* (iv) add it to the appropriate handling routine (perhaps
@@ -147,15 +147,6 @@ static relopt_bool boolRelOpts[] =
},
false
},
- {
- {
- "vacuum_truncate",
- "Enables vacuum to truncate empty pages at the end of this table",
- RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
- ShareUpdateExclusiveLock
- },
- true
- },
{
{
"deduplicate_items",
@@ -170,6 +161,21 @@ static relopt_bool boolRelOpts[] =
{{NULL}}
};
+static relopt_ternary ternaryRelOpts[] =
+{
+ {
+ {
+ "vacuum_truncate",
+ "Enables vacuum to truncate empty pages at the end of this table",
+ RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
+ ShareUpdateExclusiveLock
+ },
+ TERNARY_UNSET
+ },
+ /* list terminator */
+ {{NULL}}
+};
+
static relopt_int intRelOpts[] =
{
{
@@ -600,6 +606,13 @@ initialize_reloptions(void)
boolRelOpts[i].gen.lockmode));
j++;
}
+ for (i = 0; ternaryRelOpts[i].gen.name; i++)
+ {
+ Assert(DoLockModesConflict(ternaryRelOpts[i].gen.lockmode,
+ ternaryRelOpts[i].gen.lockmode));
+ j++;
+ }
+
for (i = 0; intRelOpts[i].gen.name; i++)
{
Assert(DoLockModesConflict(intRelOpts[i].gen.lockmode,
@@ -640,6 +653,14 @@ initialize_reloptions(void)
j++;
}
+ for (i = 0; ternaryRelOpts[i].gen.name; i++)
+ {
+ relOpts[j] = &ternaryRelOpts[i].gen;
+ relOpts[j]->type = RELOPT_TYPE_TERNARY;
+ relOpts[j]->namelen = strlen(relOpts[j]->name);
+ j++;
+ }
+
for (i = 0; intRelOpts[i].gen.name; i++)
{
relOpts[j] = &intRelOpts[i].gen;
@@ -800,6 +821,9 @@ allocate_reloption(bits32 kinds, int type, const char *name, const char *desc,
case RELOPT_TYPE_BOOL:
size = sizeof(relopt_bool);
break;
+ case RELOPT_TYPE_TERNARY:
+ size = sizeof(relopt_ternary);
+ break;
case RELOPT_TYPE_INT:
size = sizeof(relopt_int);
break;
@@ -883,6 +907,54 @@ add_local_bool_reloption(local_relopts *relopts, const char *name,
add_local_reloption(relopts, (relopt_gen *) newoption, offset);
}
+/*
+ * init_ternary_reloption
+ * Allocate and initialize a new ternary reloption
+ */
+static relopt_ternary *
+init_ternary_reloption(bits32 kinds, const char *name, const char *desc,
+ ternary default_val, LOCKMODE lockmode)
+{
+ relopt_ternary *newoption;
+
+ newoption = (relopt_ternary *) allocate_reloption(kinds,
+ RELOPT_TYPE_TERNARY, name, desc, lockmode);
+ newoption->default_val = default_val;
+
+ return newoption;
+}
+
+/*
+ * add_ternary_reloption
+ * Add a new ternary reloption
+ */
+void
+add_ternary_reloption(bits32 kinds, const char *name, const char *desc,
+ ternary default_val, LOCKMODE lockmode)
+{
+ relopt_ternary *newoption = init_ternary_reloption(kinds, name, desc,
+ default_val, lockmode);
+
+ add_reloption((relopt_gen *) newoption);
+}
+
+/*
+ * add_local_ternary_reloption
+ * Add a new ternary local reloption
+ *
+ * 'offset' is offset of ternary-typed field.
+ */
+void
+add_local_ternary_reloption(local_relopts *relopts, const char *name,
+ const char *desc, ternary default_val,
+ int offset)
+{
+ relopt_ternary *newoption = init_ternary_reloption(RELOPT_KIND_LOCAL,
+ name, desc,
+ default_val, 0);
+
+ add_local_reloption(relopts, (relopt_gen *) newoption, offset);
+}
/*
* init_real_reloption
@@ -1617,6 +1689,18 @@ parse_one_reloption(relopt_value *option, char *text_str, int text_len,
option->gen->name, value)));
}
break;
+ case RELOPT_TYPE_TERNARY:
+ {
+ bool b;
+ parsed = parse_bool(value, &b);
+ option->values.ternary_val = b ? TERNARY_TRUE : TERNARY_FALSE;
+ if (validate && !parsed)
+ ereport(ERROR,
+ (errcode(ERRCODE_INVALID_PARAMETER_VALUE),
+ errmsg("invalid value for ternary option \"%s\": %s",
+ option->gen->name, value)));
+ }
+ break;
case RELOPT_TYPE_INT:
{
relopt_int *optint = (relopt_int *) option->gen;
@@ -1780,17 +1864,6 @@ fillRelOptions(void *rdopts, Size basesize,
char *itempos = ((char *) rdopts) + elems[j].offset;
char *string_val;
- /*
- * If isset_offset is provided, store whether the reloption is
- * set there.
- */
- if (elems[j].isset_offset > 0)
- {
- char *setpos = ((char *) rdopts) + elems[j].isset_offset;
-
- *(bool *) setpos = options[i].isset;
- }
-
switch (options[i].gen->type)
{
case RELOPT_TYPE_BOOL:
@@ -1798,6 +1871,11 @@ fillRelOptions(void *rdopts, Size basesize,
options[i].values.bool_val :
((relopt_bool *) options[i].gen)->default_val;
break;
+ case RELOPT_TYPE_TERNARY:
+ *(ternary *) itempos = options[i].isset ?
+ options[i].values.ternary_val :
+ ((relopt_ternary *) options[i].gen)->default_val;
+ break;
case RELOPT_TYPE_INT:
*(int *) itempos = options[i].isset ?
options[i].values.int_val :
@@ -1912,8 +1990,8 @@ default_reloptions(Datum reloptions, bool validate, relopt_kind kind)
offsetof(StdRdOptions, parallel_workers)},
{"vacuum_index_cleanup", RELOPT_TYPE_ENUM,
offsetof(StdRdOptions, vacuum_index_cleanup)},
- {"vacuum_truncate", RELOPT_TYPE_BOOL,
- offsetof(StdRdOptions, vacuum_truncate), offsetof(StdRdOptions, vacuum_truncate_set)},
+ {"vacuum_truncate", RELOPT_TYPE_TERNARY,
+ offsetof(StdRdOptions, vacuum_truncate)},
{"vacuum_max_eager_freeze_failure_rate", RELOPT_TYPE_REAL,
offsetof(StdRdOptions, vacuum_max_eager_freeze_failure_rate)}
};
@@ -1993,7 +2071,6 @@ build_local_reloptions(local_relopts *relopts, Datum options, bool validate)
elems[i].optname = opt->option->name;
elems[i].opttype = opt->option->type;
elems[i].offset = opt->offset;
- elems[i].isset_offset = 0; /* not supported for local relopts yet */
i++;
}
diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c
index 733ef40ae7..977babff54 100644
--- a/src/backend/commands/vacuum.c
+++ b/src/backend/commands/vacuum.c
@@ -2221,9 +2221,9 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams params,
{
StdRdOptions *opts = (StdRdOptions *) rel->rd_options;
- if (opts && opts->vacuum_truncate_set)
+ if (opts && opts->vacuum_truncate != TERNARY_UNSET)
{
- if (opts->vacuum_truncate)
+ if (opts->vacuum_truncate == TERNARY_TRUE)
params.truncate = VACOPTVALUE_ENABLED;
else
params.truncate = VACOPTVALUE_DISABLED;
diff --git a/src/include/access/reloptions.h b/src/include/access/reloptions.h
index a604a4702c..a436697658 100644
--- a/src/include/access/reloptions.h
+++ b/src/include/access/reloptions.h
@@ -29,6 +29,7 @@
typedef enum relopt_type
{
RELOPT_TYPE_BOOL,
+ RELOPT_TYPE_TERNARY, /* on, off, unset */
RELOPT_TYPE_INT,
RELOPT_TYPE_REAL,
RELOPT_TYPE_ENUM,
@@ -80,6 +81,7 @@ typedef struct relopt_value
union
{
bool bool_val;
+ ternary ternary_val;
int int_val;
double real_val;
int enum_val;
@@ -94,6 +96,12 @@ typedef struct relopt_bool
bool default_val;
} relopt_bool;
+typedef struct relopt_rernary
+{
+ relopt_gen gen;
+ int default_val;
+} relopt_ternary;
+
typedef struct relopt_int
{
relopt_gen gen;
@@ -152,19 +160,6 @@ typedef struct
const char *optname; /* option's name */
relopt_type opttype; /* option's datatype */
int offset; /* offset of field in result struct */
-
- /*
- * isset_offset is an optional offset of a field in the result struct that
- * stores whether the option is explicitly set for the relation or if it
- * just picked up the default value. In most cases, this can be
- * accomplished by giving the reloption a special out-of-range default
- * value (e.g., some integer reloptions use -2), but this isn't always
- * possible. For example, a Boolean reloption cannot be given an
- * out-of-range default, so we need another way to discover the source of
- * its value. This offset is only used if given a value greater than
- * zero.
- */
- int isset_offset;
} relopt_parse_elt;
/* Local reloption definition */
@@ -195,6 +190,8 @@ typedef struct local_relopts
extern relopt_kind add_reloption_kind(void);
extern void add_bool_reloption(bits32 kinds, const char *name, const char *desc,
bool default_val, LOCKMODE lockmode);
+extern void add_ternary_reloption(bits32 kinds, const char *name,
+ const char *desc, int default_val, LOCKMODE lockmode);
extern void add_int_reloption(bits32 kinds, const char *name, const char *desc,
int default_val, int min_val, int max_val,
LOCKMODE lockmode);
@@ -214,6 +211,9 @@ extern void register_reloptions_validator(local_relopts *relopts,
extern void add_local_bool_reloption(local_relopts *relopts, const char *name,
const char *desc, bool default_val,
int offset);
+extern void add_local_ternary_reloption(local_relopts *relopts,
+ const char *name, const char *desc,
+ ternary default_val, int offset);
extern void add_local_int_reloption(local_relopts *relopts, const char *name,
const char *desc, int default_val,
int min_val, int max_val, int offset);
diff --git a/src/include/c.h b/src/include/c.h
index 39022f8a9d..9c59ccccf8 100644
--- a/src/include/c.h
+++ b/src/include/c.h
@@ -488,6 +488,22 @@ typedef void (*pg_funcptr_t) (void);
#include <stdbool.h>
+/*
+ * ternary
+ * Boolean value with an extrea "unset" option
+ *
+ * Ternary data type is used in relation options that can be "true", "false" or
+ * "unset". Since relation options are used deep inside the PostgreSQL code,
+ * this type is declared globally.
+*/
+
+typedef enum ternary
+{
+ TERNARY_FALSE = 0,
+ TERNARY_TRUE = 1,
+ TERNARY_UNSET = -1
+} ternary;
+
/* ----------------------------------------------------------------
* Section 3: standard system types
diff --git a/src/include/utils/rel.h b/src/include/utils/rel.h
index b552359915..7346d618f9 100644
--- a/src/include/utils/rel.h
+++ b/src/include/utils/rel.h
@@ -346,8 +346,7 @@ typedef struct StdRdOptions
bool user_catalog_table; /* use as an additional catalog relation */
int parallel_workers; /* max number of parallel workers */
StdRdOptIndexCleanup vacuum_index_cleanup; /* controls index vacuuming */
- bool vacuum_truncate; /* enables vacuum to truncate a relation */
- bool vacuum_truncate_set; /* whether vacuum_truncate is set */
+ ternary vacuum_truncate; /* enables vacuum to truncate a relation */
/*
* Fraction of pages in a relation that vacuum can eagerly scan and fail
--
2.39.2
--nextPart4155600.3ZeAukHxDK
Content-Disposition: attachment;
filename="v2a-0003-Add-alias-to-be-used-as-unset-state.patch"
Content-Transfer-Encoding: 7Bit
Content-Type: text/x-patch; charset="unicode-2-0-utf-8";
name="v2a-0003-Add-alias-to-be-used-as-unset-state.patch"
^ permalink raw reply [nested|flat] 868+ messages in thread
* [PATCH v2a 2/4] Introduce ternary reloptions
@ 2025-09-04 14:41 Nikolay Shaplov <[email protected]>
0 siblings, 0 replies; 868+ messages in thread
From: Nikolay Shaplov @ 2025-09-04 14:41 UTC (permalink / raw)
Introduce ternary reloption as a replacement for current `vacuum_truncate`
implementation. Remove `vacuum_truncate_set` additional flag and using
`TERNARY_UNSET` value instead.
---
src/backend/access/common/reloptions.c | 129 ++++++++++++++++++++-----
src/backend/commands/vacuum.c | 4 +-
src/include/access/reloptions.h | 26 ++---
src/include/c.h | 16 +++
src/include/utils/rel.h | 3 +-
5 files changed, 135 insertions(+), 43 deletions(-)
diff --git a/src/backend/access/common/reloptions.c b/src/backend/access/common/reloptions.c
index 0af3fea68f..f543f11001 100644
--- a/src/backend/access/common/reloptions.c
+++ b/src/backend/access/common/reloptions.c
@@ -40,9 +40,9 @@
*
* To add an option:
*
- * (i) decide on a type (bool, integer, real, enum, string), name, default
- * value, upper and lower bounds (if applicable); for strings, consider a
- * validation routine.
+ * (i) decide on a type (bool, ternary, integer, real, enum, string), name,
+ * default value, upper and lower bounds (if applicable); for strings,
+ * consider a validation routine.
* (ii) add a record below (or use add_<type>_reloption).
* (iii) add it to the appropriate options struct (perhaps StdRdOptions)
* (iv) add it to the appropriate handling routine (perhaps
@@ -147,15 +147,6 @@ static relopt_bool boolRelOpts[] =
},
false
},
- {
- {
- "vacuum_truncate",
- "Enables vacuum to truncate empty pages at the end of this table",
- RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
- ShareUpdateExclusiveLock
- },
- true
- },
{
{
"deduplicate_items",
@@ -170,6 +161,21 @@ static relopt_bool boolRelOpts[] =
{{NULL}}
};
+static relopt_ternary ternaryRelOpts[] =
+{
+ {
+ {
+ "vacuum_truncate",
+ "Enables vacuum to truncate empty pages at the end of this table",
+ RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
+ ShareUpdateExclusiveLock
+ },
+ TERNARY_UNSET
+ },
+ /* list terminator */
+ {{NULL}}
+};
+
static relopt_int intRelOpts[] =
{
{
@@ -600,6 +606,13 @@ initialize_reloptions(void)
boolRelOpts[i].gen.lockmode));
j++;
}
+ for (i = 0; ternaryRelOpts[i].gen.name; i++)
+ {
+ Assert(DoLockModesConflict(ternaryRelOpts[i].gen.lockmode,
+ ternaryRelOpts[i].gen.lockmode));
+ j++;
+ }
+
for (i = 0; intRelOpts[i].gen.name; i++)
{
Assert(DoLockModesConflict(intRelOpts[i].gen.lockmode,
@@ -640,6 +653,14 @@ initialize_reloptions(void)
j++;
}
+ for (i = 0; ternaryRelOpts[i].gen.name; i++)
+ {
+ relOpts[j] = &ternaryRelOpts[i].gen;
+ relOpts[j]->type = RELOPT_TYPE_TERNARY;
+ relOpts[j]->namelen = strlen(relOpts[j]->name);
+ j++;
+ }
+
for (i = 0; intRelOpts[i].gen.name; i++)
{
relOpts[j] = &intRelOpts[i].gen;
@@ -800,6 +821,9 @@ allocate_reloption(bits32 kinds, int type, const char *name, const char *desc,
case RELOPT_TYPE_BOOL:
size = sizeof(relopt_bool);
break;
+ case RELOPT_TYPE_TERNARY:
+ size = sizeof(relopt_ternary);
+ break;
case RELOPT_TYPE_INT:
size = sizeof(relopt_int);
break;
@@ -883,6 +907,54 @@ add_local_bool_reloption(local_relopts *relopts, const char *name,
add_local_reloption(relopts, (relopt_gen *) newoption, offset);
}
+/*
+ * init_ternary_reloption
+ * Allocate and initialize a new ternary reloption
+ */
+static relopt_ternary *
+init_ternary_reloption(bits32 kinds, const char *name, const char *desc,
+ ternary default_val, LOCKMODE lockmode)
+{
+ relopt_ternary *newoption;
+
+ newoption = (relopt_ternary *) allocate_reloption(kinds,
+ RELOPT_TYPE_TERNARY, name, desc, lockmode);
+ newoption->default_val = default_val;
+
+ return newoption;
+}
+
+/*
+ * add_ternary_reloption
+ * Add a new ternary reloption
+ */
+void
+add_ternary_reloption(bits32 kinds, const char *name, const char *desc,
+ ternary default_val, LOCKMODE lockmode)
+{
+ relopt_ternary *newoption = init_ternary_reloption(kinds, name, desc,
+ default_val, lockmode);
+
+ add_reloption((relopt_gen *) newoption);
+}
+
+/*
+ * add_local_ternary_reloption
+ * Add a new ternary local reloption
+ *
+ * 'offset' is offset of ternary-typed field.
+ */
+void
+add_local_ternary_reloption(local_relopts *relopts, const char *name,
+ const char *desc, ternary default_val,
+ int offset)
+{
+ relopt_ternary *newoption = init_ternary_reloption(RELOPT_KIND_LOCAL,
+ name, desc,
+ default_val, 0);
+
+ add_local_reloption(relopts, (relopt_gen *) newoption, offset);
+}
/*
* init_real_reloption
@@ -1617,6 +1689,18 @@ parse_one_reloption(relopt_value *option, char *text_str, int text_len,
option->gen->name, value)));
}
break;
+ case RELOPT_TYPE_TERNARY:
+ {
+ bool b;
+ parsed = parse_bool(value, &b);
+ option->values.ternary_val = b ? TERNARY_TRUE : TERNARY_FALSE;
+ if (validate && !parsed)
+ ereport(ERROR,
+ (errcode(ERRCODE_INVALID_PARAMETER_VALUE),
+ errmsg("invalid value for ternary option \"%s\": %s",
+ option->gen->name, value)));
+ }
+ break;
case RELOPT_TYPE_INT:
{
relopt_int *optint = (relopt_int *) option->gen;
@@ -1780,17 +1864,6 @@ fillRelOptions(void *rdopts, Size basesize,
char *itempos = ((char *) rdopts) + elems[j].offset;
char *string_val;
- /*
- * If isset_offset is provided, store whether the reloption is
- * set there.
- */
- if (elems[j].isset_offset > 0)
- {
- char *setpos = ((char *) rdopts) + elems[j].isset_offset;
-
- *(bool *) setpos = options[i].isset;
- }
-
switch (options[i].gen->type)
{
case RELOPT_TYPE_BOOL:
@@ -1798,6 +1871,11 @@ fillRelOptions(void *rdopts, Size basesize,
options[i].values.bool_val :
((relopt_bool *) options[i].gen)->default_val;
break;
+ case RELOPT_TYPE_TERNARY:
+ *(ternary *) itempos = options[i].isset ?
+ options[i].values.ternary_val :
+ ((relopt_ternary *) options[i].gen)->default_val;
+ break;
case RELOPT_TYPE_INT:
*(int *) itempos = options[i].isset ?
options[i].values.int_val :
@@ -1912,8 +1990,8 @@ default_reloptions(Datum reloptions, bool validate, relopt_kind kind)
offsetof(StdRdOptions, parallel_workers)},
{"vacuum_index_cleanup", RELOPT_TYPE_ENUM,
offsetof(StdRdOptions, vacuum_index_cleanup)},
- {"vacuum_truncate", RELOPT_TYPE_BOOL,
- offsetof(StdRdOptions, vacuum_truncate), offsetof(StdRdOptions, vacuum_truncate_set)},
+ {"vacuum_truncate", RELOPT_TYPE_TERNARY,
+ offsetof(StdRdOptions, vacuum_truncate)},
{"vacuum_max_eager_freeze_failure_rate", RELOPT_TYPE_REAL,
offsetof(StdRdOptions, vacuum_max_eager_freeze_failure_rate)}
};
@@ -1993,7 +2071,6 @@ build_local_reloptions(local_relopts *relopts, Datum options, bool validate)
elems[i].optname = opt->option->name;
elems[i].opttype = opt->option->type;
elems[i].offset = opt->offset;
- elems[i].isset_offset = 0; /* not supported for local relopts yet */
i++;
}
diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c
index 733ef40ae7..977babff54 100644
--- a/src/backend/commands/vacuum.c
+++ b/src/backend/commands/vacuum.c
@@ -2221,9 +2221,9 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams params,
{
StdRdOptions *opts = (StdRdOptions *) rel->rd_options;
- if (opts && opts->vacuum_truncate_set)
+ if (opts && opts->vacuum_truncate != TERNARY_UNSET)
{
- if (opts->vacuum_truncate)
+ if (opts->vacuum_truncate == TERNARY_TRUE)
params.truncate = VACOPTVALUE_ENABLED;
else
params.truncate = VACOPTVALUE_DISABLED;
diff --git a/src/include/access/reloptions.h b/src/include/access/reloptions.h
index a604a4702c..a436697658 100644
--- a/src/include/access/reloptions.h
+++ b/src/include/access/reloptions.h
@@ -29,6 +29,7 @@
typedef enum relopt_type
{
RELOPT_TYPE_BOOL,
+ RELOPT_TYPE_TERNARY, /* on, off, unset */
RELOPT_TYPE_INT,
RELOPT_TYPE_REAL,
RELOPT_TYPE_ENUM,
@@ -80,6 +81,7 @@ typedef struct relopt_value
union
{
bool bool_val;
+ ternary ternary_val;
int int_val;
double real_val;
int enum_val;
@@ -94,6 +96,12 @@ typedef struct relopt_bool
bool default_val;
} relopt_bool;
+typedef struct relopt_rernary
+{
+ relopt_gen gen;
+ int default_val;
+} relopt_ternary;
+
typedef struct relopt_int
{
relopt_gen gen;
@@ -152,19 +160,6 @@ typedef struct
const char *optname; /* option's name */
relopt_type opttype; /* option's datatype */
int offset; /* offset of field in result struct */
-
- /*
- * isset_offset is an optional offset of a field in the result struct that
- * stores whether the option is explicitly set for the relation or if it
- * just picked up the default value. In most cases, this can be
- * accomplished by giving the reloption a special out-of-range default
- * value (e.g., some integer reloptions use -2), but this isn't always
- * possible. For example, a Boolean reloption cannot be given an
- * out-of-range default, so we need another way to discover the source of
- * its value. This offset is only used if given a value greater than
- * zero.
- */
- int isset_offset;
} relopt_parse_elt;
/* Local reloption definition */
@@ -195,6 +190,8 @@ typedef struct local_relopts
extern relopt_kind add_reloption_kind(void);
extern void add_bool_reloption(bits32 kinds, const char *name, const char *desc,
bool default_val, LOCKMODE lockmode);
+extern void add_ternary_reloption(bits32 kinds, const char *name,
+ const char *desc, int default_val, LOCKMODE lockmode);
extern void add_int_reloption(bits32 kinds, const char *name, const char *desc,
int default_val, int min_val, int max_val,
LOCKMODE lockmode);
@@ -214,6 +211,9 @@ extern void register_reloptions_validator(local_relopts *relopts,
extern void add_local_bool_reloption(local_relopts *relopts, const char *name,
const char *desc, bool default_val,
int offset);
+extern void add_local_ternary_reloption(local_relopts *relopts,
+ const char *name, const char *desc,
+ ternary default_val, int offset);
extern void add_local_int_reloption(local_relopts *relopts, const char *name,
const char *desc, int default_val,
int min_val, int max_val, int offset);
diff --git a/src/include/c.h b/src/include/c.h
index 39022f8a9d..9c59ccccf8 100644
--- a/src/include/c.h
+++ b/src/include/c.h
@@ -488,6 +488,22 @@ typedef void (*pg_funcptr_t) (void);
#include <stdbool.h>
+/*
+ * ternary
+ * Boolean value with an extrea "unset" option
+ *
+ * Ternary data type is used in relation options that can be "true", "false" or
+ * "unset". Since relation options are used deep inside the PostgreSQL code,
+ * this type is declared globally.
+*/
+
+typedef enum ternary
+{
+ TERNARY_FALSE = 0,
+ TERNARY_TRUE = 1,
+ TERNARY_UNSET = -1
+} ternary;
+
/* ----------------------------------------------------------------
* Section 3: standard system types
diff --git a/src/include/utils/rel.h b/src/include/utils/rel.h
index b552359915..7346d618f9 100644
--- a/src/include/utils/rel.h
+++ b/src/include/utils/rel.h
@@ -346,8 +346,7 @@ typedef struct StdRdOptions
bool user_catalog_table; /* use as an additional catalog relation */
int parallel_workers; /* max number of parallel workers */
StdRdOptIndexCleanup vacuum_index_cleanup; /* controls index vacuuming */
- bool vacuum_truncate; /* enables vacuum to truncate a relation */
- bool vacuum_truncate_set; /* whether vacuum_truncate is set */
+ ternary vacuum_truncate; /* enables vacuum to truncate a relation */
/*
* Fraction of pages in a relation that vacuum can eagerly scan and fail
--
2.39.2
--nextPart4155600.3ZeAukHxDK
Content-Disposition: attachment;
filename="v2a-0003-Add-alias-to-be-used-as-unset-state.patch"
Content-Transfer-Encoding: 7Bit
Content-Type: text/x-patch; charset="unicode-2-0-utf-8";
name="v2a-0003-Add-alias-to-be-used-as-unset-state.patch"
^ permalink raw reply [nested|flat] 868+ messages in thread
* [PATCH v2 2/4] Introduce ternary reloptions
@ 2025-09-04 14:41 Nikolay Shaplov <[email protected]>
0 siblings, 0 replies; 868+ messages in thread
From: Nikolay Shaplov @ 2025-09-04 14:41 UTC (permalink / raw)
Introduce ternary reloption as a replacement for current `vacuum_truncate`
implementation. Remove `vacuum_truncate_set` additional flag and using
`TERNARY_UNSET` value instead.
---
src/backend/access/common/reloptions.c | 129 ++++++++++++++++++++-----
src/backend/commands/vacuum.c | 4 +-
src/include/access/reloptions.h | 26 ++---
src/include/c.h | 16 +++
src/include/utils/rel.h | 3 +-
5 files changed, 135 insertions(+), 43 deletions(-)
diff --git a/src/backend/access/common/reloptions.c b/src/backend/access/common/reloptions.c
index 0af3fea68f..f543f11001 100644
--- a/src/backend/access/common/reloptions.c
+++ b/src/backend/access/common/reloptions.c
@@ -40,9 +40,9 @@
*
* To add an option:
*
- * (i) decide on a type (bool, integer, real, enum, string), name, default
- * value, upper and lower bounds (if applicable); for strings, consider a
- * validation routine.
+ * (i) decide on a type (bool, ternary, integer, real, enum, string), name,
+ * default value, upper and lower bounds (if applicable); for strings,
+ * consider a validation routine.
* (ii) add a record below (or use add_<type>_reloption).
* (iii) add it to the appropriate options struct (perhaps StdRdOptions)
* (iv) add it to the appropriate handling routine (perhaps
@@ -147,15 +147,6 @@ static relopt_bool boolRelOpts[] =
},
false
},
- {
- {
- "vacuum_truncate",
- "Enables vacuum to truncate empty pages at the end of this table",
- RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
- ShareUpdateExclusiveLock
- },
- true
- },
{
{
"deduplicate_items",
@@ -170,6 +161,21 @@ static relopt_bool boolRelOpts[] =
{{NULL}}
};
+static relopt_ternary ternaryRelOpts[] =
+{
+ {
+ {
+ "vacuum_truncate",
+ "Enables vacuum to truncate empty pages at the end of this table",
+ RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
+ ShareUpdateExclusiveLock
+ },
+ TERNARY_UNSET
+ },
+ /* list terminator */
+ {{NULL}}
+};
+
static relopt_int intRelOpts[] =
{
{
@@ -600,6 +606,13 @@ initialize_reloptions(void)
boolRelOpts[i].gen.lockmode));
j++;
}
+ for (i = 0; ternaryRelOpts[i].gen.name; i++)
+ {
+ Assert(DoLockModesConflict(ternaryRelOpts[i].gen.lockmode,
+ ternaryRelOpts[i].gen.lockmode));
+ j++;
+ }
+
for (i = 0; intRelOpts[i].gen.name; i++)
{
Assert(DoLockModesConflict(intRelOpts[i].gen.lockmode,
@@ -640,6 +653,14 @@ initialize_reloptions(void)
j++;
}
+ for (i = 0; ternaryRelOpts[i].gen.name; i++)
+ {
+ relOpts[j] = &ternaryRelOpts[i].gen;
+ relOpts[j]->type = RELOPT_TYPE_TERNARY;
+ relOpts[j]->namelen = strlen(relOpts[j]->name);
+ j++;
+ }
+
for (i = 0; intRelOpts[i].gen.name; i++)
{
relOpts[j] = &intRelOpts[i].gen;
@@ -800,6 +821,9 @@ allocate_reloption(bits32 kinds, int type, const char *name, const char *desc,
case RELOPT_TYPE_BOOL:
size = sizeof(relopt_bool);
break;
+ case RELOPT_TYPE_TERNARY:
+ size = sizeof(relopt_ternary);
+ break;
case RELOPT_TYPE_INT:
size = sizeof(relopt_int);
break;
@@ -883,6 +907,54 @@ add_local_bool_reloption(local_relopts *relopts, const char *name,
add_local_reloption(relopts, (relopt_gen *) newoption, offset);
}
+/*
+ * init_ternary_reloption
+ * Allocate and initialize a new ternary reloption
+ */
+static relopt_ternary *
+init_ternary_reloption(bits32 kinds, const char *name, const char *desc,
+ ternary default_val, LOCKMODE lockmode)
+{
+ relopt_ternary *newoption;
+
+ newoption = (relopt_ternary *) allocate_reloption(kinds,
+ RELOPT_TYPE_TERNARY, name, desc, lockmode);
+ newoption->default_val = default_val;
+
+ return newoption;
+}
+
+/*
+ * add_ternary_reloption
+ * Add a new ternary reloption
+ */
+void
+add_ternary_reloption(bits32 kinds, const char *name, const char *desc,
+ ternary default_val, LOCKMODE lockmode)
+{
+ relopt_ternary *newoption = init_ternary_reloption(kinds, name, desc,
+ default_val, lockmode);
+
+ add_reloption((relopt_gen *) newoption);
+}
+
+/*
+ * add_local_ternary_reloption
+ * Add a new ternary local reloption
+ *
+ * 'offset' is offset of ternary-typed field.
+ */
+void
+add_local_ternary_reloption(local_relopts *relopts, const char *name,
+ const char *desc, ternary default_val,
+ int offset)
+{
+ relopt_ternary *newoption = init_ternary_reloption(RELOPT_KIND_LOCAL,
+ name, desc,
+ default_val, 0);
+
+ add_local_reloption(relopts, (relopt_gen *) newoption, offset);
+}
/*
* init_real_reloption
@@ -1617,6 +1689,18 @@ parse_one_reloption(relopt_value *option, char *text_str, int text_len,
option->gen->name, value)));
}
break;
+ case RELOPT_TYPE_TERNARY:
+ {
+ bool b;
+ parsed = parse_bool(value, &b);
+ option->values.ternary_val = b ? TERNARY_TRUE : TERNARY_FALSE;
+ if (validate && !parsed)
+ ereport(ERROR,
+ (errcode(ERRCODE_INVALID_PARAMETER_VALUE),
+ errmsg("invalid value for ternary option \"%s\": %s",
+ option->gen->name, value)));
+ }
+ break;
case RELOPT_TYPE_INT:
{
relopt_int *optint = (relopt_int *) option->gen;
@@ -1780,17 +1864,6 @@ fillRelOptions(void *rdopts, Size basesize,
char *itempos = ((char *) rdopts) + elems[j].offset;
char *string_val;
- /*
- * If isset_offset is provided, store whether the reloption is
- * set there.
- */
- if (elems[j].isset_offset > 0)
- {
- char *setpos = ((char *) rdopts) + elems[j].isset_offset;
-
- *(bool *) setpos = options[i].isset;
- }
-
switch (options[i].gen->type)
{
case RELOPT_TYPE_BOOL:
@@ -1798,6 +1871,11 @@ fillRelOptions(void *rdopts, Size basesize,
options[i].values.bool_val :
((relopt_bool *) options[i].gen)->default_val;
break;
+ case RELOPT_TYPE_TERNARY:
+ *(ternary *) itempos = options[i].isset ?
+ options[i].values.ternary_val :
+ ((relopt_ternary *) options[i].gen)->default_val;
+ break;
case RELOPT_TYPE_INT:
*(int *) itempos = options[i].isset ?
options[i].values.int_val :
@@ -1912,8 +1990,8 @@ default_reloptions(Datum reloptions, bool validate, relopt_kind kind)
offsetof(StdRdOptions, parallel_workers)},
{"vacuum_index_cleanup", RELOPT_TYPE_ENUM,
offsetof(StdRdOptions, vacuum_index_cleanup)},
- {"vacuum_truncate", RELOPT_TYPE_BOOL,
- offsetof(StdRdOptions, vacuum_truncate), offsetof(StdRdOptions, vacuum_truncate_set)},
+ {"vacuum_truncate", RELOPT_TYPE_TERNARY,
+ offsetof(StdRdOptions, vacuum_truncate)},
{"vacuum_max_eager_freeze_failure_rate", RELOPT_TYPE_REAL,
offsetof(StdRdOptions, vacuum_max_eager_freeze_failure_rate)}
};
@@ -1993,7 +2071,6 @@ build_local_reloptions(local_relopts *relopts, Datum options, bool validate)
elems[i].optname = opt->option->name;
elems[i].opttype = opt->option->type;
elems[i].offset = opt->offset;
- elems[i].isset_offset = 0; /* not supported for local relopts yet */
i++;
}
diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c
index 733ef40ae7..977babff54 100644
--- a/src/backend/commands/vacuum.c
+++ b/src/backend/commands/vacuum.c
@@ -2221,9 +2221,9 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams params,
{
StdRdOptions *opts = (StdRdOptions *) rel->rd_options;
- if (opts && opts->vacuum_truncate_set)
+ if (opts && opts->vacuum_truncate != TERNARY_UNSET)
{
- if (opts->vacuum_truncate)
+ if (opts->vacuum_truncate == TERNARY_TRUE)
params.truncate = VACOPTVALUE_ENABLED;
else
params.truncate = VACOPTVALUE_DISABLED;
diff --git a/src/include/access/reloptions.h b/src/include/access/reloptions.h
index a604a4702c..a436697658 100644
--- a/src/include/access/reloptions.h
+++ b/src/include/access/reloptions.h
@@ -29,6 +29,7 @@
typedef enum relopt_type
{
RELOPT_TYPE_BOOL,
+ RELOPT_TYPE_TERNARY, /* on, off, unset */
RELOPT_TYPE_INT,
RELOPT_TYPE_REAL,
RELOPT_TYPE_ENUM,
@@ -80,6 +81,7 @@ typedef struct relopt_value
union
{
bool bool_val;
+ ternary ternary_val;
int int_val;
double real_val;
int enum_val;
@@ -94,6 +96,12 @@ typedef struct relopt_bool
bool default_val;
} relopt_bool;
+typedef struct relopt_rernary
+{
+ relopt_gen gen;
+ int default_val;
+} relopt_ternary;
+
typedef struct relopt_int
{
relopt_gen gen;
@@ -152,19 +160,6 @@ typedef struct
const char *optname; /* option's name */
relopt_type opttype; /* option's datatype */
int offset; /* offset of field in result struct */
-
- /*
- * isset_offset is an optional offset of a field in the result struct that
- * stores whether the option is explicitly set for the relation or if it
- * just picked up the default value. In most cases, this can be
- * accomplished by giving the reloption a special out-of-range default
- * value (e.g., some integer reloptions use -2), but this isn't always
- * possible. For example, a Boolean reloption cannot be given an
- * out-of-range default, so we need another way to discover the source of
- * its value. This offset is only used if given a value greater than
- * zero.
- */
- int isset_offset;
} relopt_parse_elt;
/* Local reloption definition */
@@ -195,6 +190,8 @@ typedef struct local_relopts
extern relopt_kind add_reloption_kind(void);
extern void add_bool_reloption(bits32 kinds, const char *name, const char *desc,
bool default_val, LOCKMODE lockmode);
+extern void add_ternary_reloption(bits32 kinds, const char *name,
+ const char *desc, int default_val, LOCKMODE lockmode);
extern void add_int_reloption(bits32 kinds, const char *name, const char *desc,
int default_val, int min_val, int max_val,
LOCKMODE lockmode);
@@ -214,6 +211,9 @@ extern void register_reloptions_validator(local_relopts *relopts,
extern void add_local_bool_reloption(local_relopts *relopts, const char *name,
const char *desc, bool default_val,
int offset);
+extern void add_local_ternary_reloption(local_relopts *relopts,
+ const char *name, const char *desc,
+ ternary default_val, int offset);
extern void add_local_int_reloption(local_relopts *relopts, const char *name,
const char *desc, int default_val,
int min_val, int max_val, int offset);
diff --git a/src/include/c.h b/src/include/c.h
index 39022f8a9d..9c59ccccf8 100644
--- a/src/include/c.h
+++ b/src/include/c.h
@@ -488,6 +488,22 @@ typedef void (*pg_funcptr_t) (void);
#include <stdbool.h>
+/*
+ * ternary
+ * Boolean value with an extrea "unset" option
+ *
+ * Ternary data type is used in relation options that can be "true", "false" or
+ * "unset". Since relation options are used deep inside the PostgreSQL code,
+ * this type is declared globally.
+*/
+
+typedef enum ternary
+{
+ TERNARY_FALSE = 0,
+ TERNARY_TRUE = 1,
+ TERNARY_UNSET = -1
+} ternary;
+
/* ----------------------------------------------------------------
* Section 3: standard system types
diff --git a/src/include/utils/rel.h b/src/include/utils/rel.h
index b552359915..7346d618f9 100644
--- a/src/include/utils/rel.h
+++ b/src/include/utils/rel.h
@@ -346,8 +346,7 @@ typedef struct StdRdOptions
bool user_catalog_table; /* use as an additional catalog relation */
int parallel_workers; /* max number of parallel workers */
StdRdOptIndexCleanup vacuum_index_cleanup; /* controls index vacuuming */
- bool vacuum_truncate; /* enables vacuum to truncate a relation */
- bool vacuum_truncate_set; /* whether vacuum_truncate is set */
+ ternary vacuum_truncate; /* enables vacuum to truncate a relation */
/*
* Fraction of pages in a relation that vacuum can eagerly scan and fail
--
2.39.2
--nextPart5129932.5fSG56mABF
Content-Disposition: attachment;
filename="v2-0003-Add-alias-to-be-used-as-unset-state.patch"
Content-Transfer-Encoding: 7Bit
Content-Type: text/x-patch; charset="unicode-2-0-utf-8";
name="v2-0003-Add-alias-to-be-used-as-unset-state.patch"
^ permalink raw reply [nested|flat] 868+ messages in thread
* [PATCH v2 2/4] Introduce ternary reloptions
@ 2025-09-04 14:41 Nikolay Shaplov <[email protected]>
0 siblings, 0 replies; 868+ messages in thread
From: Nikolay Shaplov @ 2025-09-04 14:41 UTC (permalink / raw)
Introduce ternary reloption as a replacement for current `vacuum_truncate`
implementation. Remove `vacuum_truncate_set` additional flag and using
`TERNARY_UNSET` value instead.
---
src/backend/access/common/reloptions.c | 129 ++++++++++++++++++++-----
src/backend/commands/vacuum.c | 4 +-
src/include/access/reloptions.h | 26 ++---
src/include/c.h | 16 +++
src/include/utils/rel.h | 3 +-
5 files changed, 135 insertions(+), 43 deletions(-)
diff --git a/src/backend/access/common/reloptions.c b/src/backend/access/common/reloptions.c
index 0af3fea68f..f543f11001 100644
--- a/src/backend/access/common/reloptions.c
+++ b/src/backend/access/common/reloptions.c
@@ -40,9 +40,9 @@
*
* To add an option:
*
- * (i) decide on a type (bool, integer, real, enum, string), name, default
- * value, upper and lower bounds (if applicable); for strings, consider a
- * validation routine.
+ * (i) decide on a type (bool, ternary, integer, real, enum, string), name,
+ * default value, upper and lower bounds (if applicable); for strings,
+ * consider a validation routine.
* (ii) add a record below (or use add_<type>_reloption).
* (iii) add it to the appropriate options struct (perhaps StdRdOptions)
* (iv) add it to the appropriate handling routine (perhaps
@@ -147,15 +147,6 @@ static relopt_bool boolRelOpts[] =
},
false
},
- {
- {
- "vacuum_truncate",
- "Enables vacuum to truncate empty pages at the end of this table",
- RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
- ShareUpdateExclusiveLock
- },
- true
- },
{
{
"deduplicate_items",
@@ -170,6 +161,21 @@ static relopt_bool boolRelOpts[] =
{{NULL}}
};
+static relopt_ternary ternaryRelOpts[] =
+{
+ {
+ {
+ "vacuum_truncate",
+ "Enables vacuum to truncate empty pages at the end of this table",
+ RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
+ ShareUpdateExclusiveLock
+ },
+ TERNARY_UNSET
+ },
+ /* list terminator */
+ {{NULL}}
+};
+
static relopt_int intRelOpts[] =
{
{
@@ -600,6 +606,13 @@ initialize_reloptions(void)
boolRelOpts[i].gen.lockmode));
j++;
}
+ for (i = 0; ternaryRelOpts[i].gen.name; i++)
+ {
+ Assert(DoLockModesConflict(ternaryRelOpts[i].gen.lockmode,
+ ternaryRelOpts[i].gen.lockmode));
+ j++;
+ }
+
for (i = 0; intRelOpts[i].gen.name; i++)
{
Assert(DoLockModesConflict(intRelOpts[i].gen.lockmode,
@@ -640,6 +653,14 @@ initialize_reloptions(void)
j++;
}
+ for (i = 0; ternaryRelOpts[i].gen.name; i++)
+ {
+ relOpts[j] = &ternaryRelOpts[i].gen;
+ relOpts[j]->type = RELOPT_TYPE_TERNARY;
+ relOpts[j]->namelen = strlen(relOpts[j]->name);
+ j++;
+ }
+
for (i = 0; intRelOpts[i].gen.name; i++)
{
relOpts[j] = &intRelOpts[i].gen;
@@ -800,6 +821,9 @@ allocate_reloption(bits32 kinds, int type, const char *name, const char *desc,
case RELOPT_TYPE_BOOL:
size = sizeof(relopt_bool);
break;
+ case RELOPT_TYPE_TERNARY:
+ size = sizeof(relopt_ternary);
+ break;
case RELOPT_TYPE_INT:
size = sizeof(relopt_int);
break;
@@ -883,6 +907,54 @@ add_local_bool_reloption(local_relopts *relopts, const char *name,
add_local_reloption(relopts, (relopt_gen *) newoption, offset);
}
+/*
+ * init_ternary_reloption
+ * Allocate and initialize a new ternary reloption
+ */
+static relopt_ternary *
+init_ternary_reloption(bits32 kinds, const char *name, const char *desc,
+ ternary default_val, LOCKMODE lockmode)
+{
+ relopt_ternary *newoption;
+
+ newoption = (relopt_ternary *) allocate_reloption(kinds,
+ RELOPT_TYPE_TERNARY, name, desc, lockmode);
+ newoption->default_val = default_val;
+
+ return newoption;
+}
+
+/*
+ * add_ternary_reloption
+ * Add a new ternary reloption
+ */
+void
+add_ternary_reloption(bits32 kinds, const char *name, const char *desc,
+ ternary default_val, LOCKMODE lockmode)
+{
+ relopt_ternary *newoption = init_ternary_reloption(kinds, name, desc,
+ default_val, lockmode);
+
+ add_reloption((relopt_gen *) newoption);
+}
+
+/*
+ * add_local_ternary_reloption
+ * Add a new ternary local reloption
+ *
+ * 'offset' is offset of ternary-typed field.
+ */
+void
+add_local_ternary_reloption(local_relopts *relopts, const char *name,
+ const char *desc, ternary default_val,
+ int offset)
+{
+ relopt_ternary *newoption = init_ternary_reloption(RELOPT_KIND_LOCAL,
+ name, desc,
+ default_val, 0);
+
+ add_local_reloption(relopts, (relopt_gen *) newoption, offset);
+}
/*
* init_real_reloption
@@ -1617,6 +1689,18 @@ parse_one_reloption(relopt_value *option, char *text_str, int text_len,
option->gen->name, value)));
}
break;
+ case RELOPT_TYPE_TERNARY:
+ {
+ bool b;
+ parsed = parse_bool(value, &b);
+ option->values.ternary_val = b ? TERNARY_TRUE : TERNARY_FALSE;
+ if (validate && !parsed)
+ ereport(ERROR,
+ (errcode(ERRCODE_INVALID_PARAMETER_VALUE),
+ errmsg("invalid value for ternary option \"%s\": %s",
+ option->gen->name, value)));
+ }
+ break;
case RELOPT_TYPE_INT:
{
relopt_int *optint = (relopt_int *) option->gen;
@@ -1780,17 +1864,6 @@ fillRelOptions(void *rdopts, Size basesize,
char *itempos = ((char *) rdopts) + elems[j].offset;
char *string_val;
- /*
- * If isset_offset is provided, store whether the reloption is
- * set there.
- */
- if (elems[j].isset_offset > 0)
- {
- char *setpos = ((char *) rdopts) + elems[j].isset_offset;
-
- *(bool *) setpos = options[i].isset;
- }
-
switch (options[i].gen->type)
{
case RELOPT_TYPE_BOOL:
@@ -1798,6 +1871,11 @@ fillRelOptions(void *rdopts, Size basesize,
options[i].values.bool_val :
((relopt_bool *) options[i].gen)->default_val;
break;
+ case RELOPT_TYPE_TERNARY:
+ *(ternary *) itempos = options[i].isset ?
+ options[i].values.ternary_val :
+ ((relopt_ternary *) options[i].gen)->default_val;
+ break;
case RELOPT_TYPE_INT:
*(int *) itempos = options[i].isset ?
options[i].values.int_val :
@@ -1912,8 +1990,8 @@ default_reloptions(Datum reloptions, bool validate, relopt_kind kind)
offsetof(StdRdOptions, parallel_workers)},
{"vacuum_index_cleanup", RELOPT_TYPE_ENUM,
offsetof(StdRdOptions, vacuum_index_cleanup)},
- {"vacuum_truncate", RELOPT_TYPE_BOOL,
- offsetof(StdRdOptions, vacuum_truncate), offsetof(StdRdOptions, vacuum_truncate_set)},
+ {"vacuum_truncate", RELOPT_TYPE_TERNARY,
+ offsetof(StdRdOptions, vacuum_truncate)},
{"vacuum_max_eager_freeze_failure_rate", RELOPT_TYPE_REAL,
offsetof(StdRdOptions, vacuum_max_eager_freeze_failure_rate)}
};
@@ -1993,7 +2071,6 @@ build_local_reloptions(local_relopts *relopts, Datum options, bool validate)
elems[i].optname = opt->option->name;
elems[i].opttype = opt->option->type;
elems[i].offset = opt->offset;
- elems[i].isset_offset = 0; /* not supported for local relopts yet */
i++;
}
diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c
index 733ef40ae7..977babff54 100644
--- a/src/backend/commands/vacuum.c
+++ b/src/backend/commands/vacuum.c
@@ -2221,9 +2221,9 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams params,
{
StdRdOptions *opts = (StdRdOptions *) rel->rd_options;
- if (opts && opts->vacuum_truncate_set)
+ if (opts && opts->vacuum_truncate != TERNARY_UNSET)
{
- if (opts->vacuum_truncate)
+ if (opts->vacuum_truncate == TERNARY_TRUE)
params.truncate = VACOPTVALUE_ENABLED;
else
params.truncate = VACOPTVALUE_DISABLED;
diff --git a/src/include/access/reloptions.h b/src/include/access/reloptions.h
index a604a4702c..a436697658 100644
--- a/src/include/access/reloptions.h
+++ b/src/include/access/reloptions.h
@@ -29,6 +29,7 @@
typedef enum relopt_type
{
RELOPT_TYPE_BOOL,
+ RELOPT_TYPE_TERNARY, /* on, off, unset */
RELOPT_TYPE_INT,
RELOPT_TYPE_REAL,
RELOPT_TYPE_ENUM,
@@ -80,6 +81,7 @@ typedef struct relopt_value
union
{
bool bool_val;
+ ternary ternary_val;
int int_val;
double real_val;
int enum_val;
@@ -94,6 +96,12 @@ typedef struct relopt_bool
bool default_val;
} relopt_bool;
+typedef struct relopt_rernary
+{
+ relopt_gen gen;
+ int default_val;
+} relopt_ternary;
+
typedef struct relopt_int
{
relopt_gen gen;
@@ -152,19 +160,6 @@ typedef struct
const char *optname; /* option's name */
relopt_type opttype; /* option's datatype */
int offset; /* offset of field in result struct */
-
- /*
- * isset_offset is an optional offset of a field in the result struct that
- * stores whether the option is explicitly set for the relation or if it
- * just picked up the default value. In most cases, this can be
- * accomplished by giving the reloption a special out-of-range default
- * value (e.g., some integer reloptions use -2), but this isn't always
- * possible. For example, a Boolean reloption cannot be given an
- * out-of-range default, so we need another way to discover the source of
- * its value. This offset is only used if given a value greater than
- * zero.
- */
- int isset_offset;
} relopt_parse_elt;
/* Local reloption definition */
@@ -195,6 +190,8 @@ typedef struct local_relopts
extern relopt_kind add_reloption_kind(void);
extern void add_bool_reloption(bits32 kinds, const char *name, const char *desc,
bool default_val, LOCKMODE lockmode);
+extern void add_ternary_reloption(bits32 kinds, const char *name,
+ const char *desc, int default_val, LOCKMODE lockmode);
extern void add_int_reloption(bits32 kinds, const char *name, const char *desc,
int default_val, int min_val, int max_val,
LOCKMODE lockmode);
@@ -214,6 +211,9 @@ extern void register_reloptions_validator(local_relopts *relopts,
extern void add_local_bool_reloption(local_relopts *relopts, const char *name,
const char *desc, bool default_val,
int offset);
+extern void add_local_ternary_reloption(local_relopts *relopts,
+ const char *name, const char *desc,
+ ternary default_val, int offset);
extern void add_local_int_reloption(local_relopts *relopts, const char *name,
const char *desc, int default_val,
int min_val, int max_val, int offset);
diff --git a/src/include/c.h b/src/include/c.h
index 39022f8a9d..9c59ccccf8 100644
--- a/src/include/c.h
+++ b/src/include/c.h
@@ -488,6 +488,22 @@ typedef void (*pg_funcptr_t) (void);
#include <stdbool.h>
+/*
+ * ternary
+ * Boolean value with an extrea "unset" option
+ *
+ * Ternary data type is used in relation options that can be "true", "false" or
+ * "unset". Since relation options are used deep inside the PostgreSQL code,
+ * this type is declared globally.
+*/
+
+typedef enum ternary
+{
+ TERNARY_FALSE = 0,
+ TERNARY_TRUE = 1,
+ TERNARY_UNSET = -1
+} ternary;
+
/* ----------------------------------------------------------------
* Section 3: standard system types
diff --git a/src/include/utils/rel.h b/src/include/utils/rel.h
index b552359915..7346d618f9 100644
--- a/src/include/utils/rel.h
+++ b/src/include/utils/rel.h
@@ -346,8 +346,7 @@ typedef struct StdRdOptions
bool user_catalog_table; /* use as an additional catalog relation */
int parallel_workers; /* max number of parallel workers */
StdRdOptIndexCleanup vacuum_index_cleanup; /* controls index vacuuming */
- bool vacuum_truncate; /* enables vacuum to truncate a relation */
- bool vacuum_truncate_set; /* whether vacuum_truncate is set */
+ ternary vacuum_truncate; /* enables vacuum to truncate a relation */
/*
* Fraction of pages in a relation that vacuum can eagerly scan and fail
--
2.39.2
--nextPart5129932.5fSG56mABF
Content-Disposition: attachment;
filename="v2-0003-Add-alias-to-be-used-as-unset-state.patch"
Content-Transfer-Encoding: 7Bit
Content-Type: text/x-patch; charset="unicode-2-0-utf-8";
name="v2-0003-Add-alias-to-be-used-as-unset-state.patch"
^ permalink raw reply [nested|flat] 868+ messages in thread
* [PATCH v2a 2/4] Introduce ternary reloptions
@ 2025-09-04 14:41 Nikolay Shaplov <[email protected]>
0 siblings, 0 replies; 868+ messages in thread
From: Nikolay Shaplov @ 2025-09-04 14:41 UTC (permalink / raw)
Introduce ternary reloption as a replacement for current `vacuum_truncate`
implementation. Remove `vacuum_truncate_set` additional flag and using
`TERNARY_UNSET` value instead.
---
src/backend/access/common/reloptions.c | 129 ++++++++++++++++++++-----
src/backend/commands/vacuum.c | 4 +-
src/include/access/reloptions.h | 26 ++---
src/include/c.h | 16 +++
src/include/utils/rel.h | 3 +-
5 files changed, 135 insertions(+), 43 deletions(-)
diff --git a/src/backend/access/common/reloptions.c b/src/backend/access/common/reloptions.c
index 0af3fea68f..f543f11001 100644
--- a/src/backend/access/common/reloptions.c
+++ b/src/backend/access/common/reloptions.c
@@ -40,9 +40,9 @@
*
* To add an option:
*
- * (i) decide on a type (bool, integer, real, enum, string), name, default
- * value, upper and lower bounds (if applicable); for strings, consider a
- * validation routine.
+ * (i) decide on a type (bool, ternary, integer, real, enum, string), name,
+ * default value, upper and lower bounds (if applicable); for strings,
+ * consider a validation routine.
* (ii) add a record below (or use add_<type>_reloption).
* (iii) add it to the appropriate options struct (perhaps StdRdOptions)
* (iv) add it to the appropriate handling routine (perhaps
@@ -147,15 +147,6 @@ static relopt_bool boolRelOpts[] =
},
false
},
- {
- {
- "vacuum_truncate",
- "Enables vacuum to truncate empty pages at the end of this table",
- RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
- ShareUpdateExclusiveLock
- },
- true
- },
{
{
"deduplicate_items",
@@ -170,6 +161,21 @@ static relopt_bool boolRelOpts[] =
{{NULL}}
};
+static relopt_ternary ternaryRelOpts[] =
+{
+ {
+ {
+ "vacuum_truncate",
+ "Enables vacuum to truncate empty pages at the end of this table",
+ RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
+ ShareUpdateExclusiveLock
+ },
+ TERNARY_UNSET
+ },
+ /* list terminator */
+ {{NULL}}
+};
+
static relopt_int intRelOpts[] =
{
{
@@ -600,6 +606,13 @@ initialize_reloptions(void)
boolRelOpts[i].gen.lockmode));
j++;
}
+ for (i = 0; ternaryRelOpts[i].gen.name; i++)
+ {
+ Assert(DoLockModesConflict(ternaryRelOpts[i].gen.lockmode,
+ ternaryRelOpts[i].gen.lockmode));
+ j++;
+ }
+
for (i = 0; intRelOpts[i].gen.name; i++)
{
Assert(DoLockModesConflict(intRelOpts[i].gen.lockmode,
@@ -640,6 +653,14 @@ initialize_reloptions(void)
j++;
}
+ for (i = 0; ternaryRelOpts[i].gen.name; i++)
+ {
+ relOpts[j] = &ternaryRelOpts[i].gen;
+ relOpts[j]->type = RELOPT_TYPE_TERNARY;
+ relOpts[j]->namelen = strlen(relOpts[j]->name);
+ j++;
+ }
+
for (i = 0; intRelOpts[i].gen.name; i++)
{
relOpts[j] = &intRelOpts[i].gen;
@@ -800,6 +821,9 @@ allocate_reloption(bits32 kinds, int type, const char *name, const char *desc,
case RELOPT_TYPE_BOOL:
size = sizeof(relopt_bool);
break;
+ case RELOPT_TYPE_TERNARY:
+ size = sizeof(relopt_ternary);
+ break;
case RELOPT_TYPE_INT:
size = sizeof(relopt_int);
break;
@@ -883,6 +907,54 @@ add_local_bool_reloption(local_relopts *relopts, const char *name,
add_local_reloption(relopts, (relopt_gen *) newoption, offset);
}
+/*
+ * init_ternary_reloption
+ * Allocate and initialize a new ternary reloption
+ */
+static relopt_ternary *
+init_ternary_reloption(bits32 kinds, const char *name, const char *desc,
+ ternary default_val, LOCKMODE lockmode)
+{
+ relopt_ternary *newoption;
+
+ newoption = (relopt_ternary *) allocate_reloption(kinds,
+ RELOPT_TYPE_TERNARY, name, desc, lockmode);
+ newoption->default_val = default_val;
+
+ return newoption;
+}
+
+/*
+ * add_ternary_reloption
+ * Add a new ternary reloption
+ */
+void
+add_ternary_reloption(bits32 kinds, const char *name, const char *desc,
+ ternary default_val, LOCKMODE lockmode)
+{
+ relopt_ternary *newoption = init_ternary_reloption(kinds, name, desc,
+ default_val, lockmode);
+
+ add_reloption((relopt_gen *) newoption);
+}
+
+/*
+ * add_local_ternary_reloption
+ * Add a new ternary local reloption
+ *
+ * 'offset' is offset of ternary-typed field.
+ */
+void
+add_local_ternary_reloption(local_relopts *relopts, const char *name,
+ const char *desc, ternary default_val,
+ int offset)
+{
+ relopt_ternary *newoption = init_ternary_reloption(RELOPT_KIND_LOCAL,
+ name, desc,
+ default_val, 0);
+
+ add_local_reloption(relopts, (relopt_gen *) newoption, offset);
+}
/*
* init_real_reloption
@@ -1617,6 +1689,18 @@ parse_one_reloption(relopt_value *option, char *text_str, int text_len,
option->gen->name, value)));
}
break;
+ case RELOPT_TYPE_TERNARY:
+ {
+ bool b;
+ parsed = parse_bool(value, &b);
+ option->values.ternary_val = b ? TERNARY_TRUE : TERNARY_FALSE;
+ if (validate && !parsed)
+ ereport(ERROR,
+ (errcode(ERRCODE_INVALID_PARAMETER_VALUE),
+ errmsg("invalid value for ternary option \"%s\": %s",
+ option->gen->name, value)));
+ }
+ break;
case RELOPT_TYPE_INT:
{
relopt_int *optint = (relopt_int *) option->gen;
@@ -1780,17 +1864,6 @@ fillRelOptions(void *rdopts, Size basesize,
char *itempos = ((char *) rdopts) + elems[j].offset;
char *string_val;
- /*
- * If isset_offset is provided, store whether the reloption is
- * set there.
- */
- if (elems[j].isset_offset > 0)
- {
- char *setpos = ((char *) rdopts) + elems[j].isset_offset;
-
- *(bool *) setpos = options[i].isset;
- }
-
switch (options[i].gen->type)
{
case RELOPT_TYPE_BOOL:
@@ -1798,6 +1871,11 @@ fillRelOptions(void *rdopts, Size basesize,
options[i].values.bool_val :
((relopt_bool *) options[i].gen)->default_val;
break;
+ case RELOPT_TYPE_TERNARY:
+ *(ternary *) itempos = options[i].isset ?
+ options[i].values.ternary_val :
+ ((relopt_ternary *) options[i].gen)->default_val;
+ break;
case RELOPT_TYPE_INT:
*(int *) itempos = options[i].isset ?
options[i].values.int_val :
@@ -1912,8 +1990,8 @@ default_reloptions(Datum reloptions, bool validate, relopt_kind kind)
offsetof(StdRdOptions, parallel_workers)},
{"vacuum_index_cleanup", RELOPT_TYPE_ENUM,
offsetof(StdRdOptions, vacuum_index_cleanup)},
- {"vacuum_truncate", RELOPT_TYPE_BOOL,
- offsetof(StdRdOptions, vacuum_truncate), offsetof(StdRdOptions, vacuum_truncate_set)},
+ {"vacuum_truncate", RELOPT_TYPE_TERNARY,
+ offsetof(StdRdOptions, vacuum_truncate)},
{"vacuum_max_eager_freeze_failure_rate", RELOPT_TYPE_REAL,
offsetof(StdRdOptions, vacuum_max_eager_freeze_failure_rate)}
};
@@ -1993,7 +2071,6 @@ build_local_reloptions(local_relopts *relopts, Datum options, bool validate)
elems[i].optname = opt->option->name;
elems[i].opttype = opt->option->type;
elems[i].offset = opt->offset;
- elems[i].isset_offset = 0; /* not supported for local relopts yet */
i++;
}
diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c
index 733ef40ae7..977babff54 100644
--- a/src/backend/commands/vacuum.c
+++ b/src/backend/commands/vacuum.c
@@ -2221,9 +2221,9 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams params,
{
StdRdOptions *opts = (StdRdOptions *) rel->rd_options;
- if (opts && opts->vacuum_truncate_set)
+ if (opts && opts->vacuum_truncate != TERNARY_UNSET)
{
- if (opts->vacuum_truncate)
+ if (opts->vacuum_truncate == TERNARY_TRUE)
params.truncate = VACOPTVALUE_ENABLED;
else
params.truncate = VACOPTVALUE_DISABLED;
diff --git a/src/include/access/reloptions.h b/src/include/access/reloptions.h
index a604a4702c..a436697658 100644
--- a/src/include/access/reloptions.h
+++ b/src/include/access/reloptions.h
@@ -29,6 +29,7 @@
typedef enum relopt_type
{
RELOPT_TYPE_BOOL,
+ RELOPT_TYPE_TERNARY, /* on, off, unset */
RELOPT_TYPE_INT,
RELOPT_TYPE_REAL,
RELOPT_TYPE_ENUM,
@@ -80,6 +81,7 @@ typedef struct relopt_value
union
{
bool bool_val;
+ ternary ternary_val;
int int_val;
double real_val;
int enum_val;
@@ -94,6 +96,12 @@ typedef struct relopt_bool
bool default_val;
} relopt_bool;
+typedef struct relopt_rernary
+{
+ relopt_gen gen;
+ int default_val;
+} relopt_ternary;
+
typedef struct relopt_int
{
relopt_gen gen;
@@ -152,19 +160,6 @@ typedef struct
const char *optname; /* option's name */
relopt_type opttype; /* option's datatype */
int offset; /* offset of field in result struct */
-
- /*
- * isset_offset is an optional offset of a field in the result struct that
- * stores whether the option is explicitly set for the relation or if it
- * just picked up the default value. In most cases, this can be
- * accomplished by giving the reloption a special out-of-range default
- * value (e.g., some integer reloptions use -2), but this isn't always
- * possible. For example, a Boolean reloption cannot be given an
- * out-of-range default, so we need another way to discover the source of
- * its value. This offset is only used if given a value greater than
- * zero.
- */
- int isset_offset;
} relopt_parse_elt;
/* Local reloption definition */
@@ -195,6 +190,8 @@ typedef struct local_relopts
extern relopt_kind add_reloption_kind(void);
extern void add_bool_reloption(bits32 kinds, const char *name, const char *desc,
bool default_val, LOCKMODE lockmode);
+extern void add_ternary_reloption(bits32 kinds, const char *name,
+ const char *desc, int default_val, LOCKMODE lockmode);
extern void add_int_reloption(bits32 kinds, const char *name, const char *desc,
int default_val, int min_val, int max_val,
LOCKMODE lockmode);
@@ -214,6 +211,9 @@ extern void register_reloptions_validator(local_relopts *relopts,
extern void add_local_bool_reloption(local_relopts *relopts, const char *name,
const char *desc, bool default_val,
int offset);
+extern void add_local_ternary_reloption(local_relopts *relopts,
+ const char *name, const char *desc,
+ ternary default_val, int offset);
extern void add_local_int_reloption(local_relopts *relopts, const char *name,
const char *desc, int default_val,
int min_val, int max_val, int offset);
diff --git a/src/include/c.h b/src/include/c.h
index 39022f8a9d..9c59ccccf8 100644
--- a/src/include/c.h
+++ b/src/include/c.h
@@ -488,6 +488,22 @@ typedef void (*pg_funcptr_t) (void);
#include <stdbool.h>
+/*
+ * ternary
+ * Boolean value with an extrea "unset" option
+ *
+ * Ternary data type is used in relation options that can be "true", "false" or
+ * "unset". Since relation options are used deep inside the PostgreSQL code,
+ * this type is declared globally.
+*/
+
+typedef enum ternary
+{
+ TERNARY_FALSE = 0,
+ TERNARY_TRUE = 1,
+ TERNARY_UNSET = -1
+} ternary;
+
/* ----------------------------------------------------------------
* Section 3: standard system types
diff --git a/src/include/utils/rel.h b/src/include/utils/rel.h
index b552359915..7346d618f9 100644
--- a/src/include/utils/rel.h
+++ b/src/include/utils/rel.h
@@ -346,8 +346,7 @@ typedef struct StdRdOptions
bool user_catalog_table; /* use as an additional catalog relation */
int parallel_workers; /* max number of parallel workers */
StdRdOptIndexCleanup vacuum_index_cleanup; /* controls index vacuuming */
- bool vacuum_truncate; /* enables vacuum to truncate a relation */
- bool vacuum_truncate_set; /* whether vacuum_truncate is set */
+ ternary vacuum_truncate; /* enables vacuum to truncate a relation */
/*
* Fraction of pages in a relation that vacuum can eagerly scan and fail
--
2.39.2
--nextPart4155600.3ZeAukHxDK
Content-Disposition: attachment;
filename="v2a-0003-Add-alias-to-be-used-as-unset-state.patch"
Content-Transfer-Encoding: 7Bit
Content-Type: text/x-patch; charset="unicode-2-0-utf-8";
name="v2a-0003-Add-alias-to-be-used-as-unset-state.patch"
^ permalink raw reply [nested|flat] 868+ messages in thread
* [PATCH v2a 2/4] Introduce ternary reloptions
@ 2025-09-04 14:41 Nikolay Shaplov <[email protected]>
0 siblings, 0 replies; 868+ messages in thread
From: Nikolay Shaplov @ 2025-09-04 14:41 UTC (permalink / raw)
Introduce ternary reloption as a replacement for current `vacuum_truncate`
implementation. Remove `vacuum_truncate_set` additional flag and using
`TERNARY_UNSET` value instead.
---
src/backend/access/common/reloptions.c | 129 ++++++++++++++++++++-----
src/backend/commands/vacuum.c | 4 +-
src/include/access/reloptions.h | 26 ++---
src/include/c.h | 16 +++
src/include/utils/rel.h | 3 +-
5 files changed, 135 insertions(+), 43 deletions(-)
diff --git a/src/backend/access/common/reloptions.c b/src/backend/access/common/reloptions.c
index 0af3fea68f..f543f11001 100644
--- a/src/backend/access/common/reloptions.c
+++ b/src/backend/access/common/reloptions.c
@@ -40,9 +40,9 @@
*
* To add an option:
*
- * (i) decide on a type (bool, integer, real, enum, string), name, default
- * value, upper and lower bounds (if applicable); for strings, consider a
- * validation routine.
+ * (i) decide on a type (bool, ternary, integer, real, enum, string), name,
+ * default value, upper and lower bounds (if applicable); for strings,
+ * consider a validation routine.
* (ii) add a record below (or use add_<type>_reloption).
* (iii) add it to the appropriate options struct (perhaps StdRdOptions)
* (iv) add it to the appropriate handling routine (perhaps
@@ -147,15 +147,6 @@ static relopt_bool boolRelOpts[] =
},
false
},
- {
- {
- "vacuum_truncate",
- "Enables vacuum to truncate empty pages at the end of this table",
- RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
- ShareUpdateExclusiveLock
- },
- true
- },
{
{
"deduplicate_items",
@@ -170,6 +161,21 @@ static relopt_bool boolRelOpts[] =
{{NULL}}
};
+static relopt_ternary ternaryRelOpts[] =
+{
+ {
+ {
+ "vacuum_truncate",
+ "Enables vacuum to truncate empty pages at the end of this table",
+ RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
+ ShareUpdateExclusiveLock
+ },
+ TERNARY_UNSET
+ },
+ /* list terminator */
+ {{NULL}}
+};
+
static relopt_int intRelOpts[] =
{
{
@@ -600,6 +606,13 @@ initialize_reloptions(void)
boolRelOpts[i].gen.lockmode));
j++;
}
+ for (i = 0; ternaryRelOpts[i].gen.name; i++)
+ {
+ Assert(DoLockModesConflict(ternaryRelOpts[i].gen.lockmode,
+ ternaryRelOpts[i].gen.lockmode));
+ j++;
+ }
+
for (i = 0; intRelOpts[i].gen.name; i++)
{
Assert(DoLockModesConflict(intRelOpts[i].gen.lockmode,
@@ -640,6 +653,14 @@ initialize_reloptions(void)
j++;
}
+ for (i = 0; ternaryRelOpts[i].gen.name; i++)
+ {
+ relOpts[j] = &ternaryRelOpts[i].gen;
+ relOpts[j]->type = RELOPT_TYPE_TERNARY;
+ relOpts[j]->namelen = strlen(relOpts[j]->name);
+ j++;
+ }
+
for (i = 0; intRelOpts[i].gen.name; i++)
{
relOpts[j] = &intRelOpts[i].gen;
@@ -800,6 +821,9 @@ allocate_reloption(bits32 kinds, int type, const char *name, const char *desc,
case RELOPT_TYPE_BOOL:
size = sizeof(relopt_bool);
break;
+ case RELOPT_TYPE_TERNARY:
+ size = sizeof(relopt_ternary);
+ break;
case RELOPT_TYPE_INT:
size = sizeof(relopt_int);
break;
@@ -883,6 +907,54 @@ add_local_bool_reloption(local_relopts *relopts, const char *name,
add_local_reloption(relopts, (relopt_gen *) newoption, offset);
}
+/*
+ * init_ternary_reloption
+ * Allocate and initialize a new ternary reloption
+ */
+static relopt_ternary *
+init_ternary_reloption(bits32 kinds, const char *name, const char *desc,
+ ternary default_val, LOCKMODE lockmode)
+{
+ relopt_ternary *newoption;
+
+ newoption = (relopt_ternary *) allocate_reloption(kinds,
+ RELOPT_TYPE_TERNARY, name, desc, lockmode);
+ newoption->default_val = default_val;
+
+ return newoption;
+}
+
+/*
+ * add_ternary_reloption
+ * Add a new ternary reloption
+ */
+void
+add_ternary_reloption(bits32 kinds, const char *name, const char *desc,
+ ternary default_val, LOCKMODE lockmode)
+{
+ relopt_ternary *newoption = init_ternary_reloption(kinds, name, desc,
+ default_val, lockmode);
+
+ add_reloption((relopt_gen *) newoption);
+}
+
+/*
+ * add_local_ternary_reloption
+ * Add a new ternary local reloption
+ *
+ * 'offset' is offset of ternary-typed field.
+ */
+void
+add_local_ternary_reloption(local_relopts *relopts, const char *name,
+ const char *desc, ternary default_val,
+ int offset)
+{
+ relopt_ternary *newoption = init_ternary_reloption(RELOPT_KIND_LOCAL,
+ name, desc,
+ default_val, 0);
+
+ add_local_reloption(relopts, (relopt_gen *) newoption, offset);
+}
/*
* init_real_reloption
@@ -1617,6 +1689,18 @@ parse_one_reloption(relopt_value *option, char *text_str, int text_len,
option->gen->name, value)));
}
break;
+ case RELOPT_TYPE_TERNARY:
+ {
+ bool b;
+ parsed = parse_bool(value, &b);
+ option->values.ternary_val = b ? TERNARY_TRUE : TERNARY_FALSE;
+ if (validate && !parsed)
+ ereport(ERROR,
+ (errcode(ERRCODE_INVALID_PARAMETER_VALUE),
+ errmsg("invalid value for ternary option \"%s\": %s",
+ option->gen->name, value)));
+ }
+ break;
case RELOPT_TYPE_INT:
{
relopt_int *optint = (relopt_int *) option->gen;
@@ -1780,17 +1864,6 @@ fillRelOptions(void *rdopts, Size basesize,
char *itempos = ((char *) rdopts) + elems[j].offset;
char *string_val;
- /*
- * If isset_offset is provided, store whether the reloption is
- * set there.
- */
- if (elems[j].isset_offset > 0)
- {
- char *setpos = ((char *) rdopts) + elems[j].isset_offset;
-
- *(bool *) setpos = options[i].isset;
- }
-
switch (options[i].gen->type)
{
case RELOPT_TYPE_BOOL:
@@ -1798,6 +1871,11 @@ fillRelOptions(void *rdopts, Size basesize,
options[i].values.bool_val :
((relopt_bool *) options[i].gen)->default_val;
break;
+ case RELOPT_TYPE_TERNARY:
+ *(ternary *) itempos = options[i].isset ?
+ options[i].values.ternary_val :
+ ((relopt_ternary *) options[i].gen)->default_val;
+ break;
case RELOPT_TYPE_INT:
*(int *) itempos = options[i].isset ?
options[i].values.int_val :
@@ -1912,8 +1990,8 @@ default_reloptions(Datum reloptions, bool validate, relopt_kind kind)
offsetof(StdRdOptions, parallel_workers)},
{"vacuum_index_cleanup", RELOPT_TYPE_ENUM,
offsetof(StdRdOptions, vacuum_index_cleanup)},
- {"vacuum_truncate", RELOPT_TYPE_BOOL,
- offsetof(StdRdOptions, vacuum_truncate), offsetof(StdRdOptions, vacuum_truncate_set)},
+ {"vacuum_truncate", RELOPT_TYPE_TERNARY,
+ offsetof(StdRdOptions, vacuum_truncate)},
{"vacuum_max_eager_freeze_failure_rate", RELOPT_TYPE_REAL,
offsetof(StdRdOptions, vacuum_max_eager_freeze_failure_rate)}
};
@@ -1993,7 +2071,6 @@ build_local_reloptions(local_relopts *relopts, Datum options, bool validate)
elems[i].optname = opt->option->name;
elems[i].opttype = opt->option->type;
elems[i].offset = opt->offset;
- elems[i].isset_offset = 0; /* not supported for local relopts yet */
i++;
}
diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c
index 733ef40ae7..977babff54 100644
--- a/src/backend/commands/vacuum.c
+++ b/src/backend/commands/vacuum.c
@@ -2221,9 +2221,9 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams params,
{
StdRdOptions *opts = (StdRdOptions *) rel->rd_options;
- if (opts && opts->vacuum_truncate_set)
+ if (opts && opts->vacuum_truncate != TERNARY_UNSET)
{
- if (opts->vacuum_truncate)
+ if (opts->vacuum_truncate == TERNARY_TRUE)
params.truncate = VACOPTVALUE_ENABLED;
else
params.truncate = VACOPTVALUE_DISABLED;
diff --git a/src/include/access/reloptions.h b/src/include/access/reloptions.h
index a604a4702c..a436697658 100644
--- a/src/include/access/reloptions.h
+++ b/src/include/access/reloptions.h
@@ -29,6 +29,7 @@
typedef enum relopt_type
{
RELOPT_TYPE_BOOL,
+ RELOPT_TYPE_TERNARY, /* on, off, unset */
RELOPT_TYPE_INT,
RELOPT_TYPE_REAL,
RELOPT_TYPE_ENUM,
@@ -80,6 +81,7 @@ typedef struct relopt_value
union
{
bool bool_val;
+ ternary ternary_val;
int int_val;
double real_val;
int enum_val;
@@ -94,6 +96,12 @@ typedef struct relopt_bool
bool default_val;
} relopt_bool;
+typedef struct relopt_rernary
+{
+ relopt_gen gen;
+ int default_val;
+} relopt_ternary;
+
typedef struct relopt_int
{
relopt_gen gen;
@@ -152,19 +160,6 @@ typedef struct
const char *optname; /* option's name */
relopt_type opttype; /* option's datatype */
int offset; /* offset of field in result struct */
-
- /*
- * isset_offset is an optional offset of a field in the result struct that
- * stores whether the option is explicitly set for the relation or if it
- * just picked up the default value. In most cases, this can be
- * accomplished by giving the reloption a special out-of-range default
- * value (e.g., some integer reloptions use -2), but this isn't always
- * possible. For example, a Boolean reloption cannot be given an
- * out-of-range default, so we need another way to discover the source of
- * its value. This offset is only used if given a value greater than
- * zero.
- */
- int isset_offset;
} relopt_parse_elt;
/* Local reloption definition */
@@ -195,6 +190,8 @@ typedef struct local_relopts
extern relopt_kind add_reloption_kind(void);
extern void add_bool_reloption(bits32 kinds, const char *name, const char *desc,
bool default_val, LOCKMODE lockmode);
+extern void add_ternary_reloption(bits32 kinds, const char *name,
+ const char *desc, int default_val, LOCKMODE lockmode);
extern void add_int_reloption(bits32 kinds, const char *name, const char *desc,
int default_val, int min_val, int max_val,
LOCKMODE lockmode);
@@ -214,6 +211,9 @@ extern void register_reloptions_validator(local_relopts *relopts,
extern void add_local_bool_reloption(local_relopts *relopts, const char *name,
const char *desc, bool default_val,
int offset);
+extern void add_local_ternary_reloption(local_relopts *relopts,
+ const char *name, const char *desc,
+ ternary default_val, int offset);
extern void add_local_int_reloption(local_relopts *relopts, const char *name,
const char *desc, int default_val,
int min_val, int max_val, int offset);
diff --git a/src/include/c.h b/src/include/c.h
index 39022f8a9d..9c59ccccf8 100644
--- a/src/include/c.h
+++ b/src/include/c.h
@@ -488,6 +488,22 @@ typedef void (*pg_funcptr_t) (void);
#include <stdbool.h>
+/*
+ * ternary
+ * Boolean value with an extrea "unset" option
+ *
+ * Ternary data type is used in relation options that can be "true", "false" or
+ * "unset". Since relation options are used deep inside the PostgreSQL code,
+ * this type is declared globally.
+*/
+
+typedef enum ternary
+{
+ TERNARY_FALSE = 0,
+ TERNARY_TRUE = 1,
+ TERNARY_UNSET = -1
+} ternary;
+
/* ----------------------------------------------------------------
* Section 3: standard system types
diff --git a/src/include/utils/rel.h b/src/include/utils/rel.h
index b552359915..7346d618f9 100644
--- a/src/include/utils/rel.h
+++ b/src/include/utils/rel.h
@@ -346,8 +346,7 @@ typedef struct StdRdOptions
bool user_catalog_table; /* use as an additional catalog relation */
int parallel_workers; /* max number of parallel workers */
StdRdOptIndexCleanup vacuum_index_cleanup; /* controls index vacuuming */
- bool vacuum_truncate; /* enables vacuum to truncate a relation */
- bool vacuum_truncate_set; /* whether vacuum_truncate is set */
+ ternary vacuum_truncate; /* enables vacuum to truncate a relation */
/*
* Fraction of pages in a relation that vacuum can eagerly scan and fail
--
2.39.2
--nextPart4155600.3ZeAukHxDK
Content-Disposition: attachment;
filename="v2a-0003-Add-alias-to-be-used-as-unset-state.patch"
Content-Transfer-Encoding: 7Bit
Content-Type: text/x-patch; charset="unicode-2-0-utf-8";
name="v2a-0003-Add-alias-to-be-used-as-unset-state.patch"
^ permalink raw reply [nested|flat] 868+ messages in thread
* [PATCH v2a 2/4] Introduce ternary reloptions
@ 2025-09-04 14:41 Nikolay Shaplov <[email protected]>
0 siblings, 0 replies; 868+ messages in thread
From: Nikolay Shaplov @ 2025-09-04 14:41 UTC (permalink / raw)
Introduce ternary reloption as a replacement for current `vacuum_truncate`
implementation. Remove `vacuum_truncate_set` additional flag and using
`TERNARY_UNSET` value instead.
---
src/backend/access/common/reloptions.c | 129 ++++++++++++++++++++-----
src/backend/commands/vacuum.c | 4 +-
src/include/access/reloptions.h | 26 ++---
src/include/c.h | 16 +++
src/include/utils/rel.h | 3 +-
5 files changed, 135 insertions(+), 43 deletions(-)
diff --git a/src/backend/access/common/reloptions.c b/src/backend/access/common/reloptions.c
index 0af3fea68f..f543f11001 100644
--- a/src/backend/access/common/reloptions.c
+++ b/src/backend/access/common/reloptions.c
@@ -40,9 +40,9 @@
*
* To add an option:
*
- * (i) decide on a type (bool, integer, real, enum, string), name, default
- * value, upper and lower bounds (if applicable); for strings, consider a
- * validation routine.
+ * (i) decide on a type (bool, ternary, integer, real, enum, string), name,
+ * default value, upper and lower bounds (if applicable); for strings,
+ * consider a validation routine.
* (ii) add a record below (or use add_<type>_reloption).
* (iii) add it to the appropriate options struct (perhaps StdRdOptions)
* (iv) add it to the appropriate handling routine (perhaps
@@ -147,15 +147,6 @@ static relopt_bool boolRelOpts[] =
},
false
},
- {
- {
- "vacuum_truncate",
- "Enables vacuum to truncate empty pages at the end of this table",
- RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
- ShareUpdateExclusiveLock
- },
- true
- },
{
{
"deduplicate_items",
@@ -170,6 +161,21 @@ static relopt_bool boolRelOpts[] =
{{NULL}}
};
+static relopt_ternary ternaryRelOpts[] =
+{
+ {
+ {
+ "vacuum_truncate",
+ "Enables vacuum to truncate empty pages at the end of this table",
+ RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
+ ShareUpdateExclusiveLock
+ },
+ TERNARY_UNSET
+ },
+ /* list terminator */
+ {{NULL}}
+};
+
static relopt_int intRelOpts[] =
{
{
@@ -600,6 +606,13 @@ initialize_reloptions(void)
boolRelOpts[i].gen.lockmode));
j++;
}
+ for (i = 0; ternaryRelOpts[i].gen.name; i++)
+ {
+ Assert(DoLockModesConflict(ternaryRelOpts[i].gen.lockmode,
+ ternaryRelOpts[i].gen.lockmode));
+ j++;
+ }
+
for (i = 0; intRelOpts[i].gen.name; i++)
{
Assert(DoLockModesConflict(intRelOpts[i].gen.lockmode,
@@ -640,6 +653,14 @@ initialize_reloptions(void)
j++;
}
+ for (i = 0; ternaryRelOpts[i].gen.name; i++)
+ {
+ relOpts[j] = &ternaryRelOpts[i].gen;
+ relOpts[j]->type = RELOPT_TYPE_TERNARY;
+ relOpts[j]->namelen = strlen(relOpts[j]->name);
+ j++;
+ }
+
for (i = 0; intRelOpts[i].gen.name; i++)
{
relOpts[j] = &intRelOpts[i].gen;
@@ -800,6 +821,9 @@ allocate_reloption(bits32 kinds, int type, const char *name, const char *desc,
case RELOPT_TYPE_BOOL:
size = sizeof(relopt_bool);
break;
+ case RELOPT_TYPE_TERNARY:
+ size = sizeof(relopt_ternary);
+ break;
case RELOPT_TYPE_INT:
size = sizeof(relopt_int);
break;
@@ -883,6 +907,54 @@ add_local_bool_reloption(local_relopts *relopts, const char *name,
add_local_reloption(relopts, (relopt_gen *) newoption, offset);
}
+/*
+ * init_ternary_reloption
+ * Allocate and initialize a new ternary reloption
+ */
+static relopt_ternary *
+init_ternary_reloption(bits32 kinds, const char *name, const char *desc,
+ ternary default_val, LOCKMODE lockmode)
+{
+ relopt_ternary *newoption;
+
+ newoption = (relopt_ternary *) allocate_reloption(kinds,
+ RELOPT_TYPE_TERNARY, name, desc, lockmode);
+ newoption->default_val = default_val;
+
+ return newoption;
+}
+
+/*
+ * add_ternary_reloption
+ * Add a new ternary reloption
+ */
+void
+add_ternary_reloption(bits32 kinds, const char *name, const char *desc,
+ ternary default_val, LOCKMODE lockmode)
+{
+ relopt_ternary *newoption = init_ternary_reloption(kinds, name, desc,
+ default_val, lockmode);
+
+ add_reloption((relopt_gen *) newoption);
+}
+
+/*
+ * add_local_ternary_reloption
+ * Add a new ternary local reloption
+ *
+ * 'offset' is offset of ternary-typed field.
+ */
+void
+add_local_ternary_reloption(local_relopts *relopts, const char *name,
+ const char *desc, ternary default_val,
+ int offset)
+{
+ relopt_ternary *newoption = init_ternary_reloption(RELOPT_KIND_LOCAL,
+ name, desc,
+ default_val, 0);
+
+ add_local_reloption(relopts, (relopt_gen *) newoption, offset);
+}
/*
* init_real_reloption
@@ -1617,6 +1689,18 @@ parse_one_reloption(relopt_value *option, char *text_str, int text_len,
option->gen->name, value)));
}
break;
+ case RELOPT_TYPE_TERNARY:
+ {
+ bool b;
+ parsed = parse_bool(value, &b);
+ option->values.ternary_val = b ? TERNARY_TRUE : TERNARY_FALSE;
+ if (validate && !parsed)
+ ereport(ERROR,
+ (errcode(ERRCODE_INVALID_PARAMETER_VALUE),
+ errmsg("invalid value for ternary option \"%s\": %s",
+ option->gen->name, value)));
+ }
+ break;
case RELOPT_TYPE_INT:
{
relopt_int *optint = (relopt_int *) option->gen;
@@ -1780,17 +1864,6 @@ fillRelOptions(void *rdopts, Size basesize,
char *itempos = ((char *) rdopts) + elems[j].offset;
char *string_val;
- /*
- * If isset_offset is provided, store whether the reloption is
- * set there.
- */
- if (elems[j].isset_offset > 0)
- {
- char *setpos = ((char *) rdopts) + elems[j].isset_offset;
-
- *(bool *) setpos = options[i].isset;
- }
-
switch (options[i].gen->type)
{
case RELOPT_TYPE_BOOL:
@@ -1798,6 +1871,11 @@ fillRelOptions(void *rdopts, Size basesize,
options[i].values.bool_val :
((relopt_bool *) options[i].gen)->default_val;
break;
+ case RELOPT_TYPE_TERNARY:
+ *(ternary *) itempos = options[i].isset ?
+ options[i].values.ternary_val :
+ ((relopt_ternary *) options[i].gen)->default_val;
+ break;
case RELOPT_TYPE_INT:
*(int *) itempos = options[i].isset ?
options[i].values.int_val :
@@ -1912,8 +1990,8 @@ default_reloptions(Datum reloptions, bool validate, relopt_kind kind)
offsetof(StdRdOptions, parallel_workers)},
{"vacuum_index_cleanup", RELOPT_TYPE_ENUM,
offsetof(StdRdOptions, vacuum_index_cleanup)},
- {"vacuum_truncate", RELOPT_TYPE_BOOL,
- offsetof(StdRdOptions, vacuum_truncate), offsetof(StdRdOptions, vacuum_truncate_set)},
+ {"vacuum_truncate", RELOPT_TYPE_TERNARY,
+ offsetof(StdRdOptions, vacuum_truncate)},
{"vacuum_max_eager_freeze_failure_rate", RELOPT_TYPE_REAL,
offsetof(StdRdOptions, vacuum_max_eager_freeze_failure_rate)}
};
@@ -1993,7 +2071,6 @@ build_local_reloptions(local_relopts *relopts, Datum options, bool validate)
elems[i].optname = opt->option->name;
elems[i].opttype = opt->option->type;
elems[i].offset = opt->offset;
- elems[i].isset_offset = 0; /* not supported for local relopts yet */
i++;
}
diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c
index 733ef40ae7..977babff54 100644
--- a/src/backend/commands/vacuum.c
+++ b/src/backend/commands/vacuum.c
@@ -2221,9 +2221,9 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams params,
{
StdRdOptions *opts = (StdRdOptions *) rel->rd_options;
- if (opts && opts->vacuum_truncate_set)
+ if (opts && opts->vacuum_truncate != TERNARY_UNSET)
{
- if (opts->vacuum_truncate)
+ if (opts->vacuum_truncate == TERNARY_TRUE)
params.truncate = VACOPTVALUE_ENABLED;
else
params.truncate = VACOPTVALUE_DISABLED;
diff --git a/src/include/access/reloptions.h b/src/include/access/reloptions.h
index a604a4702c..a436697658 100644
--- a/src/include/access/reloptions.h
+++ b/src/include/access/reloptions.h
@@ -29,6 +29,7 @@
typedef enum relopt_type
{
RELOPT_TYPE_BOOL,
+ RELOPT_TYPE_TERNARY, /* on, off, unset */
RELOPT_TYPE_INT,
RELOPT_TYPE_REAL,
RELOPT_TYPE_ENUM,
@@ -80,6 +81,7 @@ typedef struct relopt_value
union
{
bool bool_val;
+ ternary ternary_val;
int int_val;
double real_val;
int enum_val;
@@ -94,6 +96,12 @@ typedef struct relopt_bool
bool default_val;
} relopt_bool;
+typedef struct relopt_rernary
+{
+ relopt_gen gen;
+ int default_val;
+} relopt_ternary;
+
typedef struct relopt_int
{
relopt_gen gen;
@@ -152,19 +160,6 @@ typedef struct
const char *optname; /* option's name */
relopt_type opttype; /* option's datatype */
int offset; /* offset of field in result struct */
-
- /*
- * isset_offset is an optional offset of a field in the result struct that
- * stores whether the option is explicitly set for the relation or if it
- * just picked up the default value. In most cases, this can be
- * accomplished by giving the reloption a special out-of-range default
- * value (e.g., some integer reloptions use -2), but this isn't always
- * possible. For example, a Boolean reloption cannot be given an
- * out-of-range default, so we need another way to discover the source of
- * its value. This offset is only used if given a value greater than
- * zero.
- */
- int isset_offset;
} relopt_parse_elt;
/* Local reloption definition */
@@ -195,6 +190,8 @@ typedef struct local_relopts
extern relopt_kind add_reloption_kind(void);
extern void add_bool_reloption(bits32 kinds, const char *name, const char *desc,
bool default_val, LOCKMODE lockmode);
+extern void add_ternary_reloption(bits32 kinds, const char *name,
+ const char *desc, int default_val, LOCKMODE lockmode);
extern void add_int_reloption(bits32 kinds, const char *name, const char *desc,
int default_val, int min_val, int max_val,
LOCKMODE lockmode);
@@ -214,6 +211,9 @@ extern void register_reloptions_validator(local_relopts *relopts,
extern void add_local_bool_reloption(local_relopts *relopts, const char *name,
const char *desc, bool default_val,
int offset);
+extern void add_local_ternary_reloption(local_relopts *relopts,
+ const char *name, const char *desc,
+ ternary default_val, int offset);
extern void add_local_int_reloption(local_relopts *relopts, const char *name,
const char *desc, int default_val,
int min_val, int max_val, int offset);
diff --git a/src/include/c.h b/src/include/c.h
index 39022f8a9d..9c59ccccf8 100644
--- a/src/include/c.h
+++ b/src/include/c.h
@@ -488,6 +488,22 @@ typedef void (*pg_funcptr_t) (void);
#include <stdbool.h>
+/*
+ * ternary
+ * Boolean value with an extrea "unset" option
+ *
+ * Ternary data type is used in relation options that can be "true", "false" or
+ * "unset". Since relation options are used deep inside the PostgreSQL code,
+ * this type is declared globally.
+*/
+
+typedef enum ternary
+{
+ TERNARY_FALSE = 0,
+ TERNARY_TRUE = 1,
+ TERNARY_UNSET = -1
+} ternary;
+
/* ----------------------------------------------------------------
* Section 3: standard system types
diff --git a/src/include/utils/rel.h b/src/include/utils/rel.h
index b552359915..7346d618f9 100644
--- a/src/include/utils/rel.h
+++ b/src/include/utils/rel.h
@@ -346,8 +346,7 @@ typedef struct StdRdOptions
bool user_catalog_table; /* use as an additional catalog relation */
int parallel_workers; /* max number of parallel workers */
StdRdOptIndexCleanup vacuum_index_cleanup; /* controls index vacuuming */
- bool vacuum_truncate; /* enables vacuum to truncate a relation */
- bool vacuum_truncate_set; /* whether vacuum_truncate is set */
+ ternary vacuum_truncate; /* enables vacuum to truncate a relation */
/*
* Fraction of pages in a relation that vacuum can eagerly scan and fail
--
2.39.2
--nextPart4155600.3ZeAukHxDK
Content-Disposition: attachment;
filename="v2a-0003-Add-alias-to-be-used-as-unset-state.patch"
Content-Transfer-Encoding: 7Bit
Content-Type: text/x-patch; charset="unicode-2-0-utf-8";
name="v2a-0003-Add-alias-to-be-used-as-unset-state.patch"
^ permalink raw reply [nested|flat] 868+ messages in thread
* [PATCH v2 2/4] Introduce ternary reloptions
@ 2025-09-04 14:41 Nikolay Shaplov <[email protected]>
0 siblings, 0 replies; 868+ messages in thread
From: Nikolay Shaplov @ 2025-09-04 14:41 UTC (permalink / raw)
Introduce ternary reloption as a replacement for current `vacuum_truncate`
implementation. Remove `vacuum_truncate_set` additional flag and using
`TERNARY_UNSET` value instead.
---
src/backend/access/common/reloptions.c | 129 ++++++++++++++++++++-----
src/backend/commands/vacuum.c | 4 +-
src/include/access/reloptions.h | 26 ++---
src/include/c.h | 16 +++
src/include/utils/rel.h | 3 +-
5 files changed, 135 insertions(+), 43 deletions(-)
diff --git a/src/backend/access/common/reloptions.c b/src/backend/access/common/reloptions.c
index 0af3fea68f..f543f11001 100644
--- a/src/backend/access/common/reloptions.c
+++ b/src/backend/access/common/reloptions.c
@@ -40,9 +40,9 @@
*
* To add an option:
*
- * (i) decide on a type (bool, integer, real, enum, string), name, default
- * value, upper and lower bounds (if applicable); for strings, consider a
- * validation routine.
+ * (i) decide on a type (bool, ternary, integer, real, enum, string), name,
+ * default value, upper and lower bounds (if applicable); for strings,
+ * consider a validation routine.
* (ii) add a record below (or use add_<type>_reloption).
* (iii) add it to the appropriate options struct (perhaps StdRdOptions)
* (iv) add it to the appropriate handling routine (perhaps
@@ -147,15 +147,6 @@ static relopt_bool boolRelOpts[] =
},
false
},
- {
- {
- "vacuum_truncate",
- "Enables vacuum to truncate empty pages at the end of this table",
- RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
- ShareUpdateExclusiveLock
- },
- true
- },
{
{
"deduplicate_items",
@@ -170,6 +161,21 @@ static relopt_bool boolRelOpts[] =
{{NULL}}
};
+static relopt_ternary ternaryRelOpts[] =
+{
+ {
+ {
+ "vacuum_truncate",
+ "Enables vacuum to truncate empty pages at the end of this table",
+ RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
+ ShareUpdateExclusiveLock
+ },
+ TERNARY_UNSET
+ },
+ /* list terminator */
+ {{NULL}}
+};
+
static relopt_int intRelOpts[] =
{
{
@@ -600,6 +606,13 @@ initialize_reloptions(void)
boolRelOpts[i].gen.lockmode));
j++;
}
+ for (i = 0; ternaryRelOpts[i].gen.name; i++)
+ {
+ Assert(DoLockModesConflict(ternaryRelOpts[i].gen.lockmode,
+ ternaryRelOpts[i].gen.lockmode));
+ j++;
+ }
+
for (i = 0; intRelOpts[i].gen.name; i++)
{
Assert(DoLockModesConflict(intRelOpts[i].gen.lockmode,
@@ -640,6 +653,14 @@ initialize_reloptions(void)
j++;
}
+ for (i = 0; ternaryRelOpts[i].gen.name; i++)
+ {
+ relOpts[j] = &ternaryRelOpts[i].gen;
+ relOpts[j]->type = RELOPT_TYPE_TERNARY;
+ relOpts[j]->namelen = strlen(relOpts[j]->name);
+ j++;
+ }
+
for (i = 0; intRelOpts[i].gen.name; i++)
{
relOpts[j] = &intRelOpts[i].gen;
@@ -800,6 +821,9 @@ allocate_reloption(bits32 kinds, int type, const char *name, const char *desc,
case RELOPT_TYPE_BOOL:
size = sizeof(relopt_bool);
break;
+ case RELOPT_TYPE_TERNARY:
+ size = sizeof(relopt_ternary);
+ break;
case RELOPT_TYPE_INT:
size = sizeof(relopt_int);
break;
@@ -883,6 +907,54 @@ add_local_bool_reloption(local_relopts *relopts, const char *name,
add_local_reloption(relopts, (relopt_gen *) newoption, offset);
}
+/*
+ * init_ternary_reloption
+ * Allocate and initialize a new ternary reloption
+ */
+static relopt_ternary *
+init_ternary_reloption(bits32 kinds, const char *name, const char *desc,
+ ternary default_val, LOCKMODE lockmode)
+{
+ relopt_ternary *newoption;
+
+ newoption = (relopt_ternary *) allocate_reloption(kinds,
+ RELOPT_TYPE_TERNARY, name, desc, lockmode);
+ newoption->default_val = default_val;
+
+ return newoption;
+}
+
+/*
+ * add_ternary_reloption
+ * Add a new ternary reloption
+ */
+void
+add_ternary_reloption(bits32 kinds, const char *name, const char *desc,
+ ternary default_val, LOCKMODE lockmode)
+{
+ relopt_ternary *newoption = init_ternary_reloption(kinds, name, desc,
+ default_val, lockmode);
+
+ add_reloption((relopt_gen *) newoption);
+}
+
+/*
+ * add_local_ternary_reloption
+ * Add a new ternary local reloption
+ *
+ * 'offset' is offset of ternary-typed field.
+ */
+void
+add_local_ternary_reloption(local_relopts *relopts, const char *name,
+ const char *desc, ternary default_val,
+ int offset)
+{
+ relopt_ternary *newoption = init_ternary_reloption(RELOPT_KIND_LOCAL,
+ name, desc,
+ default_val, 0);
+
+ add_local_reloption(relopts, (relopt_gen *) newoption, offset);
+}
/*
* init_real_reloption
@@ -1617,6 +1689,18 @@ parse_one_reloption(relopt_value *option, char *text_str, int text_len,
option->gen->name, value)));
}
break;
+ case RELOPT_TYPE_TERNARY:
+ {
+ bool b;
+ parsed = parse_bool(value, &b);
+ option->values.ternary_val = b ? TERNARY_TRUE : TERNARY_FALSE;
+ if (validate && !parsed)
+ ereport(ERROR,
+ (errcode(ERRCODE_INVALID_PARAMETER_VALUE),
+ errmsg("invalid value for ternary option \"%s\": %s",
+ option->gen->name, value)));
+ }
+ break;
case RELOPT_TYPE_INT:
{
relopt_int *optint = (relopt_int *) option->gen;
@@ -1780,17 +1864,6 @@ fillRelOptions(void *rdopts, Size basesize,
char *itempos = ((char *) rdopts) + elems[j].offset;
char *string_val;
- /*
- * If isset_offset is provided, store whether the reloption is
- * set there.
- */
- if (elems[j].isset_offset > 0)
- {
- char *setpos = ((char *) rdopts) + elems[j].isset_offset;
-
- *(bool *) setpos = options[i].isset;
- }
-
switch (options[i].gen->type)
{
case RELOPT_TYPE_BOOL:
@@ -1798,6 +1871,11 @@ fillRelOptions(void *rdopts, Size basesize,
options[i].values.bool_val :
((relopt_bool *) options[i].gen)->default_val;
break;
+ case RELOPT_TYPE_TERNARY:
+ *(ternary *) itempos = options[i].isset ?
+ options[i].values.ternary_val :
+ ((relopt_ternary *) options[i].gen)->default_val;
+ break;
case RELOPT_TYPE_INT:
*(int *) itempos = options[i].isset ?
options[i].values.int_val :
@@ -1912,8 +1990,8 @@ default_reloptions(Datum reloptions, bool validate, relopt_kind kind)
offsetof(StdRdOptions, parallel_workers)},
{"vacuum_index_cleanup", RELOPT_TYPE_ENUM,
offsetof(StdRdOptions, vacuum_index_cleanup)},
- {"vacuum_truncate", RELOPT_TYPE_BOOL,
- offsetof(StdRdOptions, vacuum_truncate), offsetof(StdRdOptions, vacuum_truncate_set)},
+ {"vacuum_truncate", RELOPT_TYPE_TERNARY,
+ offsetof(StdRdOptions, vacuum_truncate)},
{"vacuum_max_eager_freeze_failure_rate", RELOPT_TYPE_REAL,
offsetof(StdRdOptions, vacuum_max_eager_freeze_failure_rate)}
};
@@ -1993,7 +2071,6 @@ build_local_reloptions(local_relopts *relopts, Datum options, bool validate)
elems[i].optname = opt->option->name;
elems[i].opttype = opt->option->type;
elems[i].offset = opt->offset;
- elems[i].isset_offset = 0; /* not supported for local relopts yet */
i++;
}
diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c
index 733ef40ae7..977babff54 100644
--- a/src/backend/commands/vacuum.c
+++ b/src/backend/commands/vacuum.c
@@ -2221,9 +2221,9 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams params,
{
StdRdOptions *opts = (StdRdOptions *) rel->rd_options;
- if (opts && opts->vacuum_truncate_set)
+ if (opts && opts->vacuum_truncate != TERNARY_UNSET)
{
- if (opts->vacuum_truncate)
+ if (opts->vacuum_truncate == TERNARY_TRUE)
params.truncate = VACOPTVALUE_ENABLED;
else
params.truncate = VACOPTVALUE_DISABLED;
diff --git a/src/include/access/reloptions.h b/src/include/access/reloptions.h
index a604a4702c..a436697658 100644
--- a/src/include/access/reloptions.h
+++ b/src/include/access/reloptions.h
@@ -29,6 +29,7 @@
typedef enum relopt_type
{
RELOPT_TYPE_BOOL,
+ RELOPT_TYPE_TERNARY, /* on, off, unset */
RELOPT_TYPE_INT,
RELOPT_TYPE_REAL,
RELOPT_TYPE_ENUM,
@@ -80,6 +81,7 @@ typedef struct relopt_value
union
{
bool bool_val;
+ ternary ternary_val;
int int_val;
double real_val;
int enum_val;
@@ -94,6 +96,12 @@ typedef struct relopt_bool
bool default_val;
} relopt_bool;
+typedef struct relopt_rernary
+{
+ relopt_gen gen;
+ int default_val;
+} relopt_ternary;
+
typedef struct relopt_int
{
relopt_gen gen;
@@ -152,19 +160,6 @@ typedef struct
const char *optname; /* option's name */
relopt_type opttype; /* option's datatype */
int offset; /* offset of field in result struct */
-
- /*
- * isset_offset is an optional offset of a field in the result struct that
- * stores whether the option is explicitly set for the relation or if it
- * just picked up the default value. In most cases, this can be
- * accomplished by giving the reloption a special out-of-range default
- * value (e.g., some integer reloptions use -2), but this isn't always
- * possible. For example, a Boolean reloption cannot be given an
- * out-of-range default, so we need another way to discover the source of
- * its value. This offset is only used if given a value greater than
- * zero.
- */
- int isset_offset;
} relopt_parse_elt;
/* Local reloption definition */
@@ -195,6 +190,8 @@ typedef struct local_relopts
extern relopt_kind add_reloption_kind(void);
extern void add_bool_reloption(bits32 kinds, const char *name, const char *desc,
bool default_val, LOCKMODE lockmode);
+extern void add_ternary_reloption(bits32 kinds, const char *name,
+ const char *desc, int default_val, LOCKMODE lockmode);
extern void add_int_reloption(bits32 kinds, const char *name, const char *desc,
int default_val, int min_val, int max_val,
LOCKMODE lockmode);
@@ -214,6 +211,9 @@ extern void register_reloptions_validator(local_relopts *relopts,
extern void add_local_bool_reloption(local_relopts *relopts, const char *name,
const char *desc, bool default_val,
int offset);
+extern void add_local_ternary_reloption(local_relopts *relopts,
+ const char *name, const char *desc,
+ ternary default_val, int offset);
extern void add_local_int_reloption(local_relopts *relopts, const char *name,
const char *desc, int default_val,
int min_val, int max_val, int offset);
diff --git a/src/include/c.h b/src/include/c.h
index 39022f8a9d..9c59ccccf8 100644
--- a/src/include/c.h
+++ b/src/include/c.h
@@ -488,6 +488,22 @@ typedef void (*pg_funcptr_t) (void);
#include <stdbool.h>
+/*
+ * ternary
+ * Boolean value with an extrea "unset" option
+ *
+ * Ternary data type is used in relation options that can be "true", "false" or
+ * "unset". Since relation options are used deep inside the PostgreSQL code,
+ * this type is declared globally.
+*/
+
+typedef enum ternary
+{
+ TERNARY_FALSE = 0,
+ TERNARY_TRUE = 1,
+ TERNARY_UNSET = -1
+} ternary;
+
/* ----------------------------------------------------------------
* Section 3: standard system types
diff --git a/src/include/utils/rel.h b/src/include/utils/rel.h
index b552359915..7346d618f9 100644
--- a/src/include/utils/rel.h
+++ b/src/include/utils/rel.h
@@ -346,8 +346,7 @@ typedef struct StdRdOptions
bool user_catalog_table; /* use as an additional catalog relation */
int parallel_workers; /* max number of parallel workers */
StdRdOptIndexCleanup vacuum_index_cleanup; /* controls index vacuuming */
- bool vacuum_truncate; /* enables vacuum to truncate a relation */
- bool vacuum_truncate_set; /* whether vacuum_truncate is set */
+ ternary vacuum_truncate; /* enables vacuum to truncate a relation */
/*
* Fraction of pages in a relation that vacuum can eagerly scan and fail
--
2.39.2
--nextPart5129932.5fSG56mABF
Content-Disposition: attachment;
filename="v2-0003-Add-alias-to-be-used-as-unset-state.patch"
Content-Transfer-Encoding: 7Bit
Content-Type: text/x-patch; charset="unicode-2-0-utf-8";
name="v2-0003-Add-alias-to-be-used-as-unset-state.patch"
^ permalink raw reply [nested|flat] 868+ messages in thread
* [PATCH v2 2/4] Introduce ternary reloptions
@ 2025-09-04 14:41 Nikolay Shaplov <[email protected]>
0 siblings, 0 replies; 868+ messages in thread
From: Nikolay Shaplov @ 2025-09-04 14:41 UTC (permalink / raw)
Introduce ternary reloption as a replacement for current `vacuum_truncate`
implementation. Remove `vacuum_truncate_set` additional flag and using
`TERNARY_UNSET` value instead.
---
src/backend/access/common/reloptions.c | 129 ++++++++++++++++++++-----
src/backend/commands/vacuum.c | 4 +-
src/include/access/reloptions.h | 26 ++---
src/include/c.h | 16 +++
src/include/utils/rel.h | 3 +-
5 files changed, 135 insertions(+), 43 deletions(-)
diff --git a/src/backend/access/common/reloptions.c b/src/backend/access/common/reloptions.c
index 0af3fea68f..f543f11001 100644
--- a/src/backend/access/common/reloptions.c
+++ b/src/backend/access/common/reloptions.c
@@ -40,9 +40,9 @@
*
* To add an option:
*
- * (i) decide on a type (bool, integer, real, enum, string), name, default
- * value, upper and lower bounds (if applicable); for strings, consider a
- * validation routine.
+ * (i) decide on a type (bool, ternary, integer, real, enum, string), name,
+ * default value, upper and lower bounds (if applicable); for strings,
+ * consider a validation routine.
* (ii) add a record below (or use add_<type>_reloption).
* (iii) add it to the appropriate options struct (perhaps StdRdOptions)
* (iv) add it to the appropriate handling routine (perhaps
@@ -147,15 +147,6 @@ static relopt_bool boolRelOpts[] =
},
false
},
- {
- {
- "vacuum_truncate",
- "Enables vacuum to truncate empty pages at the end of this table",
- RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
- ShareUpdateExclusiveLock
- },
- true
- },
{
{
"deduplicate_items",
@@ -170,6 +161,21 @@ static relopt_bool boolRelOpts[] =
{{NULL}}
};
+static relopt_ternary ternaryRelOpts[] =
+{
+ {
+ {
+ "vacuum_truncate",
+ "Enables vacuum to truncate empty pages at the end of this table",
+ RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
+ ShareUpdateExclusiveLock
+ },
+ TERNARY_UNSET
+ },
+ /* list terminator */
+ {{NULL}}
+};
+
static relopt_int intRelOpts[] =
{
{
@@ -600,6 +606,13 @@ initialize_reloptions(void)
boolRelOpts[i].gen.lockmode));
j++;
}
+ for (i = 0; ternaryRelOpts[i].gen.name; i++)
+ {
+ Assert(DoLockModesConflict(ternaryRelOpts[i].gen.lockmode,
+ ternaryRelOpts[i].gen.lockmode));
+ j++;
+ }
+
for (i = 0; intRelOpts[i].gen.name; i++)
{
Assert(DoLockModesConflict(intRelOpts[i].gen.lockmode,
@@ -640,6 +653,14 @@ initialize_reloptions(void)
j++;
}
+ for (i = 0; ternaryRelOpts[i].gen.name; i++)
+ {
+ relOpts[j] = &ternaryRelOpts[i].gen;
+ relOpts[j]->type = RELOPT_TYPE_TERNARY;
+ relOpts[j]->namelen = strlen(relOpts[j]->name);
+ j++;
+ }
+
for (i = 0; intRelOpts[i].gen.name; i++)
{
relOpts[j] = &intRelOpts[i].gen;
@@ -800,6 +821,9 @@ allocate_reloption(bits32 kinds, int type, const char *name, const char *desc,
case RELOPT_TYPE_BOOL:
size = sizeof(relopt_bool);
break;
+ case RELOPT_TYPE_TERNARY:
+ size = sizeof(relopt_ternary);
+ break;
case RELOPT_TYPE_INT:
size = sizeof(relopt_int);
break;
@@ -883,6 +907,54 @@ add_local_bool_reloption(local_relopts *relopts, const char *name,
add_local_reloption(relopts, (relopt_gen *) newoption, offset);
}
+/*
+ * init_ternary_reloption
+ * Allocate and initialize a new ternary reloption
+ */
+static relopt_ternary *
+init_ternary_reloption(bits32 kinds, const char *name, const char *desc,
+ ternary default_val, LOCKMODE lockmode)
+{
+ relopt_ternary *newoption;
+
+ newoption = (relopt_ternary *) allocate_reloption(kinds,
+ RELOPT_TYPE_TERNARY, name, desc, lockmode);
+ newoption->default_val = default_val;
+
+ return newoption;
+}
+
+/*
+ * add_ternary_reloption
+ * Add a new ternary reloption
+ */
+void
+add_ternary_reloption(bits32 kinds, const char *name, const char *desc,
+ ternary default_val, LOCKMODE lockmode)
+{
+ relopt_ternary *newoption = init_ternary_reloption(kinds, name, desc,
+ default_val, lockmode);
+
+ add_reloption((relopt_gen *) newoption);
+}
+
+/*
+ * add_local_ternary_reloption
+ * Add a new ternary local reloption
+ *
+ * 'offset' is offset of ternary-typed field.
+ */
+void
+add_local_ternary_reloption(local_relopts *relopts, const char *name,
+ const char *desc, ternary default_val,
+ int offset)
+{
+ relopt_ternary *newoption = init_ternary_reloption(RELOPT_KIND_LOCAL,
+ name, desc,
+ default_val, 0);
+
+ add_local_reloption(relopts, (relopt_gen *) newoption, offset);
+}
/*
* init_real_reloption
@@ -1617,6 +1689,18 @@ parse_one_reloption(relopt_value *option, char *text_str, int text_len,
option->gen->name, value)));
}
break;
+ case RELOPT_TYPE_TERNARY:
+ {
+ bool b;
+ parsed = parse_bool(value, &b);
+ option->values.ternary_val = b ? TERNARY_TRUE : TERNARY_FALSE;
+ if (validate && !parsed)
+ ereport(ERROR,
+ (errcode(ERRCODE_INVALID_PARAMETER_VALUE),
+ errmsg("invalid value for ternary option \"%s\": %s",
+ option->gen->name, value)));
+ }
+ break;
case RELOPT_TYPE_INT:
{
relopt_int *optint = (relopt_int *) option->gen;
@@ -1780,17 +1864,6 @@ fillRelOptions(void *rdopts, Size basesize,
char *itempos = ((char *) rdopts) + elems[j].offset;
char *string_val;
- /*
- * If isset_offset is provided, store whether the reloption is
- * set there.
- */
- if (elems[j].isset_offset > 0)
- {
- char *setpos = ((char *) rdopts) + elems[j].isset_offset;
-
- *(bool *) setpos = options[i].isset;
- }
-
switch (options[i].gen->type)
{
case RELOPT_TYPE_BOOL:
@@ -1798,6 +1871,11 @@ fillRelOptions(void *rdopts, Size basesize,
options[i].values.bool_val :
((relopt_bool *) options[i].gen)->default_val;
break;
+ case RELOPT_TYPE_TERNARY:
+ *(ternary *) itempos = options[i].isset ?
+ options[i].values.ternary_val :
+ ((relopt_ternary *) options[i].gen)->default_val;
+ break;
case RELOPT_TYPE_INT:
*(int *) itempos = options[i].isset ?
options[i].values.int_val :
@@ -1912,8 +1990,8 @@ default_reloptions(Datum reloptions, bool validate, relopt_kind kind)
offsetof(StdRdOptions, parallel_workers)},
{"vacuum_index_cleanup", RELOPT_TYPE_ENUM,
offsetof(StdRdOptions, vacuum_index_cleanup)},
- {"vacuum_truncate", RELOPT_TYPE_BOOL,
- offsetof(StdRdOptions, vacuum_truncate), offsetof(StdRdOptions, vacuum_truncate_set)},
+ {"vacuum_truncate", RELOPT_TYPE_TERNARY,
+ offsetof(StdRdOptions, vacuum_truncate)},
{"vacuum_max_eager_freeze_failure_rate", RELOPT_TYPE_REAL,
offsetof(StdRdOptions, vacuum_max_eager_freeze_failure_rate)}
};
@@ -1993,7 +2071,6 @@ build_local_reloptions(local_relopts *relopts, Datum options, bool validate)
elems[i].optname = opt->option->name;
elems[i].opttype = opt->option->type;
elems[i].offset = opt->offset;
- elems[i].isset_offset = 0; /* not supported for local relopts yet */
i++;
}
diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c
index 733ef40ae7..977babff54 100644
--- a/src/backend/commands/vacuum.c
+++ b/src/backend/commands/vacuum.c
@@ -2221,9 +2221,9 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams params,
{
StdRdOptions *opts = (StdRdOptions *) rel->rd_options;
- if (opts && opts->vacuum_truncate_set)
+ if (opts && opts->vacuum_truncate != TERNARY_UNSET)
{
- if (opts->vacuum_truncate)
+ if (opts->vacuum_truncate == TERNARY_TRUE)
params.truncate = VACOPTVALUE_ENABLED;
else
params.truncate = VACOPTVALUE_DISABLED;
diff --git a/src/include/access/reloptions.h b/src/include/access/reloptions.h
index a604a4702c..a436697658 100644
--- a/src/include/access/reloptions.h
+++ b/src/include/access/reloptions.h
@@ -29,6 +29,7 @@
typedef enum relopt_type
{
RELOPT_TYPE_BOOL,
+ RELOPT_TYPE_TERNARY, /* on, off, unset */
RELOPT_TYPE_INT,
RELOPT_TYPE_REAL,
RELOPT_TYPE_ENUM,
@@ -80,6 +81,7 @@ typedef struct relopt_value
union
{
bool bool_val;
+ ternary ternary_val;
int int_val;
double real_val;
int enum_val;
@@ -94,6 +96,12 @@ typedef struct relopt_bool
bool default_val;
} relopt_bool;
+typedef struct relopt_rernary
+{
+ relopt_gen gen;
+ int default_val;
+} relopt_ternary;
+
typedef struct relopt_int
{
relopt_gen gen;
@@ -152,19 +160,6 @@ typedef struct
const char *optname; /* option's name */
relopt_type opttype; /* option's datatype */
int offset; /* offset of field in result struct */
-
- /*
- * isset_offset is an optional offset of a field in the result struct that
- * stores whether the option is explicitly set for the relation or if it
- * just picked up the default value. In most cases, this can be
- * accomplished by giving the reloption a special out-of-range default
- * value (e.g., some integer reloptions use -2), but this isn't always
- * possible. For example, a Boolean reloption cannot be given an
- * out-of-range default, so we need another way to discover the source of
- * its value. This offset is only used if given a value greater than
- * zero.
- */
- int isset_offset;
} relopt_parse_elt;
/* Local reloption definition */
@@ -195,6 +190,8 @@ typedef struct local_relopts
extern relopt_kind add_reloption_kind(void);
extern void add_bool_reloption(bits32 kinds, const char *name, const char *desc,
bool default_val, LOCKMODE lockmode);
+extern void add_ternary_reloption(bits32 kinds, const char *name,
+ const char *desc, int default_val, LOCKMODE lockmode);
extern void add_int_reloption(bits32 kinds, const char *name, const char *desc,
int default_val, int min_val, int max_val,
LOCKMODE lockmode);
@@ -214,6 +211,9 @@ extern void register_reloptions_validator(local_relopts *relopts,
extern void add_local_bool_reloption(local_relopts *relopts, const char *name,
const char *desc, bool default_val,
int offset);
+extern void add_local_ternary_reloption(local_relopts *relopts,
+ const char *name, const char *desc,
+ ternary default_val, int offset);
extern void add_local_int_reloption(local_relopts *relopts, const char *name,
const char *desc, int default_val,
int min_val, int max_val, int offset);
diff --git a/src/include/c.h b/src/include/c.h
index 39022f8a9d..9c59ccccf8 100644
--- a/src/include/c.h
+++ b/src/include/c.h
@@ -488,6 +488,22 @@ typedef void (*pg_funcptr_t) (void);
#include <stdbool.h>
+/*
+ * ternary
+ * Boolean value with an extrea "unset" option
+ *
+ * Ternary data type is used in relation options that can be "true", "false" or
+ * "unset". Since relation options are used deep inside the PostgreSQL code,
+ * this type is declared globally.
+*/
+
+typedef enum ternary
+{
+ TERNARY_FALSE = 0,
+ TERNARY_TRUE = 1,
+ TERNARY_UNSET = -1
+} ternary;
+
/* ----------------------------------------------------------------
* Section 3: standard system types
diff --git a/src/include/utils/rel.h b/src/include/utils/rel.h
index b552359915..7346d618f9 100644
--- a/src/include/utils/rel.h
+++ b/src/include/utils/rel.h
@@ -346,8 +346,7 @@ typedef struct StdRdOptions
bool user_catalog_table; /* use as an additional catalog relation */
int parallel_workers; /* max number of parallel workers */
StdRdOptIndexCleanup vacuum_index_cleanup; /* controls index vacuuming */
- bool vacuum_truncate; /* enables vacuum to truncate a relation */
- bool vacuum_truncate_set; /* whether vacuum_truncate is set */
+ ternary vacuum_truncate; /* enables vacuum to truncate a relation */
/*
* Fraction of pages in a relation that vacuum can eagerly scan and fail
--
2.39.2
--nextPart5129932.5fSG56mABF
Content-Disposition: attachment;
filename="v2-0003-Add-alias-to-be-used-as-unset-state.patch"
Content-Transfer-Encoding: 7Bit
Content-Type: text/x-patch; charset="unicode-2-0-utf-8";
name="v2-0003-Add-alias-to-be-used-as-unset-state.patch"
^ permalink raw reply [nested|flat] 868+ messages in thread
* [PATCH v2a 2/4] Introduce ternary reloptions
@ 2025-09-04 14:41 Nikolay Shaplov <[email protected]>
0 siblings, 0 replies; 868+ messages in thread
From: Nikolay Shaplov @ 2025-09-04 14:41 UTC (permalink / raw)
Introduce ternary reloption as a replacement for current `vacuum_truncate`
implementation. Remove `vacuum_truncate_set` additional flag and using
`TERNARY_UNSET` value instead.
---
src/backend/access/common/reloptions.c | 129 ++++++++++++++++++++-----
src/backend/commands/vacuum.c | 4 +-
src/include/access/reloptions.h | 26 ++---
src/include/c.h | 16 +++
src/include/utils/rel.h | 3 +-
5 files changed, 135 insertions(+), 43 deletions(-)
diff --git a/src/backend/access/common/reloptions.c b/src/backend/access/common/reloptions.c
index 0af3fea68f..f543f11001 100644
--- a/src/backend/access/common/reloptions.c
+++ b/src/backend/access/common/reloptions.c
@@ -40,9 +40,9 @@
*
* To add an option:
*
- * (i) decide on a type (bool, integer, real, enum, string), name, default
- * value, upper and lower bounds (if applicable); for strings, consider a
- * validation routine.
+ * (i) decide on a type (bool, ternary, integer, real, enum, string), name,
+ * default value, upper and lower bounds (if applicable); for strings,
+ * consider a validation routine.
* (ii) add a record below (or use add_<type>_reloption).
* (iii) add it to the appropriate options struct (perhaps StdRdOptions)
* (iv) add it to the appropriate handling routine (perhaps
@@ -147,15 +147,6 @@ static relopt_bool boolRelOpts[] =
},
false
},
- {
- {
- "vacuum_truncate",
- "Enables vacuum to truncate empty pages at the end of this table",
- RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
- ShareUpdateExclusiveLock
- },
- true
- },
{
{
"deduplicate_items",
@@ -170,6 +161,21 @@ static relopt_bool boolRelOpts[] =
{{NULL}}
};
+static relopt_ternary ternaryRelOpts[] =
+{
+ {
+ {
+ "vacuum_truncate",
+ "Enables vacuum to truncate empty pages at the end of this table",
+ RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
+ ShareUpdateExclusiveLock
+ },
+ TERNARY_UNSET
+ },
+ /* list terminator */
+ {{NULL}}
+};
+
static relopt_int intRelOpts[] =
{
{
@@ -600,6 +606,13 @@ initialize_reloptions(void)
boolRelOpts[i].gen.lockmode));
j++;
}
+ for (i = 0; ternaryRelOpts[i].gen.name; i++)
+ {
+ Assert(DoLockModesConflict(ternaryRelOpts[i].gen.lockmode,
+ ternaryRelOpts[i].gen.lockmode));
+ j++;
+ }
+
for (i = 0; intRelOpts[i].gen.name; i++)
{
Assert(DoLockModesConflict(intRelOpts[i].gen.lockmode,
@@ -640,6 +653,14 @@ initialize_reloptions(void)
j++;
}
+ for (i = 0; ternaryRelOpts[i].gen.name; i++)
+ {
+ relOpts[j] = &ternaryRelOpts[i].gen;
+ relOpts[j]->type = RELOPT_TYPE_TERNARY;
+ relOpts[j]->namelen = strlen(relOpts[j]->name);
+ j++;
+ }
+
for (i = 0; intRelOpts[i].gen.name; i++)
{
relOpts[j] = &intRelOpts[i].gen;
@@ -800,6 +821,9 @@ allocate_reloption(bits32 kinds, int type, const char *name, const char *desc,
case RELOPT_TYPE_BOOL:
size = sizeof(relopt_bool);
break;
+ case RELOPT_TYPE_TERNARY:
+ size = sizeof(relopt_ternary);
+ break;
case RELOPT_TYPE_INT:
size = sizeof(relopt_int);
break;
@@ -883,6 +907,54 @@ add_local_bool_reloption(local_relopts *relopts, const char *name,
add_local_reloption(relopts, (relopt_gen *) newoption, offset);
}
+/*
+ * init_ternary_reloption
+ * Allocate and initialize a new ternary reloption
+ */
+static relopt_ternary *
+init_ternary_reloption(bits32 kinds, const char *name, const char *desc,
+ ternary default_val, LOCKMODE lockmode)
+{
+ relopt_ternary *newoption;
+
+ newoption = (relopt_ternary *) allocate_reloption(kinds,
+ RELOPT_TYPE_TERNARY, name, desc, lockmode);
+ newoption->default_val = default_val;
+
+ return newoption;
+}
+
+/*
+ * add_ternary_reloption
+ * Add a new ternary reloption
+ */
+void
+add_ternary_reloption(bits32 kinds, const char *name, const char *desc,
+ ternary default_val, LOCKMODE lockmode)
+{
+ relopt_ternary *newoption = init_ternary_reloption(kinds, name, desc,
+ default_val, lockmode);
+
+ add_reloption((relopt_gen *) newoption);
+}
+
+/*
+ * add_local_ternary_reloption
+ * Add a new ternary local reloption
+ *
+ * 'offset' is offset of ternary-typed field.
+ */
+void
+add_local_ternary_reloption(local_relopts *relopts, const char *name,
+ const char *desc, ternary default_val,
+ int offset)
+{
+ relopt_ternary *newoption = init_ternary_reloption(RELOPT_KIND_LOCAL,
+ name, desc,
+ default_val, 0);
+
+ add_local_reloption(relopts, (relopt_gen *) newoption, offset);
+}
/*
* init_real_reloption
@@ -1617,6 +1689,18 @@ parse_one_reloption(relopt_value *option, char *text_str, int text_len,
option->gen->name, value)));
}
break;
+ case RELOPT_TYPE_TERNARY:
+ {
+ bool b;
+ parsed = parse_bool(value, &b);
+ option->values.ternary_val = b ? TERNARY_TRUE : TERNARY_FALSE;
+ if (validate && !parsed)
+ ereport(ERROR,
+ (errcode(ERRCODE_INVALID_PARAMETER_VALUE),
+ errmsg("invalid value for ternary option \"%s\": %s",
+ option->gen->name, value)));
+ }
+ break;
case RELOPT_TYPE_INT:
{
relopt_int *optint = (relopt_int *) option->gen;
@@ -1780,17 +1864,6 @@ fillRelOptions(void *rdopts, Size basesize,
char *itempos = ((char *) rdopts) + elems[j].offset;
char *string_val;
- /*
- * If isset_offset is provided, store whether the reloption is
- * set there.
- */
- if (elems[j].isset_offset > 0)
- {
- char *setpos = ((char *) rdopts) + elems[j].isset_offset;
-
- *(bool *) setpos = options[i].isset;
- }
-
switch (options[i].gen->type)
{
case RELOPT_TYPE_BOOL:
@@ -1798,6 +1871,11 @@ fillRelOptions(void *rdopts, Size basesize,
options[i].values.bool_val :
((relopt_bool *) options[i].gen)->default_val;
break;
+ case RELOPT_TYPE_TERNARY:
+ *(ternary *) itempos = options[i].isset ?
+ options[i].values.ternary_val :
+ ((relopt_ternary *) options[i].gen)->default_val;
+ break;
case RELOPT_TYPE_INT:
*(int *) itempos = options[i].isset ?
options[i].values.int_val :
@@ -1912,8 +1990,8 @@ default_reloptions(Datum reloptions, bool validate, relopt_kind kind)
offsetof(StdRdOptions, parallel_workers)},
{"vacuum_index_cleanup", RELOPT_TYPE_ENUM,
offsetof(StdRdOptions, vacuum_index_cleanup)},
- {"vacuum_truncate", RELOPT_TYPE_BOOL,
- offsetof(StdRdOptions, vacuum_truncate), offsetof(StdRdOptions, vacuum_truncate_set)},
+ {"vacuum_truncate", RELOPT_TYPE_TERNARY,
+ offsetof(StdRdOptions, vacuum_truncate)},
{"vacuum_max_eager_freeze_failure_rate", RELOPT_TYPE_REAL,
offsetof(StdRdOptions, vacuum_max_eager_freeze_failure_rate)}
};
@@ -1993,7 +2071,6 @@ build_local_reloptions(local_relopts *relopts, Datum options, bool validate)
elems[i].optname = opt->option->name;
elems[i].opttype = opt->option->type;
elems[i].offset = opt->offset;
- elems[i].isset_offset = 0; /* not supported for local relopts yet */
i++;
}
diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c
index 733ef40ae7..977babff54 100644
--- a/src/backend/commands/vacuum.c
+++ b/src/backend/commands/vacuum.c
@@ -2221,9 +2221,9 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams params,
{
StdRdOptions *opts = (StdRdOptions *) rel->rd_options;
- if (opts && opts->vacuum_truncate_set)
+ if (opts && opts->vacuum_truncate != TERNARY_UNSET)
{
- if (opts->vacuum_truncate)
+ if (opts->vacuum_truncate == TERNARY_TRUE)
params.truncate = VACOPTVALUE_ENABLED;
else
params.truncate = VACOPTVALUE_DISABLED;
diff --git a/src/include/access/reloptions.h b/src/include/access/reloptions.h
index a604a4702c..a436697658 100644
--- a/src/include/access/reloptions.h
+++ b/src/include/access/reloptions.h
@@ -29,6 +29,7 @@
typedef enum relopt_type
{
RELOPT_TYPE_BOOL,
+ RELOPT_TYPE_TERNARY, /* on, off, unset */
RELOPT_TYPE_INT,
RELOPT_TYPE_REAL,
RELOPT_TYPE_ENUM,
@@ -80,6 +81,7 @@ typedef struct relopt_value
union
{
bool bool_val;
+ ternary ternary_val;
int int_val;
double real_val;
int enum_val;
@@ -94,6 +96,12 @@ typedef struct relopt_bool
bool default_val;
} relopt_bool;
+typedef struct relopt_rernary
+{
+ relopt_gen gen;
+ int default_val;
+} relopt_ternary;
+
typedef struct relopt_int
{
relopt_gen gen;
@@ -152,19 +160,6 @@ typedef struct
const char *optname; /* option's name */
relopt_type opttype; /* option's datatype */
int offset; /* offset of field in result struct */
-
- /*
- * isset_offset is an optional offset of a field in the result struct that
- * stores whether the option is explicitly set for the relation or if it
- * just picked up the default value. In most cases, this can be
- * accomplished by giving the reloption a special out-of-range default
- * value (e.g., some integer reloptions use -2), but this isn't always
- * possible. For example, a Boolean reloption cannot be given an
- * out-of-range default, so we need another way to discover the source of
- * its value. This offset is only used if given a value greater than
- * zero.
- */
- int isset_offset;
} relopt_parse_elt;
/* Local reloption definition */
@@ -195,6 +190,8 @@ typedef struct local_relopts
extern relopt_kind add_reloption_kind(void);
extern void add_bool_reloption(bits32 kinds, const char *name, const char *desc,
bool default_val, LOCKMODE lockmode);
+extern void add_ternary_reloption(bits32 kinds, const char *name,
+ const char *desc, int default_val, LOCKMODE lockmode);
extern void add_int_reloption(bits32 kinds, const char *name, const char *desc,
int default_val, int min_val, int max_val,
LOCKMODE lockmode);
@@ -214,6 +211,9 @@ extern void register_reloptions_validator(local_relopts *relopts,
extern void add_local_bool_reloption(local_relopts *relopts, const char *name,
const char *desc, bool default_val,
int offset);
+extern void add_local_ternary_reloption(local_relopts *relopts,
+ const char *name, const char *desc,
+ ternary default_val, int offset);
extern void add_local_int_reloption(local_relopts *relopts, const char *name,
const char *desc, int default_val,
int min_val, int max_val, int offset);
diff --git a/src/include/c.h b/src/include/c.h
index 39022f8a9d..9c59ccccf8 100644
--- a/src/include/c.h
+++ b/src/include/c.h
@@ -488,6 +488,22 @@ typedef void (*pg_funcptr_t) (void);
#include <stdbool.h>
+/*
+ * ternary
+ * Boolean value with an extrea "unset" option
+ *
+ * Ternary data type is used in relation options that can be "true", "false" or
+ * "unset". Since relation options are used deep inside the PostgreSQL code,
+ * this type is declared globally.
+*/
+
+typedef enum ternary
+{
+ TERNARY_FALSE = 0,
+ TERNARY_TRUE = 1,
+ TERNARY_UNSET = -1
+} ternary;
+
/* ----------------------------------------------------------------
* Section 3: standard system types
diff --git a/src/include/utils/rel.h b/src/include/utils/rel.h
index b552359915..7346d618f9 100644
--- a/src/include/utils/rel.h
+++ b/src/include/utils/rel.h
@@ -346,8 +346,7 @@ typedef struct StdRdOptions
bool user_catalog_table; /* use as an additional catalog relation */
int parallel_workers; /* max number of parallel workers */
StdRdOptIndexCleanup vacuum_index_cleanup; /* controls index vacuuming */
- bool vacuum_truncate; /* enables vacuum to truncate a relation */
- bool vacuum_truncate_set; /* whether vacuum_truncate is set */
+ ternary vacuum_truncate; /* enables vacuum to truncate a relation */
/*
* Fraction of pages in a relation that vacuum can eagerly scan and fail
--
2.39.2
--nextPart4155600.3ZeAukHxDK
Content-Disposition: attachment;
filename="v2a-0003-Add-alias-to-be-used-as-unset-state.patch"
Content-Transfer-Encoding: 7Bit
Content-Type: text/x-patch; charset="unicode-2-0-utf-8";
name="v2a-0003-Add-alias-to-be-used-as-unset-state.patch"
^ permalink raw reply [nested|flat] 868+ messages in thread
* [PATCH v2a 2/4] Introduce ternary reloptions
@ 2025-09-04 14:41 Nikolay Shaplov <[email protected]>
0 siblings, 0 replies; 868+ messages in thread
From: Nikolay Shaplov @ 2025-09-04 14:41 UTC (permalink / raw)
Introduce ternary reloption as a replacement for current `vacuum_truncate`
implementation. Remove `vacuum_truncate_set` additional flag and using
`TERNARY_UNSET` value instead.
---
src/backend/access/common/reloptions.c | 129 ++++++++++++++++++++-----
src/backend/commands/vacuum.c | 4 +-
src/include/access/reloptions.h | 26 ++---
src/include/c.h | 16 +++
src/include/utils/rel.h | 3 +-
5 files changed, 135 insertions(+), 43 deletions(-)
diff --git a/src/backend/access/common/reloptions.c b/src/backend/access/common/reloptions.c
index 0af3fea68f..f543f11001 100644
--- a/src/backend/access/common/reloptions.c
+++ b/src/backend/access/common/reloptions.c
@@ -40,9 +40,9 @@
*
* To add an option:
*
- * (i) decide on a type (bool, integer, real, enum, string), name, default
- * value, upper and lower bounds (if applicable); for strings, consider a
- * validation routine.
+ * (i) decide on a type (bool, ternary, integer, real, enum, string), name,
+ * default value, upper and lower bounds (if applicable); for strings,
+ * consider a validation routine.
* (ii) add a record below (or use add_<type>_reloption).
* (iii) add it to the appropriate options struct (perhaps StdRdOptions)
* (iv) add it to the appropriate handling routine (perhaps
@@ -147,15 +147,6 @@ static relopt_bool boolRelOpts[] =
},
false
},
- {
- {
- "vacuum_truncate",
- "Enables vacuum to truncate empty pages at the end of this table",
- RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
- ShareUpdateExclusiveLock
- },
- true
- },
{
{
"deduplicate_items",
@@ -170,6 +161,21 @@ static relopt_bool boolRelOpts[] =
{{NULL}}
};
+static relopt_ternary ternaryRelOpts[] =
+{
+ {
+ {
+ "vacuum_truncate",
+ "Enables vacuum to truncate empty pages at the end of this table",
+ RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
+ ShareUpdateExclusiveLock
+ },
+ TERNARY_UNSET
+ },
+ /* list terminator */
+ {{NULL}}
+};
+
static relopt_int intRelOpts[] =
{
{
@@ -600,6 +606,13 @@ initialize_reloptions(void)
boolRelOpts[i].gen.lockmode));
j++;
}
+ for (i = 0; ternaryRelOpts[i].gen.name; i++)
+ {
+ Assert(DoLockModesConflict(ternaryRelOpts[i].gen.lockmode,
+ ternaryRelOpts[i].gen.lockmode));
+ j++;
+ }
+
for (i = 0; intRelOpts[i].gen.name; i++)
{
Assert(DoLockModesConflict(intRelOpts[i].gen.lockmode,
@@ -640,6 +653,14 @@ initialize_reloptions(void)
j++;
}
+ for (i = 0; ternaryRelOpts[i].gen.name; i++)
+ {
+ relOpts[j] = &ternaryRelOpts[i].gen;
+ relOpts[j]->type = RELOPT_TYPE_TERNARY;
+ relOpts[j]->namelen = strlen(relOpts[j]->name);
+ j++;
+ }
+
for (i = 0; intRelOpts[i].gen.name; i++)
{
relOpts[j] = &intRelOpts[i].gen;
@@ -800,6 +821,9 @@ allocate_reloption(bits32 kinds, int type, const char *name, const char *desc,
case RELOPT_TYPE_BOOL:
size = sizeof(relopt_bool);
break;
+ case RELOPT_TYPE_TERNARY:
+ size = sizeof(relopt_ternary);
+ break;
case RELOPT_TYPE_INT:
size = sizeof(relopt_int);
break;
@@ -883,6 +907,54 @@ add_local_bool_reloption(local_relopts *relopts, const char *name,
add_local_reloption(relopts, (relopt_gen *) newoption, offset);
}
+/*
+ * init_ternary_reloption
+ * Allocate and initialize a new ternary reloption
+ */
+static relopt_ternary *
+init_ternary_reloption(bits32 kinds, const char *name, const char *desc,
+ ternary default_val, LOCKMODE lockmode)
+{
+ relopt_ternary *newoption;
+
+ newoption = (relopt_ternary *) allocate_reloption(kinds,
+ RELOPT_TYPE_TERNARY, name, desc, lockmode);
+ newoption->default_val = default_val;
+
+ return newoption;
+}
+
+/*
+ * add_ternary_reloption
+ * Add a new ternary reloption
+ */
+void
+add_ternary_reloption(bits32 kinds, const char *name, const char *desc,
+ ternary default_val, LOCKMODE lockmode)
+{
+ relopt_ternary *newoption = init_ternary_reloption(kinds, name, desc,
+ default_val, lockmode);
+
+ add_reloption((relopt_gen *) newoption);
+}
+
+/*
+ * add_local_ternary_reloption
+ * Add a new ternary local reloption
+ *
+ * 'offset' is offset of ternary-typed field.
+ */
+void
+add_local_ternary_reloption(local_relopts *relopts, const char *name,
+ const char *desc, ternary default_val,
+ int offset)
+{
+ relopt_ternary *newoption = init_ternary_reloption(RELOPT_KIND_LOCAL,
+ name, desc,
+ default_val, 0);
+
+ add_local_reloption(relopts, (relopt_gen *) newoption, offset);
+}
/*
* init_real_reloption
@@ -1617,6 +1689,18 @@ parse_one_reloption(relopt_value *option, char *text_str, int text_len,
option->gen->name, value)));
}
break;
+ case RELOPT_TYPE_TERNARY:
+ {
+ bool b;
+ parsed = parse_bool(value, &b);
+ option->values.ternary_val = b ? TERNARY_TRUE : TERNARY_FALSE;
+ if (validate && !parsed)
+ ereport(ERROR,
+ (errcode(ERRCODE_INVALID_PARAMETER_VALUE),
+ errmsg("invalid value for ternary option \"%s\": %s",
+ option->gen->name, value)));
+ }
+ break;
case RELOPT_TYPE_INT:
{
relopt_int *optint = (relopt_int *) option->gen;
@@ -1780,17 +1864,6 @@ fillRelOptions(void *rdopts, Size basesize,
char *itempos = ((char *) rdopts) + elems[j].offset;
char *string_val;
- /*
- * If isset_offset is provided, store whether the reloption is
- * set there.
- */
- if (elems[j].isset_offset > 0)
- {
- char *setpos = ((char *) rdopts) + elems[j].isset_offset;
-
- *(bool *) setpos = options[i].isset;
- }
-
switch (options[i].gen->type)
{
case RELOPT_TYPE_BOOL:
@@ -1798,6 +1871,11 @@ fillRelOptions(void *rdopts, Size basesize,
options[i].values.bool_val :
((relopt_bool *) options[i].gen)->default_val;
break;
+ case RELOPT_TYPE_TERNARY:
+ *(ternary *) itempos = options[i].isset ?
+ options[i].values.ternary_val :
+ ((relopt_ternary *) options[i].gen)->default_val;
+ break;
case RELOPT_TYPE_INT:
*(int *) itempos = options[i].isset ?
options[i].values.int_val :
@@ -1912,8 +1990,8 @@ default_reloptions(Datum reloptions, bool validate, relopt_kind kind)
offsetof(StdRdOptions, parallel_workers)},
{"vacuum_index_cleanup", RELOPT_TYPE_ENUM,
offsetof(StdRdOptions, vacuum_index_cleanup)},
- {"vacuum_truncate", RELOPT_TYPE_BOOL,
- offsetof(StdRdOptions, vacuum_truncate), offsetof(StdRdOptions, vacuum_truncate_set)},
+ {"vacuum_truncate", RELOPT_TYPE_TERNARY,
+ offsetof(StdRdOptions, vacuum_truncate)},
{"vacuum_max_eager_freeze_failure_rate", RELOPT_TYPE_REAL,
offsetof(StdRdOptions, vacuum_max_eager_freeze_failure_rate)}
};
@@ -1993,7 +2071,6 @@ build_local_reloptions(local_relopts *relopts, Datum options, bool validate)
elems[i].optname = opt->option->name;
elems[i].opttype = opt->option->type;
elems[i].offset = opt->offset;
- elems[i].isset_offset = 0; /* not supported for local relopts yet */
i++;
}
diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c
index 733ef40ae7..977babff54 100644
--- a/src/backend/commands/vacuum.c
+++ b/src/backend/commands/vacuum.c
@@ -2221,9 +2221,9 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams params,
{
StdRdOptions *opts = (StdRdOptions *) rel->rd_options;
- if (opts && opts->vacuum_truncate_set)
+ if (opts && opts->vacuum_truncate != TERNARY_UNSET)
{
- if (opts->vacuum_truncate)
+ if (opts->vacuum_truncate == TERNARY_TRUE)
params.truncate = VACOPTVALUE_ENABLED;
else
params.truncate = VACOPTVALUE_DISABLED;
diff --git a/src/include/access/reloptions.h b/src/include/access/reloptions.h
index a604a4702c..a436697658 100644
--- a/src/include/access/reloptions.h
+++ b/src/include/access/reloptions.h
@@ -29,6 +29,7 @@
typedef enum relopt_type
{
RELOPT_TYPE_BOOL,
+ RELOPT_TYPE_TERNARY, /* on, off, unset */
RELOPT_TYPE_INT,
RELOPT_TYPE_REAL,
RELOPT_TYPE_ENUM,
@@ -80,6 +81,7 @@ typedef struct relopt_value
union
{
bool bool_val;
+ ternary ternary_val;
int int_val;
double real_val;
int enum_val;
@@ -94,6 +96,12 @@ typedef struct relopt_bool
bool default_val;
} relopt_bool;
+typedef struct relopt_rernary
+{
+ relopt_gen gen;
+ int default_val;
+} relopt_ternary;
+
typedef struct relopt_int
{
relopt_gen gen;
@@ -152,19 +160,6 @@ typedef struct
const char *optname; /* option's name */
relopt_type opttype; /* option's datatype */
int offset; /* offset of field in result struct */
-
- /*
- * isset_offset is an optional offset of a field in the result struct that
- * stores whether the option is explicitly set for the relation or if it
- * just picked up the default value. In most cases, this can be
- * accomplished by giving the reloption a special out-of-range default
- * value (e.g., some integer reloptions use -2), but this isn't always
- * possible. For example, a Boolean reloption cannot be given an
- * out-of-range default, so we need another way to discover the source of
- * its value. This offset is only used if given a value greater than
- * zero.
- */
- int isset_offset;
} relopt_parse_elt;
/* Local reloption definition */
@@ -195,6 +190,8 @@ typedef struct local_relopts
extern relopt_kind add_reloption_kind(void);
extern void add_bool_reloption(bits32 kinds, const char *name, const char *desc,
bool default_val, LOCKMODE lockmode);
+extern void add_ternary_reloption(bits32 kinds, const char *name,
+ const char *desc, int default_val, LOCKMODE lockmode);
extern void add_int_reloption(bits32 kinds, const char *name, const char *desc,
int default_val, int min_val, int max_val,
LOCKMODE lockmode);
@@ -214,6 +211,9 @@ extern void register_reloptions_validator(local_relopts *relopts,
extern void add_local_bool_reloption(local_relopts *relopts, const char *name,
const char *desc, bool default_val,
int offset);
+extern void add_local_ternary_reloption(local_relopts *relopts,
+ const char *name, const char *desc,
+ ternary default_val, int offset);
extern void add_local_int_reloption(local_relopts *relopts, const char *name,
const char *desc, int default_val,
int min_val, int max_val, int offset);
diff --git a/src/include/c.h b/src/include/c.h
index 39022f8a9d..9c59ccccf8 100644
--- a/src/include/c.h
+++ b/src/include/c.h
@@ -488,6 +488,22 @@ typedef void (*pg_funcptr_t) (void);
#include <stdbool.h>
+/*
+ * ternary
+ * Boolean value with an extrea "unset" option
+ *
+ * Ternary data type is used in relation options that can be "true", "false" or
+ * "unset". Since relation options are used deep inside the PostgreSQL code,
+ * this type is declared globally.
+*/
+
+typedef enum ternary
+{
+ TERNARY_FALSE = 0,
+ TERNARY_TRUE = 1,
+ TERNARY_UNSET = -1
+} ternary;
+
/* ----------------------------------------------------------------
* Section 3: standard system types
diff --git a/src/include/utils/rel.h b/src/include/utils/rel.h
index b552359915..7346d618f9 100644
--- a/src/include/utils/rel.h
+++ b/src/include/utils/rel.h
@@ -346,8 +346,7 @@ typedef struct StdRdOptions
bool user_catalog_table; /* use as an additional catalog relation */
int parallel_workers; /* max number of parallel workers */
StdRdOptIndexCleanup vacuum_index_cleanup; /* controls index vacuuming */
- bool vacuum_truncate; /* enables vacuum to truncate a relation */
- bool vacuum_truncate_set; /* whether vacuum_truncate is set */
+ ternary vacuum_truncate; /* enables vacuum to truncate a relation */
/*
* Fraction of pages in a relation that vacuum can eagerly scan and fail
--
2.39.2
--nextPart4155600.3ZeAukHxDK
Content-Disposition: attachment;
filename="v2a-0003-Add-alias-to-be-used-as-unset-state.patch"
Content-Transfer-Encoding: 7Bit
Content-Type: text/x-patch; charset="unicode-2-0-utf-8";
name="v2a-0003-Add-alias-to-be-used-as-unset-state.patch"
^ permalink raw reply [nested|flat] 868+ messages in thread
* [PATCH v2a 2/4] Introduce ternary reloptions
@ 2025-09-04 14:41 Nikolay Shaplov <[email protected]>
0 siblings, 0 replies; 868+ messages in thread
From: Nikolay Shaplov @ 2025-09-04 14:41 UTC (permalink / raw)
Introduce ternary reloption as a replacement for current `vacuum_truncate`
implementation. Remove `vacuum_truncate_set` additional flag and using
`TERNARY_UNSET` value instead.
---
src/backend/access/common/reloptions.c | 129 ++++++++++++++++++++-----
src/backend/commands/vacuum.c | 4 +-
src/include/access/reloptions.h | 26 ++---
src/include/c.h | 16 +++
src/include/utils/rel.h | 3 +-
5 files changed, 135 insertions(+), 43 deletions(-)
diff --git a/src/backend/access/common/reloptions.c b/src/backend/access/common/reloptions.c
index 0af3fea68f..f543f11001 100644
--- a/src/backend/access/common/reloptions.c
+++ b/src/backend/access/common/reloptions.c
@@ -40,9 +40,9 @@
*
* To add an option:
*
- * (i) decide on a type (bool, integer, real, enum, string), name, default
- * value, upper and lower bounds (if applicable); for strings, consider a
- * validation routine.
+ * (i) decide on a type (bool, ternary, integer, real, enum, string), name,
+ * default value, upper and lower bounds (if applicable); for strings,
+ * consider a validation routine.
* (ii) add a record below (or use add_<type>_reloption).
* (iii) add it to the appropriate options struct (perhaps StdRdOptions)
* (iv) add it to the appropriate handling routine (perhaps
@@ -147,15 +147,6 @@ static relopt_bool boolRelOpts[] =
},
false
},
- {
- {
- "vacuum_truncate",
- "Enables vacuum to truncate empty pages at the end of this table",
- RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
- ShareUpdateExclusiveLock
- },
- true
- },
{
{
"deduplicate_items",
@@ -170,6 +161,21 @@ static relopt_bool boolRelOpts[] =
{{NULL}}
};
+static relopt_ternary ternaryRelOpts[] =
+{
+ {
+ {
+ "vacuum_truncate",
+ "Enables vacuum to truncate empty pages at the end of this table",
+ RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
+ ShareUpdateExclusiveLock
+ },
+ TERNARY_UNSET
+ },
+ /* list terminator */
+ {{NULL}}
+};
+
static relopt_int intRelOpts[] =
{
{
@@ -600,6 +606,13 @@ initialize_reloptions(void)
boolRelOpts[i].gen.lockmode));
j++;
}
+ for (i = 0; ternaryRelOpts[i].gen.name; i++)
+ {
+ Assert(DoLockModesConflict(ternaryRelOpts[i].gen.lockmode,
+ ternaryRelOpts[i].gen.lockmode));
+ j++;
+ }
+
for (i = 0; intRelOpts[i].gen.name; i++)
{
Assert(DoLockModesConflict(intRelOpts[i].gen.lockmode,
@@ -640,6 +653,14 @@ initialize_reloptions(void)
j++;
}
+ for (i = 0; ternaryRelOpts[i].gen.name; i++)
+ {
+ relOpts[j] = &ternaryRelOpts[i].gen;
+ relOpts[j]->type = RELOPT_TYPE_TERNARY;
+ relOpts[j]->namelen = strlen(relOpts[j]->name);
+ j++;
+ }
+
for (i = 0; intRelOpts[i].gen.name; i++)
{
relOpts[j] = &intRelOpts[i].gen;
@@ -800,6 +821,9 @@ allocate_reloption(bits32 kinds, int type, const char *name, const char *desc,
case RELOPT_TYPE_BOOL:
size = sizeof(relopt_bool);
break;
+ case RELOPT_TYPE_TERNARY:
+ size = sizeof(relopt_ternary);
+ break;
case RELOPT_TYPE_INT:
size = sizeof(relopt_int);
break;
@@ -883,6 +907,54 @@ add_local_bool_reloption(local_relopts *relopts, const char *name,
add_local_reloption(relopts, (relopt_gen *) newoption, offset);
}
+/*
+ * init_ternary_reloption
+ * Allocate and initialize a new ternary reloption
+ */
+static relopt_ternary *
+init_ternary_reloption(bits32 kinds, const char *name, const char *desc,
+ ternary default_val, LOCKMODE lockmode)
+{
+ relopt_ternary *newoption;
+
+ newoption = (relopt_ternary *) allocate_reloption(kinds,
+ RELOPT_TYPE_TERNARY, name, desc, lockmode);
+ newoption->default_val = default_val;
+
+ return newoption;
+}
+
+/*
+ * add_ternary_reloption
+ * Add a new ternary reloption
+ */
+void
+add_ternary_reloption(bits32 kinds, const char *name, const char *desc,
+ ternary default_val, LOCKMODE lockmode)
+{
+ relopt_ternary *newoption = init_ternary_reloption(kinds, name, desc,
+ default_val, lockmode);
+
+ add_reloption((relopt_gen *) newoption);
+}
+
+/*
+ * add_local_ternary_reloption
+ * Add a new ternary local reloption
+ *
+ * 'offset' is offset of ternary-typed field.
+ */
+void
+add_local_ternary_reloption(local_relopts *relopts, const char *name,
+ const char *desc, ternary default_val,
+ int offset)
+{
+ relopt_ternary *newoption = init_ternary_reloption(RELOPT_KIND_LOCAL,
+ name, desc,
+ default_val, 0);
+
+ add_local_reloption(relopts, (relopt_gen *) newoption, offset);
+}
/*
* init_real_reloption
@@ -1617,6 +1689,18 @@ parse_one_reloption(relopt_value *option, char *text_str, int text_len,
option->gen->name, value)));
}
break;
+ case RELOPT_TYPE_TERNARY:
+ {
+ bool b;
+ parsed = parse_bool(value, &b);
+ option->values.ternary_val = b ? TERNARY_TRUE : TERNARY_FALSE;
+ if (validate && !parsed)
+ ereport(ERROR,
+ (errcode(ERRCODE_INVALID_PARAMETER_VALUE),
+ errmsg("invalid value for ternary option \"%s\": %s",
+ option->gen->name, value)));
+ }
+ break;
case RELOPT_TYPE_INT:
{
relopt_int *optint = (relopt_int *) option->gen;
@@ -1780,17 +1864,6 @@ fillRelOptions(void *rdopts, Size basesize,
char *itempos = ((char *) rdopts) + elems[j].offset;
char *string_val;
- /*
- * If isset_offset is provided, store whether the reloption is
- * set there.
- */
- if (elems[j].isset_offset > 0)
- {
- char *setpos = ((char *) rdopts) + elems[j].isset_offset;
-
- *(bool *) setpos = options[i].isset;
- }
-
switch (options[i].gen->type)
{
case RELOPT_TYPE_BOOL:
@@ -1798,6 +1871,11 @@ fillRelOptions(void *rdopts, Size basesize,
options[i].values.bool_val :
((relopt_bool *) options[i].gen)->default_val;
break;
+ case RELOPT_TYPE_TERNARY:
+ *(ternary *) itempos = options[i].isset ?
+ options[i].values.ternary_val :
+ ((relopt_ternary *) options[i].gen)->default_val;
+ break;
case RELOPT_TYPE_INT:
*(int *) itempos = options[i].isset ?
options[i].values.int_val :
@@ -1912,8 +1990,8 @@ default_reloptions(Datum reloptions, bool validate, relopt_kind kind)
offsetof(StdRdOptions, parallel_workers)},
{"vacuum_index_cleanup", RELOPT_TYPE_ENUM,
offsetof(StdRdOptions, vacuum_index_cleanup)},
- {"vacuum_truncate", RELOPT_TYPE_BOOL,
- offsetof(StdRdOptions, vacuum_truncate), offsetof(StdRdOptions, vacuum_truncate_set)},
+ {"vacuum_truncate", RELOPT_TYPE_TERNARY,
+ offsetof(StdRdOptions, vacuum_truncate)},
{"vacuum_max_eager_freeze_failure_rate", RELOPT_TYPE_REAL,
offsetof(StdRdOptions, vacuum_max_eager_freeze_failure_rate)}
};
@@ -1993,7 +2071,6 @@ build_local_reloptions(local_relopts *relopts, Datum options, bool validate)
elems[i].optname = opt->option->name;
elems[i].opttype = opt->option->type;
elems[i].offset = opt->offset;
- elems[i].isset_offset = 0; /* not supported for local relopts yet */
i++;
}
diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c
index 733ef40ae7..977babff54 100644
--- a/src/backend/commands/vacuum.c
+++ b/src/backend/commands/vacuum.c
@@ -2221,9 +2221,9 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams params,
{
StdRdOptions *opts = (StdRdOptions *) rel->rd_options;
- if (opts && opts->vacuum_truncate_set)
+ if (opts && opts->vacuum_truncate != TERNARY_UNSET)
{
- if (opts->vacuum_truncate)
+ if (opts->vacuum_truncate == TERNARY_TRUE)
params.truncate = VACOPTVALUE_ENABLED;
else
params.truncate = VACOPTVALUE_DISABLED;
diff --git a/src/include/access/reloptions.h b/src/include/access/reloptions.h
index a604a4702c..a436697658 100644
--- a/src/include/access/reloptions.h
+++ b/src/include/access/reloptions.h
@@ -29,6 +29,7 @@
typedef enum relopt_type
{
RELOPT_TYPE_BOOL,
+ RELOPT_TYPE_TERNARY, /* on, off, unset */
RELOPT_TYPE_INT,
RELOPT_TYPE_REAL,
RELOPT_TYPE_ENUM,
@@ -80,6 +81,7 @@ typedef struct relopt_value
union
{
bool bool_val;
+ ternary ternary_val;
int int_val;
double real_val;
int enum_val;
@@ -94,6 +96,12 @@ typedef struct relopt_bool
bool default_val;
} relopt_bool;
+typedef struct relopt_rernary
+{
+ relopt_gen gen;
+ int default_val;
+} relopt_ternary;
+
typedef struct relopt_int
{
relopt_gen gen;
@@ -152,19 +160,6 @@ typedef struct
const char *optname; /* option's name */
relopt_type opttype; /* option's datatype */
int offset; /* offset of field in result struct */
-
- /*
- * isset_offset is an optional offset of a field in the result struct that
- * stores whether the option is explicitly set for the relation or if it
- * just picked up the default value. In most cases, this can be
- * accomplished by giving the reloption a special out-of-range default
- * value (e.g., some integer reloptions use -2), but this isn't always
- * possible. For example, a Boolean reloption cannot be given an
- * out-of-range default, so we need another way to discover the source of
- * its value. This offset is only used if given a value greater than
- * zero.
- */
- int isset_offset;
} relopt_parse_elt;
/* Local reloption definition */
@@ -195,6 +190,8 @@ typedef struct local_relopts
extern relopt_kind add_reloption_kind(void);
extern void add_bool_reloption(bits32 kinds, const char *name, const char *desc,
bool default_val, LOCKMODE lockmode);
+extern void add_ternary_reloption(bits32 kinds, const char *name,
+ const char *desc, int default_val, LOCKMODE lockmode);
extern void add_int_reloption(bits32 kinds, const char *name, const char *desc,
int default_val, int min_val, int max_val,
LOCKMODE lockmode);
@@ -214,6 +211,9 @@ extern void register_reloptions_validator(local_relopts *relopts,
extern void add_local_bool_reloption(local_relopts *relopts, const char *name,
const char *desc, bool default_val,
int offset);
+extern void add_local_ternary_reloption(local_relopts *relopts,
+ const char *name, const char *desc,
+ ternary default_val, int offset);
extern void add_local_int_reloption(local_relopts *relopts, const char *name,
const char *desc, int default_val,
int min_val, int max_val, int offset);
diff --git a/src/include/c.h b/src/include/c.h
index 39022f8a9d..9c59ccccf8 100644
--- a/src/include/c.h
+++ b/src/include/c.h
@@ -488,6 +488,22 @@ typedef void (*pg_funcptr_t) (void);
#include <stdbool.h>
+/*
+ * ternary
+ * Boolean value with an extrea "unset" option
+ *
+ * Ternary data type is used in relation options that can be "true", "false" or
+ * "unset". Since relation options are used deep inside the PostgreSQL code,
+ * this type is declared globally.
+*/
+
+typedef enum ternary
+{
+ TERNARY_FALSE = 0,
+ TERNARY_TRUE = 1,
+ TERNARY_UNSET = -1
+} ternary;
+
/* ----------------------------------------------------------------
* Section 3: standard system types
diff --git a/src/include/utils/rel.h b/src/include/utils/rel.h
index b552359915..7346d618f9 100644
--- a/src/include/utils/rel.h
+++ b/src/include/utils/rel.h
@@ -346,8 +346,7 @@ typedef struct StdRdOptions
bool user_catalog_table; /* use as an additional catalog relation */
int parallel_workers; /* max number of parallel workers */
StdRdOptIndexCleanup vacuum_index_cleanup; /* controls index vacuuming */
- bool vacuum_truncate; /* enables vacuum to truncate a relation */
- bool vacuum_truncate_set; /* whether vacuum_truncate is set */
+ ternary vacuum_truncate; /* enables vacuum to truncate a relation */
/*
* Fraction of pages in a relation that vacuum can eagerly scan and fail
--
2.39.2
--nextPart4155600.3ZeAukHxDK
Content-Disposition: attachment;
filename="v2a-0003-Add-alias-to-be-used-as-unset-state.patch"
Content-Transfer-Encoding: 7Bit
Content-Type: text/x-patch; charset="unicode-2-0-utf-8";
name="v2a-0003-Add-alias-to-be-used-as-unset-state.patch"
^ permalink raw reply [nested|flat] 868+ messages in thread
* [PATCH v2a 2/4] Introduce ternary reloptions
@ 2025-09-04 14:41 Nikolay Shaplov <[email protected]>
0 siblings, 0 replies; 868+ messages in thread
From: Nikolay Shaplov @ 2025-09-04 14:41 UTC (permalink / raw)
Introduce ternary reloption as a replacement for current `vacuum_truncate`
implementation. Remove `vacuum_truncate_set` additional flag and using
`TERNARY_UNSET` value instead.
---
src/backend/access/common/reloptions.c | 129 ++++++++++++++++++++-----
src/backend/commands/vacuum.c | 4 +-
src/include/access/reloptions.h | 26 ++---
src/include/c.h | 16 +++
src/include/utils/rel.h | 3 +-
5 files changed, 135 insertions(+), 43 deletions(-)
diff --git a/src/backend/access/common/reloptions.c b/src/backend/access/common/reloptions.c
index 0af3fea68f..f543f11001 100644
--- a/src/backend/access/common/reloptions.c
+++ b/src/backend/access/common/reloptions.c
@@ -40,9 +40,9 @@
*
* To add an option:
*
- * (i) decide on a type (bool, integer, real, enum, string), name, default
- * value, upper and lower bounds (if applicable); for strings, consider a
- * validation routine.
+ * (i) decide on a type (bool, ternary, integer, real, enum, string), name,
+ * default value, upper and lower bounds (if applicable); for strings,
+ * consider a validation routine.
* (ii) add a record below (or use add_<type>_reloption).
* (iii) add it to the appropriate options struct (perhaps StdRdOptions)
* (iv) add it to the appropriate handling routine (perhaps
@@ -147,15 +147,6 @@ static relopt_bool boolRelOpts[] =
},
false
},
- {
- {
- "vacuum_truncate",
- "Enables vacuum to truncate empty pages at the end of this table",
- RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
- ShareUpdateExclusiveLock
- },
- true
- },
{
{
"deduplicate_items",
@@ -170,6 +161,21 @@ static relopt_bool boolRelOpts[] =
{{NULL}}
};
+static relopt_ternary ternaryRelOpts[] =
+{
+ {
+ {
+ "vacuum_truncate",
+ "Enables vacuum to truncate empty pages at the end of this table",
+ RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
+ ShareUpdateExclusiveLock
+ },
+ TERNARY_UNSET
+ },
+ /* list terminator */
+ {{NULL}}
+};
+
static relopt_int intRelOpts[] =
{
{
@@ -600,6 +606,13 @@ initialize_reloptions(void)
boolRelOpts[i].gen.lockmode));
j++;
}
+ for (i = 0; ternaryRelOpts[i].gen.name; i++)
+ {
+ Assert(DoLockModesConflict(ternaryRelOpts[i].gen.lockmode,
+ ternaryRelOpts[i].gen.lockmode));
+ j++;
+ }
+
for (i = 0; intRelOpts[i].gen.name; i++)
{
Assert(DoLockModesConflict(intRelOpts[i].gen.lockmode,
@@ -640,6 +653,14 @@ initialize_reloptions(void)
j++;
}
+ for (i = 0; ternaryRelOpts[i].gen.name; i++)
+ {
+ relOpts[j] = &ternaryRelOpts[i].gen;
+ relOpts[j]->type = RELOPT_TYPE_TERNARY;
+ relOpts[j]->namelen = strlen(relOpts[j]->name);
+ j++;
+ }
+
for (i = 0; intRelOpts[i].gen.name; i++)
{
relOpts[j] = &intRelOpts[i].gen;
@@ -800,6 +821,9 @@ allocate_reloption(bits32 kinds, int type, const char *name, const char *desc,
case RELOPT_TYPE_BOOL:
size = sizeof(relopt_bool);
break;
+ case RELOPT_TYPE_TERNARY:
+ size = sizeof(relopt_ternary);
+ break;
case RELOPT_TYPE_INT:
size = sizeof(relopt_int);
break;
@@ -883,6 +907,54 @@ add_local_bool_reloption(local_relopts *relopts, const char *name,
add_local_reloption(relopts, (relopt_gen *) newoption, offset);
}
+/*
+ * init_ternary_reloption
+ * Allocate and initialize a new ternary reloption
+ */
+static relopt_ternary *
+init_ternary_reloption(bits32 kinds, const char *name, const char *desc,
+ ternary default_val, LOCKMODE lockmode)
+{
+ relopt_ternary *newoption;
+
+ newoption = (relopt_ternary *) allocate_reloption(kinds,
+ RELOPT_TYPE_TERNARY, name, desc, lockmode);
+ newoption->default_val = default_val;
+
+ return newoption;
+}
+
+/*
+ * add_ternary_reloption
+ * Add a new ternary reloption
+ */
+void
+add_ternary_reloption(bits32 kinds, const char *name, const char *desc,
+ ternary default_val, LOCKMODE lockmode)
+{
+ relopt_ternary *newoption = init_ternary_reloption(kinds, name, desc,
+ default_val, lockmode);
+
+ add_reloption((relopt_gen *) newoption);
+}
+
+/*
+ * add_local_ternary_reloption
+ * Add a new ternary local reloption
+ *
+ * 'offset' is offset of ternary-typed field.
+ */
+void
+add_local_ternary_reloption(local_relopts *relopts, const char *name,
+ const char *desc, ternary default_val,
+ int offset)
+{
+ relopt_ternary *newoption = init_ternary_reloption(RELOPT_KIND_LOCAL,
+ name, desc,
+ default_val, 0);
+
+ add_local_reloption(relopts, (relopt_gen *) newoption, offset);
+}
/*
* init_real_reloption
@@ -1617,6 +1689,18 @@ parse_one_reloption(relopt_value *option, char *text_str, int text_len,
option->gen->name, value)));
}
break;
+ case RELOPT_TYPE_TERNARY:
+ {
+ bool b;
+ parsed = parse_bool(value, &b);
+ option->values.ternary_val = b ? TERNARY_TRUE : TERNARY_FALSE;
+ if (validate && !parsed)
+ ereport(ERROR,
+ (errcode(ERRCODE_INVALID_PARAMETER_VALUE),
+ errmsg("invalid value for ternary option \"%s\": %s",
+ option->gen->name, value)));
+ }
+ break;
case RELOPT_TYPE_INT:
{
relopt_int *optint = (relopt_int *) option->gen;
@@ -1780,17 +1864,6 @@ fillRelOptions(void *rdopts, Size basesize,
char *itempos = ((char *) rdopts) + elems[j].offset;
char *string_val;
- /*
- * If isset_offset is provided, store whether the reloption is
- * set there.
- */
- if (elems[j].isset_offset > 0)
- {
- char *setpos = ((char *) rdopts) + elems[j].isset_offset;
-
- *(bool *) setpos = options[i].isset;
- }
-
switch (options[i].gen->type)
{
case RELOPT_TYPE_BOOL:
@@ -1798,6 +1871,11 @@ fillRelOptions(void *rdopts, Size basesize,
options[i].values.bool_val :
((relopt_bool *) options[i].gen)->default_val;
break;
+ case RELOPT_TYPE_TERNARY:
+ *(ternary *) itempos = options[i].isset ?
+ options[i].values.ternary_val :
+ ((relopt_ternary *) options[i].gen)->default_val;
+ break;
case RELOPT_TYPE_INT:
*(int *) itempos = options[i].isset ?
options[i].values.int_val :
@@ -1912,8 +1990,8 @@ default_reloptions(Datum reloptions, bool validate, relopt_kind kind)
offsetof(StdRdOptions, parallel_workers)},
{"vacuum_index_cleanup", RELOPT_TYPE_ENUM,
offsetof(StdRdOptions, vacuum_index_cleanup)},
- {"vacuum_truncate", RELOPT_TYPE_BOOL,
- offsetof(StdRdOptions, vacuum_truncate), offsetof(StdRdOptions, vacuum_truncate_set)},
+ {"vacuum_truncate", RELOPT_TYPE_TERNARY,
+ offsetof(StdRdOptions, vacuum_truncate)},
{"vacuum_max_eager_freeze_failure_rate", RELOPT_TYPE_REAL,
offsetof(StdRdOptions, vacuum_max_eager_freeze_failure_rate)}
};
@@ -1993,7 +2071,6 @@ build_local_reloptions(local_relopts *relopts, Datum options, bool validate)
elems[i].optname = opt->option->name;
elems[i].opttype = opt->option->type;
elems[i].offset = opt->offset;
- elems[i].isset_offset = 0; /* not supported for local relopts yet */
i++;
}
diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c
index 733ef40ae7..977babff54 100644
--- a/src/backend/commands/vacuum.c
+++ b/src/backend/commands/vacuum.c
@@ -2221,9 +2221,9 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams params,
{
StdRdOptions *opts = (StdRdOptions *) rel->rd_options;
- if (opts && opts->vacuum_truncate_set)
+ if (opts && opts->vacuum_truncate != TERNARY_UNSET)
{
- if (opts->vacuum_truncate)
+ if (opts->vacuum_truncate == TERNARY_TRUE)
params.truncate = VACOPTVALUE_ENABLED;
else
params.truncate = VACOPTVALUE_DISABLED;
diff --git a/src/include/access/reloptions.h b/src/include/access/reloptions.h
index a604a4702c..a436697658 100644
--- a/src/include/access/reloptions.h
+++ b/src/include/access/reloptions.h
@@ -29,6 +29,7 @@
typedef enum relopt_type
{
RELOPT_TYPE_BOOL,
+ RELOPT_TYPE_TERNARY, /* on, off, unset */
RELOPT_TYPE_INT,
RELOPT_TYPE_REAL,
RELOPT_TYPE_ENUM,
@@ -80,6 +81,7 @@ typedef struct relopt_value
union
{
bool bool_val;
+ ternary ternary_val;
int int_val;
double real_val;
int enum_val;
@@ -94,6 +96,12 @@ typedef struct relopt_bool
bool default_val;
} relopt_bool;
+typedef struct relopt_rernary
+{
+ relopt_gen gen;
+ int default_val;
+} relopt_ternary;
+
typedef struct relopt_int
{
relopt_gen gen;
@@ -152,19 +160,6 @@ typedef struct
const char *optname; /* option's name */
relopt_type opttype; /* option's datatype */
int offset; /* offset of field in result struct */
-
- /*
- * isset_offset is an optional offset of a field in the result struct that
- * stores whether the option is explicitly set for the relation or if it
- * just picked up the default value. In most cases, this can be
- * accomplished by giving the reloption a special out-of-range default
- * value (e.g., some integer reloptions use -2), but this isn't always
- * possible. For example, a Boolean reloption cannot be given an
- * out-of-range default, so we need another way to discover the source of
- * its value. This offset is only used if given a value greater than
- * zero.
- */
- int isset_offset;
} relopt_parse_elt;
/* Local reloption definition */
@@ -195,6 +190,8 @@ typedef struct local_relopts
extern relopt_kind add_reloption_kind(void);
extern void add_bool_reloption(bits32 kinds, const char *name, const char *desc,
bool default_val, LOCKMODE lockmode);
+extern void add_ternary_reloption(bits32 kinds, const char *name,
+ const char *desc, int default_val, LOCKMODE lockmode);
extern void add_int_reloption(bits32 kinds, const char *name, const char *desc,
int default_val, int min_val, int max_val,
LOCKMODE lockmode);
@@ -214,6 +211,9 @@ extern void register_reloptions_validator(local_relopts *relopts,
extern void add_local_bool_reloption(local_relopts *relopts, const char *name,
const char *desc, bool default_val,
int offset);
+extern void add_local_ternary_reloption(local_relopts *relopts,
+ const char *name, const char *desc,
+ ternary default_val, int offset);
extern void add_local_int_reloption(local_relopts *relopts, const char *name,
const char *desc, int default_val,
int min_val, int max_val, int offset);
diff --git a/src/include/c.h b/src/include/c.h
index 39022f8a9d..9c59ccccf8 100644
--- a/src/include/c.h
+++ b/src/include/c.h
@@ -488,6 +488,22 @@ typedef void (*pg_funcptr_t) (void);
#include <stdbool.h>
+/*
+ * ternary
+ * Boolean value with an extrea "unset" option
+ *
+ * Ternary data type is used in relation options that can be "true", "false" or
+ * "unset". Since relation options are used deep inside the PostgreSQL code,
+ * this type is declared globally.
+*/
+
+typedef enum ternary
+{
+ TERNARY_FALSE = 0,
+ TERNARY_TRUE = 1,
+ TERNARY_UNSET = -1
+} ternary;
+
/* ----------------------------------------------------------------
* Section 3: standard system types
diff --git a/src/include/utils/rel.h b/src/include/utils/rel.h
index b552359915..7346d618f9 100644
--- a/src/include/utils/rel.h
+++ b/src/include/utils/rel.h
@@ -346,8 +346,7 @@ typedef struct StdRdOptions
bool user_catalog_table; /* use as an additional catalog relation */
int parallel_workers; /* max number of parallel workers */
StdRdOptIndexCleanup vacuum_index_cleanup; /* controls index vacuuming */
- bool vacuum_truncate; /* enables vacuum to truncate a relation */
- bool vacuum_truncate_set; /* whether vacuum_truncate is set */
+ ternary vacuum_truncate; /* enables vacuum to truncate a relation */
/*
* Fraction of pages in a relation that vacuum can eagerly scan and fail
--
2.39.2
--nextPart4155600.3ZeAukHxDK
Content-Disposition: attachment;
filename="v2a-0003-Add-alias-to-be-used-as-unset-state.patch"
Content-Transfer-Encoding: 7Bit
Content-Type: text/x-patch; charset="unicode-2-0-utf-8";
name="v2a-0003-Add-alias-to-be-used-as-unset-state.patch"
^ permalink raw reply [nested|flat] 868+ messages in thread
* [PATCH v2 2/4] Introduce ternary reloptions
@ 2025-09-04 14:41 Nikolay Shaplov <[email protected]>
0 siblings, 0 replies; 868+ messages in thread
From: Nikolay Shaplov @ 2025-09-04 14:41 UTC (permalink / raw)
Introduce ternary reloption as a replacement for current `vacuum_truncate`
implementation. Remove `vacuum_truncate_set` additional flag and using
`TERNARY_UNSET` value instead.
---
src/backend/access/common/reloptions.c | 129 ++++++++++++++++++++-----
src/backend/commands/vacuum.c | 4 +-
src/include/access/reloptions.h | 26 ++---
src/include/c.h | 16 +++
src/include/utils/rel.h | 3 +-
5 files changed, 135 insertions(+), 43 deletions(-)
diff --git a/src/backend/access/common/reloptions.c b/src/backend/access/common/reloptions.c
index 0af3fea68f..f543f11001 100644
--- a/src/backend/access/common/reloptions.c
+++ b/src/backend/access/common/reloptions.c
@@ -40,9 +40,9 @@
*
* To add an option:
*
- * (i) decide on a type (bool, integer, real, enum, string), name, default
- * value, upper and lower bounds (if applicable); for strings, consider a
- * validation routine.
+ * (i) decide on a type (bool, ternary, integer, real, enum, string), name,
+ * default value, upper and lower bounds (if applicable); for strings,
+ * consider a validation routine.
* (ii) add a record below (or use add_<type>_reloption).
* (iii) add it to the appropriate options struct (perhaps StdRdOptions)
* (iv) add it to the appropriate handling routine (perhaps
@@ -147,15 +147,6 @@ static relopt_bool boolRelOpts[] =
},
false
},
- {
- {
- "vacuum_truncate",
- "Enables vacuum to truncate empty pages at the end of this table",
- RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
- ShareUpdateExclusiveLock
- },
- true
- },
{
{
"deduplicate_items",
@@ -170,6 +161,21 @@ static relopt_bool boolRelOpts[] =
{{NULL}}
};
+static relopt_ternary ternaryRelOpts[] =
+{
+ {
+ {
+ "vacuum_truncate",
+ "Enables vacuum to truncate empty pages at the end of this table",
+ RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
+ ShareUpdateExclusiveLock
+ },
+ TERNARY_UNSET
+ },
+ /* list terminator */
+ {{NULL}}
+};
+
static relopt_int intRelOpts[] =
{
{
@@ -600,6 +606,13 @@ initialize_reloptions(void)
boolRelOpts[i].gen.lockmode));
j++;
}
+ for (i = 0; ternaryRelOpts[i].gen.name; i++)
+ {
+ Assert(DoLockModesConflict(ternaryRelOpts[i].gen.lockmode,
+ ternaryRelOpts[i].gen.lockmode));
+ j++;
+ }
+
for (i = 0; intRelOpts[i].gen.name; i++)
{
Assert(DoLockModesConflict(intRelOpts[i].gen.lockmode,
@@ -640,6 +653,14 @@ initialize_reloptions(void)
j++;
}
+ for (i = 0; ternaryRelOpts[i].gen.name; i++)
+ {
+ relOpts[j] = &ternaryRelOpts[i].gen;
+ relOpts[j]->type = RELOPT_TYPE_TERNARY;
+ relOpts[j]->namelen = strlen(relOpts[j]->name);
+ j++;
+ }
+
for (i = 0; intRelOpts[i].gen.name; i++)
{
relOpts[j] = &intRelOpts[i].gen;
@@ -800,6 +821,9 @@ allocate_reloption(bits32 kinds, int type, const char *name, const char *desc,
case RELOPT_TYPE_BOOL:
size = sizeof(relopt_bool);
break;
+ case RELOPT_TYPE_TERNARY:
+ size = sizeof(relopt_ternary);
+ break;
case RELOPT_TYPE_INT:
size = sizeof(relopt_int);
break;
@@ -883,6 +907,54 @@ add_local_bool_reloption(local_relopts *relopts, const char *name,
add_local_reloption(relopts, (relopt_gen *) newoption, offset);
}
+/*
+ * init_ternary_reloption
+ * Allocate and initialize a new ternary reloption
+ */
+static relopt_ternary *
+init_ternary_reloption(bits32 kinds, const char *name, const char *desc,
+ ternary default_val, LOCKMODE lockmode)
+{
+ relopt_ternary *newoption;
+
+ newoption = (relopt_ternary *) allocate_reloption(kinds,
+ RELOPT_TYPE_TERNARY, name, desc, lockmode);
+ newoption->default_val = default_val;
+
+ return newoption;
+}
+
+/*
+ * add_ternary_reloption
+ * Add a new ternary reloption
+ */
+void
+add_ternary_reloption(bits32 kinds, const char *name, const char *desc,
+ ternary default_val, LOCKMODE lockmode)
+{
+ relopt_ternary *newoption = init_ternary_reloption(kinds, name, desc,
+ default_val, lockmode);
+
+ add_reloption((relopt_gen *) newoption);
+}
+
+/*
+ * add_local_ternary_reloption
+ * Add a new ternary local reloption
+ *
+ * 'offset' is offset of ternary-typed field.
+ */
+void
+add_local_ternary_reloption(local_relopts *relopts, const char *name,
+ const char *desc, ternary default_val,
+ int offset)
+{
+ relopt_ternary *newoption = init_ternary_reloption(RELOPT_KIND_LOCAL,
+ name, desc,
+ default_val, 0);
+
+ add_local_reloption(relopts, (relopt_gen *) newoption, offset);
+}
/*
* init_real_reloption
@@ -1617,6 +1689,18 @@ parse_one_reloption(relopt_value *option, char *text_str, int text_len,
option->gen->name, value)));
}
break;
+ case RELOPT_TYPE_TERNARY:
+ {
+ bool b;
+ parsed = parse_bool(value, &b);
+ option->values.ternary_val = b ? TERNARY_TRUE : TERNARY_FALSE;
+ if (validate && !parsed)
+ ereport(ERROR,
+ (errcode(ERRCODE_INVALID_PARAMETER_VALUE),
+ errmsg("invalid value for ternary option \"%s\": %s",
+ option->gen->name, value)));
+ }
+ break;
case RELOPT_TYPE_INT:
{
relopt_int *optint = (relopt_int *) option->gen;
@@ -1780,17 +1864,6 @@ fillRelOptions(void *rdopts, Size basesize,
char *itempos = ((char *) rdopts) + elems[j].offset;
char *string_val;
- /*
- * If isset_offset is provided, store whether the reloption is
- * set there.
- */
- if (elems[j].isset_offset > 0)
- {
- char *setpos = ((char *) rdopts) + elems[j].isset_offset;
-
- *(bool *) setpos = options[i].isset;
- }
-
switch (options[i].gen->type)
{
case RELOPT_TYPE_BOOL:
@@ -1798,6 +1871,11 @@ fillRelOptions(void *rdopts, Size basesize,
options[i].values.bool_val :
((relopt_bool *) options[i].gen)->default_val;
break;
+ case RELOPT_TYPE_TERNARY:
+ *(ternary *) itempos = options[i].isset ?
+ options[i].values.ternary_val :
+ ((relopt_ternary *) options[i].gen)->default_val;
+ break;
case RELOPT_TYPE_INT:
*(int *) itempos = options[i].isset ?
options[i].values.int_val :
@@ -1912,8 +1990,8 @@ default_reloptions(Datum reloptions, bool validate, relopt_kind kind)
offsetof(StdRdOptions, parallel_workers)},
{"vacuum_index_cleanup", RELOPT_TYPE_ENUM,
offsetof(StdRdOptions, vacuum_index_cleanup)},
- {"vacuum_truncate", RELOPT_TYPE_BOOL,
- offsetof(StdRdOptions, vacuum_truncate), offsetof(StdRdOptions, vacuum_truncate_set)},
+ {"vacuum_truncate", RELOPT_TYPE_TERNARY,
+ offsetof(StdRdOptions, vacuum_truncate)},
{"vacuum_max_eager_freeze_failure_rate", RELOPT_TYPE_REAL,
offsetof(StdRdOptions, vacuum_max_eager_freeze_failure_rate)}
};
@@ -1993,7 +2071,6 @@ build_local_reloptions(local_relopts *relopts, Datum options, bool validate)
elems[i].optname = opt->option->name;
elems[i].opttype = opt->option->type;
elems[i].offset = opt->offset;
- elems[i].isset_offset = 0; /* not supported for local relopts yet */
i++;
}
diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c
index 733ef40ae7..977babff54 100644
--- a/src/backend/commands/vacuum.c
+++ b/src/backend/commands/vacuum.c
@@ -2221,9 +2221,9 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams params,
{
StdRdOptions *opts = (StdRdOptions *) rel->rd_options;
- if (opts && opts->vacuum_truncate_set)
+ if (opts && opts->vacuum_truncate != TERNARY_UNSET)
{
- if (opts->vacuum_truncate)
+ if (opts->vacuum_truncate == TERNARY_TRUE)
params.truncate = VACOPTVALUE_ENABLED;
else
params.truncate = VACOPTVALUE_DISABLED;
diff --git a/src/include/access/reloptions.h b/src/include/access/reloptions.h
index a604a4702c..a436697658 100644
--- a/src/include/access/reloptions.h
+++ b/src/include/access/reloptions.h
@@ -29,6 +29,7 @@
typedef enum relopt_type
{
RELOPT_TYPE_BOOL,
+ RELOPT_TYPE_TERNARY, /* on, off, unset */
RELOPT_TYPE_INT,
RELOPT_TYPE_REAL,
RELOPT_TYPE_ENUM,
@@ -80,6 +81,7 @@ typedef struct relopt_value
union
{
bool bool_val;
+ ternary ternary_val;
int int_val;
double real_val;
int enum_val;
@@ -94,6 +96,12 @@ typedef struct relopt_bool
bool default_val;
} relopt_bool;
+typedef struct relopt_rernary
+{
+ relopt_gen gen;
+ int default_val;
+} relopt_ternary;
+
typedef struct relopt_int
{
relopt_gen gen;
@@ -152,19 +160,6 @@ typedef struct
const char *optname; /* option's name */
relopt_type opttype; /* option's datatype */
int offset; /* offset of field in result struct */
-
- /*
- * isset_offset is an optional offset of a field in the result struct that
- * stores whether the option is explicitly set for the relation or if it
- * just picked up the default value. In most cases, this can be
- * accomplished by giving the reloption a special out-of-range default
- * value (e.g., some integer reloptions use -2), but this isn't always
- * possible. For example, a Boolean reloption cannot be given an
- * out-of-range default, so we need another way to discover the source of
- * its value. This offset is only used if given a value greater than
- * zero.
- */
- int isset_offset;
} relopt_parse_elt;
/* Local reloption definition */
@@ -195,6 +190,8 @@ typedef struct local_relopts
extern relopt_kind add_reloption_kind(void);
extern void add_bool_reloption(bits32 kinds, const char *name, const char *desc,
bool default_val, LOCKMODE lockmode);
+extern void add_ternary_reloption(bits32 kinds, const char *name,
+ const char *desc, int default_val, LOCKMODE lockmode);
extern void add_int_reloption(bits32 kinds, const char *name, const char *desc,
int default_val, int min_val, int max_val,
LOCKMODE lockmode);
@@ -214,6 +211,9 @@ extern void register_reloptions_validator(local_relopts *relopts,
extern void add_local_bool_reloption(local_relopts *relopts, const char *name,
const char *desc, bool default_val,
int offset);
+extern void add_local_ternary_reloption(local_relopts *relopts,
+ const char *name, const char *desc,
+ ternary default_val, int offset);
extern void add_local_int_reloption(local_relopts *relopts, const char *name,
const char *desc, int default_val,
int min_val, int max_val, int offset);
diff --git a/src/include/c.h b/src/include/c.h
index 39022f8a9d..9c59ccccf8 100644
--- a/src/include/c.h
+++ b/src/include/c.h
@@ -488,6 +488,22 @@ typedef void (*pg_funcptr_t) (void);
#include <stdbool.h>
+/*
+ * ternary
+ * Boolean value with an extrea "unset" option
+ *
+ * Ternary data type is used in relation options that can be "true", "false" or
+ * "unset". Since relation options are used deep inside the PostgreSQL code,
+ * this type is declared globally.
+*/
+
+typedef enum ternary
+{
+ TERNARY_FALSE = 0,
+ TERNARY_TRUE = 1,
+ TERNARY_UNSET = -1
+} ternary;
+
/* ----------------------------------------------------------------
* Section 3: standard system types
diff --git a/src/include/utils/rel.h b/src/include/utils/rel.h
index b552359915..7346d618f9 100644
--- a/src/include/utils/rel.h
+++ b/src/include/utils/rel.h
@@ -346,8 +346,7 @@ typedef struct StdRdOptions
bool user_catalog_table; /* use as an additional catalog relation */
int parallel_workers; /* max number of parallel workers */
StdRdOptIndexCleanup vacuum_index_cleanup; /* controls index vacuuming */
- bool vacuum_truncate; /* enables vacuum to truncate a relation */
- bool vacuum_truncate_set; /* whether vacuum_truncate is set */
+ ternary vacuum_truncate; /* enables vacuum to truncate a relation */
/*
* Fraction of pages in a relation that vacuum can eagerly scan and fail
--
2.39.2
--nextPart5129932.5fSG56mABF
Content-Disposition: attachment;
filename="v2-0003-Add-alias-to-be-used-as-unset-state.patch"
Content-Transfer-Encoding: 7Bit
Content-Type: text/x-patch; charset="unicode-2-0-utf-8";
name="v2-0003-Add-alias-to-be-used-as-unset-state.patch"
^ permalink raw reply [nested|flat] 868+ messages in thread
* [PATCH v2 2/4] Introduce ternary reloptions
@ 2025-09-04 14:41 Nikolay Shaplov <[email protected]>
0 siblings, 0 replies; 868+ messages in thread
From: Nikolay Shaplov @ 2025-09-04 14:41 UTC (permalink / raw)
Introduce ternary reloption as a replacement for current `vacuum_truncate`
implementation. Remove `vacuum_truncate_set` additional flag and using
`TERNARY_UNSET` value instead.
---
src/backend/access/common/reloptions.c | 129 ++++++++++++++++++++-----
src/backend/commands/vacuum.c | 4 +-
src/include/access/reloptions.h | 26 ++---
src/include/c.h | 16 +++
src/include/utils/rel.h | 3 +-
5 files changed, 135 insertions(+), 43 deletions(-)
diff --git a/src/backend/access/common/reloptions.c b/src/backend/access/common/reloptions.c
index 0af3fea68f..f543f11001 100644
--- a/src/backend/access/common/reloptions.c
+++ b/src/backend/access/common/reloptions.c
@@ -40,9 +40,9 @@
*
* To add an option:
*
- * (i) decide on a type (bool, integer, real, enum, string), name, default
- * value, upper and lower bounds (if applicable); for strings, consider a
- * validation routine.
+ * (i) decide on a type (bool, ternary, integer, real, enum, string), name,
+ * default value, upper and lower bounds (if applicable); for strings,
+ * consider a validation routine.
* (ii) add a record below (or use add_<type>_reloption).
* (iii) add it to the appropriate options struct (perhaps StdRdOptions)
* (iv) add it to the appropriate handling routine (perhaps
@@ -147,15 +147,6 @@ static relopt_bool boolRelOpts[] =
},
false
},
- {
- {
- "vacuum_truncate",
- "Enables vacuum to truncate empty pages at the end of this table",
- RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
- ShareUpdateExclusiveLock
- },
- true
- },
{
{
"deduplicate_items",
@@ -170,6 +161,21 @@ static relopt_bool boolRelOpts[] =
{{NULL}}
};
+static relopt_ternary ternaryRelOpts[] =
+{
+ {
+ {
+ "vacuum_truncate",
+ "Enables vacuum to truncate empty pages at the end of this table",
+ RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
+ ShareUpdateExclusiveLock
+ },
+ TERNARY_UNSET
+ },
+ /* list terminator */
+ {{NULL}}
+};
+
static relopt_int intRelOpts[] =
{
{
@@ -600,6 +606,13 @@ initialize_reloptions(void)
boolRelOpts[i].gen.lockmode));
j++;
}
+ for (i = 0; ternaryRelOpts[i].gen.name; i++)
+ {
+ Assert(DoLockModesConflict(ternaryRelOpts[i].gen.lockmode,
+ ternaryRelOpts[i].gen.lockmode));
+ j++;
+ }
+
for (i = 0; intRelOpts[i].gen.name; i++)
{
Assert(DoLockModesConflict(intRelOpts[i].gen.lockmode,
@@ -640,6 +653,14 @@ initialize_reloptions(void)
j++;
}
+ for (i = 0; ternaryRelOpts[i].gen.name; i++)
+ {
+ relOpts[j] = &ternaryRelOpts[i].gen;
+ relOpts[j]->type = RELOPT_TYPE_TERNARY;
+ relOpts[j]->namelen = strlen(relOpts[j]->name);
+ j++;
+ }
+
for (i = 0; intRelOpts[i].gen.name; i++)
{
relOpts[j] = &intRelOpts[i].gen;
@@ -800,6 +821,9 @@ allocate_reloption(bits32 kinds, int type, const char *name, const char *desc,
case RELOPT_TYPE_BOOL:
size = sizeof(relopt_bool);
break;
+ case RELOPT_TYPE_TERNARY:
+ size = sizeof(relopt_ternary);
+ break;
case RELOPT_TYPE_INT:
size = sizeof(relopt_int);
break;
@@ -883,6 +907,54 @@ add_local_bool_reloption(local_relopts *relopts, const char *name,
add_local_reloption(relopts, (relopt_gen *) newoption, offset);
}
+/*
+ * init_ternary_reloption
+ * Allocate and initialize a new ternary reloption
+ */
+static relopt_ternary *
+init_ternary_reloption(bits32 kinds, const char *name, const char *desc,
+ ternary default_val, LOCKMODE lockmode)
+{
+ relopt_ternary *newoption;
+
+ newoption = (relopt_ternary *) allocate_reloption(kinds,
+ RELOPT_TYPE_TERNARY, name, desc, lockmode);
+ newoption->default_val = default_val;
+
+ return newoption;
+}
+
+/*
+ * add_ternary_reloption
+ * Add a new ternary reloption
+ */
+void
+add_ternary_reloption(bits32 kinds, const char *name, const char *desc,
+ ternary default_val, LOCKMODE lockmode)
+{
+ relopt_ternary *newoption = init_ternary_reloption(kinds, name, desc,
+ default_val, lockmode);
+
+ add_reloption((relopt_gen *) newoption);
+}
+
+/*
+ * add_local_ternary_reloption
+ * Add a new ternary local reloption
+ *
+ * 'offset' is offset of ternary-typed field.
+ */
+void
+add_local_ternary_reloption(local_relopts *relopts, const char *name,
+ const char *desc, ternary default_val,
+ int offset)
+{
+ relopt_ternary *newoption = init_ternary_reloption(RELOPT_KIND_LOCAL,
+ name, desc,
+ default_val, 0);
+
+ add_local_reloption(relopts, (relopt_gen *) newoption, offset);
+}
/*
* init_real_reloption
@@ -1617,6 +1689,18 @@ parse_one_reloption(relopt_value *option, char *text_str, int text_len,
option->gen->name, value)));
}
break;
+ case RELOPT_TYPE_TERNARY:
+ {
+ bool b;
+ parsed = parse_bool(value, &b);
+ option->values.ternary_val = b ? TERNARY_TRUE : TERNARY_FALSE;
+ if (validate && !parsed)
+ ereport(ERROR,
+ (errcode(ERRCODE_INVALID_PARAMETER_VALUE),
+ errmsg("invalid value for ternary option \"%s\": %s",
+ option->gen->name, value)));
+ }
+ break;
case RELOPT_TYPE_INT:
{
relopt_int *optint = (relopt_int *) option->gen;
@@ -1780,17 +1864,6 @@ fillRelOptions(void *rdopts, Size basesize,
char *itempos = ((char *) rdopts) + elems[j].offset;
char *string_val;
- /*
- * If isset_offset is provided, store whether the reloption is
- * set there.
- */
- if (elems[j].isset_offset > 0)
- {
- char *setpos = ((char *) rdopts) + elems[j].isset_offset;
-
- *(bool *) setpos = options[i].isset;
- }
-
switch (options[i].gen->type)
{
case RELOPT_TYPE_BOOL:
@@ -1798,6 +1871,11 @@ fillRelOptions(void *rdopts, Size basesize,
options[i].values.bool_val :
((relopt_bool *) options[i].gen)->default_val;
break;
+ case RELOPT_TYPE_TERNARY:
+ *(ternary *) itempos = options[i].isset ?
+ options[i].values.ternary_val :
+ ((relopt_ternary *) options[i].gen)->default_val;
+ break;
case RELOPT_TYPE_INT:
*(int *) itempos = options[i].isset ?
options[i].values.int_val :
@@ -1912,8 +1990,8 @@ default_reloptions(Datum reloptions, bool validate, relopt_kind kind)
offsetof(StdRdOptions, parallel_workers)},
{"vacuum_index_cleanup", RELOPT_TYPE_ENUM,
offsetof(StdRdOptions, vacuum_index_cleanup)},
- {"vacuum_truncate", RELOPT_TYPE_BOOL,
- offsetof(StdRdOptions, vacuum_truncate), offsetof(StdRdOptions, vacuum_truncate_set)},
+ {"vacuum_truncate", RELOPT_TYPE_TERNARY,
+ offsetof(StdRdOptions, vacuum_truncate)},
{"vacuum_max_eager_freeze_failure_rate", RELOPT_TYPE_REAL,
offsetof(StdRdOptions, vacuum_max_eager_freeze_failure_rate)}
};
@@ -1993,7 +2071,6 @@ build_local_reloptions(local_relopts *relopts, Datum options, bool validate)
elems[i].optname = opt->option->name;
elems[i].opttype = opt->option->type;
elems[i].offset = opt->offset;
- elems[i].isset_offset = 0; /* not supported for local relopts yet */
i++;
}
diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c
index 733ef40ae7..977babff54 100644
--- a/src/backend/commands/vacuum.c
+++ b/src/backend/commands/vacuum.c
@@ -2221,9 +2221,9 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams params,
{
StdRdOptions *opts = (StdRdOptions *) rel->rd_options;
- if (opts && opts->vacuum_truncate_set)
+ if (opts && opts->vacuum_truncate != TERNARY_UNSET)
{
- if (opts->vacuum_truncate)
+ if (opts->vacuum_truncate == TERNARY_TRUE)
params.truncate = VACOPTVALUE_ENABLED;
else
params.truncate = VACOPTVALUE_DISABLED;
diff --git a/src/include/access/reloptions.h b/src/include/access/reloptions.h
index a604a4702c..a436697658 100644
--- a/src/include/access/reloptions.h
+++ b/src/include/access/reloptions.h
@@ -29,6 +29,7 @@
typedef enum relopt_type
{
RELOPT_TYPE_BOOL,
+ RELOPT_TYPE_TERNARY, /* on, off, unset */
RELOPT_TYPE_INT,
RELOPT_TYPE_REAL,
RELOPT_TYPE_ENUM,
@@ -80,6 +81,7 @@ typedef struct relopt_value
union
{
bool bool_val;
+ ternary ternary_val;
int int_val;
double real_val;
int enum_val;
@@ -94,6 +96,12 @@ typedef struct relopt_bool
bool default_val;
} relopt_bool;
+typedef struct relopt_rernary
+{
+ relopt_gen gen;
+ int default_val;
+} relopt_ternary;
+
typedef struct relopt_int
{
relopt_gen gen;
@@ -152,19 +160,6 @@ typedef struct
const char *optname; /* option's name */
relopt_type opttype; /* option's datatype */
int offset; /* offset of field in result struct */
-
- /*
- * isset_offset is an optional offset of a field in the result struct that
- * stores whether the option is explicitly set for the relation or if it
- * just picked up the default value. In most cases, this can be
- * accomplished by giving the reloption a special out-of-range default
- * value (e.g., some integer reloptions use -2), but this isn't always
- * possible. For example, a Boolean reloption cannot be given an
- * out-of-range default, so we need another way to discover the source of
- * its value. This offset is only used if given a value greater than
- * zero.
- */
- int isset_offset;
} relopt_parse_elt;
/* Local reloption definition */
@@ -195,6 +190,8 @@ typedef struct local_relopts
extern relopt_kind add_reloption_kind(void);
extern void add_bool_reloption(bits32 kinds, const char *name, const char *desc,
bool default_val, LOCKMODE lockmode);
+extern void add_ternary_reloption(bits32 kinds, const char *name,
+ const char *desc, int default_val, LOCKMODE lockmode);
extern void add_int_reloption(bits32 kinds, const char *name, const char *desc,
int default_val, int min_val, int max_val,
LOCKMODE lockmode);
@@ -214,6 +211,9 @@ extern void register_reloptions_validator(local_relopts *relopts,
extern void add_local_bool_reloption(local_relopts *relopts, const char *name,
const char *desc, bool default_val,
int offset);
+extern void add_local_ternary_reloption(local_relopts *relopts,
+ const char *name, const char *desc,
+ ternary default_val, int offset);
extern void add_local_int_reloption(local_relopts *relopts, const char *name,
const char *desc, int default_val,
int min_val, int max_val, int offset);
diff --git a/src/include/c.h b/src/include/c.h
index 39022f8a9d..9c59ccccf8 100644
--- a/src/include/c.h
+++ b/src/include/c.h
@@ -488,6 +488,22 @@ typedef void (*pg_funcptr_t) (void);
#include <stdbool.h>
+/*
+ * ternary
+ * Boolean value with an extrea "unset" option
+ *
+ * Ternary data type is used in relation options that can be "true", "false" or
+ * "unset". Since relation options are used deep inside the PostgreSQL code,
+ * this type is declared globally.
+*/
+
+typedef enum ternary
+{
+ TERNARY_FALSE = 0,
+ TERNARY_TRUE = 1,
+ TERNARY_UNSET = -1
+} ternary;
+
/* ----------------------------------------------------------------
* Section 3: standard system types
diff --git a/src/include/utils/rel.h b/src/include/utils/rel.h
index b552359915..7346d618f9 100644
--- a/src/include/utils/rel.h
+++ b/src/include/utils/rel.h
@@ -346,8 +346,7 @@ typedef struct StdRdOptions
bool user_catalog_table; /* use as an additional catalog relation */
int parallel_workers; /* max number of parallel workers */
StdRdOptIndexCleanup vacuum_index_cleanup; /* controls index vacuuming */
- bool vacuum_truncate; /* enables vacuum to truncate a relation */
- bool vacuum_truncate_set; /* whether vacuum_truncate is set */
+ ternary vacuum_truncate; /* enables vacuum to truncate a relation */
/*
* Fraction of pages in a relation that vacuum can eagerly scan and fail
--
2.39.2
--nextPart5129932.5fSG56mABF
Content-Disposition: attachment;
filename="v2-0003-Add-alias-to-be-used-as-unset-state.patch"
Content-Transfer-Encoding: 7Bit
Content-Type: text/x-patch; charset="unicode-2-0-utf-8";
name="v2-0003-Add-alias-to-be-used-as-unset-state.patch"
^ permalink raw reply [nested|flat] 868+ messages in thread
* [PATCH v2 2/4] Introduce ternary reloptions
@ 2025-09-04 14:41 Nikolay Shaplov <[email protected]>
0 siblings, 0 replies; 868+ messages in thread
From: Nikolay Shaplov @ 2025-09-04 14:41 UTC (permalink / raw)
Introduce ternary reloption as a replacement for current `vacuum_truncate`
implementation. Remove `vacuum_truncate_set` additional flag and using
`TERNARY_UNSET` value instead.
---
src/backend/access/common/reloptions.c | 129 ++++++++++++++++++++-----
src/backend/commands/vacuum.c | 4 +-
src/include/access/reloptions.h | 26 ++---
src/include/c.h | 16 +++
src/include/utils/rel.h | 3 +-
5 files changed, 135 insertions(+), 43 deletions(-)
diff --git a/src/backend/access/common/reloptions.c b/src/backend/access/common/reloptions.c
index 0af3fea68f..f543f11001 100644
--- a/src/backend/access/common/reloptions.c
+++ b/src/backend/access/common/reloptions.c
@@ -40,9 +40,9 @@
*
* To add an option:
*
- * (i) decide on a type (bool, integer, real, enum, string), name, default
- * value, upper and lower bounds (if applicable); for strings, consider a
- * validation routine.
+ * (i) decide on a type (bool, ternary, integer, real, enum, string), name,
+ * default value, upper and lower bounds (if applicable); for strings,
+ * consider a validation routine.
* (ii) add a record below (or use add_<type>_reloption).
* (iii) add it to the appropriate options struct (perhaps StdRdOptions)
* (iv) add it to the appropriate handling routine (perhaps
@@ -147,15 +147,6 @@ static relopt_bool boolRelOpts[] =
},
false
},
- {
- {
- "vacuum_truncate",
- "Enables vacuum to truncate empty pages at the end of this table",
- RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
- ShareUpdateExclusiveLock
- },
- true
- },
{
{
"deduplicate_items",
@@ -170,6 +161,21 @@ static relopt_bool boolRelOpts[] =
{{NULL}}
};
+static relopt_ternary ternaryRelOpts[] =
+{
+ {
+ {
+ "vacuum_truncate",
+ "Enables vacuum to truncate empty pages at the end of this table",
+ RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
+ ShareUpdateExclusiveLock
+ },
+ TERNARY_UNSET
+ },
+ /* list terminator */
+ {{NULL}}
+};
+
static relopt_int intRelOpts[] =
{
{
@@ -600,6 +606,13 @@ initialize_reloptions(void)
boolRelOpts[i].gen.lockmode));
j++;
}
+ for (i = 0; ternaryRelOpts[i].gen.name; i++)
+ {
+ Assert(DoLockModesConflict(ternaryRelOpts[i].gen.lockmode,
+ ternaryRelOpts[i].gen.lockmode));
+ j++;
+ }
+
for (i = 0; intRelOpts[i].gen.name; i++)
{
Assert(DoLockModesConflict(intRelOpts[i].gen.lockmode,
@@ -640,6 +653,14 @@ initialize_reloptions(void)
j++;
}
+ for (i = 0; ternaryRelOpts[i].gen.name; i++)
+ {
+ relOpts[j] = &ternaryRelOpts[i].gen;
+ relOpts[j]->type = RELOPT_TYPE_TERNARY;
+ relOpts[j]->namelen = strlen(relOpts[j]->name);
+ j++;
+ }
+
for (i = 0; intRelOpts[i].gen.name; i++)
{
relOpts[j] = &intRelOpts[i].gen;
@@ -800,6 +821,9 @@ allocate_reloption(bits32 kinds, int type, const char *name, const char *desc,
case RELOPT_TYPE_BOOL:
size = sizeof(relopt_bool);
break;
+ case RELOPT_TYPE_TERNARY:
+ size = sizeof(relopt_ternary);
+ break;
case RELOPT_TYPE_INT:
size = sizeof(relopt_int);
break;
@@ -883,6 +907,54 @@ add_local_bool_reloption(local_relopts *relopts, const char *name,
add_local_reloption(relopts, (relopt_gen *) newoption, offset);
}
+/*
+ * init_ternary_reloption
+ * Allocate and initialize a new ternary reloption
+ */
+static relopt_ternary *
+init_ternary_reloption(bits32 kinds, const char *name, const char *desc,
+ ternary default_val, LOCKMODE lockmode)
+{
+ relopt_ternary *newoption;
+
+ newoption = (relopt_ternary *) allocate_reloption(kinds,
+ RELOPT_TYPE_TERNARY, name, desc, lockmode);
+ newoption->default_val = default_val;
+
+ return newoption;
+}
+
+/*
+ * add_ternary_reloption
+ * Add a new ternary reloption
+ */
+void
+add_ternary_reloption(bits32 kinds, const char *name, const char *desc,
+ ternary default_val, LOCKMODE lockmode)
+{
+ relopt_ternary *newoption = init_ternary_reloption(kinds, name, desc,
+ default_val, lockmode);
+
+ add_reloption((relopt_gen *) newoption);
+}
+
+/*
+ * add_local_ternary_reloption
+ * Add a new ternary local reloption
+ *
+ * 'offset' is offset of ternary-typed field.
+ */
+void
+add_local_ternary_reloption(local_relopts *relopts, const char *name,
+ const char *desc, ternary default_val,
+ int offset)
+{
+ relopt_ternary *newoption = init_ternary_reloption(RELOPT_KIND_LOCAL,
+ name, desc,
+ default_val, 0);
+
+ add_local_reloption(relopts, (relopt_gen *) newoption, offset);
+}
/*
* init_real_reloption
@@ -1617,6 +1689,18 @@ parse_one_reloption(relopt_value *option, char *text_str, int text_len,
option->gen->name, value)));
}
break;
+ case RELOPT_TYPE_TERNARY:
+ {
+ bool b;
+ parsed = parse_bool(value, &b);
+ option->values.ternary_val = b ? TERNARY_TRUE : TERNARY_FALSE;
+ if (validate && !parsed)
+ ereport(ERROR,
+ (errcode(ERRCODE_INVALID_PARAMETER_VALUE),
+ errmsg("invalid value for ternary option \"%s\": %s",
+ option->gen->name, value)));
+ }
+ break;
case RELOPT_TYPE_INT:
{
relopt_int *optint = (relopt_int *) option->gen;
@@ -1780,17 +1864,6 @@ fillRelOptions(void *rdopts, Size basesize,
char *itempos = ((char *) rdopts) + elems[j].offset;
char *string_val;
- /*
- * If isset_offset is provided, store whether the reloption is
- * set there.
- */
- if (elems[j].isset_offset > 0)
- {
- char *setpos = ((char *) rdopts) + elems[j].isset_offset;
-
- *(bool *) setpos = options[i].isset;
- }
-
switch (options[i].gen->type)
{
case RELOPT_TYPE_BOOL:
@@ -1798,6 +1871,11 @@ fillRelOptions(void *rdopts, Size basesize,
options[i].values.bool_val :
((relopt_bool *) options[i].gen)->default_val;
break;
+ case RELOPT_TYPE_TERNARY:
+ *(ternary *) itempos = options[i].isset ?
+ options[i].values.ternary_val :
+ ((relopt_ternary *) options[i].gen)->default_val;
+ break;
case RELOPT_TYPE_INT:
*(int *) itempos = options[i].isset ?
options[i].values.int_val :
@@ -1912,8 +1990,8 @@ default_reloptions(Datum reloptions, bool validate, relopt_kind kind)
offsetof(StdRdOptions, parallel_workers)},
{"vacuum_index_cleanup", RELOPT_TYPE_ENUM,
offsetof(StdRdOptions, vacuum_index_cleanup)},
- {"vacuum_truncate", RELOPT_TYPE_BOOL,
- offsetof(StdRdOptions, vacuum_truncate), offsetof(StdRdOptions, vacuum_truncate_set)},
+ {"vacuum_truncate", RELOPT_TYPE_TERNARY,
+ offsetof(StdRdOptions, vacuum_truncate)},
{"vacuum_max_eager_freeze_failure_rate", RELOPT_TYPE_REAL,
offsetof(StdRdOptions, vacuum_max_eager_freeze_failure_rate)}
};
@@ -1993,7 +2071,6 @@ build_local_reloptions(local_relopts *relopts, Datum options, bool validate)
elems[i].optname = opt->option->name;
elems[i].opttype = opt->option->type;
elems[i].offset = opt->offset;
- elems[i].isset_offset = 0; /* not supported for local relopts yet */
i++;
}
diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c
index 733ef40ae7..977babff54 100644
--- a/src/backend/commands/vacuum.c
+++ b/src/backend/commands/vacuum.c
@@ -2221,9 +2221,9 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams params,
{
StdRdOptions *opts = (StdRdOptions *) rel->rd_options;
- if (opts && opts->vacuum_truncate_set)
+ if (opts && opts->vacuum_truncate != TERNARY_UNSET)
{
- if (opts->vacuum_truncate)
+ if (opts->vacuum_truncate == TERNARY_TRUE)
params.truncate = VACOPTVALUE_ENABLED;
else
params.truncate = VACOPTVALUE_DISABLED;
diff --git a/src/include/access/reloptions.h b/src/include/access/reloptions.h
index a604a4702c..a436697658 100644
--- a/src/include/access/reloptions.h
+++ b/src/include/access/reloptions.h
@@ -29,6 +29,7 @@
typedef enum relopt_type
{
RELOPT_TYPE_BOOL,
+ RELOPT_TYPE_TERNARY, /* on, off, unset */
RELOPT_TYPE_INT,
RELOPT_TYPE_REAL,
RELOPT_TYPE_ENUM,
@@ -80,6 +81,7 @@ typedef struct relopt_value
union
{
bool bool_val;
+ ternary ternary_val;
int int_val;
double real_val;
int enum_val;
@@ -94,6 +96,12 @@ typedef struct relopt_bool
bool default_val;
} relopt_bool;
+typedef struct relopt_rernary
+{
+ relopt_gen gen;
+ int default_val;
+} relopt_ternary;
+
typedef struct relopt_int
{
relopt_gen gen;
@@ -152,19 +160,6 @@ typedef struct
const char *optname; /* option's name */
relopt_type opttype; /* option's datatype */
int offset; /* offset of field in result struct */
-
- /*
- * isset_offset is an optional offset of a field in the result struct that
- * stores whether the option is explicitly set for the relation or if it
- * just picked up the default value. In most cases, this can be
- * accomplished by giving the reloption a special out-of-range default
- * value (e.g., some integer reloptions use -2), but this isn't always
- * possible. For example, a Boolean reloption cannot be given an
- * out-of-range default, so we need another way to discover the source of
- * its value. This offset is only used if given a value greater than
- * zero.
- */
- int isset_offset;
} relopt_parse_elt;
/* Local reloption definition */
@@ -195,6 +190,8 @@ typedef struct local_relopts
extern relopt_kind add_reloption_kind(void);
extern void add_bool_reloption(bits32 kinds, const char *name, const char *desc,
bool default_val, LOCKMODE lockmode);
+extern void add_ternary_reloption(bits32 kinds, const char *name,
+ const char *desc, int default_val, LOCKMODE lockmode);
extern void add_int_reloption(bits32 kinds, const char *name, const char *desc,
int default_val, int min_val, int max_val,
LOCKMODE lockmode);
@@ -214,6 +211,9 @@ extern void register_reloptions_validator(local_relopts *relopts,
extern void add_local_bool_reloption(local_relopts *relopts, const char *name,
const char *desc, bool default_val,
int offset);
+extern void add_local_ternary_reloption(local_relopts *relopts,
+ const char *name, const char *desc,
+ ternary default_val, int offset);
extern void add_local_int_reloption(local_relopts *relopts, const char *name,
const char *desc, int default_val,
int min_val, int max_val, int offset);
diff --git a/src/include/c.h b/src/include/c.h
index 39022f8a9d..9c59ccccf8 100644
--- a/src/include/c.h
+++ b/src/include/c.h
@@ -488,6 +488,22 @@ typedef void (*pg_funcptr_t) (void);
#include <stdbool.h>
+/*
+ * ternary
+ * Boolean value with an extrea "unset" option
+ *
+ * Ternary data type is used in relation options that can be "true", "false" or
+ * "unset". Since relation options are used deep inside the PostgreSQL code,
+ * this type is declared globally.
+*/
+
+typedef enum ternary
+{
+ TERNARY_FALSE = 0,
+ TERNARY_TRUE = 1,
+ TERNARY_UNSET = -1
+} ternary;
+
/* ----------------------------------------------------------------
* Section 3: standard system types
diff --git a/src/include/utils/rel.h b/src/include/utils/rel.h
index b552359915..7346d618f9 100644
--- a/src/include/utils/rel.h
+++ b/src/include/utils/rel.h
@@ -346,8 +346,7 @@ typedef struct StdRdOptions
bool user_catalog_table; /* use as an additional catalog relation */
int parallel_workers; /* max number of parallel workers */
StdRdOptIndexCleanup vacuum_index_cleanup; /* controls index vacuuming */
- bool vacuum_truncate; /* enables vacuum to truncate a relation */
- bool vacuum_truncate_set; /* whether vacuum_truncate is set */
+ ternary vacuum_truncate; /* enables vacuum to truncate a relation */
/*
* Fraction of pages in a relation that vacuum can eagerly scan and fail
--
2.39.2
--nextPart5129932.5fSG56mABF
Content-Disposition: attachment;
filename="v2-0003-Add-alias-to-be-used-as-unset-state.patch"
Content-Transfer-Encoding: 7Bit
Content-Type: text/x-patch; charset="unicode-2-0-utf-8";
name="v2-0003-Add-alias-to-be-used-as-unset-state.patch"
^ permalink raw reply [nested|flat] 868+ messages in thread
* [PATCH v2 2/4] Introduce ternary reloptions
@ 2025-09-04 14:41 Nikolay Shaplov <[email protected]>
0 siblings, 0 replies; 868+ messages in thread
From: Nikolay Shaplov @ 2025-09-04 14:41 UTC (permalink / raw)
Introduce ternary reloption as a replacement for current `vacuum_truncate`
implementation. Remove `vacuum_truncate_set` additional flag and using
`TERNARY_UNSET` value instead.
---
src/backend/access/common/reloptions.c | 129 ++++++++++++++++++++-----
src/backend/commands/vacuum.c | 4 +-
src/include/access/reloptions.h | 26 ++---
src/include/c.h | 16 +++
src/include/utils/rel.h | 3 +-
5 files changed, 135 insertions(+), 43 deletions(-)
diff --git a/src/backend/access/common/reloptions.c b/src/backend/access/common/reloptions.c
index 0af3fea68f..f543f11001 100644
--- a/src/backend/access/common/reloptions.c
+++ b/src/backend/access/common/reloptions.c
@@ -40,9 +40,9 @@
*
* To add an option:
*
- * (i) decide on a type (bool, integer, real, enum, string), name, default
- * value, upper and lower bounds (if applicable); for strings, consider a
- * validation routine.
+ * (i) decide on a type (bool, ternary, integer, real, enum, string), name,
+ * default value, upper and lower bounds (if applicable); for strings,
+ * consider a validation routine.
* (ii) add a record below (or use add_<type>_reloption).
* (iii) add it to the appropriate options struct (perhaps StdRdOptions)
* (iv) add it to the appropriate handling routine (perhaps
@@ -147,15 +147,6 @@ static relopt_bool boolRelOpts[] =
},
false
},
- {
- {
- "vacuum_truncate",
- "Enables vacuum to truncate empty pages at the end of this table",
- RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
- ShareUpdateExclusiveLock
- },
- true
- },
{
{
"deduplicate_items",
@@ -170,6 +161,21 @@ static relopt_bool boolRelOpts[] =
{{NULL}}
};
+static relopt_ternary ternaryRelOpts[] =
+{
+ {
+ {
+ "vacuum_truncate",
+ "Enables vacuum to truncate empty pages at the end of this table",
+ RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
+ ShareUpdateExclusiveLock
+ },
+ TERNARY_UNSET
+ },
+ /* list terminator */
+ {{NULL}}
+};
+
static relopt_int intRelOpts[] =
{
{
@@ -600,6 +606,13 @@ initialize_reloptions(void)
boolRelOpts[i].gen.lockmode));
j++;
}
+ for (i = 0; ternaryRelOpts[i].gen.name; i++)
+ {
+ Assert(DoLockModesConflict(ternaryRelOpts[i].gen.lockmode,
+ ternaryRelOpts[i].gen.lockmode));
+ j++;
+ }
+
for (i = 0; intRelOpts[i].gen.name; i++)
{
Assert(DoLockModesConflict(intRelOpts[i].gen.lockmode,
@@ -640,6 +653,14 @@ initialize_reloptions(void)
j++;
}
+ for (i = 0; ternaryRelOpts[i].gen.name; i++)
+ {
+ relOpts[j] = &ternaryRelOpts[i].gen;
+ relOpts[j]->type = RELOPT_TYPE_TERNARY;
+ relOpts[j]->namelen = strlen(relOpts[j]->name);
+ j++;
+ }
+
for (i = 0; intRelOpts[i].gen.name; i++)
{
relOpts[j] = &intRelOpts[i].gen;
@@ -800,6 +821,9 @@ allocate_reloption(bits32 kinds, int type, const char *name, const char *desc,
case RELOPT_TYPE_BOOL:
size = sizeof(relopt_bool);
break;
+ case RELOPT_TYPE_TERNARY:
+ size = sizeof(relopt_ternary);
+ break;
case RELOPT_TYPE_INT:
size = sizeof(relopt_int);
break;
@@ -883,6 +907,54 @@ add_local_bool_reloption(local_relopts *relopts, const char *name,
add_local_reloption(relopts, (relopt_gen *) newoption, offset);
}
+/*
+ * init_ternary_reloption
+ * Allocate and initialize a new ternary reloption
+ */
+static relopt_ternary *
+init_ternary_reloption(bits32 kinds, const char *name, const char *desc,
+ ternary default_val, LOCKMODE lockmode)
+{
+ relopt_ternary *newoption;
+
+ newoption = (relopt_ternary *) allocate_reloption(kinds,
+ RELOPT_TYPE_TERNARY, name, desc, lockmode);
+ newoption->default_val = default_val;
+
+ return newoption;
+}
+
+/*
+ * add_ternary_reloption
+ * Add a new ternary reloption
+ */
+void
+add_ternary_reloption(bits32 kinds, const char *name, const char *desc,
+ ternary default_val, LOCKMODE lockmode)
+{
+ relopt_ternary *newoption = init_ternary_reloption(kinds, name, desc,
+ default_val, lockmode);
+
+ add_reloption((relopt_gen *) newoption);
+}
+
+/*
+ * add_local_ternary_reloption
+ * Add a new ternary local reloption
+ *
+ * 'offset' is offset of ternary-typed field.
+ */
+void
+add_local_ternary_reloption(local_relopts *relopts, const char *name,
+ const char *desc, ternary default_val,
+ int offset)
+{
+ relopt_ternary *newoption = init_ternary_reloption(RELOPT_KIND_LOCAL,
+ name, desc,
+ default_val, 0);
+
+ add_local_reloption(relopts, (relopt_gen *) newoption, offset);
+}
/*
* init_real_reloption
@@ -1617,6 +1689,18 @@ parse_one_reloption(relopt_value *option, char *text_str, int text_len,
option->gen->name, value)));
}
break;
+ case RELOPT_TYPE_TERNARY:
+ {
+ bool b;
+ parsed = parse_bool(value, &b);
+ option->values.ternary_val = b ? TERNARY_TRUE : TERNARY_FALSE;
+ if (validate && !parsed)
+ ereport(ERROR,
+ (errcode(ERRCODE_INVALID_PARAMETER_VALUE),
+ errmsg("invalid value for ternary option \"%s\": %s",
+ option->gen->name, value)));
+ }
+ break;
case RELOPT_TYPE_INT:
{
relopt_int *optint = (relopt_int *) option->gen;
@@ -1780,17 +1864,6 @@ fillRelOptions(void *rdopts, Size basesize,
char *itempos = ((char *) rdopts) + elems[j].offset;
char *string_val;
- /*
- * If isset_offset is provided, store whether the reloption is
- * set there.
- */
- if (elems[j].isset_offset > 0)
- {
- char *setpos = ((char *) rdopts) + elems[j].isset_offset;
-
- *(bool *) setpos = options[i].isset;
- }
-
switch (options[i].gen->type)
{
case RELOPT_TYPE_BOOL:
@@ -1798,6 +1871,11 @@ fillRelOptions(void *rdopts, Size basesize,
options[i].values.bool_val :
((relopt_bool *) options[i].gen)->default_val;
break;
+ case RELOPT_TYPE_TERNARY:
+ *(ternary *) itempos = options[i].isset ?
+ options[i].values.ternary_val :
+ ((relopt_ternary *) options[i].gen)->default_val;
+ break;
case RELOPT_TYPE_INT:
*(int *) itempos = options[i].isset ?
options[i].values.int_val :
@@ -1912,8 +1990,8 @@ default_reloptions(Datum reloptions, bool validate, relopt_kind kind)
offsetof(StdRdOptions, parallel_workers)},
{"vacuum_index_cleanup", RELOPT_TYPE_ENUM,
offsetof(StdRdOptions, vacuum_index_cleanup)},
- {"vacuum_truncate", RELOPT_TYPE_BOOL,
- offsetof(StdRdOptions, vacuum_truncate), offsetof(StdRdOptions, vacuum_truncate_set)},
+ {"vacuum_truncate", RELOPT_TYPE_TERNARY,
+ offsetof(StdRdOptions, vacuum_truncate)},
{"vacuum_max_eager_freeze_failure_rate", RELOPT_TYPE_REAL,
offsetof(StdRdOptions, vacuum_max_eager_freeze_failure_rate)}
};
@@ -1993,7 +2071,6 @@ build_local_reloptions(local_relopts *relopts, Datum options, bool validate)
elems[i].optname = opt->option->name;
elems[i].opttype = opt->option->type;
elems[i].offset = opt->offset;
- elems[i].isset_offset = 0; /* not supported for local relopts yet */
i++;
}
diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c
index 733ef40ae7..977babff54 100644
--- a/src/backend/commands/vacuum.c
+++ b/src/backend/commands/vacuum.c
@@ -2221,9 +2221,9 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams params,
{
StdRdOptions *opts = (StdRdOptions *) rel->rd_options;
- if (opts && opts->vacuum_truncate_set)
+ if (opts && opts->vacuum_truncate != TERNARY_UNSET)
{
- if (opts->vacuum_truncate)
+ if (opts->vacuum_truncate == TERNARY_TRUE)
params.truncate = VACOPTVALUE_ENABLED;
else
params.truncate = VACOPTVALUE_DISABLED;
diff --git a/src/include/access/reloptions.h b/src/include/access/reloptions.h
index a604a4702c..a436697658 100644
--- a/src/include/access/reloptions.h
+++ b/src/include/access/reloptions.h
@@ -29,6 +29,7 @@
typedef enum relopt_type
{
RELOPT_TYPE_BOOL,
+ RELOPT_TYPE_TERNARY, /* on, off, unset */
RELOPT_TYPE_INT,
RELOPT_TYPE_REAL,
RELOPT_TYPE_ENUM,
@@ -80,6 +81,7 @@ typedef struct relopt_value
union
{
bool bool_val;
+ ternary ternary_val;
int int_val;
double real_val;
int enum_val;
@@ -94,6 +96,12 @@ typedef struct relopt_bool
bool default_val;
} relopt_bool;
+typedef struct relopt_rernary
+{
+ relopt_gen gen;
+ int default_val;
+} relopt_ternary;
+
typedef struct relopt_int
{
relopt_gen gen;
@@ -152,19 +160,6 @@ typedef struct
const char *optname; /* option's name */
relopt_type opttype; /* option's datatype */
int offset; /* offset of field in result struct */
-
- /*
- * isset_offset is an optional offset of a field in the result struct that
- * stores whether the option is explicitly set for the relation or if it
- * just picked up the default value. In most cases, this can be
- * accomplished by giving the reloption a special out-of-range default
- * value (e.g., some integer reloptions use -2), but this isn't always
- * possible. For example, a Boolean reloption cannot be given an
- * out-of-range default, so we need another way to discover the source of
- * its value. This offset is only used if given a value greater than
- * zero.
- */
- int isset_offset;
} relopt_parse_elt;
/* Local reloption definition */
@@ -195,6 +190,8 @@ typedef struct local_relopts
extern relopt_kind add_reloption_kind(void);
extern void add_bool_reloption(bits32 kinds, const char *name, const char *desc,
bool default_val, LOCKMODE lockmode);
+extern void add_ternary_reloption(bits32 kinds, const char *name,
+ const char *desc, int default_val, LOCKMODE lockmode);
extern void add_int_reloption(bits32 kinds, const char *name, const char *desc,
int default_val, int min_val, int max_val,
LOCKMODE lockmode);
@@ -214,6 +211,9 @@ extern void register_reloptions_validator(local_relopts *relopts,
extern void add_local_bool_reloption(local_relopts *relopts, const char *name,
const char *desc, bool default_val,
int offset);
+extern void add_local_ternary_reloption(local_relopts *relopts,
+ const char *name, const char *desc,
+ ternary default_val, int offset);
extern void add_local_int_reloption(local_relopts *relopts, const char *name,
const char *desc, int default_val,
int min_val, int max_val, int offset);
diff --git a/src/include/c.h b/src/include/c.h
index 39022f8a9d..9c59ccccf8 100644
--- a/src/include/c.h
+++ b/src/include/c.h
@@ -488,6 +488,22 @@ typedef void (*pg_funcptr_t) (void);
#include <stdbool.h>
+/*
+ * ternary
+ * Boolean value with an extrea "unset" option
+ *
+ * Ternary data type is used in relation options that can be "true", "false" or
+ * "unset". Since relation options are used deep inside the PostgreSQL code,
+ * this type is declared globally.
+*/
+
+typedef enum ternary
+{
+ TERNARY_FALSE = 0,
+ TERNARY_TRUE = 1,
+ TERNARY_UNSET = -1
+} ternary;
+
/* ----------------------------------------------------------------
* Section 3: standard system types
diff --git a/src/include/utils/rel.h b/src/include/utils/rel.h
index b552359915..7346d618f9 100644
--- a/src/include/utils/rel.h
+++ b/src/include/utils/rel.h
@@ -346,8 +346,7 @@ typedef struct StdRdOptions
bool user_catalog_table; /* use as an additional catalog relation */
int parallel_workers; /* max number of parallel workers */
StdRdOptIndexCleanup vacuum_index_cleanup; /* controls index vacuuming */
- bool vacuum_truncate; /* enables vacuum to truncate a relation */
- bool vacuum_truncate_set; /* whether vacuum_truncate is set */
+ ternary vacuum_truncate; /* enables vacuum to truncate a relation */
/*
* Fraction of pages in a relation that vacuum can eagerly scan and fail
--
2.39.2
--nextPart5129932.5fSG56mABF
Content-Disposition: attachment;
filename="v2-0003-Add-alias-to-be-used-as-unset-state.patch"
Content-Transfer-Encoding: 7Bit
Content-Type: text/x-patch; charset="unicode-2-0-utf-8";
name="v2-0003-Add-alias-to-be-used-as-unset-state.patch"
^ permalink raw reply [nested|flat] 868+ messages in thread
* [PATCH v2 2/4] Introduce ternary reloptions
@ 2025-09-04 14:41 Nikolay Shaplov <[email protected]>
0 siblings, 0 replies; 868+ messages in thread
From: Nikolay Shaplov @ 2025-09-04 14:41 UTC (permalink / raw)
Introduce ternary reloption as a replacement for current `vacuum_truncate`
implementation. Remove `vacuum_truncate_set` additional flag and using
`TERNARY_UNSET` value instead.
---
src/backend/access/common/reloptions.c | 129 ++++++++++++++++++++-----
src/backend/commands/vacuum.c | 4 +-
src/include/access/reloptions.h | 26 ++---
src/include/c.h | 16 +++
src/include/utils/rel.h | 3 +-
5 files changed, 135 insertions(+), 43 deletions(-)
diff --git a/src/backend/access/common/reloptions.c b/src/backend/access/common/reloptions.c
index 0af3fea68f..f543f11001 100644
--- a/src/backend/access/common/reloptions.c
+++ b/src/backend/access/common/reloptions.c
@@ -40,9 +40,9 @@
*
* To add an option:
*
- * (i) decide on a type (bool, integer, real, enum, string), name, default
- * value, upper and lower bounds (if applicable); for strings, consider a
- * validation routine.
+ * (i) decide on a type (bool, ternary, integer, real, enum, string), name,
+ * default value, upper and lower bounds (if applicable); for strings,
+ * consider a validation routine.
* (ii) add a record below (or use add_<type>_reloption).
* (iii) add it to the appropriate options struct (perhaps StdRdOptions)
* (iv) add it to the appropriate handling routine (perhaps
@@ -147,15 +147,6 @@ static relopt_bool boolRelOpts[] =
},
false
},
- {
- {
- "vacuum_truncate",
- "Enables vacuum to truncate empty pages at the end of this table",
- RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
- ShareUpdateExclusiveLock
- },
- true
- },
{
{
"deduplicate_items",
@@ -170,6 +161,21 @@ static relopt_bool boolRelOpts[] =
{{NULL}}
};
+static relopt_ternary ternaryRelOpts[] =
+{
+ {
+ {
+ "vacuum_truncate",
+ "Enables vacuum to truncate empty pages at the end of this table",
+ RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
+ ShareUpdateExclusiveLock
+ },
+ TERNARY_UNSET
+ },
+ /* list terminator */
+ {{NULL}}
+};
+
static relopt_int intRelOpts[] =
{
{
@@ -600,6 +606,13 @@ initialize_reloptions(void)
boolRelOpts[i].gen.lockmode));
j++;
}
+ for (i = 0; ternaryRelOpts[i].gen.name; i++)
+ {
+ Assert(DoLockModesConflict(ternaryRelOpts[i].gen.lockmode,
+ ternaryRelOpts[i].gen.lockmode));
+ j++;
+ }
+
for (i = 0; intRelOpts[i].gen.name; i++)
{
Assert(DoLockModesConflict(intRelOpts[i].gen.lockmode,
@@ -640,6 +653,14 @@ initialize_reloptions(void)
j++;
}
+ for (i = 0; ternaryRelOpts[i].gen.name; i++)
+ {
+ relOpts[j] = &ternaryRelOpts[i].gen;
+ relOpts[j]->type = RELOPT_TYPE_TERNARY;
+ relOpts[j]->namelen = strlen(relOpts[j]->name);
+ j++;
+ }
+
for (i = 0; intRelOpts[i].gen.name; i++)
{
relOpts[j] = &intRelOpts[i].gen;
@@ -800,6 +821,9 @@ allocate_reloption(bits32 kinds, int type, const char *name, const char *desc,
case RELOPT_TYPE_BOOL:
size = sizeof(relopt_bool);
break;
+ case RELOPT_TYPE_TERNARY:
+ size = sizeof(relopt_ternary);
+ break;
case RELOPT_TYPE_INT:
size = sizeof(relopt_int);
break;
@@ -883,6 +907,54 @@ add_local_bool_reloption(local_relopts *relopts, const char *name,
add_local_reloption(relopts, (relopt_gen *) newoption, offset);
}
+/*
+ * init_ternary_reloption
+ * Allocate and initialize a new ternary reloption
+ */
+static relopt_ternary *
+init_ternary_reloption(bits32 kinds, const char *name, const char *desc,
+ ternary default_val, LOCKMODE lockmode)
+{
+ relopt_ternary *newoption;
+
+ newoption = (relopt_ternary *) allocate_reloption(kinds,
+ RELOPT_TYPE_TERNARY, name, desc, lockmode);
+ newoption->default_val = default_val;
+
+ return newoption;
+}
+
+/*
+ * add_ternary_reloption
+ * Add a new ternary reloption
+ */
+void
+add_ternary_reloption(bits32 kinds, const char *name, const char *desc,
+ ternary default_val, LOCKMODE lockmode)
+{
+ relopt_ternary *newoption = init_ternary_reloption(kinds, name, desc,
+ default_val, lockmode);
+
+ add_reloption((relopt_gen *) newoption);
+}
+
+/*
+ * add_local_ternary_reloption
+ * Add a new ternary local reloption
+ *
+ * 'offset' is offset of ternary-typed field.
+ */
+void
+add_local_ternary_reloption(local_relopts *relopts, const char *name,
+ const char *desc, ternary default_val,
+ int offset)
+{
+ relopt_ternary *newoption = init_ternary_reloption(RELOPT_KIND_LOCAL,
+ name, desc,
+ default_val, 0);
+
+ add_local_reloption(relopts, (relopt_gen *) newoption, offset);
+}
/*
* init_real_reloption
@@ -1617,6 +1689,18 @@ parse_one_reloption(relopt_value *option, char *text_str, int text_len,
option->gen->name, value)));
}
break;
+ case RELOPT_TYPE_TERNARY:
+ {
+ bool b;
+ parsed = parse_bool(value, &b);
+ option->values.ternary_val = b ? TERNARY_TRUE : TERNARY_FALSE;
+ if (validate && !parsed)
+ ereport(ERROR,
+ (errcode(ERRCODE_INVALID_PARAMETER_VALUE),
+ errmsg("invalid value for ternary option \"%s\": %s",
+ option->gen->name, value)));
+ }
+ break;
case RELOPT_TYPE_INT:
{
relopt_int *optint = (relopt_int *) option->gen;
@@ -1780,17 +1864,6 @@ fillRelOptions(void *rdopts, Size basesize,
char *itempos = ((char *) rdopts) + elems[j].offset;
char *string_val;
- /*
- * If isset_offset is provided, store whether the reloption is
- * set there.
- */
- if (elems[j].isset_offset > 0)
- {
- char *setpos = ((char *) rdopts) + elems[j].isset_offset;
-
- *(bool *) setpos = options[i].isset;
- }
-
switch (options[i].gen->type)
{
case RELOPT_TYPE_BOOL:
@@ -1798,6 +1871,11 @@ fillRelOptions(void *rdopts, Size basesize,
options[i].values.bool_val :
((relopt_bool *) options[i].gen)->default_val;
break;
+ case RELOPT_TYPE_TERNARY:
+ *(ternary *) itempos = options[i].isset ?
+ options[i].values.ternary_val :
+ ((relopt_ternary *) options[i].gen)->default_val;
+ break;
case RELOPT_TYPE_INT:
*(int *) itempos = options[i].isset ?
options[i].values.int_val :
@@ -1912,8 +1990,8 @@ default_reloptions(Datum reloptions, bool validate, relopt_kind kind)
offsetof(StdRdOptions, parallel_workers)},
{"vacuum_index_cleanup", RELOPT_TYPE_ENUM,
offsetof(StdRdOptions, vacuum_index_cleanup)},
- {"vacuum_truncate", RELOPT_TYPE_BOOL,
- offsetof(StdRdOptions, vacuum_truncate), offsetof(StdRdOptions, vacuum_truncate_set)},
+ {"vacuum_truncate", RELOPT_TYPE_TERNARY,
+ offsetof(StdRdOptions, vacuum_truncate)},
{"vacuum_max_eager_freeze_failure_rate", RELOPT_TYPE_REAL,
offsetof(StdRdOptions, vacuum_max_eager_freeze_failure_rate)}
};
@@ -1993,7 +2071,6 @@ build_local_reloptions(local_relopts *relopts, Datum options, bool validate)
elems[i].optname = opt->option->name;
elems[i].opttype = opt->option->type;
elems[i].offset = opt->offset;
- elems[i].isset_offset = 0; /* not supported for local relopts yet */
i++;
}
diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c
index 733ef40ae7..977babff54 100644
--- a/src/backend/commands/vacuum.c
+++ b/src/backend/commands/vacuum.c
@@ -2221,9 +2221,9 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams params,
{
StdRdOptions *opts = (StdRdOptions *) rel->rd_options;
- if (opts && opts->vacuum_truncate_set)
+ if (opts && opts->vacuum_truncate != TERNARY_UNSET)
{
- if (opts->vacuum_truncate)
+ if (opts->vacuum_truncate == TERNARY_TRUE)
params.truncate = VACOPTVALUE_ENABLED;
else
params.truncate = VACOPTVALUE_DISABLED;
diff --git a/src/include/access/reloptions.h b/src/include/access/reloptions.h
index a604a4702c..a436697658 100644
--- a/src/include/access/reloptions.h
+++ b/src/include/access/reloptions.h
@@ -29,6 +29,7 @@
typedef enum relopt_type
{
RELOPT_TYPE_BOOL,
+ RELOPT_TYPE_TERNARY, /* on, off, unset */
RELOPT_TYPE_INT,
RELOPT_TYPE_REAL,
RELOPT_TYPE_ENUM,
@@ -80,6 +81,7 @@ typedef struct relopt_value
union
{
bool bool_val;
+ ternary ternary_val;
int int_val;
double real_val;
int enum_val;
@@ -94,6 +96,12 @@ typedef struct relopt_bool
bool default_val;
} relopt_bool;
+typedef struct relopt_rernary
+{
+ relopt_gen gen;
+ int default_val;
+} relopt_ternary;
+
typedef struct relopt_int
{
relopt_gen gen;
@@ -152,19 +160,6 @@ typedef struct
const char *optname; /* option's name */
relopt_type opttype; /* option's datatype */
int offset; /* offset of field in result struct */
-
- /*
- * isset_offset is an optional offset of a field in the result struct that
- * stores whether the option is explicitly set for the relation or if it
- * just picked up the default value. In most cases, this can be
- * accomplished by giving the reloption a special out-of-range default
- * value (e.g., some integer reloptions use -2), but this isn't always
- * possible. For example, a Boolean reloption cannot be given an
- * out-of-range default, so we need another way to discover the source of
- * its value. This offset is only used if given a value greater than
- * zero.
- */
- int isset_offset;
} relopt_parse_elt;
/* Local reloption definition */
@@ -195,6 +190,8 @@ typedef struct local_relopts
extern relopt_kind add_reloption_kind(void);
extern void add_bool_reloption(bits32 kinds, const char *name, const char *desc,
bool default_val, LOCKMODE lockmode);
+extern void add_ternary_reloption(bits32 kinds, const char *name,
+ const char *desc, int default_val, LOCKMODE lockmode);
extern void add_int_reloption(bits32 kinds, const char *name, const char *desc,
int default_val, int min_val, int max_val,
LOCKMODE lockmode);
@@ -214,6 +211,9 @@ extern void register_reloptions_validator(local_relopts *relopts,
extern void add_local_bool_reloption(local_relopts *relopts, const char *name,
const char *desc, bool default_val,
int offset);
+extern void add_local_ternary_reloption(local_relopts *relopts,
+ const char *name, const char *desc,
+ ternary default_val, int offset);
extern void add_local_int_reloption(local_relopts *relopts, const char *name,
const char *desc, int default_val,
int min_val, int max_val, int offset);
diff --git a/src/include/c.h b/src/include/c.h
index 39022f8a9d..9c59ccccf8 100644
--- a/src/include/c.h
+++ b/src/include/c.h
@@ -488,6 +488,22 @@ typedef void (*pg_funcptr_t) (void);
#include <stdbool.h>
+/*
+ * ternary
+ * Boolean value with an extrea "unset" option
+ *
+ * Ternary data type is used in relation options that can be "true", "false" or
+ * "unset". Since relation options are used deep inside the PostgreSQL code,
+ * this type is declared globally.
+*/
+
+typedef enum ternary
+{
+ TERNARY_FALSE = 0,
+ TERNARY_TRUE = 1,
+ TERNARY_UNSET = -1
+} ternary;
+
/* ----------------------------------------------------------------
* Section 3: standard system types
diff --git a/src/include/utils/rel.h b/src/include/utils/rel.h
index b552359915..7346d618f9 100644
--- a/src/include/utils/rel.h
+++ b/src/include/utils/rel.h
@@ -346,8 +346,7 @@ typedef struct StdRdOptions
bool user_catalog_table; /* use as an additional catalog relation */
int parallel_workers; /* max number of parallel workers */
StdRdOptIndexCleanup vacuum_index_cleanup; /* controls index vacuuming */
- bool vacuum_truncate; /* enables vacuum to truncate a relation */
- bool vacuum_truncate_set; /* whether vacuum_truncate is set */
+ ternary vacuum_truncate; /* enables vacuum to truncate a relation */
/*
* Fraction of pages in a relation that vacuum can eagerly scan and fail
--
2.39.2
--nextPart5129932.5fSG56mABF
Content-Disposition: attachment;
filename="v2-0003-Add-alias-to-be-used-as-unset-state.patch"
Content-Transfer-Encoding: 7Bit
Content-Type: text/x-patch; charset="unicode-2-0-utf-8";
name="v2-0003-Add-alias-to-be-used-as-unset-state.patch"
^ permalink raw reply [nested|flat] 868+ messages in thread
* [PATCH v2 2/4] Introduce ternary reloptions
@ 2025-09-04 14:41 Nikolay Shaplov <[email protected]>
0 siblings, 0 replies; 868+ messages in thread
From: Nikolay Shaplov @ 2025-09-04 14:41 UTC (permalink / raw)
Introduce ternary reloption as a replacement for current `vacuum_truncate`
implementation. Remove `vacuum_truncate_set` additional flag and using
`TERNARY_UNSET` value instead.
---
src/backend/access/common/reloptions.c | 129 ++++++++++++++++++++-----
src/backend/commands/vacuum.c | 4 +-
src/include/access/reloptions.h | 26 ++---
src/include/c.h | 16 +++
src/include/utils/rel.h | 3 +-
5 files changed, 135 insertions(+), 43 deletions(-)
diff --git a/src/backend/access/common/reloptions.c b/src/backend/access/common/reloptions.c
index 0af3fea68f..f543f11001 100644
--- a/src/backend/access/common/reloptions.c
+++ b/src/backend/access/common/reloptions.c
@@ -40,9 +40,9 @@
*
* To add an option:
*
- * (i) decide on a type (bool, integer, real, enum, string), name, default
- * value, upper and lower bounds (if applicable); for strings, consider a
- * validation routine.
+ * (i) decide on a type (bool, ternary, integer, real, enum, string), name,
+ * default value, upper and lower bounds (if applicable); for strings,
+ * consider a validation routine.
* (ii) add a record below (or use add_<type>_reloption).
* (iii) add it to the appropriate options struct (perhaps StdRdOptions)
* (iv) add it to the appropriate handling routine (perhaps
@@ -147,15 +147,6 @@ static relopt_bool boolRelOpts[] =
},
false
},
- {
- {
- "vacuum_truncate",
- "Enables vacuum to truncate empty pages at the end of this table",
- RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
- ShareUpdateExclusiveLock
- },
- true
- },
{
{
"deduplicate_items",
@@ -170,6 +161,21 @@ static relopt_bool boolRelOpts[] =
{{NULL}}
};
+static relopt_ternary ternaryRelOpts[] =
+{
+ {
+ {
+ "vacuum_truncate",
+ "Enables vacuum to truncate empty pages at the end of this table",
+ RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
+ ShareUpdateExclusiveLock
+ },
+ TERNARY_UNSET
+ },
+ /* list terminator */
+ {{NULL}}
+};
+
static relopt_int intRelOpts[] =
{
{
@@ -600,6 +606,13 @@ initialize_reloptions(void)
boolRelOpts[i].gen.lockmode));
j++;
}
+ for (i = 0; ternaryRelOpts[i].gen.name; i++)
+ {
+ Assert(DoLockModesConflict(ternaryRelOpts[i].gen.lockmode,
+ ternaryRelOpts[i].gen.lockmode));
+ j++;
+ }
+
for (i = 0; intRelOpts[i].gen.name; i++)
{
Assert(DoLockModesConflict(intRelOpts[i].gen.lockmode,
@@ -640,6 +653,14 @@ initialize_reloptions(void)
j++;
}
+ for (i = 0; ternaryRelOpts[i].gen.name; i++)
+ {
+ relOpts[j] = &ternaryRelOpts[i].gen;
+ relOpts[j]->type = RELOPT_TYPE_TERNARY;
+ relOpts[j]->namelen = strlen(relOpts[j]->name);
+ j++;
+ }
+
for (i = 0; intRelOpts[i].gen.name; i++)
{
relOpts[j] = &intRelOpts[i].gen;
@@ -800,6 +821,9 @@ allocate_reloption(bits32 kinds, int type, const char *name, const char *desc,
case RELOPT_TYPE_BOOL:
size = sizeof(relopt_bool);
break;
+ case RELOPT_TYPE_TERNARY:
+ size = sizeof(relopt_ternary);
+ break;
case RELOPT_TYPE_INT:
size = sizeof(relopt_int);
break;
@@ -883,6 +907,54 @@ add_local_bool_reloption(local_relopts *relopts, const char *name,
add_local_reloption(relopts, (relopt_gen *) newoption, offset);
}
+/*
+ * init_ternary_reloption
+ * Allocate and initialize a new ternary reloption
+ */
+static relopt_ternary *
+init_ternary_reloption(bits32 kinds, const char *name, const char *desc,
+ ternary default_val, LOCKMODE lockmode)
+{
+ relopt_ternary *newoption;
+
+ newoption = (relopt_ternary *) allocate_reloption(kinds,
+ RELOPT_TYPE_TERNARY, name, desc, lockmode);
+ newoption->default_val = default_val;
+
+ return newoption;
+}
+
+/*
+ * add_ternary_reloption
+ * Add a new ternary reloption
+ */
+void
+add_ternary_reloption(bits32 kinds, const char *name, const char *desc,
+ ternary default_val, LOCKMODE lockmode)
+{
+ relopt_ternary *newoption = init_ternary_reloption(kinds, name, desc,
+ default_val, lockmode);
+
+ add_reloption((relopt_gen *) newoption);
+}
+
+/*
+ * add_local_ternary_reloption
+ * Add a new ternary local reloption
+ *
+ * 'offset' is offset of ternary-typed field.
+ */
+void
+add_local_ternary_reloption(local_relopts *relopts, const char *name,
+ const char *desc, ternary default_val,
+ int offset)
+{
+ relopt_ternary *newoption = init_ternary_reloption(RELOPT_KIND_LOCAL,
+ name, desc,
+ default_val, 0);
+
+ add_local_reloption(relopts, (relopt_gen *) newoption, offset);
+}
/*
* init_real_reloption
@@ -1617,6 +1689,18 @@ parse_one_reloption(relopt_value *option, char *text_str, int text_len,
option->gen->name, value)));
}
break;
+ case RELOPT_TYPE_TERNARY:
+ {
+ bool b;
+ parsed = parse_bool(value, &b);
+ option->values.ternary_val = b ? TERNARY_TRUE : TERNARY_FALSE;
+ if (validate && !parsed)
+ ereport(ERROR,
+ (errcode(ERRCODE_INVALID_PARAMETER_VALUE),
+ errmsg("invalid value for ternary option \"%s\": %s",
+ option->gen->name, value)));
+ }
+ break;
case RELOPT_TYPE_INT:
{
relopt_int *optint = (relopt_int *) option->gen;
@@ -1780,17 +1864,6 @@ fillRelOptions(void *rdopts, Size basesize,
char *itempos = ((char *) rdopts) + elems[j].offset;
char *string_val;
- /*
- * If isset_offset is provided, store whether the reloption is
- * set there.
- */
- if (elems[j].isset_offset > 0)
- {
- char *setpos = ((char *) rdopts) + elems[j].isset_offset;
-
- *(bool *) setpos = options[i].isset;
- }
-
switch (options[i].gen->type)
{
case RELOPT_TYPE_BOOL:
@@ -1798,6 +1871,11 @@ fillRelOptions(void *rdopts, Size basesize,
options[i].values.bool_val :
((relopt_bool *) options[i].gen)->default_val;
break;
+ case RELOPT_TYPE_TERNARY:
+ *(ternary *) itempos = options[i].isset ?
+ options[i].values.ternary_val :
+ ((relopt_ternary *) options[i].gen)->default_val;
+ break;
case RELOPT_TYPE_INT:
*(int *) itempos = options[i].isset ?
options[i].values.int_val :
@@ -1912,8 +1990,8 @@ default_reloptions(Datum reloptions, bool validate, relopt_kind kind)
offsetof(StdRdOptions, parallel_workers)},
{"vacuum_index_cleanup", RELOPT_TYPE_ENUM,
offsetof(StdRdOptions, vacuum_index_cleanup)},
- {"vacuum_truncate", RELOPT_TYPE_BOOL,
- offsetof(StdRdOptions, vacuum_truncate), offsetof(StdRdOptions, vacuum_truncate_set)},
+ {"vacuum_truncate", RELOPT_TYPE_TERNARY,
+ offsetof(StdRdOptions, vacuum_truncate)},
{"vacuum_max_eager_freeze_failure_rate", RELOPT_TYPE_REAL,
offsetof(StdRdOptions, vacuum_max_eager_freeze_failure_rate)}
};
@@ -1993,7 +2071,6 @@ build_local_reloptions(local_relopts *relopts, Datum options, bool validate)
elems[i].optname = opt->option->name;
elems[i].opttype = opt->option->type;
elems[i].offset = opt->offset;
- elems[i].isset_offset = 0; /* not supported for local relopts yet */
i++;
}
diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c
index 733ef40ae7..977babff54 100644
--- a/src/backend/commands/vacuum.c
+++ b/src/backend/commands/vacuum.c
@@ -2221,9 +2221,9 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams params,
{
StdRdOptions *opts = (StdRdOptions *) rel->rd_options;
- if (opts && opts->vacuum_truncate_set)
+ if (opts && opts->vacuum_truncate != TERNARY_UNSET)
{
- if (opts->vacuum_truncate)
+ if (opts->vacuum_truncate == TERNARY_TRUE)
params.truncate = VACOPTVALUE_ENABLED;
else
params.truncate = VACOPTVALUE_DISABLED;
diff --git a/src/include/access/reloptions.h b/src/include/access/reloptions.h
index a604a4702c..a436697658 100644
--- a/src/include/access/reloptions.h
+++ b/src/include/access/reloptions.h
@@ -29,6 +29,7 @@
typedef enum relopt_type
{
RELOPT_TYPE_BOOL,
+ RELOPT_TYPE_TERNARY, /* on, off, unset */
RELOPT_TYPE_INT,
RELOPT_TYPE_REAL,
RELOPT_TYPE_ENUM,
@@ -80,6 +81,7 @@ typedef struct relopt_value
union
{
bool bool_val;
+ ternary ternary_val;
int int_val;
double real_val;
int enum_val;
@@ -94,6 +96,12 @@ typedef struct relopt_bool
bool default_val;
} relopt_bool;
+typedef struct relopt_rernary
+{
+ relopt_gen gen;
+ int default_val;
+} relopt_ternary;
+
typedef struct relopt_int
{
relopt_gen gen;
@@ -152,19 +160,6 @@ typedef struct
const char *optname; /* option's name */
relopt_type opttype; /* option's datatype */
int offset; /* offset of field in result struct */
-
- /*
- * isset_offset is an optional offset of a field in the result struct that
- * stores whether the option is explicitly set for the relation or if it
- * just picked up the default value. In most cases, this can be
- * accomplished by giving the reloption a special out-of-range default
- * value (e.g., some integer reloptions use -2), but this isn't always
- * possible. For example, a Boolean reloption cannot be given an
- * out-of-range default, so we need another way to discover the source of
- * its value. This offset is only used if given a value greater than
- * zero.
- */
- int isset_offset;
} relopt_parse_elt;
/* Local reloption definition */
@@ -195,6 +190,8 @@ typedef struct local_relopts
extern relopt_kind add_reloption_kind(void);
extern void add_bool_reloption(bits32 kinds, const char *name, const char *desc,
bool default_val, LOCKMODE lockmode);
+extern void add_ternary_reloption(bits32 kinds, const char *name,
+ const char *desc, int default_val, LOCKMODE lockmode);
extern void add_int_reloption(bits32 kinds, const char *name, const char *desc,
int default_val, int min_val, int max_val,
LOCKMODE lockmode);
@@ -214,6 +211,9 @@ extern void register_reloptions_validator(local_relopts *relopts,
extern void add_local_bool_reloption(local_relopts *relopts, const char *name,
const char *desc, bool default_val,
int offset);
+extern void add_local_ternary_reloption(local_relopts *relopts,
+ const char *name, const char *desc,
+ ternary default_val, int offset);
extern void add_local_int_reloption(local_relopts *relopts, const char *name,
const char *desc, int default_val,
int min_val, int max_val, int offset);
diff --git a/src/include/c.h b/src/include/c.h
index 39022f8a9d..9c59ccccf8 100644
--- a/src/include/c.h
+++ b/src/include/c.h
@@ -488,6 +488,22 @@ typedef void (*pg_funcptr_t) (void);
#include <stdbool.h>
+/*
+ * ternary
+ * Boolean value with an extrea "unset" option
+ *
+ * Ternary data type is used in relation options that can be "true", "false" or
+ * "unset". Since relation options are used deep inside the PostgreSQL code,
+ * this type is declared globally.
+*/
+
+typedef enum ternary
+{
+ TERNARY_FALSE = 0,
+ TERNARY_TRUE = 1,
+ TERNARY_UNSET = -1
+} ternary;
+
/* ----------------------------------------------------------------
* Section 3: standard system types
diff --git a/src/include/utils/rel.h b/src/include/utils/rel.h
index b552359915..7346d618f9 100644
--- a/src/include/utils/rel.h
+++ b/src/include/utils/rel.h
@@ -346,8 +346,7 @@ typedef struct StdRdOptions
bool user_catalog_table; /* use as an additional catalog relation */
int parallel_workers; /* max number of parallel workers */
StdRdOptIndexCleanup vacuum_index_cleanup; /* controls index vacuuming */
- bool vacuum_truncate; /* enables vacuum to truncate a relation */
- bool vacuum_truncate_set; /* whether vacuum_truncate is set */
+ ternary vacuum_truncate; /* enables vacuum to truncate a relation */
/*
* Fraction of pages in a relation that vacuum can eagerly scan and fail
--
2.39.2
--nextPart5129932.5fSG56mABF
Content-Disposition: attachment;
filename="v2-0003-Add-alias-to-be-used-as-unset-state.patch"
Content-Transfer-Encoding: 7Bit
Content-Type: text/x-patch; charset="unicode-2-0-utf-8";
name="v2-0003-Add-alias-to-be-used-as-unset-state.patch"
^ permalink raw reply [nested|flat] 868+ messages in thread
* [PATCH v2a 2/4] Introduce ternary reloptions
@ 2025-09-04 14:41 Nikolay Shaplov <[email protected]>
0 siblings, 0 replies; 868+ messages in thread
From: Nikolay Shaplov @ 2025-09-04 14:41 UTC (permalink / raw)
Introduce ternary reloption as a replacement for current `vacuum_truncate`
implementation. Remove `vacuum_truncate_set` additional flag and using
`TERNARY_UNSET` value instead.
---
src/backend/access/common/reloptions.c | 129 ++++++++++++++++++++-----
src/backend/commands/vacuum.c | 4 +-
src/include/access/reloptions.h | 26 ++---
src/include/c.h | 16 +++
src/include/utils/rel.h | 3 +-
5 files changed, 135 insertions(+), 43 deletions(-)
diff --git a/src/backend/access/common/reloptions.c b/src/backend/access/common/reloptions.c
index 0af3fea68f..f543f11001 100644
--- a/src/backend/access/common/reloptions.c
+++ b/src/backend/access/common/reloptions.c
@@ -40,9 +40,9 @@
*
* To add an option:
*
- * (i) decide on a type (bool, integer, real, enum, string), name, default
- * value, upper and lower bounds (if applicable); for strings, consider a
- * validation routine.
+ * (i) decide on a type (bool, ternary, integer, real, enum, string), name,
+ * default value, upper and lower bounds (if applicable); for strings,
+ * consider a validation routine.
* (ii) add a record below (or use add_<type>_reloption).
* (iii) add it to the appropriate options struct (perhaps StdRdOptions)
* (iv) add it to the appropriate handling routine (perhaps
@@ -147,15 +147,6 @@ static relopt_bool boolRelOpts[] =
},
false
},
- {
- {
- "vacuum_truncate",
- "Enables vacuum to truncate empty pages at the end of this table",
- RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
- ShareUpdateExclusiveLock
- },
- true
- },
{
{
"deduplicate_items",
@@ -170,6 +161,21 @@ static relopt_bool boolRelOpts[] =
{{NULL}}
};
+static relopt_ternary ternaryRelOpts[] =
+{
+ {
+ {
+ "vacuum_truncate",
+ "Enables vacuum to truncate empty pages at the end of this table",
+ RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
+ ShareUpdateExclusiveLock
+ },
+ TERNARY_UNSET
+ },
+ /* list terminator */
+ {{NULL}}
+};
+
static relopt_int intRelOpts[] =
{
{
@@ -600,6 +606,13 @@ initialize_reloptions(void)
boolRelOpts[i].gen.lockmode));
j++;
}
+ for (i = 0; ternaryRelOpts[i].gen.name; i++)
+ {
+ Assert(DoLockModesConflict(ternaryRelOpts[i].gen.lockmode,
+ ternaryRelOpts[i].gen.lockmode));
+ j++;
+ }
+
for (i = 0; intRelOpts[i].gen.name; i++)
{
Assert(DoLockModesConflict(intRelOpts[i].gen.lockmode,
@@ -640,6 +653,14 @@ initialize_reloptions(void)
j++;
}
+ for (i = 0; ternaryRelOpts[i].gen.name; i++)
+ {
+ relOpts[j] = &ternaryRelOpts[i].gen;
+ relOpts[j]->type = RELOPT_TYPE_TERNARY;
+ relOpts[j]->namelen = strlen(relOpts[j]->name);
+ j++;
+ }
+
for (i = 0; intRelOpts[i].gen.name; i++)
{
relOpts[j] = &intRelOpts[i].gen;
@@ -800,6 +821,9 @@ allocate_reloption(bits32 kinds, int type, const char *name, const char *desc,
case RELOPT_TYPE_BOOL:
size = sizeof(relopt_bool);
break;
+ case RELOPT_TYPE_TERNARY:
+ size = sizeof(relopt_ternary);
+ break;
case RELOPT_TYPE_INT:
size = sizeof(relopt_int);
break;
@@ -883,6 +907,54 @@ add_local_bool_reloption(local_relopts *relopts, const char *name,
add_local_reloption(relopts, (relopt_gen *) newoption, offset);
}
+/*
+ * init_ternary_reloption
+ * Allocate and initialize a new ternary reloption
+ */
+static relopt_ternary *
+init_ternary_reloption(bits32 kinds, const char *name, const char *desc,
+ ternary default_val, LOCKMODE lockmode)
+{
+ relopt_ternary *newoption;
+
+ newoption = (relopt_ternary *) allocate_reloption(kinds,
+ RELOPT_TYPE_TERNARY, name, desc, lockmode);
+ newoption->default_val = default_val;
+
+ return newoption;
+}
+
+/*
+ * add_ternary_reloption
+ * Add a new ternary reloption
+ */
+void
+add_ternary_reloption(bits32 kinds, const char *name, const char *desc,
+ ternary default_val, LOCKMODE lockmode)
+{
+ relopt_ternary *newoption = init_ternary_reloption(kinds, name, desc,
+ default_val, lockmode);
+
+ add_reloption((relopt_gen *) newoption);
+}
+
+/*
+ * add_local_ternary_reloption
+ * Add a new ternary local reloption
+ *
+ * 'offset' is offset of ternary-typed field.
+ */
+void
+add_local_ternary_reloption(local_relopts *relopts, const char *name,
+ const char *desc, ternary default_val,
+ int offset)
+{
+ relopt_ternary *newoption = init_ternary_reloption(RELOPT_KIND_LOCAL,
+ name, desc,
+ default_val, 0);
+
+ add_local_reloption(relopts, (relopt_gen *) newoption, offset);
+}
/*
* init_real_reloption
@@ -1617,6 +1689,18 @@ parse_one_reloption(relopt_value *option, char *text_str, int text_len,
option->gen->name, value)));
}
break;
+ case RELOPT_TYPE_TERNARY:
+ {
+ bool b;
+ parsed = parse_bool(value, &b);
+ option->values.ternary_val = b ? TERNARY_TRUE : TERNARY_FALSE;
+ if (validate && !parsed)
+ ereport(ERROR,
+ (errcode(ERRCODE_INVALID_PARAMETER_VALUE),
+ errmsg("invalid value for ternary option \"%s\": %s",
+ option->gen->name, value)));
+ }
+ break;
case RELOPT_TYPE_INT:
{
relopt_int *optint = (relopt_int *) option->gen;
@@ -1780,17 +1864,6 @@ fillRelOptions(void *rdopts, Size basesize,
char *itempos = ((char *) rdopts) + elems[j].offset;
char *string_val;
- /*
- * If isset_offset is provided, store whether the reloption is
- * set there.
- */
- if (elems[j].isset_offset > 0)
- {
- char *setpos = ((char *) rdopts) + elems[j].isset_offset;
-
- *(bool *) setpos = options[i].isset;
- }
-
switch (options[i].gen->type)
{
case RELOPT_TYPE_BOOL:
@@ -1798,6 +1871,11 @@ fillRelOptions(void *rdopts, Size basesize,
options[i].values.bool_val :
((relopt_bool *) options[i].gen)->default_val;
break;
+ case RELOPT_TYPE_TERNARY:
+ *(ternary *) itempos = options[i].isset ?
+ options[i].values.ternary_val :
+ ((relopt_ternary *) options[i].gen)->default_val;
+ break;
case RELOPT_TYPE_INT:
*(int *) itempos = options[i].isset ?
options[i].values.int_val :
@@ -1912,8 +1990,8 @@ default_reloptions(Datum reloptions, bool validate, relopt_kind kind)
offsetof(StdRdOptions, parallel_workers)},
{"vacuum_index_cleanup", RELOPT_TYPE_ENUM,
offsetof(StdRdOptions, vacuum_index_cleanup)},
- {"vacuum_truncate", RELOPT_TYPE_BOOL,
- offsetof(StdRdOptions, vacuum_truncate), offsetof(StdRdOptions, vacuum_truncate_set)},
+ {"vacuum_truncate", RELOPT_TYPE_TERNARY,
+ offsetof(StdRdOptions, vacuum_truncate)},
{"vacuum_max_eager_freeze_failure_rate", RELOPT_TYPE_REAL,
offsetof(StdRdOptions, vacuum_max_eager_freeze_failure_rate)}
};
@@ -1993,7 +2071,6 @@ build_local_reloptions(local_relopts *relopts, Datum options, bool validate)
elems[i].optname = opt->option->name;
elems[i].opttype = opt->option->type;
elems[i].offset = opt->offset;
- elems[i].isset_offset = 0; /* not supported for local relopts yet */
i++;
}
diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c
index 733ef40ae7..977babff54 100644
--- a/src/backend/commands/vacuum.c
+++ b/src/backend/commands/vacuum.c
@@ -2221,9 +2221,9 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams params,
{
StdRdOptions *opts = (StdRdOptions *) rel->rd_options;
- if (opts && opts->vacuum_truncate_set)
+ if (opts && opts->vacuum_truncate != TERNARY_UNSET)
{
- if (opts->vacuum_truncate)
+ if (opts->vacuum_truncate == TERNARY_TRUE)
params.truncate = VACOPTVALUE_ENABLED;
else
params.truncate = VACOPTVALUE_DISABLED;
diff --git a/src/include/access/reloptions.h b/src/include/access/reloptions.h
index a604a4702c..a436697658 100644
--- a/src/include/access/reloptions.h
+++ b/src/include/access/reloptions.h
@@ -29,6 +29,7 @@
typedef enum relopt_type
{
RELOPT_TYPE_BOOL,
+ RELOPT_TYPE_TERNARY, /* on, off, unset */
RELOPT_TYPE_INT,
RELOPT_TYPE_REAL,
RELOPT_TYPE_ENUM,
@@ -80,6 +81,7 @@ typedef struct relopt_value
union
{
bool bool_val;
+ ternary ternary_val;
int int_val;
double real_val;
int enum_val;
@@ -94,6 +96,12 @@ typedef struct relopt_bool
bool default_val;
} relopt_bool;
+typedef struct relopt_rernary
+{
+ relopt_gen gen;
+ int default_val;
+} relopt_ternary;
+
typedef struct relopt_int
{
relopt_gen gen;
@@ -152,19 +160,6 @@ typedef struct
const char *optname; /* option's name */
relopt_type opttype; /* option's datatype */
int offset; /* offset of field in result struct */
-
- /*
- * isset_offset is an optional offset of a field in the result struct that
- * stores whether the option is explicitly set for the relation or if it
- * just picked up the default value. In most cases, this can be
- * accomplished by giving the reloption a special out-of-range default
- * value (e.g., some integer reloptions use -2), but this isn't always
- * possible. For example, a Boolean reloption cannot be given an
- * out-of-range default, so we need another way to discover the source of
- * its value. This offset is only used if given a value greater than
- * zero.
- */
- int isset_offset;
} relopt_parse_elt;
/* Local reloption definition */
@@ -195,6 +190,8 @@ typedef struct local_relopts
extern relopt_kind add_reloption_kind(void);
extern void add_bool_reloption(bits32 kinds, const char *name, const char *desc,
bool default_val, LOCKMODE lockmode);
+extern void add_ternary_reloption(bits32 kinds, const char *name,
+ const char *desc, int default_val, LOCKMODE lockmode);
extern void add_int_reloption(bits32 kinds, const char *name, const char *desc,
int default_val, int min_val, int max_val,
LOCKMODE lockmode);
@@ -214,6 +211,9 @@ extern void register_reloptions_validator(local_relopts *relopts,
extern void add_local_bool_reloption(local_relopts *relopts, const char *name,
const char *desc, bool default_val,
int offset);
+extern void add_local_ternary_reloption(local_relopts *relopts,
+ const char *name, const char *desc,
+ ternary default_val, int offset);
extern void add_local_int_reloption(local_relopts *relopts, const char *name,
const char *desc, int default_val,
int min_val, int max_val, int offset);
diff --git a/src/include/c.h b/src/include/c.h
index 39022f8a9d..9c59ccccf8 100644
--- a/src/include/c.h
+++ b/src/include/c.h
@@ -488,6 +488,22 @@ typedef void (*pg_funcptr_t) (void);
#include <stdbool.h>
+/*
+ * ternary
+ * Boolean value with an extrea "unset" option
+ *
+ * Ternary data type is used in relation options that can be "true", "false" or
+ * "unset". Since relation options are used deep inside the PostgreSQL code,
+ * this type is declared globally.
+*/
+
+typedef enum ternary
+{
+ TERNARY_FALSE = 0,
+ TERNARY_TRUE = 1,
+ TERNARY_UNSET = -1
+} ternary;
+
/* ----------------------------------------------------------------
* Section 3: standard system types
diff --git a/src/include/utils/rel.h b/src/include/utils/rel.h
index b552359915..7346d618f9 100644
--- a/src/include/utils/rel.h
+++ b/src/include/utils/rel.h
@@ -346,8 +346,7 @@ typedef struct StdRdOptions
bool user_catalog_table; /* use as an additional catalog relation */
int parallel_workers; /* max number of parallel workers */
StdRdOptIndexCleanup vacuum_index_cleanup; /* controls index vacuuming */
- bool vacuum_truncate; /* enables vacuum to truncate a relation */
- bool vacuum_truncate_set; /* whether vacuum_truncate is set */
+ ternary vacuum_truncate; /* enables vacuum to truncate a relation */
/*
* Fraction of pages in a relation that vacuum can eagerly scan and fail
--
2.39.2
--nextPart4155600.3ZeAukHxDK
Content-Disposition: attachment;
filename="v2a-0003-Add-alias-to-be-used-as-unset-state.patch"
Content-Transfer-Encoding: 7Bit
Content-Type: text/x-patch; charset="unicode-2-0-utf-8";
name="v2a-0003-Add-alias-to-be-used-as-unset-state.patch"
^ permalink raw reply [nested|flat] 868+ messages in thread
* [PATCH v2 2/4] Introduce ternary reloptions
@ 2025-09-04 14:41 Nikolay Shaplov <[email protected]>
0 siblings, 0 replies; 868+ messages in thread
From: Nikolay Shaplov @ 2025-09-04 14:41 UTC (permalink / raw)
Introduce ternary reloption as a replacement for current `vacuum_truncate`
implementation. Remove `vacuum_truncate_set` additional flag and using
`TERNARY_UNSET` value instead.
---
src/backend/access/common/reloptions.c | 129 ++++++++++++++++++++-----
src/backend/commands/vacuum.c | 4 +-
src/include/access/reloptions.h | 26 ++---
src/include/c.h | 16 +++
src/include/utils/rel.h | 3 +-
5 files changed, 135 insertions(+), 43 deletions(-)
diff --git a/src/backend/access/common/reloptions.c b/src/backend/access/common/reloptions.c
index 0af3fea68f..f543f11001 100644
--- a/src/backend/access/common/reloptions.c
+++ b/src/backend/access/common/reloptions.c
@@ -40,9 +40,9 @@
*
* To add an option:
*
- * (i) decide on a type (bool, integer, real, enum, string), name, default
- * value, upper and lower bounds (if applicable); for strings, consider a
- * validation routine.
+ * (i) decide on a type (bool, ternary, integer, real, enum, string), name,
+ * default value, upper and lower bounds (if applicable); for strings,
+ * consider a validation routine.
* (ii) add a record below (or use add_<type>_reloption).
* (iii) add it to the appropriate options struct (perhaps StdRdOptions)
* (iv) add it to the appropriate handling routine (perhaps
@@ -147,15 +147,6 @@ static relopt_bool boolRelOpts[] =
},
false
},
- {
- {
- "vacuum_truncate",
- "Enables vacuum to truncate empty pages at the end of this table",
- RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
- ShareUpdateExclusiveLock
- },
- true
- },
{
{
"deduplicate_items",
@@ -170,6 +161,21 @@ static relopt_bool boolRelOpts[] =
{{NULL}}
};
+static relopt_ternary ternaryRelOpts[] =
+{
+ {
+ {
+ "vacuum_truncate",
+ "Enables vacuum to truncate empty pages at the end of this table",
+ RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
+ ShareUpdateExclusiveLock
+ },
+ TERNARY_UNSET
+ },
+ /* list terminator */
+ {{NULL}}
+};
+
static relopt_int intRelOpts[] =
{
{
@@ -600,6 +606,13 @@ initialize_reloptions(void)
boolRelOpts[i].gen.lockmode));
j++;
}
+ for (i = 0; ternaryRelOpts[i].gen.name; i++)
+ {
+ Assert(DoLockModesConflict(ternaryRelOpts[i].gen.lockmode,
+ ternaryRelOpts[i].gen.lockmode));
+ j++;
+ }
+
for (i = 0; intRelOpts[i].gen.name; i++)
{
Assert(DoLockModesConflict(intRelOpts[i].gen.lockmode,
@@ -640,6 +653,14 @@ initialize_reloptions(void)
j++;
}
+ for (i = 0; ternaryRelOpts[i].gen.name; i++)
+ {
+ relOpts[j] = &ternaryRelOpts[i].gen;
+ relOpts[j]->type = RELOPT_TYPE_TERNARY;
+ relOpts[j]->namelen = strlen(relOpts[j]->name);
+ j++;
+ }
+
for (i = 0; intRelOpts[i].gen.name; i++)
{
relOpts[j] = &intRelOpts[i].gen;
@@ -800,6 +821,9 @@ allocate_reloption(bits32 kinds, int type, const char *name, const char *desc,
case RELOPT_TYPE_BOOL:
size = sizeof(relopt_bool);
break;
+ case RELOPT_TYPE_TERNARY:
+ size = sizeof(relopt_ternary);
+ break;
case RELOPT_TYPE_INT:
size = sizeof(relopt_int);
break;
@@ -883,6 +907,54 @@ add_local_bool_reloption(local_relopts *relopts, const char *name,
add_local_reloption(relopts, (relopt_gen *) newoption, offset);
}
+/*
+ * init_ternary_reloption
+ * Allocate and initialize a new ternary reloption
+ */
+static relopt_ternary *
+init_ternary_reloption(bits32 kinds, const char *name, const char *desc,
+ ternary default_val, LOCKMODE lockmode)
+{
+ relopt_ternary *newoption;
+
+ newoption = (relopt_ternary *) allocate_reloption(kinds,
+ RELOPT_TYPE_TERNARY, name, desc, lockmode);
+ newoption->default_val = default_val;
+
+ return newoption;
+}
+
+/*
+ * add_ternary_reloption
+ * Add a new ternary reloption
+ */
+void
+add_ternary_reloption(bits32 kinds, const char *name, const char *desc,
+ ternary default_val, LOCKMODE lockmode)
+{
+ relopt_ternary *newoption = init_ternary_reloption(kinds, name, desc,
+ default_val, lockmode);
+
+ add_reloption((relopt_gen *) newoption);
+}
+
+/*
+ * add_local_ternary_reloption
+ * Add a new ternary local reloption
+ *
+ * 'offset' is offset of ternary-typed field.
+ */
+void
+add_local_ternary_reloption(local_relopts *relopts, const char *name,
+ const char *desc, ternary default_val,
+ int offset)
+{
+ relopt_ternary *newoption = init_ternary_reloption(RELOPT_KIND_LOCAL,
+ name, desc,
+ default_val, 0);
+
+ add_local_reloption(relopts, (relopt_gen *) newoption, offset);
+}
/*
* init_real_reloption
@@ -1617,6 +1689,18 @@ parse_one_reloption(relopt_value *option, char *text_str, int text_len,
option->gen->name, value)));
}
break;
+ case RELOPT_TYPE_TERNARY:
+ {
+ bool b;
+ parsed = parse_bool(value, &b);
+ option->values.ternary_val = b ? TERNARY_TRUE : TERNARY_FALSE;
+ if (validate && !parsed)
+ ereport(ERROR,
+ (errcode(ERRCODE_INVALID_PARAMETER_VALUE),
+ errmsg("invalid value for ternary option \"%s\": %s",
+ option->gen->name, value)));
+ }
+ break;
case RELOPT_TYPE_INT:
{
relopt_int *optint = (relopt_int *) option->gen;
@@ -1780,17 +1864,6 @@ fillRelOptions(void *rdopts, Size basesize,
char *itempos = ((char *) rdopts) + elems[j].offset;
char *string_val;
- /*
- * If isset_offset is provided, store whether the reloption is
- * set there.
- */
- if (elems[j].isset_offset > 0)
- {
- char *setpos = ((char *) rdopts) + elems[j].isset_offset;
-
- *(bool *) setpos = options[i].isset;
- }
-
switch (options[i].gen->type)
{
case RELOPT_TYPE_BOOL:
@@ -1798,6 +1871,11 @@ fillRelOptions(void *rdopts, Size basesize,
options[i].values.bool_val :
((relopt_bool *) options[i].gen)->default_val;
break;
+ case RELOPT_TYPE_TERNARY:
+ *(ternary *) itempos = options[i].isset ?
+ options[i].values.ternary_val :
+ ((relopt_ternary *) options[i].gen)->default_val;
+ break;
case RELOPT_TYPE_INT:
*(int *) itempos = options[i].isset ?
options[i].values.int_val :
@@ -1912,8 +1990,8 @@ default_reloptions(Datum reloptions, bool validate, relopt_kind kind)
offsetof(StdRdOptions, parallel_workers)},
{"vacuum_index_cleanup", RELOPT_TYPE_ENUM,
offsetof(StdRdOptions, vacuum_index_cleanup)},
- {"vacuum_truncate", RELOPT_TYPE_BOOL,
- offsetof(StdRdOptions, vacuum_truncate), offsetof(StdRdOptions, vacuum_truncate_set)},
+ {"vacuum_truncate", RELOPT_TYPE_TERNARY,
+ offsetof(StdRdOptions, vacuum_truncate)},
{"vacuum_max_eager_freeze_failure_rate", RELOPT_TYPE_REAL,
offsetof(StdRdOptions, vacuum_max_eager_freeze_failure_rate)}
};
@@ -1993,7 +2071,6 @@ build_local_reloptions(local_relopts *relopts, Datum options, bool validate)
elems[i].optname = opt->option->name;
elems[i].opttype = opt->option->type;
elems[i].offset = opt->offset;
- elems[i].isset_offset = 0; /* not supported for local relopts yet */
i++;
}
diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c
index 733ef40ae7..977babff54 100644
--- a/src/backend/commands/vacuum.c
+++ b/src/backend/commands/vacuum.c
@@ -2221,9 +2221,9 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams params,
{
StdRdOptions *opts = (StdRdOptions *) rel->rd_options;
- if (opts && opts->vacuum_truncate_set)
+ if (opts && opts->vacuum_truncate != TERNARY_UNSET)
{
- if (opts->vacuum_truncate)
+ if (opts->vacuum_truncate == TERNARY_TRUE)
params.truncate = VACOPTVALUE_ENABLED;
else
params.truncate = VACOPTVALUE_DISABLED;
diff --git a/src/include/access/reloptions.h b/src/include/access/reloptions.h
index a604a4702c..a436697658 100644
--- a/src/include/access/reloptions.h
+++ b/src/include/access/reloptions.h
@@ -29,6 +29,7 @@
typedef enum relopt_type
{
RELOPT_TYPE_BOOL,
+ RELOPT_TYPE_TERNARY, /* on, off, unset */
RELOPT_TYPE_INT,
RELOPT_TYPE_REAL,
RELOPT_TYPE_ENUM,
@@ -80,6 +81,7 @@ typedef struct relopt_value
union
{
bool bool_val;
+ ternary ternary_val;
int int_val;
double real_val;
int enum_val;
@@ -94,6 +96,12 @@ typedef struct relopt_bool
bool default_val;
} relopt_bool;
+typedef struct relopt_rernary
+{
+ relopt_gen gen;
+ int default_val;
+} relopt_ternary;
+
typedef struct relopt_int
{
relopt_gen gen;
@@ -152,19 +160,6 @@ typedef struct
const char *optname; /* option's name */
relopt_type opttype; /* option's datatype */
int offset; /* offset of field in result struct */
-
- /*
- * isset_offset is an optional offset of a field in the result struct that
- * stores whether the option is explicitly set for the relation or if it
- * just picked up the default value. In most cases, this can be
- * accomplished by giving the reloption a special out-of-range default
- * value (e.g., some integer reloptions use -2), but this isn't always
- * possible. For example, a Boolean reloption cannot be given an
- * out-of-range default, so we need another way to discover the source of
- * its value. This offset is only used if given a value greater than
- * zero.
- */
- int isset_offset;
} relopt_parse_elt;
/* Local reloption definition */
@@ -195,6 +190,8 @@ typedef struct local_relopts
extern relopt_kind add_reloption_kind(void);
extern void add_bool_reloption(bits32 kinds, const char *name, const char *desc,
bool default_val, LOCKMODE lockmode);
+extern void add_ternary_reloption(bits32 kinds, const char *name,
+ const char *desc, int default_val, LOCKMODE lockmode);
extern void add_int_reloption(bits32 kinds, const char *name, const char *desc,
int default_val, int min_val, int max_val,
LOCKMODE lockmode);
@@ -214,6 +211,9 @@ extern void register_reloptions_validator(local_relopts *relopts,
extern void add_local_bool_reloption(local_relopts *relopts, const char *name,
const char *desc, bool default_val,
int offset);
+extern void add_local_ternary_reloption(local_relopts *relopts,
+ const char *name, const char *desc,
+ ternary default_val, int offset);
extern void add_local_int_reloption(local_relopts *relopts, const char *name,
const char *desc, int default_val,
int min_val, int max_val, int offset);
diff --git a/src/include/c.h b/src/include/c.h
index 39022f8a9d..9c59ccccf8 100644
--- a/src/include/c.h
+++ b/src/include/c.h
@@ -488,6 +488,22 @@ typedef void (*pg_funcptr_t) (void);
#include <stdbool.h>
+/*
+ * ternary
+ * Boolean value with an extrea "unset" option
+ *
+ * Ternary data type is used in relation options that can be "true", "false" or
+ * "unset". Since relation options are used deep inside the PostgreSQL code,
+ * this type is declared globally.
+*/
+
+typedef enum ternary
+{
+ TERNARY_FALSE = 0,
+ TERNARY_TRUE = 1,
+ TERNARY_UNSET = -1
+} ternary;
+
/* ----------------------------------------------------------------
* Section 3: standard system types
diff --git a/src/include/utils/rel.h b/src/include/utils/rel.h
index b552359915..7346d618f9 100644
--- a/src/include/utils/rel.h
+++ b/src/include/utils/rel.h
@@ -346,8 +346,7 @@ typedef struct StdRdOptions
bool user_catalog_table; /* use as an additional catalog relation */
int parallel_workers; /* max number of parallel workers */
StdRdOptIndexCleanup vacuum_index_cleanup; /* controls index vacuuming */
- bool vacuum_truncate; /* enables vacuum to truncate a relation */
- bool vacuum_truncate_set; /* whether vacuum_truncate is set */
+ ternary vacuum_truncate; /* enables vacuum to truncate a relation */
/*
* Fraction of pages in a relation that vacuum can eagerly scan and fail
--
2.39.2
--nextPart5129932.5fSG56mABF
Content-Disposition: attachment;
filename="v2-0003-Add-alias-to-be-used-as-unset-state.patch"
Content-Transfer-Encoding: 7Bit
Content-Type: text/x-patch; charset="unicode-2-0-utf-8";
name="v2-0003-Add-alias-to-be-used-as-unset-state.patch"
^ permalink raw reply [nested|flat] 868+ messages in thread
* [PATCH v2 2/4] Introduce ternary reloptions
@ 2025-09-04 14:41 Nikolay Shaplov <[email protected]>
0 siblings, 0 replies; 868+ messages in thread
From: Nikolay Shaplov @ 2025-09-04 14:41 UTC (permalink / raw)
Introduce ternary reloption as a replacement for current `vacuum_truncate`
implementation. Remove `vacuum_truncate_set` additional flag and using
`TERNARY_UNSET` value instead.
---
src/backend/access/common/reloptions.c | 129 ++++++++++++++++++++-----
src/backend/commands/vacuum.c | 4 +-
src/include/access/reloptions.h | 26 ++---
src/include/c.h | 16 +++
src/include/utils/rel.h | 3 +-
5 files changed, 135 insertions(+), 43 deletions(-)
diff --git a/src/backend/access/common/reloptions.c b/src/backend/access/common/reloptions.c
index 0af3fea68f..f543f11001 100644
--- a/src/backend/access/common/reloptions.c
+++ b/src/backend/access/common/reloptions.c
@@ -40,9 +40,9 @@
*
* To add an option:
*
- * (i) decide on a type (bool, integer, real, enum, string), name, default
- * value, upper and lower bounds (if applicable); for strings, consider a
- * validation routine.
+ * (i) decide on a type (bool, ternary, integer, real, enum, string), name,
+ * default value, upper and lower bounds (if applicable); for strings,
+ * consider a validation routine.
* (ii) add a record below (or use add_<type>_reloption).
* (iii) add it to the appropriate options struct (perhaps StdRdOptions)
* (iv) add it to the appropriate handling routine (perhaps
@@ -147,15 +147,6 @@ static relopt_bool boolRelOpts[] =
},
false
},
- {
- {
- "vacuum_truncate",
- "Enables vacuum to truncate empty pages at the end of this table",
- RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
- ShareUpdateExclusiveLock
- },
- true
- },
{
{
"deduplicate_items",
@@ -170,6 +161,21 @@ static relopt_bool boolRelOpts[] =
{{NULL}}
};
+static relopt_ternary ternaryRelOpts[] =
+{
+ {
+ {
+ "vacuum_truncate",
+ "Enables vacuum to truncate empty pages at the end of this table",
+ RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
+ ShareUpdateExclusiveLock
+ },
+ TERNARY_UNSET
+ },
+ /* list terminator */
+ {{NULL}}
+};
+
static relopt_int intRelOpts[] =
{
{
@@ -600,6 +606,13 @@ initialize_reloptions(void)
boolRelOpts[i].gen.lockmode));
j++;
}
+ for (i = 0; ternaryRelOpts[i].gen.name; i++)
+ {
+ Assert(DoLockModesConflict(ternaryRelOpts[i].gen.lockmode,
+ ternaryRelOpts[i].gen.lockmode));
+ j++;
+ }
+
for (i = 0; intRelOpts[i].gen.name; i++)
{
Assert(DoLockModesConflict(intRelOpts[i].gen.lockmode,
@@ -640,6 +653,14 @@ initialize_reloptions(void)
j++;
}
+ for (i = 0; ternaryRelOpts[i].gen.name; i++)
+ {
+ relOpts[j] = &ternaryRelOpts[i].gen;
+ relOpts[j]->type = RELOPT_TYPE_TERNARY;
+ relOpts[j]->namelen = strlen(relOpts[j]->name);
+ j++;
+ }
+
for (i = 0; intRelOpts[i].gen.name; i++)
{
relOpts[j] = &intRelOpts[i].gen;
@@ -800,6 +821,9 @@ allocate_reloption(bits32 kinds, int type, const char *name, const char *desc,
case RELOPT_TYPE_BOOL:
size = sizeof(relopt_bool);
break;
+ case RELOPT_TYPE_TERNARY:
+ size = sizeof(relopt_ternary);
+ break;
case RELOPT_TYPE_INT:
size = sizeof(relopt_int);
break;
@@ -883,6 +907,54 @@ add_local_bool_reloption(local_relopts *relopts, const char *name,
add_local_reloption(relopts, (relopt_gen *) newoption, offset);
}
+/*
+ * init_ternary_reloption
+ * Allocate and initialize a new ternary reloption
+ */
+static relopt_ternary *
+init_ternary_reloption(bits32 kinds, const char *name, const char *desc,
+ ternary default_val, LOCKMODE lockmode)
+{
+ relopt_ternary *newoption;
+
+ newoption = (relopt_ternary *) allocate_reloption(kinds,
+ RELOPT_TYPE_TERNARY, name, desc, lockmode);
+ newoption->default_val = default_val;
+
+ return newoption;
+}
+
+/*
+ * add_ternary_reloption
+ * Add a new ternary reloption
+ */
+void
+add_ternary_reloption(bits32 kinds, const char *name, const char *desc,
+ ternary default_val, LOCKMODE lockmode)
+{
+ relopt_ternary *newoption = init_ternary_reloption(kinds, name, desc,
+ default_val, lockmode);
+
+ add_reloption((relopt_gen *) newoption);
+}
+
+/*
+ * add_local_ternary_reloption
+ * Add a new ternary local reloption
+ *
+ * 'offset' is offset of ternary-typed field.
+ */
+void
+add_local_ternary_reloption(local_relopts *relopts, const char *name,
+ const char *desc, ternary default_val,
+ int offset)
+{
+ relopt_ternary *newoption = init_ternary_reloption(RELOPT_KIND_LOCAL,
+ name, desc,
+ default_val, 0);
+
+ add_local_reloption(relopts, (relopt_gen *) newoption, offset);
+}
/*
* init_real_reloption
@@ -1617,6 +1689,18 @@ parse_one_reloption(relopt_value *option, char *text_str, int text_len,
option->gen->name, value)));
}
break;
+ case RELOPT_TYPE_TERNARY:
+ {
+ bool b;
+ parsed = parse_bool(value, &b);
+ option->values.ternary_val = b ? TERNARY_TRUE : TERNARY_FALSE;
+ if (validate && !parsed)
+ ereport(ERROR,
+ (errcode(ERRCODE_INVALID_PARAMETER_VALUE),
+ errmsg("invalid value for ternary option \"%s\": %s",
+ option->gen->name, value)));
+ }
+ break;
case RELOPT_TYPE_INT:
{
relopt_int *optint = (relopt_int *) option->gen;
@@ -1780,17 +1864,6 @@ fillRelOptions(void *rdopts, Size basesize,
char *itempos = ((char *) rdopts) + elems[j].offset;
char *string_val;
- /*
- * If isset_offset is provided, store whether the reloption is
- * set there.
- */
- if (elems[j].isset_offset > 0)
- {
- char *setpos = ((char *) rdopts) + elems[j].isset_offset;
-
- *(bool *) setpos = options[i].isset;
- }
-
switch (options[i].gen->type)
{
case RELOPT_TYPE_BOOL:
@@ -1798,6 +1871,11 @@ fillRelOptions(void *rdopts, Size basesize,
options[i].values.bool_val :
((relopt_bool *) options[i].gen)->default_val;
break;
+ case RELOPT_TYPE_TERNARY:
+ *(ternary *) itempos = options[i].isset ?
+ options[i].values.ternary_val :
+ ((relopt_ternary *) options[i].gen)->default_val;
+ break;
case RELOPT_TYPE_INT:
*(int *) itempos = options[i].isset ?
options[i].values.int_val :
@@ -1912,8 +1990,8 @@ default_reloptions(Datum reloptions, bool validate, relopt_kind kind)
offsetof(StdRdOptions, parallel_workers)},
{"vacuum_index_cleanup", RELOPT_TYPE_ENUM,
offsetof(StdRdOptions, vacuum_index_cleanup)},
- {"vacuum_truncate", RELOPT_TYPE_BOOL,
- offsetof(StdRdOptions, vacuum_truncate), offsetof(StdRdOptions, vacuum_truncate_set)},
+ {"vacuum_truncate", RELOPT_TYPE_TERNARY,
+ offsetof(StdRdOptions, vacuum_truncate)},
{"vacuum_max_eager_freeze_failure_rate", RELOPT_TYPE_REAL,
offsetof(StdRdOptions, vacuum_max_eager_freeze_failure_rate)}
};
@@ -1993,7 +2071,6 @@ build_local_reloptions(local_relopts *relopts, Datum options, bool validate)
elems[i].optname = opt->option->name;
elems[i].opttype = opt->option->type;
elems[i].offset = opt->offset;
- elems[i].isset_offset = 0; /* not supported for local relopts yet */
i++;
}
diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c
index 733ef40ae7..977babff54 100644
--- a/src/backend/commands/vacuum.c
+++ b/src/backend/commands/vacuum.c
@@ -2221,9 +2221,9 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams params,
{
StdRdOptions *opts = (StdRdOptions *) rel->rd_options;
- if (opts && opts->vacuum_truncate_set)
+ if (opts && opts->vacuum_truncate != TERNARY_UNSET)
{
- if (opts->vacuum_truncate)
+ if (opts->vacuum_truncate == TERNARY_TRUE)
params.truncate = VACOPTVALUE_ENABLED;
else
params.truncate = VACOPTVALUE_DISABLED;
diff --git a/src/include/access/reloptions.h b/src/include/access/reloptions.h
index a604a4702c..a436697658 100644
--- a/src/include/access/reloptions.h
+++ b/src/include/access/reloptions.h
@@ -29,6 +29,7 @@
typedef enum relopt_type
{
RELOPT_TYPE_BOOL,
+ RELOPT_TYPE_TERNARY, /* on, off, unset */
RELOPT_TYPE_INT,
RELOPT_TYPE_REAL,
RELOPT_TYPE_ENUM,
@@ -80,6 +81,7 @@ typedef struct relopt_value
union
{
bool bool_val;
+ ternary ternary_val;
int int_val;
double real_val;
int enum_val;
@@ -94,6 +96,12 @@ typedef struct relopt_bool
bool default_val;
} relopt_bool;
+typedef struct relopt_rernary
+{
+ relopt_gen gen;
+ int default_val;
+} relopt_ternary;
+
typedef struct relopt_int
{
relopt_gen gen;
@@ -152,19 +160,6 @@ typedef struct
const char *optname; /* option's name */
relopt_type opttype; /* option's datatype */
int offset; /* offset of field in result struct */
-
- /*
- * isset_offset is an optional offset of a field in the result struct that
- * stores whether the option is explicitly set for the relation or if it
- * just picked up the default value. In most cases, this can be
- * accomplished by giving the reloption a special out-of-range default
- * value (e.g., some integer reloptions use -2), but this isn't always
- * possible. For example, a Boolean reloption cannot be given an
- * out-of-range default, so we need another way to discover the source of
- * its value. This offset is only used if given a value greater than
- * zero.
- */
- int isset_offset;
} relopt_parse_elt;
/* Local reloption definition */
@@ -195,6 +190,8 @@ typedef struct local_relopts
extern relopt_kind add_reloption_kind(void);
extern void add_bool_reloption(bits32 kinds, const char *name, const char *desc,
bool default_val, LOCKMODE lockmode);
+extern void add_ternary_reloption(bits32 kinds, const char *name,
+ const char *desc, int default_val, LOCKMODE lockmode);
extern void add_int_reloption(bits32 kinds, const char *name, const char *desc,
int default_val, int min_val, int max_val,
LOCKMODE lockmode);
@@ -214,6 +211,9 @@ extern void register_reloptions_validator(local_relopts *relopts,
extern void add_local_bool_reloption(local_relopts *relopts, const char *name,
const char *desc, bool default_val,
int offset);
+extern void add_local_ternary_reloption(local_relopts *relopts,
+ const char *name, const char *desc,
+ ternary default_val, int offset);
extern void add_local_int_reloption(local_relopts *relopts, const char *name,
const char *desc, int default_val,
int min_val, int max_val, int offset);
diff --git a/src/include/c.h b/src/include/c.h
index 39022f8a9d..9c59ccccf8 100644
--- a/src/include/c.h
+++ b/src/include/c.h
@@ -488,6 +488,22 @@ typedef void (*pg_funcptr_t) (void);
#include <stdbool.h>
+/*
+ * ternary
+ * Boolean value with an extrea "unset" option
+ *
+ * Ternary data type is used in relation options that can be "true", "false" or
+ * "unset". Since relation options are used deep inside the PostgreSQL code,
+ * this type is declared globally.
+*/
+
+typedef enum ternary
+{
+ TERNARY_FALSE = 0,
+ TERNARY_TRUE = 1,
+ TERNARY_UNSET = -1
+} ternary;
+
/* ----------------------------------------------------------------
* Section 3: standard system types
diff --git a/src/include/utils/rel.h b/src/include/utils/rel.h
index b552359915..7346d618f9 100644
--- a/src/include/utils/rel.h
+++ b/src/include/utils/rel.h
@@ -346,8 +346,7 @@ typedef struct StdRdOptions
bool user_catalog_table; /* use as an additional catalog relation */
int parallel_workers; /* max number of parallel workers */
StdRdOptIndexCleanup vacuum_index_cleanup; /* controls index vacuuming */
- bool vacuum_truncate; /* enables vacuum to truncate a relation */
- bool vacuum_truncate_set; /* whether vacuum_truncate is set */
+ ternary vacuum_truncate; /* enables vacuum to truncate a relation */
/*
* Fraction of pages in a relation that vacuum can eagerly scan and fail
--
2.39.2
--nextPart5129932.5fSG56mABF
Content-Disposition: attachment;
filename="v2-0003-Add-alias-to-be-used-as-unset-state.patch"
Content-Transfer-Encoding: 7Bit
Content-Type: text/x-patch; charset="unicode-2-0-utf-8";
name="v2-0003-Add-alias-to-be-used-as-unset-state.patch"
^ permalink raw reply [nested|flat] 868+ messages in thread
* [PATCH v2a 2/4] Introduce ternary reloptions
@ 2025-09-04 14:41 Nikolay Shaplov <[email protected]>
0 siblings, 0 replies; 868+ messages in thread
From: Nikolay Shaplov @ 2025-09-04 14:41 UTC (permalink / raw)
Introduce ternary reloption as a replacement for current `vacuum_truncate`
implementation. Remove `vacuum_truncate_set` additional flag and using
`TERNARY_UNSET` value instead.
---
src/backend/access/common/reloptions.c | 129 ++++++++++++++++++++-----
src/backend/commands/vacuum.c | 4 +-
src/include/access/reloptions.h | 26 ++---
src/include/c.h | 16 +++
src/include/utils/rel.h | 3 +-
5 files changed, 135 insertions(+), 43 deletions(-)
diff --git a/src/backend/access/common/reloptions.c b/src/backend/access/common/reloptions.c
index 0af3fea68f..f543f11001 100644
--- a/src/backend/access/common/reloptions.c
+++ b/src/backend/access/common/reloptions.c
@@ -40,9 +40,9 @@
*
* To add an option:
*
- * (i) decide on a type (bool, integer, real, enum, string), name, default
- * value, upper and lower bounds (if applicable); for strings, consider a
- * validation routine.
+ * (i) decide on a type (bool, ternary, integer, real, enum, string), name,
+ * default value, upper and lower bounds (if applicable); for strings,
+ * consider a validation routine.
* (ii) add a record below (or use add_<type>_reloption).
* (iii) add it to the appropriate options struct (perhaps StdRdOptions)
* (iv) add it to the appropriate handling routine (perhaps
@@ -147,15 +147,6 @@ static relopt_bool boolRelOpts[] =
},
false
},
- {
- {
- "vacuum_truncate",
- "Enables vacuum to truncate empty pages at the end of this table",
- RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
- ShareUpdateExclusiveLock
- },
- true
- },
{
{
"deduplicate_items",
@@ -170,6 +161,21 @@ static relopt_bool boolRelOpts[] =
{{NULL}}
};
+static relopt_ternary ternaryRelOpts[] =
+{
+ {
+ {
+ "vacuum_truncate",
+ "Enables vacuum to truncate empty pages at the end of this table",
+ RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
+ ShareUpdateExclusiveLock
+ },
+ TERNARY_UNSET
+ },
+ /* list terminator */
+ {{NULL}}
+};
+
static relopt_int intRelOpts[] =
{
{
@@ -600,6 +606,13 @@ initialize_reloptions(void)
boolRelOpts[i].gen.lockmode));
j++;
}
+ for (i = 0; ternaryRelOpts[i].gen.name; i++)
+ {
+ Assert(DoLockModesConflict(ternaryRelOpts[i].gen.lockmode,
+ ternaryRelOpts[i].gen.lockmode));
+ j++;
+ }
+
for (i = 0; intRelOpts[i].gen.name; i++)
{
Assert(DoLockModesConflict(intRelOpts[i].gen.lockmode,
@@ -640,6 +653,14 @@ initialize_reloptions(void)
j++;
}
+ for (i = 0; ternaryRelOpts[i].gen.name; i++)
+ {
+ relOpts[j] = &ternaryRelOpts[i].gen;
+ relOpts[j]->type = RELOPT_TYPE_TERNARY;
+ relOpts[j]->namelen = strlen(relOpts[j]->name);
+ j++;
+ }
+
for (i = 0; intRelOpts[i].gen.name; i++)
{
relOpts[j] = &intRelOpts[i].gen;
@@ -800,6 +821,9 @@ allocate_reloption(bits32 kinds, int type, const char *name, const char *desc,
case RELOPT_TYPE_BOOL:
size = sizeof(relopt_bool);
break;
+ case RELOPT_TYPE_TERNARY:
+ size = sizeof(relopt_ternary);
+ break;
case RELOPT_TYPE_INT:
size = sizeof(relopt_int);
break;
@@ -883,6 +907,54 @@ add_local_bool_reloption(local_relopts *relopts, const char *name,
add_local_reloption(relopts, (relopt_gen *) newoption, offset);
}
+/*
+ * init_ternary_reloption
+ * Allocate and initialize a new ternary reloption
+ */
+static relopt_ternary *
+init_ternary_reloption(bits32 kinds, const char *name, const char *desc,
+ ternary default_val, LOCKMODE lockmode)
+{
+ relopt_ternary *newoption;
+
+ newoption = (relopt_ternary *) allocate_reloption(kinds,
+ RELOPT_TYPE_TERNARY, name, desc, lockmode);
+ newoption->default_val = default_val;
+
+ return newoption;
+}
+
+/*
+ * add_ternary_reloption
+ * Add a new ternary reloption
+ */
+void
+add_ternary_reloption(bits32 kinds, const char *name, const char *desc,
+ ternary default_val, LOCKMODE lockmode)
+{
+ relopt_ternary *newoption = init_ternary_reloption(kinds, name, desc,
+ default_val, lockmode);
+
+ add_reloption((relopt_gen *) newoption);
+}
+
+/*
+ * add_local_ternary_reloption
+ * Add a new ternary local reloption
+ *
+ * 'offset' is offset of ternary-typed field.
+ */
+void
+add_local_ternary_reloption(local_relopts *relopts, const char *name,
+ const char *desc, ternary default_val,
+ int offset)
+{
+ relopt_ternary *newoption = init_ternary_reloption(RELOPT_KIND_LOCAL,
+ name, desc,
+ default_val, 0);
+
+ add_local_reloption(relopts, (relopt_gen *) newoption, offset);
+}
/*
* init_real_reloption
@@ -1617,6 +1689,18 @@ parse_one_reloption(relopt_value *option, char *text_str, int text_len,
option->gen->name, value)));
}
break;
+ case RELOPT_TYPE_TERNARY:
+ {
+ bool b;
+ parsed = parse_bool(value, &b);
+ option->values.ternary_val = b ? TERNARY_TRUE : TERNARY_FALSE;
+ if (validate && !parsed)
+ ereport(ERROR,
+ (errcode(ERRCODE_INVALID_PARAMETER_VALUE),
+ errmsg("invalid value for ternary option \"%s\": %s",
+ option->gen->name, value)));
+ }
+ break;
case RELOPT_TYPE_INT:
{
relopt_int *optint = (relopt_int *) option->gen;
@@ -1780,17 +1864,6 @@ fillRelOptions(void *rdopts, Size basesize,
char *itempos = ((char *) rdopts) + elems[j].offset;
char *string_val;
- /*
- * If isset_offset is provided, store whether the reloption is
- * set there.
- */
- if (elems[j].isset_offset > 0)
- {
- char *setpos = ((char *) rdopts) + elems[j].isset_offset;
-
- *(bool *) setpos = options[i].isset;
- }
-
switch (options[i].gen->type)
{
case RELOPT_TYPE_BOOL:
@@ -1798,6 +1871,11 @@ fillRelOptions(void *rdopts, Size basesize,
options[i].values.bool_val :
((relopt_bool *) options[i].gen)->default_val;
break;
+ case RELOPT_TYPE_TERNARY:
+ *(ternary *) itempos = options[i].isset ?
+ options[i].values.ternary_val :
+ ((relopt_ternary *) options[i].gen)->default_val;
+ break;
case RELOPT_TYPE_INT:
*(int *) itempos = options[i].isset ?
options[i].values.int_val :
@@ -1912,8 +1990,8 @@ default_reloptions(Datum reloptions, bool validate, relopt_kind kind)
offsetof(StdRdOptions, parallel_workers)},
{"vacuum_index_cleanup", RELOPT_TYPE_ENUM,
offsetof(StdRdOptions, vacuum_index_cleanup)},
- {"vacuum_truncate", RELOPT_TYPE_BOOL,
- offsetof(StdRdOptions, vacuum_truncate), offsetof(StdRdOptions, vacuum_truncate_set)},
+ {"vacuum_truncate", RELOPT_TYPE_TERNARY,
+ offsetof(StdRdOptions, vacuum_truncate)},
{"vacuum_max_eager_freeze_failure_rate", RELOPT_TYPE_REAL,
offsetof(StdRdOptions, vacuum_max_eager_freeze_failure_rate)}
};
@@ -1993,7 +2071,6 @@ build_local_reloptions(local_relopts *relopts, Datum options, bool validate)
elems[i].optname = opt->option->name;
elems[i].opttype = opt->option->type;
elems[i].offset = opt->offset;
- elems[i].isset_offset = 0; /* not supported for local relopts yet */
i++;
}
diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c
index 733ef40ae7..977babff54 100644
--- a/src/backend/commands/vacuum.c
+++ b/src/backend/commands/vacuum.c
@@ -2221,9 +2221,9 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams params,
{
StdRdOptions *opts = (StdRdOptions *) rel->rd_options;
- if (opts && opts->vacuum_truncate_set)
+ if (opts && opts->vacuum_truncate != TERNARY_UNSET)
{
- if (opts->vacuum_truncate)
+ if (opts->vacuum_truncate == TERNARY_TRUE)
params.truncate = VACOPTVALUE_ENABLED;
else
params.truncate = VACOPTVALUE_DISABLED;
diff --git a/src/include/access/reloptions.h b/src/include/access/reloptions.h
index a604a4702c..a436697658 100644
--- a/src/include/access/reloptions.h
+++ b/src/include/access/reloptions.h
@@ -29,6 +29,7 @@
typedef enum relopt_type
{
RELOPT_TYPE_BOOL,
+ RELOPT_TYPE_TERNARY, /* on, off, unset */
RELOPT_TYPE_INT,
RELOPT_TYPE_REAL,
RELOPT_TYPE_ENUM,
@@ -80,6 +81,7 @@ typedef struct relopt_value
union
{
bool bool_val;
+ ternary ternary_val;
int int_val;
double real_val;
int enum_val;
@@ -94,6 +96,12 @@ typedef struct relopt_bool
bool default_val;
} relopt_bool;
+typedef struct relopt_rernary
+{
+ relopt_gen gen;
+ int default_val;
+} relopt_ternary;
+
typedef struct relopt_int
{
relopt_gen gen;
@@ -152,19 +160,6 @@ typedef struct
const char *optname; /* option's name */
relopt_type opttype; /* option's datatype */
int offset; /* offset of field in result struct */
-
- /*
- * isset_offset is an optional offset of a field in the result struct that
- * stores whether the option is explicitly set for the relation or if it
- * just picked up the default value. In most cases, this can be
- * accomplished by giving the reloption a special out-of-range default
- * value (e.g., some integer reloptions use -2), but this isn't always
- * possible. For example, a Boolean reloption cannot be given an
- * out-of-range default, so we need another way to discover the source of
- * its value. This offset is only used if given a value greater than
- * zero.
- */
- int isset_offset;
} relopt_parse_elt;
/* Local reloption definition */
@@ -195,6 +190,8 @@ typedef struct local_relopts
extern relopt_kind add_reloption_kind(void);
extern void add_bool_reloption(bits32 kinds, const char *name, const char *desc,
bool default_val, LOCKMODE lockmode);
+extern void add_ternary_reloption(bits32 kinds, const char *name,
+ const char *desc, int default_val, LOCKMODE lockmode);
extern void add_int_reloption(bits32 kinds, const char *name, const char *desc,
int default_val, int min_val, int max_val,
LOCKMODE lockmode);
@@ -214,6 +211,9 @@ extern void register_reloptions_validator(local_relopts *relopts,
extern void add_local_bool_reloption(local_relopts *relopts, const char *name,
const char *desc, bool default_val,
int offset);
+extern void add_local_ternary_reloption(local_relopts *relopts,
+ const char *name, const char *desc,
+ ternary default_val, int offset);
extern void add_local_int_reloption(local_relopts *relopts, const char *name,
const char *desc, int default_val,
int min_val, int max_val, int offset);
diff --git a/src/include/c.h b/src/include/c.h
index 39022f8a9d..9c59ccccf8 100644
--- a/src/include/c.h
+++ b/src/include/c.h
@@ -488,6 +488,22 @@ typedef void (*pg_funcptr_t) (void);
#include <stdbool.h>
+/*
+ * ternary
+ * Boolean value with an extrea "unset" option
+ *
+ * Ternary data type is used in relation options that can be "true", "false" or
+ * "unset". Since relation options are used deep inside the PostgreSQL code,
+ * this type is declared globally.
+*/
+
+typedef enum ternary
+{
+ TERNARY_FALSE = 0,
+ TERNARY_TRUE = 1,
+ TERNARY_UNSET = -1
+} ternary;
+
/* ----------------------------------------------------------------
* Section 3: standard system types
diff --git a/src/include/utils/rel.h b/src/include/utils/rel.h
index b552359915..7346d618f9 100644
--- a/src/include/utils/rel.h
+++ b/src/include/utils/rel.h
@@ -346,8 +346,7 @@ typedef struct StdRdOptions
bool user_catalog_table; /* use as an additional catalog relation */
int parallel_workers; /* max number of parallel workers */
StdRdOptIndexCleanup vacuum_index_cleanup; /* controls index vacuuming */
- bool vacuum_truncate; /* enables vacuum to truncate a relation */
- bool vacuum_truncate_set; /* whether vacuum_truncate is set */
+ ternary vacuum_truncate; /* enables vacuum to truncate a relation */
/*
* Fraction of pages in a relation that vacuum can eagerly scan and fail
--
2.39.2
--nextPart4155600.3ZeAukHxDK
Content-Disposition: attachment;
filename="v2a-0003-Add-alias-to-be-used-as-unset-state.patch"
Content-Transfer-Encoding: 7Bit
Content-Type: text/x-patch; charset="unicode-2-0-utf-8";
name="v2a-0003-Add-alias-to-be-used-as-unset-state.patch"
^ permalink raw reply [nested|flat] 868+ messages in thread
* [PATCH v2 2/4] Introduce ternary reloptions
@ 2025-09-04 14:41 Nikolay Shaplov <[email protected]>
0 siblings, 0 replies; 868+ messages in thread
From: Nikolay Shaplov @ 2025-09-04 14:41 UTC (permalink / raw)
Introduce ternary reloption as a replacement for current `vacuum_truncate`
implementation. Remove `vacuum_truncate_set` additional flag and using
`TERNARY_UNSET` value instead.
---
src/backend/access/common/reloptions.c | 129 ++++++++++++++++++++-----
src/backend/commands/vacuum.c | 4 +-
src/include/access/reloptions.h | 26 ++---
src/include/c.h | 16 +++
src/include/utils/rel.h | 3 +-
5 files changed, 135 insertions(+), 43 deletions(-)
diff --git a/src/backend/access/common/reloptions.c b/src/backend/access/common/reloptions.c
index 0af3fea68f..f543f11001 100644
--- a/src/backend/access/common/reloptions.c
+++ b/src/backend/access/common/reloptions.c
@@ -40,9 +40,9 @@
*
* To add an option:
*
- * (i) decide on a type (bool, integer, real, enum, string), name, default
- * value, upper and lower bounds (if applicable); for strings, consider a
- * validation routine.
+ * (i) decide on a type (bool, ternary, integer, real, enum, string), name,
+ * default value, upper and lower bounds (if applicable); for strings,
+ * consider a validation routine.
* (ii) add a record below (or use add_<type>_reloption).
* (iii) add it to the appropriate options struct (perhaps StdRdOptions)
* (iv) add it to the appropriate handling routine (perhaps
@@ -147,15 +147,6 @@ static relopt_bool boolRelOpts[] =
},
false
},
- {
- {
- "vacuum_truncate",
- "Enables vacuum to truncate empty pages at the end of this table",
- RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
- ShareUpdateExclusiveLock
- },
- true
- },
{
{
"deduplicate_items",
@@ -170,6 +161,21 @@ static relopt_bool boolRelOpts[] =
{{NULL}}
};
+static relopt_ternary ternaryRelOpts[] =
+{
+ {
+ {
+ "vacuum_truncate",
+ "Enables vacuum to truncate empty pages at the end of this table",
+ RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
+ ShareUpdateExclusiveLock
+ },
+ TERNARY_UNSET
+ },
+ /* list terminator */
+ {{NULL}}
+};
+
static relopt_int intRelOpts[] =
{
{
@@ -600,6 +606,13 @@ initialize_reloptions(void)
boolRelOpts[i].gen.lockmode));
j++;
}
+ for (i = 0; ternaryRelOpts[i].gen.name; i++)
+ {
+ Assert(DoLockModesConflict(ternaryRelOpts[i].gen.lockmode,
+ ternaryRelOpts[i].gen.lockmode));
+ j++;
+ }
+
for (i = 0; intRelOpts[i].gen.name; i++)
{
Assert(DoLockModesConflict(intRelOpts[i].gen.lockmode,
@@ -640,6 +653,14 @@ initialize_reloptions(void)
j++;
}
+ for (i = 0; ternaryRelOpts[i].gen.name; i++)
+ {
+ relOpts[j] = &ternaryRelOpts[i].gen;
+ relOpts[j]->type = RELOPT_TYPE_TERNARY;
+ relOpts[j]->namelen = strlen(relOpts[j]->name);
+ j++;
+ }
+
for (i = 0; intRelOpts[i].gen.name; i++)
{
relOpts[j] = &intRelOpts[i].gen;
@@ -800,6 +821,9 @@ allocate_reloption(bits32 kinds, int type, const char *name, const char *desc,
case RELOPT_TYPE_BOOL:
size = sizeof(relopt_bool);
break;
+ case RELOPT_TYPE_TERNARY:
+ size = sizeof(relopt_ternary);
+ break;
case RELOPT_TYPE_INT:
size = sizeof(relopt_int);
break;
@@ -883,6 +907,54 @@ add_local_bool_reloption(local_relopts *relopts, const char *name,
add_local_reloption(relopts, (relopt_gen *) newoption, offset);
}
+/*
+ * init_ternary_reloption
+ * Allocate and initialize a new ternary reloption
+ */
+static relopt_ternary *
+init_ternary_reloption(bits32 kinds, const char *name, const char *desc,
+ ternary default_val, LOCKMODE lockmode)
+{
+ relopt_ternary *newoption;
+
+ newoption = (relopt_ternary *) allocate_reloption(kinds,
+ RELOPT_TYPE_TERNARY, name, desc, lockmode);
+ newoption->default_val = default_val;
+
+ return newoption;
+}
+
+/*
+ * add_ternary_reloption
+ * Add a new ternary reloption
+ */
+void
+add_ternary_reloption(bits32 kinds, const char *name, const char *desc,
+ ternary default_val, LOCKMODE lockmode)
+{
+ relopt_ternary *newoption = init_ternary_reloption(kinds, name, desc,
+ default_val, lockmode);
+
+ add_reloption((relopt_gen *) newoption);
+}
+
+/*
+ * add_local_ternary_reloption
+ * Add a new ternary local reloption
+ *
+ * 'offset' is offset of ternary-typed field.
+ */
+void
+add_local_ternary_reloption(local_relopts *relopts, const char *name,
+ const char *desc, ternary default_val,
+ int offset)
+{
+ relopt_ternary *newoption = init_ternary_reloption(RELOPT_KIND_LOCAL,
+ name, desc,
+ default_val, 0);
+
+ add_local_reloption(relopts, (relopt_gen *) newoption, offset);
+}
/*
* init_real_reloption
@@ -1617,6 +1689,18 @@ parse_one_reloption(relopt_value *option, char *text_str, int text_len,
option->gen->name, value)));
}
break;
+ case RELOPT_TYPE_TERNARY:
+ {
+ bool b;
+ parsed = parse_bool(value, &b);
+ option->values.ternary_val = b ? TERNARY_TRUE : TERNARY_FALSE;
+ if (validate && !parsed)
+ ereport(ERROR,
+ (errcode(ERRCODE_INVALID_PARAMETER_VALUE),
+ errmsg("invalid value for ternary option \"%s\": %s",
+ option->gen->name, value)));
+ }
+ break;
case RELOPT_TYPE_INT:
{
relopt_int *optint = (relopt_int *) option->gen;
@@ -1780,17 +1864,6 @@ fillRelOptions(void *rdopts, Size basesize,
char *itempos = ((char *) rdopts) + elems[j].offset;
char *string_val;
- /*
- * If isset_offset is provided, store whether the reloption is
- * set there.
- */
- if (elems[j].isset_offset > 0)
- {
- char *setpos = ((char *) rdopts) + elems[j].isset_offset;
-
- *(bool *) setpos = options[i].isset;
- }
-
switch (options[i].gen->type)
{
case RELOPT_TYPE_BOOL:
@@ -1798,6 +1871,11 @@ fillRelOptions(void *rdopts, Size basesize,
options[i].values.bool_val :
((relopt_bool *) options[i].gen)->default_val;
break;
+ case RELOPT_TYPE_TERNARY:
+ *(ternary *) itempos = options[i].isset ?
+ options[i].values.ternary_val :
+ ((relopt_ternary *) options[i].gen)->default_val;
+ break;
case RELOPT_TYPE_INT:
*(int *) itempos = options[i].isset ?
options[i].values.int_val :
@@ -1912,8 +1990,8 @@ default_reloptions(Datum reloptions, bool validate, relopt_kind kind)
offsetof(StdRdOptions, parallel_workers)},
{"vacuum_index_cleanup", RELOPT_TYPE_ENUM,
offsetof(StdRdOptions, vacuum_index_cleanup)},
- {"vacuum_truncate", RELOPT_TYPE_BOOL,
- offsetof(StdRdOptions, vacuum_truncate), offsetof(StdRdOptions, vacuum_truncate_set)},
+ {"vacuum_truncate", RELOPT_TYPE_TERNARY,
+ offsetof(StdRdOptions, vacuum_truncate)},
{"vacuum_max_eager_freeze_failure_rate", RELOPT_TYPE_REAL,
offsetof(StdRdOptions, vacuum_max_eager_freeze_failure_rate)}
};
@@ -1993,7 +2071,6 @@ build_local_reloptions(local_relopts *relopts, Datum options, bool validate)
elems[i].optname = opt->option->name;
elems[i].opttype = opt->option->type;
elems[i].offset = opt->offset;
- elems[i].isset_offset = 0; /* not supported for local relopts yet */
i++;
}
diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c
index 733ef40ae7..977babff54 100644
--- a/src/backend/commands/vacuum.c
+++ b/src/backend/commands/vacuum.c
@@ -2221,9 +2221,9 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams params,
{
StdRdOptions *opts = (StdRdOptions *) rel->rd_options;
- if (opts && opts->vacuum_truncate_set)
+ if (opts && opts->vacuum_truncate != TERNARY_UNSET)
{
- if (opts->vacuum_truncate)
+ if (opts->vacuum_truncate == TERNARY_TRUE)
params.truncate = VACOPTVALUE_ENABLED;
else
params.truncate = VACOPTVALUE_DISABLED;
diff --git a/src/include/access/reloptions.h b/src/include/access/reloptions.h
index a604a4702c..a436697658 100644
--- a/src/include/access/reloptions.h
+++ b/src/include/access/reloptions.h
@@ -29,6 +29,7 @@
typedef enum relopt_type
{
RELOPT_TYPE_BOOL,
+ RELOPT_TYPE_TERNARY, /* on, off, unset */
RELOPT_TYPE_INT,
RELOPT_TYPE_REAL,
RELOPT_TYPE_ENUM,
@@ -80,6 +81,7 @@ typedef struct relopt_value
union
{
bool bool_val;
+ ternary ternary_val;
int int_val;
double real_val;
int enum_val;
@@ -94,6 +96,12 @@ typedef struct relopt_bool
bool default_val;
} relopt_bool;
+typedef struct relopt_rernary
+{
+ relopt_gen gen;
+ int default_val;
+} relopt_ternary;
+
typedef struct relopt_int
{
relopt_gen gen;
@@ -152,19 +160,6 @@ typedef struct
const char *optname; /* option's name */
relopt_type opttype; /* option's datatype */
int offset; /* offset of field in result struct */
-
- /*
- * isset_offset is an optional offset of a field in the result struct that
- * stores whether the option is explicitly set for the relation or if it
- * just picked up the default value. In most cases, this can be
- * accomplished by giving the reloption a special out-of-range default
- * value (e.g., some integer reloptions use -2), but this isn't always
- * possible. For example, a Boolean reloption cannot be given an
- * out-of-range default, so we need another way to discover the source of
- * its value. This offset is only used if given a value greater than
- * zero.
- */
- int isset_offset;
} relopt_parse_elt;
/* Local reloption definition */
@@ -195,6 +190,8 @@ typedef struct local_relopts
extern relopt_kind add_reloption_kind(void);
extern void add_bool_reloption(bits32 kinds, const char *name, const char *desc,
bool default_val, LOCKMODE lockmode);
+extern void add_ternary_reloption(bits32 kinds, const char *name,
+ const char *desc, int default_val, LOCKMODE lockmode);
extern void add_int_reloption(bits32 kinds, const char *name, const char *desc,
int default_val, int min_val, int max_val,
LOCKMODE lockmode);
@@ -214,6 +211,9 @@ extern void register_reloptions_validator(local_relopts *relopts,
extern void add_local_bool_reloption(local_relopts *relopts, const char *name,
const char *desc, bool default_val,
int offset);
+extern void add_local_ternary_reloption(local_relopts *relopts,
+ const char *name, const char *desc,
+ ternary default_val, int offset);
extern void add_local_int_reloption(local_relopts *relopts, const char *name,
const char *desc, int default_val,
int min_val, int max_val, int offset);
diff --git a/src/include/c.h b/src/include/c.h
index 39022f8a9d..9c59ccccf8 100644
--- a/src/include/c.h
+++ b/src/include/c.h
@@ -488,6 +488,22 @@ typedef void (*pg_funcptr_t) (void);
#include <stdbool.h>
+/*
+ * ternary
+ * Boolean value with an extrea "unset" option
+ *
+ * Ternary data type is used in relation options that can be "true", "false" or
+ * "unset". Since relation options are used deep inside the PostgreSQL code,
+ * this type is declared globally.
+*/
+
+typedef enum ternary
+{
+ TERNARY_FALSE = 0,
+ TERNARY_TRUE = 1,
+ TERNARY_UNSET = -1
+} ternary;
+
/* ----------------------------------------------------------------
* Section 3: standard system types
diff --git a/src/include/utils/rel.h b/src/include/utils/rel.h
index b552359915..7346d618f9 100644
--- a/src/include/utils/rel.h
+++ b/src/include/utils/rel.h
@@ -346,8 +346,7 @@ typedef struct StdRdOptions
bool user_catalog_table; /* use as an additional catalog relation */
int parallel_workers; /* max number of parallel workers */
StdRdOptIndexCleanup vacuum_index_cleanup; /* controls index vacuuming */
- bool vacuum_truncate; /* enables vacuum to truncate a relation */
- bool vacuum_truncate_set; /* whether vacuum_truncate is set */
+ ternary vacuum_truncate; /* enables vacuum to truncate a relation */
/*
* Fraction of pages in a relation that vacuum can eagerly scan and fail
--
2.39.2
--nextPart5129932.5fSG56mABF
Content-Disposition: attachment;
filename="v2-0003-Add-alias-to-be-used-as-unset-state.patch"
Content-Transfer-Encoding: 7Bit
Content-Type: text/x-patch; charset="unicode-2-0-utf-8";
name="v2-0003-Add-alias-to-be-used-as-unset-state.patch"
^ permalink raw reply [nested|flat] 868+ messages in thread
* [PATCH v2 2/4] Introduce ternary reloptions
@ 2025-09-04 14:41 Nikolay Shaplov <[email protected]>
0 siblings, 0 replies; 868+ messages in thread
From: Nikolay Shaplov @ 2025-09-04 14:41 UTC (permalink / raw)
Introduce ternary reloption as a replacement for current `vacuum_truncate`
implementation. Remove `vacuum_truncate_set` additional flag and using
`TERNARY_UNSET` value instead.
---
src/backend/access/common/reloptions.c | 129 ++++++++++++++++++++-----
src/backend/commands/vacuum.c | 4 +-
src/include/access/reloptions.h | 26 ++---
src/include/c.h | 16 +++
src/include/utils/rel.h | 3 +-
5 files changed, 135 insertions(+), 43 deletions(-)
diff --git a/src/backend/access/common/reloptions.c b/src/backend/access/common/reloptions.c
index 0af3fea68f..f543f11001 100644
--- a/src/backend/access/common/reloptions.c
+++ b/src/backend/access/common/reloptions.c
@@ -40,9 +40,9 @@
*
* To add an option:
*
- * (i) decide on a type (bool, integer, real, enum, string), name, default
- * value, upper and lower bounds (if applicable); for strings, consider a
- * validation routine.
+ * (i) decide on a type (bool, ternary, integer, real, enum, string), name,
+ * default value, upper and lower bounds (if applicable); for strings,
+ * consider a validation routine.
* (ii) add a record below (or use add_<type>_reloption).
* (iii) add it to the appropriate options struct (perhaps StdRdOptions)
* (iv) add it to the appropriate handling routine (perhaps
@@ -147,15 +147,6 @@ static relopt_bool boolRelOpts[] =
},
false
},
- {
- {
- "vacuum_truncate",
- "Enables vacuum to truncate empty pages at the end of this table",
- RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
- ShareUpdateExclusiveLock
- },
- true
- },
{
{
"deduplicate_items",
@@ -170,6 +161,21 @@ static relopt_bool boolRelOpts[] =
{{NULL}}
};
+static relopt_ternary ternaryRelOpts[] =
+{
+ {
+ {
+ "vacuum_truncate",
+ "Enables vacuum to truncate empty pages at the end of this table",
+ RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
+ ShareUpdateExclusiveLock
+ },
+ TERNARY_UNSET
+ },
+ /* list terminator */
+ {{NULL}}
+};
+
static relopt_int intRelOpts[] =
{
{
@@ -600,6 +606,13 @@ initialize_reloptions(void)
boolRelOpts[i].gen.lockmode));
j++;
}
+ for (i = 0; ternaryRelOpts[i].gen.name; i++)
+ {
+ Assert(DoLockModesConflict(ternaryRelOpts[i].gen.lockmode,
+ ternaryRelOpts[i].gen.lockmode));
+ j++;
+ }
+
for (i = 0; intRelOpts[i].gen.name; i++)
{
Assert(DoLockModesConflict(intRelOpts[i].gen.lockmode,
@@ -640,6 +653,14 @@ initialize_reloptions(void)
j++;
}
+ for (i = 0; ternaryRelOpts[i].gen.name; i++)
+ {
+ relOpts[j] = &ternaryRelOpts[i].gen;
+ relOpts[j]->type = RELOPT_TYPE_TERNARY;
+ relOpts[j]->namelen = strlen(relOpts[j]->name);
+ j++;
+ }
+
for (i = 0; intRelOpts[i].gen.name; i++)
{
relOpts[j] = &intRelOpts[i].gen;
@@ -800,6 +821,9 @@ allocate_reloption(bits32 kinds, int type, const char *name, const char *desc,
case RELOPT_TYPE_BOOL:
size = sizeof(relopt_bool);
break;
+ case RELOPT_TYPE_TERNARY:
+ size = sizeof(relopt_ternary);
+ break;
case RELOPT_TYPE_INT:
size = sizeof(relopt_int);
break;
@@ -883,6 +907,54 @@ add_local_bool_reloption(local_relopts *relopts, const char *name,
add_local_reloption(relopts, (relopt_gen *) newoption, offset);
}
+/*
+ * init_ternary_reloption
+ * Allocate and initialize a new ternary reloption
+ */
+static relopt_ternary *
+init_ternary_reloption(bits32 kinds, const char *name, const char *desc,
+ ternary default_val, LOCKMODE lockmode)
+{
+ relopt_ternary *newoption;
+
+ newoption = (relopt_ternary *) allocate_reloption(kinds,
+ RELOPT_TYPE_TERNARY, name, desc, lockmode);
+ newoption->default_val = default_val;
+
+ return newoption;
+}
+
+/*
+ * add_ternary_reloption
+ * Add a new ternary reloption
+ */
+void
+add_ternary_reloption(bits32 kinds, const char *name, const char *desc,
+ ternary default_val, LOCKMODE lockmode)
+{
+ relopt_ternary *newoption = init_ternary_reloption(kinds, name, desc,
+ default_val, lockmode);
+
+ add_reloption((relopt_gen *) newoption);
+}
+
+/*
+ * add_local_ternary_reloption
+ * Add a new ternary local reloption
+ *
+ * 'offset' is offset of ternary-typed field.
+ */
+void
+add_local_ternary_reloption(local_relopts *relopts, const char *name,
+ const char *desc, ternary default_val,
+ int offset)
+{
+ relopt_ternary *newoption = init_ternary_reloption(RELOPT_KIND_LOCAL,
+ name, desc,
+ default_val, 0);
+
+ add_local_reloption(relopts, (relopt_gen *) newoption, offset);
+}
/*
* init_real_reloption
@@ -1617,6 +1689,18 @@ parse_one_reloption(relopt_value *option, char *text_str, int text_len,
option->gen->name, value)));
}
break;
+ case RELOPT_TYPE_TERNARY:
+ {
+ bool b;
+ parsed = parse_bool(value, &b);
+ option->values.ternary_val = b ? TERNARY_TRUE : TERNARY_FALSE;
+ if (validate && !parsed)
+ ereport(ERROR,
+ (errcode(ERRCODE_INVALID_PARAMETER_VALUE),
+ errmsg("invalid value for ternary option \"%s\": %s",
+ option->gen->name, value)));
+ }
+ break;
case RELOPT_TYPE_INT:
{
relopt_int *optint = (relopt_int *) option->gen;
@@ -1780,17 +1864,6 @@ fillRelOptions(void *rdopts, Size basesize,
char *itempos = ((char *) rdopts) + elems[j].offset;
char *string_val;
- /*
- * If isset_offset is provided, store whether the reloption is
- * set there.
- */
- if (elems[j].isset_offset > 0)
- {
- char *setpos = ((char *) rdopts) + elems[j].isset_offset;
-
- *(bool *) setpos = options[i].isset;
- }
-
switch (options[i].gen->type)
{
case RELOPT_TYPE_BOOL:
@@ -1798,6 +1871,11 @@ fillRelOptions(void *rdopts, Size basesize,
options[i].values.bool_val :
((relopt_bool *) options[i].gen)->default_val;
break;
+ case RELOPT_TYPE_TERNARY:
+ *(ternary *) itempos = options[i].isset ?
+ options[i].values.ternary_val :
+ ((relopt_ternary *) options[i].gen)->default_val;
+ break;
case RELOPT_TYPE_INT:
*(int *) itempos = options[i].isset ?
options[i].values.int_val :
@@ -1912,8 +1990,8 @@ default_reloptions(Datum reloptions, bool validate, relopt_kind kind)
offsetof(StdRdOptions, parallel_workers)},
{"vacuum_index_cleanup", RELOPT_TYPE_ENUM,
offsetof(StdRdOptions, vacuum_index_cleanup)},
- {"vacuum_truncate", RELOPT_TYPE_BOOL,
- offsetof(StdRdOptions, vacuum_truncate), offsetof(StdRdOptions, vacuum_truncate_set)},
+ {"vacuum_truncate", RELOPT_TYPE_TERNARY,
+ offsetof(StdRdOptions, vacuum_truncate)},
{"vacuum_max_eager_freeze_failure_rate", RELOPT_TYPE_REAL,
offsetof(StdRdOptions, vacuum_max_eager_freeze_failure_rate)}
};
@@ -1993,7 +2071,6 @@ build_local_reloptions(local_relopts *relopts, Datum options, bool validate)
elems[i].optname = opt->option->name;
elems[i].opttype = opt->option->type;
elems[i].offset = opt->offset;
- elems[i].isset_offset = 0; /* not supported for local relopts yet */
i++;
}
diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c
index 733ef40ae7..977babff54 100644
--- a/src/backend/commands/vacuum.c
+++ b/src/backend/commands/vacuum.c
@@ -2221,9 +2221,9 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams params,
{
StdRdOptions *opts = (StdRdOptions *) rel->rd_options;
- if (opts && opts->vacuum_truncate_set)
+ if (opts && opts->vacuum_truncate != TERNARY_UNSET)
{
- if (opts->vacuum_truncate)
+ if (opts->vacuum_truncate == TERNARY_TRUE)
params.truncate = VACOPTVALUE_ENABLED;
else
params.truncate = VACOPTVALUE_DISABLED;
diff --git a/src/include/access/reloptions.h b/src/include/access/reloptions.h
index a604a4702c..a436697658 100644
--- a/src/include/access/reloptions.h
+++ b/src/include/access/reloptions.h
@@ -29,6 +29,7 @@
typedef enum relopt_type
{
RELOPT_TYPE_BOOL,
+ RELOPT_TYPE_TERNARY, /* on, off, unset */
RELOPT_TYPE_INT,
RELOPT_TYPE_REAL,
RELOPT_TYPE_ENUM,
@@ -80,6 +81,7 @@ typedef struct relopt_value
union
{
bool bool_val;
+ ternary ternary_val;
int int_val;
double real_val;
int enum_val;
@@ -94,6 +96,12 @@ typedef struct relopt_bool
bool default_val;
} relopt_bool;
+typedef struct relopt_rernary
+{
+ relopt_gen gen;
+ int default_val;
+} relopt_ternary;
+
typedef struct relopt_int
{
relopt_gen gen;
@@ -152,19 +160,6 @@ typedef struct
const char *optname; /* option's name */
relopt_type opttype; /* option's datatype */
int offset; /* offset of field in result struct */
-
- /*
- * isset_offset is an optional offset of a field in the result struct that
- * stores whether the option is explicitly set for the relation or if it
- * just picked up the default value. In most cases, this can be
- * accomplished by giving the reloption a special out-of-range default
- * value (e.g., some integer reloptions use -2), but this isn't always
- * possible. For example, a Boolean reloption cannot be given an
- * out-of-range default, so we need another way to discover the source of
- * its value. This offset is only used if given a value greater than
- * zero.
- */
- int isset_offset;
} relopt_parse_elt;
/* Local reloption definition */
@@ -195,6 +190,8 @@ typedef struct local_relopts
extern relopt_kind add_reloption_kind(void);
extern void add_bool_reloption(bits32 kinds, const char *name, const char *desc,
bool default_val, LOCKMODE lockmode);
+extern void add_ternary_reloption(bits32 kinds, const char *name,
+ const char *desc, int default_val, LOCKMODE lockmode);
extern void add_int_reloption(bits32 kinds, const char *name, const char *desc,
int default_val, int min_val, int max_val,
LOCKMODE lockmode);
@@ -214,6 +211,9 @@ extern void register_reloptions_validator(local_relopts *relopts,
extern void add_local_bool_reloption(local_relopts *relopts, const char *name,
const char *desc, bool default_val,
int offset);
+extern void add_local_ternary_reloption(local_relopts *relopts,
+ const char *name, const char *desc,
+ ternary default_val, int offset);
extern void add_local_int_reloption(local_relopts *relopts, const char *name,
const char *desc, int default_val,
int min_val, int max_val, int offset);
diff --git a/src/include/c.h b/src/include/c.h
index 39022f8a9d..9c59ccccf8 100644
--- a/src/include/c.h
+++ b/src/include/c.h
@@ -488,6 +488,22 @@ typedef void (*pg_funcptr_t) (void);
#include <stdbool.h>
+/*
+ * ternary
+ * Boolean value with an extrea "unset" option
+ *
+ * Ternary data type is used in relation options that can be "true", "false" or
+ * "unset". Since relation options are used deep inside the PostgreSQL code,
+ * this type is declared globally.
+*/
+
+typedef enum ternary
+{
+ TERNARY_FALSE = 0,
+ TERNARY_TRUE = 1,
+ TERNARY_UNSET = -1
+} ternary;
+
/* ----------------------------------------------------------------
* Section 3: standard system types
diff --git a/src/include/utils/rel.h b/src/include/utils/rel.h
index b552359915..7346d618f9 100644
--- a/src/include/utils/rel.h
+++ b/src/include/utils/rel.h
@@ -346,8 +346,7 @@ typedef struct StdRdOptions
bool user_catalog_table; /* use as an additional catalog relation */
int parallel_workers; /* max number of parallel workers */
StdRdOptIndexCleanup vacuum_index_cleanup; /* controls index vacuuming */
- bool vacuum_truncate; /* enables vacuum to truncate a relation */
- bool vacuum_truncate_set; /* whether vacuum_truncate is set */
+ ternary vacuum_truncate; /* enables vacuum to truncate a relation */
/*
* Fraction of pages in a relation that vacuum can eagerly scan and fail
--
2.39.2
--nextPart5129932.5fSG56mABF
Content-Disposition: attachment;
filename="v2-0003-Add-alias-to-be-used-as-unset-state.patch"
Content-Transfer-Encoding: 7Bit
Content-Type: text/x-patch; charset="unicode-2-0-utf-8";
name="v2-0003-Add-alias-to-be-used-as-unset-state.patch"
^ permalink raw reply [nested|flat] 868+ messages in thread
* [PATCH v2 2/4] Introduce ternary reloptions
@ 2025-09-04 14:41 Nikolay Shaplov <[email protected]>
0 siblings, 0 replies; 868+ messages in thread
From: Nikolay Shaplov @ 2025-09-04 14:41 UTC (permalink / raw)
Introduce ternary reloption as a replacement for current `vacuum_truncate`
implementation. Remove `vacuum_truncate_set` additional flag and using
`TERNARY_UNSET` value instead.
---
src/backend/access/common/reloptions.c | 129 ++++++++++++++++++++-----
src/backend/commands/vacuum.c | 4 +-
src/include/access/reloptions.h | 26 ++---
src/include/c.h | 16 +++
src/include/utils/rel.h | 3 +-
5 files changed, 135 insertions(+), 43 deletions(-)
diff --git a/src/backend/access/common/reloptions.c b/src/backend/access/common/reloptions.c
index 0af3fea68f..f543f11001 100644
--- a/src/backend/access/common/reloptions.c
+++ b/src/backend/access/common/reloptions.c
@@ -40,9 +40,9 @@
*
* To add an option:
*
- * (i) decide on a type (bool, integer, real, enum, string), name, default
- * value, upper and lower bounds (if applicable); for strings, consider a
- * validation routine.
+ * (i) decide on a type (bool, ternary, integer, real, enum, string), name,
+ * default value, upper and lower bounds (if applicable); for strings,
+ * consider a validation routine.
* (ii) add a record below (or use add_<type>_reloption).
* (iii) add it to the appropriate options struct (perhaps StdRdOptions)
* (iv) add it to the appropriate handling routine (perhaps
@@ -147,15 +147,6 @@ static relopt_bool boolRelOpts[] =
},
false
},
- {
- {
- "vacuum_truncate",
- "Enables vacuum to truncate empty pages at the end of this table",
- RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
- ShareUpdateExclusiveLock
- },
- true
- },
{
{
"deduplicate_items",
@@ -170,6 +161,21 @@ static relopt_bool boolRelOpts[] =
{{NULL}}
};
+static relopt_ternary ternaryRelOpts[] =
+{
+ {
+ {
+ "vacuum_truncate",
+ "Enables vacuum to truncate empty pages at the end of this table",
+ RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
+ ShareUpdateExclusiveLock
+ },
+ TERNARY_UNSET
+ },
+ /* list terminator */
+ {{NULL}}
+};
+
static relopt_int intRelOpts[] =
{
{
@@ -600,6 +606,13 @@ initialize_reloptions(void)
boolRelOpts[i].gen.lockmode));
j++;
}
+ for (i = 0; ternaryRelOpts[i].gen.name; i++)
+ {
+ Assert(DoLockModesConflict(ternaryRelOpts[i].gen.lockmode,
+ ternaryRelOpts[i].gen.lockmode));
+ j++;
+ }
+
for (i = 0; intRelOpts[i].gen.name; i++)
{
Assert(DoLockModesConflict(intRelOpts[i].gen.lockmode,
@@ -640,6 +653,14 @@ initialize_reloptions(void)
j++;
}
+ for (i = 0; ternaryRelOpts[i].gen.name; i++)
+ {
+ relOpts[j] = &ternaryRelOpts[i].gen;
+ relOpts[j]->type = RELOPT_TYPE_TERNARY;
+ relOpts[j]->namelen = strlen(relOpts[j]->name);
+ j++;
+ }
+
for (i = 0; intRelOpts[i].gen.name; i++)
{
relOpts[j] = &intRelOpts[i].gen;
@@ -800,6 +821,9 @@ allocate_reloption(bits32 kinds, int type, const char *name, const char *desc,
case RELOPT_TYPE_BOOL:
size = sizeof(relopt_bool);
break;
+ case RELOPT_TYPE_TERNARY:
+ size = sizeof(relopt_ternary);
+ break;
case RELOPT_TYPE_INT:
size = sizeof(relopt_int);
break;
@@ -883,6 +907,54 @@ add_local_bool_reloption(local_relopts *relopts, const char *name,
add_local_reloption(relopts, (relopt_gen *) newoption, offset);
}
+/*
+ * init_ternary_reloption
+ * Allocate and initialize a new ternary reloption
+ */
+static relopt_ternary *
+init_ternary_reloption(bits32 kinds, const char *name, const char *desc,
+ ternary default_val, LOCKMODE lockmode)
+{
+ relopt_ternary *newoption;
+
+ newoption = (relopt_ternary *) allocate_reloption(kinds,
+ RELOPT_TYPE_TERNARY, name, desc, lockmode);
+ newoption->default_val = default_val;
+
+ return newoption;
+}
+
+/*
+ * add_ternary_reloption
+ * Add a new ternary reloption
+ */
+void
+add_ternary_reloption(bits32 kinds, const char *name, const char *desc,
+ ternary default_val, LOCKMODE lockmode)
+{
+ relopt_ternary *newoption = init_ternary_reloption(kinds, name, desc,
+ default_val, lockmode);
+
+ add_reloption((relopt_gen *) newoption);
+}
+
+/*
+ * add_local_ternary_reloption
+ * Add a new ternary local reloption
+ *
+ * 'offset' is offset of ternary-typed field.
+ */
+void
+add_local_ternary_reloption(local_relopts *relopts, const char *name,
+ const char *desc, ternary default_val,
+ int offset)
+{
+ relopt_ternary *newoption = init_ternary_reloption(RELOPT_KIND_LOCAL,
+ name, desc,
+ default_val, 0);
+
+ add_local_reloption(relopts, (relopt_gen *) newoption, offset);
+}
/*
* init_real_reloption
@@ -1617,6 +1689,18 @@ parse_one_reloption(relopt_value *option, char *text_str, int text_len,
option->gen->name, value)));
}
break;
+ case RELOPT_TYPE_TERNARY:
+ {
+ bool b;
+ parsed = parse_bool(value, &b);
+ option->values.ternary_val = b ? TERNARY_TRUE : TERNARY_FALSE;
+ if (validate && !parsed)
+ ereport(ERROR,
+ (errcode(ERRCODE_INVALID_PARAMETER_VALUE),
+ errmsg("invalid value for ternary option \"%s\": %s",
+ option->gen->name, value)));
+ }
+ break;
case RELOPT_TYPE_INT:
{
relopt_int *optint = (relopt_int *) option->gen;
@@ -1780,17 +1864,6 @@ fillRelOptions(void *rdopts, Size basesize,
char *itempos = ((char *) rdopts) + elems[j].offset;
char *string_val;
- /*
- * If isset_offset is provided, store whether the reloption is
- * set there.
- */
- if (elems[j].isset_offset > 0)
- {
- char *setpos = ((char *) rdopts) + elems[j].isset_offset;
-
- *(bool *) setpos = options[i].isset;
- }
-
switch (options[i].gen->type)
{
case RELOPT_TYPE_BOOL:
@@ -1798,6 +1871,11 @@ fillRelOptions(void *rdopts, Size basesize,
options[i].values.bool_val :
((relopt_bool *) options[i].gen)->default_val;
break;
+ case RELOPT_TYPE_TERNARY:
+ *(ternary *) itempos = options[i].isset ?
+ options[i].values.ternary_val :
+ ((relopt_ternary *) options[i].gen)->default_val;
+ break;
case RELOPT_TYPE_INT:
*(int *) itempos = options[i].isset ?
options[i].values.int_val :
@@ -1912,8 +1990,8 @@ default_reloptions(Datum reloptions, bool validate, relopt_kind kind)
offsetof(StdRdOptions, parallel_workers)},
{"vacuum_index_cleanup", RELOPT_TYPE_ENUM,
offsetof(StdRdOptions, vacuum_index_cleanup)},
- {"vacuum_truncate", RELOPT_TYPE_BOOL,
- offsetof(StdRdOptions, vacuum_truncate), offsetof(StdRdOptions, vacuum_truncate_set)},
+ {"vacuum_truncate", RELOPT_TYPE_TERNARY,
+ offsetof(StdRdOptions, vacuum_truncate)},
{"vacuum_max_eager_freeze_failure_rate", RELOPT_TYPE_REAL,
offsetof(StdRdOptions, vacuum_max_eager_freeze_failure_rate)}
};
@@ -1993,7 +2071,6 @@ build_local_reloptions(local_relopts *relopts, Datum options, bool validate)
elems[i].optname = opt->option->name;
elems[i].opttype = opt->option->type;
elems[i].offset = opt->offset;
- elems[i].isset_offset = 0; /* not supported for local relopts yet */
i++;
}
diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c
index 733ef40ae7..977babff54 100644
--- a/src/backend/commands/vacuum.c
+++ b/src/backend/commands/vacuum.c
@@ -2221,9 +2221,9 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams params,
{
StdRdOptions *opts = (StdRdOptions *) rel->rd_options;
- if (opts && opts->vacuum_truncate_set)
+ if (opts && opts->vacuum_truncate != TERNARY_UNSET)
{
- if (opts->vacuum_truncate)
+ if (opts->vacuum_truncate == TERNARY_TRUE)
params.truncate = VACOPTVALUE_ENABLED;
else
params.truncate = VACOPTVALUE_DISABLED;
diff --git a/src/include/access/reloptions.h b/src/include/access/reloptions.h
index a604a4702c..a436697658 100644
--- a/src/include/access/reloptions.h
+++ b/src/include/access/reloptions.h
@@ -29,6 +29,7 @@
typedef enum relopt_type
{
RELOPT_TYPE_BOOL,
+ RELOPT_TYPE_TERNARY, /* on, off, unset */
RELOPT_TYPE_INT,
RELOPT_TYPE_REAL,
RELOPT_TYPE_ENUM,
@@ -80,6 +81,7 @@ typedef struct relopt_value
union
{
bool bool_val;
+ ternary ternary_val;
int int_val;
double real_val;
int enum_val;
@@ -94,6 +96,12 @@ typedef struct relopt_bool
bool default_val;
} relopt_bool;
+typedef struct relopt_rernary
+{
+ relopt_gen gen;
+ int default_val;
+} relopt_ternary;
+
typedef struct relopt_int
{
relopt_gen gen;
@@ -152,19 +160,6 @@ typedef struct
const char *optname; /* option's name */
relopt_type opttype; /* option's datatype */
int offset; /* offset of field in result struct */
-
- /*
- * isset_offset is an optional offset of a field in the result struct that
- * stores whether the option is explicitly set for the relation or if it
- * just picked up the default value. In most cases, this can be
- * accomplished by giving the reloption a special out-of-range default
- * value (e.g., some integer reloptions use -2), but this isn't always
- * possible. For example, a Boolean reloption cannot be given an
- * out-of-range default, so we need another way to discover the source of
- * its value. This offset is only used if given a value greater than
- * zero.
- */
- int isset_offset;
} relopt_parse_elt;
/* Local reloption definition */
@@ -195,6 +190,8 @@ typedef struct local_relopts
extern relopt_kind add_reloption_kind(void);
extern void add_bool_reloption(bits32 kinds, const char *name, const char *desc,
bool default_val, LOCKMODE lockmode);
+extern void add_ternary_reloption(bits32 kinds, const char *name,
+ const char *desc, int default_val, LOCKMODE lockmode);
extern void add_int_reloption(bits32 kinds, const char *name, const char *desc,
int default_val, int min_val, int max_val,
LOCKMODE lockmode);
@@ -214,6 +211,9 @@ extern void register_reloptions_validator(local_relopts *relopts,
extern void add_local_bool_reloption(local_relopts *relopts, const char *name,
const char *desc, bool default_val,
int offset);
+extern void add_local_ternary_reloption(local_relopts *relopts,
+ const char *name, const char *desc,
+ ternary default_val, int offset);
extern void add_local_int_reloption(local_relopts *relopts, const char *name,
const char *desc, int default_val,
int min_val, int max_val, int offset);
diff --git a/src/include/c.h b/src/include/c.h
index 39022f8a9d..9c59ccccf8 100644
--- a/src/include/c.h
+++ b/src/include/c.h
@@ -488,6 +488,22 @@ typedef void (*pg_funcptr_t) (void);
#include <stdbool.h>
+/*
+ * ternary
+ * Boolean value with an extrea "unset" option
+ *
+ * Ternary data type is used in relation options that can be "true", "false" or
+ * "unset". Since relation options are used deep inside the PostgreSQL code,
+ * this type is declared globally.
+*/
+
+typedef enum ternary
+{
+ TERNARY_FALSE = 0,
+ TERNARY_TRUE = 1,
+ TERNARY_UNSET = -1
+} ternary;
+
/* ----------------------------------------------------------------
* Section 3: standard system types
diff --git a/src/include/utils/rel.h b/src/include/utils/rel.h
index b552359915..7346d618f9 100644
--- a/src/include/utils/rel.h
+++ b/src/include/utils/rel.h
@@ -346,8 +346,7 @@ typedef struct StdRdOptions
bool user_catalog_table; /* use as an additional catalog relation */
int parallel_workers; /* max number of parallel workers */
StdRdOptIndexCleanup vacuum_index_cleanup; /* controls index vacuuming */
- bool vacuum_truncate; /* enables vacuum to truncate a relation */
- bool vacuum_truncate_set; /* whether vacuum_truncate is set */
+ ternary vacuum_truncate; /* enables vacuum to truncate a relation */
/*
* Fraction of pages in a relation that vacuum can eagerly scan and fail
--
2.39.2
--nextPart5129932.5fSG56mABF
Content-Disposition: attachment;
filename="v2-0003-Add-alias-to-be-used-as-unset-state.patch"
Content-Transfer-Encoding: 7Bit
Content-Type: text/x-patch; charset="unicode-2-0-utf-8";
name="v2-0003-Add-alias-to-be-used-as-unset-state.patch"
^ permalink raw reply [nested|flat] 868+ messages in thread
* [PATCH v2a 2/4] Introduce ternary reloptions
@ 2025-09-04 14:41 Nikolay Shaplov <[email protected]>
0 siblings, 0 replies; 868+ messages in thread
From: Nikolay Shaplov @ 2025-09-04 14:41 UTC (permalink / raw)
Introduce ternary reloption as a replacement for current `vacuum_truncate`
implementation. Remove `vacuum_truncate_set` additional flag and using
`TERNARY_UNSET` value instead.
---
src/backend/access/common/reloptions.c | 129 ++++++++++++++++++++-----
src/backend/commands/vacuum.c | 4 +-
src/include/access/reloptions.h | 26 ++---
src/include/c.h | 16 +++
src/include/utils/rel.h | 3 +-
5 files changed, 135 insertions(+), 43 deletions(-)
diff --git a/src/backend/access/common/reloptions.c b/src/backend/access/common/reloptions.c
index 0af3fea68f..f543f11001 100644
--- a/src/backend/access/common/reloptions.c
+++ b/src/backend/access/common/reloptions.c
@@ -40,9 +40,9 @@
*
* To add an option:
*
- * (i) decide on a type (bool, integer, real, enum, string), name, default
- * value, upper and lower bounds (if applicable); for strings, consider a
- * validation routine.
+ * (i) decide on a type (bool, ternary, integer, real, enum, string), name,
+ * default value, upper and lower bounds (if applicable); for strings,
+ * consider a validation routine.
* (ii) add a record below (or use add_<type>_reloption).
* (iii) add it to the appropriate options struct (perhaps StdRdOptions)
* (iv) add it to the appropriate handling routine (perhaps
@@ -147,15 +147,6 @@ static relopt_bool boolRelOpts[] =
},
false
},
- {
- {
- "vacuum_truncate",
- "Enables vacuum to truncate empty pages at the end of this table",
- RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
- ShareUpdateExclusiveLock
- },
- true
- },
{
{
"deduplicate_items",
@@ -170,6 +161,21 @@ static relopt_bool boolRelOpts[] =
{{NULL}}
};
+static relopt_ternary ternaryRelOpts[] =
+{
+ {
+ {
+ "vacuum_truncate",
+ "Enables vacuum to truncate empty pages at the end of this table",
+ RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
+ ShareUpdateExclusiveLock
+ },
+ TERNARY_UNSET
+ },
+ /* list terminator */
+ {{NULL}}
+};
+
static relopt_int intRelOpts[] =
{
{
@@ -600,6 +606,13 @@ initialize_reloptions(void)
boolRelOpts[i].gen.lockmode));
j++;
}
+ for (i = 0; ternaryRelOpts[i].gen.name; i++)
+ {
+ Assert(DoLockModesConflict(ternaryRelOpts[i].gen.lockmode,
+ ternaryRelOpts[i].gen.lockmode));
+ j++;
+ }
+
for (i = 0; intRelOpts[i].gen.name; i++)
{
Assert(DoLockModesConflict(intRelOpts[i].gen.lockmode,
@@ -640,6 +653,14 @@ initialize_reloptions(void)
j++;
}
+ for (i = 0; ternaryRelOpts[i].gen.name; i++)
+ {
+ relOpts[j] = &ternaryRelOpts[i].gen;
+ relOpts[j]->type = RELOPT_TYPE_TERNARY;
+ relOpts[j]->namelen = strlen(relOpts[j]->name);
+ j++;
+ }
+
for (i = 0; intRelOpts[i].gen.name; i++)
{
relOpts[j] = &intRelOpts[i].gen;
@@ -800,6 +821,9 @@ allocate_reloption(bits32 kinds, int type, const char *name, const char *desc,
case RELOPT_TYPE_BOOL:
size = sizeof(relopt_bool);
break;
+ case RELOPT_TYPE_TERNARY:
+ size = sizeof(relopt_ternary);
+ break;
case RELOPT_TYPE_INT:
size = sizeof(relopt_int);
break;
@@ -883,6 +907,54 @@ add_local_bool_reloption(local_relopts *relopts, const char *name,
add_local_reloption(relopts, (relopt_gen *) newoption, offset);
}
+/*
+ * init_ternary_reloption
+ * Allocate and initialize a new ternary reloption
+ */
+static relopt_ternary *
+init_ternary_reloption(bits32 kinds, const char *name, const char *desc,
+ ternary default_val, LOCKMODE lockmode)
+{
+ relopt_ternary *newoption;
+
+ newoption = (relopt_ternary *) allocate_reloption(kinds,
+ RELOPT_TYPE_TERNARY, name, desc, lockmode);
+ newoption->default_val = default_val;
+
+ return newoption;
+}
+
+/*
+ * add_ternary_reloption
+ * Add a new ternary reloption
+ */
+void
+add_ternary_reloption(bits32 kinds, const char *name, const char *desc,
+ ternary default_val, LOCKMODE lockmode)
+{
+ relopt_ternary *newoption = init_ternary_reloption(kinds, name, desc,
+ default_val, lockmode);
+
+ add_reloption((relopt_gen *) newoption);
+}
+
+/*
+ * add_local_ternary_reloption
+ * Add a new ternary local reloption
+ *
+ * 'offset' is offset of ternary-typed field.
+ */
+void
+add_local_ternary_reloption(local_relopts *relopts, const char *name,
+ const char *desc, ternary default_val,
+ int offset)
+{
+ relopt_ternary *newoption = init_ternary_reloption(RELOPT_KIND_LOCAL,
+ name, desc,
+ default_val, 0);
+
+ add_local_reloption(relopts, (relopt_gen *) newoption, offset);
+}
/*
* init_real_reloption
@@ -1617,6 +1689,18 @@ parse_one_reloption(relopt_value *option, char *text_str, int text_len,
option->gen->name, value)));
}
break;
+ case RELOPT_TYPE_TERNARY:
+ {
+ bool b;
+ parsed = parse_bool(value, &b);
+ option->values.ternary_val = b ? TERNARY_TRUE : TERNARY_FALSE;
+ if (validate && !parsed)
+ ereport(ERROR,
+ (errcode(ERRCODE_INVALID_PARAMETER_VALUE),
+ errmsg("invalid value for ternary option \"%s\": %s",
+ option->gen->name, value)));
+ }
+ break;
case RELOPT_TYPE_INT:
{
relopt_int *optint = (relopt_int *) option->gen;
@@ -1780,17 +1864,6 @@ fillRelOptions(void *rdopts, Size basesize,
char *itempos = ((char *) rdopts) + elems[j].offset;
char *string_val;
- /*
- * If isset_offset is provided, store whether the reloption is
- * set there.
- */
- if (elems[j].isset_offset > 0)
- {
- char *setpos = ((char *) rdopts) + elems[j].isset_offset;
-
- *(bool *) setpos = options[i].isset;
- }
-
switch (options[i].gen->type)
{
case RELOPT_TYPE_BOOL:
@@ -1798,6 +1871,11 @@ fillRelOptions(void *rdopts, Size basesize,
options[i].values.bool_val :
((relopt_bool *) options[i].gen)->default_val;
break;
+ case RELOPT_TYPE_TERNARY:
+ *(ternary *) itempos = options[i].isset ?
+ options[i].values.ternary_val :
+ ((relopt_ternary *) options[i].gen)->default_val;
+ break;
case RELOPT_TYPE_INT:
*(int *) itempos = options[i].isset ?
options[i].values.int_val :
@@ -1912,8 +1990,8 @@ default_reloptions(Datum reloptions, bool validate, relopt_kind kind)
offsetof(StdRdOptions, parallel_workers)},
{"vacuum_index_cleanup", RELOPT_TYPE_ENUM,
offsetof(StdRdOptions, vacuum_index_cleanup)},
- {"vacuum_truncate", RELOPT_TYPE_BOOL,
- offsetof(StdRdOptions, vacuum_truncate), offsetof(StdRdOptions, vacuum_truncate_set)},
+ {"vacuum_truncate", RELOPT_TYPE_TERNARY,
+ offsetof(StdRdOptions, vacuum_truncate)},
{"vacuum_max_eager_freeze_failure_rate", RELOPT_TYPE_REAL,
offsetof(StdRdOptions, vacuum_max_eager_freeze_failure_rate)}
};
@@ -1993,7 +2071,6 @@ build_local_reloptions(local_relopts *relopts, Datum options, bool validate)
elems[i].optname = opt->option->name;
elems[i].opttype = opt->option->type;
elems[i].offset = opt->offset;
- elems[i].isset_offset = 0; /* not supported for local relopts yet */
i++;
}
diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c
index 733ef40ae7..977babff54 100644
--- a/src/backend/commands/vacuum.c
+++ b/src/backend/commands/vacuum.c
@@ -2221,9 +2221,9 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams params,
{
StdRdOptions *opts = (StdRdOptions *) rel->rd_options;
- if (opts && opts->vacuum_truncate_set)
+ if (opts && opts->vacuum_truncate != TERNARY_UNSET)
{
- if (opts->vacuum_truncate)
+ if (opts->vacuum_truncate == TERNARY_TRUE)
params.truncate = VACOPTVALUE_ENABLED;
else
params.truncate = VACOPTVALUE_DISABLED;
diff --git a/src/include/access/reloptions.h b/src/include/access/reloptions.h
index a604a4702c..a436697658 100644
--- a/src/include/access/reloptions.h
+++ b/src/include/access/reloptions.h
@@ -29,6 +29,7 @@
typedef enum relopt_type
{
RELOPT_TYPE_BOOL,
+ RELOPT_TYPE_TERNARY, /* on, off, unset */
RELOPT_TYPE_INT,
RELOPT_TYPE_REAL,
RELOPT_TYPE_ENUM,
@@ -80,6 +81,7 @@ typedef struct relopt_value
union
{
bool bool_val;
+ ternary ternary_val;
int int_val;
double real_val;
int enum_val;
@@ -94,6 +96,12 @@ typedef struct relopt_bool
bool default_val;
} relopt_bool;
+typedef struct relopt_rernary
+{
+ relopt_gen gen;
+ int default_val;
+} relopt_ternary;
+
typedef struct relopt_int
{
relopt_gen gen;
@@ -152,19 +160,6 @@ typedef struct
const char *optname; /* option's name */
relopt_type opttype; /* option's datatype */
int offset; /* offset of field in result struct */
-
- /*
- * isset_offset is an optional offset of a field in the result struct that
- * stores whether the option is explicitly set for the relation or if it
- * just picked up the default value. In most cases, this can be
- * accomplished by giving the reloption a special out-of-range default
- * value (e.g., some integer reloptions use -2), but this isn't always
- * possible. For example, a Boolean reloption cannot be given an
- * out-of-range default, so we need another way to discover the source of
- * its value. This offset is only used if given a value greater than
- * zero.
- */
- int isset_offset;
} relopt_parse_elt;
/* Local reloption definition */
@@ -195,6 +190,8 @@ typedef struct local_relopts
extern relopt_kind add_reloption_kind(void);
extern void add_bool_reloption(bits32 kinds, const char *name, const char *desc,
bool default_val, LOCKMODE lockmode);
+extern void add_ternary_reloption(bits32 kinds, const char *name,
+ const char *desc, int default_val, LOCKMODE lockmode);
extern void add_int_reloption(bits32 kinds, const char *name, const char *desc,
int default_val, int min_val, int max_val,
LOCKMODE lockmode);
@@ -214,6 +211,9 @@ extern void register_reloptions_validator(local_relopts *relopts,
extern void add_local_bool_reloption(local_relopts *relopts, const char *name,
const char *desc, bool default_val,
int offset);
+extern void add_local_ternary_reloption(local_relopts *relopts,
+ const char *name, const char *desc,
+ ternary default_val, int offset);
extern void add_local_int_reloption(local_relopts *relopts, const char *name,
const char *desc, int default_val,
int min_val, int max_val, int offset);
diff --git a/src/include/c.h b/src/include/c.h
index 39022f8a9d..9c59ccccf8 100644
--- a/src/include/c.h
+++ b/src/include/c.h
@@ -488,6 +488,22 @@ typedef void (*pg_funcptr_t) (void);
#include <stdbool.h>
+/*
+ * ternary
+ * Boolean value with an extrea "unset" option
+ *
+ * Ternary data type is used in relation options that can be "true", "false" or
+ * "unset". Since relation options are used deep inside the PostgreSQL code,
+ * this type is declared globally.
+*/
+
+typedef enum ternary
+{
+ TERNARY_FALSE = 0,
+ TERNARY_TRUE = 1,
+ TERNARY_UNSET = -1
+} ternary;
+
/* ----------------------------------------------------------------
* Section 3: standard system types
diff --git a/src/include/utils/rel.h b/src/include/utils/rel.h
index b552359915..7346d618f9 100644
--- a/src/include/utils/rel.h
+++ b/src/include/utils/rel.h
@@ -346,8 +346,7 @@ typedef struct StdRdOptions
bool user_catalog_table; /* use as an additional catalog relation */
int parallel_workers; /* max number of parallel workers */
StdRdOptIndexCleanup vacuum_index_cleanup; /* controls index vacuuming */
- bool vacuum_truncate; /* enables vacuum to truncate a relation */
- bool vacuum_truncate_set; /* whether vacuum_truncate is set */
+ ternary vacuum_truncate; /* enables vacuum to truncate a relation */
/*
* Fraction of pages in a relation that vacuum can eagerly scan and fail
--
2.39.2
--nextPart4155600.3ZeAukHxDK
Content-Disposition: attachment;
filename="v2a-0003-Add-alias-to-be-used-as-unset-state.patch"
Content-Transfer-Encoding: 7Bit
Content-Type: text/x-patch; charset="unicode-2-0-utf-8";
name="v2a-0003-Add-alias-to-be-used-as-unset-state.patch"
^ permalink raw reply [nested|flat] 868+ messages in thread
* [PATCH v2 2/4] Introduce ternary reloptions
@ 2025-09-04 14:41 Nikolay Shaplov <[email protected]>
0 siblings, 0 replies; 868+ messages in thread
From: Nikolay Shaplov @ 2025-09-04 14:41 UTC (permalink / raw)
Introduce ternary reloption as a replacement for current `vacuum_truncate`
implementation. Remove `vacuum_truncate_set` additional flag and using
`TERNARY_UNSET` value instead.
---
src/backend/access/common/reloptions.c | 129 ++++++++++++++++++++-----
src/backend/commands/vacuum.c | 4 +-
src/include/access/reloptions.h | 26 ++---
src/include/c.h | 16 +++
src/include/utils/rel.h | 3 +-
5 files changed, 135 insertions(+), 43 deletions(-)
diff --git a/src/backend/access/common/reloptions.c b/src/backend/access/common/reloptions.c
index 0af3fea68f..f543f11001 100644
--- a/src/backend/access/common/reloptions.c
+++ b/src/backend/access/common/reloptions.c
@@ -40,9 +40,9 @@
*
* To add an option:
*
- * (i) decide on a type (bool, integer, real, enum, string), name, default
- * value, upper and lower bounds (if applicable); for strings, consider a
- * validation routine.
+ * (i) decide on a type (bool, ternary, integer, real, enum, string), name,
+ * default value, upper and lower bounds (if applicable); for strings,
+ * consider a validation routine.
* (ii) add a record below (or use add_<type>_reloption).
* (iii) add it to the appropriate options struct (perhaps StdRdOptions)
* (iv) add it to the appropriate handling routine (perhaps
@@ -147,15 +147,6 @@ static relopt_bool boolRelOpts[] =
},
false
},
- {
- {
- "vacuum_truncate",
- "Enables vacuum to truncate empty pages at the end of this table",
- RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
- ShareUpdateExclusiveLock
- },
- true
- },
{
{
"deduplicate_items",
@@ -170,6 +161,21 @@ static relopt_bool boolRelOpts[] =
{{NULL}}
};
+static relopt_ternary ternaryRelOpts[] =
+{
+ {
+ {
+ "vacuum_truncate",
+ "Enables vacuum to truncate empty pages at the end of this table",
+ RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
+ ShareUpdateExclusiveLock
+ },
+ TERNARY_UNSET
+ },
+ /* list terminator */
+ {{NULL}}
+};
+
static relopt_int intRelOpts[] =
{
{
@@ -600,6 +606,13 @@ initialize_reloptions(void)
boolRelOpts[i].gen.lockmode));
j++;
}
+ for (i = 0; ternaryRelOpts[i].gen.name; i++)
+ {
+ Assert(DoLockModesConflict(ternaryRelOpts[i].gen.lockmode,
+ ternaryRelOpts[i].gen.lockmode));
+ j++;
+ }
+
for (i = 0; intRelOpts[i].gen.name; i++)
{
Assert(DoLockModesConflict(intRelOpts[i].gen.lockmode,
@@ -640,6 +653,14 @@ initialize_reloptions(void)
j++;
}
+ for (i = 0; ternaryRelOpts[i].gen.name; i++)
+ {
+ relOpts[j] = &ternaryRelOpts[i].gen;
+ relOpts[j]->type = RELOPT_TYPE_TERNARY;
+ relOpts[j]->namelen = strlen(relOpts[j]->name);
+ j++;
+ }
+
for (i = 0; intRelOpts[i].gen.name; i++)
{
relOpts[j] = &intRelOpts[i].gen;
@@ -800,6 +821,9 @@ allocate_reloption(bits32 kinds, int type, const char *name, const char *desc,
case RELOPT_TYPE_BOOL:
size = sizeof(relopt_bool);
break;
+ case RELOPT_TYPE_TERNARY:
+ size = sizeof(relopt_ternary);
+ break;
case RELOPT_TYPE_INT:
size = sizeof(relopt_int);
break;
@@ -883,6 +907,54 @@ add_local_bool_reloption(local_relopts *relopts, const char *name,
add_local_reloption(relopts, (relopt_gen *) newoption, offset);
}
+/*
+ * init_ternary_reloption
+ * Allocate and initialize a new ternary reloption
+ */
+static relopt_ternary *
+init_ternary_reloption(bits32 kinds, const char *name, const char *desc,
+ ternary default_val, LOCKMODE lockmode)
+{
+ relopt_ternary *newoption;
+
+ newoption = (relopt_ternary *) allocate_reloption(kinds,
+ RELOPT_TYPE_TERNARY, name, desc, lockmode);
+ newoption->default_val = default_val;
+
+ return newoption;
+}
+
+/*
+ * add_ternary_reloption
+ * Add a new ternary reloption
+ */
+void
+add_ternary_reloption(bits32 kinds, const char *name, const char *desc,
+ ternary default_val, LOCKMODE lockmode)
+{
+ relopt_ternary *newoption = init_ternary_reloption(kinds, name, desc,
+ default_val, lockmode);
+
+ add_reloption((relopt_gen *) newoption);
+}
+
+/*
+ * add_local_ternary_reloption
+ * Add a new ternary local reloption
+ *
+ * 'offset' is offset of ternary-typed field.
+ */
+void
+add_local_ternary_reloption(local_relopts *relopts, const char *name,
+ const char *desc, ternary default_val,
+ int offset)
+{
+ relopt_ternary *newoption = init_ternary_reloption(RELOPT_KIND_LOCAL,
+ name, desc,
+ default_val, 0);
+
+ add_local_reloption(relopts, (relopt_gen *) newoption, offset);
+}
/*
* init_real_reloption
@@ -1617,6 +1689,18 @@ parse_one_reloption(relopt_value *option, char *text_str, int text_len,
option->gen->name, value)));
}
break;
+ case RELOPT_TYPE_TERNARY:
+ {
+ bool b;
+ parsed = parse_bool(value, &b);
+ option->values.ternary_val = b ? TERNARY_TRUE : TERNARY_FALSE;
+ if (validate && !parsed)
+ ereport(ERROR,
+ (errcode(ERRCODE_INVALID_PARAMETER_VALUE),
+ errmsg("invalid value for ternary option \"%s\": %s",
+ option->gen->name, value)));
+ }
+ break;
case RELOPT_TYPE_INT:
{
relopt_int *optint = (relopt_int *) option->gen;
@@ -1780,17 +1864,6 @@ fillRelOptions(void *rdopts, Size basesize,
char *itempos = ((char *) rdopts) + elems[j].offset;
char *string_val;
- /*
- * If isset_offset is provided, store whether the reloption is
- * set there.
- */
- if (elems[j].isset_offset > 0)
- {
- char *setpos = ((char *) rdopts) + elems[j].isset_offset;
-
- *(bool *) setpos = options[i].isset;
- }
-
switch (options[i].gen->type)
{
case RELOPT_TYPE_BOOL:
@@ -1798,6 +1871,11 @@ fillRelOptions(void *rdopts, Size basesize,
options[i].values.bool_val :
((relopt_bool *) options[i].gen)->default_val;
break;
+ case RELOPT_TYPE_TERNARY:
+ *(ternary *) itempos = options[i].isset ?
+ options[i].values.ternary_val :
+ ((relopt_ternary *) options[i].gen)->default_val;
+ break;
case RELOPT_TYPE_INT:
*(int *) itempos = options[i].isset ?
options[i].values.int_val :
@@ -1912,8 +1990,8 @@ default_reloptions(Datum reloptions, bool validate, relopt_kind kind)
offsetof(StdRdOptions, parallel_workers)},
{"vacuum_index_cleanup", RELOPT_TYPE_ENUM,
offsetof(StdRdOptions, vacuum_index_cleanup)},
- {"vacuum_truncate", RELOPT_TYPE_BOOL,
- offsetof(StdRdOptions, vacuum_truncate), offsetof(StdRdOptions, vacuum_truncate_set)},
+ {"vacuum_truncate", RELOPT_TYPE_TERNARY,
+ offsetof(StdRdOptions, vacuum_truncate)},
{"vacuum_max_eager_freeze_failure_rate", RELOPT_TYPE_REAL,
offsetof(StdRdOptions, vacuum_max_eager_freeze_failure_rate)}
};
@@ -1993,7 +2071,6 @@ build_local_reloptions(local_relopts *relopts, Datum options, bool validate)
elems[i].optname = opt->option->name;
elems[i].opttype = opt->option->type;
elems[i].offset = opt->offset;
- elems[i].isset_offset = 0; /* not supported for local relopts yet */
i++;
}
diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c
index 733ef40ae7..977babff54 100644
--- a/src/backend/commands/vacuum.c
+++ b/src/backend/commands/vacuum.c
@@ -2221,9 +2221,9 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams params,
{
StdRdOptions *opts = (StdRdOptions *) rel->rd_options;
- if (opts && opts->vacuum_truncate_set)
+ if (opts && opts->vacuum_truncate != TERNARY_UNSET)
{
- if (opts->vacuum_truncate)
+ if (opts->vacuum_truncate == TERNARY_TRUE)
params.truncate = VACOPTVALUE_ENABLED;
else
params.truncate = VACOPTVALUE_DISABLED;
diff --git a/src/include/access/reloptions.h b/src/include/access/reloptions.h
index a604a4702c..a436697658 100644
--- a/src/include/access/reloptions.h
+++ b/src/include/access/reloptions.h
@@ -29,6 +29,7 @@
typedef enum relopt_type
{
RELOPT_TYPE_BOOL,
+ RELOPT_TYPE_TERNARY, /* on, off, unset */
RELOPT_TYPE_INT,
RELOPT_TYPE_REAL,
RELOPT_TYPE_ENUM,
@@ -80,6 +81,7 @@ typedef struct relopt_value
union
{
bool bool_val;
+ ternary ternary_val;
int int_val;
double real_val;
int enum_val;
@@ -94,6 +96,12 @@ typedef struct relopt_bool
bool default_val;
} relopt_bool;
+typedef struct relopt_rernary
+{
+ relopt_gen gen;
+ int default_val;
+} relopt_ternary;
+
typedef struct relopt_int
{
relopt_gen gen;
@@ -152,19 +160,6 @@ typedef struct
const char *optname; /* option's name */
relopt_type opttype; /* option's datatype */
int offset; /* offset of field in result struct */
-
- /*
- * isset_offset is an optional offset of a field in the result struct that
- * stores whether the option is explicitly set for the relation or if it
- * just picked up the default value. In most cases, this can be
- * accomplished by giving the reloption a special out-of-range default
- * value (e.g., some integer reloptions use -2), but this isn't always
- * possible. For example, a Boolean reloption cannot be given an
- * out-of-range default, so we need another way to discover the source of
- * its value. This offset is only used if given a value greater than
- * zero.
- */
- int isset_offset;
} relopt_parse_elt;
/* Local reloption definition */
@@ -195,6 +190,8 @@ typedef struct local_relopts
extern relopt_kind add_reloption_kind(void);
extern void add_bool_reloption(bits32 kinds, const char *name, const char *desc,
bool default_val, LOCKMODE lockmode);
+extern void add_ternary_reloption(bits32 kinds, const char *name,
+ const char *desc, int default_val, LOCKMODE lockmode);
extern void add_int_reloption(bits32 kinds, const char *name, const char *desc,
int default_val, int min_val, int max_val,
LOCKMODE lockmode);
@@ -214,6 +211,9 @@ extern void register_reloptions_validator(local_relopts *relopts,
extern void add_local_bool_reloption(local_relopts *relopts, const char *name,
const char *desc, bool default_val,
int offset);
+extern void add_local_ternary_reloption(local_relopts *relopts,
+ const char *name, const char *desc,
+ ternary default_val, int offset);
extern void add_local_int_reloption(local_relopts *relopts, const char *name,
const char *desc, int default_val,
int min_val, int max_val, int offset);
diff --git a/src/include/c.h b/src/include/c.h
index 39022f8a9d..9c59ccccf8 100644
--- a/src/include/c.h
+++ b/src/include/c.h
@@ -488,6 +488,22 @@ typedef void (*pg_funcptr_t) (void);
#include <stdbool.h>
+/*
+ * ternary
+ * Boolean value with an extrea "unset" option
+ *
+ * Ternary data type is used in relation options that can be "true", "false" or
+ * "unset". Since relation options are used deep inside the PostgreSQL code,
+ * this type is declared globally.
+*/
+
+typedef enum ternary
+{
+ TERNARY_FALSE = 0,
+ TERNARY_TRUE = 1,
+ TERNARY_UNSET = -1
+} ternary;
+
/* ----------------------------------------------------------------
* Section 3: standard system types
diff --git a/src/include/utils/rel.h b/src/include/utils/rel.h
index b552359915..7346d618f9 100644
--- a/src/include/utils/rel.h
+++ b/src/include/utils/rel.h
@@ -346,8 +346,7 @@ typedef struct StdRdOptions
bool user_catalog_table; /* use as an additional catalog relation */
int parallel_workers; /* max number of parallel workers */
StdRdOptIndexCleanup vacuum_index_cleanup; /* controls index vacuuming */
- bool vacuum_truncate; /* enables vacuum to truncate a relation */
- bool vacuum_truncate_set; /* whether vacuum_truncate is set */
+ ternary vacuum_truncate; /* enables vacuum to truncate a relation */
/*
* Fraction of pages in a relation that vacuum can eagerly scan and fail
--
2.39.2
--nextPart5129932.5fSG56mABF
Content-Disposition: attachment;
filename="v2-0003-Add-alias-to-be-used-as-unset-state.patch"
Content-Transfer-Encoding: 7Bit
Content-Type: text/x-patch; charset="unicode-2-0-utf-8";
name="v2-0003-Add-alias-to-be-used-as-unset-state.patch"
^ permalink raw reply [nested|flat] 868+ messages in thread
* [PATCH v2 2/4] Introduce ternary reloptions
@ 2025-09-04 14:41 Nikolay Shaplov <[email protected]>
0 siblings, 0 replies; 868+ messages in thread
From: Nikolay Shaplov @ 2025-09-04 14:41 UTC (permalink / raw)
Introduce ternary reloption as a replacement for current `vacuum_truncate`
implementation. Remove `vacuum_truncate_set` additional flag and using
`TERNARY_UNSET` value instead.
---
src/backend/access/common/reloptions.c | 129 ++++++++++++++++++++-----
src/backend/commands/vacuum.c | 4 +-
src/include/access/reloptions.h | 26 ++---
src/include/c.h | 16 +++
src/include/utils/rel.h | 3 +-
5 files changed, 135 insertions(+), 43 deletions(-)
diff --git a/src/backend/access/common/reloptions.c b/src/backend/access/common/reloptions.c
index 0af3fea68f..f543f11001 100644
--- a/src/backend/access/common/reloptions.c
+++ b/src/backend/access/common/reloptions.c
@@ -40,9 +40,9 @@
*
* To add an option:
*
- * (i) decide on a type (bool, integer, real, enum, string), name, default
- * value, upper and lower bounds (if applicable); for strings, consider a
- * validation routine.
+ * (i) decide on a type (bool, ternary, integer, real, enum, string), name,
+ * default value, upper and lower bounds (if applicable); for strings,
+ * consider a validation routine.
* (ii) add a record below (or use add_<type>_reloption).
* (iii) add it to the appropriate options struct (perhaps StdRdOptions)
* (iv) add it to the appropriate handling routine (perhaps
@@ -147,15 +147,6 @@ static relopt_bool boolRelOpts[] =
},
false
},
- {
- {
- "vacuum_truncate",
- "Enables vacuum to truncate empty pages at the end of this table",
- RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
- ShareUpdateExclusiveLock
- },
- true
- },
{
{
"deduplicate_items",
@@ -170,6 +161,21 @@ static relopt_bool boolRelOpts[] =
{{NULL}}
};
+static relopt_ternary ternaryRelOpts[] =
+{
+ {
+ {
+ "vacuum_truncate",
+ "Enables vacuum to truncate empty pages at the end of this table",
+ RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
+ ShareUpdateExclusiveLock
+ },
+ TERNARY_UNSET
+ },
+ /* list terminator */
+ {{NULL}}
+};
+
static relopt_int intRelOpts[] =
{
{
@@ -600,6 +606,13 @@ initialize_reloptions(void)
boolRelOpts[i].gen.lockmode));
j++;
}
+ for (i = 0; ternaryRelOpts[i].gen.name; i++)
+ {
+ Assert(DoLockModesConflict(ternaryRelOpts[i].gen.lockmode,
+ ternaryRelOpts[i].gen.lockmode));
+ j++;
+ }
+
for (i = 0; intRelOpts[i].gen.name; i++)
{
Assert(DoLockModesConflict(intRelOpts[i].gen.lockmode,
@@ -640,6 +653,14 @@ initialize_reloptions(void)
j++;
}
+ for (i = 0; ternaryRelOpts[i].gen.name; i++)
+ {
+ relOpts[j] = &ternaryRelOpts[i].gen;
+ relOpts[j]->type = RELOPT_TYPE_TERNARY;
+ relOpts[j]->namelen = strlen(relOpts[j]->name);
+ j++;
+ }
+
for (i = 0; intRelOpts[i].gen.name; i++)
{
relOpts[j] = &intRelOpts[i].gen;
@@ -800,6 +821,9 @@ allocate_reloption(bits32 kinds, int type, const char *name, const char *desc,
case RELOPT_TYPE_BOOL:
size = sizeof(relopt_bool);
break;
+ case RELOPT_TYPE_TERNARY:
+ size = sizeof(relopt_ternary);
+ break;
case RELOPT_TYPE_INT:
size = sizeof(relopt_int);
break;
@@ -883,6 +907,54 @@ add_local_bool_reloption(local_relopts *relopts, const char *name,
add_local_reloption(relopts, (relopt_gen *) newoption, offset);
}
+/*
+ * init_ternary_reloption
+ * Allocate and initialize a new ternary reloption
+ */
+static relopt_ternary *
+init_ternary_reloption(bits32 kinds, const char *name, const char *desc,
+ ternary default_val, LOCKMODE lockmode)
+{
+ relopt_ternary *newoption;
+
+ newoption = (relopt_ternary *) allocate_reloption(kinds,
+ RELOPT_TYPE_TERNARY, name, desc, lockmode);
+ newoption->default_val = default_val;
+
+ return newoption;
+}
+
+/*
+ * add_ternary_reloption
+ * Add a new ternary reloption
+ */
+void
+add_ternary_reloption(bits32 kinds, const char *name, const char *desc,
+ ternary default_val, LOCKMODE lockmode)
+{
+ relopt_ternary *newoption = init_ternary_reloption(kinds, name, desc,
+ default_val, lockmode);
+
+ add_reloption((relopt_gen *) newoption);
+}
+
+/*
+ * add_local_ternary_reloption
+ * Add a new ternary local reloption
+ *
+ * 'offset' is offset of ternary-typed field.
+ */
+void
+add_local_ternary_reloption(local_relopts *relopts, const char *name,
+ const char *desc, ternary default_val,
+ int offset)
+{
+ relopt_ternary *newoption = init_ternary_reloption(RELOPT_KIND_LOCAL,
+ name, desc,
+ default_val, 0);
+
+ add_local_reloption(relopts, (relopt_gen *) newoption, offset);
+}
/*
* init_real_reloption
@@ -1617,6 +1689,18 @@ parse_one_reloption(relopt_value *option, char *text_str, int text_len,
option->gen->name, value)));
}
break;
+ case RELOPT_TYPE_TERNARY:
+ {
+ bool b;
+ parsed = parse_bool(value, &b);
+ option->values.ternary_val = b ? TERNARY_TRUE : TERNARY_FALSE;
+ if (validate && !parsed)
+ ereport(ERROR,
+ (errcode(ERRCODE_INVALID_PARAMETER_VALUE),
+ errmsg("invalid value for ternary option \"%s\": %s",
+ option->gen->name, value)));
+ }
+ break;
case RELOPT_TYPE_INT:
{
relopt_int *optint = (relopt_int *) option->gen;
@@ -1780,17 +1864,6 @@ fillRelOptions(void *rdopts, Size basesize,
char *itempos = ((char *) rdopts) + elems[j].offset;
char *string_val;
- /*
- * If isset_offset is provided, store whether the reloption is
- * set there.
- */
- if (elems[j].isset_offset > 0)
- {
- char *setpos = ((char *) rdopts) + elems[j].isset_offset;
-
- *(bool *) setpos = options[i].isset;
- }
-
switch (options[i].gen->type)
{
case RELOPT_TYPE_BOOL:
@@ -1798,6 +1871,11 @@ fillRelOptions(void *rdopts, Size basesize,
options[i].values.bool_val :
((relopt_bool *) options[i].gen)->default_val;
break;
+ case RELOPT_TYPE_TERNARY:
+ *(ternary *) itempos = options[i].isset ?
+ options[i].values.ternary_val :
+ ((relopt_ternary *) options[i].gen)->default_val;
+ break;
case RELOPT_TYPE_INT:
*(int *) itempos = options[i].isset ?
options[i].values.int_val :
@@ -1912,8 +1990,8 @@ default_reloptions(Datum reloptions, bool validate, relopt_kind kind)
offsetof(StdRdOptions, parallel_workers)},
{"vacuum_index_cleanup", RELOPT_TYPE_ENUM,
offsetof(StdRdOptions, vacuum_index_cleanup)},
- {"vacuum_truncate", RELOPT_TYPE_BOOL,
- offsetof(StdRdOptions, vacuum_truncate), offsetof(StdRdOptions, vacuum_truncate_set)},
+ {"vacuum_truncate", RELOPT_TYPE_TERNARY,
+ offsetof(StdRdOptions, vacuum_truncate)},
{"vacuum_max_eager_freeze_failure_rate", RELOPT_TYPE_REAL,
offsetof(StdRdOptions, vacuum_max_eager_freeze_failure_rate)}
};
@@ -1993,7 +2071,6 @@ build_local_reloptions(local_relopts *relopts, Datum options, bool validate)
elems[i].optname = opt->option->name;
elems[i].opttype = opt->option->type;
elems[i].offset = opt->offset;
- elems[i].isset_offset = 0; /* not supported for local relopts yet */
i++;
}
diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c
index 733ef40ae7..977babff54 100644
--- a/src/backend/commands/vacuum.c
+++ b/src/backend/commands/vacuum.c
@@ -2221,9 +2221,9 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams params,
{
StdRdOptions *opts = (StdRdOptions *) rel->rd_options;
- if (opts && opts->vacuum_truncate_set)
+ if (opts && opts->vacuum_truncate != TERNARY_UNSET)
{
- if (opts->vacuum_truncate)
+ if (opts->vacuum_truncate == TERNARY_TRUE)
params.truncate = VACOPTVALUE_ENABLED;
else
params.truncate = VACOPTVALUE_DISABLED;
diff --git a/src/include/access/reloptions.h b/src/include/access/reloptions.h
index a604a4702c..a436697658 100644
--- a/src/include/access/reloptions.h
+++ b/src/include/access/reloptions.h
@@ -29,6 +29,7 @@
typedef enum relopt_type
{
RELOPT_TYPE_BOOL,
+ RELOPT_TYPE_TERNARY, /* on, off, unset */
RELOPT_TYPE_INT,
RELOPT_TYPE_REAL,
RELOPT_TYPE_ENUM,
@@ -80,6 +81,7 @@ typedef struct relopt_value
union
{
bool bool_val;
+ ternary ternary_val;
int int_val;
double real_val;
int enum_val;
@@ -94,6 +96,12 @@ typedef struct relopt_bool
bool default_val;
} relopt_bool;
+typedef struct relopt_rernary
+{
+ relopt_gen gen;
+ int default_val;
+} relopt_ternary;
+
typedef struct relopt_int
{
relopt_gen gen;
@@ -152,19 +160,6 @@ typedef struct
const char *optname; /* option's name */
relopt_type opttype; /* option's datatype */
int offset; /* offset of field in result struct */
-
- /*
- * isset_offset is an optional offset of a field in the result struct that
- * stores whether the option is explicitly set for the relation or if it
- * just picked up the default value. In most cases, this can be
- * accomplished by giving the reloption a special out-of-range default
- * value (e.g., some integer reloptions use -2), but this isn't always
- * possible. For example, a Boolean reloption cannot be given an
- * out-of-range default, so we need another way to discover the source of
- * its value. This offset is only used if given a value greater than
- * zero.
- */
- int isset_offset;
} relopt_parse_elt;
/* Local reloption definition */
@@ -195,6 +190,8 @@ typedef struct local_relopts
extern relopt_kind add_reloption_kind(void);
extern void add_bool_reloption(bits32 kinds, const char *name, const char *desc,
bool default_val, LOCKMODE lockmode);
+extern void add_ternary_reloption(bits32 kinds, const char *name,
+ const char *desc, int default_val, LOCKMODE lockmode);
extern void add_int_reloption(bits32 kinds, const char *name, const char *desc,
int default_val, int min_val, int max_val,
LOCKMODE lockmode);
@@ -214,6 +211,9 @@ extern void register_reloptions_validator(local_relopts *relopts,
extern void add_local_bool_reloption(local_relopts *relopts, const char *name,
const char *desc, bool default_val,
int offset);
+extern void add_local_ternary_reloption(local_relopts *relopts,
+ const char *name, const char *desc,
+ ternary default_val, int offset);
extern void add_local_int_reloption(local_relopts *relopts, const char *name,
const char *desc, int default_val,
int min_val, int max_val, int offset);
diff --git a/src/include/c.h b/src/include/c.h
index 39022f8a9d..9c59ccccf8 100644
--- a/src/include/c.h
+++ b/src/include/c.h
@@ -488,6 +488,22 @@ typedef void (*pg_funcptr_t) (void);
#include <stdbool.h>
+/*
+ * ternary
+ * Boolean value with an extrea "unset" option
+ *
+ * Ternary data type is used in relation options that can be "true", "false" or
+ * "unset". Since relation options are used deep inside the PostgreSQL code,
+ * this type is declared globally.
+*/
+
+typedef enum ternary
+{
+ TERNARY_FALSE = 0,
+ TERNARY_TRUE = 1,
+ TERNARY_UNSET = -1
+} ternary;
+
/* ----------------------------------------------------------------
* Section 3: standard system types
diff --git a/src/include/utils/rel.h b/src/include/utils/rel.h
index b552359915..7346d618f9 100644
--- a/src/include/utils/rel.h
+++ b/src/include/utils/rel.h
@@ -346,8 +346,7 @@ typedef struct StdRdOptions
bool user_catalog_table; /* use as an additional catalog relation */
int parallel_workers; /* max number of parallel workers */
StdRdOptIndexCleanup vacuum_index_cleanup; /* controls index vacuuming */
- bool vacuum_truncate; /* enables vacuum to truncate a relation */
- bool vacuum_truncate_set; /* whether vacuum_truncate is set */
+ ternary vacuum_truncate; /* enables vacuum to truncate a relation */
/*
* Fraction of pages in a relation that vacuum can eagerly scan and fail
--
2.39.2
--nextPart5129932.5fSG56mABF
Content-Disposition: attachment;
filename="v2-0003-Add-alias-to-be-used-as-unset-state.patch"
Content-Transfer-Encoding: 7Bit
Content-Type: text/x-patch; charset="unicode-2-0-utf-8";
name="v2-0003-Add-alias-to-be-used-as-unset-state.patch"
^ permalink raw reply [nested|flat] 868+ messages in thread
* [PATCH v2 2/4] Introduce ternary reloptions
@ 2025-09-04 14:41 Nikolay Shaplov <[email protected]>
0 siblings, 0 replies; 868+ messages in thread
From: Nikolay Shaplov @ 2025-09-04 14:41 UTC (permalink / raw)
Introduce ternary reloption as a replacement for current `vacuum_truncate`
implementation. Remove `vacuum_truncate_set` additional flag and using
`TERNARY_UNSET` value instead.
---
src/backend/access/common/reloptions.c | 129 ++++++++++++++++++++-----
src/backend/commands/vacuum.c | 4 +-
src/include/access/reloptions.h | 26 ++---
src/include/c.h | 16 +++
src/include/utils/rel.h | 3 +-
5 files changed, 135 insertions(+), 43 deletions(-)
diff --git a/src/backend/access/common/reloptions.c b/src/backend/access/common/reloptions.c
index 0af3fea68f..f543f11001 100644
--- a/src/backend/access/common/reloptions.c
+++ b/src/backend/access/common/reloptions.c
@@ -40,9 +40,9 @@
*
* To add an option:
*
- * (i) decide on a type (bool, integer, real, enum, string), name, default
- * value, upper and lower bounds (if applicable); for strings, consider a
- * validation routine.
+ * (i) decide on a type (bool, ternary, integer, real, enum, string), name,
+ * default value, upper and lower bounds (if applicable); for strings,
+ * consider a validation routine.
* (ii) add a record below (or use add_<type>_reloption).
* (iii) add it to the appropriate options struct (perhaps StdRdOptions)
* (iv) add it to the appropriate handling routine (perhaps
@@ -147,15 +147,6 @@ static relopt_bool boolRelOpts[] =
},
false
},
- {
- {
- "vacuum_truncate",
- "Enables vacuum to truncate empty pages at the end of this table",
- RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
- ShareUpdateExclusiveLock
- },
- true
- },
{
{
"deduplicate_items",
@@ -170,6 +161,21 @@ static relopt_bool boolRelOpts[] =
{{NULL}}
};
+static relopt_ternary ternaryRelOpts[] =
+{
+ {
+ {
+ "vacuum_truncate",
+ "Enables vacuum to truncate empty pages at the end of this table",
+ RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
+ ShareUpdateExclusiveLock
+ },
+ TERNARY_UNSET
+ },
+ /* list terminator */
+ {{NULL}}
+};
+
static relopt_int intRelOpts[] =
{
{
@@ -600,6 +606,13 @@ initialize_reloptions(void)
boolRelOpts[i].gen.lockmode));
j++;
}
+ for (i = 0; ternaryRelOpts[i].gen.name; i++)
+ {
+ Assert(DoLockModesConflict(ternaryRelOpts[i].gen.lockmode,
+ ternaryRelOpts[i].gen.lockmode));
+ j++;
+ }
+
for (i = 0; intRelOpts[i].gen.name; i++)
{
Assert(DoLockModesConflict(intRelOpts[i].gen.lockmode,
@@ -640,6 +653,14 @@ initialize_reloptions(void)
j++;
}
+ for (i = 0; ternaryRelOpts[i].gen.name; i++)
+ {
+ relOpts[j] = &ternaryRelOpts[i].gen;
+ relOpts[j]->type = RELOPT_TYPE_TERNARY;
+ relOpts[j]->namelen = strlen(relOpts[j]->name);
+ j++;
+ }
+
for (i = 0; intRelOpts[i].gen.name; i++)
{
relOpts[j] = &intRelOpts[i].gen;
@@ -800,6 +821,9 @@ allocate_reloption(bits32 kinds, int type, const char *name, const char *desc,
case RELOPT_TYPE_BOOL:
size = sizeof(relopt_bool);
break;
+ case RELOPT_TYPE_TERNARY:
+ size = sizeof(relopt_ternary);
+ break;
case RELOPT_TYPE_INT:
size = sizeof(relopt_int);
break;
@@ -883,6 +907,54 @@ add_local_bool_reloption(local_relopts *relopts, const char *name,
add_local_reloption(relopts, (relopt_gen *) newoption, offset);
}
+/*
+ * init_ternary_reloption
+ * Allocate and initialize a new ternary reloption
+ */
+static relopt_ternary *
+init_ternary_reloption(bits32 kinds, const char *name, const char *desc,
+ ternary default_val, LOCKMODE lockmode)
+{
+ relopt_ternary *newoption;
+
+ newoption = (relopt_ternary *) allocate_reloption(kinds,
+ RELOPT_TYPE_TERNARY, name, desc, lockmode);
+ newoption->default_val = default_val;
+
+ return newoption;
+}
+
+/*
+ * add_ternary_reloption
+ * Add a new ternary reloption
+ */
+void
+add_ternary_reloption(bits32 kinds, const char *name, const char *desc,
+ ternary default_val, LOCKMODE lockmode)
+{
+ relopt_ternary *newoption = init_ternary_reloption(kinds, name, desc,
+ default_val, lockmode);
+
+ add_reloption((relopt_gen *) newoption);
+}
+
+/*
+ * add_local_ternary_reloption
+ * Add a new ternary local reloption
+ *
+ * 'offset' is offset of ternary-typed field.
+ */
+void
+add_local_ternary_reloption(local_relopts *relopts, const char *name,
+ const char *desc, ternary default_val,
+ int offset)
+{
+ relopt_ternary *newoption = init_ternary_reloption(RELOPT_KIND_LOCAL,
+ name, desc,
+ default_val, 0);
+
+ add_local_reloption(relopts, (relopt_gen *) newoption, offset);
+}
/*
* init_real_reloption
@@ -1617,6 +1689,18 @@ parse_one_reloption(relopt_value *option, char *text_str, int text_len,
option->gen->name, value)));
}
break;
+ case RELOPT_TYPE_TERNARY:
+ {
+ bool b;
+ parsed = parse_bool(value, &b);
+ option->values.ternary_val = b ? TERNARY_TRUE : TERNARY_FALSE;
+ if (validate && !parsed)
+ ereport(ERROR,
+ (errcode(ERRCODE_INVALID_PARAMETER_VALUE),
+ errmsg("invalid value for ternary option \"%s\": %s",
+ option->gen->name, value)));
+ }
+ break;
case RELOPT_TYPE_INT:
{
relopt_int *optint = (relopt_int *) option->gen;
@@ -1780,17 +1864,6 @@ fillRelOptions(void *rdopts, Size basesize,
char *itempos = ((char *) rdopts) + elems[j].offset;
char *string_val;
- /*
- * If isset_offset is provided, store whether the reloption is
- * set there.
- */
- if (elems[j].isset_offset > 0)
- {
- char *setpos = ((char *) rdopts) + elems[j].isset_offset;
-
- *(bool *) setpos = options[i].isset;
- }
-
switch (options[i].gen->type)
{
case RELOPT_TYPE_BOOL:
@@ -1798,6 +1871,11 @@ fillRelOptions(void *rdopts, Size basesize,
options[i].values.bool_val :
((relopt_bool *) options[i].gen)->default_val;
break;
+ case RELOPT_TYPE_TERNARY:
+ *(ternary *) itempos = options[i].isset ?
+ options[i].values.ternary_val :
+ ((relopt_ternary *) options[i].gen)->default_val;
+ break;
case RELOPT_TYPE_INT:
*(int *) itempos = options[i].isset ?
options[i].values.int_val :
@@ -1912,8 +1990,8 @@ default_reloptions(Datum reloptions, bool validate, relopt_kind kind)
offsetof(StdRdOptions, parallel_workers)},
{"vacuum_index_cleanup", RELOPT_TYPE_ENUM,
offsetof(StdRdOptions, vacuum_index_cleanup)},
- {"vacuum_truncate", RELOPT_TYPE_BOOL,
- offsetof(StdRdOptions, vacuum_truncate), offsetof(StdRdOptions, vacuum_truncate_set)},
+ {"vacuum_truncate", RELOPT_TYPE_TERNARY,
+ offsetof(StdRdOptions, vacuum_truncate)},
{"vacuum_max_eager_freeze_failure_rate", RELOPT_TYPE_REAL,
offsetof(StdRdOptions, vacuum_max_eager_freeze_failure_rate)}
};
@@ -1993,7 +2071,6 @@ build_local_reloptions(local_relopts *relopts, Datum options, bool validate)
elems[i].optname = opt->option->name;
elems[i].opttype = opt->option->type;
elems[i].offset = opt->offset;
- elems[i].isset_offset = 0; /* not supported for local relopts yet */
i++;
}
diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c
index 733ef40ae7..977babff54 100644
--- a/src/backend/commands/vacuum.c
+++ b/src/backend/commands/vacuum.c
@@ -2221,9 +2221,9 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams params,
{
StdRdOptions *opts = (StdRdOptions *) rel->rd_options;
- if (opts && opts->vacuum_truncate_set)
+ if (opts && opts->vacuum_truncate != TERNARY_UNSET)
{
- if (opts->vacuum_truncate)
+ if (opts->vacuum_truncate == TERNARY_TRUE)
params.truncate = VACOPTVALUE_ENABLED;
else
params.truncate = VACOPTVALUE_DISABLED;
diff --git a/src/include/access/reloptions.h b/src/include/access/reloptions.h
index a604a4702c..a436697658 100644
--- a/src/include/access/reloptions.h
+++ b/src/include/access/reloptions.h
@@ -29,6 +29,7 @@
typedef enum relopt_type
{
RELOPT_TYPE_BOOL,
+ RELOPT_TYPE_TERNARY, /* on, off, unset */
RELOPT_TYPE_INT,
RELOPT_TYPE_REAL,
RELOPT_TYPE_ENUM,
@@ -80,6 +81,7 @@ typedef struct relopt_value
union
{
bool bool_val;
+ ternary ternary_val;
int int_val;
double real_val;
int enum_val;
@@ -94,6 +96,12 @@ typedef struct relopt_bool
bool default_val;
} relopt_bool;
+typedef struct relopt_rernary
+{
+ relopt_gen gen;
+ int default_val;
+} relopt_ternary;
+
typedef struct relopt_int
{
relopt_gen gen;
@@ -152,19 +160,6 @@ typedef struct
const char *optname; /* option's name */
relopt_type opttype; /* option's datatype */
int offset; /* offset of field in result struct */
-
- /*
- * isset_offset is an optional offset of a field in the result struct that
- * stores whether the option is explicitly set for the relation or if it
- * just picked up the default value. In most cases, this can be
- * accomplished by giving the reloption a special out-of-range default
- * value (e.g., some integer reloptions use -2), but this isn't always
- * possible. For example, a Boolean reloption cannot be given an
- * out-of-range default, so we need another way to discover the source of
- * its value. This offset is only used if given a value greater than
- * zero.
- */
- int isset_offset;
} relopt_parse_elt;
/* Local reloption definition */
@@ -195,6 +190,8 @@ typedef struct local_relopts
extern relopt_kind add_reloption_kind(void);
extern void add_bool_reloption(bits32 kinds, const char *name, const char *desc,
bool default_val, LOCKMODE lockmode);
+extern void add_ternary_reloption(bits32 kinds, const char *name,
+ const char *desc, int default_val, LOCKMODE lockmode);
extern void add_int_reloption(bits32 kinds, const char *name, const char *desc,
int default_val, int min_val, int max_val,
LOCKMODE lockmode);
@@ -214,6 +211,9 @@ extern void register_reloptions_validator(local_relopts *relopts,
extern void add_local_bool_reloption(local_relopts *relopts, const char *name,
const char *desc, bool default_val,
int offset);
+extern void add_local_ternary_reloption(local_relopts *relopts,
+ const char *name, const char *desc,
+ ternary default_val, int offset);
extern void add_local_int_reloption(local_relopts *relopts, const char *name,
const char *desc, int default_val,
int min_val, int max_val, int offset);
diff --git a/src/include/c.h b/src/include/c.h
index 39022f8a9d..9c59ccccf8 100644
--- a/src/include/c.h
+++ b/src/include/c.h
@@ -488,6 +488,22 @@ typedef void (*pg_funcptr_t) (void);
#include <stdbool.h>
+/*
+ * ternary
+ * Boolean value with an extrea "unset" option
+ *
+ * Ternary data type is used in relation options that can be "true", "false" or
+ * "unset". Since relation options are used deep inside the PostgreSQL code,
+ * this type is declared globally.
+*/
+
+typedef enum ternary
+{
+ TERNARY_FALSE = 0,
+ TERNARY_TRUE = 1,
+ TERNARY_UNSET = -1
+} ternary;
+
/* ----------------------------------------------------------------
* Section 3: standard system types
diff --git a/src/include/utils/rel.h b/src/include/utils/rel.h
index b552359915..7346d618f9 100644
--- a/src/include/utils/rel.h
+++ b/src/include/utils/rel.h
@@ -346,8 +346,7 @@ typedef struct StdRdOptions
bool user_catalog_table; /* use as an additional catalog relation */
int parallel_workers; /* max number of parallel workers */
StdRdOptIndexCleanup vacuum_index_cleanup; /* controls index vacuuming */
- bool vacuum_truncate; /* enables vacuum to truncate a relation */
- bool vacuum_truncate_set; /* whether vacuum_truncate is set */
+ ternary vacuum_truncate; /* enables vacuum to truncate a relation */
/*
* Fraction of pages in a relation that vacuum can eagerly scan and fail
--
2.39.2
--nextPart5129932.5fSG56mABF
Content-Disposition: attachment;
filename="v2-0003-Add-alias-to-be-used-as-unset-state.patch"
Content-Transfer-Encoding: 7Bit
Content-Type: text/x-patch; charset="unicode-2-0-utf-8";
name="v2-0003-Add-alias-to-be-used-as-unset-state.patch"
^ permalink raw reply [nested|flat] 868+ messages in thread
* [PATCH v2 2/4] Introduce ternary reloptions
@ 2025-09-04 14:41 Nikolay Shaplov <[email protected]>
0 siblings, 0 replies; 868+ messages in thread
From: Nikolay Shaplov @ 2025-09-04 14:41 UTC (permalink / raw)
Introduce ternary reloption as a replacement for current `vacuum_truncate`
implementation. Remove `vacuum_truncate_set` additional flag and using
`TERNARY_UNSET` value instead.
---
src/backend/access/common/reloptions.c | 129 ++++++++++++++++++++-----
src/backend/commands/vacuum.c | 4 +-
src/include/access/reloptions.h | 26 ++---
src/include/c.h | 16 +++
src/include/utils/rel.h | 3 +-
5 files changed, 135 insertions(+), 43 deletions(-)
diff --git a/src/backend/access/common/reloptions.c b/src/backend/access/common/reloptions.c
index 0af3fea68f..f543f11001 100644
--- a/src/backend/access/common/reloptions.c
+++ b/src/backend/access/common/reloptions.c
@@ -40,9 +40,9 @@
*
* To add an option:
*
- * (i) decide on a type (bool, integer, real, enum, string), name, default
- * value, upper and lower bounds (if applicable); for strings, consider a
- * validation routine.
+ * (i) decide on a type (bool, ternary, integer, real, enum, string), name,
+ * default value, upper and lower bounds (if applicable); for strings,
+ * consider a validation routine.
* (ii) add a record below (or use add_<type>_reloption).
* (iii) add it to the appropriate options struct (perhaps StdRdOptions)
* (iv) add it to the appropriate handling routine (perhaps
@@ -147,15 +147,6 @@ static relopt_bool boolRelOpts[] =
},
false
},
- {
- {
- "vacuum_truncate",
- "Enables vacuum to truncate empty pages at the end of this table",
- RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
- ShareUpdateExclusiveLock
- },
- true
- },
{
{
"deduplicate_items",
@@ -170,6 +161,21 @@ static relopt_bool boolRelOpts[] =
{{NULL}}
};
+static relopt_ternary ternaryRelOpts[] =
+{
+ {
+ {
+ "vacuum_truncate",
+ "Enables vacuum to truncate empty pages at the end of this table",
+ RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
+ ShareUpdateExclusiveLock
+ },
+ TERNARY_UNSET
+ },
+ /* list terminator */
+ {{NULL}}
+};
+
static relopt_int intRelOpts[] =
{
{
@@ -600,6 +606,13 @@ initialize_reloptions(void)
boolRelOpts[i].gen.lockmode));
j++;
}
+ for (i = 0; ternaryRelOpts[i].gen.name; i++)
+ {
+ Assert(DoLockModesConflict(ternaryRelOpts[i].gen.lockmode,
+ ternaryRelOpts[i].gen.lockmode));
+ j++;
+ }
+
for (i = 0; intRelOpts[i].gen.name; i++)
{
Assert(DoLockModesConflict(intRelOpts[i].gen.lockmode,
@@ -640,6 +653,14 @@ initialize_reloptions(void)
j++;
}
+ for (i = 0; ternaryRelOpts[i].gen.name; i++)
+ {
+ relOpts[j] = &ternaryRelOpts[i].gen;
+ relOpts[j]->type = RELOPT_TYPE_TERNARY;
+ relOpts[j]->namelen = strlen(relOpts[j]->name);
+ j++;
+ }
+
for (i = 0; intRelOpts[i].gen.name; i++)
{
relOpts[j] = &intRelOpts[i].gen;
@@ -800,6 +821,9 @@ allocate_reloption(bits32 kinds, int type, const char *name, const char *desc,
case RELOPT_TYPE_BOOL:
size = sizeof(relopt_bool);
break;
+ case RELOPT_TYPE_TERNARY:
+ size = sizeof(relopt_ternary);
+ break;
case RELOPT_TYPE_INT:
size = sizeof(relopt_int);
break;
@@ -883,6 +907,54 @@ add_local_bool_reloption(local_relopts *relopts, const char *name,
add_local_reloption(relopts, (relopt_gen *) newoption, offset);
}
+/*
+ * init_ternary_reloption
+ * Allocate and initialize a new ternary reloption
+ */
+static relopt_ternary *
+init_ternary_reloption(bits32 kinds, const char *name, const char *desc,
+ ternary default_val, LOCKMODE lockmode)
+{
+ relopt_ternary *newoption;
+
+ newoption = (relopt_ternary *) allocate_reloption(kinds,
+ RELOPT_TYPE_TERNARY, name, desc, lockmode);
+ newoption->default_val = default_val;
+
+ return newoption;
+}
+
+/*
+ * add_ternary_reloption
+ * Add a new ternary reloption
+ */
+void
+add_ternary_reloption(bits32 kinds, const char *name, const char *desc,
+ ternary default_val, LOCKMODE lockmode)
+{
+ relopt_ternary *newoption = init_ternary_reloption(kinds, name, desc,
+ default_val, lockmode);
+
+ add_reloption((relopt_gen *) newoption);
+}
+
+/*
+ * add_local_ternary_reloption
+ * Add a new ternary local reloption
+ *
+ * 'offset' is offset of ternary-typed field.
+ */
+void
+add_local_ternary_reloption(local_relopts *relopts, const char *name,
+ const char *desc, ternary default_val,
+ int offset)
+{
+ relopt_ternary *newoption = init_ternary_reloption(RELOPT_KIND_LOCAL,
+ name, desc,
+ default_val, 0);
+
+ add_local_reloption(relopts, (relopt_gen *) newoption, offset);
+}
/*
* init_real_reloption
@@ -1617,6 +1689,18 @@ parse_one_reloption(relopt_value *option, char *text_str, int text_len,
option->gen->name, value)));
}
break;
+ case RELOPT_TYPE_TERNARY:
+ {
+ bool b;
+ parsed = parse_bool(value, &b);
+ option->values.ternary_val = b ? TERNARY_TRUE : TERNARY_FALSE;
+ if (validate && !parsed)
+ ereport(ERROR,
+ (errcode(ERRCODE_INVALID_PARAMETER_VALUE),
+ errmsg("invalid value for ternary option \"%s\": %s",
+ option->gen->name, value)));
+ }
+ break;
case RELOPT_TYPE_INT:
{
relopt_int *optint = (relopt_int *) option->gen;
@@ -1780,17 +1864,6 @@ fillRelOptions(void *rdopts, Size basesize,
char *itempos = ((char *) rdopts) + elems[j].offset;
char *string_val;
- /*
- * If isset_offset is provided, store whether the reloption is
- * set there.
- */
- if (elems[j].isset_offset > 0)
- {
- char *setpos = ((char *) rdopts) + elems[j].isset_offset;
-
- *(bool *) setpos = options[i].isset;
- }
-
switch (options[i].gen->type)
{
case RELOPT_TYPE_BOOL:
@@ -1798,6 +1871,11 @@ fillRelOptions(void *rdopts, Size basesize,
options[i].values.bool_val :
((relopt_bool *) options[i].gen)->default_val;
break;
+ case RELOPT_TYPE_TERNARY:
+ *(ternary *) itempos = options[i].isset ?
+ options[i].values.ternary_val :
+ ((relopt_ternary *) options[i].gen)->default_val;
+ break;
case RELOPT_TYPE_INT:
*(int *) itempos = options[i].isset ?
options[i].values.int_val :
@@ -1912,8 +1990,8 @@ default_reloptions(Datum reloptions, bool validate, relopt_kind kind)
offsetof(StdRdOptions, parallel_workers)},
{"vacuum_index_cleanup", RELOPT_TYPE_ENUM,
offsetof(StdRdOptions, vacuum_index_cleanup)},
- {"vacuum_truncate", RELOPT_TYPE_BOOL,
- offsetof(StdRdOptions, vacuum_truncate), offsetof(StdRdOptions, vacuum_truncate_set)},
+ {"vacuum_truncate", RELOPT_TYPE_TERNARY,
+ offsetof(StdRdOptions, vacuum_truncate)},
{"vacuum_max_eager_freeze_failure_rate", RELOPT_TYPE_REAL,
offsetof(StdRdOptions, vacuum_max_eager_freeze_failure_rate)}
};
@@ -1993,7 +2071,6 @@ build_local_reloptions(local_relopts *relopts, Datum options, bool validate)
elems[i].optname = opt->option->name;
elems[i].opttype = opt->option->type;
elems[i].offset = opt->offset;
- elems[i].isset_offset = 0; /* not supported for local relopts yet */
i++;
}
diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c
index 733ef40ae7..977babff54 100644
--- a/src/backend/commands/vacuum.c
+++ b/src/backend/commands/vacuum.c
@@ -2221,9 +2221,9 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams params,
{
StdRdOptions *opts = (StdRdOptions *) rel->rd_options;
- if (opts && opts->vacuum_truncate_set)
+ if (opts && opts->vacuum_truncate != TERNARY_UNSET)
{
- if (opts->vacuum_truncate)
+ if (opts->vacuum_truncate == TERNARY_TRUE)
params.truncate = VACOPTVALUE_ENABLED;
else
params.truncate = VACOPTVALUE_DISABLED;
diff --git a/src/include/access/reloptions.h b/src/include/access/reloptions.h
index a604a4702c..a436697658 100644
--- a/src/include/access/reloptions.h
+++ b/src/include/access/reloptions.h
@@ -29,6 +29,7 @@
typedef enum relopt_type
{
RELOPT_TYPE_BOOL,
+ RELOPT_TYPE_TERNARY, /* on, off, unset */
RELOPT_TYPE_INT,
RELOPT_TYPE_REAL,
RELOPT_TYPE_ENUM,
@@ -80,6 +81,7 @@ typedef struct relopt_value
union
{
bool bool_val;
+ ternary ternary_val;
int int_val;
double real_val;
int enum_val;
@@ -94,6 +96,12 @@ typedef struct relopt_bool
bool default_val;
} relopt_bool;
+typedef struct relopt_rernary
+{
+ relopt_gen gen;
+ int default_val;
+} relopt_ternary;
+
typedef struct relopt_int
{
relopt_gen gen;
@@ -152,19 +160,6 @@ typedef struct
const char *optname; /* option's name */
relopt_type opttype; /* option's datatype */
int offset; /* offset of field in result struct */
-
- /*
- * isset_offset is an optional offset of a field in the result struct that
- * stores whether the option is explicitly set for the relation or if it
- * just picked up the default value. In most cases, this can be
- * accomplished by giving the reloption a special out-of-range default
- * value (e.g., some integer reloptions use -2), but this isn't always
- * possible. For example, a Boolean reloption cannot be given an
- * out-of-range default, so we need another way to discover the source of
- * its value. This offset is only used if given a value greater than
- * zero.
- */
- int isset_offset;
} relopt_parse_elt;
/* Local reloption definition */
@@ -195,6 +190,8 @@ typedef struct local_relopts
extern relopt_kind add_reloption_kind(void);
extern void add_bool_reloption(bits32 kinds, const char *name, const char *desc,
bool default_val, LOCKMODE lockmode);
+extern void add_ternary_reloption(bits32 kinds, const char *name,
+ const char *desc, int default_val, LOCKMODE lockmode);
extern void add_int_reloption(bits32 kinds, const char *name, const char *desc,
int default_val, int min_val, int max_val,
LOCKMODE lockmode);
@@ -214,6 +211,9 @@ extern void register_reloptions_validator(local_relopts *relopts,
extern void add_local_bool_reloption(local_relopts *relopts, const char *name,
const char *desc, bool default_val,
int offset);
+extern void add_local_ternary_reloption(local_relopts *relopts,
+ const char *name, const char *desc,
+ ternary default_val, int offset);
extern void add_local_int_reloption(local_relopts *relopts, const char *name,
const char *desc, int default_val,
int min_val, int max_val, int offset);
diff --git a/src/include/c.h b/src/include/c.h
index 39022f8a9d..9c59ccccf8 100644
--- a/src/include/c.h
+++ b/src/include/c.h
@@ -488,6 +488,22 @@ typedef void (*pg_funcptr_t) (void);
#include <stdbool.h>
+/*
+ * ternary
+ * Boolean value with an extrea "unset" option
+ *
+ * Ternary data type is used in relation options that can be "true", "false" or
+ * "unset". Since relation options are used deep inside the PostgreSQL code,
+ * this type is declared globally.
+*/
+
+typedef enum ternary
+{
+ TERNARY_FALSE = 0,
+ TERNARY_TRUE = 1,
+ TERNARY_UNSET = -1
+} ternary;
+
/* ----------------------------------------------------------------
* Section 3: standard system types
diff --git a/src/include/utils/rel.h b/src/include/utils/rel.h
index b552359915..7346d618f9 100644
--- a/src/include/utils/rel.h
+++ b/src/include/utils/rel.h
@@ -346,8 +346,7 @@ typedef struct StdRdOptions
bool user_catalog_table; /* use as an additional catalog relation */
int parallel_workers; /* max number of parallel workers */
StdRdOptIndexCleanup vacuum_index_cleanup; /* controls index vacuuming */
- bool vacuum_truncate; /* enables vacuum to truncate a relation */
- bool vacuum_truncate_set; /* whether vacuum_truncate is set */
+ ternary vacuum_truncate; /* enables vacuum to truncate a relation */
/*
* Fraction of pages in a relation that vacuum can eagerly scan and fail
--
2.39.2
--nextPart5129932.5fSG56mABF
Content-Disposition: attachment;
filename="v2-0003-Add-alias-to-be-used-as-unset-state.patch"
Content-Transfer-Encoding: 7Bit
Content-Type: text/x-patch; charset="unicode-2-0-utf-8";
name="v2-0003-Add-alias-to-be-used-as-unset-state.patch"
^ permalink raw reply [nested|flat] 868+ messages in thread
* [PATCH v2 2/4] Introduce ternary reloptions
@ 2025-09-04 14:41 Nikolay Shaplov <[email protected]>
0 siblings, 0 replies; 868+ messages in thread
From: Nikolay Shaplov @ 2025-09-04 14:41 UTC (permalink / raw)
Introduce ternary reloption as a replacement for current `vacuum_truncate`
implementation. Remove `vacuum_truncate_set` additional flag and using
`TERNARY_UNSET` value instead.
---
src/backend/access/common/reloptions.c | 129 ++++++++++++++++++++-----
src/backend/commands/vacuum.c | 4 +-
src/include/access/reloptions.h | 26 ++---
src/include/c.h | 16 +++
src/include/utils/rel.h | 3 +-
5 files changed, 135 insertions(+), 43 deletions(-)
diff --git a/src/backend/access/common/reloptions.c b/src/backend/access/common/reloptions.c
index 0af3fea68f..f543f11001 100644
--- a/src/backend/access/common/reloptions.c
+++ b/src/backend/access/common/reloptions.c
@@ -40,9 +40,9 @@
*
* To add an option:
*
- * (i) decide on a type (bool, integer, real, enum, string), name, default
- * value, upper and lower bounds (if applicable); for strings, consider a
- * validation routine.
+ * (i) decide on a type (bool, ternary, integer, real, enum, string), name,
+ * default value, upper and lower bounds (if applicable); for strings,
+ * consider a validation routine.
* (ii) add a record below (or use add_<type>_reloption).
* (iii) add it to the appropriate options struct (perhaps StdRdOptions)
* (iv) add it to the appropriate handling routine (perhaps
@@ -147,15 +147,6 @@ static relopt_bool boolRelOpts[] =
},
false
},
- {
- {
- "vacuum_truncate",
- "Enables vacuum to truncate empty pages at the end of this table",
- RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
- ShareUpdateExclusiveLock
- },
- true
- },
{
{
"deduplicate_items",
@@ -170,6 +161,21 @@ static relopt_bool boolRelOpts[] =
{{NULL}}
};
+static relopt_ternary ternaryRelOpts[] =
+{
+ {
+ {
+ "vacuum_truncate",
+ "Enables vacuum to truncate empty pages at the end of this table",
+ RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
+ ShareUpdateExclusiveLock
+ },
+ TERNARY_UNSET
+ },
+ /* list terminator */
+ {{NULL}}
+};
+
static relopt_int intRelOpts[] =
{
{
@@ -600,6 +606,13 @@ initialize_reloptions(void)
boolRelOpts[i].gen.lockmode));
j++;
}
+ for (i = 0; ternaryRelOpts[i].gen.name; i++)
+ {
+ Assert(DoLockModesConflict(ternaryRelOpts[i].gen.lockmode,
+ ternaryRelOpts[i].gen.lockmode));
+ j++;
+ }
+
for (i = 0; intRelOpts[i].gen.name; i++)
{
Assert(DoLockModesConflict(intRelOpts[i].gen.lockmode,
@@ -640,6 +653,14 @@ initialize_reloptions(void)
j++;
}
+ for (i = 0; ternaryRelOpts[i].gen.name; i++)
+ {
+ relOpts[j] = &ternaryRelOpts[i].gen;
+ relOpts[j]->type = RELOPT_TYPE_TERNARY;
+ relOpts[j]->namelen = strlen(relOpts[j]->name);
+ j++;
+ }
+
for (i = 0; intRelOpts[i].gen.name; i++)
{
relOpts[j] = &intRelOpts[i].gen;
@@ -800,6 +821,9 @@ allocate_reloption(bits32 kinds, int type, const char *name, const char *desc,
case RELOPT_TYPE_BOOL:
size = sizeof(relopt_bool);
break;
+ case RELOPT_TYPE_TERNARY:
+ size = sizeof(relopt_ternary);
+ break;
case RELOPT_TYPE_INT:
size = sizeof(relopt_int);
break;
@@ -883,6 +907,54 @@ add_local_bool_reloption(local_relopts *relopts, const char *name,
add_local_reloption(relopts, (relopt_gen *) newoption, offset);
}
+/*
+ * init_ternary_reloption
+ * Allocate and initialize a new ternary reloption
+ */
+static relopt_ternary *
+init_ternary_reloption(bits32 kinds, const char *name, const char *desc,
+ ternary default_val, LOCKMODE lockmode)
+{
+ relopt_ternary *newoption;
+
+ newoption = (relopt_ternary *) allocate_reloption(kinds,
+ RELOPT_TYPE_TERNARY, name, desc, lockmode);
+ newoption->default_val = default_val;
+
+ return newoption;
+}
+
+/*
+ * add_ternary_reloption
+ * Add a new ternary reloption
+ */
+void
+add_ternary_reloption(bits32 kinds, const char *name, const char *desc,
+ ternary default_val, LOCKMODE lockmode)
+{
+ relopt_ternary *newoption = init_ternary_reloption(kinds, name, desc,
+ default_val, lockmode);
+
+ add_reloption((relopt_gen *) newoption);
+}
+
+/*
+ * add_local_ternary_reloption
+ * Add a new ternary local reloption
+ *
+ * 'offset' is offset of ternary-typed field.
+ */
+void
+add_local_ternary_reloption(local_relopts *relopts, const char *name,
+ const char *desc, ternary default_val,
+ int offset)
+{
+ relopt_ternary *newoption = init_ternary_reloption(RELOPT_KIND_LOCAL,
+ name, desc,
+ default_val, 0);
+
+ add_local_reloption(relopts, (relopt_gen *) newoption, offset);
+}
/*
* init_real_reloption
@@ -1617,6 +1689,18 @@ parse_one_reloption(relopt_value *option, char *text_str, int text_len,
option->gen->name, value)));
}
break;
+ case RELOPT_TYPE_TERNARY:
+ {
+ bool b;
+ parsed = parse_bool(value, &b);
+ option->values.ternary_val = b ? TERNARY_TRUE : TERNARY_FALSE;
+ if (validate && !parsed)
+ ereport(ERROR,
+ (errcode(ERRCODE_INVALID_PARAMETER_VALUE),
+ errmsg("invalid value for ternary option \"%s\": %s",
+ option->gen->name, value)));
+ }
+ break;
case RELOPT_TYPE_INT:
{
relopt_int *optint = (relopt_int *) option->gen;
@@ -1780,17 +1864,6 @@ fillRelOptions(void *rdopts, Size basesize,
char *itempos = ((char *) rdopts) + elems[j].offset;
char *string_val;
- /*
- * If isset_offset is provided, store whether the reloption is
- * set there.
- */
- if (elems[j].isset_offset > 0)
- {
- char *setpos = ((char *) rdopts) + elems[j].isset_offset;
-
- *(bool *) setpos = options[i].isset;
- }
-
switch (options[i].gen->type)
{
case RELOPT_TYPE_BOOL:
@@ -1798,6 +1871,11 @@ fillRelOptions(void *rdopts, Size basesize,
options[i].values.bool_val :
((relopt_bool *) options[i].gen)->default_val;
break;
+ case RELOPT_TYPE_TERNARY:
+ *(ternary *) itempos = options[i].isset ?
+ options[i].values.ternary_val :
+ ((relopt_ternary *) options[i].gen)->default_val;
+ break;
case RELOPT_TYPE_INT:
*(int *) itempos = options[i].isset ?
options[i].values.int_val :
@@ -1912,8 +1990,8 @@ default_reloptions(Datum reloptions, bool validate, relopt_kind kind)
offsetof(StdRdOptions, parallel_workers)},
{"vacuum_index_cleanup", RELOPT_TYPE_ENUM,
offsetof(StdRdOptions, vacuum_index_cleanup)},
- {"vacuum_truncate", RELOPT_TYPE_BOOL,
- offsetof(StdRdOptions, vacuum_truncate), offsetof(StdRdOptions, vacuum_truncate_set)},
+ {"vacuum_truncate", RELOPT_TYPE_TERNARY,
+ offsetof(StdRdOptions, vacuum_truncate)},
{"vacuum_max_eager_freeze_failure_rate", RELOPT_TYPE_REAL,
offsetof(StdRdOptions, vacuum_max_eager_freeze_failure_rate)}
};
@@ -1993,7 +2071,6 @@ build_local_reloptions(local_relopts *relopts, Datum options, bool validate)
elems[i].optname = opt->option->name;
elems[i].opttype = opt->option->type;
elems[i].offset = opt->offset;
- elems[i].isset_offset = 0; /* not supported for local relopts yet */
i++;
}
diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c
index 733ef40ae7..977babff54 100644
--- a/src/backend/commands/vacuum.c
+++ b/src/backend/commands/vacuum.c
@@ -2221,9 +2221,9 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams params,
{
StdRdOptions *opts = (StdRdOptions *) rel->rd_options;
- if (opts && opts->vacuum_truncate_set)
+ if (opts && opts->vacuum_truncate != TERNARY_UNSET)
{
- if (opts->vacuum_truncate)
+ if (opts->vacuum_truncate == TERNARY_TRUE)
params.truncate = VACOPTVALUE_ENABLED;
else
params.truncate = VACOPTVALUE_DISABLED;
diff --git a/src/include/access/reloptions.h b/src/include/access/reloptions.h
index a604a4702c..a436697658 100644
--- a/src/include/access/reloptions.h
+++ b/src/include/access/reloptions.h
@@ -29,6 +29,7 @@
typedef enum relopt_type
{
RELOPT_TYPE_BOOL,
+ RELOPT_TYPE_TERNARY, /* on, off, unset */
RELOPT_TYPE_INT,
RELOPT_TYPE_REAL,
RELOPT_TYPE_ENUM,
@@ -80,6 +81,7 @@ typedef struct relopt_value
union
{
bool bool_val;
+ ternary ternary_val;
int int_val;
double real_val;
int enum_val;
@@ -94,6 +96,12 @@ typedef struct relopt_bool
bool default_val;
} relopt_bool;
+typedef struct relopt_rernary
+{
+ relopt_gen gen;
+ int default_val;
+} relopt_ternary;
+
typedef struct relopt_int
{
relopt_gen gen;
@@ -152,19 +160,6 @@ typedef struct
const char *optname; /* option's name */
relopt_type opttype; /* option's datatype */
int offset; /* offset of field in result struct */
-
- /*
- * isset_offset is an optional offset of a field in the result struct that
- * stores whether the option is explicitly set for the relation or if it
- * just picked up the default value. In most cases, this can be
- * accomplished by giving the reloption a special out-of-range default
- * value (e.g., some integer reloptions use -2), but this isn't always
- * possible. For example, a Boolean reloption cannot be given an
- * out-of-range default, so we need another way to discover the source of
- * its value. This offset is only used if given a value greater than
- * zero.
- */
- int isset_offset;
} relopt_parse_elt;
/* Local reloption definition */
@@ -195,6 +190,8 @@ typedef struct local_relopts
extern relopt_kind add_reloption_kind(void);
extern void add_bool_reloption(bits32 kinds, const char *name, const char *desc,
bool default_val, LOCKMODE lockmode);
+extern void add_ternary_reloption(bits32 kinds, const char *name,
+ const char *desc, int default_val, LOCKMODE lockmode);
extern void add_int_reloption(bits32 kinds, const char *name, const char *desc,
int default_val, int min_val, int max_val,
LOCKMODE lockmode);
@@ -214,6 +211,9 @@ extern void register_reloptions_validator(local_relopts *relopts,
extern void add_local_bool_reloption(local_relopts *relopts, const char *name,
const char *desc, bool default_val,
int offset);
+extern void add_local_ternary_reloption(local_relopts *relopts,
+ const char *name, const char *desc,
+ ternary default_val, int offset);
extern void add_local_int_reloption(local_relopts *relopts, const char *name,
const char *desc, int default_val,
int min_val, int max_val, int offset);
diff --git a/src/include/c.h b/src/include/c.h
index 39022f8a9d..9c59ccccf8 100644
--- a/src/include/c.h
+++ b/src/include/c.h
@@ -488,6 +488,22 @@ typedef void (*pg_funcptr_t) (void);
#include <stdbool.h>
+/*
+ * ternary
+ * Boolean value with an extrea "unset" option
+ *
+ * Ternary data type is used in relation options that can be "true", "false" or
+ * "unset". Since relation options are used deep inside the PostgreSQL code,
+ * this type is declared globally.
+*/
+
+typedef enum ternary
+{
+ TERNARY_FALSE = 0,
+ TERNARY_TRUE = 1,
+ TERNARY_UNSET = -1
+} ternary;
+
/* ----------------------------------------------------------------
* Section 3: standard system types
diff --git a/src/include/utils/rel.h b/src/include/utils/rel.h
index b552359915..7346d618f9 100644
--- a/src/include/utils/rel.h
+++ b/src/include/utils/rel.h
@@ -346,8 +346,7 @@ typedef struct StdRdOptions
bool user_catalog_table; /* use as an additional catalog relation */
int parallel_workers; /* max number of parallel workers */
StdRdOptIndexCleanup vacuum_index_cleanup; /* controls index vacuuming */
- bool vacuum_truncate; /* enables vacuum to truncate a relation */
- bool vacuum_truncate_set; /* whether vacuum_truncate is set */
+ ternary vacuum_truncate; /* enables vacuum to truncate a relation */
/*
* Fraction of pages in a relation that vacuum can eagerly scan and fail
--
2.39.2
--nextPart5129932.5fSG56mABF
Content-Disposition: attachment;
filename="v2-0003-Add-alias-to-be-used-as-unset-state.patch"
Content-Transfer-Encoding: 7Bit
Content-Type: text/x-patch; charset="unicode-2-0-utf-8";
name="v2-0003-Add-alias-to-be-used-as-unset-state.patch"
^ permalink raw reply [nested|flat] 868+ messages in thread
* [PATCH v2a 2/4] Introduce ternary reloptions
@ 2025-09-04 14:41 Nikolay Shaplov <[email protected]>
0 siblings, 0 replies; 868+ messages in thread
From: Nikolay Shaplov @ 2025-09-04 14:41 UTC (permalink / raw)
Introduce ternary reloption as a replacement for current `vacuum_truncate`
implementation. Remove `vacuum_truncate_set` additional flag and using
`TERNARY_UNSET` value instead.
---
src/backend/access/common/reloptions.c | 129 ++++++++++++++++++++-----
src/backend/commands/vacuum.c | 4 +-
src/include/access/reloptions.h | 26 ++---
src/include/c.h | 16 +++
src/include/utils/rel.h | 3 +-
5 files changed, 135 insertions(+), 43 deletions(-)
diff --git a/src/backend/access/common/reloptions.c b/src/backend/access/common/reloptions.c
index 0af3fea68f..f543f11001 100644
--- a/src/backend/access/common/reloptions.c
+++ b/src/backend/access/common/reloptions.c
@@ -40,9 +40,9 @@
*
* To add an option:
*
- * (i) decide on a type (bool, integer, real, enum, string), name, default
- * value, upper and lower bounds (if applicable); for strings, consider a
- * validation routine.
+ * (i) decide on a type (bool, ternary, integer, real, enum, string), name,
+ * default value, upper and lower bounds (if applicable); for strings,
+ * consider a validation routine.
* (ii) add a record below (or use add_<type>_reloption).
* (iii) add it to the appropriate options struct (perhaps StdRdOptions)
* (iv) add it to the appropriate handling routine (perhaps
@@ -147,15 +147,6 @@ static relopt_bool boolRelOpts[] =
},
false
},
- {
- {
- "vacuum_truncate",
- "Enables vacuum to truncate empty pages at the end of this table",
- RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
- ShareUpdateExclusiveLock
- },
- true
- },
{
{
"deduplicate_items",
@@ -170,6 +161,21 @@ static relopt_bool boolRelOpts[] =
{{NULL}}
};
+static relopt_ternary ternaryRelOpts[] =
+{
+ {
+ {
+ "vacuum_truncate",
+ "Enables vacuum to truncate empty pages at the end of this table",
+ RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
+ ShareUpdateExclusiveLock
+ },
+ TERNARY_UNSET
+ },
+ /* list terminator */
+ {{NULL}}
+};
+
static relopt_int intRelOpts[] =
{
{
@@ -600,6 +606,13 @@ initialize_reloptions(void)
boolRelOpts[i].gen.lockmode));
j++;
}
+ for (i = 0; ternaryRelOpts[i].gen.name; i++)
+ {
+ Assert(DoLockModesConflict(ternaryRelOpts[i].gen.lockmode,
+ ternaryRelOpts[i].gen.lockmode));
+ j++;
+ }
+
for (i = 0; intRelOpts[i].gen.name; i++)
{
Assert(DoLockModesConflict(intRelOpts[i].gen.lockmode,
@@ -640,6 +653,14 @@ initialize_reloptions(void)
j++;
}
+ for (i = 0; ternaryRelOpts[i].gen.name; i++)
+ {
+ relOpts[j] = &ternaryRelOpts[i].gen;
+ relOpts[j]->type = RELOPT_TYPE_TERNARY;
+ relOpts[j]->namelen = strlen(relOpts[j]->name);
+ j++;
+ }
+
for (i = 0; intRelOpts[i].gen.name; i++)
{
relOpts[j] = &intRelOpts[i].gen;
@@ -800,6 +821,9 @@ allocate_reloption(bits32 kinds, int type, const char *name, const char *desc,
case RELOPT_TYPE_BOOL:
size = sizeof(relopt_bool);
break;
+ case RELOPT_TYPE_TERNARY:
+ size = sizeof(relopt_ternary);
+ break;
case RELOPT_TYPE_INT:
size = sizeof(relopt_int);
break;
@@ -883,6 +907,54 @@ add_local_bool_reloption(local_relopts *relopts, const char *name,
add_local_reloption(relopts, (relopt_gen *) newoption, offset);
}
+/*
+ * init_ternary_reloption
+ * Allocate and initialize a new ternary reloption
+ */
+static relopt_ternary *
+init_ternary_reloption(bits32 kinds, const char *name, const char *desc,
+ ternary default_val, LOCKMODE lockmode)
+{
+ relopt_ternary *newoption;
+
+ newoption = (relopt_ternary *) allocate_reloption(kinds,
+ RELOPT_TYPE_TERNARY, name, desc, lockmode);
+ newoption->default_val = default_val;
+
+ return newoption;
+}
+
+/*
+ * add_ternary_reloption
+ * Add a new ternary reloption
+ */
+void
+add_ternary_reloption(bits32 kinds, const char *name, const char *desc,
+ ternary default_val, LOCKMODE lockmode)
+{
+ relopt_ternary *newoption = init_ternary_reloption(kinds, name, desc,
+ default_val, lockmode);
+
+ add_reloption((relopt_gen *) newoption);
+}
+
+/*
+ * add_local_ternary_reloption
+ * Add a new ternary local reloption
+ *
+ * 'offset' is offset of ternary-typed field.
+ */
+void
+add_local_ternary_reloption(local_relopts *relopts, const char *name,
+ const char *desc, ternary default_val,
+ int offset)
+{
+ relopt_ternary *newoption = init_ternary_reloption(RELOPT_KIND_LOCAL,
+ name, desc,
+ default_val, 0);
+
+ add_local_reloption(relopts, (relopt_gen *) newoption, offset);
+}
/*
* init_real_reloption
@@ -1617,6 +1689,18 @@ parse_one_reloption(relopt_value *option, char *text_str, int text_len,
option->gen->name, value)));
}
break;
+ case RELOPT_TYPE_TERNARY:
+ {
+ bool b;
+ parsed = parse_bool(value, &b);
+ option->values.ternary_val = b ? TERNARY_TRUE : TERNARY_FALSE;
+ if (validate && !parsed)
+ ereport(ERROR,
+ (errcode(ERRCODE_INVALID_PARAMETER_VALUE),
+ errmsg("invalid value for ternary option \"%s\": %s",
+ option->gen->name, value)));
+ }
+ break;
case RELOPT_TYPE_INT:
{
relopt_int *optint = (relopt_int *) option->gen;
@@ -1780,17 +1864,6 @@ fillRelOptions(void *rdopts, Size basesize,
char *itempos = ((char *) rdopts) + elems[j].offset;
char *string_val;
- /*
- * If isset_offset is provided, store whether the reloption is
- * set there.
- */
- if (elems[j].isset_offset > 0)
- {
- char *setpos = ((char *) rdopts) + elems[j].isset_offset;
-
- *(bool *) setpos = options[i].isset;
- }
-
switch (options[i].gen->type)
{
case RELOPT_TYPE_BOOL:
@@ -1798,6 +1871,11 @@ fillRelOptions(void *rdopts, Size basesize,
options[i].values.bool_val :
((relopt_bool *) options[i].gen)->default_val;
break;
+ case RELOPT_TYPE_TERNARY:
+ *(ternary *) itempos = options[i].isset ?
+ options[i].values.ternary_val :
+ ((relopt_ternary *) options[i].gen)->default_val;
+ break;
case RELOPT_TYPE_INT:
*(int *) itempos = options[i].isset ?
options[i].values.int_val :
@@ -1912,8 +1990,8 @@ default_reloptions(Datum reloptions, bool validate, relopt_kind kind)
offsetof(StdRdOptions, parallel_workers)},
{"vacuum_index_cleanup", RELOPT_TYPE_ENUM,
offsetof(StdRdOptions, vacuum_index_cleanup)},
- {"vacuum_truncate", RELOPT_TYPE_BOOL,
- offsetof(StdRdOptions, vacuum_truncate), offsetof(StdRdOptions, vacuum_truncate_set)},
+ {"vacuum_truncate", RELOPT_TYPE_TERNARY,
+ offsetof(StdRdOptions, vacuum_truncate)},
{"vacuum_max_eager_freeze_failure_rate", RELOPT_TYPE_REAL,
offsetof(StdRdOptions, vacuum_max_eager_freeze_failure_rate)}
};
@@ -1993,7 +2071,6 @@ build_local_reloptions(local_relopts *relopts, Datum options, bool validate)
elems[i].optname = opt->option->name;
elems[i].opttype = opt->option->type;
elems[i].offset = opt->offset;
- elems[i].isset_offset = 0; /* not supported for local relopts yet */
i++;
}
diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c
index 733ef40ae7..977babff54 100644
--- a/src/backend/commands/vacuum.c
+++ b/src/backend/commands/vacuum.c
@@ -2221,9 +2221,9 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams params,
{
StdRdOptions *opts = (StdRdOptions *) rel->rd_options;
- if (opts && opts->vacuum_truncate_set)
+ if (opts && opts->vacuum_truncate != TERNARY_UNSET)
{
- if (opts->vacuum_truncate)
+ if (opts->vacuum_truncate == TERNARY_TRUE)
params.truncate = VACOPTVALUE_ENABLED;
else
params.truncate = VACOPTVALUE_DISABLED;
diff --git a/src/include/access/reloptions.h b/src/include/access/reloptions.h
index a604a4702c..a436697658 100644
--- a/src/include/access/reloptions.h
+++ b/src/include/access/reloptions.h
@@ -29,6 +29,7 @@
typedef enum relopt_type
{
RELOPT_TYPE_BOOL,
+ RELOPT_TYPE_TERNARY, /* on, off, unset */
RELOPT_TYPE_INT,
RELOPT_TYPE_REAL,
RELOPT_TYPE_ENUM,
@@ -80,6 +81,7 @@ typedef struct relopt_value
union
{
bool bool_val;
+ ternary ternary_val;
int int_val;
double real_val;
int enum_val;
@@ -94,6 +96,12 @@ typedef struct relopt_bool
bool default_val;
} relopt_bool;
+typedef struct relopt_rernary
+{
+ relopt_gen gen;
+ int default_val;
+} relopt_ternary;
+
typedef struct relopt_int
{
relopt_gen gen;
@@ -152,19 +160,6 @@ typedef struct
const char *optname; /* option's name */
relopt_type opttype; /* option's datatype */
int offset; /* offset of field in result struct */
-
- /*
- * isset_offset is an optional offset of a field in the result struct that
- * stores whether the option is explicitly set for the relation or if it
- * just picked up the default value. In most cases, this can be
- * accomplished by giving the reloption a special out-of-range default
- * value (e.g., some integer reloptions use -2), but this isn't always
- * possible. For example, a Boolean reloption cannot be given an
- * out-of-range default, so we need another way to discover the source of
- * its value. This offset is only used if given a value greater than
- * zero.
- */
- int isset_offset;
} relopt_parse_elt;
/* Local reloption definition */
@@ -195,6 +190,8 @@ typedef struct local_relopts
extern relopt_kind add_reloption_kind(void);
extern void add_bool_reloption(bits32 kinds, const char *name, const char *desc,
bool default_val, LOCKMODE lockmode);
+extern void add_ternary_reloption(bits32 kinds, const char *name,
+ const char *desc, int default_val, LOCKMODE lockmode);
extern void add_int_reloption(bits32 kinds, const char *name, const char *desc,
int default_val, int min_val, int max_val,
LOCKMODE lockmode);
@@ -214,6 +211,9 @@ extern void register_reloptions_validator(local_relopts *relopts,
extern void add_local_bool_reloption(local_relopts *relopts, const char *name,
const char *desc, bool default_val,
int offset);
+extern void add_local_ternary_reloption(local_relopts *relopts,
+ const char *name, const char *desc,
+ ternary default_val, int offset);
extern void add_local_int_reloption(local_relopts *relopts, const char *name,
const char *desc, int default_val,
int min_val, int max_val, int offset);
diff --git a/src/include/c.h b/src/include/c.h
index 39022f8a9d..9c59ccccf8 100644
--- a/src/include/c.h
+++ b/src/include/c.h
@@ -488,6 +488,22 @@ typedef void (*pg_funcptr_t) (void);
#include <stdbool.h>
+/*
+ * ternary
+ * Boolean value with an extrea "unset" option
+ *
+ * Ternary data type is used in relation options that can be "true", "false" or
+ * "unset". Since relation options are used deep inside the PostgreSQL code,
+ * this type is declared globally.
+*/
+
+typedef enum ternary
+{
+ TERNARY_FALSE = 0,
+ TERNARY_TRUE = 1,
+ TERNARY_UNSET = -1
+} ternary;
+
/* ----------------------------------------------------------------
* Section 3: standard system types
diff --git a/src/include/utils/rel.h b/src/include/utils/rel.h
index b552359915..7346d618f9 100644
--- a/src/include/utils/rel.h
+++ b/src/include/utils/rel.h
@@ -346,8 +346,7 @@ typedef struct StdRdOptions
bool user_catalog_table; /* use as an additional catalog relation */
int parallel_workers; /* max number of parallel workers */
StdRdOptIndexCleanup vacuum_index_cleanup; /* controls index vacuuming */
- bool vacuum_truncate; /* enables vacuum to truncate a relation */
- bool vacuum_truncate_set; /* whether vacuum_truncate is set */
+ ternary vacuum_truncate; /* enables vacuum to truncate a relation */
/*
* Fraction of pages in a relation that vacuum can eagerly scan and fail
--
2.39.2
--nextPart4155600.3ZeAukHxDK
Content-Disposition: attachment;
filename="v2a-0003-Add-alias-to-be-used-as-unset-state.patch"
Content-Transfer-Encoding: 7Bit
Content-Type: text/x-patch; charset="unicode-2-0-utf-8";
name="v2a-0003-Add-alias-to-be-used-as-unset-state.patch"
^ permalink raw reply [nested|flat] 868+ messages in thread
* [PATCH v2a 2/4] Introduce ternary reloptions
@ 2025-09-04 14:41 Nikolay Shaplov <[email protected]>
0 siblings, 0 replies; 868+ messages in thread
From: Nikolay Shaplov @ 2025-09-04 14:41 UTC (permalink / raw)
Introduce ternary reloption as a replacement for current `vacuum_truncate`
implementation. Remove `vacuum_truncate_set` additional flag and using
`TERNARY_UNSET` value instead.
---
src/backend/access/common/reloptions.c | 129 ++++++++++++++++++++-----
src/backend/commands/vacuum.c | 4 +-
src/include/access/reloptions.h | 26 ++---
src/include/c.h | 16 +++
src/include/utils/rel.h | 3 +-
5 files changed, 135 insertions(+), 43 deletions(-)
diff --git a/src/backend/access/common/reloptions.c b/src/backend/access/common/reloptions.c
index 0af3fea68f..f543f11001 100644
--- a/src/backend/access/common/reloptions.c
+++ b/src/backend/access/common/reloptions.c
@@ -40,9 +40,9 @@
*
* To add an option:
*
- * (i) decide on a type (bool, integer, real, enum, string), name, default
- * value, upper and lower bounds (if applicable); for strings, consider a
- * validation routine.
+ * (i) decide on a type (bool, ternary, integer, real, enum, string), name,
+ * default value, upper and lower bounds (if applicable); for strings,
+ * consider a validation routine.
* (ii) add a record below (or use add_<type>_reloption).
* (iii) add it to the appropriate options struct (perhaps StdRdOptions)
* (iv) add it to the appropriate handling routine (perhaps
@@ -147,15 +147,6 @@ static relopt_bool boolRelOpts[] =
},
false
},
- {
- {
- "vacuum_truncate",
- "Enables vacuum to truncate empty pages at the end of this table",
- RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
- ShareUpdateExclusiveLock
- },
- true
- },
{
{
"deduplicate_items",
@@ -170,6 +161,21 @@ static relopt_bool boolRelOpts[] =
{{NULL}}
};
+static relopt_ternary ternaryRelOpts[] =
+{
+ {
+ {
+ "vacuum_truncate",
+ "Enables vacuum to truncate empty pages at the end of this table",
+ RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
+ ShareUpdateExclusiveLock
+ },
+ TERNARY_UNSET
+ },
+ /* list terminator */
+ {{NULL}}
+};
+
static relopt_int intRelOpts[] =
{
{
@@ -600,6 +606,13 @@ initialize_reloptions(void)
boolRelOpts[i].gen.lockmode));
j++;
}
+ for (i = 0; ternaryRelOpts[i].gen.name; i++)
+ {
+ Assert(DoLockModesConflict(ternaryRelOpts[i].gen.lockmode,
+ ternaryRelOpts[i].gen.lockmode));
+ j++;
+ }
+
for (i = 0; intRelOpts[i].gen.name; i++)
{
Assert(DoLockModesConflict(intRelOpts[i].gen.lockmode,
@@ -640,6 +653,14 @@ initialize_reloptions(void)
j++;
}
+ for (i = 0; ternaryRelOpts[i].gen.name; i++)
+ {
+ relOpts[j] = &ternaryRelOpts[i].gen;
+ relOpts[j]->type = RELOPT_TYPE_TERNARY;
+ relOpts[j]->namelen = strlen(relOpts[j]->name);
+ j++;
+ }
+
for (i = 0; intRelOpts[i].gen.name; i++)
{
relOpts[j] = &intRelOpts[i].gen;
@@ -800,6 +821,9 @@ allocate_reloption(bits32 kinds, int type, const char *name, const char *desc,
case RELOPT_TYPE_BOOL:
size = sizeof(relopt_bool);
break;
+ case RELOPT_TYPE_TERNARY:
+ size = sizeof(relopt_ternary);
+ break;
case RELOPT_TYPE_INT:
size = sizeof(relopt_int);
break;
@@ -883,6 +907,54 @@ add_local_bool_reloption(local_relopts *relopts, const char *name,
add_local_reloption(relopts, (relopt_gen *) newoption, offset);
}
+/*
+ * init_ternary_reloption
+ * Allocate and initialize a new ternary reloption
+ */
+static relopt_ternary *
+init_ternary_reloption(bits32 kinds, const char *name, const char *desc,
+ ternary default_val, LOCKMODE lockmode)
+{
+ relopt_ternary *newoption;
+
+ newoption = (relopt_ternary *) allocate_reloption(kinds,
+ RELOPT_TYPE_TERNARY, name, desc, lockmode);
+ newoption->default_val = default_val;
+
+ return newoption;
+}
+
+/*
+ * add_ternary_reloption
+ * Add a new ternary reloption
+ */
+void
+add_ternary_reloption(bits32 kinds, const char *name, const char *desc,
+ ternary default_val, LOCKMODE lockmode)
+{
+ relopt_ternary *newoption = init_ternary_reloption(kinds, name, desc,
+ default_val, lockmode);
+
+ add_reloption((relopt_gen *) newoption);
+}
+
+/*
+ * add_local_ternary_reloption
+ * Add a new ternary local reloption
+ *
+ * 'offset' is offset of ternary-typed field.
+ */
+void
+add_local_ternary_reloption(local_relopts *relopts, const char *name,
+ const char *desc, ternary default_val,
+ int offset)
+{
+ relopt_ternary *newoption = init_ternary_reloption(RELOPT_KIND_LOCAL,
+ name, desc,
+ default_val, 0);
+
+ add_local_reloption(relopts, (relopt_gen *) newoption, offset);
+}
/*
* init_real_reloption
@@ -1617,6 +1689,18 @@ parse_one_reloption(relopt_value *option, char *text_str, int text_len,
option->gen->name, value)));
}
break;
+ case RELOPT_TYPE_TERNARY:
+ {
+ bool b;
+ parsed = parse_bool(value, &b);
+ option->values.ternary_val = b ? TERNARY_TRUE : TERNARY_FALSE;
+ if (validate && !parsed)
+ ereport(ERROR,
+ (errcode(ERRCODE_INVALID_PARAMETER_VALUE),
+ errmsg("invalid value for ternary option \"%s\": %s",
+ option->gen->name, value)));
+ }
+ break;
case RELOPT_TYPE_INT:
{
relopt_int *optint = (relopt_int *) option->gen;
@@ -1780,17 +1864,6 @@ fillRelOptions(void *rdopts, Size basesize,
char *itempos = ((char *) rdopts) + elems[j].offset;
char *string_val;
- /*
- * If isset_offset is provided, store whether the reloption is
- * set there.
- */
- if (elems[j].isset_offset > 0)
- {
- char *setpos = ((char *) rdopts) + elems[j].isset_offset;
-
- *(bool *) setpos = options[i].isset;
- }
-
switch (options[i].gen->type)
{
case RELOPT_TYPE_BOOL:
@@ -1798,6 +1871,11 @@ fillRelOptions(void *rdopts, Size basesize,
options[i].values.bool_val :
((relopt_bool *) options[i].gen)->default_val;
break;
+ case RELOPT_TYPE_TERNARY:
+ *(ternary *) itempos = options[i].isset ?
+ options[i].values.ternary_val :
+ ((relopt_ternary *) options[i].gen)->default_val;
+ break;
case RELOPT_TYPE_INT:
*(int *) itempos = options[i].isset ?
options[i].values.int_val :
@@ -1912,8 +1990,8 @@ default_reloptions(Datum reloptions, bool validate, relopt_kind kind)
offsetof(StdRdOptions, parallel_workers)},
{"vacuum_index_cleanup", RELOPT_TYPE_ENUM,
offsetof(StdRdOptions, vacuum_index_cleanup)},
- {"vacuum_truncate", RELOPT_TYPE_BOOL,
- offsetof(StdRdOptions, vacuum_truncate), offsetof(StdRdOptions, vacuum_truncate_set)},
+ {"vacuum_truncate", RELOPT_TYPE_TERNARY,
+ offsetof(StdRdOptions, vacuum_truncate)},
{"vacuum_max_eager_freeze_failure_rate", RELOPT_TYPE_REAL,
offsetof(StdRdOptions, vacuum_max_eager_freeze_failure_rate)}
};
@@ -1993,7 +2071,6 @@ build_local_reloptions(local_relopts *relopts, Datum options, bool validate)
elems[i].optname = opt->option->name;
elems[i].opttype = opt->option->type;
elems[i].offset = opt->offset;
- elems[i].isset_offset = 0; /* not supported for local relopts yet */
i++;
}
diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c
index 733ef40ae7..977babff54 100644
--- a/src/backend/commands/vacuum.c
+++ b/src/backend/commands/vacuum.c
@@ -2221,9 +2221,9 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams params,
{
StdRdOptions *opts = (StdRdOptions *) rel->rd_options;
- if (opts && opts->vacuum_truncate_set)
+ if (opts && opts->vacuum_truncate != TERNARY_UNSET)
{
- if (opts->vacuum_truncate)
+ if (opts->vacuum_truncate == TERNARY_TRUE)
params.truncate = VACOPTVALUE_ENABLED;
else
params.truncate = VACOPTVALUE_DISABLED;
diff --git a/src/include/access/reloptions.h b/src/include/access/reloptions.h
index a604a4702c..a436697658 100644
--- a/src/include/access/reloptions.h
+++ b/src/include/access/reloptions.h
@@ -29,6 +29,7 @@
typedef enum relopt_type
{
RELOPT_TYPE_BOOL,
+ RELOPT_TYPE_TERNARY, /* on, off, unset */
RELOPT_TYPE_INT,
RELOPT_TYPE_REAL,
RELOPT_TYPE_ENUM,
@@ -80,6 +81,7 @@ typedef struct relopt_value
union
{
bool bool_val;
+ ternary ternary_val;
int int_val;
double real_val;
int enum_val;
@@ -94,6 +96,12 @@ typedef struct relopt_bool
bool default_val;
} relopt_bool;
+typedef struct relopt_rernary
+{
+ relopt_gen gen;
+ int default_val;
+} relopt_ternary;
+
typedef struct relopt_int
{
relopt_gen gen;
@@ -152,19 +160,6 @@ typedef struct
const char *optname; /* option's name */
relopt_type opttype; /* option's datatype */
int offset; /* offset of field in result struct */
-
- /*
- * isset_offset is an optional offset of a field in the result struct that
- * stores whether the option is explicitly set for the relation or if it
- * just picked up the default value. In most cases, this can be
- * accomplished by giving the reloption a special out-of-range default
- * value (e.g., some integer reloptions use -2), but this isn't always
- * possible. For example, a Boolean reloption cannot be given an
- * out-of-range default, so we need another way to discover the source of
- * its value. This offset is only used if given a value greater than
- * zero.
- */
- int isset_offset;
} relopt_parse_elt;
/* Local reloption definition */
@@ -195,6 +190,8 @@ typedef struct local_relopts
extern relopt_kind add_reloption_kind(void);
extern void add_bool_reloption(bits32 kinds, const char *name, const char *desc,
bool default_val, LOCKMODE lockmode);
+extern void add_ternary_reloption(bits32 kinds, const char *name,
+ const char *desc, int default_val, LOCKMODE lockmode);
extern void add_int_reloption(bits32 kinds, const char *name, const char *desc,
int default_val, int min_val, int max_val,
LOCKMODE lockmode);
@@ -214,6 +211,9 @@ extern void register_reloptions_validator(local_relopts *relopts,
extern void add_local_bool_reloption(local_relopts *relopts, const char *name,
const char *desc, bool default_val,
int offset);
+extern void add_local_ternary_reloption(local_relopts *relopts,
+ const char *name, const char *desc,
+ ternary default_val, int offset);
extern void add_local_int_reloption(local_relopts *relopts, const char *name,
const char *desc, int default_val,
int min_val, int max_val, int offset);
diff --git a/src/include/c.h b/src/include/c.h
index 39022f8a9d..9c59ccccf8 100644
--- a/src/include/c.h
+++ b/src/include/c.h
@@ -488,6 +488,22 @@ typedef void (*pg_funcptr_t) (void);
#include <stdbool.h>
+/*
+ * ternary
+ * Boolean value with an extrea "unset" option
+ *
+ * Ternary data type is used in relation options that can be "true", "false" or
+ * "unset". Since relation options are used deep inside the PostgreSQL code,
+ * this type is declared globally.
+*/
+
+typedef enum ternary
+{
+ TERNARY_FALSE = 0,
+ TERNARY_TRUE = 1,
+ TERNARY_UNSET = -1
+} ternary;
+
/* ----------------------------------------------------------------
* Section 3: standard system types
diff --git a/src/include/utils/rel.h b/src/include/utils/rel.h
index b552359915..7346d618f9 100644
--- a/src/include/utils/rel.h
+++ b/src/include/utils/rel.h
@@ -346,8 +346,7 @@ typedef struct StdRdOptions
bool user_catalog_table; /* use as an additional catalog relation */
int parallel_workers; /* max number of parallel workers */
StdRdOptIndexCleanup vacuum_index_cleanup; /* controls index vacuuming */
- bool vacuum_truncate; /* enables vacuum to truncate a relation */
- bool vacuum_truncate_set; /* whether vacuum_truncate is set */
+ ternary vacuum_truncate; /* enables vacuum to truncate a relation */
/*
* Fraction of pages in a relation that vacuum can eagerly scan and fail
--
2.39.2
--nextPart4155600.3ZeAukHxDK
Content-Disposition: attachment;
filename="v2a-0003-Add-alias-to-be-used-as-unset-state.patch"
Content-Transfer-Encoding: 7Bit
Content-Type: text/x-patch; charset="unicode-2-0-utf-8";
name="v2a-0003-Add-alias-to-be-used-as-unset-state.patch"
^ permalink raw reply [nested|flat] 868+ messages in thread
* [PATCH v2 2/4] Introduce ternary reloptions
@ 2025-09-04 14:41 Nikolay Shaplov <[email protected]>
0 siblings, 0 replies; 868+ messages in thread
From: Nikolay Shaplov @ 2025-09-04 14:41 UTC (permalink / raw)
Introduce ternary reloption as a replacement for current `vacuum_truncate`
implementation. Remove `vacuum_truncate_set` additional flag and using
`TERNARY_UNSET` value instead.
---
src/backend/access/common/reloptions.c | 129 ++++++++++++++++++++-----
src/backend/commands/vacuum.c | 4 +-
src/include/access/reloptions.h | 26 ++---
src/include/c.h | 16 +++
src/include/utils/rel.h | 3 +-
5 files changed, 135 insertions(+), 43 deletions(-)
diff --git a/src/backend/access/common/reloptions.c b/src/backend/access/common/reloptions.c
index 0af3fea68f..f543f11001 100644
--- a/src/backend/access/common/reloptions.c
+++ b/src/backend/access/common/reloptions.c
@@ -40,9 +40,9 @@
*
* To add an option:
*
- * (i) decide on a type (bool, integer, real, enum, string), name, default
- * value, upper and lower bounds (if applicable); for strings, consider a
- * validation routine.
+ * (i) decide on a type (bool, ternary, integer, real, enum, string), name,
+ * default value, upper and lower bounds (if applicable); for strings,
+ * consider a validation routine.
* (ii) add a record below (or use add_<type>_reloption).
* (iii) add it to the appropriate options struct (perhaps StdRdOptions)
* (iv) add it to the appropriate handling routine (perhaps
@@ -147,15 +147,6 @@ static relopt_bool boolRelOpts[] =
},
false
},
- {
- {
- "vacuum_truncate",
- "Enables vacuum to truncate empty pages at the end of this table",
- RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
- ShareUpdateExclusiveLock
- },
- true
- },
{
{
"deduplicate_items",
@@ -170,6 +161,21 @@ static relopt_bool boolRelOpts[] =
{{NULL}}
};
+static relopt_ternary ternaryRelOpts[] =
+{
+ {
+ {
+ "vacuum_truncate",
+ "Enables vacuum to truncate empty pages at the end of this table",
+ RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
+ ShareUpdateExclusiveLock
+ },
+ TERNARY_UNSET
+ },
+ /* list terminator */
+ {{NULL}}
+};
+
static relopt_int intRelOpts[] =
{
{
@@ -600,6 +606,13 @@ initialize_reloptions(void)
boolRelOpts[i].gen.lockmode));
j++;
}
+ for (i = 0; ternaryRelOpts[i].gen.name; i++)
+ {
+ Assert(DoLockModesConflict(ternaryRelOpts[i].gen.lockmode,
+ ternaryRelOpts[i].gen.lockmode));
+ j++;
+ }
+
for (i = 0; intRelOpts[i].gen.name; i++)
{
Assert(DoLockModesConflict(intRelOpts[i].gen.lockmode,
@@ -640,6 +653,14 @@ initialize_reloptions(void)
j++;
}
+ for (i = 0; ternaryRelOpts[i].gen.name; i++)
+ {
+ relOpts[j] = &ternaryRelOpts[i].gen;
+ relOpts[j]->type = RELOPT_TYPE_TERNARY;
+ relOpts[j]->namelen = strlen(relOpts[j]->name);
+ j++;
+ }
+
for (i = 0; intRelOpts[i].gen.name; i++)
{
relOpts[j] = &intRelOpts[i].gen;
@@ -800,6 +821,9 @@ allocate_reloption(bits32 kinds, int type, const char *name, const char *desc,
case RELOPT_TYPE_BOOL:
size = sizeof(relopt_bool);
break;
+ case RELOPT_TYPE_TERNARY:
+ size = sizeof(relopt_ternary);
+ break;
case RELOPT_TYPE_INT:
size = sizeof(relopt_int);
break;
@@ -883,6 +907,54 @@ add_local_bool_reloption(local_relopts *relopts, const char *name,
add_local_reloption(relopts, (relopt_gen *) newoption, offset);
}
+/*
+ * init_ternary_reloption
+ * Allocate and initialize a new ternary reloption
+ */
+static relopt_ternary *
+init_ternary_reloption(bits32 kinds, const char *name, const char *desc,
+ ternary default_val, LOCKMODE lockmode)
+{
+ relopt_ternary *newoption;
+
+ newoption = (relopt_ternary *) allocate_reloption(kinds,
+ RELOPT_TYPE_TERNARY, name, desc, lockmode);
+ newoption->default_val = default_val;
+
+ return newoption;
+}
+
+/*
+ * add_ternary_reloption
+ * Add a new ternary reloption
+ */
+void
+add_ternary_reloption(bits32 kinds, const char *name, const char *desc,
+ ternary default_val, LOCKMODE lockmode)
+{
+ relopt_ternary *newoption = init_ternary_reloption(kinds, name, desc,
+ default_val, lockmode);
+
+ add_reloption((relopt_gen *) newoption);
+}
+
+/*
+ * add_local_ternary_reloption
+ * Add a new ternary local reloption
+ *
+ * 'offset' is offset of ternary-typed field.
+ */
+void
+add_local_ternary_reloption(local_relopts *relopts, const char *name,
+ const char *desc, ternary default_val,
+ int offset)
+{
+ relopt_ternary *newoption = init_ternary_reloption(RELOPT_KIND_LOCAL,
+ name, desc,
+ default_val, 0);
+
+ add_local_reloption(relopts, (relopt_gen *) newoption, offset);
+}
/*
* init_real_reloption
@@ -1617,6 +1689,18 @@ parse_one_reloption(relopt_value *option, char *text_str, int text_len,
option->gen->name, value)));
}
break;
+ case RELOPT_TYPE_TERNARY:
+ {
+ bool b;
+ parsed = parse_bool(value, &b);
+ option->values.ternary_val = b ? TERNARY_TRUE : TERNARY_FALSE;
+ if (validate && !parsed)
+ ereport(ERROR,
+ (errcode(ERRCODE_INVALID_PARAMETER_VALUE),
+ errmsg("invalid value for ternary option \"%s\": %s",
+ option->gen->name, value)));
+ }
+ break;
case RELOPT_TYPE_INT:
{
relopt_int *optint = (relopt_int *) option->gen;
@@ -1780,17 +1864,6 @@ fillRelOptions(void *rdopts, Size basesize,
char *itempos = ((char *) rdopts) + elems[j].offset;
char *string_val;
- /*
- * If isset_offset is provided, store whether the reloption is
- * set there.
- */
- if (elems[j].isset_offset > 0)
- {
- char *setpos = ((char *) rdopts) + elems[j].isset_offset;
-
- *(bool *) setpos = options[i].isset;
- }
-
switch (options[i].gen->type)
{
case RELOPT_TYPE_BOOL:
@@ -1798,6 +1871,11 @@ fillRelOptions(void *rdopts, Size basesize,
options[i].values.bool_val :
((relopt_bool *) options[i].gen)->default_val;
break;
+ case RELOPT_TYPE_TERNARY:
+ *(ternary *) itempos = options[i].isset ?
+ options[i].values.ternary_val :
+ ((relopt_ternary *) options[i].gen)->default_val;
+ break;
case RELOPT_TYPE_INT:
*(int *) itempos = options[i].isset ?
options[i].values.int_val :
@@ -1912,8 +1990,8 @@ default_reloptions(Datum reloptions, bool validate, relopt_kind kind)
offsetof(StdRdOptions, parallel_workers)},
{"vacuum_index_cleanup", RELOPT_TYPE_ENUM,
offsetof(StdRdOptions, vacuum_index_cleanup)},
- {"vacuum_truncate", RELOPT_TYPE_BOOL,
- offsetof(StdRdOptions, vacuum_truncate), offsetof(StdRdOptions, vacuum_truncate_set)},
+ {"vacuum_truncate", RELOPT_TYPE_TERNARY,
+ offsetof(StdRdOptions, vacuum_truncate)},
{"vacuum_max_eager_freeze_failure_rate", RELOPT_TYPE_REAL,
offsetof(StdRdOptions, vacuum_max_eager_freeze_failure_rate)}
};
@@ -1993,7 +2071,6 @@ build_local_reloptions(local_relopts *relopts, Datum options, bool validate)
elems[i].optname = opt->option->name;
elems[i].opttype = opt->option->type;
elems[i].offset = opt->offset;
- elems[i].isset_offset = 0; /* not supported for local relopts yet */
i++;
}
diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c
index 733ef40ae7..977babff54 100644
--- a/src/backend/commands/vacuum.c
+++ b/src/backend/commands/vacuum.c
@@ -2221,9 +2221,9 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams params,
{
StdRdOptions *opts = (StdRdOptions *) rel->rd_options;
- if (opts && opts->vacuum_truncate_set)
+ if (opts && opts->vacuum_truncate != TERNARY_UNSET)
{
- if (opts->vacuum_truncate)
+ if (opts->vacuum_truncate == TERNARY_TRUE)
params.truncate = VACOPTVALUE_ENABLED;
else
params.truncate = VACOPTVALUE_DISABLED;
diff --git a/src/include/access/reloptions.h b/src/include/access/reloptions.h
index a604a4702c..a436697658 100644
--- a/src/include/access/reloptions.h
+++ b/src/include/access/reloptions.h
@@ -29,6 +29,7 @@
typedef enum relopt_type
{
RELOPT_TYPE_BOOL,
+ RELOPT_TYPE_TERNARY, /* on, off, unset */
RELOPT_TYPE_INT,
RELOPT_TYPE_REAL,
RELOPT_TYPE_ENUM,
@@ -80,6 +81,7 @@ typedef struct relopt_value
union
{
bool bool_val;
+ ternary ternary_val;
int int_val;
double real_val;
int enum_val;
@@ -94,6 +96,12 @@ typedef struct relopt_bool
bool default_val;
} relopt_bool;
+typedef struct relopt_rernary
+{
+ relopt_gen gen;
+ int default_val;
+} relopt_ternary;
+
typedef struct relopt_int
{
relopt_gen gen;
@@ -152,19 +160,6 @@ typedef struct
const char *optname; /* option's name */
relopt_type opttype; /* option's datatype */
int offset; /* offset of field in result struct */
-
- /*
- * isset_offset is an optional offset of a field in the result struct that
- * stores whether the option is explicitly set for the relation or if it
- * just picked up the default value. In most cases, this can be
- * accomplished by giving the reloption a special out-of-range default
- * value (e.g., some integer reloptions use -2), but this isn't always
- * possible. For example, a Boolean reloption cannot be given an
- * out-of-range default, so we need another way to discover the source of
- * its value. This offset is only used if given a value greater than
- * zero.
- */
- int isset_offset;
} relopt_parse_elt;
/* Local reloption definition */
@@ -195,6 +190,8 @@ typedef struct local_relopts
extern relopt_kind add_reloption_kind(void);
extern void add_bool_reloption(bits32 kinds, const char *name, const char *desc,
bool default_val, LOCKMODE lockmode);
+extern void add_ternary_reloption(bits32 kinds, const char *name,
+ const char *desc, int default_val, LOCKMODE lockmode);
extern void add_int_reloption(bits32 kinds, const char *name, const char *desc,
int default_val, int min_val, int max_val,
LOCKMODE lockmode);
@@ -214,6 +211,9 @@ extern void register_reloptions_validator(local_relopts *relopts,
extern void add_local_bool_reloption(local_relopts *relopts, const char *name,
const char *desc, bool default_val,
int offset);
+extern void add_local_ternary_reloption(local_relopts *relopts,
+ const char *name, const char *desc,
+ ternary default_val, int offset);
extern void add_local_int_reloption(local_relopts *relopts, const char *name,
const char *desc, int default_val,
int min_val, int max_val, int offset);
diff --git a/src/include/c.h b/src/include/c.h
index 39022f8a9d..9c59ccccf8 100644
--- a/src/include/c.h
+++ b/src/include/c.h
@@ -488,6 +488,22 @@ typedef void (*pg_funcptr_t) (void);
#include <stdbool.h>
+/*
+ * ternary
+ * Boolean value with an extrea "unset" option
+ *
+ * Ternary data type is used in relation options that can be "true", "false" or
+ * "unset". Since relation options are used deep inside the PostgreSQL code,
+ * this type is declared globally.
+*/
+
+typedef enum ternary
+{
+ TERNARY_FALSE = 0,
+ TERNARY_TRUE = 1,
+ TERNARY_UNSET = -1
+} ternary;
+
/* ----------------------------------------------------------------
* Section 3: standard system types
diff --git a/src/include/utils/rel.h b/src/include/utils/rel.h
index b552359915..7346d618f9 100644
--- a/src/include/utils/rel.h
+++ b/src/include/utils/rel.h
@@ -346,8 +346,7 @@ typedef struct StdRdOptions
bool user_catalog_table; /* use as an additional catalog relation */
int parallel_workers; /* max number of parallel workers */
StdRdOptIndexCleanup vacuum_index_cleanup; /* controls index vacuuming */
- bool vacuum_truncate; /* enables vacuum to truncate a relation */
- bool vacuum_truncate_set; /* whether vacuum_truncate is set */
+ ternary vacuum_truncate; /* enables vacuum to truncate a relation */
/*
* Fraction of pages in a relation that vacuum can eagerly scan and fail
--
2.39.2
--nextPart5129932.5fSG56mABF
Content-Disposition: attachment;
filename="v2-0003-Add-alias-to-be-used-as-unset-state.patch"
Content-Transfer-Encoding: 7Bit
Content-Type: text/x-patch; charset="unicode-2-0-utf-8";
name="v2-0003-Add-alias-to-be-used-as-unset-state.patch"
^ permalink raw reply [nested|flat] 868+ messages in thread
* [PATCH v2a 2/4] Introduce ternary reloptions
@ 2025-09-04 14:41 Nikolay Shaplov <[email protected]>
0 siblings, 0 replies; 868+ messages in thread
From: Nikolay Shaplov @ 2025-09-04 14:41 UTC (permalink / raw)
Introduce ternary reloption as a replacement for current `vacuum_truncate`
implementation. Remove `vacuum_truncate_set` additional flag and using
`TERNARY_UNSET` value instead.
---
src/backend/access/common/reloptions.c | 129 ++++++++++++++++++++-----
src/backend/commands/vacuum.c | 4 +-
src/include/access/reloptions.h | 26 ++---
src/include/c.h | 16 +++
src/include/utils/rel.h | 3 +-
5 files changed, 135 insertions(+), 43 deletions(-)
diff --git a/src/backend/access/common/reloptions.c b/src/backend/access/common/reloptions.c
index 0af3fea68f..f543f11001 100644
--- a/src/backend/access/common/reloptions.c
+++ b/src/backend/access/common/reloptions.c
@@ -40,9 +40,9 @@
*
* To add an option:
*
- * (i) decide on a type (bool, integer, real, enum, string), name, default
- * value, upper and lower bounds (if applicable); for strings, consider a
- * validation routine.
+ * (i) decide on a type (bool, ternary, integer, real, enum, string), name,
+ * default value, upper and lower bounds (if applicable); for strings,
+ * consider a validation routine.
* (ii) add a record below (or use add_<type>_reloption).
* (iii) add it to the appropriate options struct (perhaps StdRdOptions)
* (iv) add it to the appropriate handling routine (perhaps
@@ -147,15 +147,6 @@ static relopt_bool boolRelOpts[] =
},
false
},
- {
- {
- "vacuum_truncate",
- "Enables vacuum to truncate empty pages at the end of this table",
- RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
- ShareUpdateExclusiveLock
- },
- true
- },
{
{
"deduplicate_items",
@@ -170,6 +161,21 @@ static relopt_bool boolRelOpts[] =
{{NULL}}
};
+static relopt_ternary ternaryRelOpts[] =
+{
+ {
+ {
+ "vacuum_truncate",
+ "Enables vacuum to truncate empty pages at the end of this table",
+ RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
+ ShareUpdateExclusiveLock
+ },
+ TERNARY_UNSET
+ },
+ /* list terminator */
+ {{NULL}}
+};
+
static relopt_int intRelOpts[] =
{
{
@@ -600,6 +606,13 @@ initialize_reloptions(void)
boolRelOpts[i].gen.lockmode));
j++;
}
+ for (i = 0; ternaryRelOpts[i].gen.name; i++)
+ {
+ Assert(DoLockModesConflict(ternaryRelOpts[i].gen.lockmode,
+ ternaryRelOpts[i].gen.lockmode));
+ j++;
+ }
+
for (i = 0; intRelOpts[i].gen.name; i++)
{
Assert(DoLockModesConflict(intRelOpts[i].gen.lockmode,
@@ -640,6 +653,14 @@ initialize_reloptions(void)
j++;
}
+ for (i = 0; ternaryRelOpts[i].gen.name; i++)
+ {
+ relOpts[j] = &ternaryRelOpts[i].gen;
+ relOpts[j]->type = RELOPT_TYPE_TERNARY;
+ relOpts[j]->namelen = strlen(relOpts[j]->name);
+ j++;
+ }
+
for (i = 0; intRelOpts[i].gen.name; i++)
{
relOpts[j] = &intRelOpts[i].gen;
@@ -800,6 +821,9 @@ allocate_reloption(bits32 kinds, int type, const char *name, const char *desc,
case RELOPT_TYPE_BOOL:
size = sizeof(relopt_bool);
break;
+ case RELOPT_TYPE_TERNARY:
+ size = sizeof(relopt_ternary);
+ break;
case RELOPT_TYPE_INT:
size = sizeof(relopt_int);
break;
@@ -883,6 +907,54 @@ add_local_bool_reloption(local_relopts *relopts, const char *name,
add_local_reloption(relopts, (relopt_gen *) newoption, offset);
}
+/*
+ * init_ternary_reloption
+ * Allocate and initialize a new ternary reloption
+ */
+static relopt_ternary *
+init_ternary_reloption(bits32 kinds, const char *name, const char *desc,
+ ternary default_val, LOCKMODE lockmode)
+{
+ relopt_ternary *newoption;
+
+ newoption = (relopt_ternary *) allocate_reloption(kinds,
+ RELOPT_TYPE_TERNARY, name, desc, lockmode);
+ newoption->default_val = default_val;
+
+ return newoption;
+}
+
+/*
+ * add_ternary_reloption
+ * Add a new ternary reloption
+ */
+void
+add_ternary_reloption(bits32 kinds, const char *name, const char *desc,
+ ternary default_val, LOCKMODE lockmode)
+{
+ relopt_ternary *newoption = init_ternary_reloption(kinds, name, desc,
+ default_val, lockmode);
+
+ add_reloption((relopt_gen *) newoption);
+}
+
+/*
+ * add_local_ternary_reloption
+ * Add a new ternary local reloption
+ *
+ * 'offset' is offset of ternary-typed field.
+ */
+void
+add_local_ternary_reloption(local_relopts *relopts, const char *name,
+ const char *desc, ternary default_val,
+ int offset)
+{
+ relopt_ternary *newoption = init_ternary_reloption(RELOPT_KIND_LOCAL,
+ name, desc,
+ default_val, 0);
+
+ add_local_reloption(relopts, (relopt_gen *) newoption, offset);
+}
/*
* init_real_reloption
@@ -1617,6 +1689,18 @@ parse_one_reloption(relopt_value *option, char *text_str, int text_len,
option->gen->name, value)));
}
break;
+ case RELOPT_TYPE_TERNARY:
+ {
+ bool b;
+ parsed = parse_bool(value, &b);
+ option->values.ternary_val = b ? TERNARY_TRUE : TERNARY_FALSE;
+ if (validate && !parsed)
+ ereport(ERROR,
+ (errcode(ERRCODE_INVALID_PARAMETER_VALUE),
+ errmsg("invalid value for ternary option \"%s\": %s",
+ option->gen->name, value)));
+ }
+ break;
case RELOPT_TYPE_INT:
{
relopt_int *optint = (relopt_int *) option->gen;
@@ -1780,17 +1864,6 @@ fillRelOptions(void *rdopts, Size basesize,
char *itempos = ((char *) rdopts) + elems[j].offset;
char *string_val;
- /*
- * If isset_offset is provided, store whether the reloption is
- * set there.
- */
- if (elems[j].isset_offset > 0)
- {
- char *setpos = ((char *) rdopts) + elems[j].isset_offset;
-
- *(bool *) setpos = options[i].isset;
- }
-
switch (options[i].gen->type)
{
case RELOPT_TYPE_BOOL:
@@ -1798,6 +1871,11 @@ fillRelOptions(void *rdopts, Size basesize,
options[i].values.bool_val :
((relopt_bool *) options[i].gen)->default_val;
break;
+ case RELOPT_TYPE_TERNARY:
+ *(ternary *) itempos = options[i].isset ?
+ options[i].values.ternary_val :
+ ((relopt_ternary *) options[i].gen)->default_val;
+ break;
case RELOPT_TYPE_INT:
*(int *) itempos = options[i].isset ?
options[i].values.int_val :
@@ -1912,8 +1990,8 @@ default_reloptions(Datum reloptions, bool validate, relopt_kind kind)
offsetof(StdRdOptions, parallel_workers)},
{"vacuum_index_cleanup", RELOPT_TYPE_ENUM,
offsetof(StdRdOptions, vacuum_index_cleanup)},
- {"vacuum_truncate", RELOPT_TYPE_BOOL,
- offsetof(StdRdOptions, vacuum_truncate), offsetof(StdRdOptions, vacuum_truncate_set)},
+ {"vacuum_truncate", RELOPT_TYPE_TERNARY,
+ offsetof(StdRdOptions, vacuum_truncate)},
{"vacuum_max_eager_freeze_failure_rate", RELOPT_TYPE_REAL,
offsetof(StdRdOptions, vacuum_max_eager_freeze_failure_rate)}
};
@@ -1993,7 +2071,6 @@ build_local_reloptions(local_relopts *relopts, Datum options, bool validate)
elems[i].optname = opt->option->name;
elems[i].opttype = opt->option->type;
elems[i].offset = opt->offset;
- elems[i].isset_offset = 0; /* not supported for local relopts yet */
i++;
}
diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c
index 733ef40ae7..977babff54 100644
--- a/src/backend/commands/vacuum.c
+++ b/src/backend/commands/vacuum.c
@@ -2221,9 +2221,9 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams params,
{
StdRdOptions *opts = (StdRdOptions *) rel->rd_options;
- if (opts && opts->vacuum_truncate_set)
+ if (opts && opts->vacuum_truncate != TERNARY_UNSET)
{
- if (opts->vacuum_truncate)
+ if (opts->vacuum_truncate == TERNARY_TRUE)
params.truncate = VACOPTVALUE_ENABLED;
else
params.truncate = VACOPTVALUE_DISABLED;
diff --git a/src/include/access/reloptions.h b/src/include/access/reloptions.h
index a604a4702c..a436697658 100644
--- a/src/include/access/reloptions.h
+++ b/src/include/access/reloptions.h
@@ -29,6 +29,7 @@
typedef enum relopt_type
{
RELOPT_TYPE_BOOL,
+ RELOPT_TYPE_TERNARY, /* on, off, unset */
RELOPT_TYPE_INT,
RELOPT_TYPE_REAL,
RELOPT_TYPE_ENUM,
@@ -80,6 +81,7 @@ typedef struct relopt_value
union
{
bool bool_val;
+ ternary ternary_val;
int int_val;
double real_val;
int enum_val;
@@ -94,6 +96,12 @@ typedef struct relopt_bool
bool default_val;
} relopt_bool;
+typedef struct relopt_rernary
+{
+ relopt_gen gen;
+ int default_val;
+} relopt_ternary;
+
typedef struct relopt_int
{
relopt_gen gen;
@@ -152,19 +160,6 @@ typedef struct
const char *optname; /* option's name */
relopt_type opttype; /* option's datatype */
int offset; /* offset of field in result struct */
-
- /*
- * isset_offset is an optional offset of a field in the result struct that
- * stores whether the option is explicitly set for the relation or if it
- * just picked up the default value. In most cases, this can be
- * accomplished by giving the reloption a special out-of-range default
- * value (e.g., some integer reloptions use -2), but this isn't always
- * possible. For example, a Boolean reloption cannot be given an
- * out-of-range default, so we need another way to discover the source of
- * its value. This offset is only used if given a value greater than
- * zero.
- */
- int isset_offset;
} relopt_parse_elt;
/* Local reloption definition */
@@ -195,6 +190,8 @@ typedef struct local_relopts
extern relopt_kind add_reloption_kind(void);
extern void add_bool_reloption(bits32 kinds, const char *name, const char *desc,
bool default_val, LOCKMODE lockmode);
+extern void add_ternary_reloption(bits32 kinds, const char *name,
+ const char *desc, int default_val, LOCKMODE lockmode);
extern void add_int_reloption(bits32 kinds, const char *name, const char *desc,
int default_val, int min_val, int max_val,
LOCKMODE lockmode);
@@ -214,6 +211,9 @@ extern void register_reloptions_validator(local_relopts *relopts,
extern void add_local_bool_reloption(local_relopts *relopts, const char *name,
const char *desc, bool default_val,
int offset);
+extern void add_local_ternary_reloption(local_relopts *relopts,
+ const char *name, const char *desc,
+ ternary default_val, int offset);
extern void add_local_int_reloption(local_relopts *relopts, const char *name,
const char *desc, int default_val,
int min_val, int max_val, int offset);
diff --git a/src/include/c.h b/src/include/c.h
index 39022f8a9d..9c59ccccf8 100644
--- a/src/include/c.h
+++ b/src/include/c.h
@@ -488,6 +488,22 @@ typedef void (*pg_funcptr_t) (void);
#include <stdbool.h>
+/*
+ * ternary
+ * Boolean value with an extrea "unset" option
+ *
+ * Ternary data type is used in relation options that can be "true", "false" or
+ * "unset". Since relation options are used deep inside the PostgreSQL code,
+ * this type is declared globally.
+*/
+
+typedef enum ternary
+{
+ TERNARY_FALSE = 0,
+ TERNARY_TRUE = 1,
+ TERNARY_UNSET = -1
+} ternary;
+
/* ----------------------------------------------------------------
* Section 3: standard system types
diff --git a/src/include/utils/rel.h b/src/include/utils/rel.h
index b552359915..7346d618f9 100644
--- a/src/include/utils/rel.h
+++ b/src/include/utils/rel.h
@@ -346,8 +346,7 @@ typedef struct StdRdOptions
bool user_catalog_table; /* use as an additional catalog relation */
int parallel_workers; /* max number of parallel workers */
StdRdOptIndexCleanup vacuum_index_cleanup; /* controls index vacuuming */
- bool vacuum_truncate; /* enables vacuum to truncate a relation */
- bool vacuum_truncate_set; /* whether vacuum_truncate is set */
+ ternary vacuum_truncate; /* enables vacuum to truncate a relation */
/*
* Fraction of pages in a relation that vacuum can eagerly scan and fail
--
2.39.2
--nextPart4155600.3ZeAukHxDK
Content-Disposition: attachment;
filename="v2a-0003-Add-alias-to-be-used-as-unset-state.patch"
Content-Transfer-Encoding: 7Bit
Content-Type: text/x-patch; charset="unicode-2-0-utf-8";
name="v2a-0003-Add-alias-to-be-used-as-unset-state.patch"
^ permalink raw reply [nested|flat] 868+ messages in thread
* [PATCH v2 2/4] Introduce ternary reloptions
@ 2025-09-04 14:41 Nikolay Shaplov <[email protected]>
0 siblings, 0 replies; 868+ messages in thread
From: Nikolay Shaplov @ 2025-09-04 14:41 UTC (permalink / raw)
Introduce ternary reloption as a replacement for current `vacuum_truncate`
implementation. Remove `vacuum_truncate_set` additional flag and using
`TERNARY_UNSET` value instead.
---
src/backend/access/common/reloptions.c | 129 ++++++++++++++++++++-----
src/backend/commands/vacuum.c | 4 +-
src/include/access/reloptions.h | 26 ++---
src/include/c.h | 16 +++
src/include/utils/rel.h | 3 +-
5 files changed, 135 insertions(+), 43 deletions(-)
diff --git a/src/backend/access/common/reloptions.c b/src/backend/access/common/reloptions.c
index 0af3fea68f..f543f11001 100644
--- a/src/backend/access/common/reloptions.c
+++ b/src/backend/access/common/reloptions.c
@@ -40,9 +40,9 @@
*
* To add an option:
*
- * (i) decide on a type (bool, integer, real, enum, string), name, default
- * value, upper and lower bounds (if applicable); for strings, consider a
- * validation routine.
+ * (i) decide on a type (bool, ternary, integer, real, enum, string), name,
+ * default value, upper and lower bounds (if applicable); for strings,
+ * consider a validation routine.
* (ii) add a record below (or use add_<type>_reloption).
* (iii) add it to the appropriate options struct (perhaps StdRdOptions)
* (iv) add it to the appropriate handling routine (perhaps
@@ -147,15 +147,6 @@ static relopt_bool boolRelOpts[] =
},
false
},
- {
- {
- "vacuum_truncate",
- "Enables vacuum to truncate empty pages at the end of this table",
- RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
- ShareUpdateExclusiveLock
- },
- true
- },
{
{
"deduplicate_items",
@@ -170,6 +161,21 @@ static relopt_bool boolRelOpts[] =
{{NULL}}
};
+static relopt_ternary ternaryRelOpts[] =
+{
+ {
+ {
+ "vacuum_truncate",
+ "Enables vacuum to truncate empty pages at the end of this table",
+ RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
+ ShareUpdateExclusiveLock
+ },
+ TERNARY_UNSET
+ },
+ /* list terminator */
+ {{NULL}}
+};
+
static relopt_int intRelOpts[] =
{
{
@@ -600,6 +606,13 @@ initialize_reloptions(void)
boolRelOpts[i].gen.lockmode));
j++;
}
+ for (i = 0; ternaryRelOpts[i].gen.name; i++)
+ {
+ Assert(DoLockModesConflict(ternaryRelOpts[i].gen.lockmode,
+ ternaryRelOpts[i].gen.lockmode));
+ j++;
+ }
+
for (i = 0; intRelOpts[i].gen.name; i++)
{
Assert(DoLockModesConflict(intRelOpts[i].gen.lockmode,
@@ -640,6 +653,14 @@ initialize_reloptions(void)
j++;
}
+ for (i = 0; ternaryRelOpts[i].gen.name; i++)
+ {
+ relOpts[j] = &ternaryRelOpts[i].gen;
+ relOpts[j]->type = RELOPT_TYPE_TERNARY;
+ relOpts[j]->namelen = strlen(relOpts[j]->name);
+ j++;
+ }
+
for (i = 0; intRelOpts[i].gen.name; i++)
{
relOpts[j] = &intRelOpts[i].gen;
@@ -800,6 +821,9 @@ allocate_reloption(bits32 kinds, int type, const char *name, const char *desc,
case RELOPT_TYPE_BOOL:
size = sizeof(relopt_bool);
break;
+ case RELOPT_TYPE_TERNARY:
+ size = sizeof(relopt_ternary);
+ break;
case RELOPT_TYPE_INT:
size = sizeof(relopt_int);
break;
@@ -883,6 +907,54 @@ add_local_bool_reloption(local_relopts *relopts, const char *name,
add_local_reloption(relopts, (relopt_gen *) newoption, offset);
}
+/*
+ * init_ternary_reloption
+ * Allocate and initialize a new ternary reloption
+ */
+static relopt_ternary *
+init_ternary_reloption(bits32 kinds, const char *name, const char *desc,
+ ternary default_val, LOCKMODE lockmode)
+{
+ relopt_ternary *newoption;
+
+ newoption = (relopt_ternary *) allocate_reloption(kinds,
+ RELOPT_TYPE_TERNARY, name, desc, lockmode);
+ newoption->default_val = default_val;
+
+ return newoption;
+}
+
+/*
+ * add_ternary_reloption
+ * Add a new ternary reloption
+ */
+void
+add_ternary_reloption(bits32 kinds, const char *name, const char *desc,
+ ternary default_val, LOCKMODE lockmode)
+{
+ relopt_ternary *newoption = init_ternary_reloption(kinds, name, desc,
+ default_val, lockmode);
+
+ add_reloption((relopt_gen *) newoption);
+}
+
+/*
+ * add_local_ternary_reloption
+ * Add a new ternary local reloption
+ *
+ * 'offset' is offset of ternary-typed field.
+ */
+void
+add_local_ternary_reloption(local_relopts *relopts, const char *name,
+ const char *desc, ternary default_val,
+ int offset)
+{
+ relopt_ternary *newoption = init_ternary_reloption(RELOPT_KIND_LOCAL,
+ name, desc,
+ default_val, 0);
+
+ add_local_reloption(relopts, (relopt_gen *) newoption, offset);
+}
/*
* init_real_reloption
@@ -1617,6 +1689,18 @@ parse_one_reloption(relopt_value *option, char *text_str, int text_len,
option->gen->name, value)));
}
break;
+ case RELOPT_TYPE_TERNARY:
+ {
+ bool b;
+ parsed = parse_bool(value, &b);
+ option->values.ternary_val = b ? TERNARY_TRUE : TERNARY_FALSE;
+ if (validate && !parsed)
+ ereport(ERROR,
+ (errcode(ERRCODE_INVALID_PARAMETER_VALUE),
+ errmsg("invalid value for ternary option \"%s\": %s",
+ option->gen->name, value)));
+ }
+ break;
case RELOPT_TYPE_INT:
{
relopt_int *optint = (relopt_int *) option->gen;
@@ -1780,17 +1864,6 @@ fillRelOptions(void *rdopts, Size basesize,
char *itempos = ((char *) rdopts) + elems[j].offset;
char *string_val;
- /*
- * If isset_offset is provided, store whether the reloption is
- * set there.
- */
- if (elems[j].isset_offset > 0)
- {
- char *setpos = ((char *) rdopts) + elems[j].isset_offset;
-
- *(bool *) setpos = options[i].isset;
- }
-
switch (options[i].gen->type)
{
case RELOPT_TYPE_BOOL:
@@ -1798,6 +1871,11 @@ fillRelOptions(void *rdopts, Size basesize,
options[i].values.bool_val :
((relopt_bool *) options[i].gen)->default_val;
break;
+ case RELOPT_TYPE_TERNARY:
+ *(ternary *) itempos = options[i].isset ?
+ options[i].values.ternary_val :
+ ((relopt_ternary *) options[i].gen)->default_val;
+ break;
case RELOPT_TYPE_INT:
*(int *) itempos = options[i].isset ?
options[i].values.int_val :
@@ -1912,8 +1990,8 @@ default_reloptions(Datum reloptions, bool validate, relopt_kind kind)
offsetof(StdRdOptions, parallel_workers)},
{"vacuum_index_cleanup", RELOPT_TYPE_ENUM,
offsetof(StdRdOptions, vacuum_index_cleanup)},
- {"vacuum_truncate", RELOPT_TYPE_BOOL,
- offsetof(StdRdOptions, vacuum_truncate), offsetof(StdRdOptions, vacuum_truncate_set)},
+ {"vacuum_truncate", RELOPT_TYPE_TERNARY,
+ offsetof(StdRdOptions, vacuum_truncate)},
{"vacuum_max_eager_freeze_failure_rate", RELOPT_TYPE_REAL,
offsetof(StdRdOptions, vacuum_max_eager_freeze_failure_rate)}
};
@@ -1993,7 +2071,6 @@ build_local_reloptions(local_relopts *relopts, Datum options, bool validate)
elems[i].optname = opt->option->name;
elems[i].opttype = opt->option->type;
elems[i].offset = opt->offset;
- elems[i].isset_offset = 0; /* not supported for local relopts yet */
i++;
}
diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c
index 733ef40ae7..977babff54 100644
--- a/src/backend/commands/vacuum.c
+++ b/src/backend/commands/vacuum.c
@@ -2221,9 +2221,9 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams params,
{
StdRdOptions *opts = (StdRdOptions *) rel->rd_options;
- if (opts && opts->vacuum_truncate_set)
+ if (opts && opts->vacuum_truncate != TERNARY_UNSET)
{
- if (opts->vacuum_truncate)
+ if (opts->vacuum_truncate == TERNARY_TRUE)
params.truncate = VACOPTVALUE_ENABLED;
else
params.truncate = VACOPTVALUE_DISABLED;
diff --git a/src/include/access/reloptions.h b/src/include/access/reloptions.h
index a604a4702c..a436697658 100644
--- a/src/include/access/reloptions.h
+++ b/src/include/access/reloptions.h
@@ -29,6 +29,7 @@
typedef enum relopt_type
{
RELOPT_TYPE_BOOL,
+ RELOPT_TYPE_TERNARY, /* on, off, unset */
RELOPT_TYPE_INT,
RELOPT_TYPE_REAL,
RELOPT_TYPE_ENUM,
@@ -80,6 +81,7 @@ typedef struct relopt_value
union
{
bool bool_val;
+ ternary ternary_val;
int int_val;
double real_val;
int enum_val;
@@ -94,6 +96,12 @@ typedef struct relopt_bool
bool default_val;
} relopt_bool;
+typedef struct relopt_rernary
+{
+ relopt_gen gen;
+ int default_val;
+} relopt_ternary;
+
typedef struct relopt_int
{
relopt_gen gen;
@@ -152,19 +160,6 @@ typedef struct
const char *optname; /* option's name */
relopt_type opttype; /* option's datatype */
int offset; /* offset of field in result struct */
-
- /*
- * isset_offset is an optional offset of a field in the result struct that
- * stores whether the option is explicitly set for the relation or if it
- * just picked up the default value. In most cases, this can be
- * accomplished by giving the reloption a special out-of-range default
- * value (e.g., some integer reloptions use -2), but this isn't always
- * possible. For example, a Boolean reloption cannot be given an
- * out-of-range default, so we need another way to discover the source of
- * its value. This offset is only used if given a value greater than
- * zero.
- */
- int isset_offset;
} relopt_parse_elt;
/* Local reloption definition */
@@ -195,6 +190,8 @@ typedef struct local_relopts
extern relopt_kind add_reloption_kind(void);
extern void add_bool_reloption(bits32 kinds, const char *name, const char *desc,
bool default_val, LOCKMODE lockmode);
+extern void add_ternary_reloption(bits32 kinds, const char *name,
+ const char *desc, int default_val, LOCKMODE lockmode);
extern void add_int_reloption(bits32 kinds, const char *name, const char *desc,
int default_val, int min_val, int max_val,
LOCKMODE lockmode);
@@ -214,6 +211,9 @@ extern void register_reloptions_validator(local_relopts *relopts,
extern void add_local_bool_reloption(local_relopts *relopts, const char *name,
const char *desc, bool default_val,
int offset);
+extern void add_local_ternary_reloption(local_relopts *relopts,
+ const char *name, const char *desc,
+ ternary default_val, int offset);
extern void add_local_int_reloption(local_relopts *relopts, const char *name,
const char *desc, int default_val,
int min_val, int max_val, int offset);
diff --git a/src/include/c.h b/src/include/c.h
index 39022f8a9d..9c59ccccf8 100644
--- a/src/include/c.h
+++ b/src/include/c.h
@@ -488,6 +488,22 @@ typedef void (*pg_funcptr_t) (void);
#include <stdbool.h>
+/*
+ * ternary
+ * Boolean value with an extrea "unset" option
+ *
+ * Ternary data type is used in relation options that can be "true", "false" or
+ * "unset". Since relation options are used deep inside the PostgreSQL code,
+ * this type is declared globally.
+*/
+
+typedef enum ternary
+{
+ TERNARY_FALSE = 0,
+ TERNARY_TRUE = 1,
+ TERNARY_UNSET = -1
+} ternary;
+
/* ----------------------------------------------------------------
* Section 3: standard system types
diff --git a/src/include/utils/rel.h b/src/include/utils/rel.h
index b552359915..7346d618f9 100644
--- a/src/include/utils/rel.h
+++ b/src/include/utils/rel.h
@@ -346,8 +346,7 @@ typedef struct StdRdOptions
bool user_catalog_table; /* use as an additional catalog relation */
int parallel_workers; /* max number of parallel workers */
StdRdOptIndexCleanup vacuum_index_cleanup; /* controls index vacuuming */
- bool vacuum_truncate; /* enables vacuum to truncate a relation */
- bool vacuum_truncate_set; /* whether vacuum_truncate is set */
+ ternary vacuum_truncate; /* enables vacuum to truncate a relation */
/*
* Fraction of pages in a relation that vacuum can eagerly scan and fail
--
2.39.2
--nextPart5129932.5fSG56mABF
Content-Disposition: attachment;
filename="v2-0003-Add-alias-to-be-used-as-unset-state.patch"
Content-Transfer-Encoding: 7Bit
Content-Type: text/x-patch; charset="unicode-2-0-utf-8";
name="v2-0003-Add-alias-to-be-used-as-unset-state.patch"
^ permalink raw reply [nested|flat] 868+ messages in thread
* [PATCH v2 2/4] Introduce ternary reloptions
@ 2025-09-04 14:41 Nikolay Shaplov <[email protected]>
0 siblings, 0 replies; 868+ messages in thread
From: Nikolay Shaplov @ 2025-09-04 14:41 UTC (permalink / raw)
Introduce ternary reloption as a replacement for current `vacuum_truncate`
implementation. Remove `vacuum_truncate_set` additional flag and using
`TERNARY_UNSET` value instead.
---
src/backend/access/common/reloptions.c | 129 ++++++++++++++++++++-----
src/backend/commands/vacuum.c | 4 +-
src/include/access/reloptions.h | 26 ++---
src/include/c.h | 16 +++
src/include/utils/rel.h | 3 +-
5 files changed, 135 insertions(+), 43 deletions(-)
diff --git a/src/backend/access/common/reloptions.c b/src/backend/access/common/reloptions.c
index 0af3fea68f..f543f11001 100644
--- a/src/backend/access/common/reloptions.c
+++ b/src/backend/access/common/reloptions.c
@@ -40,9 +40,9 @@
*
* To add an option:
*
- * (i) decide on a type (bool, integer, real, enum, string), name, default
- * value, upper and lower bounds (if applicable); for strings, consider a
- * validation routine.
+ * (i) decide on a type (bool, ternary, integer, real, enum, string), name,
+ * default value, upper and lower bounds (if applicable); for strings,
+ * consider a validation routine.
* (ii) add a record below (or use add_<type>_reloption).
* (iii) add it to the appropriate options struct (perhaps StdRdOptions)
* (iv) add it to the appropriate handling routine (perhaps
@@ -147,15 +147,6 @@ static relopt_bool boolRelOpts[] =
},
false
},
- {
- {
- "vacuum_truncate",
- "Enables vacuum to truncate empty pages at the end of this table",
- RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
- ShareUpdateExclusiveLock
- },
- true
- },
{
{
"deduplicate_items",
@@ -170,6 +161,21 @@ static relopt_bool boolRelOpts[] =
{{NULL}}
};
+static relopt_ternary ternaryRelOpts[] =
+{
+ {
+ {
+ "vacuum_truncate",
+ "Enables vacuum to truncate empty pages at the end of this table",
+ RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
+ ShareUpdateExclusiveLock
+ },
+ TERNARY_UNSET
+ },
+ /* list terminator */
+ {{NULL}}
+};
+
static relopt_int intRelOpts[] =
{
{
@@ -600,6 +606,13 @@ initialize_reloptions(void)
boolRelOpts[i].gen.lockmode));
j++;
}
+ for (i = 0; ternaryRelOpts[i].gen.name; i++)
+ {
+ Assert(DoLockModesConflict(ternaryRelOpts[i].gen.lockmode,
+ ternaryRelOpts[i].gen.lockmode));
+ j++;
+ }
+
for (i = 0; intRelOpts[i].gen.name; i++)
{
Assert(DoLockModesConflict(intRelOpts[i].gen.lockmode,
@@ -640,6 +653,14 @@ initialize_reloptions(void)
j++;
}
+ for (i = 0; ternaryRelOpts[i].gen.name; i++)
+ {
+ relOpts[j] = &ternaryRelOpts[i].gen;
+ relOpts[j]->type = RELOPT_TYPE_TERNARY;
+ relOpts[j]->namelen = strlen(relOpts[j]->name);
+ j++;
+ }
+
for (i = 0; intRelOpts[i].gen.name; i++)
{
relOpts[j] = &intRelOpts[i].gen;
@@ -800,6 +821,9 @@ allocate_reloption(bits32 kinds, int type, const char *name, const char *desc,
case RELOPT_TYPE_BOOL:
size = sizeof(relopt_bool);
break;
+ case RELOPT_TYPE_TERNARY:
+ size = sizeof(relopt_ternary);
+ break;
case RELOPT_TYPE_INT:
size = sizeof(relopt_int);
break;
@@ -883,6 +907,54 @@ add_local_bool_reloption(local_relopts *relopts, const char *name,
add_local_reloption(relopts, (relopt_gen *) newoption, offset);
}
+/*
+ * init_ternary_reloption
+ * Allocate and initialize a new ternary reloption
+ */
+static relopt_ternary *
+init_ternary_reloption(bits32 kinds, const char *name, const char *desc,
+ ternary default_val, LOCKMODE lockmode)
+{
+ relopt_ternary *newoption;
+
+ newoption = (relopt_ternary *) allocate_reloption(kinds,
+ RELOPT_TYPE_TERNARY, name, desc, lockmode);
+ newoption->default_val = default_val;
+
+ return newoption;
+}
+
+/*
+ * add_ternary_reloption
+ * Add a new ternary reloption
+ */
+void
+add_ternary_reloption(bits32 kinds, const char *name, const char *desc,
+ ternary default_val, LOCKMODE lockmode)
+{
+ relopt_ternary *newoption = init_ternary_reloption(kinds, name, desc,
+ default_val, lockmode);
+
+ add_reloption((relopt_gen *) newoption);
+}
+
+/*
+ * add_local_ternary_reloption
+ * Add a new ternary local reloption
+ *
+ * 'offset' is offset of ternary-typed field.
+ */
+void
+add_local_ternary_reloption(local_relopts *relopts, const char *name,
+ const char *desc, ternary default_val,
+ int offset)
+{
+ relopt_ternary *newoption = init_ternary_reloption(RELOPT_KIND_LOCAL,
+ name, desc,
+ default_val, 0);
+
+ add_local_reloption(relopts, (relopt_gen *) newoption, offset);
+}
/*
* init_real_reloption
@@ -1617,6 +1689,18 @@ parse_one_reloption(relopt_value *option, char *text_str, int text_len,
option->gen->name, value)));
}
break;
+ case RELOPT_TYPE_TERNARY:
+ {
+ bool b;
+ parsed = parse_bool(value, &b);
+ option->values.ternary_val = b ? TERNARY_TRUE : TERNARY_FALSE;
+ if (validate && !parsed)
+ ereport(ERROR,
+ (errcode(ERRCODE_INVALID_PARAMETER_VALUE),
+ errmsg("invalid value for ternary option \"%s\": %s",
+ option->gen->name, value)));
+ }
+ break;
case RELOPT_TYPE_INT:
{
relopt_int *optint = (relopt_int *) option->gen;
@@ -1780,17 +1864,6 @@ fillRelOptions(void *rdopts, Size basesize,
char *itempos = ((char *) rdopts) + elems[j].offset;
char *string_val;
- /*
- * If isset_offset is provided, store whether the reloption is
- * set there.
- */
- if (elems[j].isset_offset > 0)
- {
- char *setpos = ((char *) rdopts) + elems[j].isset_offset;
-
- *(bool *) setpos = options[i].isset;
- }
-
switch (options[i].gen->type)
{
case RELOPT_TYPE_BOOL:
@@ -1798,6 +1871,11 @@ fillRelOptions(void *rdopts, Size basesize,
options[i].values.bool_val :
((relopt_bool *) options[i].gen)->default_val;
break;
+ case RELOPT_TYPE_TERNARY:
+ *(ternary *) itempos = options[i].isset ?
+ options[i].values.ternary_val :
+ ((relopt_ternary *) options[i].gen)->default_val;
+ break;
case RELOPT_TYPE_INT:
*(int *) itempos = options[i].isset ?
options[i].values.int_val :
@@ -1912,8 +1990,8 @@ default_reloptions(Datum reloptions, bool validate, relopt_kind kind)
offsetof(StdRdOptions, parallel_workers)},
{"vacuum_index_cleanup", RELOPT_TYPE_ENUM,
offsetof(StdRdOptions, vacuum_index_cleanup)},
- {"vacuum_truncate", RELOPT_TYPE_BOOL,
- offsetof(StdRdOptions, vacuum_truncate), offsetof(StdRdOptions, vacuum_truncate_set)},
+ {"vacuum_truncate", RELOPT_TYPE_TERNARY,
+ offsetof(StdRdOptions, vacuum_truncate)},
{"vacuum_max_eager_freeze_failure_rate", RELOPT_TYPE_REAL,
offsetof(StdRdOptions, vacuum_max_eager_freeze_failure_rate)}
};
@@ -1993,7 +2071,6 @@ build_local_reloptions(local_relopts *relopts, Datum options, bool validate)
elems[i].optname = opt->option->name;
elems[i].opttype = opt->option->type;
elems[i].offset = opt->offset;
- elems[i].isset_offset = 0; /* not supported for local relopts yet */
i++;
}
diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c
index 733ef40ae7..977babff54 100644
--- a/src/backend/commands/vacuum.c
+++ b/src/backend/commands/vacuum.c
@@ -2221,9 +2221,9 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams params,
{
StdRdOptions *opts = (StdRdOptions *) rel->rd_options;
- if (opts && opts->vacuum_truncate_set)
+ if (opts && opts->vacuum_truncate != TERNARY_UNSET)
{
- if (opts->vacuum_truncate)
+ if (opts->vacuum_truncate == TERNARY_TRUE)
params.truncate = VACOPTVALUE_ENABLED;
else
params.truncate = VACOPTVALUE_DISABLED;
diff --git a/src/include/access/reloptions.h b/src/include/access/reloptions.h
index a604a4702c..a436697658 100644
--- a/src/include/access/reloptions.h
+++ b/src/include/access/reloptions.h
@@ -29,6 +29,7 @@
typedef enum relopt_type
{
RELOPT_TYPE_BOOL,
+ RELOPT_TYPE_TERNARY, /* on, off, unset */
RELOPT_TYPE_INT,
RELOPT_TYPE_REAL,
RELOPT_TYPE_ENUM,
@@ -80,6 +81,7 @@ typedef struct relopt_value
union
{
bool bool_val;
+ ternary ternary_val;
int int_val;
double real_val;
int enum_val;
@@ -94,6 +96,12 @@ typedef struct relopt_bool
bool default_val;
} relopt_bool;
+typedef struct relopt_rernary
+{
+ relopt_gen gen;
+ int default_val;
+} relopt_ternary;
+
typedef struct relopt_int
{
relopt_gen gen;
@@ -152,19 +160,6 @@ typedef struct
const char *optname; /* option's name */
relopt_type opttype; /* option's datatype */
int offset; /* offset of field in result struct */
-
- /*
- * isset_offset is an optional offset of a field in the result struct that
- * stores whether the option is explicitly set for the relation or if it
- * just picked up the default value. In most cases, this can be
- * accomplished by giving the reloption a special out-of-range default
- * value (e.g., some integer reloptions use -2), but this isn't always
- * possible. For example, a Boolean reloption cannot be given an
- * out-of-range default, so we need another way to discover the source of
- * its value. This offset is only used if given a value greater than
- * zero.
- */
- int isset_offset;
} relopt_parse_elt;
/* Local reloption definition */
@@ -195,6 +190,8 @@ typedef struct local_relopts
extern relopt_kind add_reloption_kind(void);
extern void add_bool_reloption(bits32 kinds, const char *name, const char *desc,
bool default_val, LOCKMODE lockmode);
+extern void add_ternary_reloption(bits32 kinds, const char *name,
+ const char *desc, int default_val, LOCKMODE lockmode);
extern void add_int_reloption(bits32 kinds, const char *name, const char *desc,
int default_val, int min_val, int max_val,
LOCKMODE lockmode);
@@ -214,6 +211,9 @@ extern void register_reloptions_validator(local_relopts *relopts,
extern void add_local_bool_reloption(local_relopts *relopts, const char *name,
const char *desc, bool default_val,
int offset);
+extern void add_local_ternary_reloption(local_relopts *relopts,
+ const char *name, const char *desc,
+ ternary default_val, int offset);
extern void add_local_int_reloption(local_relopts *relopts, const char *name,
const char *desc, int default_val,
int min_val, int max_val, int offset);
diff --git a/src/include/c.h b/src/include/c.h
index 39022f8a9d..9c59ccccf8 100644
--- a/src/include/c.h
+++ b/src/include/c.h
@@ -488,6 +488,22 @@ typedef void (*pg_funcptr_t) (void);
#include <stdbool.h>
+/*
+ * ternary
+ * Boolean value with an extrea "unset" option
+ *
+ * Ternary data type is used in relation options that can be "true", "false" or
+ * "unset". Since relation options are used deep inside the PostgreSQL code,
+ * this type is declared globally.
+*/
+
+typedef enum ternary
+{
+ TERNARY_FALSE = 0,
+ TERNARY_TRUE = 1,
+ TERNARY_UNSET = -1
+} ternary;
+
/* ----------------------------------------------------------------
* Section 3: standard system types
diff --git a/src/include/utils/rel.h b/src/include/utils/rel.h
index b552359915..7346d618f9 100644
--- a/src/include/utils/rel.h
+++ b/src/include/utils/rel.h
@@ -346,8 +346,7 @@ typedef struct StdRdOptions
bool user_catalog_table; /* use as an additional catalog relation */
int parallel_workers; /* max number of parallel workers */
StdRdOptIndexCleanup vacuum_index_cleanup; /* controls index vacuuming */
- bool vacuum_truncate; /* enables vacuum to truncate a relation */
- bool vacuum_truncate_set; /* whether vacuum_truncate is set */
+ ternary vacuum_truncate; /* enables vacuum to truncate a relation */
/*
* Fraction of pages in a relation that vacuum can eagerly scan and fail
--
2.39.2
--nextPart5129932.5fSG56mABF
Content-Disposition: attachment;
filename="v2-0003-Add-alias-to-be-used-as-unset-state.patch"
Content-Transfer-Encoding: 7Bit
Content-Type: text/x-patch; charset="unicode-2-0-utf-8";
name="v2-0003-Add-alias-to-be-used-as-unset-state.patch"
^ permalink raw reply [nested|flat] 868+ messages in thread
* [PATCH v2 2/4] Introduce ternary reloptions
@ 2025-09-04 14:41 Nikolay Shaplov <[email protected]>
0 siblings, 0 replies; 868+ messages in thread
From: Nikolay Shaplov @ 2025-09-04 14:41 UTC (permalink / raw)
Introduce ternary reloption as a replacement for current `vacuum_truncate`
implementation. Remove `vacuum_truncate_set` additional flag and using
`TERNARY_UNSET` value instead.
---
src/backend/access/common/reloptions.c | 129 ++++++++++++++++++++-----
src/backend/commands/vacuum.c | 4 +-
src/include/access/reloptions.h | 26 ++---
src/include/c.h | 16 +++
src/include/utils/rel.h | 3 +-
5 files changed, 135 insertions(+), 43 deletions(-)
diff --git a/src/backend/access/common/reloptions.c b/src/backend/access/common/reloptions.c
index 0af3fea68f..f543f11001 100644
--- a/src/backend/access/common/reloptions.c
+++ b/src/backend/access/common/reloptions.c
@@ -40,9 +40,9 @@
*
* To add an option:
*
- * (i) decide on a type (bool, integer, real, enum, string), name, default
- * value, upper and lower bounds (if applicable); for strings, consider a
- * validation routine.
+ * (i) decide on a type (bool, ternary, integer, real, enum, string), name,
+ * default value, upper and lower bounds (if applicable); for strings,
+ * consider a validation routine.
* (ii) add a record below (or use add_<type>_reloption).
* (iii) add it to the appropriate options struct (perhaps StdRdOptions)
* (iv) add it to the appropriate handling routine (perhaps
@@ -147,15 +147,6 @@ static relopt_bool boolRelOpts[] =
},
false
},
- {
- {
- "vacuum_truncate",
- "Enables vacuum to truncate empty pages at the end of this table",
- RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
- ShareUpdateExclusiveLock
- },
- true
- },
{
{
"deduplicate_items",
@@ -170,6 +161,21 @@ static relopt_bool boolRelOpts[] =
{{NULL}}
};
+static relopt_ternary ternaryRelOpts[] =
+{
+ {
+ {
+ "vacuum_truncate",
+ "Enables vacuum to truncate empty pages at the end of this table",
+ RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
+ ShareUpdateExclusiveLock
+ },
+ TERNARY_UNSET
+ },
+ /* list terminator */
+ {{NULL}}
+};
+
static relopt_int intRelOpts[] =
{
{
@@ -600,6 +606,13 @@ initialize_reloptions(void)
boolRelOpts[i].gen.lockmode));
j++;
}
+ for (i = 0; ternaryRelOpts[i].gen.name; i++)
+ {
+ Assert(DoLockModesConflict(ternaryRelOpts[i].gen.lockmode,
+ ternaryRelOpts[i].gen.lockmode));
+ j++;
+ }
+
for (i = 0; intRelOpts[i].gen.name; i++)
{
Assert(DoLockModesConflict(intRelOpts[i].gen.lockmode,
@@ -640,6 +653,14 @@ initialize_reloptions(void)
j++;
}
+ for (i = 0; ternaryRelOpts[i].gen.name; i++)
+ {
+ relOpts[j] = &ternaryRelOpts[i].gen;
+ relOpts[j]->type = RELOPT_TYPE_TERNARY;
+ relOpts[j]->namelen = strlen(relOpts[j]->name);
+ j++;
+ }
+
for (i = 0; intRelOpts[i].gen.name; i++)
{
relOpts[j] = &intRelOpts[i].gen;
@@ -800,6 +821,9 @@ allocate_reloption(bits32 kinds, int type, const char *name, const char *desc,
case RELOPT_TYPE_BOOL:
size = sizeof(relopt_bool);
break;
+ case RELOPT_TYPE_TERNARY:
+ size = sizeof(relopt_ternary);
+ break;
case RELOPT_TYPE_INT:
size = sizeof(relopt_int);
break;
@@ -883,6 +907,54 @@ add_local_bool_reloption(local_relopts *relopts, const char *name,
add_local_reloption(relopts, (relopt_gen *) newoption, offset);
}
+/*
+ * init_ternary_reloption
+ * Allocate and initialize a new ternary reloption
+ */
+static relopt_ternary *
+init_ternary_reloption(bits32 kinds, const char *name, const char *desc,
+ ternary default_val, LOCKMODE lockmode)
+{
+ relopt_ternary *newoption;
+
+ newoption = (relopt_ternary *) allocate_reloption(kinds,
+ RELOPT_TYPE_TERNARY, name, desc, lockmode);
+ newoption->default_val = default_val;
+
+ return newoption;
+}
+
+/*
+ * add_ternary_reloption
+ * Add a new ternary reloption
+ */
+void
+add_ternary_reloption(bits32 kinds, const char *name, const char *desc,
+ ternary default_val, LOCKMODE lockmode)
+{
+ relopt_ternary *newoption = init_ternary_reloption(kinds, name, desc,
+ default_val, lockmode);
+
+ add_reloption((relopt_gen *) newoption);
+}
+
+/*
+ * add_local_ternary_reloption
+ * Add a new ternary local reloption
+ *
+ * 'offset' is offset of ternary-typed field.
+ */
+void
+add_local_ternary_reloption(local_relopts *relopts, const char *name,
+ const char *desc, ternary default_val,
+ int offset)
+{
+ relopt_ternary *newoption = init_ternary_reloption(RELOPT_KIND_LOCAL,
+ name, desc,
+ default_val, 0);
+
+ add_local_reloption(relopts, (relopt_gen *) newoption, offset);
+}
/*
* init_real_reloption
@@ -1617,6 +1689,18 @@ parse_one_reloption(relopt_value *option, char *text_str, int text_len,
option->gen->name, value)));
}
break;
+ case RELOPT_TYPE_TERNARY:
+ {
+ bool b;
+ parsed = parse_bool(value, &b);
+ option->values.ternary_val = b ? TERNARY_TRUE : TERNARY_FALSE;
+ if (validate && !parsed)
+ ereport(ERROR,
+ (errcode(ERRCODE_INVALID_PARAMETER_VALUE),
+ errmsg("invalid value for ternary option \"%s\": %s",
+ option->gen->name, value)));
+ }
+ break;
case RELOPT_TYPE_INT:
{
relopt_int *optint = (relopt_int *) option->gen;
@@ -1780,17 +1864,6 @@ fillRelOptions(void *rdopts, Size basesize,
char *itempos = ((char *) rdopts) + elems[j].offset;
char *string_val;
- /*
- * If isset_offset is provided, store whether the reloption is
- * set there.
- */
- if (elems[j].isset_offset > 0)
- {
- char *setpos = ((char *) rdopts) + elems[j].isset_offset;
-
- *(bool *) setpos = options[i].isset;
- }
-
switch (options[i].gen->type)
{
case RELOPT_TYPE_BOOL:
@@ -1798,6 +1871,11 @@ fillRelOptions(void *rdopts, Size basesize,
options[i].values.bool_val :
((relopt_bool *) options[i].gen)->default_val;
break;
+ case RELOPT_TYPE_TERNARY:
+ *(ternary *) itempos = options[i].isset ?
+ options[i].values.ternary_val :
+ ((relopt_ternary *) options[i].gen)->default_val;
+ break;
case RELOPT_TYPE_INT:
*(int *) itempos = options[i].isset ?
options[i].values.int_val :
@@ -1912,8 +1990,8 @@ default_reloptions(Datum reloptions, bool validate, relopt_kind kind)
offsetof(StdRdOptions, parallel_workers)},
{"vacuum_index_cleanup", RELOPT_TYPE_ENUM,
offsetof(StdRdOptions, vacuum_index_cleanup)},
- {"vacuum_truncate", RELOPT_TYPE_BOOL,
- offsetof(StdRdOptions, vacuum_truncate), offsetof(StdRdOptions, vacuum_truncate_set)},
+ {"vacuum_truncate", RELOPT_TYPE_TERNARY,
+ offsetof(StdRdOptions, vacuum_truncate)},
{"vacuum_max_eager_freeze_failure_rate", RELOPT_TYPE_REAL,
offsetof(StdRdOptions, vacuum_max_eager_freeze_failure_rate)}
};
@@ -1993,7 +2071,6 @@ build_local_reloptions(local_relopts *relopts, Datum options, bool validate)
elems[i].optname = opt->option->name;
elems[i].opttype = opt->option->type;
elems[i].offset = opt->offset;
- elems[i].isset_offset = 0; /* not supported for local relopts yet */
i++;
}
diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c
index 733ef40ae7..977babff54 100644
--- a/src/backend/commands/vacuum.c
+++ b/src/backend/commands/vacuum.c
@@ -2221,9 +2221,9 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams params,
{
StdRdOptions *opts = (StdRdOptions *) rel->rd_options;
- if (opts && opts->vacuum_truncate_set)
+ if (opts && opts->vacuum_truncate != TERNARY_UNSET)
{
- if (opts->vacuum_truncate)
+ if (opts->vacuum_truncate == TERNARY_TRUE)
params.truncate = VACOPTVALUE_ENABLED;
else
params.truncate = VACOPTVALUE_DISABLED;
diff --git a/src/include/access/reloptions.h b/src/include/access/reloptions.h
index a604a4702c..a436697658 100644
--- a/src/include/access/reloptions.h
+++ b/src/include/access/reloptions.h
@@ -29,6 +29,7 @@
typedef enum relopt_type
{
RELOPT_TYPE_BOOL,
+ RELOPT_TYPE_TERNARY, /* on, off, unset */
RELOPT_TYPE_INT,
RELOPT_TYPE_REAL,
RELOPT_TYPE_ENUM,
@@ -80,6 +81,7 @@ typedef struct relopt_value
union
{
bool bool_val;
+ ternary ternary_val;
int int_val;
double real_val;
int enum_val;
@@ -94,6 +96,12 @@ typedef struct relopt_bool
bool default_val;
} relopt_bool;
+typedef struct relopt_rernary
+{
+ relopt_gen gen;
+ int default_val;
+} relopt_ternary;
+
typedef struct relopt_int
{
relopt_gen gen;
@@ -152,19 +160,6 @@ typedef struct
const char *optname; /* option's name */
relopt_type opttype; /* option's datatype */
int offset; /* offset of field in result struct */
-
- /*
- * isset_offset is an optional offset of a field in the result struct that
- * stores whether the option is explicitly set for the relation or if it
- * just picked up the default value. In most cases, this can be
- * accomplished by giving the reloption a special out-of-range default
- * value (e.g., some integer reloptions use -2), but this isn't always
- * possible. For example, a Boolean reloption cannot be given an
- * out-of-range default, so we need another way to discover the source of
- * its value. This offset is only used if given a value greater than
- * zero.
- */
- int isset_offset;
} relopt_parse_elt;
/* Local reloption definition */
@@ -195,6 +190,8 @@ typedef struct local_relopts
extern relopt_kind add_reloption_kind(void);
extern void add_bool_reloption(bits32 kinds, const char *name, const char *desc,
bool default_val, LOCKMODE lockmode);
+extern void add_ternary_reloption(bits32 kinds, const char *name,
+ const char *desc, int default_val, LOCKMODE lockmode);
extern void add_int_reloption(bits32 kinds, const char *name, const char *desc,
int default_val, int min_val, int max_val,
LOCKMODE lockmode);
@@ -214,6 +211,9 @@ extern void register_reloptions_validator(local_relopts *relopts,
extern void add_local_bool_reloption(local_relopts *relopts, const char *name,
const char *desc, bool default_val,
int offset);
+extern void add_local_ternary_reloption(local_relopts *relopts,
+ const char *name, const char *desc,
+ ternary default_val, int offset);
extern void add_local_int_reloption(local_relopts *relopts, const char *name,
const char *desc, int default_val,
int min_val, int max_val, int offset);
diff --git a/src/include/c.h b/src/include/c.h
index 39022f8a9d..9c59ccccf8 100644
--- a/src/include/c.h
+++ b/src/include/c.h
@@ -488,6 +488,22 @@ typedef void (*pg_funcptr_t) (void);
#include <stdbool.h>
+/*
+ * ternary
+ * Boolean value with an extrea "unset" option
+ *
+ * Ternary data type is used in relation options that can be "true", "false" or
+ * "unset". Since relation options are used deep inside the PostgreSQL code,
+ * this type is declared globally.
+*/
+
+typedef enum ternary
+{
+ TERNARY_FALSE = 0,
+ TERNARY_TRUE = 1,
+ TERNARY_UNSET = -1
+} ternary;
+
/* ----------------------------------------------------------------
* Section 3: standard system types
diff --git a/src/include/utils/rel.h b/src/include/utils/rel.h
index b552359915..7346d618f9 100644
--- a/src/include/utils/rel.h
+++ b/src/include/utils/rel.h
@@ -346,8 +346,7 @@ typedef struct StdRdOptions
bool user_catalog_table; /* use as an additional catalog relation */
int parallel_workers; /* max number of parallel workers */
StdRdOptIndexCleanup vacuum_index_cleanup; /* controls index vacuuming */
- bool vacuum_truncate; /* enables vacuum to truncate a relation */
- bool vacuum_truncate_set; /* whether vacuum_truncate is set */
+ ternary vacuum_truncate; /* enables vacuum to truncate a relation */
/*
* Fraction of pages in a relation that vacuum can eagerly scan and fail
--
2.39.2
--nextPart5129932.5fSG56mABF
Content-Disposition: attachment;
filename="v2-0003-Add-alias-to-be-used-as-unset-state.patch"
Content-Transfer-Encoding: 7Bit
Content-Type: text/x-patch; charset="unicode-2-0-utf-8";
name="v2-0003-Add-alias-to-be-used-as-unset-state.patch"
^ permalink raw reply [nested|flat] 868+ messages in thread
* [PATCH v2a 2/4] Introduce ternary reloptions
@ 2025-09-04 14:41 Nikolay Shaplov <[email protected]>
0 siblings, 0 replies; 868+ messages in thread
From: Nikolay Shaplov @ 2025-09-04 14:41 UTC (permalink / raw)
Introduce ternary reloption as a replacement for current `vacuum_truncate`
implementation. Remove `vacuum_truncate_set` additional flag and using
`TERNARY_UNSET` value instead.
---
src/backend/access/common/reloptions.c | 129 ++++++++++++++++++++-----
src/backend/commands/vacuum.c | 4 +-
src/include/access/reloptions.h | 26 ++---
src/include/c.h | 16 +++
src/include/utils/rel.h | 3 +-
5 files changed, 135 insertions(+), 43 deletions(-)
diff --git a/src/backend/access/common/reloptions.c b/src/backend/access/common/reloptions.c
index 0af3fea68f..f543f11001 100644
--- a/src/backend/access/common/reloptions.c
+++ b/src/backend/access/common/reloptions.c
@@ -40,9 +40,9 @@
*
* To add an option:
*
- * (i) decide on a type (bool, integer, real, enum, string), name, default
- * value, upper and lower bounds (if applicable); for strings, consider a
- * validation routine.
+ * (i) decide on a type (bool, ternary, integer, real, enum, string), name,
+ * default value, upper and lower bounds (if applicable); for strings,
+ * consider a validation routine.
* (ii) add a record below (or use add_<type>_reloption).
* (iii) add it to the appropriate options struct (perhaps StdRdOptions)
* (iv) add it to the appropriate handling routine (perhaps
@@ -147,15 +147,6 @@ static relopt_bool boolRelOpts[] =
},
false
},
- {
- {
- "vacuum_truncate",
- "Enables vacuum to truncate empty pages at the end of this table",
- RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
- ShareUpdateExclusiveLock
- },
- true
- },
{
{
"deduplicate_items",
@@ -170,6 +161,21 @@ static relopt_bool boolRelOpts[] =
{{NULL}}
};
+static relopt_ternary ternaryRelOpts[] =
+{
+ {
+ {
+ "vacuum_truncate",
+ "Enables vacuum to truncate empty pages at the end of this table",
+ RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
+ ShareUpdateExclusiveLock
+ },
+ TERNARY_UNSET
+ },
+ /* list terminator */
+ {{NULL}}
+};
+
static relopt_int intRelOpts[] =
{
{
@@ -600,6 +606,13 @@ initialize_reloptions(void)
boolRelOpts[i].gen.lockmode));
j++;
}
+ for (i = 0; ternaryRelOpts[i].gen.name; i++)
+ {
+ Assert(DoLockModesConflict(ternaryRelOpts[i].gen.lockmode,
+ ternaryRelOpts[i].gen.lockmode));
+ j++;
+ }
+
for (i = 0; intRelOpts[i].gen.name; i++)
{
Assert(DoLockModesConflict(intRelOpts[i].gen.lockmode,
@@ -640,6 +653,14 @@ initialize_reloptions(void)
j++;
}
+ for (i = 0; ternaryRelOpts[i].gen.name; i++)
+ {
+ relOpts[j] = &ternaryRelOpts[i].gen;
+ relOpts[j]->type = RELOPT_TYPE_TERNARY;
+ relOpts[j]->namelen = strlen(relOpts[j]->name);
+ j++;
+ }
+
for (i = 0; intRelOpts[i].gen.name; i++)
{
relOpts[j] = &intRelOpts[i].gen;
@@ -800,6 +821,9 @@ allocate_reloption(bits32 kinds, int type, const char *name, const char *desc,
case RELOPT_TYPE_BOOL:
size = sizeof(relopt_bool);
break;
+ case RELOPT_TYPE_TERNARY:
+ size = sizeof(relopt_ternary);
+ break;
case RELOPT_TYPE_INT:
size = sizeof(relopt_int);
break;
@@ -883,6 +907,54 @@ add_local_bool_reloption(local_relopts *relopts, const char *name,
add_local_reloption(relopts, (relopt_gen *) newoption, offset);
}
+/*
+ * init_ternary_reloption
+ * Allocate and initialize a new ternary reloption
+ */
+static relopt_ternary *
+init_ternary_reloption(bits32 kinds, const char *name, const char *desc,
+ ternary default_val, LOCKMODE lockmode)
+{
+ relopt_ternary *newoption;
+
+ newoption = (relopt_ternary *) allocate_reloption(kinds,
+ RELOPT_TYPE_TERNARY, name, desc, lockmode);
+ newoption->default_val = default_val;
+
+ return newoption;
+}
+
+/*
+ * add_ternary_reloption
+ * Add a new ternary reloption
+ */
+void
+add_ternary_reloption(bits32 kinds, const char *name, const char *desc,
+ ternary default_val, LOCKMODE lockmode)
+{
+ relopt_ternary *newoption = init_ternary_reloption(kinds, name, desc,
+ default_val, lockmode);
+
+ add_reloption((relopt_gen *) newoption);
+}
+
+/*
+ * add_local_ternary_reloption
+ * Add a new ternary local reloption
+ *
+ * 'offset' is offset of ternary-typed field.
+ */
+void
+add_local_ternary_reloption(local_relopts *relopts, const char *name,
+ const char *desc, ternary default_val,
+ int offset)
+{
+ relopt_ternary *newoption = init_ternary_reloption(RELOPT_KIND_LOCAL,
+ name, desc,
+ default_val, 0);
+
+ add_local_reloption(relopts, (relopt_gen *) newoption, offset);
+}
/*
* init_real_reloption
@@ -1617,6 +1689,18 @@ parse_one_reloption(relopt_value *option, char *text_str, int text_len,
option->gen->name, value)));
}
break;
+ case RELOPT_TYPE_TERNARY:
+ {
+ bool b;
+ parsed = parse_bool(value, &b);
+ option->values.ternary_val = b ? TERNARY_TRUE : TERNARY_FALSE;
+ if (validate && !parsed)
+ ereport(ERROR,
+ (errcode(ERRCODE_INVALID_PARAMETER_VALUE),
+ errmsg("invalid value for ternary option \"%s\": %s",
+ option->gen->name, value)));
+ }
+ break;
case RELOPT_TYPE_INT:
{
relopt_int *optint = (relopt_int *) option->gen;
@@ -1780,17 +1864,6 @@ fillRelOptions(void *rdopts, Size basesize,
char *itempos = ((char *) rdopts) + elems[j].offset;
char *string_val;
- /*
- * If isset_offset is provided, store whether the reloption is
- * set there.
- */
- if (elems[j].isset_offset > 0)
- {
- char *setpos = ((char *) rdopts) + elems[j].isset_offset;
-
- *(bool *) setpos = options[i].isset;
- }
-
switch (options[i].gen->type)
{
case RELOPT_TYPE_BOOL:
@@ -1798,6 +1871,11 @@ fillRelOptions(void *rdopts, Size basesize,
options[i].values.bool_val :
((relopt_bool *) options[i].gen)->default_val;
break;
+ case RELOPT_TYPE_TERNARY:
+ *(ternary *) itempos = options[i].isset ?
+ options[i].values.ternary_val :
+ ((relopt_ternary *) options[i].gen)->default_val;
+ break;
case RELOPT_TYPE_INT:
*(int *) itempos = options[i].isset ?
options[i].values.int_val :
@@ -1912,8 +1990,8 @@ default_reloptions(Datum reloptions, bool validate, relopt_kind kind)
offsetof(StdRdOptions, parallel_workers)},
{"vacuum_index_cleanup", RELOPT_TYPE_ENUM,
offsetof(StdRdOptions, vacuum_index_cleanup)},
- {"vacuum_truncate", RELOPT_TYPE_BOOL,
- offsetof(StdRdOptions, vacuum_truncate), offsetof(StdRdOptions, vacuum_truncate_set)},
+ {"vacuum_truncate", RELOPT_TYPE_TERNARY,
+ offsetof(StdRdOptions, vacuum_truncate)},
{"vacuum_max_eager_freeze_failure_rate", RELOPT_TYPE_REAL,
offsetof(StdRdOptions, vacuum_max_eager_freeze_failure_rate)}
};
@@ -1993,7 +2071,6 @@ build_local_reloptions(local_relopts *relopts, Datum options, bool validate)
elems[i].optname = opt->option->name;
elems[i].opttype = opt->option->type;
elems[i].offset = opt->offset;
- elems[i].isset_offset = 0; /* not supported for local relopts yet */
i++;
}
diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c
index 733ef40ae7..977babff54 100644
--- a/src/backend/commands/vacuum.c
+++ b/src/backend/commands/vacuum.c
@@ -2221,9 +2221,9 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams params,
{
StdRdOptions *opts = (StdRdOptions *) rel->rd_options;
- if (opts && opts->vacuum_truncate_set)
+ if (opts && opts->vacuum_truncate != TERNARY_UNSET)
{
- if (opts->vacuum_truncate)
+ if (opts->vacuum_truncate == TERNARY_TRUE)
params.truncate = VACOPTVALUE_ENABLED;
else
params.truncate = VACOPTVALUE_DISABLED;
diff --git a/src/include/access/reloptions.h b/src/include/access/reloptions.h
index a604a4702c..a436697658 100644
--- a/src/include/access/reloptions.h
+++ b/src/include/access/reloptions.h
@@ -29,6 +29,7 @@
typedef enum relopt_type
{
RELOPT_TYPE_BOOL,
+ RELOPT_TYPE_TERNARY, /* on, off, unset */
RELOPT_TYPE_INT,
RELOPT_TYPE_REAL,
RELOPT_TYPE_ENUM,
@@ -80,6 +81,7 @@ typedef struct relopt_value
union
{
bool bool_val;
+ ternary ternary_val;
int int_val;
double real_val;
int enum_val;
@@ -94,6 +96,12 @@ typedef struct relopt_bool
bool default_val;
} relopt_bool;
+typedef struct relopt_rernary
+{
+ relopt_gen gen;
+ int default_val;
+} relopt_ternary;
+
typedef struct relopt_int
{
relopt_gen gen;
@@ -152,19 +160,6 @@ typedef struct
const char *optname; /* option's name */
relopt_type opttype; /* option's datatype */
int offset; /* offset of field in result struct */
-
- /*
- * isset_offset is an optional offset of a field in the result struct that
- * stores whether the option is explicitly set for the relation or if it
- * just picked up the default value. In most cases, this can be
- * accomplished by giving the reloption a special out-of-range default
- * value (e.g., some integer reloptions use -2), but this isn't always
- * possible. For example, a Boolean reloption cannot be given an
- * out-of-range default, so we need another way to discover the source of
- * its value. This offset is only used if given a value greater than
- * zero.
- */
- int isset_offset;
} relopt_parse_elt;
/* Local reloption definition */
@@ -195,6 +190,8 @@ typedef struct local_relopts
extern relopt_kind add_reloption_kind(void);
extern void add_bool_reloption(bits32 kinds, const char *name, const char *desc,
bool default_val, LOCKMODE lockmode);
+extern void add_ternary_reloption(bits32 kinds, const char *name,
+ const char *desc, int default_val, LOCKMODE lockmode);
extern void add_int_reloption(bits32 kinds, const char *name, const char *desc,
int default_val, int min_val, int max_val,
LOCKMODE lockmode);
@@ -214,6 +211,9 @@ extern void register_reloptions_validator(local_relopts *relopts,
extern void add_local_bool_reloption(local_relopts *relopts, const char *name,
const char *desc, bool default_val,
int offset);
+extern void add_local_ternary_reloption(local_relopts *relopts,
+ const char *name, const char *desc,
+ ternary default_val, int offset);
extern void add_local_int_reloption(local_relopts *relopts, const char *name,
const char *desc, int default_val,
int min_val, int max_val, int offset);
diff --git a/src/include/c.h b/src/include/c.h
index 39022f8a9d..9c59ccccf8 100644
--- a/src/include/c.h
+++ b/src/include/c.h
@@ -488,6 +488,22 @@ typedef void (*pg_funcptr_t) (void);
#include <stdbool.h>
+/*
+ * ternary
+ * Boolean value with an extrea "unset" option
+ *
+ * Ternary data type is used in relation options that can be "true", "false" or
+ * "unset". Since relation options are used deep inside the PostgreSQL code,
+ * this type is declared globally.
+*/
+
+typedef enum ternary
+{
+ TERNARY_FALSE = 0,
+ TERNARY_TRUE = 1,
+ TERNARY_UNSET = -1
+} ternary;
+
/* ----------------------------------------------------------------
* Section 3: standard system types
diff --git a/src/include/utils/rel.h b/src/include/utils/rel.h
index b552359915..7346d618f9 100644
--- a/src/include/utils/rel.h
+++ b/src/include/utils/rel.h
@@ -346,8 +346,7 @@ typedef struct StdRdOptions
bool user_catalog_table; /* use as an additional catalog relation */
int parallel_workers; /* max number of parallel workers */
StdRdOptIndexCleanup vacuum_index_cleanup; /* controls index vacuuming */
- bool vacuum_truncate; /* enables vacuum to truncate a relation */
- bool vacuum_truncate_set; /* whether vacuum_truncate is set */
+ ternary vacuum_truncate; /* enables vacuum to truncate a relation */
/*
* Fraction of pages in a relation that vacuum can eagerly scan and fail
--
2.39.2
--nextPart4155600.3ZeAukHxDK
Content-Disposition: attachment;
filename="v2a-0003-Add-alias-to-be-used-as-unset-state.patch"
Content-Transfer-Encoding: 7Bit
Content-Type: text/x-patch; charset="unicode-2-0-utf-8";
name="v2a-0003-Add-alias-to-be-used-as-unset-state.patch"
^ permalink raw reply [nested|flat] 868+ messages in thread
* [PATCH v2 2/4] Introduce ternary reloptions
@ 2025-09-04 14:41 Nikolay Shaplov <[email protected]>
0 siblings, 0 replies; 868+ messages in thread
From: Nikolay Shaplov @ 2025-09-04 14:41 UTC (permalink / raw)
Introduce ternary reloption as a replacement for current `vacuum_truncate`
implementation. Remove `vacuum_truncate_set` additional flag and using
`TERNARY_UNSET` value instead.
---
src/backend/access/common/reloptions.c | 129 ++++++++++++++++++++-----
src/backend/commands/vacuum.c | 4 +-
src/include/access/reloptions.h | 26 ++---
src/include/c.h | 16 +++
src/include/utils/rel.h | 3 +-
5 files changed, 135 insertions(+), 43 deletions(-)
diff --git a/src/backend/access/common/reloptions.c b/src/backend/access/common/reloptions.c
index 0af3fea68f..f543f11001 100644
--- a/src/backend/access/common/reloptions.c
+++ b/src/backend/access/common/reloptions.c
@@ -40,9 +40,9 @@
*
* To add an option:
*
- * (i) decide on a type (bool, integer, real, enum, string), name, default
- * value, upper and lower bounds (if applicable); for strings, consider a
- * validation routine.
+ * (i) decide on a type (bool, ternary, integer, real, enum, string), name,
+ * default value, upper and lower bounds (if applicable); for strings,
+ * consider a validation routine.
* (ii) add a record below (or use add_<type>_reloption).
* (iii) add it to the appropriate options struct (perhaps StdRdOptions)
* (iv) add it to the appropriate handling routine (perhaps
@@ -147,15 +147,6 @@ static relopt_bool boolRelOpts[] =
},
false
},
- {
- {
- "vacuum_truncate",
- "Enables vacuum to truncate empty pages at the end of this table",
- RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
- ShareUpdateExclusiveLock
- },
- true
- },
{
{
"deduplicate_items",
@@ -170,6 +161,21 @@ static relopt_bool boolRelOpts[] =
{{NULL}}
};
+static relopt_ternary ternaryRelOpts[] =
+{
+ {
+ {
+ "vacuum_truncate",
+ "Enables vacuum to truncate empty pages at the end of this table",
+ RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
+ ShareUpdateExclusiveLock
+ },
+ TERNARY_UNSET
+ },
+ /* list terminator */
+ {{NULL}}
+};
+
static relopt_int intRelOpts[] =
{
{
@@ -600,6 +606,13 @@ initialize_reloptions(void)
boolRelOpts[i].gen.lockmode));
j++;
}
+ for (i = 0; ternaryRelOpts[i].gen.name; i++)
+ {
+ Assert(DoLockModesConflict(ternaryRelOpts[i].gen.lockmode,
+ ternaryRelOpts[i].gen.lockmode));
+ j++;
+ }
+
for (i = 0; intRelOpts[i].gen.name; i++)
{
Assert(DoLockModesConflict(intRelOpts[i].gen.lockmode,
@@ -640,6 +653,14 @@ initialize_reloptions(void)
j++;
}
+ for (i = 0; ternaryRelOpts[i].gen.name; i++)
+ {
+ relOpts[j] = &ternaryRelOpts[i].gen;
+ relOpts[j]->type = RELOPT_TYPE_TERNARY;
+ relOpts[j]->namelen = strlen(relOpts[j]->name);
+ j++;
+ }
+
for (i = 0; intRelOpts[i].gen.name; i++)
{
relOpts[j] = &intRelOpts[i].gen;
@@ -800,6 +821,9 @@ allocate_reloption(bits32 kinds, int type, const char *name, const char *desc,
case RELOPT_TYPE_BOOL:
size = sizeof(relopt_bool);
break;
+ case RELOPT_TYPE_TERNARY:
+ size = sizeof(relopt_ternary);
+ break;
case RELOPT_TYPE_INT:
size = sizeof(relopt_int);
break;
@@ -883,6 +907,54 @@ add_local_bool_reloption(local_relopts *relopts, const char *name,
add_local_reloption(relopts, (relopt_gen *) newoption, offset);
}
+/*
+ * init_ternary_reloption
+ * Allocate and initialize a new ternary reloption
+ */
+static relopt_ternary *
+init_ternary_reloption(bits32 kinds, const char *name, const char *desc,
+ ternary default_val, LOCKMODE lockmode)
+{
+ relopt_ternary *newoption;
+
+ newoption = (relopt_ternary *) allocate_reloption(kinds,
+ RELOPT_TYPE_TERNARY, name, desc, lockmode);
+ newoption->default_val = default_val;
+
+ return newoption;
+}
+
+/*
+ * add_ternary_reloption
+ * Add a new ternary reloption
+ */
+void
+add_ternary_reloption(bits32 kinds, const char *name, const char *desc,
+ ternary default_val, LOCKMODE lockmode)
+{
+ relopt_ternary *newoption = init_ternary_reloption(kinds, name, desc,
+ default_val, lockmode);
+
+ add_reloption((relopt_gen *) newoption);
+}
+
+/*
+ * add_local_ternary_reloption
+ * Add a new ternary local reloption
+ *
+ * 'offset' is offset of ternary-typed field.
+ */
+void
+add_local_ternary_reloption(local_relopts *relopts, const char *name,
+ const char *desc, ternary default_val,
+ int offset)
+{
+ relopt_ternary *newoption = init_ternary_reloption(RELOPT_KIND_LOCAL,
+ name, desc,
+ default_val, 0);
+
+ add_local_reloption(relopts, (relopt_gen *) newoption, offset);
+}
/*
* init_real_reloption
@@ -1617,6 +1689,18 @@ parse_one_reloption(relopt_value *option, char *text_str, int text_len,
option->gen->name, value)));
}
break;
+ case RELOPT_TYPE_TERNARY:
+ {
+ bool b;
+ parsed = parse_bool(value, &b);
+ option->values.ternary_val = b ? TERNARY_TRUE : TERNARY_FALSE;
+ if (validate && !parsed)
+ ereport(ERROR,
+ (errcode(ERRCODE_INVALID_PARAMETER_VALUE),
+ errmsg("invalid value for ternary option \"%s\": %s",
+ option->gen->name, value)));
+ }
+ break;
case RELOPT_TYPE_INT:
{
relopt_int *optint = (relopt_int *) option->gen;
@@ -1780,17 +1864,6 @@ fillRelOptions(void *rdopts, Size basesize,
char *itempos = ((char *) rdopts) + elems[j].offset;
char *string_val;
- /*
- * If isset_offset is provided, store whether the reloption is
- * set there.
- */
- if (elems[j].isset_offset > 0)
- {
- char *setpos = ((char *) rdopts) + elems[j].isset_offset;
-
- *(bool *) setpos = options[i].isset;
- }
-
switch (options[i].gen->type)
{
case RELOPT_TYPE_BOOL:
@@ -1798,6 +1871,11 @@ fillRelOptions(void *rdopts, Size basesize,
options[i].values.bool_val :
((relopt_bool *) options[i].gen)->default_val;
break;
+ case RELOPT_TYPE_TERNARY:
+ *(ternary *) itempos = options[i].isset ?
+ options[i].values.ternary_val :
+ ((relopt_ternary *) options[i].gen)->default_val;
+ break;
case RELOPT_TYPE_INT:
*(int *) itempos = options[i].isset ?
options[i].values.int_val :
@@ -1912,8 +1990,8 @@ default_reloptions(Datum reloptions, bool validate, relopt_kind kind)
offsetof(StdRdOptions, parallel_workers)},
{"vacuum_index_cleanup", RELOPT_TYPE_ENUM,
offsetof(StdRdOptions, vacuum_index_cleanup)},
- {"vacuum_truncate", RELOPT_TYPE_BOOL,
- offsetof(StdRdOptions, vacuum_truncate), offsetof(StdRdOptions, vacuum_truncate_set)},
+ {"vacuum_truncate", RELOPT_TYPE_TERNARY,
+ offsetof(StdRdOptions, vacuum_truncate)},
{"vacuum_max_eager_freeze_failure_rate", RELOPT_TYPE_REAL,
offsetof(StdRdOptions, vacuum_max_eager_freeze_failure_rate)}
};
@@ -1993,7 +2071,6 @@ build_local_reloptions(local_relopts *relopts, Datum options, bool validate)
elems[i].optname = opt->option->name;
elems[i].opttype = opt->option->type;
elems[i].offset = opt->offset;
- elems[i].isset_offset = 0; /* not supported for local relopts yet */
i++;
}
diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c
index 733ef40ae7..977babff54 100644
--- a/src/backend/commands/vacuum.c
+++ b/src/backend/commands/vacuum.c
@@ -2221,9 +2221,9 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams params,
{
StdRdOptions *opts = (StdRdOptions *) rel->rd_options;
- if (opts && opts->vacuum_truncate_set)
+ if (opts && opts->vacuum_truncate != TERNARY_UNSET)
{
- if (opts->vacuum_truncate)
+ if (opts->vacuum_truncate == TERNARY_TRUE)
params.truncate = VACOPTVALUE_ENABLED;
else
params.truncate = VACOPTVALUE_DISABLED;
diff --git a/src/include/access/reloptions.h b/src/include/access/reloptions.h
index a604a4702c..a436697658 100644
--- a/src/include/access/reloptions.h
+++ b/src/include/access/reloptions.h
@@ -29,6 +29,7 @@
typedef enum relopt_type
{
RELOPT_TYPE_BOOL,
+ RELOPT_TYPE_TERNARY, /* on, off, unset */
RELOPT_TYPE_INT,
RELOPT_TYPE_REAL,
RELOPT_TYPE_ENUM,
@@ -80,6 +81,7 @@ typedef struct relopt_value
union
{
bool bool_val;
+ ternary ternary_val;
int int_val;
double real_val;
int enum_val;
@@ -94,6 +96,12 @@ typedef struct relopt_bool
bool default_val;
} relopt_bool;
+typedef struct relopt_rernary
+{
+ relopt_gen gen;
+ int default_val;
+} relopt_ternary;
+
typedef struct relopt_int
{
relopt_gen gen;
@@ -152,19 +160,6 @@ typedef struct
const char *optname; /* option's name */
relopt_type opttype; /* option's datatype */
int offset; /* offset of field in result struct */
-
- /*
- * isset_offset is an optional offset of a field in the result struct that
- * stores whether the option is explicitly set for the relation or if it
- * just picked up the default value. In most cases, this can be
- * accomplished by giving the reloption a special out-of-range default
- * value (e.g., some integer reloptions use -2), but this isn't always
- * possible. For example, a Boolean reloption cannot be given an
- * out-of-range default, so we need another way to discover the source of
- * its value. This offset is only used if given a value greater than
- * zero.
- */
- int isset_offset;
} relopt_parse_elt;
/* Local reloption definition */
@@ -195,6 +190,8 @@ typedef struct local_relopts
extern relopt_kind add_reloption_kind(void);
extern void add_bool_reloption(bits32 kinds, const char *name, const char *desc,
bool default_val, LOCKMODE lockmode);
+extern void add_ternary_reloption(bits32 kinds, const char *name,
+ const char *desc, int default_val, LOCKMODE lockmode);
extern void add_int_reloption(bits32 kinds, const char *name, const char *desc,
int default_val, int min_val, int max_val,
LOCKMODE lockmode);
@@ -214,6 +211,9 @@ extern void register_reloptions_validator(local_relopts *relopts,
extern void add_local_bool_reloption(local_relopts *relopts, const char *name,
const char *desc, bool default_val,
int offset);
+extern void add_local_ternary_reloption(local_relopts *relopts,
+ const char *name, const char *desc,
+ ternary default_val, int offset);
extern void add_local_int_reloption(local_relopts *relopts, const char *name,
const char *desc, int default_val,
int min_val, int max_val, int offset);
diff --git a/src/include/c.h b/src/include/c.h
index 39022f8a9d..9c59ccccf8 100644
--- a/src/include/c.h
+++ b/src/include/c.h
@@ -488,6 +488,22 @@ typedef void (*pg_funcptr_t) (void);
#include <stdbool.h>
+/*
+ * ternary
+ * Boolean value with an extrea "unset" option
+ *
+ * Ternary data type is used in relation options that can be "true", "false" or
+ * "unset". Since relation options are used deep inside the PostgreSQL code,
+ * this type is declared globally.
+*/
+
+typedef enum ternary
+{
+ TERNARY_FALSE = 0,
+ TERNARY_TRUE = 1,
+ TERNARY_UNSET = -1
+} ternary;
+
/* ----------------------------------------------------------------
* Section 3: standard system types
diff --git a/src/include/utils/rel.h b/src/include/utils/rel.h
index b552359915..7346d618f9 100644
--- a/src/include/utils/rel.h
+++ b/src/include/utils/rel.h
@@ -346,8 +346,7 @@ typedef struct StdRdOptions
bool user_catalog_table; /* use as an additional catalog relation */
int parallel_workers; /* max number of parallel workers */
StdRdOptIndexCleanup vacuum_index_cleanup; /* controls index vacuuming */
- bool vacuum_truncate; /* enables vacuum to truncate a relation */
- bool vacuum_truncate_set; /* whether vacuum_truncate is set */
+ ternary vacuum_truncate; /* enables vacuum to truncate a relation */
/*
* Fraction of pages in a relation that vacuum can eagerly scan and fail
--
2.39.2
--nextPart5129932.5fSG56mABF
Content-Disposition: attachment;
filename="v2-0003-Add-alias-to-be-used-as-unset-state.patch"
Content-Transfer-Encoding: 7Bit
Content-Type: text/x-patch; charset="unicode-2-0-utf-8";
name="v2-0003-Add-alias-to-be-used-as-unset-state.patch"
^ permalink raw reply [nested|flat] 868+ messages in thread
* [PATCH v2 2/4] Introduce ternary reloptions
@ 2025-09-04 14:41 Nikolay Shaplov <[email protected]>
0 siblings, 0 replies; 868+ messages in thread
From: Nikolay Shaplov @ 2025-09-04 14:41 UTC (permalink / raw)
Introduce ternary reloption as a replacement for current `vacuum_truncate`
implementation. Remove `vacuum_truncate_set` additional flag and using
`TERNARY_UNSET` value instead.
---
src/backend/access/common/reloptions.c | 129 ++++++++++++++++++++-----
src/backend/commands/vacuum.c | 4 +-
src/include/access/reloptions.h | 26 ++---
src/include/c.h | 16 +++
src/include/utils/rel.h | 3 +-
5 files changed, 135 insertions(+), 43 deletions(-)
diff --git a/src/backend/access/common/reloptions.c b/src/backend/access/common/reloptions.c
index 0af3fea68f..f543f11001 100644
--- a/src/backend/access/common/reloptions.c
+++ b/src/backend/access/common/reloptions.c
@@ -40,9 +40,9 @@
*
* To add an option:
*
- * (i) decide on a type (bool, integer, real, enum, string), name, default
- * value, upper and lower bounds (if applicable); for strings, consider a
- * validation routine.
+ * (i) decide on a type (bool, ternary, integer, real, enum, string), name,
+ * default value, upper and lower bounds (if applicable); for strings,
+ * consider a validation routine.
* (ii) add a record below (or use add_<type>_reloption).
* (iii) add it to the appropriate options struct (perhaps StdRdOptions)
* (iv) add it to the appropriate handling routine (perhaps
@@ -147,15 +147,6 @@ static relopt_bool boolRelOpts[] =
},
false
},
- {
- {
- "vacuum_truncate",
- "Enables vacuum to truncate empty pages at the end of this table",
- RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
- ShareUpdateExclusiveLock
- },
- true
- },
{
{
"deduplicate_items",
@@ -170,6 +161,21 @@ static relopt_bool boolRelOpts[] =
{{NULL}}
};
+static relopt_ternary ternaryRelOpts[] =
+{
+ {
+ {
+ "vacuum_truncate",
+ "Enables vacuum to truncate empty pages at the end of this table",
+ RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
+ ShareUpdateExclusiveLock
+ },
+ TERNARY_UNSET
+ },
+ /* list terminator */
+ {{NULL}}
+};
+
static relopt_int intRelOpts[] =
{
{
@@ -600,6 +606,13 @@ initialize_reloptions(void)
boolRelOpts[i].gen.lockmode));
j++;
}
+ for (i = 0; ternaryRelOpts[i].gen.name; i++)
+ {
+ Assert(DoLockModesConflict(ternaryRelOpts[i].gen.lockmode,
+ ternaryRelOpts[i].gen.lockmode));
+ j++;
+ }
+
for (i = 0; intRelOpts[i].gen.name; i++)
{
Assert(DoLockModesConflict(intRelOpts[i].gen.lockmode,
@@ -640,6 +653,14 @@ initialize_reloptions(void)
j++;
}
+ for (i = 0; ternaryRelOpts[i].gen.name; i++)
+ {
+ relOpts[j] = &ternaryRelOpts[i].gen;
+ relOpts[j]->type = RELOPT_TYPE_TERNARY;
+ relOpts[j]->namelen = strlen(relOpts[j]->name);
+ j++;
+ }
+
for (i = 0; intRelOpts[i].gen.name; i++)
{
relOpts[j] = &intRelOpts[i].gen;
@@ -800,6 +821,9 @@ allocate_reloption(bits32 kinds, int type, const char *name, const char *desc,
case RELOPT_TYPE_BOOL:
size = sizeof(relopt_bool);
break;
+ case RELOPT_TYPE_TERNARY:
+ size = sizeof(relopt_ternary);
+ break;
case RELOPT_TYPE_INT:
size = sizeof(relopt_int);
break;
@@ -883,6 +907,54 @@ add_local_bool_reloption(local_relopts *relopts, const char *name,
add_local_reloption(relopts, (relopt_gen *) newoption, offset);
}
+/*
+ * init_ternary_reloption
+ * Allocate and initialize a new ternary reloption
+ */
+static relopt_ternary *
+init_ternary_reloption(bits32 kinds, const char *name, const char *desc,
+ ternary default_val, LOCKMODE lockmode)
+{
+ relopt_ternary *newoption;
+
+ newoption = (relopt_ternary *) allocate_reloption(kinds,
+ RELOPT_TYPE_TERNARY, name, desc, lockmode);
+ newoption->default_val = default_val;
+
+ return newoption;
+}
+
+/*
+ * add_ternary_reloption
+ * Add a new ternary reloption
+ */
+void
+add_ternary_reloption(bits32 kinds, const char *name, const char *desc,
+ ternary default_val, LOCKMODE lockmode)
+{
+ relopt_ternary *newoption = init_ternary_reloption(kinds, name, desc,
+ default_val, lockmode);
+
+ add_reloption((relopt_gen *) newoption);
+}
+
+/*
+ * add_local_ternary_reloption
+ * Add a new ternary local reloption
+ *
+ * 'offset' is offset of ternary-typed field.
+ */
+void
+add_local_ternary_reloption(local_relopts *relopts, const char *name,
+ const char *desc, ternary default_val,
+ int offset)
+{
+ relopt_ternary *newoption = init_ternary_reloption(RELOPT_KIND_LOCAL,
+ name, desc,
+ default_val, 0);
+
+ add_local_reloption(relopts, (relopt_gen *) newoption, offset);
+}
/*
* init_real_reloption
@@ -1617,6 +1689,18 @@ parse_one_reloption(relopt_value *option, char *text_str, int text_len,
option->gen->name, value)));
}
break;
+ case RELOPT_TYPE_TERNARY:
+ {
+ bool b;
+ parsed = parse_bool(value, &b);
+ option->values.ternary_val = b ? TERNARY_TRUE : TERNARY_FALSE;
+ if (validate && !parsed)
+ ereport(ERROR,
+ (errcode(ERRCODE_INVALID_PARAMETER_VALUE),
+ errmsg("invalid value for ternary option \"%s\": %s",
+ option->gen->name, value)));
+ }
+ break;
case RELOPT_TYPE_INT:
{
relopt_int *optint = (relopt_int *) option->gen;
@@ -1780,17 +1864,6 @@ fillRelOptions(void *rdopts, Size basesize,
char *itempos = ((char *) rdopts) + elems[j].offset;
char *string_val;
- /*
- * If isset_offset is provided, store whether the reloption is
- * set there.
- */
- if (elems[j].isset_offset > 0)
- {
- char *setpos = ((char *) rdopts) + elems[j].isset_offset;
-
- *(bool *) setpos = options[i].isset;
- }
-
switch (options[i].gen->type)
{
case RELOPT_TYPE_BOOL:
@@ -1798,6 +1871,11 @@ fillRelOptions(void *rdopts, Size basesize,
options[i].values.bool_val :
((relopt_bool *) options[i].gen)->default_val;
break;
+ case RELOPT_TYPE_TERNARY:
+ *(ternary *) itempos = options[i].isset ?
+ options[i].values.ternary_val :
+ ((relopt_ternary *) options[i].gen)->default_val;
+ break;
case RELOPT_TYPE_INT:
*(int *) itempos = options[i].isset ?
options[i].values.int_val :
@@ -1912,8 +1990,8 @@ default_reloptions(Datum reloptions, bool validate, relopt_kind kind)
offsetof(StdRdOptions, parallel_workers)},
{"vacuum_index_cleanup", RELOPT_TYPE_ENUM,
offsetof(StdRdOptions, vacuum_index_cleanup)},
- {"vacuum_truncate", RELOPT_TYPE_BOOL,
- offsetof(StdRdOptions, vacuum_truncate), offsetof(StdRdOptions, vacuum_truncate_set)},
+ {"vacuum_truncate", RELOPT_TYPE_TERNARY,
+ offsetof(StdRdOptions, vacuum_truncate)},
{"vacuum_max_eager_freeze_failure_rate", RELOPT_TYPE_REAL,
offsetof(StdRdOptions, vacuum_max_eager_freeze_failure_rate)}
};
@@ -1993,7 +2071,6 @@ build_local_reloptions(local_relopts *relopts, Datum options, bool validate)
elems[i].optname = opt->option->name;
elems[i].opttype = opt->option->type;
elems[i].offset = opt->offset;
- elems[i].isset_offset = 0; /* not supported for local relopts yet */
i++;
}
diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c
index 733ef40ae7..977babff54 100644
--- a/src/backend/commands/vacuum.c
+++ b/src/backend/commands/vacuum.c
@@ -2221,9 +2221,9 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams params,
{
StdRdOptions *opts = (StdRdOptions *) rel->rd_options;
- if (opts && opts->vacuum_truncate_set)
+ if (opts && opts->vacuum_truncate != TERNARY_UNSET)
{
- if (opts->vacuum_truncate)
+ if (opts->vacuum_truncate == TERNARY_TRUE)
params.truncate = VACOPTVALUE_ENABLED;
else
params.truncate = VACOPTVALUE_DISABLED;
diff --git a/src/include/access/reloptions.h b/src/include/access/reloptions.h
index a604a4702c..a436697658 100644
--- a/src/include/access/reloptions.h
+++ b/src/include/access/reloptions.h
@@ -29,6 +29,7 @@
typedef enum relopt_type
{
RELOPT_TYPE_BOOL,
+ RELOPT_TYPE_TERNARY, /* on, off, unset */
RELOPT_TYPE_INT,
RELOPT_TYPE_REAL,
RELOPT_TYPE_ENUM,
@@ -80,6 +81,7 @@ typedef struct relopt_value
union
{
bool bool_val;
+ ternary ternary_val;
int int_val;
double real_val;
int enum_val;
@@ -94,6 +96,12 @@ typedef struct relopt_bool
bool default_val;
} relopt_bool;
+typedef struct relopt_rernary
+{
+ relopt_gen gen;
+ int default_val;
+} relopt_ternary;
+
typedef struct relopt_int
{
relopt_gen gen;
@@ -152,19 +160,6 @@ typedef struct
const char *optname; /* option's name */
relopt_type opttype; /* option's datatype */
int offset; /* offset of field in result struct */
-
- /*
- * isset_offset is an optional offset of a field in the result struct that
- * stores whether the option is explicitly set for the relation or if it
- * just picked up the default value. In most cases, this can be
- * accomplished by giving the reloption a special out-of-range default
- * value (e.g., some integer reloptions use -2), but this isn't always
- * possible. For example, a Boolean reloption cannot be given an
- * out-of-range default, so we need another way to discover the source of
- * its value. This offset is only used if given a value greater than
- * zero.
- */
- int isset_offset;
} relopt_parse_elt;
/* Local reloption definition */
@@ -195,6 +190,8 @@ typedef struct local_relopts
extern relopt_kind add_reloption_kind(void);
extern void add_bool_reloption(bits32 kinds, const char *name, const char *desc,
bool default_val, LOCKMODE lockmode);
+extern void add_ternary_reloption(bits32 kinds, const char *name,
+ const char *desc, int default_val, LOCKMODE lockmode);
extern void add_int_reloption(bits32 kinds, const char *name, const char *desc,
int default_val, int min_val, int max_val,
LOCKMODE lockmode);
@@ -214,6 +211,9 @@ extern void register_reloptions_validator(local_relopts *relopts,
extern void add_local_bool_reloption(local_relopts *relopts, const char *name,
const char *desc, bool default_val,
int offset);
+extern void add_local_ternary_reloption(local_relopts *relopts,
+ const char *name, const char *desc,
+ ternary default_val, int offset);
extern void add_local_int_reloption(local_relopts *relopts, const char *name,
const char *desc, int default_val,
int min_val, int max_val, int offset);
diff --git a/src/include/c.h b/src/include/c.h
index 39022f8a9d..9c59ccccf8 100644
--- a/src/include/c.h
+++ b/src/include/c.h
@@ -488,6 +488,22 @@ typedef void (*pg_funcptr_t) (void);
#include <stdbool.h>
+/*
+ * ternary
+ * Boolean value with an extrea "unset" option
+ *
+ * Ternary data type is used in relation options that can be "true", "false" or
+ * "unset". Since relation options are used deep inside the PostgreSQL code,
+ * this type is declared globally.
+*/
+
+typedef enum ternary
+{
+ TERNARY_FALSE = 0,
+ TERNARY_TRUE = 1,
+ TERNARY_UNSET = -1
+} ternary;
+
/* ----------------------------------------------------------------
* Section 3: standard system types
diff --git a/src/include/utils/rel.h b/src/include/utils/rel.h
index b552359915..7346d618f9 100644
--- a/src/include/utils/rel.h
+++ b/src/include/utils/rel.h
@@ -346,8 +346,7 @@ typedef struct StdRdOptions
bool user_catalog_table; /* use as an additional catalog relation */
int parallel_workers; /* max number of parallel workers */
StdRdOptIndexCleanup vacuum_index_cleanup; /* controls index vacuuming */
- bool vacuum_truncate; /* enables vacuum to truncate a relation */
- bool vacuum_truncate_set; /* whether vacuum_truncate is set */
+ ternary vacuum_truncate; /* enables vacuum to truncate a relation */
/*
* Fraction of pages in a relation that vacuum can eagerly scan and fail
--
2.39.2
--nextPart5129932.5fSG56mABF
Content-Disposition: attachment;
filename="v2-0003-Add-alias-to-be-used-as-unset-state.patch"
Content-Transfer-Encoding: 7Bit
Content-Type: text/x-patch; charset="unicode-2-0-utf-8";
name="v2-0003-Add-alias-to-be-used-as-unset-state.patch"
^ permalink raw reply [nested|flat] 868+ messages in thread
* [PATCH v2 2/4] Introduce ternary reloptions
@ 2025-09-04 14:41 Nikolay Shaplov <[email protected]>
0 siblings, 0 replies; 868+ messages in thread
From: Nikolay Shaplov @ 2025-09-04 14:41 UTC (permalink / raw)
Introduce ternary reloption as a replacement for current `vacuum_truncate`
implementation. Remove `vacuum_truncate_set` additional flag and using
`TERNARY_UNSET` value instead.
---
src/backend/access/common/reloptions.c | 129 ++++++++++++++++++++-----
src/backend/commands/vacuum.c | 4 +-
src/include/access/reloptions.h | 26 ++---
src/include/c.h | 16 +++
src/include/utils/rel.h | 3 +-
5 files changed, 135 insertions(+), 43 deletions(-)
diff --git a/src/backend/access/common/reloptions.c b/src/backend/access/common/reloptions.c
index 0af3fea68f..f543f11001 100644
--- a/src/backend/access/common/reloptions.c
+++ b/src/backend/access/common/reloptions.c
@@ -40,9 +40,9 @@
*
* To add an option:
*
- * (i) decide on a type (bool, integer, real, enum, string), name, default
- * value, upper and lower bounds (if applicable); for strings, consider a
- * validation routine.
+ * (i) decide on a type (bool, ternary, integer, real, enum, string), name,
+ * default value, upper and lower bounds (if applicable); for strings,
+ * consider a validation routine.
* (ii) add a record below (or use add_<type>_reloption).
* (iii) add it to the appropriate options struct (perhaps StdRdOptions)
* (iv) add it to the appropriate handling routine (perhaps
@@ -147,15 +147,6 @@ static relopt_bool boolRelOpts[] =
},
false
},
- {
- {
- "vacuum_truncate",
- "Enables vacuum to truncate empty pages at the end of this table",
- RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
- ShareUpdateExclusiveLock
- },
- true
- },
{
{
"deduplicate_items",
@@ -170,6 +161,21 @@ static relopt_bool boolRelOpts[] =
{{NULL}}
};
+static relopt_ternary ternaryRelOpts[] =
+{
+ {
+ {
+ "vacuum_truncate",
+ "Enables vacuum to truncate empty pages at the end of this table",
+ RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
+ ShareUpdateExclusiveLock
+ },
+ TERNARY_UNSET
+ },
+ /* list terminator */
+ {{NULL}}
+};
+
static relopt_int intRelOpts[] =
{
{
@@ -600,6 +606,13 @@ initialize_reloptions(void)
boolRelOpts[i].gen.lockmode));
j++;
}
+ for (i = 0; ternaryRelOpts[i].gen.name; i++)
+ {
+ Assert(DoLockModesConflict(ternaryRelOpts[i].gen.lockmode,
+ ternaryRelOpts[i].gen.lockmode));
+ j++;
+ }
+
for (i = 0; intRelOpts[i].gen.name; i++)
{
Assert(DoLockModesConflict(intRelOpts[i].gen.lockmode,
@@ -640,6 +653,14 @@ initialize_reloptions(void)
j++;
}
+ for (i = 0; ternaryRelOpts[i].gen.name; i++)
+ {
+ relOpts[j] = &ternaryRelOpts[i].gen;
+ relOpts[j]->type = RELOPT_TYPE_TERNARY;
+ relOpts[j]->namelen = strlen(relOpts[j]->name);
+ j++;
+ }
+
for (i = 0; intRelOpts[i].gen.name; i++)
{
relOpts[j] = &intRelOpts[i].gen;
@@ -800,6 +821,9 @@ allocate_reloption(bits32 kinds, int type, const char *name, const char *desc,
case RELOPT_TYPE_BOOL:
size = sizeof(relopt_bool);
break;
+ case RELOPT_TYPE_TERNARY:
+ size = sizeof(relopt_ternary);
+ break;
case RELOPT_TYPE_INT:
size = sizeof(relopt_int);
break;
@@ -883,6 +907,54 @@ add_local_bool_reloption(local_relopts *relopts, const char *name,
add_local_reloption(relopts, (relopt_gen *) newoption, offset);
}
+/*
+ * init_ternary_reloption
+ * Allocate and initialize a new ternary reloption
+ */
+static relopt_ternary *
+init_ternary_reloption(bits32 kinds, const char *name, const char *desc,
+ ternary default_val, LOCKMODE lockmode)
+{
+ relopt_ternary *newoption;
+
+ newoption = (relopt_ternary *) allocate_reloption(kinds,
+ RELOPT_TYPE_TERNARY, name, desc, lockmode);
+ newoption->default_val = default_val;
+
+ return newoption;
+}
+
+/*
+ * add_ternary_reloption
+ * Add a new ternary reloption
+ */
+void
+add_ternary_reloption(bits32 kinds, const char *name, const char *desc,
+ ternary default_val, LOCKMODE lockmode)
+{
+ relopt_ternary *newoption = init_ternary_reloption(kinds, name, desc,
+ default_val, lockmode);
+
+ add_reloption((relopt_gen *) newoption);
+}
+
+/*
+ * add_local_ternary_reloption
+ * Add a new ternary local reloption
+ *
+ * 'offset' is offset of ternary-typed field.
+ */
+void
+add_local_ternary_reloption(local_relopts *relopts, const char *name,
+ const char *desc, ternary default_val,
+ int offset)
+{
+ relopt_ternary *newoption = init_ternary_reloption(RELOPT_KIND_LOCAL,
+ name, desc,
+ default_val, 0);
+
+ add_local_reloption(relopts, (relopt_gen *) newoption, offset);
+}
/*
* init_real_reloption
@@ -1617,6 +1689,18 @@ parse_one_reloption(relopt_value *option, char *text_str, int text_len,
option->gen->name, value)));
}
break;
+ case RELOPT_TYPE_TERNARY:
+ {
+ bool b;
+ parsed = parse_bool(value, &b);
+ option->values.ternary_val = b ? TERNARY_TRUE : TERNARY_FALSE;
+ if (validate && !parsed)
+ ereport(ERROR,
+ (errcode(ERRCODE_INVALID_PARAMETER_VALUE),
+ errmsg("invalid value for ternary option \"%s\": %s",
+ option->gen->name, value)));
+ }
+ break;
case RELOPT_TYPE_INT:
{
relopt_int *optint = (relopt_int *) option->gen;
@@ -1780,17 +1864,6 @@ fillRelOptions(void *rdopts, Size basesize,
char *itempos = ((char *) rdopts) + elems[j].offset;
char *string_val;
- /*
- * If isset_offset is provided, store whether the reloption is
- * set there.
- */
- if (elems[j].isset_offset > 0)
- {
- char *setpos = ((char *) rdopts) + elems[j].isset_offset;
-
- *(bool *) setpos = options[i].isset;
- }
-
switch (options[i].gen->type)
{
case RELOPT_TYPE_BOOL:
@@ -1798,6 +1871,11 @@ fillRelOptions(void *rdopts, Size basesize,
options[i].values.bool_val :
((relopt_bool *) options[i].gen)->default_val;
break;
+ case RELOPT_TYPE_TERNARY:
+ *(ternary *) itempos = options[i].isset ?
+ options[i].values.ternary_val :
+ ((relopt_ternary *) options[i].gen)->default_val;
+ break;
case RELOPT_TYPE_INT:
*(int *) itempos = options[i].isset ?
options[i].values.int_val :
@@ -1912,8 +1990,8 @@ default_reloptions(Datum reloptions, bool validate, relopt_kind kind)
offsetof(StdRdOptions, parallel_workers)},
{"vacuum_index_cleanup", RELOPT_TYPE_ENUM,
offsetof(StdRdOptions, vacuum_index_cleanup)},
- {"vacuum_truncate", RELOPT_TYPE_BOOL,
- offsetof(StdRdOptions, vacuum_truncate), offsetof(StdRdOptions, vacuum_truncate_set)},
+ {"vacuum_truncate", RELOPT_TYPE_TERNARY,
+ offsetof(StdRdOptions, vacuum_truncate)},
{"vacuum_max_eager_freeze_failure_rate", RELOPT_TYPE_REAL,
offsetof(StdRdOptions, vacuum_max_eager_freeze_failure_rate)}
};
@@ -1993,7 +2071,6 @@ build_local_reloptions(local_relopts *relopts, Datum options, bool validate)
elems[i].optname = opt->option->name;
elems[i].opttype = opt->option->type;
elems[i].offset = opt->offset;
- elems[i].isset_offset = 0; /* not supported for local relopts yet */
i++;
}
diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c
index 733ef40ae7..977babff54 100644
--- a/src/backend/commands/vacuum.c
+++ b/src/backend/commands/vacuum.c
@@ -2221,9 +2221,9 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams params,
{
StdRdOptions *opts = (StdRdOptions *) rel->rd_options;
- if (opts && opts->vacuum_truncate_set)
+ if (opts && opts->vacuum_truncate != TERNARY_UNSET)
{
- if (opts->vacuum_truncate)
+ if (opts->vacuum_truncate == TERNARY_TRUE)
params.truncate = VACOPTVALUE_ENABLED;
else
params.truncate = VACOPTVALUE_DISABLED;
diff --git a/src/include/access/reloptions.h b/src/include/access/reloptions.h
index a604a4702c..a436697658 100644
--- a/src/include/access/reloptions.h
+++ b/src/include/access/reloptions.h
@@ -29,6 +29,7 @@
typedef enum relopt_type
{
RELOPT_TYPE_BOOL,
+ RELOPT_TYPE_TERNARY, /* on, off, unset */
RELOPT_TYPE_INT,
RELOPT_TYPE_REAL,
RELOPT_TYPE_ENUM,
@@ -80,6 +81,7 @@ typedef struct relopt_value
union
{
bool bool_val;
+ ternary ternary_val;
int int_val;
double real_val;
int enum_val;
@@ -94,6 +96,12 @@ typedef struct relopt_bool
bool default_val;
} relopt_bool;
+typedef struct relopt_rernary
+{
+ relopt_gen gen;
+ int default_val;
+} relopt_ternary;
+
typedef struct relopt_int
{
relopt_gen gen;
@@ -152,19 +160,6 @@ typedef struct
const char *optname; /* option's name */
relopt_type opttype; /* option's datatype */
int offset; /* offset of field in result struct */
-
- /*
- * isset_offset is an optional offset of a field in the result struct that
- * stores whether the option is explicitly set for the relation or if it
- * just picked up the default value. In most cases, this can be
- * accomplished by giving the reloption a special out-of-range default
- * value (e.g., some integer reloptions use -2), but this isn't always
- * possible. For example, a Boolean reloption cannot be given an
- * out-of-range default, so we need another way to discover the source of
- * its value. This offset is only used if given a value greater than
- * zero.
- */
- int isset_offset;
} relopt_parse_elt;
/* Local reloption definition */
@@ -195,6 +190,8 @@ typedef struct local_relopts
extern relopt_kind add_reloption_kind(void);
extern void add_bool_reloption(bits32 kinds, const char *name, const char *desc,
bool default_val, LOCKMODE lockmode);
+extern void add_ternary_reloption(bits32 kinds, const char *name,
+ const char *desc, int default_val, LOCKMODE lockmode);
extern void add_int_reloption(bits32 kinds, const char *name, const char *desc,
int default_val, int min_val, int max_val,
LOCKMODE lockmode);
@@ -214,6 +211,9 @@ extern void register_reloptions_validator(local_relopts *relopts,
extern void add_local_bool_reloption(local_relopts *relopts, const char *name,
const char *desc, bool default_val,
int offset);
+extern void add_local_ternary_reloption(local_relopts *relopts,
+ const char *name, const char *desc,
+ ternary default_val, int offset);
extern void add_local_int_reloption(local_relopts *relopts, const char *name,
const char *desc, int default_val,
int min_val, int max_val, int offset);
diff --git a/src/include/c.h b/src/include/c.h
index 39022f8a9d..9c59ccccf8 100644
--- a/src/include/c.h
+++ b/src/include/c.h
@@ -488,6 +488,22 @@ typedef void (*pg_funcptr_t) (void);
#include <stdbool.h>
+/*
+ * ternary
+ * Boolean value with an extrea "unset" option
+ *
+ * Ternary data type is used in relation options that can be "true", "false" or
+ * "unset". Since relation options are used deep inside the PostgreSQL code,
+ * this type is declared globally.
+*/
+
+typedef enum ternary
+{
+ TERNARY_FALSE = 0,
+ TERNARY_TRUE = 1,
+ TERNARY_UNSET = -1
+} ternary;
+
/* ----------------------------------------------------------------
* Section 3: standard system types
diff --git a/src/include/utils/rel.h b/src/include/utils/rel.h
index b552359915..7346d618f9 100644
--- a/src/include/utils/rel.h
+++ b/src/include/utils/rel.h
@@ -346,8 +346,7 @@ typedef struct StdRdOptions
bool user_catalog_table; /* use as an additional catalog relation */
int parallel_workers; /* max number of parallel workers */
StdRdOptIndexCleanup vacuum_index_cleanup; /* controls index vacuuming */
- bool vacuum_truncate; /* enables vacuum to truncate a relation */
- bool vacuum_truncate_set; /* whether vacuum_truncate is set */
+ ternary vacuum_truncate; /* enables vacuum to truncate a relation */
/*
* Fraction of pages in a relation that vacuum can eagerly scan and fail
--
2.39.2
--nextPart5129932.5fSG56mABF
Content-Disposition: attachment;
filename="v2-0003-Add-alias-to-be-used-as-unset-state.patch"
Content-Transfer-Encoding: 7Bit
Content-Type: text/x-patch; charset="unicode-2-0-utf-8";
name="v2-0003-Add-alias-to-be-used-as-unset-state.patch"
^ permalink raw reply [nested|flat] 868+ messages in thread
* [PATCH v2a 2/4] Introduce ternary reloptions
@ 2025-09-04 14:41 Nikolay Shaplov <[email protected]>
0 siblings, 0 replies; 868+ messages in thread
From: Nikolay Shaplov @ 2025-09-04 14:41 UTC (permalink / raw)
Introduce ternary reloption as a replacement for current `vacuum_truncate`
implementation. Remove `vacuum_truncate_set` additional flag and using
`TERNARY_UNSET` value instead.
---
src/backend/access/common/reloptions.c | 129 ++++++++++++++++++++-----
src/backend/commands/vacuum.c | 4 +-
src/include/access/reloptions.h | 26 ++---
src/include/c.h | 16 +++
src/include/utils/rel.h | 3 +-
5 files changed, 135 insertions(+), 43 deletions(-)
diff --git a/src/backend/access/common/reloptions.c b/src/backend/access/common/reloptions.c
index 0af3fea68f..f543f11001 100644
--- a/src/backend/access/common/reloptions.c
+++ b/src/backend/access/common/reloptions.c
@@ -40,9 +40,9 @@
*
* To add an option:
*
- * (i) decide on a type (bool, integer, real, enum, string), name, default
- * value, upper and lower bounds (if applicable); for strings, consider a
- * validation routine.
+ * (i) decide on a type (bool, ternary, integer, real, enum, string), name,
+ * default value, upper and lower bounds (if applicable); for strings,
+ * consider a validation routine.
* (ii) add a record below (or use add_<type>_reloption).
* (iii) add it to the appropriate options struct (perhaps StdRdOptions)
* (iv) add it to the appropriate handling routine (perhaps
@@ -147,15 +147,6 @@ static relopt_bool boolRelOpts[] =
},
false
},
- {
- {
- "vacuum_truncate",
- "Enables vacuum to truncate empty pages at the end of this table",
- RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
- ShareUpdateExclusiveLock
- },
- true
- },
{
{
"deduplicate_items",
@@ -170,6 +161,21 @@ static relopt_bool boolRelOpts[] =
{{NULL}}
};
+static relopt_ternary ternaryRelOpts[] =
+{
+ {
+ {
+ "vacuum_truncate",
+ "Enables vacuum to truncate empty pages at the end of this table",
+ RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
+ ShareUpdateExclusiveLock
+ },
+ TERNARY_UNSET
+ },
+ /* list terminator */
+ {{NULL}}
+};
+
static relopt_int intRelOpts[] =
{
{
@@ -600,6 +606,13 @@ initialize_reloptions(void)
boolRelOpts[i].gen.lockmode));
j++;
}
+ for (i = 0; ternaryRelOpts[i].gen.name; i++)
+ {
+ Assert(DoLockModesConflict(ternaryRelOpts[i].gen.lockmode,
+ ternaryRelOpts[i].gen.lockmode));
+ j++;
+ }
+
for (i = 0; intRelOpts[i].gen.name; i++)
{
Assert(DoLockModesConflict(intRelOpts[i].gen.lockmode,
@@ -640,6 +653,14 @@ initialize_reloptions(void)
j++;
}
+ for (i = 0; ternaryRelOpts[i].gen.name; i++)
+ {
+ relOpts[j] = &ternaryRelOpts[i].gen;
+ relOpts[j]->type = RELOPT_TYPE_TERNARY;
+ relOpts[j]->namelen = strlen(relOpts[j]->name);
+ j++;
+ }
+
for (i = 0; intRelOpts[i].gen.name; i++)
{
relOpts[j] = &intRelOpts[i].gen;
@@ -800,6 +821,9 @@ allocate_reloption(bits32 kinds, int type, const char *name, const char *desc,
case RELOPT_TYPE_BOOL:
size = sizeof(relopt_bool);
break;
+ case RELOPT_TYPE_TERNARY:
+ size = sizeof(relopt_ternary);
+ break;
case RELOPT_TYPE_INT:
size = sizeof(relopt_int);
break;
@@ -883,6 +907,54 @@ add_local_bool_reloption(local_relopts *relopts, const char *name,
add_local_reloption(relopts, (relopt_gen *) newoption, offset);
}
+/*
+ * init_ternary_reloption
+ * Allocate and initialize a new ternary reloption
+ */
+static relopt_ternary *
+init_ternary_reloption(bits32 kinds, const char *name, const char *desc,
+ ternary default_val, LOCKMODE lockmode)
+{
+ relopt_ternary *newoption;
+
+ newoption = (relopt_ternary *) allocate_reloption(kinds,
+ RELOPT_TYPE_TERNARY, name, desc, lockmode);
+ newoption->default_val = default_val;
+
+ return newoption;
+}
+
+/*
+ * add_ternary_reloption
+ * Add a new ternary reloption
+ */
+void
+add_ternary_reloption(bits32 kinds, const char *name, const char *desc,
+ ternary default_val, LOCKMODE lockmode)
+{
+ relopt_ternary *newoption = init_ternary_reloption(kinds, name, desc,
+ default_val, lockmode);
+
+ add_reloption((relopt_gen *) newoption);
+}
+
+/*
+ * add_local_ternary_reloption
+ * Add a new ternary local reloption
+ *
+ * 'offset' is offset of ternary-typed field.
+ */
+void
+add_local_ternary_reloption(local_relopts *relopts, const char *name,
+ const char *desc, ternary default_val,
+ int offset)
+{
+ relopt_ternary *newoption = init_ternary_reloption(RELOPT_KIND_LOCAL,
+ name, desc,
+ default_val, 0);
+
+ add_local_reloption(relopts, (relopt_gen *) newoption, offset);
+}
/*
* init_real_reloption
@@ -1617,6 +1689,18 @@ parse_one_reloption(relopt_value *option, char *text_str, int text_len,
option->gen->name, value)));
}
break;
+ case RELOPT_TYPE_TERNARY:
+ {
+ bool b;
+ parsed = parse_bool(value, &b);
+ option->values.ternary_val = b ? TERNARY_TRUE : TERNARY_FALSE;
+ if (validate && !parsed)
+ ereport(ERROR,
+ (errcode(ERRCODE_INVALID_PARAMETER_VALUE),
+ errmsg("invalid value for ternary option \"%s\": %s",
+ option->gen->name, value)));
+ }
+ break;
case RELOPT_TYPE_INT:
{
relopt_int *optint = (relopt_int *) option->gen;
@@ -1780,17 +1864,6 @@ fillRelOptions(void *rdopts, Size basesize,
char *itempos = ((char *) rdopts) + elems[j].offset;
char *string_val;
- /*
- * If isset_offset is provided, store whether the reloption is
- * set there.
- */
- if (elems[j].isset_offset > 0)
- {
- char *setpos = ((char *) rdopts) + elems[j].isset_offset;
-
- *(bool *) setpos = options[i].isset;
- }
-
switch (options[i].gen->type)
{
case RELOPT_TYPE_BOOL:
@@ -1798,6 +1871,11 @@ fillRelOptions(void *rdopts, Size basesize,
options[i].values.bool_val :
((relopt_bool *) options[i].gen)->default_val;
break;
+ case RELOPT_TYPE_TERNARY:
+ *(ternary *) itempos = options[i].isset ?
+ options[i].values.ternary_val :
+ ((relopt_ternary *) options[i].gen)->default_val;
+ break;
case RELOPT_TYPE_INT:
*(int *) itempos = options[i].isset ?
options[i].values.int_val :
@@ -1912,8 +1990,8 @@ default_reloptions(Datum reloptions, bool validate, relopt_kind kind)
offsetof(StdRdOptions, parallel_workers)},
{"vacuum_index_cleanup", RELOPT_TYPE_ENUM,
offsetof(StdRdOptions, vacuum_index_cleanup)},
- {"vacuum_truncate", RELOPT_TYPE_BOOL,
- offsetof(StdRdOptions, vacuum_truncate), offsetof(StdRdOptions, vacuum_truncate_set)},
+ {"vacuum_truncate", RELOPT_TYPE_TERNARY,
+ offsetof(StdRdOptions, vacuum_truncate)},
{"vacuum_max_eager_freeze_failure_rate", RELOPT_TYPE_REAL,
offsetof(StdRdOptions, vacuum_max_eager_freeze_failure_rate)}
};
@@ -1993,7 +2071,6 @@ build_local_reloptions(local_relopts *relopts, Datum options, bool validate)
elems[i].optname = opt->option->name;
elems[i].opttype = opt->option->type;
elems[i].offset = opt->offset;
- elems[i].isset_offset = 0; /* not supported for local relopts yet */
i++;
}
diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c
index 733ef40ae7..977babff54 100644
--- a/src/backend/commands/vacuum.c
+++ b/src/backend/commands/vacuum.c
@@ -2221,9 +2221,9 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams params,
{
StdRdOptions *opts = (StdRdOptions *) rel->rd_options;
- if (opts && opts->vacuum_truncate_set)
+ if (opts && opts->vacuum_truncate != TERNARY_UNSET)
{
- if (opts->vacuum_truncate)
+ if (opts->vacuum_truncate == TERNARY_TRUE)
params.truncate = VACOPTVALUE_ENABLED;
else
params.truncate = VACOPTVALUE_DISABLED;
diff --git a/src/include/access/reloptions.h b/src/include/access/reloptions.h
index a604a4702c..a436697658 100644
--- a/src/include/access/reloptions.h
+++ b/src/include/access/reloptions.h
@@ -29,6 +29,7 @@
typedef enum relopt_type
{
RELOPT_TYPE_BOOL,
+ RELOPT_TYPE_TERNARY, /* on, off, unset */
RELOPT_TYPE_INT,
RELOPT_TYPE_REAL,
RELOPT_TYPE_ENUM,
@@ -80,6 +81,7 @@ typedef struct relopt_value
union
{
bool bool_val;
+ ternary ternary_val;
int int_val;
double real_val;
int enum_val;
@@ -94,6 +96,12 @@ typedef struct relopt_bool
bool default_val;
} relopt_bool;
+typedef struct relopt_rernary
+{
+ relopt_gen gen;
+ int default_val;
+} relopt_ternary;
+
typedef struct relopt_int
{
relopt_gen gen;
@@ -152,19 +160,6 @@ typedef struct
const char *optname; /* option's name */
relopt_type opttype; /* option's datatype */
int offset; /* offset of field in result struct */
-
- /*
- * isset_offset is an optional offset of a field in the result struct that
- * stores whether the option is explicitly set for the relation or if it
- * just picked up the default value. In most cases, this can be
- * accomplished by giving the reloption a special out-of-range default
- * value (e.g., some integer reloptions use -2), but this isn't always
- * possible. For example, a Boolean reloption cannot be given an
- * out-of-range default, so we need another way to discover the source of
- * its value. This offset is only used if given a value greater than
- * zero.
- */
- int isset_offset;
} relopt_parse_elt;
/* Local reloption definition */
@@ -195,6 +190,8 @@ typedef struct local_relopts
extern relopt_kind add_reloption_kind(void);
extern void add_bool_reloption(bits32 kinds, const char *name, const char *desc,
bool default_val, LOCKMODE lockmode);
+extern void add_ternary_reloption(bits32 kinds, const char *name,
+ const char *desc, int default_val, LOCKMODE lockmode);
extern void add_int_reloption(bits32 kinds, const char *name, const char *desc,
int default_val, int min_val, int max_val,
LOCKMODE lockmode);
@@ -214,6 +211,9 @@ extern void register_reloptions_validator(local_relopts *relopts,
extern void add_local_bool_reloption(local_relopts *relopts, const char *name,
const char *desc, bool default_val,
int offset);
+extern void add_local_ternary_reloption(local_relopts *relopts,
+ const char *name, const char *desc,
+ ternary default_val, int offset);
extern void add_local_int_reloption(local_relopts *relopts, const char *name,
const char *desc, int default_val,
int min_val, int max_val, int offset);
diff --git a/src/include/c.h b/src/include/c.h
index 39022f8a9d..9c59ccccf8 100644
--- a/src/include/c.h
+++ b/src/include/c.h
@@ -488,6 +488,22 @@ typedef void (*pg_funcptr_t) (void);
#include <stdbool.h>
+/*
+ * ternary
+ * Boolean value with an extrea "unset" option
+ *
+ * Ternary data type is used in relation options that can be "true", "false" or
+ * "unset". Since relation options are used deep inside the PostgreSQL code,
+ * this type is declared globally.
+*/
+
+typedef enum ternary
+{
+ TERNARY_FALSE = 0,
+ TERNARY_TRUE = 1,
+ TERNARY_UNSET = -1
+} ternary;
+
/* ----------------------------------------------------------------
* Section 3: standard system types
diff --git a/src/include/utils/rel.h b/src/include/utils/rel.h
index b552359915..7346d618f9 100644
--- a/src/include/utils/rel.h
+++ b/src/include/utils/rel.h
@@ -346,8 +346,7 @@ typedef struct StdRdOptions
bool user_catalog_table; /* use as an additional catalog relation */
int parallel_workers; /* max number of parallel workers */
StdRdOptIndexCleanup vacuum_index_cleanup; /* controls index vacuuming */
- bool vacuum_truncate; /* enables vacuum to truncate a relation */
- bool vacuum_truncate_set; /* whether vacuum_truncate is set */
+ ternary vacuum_truncate; /* enables vacuum to truncate a relation */
/*
* Fraction of pages in a relation that vacuum can eagerly scan and fail
--
2.39.2
--nextPart4155600.3ZeAukHxDK
Content-Disposition: attachment;
filename="v2a-0003-Add-alias-to-be-used-as-unset-state.patch"
Content-Transfer-Encoding: 7Bit
Content-Type: text/x-patch; charset="unicode-2-0-utf-8";
name="v2a-0003-Add-alias-to-be-used-as-unset-state.patch"
^ permalink raw reply [nested|flat] 868+ messages in thread
* [PATCH v2a 2/4] Introduce ternary reloptions
@ 2025-09-04 14:41 Nikolay Shaplov <[email protected]>
0 siblings, 0 replies; 868+ messages in thread
From: Nikolay Shaplov @ 2025-09-04 14:41 UTC (permalink / raw)
Introduce ternary reloption as a replacement for current `vacuum_truncate`
implementation. Remove `vacuum_truncate_set` additional flag and using
`TERNARY_UNSET` value instead.
---
src/backend/access/common/reloptions.c | 129 ++++++++++++++++++++-----
src/backend/commands/vacuum.c | 4 +-
src/include/access/reloptions.h | 26 ++---
src/include/c.h | 16 +++
src/include/utils/rel.h | 3 +-
5 files changed, 135 insertions(+), 43 deletions(-)
diff --git a/src/backend/access/common/reloptions.c b/src/backend/access/common/reloptions.c
index 0af3fea68f..f543f11001 100644
--- a/src/backend/access/common/reloptions.c
+++ b/src/backend/access/common/reloptions.c
@@ -40,9 +40,9 @@
*
* To add an option:
*
- * (i) decide on a type (bool, integer, real, enum, string), name, default
- * value, upper and lower bounds (if applicable); for strings, consider a
- * validation routine.
+ * (i) decide on a type (bool, ternary, integer, real, enum, string), name,
+ * default value, upper and lower bounds (if applicable); for strings,
+ * consider a validation routine.
* (ii) add a record below (or use add_<type>_reloption).
* (iii) add it to the appropriate options struct (perhaps StdRdOptions)
* (iv) add it to the appropriate handling routine (perhaps
@@ -147,15 +147,6 @@ static relopt_bool boolRelOpts[] =
},
false
},
- {
- {
- "vacuum_truncate",
- "Enables vacuum to truncate empty pages at the end of this table",
- RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
- ShareUpdateExclusiveLock
- },
- true
- },
{
{
"deduplicate_items",
@@ -170,6 +161,21 @@ static relopt_bool boolRelOpts[] =
{{NULL}}
};
+static relopt_ternary ternaryRelOpts[] =
+{
+ {
+ {
+ "vacuum_truncate",
+ "Enables vacuum to truncate empty pages at the end of this table",
+ RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
+ ShareUpdateExclusiveLock
+ },
+ TERNARY_UNSET
+ },
+ /* list terminator */
+ {{NULL}}
+};
+
static relopt_int intRelOpts[] =
{
{
@@ -600,6 +606,13 @@ initialize_reloptions(void)
boolRelOpts[i].gen.lockmode));
j++;
}
+ for (i = 0; ternaryRelOpts[i].gen.name; i++)
+ {
+ Assert(DoLockModesConflict(ternaryRelOpts[i].gen.lockmode,
+ ternaryRelOpts[i].gen.lockmode));
+ j++;
+ }
+
for (i = 0; intRelOpts[i].gen.name; i++)
{
Assert(DoLockModesConflict(intRelOpts[i].gen.lockmode,
@@ -640,6 +653,14 @@ initialize_reloptions(void)
j++;
}
+ for (i = 0; ternaryRelOpts[i].gen.name; i++)
+ {
+ relOpts[j] = &ternaryRelOpts[i].gen;
+ relOpts[j]->type = RELOPT_TYPE_TERNARY;
+ relOpts[j]->namelen = strlen(relOpts[j]->name);
+ j++;
+ }
+
for (i = 0; intRelOpts[i].gen.name; i++)
{
relOpts[j] = &intRelOpts[i].gen;
@@ -800,6 +821,9 @@ allocate_reloption(bits32 kinds, int type, const char *name, const char *desc,
case RELOPT_TYPE_BOOL:
size = sizeof(relopt_bool);
break;
+ case RELOPT_TYPE_TERNARY:
+ size = sizeof(relopt_ternary);
+ break;
case RELOPT_TYPE_INT:
size = sizeof(relopt_int);
break;
@@ -883,6 +907,54 @@ add_local_bool_reloption(local_relopts *relopts, const char *name,
add_local_reloption(relopts, (relopt_gen *) newoption, offset);
}
+/*
+ * init_ternary_reloption
+ * Allocate and initialize a new ternary reloption
+ */
+static relopt_ternary *
+init_ternary_reloption(bits32 kinds, const char *name, const char *desc,
+ ternary default_val, LOCKMODE lockmode)
+{
+ relopt_ternary *newoption;
+
+ newoption = (relopt_ternary *) allocate_reloption(kinds,
+ RELOPT_TYPE_TERNARY, name, desc, lockmode);
+ newoption->default_val = default_val;
+
+ return newoption;
+}
+
+/*
+ * add_ternary_reloption
+ * Add a new ternary reloption
+ */
+void
+add_ternary_reloption(bits32 kinds, const char *name, const char *desc,
+ ternary default_val, LOCKMODE lockmode)
+{
+ relopt_ternary *newoption = init_ternary_reloption(kinds, name, desc,
+ default_val, lockmode);
+
+ add_reloption((relopt_gen *) newoption);
+}
+
+/*
+ * add_local_ternary_reloption
+ * Add a new ternary local reloption
+ *
+ * 'offset' is offset of ternary-typed field.
+ */
+void
+add_local_ternary_reloption(local_relopts *relopts, const char *name,
+ const char *desc, ternary default_val,
+ int offset)
+{
+ relopt_ternary *newoption = init_ternary_reloption(RELOPT_KIND_LOCAL,
+ name, desc,
+ default_val, 0);
+
+ add_local_reloption(relopts, (relopt_gen *) newoption, offset);
+}
/*
* init_real_reloption
@@ -1617,6 +1689,18 @@ parse_one_reloption(relopt_value *option, char *text_str, int text_len,
option->gen->name, value)));
}
break;
+ case RELOPT_TYPE_TERNARY:
+ {
+ bool b;
+ parsed = parse_bool(value, &b);
+ option->values.ternary_val = b ? TERNARY_TRUE : TERNARY_FALSE;
+ if (validate && !parsed)
+ ereport(ERROR,
+ (errcode(ERRCODE_INVALID_PARAMETER_VALUE),
+ errmsg("invalid value for ternary option \"%s\": %s",
+ option->gen->name, value)));
+ }
+ break;
case RELOPT_TYPE_INT:
{
relopt_int *optint = (relopt_int *) option->gen;
@@ -1780,17 +1864,6 @@ fillRelOptions(void *rdopts, Size basesize,
char *itempos = ((char *) rdopts) + elems[j].offset;
char *string_val;
- /*
- * If isset_offset is provided, store whether the reloption is
- * set there.
- */
- if (elems[j].isset_offset > 0)
- {
- char *setpos = ((char *) rdopts) + elems[j].isset_offset;
-
- *(bool *) setpos = options[i].isset;
- }
-
switch (options[i].gen->type)
{
case RELOPT_TYPE_BOOL:
@@ -1798,6 +1871,11 @@ fillRelOptions(void *rdopts, Size basesize,
options[i].values.bool_val :
((relopt_bool *) options[i].gen)->default_val;
break;
+ case RELOPT_TYPE_TERNARY:
+ *(ternary *) itempos = options[i].isset ?
+ options[i].values.ternary_val :
+ ((relopt_ternary *) options[i].gen)->default_val;
+ break;
case RELOPT_TYPE_INT:
*(int *) itempos = options[i].isset ?
options[i].values.int_val :
@@ -1912,8 +1990,8 @@ default_reloptions(Datum reloptions, bool validate, relopt_kind kind)
offsetof(StdRdOptions, parallel_workers)},
{"vacuum_index_cleanup", RELOPT_TYPE_ENUM,
offsetof(StdRdOptions, vacuum_index_cleanup)},
- {"vacuum_truncate", RELOPT_TYPE_BOOL,
- offsetof(StdRdOptions, vacuum_truncate), offsetof(StdRdOptions, vacuum_truncate_set)},
+ {"vacuum_truncate", RELOPT_TYPE_TERNARY,
+ offsetof(StdRdOptions, vacuum_truncate)},
{"vacuum_max_eager_freeze_failure_rate", RELOPT_TYPE_REAL,
offsetof(StdRdOptions, vacuum_max_eager_freeze_failure_rate)}
};
@@ -1993,7 +2071,6 @@ build_local_reloptions(local_relopts *relopts, Datum options, bool validate)
elems[i].optname = opt->option->name;
elems[i].opttype = opt->option->type;
elems[i].offset = opt->offset;
- elems[i].isset_offset = 0; /* not supported for local relopts yet */
i++;
}
diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c
index 733ef40ae7..977babff54 100644
--- a/src/backend/commands/vacuum.c
+++ b/src/backend/commands/vacuum.c
@@ -2221,9 +2221,9 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams params,
{
StdRdOptions *opts = (StdRdOptions *) rel->rd_options;
- if (opts && opts->vacuum_truncate_set)
+ if (opts && opts->vacuum_truncate != TERNARY_UNSET)
{
- if (opts->vacuum_truncate)
+ if (opts->vacuum_truncate == TERNARY_TRUE)
params.truncate = VACOPTVALUE_ENABLED;
else
params.truncate = VACOPTVALUE_DISABLED;
diff --git a/src/include/access/reloptions.h b/src/include/access/reloptions.h
index a604a4702c..a436697658 100644
--- a/src/include/access/reloptions.h
+++ b/src/include/access/reloptions.h
@@ -29,6 +29,7 @@
typedef enum relopt_type
{
RELOPT_TYPE_BOOL,
+ RELOPT_TYPE_TERNARY, /* on, off, unset */
RELOPT_TYPE_INT,
RELOPT_TYPE_REAL,
RELOPT_TYPE_ENUM,
@@ -80,6 +81,7 @@ typedef struct relopt_value
union
{
bool bool_val;
+ ternary ternary_val;
int int_val;
double real_val;
int enum_val;
@@ -94,6 +96,12 @@ typedef struct relopt_bool
bool default_val;
} relopt_bool;
+typedef struct relopt_rernary
+{
+ relopt_gen gen;
+ int default_val;
+} relopt_ternary;
+
typedef struct relopt_int
{
relopt_gen gen;
@@ -152,19 +160,6 @@ typedef struct
const char *optname; /* option's name */
relopt_type opttype; /* option's datatype */
int offset; /* offset of field in result struct */
-
- /*
- * isset_offset is an optional offset of a field in the result struct that
- * stores whether the option is explicitly set for the relation or if it
- * just picked up the default value. In most cases, this can be
- * accomplished by giving the reloption a special out-of-range default
- * value (e.g., some integer reloptions use -2), but this isn't always
- * possible. For example, a Boolean reloption cannot be given an
- * out-of-range default, so we need another way to discover the source of
- * its value. This offset is only used if given a value greater than
- * zero.
- */
- int isset_offset;
} relopt_parse_elt;
/* Local reloption definition */
@@ -195,6 +190,8 @@ typedef struct local_relopts
extern relopt_kind add_reloption_kind(void);
extern void add_bool_reloption(bits32 kinds, const char *name, const char *desc,
bool default_val, LOCKMODE lockmode);
+extern void add_ternary_reloption(bits32 kinds, const char *name,
+ const char *desc, int default_val, LOCKMODE lockmode);
extern void add_int_reloption(bits32 kinds, const char *name, const char *desc,
int default_val, int min_val, int max_val,
LOCKMODE lockmode);
@@ -214,6 +211,9 @@ extern void register_reloptions_validator(local_relopts *relopts,
extern void add_local_bool_reloption(local_relopts *relopts, const char *name,
const char *desc, bool default_val,
int offset);
+extern void add_local_ternary_reloption(local_relopts *relopts,
+ const char *name, const char *desc,
+ ternary default_val, int offset);
extern void add_local_int_reloption(local_relopts *relopts, const char *name,
const char *desc, int default_val,
int min_val, int max_val, int offset);
diff --git a/src/include/c.h b/src/include/c.h
index 39022f8a9d..9c59ccccf8 100644
--- a/src/include/c.h
+++ b/src/include/c.h
@@ -488,6 +488,22 @@ typedef void (*pg_funcptr_t) (void);
#include <stdbool.h>
+/*
+ * ternary
+ * Boolean value with an extrea "unset" option
+ *
+ * Ternary data type is used in relation options that can be "true", "false" or
+ * "unset". Since relation options are used deep inside the PostgreSQL code,
+ * this type is declared globally.
+*/
+
+typedef enum ternary
+{
+ TERNARY_FALSE = 0,
+ TERNARY_TRUE = 1,
+ TERNARY_UNSET = -1
+} ternary;
+
/* ----------------------------------------------------------------
* Section 3: standard system types
diff --git a/src/include/utils/rel.h b/src/include/utils/rel.h
index b552359915..7346d618f9 100644
--- a/src/include/utils/rel.h
+++ b/src/include/utils/rel.h
@@ -346,8 +346,7 @@ typedef struct StdRdOptions
bool user_catalog_table; /* use as an additional catalog relation */
int parallel_workers; /* max number of parallel workers */
StdRdOptIndexCleanup vacuum_index_cleanup; /* controls index vacuuming */
- bool vacuum_truncate; /* enables vacuum to truncate a relation */
- bool vacuum_truncate_set; /* whether vacuum_truncate is set */
+ ternary vacuum_truncate; /* enables vacuum to truncate a relation */
/*
* Fraction of pages in a relation that vacuum can eagerly scan and fail
--
2.39.2
--nextPart4155600.3ZeAukHxDK
Content-Disposition: attachment;
filename="v2a-0003-Add-alias-to-be-used-as-unset-state.patch"
Content-Transfer-Encoding: 7Bit
Content-Type: text/x-patch; charset="unicode-2-0-utf-8";
name="v2a-0003-Add-alias-to-be-used-as-unset-state.patch"
^ permalink raw reply [nested|flat] 868+ messages in thread
* [PATCH v2 2/4] Introduce ternary reloptions
@ 2025-09-04 14:41 Nikolay Shaplov <[email protected]>
0 siblings, 0 replies; 868+ messages in thread
From: Nikolay Shaplov @ 2025-09-04 14:41 UTC (permalink / raw)
Introduce ternary reloption as a replacement for current `vacuum_truncate`
implementation. Remove `vacuum_truncate_set` additional flag and using
`TERNARY_UNSET` value instead.
---
src/backend/access/common/reloptions.c | 129 ++++++++++++++++++++-----
src/backend/commands/vacuum.c | 4 +-
src/include/access/reloptions.h | 26 ++---
src/include/c.h | 16 +++
src/include/utils/rel.h | 3 +-
5 files changed, 135 insertions(+), 43 deletions(-)
diff --git a/src/backend/access/common/reloptions.c b/src/backend/access/common/reloptions.c
index 0af3fea68f..f543f11001 100644
--- a/src/backend/access/common/reloptions.c
+++ b/src/backend/access/common/reloptions.c
@@ -40,9 +40,9 @@
*
* To add an option:
*
- * (i) decide on a type (bool, integer, real, enum, string), name, default
- * value, upper and lower bounds (if applicable); for strings, consider a
- * validation routine.
+ * (i) decide on a type (bool, ternary, integer, real, enum, string), name,
+ * default value, upper and lower bounds (if applicable); for strings,
+ * consider a validation routine.
* (ii) add a record below (or use add_<type>_reloption).
* (iii) add it to the appropriate options struct (perhaps StdRdOptions)
* (iv) add it to the appropriate handling routine (perhaps
@@ -147,15 +147,6 @@ static relopt_bool boolRelOpts[] =
},
false
},
- {
- {
- "vacuum_truncate",
- "Enables vacuum to truncate empty pages at the end of this table",
- RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
- ShareUpdateExclusiveLock
- },
- true
- },
{
{
"deduplicate_items",
@@ -170,6 +161,21 @@ static relopt_bool boolRelOpts[] =
{{NULL}}
};
+static relopt_ternary ternaryRelOpts[] =
+{
+ {
+ {
+ "vacuum_truncate",
+ "Enables vacuum to truncate empty pages at the end of this table",
+ RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
+ ShareUpdateExclusiveLock
+ },
+ TERNARY_UNSET
+ },
+ /* list terminator */
+ {{NULL}}
+};
+
static relopt_int intRelOpts[] =
{
{
@@ -600,6 +606,13 @@ initialize_reloptions(void)
boolRelOpts[i].gen.lockmode));
j++;
}
+ for (i = 0; ternaryRelOpts[i].gen.name; i++)
+ {
+ Assert(DoLockModesConflict(ternaryRelOpts[i].gen.lockmode,
+ ternaryRelOpts[i].gen.lockmode));
+ j++;
+ }
+
for (i = 0; intRelOpts[i].gen.name; i++)
{
Assert(DoLockModesConflict(intRelOpts[i].gen.lockmode,
@@ -640,6 +653,14 @@ initialize_reloptions(void)
j++;
}
+ for (i = 0; ternaryRelOpts[i].gen.name; i++)
+ {
+ relOpts[j] = &ternaryRelOpts[i].gen;
+ relOpts[j]->type = RELOPT_TYPE_TERNARY;
+ relOpts[j]->namelen = strlen(relOpts[j]->name);
+ j++;
+ }
+
for (i = 0; intRelOpts[i].gen.name; i++)
{
relOpts[j] = &intRelOpts[i].gen;
@@ -800,6 +821,9 @@ allocate_reloption(bits32 kinds, int type, const char *name, const char *desc,
case RELOPT_TYPE_BOOL:
size = sizeof(relopt_bool);
break;
+ case RELOPT_TYPE_TERNARY:
+ size = sizeof(relopt_ternary);
+ break;
case RELOPT_TYPE_INT:
size = sizeof(relopt_int);
break;
@@ -883,6 +907,54 @@ add_local_bool_reloption(local_relopts *relopts, const char *name,
add_local_reloption(relopts, (relopt_gen *) newoption, offset);
}
+/*
+ * init_ternary_reloption
+ * Allocate and initialize a new ternary reloption
+ */
+static relopt_ternary *
+init_ternary_reloption(bits32 kinds, const char *name, const char *desc,
+ ternary default_val, LOCKMODE lockmode)
+{
+ relopt_ternary *newoption;
+
+ newoption = (relopt_ternary *) allocate_reloption(kinds,
+ RELOPT_TYPE_TERNARY, name, desc, lockmode);
+ newoption->default_val = default_val;
+
+ return newoption;
+}
+
+/*
+ * add_ternary_reloption
+ * Add a new ternary reloption
+ */
+void
+add_ternary_reloption(bits32 kinds, const char *name, const char *desc,
+ ternary default_val, LOCKMODE lockmode)
+{
+ relopt_ternary *newoption = init_ternary_reloption(kinds, name, desc,
+ default_val, lockmode);
+
+ add_reloption((relopt_gen *) newoption);
+}
+
+/*
+ * add_local_ternary_reloption
+ * Add a new ternary local reloption
+ *
+ * 'offset' is offset of ternary-typed field.
+ */
+void
+add_local_ternary_reloption(local_relopts *relopts, const char *name,
+ const char *desc, ternary default_val,
+ int offset)
+{
+ relopt_ternary *newoption = init_ternary_reloption(RELOPT_KIND_LOCAL,
+ name, desc,
+ default_val, 0);
+
+ add_local_reloption(relopts, (relopt_gen *) newoption, offset);
+}
/*
* init_real_reloption
@@ -1617,6 +1689,18 @@ parse_one_reloption(relopt_value *option, char *text_str, int text_len,
option->gen->name, value)));
}
break;
+ case RELOPT_TYPE_TERNARY:
+ {
+ bool b;
+ parsed = parse_bool(value, &b);
+ option->values.ternary_val = b ? TERNARY_TRUE : TERNARY_FALSE;
+ if (validate && !parsed)
+ ereport(ERROR,
+ (errcode(ERRCODE_INVALID_PARAMETER_VALUE),
+ errmsg("invalid value for ternary option \"%s\": %s",
+ option->gen->name, value)));
+ }
+ break;
case RELOPT_TYPE_INT:
{
relopt_int *optint = (relopt_int *) option->gen;
@@ -1780,17 +1864,6 @@ fillRelOptions(void *rdopts, Size basesize,
char *itempos = ((char *) rdopts) + elems[j].offset;
char *string_val;
- /*
- * If isset_offset is provided, store whether the reloption is
- * set there.
- */
- if (elems[j].isset_offset > 0)
- {
- char *setpos = ((char *) rdopts) + elems[j].isset_offset;
-
- *(bool *) setpos = options[i].isset;
- }
-
switch (options[i].gen->type)
{
case RELOPT_TYPE_BOOL:
@@ -1798,6 +1871,11 @@ fillRelOptions(void *rdopts, Size basesize,
options[i].values.bool_val :
((relopt_bool *) options[i].gen)->default_val;
break;
+ case RELOPT_TYPE_TERNARY:
+ *(ternary *) itempos = options[i].isset ?
+ options[i].values.ternary_val :
+ ((relopt_ternary *) options[i].gen)->default_val;
+ break;
case RELOPT_TYPE_INT:
*(int *) itempos = options[i].isset ?
options[i].values.int_val :
@@ -1912,8 +1990,8 @@ default_reloptions(Datum reloptions, bool validate, relopt_kind kind)
offsetof(StdRdOptions, parallel_workers)},
{"vacuum_index_cleanup", RELOPT_TYPE_ENUM,
offsetof(StdRdOptions, vacuum_index_cleanup)},
- {"vacuum_truncate", RELOPT_TYPE_BOOL,
- offsetof(StdRdOptions, vacuum_truncate), offsetof(StdRdOptions, vacuum_truncate_set)},
+ {"vacuum_truncate", RELOPT_TYPE_TERNARY,
+ offsetof(StdRdOptions, vacuum_truncate)},
{"vacuum_max_eager_freeze_failure_rate", RELOPT_TYPE_REAL,
offsetof(StdRdOptions, vacuum_max_eager_freeze_failure_rate)}
};
@@ -1993,7 +2071,6 @@ build_local_reloptions(local_relopts *relopts, Datum options, bool validate)
elems[i].optname = opt->option->name;
elems[i].opttype = opt->option->type;
elems[i].offset = opt->offset;
- elems[i].isset_offset = 0; /* not supported for local relopts yet */
i++;
}
diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c
index 733ef40ae7..977babff54 100644
--- a/src/backend/commands/vacuum.c
+++ b/src/backend/commands/vacuum.c
@@ -2221,9 +2221,9 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams params,
{
StdRdOptions *opts = (StdRdOptions *) rel->rd_options;
- if (opts && opts->vacuum_truncate_set)
+ if (opts && opts->vacuum_truncate != TERNARY_UNSET)
{
- if (opts->vacuum_truncate)
+ if (opts->vacuum_truncate == TERNARY_TRUE)
params.truncate = VACOPTVALUE_ENABLED;
else
params.truncate = VACOPTVALUE_DISABLED;
diff --git a/src/include/access/reloptions.h b/src/include/access/reloptions.h
index a604a4702c..a436697658 100644
--- a/src/include/access/reloptions.h
+++ b/src/include/access/reloptions.h
@@ -29,6 +29,7 @@
typedef enum relopt_type
{
RELOPT_TYPE_BOOL,
+ RELOPT_TYPE_TERNARY, /* on, off, unset */
RELOPT_TYPE_INT,
RELOPT_TYPE_REAL,
RELOPT_TYPE_ENUM,
@@ -80,6 +81,7 @@ typedef struct relopt_value
union
{
bool bool_val;
+ ternary ternary_val;
int int_val;
double real_val;
int enum_val;
@@ -94,6 +96,12 @@ typedef struct relopt_bool
bool default_val;
} relopt_bool;
+typedef struct relopt_rernary
+{
+ relopt_gen gen;
+ int default_val;
+} relopt_ternary;
+
typedef struct relopt_int
{
relopt_gen gen;
@@ -152,19 +160,6 @@ typedef struct
const char *optname; /* option's name */
relopt_type opttype; /* option's datatype */
int offset; /* offset of field in result struct */
-
- /*
- * isset_offset is an optional offset of a field in the result struct that
- * stores whether the option is explicitly set for the relation or if it
- * just picked up the default value. In most cases, this can be
- * accomplished by giving the reloption a special out-of-range default
- * value (e.g., some integer reloptions use -2), but this isn't always
- * possible. For example, a Boolean reloption cannot be given an
- * out-of-range default, so we need another way to discover the source of
- * its value. This offset is only used if given a value greater than
- * zero.
- */
- int isset_offset;
} relopt_parse_elt;
/* Local reloption definition */
@@ -195,6 +190,8 @@ typedef struct local_relopts
extern relopt_kind add_reloption_kind(void);
extern void add_bool_reloption(bits32 kinds, const char *name, const char *desc,
bool default_val, LOCKMODE lockmode);
+extern void add_ternary_reloption(bits32 kinds, const char *name,
+ const char *desc, int default_val, LOCKMODE lockmode);
extern void add_int_reloption(bits32 kinds, const char *name, const char *desc,
int default_val, int min_val, int max_val,
LOCKMODE lockmode);
@@ -214,6 +211,9 @@ extern void register_reloptions_validator(local_relopts *relopts,
extern void add_local_bool_reloption(local_relopts *relopts, const char *name,
const char *desc, bool default_val,
int offset);
+extern void add_local_ternary_reloption(local_relopts *relopts,
+ const char *name, const char *desc,
+ ternary default_val, int offset);
extern void add_local_int_reloption(local_relopts *relopts, const char *name,
const char *desc, int default_val,
int min_val, int max_val, int offset);
diff --git a/src/include/c.h b/src/include/c.h
index 39022f8a9d..9c59ccccf8 100644
--- a/src/include/c.h
+++ b/src/include/c.h
@@ -488,6 +488,22 @@ typedef void (*pg_funcptr_t) (void);
#include <stdbool.h>
+/*
+ * ternary
+ * Boolean value with an extrea "unset" option
+ *
+ * Ternary data type is used in relation options that can be "true", "false" or
+ * "unset". Since relation options are used deep inside the PostgreSQL code,
+ * this type is declared globally.
+*/
+
+typedef enum ternary
+{
+ TERNARY_FALSE = 0,
+ TERNARY_TRUE = 1,
+ TERNARY_UNSET = -1
+} ternary;
+
/* ----------------------------------------------------------------
* Section 3: standard system types
diff --git a/src/include/utils/rel.h b/src/include/utils/rel.h
index b552359915..7346d618f9 100644
--- a/src/include/utils/rel.h
+++ b/src/include/utils/rel.h
@@ -346,8 +346,7 @@ typedef struct StdRdOptions
bool user_catalog_table; /* use as an additional catalog relation */
int parallel_workers; /* max number of parallel workers */
StdRdOptIndexCleanup vacuum_index_cleanup; /* controls index vacuuming */
- bool vacuum_truncate; /* enables vacuum to truncate a relation */
- bool vacuum_truncate_set; /* whether vacuum_truncate is set */
+ ternary vacuum_truncate; /* enables vacuum to truncate a relation */
/*
* Fraction of pages in a relation that vacuum can eagerly scan and fail
--
2.39.2
--nextPart5129932.5fSG56mABF
Content-Disposition: attachment;
filename="v2-0003-Add-alias-to-be-used-as-unset-state.patch"
Content-Transfer-Encoding: 7Bit
Content-Type: text/x-patch; charset="unicode-2-0-utf-8";
name="v2-0003-Add-alias-to-be-used-as-unset-state.patch"
^ permalink raw reply [nested|flat] 868+ messages in thread
* [PATCH v2a 2/4] Introduce ternary reloptions
@ 2025-09-04 14:41 Nikolay Shaplov <[email protected]>
0 siblings, 0 replies; 868+ messages in thread
From: Nikolay Shaplov @ 2025-09-04 14:41 UTC (permalink / raw)
Introduce ternary reloption as a replacement for current `vacuum_truncate`
implementation. Remove `vacuum_truncate_set` additional flag and using
`TERNARY_UNSET` value instead.
---
src/backend/access/common/reloptions.c | 129 ++++++++++++++++++++-----
src/backend/commands/vacuum.c | 4 +-
src/include/access/reloptions.h | 26 ++---
src/include/c.h | 16 +++
src/include/utils/rel.h | 3 +-
5 files changed, 135 insertions(+), 43 deletions(-)
diff --git a/src/backend/access/common/reloptions.c b/src/backend/access/common/reloptions.c
index 0af3fea68f..f543f11001 100644
--- a/src/backend/access/common/reloptions.c
+++ b/src/backend/access/common/reloptions.c
@@ -40,9 +40,9 @@
*
* To add an option:
*
- * (i) decide on a type (bool, integer, real, enum, string), name, default
- * value, upper and lower bounds (if applicable); for strings, consider a
- * validation routine.
+ * (i) decide on a type (bool, ternary, integer, real, enum, string), name,
+ * default value, upper and lower bounds (if applicable); for strings,
+ * consider a validation routine.
* (ii) add a record below (or use add_<type>_reloption).
* (iii) add it to the appropriate options struct (perhaps StdRdOptions)
* (iv) add it to the appropriate handling routine (perhaps
@@ -147,15 +147,6 @@ static relopt_bool boolRelOpts[] =
},
false
},
- {
- {
- "vacuum_truncate",
- "Enables vacuum to truncate empty pages at the end of this table",
- RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
- ShareUpdateExclusiveLock
- },
- true
- },
{
{
"deduplicate_items",
@@ -170,6 +161,21 @@ static relopt_bool boolRelOpts[] =
{{NULL}}
};
+static relopt_ternary ternaryRelOpts[] =
+{
+ {
+ {
+ "vacuum_truncate",
+ "Enables vacuum to truncate empty pages at the end of this table",
+ RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
+ ShareUpdateExclusiveLock
+ },
+ TERNARY_UNSET
+ },
+ /* list terminator */
+ {{NULL}}
+};
+
static relopt_int intRelOpts[] =
{
{
@@ -600,6 +606,13 @@ initialize_reloptions(void)
boolRelOpts[i].gen.lockmode));
j++;
}
+ for (i = 0; ternaryRelOpts[i].gen.name; i++)
+ {
+ Assert(DoLockModesConflict(ternaryRelOpts[i].gen.lockmode,
+ ternaryRelOpts[i].gen.lockmode));
+ j++;
+ }
+
for (i = 0; intRelOpts[i].gen.name; i++)
{
Assert(DoLockModesConflict(intRelOpts[i].gen.lockmode,
@@ -640,6 +653,14 @@ initialize_reloptions(void)
j++;
}
+ for (i = 0; ternaryRelOpts[i].gen.name; i++)
+ {
+ relOpts[j] = &ternaryRelOpts[i].gen;
+ relOpts[j]->type = RELOPT_TYPE_TERNARY;
+ relOpts[j]->namelen = strlen(relOpts[j]->name);
+ j++;
+ }
+
for (i = 0; intRelOpts[i].gen.name; i++)
{
relOpts[j] = &intRelOpts[i].gen;
@@ -800,6 +821,9 @@ allocate_reloption(bits32 kinds, int type, const char *name, const char *desc,
case RELOPT_TYPE_BOOL:
size = sizeof(relopt_bool);
break;
+ case RELOPT_TYPE_TERNARY:
+ size = sizeof(relopt_ternary);
+ break;
case RELOPT_TYPE_INT:
size = sizeof(relopt_int);
break;
@@ -883,6 +907,54 @@ add_local_bool_reloption(local_relopts *relopts, const char *name,
add_local_reloption(relopts, (relopt_gen *) newoption, offset);
}
+/*
+ * init_ternary_reloption
+ * Allocate and initialize a new ternary reloption
+ */
+static relopt_ternary *
+init_ternary_reloption(bits32 kinds, const char *name, const char *desc,
+ ternary default_val, LOCKMODE lockmode)
+{
+ relopt_ternary *newoption;
+
+ newoption = (relopt_ternary *) allocate_reloption(kinds,
+ RELOPT_TYPE_TERNARY, name, desc, lockmode);
+ newoption->default_val = default_val;
+
+ return newoption;
+}
+
+/*
+ * add_ternary_reloption
+ * Add a new ternary reloption
+ */
+void
+add_ternary_reloption(bits32 kinds, const char *name, const char *desc,
+ ternary default_val, LOCKMODE lockmode)
+{
+ relopt_ternary *newoption = init_ternary_reloption(kinds, name, desc,
+ default_val, lockmode);
+
+ add_reloption((relopt_gen *) newoption);
+}
+
+/*
+ * add_local_ternary_reloption
+ * Add a new ternary local reloption
+ *
+ * 'offset' is offset of ternary-typed field.
+ */
+void
+add_local_ternary_reloption(local_relopts *relopts, const char *name,
+ const char *desc, ternary default_val,
+ int offset)
+{
+ relopt_ternary *newoption = init_ternary_reloption(RELOPT_KIND_LOCAL,
+ name, desc,
+ default_val, 0);
+
+ add_local_reloption(relopts, (relopt_gen *) newoption, offset);
+}
/*
* init_real_reloption
@@ -1617,6 +1689,18 @@ parse_one_reloption(relopt_value *option, char *text_str, int text_len,
option->gen->name, value)));
}
break;
+ case RELOPT_TYPE_TERNARY:
+ {
+ bool b;
+ parsed = parse_bool(value, &b);
+ option->values.ternary_val = b ? TERNARY_TRUE : TERNARY_FALSE;
+ if (validate && !parsed)
+ ereport(ERROR,
+ (errcode(ERRCODE_INVALID_PARAMETER_VALUE),
+ errmsg("invalid value for ternary option \"%s\": %s",
+ option->gen->name, value)));
+ }
+ break;
case RELOPT_TYPE_INT:
{
relopt_int *optint = (relopt_int *) option->gen;
@@ -1780,17 +1864,6 @@ fillRelOptions(void *rdopts, Size basesize,
char *itempos = ((char *) rdopts) + elems[j].offset;
char *string_val;
- /*
- * If isset_offset is provided, store whether the reloption is
- * set there.
- */
- if (elems[j].isset_offset > 0)
- {
- char *setpos = ((char *) rdopts) + elems[j].isset_offset;
-
- *(bool *) setpos = options[i].isset;
- }
-
switch (options[i].gen->type)
{
case RELOPT_TYPE_BOOL:
@@ -1798,6 +1871,11 @@ fillRelOptions(void *rdopts, Size basesize,
options[i].values.bool_val :
((relopt_bool *) options[i].gen)->default_val;
break;
+ case RELOPT_TYPE_TERNARY:
+ *(ternary *) itempos = options[i].isset ?
+ options[i].values.ternary_val :
+ ((relopt_ternary *) options[i].gen)->default_val;
+ break;
case RELOPT_TYPE_INT:
*(int *) itempos = options[i].isset ?
options[i].values.int_val :
@@ -1912,8 +1990,8 @@ default_reloptions(Datum reloptions, bool validate, relopt_kind kind)
offsetof(StdRdOptions, parallel_workers)},
{"vacuum_index_cleanup", RELOPT_TYPE_ENUM,
offsetof(StdRdOptions, vacuum_index_cleanup)},
- {"vacuum_truncate", RELOPT_TYPE_BOOL,
- offsetof(StdRdOptions, vacuum_truncate), offsetof(StdRdOptions, vacuum_truncate_set)},
+ {"vacuum_truncate", RELOPT_TYPE_TERNARY,
+ offsetof(StdRdOptions, vacuum_truncate)},
{"vacuum_max_eager_freeze_failure_rate", RELOPT_TYPE_REAL,
offsetof(StdRdOptions, vacuum_max_eager_freeze_failure_rate)}
};
@@ -1993,7 +2071,6 @@ build_local_reloptions(local_relopts *relopts, Datum options, bool validate)
elems[i].optname = opt->option->name;
elems[i].opttype = opt->option->type;
elems[i].offset = opt->offset;
- elems[i].isset_offset = 0; /* not supported for local relopts yet */
i++;
}
diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c
index 733ef40ae7..977babff54 100644
--- a/src/backend/commands/vacuum.c
+++ b/src/backend/commands/vacuum.c
@@ -2221,9 +2221,9 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams params,
{
StdRdOptions *opts = (StdRdOptions *) rel->rd_options;
- if (opts && opts->vacuum_truncate_set)
+ if (opts && opts->vacuum_truncate != TERNARY_UNSET)
{
- if (opts->vacuum_truncate)
+ if (opts->vacuum_truncate == TERNARY_TRUE)
params.truncate = VACOPTVALUE_ENABLED;
else
params.truncate = VACOPTVALUE_DISABLED;
diff --git a/src/include/access/reloptions.h b/src/include/access/reloptions.h
index a604a4702c..a436697658 100644
--- a/src/include/access/reloptions.h
+++ b/src/include/access/reloptions.h
@@ -29,6 +29,7 @@
typedef enum relopt_type
{
RELOPT_TYPE_BOOL,
+ RELOPT_TYPE_TERNARY, /* on, off, unset */
RELOPT_TYPE_INT,
RELOPT_TYPE_REAL,
RELOPT_TYPE_ENUM,
@@ -80,6 +81,7 @@ typedef struct relopt_value
union
{
bool bool_val;
+ ternary ternary_val;
int int_val;
double real_val;
int enum_val;
@@ -94,6 +96,12 @@ typedef struct relopt_bool
bool default_val;
} relopt_bool;
+typedef struct relopt_rernary
+{
+ relopt_gen gen;
+ int default_val;
+} relopt_ternary;
+
typedef struct relopt_int
{
relopt_gen gen;
@@ -152,19 +160,6 @@ typedef struct
const char *optname; /* option's name */
relopt_type opttype; /* option's datatype */
int offset; /* offset of field in result struct */
-
- /*
- * isset_offset is an optional offset of a field in the result struct that
- * stores whether the option is explicitly set for the relation or if it
- * just picked up the default value. In most cases, this can be
- * accomplished by giving the reloption a special out-of-range default
- * value (e.g., some integer reloptions use -2), but this isn't always
- * possible. For example, a Boolean reloption cannot be given an
- * out-of-range default, so we need another way to discover the source of
- * its value. This offset is only used if given a value greater than
- * zero.
- */
- int isset_offset;
} relopt_parse_elt;
/* Local reloption definition */
@@ -195,6 +190,8 @@ typedef struct local_relopts
extern relopt_kind add_reloption_kind(void);
extern void add_bool_reloption(bits32 kinds, const char *name, const char *desc,
bool default_val, LOCKMODE lockmode);
+extern void add_ternary_reloption(bits32 kinds, const char *name,
+ const char *desc, int default_val, LOCKMODE lockmode);
extern void add_int_reloption(bits32 kinds, const char *name, const char *desc,
int default_val, int min_val, int max_val,
LOCKMODE lockmode);
@@ -214,6 +211,9 @@ extern void register_reloptions_validator(local_relopts *relopts,
extern void add_local_bool_reloption(local_relopts *relopts, const char *name,
const char *desc, bool default_val,
int offset);
+extern void add_local_ternary_reloption(local_relopts *relopts,
+ const char *name, const char *desc,
+ ternary default_val, int offset);
extern void add_local_int_reloption(local_relopts *relopts, const char *name,
const char *desc, int default_val,
int min_val, int max_val, int offset);
diff --git a/src/include/c.h b/src/include/c.h
index 39022f8a9d..9c59ccccf8 100644
--- a/src/include/c.h
+++ b/src/include/c.h
@@ -488,6 +488,22 @@ typedef void (*pg_funcptr_t) (void);
#include <stdbool.h>
+/*
+ * ternary
+ * Boolean value with an extrea "unset" option
+ *
+ * Ternary data type is used in relation options that can be "true", "false" or
+ * "unset". Since relation options are used deep inside the PostgreSQL code,
+ * this type is declared globally.
+*/
+
+typedef enum ternary
+{
+ TERNARY_FALSE = 0,
+ TERNARY_TRUE = 1,
+ TERNARY_UNSET = -1
+} ternary;
+
/* ----------------------------------------------------------------
* Section 3: standard system types
diff --git a/src/include/utils/rel.h b/src/include/utils/rel.h
index b552359915..7346d618f9 100644
--- a/src/include/utils/rel.h
+++ b/src/include/utils/rel.h
@@ -346,8 +346,7 @@ typedef struct StdRdOptions
bool user_catalog_table; /* use as an additional catalog relation */
int parallel_workers; /* max number of parallel workers */
StdRdOptIndexCleanup vacuum_index_cleanup; /* controls index vacuuming */
- bool vacuum_truncate; /* enables vacuum to truncate a relation */
- bool vacuum_truncate_set; /* whether vacuum_truncate is set */
+ ternary vacuum_truncate; /* enables vacuum to truncate a relation */
/*
* Fraction of pages in a relation that vacuum can eagerly scan and fail
--
2.39.2
--nextPart4155600.3ZeAukHxDK
Content-Disposition: attachment;
filename="v2a-0003-Add-alias-to-be-used-as-unset-state.patch"
Content-Transfer-Encoding: 7Bit
Content-Type: text/x-patch; charset="unicode-2-0-utf-8";
name="v2a-0003-Add-alias-to-be-used-as-unset-state.patch"
^ permalink raw reply [nested|flat] 868+ messages in thread
* [PATCH v2 2/4] Introduce ternary reloptions
@ 2025-09-04 14:41 Nikolay Shaplov <[email protected]>
0 siblings, 0 replies; 868+ messages in thread
From: Nikolay Shaplov @ 2025-09-04 14:41 UTC (permalink / raw)
Introduce ternary reloption as a replacement for current `vacuum_truncate`
implementation. Remove `vacuum_truncate_set` additional flag and using
`TERNARY_UNSET` value instead.
---
src/backend/access/common/reloptions.c | 129 ++++++++++++++++++++-----
src/backend/commands/vacuum.c | 4 +-
src/include/access/reloptions.h | 26 ++---
src/include/c.h | 16 +++
src/include/utils/rel.h | 3 +-
5 files changed, 135 insertions(+), 43 deletions(-)
diff --git a/src/backend/access/common/reloptions.c b/src/backend/access/common/reloptions.c
index 0af3fea68f..f543f11001 100644
--- a/src/backend/access/common/reloptions.c
+++ b/src/backend/access/common/reloptions.c
@@ -40,9 +40,9 @@
*
* To add an option:
*
- * (i) decide on a type (bool, integer, real, enum, string), name, default
- * value, upper and lower bounds (if applicable); for strings, consider a
- * validation routine.
+ * (i) decide on a type (bool, ternary, integer, real, enum, string), name,
+ * default value, upper and lower bounds (if applicable); for strings,
+ * consider a validation routine.
* (ii) add a record below (or use add_<type>_reloption).
* (iii) add it to the appropriate options struct (perhaps StdRdOptions)
* (iv) add it to the appropriate handling routine (perhaps
@@ -147,15 +147,6 @@ static relopt_bool boolRelOpts[] =
},
false
},
- {
- {
- "vacuum_truncate",
- "Enables vacuum to truncate empty pages at the end of this table",
- RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
- ShareUpdateExclusiveLock
- },
- true
- },
{
{
"deduplicate_items",
@@ -170,6 +161,21 @@ static relopt_bool boolRelOpts[] =
{{NULL}}
};
+static relopt_ternary ternaryRelOpts[] =
+{
+ {
+ {
+ "vacuum_truncate",
+ "Enables vacuum to truncate empty pages at the end of this table",
+ RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
+ ShareUpdateExclusiveLock
+ },
+ TERNARY_UNSET
+ },
+ /* list terminator */
+ {{NULL}}
+};
+
static relopt_int intRelOpts[] =
{
{
@@ -600,6 +606,13 @@ initialize_reloptions(void)
boolRelOpts[i].gen.lockmode));
j++;
}
+ for (i = 0; ternaryRelOpts[i].gen.name; i++)
+ {
+ Assert(DoLockModesConflict(ternaryRelOpts[i].gen.lockmode,
+ ternaryRelOpts[i].gen.lockmode));
+ j++;
+ }
+
for (i = 0; intRelOpts[i].gen.name; i++)
{
Assert(DoLockModesConflict(intRelOpts[i].gen.lockmode,
@@ -640,6 +653,14 @@ initialize_reloptions(void)
j++;
}
+ for (i = 0; ternaryRelOpts[i].gen.name; i++)
+ {
+ relOpts[j] = &ternaryRelOpts[i].gen;
+ relOpts[j]->type = RELOPT_TYPE_TERNARY;
+ relOpts[j]->namelen = strlen(relOpts[j]->name);
+ j++;
+ }
+
for (i = 0; intRelOpts[i].gen.name; i++)
{
relOpts[j] = &intRelOpts[i].gen;
@@ -800,6 +821,9 @@ allocate_reloption(bits32 kinds, int type, const char *name, const char *desc,
case RELOPT_TYPE_BOOL:
size = sizeof(relopt_bool);
break;
+ case RELOPT_TYPE_TERNARY:
+ size = sizeof(relopt_ternary);
+ break;
case RELOPT_TYPE_INT:
size = sizeof(relopt_int);
break;
@@ -883,6 +907,54 @@ add_local_bool_reloption(local_relopts *relopts, const char *name,
add_local_reloption(relopts, (relopt_gen *) newoption, offset);
}
+/*
+ * init_ternary_reloption
+ * Allocate and initialize a new ternary reloption
+ */
+static relopt_ternary *
+init_ternary_reloption(bits32 kinds, const char *name, const char *desc,
+ ternary default_val, LOCKMODE lockmode)
+{
+ relopt_ternary *newoption;
+
+ newoption = (relopt_ternary *) allocate_reloption(kinds,
+ RELOPT_TYPE_TERNARY, name, desc, lockmode);
+ newoption->default_val = default_val;
+
+ return newoption;
+}
+
+/*
+ * add_ternary_reloption
+ * Add a new ternary reloption
+ */
+void
+add_ternary_reloption(bits32 kinds, const char *name, const char *desc,
+ ternary default_val, LOCKMODE lockmode)
+{
+ relopt_ternary *newoption = init_ternary_reloption(kinds, name, desc,
+ default_val, lockmode);
+
+ add_reloption((relopt_gen *) newoption);
+}
+
+/*
+ * add_local_ternary_reloption
+ * Add a new ternary local reloption
+ *
+ * 'offset' is offset of ternary-typed field.
+ */
+void
+add_local_ternary_reloption(local_relopts *relopts, const char *name,
+ const char *desc, ternary default_val,
+ int offset)
+{
+ relopt_ternary *newoption = init_ternary_reloption(RELOPT_KIND_LOCAL,
+ name, desc,
+ default_val, 0);
+
+ add_local_reloption(relopts, (relopt_gen *) newoption, offset);
+}
/*
* init_real_reloption
@@ -1617,6 +1689,18 @@ parse_one_reloption(relopt_value *option, char *text_str, int text_len,
option->gen->name, value)));
}
break;
+ case RELOPT_TYPE_TERNARY:
+ {
+ bool b;
+ parsed = parse_bool(value, &b);
+ option->values.ternary_val = b ? TERNARY_TRUE : TERNARY_FALSE;
+ if (validate && !parsed)
+ ereport(ERROR,
+ (errcode(ERRCODE_INVALID_PARAMETER_VALUE),
+ errmsg("invalid value for ternary option \"%s\": %s",
+ option->gen->name, value)));
+ }
+ break;
case RELOPT_TYPE_INT:
{
relopt_int *optint = (relopt_int *) option->gen;
@@ -1780,17 +1864,6 @@ fillRelOptions(void *rdopts, Size basesize,
char *itempos = ((char *) rdopts) + elems[j].offset;
char *string_val;
- /*
- * If isset_offset is provided, store whether the reloption is
- * set there.
- */
- if (elems[j].isset_offset > 0)
- {
- char *setpos = ((char *) rdopts) + elems[j].isset_offset;
-
- *(bool *) setpos = options[i].isset;
- }
-
switch (options[i].gen->type)
{
case RELOPT_TYPE_BOOL:
@@ -1798,6 +1871,11 @@ fillRelOptions(void *rdopts, Size basesize,
options[i].values.bool_val :
((relopt_bool *) options[i].gen)->default_val;
break;
+ case RELOPT_TYPE_TERNARY:
+ *(ternary *) itempos = options[i].isset ?
+ options[i].values.ternary_val :
+ ((relopt_ternary *) options[i].gen)->default_val;
+ break;
case RELOPT_TYPE_INT:
*(int *) itempos = options[i].isset ?
options[i].values.int_val :
@@ -1912,8 +1990,8 @@ default_reloptions(Datum reloptions, bool validate, relopt_kind kind)
offsetof(StdRdOptions, parallel_workers)},
{"vacuum_index_cleanup", RELOPT_TYPE_ENUM,
offsetof(StdRdOptions, vacuum_index_cleanup)},
- {"vacuum_truncate", RELOPT_TYPE_BOOL,
- offsetof(StdRdOptions, vacuum_truncate), offsetof(StdRdOptions, vacuum_truncate_set)},
+ {"vacuum_truncate", RELOPT_TYPE_TERNARY,
+ offsetof(StdRdOptions, vacuum_truncate)},
{"vacuum_max_eager_freeze_failure_rate", RELOPT_TYPE_REAL,
offsetof(StdRdOptions, vacuum_max_eager_freeze_failure_rate)}
};
@@ -1993,7 +2071,6 @@ build_local_reloptions(local_relopts *relopts, Datum options, bool validate)
elems[i].optname = opt->option->name;
elems[i].opttype = opt->option->type;
elems[i].offset = opt->offset;
- elems[i].isset_offset = 0; /* not supported for local relopts yet */
i++;
}
diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c
index 733ef40ae7..977babff54 100644
--- a/src/backend/commands/vacuum.c
+++ b/src/backend/commands/vacuum.c
@@ -2221,9 +2221,9 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams params,
{
StdRdOptions *opts = (StdRdOptions *) rel->rd_options;
- if (opts && opts->vacuum_truncate_set)
+ if (opts && opts->vacuum_truncate != TERNARY_UNSET)
{
- if (opts->vacuum_truncate)
+ if (opts->vacuum_truncate == TERNARY_TRUE)
params.truncate = VACOPTVALUE_ENABLED;
else
params.truncate = VACOPTVALUE_DISABLED;
diff --git a/src/include/access/reloptions.h b/src/include/access/reloptions.h
index a604a4702c..a436697658 100644
--- a/src/include/access/reloptions.h
+++ b/src/include/access/reloptions.h
@@ -29,6 +29,7 @@
typedef enum relopt_type
{
RELOPT_TYPE_BOOL,
+ RELOPT_TYPE_TERNARY, /* on, off, unset */
RELOPT_TYPE_INT,
RELOPT_TYPE_REAL,
RELOPT_TYPE_ENUM,
@@ -80,6 +81,7 @@ typedef struct relopt_value
union
{
bool bool_val;
+ ternary ternary_val;
int int_val;
double real_val;
int enum_val;
@@ -94,6 +96,12 @@ typedef struct relopt_bool
bool default_val;
} relopt_bool;
+typedef struct relopt_rernary
+{
+ relopt_gen gen;
+ int default_val;
+} relopt_ternary;
+
typedef struct relopt_int
{
relopt_gen gen;
@@ -152,19 +160,6 @@ typedef struct
const char *optname; /* option's name */
relopt_type opttype; /* option's datatype */
int offset; /* offset of field in result struct */
-
- /*
- * isset_offset is an optional offset of a field in the result struct that
- * stores whether the option is explicitly set for the relation or if it
- * just picked up the default value. In most cases, this can be
- * accomplished by giving the reloption a special out-of-range default
- * value (e.g., some integer reloptions use -2), but this isn't always
- * possible. For example, a Boolean reloption cannot be given an
- * out-of-range default, so we need another way to discover the source of
- * its value. This offset is only used if given a value greater than
- * zero.
- */
- int isset_offset;
} relopt_parse_elt;
/* Local reloption definition */
@@ -195,6 +190,8 @@ typedef struct local_relopts
extern relopt_kind add_reloption_kind(void);
extern void add_bool_reloption(bits32 kinds, const char *name, const char *desc,
bool default_val, LOCKMODE lockmode);
+extern void add_ternary_reloption(bits32 kinds, const char *name,
+ const char *desc, int default_val, LOCKMODE lockmode);
extern void add_int_reloption(bits32 kinds, const char *name, const char *desc,
int default_val, int min_val, int max_val,
LOCKMODE lockmode);
@@ -214,6 +211,9 @@ extern void register_reloptions_validator(local_relopts *relopts,
extern void add_local_bool_reloption(local_relopts *relopts, const char *name,
const char *desc, bool default_val,
int offset);
+extern void add_local_ternary_reloption(local_relopts *relopts,
+ const char *name, const char *desc,
+ ternary default_val, int offset);
extern void add_local_int_reloption(local_relopts *relopts, const char *name,
const char *desc, int default_val,
int min_val, int max_val, int offset);
diff --git a/src/include/c.h b/src/include/c.h
index 39022f8a9d..9c59ccccf8 100644
--- a/src/include/c.h
+++ b/src/include/c.h
@@ -488,6 +488,22 @@ typedef void (*pg_funcptr_t) (void);
#include <stdbool.h>
+/*
+ * ternary
+ * Boolean value with an extrea "unset" option
+ *
+ * Ternary data type is used in relation options that can be "true", "false" or
+ * "unset". Since relation options are used deep inside the PostgreSQL code,
+ * this type is declared globally.
+*/
+
+typedef enum ternary
+{
+ TERNARY_FALSE = 0,
+ TERNARY_TRUE = 1,
+ TERNARY_UNSET = -1
+} ternary;
+
/* ----------------------------------------------------------------
* Section 3: standard system types
diff --git a/src/include/utils/rel.h b/src/include/utils/rel.h
index b552359915..7346d618f9 100644
--- a/src/include/utils/rel.h
+++ b/src/include/utils/rel.h
@@ -346,8 +346,7 @@ typedef struct StdRdOptions
bool user_catalog_table; /* use as an additional catalog relation */
int parallel_workers; /* max number of parallel workers */
StdRdOptIndexCleanup vacuum_index_cleanup; /* controls index vacuuming */
- bool vacuum_truncate; /* enables vacuum to truncate a relation */
- bool vacuum_truncate_set; /* whether vacuum_truncate is set */
+ ternary vacuum_truncate; /* enables vacuum to truncate a relation */
/*
* Fraction of pages in a relation that vacuum can eagerly scan and fail
--
2.39.2
--nextPart5129932.5fSG56mABF
Content-Disposition: attachment;
filename="v2-0003-Add-alias-to-be-used-as-unset-state.patch"
Content-Transfer-Encoding: 7Bit
Content-Type: text/x-patch; charset="unicode-2-0-utf-8";
name="v2-0003-Add-alias-to-be-used-as-unset-state.patch"
^ permalink raw reply [nested|flat] 868+ messages in thread
* [PATCH v2a 2/4] Introduce ternary reloptions
@ 2025-09-04 14:41 Nikolay Shaplov <[email protected]>
0 siblings, 0 replies; 868+ messages in thread
From: Nikolay Shaplov @ 2025-09-04 14:41 UTC (permalink / raw)
Introduce ternary reloption as a replacement for current `vacuum_truncate`
implementation. Remove `vacuum_truncate_set` additional flag and using
`TERNARY_UNSET` value instead.
---
src/backend/access/common/reloptions.c | 129 ++++++++++++++++++++-----
src/backend/commands/vacuum.c | 4 +-
src/include/access/reloptions.h | 26 ++---
src/include/c.h | 16 +++
src/include/utils/rel.h | 3 +-
5 files changed, 135 insertions(+), 43 deletions(-)
diff --git a/src/backend/access/common/reloptions.c b/src/backend/access/common/reloptions.c
index 0af3fea68f..f543f11001 100644
--- a/src/backend/access/common/reloptions.c
+++ b/src/backend/access/common/reloptions.c
@@ -40,9 +40,9 @@
*
* To add an option:
*
- * (i) decide on a type (bool, integer, real, enum, string), name, default
- * value, upper and lower bounds (if applicable); for strings, consider a
- * validation routine.
+ * (i) decide on a type (bool, ternary, integer, real, enum, string), name,
+ * default value, upper and lower bounds (if applicable); for strings,
+ * consider a validation routine.
* (ii) add a record below (or use add_<type>_reloption).
* (iii) add it to the appropriate options struct (perhaps StdRdOptions)
* (iv) add it to the appropriate handling routine (perhaps
@@ -147,15 +147,6 @@ static relopt_bool boolRelOpts[] =
},
false
},
- {
- {
- "vacuum_truncate",
- "Enables vacuum to truncate empty pages at the end of this table",
- RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
- ShareUpdateExclusiveLock
- },
- true
- },
{
{
"deduplicate_items",
@@ -170,6 +161,21 @@ static relopt_bool boolRelOpts[] =
{{NULL}}
};
+static relopt_ternary ternaryRelOpts[] =
+{
+ {
+ {
+ "vacuum_truncate",
+ "Enables vacuum to truncate empty pages at the end of this table",
+ RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
+ ShareUpdateExclusiveLock
+ },
+ TERNARY_UNSET
+ },
+ /* list terminator */
+ {{NULL}}
+};
+
static relopt_int intRelOpts[] =
{
{
@@ -600,6 +606,13 @@ initialize_reloptions(void)
boolRelOpts[i].gen.lockmode));
j++;
}
+ for (i = 0; ternaryRelOpts[i].gen.name; i++)
+ {
+ Assert(DoLockModesConflict(ternaryRelOpts[i].gen.lockmode,
+ ternaryRelOpts[i].gen.lockmode));
+ j++;
+ }
+
for (i = 0; intRelOpts[i].gen.name; i++)
{
Assert(DoLockModesConflict(intRelOpts[i].gen.lockmode,
@@ -640,6 +653,14 @@ initialize_reloptions(void)
j++;
}
+ for (i = 0; ternaryRelOpts[i].gen.name; i++)
+ {
+ relOpts[j] = &ternaryRelOpts[i].gen;
+ relOpts[j]->type = RELOPT_TYPE_TERNARY;
+ relOpts[j]->namelen = strlen(relOpts[j]->name);
+ j++;
+ }
+
for (i = 0; intRelOpts[i].gen.name; i++)
{
relOpts[j] = &intRelOpts[i].gen;
@@ -800,6 +821,9 @@ allocate_reloption(bits32 kinds, int type, const char *name, const char *desc,
case RELOPT_TYPE_BOOL:
size = sizeof(relopt_bool);
break;
+ case RELOPT_TYPE_TERNARY:
+ size = sizeof(relopt_ternary);
+ break;
case RELOPT_TYPE_INT:
size = sizeof(relopt_int);
break;
@@ -883,6 +907,54 @@ add_local_bool_reloption(local_relopts *relopts, const char *name,
add_local_reloption(relopts, (relopt_gen *) newoption, offset);
}
+/*
+ * init_ternary_reloption
+ * Allocate and initialize a new ternary reloption
+ */
+static relopt_ternary *
+init_ternary_reloption(bits32 kinds, const char *name, const char *desc,
+ ternary default_val, LOCKMODE lockmode)
+{
+ relopt_ternary *newoption;
+
+ newoption = (relopt_ternary *) allocate_reloption(kinds,
+ RELOPT_TYPE_TERNARY, name, desc, lockmode);
+ newoption->default_val = default_val;
+
+ return newoption;
+}
+
+/*
+ * add_ternary_reloption
+ * Add a new ternary reloption
+ */
+void
+add_ternary_reloption(bits32 kinds, const char *name, const char *desc,
+ ternary default_val, LOCKMODE lockmode)
+{
+ relopt_ternary *newoption = init_ternary_reloption(kinds, name, desc,
+ default_val, lockmode);
+
+ add_reloption((relopt_gen *) newoption);
+}
+
+/*
+ * add_local_ternary_reloption
+ * Add a new ternary local reloption
+ *
+ * 'offset' is offset of ternary-typed field.
+ */
+void
+add_local_ternary_reloption(local_relopts *relopts, const char *name,
+ const char *desc, ternary default_val,
+ int offset)
+{
+ relopt_ternary *newoption = init_ternary_reloption(RELOPT_KIND_LOCAL,
+ name, desc,
+ default_val, 0);
+
+ add_local_reloption(relopts, (relopt_gen *) newoption, offset);
+}
/*
* init_real_reloption
@@ -1617,6 +1689,18 @@ parse_one_reloption(relopt_value *option, char *text_str, int text_len,
option->gen->name, value)));
}
break;
+ case RELOPT_TYPE_TERNARY:
+ {
+ bool b;
+ parsed = parse_bool(value, &b);
+ option->values.ternary_val = b ? TERNARY_TRUE : TERNARY_FALSE;
+ if (validate && !parsed)
+ ereport(ERROR,
+ (errcode(ERRCODE_INVALID_PARAMETER_VALUE),
+ errmsg("invalid value for ternary option \"%s\": %s",
+ option->gen->name, value)));
+ }
+ break;
case RELOPT_TYPE_INT:
{
relopt_int *optint = (relopt_int *) option->gen;
@@ -1780,17 +1864,6 @@ fillRelOptions(void *rdopts, Size basesize,
char *itempos = ((char *) rdopts) + elems[j].offset;
char *string_val;
- /*
- * If isset_offset is provided, store whether the reloption is
- * set there.
- */
- if (elems[j].isset_offset > 0)
- {
- char *setpos = ((char *) rdopts) + elems[j].isset_offset;
-
- *(bool *) setpos = options[i].isset;
- }
-
switch (options[i].gen->type)
{
case RELOPT_TYPE_BOOL:
@@ -1798,6 +1871,11 @@ fillRelOptions(void *rdopts, Size basesize,
options[i].values.bool_val :
((relopt_bool *) options[i].gen)->default_val;
break;
+ case RELOPT_TYPE_TERNARY:
+ *(ternary *) itempos = options[i].isset ?
+ options[i].values.ternary_val :
+ ((relopt_ternary *) options[i].gen)->default_val;
+ break;
case RELOPT_TYPE_INT:
*(int *) itempos = options[i].isset ?
options[i].values.int_val :
@@ -1912,8 +1990,8 @@ default_reloptions(Datum reloptions, bool validate, relopt_kind kind)
offsetof(StdRdOptions, parallel_workers)},
{"vacuum_index_cleanup", RELOPT_TYPE_ENUM,
offsetof(StdRdOptions, vacuum_index_cleanup)},
- {"vacuum_truncate", RELOPT_TYPE_BOOL,
- offsetof(StdRdOptions, vacuum_truncate), offsetof(StdRdOptions, vacuum_truncate_set)},
+ {"vacuum_truncate", RELOPT_TYPE_TERNARY,
+ offsetof(StdRdOptions, vacuum_truncate)},
{"vacuum_max_eager_freeze_failure_rate", RELOPT_TYPE_REAL,
offsetof(StdRdOptions, vacuum_max_eager_freeze_failure_rate)}
};
@@ -1993,7 +2071,6 @@ build_local_reloptions(local_relopts *relopts, Datum options, bool validate)
elems[i].optname = opt->option->name;
elems[i].opttype = opt->option->type;
elems[i].offset = opt->offset;
- elems[i].isset_offset = 0; /* not supported for local relopts yet */
i++;
}
diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c
index 733ef40ae7..977babff54 100644
--- a/src/backend/commands/vacuum.c
+++ b/src/backend/commands/vacuum.c
@@ -2221,9 +2221,9 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams params,
{
StdRdOptions *opts = (StdRdOptions *) rel->rd_options;
- if (opts && opts->vacuum_truncate_set)
+ if (opts && opts->vacuum_truncate != TERNARY_UNSET)
{
- if (opts->vacuum_truncate)
+ if (opts->vacuum_truncate == TERNARY_TRUE)
params.truncate = VACOPTVALUE_ENABLED;
else
params.truncate = VACOPTVALUE_DISABLED;
diff --git a/src/include/access/reloptions.h b/src/include/access/reloptions.h
index a604a4702c..a436697658 100644
--- a/src/include/access/reloptions.h
+++ b/src/include/access/reloptions.h
@@ -29,6 +29,7 @@
typedef enum relopt_type
{
RELOPT_TYPE_BOOL,
+ RELOPT_TYPE_TERNARY, /* on, off, unset */
RELOPT_TYPE_INT,
RELOPT_TYPE_REAL,
RELOPT_TYPE_ENUM,
@@ -80,6 +81,7 @@ typedef struct relopt_value
union
{
bool bool_val;
+ ternary ternary_val;
int int_val;
double real_val;
int enum_val;
@@ -94,6 +96,12 @@ typedef struct relopt_bool
bool default_val;
} relopt_bool;
+typedef struct relopt_rernary
+{
+ relopt_gen gen;
+ int default_val;
+} relopt_ternary;
+
typedef struct relopt_int
{
relopt_gen gen;
@@ -152,19 +160,6 @@ typedef struct
const char *optname; /* option's name */
relopt_type opttype; /* option's datatype */
int offset; /* offset of field in result struct */
-
- /*
- * isset_offset is an optional offset of a field in the result struct that
- * stores whether the option is explicitly set for the relation or if it
- * just picked up the default value. In most cases, this can be
- * accomplished by giving the reloption a special out-of-range default
- * value (e.g., some integer reloptions use -2), but this isn't always
- * possible. For example, a Boolean reloption cannot be given an
- * out-of-range default, so we need another way to discover the source of
- * its value. This offset is only used if given a value greater than
- * zero.
- */
- int isset_offset;
} relopt_parse_elt;
/* Local reloption definition */
@@ -195,6 +190,8 @@ typedef struct local_relopts
extern relopt_kind add_reloption_kind(void);
extern void add_bool_reloption(bits32 kinds, const char *name, const char *desc,
bool default_val, LOCKMODE lockmode);
+extern void add_ternary_reloption(bits32 kinds, const char *name,
+ const char *desc, int default_val, LOCKMODE lockmode);
extern void add_int_reloption(bits32 kinds, const char *name, const char *desc,
int default_val, int min_val, int max_val,
LOCKMODE lockmode);
@@ -214,6 +211,9 @@ extern void register_reloptions_validator(local_relopts *relopts,
extern void add_local_bool_reloption(local_relopts *relopts, const char *name,
const char *desc, bool default_val,
int offset);
+extern void add_local_ternary_reloption(local_relopts *relopts,
+ const char *name, const char *desc,
+ ternary default_val, int offset);
extern void add_local_int_reloption(local_relopts *relopts, const char *name,
const char *desc, int default_val,
int min_val, int max_val, int offset);
diff --git a/src/include/c.h b/src/include/c.h
index 39022f8a9d..9c59ccccf8 100644
--- a/src/include/c.h
+++ b/src/include/c.h
@@ -488,6 +488,22 @@ typedef void (*pg_funcptr_t) (void);
#include <stdbool.h>
+/*
+ * ternary
+ * Boolean value with an extrea "unset" option
+ *
+ * Ternary data type is used in relation options that can be "true", "false" or
+ * "unset". Since relation options are used deep inside the PostgreSQL code,
+ * this type is declared globally.
+*/
+
+typedef enum ternary
+{
+ TERNARY_FALSE = 0,
+ TERNARY_TRUE = 1,
+ TERNARY_UNSET = -1
+} ternary;
+
/* ----------------------------------------------------------------
* Section 3: standard system types
diff --git a/src/include/utils/rel.h b/src/include/utils/rel.h
index b552359915..7346d618f9 100644
--- a/src/include/utils/rel.h
+++ b/src/include/utils/rel.h
@@ -346,8 +346,7 @@ typedef struct StdRdOptions
bool user_catalog_table; /* use as an additional catalog relation */
int parallel_workers; /* max number of parallel workers */
StdRdOptIndexCleanup vacuum_index_cleanup; /* controls index vacuuming */
- bool vacuum_truncate; /* enables vacuum to truncate a relation */
- bool vacuum_truncate_set; /* whether vacuum_truncate is set */
+ ternary vacuum_truncate; /* enables vacuum to truncate a relation */
/*
* Fraction of pages in a relation that vacuum can eagerly scan and fail
--
2.39.2
--nextPart4155600.3ZeAukHxDK
Content-Disposition: attachment;
filename="v2a-0003-Add-alias-to-be-used-as-unset-state.patch"
Content-Transfer-Encoding: 7Bit
Content-Type: text/x-patch; charset="unicode-2-0-utf-8";
name="v2a-0003-Add-alias-to-be-used-as-unset-state.patch"
^ permalink raw reply [nested|flat] 868+ messages in thread
* [PATCH v2a 2/4] Introduce ternary reloptions
@ 2025-09-04 14:41 Nikolay Shaplov <[email protected]>
0 siblings, 0 replies; 868+ messages in thread
From: Nikolay Shaplov @ 2025-09-04 14:41 UTC (permalink / raw)
Introduce ternary reloption as a replacement for current `vacuum_truncate`
implementation. Remove `vacuum_truncate_set` additional flag and using
`TERNARY_UNSET` value instead.
---
src/backend/access/common/reloptions.c | 129 ++++++++++++++++++++-----
src/backend/commands/vacuum.c | 4 +-
src/include/access/reloptions.h | 26 ++---
src/include/c.h | 16 +++
src/include/utils/rel.h | 3 +-
5 files changed, 135 insertions(+), 43 deletions(-)
diff --git a/src/backend/access/common/reloptions.c b/src/backend/access/common/reloptions.c
index 0af3fea68f..f543f11001 100644
--- a/src/backend/access/common/reloptions.c
+++ b/src/backend/access/common/reloptions.c
@@ -40,9 +40,9 @@
*
* To add an option:
*
- * (i) decide on a type (bool, integer, real, enum, string), name, default
- * value, upper and lower bounds (if applicable); for strings, consider a
- * validation routine.
+ * (i) decide on a type (bool, ternary, integer, real, enum, string), name,
+ * default value, upper and lower bounds (if applicable); for strings,
+ * consider a validation routine.
* (ii) add a record below (or use add_<type>_reloption).
* (iii) add it to the appropriate options struct (perhaps StdRdOptions)
* (iv) add it to the appropriate handling routine (perhaps
@@ -147,15 +147,6 @@ static relopt_bool boolRelOpts[] =
},
false
},
- {
- {
- "vacuum_truncate",
- "Enables vacuum to truncate empty pages at the end of this table",
- RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
- ShareUpdateExclusiveLock
- },
- true
- },
{
{
"deduplicate_items",
@@ -170,6 +161,21 @@ static relopt_bool boolRelOpts[] =
{{NULL}}
};
+static relopt_ternary ternaryRelOpts[] =
+{
+ {
+ {
+ "vacuum_truncate",
+ "Enables vacuum to truncate empty pages at the end of this table",
+ RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
+ ShareUpdateExclusiveLock
+ },
+ TERNARY_UNSET
+ },
+ /* list terminator */
+ {{NULL}}
+};
+
static relopt_int intRelOpts[] =
{
{
@@ -600,6 +606,13 @@ initialize_reloptions(void)
boolRelOpts[i].gen.lockmode));
j++;
}
+ for (i = 0; ternaryRelOpts[i].gen.name; i++)
+ {
+ Assert(DoLockModesConflict(ternaryRelOpts[i].gen.lockmode,
+ ternaryRelOpts[i].gen.lockmode));
+ j++;
+ }
+
for (i = 0; intRelOpts[i].gen.name; i++)
{
Assert(DoLockModesConflict(intRelOpts[i].gen.lockmode,
@@ -640,6 +653,14 @@ initialize_reloptions(void)
j++;
}
+ for (i = 0; ternaryRelOpts[i].gen.name; i++)
+ {
+ relOpts[j] = &ternaryRelOpts[i].gen;
+ relOpts[j]->type = RELOPT_TYPE_TERNARY;
+ relOpts[j]->namelen = strlen(relOpts[j]->name);
+ j++;
+ }
+
for (i = 0; intRelOpts[i].gen.name; i++)
{
relOpts[j] = &intRelOpts[i].gen;
@@ -800,6 +821,9 @@ allocate_reloption(bits32 kinds, int type, const char *name, const char *desc,
case RELOPT_TYPE_BOOL:
size = sizeof(relopt_bool);
break;
+ case RELOPT_TYPE_TERNARY:
+ size = sizeof(relopt_ternary);
+ break;
case RELOPT_TYPE_INT:
size = sizeof(relopt_int);
break;
@@ -883,6 +907,54 @@ add_local_bool_reloption(local_relopts *relopts, const char *name,
add_local_reloption(relopts, (relopt_gen *) newoption, offset);
}
+/*
+ * init_ternary_reloption
+ * Allocate and initialize a new ternary reloption
+ */
+static relopt_ternary *
+init_ternary_reloption(bits32 kinds, const char *name, const char *desc,
+ ternary default_val, LOCKMODE lockmode)
+{
+ relopt_ternary *newoption;
+
+ newoption = (relopt_ternary *) allocate_reloption(kinds,
+ RELOPT_TYPE_TERNARY, name, desc, lockmode);
+ newoption->default_val = default_val;
+
+ return newoption;
+}
+
+/*
+ * add_ternary_reloption
+ * Add a new ternary reloption
+ */
+void
+add_ternary_reloption(bits32 kinds, const char *name, const char *desc,
+ ternary default_val, LOCKMODE lockmode)
+{
+ relopt_ternary *newoption = init_ternary_reloption(kinds, name, desc,
+ default_val, lockmode);
+
+ add_reloption((relopt_gen *) newoption);
+}
+
+/*
+ * add_local_ternary_reloption
+ * Add a new ternary local reloption
+ *
+ * 'offset' is offset of ternary-typed field.
+ */
+void
+add_local_ternary_reloption(local_relopts *relopts, const char *name,
+ const char *desc, ternary default_val,
+ int offset)
+{
+ relopt_ternary *newoption = init_ternary_reloption(RELOPT_KIND_LOCAL,
+ name, desc,
+ default_val, 0);
+
+ add_local_reloption(relopts, (relopt_gen *) newoption, offset);
+}
/*
* init_real_reloption
@@ -1617,6 +1689,18 @@ parse_one_reloption(relopt_value *option, char *text_str, int text_len,
option->gen->name, value)));
}
break;
+ case RELOPT_TYPE_TERNARY:
+ {
+ bool b;
+ parsed = parse_bool(value, &b);
+ option->values.ternary_val = b ? TERNARY_TRUE : TERNARY_FALSE;
+ if (validate && !parsed)
+ ereport(ERROR,
+ (errcode(ERRCODE_INVALID_PARAMETER_VALUE),
+ errmsg("invalid value for ternary option \"%s\": %s",
+ option->gen->name, value)));
+ }
+ break;
case RELOPT_TYPE_INT:
{
relopt_int *optint = (relopt_int *) option->gen;
@@ -1780,17 +1864,6 @@ fillRelOptions(void *rdopts, Size basesize,
char *itempos = ((char *) rdopts) + elems[j].offset;
char *string_val;
- /*
- * If isset_offset is provided, store whether the reloption is
- * set there.
- */
- if (elems[j].isset_offset > 0)
- {
- char *setpos = ((char *) rdopts) + elems[j].isset_offset;
-
- *(bool *) setpos = options[i].isset;
- }
-
switch (options[i].gen->type)
{
case RELOPT_TYPE_BOOL:
@@ -1798,6 +1871,11 @@ fillRelOptions(void *rdopts, Size basesize,
options[i].values.bool_val :
((relopt_bool *) options[i].gen)->default_val;
break;
+ case RELOPT_TYPE_TERNARY:
+ *(ternary *) itempos = options[i].isset ?
+ options[i].values.ternary_val :
+ ((relopt_ternary *) options[i].gen)->default_val;
+ break;
case RELOPT_TYPE_INT:
*(int *) itempos = options[i].isset ?
options[i].values.int_val :
@@ -1912,8 +1990,8 @@ default_reloptions(Datum reloptions, bool validate, relopt_kind kind)
offsetof(StdRdOptions, parallel_workers)},
{"vacuum_index_cleanup", RELOPT_TYPE_ENUM,
offsetof(StdRdOptions, vacuum_index_cleanup)},
- {"vacuum_truncate", RELOPT_TYPE_BOOL,
- offsetof(StdRdOptions, vacuum_truncate), offsetof(StdRdOptions, vacuum_truncate_set)},
+ {"vacuum_truncate", RELOPT_TYPE_TERNARY,
+ offsetof(StdRdOptions, vacuum_truncate)},
{"vacuum_max_eager_freeze_failure_rate", RELOPT_TYPE_REAL,
offsetof(StdRdOptions, vacuum_max_eager_freeze_failure_rate)}
};
@@ -1993,7 +2071,6 @@ build_local_reloptions(local_relopts *relopts, Datum options, bool validate)
elems[i].optname = opt->option->name;
elems[i].opttype = opt->option->type;
elems[i].offset = opt->offset;
- elems[i].isset_offset = 0; /* not supported for local relopts yet */
i++;
}
diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c
index 733ef40ae7..977babff54 100644
--- a/src/backend/commands/vacuum.c
+++ b/src/backend/commands/vacuum.c
@@ -2221,9 +2221,9 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams params,
{
StdRdOptions *opts = (StdRdOptions *) rel->rd_options;
- if (opts && opts->vacuum_truncate_set)
+ if (opts && opts->vacuum_truncate != TERNARY_UNSET)
{
- if (opts->vacuum_truncate)
+ if (opts->vacuum_truncate == TERNARY_TRUE)
params.truncate = VACOPTVALUE_ENABLED;
else
params.truncate = VACOPTVALUE_DISABLED;
diff --git a/src/include/access/reloptions.h b/src/include/access/reloptions.h
index a604a4702c..a436697658 100644
--- a/src/include/access/reloptions.h
+++ b/src/include/access/reloptions.h
@@ -29,6 +29,7 @@
typedef enum relopt_type
{
RELOPT_TYPE_BOOL,
+ RELOPT_TYPE_TERNARY, /* on, off, unset */
RELOPT_TYPE_INT,
RELOPT_TYPE_REAL,
RELOPT_TYPE_ENUM,
@@ -80,6 +81,7 @@ typedef struct relopt_value
union
{
bool bool_val;
+ ternary ternary_val;
int int_val;
double real_val;
int enum_val;
@@ -94,6 +96,12 @@ typedef struct relopt_bool
bool default_val;
} relopt_bool;
+typedef struct relopt_rernary
+{
+ relopt_gen gen;
+ int default_val;
+} relopt_ternary;
+
typedef struct relopt_int
{
relopt_gen gen;
@@ -152,19 +160,6 @@ typedef struct
const char *optname; /* option's name */
relopt_type opttype; /* option's datatype */
int offset; /* offset of field in result struct */
-
- /*
- * isset_offset is an optional offset of a field in the result struct that
- * stores whether the option is explicitly set for the relation or if it
- * just picked up the default value. In most cases, this can be
- * accomplished by giving the reloption a special out-of-range default
- * value (e.g., some integer reloptions use -2), but this isn't always
- * possible. For example, a Boolean reloption cannot be given an
- * out-of-range default, so we need another way to discover the source of
- * its value. This offset is only used if given a value greater than
- * zero.
- */
- int isset_offset;
} relopt_parse_elt;
/* Local reloption definition */
@@ -195,6 +190,8 @@ typedef struct local_relopts
extern relopt_kind add_reloption_kind(void);
extern void add_bool_reloption(bits32 kinds, const char *name, const char *desc,
bool default_val, LOCKMODE lockmode);
+extern void add_ternary_reloption(bits32 kinds, const char *name,
+ const char *desc, int default_val, LOCKMODE lockmode);
extern void add_int_reloption(bits32 kinds, const char *name, const char *desc,
int default_val, int min_val, int max_val,
LOCKMODE lockmode);
@@ -214,6 +211,9 @@ extern void register_reloptions_validator(local_relopts *relopts,
extern void add_local_bool_reloption(local_relopts *relopts, const char *name,
const char *desc, bool default_val,
int offset);
+extern void add_local_ternary_reloption(local_relopts *relopts,
+ const char *name, const char *desc,
+ ternary default_val, int offset);
extern void add_local_int_reloption(local_relopts *relopts, const char *name,
const char *desc, int default_val,
int min_val, int max_val, int offset);
diff --git a/src/include/c.h b/src/include/c.h
index 39022f8a9d..9c59ccccf8 100644
--- a/src/include/c.h
+++ b/src/include/c.h
@@ -488,6 +488,22 @@ typedef void (*pg_funcptr_t) (void);
#include <stdbool.h>
+/*
+ * ternary
+ * Boolean value with an extrea "unset" option
+ *
+ * Ternary data type is used in relation options that can be "true", "false" or
+ * "unset". Since relation options are used deep inside the PostgreSQL code,
+ * this type is declared globally.
+*/
+
+typedef enum ternary
+{
+ TERNARY_FALSE = 0,
+ TERNARY_TRUE = 1,
+ TERNARY_UNSET = -1
+} ternary;
+
/* ----------------------------------------------------------------
* Section 3: standard system types
diff --git a/src/include/utils/rel.h b/src/include/utils/rel.h
index b552359915..7346d618f9 100644
--- a/src/include/utils/rel.h
+++ b/src/include/utils/rel.h
@@ -346,8 +346,7 @@ typedef struct StdRdOptions
bool user_catalog_table; /* use as an additional catalog relation */
int parallel_workers; /* max number of parallel workers */
StdRdOptIndexCleanup vacuum_index_cleanup; /* controls index vacuuming */
- bool vacuum_truncate; /* enables vacuum to truncate a relation */
- bool vacuum_truncate_set; /* whether vacuum_truncate is set */
+ ternary vacuum_truncate; /* enables vacuum to truncate a relation */
/*
* Fraction of pages in a relation that vacuum can eagerly scan and fail
--
2.39.2
--nextPart4155600.3ZeAukHxDK
Content-Disposition: attachment;
filename="v2a-0003-Add-alias-to-be-used-as-unset-state.patch"
Content-Transfer-Encoding: 7Bit
Content-Type: text/x-patch; charset="unicode-2-0-utf-8";
name="v2a-0003-Add-alias-to-be-used-as-unset-state.patch"
^ permalink raw reply [nested|flat] 868+ messages in thread
* [PATCH v2a 2/4] Introduce ternary reloptions
@ 2025-09-04 14:41 Nikolay Shaplov <[email protected]>
0 siblings, 0 replies; 868+ messages in thread
From: Nikolay Shaplov @ 2025-09-04 14:41 UTC (permalink / raw)
Introduce ternary reloption as a replacement for current `vacuum_truncate`
implementation. Remove `vacuum_truncate_set` additional flag and using
`TERNARY_UNSET` value instead.
---
src/backend/access/common/reloptions.c | 129 ++++++++++++++++++++-----
src/backend/commands/vacuum.c | 4 +-
src/include/access/reloptions.h | 26 ++---
src/include/c.h | 16 +++
src/include/utils/rel.h | 3 +-
5 files changed, 135 insertions(+), 43 deletions(-)
diff --git a/src/backend/access/common/reloptions.c b/src/backend/access/common/reloptions.c
index 0af3fea68f..f543f11001 100644
--- a/src/backend/access/common/reloptions.c
+++ b/src/backend/access/common/reloptions.c
@@ -40,9 +40,9 @@
*
* To add an option:
*
- * (i) decide on a type (bool, integer, real, enum, string), name, default
- * value, upper and lower bounds (if applicable); for strings, consider a
- * validation routine.
+ * (i) decide on a type (bool, ternary, integer, real, enum, string), name,
+ * default value, upper and lower bounds (if applicable); for strings,
+ * consider a validation routine.
* (ii) add a record below (or use add_<type>_reloption).
* (iii) add it to the appropriate options struct (perhaps StdRdOptions)
* (iv) add it to the appropriate handling routine (perhaps
@@ -147,15 +147,6 @@ static relopt_bool boolRelOpts[] =
},
false
},
- {
- {
- "vacuum_truncate",
- "Enables vacuum to truncate empty pages at the end of this table",
- RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
- ShareUpdateExclusiveLock
- },
- true
- },
{
{
"deduplicate_items",
@@ -170,6 +161,21 @@ static relopt_bool boolRelOpts[] =
{{NULL}}
};
+static relopt_ternary ternaryRelOpts[] =
+{
+ {
+ {
+ "vacuum_truncate",
+ "Enables vacuum to truncate empty pages at the end of this table",
+ RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
+ ShareUpdateExclusiveLock
+ },
+ TERNARY_UNSET
+ },
+ /* list terminator */
+ {{NULL}}
+};
+
static relopt_int intRelOpts[] =
{
{
@@ -600,6 +606,13 @@ initialize_reloptions(void)
boolRelOpts[i].gen.lockmode));
j++;
}
+ for (i = 0; ternaryRelOpts[i].gen.name; i++)
+ {
+ Assert(DoLockModesConflict(ternaryRelOpts[i].gen.lockmode,
+ ternaryRelOpts[i].gen.lockmode));
+ j++;
+ }
+
for (i = 0; intRelOpts[i].gen.name; i++)
{
Assert(DoLockModesConflict(intRelOpts[i].gen.lockmode,
@@ -640,6 +653,14 @@ initialize_reloptions(void)
j++;
}
+ for (i = 0; ternaryRelOpts[i].gen.name; i++)
+ {
+ relOpts[j] = &ternaryRelOpts[i].gen;
+ relOpts[j]->type = RELOPT_TYPE_TERNARY;
+ relOpts[j]->namelen = strlen(relOpts[j]->name);
+ j++;
+ }
+
for (i = 0; intRelOpts[i].gen.name; i++)
{
relOpts[j] = &intRelOpts[i].gen;
@@ -800,6 +821,9 @@ allocate_reloption(bits32 kinds, int type, const char *name, const char *desc,
case RELOPT_TYPE_BOOL:
size = sizeof(relopt_bool);
break;
+ case RELOPT_TYPE_TERNARY:
+ size = sizeof(relopt_ternary);
+ break;
case RELOPT_TYPE_INT:
size = sizeof(relopt_int);
break;
@@ -883,6 +907,54 @@ add_local_bool_reloption(local_relopts *relopts, const char *name,
add_local_reloption(relopts, (relopt_gen *) newoption, offset);
}
+/*
+ * init_ternary_reloption
+ * Allocate and initialize a new ternary reloption
+ */
+static relopt_ternary *
+init_ternary_reloption(bits32 kinds, const char *name, const char *desc,
+ ternary default_val, LOCKMODE lockmode)
+{
+ relopt_ternary *newoption;
+
+ newoption = (relopt_ternary *) allocate_reloption(kinds,
+ RELOPT_TYPE_TERNARY, name, desc, lockmode);
+ newoption->default_val = default_val;
+
+ return newoption;
+}
+
+/*
+ * add_ternary_reloption
+ * Add a new ternary reloption
+ */
+void
+add_ternary_reloption(bits32 kinds, const char *name, const char *desc,
+ ternary default_val, LOCKMODE lockmode)
+{
+ relopt_ternary *newoption = init_ternary_reloption(kinds, name, desc,
+ default_val, lockmode);
+
+ add_reloption((relopt_gen *) newoption);
+}
+
+/*
+ * add_local_ternary_reloption
+ * Add a new ternary local reloption
+ *
+ * 'offset' is offset of ternary-typed field.
+ */
+void
+add_local_ternary_reloption(local_relopts *relopts, const char *name,
+ const char *desc, ternary default_val,
+ int offset)
+{
+ relopt_ternary *newoption = init_ternary_reloption(RELOPT_KIND_LOCAL,
+ name, desc,
+ default_val, 0);
+
+ add_local_reloption(relopts, (relopt_gen *) newoption, offset);
+}
/*
* init_real_reloption
@@ -1617,6 +1689,18 @@ parse_one_reloption(relopt_value *option, char *text_str, int text_len,
option->gen->name, value)));
}
break;
+ case RELOPT_TYPE_TERNARY:
+ {
+ bool b;
+ parsed = parse_bool(value, &b);
+ option->values.ternary_val = b ? TERNARY_TRUE : TERNARY_FALSE;
+ if (validate && !parsed)
+ ereport(ERROR,
+ (errcode(ERRCODE_INVALID_PARAMETER_VALUE),
+ errmsg("invalid value for ternary option \"%s\": %s",
+ option->gen->name, value)));
+ }
+ break;
case RELOPT_TYPE_INT:
{
relopt_int *optint = (relopt_int *) option->gen;
@@ -1780,17 +1864,6 @@ fillRelOptions(void *rdopts, Size basesize,
char *itempos = ((char *) rdopts) + elems[j].offset;
char *string_val;
- /*
- * If isset_offset is provided, store whether the reloption is
- * set there.
- */
- if (elems[j].isset_offset > 0)
- {
- char *setpos = ((char *) rdopts) + elems[j].isset_offset;
-
- *(bool *) setpos = options[i].isset;
- }
-
switch (options[i].gen->type)
{
case RELOPT_TYPE_BOOL:
@@ -1798,6 +1871,11 @@ fillRelOptions(void *rdopts, Size basesize,
options[i].values.bool_val :
((relopt_bool *) options[i].gen)->default_val;
break;
+ case RELOPT_TYPE_TERNARY:
+ *(ternary *) itempos = options[i].isset ?
+ options[i].values.ternary_val :
+ ((relopt_ternary *) options[i].gen)->default_val;
+ break;
case RELOPT_TYPE_INT:
*(int *) itempos = options[i].isset ?
options[i].values.int_val :
@@ -1912,8 +1990,8 @@ default_reloptions(Datum reloptions, bool validate, relopt_kind kind)
offsetof(StdRdOptions, parallel_workers)},
{"vacuum_index_cleanup", RELOPT_TYPE_ENUM,
offsetof(StdRdOptions, vacuum_index_cleanup)},
- {"vacuum_truncate", RELOPT_TYPE_BOOL,
- offsetof(StdRdOptions, vacuum_truncate), offsetof(StdRdOptions, vacuum_truncate_set)},
+ {"vacuum_truncate", RELOPT_TYPE_TERNARY,
+ offsetof(StdRdOptions, vacuum_truncate)},
{"vacuum_max_eager_freeze_failure_rate", RELOPT_TYPE_REAL,
offsetof(StdRdOptions, vacuum_max_eager_freeze_failure_rate)}
};
@@ -1993,7 +2071,6 @@ build_local_reloptions(local_relopts *relopts, Datum options, bool validate)
elems[i].optname = opt->option->name;
elems[i].opttype = opt->option->type;
elems[i].offset = opt->offset;
- elems[i].isset_offset = 0; /* not supported for local relopts yet */
i++;
}
diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c
index 733ef40ae7..977babff54 100644
--- a/src/backend/commands/vacuum.c
+++ b/src/backend/commands/vacuum.c
@@ -2221,9 +2221,9 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams params,
{
StdRdOptions *opts = (StdRdOptions *) rel->rd_options;
- if (opts && opts->vacuum_truncate_set)
+ if (opts && opts->vacuum_truncate != TERNARY_UNSET)
{
- if (opts->vacuum_truncate)
+ if (opts->vacuum_truncate == TERNARY_TRUE)
params.truncate = VACOPTVALUE_ENABLED;
else
params.truncate = VACOPTVALUE_DISABLED;
diff --git a/src/include/access/reloptions.h b/src/include/access/reloptions.h
index a604a4702c..a436697658 100644
--- a/src/include/access/reloptions.h
+++ b/src/include/access/reloptions.h
@@ -29,6 +29,7 @@
typedef enum relopt_type
{
RELOPT_TYPE_BOOL,
+ RELOPT_TYPE_TERNARY, /* on, off, unset */
RELOPT_TYPE_INT,
RELOPT_TYPE_REAL,
RELOPT_TYPE_ENUM,
@@ -80,6 +81,7 @@ typedef struct relopt_value
union
{
bool bool_val;
+ ternary ternary_val;
int int_val;
double real_val;
int enum_val;
@@ -94,6 +96,12 @@ typedef struct relopt_bool
bool default_val;
} relopt_bool;
+typedef struct relopt_rernary
+{
+ relopt_gen gen;
+ int default_val;
+} relopt_ternary;
+
typedef struct relopt_int
{
relopt_gen gen;
@@ -152,19 +160,6 @@ typedef struct
const char *optname; /* option's name */
relopt_type opttype; /* option's datatype */
int offset; /* offset of field in result struct */
-
- /*
- * isset_offset is an optional offset of a field in the result struct that
- * stores whether the option is explicitly set for the relation or if it
- * just picked up the default value. In most cases, this can be
- * accomplished by giving the reloption a special out-of-range default
- * value (e.g., some integer reloptions use -2), but this isn't always
- * possible. For example, a Boolean reloption cannot be given an
- * out-of-range default, so we need another way to discover the source of
- * its value. This offset is only used if given a value greater than
- * zero.
- */
- int isset_offset;
} relopt_parse_elt;
/* Local reloption definition */
@@ -195,6 +190,8 @@ typedef struct local_relopts
extern relopt_kind add_reloption_kind(void);
extern void add_bool_reloption(bits32 kinds, const char *name, const char *desc,
bool default_val, LOCKMODE lockmode);
+extern void add_ternary_reloption(bits32 kinds, const char *name,
+ const char *desc, int default_val, LOCKMODE lockmode);
extern void add_int_reloption(bits32 kinds, const char *name, const char *desc,
int default_val, int min_val, int max_val,
LOCKMODE lockmode);
@@ -214,6 +211,9 @@ extern void register_reloptions_validator(local_relopts *relopts,
extern void add_local_bool_reloption(local_relopts *relopts, const char *name,
const char *desc, bool default_val,
int offset);
+extern void add_local_ternary_reloption(local_relopts *relopts,
+ const char *name, const char *desc,
+ ternary default_val, int offset);
extern void add_local_int_reloption(local_relopts *relopts, const char *name,
const char *desc, int default_val,
int min_val, int max_val, int offset);
diff --git a/src/include/c.h b/src/include/c.h
index 39022f8a9d..9c59ccccf8 100644
--- a/src/include/c.h
+++ b/src/include/c.h
@@ -488,6 +488,22 @@ typedef void (*pg_funcptr_t) (void);
#include <stdbool.h>
+/*
+ * ternary
+ * Boolean value with an extrea "unset" option
+ *
+ * Ternary data type is used in relation options that can be "true", "false" or
+ * "unset". Since relation options are used deep inside the PostgreSQL code,
+ * this type is declared globally.
+*/
+
+typedef enum ternary
+{
+ TERNARY_FALSE = 0,
+ TERNARY_TRUE = 1,
+ TERNARY_UNSET = -1
+} ternary;
+
/* ----------------------------------------------------------------
* Section 3: standard system types
diff --git a/src/include/utils/rel.h b/src/include/utils/rel.h
index b552359915..7346d618f9 100644
--- a/src/include/utils/rel.h
+++ b/src/include/utils/rel.h
@@ -346,8 +346,7 @@ typedef struct StdRdOptions
bool user_catalog_table; /* use as an additional catalog relation */
int parallel_workers; /* max number of parallel workers */
StdRdOptIndexCleanup vacuum_index_cleanup; /* controls index vacuuming */
- bool vacuum_truncate; /* enables vacuum to truncate a relation */
- bool vacuum_truncate_set; /* whether vacuum_truncate is set */
+ ternary vacuum_truncate; /* enables vacuum to truncate a relation */
/*
* Fraction of pages in a relation that vacuum can eagerly scan and fail
--
2.39.2
--nextPart4155600.3ZeAukHxDK
Content-Disposition: attachment;
filename="v2a-0003-Add-alias-to-be-used-as-unset-state.patch"
Content-Transfer-Encoding: 7Bit
Content-Type: text/x-patch; charset="unicode-2-0-utf-8";
name="v2a-0003-Add-alias-to-be-used-as-unset-state.patch"
^ permalink raw reply [nested|flat] 868+ messages in thread
* [PATCH v2a 2/4] Introduce ternary reloptions
@ 2025-09-04 14:41 Nikolay Shaplov <[email protected]>
0 siblings, 0 replies; 868+ messages in thread
From: Nikolay Shaplov @ 2025-09-04 14:41 UTC (permalink / raw)
Introduce ternary reloption as a replacement for current `vacuum_truncate`
implementation. Remove `vacuum_truncate_set` additional flag and using
`TERNARY_UNSET` value instead.
---
src/backend/access/common/reloptions.c | 129 ++++++++++++++++++++-----
src/backend/commands/vacuum.c | 4 +-
src/include/access/reloptions.h | 26 ++---
src/include/c.h | 16 +++
src/include/utils/rel.h | 3 +-
5 files changed, 135 insertions(+), 43 deletions(-)
diff --git a/src/backend/access/common/reloptions.c b/src/backend/access/common/reloptions.c
index 0af3fea68f..f543f11001 100644
--- a/src/backend/access/common/reloptions.c
+++ b/src/backend/access/common/reloptions.c
@@ -40,9 +40,9 @@
*
* To add an option:
*
- * (i) decide on a type (bool, integer, real, enum, string), name, default
- * value, upper and lower bounds (if applicable); for strings, consider a
- * validation routine.
+ * (i) decide on a type (bool, ternary, integer, real, enum, string), name,
+ * default value, upper and lower bounds (if applicable); for strings,
+ * consider a validation routine.
* (ii) add a record below (or use add_<type>_reloption).
* (iii) add it to the appropriate options struct (perhaps StdRdOptions)
* (iv) add it to the appropriate handling routine (perhaps
@@ -147,15 +147,6 @@ static relopt_bool boolRelOpts[] =
},
false
},
- {
- {
- "vacuum_truncate",
- "Enables vacuum to truncate empty pages at the end of this table",
- RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
- ShareUpdateExclusiveLock
- },
- true
- },
{
{
"deduplicate_items",
@@ -170,6 +161,21 @@ static relopt_bool boolRelOpts[] =
{{NULL}}
};
+static relopt_ternary ternaryRelOpts[] =
+{
+ {
+ {
+ "vacuum_truncate",
+ "Enables vacuum to truncate empty pages at the end of this table",
+ RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
+ ShareUpdateExclusiveLock
+ },
+ TERNARY_UNSET
+ },
+ /* list terminator */
+ {{NULL}}
+};
+
static relopt_int intRelOpts[] =
{
{
@@ -600,6 +606,13 @@ initialize_reloptions(void)
boolRelOpts[i].gen.lockmode));
j++;
}
+ for (i = 0; ternaryRelOpts[i].gen.name; i++)
+ {
+ Assert(DoLockModesConflict(ternaryRelOpts[i].gen.lockmode,
+ ternaryRelOpts[i].gen.lockmode));
+ j++;
+ }
+
for (i = 0; intRelOpts[i].gen.name; i++)
{
Assert(DoLockModesConflict(intRelOpts[i].gen.lockmode,
@@ -640,6 +653,14 @@ initialize_reloptions(void)
j++;
}
+ for (i = 0; ternaryRelOpts[i].gen.name; i++)
+ {
+ relOpts[j] = &ternaryRelOpts[i].gen;
+ relOpts[j]->type = RELOPT_TYPE_TERNARY;
+ relOpts[j]->namelen = strlen(relOpts[j]->name);
+ j++;
+ }
+
for (i = 0; intRelOpts[i].gen.name; i++)
{
relOpts[j] = &intRelOpts[i].gen;
@@ -800,6 +821,9 @@ allocate_reloption(bits32 kinds, int type, const char *name, const char *desc,
case RELOPT_TYPE_BOOL:
size = sizeof(relopt_bool);
break;
+ case RELOPT_TYPE_TERNARY:
+ size = sizeof(relopt_ternary);
+ break;
case RELOPT_TYPE_INT:
size = sizeof(relopt_int);
break;
@@ -883,6 +907,54 @@ add_local_bool_reloption(local_relopts *relopts, const char *name,
add_local_reloption(relopts, (relopt_gen *) newoption, offset);
}
+/*
+ * init_ternary_reloption
+ * Allocate and initialize a new ternary reloption
+ */
+static relopt_ternary *
+init_ternary_reloption(bits32 kinds, const char *name, const char *desc,
+ ternary default_val, LOCKMODE lockmode)
+{
+ relopt_ternary *newoption;
+
+ newoption = (relopt_ternary *) allocate_reloption(kinds,
+ RELOPT_TYPE_TERNARY, name, desc, lockmode);
+ newoption->default_val = default_val;
+
+ return newoption;
+}
+
+/*
+ * add_ternary_reloption
+ * Add a new ternary reloption
+ */
+void
+add_ternary_reloption(bits32 kinds, const char *name, const char *desc,
+ ternary default_val, LOCKMODE lockmode)
+{
+ relopt_ternary *newoption = init_ternary_reloption(kinds, name, desc,
+ default_val, lockmode);
+
+ add_reloption((relopt_gen *) newoption);
+}
+
+/*
+ * add_local_ternary_reloption
+ * Add a new ternary local reloption
+ *
+ * 'offset' is offset of ternary-typed field.
+ */
+void
+add_local_ternary_reloption(local_relopts *relopts, const char *name,
+ const char *desc, ternary default_val,
+ int offset)
+{
+ relopt_ternary *newoption = init_ternary_reloption(RELOPT_KIND_LOCAL,
+ name, desc,
+ default_val, 0);
+
+ add_local_reloption(relopts, (relopt_gen *) newoption, offset);
+}
/*
* init_real_reloption
@@ -1617,6 +1689,18 @@ parse_one_reloption(relopt_value *option, char *text_str, int text_len,
option->gen->name, value)));
}
break;
+ case RELOPT_TYPE_TERNARY:
+ {
+ bool b;
+ parsed = parse_bool(value, &b);
+ option->values.ternary_val = b ? TERNARY_TRUE : TERNARY_FALSE;
+ if (validate && !parsed)
+ ereport(ERROR,
+ (errcode(ERRCODE_INVALID_PARAMETER_VALUE),
+ errmsg("invalid value for ternary option \"%s\": %s",
+ option->gen->name, value)));
+ }
+ break;
case RELOPT_TYPE_INT:
{
relopt_int *optint = (relopt_int *) option->gen;
@@ -1780,17 +1864,6 @@ fillRelOptions(void *rdopts, Size basesize,
char *itempos = ((char *) rdopts) + elems[j].offset;
char *string_val;
- /*
- * If isset_offset is provided, store whether the reloption is
- * set there.
- */
- if (elems[j].isset_offset > 0)
- {
- char *setpos = ((char *) rdopts) + elems[j].isset_offset;
-
- *(bool *) setpos = options[i].isset;
- }
-
switch (options[i].gen->type)
{
case RELOPT_TYPE_BOOL:
@@ -1798,6 +1871,11 @@ fillRelOptions(void *rdopts, Size basesize,
options[i].values.bool_val :
((relopt_bool *) options[i].gen)->default_val;
break;
+ case RELOPT_TYPE_TERNARY:
+ *(ternary *) itempos = options[i].isset ?
+ options[i].values.ternary_val :
+ ((relopt_ternary *) options[i].gen)->default_val;
+ break;
case RELOPT_TYPE_INT:
*(int *) itempos = options[i].isset ?
options[i].values.int_val :
@@ -1912,8 +1990,8 @@ default_reloptions(Datum reloptions, bool validate, relopt_kind kind)
offsetof(StdRdOptions, parallel_workers)},
{"vacuum_index_cleanup", RELOPT_TYPE_ENUM,
offsetof(StdRdOptions, vacuum_index_cleanup)},
- {"vacuum_truncate", RELOPT_TYPE_BOOL,
- offsetof(StdRdOptions, vacuum_truncate), offsetof(StdRdOptions, vacuum_truncate_set)},
+ {"vacuum_truncate", RELOPT_TYPE_TERNARY,
+ offsetof(StdRdOptions, vacuum_truncate)},
{"vacuum_max_eager_freeze_failure_rate", RELOPT_TYPE_REAL,
offsetof(StdRdOptions, vacuum_max_eager_freeze_failure_rate)}
};
@@ -1993,7 +2071,6 @@ build_local_reloptions(local_relopts *relopts, Datum options, bool validate)
elems[i].optname = opt->option->name;
elems[i].opttype = opt->option->type;
elems[i].offset = opt->offset;
- elems[i].isset_offset = 0; /* not supported for local relopts yet */
i++;
}
diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c
index 733ef40ae7..977babff54 100644
--- a/src/backend/commands/vacuum.c
+++ b/src/backend/commands/vacuum.c
@@ -2221,9 +2221,9 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams params,
{
StdRdOptions *opts = (StdRdOptions *) rel->rd_options;
- if (opts && opts->vacuum_truncate_set)
+ if (opts && opts->vacuum_truncate != TERNARY_UNSET)
{
- if (opts->vacuum_truncate)
+ if (opts->vacuum_truncate == TERNARY_TRUE)
params.truncate = VACOPTVALUE_ENABLED;
else
params.truncate = VACOPTVALUE_DISABLED;
diff --git a/src/include/access/reloptions.h b/src/include/access/reloptions.h
index a604a4702c..a436697658 100644
--- a/src/include/access/reloptions.h
+++ b/src/include/access/reloptions.h
@@ -29,6 +29,7 @@
typedef enum relopt_type
{
RELOPT_TYPE_BOOL,
+ RELOPT_TYPE_TERNARY, /* on, off, unset */
RELOPT_TYPE_INT,
RELOPT_TYPE_REAL,
RELOPT_TYPE_ENUM,
@@ -80,6 +81,7 @@ typedef struct relopt_value
union
{
bool bool_val;
+ ternary ternary_val;
int int_val;
double real_val;
int enum_val;
@@ -94,6 +96,12 @@ typedef struct relopt_bool
bool default_val;
} relopt_bool;
+typedef struct relopt_rernary
+{
+ relopt_gen gen;
+ int default_val;
+} relopt_ternary;
+
typedef struct relopt_int
{
relopt_gen gen;
@@ -152,19 +160,6 @@ typedef struct
const char *optname; /* option's name */
relopt_type opttype; /* option's datatype */
int offset; /* offset of field in result struct */
-
- /*
- * isset_offset is an optional offset of a field in the result struct that
- * stores whether the option is explicitly set for the relation or if it
- * just picked up the default value. In most cases, this can be
- * accomplished by giving the reloption a special out-of-range default
- * value (e.g., some integer reloptions use -2), but this isn't always
- * possible. For example, a Boolean reloption cannot be given an
- * out-of-range default, so we need another way to discover the source of
- * its value. This offset is only used if given a value greater than
- * zero.
- */
- int isset_offset;
} relopt_parse_elt;
/* Local reloption definition */
@@ -195,6 +190,8 @@ typedef struct local_relopts
extern relopt_kind add_reloption_kind(void);
extern void add_bool_reloption(bits32 kinds, const char *name, const char *desc,
bool default_val, LOCKMODE lockmode);
+extern void add_ternary_reloption(bits32 kinds, const char *name,
+ const char *desc, int default_val, LOCKMODE lockmode);
extern void add_int_reloption(bits32 kinds, const char *name, const char *desc,
int default_val, int min_val, int max_val,
LOCKMODE lockmode);
@@ -214,6 +211,9 @@ extern void register_reloptions_validator(local_relopts *relopts,
extern void add_local_bool_reloption(local_relopts *relopts, const char *name,
const char *desc, bool default_val,
int offset);
+extern void add_local_ternary_reloption(local_relopts *relopts,
+ const char *name, const char *desc,
+ ternary default_val, int offset);
extern void add_local_int_reloption(local_relopts *relopts, const char *name,
const char *desc, int default_val,
int min_val, int max_val, int offset);
diff --git a/src/include/c.h b/src/include/c.h
index 39022f8a9d..9c59ccccf8 100644
--- a/src/include/c.h
+++ b/src/include/c.h
@@ -488,6 +488,22 @@ typedef void (*pg_funcptr_t) (void);
#include <stdbool.h>
+/*
+ * ternary
+ * Boolean value with an extrea "unset" option
+ *
+ * Ternary data type is used in relation options that can be "true", "false" or
+ * "unset". Since relation options are used deep inside the PostgreSQL code,
+ * this type is declared globally.
+*/
+
+typedef enum ternary
+{
+ TERNARY_FALSE = 0,
+ TERNARY_TRUE = 1,
+ TERNARY_UNSET = -1
+} ternary;
+
/* ----------------------------------------------------------------
* Section 3: standard system types
diff --git a/src/include/utils/rel.h b/src/include/utils/rel.h
index b552359915..7346d618f9 100644
--- a/src/include/utils/rel.h
+++ b/src/include/utils/rel.h
@@ -346,8 +346,7 @@ typedef struct StdRdOptions
bool user_catalog_table; /* use as an additional catalog relation */
int parallel_workers; /* max number of parallel workers */
StdRdOptIndexCleanup vacuum_index_cleanup; /* controls index vacuuming */
- bool vacuum_truncate; /* enables vacuum to truncate a relation */
- bool vacuum_truncate_set; /* whether vacuum_truncate is set */
+ ternary vacuum_truncate; /* enables vacuum to truncate a relation */
/*
* Fraction of pages in a relation that vacuum can eagerly scan and fail
--
2.39.2
--nextPart4155600.3ZeAukHxDK
Content-Disposition: attachment;
filename="v2a-0003-Add-alias-to-be-used-as-unset-state.patch"
Content-Transfer-Encoding: 7Bit
Content-Type: text/x-patch; charset="unicode-2-0-utf-8";
name="v2a-0003-Add-alias-to-be-used-as-unset-state.patch"
^ permalink raw reply [nested|flat] 868+ messages in thread
* [PATCH v2 2/4] Introduce ternary reloptions
@ 2025-09-04 14:41 Nikolay Shaplov <[email protected]>
0 siblings, 0 replies; 868+ messages in thread
From: Nikolay Shaplov @ 2025-09-04 14:41 UTC (permalink / raw)
Introduce ternary reloption as a replacement for current `vacuum_truncate`
implementation. Remove `vacuum_truncate_set` additional flag and using
`TERNARY_UNSET` value instead.
---
src/backend/access/common/reloptions.c | 129 ++++++++++++++++++++-----
src/backend/commands/vacuum.c | 4 +-
src/include/access/reloptions.h | 26 ++---
src/include/c.h | 16 +++
src/include/utils/rel.h | 3 +-
5 files changed, 135 insertions(+), 43 deletions(-)
diff --git a/src/backend/access/common/reloptions.c b/src/backend/access/common/reloptions.c
index 0af3fea68f..f543f11001 100644
--- a/src/backend/access/common/reloptions.c
+++ b/src/backend/access/common/reloptions.c
@@ -40,9 +40,9 @@
*
* To add an option:
*
- * (i) decide on a type (bool, integer, real, enum, string), name, default
- * value, upper and lower bounds (if applicable); for strings, consider a
- * validation routine.
+ * (i) decide on a type (bool, ternary, integer, real, enum, string), name,
+ * default value, upper and lower bounds (if applicable); for strings,
+ * consider a validation routine.
* (ii) add a record below (or use add_<type>_reloption).
* (iii) add it to the appropriate options struct (perhaps StdRdOptions)
* (iv) add it to the appropriate handling routine (perhaps
@@ -147,15 +147,6 @@ static relopt_bool boolRelOpts[] =
},
false
},
- {
- {
- "vacuum_truncate",
- "Enables vacuum to truncate empty pages at the end of this table",
- RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
- ShareUpdateExclusiveLock
- },
- true
- },
{
{
"deduplicate_items",
@@ -170,6 +161,21 @@ static relopt_bool boolRelOpts[] =
{{NULL}}
};
+static relopt_ternary ternaryRelOpts[] =
+{
+ {
+ {
+ "vacuum_truncate",
+ "Enables vacuum to truncate empty pages at the end of this table",
+ RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
+ ShareUpdateExclusiveLock
+ },
+ TERNARY_UNSET
+ },
+ /* list terminator */
+ {{NULL}}
+};
+
static relopt_int intRelOpts[] =
{
{
@@ -600,6 +606,13 @@ initialize_reloptions(void)
boolRelOpts[i].gen.lockmode));
j++;
}
+ for (i = 0; ternaryRelOpts[i].gen.name; i++)
+ {
+ Assert(DoLockModesConflict(ternaryRelOpts[i].gen.lockmode,
+ ternaryRelOpts[i].gen.lockmode));
+ j++;
+ }
+
for (i = 0; intRelOpts[i].gen.name; i++)
{
Assert(DoLockModesConflict(intRelOpts[i].gen.lockmode,
@@ -640,6 +653,14 @@ initialize_reloptions(void)
j++;
}
+ for (i = 0; ternaryRelOpts[i].gen.name; i++)
+ {
+ relOpts[j] = &ternaryRelOpts[i].gen;
+ relOpts[j]->type = RELOPT_TYPE_TERNARY;
+ relOpts[j]->namelen = strlen(relOpts[j]->name);
+ j++;
+ }
+
for (i = 0; intRelOpts[i].gen.name; i++)
{
relOpts[j] = &intRelOpts[i].gen;
@@ -800,6 +821,9 @@ allocate_reloption(bits32 kinds, int type, const char *name, const char *desc,
case RELOPT_TYPE_BOOL:
size = sizeof(relopt_bool);
break;
+ case RELOPT_TYPE_TERNARY:
+ size = sizeof(relopt_ternary);
+ break;
case RELOPT_TYPE_INT:
size = sizeof(relopt_int);
break;
@@ -883,6 +907,54 @@ add_local_bool_reloption(local_relopts *relopts, const char *name,
add_local_reloption(relopts, (relopt_gen *) newoption, offset);
}
+/*
+ * init_ternary_reloption
+ * Allocate and initialize a new ternary reloption
+ */
+static relopt_ternary *
+init_ternary_reloption(bits32 kinds, const char *name, const char *desc,
+ ternary default_val, LOCKMODE lockmode)
+{
+ relopt_ternary *newoption;
+
+ newoption = (relopt_ternary *) allocate_reloption(kinds,
+ RELOPT_TYPE_TERNARY, name, desc, lockmode);
+ newoption->default_val = default_val;
+
+ return newoption;
+}
+
+/*
+ * add_ternary_reloption
+ * Add a new ternary reloption
+ */
+void
+add_ternary_reloption(bits32 kinds, const char *name, const char *desc,
+ ternary default_val, LOCKMODE lockmode)
+{
+ relopt_ternary *newoption = init_ternary_reloption(kinds, name, desc,
+ default_val, lockmode);
+
+ add_reloption((relopt_gen *) newoption);
+}
+
+/*
+ * add_local_ternary_reloption
+ * Add a new ternary local reloption
+ *
+ * 'offset' is offset of ternary-typed field.
+ */
+void
+add_local_ternary_reloption(local_relopts *relopts, const char *name,
+ const char *desc, ternary default_val,
+ int offset)
+{
+ relopt_ternary *newoption = init_ternary_reloption(RELOPT_KIND_LOCAL,
+ name, desc,
+ default_val, 0);
+
+ add_local_reloption(relopts, (relopt_gen *) newoption, offset);
+}
/*
* init_real_reloption
@@ -1617,6 +1689,18 @@ parse_one_reloption(relopt_value *option, char *text_str, int text_len,
option->gen->name, value)));
}
break;
+ case RELOPT_TYPE_TERNARY:
+ {
+ bool b;
+ parsed = parse_bool(value, &b);
+ option->values.ternary_val = b ? TERNARY_TRUE : TERNARY_FALSE;
+ if (validate && !parsed)
+ ereport(ERROR,
+ (errcode(ERRCODE_INVALID_PARAMETER_VALUE),
+ errmsg("invalid value for ternary option \"%s\": %s",
+ option->gen->name, value)));
+ }
+ break;
case RELOPT_TYPE_INT:
{
relopt_int *optint = (relopt_int *) option->gen;
@@ -1780,17 +1864,6 @@ fillRelOptions(void *rdopts, Size basesize,
char *itempos = ((char *) rdopts) + elems[j].offset;
char *string_val;
- /*
- * If isset_offset is provided, store whether the reloption is
- * set there.
- */
- if (elems[j].isset_offset > 0)
- {
- char *setpos = ((char *) rdopts) + elems[j].isset_offset;
-
- *(bool *) setpos = options[i].isset;
- }
-
switch (options[i].gen->type)
{
case RELOPT_TYPE_BOOL:
@@ -1798,6 +1871,11 @@ fillRelOptions(void *rdopts, Size basesize,
options[i].values.bool_val :
((relopt_bool *) options[i].gen)->default_val;
break;
+ case RELOPT_TYPE_TERNARY:
+ *(ternary *) itempos = options[i].isset ?
+ options[i].values.ternary_val :
+ ((relopt_ternary *) options[i].gen)->default_val;
+ break;
case RELOPT_TYPE_INT:
*(int *) itempos = options[i].isset ?
options[i].values.int_val :
@@ -1912,8 +1990,8 @@ default_reloptions(Datum reloptions, bool validate, relopt_kind kind)
offsetof(StdRdOptions, parallel_workers)},
{"vacuum_index_cleanup", RELOPT_TYPE_ENUM,
offsetof(StdRdOptions, vacuum_index_cleanup)},
- {"vacuum_truncate", RELOPT_TYPE_BOOL,
- offsetof(StdRdOptions, vacuum_truncate), offsetof(StdRdOptions, vacuum_truncate_set)},
+ {"vacuum_truncate", RELOPT_TYPE_TERNARY,
+ offsetof(StdRdOptions, vacuum_truncate)},
{"vacuum_max_eager_freeze_failure_rate", RELOPT_TYPE_REAL,
offsetof(StdRdOptions, vacuum_max_eager_freeze_failure_rate)}
};
@@ -1993,7 +2071,6 @@ build_local_reloptions(local_relopts *relopts, Datum options, bool validate)
elems[i].optname = opt->option->name;
elems[i].opttype = opt->option->type;
elems[i].offset = opt->offset;
- elems[i].isset_offset = 0; /* not supported for local relopts yet */
i++;
}
diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c
index 733ef40ae7..977babff54 100644
--- a/src/backend/commands/vacuum.c
+++ b/src/backend/commands/vacuum.c
@@ -2221,9 +2221,9 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams params,
{
StdRdOptions *opts = (StdRdOptions *) rel->rd_options;
- if (opts && opts->vacuum_truncate_set)
+ if (opts && opts->vacuum_truncate != TERNARY_UNSET)
{
- if (opts->vacuum_truncate)
+ if (opts->vacuum_truncate == TERNARY_TRUE)
params.truncate = VACOPTVALUE_ENABLED;
else
params.truncate = VACOPTVALUE_DISABLED;
diff --git a/src/include/access/reloptions.h b/src/include/access/reloptions.h
index a604a4702c..a436697658 100644
--- a/src/include/access/reloptions.h
+++ b/src/include/access/reloptions.h
@@ -29,6 +29,7 @@
typedef enum relopt_type
{
RELOPT_TYPE_BOOL,
+ RELOPT_TYPE_TERNARY, /* on, off, unset */
RELOPT_TYPE_INT,
RELOPT_TYPE_REAL,
RELOPT_TYPE_ENUM,
@@ -80,6 +81,7 @@ typedef struct relopt_value
union
{
bool bool_val;
+ ternary ternary_val;
int int_val;
double real_val;
int enum_val;
@@ -94,6 +96,12 @@ typedef struct relopt_bool
bool default_val;
} relopt_bool;
+typedef struct relopt_rernary
+{
+ relopt_gen gen;
+ int default_val;
+} relopt_ternary;
+
typedef struct relopt_int
{
relopt_gen gen;
@@ -152,19 +160,6 @@ typedef struct
const char *optname; /* option's name */
relopt_type opttype; /* option's datatype */
int offset; /* offset of field in result struct */
-
- /*
- * isset_offset is an optional offset of a field in the result struct that
- * stores whether the option is explicitly set for the relation or if it
- * just picked up the default value. In most cases, this can be
- * accomplished by giving the reloption a special out-of-range default
- * value (e.g., some integer reloptions use -2), but this isn't always
- * possible. For example, a Boolean reloption cannot be given an
- * out-of-range default, so we need another way to discover the source of
- * its value. This offset is only used if given a value greater than
- * zero.
- */
- int isset_offset;
} relopt_parse_elt;
/* Local reloption definition */
@@ -195,6 +190,8 @@ typedef struct local_relopts
extern relopt_kind add_reloption_kind(void);
extern void add_bool_reloption(bits32 kinds, const char *name, const char *desc,
bool default_val, LOCKMODE lockmode);
+extern void add_ternary_reloption(bits32 kinds, const char *name,
+ const char *desc, int default_val, LOCKMODE lockmode);
extern void add_int_reloption(bits32 kinds, const char *name, const char *desc,
int default_val, int min_val, int max_val,
LOCKMODE lockmode);
@@ -214,6 +211,9 @@ extern void register_reloptions_validator(local_relopts *relopts,
extern void add_local_bool_reloption(local_relopts *relopts, const char *name,
const char *desc, bool default_val,
int offset);
+extern void add_local_ternary_reloption(local_relopts *relopts,
+ const char *name, const char *desc,
+ ternary default_val, int offset);
extern void add_local_int_reloption(local_relopts *relopts, const char *name,
const char *desc, int default_val,
int min_val, int max_val, int offset);
diff --git a/src/include/c.h b/src/include/c.h
index 39022f8a9d..9c59ccccf8 100644
--- a/src/include/c.h
+++ b/src/include/c.h
@@ -488,6 +488,22 @@ typedef void (*pg_funcptr_t) (void);
#include <stdbool.h>
+/*
+ * ternary
+ * Boolean value with an extrea "unset" option
+ *
+ * Ternary data type is used in relation options that can be "true", "false" or
+ * "unset". Since relation options are used deep inside the PostgreSQL code,
+ * this type is declared globally.
+*/
+
+typedef enum ternary
+{
+ TERNARY_FALSE = 0,
+ TERNARY_TRUE = 1,
+ TERNARY_UNSET = -1
+} ternary;
+
/* ----------------------------------------------------------------
* Section 3: standard system types
diff --git a/src/include/utils/rel.h b/src/include/utils/rel.h
index b552359915..7346d618f9 100644
--- a/src/include/utils/rel.h
+++ b/src/include/utils/rel.h
@@ -346,8 +346,7 @@ typedef struct StdRdOptions
bool user_catalog_table; /* use as an additional catalog relation */
int parallel_workers; /* max number of parallel workers */
StdRdOptIndexCleanup vacuum_index_cleanup; /* controls index vacuuming */
- bool vacuum_truncate; /* enables vacuum to truncate a relation */
- bool vacuum_truncate_set; /* whether vacuum_truncate is set */
+ ternary vacuum_truncate; /* enables vacuum to truncate a relation */
/*
* Fraction of pages in a relation that vacuum can eagerly scan and fail
--
2.39.2
--nextPart5129932.5fSG56mABF
Content-Disposition: attachment;
filename="v2-0003-Add-alias-to-be-used-as-unset-state.patch"
Content-Transfer-Encoding: 7Bit
Content-Type: text/x-patch; charset="unicode-2-0-utf-8";
name="v2-0003-Add-alias-to-be-used-as-unset-state.patch"
^ permalink raw reply [nested|flat] 868+ messages in thread
* [PATCH v2a 2/4] Introduce ternary reloptions
@ 2025-09-04 14:41 Nikolay Shaplov <[email protected]>
0 siblings, 0 replies; 868+ messages in thread
From: Nikolay Shaplov @ 2025-09-04 14:41 UTC (permalink / raw)
Introduce ternary reloption as a replacement for current `vacuum_truncate`
implementation. Remove `vacuum_truncate_set` additional flag and using
`TERNARY_UNSET` value instead.
---
src/backend/access/common/reloptions.c | 129 ++++++++++++++++++++-----
src/backend/commands/vacuum.c | 4 +-
src/include/access/reloptions.h | 26 ++---
src/include/c.h | 16 +++
src/include/utils/rel.h | 3 +-
5 files changed, 135 insertions(+), 43 deletions(-)
diff --git a/src/backend/access/common/reloptions.c b/src/backend/access/common/reloptions.c
index 0af3fea68f..f543f11001 100644
--- a/src/backend/access/common/reloptions.c
+++ b/src/backend/access/common/reloptions.c
@@ -40,9 +40,9 @@
*
* To add an option:
*
- * (i) decide on a type (bool, integer, real, enum, string), name, default
- * value, upper and lower bounds (if applicable); for strings, consider a
- * validation routine.
+ * (i) decide on a type (bool, ternary, integer, real, enum, string), name,
+ * default value, upper and lower bounds (if applicable); for strings,
+ * consider a validation routine.
* (ii) add a record below (or use add_<type>_reloption).
* (iii) add it to the appropriate options struct (perhaps StdRdOptions)
* (iv) add it to the appropriate handling routine (perhaps
@@ -147,15 +147,6 @@ static relopt_bool boolRelOpts[] =
},
false
},
- {
- {
- "vacuum_truncate",
- "Enables vacuum to truncate empty pages at the end of this table",
- RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
- ShareUpdateExclusiveLock
- },
- true
- },
{
{
"deduplicate_items",
@@ -170,6 +161,21 @@ static relopt_bool boolRelOpts[] =
{{NULL}}
};
+static relopt_ternary ternaryRelOpts[] =
+{
+ {
+ {
+ "vacuum_truncate",
+ "Enables vacuum to truncate empty pages at the end of this table",
+ RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
+ ShareUpdateExclusiveLock
+ },
+ TERNARY_UNSET
+ },
+ /* list terminator */
+ {{NULL}}
+};
+
static relopt_int intRelOpts[] =
{
{
@@ -600,6 +606,13 @@ initialize_reloptions(void)
boolRelOpts[i].gen.lockmode));
j++;
}
+ for (i = 0; ternaryRelOpts[i].gen.name; i++)
+ {
+ Assert(DoLockModesConflict(ternaryRelOpts[i].gen.lockmode,
+ ternaryRelOpts[i].gen.lockmode));
+ j++;
+ }
+
for (i = 0; intRelOpts[i].gen.name; i++)
{
Assert(DoLockModesConflict(intRelOpts[i].gen.lockmode,
@@ -640,6 +653,14 @@ initialize_reloptions(void)
j++;
}
+ for (i = 0; ternaryRelOpts[i].gen.name; i++)
+ {
+ relOpts[j] = &ternaryRelOpts[i].gen;
+ relOpts[j]->type = RELOPT_TYPE_TERNARY;
+ relOpts[j]->namelen = strlen(relOpts[j]->name);
+ j++;
+ }
+
for (i = 0; intRelOpts[i].gen.name; i++)
{
relOpts[j] = &intRelOpts[i].gen;
@@ -800,6 +821,9 @@ allocate_reloption(bits32 kinds, int type, const char *name, const char *desc,
case RELOPT_TYPE_BOOL:
size = sizeof(relopt_bool);
break;
+ case RELOPT_TYPE_TERNARY:
+ size = sizeof(relopt_ternary);
+ break;
case RELOPT_TYPE_INT:
size = sizeof(relopt_int);
break;
@@ -883,6 +907,54 @@ add_local_bool_reloption(local_relopts *relopts, const char *name,
add_local_reloption(relopts, (relopt_gen *) newoption, offset);
}
+/*
+ * init_ternary_reloption
+ * Allocate and initialize a new ternary reloption
+ */
+static relopt_ternary *
+init_ternary_reloption(bits32 kinds, const char *name, const char *desc,
+ ternary default_val, LOCKMODE lockmode)
+{
+ relopt_ternary *newoption;
+
+ newoption = (relopt_ternary *) allocate_reloption(kinds,
+ RELOPT_TYPE_TERNARY, name, desc, lockmode);
+ newoption->default_val = default_val;
+
+ return newoption;
+}
+
+/*
+ * add_ternary_reloption
+ * Add a new ternary reloption
+ */
+void
+add_ternary_reloption(bits32 kinds, const char *name, const char *desc,
+ ternary default_val, LOCKMODE lockmode)
+{
+ relopt_ternary *newoption = init_ternary_reloption(kinds, name, desc,
+ default_val, lockmode);
+
+ add_reloption((relopt_gen *) newoption);
+}
+
+/*
+ * add_local_ternary_reloption
+ * Add a new ternary local reloption
+ *
+ * 'offset' is offset of ternary-typed field.
+ */
+void
+add_local_ternary_reloption(local_relopts *relopts, const char *name,
+ const char *desc, ternary default_val,
+ int offset)
+{
+ relopt_ternary *newoption = init_ternary_reloption(RELOPT_KIND_LOCAL,
+ name, desc,
+ default_val, 0);
+
+ add_local_reloption(relopts, (relopt_gen *) newoption, offset);
+}
/*
* init_real_reloption
@@ -1617,6 +1689,18 @@ parse_one_reloption(relopt_value *option, char *text_str, int text_len,
option->gen->name, value)));
}
break;
+ case RELOPT_TYPE_TERNARY:
+ {
+ bool b;
+ parsed = parse_bool(value, &b);
+ option->values.ternary_val = b ? TERNARY_TRUE : TERNARY_FALSE;
+ if (validate && !parsed)
+ ereport(ERROR,
+ (errcode(ERRCODE_INVALID_PARAMETER_VALUE),
+ errmsg("invalid value for ternary option \"%s\": %s",
+ option->gen->name, value)));
+ }
+ break;
case RELOPT_TYPE_INT:
{
relopt_int *optint = (relopt_int *) option->gen;
@@ -1780,17 +1864,6 @@ fillRelOptions(void *rdopts, Size basesize,
char *itempos = ((char *) rdopts) + elems[j].offset;
char *string_val;
- /*
- * If isset_offset is provided, store whether the reloption is
- * set there.
- */
- if (elems[j].isset_offset > 0)
- {
- char *setpos = ((char *) rdopts) + elems[j].isset_offset;
-
- *(bool *) setpos = options[i].isset;
- }
-
switch (options[i].gen->type)
{
case RELOPT_TYPE_BOOL:
@@ -1798,6 +1871,11 @@ fillRelOptions(void *rdopts, Size basesize,
options[i].values.bool_val :
((relopt_bool *) options[i].gen)->default_val;
break;
+ case RELOPT_TYPE_TERNARY:
+ *(ternary *) itempos = options[i].isset ?
+ options[i].values.ternary_val :
+ ((relopt_ternary *) options[i].gen)->default_val;
+ break;
case RELOPT_TYPE_INT:
*(int *) itempos = options[i].isset ?
options[i].values.int_val :
@@ -1912,8 +1990,8 @@ default_reloptions(Datum reloptions, bool validate, relopt_kind kind)
offsetof(StdRdOptions, parallel_workers)},
{"vacuum_index_cleanup", RELOPT_TYPE_ENUM,
offsetof(StdRdOptions, vacuum_index_cleanup)},
- {"vacuum_truncate", RELOPT_TYPE_BOOL,
- offsetof(StdRdOptions, vacuum_truncate), offsetof(StdRdOptions, vacuum_truncate_set)},
+ {"vacuum_truncate", RELOPT_TYPE_TERNARY,
+ offsetof(StdRdOptions, vacuum_truncate)},
{"vacuum_max_eager_freeze_failure_rate", RELOPT_TYPE_REAL,
offsetof(StdRdOptions, vacuum_max_eager_freeze_failure_rate)}
};
@@ -1993,7 +2071,6 @@ build_local_reloptions(local_relopts *relopts, Datum options, bool validate)
elems[i].optname = opt->option->name;
elems[i].opttype = opt->option->type;
elems[i].offset = opt->offset;
- elems[i].isset_offset = 0; /* not supported for local relopts yet */
i++;
}
diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c
index 733ef40ae7..977babff54 100644
--- a/src/backend/commands/vacuum.c
+++ b/src/backend/commands/vacuum.c
@@ -2221,9 +2221,9 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams params,
{
StdRdOptions *opts = (StdRdOptions *) rel->rd_options;
- if (opts && opts->vacuum_truncate_set)
+ if (opts && opts->vacuum_truncate != TERNARY_UNSET)
{
- if (opts->vacuum_truncate)
+ if (opts->vacuum_truncate == TERNARY_TRUE)
params.truncate = VACOPTVALUE_ENABLED;
else
params.truncate = VACOPTVALUE_DISABLED;
diff --git a/src/include/access/reloptions.h b/src/include/access/reloptions.h
index a604a4702c..a436697658 100644
--- a/src/include/access/reloptions.h
+++ b/src/include/access/reloptions.h
@@ -29,6 +29,7 @@
typedef enum relopt_type
{
RELOPT_TYPE_BOOL,
+ RELOPT_TYPE_TERNARY, /* on, off, unset */
RELOPT_TYPE_INT,
RELOPT_TYPE_REAL,
RELOPT_TYPE_ENUM,
@@ -80,6 +81,7 @@ typedef struct relopt_value
union
{
bool bool_val;
+ ternary ternary_val;
int int_val;
double real_val;
int enum_val;
@@ -94,6 +96,12 @@ typedef struct relopt_bool
bool default_val;
} relopt_bool;
+typedef struct relopt_rernary
+{
+ relopt_gen gen;
+ int default_val;
+} relopt_ternary;
+
typedef struct relopt_int
{
relopt_gen gen;
@@ -152,19 +160,6 @@ typedef struct
const char *optname; /* option's name */
relopt_type opttype; /* option's datatype */
int offset; /* offset of field in result struct */
-
- /*
- * isset_offset is an optional offset of a field in the result struct that
- * stores whether the option is explicitly set for the relation or if it
- * just picked up the default value. In most cases, this can be
- * accomplished by giving the reloption a special out-of-range default
- * value (e.g., some integer reloptions use -2), but this isn't always
- * possible. For example, a Boolean reloption cannot be given an
- * out-of-range default, so we need another way to discover the source of
- * its value. This offset is only used if given a value greater than
- * zero.
- */
- int isset_offset;
} relopt_parse_elt;
/* Local reloption definition */
@@ -195,6 +190,8 @@ typedef struct local_relopts
extern relopt_kind add_reloption_kind(void);
extern void add_bool_reloption(bits32 kinds, const char *name, const char *desc,
bool default_val, LOCKMODE lockmode);
+extern void add_ternary_reloption(bits32 kinds, const char *name,
+ const char *desc, int default_val, LOCKMODE lockmode);
extern void add_int_reloption(bits32 kinds, const char *name, const char *desc,
int default_val, int min_val, int max_val,
LOCKMODE lockmode);
@@ -214,6 +211,9 @@ extern void register_reloptions_validator(local_relopts *relopts,
extern void add_local_bool_reloption(local_relopts *relopts, const char *name,
const char *desc, bool default_val,
int offset);
+extern void add_local_ternary_reloption(local_relopts *relopts,
+ const char *name, const char *desc,
+ ternary default_val, int offset);
extern void add_local_int_reloption(local_relopts *relopts, const char *name,
const char *desc, int default_val,
int min_val, int max_val, int offset);
diff --git a/src/include/c.h b/src/include/c.h
index 39022f8a9d..9c59ccccf8 100644
--- a/src/include/c.h
+++ b/src/include/c.h
@@ -488,6 +488,22 @@ typedef void (*pg_funcptr_t) (void);
#include <stdbool.h>
+/*
+ * ternary
+ * Boolean value with an extrea "unset" option
+ *
+ * Ternary data type is used in relation options that can be "true", "false" or
+ * "unset". Since relation options are used deep inside the PostgreSQL code,
+ * this type is declared globally.
+*/
+
+typedef enum ternary
+{
+ TERNARY_FALSE = 0,
+ TERNARY_TRUE = 1,
+ TERNARY_UNSET = -1
+} ternary;
+
/* ----------------------------------------------------------------
* Section 3: standard system types
diff --git a/src/include/utils/rel.h b/src/include/utils/rel.h
index b552359915..7346d618f9 100644
--- a/src/include/utils/rel.h
+++ b/src/include/utils/rel.h
@@ -346,8 +346,7 @@ typedef struct StdRdOptions
bool user_catalog_table; /* use as an additional catalog relation */
int parallel_workers; /* max number of parallel workers */
StdRdOptIndexCleanup vacuum_index_cleanup; /* controls index vacuuming */
- bool vacuum_truncate; /* enables vacuum to truncate a relation */
- bool vacuum_truncate_set; /* whether vacuum_truncate is set */
+ ternary vacuum_truncate; /* enables vacuum to truncate a relation */
/*
* Fraction of pages in a relation that vacuum can eagerly scan and fail
--
2.39.2
--nextPart4155600.3ZeAukHxDK
Content-Disposition: attachment;
filename="v2a-0003-Add-alias-to-be-used-as-unset-state.patch"
Content-Transfer-Encoding: 7Bit
Content-Type: text/x-patch; charset="unicode-2-0-utf-8";
name="v2a-0003-Add-alias-to-be-used-as-unset-state.patch"
^ permalink raw reply [nested|flat] 868+ messages in thread
* [PATCH v2a 2/4] Introduce ternary reloptions
@ 2025-09-04 14:41 Nikolay Shaplov <[email protected]>
0 siblings, 0 replies; 868+ messages in thread
From: Nikolay Shaplov @ 2025-09-04 14:41 UTC (permalink / raw)
Introduce ternary reloption as a replacement for current `vacuum_truncate`
implementation. Remove `vacuum_truncate_set` additional flag and using
`TERNARY_UNSET` value instead.
---
src/backend/access/common/reloptions.c | 129 ++++++++++++++++++++-----
src/backend/commands/vacuum.c | 4 +-
src/include/access/reloptions.h | 26 ++---
src/include/c.h | 16 +++
src/include/utils/rel.h | 3 +-
5 files changed, 135 insertions(+), 43 deletions(-)
diff --git a/src/backend/access/common/reloptions.c b/src/backend/access/common/reloptions.c
index 0af3fea68f..f543f11001 100644
--- a/src/backend/access/common/reloptions.c
+++ b/src/backend/access/common/reloptions.c
@@ -40,9 +40,9 @@
*
* To add an option:
*
- * (i) decide on a type (bool, integer, real, enum, string), name, default
- * value, upper and lower bounds (if applicable); for strings, consider a
- * validation routine.
+ * (i) decide on a type (bool, ternary, integer, real, enum, string), name,
+ * default value, upper and lower bounds (if applicable); for strings,
+ * consider a validation routine.
* (ii) add a record below (or use add_<type>_reloption).
* (iii) add it to the appropriate options struct (perhaps StdRdOptions)
* (iv) add it to the appropriate handling routine (perhaps
@@ -147,15 +147,6 @@ static relopt_bool boolRelOpts[] =
},
false
},
- {
- {
- "vacuum_truncate",
- "Enables vacuum to truncate empty pages at the end of this table",
- RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
- ShareUpdateExclusiveLock
- },
- true
- },
{
{
"deduplicate_items",
@@ -170,6 +161,21 @@ static relopt_bool boolRelOpts[] =
{{NULL}}
};
+static relopt_ternary ternaryRelOpts[] =
+{
+ {
+ {
+ "vacuum_truncate",
+ "Enables vacuum to truncate empty pages at the end of this table",
+ RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
+ ShareUpdateExclusiveLock
+ },
+ TERNARY_UNSET
+ },
+ /* list terminator */
+ {{NULL}}
+};
+
static relopt_int intRelOpts[] =
{
{
@@ -600,6 +606,13 @@ initialize_reloptions(void)
boolRelOpts[i].gen.lockmode));
j++;
}
+ for (i = 0; ternaryRelOpts[i].gen.name; i++)
+ {
+ Assert(DoLockModesConflict(ternaryRelOpts[i].gen.lockmode,
+ ternaryRelOpts[i].gen.lockmode));
+ j++;
+ }
+
for (i = 0; intRelOpts[i].gen.name; i++)
{
Assert(DoLockModesConflict(intRelOpts[i].gen.lockmode,
@@ -640,6 +653,14 @@ initialize_reloptions(void)
j++;
}
+ for (i = 0; ternaryRelOpts[i].gen.name; i++)
+ {
+ relOpts[j] = &ternaryRelOpts[i].gen;
+ relOpts[j]->type = RELOPT_TYPE_TERNARY;
+ relOpts[j]->namelen = strlen(relOpts[j]->name);
+ j++;
+ }
+
for (i = 0; intRelOpts[i].gen.name; i++)
{
relOpts[j] = &intRelOpts[i].gen;
@@ -800,6 +821,9 @@ allocate_reloption(bits32 kinds, int type, const char *name, const char *desc,
case RELOPT_TYPE_BOOL:
size = sizeof(relopt_bool);
break;
+ case RELOPT_TYPE_TERNARY:
+ size = sizeof(relopt_ternary);
+ break;
case RELOPT_TYPE_INT:
size = sizeof(relopt_int);
break;
@@ -883,6 +907,54 @@ add_local_bool_reloption(local_relopts *relopts, const char *name,
add_local_reloption(relopts, (relopt_gen *) newoption, offset);
}
+/*
+ * init_ternary_reloption
+ * Allocate and initialize a new ternary reloption
+ */
+static relopt_ternary *
+init_ternary_reloption(bits32 kinds, const char *name, const char *desc,
+ ternary default_val, LOCKMODE lockmode)
+{
+ relopt_ternary *newoption;
+
+ newoption = (relopt_ternary *) allocate_reloption(kinds,
+ RELOPT_TYPE_TERNARY, name, desc, lockmode);
+ newoption->default_val = default_val;
+
+ return newoption;
+}
+
+/*
+ * add_ternary_reloption
+ * Add a new ternary reloption
+ */
+void
+add_ternary_reloption(bits32 kinds, const char *name, const char *desc,
+ ternary default_val, LOCKMODE lockmode)
+{
+ relopt_ternary *newoption = init_ternary_reloption(kinds, name, desc,
+ default_val, lockmode);
+
+ add_reloption((relopt_gen *) newoption);
+}
+
+/*
+ * add_local_ternary_reloption
+ * Add a new ternary local reloption
+ *
+ * 'offset' is offset of ternary-typed field.
+ */
+void
+add_local_ternary_reloption(local_relopts *relopts, const char *name,
+ const char *desc, ternary default_val,
+ int offset)
+{
+ relopt_ternary *newoption = init_ternary_reloption(RELOPT_KIND_LOCAL,
+ name, desc,
+ default_val, 0);
+
+ add_local_reloption(relopts, (relopt_gen *) newoption, offset);
+}
/*
* init_real_reloption
@@ -1617,6 +1689,18 @@ parse_one_reloption(relopt_value *option, char *text_str, int text_len,
option->gen->name, value)));
}
break;
+ case RELOPT_TYPE_TERNARY:
+ {
+ bool b;
+ parsed = parse_bool(value, &b);
+ option->values.ternary_val = b ? TERNARY_TRUE : TERNARY_FALSE;
+ if (validate && !parsed)
+ ereport(ERROR,
+ (errcode(ERRCODE_INVALID_PARAMETER_VALUE),
+ errmsg("invalid value for ternary option \"%s\": %s",
+ option->gen->name, value)));
+ }
+ break;
case RELOPT_TYPE_INT:
{
relopt_int *optint = (relopt_int *) option->gen;
@@ -1780,17 +1864,6 @@ fillRelOptions(void *rdopts, Size basesize,
char *itempos = ((char *) rdopts) + elems[j].offset;
char *string_val;
- /*
- * If isset_offset is provided, store whether the reloption is
- * set there.
- */
- if (elems[j].isset_offset > 0)
- {
- char *setpos = ((char *) rdopts) + elems[j].isset_offset;
-
- *(bool *) setpos = options[i].isset;
- }
-
switch (options[i].gen->type)
{
case RELOPT_TYPE_BOOL:
@@ -1798,6 +1871,11 @@ fillRelOptions(void *rdopts, Size basesize,
options[i].values.bool_val :
((relopt_bool *) options[i].gen)->default_val;
break;
+ case RELOPT_TYPE_TERNARY:
+ *(ternary *) itempos = options[i].isset ?
+ options[i].values.ternary_val :
+ ((relopt_ternary *) options[i].gen)->default_val;
+ break;
case RELOPT_TYPE_INT:
*(int *) itempos = options[i].isset ?
options[i].values.int_val :
@@ -1912,8 +1990,8 @@ default_reloptions(Datum reloptions, bool validate, relopt_kind kind)
offsetof(StdRdOptions, parallel_workers)},
{"vacuum_index_cleanup", RELOPT_TYPE_ENUM,
offsetof(StdRdOptions, vacuum_index_cleanup)},
- {"vacuum_truncate", RELOPT_TYPE_BOOL,
- offsetof(StdRdOptions, vacuum_truncate), offsetof(StdRdOptions, vacuum_truncate_set)},
+ {"vacuum_truncate", RELOPT_TYPE_TERNARY,
+ offsetof(StdRdOptions, vacuum_truncate)},
{"vacuum_max_eager_freeze_failure_rate", RELOPT_TYPE_REAL,
offsetof(StdRdOptions, vacuum_max_eager_freeze_failure_rate)}
};
@@ -1993,7 +2071,6 @@ build_local_reloptions(local_relopts *relopts, Datum options, bool validate)
elems[i].optname = opt->option->name;
elems[i].opttype = opt->option->type;
elems[i].offset = opt->offset;
- elems[i].isset_offset = 0; /* not supported for local relopts yet */
i++;
}
diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c
index 733ef40ae7..977babff54 100644
--- a/src/backend/commands/vacuum.c
+++ b/src/backend/commands/vacuum.c
@@ -2221,9 +2221,9 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams params,
{
StdRdOptions *opts = (StdRdOptions *) rel->rd_options;
- if (opts && opts->vacuum_truncate_set)
+ if (opts && opts->vacuum_truncate != TERNARY_UNSET)
{
- if (opts->vacuum_truncate)
+ if (opts->vacuum_truncate == TERNARY_TRUE)
params.truncate = VACOPTVALUE_ENABLED;
else
params.truncate = VACOPTVALUE_DISABLED;
diff --git a/src/include/access/reloptions.h b/src/include/access/reloptions.h
index a604a4702c..a436697658 100644
--- a/src/include/access/reloptions.h
+++ b/src/include/access/reloptions.h
@@ -29,6 +29,7 @@
typedef enum relopt_type
{
RELOPT_TYPE_BOOL,
+ RELOPT_TYPE_TERNARY, /* on, off, unset */
RELOPT_TYPE_INT,
RELOPT_TYPE_REAL,
RELOPT_TYPE_ENUM,
@@ -80,6 +81,7 @@ typedef struct relopt_value
union
{
bool bool_val;
+ ternary ternary_val;
int int_val;
double real_val;
int enum_val;
@@ -94,6 +96,12 @@ typedef struct relopt_bool
bool default_val;
} relopt_bool;
+typedef struct relopt_rernary
+{
+ relopt_gen gen;
+ int default_val;
+} relopt_ternary;
+
typedef struct relopt_int
{
relopt_gen gen;
@@ -152,19 +160,6 @@ typedef struct
const char *optname; /* option's name */
relopt_type opttype; /* option's datatype */
int offset; /* offset of field in result struct */
-
- /*
- * isset_offset is an optional offset of a field in the result struct that
- * stores whether the option is explicitly set for the relation or if it
- * just picked up the default value. In most cases, this can be
- * accomplished by giving the reloption a special out-of-range default
- * value (e.g., some integer reloptions use -2), but this isn't always
- * possible. For example, a Boolean reloption cannot be given an
- * out-of-range default, so we need another way to discover the source of
- * its value. This offset is only used if given a value greater than
- * zero.
- */
- int isset_offset;
} relopt_parse_elt;
/* Local reloption definition */
@@ -195,6 +190,8 @@ typedef struct local_relopts
extern relopt_kind add_reloption_kind(void);
extern void add_bool_reloption(bits32 kinds, const char *name, const char *desc,
bool default_val, LOCKMODE lockmode);
+extern void add_ternary_reloption(bits32 kinds, const char *name,
+ const char *desc, int default_val, LOCKMODE lockmode);
extern void add_int_reloption(bits32 kinds, const char *name, const char *desc,
int default_val, int min_val, int max_val,
LOCKMODE lockmode);
@@ -214,6 +211,9 @@ extern void register_reloptions_validator(local_relopts *relopts,
extern void add_local_bool_reloption(local_relopts *relopts, const char *name,
const char *desc, bool default_val,
int offset);
+extern void add_local_ternary_reloption(local_relopts *relopts,
+ const char *name, const char *desc,
+ ternary default_val, int offset);
extern void add_local_int_reloption(local_relopts *relopts, const char *name,
const char *desc, int default_val,
int min_val, int max_val, int offset);
diff --git a/src/include/c.h b/src/include/c.h
index 39022f8a9d..9c59ccccf8 100644
--- a/src/include/c.h
+++ b/src/include/c.h
@@ -488,6 +488,22 @@ typedef void (*pg_funcptr_t) (void);
#include <stdbool.h>
+/*
+ * ternary
+ * Boolean value with an extrea "unset" option
+ *
+ * Ternary data type is used in relation options that can be "true", "false" or
+ * "unset". Since relation options are used deep inside the PostgreSQL code,
+ * this type is declared globally.
+*/
+
+typedef enum ternary
+{
+ TERNARY_FALSE = 0,
+ TERNARY_TRUE = 1,
+ TERNARY_UNSET = -1
+} ternary;
+
/* ----------------------------------------------------------------
* Section 3: standard system types
diff --git a/src/include/utils/rel.h b/src/include/utils/rel.h
index b552359915..7346d618f9 100644
--- a/src/include/utils/rel.h
+++ b/src/include/utils/rel.h
@@ -346,8 +346,7 @@ typedef struct StdRdOptions
bool user_catalog_table; /* use as an additional catalog relation */
int parallel_workers; /* max number of parallel workers */
StdRdOptIndexCleanup vacuum_index_cleanup; /* controls index vacuuming */
- bool vacuum_truncate; /* enables vacuum to truncate a relation */
- bool vacuum_truncate_set; /* whether vacuum_truncate is set */
+ ternary vacuum_truncate; /* enables vacuum to truncate a relation */
/*
* Fraction of pages in a relation that vacuum can eagerly scan and fail
--
2.39.2
--nextPart4155600.3ZeAukHxDK
Content-Disposition: attachment;
filename="v2a-0003-Add-alias-to-be-used-as-unset-state.patch"
Content-Transfer-Encoding: 7Bit
Content-Type: text/x-patch; charset="unicode-2-0-utf-8";
name="v2a-0003-Add-alias-to-be-used-as-unset-state.patch"
^ permalink raw reply [nested|flat] 868+ messages in thread
* [PATCH v2 2/4] Introduce ternary reloptions
@ 2025-09-04 14:41 Nikolay Shaplov <[email protected]>
0 siblings, 0 replies; 868+ messages in thread
From: Nikolay Shaplov @ 2025-09-04 14:41 UTC (permalink / raw)
Introduce ternary reloption as a replacement for current `vacuum_truncate`
implementation. Remove `vacuum_truncate_set` additional flag and using
`TERNARY_UNSET` value instead.
---
src/backend/access/common/reloptions.c | 129 ++++++++++++++++++++-----
src/backend/commands/vacuum.c | 4 +-
src/include/access/reloptions.h | 26 ++---
src/include/c.h | 16 +++
src/include/utils/rel.h | 3 +-
5 files changed, 135 insertions(+), 43 deletions(-)
diff --git a/src/backend/access/common/reloptions.c b/src/backend/access/common/reloptions.c
index 0af3fea68f..f543f11001 100644
--- a/src/backend/access/common/reloptions.c
+++ b/src/backend/access/common/reloptions.c
@@ -40,9 +40,9 @@
*
* To add an option:
*
- * (i) decide on a type (bool, integer, real, enum, string), name, default
- * value, upper and lower bounds (if applicable); for strings, consider a
- * validation routine.
+ * (i) decide on a type (bool, ternary, integer, real, enum, string), name,
+ * default value, upper and lower bounds (if applicable); for strings,
+ * consider a validation routine.
* (ii) add a record below (or use add_<type>_reloption).
* (iii) add it to the appropriate options struct (perhaps StdRdOptions)
* (iv) add it to the appropriate handling routine (perhaps
@@ -147,15 +147,6 @@ static relopt_bool boolRelOpts[] =
},
false
},
- {
- {
- "vacuum_truncate",
- "Enables vacuum to truncate empty pages at the end of this table",
- RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
- ShareUpdateExclusiveLock
- },
- true
- },
{
{
"deduplicate_items",
@@ -170,6 +161,21 @@ static relopt_bool boolRelOpts[] =
{{NULL}}
};
+static relopt_ternary ternaryRelOpts[] =
+{
+ {
+ {
+ "vacuum_truncate",
+ "Enables vacuum to truncate empty pages at the end of this table",
+ RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
+ ShareUpdateExclusiveLock
+ },
+ TERNARY_UNSET
+ },
+ /* list terminator */
+ {{NULL}}
+};
+
static relopt_int intRelOpts[] =
{
{
@@ -600,6 +606,13 @@ initialize_reloptions(void)
boolRelOpts[i].gen.lockmode));
j++;
}
+ for (i = 0; ternaryRelOpts[i].gen.name; i++)
+ {
+ Assert(DoLockModesConflict(ternaryRelOpts[i].gen.lockmode,
+ ternaryRelOpts[i].gen.lockmode));
+ j++;
+ }
+
for (i = 0; intRelOpts[i].gen.name; i++)
{
Assert(DoLockModesConflict(intRelOpts[i].gen.lockmode,
@@ -640,6 +653,14 @@ initialize_reloptions(void)
j++;
}
+ for (i = 0; ternaryRelOpts[i].gen.name; i++)
+ {
+ relOpts[j] = &ternaryRelOpts[i].gen;
+ relOpts[j]->type = RELOPT_TYPE_TERNARY;
+ relOpts[j]->namelen = strlen(relOpts[j]->name);
+ j++;
+ }
+
for (i = 0; intRelOpts[i].gen.name; i++)
{
relOpts[j] = &intRelOpts[i].gen;
@@ -800,6 +821,9 @@ allocate_reloption(bits32 kinds, int type, const char *name, const char *desc,
case RELOPT_TYPE_BOOL:
size = sizeof(relopt_bool);
break;
+ case RELOPT_TYPE_TERNARY:
+ size = sizeof(relopt_ternary);
+ break;
case RELOPT_TYPE_INT:
size = sizeof(relopt_int);
break;
@@ -883,6 +907,54 @@ add_local_bool_reloption(local_relopts *relopts, const char *name,
add_local_reloption(relopts, (relopt_gen *) newoption, offset);
}
+/*
+ * init_ternary_reloption
+ * Allocate and initialize a new ternary reloption
+ */
+static relopt_ternary *
+init_ternary_reloption(bits32 kinds, const char *name, const char *desc,
+ ternary default_val, LOCKMODE lockmode)
+{
+ relopt_ternary *newoption;
+
+ newoption = (relopt_ternary *) allocate_reloption(kinds,
+ RELOPT_TYPE_TERNARY, name, desc, lockmode);
+ newoption->default_val = default_val;
+
+ return newoption;
+}
+
+/*
+ * add_ternary_reloption
+ * Add a new ternary reloption
+ */
+void
+add_ternary_reloption(bits32 kinds, const char *name, const char *desc,
+ ternary default_val, LOCKMODE lockmode)
+{
+ relopt_ternary *newoption = init_ternary_reloption(kinds, name, desc,
+ default_val, lockmode);
+
+ add_reloption((relopt_gen *) newoption);
+}
+
+/*
+ * add_local_ternary_reloption
+ * Add a new ternary local reloption
+ *
+ * 'offset' is offset of ternary-typed field.
+ */
+void
+add_local_ternary_reloption(local_relopts *relopts, const char *name,
+ const char *desc, ternary default_val,
+ int offset)
+{
+ relopt_ternary *newoption = init_ternary_reloption(RELOPT_KIND_LOCAL,
+ name, desc,
+ default_val, 0);
+
+ add_local_reloption(relopts, (relopt_gen *) newoption, offset);
+}
/*
* init_real_reloption
@@ -1617,6 +1689,18 @@ parse_one_reloption(relopt_value *option, char *text_str, int text_len,
option->gen->name, value)));
}
break;
+ case RELOPT_TYPE_TERNARY:
+ {
+ bool b;
+ parsed = parse_bool(value, &b);
+ option->values.ternary_val = b ? TERNARY_TRUE : TERNARY_FALSE;
+ if (validate && !parsed)
+ ereport(ERROR,
+ (errcode(ERRCODE_INVALID_PARAMETER_VALUE),
+ errmsg("invalid value for ternary option \"%s\": %s",
+ option->gen->name, value)));
+ }
+ break;
case RELOPT_TYPE_INT:
{
relopt_int *optint = (relopt_int *) option->gen;
@@ -1780,17 +1864,6 @@ fillRelOptions(void *rdopts, Size basesize,
char *itempos = ((char *) rdopts) + elems[j].offset;
char *string_val;
- /*
- * If isset_offset is provided, store whether the reloption is
- * set there.
- */
- if (elems[j].isset_offset > 0)
- {
- char *setpos = ((char *) rdopts) + elems[j].isset_offset;
-
- *(bool *) setpos = options[i].isset;
- }
-
switch (options[i].gen->type)
{
case RELOPT_TYPE_BOOL:
@@ -1798,6 +1871,11 @@ fillRelOptions(void *rdopts, Size basesize,
options[i].values.bool_val :
((relopt_bool *) options[i].gen)->default_val;
break;
+ case RELOPT_TYPE_TERNARY:
+ *(ternary *) itempos = options[i].isset ?
+ options[i].values.ternary_val :
+ ((relopt_ternary *) options[i].gen)->default_val;
+ break;
case RELOPT_TYPE_INT:
*(int *) itempos = options[i].isset ?
options[i].values.int_val :
@@ -1912,8 +1990,8 @@ default_reloptions(Datum reloptions, bool validate, relopt_kind kind)
offsetof(StdRdOptions, parallel_workers)},
{"vacuum_index_cleanup", RELOPT_TYPE_ENUM,
offsetof(StdRdOptions, vacuum_index_cleanup)},
- {"vacuum_truncate", RELOPT_TYPE_BOOL,
- offsetof(StdRdOptions, vacuum_truncate), offsetof(StdRdOptions, vacuum_truncate_set)},
+ {"vacuum_truncate", RELOPT_TYPE_TERNARY,
+ offsetof(StdRdOptions, vacuum_truncate)},
{"vacuum_max_eager_freeze_failure_rate", RELOPT_TYPE_REAL,
offsetof(StdRdOptions, vacuum_max_eager_freeze_failure_rate)}
};
@@ -1993,7 +2071,6 @@ build_local_reloptions(local_relopts *relopts, Datum options, bool validate)
elems[i].optname = opt->option->name;
elems[i].opttype = opt->option->type;
elems[i].offset = opt->offset;
- elems[i].isset_offset = 0; /* not supported for local relopts yet */
i++;
}
diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c
index 733ef40ae7..977babff54 100644
--- a/src/backend/commands/vacuum.c
+++ b/src/backend/commands/vacuum.c
@@ -2221,9 +2221,9 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams params,
{
StdRdOptions *opts = (StdRdOptions *) rel->rd_options;
- if (opts && opts->vacuum_truncate_set)
+ if (opts && opts->vacuum_truncate != TERNARY_UNSET)
{
- if (opts->vacuum_truncate)
+ if (opts->vacuum_truncate == TERNARY_TRUE)
params.truncate = VACOPTVALUE_ENABLED;
else
params.truncate = VACOPTVALUE_DISABLED;
diff --git a/src/include/access/reloptions.h b/src/include/access/reloptions.h
index a604a4702c..a436697658 100644
--- a/src/include/access/reloptions.h
+++ b/src/include/access/reloptions.h
@@ -29,6 +29,7 @@
typedef enum relopt_type
{
RELOPT_TYPE_BOOL,
+ RELOPT_TYPE_TERNARY, /* on, off, unset */
RELOPT_TYPE_INT,
RELOPT_TYPE_REAL,
RELOPT_TYPE_ENUM,
@@ -80,6 +81,7 @@ typedef struct relopt_value
union
{
bool bool_val;
+ ternary ternary_val;
int int_val;
double real_val;
int enum_val;
@@ -94,6 +96,12 @@ typedef struct relopt_bool
bool default_val;
} relopt_bool;
+typedef struct relopt_rernary
+{
+ relopt_gen gen;
+ int default_val;
+} relopt_ternary;
+
typedef struct relopt_int
{
relopt_gen gen;
@@ -152,19 +160,6 @@ typedef struct
const char *optname; /* option's name */
relopt_type opttype; /* option's datatype */
int offset; /* offset of field in result struct */
-
- /*
- * isset_offset is an optional offset of a field in the result struct that
- * stores whether the option is explicitly set for the relation or if it
- * just picked up the default value. In most cases, this can be
- * accomplished by giving the reloption a special out-of-range default
- * value (e.g., some integer reloptions use -2), but this isn't always
- * possible. For example, a Boolean reloption cannot be given an
- * out-of-range default, so we need another way to discover the source of
- * its value. This offset is only used if given a value greater than
- * zero.
- */
- int isset_offset;
} relopt_parse_elt;
/* Local reloption definition */
@@ -195,6 +190,8 @@ typedef struct local_relopts
extern relopt_kind add_reloption_kind(void);
extern void add_bool_reloption(bits32 kinds, const char *name, const char *desc,
bool default_val, LOCKMODE lockmode);
+extern void add_ternary_reloption(bits32 kinds, const char *name,
+ const char *desc, int default_val, LOCKMODE lockmode);
extern void add_int_reloption(bits32 kinds, const char *name, const char *desc,
int default_val, int min_val, int max_val,
LOCKMODE lockmode);
@@ -214,6 +211,9 @@ extern void register_reloptions_validator(local_relopts *relopts,
extern void add_local_bool_reloption(local_relopts *relopts, const char *name,
const char *desc, bool default_val,
int offset);
+extern void add_local_ternary_reloption(local_relopts *relopts,
+ const char *name, const char *desc,
+ ternary default_val, int offset);
extern void add_local_int_reloption(local_relopts *relopts, const char *name,
const char *desc, int default_val,
int min_val, int max_val, int offset);
diff --git a/src/include/c.h b/src/include/c.h
index 39022f8a9d..9c59ccccf8 100644
--- a/src/include/c.h
+++ b/src/include/c.h
@@ -488,6 +488,22 @@ typedef void (*pg_funcptr_t) (void);
#include <stdbool.h>
+/*
+ * ternary
+ * Boolean value with an extrea "unset" option
+ *
+ * Ternary data type is used in relation options that can be "true", "false" or
+ * "unset". Since relation options are used deep inside the PostgreSQL code,
+ * this type is declared globally.
+*/
+
+typedef enum ternary
+{
+ TERNARY_FALSE = 0,
+ TERNARY_TRUE = 1,
+ TERNARY_UNSET = -1
+} ternary;
+
/* ----------------------------------------------------------------
* Section 3: standard system types
diff --git a/src/include/utils/rel.h b/src/include/utils/rel.h
index b552359915..7346d618f9 100644
--- a/src/include/utils/rel.h
+++ b/src/include/utils/rel.h
@@ -346,8 +346,7 @@ typedef struct StdRdOptions
bool user_catalog_table; /* use as an additional catalog relation */
int parallel_workers; /* max number of parallel workers */
StdRdOptIndexCleanup vacuum_index_cleanup; /* controls index vacuuming */
- bool vacuum_truncate; /* enables vacuum to truncate a relation */
- bool vacuum_truncate_set; /* whether vacuum_truncate is set */
+ ternary vacuum_truncate; /* enables vacuum to truncate a relation */
/*
* Fraction of pages in a relation that vacuum can eagerly scan and fail
--
2.39.2
--nextPart5129932.5fSG56mABF
Content-Disposition: attachment;
filename="v2-0003-Add-alias-to-be-used-as-unset-state.patch"
Content-Transfer-Encoding: 7Bit
Content-Type: text/x-patch; charset="unicode-2-0-utf-8";
name="v2-0003-Add-alias-to-be-used-as-unset-state.patch"
^ permalink raw reply [nested|flat] 868+ messages in thread
* [PATCH v2a 2/4] Introduce ternary reloptions
@ 2025-09-04 14:41 Nikolay Shaplov <[email protected]>
0 siblings, 0 replies; 868+ messages in thread
From: Nikolay Shaplov @ 2025-09-04 14:41 UTC (permalink / raw)
Introduce ternary reloption as a replacement for current `vacuum_truncate`
implementation. Remove `vacuum_truncate_set` additional flag and using
`TERNARY_UNSET` value instead.
---
src/backend/access/common/reloptions.c | 129 ++++++++++++++++++++-----
src/backend/commands/vacuum.c | 4 +-
src/include/access/reloptions.h | 26 ++---
src/include/c.h | 16 +++
src/include/utils/rel.h | 3 +-
5 files changed, 135 insertions(+), 43 deletions(-)
diff --git a/src/backend/access/common/reloptions.c b/src/backend/access/common/reloptions.c
index 0af3fea68f..f543f11001 100644
--- a/src/backend/access/common/reloptions.c
+++ b/src/backend/access/common/reloptions.c
@@ -40,9 +40,9 @@
*
* To add an option:
*
- * (i) decide on a type (bool, integer, real, enum, string), name, default
- * value, upper and lower bounds (if applicable); for strings, consider a
- * validation routine.
+ * (i) decide on a type (bool, ternary, integer, real, enum, string), name,
+ * default value, upper and lower bounds (if applicable); for strings,
+ * consider a validation routine.
* (ii) add a record below (or use add_<type>_reloption).
* (iii) add it to the appropriate options struct (perhaps StdRdOptions)
* (iv) add it to the appropriate handling routine (perhaps
@@ -147,15 +147,6 @@ static relopt_bool boolRelOpts[] =
},
false
},
- {
- {
- "vacuum_truncate",
- "Enables vacuum to truncate empty pages at the end of this table",
- RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
- ShareUpdateExclusiveLock
- },
- true
- },
{
{
"deduplicate_items",
@@ -170,6 +161,21 @@ static relopt_bool boolRelOpts[] =
{{NULL}}
};
+static relopt_ternary ternaryRelOpts[] =
+{
+ {
+ {
+ "vacuum_truncate",
+ "Enables vacuum to truncate empty pages at the end of this table",
+ RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
+ ShareUpdateExclusiveLock
+ },
+ TERNARY_UNSET
+ },
+ /* list terminator */
+ {{NULL}}
+};
+
static relopt_int intRelOpts[] =
{
{
@@ -600,6 +606,13 @@ initialize_reloptions(void)
boolRelOpts[i].gen.lockmode));
j++;
}
+ for (i = 0; ternaryRelOpts[i].gen.name; i++)
+ {
+ Assert(DoLockModesConflict(ternaryRelOpts[i].gen.lockmode,
+ ternaryRelOpts[i].gen.lockmode));
+ j++;
+ }
+
for (i = 0; intRelOpts[i].gen.name; i++)
{
Assert(DoLockModesConflict(intRelOpts[i].gen.lockmode,
@@ -640,6 +653,14 @@ initialize_reloptions(void)
j++;
}
+ for (i = 0; ternaryRelOpts[i].gen.name; i++)
+ {
+ relOpts[j] = &ternaryRelOpts[i].gen;
+ relOpts[j]->type = RELOPT_TYPE_TERNARY;
+ relOpts[j]->namelen = strlen(relOpts[j]->name);
+ j++;
+ }
+
for (i = 0; intRelOpts[i].gen.name; i++)
{
relOpts[j] = &intRelOpts[i].gen;
@@ -800,6 +821,9 @@ allocate_reloption(bits32 kinds, int type, const char *name, const char *desc,
case RELOPT_TYPE_BOOL:
size = sizeof(relopt_bool);
break;
+ case RELOPT_TYPE_TERNARY:
+ size = sizeof(relopt_ternary);
+ break;
case RELOPT_TYPE_INT:
size = sizeof(relopt_int);
break;
@@ -883,6 +907,54 @@ add_local_bool_reloption(local_relopts *relopts, const char *name,
add_local_reloption(relopts, (relopt_gen *) newoption, offset);
}
+/*
+ * init_ternary_reloption
+ * Allocate and initialize a new ternary reloption
+ */
+static relopt_ternary *
+init_ternary_reloption(bits32 kinds, const char *name, const char *desc,
+ ternary default_val, LOCKMODE lockmode)
+{
+ relopt_ternary *newoption;
+
+ newoption = (relopt_ternary *) allocate_reloption(kinds,
+ RELOPT_TYPE_TERNARY, name, desc, lockmode);
+ newoption->default_val = default_val;
+
+ return newoption;
+}
+
+/*
+ * add_ternary_reloption
+ * Add a new ternary reloption
+ */
+void
+add_ternary_reloption(bits32 kinds, const char *name, const char *desc,
+ ternary default_val, LOCKMODE lockmode)
+{
+ relopt_ternary *newoption = init_ternary_reloption(kinds, name, desc,
+ default_val, lockmode);
+
+ add_reloption((relopt_gen *) newoption);
+}
+
+/*
+ * add_local_ternary_reloption
+ * Add a new ternary local reloption
+ *
+ * 'offset' is offset of ternary-typed field.
+ */
+void
+add_local_ternary_reloption(local_relopts *relopts, const char *name,
+ const char *desc, ternary default_val,
+ int offset)
+{
+ relopt_ternary *newoption = init_ternary_reloption(RELOPT_KIND_LOCAL,
+ name, desc,
+ default_val, 0);
+
+ add_local_reloption(relopts, (relopt_gen *) newoption, offset);
+}
/*
* init_real_reloption
@@ -1617,6 +1689,18 @@ parse_one_reloption(relopt_value *option, char *text_str, int text_len,
option->gen->name, value)));
}
break;
+ case RELOPT_TYPE_TERNARY:
+ {
+ bool b;
+ parsed = parse_bool(value, &b);
+ option->values.ternary_val = b ? TERNARY_TRUE : TERNARY_FALSE;
+ if (validate && !parsed)
+ ereport(ERROR,
+ (errcode(ERRCODE_INVALID_PARAMETER_VALUE),
+ errmsg("invalid value for ternary option \"%s\": %s",
+ option->gen->name, value)));
+ }
+ break;
case RELOPT_TYPE_INT:
{
relopt_int *optint = (relopt_int *) option->gen;
@@ -1780,17 +1864,6 @@ fillRelOptions(void *rdopts, Size basesize,
char *itempos = ((char *) rdopts) + elems[j].offset;
char *string_val;
- /*
- * If isset_offset is provided, store whether the reloption is
- * set there.
- */
- if (elems[j].isset_offset > 0)
- {
- char *setpos = ((char *) rdopts) + elems[j].isset_offset;
-
- *(bool *) setpos = options[i].isset;
- }
-
switch (options[i].gen->type)
{
case RELOPT_TYPE_BOOL:
@@ -1798,6 +1871,11 @@ fillRelOptions(void *rdopts, Size basesize,
options[i].values.bool_val :
((relopt_bool *) options[i].gen)->default_val;
break;
+ case RELOPT_TYPE_TERNARY:
+ *(ternary *) itempos = options[i].isset ?
+ options[i].values.ternary_val :
+ ((relopt_ternary *) options[i].gen)->default_val;
+ break;
case RELOPT_TYPE_INT:
*(int *) itempos = options[i].isset ?
options[i].values.int_val :
@@ -1912,8 +1990,8 @@ default_reloptions(Datum reloptions, bool validate, relopt_kind kind)
offsetof(StdRdOptions, parallel_workers)},
{"vacuum_index_cleanup", RELOPT_TYPE_ENUM,
offsetof(StdRdOptions, vacuum_index_cleanup)},
- {"vacuum_truncate", RELOPT_TYPE_BOOL,
- offsetof(StdRdOptions, vacuum_truncate), offsetof(StdRdOptions, vacuum_truncate_set)},
+ {"vacuum_truncate", RELOPT_TYPE_TERNARY,
+ offsetof(StdRdOptions, vacuum_truncate)},
{"vacuum_max_eager_freeze_failure_rate", RELOPT_TYPE_REAL,
offsetof(StdRdOptions, vacuum_max_eager_freeze_failure_rate)}
};
@@ -1993,7 +2071,6 @@ build_local_reloptions(local_relopts *relopts, Datum options, bool validate)
elems[i].optname = opt->option->name;
elems[i].opttype = opt->option->type;
elems[i].offset = opt->offset;
- elems[i].isset_offset = 0; /* not supported for local relopts yet */
i++;
}
diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c
index 733ef40ae7..977babff54 100644
--- a/src/backend/commands/vacuum.c
+++ b/src/backend/commands/vacuum.c
@@ -2221,9 +2221,9 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams params,
{
StdRdOptions *opts = (StdRdOptions *) rel->rd_options;
- if (opts && opts->vacuum_truncate_set)
+ if (opts && opts->vacuum_truncate != TERNARY_UNSET)
{
- if (opts->vacuum_truncate)
+ if (opts->vacuum_truncate == TERNARY_TRUE)
params.truncate = VACOPTVALUE_ENABLED;
else
params.truncate = VACOPTVALUE_DISABLED;
diff --git a/src/include/access/reloptions.h b/src/include/access/reloptions.h
index a604a4702c..a436697658 100644
--- a/src/include/access/reloptions.h
+++ b/src/include/access/reloptions.h
@@ -29,6 +29,7 @@
typedef enum relopt_type
{
RELOPT_TYPE_BOOL,
+ RELOPT_TYPE_TERNARY, /* on, off, unset */
RELOPT_TYPE_INT,
RELOPT_TYPE_REAL,
RELOPT_TYPE_ENUM,
@@ -80,6 +81,7 @@ typedef struct relopt_value
union
{
bool bool_val;
+ ternary ternary_val;
int int_val;
double real_val;
int enum_val;
@@ -94,6 +96,12 @@ typedef struct relopt_bool
bool default_val;
} relopt_bool;
+typedef struct relopt_rernary
+{
+ relopt_gen gen;
+ int default_val;
+} relopt_ternary;
+
typedef struct relopt_int
{
relopt_gen gen;
@@ -152,19 +160,6 @@ typedef struct
const char *optname; /* option's name */
relopt_type opttype; /* option's datatype */
int offset; /* offset of field in result struct */
-
- /*
- * isset_offset is an optional offset of a field in the result struct that
- * stores whether the option is explicitly set for the relation or if it
- * just picked up the default value. In most cases, this can be
- * accomplished by giving the reloption a special out-of-range default
- * value (e.g., some integer reloptions use -2), but this isn't always
- * possible. For example, a Boolean reloption cannot be given an
- * out-of-range default, so we need another way to discover the source of
- * its value. This offset is only used if given a value greater than
- * zero.
- */
- int isset_offset;
} relopt_parse_elt;
/* Local reloption definition */
@@ -195,6 +190,8 @@ typedef struct local_relopts
extern relopt_kind add_reloption_kind(void);
extern void add_bool_reloption(bits32 kinds, const char *name, const char *desc,
bool default_val, LOCKMODE lockmode);
+extern void add_ternary_reloption(bits32 kinds, const char *name,
+ const char *desc, int default_val, LOCKMODE lockmode);
extern void add_int_reloption(bits32 kinds, const char *name, const char *desc,
int default_val, int min_val, int max_val,
LOCKMODE lockmode);
@@ -214,6 +211,9 @@ extern void register_reloptions_validator(local_relopts *relopts,
extern void add_local_bool_reloption(local_relopts *relopts, const char *name,
const char *desc, bool default_val,
int offset);
+extern void add_local_ternary_reloption(local_relopts *relopts,
+ const char *name, const char *desc,
+ ternary default_val, int offset);
extern void add_local_int_reloption(local_relopts *relopts, const char *name,
const char *desc, int default_val,
int min_val, int max_val, int offset);
diff --git a/src/include/c.h b/src/include/c.h
index 39022f8a9d..9c59ccccf8 100644
--- a/src/include/c.h
+++ b/src/include/c.h
@@ -488,6 +488,22 @@ typedef void (*pg_funcptr_t) (void);
#include <stdbool.h>
+/*
+ * ternary
+ * Boolean value with an extrea "unset" option
+ *
+ * Ternary data type is used in relation options that can be "true", "false" or
+ * "unset". Since relation options are used deep inside the PostgreSQL code,
+ * this type is declared globally.
+*/
+
+typedef enum ternary
+{
+ TERNARY_FALSE = 0,
+ TERNARY_TRUE = 1,
+ TERNARY_UNSET = -1
+} ternary;
+
/* ----------------------------------------------------------------
* Section 3: standard system types
diff --git a/src/include/utils/rel.h b/src/include/utils/rel.h
index b552359915..7346d618f9 100644
--- a/src/include/utils/rel.h
+++ b/src/include/utils/rel.h
@@ -346,8 +346,7 @@ typedef struct StdRdOptions
bool user_catalog_table; /* use as an additional catalog relation */
int parallel_workers; /* max number of parallel workers */
StdRdOptIndexCleanup vacuum_index_cleanup; /* controls index vacuuming */
- bool vacuum_truncate; /* enables vacuum to truncate a relation */
- bool vacuum_truncate_set; /* whether vacuum_truncate is set */
+ ternary vacuum_truncate; /* enables vacuum to truncate a relation */
/*
* Fraction of pages in a relation that vacuum can eagerly scan and fail
--
2.39.2
--nextPart4155600.3ZeAukHxDK
Content-Disposition: attachment;
filename="v2a-0003-Add-alias-to-be-used-as-unset-state.patch"
Content-Transfer-Encoding: 7Bit
Content-Type: text/x-patch; charset="unicode-2-0-utf-8";
name="v2a-0003-Add-alias-to-be-used-as-unset-state.patch"
^ permalink raw reply [nested|flat] 868+ messages in thread
* [PATCH v2 2/4] Introduce ternary reloptions
@ 2025-09-04 14:41 Nikolay Shaplov <[email protected]>
0 siblings, 0 replies; 868+ messages in thread
From: Nikolay Shaplov @ 2025-09-04 14:41 UTC (permalink / raw)
Introduce ternary reloption as a replacement for current `vacuum_truncate`
implementation. Remove `vacuum_truncate_set` additional flag and using
`TERNARY_UNSET` value instead.
---
src/backend/access/common/reloptions.c | 129 ++++++++++++++++++++-----
src/backend/commands/vacuum.c | 4 +-
src/include/access/reloptions.h | 26 ++---
src/include/c.h | 16 +++
src/include/utils/rel.h | 3 +-
5 files changed, 135 insertions(+), 43 deletions(-)
diff --git a/src/backend/access/common/reloptions.c b/src/backend/access/common/reloptions.c
index 0af3fea68f..f543f11001 100644
--- a/src/backend/access/common/reloptions.c
+++ b/src/backend/access/common/reloptions.c
@@ -40,9 +40,9 @@
*
* To add an option:
*
- * (i) decide on a type (bool, integer, real, enum, string), name, default
- * value, upper and lower bounds (if applicable); for strings, consider a
- * validation routine.
+ * (i) decide on a type (bool, ternary, integer, real, enum, string), name,
+ * default value, upper and lower bounds (if applicable); for strings,
+ * consider a validation routine.
* (ii) add a record below (or use add_<type>_reloption).
* (iii) add it to the appropriate options struct (perhaps StdRdOptions)
* (iv) add it to the appropriate handling routine (perhaps
@@ -147,15 +147,6 @@ static relopt_bool boolRelOpts[] =
},
false
},
- {
- {
- "vacuum_truncate",
- "Enables vacuum to truncate empty pages at the end of this table",
- RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
- ShareUpdateExclusiveLock
- },
- true
- },
{
{
"deduplicate_items",
@@ -170,6 +161,21 @@ static relopt_bool boolRelOpts[] =
{{NULL}}
};
+static relopt_ternary ternaryRelOpts[] =
+{
+ {
+ {
+ "vacuum_truncate",
+ "Enables vacuum to truncate empty pages at the end of this table",
+ RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
+ ShareUpdateExclusiveLock
+ },
+ TERNARY_UNSET
+ },
+ /* list terminator */
+ {{NULL}}
+};
+
static relopt_int intRelOpts[] =
{
{
@@ -600,6 +606,13 @@ initialize_reloptions(void)
boolRelOpts[i].gen.lockmode));
j++;
}
+ for (i = 0; ternaryRelOpts[i].gen.name; i++)
+ {
+ Assert(DoLockModesConflict(ternaryRelOpts[i].gen.lockmode,
+ ternaryRelOpts[i].gen.lockmode));
+ j++;
+ }
+
for (i = 0; intRelOpts[i].gen.name; i++)
{
Assert(DoLockModesConflict(intRelOpts[i].gen.lockmode,
@@ -640,6 +653,14 @@ initialize_reloptions(void)
j++;
}
+ for (i = 0; ternaryRelOpts[i].gen.name; i++)
+ {
+ relOpts[j] = &ternaryRelOpts[i].gen;
+ relOpts[j]->type = RELOPT_TYPE_TERNARY;
+ relOpts[j]->namelen = strlen(relOpts[j]->name);
+ j++;
+ }
+
for (i = 0; intRelOpts[i].gen.name; i++)
{
relOpts[j] = &intRelOpts[i].gen;
@@ -800,6 +821,9 @@ allocate_reloption(bits32 kinds, int type, const char *name, const char *desc,
case RELOPT_TYPE_BOOL:
size = sizeof(relopt_bool);
break;
+ case RELOPT_TYPE_TERNARY:
+ size = sizeof(relopt_ternary);
+ break;
case RELOPT_TYPE_INT:
size = sizeof(relopt_int);
break;
@@ -883,6 +907,54 @@ add_local_bool_reloption(local_relopts *relopts, const char *name,
add_local_reloption(relopts, (relopt_gen *) newoption, offset);
}
+/*
+ * init_ternary_reloption
+ * Allocate and initialize a new ternary reloption
+ */
+static relopt_ternary *
+init_ternary_reloption(bits32 kinds, const char *name, const char *desc,
+ ternary default_val, LOCKMODE lockmode)
+{
+ relopt_ternary *newoption;
+
+ newoption = (relopt_ternary *) allocate_reloption(kinds,
+ RELOPT_TYPE_TERNARY, name, desc, lockmode);
+ newoption->default_val = default_val;
+
+ return newoption;
+}
+
+/*
+ * add_ternary_reloption
+ * Add a new ternary reloption
+ */
+void
+add_ternary_reloption(bits32 kinds, const char *name, const char *desc,
+ ternary default_val, LOCKMODE lockmode)
+{
+ relopt_ternary *newoption = init_ternary_reloption(kinds, name, desc,
+ default_val, lockmode);
+
+ add_reloption((relopt_gen *) newoption);
+}
+
+/*
+ * add_local_ternary_reloption
+ * Add a new ternary local reloption
+ *
+ * 'offset' is offset of ternary-typed field.
+ */
+void
+add_local_ternary_reloption(local_relopts *relopts, const char *name,
+ const char *desc, ternary default_val,
+ int offset)
+{
+ relopt_ternary *newoption = init_ternary_reloption(RELOPT_KIND_LOCAL,
+ name, desc,
+ default_val, 0);
+
+ add_local_reloption(relopts, (relopt_gen *) newoption, offset);
+}
/*
* init_real_reloption
@@ -1617,6 +1689,18 @@ parse_one_reloption(relopt_value *option, char *text_str, int text_len,
option->gen->name, value)));
}
break;
+ case RELOPT_TYPE_TERNARY:
+ {
+ bool b;
+ parsed = parse_bool(value, &b);
+ option->values.ternary_val = b ? TERNARY_TRUE : TERNARY_FALSE;
+ if (validate && !parsed)
+ ereport(ERROR,
+ (errcode(ERRCODE_INVALID_PARAMETER_VALUE),
+ errmsg("invalid value for ternary option \"%s\": %s",
+ option->gen->name, value)));
+ }
+ break;
case RELOPT_TYPE_INT:
{
relopt_int *optint = (relopt_int *) option->gen;
@@ -1780,17 +1864,6 @@ fillRelOptions(void *rdopts, Size basesize,
char *itempos = ((char *) rdopts) + elems[j].offset;
char *string_val;
- /*
- * If isset_offset is provided, store whether the reloption is
- * set there.
- */
- if (elems[j].isset_offset > 0)
- {
- char *setpos = ((char *) rdopts) + elems[j].isset_offset;
-
- *(bool *) setpos = options[i].isset;
- }
-
switch (options[i].gen->type)
{
case RELOPT_TYPE_BOOL:
@@ -1798,6 +1871,11 @@ fillRelOptions(void *rdopts, Size basesize,
options[i].values.bool_val :
((relopt_bool *) options[i].gen)->default_val;
break;
+ case RELOPT_TYPE_TERNARY:
+ *(ternary *) itempos = options[i].isset ?
+ options[i].values.ternary_val :
+ ((relopt_ternary *) options[i].gen)->default_val;
+ break;
case RELOPT_TYPE_INT:
*(int *) itempos = options[i].isset ?
options[i].values.int_val :
@@ -1912,8 +1990,8 @@ default_reloptions(Datum reloptions, bool validate, relopt_kind kind)
offsetof(StdRdOptions, parallel_workers)},
{"vacuum_index_cleanup", RELOPT_TYPE_ENUM,
offsetof(StdRdOptions, vacuum_index_cleanup)},
- {"vacuum_truncate", RELOPT_TYPE_BOOL,
- offsetof(StdRdOptions, vacuum_truncate), offsetof(StdRdOptions, vacuum_truncate_set)},
+ {"vacuum_truncate", RELOPT_TYPE_TERNARY,
+ offsetof(StdRdOptions, vacuum_truncate)},
{"vacuum_max_eager_freeze_failure_rate", RELOPT_TYPE_REAL,
offsetof(StdRdOptions, vacuum_max_eager_freeze_failure_rate)}
};
@@ -1993,7 +2071,6 @@ build_local_reloptions(local_relopts *relopts, Datum options, bool validate)
elems[i].optname = opt->option->name;
elems[i].opttype = opt->option->type;
elems[i].offset = opt->offset;
- elems[i].isset_offset = 0; /* not supported for local relopts yet */
i++;
}
diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c
index 733ef40ae7..977babff54 100644
--- a/src/backend/commands/vacuum.c
+++ b/src/backend/commands/vacuum.c
@@ -2221,9 +2221,9 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams params,
{
StdRdOptions *opts = (StdRdOptions *) rel->rd_options;
- if (opts && opts->vacuum_truncate_set)
+ if (opts && opts->vacuum_truncate != TERNARY_UNSET)
{
- if (opts->vacuum_truncate)
+ if (opts->vacuum_truncate == TERNARY_TRUE)
params.truncate = VACOPTVALUE_ENABLED;
else
params.truncate = VACOPTVALUE_DISABLED;
diff --git a/src/include/access/reloptions.h b/src/include/access/reloptions.h
index a604a4702c..a436697658 100644
--- a/src/include/access/reloptions.h
+++ b/src/include/access/reloptions.h
@@ -29,6 +29,7 @@
typedef enum relopt_type
{
RELOPT_TYPE_BOOL,
+ RELOPT_TYPE_TERNARY, /* on, off, unset */
RELOPT_TYPE_INT,
RELOPT_TYPE_REAL,
RELOPT_TYPE_ENUM,
@@ -80,6 +81,7 @@ typedef struct relopt_value
union
{
bool bool_val;
+ ternary ternary_val;
int int_val;
double real_val;
int enum_val;
@@ -94,6 +96,12 @@ typedef struct relopt_bool
bool default_val;
} relopt_bool;
+typedef struct relopt_rernary
+{
+ relopt_gen gen;
+ int default_val;
+} relopt_ternary;
+
typedef struct relopt_int
{
relopt_gen gen;
@@ -152,19 +160,6 @@ typedef struct
const char *optname; /* option's name */
relopt_type opttype; /* option's datatype */
int offset; /* offset of field in result struct */
-
- /*
- * isset_offset is an optional offset of a field in the result struct that
- * stores whether the option is explicitly set for the relation or if it
- * just picked up the default value. In most cases, this can be
- * accomplished by giving the reloption a special out-of-range default
- * value (e.g., some integer reloptions use -2), but this isn't always
- * possible. For example, a Boolean reloption cannot be given an
- * out-of-range default, so we need another way to discover the source of
- * its value. This offset is only used if given a value greater than
- * zero.
- */
- int isset_offset;
} relopt_parse_elt;
/* Local reloption definition */
@@ -195,6 +190,8 @@ typedef struct local_relopts
extern relopt_kind add_reloption_kind(void);
extern void add_bool_reloption(bits32 kinds, const char *name, const char *desc,
bool default_val, LOCKMODE lockmode);
+extern void add_ternary_reloption(bits32 kinds, const char *name,
+ const char *desc, int default_val, LOCKMODE lockmode);
extern void add_int_reloption(bits32 kinds, const char *name, const char *desc,
int default_val, int min_val, int max_val,
LOCKMODE lockmode);
@@ -214,6 +211,9 @@ extern void register_reloptions_validator(local_relopts *relopts,
extern void add_local_bool_reloption(local_relopts *relopts, const char *name,
const char *desc, bool default_val,
int offset);
+extern void add_local_ternary_reloption(local_relopts *relopts,
+ const char *name, const char *desc,
+ ternary default_val, int offset);
extern void add_local_int_reloption(local_relopts *relopts, const char *name,
const char *desc, int default_val,
int min_val, int max_val, int offset);
diff --git a/src/include/c.h b/src/include/c.h
index 39022f8a9d..9c59ccccf8 100644
--- a/src/include/c.h
+++ b/src/include/c.h
@@ -488,6 +488,22 @@ typedef void (*pg_funcptr_t) (void);
#include <stdbool.h>
+/*
+ * ternary
+ * Boolean value with an extrea "unset" option
+ *
+ * Ternary data type is used in relation options that can be "true", "false" or
+ * "unset". Since relation options are used deep inside the PostgreSQL code,
+ * this type is declared globally.
+*/
+
+typedef enum ternary
+{
+ TERNARY_FALSE = 0,
+ TERNARY_TRUE = 1,
+ TERNARY_UNSET = -1
+} ternary;
+
/* ----------------------------------------------------------------
* Section 3: standard system types
diff --git a/src/include/utils/rel.h b/src/include/utils/rel.h
index b552359915..7346d618f9 100644
--- a/src/include/utils/rel.h
+++ b/src/include/utils/rel.h
@@ -346,8 +346,7 @@ typedef struct StdRdOptions
bool user_catalog_table; /* use as an additional catalog relation */
int parallel_workers; /* max number of parallel workers */
StdRdOptIndexCleanup vacuum_index_cleanup; /* controls index vacuuming */
- bool vacuum_truncate; /* enables vacuum to truncate a relation */
- bool vacuum_truncate_set; /* whether vacuum_truncate is set */
+ ternary vacuum_truncate; /* enables vacuum to truncate a relation */
/*
* Fraction of pages in a relation that vacuum can eagerly scan and fail
--
2.39.2
--nextPart5129932.5fSG56mABF
Content-Disposition: attachment;
filename="v2-0003-Add-alias-to-be-used-as-unset-state.patch"
Content-Transfer-Encoding: 7Bit
Content-Type: text/x-patch; charset="unicode-2-0-utf-8";
name="v2-0003-Add-alias-to-be-used-as-unset-state.patch"
^ permalink raw reply [nested|flat] 868+ messages in thread
* [PATCH v2a 2/4] Introduce ternary reloptions
@ 2025-09-04 14:41 Nikolay Shaplov <[email protected]>
0 siblings, 0 replies; 868+ messages in thread
From: Nikolay Shaplov @ 2025-09-04 14:41 UTC (permalink / raw)
Introduce ternary reloption as a replacement for current `vacuum_truncate`
implementation. Remove `vacuum_truncate_set` additional flag and using
`TERNARY_UNSET` value instead.
---
src/backend/access/common/reloptions.c | 129 ++++++++++++++++++++-----
src/backend/commands/vacuum.c | 4 +-
src/include/access/reloptions.h | 26 ++---
src/include/c.h | 16 +++
src/include/utils/rel.h | 3 +-
5 files changed, 135 insertions(+), 43 deletions(-)
diff --git a/src/backend/access/common/reloptions.c b/src/backend/access/common/reloptions.c
index 0af3fea68f..f543f11001 100644
--- a/src/backend/access/common/reloptions.c
+++ b/src/backend/access/common/reloptions.c
@@ -40,9 +40,9 @@
*
* To add an option:
*
- * (i) decide on a type (bool, integer, real, enum, string), name, default
- * value, upper and lower bounds (if applicable); for strings, consider a
- * validation routine.
+ * (i) decide on a type (bool, ternary, integer, real, enum, string), name,
+ * default value, upper and lower bounds (if applicable); for strings,
+ * consider a validation routine.
* (ii) add a record below (or use add_<type>_reloption).
* (iii) add it to the appropriate options struct (perhaps StdRdOptions)
* (iv) add it to the appropriate handling routine (perhaps
@@ -147,15 +147,6 @@ static relopt_bool boolRelOpts[] =
},
false
},
- {
- {
- "vacuum_truncate",
- "Enables vacuum to truncate empty pages at the end of this table",
- RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
- ShareUpdateExclusiveLock
- },
- true
- },
{
{
"deduplicate_items",
@@ -170,6 +161,21 @@ static relopt_bool boolRelOpts[] =
{{NULL}}
};
+static relopt_ternary ternaryRelOpts[] =
+{
+ {
+ {
+ "vacuum_truncate",
+ "Enables vacuum to truncate empty pages at the end of this table",
+ RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
+ ShareUpdateExclusiveLock
+ },
+ TERNARY_UNSET
+ },
+ /* list terminator */
+ {{NULL}}
+};
+
static relopt_int intRelOpts[] =
{
{
@@ -600,6 +606,13 @@ initialize_reloptions(void)
boolRelOpts[i].gen.lockmode));
j++;
}
+ for (i = 0; ternaryRelOpts[i].gen.name; i++)
+ {
+ Assert(DoLockModesConflict(ternaryRelOpts[i].gen.lockmode,
+ ternaryRelOpts[i].gen.lockmode));
+ j++;
+ }
+
for (i = 0; intRelOpts[i].gen.name; i++)
{
Assert(DoLockModesConflict(intRelOpts[i].gen.lockmode,
@@ -640,6 +653,14 @@ initialize_reloptions(void)
j++;
}
+ for (i = 0; ternaryRelOpts[i].gen.name; i++)
+ {
+ relOpts[j] = &ternaryRelOpts[i].gen;
+ relOpts[j]->type = RELOPT_TYPE_TERNARY;
+ relOpts[j]->namelen = strlen(relOpts[j]->name);
+ j++;
+ }
+
for (i = 0; intRelOpts[i].gen.name; i++)
{
relOpts[j] = &intRelOpts[i].gen;
@@ -800,6 +821,9 @@ allocate_reloption(bits32 kinds, int type, const char *name, const char *desc,
case RELOPT_TYPE_BOOL:
size = sizeof(relopt_bool);
break;
+ case RELOPT_TYPE_TERNARY:
+ size = sizeof(relopt_ternary);
+ break;
case RELOPT_TYPE_INT:
size = sizeof(relopt_int);
break;
@@ -883,6 +907,54 @@ add_local_bool_reloption(local_relopts *relopts, const char *name,
add_local_reloption(relopts, (relopt_gen *) newoption, offset);
}
+/*
+ * init_ternary_reloption
+ * Allocate and initialize a new ternary reloption
+ */
+static relopt_ternary *
+init_ternary_reloption(bits32 kinds, const char *name, const char *desc,
+ ternary default_val, LOCKMODE lockmode)
+{
+ relopt_ternary *newoption;
+
+ newoption = (relopt_ternary *) allocate_reloption(kinds,
+ RELOPT_TYPE_TERNARY, name, desc, lockmode);
+ newoption->default_val = default_val;
+
+ return newoption;
+}
+
+/*
+ * add_ternary_reloption
+ * Add a new ternary reloption
+ */
+void
+add_ternary_reloption(bits32 kinds, const char *name, const char *desc,
+ ternary default_val, LOCKMODE lockmode)
+{
+ relopt_ternary *newoption = init_ternary_reloption(kinds, name, desc,
+ default_val, lockmode);
+
+ add_reloption((relopt_gen *) newoption);
+}
+
+/*
+ * add_local_ternary_reloption
+ * Add a new ternary local reloption
+ *
+ * 'offset' is offset of ternary-typed field.
+ */
+void
+add_local_ternary_reloption(local_relopts *relopts, const char *name,
+ const char *desc, ternary default_val,
+ int offset)
+{
+ relopt_ternary *newoption = init_ternary_reloption(RELOPT_KIND_LOCAL,
+ name, desc,
+ default_val, 0);
+
+ add_local_reloption(relopts, (relopt_gen *) newoption, offset);
+}
/*
* init_real_reloption
@@ -1617,6 +1689,18 @@ parse_one_reloption(relopt_value *option, char *text_str, int text_len,
option->gen->name, value)));
}
break;
+ case RELOPT_TYPE_TERNARY:
+ {
+ bool b;
+ parsed = parse_bool(value, &b);
+ option->values.ternary_val = b ? TERNARY_TRUE : TERNARY_FALSE;
+ if (validate && !parsed)
+ ereport(ERROR,
+ (errcode(ERRCODE_INVALID_PARAMETER_VALUE),
+ errmsg("invalid value for ternary option \"%s\": %s",
+ option->gen->name, value)));
+ }
+ break;
case RELOPT_TYPE_INT:
{
relopt_int *optint = (relopt_int *) option->gen;
@@ -1780,17 +1864,6 @@ fillRelOptions(void *rdopts, Size basesize,
char *itempos = ((char *) rdopts) + elems[j].offset;
char *string_val;
- /*
- * If isset_offset is provided, store whether the reloption is
- * set there.
- */
- if (elems[j].isset_offset > 0)
- {
- char *setpos = ((char *) rdopts) + elems[j].isset_offset;
-
- *(bool *) setpos = options[i].isset;
- }
-
switch (options[i].gen->type)
{
case RELOPT_TYPE_BOOL:
@@ -1798,6 +1871,11 @@ fillRelOptions(void *rdopts, Size basesize,
options[i].values.bool_val :
((relopt_bool *) options[i].gen)->default_val;
break;
+ case RELOPT_TYPE_TERNARY:
+ *(ternary *) itempos = options[i].isset ?
+ options[i].values.ternary_val :
+ ((relopt_ternary *) options[i].gen)->default_val;
+ break;
case RELOPT_TYPE_INT:
*(int *) itempos = options[i].isset ?
options[i].values.int_val :
@@ -1912,8 +1990,8 @@ default_reloptions(Datum reloptions, bool validate, relopt_kind kind)
offsetof(StdRdOptions, parallel_workers)},
{"vacuum_index_cleanup", RELOPT_TYPE_ENUM,
offsetof(StdRdOptions, vacuum_index_cleanup)},
- {"vacuum_truncate", RELOPT_TYPE_BOOL,
- offsetof(StdRdOptions, vacuum_truncate), offsetof(StdRdOptions, vacuum_truncate_set)},
+ {"vacuum_truncate", RELOPT_TYPE_TERNARY,
+ offsetof(StdRdOptions, vacuum_truncate)},
{"vacuum_max_eager_freeze_failure_rate", RELOPT_TYPE_REAL,
offsetof(StdRdOptions, vacuum_max_eager_freeze_failure_rate)}
};
@@ -1993,7 +2071,6 @@ build_local_reloptions(local_relopts *relopts, Datum options, bool validate)
elems[i].optname = opt->option->name;
elems[i].opttype = opt->option->type;
elems[i].offset = opt->offset;
- elems[i].isset_offset = 0; /* not supported for local relopts yet */
i++;
}
diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c
index 733ef40ae7..977babff54 100644
--- a/src/backend/commands/vacuum.c
+++ b/src/backend/commands/vacuum.c
@@ -2221,9 +2221,9 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams params,
{
StdRdOptions *opts = (StdRdOptions *) rel->rd_options;
- if (opts && opts->vacuum_truncate_set)
+ if (opts && opts->vacuum_truncate != TERNARY_UNSET)
{
- if (opts->vacuum_truncate)
+ if (opts->vacuum_truncate == TERNARY_TRUE)
params.truncate = VACOPTVALUE_ENABLED;
else
params.truncate = VACOPTVALUE_DISABLED;
diff --git a/src/include/access/reloptions.h b/src/include/access/reloptions.h
index a604a4702c..a436697658 100644
--- a/src/include/access/reloptions.h
+++ b/src/include/access/reloptions.h
@@ -29,6 +29,7 @@
typedef enum relopt_type
{
RELOPT_TYPE_BOOL,
+ RELOPT_TYPE_TERNARY, /* on, off, unset */
RELOPT_TYPE_INT,
RELOPT_TYPE_REAL,
RELOPT_TYPE_ENUM,
@@ -80,6 +81,7 @@ typedef struct relopt_value
union
{
bool bool_val;
+ ternary ternary_val;
int int_val;
double real_val;
int enum_val;
@@ -94,6 +96,12 @@ typedef struct relopt_bool
bool default_val;
} relopt_bool;
+typedef struct relopt_rernary
+{
+ relopt_gen gen;
+ int default_val;
+} relopt_ternary;
+
typedef struct relopt_int
{
relopt_gen gen;
@@ -152,19 +160,6 @@ typedef struct
const char *optname; /* option's name */
relopt_type opttype; /* option's datatype */
int offset; /* offset of field in result struct */
-
- /*
- * isset_offset is an optional offset of a field in the result struct that
- * stores whether the option is explicitly set for the relation or if it
- * just picked up the default value. In most cases, this can be
- * accomplished by giving the reloption a special out-of-range default
- * value (e.g., some integer reloptions use -2), but this isn't always
- * possible. For example, a Boolean reloption cannot be given an
- * out-of-range default, so we need another way to discover the source of
- * its value. This offset is only used if given a value greater than
- * zero.
- */
- int isset_offset;
} relopt_parse_elt;
/* Local reloption definition */
@@ -195,6 +190,8 @@ typedef struct local_relopts
extern relopt_kind add_reloption_kind(void);
extern void add_bool_reloption(bits32 kinds, const char *name, const char *desc,
bool default_val, LOCKMODE lockmode);
+extern void add_ternary_reloption(bits32 kinds, const char *name,
+ const char *desc, int default_val, LOCKMODE lockmode);
extern void add_int_reloption(bits32 kinds, const char *name, const char *desc,
int default_val, int min_val, int max_val,
LOCKMODE lockmode);
@@ -214,6 +211,9 @@ extern void register_reloptions_validator(local_relopts *relopts,
extern void add_local_bool_reloption(local_relopts *relopts, const char *name,
const char *desc, bool default_val,
int offset);
+extern void add_local_ternary_reloption(local_relopts *relopts,
+ const char *name, const char *desc,
+ ternary default_val, int offset);
extern void add_local_int_reloption(local_relopts *relopts, const char *name,
const char *desc, int default_val,
int min_val, int max_val, int offset);
diff --git a/src/include/c.h b/src/include/c.h
index 39022f8a9d..9c59ccccf8 100644
--- a/src/include/c.h
+++ b/src/include/c.h
@@ -488,6 +488,22 @@ typedef void (*pg_funcptr_t) (void);
#include <stdbool.h>
+/*
+ * ternary
+ * Boolean value with an extrea "unset" option
+ *
+ * Ternary data type is used in relation options that can be "true", "false" or
+ * "unset". Since relation options are used deep inside the PostgreSQL code,
+ * this type is declared globally.
+*/
+
+typedef enum ternary
+{
+ TERNARY_FALSE = 0,
+ TERNARY_TRUE = 1,
+ TERNARY_UNSET = -1
+} ternary;
+
/* ----------------------------------------------------------------
* Section 3: standard system types
diff --git a/src/include/utils/rel.h b/src/include/utils/rel.h
index b552359915..7346d618f9 100644
--- a/src/include/utils/rel.h
+++ b/src/include/utils/rel.h
@@ -346,8 +346,7 @@ typedef struct StdRdOptions
bool user_catalog_table; /* use as an additional catalog relation */
int parallel_workers; /* max number of parallel workers */
StdRdOptIndexCleanup vacuum_index_cleanup; /* controls index vacuuming */
- bool vacuum_truncate; /* enables vacuum to truncate a relation */
- bool vacuum_truncate_set; /* whether vacuum_truncate is set */
+ ternary vacuum_truncate; /* enables vacuum to truncate a relation */
/*
* Fraction of pages in a relation that vacuum can eagerly scan and fail
--
2.39.2
--nextPart4155600.3ZeAukHxDK
Content-Disposition: attachment;
filename="v2a-0003-Add-alias-to-be-used-as-unset-state.patch"
Content-Transfer-Encoding: 7Bit
Content-Type: text/x-patch; charset="unicode-2-0-utf-8";
name="v2a-0003-Add-alias-to-be-used-as-unset-state.patch"
^ permalink raw reply [nested|flat] 868+ messages in thread
* [PATCH v2a 2/4] Introduce ternary reloptions
@ 2025-09-04 14:41 Nikolay Shaplov <[email protected]>
0 siblings, 0 replies; 868+ messages in thread
From: Nikolay Shaplov @ 2025-09-04 14:41 UTC (permalink / raw)
Introduce ternary reloption as a replacement for current `vacuum_truncate`
implementation. Remove `vacuum_truncate_set` additional flag and using
`TERNARY_UNSET` value instead.
---
src/backend/access/common/reloptions.c | 129 ++++++++++++++++++++-----
src/backend/commands/vacuum.c | 4 +-
src/include/access/reloptions.h | 26 ++---
src/include/c.h | 16 +++
src/include/utils/rel.h | 3 +-
5 files changed, 135 insertions(+), 43 deletions(-)
diff --git a/src/backend/access/common/reloptions.c b/src/backend/access/common/reloptions.c
index 0af3fea68f..f543f11001 100644
--- a/src/backend/access/common/reloptions.c
+++ b/src/backend/access/common/reloptions.c
@@ -40,9 +40,9 @@
*
* To add an option:
*
- * (i) decide on a type (bool, integer, real, enum, string), name, default
- * value, upper and lower bounds (if applicable); for strings, consider a
- * validation routine.
+ * (i) decide on a type (bool, ternary, integer, real, enum, string), name,
+ * default value, upper and lower bounds (if applicable); for strings,
+ * consider a validation routine.
* (ii) add a record below (or use add_<type>_reloption).
* (iii) add it to the appropriate options struct (perhaps StdRdOptions)
* (iv) add it to the appropriate handling routine (perhaps
@@ -147,15 +147,6 @@ static relopt_bool boolRelOpts[] =
},
false
},
- {
- {
- "vacuum_truncate",
- "Enables vacuum to truncate empty pages at the end of this table",
- RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
- ShareUpdateExclusiveLock
- },
- true
- },
{
{
"deduplicate_items",
@@ -170,6 +161,21 @@ static relopt_bool boolRelOpts[] =
{{NULL}}
};
+static relopt_ternary ternaryRelOpts[] =
+{
+ {
+ {
+ "vacuum_truncate",
+ "Enables vacuum to truncate empty pages at the end of this table",
+ RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
+ ShareUpdateExclusiveLock
+ },
+ TERNARY_UNSET
+ },
+ /* list terminator */
+ {{NULL}}
+};
+
static relopt_int intRelOpts[] =
{
{
@@ -600,6 +606,13 @@ initialize_reloptions(void)
boolRelOpts[i].gen.lockmode));
j++;
}
+ for (i = 0; ternaryRelOpts[i].gen.name; i++)
+ {
+ Assert(DoLockModesConflict(ternaryRelOpts[i].gen.lockmode,
+ ternaryRelOpts[i].gen.lockmode));
+ j++;
+ }
+
for (i = 0; intRelOpts[i].gen.name; i++)
{
Assert(DoLockModesConflict(intRelOpts[i].gen.lockmode,
@@ -640,6 +653,14 @@ initialize_reloptions(void)
j++;
}
+ for (i = 0; ternaryRelOpts[i].gen.name; i++)
+ {
+ relOpts[j] = &ternaryRelOpts[i].gen;
+ relOpts[j]->type = RELOPT_TYPE_TERNARY;
+ relOpts[j]->namelen = strlen(relOpts[j]->name);
+ j++;
+ }
+
for (i = 0; intRelOpts[i].gen.name; i++)
{
relOpts[j] = &intRelOpts[i].gen;
@@ -800,6 +821,9 @@ allocate_reloption(bits32 kinds, int type, const char *name, const char *desc,
case RELOPT_TYPE_BOOL:
size = sizeof(relopt_bool);
break;
+ case RELOPT_TYPE_TERNARY:
+ size = sizeof(relopt_ternary);
+ break;
case RELOPT_TYPE_INT:
size = sizeof(relopt_int);
break;
@@ -883,6 +907,54 @@ add_local_bool_reloption(local_relopts *relopts, const char *name,
add_local_reloption(relopts, (relopt_gen *) newoption, offset);
}
+/*
+ * init_ternary_reloption
+ * Allocate and initialize a new ternary reloption
+ */
+static relopt_ternary *
+init_ternary_reloption(bits32 kinds, const char *name, const char *desc,
+ ternary default_val, LOCKMODE lockmode)
+{
+ relopt_ternary *newoption;
+
+ newoption = (relopt_ternary *) allocate_reloption(kinds,
+ RELOPT_TYPE_TERNARY, name, desc, lockmode);
+ newoption->default_val = default_val;
+
+ return newoption;
+}
+
+/*
+ * add_ternary_reloption
+ * Add a new ternary reloption
+ */
+void
+add_ternary_reloption(bits32 kinds, const char *name, const char *desc,
+ ternary default_val, LOCKMODE lockmode)
+{
+ relopt_ternary *newoption = init_ternary_reloption(kinds, name, desc,
+ default_val, lockmode);
+
+ add_reloption((relopt_gen *) newoption);
+}
+
+/*
+ * add_local_ternary_reloption
+ * Add a new ternary local reloption
+ *
+ * 'offset' is offset of ternary-typed field.
+ */
+void
+add_local_ternary_reloption(local_relopts *relopts, const char *name,
+ const char *desc, ternary default_val,
+ int offset)
+{
+ relopt_ternary *newoption = init_ternary_reloption(RELOPT_KIND_LOCAL,
+ name, desc,
+ default_val, 0);
+
+ add_local_reloption(relopts, (relopt_gen *) newoption, offset);
+}
/*
* init_real_reloption
@@ -1617,6 +1689,18 @@ parse_one_reloption(relopt_value *option, char *text_str, int text_len,
option->gen->name, value)));
}
break;
+ case RELOPT_TYPE_TERNARY:
+ {
+ bool b;
+ parsed = parse_bool(value, &b);
+ option->values.ternary_val = b ? TERNARY_TRUE : TERNARY_FALSE;
+ if (validate && !parsed)
+ ereport(ERROR,
+ (errcode(ERRCODE_INVALID_PARAMETER_VALUE),
+ errmsg("invalid value for ternary option \"%s\": %s",
+ option->gen->name, value)));
+ }
+ break;
case RELOPT_TYPE_INT:
{
relopt_int *optint = (relopt_int *) option->gen;
@@ -1780,17 +1864,6 @@ fillRelOptions(void *rdopts, Size basesize,
char *itempos = ((char *) rdopts) + elems[j].offset;
char *string_val;
- /*
- * If isset_offset is provided, store whether the reloption is
- * set there.
- */
- if (elems[j].isset_offset > 0)
- {
- char *setpos = ((char *) rdopts) + elems[j].isset_offset;
-
- *(bool *) setpos = options[i].isset;
- }
-
switch (options[i].gen->type)
{
case RELOPT_TYPE_BOOL:
@@ -1798,6 +1871,11 @@ fillRelOptions(void *rdopts, Size basesize,
options[i].values.bool_val :
((relopt_bool *) options[i].gen)->default_val;
break;
+ case RELOPT_TYPE_TERNARY:
+ *(ternary *) itempos = options[i].isset ?
+ options[i].values.ternary_val :
+ ((relopt_ternary *) options[i].gen)->default_val;
+ break;
case RELOPT_TYPE_INT:
*(int *) itempos = options[i].isset ?
options[i].values.int_val :
@@ -1912,8 +1990,8 @@ default_reloptions(Datum reloptions, bool validate, relopt_kind kind)
offsetof(StdRdOptions, parallel_workers)},
{"vacuum_index_cleanup", RELOPT_TYPE_ENUM,
offsetof(StdRdOptions, vacuum_index_cleanup)},
- {"vacuum_truncate", RELOPT_TYPE_BOOL,
- offsetof(StdRdOptions, vacuum_truncate), offsetof(StdRdOptions, vacuum_truncate_set)},
+ {"vacuum_truncate", RELOPT_TYPE_TERNARY,
+ offsetof(StdRdOptions, vacuum_truncate)},
{"vacuum_max_eager_freeze_failure_rate", RELOPT_TYPE_REAL,
offsetof(StdRdOptions, vacuum_max_eager_freeze_failure_rate)}
};
@@ -1993,7 +2071,6 @@ build_local_reloptions(local_relopts *relopts, Datum options, bool validate)
elems[i].optname = opt->option->name;
elems[i].opttype = opt->option->type;
elems[i].offset = opt->offset;
- elems[i].isset_offset = 0; /* not supported for local relopts yet */
i++;
}
diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c
index 733ef40ae7..977babff54 100644
--- a/src/backend/commands/vacuum.c
+++ b/src/backend/commands/vacuum.c
@@ -2221,9 +2221,9 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams params,
{
StdRdOptions *opts = (StdRdOptions *) rel->rd_options;
- if (opts && opts->vacuum_truncate_set)
+ if (opts && opts->vacuum_truncate != TERNARY_UNSET)
{
- if (opts->vacuum_truncate)
+ if (opts->vacuum_truncate == TERNARY_TRUE)
params.truncate = VACOPTVALUE_ENABLED;
else
params.truncate = VACOPTVALUE_DISABLED;
diff --git a/src/include/access/reloptions.h b/src/include/access/reloptions.h
index a604a4702c..a436697658 100644
--- a/src/include/access/reloptions.h
+++ b/src/include/access/reloptions.h
@@ -29,6 +29,7 @@
typedef enum relopt_type
{
RELOPT_TYPE_BOOL,
+ RELOPT_TYPE_TERNARY, /* on, off, unset */
RELOPT_TYPE_INT,
RELOPT_TYPE_REAL,
RELOPT_TYPE_ENUM,
@@ -80,6 +81,7 @@ typedef struct relopt_value
union
{
bool bool_val;
+ ternary ternary_val;
int int_val;
double real_val;
int enum_val;
@@ -94,6 +96,12 @@ typedef struct relopt_bool
bool default_val;
} relopt_bool;
+typedef struct relopt_rernary
+{
+ relopt_gen gen;
+ int default_val;
+} relopt_ternary;
+
typedef struct relopt_int
{
relopt_gen gen;
@@ -152,19 +160,6 @@ typedef struct
const char *optname; /* option's name */
relopt_type opttype; /* option's datatype */
int offset; /* offset of field in result struct */
-
- /*
- * isset_offset is an optional offset of a field in the result struct that
- * stores whether the option is explicitly set for the relation or if it
- * just picked up the default value. In most cases, this can be
- * accomplished by giving the reloption a special out-of-range default
- * value (e.g., some integer reloptions use -2), but this isn't always
- * possible. For example, a Boolean reloption cannot be given an
- * out-of-range default, so we need another way to discover the source of
- * its value. This offset is only used if given a value greater than
- * zero.
- */
- int isset_offset;
} relopt_parse_elt;
/* Local reloption definition */
@@ -195,6 +190,8 @@ typedef struct local_relopts
extern relopt_kind add_reloption_kind(void);
extern void add_bool_reloption(bits32 kinds, const char *name, const char *desc,
bool default_val, LOCKMODE lockmode);
+extern void add_ternary_reloption(bits32 kinds, const char *name,
+ const char *desc, int default_val, LOCKMODE lockmode);
extern void add_int_reloption(bits32 kinds, const char *name, const char *desc,
int default_val, int min_val, int max_val,
LOCKMODE lockmode);
@@ -214,6 +211,9 @@ extern void register_reloptions_validator(local_relopts *relopts,
extern void add_local_bool_reloption(local_relopts *relopts, const char *name,
const char *desc, bool default_val,
int offset);
+extern void add_local_ternary_reloption(local_relopts *relopts,
+ const char *name, const char *desc,
+ ternary default_val, int offset);
extern void add_local_int_reloption(local_relopts *relopts, const char *name,
const char *desc, int default_val,
int min_val, int max_val, int offset);
diff --git a/src/include/c.h b/src/include/c.h
index 39022f8a9d..9c59ccccf8 100644
--- a/src/include/c.h
+++ b/src/include/c.h
@@ -488,6 +488,22 @@ typedef void (*pg_funcptr_t) (void);
#include <stdbool.h>
+/*
+ * ternary
+ * Boolean value with an extrea "unset" option
+ *
+ * Ternary data type is used in relation options that can be "true", "false" or
+ * "unset". Since relation options are used deep inside the PostgreSQL code,
+ * this type is declared globally.
+*/
+
+typedef enum ternary
+{
+ TERNARY_FALSE = 0,
+ TERNARY_TRUE = 1,
+ TERNARY_UNSET = -1
+} ternary;
+
/* ----------------------------------------------------------------
* Section 3: standard system types
diff --git a/src/include/utils/rel.h b/src/include/utils/rel.h
index b552359915..7346d618f9 100644
--- a/src/include/utils/rel.h
+++ b/src/include/utils/rel.h
@@ -346,8 +346,7 @@ typedef struct StdRdOptions
bool user_catalog_table; /* use as an additional catalog relation */
int parallel_workers; /* max number of parallel workers */
StdRdOptIndexCleanup vacuum_index_cleanup; /* controls index vacuuming */
- bool vacuum_truncate; /* enables vacuum to truncate a relation */
- bool vacuum_truncate_set; /* whether vacuum_truncate is set */
+ ternary vacuum_truncate; /* enables vacuum to truncate a relation */
/*
* Fraction of pages in a relation that vacuum can eagerly scan and fail
--
2.39.2
--nextPart4155600.3ZeAukHxDK
Content-Disposition: attachment;
filename="v2a-0003-Add-alias-to-be-used-as-unset-state.patch"
Content-Transfer-Encoding: 7Bit
Content-Type: text/x-patch; charset="unicode-2-0-utf-8";
name="v2a-0003-Add-alias-to-be-used-as-unset-state.patch"
^ permalink raw reply [nested|flat] 868+ messages in thread
* [PATCH v2 2/4] Introduce ternary reloptions
@ 2025-09-04 14:41 Nikolay Shaplov <[email protected]>
0 siblings, 0 replies; 868+ messages in thread
From: Nikolay Shaplov @ 2025-09-04 14:41 UTC (permalink / raw)
Introduce ternary reloption as a replacement for current `vacuum_truncate`
implementation. Remove `vacuum_truncate_set` additional flag and using
`TERNARY_UNSET` value instead.
---
src/backend/access/common/reloptions.c | 129 ++++++++++++++++++++-----
src/backend/commands/vacuum.c | 4 +-
src/include/access/reloptions.h | 26 ++---
src/include/c.h | 16 +++
src/include/utils/rel.h | 3 +-
5 files changed, 135 insertions(+), 43 deletions(-)
diff --git a/src/backend/access/common/reloptions.c b/src/backend/access/common/reloptions.c
index 0af3fea68f..f543f11001 100644
--- a/src/backend/access/common/reloptions.c
+++ b/src/backend/access/common/reloptions.c
@@ -40,9 +40,9 @@
*
* To add an option:
*
- * (i) decide on a type (bool, integer, real, enum, string), name, default
- * value, upper and lower bounds (if applicable); for strings, consider a
- * validation routine.
+ * (i) decide on a type (bool, ternary, integer, real, enum, string), name,
+ * default value, upper and lower bounds (if applicable); for strings,
+ * consider a validation routine.
* (ii) add a record below (or use add_<type>_reloption).
* (iii) add it to the appropriate options struct (perhaps StdRdOptions)
* (iv) add it to the appropriate handling routine (perhaps
@@ -147,15 +147,6 @@ static relopt_bool boolRelOpts[] =
},
false
},
- {
- {
- "vacuum_truncate",
- "Enables vacuum to truncate empty pages at the end of this table",
- RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
- ShareUpdateExclusiveLock
- },
- true
- },
{
{
"deduplicate_items",
@@ -170,6 +161,21 @@ static relopt_bool boolRelOpts[] =
{{NULL}}
};
+static relopt_ternary ternaryRelOpts[] =
+{
+ {
+ {
+ "vacuum_truncate",
+ "Enables vacuum to truncate empty pages at the end of this table",
+ RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
+ ShareUpdateExclusiveLock
+ },
+ TERNARY_UNSET
+ },
+ /* list terminator */
+ {{NULL}}
+};
+
static relopt_int intRelOpts[] =
{
{
@@ -600,6 +606,13 @@ initialize_reloptions(void)
boolRelOpts[i].gen.lockmode));
j++;
}
+ for (i = 0; ternaryRelOpts[i].gen.name; i++)
+ {
+ Assert(DoLockModesConflict(ternaryRelOpts[i].gen.lockmode,
+ ternaryRelOpts[i].gen.lockmode));
+ j++;
+ }
+
for (i = 0; intRelOpts[i].gen.name; i++)
{
Assert(DoLockModesConflict(intRelOpts[i].gen.lockmode,
@@ -640,6 +653,14 @@ initialize_reloptions(void)
j++;
}
+ for (i = 0; ternaryRelOpts[i].gen.name; i++)
+ {
+ relOpts[j] = &ternaryRelOpts[i].gen;
+ relOpts[j]->type = RELOPT_TYPE_TERNARY;
+ relOpts[j]->namelen = strlen(relOpts[j]->name);
+ j++;
+ }
+
for (i = 0; intRelOpts[i].gen.name; i++)
{
relOpts[j] = &intRelOpts[i].gen;
@@ -800,6 +821,9 @@ allocate_reloption(bits32 kinds, int type, const char *name, const char *desc,
case RELOPT_TYPE_BOOL:
size = sizeof(relopt_bool);
break;
+ case RELOPT_TYPE_TERNARY:
+ size = sizeof(relopt_ternary);
+ break;
case RELOPT_TYPE_INT:
size = sizeof(relopt_int);
break;
@@ -883,6 +907,54 @@ add_local_bool_reloption(local_relopts *relopts, const char *name,
add_local_reloption(relopts, (relopt_gen *) newoption, offset);
}
+/*
+ * init_ternary_reloption
+ * Allocate and initialize a new ternary reloption
+ */
+static relopt_ternary *
+init_ternary_reloption(bits32 kinds, const char *name, const char *desc,
+ ternary default_val, LOCKMODE lockmode)
+{
+ relopt_ternary *newoption;
+
+ newoption = (relopt_ternary *) allocate_reloption(kinds,
+ RELOPT_TYPE_TERNARY, name, desc, lockmode);
+ newoption->default_val = default_val;
+
+ return newoption;
+}
+
+/*
+ * add_ternary_reloption
+ * Add a new ternary reloption
+ */
+void
+add_ternary_reloption(bits32 kinds, const char *name, const char *desc,
+ ternary default_val, LOCKMODE lockmode)
+{
+ relopt_ternary *newoption = init_ternary_reloption(kinds, name, desc,
+ default_val, lockmode);
+
+ add_reloption((relopt_gen *) newoption);
+}
+
+/*
+ * add_local_ternary_reloption
+ * Add a new ternary local reloption
+ *
+ * 'offset' is offset of ternary-typed field.
+ */
+void
+add_local_ternary_reloption(local_relopts *relopts, const char *name,
+ const char *desc, ternary default_val,
+ int offset)
+{
+ relopt_ternary *newoption = init_ternary_reloption(RELOPT_KIND_LOCAL,
+ name, desc,
+ default_val, 0);
+
+ add_local_reloption(relopts, (relopt_gen *) newoption, offset);
+}
/*
* init_real_reloption
@@ -1617,6 +1689,18 @@ parse_one_reloption(relopt_value *option, char *text_str, int text_len,
option->gen->name, value)));
}
break;
+ case RELOPT_TYPE_TERNARY:
+ {
+ bool b;
+ parsed = parse_bool(value, &b);
+ option->values.ternary_val = b ? TERNARY_TRUE : TERNARY_FALSE;
+ if (validate && !parsed)
+ ereport(ERROR,
+ (errcode(ERRCODE_INVALID_PARAMETER_VALUE),
+ errmsg("invalid value for ternary option \"%s\": %s",
+ option->gen->name, value)));
+ }
+ break;
case RELOPT_TYPE_INT:
{
relopt_int *optint = (relopt_int *) option->gen;
@@ -1780,17 +1864,6 @@ fillRelOptions(void *rdopts, Size basesize,
char *itempos = ((char *) rdopts) + elems[j].offset;
char *string_val;
- /*
- * If isset_offset is provided, store whether the reloption is
- * set there.
- */
- if (elems[j].isset_offset > 0)
- {
- char *setpos = ((char *) rdopts) + elems[j].isset_offset;
-
- *(bool *) setpos = options[i].isset;
- }
-
switch (options[i].gen->type)
{
case RELOPT_TYPE_BOOL:
@@ -1798,6 +1871,11 @@ fillRelOptions(void *rdopts, Size basesize,
options[i].values.bool_val :
((relopt_bool *) options[i].gen)->default_val;
break;
+ case RELOPT_TYPE_TERNARY:
+ *(ternary *) itempos = options[i].isset ?
+ options[i].values.ternary_val :
+ ((relopt_ternary *) options[i].gen)->default_val;
+ break;
case RELOPT_TYPE_INT:
*(int *) itempos = options[i].isset ?
options[i].values.int_val :
@@ -1912,8 +1990,8 @@ default_reloptions(Datum reloptions, bool validate, relopt_kind kind)
offsetof(StdRdOptions, parallel_workers)},
{"vacuum_index_cleanup", RELOPT_TYPE_ENUM,
offsetof(StdRdOptions, vacuum_index_cleanup)},
- {"vacuum_truncate", RELOPT_TYPE_BOOL,
- offsetof(StdRdOptions, vacuum_truncate), offsetof(StdRdOptions, vacuum_truncate_set)},
+ {"vacuum_truncate", RELOPT_TYPE_TERNARY,
+ offsetof(StdRdOptions, vacuum_truncate)},
{"vacuum_max_eager_freeze_failure_rate", RELOPT_TYPE_REAL,
offsetof(StdRdOptions, vacuum_max_eager_freeze_failure_rate)}
};
@@ -1993,7 +2071,6 @@ build_local_reloptions(local_relopts *relopts, Datum options, bool validate)
elems[i].optname = opt->option->name;
elems[i].opttype = opt->option->type;
elems[i].offset = opt->offset;
- elems[i].isset_offset = 0; /* not supported for local relopts yet */
i++;
}
diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c
index 733ef40ae7..977babff54 100644
--- a/src/backend/commands/vacuum.c
+++ b/src/backend/commands/vacuum.c
@@ -2221,9 +2221,9 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams params,
{
StdRdOptions *opts = (StdRdOptions *) rel->rd_options;
- if (opts && opts->vacuum_truncate_set)
+ if (opts && opts->vacuum_truncate != TERNARY_UNSET)
{
- if (opts->vacuum_truncate)
+ if (opts->vacuum_truncate == TERNARY_TRUE)
params.truncate = VACOPTVALUE_ENABLED;
else
params.truncate = VACOPTVALUE_DISABLED;
diff --git a/src/include/access/reloptions.h b/src/include/access/reloptions.h
index a604a4702c..a436697658 100644
--- a/src/include/access/reloptions.h
+++ b/src/include/access/reloptions.h
@@ -29,6 +29,7 @@
typedef enum relopt_type
{
RELOPT_TYPE_BOOL,
+ RELOPT_TYPE_TERNARY, /* on, off, unset */
RELOPT_TYPE_INT,
RELOPT_TYPE_REAL,
RELOPT_TYPE_ENUM,
@@ -80,6 +81,7 @@ typedef struct relopt_value
union
{
bool bool_val;
+ ternary ternary_val;
int int_val;
double real_val;
int enum_val;
@@ -94,6 +96,12 @@ typedef struct relopt_bool
bool default_val;
} relopt_bool;
+typedef struct relopt_rernary
+{
+ relopt_gen gen;
+ int default_val;
+} relopt_ternary;
+
typedef struct relopt_int
{
relopt_gen gen;
@@ -152,19 +160,6 @@ typedef struct
const char *optname; /* option's name */
relopt_type opttype; /* option's datatype */
int offset; /* offset of field in result struct */
-
- /*
- * isset_offset is an optional offset of a field in the result struct that
- * stores whether the option is explicitly set for the relation or if it
- * just picked up the default value. In most cases, this can be
- * accomplished by giving the reloption a special out-of-range default
- * value (e.g., some integer reloptions use -2), but this isn't always
- * possible. For example, a Boolean reloption cannot be given an
- * out-of-range default, so we need another way to discover the source of
- * its value. This offset is only used if given a value greater than
- * zero.
- */
- int isset_offset;
} relopt_parse_elt;
/* Local reloption definition */
@@ -195,6 +190,8 @@ typedef struct local_relopts
extern relopt_kind add_reloption_kind(void);
extern void add_bool_reloption(bits32 kinds, const char *name, const char *desc,
bool default_val, LOCKMODE lockmode);
+extern void add_ternary_reloption(bits32 kinds, const char *name,
+ const char *desc, int default_val, LOCKMODE lockmode);
extern void add_int_reloption(bits32 kinds, const char *name, const char *desc,
int default_val, int min_val, int max_val,
LOCKMODE lockmode);
@@ -214,6 +211,9 @@ extern void register_reloptions_validator(local_relopts *relopts,
extern void add_local_bool_reloption(local_relopts *relopts, const char *name,
const char *desc, bool default_val,
int offset);
+extern void add_local_ternary_reloption(local_relopts *relopts,
+ const char *name, const char *desc,
+ ternary default_val, int offset);
extern void add_local_int_reloption(local_relopts *relopts, const char *name,
const char *desc, int default_val,
int min_val, int max_val, int offset);
diff --git a/src/include/c.h b/src/include/c.h
index 39022f8a9d..9c59ccccf8 100644
--- a/src/include/c.h
+++ b/src/include/c.h
@@ -488,6 +488,22 @@ typedef void (*pg_funcptr_t) (void);
#include <stdbool.h>
+/*
+ * ternary
+ * Boolean value with an extrea "unset" option
+ *
+ * Ternary data type is used in relation options that can be "true", "false" or
+ * "unset". Since relation options are used deep inside the PostgreSQL code,
+ * this type is declared globally.
+*/
+
+typedef enum ternary
+{
+ TERNARY_FALSE = 0,
+ TERNARY_TRUE = 1,
+ TERNARY_UNSET = -1
+} ternary;
+
/* ----------------------------------------------------------------
* Section 3: standard system types
diff --git a/src/include/utils/rel.h b/src/include/utils/rel.h
index b552359915..7346d618f9 100644
--- a/src/include/utils/rel.h
+++ b/src/include/utils/rel.h
@@ -346,8 +346,7 @@ typedef struct StdRdOptions
bool user_catalog_table; /* use as an additional catalog relation */
int parallel_workers; /* max number of parallel workers */
StdRdOptIndexCleanup vacuum_index_cleanup; /* controls index vacuuming */
- bool vacuum_truncate; /* enables vacuum to truncate a relation */
- bool vacuum_truncate_set; /* whether vacuum_truncate is set */
+ ternary vacuum_truncate; /* enables vacuum to truncate a relation */
/*
* Fraction of pages in a relation that vacuum can eagerly scan and fail
--
2.39.2
--nextPart5129932.5fSG56mABF
Content-Disposition: attachment;
filename="v2-0003-Add-alias-to-be-used-as-unset-state.patch"
Content-Transfer-Encoding: 7Bit
Content-Type: text/x-patch; charset="unicode-2-0-utf-8";
name="v2-0003-Add-alias-to-be-used-as-unset-state.patch"
^ permalink raw reply [nested|flat] 868+ messages in thread
* [PATCH v2 2/4] Introduce ternary reloptions
@ 2025-09-04 14:41 Nikolay Shaplov <[email protected]>
0 siblings, 0 replies; 868+ messages in thread
From: Nikolay Shaplov @ 2025-09-04 14:41 UTC (permalink / raw)
Introduce ternary reloption as a replacement for current `vacuum_truncate`
implementation. Remove `vacuum_truncate_set` additional flag and using
`TERNARY_UNSET` value instead.
---
src/backend/access/common/reloptions.c | 129 ++++++++++++++++++++-----
src/backend/commands/vacuum.c | 4 +-
src/include/access/reloptions.h | 26 ++---
src/include/c.h | 16 +++
src/include/utils/rel.h | 3 +-
5 files changed, 135 insertions(+), 43 deletions(-)
diff --git a/src/backend/access/common/reloptions.c b/src/backend/access/common/reloptions.c
index 0af3fea68f..f543f11001 100644
--- a/src/backend/access/common/reloptions.c
+++ b/src/backend/access/common/reloptions.c
@@ -40,9 +40,9 @@
*
* To add an option:
*
- * (i) decide on a type (bool, integer, real, enum, string), name, default
- * value, upper and lower bounds (if applicable); for strings, consider a
- * validation routine.
+ * (i) decide on a type (bool, ternary, integer, real, enum, string), name,
+ * default value, upper and lower bounds (if applicable); for strings,
+ * consider a validation routine.
* (ii) add a record below (or use add_<type>_reloption).
* (iii) add it to the appropriate options struct (perhaps StdRdOptions)
* (iv) add it to the appropriate handling routine (perhaps
@@ -147,15 +147,6 @@ static relopt_bool boolRelOpts[] =
},
false
},
- {
- {
- "vacuum_truncate",
- "Enables vacuum to truncate empty pages at the end of this table",
- RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
- ShareUpdateExclusiveLock
- },
- true
- },
{
{
"deduplicate_items",
@@ -170,6 +161,21 @@ static relopt_bool boolRelOpts[] =
{{NULL}}
};
+static relopt_ternary ternaryRelOpts[] =
+{
+ {
+ {
+ "vacuum_truncate",
+ "Enables vacuum to truncate empty pages at the end of this table",
+ RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
+ ShareUpdateExclusiveLock
+ },
+ TERNARY_UNSET
+ },
+ /* list terminator */
+ {{NULL}}
+};
+
static relopt_int intRelOpts[] =
{
{
@@ -600,6 +606,13 @@ initialize_reloptions(void)
boolRelOpts[i].gen.lockmode));
j++;
}
+ for (i = 0; ternaryRelOpts[i].gen.name; i++)
+ {
+ Assert(DoLockModesConflict(ternaryRelOpts[i].gen.lockmode,
+ ternaryRelOpts[i].gen.lockmode));
+ j++;
+ }
+
for (i = 0; intRelOpts[i].gen.name; i++)
{
Assert(DoLockModesConflict(intRelOpts[i].gen.lockmode,
@@ -640,6 +653,14 @@ initialize_reloptions(void)
j++;
}
+ for (i = 0; ternaryRelOpts[i].gen.name; i++)
+ {
+ relOpts[j] = &ternaryRelOpts[i].gen;
+ relOpts[j]->type = RELOPT_TYPE_TERNARY;
+ relOpts[j]->namelen = strlen(relOpts[j]->name);
+ j++;
+ }
+
for (i = 0; intRelOpts[i].gen.name; i++)
{
relOpts[j] = &intRelOpts[i].gen;
@@ -800,6 +821,9 @@ allocate_reloption(bits32 kinds, int type, const char *name, const char *desc,
case RELOPT_TYPE_BOOL:
size = sizeof(relopt_bool);
break;
+ case RELOPT_TYPE_TERNARY:
+ size = sizeof(relopt_ternary);
+ break;
case RELOPT_TYPE_INT:
size = sizeof(relopt_int);
break;
@@ -883,6 +907,54 @@ add_local_bool_reloption(local_relopts *relopts, const char *name,
add_local_reloption(relopts, (relopt_gen *) newoption, offset);
}
+/*
+ * init_ternary_reloption
+ * Allocate and initialize a new ternary reloption
+ */
+static relopt_ternary *
+init_ternary_reloption(bits32 kinds, const char *name, const char *desc,
+ ternary default_val, LOCKMODE lockmode)
+{
+ relopt_ternary *newoption;
+
+ newoption = (relopt_ternary *) allocate_reloption(kinds,
+ RELOPT_TYPE_TERNARY, name, desc, lockmode);
+ newoption->default_val = default_val;
+
+ return newoption;
+}
+
+/*
+ * add_ternary_reloption
+ * Add a new ternary reloption
+ */
+void
+add_ternary_reloption(bits32 kinds, const char *name, const char *desc,
+ ternary default_val, LOCKMODE lockmode)
+{
+ relopt_ternary *newoption = init_ternary_reloption(kinds, name, desc,
+ default_val, lockmode);
+
+ add_reloption((relopt_gen *) newoption);
+}
+
+/*
+ * add_local_ternary_reloption
+ * Add a new ternary local reloption
+ *
+ * 'offset' is offset of ternary-typed field.
+ */
+void
+add_local_ternary_reloption(local_relopts *relopts, const char *name,
+ const char *desc, ternary default_val,
+ int offset)
+{
+ relopt_ternary *newoption = init_ternary_reloption(RELOPT_KIND_LOCAL,
+ name, desc,
+ default_val, 0);
+
+ add_local_reloption(relopts, (relopt_gen *) newoption, offset);
+}
/*
* init_real_reloption
@@ -1617,6 +1689,18 @@ parse_one_reloption(relopt_value *option, char *text_str, int text_len,
option->gen->name, value)));
}
break;
+ case RELOPT_TYPE_TERNARY:
+ {
+ bool b;
+ parsed = parse_bool(value, &b);
+ option->values.ternary_val = b ? TERNARY_TRUE : TERNARY_FALSE;
+ if (validate && !parsed)
+ ereport(ERROR,
+ (errcode(ERRCODE_INVALID_PARAMETER_VALUE),
+ errmsg("invalid value for ternary option \"%s\": %s",
+ option->gen->name, value)));
+ }
+ break;
case RELOPT_TYPE_INT:
{
relopt_int *optint = (relopt_int *) option->gen;
@@ -1780,17 +1864,6 @@ fillRelOptions(void *rdopts, Size basesize,
char *itempos = ((char *) rdopts) + elems[j].offset;
char *string_val;
- /*
- * If isset_offset is provided, store whether the reloption is
- * set there.
- */
- if (elems[j].isset_offset > 0)
- {
- char *setpos = ((char *) rdopts) + elems[j].isset_offset;
-
- *(bool *) setpos = options[i].isset;
- }
-
switch (options[i].gen->type)
{
case RELOPT_TYPE_BOOL:
@@ -1798,6 +1871,11 @@ fillRelOptions(void *rdopts, Size basesize,
options[i].values.bool_val :
((relopt_bool *) options[i].gen)->default_val;
break;
+ case RELOPT_TYPE_TERNARY:
+ *(ternary *) itempos = options[i].isset ?
+ options[i].values.ternary_val :
+ ((relopt_ternary *) options[i].gen)->default_val;
+ break;
case RELOPT_TYPE_INT:
*(int *) itempos = options[i].isset ?
options[i].values.int_val :
@@ -1912,8 +1990,8 @@ default_reloptions(Datum reloptions, bool validate, relopt_kind kind)
offsetof(StdRdOptions, parallel_workers)},
{"vacuum_index_cleanup", RELOPT_TYPE_ENUM,
offsetof(StdRdOptions, vacuum_index_cleanup)},
- {"vacuum_truncate", RELOPT_TYPE_BOOL,
- offsetof(StdRdOptions, vacuum_truncate), offsetof(StdRdOptions, vacuum_truncate_set)},
+ {"vacuum_truncate", RELOPT_TYPE_TERNARY,
+ offsetof(StdRdOptions, vacuum_truncate)},
{"vacuum_max_eager_freeze_failure_rate", RELOPT_TYPE_REAL,
offsetof(StdRdOptions, vacuum_max_eager_freeze_failure_rate)}
};
@@ -1993,7 +2071,6 @@ build_local_reloptions(local_relopts *relopts, Datum options, bool validate)
elems[i].optname = opt->option->name;
elems[i].opttype = opt->option->type;
elems[i].offset = opt->offset;
- elems[i].isset_offset = 0; /* not supported for local relopts yet */
i++;
}
diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c
index 733ef40ae7..977babff54 100644
--- a/src/backend/commands/vacuum.c
+++ b/src/backend/commands/vacuum.c
@@ -2221,9 +2221,9 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams params,
{
StdRdOptions *opts = (StdRdOptions *) rel->rd_options;
- if (opts && opts->vacuum_truncate_set)
+ if (opts && opts->vacuum_truncate != TERNARY_UNSET)
{
- if (opts->vacuum_truncate)
+ if (opts->vacuum_truncate == TERNARY_TRUE)
params.truncate = VACOPTVALUE_ENABLED;
else
params.truncate = VACOPTVALUE_DISABLED;
diff --git a/src/include/access/reloptions.h b/src/include/access/reloptions.h
index a604a4702c..a436697658 100644
--- a/src/include/access/reloptions.h
+++ b/src/include/access/reloptions.h
@@ -29,6 +29,7 @@
typedef enum relopt_type
{
RELOPT_TYPE_BOOL,
+ RELOPT_TYPE_TERNARY, /* on, off, unset */
RELOPT_TYPE_INT,
RELOPT_TYPE_REAL,
RELOPT_TYPE_ENUM,
@@ -80,6 +81,7 @@ typedef struct relopt_value
union
{
bool bool_val;
+ ternary ternary_val;
int int_val;
double real_val;
int enum_val;
@@ -94,6 +96,12 @@ typedef struct relopt_bool
bool default_val;
} relopt_bool;
+typedef struct relopt_rernary
+{
+ relopt_gen gen;
+ int default_val;
+} relopt_ternary;
+
typedef struct relopt_int
{
relopt_gen gen;
@@ -152,19 +160,6 @@ typedef struct
const char *optname; /* option's name */
relopt_type opttype; /* option's datatype */
int offset; /* offset of field in result struct */
-
- /*
- * isset_offset is an optional offset of a field in the result struct that
- * stores whether the option is explicitly set for the relation or if it
- * just picked up the default value. In most cases, this can be
- * accomplished by giving the reloption a special out-of-range default
- * value (e.g., some integer reloptions use -2), but this isn't always
- * possible. For example, a Boolean reloption cannot be given an
- * out-of-range default, so we need another way to discover the source of
- * its value. This offset is only used if given a value greater than
- * zero.
- */
- int isset_offset;
} relopt_parse_elt;
/* Local reloption definition */
@@ -195,6 +190,8 @@ typedef struct local_relopts
extern relopt_kind add_reloption_kind(void);
extern void add_bool_reloption(bits32 kinds, const char *name, const char *desc,
bool default_val, LOCKMODE lockmode);
+extern void add_ternary_reloption(bits32 kinds, const char *name,
+ const char *desc, int default_val, LOCKMODE lockmode);
extern void add_int_reloption(bits32 kinds, const char *name, const char *desc,
int default_val, int min_val, int max_val,
LOCKMODE lockmode);
@@ -214,6 +211,9 @@ extern void register_reloptions_validator(local_relopts *relopts,
extern void add_local_bool_reloption(local_relopts *relopts, const char *name,
const char *desc, bool default_val,
int offset);
+extern void add_local_ternary_reloption(local_relopts *relopts,
+ const char *name, const char *desc,
+ ternary default_val, int offset);
extern void add_local_int_reloption(local_relopts *relopts, const char *name,
const char *desc, int default_val,
int min_val, int max_val, int offset);
diff --git a/src/include/c.h b/src/include/c.h
index 39022f8a9d..9c59ccccf8 100644
--- a/src/include/c.h
+++ b/src/include/c.h
@@ -488,6 +488,22 @@ typedef void (*pg_funcptr_t) (void);
#include <stdbool.h>
+/*
+ * ternary
+ * Boolean value with an extrea "unset" option
+ *
+ * Ternary data type is used in relation options that can be "true", "false" or
+ * "unset". Since relation options are used deep inside the PostgreSQL code,
+ * this type is declared globally.
+*/
+
+typedef enum ternary
+{
+ TERNARY_FALSE = 0,
+ TERNARY_TRUE = 1,
+ TERNARY_UNSET = -1
+} ternary;
+
/* ----------------------------------------------------------------
* Section 3: standard system types
diff --git a/src/include/utils/rel.h b/src/include/utils/rel.h
index b552359915..7346d618f9 100644
--- a/src/include/utils/rel.h
+++ b/src/include/utils/rel.h
@@ -346,8 +346,7 @@ typedef struct StdRdOptions
bool user_catalog_table; /* use as an additional catalog relation */
int parallel_workers; /* max number of parallel workers */
StdRdOptIndexCleanup vacuum_index_cleanup; /* controls index vacuuming */
- bool vacuum_truncate; /* enables vacuum to truncate a relation */
- bool vacuum_truncate_set; /* whether vacuum_truncate is set */
+ ternary vacuum_truncate; /* enables vacuum to truncate a relation */
/*
* Fraction of pages in a relation that vacuum can eagerly scan and fail
--
2.39.2
--nextPart5129932.5fSG56mABF
Content-Disposition: attachment;
filename="v2-0003-Add-alias-to-be-used-as-unset-state.patch"
Content-Transfer-Encoding: 7Bit
Content-Type: text/x-patch; charset="unicode-2-0-utf-8";
name="v2-0003-Add-alias-to-be-used-as-unset-state.patch"
^ permalink raw reply [nested|flat] 868+ messages in thread
* [PATCH v2a 2/4] Introduce ternary reloptions
@ 2025-09-04 14:41 Nikolay Shaplov <[email protected]>
0 siblings, 0 replies; 868+ messages in thread
From: Nikolay Shaplov @ 2025-09-04 14:41 UTC (permalink / raw)
Introduce ternary reloption as a replacement for current `vacuum_truncate`
implementation. Remove `vacuum_truncate_set` additional flag and using
`TERNARY_UNSET` value instead.
---
src/backend/access/common/reloptions.c | 129 ++++++++++++++++++++-----
src/backend/commands/vacuum.c | 4 +-
src/include/access/reloptions.h | 26 ++---
src/include/c.h | 16 +++
src/include/utils/rel.h | 3 +-
5 files changed, 135 insertions(+), 43 deletions(-)
diff --git a/src/backend/access/common/reloptions.c b/src/backend/access/common/reloptions.c
index 0af3fea68f..f543f11001 100644
--- a/src/backend/access/common/reloptions.c
+++ b/src/backend/access/common/reloptions.c
@@ -40,9 +40,9 @@
*
* To add an option:
*
- * (i) decide on a type (bool, integer, real, enum, string), name, default
- * value, upper and lower bounds (if applicable); for strings, consider a
- * validation routine.
+ * (i) decide on a type (bool, ternary, integer, real, enum, string), name,
+ * default value, upper and lower bounds (if applicable); for strings,
+ * consider a validation routine.
* (ii) add a record below (or use add_<type>_reloption).
* (iii) add it to the appropriate options struct (perhaps StdRdOptions)
* (iv) add it to the appropriate handling routine (perhaps
@@ -147,15 +147,6 @@ static relopt_bool boolRelOpts[] =
},
false
},
- {
- {
- "vacuum_truncate",
- "Enables vacuum to truncate empty pages at the end of this table",
- RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
- ShareUpdateExclusiveLock
- },
- true
- },
{
{
"deduplicate_items",
@@ -170,6 +161,21 @@ static relopt_bool boolRelOpts[] =
{{NULL}}
};
+static relopt_ternary ternaryRelOpts[] =
+{
+ {
+ {
+ "vacuum_truncate",
+ "Enables vacuum to truncate empty pages at the end of this table",
+ RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
+ ShareUpdateExclusiveLock
+ },
+ TERNARY_UNSET
+ },
+ /* list terminator */
+ {{NULL}}
+};
+
static relopt_int intRelOpts[] =
{
{
@@ -600,6 +606,13 @@ initialize_reloptions(void)
boolRelOpts[i].gen.lockmode));
j++;
}
+ for (i = 0; ternaryRelOpts[i].gen.name; i++)
+ {
+ Assert(DoLockModesConflict(ternaryRelOpts[i].gen.lockmode,
+ ternaryRelOpts[i].gen.lockmode));
+ j++;
+ }
+
for (i = 0; intRelOpts[i].gen.name; i++)
{
Assert(DoLockModesConflict(intRelOpts[i].gen.lockmode,
@@ -640,6 +653,14 @@ initialize_reloptions(void)
j++;
}
+ for (i = 0; ternaryRelOpts[i].gen.name; i++)
+ {
+ relOpts[j] = &ternaryRelOpts[i].gen;
+ relOpts[j]->type = RELOPT_TYPE_TERNARY;
+ relOpts[j]->namelen = strlen(relOpts[j]->name);
+ j++;
+ }
+
for (i = 0; intRelOpts[i].gen.name; i++)
{
relOpts[j] = &intRelOpts[i].gen;
@@ -800,6 +821,9 @@ allocate_reloption(bits32 kinds, int type, const char *name, const char *desc,
case RELOPT_TYPE_BOOL:
size = sizeof(relopt_bool);
break;
+ case RELOPT_TYPE_TERNARY:
+ size = sizeof(relopt_ternary);
+ break;
case RELOPT_TYPE_INT:
size = sizeof(relopt_int);
break;
@@ -883,6 +907,54 @@ add_local_bool_reloption(local_relopts *relopts, const char *name,
add_local_reloption(relopts, (relopt_gen *) newoption, offset);
}
+/*
+ * init_ternary_reloption
+ * Allocate and initialize a new ternary reloption
+ */
+static relopt_ternary *
+init_ternary_reloption(bits32 kinds, const char *name, const char *desc,
+ ternary default_val, LOCKMODE lockmode)
+{
+ relopt_ternary *newoption;
+
+ newoption = (relopt_ternary *) allocate_reloption(kinds,
+ RELOPT_TYPE_TERNARY, name, desc, lockmode);
+ newoption->default_val = default_val;
+
+ return newoption;
+}
+
+/*
+ * add_ternary_reloption
+ * Add a new ternary reloption
+ */
+void
+add_ternary_reloption(bits32 kinds, const char *name, const char *desc,
+ ternary default_val, LOCKMODE lockmode)
+{
+ relopt_ternary *newoption = init_ternary_reloption(kinds, name, desc,
+ default_val, lockmode);
+
+ add_reloption((relopt_gen *) newoption);
+}
+
+/*
+ * add_local_ternary_reloption
+ * Add a new ternary local reloption
+ *
+ * 'offset' is offset of ternary-typed field.
+ */
+void
+add_local_ternary_reloption(local_relopts *relopts, const char *name,
+ const char *desc, ternary default_val,
+ int offset)
+{
+ relopt_ternary *newoption = init_ternary_reloption(RELOPT_KIND_LOCAL,
+ name, desc,
+ default_val, 0);
+
+ add_local_reloption(relopts, (relopt_gen *) newoption, offset);
+}
/*
* init_real_reloption
@@ -1617,6 +1689,18 @@ parse_one_reloption(relopt_value *option, char *text_str, int text_len,
option->gen->name, value)));
}
break;
+ case RELOPT_TYPE_TERNARY:
+ {
+ bool b;
+ parsed = parse_bool(value, &b);
+ option->values.ternary_val = b ? TERNARY_TRUE : TERNARY_FALSE;
+ if (validate && !parsed)
+ ereport(ERROR,
+ (errcode(ERRCODE_INVALID_PARAMETER_VALUE),
+ errmsg("invalid value for ternary option \"%s\": %s",
+ option->gen->name, value)));
+ }
+ break;
case RELOPT_TYPE_INT:
{
relopt_int *optint = (relopt_int *) option->gen;
@@ -1780,17 +1864,6 @@ fillRelOptions(void *rdopts, Size basesize,
char *itempos = ((char *) rdopts) + elems[j].offset;
char *string_val;
- /*
- * If isset_offset is provided, store whether the reloption is
- * set there.
- */
- if (elems[j].isset_offset > 0)
- {
- char *setpos = ((char *) rdopts) + elems[j].isset_offset;
-
- *(bool *) setpos = options[i].isset;
- }
-
switch (options[i].gen->type)
{
case RELOPT_TYPE_BOOL:
@@ -1798,6 +1871,11 @@ fillRelOptions(void *rdopts, Size basesize,
options[i].values.bool_val :
((relopt_bool *) options[i].gen)->default_val;
break;
+ case RELOPT_TYPE_TERNARY:
+ *(ternary *) itempos = options[i].isset ?
+ options[i].values.ternary_val :
+ ((relopt_ternary *) options[i].gen)->default_val;
+ break;
case RELOPT_TYPE_INT:
*(int *) itempos = options[i].isset ?
options[i].values.int_val :
@@ -1912,8 +1990,8 @@ default_reloptions(Datum reloptions, bool validate, relopt_kind kind)
offsetof(StdRdOptions, parallel_workers)},
{"vacuum_index_cleanup", RELOPT_TYPE_ENUM,
offsetof(StdRdOptions, vacuum_index_cleanup)},
- {"vacuum_truncate", RELOPT_TYPE_BOOL,
- offsetof(StdRdOptions, vacuum_truncate), offsetof(StdRdOptions, vacuum_truncate_set)},
+ {"vacuum_truncate", RELOPT_TYPE_TERNARY,
+ offsetof(StdRdOptions, vacuum_truncate)},
{"vacuum_max_eager_freeze_failure_rate", RELOPT_TYPE_REAL,
offsetof(StdRdOptions, vacuum_max_eager_freeze_failure_rate)}
};
@@ -1993,7 +2071,6 @@ build_local_reloptions(local_relopts *relopts, Datum options, bool validate)
elems[i].optname = opt->option->name;
elems[i].opttype = opt->option->type;
elems[i].offset = opt->offset;
- elems[i].isset_offset = 0; /* not supported for local relopts yet */
i++;
}
diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c
index 733ef40ae7..977babff54 100644
--- a/src/backend/commands/vacuum.c
+++ b/src/backend/commands/vacuum.c
@@ -2221,9 +2221,9 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams params,
{
StdRdOptions *opts = (StdRdOptions *) rel->rd_options;
- if (opts && opts->vacuum_truncate_set)
+ if (opts && opts->vacuum_truncate != TERNARY_UNSET)
{
- if (opts->vacuum_truncate)
+ if (opts->vacuum_truncate == TERNARY_TRUE)
params.truncate = VACOPTVALUE_ENABLED;
else
params.truncate = VACOPTVALUE_DISABLED;
diff --git a/src/include/access/reloptions.h b/src/include/access/reloptions.h
index a604a4702c..a436697658 100644
--- a/src/include/access/reloptions.h
+++ b/src/include/access/reloptions.h
@@ -29,6 +29,7 @@
typedef enum relopt_type
{
RELOPT_TYPE_BOOL,
+ RELOPT_TYPE_TERNARY, /* on, off, unset */
RELOPT_TYPE_INT,
RELOPT_TYPE_REAL,
RELOPT_TYPE_ENUM,
@@ -80,6 +81,7 @@ typedef struct relopt_value
union
{
bool bool_val;
+ ternary ternary_val;
int int_val;
double real_val;
int enum_val;
@@ -94,6 +96,12 @@ typedef struct relopt_bool
bool default_val;
} relopt_bool;
+typedef struct relopt_rernary
+{
+ relopt_gen gen;
+ int default_val;
+} relopt_ternary;
+
typedef struct relopt_int
{
relopt_gen gen;
@@ -152,19 +160,6 @@ typedef struct
const char *optname; /* option's name */
relopt_type opttype; /* option's datatype */
int offset; /* offset of field in result struct */
-
- /*
- * isset_offset is an optional offset of a field in the result struct that
- * stores whether the option is explicitly set for the relation or if it
- * just picked up the default value. In most cases, this can be
- * accomplished by giving the reloption a special out-of-range default
- * value (e.g., some integer reloptions use -2), but this isn't always
- * possible. For example, a Boolean reloption cannot be given an
- * out-of-range default, so we need another way to discover the source of
- * its value. This offset is only used if given a value greater than
- * zero.
- */
- int isset_offset;
} relopt_parse_elt;
/* Local reloption definition */
@@ -195,6 +190,8 @@ typedef struct local_relopts
extern relopt_kind add_reloption_kind(void);
extern void add_bool_reloption(bits32 kinds, const char *name, const char *desc,
bool default_val, LOCKMODE lockmode);
+extern void add_ternary_reloption(bits32 kinds, const char *name,
+ const char *desc, int default_val, LOCKMODE lockmode);
extern void add_int_reloption(bits32 kinds, const char *name, const char *desc,
int default_val, int min_val, int max_val,
LOCKMODE lockmode);
@@ -214,6 +211,9 @@ extern void register_reloptions_validator(local_relopts *relopts,
extern void add_local_bool_reloption(local_relopts *relopts, const char *name,
const char *desc, bool default_val,
int offset);
+extern void add_local_ternary_reloption(local_relopts *relopts,
+ const char *name, const char *desc,
+ ternary default_val, int offset);
extern void add_local_int_reloption(local_relopts *relopts, const char *name,
const char *desc, int default_val,
int min_val, int max_val, int offset);
diff --git a/src/include/c.h b/src/include/c.h
index 39022f8a9d..9c59ccccf8 100644
--- a/src/include/c.h
+++ b/src/include/c.h
@@ -488,6 +488,22 @@ typedef void (*pg_funcptr_t) (void);
#include <stdbool.h>
+/*
+ * ternary
+ * Boolean value with an extrea "unset" option
+ *
+ * Ternary data type is used in relation options that can be "true", "false" or
+ * "unset". Since relation options are used deep inside the PostgreSQL code,
+ * this type is declared globally.
+*/
+
+typedef enum ternary
+{
+ TERNARY_FALSE = 0,
+ TERNARY_TRUE = 1,
+ TERNARY_UNSET = -1
+} ternary;
+
/* ----------------------------------------------------------------
* Section 3: standard system types
diff --git a/src/include/utils/rel.h b/src/include/utils/rel.h
index b552359915..7346d618f9 100644
--- a/src/include/utils/rel.h
+++ b/src/include/utils/rel.h
@@ -346,8 +346,7 @@ typedef struct StdRdOptions
bool user_catalog_table; /* use as an additional catalog relation */
int parallel_workers; /* max number of parallel workers */
StdRdOptIndexCleanup vacuum_index_cleanup; /* controls index vacuuming */
- bool vacuum_truncate; /* enables vacuum to truncate a relation */
- bool vacuum_truncate_set; /* whether vacuum_truncate is set */
+ ternary vacuum_truncate; /* enables vacuum to truncate a relation */
/*
* Fraction of pages in a relation that vacuum can eagerly scan and fail
--
2.39.2
--nextPart4155600.3ZeAukHxDK
Content-Disposition: attachment;
filename="v2a-0003-Add-alias-to-be-used-as-unset-state.patch"
Content-Transfer-Encoding: 7Bit
Content-Type: text/x-patch; charset="unicode-2-0-utf-8";
name="v2a-0003-Add-alias-to-be-used-as-unset-state.patch"
^ permalink raw reply [nested|flat] 868+ messages in thread
* [PATCH v2 2/4] Introduce ternary reloptions
@ 2025-09-04 14:41 Nikolay Shaplov <[email protected]>
0 siblings, 0 replies; 868+ messages in thread
From: Nikolay Shaplov @ 2025-09-04 14:41 UTC (permalink / raw)
Introduce ternary reloption as a replacement for current `vacuum_truncate`
implementation. Remove `vacuum_truncate_set` additional flag and using
`TERNARY_UNSET` value instead.
---
src/backend/access/common/reloptions.c | 129 ++++++++++++++++++++-----
src/backend/commands/vacuum.c | 4 +-
src/include/access/reloptions.h | 26 ++---
src/include/c.h | 16 +++
src/include/utils/rel.h | 3 +-
5 files changed, 135 insertions(+), 43 deletions(-)
diff --git a/src/backend/access/common/reloptions.c b/src/backend/access/common/reloptions.c
index 0af3fea68f..f543f11001 100644
--- a/src/backend/access/common/reloptions.c
+++ b/src/backend/access/common/reloptions.c
@@ -40,9 +40,9 @@
*
* To add an option:
*
- * (i) decide on a type (bool, integer, real, enum, string), name, default
- * value, upper and lower bounds (if applicable); for strings, consider a
- * validation routine.
+ * (i) decide on a type (bool, ternary, integer, real, enum, string), name,
+ * default value, upper and lower bounds (if applicable); for strings,
+ * consider a validation routine.
* (ii) add a record below (or use add_<type>_reloption).
* (iii) add it to the appropriate options struct (perhaps StdRdOptions)
* (iv) add it to the appropriate handling routine (perhaps
@@ -147,15 +147,6 @@ static relopt_bool boolRelOpts[] =
},
false
},
- {
- {
- "vacuum_truncate",
- "Enables vacuum to truncate empty pages at the end of this table",
- RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
- ShareUpdateExclusiveLock
- },
- true
- },
{
{
"deduplicate_items",
@@ -170,6 +161,21 @@ static relopt_bool boolRelOpts[] =
{{NULL}}
};
+static relopt_ternary ternaryRelOpts[] =
+{
+ {
+ {
+ "vacuum_truncate",
+ "Enables vacuum to truncate empty pages at the end of this table",
+ RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
+ ShareUpdateExclusiveLock
+ },
+ TERNARY_UNSET
+ },
+ /* list terminator */
+ {{NULL}}
+};
+
static relopt_int intRelOpts[] =
{
{
@@ -600,6 +606,13 @@ initialize_reloptions(void)
boolRelOpts[i].gen.lockmode));
j++;
}
+ for (i = 0; ternaryRelOpts[i].gen.name; i++)
+ {
+ Assert(DoLockModesConflict(ternaryRelOpts[i].gen.lockmode,
+ ternaryRelOpts[i].gen.lockmode));
+ j++;
+ }
+
for (i = 0; intRelOpts[i].gen.name; i++)
{
Assert(DoLockModesConflict(intRelOpts[i].gen.lockmode,
@@ -640,6 +653,14 @@ initialize_reloptions(void)
j++;
}
+ for (i = 0; ternaryRelOpts[i].gen.name; i++)
+ {
+ relOpts[j] = &ternaryRelOpts[i].gen;
+ relOpts[j]->type = RELOPT_TYPE_TERNARY;
+ relOpts[j]->namelen = strlen(relOpts[j]->name);
+ j++;
+ }
+
for (i = 0; intRelOpts[i].gen.name; i++)
{
relOpts[j] = &intRelOpts[i].gen;
@@ -800,6 +821,9 @@ allocate_reloption(bits32 kinds, int type, const char *name, const char *desc,
case RELOPT_TYPE_BOOL:
size = sizeof(relopt_bool);
break;
+ case RELOPT_TYPE_TERNARY:
+ size = sizeof(relopt_ternary);
+ break;
case RELOPT_TYPE_INT:
size = sizeof(relopt_int);
break;
@@ -883,6 +907,54 @@ add_local_bool_reloption(local_relopts *relopts, const char *name,
add_local_reloption(relopts, (relopt_gen *) newoption, offset);
}
+/*
+ * init_ternary_reloption
+ * Allocate and initialize a new ternary reloption
+ */
+static relopt_ternary *
+init_ternary_reloption(bits32 kinds, const char *name, const char *desc,
+ ternary default_val, LOCKMODE lockmode)
+{
+ relopt_ternary *newoption;
+
+ newoption = (relopt_ternary *) allocate_reloption(kinds,
+ RELOPT_TYPE_TERNARY, name, desc, lockmode);
+ newoption->default_val = default_val;
+
+ return newoption;
+}
+
+/*
+ * add_ternary_reloption
+ * Add a new ternary reloption
+ */
+void
+add_ternary_reloption(bits32 kinds, const char *name, const char *desc,
+ ternary default_val, LOCKMODE lockmode)
+{
+ relopt_ternary *newoption = init_ternary_reloption(kinds, name, desc,
+ default_val, lockmode);
+
+ add_reloption((relopt_gen *) newoption);
+}
+
+/*
+ * add_local_ternary_reloption
+ * Add a new ternary local reloption
+ *
+ * 'offset' is offset of ternary-typed field.
+ */
+void
+add_local_ternary_reloption(local_relopts *relopts, const char *name,
+ const char *desc, ternary default_val,
+ int offset)
+{
+ relopt_ternary *newoption = init_ternary_reloption(RELOPT_KIND_LOCAL,
+ name, desc,
+ default_val, 0);
+
+ add_local_reloption(relopts, (relopt_gen *) newoption, offset);
+}
/*
* init_real_reloption
@@ -1617,6 +1689,18 @@ parse_one_reloption(relopt_value *option, char *text_str, int text_len,
option->gen->name, value)));
}
break;
+ case RELOPT_TYPE_TERNARY:
+ {
+ bool b;
+ parsed = parse_bool(value, &b);
+ option->values.ternary_val = b ? TERNARY_TRUE : TERNARY_FALSE;
+ if (validate && !parsed)
+ ereport(ERROR,
+ (errcode(ERRCODE_INVALID_PARAMETER_VALUE),
+ errmsg("invalid value for ternary option \"%s\": %s",
+ option->gen->name, value)));
+ }
+ break;
case RELOPT_TYPE_INT:
{
relopt_int *optint = (relopt_int *) option->gen;
@@ -1780,17 +1864,6 @@ fillRelOptions(void *rdopts, Size basesize,
char *itempos = ((char *) rdopts) + elems[j].offset;
char *string_val;
- /*
- * If isset_offset is provided, store whether the reloption is
- * set there.
- */
- if (elems[j].isset_offset > 0)
- {
- char *setpos = ((char *) rdopts) + elems[j].isset_offset;
-
- *(bool *) setpos = options[i].isset;
- }
-
switch (options[i].gen->type)
{
case RELOPT_TYPE_BOOL:
@@ -1798,6 +1871,11 @@ fillRelOptions(void *rdopts, Size basesize,
options[i].values.bool_val :
((relopt_bool *) options[i].gen)->default_val;
break;
+ case RELOPT_TYPE_TERNARY:
+ *(ternary *) itempos = options[i].isset ?
+ options[i].values.ternary_val :
+ ((relopt_ternary *) options[i].gen)->default_val;
+ break;
case RELOPT_TYPE_INT:
*(int *) itempos = options[i].isset ?
options[i].values.int_val :
@@ -1912,8 +1990,8 @@ default_reloptions(Datum reloptions, bool validate, relopt_kind kind)
offsetof(StdRdOptions, parallel_workers)},
{"vacuum_index_cleanup", RELOPT_TYPE_ENUM,
offsetof(StdRdOptions, vacuum_index_cleanup)},
- {"vacuum_truncate", RELOPT_TYPE_BOOL,
- offsetof(StdRdOptions, vacuum_truncate), offsetof(StdRdOptions, vacuum_truncate_set)},
+ {"vacuum_truncate", RELOPT_TYPE_TERNARY,
+ offsetof(StdRdOptions, vacuum_truncate)},
{"vacuum_max_eager_freeze_failure_rate", RELOPT_TYPE_REAL,
offsetof(StdRdOptions, vacuum_max_eager_freeze_failure_rate)}
};
@@ -1993,7 +2071,6 @@ build_local_reloptions(local_relopts *relopts, Datum options, bool validate)
elems[i].optname = opt->option->name;
elems[i].opttype = opt->option->type;
elems[i].offset = opt->offset;
- elems[i].isset_offset = 0; /* not supported for local relopts yet */
i++;
}
diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c
index 733ef40ae7..977babff54 100644
--- a/src/backend/commands/vacuum.c
+++ b/src/backend/commands/vacuum.c
@@ -2221,9 +2221,9 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams params,
{
StdRdOptions *opts = (StdRdOptions *) rel->rd_options;
- if (opts && opts->vacuum_truncate_set)
+ if (opts && opts->vacuum_truncate != TERNARY_UNSET)
{
- if (opts->vacuum_truncate)
+ if (opts->vacuum_truncate == TERNARY_TRUE)
params.truncate = VACOPTVALUE_ENABLED;
else
params.truncate = VACOPTVALUE_DISABLED;
diff --git a/src/include/access/reloptions.h b/src/include/access/reloptions.h
index a604a4702c..a436697658 100644
--- a/src/include/access/reloptions.h
+++ b/src/include/access/reloptions.h
@@ -29,6 +29,7 @@
typedef enum relopt_type
{
RELOPT_TYPE_BOOL,
+ RELOPT_TYPE_TERNARY, /* on, off, unset */
RELOPT_TYPE_INT,
RELOPT_TYPE_REAL,
RELOPT_TYPE_ENUM,
@@ -80,6 +81,7 @@ typedef struct relopt_value
union
{
bool bool_val;
+ ternary ternary_val;
int int_val;
double real_val;
int enum_val;
@@ -94,6 +96,12 @@ typedef struct relopt_bool
bool default_val;
} relopt_bool;
+typedef struct relopt_rernary
+{
+ relopt_gen gen;
+ int default_val;
+} relopt_ternary;
+
typedef struct relopt_int
{
relopt_gen gen;
@@ -152,19 +160,6 @@ typedef struct
const char *optname; /* option's name */
relopt_type opttype; /* option's datatype */
int offset; /* offset of field in result struct */
-
- /*
- * isset_offset is an optional offset of a field in the result struct that
- * stores whether the option is explicitly set for the relation or if it
- * just picked up the default value. In most cases, this can be
- * accomplished by giving the reloption a special out-of-range default
- * value (e.g., some integer reloptions use -2), but this isn't always
- * possible. For example, a Boolean reloption cannot be given an
- * out-of-range default, so we need another way to discover the source of
- * its value. This offset is only used if given a value greater than
- * zero.
- */
- int isset_offset;
} relopt_parse_elt;
/* Local reloption definition */
@@ -195,6 +190,8 @@ typedef struct local_relopts
extern relopt_kind add_reloption_kind(void);
extern void add_bool_reloption(bits32 kinds, const char *name, const char *desc,
bool default_val, LOCKMODE lockmode);
+extern void add_ternary_reloption(bits32 kinds, const char *name,
+ const char *desc, int default_val, LOCKMODE lockmode);
extern void add_int_reloption(bits32 kinds, const char *name, const char *desc,
int default_val, int min_val, int max_val,
LOCKMODE lockmode);
@@ -214,6 +211,9 @@ extern void register_reloptions_validator(local_relopts *relopts,
extern void add_local_bool_reloption(local_relopts *relopts, const char *name,
const char *desc, bool default_val,
int offset);
+extern void add_local_ternary_reloption(local_relopts *relopts,
+ const char *name, const char *desc,
+ ternary default_val, int offset);
extern void add_local_int_reloption(local_relopts *relopts, const char *name,
const char *desc, int default_val,
int min_val, int max_val, int offset);
diff --git a/src/include/c.h b/src/include/c.h
index 39022f8a9d..9c59ccccf8 100644
--- a/src/include/c.h
+++ b/src/include/c.h
@@ -488,6 +488,22 @@ typedef void (*pg_funcptr_t) (void);
#include <stdbool.h>
+/*
+ * ternary
+ * Boolean value with an extrea "unset" option
+ *
+ * Ternary data type is used in relation options that can be "true", "false" or
+ * "unset". Since relation options are used deep inside the PostgreSQL code,
+ * this type is declared globally.
+*/
+
+typedef enum ternary
+{
+ TERNARY_FALSE = 0,
+ TERNARY_TRUE = 1,
+ TERNARY_UNSET = -1
+} ternary;
+
/* ----------------------------------------------------------------
* Section 3: standard system types
diff --git a/src/include/utils/rel.h b/src/include/utils/rel.h
index b552359915..7346d618f9 100644
--- a/src/include/utils/rel.h
+++ b/src/include/utils/rel.h
@@ -346,8 +346,7 @@ typedef struct StdRdOptions
bool user_catalog_table; /* use as an additional catalog relation */
int parallel_workers; /* max number of parallel workers */
StdRdOptIndexCleanup vacuum_index_cleanup; /* controls index vacuuming */
- bool vacuum_truncate; /* enables vacuum to truncate a relation */
- bool vacuum_truncate_set; /* whether vacuum_truncate is set */
+ ternary vacuum_truncate; /* enables vacuum to truncate a relation */
/*
* Fraction of pages in a relation that vacuum can eagerly scan and fail
--
2.39.2
--nextPart5129932.5fSG56mABF
Content-Disposition: attachment;
filename="v2-0003-Add-alias-to-be-used-as-unset-state.patch"
Content-Transfer-Encoding: 7Bit
Content-Type: text/x-patch; charset="unicode-2-0-utf-8";
name="v2-0003-Add-alias-to-be-used-as-unset-state.patch"
^ permalink raw reply [nested|flat] 868+ messages in thread
* [PATCH v2a 2/4] Introduce ternary reloptions
@ 2025-09-04 14:41 Nikolay Shaplov <[email protected]>
0 siblings, 0 replies; 868+ messages in thread
From: Nikolay Shaplov @ 2025-09-04 14:41 UTC (permalink / raw)
Introduce ternary reloption as a replacement for current `vacuum_truncate`
implementation. Remove `vacuum_truncate_set` additional flag and using
`TERNARY_UNSET` value instead.
---
src/backend/access/common/reloptions.c | 129 ++++++++++++++++++++-----
src/backend/commands/vacuum.c | 4 +-
src/include/access/reloptions.h | 26 ++---
src/include/c.h | 16 +++
src/include/utils/rel.h | 3 +-
5 files changed, 135 insertions(+), 43 deletions(-)
diff --git a/src/backend/access/common/reloptions.c b/src/backend/access/common/reloptions.c
index 0af3fea68f..f543f11001 100644
--- a/src/backend/access/common/reloptions.c
+++ b/src/backend/access/common/reloptions.c
@@ -40,9 +40,9 @@
*
* To add an option:
*
- * (i) decide on a type (bool, integer, real, enum, string), name, default
- * value, upper and lower bounds (if applicable); for strings, consider a
- * validation routine.
+ * (i) decide on a type (bool, ternary, integer, real, enum, string), name,
+ * default value, upper and lower bounds (if applicable); for strings,
+ * consider a validation routine.
* (ii) add a record below (or use add_<type>_reloption).
* (iii) add it to the appropriate options struct (perhaps StdRdOptions)
* (iv) add it to the appropriate handling routine (perhaps
@@ -147,15 +147,6 @@ static relopt_bool boolRelOpts[] =
},
false
},
- {
- {
- "vacuum_truncate",
- "Enables vacuum to truncate empty pages at the end of this table",
- RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
- ShareUpdateExclusiveLock
- },
- true
- },
{
{
"deduplicate_items",
@@ -170,6 +161,21 @@ static relopt_bool boolRelOpts[] =
{{NULL}}
};
+static relopt_ternary ternaryRelOpts[] =
+{
+ {
+ {
+ "vacuum_truncate",
+ "Enables vacuum to truncate empty pages at the end of this table",
+ RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
+ ShareUpdateExclusiveLock
+ },
+ TERNARY_UNSET
+ },
+ /* list terminator */
+ {{NULL}}
+};
+
static relopt_int intRelOpts[] =
{
{
@@ -600,6 +606,13 @@ initialize_reloptions(void)
boolRelOpts[i].gen.lockmode));
j++;
}
+ for (i = 0; ternaryRelOpts[i].gen.name; i++)
+ {
+ Assert(DoLockModesConflict(ternaryRelOpts[i].gen.lockmode,
+ ternaryRelOpts[i].gen.lockmode));
+ j++;
+ }
+
for (i = 0; intRelOpts[i].gen.name; i++)
{
Assert(DoLockModesConflict(intRelOpts[i].gen.lockmode,
@@ -640,6 +653,14 @@ initialize_reloptions(void)
j++;
}
+ for (i = 0; ternaryRelOpts[i].gen.name; i++)
+ {
+ relOpts[j] = &ternaryRelOpts[i].gen;
+ relOpts[j]->type = RELOPT_TYPE_TERNARY;
+ relOpts[j]->namelen = strlen(relOpts[j]->name);
+ j++;
+ }
+
for (i = 0; intRelOpts[i].gen.name; i++)
{
relOpts[j] = &intRelOpts[i].gen;
@@ -800,6 +821,9 @@ allocate_reloption(bits32 kinds, int type, const char *name, const char *desc,
case RELOPT_TYPE_BOOL:
size = sizeof(relopt_bool);
break;
+ case RELOPT_TYPE_TERNARY:
+ size = sizeof(relopt_ternary);
+ break;
case RELOPT_TYPE_INT:
size = sizeof(relopt_int);
break;
@@ -883,6 +907,54 @@ add_local_bool_reloption(local_relopts *relopts, const char *name,
add_local_reloption(relopts, (relopt_gen *) newoption, offset);
}
+/*
+ * init_ternary_reloption
+ * Allocate and initialize a new ternary reloption
+ */
+static relopt_ternary *
+init_ternary_reloption(bits32 kinds, const char *name, const char *desc,
+ ternary default_val, LOCKMODE lockmode)
+{
+ relopt_ternary *newoption;
+
+ newoption = (relopt_ternary *) allocate_reloption(kinds,
+ RELOPT_TYPE_TERNARY, name, desc, lockmode);
+ newoption->default_val = default_val;
+
+ return newoption;
+}
+
+/*
+ * add_ternary_reloption
+ * Add a new ternary reloption
+ */
+void
+add_ternary_reloption(bits32 kinds, const char *name, const char *desc,
+ ternary default_val, LOCKMODE lockmode)
+{
+ relopt_ternary *newoption = init_ternary_reloption(kinds, name, desc,
+ default_val, lockmode);
+
+ add_reloption((relopt_gen *) newoption);
+}
+
+/*
+ * add_local_ternary_reloption
+ * Add a new ternary local reloption
+ *
+ * 'offset' is offset of ternary-typed field.
+ */
+void
+add_local_ternary_reloption(local_relopts *relopts, const char *name,
+ const char *desc, ternary default_val,
+ int offset)
+{
+ relopt_ternary *newoption = init_ternary_reloption(RELOPT_KIND_LOCAL,
+ name, desc,
+ default_val, 0);
+
+ add_local_reloption(relopts, (relopt_gen *) newoption, offset);
+}
/*
* init_real_reloption
@@ -1617,6 +1689,18 @@ parse_one_reloption(relopt_value *option, char *text_str, int text_len,
option->gen->name, value)));
}
break;
+ case RELOPT_TYPE_TERNARY:
+ {
+ bool b;
+ parsed = parse_bool(value, &b);
+ option->values.ternary_val = b ? TERNARY_TRUE : TERNARY_FALSE;
+ if (validate && !parsed)
+ ereport(ERROR,
+ (errcode(ERRCODE_INVALID_PARAMETER_VALUE),
+ errmsg("invalid value for ternary option \"%s\": %s",
+ option->gen->name, value)));
+ }
+ break;
case RELOPT_TYPE_INT:
{
relopt_int *optint = (relopt_int *) option->gen;
@@ -1780,17 +1864,6 @@ fillRelOptions(void *rdopts, Size basesize,
char *itempos = ((char *) rdopts) + elems[j].offset;
char *string_val;
- /*
- * If isset_offset is provided, store whether the reloption is
- * set there.
- */
- if (elems[j].isset_offset > 0)
- {
- char *setpos = ((char *) rdopts) + elems[j].isset_offset;
-
- *(bool *) setpos = options[i].isset;
- }
-
switch (options[i].gen->type)
{
case RELOPT_TYPE_BOOL:
@@ -1798,6 +1871,11 @@ fillRelOptions(void *rdopts, Size basesize,
options[i].values.bool_val :
((relopt_bool *) options[i].gen)->default_val;
break;
+ case RELOPT_TYPE_TERNARY:
+ *(ternary *) itempos = options[i].isset ?
+ options[i].values.ternary_val :
+ ((relopt_ternary *) options[i].gen)->default_val;
+ break;
case RELOPT_TYPE_INT:
*(int *) itempos = options[i].isset ?
options[i].values.int_val :
@@ -1912,8 +1990,8 @@ default_reloptions(Datum reloptions, bool validate, relopt_kind kind)
offsetof(StdRdOptions, parallel_workers)},
{"vacuum_index_cleanup", RELOPT_TYPE_ENUM,
offsetof(StdRdOptions, vacuum_index_cleanup)},
- {"vacuum_truncate", RELOPT_TYPE_BOOL,
- offsetof(StdRdOptions, vacuum_truncate), offsetof(StdRdOptions, vacuum_truncate_set)},
+ {"vacuum_truncate", RELOPT_TYPE_TERNARY,
+ offsetof(StdRdOptions, vacuum_truncate)},
{"vacuum_max_eager_freeze_failure_rate", RELOPT_TYPE_REAL,
offsetof(StdRdOptions, vacuum_max_eager_freeze_failure_rate)}
};
@@ -1993,7 +2071,6 @@ build_local_reloptions(local_relopts *relopts, Datum options, bool validate)
elems[i].optname = opt->option->name;
elems[i].opttype = opt->option->type;
elems[i].offset = opt->offset;
- elems[i].isset_offset = 0; /* not supported for local relopts yet */
i++;
}
diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c
index 733ef40ae7..977babff54 100644
--- a/src/backend/commands/vacuum.c
+++ b/src/backend/commands/vacuum.c
@@ -2221,9 +2221,9 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams params,
{
StdRdOptions *opts = (StdRdOptions *) rel->rd_options;
- if (opts && opts->vacuum_truncate_set)
+ if (opts && opts->vacuum_truncate != TERNARY_UNSET)
{
- if (opts->vacuum_truncate)
+ if (opts->vacuum_truncate == TERNARY_TRUE)
params.truncate = VACOPTVALUE_ENABLED;
else
params.truncate = VACOPTVALUE_DISABLED;
diff --git a/src/include/access/reloptions.h b/src/include/access/reloptions.h
index a604a4702c..a436697658 100644
--- a/src/include/access/reloptions.h
+++ b/src/include/access/reloptions.h
@@ -29,6 +29,7 @@
typedef enum relopt_type
{
RELOPT_TYPE_BOOL,
+ RELOPT_TYPE_TERNARY, /* on, off, unset */
RELOPT_TYPE_INT,
RELOPT_TYPE_REAL,
RELOPT_TYPE_ENUM,
@@ -80,6 +81,7 @@ typedef struct relopt_value
union
{
bool bool_val;
+ ternary ternary_val;
int int_val;
double real_val;
int enum_val;
@@ -94,6 +96,12 @@ typedef struct relopt_bool
bool default_val;
} relopt_bool;
+typedef struct relopt_rernary
+{
+ relopt_gen gen;
+ int default_val;
+} relopt_ternary;
+
typedef struct relopt_int
{
relopt_gen gen;
@@ -152,19 +160,6 @@ typedef struct
const char *optname; /* option's name */
relopt_type opttype; /* option's datatype */
int offset; /* offset of field in result struct */
-
- /*
- * isset_offset is an optional offset of a field in the result struct that
- * stores whether the option is explicitly set for the relation or if it
- * just picked up the default value. In most cases, this can be
- * accomplished by giving the reloption a special out-of-range default
- * value (e.g., some integer reloptions use -2), but this isn't always
- * possible. For example, a Boolean reloption cannot be given an
- * out-of-range default, so we need another way to discover the source of
- * its value. This offset is only used if given a value greater than
- * zero.
- */
- int isset_offset;
} relopt_parse_elt;
/* Local reloption definition */
@@ -195,6 +190,8 @@ typedef struct local_relopts
extern relopt_kind add_reloption_kind(void);
extern void add_bool_reloption(bits32 kinds, const char *name, const char *desc,
bool default_val, LOCKMODE lockmode);
+extern void add_ternary_reloption(bits32 kinds, const char *name,
+ const char *desc, int default_val, LOCKMODE lockmode);
extern void add_int_reloption(bits32 kinds, const char *name, const char *desc,
int default_val, int min_val, int max_val,
LOCKMODE lockmode);
@@ -214,6 +211,9 @@ extern void register_reloptions_validator(local_relopts *relopts,
extern void add_local_bool_reloption(local_relopts *relopts, const char *name,
const char *desc, bool default_val,
int offset);
+extern void add_local_ternary_reloption(local_relopts *relopts,
+ const char *name, const char *desc,
+ ternary default_val, int offset);
extern void add_local_int_reloption(local_relopts *relopts, const char *name,
const char *desc, int default_val,
int min_val, int max_val, int offset);
diff --git a/src/include/c.h b/src/include/c.h
index 39022f8a9d..9c59ccccf8 100644
--- a/src/include/c.h
+++ b/src/include/c.h
@@ -488,6 +488,22 @@ typedef void (*pg_funcptr_t) (void);
#include <stdbool.h>
+/*
+ * ternary
+ * Boolean value with an extrea "unset" option
+ *
+ * Ternary data type is used in relation options that can be "true", "false" or
+ * "unset". Since relation options are used deep inside the PostgreSQL code,
+ * this type is declared globally.
+*/
+
+typedef enum ternary
+{
+ TERNARY_FALSE = 0,
+ TERNARY_TRUE = 1,
+ TERNARY_UNSET = -1
+} ternary;
+
/* ----------------------------------------------------------------
* Section 3: standard system types
diff --git a/src/include/utils/rel.h b/src/include/utils/rel.h
index b552359915..7346d618f9 100644
--- a/src/include/utils/rel.h
+++ b/src/include/utils/rel.h
@@ -346,8 +346,7 @@ typedef struct StdRdOptions
bool user_catalog_table; /* use as an additional catalog relation */
int parallel_workers; /* max number of parallel workers */
StdRdOptIndexCleanup vacuum_index_cleanup; /* controls index vacuuming */
- bool vacuum_truncate; /* enables vacuum to truncate a relation */
- bool vacuum_truncate_set; /* whether vacuum_truncate is set */
+ ternary vacuum_truncate; /* enables vacuum to truncate a relation */
/*
* Fraction of pages in a relation that vacuum can eagerly scan and fail
--
2.39.2
--nextPart4155600.3ZeAukHxDK
Content-Disposition: attachment;
filename="v2a-0003-Add-alias-to-be-used-as-unset-state.patch"
Content-Transfer-Encoding: 7Bit
Content-Type: text/x-patch; charset="unicode-2-0-utf-8";
name="v2a-0003-Add-alias-to-be-used-as-unset-state.patch"
^ permalink raw reply [nested|flat] 868+ messages in thread
* [PATCH v2a 2/4] Introduce ternary reloptions
@ 2025-09-04 14:41 Nikolay Shaplov <[email protected]>
0 siblings, 0 replies; 868+ messages in thread
From: Nikolay Shaplov @ 2025-09-04 14:41 UTC (permalink / raw)
Introduce ternary reloption as a replacement for current `vacuum_truncate`
implementation. Remove `vacuum_truncate_set` additional flag and using
`TERNARY_UNSET` value instead.
---
src/backend/access/common/reloptions.c | 129 ++++++++++++++++++++-----
src/backend/commands/vacuum.c | 4 +-
src/include/access/reloptions.h | 26 ++---
src/include/c.h | 16 +++
src/include/utils/rel.h | 3 +-
5 files changed, 135 insertions(+), 43 deletions(-)
diff --git a/src/backend/access/common/reloptions.c b/src/backend/access/common/reloptions.c
index 0af3fea68f..f543f11001 100644
--- a/src/backend/access/common/reloptions.c
+++ b/src/backend/access/common/reloptions.c
@@ -40,9 +40,9 @@
*
* To add an option:
*
- * (i) decide on a type (bool, integer, real, enum, string), name, default
- * value, upper and lower bounds (if applicable); for strings, consider a
- * validation routine.
+ * (i) decide on a type (bool, ternary, integer, real, enum, string), name,
+ * default value, upper and lower bounds (if applicable); for strings,
+ * consider a validation routine.
* (ii) add a record below (or use add_<type>_reloption).
* (iii) add it to the appropriate options struct (perhaps StdRdOptions)
* (iv) add it to the appropriate handling routine (perhaps
@@ -147,15 +147,6 @@ static relopt_bool boolRelOpts[] =
},
false
},
- {
- {
- "vacuum_truncate",
- "Enables vacuum to truncate empty pages at the end of this table",
- RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
- ShareUpdateExclusiveLock
- },
- true
- },
{
{
"deduplicate_items",
@@ -170,6 +161,21 @@ static relopt_bool boolRelOpts[] =
{{NULL}}
};
+static relopt_ternary ternaryRelOpts[] =
+{
+ {
+ {
+ "vacuum_truncate",
+ "Enables vacuum to truncate empty pages at the end of this table",
+ RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
+ ShareUpdateExclusiveLock
+ },
+ TERNARY_UNSET
+ },
+ /* list terminator */
+ {{NULL}}
+};
+
static relopt_int intRelOpts[] =
{
{
@@ -600,6 +606,13 @@ initialize_reloptions(void)
boolRelOpts[i].gen.lockmode));
j++;
}
+ for (i = 0; ternaryRelOpts[i].gen.name; i++)
+ {
+ Assert(DoLockModesConflict(ternaryRelOpts[i].gen.lockmode,
+ ternaryRelOpts[i].gen.lockmode));
+ j++;
+ }
+
for (i = 0; intRelOpts[i].gen.name; i++)
{
Assert(DoLockModesConflict(intRelOpts[i].gen.lockmode,
@@ -640,6 +653,14 @@ initialize_reloptions(void)
j++;
}
+ for (i = 0; ternaryRelOpts[i].gen.name; i++)
+ {
+ relOpts[j] = &ternaryRelOpts[i].gen;
+ relOpts[j]->type = RELOPT_TYPE_TERNARY;
+ relOpts[j]->namelen = strlen(relOpts[j]->name);
+ j++;
+ }
+
for (i = 0; intRelOpts[i].gen.name; i++)
{
relOpts[j] = &intRelOpts[i].gen;
@@ -800,6 +821,9 @@ allocate_reloption(bits32 kinds, int type, const char *name, const char *desc,
case RELOPT_TYPE_BOOL:
size = sizeof(relopt_bool);
break;
+ case RELOPT_TYPE_TERNARY:
+ size = sizeof(relopt_ternary);
+ break;
case RELOPT_TYPE_INT:
size = sizeof(relopt_int);
break;
@@ -883,6 +907,54 @@ add_local_bool_reloption(local_relopts *relopts, const char *name,
add_local_reloption(relopts, (relopt_gen *) newoption, offset);
}
+/*
+ * init_ternary_reloption
+ * Allocate and initialize a new ternary reloption
+ */
+static relopt_ternary *
+init_ternary_reloption(bits32 kinds, const char *name, const char *desc,
+ ternary default_val, LOCKMODE lockmode)
+{
+ relopt_ternary *newoption;
+
+ newoption = (relopt_ternary *) allocate_reloption(kinds,
+ RELOPT_TYPE_TERNARY, name, desc, lockmode);
+ newoption->default_val = default_val;
+
+ return newoption;
+}
+
+/*
+ * add_ternary_reloption
+ * Add a new ternary reloption
+ */
+void
+add_ternary_reloption(bits32 kinds, const char *name, const char *desc,
+ ternary default_val, LOCKMODE lockmode)
+{
+ relopt_ternary *newoption = init_ternary_reloption(kinds, name, desc,
+ default_val, lockmode);
+
+ add_reloption((relopt_gen *) newoption);
+}
+
+/*
+ * add_local_ternary_reloption
+ * Add a new ternary local reloption
+ *
+ * 'offset' is offset of ternary-typed field.
+ */
+void
+add_local_ternary_reloption(local_relopts *relopts, const char *name,
+ const char *desc, ternary default_val,
+ int offset)
+{
+ relopt_ternary *newoption = init_ternary_reloption(RELOPT_KIND_LOCAL,
+ name, desc,
+ default_val, 0);
+
+ add_local_reloption(relopts, (relopt_gen *) newoption, offset);
+}
/*
* init_real_reloption
@@ -1617,6 +1689,18 @@ parse_one_reloption(relopt_value *option, char *text_str, int text_len,
option->gen->name, value)));
}
break;
+ case RELOPT_TYPE_TERNARY:
+ {
+ bool b;
+ parsed = parse_bool(value, &b);
+ option->values.ternary_val = b ? TERNARY_TRUE : TERNARY_FALSE;
+ if (validate && !parsed)
+ ereport(ERROR,
+ (errcode(ERRCODE_INVALID_PARAMETER_VALUE),
+ errmsg("invalid value for ternary option \"%s\": %s",
+ option->gen->name, value)));
+ }
+ break;
case RELOPT_TYPE_INT:
{
relopt_int *optint = (relopt_int *) option->gen;
@@ -1780,17 +1864,6 @@ fillRelOptions(void *rdopts, Size basesize,
char *itempos = ((char *) rdopts) + elems[j].offset;
char *string_val;
- /*
- * If isset_offset is provided, store whether the reloption is
- * set there.
- */
- if (elems[j].isset_offset > 0)
- {
- char *setpos = ((char *) rdopts) + elems[j].isset_offset;
-
- *(bool *) setpos = options[i].isset;
- }
-
switch (options[i].gen->type)
{
case RELOPT_TYPE_BOOL:
@@ -1798,6 +1871,11 @@ fillRelOptions(void *rdopts, Size basesize,
options[i].values.bool_val :
((relopt_bool *) options[i].gen)->default_val;
break;
+ case RELOPT_TYPE_TERNARY:
+ *(ternary *) itempos = options[i].isset ?
+ options[i].values.ternary_val :
+ ((relopt_ternary *) options[i].gen)->default_val;
+ break;
case RELOPT_TYPE_INT:
*(int *) itempos = options[i].isset ?
options[i].values.int_val :
@@ -1912,8 +1990,8 @@ default_reloptions(Datum reloptions, bool validate, relopt_kind kind)
offsetof(StdRdOptions, parallel_workers)},
{"vacuum_index_cleanup", RELOPT_TYPE_ENUM,
offsetof(StdRdOptions, vacuum_index_cleanup)},
- {"vacuum_truncate", RELOPT_TYPE_BOOL,
- offsetof(StdRdOptions, vacuum_truncate), offsetof(StdRdOptions, vacuum_truncate_set)},
+ {"vacuum_truncate", RELOPT_TYPE_TERNARY,
+ offsetof(StdRdOptions, vacuum_truncate)},
{"vacuum_max_eager_freeze_failure_rate", RELOPT_TYPE_REAL,
offsetof(StdRdOptions, vacuum_max_eager_freeze_failure_rate)}
};
@@ -1993,7 +2071,6 @@ build_local_reloptions(local_relopts *relopts, Datum options, bool validate)
elems[i].optname = opt->option->name;
elems[i].opttype = opt->option->type;
elems[i].offset = opt->offset;
- elems[i].isset_offset = 0; /* not supported for local relopts yet */
i++;
}
diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c
index 733ef40ae7..977babff54 100644
--- a/src/backend/commands/vacuum.c
+++ b/src/backend/commands/vacuum.c
@@ -2221,9 +2221,9 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams params,
{
StdRdOptions *opts = (StdRdOptions *) rel->rd_options;
- if (opts && opts->vacuum_truncate_set)
+ if (opts && opts->vacuum_truncate != TERNARY_UNSET)
{
- if (opts->vacuum_truncate)
+ if (opts->vacuum_truncate == TERNARY_TRUE)
params.truncate = VACOPTVALUE_ENABLED;
else
params.truncate = VACOPTVALUE_DISABLED;
diff --git a/src/include/access/reloptions.h b/src/include/access/reloptions.h
index a604a4702c..a436697658 100644
--- a/src/include/access/reloptions.h
+++ b/src/include/access/reloptions.h
@@ -29,6 +29,7 @@
typedef enum relopt_type
{
RELOPT_TYPE_BOOL,
+ RELOPT_TYPE_TERNARY, /* on, off, unset */
RELOPT_TYPE_INT,
RELOPT_TYPE_REAL,
RELOPT_TYPE_ENUM,
@@ -80,6 +81,7 @@ typedef struct relopt_value
union
{
bool bool_val;
+ ternary ternary_val;
int int_val;
double real_val;
int enum_val;
@@ -94,6 +96,12 @@ typedef struct relopt_bool
bool default_val;
} relopt_bool;
+typedef struct relopt_rernary
+{
+ relopt_gen gen;
+ int default_val;
+} relopt_ternary;
+
typedef struct relopt_int
{
relopt_gen gen;
@@ -152,19 +160,6 @@ typedef struct
const char *optname; /* option's name */
relopt_type opttype; /* option's datatype */
int offset; /* offset of field in result struct */
-
- /*
- * isset_offset is an optional offset of a field in the result struct that
- * stores whether the option is explicitly set for the relation or if it
- * just picked up the default value. In most cases, this can be
- * accomplished by giving the reloption a special out-of-range default
- * value (e.g., some integer reloptions use -2), but this isn't always
- * possible. For example, a Boolean reloption cannot be given an
- * out-of-range default, so we need another way to discover the source of
- * its value. This offset is only used if given a value greater than
- * zero.
- */
- int isset_offset;
} relopt_parse_elt;
/* Local reloption definition */
@@ -195,6 +190,8 @@ typedef struct local_relopts
extern relopt_kind add_reloption_kind(void);
extern void add_bool_reloption(bits32 kinds, const char *name, const char *desc,
bool default_val, LOCKMODE lockmode);
+extern void add_ternary_reloption(bits32 kinds, const char *name,
+ const char *desc, int default_val, LOCKMODE lockmode);
extern void add_int_reloption(bits32 kinds, const char *name, const char *desc,
int default_val, int min_val, int max_val,
LOCKMODE lockmode);
@@ -214,6 +211,9 @@ extern void register_reloptions_validator(local_relopts *relopts,
extern void add_local_bool_reloption(local_relopts *relopts, const char *name,
const char *desc, bool default_val,
int offset);
+extern void add_local_ternary_reloption(local_relopts *relopts,
+ const char *name, const char *desc,
+ ternary default_val, int offset);
extern void add_local_int_reloption(local_relopts *relopts, const char *name,
const char *desc, int default_val,
int min_val, int max_val, int offset);
diff --git a/src/include/c.h b/src/include/c.h
index 39022f8a9d..9c59ccccf8 100644
--- a/src/include/c.h
+++ b/src/include/c.h
@@ -488,6 +488,22 @@ typedef void (*pg_funcptr_t) (void);
#include <stdbool.h>
+/*
+ * ternary
+ * Boolean value with an extrea "unset" option
+ *
+ * Ternary data type is used in relation options that can be "true", "false" or
+ * "unset". Since relation options are used deep inside the PostgreSQL code,
+ * this type is declared globally.
+*/
+
+typedef enum ternary
+{
+ TERNARY_FALSE = 0,
+ TERNARY_TRUE = 1,
+ TERNARY_UNSET = -1
+} ternary;
+
/* ----------------------------------------------------------------
* Section 3: standard system types
diff --git a/src/include/utils/rel.h b/src/include/utils/rel.h
index b552359915..7346d618f9 100644
--- a/src/include/utils/rel.h
+++ b/src/include/utils/rel.h
@@ -346,8 +346,7 @@ typedef struct StdRdOptions
bool user_catalog_table; /* use as an additional catalog relation */
int parallel_workers; /* max number of parallel workers */
StdRdOptIndexCleanup vacuum_index_cleanup; /* controls index vacuuming */
- bool vacuum_truncate; /* enables vacuum to truncate a relation */
- bool vacuum_truncate_set; /* whether vacuum_truncate is set */
+ ternary vacuum_truncate; /* enables vacuum to truncate a relation */
/*
* Fraction of pages in a relation that vacuum can eagerly scan and fail
--
2.39.2
--nextPart4155600.3ZeAukHxDK
Content-Disposition: attachment;
filename="v2a-0003-Add-alias-to-be-used-as-unset-state.patch"
Content-Transfer-Encoding: 7Bit
Content-Type: text/x-patch; charset="unicode-2-0-utf-8";
name="v2a-0003-Add-alias-to-be-used-as-unset-state.patch"
^ permalink raw reply [nested|flat] 868+ messages in thread
* [PATCH v2a 2/4] Introduce ternary reloptions
@ 2025-09-04 14:41 Nikolay Shaplov <[email protected]>
0 siblings, 0 replies; 868+ messages in thread
From: Nikolay Shaplov @ 2025-09-04 14:41 UTC (permalink / raw)
Introduce ternary reloption as a replacement for current `vacuum_truncate`
implementation. Remove `vacuum_truncate_set` additional flag and using
`TERNARY_UNSET` value instead.
---
src/backend/access/common/reloptions.c | 129 ++++++++++++++++++++-----
src/backend/commands/vacuum.c | 4 +-
src/include/access/reloptions.h | 26 ++---
src/include/c.h | 16 +++
src/include/utils/rel.h | 3 +-
5 files changed, 135 insertions(+), 43 deletions(-)
diff --git a/src/backend/access/common/reloptions.c b/src/backend/access/common/reloptions.c
index 0af3fea68f..f543f11001 100644
--- a/src/backend/access/common/reloptions.c
+++ b/src/backend/access/common/reloptions.c
@@ -40,9 +40,9 @@
*
* To add an option:
*
- * (i) decide on a type (bool, integer, real, enum, string), name, default
- * value, upper and lower bounds (if applicable); for strings, consider a
- * validation routine.
+ * (i) decide on a type (bool, ternary, integer, real, enum, string), name,
+ * default value, upper and lower bounds (if applicable); for strings,
+ * consider a validation routine.
* (ii) add a record below (or use add_<type>_reloption).
* (iii) add it to the appropriate options struct (perhaps StdRdOptions)
* (iv) add it to the appropriate handling routine (perhaps
@@ -147,15 +147,6 @@ static relopt_bool boolRelOpts[] =
},
false
},
- {
- {
- "vacuum_truncate",
- "Enables vacuum to truncate empty pages at the end of this table",
- RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
- ShareUpdateExclusiveLock
- },
- true
- },
{
{
"deduplicate_items",
@@ -170,6 +161,21 @@ static relopt_bool boolRelOpts[] =
{{NULL}}
};
+static relopt_ternary ternaryRelOpts[] =
+{
+ {
+ {
+ "vacuum_truncate",
+ "Enables vacuum to truncate empty pages at the end of this table",
+ RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
+ ShareUpdateExclusiveLock
+ },
+ TERNARY_UNSET
+ },
+ /* list terminator */
+ {{NULL}}
+};
+
static relopt_int intRelOpts[] =
{
{
@@ -600,6 +606,13 @@ initialize_reloptions(void)
boolRelOpts[i].gen.lockmode));
j++;
}
+ for (i = 0; ternaryRelOpts[i].gen.name; i++)
+ {
+ Assert(DoLockModesConflict(ternaryRelOpts[i].gen.lockmode,
+ ternaryRelOpts[i].gen.lockmode));
+ j++;
+ }
+
for (i = 0; intRelOpts[i].gen.name; i++)
{
Assert(DoLockModesConflict(intRelOpts[i].gen.lockmode,
@@ -640,6 +653,14 @@ initialize_reloptions(void)
j++;
}
+ for (i = 0; ternaryRelOpts[i].gen.name; i++)
+ {
+ relOpts[j] = &ternaryRelOpts[i].gen;
+ relOpts[j]->type = RELOPT_TYPE_TERNARY;
+ relOpts[j]->namelen = strlen(relOpts[j]->name);
+ j++;
+ }
+
for (i = 0; intRelOpts[i].gen.name; i++)
{
relOpts[j] = &intRelOpts[i].gen;
@@ -800,6 +821,9 @@ allocate_reloption(bits32 kinds, int type, const char *name, const char *desc,
case RELOPT_TYPE_BOOL:
size = sizeof(relopt_bool);
break;
+ case RELOPT_TYPE_TERNARY:
+ size = sizeof(relopt_ternary);
+ break;
case RELOPT_TYPE_INT:
size = sizeof(relopt_int);
break;
@@ -883,6 +907,54 @@ add_local_bool_reloption(local_relopts *relopts, const char *name,
add_local_reloption(relopts, (relopt_gen *) newoption, offset);
}
+/*
+ * init_ternary_reloption
+ * Allocate and initialize a new ternary reloption
+ */
+static relopt_ternary *
+init_ternary_reloption(bits32 kinds, const char *name, const char *desc,
+ ternary default_val, LOCKMODE lockmode)
+{
+ relopt_ternary *newoption;
+
+ newoption = (relopt_ternary *) allocate_reloption(kinds,
+ RELOPT_TYPE_TERNARY, name, desc, lockmode);
+ newoption->default_val = default_val;
+
+ return newoption;
+}
+
+/*
+ * add_ternary_reloption
+ * Add a new ternary reloption
+ */
+void
+add_ternary_reloption(bits32 kinds, const char *name, const char *desc,
+ ternary default_val, LOCKMODE lockmode)
+{
+ relopt_ternary *newoption = init_ternary_reloption(kinds, name, desc,
+ default_val, lockmode);
+
+ add_reloption((relopt_gen *) newoption);
+}
+
+/*
+ * add_local_ternary_reloption
+ * Add a new ternary local reloption
+ *
+ * 'offset' is offset of ternary-typed field.
+ */
+void
+add_local_ternary_reloption(local_relopts *relopts, const char *name,
+ const char *desc, ternary default_val,
+ int offset)
+{
+ relopt_ternary *newoption = init_ternary_reloption(RELOPT_KIND_LOCAL,
+ name, desc,
+ default_val, 0);
+
+ add_local_reloption(relopts, (relopt_gen *) newoption, offset);
+}
/*
* init_real_reloption
@@ -1617,6 +1689,18 @@ parse_one_reloption(relopt_value *option, char *text_str, int text_len,
option->gen->name, value)));
}
break;
+ case RELOPT_TYPE_TERNARY:
+ {
+ bool b;
+ parsed = parse_bool(value, &b);
+ option->values.ternary_val = b ? TERNARY_TRUE : TERNARY_FALSE;
+ if (validate && !parsed)
+ ereport(ERROR,
+ (errcode(ERRCODE_INVALID_PARAMETER_VALUE),
+ errmsg("invalid value for ternary option \"%s\": %s",
+ option->gen->name, value)));
+ }
+ break;
case RELOPT_TYPE_INT:
{
relopt_int *optint = (relopt_int *) option->gen;
@@ -1780,17 +1864,6 @@ fillRelOptions(void *rdopts, Size basesize,
char *itempos = ((char *) rdopts) + elems[j].offset;
char *string_val;
- /*
- * If isset_offset is provided, store whether the reloption is
- * set there.
- */
- if (elems[j].isset_offset > 0)
- {
- char *setpos = ((char *) rdopts) + elems[j].isset_offset;
-
- *(bool *) setpos = options[i].isset;
- }
-
switch (options[i].gen->type)
{
case RELOPT_TYPE_BOOL:
@@ -1798,6 +1871,11 @@ fillRelOptions(void *rdopts, Size basesize,
options[i].values.bool_val :
((relopt_bool *) options[i].gen)->default_val;
break;
+ case RELOPT_TYPE_TERNARY:
+ *(ternary *) itempos = options[i].isset ?
+ options[i].values.ternary_val :
+ ((relopt_ternary *) options[i].gen)->default_val;
+ break;
case RELOPT_TYPE_INT:
*(int *) itempos = options[i].isset ?
options[i].values.int_val :
@@ -1912,8 +1990,8 @@ default_reloptions(Datum reloptions, bool validate, relopt_kind kind)
offsetof(StdRdOptions, parallel_workers)},
{"vacuum_index_cleanup", RELOPT_TYPE_ENUM,
offsetof(StdRdOptions, vacuum_index_cleanup)},
- {"vacuum_truncate", RELOPT_TYPE_BOOL,
- offsetof(StdRdOptions, vacuum_truncate), offsetof(StdRdOptions, vacuum_truncate_set)},
+ {"vacuum_truncate", RELOPT_TYPE_TERNARY,
+ offsetof(StdRdOptions, vacuum_truncate)},
{"vacuum_max_eager_freeze_failure_rate", RELOPT_TYPE_REAL,
offsetof(StdRdOptions, vacuum_max_eager_freeze_failure_rate)}
};
@@ -1993,7 +2071,6 @@ build_local_reloptions(local_relopts *relopts, Datum options, bool validate)
elems[i].optname = opt->option->name;
elems[i].opttype = opt->option->type;
elems[i].offset = opt->offset;
- elems[i].isset_offset = 0; /* not supported for local relopts yet */
i++;
}
diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c
index 733ef40ae7..977babff54 100644
--- a/src/backend/commands/vacuum.c
+++ b/src/backend/commands/vacuum.c
@@ -2221,9 +2221,9 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams params,
{
StdRdOptions *opts = (StdRdOptions *) rel->rd_options;
- if (opts && opts->vacuum_truncate_set)
+ if (opts && opts->vacuum_truncate != TERNARY_UNSET)
{
- if (opts->vacuum_truncate)
+ if (opts->vacuum_truncate == TERNARY_TRUE)
params.truncate = VACOPTVALUE_ENABLED;
else
params.truncate = VACOPTVALUE_DISABLED;
diff --git a/src/include/access/reloptions.h b/src/include/access/reloptions.h
index a604a4702c..a436697658 100644
--- a/src/include/access/reloptions.h
+++ b/src/include/access/reloptions.h
@@ -29,6 +29,7 @@
typedef enum relopt_type
{
RELOPT_TYPE_BOOL,
+ RELOPT_TYPE_TERNARY, /* on, off, unset */
RELOPT_TYPE_INT,
RELOPT_TYPE_REAL,
RELOPT_TYPE_ENUM,
@@ -80,6 +81,7 @@ typedef struct relopt_value
union
{
bool bool_val;
+ ternary ternary_val;
int int_val;
double real_val;
int enum_val;
@@ -94,6 +96,12 @@ typedef struct relopt_bool
bool default_val;
} relopt_bool;
+typedef struct relopt_rernary
+{
+ relopt_gen gen;
+ int default_val;
+} relopt_ternary;
+
typedef struct relopt_int
{
relopt_gen gen;
@@ -152,19 +160,6 @@ typedef struct
const char *optname; /* option's name */
relopt_type opttype; /* option's datatype */
int offset; /* offset of field in result struct */
-
- /*
- * isset_offset is an optional offset of a field in the result struct that
- * stores whether the option is explicitly set for the relation or if it
- * just picked up the default value. In most cases, this can be
- * accomplished by giving the reloption a special out-of-range default
- * value (e.g., some integer reloptions use -2), but this isn't always
- * possible. For example, a Boolean reloption cannot be given an
- * out-of-range default, so we need another way to discover the source of
- * its value. This offset is only used if given a value greater than
- * zero.
- */
- int isset_offset;
} relopt_parse_elt;
/* Local reloption definition */
@@ -195,6 +190,8 @@ typedef struct local_relopts
extern relopt_kind add_reloption_kind(void);
extern void add_bool_reloption(bits32 kinds, const char *name, const char *desc,
bool default_val, LOCKMODE lockmode);
+extern void add_ternary_reloption(bits32 kinds, const char *name,
+ const char *desc, int default_val, LOCKMODE lockmode);
extern void add_int_reloption(bits32 kinds, const char *name, const char *desc,
int default_val, int min_val, int max_val,
LOCKMODE lockmode);
@@ -214,6 +211,9 @@ extern void register_reloptions_validator(local_relopts *relopts,
extern void add_local_bool_reloption(local_relopts *relopts, const char *name,
const char *desc, bool default_val,
int offset);
+extern void add_local_ternary_reloption(local_relopts *relopts,
+ const char *name, const char *desc,
+ ternary default_val, int offset);
extern void add_local_int_reloption(local_relopts *relopts, const char *name,
const char *desc, int default_val,
int min_val, int max_val, int offset);
diff --git a/src/include/c.h b/src/include/c.h
index 39022f8a9d..9c59ccccf8 100644
--- a/src/include/c.h
+++ b/src/include/c.h
@@ -488,6 +488,22 @@ typedef void (*pg_funcptr_t) (void);
#include <stdbool.h>
+/*
+ * ternary
+ * Boolean value with an extrea "unset" option
+ *
+ * Ternary data type is used in relation options that can be "true", "false" or
+ * "unset". Since relation options are used deep inside the PostgreSQL code,
+ * this type is declared globally.
+*/
+
+typedef enum ternary
+{
+ TERNARY_FALSE = 0,
+ TERNARY_TRUE = 1,
+ TERNARY_UNSET = -1
+} ternary;
+
/* ----------------------------------------------------------------
* Section 3: standard system types
diff --git a/src/include/utils/rel.h b/src/include/utils/rel.h
index b552359915..7346d618f9 100644
--- a/src/include/utils/rel.h
+++ b/src/include/utils/rel.h
@@ -346,8 +346,7 @@ typedef struct StdRdOptions
bool user_catalog_table; /* use as an additional catalog relation */
int parallel_workers; /* max number of parallel workers */
StdRdOptIndexCleanup vacuum_index_cleanup; /* controls index vacuuming */
- bool vacuum_truncate; /* enables vacuum to truncate a relation */
- bool vacuum_truncate_set; /* whether vacuum_truncate is set */
+ ternary vacuum_truncate; /* enables vacuum to truncate a relation */
/*
* Fraction of pages in a relation that vacuum can eagerly scan and fail
--
2.39.2
--nextPart4155600.3ZeAukHxDK
Content-Disposition: attachment;
filename="v2a-0003-Add-alias-to-be-used-as-unset-state.patch"
Content-Transfer-Encoding: 7Bit
Content-Type: text/x-patch; charset="unicode-2-0-utf-8";
name="v2a-0003-Add-alias-to-be-used-as-unset-state.patch"
^ permalink raw reply [nested|flat] 868+ messages in thread
* [PATCH v2a 2/4] Introduce ternary reloptions
@ 2025-09-04 14:41 Nikolay Shaplov <[email protected]>
0 siblings, 0 replies; 868+ messages in thread
From: Nikolay Shaplov @ 2025-09-04 14:41 UTC (permalink / raw)
Introduce ternary reloption as a replacement for current `vacuum_truncate`
implementation. Remove `vacuum_truncate_set` additional flag and using
`TERNARY_UNSET` value instead.
---
src/backend/access/common/reloptions.c | 129 ++++++++++++++++++++-----
src/backend/commands/vacuum.c | 4 +-
src/include/access/reloptions.h | 26 ++---
src/include/c.h | 16 +++
src/include/utils/rel.h | 3 +-
5 files changed, 135 insertions(+), 43 deletions(-)
diff --git a/src/backend/access/common/reloptions.c b/src/backend/access/common/reloptions.c
index 0af3fea68f..f543f11001 100644
--- a/src/backend/access/common/reloptions.c
+++ b/src/backend/access/common/reloptions.c
@@ -40,9 +40,9 @@
*
* To add an option:
*
- * (i) decide on a type (bool, integer, real, enum, string), name, default
- * value, upper and lower bounds (if applicable); for strings, consider a
- * validation routine.
+ * (i) decide on a type (bool, ternary, integer, real, enum, string), name,
+ * default value, upper and lower bounds (if applicable); for strings,
+ * consider a validation routine.
* (ii) add a record below (or use add_<type>_reloption).
* (iii) add it to the appropriate options struct (perhaps StdRdOptions)
* (iv) add it to the appropriate handling routine (perhaps
@@ -147,15 +147,6 @@ static relopt_bool boolRelOpts[] =
},
false
},
- {
- {
- "vacuum_truncate",
- "Enables vacuum to truncate empty pages at the end of this table",
- RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
- ShareUpdateExclusiveLock
- },
- true
- },
{
{
"deduplicate_items",
@@ -170,6 +161,21 @@ static relopt_bool boolRelOpts[] =
{{NULL}}
};
+static relopt_ternary ternaryRelOpts[] =
+{
+ {
+ {
+ "vacuum_truncate",
+ "Enables vacuum to truncate empty pages at the end of this table",
+ RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
+ ShareUpdateExclusiveLock
+ },
+ TERNARY_UNSET
+ },
+ /* list terminator */
+ {{NULL}}
+};
+
static relopt_int intRelOpts[] =
{
{
@@ -600,6 +606,13 @@ initialize_reloptions(void)
boolRelOpts[i].gen.lockmode));
j++;
}
+ for (i = 0; ternaryRelOpts[i].gen.name; i++)
+ {
+ Assert(DoLockModesConflict(ternaryRelOpts[i].gen.lockmode,
+ ternaryRelOpts[i].gen.lockmode));
+ j++;
+ }
+
for (i = 0; intRelOpts[i].gen.name; i++)
{
Assert(DoLockModesConflict(intRelOpts[i].gen.lockmode,
@@ -640,6 +653,14 @@ initialize_reloptions(void)
j++;
}
+ for (i = 0; ternaryRelOpts[i].gen.name; i++)
+ {
+ relOpts[j] = &ternaryRelOpts[i].gen;
+ relOpts[j]->type = RELOPT_TYPE_TERNARY;
+ relOpts[j]->namelen = strlen(relOpts[j]->name);
+ j++;
+ }
+
for (i = 0; intRelOpts[i].gen.name; i++)
{
relOpts[j] = &intRelOpts[i].gen;
@@ -800,6 +821,9 @@ allocate_reloption(bits32 kinds, int type, const char *name, const char *desc,
case RELOPT_TYPE_BOOL:
size = sizeof(relopt_bool);
break;
+ case RELOPT_TYPE_TERNARY:
+ size = sizeof(relopt_ternary);
+ break;
case RELOPT_TYPE_INT:
size = sizeof(relopt_int);
break;
@@ -883,6 +907,54 @@ add_local_bool_reloption(local_relopts *relopts, const char *name,
add_local_reloption(relopts, (relopt_gen *) newoption, offset);
}
+/*
+ * init_ternary_reloption
+ * Allocate and initialize a new ternary reloption
+ */
+static relopt_ternary *
+init_ternary_reloption(bits32 kinds, const char *name, const char *desc,
+ ternary default_val, LOCKMODE lockmode)
+{
+ relopt_ternary *newoption;
+
+ newoption = (relopt_ternary *) allocate_reloption(kinds,
+ RELOPT_TYPE_TERNARY, name, desc, lockmode);
+ newoption->default_val = default_val;
+
+ return newoption;
+}
+
+/*
+ * add_ternary_reloption
+ * Add a new ternary reloption
+ */
+void
+add_ternary_reloption(bits32 kinds, const char *name, const char *desc,
+ ternary default_val, LOCKMODE lockmode)
+{
+ relopt_ternary *newoption = init_ternary_reloption(kinds, name, desc,
+ default_val, lockmode);
+
+ add_reloption((relopt_gen *) newoption);
+}
+
+/*
+ * add_local_ternary_reloption
+ * Add a new ternary local reloption
+ *
+ * 'offset' is offset of ternary-typed field.
+ */
+void
+add_local_ternary_reloption(local_relopts *relopts, const char *name,
+ const char *desc, ternary default_val,
+ int offset)
+{
+ relopt_ternary *newoption = init_ternary_reloption(RELOPT_KIND_LOCAL,
+ name, desc,
+ default_val, 0);
+
+ add_local_reloption(relopts, (relopt_gen *) newoption, offset);
+}
/*
* init_real_reloption
@@ -1617,6 +1689,18 @@ parse_one_reloption(relopt_value *option, char *text_str, int text_len,
option->gen->name, value)));
}
break;
+ case RELOPT_TYPE_TERNARY:
+ {
+ bool b;
+ parsed = parse_bool(value, &b);
+ option->values.ternary_val = b ? TERNARY_TRUE : TERNARY_FALSE;
+ if (validate && !parsed)
+ ereport(ERROR,
+ (errcode(ERRCODE_INVALID_PARAMETER_VALUE),
+ errmsg("invalid value for ternary option \"%s\": %s",
+ option->gen->name, value)));
+ }
+ break;
case RELOPT_TYPE_INT:
{
relopt_int *optint = (relopt_int *) option->gen;
@@ -1780,17 +1864,6 @@ fillRelOptions(void *rdopts, Size basesize,
char *itempos = ((char *) rdopts) + elems[j].offset;
char *string_val;
- /*
- * If isset_offset is provided, store whether the reloption is
- * set there.
- */
- if (elems[j].isset_offset > 0)
- {
- char *setpos = ((char *) rdopts) + elems[j].isset_offset;
-
- *(bool *) setpos = options[i].isset;
- }
-
switch (options[i].gen->type)
{
case RELOPT_TYPE_BOOL:
@@ -1798,6 +1871,11 @@ fillRelOptions(void *rdopts, Size basesize,
options[i].values.bool_val :
((relopt_bool *) options[i].gen)->default_val;
break;
+ case RELOPT_TYPE_TERNARY:
+ *(ternary *) itempos = options[i].isset ?
+ options[i].values.ternary_val :
+ ((relopt_ternary *) options[i].gen)->default_val;
+ break;
case RELOPT_TYPE_INT:
*(int *) itempos = options[i].isset ?
options[i].values.int_val :
@@ -1912,8 +1990,8 @@ default_reloptions(Datum reloptions, bool validate, relopt_kind kind)
offsetof(StdRdOptions, parallel_workers)},
{"vacuum_index_cleanup", RELOPT_TYPE_ENUM,
offsetof(StdRdOptions, vacuum_index_cleanup)},
- {"vacuum_truncate", RELOPT_TYPE_BOOL,
- offsetof(StdRdOptions, vacuum_truncate), offsetof(StdRdOptions, vacuum_truncate_set)},
+ {"vacuum_truncate", RELOPT_TYPE_TERNARY,
+ offsetof(StdRdOptions, vacuum_truncate)},
{"vacuum_max_eager_freeze_failure_rate", RELOPT_TYPE_REAL,
offsetof(StdRdOptions, vacuum_max_eager_freeze_failure_rate)}
};
@@ -1993,7 +2071,6 @@ build_local_reloptions(local_relopts *relopts, Datum options, bool validate)
elems[i].optname = opt->option->name;
elems[i].opttype = opt->option->type;
elems[i].offset = opt->offset;
- elems[i].isset_offset = 0; /* not supported for local relopts yet */
i++;
}
diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c
index 733ef40ae7..977babff54 100644
--- a/src/backend/commands/vacuum.c
+++ b/src/backend/commands/vacuum.c
@@ -2221,9 +2221,9 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams params,
{
StdRdOptions *opts = (StdRdOptions *) rel->rd_options;
- if (opts && opts->vacuum_truncate_set)
+ if (opts && opts->vacuum_truncate != TERNARY_UNSET)
{
- if (opts->vacuum_truncate)
+ if (opts->vacuum_truncate == TERNARY_TRUE)
params.truncate = VACOPTVALUE_ENABLED;
else
params.truncate = VACOPTVALUE_DISABLED;
diff --git a/src/include/access/reloptions.h b/src/include/access/reloptions.h
index a604a4702c..a436697658 100644
--- a/src/include/access/reloptions.h
+++ b/src/include/access/reloptions.h
@@ -29,6 +29,7 @@
typedef enum relopt_type
{
RELOPT_TYPE_BOOL,
+ RELOPT_TYPE_TERNARY, /* on, off, unset */
RELOPT_TYPE_INT,
RELOPT_TYPE_REAL,
RELOPT_TYPE_ENUM,
@@ -80,6 +81,7 @@ typedef struct relopt_value
union
{
bool bool_val;
+ ternary ternary_val;
int int_val;
double real_val;
int enum_val;
@@ -94,6 +96,12 @@ typedef struct relopt_bool
bool default_val;
} relopt_bool;
+typedef struct relopt_rernary
+{
+ relopt_gen gen;
+ int default_val;
+} relopt_ternary;
+
typedef struct relopt_int
{
relopt_gen gen;
@@ -152,19 +160,6 @@ typedef struct
const char *optname; /* option's name */
relopt_type opttype; /* option's datatype */
int offset; /* offset of field in result struct */
-
- /*
- * isset_offset is an optional offset of a field in the result struct that
- * stores whether the option is explicitly set for the relation or if it
- * just picked up the default value. In most cases, this can be
- * accomplished by giving the reloption a special out-of-range default
- * value (e.g., some integer reloptions use -2), but this isn't always
- * possible. For example, a Boolean reloption cannot be given an
- * out-of-range default, so we need another way to discover the source of
- * its value. This offset is only used if given a value greater than
- * zero.
- */
- int isset_offset;
} relopt_parse_elt;
/* Local reloption definition */
@@ -195,6 +190,8 @@ typedef struct local_relopts
extern relopt_kind add_reloption_kind(void);
extern void add_bool_reloption(bits32 kinds, const char *name, const char *desc,
bool default_val, LOCKMODE lockmode);
+extern void add_ternary_reloption(bits32 kinds, const char *name,
+ const char *desc, int default_val, LOCKMODE lockmode);
extern void add_int_reloption(bits32 kinds, const char *name, const char *desc,
int default_val, int min_val, int max_val,
LOCKMODE lockmode);
@@ -214,6 +211,9 @@ extern void register_reloptions_validator(local_relopts *relopts,
extern void add_local_bool_reloption(local_relopts *relopts, const char *name,
const char *desc, bool default_val,
int offset);
+extern void add_local_ternary_reloption(local_relopts *relopts,
+ const char *name, const char *desc,
+ ternary default_val, int offset);
extern void add_local_int_reloption(local_relopts *relopts, const char *name,
const char *desc, int default_val,
int min_val, int max_val, int offset);
diff --git a/src/include/c.h b/src/include/c.h
index 39022f8a9d..9c59ccccf8 100644
--- a/src/include/c.h
+++ b/src/include/c.h
@@ -488,6 +488,22 @@ typedef void (*pg_funcptr_t) (void);
#include <stdbool.h>
+/*
+ * ternary
+ * Boolean value with an extrea "unset" option
+ *
+ * Ternary data type is used in relation options that can be "true", "false" or
+ * "unset". Since relation options are used deep inside the PostgreSQL code,
+ * this type is declared globally.
+*/
+
+typedef enum ternary
+{
+ TERNARY_FALSE = 0,
+ TERNARY_TRUE = 1,
+ TERNARY_UNSET = -1
+} ternary;
+
/* ----------------------------------------------------------------
* Section 3: standard system types
diff --git a/src/include/utils/rel.h b/src/include/utils/rel.h
index b552359915..7346d618f9 100644
--- a/src/include/utils/rel.h
+++ b/src/include/utils/rel.h
@@ -346,8 +346,7 @@ typedef struct StdRdOptions
bool user_catalog_table; /* use as an additional catalog relation */
int parallel_workers; /* max number of parallel workers */
StdRdOptIndexCleanup vacuum_index_cleanup; /* controls index vacuuming */
- bool vacuum_truncate; /* enables vacuum to truncate a relation */
- bool vacuum_truncate_set; /* whether vacuum_truncate is set */
+ ternary vacuum_truncate; /* enables vacuum to truncate a relation */
/*
* Fraction of pages in a relation that vacuum can eagerly scan and fail
--
2.39.2
--nextPart4155600.3ZeAukHxDK
Content-Disposition: attachment;
filename="v2a-0003-Add-alias-to-be-used-as-unset-state.patch"
Content-Transfer-Encoding: 7Bit
Content-Type: text/x-patch; charset="unicode-2-0-utf-8";
name="v2a-0003-Add-alias-to-be-used-as-unset-state.patch"
^ permalink raw reply [nested|flat] 868+ messages in thread
* [PATCH v2 2/4] Introduce ternary reloptions
@ 2025-09-04 14:41 Nikolay Shaplov <[email protected]>
0 siblings, 0 replies; 868+ messages in thread
From: Nikolay Shaplov @ 2025-09-04 14:41 UTC (permalink / raw)
Introduce ternary reloption as a replacement for current `vacuum_truncate`
implementation. Remove `vacuum_truncate_set` additional flag and using
`TERNARY_UNSET` value instead.
---
src/backend/access/common/reloptions.c | 129 ++++++++++++++++++++-----
src/backend/commands/vacuum.c | 4 +-
src/include/access/reloptions.h | 26 ++---
src/include/c.h | 16 +++
src/include/utils/rel.h | 3 +-
5 files changed, 135 insertions(+), 43 deletions(-)
diff --git a/src/backend/access/common/reloptions.c b/src/backend/access/common/reloptions.c
index 0af3fea68f..f543f11001 100644
--- a/src/backend/access/common/reloptions.c
+++ b/src/backend/access/common/reloptions.c
@@ -40,9 +40,9 @@
*
* To add an option:
*
- * (i) decide on a type (bool, integer, real, enum, string), name, default
- * value, upper and lower bounds (if applicable); for strings, consider a
- * validation routine.
+ * (i) decide on a type (bool, ternary, integer, real, enum, string), name,
+ * default value, upper and lower bounds (if applicable); for strings,
+ * consider a validation routine.
* (ii) add a record below (or use add_<type>_reloption).
* (iii) add it to the appropriate options struct (perhaps StdRdOptions)
* (iv) add it to the appropriate handling routine (perhaps
@@ -147,15 +147,6 @@ static relopt_bool boolRelOpts[] =
},
false
},
- {
- {
- "vacuum_truncate",
- "Enables vacuum to truncate empty pages at the end of this table",
- RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
- ShareUpdateExclusiveLock
- },
- true
- },
{
{
"deduplicate_items",
@@ -170,6 +161,21 @@ static relopt_bool boolRelOpts[] =
{{NULL}}
};
+static relopt_ternary ternaryRelOpts[] =
+{
+ {
+ {
+ "vacuum_truncate",
+ "Enables vacuum to truncate empty pages at the end of this table",
+ RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
+ ShareUpdateExclusiveLock
+ },
+ TERNARY_UNSET
+ },
+ /* list terminator */
+ {{NULL}}
+};
+
static relopt_int intRelOpts[] =
{
{
@@ -600,6 +606,13 @@ initialize_reloptions(void)
boolRelOpts[i].gen.lockmode));
j++;
}
+ for (i = 0; ternaryRelOpts[i].gen.name; i++)
+ {
+ Assert(DoLockModesConflict(ternaryRelOpts[i].gen.lockmode,
+ ternaryRelOpts[i].gen.lockmode));
+ j++;
+ }
+
for (i = 0; intRelOpts[i].gen.name; i++)
{
Assert(DoLockModesConflict(intRelOpts[i].gen.lockmode,
@@ -640,6 +653,14 @@ initialize_reloptions(void)
j++;
}
+ for (i = 0; ternaryRelOpts[i].gen.name; i++)
+ {
+ relOpts[j] = &ternaryRelOpts[i].gen;
+ relOpts[j]->type = RELOPT_TYPE_TERNARY;
+ relOpts[j]->namelen = strlen(relOpts[j]->name);
+ j++;
+ }
+
for (i = 0; intRelOpts[i].gen.name; i++)
{
relOpts[j] = &intRelOpts[i].gen;
@@ -800,6 +821,9 @@ allocate_reloption(bits32 kinds, int type, const char *name, const char *desc,
case RELOPT_TYPE_BOOL:
size = sizeof(relopt_bool);
break;
+ case RELOPT_TYPE_TERNARY:
+ size = sizeof(relopt_ternary);
+ break;
case RELOPT_TYPE_INT:
size = sizeof(relopt_int);
break;
@@ -883,6 +907,54 @@ add_local_bool_reloption(local_relopts *relopts, const char *name,
add_local_reloption(relopts, (relopt_gen *) newoption, offset);
}
+/*
+ * init_ternary_reloption
+ * Allocate and initialize a new ternary reloption
+ */
+static relopt_ternary *
+init_ternary_reloption(bits32 kinds, const char *name, const char *desc,
+ ternary default_val, LOCKMODE lockmode)
+{
+ relopt_ternary *newoption;
+
+ newoption = (relopt_ternary *) allocate_reloption(kinds,
+ RELOPT_TYPE_TERNARY, name, desc, lockmode);
+ newoption->default_val = default_val;
+
+ return newoption;
+}
+
+/*
+ * add_ternary_reloption
+ * Add a new ternary reloption
+ */
+void
+add_ternary_reloption(bits32 kinds, const char *name, const char *desc,
+ ternary default_val, LOCKMODE lockmode)
+{
+ relopt_ternary *newoption = init_ternary_reloption(kinds, name, desc,
+ default_val, lockmode);
+
+ add_reloption((relopt_gen *) newoption);
+}
+
+/*
+ * add_local_ternary_reloption
+ * Add a new ternary local reloption
+ *
+ * 'offset' is offset of ternary-typed field.
+ */
+void
+add_local_ternary_reloption(local_relopts *relopts, const char *name,
+ const char *desc, ternary default_val,
+ int offset)
+{
+ relopt_ternary *newoption = init_ternary_reloption(RELOPT_KIND_LOCAL,
+ name, desc,
+ default_val, 0);
+
+ add_local_reloption(relopts, (relopt_gen *) newoption, offset);
+}
/*
* init_real_reloption
@@ -1617,6 +1689,18 @@ parse_one_reloption(relopt_value *option, char *text_str, int text_len,
option->gen->name, value)));
}
break;
+ case RELOPT_TYPE_TERNARY:
+ {
+ bool b;
+ parsed = parse_bool(value, &b);
+ option->values.ternary_val = b ? TERNARY_TRUE : TERNARY_FALSE;
+ if (validate && !parsed)
+ ereport(ERROR,
+ (errcode(ERRCODE_INVALID_PARAMETER_VALUE),
+ errmsg("invalid value for ternary option \"%s\": %s",
+ option->gen->name, value)));
+ }
+ break;
case RELOPT_TYPE_INT:
{
relopt_int *optint = (relopt_int *) option->gen;
@@ -1780,17 +1864,6 @@ fillRelOptions(void *rdopts, Size basesize,
char *itempos = ((char *) rdopts) + elems[j].offset;
char *string_val;
- /*
- * If isset_offset is provided, store whether the reloption is
- * set there.
- */
- if (elems[j].isset_offset > 0)
- {
- char *setpos = ((char *) rdopts) + elems[j].isset_offset;
-
- *(bool *) setpos = options[i].isset;
- }
-
switch (options[i].gen->type)
{
case RELOPT_TYPE_BOOL:
@@ -1798,6 +1871,11 @@ fillRelOptions(void *rdopts, Size basesize,
options[i].values.bool_val :
((relopt_bool *) options[i].gen)->default_val;
break;
+ case RELOPT_TYPE_TERNARY:
+ *(ternary *) itempos = options[i].isset ?
+ options[i].values.ternary_val :
+ ((relopt_ternary *) options[i].gen)->default_val;
+ break;
case RELOPT_TYPE_INT:
*(int *) itempos = options[i].isset ?
options[i].values.int_val :
@@ -1912,8 +1990,8 @@ default_reloptions(Datum reloptions, bool validate, relopt_kind kind)
offsetof(StdRdOptions, parallel_workers)},
{"vacuum_index_cleanup", RELOPT_TYPE_ENUM,
offsetof(StdRdOptions, vacuum_index_cleanup)},
- {"vacuum_truncate", RELOPT_TYPE_BOOL,
- offsetof(StdRdOptions, vacuum_truncate), offsetof(StdRdOptions, vacuum_truncate_set)},
+ {"vacuum_truncate", RELOPT_TYPE_TERNARY,
+ offsetof(StdRdOptions, vacuum_truncate)},
{"vacuum_max_eager_freeze_failure_rate", RELOPT_TYPE_REAL,
offsetof(StdRdOptions, vacuum_max_eager_freeze_failure_rate)}
};
@@ -1993,7 +2071,6 @@ build_local_reloptions(local_relopts *relopts, Datum options, bool validate)
elems[i].optname = opt->option->name;
elems[i].opttype = opt->option->type;
elems[i].offset = opt->offset;
- elems[i].isset_offset = 0; /* not supported for local relopts yet */
i++;
}
diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c
index 733ef40ae7..977babff54 100644
--- a/src/backend/commands/vacuum.c
+++ b/src/backend/commands/vacuum.c
@@ -2221,9 +2221,9 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams params,
{
StdRdOptions *opts = (StdRdOptions *) rel->rd_options;
- if (opts && opts->vacuum_truncate_set)
+ if (opts && opts->vacuum_truncate != TERNARY_UNSET)
{
- if (opts->vacuum_truncate)
+ if (opts->vacuum_truncate == TERNARY_TRUE)
params.truncate = VACOPTVALUE_ENABLED;
else
params.truncate = VACOPTVALUE_DISABLED;
diff --git a/src/include/access/reloptions.h b/src/include/access/reloptions.h
index a604a4702c..a436697658 100644
--- a/src/include/access/reloptions.h
+++ b/src/include/access/reloptions.h
@@ -29,6 +29,7 @@
typedef enum relopt_type
{
RELOPT_TYPE_BOOL,
+ RELOPT_TYPE_TERNARY, /* on, off, unset */
RELOPT_TYPE_INT,
RELOPT_TYPE_REAL,
RELOPT_TYPE_ENUM,
@@ -80,6 +81,7 @@ typedef struct relopt_value
union
{
bool bool_val;
+ ternary ternary_val;
int int_val;
double real_val;
int enum_val;
@@ -94,6 +96,12 @@ typedef struct relopt_bool
bool default_val;
} relopt_bool;
+typedef struct relopt_rernary
+{
+ relopt_gen gen;
+ int default_val;
+} relopt_ternary;
+
typedef struct relopt_int
{
relopt_gen gen;
@@ -152,19 +160,6 @@ typedef struct
const char *optname; /* option's name */
relopt_type opttype; /* option's datatype */
int offset; /* offset of field in result struct */
-
- /*
- * isset_offset is an optional offset of a field in the result struct that
- * stores whether the option is explicitly set for the relation or if it
- * just picked up the default value. In most cases, this can be
- * accomplished by giving the reloption a special out-of-range default
- * value (e.g., some integer reloptions use -2), but this isn't always
- * possible. For example, a Boolean reloption cannot be given an
- * out-of-range default, so we need another way to discover the source of
- * its value. This offset is only used if given a value greater than
- * zero.
- */
- int isset_offset;
} relopt_parse_elt;
/* Local reloption definition */
@@ -195,6 +190,8 @@ typedef struct local_relopts
extern relopt_kind add_reloption_kind(void);
extern void add_bool_reloption(bits32 kinds, const char *name, const char *desc,
bool default_val, LOCKMODE lockmode);
+extern void add_ternary_reloption(bits32 kinds, const char *name,
+ const char *desc, int default_val, LOCKMODE lockmode);
extern void add_int_reloption(bits32 kinds, const char *name, const char *desc,
int default_val, int min_val, int max_val,
LOCKMODE lockmode);
@@ -214,6 +211,9 @@ extern void register_reloptions_validator(local_relopts *relopts,
extern void add_local_bool_reloption(local_relopts *relopts, const char *name,
const char *desc, bool default_val,
int offset);
+extern void add_local_ternary_reloption(local_relopts *relopts,
+ const char *name, const char *desc,
+ ternary default_val, int offset);
extern void add_local_int_reloption(local_relopts *relopts, const char *name,
const char *desc, int default_val,
int min_val, int max_val, int offset);
diff --git a/src/include/c.h b/src/include/c.h
index 39022f8a9d..9c59ccccf8 100644
--- a/src/include/c.h
+++ b/src/include/c.h
@@ -488,6 +488,22 @@ typedef void (*pg_funcptr_t) (void);
#include <stdbool.h>
+/*
+ * ternary
+ * Boolean value with an extrea "unset" option
+ *
+ * Ternary data type is used in relation options that can be "true", "false" or
+ * "unset". Since relation options are used deep inside the PostgreSQL code,
+ * this type is declared globally.
+*/
+
+typedef enum ternary
+{
+ TERNARY_FALSE = 0,
+ TERNARY_TRUE = 1,
+ TERNARY_UNSET = -1
+} ternary;
+
/* ----------------------------------------------------------------
* Section 3: standard system types
diff --git a/src/include/utils/rel.h b/src/include/utils/rel.h
index b552359915..7346d618f9 100644
--- a/src/include/utils/rel.h
+++ b/src/include/utils/rel.h
@@ -346,8 +346,7 @@ typedef struct StdRdOptions
bool user_catalog_table; /* use as an additional catalog relation */
int parallel_workers; /* max number of parallel workers */
StdRdOptIndexCleanup vacuum_index_cleanup; /* controls index vacuuming */
- bool vacuum_truncate; /* enables vacuum to truncate a relation */
- bool vacuum_truncate_set; /* whether vacuum_truncate is set */
+ ternary vacuum_truncate; /* enables vacuum to truncate a relation */
/*
* Fraction of pages in a relation that vacuum can eagerly scan and fail
--
2.39.2
--nextPart5129932.5fSG56mABF
Content-Disposition: attachment;
filename="v2-0003-Add-alias-to-be-used-as-unset-state.patch"
Content-Transfer-Encoding: 7Bit
Content-Type: text/x-patch; charset="unicode-2-0-utf-8";
name="v2-0003-Add-alias-to-be-used-as-unset-state.patch"
^ permalink raw reply [nested|flat] 868+ messages in thread
* [PATCH v2a 2/4] Introduce ternary reloptions
@ 2025-09-04 14:41 Nikolay Shaplov <[email protected]>
0 siblings, 0 replies; 868+ messages in thread
From: Nikolay Shaplov @ 2025-09-04 14:41 UTC (permalink / raw)
Introduce ternary reloption as a replacement for current `vacuum_truncate`
implementation. Remove `vacuum_truncate_set` additional flag and using
`TERNARY_UNSET` value instead.
---
src/backend/access/common/reloptions.c | 129 ++++++++++++++++++++-----
src/backend/commands/vacuum.c | 4 +-
src/include/access/reloptions.h | 26 ++---
src/include/c.h | 16 +++
src/include/utils/rel.h | 3 +-
5 files changed, 135 insertions(+), 43 deletions(-)
diff --git a/src/backend/access/common/reloptions.c b/src/backend/access/common/reloptions.c
index 0af3fea68f..f543f11001 100644
--- a/src/backend/access/common/reloptions.c
+++ b/src/backend/access/common/reloptions.c
@@ -40,9 +40,9 @@
*
* To add an option:
*
- * (i) decide on a type (bool, integer, real, enum, string), name, default
- * value, upper and lower bounds (if applicable); for strings, consider a
- * validation routine.
+ * (i) decide on a type (bool, ternary, integer, real, enum, string), name,
+ * default value, upper and lower bounds (if applicable); for strings,
+ * consider a validation routine.
* (ii) add a record below (or use add_<type>_reloption).
* (iii) add it to the appropriate options struct (perhaps StdRdOptions)
* (iv) add it to the appropriate handling routine (perhaps
@@ -147,15 +147,6 @@ static relopt_bool boolRelOpts[] =
},
false
},
- {
- {
- "vacuum_truncate",
- "Enables vacuum to truncate empty pages at the end of this table",
- RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
- ShareUpdateExclusiveLock
- },
- true
- },
{
{
"deduplicate_items",
@@ -170,6 +161,21 @@ static relopt_bool boolRelOpts[] =
{{NULL}}
};
+static relopt_ternary ternaryRelOpts[] =
+{
+ {
+ {
+ "vacuum_truncate",
+ "Enables vacuum to truncate empty pages at the end of this table",
+ RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
+ ShareUpdateExclusiveLock
+ },
+ TERNARY_UNSET
+ },
+ /* list terminator */
+ {{NULL}}
+};
+
static relopt_int intRelOpts[] =
{
{
@@ -600,6 +606,13 @@ initialize_reloptions(void)
boolRelOpts[i].gen.lockmode));
j++;
}
+ for (i = 0; ternaryRelOpts[i].gen.name; i++)
+ {
+ Assert(DoLockModesConflict(ternaryRelOpts[i].gen.lockmode,
+ ternaryRelOpts[i].gen.lockmode));
+ j++;
+ }
+
for (i = 0; intRelOpts[i].gen.name; i++)
{
Assert(DoLockModesConflict(intRelOpts[i].gen.lockmode,
@@ -640,6 +653,14 @@ initialize_reloptions(void)
j++;
}
+ for (i = 0; ternaryRelOpts[i].gen.name; i++)
+ {
+ relOpts[j] = &ternaryRelOpts[i].gen;
+ relOpts[j]->type = RELOPT_TYPE_TERNARY;
+ relOpts[j]->namelen = strlen(relOpts[j]->name);
+ j++;
+ }
+
for (i = 0; intRelOpts[i].gen.name; i++)
{
relOpts[j] = &intRelOpts[i].gen;
@@ -800,6 +821,9 @@ allocate_reloption(bits32 kinds, int type, const char *name, const char *desc,
case RELOPT_TYPE_BOOL:
size = sizeof(relopt_bool);
break;
+ case RELOPT_TYPE_TERNARY:
+ size = sizeof(relopt_ternary);
+ break;
case RELOPT_TYPE_INT:
size = sizeof(relopt_int);
break;
@@ -883,6 +907,54 @@ add_local_bool_reloption(local_relopts *relopts, const char *name,
add_local_reloption(relopts, (relopt_gen *) newoption, offset);
}
+/*
+ * init_ternary_reloption
+ * Allocate and initialize a new ternary reloption
+ */
+static relopt_ternary *
+init_ternary_reloption(bits32 kinds, const char *name, const char *desc,
+ ternary default_val, LOCKMODE lockmode)
+{
+ relopt_ternary *newoption;
+
+ newoption = (relopt_ternary *) allocate_reloption(kinds,
+ RELOPT_TYPE_TERNARY, name, desc, lockmode);
+ newoption->default_val = default_val;
+
+ return newoption;
+}
+
+/*
+ * add_ternary_reloption
+ * Add a new ternary reloption
+ */
+void
+add_ternary_reloption(bits32 kinds, const char *name, const char *desc,
+ ternary default_val, LOCKMODE lockmode)
+{
+ relopt_ternary *newoption = init_ternary_reloption(kinds, name, desc,
+ default_val, lockmode);
+
+ add_reloption((relopt_gen *) newoption);
+}
+
+/*
+ * add_local_ternary_reloption
+ * Add a new ternary local reloption
+ *
+ * 'offset' is offset of ternary-typed field.
+ */
+void
+add_local_ternary_reloption(local_relopts *relopts, const char *name,
+ const char *desc, ternary default_val,
+ int offset)
+{
+ relopt_ternary *newoption = init_ternary_reloption(RELOPT_KIND_LOCAL,
+ name, desc,
+ default_val, 0);
+
+ add_local_reloption(relopts, (relopt_gen *) newoption, offset);
+}
/*
* init_real_reloption
@@ -1617,6 +1689,18 @@ parse_one_reloption(relopt_value *option, char *text_str, int text_len,
option->gen->name, value)));
}
break;
+ case RELOPT_TYPE_TERNARY:
+ {
+ bool b;
+ parsed = parse_bool(value, &b);
+ option->values.ternary_val = b ? TERNARY_TRUE : TERNARY_FALSE;
+ if (validate && !parsed)
+ ereport(ERROR,
+ (errcode(ERRCODE_INVALID_PARAMETER_VALUE),
+ errmsg("invalid value for ternary option \"%s\": %s",
+ option->gen->name, value)));
+ }
+ break;
case RELOPT_TYPE_INT:
{
relopt_int *optint = (relopt_int *) option->gen;
@@ -1780,17 +1864,6 @@ fillRelOptions(void *rdopts, Size basesize,
char *itempos = ((char *) rdopts) + elems[j].offset;
char *string_val;
- /*
- * If isset_offset is provided, store whether the reloption is
- * set there.
- */
- if (elems[j].isset_offset > 0)
- {
- char *setpos = ((char *) rdopts) + elems[j].isset_offset;
-
- *(bool *) setpos = options[i].isset;
- }
-
switch (options[i].gen->type)
{
case RELOPT_TYPE_BOOL:
@@ -1798,6 +1871,11 @@ fillRelOptions(void *rdopts, Size basesize,
options[i].values.bool_val :
((relopt_bool *) options[i].gen)->default_val;
break;
+ case RELOPT_TYPE_TERNARY:
+ *(ternary *) itempos = options[i].isset ?
+ options[i].values.ternary_val :
+ ((relopt_ternary *) options[i].gen)->default_val;
+ break;
case RELOPT_TYPE_INT:
*(int *) itempos = options[i].isset ?
options[i].values.int_val :
@@ -1912,8 +1990,8 @@ default_reloptions(Datum reloptions, bool validate, relopt_kind kind)
offsetof(StdRdOptions, parallel_workers)},
{"vacuum_index_cleanup", RELOPT_TYPE_ENUM,
offsetof(StdRdOptions, vacuum_index_cleanup)},
- {"vacuum_truncate", RELOPT_TYPE_BOOL,
- offsetof(StdRdOptions, vacuum_truncate), offsetof(StdRdOptions, vacuum_truncate_set)},
+ {"vacuum_truncate", RELOPT_TYPE_TERNARY,
+ offsetof(StdRdOptions, vacuum_truncate)},
{"vacuum_max_eager_freeze_failure_rate", RELOPT_TYPE_REAL,
offsetof(StdRdOptions, vacuum_max_eager_freeze_failure_rate)}
};
@@ -1993,7 +2071,6 @@ build_local_reloptions(local_relopts *relopts, Datum options, bool validate)
elems[i].optname = opt->option->name;
elems[i].opttype = opt->option->type;
elems[i].offset = opt->offset;
- elems[i].isset_offset = 0; /* not supported for local relopts yet */
i++;
}
diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c
index 733ef40ae7..977babff54 100644
--- a/src/backend/commands/vacuum.c
+++ b/src/backend/commands/vacuum.c
@@ -2221,9 +2221,9 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams params,
{
StdRdOptions *opts = (StdRdOptions *) rel->rd_options;
- if (opts && opts->vacuum_truncate_set)
+ if (opts && opts->vacuum_truncate != TERNARY_UNSET)
{
- if (opts->vacuum_truncate)
+ if (opts->vacuum_truncate == TERNARY_TRUE)
params.truncate = VACOPTVALUE_ENABLED;
else
params.truncate = VACOPTVALUE_DISABLED;
diff --git a/src/include/access/reloptions.h b/src/include/access/reloptions.h
index a604a4702c..a436697658 100644
--- a/src/include/access/reloptions.h
+++ b/src/include/access/reloptions.h
@@ -29,6 +29,7 @@
typedef enum relopt_type
{
RELOPT_TYPE_BOOL,
+ RELOPT_TYPE_TERNARY, /* on, off, unset */
RELOPT_TYPE_INT,
RELOPT_TYPE_REAL,
RELOPT_TYPE_ENUM,
@@ -80,6 +81,7 @@ typedef struct relopt_value
union
{
bool bool_val;
+ ternary ternary_val;
int int_val;
double real_val;
int enum_val;
@@ -94,6 +96,12 @@ typedef struct relopt_bool
bool default_val;
} relopt_bool;
+typedef struct relopt_rernary
+{
+ relopt_gen gen;
+ int default_val;
+} relopt_ternary;
+
typedef struct relopt_int
{
relopt_gen gen;
@@ -152,19 +160,6 @@ typedef struct
const char *optname; /* option's name */
relopt_type opttype; /* option's datatype */
int offset; /* offset of field in result struct */
-
- /*
- * isset_offset is an optional offset of a field in the result struct that
- * stores whether the option is explicitly set for the relation or if it
- * just picked up the default value. In most cases, this can be
- * accomplished by giving the reloption a special out-of-range default
- * value (e.g., some integer reloptions use -2), but this isn't always
- * possible. For example, a Boolean reloption cannot be given an
- * out-of-range default, so we need another way to discover the source of
- * its value. This offset is only used if given a value greater than
- * zero.
- */
- int isset_offset;
} relopt_parse_elt;
/* Local reloption definition */
@@ -195,6 +190,8 @@ typedef struct local_relopts
extern relopt_kind add_reloption_kind(void);
extern void add_bool_reloption(bits32 kinds, const char *name, const char *desc,
bool default_val, LOCKMODE lockmode);
+extern void add_ternary_reloption(bits32 kinds, const char *name,
+ const char *desc, int default_val, LOCKMODE lockmode);
extern void add_int_reloption(bits32 kinds, const char *name, const char *desc,
int default_val, int min_val, int max_val,
LOCKMODE lockmode);
@@ -214,6 +211,9 @@ extern void register_reloptions_validator(local_relopts *relopts,
extern void add_local_bool_reloption(local_relopts *relopts, const char *name,
const char *desc, bool default_val,
int offset);
+extern void add_local_ternary_reloption(local_relopts *relopts,
+ const char *name, const char *desc,
+ ternary default_val, int offset);
extern void add_local_int_reloption(local_relopts *relopts, const char *name,
const char *desc, int default_val,
int min_val, int max_val, int offset);
diff --git a/src/include/c.h b/src/include/c.h
index 39022f8a9d..9c59ccccf8 100644
--- a/src/include/c.h
+++ b/src/include/c.h
@@ -488,6 +488,22 @@ typedef void (*pg_funcptr_t) (void);
#include <stdbool.h>
+/*
+ * ternary
+ * Boolean value with an extrea "unset" option
+ *
+ * Ternary data type is used in relation options that can be "true", "false" or
+ * "unset". Since relation options are used deep inside the PostgreSQL code,
+ * this type is declared globally.
+*/
+
+typedef enum ternary
+{
+ TERNARY_FALSE = 0,
+ TERNARY_TRUE = 1,
+ TERNARY_UNSET = -1
+} ternary;
+
/* ----------------------------------------------------------------
* Section 3: standard system types
diff --git a/src/include/utils/rel.h b/src/include/utils/rel.h
index b552359915..7346d618f9 100644
--- a/src/include/utils/rel.h
+++ b/src/include/utils/rel.h
@@ -346,8 +346,7 @@ typedef struct StdRdOptions
bool user_catalog_table; /* use as an additional catalog relation */
int parallel_workers; /* max number of parallel workers */
StdRdOptIndexCleanup vacuum_index_cleanup; /* controls index vacuuming */
- bool vacuum_truncate; /* enables vacuum to truncate a relation */
- bool vacuum_truncate_set; /* whether vacuum_truncate is set */
+ ternary vacuum_truncate; /* enables vacuum to truncate a relation */
/*
* Fraction of pages in a relation that vacuum can eagerly scan and fail
--
2.39.2
--nextPart4155600.3ZeAukHxDK
Content-Disposition: attachment;
filename="v2a-0003-Add-alias-to-be-used-as-unset-state.patch"
Content-Transfer-Encoding: 7Bit
Content-Type: text/x-patch; charset="unicode-2-0-utf-8";
name="v2a-0003-Add-alias-to-be-used-as-unset-state.patch"
^ permalink raw reply [nested|flat] 868+ messages in thread
* [PATCH v2a 2/4] Introduce ternary reloptions
@ 2025-09-04 14:41 Nikolay Shaplov <[email protected]>
0 siblings, 0 replies; 868+ messages in thread
From: Nikolay Shaplov @ 2025-09-04 14:41 UTC (permalink / raw)
Introduce ternary reloption as a replacement for current `vacuum_truncate`
implementation. Remove `vacuum_truncate_set` additional flag and using
`TERNARY_UNSET` value instead.
---
src/backend/access/common/reloptions.c | 129 ++++++++++++++++++++-----
src/backend/commands/vacuum.c | 4 +-
src/include/access/reloptions.h | 26 ++---
src/include/c.h | 16 +++
src/include/utils/rel.h | 3 +-
5 files changed, 135 insertions(+), 43 deletions(-)
diff --git a/src/backend/access/common/reloptions.c b/src/backend/access/common/reloptions.c
index 0af3fea68f..f543f11001 100644
--- a/src/backend/access/common/reloptions.c
+++ b/src/backend/access/common/reloptions.c
@@ -40,9 +40,9 @@
*
* To add an option:
*
- * (i) decide on a type (bool, integer, real, enum, string), name, default
- * value, upper and lower bounds (if applicable); for strings, consider a
- * validation routine.
+ * (i) decide on a type (bool, ternary, integer, real, enum, string), name,
+ * default value, upper and lower bounds (if applicable); for strings,
+ * consider a validation routine.
* (ii) add a record below (or use add_<type>_reloption).
* (iii) add it to the appropriate options struct (perhaps StdRdOptions)
* (iv) add it to the appropriate handling routine (perhaps
@@ -147,15 +147,6 @@ static relopt_bool boolRelOpts[] =
},
false
},
- {
- {
- "vacuum_truncate",
- "Enables vacuum to truncate empty pages at the end of this table",
- RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
- ShareUpdateExclusiveLock
- },
- true
- },
{
{
"deduplicate_items",
@@ -170,6 +161,21 @@ static relopt_bool boolRelOpts[] =
{{NULL}}
};
+static relopt_ternary ternaryRelOpts[] =
+{
+ {
+ {
+ "vacuum_truncate",
+ "Enables vacuum to truncate empty pages at the end of this table",
+ RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
+ ShareUpdateExclusiveLock
+ },
+ TERNARY_UNSET
+ },
+ /* list terminator */
+ {{NULL}}
+};
+
static relopt_int intRelOpts[] =
{
{
@@ -600,6 +606,13 @@ initialize_reloptions(void)
boolRelOpts[i].gen.lockmode));
j++;
}
+ for (i = 0; ternaryRelOpts[i].gen.name; i++)
+ {
+ Assert(DoLockModesConflict(ternaryRelOpts[i].gen.lockmode,
+ ternaryRelOpts[i].gen.lockmode));
+ j++;
+ }
+
for (i = 0; intRelOpts[i].gen.name; i++)
{
Assert(DoLockModesConflict(intRelOpts[i].gen.lockmode,
@@ -640,6 +653,14 @@ initialize_reloptions(void)
j++;
}
+ for (i = 0; ternaryRelOpts[i].gen.name; i++)
+ {
+ relOpts[j] = &ternaryRelOpts[i].gen;
+ relOpts[j]->type = RELOPT_TYPE_TERNARY;
+ relOpts[j]->namelen = strlen(relOpts[j]->name);
+ j++;
+ }
+
for (i = 0; intRelOpts[i].gen.name; i++)
{
relOpts[j] = &intRelOpts[i].gen;
@@ -800,6 +821,9 @@ allocate_reloption(bits32 kinds, int type, const char *name, const char *desc,
case RELOPT_TYPE_BOOL:
size = sizeof(relopt_bool);
break;
+ case RELOPT_TYPE_TERNARY:
+ size = sizeof(relopt_ternary);
+ break;
case RELOPT_TYPE_INT:
size = sizeof(relopt_int);
break;
@@ -883,6 +907,54 @@ add_local_bool_reloption(local_relopts *relopts, const char *name,
add_local_reloption(relopts, (relopt_gen *) newoption, offset);
}
+/*
+ * init_ternary_reloption
+ * Allocate and initialize a new ternary reloption
+ */
+static relopt_ternary *
+init_ternary_reloption(bits32 kinds, const char *name, const char *desc,
+ ternary default_val, LOCKMODE lockmode)
+{
+ relopt_ternary *newoption;
+
+ newoption = (relopt_ternary *) allocate_reloption(kinds,
+ RELOPT_TYPE_TERNARY, name, desc, lockmode);
+ newoption->default_val = default_val;
+
+ return newoption;
+}
+
+/*
+ * add_ternary_reloption
+ * Add a new ternary reloption
+ */
+void
+add_ternary_reloption(bits32 kinds, const char *name, const char *desc,
+ ternary default_val, LOCKMODE lockmode)
+{
+ relopt_ternary *newoption = init_ternary_reloption(kinds, name, desc,
+ default_val, lockmode);
+
+ add_reloption((relopt_gen *) newoption);
+}
+
+/*
+ * add_local_ternary_reloption
+ * Add a new ternary local reloption
+ *
+ * 'offset' is offset of ternary-typed field.
+ */
+void
+add_local_ternary_reloption(local_relopts *relopts, const char *name,
+ const char *desc, ternary default_val,
+ int offset)
+{
+ relopt_ternary *newoption = init_ternary_reloption(RELOPT_KIND_LOCAL,
+ name, desc,
+ default_val, 0);
+
+ add_local_reloption(relopts, (relopt_gen *) newoption, offset);
+}
/*
* init_real_reloption
@@ -1617,6 +1689,18 @@ parse_one_reloption(relopt_value *option, char *text_str, int text_len,
option->gen->name, value)));
}
break;
+ case RELOPT_TYPE_TERNARY:
+ {
+ bool b;
+ parsed = parse_bool(value, &b);
+ option->values.ternary_val = b ? TERNARY_TRUE : TERNARY_FALSE;
+ if (validate && !parsed)
+ ereport(ERROR,
+ (errcode(ERRCODE_INVALID_PARAMETER_VALUE),
+ errmsg("invalid value for ternary option \"%s\": %s",
+ option->gen->name, value)));
+ }
+ break;
case RELOPT_TYPE_INT:
{
relopt_int *optint = (relopt_int *) option->gen;
@@ -1780,17 +1864,6 @@ fillRelOptions(void *rdopts, Size basesize,
char *itempos = ((char *) rdopts) + elems[j].offset;
char *string_val;
- /*
- * If isset_offset is provided, store whether the reloption is
- * set there.
- */
- if (elems[j].isset_offset > 0)
- {
- char *setpos = ((char *) rdopts) + elems[j].isset_offset;
-
- *(bool *) setpos = options[i].isset;
- }
-
switch (options[i].gen->type)
{
case RELOPT_TYPE_BOOL:
@@ -1798,6 +1871,11 @@ fillRelOptions(void *rdopts, Size basesize,
options[i].values.bool_val :
((relopt_bool *) options[i].gen)->default_val;
break;
+ case RELOPT_TYPE_TERNARY:
+ *(ternary *) itempos = options[i].isset ?
+ options[i].values.ternary_val :
+ ((relopt_ternary *) options[i].gen)->default_val;
+ break;
case RELOPT_TYPE_INT:
*(int *) itempos = options[i].isset ?
options[i].values.int_val :
@@ -1912,8 +1990,8 @@ default_reloptions(Datum reloptions, bool validate, relopt_kind kind)
offsetof(StdRdOptions, parallel_workers)},
{"vacuum_index_cleanup", RELOPT_TYPE_ENUM,
offsetof(StdRdOptions, vacuum_index_cleanup)},
- {"vacuum_truncate", RELOPT_TYPE_BOOL,
- offsetof(StdRdOptions, vacuum_truncate), offsetof(StdRdOptions, vacuum_truncate_set)},
+ {"vacuum_truncate", RELOPT_TYPE_TERNARY,
+ offsetof(StdRdOptions, vacuum_truncate)},
{"vacuum_max_eager_freeze_failure_rate", RELOPT_TYPE_REAL,
offsetof(StdRdOptions, vacuum_max_eager_freeze_failure_rate)}
};
@@ -1993,7 +2071,6 @@ build_local_reloptions(local_relopts *relopts, Datum options, bool validate)
elems[i].optname = opt->option->name;
elems[i].opttype = opt->option->type;
elems[i].offset = opt->offset;
- elems[i].isset_offset = 0; /* not supported for local relopts yet */
i++;
}
diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c
index 733ef40ae7..977babff54 100644
--- a/src/backend/commands/vacuum.c
+++ b/src/backend/commands/vacuum.c
@@ -2221,9 +2221,9 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams params,
{
StdRdOptions *opts = (StdRdOptions *) rel->rd_options;
- if (opts && opts->vacuum_truncate_set)
+ if (opts && opts->vacuum_truncate != TERNARY_UNSET)
{
- if (opts->vacuum_truncate)
+ if (opts->vacuum_truncate == TERNARY_TRUE)
params.truncate = VACOPTVALUE_ENABLED;
else
params.truncate = VACOPTVALUE_DISABLED;
diff --git a/src/include/access/reloptions.h b/src/include/access/reloptions.h
index a604a4702c..a436697658 100644
--- a/src/include/access/reloptions.h
+++ b/src/include/access/reloptions.h
@@ -29,6 +29,7 @@
typedef enum relopt_type
{
RELOPT_TYPE_BOOL,
+ RELOPT_TYPE_TERNARY, /* on, off, unset */
RELOPT_TYPE_INT,
RELOPT_TYPE_REAL,
RELOPT_TYPE_ENUM,
@@ -80,6 +81,7 @@ typedef struct relopt_value
union
{
bool bool_val;
+ ternary ternary_val;
int int_val;
double real_val;
int enum_val;
@@ -94,6 +96,12 @@ typedef struct relopt_bool
bool default_val;
} relopt_bool;
+typedef struct relopt_rernary
+{
+ relopt_gen gen;
+ int default_val;
+} relopt_ternary;
+
typedef struct relopt_int
{
relopt_gen gen;
@@ -152,19 +160,6 @@ typedef struct
const char *optname; /* option's name */
relopt_type opttype; /* option's datatype */
int offset; /* offset of field in result struct */
-
- /*
- * isset_offset is an optional offset of a field in the result struct that
- * stores whether the option is explicitly set for the relation or if it
- * just picked up the default value. In most cases, this can be
- * accomplished by giving the reloption a special out-of-range default
- * value (e.g., some integer reloptions use -2), but this isn't always
- * possible. For example, a Boolean reloption cannot be given an
- * out-of-range default, so we need another way to discover the source of
- * its value. This offset is only used if given a value greater than
- * zero.
- */
- int isset_offset;
} relopt_parse_elt;
/* Local reloption definition */
@@ -195,6 +190,8 @@ typedef struct local_relopts
extern relopt_kind add_reloption_kind(void);
extern void add_bool_reloption(bits32 kinds, const char *name, const char *desc,
bool default_val, LOCKMODE lockmode);
+extern void add_ternary_reloption(bits32 kinds, const char *name,
+ const char *desc, int default_val, LOCKMODE lockmode);
extern void add_int_reloption(bits32 kinds, const char *name, const char *desc,
int default_val, int min_val, int max_val,
LOCKMODE lockmode);
@@ -214,6 +211,9 @@ extern void register_reloptions_validator(local_relopts *relopts,
extern void add_local_bool_reloption(local_relopts *relopts, const char *name,
const char *desc, bool default_val,
int offset);
+extern void add_local_ternary_reloption(local_relopts *relopts,
+ const char *name, const char *desc,
+ ternary default_val, int offset);
extern void add_local_int_reloption(local_relopts *relopts, const char *name,
const char *desc, int default_val,
int min_val, int max_val, int offset);
diff --git a/src/include/c.h b/src/include/c.h
index 39022f8a9d..9c59ccccf8 100644
--- a/src/include/c.h
+++ b/src/include/c.h
@@ -488,6 +488,22 @@ typedef void (*pg_funcptr_t) (void);
#include <stdbool.h>
+/*
+ * ternary
+ * Boolean value with an extrea "unset" option
+ *
+ * Ternary data type is used in relation options that can be "true", "false" or
+ * "unset". Since relation options are used deep inside the PostgreSQL code,
+ * this type is declared globally.
+*/
+
+typedef enum ternary
+{
+ TERNARY_FALSE = 0,
+ TERNARY_TRUE = 1,
+ TERNARY_UNSET = -1
+} ternary;
+
/* ----------------------------------------------------------------
* Section 3: standard system types
diff --git a/src/include/utils/rel.h b/src/include/utils/rel.h
index b552359915..7346d618f9 100644
--- a/src/include/utils/rel.h
+++ b/src/include/utils/rel.h
@@ -346,8 +346,7 @@ typedef struct StdRdOptions
bool user_catalog_table; /* use as an additional catalog relation */
int parallel_workers; /* max number of parallel workers */
StdRdOptIndexCleanup vacuum_index_cleanup; /* controls index vacuuming */
- bool vacuum_truncate; /* enables vacuum to truncate a relation */
- bool vacuum_truncate_set; /* whether vacuum_truncate is set */
+ ternary vacuum_truncate; /* enables vacuum to truncate a relation */
/*
* Fraction of pages in a relation that vacuum can eagerly scan and fail
--
2.39.2
--nextPart4155600.3ZeAukHxDK
Content-Disposition: attachment;
filename="v2a-0003-Add-alias-to-be-used-as-unset-state.patch"
Content-Transfer-Encoding: 7Bit
Content-Type: text/x-patch; charset="unicode-2-0-utf-8";
name="v2a-0003-Add-alias-to-be-used-as-unset-state.patch"
^ permalink raw reply [nested|flat] 868+ messages in thread
* [PATCH v2a 2/4] Introduce ternary reloptions
@ 2025-09-04 14:41 Nikolay Shaplov <[email protected]>
0 siblings, 0 replies; 868+ messages in thread
From: Nikolay Shaplov @ 2025-09-04 14:41 UTC (permalink / raw)
Introduce ternary reloption as a replacement for current `vacuum_truncate`
implementation. Remove `vacuum_truncate_set` additional flag and using
`TERNARY_UNSET` value instead.
---
src/backend/access/common/reloptions.c | 129 ++++++++++++++++++++-----
src/backend/commands/vacuum.c | 4 +-
src/include/access/reloptions.h | 26 ++---
src/include/c.h | 16 +++
src/include/utils/rel.h | 3 +-
5 files changed, 135 insertions(+), 43 deletions(-)
diff --git a/src/backend/access/common/reloptions.c b/src/backend/access/common/reloptions.c
index 0af3fea68f..f543f11001 100644
--- a/src/backend/access/common/reloptions.c
+++ b/src/backend/access/common/reloptions.c
@@ -40,9 +40,9 @@
*
* To add an option:
*
- * (i) decide on a type (bool, integer, real, enum, string), name, default
- * value, upper and lower bounds (if applicable); for strings, consider a
- * validation routine.
+ * (i) decide on a type (bool, ternary, integer, real, enum, string), name,
+ * default value, upper and lower bounds (if applicable); for strings,
+ * consider a validation routine.
* (ii) add a record below (or use add_<type>_reloption).
* (iii) add it to the appropriate options struct (perhaps StdRdOptions)
* (iv) add it to the appropriate handling routine (perhaps
@@ -147,15 +147,6 @@ static relopt_bool boolRelOpts[] =
},
false
},
- {
- {
- "vacuum_truncate",
- "Enables vacuum to truncate empty pages at the end of this table",
- RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
- ShareUpdateExclusiveLock
- },
- true
- },
{
{
"deduplicate_items",
@@ -170,6 +161,21 @@ static relopt_bool boolRelOpts[] =
{{NULL}}
};
+static relopt_ternary ternaryRelOpts[] =
+{
+ {
+ {
+ "vacuum_truncate",
+ "Enables vacuum to truncate empty pages at the end of this table",
+ RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
+ ShareUpdateExclusiveLock
+ },
+ TERNARY_UNSET
+ },
+ /* list terminator */
+ {{NULL}}
+};
+
static relopt_int intRelOpts[] =
{
{
@@ -600,6 +606,13 @@ initialize_reloptions(void)
boolRelOpts[i].gen.lockmode));
j++;
}
+ for (i = 0; ternaryRelOpts[i].gen.name; i++)
+ {
+ Assert(DoLockModesConflict(ternaryRelOpts[i].gen.lockmode,
+ ternaryRelOpts[i].gen.lockmode));
+ j++;
+ }
+
for (i = 0; intRelOpts[i].gen.name; i++)
{
Assert(DoLockModesConflict(intRelOpts[i].gen.lockmode,
@@ -640,6 +653,14 @@ initialize_reloptions(void)
j++;
}
+ for (i = 0; ternaryRelOpts[i].gen.name; i++)
+ {
+ relOpts[j] = &ternaryRelOpts[i].gen;
+ relOpts[j]->type = RELOPT_TYPE_TERNARY;
+ relOpts[j]->namelen = strlen(relOpts[j]->name);
+ j++;
+ }
+
for (i = 0; intRelOpts[i].gen.name; i++)
{
relOpts[j] = &intRelOpts[i].gen;
@@ -800,6 +821,9 @@ allocate_reloption(bits32 kinds, int type, const char *name, const char *desc,
case RELOPT_TYPE_BOOL:
size = sizeof(relopt_bool);
break;
+ case RELOPT_TYPE_TERNARY:
+ size = sizeof(relopt_ternary);
+ break;
case RELOPT_TYPE_INT:
size = sizeof(relopt_int);
break;
@@ -883,6 +907,54 @@ add_local_bool_reloption(local_relopts *relopts, const char *name,
add_local_reloption(relopts, (relopt_gen *) newoption, offset);
}
+/*
+ * init_ternary_reloption
+ * Allocate and initialize a new ternary reloption
+ */
+static relopt_ternary *
+init_ternary_reloption(bits32 kinds, const char *name, const char *desc,
+ ternary default_val, LOCKMODE lockmode)
+{
+ relopt_ternary *newoption;
+
+ newoption = (relopt_ternary *) allocate_reloption(kinds,
+ RELOPT_TYPE_TERNARY, name, desc, lockmode);
+ newoption->default_val = default_val;
+
+ return newoption;
+}
+
+/*
+ * add_ternary_reloption
+ * Add a new ternary reloption
+ */
+void
+add_ternary_reloption(bits32 kinds, const char *name, const char *desc,
+ ternary default_val, LOCKMODE lockmode)
+{
+ relopt_ternary *newoption = init_ternary_reloption(kinds, name, desc,
+ default_val, lockmode);
+
+ add_reloption((relopt_gen *) newoption);
+}
+
+/*
+ * add_local_ternary_reloption
+ * Add a new ternary local reloption
+ *
+ * 'offset' is offset of ternary-typed field.
+ */
+void
+add_local_ternary_reloption(local_relopts *relopts, const char *name,
+ const char *desc, ternary default_val,
+ int offset)
+{
+ relopt_ternary *newoption = init_ternary_reloption(RELOPT_KIND_LOCAL,
+ name, desc,
+ default_val, 0);
+
+ add_local_reloption(relopts, (relopt_gen *) newoption, offset);
+}
/*
* init_real_reloption
@@ -1617,6 +1689,18 @@ parse_one_reloption(relopt_value *option, char *text_str, int text_len,
option->gen->name, value)));
}
break;
+ case RELOPT_TYPE_TERNARY:
+ {
+ bool b;
+ parsed = parse_bool(value, &b);
+ option->values.ternary_val = b ? TERNARY_TRUE : TERNARY_FALSE;
+ if (validate && !parsed)
+ ereport(ERROR,
+ (errcode(ERRCODE_INVALID_PARAMETER_VALUE),
+ errmsg("invalid value for ternary option \"%s\": %s",
+ option->gen->name, value)));
+ }
+ break;
case RELOPT_TYPE_INT:
{
relopt_int *optint = (relopt_int *) option->gen;
@@ -1780,17 +1864,6 @@ fillRelOptions(void *rdopts, Size basesize,
char *itempos = ((char *) rdopts) + elems[j].offset;
char *string_val;
- /*
- * If isset_offset is provided, store whether the reloption is
- * set there.
- */
- if (elems[j].isset_offset > 0)
- {
- char *setpos = ((char *) rdopts) + elems[j].isset_offset;
-
- *(bool *) setpos = options[i].isset;
- }
-
switch (options[i].gen->type)
{
case RELOPT_TYPE_BOOL:
@@ -1798,6 +1871,11 @@ fillRelOptions(void *rdopts, Size basesize,
options[i].values.bool_val :
((relopt_bool *) options[i].gen)->default_val;
break;
+ case RELOPT_TYPE_TERNARY:
+ *(ternary *) itempos = options[i].isset ?
+ options[i].values.ternary_val :
+ ((relopt_ternary *) options[i].gen)->default_val;
+ break;
case RELOPT_TYPE_INT:
*(int *) itempos = options[i].isset ?
options[i].values.int_val :
@@ -1912,8 +1990,8 @@ default_reloptions(Datum reloptions, bool validate, relopt_kind kind)
offsetof(StdRdOptions, parallel_workers)},
{"vacuum_index_cleanup", RELOPT_TYPE_ENUM,
offsetof(StdRdOptions, vacuum_index_cleanup)},
- {"vacuum_truncate", RELOPT_TYPE_BOOL,
- offsetof(StdRdOptions, vacuum_truncate), offsetof(StdRdOptions, vacuum_truncate_set)},
+ {"vacuum_truncate", RELOPT_TYPE_TERNARY,
+ offsetof(StdRdOptions, vacuum_truncate)},
{"vacuum_max_eager_freeze_failure_rate", RELOPT_TYPE_REAL,
offsetof(StdRdOptions, vacuum_max_eager_freeze_failure_rate)}
};
@@ -1993,7 +2071,6 @@ build_local_reloptions(local_relopts *relopts, Datum options, bool validate)
elems[i].optname = opt->option->name;
elems[i].opttype = opt->option->type;
elems[i].offset = opt->offset;
- elems[i].isset_offset = 0; /* not supported for local relopts yet */
i++;
}
diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c
index 733ef40ae7..977babff54 100644
--- a/src/backend/commands/vacuum.c
+++ b/src/backend/commands/vacuum.c
@@ -2221,9 +2221,9 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams params,
{
StdRdOptions *opts = (StdRdOptions *) rel->rd_options;
- if (opts && opts->vacuum_truncate_set)
+ if (opts && opts->vacuum_truncate != TERNARY_UNSET)
{
- if (opts->vacuum_truncate)
+ if (opts->vacuum_truncate == TERNARY_TRUE)
params.truncate = VACOPTVALUE_ENABLED;
else
params.truncate = VACOPTVALUE_DISABLED;
diff --git a/src/include/access/reloptions.h b/src/include/access/reloptions.h
index a604a4702c..a436697658 100644
--- a/src/include/access/reloptions.h
+++ b/src/include/access/reloptions.h
@@ -29,6 +29,7 @@
typedef enum relopt_type
{
RELOPT_TYPE_BOOL,
+ RELOPT_TYPE_TERNARY, /* on, off, unset */
RELOPT_TYPE_INT,
RELOPT_TYPE_REAL,
RELOPT_TYPE_ENUM,
@@ -80,6 +81,7 @@ typedef struct relopt_value
union
{
bool bool_val;
+ ternary ternary_val;
int int_val;
double real_val;
int enum_val;
@@ -94,6 +96,12 @@ typedef struct relopt_bool
bool default_val;
} relopt_bool;
+typedef struct relopt_rernary
+{
+ relopt_gen gen;
+ int default_val;
+} relopt_ternary;
+
typedef struct relopt_int
{
relopt_gen gen;
@@ -152,19 +160,6 @@ typedef struct
const char *optname; /* option's name */
relopt_type opttype; /* option's datatype */
int offset; /* offset of field in result struct */
-
- /*
- * isset_offset is an optional offset of a field in the result struct that
- * stores whether the option is explicitly set for the relation or if it
- * just picked up the default value. In most cases, this can be
- * accomplished by giving the reloption a special out-of-range default
- * value (e.g., some integer reloptions use -2), but this isn't always
- * possible. For example, a Boolean reloption cannot be given an
- * out-of-range default, so we need another way to discover the source of
- * its value. This offset is only used if given a value greater than
- * zero.
- */
- int isset_offset;
} relopt_parse_elt;
/* Local reloption definition */
@@ -195,6 +190,8 @@ typedef struct local_relopts
extern relopt_kind add_reloption_kind(void);
extern void add_bool_reloption(bits32 kinds, const char *name, const char *desc,
bool default_val, LOCKMODE lockmode);
+extern void add_ternary_reloption(bits32 kinds, const char *name,
+ const char *desc, int default_val, LOCKMODE lockmode);
extern void add_int_reloption(bits32 kinds, const char *name, const char *desc,
int default_val, int min_val, int max_val,
LOCKMODE lockmode);
@@ -214,6 +211,9 @@ extern void register_reloptions_validator(local_relopts *relopts,
extern void add_local_bool_reloption(local_relopts *relopts, const char *name,
const char *desc, bool default_val,
int offset);
+extern void add_local_ternary_reloption(local_relopts *relopts,
+ const char *name, const char *desc,
+ ternary default_val, int offset);
extern void add_local_int_reloption(local_relopts *relopts, const char *name,
const char *desc, int default_val,
int min_val, int max_val, int offset);
diff --git a/src/include/c.h b/src/include/c.h
index 39022f8a9d..9c59ccccf8 100644
--- a/src/include/c.h
+++ b/src/include/c.h
@@ -488,6 +488,22 @@ typedef void (*pg_funcptr_t) (void);
#include <stdbool.h>
+/*
+ * ternary
+ * Boolean value with an extrea "unset" option
+ *
+ * Ternary data type is used in relation options that can be "true", "false" or
+ * "unset". Since relation options are used deep inside the PostgreSQL code,
+ * this type is declared globally.
+*/
+
+typedef enum ternary
+{
+ TERNARY_FALSE = 0,
+ TERNARY_TRUE = 1,
+ TERNARY_UNSET = -1
+} ternary;
+
/* ----------------------------------------------------------------
* Section 3: standard system types
diff --git a/src/include/utils/rel.h b/src/include/utils/rel.h
index b552359915..7346d618f9 100644
--- a/src/include/utils/rel.h
+++ b/src/include/utils/rel.h
@@ -346,8 +346,7 @@ typedef struct StdRdOptions
bool user_catalog_table; /* use as an additional catalog relation */
int parallel_workers; /* max number of parallel workers */
StdRdOptIndexCleanup vacuum_index_cleanup; /* controls index vacuuming */
- bool vacuum_truncate; /* enables vacuum to truncate a relation */
- bool vacuum_truncate_set; /* whether vacuum_truncate is set */
+ ternary vacuum_truncate; /* enables vacuum to truncate a relation */
/*
* Fraction of pages in a relation that vacuum can eagerly scan and fail
--
2.39.2
--nextPart4155600.3ZeAukHxDK
Content-Disposition: attachment;
filename="v2a-0003-Add-alias-to-be-used-as-unset-state.patch"
Content-Transfer-Encoding: 7Bit
Content-Type: text/x-patch; charset="unicode-2-0-utf-8";
name="v2a-0003-Add-alias-to-be-used-as-unset-state.patch"
^ permalink raw reply [nested|flat] 868+ messages in thread
* [PATCH v2 2/4] Introduce ternary reloptions
@ 2025-09-04 14:41 Nikolay Shaplov <[email protected]>
0 siblings, 0 replies; 868+ messages in thread
From: Nikolay Shaplov @ 2025-09-04 14:41 UTC (permalink / raw)
Introduce ternary reloption as a replacement for current `vacuum_truncate`
implementation. Remove `vacuum_truncate_set` additional flag and using
`TERNARY_UNSET` value instead.
---
src/backend/access/common/reloptions.c | 129 ++++++++++++++++++++-----
src/backend/commands/vacuum.c | 4 +-
src/include/access/reloptions.h | 26 ++---
src/include/c.h | 16 +++
src/include/utils/rel.h | 3 +-
5 files changed, 135 insertions(+), 43 deletions(-)
diff --git a/src/backend/access/common/reloptions.c b/src/backend/access/common/reloptions.c
index 0af3fea68f..f543f11001 100644
--- a/src/backend/access/common/reloptions.c
+++ b/src/backend/access/common/reloptions.c
@@ -40,9 +40,9 @@
*
* To add an option:
*
- * (i) decide on a type (bool, integer, real, enum, string), name, default
- * value, upper and lower bounds (if applicable); for strings, consider a
- * validation routine.
+ * (i) decide on a type (bool, ternary, integer, real, enum, string), name,
+ * default value, upper and lower bounds (if applicable); for strings,
+ * consider a validation routine.
* (ii) add a record below (or use add_<type>_reloption).
* (iii) add it to the appropriate options struct (perhaps StdRdOptions)
* (iv) add it to the appropriate handling routine (perhaps
@@ -147,15 +147,6 @@ static relopt_bool boolRelOpts[] =
},
false
},
- {
- {
- "vacuum_truncate",
- "Enables vacuum to truncate empty pages at the end of this table",
- RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
- ShareUpdateExclusiveLock
- },
- true
- },
{
{
"deduplicate_items",
@@ -170,6 +161,21 @@ static relopt_bool boolRelOpts[] =
{{NULL}}
};
+static relopt_ternary ternaryRelOpts[] =
+{
+ {
+ {
+ "vacuum_truncate",
+ "Enables vacuum to truncate empty pages at the end of this table",
+ RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
+ ShareUpdateExclusiveLock
+ },
+ TERNARY_UNSET
+ },
+ /* list terminator */
+ {{NULL}}
+};
+
static relopt_int intRelOpts[] =
{
{
@@ -600,6 +606,13 @@ initialize_reloptions(void)
boolRelOpts[i].gen.lockmode));
j++;
}
+ for (i = 0; ternaryRelOpts[i].gen.name; i++)
+ {
+ Assert(DoLockModesConflict(ternaryRelOpts[i].gen.lockmode,
+ ternaryRelOpts[i].gen.lockmode));
+ j++;
+ }
+
for (i = 0; intRelOpts[i].gen.name; i++)
{
Assert(DoLockModesConflict(intRelOpts[i].gen.lockmode,
@@ -640,6 +653,14 @@ initialize_reloptions(void)
j++;
}
+ for (i = 0; ternaryRelOpts[i].gen.name; i++)
+ {
+ relOpts[j] = &ternaryRelOpts[i].gen;
+ relOpts[j]->type = RELOPT_TYPE_TERNARY;
+ relOpts[j]->namelen = strlen(relOpts[j]->name);
+ j++;
+ }
+
for (i = 0; intRelOpts[i].gen.name; i++)
{
relOpts[j] = &intRelOpts[i].gen;
@@ -800,6 +821,9 @@ allocate_reloption(bits32 kinds, int type, const char *name, const char *desc,
case RELOPT_TYPE_BOOL:
size = sizeof(relopt_bool);
break;
+ case RELOPT_TYPE_TERNARY:
+ size = sizeof(relopt_ternary);
+ break;
case RELOPT_TYPE_INT:
size = sizeof(relopt_int);
break;
@@ -883,6 +907,54 @@ add_local_bool_reloption(local_relopts *relopts, const char *name,
add_local_reloption(relopts, (relopt_gen *) newoption, offset);
}
+/*
+ * init_ternary_reloption
+ * Allocate and initialize a new ternary reloption
+ */
+static relopt_ternary *
+init_ternary_reloption(bits32 kinds, const char *name, const char *desc,
+ ternary default_val, LOCKMODE lockmode)
+{
+ relopt_ternary *newoption;
+
+ newoption = (relopt_ternary *) allocate_reloption(kinds,
+ RELOPT_TYPE_TERNARY, name, desc, lockmode);
+ newoption->default_val = default_val;
+
+ return newoption;
+}
+
+/*
+ * add_ternary_reloption
+ * Add a new ternary reloption
+ */
+void
+add_ternary_reloption(bits32 kinds, const char *name, const char *desc,
+ ternary default_val, LOCKMODE lockmode)
+{
+ relopt_ternary *newoption = init_ternary_reloption(kinds, name, desc,
+ default_val, lockmode);
+
+ add_reloption((relopt_gen *) newoption);
+}
+
+/*
+ * add_local_ternary_reloption
+ * Add a new ternary local reloption
+ *
+ * 'offset' is offset of ternary-typed field.
+ */
+void
+add_local_ternary_reloption(local_relopts *relopts, const char *name,
+ const char *desc, ternary default_val,
+ int offset)
+{
+ relopt_ternary *newoption = init_ternary_reloption(RELOPT_KIND_LOCAL,
+ name, desc,
+ default_val, 0);
+
+ add_local_reloption(relopts, (relopt_gen *) newoption, offset);
+}
/*
* init_real_reloption
@@ -1617,6 +1689,18 @@ parse_one_reloption(relopt_value *option, char *text_str, int text_len,
option->gen->name, value)));
}
break;
+ case RELOPT_TYPE_TERNARY:
+ {
+ bool b;
+ parsed = parse_bool(value, &b);
+ option->values.ternary_val = b ? TERNARY_TRUE : TERNARY_FALSE;
+ if (validate && !parsed)
+ ereport(ERROR,
+ (errcode(ERRCODE_INVALID_PARAMETER_VALUE),
+ errmsg("invalid value for ternary option \"%s\": %s",
+ option->gen->name, value)));
+ }
+ break;
case RELOPT_TYPE_INT:
{
relopt_int *optint = (relopt_int *) option->gen;
@@ -1780,17 +1864,6 @@ fillRelOptions(void *rdopts, Size basesize,
char *itempos = ((char *) rdopts) + elems[j].offset;
char *string_val;
- /*
- * If isset_offset is provided, store whether the reloption is
- * set there.
- */
- if (elems[j].isset_offset > 0)
- {
- char *setpos = ((char *) rdopts) + elems[j].isset_offset;
-
- *(bool *) setpos = options[i].isset;
- }
-
switch (options[i].gen->type)
{
case RELOPT_TYPE_BOOL:
@@ -1798,6 +1871,11 @@ fillRelOptions(void *rdopts, Size basesize,
options[i].values.bool_val :
((relopt_bool *) options[i].gen)->default_val;
break;
+ case RELOPT_TYPE_TERNARY:
+ *(ternary *) itempos = options[i].isset ?
+ options[i].values.ternary_val :
+ ((relopt_ternary *) options[i].gen)->default_val;
+ break;
case RELOPT_TYPE_INT:
*(int *) itempos = options[i].isset ?
options[i].values.int_val :
@@ -1912,8 +1990,8 @@ default_reloptions(Datum reloptions, bool validate, relopt_kind kind)
offsetof(StdRdOptions, parallel_workers)},
{"vacuum_index_cleanup", RELOPT_TYPE_ENUM,
offsetof(StdRdOptions, vacuum_index_cleanup)},
- {"vacuum_truncate", RELOPT_TYPE_BOOL,
- offsetof(StdRdOptions, vacuum_truncate), offsetof(StdRdOptions, vacuum_truncate_set)},
+ {"vacuum_truncate", RELOPT_TYPE_TERNARY,
+ offsetof(StdRdOptions, vacuum_truncate)},
{"vacuum_max_eager_freeze_failure_rate", RELOPT_TYPE_REAL,
offsetof(StdRdOptions, vacuum_max_eager_freeze_failure_rate)}
};
@@ -1993,7 +2071,6 @@ build_local_reloptions(local_relopts *relopts, Datum options, bool validate)
elems[i].optname = opt->option->name;
elems[i].opttype = opt->option->type;
elems[i].offset = opt->offset;
- elems[i].isset_offset = 0; /* not supported for local relopts yet */
i++;
}
diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c
index 733ef40ae7..977babff54 100644
--- a/src/backend/commands/vacuum.c
+++ b/src/backend/commands/vacuum.c
@@ -2221,9 +2221,9 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams params,
{
StdRdOptions *opts = (StdRdOptions *) rel->rd_options;
- if (opts && opts->vacuum_truncate_set)
+ if (opts && opts->vacuum_truncate != TERNARY_UNSET)
{
- if (opts->vacuum_truncate)
+ if (opts->vacuum_truncate == TERNARY_TRUE)
params.truncate = VACOPTVALUE_ENABLED;
else
params.truncate = VACOPTVALUE_DISABLED;
diff --git a/src/include/access/reloptions.h b/src/include/access/reloptions.h
index a604a4702c..a436697658 100644
--- a/src/include/access/reloptions.h
+++ b/src/include/access/reloptions.h
@@ -29,6 +29,7 @@
typedef enum relopt_type
{
RELOPT_TYPE_BOOL,
+ RELOPT_TYPE_TERNARY, /* on, off, unset */
RELOPT_TYPE_INT,
RELOPT_TYPE_REAL,
RELOPT_TYPE_ENUM,
@@ -80,6 +81,7 @@ typedef struct relopt_value
union
{
bool bool_val;
+ ternary ternary_val;
int int_val;
double real_val;
int enum_val;
@@ -94,6 +96,12 @@ typedef struct relopt_bool
bool default_val;
} relopt_bool;
+typedef struct relopt_rernary
+{
+ relopt_gen gen;
+ int default_val;
+} relopt_ternary;
+
typedef struct relopt_int
{
relopt_gen gen;
@@ -152,19 +160,6 @@ typedef struct
const char *optname; /* option's name */
relopt_type opttype; /* option's datatype */
int offset; /* offset of field in result struct */
-
- /*
- * isset_offset is an optional offset of a field in the result struct that
- * stores whether the option is explicitly set for the relation or if it
- * just picked up the default value. In most cases, this can be
- * accomplished by giving the reloption a special out-of-range default
- * value (e.g., some integer reloptions use -2), but this isn't always
- * possible. For example, a Boolean reloption cannot be given an
- * out-of-range default, so we need another way to discover the source of
- * its value. This offset is only used if given a value greater than
- * zero.
- */
- int isset_offset;
} relopt_parse_elt;
/* Local reloption definition */
@@ -195,6 +190,8 @@ typedef struct local_relopts
extern relopt_kind add_reloption_kind(void);
extern void add_bool_reloption(bits32 kinds, const char *name, const char *desc,
bool default_val, LOCKMODE lockmode);
+extern void add_ternary_reloption(bits32 kinds, const char *name,
+ const char *desc, int default_val, LOCKMODE lockmode);
extern void add_int_reloption(bits32 kinds, const char *name, const char *desc,
int default_val, int min_val, int max_val,
LOCKMODE lockmode);
@@ -214,6 +211,9 @@ extern void register_reloptions_validator(local_relopts *relopts,
extern void add_local_bool_reloption(local_relopts *relopts, const char *name,
const char *desc, bool default_val,
int offset);
+extern void add_local_ternary_reloption(local_relopts *relopts,
+ const char *name, const char *desc,
+ ternary default_val, int offset);
extern void add_local_int_reloption(local_relopts *relopts, const char *name,
const char *desc, int default_val,
int min_val, int max_val, int offset);
diff --git a/src/include/c.h b/src/include/c.h
index 39022f8a9d..9c59ccccf8 100644
--- a/src/include/c.h
+++ b/src/include/c.h
@@ -488,6 +488,22 @@ typedef void (*pg_funcptr_t) (void);
#include <stdbool.h>
+/*
+ * ternary
+ * Boolean value with an extrea "unset" option
+ *
+ * Ternary data type is used in relation options that can be "true", "false" or
+ * "unset". Since relation options are used deep inside the PostgreSQL code,
+ * this type is declared globally.
+*/
+
+typedef enum ternary
+{
+ TERNARY_FALSE = 0,
+ TERNARY_TRUE = 1,
+ TERNARY_UNSET = -1
+} ternary;
+
/* ----------------------------------------------------------------
* Section 3: standard system types
diff --git a/src/include/utils/rel.h b/src/include/utils/rel.h
index b552359915..7346d618f9 100644
--- a/src/include/utils/rel.h
+++ b/src/include/utils/rel.h
@@ -346,8 +346,7 @@ typedef struct StdRdOptions
bool user_catalog_table; /* use as an additional catalog relation */
int parallel_workers; /* max number of parallel workers */
StdRdOptIndexCleanup vacuum_index_cleanup; /* controls index vacuuming */
- bool vacuum_truncate; /* enables vacuum to truncate a relation */
- bool vacuum_truncate_set; /* whether vacuum_truncate is set */
+ ternary vacuum_truncate; /* enables vacuum to truncate a relation */
/*
* Fraction of pages in a relation that vacuum can eagerly scan and fail
--
2.39.2
--nextPart5129932.5fSG56mABF
Content-Disposition: attachment;
filename="v2-0003-Add-alias-to-be-used-as-unset-state.patch"
Content-Transfer-Encoding: 7Bit
Content-Type: text/x-patch; charset="unicode-2-0-utf-8";
name="v2-0003-Add-alias-to-be-used-as-unset-state.patch"
^ permalink raw reply [nested|flat] 868+ messages in thread
* [PATCH v2 2/4] Introduce ternary reloptions
@ 2025-09-04 14:41 Nikolay Shaplov <[email protected]>
0 siblings, 0 replies; 868+ messages in thread
From: Nikolay Shaplov @ 2025-09-04 14:41 UTC (permalink / raw)
Introduce ternary reloption as a replacement for current `vacuum_truncate`
implementation. Remove `vacuum_truncate_set` additional flag and using
`TERNARY_UNSET` value instead.
---
src/backend/access/common/reloptions.c | 129 ++++++++++++++++++++-----
src/backend/commands/vacuum.c | 4 +-
src/include/access/reloptions.h | 26 ++---
src/include/c.h | 16 +++
src/include/utils/rel.h | 3 +-
5 files changed, 135 insertions(+), 43 deletions(-)
diff --git a/src/backend/access/common/reloptions.c b/src/backend/access/common/reloptions.c
index 0af3fea68f..f543f11001 100644
--- a/src/backend/access/common/reloptions.c
+++ b/src/backend/access/common/reloptions.c
@@ -40,9 +40,9 @@
*
* To add an option:
*
- * (i) decide on a type (bool, integer, real, enum, string), name, default
- * value, upper and lower bounds (if applicable); for strings, consider a
- * validation routine.
+ * (i) decide on a type (bool, ternary, integer, real, enum, string), name,
+ * default value, upper and lower bounds (if applicable); for strings,
+ * consider a validation routine.
* (ii) add a record below (or use add_<type>_reloption).
* (iii) add it to the appropriate options struct (perhaps StdRdOptions)
* (iv) add it to the appropriate handling routine (perhaps
@@ -147,15 +147,6 @@ static relopt_bool boolRelOpts[] =
},
false
},
- {
- {
- "vacuum_truncate",
- "Enables vacuum to truncate empty pages at the end of this table",
- RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
- ShareUpdateExclusiveLock
- },
- true
- },
{
{
"deduplicate_items",
@@ -170,6 +161,21 @@ static relopt_bool boolRelOpts[] =
{{NULL}}
};
+static relopt_ternary ternaryRelOpts[] =
+{
+ {
+ {
+ "vacuum_truncate",
+ "Enables vacuum to truncate empty pages at the end of this table",
+ RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
+ ShareUpdateExclusiveLock
+ },
+ TERNARY_UNSET
+ },
+ /* list terminator */
+ {{NULL}}
+};
+
static relopt_int intRelOpts[] =
{
{
@@ -600,6 +606,13 @@ initialize_reloptions(void)
boolRelOpts[i].gen.lockmode));
j++;
}
+ for (i = 0; ternaryRelOpts[i].gen.name; i++)
+ {
+ Assert(DoLockModesConflict(ternaryRelOpts[i].gen.lockmode,
+ ternaryRelOpts[i].gen.lockmode));
+ j++;
+ }
+
for (i = 0; intRelOpts[i].gen.name; i++)
{
Assert(DoLockModesConflict(intRelOpts[i].gen.lockmode,
@@ -640,6 +653,14 @@ initialize_reloptions(void)
j++;
}
+ for (i = 0; ternaryRelOpts[i].gen.name; i++)
+ {
+ relOpts[j] = &ternaryRelOpts[i].gen;
+ relOpts[j]->type = RELOPT_TYPE_TERNARY;
+ relOpts[j]->namelen = strlen(relOpts[j]->name);
+ j++;
+ }
+
for (i = 0; intRelOpts[i].gen.name; i++)
{
relOpts[j] = &intRelOpts[i].gen;
@@ -800,6 +821,9 @@ allocate_reloption(bits32 kinds, int type, const char *name, const char *desc,
case RELOPT_TYPE_BOOL:
size = sizeof(relopt_bool);
break;
+ case RELOPT_TYPE_TERNARY:
+ size = sizeof(relopt_ternary);
+ break;
case RELOPT_TYPE_INT:
size = sizeof(relopt_int);
break;
@@ -883,6 +907,54 @@ add_local_bool_reloption(local_relopts *relopts, const char *name,
add_local_reloption(relopts, (relopt_gen *) newoption, offset);
}
+/*
+ * init_ternary_reloption
+ * Allocate and initialize a new ternary reloption
+ */
+static relopt_ternary *
+init_ternary_reloption(bits32 kinds, const char *name, const char *desc,
+ ternary default_val, LOCKMODE lockmode)
+{
+ relopt_ternary *newoption;
+
+ newoption = (relopt_ternary *) allocate_reloption(kinds,
+ RELOPT_TYPE_TERNARY, name, desc, lockmode);
+ newoption->default_val = default_val;
+
+ return newoption;
+}
+
+/*
+ * add_ternary_reloption
+ * Add a new ternary reloption
+ */
+void
+add_ternary_reloption(bits32 kinds, const char *name, const char *desc,
+ ternary default_val, LOCKMODE lockmode)
+{
+ relopt_ternary *newoption = init_ternary_reloption(kinds, name, desc,
+ default_val, lockmode);
+
+ add_reloption((relopt_gen *) newoption);
+}
+
+/*
+ * add_local_ternary_reloption
+ * Add a new ternary local reloption
+ *
+ * 'offset' is offset of ternary-typed field.
+ */
+void
+add_local_ternary_reloption(local_relopts *relopts, const char *name,
+ const char *desc, ternary default_val,
+ int offset)
+{
+ relopt_ternary *newoption = init_ternary_reloption(RELOPT_KIND_LOCAL,
+ name, desc,
+ default_val, 0);
+
+ add_local_reloption(relopts, (relopt_gen *) newoption, offset);
+}
/*
* init_real_reloption
@@ -1617,6 +1689,18 @@ parse_one_reloption(relopt_value *option, char *text_str, int text_len,
option->gen->name, value)));
}
break;
+ case RELOPT_TYPE_TERNARY:
+ {
+ bool b;
+ parsed = parse_bool(value, &b);
+ option->values.ternary_val = b ? TERNARY_TRUE : TERNARY_FALSE;
+ if (validate && !parsed)
+ ereport(ERROR,
+ (errcode(ERRCODE_INVALID_PARAMETER_VALUE),
+ errmsg("invalid value for ternary option \"%s\": %s",
+ option->gen->name, value)));
+ }
+ break;
case RELOPT_TYPE_INT:
{
relopt_int *optint = (relopt_int *) option->gen;
@@ -1780,17 +1864,6 @@ fillRelOptions(void *rdopts, Size basesize,
char *itempos = ((char *) rdopts) + elems[j].offset;
char *string_val;
- /*
- * If isset_offset is provided, store whether the reloption is
- * set there.
- */
- if (elems[j].isset_offset > 0)
- {
- char *setpos = ((char *) rdopts) + elems[j].isset_offset;
-
- *(bool *) setpos = options[i].isset;
- }
-
switch (options[i].gen->type)
{
case RELOPT_TYPE_BOOL:
@@ -1798,6 +1871,11 @@ fillRelOptions(void *rdopts, Size basesize,
options[i].values.bool_val :
((relopt_bool *) options[i].gen)->default_val;
break;
+ case RELOPT_TYPE_TERNARY:
+ *(ternary *) itempos = options[i].isset ?
+ options[i].values.ternary_val :
+ ((relopt_ternary *) options[i].gen)->default_val;
+ break;
case RELOPT_TYPE_INT:
*(int *) itempos = options[i].isset ?
options[i].values.int_val :
@@ -1912,8 +1990,8 @@ default_reloptions(Datum reloptions, bool validate, relopt_kind kind)
offsetof(StdRdOptions, parallel_workers)},
{"vacuum_index_cleanup", RELOPT_TYPE_ENUM,
offsetof(StdRdOptions, vacuum_index_cleanup)},
- {"vacuum_truncate", RELOPT_TYPE_BOOL,
- offsetof(StdRdOptions, vacuum_truncate), offsetof(StdRdOptions, vacuum_truncate_set)},
+ {"vacuum_truncate", RELOPT_TYPE_TERNARY,
+ offsetof(StdRdOptions, vacuum_truncate)},
{"vacuum_max_eager_freeze_failure_rate", RELOPT_TYPE_REAL,
offsetof(StdRdOptions, vacuum_max_eager_freeze_failure_rate)}
};
@@ -1993,7 +2071,6 @@ build_local_reloptions(local_relopts *relopts, Datum options, bool validate)
elems[i].optname = opt->option->name;
elems[i].opttype = opt->option->type;
elems[i].offset = opt->offset;
- elems[i].isset_offset = 0; /* not supported for local relopts yet */
i++;
}
diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c
index 733ef40ae7..977babff54 100644
--- a/src/backend/commands/vacuum.c
+++ b/src/backend/commands/vacuum.c
@@ -2221,9 +2221,9 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams params,
{
StdRdOptions *opts = (StdRdOptions *) rel->rd_options;
- if (opts && opts->vacuum_truncate_set)
+ if (opts && opts->vacuum_truncate != TERNARY_UNSET)
{
- if (opts->vacuum_truncate)
+ if (opts->vacuum_truncate == TERNARY_TRUE)
params.truncate = VACOPTVALUE_ENABLED;
else
params.truncate = VACOPTVALUE_DISABLED;
diff --git a/src/include/access/reloptions.h b/src/include/access/reloptions.h
index a604a4702c..a436697658 100644
--- a/src/include/access/reloptions.h
+++ b/src/include/access/reloptions.h
@@ -29,6 +29,7 @@
typedef enum relopt_type
{
RELOPT_TYPE_BOOL,
+ RELOPT_TYPE_TERNARY, /* on, off, unset */
RELOPT_TYPE_INT,
RELOPT_TYPE_REAL,
RELOPT_TYPE_ENUM,
@@ -80,6 +81,7 @@ typedef struct relopt_value
union
{
bool bool_val;
+ ternary ternary_val;
int int_val;
double real_val;
int enum_val;
@@ -94,6 +96,12 @@ typedef struct relopt_bool
bool default_val;
} relopt_bool;
+typedef struct relopt_rernary
+{
+ relopt_gen gen;
+ int default_val;
+} relopt_ternary;
+
typedef struct relopt_int
{
relopt_gen gen;
@@ -152,19 +160,6 @@ typedef struct
const char *optname; /* option's name */
relopt_type opttype; /* option's datatype */
int offset; /* offset of field in result struct */
-
- /*
- * isset_offset is an optional offset of a field in the result struct that
- * stores whether the option is explicitly set for the relation or if it
- * just picked up the default value. In most cases, this can be
- * accomplished by giving the reloption a special out-of-range default
- * value (e.g., some integer reloptions use -2), but this isn't always
- * possible. For example, a Boolean reloption cannot be given an
- * out-of-range default, so we need another way to discover the source of
- * its value. This offset is only used if given a value greater than
- * zero.
- */
- int isset_offset;
} relopt_parse_elt;
/* Local reloption definition */
@@ -195,6 +190,8 @@ typedef struct local_relopts
extern relopt_kind add_reloption_kind(void);
extern void add_bool_reloption(bits32 kinds, const char *name, const char *desc,
bool default_val, LOCKMODE lockmode);
+extern void add_ternary_reloption(bits32 kinds, const char *name,
+ const char *desc, int default_val, LOCKMODE lockmode);
extern void add_int_reloption(bits32 kinds, const char *name, const char *desc,
int default_val, int min_val, int max_val,
LOCKMODE lockmode);
@@ -214,6 +211,9 @@ extern void register_reloptions_validator(local_relopts *relopts,
extern void add_local_bool_reloption(local_relopts *relopts, const char *name,
const char *desc, bool default_val,
int offset);
+extern void add_local_ternary_reloption(local_relopts *relopts,
+ const char *name, const char *desc,
+ ternary default_val, int offset);
extern void add_local_int_reloption(local_relopts *relopts, const char *name,
const char *desc, int default_val,
int min_val, int max_val, int offset);
diff --git a/src/include/c.h b/src/include/c.h
index 39022f8a9d..9c59ccccf8 100644
--- a/src/include/c.h
+++ b/src/include/c.h
@@ -488,6 +488,22 @@ typedef void (*pg_funcptr_t) (void);
#include <stdbool.h>
+/*
+ * ternary
+ * Boolean value with an extrea "unset" option
+ *
+ * Ternary data type is used in relation options that can be "true", "false" or
+ * "unset". Since relation options are used deep inside the PostgreSQL code,
+ * this type is declared globally.
+*/
+
+typedef enum ternary
+{
+ TERNARY_FALSE = 0,
+ TERNARY_TRUE = 1,
+ TERNARY_UNSET = -1
+} ternary;
+
/* ----------------------------------------------------------------
* Section 3: standard system types
diff --git a/src/include/utils/rel.h b/src/include/utils/rel.h
index b552359915..7346d618f9 100644
--- a/src/include/utils/rel.h
+++ b/src/include/utils/rel.h
@@ -346,8 +346,7 @@ typedef struct StdRdOptions
bool user_catalog_table; /* use as an additional catalog relation */
int parallel_workers; /* max number of parallel workers */
StdRdOptIndexCleanup vacuum_index_cleanup; /* controls index vacuuming */
- bool vacuum_truncate; /* enables vacuum to truncate a relation */
- bool vacuum_truncate_set; /* whether vacuum_truncate is set */
+ ternary vacuum_truncate; /* enables vacuum to truncate a relation */
/*
* Fraction of pages in a relation that vacuum can eagerly scan and fail
--
2.39.2
--nextPart5129932.5fSG56mABF
Content-Disposition: attachment;
filename="v2-0003-Add-alias-to-be-used-as-unset-state.patch"
Content-Transfer-Encoding: 7Bit
Content-Type: text/x-patch; charset="unicode-2-0-utf-8";
name="v2-0003-Add-alias-to-be-used-as-unset-state.patch"
^ permalink raw reply [nested|flat] 868+ messages in thread
* [PATCH v2 2/4] Introduce ternary reloptions
@ 2025-09-04 14:41 Nikolay Shaplov <[email protected]>
0 siblings, 0 replies; 868+ messages in thread
From: Nikolay Shaplov @ 2025-09-04 14:41 UTC (permalink / raw)
Introduce ternary reloption as a replacement for current `vacuum_truncate`
implementation. Remove `vacuum_truncate_set` additional flag and using
`TERNARY_UNSET` value instead.
---
src/backend/access/common/reloptions.c | 129 ++++++++++++++++++++-----
src/backend/commands/vacuum.c | 4 +-
src/include/access/reloptions.h | 26 ++---
src/include/c.h | 16 +++
src/include/utils/rel.h | 3 +-
5 files changed, 135 insertions(+), 43 deletions(-)
diff --git a/src/backend/access/common/reloptions.c b/src/backend/access/common/reloptions.c
index 0af3fea68f..f543f11001 100644
--- a/src/backend/access/common/reloptions.c
+++ b/src/backend/access/common/reloptions.c
@@ -40,9 +40,9 @@
*
* To add an option:
*
- * (i) decide on a type (bool, integer, real, enum, string), name, default
- * value, upper and lower bounds (if applicable); for strings, consider a
- * validation routine.
+ * (i) decide on a type (bool, ternary, integer, real, enum, string), name,
+ * default value, upper and lower bounds (if applicable); for strings,
+ * consider a validation routine.
* (ii) add a record below (or use add_<type>_reloption).
* (iii) add it to the appropriate options struct (perhaps StdRdOptions)
* (iv) add it to the appropriate handling routine (perhaps
@@ -147,15 +147,6 @@ static relopt_bool boolRelOpts[] =
},
false
},
- {
- {
- "vacuum_truncate",
- "Enables vacuum to truncate empty pages at the end of this table",
- RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
- ShareUpdateExclusiveLock
- },
- true
- },
{
{
"deduplicate_items",
@@ -170,6 +161,21 @@ static relopt_bool boolRelOpts[] =
{{NULL}}
};
+static relopt_ternary ternaryRelOpts[] =
+{
+ {
+ {
+ "vacuum_truncate",
+ "Enables vacuum to truncate empty pages at the end of this table",
+ RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
+ ShareUpdateExclusiveLock
+ },
+ TERNARY_UNSET
+ },
+ /* list terminator */
+ {{NULL}}
+};
+
static relopt_int intRelOpts[] =
{
{
@@ -600,6 +606,13 @@ initialize_reloptions(void)
boolRelOpts[i].gen.lockmode));
j++;
}
+ for (i = 0; ternaryRelOpts[i].gen.name; i++)
+ {
+ Assert(DoLockModesConflict(ternaryRelOpts[i].gen.lockmode,
+ ternaryRelOpts[i].gen.lockmode));
+ j++;
+ }
+
for (i = 0; intRelOpts[i].gen.name; i++)
{
Assert(DoLockModesConflict(intRelOpts[i].gen.lockmode,
@@ -640,6 +653,14 @@ initialize_reloptions(void)
j++;
}
+ for (i = 0; ternaryRelOpts[i].gen.name; i++)
+ {
+ relOpts[j] = &ternaryRelOpts[i].gen;
+ relOpts[j]->type = RELOPT_TYPE_TERNARY;
+ relOpts[j]->namelen = strlen(relOpts[j]->name);
+ j++;
+ }
+
for (i = 0; intRelOpts[i].gen.name; i++)
{
relOpts[j] = &intRelOpts[i].gen;
@@ -800,6 +821,9 @@ allocate_reloption(bits32 kinds, int type, const char *name, const char *desc,
case RELOPT_TYPE_BOOL:
size = sizeof(relopt_bool);
break;
+ case RELOPT_TYPE_TERNARY:
+ size = sizeof(relopt_ternary);
+ break;
case RELOPT_TYPE_INT:
size = sizeof(relopt_int);
break;
@@ -883,6 +907,54 @@ add_local_bool_reloption(local_relopts *relopts, const char *name,
add_local_reloption(relopts, (relopt_gen *) newoption, offset);
}
+/*
+ * init_ternary_reloption
+ * Allocate and initialize a new ternary reloption
+ */
+static relopt_ternary *
+init_ternary_reloption(bits32 kinds, const char *name, const char *desc,
+ ternary default_val, LOCKMODE lockmode)
+{
+ relopt_ternary *newoption;
+
+ newoption = (relopt_ternary *) allocate_reloption(kinds,
+ RELOPT_TYPE_TERNARY, name, desc, lockmode);
+ newoption->default_val = default_val;
+
+ return newoption;
+}
+
+/*
+ * add_ternary_reloption
+ * Add a new ternary reloption
+ */
+void
+add_ternary_reloption(bits32 kinds, const char *name, const char *desc,
+ ternary default_val, LOCKMODE lockmode)
+{
+ relopt_ternary *newoption = init_ternary_reloption(kinds, name, desc,
+ default_val, lockmode);
+
+ add_reloption((relopt_gen *) newoption);
+}
+
+/*
+ * add_local_ternary_reloption
+ * Add a new ternary local reloption
+ *
+ * 'offset' is offset of ternary-typed field.
+ */
+void
+add_local_ternary_reloption(local_relopts *relopts, const char *name,
+ const char *desc, ternary default_val,
+ int offset)
+{
+ relopt_ternary *newoption = init_ternary_reloption(RELOPT_KIND_LOCAL,
+ name, desc,
+ default_val, 0);
+
+ add_local_reloption(relopts, (relopt_gen *) newoption, offset);
+}
/*
* init_real_reloption
@@ -1617,6 +1689,18 @@ parse_one_reloption(relopt_value *option, char *text_str, int text_len,
option->gen->name, value)));
}
break;
+ case RELOPT_TYPE_TERNARY:
+ {
+ bool b;
+ parsed = parse_bool(value, &b);
+ option->values.ternary_val = b ? TERNARY_TRUE : TERNARY_FALSE;
+ if (validate && !parsed)
+ ereport(ERROR,
+ (errcode(ERRCODE_INVALID_PARAMETER_VALUE),
+ errmsg("invalid value for ternary option \"%s\": %s",
+ option->gen->name, value)));
+ }
+ break;
case RELOPT_TYPE_INT:
{
relopt_int *optint = (relopt_int *) option->gen;
@@ -1780,17 +1864,6 @@ fillRelOptions(void *rdopts, Size basesize,
char *itempos = ((char *) rdopts) + elems[j].offset;
char *string_val;
- /*
- * If isset_offset is provided, store whether the reloption is
- * set there.
- */
- if (elems[j].isset_offset > 0)
- {
- char *setpos = ((char *) rdopts) + elems[j].isset_offset;
-
- *(bool *) setpos = options[i].isset;
- }
-
switch (options[i].gen->type)
{
case RELOPT_TYPE_BOOL:
@@ -1798,6 +1871,11 @@ fillRelOptions(void *rdopts, Size basesize,
options[i].values.bool_val :
((relopt_bool *) options[i].gen)->default_val;
break;
+ case RELOPT_TYPE_TERNARY:
+ *(ternary *) itempos = options[i].isset ?
+ options[i].values.ternary_val :
+ ((relopt_ternary *) options[i].gen)->default_val;
+ break;
case RELOPT_TYPE_INT:
*(int *) itempos = options[i].isset ?
options[i].values.int_val :
@@ -1912,8 +1990,8 @@ default_reloptions(Datum reloptions, bool validate, relopt_kind kind)
offsetof(StdRdOptions, parallel_workers)},
{"vacuum_index_cleanup", RELOPT_TYPE_ENUM,
offsetof(StdRdOptions, vacuum_index_cleanup)},
- {"vacuum_truncate", RELOPT_TYPE_BOOL,
- offsetof(StdRdOptions, vacuum_truncate), offsetof(StdRdOptions, vacuum_truncate_set)},
+ {"vacuum_truncate", RELOPT_TYPE_TERNARY,
+ offsetof(StdRdOptions, vacuum_truncate)},
{"vacuum_max_eager_freeze_failure_rate", RELOPT_TYPE_REAL,
offsetof(StdRdOptions, vacuum_max_eager_freeze_failure_rate)}
};
@@ -1993,7 +2071,6 @@ build_local_reloptions(local_relopts *relopts, Datum options, bool validate)
elems[i].optname = opt->option->name;
elems[i].opttype = opt->option->type;
elems[i].offset = opt->offset;
- elems[i].isset_offset = 0; /* not supported for local relopts yet */
i++;
}
diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c
index 733ef40ae7..977babff54 100644
--- a/src/backend/commands/vacuum.c
+++ b/src/backend/commands/vacuum.c
@@ -2221,9 +2221,9 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams params,
{
StdRdOptions *opts = (StdRdOptions *) rel->rd_options;
- if (opts && opts->vacuum_truncate_set)
+ if (opts && opts->vacuum_truncate != TERNARY_UNSET)
{
- if (opts->vacuum_truncate)
+ if (opts->vacuum_truncate == TERNARY_TRUE)
params.truncate = VACOPTVALUE_ENABLED;
else
params.truncate = VACOPTVALUE_DISABLED;
diff --git a/src/include/access/reloptions.h b/src/include/access/reloptions.h
index a604a4702c..a436697658 100644
--- a/src/include/access/reloptions.h
+++ b/src/include/access/reloptions.h
@@ -29,6 +29,7 @@
typedef enum relopt_type
{
RELOPT_TYPE_BOOL,
+ RELOPT_TYPE_TERNARY, /* on, off, unset */
RELOPT_TYPE_INT,
RELOPT_TYPE_REAL,
RELOPT_TYPE_ENUM,
@@ -80,6 +81,7 @@ typedef struct relopt_value
union
{
bool bool_val;
+ ternary ternary_val;
int int_val;
double real_val;
int enum_val;
@@ -94,6 +96,12 @@ typedef struct relopt_bool
bool default_val;
} relopt_bool;
+typedef struct relopt_rernary
+{
+ relopt_gen gen;
+ int default_val;
+} relopt_ternary;
+
typedef struct relopt_int
{
relopt_gen gen;
@@ -152,19 +160,6 @@ typedef struct
const char *optname; /* option's name */
relopt_type opttype; /* option's datatype */
int offset; /* offset of field in result struct */
-
- /*
- * isset_offset is an optional offset of a field in the result struct that
- * stores whether the option is explicitly set for the relation or if it
- * just picked up the default value. In most cases, this can be
- * accomplished by giving the reloption a special out-of-range default
- * value (e.g., some integer reloptions use -2), but this isn't always
- * possible. For example, a Boolean reloption cannot be given an
- * out-of-range default, so we need another way to discover the source of
- * its value. This offset is only used if given a value greater than
- * zero.
- */
- int isset_offset;
} relopt_parse_elt;
/* Local reloption definition */
@@ -195,6 +190,8 @@ typedef struct local_relopts
extern relopt_kind add_reloption_kind(void);
extern void add_bool_reloption(bits32 kinds, const char *name, const char *desc,
bool default_val, LOCKMODE lockmode);
+extern void add_ternary_reloption(bits32 kinds, const char *name,
+ const char *desc, int default_val, LOCKMODE lockmode);
extern void add_int_reloption(bits32 kinds, const char *name, const char *desc,
int default_val, int min_val, int max_val,
LOCKMODE lockmode);
@@ -214,6 +211,9 @@ extern void register_reloptions_validator(local_relopts *relopts,
extern void add_local_bool_reloption(local_relopts *relopts, const char *name,
const char *desc, bool default_val,
int offset);
+extern void add_local_ternary_reloption(local_relopts *relopts,
+ const char *name, const char *desc,
+ ternary default_val, int offset);
extern void add_local_int_reloption(local_relopts *relopts, const char *name,
const char *desc, int default_val,
int min_val, int max_val, int offset);
diff --git a/src/include/c.h b/src/include/c.h
index 39022f8a9d..9c59ccccf8 100644
--- a/src/include/c.h
+++ b/src/include/c.h
@@ -488,6 +488,22 @@ typedef void (*pg_funcptr_t) (void);
#include <stdbool.h>
+/*
+ * ternary
+ * Boolean value with an extrea "unset" option
+ *
+ * Ternary data type is used in relation options that can be "true", "false" or
+ * "unset". Since relation options are used deep inside the PostgreSQL code,
+ * this type is declared globally.
+*/
+
+typedef enum ternary
+{
+ TERNARY_FALSE = 0,
+ TERNARY_TRUE = 1,
+ TERNARY_UNSET = -1
+} ternary;
+
/* ----------------------------------------------------------------
* Section 3: standard system types
diff --git a/src/include/utils/rel.h b/src/include/utils/rel.h
index b552359915..7346d618f9 100644
--- a/src/include/utils/rel.h
+++ b/src/include/utils/rel.h
@@ -346,8 +346,7 @@ typedef struct StdRdOptions
bool user_catalog_table; /* use as an additional catalog relation */
int parallel_workers; /* max number of parallel workers */
StdRdOptIndexCleanup vacuum_index_cleanup; /* controls index vacuuming */
- bool vacuum_truncate; /* enables vacuum to truncate a relation */
- bool vacuum_truncate_set; /* whether vacuum_truncate is set */
+ ternary vacuum_truncate; /* enables vacuum to truncate a relation */
/*
* Fraction of pages in a relation that vacuum can eagerly scan and fail
--
2.39.2
--nextPart5129932.5fSG56mABF
Content-Disposition: attachment;
filename="v2-0003-Add-alias-to-be-used-as-unset-state.patch"
Content-Transfer-Encoding: 7Bit
Content-Type: text/x-patch; charset="unicode-2-0-utf-8";
name="v2-0003-Add-alias-to-be-used-as-unset-state.patch"
^ permalink raw reply [nested|flat] 868+ messages in thread
* [PATCH v2a 2/4] Introduce ternary reloptions
@ 2025-09-04 14:41 Nikolay Shaplov <[email protected]>
0 siblings, 0 replies; 868+ messages in thread
From: Nikolay Shaplov @ 2025-09-04 14:41 UTC (permalink / raw)
Introduce ternary reloption as a replacement for current `vacuum_truncate`
implementation. Remove `vacuum_truncate_set` additional flag and using
`TERNARY_UNSET` value instead.
---
src/backend/access/common/reloptions.c | 129 ++++++++++++++++++++-----
src/backend/commands/vacuum.c | 4 +-
src/include/access/reloptions.h | 26 ++---
src/include/c.h | 16 +++
src/include/utils/rel.h | 3 +-
5 files changed, 135 insertions(+), 43 deletions(-)
diff --git a/src/backend/access/common/reloptions.c b/src/backend/access/common/reloptions.c
index 0af3fea68f..f543f11001 100644
--- a/src/backend/access/common/reloptions.c
+++ b/src/backend/access/common/reloptions.c
@@ -40,9 +40,9 @@
*
* To add an option:
*
- * (i) decide on a type (bool, integer, real, enum, string), name, default
- * value, upper and lower bounds (if applicable); for strings, consider a
- * validation routine.
+ * (i) decide on a type (bool, ternary, integer, real, enum, string), name,
+ * default value, upper and lower bounds (if applicable); for strings,
+ * consider a validation routine.
* (ii) add a record below (or use add_<type>_reloption).
* (iii) add it to the appropriate options struct (perhaps StdRdOptions)
* (iv) add it to the appropriate handling routine (perhaps
@@ -147,15 +147,6 @@ static relopt_bool boolRelOpts[] =
},
false
},
- {
- {
- "vacuum_truncate",
- "Enables vacuum to truncate empty pages at the end of this table",
- RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
- ShareUpdateExclusiveLock
- },
- true
- },
{
{
"deduplicate_items",
@@ -170,6 +161,21 @@ static relopt_bool boolRelOpts[] =
{{NULL}}
};
+static relopt_ternary ternaryRelOpts[] =
+{
+ {
+ {
+ "vacuum_truncate",
+ "Enables vacuum to truncate empty pages at the end of this table",
+ RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
+ ShareUpdateExclusiveLock
+ },
+ TERNARY_UNSET
+ },
+ /* list terminator */
+ {{NULL}}
+};
+
static relopt_int intRelOpts[] =
{
{
@@ -600,6 +606,13 @@ initialize_reloptions(void)
boolRelOpts[i].gen.lockmode));
j++;
}
+ for (i = 0; ternaryRelOpts[i].gen.name; i++)
+ {
+ Assert(DoLockModesConflict(ternaryRelOpts[i].gen.lockmode,
+ ternaryRelOpts[i].gen.lockmode));
+ j++;
+ }
+
for (i = 0; intRelOpts[i].gen.name; i++)
{
Assert(DoLockModesConflict(intRelOpts[i].gen.lockmode,
@@ -640,6 +653,14 @@ initialize_reloptions(void)
j++;
}
+ for (i = 0; ternaryRelOpts[i].gen.name; i++)
+ {
+ relOpts[j] = &ternaryRelOpts[i].gen;
+ relOpts[j]->type = RELOPT_TYPE_TERNARY;
+ relOpts[j]->namelen = strlen(relOpts[j]->name);
+ j++;
+ }
+
for (i = 0; intRelOpts[i].gen.name; i++)
{
relOpts[j] = &intRelOpts[i].gen;
@@ -800,6 +821,9 @@ allocate_reloption(bits32 kinds, int type, const char *name, const char *desc,
case RELOPT_TYPE_BOOL:
size = sizeof(relopt_bool);
break;
+ case RELOPT_TYPE_TERNARY:
+ size = sizeof(relopt_ternary);
+ break;
case RELOPT_TYPE_INT:
size = sizeof(relopt_int);
break;
@@ -883,6 +907,54 @@ add_local_bool_reloption(local_relopts *relopts, const char *name,
add_local_reloption(relopts, (relopt_gen *) newoption, offset);
}
+/*
+ * init_ternary_reloption
+ * Allocate and initialize a new ternary reloption
+ */
+static relopt_ternary *
+init_ternary_reloption(bits32 kinds, const char *name, const char *desc,
+ ternary default_val, LOCKMODE lockmode)
+{
+ relopt_ternary *newoption;
+
+ newoption = (relopt_ternary *) allocate_reloption(kinds,
+ RELOPT_TYPE_TERNARY, name, desc, lockmode);
+ newoption->default_val = default_val;
+
+ return newoption;
+}
+
+/*
+ * add_ternary_reloption
+ * Add a new ternary reloption
+ */
+void
+add_ternary_reloption(bits32 kinds, const char *name, const char *desc,
+ ternary default_val, LOCKMODE lockmode)
+{
+ relopt_ternary *newoption = init_ternary_reloption(kinds, name, desc,
+ default_val, lockmode);
+
+ add_reloption((relopt_gen *) newoption);
+}
+
+/*
+ * add_local_ternary_reloption
+ * Add a new ternary local reloption
+ *
+ * 'offset' is offset of ternary-typed field.
+ */
+void
+add_local_ternary_reloption(local_relopts *relopts, const char *name,
+ const char *desc, ternary default_val,
+ int offset)
+{
+ relopt_ternary *newoption = init_ternary_reloption(RELOPT_KIND_LOCAL,
+ name, desc,
+ default_val, 0);
+
+ add_local_reloption(relopts, (relopt_gen *) newoption, offset);
+}
/*
* init_real_reloption
@@ -1617,6 +1689,18 @@ parse_one_reloption(relopt_value *option, char *text_str, int text_len,
option->gen->name, value)));
}
break;
+ case RELOPT_TYPE_TERNARY:
+ {
+ bool b;
+ parsed = parse_bool(value, &b);
+ option->values.ternary_val = b ? TERNARY_TRUE : TERNARY_FALSE;
+ if (validate && !parsed)
+ ereport(ERROR,
+ (errcode(ERRCODE_INVALID_PARAMETER_VALUE),
+ errmsg("invalid value for ternary option \"%s\": %s",
+ option->gen->name, value)));
+ }
+ break;
case RELOPT_TYPE_INT:
{
relopt_int *optint = (relopt_int *) option->gen;
@@ -1780,17 +1864,6 @@ fillRelOptions(void *rdopts, Size basesize,
char *itempos = ((char *) rdopts) + elems[j].offset;
char *string_val;
- /*
- * If isset_offset is provided, store whether the reloption is
- * set there.
- */
- if (elems[j].isset_offset > 0)
- {
- char *setpos = ((char *) rdopts) + elems[j].isset_offset;
-
- *(bool *) setpos = options[i].isset;
- }
-
switch (options[i].gen->type)
{
case RELOPT_TYPE_BOOL:
@@ -1798,6 +1871,11 @@ fillRelOptions(void *rdopts, Size basesize,
options[i].values.bool_val :
((relopt_bool *) options[i].gen)->default_val;
break;
+ case RELOPT_TYPE_TERNARY:
+ *(ternary *) itempos = options[i].isset ?
+ options[i].values.ternary_val :
+ ((relopt_ternary *) options[i].gen)->default_val;
+ break;
case RELOPT_TYPE_INT:
*(int *) itempos = options[i].isset ?
options[i].values.int_val :
@@ -1912,8 +1990,8 @@ default_reloptions(Datum reloptions, bool validate, relopt_kind kind)
offsetof(StdRdOptions, parallel_workers)},
{"vacuum_index_cleanup", RELOPT_TYPE_ENUM,
offsetof(StdRdOptions, vacuum_index_cleanup)},
- {"vacuum_truncate", RELOPT_TYPE_BOOL,
- offsetof(StdRdOptions, vacuum_truncate), offsetof(StdRdOptions, vacuum_truncate_set)},
+ {"vacuum_truncate", RELOPT_TYPE_TERNARY,
+ offsetof(StdRdOptions, vacuum_truncate)},
{"vacuum_max_eager_freeze_failure_rate", RELOPT_TYPE_REAL,
offsetof(StdRdOptions, vacuum_max_eager_freeze_failure_rate)}
};
@@ -1993,7 +2071,6 @@ build_local_reloptions(local_relopts *relopts, Datum options, bool validate)
elems[i].optname = opt->option->name;
elems[i].opttype = opt->option->type;
elems[i].offset = opt->offset;
- elems[i].isset_offset = 0; /* not supported for local relopts yet */
i++;
}
diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c
index 733ef40ae7..977babff54 100644
--- a/src/backend/commands/vacuum.c
+++ b/src/backend/commands/vacuum.c
@@ -2221,9 +2221,9 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams params,
{
StdRdOptions *opts = (StdRdOptions *) rel->rd_options;
- if (opts && opts->vacuum_truncate_set)
+ if (opts && opts->vacuum_truncate != TERNARY_UNSET)
{
- if (opts->vacuum_truncate)
+ if (opts->vacuum_truncate == TERNARY_TRUE)
params.truncate = VACOPTVALUE_ENABLED;
else
params.truncate = VACOPTVALUE_DISABLED;
diff --git a/src/include/access/reloptions.h b/src/include/access/reloptions.h
index a604a4702c..a436697658 100644
--- a/src/include/access/reloptions.h
+++ b/src/include/access/reloptions.h
@@ -29,6 +29,7 @@
typedef enum relopt_type
{
RELOPT_TYPE_BOOL,
+ RELOPT_TYPE_TERNARY, /* on, off, unset */
RELOPT_TYPE_INT,
RELOPT_TYPE_REAL,
RELOPT_TYPE_ENUM,
@@ -80,6 +81,7 @@ typedef struct relopt_value
union
{
bool bool_val;
+ ternary ternary_val;
int int_val;
double real_val;
int enum_val;
@@ -94,6 +96,12 @@ typedef struct relopt_bool
bool default_val;
} relopt_bool;
+typedef struct relopt_rernary
+{
+ relopt_gen gen;
+ int default_val;
+} relopt_ternary;
+
typedef struct relopt_int
{
relopt_gen gen;
@@ -152,19 +160,6 @@ typedef struct
const char *optname; /* option's name */
relopt_type opttype; /* option's datatype */
int offset; /* offset of field in result struct */
-
- /*
- * isset_offset is an optional offset of a field in the result struct that
- * stores whether the option is explicitly set for the relation or if it
- * just picked up the default value. In most cases, this can be
- * accomplished by giving the reloption a special out-of-range default
- * value (e.g., some integer reloptions use -2), but this isn't always
- * possible. For example, a Boolean reloption cannot be given an
- * out-of-range default, so we need another way to discover the source of
- * its value. This offset is only used if given a value greater than
- * zero.
- */
- int isset_offset;
} relopt_parse_elt;
/* Local reloption definition */
@@ -195,6 +190,8 @@ typedef struct local_relopts
extern relopt_kind add_reloption_kind(void);
extern void add_bool_reloption(bits32 kinds, const char *name, const char *desc,
bool default_val, LOCKMODE lockmode);
+extern void add_ternary_reloption(bits32 kinds, const char *name,
+ const char *desc, int default_val, LOCKMODE lockmode);
extern void add_int_reloption(bits32 kinds, const char *name, const char *desc,
int default_val, int min_val, int max_val,
LOCKMODE lockmode);
@@ -214,6 +211,9 @@ extern void register_reloptions_validator(local_relopts *relopts,
extern void add_local_bool_reloption(local_relopts *relopts, const char *name,
const char *desc, bool default_val,
int offset);
+extern void add_local_ternary_reloption(local_relopts *relopts,
+ const char *name, const char *desc,
+ ternary default_val, int offset);
extern void add_local_int_reloption(local_relopts *relopts, const char *name,
const char *desc, int default_val,
int min_val, int max_val, int offset);
diff --git a/src/include/c.h b/src/include/c.h
index 39022f8a9d..9c59ccccf8 100644
--- a/src/include/c.h
+++ b/src/include/c.h
@@ -488,6 +488,22 @@ typedef void (*pg_funcptr_t) (void);
#include <stdbool.h>
+/*
+ * ternary
+ * Boolean value with an extrea "unset" option
+ *
+ * Ternary data type is used in relation options that can be "true", "false" or
+ * "unset". Since relation options are used deep inside the PostgreSQL code,
+ * this type is declared globally.
+*/
+
+typedef enum ternary
+{
+ TERNARY_FALSE = 0,
+ TERNARY_TRUE = 1,
+ TERNARY_UNSET = -1
+} ternary;
+
/* ----------------------------------------------------------------
* Section 3: standard system types
diff --git a/src/include/utils/rel.h b/src/include/utils/rel.h
index b552359915..7346d618f9 100644
--- a/src/include/utils/rel.h
+++ b/src/include/utils/rel.h
@@ -346,8 +346,7 @@ typedef struct StdRdOptions
bool user_catalog_table; /* use as an additional catalog relation */
int parallel_workers; /* max number of parallel workers */
StdRdOptIndexCleanup vacuum_index_cleanup; /* controls index vacuuming */
- bool vacuum_truncate; /* enables vacuum to truncate a relation */
- bool vacuum_truncate_set; /* whether vacuum_truncate is set */
+ ternary vacuum_truncate; /* enables vacuum to truncate a relation */
/*
* Fraction of pages in a relation that vacuum can eagerly scan and fail
--
2.39.2
--nextPart4155600.3ZeAukHxDK
Content-Disposition: attachment;
filename="v2a-0003-Add-alias-to-be-used-as-unset-state.patch"
Content-Transfer-Encoding: 7Bit
Content-Type: text/x-patch; charset="unicode-2-0-utf-8";
name="v2a-0003-Add-alias-to-be-used-as-unset-state.patch"
^ permalink raw reply [nested|flat] 868+ messages in thread
* [PATCH v2 2/4] Introduce ternary reloptions
@ 2025-09-04 14:41 Nikolay Shaplov <[email protected]>
0 siblings, 0 replies; 868+ messages in thread
From: Nikolay Shaplov @ 2025-09-04 14:41 UTC (permalink / raw)
Introduce ternary reloption as a replacement for current `vacuum_truncate`
implementation. Remove `vacuum_truncate_set` additional flag and using
`TERNARY_UNSET` value instead.
---
src/backend/access/common/reloptions.c | 129 ++++++++++++++++++++-----
src/backend/commands/vacuum.c | 4 +-
src/include/access/reloptions.h | 26 ++---
src/include/c.h | 16 +++
src/include/utils/rel.h | 3 +-
5 files changed, 135 insertions(+), 43 deletions(-)
diff --git a/src/backend/access/common/reloptions.c b/src/backend/access/common/reloptions.c
index 0af3fea68f..f543f11001 100644
--- a/src/backend/access/common/reloptions.c
+++ b/src/backend/access/common/reloptions.c
@@ -40,9 +40,9 @@
*
* To add an option:
*
- * (i) decide on a type (bool, integer, real, enum, string), name, default
- * value, upper and lower bounds (if applicable); for strings, consider a
- * validation routine.
+ * (i) decide on a type (bool, ternary, integer, real, enum, string), name,
+ * default value, upper and lower bounds (if applicable); for strings,
+ * consider a validation routine.
* (ii) add a record below (or use add_<type>_reloption).
* (iii) add it to the appropriate options struct (perhaps StdRdOptions)
* (iv) add it to the appropriate handling routine (perhaps
@@ -147,15 +147,6 @@ static relopt_bool boolRelOpts[] =
},
false
},
- {
- {
- "vacuum_truncate",
- "Enables vacuum to truncate empty pages at the end of this table",
- RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
- ShareUpdateExclusiveLock
- },
- true
- },
{
{
"deduplicate_items",
@@ -170,6 +161,21 @@ static relopt_bool boolRelOpts[] =
{{NULL}}
};
+static relopt_ternary ternaryRelOpts[] =
+{
+ {
+ {
+ "vacuum_truncate",
+ "Enables vacuum to truncate empty pages at the end of this table",
+ RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
+ ShareUpdateExclusiveLock
+ },
+ TERNARY_UNSET
+ },
+ /* list terminator */
+ {{NULL}}
+};
+
static relopt_int intRelOpts[] =
{
{
@@ -600,6 +606,13 @@ initialize_reloptions(void)
boolRelOpts[i].gen.lockmode));
j++;
}
+ for (i = 0; ternaryRelOpts[i].gen.name; i++)
+ {
+ Assert(DoLockModesConflict(ternaryRelOpts[i].gen.lockmode,
+ ternaryRelOpts[i].gen.lockmode));
+ j++;
+ }
+
for (i = 0; intRelOpts[i].gen.name; i++)
{
Assert(DoLockModesConflict(intRelOpts[i].gen.lockmode,
@@ -640,6 +653,14 @@ initialize_reloptions(void)
j++;
}
+ for (i = 0; ternaryRelOpts[i].gen.name; i++)
+ {
+ relOpts[j] = &ternaryRelOpts[i].gen;
+ relOpts[j]->type = RELOPT_TYPE_TERNARY;
+ relOpts[j]->namelen = strlen(relOpts[j]->name);
+ j++;
+ }
+
for (i = 0; intRelOpts[i].gen.name; i++)
{
relOpts[j] = &intRelOpts[i].gen;
@@ -800,6 +821,9 @@ allocate_reloption(bits32 kinds, int type, const char *name, const char *desc,
case RELOPT_TYPE_BOOL:
size = sizeof(relopt_bool);
break;
+ case RELOPT_TYPE_TERNARY:
+ size = sizeof(relopt_ternary);
+ break;
case RELOPT_TYPE_INT:
size = sizeof(relopt_int);
break;
@@ -883,6 +907,54 @@ add_local_bool_reloption(local_relopts *relopts, const char *name,
add_local_reloption(relopts, (relopt_gen *) newoption, offset);
}
+/*
+ * init_ternary_reloption
+ * Allocate and initialize a new ternary reloption
+ */
+static relopt_ternary *
+init_ternary_reloption(bits32 kinds, const char *name, const char *desc,
+ ternary default_val, LOCKMODE lockmode)
+{
+ relopt_ternary *newoption;
+
+ newoption = (relopt_ternary *) allocate_reloption(kinds,
+ RELOPT_TYPE_TERNARY, name, desc, lockmode);
+ newoption->default_val = default_val;
+
+ return newoption;
+}
+
+/*
+ * add_ternary_reloption
+ * Add a new ternary reloption
+ */
+void
+add_ternary_reloption(bits32 kinds, const char *name, const char *desc,
+ ternary default_val, LOCKMODE lockmode)
+{
+ relopt_ternary *newoption = init_ternary_reloption(kinds, name, desc,
+ default_val, lockmode);
+
+ add_reloption((relopt_gen *) newoption);
+}
+
+/*
+ * add_local_ternary_reloption
+ * Add a new ternary local reloption
+ *
+ * 'offset' is offset of ternary-typed field.
+ */
+void
+add_local_ternary_reloption(local_relopts *relopts, const char *name,
+ const char *desc, ternary default_val,
+ int offset)
+{
+ relopt_ternary *newoption = init_ternary_reloption(RELOPT_KIND_LOCAL,
+ name, desc,
+ default_val, 0);
+
+ add_local_reloption(relopts, (relopt_gen *) newoption, offset);
+}
/*
* init_real_reloption
@@ -1617,6 +1689,18 @@ parse_one_reloption(relopt_value *option, char *text_str, int text_len,
option->gen->name, value)));
}
break;
+ case RELOPT_TYPE_TERNARY:
+ {
+ bool b;
+ parsed = parse_bool(value, &b);
+ option->values.ternary_val = b ? TERNARY_TRUE : TERNARY_FALSE;
+ if (validate && !parsed)
+ ereport(ERROR,
+ (errcode(ERRCODE_INVALID_PARAMETER_VALUE),
+ errmsg("invalid value for ternary option \"%s\": %s",
+ option->gen->name, value)));
+ }
+ break;
case RELOPT_TYPE_INT:
{
relopt_int *optint = (relopt_int *) option->gen;
@@ -1780,17 +1864,6 @@ fillRelOptions(void *rdopts, Size basesize,
char *itempos = ((char *) rdopts) + elems[j].offset;
char *string_val;
- /*
- * If isset_offset is provided, store whether the reloption is
- * set there.
- */
- if (elems[j].isset_offset > 0)
- {
- char *setpos = ((char *) rdopts) + elems[j].isset_offset;
-
- *(bool *) setpos = options[i].isset;
- }
-
switch (options[i].gen->type)
{
case RELOPT_TYPE_BOOL:
@@ -1798,6 +1871,11 @@ fillRelOptions(void *rdopts, Size basesize,
options[i].values.bool_val :
((relopt_bool *) options[i].gen)->default_val;
break;
+ case RELOPT_TYPE_TERNARY:
+ *(ternary *) itempos = options[i].isset ?
+ options[i].values.ternary_val :
+ ((relopt_ternary *) options[i].gen)->default_val;
+ break;
case RELOPT_TYPE_INT:
*(int *) itempos = options[i].isset ?
options[i].values.int_val :
@@ -1912,8 +1990,8 @@ default_reloptions(Datum reloptions, bool validate, relopt_kind kind)
offsetof(StdRdOptions, parallel_workers)},
{"vacuum_index_cleanup", RELOPT_TYPE_ENUM,
offsetof(StdRdOptions, vacuum_index_cleanup)},
- {"vacuum_truncate", RELOPT_TYPE_BOOL,
- offsetof(StdRdOptions, vacuum_truncate), offsetof(StdRdOptions, vacuum_truncate_set)},
+ {"vacuum_truncate", RELOPT_TYPE_TERNARY,
+ offsetof(StdRdOptions, vacuum_truncate)},
{"vacuum_max_eager_freeze_failure_rate", RELOPT_TYPE_REAL,
offsetof(StdRdOptions, vacuum_max_eager_freeze_failure_rate)}
};
@@ -1993,7 +2071,6 @@ build_local_reloptions(local_relopts *relopts, Datum options, bool validate)
elems[i].optname = opt->option->name;
elems[i].opttype = opt->option->type;
elems[i].offset = opt->offset;
- elems[i].isset_offset = 0; /* not supported for local relopts yet */
i++;
}
diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c
index 733ef40ae7..977babff54 100644
--- a/src/backend/commands/vacuum.c
+++ b/src/backend/commands/vacuum.c
@@ -2221,9 +2221,9 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams params,
{
StdRdOptions *opts = (StdRdOptions *) rel->rd_options;
- if (opts && opts->vacuum_truncate_set)
+ if (opts && opts->vacuum_truncate != TERNARY_UNSET)
{
- if (opts->vacuum_truncate)
+ if (opts->vacuum_truncate == TERNARY_TRUE)
params.truncate = VACOPTVALUE_ENABLED;
else
params.truncate = VACOPTVALUE_DISABLED;
diff --git a/src/include/access/reloptions.h b/src/include/access/reloptions.h
index a604a4702c..a436697658 100644
--- a/src/include/access/reloptions.h
+++ b/src/include/access/reloptions.h
@@ -29,6 +29,7 @@
typedef enum relopt_type
{
RELOPT_TYPE_BOOL,
+ RELOPT_TYPE_TERNARY, /* on, off, unset */
RELOPT_TYPE_INT,
RELOPT_TYPE_REAL,
RELOPT_TYPE_ENUM,
@@ -80,6 +81,7 @@ typedef struct relopt_value
union
{
bool bool_val;
+ ternary ternary_val;
int int_val;
double real_val;
int enum_val;
@@ -94,6 +96,12 @@ typedef struct relopt_bool
bool default_val;
} relopt_bool;
+typedef struct relopt_rernary
+{
+ relopt_gen gen;
+ int default_val;
+} relopt_ternary;
+
typedef struct relopt_int
{
relopt_gen gen;
@@ -152,19 +160,6 @@ typedef struct
const char *optname; /* option's name */
relopt_type opttype; /* option's datatype */
int offset; /* offset of field in result struct */
-
- /*
- * isset_offset is an optional offset of a field in the result struct that
- * stores whether the option is explicitly set for the relation or if it
- * just picked up the default value. In most cases, this can be
- * accomplished by giving the reloption a special out-of-range default
- * value (e.g., some integer reloptions use -2), but this isn't always
- * possible. For example, a Boolean reloption cannot be given an
- * out-of-range default, so we need another way to discover the source of
- * its value. This offset is only used if given a value greater than
- * zero.
- */
- int isset_offset;
} relopt_parse_elt;
/* Local reloption definition */
@@ -195,6 +190,8 @@ typedef struct local_relopts
extern relopt_kind add_reloption_kind(void);
extern void add_bool_reloption(bits32 kinds, const char *name, const char *desc,
bool default_val, LOCKMODE lockmode);
+extern void add_ternary_reloption(bits32 kinds, const char *name,
+ const char *desc, int default_val, LOCKMODE lockmode);
extern void add_int_reloption(bits32 kinds, const char *name, const char *desc,
int default_val, int min_val, int max_val,
LOCKMODE lockmode);
@@ -214,6 +211,9 @@ extern void register_reloptions_validator(local_relopts *relopts,
extern void add_local_bool_reloption(local_relopts *relopts, const char *name,
const char *desc, bool default_val,
int offset);
+extern void add_local_ternary_reloption(local_relopts *relopts,
+ const char *name, const char *desc,
+ ternary default_val, int offset);
extern void add_local_int_reloption(local_relopts *relopts, const char *name,
const char *desc, int default_val,
int min_val, int max_val, int offset);
diff --git a/src/include/c.h b/src/include/c.h
index 39022f8a9d..9c59ccccf8 100644
--- a/src/include/c.h
+++ b/src/include/c.h
@@ -488,6 +488,22 @@ typedef void (*pg_funcptr_t) (void);
#include <stdbool.h>
+/*
+ * ternary
+ * Boolean value with an extrea "unset" option
+ *
+ * Ternary data type is used in relation options that can be "true", "false" or
+ * "unset". Since relation options are used deep inside the PostgreSQL code,
+ * this type is declared globally.
+*/
+
+typedef enum ternary
+{
+ TERNARY_FALSE = 0,
+ TERNARY_TRUE = 1,
+ TERNARY_UNSET = -1
+} ternary;
+
/* ----------------------------------------------------------------
* Section 3: standard system types
diff --git a/src/include/utils/rel.h b/src/include/utils/rel.h
index b552359915..7346d618f9 100644
--- a/src/include/utils/rel.h
+++ b/src/include/utils/rel.h
@@ -346,8 +346,7 @@ typedef struct StdRdOptions
bool user_catalog_table; /* use as an additional catalog relation */
int parallel_workers; /* max number of parallel workers */
StdRdOptIndexCleanup vacuum_index_cleanup; /* controls index vacuuming */
- bool vacuum_truncate; /* enables vacuum to truncate a relation */
- bool vacuum_truncate_set; /* whether vacuum_truncate is set */
+ ternary vacuum_truncate; /* enables vacuum to truncate a relation */
/*
* Fraction of pages in a relation that vacuum can eagerly scan and fail
--
2.39.2
--nextPart5129932.5fSG56mABF
Content-Disposition: attachment;
filename="v2-0003-Add-alias-to-be-used-as-unset-state.patch"
Content-Transfer-Encoding: 7Bit
Content-Type: text/x-patch; charset="unicode-2-0-utf-8";
name="v2-0003-Add-alias-to-be-used-as-unset-state.patch"
^ permalink raw reply [nested|flat] 868+ messages in thread
* [PATCH v2a 2/4] Introduce ternary reloptions
@ 2025-09-04 14:41 Nikolay Shaplov <[email protected]>
0 siblings, 0 replies; 868+ messages in thread
From: Nikolay Shaplov @ 2025-09-04 14:41 UTC (permalink / raw)
Introduce ternary reloption as a replacement for current `vacuum_truncate`
implementation. Remove `vacuum_truncate_set` additional flag and using
`TERNARY_UNSET` value instead.
---
src/backend/access/common/reloptions.c | 129 ++++++++++++++++++++-----
src/backend/commands/vacuum.c | 4 +-
src/include/access/reloptions.h | 26 ++---
src/include/c.h | 16 +++
src/include/utils/rel.h | 3 +-
5 files changed, 135 insertions(+), 43 deletions(-)
diff --git a/src/backend/access/common/reloptions.c b/src/backend/access/common/reloptions.c
index 0af3fea68f..f543f11001 100644
--- a/src/backend/access/common/reloptions.c
+++ b/src/backend/access/common/reloptions.c
@@ -40,9 +40,9 @@
*
* To add an option:
*
- * (i) decide on a type (bool, integer, real, enum, string), name, default
- * value, upper and lower bounds (if applicable); for strings, consider a
- * validation routine.
+ * (i) decide on a type (bool, ternary, integer, real, enum, string), name,
+ * default value, upper and lower bounds (if applicable); for strings,
+ * consider a validation routine.
* (ii) add a record below (or use add_<type>_reloption).
* (iii) add it to the appropriate options struct (perhaps StdRdOptions)
* (iv) add it to the appropriate handling routine (perhaps
@@ -147,15 +147,6 @@ static relopt_bool boolRelOpts[] =
},
false
},
- {
- {
- "vacuum_truncate",
- "Enables vacuum to truncate empty pages at the end of this table",
- RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
- ShareUpdateExclusiveLock
- },
- true
- },
{
{
"deduplicate_items",
@@ -170,6 +161,21 @@ static relopt_bool boolRelOpts[] =
{{NULL}}
};
+static relopt_ternary ternaryRelOpts[] =
+{
+ {
+ {
+ "vacuum_truncate",
+ "Enables vacuum to truncate empty pages at the end of this table",
+ RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
+ ShareUpdateExclusiveLock
+ },
+ TERNARY_UNSET
+ },
+ /* list terminator */
+ {{NULL}}
+};
+
static relopt_int intRelOpts[] =
{
{
@@ -600,6 +606,13 @@ initialize_reloptions(void)
boolRelOpts[i].gen.lockmode));
j++;
}
+ for (i = 0; ternaryRelOpts[i].gen.name; i++)
+ {
+ Assert(DoLockModesConflict(ternaryRelOpts[i].gen.lockmode,
+ ternaryRelOpts[i].gen.lockmode));
+ j++;
+ }
+
for (i = 0; intRelOpts[i].gen.name; i++)
{
Assert(DoLockModesConflict(intRelOpts[i].gen.lockmode,
@@ -640,6 +653,14 @@ initialize_reloptions(void)
j++;
}
+ for (i = 0; ternaryRelOpts[i].gen.name; i++)
+ {
+ relOpts[j] = &ternaryRelOpts[i].gen;
+ relOpts[j]->type = RELOPT_TYPE_TERNARY;
+ relOpts[j]->namelen = strlen(relOpts[j]->name);
+ j++;
+ }
+
for (i = 0; intRelOpts[i].gen.name; i++)
{
relOpts[j] = &intRelOpts[i].gen;
@@ -800,6 +821,9 @@ allocate_reloption(bits32 kinds, int type, const char *name, const char *desc,
case RELOPT_TYPE_BOOL:
size = sizeof(relopt_bool);
break;
+ case RELOPT_TYPE_TERNARY:
+ size = sizeof(relopt_ternary);
+ break;
case RELOPT_TYPE_INT:
size = sizeof(relopt_int);
break;
@@ -883,6 +907,54 @@ add_local_bool_reloption(local_relopts *relopts, const char *name,
add_local_reloption(relopts, (relopt_gen *) newoption, offset);
}
+/*
+ * init_ternary_reloption
+ * Allocate and initialize a new ternary reloption
+ */
+static relopt_ternary *
+init_ternary_reloption(bits32 kinds, const char *name, const char *desc,
+ ternary default_val, LOCKMODE lockmode)
+{
+ relopt_ternary *newoption;
+
+ newoption = (relopt_ternary *) allocate_reloption(kinds,
+ RELOPT_TYPE_TERNARY, name, desc, lockmode);
+ newoption->default_val = default_val;
+
+ return newoption;
+}
+
+/*
+ * add_ternary_reloption
+ * Add a new ternary reloption
+ */
+void
+add_ternary_reloption(bits32 kinds, const char *name, const char *desc,
+ ternary default_val, LOCKMODE lockmode)
+{
+ relopt_ternary *newoption = init_ternary_reloption(kinds, name, desc,
+ default_val, lockmode);
+
+ add_reloption((relopt_gen *) newoption);
+}
+
+/*
+ * add_local_ternary_reloption
+ * Add a new ternary local reloption
+ *
+ * 'offset' is offset of ternary-typed field.
+ */
+void
+add_local_ternary_reloption(local_relopts *relopts, const char *name,
+ const char *desc, ternary default_val,
+ int offset)
+{
+ relopt_ternary *newoption = init_ternary_reloption(RELOPT_KIND_LOCAL,
+ name, desc,
+ default_val, 0);
+
+ add_local_reloption(relopts, (relopt_gen *) newoption, offset);
+}
/*
* init_real_reloption
@@ -1617,6 +1689,18 @@ parse_one_reloption(relopt_value *option, char *text_str, int text_len,
option->gen->name, value)));
}
break;
+ case RELOPT_TYPE_TERNARY:
+ {
+ bool b;
+ parsed = parse_bool(value, &b);
+ option->values.ternary_val = b ? TERNARY_TRUE : TERNARY_FALSE;
+ if (validate && !parsed)
+ ereport(ERROR,
+ (errcode(ERRCODE_INVALID_PARAMETER_VALUE),
+ errmsg("invalid value for ternary option \"%s\": %s",
+ option->gen->name, value)));
+ }
+ break;
case RELOPT_TYPE_INT:
{
relopt_int *optint = (relopt_int *) option->gen;
@@ -1780,17 +1864,6 @@ fillRelOptions(void *rdopts, Size basesize,
char *itempos = ((char *) rdopts) + elems[j].offset;
char *string_val;
- /*
- * If isset_offset is provided, store whether the reloption is
- * set there.
- */
- if (elems[j].isset_offset > 0)
- {
- char *setpos = ((char *) rdopts) + elems[j].isset_offset;
-
- *(bool *) setpos = options[i].isset;
- }
-
switch (options[i].gen->type)
{
case RELOPT_TYPE_BOOL:
@@ -1798,6 +1871,11 @@ fillRelOptions(void *rdopts, Size basesize,
options[i].values.bool_val :
((relopt_bool *) options[i].gen)->default_val;
break;
+ case RELOPT_TYPE_TERNARY:
+ *(ternary *) itempos = options[i].isset ?
+ options[i].values.ternary_val :
+ ((relopt_ternary *) options[i].gen)->default_val;
+ break;
case RELOPT_TYPE_INT:
*(int *) itempos = options[i].isset ?
options[i].values.int_val :
@@ -1912,8 +1990,8 @@ default_reloptions(Datum reloptions, bool validate, relopt_kind kind)
offsetof(StdRdOptions, parallel_workers)},
{"vacuum_index_cleanup", RELOPT_TYPE_ENUM,
offsetof(StdRdOptions, vacuum_index_cleanup)},
- {"vacuum_truncate", RELOPT_TYPE_BOOL,
- offsetof(StdRdOptions, vacuum_truncate), offsetof(StdRdOptions, vacuum_truncate_set)},
+ {"vacuum_truncate", RELOPT_TYPE_TERNARY,
+ offsetof(StdRdOptions, vacuum_truncate)},
{"vacuum_max_eager_freeze_failure_rate", RELOPT_TYPE_REAL,
offsetof(StdRdOptions, vacuum_max_eager_freeze_failure_rate)}
};
@@ -1993,7 +2071,6 @@ build_local_reloptions(local_relopts *relopts, Datum options, bool validate)
elems[i].optname = opt->option->name;
elems[i].opttype = opt->option->type;
elems[i].offset = opt->offset;
- elems[i].isset_offset = 0; /* not supported for local relopts yet */
i++;
}
diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c
index 733ef40ae7..977babff54 100644
--- a/src/backend/commands/vacuum.c
+++ b/src/backend/commands/vacuum.c
@@ -2221,9 +2221,9 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams params,
{
StdRdOptions *opts = (StdRdOptions *) rel->rd_options;
- if (opts && opts->vacuum_truncate_set)
+ if (opts && opts->vacuum_truncate != TERNARY_UNSET)
{
- if (opts->vacuum_truncate)
+ if (opts->vacuum_truncate == TERNARY_TRUE)
params.truncate = VACOPTVALUE_ENABLED;
else
params.truncate = VACOPTVALUE_DISABLED;
diff --git a/src/include/access/reloptions.h b/src/include/access/reloptions.h
index a604a4702c..a436697658 100644
--- a/src/include/access/reloptions.h
+++ b/src/include/access/reloptions.h
@@ -29,6 +29,7 @@
typedef enum relopt_type
{
RELOPT_TYPE_BOOL,
+ RELOPT_TYPE_TERNARY, /* on, off, unset */
RELOPT_TYPE_INT,
RELOPT_TYPE_REAL,
RELOPT_TYPE_ENUM,
@@ -80,6 +81,7 @@ typedef struct relopt_value
union
{
bool bool_val;
+ ternary ternary_val;
int int_val;
double real_val;
int enum_val;
@@ -94,6 +96,12 @@ typedef struct relopt_bool
bool default_val;
} relopt_bool;
+typedef struct relopt_rernary
+{
+ relopt_gen gen;
+ int default_val;
+} relopt_ternary;
+
typedef struct relopt_int
{
relopt_gen gen;
@@ -152,19 +160,6 @@ typedef struct
const char *optname; /* option's name */
relopt_type opttype; /* option's datatype */
int offset; /* offset of field in result struct */
-
- /*
- * isset_offset is an optional offset of a field in the result struct that
- * stores whether the option is explicitly set for the relation or if it
- * just picked up the default value. In most cases, this can be
- * accomplished by giving the reloption a special out-of-range default
- * value (e.g., some integer reloptions use -2), but this isn't always
- * possible. For example, a Boolean reloption cannot be given an
- * out-of-range default, so we need another way to discover the source of
- * its value. This offset is only used if given a value greater than
- * zero.
- */
- int isset_offset;
} relopt_parse_elt;
/* Local reloption definition */
@@ -195,6 +190,8 @@ typedef struct local_relopts
extern relopt_kind add_reloption_kind(void);
extern void add_bool_reloption(bits32 kinds, const char *name, const char *desc,
bool default_val, LOCKMODE lockmode);
+extern void add_ternary_reloption(bits32 kinds, const char *name,
+ const char *desc, int default_val, LOCKMODE lockmode);
extern void add_int_reloption(bits32 kinds, const char *name, const char *desc,
int default_val, int min_val, int max_val,
LOCKMODE lockmode);
@@ -214,6 +211,9 @@ extern void register_reloptions_validator(local_relopts *relopts,
extern void add_local_bool_reloption(local_relopts *relopts, const char *name,
const char *desc, bool default_val,
int offset);
+extern void add_local_ternary_reloption(local_relopts *relopts,
+ const char *name, const char *desc,
+ ternary default_val, int offset);
extern void add_local_int_reloption(local_relopts *relopts, const char *name,
const char *desc, int default_val,
int min_val, int max_val, int offset);
diff --git a/src/include/c.h b/src/include/c.h
index 39022f8a9d..9c59ccccf8 100644
--- a/src/include/c.h
+++ b/src/include/c.h
@@ -488,6 +488,22 @@ typedef void (*pg_funcptr_t) (void);
#include <stdbool.h>
+/*
+ * ternary
+ * Boolean value with an extrea "unset" option
+ *
+ * Ternary data type is used in relation options that can be "true", "false" or
+ * "unset". Since relation options are used deep inside the PostgreSQL code,
+ * this type is declared globally.
+*/
+
+typedef enum ternary
+{
+ TERNARY_FALSE = 0,
+ TERNARY_TRUE = 1,
+ TERNARY_UNSET = -1
+} ternary;
+
/* ----------------------------------------------------------------
* Section 3: standard system types
diff --git a/src/include/utils/rel.h b/src/include/utils/rel.h
index b552359915..7346d618f9 100644
--- a/src/include/utils/rel.h
+++ b/src/include/utils/rel.h
@@ -346,8 +346,7 @@ typedef struct StdRdOptions
bool user_catalog_table; /* use as an additional catalog relation */
int parallel_workers; /* max number of parallel workers */
StdRdOptIndexCleanup vacuum_index_cleanup; /* controls index vacuuming */
- bool vacuum_truncate; /* enables vacuum to truncate a relation */
- bool vacuum_truncate_set; /* whether vacuum_truncate is set */
+ ternary vacuum_truncate; /* enables vacuum to truncate a relation */
/*
* Fraction of pages in a relation that vacuum can eagerly scan and fail
--
2.39.2
--nextPart4155600.3ZeAukHxDK
Content-Disposition: attachment;
filename="v2a-0003-Add-alias-to-be-used-as-unset-state.patch"
Content-Transfer-Encoding: 7Bit
Content-Type: text/x-patch; charset="unicode-2-0-utf-8";
name="v2a-0003-Add-alias-to-be-used-as-unset-state.patch"
^ permalink raw reply [nested|flat] 868+ messages in thread
* [PATCH v2a 2/4] Introduce ternary reloptions
@ 2025-09-04 14:41 Nikolay Shaplov <[email protected]>
0 siblings, 0 replies; 868+ messages in thread
From: Nikolay Shaplov @ 2025-09-04 14:41 UTC (permalink / raw)
Introduce ternary reloption as a replacement for current `vacuum_truncate`
implementation. Remove `vacuum_truncate_set` additional flag and using
`TERNARY_UNSET` value instead.
---
src/backend/access/common/reloptions.c | 129 ++++++++++++++++++++-----
src/backend/commands/vacuum.c | 4 +-
src/include/access/reloptions.h | 26 ++---
src/include/c.h | 16 +++
src/include/utils/rel.h | 3 +-
5 files changed, 135 insertions(+), 43 deletions(-)
diff --git a/src/backend/access/common/reloptions.c b/src/backend/access/common/reloptions.c
index 0af3fea68f..f543f11001 100644
--- a/src/backend/access/common/reloptions.c
+++ b/src/backend/access/common/reloptions.c
@@ -40,9 +40,9 @@
*
* To add an option:
*
- * (i) decide on a type (bool, integer, real, enum, string), name, default
- * value, upper and lower bounds (if applicable); for strings, consider a
- * validation routine.
+ * (i) decide on a type (bool, ternary, integer, real, enum, string), name,
+ * default value, upper and lower bounds (if applicable); for strings,
+ * consider a validation routine.
* (ii) add a record below (or use add_<type>_reloption).
* (iii) add it to the appropriate options struct (perhaps StdRdOptions)
* (iv) add it to the appropriate handling routine (perhaps
@@ -147,15 +147,6 @@ static relopt_bool boolRelOpts[] =
},
false
},
- {
- {
- "vacuum_truncate",
- "Enables vacuum to truncate empty pages at the end of this table",
- RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
- ShareUpdateExclusiveLock
- },
- true
- },
{
{
"deduplicate_items",
@@ -170,6 +161,21 @@ static relopt_bool boolRelOpts[] =
{{NULL}}
};
+static relopt_ternary ternaryRelOpts[] =
+{
+ {
+ {
+ "vacuum_truncate",
+ "Enables vacuum to truncate empty pages at the end of this table",
+ RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
+ ShareUpdateExclusiveLock
+ },
+ TERNARY_UNSET
+ },
+ /* list terminator */
+ {{NULL}}
+};
+
static relopt_int intRelOpts[] =
{
{
@@ -600,6 +606,13 @@ initialize_reloptions(void)
boolRelOpts[i].gen.lockmode));
j++;
}
+ for (i = 0; ternaryRelOpts[i].gen.name; i++)
+ {
+ Assert(DoLockModesConflict(ternaryRelOpts[i].gen.lockmode,
+ ternaryRelOpts[i].gen.lockmode));
+ j++;
+ }
+
for (i = 0; intRelOpts[i].gen.name; i++)
{
Assert(DoLockModesConflict(intRelOpts[i].gen.lockmode,
@@ -640,6 +653,14 @@ initialize_reloptions(void)
j++;
}
+ for (i = 0; ternaryRelOpts[i].gen.name; i++)
+ {
+ relOpts[j] = &ternaryRelOpts[i].gen;
+ relOpts[j]->type = RELOPT_TYPE_TERNARY;
+ relOpts[j]->namelen = strlen(relOpts[j]->name);
+ j++;
+ }
+
for (i = 0; intRelOpts[i].gen.name; i++)
{
relOpts[j] = &intRelOpts[i].gen;
@@ -800,6 +821,9 @@ allocate_reloption(bits32 kinds, int type, const char *name, const char *desc,
case RELOPT_TYPE_BOOL:
size = sizeof(relopt_bool);
break;
+ case RELOPT_TYPE_TERNARY:
+ size = sizeof(relopt_ternary);
+ break;
case RELOPT_TYPE_INT:
size = sizeof(relopt_int);
break;
@@ -883,6 +907,54 @@ add_local_bool_reloption(local_relopts *relopts, const char *name,
add_local_reloption(relopts, (relopt_gen *) newoption, offset);
}
+/*
+ * init_ternary_reloption
+ * Allocate and initialize a new ternary reloption
+ */
+static relopt_ternary *
+init_ternary_reloption(bits32 kinds, const char *name, const char *desc,
+ ternary default_val, LOCKMODE lockmode)
+{
+ relopt_ternary *newoption;
+
+ newoption = (relopt_ternary *) allocate_reloption(kinds,
+ RELOPT_TYPE_TERNARY, name, desc, lockmode);
+ newoption->default_val = default_val;
+
+ return newoption;
+}
+
+/*
+ * add_ternary_reloption
+ * Add a new ternary reloption
+ */
+void
+add_ternary_reloption(bits32 kinds, const char *name, const char *desc,
+ ternary default_val, LOCKMODE lockmode)
+{
+ relopt_ternary *newoption = init_ternary_reloption(kinds, name, desc,
+ default_val, lockmode);
+
+ add_reloption((relopt_gen *) newoption);
+}
+
+/*
+ * add_local_ternary_reloption
+ * Add a new ternary local reloption
+ *
+ * 'offset' is offset of ternary-typed field.
+ */
+void
+add_local_ternary_reloption(local_relopts *relopts, const char *name,
+ const char *desc, ternary default_val,
+ int offset)
+{
+ relopt_ternary *newoption = init_ternary_reloption(RELOPT_KIND_LOCAL,
+ name, desc,
+ default_val, 0);
+
+ add_local_reloption(relopts, (relopt_gen *) newoption, offset);
+}
/*
* init_real_reloption
@@ -1617,6 +1689,18 @@ parse_one_reloption(relopt_value *option, char *text_str, int text_len,
option->gen->name, value)));
}
break;
+ case RELOPT_TYPE_TERNARY:
+ {
+ bool b;
+ parsed = parse_bool(value, &b);
+ option->values.ternary_val = b ? TERNARY_TRUE : TERNARY_FALSE;
+ if (validate && !parsed)
+ ereport(ERROR,
+ (errcode(ERRCODE_INVALID_PARAMETER_VALUE),
+ errmsg("invalid value for ternary option \"%s\": %s",
+ option->gen->name, value)));
+ }
+ break;
case RELOPT_TYPE_INT:
{
relopt_int *optint = (relopt_int *) option->gen;
@@ -1780,17 +1864,6 @@ fillRelOptions(void *rdopts, Size basesize,
char *itempos = ((char *) rdopts) + elems[j].offset;
char *string_val;
- /*
- * If isset_offset is provided, store whether the reloption is
- * set there.
- */
- if (elems[j].isset_offset > 0)
- {
- char *setpos = ((char *) rdopts) + elems[j].isset_offset;
-
- *(bool *) setpos = options[i].isset;
- }
-
switch (options[i].gen->type)
{
case RELOPT_TYPE_BOOL:
@@ -1798,6 +1871,11 @@ fillRelOptions(void *rdopts, Size basesize,
options[i].values.bool_val :
((relopt_bool *) options[i].gen)->default_val;
break;
+ case RELOPT_TYPE_TERNARY:
+ *(ternary *) itempos = options[i].isset ?
+ options[i].values.ternary_val :
+ ((relopt_ternary *) options[i].gen)->default_val;
+ break;
case RELOPT_TYPE_INT:
*(int *) itempos = options[i].isset ?
options[i].values.int_val :
@@ -1912,8 +1990,8 @@ default_reloptions(Datum reloptions, bool validate, relopt_kind kind)
offsetof(StdRdOptions, parallel_workers)},
{"vacuum_index_cleanup", RELOPT_TYPE_ENUM,
offsetof(StdRdOptions, vacuum_index_cleanup)},
- {"vacuum_truncate", RELOPT_TYPE_BOOL,
- offsetof(StdRdOptions, vacuum_truncate), offsetof(StdRdOptions, vacuum_truncate_set)},
+ {"vacuum_truncate", RELOPT_TYPE_TERNARY,
+ offsetof(StdRdOptions, vacuum_truncate)},
{"vacuum_max_eager_freeze_failure_rate", RELOPT_TYPE_REAL,
offsetof(StdRdOptions, vacuum_max_eager_freeze_failure_rate)}
};
@@ -1993,7 +2071,6 @@ build_local_reloptions(local_relopts *relopts, Datum options, bool validate)
elems[i].optname = opt->option->name;
elems[i].opttype = opt->option->type;
elems[i].offset = opt->offset;
- elems[i].isset_offset = 0; /* not supported for local relopts yet */
i++;
}
diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c
index 733ef40ae7..977babff54 100644
--- a/src/backend/commands/vacuum.c
+++ b/src/backend/commands/vacuum.c
@@ -2221,9 +2221,9 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams params,
{
StdRdOptions *opts = (StdRdOptions *) rel->rd_options;
- if (opts && opts->vacuum_truncate_set)
+ if (opts && opts->vacuum_truncate != TERNARY_UNSET)
{
- if (opts->vacuum_truncate)
+ if (opts->vacuum_truncate == TERNARY_TRUE)
params.truncate = VACOPTVALUE_ENABLED;
else
params.truncate = VACOPTVALUE_DISABLED;
diff --git a/src/include/access/reloptions.h b/src/include/access/reloptions.h
index a604a4702c..a436697658 100644
--- a/src/include/access/reloptions.h
+++ b/src/include/access/reloptions.h
@@ -29,6 +29,7 @@
typedef enum relopt_type
{
RELOPT_TYPE_BOOL,
+ RELOPT_TYPE_TERNARY, /* on, off, unset */
RELOPT_TYPE_INT,
RELOPT_TYPE_REAL,
RELOPT_TYPE_ENUM,
@@ -80,6 +81,7 @@ typedef struct relopt_value
union
{
bool bool_val;
+ ternary ternary_val;
int int_val;
double real_val;
int enum_val;
@@ -94,6 +96,12 @@ typedef struct relopt_bool
bool default_val;
} relopt_bool;
+typedef struct relopt_rernary
+{
+ relopt_gen gen;
+ int default_val;
+} relopt_ternary;
+
typedef struct relopt_int
{
relopt_gen gen;
@@ -152,19 +160,6 @@ typedef struct
const char *optname; /* option's name */
relopt_type opttype; /* option's datatype */
int offset; /* offset of field in result struct */
-
- /*
- * isset_offset is an optional offset of a field in the result struct that
- * stores whether the option is explicitly set for the relation or if it
- * just picked up the default value. In most cases, this can be
- * accomplished by giving the reloption a special out-of-range default
- * value (e.g., some integer reloptions use -2), but this isn't always
- * possible. For example, a Boolean reloption cannot be given an
- * out-of-range default, so we need another way to discover the source of
- * its value. This offset is only used if given a value greater than
- * zero.
- */
- int isset_offset;
} relopt_parse_elt;
/* Local reloption definition */
@@ -195,6 +190,8 @@ typedef struct local_relopts
extern relopt_kind add_reloption_kind(void);
extern void add_bool_reloption(bits32 kinds, const char *name, const char *desc,
bool default_val, LOCKMODE lockmode);
+extern void add_ternary_reloption(bits32 kinds, const char *name,
+ const char *desc, int default_val, LOCKMODE lockmode);
extern void add_int_reloption(bits32 kinds, const char *name, const char *desc,
int default_val, int min_val, int max_val,
LOCKMODE lockmode);
@@ -214,6 +211,9 @@ extern void register_reloptions_validator(local_relopts *relopts,
extern void add_local_bool_reloption(local_relopts *relopts, const char *name,
const char *desc, bool default_val,
int offset);
+extern void add_local_ternary_reloption(local_relopts *relopts,
+ const char *name, const char *desc,
+ ternary default_val, int offset);
extern void add_local_int_reloption(local_relopts *relopts, const char *name,
const char *desc, int default_val,
int min_val, int max_val, int offset);
diff --git a/src/include/c.h b/src/include/c.h
index 39022f8a9d..9c59ccccf8 100644
--- a/src/include/c.h
+++ b/src/include/c.h
@@ -488,6 +488,22 @@ typedef void (*pg_funcptr_t) (void);
#include <stdbool.h>
+/*
+ * ternary
+ * Boolean value with an extrea "unset" option
+ *
+ * Ternary data type is used in relation options that can be "true", "false" or
+ * "unset". Since relation options are used deep inside the PostgreSQL code,
+ * this type is declared globally.
+*/
+
+typedef enum ternary
+{
+ TERNARY_FALSE = 0,
+ TERNARY_TRUE = 1,
+ TERNARY_UNSET = -1
+} ternary;
+
/* ----------------------------------------------------------------
* Section 3: standard system types
diff --git a/src/include/utils/rel.h b/src/include/utils/rel.h
index b552359915..7346d618f9 100644
--- a/src/include/utils/rel.h
+++ b/src/include/utils/rel.h
@@ -346,8 +346,7 @@ typedef struct StdRdOptions
bool user_catalog_table; /* use as an additional catalog relation */
int parallel_workers; /* max number of parallel workers */
StdRdOptIndexCleanup vacuum_index_cleanup; /* controls index vacuuming */
- bool vacuum_truncate; /* enables vacuum to truncate a relation */
- bool vacuum_truncate_set; /* whether vacuum_truncate is set */
+ ternary vacuum_truncate; /* enables vacuum to truncate a relation */
/*
* Fraction of pages in a relation that vacuum can eagerly scan and fail
--
2.39.2
--nextPart4155600.3ZeAukHxDK
Content-Disposition: attachment;
filename="v2a-0003-Add-alias-to-be-used-as-unset-state.patch"
Content-Transfer-Encoding: 7Bit
Content-Type: text/x-patch; charset="unicode-2-0-utf-8";
name="v2a-0003-Add-alias-to-be-used-as-unset-state.patch"
^ permalink raw reply [nested|flat] 868+ messages in thread
* [PATCH v2 2/4] Introduce ternary reloptions
@ 2025-09-04 14:41 Nikolay Shaplov <[email protected]>
0 siblings, 0 replies; 868+ messages in thread
From: Nikolay Shaplov @ 2025-09-04 14:41 UTC (permalink / raw)
Introduce ternary reloption as a replacement for current `vacuum_truncate`
implementation. Remove `vacuum_truncate_set` additional flag and using
`TERNARY_UNSET` value instead.
---
src/backend/access/common/reloptions.c | 129 ++++++++++++++++++++-----
src/backend/commands/vacuum.c | 4 +-
src/include/access/reloptions.h | 26 ++---
src/include/c.h | 16 +++
src/include/utils/rel.h | 3 +-
5 files changed, 135 insertions(+), 43 deletions(-)
diff --git a/src/backend/access/common/reloptions.c b/src/backend/access/common/reloptions.c
index 0af3fea68f..f543f11001 100644
--- a/src/backend/access/common/reloptions.c
+++ b/src/backend/access/common/reloptions.c
@@ -40,9 +40,9 @@
*
* To add an option:
*
- * (i) decide on a type (bool, integer, real, enum, string), name, default
- * value, upper and lower bounds (if applicable); for strings, consider a
- * validation routine.
+ * (i) decide on a type (bool, ternary, integer, real, enum, string), name,
+ * default value, upper and lower bounds (if applicable); for strings,
+ * consider a validation routine.
* (ii) add a record below (or use add_<type>_reloption).
* (iii) add it to the appropriate options struct (perhaps StdRdOptions)
* (iv) add it to the appropriate handling routine (perhaps
@@ -147,15 +147,6 @@ static relopt_bool boolRelOpts[] =
},
false
},
- {
- {
- "vacuum_truncate",
- "Enables vacuum to truncate empty pages at the end of this table",
- RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
- ShareUpdateExclusiveLock
- },
- true
- },
{
{
"deduplicate_items",
@@ -170,6 +161,21 @@ static relopt_bool boolRelOpts[] =
{{NULL}}
};
+static relopt_ternary ternaryRelOpts[] =
+{
+ {
+ {
+ "vacuum_truncate",
+ "Enables vacuum to truncate empty pages at the end of this table",
+ RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
+ ShareUpdateExclusiveLock
+ },
+ TERNARY_UNSET
+ },
+ /* list terminator */
+ {{NULL}}
+};
+
static relopt_int intRelOpts[] =
{
{
@@ -600,6 +606,13 @@ initialize_reloptions(void)
boolRelOpts[i].gen.lockmode));
j++;
}
+ for (i = 0; ternaryRelOpts[i].gen.name; i++)
+ {
+ Assert(DoLockModesConflict(ternaryRelOpts[i].gen.lockmode,
+ ternaryRelOpts[i].gen.lockmode));
+ j++;
+ }
+
for (i = 0; intRelOpts[i].gen.name; i++)
{
Assert(DoLockModesConflict(intRelOpts[i].gen.lockmode,
@@ -640,6 +653,14 @@ initialize_reloptions(void)
j++;
}
+ for (i = 0; ternaryRelOpts[i].gen.name; i++)
+ {
+ relOpts[j] = &ternaryRelOpts[i].gen;
+ relOpts[j]->type = RELOPT_TYPE_TERNARY;
+ relOpts[j]->namelen = strlen(relOpts[j]->name);
+ j++;
+ }
+
for (i = 0; intRelOpts[i].gen.name; i++)
{
relOpts[j] = &intRelOpts[i].gen;
@@ -800,6 +821,9 @@ allocate_reloption(bits32 kinds, int type, const char *name, const char *desc,
case RELOPT_TYPE_BOOL:
size = sizeof(relopt_bool);
break;
+ case RELOPT_TYPE_TERNARY:
+ size = sizeof(relopt_ternary);
+ break;
case RELOPT_TYPE_INT:
size = sizeof(relopt_int);
break;
@@ -883,6 +907,54 @@ add_local_bool_reloption(local_relopts *relopts, const char *name,
add_local_reloption(relopts, (relopt_gen *) newoption, offset);
}
+/*
+ * init_ternary_reloption
+ * Allocate and initialize a new ternary reloption
+ */
+static relopt_ternary *
+init_ternary_reloption(bits32 kinds, const char *name, const char *desc,
+ ternary default_val, LOCKMODE lockmode)
+{
+ relopt_ternary *newoption;
+
+ newoption = (relopt_ternary *) allocate_reloption(kinds,
+ RELOPT_TYPE_TERNARY, name, desc, lockmode);
+ newoption->default_val = default_val;
+
+ return newoption;
+}
+
+/*
+ * add_ternary_reloption
+ * Add a new ternary reloption
+ */
+void
+add_ternary_reloption(bits32 kinds, const char *name, const char *desc,
+ ternary default_val, LOCKMODE lockmode)
+{
+ relopt_ternary *newoption = init_ternary_reloption(kinds, name, desc,
+ default_val, lockmode);
+
+ add_reloption((relopt_gen *) newoption);
+}
+
+/*
+ * add_local_ternary_reloption
+ * Add a new ternary local reloption
+ *
+ * 'offset' is offset of ternary-typed field.
+ */
+void
+add_local_ternary_reloption(local_relopts *relopts, const char *name,
+ const char *desc, ternary default_val,
+ int offset)
+{
+ relopt_ternary *newoption = init_ternary_reloption(RELOPT_KIND_LOCAL,
+ name, desc,
+ default_val, 0);
+
+ add_local_reloption(relopts, (relopt_gen *) newoption, offset);
+}
/*
* init_real_reloption
@@ -1617,6 +1689,18 @@ parse_one_reloption(relopt_value *option, char *text_str, int text_len,
option->gen->name, value)));
}
break;
+ case RELOPT_TYPE_TERNARY:
+ {
+ bool b;
+ parsed = parse_bool(value, &b);
+ option->values.ternary_val = b ? TERNARY_TRUE : TERNARY_FALSE;
+ if (validate && !parsed)
+ ereport(ERROR,
+ (errcode(ERRCODE_INVALID_PARAMETER_VALUE),
+ errmsg("invalid value for ternary option \"%s\": %s",
+ option->gen->name, value)));
+ }
+ break;
case RELOPT_TYPE_INT:
{
relopt_int *optint = (relopt_int *) option->gen;
@@ -1780,17 +1864,6 @@ fillRelOptions(void *rdopts, Size basesize,
char *itempos = ((char *) rdopts) + elems[j].offset;
char *string_val;
- /*
- * If isset_offset is provided, store whether the reloption is
- * set there.
- */
- if (elems[j].isset_offset > 0)
- {
- char *setpos = ((char *) rdopts) + elems[j].isset_offset;
-
- *(bool *) setpos = options[i].isset;
- }
-
switch (options[i].gen->type)
{
case RELOPT_TYPE_BOOL:
@@ -1798,6 +1871,11 @@ fillRelOptions(void *rdopts, Size basesize,
options[i].values.bool_val :
((relopt_bool *) options[i].gen)->default_val;
break;
+ case RELOPT_TYPE_TERNARY:
+ *(ternary *) itempos = options[i].isset ?
+ options[i].values.ternary_val :
+ ((relopt_ternary *) options[i].gen)->default_val;
+ break;
case RELOPT_TYPE_INT:
*(int *) itempos = options[i].isset ?
options[i].values.int_val :
@@ -1912,8 +1990,8 @@ default_reloptions(Datum reloptions, bool validate, relopt_kind kind)
offsetof(StdRdOptions, parallel_workers)},
{"vacuum_index_cleanup", RELOPT_TYPE_ENUM,
offsetof(StdRdOptions, vacuum_index_cleanup)},
- {"vacuum_truncate", RELOPT_TYPE_BOOL,
- offsetof(StdRdOptions, vacuum_truncate), offsetof(StdRdOptions, vacuum_truncate_set)},
+ {"vacuum_truncate", RELOPT_TYPE_TERNARY,
+ offsetof(StdRdOptions, vacuum_truncate)},
{"vacuum_max_eager_freeze_failure_rate", RELOPT_TYPE_REAL,
offsetof(StdRdOptions, vacuum_max_eager_freeze_failure_rate)}
};
@@ -1993,7 +2071,6 @@ build_local_reloptions(local_relopts *relopts, Datum options, bool validate)
elems[i].optname = opt->option->name;
elems[i].opttype = opt->option->type;
elems[i].offset = opt->offset;
- elems[i].isset_offset = 0; /* not supported for local relopts yet */
i++;
}
diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c
index 733ef40ae7..977babff54 100644
--- a/src/backend/commands/vacuum.c
+++ b/src/backend/commands/vacuum.c
@@ -2221,9 +2221,9 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams params,
{
StdRdOptions *opts = (StdRdOptions *) rel->rd_options;
- if (opts && opts->vacuum_truncate_set)
+ if (opts && opts->vacuum_truncate != TERNARY_UNSET)
{
- if (opts->vacuum_truncate)
+ if (opts->vacuum_truncate == TERNARY_TRUE)
params.truncate = VACOPTVALUE_ENABLED;
else
params.truncate = VACOPTVALUE_DISABLED;
diff --git a/src/include/access/reloptions.h b/src/include/access/reloptions.h
index a604a4702c..a436697658 100644
--- a/src/include/access/reloptions.h
+++ b/src/include/access/reloptions.h
@@ -29,6 +29,7 @@
typedef enum relopt_type
{
RELOPT_TYPE_BOOL,
+ RELOPT_TYPE_TERNARY, /* on, off, unset */
RELOPT_TYPE_INT,
RELOPT_TYPE_REAL,
RELOPT_TYPE_ENUM,
@@ -80,6 +81,7 @@ typedef struct relopt_value
union
{
bool bool_val;
+ ternary ternary_val;
int int_val;
double real_val;
int enum_val;
@@ -94,6 +96,12 @@ typedef struct relopt_bool
bool default_val;
} relopt_bool;
+typedef struct relopt_rernary
+{
+ relopt_gen gen;
+ int default_val;
+} relopt_ternary;
+
typedef struct relopt_int
{
relopt_gen gen;
@@ -152,19 +160,6 @@ typedef struct
const char *optname; /* option's name */
relopt_type opttype; /* option's datatype */
int offset; /* offset of field in result struct */
-
- /*
- * isset_offset is an optional offset of a field in the result struct that
- * stores whether the option is explicitly set for the relation or if it
- * just picked up the default value. In most cases, this can be
- * accomplished by giving the reloption a special out-of-range default
- * value (e.g., some integer reloptions use -2), but this isn't always
- * possible. For example, a Boolean reloption cannot be given an
- * out-of-range default, so we need another way to discover the source of
- * its value. This offset is only used if given a value greater than
- * zero.
- */
- int isset_offset;
} relopt_parse_elt;
/* Local reloption definition */
@@ -195,6 +190,8 @@ typedef struct local_relopts
extern relopt_kind add_reloption_kind(void);
extern void add_bool_reloption(bits32 kinds, const char *name, const char *desc,
bool default_val, LOCKMODE lockmode);
+extern void add_ternary_reloption(bits32 kinds, const char *name,
+ const char *desc, int default_val, LOCKMODE lockmode);
extern void add_int_reloption(bits32 kinds, const char *name, const char *desc,
int default_val, int min_val, int max_val,
LOCKMODE lockmode);
@@ -214,6 +211,9 @@ extern void register_reloptions_validator(local_relopts *relopts,
extern void add_local_bool_reloption(local_relopts *relopts, const char *name,
const char *desc, bool default_val,
int offset);
+extern void add_local_ternary_reloption(local_relopts *relopts,
+ const char *name, const char *desc,
+ ternary default_val, int offset);
extern void add_local_int_reloption(local_relopts *relopts, const char *name,
const char *desc, int default_val,
int min_val, int max_val, int offset);
diff --git a/src/include/c.h b/src/include/c.h
index 39022f8a9d..9c59ccccf8 100644
--- a/src/include/c.h
+++ b/src/include/c.h
@@ -488,6 +488,22 @@ typedef void (*pg_funcptr_t) (void);
#include <stdbool.h>
+/*
+ * ternary
+ * Boolean value with an extrea "unset" option
+ *
+ * Ternary data type is used in relation options that can be "true", "false" or
+ * "unset". Since relation options are used deep inside the PostgreSQL code,
+ * this type is declared globally.
+*/
+
+typedef enum ternary
+{
+ TERNARY_FALSE = 0,
+ TERNARY_TRUE = 1,
+ TERNARY_UNSET = -1
+} ternary;
+
/* ----------------------------------------------------------------
* Section 3: standard system types
diff --git a/src/include/utils/rel.h b/src/include/utils/rel.h
index b552359915..7346d618f9 100644
--- a/src/include/utils/rel.h
+++ b/src/include/utils/rel.h
@@ -346,8 +346,7 @@ typedef struct StdRdOptions
bool user_catalog_table; /* use as an additional catalog relation */
int parallel_workers; /* max number of parallel workers */
StdRdOptIndexCleanup vacuum_index_cleanup; /* controls index vacuuming */
- bool vacuum_truncate; /* enables vacuum to truncate a relation */
- bool vacuum_truncate_set; /* whether vacuum_truncate is set */
+ ternary vacuum_truncate; /* enables vacuum to truncate a relation */
/*
* Fraction of pages in a relation that vacuum can eagerly scan and fail
--
2.39.2
--nextPart5129932.5fSG56mABF
Content-Disposition: attachment;
filename="v2-0003-Add-alias-to-be-used-as-unset-state.patch"
Content-Transfer-Encoding: 7Bit
Content-Type: text/x-patch; charset="unicode-2-0-utf-8";
name="v2-0003-Add-alias-to-be-used-as-unset-state.patch"
^ permalink raw reply [nested|flat] 868+ messages in thread
* [PATCH v2 2/4] Introduce ternary reloptions
@ 2025-09-04 14:41 Nikolay Shaplov <[email protected]>
0 siblings, 0 replies; 868+ messages in thread
From: Nikolay Shaplov @ 2025-09-04 14:41 UTC (permalink / raw)
Introduce ternary reloption as a replacement for current `vacuum_truncate`
implementation. Remove `vacuum_truncate_set` additional flag and using
`TERNARY_UNSET` value instead.
---
src/backend/access/common/reloptions.c | 129 ++++++++++++++++++++-----
src/backend/commands/vacuum.c | 4 +-
src/include/access/reloptions.h | 26 ++---
src/include/c.h | 16 +++
src/include/utils/rel.h | 3 +-
5 files changed, 135 insertions(+), 43 deletions(-)
diff --git a/src/backend/access/common/reloptions.c b/src/backend/access/common/reloptions.c
index 0af3fea68f..f543f11001 100644
--- a/src/backend/access/common/reloptions.c
+++ b/src/backend/access/common/reloptions.c
@@ -40,9 +40,9 @@
*
* To add an option:
*
- * (i) decide on a type (bool, integer, real, enum, string), name, default
- * value, upper and lower bounds (if applicable); for strings, consider a
- * validation routine.
+ * (i) decide on a type (bool, ternary, integer, real, enum, string), name,
+ * default value, upper and lower bounds (if applicable); for strings,
+ * consider a validation routine.
* (ii) add a record below (or use add_<type>_reloption).
* (iii) add it to the appropriate options struct (perhaps StdRdOptions)
* (iv) add it to the appropriate handling routine (perhaps
@@ -147,15 +147,6 @@ static relopt_bool boolRelOpts[] =
},
false
},
- {
- {
- "vacuum_truncate",
- "Enables vacuum to truncate empty pages at the end of this table",
- RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
- ShareUpdateExclusiveLock
- },
- true
- },
{
{
"deduplicate_items",
@@ -170,6 +161,21 @@ static relopt_bool boolRelOpts[] =
{{NULL}}
};
+static relopt_ternary ternaryRelOpts[] =
+{
+ {
+ {
+ "vacuum_truncate",
+ "Enables vacuum to truncate empty pages at the end of this table",
+ RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
+ ShareUpdateExclusiveLock
+ },
+ TERNARY_UNSET
+ },
+ /* list terminator */
+ {{NULL}}
+};
+
static relopt_int intRelOpts[] =
{
{
@@ -600,6 +606,13 @@ initialize_reloptions(void)
boolRelOpts[i].gen.lockmode));
j++;
}
+ for (i = 0; ternaryRelOpts[i].gen.name; i++)
+ {
+ Assert(DoLockModesConflict(ternaryRelOpts[i].gen.lockmode,
+ ternaryRelOpts[i].gen.lockmode));
+ j++;
+ }
+
for (i = 0; intRelOpts[i].gen.name; i++)
{
Assert(DoLockModesConflict(intRelOpts[i].gen.lockmode,
@@ -640,6 +653,14 @@ initialize_reloptions(void)
j++;
}
+ for (i = 0; ternaryRelOpts[i].gen.name; i++)
+ {
+ relOpts[j] = &ternaryRelOpts[i].gen;
+ relOpts[j]->type = RELOPT_TYPE_TERNARY;
+ relOpts[j]->namelen = strlen(relOpts[j]->name);
+ j++;
+ }
+
for (i = 0; intRelOpts[i].gen.name; i++)
{
relOpts[j] = &intRelOpts[i].gen;
@@ -800,6 +821,9 @@ allocate_reloption(bits32 kinds, int type, const char *name, const char *desc,
case RELOPT_TYPE_BOOL:
size = sizeof(relopt_bool);
break;
+ case RELOPT_TYPE_TERNARY:
+ size = sizeof(relopt_ternary);
+ break;
case RELOPT_TYPE_INT:
size = sizeof(relopt_int);
break;
@@ -883,6 +907,54 @@ add_local_bool_reloption(local_relopts *relopts, const char *name,
add_local_reloption(relopts, (relopt_gen *) newoption, offset);
}
+/*
+ * init_ternary_reloption
+ * Allocate and initialize a new ternary reloption
+ */
+static relopt_ternary *
+init_ternary_reloption(bits32 kinds, const char *name, const char *desc,
+ ternary default_val, LOCKMODE lockmode)
+{
+ relopt_ternary *newoption;
+
+ newoption = (relopt_ternary *) allocate_reloption(kinds,
+ RELOPT_TYPE_TERNARY, name, desc, lockmode);
+ newoption->default_val = default_val;
+
+ return newoption;
+}
+
+/*
+ * add_ternary_reloption
+ * Add a new ternary reloption
+ */
+void
+add_ternary_reloption(bits32 kinds, const char *name, const char *desc,
+ ternary default_val, LOCKMODE lockmode)
+{
+ relopt_ternary *newoption = init_ternary_reloption(kinds, name, desc,
+ default_val, lockmode);
+
+ add_reloption((relopt_gen *) newoption);
+}
+
+/*
+ * add_local_ternary_reloption
+ * Add a new ternary local reloption
+ *
+ * 'offset' is offset of ternary-typed field.
+ */
+void
+add_local_ternary_reloption(local_relopts *relopts, const char *name,
+ const char *desc, ternary default_val,
+ int offset)
+{
+ relopt_ternary *newoption = init_ternary_reloption(RELOPT_KIND_LOCAL,
+ name, desc,
+ default_val, 0);
+
+ add_local_reloption(relopts, (relopt_gen *) newoption, offset);
+}
/*
* init_real_reloption
@@ -1617,6 +1689,18 @@ parse_one_reloption(relopt_value *option, char *text_str, int text_len,
option->gen->name, value)));
}
break;
+ case RELOPT_TYPE_TERNARY:
+ {
+ bool b;
+ parsed = parse_bool(value, &b);
+ option->values.ternary_val = b ? TERNARY_TRUE : TERNARY_FALSE;
+ if (validate && !parsed)
+ ereport(ERROR,
+ (errcode(ERRCODE_INVALID_PARAMETER_VALUE),
+ errmsg("invalid value for ternary option \"%s\": %s",
+ option->gen->name, value)));
+ }
+ break;
case RELOPT_TYPE_INT:
{
relopt_int *optint = (relopt_int *) option->gen;
@@ -1780,17 +1864,6 @@ fillRelOptions(void *rdopts, Size basesize,
char *itempos = ((char *) rdopts) + elems[j].offset;
char *string_val;
- /*
- * If isset_offset is provided, store whether the reloption is
- * set there.
- */
- if (elems[j].isset_offset > 0)
- {
- char *setpos = ((char *) rdopts) + elems[j].isset_offset;
-
- *(bool *) setpos = options[i].isset;
- }
-
switch (options[i].gen->type)
{
case RELOPT_TYPE_BOOL:
@@ -1798,6 +1871,11 @@ fillRelOptions(void *rdopts, Size basesize,
options[i].values.bool_val :
((relopt_bool *) options[i].gen)->default_val;
break;
+ case RELOPT_TYPE_TERNARY:
+ *(ternary *) itempos = options[i].isset ?
+ options[i].values.ternary_val :
+ ((relopt_ternary *) options[i].gen)->default_val;
+ break;
case RELOPT_TYPE_INT:
*(int *) itempos = options[i].isset ?
options[i].values.int_val :
@@ -1912,8 +1990,8 @@ default_reloptions(Datum reloptions, bool validate, relopt_kind kind)
offsetof(StdRdOptions, parallel_workers)},
{"vacuum_index_cleanup", RELOPT_TYPE_ENUM,
offsetof(StdRdOptions, vacuum_index_cleanup)},
- {"vacuum_truncate", RELOPT_TYPE_BOOL,
- offsetof(StdRdOptions, vacuum_truncate), offsetof(StdRdOptions, vacuum_truncate_set)},
+ {"vacuum_truncate", RELOPT_TYPE_TERNARY,
+ offsetof(StdRdOptions, vacuum_truncate)},
{"vacuum_max_eager_freeze_failure_rate", RELOPT_TYPE_REAL,
offsetof(StdRdOptions, vacuum_max_eager_freeze_failure_rate)}
};
@@ -1993,7 +2071,6 @@ build_local_reloptions(local_relopts *relopts, Datum options, bool validate)
elems[i].optname = opt->option->name;
elems[i].opttype = opt->option->type;
elems[i].offset = opt->offset;
- elems[i].isset_offset = 0; /* not supported for local relopts yet */
i++;
}
diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c
index 733ef40ae7..977babff54 100644
--- a/src/backend/commands/vacuum.c
+++ b/src/backend/commands/vacuum.c
@@ -2221,9 +2221,9 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams params,
{
StdRdOptions *opts = (StdRdOptions *) rel->rd_options;
- if (opts && opts->vacuum_truncate_set)
+ if (opts && opts->vacuum_truncate != TERNARY_UNSET)
{
- if (opts->vacuum_truncate)
+ if (opts->vacuum_truncate == TERNARY_TRUE)
params.truncate = VACOPTVALUE_ENABLED;
else
params.truncate = VACOPTVALUE_DISABLED;
diff --git a/src/include/access/reloptions.h b/src/include/access/reloptions.h
index a604a4702c..a436697658 100644
--- a/src/include/access/reloptions.h
+++ b/src/include/access/reloptions.h
@@ -29,6 +29,7 @@
typedef enum relopt_type
{
RELOPT_TYPE_BOOL,
+ RELOPT_TYPE_TERNARY, /* on, off, unset */
RELOPT_TYPE_INT,
RELOPT_TYPE_REAL,
RELOPT_TYPE_ENUM,
@@ -80,6 +81,7 @@ typedef struct relopt_value
union
{
bool bool_val;
+ ternary ternary_val;
int int_val;
double real_val;
int enum_val;
@@ -94,6 +96,12 @@ typedef struct relopt_bool
bool default_val;
} relopt_bool;
+typedef struct relopt_rernary
+{
+ relopt_gen gen;
+ int default_val;
+} relopt_ternary;
+
typedef struct relopt_int
{
relopt_gen gen;
@@ -152,19 +160,6 @@ typedef struct
const char *optname; /* option's name */
relopt_type opttype; /* option's datatype */
int offset; /* offset of field in result struct */
-
- /*
- * isset_offset is an optional offset of a field in the result struct that
- * stores whether the option is explicitly set for the relation or if it
- * just picked up the default value. In most cases, this can be
- * accomplished by giving the reloption a special out-of-range default
- * value (e.g., some integer reloptions use -2), but this isn't always
- * possible. For example, a Boolean reloption cannot be given an
- * out-of-range default, so we need another way to discover the source of
- * its value. This offset is only used if given a value greater than
- * zero.
- */
- int isset_offset;
} relopt_parse_elt;
/* Local reloption definition */
@@ -195,6 +190,8 @@ typedef struct local_relopts
extern relopt_kind add_reloption_kind(void);
extern void add_bool_reloption(bits32 kinds, const char *name, const char *desc,
bool default_val, LOCKMODE lockmode);
+extern void add_ternary_reloption(bits32 kinds, const char *name,
+ const char *desc, int default_val, LOCKMODE lockmode);
extern void add_int_reloption(bits32 kinds, const char *name, const char *desc,
int default_val, int min_val, int max_val,
LOCKMODE lockmode);
@@ -214,6 +211,9 @@ extern void register_reloptions_validator(local_relopts *relopts,
extern void add_local_bool_reloption(local_relopts *relopts, const char *name,
const char *desc, bool default_val,
int offset);
+extern void add_local_ternary_reloption(local_relopts *relopts,
+ const char *name, const char *desc,
+ ternary default_val, int offset);
extern void add_local_int_reloption(local_relopts *relopts, const char *name,
const char *desc, int default_val,
int min_val, int max_val, int offset);
diff --git a/src/include/c.h b/src/include/c.h
index 39022f8a9d..9c59ccccf8 100644
--- a/src/include/c.h
+++ b/src/include/c.h
@@ -488,6 +488,22 @@ typedef void (*pg_funcptr_t) (void);
#include <stdbool.h>
+/*
+ * ternary
+ * Boolean value with an extrea "unset" option
+ *
+ * Ternary data type is used in relation options that can be "true", "false" or
+ * "unset". Since relation options are used deep inside the PostgreSQL code,
+ * this type is declared globally.
+*/
+
+typedef enum ternary
+{
+ TERNARY_FALSE = 0,
+ TERNARY_TRUE = 1,
+ TERNARY_UNSET = -1
+} ternary;
+
/* ----------------------------------------------------------------
* Section 3: standard system types
diff --git a/src/include/utils/rel.h b/src/include/utils/rel.h
index b552359915..7346d618f9 100644
--- a/src/include/utils/rel.h
+++ b/src/include/utils/rel.h
@@ -346,8 +346,7 @@ typedef struct StdRdOptions
bool user_catalog_table; /* use as an additional catalog relation */
int parallel_workers; /* max number of parallel workers */
StdRdOptIndexCleanup vacuum_index_cleanup; /* controls index vacuuming */
- bool vacuum_truncate; /* enables vacuum to truncate a relation */
- bool vacuum_truncate_set; /* whether vacuum_truncate is set */
+ ternary vacuum_truncate; /* enables vacuum to truncate a relation */
/*
* Fraction of pages in a relation that vacuum can eagerly scan and fail
--
2.39.2
--nextPart5129932.5fSG56mABF
Content-Disposition: attachment;
filename="v2-0003-Add-alias-to-be-used-as-unset-state.patch"
Content-Transfer-Encoding: 7Bit
Content-Type: text/x-patch; charset="unicode-2-0-utf-8";
name="v2-0003-Add-alias-to-be-used-as-unset-state.patch"
^ permalink raw reply [nested|flat] 868+ messages in thread
* [PATCH v2 2/4] Introduce ternary reloptions
@ 2025-09-04 14:41 Nikolay Shaplov <[email protected]>
0 siblings, 0 replies; 868+ messages in thread
From: Nikolay Shaplov @ 2025-09-04 14:41 UTC (permalink / raw)
Introduce ternary reloption as a replacement for current `vacuum_truncate`
implementation. Remove `vacuum_truncate_set` additional flag and using
`TERNARY_UNSET` value instead.
---
src/backend/access/common/reloptions.c | 129 ++++++++++++++++++++-----
src/backend/commands/vacuum.c | 4 +-
src/include/access/reloptions.h | 26 ++---
src/include/c.h | 16 +++
src/include/utils/rel.h | 3 +-
5 files changed, 135 insertions(+), 43 deletions(-)
diff --git a/src/backend/access/common/reloptions.c b/src/backend/access/common/reloptions.c
index 0af3fea68f..f543f11001 100644
--- a/src/backend/access/common/reloptions.c
+++ b/src/backend/access/common/reloptions.c
@@ -40,9 +40,9 @@
*
* To add an option:
*
- * (i) decide on a type (bool, integer, real, enum, string), name, default
- * value, upper and lower bounds (if applicable); for strings, consider a
- * validation routine.
+ * (i) decide on a type (bool, ternary, integer, real, enum, string), name,
+ * default value, upper and lower bounds (if applicable); for strings,
+ * consider a validation routine.
* (ii) add a record below (or use add_<type>_reloption).
* (iii) add it to the appropriate options struct (perhaps StdRdOptions)
* (iv) add it to the appropriate handling routine (perhaps
@@ -147,15 +147,6 @@ static relopt_bool boolRelOpts[] =
},
false
},
- {
- {
- "vacuum_truncate",
- "Enables vacuum to truncate empty pages at the end of this table",
- RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
- ShareUpdateExclusiveLock
- },
- true
- },
{
{
"deduplicate_items",
@@ -170,6 +161,21 @@ static relopt_bool boolRelOpts[] =
{{NULL}}
};
+static relopt_ternary ternaryRelOpts[] =
+{
+ {
+ {
+ "vacuum_truncate",
+ "Enables vacuum to truncate empty pages at the end of this table",
+ RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
+ ShareUpdateExclusiveLock
+ },
+ TERNARY_UNSET
+ },
+ /* list terminator */
+ {{NULL}}
+};
+
static relopt_int intRelOpts[] =
{
{
@@ -600,6 +606,13 @@ initialize_reloptions(void)
boolRelOpts[i].gen.lockmode));
j++;
}
+ for (i = 0; ternaryRelOpts[i].gen.name; i++)
+ {
+ Assert(DoLockModesConflict(ternaryRelOpts[i].gen.lockmode,
+ ternaryRelOpts[i].gen.lockmode));
+ j++;
+ }
+
for (i = 0; intRelOpts[i].gen.name; i++)
{
Assert(DoLockModesConflict(intRelOpts[i].gen.lockmode,
@@ -640,6 +653,14 @@ initialize_reloptions(void)
j++;
}
+ for (i = 0; ternaryRelOpts[i].gen.name; i++)
+ {
+ relOpts[j] = &ternaryRelOpts[i].gen;
+ relOpts[j]->type = RELOPT_TYPE_TERNARY;
+ relOpts[j]->namelen = strlen(relOpts[j]->name);
+ j++;
+ }
+
for (i = 0; intRelOpts[i].gen.name; i++)
{
relOpts[j] = &intRelOpts[i].gen;
@@ -800,6 +821,9 @@ allocate_reloption(bits32 kinds, int type, const char *name, const char *desc,
case RELOPT_TYPE_BOOL:
size = sizeof(relopt_bool);
break;
+ case RELOPT_TYPE_TERNARY:
+ size = sizeof(relopt_ternary);
+ break;
case RELOPT_TYPE_INT:
size = sizeof(relopt_int);
break;
@@ -883,6 +907,54 @@ add_local_bool_reloption(local_relopts *relopts, const char *name,
add_local_reloption(relopts, (relopt_gen *) newoption, offset);
}
+/*
+ * init_ternary_reloption
+ * Allocate and initialize a new ternary reloption
+ */
+static relopt_ternary *
+init_ternary_reloption(bits32 kinds, const char *name, const char *desc,
+ ternary default_val, LOCKMODE lockmode)
+{
+ relopt_ternary *newoption;
+
+ newoption = (relopt_ternary *) allocate_reloption(kinds,
+ RELOPT_TYPE_TERNARY, name, desc, lockmode);
+ newoption->default_val = default_val;
+
+ return newoption;
+}
+
+/*
+ * add_ternary_reloption
+ * Add a new ternary reloption
+ */
+void
+add_ternary_reloption(bits32 kinds, const char *name, const char *desc,
+ ternary default_val, LOCKMODE lockmode)
+{
+ relopt_ternary *newoption = init_ternary_reloption(kinds, name, desc,
+ default_val, lockmode);
+
+ add_reloption((relopt_gen *) newoption);
+}
+
+/*
+ * add_local_ternary_reloption
+ * Add a new ternary local reloption
+ *
+ * 'offset' is offset of ternary-typed field.
+ */
+void
+add_local_ternary_reloption(local_relopts *relopts, const char *name,
+ const char *desc, ternary default_val,
+ int offset)
+{
+ relopt_ternary *newoption = init_ternary_reloption(RELOPT_KIND_LOCAL,
+ name, desc,
+ default_val, 0);
+
+ add_local_reloption(relopts, (relopt_gen *) newoption, offset);
+}
/*
* init_real_reloption
@@ -1617,6 +1689,18 @@ parse_one_reloption(relopt_value *option, char *text_str, int text_len,
option->gen->name, value)));
}
break;
+ case RELOPT_TYPE_TERNARY:
+ {
+ bool b;
+ parsed = parse_bool(value, &b);
+ option->values.ternary_val = b ? TERNARY_TRUE : TERNARY_FALSE;
+ if (validate && !parsed)
+ ereport(ERROR,
+ (errcode(ERRCODE_INVALID_PARAMETER_VALUE),
+ errmsg("invalid value for ternary option \"%s\": %s",
+ option->gen->name, value)));
+ }
+ break;
case RELOPT_TYPE_INT:
{
relopt_int *optint = (relopt_int *) option->gen;
@@ -1780,17 +1864,6 @@ fillRelOptions(void *rdopts, Size basesize,
char *itempos = ((char *) rdopts) + elems[j].offset;
char *string_val;
- /*
- * If isset_offset is provided, store whether the reloption is
- * set there.
- */
- if (elems[j].isset_offset > 0)
- {
- char *setpos = ((char *) rdopts) + elems[j].isset_offset;
-
- *(bool *) setpos = options[i].isset;
- }
-
switch (options[i].gen->type)
{
case RELOPT_TYPE_BOOL:
@@ -1798,6 +1871,11 @@ fillRelOptions(void *rdopts, Size basesize,
options[i].values.bool_val :
((relopt_bool *) options[i].gen)->default_val;
break;
+ case RELOPT_TYPE_TERNARY:
+ *(ternary *) itempos = options[i].isset ?
+ options[i].values.ternary_val :
+ ((relopt_ternary *) options[i].gen)->default_val;
+ break;
case RELOPT_TYPE_INT:
*(int *) itempos = options[i].isset ?
options[i].values.int_val :
@@ -1912,8 +1990,8 @@ default_reloptions(Datum reloptions, bool validate, relopt_kind kind)
offsetof(StdRdOptions, parallel_workers)},
{"vacuum_index_cleanup", RELOPT_TYPE_ENUM,
offsetof(StdRdOptions, vacuum_index_cleanup)},
- {"vacuum_truncate", RELOPT_TYPE_BOOL,
- offsetof(StdRdOptions, vacuum_truncate), offsetof(StdRdOptions, vacuum_truncate_set)},
+ {"vacuum_truncate", RELOPT_TYPE_TERNARY,
+ offsetof(StdRdOptions, vacuum_truncate)},
{"vacuum_max_eager_freeze_failure_rate", RELOPT_TYPE_REAL,
offsetof(StdRdOptions, vacuum_max_eager_freeze_failure_rate)}
};
@@ -1993,7 +2071,6 @@ build_local_reloptions(local_relopts *relopts, Datum options, bool validate)
elems[i].optname = opt->option->name;
elems[i].opttype = opt->option->type;
elems[i].offset = opt->offset;
- elems[i].isset_offset = 0; /* not supported for local relopts yet */
i++;
}
diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c
index 733ef40ae7..977babff54 100644
--- a/src/backend/commands/vacuum.c
+++ b/src/backend/commands/vacuum.c
@@ -2221,9 +2221,9 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams params,
{
StdRdOptions *opts = (StdRdOptions *) rel->rd_options;
- if (opts && opts->vacuum_truncate_set)
+ if (opts && opts->vacuum_truncate != TERNARY_UNSET)
{
- if (opts->vacuum_truncate)
+ if (opts->vacuum_truncate == TERNARY_TRUE)
params.truncate = VACOPTVALUE_ENABLED;
else
params.truncate = VACOPTVALUE_DISABLED;
diff --git a/src/include/access/reloptions.h b/src/include/access/reloptions.h
index a604a4702c..a436697658 100644
--- a/src/include/access/reloptions.h
+++ b/src/include/access/reloptions.h
@@ -29,6 +29,7 @@
typedef enum relopt_type
{
RELOPT_TYPE_BOOL,
+ RELOPT_TYPE_TERNARY, /* on, off, unset */
RELOPT_TYPE_INT,
RELOPT_TYPE_REAL,
RELOPT_TYPE_ENUM,
@@ -80,6 +81,7 @@ typedef struct relopt_value
union
{
bool bool_val;
+ ternary ternary_val;
int int_val;
double real_val;
int enum_val;
@@ -94,6 +96,12 @@ typedef struct relopt_bool
bool default_val;
} relopt_bool;
+typedef struct relopt_rernary
+{
+ relopt_gen gen;
+ int default_val;
+} relopt_ternary;
+
typedef struct relopt_int
{
relopt_gen gen;
@@ -152,19 +160,6 @@ typedef struct
const char *optname; /* option's name */
relopt_type opttype; /* option's datatype */
int offset; /* offset of field in result struct */
-
- /*
- * isset_offset is an optional offset of a field in the result struct that
- * stores whether the option is explicitly set for the relation or if it
- * just picked up the default value. In most cases, this can be
- * accomplished by giving the reloption a special out-of-range default
- * value (e.g., some integer reloptions use -2), but this isn't always
- * possible. For example, a Boolean reloption cannot be given an
- * out-of-range default, so we need another way to discover the source of
- * its value. This offset is only used if given a value greater than
- * zero.
- */
- int isset_offset;
} relopt_parse_elt;
/* Local reloption definition */
@@ -195,6 +190,8 @@ typedef struct local_relopts
extern relopt_kind add_reloption_kind(void);
extern void add_bool_reloption(bits32 kinds, const char *name, const char *desc,
bool default_val, LOCKMODE lockmode);
+extern void add_ternary_reloption(bits32 kinds, const char *name,
+ const char *desc, int default_val, LOCKMODE lockmode);
extern void add_int_reloption(bits32 kinds, const char *name, const char *desc,
int default_val, int min_val, int max_val,
LOCKMODE lockmode);
@@ -214,6 +211,9 @@ extern void register_reloptions_validator(local_relopts *relopts,
extern void add_local_bool_reloption(local_relopts *relopts, const char *name,
const char *desc, bool default_val,
int offset);
+extern void add_local_ternary_reloption(local_relopts *relopts,
+ const char *name, const char *desc,
+ ternary default_val, int offset);
extern void add_local_int_reloption(local_relopts *relopts, const char *name,
const char *desc, int default_val,
int min_val, int max_val, int offset);
diff --git a/src/include/c.h b/src/include/c.h
index 39022f8a9d..9c59ccccf8 100644
--- a/src/include/c.h
+++ b/src/include/c.h
@@ -488,6 +488,22 @@ typedef void (*pg_funcptr_t) (void);
#include <stdbool.h>
+/*
+ * ternary
+ * Boolean value with an extrea "unset" option
+ *
+ * Ternary data type is used in relation options that can be "true", "false" or
+ * "unset". Since relation options are used deep inside the PostgreSQL code,
+ * this type is declared globally.
+*/
+
+typedef enum ternary
+{
+ TERNARY_FALSE = 0,
+ TERNARY_TRUE = 1,
+ TERNARY_UNSET = -1
+} ternary;
+
/* ----------------------------------------------------------------
* Section 3: standard system types
diff --git a/src/include/utils/rel.h b/src/include/utils/rel.h
index b552359915..7346d618f9 100644
--- a/src/include/utils/rel.h
+++ b/src/include/utils/rel.h
@@ -346,8 +346,7 @@ typedef struct StdRdOptions
bool user_catalog_table; /* use as an additional catalog relation */
int parallel_workers; /* max number of parallel workers */
StdRdOptIndexCleanup vacuum_index_cleanup; /* controls index vacuuming */
- bool vacuum_truncate; /* enables vacuum to truncate a relation */
- bool vacuum_truncate_set; /* whether vacuum_truncate is set */
+ ternary vacuum_truncate; /* enables vacuum to truncate a relation */
/*
* Fraction of pages in a relation that vacuum can eagerly scan and fail
--
2.39.2
--nextPart5129932.5fSG56mABF
Content-Disposition: attachment;
filename="v2-0003-Add-alias-to-be-used-as-unset-state.patch"
Content-Transfer-Encoding: 7Bit
Content-Type: text/x-patch; charset="unicode-2-0-utf-8";
name="v2-0003-Add-alias-to-be-used-as-unset-state.patch"
^ permalink raw reply [nested|flat] 868+ messages in thread
* [PATCH v2 2/4] Introduce ternary reloptions
@ 2025-09-04 14:41 Nikolay Shaplov <[email protected]>
0 siblings, 0 replies; 868+ messages in thread
From: Nikolay Shaplov @ 2025-09-04 14:41 UTC (permalink / raw)
Introduce ternary reloption as a replacement for current `vacuum_truncate`
implementation. Remove `vacuum_truncate_set` additional flag and using
`TERNARY_UNSET` value instead.
---
src/backend/access/common/reloptions.c | 129 ++++++++++++++++++++-----
src/backend/commands/vacuum.c | 4 +-
src/include/access/reloptions.h | 26 ++---
src/include/c.h | 16 +++
src/include/utils/rel.h | 3 +-
5 files changed, 135 insertions(+), 43 deletions(-)
diff --git a/src/backend/access/common/reloptions.c b/src/backend/access/common/reloptions.c
index 0af3fea68f..f543f11001 100644
--- a/src/backend/access/common/reloptions.c
+++ b/src/backend/access/common/reloptions.c
@@ -40,9 +40,9 @@
*
* To add an option:
*
- * (i) decide on a type (bool, integer, real, enum, string), name, default
- * value, upper and lower bounds (if applicable); for strings, consider a
- * validation routine.
+ * (i) decide on a type (bool, ternary, integer, real, enum, string), name,
+ * default value, upper and lower bounds (if applicable); for strings,
+ * consider a validation routine.
* (ii) add a record below (or use add_<type>_reloption).
* (iii) add it to the appropriate options struct (perhaps StdRdOptions)
* (iv) add it to the appropriate handling routine (perhaps
@@ -147,15 +147,6 @@ static relopt_bool boolRelOpts[] =
},
false
},
- {
- {
- "vacuum_truncate",
- "Enables vacuum to truncate empty pages at the end of this table",
- RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
- ShareUpdateExclusiveLock
- },
- true
- },
{
{
"deduplicate_items",
@@ -170,6 +161,21 @@ static relopt_bool boolRelOpts[] =
{{NULL}}
};
+static relopt_ternary ternaryRelOpts[] =
+{
+ {
+ {
+ "vacuum_truncate",
+ "Enables vacuum to truncate empty pages at the end of this table",
+ RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
+ ShareUpdateExclusiveLock
+ },
+ TERNARY_UNSET
+ },
+ /* list terminator */
+ {{NULL}}
+};
+
static relopt_int intRelOpts[] =
{
{
@@ -600,6 +606,13 @@ initialize_reloptions(void)
boolRelOpts[i].gen.lockmode));
j++;
}
+ for (i = 0; ternaryRelOpts[i].gen.name; i++)
+ {
+ Assert(DoLockModesConflict(ternaryRelOpts[i].gen.lockmode,
+ ternaryRelOpts[i].gen.lockmode));
+ j++;
+ }
+
for (i = 0; intRelOpts[i].gen.name; i++)
{
Assert(DoLockModesConflict(intRelOpts[i].gen.lockmode,
@@ -640,6 +653,14 @@ initialize_reloptions(void)
j++;
}
+ for (i = 0; ternaryRelOpts[i].gen.name; i++)
+ {
+ relOpts[j] = &ternaryRelOpts[i].gen;
+ relOpts[j]->type = RELOPT_TYPE_TERNARY;
+ relOpts[j]->namelen = strlen(relOpts[j]->name);
+ j++;
+ }
+
for (i = 0; intRelOpts[i].gen.name; i++)
{
relOpts[j] = &intRelOpts[i].gen;
@@ -800,6 +821,9 @@ allocate_reloption(bits32 kinds, int type, const char *name, const char *desc,
case RELOPT_TYPE_BOOL:
size = sizeof(relopt_bool);
break;
+ case RELOPT_TYPE_TERNARY:
+ size = sizeof(relopt_ternary);
+ break;
case RELOPT_TYPE_INT:
size = sizeof(relopt_int);
break;
@@ -883,6 +907,54 @@ add_local_bool_reloption(local_relopts *relopts, const char *name,
add_local_reloption(relopts, (relopt_gen *) newoption, offset);
}
+/*
+ * init_ternary_reloption
+ * Allocate and initialize a new ternary reloption
+ */
+static relopt_ternary *
+init_ternary_reloption(bits32 kinds, const char *name, const char *desc,
+ ternary default_val, LOCKMODE lockmode)
+{
+ relopt_ternary *newoption;
+
+ newoption = (relopt_ternary *) allocate_reloption(kinds,
+ RELOPT_TYPE_TERNARY, name, desc, lockmode);
+ newoption->default_val = default_val;
+
+ return newoption;
+}
+
+/*
+ * add_ternary_reloption
+ * Add a new ternary reloption
+ */
+void
+add_ternary_reloption(bits32 kinds, const char *name, const char *desc,
+ ternary default_val, LOCKMODE lockmode)
+{
+ relopt_ternary *newoption = init_ternary_reloption(kinds, name, desc,
+ default_val, lockmode);
+
+ add_reloption((relopt_gen *) newoption);
+}
+
+/*
+ * add_local_ternary_reloption
+ * Add a new ternary local reloption
+ *
+ * 'offset' is offset of ternary-typed field.
+ */
+void
+add_local_ternary_reloption(local_relopts *relopts, const char *name,
+ const char *desc, ternary default_val,
+ int offset)
+{
+ relopt_ternary *newoption = init_ternary_reloption(RELOPT_KIND_LOCAL,
+ name, desc,
+ default_val, 0);
+
+ add_local_reloption(relopts, (relopt_gen *) newoption, offset);
+}
/*
* init_real_reloption
@@ -1617,6 +1689,18 @@ parse_one_reloption(relopt_value *option, char *text_str, int text_len,
option->gen->name, value)));
}
break;
+ case RELOPT_TYPE_TERNARY:
+ {
+ bool b;
+ parsed = parse_bool(value, &b);
+ option->values.ternary_val = b ? TERNARY_TRUE : TERNARY_FALSE;
+ if (validate && !parsed)
+ ereport(ERROR,
+ (errcode(ERRCODE_INVALID_PARAMETER_VALUE),
+ errmsg("invalid value for ternary option \"%s\": %s",
+ option->gen->name, value)));
+ }
+ break;
case RELOPT_TYPE_INT:
{
relopt_int *optint = (relopt_int *) option->gen;
@@ -1780,17 +1864,6 @@ fillRelOptions(void *rdopts, Size basesize,
char *itempos = ((char *) rdopts) + elems[j].offset;
char *string_val;
- /*
- * If isset_offset is provided, store whether the reloption is
- * set there.
- */
- if (elems[j].isset_offset > 0)
- {
- char *setpos = ((char *) rdopts) + elems[j].isset_offset;
-
- *(bool *) setpos = options[i].isset;
- }
-
switch (options[i].gen->type)
{
case RELOPT_TYPE_BOOL:
@@ -1798,6 +1871,11 @@ fillRelOptions(void *rdopts, Size basesize,
options[i].values.bool_val :
((relopt_bool *) options[i].gen)->default_val;
break;
+ case RELOPT_TYPE_TERNARY:
+ *(ternary *) itempos = options[i].isset ?
+ options[i].values.ternary_val :
+ ((relopt_ternary *) options[i].gen)->default_val;
+ break;
case RELOPT_TYPE_INT:
*(int *) itempos = options[i].isset ?
options[i].values.int_val :
@@ -1912,8 +1990,8 @@ default_reloptions(Datum reloptions, bool validate, relopt_kind kind)
offsetof(StdRdOptions, parallel_workers)},
{"vacuum_index_cleanup", RELOPT_TYPE_ENUM,
offsetof(StdRdOptions, vacuum_index_cleanup)},
- {"vacuum_truncate", RELOPT_TYPE_BOOL,
- offsetof(StdRdOptions, vacuum_truncate), offsetof(StdRdOptions, vacuum_truncate_set)},
+ {"vacuum_truncate", RELOPT_TYPE_TERNARY,
+ offsetof(StdRdOptions, vacuum_truncate)},
{"vacuum_max_eager_freeze_failure_rate", RELOPT_TYPE_REAL,
offsetof(StdRdOptions, vacuum_max_eager_freeze_failure_rate)}
};
@@ -1993,7 +2071,6 @@ build_local_reloptions(local_relopts *relopts, Datum options, bool validate)
elems[i].optname = opt->option->name;
elems[i].opttype = opt->option->type;
elems[i].offset = opt->offset;
- elems[i].isset_offset = 0; /* not supported for local relopts yet */
i++;
}
diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c
index 733ef40ae7..977babff54 100644
--- a/src/backend/commands/vacuum.c
+++ b/src/backend/commands/vacuum.c
@@ -2221,9 +2221,9 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams params,
{
StdRdOptions *opts = (StdRdOptions *) rel->rd_options;
- if (opts && opts->vacuum_truncate_set)
+ if (opts && opts->vacuum_truncate != TERNARY_UNSET)
{
- if (opts->vacuum_truncate)
+ if (opts->vacuum_truncate == TERNARY_TRUE)
params.truncate = VACOPTVALUE_ENABLED;
else
params.truncate = VACOPTVALUE_DISABLED;
diff --git a/src/include/access/reloptions.h b/src/include/access/reloptions.h
index a604a4702c..a436697658 100644
--- a/src/include/access/reloptions.h
+++ b/src/include/access/reloptions.h
@@ -29,6 +29,7 @@
typedef enum relopt_type
{
RELOPT_TYPE_BOOL,
+ RELOPT_TYPE_TERNARY, /* on, off, unset */
RELOPT_TYPE_INT,
RELOPT_TYPE_REAL,
RELOPT_TYPE_ENUM,
@@ -80,6 +81,7 @@ typedef struct relopt_value
union
{
bool bool_val;
+ ternary ternary_val;
int int_val;
double real_val;
int enum_val;
@@ -94,6 +96,12 @@ typedef struct relopt_bool
bool default_val;
} relopt_bool;
+typedef struct relopt_rernary
+{
+ relopt_gen gen;
+ int default_val;
+} relopt_ternary;
+
typedef struct relopt_int
{
relopt_gen gen;
@@ -152,19 +160,6 @@ typedef struct
const char *optname; /* option's name */
relopt_type opttype; /* option's datatype */
int offset; /* offset of field in result struct */
-
- /*
- * isset_offset is an optional offset of a field in the result struct that
- * stores whether the option is explicitly set for the relation or if it
- * just picked up the default value. In most cases, this can be
- * accomplished by giving the reloption a special out-of-range default
- * value (e.g., some integer reloptions use -2), but this isn't always
- * possible. For example, a Boolean reloption cannot be given an
- * out-of-range default, so we need another way to discover the source of
- * its value. This offset is only used if given a value greater than
- * zero.
- */
- int isset_offset;
} relopt_parse_elt;
/* Local reloption definition */
@@ -195,6 +190,8 @@ typedef struct local_relopts
extern relopt_kind add_reloption_kind(void);
extern void add_bool_reloption(bits32 kinds, const char *name, const char *desc,
bool default_val, LOCKMODE lockmode);
+extern void add_ternary_reloption(bits32 kinds, const char *name,
+ const char *desc, int default_val, LOCKMODE lockmode);
extern void add_int_reloption(bits32 kinds, const char *name, const char *desc,
int default_val, int min_val, int max_val,
LOCKMODE lockmode);
@@ -214,6 +211,9 @@ extern void register_reloptions_validator(local_relopts *relopts,
extern void add_local_bool_reloption(local_relopts *relopts, const char *name,
const char *desc, bool default_val,
int offset);
+extern void add_local_ternary_reloption(local_relopts *relopts,
+ const char *name, const char *desc,
+ ternary default_val, int offset);
extern void add_local_int_reloption(local_relopts *relopts, const char *name,
const char *desc, int default_val,
int min_val, int max_val, int offset);
diff --git a/src/include/c.h b/src/include/c.h
index 39022f8a9d..9c59ccccf8 100644
--- a/src/include/c.h
+++ b/src/include/c.h
@@ -488,6 +488,22 @@ typedef void (*pg_funcptr_t) (void);
#include <stdbool.h>
+/*
+ * ternary
+ * Boolean value with an extrea "unset" option
+ *
+ * Ternary data type is used in relation options that can be "true", "false" or
+ * "unset". Since relation options are used deep inside the PostgreSQL code,
+ * this type is declared globally.
+*/
+
+typedef enum ternary
+{
+ TERNARY_FALSE = 0,
+ TERNARY_TRUE = 1,
+ TERNARY_UNSET = -1
+} ternary;
+
/* ----------------------------------------------------------------
* Section 3: standard system types
diff --git a/src/include/utils/rel.h b/src/include/utils/rel.h
index b552359915..7346d618f9 100644
--- a/src/include/utils/rel.h
+++ b/src/include/utils/rel.h
@@ -346,8 +346,7 @@ typedef struct StdRdOptions
bool user_catalog_table; /* use as an additional catalog relation */
int parallel_workers; /* max number of parallel workers */
StdRdOptIndexCleanup vacuum_index_cleanup; /* controls index vacuuming */
- bool vacuum_truncate; /* enables vacuum to truncate a relation */
- bool vacuum_truncate_set; /* whether vacuum_truncate is set */
+ ternary vacuum_truncate; /* enables vacuum to truncate a relation */
/*
* Fraction of pages in a relation that vacuum can eagerly scan and fail
--
2.39.2
--nextPart5129932.5fSG56mABF
Content-Disposition: attachment;
filename="v2-0003-Add-alias-to-be-used-as-unset-state.patch"
Content-Transfer-Encoding: 7Bit
Content-Type: text/x-patch; charset="unicode-2-0-utf-8";
name="v2-0003-Add-alias-to-be-used-as-unset-state.patch"
^ permalink raw reply [nested|flat] 868+ messages in thread
* [PATCH v2 2/4] Introduce ternary reloptions
@ 2025-09-04 14:41 Nikolay Shaplov <[email protected]>
0 siblings, 0 replies; 868+ messages in thread
From: Nikolay Shaplov @ 2025-09-04 14:41 UTC (permalink / raw)
Introduce ternary reloption as a replacement for current `vacuum_truncate`
implementation. Remove `vacuum_truncate_set` additional flag and using
`TERNARY_UNSET` value instead.
---
src/backend/access/common/reloptions.c | 129 ++++++++++++++++++++-----
src/backend/commands/vacuum.c | 4 +-
src/include/access/reloptions.h | 26 ++---
src/include/c.h | 16 +++
src/include/utils/rel.h | 3 +-
5 files changed, 135 insertions(+), 43 deletions(-)
diff --git a/src/backend/access/common/reloptions.c b/src/backend/access/common/reloptions.c
index 0af3fea68f..f543f11001 100644
--- a/src/backend/access/common/reloptions.c
+++ b/src/backend/access/common/reloptions.c
@@ -40,9 +40,9 @@
*
* To add an option:
*
- * (i) decide on a type (bool, integer, real, enum, string), name, default
- * value, upper and lower bounds (if applicable); for strings, consider a
- * validation routine.
+ * (i) decide on a type (bool, ternary, integer, real, enum, string), name,
+ * default value, upper and lower bounds (if applicable); for strings,
+ * consider a validation routine.
* (ii) add a record below (or use add_<type>_reloption).
* (iii) add it to the appropriate options struct (perhaps StdRdOptions)
* (iv) add it to the appropriate handling routine (perhaps
@@ -147,15 +147,6 @@ static relopt_bool boolRelOpts[] =
},
false
},
- {
- {
- "vacuum_truncate",
- "Enables vacuum to truncate empty pages at the end of this table",
- RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
- ShareUpdateExclusiveLock
- },
- true
- },
{
{
"deduplicate_items",
@@ -170,6 +161,21 @@ static relopt_bool boolRelOpts[] =
{{NULL}}
};
+static relopt_ternary ternaryRelOpts[] =
+{
+ {
+ {
+ "vacuum_truncate",
+ "Enables vacuum to truncate empty pages at the end of this table",
+ RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
+ ShareUpdateExclusiveLock
+ },
+ TERNARY_UNSET
+ },
+ /* list terminator */
+ {{NULL}}
+};
+
static relopt_int intRelOpts[] =
{
{
@@ -600,6 +606,13 @@ initialize_reloptions(void)
boolRelOpts[i].gen.lockmode));
j++;
}
+ for (i = 0; ternaryRelOpts[i].gen.name; i++)
+ {
+ Assert(DoLockModesConflict(ternaryRelOpts[i].gen.lockmode,
+ ternaryRelOpts[i].gen.lockmode));
+ j++;
+ }
+
for (i = 0; intRelOpts[i].gen.name; i++)
{
Assert(DoLockModesConflict(intRelOpts[i].gen.lockmode,
@@ -640,6 +653,14 @@ initialize_reloptions(void)
j++;
}
+ for (i = 0; ternaryRelOpts[i].gen.name; i++)
+ {
+ relOpts[j] = &ternaryRelOpts[i].gen;
+ relOpts[j]->type = RELOPT_TYPE_TERNARY;
+ relOpts[j]->namelen = strlen(relOpts[j]->name);
+ j++;
+ }
+
for (i = 0; intRelOpts[i].gen.name; i++)
{
relOpts[j] = &intRelOpts[i].gen;
@@ -800,6 +821,9 @@ allocate_reloption(bits32 kinds, int type, const char *name, const char *desc,
case RELOPT_TYPE_BOOL:
size = sizeof(relopt_bool);
break;
+ case RELOPT_TYPE_TERNARY:
+ size = sizeof(relopt_ternary);
+ break;
case RELOPT_TYPE_INT:
size = sizeof(relopt_int);
break;
@@ -883,6 +907,54 @@ add_local_bool_reloption(local_relopts *relopts, const char *name,
add_local_reloption(relopts, (relopt_gen *) newoption, offset);
}
+/*
+ * init_ternary_reloption
+ * Allocate and initialize a new ternary reloption
+ */
+static relopt_ternary *
+init_ternary_reloption(bits32 kinds, const char *name, const char *desc,
+ ternary default_val, LOCKMODE lockmode)
+{
+ relopt_ternary *newoption;
+
+ newoption = (relopt_ternary *) allocate_reloption(kinds,
+ RELOPT_TYPE_TERNARY, name, desc, lockmode);
+ newoption->default_val = default_val;
+
+ return newoption;
+}
+
+/*
+ * add_ternary_reloption
+ * Add a new ternary reloption
+ */
+void
+add_ternary_reloption(bits32 kinds, const char *name, const char *desc,
+ ternary default_val, LOCKMODE lockmode)
+{
+ relopt_ternary *newoption = init_ternary_reloption(kinds, name, desc,
+ default_val, lockmode);
+
+ add_reloption((relopt_gen *) newoption);
+}
+
+/*
+ * add_local_ternary_reloption
+ * Add a new ternary local reloption
+ *
+ * 'offset' is offset of ternary-typed field.
+ */
+void
+add_local_ternary_reloption(local_relopts *relopts, const char *name,
+ const char *desc, ternary default_val,
+ int offset)
+{
+ relopt_ternary *newoption = init_ternary_reloption(RELOPT_KIND_LOCAL,
+ name, desc,
+ default_val, 0);
+
+ add_local_reloption(relopts, (relopt_gen *) newoption, offset);
+}
/*
* init_real_reloption
@@ -1617,6 +1689,18 @@ parse_one_reloption(relopt_value *option, char *text_str, int text_len,
option->gen->name, value)));
}
break;
+ case RELOPT_TYPE_TERNARY:
+ {
+ bool b;
+ parsed = parse_bool(value, &b);
+ option->values.ternary_val = b ? TERNARY_TRUE : TERNARY_FALSE;
+ if (validate && !parsed)
+ ereport(ERROR,
+ (errcode(ERRCODE_INVALID_PARAMETER_VALUE),
+ errmsg("invalid value for ternary option \"%s\": %s",
+ option->gen->name, value)));
+ }
+ break;
case RELOPT_TYPE_INT:
{
relopt_int *optint = (relopt_int *) option->gen;
@@ -1780,17 +1864,6 @@ fillRelOptions(void *rdopts, Size basesize,
char *itempos = ((char *) rdopts) + elems[j].offset;
char *string_val;
- /*
- * If isset_offset is provided, store whether the reloption is
- * set there.
- */
- if (elems[j].isset_offset > 0)
- {
- char *setpos = ((char *) rdopts) + elems[j].isset_offset;
-
- *(bool *) setpos = options[i].isset;
- }
-
switch (options[i].gen->type)
{
case RELOPT_TYPE_BOOL:
@@ -1798,6 +1871,11 @@ fillRelOptions(void *rdopts, Size basesize,
options[i].values.bool_val :
((relopt_bool *) options[i].gen)->default_val;
break;
+ case RELOPT_TYPE_TERNARY:
+ *(ternary *) itempos = options[i].isset ?
+ options[i].values.ternary_val :
+ ((relopt_ternary *) options[i].gen)->default_val;
+ break;
case RELOPT_TYPE_INT:
*(int *) itempos = options[i].isset ?
options[i].values.int_val :
@@ -1912,8 +1990,8 @@ default_reloptions(Datum reloptions, bool validate, relopt_kind kind)
offsetof(StdRdOptions, parallel_workers)},
{"vacuum_index_cleanup", RELOPT_TYPE_ENUM,
offsetof(StdRdOptions, vacuum_index_cleanup)},
- {"vacuum_truncate", RELOPT_TYPE_BOOL,
- offsetof(StdRdOptions, vacuum_truncate), offsetof(StdRdOptions, vacuum_truncate_set)},
+ {"vacuum_truncate", RELOPT_TYPE_TERNARY,
+ offsetof(StdRdOptions, vacuum_truncate)},
{"vacuum_max_eager_freeze_failure_rate", RELOPT_TYPE_REAL,
offsetof(StdRdOptions, vacuum_max_eager_freeze_failure_rate)}
};
@@ -1993,7 +2071,6 @@ build_local_reloptions(local_relopts *relopts, Datum options, bool validate)
elems[i].optname = opt->option->name;
elems[i].opttype = opt->option->type;
elems[i].offset = opt->offset;
- elems[i].isset_offset = 0; /* not supported for local relopts yet */
i++;
}
diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c
index 733ef40ae7..977babff54 100644
--- a/src/backend/commands/vacuum.c
+++ b/src/backend/commands/vacuum.c
@@ -2221,9 +2221,9 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams params,
{
StdRdOptions *opts = (StdRdOptions *) rel->rd_options;
- if (opts && opts->vacuum_truncate_set)
+ if (opts && opts->vacuum_truncate != TERNARY_UNSET)
{
- if (opts->vacuum_truncate)
+ if (opts->vacuum_truncate == TERNARY_TRUE)
params.truncate = VACOPTVALUE_ENABLED;
else
params.truncate = VACOPTVALUE_DISABLED;
diff --git a/src/include/access/reloptions.h b/src/include/access/reloptions.h
index a604a4702c..a436697658 100644
--- a/src/include/access/reloptions.h
+++ b/src/include/access/reloptions.h
@@ -29,6 +29,7 @@
typedef enum relopt_type
{
RELOPT_TYPE_BOOL,
+ RELOPT_TYPE_TERNARY, /* on, off, unset */
RELOPT_TYPE_INT,
RELOPT_TYPE_REAL,
RELOPT_TYPE_ENUM,
@@ -80,6 +81,7 @@ typedef struct relopt_value
union
{
bool bool_val;
+ ternary ternary_val;
int int_val;
double real_val;
int enum_val;
@@ -94,6 +96,12 @@ typedef struct relopt_bool
bool default_val;
} relopt_bool;
+typedef struct relopt_rernary
+{
+ relopt_gen gen;
+ int default_val;
+} relopt_ternary;
+
typedef struct relopt_int
{
relopt_gen gen;
@@ -152,19 +160,6 @@ typedef struct
const char *optname; /* option's name */
relopt_type opttype; /* option's datatype */
int offset; /* offset of field in result struct */
-
- /*
- * isset_offset is an optional offset of a field in the result struct that
- * stores whether the option is explicitly set for the relation or if it
- * just picked up the default value. In most cases, this can be
- * accomplished by giving the reloption a special out-of-range default
- * value (e.g., some integer reloptions use -2), but this isn't always
- * possible. For example, a Boolean reloption cannot be given an
- * out-of-range default, so we need another way to discover the source of
- * its value. This offset is only used if given a value greater than
- * zero.
- */
- int isset_offset;
} relopt_parse_elt;
/* Local reloption definition */
@@ -195,6 +190,8 @@ typedef struct local_relopts
extern relopt_kind add_reloption_kind(void);
extern void add_bool_reloption(bits32 kinds, const char *name, const char *desc,
bool default_val, LOCKMODE lockmode);
+extern void add_ternary_reloption(bits32 kinds, const char *name,
+ const char *desc, int default_val, LOCKMODE lockmode);
extern void add_int_reloption(bits32 kinds, const char *name, const char *desc,
int default_val, int min_val, int max_val,
LOCKMODE lockmode);
@@ -214,6 +211,9 @@ extern void register_reloptions_validator(local_relopts *relopts,
extern void add_local_bool_reloption(local_relopts *relopts, const char *name,
const char *desc, bool default_val,
int offset);
+extern void add_local_ternary_reloption(local_relopts *relopts,
+ const char *name, const char *desc,
+ ternary default_val, int offset);
extern void add_local_int_reloption(local_relopts *relopts, const char *name,
const char *desc, int default_val,
int min_val, int max_val, int offset);
diff --git a/src/include/c.h b/src/include/c.h
index 39022f8a9d..9c59ccccf8 100644
--- a/src/include/c.h
+++ b/src/include/c.h
@@ -488,6 +488,22 @@ typedef void (*pg_funcptr_t) (void);
#include <stdbool.h>
+/*
+ * ternary
+ * Boolean value with an extrea "unset" option
+ *
+ * Ternary data type is used in relation options that can be "true", "false" or
+ * "unset". Since relation options are used deep inside the PostgreSQL code,
+ * this type is declared globally.
+*/
+
+typedef enum ternary
+{
+ TERNARY_FALSE = 0,
+ TERNARY_TRUE = 1,
+ TERNARY_UNSET = -1
+} ternary;
+
/* ----------------------------------------------------------------
* Section 3: standard system types
diff --git a/src/include/utils/rel.h b/src/include/utils/rel.h
index b552359915..7346d618f9 100644
--- a/src/include/utils/rel.h
+++ b/src/include/utils/rel.h
@@ -346,8 +346,7 @@ typedef struct StdRdOptions
bool user_catalog_table; /* use as an additional catalog relation */
int parallel_workers; /* max number of parallel workers */
StdRdOptIndexCleanup vacuum_index_cleanup; /* controls index vacuuming */
- bool vacuum_truncate; /* enables vacuum to truncate a relation */
- bool vacuum_truncate_set; /* whether vacuum_truncate is set */
+ ternary vacuum_truncate; /* enables vacuum to truncate a relation */
/*
* Fraction of pages in a relation that vacuum can eagerly scan and fail
--
2.39.2
--nextPart5129932.5fSG56mABF
Content-Disposition: attachment;
filename="v2-0003-Add-alias-to-be-used-as-unset-state.patch"
Content-Transfer-Encoding: 7Bit
Content-Type: text/x-patch; charset="unicode-2-0-utf-8";
name="v2-0003-Add-alias-to-be-used-as-unset-state.patch"
^ permalink raw reply [nested|flat] 868+ messages in thread
* [PATCH v2 2/4] Introduce ternary reloptions
@ 2025-09-04 14:41 Nikolay Shaplov <[email protected]>
0 siblings, 0 replies; 868+ messages in thread
From: Nikolay Shaplov @ 2025-09-04 14:41 UTC (permalink / raw)
Introduce ternary reloption as a replacement for current `vacuum_truncate`
implementation. Remove `vacuum_truncate_set` additional flag and using
`TERNARY_UNSET` value instead.
---
src/backend/access/common/reloptions.c | 129 ++++++++++++++++++++-----
src/backend/commands/vacuum.c | 4 +-
src/include/access/reloptions.h | 26 ++---
src/include/c.h | 16 +++
src/include/utils/rel.h | 3 +-
5 files changed, 135 insertions(+), 43 deletions(-)
diff --git a/src/backend/access/common/reloptions.c b/src/backend/access/common/reloptions.c
index 0af3fea68f..f543f11001 100644
--- a/src/backend/access/common/reloptions.c
+++ b/src/backend/access/common/reloptions.c
@@ -40,9 +40,9 @@
*
* To add an option:
*
- * (i) decide on a type (bool, integer, real, enum, string), name, default
- * value, upper and lower bounds (if applicable); for strings, consider a
- * validation routine.
+ * (i) decide on a type (bool, ternary, integer, real, enum, string), name,
+ * default value, upper and lower bounds (if applicable); for strings,
+ * consider a validation routine.
* (ii) add a record below (or use add_<type>_reloption).
* (iii) add it to the appropriate options struct (perhaps StdRdOptions)
* (iv) add it to the appropriate handling routine (perhaps
@@ -147,15 +147,6 @@ static relopt_bool boolRelOpts[] =
},
false
},
- {
- {
- "vacuum_truncate",
- "Enables vacuum to truncate empty pages at the end of this table",
- RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
- ShareUpdateExclusiveLock
- },
- true
- },
{
{
"deduplicate_items",
@@ -170,6 +161,21 @@ static relopt_bool boolRelOpts[] =
{{NULL}}
};
+static relopt_ternary ternaryRelOpts[] =
+{
+ {
+ {
+ "vacuum_truncate",
+ "Enables vacuum to truncate empty pages at the end of this table",
+ RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
+ ShareUpdateExclusiveLock
+ },
+ TERNARY_UNSET
+ },
+ /* list terminator */
+ {{NULL}}
+};
+
static relopt_int intRelOpts[] =
{
{
@@ -600,6 +606,13 @@ initialize_reloptions(void)
boolRelOpts[i].gen.lockmode));
j++;
}
+ for (i = 0; ternaryRelOpts[i].gen.name; i++)
+ {
+ Assert(DoLockModesConflict(ternaryRelOpts[i].gen.lockmode,
+ ternaryRelOpts[i].gen.lockmode));
+ j++;
+ }
+
for (i = 0; intRelOpts[i].gen.name; i++)
{
Assert(DoLockModesConflict(intRelOpts[i].gen.lockmode,
@@ -640,6 +653,14 @@ initialize_reloptions(void)
j++;
}
+ for (i = 0; ternaryRelOpts[i].gen.name; i++)
+ {
+ relOpts[j] = &ternaryRelOpts[i].gen;
+ relOpts[j]->type = RELOPT_TYPE_TERNARY;
+ relOpts[j]->namelen = strlen(relOpts[j]->name);
+ j++;
+ }
+
for (i = 0; intRelOpts[i].gen.name; i++)
{
relOpts[j] = &intRelOpts[i].gen;
@@ -800,6 +821,9 @@ allocate_reloption(bits32 kinds, int type, const char *name, const char *desc,
case RELOPT_TYPE_BOOL:
size = sizeof(relopt_bool);
break;
+ case RELOPT_TYPE_TERNARY:
+ size = sizeof(relopt_ternary);
+ break;
case RELOPT_TYPE_INT:
size = sizeof(relopt_int);
break;
@@ -883,6 +907,54 @@ add_local_bool_reloption(local_relopts *relopts, const char *name,
add_local_reloption(relopts, (relopt_gen *) newoption, offset);
}
+/*
+ * init_ternary_reloption
+ * Allocate and initialize a new ternary reloption
+ */
+static relopt_ternary *
+init_ternary_reloption(bits32 kinds, const char *name, const char *desc,
+ ternary default_val, LOCKMODE lockmode)
+{
+ relopt_ternary *newoption;
+
+ newoption = (relopt_ternary *) allocate_reloption(kinds,
+ RELOPT_TYPE_TERNARY, name, desc, lockmode);
+ newoption->default_val = default_val;
+
+ return newoption;
+}
+
+/*
+ * add_ternary_reloption
+ * Add a new ternary reloption
+ */
+void
+add_ternary_reloption(bits32 kinds, const char *name, const char *desc,
+ ternary default_val, LOCKMODE lockmode)
+{
+ relopt_ternary *newoption = init_ternary_reloption(kinds, name, desc,
+ default_val, lockmode);
+
+ add_reloption((relopt_gen *) newoption);
+}
+
+/*
+ * add_local_ternary_reloption
+ * Add a new ternary local reloption
+ *
+ * 'offset' is offset of ternary-typed field.
+ */
+void
+add_local_ternary_reloption(local_relopts *relopts, const char *name,
+ const char *desc, ternary default_val,
+ int offset)
+{
+ relopt_ternary *newoption = init_ternary_reloption(RELOPT_KIND_LOCAL,
+ name, desc,
+ default_val, 0);
+
+ add_local_reloption(relopts, (relopt_gen *) newoption, offset);
+}
/*
* init_real_reloption
@@ -1617,6 +1689,18 @@ parse_one_reloption(relopt_value *option, char *text_str, int text_len,
option->gen->name, value)));
}
break;
+ case RELOPT_TYPE_TERNARY:
+ {
+ bool b;
+ parsed = parse_bool(value, &b);
+ option->values.ternary_val = b ? TERNARY_TRUE : TERNARY_FALSE;
+ if (validate && !parsed)
+ ereport(ERROR,
+ (errcode(ERRCODE_INVALID_PARAMETER_VALUE),
+ errmsg("invalid value for ternary option \"%s\": %s",
+ option->gen->name, value)));
+ }
+ break;
case RELOPT_TYPE_INT:
{
relopt_int *optint = (relopt_int *) option->gen;
@@ -1780,17 +1864,6 @@ fillRelOptions(void *rdopts, Size basesize,
char *itempos = ((char *) rdopts) + elems[j].offset;
char *string_val;
- /*
- * If isset_offset is provided, store whether the reloption is
- * set there.
- */
- if (elems[j].isset_offset > 0)
- {
- char *setpos = ((char *) rdopts) + elems[j].isset_offset;
-
- *(bool *) setpos = options[i].isset;
- }
-
switch (options[i].gen->type)
{
case RELOPT_TYPE_BOOL:
@@ -1798,6 +1871,11 @@ fillRelOptions(void *rdopts, Size basesize,
options[i].values.bool_val :
((relopt_bool *) options[i].gen)->default_val;
break;
+ case RELOPT_TYPE_TERNARY:
+ *(ternary *) itempos = options[i].isset ?
+ options[i].values.ternary_val :
+ ((relopt_ternary *) options[i].gen)->default_val;
+ break;
case RELOPT_TYPE_INT:
*(int *) itempos = options[i].isset ?
options[i].values.int_val :
@@ -1912,8 +1990,8 @@ default_reloptions(Datum reloptions, bool validate, relopt_kind kind)
offsetof(StdRdOptions, parallel_workers)},
{"vacuum_index_cleanup", RELOPT_TYPE_ENUM,
offsetof(StdRdOptions, vacuum_index_cleanup)},
- {"vacuum_truncate", RELOPT_TYPE_BOOL,
- offsetof(StdRdOptions, vacuum_truncate), offsetof(StdRdOptions, vacuum_truncate_set)},
+ {"vacuum_truncate", RELOPT_TYPE_TERNARY,
+ offsetof(StdRdOptions, vacuum_truncate)},
{"vacuum_max_eager_freeze_failure_rate", RELOPT_TYPE_REAL,
offsetof(StdRdOptions, vacuum_max_eager_freeze_failure_rate)}
};
@@ -1993,7 +2071,6 @@ build_local_reloptions(local_relopts *relopts, Datum options, bool validate)
elems[i].optname = opt->option->name;
elems[i].opttype = opt->option->type;
elems[i].offset = opt->offset;
- elems[i].isset_offset = 0; /* not supported for local relopts yet */
i++;
}
diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c
index 733ef40ae7..977babff54 100644
--- a/src/backend/commands/vacuum.c
+++ b/src/backend/commands/vacuum.c
@@ -2221,9 +2221,9 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams params,
{
StdRdOptions *opts = (StdRdOptions *) rel->rd_options;
- if (opts && opts->vacuum_truncate_set)
+ if (opts && opts->vacuum_truncate != TERNARY_UNSET)
{
- if (opts->vacuum_truncate)
+ if (opts->vacuum_truncate == TERNARY_TRUE)
params.truncate = VACOPTVALUE_ENABLED;
else
params.truncate = VACOPTVALUE_DISABLED;
diff --git a/src/include/access/reloptions.h b/src/include/access/reloptions.h
index a604a4702c..a436697658 100644
--- a/src/include/access/reloptions.h
+++ b/src/include/access/reloptions.h
@@ -29,6 +29,7 @@
typedef enum relopt_type
{
RELOPT_TYPE_BOOL,
+ RELOPT_TYPE_TERNARY, /* on, off, unset */
RELOPT_TYPE_INT,
RELOPT_TYPE_REAL,
RELOPT_TYPE_ENUM,
@@ -80,6 +81,7 @@ typedef struct relopt_value
union
{
bool bool_val;
+ ternary ternary_val;
int int_val;
double real_val;
int enum_val;
@@ -94,6 +96,12 @@ typedef struct relopt_bool
bool default_val;
} relopt_bool;
+typedef struct relopt_rernary
+{
+ relopt_gen gen;
+ int default_val;
+} relopt_ternary;
+
typedef struct relopt_int
{
relopt_gen gen;
@@ -152,19 +160,6 @@ typedef struct
const char *optname; /* option's name */
relopt_type opttype; /* option's datatype */
int offset; /* offset of field in result struct */
-
- /*
- * isset_offset is an optional offset of a field in the result struct that
- * stores whether the option is explicitly set for the relation or if it
- * just picked up the default value. In most cases, this can be
- * accomplished by giving the reloption a special out-of-range default
- * value (e.g., some integer reloptions use -2), but this isn't always
- * possible. For example, a Boolean reloption cannot be given an
- * out-of-range default, so we need another way to discover the source of
- * its value. This offset is only used if given a value greater than
- * zero.
- */
- int isset_offset;
} relopt_parse_elt;
/* Local reloption definition */
@@ -195,6 +190,8 @@ typedef struct local_relopts
extern relopt_kind add_reloption_kind(void);
extern void add_bool_reloption(bits32 kinds, const char *name, const char *desc,
bool default_val, LOCKMODE lockmode);
+extern void add_ternary_reloption(bits32 kinds, const char *name,
+ const char *desc, int default_val, LOCKMODE lockmode);
extern void add_int_reloption(bits32 kinds, const char *name, const char *desc,
int default_val, int min_val, int max_val,
LOCKMODE lockmode);
@@ -214,6 +211,9 @@ extern void register_reloptions_validator(local_relopts *relopts,
extern void add_local_bool_reloption(local_relopts *relopts, const char *name,
const char *desc, bool default_val,
int offset);
+extern void add_local_ternary_reloption(local_relopts *relopts,
+ const char *name, const char *desc,
+ ternary default_val, int offset);
extern void add_local_int_reloption(local_relopts *relopts, const char *name,
const char *desc, int default_val,
int min_val, int max_val, int offset);
diff --git a/src/include/c.h b/src/include/c.h
index 39022f8a9d..9c59ccccf8 100644
--- a/src/include/c.h
+++ b/src/include/c.h
@@ -488,6 +488,22 @@ typedef void (*pg_funcptr_t) (void);
#include <stdbool.h>
+/*
+ * ternary
+ * Boolean value with an extrea "unset" option
+ *
+ * Ternary data type is used in relation options that can be "true", "false" or
+ * "unset". Since relation options are used deep inside the PostgreSQL code,
+ * this type is declared globally.
+*/
+
+typedef enum ternary
+{
+ TERNARY_FALSE = 0,
+ TERNARY_TRUE = 1,
+ TERNARY_UNSET = -1
+} ternary;
+
/* ----------------------------------------------------------------
* Section 3: standard system types
diff --git a/src/include/utils/rel.h b/src/include/utils/rel.h
index b552359915..7346d618f9 100644
--- a/src/include/utils/rel.h
+++ b/src/include/utils/rel.h
@@ -346,8 +346,7 @@ typedef struct StdRdOptions
bool user_catalog_table; /* use as an additional catalog relation */
int parallel_workers; /* max number of parallel workers */
StdRdOptIndexCleanup vacuum_index_cleanup; /* controls index vacuuming */
- bool vacuum_truncate; /* enables vacuum to truncate a relation */
- bool vacuum_truncate_set; /* whether vacuum_truncate is set */
+ ternary vacuum_truncate; /* enables vacuum to truncate a relation */
/*
* Fraction of pages in a relation that vacuum can eagerly scan and fail
--
2.39.2
--nextPart5129932.5fSG56mABF
Content-Disposition: attachment;
filename="v2-0003-Add-alias-to-be-used-as-unset-state.patch"
Content-Transfer-Encoding: 7Bit
Content-Type: text/x-patch; charset="unicode-2-0-utf-8";
name="v2-0003-Add-alias-to-be-used-as-unset-state.patch"
^ permalink raw reply [nested|flat] 868+ messages in thread
* [PATCH v2a 2/4] Introduce ternary reloptions
@ 2025-09-04 14:41 Nikolay Shaplov <[email protected]>
0 siblings, 0 replies; 868+ messages in thread
From: Nikolay Shaplov @ 2025-09-04 14:41 UTC (permalink / raw)
Introduce ternary reloption as a replacement for current `vacuum_truncate`
implementation. Remove `vacuum_truncate_set` additional flag and using
`TERNARY_UNSET` value instead.
---
src/backend/access/common/reloptions.c | 129 ++++++++++++++++++++-----
src/backend/commands/vacuum.c | 4 +-
src/include/access/reloptions.h | 26 ++---
src/include/c.h | 16 +++
src/include/utils/rel.h | 3 +-
5 files changed, 135 insertions(+), 43 deletions(-)
diff --git a/src/backend/access/common/reloptions.c b/src/backend/access/common/reloptions.c
index 0af3fea68f..f543f11001 100644
--- a/src/backend/access/common/reloptions.c
+++ b/src/backend/access/common/reloptions.c
@@ -40,9 +40,9 @@
*
* To add an option:
*
- * (i) decide on a type (bool, integer, real, enum, string), name, default
- * value, upper and lower bounds (if applicable); for strings, consider a
- * validation routine.
+ * (i) decide on a type (bool, ternary, integer, real, enum, string), name,
+ * default value, upper and lower bounds (if applicable); for strings,
+ * consider a validation routine.
* (ii) add a record below (or use add_<type>_reloption).
* (iii) add it to the appropriate options struct (perhaps StdRdOptions)
* (iv) add it to the appropriate handling routine (perhaps
@@ -147,15 +147,6 @@ static relopt_bool boolRelOpts[] =
},
false
},
- {
- {
- "vacuum_truncate",
- "Enables vacuum to truncate empty pages at the end of this table",
- RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
- ShareUpdateExclusiveLock
- },
- true
- },
{
{
"deduplicate_items",
@@ -170,6 +161,21 @@ static relopt_bool boolRelOpts[] =
{{NULL}}
};
+static relopt_ternary ternaryRelOpts[] =
+{
+ {
+ {
+ "vacuum_truncate",
+ "Enables vacuum to truncate empty pages at the end of this table",
+ RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
+ ShareUpdateExclusiveLock
+ },
+ TERNARY_UNSET
+ },
+ /* list terminator */
+ {{NULL}}
+};
+
static relopt_int intRelOpts[] =
{
{
@@ -600,6 +606,13 @@ initialize_reloptions(void)
boolRelOpts[i].gen.lockmode));
j++;
}
+ for (i = 0; ternaryRelOpts[i].gen.name; i++)
+ {
+ Assert(DoLockModesConflict(ternaryRelOpts[i].gen.lockmode,
+ ternaryRelOpts[i].gen.lockmode));
+ j++;
+ }
+
for (i = 0; intRelOpts[i].gen.name; i++)
{
Assert(DoLockModesConflict(intRelOpts[i].gen.lockmode,
@@ -640,6 +653,14 @@ initialize_reloptions(void)
j++;
}
+ for (i = 0; ternaryRelOpts[i].gen.name; i++)
+ {
+ relOpts[j] = &ternaryRelOpts[i].gen;
+ relOpts[j]->type = RELOPT_TYPE_TERNARY;
+ relOpts[j]->namelen = strlen(relOpts[j]->name);
+ j++;
+ }
+
for (i = 0; intRelOpts[i].gen.name; i++)
{
relOpts[j] = &intRelOpts[i].gen;
@@ -800,6 +821,9 @@ allocate_reloption(bits32 kinds, int type, const char *name, const char *desc,
case RELOPT_TYPE_BOOL:
size = sizeof(relopt_bool);
break;
+ case RELOPT_TYPE_TERNARY:
+ size = sizeof(relopt_ternary);
+ break;
case RELOPT_TYPE_INT:
size = sizeof(relopt_int);
break;
@@ -883,6 +907,54 @@ add_local_bool_reloption(local_relopts *relopts, const char *name,
add_local_reloption(relopts, (relopt_gen *) newoption, offset);
}
+/*
+ * init_ternary_reloption
+ * Allocate and initialize a new ternary reloption
+ */
+static relopt_ternary *
+init_ternary_reloption(bits32 kinds, const char *name, const char *desc,
+ ternary default_val, LOCKMODE lockmode)
+{
+ relopt_ternary *newoption;
+
+ newoption = (relopt_ternary *) allocate_reloption(kinds,
+ RELOPT_TYPE_TERNARY, name, desc, lockmode);
+ newoption->default_val = default_val;
+
+ return newoption;
+}
+
+/*
+ * add_ternary_reloption
+ * Add a new ternary reloption
+ */
+void
+add_ternary_reloption(bits32 kinds, const char *name, const char *desc,
+ ternary default_val, LOCKMODE lockmode)
+{
+ relopt_ternary *newoption = init_ternary_reloption(kinds, name, desc,
+ default_val, lockmode);
+
+ add_reloption((relopt_gen *) newoption);
+}
+
+/*
+ * add_local_ternary_reloption
+ * Add a new ternary local reloption
+ *
+ * 'offset' is offset of ternary-typed field.
+ */
+void
+add_local_ternary_reloption(local_relopts *relopts, const char *name,
+ const char *desc, ternary default_val,
+ int offset)
+{
+ relopt_ternary *newoption = init_ternary_reloption(RELOPT_KIND_LOCAL,
+ name, desc,
+ default_val, 0);
+
+ add_local_reloption(relopts, (relopt_gen *) newoption, offset);
+}
/*
* init_real_reloption
@@ -1617,6 +1689,18 @@ parse_one_reloption(relopt_value *option, char *text_str, int text_len,
option->gen->name, value)));
}
break;
+ case RELOPT_TYPE_TERNARY:
+ {
+ bool b;
+ parsed = parse_bool(value, &b);
+ option->values.ternary_val = b ? TERNARY_TRUE : TERNARY_FALSE;
+ if (validate && !parsed)
+ ereport(ERROR,
+ (errcode(ERRCODE_INVALID_PARAMETER_VALUE),
+ errmsg("invalid value for ternary option \"%s\": %s",
+ option->gen->name, value)));
+ }
+ break;
case RELOPT_TYPE_INT:
{
relopt_int *optint = (relopt_int *) option->gen;
@@ -1780,17 +1864,6 @@ fillRelOptions(void *rdopts, Size basesize,
char *itempos = ((char *) rdopts) + elems[j].offset;
char *string_val;
- /*
- * If isset_offset is provided, store whether the reloption is
- * set there.
- */
- if (elems[j].isset_offset > 0)
- {
- char *setpos = ((char *) rdopts) + elems[j].isset_offset;
-
- *(bool *) setpos = options[i].isset;
- }
-
switch (options[i].gen->type)
{
case RELOPT_TYPE_BOOL:
@@ -1798,6 +1871,11 @@ fillRelOptions(void *rdopts, Size basesize,
options[i].values.bool_val :
((relopt_bool *) options[i].gen)->default_val;
break;
+ case RELOPT_TYPE_TERNARY:
+ *(ternary *) itempos = options[i].isset ?
+ options[i].values.ternary_val :
+ ((relopt_ternary *) options[i].gen)->default_val;
+ break;
case RELOPT_TYPE_INT:
*(int *) itempos = options[i].isset ?
options[i].values.int_val :
@@ -1912,8 +1990,8 @@ default_reloptions(Datum reloptions, bool validate, relopt_kind kind)
offsetof(StdRdOptions, parallel_workers)},
{"vacuum_index_cleanup", RELOPT_TYPE_ENUM,
offsetof(StdRdOptions, vacuum_index_cleanup)},
- {"vacuum_truncate", RELOPT_TYPE_BOOL,
- offsetof(StdRdOptions, vacuum_truncate), offsetof(StdRdOptions, vacuum_truncate_set)},
+ {"vacuum_truncate", RELOPT_TYPE_TERNARY,
+ offsetof(StdRdOptions, vacuum_truncate)},
{"vacuum_max_eager_freeze_failure_rate", RELOPT_TYPE_REAL,
offsetof(StdRdOptions, vacuum_max_eager_freeze_failure_rate)}
};
@@ -1993,7 +2071,6 @@ build_local_reloptions(local_relopts *relopts, Datum options, bool validate)
elems[i].optname = opt->option->name;
elems[i].opttype = opt->option->type;
elems[i].offset = opt->offset;
- elems[i].isset_offset = 0; /* not supported for local relopts yet */
i++;
}
diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c
index 733ef40ae7..977babff54 100644
--- a/src/backend/commands/vacuum.c
+++ b/src/backend/commands/vacuum.c
@@ -2221,9 +2221,9 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams params,
{
StdRdOptions *opts = (StdRdOptions *) rel->rd_options;
- if (opts && opts->vacuum_truncate_set)
+ if (opts && opts->vacuum_truncate != TERNARY_UNSET)
{
- if (opts->vacuum_truncate)
+ if (opts->vacuum_truncate == TERNARY_TRUE)
params.truncate = VACOPTVALUE_ENABLED;
else
params.truncate = VACOPTVALUE_DISABLED;
diff --git a/src/include/access/reloptions.h b/src/include/access/reloptions.h
index a604a4702c..a436697658 100644
--- a/src/include/access/reloptions.h
+++ b/src/include/access/reloptions.h
@@ -29,6 +29,7 @@
typedef enum relopt_type
{
RELOPT_TYPE_BOOL,
+ RELOPT_TYPE_TERNARY, /* on, off, unset */
RELOPT_TYPE_INT,
RELOPT_TYPE_REAL,
RELOPT_TYPE_ENUM,
@@ -80,6 +81,7 @@ typedef struct relopt_value
union
{
bool bool_val;
+ ternary ternary_val;
int int_val;
double real_val;
int enum_val;
@@ -94,6 +96,12 @@ typedef struct relopt_bool
bool default_val;
} relopt_bool;
+typedef struct relopt_rernary
+{
+ relopt_gen gen;
+ int default_val;
+} relopt_ternary;
+
typedef struct relopt_int
{
relopt_gen gen;
@@ -152,19 +160,6 @@ typedef struct
const char *optname; /* option's name */
relopt_type opttype; /* option's datatype */
int offset; /* offset of field in result struct */
-
- /*
- * isset_offset is an optional offset of a field in the result struct that
- * stores whether the option is explicitly set for the relation or if it
- * just picked up the default value. In most cases, this can be
- * accomplished by giving the reloption a special out-of-range default
- * value (e.g., some integer reloptions use -2), but this isn't always
- * possible. For example, a Boolean reloption cannot be given an
- * out-of-range default, so we need another way to discover the source of
- * its value. This offset is only used if given a value greater than
- * zero.
- */
- int isset_offset;
} relopt_parse_elt;
/* Local reloption definition */
@@ -195,6 +190,8 @@ typedef struct local_relopts
extern relopt_kind add_reloption_kind(void);
extern void add_bool_reloption(bits32 kinds, const char *name, const char *desc,
bool default_val, LOCKMODE lockmode);
+extern void add_ternary_reloption(bits32 kinds, const char *name,
+ const char *desc, int default_val, LOCKMODE lockmode);
extern void add_int_reloption(bits32 kinds, const char *name, const char *desc,
int default_val, int min_val, int max_val,
LOCKMODE lockmode);
@@ -214,6 +211,9 @@ extern void register_reloptions_validator(local_relopts *relopts,
extern void add_local_bool_reloption(local_relopts *relopts, const char *name,
const char *desc, bool default_val,
int offset);
+extern void add_local_ternary_reloption(local_relopts *relopts,
+ const char *name, const char *desc,
+ ternary default_val, int offset);
extern void add_local_int_reloption(local_relopts *relopts, const char *name,
const char *desc, int default_val,
int min_val, int max_val, int offset);
diff --git a/src/include/c.h b/src/include/c.h
index 39022f8a9d..9c59ccccf8 100644
--- a/src/include/c.h
+++ b/src/include/c.h
@@ -488,6 +488,22 @@ typedef void (*pg_funcptr_t) (void);
#include <stdbool.h>
+/*
+ * ternary
+ * Boolean value with an extrea "unset" option
+ *
+ * Ternary data type is used in relation options that can be "true", "false" or
+ * "unset". Since relation options are used deep inside the PostgreSQL code,
+ * this type is declared globally.
+*/
+
+typedef enum ternary
+{
+ TERNARY_FALSE = 0,
+ TERNARY_TRUE = 1,
+ TERNARY_UNSET = -1
+} ternary;
+
/* ----------------------------------------------------------------
* Section 3: standard system types
diff --git a/src/include/utils/rel.h b/src/include/utils/rel.h
index b552359915..7346d618f9 100644
--- a/src/include/utils/rel.h
+++ b/src/include/utils/rel.h
@@ -346,8 +346,7 @@ typedef struct StdRdOptions
bool user_catalog_table; /* use as an additional catalog relation */
int parallel_workers; /* max number of parallel workers */
StdRdOptIndexCleanup vacuum_index_cleanup; /* controls index vacuuming */
- bool vacuum_truncate; /* enables vacuum to truncate a relation */
- bool vacuum_truncate_set; /* whether vacuum_truncate is set */
+ ternary vacuum_truncate; /* enables vacuum to truncate a relation */
/*
* Fraction of pages in a relation that vacuum can eagerly scan and fail
--
2.39.2
--nextPart4155600.3ZeAukHxDK
Content-Disposition: attachment;
filename="v2a-0003-Add-alias-to-be-used-as-unset-state.patch"
Content-Transfer-Encoding: 7Bit
Content-Type: text/x-patch; charset="unicode-2-0-utf-8";
name="v2a-0003-Add-alias-to-be-used-as-unset-state.patch"
^ permalink raw reply [nested|flat] 868+ messages in thread
* [PATCH v2a 2/4] Introduce ternary reloptions
@ 2025-09-04 14:41 Nikolay Shaplov <[email protected]>
0 siblings, 0 replies; 868+ messages in thread
From: Nikolay Shaplov @ 2025-09-04 14:41 UTC (permalink / raw)
Introduce ternary reloption as a replacement for current `vacuum_truncate`
implementation. Remove `vacuum_truncate_set` additional flag and using
`TERNARY_UNSET` value instead.
---
src/backend/access/common/reloptions.c | 129 ++++++++++++++++++++-----
src/backend/commands/vacuum.c | 4 +-
src/include/access/reloptions.h | 26 ++---
src/include/c.h | 16 +++
src/include/utils/rel.h | 3 +-
5 files changed, 135 insertions(+), 43 deletions(-)
diff --git a/src/backend/access/common/reloptions.c b/src/backend/access/common/reloptions.c
index 0af3fea68f..f543f11001 100644
--- a/src/backend/access/common/reloptions.c
+++ b/src/backend/access/common/reloptions.c
@@ -40,9 +40,9 @@
*
* To add an option:
*
- * (i) decide on a type (bool, integer, real, enum, string), name, default
- * value, upper and lower bounds (if applicable); for strings, consider a
- * validation routine.
+ * (i) decide on a type (bool, ternary, integer, real, enum, string), name,
+ * default value, upper and lower bounds (if applicable); for strings,
+ * consider a validation routine.
* (ii) add a record below (or use add_<type>_reloption).
* (iii) add it to the appropriate options struct (perhaps StdRdOptions)
* (iv) add it to the appropriate handling routine (perhaps
@@ -147,15 +147,6 @@ static relopt_bool boolRelOpts[] =
},
false
},
- {
- {
- "vacuum_truncate",
- "Enables vacuum to truncate empty pages at the end of this table",
- RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
- ShareUpdateExclusiveLock
- },
- true
- },
{
{
"deduplicate_items",
@@ -170,6 +161,21 @@ static relopt_bool boolRelOpts[] =
{{NULL}}
};
+static relopt_ternary ternaryRelOpts[] =
+{
+ {
+ {
+ "vacuum_truncate",
+ "Enables vacuum to truncate empty pages at the end of this table",
+ RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
+ ShareUpdateExclusiveLock
+ },
+ TERNARY_UNSET
+ },
+ /* list terminator */
+ {{NULL}}
+};
+
static relopt_int intRelOpts[] =
{
{
@@ -600,6 +606,13 @@ initialize_reloptions(void)
boolRelOpts[i].gen.lockmode));
j++;
}
+ for (i = 0; ternaryRelOpts[i].gen.name; i++)
+ {
+ Assert(DoLockModesConflict(ternaryRelOpts[i].gen.lockmode,
+ ternaryRelOpts[i].gen.lockmode));
+ j++;
+ }
+
for (i = 0; intRelOpts[i].gen.name; i++)
{
Assert(DoLockModesConflict(intRelOpts[i].gen.lockmode,
@@ -640,6 +653,14 @@ initialize_reloptions(void)
j++;
}
+ for (i = 0; ternaryRelOpts[i].gen.name; i++)
+ {
+ relOpts[j] = &ternaryRelOpts[i].gen;
+ relOpts[j]->type = RELOPT_TYPE_TERNARY;
+ relOpts[j]->namelen = strlen(relOpts[j]->name);
+ j++;
+ }
+
for (i = 0; intRelOpts[i].gen.name; i++)
{
relOpts[j] = &intRelOpts[i].gen;
@@ -800,6 +821,9 @@ allocate_reloption(bits32 kinds, int type, const char *name, const char *desc,
case RELOPT_TYPE_BOOL:
size = sizeof(relopt_bool);
break;
+ case RELOPT_TYPE_TERNARY:
+ size = sizeof(relopt_ternary);
+ break;
case RELOPT_TYPE_INT:
size = sizeof(relopt_int);
break;
@@ -883,6 +907,54 @@ add_local_bool_reloption(local_relopts *relopts, const char *name,
add_local_reloption(relopts, (relopt_gen *) newoption, offset);
}
+/*
+ * init_ternary_reloption
+ * Allocate and initialize a new ternary reloption
+ */
+static relopt_ternary *
+init_ternary_reloption(bits32 kinds, const char *name, const char *desc,
+ ternary default_val, LOCKMODE lockmode)
+{
+ relopt_ternary *newoption;
+
+ newoption = (relopt_ternary *) allocate_reloption(kinds,
+ RELOPT_TYPE_TERNARY, name, desc, lockmode);
+ newoption->default_val = default_val;
+
+ return newoption;
+}
+
+/*
+ * add_ternary_reloption
+ * Add a new ternary reloption
+ */
+void
+add_ternary_reloption(bits32 kinds, const char *name, const char *desc,
+ ternary default_val, LOCKMODE lockmode)
+{
+ relopt_ternary *newoption = init_ternary_reloption(kinds, name, desc,
+ default_val, lockmode);
+
+ add_reloption((relopt_gen *) newoption);
+}
+
+/*
+ * add_local_ternary_reloption
+ * Add a new ternary local reloption
+ *
+ * 'offset' is offset of ternary-typed field.
+ */
+void
+add_local_ternary_reloption(local_relopts *relopts, const char *name,
+ const char *desc, ternary default_val,
+ int offset)
+{
+ relopt_ternary *newoption = init_ternary_reloption(RELOPT_KIND_LOCAL,
+ name, desc,
+ default_val, 0);
+
+ add_local_reloption(relopts, (relopt_gen *) newoption, offset);
+}
/*
* init_real_reloption
@@ -1617,6 +1689,18 @@ parse_one_reloption(relopt_value *option, char *text_str, int text_len,
option->gen->name, value)));
}
break;
+ case RELOPT_TYPE_TERNARY:
+ {
+ bool b;
+ parsed = parse_bool(value, &b);
+ option->values.ternary_val = b ? TERNARY_TRUE : TERNARY_FALSE;
+ if (validate && !parsed)
+ ereport(ERROR,
+ (errcode(ERRCODE_INVALID_PARAMETER_VALUE),
+ errmsg("invalid value for ternary option \"%s\": %s",
+ option->gen->name, value)));
+ }
+ break;
case RELOPT_TYPE_INT:
{
relopt_int *optint = (relopt_int *) option->gen;
@@ -1780,17 +1864,6 @@ fillRelOptions(void *rdopts, Size basesize,
char *itempos = ((char *) rdopts) + elems[j].offset;
char *string_val;
- /*
- * If isset_offset is provided, store whether the reloption is
- * set there.
- */
- if (elems[j].isset_offset > 0)
- {
- char *setpos = ((char *) rdopts) + elems[j].isset_offset;
-
- *(bool *) setpos = options[i].isset;
- }
-
switch (options[i].gen->type)
{
case RELOPT_TYPE_BOOL:
@@ -1798,6 +1871,11 @@ fillRelOptions(void *rdopts, Size basesize,
options[i].values.bool_val :
((relopt_bool *) options[i].gen)->default_val;
break;
+ case RELOPT_TYPE_TERNARY:
+ *(ternary *) itempos = options[i].isset ?
+ options[i].values.ternary_val :
+ ((relopt_ternary *) options[i].gen)->default_val;
+ break;
case RELOPT_TYPE_INT:
*(int *) itempos = options[i].isset ?
options[i].values.int_val :
@@ -1912,8 +1990,8 @@ default_reloptions(Datum reloptions, bool validate, relopt_kind kind)
offsetof(StdRdOptions, parallel_workers)},
{"vacuum_index_cleanup", RELOPT_TYPE_ENUM,
offsetof(StdRdOptions, vacuum_index_cleanup)},
- {"vacuum_truncate", RELOPT_TYPE_BOOL,
- offsetof(StdRdOptions, vacuum_truncate), offsetof(StdRdOptions, vacuum_truncate_set)},
+ {"vacuum_truncate", RELOPT_TYPE_TERNARY,
+ offsetof(StdRdOptions, vacuum_truncate)},
{"vacuum_max_eager_freeze_failure_rate", RELOPT_TYPE_REAL,
offsetof(StdRdOptions, vacuum_max_eager_freeze_failure_rate)}
};
@@ -1993,7 +2071,6 @@ build_local_reloptions(local_relopts *relopts, Datum options, bool validate)
elems[i].optname = opt->option->name;
elems[i].opttype = opt->option->type;
elems[i].offset = opt->offset;
- elems[i].isset_offset = 0; /* not supported for local relopts yet */
i++;
}
diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c
index 733ef40ae7..977babff54 100644
--- a/src/backend/commands/vacuum.c
+++ b/src/backend/commands/vacuum.c
@@ -2221,9 +2221,9 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams params,
{
StdRdOptions *opts = (StdRdOptions *) rel->rd_options;
- if (opts && opts->vacuum_truncate_set)
+ if (opts && opts->vacuum_truncate != TERNARY_UNSET)
{
- if (opts->vacuum_truncate)
+ if (opts->vacuum_truncate == TERNARY_TRUE)
params.truncate = VACOPTVALUE_ENABLED;
else
params.truncate = VACOPTVALUE_DISABLED;
diff --git a/src/include/access/reloptions.h b/src/include/access/reloptions.h
index a604a4702c..a436697658 100644
--- a/src/include/access/reloptions.h
+++ b/src/include/access/reloptions.h
@@ -29,6 +29,7 @@
typedef enum relopt_type
{
RELOPT_TYPE_BOOL,
+ RELOPT_TYPE_TERNARY, /* on, off, unset */
RELOPT_TYPE_INT,
RELOPT_TYPE_REAL,
RELOPT_TYPE_ENUM,
@@ -80,6 +81,7 @@ typedef struct relopt_value
union
{
bool bool_val;
+ ternary ternary_val;
int int_val;
double real_val;
int enum_val;
@@ -94,6 +96,12 @@ typedef struct relopt_bool
bool default_val;
} relopt_bool;
+typedef struct relopt_rernary
+{
+ relopt_gen gen;
+ int default_val;
+} relopt_ternary;
+
typedef struct relopt_int
{
relopt_gen gen;
@@ -152,19 +160,6 @@ typedef struct
const char *optname; /* option's name */
relopt_type opttype; /* option's datatype */
int offset; /* offset of field in result struct */
-
- /*
- * isset_offset is an optional offset of a field in the result struct that
- * stores whether the option is explicitly set for the relation or if it
- * just picked up the default value. In most cases, this can be
- * accomplished by giving the reloption a special out-of-range default
- * value (e.g., some integer reloptions use -2), but this isn't always
- * possible. For example, a Boolean reloption cannot be given an
- * out-of-range default, so we need another way to discover the source of
- * its value. This offset is only used if given a value greater than
- * zero.
- */
- int isset_offset;
} relopt_parse_elt;
/* Local reloption definition */
@@ -195,6 +190,8 @@ typedef struct local_relopts
extern relopt_kind add_reloption_kind(void);
extern void add_bool_reloption(bits32 kinds, const char *name, const char *desc,
bool default_val, LOCKMODE lockmode);
+extern void add_ternary_reloption(bits32 kinds, const char *name,
+ const char *desc, int default_val, LOCKMODE lockmode);
extern void add_int_reloption(bits32 kinds, const char *name, const char *desc,
int default_val, int min_val, int max_val,
LOCKMODE lockmode);
@@ -214,6 +211,9 @@ extern void register_reloptions_validator(local_relopts *relopts,
extern void add_local_bool_reloption(local_relopts *relopts, const char *name,
const char *desc, bool default_val,
int offset);
+extern void add_local_ternary_reloption(local_relopts *relopts,
+ const char *name, const char *desc,
+ ternary default_val, int offset);
extern void add_local_int_reloption(local_relopts *relopts, const char *name,
const char *desc, int default_val,
int min_val, int max_val, int offset);
diff --git a/src/include/c.h b/src/include/c.h
index 39022f8a9d..9c59ccccf8 100644
--- a/src/include/c.h
+++ b/src/include/c.h
@@ -488,6 +488,22 @@ typedef void (*pg_funcptr_t) (void);
#include <stdbool.h>
+/*
+ * ternary
+ * Boolean value with an extrea "unset" option
+ *
+ * Ternary data type is used in relation options that can be "true", "false" or
+ * "unset". Since relation options are used deep inside the PostgreSQL code,
+ * this type is declared globally.
+*/
+
+typedef enum ternary
+{
+ TERNARY_FALSE = 0,
+ TERNARY_TRUE = 1,
+ TERNARY_UNSET = -1
+} ternary;
+
/* ----------------------------------------------------------------
* Section 3: standard system types
diff --git a/src/include/utils/rel.h b/src/include/utils/rel.h
index b552359915..7346d618f9 100644
--- a/src/include/utils/rel.h
+++ b/src/include/utils/rel.h
@@ -346,8 +346,7 @@ typedef struct StdRdOptions
bool user_catalog_table; /* use as an additional catalog relation */
int parallel_workers; /* max number of parallel workers */
StdRdOptIndexCleanup vacuum_index_cleanup; /* controls index vacuuming */
- bool vacuum_truncate; /* enables vacuum to truncate a relation */
- bool vacuum_truncate_set; /* whether vacuum_truncate is set */
+ ternary vacuum_truncate; /* enables vacuum to truncate a relation */
/*
* Fraction of pages in a relation that vacuum can eagerly scan and fail
--
2.39.2
--nextPart4155600.3ZeAukHxDK
Content-Disposition: attachment;
filename="v2a-0003-Add-alias-to-be-used-as-unset-state.patch"
Content-Transfer-Encoding: 7Bit
Content-Type: text/x-patch; charset="unicode-2-0-utf-8";
name="v2a-0003-Add-alias-to-be-used-as-unset-state.patch"
^ permalink raw reply [nested|flat] 868+ messages in thread
* [PATCH v2 2/4] Introduce ternary reloptions
@ 2025-09-04 14:41 Nikolay Shaplov <[email protected]>
0 siblings, 0 replies; 868+ messages in thread
From: Nikolay Shaplov @ 2025-09-04 14:41 UTC (permalink / raw)
Introduce ternary reloption as a replacement for current `vacuum_truncate`
implementation. Remove `vacuum_truncate_set` additional flag and using
`TERNARY_UNSET` value instead.
---
src/backend/access/common/reloptions.c | 129 ++++++++++++++++++++-----
src/backend/commands/vacuum.c | 4 +-
src/include/access/reloptions.h | 26 ++---
src/include/c.h | 16 +++
src/include/utils/rel.h | 3 +-
5 files changed, 135 insertions(+), 43 deletions(-)
diff --git a/src/backend/access/common/reloptions.c b/src/backend/access/common/reloptions.c
index 0af3fea68f..f543f11001 100644
--- a/src/backend/access/common/reloptions.c
+++ b/src/backend/access/common/reloptions.c
@@ -40,9 +40,9 @@
*
* To add an option:
*
- * (i) decide on a type (bool, integer, real, enum, string), name, default
- * value, upper and lower bounds (if applicable); for strings, consider a
- * validation routine.
+ * (i) decide on a type (bool, ternary, integer, real, enum, string), name,
+ * default value, upper and lower bounds (if applicable); for strings,
+ * consider a validation routine.
* (ii) add a record below (or use add_<type>_reloption).
* (iii) add it to the appropriate options struct (perhaps StdRdOptions)
* (iv) add it to the appropriate handling routine (perhaps
@@ -147,15 +147,6 @@ static relopt_bool boolRelOpts[] =
},
false
},
- {
- {
- "vacuum_truncate",
- "Enables vacuum to truncate empty pages at the end of this table",
- RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
- ShareUpdateExclusiveLock
- },
- true
- },
{
{
"deduplicate_items",
@@ -170,6 +161,21 @@ static relopt_bool boolRelOpts[] =
{{NULL}}
};
+static relopt_ternary ternaryRelOpts[] =
+{
+ {
+ {
+ "vacuum_truncate",
+ "Enables vacuum to truncate empty pages at the end of this table",
+ RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
+ ShareUpdateExclusiveLock
+ },
+ TERNARY_UNSET
+ },
+ /* list terminator */
+ {{NULL}}
+};
+
static relopt_int intRelOpts[] =
{
{
@@ -600,6 +606,13 @@ initialize_reloptions(void)
boolRelOpts[i].gen.lockmode));
j++;
}
+ for (i = 0; ternaryRelOpts[i].gen.name; i++)
+ {
+ Assert(DoLockModesConflict(ternaryRelOpts[i].gen.lockmode,
+ ternaryRelOpts[i].gen.lockmode));
+ j++;
+ }
+
for (i = 0; intRelOpts[i].gen.name; i++)
{
Assert(DoLockModesConflict(intRelOpts[i].gen.lockmode,
@@ -640,6 +653,14 @@ initialize_reloptions(void)
j++;
}
+ for (i = 0; ternaryRelOpts[i].gen.name; i++)
+ {
+ relOpts[j] = &ternaryRelOpts[i].gen;
+ relOpts[j]->type = RELOPT_TYPE_TERNARY;
+ relOpts[j]->namelen = strlen(relOpts[j]->name);
+ j++;
+ }
+
for (i = 0; intRelOpts[i].gen.name; i++)
{
relOpts[j] = &intRelOpts[i].gen;
@@ -800,6 +821,9 @@ allocate_reloption(bits32 kinds, int type, const char *name, const char *desc,
case RELOPT_TYPE_BOOL:
size = sizeof(relopt_bool);
break;
+ case RELOPT_TYPE_TERNARY:
+ size = sizeof(relopt_ternary);
+ break;
case RELOPT_TYPE_INT:
size = sizeof(relopt_int);
break;
@@ -883,6 +907,54 @@ add_local_bool_reloption(local_relopts *relopts, const char *name,
add_local_reloption(relopts, (relopt_gen *) newoption, offset);
}
+/*
+ * init_ternary_reloption
+ * Allocate and initialize a new ternary reloption
+ */
+static relopt_ternary *
+init_ternary_reloption(bits32 kinds, const char *name, const char *desc,
+ ternary default_val, LOCKMODE lockmode)
+{
+ relopt_ternary *newoption;
+
+ newoption = (relopt_ternary *) allocate_reloption(kinds,
+ RELOPT_TYPE_TERNARY, name, desc, lockmode);
+ newoption->default_val = default_val;
+
+ return newoption;
+}
+
+/*
+ * add_ternary_reloption
+ * Add a new ternary reloption
+ */
+void
+add_ternary_reloption(bits32 kinds, const char *name, const char *desc,
+ ternary default_val, LOCKMODE lockmode)
+{
+ relopt_ternary *newoption = init_ternary_reloption(kinds, name, desc,
+ default_val, lockmode);
+
+ add_reloption((relopt_gen *) newoption);
+}
+
+/*
+ * add_local_ternary_reloption
+ * Add a new ternary local reloption
+ *
+ * 'offset' is offset of ternary-typed field.
+ */
+void
+add_local_ternary_reloption(local_relopts *relopts, const char *name,
+ const char *desc, ternary default_val,
+ int offset)
+{
+ relopt_ternary *newoption = init_ternary_reloption(RELOPT_KIND_LOCAL,
+ name, desc,
+ default_val, 0);
+
+ add_local_reloption(relopts, (relopt_gen *) newoption, offset);
+}
/*
* init_real_reloption
@@ -1617,6 +1689,18 @@ parse_one_reloption(relopt_value *option, char *text_str, int text_len,
option->gen->name, value)));
}
break;
+ case RELOPT_TYPE_TERNARY:
+ {
+ bool b;
+ parsed = parse_bool(value, &b);
+ option->values.ternary_val = b ? TERNARY_TRUE : TERNARY_FALSE;
+ if (validate && !parsed)
+ ereport(ERROR,
+ (errcode(ERRCODE_INVALID_PARAMETER_VALUE),
+ errmsg("invalid value for ternary option \"%s\": %s",
+ option->gen->name, value)));
+ }
+ break;
case RELOPT_TYPE_INT:
{
relopt_int *optint = (relopt_int *) option->gen;
@@ -1780,17 +1864,6 @@ fillRelOptions(void *rdopts, Size basesize,
char *itempos = ((char *) rdopts) + elems[j].offset;
char *string_val;
- /*
- * If isset_offset is provided, store whether the reloption is
- * set there.
- */
- if (elems[j].isset_offset > 0)
- {
- char *setpos = ((char *) rdopts) + elems[j].isset_offset;
-
- *(bool *) setpos = options[i].isset;
- }
-
switch (options[i].gen->type)
{
case RELOPT_TYPE_BOOL:
@@ -1798,6 +1871,11 @@ fillRelOptions(void *rdopts, Size basesize,
options[i].values.bool_val :
((relopt_bool *) options[i].gen)->default_val;
break;
+ case RELOPT_TYPE_TERNARY:
+ *(ternary *) itempos = options[i].isset ?
+ options[i].values.ternary_val :
+ ((relopt_ternary *) options[i].gen)->default_val;
+ break;
case RELOPT_TYPE_INT:
*(int *) itempos = options[i].isset ?
options[i].values.int_val :
@@ -1912,8 +1990,8 @@ default_reloptions(Datum reloptions, bool validate, relopt_kind kind)
offsetof(StdRdOptions, parallel_workers)},
{"vacuum_index_cleanup", RELOPT_TYPE_ENUM,
offsetof(StdRdOptions, vacuum_index_cleanup)},
- {"vacuum_truncate", RELOPT_TYPE_BOOL,
- offsetof(StdRdOptions, vacuum_truncate), offsetof(StdRdOptions, vacuum_truncate_set)},
+ {"vacuum_truncate", RELOPT_TYPE_TERNARY,
+ offsetof(StdRdOptions, vacuum_truncate)},
{"vacuum_max_eager_freeze_failure_rate", RELOPT_TYPE_REAL,
offsetof(StdRdOptions, vacuum_max_eager_freeze_failure_rate)}
};
@@ -1993,7 +2071,6 @@ build_local_reloptions(local_relopts *relopts, Datum options, bool validate)
elems[i].optname = opt->option->name;
elems[i].opttype = opt->option->type;
elems[i].offset = opt->offset;
- elems[i].isset_offset = 0; /* not supported for local relopts yet */
i++;
}
diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c
index 733ef40ae7..977babff54 100644
--- a/src/backend/commands/vacuum.c
+++ b/src/backend/commands/vacuum.c
@@ -2221,9 +2221,9 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams params,
{
StdRdOptions *opts = (StdRdOptions *) rel->rd_options;
- if (opts && opts->vacuum_truncate_set)
+ if (opts && opts->vacuum_truncate != TERNARY_UNSET)
{
- if (opts->vacuum_truncate)
+ if (opts->vacuum_truncate == TERNARY_TRUE)
params.truncate = VACOPTVALUE_ENABLED;
else
params.truncate = VACOPTVALUE_DISABLED;
diff --git a/src/include/access/reloptions.h b/src/include/access/reloptions.h
index a604a4702c..a436697658 100644
--- a/src/include/access/reloptions.h
+++ b/src/include/access/reloptions.h
@@ -29,6 +29,7 @@
typedef enum relopt_type
{
RELOPT_TYPE_BOOL,
+ RELOPT_TYPE_TERNARY, /* on, off, unset */
RELOPT_TYPE_INT,
RELOPT_TYPE_REAL,
RELOPT_TYPE_ENUM,
@@ -80,6 +81,7 @@ typedef struct relopt_value
union
{
bool bool_val;
+ ternary ternary_val;
int int_val;
double real_val;
int enum_val;
@@ -94,6 +96,12 @@ typedef struct relopt_bool
bool default_val;
} relopt_bool;
+typedef struct relopt_rernary
+{
+ relopt_gen gen;
+ int default_val;
+} relopt_ternary;
+
typedef struct relopt_int
{
relopt_gen gen;
@@ -152,19 +160,6 @@ typedef struct
const char *optname; /* option's name */
relopt_type opttype; /* option's datatype */
int offset; /* offset of field in result struct */
-
- /*
- * isset_offset is an optional offset of a field in the result struct that
- * stores whether the option is explicitly set for the relation or if it
- * just picked up the default value. In most cases, this can be
- * accomplished by giving the reloption a special out-of-range default
- * value (e.g., some integer reloptions use -2), but this isn't always
- * possible. For example, a Boolean reloption cannot be given an
- * out-of-range default, so we need another way to discover the source of
- * its value. This offset is only used if given a value greater than
- * zero.
- */
- int isset_offset;
} relopt_parse_elt;
/* Local reloption definition */
@@ -195,6 +190,8 @@ typedef struct local_relopts
extern relopt_kind add_reloption_kind(void);
extern void add_bool_reloption(bits32 kinds, const char *name, const char *desc,
bool default_val, LOCKMODE lockmode);
+extern void add_ternary_reloption(bits32 kinds, const char *name,
+ const char *desc, int default_val, LOCKMODE lockmode);
extern void add_int_reloption(bits32 kinds, const char *name, const char *desc,
int default_val, int min_val, int max_val,
LOCKMODE lockmode);
@@ -214,6 +211,9 @@ extern void register_reloptions_validator(local_relopts *relopts,
extern void add_local_bool_reloption(local_relopts *relopts, const char *name,
const char *desc, bool default_val,
int offset);
+extern void add_local_ternary_reloption(local_relopts *relopts,
+ const char *name, const char *desc,
+ ternary default_val, int offset);
extern void add_local_int_reloption(local_relopts *relopts, const char *name,
const char *desc, int default_val,
int min_val, int max_val, int offset);
diff --git a/src/include/c.h b/src/include/c.h
index 39022f8a9d..9c59ccccf8 100644
--- a/src/include/c.h
+++ b/src/include/c.h
@@ -488,6 +488,22 @@ typedef void (*pg_funcptr_t) (void);
#include <stdbool.h>
+/*
+ * ternary
+ * Boolean value with an extrea "unset" option
+ *
+ * Ternary data type is used in relation options that can be "true", "false" or
+ * "unset". Since relation options are used deep inside the PostgreSQL code,
+ * this type is declared globally.
+*/
+
+typedef enum ternary
+{
+ TERNARY_FALSE = 0,
+ TERNARY_TRUE = 1,
+ TERNARY_UNSET = -1
+} ternary;
+
/* ----------------------------------------------------------------
* Section 3: standard system types
diff --git a/src/include/utils/rel.h b/src/include/utils/rel.h
index b552359915..7346d618f9 100644
--- a/src/include/utils/rel.h
+++ b/src/include/utils/rel.h
@@ -346,8 +346,7 @@ typedef struct StdRdOptions
bool user_catalog_table; /* use as an additional catalog relation */
int parallel_workers; /* max number of parallel workers */
StdRdOptIndexCleanup vacuum_index_cleanup; /* controls index vacuuming */
- bool vacuum_truncate; /* enables vacuum to truncate a relation */
- bool vacuum_truncate_set; /* whether vacuum_truncate is set */
+ ternary vacuum_truncate; /* enables vacuum to truncate a relation */
/*
* Fraction of pages in a relation that vacuum can eagerly scan and fail
--
2.39.2
--nextPart5129932.5fSG56mABF
Content-Disposition: attachment;
filename="v2-0003-Add-alias-to-be-used-as-unset-state.patch"
Content-Transfer-Encoding: 7Bit
Content-Type: text/x-patch; charset="unicode-2-0-utf-8";
name="v2-0003-Add-alias-to-be-used-as-unset-state.patch"
^ permalink raw reply [nested|flat] 868+ messages in thread
* [PATCH v2 2/4] Introduce ternary reloptions
@ 2025-09-04 14:41 Nikolay Shaplov <[email protected]>
0 siblings, 0 replies; 868+ messages in thread
From: Nikolay Shaplov @ 2025-09-04 14:41 UTC (permalink / raw)
Introduce ternary reloption as a replacement for current `vacuum_truncate`
implementation. Remove `vacuum_truncate_set` additional flag and using
`TERNARY_UNSET` value instead.
---
src/backend/access/common/reloptions.c | 129 ++++++++++++++++++++-----
src/backend/commands/vacuum.c | 4 +-
src/include/access/reloptions.h | 26 ++---
src/include/c.h | 16 +++
src/include/utils/rel.h | 3 +-
5 files changed, 135 insertions(+), 43 deletions(-)
diff --git a/src/backend/access/common/reloptions.c b/src/backend/access/common/reloptions.c
index 0af3fea68f..f543f11001 100644
--- a/src/backend/access/common/reloptions.c
+++ b/src/backend/access/common/reloptions.c
@@ -40,9 +40,9 @@
*
* To add an option:
*
- * (i) decide on a type (bool, integer, real, enum, string), name, default
- * value, upper and lower bounds (if applicable); for strings, consider a
- * validation routine.
+ * (i) decide on a type (bool, ternary, integer, real, enum, string), name,
+ * default value, upper and lower bounds (if applicable); for strings,
+ * consider a validation routine.
* (ii) add a record below (or use add_<type>_reloption).
* (iii) add it to the appropriate options struct (perhaps StdRdOptions)
* (iv) add it to the appropriate handling routine (perhaps
@@ -147,15 +147,6 @@ static relopt_bool boolRelOpts[] =
},
false
},
- {
- {
- "vacuum_truncate",
- "Enables vacuum to truncate empty pages at the end of this table",
- RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
- ShareUpdateExclusiveLock
- },
- true
- },
{
{
"deduplicate_items",
@@ -170,6 +161,21 @@ static relopt_bool boolRelOpts[] =
{{NULL}}
};
+static relopt_ternary ternaryRelOpts[] =
+{
+ {
+ {
+ "vacuum_truncate",
+ "Enables vacuum to truncate empty pages at the end of this table",
+ RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
+ ShareUpdateExclusiveLock
+ },
+ TERNARY_UNSET
+ },
+ /* list terminator */
+ {{NULL}}
+};
+
static relopt_int intRelOpts[] =
{
{
@@ -600,6 +606,13 @@ initialize_reloptions(void)
boolRelOpts[i].gen.lockmode));
j++;
}
+ for (i = 0; ternaryRelOpts[i].gen.name; i++)
+ {
+ Assert(DoLockModesConflict(ternaryRelOpts[i].gen.lockmode,
+ ternaryRelOpts[i].gen.lockmode));
+ j++;
+ }
+
for (i = 0; intRelOpts[i].gen.name; i++)
{
Assert(DoLockModesConflict(intRelOpts[i].gen.lockmode,
@@ -640,6 +653,14 @@ initialize_reloptions(void)
j++;
}
+ for (i = 0; ternaryRelOpts[i].gen.name; i++)
+ {
+ relOpts[j] = &ternaryRelOpts[i].gen;
+ relOpts[j]->type = RELOPT_TYPE_TERNARY;
+ relOpts[j]->namelen = strlen(relOpts[j]->name);
+ j++;
+ }
+
for (i = 0; intRelOpts[i].gen.name; i++)
{
relOpts[j] = &intRelOpts[i].gen;
@@ -800,6 +821,9 @@ allocate_reloption(bits32 kinds, int type, const char *name, const char *desc,
case RELOPT_TYPE_BOOL:
size = sizeof(relopt_bool);
break;
+ case RELOPT_TYPE_TERNARY:
+ size = sizeof(relopt_ternary);
+ break;
case RELOPT_TYPE_INT:
size = sizeof(relopt_int);
break;
@@ -883,6 +907,54 @@ add_local_bool_reloption(local_relopts *relopts, const char *name,
add_local_reloption(relopts, (relopt_gen *) newoption, offset);
}
+/*
+ * init_ternary_reloption
+ * Allocate and initialize a new ternary reloption
+ */
+static relopt_ternary *
+init_ternary_reloption(bits32 kinds, const char *name, const char *desc,
+ ternary default_val, LOCKMODE lockmode)
+{
+ relopt_ternary *newoption;
+
+ newoption = (relopt_ternary *) allocate_reloption(kinds,
+ RELOPT_TYPE_TERNARY, name, desc, lockmode);
+ newoption->default_val = default_val;
+
+ return newoption;
+}
+
+/*
+ * add_ternary_reloption
+ * Add a new ternary reloption
+ */
+void
+add_ternary_reloption(bits32 kinds, const char *name, const char *desc,
+ ternary default_val, LOCKMODE lockmode)
+{
+ relopt_ternary *newoption = init_ternary_reloption(kinds, name, desc,
+ default_val, lockmode);
+
+ add_reloption((relopt_gen *) newoption);
+}
+
+/*
+ * add_local_ternary_reloption
+ * Add a new ternary local reloption
+ *
+ * 'offset' is offset of ternary-typed field.
+ */
+void
+add_local_ternary_reloption(local_relopts *relopts, const char *name,
+ const char *desc, ternary default_val,
+ int offset)
+{
+ relopt_ternary *newoption = init_ternary_reloption(RELOPT_KIND_LOCAL,
+ name, desc,
+ default_val, 0);
+
+ add_local_reloption(relopts, (relopt_gen *) newoption, offset);
+}
/*
* init_real_reloption
@@ -1617,6 +1689,18 @@ parse_one_reloption(relopt_value *option, char *text_str, int text_len,
option->gen->name, value)));
}
break;
+ case RELOPT_TYPE_TERNARY:
+ {
+ bool b;
+ parsed = parse_bool(value, &b);
+ option->values.ternary_val = b ? TERNARY_TRUE : TERNARY_FALSE;
+ if (validate && !parsed)
+ ereport(ERROR,
+ (errcode(ERRCODE_INVALID_PARAMETER_VALUE),
+ errmsg("invalid value for ternary option \"%s\": %s",
+ option->gen->name, value)));
+ }
+ break;
case RELOPT_TYPE_INT:
{
relopt_int *optint = (relopt_int *) option->gen;
@@ -1780,17 +1864,6 @@ fillRelOptions(void *rdopts, Size basesize,
char *itempos = ((char *) rdopts) + elems[j].offset;
char *string_val;
- /*
- * If isset_offset is provided, store whether the reloption is
- * set there.
- */
- if (elems[j].isset_offset > 0)
- {
- char *setpos = ((char *) rdopts) + elems[j].isset_offset;
-
- *(bool *) setpos = options[i].isset;
- }
-
switch (options[i].gen->type)
{
case RELOPT_TYPE_BOOL:
@@ -1798,6 +1871,11 @@ fillRelOptions(void *rdopts, Size basesize,
options[i].values.bool_val :
((relopt_bool *) options[i].gen)->default_val;
break;
+ case RELOPT_TYPE_TERNARY:
+ *(ternary *) itempos = options[i].isset ?
+ options[i].values.ternary_val :
+ ((relopt_ternary *) options[i].gen)->default_val;
+ break;
case RELOPT_TYPE_INT:
*(int *) itempos = options[i].isset ?
options[i].values.int_val :
@@ -1912,8 +1990,8 @@ default_reloptions(Datum reloptions, bool validate, relopt_kind kind)
offsetof(StdRdOptions, parallel_workers)},
{"vacuum_index_cleanup", RELOPT_TYPE_ENUM,
offsetof(StdRdOptions, vacuum_index_cleanup)},
- {"vacuum_truncate", RELOPT_TYPE_BOOL,
- offsetof(StdRdOptions, vacuum_truncate), offsetof(StdRdOptions, vacuum_truncate_set)},
+ {"vacuum_truncate", RELOPT_TYPE_TERNARY,
+ offsetof(StdRdOptions, vacuum_truncate)},
{"vacuum_max_eager_freeze_failure_rate", RELOPT_TYPE_REAL,
offsetof(StdRdOptions, vacuum_max_eager_freeze_failure_rate)}
};
@@ -1993,7 +2071,6 @@ build_local_reloptions(local_relopts *relopts, Datum options, bool validate)
elems[i].optname = opt->option->name;
elems[i].opttype = opt->option->type;
elems[i].offset = opt->offset;
- elems[i].isset_offset = 0; /* not supported for local relopts yet */
i++;
}
diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c
index 733ef40ae7..977babff54 100644
--- a/src/backend/commands/vacuum.c
+++ b/src/backend/commands/vacuum.c
@@ -2221,9 +2221,9 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams params,
{
StdRdOptions *opts = (StdRdOptions *) rel->rd_options;
- if (opts && opts->vacuum_truncate_set)
+ if (opts && opts->vacuum_truncate != TERNARY_UNSET)
{
- if (opts->vacuum_truncate)
+ if (opts->vacuum_truncate == TERNARY_TRUE)
params.truncate = VACOPTVALUE_ENABLED;
else
params.truncate = VACOPTVALUE_DISABLED;
diff --git a/src/include/access/reloptions.h b/src/include/access/reloptions.h
index a604a4702c..a436697658 100644
--- a/src/include/access/reloptions.h
+++ b/src/include/access/reloptions.h
@@ -29,6 +29,7 @@
typedef enum relopt_type
{
RELOPT_TYPE_BOOL,
+ RELOPT_TYPE_TERNARY, /* on, off, unset */
RELOPT_TYPE_INT,
RELOPT_TYPE_REAL,
RELOPT_TYPE_ENUM,
@@ -80,6 +81,7 @@ typedef struct relopt_value
union
{
bool bool_val;
+ ternary ternary_val;
int int_val;
double real_val;
int enum_val;
@@ -94,6 +96,12 @@ typedef struct relopt_bool
bool default_val;
} relopt_bool;
+typedef struct relopt_rernary
+{
+ relopt_gen gen;
+ int default_val;
+} relopt_ternary;
+
typedef struct relopt_int
{
relopt_gen gen;
@@ -152,19 +160,6 @@ typedef struct
const char *optname; /* option's name */
relopt_type opttype; /* option's datatype */
int offset; /* offset of field in result struct */
-
- /*
- * isset_offset is an optional offset of a field in the result struct that
- * stores whether the option is explicitly set for the relation or if it
- * just picked up the default value. In most cases, this can be
- * accomplished by giving the reloption a special out-of-range default
- * value (e.g., some integer reloptions use -2), but this isn't always
- * possible. For example, a Boolean reloption cannot be given an
- * out-of-range default, so we need another way to discover the source of
- * its value. This offset is only used if given a value greater than
- * zero.
- */
- int isset_offset;
} relopt_parse_elt;
/* Local reloption definition */
@@ -195,6 +190,8 @@ typedef struct local_relopts
extern relopt_kind add_reloption_kind(void);
extern void add_bool_reloption(bits32 kinds, const char *name, const char *desc,
bool default_val, LOCKMODE lockmode);
+extern void add_ternary_reloption(bits32 kinds, const char *name,
+ const char *desc, int default_val, LOCKMODE lockmode);
extern void add_int_reloption(bits32 kinds, const char *name, const char *desc,
int default_val, int min_val, int max_val,
LOCKMODE lockmode);
@@ -214,6 +211,9 @@ extern void register_reloptions_validator(local_relopts *relopts,
extern void add_local_bool_reloption(local_relopts *relopts, const char *name,
const char *desc, bool default_val,
int offset);
+extern void add_local_ternary_reloption(local_relopts *relopts,
+ const char *name, const char *desc,
+ ternary default_val, int offset);
extern void add_local_int_reloption(local_relopts *relopts, const char *name,
const char *desc, int default_val,
int min_val, int max_val, int offset);
diff --git a/src/include/c.h b/src/include/c.h
index 39022f8a9d..9c59ccccf8 100644
--- a/src/include/c.h
+++ b/src/include/c.h
@@ -488,6 +488,22 @@ typedef void (*pg_funcptr_t) (void);
#include <stdbool.h>
+/*
+ * ternary
+ * Boolean value with an extrea "unset" option
+ *
+ * Ternary data type is used in relation options that can be "true", "false" or
+ * "unset". Since relation options are used deep inside the PostgreSQL code,
+ * this type is declared globally.
+*/
+
+typedef enum ternary
+{
+ TERNARY_FALSE = 0,
+ TERNARY_TRUE = 1,
+ TERNARY_UNSET = -1
+} ternary;
+
/* ----------------------------------------------------------------
* Section 3: standard system types
diff --git a/src/include/utils/rel.h b/src/include/utils/rel.h
index b552359915..7346d618f9 100644
--- a/src/include/utils/rel.h
+++ b/src/include/utils/rel.h
@@ -346,8 +346,7 @@ typedef struct StdRdOptions
bool user_catalog_table; /* use as an additional catalog relation */
int parallel_workers; /* max number of parallel workers */
StdRdOptIndexCleanup vacuum_index_cleanup; /* controls index vacuuming */
- bool vacuum_truncate; /* enables vacuum to truncate a relation */
- bool vacuum_truncate_set; /* whether vacuum_truncate is set */
+ ternary vacuum_truncate; /* enables vacuum to truncate a relation */
/*
* Fraction of pages in a relation that vacuum can eagerly scan and fail
--
2.39.2
--nextPart5129932.5fSG56mABF
Content-Disposition: attachment;
filename="v2-0003-Add-alias-to-be-used-as-unset-state.patch"
Content-Transfer-Encoding: 7Bit
Content-Type: text/x-patch; charset="unicode-2-0-utf-8";
name="v2-0003-Add-alias-to-be-used-as-unset-state.patch"
^ permalink raw reply [nested|flat] 868+ messages in thread
* [PATCH v2 2/4] Introduce ternary reloptions
@ 2025-09-04 14:41 Nikolay Shaplov <[email protected]>
0 siblings, 0 replies; 868+ messages in thread
From: Nikolay Shaplov @ 2025-09-04 14:41 UTC (permalink / raw)
Introduce ternary reloption as a replacement for current `vacuum_truncate`
implementation. Remove `vacuum_truncate_set` additional flag and using
`TERNARY_UNSET` value instead.
---
src/backend/access/common/reloptions.c | 129 ++++++++++++++++++++-----
src/backend/commands/vacuum.c | 4 +-
src/include/access/reloptions.h | 26 ++---
src/include/c.h | 16 +++
src/include/utils/rel.h | 3 +-
5 files changed, 135 insertions(+), 43 deletions(-)
diff --git a/src/backend/access/common/reloptions.c b/src/backend/access/common/reloptions.c
index 0af3fea68f..f543f11001 100644
--- a/src/backend/access/common/reloptions.c
+++ b/src/backend/access/common/reloptions.c
@@ -40,9 +40,9 @@
*
* To add an option:
*
- * (i) decide on a type (bool, integer, real, enum, string), name, default
- * value, upper and lower bounds (if applicable); for strings, consider a
- * validation routine.
+ * (i) decide on a type (bool, ternary, integer, real, enum, string), name,
+ * default value, upper and lower bounds (if applicable); for strings,
+ * consider a validation routine.
* (ii) add a record below (or use add_<type>_reloption).
* (iii) add it to the appropriate options struct (perhaps StdRdOptions)
* (iv) add it to the appropriate handling routine (perhaps
@@ -147,15 +147,6 @@ static relopt_bool boolRelOpts[] =
},
false
},
- {
- {
- "vacuum_truncate",
- "Enables vacuum to truncate empty pages at the end of this table",
- RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
- ShareUpdateExclusiveLock
- },
- true
- },
{
{
"deduplicate_items",
@@ -170,6 +161,21 @@ static relopt_bool boolRelOpts[] =
{{NULL}}
};
+static relopt_ternary ternaryRelOpts[] =
+{
+ {
+ {
+ "vacuum_truncate",
+ "Enables vacuum to truncate empty pages at the end of this table",
+ RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
+ ShareUpdateExclusiveLock
+ },
+ TERNARY_UNSET
+ },
+ /* list terminator */
+ {{NULL}}
+};
+
static relopt_int intRelOpts[] =
{
{
@@ -600,6 +606,13 @@ initialize_reloptions(void)
boolRelOpts[i].gen.lockmode));
j++;
}
+ for (i = 0; ternaryRelOpts[i].gen.name; i++)
+ {
+ Assert(DoLockModesConflict(ternaryRelOpts[i].gen.lockmode,
+ ternaryRelOpts[i].gen.lockmode));
+ j++;
+ }
+
for (i = 0; intRelOpts[i].gen.name; i++)
{
Assert(DoLockModesConflict(intRelOpts[i].gen.lockmode,
@@ -640,6 +653,14 @@ initialize_reloptions(void)
j++;
}
+ for (i = 0; ternaryRelOpts[i].gen.name; i++)
+ {
+ relOpts[j] = &ternaryRelOpts[i].gen;
+ relOpts[j]->type = RELOPT_TYPE_TERNARY;
+ relOpts[j]->namelen = strlen(relOpts[j]->name);
+ j++;
+ }
+
for (i = 0; intRelOpts[i].gen.name; i++)
{
relOpts[j] = &intRelOpts[i].gen;
@@ -800,6 +821,9 @@ allocate_reloption(bits32 kinds, int type, const char *name, const char *desc,
case RELOPT_TYPE_BOOL:
size = sizeof(relopt_bool);
break;
+ case RELOPT_TYPE_TERNARY:
+ size = sizeof(relopt_ternary);
+ break;
case RELOPT_TYPE_INT:
size = sizeof(relopt_int);
break;
@@ -883,6 +907,54 @@ add_local_bool_reloption(local_relopts *relopts, const char *name,
add_local_reloption(relopts, (relopt_gen *) newoption, offset);
}
+/*
+ * init_ternary_reloption
+ * Allocate and initialize a new ternary reloption
+ */
+static relopt_ternary *
+init_ternary_reloption(bits32 kinds, const char *name, const char *desc,
+ ternary default_val, LOCKMODE lockmode)
+{
+ relopt_ternary *newoption;
+
+ newoption = (relopt_ternary *) allocate_reloption(kinds,
+ RELOPT_TYPE_TERNARY, name, desc, lockmode);
+ newoption->default_val = default_val;
+
+ return newoption;
+}
+
+/*
+ * add_ternary_reloption
+ * Add a new ternary reloption
+ */
+void
+add_ternary_reloption(bits32 kinds, const char *name, const char *desc,
+ ternary default_val, LOCKMODE lockmode)
+{
+ relopt_ternary *newoption = init_ternary_reloption(kinds, name, desc,
+ default_val, lockmode);
+
+ add_reloption((relopt_gen *) newoption);
+}
+
+/*
+ * add_local_ternary_reloption
+ * Add a new ternary local reloption
+ *
+ * 'offset' is offset of ternary-typed field.
+ */
+void
+add_local_ternary_reloption(local_relopts *relopts, const char *name,
+ const char *desc, ternary default_val,
+ int offset)
+{
+ relopt_ternary *newoption = init_ternary_reloption(RELOPT_KIND_LOCAL,
+ name, desc,
+ default_val, 0);
+
+ add_local_reloption(relopts, (relopt_gen *) newoption, offset);
+}
/*
* init_real_reloption
@@ -1617,6 +1689,18 @@ parse_one_reloption(relopt_value *option, char *text_str, int text_len,
option->gen->name, value)));
}
break;
+ case RELOPT_TYPE_TERNARY:
+ {
+ bool b;
+ parsed = parse_bool(value, &b);
+ option->values.ternary_val = b ? TERNARY_TRUE : TERNARY_FALSE;
+ if (validate && !parsed)
+ ereport(ERROR,
+ (errcode(ERRCODE_INVALID_PARAMETER_VALUE),
+ errmsg("invalid value for ternary option \"%s\": %s",
+ option->gen->name, value)));
+ }
+ break;
case RELOPT_TYPE_INT:
{
relopt_int *optint = (relopt_int *) option->gen;
@@ -1780,17 +1864,6 @@ fillRelOptions(void *rdopts, Size basesize,
char *itempos = ((char *) rdopts) + elems[j].offset;
char *string_val;
- /*
- * If isset_offset is provided, store whether the reloption is
- * set there.
- */
- if (elems[j].isset_offset > 0)
- {
- char *setpos = ((char *) rdopts) + elems[j].isset_offset;
-
- *(bool *) setpos = options[i].isset;
- }
-
switch (options[i].gen->type)
{
case RELOPT_TYPE_BOOL:
@@ -1798,6 +1871,11 @@ fillRelOptions(void *rdopts, Size basesize,
options[i].values.bool_val :
((relopt_bool *) options[i].gen)->default_val;
break;
+ case RELOPT_TYPE_TERNARY:
+ *(ternary *) itempos = options[i].isset ?
+ options[i].values.ternary_val :
+ ((relopt_ternary *) options[i].gen)->default_val;
+ break;
case RELOPT_TYPE_INT:
*(int *) itempos = options[i].isset ?
options[i].values.int_val :
@@ -1912,8 +1990,8 @@ default_reloptions(Datum reloptions, bool validate, relopt_kind kind)
offsetof(StdRdOptions, parallel_workers)},
{"vacuum_index_cleanup", RELOPT_TYPE_ENUM,
offsetof(StdRdOptions, vacuum_index_cleanup)},
- {"vacuum_truncate", RELOPT_TYPE_BOOL,
- offsetof(StdRdOptions, vacuum_truncate), offsetof(StdRdOptions, vacuum_truncate_set)},
+ {"vacuum_truncate", RELOPT_TYPE_TERNARY,
+ offsetof(StdRdOptions, vacuum_truncate)},
{"vacuum_max_eager_freeze_failure_rate", RELOPT_TYPE_REAL,
offsetof(StdRdOptions, vacuum_max_eager_freeze_failure_rate)}
};
@@ -1993,7 +2071,6 @@ build_local_reloptions(local_relopts *relopts, Datum options, bool validate)
elems[i].optname = opt->option->name;
elems[i].opttype = opt->option->type;
elems[i].offset = opt->offset;
- elems[i].isset_offset = 0; /* not supported for local relopts yet */
i++;
}
diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c
index 733ef40ae7..977babff54 100644
--- a/src/backend/commands/vacuum.c
+++ b/src/backend/commands/vacuum.c
@@ -2221,9 +2221,9 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams params,
{
StdRdOptions *opts = (StdRdOptions *) rel->rd_options;
- if (opts && opts->vacuum_truncate_set)
+ if (opts && opts->vacuum_truncate != TERNARY_UNSET)
{
- if (opts->vacuum_truncate)
+ if (opts->vacuum_truncate == TERNARY_TRUE)
params.truncate = VACOPTVALUE_ENABLED;
else
params.truncate = VACOPTVALUE_DISABLED;
diff --git a/src/include/access/reloptions.h b/src/include/access/reloptions.h
index a604a4702c..a436697658 100644
--- a/src/include/access/reloptions.h
+++ b/src/include/access/reloptions.h
@@ -29,6 +29,7 @@
typedef enum relopt_type
{
RELOPT_TYPE_BOOL,
+ RELOPT_TYPE_TERNARY, /* on, off, unset */
RELOPT_TYPE_INT,
RELOPT_TYPE_REAL,
RELOPT_TYPE_ENUM,
@@ -80,6 +81,7 @@ typedef struct relopt_value
union
{
bool bool_val;
+ ternary ternary_val;
int int_val;
double real_val;
int enum_val;
@@ -94,6 +96,12 @@ typedef struct relopt_bool
bool default_val;
} relopt_bool;
+typedef struct relopt_rernary
+{
+ relopt_gen gen;
+ int default_val;
+} relopt_ternary;
+
typedef struct relopt_int
{
relopt_gen gen;
@@ -152,19 +160,6 @@ typedef struct
const char *optname; /* option's name */
relopt_type opttype; /* option's datatype */
int offset; /* offset of field in result struct */
-
- /*
- * isset_offset is an optional offset of a field in the result struct that
- * stores whether the option is explicitly set for the relation or if it
- * just picked up the default value. In most cases, this can be
- * accomplished by giving the reloption a special out-of-range default
- * value (e.g., some integer reloptions use -2), but this isn't always
- * possible. For example, a Boolean reloption cannot be given an
- * out-of-range default, so we need another way to discover the source of
- * its value. This offset is only used if given a value greater than
- * zero.
- */
- int isset_offset;
} relopt_parse_elt;
/* Local reloption definition */
@@ -195,6 +190,8 @@ typedef struct local_relopts
extern relopt_kind add_reloption_kind(void);
extern void add_bool_reloption(bits32 kinds, const char *name, const char *desc,
bool default_val, LOCKMODE lockmode);
+extern void add_ternary_reloption(bits32 kinds, const char *name,
+ const char *desc, int default_val, LOCKMODE lockmode);
extern void add_int_reloption(bits32 kinds, const char *name, const char *desc,
int default_val, int min_val, int max_val,
LOCKMODE lockmode);
@@ -214,6 +211,9 @@ extern void register_reloptions_validator(local_relopts *relopts,
extern void add_local_bool_reloption(local_relopts *relopts, const char *name,
const char *desc, bool default_val,
int offset);
+extern void add_local_ternary_reloption(local_relopts *relopts,
+ const char *name, const char *desc,
+ ternary default_val, int offset);
extern void add_local_int_reloption(local_relopts *relopts, const char *name,
const char *desc, int default_val,
int min_val, int max_val, int offset);
diff --git a/src/include/c.h b/src/include/c.h
index 39022f8a9d..9c59ccccf8 100644
--- a/src/include/c.h
+++ b/src/include/c.h
@@ -488,6 +488,22 @@ typedef void (*pg_funcptr_t) (void);
#include <stdbool.h>
+/*
+ * ternary
+ * Boolean value with an extrea "unset" option
+ *
+ * Ternary data type is used in relation options that can be "true", "false" or
+ * "unset". Since relation options are used deep inside the PostgreSQL code,
+ * this type is declared globally.
+*/
+
+typedef enum ternary
+{
+ TERNARY_FALSE = 0,
+ TERNARY_TRUE = 1,
+ TERNARY_UNSET = -1
+} ternary;
+
/* ----------------------------------------------------------------
* Section 3: standard system types
diff --git a/src/include/utils/rel.h b/src/include/utils/rel.h
index b552359915..7346d618f9 100644
--- a/src/include/utils/rel.h
+++ b/src/include/utils/rel.h
@@ -346,8 +346,7 @@ typedef struct StdRdOptions
bool user_catalog_table; /* use as an additional catalog relation */
int parallel_workers; /* max number of parallel workers */
StdRdOptIndexCleanup vacuum_index_cleanup; /* controls index vacuuming */
- bool vacuum_truncate; /* enables vacuum to truncate a relation */
- bool vacuum_truncate_set; /* whether vacuum_truncate is set */
+ ternary vacuum_truncate; /* enables vacuum to truncate a relation */
/*
* Fraction of pages in a relation that vacuum can eagerly scan and fail
--
2.39.2
--nextPart5129932.5fSG56mABF
Content-Disposition: attachment;
filename="v2-0003-Add-alias-to-be-used-as-unset-state.patch"
Content-Transfer-Encoding: 7Bit
Content-Type: text/x-patch; charset="unicode-2-0-utf-8";
name="v2-0003-Add-alias-to-be-used-as-unset-state.patch"
^ permalink raw reply [nested|flat] 868+ messages in thread
* [PATCH v2a 2/4] Introduce ternary reloptions
@ 2025-09-04 14:41 Nikolay Shaplov <[email protected]>
0 siblings, 0 replies; 868+ messages in thread
From: Nikolay Shaplov @ 2025-09-04 14:41 UTC (permalink / raw)
Introduce ternary reloption as a replacement for current `vacuum_truncate`
implementation. Remove `vacuum_truncate_set` additional flag and using
`TERNARY_UNSET` value instead.
---
src/backend/access/common/reloptions.c | 129 ++++++++++++++++++++-----
src/backend/commands/vacuum.c | 4 +-
src/include/access/reloptions.h | 26 ++---
src/include/c.h | 16 +++
src/include/utils/rel.h | 3 +-
5 files changed, 135 insertions(+), 43 deletions(-)
diff --git a/src/backend/access/common/reloptions.c b/src/backend/access/common/reloptions.c
index 0af3fea68f..f543f11001 100644
--- a/src/backend/access/common/reloptions.c
+++ b/src/backend/access/common/reloptions.c
@@ -40,9 +40,9 @@
*
* To add an option:
*
- * (i) decide on a type (bool, integer, real, enum, string), name, default
- * value, upper and lower bounds (if applicable); for strings, consider a
- * validation routine.
+ * (i) decide on a type (bool, ternary, integer, real, enum, string), name,
+ * default value, upper and lower bounds (if applicable); for strings,
+ * consider a validation routine.
* (ii) add a record below (or use add_<type>_reloption).
* (iii) add it to the appropriate options struct (perhaps StdRdOptions)
* (iv) add it to the appropriate handling routine (perhaps
@@ -147,15 +147,6 @@ static relopt_bool boolRelOpts[] =
},
false
},
- {
- {
- "vacuum_truncate",
- "Enables vacuum to truncate empty pages at the end of this table",
- RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
- ShareUpdateExclusiveLock
- },
- true
- },
{
{
"deduplicate_items",
@@ -170,6 +161,21 @@ static relopt_bool boolRelOpts[] =
{{NULL}}
};
+static relopt_ternary ternaryRelOpts[] =
+{
+ {
+ {
+ "vacuum_truncate",
+ "Enables vacuum to truncate empty pages at the end of this table",
+ RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
+ ShareUpdateExclusiveLock
+ },
+ TERNARY_UNSET
+ },
+ /* list terminator */
+ {{NULL}}
+};
+
static relopt_int intRelOpts[] =
{
{
@@ -600,6 +606,13 @@ initialize_reloptions(void)
boolRelOpts[i].gen.lockmode));
j++;
}
+ for (i = 0; ternaryRelOpts[i].gen.name; i++)
+ {
+ Assert(DoLockModesConflict(ternaryRelOpts[i].gen.lockmode,
+ ternaryRelOpts[i].gen.lockmode));
+ j++;
+ }
+
for (i = 0; intRelOpts[i].gen.name; i++)
{
Assert(DoLockModesConflict(intRelOpts[i].gen.lockmode,
@@ -640,6 +653,14 @@ initialize_reloptions(void)
j++;
}
+ for (i = 0; ternaryRelOpts[i].gen.name; i++)
+ {
+ relOpts[j] = &ternaryRelOpts[i].gen;
+ relOpts[j]->type = RELOPT_TYPE_TERNARY;
+ relOpts[j]->namelen = strlen(relOpts[j]->name);
+ j++;
+ }
+
for (i = 0; intRelOpts[i].gen.name; i++)
{
relOpts[j] = &intRelOpts[i].gen;
@@ -800,6 +821,9 @@ allocate_reloption(bits32 kinds, int type, const char *name, const char *desc,
case RELOPT_TYPE_BOOL:
size = sizeof(relopt_bool);
break;
+ case RELOPT_TYPE_TERNARY:
+ size = sizeof(relopt_ternary);
+ break;
case RELOPT_TYPE_INT:
size = sizeof(relopt_int);
break;
@@ -883,6 +907,54 @@ add_local_bool_reloption(local_relopts *relopts, const char *name,
add_local_reloption(relopts, (relopt_gen *) newoption, offset);
}
+/*
+ * init_ternary_reloption
+ * Allocate and initialize a new ternary reloption
+ */
+static relopt_ternary *
+init_ternary_reloption(bits32 kinds, const char *name, const char *desc,
+ ternary default_val, LOCKMODE lockmode)
+{
+ relopt_ternary *newoption;
+
+ newoption = (relopt_ternary *) allocate_reloption(kinds,
+ RELOPT_TYPE_TERNARY, name, desc, lockmode);
+ newoption->default_val = default_val;
+
+ return newoption;
+}
+
+/*
+ * add_ternary_reloption
+ * Add a new ternary reloption
+ */
+void
+add_ternary_reloption(bits32 kinds, const char *name, const char *desc,
+ ternary default_val, LOCKMODE lockmode)
+{
+ relopt_ternary *newoption = init_ternary_reloption(kinds, name, desc,
+ default_val, lockmode);
+
+ add_reloption((relopt_gen *) newoption);
+}
+
+/*
+ * add_local_ternary_reloption
+ * Add a new ternary local reloption
+ *
+ * 'offset' is offset of ternary-typed field.
+ */
+void
+add_local_ternary_reloption(local_relopts *relopts, const char *name,
+ const char *desc, ternary default_val,
+ int offset)
+{
+ relopt_ternary *newoption = init_ternary_reloption(RELOPT_KIND_LOCAL,
+ name, desc,
+ default_val, 0);
+
+ add_local_reloption(relopts, (relopt_gen *) newoption, offset);
+}
/*
* init_real_reloption
@@ -1617,6 +1689,18 @@ parse_one_reloption(relopt_value *option, char *text_str, int text_len,
option->gen->name, value)));
}
break;
+ case RELOPT_TYPE_TERNARY:
+ {
+ bool b;
+ parsed = parse_bool(value, &b);
+ option->values.ternary_val = b ? TERNARY_TRUE : TERNARY_FALSE;
+ if (validate && !parsed)
+ ereport(ERROR,
+ (errcode(ERRCODE_INVALID_PARAMETER_VALUE),
+ errmsg("invalid value for ternary option \"%s\": %s",
+ option->gen->name, value)));
+ }
+ break;
case RELOPT_TYPE_INT:
{
relopt_int *optint = (relopt_int *) option->gen;
@@ -1780,17 +1864,6 @@ fillRelOptions(void *rdopts, Size basesize,
char *itempos = ((char *) rdopts) + elems[j].offset;
char *string_val;
- /*
- * If isset_offset is provided, store whether the reloption is
- * set there.
- */
- if (elems[j].isset_offset > 0)
- {
- char *setpos = ((char *) rdopts) + elems[j].isset_offset;
-
- *(bool *) setpos = options[i].isset;
- }
-
switch (options[i].gen->type)
{
case RELOPT_TYPE_BOOL:
@@ -1798,6 +1871,11 @@ fillRelOptions(void *rdopts, Size basesize,
options[i].values.bool_val :
((relopt_bool *) options[i].gen)->default_val;
break;
+ case RELOPT_TYPE_TERNARY:
+ *(ternary *) itempos = options[i].isset ?
+ options[i].values.ternary_val :
+ ((relopt_ternary *) options[i].gen)->default_val;
+ break;
case RELOPT_TYPE_INT:
*(int *) itempos = options[i].isset ?
options[i].values.int_val :
@@ -1912,8 +1990,8 @@ default_reloptions(Datum reloptions, bool validate, relopt_kind kind)
offsetof(StdRdOptions, parallel_workers)},
{"vacuum_index_cleanup", RELOPT_TYPE_ENUM,
offsetof(StdRdOptions, vacuum_index_cleanup)},
- {"vacuum_truncate", RELOPT_TYPE_BOOL,
- offsetof(StdRdOptions, vacuum_truncate), offsetof(StdRdOptions, vacuum_truncate_set)},
+ {"vacuum_truncate", RELOPT_TYPE_TERNARY,
+ offsetof(StdRdOptions, vacuum_truncate)},
{"vacuum_max_eager_freeze_failure_rate", RELOPT_TYPE_REAL,
offsetof(StdRdOptions, vacuum_max_eager_freeze_failure_rate)}
};
@@ -1993,7 +2071,6 @@ build_local_reloptions(local_relopts *relopts, Datum options, bool validate)
elems[i].optname = opt->option->name;
elems[i].opttype = opt->option->type;
elems[i].offset = opt->offset;
- elems[i].isset_offset = 0; /* not supported for local relopts yet */
i++;
}
diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c
index 733ef40ae7..977babff54 100644
--- a/src/backend/commands/vacuum.c
+++ b/src/backend/commands/vacuum.c
@@ -2221,9 +2221,9 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams params,
{
StdRdOptions *opts = (StdRdOptions *) rel->rd_options;
- if (opts && opts->vacuum_truncate_set)
+ if (opts && opts->vacuum_truncate != TERNARY_UNSET)
{
- if (opts->vacuum_truncate)
+ if (opts->vacuum_truncate == TERNARY_TRUE)
params.truncate = VACOPTVALUE_ENABLED;
else
params.truncate = VACOPTVALUE_DISABLED;
diff --git a/src/include/access/reloptions.h b/src/include/access/reloptions.h
index a604a4702c..a436697658 100644
--- a/src/include/access/reloptions.h
+++ b/src/include/access/reloptions.h
@@ -29,6 +29,7 @@
typedef enum relopt_type
{
RELOPT_TYPE_BOOL,
+ RELOPT_TYPE_TERNARY, /* on, off, unset */
RELOPT_TYPE_INT,
RELOPT_TYPE_REAL,
RELOPT_TYPE_ENUM,
@@ -80,6 +81,7 @@ typedef struct relopt_value
union
{
bool bool_val;
+ ternary ternary_val;
int int_val;
double real_val;
int enum_val;
@@ -94,6 +96,12 @@ typedef struct relopt_bool
bool default_val;
} relopt_bool;
+typedef struct relopt_rernary
+{
+ relopt_gen gen;
+ int default_val;
+} relopt_ternary;
+
typedef struct relopt_int
{
relopt_gen gen;
@@ -152,19 +160,6 @@ typedef struct
const char *optname; /* option's name */
relopt_type opttype; /* option's datatype */
int offset; /* offset of field in result struct */
-
- /*
- * isset_offset is an optional offset of a field in the result struct that
- * stores whether the option is explicitly set for the relation or if it
- * just picked up the default value. In most cases, this can be
- * accomplished by giving the reloption a special out-of-range default
- * value (e.g., some integer reloptions use -2), but this isn't always
- * possible. For example, a Boolean reloption cannot be given an
- * out-of-range default, so we need another way to discover the source of
- * its value. This offset is only used if given a value greater than
- * zero.
- */
- int isset_offset;
} relopt_parse_elt;
/* Local reloption definition */
@@ -195,6 +190,8 @@ typedef struct local_relopts
extern relopt_kind add_reloption_kind(void);
extern void add_bool_reloption(bits32 kinds, const char *name, const char *desc,
bool default_val, LOCKMODE lockmode);
+extern void add_ternary_reloption(bits32 kinds, const char *name,
+ const char *desc, int default_val, LOCKMODE lockmode);
extern void add_int_reloption(bits32 kinds, const char *name, const char *desc,
int default_val, int min_val, int max_val,
LOCKMODE lockmode);
@@ -214,6 +211,9 @@ extern void register_reloptions_validator(local_relopts *relopts,
extern void add_local_bool_reloption(local_relopts *relopts, const char *name,
const char *desc, bool default_val,
int offset);
+extern void add_local_ternary_reloption(local_relopts *relopts,
+ const char *name, const char *desc,
+ ternary default_val, int offset);
extern void add_local_int_reloption(local_relopts *relopts, const char *name,
const char *desc, int default_val,
int min_val, int max_val, int offset);
diff --git a/src/include/c.h b/src/include/c.h
index 39022f8a9d..9c59ccccf8 100644
--- a/src/include/c.h
+++ b/src/include/c.h
@@ -488,6 +488,22 @@ typedef void (*pg_funcptr_t) (void);
#include <stdbool.h>
+/*
+ * ternary
+ * Boolean value with an extrea "unset" option
+ *
+ * Ternary data type is used in relation options that can be "true", "false" or
+ * "unset". Since relation options are used deep inside the PostgreSQL code,
+ * this type is declared globally.
+*/
+
+typedef enum ternary
+{
+ TERNARY_FALSE = 0,
+ TERNARY_TRUE = 1,
+ TERNARY_UNSET = -1
+} ternary;
+
/* ----------------------------------------------------------------
* Section 3: standard system types
diff --git a/src/include/utils/rel.h b/src/include/utils/rel.h
index b552359915..7346d618f9 100644
--- a/src/include/utils/rel.h
+++ b/src/include/utils/rel.h
@@ -346,8 +346,7 @@ typedef struct StdRdOptions
bool user_catalog_table; /* use as an additional catalog relation */
int parallel_workers; /* max number of parallel workers */
StdRdOptIndexCleanup vacuum_index_cleanup; /* controls index vacuuming */
- bool vacuum_truncate; /* enables vacuum to truncate a relation */
- bool vacuum_truncate_set; /* whether vacuum_truncate is set */
+ ternary vacuum_truncate; /* enables vacuum to truncate a relation */
/*
* Fraction of pages in a relation that vacuum can eagerly scan and fail
--
2.39.2
--nextPart4155600.3ZeAukHxDK
Content-Disposition: attachment;
filename="v2a-0003-Add-alias-to-be-used-as-unset-state.patch"
Content-Transfer-Encoding: 7Bit
Content-Type: text/x-patch; charset="unicode-2-0-utf-8";
name="v2a-0003-Add-alias-to-be-used-as-unset-state.patch"
^ permalink raw reply [nested|flat] 868+ messages in thread
* [PATCH v2 2/4] Introduce ternary reloptions
@ 2025-09-04 14:41 Nikolay Shaplov <[email protected]>
0 siblings, 0 replies; 868+ messages in thread
From: Nikolay Shaplov @ 2025-09-04 14:41 UTC (permalink / raw)
Introduce ternary reloption as a replacement for current `vacuum_truncate`
implementation. Remove `vacuum_truncate_set` additional flag and using
`TERNARY_UNSET` value instead.
---
src/backend/access/common/reloptions.c | 129 ++++++++++++++++++++-----
src/backend/commands/vacuum.c | 4 +-
src/include/access/reloptions.h | 26 ++---
src/include/c.h | 16 +++
src/include/utils/rel.h | 3 +-
5 files changed, 135 insertions(+), 43 deletions(-)
diff --git a/src/backend/access/common/reloptions.c b/src/backend/access/common/reloptions.c
index 0af3fea68f..f543f11001 100644
--- a/src/backend/access/common/reloptions.c
+++ b/src/backend/access/common/reloptions.c
@@ -40,9 +40,9 @@
*
* To add an option:
*
- * (i) decide on a type (bool, integer, real, enum, string), name, default
- * value, upper and lower bounds (if applicable); for strings, consider a
- * validation routine.
+ * (i) decide on a type (bool, ternary, integer, real, enum, string), name,
+ * default value, upper and lower bounds (if applicable); for strings,
+ * consider a validation routine.
* (ii) add a record below (or use add_<type>_reloption).
* (iii) add it to the appropriate options struct (perhaps StdRdOptions)
* (iv) add it to the appropriate handling routine (perhaps
@@ -147,15 +147,6 @@ static relopt_bool boolRelOpts[] =
},
false
},
- {
- {
- "vacuum_truncate",
- "Enables vacuum to truncate empty pages at the end of this table",
- RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
- ShareUpdateExclusiveLock
- },
- true
- },
{
{
"deduplicate_items",
@@ -170,6 +161,21 @@ static relopt_bool boolRelOpts[] =
{{NULL}}
};
+static relopt_ternary ternaryRelOpts[] =
+{
+ {
+ {
+ "vacuum_truncate",
+ "Enables vacuum to truncate empty pages at the end of this table",
+ RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
+ ShareUpdateExclusiveLock
+ },
+ TERNARY_UNSET
+ },
+ /* list terminator */
+ {{NULL}}
+};
+
static relopt_int intRelOpts[] =
{
{
@@ -600,6 +606,13 @@ initialize_reloptions(void)
boolRelOpts[i].gen.lockmode));
j++;
}
+ for (i = 0; ternaryRelOpts[i].gen.name; i++)
+ {
+ Assert(DoLockModesConflict(ternaryRelOpts[i].gen.lockmode,
+ ternaryRelOpts[i].gen.lockmode));
+ j++;
+ }
+
for (i = 0; intRelOpts[i].gen.name; i++)
{
Assert(DoLockModesConflict(intRelOpts[i].gen.lockmode,
@@ -640,6 +653,14 @@ initialize_reloptions(void)
j++;
}
+ for (i = 0; ternaryRelOpts[i].gen.name; i++)
+ {
+ relOpts[j] = &ternaryRelOpts[i].gen;
+ relOpts[j]->type = RELOPT_TYPE_TERNARY;
+ relOpts[j]->namelen = strlen(relOpts[j]->name);
+ j++;
+ }
+
for (i = 0; intRelOpts[i].gen.name; i++)
{
relOpts[j] = &intRelOpts[i].gen;
@@ -800,6 +821,9 @@ allocate_reloption(bits32 kinds, int type, const char *name, const char *desc,
case RELOPT_TYPE_BOOL:
size = sizeof(relopt_bool);
break;
+ case RELOPT_TYPE_TERNARY:
+ size = sizeof(relopt_ternary);
+ break;
case RELOPT_TYPE_INT:
size = sizeof(relopt_int);
break;
@@ -883,6 +907,54 @@ add_local_bool_reloption(local_relopts *relopts, const char *name,
add_local_reloption(relopts, (relopt_gen *) newoption, offset);
}
+/*
+ * init_ternary_reloption
+ * Allocate and initialize a new ternary reloption
+ */
+static relopt_ternary *
+init_ternary_reloption(bits32 kinds, const char *name, const char *desc,
+ ternary default_val, LOCKMODE lockmode)
+{
+ relopt_ternary *newoption;
+
+ newoption = (relopt_ternary *) allocate_reloption(kinds,
+ RELOPT_TYPE_TERNARY, name, desc, lockmode);
+ newoption->default_val = default_val;
+
+ return newoption;
+}
+
+/*
+ * add_ternary_reloption
+ * Add a new ternary reloption
+ */
+void
+add_ternary_reloption(bits32 kinds, const char *name, const char *desc,
+ ternary default_val, LOCKMODE lockmode)
+{
+ relopt_ternary *newoption = init_ternary_reloption(kinds, name, desc,
+ default_val, lockmode);
+
+ add_reloption((relopt_gen *) newoption);
+}
+
+/*
+ * add_local_ternary_reloption
+ * Add a new ternary local reloption
+ *
+ * 'offset' is offset of ternary-typed field.
+ */
+void
+add_local_ternary_reloption(local_relopts *relopts, const char *name,
+ const char *desc, ternary default_val,
+ int offset)
+{
+ relopt_ternary *newoption = init_ternary_reloption(RELOPT_KIND_LOCAL,
+ name, desc,
+ default_val, 0);
+
+ add_local_reloption(relopts, (relopt_gen *) newoption, offset);
+}
/*
* init_real_reloption
@@ -1617,6 +1689,18 @@ parse_one_reloption(relopt_value *option, char *text_str, int text_len,
option->gen->name, value)));
}
break;
+ case RELOPT_TYPE_TERNARY:
+ {
+ bool b;
+ parsed = parse_bool(value, &b);
+ option->values.ternary_val = b ? TERNARY_TRUE : TERNARY_FALSE;
+ if (validate && !parsed)
+ ereport(ERROR,
+ (errcode(ERRCODE_INVALID_PARAMETER_VALUE),
+ errmsg("invalid value for ternary option \"%s\": %s",
+ option->gen->name, value)));
+ }
+ break;
case RELOPT_TYPE_INT:
{
relopt_int *optint = (relopt_int *) option->gen;
@@ -1780,17 +1864,6 @@ fillRelOptions(void *rdopts, Size basesize,
char *itempos = ((char *) rdopts) + elems[j].offset;
char *string_val;
- /*
- * If isset_offset is provided, store whether the reloption is
- * set there.
- */
- if (elems[j].isset_offset > 0)
- {
- char *setpos = ((char *) rdopts) + elems[j].isset_offset;
-
- *(bool *) setpos = options[i].isset;
- }
-
switch (options[i].gen->type)
{
case RELOPT_TYPE_BOOL:
@@ -1798,6 +1871,11 @@ fillRelOptions(void *rdopts, Size basesize,
options[i].values.bool_val :
((relopt_bool *) options[i].gen)->default_val;
break;
+ case RELOPT_TYPE_TERNARY:
+ *(ternary *) itempos = options[i].isset ?
+ options[i].values.ternary_val :
+ ((relopt_ternary *) options[i].gen)->default_val;
+ break;
case RELOPT_TYPE_INT:
*(int *) itempos = options[i].isset ?
options[i].values.int_val :
@@ -1912,8 +1990,8 @@ default_reloptions(Datum reloptions, bool validate, relopt_kind kind)
offsetof(StdRdOptions, parallel_workers)},
{"vacuum_index_cleanup", RELOPT_TYPE_ENUM,
offsetof(StdRdOptions, vacuum_index_cleanup)},
- {"vacuum_truncate", RELOPT_TYPE_BOOL,
- offsetof(StdRdOptions, vacuum_truncate), offsetof(StdRdOptions, vacuum_truncate_set)},
+ {"vacuum_truncate", RELOPT_TYPE_TERNARY,
+ offsetof(StdRdOptions, vacuum_truncate)},
{"vacuum_max_eager_freeze_failure_rate", RELOPT_TYPE_REAL,
offsetof(StdRdOptions, vacuum_max_eager_freeze_failure_rate)}
};
@@ -1993,7 +2071,6 @@ build_local_reloptions(local_relopts *relopts, Datum options, bool validate)
elems[i].optname = opt->option->name;
elems[i].opttype = opt->option->type;
elems[i].offset = opt->offset;
- elems[i].isset_offset = 0; /* not supported for local relopts yet */
i++;
}
diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c
index 733ef40ae7..977babff54 100644
--- a/src/backend/commands/vacuum.c
+++ b/src/backend/commands/vacuum.c
@@ -2221,9 +2221,9 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams params,
{
StdRdOptions *opts = (StdRdOptions *) rel->rd_options;
- if (opts && opts->vacuum_truncate_set)
+ if (opts && opts->vacuum_truncate != TERNARY_UNSET)
{
- if (opts->vacuum_truncate)
+ if (opts->vacuum_truncate == TERNARY_TRUE)
params.truncate = VACOPTVALUE_ENABLED;
else
params.truncate = VACOPTVALUE_DISABLED;
diff --git a/src/include/access/reloptions.h b/src/include/access/reloptions.h
index a604a4702c..a436697658 100644
--- a/src/include/access/reloptions.h
+++ b/src/include/access/reloptions.h
@@ -29,6 +29,7 @@
typedef enum relopt_type
{
RELOPT_TYPE_BOOL,
+ RELOPT_TYPE_TERNARY, /* on, off, unset */
RELOPT_TYPE_INT,
RELOPT_TYPE_REAL,
RELOPT_TYPE_ENUM,
@@ -80,6 +81,7 @@ typedef struct relopt_value
union
{
bool bool_val;
+ ternary ternary_val;
int int_val;
double real_val;
int enum_val;
@@ -94,6 +96,12 @@ typedef struct relopt_bool
bool default_val;
} relopt_bool;
+typedef struct relopt_rernary
+{
+ relopt_gen gen;
+ int default_val;
+} relopt_ternary;
+
typedef struct relopt_int
{
relopt_gen gen;
@@ -152,19 +160,6 @@ typedef struct
const char *optname; /* option's name */
relopt_type opttype; /* option's datatype */
int offset; /* offset of field in result struct */
-
- /*
- * isset_offset is an optional offset of a field in the result struct that
- * stores whether the option is explicitly set for the relation or if it
- * just picked up the default value. In most cases, this can be
- * accomplished by giving the reloption a special out-of-range default
- * value (e.g., some integer reloptions use -2), but this isn't always
- * possible. For example, a Boolean reloption cannot be given an
- * out-of-range default, so we need another way to discover the source of
- * its value. This offset is only used if given a value greater than
- * zero.
- */
- int isset_offset;
} relopt_parse_elt;
/* Local reloption definition */
@@ -195,6 +190,8 @@ typedef struct local_relopts
extern relopt_kind add_reloption_kind(void);
extern void add_bool_reloption(bits32 kinds, const char *name, const char *desc,
bool default_val, LOCKMODE lockmode);
+extern void add_ternary_reloption(bits32 kinds, const char *name,
+ const char *desc, int default_val, LOCKMODE lockmode);
extern void add_int_reloption(bits32 kinds, const char *name, const char *desc,
int default_val, int min_val, int max_val,
LOCKMODE lockmode);
@@ -214,6 +211,9 @@ extern void register_reloptions_validator(local_relopts *relopts,
extern void add_local_bool_reloption(local_relopts *relopts, const char *name,
const char *desc, bool default_val,
int offset);
+extern void add_local_ternary_reloption(local_relopts *relopts,
+ const char *name, const char *desc,
+ ternary default_val, int offset);
extern void add_local_int_reloption(local_relopts *relopts, const char *name,
const char *desc, int default_val,
int min_val, int max_val, int offset);
diff --git a/src/include/c.h b/src/include/c.h
index 39022f8a9d..9c59ccccf8 100644
--- a/src/include/c.h
+++ b/src/include/c.h
@@ -488,6 +488,22 @@ typedef void (*pg_funcptr_t) (void);
#include <stdbool.h>
+/*
+ * ternary
+ * Boolean value with an extrea "unset" option
+ *
+ * Ternary data type is used in relation options that can be "true", "false" or
+ * "unset". Since relation options are used deep inside the PostgreSQL code,
+ * this type is declared globally.
+*/
+
+typedef enum ternary
+{
+ TERNARY_FALSE = 0,
+ TERNARY_TRUE = 1,
+ TERNARY_UNSET = -1
+} ternary;
+
/* ----------------------------------------------------------------
* Section 3: standard system types
diff --git a/src/include/utils/rel.h b/src/include/utils/rel.h
index b552359915..7346d618f9 100644
--- a/src/include/utils/rel.h
+++ b/src/include/utils/rel.h
@@ -346,8 +346,7 @@ typedef struct StdRdOptions
bool user_catalog_table; /* use as an additional catalog relation */
int parallel_workers; /* max number of parallel workers */
StdRdOptIndexCleanup vacuum_index_cleanup; /* controls index vacuuming */
- bool vacuum_truncate; /* enables vacuum to truncate a relation */
- bool vacuum_truncate_set; /* whether vacuum_truncate is set */
+ ternary vacuum_truncate; /* enables vacuum to truncate a relation */
/*
* Fraction of pages in a relation that vacuum can eagerly scan and fail
--
2.39.2
--nextPart5129932.5fSG56mABF
Content-Disposition: attachment;
filename="v2-0003-Add-alias-to-be-used-as-unset-state.patch"
Content-Transfer-Encoding: 7Bit
Content-Type: text/x-patch; charset="unicode-2-0-utf-8";
name="v2-0003-Add-alias-to-be-used-as-unset-state.patch"
^ permalink raw reply [nested|flat] 868+ messages in thread
* [PATCH v2a 2/4] Introduce ternary reloptions
@ 2025-09-04 14:41 Nikolay Shaplov <[email protected]>
0 siblings, 0 replies; 868+ messages in thread
From: Nikolay Shaplov @ 2025-09-04 14:41 UTC (permalink / raw)
Introduce ternary reloption as a replacement for current `vacuum_truncate`
implementation. Remove `vacuum_truncate_set` additional flag and using
`TERNARY_UNSET` value instead.
---
src/backend/access/common/reloptions.c | 129 ++++++++++++++++++++-----
src/backend/commands/vacuum.c | 4 +-
src/include/access/reloptions.h | 26 ++---
src/include/c.h | 16 +++
src/include/utils/rel.h | 3 +-
5 files changed, 135 insertions(+), 43 deletions(-)
diff --git a/src/backend/access/common/reloptions.c b/src/backend/access/common/reloptions.c
index 0af3fea68f..f543f11001 100644
--- a/src/backend/access/common/reloptions.c
+++ b/src/backend/access/common/reloptions.c
@@ -40,9 +40,9 @@
*
* To add an option:
*
- * (i) decide on a type (bool, integer, real, enum, string), name, default
- * value, upper and lower bounds (if applicable); for strings, consider a
- * validation routine.
+ * (i) decide on a type (bool, ternary, integer, real, enum, string), name,
+ * default value, upper and lower bounds (if applicable); for strings,
+ * consider a validation routine.
* (ii) add a record below (or use add_<type>_reloption).
* (iii) add it to the appropriate options struct (perhaps StdRdOptions)
* (iv) add it to the appropriate handling routine (perhaps
@@ -147,15 +147,6 @@ static relopt_bool boolRelOpts[] =
},
false
},
- {
- {
- "vacuum_truncate",
- "Enables vacuum to truncate empty pages at the end of this table",
- RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
- ShareUpdateExclusiveLock
- },
- true
- },
{
{
"deduplicate_items",
@@ -170,6 +161,21 @@ static relopt_bool boolRelOpts[] =
{{NULL}}
};
+static relopt_ternary ternaryRelOpts[] =
+{
+ {
+ {
+ "vacuum_truncate",
+ "Enables vacuum to truncate empty pages at the end of this table",
+ RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
+ ShareUpdateExclusiveLock
+ },
+ TERNARY_UNSET
+ },
+ /* list terminator */
+ {{NULL}}
+};
+
static relopt_int intRelOpts[] =
{
{
@@ -600,6 +606,13 @@ initialize_reloptions(void)
boolRelOpts[i].gen.lockmode));
j++;
}
+ for (i = 0; ternaryRelOpts[i].gen.name; i++)
+ {
+ Assert(DoLockModesConflict(ternaryRelOpts[i].gen.lockmode,
+ ternaryRelOpts[i].gen.lockmode));
+ j++;
+ }
+
for (i = 0; intRelOpts[i].gen.name; i++)
{
Assert(DoLockModesConflict(intRelOpts[i].gen.lockmode,
@@ -640,6 +653,14 @@ initialize_reloptions(void)
j++;
}
+ for (i = 0; ternaryRelOpts[i].gen.name; i++)
+ {
+ relOpts[j] = &ternaryRelOpts[i].gen;
+ relOpts[j]->type = RELOPT_TYPE_TERNARY;
+ relOpts[j]->namelen = strlen(relOpts[j]->name);
+ j++;
+ }
+
for (i = 0; intRelOpts[i].gen.name; i++)
{
relOpts[j] = &intRelOpts[i].gen;
@@ -800,6 +821,9 @@ allocate_reloption(bits32 kinds, int type, const char *name, const char *desc,
case RELOPT_TYPE_BOOL:
size = sizeof(relopt_bool);
break;
+ case RELOPT_TYPE_TERNARY:
+ size = sizeof(relopt_ternary);
+ break;
case RELOPT_TYPE_INT:
size = sizeof(relopt_int);
break;
@@ -883,6 +907,54 @@ add_local_bool_reloption(local_relopts *relopts, const char *name,
add_local_reloption(relopts, (relopt_gen *) newoption, offset);
}
+/*
+ * init_ternary_reloption
+ * Allocate and initialize a new ternary reloption
+ */
+static relopt_ternary *
+init_ternary_reloption(bits32 kinds, const char *name, const char *desc,
+ ternary default_val, LOCKMODE lockmode)
+{
+ relopt_ternary *newoption;
+
+ newoption = (relopt_ternary *) allocate_reloption(kinds,
+ RELOPT_TYPE_TERNARY, name, desc, lockmode);
+ newoption->default_val = default_val;
+
+ return newoption;
+}
+
+/*
+ * add_ternary_reloption
+ * Add a new ternary reloption
+ */
+void
+add_ternary_reloption(bits32 kinds, const char *name, const char *desc,
+ ternary default_val, LOCKMODE lockmode)
+{
+ relopt_ternary *newoption = init_ternary_reloption(kinds, name, desc,
+ default_val, lockmode);
+
+ add_reloption((relopt_gen *) newoption);
+}
+
+/*
+ * add_local_ternary_reloption
+ * Add a new ternary local reloption
+ *
+ * 'offset' is offset of ternary-typed field.
+ */
+void
+add_local_ternary_reloption(local_relopts *relopts, const char *name,
+ const char *desc, ternary default_val,
+ int offset)
+{
+ relopt_ternary *newoption = init_ternary_reloption(RELOPT_KIND_LOCAL,
+ name, desc,
+ default_val, 0);
+
+ add_local_reloption(relopts, (relopt_gen *) newoption, offset);
+}
/*
* init_real_reloption
@@ -1617,6 +1689,18 @@ parse_one_reloption(relopt_value *option, char *text_str, int text_len,
option->gen->name, value)));
}
break;
+ case RELOPT_TYPE_TERNARY:
+ {
+ bool b;
+ parsed = parse_bool(value, &b);
+ option->values.ternary_val = b ? TERNARY_TRUE : TERNARY_FALSE;
+ if (validate && !parsed)
+ ereport(ERROR,
+ (errcode(ERRCODE_INVALID_PARAMETER_VALUE),
+ errmsg("invalid value for ternary option \"%s\": %s",
+ option->gen->name, value)));
+ }
+ break;
case RELOPT_TYPE_INT:
{
relopt_int *optint = (relopt_int *) option->gen;
@@ -1780,17 +1864,6 @@ fillRelOptions(void *rdopts, Size basesize,
char *itempos = ((char *) rdopts) + elems[j].offset;
char *string_val;
- /*
- * If isset_offset is provided, store whether the reloption is
- * set there.
- */
- if (elems[j].isset_offset > 0)
- {
- char *setpos = ((char *) rdopts) + elems[j].isset_offset;
-
- *(bool *) setpos = options[i].isset;
- }
-
switch (options[i].gen->type)
{
case RELOPT_TYPE_BOOL:
@@ -1798,6 +1871,11 @@ fillRelOptions(void *rdopts, Size basesize,
options[i].values.bool_val :
((relopt_bool *) options[i].gen)->default_val;
break;
+ case RELOPT_TYPE_TERNARY:
+ *(ternary *) itempos = options[i].isset ?
+ options[i].values.ternary_val :
+ ((relopt_ternary *) options[i].gen)->default_val;
+ break;
case RELOPT_TYPE_INT:
*(int *) itempos = options[i].isset ?
options[i].values.int_val :
@@ -1912,8 +1990,8 @@ default_reloptions(Datum reloptions, bool validate, relopt_kind kind)
offsetof(StdRdOptions, parallel_workers)},
{"vacuum_index_cleanup", RELOPT_TYPE_ENUM,
offsetof(StdRdOptions, vacuum_index_cleanup)},
- {"vacuum_truncate", RELOPT_TYPE_BOOL,
- offsetof(StdRdOptions, vacuum_truncate), offsetof(StdRdOptions, vacuum_truncate_set)},
+ {"vacuum_truncate", RELOPT_TYPE_TERNARY,
+ offsetof(StdRdOptions, vacuum_truncate)},
{"vacuum_max_eager_freeze_failure_rate", RELOPT_TYPE_REAL,
offsetof(StdRdOptions, vacuum_max_eager_freeze_failure_rate)}
};
@@ -1993,7 +2071,6 @@ build_local_reloptions(local_relopts *relopts, Datum options, bool validate)
elems[i].optname = opt->option->name;
elems[i].opttype = opt->option->type;
elems[i].offset = opt->offset;
- elems[i].isset_offset = 0; /* not supported for local relopts yet */
i++;
}
diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c
index 733ef40ae7..977babff54 100644
--- a/src/backend/commands/vacuum.c
+++ b/src/backend/commands/vacuum.c
@@ -2221,9 +2221,9 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams params,
{
StdRdOptions *opts = (StdRdOptions *) rel->rd_options;
- if (opts && opts->vacuum_truncate_set)
+ if (opts && opts->vacuum_truncate != TERNARY_UNSET)
{
- if (opts->vacuum_truncate)
+ if (opts->vacuum_truncate == TERNARY_TRUE)
params.truncate = VACOPTVALUE_ENABLED;
else
params.truncate = VACOPTVALUE_DISABLED;
diff --git a/src/include/access/reloptions.h b/src/include/access/reloptions.h
index a604a4702c..a436697658 100644
--- a/src/include/access/reloptions.h
+++ b/src/include/access/reloptions.h
@@ -29,6 +29,7 @@
typedef enum relopt_type
{
RELOPT_TYPE_BOOL,
+ RELOPT_TYPE_TERNARY, /* on, off, unset */
RELOPT_TYPE_INT,
RELOPT_TYPE_REAL,
RELOPT_TYPE_ENUM,
@@ -80,6 +81,7 @@ typedef struct relopt_value
union
{
bool bool_val;
+ ternary ternary_val;
int int_val;
double real_val;
int enum_val;
@@ -94,6 +96,12 @@ typedef struct relopt_bool
bool default_val;
} relopt_bool;
+typedef struct relopt_rernary
+{
+ relopt_gen gen;
+ int default_val;
+} relopt_ternary;
+
typedef struct relopt_int
{
relopt_gen gen;
@@ -152,19 +160,6 @@ typedef struct
const char *optname; /* option's name */
relopt_type opttype; /* option's datatype */
int offset; /* offset of field in result struct */
-
- /*
- * isset_offset is an optional offset of a field in the result struct that
- * stores whether the option is explicitly set for the relation or if it
- * just picked up the default value. In most cases, this can be
- * accomplished by giving the reloption a special out-of-range default
- * value (e.g., some integer reloptions use -2), but this isn't always
- * possible. For example, a Boolean reloption cannot be given an
- * out-of-range default, so we need another way to discover the source of
- * its value. This offset is only used if given a value greater than
- * zero.
- */
- int isset_offset;
} relopt_parse_elt;
/* Local reloption definition */
@@ -195,6 +190,8 @@ typedef struct local_relopts
extern relopt_kind add_reloption_kind(void);
extern void add_bool_reloption(bits32 kinds, const char *name, const char *desc,
bool default_val, LOCKMODE lockmode);
+extern void add_ternary_reloption(bits32 kinds, const char *name,
+ const char *desc, int default_val, LOCKMODE lockmode);
extern void add_int_reloption(bits32 kinds, const char *name, const char *desc,
int default_val, int min_val, int max_val,
LOCKMODE lockmode);
@@ -214,6 +211,9 @@ extern void register_reloptions_validator(local_relopts *relopts,
extern void add_local_bool_reloption(local_relopts *relopts, const char *name,
const char *desc, bool default_val,
int offset);
+extern void add_local_ternary_reloption(local_relopts *relopts,
+ const char *name, const char *desc,
+ ternary default_val, int offset);
extern void add_local_int_reloption(local_relopts *relopts, const char *name,
const char *desc, int default_val,
int min_val, int max_val, int offset);
diff --git a/src/include/c.h b/src/include/c.h
index 39022f8a9d..9c59ccccf8 100644
--- a/src/include/c.h
+++ b/src/include/c.h
@@ -488,6 +488,22 @@ typedef void (*pg_funcptr_t) (void);
#include <stdbool.h>
+/*
+ * ternary
+ * Boolean value with an extrea "unset" option
+ *
+ * Ternary data type is used in relation options that can be "true", "false" or
+ * "unset". Since relation options are used deep inside the PostgreSQL code,
+ * this type is declared globally.
+*/
+
+typedef enum ternary
+{
+ TERNARY_FALSE = 0,
+ TERNARY_TRUE = 1,
+ TERNARY_UNSET = -1
+} ternary;
+
/* ----------------------------------------------------------------
* Section 3: standard system types
diff --git a/src/include/utils/rel.h b/src/include/utils/rel.h
index b552359915..7346d618f9 100644
--- a/src/include/utils/rel.h
+++ b/src/include/utils/rel.h
@@ -346,8 +346,7 @@ typedef struct StdRdOptions
bool user_catalog_table; /* use as an additional catalog relation */
int parallel_workers; /* max number of parallel workers */
StdRdOptIndexCleanup vacuum_index_cleanup; /* controls index vacuuming */
- bool vacuum_truncate; /* enables vacuum to truncate a relation */
- bool vacuum_truncate_set; /* whether vacuum_truncate is set */
+ ternary vacuum_truncate; /* enables vacuum to truncate a relation */
/*
* Fraction of pages in a relation that vacuum can eagerly scan and fail
--
2.39.2
--nextPart4155600.3ZeAukHxDK
Content-Disposition: attachment;
filename="v2a-0003-Add-alias-to-be-used-as-unset-state.patch"
Content-Transfer-Encoding: 7Bit
Content-Type: text/x-patch; charset="unicode-2-0-utf-8";
name="v2a-0003-Add-alias-to-be-used-as-unset-state.patch"
^ permalink raw reply [nested|flat] 868+ messages in thread
* [PATCH v2a 2/4] Introduce ternary reloptions
@ 2025-09-04 14:41 Nikolay Shaplov <[email protected]>
0 siblings, 0 replies; 868+ messages in thread
From: Nikolay Shaplov @ 2025-09-04 14:41 UTC (permalink / raw)
Introduce ternary reloption as a replacement for current `vacuum_truncate`
implementation. Remove `vacuum_truncate_set` additional flag and using
`TERNARY_UNSET` value instead.
---
src/backend/access/common/reloptions.c | 129 ++++++++++++++++++++-----
src/backend/commands/vacuum.c | 4 +-
src/include/access/reloptions.h | 26 ++---
src/include/c.h | 16 +++
src/include/utils/rel.h | 3 +-
5 files changed, 135 insertions(+), 43 deletions(-)
diff --git a/src/backend/access/common/reloptions.c b/src/backend/access/common/reloptions.c
index 0af3fea68f..f543f11001 100644
--- a/src/backend/access/common/reloptions.c
+++ b/src/backend/access/common/reloptions.c
@@ -40,9 +40,9 @@
*
* To add an option:
*
- * (i) decide on a type (bool, integer, real, enum, string), name, default
- * value, upper and lower bounds (if applicable); for strings, consider a
- * validation routine.
+ * (i) decide on a type (bool, ternary, integer, real, enum, string), name,
+ * default value, upper and lower bounds (if applicable); for strings,
+ * consider a validation routine.
* (ii) add a record below (or use add_<type>_reloption).
* (iii) add it to the appropriate options struct (perhaps StdRdOptions)
* (iv) add it to the appropriate handling routine (perhaps
@@ -147,15 +147,6 @@ static relopt_bool boolRelOpts[] =
},
false
},
- {
- {
- "vacuum_truncate",
- "Enables vacuum to truncate empty pages at the end of this table",
- RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
- ShareUpdateExclusiveLock
- },
- true
- },
{
{
"deduplicate_items",
@@ -170,6 +161,21 @@ static relopt_bool boolRelOpts[] =
{{NULL}}
};
+static relopt_ternary ternaryRelOpts[] =
+{
+ {
+ {
+ "vacuum_truncate",
+ "Enables vacuum to truncate empty pages at the end of this table",
+ RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
+ ShareUpdateExclusiveLock
+ },
+ TERNARY_UNSET
+ },
+ /* list terminator */
+ {{NULL}}
+};
+
static relopt_int intRelOpts[] =
{
{
@@ -600,6 +606,13 @@ initialize_reloptions(void)
boolRelOpts[i].gen.lockmode));
j++;
}
+ for (i = 0; ternaryRelOpts[i].gen.name; i++)
+ {
+ Assert(DoLockModesConflict(ternaryRelOpts[i].gen.lockmode,
+ ternaryRelOpts[i].gen.lockmode));
+ j++;
+ }
+
for (i = 0; intRelOpts[i].gen.name; i++)
{
Assert(DoLockModesConflict(intRelOpts[i].gen.lockmode,
@@ -640,6 +653,14 @@ initialize_reloptions(void)
j++;
}
+ for (i = 0; ternaryRelOpts[i].gen.name; i++)
+ {
+ relOpts[j] = &ternaryRelOpts[i].gen;
+ relOpts[j]->type = RELOPT_TYPE_TERNARY;
+ relOpts[j]->namelen = strlen(relOpts[j]->name);
+ j++;
+ }
+
for (i = 0; intRelOpts[i].gen.name; i++)
{
relOpts[j] = &intRelOpts[i].gen;
@@ -800,6 +821,9 @@ allocate_reloption(bits32 kinds, int type, const char *name, const char *desc,
case RELOPT_TYPE_BOOL:
size = sizeof(relopt_bool);
break;
+ case RELOPT_TYPE_TERNARY:
+ size = sizeof(relopt_ternary);
+ break;
case RELOPT_TYPE_INT:
size = sizeof(relopt_int);
break;
@@ -883,6 +907,54 @@ add_local_bool_reloption(local_relopts *relopts, const char *name,
add_local_reloption(relopts, (relopt_gen *) newoption, offset);
}
+/*
+ * init_ternary_reloption
+ * Allocate and initialize a new ternary reloption
+ */
+static relopt_ternary *
+init_ternary_reloption(bits32 kinds, const char *name, const char *desc,
+ ternary default_val, LOCKMODE lockmode)
+{
+ relopt_ternary *newoption;
+
+ newoption = (relopt_ternary *) allocate_reloption(kinds,
+ RELOPT_TYPE_TERNARY, name, desc, lockmode);
+ newoption->default_val = default_val;
+
+ return newoption;
+}
+
+/*
+ * add_ternary_reloption
+ * Add a new ternary reloption
+ */
+void
+add_ternary_reloption(bits32 kinds, const char *name, const char *desc,
+ ternary default_val, LOCKMODE lockmode)
+{
+ relopt_ternary *newoption = init_ternary_reloption(kinds, name, desc,
+ default_val, lockmode);
+
+ add_reloption((relopt_gen *) newoption);
+}
+
+/*
+ * add_local_ternary_reloption
+ * Add a new ternary local reloption
+ *
+ * 'offset' is offset of ternary-typed field.
+ */
+void
+add_local_ternary_reloption(local_relopts *relopts, const char *name,
+ const char *desc, ternary default_val,
+ int offset)
+{
+ relopt_ternary *newoption = init_ternary_reloption(RELOPT_KIND_LOCAL,
+ name, desc,
+ default_val, 0);
+
+ add_local_reloption(relopts, (relopt_gen *) newoption, offset);
+}
/*
* init_real_reloption
@@ -1617,6 +1689,18 @@ parse_one_reloption(relopt_value *option, char *text_str, int text_len,
option->gen->name, value)));
}
break;
+ case RELOPT_TYPE_TERNARY:
+ {
+ bool b;
+ parsed = parse_bool(value, &b);
+ option->values.ternary_val = b ? TERNARY_TRUE : TERNARY_FALSE;
+ if (validate && !parsed)
+ ereport(ERROR,
+ (errcode(ERRCODE_INVALID_PARAMETER_VALUE),
+ errmsg("invalid value for ternary option \"%s\": %s",
+ option->gen->name, value)));
+ }
+ break;
case RELOPT_TYPE_INT:
{
relopt_int *optint = (relopt_int *) option->gen;
@@ -1780,17 +1864,6 @@ fillRelOptions(void *rdopts, Size basesize,
char *itempos = ((char *) rdopts) + elems[j].offset;
char *string_val;
- /*
- * If isset_offset is provided, store whether the reloption is
- * set there.
- */
- if (elems[j].isset_offset > 0)
- {
- char *setpos = ((char *) rdopts) + elems[j].isset_offset;
-
- *(bool *) setpos = options[i].isset;
- }
-
switch (options[i].gen->type)
{
case RELOPT_TYPE_BOOL:
@@ -1798,6 +1871,11 @@ fillRelOptions(void *rdopts, Size basesize,
options[i].values.bool_val :
((relopt_bool *) options[i].gen)->default_val;
break;
+ case RELOPT_TYPE_TERNARY:
+ *(ternary *) itempos = options[i].isset ?
+ options[i].values.ternary_val :
+ ((relopt_ternary *) options[i].gen)->default_val;
+ break;
case RELOPT_TYPE_INT:
*(int *) itempos = options[i].isset ?
options[i].values.int_val :
@@ -1912,8 +1990,8 @@ default_reloptions(Datum reloptions, bool validate, relopt_kind kind)
offsetof(StdRdOptions, parallel_workers)},
{"vacuum_index_cleanup", RELOPT_TYPE_ENUM,
offsetof(StdRdOptions, vacuum_index_cleanup)},
- {"vacuum_truncate", RELOPT_TYPE_BOOL,
- offsetof(StdRdOptions, vacuum_truncate), offsetof(StdRdOptions, vacuum_truncate_set)},
+ {"vacuum_truncate", RELOPT_TYPE_TERNARY,
+ offsetof(StdRdOptions, vacuum_truncate)},
{"vacuum_max_eager_freeze_failure_rate", RELOPT_TYPE_REAL,
offsetof(StdRdOptions, vacuum_max_eager_freeze_failure_rate)}
};
@@ -1993,7 +2071,6 @@ build_local_reloptions(local_relopts *relopts, Datum options, bool validate)
elems[i].optname = opt->option->name;
elems[i].opttype = opt->option->type;
elems[i].offset = opt->offset;
- elems[i].isset_offset = 0; /* not supported for local relopts yet */
i++;
}
diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c
index 733ef40ae7..977babff54 100644
--- a/src/backend/commands/vacuum.c
+++ b/src/backend/commands/vacuum.c
@@ -2221,9 +2221,9 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams params,
{
StdRdOptions *opts = (StdRdOptions *) rel->rd_options;
- if (opts && opts->vacuum_truncate_set)
+ if (opts && opts->vacuum_truncate != TERNARY_UNSET)
{
- if (opts->vacuum_truncate)
+ if (opts->vacuum_truncate == TERNARY_TRUE)
params.truncate = VACOPTVALUE_ENABLED;
else
params.truncate = VACOPTVALUE_DISABLED;
diff --git a/src/include/access/reloptions.h b/src/include/access/reloptions.h
index a604a4702c..a436697658 100644
--- a/src/include/access/reloptions.h
+++ b/src/include/access/reloptions.h
@@ -29,6 +29,7 @@
typedef enum relopt_type
{
RELOPT_TYPE_BOOL,
+ RELOPT_TYPE_TERNARY, /* on, off, unset */
RELOPT_TYPE_INT,
RELOPT_TYPE_REAL,
RELOPT_TYPE_ENUM,
@@ -80,6 +81,7 @@ typedef struct relopt_value
union
{
bool bool_val;
+ ternary ternary_val;
int int_val;
double real_val;
int enum_val;
@@ -94,6 +96,12 @@ typedef struct relopt_bool
bool default_val;
} relopt_bool;
+typedef struct relopt_rernary
+{
+ relopt_gen gen;
+ int default_val;
+} relopt_ternary;
+
typedef struct relopt_int
{
relopt_gen gen;
@@ -152,19 +160,6 @@ typedef struct
const char *optname; /* option's name */
relopt_type opttype; /* option's datatype */
int offset; /* offset of field in result struct */
-
- /*
- * isset_offset is an optional offset of a field in the result struct that
- * stores whether the option is explicitly set for the relation or if it
- * just picked up the default value. In most cases, this can be
- * accomplished by giving the reloption a special out-of-range default
- * value (e.g., some integer reloptions use -2), but this isn't always
- * possible. For example, a Boolean reloption cannot be given an
- * out-of-range default, so we need another way to discover the source of
- * its value. This offset is only used if given a value greater than
- * zero.
- */
- int isset_offset;
} relopt_parse_elt;
/* Local reloption definition */
@@ -195,6 +190,8 @@ typedef struct local_relopts
extern relopt_kind add_reloption_kind(void);
extern void add_bool_reloption(bits32 kinds, const char *name, const char *desc,
bool default_val, LOCKMODE lockmode);
+extern void add_ternary_reloption(bits32 kinds, const char *name,
+ const char *desc, int default_val, LOCKMODE lockmode);
extern void add_int_reloption(bits32 kinds, const char *name, const char *desc,
int default_val, int min_val, int max_val,
LOCKMODE lockmode);
@@ -214,6 +211,9 @@ extern void register_reloptions_validator(local_relopts *relopts,
extern void add_local_bool_reloption(local_relopts *relopts, const char *name,
const char *desc, bool default_val,
int offset);
+extern void add_local_ternary_reloption(local_relopts *relopts,
+ const char *name, const char *desc,
+ ternary default_val, int offset);
extern void add_local_int_reloption(local_relopts *relopts, const char *name,
const char *desc, int default_val,
int min_val, int max_val, int offset);
diff --git a/src/include/c.h b/src/include/c.h
index 39022f8a9d..9c59ccccf8 100644
--- a/src/include/c.h
+++ b/src/include/c.h
@@ -488,6 +488,22 @@ typedef void (*pg_funcptr_t) (void);
#include <stdbool.h>
+/*
+ * ternary
+ * Boolean value with an extrea "unset" option
+ *
+ * Ternary data type is used in relation options that can be "true", "false" or
+ * "unset". Since relation options are used deep inside the PostgreSQL code,
+ * this type is declared globally.
+*/
+
+typedef enum ternary
+{
+ TERNARY_FALSE = 0,
+ TERNARY_TRUE = 1,
+ TERNARY_UNSET = -1
+} ternary;
+
/* ----------------------------------------------------------------
* Section 3: standard system types
diff --git a/src/include/utils/rel.h b/src/include/utils/rel.h
index b552359915..7346d618f9 100644
--- a/src/include/utils/rel.h
+++ b/src/include/utils/rel.h
@@ -346,8 +346,7 @@ typedef struct StdRdOptions
bool user_catalog_table; /* use as an additional catalog relation */
int parallel_workers; /* max number of parallel workers */
StdRdOptIndexCleanup vacuum_index_cleanup; /* controls index vacuuming */
- bool vacuum_truncate; /* enables vacuum to truncate a relation */
- bool vacuum_truncate_set; /* whether vacuum_truncate is set */
+ ternary vacuum_truncate; /* enables vacuum to truncate a relation */
/*
* Fraction of pages in a relation that vacuum can eagerly scan and fail
--
2.39.2
--nextPart4155600.3ZeAukHxDK
Content-Disposition: attachment;
filename="v2a-0003-Add-alias-to-be-used-as-unset-state.patch"
Content-Transfer-Encoding: 7Bit
Content-Type: text/x-patch; charset="unicode-2-0-utf-8";
name="v2a-0003-Add-alias-to-be-used-as-unset-state.patch"
^ permalink raw reply [nested|flat] 868+ messages in thread
* [PATCH v2a 2/4] Introduce ternary reloptions
@ 2025-09-04 14:41 Nikolay Shaplov <[email protected]>
0 siblings, 0 replies; 868+ messages in thread
From: Nikolay Shaplov @ 2025-09-04 14:41 UTC (permalink / raw)
Introduce ternary reloption as a replacement for current `vacuum_truncate`
implementation. Remove `vacuum_truncate_set` additional flag and using
`TERNARY_UNSET` value instead.
---
src/backend/access/common/reloptions.c | 129 ++++++++++++++++++++-----
src/backend/commands/vacuum.c | 4 +-
src/include/access/reloptions.h | 26 ++---
src/include/c.h | 16 +++
src/include/utils/rel.h | 3 +-
5 files changed, 135 insertions(+), 43 deletions(-)
diff --git a/src/backend/access/common/reloptions.c b/src/backend/access/common/reloptions.c
index 0af3fea68f..f543f11001 100644
--- a/src/backend/access/common/reloptions.c
+++ b/src/backend/access/common/reloptions.c
@@ -40,9 +40,9 @@
*
* To add an option:
*
- * (i) decide on a type (bool, integer, real, enum, string), name, default
- * value, upper and lower bounds (if applicable); for strings, consider a
- * validation routine.
+ * (i) decide on a type (bool, ternary, integer, real, enum, string), name,
+ * default value, upper and lower bounds (if applicable); for strings,
+ * consider a validation routine.
* (ii) add a record below (or use add_<type>_reloption).
* (iii) add it to the appropriate options struct (perhaps StdRdOptions)
* (iv) add it to the appropriate handling routine (perhaps
@@ -147,15 +147,6 @@ static relopt_bool boolRelOpts[] =
},
false
},
- {
- {
- "vacuum_truncate",
- "Enables vacuum to truncate empty pages at the end of this table",
- RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
- ShareUpdateExclusiveLock
- },
- true
- },
{
{
"deduplicate_items",
@@ -170,6 +161,21 @@ static relopt_bool boolRelOpts[] =
{{NULL}}
};
+static relopt_ternary ternaryRelOpts[] =
+{
+ {
+ {
+ "vacuum_truncate",
+ "Enables vacuum to truncate empty pages at the end of this table",
+ RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
+ ShareUpdateExclusiveLock
+ },
+ TERNARY_UNSET
+ },
+ /* list terminator */
+ {{NULL}}
+};
+
static relopt_int intRelOpts[] =
{
{
@@ -600,6 +606,13 @@ initialize_reloptions(void)
boolRelOpts[i].gen.lockmode));
j++;
}
+ for (i = 0; ternaryRelOpts[i].gen.name; i++)
+ {
+ Assert(DoLockModesConflict(ternaryRelOpts[i].gen.lockmode,
+ ternaryRelOpts[i].gen.lockmode));
+ j++;
+ }
+
for (i = 0; intRelOpts[i].gen.name; i++)
{
Assert(DoLockModesConflict(intRelOpts[i].gen.lockmode,
@@ -640,6 +653,14 @@ initialize_reloptions(void)
j++;
}
+ for (i = 0; ternaryRelOpts[i].gen.name; i++)
+ {
+ relOpts[j] = &ternaryRelOpts[i].gen;
+ relOpts[j]->type = RELOPT_TYPE_TERNARY;
+ relOpts[j]->namelen = strlen(relOpts[j]->name);
+ j++;
+ }
+
for (i = 0; intRelOpts[i].gen.name; i++)
{
relOpts[j] = &intRelOpts[i].gen;
@@ -800,6 +821,9 @@ allocate_reloption(bits32 kinds, int type, const char *name, const char *desc,
case RELOPT_TYPE_BOOL:
size = sizeof(relopt_bool);
break;
+ case RELOPT_TYPE_TERNARY:
+ size = sizeof(relopt_ternary);
+ break;
case RELOPT_TYPE_INT:
size = sizeof(relopt_int);
break;
@@ -883,6 +907,54 @@ add_local_bool_reloption(local_relopts *relopts, const char *name,
add_local_reloption(relopts, (relopt_gen *) newoption, offset);
}
+/*
+ * init_ternary_reloption
+ * Allocate and initialize a new ternary reloption
+ */
+static relopt_ternary *
+init_ternary_reloption(bits32 kinds, const char *name, const char *desc,
+ ternary default_val, LOCKMODE lockmode)
+{
+ relopt_ternary *newoption;
+
+ newoption = (relopt_ternary *) allocate_reloption(kinds,
+ RELOPT_TYPE_TERNARY, name, desc, lockmode);
+ newoption->default_val = default_val;
+
+ return newoption;
+}
+
+/*
+ * add_ternary_reloption
+ * Add a new ternary reloption
+ */
+void
+add_ternary_reloption(bits32 kinds, const char *name, const char *desc,
+ ternary default_val, LOCKMODE lockmode)
+{
+ relopt_ternary *newoption = init_ternary_reloption(kinds, name, desc,
+ default_val, lockmode);
+
+ add_reloption((relopt_gen *) newoption);
+}
+
+/*
+ * add_local_ternary_reloption
+ * Add a new ternary local reloption
+ *
+ * 'offset' is offset of ternary-typed field.
+ */
+void
+add_local_ternary_reloption(local_relopts *relopts, const char *name,
+ const char *desc, ternary default_val,
+ int offset)
+{
+ relopt_ternary *newoption = init_ternary_reloption(RELOPT_KIND_LOCAL,
+ name, desc,
+ default_val, 0);
+
+ add_local_reloption(relopts, (relopt_gen *) newoption, offset);
+}
/*
* init_real_reloption
@@ -1617,6 +1689,18 @@ parse_one_reloption(relopt_value *option, char *text_str, int text_len,
option->gen->name, value)));
}
break;
+ case RELOPT_TYPE_TERNARY:
+ {
+ bool b;
+ parsed = parse_bool(value, &b);
+ option->values.ternary_val = b ? TERNARY_TRUE : TERNARY_FALSE;
+ if (validate && !parsed)
+ ereport(ERROR,
+ (errcode(ERRCODE_INVALID_PARAMETER_VALUE),
+ errmsg("invalid value for ternary option \"%s\": %s",
+ option->gen->name, value)));
+ }
+ break;
case RELOPT_TYPE_INT:
{
relopt_int *optint = (relopt_int *) option->gen;
@@ -1780,17 +1864,6 @@ fillRelOptions(void *rdopts, Size basesize,
char *itempos = ((char *) rdopts) + elems[j].offset;
char *string_val;
- /*
- * If isset_offset is provided, store whether the reloption is
- * set there.
- */
- if (elems[j].isset_offset > 0)
- {
- char *setpos = ((char *) rdopts) + elems[j].isset_offset;
-
- *(bool *) setpos = options[i].isset;
- }
-
switch (options[i].gen->type)
{
case RELOPT_TYPE_BOOL:
@@ -1798,6 +1871,11 @@ fillRelOptions(void *rdopts, Size basesize,
options[i].values.bool_val :
((relopt_bool *) options[i].gen)->default_val;
break;
+ case RELOPT_TYPE_TERNARY:
+ *(ternary *) itempos = options[i].isset ?
+ options[i].values.ternary_val :
+ ((relopt_ternary *) options[i].gen)->default_val;
+ break;
case RELOPT_TYPE_INT:
*(int *) itempos = options[i].isset ?
options[i].values.int_val :
@@ -1912,8 +1990,8 @@ default_reloptions(Datum reloptions, bool validate, relopt_kind kind)
offsetof(StdRdOptions, parallel_workers)},
{"vacuum_index_cleanup", RELOPT_TYPE_ENUM,
offsetof(StdRdOptions, vacuum_index_cleanup)},
- {"vacuum_truncate", RELOPT_TYPE_BOOL,
- offsetof(StdRdOptions, vacuum_truncate), offsetof(StdRdOptions, vacuum_truncate_set)},
+ {"vacuum_truncate", RELOPT_TYPE_TERNARY,
+ offsetof(StdRdOptions, vacuum_truncate)},
{"vacuum_max_eager_freeze_failure_rate", RELOPT_TYPE_REAL,
offsetof(StdRdOptions, vacuum_max_eager_freeze_failure_rate)}
};
@@ -1993,7 +2071,6 @@ build_local_reloptions(local_relopts *relopts, Datum options, bool validate)
elems[i].optname = opt->option->name;
elems[i].opttype = opt->option->type;
elems[i].offset = opt->offset;
- elems[i].isset_offset = 0; /* not supported for local relopts yet */
i++;
}
diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c
index 733ef40ae7..977babff54 100644
--- a/src/backend/commands/vacuum.c
+++ b/src/backend/commands/vacuum.c
@@ -2221,9 +2221,9 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams params,
{
StdRdOptions *opts = (StdRdOptions *) rel->rd_options;
- if (opts && opts->vacuum_truncate_set)
+ if (opts && opts->vacuum_truncate != TERNARY_UNSET)
{
- if (opts->vacuum_truncate)
+ if (opts->vacuum_truncate == TERNARY_TRUE)
params.truncate = VACOPTVALUE_ENABLED;
else
params.truncate = VACOPTVALUE_DISABLED;
diff --git a/src/include/access/reloptions.h b/src/include/access/reloptions.h
index a604a4702c..a436697658 100644
--- a/src/include/access/reloptions.h
+++ b/src/include/access/reloptions.h
@@ -29,6 +29,7 @@
typedef enum relopt_type
{
RELOPT_TYPE_BOOL,
+ RELOPT_TYPE_TERNARY, /* on, off, unset */
RELOPT_TYPE_INT,
RELOPT_TYPE_REAL,
RELOPT_TYPE_ENUM,
@@ -80,6 +81,7 @@ typedef struct relopt_value
union
{
bool bool_val;
+ ternary ternary_val;
int int_val;
double real_val;
int enum_val;
@@ -94,6 +96,12 @@ typedef struct relopt_bool
bool default_val;
} relopt_bool;
+typedef struct relopt_rernary
+{
+ relopt_gen gen;
+ int default_val;
+} relopt_ternary;
+
typedef struct relopt_int
{
relopt_gen gen;
@@ -152,19 +160,6 @@ typedef struct
const char *optname; /* option's name */
relopt_type opttype; /* option's datatype */
int offset; /* offset of field in result struct */
-
- /*
- * isset_offset is an optional offset of a field in the result struct that
- * stores whether the option is explicitly set for the relation or if it
- * just picked up the default value. In most cases, this can be
- * accomplished by giving the reloption a special out-of-range default
- * value (e.g., some integer reloptions use -2), but this isn't always
- * possible. For example, a Boolean reloption cannot be given an
- * out-of-range default, so we need another way to discover the source of
- * its value. This offset is only used if given a value greater than
- * zero.
- */
- int isset_offset;
} relopt_parse_elt;
/* Local reloption definition */
@@ -195,6 +190,8 @@ typedef struct local_relopts
extern relopt_kind add_reloption_kind(void);
extern void add_bool_reloption(bits32 kinds, const char *name, const char *desc,
bool default_val, LOCKMODE lockmode);
+extern void add_ternary_reloption(bits32 kinds, const char *name,
+ const char *desc, int default_val, LOCKMODE lockmode);
extern void add_int_reloption(bits32 kinds, const char *name, const char *desc,
int default_val, int min_val, int max_val,
LOCKMODE lockmode);
@@ -214,6 +211,9 @@ extern void register_reloptions_validator(local_relopts *relopts,
extern void add_local_bool_reloption(local_relopts *relopts, const char *name,
const char *desc, bool default_val,
int offset);
+extern void add_local_ternary_reloption(local_relopts *relopts,
+ const char *name, const char *desc,
+ ternary default_val, int offset);
extern void add_local_int_reloption(local_relopts *relopts, const char *name,
const char *desc, int default_val,
int min_val, int max_val, int offset);
diff --git a/src/include/c.h b/src/include/c.h
index 39022f8a9d..9c59ccccf8 100644
--- a/src/include/c.h
+++ b/src/include/c.h
@@ -488,6 +488,22 @@ typedef void (*pg_funcptr_t) (void);
#include <stdbool.h>
+/*
+ * ternary
+ * Boolean value with an extrea "unset" option
+ *
+ * Ternary data type is used in relation options that can be "true", "false" or
+ * "unset". Since relation options are used deep inside the PostgreSQL code,
+ * this type is declared globally.
+*/
+
+typedef enum ternary
+{
+ TERNARY_FALSE = 0,
+ TERNARY_TRUE = 1,
+ TERNARY_UNSET = -1
+} ternary;
+
/* ----------------------------------------------------------------
* Section 3: standard system types
diff --git a/src/include/utils/rel.h b/src/include/utils/rel.h
index b552359915..7346d618f9 100644
--- a/src/include/utils/rel.h
+++ b/src/include/utils/rel.h
@@ -346,8 +346,7 @@ typedef struct StdRdOptions
bool user_catalog_table; /* use as an additional catalog relation */
int parallel_workers; /* max number of parallel workers */
StdRdOptIndexCleanup vacuum_index_cleanup; /* controls index vacuuming */
- bool vacuum_truncate; /* enables vacuum to truncate a relation */
- bool vacuum_truncate_set; /* whether vacuum_truncate is set */
+ ternary vacuum_truncate; /* enables vacuum to truncate a relation */
/*
* Fraction of pages in a relation that vacuum can eagerly scan and fail
--
2.39.2
--nextPart4155600.3ZeAukHxDK
Content-Disposition: attachment;
filename="v2a-0003-Add-alias-to-be-used-as-unset-state.patch"
Content-Transfer-Encoding: 7Bit
Content-Type: text/x-patch; charset="unicode-2-0-utf-8";
name="v2a-0003-Add-alias-to-be-used-as-unset-state.patch"
^ permalink raw reply [nested|flat] 868+ messages in thread
* [PATCH v2a 2/4] Introduce ternary reloptions
@ 2025-09-04 14:41 Nikolay Shaplov <[email protected]>
0 siblings, 0 replies; 868+ messages in thread
From: Nikolay Shaplov @ 2025-09-04 14:41 UTC (permalink / raw)
Introduce ternary reloption as a replacement for current `vacuum_truncate`
implementation. Remove `vacuum_truncate_set` additional flag and using
`TERNARY_UNSET` value instead.
---
src/backend/access/common/reloptions.c | 129 ++++++++++++++++++++-----
src/backend/commands/vacuum.c | 4 +-
src/include/access/reloptions.h | 26 ++---
src/include/c.h | 16 +++
src/include/utils/rel.h | 3 +-
5 files changed, 135 insertions(+), 43 deletions(-)
diff --git a/src/backend/access/common/reloptions.c b/src/backend/access/common/reloptions.c
index 0af3fea68f..f543f11001 100644
--- a/src/backend/access/common/reloptions.c
+++ b/src/backend/access/common/reloptions.c
@@ -40,9 +40,9 @@
*
* To add an option:
*
- * (i) decide on a type (bool, integer, real, enum, string), name, default
- * value, upper and lower bounds (if applicable); for strings, consider a
- * validation routine.
+ * (i) decide on a type (bool, ternary, integer, real, enum, string), name,
+ * default value, upper and lower bounds (if applicable); for strings,
+ * consider a validation routine.
* (ii) add a record below (or use add_<type>_reloption).
* (iii) add it to the appropriate options struct (perhaps StdRdOptions)
* (iv) add it to the appropriate handling routine (perhaps
@@ -147,15 +147,6 @@ static relopt_bool boolRelOpts[] =
},
false
},
- {
- {
- "vacuum_truncate",
- "Enables vacuum to truncate empty pages at the end of this table",
- RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
- ShareUpdateExclusiveLock
- },
- true
- },
{
{
"deduplicate_items",
@@ -170,6 +161,21 @@ static relopt_bool boolRelOpts[] =
{{NULL}}
};
+static relopt_ternary ternaryRelOpts[] =
+{
+ {
+ {
+ "vacuum_truncate",
+ "Enables vacuum to truncate empty pages at the end of this table",
+ RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
+ ShareUpdateExclusiveLock
+ },
+ TERNARY_UNSET
+ },
+ /* list terminator */
+ {{NULL}}
+};
+
static relopt_int intRelOpts[] =
{
{
@@ -600,6 +606,13 @@ initialize_reloptions(void)
boolRelOpts[i].gen.lockmode));
j++;
}
+ for (i = 0; ternaryRelOpts[i].gen.name; i++)
+ {
+ Assert(DoLockModesConflict(ternaryRelOpts[i].gen.lockmode,
+ ternaryRelOpts[i].gen.lockmode));
+ j++;
+ }
+
for (i = 0; intRelOpts[i].gen.name; i++)
{
Assert(DoLockModesConflict(intRelOpts[i].gen.lockmode,
@@ -640,6 +653,14 @@ initialize_reloptions(void)
j++;
}
+ for (i = 0; ternaryRelOpts[i].gen.name; i++)
+ {
+ relOpts[j] = &ternaryRelOpts[i].gen;
+ relOpts[j]->type = RELOPT_TYPE_TERNARY;
+ relOpts[j]->namelen = strlen(relOpts[j]->name);
+ j++;
+ }
+
for (i = 0; intRelOpts[i].gen.name; i++)
{
relOpts[j] = &intRelOpts[i].gen;
@@ -800,6 +821,9 @@ allocate_reloption(bits32 kinds, int type, const char *name, const char *desc,
case RELOPT_TYPE_BOOL:
size = sizeof(relopt_bool);
break;
+ case RELOPT_TYPE_TERNARY:
+ size = sizeof(relopt_ternary);
+ break;
case RELOPT_TYPE_INT:
size = sizeof(relopt_int);
break;
@@ -883,6 +907,54 @@ add_local_bool_reloption(local_relopts *relopts, const char *name,
add_local_reloption(relopts, (relopt_gen *) newoption, offset);
}
+/*
+ * init_ternary_reloption
+ * Allocate and initialize a new ternary reloption
+ */
+static relopt_ternary *
+init_ternary_reloption(bits32 kinds, const char *name, const char *desc,
+ ternary default_val, LOCKMODE lockmode)
+{
+ relopt_ternary *newoption;
+
+ newoption = (relopt_ternary *) allocate_reloption(kinds,
+ RELOPT_TYPE_TERNARY, name, desc, lockmode);
+ newoption->default_val = default_val;
+
+ return newoption;
+}
+
+/*
+ * add_ternary_reloption
+ * Add a new ternary reloption
+ */
+void
+add_ternary_reloption(bits32 kinds, const char *name, const char *desc,
+ ternary default_val, LOCKMODE lockmode)
+{
+ relopt_ternary *newoption = init_ternary_reloption(kinds, name, desc,
+ default_val, lockmode);
+
+ add_reloption((relopt_gen *) newoption);
+}
+
+/*
+ * add_local_ternary_reloption
+ * Add a new ternary local reloption
+ *
+ * 'offset' is offset of ternary-typed field.
+ */
+void
+add_local_ternary_reloption(local_relopts *relopts, const char *name,
+ const char *desc, ternary default_val,
+ int offset)
+{
+ relopt_ternary *newoption = init_ternary_reloption(RELOPT_KIND_LOCAL,
+ name, desc,
+ default_val, 0);
+
+ add_local_reloption(relopts, (relopt_gen *) newoption, offset);
+}
/*
* init_real_reloption
@@ -1617,6 +1689,18 @@ parse_one_reloption(relopt_value *option, char *text_str, int text_len,
option->gen->name, value)));
}
break;
+ case RELOPT_TYPE_TERNARY:
+ {
+ bool b;
+ parsed = parse_bool(value, &b);
+ option->values.ternary_val = b ? TERNARY_TRUE : TERNARY_FALSE;
+ if (validate && !parsed)
+ ereport(ERROR,
+ (errcode(ERRCODE_INVALID_PARAMETER_VALUE),
+ errmsg("invalid value for ternary option \"%s\": %s",
+ option->gen->name, value)));
+ }
+ break;
case RELOPT_TYPE_INT:
{
relopt_int *optint = (relopt_int *) option->gen;
@@ -1780,17 +1864,6 @@ fillRelOptions(void *rdopts, Size basesize,
char *itempos = ((char *) rdopts) + elems[j].offset;
char *string_val;
- /*
- * If isset_offset is provided, store whether the reloption is
- * set there.
- */
- if (elems[j].isset_offset > 0)
- {
- char *setpos = ((char *) rdopts) + elems[j].isset_offset;
-
- *(bool *) setpos = options[i].isset;
- }
-
switch (options[i].gen->type)
{
case RELOPT_TYPE_BOOL:
@@ -1798,6 +1871,11 @@ fillRelOptions(void *rdopts, Size basesize,
options[i].values.bool_val :
((relopt_bool *) options[i].gen)->default_val;
break;
+ case RELOPT_TYPE_TERNARY:
+ *(ternary *) itempos = options[i].isset ?
+ options[i].values.ternary_val :
+ ((relopt_ternary *) options[i].gen)->default_val;
+ break;
case RELOPT_TYPE_INT:
*(int *) itempos = options[i].isset ?
options[i].values.int_val :
@@ -1912,8 +1990,8 @@ default_reloptions(Datum reloptions, bool validate, relopt_kind kind)
offsetof(StdRdOptions, parallel_workers)},
{"vacuum_index_cleanup", RELOPT_TYPE_ENUM,
offsetof(StdRdOptions, vacuum_index_cleanup)},
- {"vacuum_truncate", RELOPT_TYPE_BOOL,
- offsetof(StdRdOptions, vacuum_truncate), offsetof(StdRdOptions, vacuum_truncate_set)},
+ {"vacuum_truncate", RELOPT_TYPE_TERNARY,
+ offsetof(StdRdOptions, vacuum_truncate)},
{"vacuum_max_eager_freeze_failure_rate", RELOPT_TYPE_REAL,
offsetof(StdRdOptions, vacuum_max_eager_freeze_failure_rate)}
};
@@ -1993,7 +2071,6 @@ build_local_reloptions(local_relopts *relopts, Datum options, bool validate)
elems[i].optname = opt->option->name;
elems[i].opttype = opt->option->type;
elems[i].offset = opt->offset;
- elems[i].isset_offset = 0; /* not supported for local relopts yet */
i++;
}
diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c
index 733ef40ae7..977babff54 100644
--- a/src/backend/commands/vacuum.c
+++ b/src/backend/commands/vacuum.c
@@ -2221,9 +2221,9 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams params,
{
StdRdOptions *opts = (StdRdOptions *) rel->rd_options;
- if (opts && opts->vacuum_truncate_set)
+ if (opts && opts->vacuum_truncate != TERNARY_UNSET)
{
- if (opts->vacuum_truncate)
+ if (opts->vacuum_truncate == TERNARY_TRUE)
params.truncate = VACOPTVALUE_ENABLED;
else
params.truncate = VACOPTVALUE_DISABLED;
diff --git a/src/include/access/reloptions.h b/src/include/access/reloptions.h
index a604a4702c..a436697658 100644
--- a/src/include/access/reloptions.h
+++ b/src/include/access/reloptions.h
@@ -29,6 +29,7 @@
typedef enum relopt_type
{
RELOPT_TYPE_BOOL,
+ RELOPT_TYPE_TERNARY, /* on, off, unset */
RELOPT_TYPE_INT,
RELOPT_TYPE_REAL,
RELOPT_TYPE_ENUM,
@@ -80,6 +81,7 @@ typedef struct relopt_value
union
{
bool bool_val;
+ ternary ternary_val;
int int_val;
double real_val;
int enum_val;
@@ -94,6 +96,12 @@ typedef struct relopt_bool
bool default_val;
} relopt_bool;
+typedef struct relopt_rernary
+{
+ relopt_gen gen;
+ int default_val;
+} relopt_ternary;
+
typedef struct relopt_int
{
relopt_gen gen;
@@ -152,19 +160,6 @@ typedef struct
const char *optname; /* option's name */
relopt_type opttype; /* option's datatype */
int offset; /* offset of field in result struct */
-
- /*
- * isset_offset is an optional offset of a field in the result struct that
- * stores whether the option is explicitly set for the relation or if it
- * just picked up the default value. In most cases, this can be
- * accomplished by giving the reloption a special out-of-range default
- * value (e.g., some integer reloptions use -2), but this isn't always
- * possible. For example, a Boolean reloption cannot be given an
- * out-of-range default, so we need another way to discover the source of
- * its value. This offset is only used if given a value greater than
- * zero.
- */
- int isset_offset;
} relopt_parse_elt;
/* Local reloption definition */
@@ -195,6 +190,8 @@ typedef struct local_relopts
extern relopt_kind add_reloption_kind(void);
extern void add_bool_reloption(bits32 kinds, const char *name, const char *desc,
bool default_val, LOCKMODE lockmode);
+extern void add_ternary_reloption(bits32 kinds, const char *name,
+ const char *desc, int default_val, LOCKMODE lockmode);
extern void add_int_reloption(bits32 kinds, const char *name, const char *desc,
int default_val, int min_val, int max_val,
LOCKMODE lockmode);
@@ -214,6 +211,9 @@ extern void register_reloptions_validator(local_relopts *relopts,
extern void add_local_bool_reloption(local_relopts *relopts, const char *name,
const char *desc, bool default_val,
int offset);
+extern void add_local_ternary_reloption(local_relopts *relopts,
+ const char *name, const char *desc,
+ ternary default_val, int offset);
extern void add_local_int_reloption(local_relopts *relopts, const char *name,
const char *desc, int default_val,
int min_val, int max_val, int offset);
diff --git a/src/include/c.h b/src/include/c.h
index 39022f8a9d..9c59ccccf8 100644
--- a/src/include/c.h
+++ b/src/include/c.h
@@ -488,6 +488,22 @@ typedef void (*pg_funcptr_t) (void);
#include <stdbool.h>
+/*
+ * ternary
+ * Boolean value with an extrea "unset" option
+ *
+ * Ternary data type is used in relation options that can be "true", "false" or
+ * "unset". Since relation options are used deep inside the PostgreSQL code,
+ * this type is declared globally.
+*/
+
+typedef enum ternary
+{
+ TERNARY_FALSE = 0,
+ TERNARY_TRUE = 1,
+ TERNARY_UNSET = -1
+} ternary;
+
/* ----------------------------------------------------------------
* Section 3: standard system types
diff --git a/src/include/utils/rel.h b/src/include/utils/rel.h
index b552359915..7346d618f9 100644
--- a/src/include/utils/rel.h
+++ b/src/include/utils/rel.h
@@ -346,8 +346,7 @@ typedef struct StdRdOptions
bool user_catalog_table; /* use as an additional catalog relation */
int parallel_workers; /* max number of parallel workers */
StdRdOptIndexCleanup vacuum_index_cleanup; /* controls index vacuuming */
- bool vacuum_truncate; /* enables vacuum to truncate a relation */
- bool vacuum_truncate_set; /* whether vacuum_truncate is set */
+ ternary vacuum_truncate; /* enables vacuum to truncate a relation */
/*
* Fraction of pages in a relation that vacuum can eagerly scan and fail
--
2.39.2
--nextPart4155600.3ZeAukHxDK
Content-Disposition: attachment;
filename="v2a-0003-Add-alias-to-be-used-as-unset-state.patch"
Content-Transfer-Encoding: 7Bit
Content-Type: text/x-patch; charset="unicode-2-0-utf-8";
name="v2a-0003-Add-alias-to-be-used-as-unset-state.patch"
^ permalink raw reply [nested|flat] 868+ messages in thread
* [PATCH v2 2/4] Introduce ternary reloptions
@ 2025-09-04 14:41 Nikolay Shaplov <[email protected]>
0 siblings, 0 replies; 868+ messages in thread
From: Nikolay Shaplov @ 2025-09-04 14:41 UTC (permalink / raw)
Introduce ternary reloption as a replacement for current `vacuum_truncate`
implementation. Remove `vacuum_truncate_set` additional flag and using
`TERNARY_UNSET` value instead.
---
src/backend/access/common/reloptions.c | 129 ++++++++++++++++++++-----
src/backend/commands/vacuum.c | 4 +-
src/include/access/reloptions.h | 26 ++---
src/include/c.h | 16 +++
src/include/utils/rel.h | 3 +-
5 files changed, 135 insertions(+), 43 deletions(-)
diff --git a/src/backend/access/common/reloptions.c b/src/backend/access/common/reloptions.c
index 0af3fea68f..f543f11001 100644
--- a/src/backend/access/common/reloptions.c
+++ b/src/backend/access/common/reloptions.c
@@ -40,9 +40,9 @@
*
* To add an option:
*
- * (i) decide on a type (bool, integer, real, enum, string), name, default
- * value, upper and lower bounds (if applicable); for strings, consider a
- * validation routine.
+ * (i) decide on a type (bool, ternary, integer, real, enum, string), name,
+ * default value, upper and lower bounds (if applicable); for strings,
+ * consider a validation routine.
* (ii) add a record below (or use add_<type>_reloption).
* (iii) add it to the appropriate options struct (perhaps StdRdOptions)
* (iv) add it to the appropriate handling routine (perhaps
@@ -147,15 +147,6 @@ static relopt_bool boolRelOpts[] =
},
false
},
- {
- {
- "vacuum_truncate",
- "Enables vacuum to truncate empty pages at the end of this table",
- RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
- ShareUpdateExclusiveLock
- },
- true
- },
{
{
"deduplicate_items",
@@ -170,6 +161,21 @@ static relopt_bool boolRelOpts[] =
{{NULL}}
};
+static relopt_ternary ternaryRelOpts[] =
+{
+ {
+ {
+ "vacuum_truncate",
+ "Enables vacuum to truncate empty pages at the end of this table",
+ RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
+ ShareUpdateExclusiveLock
+ },
+ TERNARY_UNSET
+ },
+ /* list terminator */
+ {{NULL}}
+};
+
static relopt_int intRelOpts[] =
{
{
@@ -600,6 +606,13 @@ initialize_reloptions(void)
boolRelOpts[i].gen.lockmode));
j++;
}
+ for (i = 0; ternaryRelOpts[i].gen.name; i++)
+ {
+ Assert(DoLockModesConflict(ternaryRelOpts[i].gen.lockmode,
+ ternaryRelOpts[i].gen.lockmode));
+ j++;
+ }
+
for (i = 0; intRelOpts[i].gen.name; i++)
{
Assert(DoLockModesConflict(intRelOpts[i].gen.lockmode,
@@ -640,6 +653,14 @@ initialize_reloptions(void)
j++;
}
+ for (i = 0; ternaryRelOpts[i].gen.name; i++)
+ {
+ relOpts[j] = &ternaryRelOpts[i].gen;
+ relOpts[j]->type = RELOPT_TYPE_TERNARY;
+ relOpts[j]->namelen = strlen(relOpts[j]->name);
+ j++;
+ }
+
for (i = 0; intRelOpts[i].gen.name; i++)
{
relOpts[j] = &intRelOpts[i].gen;
@@ -800,6 +821,9 @@ allocate_reloption(bits32 kinds, int type, const char *name, const char *desc,
case RELOPT_TYPE_BOOL:
size = sizeof(relopt_bool);
break;
+ case RELOPT_TYPE_TERNARY:
+ size = sizeof(relopt_ternary);
+ break;
case RELOPT_TYPE_INT:
size = sizeof(relopt_int);
break;
@@ -883,6 +907,54 @@ add_local_bool_reloption(local_relopts *relopts, const char *name,
add_local_reloption(relopts, (relopt_gen *) newoption, offset);
}
+/*
+ * init_ternary_reloption
+ * Allocate and initialize a new ternary reloption
+ */
+static relopt_ternary *
+init_ternary_reloption(bits32 kinds, const char *name, const char *desc,
+ ternary default_val, LOCKMODE lockmode)
+{
+ relopt_ternary *newoption;
+
+ newoption = (relopt_ternary *) allocate_reloption(kinds,
+ RELOPT_TYPE_TERNARY, name, desc, lockmode);
+ newoption->default_val = default_val;
+
+ return newoption;
+}
+
+/*
+ * add_ternary_reloption
+ * Add a new ternary reloption
+ */
+void
+add_ternary_reloption(bits32 kinds, const char *name, const char *desc,
+ ternary default_val, LOCKMODE lockmode)
+{
+ relopt_ternary *newoption = init_ternary_reloption(kinds, name, desc,
+ default_val, lockmode);
+
+ add_reloption((relopt_gen *) newoption);
+}
+
+/*
+ * add_local_ternary_reloption
+ * Add a new ternary local reloption
+ *
+ * 'offset' is offset of ternary-typed field.
+ */
+void
+add_local_ternary_reloption(local_relopts *relopts, const char *name,
+ const char *desc, ternary default_val,
+ int offset)
+{
+ relopt_ternary *newoption = init_ternary_reloption(RELOPT_KIND_LOCAL,
+ name, desc,
+ default_val, 0);
+
+ add_local_reloption(relopts, (relopt_gen *) newoption, offset);
+}
/*
* init_real_reloption
@@ -1617,6 +1689,18 @@ parse_one_reloption(relopt_value *option, char *text_str, int text_len,
option->gen->name, value)));
}
break;
+ case RELOPT_TYPE_TERNARY:
+ {
+ bool b;
+ parsed = parse_bool(value, &b);
+ option->values.ternary_val = b ? TERNARY_TRUE : TERNARY_FALSE;
+ if (validate && !parsed)
+ ereport(ERROR,
+ (errcode(ERRCODE_INVALID_PARAMETER_VALUE),
+ errmsg("invalid value for ternary option \"%s\": %s",
+ option->gen->name, value)));
+ }
+ break;
case RELOPT_TYPE_INT:
{
relopt_int *optint = (relopt_int *) option->gen;
@@ -1780,17 +1864,6 @@ fillRelOptions(void *rdopts, Size basesize,
char *itempos = ((char *) rdopts) + elems[j].offset;
char *string_val;
- /*
- * If isset_offset is provided, store whether the reloption is
- * set there.
- */
- if (elems[j].isset_offset > 0)
- {
- char *setpos = ((char *) rdopts) + elems[j].isset_offset;
-
- *(bool *) setpos = options[i].isset;
- }
-
switch (options[i].gen->type)
{
case RELOPT_TYPE_BOOL:
@@ -1798,6 +1871,11 @@ fillRelOptions(void *rdopts, Size basesize,
options[i].values.bool_val :
((relopt_bool *) options[i].gen)->default_val;
break;
+ case RELOPT_TYPE_TERNARY:
+ *(ternary *) itempos = options[i].isset ?
+ options[i].values.ternary_val :
+ ((relopt_ternary *) options[i].gen)->default_val;
+ break;
case RELOPT_TYPE_INT:
*(int *) itempos = options[i].isset ?
options[i].values.int_val :
@@ -1912,8 +1990,8 @@ default_reloptions(Datum reloptions, bool validate, relopt_kind kind)
offsetof(StdRdOptions, parallel_workers)},
{"vacuum_index_cleanup", RELOPT_TYPE_ENUM,
offsetof(StdRdOptions, vacuum_index_cleanup)},
- {"vacuum_truncate", RELOPT_TYPE_BOOL,
- offsetof(StdRdOptions, vacuum_truncate), offsetof(StdRdOptions, vacuum_truncate_set)},
+ {"vacuum_truncate", RELOPT_TYPE_TERNARY,
+ offsetof(StdRdOptions, vacuum_truncate)},
{"vacuum_max_eager_freeze_failure_rate", RELOPT_TYPE_REAL,
offsetof(StdRdOptions, vacuum_max_eager_freeze_failure_rate)}
};
@@ -1993,7 +2071,6 @@ build_local_reloptions(local_relopts *relopts, Datum options, bool validate)
elems[i].optname = opt->option->name;
elems[i].opttype = opt->option->type;
elems[i].offset = opt->offset;
- elems[i].isset_offset = 0; /* not supported for local relopts yet */
i++;
}
diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c
index 733ef40ae7..977babff54 100644
--- a/src/backend/commands/vacuum.c
+++ b/src/backend/commands/vacuum.c
@@ -2221,9 +2221,9 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams params,
{
StdRdOptions *opts = (StdRdOptions *) rel->rd_options;
- if (opts && opts->vacuum_truncate_set)
+ if (opts && opts->vacuum_truncate != TERNARY_UNSET)
{
- if (opts->vacuum_truncate)
+ if (opts->vacuum_truncate == TERNARY_TRUE)
params.truncate = VACOPTVALUE_ENABLED;
else
params.truncate = VACOPTVALUE_DISABLED;
diff --git a/src/include/access/reloptions.h b/src/include/access/reloptions.h
index a604a4702c..a436697658 100644
--- a/src/include/access/reloptions.h
+++ b/src/include/access/reloptions.h
@@ -29,6 +29,7 @@
typedef enum relopt_type
{
RELOPT_TYPE_BOOL,
+ RELOPT_TYPE_TERNARY, /* on, off, unset */
RELOPT_TYPE_INT,
RELOPT_TYPE_REAL,
RELOPT_TYPE_ENUM,
@@ -80,6 +81,7 @@ typedef struct relopt_value
union
{
bool bool_val;
+ ternary ternary_val;
int int_val;
double real_val;
int enum_val;
@@ -94,6 +96,12 @@ typedef struct relopt_bool
bool default_val;
} relopt_bool;
+typedef struct relopt_rernary
+{
+ relopt_gen gen;
+ int default_val;
+} relopt_ternary;
+
typedef struct relopt_int
{
relopt_gen gen;
@@ -152,19 +160,6 @@ typedef struct
const char *optname; /* option's name */
relopt_type opttype; /* option's datatype */
int offset; /* offset of field in result struct */
-
- /*
- * isset_offset is an optional offset of a field in the result struct that
- * stores whether the option is explicitly set for the relation or if it
- * just picked up the default value. In most cases, this can be
- * accomplished by giving the reloption a special out-of-range default
- * value (e.g., some integer reloptions use -2), but this isn't always
- * possible. For example, a Boolean reloption cannot be given an
- * out-of-range default, so we need another way to discover the source of
- * its value. This offset is only used if given a value greater than
- * zero.
- */
- int isset_offset;
} relopt_parse_elt;
/* Local reloption definition */
@@ -195,6 +190,8 @@ typedef struct local_relopts
extern relopt_kind add_reloption_kind(void);
extern void add_bool_reloption(bits32 kinds, const char *name, const char *desc,
bool default_val, LOCKMODE lockmode);
+extern void add_ternary_reloption(bits32 kinds, const char *name,
+ const char *desc, int default_val, LOCKMODE lockmode);
extern void add_int_reloption(bits32 kinds, const char *name, const char *desc,
int default_val, int min_val, int max_val,
LOCKMODE lockmode);
@@ -214,6 +211,9 @@ extern void register_reloptions_validator(local_relopts *relopts,
extern void add_local_bool_reloption(local_relopts *relopts, const char *name,
const char *desc, bool default_val,
int offset);
+extern void add_local_ternary_reloption(local_relopts *relopts,
+ const char *name, const char *desc,
+ ternary default_val, int offset);
extern void add_local_int_reloption(local_relopts *relopts, const char *name,
const char *desc, int default_val,
int min_val, int max_val, int offset);
diff --git a/src/include/c.h b/src/include/c.h
index 39022f8a9d..9c59ccccf8 100644
--- a/src/include/c.h
+++ b/src/include/c.h
@@ -488,6 +488,22 @@ typedef void (*pg_funcptr_t) (void);
#include <stdbool.h>
+/*
+ * ternary
+ * Boolean value with an extrea "unset" option
+ *
+ * Ternary data type is used in relation options that can be "true", "false" or
+ * "unset". Since relation options are used deep inside the PostgreSQL code,
+ * this type is declared globally.
+*/
+
+typedef enum ternary
+{
+ TERNARY_FALSE = 0,
+ TERNARY_TRUE = 1,
+ TERNARY_UNSET = -1
+} ternary;
+
/* ----------------------------------------------------------------
* Section 3: standard system types
diff --git a/src/include/utils/rel.h b/src/include/utils/rel.h
index b552359915..7346d618f9 100644
--- a/src/include/utils/rel.h
+++ b/src/include/utils/rel.h
@@ -346,8 +346,7 @@ typedef struct StdRdOptions
bool user_catalog_table; /* use as an additional catalog relation */
int parallel_workers; /* max number of parallel workers */
StdRdOptIndexCleanup vacuum_index_cleanup; /* controls index vacuuming */
- bool vacuum_truncate; /* enables vacuum to truncate a relation */
- bool vacuum_truncate_set; /* whether vacuum_truncate is set */
+ ternary vacuum_truncate; /* enables vacuum to truncate a relation */
/*
* Fraction of pages in a relation that vacuum can eagerly scan and fail
--
2.39.2
--nextPart5129932.5fSG56mABF
Content-Disposition: attachment;
filename="v2-0003-Add-alias-to-be-used-as-unset-state.patch"
Content-Transfer-Encoding: 7Bit
Content-Type: text/x-patch; charset="unicode-2-0-utf-8";
name="v2-0003-Add-alias-to-be-used-as-unset-state.patch"
^ permalink raw reply [nested|flat] 868+ messages in thread
* [PATCH v2 2/4] Introduce ternary reloptions
@ 2025-09-04 14:41 Nikolay Shaplov <[email protected]>
0 siblings, 0 replies; 868+ messages in thread
From: Nikolay Shaplov @ 2025-09-04 14:41 UTC (permalink / raw)
Introduce ternary reloption as a replacement for current `vacuum_truncate`
implementation. Remove `vacuum_truncate_set` additional flag and using
`TERNARY_UNSET` value instead.
---
src/backend/access/common/reloptions.c | 129 ++++++++++++++++++++-----
src/backend/commands/vacuum.c | 4 +-
src/include/access/reloptions.h | 26 ++---
src/include/c.h | 16 +++
src/include/utils/rel.h | 3 +-
5 files changed, 135 insertions(+), 43 deletions(-)
diff --git a/src/backend/access/common/reloptions.c b/src/backend/access/common/reloptions.c
index 0af3fea68f..f543f11001 100644
--- a/src/backend/access/common/reloptions.c
+++ b/src/backend/access/common/reloptions.c
@@ -40,9 +40,9 @@
*
* To add an option:
*
- * (i) decide on a type (bool, integer, real, enum, string), name, default
- * value, upper and lower bounds (if applicable); for strings, consider a
- * validation routine.
+ * (i) decide on a type (bool, ternary, integer, real, enum, string), name,
+ * default value, upper and lower bounds (if applicable); for strings,
+ * consider a validation routine.
* (ii) add a record below (or use add_<type>_reloption).
* (iii) add it to the appropriate options struct (perhaps StdRdOptions)
* (iv) add it to the appropriate handling routine (perhaps
@@ -147,15 +147,6 @@ static relopt_bool boolRelOpts[] =
},
false
},
- {
- {
- "vacuum_truncate",
- "Enables vacuum to truncate empty pages at the end of this table",
- RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
- ShareUpdateExclusiveLock
- },
- true
- },
{
{
"deduplicate_items",
@@ -170,6 +161,21 @@ static relopt_bool boolRelOpts[] =
{{NULL}}
};
+static relopt_ternary ternaryRelOpts[] =
+{
+ {
+ {
+ "vacuum_truncate",
+ "Enables vacuum to truncate empty pages at the end of this table",
+ RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
+ ShareUpdateExclusiveLock
+ },
+ TERNARY_UNSET
+ },
+ /* list terminator */
+ {{NULL}}
+};
+
static relopt_int intRelOpts[] =
{
{
@@ -600,6 +606,13 @@ initialize_reloptions(void)
boolRelOpts[i].gen.lockmode));
j++;
}
+ for (i = 0; ternaryRelOpts[i].gen.name; i++)
+ {
+ Assert(DoLockModesConflict(ternaryRelOpts[i].gen.lockmode,
+ ternaryRelOpts[i].gen.lockmode));
+ j++;
+ }
+
for (i = 0; intRelOpts[i].gen.name; i++)
{
Assert(DoLockModesConflict(intRelOpts[i].gen.lockmode,
@@ -640,6 +653,14 @@ initialize_reloptions(void)
j++;
}
+ for (i = 0; ternaryRelOpts[i].gen.name; i++)
+ {
+ relOpts[j] = &ternaryRelOpts[i].gen;
+ relOpts[j]->type = RELOPT_TYPE_TERNARY;
+ relOpts[j]->namelen = strlen(relOpts[j]->name);
+ j++;
+ }
+
for (i = 0; intRelOpts[i].gen.name; i++)
{
relOpts[j] = &intRelOpts[i].gen;
@@ -800,6 +821,9 @@ allocate_reloption(bits32 kinds, int type, const char *name, const char *desc,
case RELOPT_TYPE_BOOL:
size = sizeof(relopt_bool);
break;
+ case RELOPT_TYPE_TERNARY:
+ size = sizeof(relopt_ternary);
+ break;
case RELOPT_TYPE_INT:
size = sizeof(relopt_int);
break;
@@ -883,6 +907,54 @@ add_local_bool_reloption(local_relopts *relopts, const char *name,
add_local_reloption(relopts, (relopt_gen *) newoption, offset);
}
+/*
+ * init_ternary_reloption
+ * Allocate and initialize a new ternary reloption
+ */
+static relopt_ternary *
+init_ternary_reloption(bits32 kinds, const char *name, const char *desc,
+ ternary default_val, LOCKMODE lockmode)
+{
+ relopt_ternary *newoption;
+
+ newoption = (relopt_ternary *) allocate_reloption(kinds,
+ RELOPT_TYPE_TERNARY, name, desc, lockmode);
+ newoption->default_val = default_val;
+
+ return newoption;
+}
+
+/*
+ * add_ternary_reloption
+ * Add a new ternary reloption
+ */
+void
+add_ternary_reloption(bits32 kinds, const char *name, const char *desc,
+ ternary default_val, LOCKMODE lockmode)
+{
+ relopt_ternary *newoption = init_ternary_reloption(kinds, name, desc,
+ default_val, lockmode);
+
+ add_reloption((relopt_gen *) newoption);
+}
+
+/*
+ * add_local_ternary_reloption
+ * Add a new ternary local reloption
+ *
+ * 'offset' is offset of ternary-typed field.
+ */
+void
+add_local_ternary_reloption(local_relopts *relopts, const char *name,
+ const char *desc, ternary default_val,
+ int offset)
+{
+ relopt_ternary *newoption = init_ternary_reloption(RELOPT_KIND_LOCAL,
+ name, desc,
+ default_val, 0);
+
+ add_local_reloption(relopts, (relopt_gen *) newoption, offset);
+}
/*
* init_real_reloption
@@ -1617,6 +1689,18 @@ parse_one_reloption(relopt_value *option, char *text_str, int text_len,
option->gen->name, value)));
}
break;
+ case RELOPT_TYPE_TERNARY:
+ {
+ bool b;
+ parsed = parse_bool(value, &b);
+ option->values.ternary_val = b ? TERNARY_TRUE : TERNARY_FALSE;
+ if (validate && !parsed)
+ ereport(ERROR,
+ (errcode(ERRCODE_INVALID_PARAMETER_VALUE),
+ errmsg("invalid value for ternary option \"%s\": %s",
+ option->gen->name, value)));
+ }
+ break;
case RELOPT_TYPE_INT:
{
relopt_int *optint = (relopt_int *) option->gen;
@@ -1780,17 +1864,6 @@ fillRelOptions(void *rdopts, Size basesize,
char *itempos = ((char *) rdopts) + elems[j].offset;
char *string_val;
- /*
- * If isset_offset is provided, store whether the reloption is
- * set there.
- */
- if (elems[j].isset_offset > 0)
- {
- char *setpos = ((char *) rdopts) + elems[j].isset_offset;
-
- *(bool *) setpos = options[i].isset;
- }
-
switch (options[i].gen->type)
{
case RELOPT_TYPE_BOOL:
@@ -1798,6 +1871,11 @@ fillRelOptions(void *rdopts, Size basesize,
options[i].values.bool_val :
((relopt_bool *) options[i].gen)->default_val;
break;
+ case RELOPT_TYPE_TERNARY:
+ *(ternary *) itempos = options[i].isset ?
+ options[i].values.ternary_val :
+ ((relopt_ternary *) options[i].gen)->default_val;
+ break;
case RELOPT_TYPE_INT:
*(int *) itempos = options[i].isset ?
options[i].values.int_val :
@@ -1912,8 +1990,8 @@ default_reloptions(Datum reloptions, bool validate, relopt_kind kind)
offsetof(StdRdOptions, parallel_workers)},
{"vacuum_index_cleanup", RELOPT_TYPE_ENUM,
offsetof(StdRdOptions, vacuum_index_cleanup)},
- {"vacuum_truncate", RELOPT_TYPE_BOOL,
- offsetof(StdRdOptions, vacuum_truncate), offsetof(StdRdOptions, vacuum_truncate_set)},
+ {"vacuum_truncate", RELOPT_TYPE_TERNARY,
+ offsetof(StdRdOptions, vacuum_truncate)},
{"vacuum_max_eager_freeze_failure_rate", RELOPT_TYPE_REAL,
offsetof(StdRdOptions, vacuum_max_eager_freeze_failure_rate)}
};
@@ -1993,7 +2071,6 @@ build_local_reloptions(local_relopts *relopts, Datum options, bool validate)
elems[i].optname = opt->option->name;
elems[i].opttype = opt->option->type;
elems[i].offset = opt->offset;
- elems[i].isset_offset = 0; /* not supported for local relopts yet */
i++;
}
diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c
index 733ef40ae7..977babff54 100644
--- a/src/backend/commands/vacuum.c
+++ b/src/backend/commands/vacuum.c
@@ -2221,9 +2221,9 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams params,
{
StdRdOptions *opts = (StdRdOptions *) rel->rd_options;
- if (opts && opts->vacuum_truncate_set)
+ if (opts && opts->vacuum_truncate != TERNARY_UNSET)
{
- if (opts->vacuum_truncate)
+ if (opts->vacuum_truncate == TERNARY_TRUE)
params.truncate = VACOPTVALUE_ENABLED;
else
params.truncate = VACOPTVALUE_DISABLED;
diff --git a/src/include/access/reloptions.h b/src/include/access/reloptions.h
index a604a4702c..a436697658 100644
--- a/src/include/access/reloptions.h
+++ b/src/include/access/reloptions.h
@@ -29,6 +29,7 @@
typedef enum relopt_type
{
RELOPT_TYPE_BOOL,
+ RELOPT_TYPE_TERNARY, /* on, off, unset */
RELOPT_TYPE_INT,
RELOPT_TYPE_REAL,
RELOPT_TYPE_ENUM,
@@ -80,6 +81,7 @@ typedef struct relopt_value
union
{
bool bool_val;
+ ternary ternary_val;
int int_val;
double real_val;
int enum_val;
@@ -94,6 +96,12 @@ typedef struct relopt_bool
bool default_val;
} relopt_bool;
+typedef struct relopt_rernary
+{
+ relopt_gen gen;
+ int default_val;
+} relopt_ternary;
+
typedef struct relopt_int
{
relopt_gen gen;
@@ -152,19 +160,6 @@ typedef struct
const char *optname; /* option's name */
relopt_type opttype; /* option's datatype */
int offset; /* offset of field in result struct */
-
- /*
- * isset_offset is an optional offset of a field in the result struct that
- * stores whether the option is explicitly set for the relation or if it
- * just picked up the default value. In most cases, this can be
- * accomplished by giving the reloption a special out-of-range default
- * value (e.g., some integer reloptions use -2), but this isn't always
- * possible. For example, a Boolean reloption cannot be given an
- * out-of-range default, so we need another way to discover the source of
- * its value. This offset is only used if given a value greater than
- * zero.
- */
- int isset_offset;
} relopt_parse_elt;
/* Local reloption definition */
@@ -195,6 +190,8 @@ typedef struct local_relopts
extern relopt_kind add_reloption_kind(void);
extern void add_bool_reloption(bits32 kinds, const char *name, const char *desc,
bool default_val, LOCKMODE lockmode);
+extern void add_ternary_reloption(bits32 kinds, const char *name,
+ const char *desc, int default_val, LOCKMODE lockmode);
extern void add_int_reloption(bits32 kinds, const char *name, const char *desc,
int default_val, int min_val, int max_val,
LOCKMODE lockmode);
@@ -214,6 +211,9 @@ extern void register_reloptions_validator(local_relopts *relopts,
extern void add_local_bool_reloption(local_relopts *relopts, const char *name,
const char *desc, bool default_val,
int offset);
+extern void add_local_ternary_reloption(local_relopts *relopts,
+ const char *name, const char *desc,
+ ternary default_val, int offset);
extern void add_local_int_reloption(local_relopts *relopts, const char *name,
const char *desc, int default_val,
int min_val, int max_val, int offset);
diff --git a/src/include/c.h b/src/include/c.h
index 39022f8a9d..9c59ccccf8 100644
--- a/src/include/c.h
+++ b/src/include/c.h
@@ -488,6 +488,22 @@ typedef void (*pg_funcptr_t) (void);
#include <stdbool.h>
+/*
+ * ternary
+ * Boolean value with an extrea "unset" option
+ *
+ * Ternary data type is used in relation options that can be "true", "false" or
+ * "unset". Since relation options are used deep inside the PostgreSQL code,
+ * this type is declared globally.
+*/
+
+typedef enum ternary
+{
+ TERNARY_FALSE = 0,
+ TERNARY_TRUE = 1,
+ TERNARY_UNSET = -1
+} ternary;
+
/* ----------------------------------------------------------------
* Section 3: standard system types
diff --git a/src/include/utils/rel.h b/src/include/utils/rel.h
index b552359915..7346d618f9 100644
--- a/src/include/utils/rel.h
+++ b/src/include/utils/rel.h
@@ -346,8 +346,7 @@ typedef struct StdRdOptions
bool user_catalog_table; /* use as an additional catalog relation */
int parallel_workers; /* max number of parallel workers */
StdRdOptIndexCleanup vacuum_index_cleanup; /* controls index vacuuming */
- bool vacuum_truncate; /* enables vacuum to truncate a relation */
- bool vacuum_truncate_set; /* whether vacuum_truncate is set */
+ ternary vacuum_truncate; /* enables vacuum to truncate a relation */
/*
* Fraction of pages in a relation that vacuum can eagerly scan and fail
--
2.39.2
--nextPart5129932.5fSG56mABF
Content-Disposition: attachment;
filename="v2-0003-Add-alias-to-be-used-as-unset-state.patch"
Content-Transfer-Encoding: 7Bit
Content-Type: text/x-patch; charset="unicode-2-0-utf-8";
name="v2-0003-Add-alias-to-be-used-as-unset-state.patch"
^ permalink raw reply [nested|flat] 868+ messages in thread
* [PATCH v2 2/4] Introduce ternary reloptions
@ 2025-09-04 14:41 Nikolay Shaplov <[email protected]>
0 siblings, 0 replies; 868+ messages in thread
From: Nikolay Shaplov @ 2025-09-04 14:41 UTC (permalink / raw)
Introduce ternary reloption as a replacement for current `vacuum_truncate`
implementation. Remove `vacuum_truncate_set` additional flag and using
`TERNARY_UNSET` value instead.
---
src/backend/access/common/reloptions.c | 129 ++++++++++++++++++++-----
src/backend/commands/vacuum.c | 4 +-
src/include/access/reloptions.h | 26 ++---
src/include/c.h | 16 +++
src/include/utils/rel.h | 3 +-
5 files changed, 135 insertions(+), 43 deletions(-)
diff --git a/src/backend/access/common/reloptions.c b/src/backend/access/common/reloptions.c
index 0af3fea68f..f543f11001 100644
--- a/src/backend/access/common/reloptions.c
+++ b/src/backend/access/common/reloptions.c
@@ -40,9 +40,9 @@
*
* To add an option:
*
- * (i) decide on a type (bool, integer, real, enum, string), name, default
- * value, upper and lower bounds (if applicable); for strings, consider a
- * validation routine.
+ * (i) decide on a type (bool, ternary, integer, real, enum, string), name,
+ * default value, upper and lower bounds (if applicable); for strings,
+ * consider a validation routine.
* (ii) add a record below (or use add_<type>_reloption).
* (iii) add it to the appropriate options struct (perhaps StdRdOptions)
* (iv) add it to the appropriate handling routine (perhaps
@@ -147,15 +147,6 @@ static relopt_bool boolRelOpts[] =
},
false
},
- {
- {
- "vacuum_truncate",
- "Enables vacuum to truncate empty pages at the end of this table",
- RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
- ShareUpdateExclusiveLock
- },
- true
- },
{
{
"deduplicate_items",
@@ -170,6 +161,21 @@ static relopt_bool boolRelOpts[] =
{{NULL}}
};
+static relopt_ternary ternaryRelOpts[] =
+{
+ {
+ {
+ "vacuum_truncate",
+ "Enables vacuum to truncate empty pages at the end of this table",
+ RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
+ ShareUpdateExclusiveLock
+ },
+ TERNARY_UNSET
+ },
+ /* list terminator */
+ {{NULL}}
+};
+
static relopt_int intRelOpts[] =
{
{
@@ -600,6 +606,13 @@ initialize_reloptions(void)
boolRelOpts[i].gen.lockmode));
j++;
}
+ for (i = 0; ternaryRelOpts[i].gen.name; i++)
+ {
+ Assert(DoLockModesConflict(ternaryRelOpts[i].gen.lockmode,
+ ternaryRelOpts[i].gen.lockmode));
+ j++;
+ }
+
for (i = 0; intRelOpts[i].gen.name; i++)
{
Assert(DoLockModesConflict(intRelOpts[i].gen.lockmode,
@@ -640,6 +653,14 @@ initialize_reloptions(void)
j++;
}
+ for (i = 0; ternaryRelOpts[i].gen.name; i++)
+ {
+ relOpts[j] = &ternaryRelOpts[i].gen;
+ relOpts[j]->type = RELOPT_TYPE_TERNARY;
+ relOpts[j]->namelen = strlen(relOpts[j]->name);
+ j++;
+ }
+
for (i = 0; intRelOpts[i].gen.name; i++)
{
relOpts[j] = &intRelOpts[i].gen;
@@ -800,6 +821,9 @@ allocate_reloption(bits32 kinds, int type, const char *name, const char *desc,
case RELOPT_TYPE_BOOL:
size = sizeof(relopt_bool);
break;
+ case RELOPT_TYPE_TERNARY:
+ size = sizeof(relopt_ternary);
+ break;
case RELOPT_TYPE_INT:
size = sizeof(relopt_int);
break;
@@ -883,6 +907,54 @@ add_local_bool_reloption(local_relopts *relopts, const char *name,
add_local_reloption(relopts, (relopt_gen *) newoption, offset);
}
+/*
+ * init_ternary_reloption
+ * Allocate and initialize a new ternary reloption
+ */
+static relopt_ternary *
+init_ternary_reloption(bits32 kinds, const char *name, const char *desc,
+ ternary default_val, LOCKMODE lockmode)
+{
+ relopt_ternary *newoption;
+
+ newoption = (relopt_ternary *) allocate_reloption(kinds,
+ RELOPT_TYPE_TERNARY, name, desc, lockmode);
+ newoption->default_val = default_val;
+
+ return newoption;
+}
+
+/*
+ * add_ternary_reloption
+ * Add a new ternary reloption
+ */
+void
+add_ternary_reloption(bits32 kinds, const char *name, const char *desc,
+ ternary default_val, LOCKMODE lockmode)
+{
+ relopt_ternary *newoption = init_ternary_reloption(kinds, name, desc,
+ default_val, lockmode);
+
+ add_reloption((relopt_gen *) newoption);
+}
+
+/*
+ * add_local_ternary_reloption
+ * Add a new ternary local reloption
+ *
+ * 'offset' is offset of ternary-typed field.
+ */
+void
+add_local_ternary_reloption(local_relopts *relopts, const char *name,
+ const char *desc, ternary default_val,
+ int offset)
+{
+ relopt_ternary *newoption = init_ternary_reloption(RELOPT_KIND_LOCAL,
+ name, desc,
+ default_val, 0);
+
+ add_local_reloption(relopts, (relopt_gen *) newoption, offset);
+}
/*
* init_real_reloption
@@ -1617,6 +1689,18 @@ parse_one_reloption(relopt_value *option, char *text_str, int text_len,
option->gen->name, value)));
}
break;
+ case RELOPT_TYPE_TERNARY:
+ {
+ bool b;
+ parsed = parse_bool(value, &b);
+ option->values.ternary_val = b ? TERNARY_TRUE : TERNARY_FALSE;
+ if (validate && !parsed)
+ ereport(ERROR,
+ (errcode(ERRCODE_INVALID_PARAMETER_VALUE),
+ errmsg("invalid value for ternary option \"%s\": %s",
+ option->gen->name, value)));
+ }
+ break;
case RELOPT_TYPE_INT:
{
relopt_int *optint = (relopt_int *) option->gen;
@@ -1780,17 +1864,6 @@ fillRelOptions(void *rdopts, Size basesize,
char *itempos = ((char *) rdopts) + elems[j].offset;
char *string_val;
- /*
- * If isset_offset is provided, store whether the reloption is
- * set there.
- */
- if (elems[j].isset_offset > 0)
- {
- char *setpos = ((char *) rdopts) + elems[j].isset_offset;
-
- *(bool *) setpos = options[i].isset;
- }
-
switch (options[i].gen->type)
{
case RELOPT_TYPE_BOOL:
@@ -1798,6 +1871,11 @@ fillRelOptions(void *rdopts, Size basesize,
options[i].values.bool_val :
((relopt_bool *) options[i].gen)->default_val;
break;
+ case RELOPT_TYPE_TERNARY:
+ *(ternary *) itempos = options[i].isset ?
+ options[i].values.ternary_val :
+ ((relopt_ternary *) options[i].gen)->default_val;
+ break;
case RELOPT_TYPE_INT:
*(int *) itempos = options[i].isset ?
options[i].values.int_val :
@@ -1912,8 +1990,8 @@ default_reloptions(Datum reloptions, bool validate, relopt_kind kind)
offsetof(StdRdOptions, parallel_workers)},
{"vacuum_index_cleanup", RELOPT_TYPE_ENUM,
offsetof(StdRdOptions, vacuum_index_cleanup)},
- {"vacuum_truncate", RELOPT_TYPE_BOOL,
- offsetof(StdRdOptions, vacuum_truncate), offsetof(StdRdOptions, vacuum_truncate_set)},
+ {"vacuum_truncate", RELOPT_TYPE_TERNARY,
+ offsetof(StdRdOptions, vacuum_truncate)},
{"vacuum_max_eager_freeze_failure_rate", RELOPT_TYPE_REAL,
offsetof(StdRdOptions, vacuum_max_eager_freeze_failure_rate)}
};
@@ -1993,7 +2071,6 @@ build_local_reloptions(local_relopts *relopts, Datum options, bool validate)
elems[i].optname = opt->option->name;
elems[i].opttype = opt->option->type;
elems[i].offset = opt->offset;
- elems[i].isset_offset = 0; /* not supported for local relopts yet */
i++;
}
diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c
index 733ef40ae7..977babff54 100644
--- a/src/backend/commands/vacuum.c
+++ b/src/backend/commands/vacuum.c
@@ -2221,9 +2221,9 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams params,
{
StdRdOptions *opts = (StdRdOptions *) rel->rd_options;
- if (opts && opts->vacuum_truncate_set)
+ if (opts && opts->vacuum_truncate != TERNARY_UNSET)
{
- if (opts->vacuum_truncate)
+ if (opts->vacuum_truncate == TERNARY_TRUE)
params.truncate = VACOPTVALUE_ENABLED;
else
params.truncate = VACOPTVALUE_DISABLED;
diff --git a/src/include/access/reloptions.h b/src/include/access/reloptions.h
index a604a4702c..a436697658 100644
--- a/src/include/access/reloptions.h
+++ b/src/include/access/reloptions.h
@@ -29,6 +29,7 @@
typedef enum relopt_type
{
RELOPT_TYPE_BOOL,
+ RELOPT_TYPE_TERNARY, /* on, off, unset */
RELOPT_TYPE_INT,
RELOPT_TYPE_REAL,
RELOPT_TYPE_ENUM,
@@ -80,6 +81,7 @@ typedef struct relopt_value
union
{
bool bool_val;
+ ternary ternary_val;
int int_val;
double real_val;
int enum_val;
@@ -94,6 +96,12 @@ typedef struct relopt_bool
bool default_val;
} relopt_bool;
+typedef struct relopt_rernary
+{
+ relopt_gen gen;
+ int default_val;
+} relopt_ternary;
+
typedef struct relopt_int
{
relopt_gen gen;
@@ -152,19 +160,6 @@ typedef struct
const char *optname; /* option's name */
relopt_type opttype; /* option's datatype */
int offset; /* offset of field in result struct */
-
- /*
- * isset_offset is an optional offset of a field in the result struct that
- * stores whether the option is explicitly set for the relation or if it
- * just picked up the default value. In most cases, this can be
- * accomplished by giving the reloption a special out-of-range default
- * value (e.g., some integer reloptions use -2), but this isn't always
- * possible. For example, a Boolean reloption cannot be given an
- * out-of-range default, so we need another way to discover the source of
- * its value. This offset is only used if given a value greater than
- * zero.
- */
- int isset_offset;
} relopt_parse_elt;
/* Local reloption definition */
@@ -195,6 +190,8 @@ typedef struct local_relopts
extern relopt_kind add_reloption_kind(void);
extern void add_bool_reloption(bits32 kinds, const char *name, const char *desc,
bool default_val, LOCKMODE lockmode);
+extern void add_ternary_reloption(bits32 kinds, const char *name,
+ const char *desc, int default_val, LOCKMODE lockmode);
extern void add_int_reloption(bits32 kinds, const char *name, const char *desc,
int default_val, int min_val, int max_val,
LOCKMODE lockmode);
@@ -214,6 +211,9 @@ extern void register_reloptions_validator(local_relopts *relopts,
extern void add_local_bool_reloption(local_relopts *relopts, const char *name,
const char *desc, bool default_val,
int offset);
+extern void add_local_ternary_reloption(local_relopts *relopts,
+ const char *name, const char *desc,
+ ternary default_val, int offset);
extern void add_local_int_reloption(local_relopts *relopts, const char *name,
const char *desc, int default_val,
int min_val, int max_val, int offset);
diff --git a/src/include/c.h b/src/include/c.h
index 39022f8a9d..9c59ccccf8 100644
--- a/src/include/c.h
+++ b/src/include/c.h
@@ -488,6 +488,22 @@ typedef void (*pg_funcptr_t) (void);
#include <stdbool.h>
+/*
+ * ternary
+ * Boolean value with an extrea "unset" option
+ *
+ * Ternary data type is used in relation options that can be "true", "false" or
+ * "unset". Since relation options are used deep inside the PostgreSQL code,
+ * this type is declared globally.
+*/
+
+typedef enum ternary
+{
+ TERNARY_FALSE = 0,
+ TERNARY_TRUE = 1,
+ TERNARY_UNSET = -1
+} ternary;
+
/* ----------------------------------------------------------------
* Section 3: standard system types
diff --git a/src/include/utils/rel.h b/src/include/utils/rel.h
index b552359915..7346d618f9 100644
--- a/src/include/utils/rel.h
+++ b/src/include/utils/rel.h
@@ -346,8 +346,7 @@ typedef struct StdRdOptions
bool user_catalog_table; /* use as an additional catalog relation */
int parallel_workers; /* max number of parallel workers */
StdRdOptIndexCleanup vacuum_index_cleanup; /* controls index vacuuming */
- bool vacuum_truncate; /* enables vacuum to truncate a relation */
- bool vacuum_truncate_set; /* whether vacuum_truncate is set */
+ ternary vacuum_truncate; /* enables vacuum to truncate a relation */
/*
* Fraction of pages in a relation that vacuum can eagerly scan and fail
--
2.39.2
--nextPart5129932.5fSG56mABF
Content-Disposition: attachment;
filename="v2-0003-Add-alias-to-be-used-as-unset-state.patch"
Content-Transfer-Encoding: 7Bit
Content-Type: text/x-patch; charset="unicode-2-0-utf-8";
name="v2-0003-Add-alias-to-be-used-as-unset-state.patch"
^ permalink raw reply [nested|flat] 868+ messages in thread
* [PATCH v2a 2/4] Introduce ternary reloptions
@ 2025-09-04 14:41 Nikolay Shaplov <[email protected]>
0 siblings, 0 replies; 868+ messages in thread
From: Nikolay Shaplov @ 2025-09-04 14:41 UTC (permalink / raw)
Introduce ternary reloption as a replacement for current `vacuum_truncate`
implementation. Remove `vacuum_truncate_set` additional flag and using
`TERNARY_UNSET` value instead.
---
src/backend/access/common/reloptions.c | 129 ++++++++++++++++++++-----
src/backend/commands/vacuum.c | 4 +-
src/include/access/reloptions.h | 26 ++---
src/include/c.h | 16 +++
src/include/utils/rel.h | 3 +-
5 files changed, 135 insertions(+), 43 deletions(-)
diff --git a/src/backend/access/common/reloptions.c b/src/backend/access/common/reloptions.c
index 0af3fea68f..f543f11001 100644
--- a/src/backend/access/common/reloptions.c
+++ b/src/backend/access/common/reloptions.c
@@ -40,9 +40,9 @@
*
* To add an option:
*
- * (i) decide on a type (bool, integer, real, enum, string), name, default
- * value, upper and lower bounds (if applicable); for strings, consider a
- * validation routine.
+ * (i) decide on a type (bool, ternary, integer, real, enum, string), name,
+ * default value, upper and lower bounds (if applicable); for strings,
+ * consider a validation routine.
* (ii) add a record below (or use add_<type>_reloption).
* (iii) add it to the appropriate options struct (perhaps StdRdOptions)
* (iv) add it to the appropriate handling routine (perhaps
@@ -147,15 +147,6 @@ static relopt_bool boolRelOpts[] =
},
false
},
- {
- {
- "vacuum_truncate",
- "Enables vacuum to truncate empty pages at the end of this table",
- RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
- ShareUpdateExclusiveLock
- },
- true
- },
{
{
"deduplicate_items",
@@ -170,6 +161,21 @@ static relopt_bool boolRelOpts[] =
{{NULL}}
};
+static relopt_ternary ternaryRelOpts[] =
+{
+ {
+ {
+ "vacuum_truncate",
+ "Enables vacuum to truncate empty pages at the end of this table",
+ RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
+ ShareUpdateExclusiveLock
+ },
+ TERNARY_UNSET
+ },
+ /* list terminator */
+ {{NULL}}
+};
+
static relopt_int intRelOpts[] =
{
{
@@ -600,6 +606,13 @@ initialize_reloptions(void)
boolRelOpts[i].gen.lockmode));
j++;
}
+ for (i = 0; ternaryRelOpts[i].gen.name; i++)
+ {
+ Assert(DoLockModesConflict(ternaryRelOpts[i].gen.lockmode,
+ ternaryRelOpts[i].gen.lockmode));
+ j++;
+ }
+
for (i = 0; intRelOpts[i].gen.name; i++)
{
Assert(DoLockModesConflict(intRelOpts[i].gen.lockmode,
@@ -640,6 +653,14 @@ initialize_reloptions(void)
j++;
}
+ for (i = 0; ternaryRelOpts[i].gen.name; i++)
+ {
+ relOpts[j] = &ternaryRelOpts[i].gen;
+ relOpts[j]->type = RELOPT_TYPE_TERNARY;
+ relOpts[j]->namelen = strlen(relOpts[j]->name);
+ j++;
+ }
+
for (i = 0; intRelOpts[i].gen.name; i++)
{
relOpts[j] = &intRelOpts[i].gen;
@@ -800,6 +821,9 @@ allocate_reloption(bits32 kinds, int type, const char *name, const char *desc,
case RELOPT_TYPE_BOOL:
size = sizeof(relopt_bool);
break;
+ case RELOPT_TYPE_TERNARY:
+ size = sizeof(relopt_ternary);
+ break;
case RELOPT_TYPE_INT:
size = sizeof(relopt_int);
break;
@@ -883,6 +907,54 @@ add_local_bool_reloption(local_relopts *relopts, const char *name,
add_local_reloption(relopts, (relopt_gen *) newoption, offset);
}
+/*
+ * init_ternary_reloption
+ * Allocate and initialize a new ternary reloption
+ */
+static relopt_ternary *
+init_ternary_reloption(bits32 kinds, const char *name, const char *desc,
+ ternary default_val, LOCKMODE lockmode)
+{
+ relopt_ternary *newoption;
+
+ newoption = (relopt_ternary *) allocate_reloption(kinds,
+ RELOPT_TYPE_TERNARY, name, desc, lockmode);
+ newoption->default_val = default_val;
+
+ return newoption;
+}
+
+/*
+ * add_ternary_reloption
+ * Add a new ternary reloption
+ */
+void
+add_ternary_reloption(bits32 kinds, const char *name, const char *desc,
+ ternary default_val, LOCKMODE lockmode)
+{
+ relopt_ternary *newoption = init_ternary_reloption(kinds, name, desc,
+ default_val, lockmode);
+
+ add_reloption((relopt_gen *) newoption);
+}
+
+/*
+ * add_local_ternary_reloption
+ * Add a new ternary local reloption
+ *
+ * 'offset' is offset of ternary-typed field.
+ */
+void
+add_local_ternary_reloption(local_relopts *relopts, const char *name,
+ const char *desc, ternary default_val,
+ int offset)
+{
+ relopt_ternary *newoption = init_ternary_reloption(RELOPT_KIND_LOCAL,
+ name, desc,
+ default_val, 0);
+
+ add_local_reloption(relopts, (relopt_gen *) newoption, offset);
+}
/*
* init_real_reloption
@@ -1617,6 +1689,18 @@ parse_one_reloption(relopt_value *option, char *text_str, int text_len,
option->gen->name, value)));
}
break;
+ case RELOPT_TYPE_TERNARY:
+ {
+ bool b;
+ parsed = parse_bool(value, &b);
+ option->values.ternary_val = b ? TERNARY_TRUE : TERNARY_FALSE;
+ if (validate && !parsed)
+ ereport(ERROR,
+ (errcode(ERRCODE_INVALID_PARAMETER_VALUE),
+ errmsg("invalid value for ternary option \"%s\": %s",
+ option->gen->name, value)));
+ }
+ break;
case RELOPT_TYPE_INT:
{
relopt_int *optint = (relopt_int *) option->gen;
@@ -1780,17 +1864,6 @@ fillRelOptions(void *rdopts, Size basesize,
char *itempos = ((char *) rdopts) + elems[j].offset;
char *string_val;
- /*
- * If isset_offset is provided, store whether the reloption is
- * set there.
- */
- if (elems[j].isset_offset > 0)
- {
- char *setpos = ((char *) rdopts) + elems[j].isset_offset;
-
- *(bool *) setpos = options[i].isset;
- }
-
switch (options[i].gen->type)
{
case RELOPT_TYPE_BOOL:
@@ -1798,6 +1871,11 @@ fillRelOptions(void *rdopts, Size basesize,
options[i].values.bool_val :
((relopt_bool *) options[i].gen)->default_val;
break;
+ case RELOPT_TYPE_TERNARY:
+ *(ternary *) itempos = options[i].isset ?
+ options[i].values.ternary_val :
+ ((relopt_ternary *) options[i].gen)->default_val;
+ break;
case RELOPT_TYPE_INT:
*(int *) itempos = options[i].isset ?
options[i].values.int_val :
@@ -1912,8 +1990,8 @@ default_reloptions(Datum reloptions, bool validate, relopt_kind kind)
offsetof(StdRdOptions, parallel_workers)},
{"vacuum_index_cleanup", RELOPT_TYPE_ENUM,
offsetof(StdRdOptions, vacuum_index_cleanup)},
- {"vacuum_truncate", RELOPT_TYPE_BOOL,
- offsetof(StdRdOptions, vacuum_truncate), offsetof(StdRdOptions, vacuum_truncate_set)},
+ {"vacuum_truncate", RELOPT_TYPE_TERNARY,
+ offsetof(StdRdOptions, vacuum_truncate)},
{"vacuum_max_eager_freeze_failure_rate", RELOPT_TYPE_REAL,
offsetof(StdRdOptions, vacuum_max_eager_freeze_failure_rate)}
};
@@ -1993,7 +2071,6 @@ build_local_reloptions(local_relopts *relopts, Datum options, bool validate)
elems[i].optname = opt->option->name;
elems[i].opttype = opt->option->type;
elems[i].offset = opt->offset;
- elems[i].isset_offset = 0; /* not supported for local relopts yet */
i++;
}
diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c
index 733ef40ae7..977babff54 100644
--- a/src/backend/commands/vacuum.c
+++ b/src/backend/commands/vacuum.c
@@ -2221,9 +2221,9 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams params,
{
StdRdOptions *opts = (StdRdOptions *) rel->rd_options;
- if (opts && opts->vacuum_truncate_set)
+ if (opts && opts->vacuum_truncate != TERNARY_UNSET)
{
- if (opts->vacuum_truncate)
+ if (opts->vacuum_truncate == TERNARY_TRUE)
params.truncate = VACOPTVALUE_ENABLED;
else
params.truncate = VACOPTVALUE_DISABLED;
diff --git a/src/include/access/reloptions.h b/src/include/access/reloptions.h
index a604a4702c..a436697658 100644
--- a/src/include/access/reloptions.h
+++ b/src/include/access/reloptions.h
@@ -29,6 +29,7 @@
typedef enum relopt_type
{
RELOPT_TYPE_BOOL,
+ RELOPT_TYPE_TERNARY, /* on, off, unset */
RELOPT_TYPE_INT,
RELOPT_TYPE_REAL,
RELOPT_TYPE_ENUM,
@@ -80,6 +81,7 @@ typedef struct relopt_value
union
{
bool bool_val;
+ ternary ternary_val;
int int_val;
double real_val;
int enum_val;
@@ -94,6 +96,12 @@ typedef struct relopt_bool
bool default_val;
} relopt_bool;
+typedef struct relopt_rernary
+{
+ relopt_gen gen;
+ int default_val;
+} relopt_ternary;
+
typedef struct relopt_int
{
relopt_gen gen;
@@ -152,19 +160,6 @@ typedef struct
const char *optname; /* option's name */
relopt_type opttype; /* option's datatype */
int offset; /* offset of field in result struct */
-
- /*
- * isset_offset is an optional offset of a field in the result struct that
- * stores whether the option is explicitly set for the relation or if it
- * just picked up the default value. In most cases, this can be
- * accomplished by giving the reloption a special out-of-range default
- * value (e.g., some integer reloptions use -2), but this isn't always
- * possible. For example, a Boolean reloption cannot be given an
- * out-of-range default, so we need another way to discover the source of
- * its value. This offset is only used if given a value greater than
- * zero.
- */
- int isset_offset;
} relopt_parse_elt;
/* Local reloption definition */
@@ -195,6 +190,8 @@ typedef struct local_relopts
extern relopt_kind add_reloption_kind(void);
extern void add_bool_reloption(bits32 kinds, const char *name, const char *desc,
bool default_val, LOCKMODE lockmode);
+extern void add_ternary_reloption(bits32 kinds, const char *name,
+ const char *desc, int default_val, LOCKMODE lockmode);
extern void add_int_reloption(bits32 kinds, const char *name, const char *desc,
int default_val, int min_val, int max_val,
LOCKMODE lockmode);
@@ -214,6 +211,9 @@ extern void register_reloptions_validator(local_relopts *relopts,
extern void add_local_bool_reloption(local_relopts *relopts, const char *name,
const char *desc, bool default_val,
int offset);
+extern void add_local_ternary_reloption(local_relopts *relopts,
+ const char *name, const char *desc,
+ ternary default_val, int offset);
extern void add_local_int_reloption(local_relopts *relopts, const char *name,
const char *desc, int default_val,
int min_val, int max_val, int offset);
diff --git a/src/include/c.h b/src/include/c.h
index 39022f8a9d..9c59ccccf8 100644
--- a/src/include/c.h
+++ b/src/include/c.h
@@ -488,6 +488,22 @@ typedef void (*pg_funcptr_t) (void);
#include <stdbool.h>
+/*
+ * ternary
+ * Boolean value with an extrea "unset" option
+ *
+ * Ternary data type is used in relation options that can be "true", "false" or
+ * "unset". Since relation options are used deep inside the PostgreSQL code,
+ * this type is declared globally.
+*/
+
+typedef enum ternary
+{
+ TERNARY_FALSE = 0,
+ TERNARY_TRUE = 1,
+ TERNARY_UNSET = -1
+} ternary;
+
/* ----------------------------------------------------------------
* Section 3: standard system types
diff --git a/src/include/utils/rel.h b/src/include/utils/rel.h
index b552359915..7346d618f9 100644
--- a/src/include/utils/rel.h
+++ b/src/include/utils/rel.h
@@ -346,8 +346,7 @@ typedef struct StdRdOptions
bool user_catalog_table; /* use as an additional catalog relation */
int parallel_workers; /* max number of parallel workers */
StdRdOptIndexCleanup vacuum_index_cleanup; /* controls index vacuuming */
- bool vacuum_truncate; /* enables vacuum to truncate a relation */
- bool vacuum_truncate_set; /* whether vacuum_truncate is set */
+ ternary vacuum_truncate; /* enables vacuum to truncate a relation */
/*
* Fraction of pages in a relation that vacuum can eagerly scan and fail
--
2.39.2
--nextPart4155600.3ZeAukHxDK
Content-Disposition: attachment;
filename="v2a-0003-Add-alias-to-be-used-as-unset-state.patch"
Content-Transfer-Encoding: 7Bit
Content-Type: text/x-patch; charset="unicode-2-0-utf-8";
name="v2a-0003-Add-alias-to-be-used-as-unset-state.patch"
^ permalink raw reply [nested|flat] 868+ messages in thread
* [PATCH v2 2/4] Introduce ternary reloptions
@ 2025-09-04 14:41 Nikolay Shaplov <[email protected]>
0 siblings, 0 replies; 868+ messages in thread
From: Nikolay Shaplov @ 2025-09-04 14:41 UTC (permalink / raw)
Introduce ternary reloption as a replacement for current `vacuum_truncate`
implementation. Remove `vacuum_truncate_set` additional flag and using
`TERNARY_UNSET` value instead.
---
src/backend/access/common/reloptions.c | 129 ++++++++++++++++++++-----
src/backend/commands/vacuum.c | 4 +-
src/include/access/reloptions.h | 26 ++---
src/include/c.h | 16 +++
src/include/utils/rel.h | 3 +-
5 files changed, 135 insertions(+), 43 deletions(-)
diff --git a/src/backend/access/common/reloptions.c b/src/backend/access/common/reloptions.c
index 0af3fea68f..f543f11001 100644
--- a/src/backend/access/common/reloptions.c
+++ b/src/backend/access/common/reloptions.c
@@ -40,9 +40,9 @@
*
* To add an option:
*
- * (i) decide on a type (bool, integer, real, enum, string), name, default
- * value, upper and lower bounds (if applicable); for strings, consider a
- * validation routine.
+ * (i) decide on a type (bool, ternary, integer, real, enum, string), name,
+ * default value, upper and lower bounds (if applicable); for strings,
+ * consider a validation routine.
* (ii) add a record below (or use add_<type>_reloption).
* (iii) add it to the appropriate options struct (perhaps StdRdOptions)
* (iv) add it to the appropriate handling routine (perhaps
@@ -147,15 +147,6 @@ static relopt_bool boolRelOpts[] =
},
false
},
- {
- {
- "vacuum_truncate",
- "Enables vacuum to truncate empty pages at the end of this table",
- RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
- ShareUpdateExclusiveLock
- },
- true
- },
{
{
"deduplicate_items",
@@ -170,6 +161,21 @@ static relopt_bool boolRelOpts[] =
{{NULL}}
};
+static relopt_ternary ternaryRelOpts[] =
+{
+ {
+ {
+ "vacuum_truncate",
+ "Enables vacuum to truncate empty pages at the end of this table",
+ RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
+ ShareUpdateExclusiveLock
+ },
+ TERNARY_UNSET
+ },
+ /* list terminator */
+ {{NULL}}
+};
+
static relopt_int intRelOpts[] =
{
{
@@ -600,6 +606,13 @@ initialize_reloptions(void)
boolRelOpts[i].gen.lockmode));
j++;
}
+ for (i = 0; ternaryRelOpts[i].gen.name; i++)
+ {
+ Assert(DoLockModesConflict(ternaryRelOpts[i].gen.lockmode,
+ ternaryRelOpts[i].gen.lockmode));
+ j++;
+ }
+
for (i = 0; intRelOpts[i].gen.name; i++)
{
Assert(DoLockModesConflict(intRelOpts[i].gen.lockmode,
@@ -640,6 +653,14 @@ initialize_reloptions(void)
j++;
}
+ for (i = 0; ternaryRelOpts[i].gen.name; i++)
+ {
+ relOpts[j] = &ternaryRelOpts[i].gen;
+ relOpts[j]->type = RELOPT_TYPE_TERNARY;
+ relOpts[j]->namelen = strlen(relOpts[j]->name);
+ j++;
+ }
+
for (i = 0; intRelOpts[i].gen.name; i++)
{
relOpts[j] = &intRelOpts[i].gen;
@@ -800,6 +821,9 @@ allocate_reloption(bits32 kinds, int type, const char *name, const char *desc,
case RELOPT_TYPE_BOOL:
size = sizeof(relopt_bool);
break;
+ case RELOPT_TYPE_TERNARY:
+ size = sizeof(relopt_ternary);
+ break;
case RELOPT_TYPE_INT:
size = sizeof(relopt_int);
break;
@@ -883,6 +907,54 @@ add_local_bool_reloption(local_relopts *relopts, const char *name,
add_local_reloption(relopts, (relopt_gen *) newoption, offset);
}
+/*
+ * init_ternary_reloption
+ * Allocate and initialize a new ternary reloption
+ */
+static relopt_ternary *
+init_ternary_reloption(bits32 kinds, const char *name, const char *desc,
+ ternary default_val, LOCKMODE lockmode)
+{
+ relopt_ternary *newoption;
+
+ newoption = (relopt_ternary *) allocate_reloption(kinds,
+ RELOPT_TYPE_TERNARY, name, desc, lockmode);
+ newoption->default_val = default_val;
+
+ return newoption;
+}
+
+/*
+ * add_ternary_reloption
+ * Add a new ternary reloption
+ */
+void
+add_ternary_reloption(bits32 kinds, const char *name, const char *desc,
+ ternary default_val, LOCKMODE lockmode)
+{
+ relopt_ternary *newoption = init_ternary_reloption(kinds, name, desc,
+ default_val, lockmode);
+
+ add_reloption((relopt_gen *) newoption);
+}
+
+/*
+ * add_local_ternary_reloption
+ * Add a new ternary local reloption
+ *
+ * 'offset' is offset of ternary-typed field.
+ */
+void
+add_local_ternary_reloption(local_relopts *relopts, const char *name,
+ const char *desc, ternary default_val,
+ int offset)
+{
+ relopt_ternary *newoption = init_ternary_reloption(RELOPT_KIND_LOCAL,
+ name, desc,
+ default_val, 0);
+
+ add_local_reloption(relopts, (relopt_gen *) newoption, offset);
+}
/*
* init_real_reloption
@@ -1617,6 +1689,18 @@ parse_one_reloption(relopt_value *option, char *text_str, int text_len,
option->gen->name, value)));
}
break;
+ case RELOPT_TYPE_TERNARY:
+ {
+ bool b;
+ parsed = parse_bool(value, &b);
+ option->values.ternary_val = b ? TERNARY_TRUE : TERNARY_FALSE;
+ if (validate && !parsed)
+ ereport(ERROR,
+ (errcode(ERRCODE_INVALID_PARAMETER_VALUE),
+ errmsg("invalid value for ternary option \"%s\": %s",
+ option->gen->name, value)));
+ }
+ break;
case RELOPT_TYPE_INT:
{
relopt_int *optint = (relopt_int *) option->gen;
@@ -1780,17 +1864,6 @@ fillRelOptions(void *rdopts, Size basesize,
char *itempos = ((char *) rdopts) + elems[j].offset;
char *string_val;
- /*
- * If isset_offset is provided, store whether the reloption is
- * set there.
- */
- if (elems[j].isset_offset > 0)
- {
- char *setpos = ((char *) rdopts) + elems[j].isset_offset;
-
- *(bool *) setpos = options[i].isset;
- }
-
switch (options[i].gen->type)
{
case RELOPT_TYPE_BOOL:
@@ -1798,6 +1871,11 @@ fillRelOptions(void *rdopts, Size basesize,
options[i].values.bool_val :
((relopt_bool *) options[i].gen)->default_val;
break;
+ case RELOPT_TYPE_TERNARY:
+ *(ternary *) itempos = options[i].isset ?
+ options[i].values.ternary_val :
+ ((relopt_ternary *) options[i].gen)->default_val;
+ break;
case RELOPT_TYPE_INT:
*(int *) itempos = options[i].isset ?
options[i].values.int_val :
@@ -1912,8 +1990,8 @@ default_reloptions(Datum reloptions, bool validate, relopt_kind kind)
offsetof(StdRdOptions, parallel_workers)},
{"vacuum_index_cleanup", RELOPT_TYPE_ENUM,
offsetof(StdRdOptions, vacuum_index_cleanup)},
- {"vacuum_truncate", RELOPT_TYPE_BOOL,
- offsetof(StdRdOptions, vacuum_truncate), offsetof(StdRdOptions, vacuum_truncate_set)},
+ {"vacuum_truncate", RELOPT_TYPE_TERNARY,
+ offsetof(StdRdOptions, vacuum_truncate)},
{"vacuum_max_eager_freeze_failure_rate", RELOPT_TYPE_REAL,
offsetof(StdRdOptions, vacuum_max_eager_freeze_failure_rate)}
};
@@ -1993,7 +2071,6 @@ build_local_reloptions(local_relopts *relopts, Datum options, bool validate)
elems[i].optname = opt->option->name;
elems[i].opttype = opt->option->type;
elems[i].offset = opt->offset;
- elems[i].isset_offset = 0; /* not supported for local relopts yet */
i++;
}
diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c
index 733ef40ae7..977babff54 100644
--- a/src/backend/commands/vacuum.c
+++ b/src/backend/commands/vacuum.c
@@ -2221,9 +2221,9 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams params,
{
StdRdOptions *opts = (StdRdOptions *) rel->rd_options;
- if (opts && opts->vacuum_truncate_set)
+ if (opts && opts->vacuum_truncate != TERNARY_UNSET)
{
- if (opts->vacuum_truncate)
+ if (opts->vacuum_truncate == TERNARY_TRUE)
params.truncate = VACOPTVALUE_ENABLED;
else
params.truncate = VACOPTVALUE_DISABLED;
diff --git a/src/include/access/reloptions.h b/src/include/access/reloptions.h
index a604a4702c..a436697658 100644
--- a/src/include/access/reloptions.h
+++ b/src/include/access/reloptions.h
@@ -29,6 +29,7 @@
typedef enum relopt_type
{
RELOPT_TYPE_BOOL,
+ RELOPT_TYPE_TERNARY, /* on, off, unset */
RELOPT_TYPE_INT,
RELOPT_TYPE_REAL,
RELOPT_TYPE_ENUM,
@@ -80,6 +81,7 @@ typedef struct relopt_value
union
{
bool bool_val;
+ ternary ternary_val;
int int_val;
double real_val;
int enum_val;
@@ -94,6 +96,12 @@ typedef struct relopt_bool
bool default_val;
} relopt_bool;
+typedef struct relopt_rernary
+{
+ relopt_gen gen;
+ int default_val;
+} relopt_ternary;
+
typedef struct relopt_int
{
relopt_gen gen;
@@ -152,19 +160,6 @@ typedef struct
const char *optname; /* option's name */
relopt_type opttype; /* option's datatype */
int offset; /* offset of field in result struct */
-
- /*
- * isset_offset is an optional offset of a field in the result struct that
- * stores whether the option is explicitly set for the relation or if it
- * just picked up the default value. In most cases, this can be
- * accomplished by giving the reloption a special out-of-range default
- * value (e.g., some integer reloptions use -2), but this isn't always
- * possible. For example, a Boolean reloption cannot be given an
- * out-of-range default, so we need another way to discover the source of
- * its value. This offset is only used if given a value greater than
- * zero.
- */
- int isset_offset;
} relopt_parse_elt;
/* Local reloption definition */
@@ -195,6 +190,8 @@ typedef struct local_relopts
extern relopt_kind add_reloption_kind(void);
extern void add_bool_reloption(bits32 kinds, const char *name, const char *desc,
bool default_val, LOCKMODE lockmode);
+extern void add_ternary_reloption(bits32 kinds, const char *name,
+ const char *desc, int default_val, LOCKMODE lockmode);
extern void add_int_reloption(bits32 kinds, const char *name, const char *desc,
int default_val, int min_val, int max_val,
LOCKMODE lockmode);
@@ -214,6 +211,9 @@ extern void register_reloptions_validator(local_relopts *relopts,
extern void add_local_bool_reloption(local_relopts *relopts, const char *name,
const char *desc, bool default_val,
int offset);
+extern void add_local_ternary_reloption(local_relopts *relopts,
+ const char *name, const char *desc,
+ ternary default_val, int offset);
extern void add_local_int_reloption(local_relopts *relopts, const char *name,
const char *desc, int default_val,
int min_val, int max_val, int offset);
diff --git a/src/include/c.h b/src/include/c.h
index 39022f8a9d..9c59ccccf8 100644
--- a/src/include/c.h
+++ b/src/include/c.h
@@ -488,6 +488,22 @@ typedef void (*pg_funcptr_t) (void);
#include <stdbool.h>
+/*
+ * ternary
+ * Boolean value with an extrea "unset" option
+ *
+ * Ternary data type is used in relation options that can be "true", "false" or
+ * "unset". Since relation options are used deep inside the PostgreSQL code,
+ * this type is declared globally.
+*/
+
+typedef enum ternary
+{
+ TERNARY_FALSE = 0,
+ TERNARY_TRUE = 1,
+ TERNARY_UNSET = -1
+} ternary;
+
/* ----------------------------------------------------------------
* Section 3: standard system types
diff --git a/src/include/utils/rel.h b/src/include/utils/rel.h
index b552359915..7346d618f9 100644
--- a/src/include/utils/rel.h
+++ b/src/include/utils/rel.h
@@ -346,8 +346,7 @@ typedef struct StdRdOptions
bool user_catalog_table; /* use as an additional catalog relation */
int parallel_workers; /* max number of parallel workers */
StdRdOptIndexCleanup vacuum_index_cleanup; /* controls index vacuuming */
- bool vacuum_truncate; /* enables vacuum to truncate a relation */
- bool vacuum_truncate_set; /* whether vacuum_truncate is set */
+ ternary vacuum_truncate; /* enables vacuum to truncate a relation */
/*
* Fraction of pages in a relation that vacuum can eagerly scan and fail
--
2.39.2
--nextPart5129932.5fSG56mABF
Content-Disposition: attachment;
filename="v2-0003-Add-alias-to-be-used-as-unset-state.patch"
Content-Transfer-Encoding: 7Bit
Content-Type: text/x-patch; charset="unicode-2-0-utf-8";
name="v2-0003-Add-alias-to-be-used-as-unset-state.patch"
^ permalink raw reply [nested|flat] 868+ messages in thread
* [PATCH v2a 2/4] Introduce ternary reloptions
@ 2025-09-04 14:41 Nikolay Shaplov <[email protected]>
0 siblings, 0 replies; 868+ messages in thread
From: Nikolay Shaplov @ 2025-09-04 14:41 UTC (permalink / raw)
Introduce ternary reloption as a replacement for current `vacuum_truncate`
implementation. Remove `vacuum_truncate_set` additional flag and using
`TERNARY_UNSET` value instead.
---
src/backend/access/common/reloptions.c | 129 ++++++++++++++++++++-----
src/backend/commands/vacuum.c | 4 +-
src/include/access/reloptions.h | 26 ++---
src/include/c.h | 16 +++
src/include/utils/rel.h | 3 +-
5 files changed, 135 insertions(+), 43 deletions(-)
diff --git a/src/backend/access/common/reloptions.c b/src/backend/access/common/reloptions.c
index 0af3fea68f..f543f11001 100644
--- a/src/backend/access/common/reloptions.c
+++ b/src/backend/access/common/reloptions.c
@@ -40,9 +40,9 @@
*
* To add an option:
*
- * (i) decide on a type (bool, integer, real, enum, string), name, default
- * value, upper and lower bounds (if applicable); for strings, consider a
- * validation routine.
+ * (i) decide on a type (bool, ternary, integer, real, enum, string), name,
+ * default value, upper and lower bounds (if applicable); for strings,
+ * consider a validation routine.
* (ii) add a record below (or use add_<type>_reloption).
* (iii) add it to the appropriate options struct (perhaps StdRdOptions)
* (iv) add it to the appropriate handling routine (perhaps
@@ -147,15 +147,6 @@ static relopt_bool boolRelOpts[] =
},
false
},
- {
- {
- "vacuum_truncate",
- "Enables vacuum to truncate empty pages at the end of this table",
- RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
- ShareUpdateExclusiveLock
- },
- true
- },
{
{
"deduplicate_items",
@@ -170,6 +161,21 @@ static relopt_bool boolRelOpts[] =
{{NULL}}
};
+static relopt_ternary ternaryRelOpts[] =
+{
+ {
+ {
+ "vacuum_truncate",
+ "Enables vacuum to truncate empty pages at the end of this table",
+ RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
+ ShareUpdateExclusiveLock
+ },
+ TERNARY_UNSET
+ },
+ /* list terminator */
+ {{NULL}}
+};
+
static relopt_int intRelOpts[] =
{
{
@@ -600,6 +606,13 @@ initialize_reloptions(void)
boolRelOpts[i].gen.lockmode));
j++;
}
+ for (i = 0; ternaryRelOpts[i].gen.name; i++)
+ {
+ Assert(DoLockModesConflict(ternaryRelOpts[i].gen.lockmode,
+ ternaryRelOpts[i].gen.lockmode));
+ j++;
+ }
+
for (i = 0; intRelOpts[i].gen.name; i++)
{
Assert(DoLockModesConflict(intRelOpts[i].gen.lockmode,
@@ -640,6 +653,14 @@ initialize_reloptions(void)
j++;
}
+ for (i = 0; ternaryRelOpts[i].gen.name; i++)
+ {
+ relOpts[j] = &ternaryRelOpts[i].gen;
+ relOpts[j]->type = RELOPT_TYPE_TERNARY;
+ relOpts[j]->namelen = strlen(relOpts[j]->name);
+ j++;
+ }
+
for (i = 0; intRelOpts[i].gen.name; i++)
{
relOpts[j] = &intRelOpts[i].gen;
@@ -800,6 +821,9 @@ allocate_reloption(bits32 kinds, int type, const char *name, const char *desc,
case RELOPT_TYPE_BOOL:
size = sizeof(relopt_bool);
break;
+ case RELOPT_TYPE_TERNARY:
+ size = sizeof(relopt_ternary);
+ break;
case RELOPT_TYPE_INT:
size = sizeof(relopt_int);
break;
@@ -883,6 +907,54 @@ add_local_bool_reloption(local_relopts *relopts, const char *name,
add_local_reloption(relopts, (relopt_gen *) newoption, offset);
}
+/*
+ * init_ternary_reloption
+ * Allocate and initialize a new ternary reloption
+ */
+static relopt_ternary *
+init_ternary_reloption(bits32 kinds, const char *name, const char *desc,
+ ternary default_val, LOCKMODE lockmode)
+{
+ relopt_ternary *newoption;
+
+ newoption = (relopt_ternary *) allocate_reloption(kinds,
+ RELOPT_TYPE_TERNARY, name, desc, lockmode);
+ newoption->default_val = default_val;
+
+ return newoption;
+}
+
+/*
+ * add_ternary_reloption
+ * Add a new ternary reloption
+ */
+void
+add_ternary_reloption(bits32 kinds, const char *name, const char *desc,
+ ternary default_val, LOCKMODE lockmode)
+{
+ relopt_ternary *newoption = init_ternary_reloption(kinds, name, desc,
+ default_val, lockmode);
+
+ add_reloption((relopt_gen *) newoption);
+}
+
+/*
+ * add_local_ternary_reloption
+ * Add a new ternary local reloption
+ *
+ * 'offset' is offset of ternary-typed field.
+ */
+void
+add_local_ternary_reloption(local_relopts *relopts, const char *name,
+ const char *desc, ternary default_val,
+ int offset)
+{
+ relopt_ternary *newoption = init_ternary_reloption(RELOPT_KIND_LOCAL,
+ name, desc,
+ default_val, 0);
+
+ add_local_reloption(relopts, (relopt_gen *) newoption, offset);
+}
/*
* init_real_reloption
@@ -1617,6 +1689,18 @@ parse_one_reloption(relopt_value *option, char *text_str, int text_len,
option->gen->name, value)));
}
break;
+ case RELOPT_TYPE_TERNARY:
+ {
+ bool b;
+ parsed = parse_bool(value, &b);
+ option->values.ternary_val = b ? TERNARY_TRUE : TERNARY_FALSE;
+ if (validate && !parsed)
+ ereport(ERROR,
+ (errcode(ERRCODE_INVALID_PARAMETER_VALUE),
+ errmsg("invalid value for ternary option \"%s\": %s",
+ option->gen->name, value)));
+ }
+ break;
case RELOPT_TYPE_INT:
{
relopt_int *optint = (relopt_int *) option->gen;
@@ -1780,17 +1864,6 @@ fillRelOptions(void *rdopts, Size basesize,
char *itempos = ((char *) rdopts) + elems[j].offset;
char *string_val;
- /*
- * If isset_offset is provided, store whether the reloption is
- * set there.
- */
- if (elems[j].isset_offset > 0)
- {
- char *setpos = ((char *) rdopts) + elems[j].isset_offset;
-
- *(bool *) setpos = options[i].isset;
- }
-
switch (options[i].gen->type)
{
case RELOPT_TYPE_BOOL:
@@ -1798,6 +1871,11 @@ fillRelOptions(void *rdopts, Size basesize,
options[i].values.bool_val :
((relopt_bool *) options[i].gen)->default_val;
break;
+ case RELOPT_TYPE_TERNARY:
+ *(ternary *) itempos = options[i].isset ?
+ options[i].values.ternary_val :
+ ((relopt_ternary *) options[i].gen)->default_val;
+ break;
case RELOPT_TYPE_INT:
*(int *) itempos = options[i].isset ?
options[i].values.int_val :
@@ -1912,8 +1990,8 @@ default_reloptions(Datum reloptions, bool validate, relopt_kind kind)
offsetof(StdRdOptions, parallel_workers)},
{"vacuum_index_cleanup", RELOPT_TYPE_ENUM,
offsetof(StdRdOptions, vacuum_index_cleanup)},
- {"vacuum_truncate", RELOPT_TYPE_BOOL,
- offsetof(StdRdOptions, vacuum_truncate), offsetof(StdRdOptions, vacuum_truncate_set)},
+ {"vacuum_truncate", RELOPT_TYPE_TERNARY,
+ offsetof(StdRdOptions, vacuum_truncate)},
{"vacuum_max_eager_freeze_failure_rate", RELOPT_TYPE_REAL,
offsetof(StdRdOptions, vacuum_max_eager_freeze_failure_rate)}
};
@@ -1993,7 +2071,6 @@ build_local_reloptions(local_relopts *relopts, Datum options, bool validate)
elems[i].optname = opt->option->name;
elems[i].opttype = opt->option->type;
elems[i].offset = opt->offset;
- elems[i].isset_offset = 0; /* not supported for local relopts yet */
i++;
}
diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c
index 733ef40ae7..977babff54 100644
--- a/src/backend/commands/vacuum.c
+++ b/src/backend/commands/vacuum.c
@@ -2221,9 +2221,9 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams params,
{
StdRdOptions *opts = (StdRdOptions *) rel->rd_options;
- if (opts && opts->vacuum_truncate_set)
+ if (opts && opts->vacuum_truncate != TERNARY_UNSET)
{
- if (opts->vacuum_truncate)
+ if (opts->vacuum_truncate == TERNARY_TRUE)
params.truncate = VACOPTVALUE_ENABLED;
else
params.truncate = VACOPTVALUE_DISABLED;
diff --git a/src/include/access/reloptions.h b/src/include/access/reloptions.h
index a604a4702c..a436697658 100644
--- a/src/include/access/reloptions.h
+++ b/src/include/access/reloptions.h
@@ -29,6 +29,7 @@
typedef enum relopt_type
{
RELOPT_TYPE_BOOL,
+ RELOPT_TYPE_TERNARY, /* on, off, unset */
RELOPT_TYPE_INT,
RELOPT_TYPE_REAL,
RELOPT_TYPE_ENUM,
@@ -80,6 +81,7 @@ typedef struct relopt_value
union
{
bool bool_val;
+ ternary ternary_val;
int int_val;
double real_val;
int enum_val;
@@ -94,6 +96,12 @@ typedef struct relopt_bool
bool default_val;
} relopt_bool;
+typedef struct relopt_rernary
+{
+ relopt_gen gen;
+ int default_val;
+} relopt_ternary;
+
typedef struct relopt_int
{
relopt_gen gen;
@@ -152,19 +160,6 @@ typedef struct
const char *optname; /* option's name */
relopt_type opttype; /* option's datatype */
int offset; /* offset of field in result struct */
-
- /*
- * isset_offset is an optional offset of a field in the result struct that
- * stores whether the option is explicitly set for the relation or if it
- * just picked up the default value. In most cases, this can be
- * accomplished by giving the reloption a special out-of-range default
- * value (e.g., some integer reloptions use -2), but this isn't always
- * possible. For example, a Boolean reloption cannot be given an
- * out-of-range default, so we need another way to discover the source of
- * its value. This offset is only used if given a value greater than
- * zero.
- */
- int isset_offset;
} relopt_parse_elt;
/* Local reloption definition */
@@ -195,6 +190,8 @@ typedef struct local_relopts
extern relopt_kind add_reloption_kind(void);
extern void add_bool_reloption(bits32 kinds, const char *name, const char *desc,
bool default_val, LOCKMODE lockmode);
+extern void add_ternary_reloption(bits32 kinds, const char *name,
+ const char *desc, int default_val, LOCKMODE lockmode);
extern void add_int_reloption(bits32 kinds, const char *name, const char *desc,
int default_val, int min_val, int max_val,
LOCKMODE lockmode);
@@ -214,6 +211,9 @@ extern void register_reloptions_validator(local_relopts *relopts,
extern void add_local_bool_reloption(local_relopts *relopts, const char *name,
const char *desc, bool default_val,
int offset);
+extern void add_local_ternary_reloption(local_relopts *relopts,
+ const char *name, const char *desc,
+ ternary default_val, int offset);
extern void add_local_int_reloption(local_relopts *relopts, const char *name,
const char *desc, int default_val,
int min_val, int max_val, int offset);
diff --git a/src/include/c.h b/src/include/c.h
index 39022f8a9d..9c59ccccf8 100644
--- a/src/include/c.h
+++ b/src/include/c.h
@@ -488,6 +488,22 @@ typedef void (*pg_funcptr_t) (void);
#include <stdbool.h>
+/*
+ * ternary
+ * Boolean value with an extrea "unset" option
+ *
+ * Ternary data type is used in relation options that can be "true", "false" or
+ * "unset". Since relation options are used deep inside the PostgreSQL code,
+ * this type is declared globally.
+*/
+
+typedef enum ternary
+{
+ TERNARY_FALSE = 0,
+ TERNARY_TRUE = 1,
+ TERNARY_UNSET = -1
+} ternary;
+
/* ----------------------------------------------------------------
* Section 3: standard system types
diff --git a/src/include/utils/rel.h b/src/include/utils/rel.h
index b552359915..7346d618f9 100644
--- a/src/include/utils/rel.h
+++ b/src/include/utils/rel.h
@@ -346,8 +346,7 @@ typedef struct StdRdOptions
bool user_catalog_table; /* use as an additional catalog relation */
int parallel_workers; /* max number of parallel workers */
StdRdOptIndexCleanup vacuum_index_cleanup; /* controls index vacuuming */
- bool vacuum_truncate; /* enables vacuum to truncate a relation */
- bool vacuum_truncate_set; /* whether vacuum_truncate is set */
+ ternary vacuum_truncate; /* enables vacuum to truncate a relation */
/*
* Fraction of pages in a relation that vacuum can eagerly scan and fail
--
2.39.2
--nextPart4155600.3ZeAukHxDK
Content-Disposition: attachment;
filename="v2a-0003-Add-alias-to-be-used-as-unset-state.patch"
Content-Transfer-Encoding: 7Bit
Content-Type: text/x-patch; charset="unicode-2-0-utf-8";
name="v2a-0003-Add-alias-to-be-used-as-unset-state.patch"
^ permalink raw reply [nested|flat] 868+ messages in thread
* [PATCH v2a 2/4] Introduce ternary reloptions
@ 2025-09-04 14:41 Nikolay Shaplov <[email protected]>
0 siblings, 0 replies; 868+ messages in thread
From: Nikolay Shaplov @ 2025-09-04 14:41 UTC (permalink / raw)
Introduce ternary reloption as a replacement for current `vacuum_truncate`
implementation. Remove `vacuum_truncate_set` additional flag and using
`TERNARY_UNSET` value instead.
---
src/backend/access/common/reloptions.c | 129 ++++++++++++++++++++-----
src/backend/commands/vacuum.c | 4 +-
src/include/access/reloptions.h | 26 ++---
src/include/c.h | 16 +++
src/include/utils/rel.h | 3 +-
5 files changed, 135 insertions(+), 43 deletions(-)
diff --git a/src/backend/access/common/reloptions.c b/src/backend/access/common/reloptions.c
index 0af3fea68f..f543f11001 100644
--- a/src/backend/access/common/reloptions.c
+++ b/src/backend/access/common/reloptions.c
@@ -40,9 +40,9 @@
*
* To add an option:
*
- * (i) decide on a type (bool, integer, real, enum, string), name, default
- * value, upper and lower bounds (if applicable); for strings, consider a
- * validation routine.
+ * (i) decide on a type (bool, ternary, integer, real, enum, string), name,
+ * default value, upper and lower bounds (if applicable); for strings,
+ * consider a validation routine.
* (ii) add a record below (or use add_<type>_reloption).
* (iii) add it to the appropriate options struct (perhaps StdRdOptions)
* (iv) add it to the appropriate handling routine (perhaps
@@ -147,15 +147,6 @@ static relopt_bool boolRelOpts[] =
},
false
},
- {
- {
- "vacuum_truncate",
- "Enables vacuum to truncate empty pages at the end of this table",
- RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
- ShareUpdateExclusiveLock
- },
- true
- },
{
{
"deduplicate_items",
@@ -170,6 +161,21 @@ static relopt_bool boolRelOpts[] =
{{NULL}}
};
+static relopt_ternary ternaryRelOpts[] =
+{
+ {
+ {
+ "vacuum_truncate",
+ "Enables vacuum to truncate empty pages at the end of this table",
+ RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
+ ShareUpdateExclusiveLock
+ },
+ TERNARY_UNSET
+ },
+ /* list terminator */
+ {{NULL}}
+};
+
static relopt_int intRelOpts[] =
{
{
@@ -600,6 +606,13 @@ initialize_reloptions(void)
boolRelOpts[i].gen.lockmode));
j++;
}
+ for (i = 0; ternaryRelOpts[i].gen.name; i++)
+ {
+ Assert(DoLockModesConflict(ternaryRelOpts[i].gen.lockmode,
+ ternaryRelOpts[i].gen.lockmode));
+ j++;
+ }
+
for (i = 0; intRelOpts[i].gen.name; i++)
{
Assert(DoLockModesConflict(intRelOpts[i].gen.lockmode,
@@ -640,6 +653,14 @@ initialize_reloptions(void)
j++;
}
+ for (i = 0; ternaryRelOpts[i].gen.name; i++)
+ {
+ relOpts[j] = &ternaryRelOpts[i].gen;
+ relOpts[j]->type = RELOPT_TYPE_TERNARY;
+ relOpts[j]->namelen = strlen(relOpts[j]->name);
+ j++;
+ }
+
for (i = 0; intRelOpts[i].gen.name; i++)
{
relOpts[j] = &intRelOpts[i].gen;
@@ -800,6 +821,9 @@ allocate_reloption(bits32 kinds, int type, const char *name, const char *desc,
case RELOPT_TYPE_BOOL:
size = sizeof(relopt_bool);
break;
+ case RELOPT_TYPE_TERNARY:
+ size = sizeof(relopt_ternary);
+ break;
case RELOPT_TYPE_INT:
size = sizeof(relopt_int);
break;
@@ -883,6 +907,54 @@ add_local_bool_reloption(local_relopts *relopts, const char *name,
add_local_reloption(relopts, (relopt_gen *) newoption, offset);
}
+/*
+ * init_ternary_reloption
+ * Allocate and initialize a new ternary reloption
+ */
+static relopt_ternary *
+init_ternary_reloption(bits32 kinds, const char *name, const char *desc,
+ ternary default_val, LOCKMODE lockmode)
+{
+ relopt_ternary *newoption;
+
+ newoption = (relopt_ternary *) allocate_reloption(kinds,
+ RELOPT_TYPE_TERNARY, name, desc, lockmode);
+ newoption->default_val = default_val;
+
+ return newoption;
+}
+
+/*
+ * add_ternary_reloption
+ * Add a new ternary reloption
+ */
+void
+add_ternary_reloption(bits32 kinds, const char *name, const char *desc,
+ ternary default_val, LOCKMODE lockmode)
+{
+ relopt_ternary *newoption = init_ternary_reloption(kinds, name, desc,
+ default_val, lockmode);
+
+ add_reloption((relopt_gen *) newoption);
+}
+
+/*
+ * add_local_ternary_reloption
+ * Add a new ternary local reloption
+ *
+ * 'offset' is offset of ternary-typed field.
+ */
+void
+add_local_ternary_reloption(local_relopts *relopts, const char *name,
+ const char *desc, ternary default_val,
+ int offset)
+{
+ relopt_ternary *newoption = init_ternary_reloption(RELOPT_KIND_LOCAL,
+ name, desc,
+ default_val, 0);
+
+ add_local_reloption(relopts, (relopt_gen *) newoption, offset);
+}
/*
* init_real_reloption
@@ -1617,6 +1689,18 @@ parse_one_reloption(relopt_value *option, char *text_str, int text_len,
option->gen->name, value)));
}
break;
+ case RELOPT_TYPE_TERNARY:
+ {
+ bool b;
+ parsed = parse_bool(value, &b);
+ option->values.ternary_val = b ? TERNARY_TRUE : TERNARY_FALSE;
+ if (validate && !parsed)
+ ereport(ERROR,
+ (errcode(ERRCODE_INVALID_PARAMETER_VALUE),
+ errmsg("invalid value for ternary option \"%s\": %s",
+ option->gen->name, value)));
+ }
+ break;
case RELOPT_TYPE_INT:
{
relopt_int *optint = (relopt_int *) option->gen;
@@ -1780,17 +1864,6 @@ fillRelOptions(void *rdopts, Size basesize,
char *itempos = ((char *) rdopts) + elems[j].offset;
char *string_val;
- /*
- * If isset_offset is provided, store whether the reloption is
- * set there.
- */
- if (elems[j].isset_offset > 0)
- {
- char *setpos = ((char *) rdopts) + elems[j].isset_offset;
-
- *(bool *) setpos = options[i].isset;
- }
-
switch (options[i].gen->type)
{
case RELOPT_TYPE_BOOL:
@@ -1798,6 +1871,11 @@ fillRelOptions(void *rdopts, Size basesize,
options[i].values.bool_val :
((relopt_bool *) options[i].gen)->default_val;
break;
+ case RELOPT_TYPE_TERNARY:
+ *(ternary *) itempos = options[i].isset ?
+ options[i].values.ternary_val :
+ ((relopt_ternary *) options[i].gen)->default_val;
+ break;
case RELOPT_TYPE_INT:
*(int *) itempos = options[i].isset ?
options[i].values.int_val :
@@ -1912,8 +1990,8 @@ default_reloptions(Datum reloptions, bool validate, relopt_kind kind)
offsetof(StdRdOptions, parallel_workers)},
{"vacuum_index_cleanup", RELOPT_TYPE_ENUM,
offsetof(StdRdOptions, vacuum_index_cleanup)},
- {"vacuum_truncate", RELOPT_TYPE_BOOL,
- offsetof(StdRdOptions, vacuum_truncate), offsetof(StdRdOptions, vacuum_truncate_set)},
+ {"vacuum_truncate", RELOPT_TYPE_TERNARY,
+ offsetof(StdRdOptions, vacuum_truncate)},
{"vacuum_max_eager_freeze_failure_rate", RELOPT_TYPE_REAL,
offsetof(StdRdOptions, vacuum_max_eager_freeze_failure_rate)}
};
@@ -1993,7 +2071,6 @@ build_local_reloptions(local_relopts *relopts, Datum options, bool validate)
elems[i].optname = opt->option->name;
elems[i].opttype = opt->option->type;
elems[i].offset = opt->offset;
- elems[i].isset_offset = 0; /* not supported for local relopts yet */
i++;
}
diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c
index 733ef40ae7..977babff54 100644
--- a/src/backend/commands/vacuum.c
+++ b/src/backend/commands/vacuum.c
@@ -2221,9 +2221,9 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams params,
{
StdRdOptions *opts = (StdRdOptions *) rel->rd_options;
- if (opts && opts->vacuum_truncate_set)
+ if (opts && opts->vacuum_truncate != TERNARY_UNSET)
{
- if (opts->vacuum_truncate)
+ if (opts->vacuum_truncate == TERNARY_TRUE)
params.truncate = VACOPTVALUE_ENABLED;
else
params.truncate = VACOPTVALUE_DISABLED;
diff --git a/src/include/access/reloptions.h b/src/include/access/reloptions.h
index a604a4702c..a436697658 100644
--- a/src/include/access/reloptions.h
+++ b/src/include/access/reloptions.h
@@ -29,6 +29,7 @@
typedef enum relopt_type
{
RELOPT_TYPE_BOOL,
+ RELOPT_TYPE_TERNARY, /* on, off, unset */
RELOPT_TYPE_INT,
RELOPT_TYPE_REAL,
RELOPT_TYPE_ENUM,
@@ -80,6 +81,7 @@ typedef struct relopt_value
union
{
bool bool_val;
+ ternary ternary_val;
int int_val;
double real_val;
int enum_val;
@@ -94,6 +96,12 @@ typedef struct relopt_bool
bool default_val;
} relopt_bool;
+typedef struct relopt_rernary
+{
+ relopt_gen gen;
+ int default_val;
+} relopt_ternary;
+
typedef struct relopt_int
{
relopt_gen gen;
@@ -152,19 +160,6 @@ typedef struct
const char *optname; /* option's name */
relopt_type opttype; /* option's datatype */
int offset; /* offset of field in result struct */
-
- /*
- * isset_offset is an optional offset of a field in the result struct that
- * stores whether the option is explicitly set for the relation or if it
- * just picked up the default value. In most cases, this can be
- * accomplished by giving the reloption a special out-of-range default
- * value (e.g., some integer reloptions use -2), but this isn't always
- * possible. For example, a Boolean reloption cannot be given an
- * out-of-range default, so we need another way to discover the source of
- * its value. This offset is only used if given a value greater than
- * zero.
- */
- int isset_offset;
} relopt_parse_elt;
/* Local reloption definition */
@@ -195,6 +190,8 @@ typedef struct local_relopts
extern relopt_kind add_reloption_kind(void);
extern void add_bool_reloption(bits32 kinds, const char *name, const char *desc,
bool default_val, LOCKMODE lockmode);
+extern void add_ternary_reloption(bits32 kinds, const char *name,
+ const char *desc, int default_val, LOCKMODE lockmode);
extern void add_int_reloption(bits32 kinds, const char *name, const char *desc,
int default_val, int min_val, int max_val,
LOCKMODE lockmode);
@@ -214,6 +211,9 @@ extern void register_reloptions_validator(local_relopts *relopts,
extern void add_local_bool_reloption(local_relopts *relopts, const char *name,
const char *desc, bool default_val,
int offset);
+extern void add_local_ternary_reloption(local_relopts *relopts,
+ const char *name, const char *desc,
+ ternary default_val, int offset);
extern void add_local_int_reloption(local_relopts *relopts, const char *name,
const char *desc, int default_val,
int min_val, int max_val, int offset);
diff --git a/src/include/c.h b/src/include/c.h
index 39022f8a9d..9c59ccccf8 100644
--- a/src/include/c.h
+++ b/src/include/c.h
@@ -488,6 +488,22 @@ typedef void (*pg_funcptr_t) (void);
#include <stdbool.h>
+/*
+ * ternary
+ * Boolean value with an extrea "unset" option
+ *
+ * Ternary data type is used in relation options that can be "true", "false" or
+ * "unset". Since relation options are used deep inside the PostgreSQL code,
+ * this type is declared globally.
+*/
+
+typedef enum ternary
+{
+ TERNARY_FALSE = 0,
+ TERNARY_TRUE = 1,
+ TERNARY_UNSET = -1
+} ternary;
+
/* ----------------------------------------------------------------
* Section 3: standard system types
diff --git a/src/include/utils/rel.h b/src/include/utils/rel.h
index b552359915..7346d618f9 100644
--- a/src/include/utils/rel.h
+++ b/src/include/utils/rel.h
@@ -346,8 +346,7 @@ typedef struct StdRdOptions
bool user_catalog_table; /* use as an additional catalog relation */
int parallel_workers; /* max number of parallel workers */
StdRdOptIndexCleanup vacuum_index_cleanup; /* controls index vacuuming */
- bool vacuum_truncate; /* enables vacuum to truncate a relation */
- bool vacuum_truncate_set; /* whether vacuum_truncate is set */
+ ternary vacuum_truncate; /* enables vacuum to truncate a relation */
/*
* Fraction of pages in a relation that vacuum can eagerly scan and fail
--
2.39.2
--nextPart4155600.3ZeAukHxDK
Content-Disposition: attachment;
filename="v2a-0003-Add-alias-to-be-used-as-unset-state.patch"
Content-Transfer-Encoding: 7Bit
Content-Type: text/x-patch; charset="unicode-2-0-utf-8";
name="v2a-0003-Add-alias-to-be-used-as-unset-state.patch"
^ permalink raw reply [nested|flat] 868+ messages in thread
* [PATCH v2a 2/4] Introduce ternary reloptions
@ 2025-09-04 14:41 Nikolay Shaplov <[email protected]>
0 siblings, 0 replies; 868+ messages in thread
From: Nikolay Shaplov @ 2025-09-04 14:41 UTC (permalink / raw)
Introduce ternary reloption as a replacement for current `vacuum_truncate`
implementation. Remove `vacuum_truncate_set` additional flag and using
`TERNARY_UNSET` value instead.
---
src/backend/access/common/reloptions.c | 129 ++++++++++++++++++++-----
src/backend/commands/vacuum.c | 4 +-
src/include/access/reloptions.h | 26 ++---
src/include/c.h | 16 +++
src/include/utils/rel.h | 3 +-
5 files changed, 135 insertions(+), 43 deletions(-)
diff --git a/src/backend/access/common/reloptions.c b/src/backend/access/common/reloptions.c
index 0af3fea68f..f543f11001 100644
--- a/src/backend/access/common/reloptions.c
+++ b/src/backend/access/common/reloptions.c
@@ -40,9 +40,9 @@
*
* To add an option:
*
- * (i) decide on a type (bool, integer, real, enum, string), name, default
- * value, upper and lower bounds (if applicable); for strings, consider a
- * validation routine.
+ * (i) decide on a type (bool, ternary, integer, real, enum, string), name,
+ * default value, upper and lower bounds (if applicable); for strings,
+ * consider a validation routine.
* (ii) add a record below (or use add_<type>_reloption).
* (iii) add it to the appropriate options struct (perhaps StdRdOptions)
* (iv) add it to the appropriate handling routine (perhaps
@@ -147,15 +147,6 @@ static relopt_bool boolRelOpts[] =
},
false
},
- {
- {
- "vacuum_truncate",
- "Enables vacuum to truncate empty pages at the end of this table",
- RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
- ShareUpdateExclusiveLock
- },
- true
- },
{
{
"deduplicate_items",
@@ -170,6 +161,21 @@ static relopt_bool boolRelOpts[] =
{{NULL}}
};
+static relopt_ternary ternaryRelOpts[] =
+{
+ {
+ {
+ "vacuum_truncate",
+ "Enables vacuum to truncate empty pages at the end of this table",
+ RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
+ ShareUpdateExclusiveLock
+ },
+ TERNARY_UNSET
+ },
+ /* list terminator */
+ {{NULL}}
+};
+
static relopt_int intRelOpts[] =
{
{
@@ -600,6 +606,13 @@ initialize_reloptions(void)
boolRelOpts[i].gen.lockmode));
j++;
}
+ for (i = 0; ternaryRelOpts[i].gen.name; i++)
+ {
+ Assert(DoLockModesConflict(ternaryRelOpts[i].gen.lockmode,
+ ternaryRelOpts[i].gen.lockmode));
+ j++;
+ }
+
for (i = 0; intRelOpts[i].gen.name; i++)
{
Assert(DoLockModesConflict(intRelOpts[i].gen.lockmode,
@@ -640,6 +653,14 @@ initialize_reloptions(void)
j++;
}
+ for (i = 0; ternaryRelOpts[i].gen.name; i++)
+ {
+ relOpts[j] = &ternaryRelOpts[i].gen;
+ relOpts[j]->type = RELOPT_TYPE_TERNARY;
+ relOpts[j]->namelen = strlen(relOpts[j]->name);
+ j++;
+ }
+
for (i = 0; intRelOpts[i].gen.name; i++)
{
relOpts[j] = &intRelOpts[i].gen;
@@ -800,6 +821,9 @@ allocate_reloption(bits32 kinds, int type, const char *name, const char *desc,
case RELOPT_TYPE_BOOL:
size = sizeof(relopt_bool);
break;
+ case RELOPT_TYPE_TERNARY:
+ size = sizeof(relopt_ternary);
+ break;
case RELOPT_TYPE_INT:
size = sizeof(relopt_int);
break;
@@ -883,6 +907,54 @@ add_local_bool_reloption(local_relopts *relopts, const char *name,
add_local_reloption(relopts, (relopt_gen *) newoption, offset);
}
+/*
+ * init_ternary_reloption
+ * Allocate and initialize a new ternary reloption
+ */
+static relopt_ternary *
+init_ternary_reloption(bits32 kinds, const char *name, const char *desc,
+ ternary default_val, LOCKMODE lockmode)
+{
+ relopt_ternary *newoption;
+
+ newoption = (relopt_ternary *) allocate_reloption(kinds,
+ RELOPT_TYPE_TERNARY, name, desc, lockmode);
+ newoption->default_val = default_val;
+
+ return newoption;
+}
+
+/*
+ * add_ternary_reloption
+ * Add a new ternary reloption
+ */
+void
+add_ternary_reloption(bits32 kinds, const char *name, const char *desc,
+ ternary default_val, LOCKMODE lockmode)
+{
+ relopt_ternary *newoption = init_ternary_reloption(kinds, name, desc,
+ default_val, lockmode);
+
+ add_reloption((relopt_gen *) newoption);
+}
+
+/*
+ * add_local_ternary_reloption
+ * Add a new ternary local reloption
+ *
+ * 'offset' is offset of ternary-typed field.
+ */
+void
+add_local_ternary_reloption(local_relopts *relopts, const char *name,
+ const char *desc, ternary default_val,
+ int offset)
+{
+ relopt_ternary *newoption = init_ternary_reloption(RELOPT_KIND_LOCAL,
+ name, desc,
+ default_val, 0);
+
+ add_local_reloption(relopts, (relopt_gen *) newoption, offset);
+}
/*
* init_real_reloption
@@ -1617,6 +1689,18 @@ parse_one_reloption(relopt_value *option, char *text_str, int text_len,
option->gen->name, value)));
}
break;
+ case RELOPT_TYPE_TERNARY:
+ {
+ bool b;
+ parsed = parse_bool(value, &b);
+ option->values.ternary_val = b ? TERNARY_TRUE : TERNARY_FALSE;
+ if (validate && !parsed)
+ ereport(ERROR,
+ (errcode(ERRCODE_INVALID_PARAMETER_VALUE),
+ errmsg("invalid value for ternary option \"%s\": %s",
+ option->gen->name, value)));
+ }
+ break;
case RELOPT_TYPE_INT:
{
relopt_int *optint = (relopt_int *) option->gen;
@@ -1780,17 +1864,6 @@ fillRelOptions(void *rdopts, Size basesize,
char *itempos = ((char *) rdopts) + elems[j].offset;
char *string_val;
- /*
- * If isset_offset is provided, store whether the reloption is
- * set there.
- */
- if (elems[j].isset_offset > 0)
- {
- char *setpos = ((char *) rdopts) + elems[j].isset_offset;
-
- *(bool *) setpos = options[i].isset;
- }
-
switch (options[i].gen->type)
{
case RELOPT_TYPE_BOOL:
@@ -1798,6 +1871,11 @@ fillRelOptions(void *rdopts, Size basesize,
options[i].values.bool_val :
((relopt_bool *) options[i].gen)->default_val;
break;
+ case RELOPT_TYPE_TERNARY:
+ *(ternary *) itempos = options[i].isset ?
+ options[i].values.ternary_val :
+ ((relopt_ternary *) options[i].gen)->default_val;
+ break;
case RELOPT_TYPE_INT:
*(int *) itempos = options[i].isset ?
options[i].values.int_val :
@@ -1912,8 +1990,8 @@ default_reloptions(Datum reloptions, bool validate, relopt_kind kind)
offsetof(StdRdOptions, parallel_workers)},
{"vacuum_index_cleanup", RELOPT_TYPE_ENUM,
offsetof(StdRdOptions, vacuum_index_cleanup)},
- {"vacuum_truncate", RELOPT_TYPE_BOOL,
- offsetof(StdRdOptions, vacuum_truncate), offsetof(StdRdOptions, vacuum_truncate_set)},
+ {"vacuum_truncate", RELOPT_TYPE_TERNARY,
+ offsetof(StdRdOptions, vacuum_truncate)},
{"vacuum_max_eager_freeze_failure_rate", RELOPT_TYPE_REAL,
offsetof(StdRdOptions, vacuum_max_eager_freeze_failure_rate)}
};
@@ -1993,7 +2071,6 @@ build_local_reloptions(local_relopts *relopts, Datum options, bool validate)
elems[i].optname = opt->option->name;
elems[i].opttype = opt->option->type;
elems[i].offset = opt->offset;
- elems[i].isset_offset = 0; /* not supported for local relopts yet */
i++;
}
diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c
index 733ef40ae7..977babff54 100644
--- a/src/backend/commands/vacuum.c
+++ b/src/backend/commands/vacuum.c
@@ -2221,9 +2221,9 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams params,
{
StdRdOptions *opts = (StdRdOptions *) rel->rd_options;
- if (opts && opts->vacuum_truncate_set)
+ if (opts && opts->vacuum_truncate != TERNARY_UNSET)
{
- if (opts->vacuum_truncate)
+ if (opts->vacuum_truncate == TERNARY_TRUE)
params.truncate = VACOPTVALUE_ENABLED;
else
params.truncate = VACOPTVALUE_DISABLED;
diff --git a/src/include/access/reloptions.h b/src/include/access/reloptions.h
index a604a4702c..a436697658 100644
--- a/src/include/access/reloptions.h
+++ b/src/include/access/reloptions.h
@@ -29,6 +29,7 @@
typedef enum relopt_type
{
RELOPT_TYPE_BOOL,
+ RELOPT_TYPE_TERNARY, /* on, off, unset */
RELOPT_TYPE_INT,
RELOPT_TYPE_REAL,
RELOPT_TYPE_ENUM,
@@ -80,6 +81,7 @@ typedef struct relopt_value
union
{
bool bool_val;
+ ternary ternary_val;
int int_val;
double real_val;
int enum_val;
@@ -94,6 +96,12 @@ typedef struct relopt_bool
bool default_val;
} relopt_bool;
+typedef struct relopt_rernary
+{
+ relopt_gen gen;
+ int default_val;
+} relopt_ternary;
+
typedef struct relopt_int
{
relopt_gen gen;
@@ -152,19 +160,6 @@ typedef struct
const char *optname; /* option's name */
relopt_type opttype; /* option's datatype */
int offset; /* offset of field in result struct */
-
- /*
- * isset_offset is an optional offset of a field in the result struct that
- * stores whether the option is explicitly set for the relation or if it
- * just picked up the default value. In most cases, this can be
- * accomplished by giving the reloption a special out-of-range default
- * value (e.g., some integer reloptions use -2), but this isn't always
- * possible. For example, a Boolean reloption cannot be given an
- * out-of-range default, so we need another way to discover the source of
- * its value. This offset is only used if given a value greater than
- * zero.
- */
- int isset_offset;
} relopt_parse_elt;
/* Local reloption definition */
@@ -195,6 +190,8 @@ typedef struct local_relopts
extern relopt_kind add_reloption_kind(void);
extern void add_bool_reloption(bits32 kinds, const char *name, const char *desc,
bool default_val, LOCKMODE lockmode);
+extern void add_ternary_reloption(bits32 kinds, const char *name,
+ const char *desc, int default_val, LOCKMODE lockmode);
extern void add_int_reloption(bits32 kinds, const char *name, const char *desc,
int default_val, int min_val, int max_val,
LOCKMODE lockmode);
@@ -214,6 +211,9 @@ extern void register_reloptions_validator(local_relopts *relopts,
extern void add_local_bool_reloption(local_relopts *relopts, const char *name,
const char *desc, bool default_val,
int offset);
+extern void add_local_ternary_reloption(local_relopts *relopts,
+ const char *name, const char *desc,
+ ternary default_val, int offset);
extern void add_local_int_reloption(local_relopts *relopts, const char *name,
const char *desc, int default_val,
int min_val, int max_val, int offset);
diff --git a/src/include/c.h b/src/include/c.h
index 39022f8a9d..9c59ccccf8 100644
--- a/src/include/c.h
+++ b/src/include/c.h
@@ -488,6 +488,22 @@ typedef void (*pg_funcptr_t) (void);
#include <stdbool.h>
+/*
+ * ternary
+ * Boolean value with an extrea "unset" option
+ *
+ * Ternary data type is used in relation options that can be "true", "false" or
+ * "unset". Since relation options are used deep inside the PostgreSQL code,
+ * this type is declared globally.
+*/
+
+typedef enum ternary
+{
+ TERNARY_FALSE = 0,
+ TERNARY_TRUE = 1,
+ TERNARY_UNSET = -1
+} ternary;
+
/* ----------------------------------------------------------------
* Section 3: standard system types
diff --git a/src/include/utils/rel.h b/src/include/utils/rel.h
index b552359915..7346d618f9 100644
--- a/src/include/utils/rel.h
+++ b/src/include/utils/rel.h
@@ -346,8 +346,7 @@ typedef struct StdRdOptions
bool user_catalog_table; /* use as an additional catalog relation */
int parallel_workers; /* max number of parallel workers */
StdRdOptIndexCleanup vacuum_index_cleanup; /* controls index vacuuming */
- bool vacuum_truncate; /* enables vacuum to truncate a relation */
- bool vacuum_truncate_set; /* whether vacuum_truncate is set */
+ ternary vacuum_truncate; /* enables vacuum to truncate a relation */
/*
* Fraction of pages in a relation that vacuum can eagerly scan and fail
--
2.39.2
--nextPart4155600.3ZeAukHxDK
Content-Disposition: attachment;
filename="v2a-0003-Add-alias-to-be-used-as-unset-state.patch"
Content-Transfer-Encoding: 7Bit
Content-Type: text/x-patch; charset="unicode-2-0-utf-8";
name="v2a-0003-Add-alias-to-be-used-as-unset-state.patch"
^ permalink raw reply [nested|flat] 868+ messages in thread
* [PATCH v2 2/4] Introduce ternary reloptions
@ 2025-09-04 14:41 Nikolay Shaplov <[email protected]>
0 siblings, 0 replies; 868+ messages in thread
From: Nikolay Shaplov @ 2025-09-04 14:41 UTC (permalink / raw)
Introduce ternary reloption as a replacement for current `vacuum_truncate`
implementation. Remove `vacuum_truncate_set` additional flag and using
`TERNARY_UNSET` value instead.
---
src/backend/access/common/reloptions.c | 129 ++++++++++++++++++++-----
src/backend/commands/vacuum.c | 4 +-
src/include/access/reloptions.h | 26 ++---
src/include/c.h | 16 +++
src/include/utils/rel.h | 3 +-
5 files changed, 135 insertions(+), 43 deletions(-)
diff --git a/src/backend/access/common/reloptions.c b/src/backend/access/common/reloptions.c
index 0af3fea68f..f543f11001 100644
--- a/src/backend/access/common/reloptions.c
+++ b/src/backend/access/common/reloptions.c
@@ -40,9 +40,9 @@
*
* To add an option:
*
- * (i) decide on a type (bool, integer, real, enum, string), name, default
- * value, upper and lower bounds (if applicable); for strings, consider a
- * validation routine.
+ * (i) decide on a type (bool, ternary, integer, real, enum, string), name,
+ * default value, upper and lower bounds (if applicable); for strings,
+ * consider a validation routine.
* (ii) add a record below (or use add_<type>_reloption).
* (iii) add it to the appropriate options struct (perhaps StdRdOptions)
* (iv) add it to the appropriate handling routine (perhaps
@@ -147,15 +147,6 @@ static relopt_bool boolRelOpts[] =
},
false
},
- {
- {
- "vacuum_truncate",
- "Enables vacuum to truncate empty pages at the end of this table",
- RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
- ShareUpdateExclusiveLock
- },
- true
- },
{
{
"deduplicate_items",
@@ -170,6 +161,21 @@ static relopt_bool boolRelOpts[] =
{{NULL}}
};
+static relopt_ternary ternaryRelOpts[] =
+{
+ {
+ {
+ "vacuum_truncate",
+ "Enables vacuum to truncate empty pages at the end of this table",
+ RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
+ ShareUpdateExclusiveLock
+ },
+ TERNARY_UNSET
+ },
+ /* list terminator */
+ {{NULL}}
+};
+
static relopt_int intRelOpts[] =
{
{
@@ -600,6 +606,13 @@ initialize_reloptions(void)
boolRelOpts[i].gen.lockmode));
j++;
}
+ for (i = 0; ternaryRelOpts[i].gen.name; i++)
+ {
+ Assert(DoLockModesConflict(ternaryRelOpts[i].gen.lockmode,
+ ternaryRelOpts[i].gen.lockmode));
+ j++;
+ }
+
for (i = 0; intRelOpts[i].gen.name; i++)
{
Assert(DoLockModesConflict(intRelOpts[i].gen.lockmode,
@@ -640,6 +653,14 @@ initialize_reloptions(void)
j++;
}
+ for (i = 0; ternaryRelOpts[i].gen.name; i++)
+ {
+ relOpts[j] = &ternaryRelOpts[i].gen;
+ relOpts[j]->type = RELOPT_TYPE_TERNARY;
+ relOpts[j]->namelen = strlen(relOpts[j]->name);
+ j++;
+ }
+
for (i = 0; intRelOpts[i].gen.name; i++)
{
relOpts[j] = &intRelOpts[i].gen;
@@ -800,6 +821,9 @@ allocate_reloption(bits32 kinds, int type, const char *name, const char *desc,
case RELOPT_TYPE_BOOL:
size = sizeof(relopt_bool);
break;
+ case RELOPT_TYPE_TERNARY:
+ size = sizeof(relopt_ternary);
+ break;
case RELOPT_TYPE_INT:
size = sizeof(relopt_int);
break;
@@ -883,6 +907,54 @@ add_local_bool_reloption(local_relopts *relopts, const char *name,
add_local_reloption(relopts, (relopt_gen *) newoption, offset);
}
+/*
+ * init_ternary_reloption
+ * Allocate and initialize a new ternary reloption
+ */
+static relopt_ternary *
+init_ternary_reloption(bits32 kinds, const char *name, const char *desc,
+ ternary default_val, LOCKMODE lockmode)
+{
+ relopt_ternary *newoption;
+
+ newoption = (relopt_ternary *) allocate_reloption(kinds,
+ RELOPT_TYPE_TERNARY, name, desc, lockmode);
+ newoption->default_val = default_val;
+
+ return newoption;
+}
+
+/*
+ * add_ternary_reloption
+ * Add a new ternary reloption
+ */
+void
+add_ternary_reloption(bits32 kinds, const char *name, const char *desc,
+ ternary default_val, LOCKMODE lockmode)
+{
+ relopt_ternary *newoption = init_ternary_reloption(kinds, name, desc,
+ default_val, lockmode);
+
+ add_reloption((relopt_gen *) newoption);
+}
+
+/*
+ * add_local_ternary_reloption
+ * Add a new ternary local reloption
+ *
+ * 'offset' is offset of ternary-typed field.
+ */
+void
+add_local_ternary_reloption(local_relopts *relopts, const char *name,
+ const char *desc, ternary default_val,
+ int offset)
+{
+ relopt_ternary *newoption = init_ternary_reloption(RELOPT_KIND_LOCAL,
+ name, desc,
+ default_val, 0);
+
+ add_local_reloption(relopts, (relopt_gen *) newoption, offset);
+}
/*
* init_real_reloption
@@ -1617,6 +1689,18 @@ parse_one_reloption(relopt_value *option, char *text_str, int text_len,
option->gen->name, value)));
}
break;
+ case RELOPT_TYPE_TERNARY:
+ {
+ bool b;
+ parsed = parse_bool(value, &b);
+ option->values.ternary_val = b ? TERNARY_TRUE : TERNARY_FALSE;
+ if (validate && !parsed)
+ ereport(ERROR,
+ (errcode(ERRCODE_INVALID_PARAMETER_VALUE),
+ errmsg("invalid value for ternary option \"%s\": %s",
+ option->gen->name, value)));
+ }
+ break;
case RELOPT_TYPE_INT:
{
relopt_int *optint = (relopt_int *) option->gen;
@@ -1780,17 +1864,6 @@ fillRelOptions(void *rdopts, Size basesize,
char *itempos = ((char *) rdopts) + elems[j].offset;
char *string_val;
- /*
- * If isset_offset is provided, store whether the reloption is
- * set there.
- */
- if (elems[j].isset_offset > 0)
- {
- char *setpos = ((char *) rdopts) + elems[j].isset_offset;
-
- *(bool *) setpos = options[i].isset;
- }
-
switch (options[i].gen->type)
{
case RELOPT_TYPE_BOOL:
@@ -1798,6 +1871,11 @@ fillRelOptions(void *rdopts, Size basesize,
options[i].values.bool_val :
((relopt_bool *) options[i].gen)->default_val;
break;
+ case RELOPT_TYPE_TERNARY:
+ *(ternary *) itempos = options[i].isset ?
+ options[i].values.ternary_val :
+ ((relopt_ternary *) options[i].gen)->default_val;
+ break;
case RELOPT_TYPE_INT:
*(int *) itempos = options[i].isset ?
options[i].values.int_val :
@@ -1912,8 +1990,8 @@ default_reloptions(Datum reloptions, bool validate, relopt_kind kind)
offsetof(StdRdOptions, parallel_workers)},
{"vacuum_index_cleanup", RELOPT_TYPE_ENUM,
offsetof(StdRdOptions, vacuum_index_cleanup)},
- {"vacuum_truncate", RELOPT_TYPE_BOOL,
- offsetof(StdRdOptions, vacuum_truncate), offsetof(StdRdOptions, vacuum_truncate_set)},
+ {"vacuum_truncate", RELOPT_TYPE_TERNARY,
+ offsetof(StdRdOptions, vacuum_truncate)},
{"vacuum_max_eager_freeze_failure_rate", RELOPT_TYPE_REAL,
offsetof(StdRdOptions, vacuum_max_eager_freeze_failure_rate)}
};
@@ -1993,7 +2071,6 @@ build_local_reloptions(local_relopts *relopts, Datum options, bool validate)
elems[i].optname = opt->option->name;
elems[i].opttype = opt->option->type;
elems[i].offset = opt->offset;
- elems[i].isset_offset = 0; /* not supported for local relopts yet */
i++;
}
diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c
index 733ef40ae7..977babff54 100644
--- a/src/backend/commands/vacuum.c
+++ b/src/backend/commands/vacuum.c
@@ -2221,9 +2221,9 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams params,
{
StdRdOptions *opts = (StdRdOptions *) rel->rd_options;
- if (opts && opts->vacuum_truncate_set)
+ if (opts && opts->vacuum_truncate != TERNARY_UNSET)
{
- if (opts->vacuum_truncate)
+ if (opts->vacuum_truncate == TERNARY_TRUE)
params.truncate = VACOPTVALUE_ENABLED;
else
params.truncate = VACOPTVALUE_DISABLED;
diff --git a/src/include/access/reloptions.h b/src/include/access/reloptions.h
index a604a4702c..a436697658 100644
--- a/src/include/access/reloptions.h
+++ b/src/include/access/reloptions.h
@@ -29,6 +29,7 @@
typedef enum relopt_type
{
RELOPT_TYPE_BOOL,
+ RELOPT_TYPE_TERNARY, /* on, off, unset */
RELOPT_TYPE_INT,
RELOPT_TYPE_REAL,
RELOPT_TYPE_ENUM,
@@ -80,6 +81,7 @@ typedef struct relopt_value
union
{
bool bool_val;
+ ternary ternary_val;
int int_val;
double real_val;
int enum_val;
@@ -94,6 +96,12 @@ typedef struct relopt_bool
bool default_val;
} relopt_bool;
+typedef struct relopt_rernary
+{
+ relopt_gen gen;
+ int default_val;
+} relopt_ternary;
+
typedef struct relopt_int
{
relopt_gen gen;
@@ -152,19 +160,6 @@ typedef struct
const char *optname; /* option's name */
relopt_type opttype; /* option's datatype */
int offset; /* offset of field in result struct */
-
- /*
- * isset_offset is an optional offset of a field in the result struct that
- * stores whether the option is explicitly set for the relation or if it
- * just picked up the default value. In most cases, this can be
- * accomplished by giving the reloption a special out-of-range default
- * value (e.g., some integer reloptions use -2), but this isn't always
- * possible. For example, a Boolean reloption cannot be given an
- * out-of-range default, so we need another way to discover the source of
- * its value. This offset is only used if given a value greater than
- * zero.
- */
- int isset_offset;
} relopt_parse_elt;
/* Local reloption definition */
@@ -195,6 +190,8 @@ typedef struct local_relopts
extern relopt_kind add_reloption_kind(void);
extern void add_bool_reloption(bits32 kinds, const char *name, const char *desc,
bool default_val, LOCKMODE lockmode);
+extern void add_ternary_reloption(bits32 kinds, const char *name,
+ const char *desc, int default_val, LOCKMODE lockmode);
extern void add_int_reloption(bits32 kinds, const char *name, const char *desc,
int default_val, int min_val, int max_val,
LOCKMODE lockmode);
@@ -214,6 +211,9 @@ extern void register_reloptions_validator(local_relopts *relopts,
extern void add_local_bool_reloption(local_relopts *relopts, const char *name,
const char *desc, bool default_val,
int offset);
+extern void add_local_ternary_reloption(local_relopts *relopts,
+ const char *name, const char *desc,
+ ternary default_val, int offset);
extern void add_local_int_reloption(local_relopts *relopts, const char *name,
const char *desc, int default_val,
int min_val, int max_val, int offset);
diff --git a/src/include/c.h b/src/include/c.h
index 39022f8a9d..9c59ccccf8 100644
--- a/src/include/c.h
+++ b/src/include/c.h
@@ -488,6 +488,22 @@ typedef void (*pg_funcptr_t) (void);
#include <stdbool.h>
+/*
+ * ternary
+ * Boolean value with an extrea "unset" option
+ *
+ * Ternary data type is used in relation options that can be "true", "false" or
+ * "unset". Since relation options are used deep inside the PostgreSQL code,
+ * this type is declared globally.
+*/
+
+typedef enum ternary
+{
+ TERNARY_FALSE = 0,
+ TERNARY_TRUE = 1,
+ TERNARY_UNSET = -1
+} ternary;
+
/* ----------------------------------------------------------------
* Section 3: standard system types
diff --git a/src/include/utils/rel.h b/src/include/utils/rel.h
index b552359915..7346d618f9 100644
--- a/src/include/utils/rel.h
+++ b/src/include/utils/rel.h
@@ -346,8 +346,7 @@ typedef struct StdRdOptions
bool user_catalog_table; /* use as an additional catalog relation */
int parallel_workers; /* max number of parallel workers */
StdRdOptIndexCleanup vacuum_index_cleanup; /* controls index vacuuming */
- bool vacuum_truncate; /* enables vacuum to truncate a relation */
- bool vacuum_truncate_set; /* whether vacuum_truncate is set */
+ ternary vacuum_truncate; /* enables vacuum to truncate a relation */
/*
* Fraction of pages in a relation that vacuum can eagerly scan and fail
--
2.39.2
--nextPart5129932.5fSG56mABF
Content-Disposition: attachment;
filename="v2-0003-Add-alias-to-be-used-as-unset-state.patch"
Content-Transfer-Encoding: 7Bit
Content-Type: text/x-patch; charset="unicode-2-0-utf-8";
name="v2-0003-Add-alias-to-be-used-as-unset-state.patch"
^ permalink raw reply [nested|flat] 868+ messages in thread
* [PATCH v2 2/4] Introduce ternary reloptions
@ 2025-09-04 14:41 Nikolay Shaplov <[email protected]>
0 siblings, 0 replies; 868+ messages in thread
From: Nikolay Shaplov @ 2025-09-04 14:41 UTC (permalink / raw)
Introduce ternary reloption as a replacement for current `vacuum_truncate`
implementation. Remove `vacuum_truncate_set` additional flag and using
`TERNARY_UNSET` value instead.
---
src/backend/access/common/reloptions.c | 129 ++++++++++++++++++++-----
src/backend/commands/vacuum.c | 4 +-
src/include/access/reloptions.h | 26 ++---
src/include/c.h | 16 +++
src/include/utils/rel.h | 3 +-
5 files changed, 135 insertions(+), 43 deletions(-)
diff --git a/src/backend/access/common/reloptions.c b/src/backend/access/common/reloptions.c
index 0af3fea68f..f543f11001 100644
--- a/src/backend/access/common/reloptions.c
+++ b/src/backend/access/common/reloptions.c
@@ -40,9 +40,9 @@
*
* To add an option:
*
- * (i) decide on a type (bool, integer, real, enum, string), name, default
- * value, upper and lower bounds (if applicable); for strings, consider a
- * validation routine.
+ * (i) decide on a type (bool, ternary, integer, real, enum, string), name,
+ * default value, upper and lower bounds (if applicable); for strings,
+ * consider a validation routine.
* (ii) add a record below (or use add_<type>_reloption).
* (iii) add it to the appropriate options struct (perhaps StdRdOptions)
* (iv) add it to the appropriate handling routine (perhaps
@@ -147,15 +147,6 @@ static relopt_bool boolRelOpts[] =
},
false
},
- {
- {
- "vacuum_truncate",
- "Enables vacuum to truncate empty pages at the end of this table",
- RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
- ShareUpdateExclusiveLock
- },
- true
- },
{
{
"deduplicate_items",
@@ -170,6 +161,21 @@ static relopt_bool boolRelOpts[] =
{{NULL}}
};
+static relopt_ternary ternaryRelOpts[] =
+{
+ {
+ {
+ "vacuum_truncate",
+ "Enables vacuum to truncate empty pages at the end of this table",
+ RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
+ ShareUpdateExclusiveLock
+ },
+ TERNARY_UNSET
+ },
+ /* list terminator */
+ {{NULL}}
+};
+
static relopt_int intRelOpts[] =
{
{
@@ -600,6 +606,13 @@ initialize_reloptions(void)
boolRelOpts[i].gen.lockmode));
j++;
}
+ for (i = 0; ternaryRelOpts[i].gen.name; i++)
+ {
+ Assert(DoLockModesConflict(ternaryRelOpts[i].gen.lockmode,
+ ternaryRelOpts[i].gen.lockmode));
+ j++;
+ }
+
for (i = 0; intRelOpts[i].gen.name; i++)
{
Assert(DoLockModesConflict(intRelOpts[i].gen.lockmode,
@@ -640,6 +653,14 @@ initialize_reloptions(void)
j++;
}
+ for (i = 0; ternaryRelOpts[i].gen.name; i++)
+ {
+ relOpts[j] = &ternaryRelOpts[i].gen;
+ relOpts[j]->type = RELOPT_TYPE_TERNARY;
+ relOpts[j]->namelen = strlen(relOpts[j]->name);
+ j++;
+ }
+
for (i = 0; intRelOpts[i].gen.name; i++)
{
relOpts[j] = &intRelOpts[i].gen;
@@ -800,6 +821,9 @@ allocate_reloption(bits32 kinds, int type, const char *name, const char *desc,
case RELOPT_TYPE_BOOL:
size = sizeof(relopt_bool);
break;
+ case RELOPT_TYPE_TERNARY:
+ size = sizeof(relopt_ternary);
+ break;
case RELOPT_TYPE_INT:
size = sizeof(relopt_int);
break;
@@ -883,6 +907,54 @@ add_local_bool_reloption(local_relopts *relopts, const char *name,
add_local_reloption(relopts, (relopt_gen *) newoption, offset);
}
+/*
+ * init_ternary_reloption
+ * Allocate and initialize a new ternary reloption
+ */
+static relopt_ternary *
+init_ternary_reloption(bits32 kinds, const char *name, const char *desc,
+ ternary default_val, LOCKMODE lockmode)
+{
+ relopt_ternary *newoption;
+
+ newoption = (relopt_ternary *) allocate_reloption(kinds,
+ RELOPT_TYPE_TERNARY, name, desc, lockmode);
+ newoption->default_val = default_val;
+
+ return newoption;
+}
+
+/*
+ * add_ternary_reloption
+ * Add a new ternary reloption
+ */
+void
+add_ternary_reloption(bits32 kinds, const char *name, const char *desc,
+ ternary default_val, LOCKMODE lockmode)
+{
+ relopt_ternary *newoption = init_ternary_reloption(kinds, name, desc,
+ default_val, lockmode);
+
+ add_reloption((relopt_gen *) newoption);
+}
+
+/*
+ * add_local_ternary_reloption
+ * Add a new ternary local reloption
+ *
+ * 'offset' is offset of ternary-typed field.
+ */
+void
+add_local_ternary_reloption(local_relopts *relopts, const char *name,
+ const char *desc, ternary default_val,
+ int offset)
+{
+ relopt_ternary *newoption = init_ternary_reloption(RELOPT_KIND_LOCAL,
+ name, desc,
+ default_val, 0);
+
+ add_local_reloption(relopts, (relopt_gen *) newoption, offset);
+}
/*
* init_real_reloption
@@ -1617,6 +1689,18 @@ parse_one_reloption(relopt_value *option, char *text_str, int text_len,
option->gen->name, value)));
}
break;
+ case RELOPT_TYPE_TERNARY:
+ {
+ bool b;
+ parsed = parse_bool(value, &b);
+ option->values.ternary_val = b ? TERNARY_TRUE : TERNARY_FALSE;
+ if (validate && !parsed)
+ ereport(ERROR,
+ (errcode(ERRCODE_INVALID_PARAMETER_VALUE),
+ errmsg("invalid value for ternary option \"%s\": %s",
+ option->gen->name, value)));
+ }
+ break;
case RELOPT_TYPE_INT:
{
relopt_int *optint = (relopt_int *) option->gen;
@@ -1780,17 +1864,6 @@ fillRelOptions(void *rdopts, Size basesize,
char *itempos = ((char *) rdopts) + elems[j].offset;
char *string_val;
- /*
- * If isset_offset is provided, store whether the reloption is
- * set there.
- */
- if (elems[j].isset_offset > 0)
- {
- char *setpos = ((char *) rdopts) + elems[j].isset_offset;
-
- *(bool *) setpos = options[i].isset;
- }
-
switch (options[i].gen->type)
{
case RELOPT_TYPE_BOOL:
@@ -1798,6 +1871,11 @@ fillRelOptions(void *rdopts, Size basesize,
options[i].values.bool_val :
((relopt_bool *) options[i].gen)->default_val;
break;
+ case RELOPT_TYPE_TERNARY:
+ *(ternary *) itempos = options[i].isset ?
+ options[i].values.ternary_val :
+ ((relopt_ternary *) options[i].gen)->default_val;
+ break;
case RELOPT_TYPE_INT:
*(int *) itempos = options[i].isset ?
options[i].values.int_val :
@@ -1912,8 +1990,8 @@ default_reloptions(Datum reloptions, bool validate, relopt_kind kind)
offsetof(StdRdOptions, parallel_workers)},
{"vacuum_index_cleanup", RELOPT_TYPE_ENUM,
offsetof(StdRdOptions, vacuum_index_cleanup)},
- {"vacuum_truncate", RELOPT_TYPE_BOOL,
- offsetof(StdRdOptions, vacuum_truncate), offsetof(StdRdOptions, vacuum_truncate_set)},
+ {"vacuum_truncate", RELOPT_TYPE_TERNARY,
+ offsetof(StdRdOptions, vacuum_truncate)},
{"vacuum_max_eager_freeze_failure_rate", RELOPT_TYPE_REAL,
offsetof(StdRdOptions, vacuum_max_eager_freeze_failure_rate)}
};
@@ -1993,7 +2071,6 @@ build_local_reloptions(local_relopts *relopts, Datum options, bool validate)
elems[i].optname = opt->option->name;
elems[i].opttype = opt->option->type;
elems[i].offset = opt->offset;
- elems[i].isset_offset = 0; /* not supported for local relopts yet */
i++;
}
diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c
index 733ef40ae7..977babff54 100644
--- a/src/backend/commands/vacuum.c
+++ b/src/backend/commands/vacuum.c
@@ -2221,9 +2221,9 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams params,
{
StdRdOptions *opts = (StdRdOptions *) rel->rd_options;
- if (opts && opts->vacuum_truncate_set)
+ if (opts && opts->vacuum_truncate != TERNARY_UNSET)
{
- if (opts->vacuum_truncate)
+ if (opts->vacuum_truncate == TERNARY_TRUE)
params.truncate = VACOPTVALUE_ENABLED;
else
params.truncate = VACOPTVALUE_DISABLED;
diff --git a/src/include/access/reloptions.h b/src/include/access/reloptions.h
index a604a4702c..a436697658 100644
--- a/src/include/access/reloptions.h
+++ b/src/include/access/reloptions.h
@@ -29,6 +29,7 @@
typedef enum relopt_type
{
RELOPT_TYPE_BOOL,
+ RELOPT_TYPE_TERNARY, /* on, off, unset */
RELOPT_TYPE_INT,
RELOPT_TYPE_REAL,
RELOPT_TYPE_ENUM,
@@ -80,6 +81,7 @@ typedef struct relopt_value
union
{
bool bool_val;
+ ternary ternary_val;
int int_val;
double real_val;
int enum_val;
@@ -94,6 +96,12 @@ typedef struct relopt_bool
bool default_val;
} relopt_bool;
+typedef struct relopt_rernary
+{
+ relopt_gen gen;
+ int default_val;
+} relopt_ternary;
+
typedef struct relopt_int
{
relopt_gen gen;
@@ -152,19 +160,6 @@ typedef struct
const char *optname; /* option's name */
relopt_type opttype; /* option's datatype */
int offset; /* offset of field in result struct */
-
- /*
- * isset_offset is an optional offset of a field in the result struct that
- * stores whether the option is explicitly set for the relation or if it
- * just picked up the default value. In most cases, this can be
- * accomplished by giving the reloption a special out-of-range default
- * value (e.g., some integer reloptions use -2), but this isn't always
- * possible. For example, a Boolean reloption cannot be given an
- * out-of-range default, so we need another way to discover the source of
- * its value. This offset is only used if given a value greater than
- * zero.
- */
- int isset_offset;
} relopt_parse_elt;
/* Local reloption definition */
@@ -195,6 +190,8 @@ typedef struct local_relopts
extern relopt_kind add_reloption_kind(void);
extern void add_bool_reloption(bits32 kinds, const char *name, const char *desc,
bool default_val, LOCKMODE lockmode);
+extern void add_ternary_reloption(bits32 kinds, const char *name,
+ const char *desc, int default_val, LOCKMODE lockmode);
extern void add_int_reloption(bits32 kinds, const char *name, const char *desc,
int default_val, int min_val, int max_val,
LOCKMODE lockmode);
@@ -214,6 +211,9 @@ extern void register_reloptions_validator(local_relopts *relopts,
extern void add_local_bool_reloption(local_relopts *relopts, const char *name,
const char *desc, bool default_val,
int offset);
+extern void add_local_ternary_reloption(local_relopts *relopts,
+ const char *name, const char *desc,
+ ternary default_val, int offset);
extern void add_local_int_reloption(local_relopts *relopts, const char *name,
const char *desc, int default_val,
int min_val, int max_val, int offset);
diff --git a/src/include/c.h b/src/include/c.h
index 39022f8a9d..9c59ccccf8 100644
--- a/src/include/c.h
+++ b/src/include/c.h
@@ -488,6 +488,22 @@ typedef void (*pg_funcptr_t) (void);
#include <stdbool.h>
+/*
+ * ternary
+ * Boolean value with an extrea "unset" option
+ *
+ * Ternary data type is used in relation options that can be "true", "false" or
+ * "unset". Since relation options are used deep inside the PostgreSQL code,
+ * this type is declared globally.
+*/
+
+typedef enum ternary
+{
+ TERNARY_FALSE = 0,
+ TERNARY_TRUE = 1,
+ TERNARY_UNSET = -1
+} ternary;
+
/* ----------------------------------------------------------------
* Section 3: standard system types
diff --git a/src/include/utils/rel.h b/src/include/utils/rel.h
index b552359915..7346d618f9 100644
--- a/src/include/utils/rel.h
+++ b/src/include/utils/rel.h
@@ -346,8 +346,7 @@ typedef struct StdRdOptions
bool user_catalog_table; /* use as an additional catalog relation */
int parallel_workers; /* max number of parallel workers */
StdRdOptIndexCleanup vacuum_index_cleanup; /* controls index vacuuming */
- bool vacuum_truncate; /* enables vacuum to truncate a relation */
- bool vacuum_truncate_set; /* whether vacuum_truncate is set */
+ ternary vacuum_truncate; /* enables vacuum to truncate a relation */
/*
* Fraction of pages in a relation that vacuum can eagerly scan and fail
--
2.39.2
--nextPart5129932.5fSG56mABF
Content-Disposition: attachment;
filename="v2-0003-Add-alias-to-be-used-as-unset-state.patch"
Content-Transfer-Encoding: 7Bit
Content-Type: text/x-patch; charset="unicode-2-0-utf-8";
name="v2-0003-Add-alias-to-be-used-as-unset-state.patch"
^ permalink raw reply [nested|flat] 868+ messages in thread
* [PATCH v2 2/4] Introduce ternary reloptions
@ 2025-09-04 14:41 Nikolay Shaplov <[email protected]>
0 siblings, 0 replies; 868+ messages in thread
From: Nikolay Shaplov @ 2025-09-04 14:41 UTC (permalink / raw)
Introduce ternary reloption as a replacement for current `vacuum_truncate`
implementation. Remove `vacuum_truncate_set` additional flag and using
`TERNARY_UNSET` value instead.
---
src/backend/access/common/reloptions.c | 129 ++++++++++++++++++++-----
src/backend/commands/vacuum.c | 4 +-
src/include/access/reloptions.h | 26 ++---
src/include/c.h | 16 +++
src/include/utils/rel.h | 3 +-
5 files changed, 135 insertions(+), 43 deletions(-)
diff --git a/src/backend/access/common/reloptions.c b/src/backend/access/common/reloptions.c
index 0af3fea68f..f543f11001 100644
--- a/src/backend/access/common/reloptions.c
+++ b/src/backend/access/common/reloptions.c
@@ -40,9 +40,9 @@
*
* To add an option:
*
- * (i) decide on a type (bool, integer, real, enum, string), name, default
- * value, upper and lower bounds (if applicable); for strings, consider a
- * validation routine.
+ * (i) decide on a type (bool, ternary, integer, real, enum, string), name,
+ * default value, upper and lower bounds (if applicable); for strings,
+ * consider a validation routine.
* (ii) add a record below (or use add_<type>_reloption).
* (iii) add it to the appropriate options struct (perhaps StdRdOptions)
* (iv) add it to the appropriate handling routine (perhaps
@@ -147,15 +147,6 @@ static relopt_bool boolRelOpts[] =
},
false
},
- {
- {
- "vacuum_truncate",
- "Enables vacuum to truncate empty pages at the end of this table",
- RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
- ShareUpdateExclusiveLock
- },
- true
- },
{
{
"deduplicate_items",
@@ -170,6 +161,21 @@ static relopt_bool boolRelOpts[] =
{{NULL}}
};
+static relopt_ternary ternaryRelOpts[] =
+{
+ {
+ {
+ "vacuum_truncate",
+ "Enables vacuum to truncate empty pages at the end of this table",
+ RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
+ ShareUpdateExclusiveLock
+ },
+ TERNARY_UNSET
+ },
+ /* list terminator */
+ {{NULL}}
+};
+
static relopt_int intRelOpts[] =
{
{
@@ -600,6 +606,13 @@ initialize_reloptions(void)
boolRelOpts[i].gen.lockmode));
j++;
}
+ for (i = 0; ternaryRelOpts[i].gen.name; i++)
+ {
+ Assert(DoLockModesConflict(ternaryRelOpts[i].gen.lockmode,
+ ternaryRelOpts[i].gen.lockmode));
+ j++;
+ }
+
for (i = 0; intRelOpts[i].gen.name; i++)
{
Assert(DoLockModesConflict(intRelOpts[i].gen.lockmode,
@@ -640,6 +653,14 @@ initialize_reloptions(void)
j++;
}
+ for (i = 0; ternaryRelOpts[i].gen.name; i++)
+ {
+ relOpts[j] = &ternaryRelOpts[i].gen;
+ relOpts[j]->type = RELOPT_TYPE_TERNARY;
+ relOpts[j]->namelen = strlen(relOpts[j]->name);
+ j++;
+ }
+
for (i = 0; intRelOpts[i].gen.name; i++)
{
relOpts[j] = &intRelOpts[i].gen;
@@ -800,6 +821,9 @@ allocate_reloption(bits32 kinds, int type, const char *name, const char *desc,
case RELOPT_TYPE_BOOL:
size = sizeof(relopt_bool);
break;
+ case RELOPT_TYPE_TERNARY:
+ size = sizeof(relopt_ternary);
+ break;
case RELOPT_TYPE_INT:
size = sizeof(relopt_int);
break;
@@ -883,6 +907,54 @@ add_local_bool_reloption(local_relopts *relopts, const char *name,
add_local_reloption(relopts, (relopt_gen *) newoption, offset);
}
+/*
+ * init_ternary_reloption
+ * Allocate and initialize a new ternary reloption
+ */
+static relopt_ternary *
+init_ternary_reloption(bits32 kinds, const char *name, const char *desc,
+ ternary default_val, LOCKMODE lockmode)
+{
+ relopt_ternary *newoption;
+
+ newoption = (relopt_ternary *) allocate_reloption(kinds,
+ RELOPT_TYPE_TERNARY, name, desc, lockmode);
+ newoption->default_val = default_val;
+
+ return newoption;
+}
+
+/*
+ * add_ternary_reloption
+ * Add a new ternary reloption
+ */
+void
+add_ternary_reloption(bits32 kinds, const char *name, const char *desc,
+ ternary default_val, LOCKMODE lockmode)
+{
+ relopt_ternary *newoption = init_ternary_reloption(kinds, name, desc,
+ default_val, lockmode);
+
+ add_reloption((relopt_gen *) newoption);
+}
+
+/*
+ * add_local_ternary_reloption
+ * Add a new ternary local reloption
+ *
+ * 'offset' is offset of ternary-typed field.
+ */
+void
+add_local_ternary_reloption(local_relopts *relopts, const char *name,
+ const char *desc, ternary default_val,
+ int offset)
+{
+ relopt_ternary *newoption = init_ternary_reloption(RELOPT_KIND_LOCAL,
+ name, desc,
+ default_val, 0);
+
+ add_local_reloption(relopts, (relopt_gen *) newoption, offset);
+}
/*
* init_real_reloption
@@ -1617,6 +1689,18 @@ parse_one_reloption(relopt_value *option, char *text_str, int text_len,
option->gen->name, value)));
}
break;
+ case RELOPT_TYPE_TERNARY:
+ {
+ bool b;
+ parsed = parse_bool(value, &b);
+ option->values.ternary_val = b ? TERNARY_TRUE : TERNARY_FALSE;
+ if (validate && !parsed)
+ ereport(ERROR,
+ (errcode(ERRCODE_INVALID_PARAMETER_VALUE),
+ errmsg("invalid value for ternary option \"%s\": %s",
+ option->gen->name, value)));
+ }
+ break;
case RELOPT_TYPE_INT:
{
relopt_int *optint = (relopt_int *) option->gen;
@@ -1780,17 +1864,6 @@ fillRelOptions(void *rdopts, Size basesize,
char *itempos = ((char *) rdopts) + elems[j].offset;
char *string_val;
- /*
- * If isset_offset is provided, store whether the reloption is
- * set there.
- */
- if (elems[j].isset_offset > 0)
- {
- char *setpos = ((char *) rdopts) + elems[j].isset_offset;
-
- *(bool *) setpos = options[i].isset;
- }
-
switch (options[i].gen->type)
{
case RELOPT_TYPE_BOOL:
@@ -1798,6 +1871,11 @@ fillRelOptions(void *rdopts, Size basesize,
options[i].values.bool_val :
((relopt_bool *) options[i].gen)->default_val;
break;
+ case RELOPT_TYPE_TERNARY:
+ *(ternary *) itempos = options[i].isset ?
+ options[i].values.ternary_val :
+ ((relopt_ternary *) options[i].gen)->default_val;
+ break;
case RELOPT_TYPE_INT:
*(int *) itempos = options[i].isset ?
options[i].values.int_val :
@@ -1912,8 +1990,8 @@ default_reloptions(Datum reloptions, bool validate, relopt_kind kind)
offsetof(StdRdOptions, parallel_workers)},
{"vacuum_index_cleanup", RELOPT_TYPE_ENUM,
offsetof(StdRdOptions, vacuum_index_cleanup)},
- {"vacuum_truncate", RELOPT_TYPE_BOOL,
- offsetof(StdRdOptions, vacuum_truncate), offsetof(StdRdOptions, vacuum_truncate_set)},
+ {"vacuum_truncate", RELOPT_TYPE_TERNARY,
+ offsetof(StdRdOptions, vacuum_truncate)},
{"vacuum_max_eager_freeze_failure_rate", RELOPT_TYPE_REAL,
offsetof(StdRdOptions, vacuum_max_eager_freeze_failure_rate)}
};
@@ -1993,7 +2071,6 @@ build_local_reloptions(local_relopts *relopts, Datum options, bool validate)
elems[i].optname = opt->option->name;
elems[i].opttype = opt->option->type;
elems[i].offset = opt->offset;
- elems[i].isset_offset = 0; /* not supported for local relopts yet */
i++;
}
diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c
index 733ef40ae7..977babff54 100644
--- a/src/backend/commands/vacuum.c
+++ b/src/backend/commands/vacuum.c
@@ -2221,9 +2221,9 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams params,
{
StdRdOptions *opts = (StdRdOptions *) rel->rd_options;
- if (opts && opts->vacuum_truncate_set)
+ if (opts && opts->vacuum_truncate != TERNARY_UNSET)
{
- if (opts->vacuum_truncate)
+ if (opts->vacuum_truncate == TERNARY_TRUE)
params.truncate = VACOPTVALUE_ENABLED;
else
params.truncate = VACOPTVALUE_DISABLED;
diff --git a/src/include/access/reloptions.h b/src/include/access/reloptions.h
index a604a4702c..a436697658 100644
--- a/src/include/access/reloptions.h
+++ b/src/include/access/reloptions.h
@@ -29,6 +29,7 @@
typedef enum relopt_type
{
RELOPT_TYPE_BOOL,
+ RELOPT_TYPE_TERNARY, /* on, off, unset */
RELOPT_TYPE_INT,
RELOPT_TYPE_REAL,
RELOPT_TYPE_ENUM,
@@ -80,6 +81,7 @@ typedef struct relopt_value
union
{
bool bool_val;
+ ternary ternary_val;
int int_val;
double real_val;
int enum_val;
@@ -94,6 +96,12 @@ typedef struct relopt_bool
bool default_val;
} relopt_bool;
+typedef struct relopt_rernary
+{
+ relopt_gen gen;
+ int default_val;
+} relopt_ternary;
+
typedef struct relopt_int
{
relopt_gen gen;
@@ -152,19 +160,6 @@ typedef struct
const char *optname; /* option's name */
relopt_type opttype; /* option's datatype */
int offset; /* offset of field in result struct */
-
- /*
- * isset_offset is an optional offset of a field in the result struct that
- * stores whether the option is explicitly set for the relation or if it
- * just picked up the default value. In most cases, this can be
- * accomplished by giving the reloption a special out-of-range default
- * value (e.g., some integer reloptions use -2), but this isn't always
- * possible. For example, a Boolean reloption cannot be given an
- * out-of-range default, so we need another way to discover the source of
- * its value. This offset is only used if given a value greater than
- * zero.
- */
- int isset_offset;
} relopt_parse_elt;
/* Local reloption definition */
@@ -195,6 +190,8 @@ typedef struct local_relopts
extern relopt_kind add_reloption_kind(void);
extern void add_bool_reloption(bits32 kinds, const char *name, const char *desc,
bool default_val, LOCKMODE lockmode);
+extern void add_ternary_reloption(bits32 kinds, const char *name,
+ const char *desc, int default_val, LOCKMODE lockmode);
extern void add_int_reloption(bits32 kinds, const char *name, const char *desc,
int default_val, int min_val, int max_val,
LOCKMODE lockmode);
@@ -214,6 +211,9 @@ extern void register_reloptions_validator(local_relopts *relopts,
extern void add_local_bool_reloption(local_relopts *relopts, const char *name,
const char *desc, bool default_val,
int offset);
+extern void add_local_ternary_reloption(local_relopts *relopts,
+ const char *name, const char *desc,
+ ternary default_val, int offset);
extern void add_local_int_reloption(local_relopts *relopts, const char *name,
const char *desc, int default_val,
int min_val, int max_val, int offset);
diff --git a/src/include/c.h b/src/include/c.h
index 39022f8a9d..9c59ccccf8 100644
--- a/src/include/c.h
+++ b/src/include/c.h
@@ -488,6 +488,22 @@ typedef void (*pg_funcptr_t) (void);
#include <stdbool.h>
+/*
+ * ternary
+ * Boolean value with an extrea "unset" option
+ *
+ * Ternary data type is used in relation options that can be "true", "false" or
+ * "unset". Since relation options are used deep inside the PostgreSQL code,
+ * this type is declared globally.
+*/
+
+typedef enum ternary
+{
+ TERNARY_FALSE = 0,
+ TERNARY_TRUE = 1,
+ TERNARY_UNSET = -1
+} ternary;
+
/* ----------------------------------------------------------------
* Section 3: standard system types
diff --git a/src/include/utils/rel.h b/src/include/utils/rel.h
index b552359915..7346d618f9 100644
--- a/src/include/utils/rel.h
+++ b/src/include/utils/rel.h
@@ -346,8 +346,7 @@ typedef struct StdRdOptions
bool user_catalog_table; /* use as an additional catalog relation */
int parallel_workers; /* max number of parallel workers */
StdRdOptIndexCleanup vacuum_index_cleanup; /* controls index vacuuming */
- bool vacuum_truncate; /* enables vacuum to truncate a relation */
- bool vacuum_truncate_set; /* whether vacuum_truncate is set */
+ ternary vacuum_truncate; /* enables vacuum to truncate a relation */
/*
* Fraction of pages in a relation that vacuum can eagerly scan and fail
--
2.39.2
--nextPart5129932.5fSG56mABF
Content-Disposition: attachment;
filename="v2-0003-Add-alias-to-be-used-as-unset-state.patch"
Content-Transfer-Encoding: 7Bit
Content-Type: text/x-patch; charset="unicode-2-0-utf-8";
name="v2-0003-Add-alias-to-be-used-as-unset-state.patch"
^ permalink raw reply [nested|flat] 868+ messages in thread
* [PATCH v2a 2/4] Introduce ternary reloptions
@ 2025-09-04 14:41 Nikolay Shaplov <[email protected]>
0 siblings, 0 replies; 868+ messages in thread
From: Nikolay Shaplov @ 2025-09-04 14:41 UTC (permalink / raw)
Introduce ternary reloption as a replacement for current `vacuum_truncate`
implementation. Remove `vacuum_truncate_set` additional flag and using
`TERNARY_UNSET` value instead.
---
src/backend/access/common/reloptions.c | 129 ++++++++++++++++++++-----
src/backend/commands/vacuum.c | 4 +-
src/include/access/reloptions.h | 26 ++---
src/include/c.h | 16 +++
src/include/utils/rel.h | 3 +-
5 files changed, 135 insertions(+), 43 deletions(-)
diff --git a/src/backend/access/common/reloptions.c b/src/backend/access/common/reloptions.c
index 0af3fea68f..f543f11001 100644
--- a/src/backend/access/common/reloptions.c
+++ b/src/backend/access/common/reloptions.c
@@ -40,9 +40,9 @@
*
* To add an option:
*
- * (i) decide on a type (bool, integer, real, enum, string), name, default
- * value, upper and lower bounds (if applicable); for strings, consider a
- * validation routine.
+ * (i) decide on a type (bool, ternary, integer, real, enum, string), name,
+ * default value, upper and lower bounds (if applicable); for strings,
+ * consider a validation routine.
* (ii) add a record below (or use add_<type>_reloption).
* (iii) add it to the appropriate options struct (perhaps StdRdOptions)
* (iv) add it to the appropriate handling routine (perhaps
@@ -147,15 +147,6 @@ static relopt_bool boolRelOpts[] =
},
false
},
- {
- {
- "vacuum_truncate",
- "Enables vacuum to truncate empty pages at the end of this table",
- RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
- ShareUpdateExclusiveLock
- },
- true
- },
{
{
"deduplicate_items",
@@ -170,6 +161,21 @@ static relopt_bool boolRelOpts[] =
{{NULL}}
};
+static relopt_ternary ternaryRelOpts[] =
+{
+ {
+ {
+ "vacuum_truncate",
+ "Enables vacuum to truncate empty pages at the end of this table",
+ RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
+ ShareUpdateExclusiveLock
+ },
+ TERNARY_UNSET
+ },
+ /* list terminator */
+ {{NULL}}
+};
+
static relopt_int intRelOpts[] =
{
{
@@ -600,6 +606,13 @@ initialize_reloptions(void)
boolRelOpts[i].gen.lockmode));
j++;
}
+ for (i = 0; ternaryRelOpts[i].gen.name; i++)
+ {
+ Assert(DoLockModesConflict(ternaryRelOpts[i].gen.lockmode,
+ ternaryRelOpts[i].gen.lockmode));
+ j++;
+ }
+
for (i = 0; intRelOpts[i].gen.name; i++)
{
Assert(DoLockModesConflict(intRelOpts[i].gen.lockmode,
@@ -640,6 +653,14 @@ initialize_reloptions(void)
j++;
}
+ for (i = 0; ternaryRelOpts[i].gen.name; i++)
+ {
+ relOpts[j] = &ternaryRelOpts[i].gen;
+ relOpts[j]->type = RELOPT_TYPE_TERNARY;
+ relOpts[j]->namelen = strlen(relOpts[j]->name);
+ j++;
+ }
+
for (i = 0; intRelOpts[i].gen.name; i++)
{
relOpts[j] = &intRelOpts[i].gen;
@@ -800,6 +821,9 @@ allocate_reloption(bits32 kinds, int type, const char *name, const char *desc,
case RELOPT_TYPE_BOOL:
size = sizeof(relopt_bool);
break;
+ case RELOPT_TYPE_TERNARY:
+ size = sizeof(relopt_ternary);
+ break;
case RELOPT_TYPE_INT:
size = sizeof(relopt_int);
break;
@@ -883,6 +907,54 @@ add_local_bool_reloption(local_relopts *relopts, const char *name,
add_local_reloption(relopts, (relopt_gen *) newoption, offset);
}
+/*
+ * init_ternary_reloption
+ * Allocate and initialize a new ternary reloption
+ */
+static relopt_ternary *
+init_ternary_reloption(bits32 kinds, const char *name, const char *desc,
+ ternary default_val, LOCKMODE lockmode)
+{
+ relopt_ternary *newoption;
+
+ newoption = (relopt_ternary *) allocate_reloption(kinds,
+ RELOPT_TYPE_TERNARY, name, desc, lockmode);
+ newoption->default_val = default_val;
+
+ return newoption;
+}
+
+/*
+ * add_ternary_reloption
+ * Add a new ternary reloption
+ */
+void
+add_ternary_reloption(bits32 kinds, const char *name, const char *desc,
+ ternary default_val, LOCKMODE lockmode)
+{
+ relopt_ternary *newoption = init_ternary_reloption(kinds, name, desc,
+ default_val, lockmode);
+
+ add_reloption((relopt_gen *) newoption);
+}
+
+/*
+ * add_local_ternary_reloption
+ * Add a new ternary local reloption
+ *
+ * 'offset' is offset of ternary-typed field.
+ */
+void
+add_local_ternary_reloption(local_relopts *relopts, const char *name,
+ const char *desc, ternary default_val,
+ int offset)
+{
+ relopt_ternary *newoption = init_ternary_reloption(RELOPT_KIND_LOCAL,
+ name, desc,
+ default_val, 0);
+
+ add_local_reloption(relopts, (relopt_gen *) newoption, offset);
+}
/*
* init_real_reloption
@@ -1617,6 +1689,18 @@ parse_one_reloption(relopt_value *option, char *text_str, int text_len,
option->gen->name, value)));
}
break;
+ case RELOPT_TYPE_TERNARY:
+ {
+ bool b;
+ parsed = parse_bool(value, &b);
+ option->values.ternary_val = b ? TERNARY_TRUE : TERNARY_FALSE;
+ if (validate && !parsed)
+ ereport(ERROR,
+ (errcode(ERRCODE_INVALID_PARAMETER_VALUE),
+ errmsg("invalid value for ternary option \"%s\": %s",
+ option->gen->name, value)));
+ }
+ break;
case RELOPT_TYPE_INT:
{
relopt_int *optint = (relopt_int *) option->gen;
@@ -1780,17 +1864,6 @@ fillRelOptions(void *rdopts, Size basesize,
char *itempos = ((char *) rdopts) + elems[j].offset;
char *string_val;
- /*
- * If isset_offset is provided, store whether the reloption is
- * set there.
- */
- if (elems[j].isset_offset > 0)
- {
- char *setpos = ((char *) rdopts) + elems[j].isset_offset;
-
- *(bool *) setpos = options[i].isset;
- }
-
switch (options[i].gen->type)
{
case RELOPT_TYPE_BOOL:
@@ -1798,6 +1871,11 @@ fillRelOptions(void *rdopts, Size basesize,
options[i].values.bool_val :
((relopt_bool *) options[i].gen)->default_val;
break;
+ case RELOPT_TYPE_TERNARY:
+ *(ternary *) itempos = options[i].isset ?
+ options[i].values.ternary_val :
+ ((relopt_ternary *) options[i].gen)->default_val;
+ break;
case RELOPT_TYPE_INT:
*(int *) itempos = options[i].isset ?
options[i].values.int_val :
@@ -1912,8 +1990,8 @@ default_reloptions(Datum reloptions, bool validate, relopt_kind kind)
offsetof(StdRdOptions, parallel_workers)},
{"vacuum_index_cleanup", RELOPT_TYPE_ENUM,
offsetof(StdRdOptions, vacuum_index_cleanup)},
- {"vacuum_truncate", RELOPT_TYPE_BOOL,
- offsetof(StdRdOptions, vacuum_truncate), offsetof(StdRdOptions, vacuum_truncate_set)},
+ {"vacuum_truncate", RELOPT_TYPE_TERNARY,
+ offsetof(StdRdOptions, vacuum_truncate)},
{"vacuum_max_eager_freeze_failure_rate", RELOPT_TYPE_REAL,
offsetof(StdRdOptions, vacuum_max_eager_freeze_failure_rate)}
};
@@ -1993,7 +2071,6 @@ build_local_reloptions(local_relopts *relopts, Datum options, bool validate)
elems[i].optname = opt->option->name;
elems[i].opttype = opt->option->type;
elems[i].offset = opt->offset;
- elems[i].isset_offset = 0; /* not supported for local relopts yet */
i++;
}
diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c
index 733ef40ae7..977babff54 100644
--- a/src/backend/commands/vacuum.c
+++ b/src/backend/commands/vacuum.c
@@ -2221,9 +2221,9 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams params,
{
StdRdOptions *opts = (StdRdOptions *) rel->rd_options;
- if (opts && opts->vacuum_truncate_set)
+ if (opts && opts->vacuum_truncate != TERNARY_UNSET)
{
- if (opts->vacuum_truncate)
+ if (opts->vacuum_truncate == TERNARY_TRUE)
params.truncate = VACOPTVALUE_ENABLED;
else
params.truncate = VACOPTVALUE_DISABLED;
diff --git a/src/include/access/reloptions.h b/src/include/access/reloptions.h
index a604a4702c..a436697658 100644
--- a/src/include/access/reloptions.h
+++ b/src/include/access/reloptions.h
@@ -29,6 +29,7 @@
typedef enum relopt_type
{
RELOPT_TYPE_BOOL,
+ RELOPT_TYPE_TERNARY, /* on, off, unset */
RELOPT_TYPE_INT,
RELOPT_TYPE_REAL,
RELOPT_TYPE_ENUM,
@@ -80,6 +81,7 @@ typedef struct relopt_value
union
{
bool bool_val;
+ ternary ternary_val;
int int_val;
double real_val;
int enum_val;
@@ -94,6 +96,12 @@ typedef struct relopt_bool
bool default_val;
} relopt_bool;
+typedef struct relopt_rernary
+{
+ relopt_gen gen;
+ int default_val;
+} relopt_ternary;
+
typedef struct relopt_int
{
relopt_gen gen;
@@ -152,19 +160,6 @@ typedef struct
const char *optname; /* option's name */
relopt_type opttype; /* option's datatype */
int offset; /* offset of field in result struct */
-
- /*
- * isset_offset is an optional offset of a field in the result struct that
- * stores whether the option is explicitly set for the relation or if it
- * just picked up the default value. In most cases, this can be
- * accomplished by giving the reloption a special out-of-range default
- * value (e.g., some integer reloptions use -2), but this isn't always
- * possible. For example, a Boolean reloption cannot be given an
- * out-of-range default, so we need another way to discover the source of
- * its value. This offset is only used if given a value greater than
- * zero.
- */
- int isset_offset;
} relopt_parse_elt;
/* Local reloption definition */
@@ -195,6 +190,8 @@ typedef struct local_relopts
extern relopt_kind add_reloption_kind(void);
extern void add_bool_reloption(bits32 kinds, const char *name, const char *desc,
bool default_val, LOCKMODE lockmode);
+extern void add_ternary_reloption(bits32 kinds, const char *name,
+ const char *desc, int default_val, LOCKMODE lockmode);
extern void add_int_reloption(bits32 kinds, const char *name, const char *desc,
int default_val, int min_val, int max_val,
LOCKMODE lockmode);
@@ -214,6 +211,9 @@ extern void register_reloptions_validator(local_relopts *relopts,
extern void add_local_bool_reloption(local_relopts *relopts, const char *name,
const char *desc, bool default_val,
int offset);
+extern void add_local_ternary_reloption(local_relopts *relopts,
+ const char *name, const char *desc,
+ ternary default_val, int offset);
extern void add_local_int_reloption(local_relopts *relopts, const char *name,
const char *desc, int default_val,
int min_val, int max_val, int offset);
diff --git a/src/include/c.h b/src/include/c.h
index 39022f8a9d..9c59ccccf8 100644
--- a/src/include/c.h
+++ b/src/include/c.h
@@ -488,6 +488,22 @@ typedef void (*pg_funcptr_t) (void);
#include <stdbool.h>
+/*
+ * ternary
+ * Boolean value with an extrea "unset" option
+ *
+ * Ternary data type is used in relation options that can be "true", "false" or
+ * "unset". Since relation options are used deep inside the PostgreSQL code,
+ * this type is declared globally.
+*/
+
+typedef enum ternary
+{
+ TERNARY_FALSE = 0,
+ TERNARY_TRUE = 1,
+ TERNARY_UNSET = -1
+} ternary;
+
/* ----------------------------------------------------------------
* Section 3: standard system types
diff --git a/src/include/utils/rel.h b/src/include/utils/rel.h
index b552359915..7346d618f9 100644
--- a/src/include/utils/rel.h
+++ b/src/include/utils/rel.h
@@ -346,8 +346,7 @@ typedef struct StdRdOptions
bool user_catalog_table; /* use as an additional catalog relation */
int parallel_workers; /* max number of parallel workers */
StdRdOptIndexCleanup vacuum_index_cleanup; /* controls index vacuuming */
- bool vacuum_truncate; /* enables vacuum to truncate a relation */
- bool vacuum_truncate_set; /* whether vacuum_truncate is set */
+ ternary vacuum_truncate; /* enables vacuum to truncate a relation */
/*
* Fraction of pages in a relation that vacuum can eagerly scan and fail
--
2.39.2
--nextPart4155600.3ZeAukHxDK
Content-Disposition: attachment;
filename="v2a-0003-Add-alias-to-be-used-as-unset-state.patch"
Content-Transfer-Encoding: 7Bit
Content-Type: text/x-patch; charset="unicode-2-0-utf-8";
name="v2a-0003-Add-alias-to-be-used-as-unset-state.patch"
^ permalink raw reply [nested|flat] 868+ messages in thread
* [PATCH v2 2/4] Introduce ternary reloptions
@ 2025-09-04 14:41 Nikolay Shaplov <[email protected]>
0 siblings, 0 replies; 868+ messages in thread
From: Nikolay Shaplov @ 2025-09-04 14:41 UTC (permalink / raw)
Introduce ternary reloption as a replacement for current `vacuum_truncate`
implementation. Remove `vacuum_truncate_set` additional flag and using
`TERNARY_UNSET` value instead.
---
src/backend/access/common/reloptions.c | 129 ++++++++++++++++++++-----
src/backend/commands/vacuum.c | 4 +-
src/include/access/reloptions.h | 26 ++---
src/include/c.h | 16 +++
src/include/utils/rel.h | 3 +-
5 files changed, 135 insertions(+), 43 deletions(-)
diff --git a/src/backend/access/common/reloptions.c b/src/backend/access/common/reloptions.c
index 0af3fea68f..f543f11001 100644
--- a/src/backend/access/common/reloptions.c
+++ b/src/backend/access/common/reloptions.c
@@ -40,9 +40,9 @@
*
* To add an option:
*
- * (i) decide on a type (bool, integer, real, enum, string), name, default
- * value, upper and lower bounds (if applicable); for strings, consider a
- * validation routine.
+ * (i) decide on a type (bool, ternary, integer, real, enum, string), name,
+ * default value, upper and lower bounds (if applicable); for strings,
+ * consider a validation routine.
* (ii) add a record below (or use add_<type>_reloption).
* (iii) add it to the appropriate options struct (perhaps StdRdOptions)
* (iv) add it to the appropriate handling routine (perhaps
@@ -147,15 +147,6 @@ static relopt_bool boolRelOpts[] =
},
false
},
- {
- {
- "vacuum_truncate",
- "Enables vacuum to truncate empty pages at the end of this table",
- RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
- ShareUpdateExclusiveLock
- },
- true
- },
{
{
"deduplicate_items",
@@ -170,6 +161,21 @@ static relopt_bool boolRelOpts[] =
{{NULL}}
};
+static relopt_ternary ternaryRelOpts[] =
+{
+ {
+ {
+ "vacuum_truncate",
+ "Enables vacuum to truncate empty pages at the end of this table",
+ RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
+ ShareUpdateExclusiveLock
+ },
+ TERNARY_UNSET
+ },
+ /* list terminator */
+ {{NULL}}
+};
+
static relopt_int intRelOpts[] =
{
{
@@ -600,6 +606,13 @@ initialize_reloptions(void)
boolRelOpts[i].gen.lockmode));
j++;
}
+ for (i = 0; ternaryRelOpts[i].gen.name; i++)
+ {
+ Assert(DoLockModesConflict(ternaryRelOpts[i].gen.lockmode,
+ ternaryRelOpts[i].gen.lockmode));
+ j++;
+ }
+
for (i = 0; intRelOpts[i].gen.name; i++)
{
Assert(DoLockModesConflict(intRelOpts[i].gen.lockmode,
@@ -640,6 +653,14 @@ initialize_reloptions(void)
j++;
}
+ for (i = 0; ternaryRelOpts[i].gen.name; i++)
+ {
+ relOpts[j] = &ternaryRelOpts[i].gen;
+ relOpts[j]->type = RELOPT_TYPE_TERNARY;
+ relOpts[j]->namelen = strlen(relOpts[j]->name);
+ j++;
+ }
+
for (i = 0; intRelOpts[i].gen.name; i++)
{
relOpts[j] = &intRelOpts[i].gen;
@@ -800,6 +821,9 @@ allocate_reloption(bits32 kinds, int type, const char *name, const char *desc,
case RELOPT_TYPE_BOOL:
size = sizeof(relopt_bool);
break;
+ case RELOPT_TYPE_TERNARY:
+ size = sizeof(relopt_ternary);
+ break;
case RELOPT_TYPE_INT:
size = sizeof(relopt_int);
break;
@@ -883,6 +907,54 @@ add_local_bool_reloption(local_relopts *relopts, const char *name,
add_local_reloption(relopts, (relopt_gen *) newoption, offset);
}
+/*
+ * init_ternary_reloption
+ * Allocate and initialize a new ternary reloption
+ */
+static relopt_ternary *
+init_ternary_reloption(bits32 kinds, const char *name, const char *desc,
+ ternary default_val, LOCKMODE lockmode)
+{
+ relopt_ternary *newoption;
+
+ newoption = (relopt_ternary *) allocate_reloption(kinds,
+ RELOPT_TYPE_TERNARY, name, desc, lockmode);
+ newoption->default_val = default_val;
+
+ return newoption;
+}
+
+/*
+ * add_ternary_reloption
+ * Add a new ternary reloption
+ */
+void
+add_ternary_reloption(bits32 kinds, const char *name, const char *desc,
+ ternary default_val, LOCKMODE lockmode)
+{
+ relopt_ternary *newoption = init_ternary_reloption(kinds, name, desc,
+ default_val, lockmode);
+
+ add_reloption((relopt_gen *) newoption);
+}
+
+/*
+ * add_local_ternary_reloption
+ * Add a new ternary local reloption
+ *
+ * 'offset' is offset of ternary-typed field.
+ */
+void
+add_local_ternary_reloption(local_relopts *relopts, const char *name,
+ const char *desc, ternary default_val,
+ int offset)
+{
+ relopt_ternary *newoption = init_ternary_reloption(RELOPT_KIND_LOCAL,
+ name, desc,
+ default_val, 0);
+
+ add_local_reloption(relopts, (relopt_gen *) newoption, offset);
+}
/*
* init_real_reloption
@@ -1617,6 +1689,18 @@ parse_one_reloption(relopt_value *option, char *text_str, int text_len,
option->gen->name, value)));
}
break;
+ case RELOPT_TYPE_TERNARY:
+ {
+ bool b;
+ parsed = parse_bool(value, &b);
+ option->values.ternary_val = b ? TERNARY_TRUE : TERNARY_FALSE;
+ if (validate && !parsed)
+ ereport(ERROR,
+ (errcode(ERRCODE_INVALID_PARAMETER_VALUE),
+ errmsg("invalid value for ternary option \"%s\": %s",
+ option->gen->name, value)));
+ }
+ break;
case RELOPT_TYPE_INT:
{
relopt_int *optint = (relopt_int *) option->gen;
@@ -1780,17 +1864,6 @@ fillRelOptions(void *rdopts, Size basesize,
char *itempos = ((char *) rdopts) + elems[j].offset;
char *string_val;
- /*
- * If isset_offset is provided, store whether the reloption is
- * set there.
- */
- if (elems[j].isset_offset > 0)
- {
- char *setpos = ((char *) rdopts) + elems[j].isset_offset;
-
- *(bool *) setpos = options[i].isset;
- }
-
switch (options[i].gen->type)
{
case RELOPT_TYPE_BOOL:
@@ -1798,6 +1871,11 @@ fillRelOptions(void *rdopts, Size basesize,
options[i].values.bool_val :
((relopt_bool *) options[i].gen)->default_val;
break;
+ case RELOPT_TYPE_TERNARY:
+ *(ternary *) itempos = options[i].isset ?
+ options[i].values.ternary_val :
+ ((relopt_ternary *) options[i].gen)->default_val;
+ break;
case RELOPT_TYPE_INT:
*(int *) itempos = options[i].isset ?
options[i].values.int_val :
@@ -1912,8 +1990,8 @@ default_reloptions(Datum reloptions, bool validate, relopt_kind kind)
offsetof(StdRdOptions, parallel_workers)},
{"vacuum_index_cleanup", RELOPT_TYPE_ENUM,
offsetof(StdRdOptions, vacuum_index_cleanup)},
- {"vacuum_truncate", RELOPT_TYPE_BOOL,
- offsetof(StdRdOptions, vacuum_truncate), offsetof(StdRdOptions, vacuum_truncate_set)},
+ {"vacuum_truncate", RELOPT_TYPE_TERNARY,
+ offsetof(StdRdOptions, vacuum_truncate)},
{"vacuum_max_eager_freeze_failure_rate", RELOPT_TYPE_REAL,
offsetof(StdRdOptions, vacuum_max_eager_freeze_failure_rate)}
};
@@ -1993,7 +2071,6 @@ build_local_reloptions(local_relopts *relopts, Datum options, bool validate)
elems[i].optname = opt->option->name;
elems[i].opttype = opt->option->type;
elems[i].offset = opt->offset;
- elems[i].isset_offset = 0; /* not supported for local relopts yet */
i++;
}
diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c
index 733ef40ae7..977babff54 100644
--- a/src/backend/commands/vacuum.c
+++ b/src/backend/commands/vacuum.c
@@ -2221,9 +2221,9 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams params,
{
StdRdOptions *opts = (StdRdOptions *) rel->rd_options;
- if (opts && opts->vacuum_truncate_set)
+ if (opts && opts->vacuum_truncate != TERNARY_UNSET)
{
- if (opts->vacuum_truncate)
+ if (opts->vacuum_truncate == TERNARY_TRUE)
params.truncate = VACOPTVALUE_ENABLED;
else
params.truncate = VACOPTVALUE_DISABLED;
diff --git a/src/include/access/reloptions.h b/src/include/access/reloptions.h
index a604a4702c..a436697658 100644
--- a/src/include/access/reloptions.h
+++ b/src/include/access/reloptions.h
@@ -29,6 +29,7 @@
typedef enum relopt_type
{
RELOPT_TYPE_BOOL,
+ RELOPT_TYPE_TERNARY, /* on, off, unset */
RELOPT_TYPE_INT,
RELOPT_TYPE_REAL,
RELOPT_TYPE_ENUM,
@@ -80,6 +81,7 @@ typedef struct relopt_value
union
{
bool bool_val;
+ ternary ternary_val;
int int_val;
double real_val;
int enum_val;
@@ -94,6 +96,12 @@ typedef struct relopt_bool
bool default_val;
} relopt_bool;
+typedef struct relopt_rernary
+{
+ relopt_gen gen;
+ int default_val;
+} relopt_ternary;
+
typedef struct relopt_int
{
relopt_gen gen;
@@ -152,19 +160,6 @@ typedef struct
const char *optname; /* option's name */
relopt_type opttype; /* option's datatype */
int offset; /* offset of field in result struct */
-
- /*
- * isset_offset is an optional offset of a field in the result struct that
- * stores whether the option is explicitly set for the relation or if it
- * just picked up the default value. In most cases, this can be
- * accomplished by giving the reloption a special out-of-range default
- * value (e.g., some integer reloptions use -2), but this isn't always
- * possible. For example, a Boolean reloption cannot be given an
- * out-of-range default, so we need another way to discover the source of
- * its value. This offset is only used if given a value greater than
- * zero.
- */
- int isset_offset;
} relopt_parse_elt;
/* Local reloption definition */
@@ -195,6 +190,8 @@ typedef struct local_relopts
extern relopt_kind add_reloption_kind(void);
extern void add_bool_reloption(bits32 kinds, const char *name, const char *desc,
bool default_val, LOCKMODE lockmode);
+extern void add_ternary_reloption(bits32 kinds, const char *name,
+ const char *desc, int default_val, LOCKMODE lockmode);
extern void add_int_reloption(bits32 kinds, const char *name, const char *desc,
int default_val, int min_val, int max_val,
LOCKMODE lockmode);
@@ -214,6 +211,9 @@ extern void register_reloptions_validator(local_relopts *relopts,
extern void add_local_bool_reloption(local_relopts *relopts, const char *name,
const char *desc, bool default_val,
int offset);
+extern void add_local_ternary_reloption(local_relopts *relopts,
+ const char *name, const char *desc,
+ ternary default_val, int offset);
extern void add_local_int_reloption(local_relopts *relopts, const char *name,
const char *desc, int default_val,
int min_val, int max_val, int offset);
diff --git a/src/include/c.h b/src/include/c.h
index 39022f8a9d..9c59ccccf8 100644
--- a/src/include/c.h
+++ b/src/include/c.h
@@ -488,6 +488,22 @@ typedef void (*pg_funcptr_t) (void);
#include <stdbool.h>
+/*
+ * ternary
+ * Boolean value with an extrea "unset" option
+ *
+ * Ternary data type is used in relation options that can be "true", "false" or
+ * "unset". Since relation options are used deep inside the PostgreSQL code,
+ * this type is declared globally.
+*/
+
+typedef enum ternary
+{
+ TERNARY_FALSE = 0,
+ TERNARY_TRUE = 1,
+ TERNARY_UNSET = -1
+} ternary;
+
/* ----------------------------------------------------------------
* Section 3: standard system types
diff --git a/src/include/utils/rel.h b/src/include/utils/rel.h
index b552359915..7346d618f9 100644
--- a/src/include/utils/rel.h
+++ b/src/include/utils/rel.h
@@ -346,8 +346,7 @@ typedef struct StdRdOptions
bool user_catalog_table; /* use as an additional catalog relation */
int parallel_workers; /* max number of parallel workers */
StdRdOptIndexCleanup vacuum_index_cleanup; /* controls index vacuuming */
- bool vacuum_truncate; /* enables vacuum to truncate a relation */
- bool vacuum_truncate_set; /* whether vacuum_truncate is set */
+ ternary vacuum_truncate; /* enables vacuum to truncate a relation */
/*
* Fraction of pages in a relation that vacuum can eagerly scan and fail
--
2.39.2
--nextPart5129932.5fSG56mABF
Content-Disposition: attachment;
filename="v2-0003-Add-alias-to-be-used-as-unset-state.patch"
Content-Transfer-Encoding: 7Bit
Content-Type: text/x-patch; charset="unicode-2-0-utf-8";
name="v2-0003-Add-alias-to-be-used-as-unset-state.patch"
^ permalink raw reply [nested|flat] 868+ messages in thread
* [PATCH v2 2/4] Introduce ternary reloptions
@ 2025-09-04 14:41 Nikolay Shaplov <[email protected]>
0 siblings, 0 replies; 868+ messages in thread
From: Nikolay Shaplov @ 2025-09-04 14:41 UTC (permalink / raw)
Introduce ternary reloption as a replacement for current `vacuum_truncate`
implementation. Remove `vacuum_truncate_set` additional flag and using
`TERNARY_UNSET` value instead.
---
src/backend/access/common/reloptions.c | 129 ++++++++++++++++++++-----
src/backend/commands/vacuum.c | 4 +-
src/include/access/reloptions.h | 26 ++---
src/include/c.h | 16 +++
src/include/utils/rel.h | 3 +-
5 files changed, 135 insertions(+), 43 deletions(-)
diff --git a/src/backend/access/common/reloptions.c b/src/backend/access/common/reloptions.c
index 0af3fea68f..f543f11001 100644
--- a/src/backend/access/common/reloptions.c
+++ b/src/backend/access/common/reloptions.c
@@ -40,9 +40,9 @@
*
* To add an option:
*
- * (i) decide on a type (bool, integer, real, enum, string), name, default
- * value, upper and lower bounds (if applicable); for strings, consider a
- * validation routine.
+ * (i) decide on a type (bool, ternary, integer, real, enum, string), name,
+ * default value, upper and lower bounds (if applicable); for strings,
+ * consider a validation routine.
* (ii) add a record below (or use add_<type>_reloption).
* (iii) add it to the appropriate options struct (perhaps StdRdOptions)
* (iv) add it to the appropriate handling routine (perhaps
@@ -147,15 +147,6 @@ static relopt_bool boolRelOpts[] =
},
false
},
- {
- {
- "vacuum_truncate",
- "Enables vacuum to truncate empty pages at the end of this table",
- RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
- ShareUpdateExclusiveLock
- },
- true
- },
{
{
"deduplicate_items",
@@ -170,6 +161,21 @@ static relopt_bool boolRelOpts[] =
{{NULL}}
};
+static relopt_ternary ternaryRelOpts[] =
+{
+ {
+ {
+ "vacuum_truncate",
+ "Enables vacuum to truncate empty pages at the end of this table",
+ RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
+ ShareUpdateExclusiveLock
+ },
+ TERNARY_UNSET
+ },
+ /* list terminator */
+ {{NULL}}
+};
+
static relopt_int intRelOpts[] =
{
{
@@ -600,6 +606,13 @@ initialize_reloptions(void)
boolRelOpts[i].gen.lockmode));
j++;
}
+ for (i = 0; ternaryRelOpts[i].gen.name; i++)
+ {
+ Assert(DoLockModesConflict(ternaryRelOpts[i].gen.lockmode,
+ ternaryRelOpts[i].gen.lockmode));
+ j++;
+ }
+
for (i = 0; intRelOpts[i].gen.name; i++)
{
Assert(DoLockModesConflict(intRelOpts[i].gen.lockmode,
@@ -640,6 +653,14 @@ initialize_reloptions(void)
j++;
}
+ for (i = 0; ternaryRelOpts[i].gen.name; i++)
+ {
+ relOpts[j] = &ternaryRelOpts[i].gen;
+ relOpts[j]->type = RELOPT_TYPE_TERNARY;
+ relOpts[j]->namelen = strlen(relOpts[j]->name);
+ j++;
+ }
+
for (i = 0; intRelOpts[i].gen.name; i++)
{
relOpts[j] = &intRelOpts[i].gen;
@@ -800,6 +821,9 @@ allocate_reloption(bits32 kinds, int type, const char *name, const char *desc,
case RELOPT_TYPE_BOOL:
size = sizeof(relopt_bool);
break;
+ case RELOPT_TYPE_TERNARY:
+ size = sizeof(relopt_ternary);
+ break;
case RELOPT_TYPE_INT:
size = sizeof(relopt_int);
break;
@@ -883,6 +907,54 @@ add_local_bool_reloption(local_relopts *relopts, const char *name,
add_local_reloption(relopts, (relopt_gen *) newoption, offset);
}
+/*
+ * init_ternary_reloption
+ * Allocate and initialize a new ternary reloption
+ */
+static relopt_ternary *
+init_ternary_reloption(bits32 kinds, const char *name, const char *desc,
+ ternary default_val, LOCKMODE lockmode)
+{
+ relopt_ternary *newoption;
+
+ newoption = (relopt_ternary *) allocate_reloption(kinds,
+ RELOPT_TYPE_TERNARY, name, desc, lockmode);
+ newoption->default_val = default_val;
+
+ return newoption;
+}
+
+/*
+ * add_ternary_reloption
+ * Add a new ternary reloption
+ */
+void
+add_ternary_reloption(bits32 kinds, const char *name, const char *desc,
+ ternary default_val, LOCKMODE lockmode)
+{
+ relopt_ternary *newoption = init_ternary_reloption(kinds, name, desc,
+ default_val, lockmode);
+
+ add_reloption((relopt_gen *) newoption);
+}
+
+/*
+ * add_local_ternary_reloption
+ * Add a new ternary local reloption
+ *
+ * 'offset' is offset of ternary-typed field.
+ */
+void
+add_local_ternary_reloption(local_relopts *relopts, const char *name,
+ const char *desc, ternary default_val,
+ int offset)
+{
+ relopt_ternary *newoption = init_ternary_reloption(RELOPT_KIND_LOCAL,
+ name, desc,
+ default_val, 0);
+
+ add_local_reloption(relopts, (relopt_gen *) newoption, offset);
+}
/*
* init_real_reloption
@@ -1617,6 +1689,18 @@ parse_one_reloption(relopt_value *option, char *text_str, int text_len,
option->gen->name, value)));
}
break;
+ case RELOPT_TYPE_TERNARY:
+ {
+ bool b;
+ parsed = parse_bool(value, &b);
+ option->values.ternary_val = b ? TERNARY_TRUE : TERNARY_FALSE;
+ if (validate && !parsed)
+ ereport(ERROR,
+ (errcode(ERRCODE_INVALID_PARAMETER_VALUE),
+ errmsg("invalid value for ternary option \"%s\": %s",
+ option->gen->name, value)));
+ }
+ break;
case RELOPT_TYPE_INT:
{
relopt_int *optint = (relopt_int *) option->gen;
@@ -1780,17 +1864,6 @@ fillRelOptions(void *rdopts, Size basesize,
char *itempos = ((char *) rdopts) + elems[j].offset;
char *string_val;
- /*
- * If isset_offset is provided, store whether the reloption is
- * set there.
- */
- if (elems[j].isset_offset > 0)
- {
- char *setpos = ((char *) rdopts) + elems[j].isset_offset;
-
- *(bool *) setpos = options[i].isset;
- }
-
switch (options[i].gen->type)
{
case RELOPT_TYPE_BOOL:
@@ -1798,6 +1871,11 @@ fillRelOptions(void *rdopts, Size basesize,
options[i].values.bool_val :
((relopt_bool *) options[i].gen)->default_val;
break;
+ case RELOPT_TYPE_TERNARY:
+ *(ternary *) itempos = options[i].isset ?
+ options[i].values.ternary_val :
+ ((relopt_ternary *) options[i].gen)->default_val;
+ break;
case RELOPT_TYPE_INT:
*(int *) itempos = options[i].isset ?
options[i].values.int_val :
@@ -1912,8 +1990,8 @@ default_reloptions(Datum reloptions, bool validate, relopt_kind kind)
offsetof(StdRdOptions, parallel_workers)},
{"vacuum_index_cleanup", RELOPT_TYPE_ENUM,
offsetof(StdRdOptions, vacuum_index_cleanup)},
- {"vacuum_truncate", RELOPT_TYPE_BOOL,
- offsetof(StdRdOptions, vacuum_truncate), offsetof(StdRdOptions, vacuum_truncate_set)},
+ {"vacuum_truncate", RELOPT_TYPE_TERNARY,
+ offsetof(StdRdOptions, vacuum_truncate)},
{"vacuum_max_eager_freeze_failure_rate", RELOPT_TYPE_REAL,
offsetof(StdRdOptions, vacuum_max_eager_freeze_failure_rate)}
};
@@ -1993,7 +2071,6 @@ build_local_reloptions(local_relopts *relopts, Datum options, bool validate)
elems[i].optname = opt->option->name;
elems[i].opttype = opt->option->type;
elems[i].offset = opt->offset;
- elems[i].isset_offset = 0; /* not supported for local relopts yet */
i++;
}
diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c
index 733ef40ae7..977babff54 100644
--- a/src/backend/commands/vacuum.c
+++ b/src/backend/commands/vacuum.c
@@ -2221,9 +2221,9 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams params,
{
StdRdOptions *opts = (StdRdOptions *) rel->rd_options;
- if (opts && opts->vacuum_truncate_set)
+ if (opts && opts->vacuum_truncate != TERNARY_UNSET)
{
- if (opts->vacuum_truncate)
+ if (opts->vacuum_truncate == TERNARY_TRUE)
params.truncate = VACOPTVALUE_ENABLED;
else
params.truncate = VACOPTVALUE_DISABLED;
diff --git a/src/include/access/reloptions.h b/src/include/access/reloptions.h
index a604a4702c..a436697658 100644
--- a/src/include/access/reloptions.h
+++ b/src/include/access/reloptions.h
@@ -29,6 +29,7 @@
typedef enum relopt_type
{
RELOPT_TYPE_BOOL,
+ RELOPT_TYPE_TERNARY, /* on, off, unset */
RELOPT_TYPE_INT,
RELOPT_TYPE_REAL,
RELOPT_TYPE_ENUM,
@@ -80,6 +81,7 @@ typedef struct relopt_value
union
{
bool bool_val;
+ ternary ternary_val;
int int_val;
double real_val;
int enum_val;
@@ -94,6 +96,12 @@ typedef struct relopt_bool
bool default_val;
} relopt_bool;
+typedef struct relopt_rernary
+{
+ relopt_gen gen;
+ int default_val;
+} relopt_ternary;
+
typedef struct relopt_int
{
relopt_gen gen;
@@ -152,19 +160,6 @@ typedef struct
const char *optname; /* option's name */
relopt_type opttype; /* option's datatype */
int offset; /* offset of field in result struct */
-
- /*
- * isset_offset is an optional offset of a field in the result struct that
- * stores whether the option is explicitly set for the relation or if it
- * just picked up the default value. In most cases, this can be
- * accomplished by giving the reloption a special out-of-range default
- * value (e.g., some integer reloptions use -2), but this isn't always
- * possible. For example, a Boolean reloption cannot be given an
- * out-of-range default, so we need another way to discover the source of
- * its value. This offset is only used if given a value greater than
- * zero.
- */
- int isset_offset;
} relopt_parse_elt;
/* Local reloption definition */
@@ -195,6 +190,8 @@ typedef struct local_relopts
extern relopt_kind add_reloption_kind(void);
extern void add_bool_reloption(bits32 kinds, const char *name, const char *desc,
bool default_val, LOCKMODE lockmode);
+extern void add_ternary_reloption(bits32 kinds, const char *name,
+ const char *desc, int default_val, LOCKMODE lockmode);
extern void add_int_reloption(bits32 kinds, const char *name, const char *desc,
int default_val, int min_val, int max_val,
LOCKMODE lockmode);
@@ -214,6 +211,9 @@ extern void register_reloptions_validator(local_relopts *relopts,
extern void add_local_bool_reloption(local_relopts *relopts, const char *name,
const char *desc, bool default_val,
int offset);
+extern void add_local_ternary_reloption(local_relopts *relopts,
+ const char *name, const char *desc,
+ ternary default_val, int offset);
extern void add_local_int_reloption(local_relopts *relopts, const char *name,
const char *desc, int default_val,
int min_val, int max_val, int offset);
diff --git a/src/include/c.h b/src/include/c.h
index 39022f8a9d..9c59ccccf8 100644
--- a/src/include/c.h
+++ b/src/include/c.h
@@ -488,6 +488,22 @@ typedef void (*pg_funcptr_t) (void);
#include <stdbool.h>
+/*
+ * ternary
+ * Boolean value with an extrea "unset" option
+ *
+ * Ternary data type is used in relation options that can be "true", "false" or
+ * "unset". Since relation options are used deep inside the PostgreSQL code,
+ * this type is declared globally.
+*/
+
+typedef enum ternary
+{
+ TERNARY_FALSE = 0,
+ TERNARY_TRUE = 1,
+ TERNARY_UNSET = -1
+} ternary;
+
/* ----------------------------------------------------------------
* Section 3: standard system types
diff --git a/src/include/utils/rel.h b/src/include/utils/rel.h
index b552359915..7346d618f9 100644
--- a/src/include/utils/rel.h
+++ b/src/include/utils/rel.h
@@ -346,8 +346,7 @@ typedef struct StdRdOptions
bool user_catalog_table; /* use as an additional catalog relation */
int parallel_workers; /* max number of parallel workers */
StdRdOptIndexCleanup vacuum_index_cleanup; /* controls index vacuuming */
- bool vacuum_truncate; /* enables vacuum to truncate a relation */
- bool vacuum_truncate_set; /* whether vacuum_truncate is set */
+ ternary vacuum_truncate; /* enables vacuum to truncate a relation */
/*
* Fraction of pages in a relation that vacuum can eagerly scan and fail
--
2.39.2
--nextPart5129932.5fSG56mABF
Content-Disposition: attachment;
filename="v2-0003-Add-alias-to-be-used-as-unset-state.patch"
Content-Transfer-Encoding: 7Bit
Content-Type: text/x-patch; charset="unicode-2-0-utf-8";
name="v2-0003-Add-alias-to-be-used-as-unset-state.patch"
^ permalink raw reply [nested|flat] 868+ messages in thread
* [PATCH v2a 2/4] Introduce ternary reloptions
@ 2025-09-04 14:41 Nikolay Shaplov <[email protected]>
0 siblings, 0 replies; 868+ messages in thread
From: Nikolay Shaplov @ 2025-09-04 14:41 UTC (permalink / raw)
Introduce ternary reloption as a replacement for current `vacuum_truncate`
implementation. Remove `vacuum_truncate_set` additional flag and using
`TERNARY_UNSET` value instead.
---
src/backend/access/common/reloptions.c | 129 ++++++++++++++++++++-----
src/backend/commands/vacuum.c | 4 +-
src/include/access/reloptions.h | 26 ++---
src/include/c.h | 16 +++
src/include/utils/rel.h | 3 +-
5 files changed, 135 insertions(+), 43 deletions(-)
diff --git a/src/backend/access/common/reloptions.c b/src/backend/access/common/reloptions.c
index 0af3fea68f..f543f11001 100644
--- a/src/backend/access/common/reloptions.c
+++ b/src/backend/access/common/reloptions.c
@@ -40,9 +40,9 @@
*
* To add an option:
*
- * (i) decide on a type (bool, integer, real, enum, string), name, default
- * value, upper and lower bounds (if applicable); for strings, consider a
- * validation routine.
+ * (i) decide on a type (bool, ternary, integer, real, enum, string), name,
+ * default value, upper and lower bounds (if applicable); for strings,
+ * consider a validation routine.
* (ii) add a record below (or use add_<type>_reloption).
* (iii) add it to the appropriate options struct (perhaps StdRdOptions)
* (iv) add it to the appropriate handling routine (perhaps
@@ -147,15 +147,6 @@ static relopt_bool boolRelOpts[] =
},
false
},
- {
- {
- "vacuum_truncate",
- "Enables vacuum to truncate empty pages at the end of this table",
- RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
- ShareUpdateExclusiveLock
- },
- true
- },
{
{
"deduplicate_items",
@@ -170,6 +161,21 @@ static relopt_bool boolRelOpts[] =
{{NULL}}
};
+static relopt_ternary ternaryRelOpts[] =
+{
+ {
+ {
+ "vacuum_truncate",
+ "Enables vacuum to truncate empty pages at the end of this table",
+ RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
+ ShareUpdateExclusiveLock
+ },
+ TERNARY_UNSET
+ },
+ /* list terminator */
+ {{NULL}}
+};
+
static relopt_int intRelOpts[] =
{
{
@@ -600,6 +606,13 @@ initialize_reloptions(void)
boolRelOpts[i].gen.lockmode));
j++;
}
+ for (i = 0; ternaryRelOpts[i].gen.name; i++)
+ {
+ Assert(DoLockModesConflict(ternaryRelOpts[i].gen.lockmode,
+ ternaryRelOpts[i].gen.lockmode));
+ j++;
+ }
+
for (i = 0; intRelOpts[i].gen.name; i++)
{
Assert(DoLockModesConflict(intRelOpts[i].gen.lockmode,
@@ -640,6 +653,14 @@ initialize_reloptions(void)
j++;
}
+ for (i = 0; ternaryRelOpts[i].gen.name; i++)
+ {
+ relOpts[j] = &ternaryRelOpts[i].gen;
+ relOpts[j]->type = RELOPT_TYPE_TERNARY;
+ relOpts[j]->namelen = strlen(relOpts[j]->name);
+ j++;
+ }
+
for (i = 0; intRelOpts[i].gen.name; i++)
{
relOpts[j] = &intRelOpts[i].gen;
@@ -800,6 +821,9 @@ allocate_reloption(bits32 kinds, int type, const char *name, const char *desc,
case RELOPT_TYPE_BOOL:
size = sizeof(relopt_bool);
break;
+ case RELOPT_TYPE_TERNARY:
+ size = sizeof(relopt_ternary);
+ break;
case RELOPT_TYPE_INT:
size = sizeof(relopt_int);
break;
@@ -883,6 +907,54 @@ add_local_bool_reloption(local_relopts *relopts, const char *name,
add_local_reloption(relopts, (relopt_gen *) newoption, offset);
}
+/*
+ * init_ternary_reloption
+ * Allocate and initialize a new ternary reloption
+ */
+static relopt_ternary *
+init_ternary_reloption(bits32 kinds, const char *name, const char *desc,
+ ternary default_val, LOCKMODE lockmode)
+{
+ relopt_ternary *newoption;
+
+ newoption = (relopt_ternary *) allocate_reloption(kinds,
+ RELOPT_TYPE_TERNARY, name, desc, lockmode);
+ newoption->default_val = default_val;
+
+ return newoption;
+}
+
+/*
+ * add_ternary_reloption
+ * Add a new ternary reloption
+ */
+void
+add_ternary_reloption(bits32 kinds, const char *name, const char *desc,
+ ternary default_val, LOCKMODE lockmode)
+{
+ relopt_ternary *newoption = init_ternary_reloption(kinds, name, desc,
+ default_val, lockmode);
+
+ add_reloption((relopt_gen *) newoption);
+}
+
+/*
+ * add_local_ternary_reloption
+ * Add a new ternary local reloption
+ *
+ * 'offset' is offset of ternary-typed field.
+ */
+void
+add_local_ternary_reloption(local_relopts *relopts, const char *name,
+ const char *desc, ternary default_val,
+ int offset)
+{
+ relopt_ternary *newoption = init_ternary_reloption(RELOPT_KIND_LOCAL,
+ name, desc,
+ default_val, 0);
+
+ add_local_reloption(relopts, (relopt_gen *) newoption, offset);
+}
/*
* init_real_reloption
@@ -1617,6 +1689,18 @@ parse_one_reloption(relopt_value *option, char *text_str, int text_len,
option->gen->name, value)));
}
break;
+ case RELOPT_TYPE_TERNARY:
+ {
+ bool b;
+ parsed = parse_bool(value, &b);
+ option->values.ternary_val = b ? TERNARY_TRUE : TERNARY_FALSE;
+ if (validate && !parsed)
+ ereport(ERROR,
+ (errcode(ERRCODE_INVALID_PARAMETER_VALUE),
+ errmsg("invalid value for ternary option \"%s\": %s",
+ option->gen->name, value)));
+ }
+ break;
case RELOPT_TYPE_INT:
{
relopt_int *optint = (relopt_int *) option->gen;
@@ -1780,17 +1864,6 @@ fillRelOptions(void *rdopts, Size basesize,
char *itempos = ((char *) rdopts) + elems[j].offset;
char *string_val;
- /*
- * If isset_offset is provided, store whether the reloption is
- * set there.
- */
- if (elems[j].isset_offset > 0)
- {
- char *setpos = ((char *) rdopts) + elems[j].isset_offset;
-
- *(bool *) setpos = options[i].isset;
- }
-
switch (options[i].gen->type)
{
case RELOPT_TYPE_BOOL:
@@ -1798,6 +1871,11 @@ fillRelOptions(void *rdopts, Size basesize,
options[i].values.bool_val :
((relopt_bool *) options[i].gen)->default_val;
break;
+ case RELOPT_TYPE_TERNARY:
+ *(ternary *) itempos = options[i].isset ?
+ options[i].values.ternary_val :
+ ((relopt_ternary *) options[i].gen)->default_val;
+ break;
case RELOPT_TYPE_INT:
*(int *) itempos = options[i].isset ?
options[i].values.int_val :
@@ -1912,8 +1990,8 @@ default_reloptions(Datum reloptions, bool validate, relopt_kind kind)
offsetof(StdRdOptions, parallel_workers)},
{"vacuum_index_cleanup", RELOPT_TYPE_ENUM,
offsetof(StdRdOptions, vacuum_index_cleanup)},
- {"vacuum_truncate", RELOPT_TYPE_BOOL,
- offsetof(StdRdOptions, vacuum_truncate), offsetof(StdRdOptions, vacuum_truncate_set)},
+ {"vacuum_truncate", RELOPT_TYPE_TERNARY,
+ offsetof(StdRdOptions, vacuum_truncate)},
{"vacuum_max_eager_freeze_failure_rate", RELOPT_TYPE_REAL,
offsetof(StdRdOptions, vacuum_max_eager_freeze_failure_rate)}
};
@@ -1993,7 +2071,6 @@ build_local_reloptions(local_relopts *relopts, Datum options, bool validate)
elems[i].optname = opt->option->name;
elems[i].opttype = opt->option->type;
elems[i].offset = opt->offset;
- elems[i].isset_offset = 0; /* not supported for local relopts yet */
i++;
}
diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c
index 733ef40ae7..977babff54 100644
--- a/src/backend/commands/vacuum.c
+++ b/src/backend/commands/vacuum.c
@@ -2221,9 +2221,9 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams params,
{
StdRdOptions *opts = (StdRdOptions *) rel->rd_options;
- if (opts && opts->vacuum_truncate_set)
+ if (opts && opts->vacuum_truncate != TERNARY_UNSET)
{
- if (opts->vacuum_truncate)
+ if (opts->vacuum_truncate == TERNARY_TRUE)
params.truncate = VACOPTVALUE_ENABLED;
else
params.truncate = VACOPTVALUE_DISABLED;
diff --git a/src/include/access/reloptions.h b/src/include/access/reloptions.h
index a604a4702c..a436697658 100644
--- a/src/include/access/reloptions.h
+++ b/src/include/access/reloptions.h
@@ -29,6 +29,7 @@
typedef enum relopt_type
{
RELOPT_TYPE_BOOL,
+ RELOPT_TYPE_TERNARY, /* on, off, unset */
RELOPT_TYPE_INT,
RELOPT_TYPE_REAL,
RELOPT_TYPE_ENUM,
@@ -80,6 +81,7 @@ typedef struct relopt_value
union
{
bool bool_val;
+ ternary ternary_val;
int int_val;
double real_val;
int enum_val;
@@ -94,6 +96,12 @@ typedef struct relopt_bool
bool default_val;
} relopt_bool;
+typedef struct relopt_rernary
+{
+ relopt_gen gen;
+ int default_val;
+} relopt_ternary;
+
typedef struct relopt_int
{
relopt_gen gen;
@@ -152,19 +160,6 @@ typedef struct
const char *optname; /* option's name */
relopt_type opttype; /* option's datatype */
int offset; /* offset of field in result struct */
-
- /*
- * isset_offset is an optional offset of a field in the result struct that
- * stores whether the option is explicitly set for the relation or if it
- * just picked up the default value. In most cases, this can be
- * accomplished by giving the reloption a special out-of-range default
- * value (e.g., some integer reloptions use -2), but this isn't always
- * possible. For example, a Boolean reloption cannot be given an
- * out-of-range default, so we need another way to discover the source of
- * its value. This offset is only used if given a value greater than
- * zero.
- */
- int isset_offset;
} relopt_parse_elt;
/* Local reloption definition */
@@ -195,6 +190,8 @@ typedef struct local_relopts
extern relopt_kind add_reloption_kind(void);
extern void add_bool_reloption(bits32 kinds, const char *name, const char *desc,
bool default_val, LOCKMODE lockmode);
+extern void add_ternary_reloption(bits32 kinds, const char *name,
+ const char *desc, int default_val, LOCKMODE lockmode);
extern void add_int_reloption(bits32 kinds, const char *name, const char *desc,
int default_val, int min_val, int max_val,
LOCKMODE lockmode);
@@ -214,6 +211,9 @@ extern void register_reloptions_validator(local_relopts *relopts,
extern void add_local_bool_reloption(local_relopts *relopts, const char *name,
const char *desc, bool default_val,
int offset);
+extern void add_local_ternary_reloption(local_relopts *relopts,
+ const char *name, const char *desc,
+ ternary default_val, int offset);
extern void add_local_int_reloption(local_relopts *relopts, const char *name,
const char *desc, int default_val,
int min_val, int max_val, int offset);
diff --git a/src/include/c.h b/src/include/c.h
index 39022f8a9d..9c59ccccf8 100644
--- a/src/include/c.h
+++ b/src/include/c.h
@@ -488,6 +488,22 @@ typedef void (*pg_funcptr_t) (void);
#include <stdbool.h>
+/*
+ * ternary
+ * Boolean value with an extrea "unset" option
+ *
+ * Ternary data type is used in relation options that can be "true", "false" or
+ * "unset". Since relation options are used deep inside the PostgreSQL code,
+ * this type is declared globally.
+*/
+
+typedef enum ternary
+{
+ TERNARY_FALSE = 0,
+ TERNARY_TRUE = 1,
+ TERNARY_UNSET = -1
+} ternary;
+
/* ----------------------------------------------------------------
* Section 3: standard system types
diff --git a/src/include/utils/rel.h b/src/include/utils/rel.h
index b552359915..7346d618f9 100644
--- a/src/include/utils/rel.h
+++ b/src/include/utils/rel.h
@@ -346,8 +346,7 @@ typedef struct StdRdOptions
bool user_catalog_table; /* use as an additional catalog relation */
int parallel_workers; /* max number of parallel workers */
StdRdOptIndexCleanup vacuum_index_cleanup; /* controls index vacuuming */
- bool vacuum_truncate; /* enables vacuum to truncate a relation */
- bool vacuum_truncate_set; /* whether vacuum_truncate is set */
+ ternary vacuum_truncate; /* enables vacuum to truncate a relation */
/*
* Fraction of pages in a relation that vacuum can eagerly scan and fail
--
2.39.2
--nextPart4155600.3ZeAukHxDK
Content-Disposition: attachment;
filename="v2a-0003-Add-alias-to-be-used-as-unset-state.patch"
Content-Transfer-Encoding: 7Bit
Content-Type: text/x-patch; charset="unicode-2-0-utf-8";
name="v2a-0003-Add-alias-to-be-used-as-unset-state.patch"
^ permalink raw reply [nested|flat] 868+ messages in thread
* [PATCH v2a 2/4] Introduce ternary reloptions
@ 2025-09-04 14:41 Nikolay Shaplov <[email protected]>
0 siblings, 0 replies; 868+ messages in thread
From: Nikolay Shaplov @ 2025-09-04 14:41 UTC (permalink / raw)
Introduce ternary reloption as a replacement for current `vacuum_truncate`
implementation. Remove `vacuum_truncate_set` additional flag and using
`TERNARY_UNSET` value instead.
---
src/backend/access/common/reloptions.c | 129 ++++++++++++++++++++-----
src/backend/commands/vacuum.c | 4 +-
src/include/access/reloptions.h | 26 ++---
src/include/c.h | 16 +++
src/include/utils/rel.h | 3 +-
5 files changed, 135 insertions(+), 43 deletions(-)
diff --git a/src/backend/access/common/reloptions.c b/src/backend/access/common/reloptions.c
index 0af3fea68f..f543f11001 100644
--- a/src/backend/access/common/reloptions.c
+++ b/src/backend/access/common/reloptions.c
@@ -40,9 +40,9 @@
*
* To add an option:
*
- * (i) decide on a type (bool, integer, real, enum, string), name, default
- * value, upper and lower bounds (if applicable); for strings, consider a
- * validation routine.
+ * (i) decide on a type (bool, ternary, integer, real, enum, string), name,
+ * default value, upper and lower bounds (if applicable); for strings,
+ * consider a validation routine.
* (ii) add a record below (or use add_<type>_reloption).
* (iii) add it to the appropriate options struct (perhaps StdRdOptions)
* (iv) add it to the appropriate handling routine (perhaps
@@ -147,15 +147,6 @@ static relopt_bool boolRelOpts[] =
},
false
},
- {
- {
- "vacuum_truncate",
- "Enables vacuum to truncate empty pages at the end of this table",
- RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
- ShareUpdateExclusiveLock
- },
- true
- },
{
{
"deduplicate_items",
@@ -170,6 +161,21 @@ static relopt_bool boolRelOpts[] =
{{NULL}}
};
+static relopt_ternary ternaryRelOpts[] =
+{
+ {
+ {
+ "vacuum_truncate",
+ "Enables vacuum to truncate empty pages at the end of this table",
+ RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
+ ShareUpdateExclusiveLock
+ },
+ TERNARY_UNSET
+ },
+ /* list terminator */
+ {{NULL}}
+};
+
static relopt_int intRelOpts[] =
{
{
@@ -600,6 +606,13 @@ initialize_reloptions(void)
boolRelOpts[i].gen.lockmode));
j++;
}
+ for (i = 0; ternaryRelOpts[i].gen.name; i++)
+ {
+ Assert(DoLockModesConflict(ternaryRelOpts[i].gen.lockmode,
+ ternaryRelOpts[i].gen.lockmode));
+ j++;
+ }
+
for (i = 0; intRelOpts[i].gen.name; i++)
{
Assert(DoLockModesConflict(intRelOpts[i].gen.lockmode,
@@ -640,6 +653,14 @@ initialize_reloptions(void)
j++;
}
+ for (i = 0; ternaryRelOpts[i].gen.name; i++)
+ {
+ relOpts[j] = &ternaryRelOpts[i].gen;
+ relOpts[j]->type = RELOPT_TYPE_TERNARY;
+ relOpts[j]->namelen = strlen(relOpts[j]->name);
+ j++;
+ }
+
for (i = 0; intRelOpts[i].gen.name; i++)
{
relOpts[j] = &intRelOpts[i].gen;
@@ -800,6 +821,9 @@ allocate_reloption(bits32 kinds, int type, const char *name, const char *desc,
case RELOPT_TYPE_BOOL:
size = sizeof(relopt_bool);
break;
+ case RELOPT_TYPE_TERNARY:
+ size = sizeof(relopt_ternary);
+ break;
case RELOPT_TYPE_INT:
size = sizeof(relopt_int);
break;
@@ -883,6 +907,54 @@ add_local_bool_reloption(local_relopts *relopts, const char *name,
add_local_reloption(relopts, (relopt_gen *) newoption, offset);
}
+/*
+ * init_ternary_reloption
+ * Allocate and initialize a new ternary reloption
+ */
+static relopt_ternary *
+init_ternary_reloption(bits32 kinds, const char *name, const char *desc,
+ ternary default_val, LOCKMODE lockmode)
+{
+ relopt_ternary *newoption;
+
+ newoption = (relopt_ternary *) allocate_reloption(kinds,
+ RELOPT_TYPE_TERNARY, name, desc, lockmode);
+ newoption->default_val = default_val;
+
+ return newoption;
+}
+
+/*
+ * add_ternary_reloption
+ * Add a new ternary reloption
+ */
+void
+add_ternary_reloption(bits32 kinds, const char *name, const char *desc,
+ ternary default_val, LOCKMODE lockmode)
+{
+ relopt_ternary *newoption = init_ternary_reloption(kinds, name, desc,
+ default_val, lockmode);
+
+ add_reloption((relopt_gen *) newoption);
+}
+
+/*
+ * add_local_ternary_reloption
+ * Add a new ternary local reloption
+ *
+ * 'offset' is offset of ternary-typed field.
+ */
+void
+add_local_ternary_reloption(local_relopts *relopts, const char *name,
+ const char *desc, ternary default_val,
+ int offset)
+{
+ relopt_ternary *newoption = init_ternary_reloption(RELOPT_KIND_LOCAL,
+ name, desc,
+ default_val, 0);
+
+ add_local_reloption(relopts, (relopt_gen *) newoption, offset);
+}
/*
* init_real_reloption
@@ -1617,6 +1689,18 @@ parse_one_reloption(relopt_value *option, char *text_str, int text_len,
option->gen->name, value)));
}
break;
+ case RELOPT_TYPE_TERNARY:
+ {
+ bool b;
+ parsed = parse_bool(value, &b);
+ option->values.ternary_val = b ? TERNARY_TRUE : TERNARY_FALSE;
+ if (validate && !parsed)
+ ereport(ERROR,
+ (errcode(ERRCODE_INVALID_PARAMETER_VALUE),
+ errmsg("invalid value for ternary option \"%s\": %s",
+ option->gen->name, value)));
+ }
+ break;
case RELOPT_TYPE_INT:
{
relopt_int *optint = (relopt_int *) option->gen;
@@ -1780,17 +1864,6 @@ fillRelOptions(void *rdopts, Size basesize,
char *itempos = ((char *) rdopts) + elems[j].offset;
char *string_val;
- /*
- * If isset_offset is provided, store whether the reloption is
- * set there.
- */
- if (elems[j].isset_offset > 0)
- {
- char *setpos = ((char *) rdopts) + elems[j].isset_offset;
-
- *(bool *) setpos = options[i].isset;
- }
-
switch (options[i].gen->type)
{
case RELOPT_TYPE_BOOL:
@@ -1798,6 +1871,11 @@ fillRelOptions(void *rdopts, Size basesize,
options[i].values.bool_val :
((relopt_bool *) options[i].gen)->default_val;
break;
+ case RELOPT_TYPE_TERNARY:
+ *(ternary *) itempos = options[i].isset ?
+ options[i].values.ternary_val :
+ ((relopt_ternary *) options[i].gen)->default_val;
+ break;
case RELOPT_TYPE_INT:
*(int *) itempos = options[i].isset ?
options[i].values.int_val :
@@ -1912,8 +1990,8 @@ default_reloptions(Datum reloptions, bool validate, relopt_kind kind)
offsetof(StdRdOptions, parallel_workers)},
{"vacuum_index_cleanup", RELOPT_TYPE_ENUM,
offsetof(StdRdOptions, vacuum_index_cleanup)},
- {"vacuum_truncate", RELOPT_TYPE_BOOL,
- offsetof(StdRdOptions, vacuum_truncate), offsetof(StdRdOptions, vacuum_truncate_set)},
+ {"vacuum_truncate", RELOPT_TYPE_TERNARY,
+ offsetof(StdRdOptions, vacuum_truncate)},
{"vacuum_max_eager_freeze_failure_rate", RELOPT_TYPE_REAL,
offsetof(StdRdOptions, vacuum_max_eager_freeze_failure_rate)}
};
@@ -1993,7 +2071,6 @@ build_local_reloptions(local_relopts *relopts, Datum options, bool validate)
elems[i].optname = opt->option->name;
elems[i].opttype = opt->option->type;
elems[i].offset = opt->offset;
- elems[i].isset_offset = 0; /* not supported for local relopts yet */
i++;
}
diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c
index 733ef40ae7..977babff54 100644
--- a/src/backend/commands/vacuum.c
+++ b/src/backend/commands/vacuum.c
@@ -2221,9 +2221,9 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams params,
{
StdRdOptions *opts = (StdRdOptions *) rel->rd_options;
- if (opts && opts->vacuum_truncate_set)
+ if (opts && opts->vacuum_truncate != TERNARY_UNSET)
{
- if (opts->vacuum_truncate)
+ if (opts->vacuum_truncate == TERNARY_TRUE)
params.truncate = VACOPTVALUE_ENABLED;
else
params.truncate = VACOPTVALUE_DISABLED;
diff --git a/src/include/access/reloptions.h b/src/include/access/reloptions.h
index a604a4702c..a436697658 100644
--- a/src/include/access/reloptions.h
+++ b/src/include/access/reloptions.h
@@ -29,6 +29,7 @@
typedef enum relopt_type
{
RELOPT_TYPE_BOOL,
+ RELOPT_TYPE_TERNARY, /* on, off, unset */
RELOPT_TYPE_INT,
RELOPT_TYPE_REAL,
RELOPT_TYPE_ENUM,
@@ -80,6 +81,7 @@ typedef struct relopt_value
union
{
bool bool_val;
+ ternary ternary_val;
int int_val;
double real_val;
int enum_val;
@@ -94,6 +96,12 @@ typedef struct relopt_bool
bool default_val;
} relopt_bool;
+typedef struct relopt_rernary
+{
+ relopt_gen gen;
+ int default_val;
+} relopt_ternary;
+
typedef struct relopt_int
{
relopt_gen gen;
@@ -152,19 +160,6 @@ typedef struct
const char *optname; /* option's name */
relopt_type opttype; /* option's datatype */
int offset; /* offset of field in result struct */
-
- /*
- * isset_offset is an optional offset of a field in the result struct that
- * stores whether the option is explicitly set for the relation or if it
- * just picked up the default value. In most cases, this can be
- * accomplished by giving the reloption a special out-of-range default
- * value (e.g., some integer reloptions use -2), but this isn't always
- * possible. For example, a Boolean reloption cannot be given an
- * out-of-range default, so we need another way to discover the source of
- * its value. This offset is only used if given a value greater than
- * zero.
- */
- int isset_offset;
} relopt_parse_elt;
/* Local reloption definition */
@@ -195,6 +190,8 @@ typedef struct local_relopts
extern relopt_kind add_reloption_kind(void);
extern void add_bool_reloption(bits32 kinds, const char *name, const char *desc,
bool default_val, LOCKMODE lockmode);
+extern void add_ternary_reloption(bits32 kinds, const char *name,
+ const char *desc, int default_val, LOCKMODE lockmode);
extern void add_int_reloption(bits32 kinds, const char *name, const char *desc,
int default_val, int min_val, int max_val,
LOCKMODE lockmode);
@@ -214,6 +211,9 @@ extern void register_reloptions_validator(local_relopts *relopts,
extern void add_local_bool_reloption(local_relopts *relopts, const char *name,
const char *desc, bool default_val,
int offset);
+extern void add_local_ternary_reloption(local_relopts *relopts,
+ const char *name, const char *desc,
+ ternary default_val, int offset);
extern void add_local_int_reloption(local_relopts *relopts, const char *name,
const char *desc, int default_val,
int min_val, int max_val, int offset);
diff --git a/src/include/c.h b/src/include/c.h
index 39022f8a9d..9c59ccccf8 100644
--- a/src/include/c.h
+++ b/src/include/c.h
@@ -488,6 +488,22 @@ typedef void (*pg_funcptr_t) (void);
#include <stdbool.h>
+/*
+ * ternary
+ * Boolean value with an extrea "unset" option
+ *
+ * Ternary data type is used in relation options that can be "true", "false" or
+ * "unset". Since relation options are used deep inside the PostgreSQL code,
+ * this type is declared globally.
+*/
+
+typedef enum ternary
+{
+ TERNARY_FALSE = 0,
+ TERNARY_TRUE = 1,
+ TERNARY_UNSET = -1
+} ternary;
+
/* ----------------------------------------------------------------
* Section 3: standard system types
diff --git a/src/include/utils/rel.h b/src/include/utils/rel.h
index b552359915..7346d618f9 100644
--- a/src/include/utils/rel.h
+++ b/src/include/utils/rel.h
@@ -346,8 +346,7 @@ typedef struct StdRdOptions
bool user_catalog_table; /* use as an additional catalog relation */
int parallel_workers; /* max number of parallel workers */
StdRdOptIndexCleanup vacuum_index_cleanup; /* controls index vacuuming */
- bool vacuum_truncate; /* enables vacuum to truncate a relation */
- bool vacuum_truncate_set; /* whether vacuum_truncate is set */
+ ternary vacuum_truncate; /* enables vacuum to truncate a relation */
/*
* Fraction of pages in a relation that vacuum can eagerly scan and fail
--
2.39.2
--nextPart4155600.3ZeAukHxDK
Content-Disposition: attachment;
filename="v2a-0003-Add-alias-to-be-used-as-unset-state.patch"
Content-Transfer-Encoding: 7Bit
Content-Type: text/x-patch; charset="unicode-2-0-utf-8";
name="v2a-0003-Add-alias-to-be-used-as-unset-state.patch"
^ permalink raw reply [nested|flat] 868+ messages in thread
* [PATCH v2 2/4] Introduce ternary reloptions
@ 2025-09-04 14:41 Nikolay Shaplov <[email protected]>
0 siblings, 0 replies; 868+ messages in thread
From: Nikolay Shaplov @ 2025-09-04 14:41 UTC (permalink / raw)
Introduce ternary reloption as a replacement for current `vacuum_truncate`
implementation. Remove `vacuum_truncate_set` additional flag and using
`TERNARY_UNSET` value instead.
---
src/backend/access/common/reloptions.c | 129 ++++++++++++++++++++-----
src/backend/commands/vacuum.c | 4 +-
src/include/access/reloptions.h | 26 ++---
src/include/c.h | 16 +++
src/include/utils/rel.h | 3 +-
5 files changed, 135 insertions(+), 43 deletions(-)
diff --git a/src/backend/access/common/reloptions.c b/src/backend/access/common/reloptions.c
index 0af3fea68f..f543f11001 100644
--- a/src/backend/access/common/reloptions.c
+++ b/src/backend/access/common/reloptions.c
@@ -40,9 +40,9 @@
*
* To add an option:
*
- * (i) decide on a type (bool, integer, real, enum, string), name, default
- * value, upper and lower bounds (if applicable); for strings, consider a
- * validation routine.
+ * (i) decide on a type (bool, ternary, integer, real, enum, string), name,
+ * default value, upper and lower bounds (if applicable); for strings,
+ * consider a validation routine.
* (ii) add a record below (or use add_<type>_reloption).
* (iii) add it to the appropriate options struct (perhaps StdRdOptions)
* (iv) add it to the appropriate handling routine (perhaps
@@ -147,15 +147,6 @@ static relopt_bool boolRelOpts[] =
},
false
},
- {
- {
- "vacuum_truncate",
- "Enables vacuum to truncate empty pages at the end of this table",
- RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
- ShareUpdateExclusiveLock
- },
- true
- },
{
{
"deduplicate_items",
@@ -170,6 +161,21 @@ static relopt_bool boolRelOpts[] =
{{NULL}}
};
+static relopt_ternary ternaryRelOpts[] =
+{
+ {
+ {
+ "vacuum_truncate",
+ "Enables vacuum to truncate empty pages at the end of this table",
+ RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
+ ShareUpdateExclusiveLock
+ },
+ TERNARY_UNSET
+ },
+ /* list terminator */
+ {{NULL}}
+};
+
static relopt_int intRelOpts[] =
{
{
@@ -600,6 +606,13 @@ initialize_reloptions(void)
boolRelOpts[i].gen.lockmode));
j++;
}
+ for (i = 0; ternaryRelOpts[i].gen.name; i++)
+ {
+ Assert(DoLockModesConflict(ternaryRelOpts[i].gen.lockmode,
+ ternaryRelOpts[i].gen.lockmode));
+ j++;
+ }
+
for (i = 0; intRelOpts[i].gen.name; i++)
{
Assert(DoLockModesConflict(intRelOpts[i].gen.lockmode,
@@ -640,6 +653,14 @@ initialize_reloptions(void)
j++;
}
+ for (i = 0; ternaryRelOpts[i].gen.name; i++)
+ {
+ relOpts[j] = &ternaryRelOpts[i].gen;
+ relOpts[j]->type = RELOPT_TYPE_TERNARY;
+ relOpts[j]->namelen = strlen(relOpts[j]->name);
+ j++;
+ }
+
for (i = 0; intRelOpts[i].gen.name; i++)
{
relOpts[j] = &intRelOpts[i].gen;
@@ -800,6 +821,9 @@ allocate_reloption(bits32 kinds, int type, const char *name, const char *desc,
case RELOPT_TYPE_BOOL:
size = sizeof(relopt_bool);
break;
+ case RELOPT_TYPE_TERNARY:
+ size = sizeof(relopt_ternary);
+ break;
case RELOPT_TYPE_INT:
size = sizeof(relopt_int);
break;
@@ -883,6 +907,54 @@ add_local_bool_reloption(local_relopts *relopts, const char *name,
add_local_reloption(relopts, (relopt_gen *) newoption, offset);
}
+/*
+ * init_ternary_reloption
+ * Allocate and initialize a new ternary reloption
+ */
+static relopt_ternary *
+init_ternary_reloption(bits32 kinds, const char *name, const char *desc,
+ ternary default_val, LOCKMODE lockmode)
+{
+ relopt_ternary *newoption;
+
+ newoption = (relopt_ternary *) allocate_reloption(kinds,
+ RELOPT_TYPE_TERNARY, name, desc, lockmode);
+ newoption->default_val = default_val;
+
+ return newoption;
+}
+
+/*
+ * add_ternary_reloption
+ * Add a new ternary reloption
+ */
+void
+add_ternary_reloption(bits32 kinds, const char *name, const char *desc,
+ ternary default_val, LOCKMODE lockmode)
+{
+ relopt_ternary *newoption = init_ternary_reloption(kinds, name, desc,
+ default_val, lockmode);
+
+ add_reloption((relopt_gen *) newoption);
+}
+
+/*
+ * add_local_ternary_reloption
+ * Add a new ternary local reloption
+ *
+ * 'offset' is offset of ternary-typed field.
+ */
+void
+add_local_ternary_reloption(local_relopts *relopts, const char *name,
+ const char *desc, ternary default_val,
+ int offset)
+{
+ relopt_ternary *newoption = init_ternary_reloption(RELOPT_KIND_LOCAL,
+ name, desc,
+ default_val, 0);
+
+ add_local_reloption(relopts, (relopt_gen *) newoption, offset);
+}
/*
* init_real_reloption
@@ -1617,6 +1689,18 @@ parse_one_reloption(relopt_value *option, char *text_str, int text_len,
option->gen->name, value)));
}
break;
+ case RELOPT_TYPE_TERNARY:
+ {
+ bool b;
+ parsed = parse_bool(value, &b);
+ option->values.ternary_val = b ? TERNARY_TRUE : TERNARY_FALSE;
+ if (validate && !parsed)
+ ereport(ERROR,
+ (errcode(ERRCODE_INVALID_PARAMETER_VALUE),
+ errmsg("invalid value for ternary option \"%s\": %s",
+ option->gen->name, value)));
+ }
+ break;
case RELOPT_TYPE_INT:
{
relopt_int *optint = (relopt_int *) option->gen;
@@ -1780,17 +1864,6 @@ fillRelOptions(void *rdopts, Size basesize,
char *itempos = ((char *) rdopts) + elems[j].offset;
char *string_val;
- /*
- * If isset_offset is provided, store whether the reloption is
- * set there.
- */
- if (elems[j].isset_offset > 0)
- {
- char *setpos = ((char *) rdopts) + elems[j].isset_offset;
-
- *(bool *) setpos = options[i].isset;
- }
-
switch (options[i].gen->type)
{
case RELOPT_TYPE_BOOL:
@@ -1798,6 +1871,11 @@ fillRelOptions(void *rdopts, Size basesize,
options[i].values.bool_val :
((relopt_bool *) options[i].gen)->default_val;
break;
+ case RELOPT_TYPE_TERNARY:
+ *(ternary *) itempos = options[i].isset ?
+ options[i].values.ternary_val :
+ ((relopt_ternary *) options[i].gen)->default_val;
+ break;
case RELOPT_TYPE_INT:
*(int *) itempos = options[i].isset ?
options[i].values.int_val :
@@ -1912,8 +1990,8 @@ default_reloptions(Datum reloptions, bool validate, relopt_kind kind)
offsetof(StdRdOptions, parallel_workers)},
{"vacuum_index_cleanup", RELOPT_TYPE_ENUM,
offsetof(StdRdOptions, vacuum_index_cleanup)},
- {"vacuum_truncate", RELOPT_TYPE_BOOL,
- offsetof(StdRdOptions, vacuum_truncate), offsetof(StdRdOptions, vacuum_truncate_set)},
+ {"vacuum_truncate", RELOPT_TYPE_TERNARY,
+ offsetof(StdRdOptions, vacuum_truncate)},
{"vacuum_max_eager_freeze_failure_rate", RELOPT_TYPE_REAL,
offsetof(StdRdOptions, vacuum_max_eager_freeze_failure_rate)}
};
@@ -1993,7 +2071,6 @@ build_local_reloptions(local_relopts *relopts, Datum options, bool validate)
elems[i].optname = opt->option->name;
elems[i].opttype = opt->option->type;
elems[i].offset = opt->offset;
- elems[i].isset_offset = 0; /* not supported for local relopts yet */
i++;
}
diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c
index 733ef40ae7..977babff54 100644
--- a/src/backend/commands/vacuum.c
+++ b/src/backend/commands/vacuum.c
@@ -2221,9 +2221,9 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams params,
{
StdRdOptions *opts = (StdRdOptions *) rel->rd_options;
- if (opts && opts->vacuum_truncate_set)
+ if (opts && opts->vacuum_truncate != TERNARY_UNSET)
{
- if (opts->vacuum_truncate)
+ if (opts->vacuum_truncate == TERNARY_TRUE)
params.truncate = VACOPTVALUE_ENABLED;
else
params.truncate = VACOPTVALUE_DISABLED;
diff --git a/src/include/access/reloptions.h b/src/include/access/reloptions.h
index a604a4702c..a436697658 100644
--- a/src/include/access/reloptions.h
+++ b/src/include/access/reloptions.h
@@ -29,6 +29,7 @@
typedef enum relopt_type
{
RELOPT_TYPE_BOOL,
+ RELOPT_TYPE_TERNARY, /* on, off, unset */
RELOPT_TYPE_INT,
RELOPT_TYPE_REAL,
RELOPT_TYPE_ENUM,
@@ -80,6 +81,7 @@ typedef struct relopt_value
union
{
bool bool_val;
+ ternary ternary_val;
int int_val;
double real_val;
int enum_val;
@@ -94,6 +96,12 @@ typedef struct relopt_bool
bool default_val;
} relopt_bool;
+typedef struct relopt_rernary
+{
+ relopt_gen gen;
+ int default_val;
+} relopt_ternary;
+
typedef struct relopt_int
{
relopt_gen gen;
@@ -152,19 +160,6 @@ typedef struct
const char *optname; /* option's name */
relopt_type opttype; /* option's datatype */
int offset; /* offset of field in result struct */
-
- /*
- * isset_offset is an optional offset of a field in the result struct that
- * stores whether the option is explicitly set for the relation or if it
- * just picked up the default value. In most cases, this can be
- * accomplished by giving the reloption a special out-of-range default
- * value (e.g., some integer reloptions use -2), but this isn't always
- * possible. For example, a Boolean reloption cannot be given an
- * out-of-range default, so we need another way to discover the source of
- * its value. This offset is only used if given a value greater than
- * zero.
- */
- int isset_offset;
} relopt_parse_elt;
/* Local reloption definition */
@@ -195,6 +190,8 @@ typedef struct local_relopts
extern relopt_kind add_reloption_kind(void);
extern void add_bool_reloption(bits32 kinds, const char *name, const char *desc,
bool default_val, LOCKMODE lockmode);
+extern void add_ternary_reloption(bits32 kinds, const char *name,
+ const char *desc, int default_val, LOCKMODE lockmode);
extern void add_int_reloption(bits32 kinds, const char *name, const char *desc,
int default_val, int min_val, int max_val,
LOCKMODE lockmode);
@@ -214,6 +211,9 @@ extern void register_reloptions_validator(local_relopts *relopts,
extern void add_local_bool_reloption(local_relopts *relopts, const char *name,
const char *desc, bool default_val,
int offset);
+extern void add_local_ternary_reloption(local_relopts *relopts,
+ const char *name, const char *desc,
+ ternary default_val, int offset);
extern void add_local_int_reloption(local_relopts *relopts, const char *name,
const char *desc, int default_val,
int min_val, int max_val, int offset);
diff --git a/src/include/c.h b/src/include/c.h
index 39022f8a9d..9c59ccccf8 100644
--- a/src/include/c.h
+++ b/src/include/c.h
@@ -488,6 +488,22 @@ typedef void (*pg_funcptr_t) (void);
#include <stdbool.h>
+/*
+ * ternary
+ * Boolean value with an extrea "unset" option
+ *
+ * Ternary data type is used in relation options that can be "true", "false" or
+ * "unset". Since relation options are used deep inside the PostgreSQL code,
+ * this type is declared globally.
+*/
+
+typedef enum ternary
+{
+ TERNARY_FALSE = 0,
+ TERNARY_TRUE = 1,
+ TERNARY_UNSET = -1
+} ternary;
+
/* ----------------------------------------------------------------
* Section 3: standard system types
diff --git a/src/include/utils/rel.h b/src/include/utils/rel.h
index b552359915..7346d618f9 100644
--- a/src/include/utils/rel.h
+++ b/src/include/utils/rel.h
@@ -346,8 +346,7 @@ typedef struct StdRdOptions
bool user_catalog_table; /* use as an additional catalog relation */
int parallel_workers; /* max number of parallel workers */
StdRdOptIndexCleanup vacuum_index_cleanup; /* controls index vacuuming */
- bool vacuum_truncate; /* enables vacuum to truncate a relation */
- bool vacuum_truncate_set; /* whether vacuum_truncate is set */
+ ternary vacuum_truncate; /* enables vacuum to truncate a relation */
/*
* Fraction of pages in a relation that vacuum can eagerly scan and fail
--
2.39.2
--nextPart5129932.5fSG56mABF
Content-Disposition: attachment;
filename="v2-0003-Add-alias-to-be-used-as-unset-state.patch"
Content-Transfer-Encoding: 7Bit
Content-Type: text/x-patch; charset="unicode-2-0-utf-8";
name="v2-0003-Add-alias-to-be-used-as-unset-state.patch"
^ permalink raw reply [nested|flat] 868+ messages in thread
* [PATCH v2a 2/4] Introduce ternary reloptions
@ 2025-09-04 14:41 Nikolay Shaplov <[email protected]>
0 siblings, 0 replies; 868+ messages in thread
From: Nikolay Shaplov @ 2025-09-04 14:41 UTC (permalink / raw)
Introduce ternary reloption as a replacement for current `vacuum_truncate`
implementation. Remove `vacuum_truncate_set` additional flag and using
`TERNARY_UNSET` value instead.
---
src/backend/access/common/reloptions.c | 129 ++++++++++++++++++++-----
src/backend/commands/vacuum.c | 4 +-
src/include/access/reloptions.h | 26 ++---
src/include/c.h | 16 +++
src/include/utils/rel.h | 3 +-
5 files changed, 135 insertions(+), 43 deletions(-)
diff --git a/src/backend/access/common/reloptions.c b/src/backend/access/common/reloptions.c
index 0af3fea68f..f543f11001 100644
--- a/src/backend/access/common/reloptions.c
+++ b/src/backend/access/common/reloptions.c
@@ -40,9 +40,9 @@
*
* To add an option:
*
- * (i) decide on a type (bool, integer, real, enum, string), name, default
- * value, upper and lower bounds (if applicable); for strings, consider a
- * validation routine.
+ * (i) decide on a type (bool, ternary, integer, real, enum, string), name,
+ * default value, upper and lower bounds (if applicable); for strings,
+ * consider a validation routine.
* (ii) add a record below (or use add_<type>_reloption).
* (iii) add it to the appropriate options struct (perhaps StdRdOptions)
* (iv) add it to the appropriate handling routine (perhaps
@@ -147,15 +147,6 @@ static relopt_bool boolRelOpts[] =
},
false
},
- {
- {
- "vacuum_truncate",
- "Enables vacuum to truncate empty pages at the end of this table",
- RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
- ShareUpdateExclusiveLock
- },
- true
- },
{
{
"deduplicate_items",
@@ -170,6 +161,21 @@ static relopt_bool boolRelOpts[] =
{{NULL}}
};
+static relopt_ternary ternaryRelOpts[] =
+{
+ {
+ {
+ "vacuum_truncate",
+ "Enables vacuum to truncate empty pages at the end of this table",
+ RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
+ ShareUpdateExclusiveLock
+ },
+ TERNARY_UNSET
+ },
+ /* list terminator */
+ {{NULL}}
+};
+
static relopt_int intRelOpts[] =
{
{
@@ -600,6 +606,13 @@ initialize_reloptions(void)
boolRelOpts[i].gen.lockmode));
j++;
}
+ for (i = 0; ternaryRelOpts[i].gen.name; i++)
+ {
+ Assert(DoLockModesConflict(ternaryRelOpts[i].gen.lockmode,
+ ternaryRelOpts[i].gen.lockmode));
+ j++;
+ }
+
for (i = 0; intRelOpts[i].gen.name; i++)
{
Assert(DoLockModesConflict(intRelOpts[i].gen.lockmode,
@@ -640,6 +653,14 @@ initialize_reloptions(void)
j++;
}
+ for (i = 0; ternaryRelOpts[i].gen.name; i++)
+ {
+ relOpts[j] = &ternaryRelOpts[i].gen;
+ relOpts[j]->type = RELOPT_TYPE_TERNARY;
+ relOpts[j]->namelen = strlen(relOpts[j]->name);
+ j++;
+ }
+
for (i = 0; intRelOpts[i].gen.name; i++)
{
relOpts[j] = &intRelOpts[i].gen;
@@ -800,6 +821,9 @@ allocate_reloption(bits32 kinds, int type, const char *name, const char *desc,
case RELOPT_TYPE_BOOL:
size = sizeof(relopt_bool);
break;
+ case RELOPT_TYPE_TERNARY:
+ size = sizeof(relopt_ternary);
+ break;
case RELOPT_TYPE_INT:
size = sizeof(relopt_int);
break;
@@ -883,6 +907,54 @@ add_local_bool_reloption(local_relopts *relopts, const char *name,
add_local_reloption(relopts, (relopt_gen *) newoption, offset);
}
+/*
+ * init_ternary_reloption
+ * Allocate and initialize a new ternary reloption
+ */
+static relopt_ternary *
+init_ternary_reloption(bits32 kinds, const char *name, const char *desc,
+ ternary default_val, LOCKMODE lockmode)
+{
+ relopt_ternary *newoption;
+
+ newoption = (relopt_ternary *) allocate_reloption(kinds,
+ RELOPT_TYPE_TERNARY, name, desc, lockmode);
+ newoption->default_val = default_val;
+
+ return newoption;
+}
+
+/*
+ * add_ternary_reloption
+ * Add a new ternary reloption
+ */
+void
+add_ternary_reloption(bits32 kinds, const char *name, const char *desc,
+ ternary default_val, LOCKMODE lockmode)
+{
+ relopt_ternary *newoption = init_ternary_reloption(kinds, name, desc,
+ default_val, lockmode);
+
+ add_reloption((relopt_gen *) newoption);
+}
+
+/*
+ * add_local_ternary_reloption
+ * Add a new ternary local reloption
+ *
+ * 'offset' is offset of ternary-typed field.
+ */
+void
+add_local_ternary_reloption(local_relopts *relopts, const char *name,
+ const char *desc, ternary default_val,
+ int offset)
+{
+ relopt_ternary *newoption = init_ternary_reloption(RELOPT_KIND_LOCAL,
+ name, desc,
+ default_val, 0);
+
+ add_local_reloption(relopts, (relopt_gen *) newoption, offset);
+}
/*
* init_real_reloption
@@ -1617,6 +1689,18 @@ parse_one_reloption(relopt_value *option, char *text_str, int text_len,
option->gen->name, value)));
}
break;
+ case RELOPT_TYPE_TERNARY:
+ {
+ bool b;
+ parsed = parse_bool(value, &b);
+ option->values.ternary_val = b ? TERNARY_TRUE : TERNARY_FALSE;
+ if (validate && !parsed)
+ ereport(ERROR,
+ (errcode(ERRCODE_INVALID_PARAMETER_VALUE),
+ errmsg("invalid value for ternary option \"%s\": %s",
+ option->gen->name, value)));
+ }
+ break;
case RELOPT_TYPE_INT:
{
relopt_int *optint = (relopt_int *) option->gen;
@@ -1780,17 +1864,6 @@ fillRelOptions(void *rdopts, Size basesize,
char *itempos = ((char *) rdopts) + elems[j].offset;
char *string_val;
- /*
- * If isset_offset is provided, store whether the reloption is
- * set there.
- */
- if (elems[j].isset_offset > 0)
- {
- char *setpos = ((char *) rdopts) + elems[j].isset_offset;
-
- *(bool *) setpos = options[i].isset;
- }
-
switch (options[i].gen->type)
{
case RELOPT_TYPE_BOOL:
@@ -1798,6 +1871,11 @@ fillRelOptions(void *rdopts, Size basesize,
options[i].values.bool_val :
((relopt_bool *) options[i].gen)->default_val;
break;
+ case RELOPT_TYPE_TERNARY:
+ *(ternary *) itempos = options[i].isset ?
+ options[i].values.ternary_val :
+ ((relopt_ternary *) options[i].gen)->default_val;
+ break;
case RELOPT_TYPE_INT:
*(int *) itempos = options[i].isset ?
options[i].values.int_val :
@@ -1912,8 +1990,8 @@ default_reloptions(Datum reloptions, bool validate, relopt_kind kind)
offsetof(StdRdOptions, parallel_workers)},
{"vacuum_index_cleanup", RELOPT_TYPE_ENUM,
offsetof(StdRdOptions, vacuum_index_cleanup)},
- {"vacuum_truncate", RELOPT_TYPE_BOOL,
- offsetof(StdRdOptions, vacuum_truncate), offsetof(StdRdOptions, vacuum_truncate_set)},
+ {"vacuum_truncate", RELOPT_TYPE_TERNARY,
+ offsetof(StdRdOptions, vacuum_truncate)},
{"vacuum_max_eager_freeze_failure_rate", RELOPT_TYPE_REAL,
offsetof(StdRdOptions, vacuum_max_eager_freeze_failure_rate)}
};
@@ -1993,7 +2071,6 @@ build_local_reloptions(local_relopts *relopts, Datum options, bool validate)
elems[i].optname = opt->option->name;
elems[i].opttype = opt->option->type;
elems[i].offset = opt->offset;
- elems[i].isset_offset = 0; /* not supported for local relopts yet */
i++;
}
diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c
index 733ef40ae7..977babff54 100644
--- a/src/backend/commands/vacuum.c
+++ b/src/backend/commands/vacuum.c
@@ -2221,9 +2221,9 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams params,
{
StdRdOptions *opts = (StdRdOptions *) rel->rd_options;
- if (opts && opts->vacuum_truncate_set)
+ if (opts && opts->vacuum_truncate != TERNARY_UNSET)
{
- if (opts->vacuum_truncate)
+ if (opts->vacuum_truncate == TERNARY_TRUE)
params.truncate = VACOPTVALUE_ENABLED;
else
params.truncate = VACOPTVALUE_DISABLED;
diff --git a/src/include/access/reloptions.h b/src/include/access/reloptions.h
index a604a4702c..a436697658 100644
--- a/src/include/access/reloptions.h
+++ b/src/include/access/reloptions.h
@@ -29,6 +29,7 @@
typedef enum relopt_type
{
RELOPT_TYPE_BOOL,
+ RELOPT_TYPE_TERNARY, /* on, off, unset */
RELOPT_TYPE_INT,
RELOPT_TYPE_REAL,
RELOPT_TYPE_ENUM,
@@ -80,6 +81,7 @@ typedef struct relopt_value
union
{
bool bool_val;
+ ternary ternary_val;
int int_val;
double real_val;
int enum_val;
@@ -94,6 +96,12 @@ typedef struct relopt_bool
bool default_val;
} relopt_bool;
+typedef struct relopt_rernary
+{
+ relopt_gen gen;
+ int default_val;
+} relopt_ternary;
+
typedef struct relopt_int
{
relopt_gen gen;
@@ -152,19 +160,6 @@ typedef struct
const char *optname; /* option's name */
relopt_type opttype; /* option's datatype */
int offset; /* offset of field in result struct */
-
- /*
- * isset_offset is an optional offset of a field in the result struct that
- * stores whether the option is explicitly set for the relation or if it
- * just picked up the default value. In most cases, this can be
- * accomplished by giving the reloption a special out-of-range default
- * value (e.g., some integer reloptions use -2), but this isn't always
- * possible. For example, a Boolean reloption cannot be given an
- * out-of-range default, so we need another way to discover the source of
- * its value. This offset is only used if given a value greater than
- * zero.
- */
- int isset_offset;
} relopt_parse_elt;
/* Local reloption definition */
@@ -195,6 +190,8 @@ typedef struct local_relopts
extern relopt_kind add_reloption_kind(void);
extern void add_bool_reloption(bits32 kinds, const char *name, const char *desc,
bool default_val, LOCKMODE lockmode);
+extern void add_ternary_reloption(bits32 kinds, const char *name,
+ const char *desc, int default_val, LOCKMODE lockmode);
extern void add_int_reloption(bits32 kinds, const char *name, const char *desc,
int default_val, int min_val, int max_val,
LOCKMODE lockmode);
@@ -214,6 +211,9 @@ extern void register_reloptions_validator(local_relopts *relopts,
extern void add_local_bool_reloption(local_relopts *relopts, const char *name,
const char *desc, bool default_val,
int offset);
+extern void add_local_ternary_reloption(local_relopts *relopts,
+ const char *name, const char *desc,
+ ternary default_val, int offset);
extern void add_local_int_reloption(local_relopts *relopts, const char *name,
const char *desc, int default_val,
int min_val, int max_val, int offset);
diff --git a/src/include/c.h b/src/include/c.h
index 39022f8a9d..9c59ccccf8 100644
--- a/src/include/c.h
+++ b/src/include/c.h
@@ -488,6 +488,22 @@ typedef void (*pg_funcptr_t) (void);
#include <stdbool.h>
+/*
+ * ternary
+ * Boolean value with an extrea "unset" option
+ *
+ * Ternary data type is used in relation options that can be "true", "false" or
+ * "unset". Since relation options are used deep inside the PostgreSQL code,
+ * this type is declared globally.
+*/
+
+typedef enum ternary
+{
+ TERNARY_FALSE = 0,
+ TERNARY_TRUE = 1,
+ TERNARY_UNSET = -1
+} ternary;
+
/* ----------------------------------------------------------------
* Section 3: standard system types
diff --git a/src/include/utils/rel.h b/src/include/utils/rel.h
index b552359915..7346d618f9 100644
--- a/src/include/utils/rel.h
+++ b/src/include/utils/rel.h
@@ -346,8 +346,7 @@ typedef struct StdRdOptions
bool user_catalog_table; /* use as an additional catalog relation */
int parallel_workers; /* max number of parallel workers */
StdRdOptIndexCleanup vacuum_index_cleanup; /* controls index vacuuming */
- bool vacuum_truncate; /* enables vacuum to truncate a relation */
- bool vacuum_truncate_set; /* whether vacuum_truncate is set */
+ ternary vacuum_truncate; /* enables vacuum to truncate a relation */
/*
* Fraction of pages in a relation that vacuum can eagerly scan and fail
--
2.39.2
--nextPart4155600.3ZeAukHxDK
Content-Disposition: attachment;
filename="v2a-0003-Add-alias-to-be-used-as-unset-state.patch"
Content-Transfer-Encoding: 7Bit
Content-Type: text/x-patch; charset="unicode-2-0-utf-8";
name="v2a-0003-Add-alias-to-be-used-as-unset-state.patch"
^ permalink raw reply [nested|flat] 868+ messages in thread
* [PATCH v2 2/4] Introduce ternary reloptions
@ 2025-09-04 14:41 Nikolay Shaplov <[email protected]>
0 siblings, 0 replies; 868+ messages in thread
From: Nikolay Shaplov @ 2025-09-04 14:41 UTC (permalink / raw)
Introduce ternary reloption as a replacement for current `vacuum_truncate`
implementation. Remove `vacuum_truncate_set` additional flag and using
`TERNARY_UNSET` value instead.
---
src/backend/access/common/reloptions.c | 129 ++++++++++++++++++++-----
src/backend/commands/vacuum.c | 4 +-
src/include/access/reloptions.h | 26 ++---
src/include/c.h | 16 +++
src/include/utils/rel.h | 3 +-
5 files changed, 135 insertions(+), 43 deletions(-)
diff --git a/src/backend/access/common/reloptions.c b/src/backend/access/common/reloptions.c
index 0af3fea68f..f543f11001 100644
--- a/src/backend/access/common/reloptions.c
+++ b/src/backend/access/common/reloptions.c
@@ -40,9 +40,9 @@
*
* To add an option:
*
- * (i) decide on a type (bool, integer, real, enum, string), name, default
- * value, upper and lower bounds (if applicable); for strings, consider a
- * validation routine.
+ * (i) decide on a type (bool, ternary, integer, real, enum, string), name,
+ * default value, upper and lower bounds (if applicable); for strings,
+ * consider a validation routine.
* (ii) add a record below (or use add_<type>_reloption).
* (iii) add it to the appropriate options struct (perhaps StdRdOptions)
* (iv) add it to the appropriate handling routine (perhaps
@@ -147,15 +147,6 @@ static relopt_bool boolRelOpts[] =
},
false
},
- {
- {
- "vacuum_truncate",
- "Enables vacuum to truncate empty pages at the end of this table",
- RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
- ShareUpdateExclusiveLock
- },
- true
- },
{
{
"deduplicate_items",
@@ -170,6 +161,21 @@ static relopt_bool boolRelOpts[] =
{{NULL}}
};
+static relopt_ternary ternaryRelOpts[] =
+{
+ {
+ {
+ "vacuum_truncate",
+ "Enables vacuum to truncate empty pages at the end of this table",
+ RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
+ ShareUpdateExclusiveLock
+ },
+ TERNARY_UNSET
+ },
+ /* list terminator */
+ {{NULL}}
+};
+
static relopt_int intRelOpts[] =
{
{
@@ -600,6 +606,13 @@ initialize_reloptions(void)
boolRelOpts[i].gen.lockmode));
j++;
}
+ for (i = 0; ternaryRelOpts[i].gen.name; i++)
+ {
+ Assert(DoLockModesConflict(ternaryRelOpts[i].gen.lockmode,
+ ternaryRelOpts[i].gen.lockmode));
+ j++;
+ }
+
for (i = 0; intRelOpts[i].gen.name; i++)
{
Assert(DoLockModesConflict(intRelOpts[i].gen.lockmode,
@@ -640,6 +653,14 @@ initialize_reloptions(void)
j++;
}
+ for (i = 0; ternaryRelOpts[i].gen.name; i++)
+ {
+ relOpts[j] = &ternaryRelOpts[i].gen;
+ relOpts[j]->type = RELOPT_TYPE_TERNARY;
+ relOpts[j]->namelen = strlen(relOpts[j]->name);
+ j++;
+ }
+
for (i = 0; intRelOpts[i].gen.name; i++)
{
relOpts[j] = &intRelOpts[i].gen;
@@ -800,6 +821,9 @@ allocate_reloption(bits32 kinds, int type, const char *name, const char *desc,
case RELOPT_TYPE_BOOL:
size = sizeof(relopt_bool);
break;
+ case RELOPT_TYPE_TERNARY:
+ size = sizeof(relopt_ternary);
+ break;
case RELOPT_TYPE_INT:
size = sizeof(relopt_int);
break;
@@ -883,6 +907,54 @@ add_local_bool_reloption(local_relopts *relopts, const char *name,
add_local_reloption(relopts, (relopt_gen *) newoption, offset);
}
+/*
+ * init_ternary_reloption
+ * Allocate and initialize a new ternary reloption
+ */
+static relopt_ternary *
+init_ternary_reloption(bits32 kinds, const char *name, const char *desc,
+ ternary default_val, LOCKMODE lockmode)
+{
+ relopt_ternary *newoption;
+
+ newoption = (relopt_ternary *) allocate_reloption(kinds,
+ RELOPT_TYPE_TERNARY, name, desc, lockmode);
+ newoption->default_val = default_val;
+
+ return newoption;
+}
+
+/*
+ * add_ternary_reloption
+ * Add a new ternary reloption
+ */
+void
+add_ternary_reloption(bits32 kinds, const char *name, const char *desc,
+ ternary default_val, LOCKMODE lockmode)
+{
+ relopt_ternary *newoption = init_ternary_reloption(kinds, name, desc,
+ default_val, lockmode);
+
+ add_reloption((relopt_gen *) newoption);
+}
+
+/*
+ * add_local_ternary_reloption
+ * Add a new ternary local reloption
+ *
+ * 'offset' is offset of ternary-typed field.
+ */
+void
+add_local_ternary_reloption(local_relopts *relopts, const char *name,
+ const char *desc, ternary default_val,
+ int offset)
+{
+ relopt_ternary *newoption = init_ternary_reloption(RELOPT_KIND_LOCAL,
+ name, desc,
+ default_val, 0);
+
+ add_local_reloption(relopts, (relopt_gen *) newoption, offset);
+}
/*
* init_real_reloption
@@ -1617,6 +1689,18 @@ parse_one_reloption(relopt_value *option, char *text_str, int text_len,
option->gen->name, value)));
}
break;
+ case RELOPT_TYPE_TERNARY:
+ {
+ bool b;
+ parsed = parse_bool(value, &b);
+ option->values.ternary_val = b ? TERNARY_TRUE : TERNARY_FALSE;
+ if (validate && !parsed)
+ ereport(ERROR,
+ (errcode(ERRCODE_INVALID_PARAMETER_VALUE),
+ errmsg("invalid value for ternary option \"%s\": %s",
+ option->gen->name, value)));
+ }
+ break;
case RELOPT_TYPE_INT:
{
relopt_int *optint = (relopt_int *) option->gen;
@@ -1780,17 +1864,6 @@ fillRelOptions(void *rdopts, Size basesize,
char *itempos = ((char *) rdopts) + elems[j].offset;
char *string_val;
- /*
- * If isset_offset is provided, store whether the reloption is
- * set there.
- */
- if (elems[j].isset_offset > 0)
- {
- char *setpos = ((char *) rdopts) + elems[j].isset_offset;
-
- *(bool *) setpos = options[i].isset;
- }
-
switch (options[i].gen->type)
{
case RELOPT_TYPE_BOOL:
@@ -1798,6 +1871,11 @@ fillRelOptions(void *rdopts, Size basesize,
options[i].values.bool_val :
((relopt_bool *) options[i].gen)->default_val;
break;
+ case RELOPT_TYPE_TERNARY:
+ *(ternary *) itempos = options[i].isset ?
+ options[i].values.ternary_val :
+ ((relopt_ternary *) options[i].gen)->default_val;
+ break;
case RELOPT_TYPE_INT:
*(int *) itempos = options[i].isset ?
options[i].values.int_val :
@@ -1912,8 +1990,8 @@ default_reloptions(Datum reloptions, bool validate, relopt_kind kind)
offsetof(StdRdOptions, parallel_workers)},
{"vacuum_index_cleanup", RELOPT_TYPE_ENUM,
offsetof(StdRdOptions, vacuum_index_cleanup)},
- {"vacuum_truncate", RELOPT_TYPE_BOOL,
- offsetof(StdRdOptions, vacuum_truncate), offsetof(StdRdOptions, vacuum_truncate_set)},
+ {"vacuum_truncate", RELOPT_TYPE_TERNARY,
+ offsetof(StdRdOptions, vacuum_truncate)},
{"vacuum_max_eager_freeze_failure_rate", RELOPT_TYPE_REAL,
offsetof(StdRdOptions, vacuum_max_eager_freeze_failure_rate)}
};
@@ -1993,7 +2071,6 @@ build_local_reloptions(local_relopts *relopts, Datum options, bool validate)
elems[i].optname = opt->option->name;
elems[i].opttype = opt->option->type;
elems[i].offset = opt->offset;
- elems[i].isset_offset = 0; /* not supported for local relopts yet */
i++;
}
diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c
index 733ef40ae7..977babff54 100644
--- a/src/backend/commands/vacuum.c
+++ b/src/backend/commands/vacuum.c
@@ -2221,9 +2221,9 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams params,
{
StdRdOptions *opts = (StdRdOptions *) rel->rd_options;
- if (opts && opts->vacuum_truncate_set)
+ if (opts && opts->vacuum_truncate != TERNARY_UNSET)
{
- if (opts->vacuum_truncate)
+ if (opts->vacuum_truncate == TERNARY_TRUE)
params.truncate = VACOPTVALUE_ENABLED;
else
params.truncate = VACOPTVALUE_DISABLED;
diff --git a/src/include/access/reloptions.h b/src/include/access/reloptions.h
index a604a4702c..a436697658 100644
--- a/src/include/access/reloptions.h
+++ b/src/include/access/reloptions.h
@@ -29,6 +29,7 @@
typedef enum relopt_type
{
RELOPT_TYPE_BOOL,
+ RELOPT_TYPE_TERNARY, /* on, off, unset */
RELOPT_TYPE_INT,
RELOPT_TYPE_REAL,
RELOPT_TYPE_ENUM,
@@ -80,6 +81,7 @@ typedef struct relopt_value
union
{
bool bool_val;
+ ternary ternary_val;
int int_val;
double real_val;
int enum_val;
@@ -94,6 +96,12 @@ typedef struct relopt_bool
bool default_val;
} relopt_bool;
+typedef struct relopt_rernary
+{
+ relopt_gen gen;
+ int default_val;
+} relopt_ternary;
+
typedef struct relopt_int
{
relopt_gen gen;
@@ -152,19 +160,6 @@ typedef struct
const char *optname; /* option's name */
relopt_type opttype; /* option's datatype */
int offset; /* offset of field in result struct */
-
- /*
- * isset_offset is an optional offset of a field in the result struct that
- * stores whether the option is explicitly set for the relation or if it
- * just picked up the default value. In most cases, this can be
- * accomplished by giving the reloption a special out-of-range default
- * value (e.g., some integer reloptions use -2), but this isn't always
- * possible. For example, a Boolean reloption cannot be given an
- * out-of-range default, so we need another way to discover the source of
- * its value. This offset is only used if given a value greater than
- * zero.
- */
- int isset_offset;
} relopt_parse_elt;
/* Local reloption definition */
@@ -195,6 +190,8 @@ typedef struct local_relopts
extern relopt_kind add_reloption_kind(void);
extern void add_bool_reloption(bits32 kinds, const char *name, const char *desc,
bool default_val, LOCKMODE lockmode);
+extern void add_ternary_reloption(bits32 kinds, const char *name,
+ const char *desc, int default_val, LOCKMODE lockmode);
extern void add_int_reloption(bits32 kinds, const char *name, const char *desc,
int default_val, int min_val, int max_val,
LOCKMODE lockmode);
@@ -214,6 +211,9 @@ extern void register_reloptions_validator(local_relopts *relopts,
extern void add_local_bool_reloption(local_relopts *relopts, const char *name,
const char *desc, bool default_val,
int offset);
+extern void add_local_ternary_reloption(local_relopts *relopts,
+ const char *name, const char *desc,
+ ternary default_val, int offset);
extern void add_local_int_reloption(local_relopts *relopts, const char *name,
const char *desc, int default_val,
int min_val, int max_val, int offset);
diff --git a/src/include/c.h b/src/include/c.h
index 39022f8a9d..9c59ccccf8 100644
--- a/src/include/c.h
+++ b/src/include/c.h
@@ -488,6 +488,22 @@ typedef void (*pg_funcptr_t) (void);
#include <stdbool.h>
+/*
+ * ternary
+ * Boolean value with an extrea "unset" option
+ *
+ * Ternary data type is used in relation options that can be "true", "false" or
+ * "unset". Since relation options are used deep inside the PostgreSQL code,
+ * this type is declared globally.
+*/
+
+typedef enum ternary
+{
+ TERNARY_FALSE = 0,
+ TERNARY_TRUE = 1,
+ TERNARY_UNSET = -1
+} ternary;
+
/* ----------------------------------------------------------------
* Section 3: standard system types
diff --git a/src/include/utils/rel.h b/src/include/utils/rel.h
index b552359915..7346d618f9 100644
--- a/src/include/utils/rel.h
+++ b/src/include/utils/rel.h
@@ -346,8 +346,7 @@ typedef struct StdRdOptions
bool user_catalog_table; /* use as an additional catalog relation */
int parallel_workers; /* max number of parallel workers */
StdRdOptIndexCleanup vacuum_index_cleanup; /* controls index vacuuming */
- bool vacuum_truncate; /* enables vacuum to truncate a relation */
- bool vacuum_truncate_set; /* whether vacuum_truncate is set */
+ ternary vacuum_truncate; /* enables vacuum to truncate a relation */
/*
* Fraction of pages in a relation that vacuum can eagerly scan and fail
--
2.39.2
--nextPart5129932.5fSG56mABF
Content-Disposition: attachment;
filename="v2-0003-Add-alias-to-be-used-as-unset-state.patch"
Content-Transfer-Encoding: 7Bit
Content-Type: text/x-patch; charset="unicode-2-0-utf-8";
name="v2-0003-Add-alias-to-be-used-as-unset-state.patch"
^ permalink raw reply [nested|flat] 868+ messages in thread
* [PATCH v2a 2/4] Introduce ternary reloptions
@ 2025-09-04 14:41 Nikolay Shaplov <[email protected]>
0 siblings, 0 replies; 868+ messages in thread
From: Nikolay Shaplov @ 2025-09-04 14:41 UTC (permalink / raw)
Introduce ternary reloption as a replacement for current `vacuum_truncate`
implementation. Remove `vacuum_truncate_set` additional flag and using
`TERNARY_UNSET` value instead.
---
src/backend/access/common/reloptions.c | 129 ++++++++++++++++++++-----
src/backend/commands/vacuum.c | 4 +-
src/include/access/reloptions.h | 26 ++---
src/include/c.h | 16 +++
src/include/utils/rel.h | 3 +-
5 files changed, 135 insertions(+), 43 deletions(-)
diff --git a/src/backend/access/common/reloptions.c b/src/backend/access/common/reloptions.c
index 0af3fea68f..f543f11001 100644
--- a/src/backend/access/common/reloptions.c
+++ b/src/backend/access/common/reloptions.c
@@ -40,9 +40,9 @@
*
* To add an option:
*
- * (i) decide on a type (bool, integer, real, enum, string), name, default
- * value, upper and lower bounds (if applicable); for strings, consider a
- * validation routine.
+ * (i) decide on a type (bool, ternary, integer, real, enum, string), name,
+ * default value, upper and lower bounds (if applicable); for strings,
+ * consider a validation routine.
* (ii) add a record below (or use add_<type>_reloption).
* (iii) add it to the appropriate options struct (perhaps StdRdOptions)
* (iv) add it to the appropriate handling routine (perhaps
@@ -147,15 +147,6 @@ static relopt_bool boolRelOpts[] =
},
false
},
- {
- {
- "vacuum_truncate",
- "Enables vacuum to truncate empty pages at the end of this table",
- RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
- ShareUpdateExclusiveLock
- },
- true
- },
{
{
"deduplicate_items",
@@ -170,6 +161,21 @@ static relopt_bool boolRelOpts[] =
{{NULL}}
};
+static relopt_ternary ternaryRelOpts[] =
+{
+ {
+ {
+ "vacuum_truncate",
+ "Enables vacuum to truncate empty pages at the end of this table",
+ RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
+ ShareUpdateExclusiveLock
+ },
+ TERNARY_UNSET
+ },
+ /* list terminator */
+ {{NULL}}
+};
+
static relopt_int intRelOpts[] =
{
{
@@ -600,6 +606,13 @@ initialize_reloptions(void)
boolRelOpts[i].gen.lockmode));
j++;
}
+ for (i = 0; ternaryRelOpts[i].gen.name; i++)
+ {
+ Assert(DoLockModesConflict(ternaryRelOpts[i].gen.lockmode,
+ ternaryRelOpts[i].gen.lockmode));
+ j++;
+ }
+
for (i = 0; intRelOpts[i].gen.name; i++)
{
Assert(DoLockModesConflict(intRelOpts[i].gen.lockmode,
@@ -640,6 +653,14 @@ initialize_reloptions(void)
j++;
}
+ for (i = 0; ternaryRelOpts[i].gen.name; i++)
+ {
+ relOpts[j] = &ternaryRelOpts[i].gen;
+ relOpts[j]->type = RELOPT_TYPE_TERNARY;
+ relOpts[j]->namelen = strlen(relOpts[j]->name);
+ j++;
+ }
+
for (i = 0; intRelOpts[i].gen.name; i++)
{
relOpts[j] = &intRelOpts[i].gen;
@@ -800,6 +821,9 @@ allocate_reloption(bits32 kinds, int type, const char *name, const char *desc,
case RELOPT_TYPE_BOOL:
size = sizeof(relopt_bool);
break;
+ case RELOPT_TYPE_TERNARY:
+ size = sizeof(relopt_ternary);
+ break;
case RELOPT_TYPE_INT:
size = sizeof(relopt_int);
break;
@@ -883,6 +907,54 @@ add_local_bool_reloption(local_relopts *relopts, const char *name,
add_local_reloption(relopts, (relopt_gen *) newoption, offset);
}
+/*
+ * init_ternary_reloption
+ * Allocate and initialize a new ternary reloption
+ */
+static relopt_ternary *
+init_ternary_reloption(bits32 kinds, const char *name, const char *desc,
+ ternary default_val, LOCKMODE lockmode)
+{
+ relopt_ternary *newoption;
+
+ newoption = (relopt_ternary *) allocate_reloption(kinds,
+ RELOPT_TYPE_TERNARY, name, desc, lockmode);
+ newoption->default_val = default_val;
+
+ return newoption;
+}
+
+/*
+ * add_ternary_reloption
+ * Add a new ternary reloption
+ */
+void
+add_ternary_reloption(bits32 kinds, const char *name, const char *desc,
+ ternary default_val, LOCKMODE lockmode)
+{
+ relopt_ternary *newoption = init_ternary_reloption(kinds, name, desc,
+ default_val, lockmode);
+
+ add_reloption((relopt_gen *) newoption);
+}
+
+/*
+ * add_local_ternary_reloption
+ * Add a new ternary local reloption
+ *
+ * 'offset' is offset of ternary-typed field.
+ */
+void
+add_local_ternary_reloption(local_relopts *relopts, const char *name,
+ const char *desc, ternary default_val,
+ int offset)
+{
+ relopt_ternary *newoption = init_ternary_reloption(RELOPT_KIND_LOCAL,
+ name, desc,
+ default_val, 0);
+
+ add_local_reloption(relopts, (relopt_gen *) newoption, offset);
+}
/*
* init_real_reloption
@@ -1617,6 +1689,18 @@ parse_one_reloption(relopt_value *option, char *text_str, int text_len,
option->gen->name, value)));
}
break;
+ case RELOPT_TYPE_TERNARY:
+ {
+ bool b;
+ parsed = parse_bool(value, &b);
+ option->values.ternary_val = b ? TERNARY_TRUE : TERNARY_FALSE;
+ if (validate && !parsed)
+ ereport(ERROR,
+ (errcode(ERRCODE_INVALID_PARAMETER_VALUE),
+ errmsg("invalid value for ternary option \"%s\": %s",
+ option->gen->name, value)));
+ }
+ break;
case RELOPT_TYPE_INT:
{
relopt_int *optint = (relopt_int *) option->gen;
@@ -1780,17 +1864,6 @@ fillRelOptions(void *rdopts, Size basesize,
char *itempos = ((char *) rdopts) + elems[j].offset;
char *string_val;
- /*
- * If isset_offset is provided, store whether the reloption is
- * set there.
- */
- if (elems[j].isset_offset > 0)
- {
- char *setpos = ((char *) rdopts) + elems[j].isset_offset;
-
- *(bool *) setpos = options[i].isset;
- }
-
switch (options[i].gen->type)
{
case RELOPT_TYPE_BOOL:
@@ -1798,6 +1871,11 @@ fillRelOptions(void *rdopts, Size basesize,
options[i].values.bool_val :
((relopt_bool *) options[i].gen)->default_val;
break;
+ case RELOPT_TYPE_TERNARY:
+ *(ternary *) itempos = options[i].isset ?
+ options[i].values.ternary_val :
+ ((relopt_ternary *) options[i].gen)->default_val;
+ break;
case RELOPT_TYPE_INT:
*(int *) itempos = options[i].isset ?
options[i].values.int_val :
@@ -1912,8 +1990,8 @@ default_reloptions(Datum reloptions, bool validate, relopt_kind kind)
offsetof(StdRdOptions, parallel_workers)},
{"vacuum_index_cleanup", RELOPT_TYPE_ENUM,
offsetof(StdRdOptions, vacuum_index_cleanup)},
- {"vacuum_truncate", RELOPT_TYPE_BOOL,
- offsetof(StdRdOptions, vacuum_truncate), offsetof(StdRdOptions, vacuum_truncate_set)},
+ {"vacuum_truncate", RELOPT_TYPE_TERNARY,
+ offsetof(StdRdOptions, vacuum_truncate)},
{"vacuum_max_eager_freeze_failure_rate", RELOPT_TYPE_REAL,
offsetof(StdRdOptions, vacuum_max_eager_freeze_failure_rate)}
};
@@ -1993,7 +2071,6 @@ build_local_reloptions(local_relopts *relopts, Datum options, bool validate)
elems[i].optname = opt->option->name;
elems[i].opttype = opt->option->type;
elems[i].offset = opt->offset;
- elems[i].isset_offset = 0; /* not supported for local relopts yet */
i++;
}
diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c
index 733ef40ae7..977babff54 100644
--- a/src/backend/commands/vacuum.c
+++ b/src/backend/commands/vacuum.c
@@ -2221,9 +2221,9 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams params,
{
StdRdOptions *opts = (StdRdOptions *) rel->rd_options;
- if (opts && opts->vacuum_truncate_set)
+ if (opts && opts->vacuum_truncate != TERNARY_UNSET)
{
- if (opts->vacuum_truncate)
+ if (opts->vacuum_truncate == TERNARY_TRUE)
params.truncate = VACOPTVALUE_ENABLED;
else
params.truncate = VACOPTVALUE_DISABLED;
diff --git a/src/include/access/reloptions.h b/src/include/access/reloptions.h
index a604a4702c..a436697658 100644
--- a/src/include/access/reloptions.h
+++ b/src/include/access/reloptions.h
@@ -29,6 +29,7 @@
typedef enum relopt_type
{
RELOPT_TYPE_BOOL,
+ RELOPT_TYPE_TERNARY, /* on, off, unset */
RELOPT_TYPE_INT,
RELOPT_TYPE_REAL,
RELOPT_TYPE_ENUM,
@@ -80,6 +81,7 @@ typedef struct relopt_value
union
{
bool bool_val;
+ ternary ternary_val;
int int_val;
double real_val;
int enum_val;
@@ -94,6 +96,12 @@ typedef struct relopt_bool
bool default_val;
} relopt_bool;
+typedef struct relopt_rernary
+{
+ relopt_gen gen;
+ int default_val;
+} relopt_ternary;
+
typedef struct relopt_int
{
relopt_gen gen;
@@ -152,19 +160,6 @@ typedef struct
const char *optname; /* option's name */
relopt_type opttype; /* option's datatype */
int offset; /* offset of field in result struct */
-
- /*
- * isset_offset is an optional offset of a field in the result struct that
- * stores whether the option is explicitly set for the relation or if it
- * just picked up the default value. In most cases, this can be
- * accomplished by giving the reloption a special out-of-range default
- * value (e.g., some integer reloptions use -2), but this isn't always
- * possible. For example, a Boolean reloption cannot be given an
- * out-of-range default, so we need another way to discover the source of
- * its value. This offset is only used if given a value greater than
- * zero.
- */
- int isset_offset;
} relopt_parse_elt;
/* Local reloption definition */
@@ -195,6 +190,8 @@ typedef struct local_relopts
extern relopt_kind add_reloption_kind(void);
extern void add_bool_reloption(bits32 kinds, const char *name, const char *desc,
bool default_val, LOCKMODE lockmode);
+extern void add_ternary_reloption(bits32 kinds, const char *name,
+ const char *desc, int default_val, LOCKMODE lockmode);
extern void add_int_reloption(bits32 kinds, const char *name, const char *desc,
int default_val, int min_val, int max_val,
LOCKMODE lockmode);
@@ -214,6 +211,9 @@ extern void register_reloptions_validator(local_relopts *relopts,
extern void add_local_bool_reloption(local_relopts *relopts, const char *name,
const char *desc, bool default_val,
int offset);
+extern void add_local_ternary_reloption(local_relopts *relopts,
+ const char *name, const char *desc,
+ ternary default_val, int offset);
extern void add_local_int_reloption(local_relopts *relopts, const char *name,
const char *desc, int default_val,
int min_val, int max_val, int offset);
diff --git a/src/include/c.h b/src/include/c.h
index 39022f8a9d..9c59ccccf8 100644
--- a/src/include/c.h
+++ b/src/include/c.h
@@ -488,6 +488,22 @@ typedef void (*pg_funcptr_t) (void);
#include <stdbool.h>
+/*
+ * ternary
+ * Boolean value with an extrea "unset" option
+ *
+ * Ternary data type is used in relation options that can be "true", "false" or
+ * "unset". Since relation options are used deep inside the PostgreSQL code,
+ * this type is declared globally.
+*/
+
+typedef enum ternary
+{
+ TERNARY_FALSE = 0,
+ TERNARY_TRUE = 1,
+ TERNARY_UNSET = -1
+} ternary;
+
/* ----------------------------------------------------------------
* Section 3: standard system types
diff --git a/src/include/utils/rel.h b/src/include/utils/rel.h
index b552359915..7346d618f9 100644
--- a/src/include/utils/rel.h
+++ b/src/include/utils/rel.h
@@ -346,8 +346,7 @@ typedef struct StdRdOptions
bool user_catalog_table; /* use as an additional catalog relation */
int parallel_workers; /* max number of parallel workers */
StdRdOptIndexCleanup vacuum_index_cleanup; /* controls index vacuuming */
- bool vacuum_truncate; /* enables vacuum to truncate a relation */
- bool vacuum_truncate_set; /* whether vacuum_truncate is set */
+ ternary vacuum_truncate; /* enables vacuum to truncate a relation */
/*
* Fraction of pages in a relation that vacuum can eagerly scan and fail
--
2.39.2
--nextPart4155600.3ZeAukHxDK
Content-Disposition: attachment;
filename="v2a-0003-Add-alias-to-be-used-as-unset-state.patch"
Content-Transfer-Encoding: 7Bit
Content-Type: text/x-patch; charset="unicode-2-0-utf-8";
name="v2a-0003-Add-alias-to-be-used-as-unset-state.patch"
^ permalink raw reply [nested|flat] 868+ messages in thread
* [PATCH v2a 2/4] Introduce ternary reloptions
@ 2025-09-04 14:41 Nikolay Shaplov <[email protected]>
0 siblings, 0 replies; 868+ messages in thread
From: Nikolay Shaplov @ 2025-09-04 14:41 UTC (permalink / raw)
Introduce ternary reloption as a replacement for current `vacuum_truncate`
implementation. Remove `vacuum_truncate_set` additional flag and using
`TERNARY_UNSET` value instead.
---
src/backend/access/common/reloptions.c | 129 ++++++++++++++++++++-----
src/backend/commands/vacuum.c | 4 +-
src/include/access/reloptions.h | 26 ++---
src/include/c.h | 16 +++
src/include/utils/rel.h | 3 +-
5 files changed, 135 insertions(+), 43 deletions(-)
diff --git a/src/backend/access/common/reloptions.c b/src/backend/access/common/reloptions.c
index 0af3fea68f..f543f11001 100644
--- a/src/backend/access/common/reloptions.c
+++ b/src/backend/access/common/reloptions.c
@@ -40,9 +40,9 @@
*
* To add an option:
*
- * (i) decide on a type (bool, integer, real, enum, string), name, default
- * value, upper and lower bounds (if applicable); for strings, consider a
- * validation routine.
+ * (i) decide on a type (bool, ternary, integer, real, enum, string), name,
+ * default value, upper and lower bounds (if applicable); for strings,
+ * consider a validation routine.
* (ii) add a record below (or use add_<type>_reloption).
* (iii) add it to the appropriate options struct (perhaps StdRdOptions)
* (iv) add it to the appropriate handling routine (perhaps
@@ -147,15 +147,6 @@ static relopt_bool boolRelOpts[] =
},
false
},
- {
- {
- "vacuum_truncate",
- "Enables vacuum to truncate empty pages at the end of this table",
- RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
- ShareUpdateExclusiveLock
- },
- true
- },
{
{
"deduplicate_items",
@@ -170,6 +161,21 @@ static relopt_bool boolRelOpts[] =
{{NULL}}
};
+static relopt_ternary ternaryRelOpts[] =
+{
+ {
+ {
+ "vacuum_truncate",
+ "Enables vacuum to truncate empty pages at the end of this table",
+ RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
+ ShareUpdateExclusiveLock
+ },
+ TERNARY_UNSET
+ },
+ /* list terminator */
+ {{NULL}}
+};
+
static relopt_int intRelOpts[] =
{
{
@@ -600,6 +606,13 @@ initialize_reloptions(void)
boolRelOpts[i].gen.lockmode));
j++;
}
+ for (i = 0; ternaryRelOpts[i].gen.name; i++)
+ {
+ Assert(DoLockModesConflict(ternaryRelOpts[i].gen.lockmode,
+ ternaryRelOpts[i].gen.lockmode));
+ j++;
+ }
+
for (i = 0; intRelOpts[i].gen.name; i++)
{
Assert(DoLockModesConflict(intRelOpts[i].gen.lockmode,
@@ -640,6 +653,14 @@ initialize_reloptions(void)
j++;
}
+ for (i = 0; ternaryRelOpts[i].gen.name; i++)
+ {
+ relOpts[j] = &ternaryRelOpts[i].gen;
+ relOpts[j]->type = RELOPT_TYPE_TERNARY;
+ relOpts[j]->namelen = strlen(relOpts[j]->name);
+ j++;
+ }
+
for (i = 0; intRelOpts[i].gen.name; i++)
{
relOpts[j] = &intRelOpts[i].gen;
@@ -800,6 +821,9 @@ allocate_reloption(bits32 kinds, int type, const char *name, const char *desc,
case RELOPT_TYPE_BOOL:
size = sizeof(relopt_bool);
break;
+ case RELOPT_TYPE_TERNARY:
+ size = sizeof(relopt_ternary);
+ break;
case RELOPT_TYPE_INT:
size = sizeof(relopt_int);
break;
@@ -883,6 +907,54 @@ add_local_bool_reloption(local_relopts *relopts, const char *name,
add_local_reloption(relopts, (relopt_gen *) newoption, offset);
}
+/*
+ * init_ternary_reloption
+ * Allocate and initialize a new ternary reloption
+ */
+static relopt_ternary *
+init_ternary_reloption(bits32 kinds, const char *name, const char *desc,
+ ternary default_val, LOCKMODE lockmode)
+{
+ relopt_ternary *newoption;
+
+ newoption = (relopt_ternary *) allocate_reloption(kinds,
+ RELOPT_TYPE_TERNARY, name, desc, lockmode);
+ newoption->default_val = default_val;
+
+ return newoption;
+}
+
+/*
+ * add_ternary_reloption
+ * Add a new ternary reloption
+ */
+void
+add_ternary_reloption(bits32 kinds, const char *name, const char *desc,
+ ternary default_val, LOCKMODE lockmode)
+{
+ relopt_ternary *newoption = init_ternary_reloption(kinds, name, desc,
+ default_val, lockmode);
+
+ add_reloption((relopt_gen *) newoption);
+}
+
+/*
+ * add_local_ternary_reloption
+ * Add a new ternary local reloption
+ *
+ * 'offset' is offset of ternary-typed field.
+ */
+void
+add_local_ternary_reloption(local_relopts *relopts, const char *name,
+ const char *desc, ternary default_val,
+ int offset)
+{
+ relopt_ternary *newoption = init_ternary_reloption(RELOPT_KIND_LOCAL,
+ name, desc,
+ default_val, 0);
+
+ add_local_reloption(relopts, (relopt_gen *) newoption, offset);
+}
/*
* init_real_reloption
@@ -1617,6 +1689,18 @@ parse_one_reloption(relopt_value *option, char *text_str, int text_len,
option->gen->name, value)));
}
break;
+ case RELOPT_TYPE_TERNARY:
+ {
+ bool b;
+ parsed = parse_bool(value, &b);
+ option->values.ternary_val = b ? TERNARY_TRUE : TERNARY_FALSE;
+ if (validate && !parsed)
+ ereport(ERROR,
+ (errcode(ERRCODE_INVALID_PARAMETER_VALUE),
+ errmsg("invalid value for ternary option \"%s\": %s",
+ option->gen->name, value)));
+ }
+ break;
case RELOPT_TYPE_INT:
{
relopt_int *optint = (relopt_int *) option->gen;
@@ -1780,17 +1864,6 @@ fillRelOptions(void *rdopts, Size basesize,
char *itempos = ((char *) rdopts) + elems[j].offset;
char *string_val;
- /*
- * If isset_offset is provided, store whether the reloption is
- * set there.
- */
- if (elems[j].isset_offset > 0)
- {
- char *setpos = ((char *) rdopts) + elems[j].isset_offset;
-
- *(bool *) setpos = options[i].isset;
- }
-
switch (options[i].gen->type)
{
case RELOPT_TYPE_BOOL:
@@ -1798,6 +1871,11 @@ fillRelOptions(void *rdopts, Size basesize,
options[i].values.bool_val :
((relopt_bool *) options[i].gen)->default_val;
break;
+ case RELOPT_TYPE_TERNARY:
+ *(ternary *) itempos = options[i].isset ?
+ options[i].values.ternary_val :
+ ((relopt_ternary *) options[i].gen)->default_val;
+ break;
case RELOPT_TYPE_INT:
*(int *) itempos = options[i].isset ?
options[i].values.int_val :
@@ -1912,8 +1990,8 @@ default_reloptions(Datum reloptions, bool validate, relopt_kind kind)
offsetof(StdRdOptions, parallel_workers)},
{"vacuum_index_cleanup", RELOPT_TYPE_ENUM,
offsetof(StdRdOptions, vacuum_index_cleanup)},
- {"vacuum_truncate", RELOPT_TYPE_BOOL,
- offsetof(StdRdOptions, vacuum_truncate), offsetof(StdRdOptions, vacuum_truncate_set)},
+ {"vacuum_truncate", RELOPT_TYPE_TERNARY,
+ offsetof(StdRdOptions, vacuum_truncate)},
{"vacuum_max_eager_freeze_failure_rate", RELOPT_TYPE_REAL,
offsetof(StdRdOptions, vacuum_max_eager_freeze_failure_rate)}
};
@@ -1993,7 +2071,6 @@ build_local_reloptions(local_relopts *relopts, Datum options, bool validate)
elems[i].optname = opt->option->name;
elems[i].opttype = opt->option->type;
elems[i].offset = opt->offset;
- elems[i].isset_offset = 0; /* not supported for local relopts yet */
i++;
}
diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c
index 733ef40ae7..977babff54 100644
--- a/src/backend/commands/vacuum.c
+++ b/src/backend/commands/vacuum.c
@@ -2221,9 +2221,9 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams params,
{
StdRdOptions *opts = (StdRdOptions *) rel->rd_options;
- if (opts && opts->vacuum_truncate_set)
+ if (opts && opts->vacuum_truncate != TERNARY_UNSET)
{
- if (opts->vacuum_truncate)
+ if (opts->vacuum_truncate == TERNARY_TRUE)
params.truncate = VACOPTVALUE_ENABLED;
else
params.truncate = VACOPTVALUE_DISABLED;
diff --git a/src/include/access/reloptions.h b/src/include/access/reloptions.h
index a604a4702c..a436697658 100644
--- a/src/include/access/reloptions.h
+++ b/src/include/access/reloptions.h
@@ -29,6 +29,7 @@
typedef enum relopt_type
{
RELOPT_TYPE_BOOL,
+ RELOPT_TYPE_TERNARY, /* on, off, unset */
RELOPT_TYPE_INT,
RELOPT_TYPE_REAL,
RELOPT_TYPE_ENUM,
@@ -80,6 +81,7 @@ typedef struct relopt_value
union
{
bool bool_val;
+ ternary ternary_val;
int int_val;
double real_val;
int enum_val;
@@ -94,6 +96,12 @@ typedef struct relopt_bool
bool default_val;
} relopt_bool;
+typedef struct relopt_rernary
+{
+ relopt_gen gen;
+ int default_val;
+} relopt_ternary;
+
typedef struct relopt_int
{
relopt_gen gen;
@@ -152,19 +160,6 @@ typedef struct
const char *optname; /* option's name */
relopt_type opttype; /* option's datatype */
int offset; /* offset of field in result struct */
-
- /*
- * isset_offset is an optional offset of a field in the result struct that
- * stores whether the option is explicitly set for the relation or if it
- * just picked up the default value. In most cases, this can be
- * accomplished by giving the reloption a special out-of-range default
- * value (e.g., some integer reloptions use -2), but this isn't always
- * possible. For example, a Boolean reloption cannot be given an
- * out-of-range default, so we need another way to discover the source of
- * its value. This offset is only used if given a value greater than
- * zero.
- */
- int isset_offset;
} relopt_parse_elt;
/* Local reloption definition */
@@ -195,6 +190,8 @@ typedef struct local_relopts
extern relopt_kind add_reloption_kind(void);
extern void add_bool_reloption(bits32 kinds, const char *name, const char *desc,
bool default_val, LOCKMODE lockmode);
+extern void add_ternary_reloption(bits32 kinds, const char *name,
+ const char *desc, int default_val, LOCKMODE lockmode);
extern void add_int_reloption(bits32 kinds, const char *name, const char *desc,
int default_val, int min_val, int max_val,
LOCKMODE lockmode);
@@ -214,6 +211,9 @@ extern void register_reloptions_validator(local_relopts *relopts,
extern void add_local_bool_reloption(local_relopts *relopts, const char *name,
const char *desc, bool default_val,
int offset);
+extern void add_local_ternary_reloption(local_relopts *relopts,
+ const char *name, const char *desc,
+ ternary default_val, int offset);
extern void add_local_int_reloption(local_relopts *relopts, const char *name,
const char *desc, int default_val,
int min_val, int max_val, int offset);
diff --git a/src/include/c.h b/src/include/c.h
index 39022f8a9d..9c59ccccf8 100644
--- a/src/include/c.h
+++ b/src/include/c.h
@@ -488,6 +488,22 @@ typedef void (*pg_funcptr_t) (void);
#include <stdbool.h>
+/*
+ * ternary
+ * Boolean value with an extrea "unset" option
+ *
+ * Ternary data type is used in relation options that can be "true", "false" or
+ * "unset". Since relation options are used deep inside the PostgreSQL code,
+ * this type is declared globally.
+*/
+
+typedef enum ternary
+{
+ TERNARY_FALSE = 0,
+ TERNARY_TRUE = 1,
+ TERNARY_UNSET = -1
+} ternary;
+
/* ----------------------------------------------------------------
* Section 3: standard system types
diff --git a/src/include/utils/rel.h b/src/include/utils/rel.h
index b552359915..7346d618f9 100644
--- a/src/include/utils/rel.h
+++ b/src/include/utils/rel.h
@@ -346,8 +346,7 @@ typedef struct StdRdOptions
bool user_catalog_table; /* use as an additional catalog relation */
int parallel_workers; /* max number of parallel workers */
StdRdOptIndexCleanup vacuum_index_cleanup; /* controls index vacuuming */
- bool vacuum_truncate; /* enables vacuum to truncate a relation */
- bool vacuum_truncate_set; /* whether vacuum_truncate is set */
+ ternary vacuum_truncate; /* enables vacuum to truncate a relation */
/*
* Fraction of pages in a relation that vacuum can eagerly scan and fail
--
2.39.2
--nextPart4155600.3ZeAukHxDK
Content-Disposition: attachment;
filename="v2a-0003-Add-alias-to-be-used-as-unset-state.patch"
Content-Transfer-Encoding: 7Bit
Content-Type: text/x-patch; charset="unicode-2-0-utf-8";
name="v2a-0003-Add-alias-to-be-used-as-unset-state.patch"
^ permalink raw reply [nested|flat] 868+ messages in thread
* [PATCH v2 2/4] Introduce ternary reloptions
@ 2025-09-04 14:41 Nikolay Shaplov <[email protected]>
0 siblings, 0 replies; 868+ messages in thread
From: Nikolay Shaplov @ 2025-09-04 14:41 UTC (permalink / raw)
Introduce ternary reloption as a replacement for current `vacuum_truncate`
implementation. Remove `vacuum_truncate_set` additional flag and using
`TERNARY_UNSET` value instead.
---
src/backend/access/common/reloptions.c | 129 ++++++++++++++++++++-----
src/backend/commands/vacuum.c | 4 +-
src/include/access/reloptions.h | 26 ++---
src/include/c.h | 16 +++
src/include/utils/rel.h | 3 +-
5 files changed, 135 insertions(+), 43 deletions(-)
diff --git a/src/backend/access/common/reloptions.c b/src/backend/access/common/reloptions.c
index 0af3fea68f..f543f11001 100644
--- a/src/backend/access/common/reloptions.c
+++ b/src/backend/access/common/reloptions.c
@@ -40,9 +40,9 @@
*
* To add an option:
*
- * (i) decide on a type (bool, integer, real, enum, string), name, default
- * value, upper and lower bounds (if applicable); for strings, consider a
- * validation routine.
+ * (i) decide on a type (bool, ternary, integer, real, enum, string), name,
+ * default value, upper and lower bounds (if applicable); for strings,
+ * consider a validation routine.
* (ii) add a record below (or use add_<type>_reloption).
* (iii) add it to the appropriate options struct (perhaps StdRdOptions)
* (iv) add it to the appropriate handling routine (perhaps
@@ -147,15 +147,6 @@ static relopt_bool boolRelOpts[] =
},
false
},
- {
- {
- "vacuum_truncate",
- "Enables vacuum to truncate empty pages at the end of this table",
- RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
- ShareUpdateExclusiveLock
- },
- true
- },
{
{
"deduplicate_items",
@@ -170,6 +161,21 @@ static relopt_bool boolRelOpts[] =
{{NULL}}
};
+static relopt_ternary ternaryRelOpts[] =
+{
+ {
+ {
+ "vacuum_truncate",
+ "Enables vacuum to truncate empty pages at the end of this table",
+ RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
+ ShareUpdateExclusiveLock
+ },
+ TERNARY_UNSET
+ },
+ /* list terminator */
+ {{NULL}}
+};
+
static relopt_int intRelOpts[] =
{
{
@@ -600,6 +606,13 @@ initialize_reloptions(void)
boolRelOpts[i].gen.lockmode));
j++;
}
+ for (i = 0; ternaryRelOpts[i].gen.name; i++)
+ {
+ Assert(DoLockModesConflict(ternaryRelOpts[i].gen.lockmode,
+ ternaryRelOpts[i].gen.lockmode));
+ j++;
+ }
+
for (i = 0; intRelOpts[i].gen.name; i++)
{
Assert(DoLockModesConflict(intRelOpts[i].gen.lockmode,
@@ -640,6 +653,14 @@ initialize_reloptions(void)
j++;
}
+ for (i = 0; ternaryRelOpts[i].gen.name; i++)
+ {
+ relOpts[j] = &ternaryRelOpts[i].gen;
+ relOpts[j]->type = RELOPT_TYPE_TERNARY;
+ relOpts[j]->namelen = strlen(relOpts[j]->name);
+ j++;
+ }
+
for (i = 0; intRelOpts[i].gen.name; i++)
{
relOpts[j] = &intRelOpts[i].gen;
@@ -800,6 +821,9 @@ allocate_reloption(bits32 kinds, int type, const char *name, const char *desc,
case RELOPT_TYPE_BOOL:
size = sizeof(relopt_bool);
break;
+ case RELOPT_TYPE_TERNARY:
+ size = sizeof(relopt_ternary);
+ break;
case RELOPT_TYPE_INT:
size = sizeof(relopt_int);
break;
@@ -883,6 +907,54 @@ add_local_bool_reloption(local_relopts *relopts, const char *name,
add_local_reloption(relopts, (relopt_gen *) newoption, offset);
}
+/*
+ * init_ternary_reloption
+ * Allocate and initialize a new ternary reloption
+ */
+static relopt_ternary *
+init_ternary_reloption(bits32 kinds, const char *name, const char *desc,
+ ternary default_val, LOCKMODE lockmode)
+{
+ relopt_ternary *newoption;
+
+ newoption = (relopt_ternary *) allocate_reloption(kinds,
+ RELOPT_TYPE_TERNARY, name, desc, lockmode);
+ newoption->default_val = default_val;
+
+ return newoption;
+}
+
+/*
+ * add_ternary_reloption
+ * Add a new ternary reloption
+ */
+void
+add_ternary_reloption(bits32 kinds, const char *name, const char *desc,
+ ternary default_val, LOCKMODE lockmode)
+{
+ relopt_ternary *newoption = init_ternary_reloption(kinds, name, desc,
+ default_val, lockmode);
+
+ add_reloption((relopt_gen *) newoption);
+}
+
+/*
+ * add_local_ternary_reloption
+ * Add a new ternary local reloption
+ *
+ * 'offset' is offset of ternary-typed field.
+ */
+void
+add_local_ternary_reloption(local_relopts *relopts, const char *name,
+ const char *desc, ternary default_val,
+ int offset)
+{
+ relopt_ternary *newoption = init_ternary_reloption(RELOPT_KIND_LOCAL,
+ name, desc,
+ default_val, 0);
+
+ add_local_reloption(relopts, (relopt_gen *) newoption, offset);
+}
/*
* init_real_reloption
@@ -1617,6 +1689,18 @@ parse_one_reloption(relopt_value *option, char *text_str, int text_len,
option->gen->name, value)));
}
break;
+ case RELOPT_TYPE_TERNARY:
+ {
+ bool b;
+ parsed = parse_bool(value, &b);
+ option->values.ternary_val = b ? TERNARY_TRUE : TERNARY_FALSE;
+ if (validate && !parsed)
+ ereport(ERROR,
+ (errcode(ERRCODE_INVALID_PARAMETER_VALUE),
+ errmsg("invalid value for ternary option \"%s\": %s",
+ option->gen->name, value)));
+ }
+ break;
case RELOPT_TYPE_INT:
{
relopt_int *optint = (relopt_int *) option->gen;
@@ -1780,17 +1864,6 @@ fillRelOptions(void *rdopts, Size basesize,
char *itempos = ((char *) rdopts) + elems[j].offset;
char *string_val;
- /*
- * If isset_offset is provided, store whether the reloption is
- * set there.
- */
- if (elems[j].isset_offset > 0)
- {
- char *setpos = ((char *) rdopts) + elems[j].isset_offset;
-
- *(bool *) setpos = options[i].isset;
- }
-
switch (options[i].gen->type)
{
case RELOPT_TYPE_BOOL:
@@ -1798,6 +1871,11 @@ fillRelOptions(void *rdopts, Size basesize,
options[i].values.bool_val :
((relopt_bool *) options[i].gen)->default_val;
break;
+ case RELOPT_TYPE_TERNARY:
+ *(ternary *) itempos = options[i].isset ?
+ options[i].values.ternary_val :
+ ((relopt_ternary *) options[i].gen)->default_val;
+ break;
case RELOPT_TYPE_INT:
*(int *) itempos = options[i].isset ?
options[i].values.int_val :
@@ -1912,8 +1990,8 @@ default_reloptions(Datum reloptions, bool validate, relopt_kind kind)
offsetof(StdRdOptions, parallel_workers)},
{"vacuum_index_cleanup", RELOPT_TYPE_ENUM,
offsetof(StdRdOptions, vacuum_index_cleanup)},
- {"vacuum_truncate", RELOPT_TYPE_BOOL,
- offsetof(StdRdOptions, vacuum_truncate), offsetof(StdRdOptions, vacuum_truncate_set)},
+ {"vacuum_truncate", RELOPT_TYPE_TERNARY,
+ offsetof(StdRdOptions, vacuum_truncate)},
{"vacuum_max_eager_freeze_failure_rate", RELOPT_TYPE_REAL,
offsetof(StdRdOptions, vacuum_max_eager_freeze_failure_rate)}
};
@@ -1993,7 +2071,6 @@ build_local_reloptions(local_relopts *relopts, Datum options, bool validate)
elems[i].optname = opt->option->name;
elems[i].opttype = opt->option->type;
elems[i].offset = opt->offset;
- elems[i].isset_offset = 0; /* not supported for local relopts yet */
i++;
}
diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c
index 733ef40ae7..977babff54 100644
--- a/src/backend/commands/vacuum.c
+++ b/src/backend/commands/vacuum.c
@@ -2221,9 +2221,9 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams params,
{
StdRdOptions *opts = (StdRdOptions *) rel->rd_options;
- if (opts && opts->vacuum_truncate_set)
+ if (opts && opts->vacuum_truncate != TERNARY_UNSET)
{
- if (opts->vacuum_truncate)
+ if (opts->vacuum_truncate == TERNARY_TRUE)
params.truncate = VACOPTVALUE_ENABLED;
else
params.truncate = VACOPTVALUE_DISABLED;
diff --git a/src/include/access/reloptions.h b/src/include/access/reloptions.h
index a604a4702c..a436697658 100644
--- a/src/include/access/reloptions.h
+++ b/src/include/access/reloptions.h
@@ -29,6 +29,7 @@
typedef enum relopt_type
{
RELOPT_TYPE_BOOL,
+ RELOPT_TYPE_TERNARY, /* on, off, unset */
RELOPT_TYPE_INT,
RELOPT_TYPE_REAL,
RELOPT_TYPE_ENUM,
@@ -80,6 +81,7 @@ typedef struct relopt_value
union
{
bool bool_val;
+ ternary ternary_val;
int int_val;
double real_val;
int enum_val;
@@ -94,6 +96,12 @@ typedef struct relopt_bool
bool default_val;
} relopt_bool;
+typedef struct relopt_rernary
+{
+ relopt_gen gen;
+ int default_val;
+} relopt_ternary;
+
typedef struct relopt_int
{
relopt_gen gen;
@@ -152,19 +160,6 @@ typedef struct
const char *optname; /* option's name */
relopt_type opttype; /* option's datatype */
int offset; /* offset of field in result struct */
-
- /*
- * isset_offset is an optional offset of a field in the result struct that
- * stores whether the option is explicitly set for the relation or if it
- * just picked up the default value. In most cases, this can be
- * accomplished by giving the reloption a special out-of-range default
- * value (e.g., some integer reloptions use -2), but this isn't always
- * possible. For example, a Boolean reloption cannot be given an
- * out-of-range default, so we need another way to discover the source of
- * its value. This offset is only used if given a value greater than
- * zero.
- */
- int isset_offset;
} relopt_parse_elt;
/* Local reloption definition */
@@ -195,6 +190,8 @@ typedef struct local_relopts
extern relopt_kind add_reloption_kind(void);
extern void add_bool_reloption(bits32 kinds, const char *name, const char *desc,
bool default_val, LOCKMODE lockmode);
+extern void add_ternary_reloption(bits32 kinds, const char *name,
+ const char *desc, int default_val, LOCKMODE lockmode);
extern void add_int_reloption(bits32 kinds, const char *name, const char *desc,
int default_val, int min_val, int max_val,
LOCKMODE lockmode);
@@ -214,6 +211,9 @@ extern void register_reloptions_validator(local_relopts *relopts,
extern void add_local_bool_reloption(local_relopts *relopts, const char *name,
const char *desc, bool default_val,
int offset);
+extern void add_local_ternary_reloption(local_relopts *relopts,
+ const char *name, const char *desc,
+ ternary default_val, int offset);
extern void add_local_int_reloption(local_relopts *relopts, const char *name,
const char *desc, int default_val,
int min_val, int max_val, int offset);
diff --git a/src/include/c.h b/src/include/c.h
index 39022f8a9d..9c59ccccf8 100644
--- a/src/include/c.h
+++ b/src/include/c.h
@@ -488,6 +488,22 @@ typedef void (*pg_funcptr_t) (void);
#include <stdbool.h>
+/*
+ * ternary
+ * Boolean value with an extrea "unset" option
+ *
+ * Ternary data type is used in relation options that can be "true", "false" or
+ * "unset". Since relation options are used deep inside the PostgreSQL code,
+ * this type is declared globally.
+*/
+
+typedef enum ternary
+{
+ TERNARY_FALSE = 0,
+ TERNARY_TRUE = 1,
+ TERNARY_UNSET = -1
+} ternary;
+
/* ----------------------------------------------------------------
* Section 3: standard system types
diff --git a/src/include/utils/rel.h b/src/include/utils/rel.h
index b552359915..7346d618f9 100644
--- a/src/include/utils/rel.h
+++ b/src/include/utils/rel.h
@@ -346,8 +346,7 @@ typedef struct StdRdOptions
bool user_catalog_table; /* use as an additional catalog relation */
int parallel_workers; /* max number of parallel workers */
StdRdOptIndexCleanup vacuum_index_cleanup; /* controls index vacuuming */
- bool vacuum_truncate; /* enables vacuum to truncate a relation */
- bool vacuum_truncate_set; /* whether vacuum_truncate is set */
+ ternary vacuum_truncate; /* enables vacuum to truncate a relation */
/*
* Fraction of pages in a relation that vacuum can eagerly scan and fail
--
2.39.2
--nextPart5129932.5fSG56mABF
Content-Disposition: attachment;
filename="v2-0003-Add-alias-to-be-used-as-unset-state.patch"
Content-Transfer-Encoding: 7Bit
Content-Type: text/x-patch; charset="unicode-2-0-utf-8";
name="v2-0003-Add-alias-to-be-used-as-unset-state.patch"
^ permalink raw reply [nested|flat] 868+ messages in thread
* [PATCH v2 2/4] Introduce ternary reloptions
@ 2025-09-04 14:41 Nikolay Shaplov <[email protected]>
0 siblings, 0 replies; 868+ messages in thread
From: Nikolay Shaplov @ 2025-09-04 14:41 UTC (permalink / raw)
Introduce ternary reloption as a replacement for current `vacuum_truncate`
implementation. Remove `vacuum_truncate_set` additional flag and using
`TERNARY_UNSET` value instead.
---
src/backend/access/common/reloptions.c | 129 ++++++++++++++++++++-----
src/backend/commands/vacuum.c | 4 +-
src/include/access/reloptions.h | 26 ++---
src/include/c.h | 16 +++
src/include/utils/rel.h | 3 +-
5 files changed, 135 insertions(+), 43 deletions(-)
diff --git a/src/backend/access/common/reloptions.c b/src/backend/access/common/reloptions.c
index 0af3fea68f..f543f11001 100644
--- a/src/backend/access/common/reloptions.c
+++ b/src/backend/access/common/reloptions.c
@@ -40,9 +40,9 @@
*
* To add an option:
*
- * (i) decide on a type (bool, integer, real, enum, string), name, default
- * value, upper and lower bounds (if applicable); for strings, consider a
- * validation routine.
+ * (i) decide on a type (bool, ternary, integer, real, enum, string), name,
+ * default value, upper and lower bounds (if applicable); for strings,
+ * consider a validation routine.
* (ii) add a record below (or use add_<type>_reloption).
* (iii) add it to the appropriate options struct (perhaps StdRdOptions)
* (iv) add it to the appropriate handling routine (perhaps
@@ -147,15 +147,6 @@ static relopt_bool boolRelOpts[] =
},
false
},
- {
- {
- "vacuum_truncate",
- "Enables vacuum to truncate empty pages at the end of this table",
- RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
- ShareUpdateExclusiveLock
- },
- true
- },
{
{
"deduplicate_items",
@@ -170,6 +161,21 @@ static relopt_bool boolRelOpts[] =
{{NULL}}
};
+static relopt_ternary ternaryRelOpts[] =
+{
+ {
+ {
+ "vacuum_truncate",
+ "Enables vacuum to truncate empty pages at the end of this table",
+ RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
+ ShareUpdateExclusiveLock
+ },
+ TERNARY_UNSET
+ },
+ /* list terminator */
+ {{NULL}}
+};
+
static relopt_int intRelOpts[] =
{
{
@@ -600,6 +606,13 @@ initialize_reloptions(void)
boolRelOpts[i].gen.lockmode));
j++;
}
+ for (i = 0; ternaryRelOpts[i].gen.name; i++)
+ {
+ Assert(DoLockModesConflict(ternaryRelOpts[i].gen.lockmode,
+ ternaryRelOpts[i].gen.lockmode));
+ j++;
+ }
+
for (i = 0; intRelOpts[i].gen.name; i++)
{
Assert(DoLockModesConflict(intRelOpts[i].gen.lockmode,
@@ -640,6 +653,14 @@ initialize_reloptions(void)
j++;
}
+ for (i = 0; ternaryRelOpts[i].gen.name; i++)
+ {
+ relOpts[j] = &ternaryRelOpts[i].gen;
+ relOpts[j]->type = RELOPT_TYPE_TERNARY;
+ relOpts[j]->namelen = strlen(relOpts[j]->name);
+ j++;
+ }
+
for (i = 0; intRelOpts[i].gen.name; i++)
{
relOpts[j] = &intRelOpts[i].gen;
@@ -800,6 +821,9 @@ allocate_reloption(bits32 kinds, int type, const char *name, const char *desc,
case RELOPT_TYPE_BOOL:
size = sizeof(relopt_bool);
break;
+ case RELOPT_TYPE_TERNARY:
+ size = sizeof(relopt_ternary);
+ break;
case RELOPT_TYPE_INT:
size = sizeof(relopt_int);
break;
@@ -883,6 +907,54 @@ add_local_bool_reloption(local_relopts *relopts, const char *name,
add_local_reloption(relopts, (relopt_gen *) newoption, offset);
}
+/*
+ * init_ternary_reloption
+ * Allocate and initialize a new ternary reloption
+ */
+static relopt_ternary *
+init_ternary_reloption(bits32 kinds, const char *name, const char *desc,
+ ternary default_val, LOCKMODE lockmode)
+{
+ relopt_ternary *newoption;
+
+ newoption = (relopt_ternary *) allocate_reloption(kinds,
+ RELOPT_TYPE_TERNARY, name, desc, lockmode);
+ newoption->default_val = default_val;
+
+ return newoption;
+}
+
+/*
+ * add_ternary_reloption
+ * Add a new ternary reloption
+ */
+void
+add_ternary_reloption(bits32 kinds, const char *name, const char *desc,
+ ternary default_val, LOCKMODE lockmode)
+{
+ relopt_ternary *newoption = init_ternary_reloption(kinds, name, desc,
+ default_val, lockmode);
+
+ add_reloption((relopt_gen *) newoption);
+}
+
+/*
+ * add_local_ternary_reloption
+ * Add a new ternary local reloption
+ *
+ * 'offset' is offset of ternary-typed field.
+ */
+void
+add_local_ternary_reloption(local_relopts *relopts, const char *name,
+ const char *desc, ternary default_val,
+ int offset)
+{
+ relopt_ternary *newoption = init_ternary_reloption(RELOPT_KIND_LOCAL,
+ name, desc,
+ default_val, 0);
+
+ add_local_reloption(relopts, (relopt_gen *) newoption, offset);
+}
/*
* init_real_reloption
@@ -1617,6 +1689,18 @@ parse_one_reloption(relopt_value *option, char *text_str, int text_len,
option->gen->name, value)));
}
break;
+ case RELOPT_TYPE_TERNARY:
+ {
+ bool b;
+ parsed = parse_bool(value, &b);
+ option->values.ternary_val = b ? TERNARY_TRUE : TERNARY_FALSE;
+ if (validate && !parsed)
+ ereport(ERROR,
+ (errcode(ERRCODE_INVALID_PARAMETER_VALUE),
+ errmsg("invalid value for ternary option \"%s\": %s",
+ option->gen->name, value)));
+ }
+ break;
case RELOPT_TYPE_INT:
{
relopt_int *optint = (relopt_int *) option->gen;
@@ -1780,17 +1864,6 @@ fillRelOptions(void *rdopts, Size basesize,
char *itempos = ((char *) rdopts) + elems[j].offset;
char *string_val;
- /*
- * If isset_offset is provided, store whether the reloption is
- * set there.
- */
- if (elems[j].isset_offset > 0)
- {
- char *setpos = ((char *) rdopts) + elems[j].isset_offset;
-
- *(bool *) setpos = options[i].isset;
- }
-
switch (options[i].gen->type)
{
case RELOPT_TYPE_BOOL:
@@ -1798,6 +1871,11 @@ fillRelOptions(void *rdopts, Size basesize,
options[i].values.bool_val :
((relopt_bool *) options[i].gen)->default_val;
break;
+ case RELOPT_TYPE_TERNARY:
+ *(ternary *) itempos = options[i].isset ?
+ options[i].values.ternary_val :
+ ((relopt_ternary *) options[i].gen)->default_val;
+ break;
case RELOPT_TYPE_INT:
*(int *) itempos = options[i].isset ?
options[i].values.int_val :
@@ -1912,8 +1990,8 @@ default_reloptions(Datum reloptions, bool validate, relopt_kind kind)
offsetof(StdRdOptions, parallel_workers)},
{"vacuum_index_cleanup", RELOPT_TYPE_ENUM,
offsetof(StdRdOptions, vacuum_index_cleanup)},
- {"vacuum_truncate", RELOPT_TYPE_BOOL,
- offsetof(StdRdOptions, vacuum_truncate), offsetof(StdRdOptions, vacuum_truncate_set)},
+ {"vacuum_truncate", RELOPT_TYPE_TERNARY,
+ offsetof(StdRdOptions, vacuum_truncate)},
{"vacuum_max_eager_freeze_failure_rate", RELOPT_TYPE_REAL,
offsetof(StdRdOptions, vacuum_max_eager_freeze_failure_rate)}
};
@@ -1993,7 +2071,6 @@ build_local_reloptions(local_relopts *relopts, Datum options, bool validate)
elems[i].optname = opt->option->name;
elems[i].opttype = opt->option->type;
elems[i].offset = opt->offset;
- elems[i].isset_offset = 0; /* not supported for local relopts yet */
i++;
}
diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c
index 733ef40ae7..977babff54 100644
--- a/src/backend/commands/vacuum.c
+++ b/src/backend/commands/vacuum.c
@@ -2221,9 +2221,9 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams params,
{
StdRdOptions *opts = (StdRdOptions *) rel->rd_options;
- if (opts && opts->vacuum_truncate_set)
+ if (opts && opts->vacuum_truncate != TERNARY_UNSET)
{
- if (opts->vacuum_truncate)
+ if (opts->vacuum_truncate == TERNARY_TRUE)
params.truncate = VACOPTVALUE_ENABLED;
else
params.truncate = VACOPTVALUE_DISABLED;
diff --git a/src/include/access/reloptions.h b/src/include/access/reloptions.h
index a604a4702c..a436697658 100644
--- a/src/include/access/reloptions.h
+++ b/src/include/access/reloptions.h
@@ -29,6 +29,7 @@
typedef enum relopt_type
{
RELOPT_TYPE_BOOL,
+ RELOPT_TYPE_TERNARY, /* on, off, unset */
RELOPT_TYPE_INT,
RELOPT_TYPE_REAL,
RELOPT_TYPE_ENUM,
@@ -80,6 +81,7 @@ typedef struct relopt_value
union
{
bool bool_val;
+ ternary ternary_val;
int int_val;
double real_val;
int enum_val;
@@ -94,6 +96,12 @@ typedef struct relopt_bool
bool default_val;
} relopt_bool;
+typedef struct relopt_rernary
+{
+ relopt_gen gen;
+ int default_val;
+} relopt_ternary;
+
typedef struct relopt_int
{
relopt_gen gen;
@@ -152,19 +160,6 @@ typedef struct
const char *optname; /* option's name */
relopt_type opttype; /* option's datatype */
int offset; /* offset of field in result struct */
-
- /*
- * isset_offset is an optional offset of a field in the result struct that
- * stores whether the option is explicitly set for the relation or if it
- * just picked up the default value. In most cases, this can be
- * accomplished by giving the reloption a special out-of-range default
- * value (e.g., some integer reloptions use -2), but this isn't always
- * possible. For example, a Boolean reloption cannot be given an
- * out-of-range default, so we need another way to discover the source of
- * its value. This offset is only used if given a value greater than
- * zero.
- */
- int isset_offset;
} relopt_parse_elt;
/* Local reloption definition */
@@ -195,6 +190,8 @@ typedef struct local_relopts
extern relopt_kind add_reloption_kind(void);
extern void add_bool_reloption(bits32 kinds, const char *name, const char *desc,
bool default_val, LOCKMODE lockmode);
+extern void add_ternary_reloption(bits32 kinds, const char *name,
+ const char *desc, int default_val, LOCKMODE lockmode);
extern void add_int_reloption(bits32 kinds, const char *name, const char *desc,
int default_val, int min_val, int max_val,
LOCKMODE lockmode);
@@ -214,6 +211,9 @@ extern void register_reloptions_validator(local_relopts *relopts,
extern void add_local_bool_reloption(local_relopts *relopts, const char *name,
const char *desc, bool default_val,
int offset);
+extern void add_local_ternary_reloption(local_relopts *relopts,
+ const char *name, const char *desc,
+ ternary default_val, int offset);
extern void add_local_int_reloption(local_relopts *relopts, const char *name,
const char *desc, int default_val,
int min_val, int max_val, int offset);
diff --git a/src/include/c.h b/src/include/c.h
index 39022f8a9d..9c59ccccf8 100644
--- a/src/include/c.h
+++ b/src/include/c.h
@@ -488,6 +488,22 @@ typedef void (*pg_funcptr_t) (void);
#include <stdbool.h>
+/*
+ * ternary
+ * Boolean value with an extrea "unset" option
+ *
+ * Ternary data type is used in relation options that can be "true", "false" or
+ * "unset". Since relation options are used deep inside the PostgreSQL code,
+ * this type is declared globally.
+*/
+
+typedef enum ternary
+{
+ TERNARY_FALSE = 0,
+ TERNARY_TRUE = 1,
+ TERNARY_UNSET = -1
+} ternary;
+
/* ----------------------------------------------------------------
* Section 3: standard system types
diff --git a/src/include/utils/rel.h b/src/include/utils/rel.h
index b552359915..7346d618f9 100644
--- a/src/include/utils/rel.h
+++ b/src/include/utils/rel.h
@@ -346,8 +346,7 @@ typedef struct StdRdOptions
bool user_catalog_table; /* use as an additional catalog relation */
int parallel_workers; /* max number of parallel workers */
StdRdOptIndexCleanup vacuum_index_cleanup; /* controls index vacuuming */
- bool vacuum_truncate; /* enables vacuum to truncate a relation */
- bool vacuum_truncate_set; /* whether vacuum_truncate is set */
+ ternary vacuum_truncate; /* enables vacuum to truncate a relation */
/*
* Fraction of pages in a relation that vacuum can eagerly scan and fail
--
2.39.2
--nextPart5129932.5fSG56mABF
Content-Disposition: attachment;
filename="v2-0003-Add-alias-to-be-used-as-unset-state.patch"
Content-Transfer-Encoding: 7Bit
Content-Type: text/x-patch; charset="unicode-2-0-utf-8";
name="v2-0003-Add-alias-to-be-used-as-unset-state.patch"
^ permalink raw reply [nested|flat] 868+ messages in thread
* [PATCH v2 2/4] Introduce ternary reloptions
@ 2025-09-04 14:41 Nikolay Shaplov <[email protected]>
0 siblings, 0 replies; 868+ messages in thread
From: Nikolay Shaplov @ 2025-09-04 14:41 UTC (permalink / raw)
Introduce ternary reloption as a replacement for current `vacuum_truncate`
implementation. Remove `vacuum_truncate_set` additional flag and using
`TERNARY_UNSET` value instead.
---
src/backend/access/common/reloptions.c | 129 ++++++++++++++++++++-----
src/backend/commands/vacuum.c | 4 +-
src/include/access/reloptions.h | 26 ++---
src/include/c.h | 16 +++
src/include/utils/rel.h | 3 +-
5 files changed, 135 insertions(+), 43 deletions(-)
diff --git a/src/backend/access/common/reloptions.c b/src/backend/access/common/reloptions.c
index 0af3fea68f..f543f11001 100644
--- a/src/backend/access/common/reloptions.c
+++ b/src/backend/access/common/reloptions.c
@@ -40,9 +40,9 @@
*
* To add an option:
*
- * (i) decide on a type (bool, integer, real, enum, string), name, default
- * value, upper and lower bounds (if applicable); for strings, consider a
- * validation routine.
+ * (i) decide on a type (bool, ternary, integer, real, enum, string), name,
+ * default value, upper and lower bounds (if applicable); for strings,
+ * consider a validation routine.
* (ii) add a record below (or use add_<type>_reloption).
* (iii) add it to the appropriate options struct (perhaps StdRdOptions)
* (iv) add it to the appropriate handling routine (perhaps
@@ -147,15 +147,6 @@ static relopt_bool boolRelOpts[] =
},
false
},
- {
- {
- "vacuum_truncate",
- "Enables vacuum to truncate empty pages at the end of this table",
- RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
- ShareUpdateExclusiveLock
- },
- true
- },
{
{
"deduplicate_items",
@@ -170,6 +161,21 @@ static relopt_bool boolRelOpts[] =
{{NULL}}
};
+static relopt_ternary ternaryRelOpts[] =
+{
+ {
+ {
+ "vacuum_truncate",
+ "Enables vacuum to truncate empty pages at the end of this table",
+ RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
+ ShareUpdateExclusiveLock
+ },
+ TERNARY_UNSET
+ },
+ /* list terminator */
+ {{NULL}}
+};
+
static relopt_int intRelOpts[] =
{
{
@@ -600,6 +606,13 @@ initialize_reloptions(void)
boolRelOpts[i].gen.lockmode));
j++;
}
+ for (i = 0; ternaryRelOpts[i].gen.name; i++)
+ {
+ Assert(DoLockModesConflict(ternaryRelOpts[i].gen.lockmode,
+ ternaryRelOpts[i].gen.lockmode));
+ j++;
+ }
+
for (i = 0; intRelOpts[i].gen.name; i++)
{
Assert(DoLockModesConflict(intRelOpts[i].gen.lockmode,
@@ -640,6 +653,14 @@ initialize_reloptions(void)
j++;
}
+ for (i = 0; ternaryRelOpts[i].gen.name; i++)
+ {
+ relOpts[j] = &ternaryRelOpts[i].gen;
+ relOpts[j]->type = RELOPT_TYPE_TERNARY;
+ relOpts[j]->namelen = strlen(relOpts[j]->name);
+ j++;
+ }
+
for (i = 0; intRelOpts[i].gen.name; i++)
{
relOpts[j] = &intRelOpts[i].gen;
@@ -800,6 +821,9 @@ allocate_reloption(bits32 kinds, int type, const char *name, const char *desc,
case RELOPT_TYPE_BOOL:
size = sizeof(relopt_bool);
break;
+ case RELOPT_TYPE_TERNARY:
+ size = sizeof(relopt_ternary);
+ break;
case RELOPT_TYPE_INT:
size = sizeof(relopt_int);
break;
@@ -883,6 +907,54 @@ add_local_bool_reloption(local_relopts *relopts, const char *name,
add_local_reloption(relopts, (relopt_gen *) newoption, offset);
}
+/*
+ * init_ternary_reloption
+ * Allocate and initialize a new ternary reloption
+ */
+static relopt_ternary *
+init_ternary_reloption(bits32 kinds, const char *name, const char *desc,
+ ternary default_val, LOCKMODE lockmode)
+{
+ relopt_ternary *newoption;
+
+ newoption = (relopt_ternary *) allocate_reloption(kinds,
+ RELOPT_TYPE_TERNARY, name, desc, lockmode);
+ newoption->default_val = default_val;
+
+ return newoption;
+}
+
+/*
+ * add_ternary_reloption
+ * Add a new ternary reloption
+ */
+void
+add_ternary_reloption(bits32 kinds, const char *name, const char *desc,
+ ternary default_val, LOCKMODE lockmode)
+{
+ relopt_ternary *newoption = init_ternary_reloption(kinds, name, desc,
+ default_val, lockmode);
+
+ add_reloption((relopt_gen *) newoption);
+}
+
+/*
+ * add_local_ternary_reloption
+ * Add a new ternary local reloption
+ *
+ * 'offset' is offset of ternary-typed field.
+ */
+void
+add_local_ternary_reloption(local_relopts *relopts, const char *name,
+ const char *desc, ternary default_val,
+ int offset)
+{
+ relopt_ternary *newoption = init_ternary_reloption(RELOPT_KIND_LOCAL,
+ name, desc,
+ default_val, 0);
+
+ add_local_reloption(relopts, (relopt_gen *) newoption, offset);
+}
/*
* init_real_reloption
@@ -1617,6 +1689,18 @@ parse_one_reloption(relopt_value *option, char *text_str, int text_len,
option->gen->name, value)));
}
break;
+ case RELOPT_TYPE_TERNARY:
+ {
+ bool b;
+ parsed = parse_bool(value, &b);
+ option->values.ternary_val = b ? TERNARY_TRUE : TERNARY_FALSE;
+ if (validate && !parsed)
+ ereport(ERROR,
+ (errcode(ERRCODE_INVALID_PARAMETER_VALUE),
+ errmsg("invalid value for ternary option \"%s\": %s",
+ option->gen->name, value)));
+ }
+ break;
case RELOPT_TYPE_INT:
{
relopt_int *optint = (relopt_int *) option->gen;
@@ -1780,17 +1864,6 @@ fillRelOptions(void *rdopts, Size basesize,
char *itempos = ((char *) rdopts) + elems[j].offset;
char *string_val;
- /*
- * If isset_offset is provided, store whether the reloption is
- * set there.
- */
- if (elems[j].isset_offset > 0)
- {
- char *setpos = ((char *) rdopts) + elems[j].isset_offset;
-
- *(bool *) setpos = options[i].isset;
- }
-
switch (options[i].gen->type)
{
case RELOPT_TYPE_BOOL:
@@ -1798,6 +1871,11 @@ fillRelOptions(void *rdopts, Size basesize,
options[i].values.bool_val :
((relopt_bool *) options[i].gen)->default_val;
break;
+ case RELOPT_TYPE_TERNARY:
+ *(ternary *) itempos = options[i].isset ?
+ options[i].values.ternary_val :
+ ((relopt_ternary *) options[i].gen)->default_val;
+ break;
case RELOPT_TYPE_INT:
*(int *) itempos = options[i].isset ?
options[i].values.int_val :
@@ -1912,8 +1990,8 @@ default_reloptions(Datum reloptions, bool validate, relopt_kind kind)
offsetof(StdRdOptions, parallel_workers)},
{"vacuum_index_cleanup", RELOPT_TYPE_ENUM,
offsetof(StdRdOptions, vacuum_index_cleanup)},
- {"vacuum_truncate", RELOPT_TYPE_BOOL,
- offsetof(StdRdOptions, vacuum_truncate), offsetof(StdRdOptions, vacuum_truncate_set)},
+ {"vacuum_truncate", RELOPT_TYPE_TERNARY,
+ offsetof(StdRdOptions, vacuum_truncate)},
{"vacuum_max_eager_freeze_failure_rate", RELOPT_TYPE_REAL,
offsetof(StdRdOptions, vacuum_max_eager_freeze_failure_rate)}
};
@@ -1993,7 +2071,6 @@ build_local_reloptions(local_relopts *relopts, Datum options, bool validate)
elems[i].optname = opt->option->name;
elems[i].opttype = opt->option->type;
elems[i].offset = opt->offset;
- elems[i].isset_offset = 0; /* not supported for local relopts yet */
i++;
}
diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c
index 733ef40ae7..977babff54 100644
--- a/src/backend/commands/vacuum.c
+++ b/src/backend/commands/vacuum.c
@@ -2221,9 +2221,9 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams params,
{
StdRdOptions *opts = (StdRdOptions *) rel->rd_options;
- if (opts && opts->vacuum_truncate_set)
+ if (opts && opts->vacuum_truncate != TERNARY_UNSET)
{
- if (opts->vacuum_truncate)
+ if (opts->vacuum_truncate == TERNARY_TRUE)
params.truncate = VACOPTVALUE_ENABLED;
else
params.truncate = VACOPTVALUE_DISABLED;
diff --git a/src/include/access/reloptions.h b/src/include/access/reloptions.h
index a604a4702c..a436697658 100644
--- a/src/include/access/reloptions.h
+++ b/src/include/access/reloptions.h
@@ -29,6 +29,7 @@
typedef enum relopt_type
{
RELOPT_TYPE_BOOL,
+ RELOPT_TYPE_TERNARY, /* on, off, unset */
RELOPT_TYPE_INT,
RELOPT_TYPE_REAL,
RELOPT_TYPE_ENUM,
@@ -80,6 +81,7 @@ typedef struct relopt_value
union
{
bool bool_val;
+ ternary ternary_val;
int int_val;
double real_val;
int enum_val;
@@ -94,6 +96,12 @@ typedef struct relopt_bool
bool default_val;
} relopt_bool;
+typedef struct relopt_rernary
+{
+ relopt_gen gen;
+ int default_val;
+} relopt_ternary;
+
typedef struct relopt_int
{
relopt_gen gen;
@@ -152,19 +160,6 @@ typedef struct
const char *optname; /* option's name */
relopt_type opttype; /* option's datatype */
int offset; /* offset of field in result struct */
-
- /*
- * isset_offset is an optional offset of a field in the result struct that
- * stores whether the option is explicitly set for the relation or if it
- * just picked up the default value. In most cases, this can be
- * accomplished by giving the reloption a special out-of-range default
- * value (e.g., some integer reloptions use -2), but this isn't always
- * possible. For example, a Boolean reloption cannot be given an
- * out-of-range default, so we need another way to discover the source of
- * its value. This offset is only used if given a value greater than
- * zero.
- */
- int isset_offset;
} relopt_parse_elt;
/* Local reloption definition */
@@ -195,6 +190,8 @@ typedef struct local_relopts
extern relopt_kind add_reloption_kind(void);
extern void add_bool_reloption(bits32 kinds, const char *name, const char *desc,
bool default_val, LOCKMODE lockmode);
+extern void add_ternary_reloption(bits32 kinds, const char *name,
+ const char *desc, int default_val, LOCKMODE lockmode);
extern void add_int_reloption(bits32 kinds, const char *name, const char *desc,
int default_val, int min_val, int max_val,
LOCKMODE lockmode);
@@ -214,6 +211,9 @@ extern void register_reloptions_validator(local_relopts *relopts,
extern void add_local_bool_reloption(local_relopts *relopts, const char *name,
const char *desc, bool default_val,
int offset);
+extern void add_local_ternary_reloption(local_relopts *relopts,
+ const char *name, const char *desc,
+ ternary default_val, int offset);
extern void add_local_int_reloption(local_relopts *relopts, const char *name,
const char *desc, int default_val,
int min_val, int max_val, int offset);
diff --git a/src/include/c.h b/src/include/c.h
index 39022f8a9d..9c59ccccf8 100644
--- a/src/include/c.h
+++ b/src/include/c.h
@@ -488,6 +488,22 @@ typedef void (*pg_funcptr_t) (void);
#include <stdbool.h>
+/*
+ * ternary
+ * Boolean value with an extrea "unset" option
+ *
+ * Ternary data type is used in relation options that can be "true", "false" or
+ * "unset". Since relation options are used deep inside the PostgreSQL code,
+ * this type is declared globally.
+*/
+
+typedef enum ternary
+{
+ TERNARY_FALSE = 0,
+ TERNARY_TRUE = 1,
+ TERNARY_UNSET = -1
+} ternary;
+
/* ----------------------------------------------------------------
* Section 3: standard system types
diff --git a/src/include/utils/rel.h b/src/include/utils/rel.h
index b552359915..7346d618f9 100644
--- a/src/include/utils/rel.h
+++ b/src/include/utils/rel.h
@@ -346,8 +346,7 @@ typedef struct StdRdOptions
bool user_catalog_table; /* use as an additional catalog relation */
int parallel_workers; /* max number of parallel workers */
StdRdOptIndexCleanup vacuum_index_cleanup; /* controls index vacuuming */
- bool vacuum_truncate; /* enables vacuum to truncate a relation */
- bool vacuum_truncate_set; /* whether vacuum_truncate is set */
+ ternary vacuum_truncate; /* enables vacuum to truncate a relation */
/*
* Fraction of pages in a relation that vacuum can eagerly scan and fail
--
2.39.2
--nextPart5129932.5fSG56mABF
Content-Disposition: attachment;
filename="v2-0003-Add-alias-to-be-used-as-unset-state.patch"
Content-Transfer-Encoding: 7Bit
Content-Type: text/x-patch; charset="unicode-2-0-utf-8";
name="v2-0003-Add-alias-to-be-used-as-unset-state.patch"
^ permalink raw reply [nested|flat] 868+ messages in thread
* [PATCH v2 2/4] Introduce ternary reloptions
@ 2025-09-04 14:41 Nikolay Shaplov <[email protected]>
0 siblings, 0 replies; 868+ messages in thread
From: Nikolay Shaplov @ 2025-09-04 14:41 UTC (permalink / raw)
Introduce ternary reloption as a replacement for current `vacuum_truncate`
implementation. Remove `vacuum_truncate_set` additional flag and using
`TERNARY_UNSET` value instead.
---
src/backend/access/common/reloptions.c | 129 ++++++++++++++++++++-----
src/backend/commands/vacuum.c | 4 +-
src/include/access/reloptions.h | 26 ++---
src/include/c.h | 16 +++
src/include/utils/rel.h | 3 +-
5 files changed, 135 insertions(+), 43 deletions(-)
diff --git a/src/backend/access/common/reloptions.c b/src/backend/access/common/reloptions.c
index 0af3fea68f..f543f11001 100644
--- a/src/backend/access/common/reloptions.c
+++ b/src/backend/access/common/reloptions.c
@@ -40,9 +40,9 @@
*
* To add an option:
*
- * (i) decide on a type (bool, integer, real, enum, string), name, default
- * value, upper and lower bounds (if applicable); for strings, consider a
- * validation routine.
+ * (i) decide on a type (bool, ternary, integer, real, enum, string), name,
+ * default value, upper and lower bounds (if applicable); for strings,
+ * consider a validation routine.
* (ii) add a record below (or use add_<type>_reloption).
* (iii) add it to the appropriate options struct (perhaps StdRdOptions)
* (iv) add it to the appropriate handling routine (perhaps
@@ -147,15 +147,6 @@ static relopt_bool boolRelOpts[] =
},
false
},
- {
- {
- "vacuum_truncate",
- "Enables vacuum to truncate empty pages at the end of this table",
- RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
- ShareUpdateExclusiveLock
- },
- true
- },
{
{
"deduplicate_items",
@@ -170,6 +161,21 @@ static relopt_bool boolRelOpts[] =
{{NULL}}
};
+static relopt_ternary ternaryRelOpts[] =
+{
+ {
+ {
+ "vacuum_truncate",
+ "Enables vacuum to truncate empty pages at the end of this table",
+ RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
+ ShareUpdateExclusiveLock
+ },
+ TERNARY_UNSET
+ },
+ /* list terminator */
+ {{NULL}}
+};
+
static relopt_int intRelOpts[] =
{
{
@@ -600,6 +606,13 @@ initialize_reloptions(void)
boolRelOpts[i].gen.lockmode));
j++;
}
+ for (i = 0; ternaryRelOpts[i].gen.name; i++)
+ {
+ Assert(DoLockModesConflict(ternaryRelOpts[i].gen.lockmode,
+ ternaryRelOpts[i].gen.lockmode));
+ j++;
+ }
+
for (i = 0; intRelOpts[i].gen.name; i++)
{
Assert(DoLockModesConflict(intRelOpts[i].gen.lockmode,
@@ -640,6 +653,14 @@ initialize_reloptions(void)
j++;
}
+ for (i = 0; ternaryRelOpts[i].gen.name; i++)
+ {
+ relOpts[j] = &ternaryRelOpts[i].gen;
+ relOpts[j]->type = RELOPT_TYPE_TERNARY;
+ relOpts[j]->namelen = strlen(relOpts[j]->name);
+ j++;
+ }
+
for (i = 0; intRelOpts[i].gen.name; i++)
{
relOpts[j] = &intRelOpts[i].gen;
@@ -800,6 +821,9 @@ allocate_reloption(bits32 kinds, int type, const char *name, const char *desc,
case RELOPT_TYPE_BOOL:
size = sizeof(relopt_bool);
break;
+ case RELOPT_TYPE_TERNARY:
+ size = sizeof(relopt_ternary);
+ break;
case RELOPT_TYPE_INT:
size = sizeof(relopt_int);
break;
@@ -883,6 +907,54 @@ add_local_bool_reloption(local_relopts *relopts, const char *name,
add_local_reloption(relopts, (relopt_gen *) newoption, offset);
}
+/*
+ * init_ternary_reloption
+ * Allocate and initialize a new ternary reloption
+ */
+static relopt_ternary *
+init_ternary_reloption(bits32 kinds, const char *name, const char *desc,
+ ternary default_val, LOCKMODE lockmode)
+{
+ relopt_ternary *newoption;
+
+ newoption = (relopt_ternary *) allocate_reloption(kinds,
+ RELOPT_TYPE_TERNARY, name, desc, lockmode);
+ newoption->default_val = default_val;
+
+ return newoption;
+}
+
+/*
+ * add_ternary_reloption
+ * Add a new ternary reloption
+ */
+void
+add_ternary_reloption(bits32 kinds, const char *name, const char *desc,
+ ternary default_val, LOCKMODE lockmode)
+{
+ relopt_ternary *newoption = init_ternary_reloption(kinds, name, desc,
+ default_val, lockmode);
+
+ add_reloption((relopt_gen *) newoption);
+}
+
+/*
+ * add_local_ternary_reloption
+ * Add a new ternary local reloption
+ *
+ * 'offset' is offset of ternary-typed field.
+ */
+void
+add_local_ternary_reloption(local_relopts *relopts, const char *name,
+ const char *desc, ternary default_val,
+ int offset)
+{
+ relopt_ternary *newoption = init_ternary_reloption(RELOPT_KIND_LOCAL,
+ name, desc,
+ default_val, 0);
+
+ add_local_reloption(relopts, (relopt_gen *) newoption, offset);
+}
/*
* init_real_reloption
@@ -1617,6 +1689,18 @@ parse_one_reloption(relopt_value *option, char *text_str, int text_len,
option->gen->name, value)));
}
break;
+ case RELOPT_TYPE_TERNARY:
+ {
+ bool b;
+ parsed = parse_bool(value, &b);
+ option->values.ternary_val = b ? TERNARY_TRUE : TERNARY_FALSE;
+ if (validate && !parsed)
+ ereport(ERROR,
+ (errcode(ERRCODE_INVALID_PARAMETER_VALUE),
+ errmsg("invalid value for ternary option \"%s\": %s",
+ option->gen->name, value)));
+ }
+ break;
case RELOPT_TYPE_INT:
{
relopt_int *optint = (relopt_int *) option->gen;
@@ -1780,17 +1864,6 @@ fillRelOptions(void *rdopts, Size basesize,
char *itempos = ((char *) rdopts) + elems[j].offset;
char *string_val;
- /*
- * If isset_offset is provided, store whether the reloption is
- * set there.
- */
- if (elems[j].isset_offset > 0)
- {
- char *setpos = ((char *) rdopts) + elems[j].isset_offset;
-
- *(bool *) setpos = options[i].isset;
- }
-
switch (options[i].gen->type)
{
case RELOPT_TYPE_BOOL:
@@ -1798,6 +1871,11 @@ fillRelOptions(void *rdopts, Size basesize,
options[i].values.bool_val :
((relopt_bool *) options[i].gen)->default_val;
break;
+ case RELOPT_TYPE_TERNARY:
+ *(ternary *) itempos = options[i].isset ?
+ options[i].values.ternary_val :
+ ((relopt_ternary *) options[i].gen)->default_val;
+ break;
case RELOPT_TYPE_INT:
*(int *) itempos = options[i].isset ?
options[i].values.int_val :
@@ -1912,8 +1990,8 @@ default_reloptions(Datum reloptions, bool validate, relopt_kind kind)
offsetof(StdRdOptions, parallel_workers)},
{"vacuum_index_cleanup", RELOPT_TYPE_ENUM,
offsetof(StdRdOptions, vacuum_index_cleanup)},
- {"vacuum_truncate", RELOPT_TYPE_BOOL,
- offsetof(StdRdOptions, vacuum_truncate), offsetof(StdRdOptions, vacuum_truncate_set)},
+ {"vacuum_truncate", RELOPT_TYPE_TERNARY,
+ offsetof(StdRdOptions, vacuum_truncate)},
{"vacuum_max_eager_freeze_failure_rate", RELOPT_TYPE_REAL,
offsetof(StdRdOptions, vacuum_max_eager_freeze_failure_rate)}
};
@@ -1993,7 +2071,6 @@ build_local_reloptions(local_relopts *relopts, Datum options, bool validate)
elems[i].optname = opt->option->name;
elems[i].opttype = opt->option->type;
elems[i].offset = opt->offset;
- elems[i].isset_offset = 0; /* not supported for local relopts yet */
i++;
}
diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c
index 733ef40ae7..977babff54 100644
--- a/src/backend/commands/vacuum.c
+++ b/src/backend/commands/vacuum.c
@@ -2221,9 +2221,9 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams params,
{
StdRdOptions *opts = (StdRdOptions *) rel->rd_options;
- if (opts && opts->vacuum_truncate_set)
+ if (opts && opts->vacuum_truncate != TERNARY_UNSET)
{
- if (opts->vacuum_truncate)
+ if (opts->vacuum_truncate == TERNARY_TRUE)
params.truncate = VACOPTVALUE_ENABLED;
else
params.truncate = VACOPTVALUE_DISABLED;
diff --git a/src/include/access/reloptions.h b/src/include/access/reloptions.h
index a604a4702c..a436697658 100644
--- a/src/include/access/reloptions.h
+++ b/src/include/access/reloptions.h
@@ -29,6 +29,7 @@
typedef enum relopt_type
{
RELOPT_TYPE_BOOL,
+ RELOPT_TYPE_TERNARY, /* on, off, unset */
RELOPT_TYPE_INT,
RELOPT_TYPE_REAL,
RELOPT_TYPE_ENUM,
@@ -80,6 +81,7 @@ typedef struct relopt_value
union
{
bool bool_val;
+ ternary ternary_val;
int int_val;
double real_val;
int enum_val;
@@ -94,6 +96,12 @@ typedef struct relopt_bool
bool default_val;
} relopt_bool;
+typedef struct relopt_rernary
+{
+ relopt_gen gen;
+ int default_val;
+} relopt_ternary;
+
typedef struct relopt_int
{
relopt_gen gen;
@@ -152,19 +160,6 @@ typedef struct
const char *optname; /* option's name */
relopt_type opttype; /* option's datatype */
int offset; /* offset of field in result struct */
-
- /*
- * isset_offset is an optional offset of a field in the result struct that
- * stores whether the option is explicitly set for the relation or if it
- * just picked up the default value. In most cases, this can be
- * accomplished by giving the reloption a special out-of-range default
- * value (e.g., some integer reloptions use -2), but this isn't always
- * possible. For example, a Boolean reloption cannot be given an
- * out-of-range default, so we need another way to discover the source of
- * its value. This offset is only used if given a value greater than
- * zero.
- */
- int isset_offset;
} relopt_parse_elt;
/* Local reloption definition */
@@ -195,6 +190,8 @@ typedef struct local_relopts
extern relopt_kind add_reloption_kind(void);
extern void add_bool_reloption(bits32 kinds, const char *name, const char *desc,
bool default_val, LOCKMODE lockmode);
+extern void add_ternary_reloption(bits32 kinds, const char *name,
+ const char *desc, int default_val, LOCKMODE lockmode);
extern void add_int_reloption(bits32 kinds, const char *name, const char *desc,
int default_val, int min_val, int max_val,
LOCKMODE lockmode);
@@ -214,6 +211,9 @@ extern void register_reloptions_validator(local_relopts *relopts,
extern void add_local_bool_reloption(local_relopts *relopts, const char *name,
const char *desc, bool default_val,
int offset);
+extern void add_local_ternary_reloption(local_relopts *relopts,
+ const char *name, const char *desc,
+ ternary default_val, int offset);
extern void add_local_int_reloption(local_relopts *relopts, const char *name,
const char *desc, int default_val,
int min_val, int max_val, int offset);
diff --git a/src/include/c.h b/src/include/c.h
index 39022f8a9d..9c59ccccf8 100644
--- a/src/include/c.h
+++ b/src/include/c.h
@@ -488,6 +488,22 @@ typedef void (*pg_funcptr_t) (void);
#include <stdbool.h>
+/*
+ * ternary
+ * Boolean value with an extrea "unset" option
+ *
+ * Ternary data type is used in relation options that can be "true", "false" or
+ * "unset". Since relation options are used deep inside the PostgreSQL code,
+ * this type is declared globally.
+*/
+
+typedef enum ternary
+{
+ TERNARY_FALSE = 0,
+ TERNARY_TRUE = 1,
+ TERNARY_UNSET = -1
+} ternary;
+
/* ----------------------------------------------------------------
* Section 3: standard system types
diff --git a/src/include/utils/rel.h b/src/include/utils/rel.h
index b552359915..7346d618f9 100644
--- a/src/include/utils/rel.h
+++ b/src/include/utils/rel.h
@@ -346,8 +346,7 @@ typedef struct StdRdOptions
bool user_catalog_table; /* use as an additional catalog relation */
int parallel_workers; /* max number of parallel workers */
StdRdOptIndexCleanup vacuum_index_cleanup; /* controls index vacuuming */
- bool vacuum_truncate; /* enables vacuum to truncate a relation */
- bool vacuum_truncate_set; /* whether vacuum_truncate is set */
+ ternary vacuum_truncate; /* enables vacuum to truncate a relation */
/*
* Fraction of pages in a relation that vacuum can eagerly scan and fail
--
2.39.2
--nextPart5129932.5fSG56mABF
Content-Disposition: attachment;
filename="v2-0003-Add-alias-to-be-used-as-unset-state.patch"
Content-Transfer-Encoding: 7Bit
Content-Type: text/x-patch; charset="unicode-2-0-utf-8";
name="v2-0003-Add-alias-to-be-used-as-unset-state.patch"
^ permalink raw reply [nested|flat] 868+ messages in thread
* [PATCH v2a 2/4] Introduce ternary reloptions
@ 2025-09-04 14:41 Nikolay Shaplov <[email protected]>
0 siblings, 0 replies; 868+ messages in thread
From: Nikolay Shaplov @ 2025-09-04 14:41 UTC (permalink / raw)
Introduce ternary reloption as a replacement for current `vacuum_truncate`
implementation. Remove `vacuum_truncate_set` additional flag and using
`TERNARY_UNSET` value instead.
---
src/backend/access/common/reloptions.c | 129 ++++++++++++++++++++-----
src/backend/commands/vacuum.c | 4 +-
src/include/access/reloptions.h | 26 ++---
src/include/c.h | 16 +++
src/include/utils/rel.h | 3 +-
5 files changed, 135 insertions(+), 43 deletions(-)
diff --git a/src/backend/access/common/reloptions.c b/src/backend/access/common/reloptions.c
index 0af3fea68f..f543f11001 100644
--- a/src/backend/access/common/reloptions.c
+++ b/src/backend/access/common/reloptions.c
@@ -40,9 +40,9 @@
*
* To add an option:
*
- * (i) decide on a type (bool, integer, real, enum, string), name, default
- * value, upper and lower bounds (if applicable); for strings, consider a
- * validation routine.
+ * (i) decide on a type (bool, ternary, integer, real, enum, string), name,
+ * default value, upper and lower bounds (if applicable); for strings,
+ * consider a validation routine.
* (ii) add a record below (or use add_<type>_reloption).
* (iii) add it to the appropriate options struct (perhaps StdRdOptions)
* (iv) add it to the appropriate handling routine (perhaps
@@ -147,15 +147,6 @@ static relopt_bool boolRelOpts[] =
},
false
},
- {
- {
- "vacuum_truncate",
- "Enables vacuum to truncate empty pages at the end of this table",
- RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
- ShareUpdateExclusiveLock
- },
- true
- },
{
{
"deduplicate_items",
@@ -170,6 +161,21 @@ static relopt_bool boolRelOpts[] =
{{NULL}}
};
+static relopt_ternary ternaryRelOpts[] =
+{
+ {
+ {
+ "vacuum_truncate",
+ "Enables vacuum to truncate empty pages at the end of this table",
+ RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
+ ShareUpdateExclusiveLock
+ },
+ TERNARY_UNSET
+ },
+ /* list terminator */
+ {{NULL}}
+};
+
static relopt_int intRelOpts[] =
{
{
@@ -600,6 +606,13 @@ initialize_reloptions(void)
boolRelOpts[i].gen.lockmode));
j++;
}
+ for (i = 0; ternaryRelOpts[i].gen.name; i++)
+ {
+ Assert(DoLockModesConflict(ternaryRelOpts[i].gen.lockmode,
+ ternaryRelOpts[i].gen.lockmode));
+ j++;
+ }
+
for (i = 0; intRelOpts[i].gen.name; i++)
{
Assert(DoLockModesConflict(intRelOpts[i].gen.lockmode,
@@ -640,6 +653,14 @@ initialize_reloptions(void)
j++;
}
+ for (i = 0; ternaryRelOpts[i].gen.name; i++)
+ {
+ relOpts[j] = &ternaryRelOpts[i].gen;
+ relOpts[j]->type = RELOPT_TYPE_TERNARY;
+ relOpts[j]->namelen = strlen(relOpts[j]->name);
+ j++;
+ }
+
for (i = 0; intRelOpts[i].gen.name; i++)
{
relOpts[j] = &intRelOpts[i].gen;
@@ -800,6 +821,9 @@ allocate_reloption(bits32 kinds, int type, const char *name, const char *desc,
case RELOPT_TYPE_BOOL:
size = sizeof(relopt_bool);
break;
+ case RELOPT_TYPE_TERNARY:
+ size = sizeof(relopt_ternary);
+ break;
case RELOPT_TYPE_INT:
size = sizeof(relopt_int);
break;
@@ -883,6 +907,54 @@ add_local_bool_reloption(local_relopts *relopts, const char *name,
add_local_reloption(relopts, (relopt_gen *) newoption, offset);
}
+/*
+ * init_ternary_reloption
+ * Allocate and initialize a new ternary reloption
+ */
+static relopt_ternary *
+init_ternary_reloption(bits32 kinds, const char *name, const char *desc,
+ ternary default_val, LOCKMODE lockmode)
+{
+ relopt_ternary *newoption;
+
+ newoption = (relopt_ternary *) allocate_reloption(kinds,
+ RELOPT_TYPE_TERNARY, name, desc, lockmode);
+ newoption->default_val = default_val;
+
+ return newoption;
+}
+
+/*
+ * add_ternary_reloption
+ * Add a new ternary reloption
+ */
+void
+add_ternary_reloption(bits32 kinds, const char *name, const char *desc,
+ ternary default_val, LOCKMODE lockmode)
+{
+ relopt_ternary *newoption = init_ternary_reloption(kinds, name, desc,
+ default_val, lockmode);
+
+ add_reloption((relopt_gen *) newoption);
+}
+
+/*
+ * add_local_ternary_reloption
+ * Add a new ternary local reloption
+ *
+ * 'offset' is offset of ternary-typed field.
+ */
+void
+add_local_ternary_reloption(local_relopts *relopts, const char *name,
+ const char *desc, ternary default_val,
+ int offset)
+{
+ relopt_ternary *newoption = init_ternary_reloption(RELOPT_KIND_LOCAL,
+ name, desc,
+ default_val, 0);
+
+ add_local_reloption(relopts, (relopt_gen *) newoption, offset);
+}
/*
* init_real_reloption
@@ -1617,6 +1689,18 @@ parse_one_reloption(relopt_value *option, char *text_str, int text_len,
option->gen->name, value)));
}
break;
+ case RELOPT_TYPE_TERNARY:
+ {
+ bool b;
+ parsed = parse_bool(value, &b);
+ option->values.ternary_val = b ? TERNARY_TRUE : TERNARY_FALSE;
+ if (validate && !parsed)
+ ereport(ERROR,
+ (errcode(ERRCODE_INVALID_PARAMETER_VALUE),
+ errmsg("invalid value for ternary option \"%s\": %s",
+ option->gen->name, value)));
+ }
+ break;
case RELOPT_TYPE_INT:
{
relopt_int *optint = (relopt_int *) option->gen;
@@ -1780,17 +1864,6 @@ fillRelOptions(void *rdopts, Size basesize,
char *itempos = ((char *) rdopts) + elems[j].offset;
char *string_val;
- /*
- * If isset_offset is provided, store whether the reloption is
- * set there.
- */
- if (elems[j].isset_offset > 0)
- {
- char *setpos = ((char *) rdopts) + elems[j].isset_offset;
-
- *(bool *) setpos = options[i].isset;
- }
-
switch (options[i].gen->type)
{
case RELOPT_TYPE_BOOL:
@@ -1798,6 +1871,11 @@ fillRelOptions(void *rdopts, Size basesize,
options[i].values.bool_val :
((relopt_bool *) options[i].gen)->default_val;
break;
+ case RELOPT_TYPE_TERNARY:
+ *(ternary *) itempos = options[i].isset ?
+ options[i].values.ternary_val :
+ ((relopt_ternary *) options[i].gen)->default_val;
+ break;
case RELOPT_TYPE_INT:
*(int *) itempos = options[i].isset ?
options[i].values.int_val :
@@ -1912,8 +1990,8 @@ default_reloptions(Datum reloptions, bool validate, relopt_kind kind)
offsetof(StdRdOptions, parallel_workers)},
{"vacuum_index_cleanup", RELOPT_TYPE_ENUM,
offsetof(StdRdOptions, vacuum_index_cleanup)},
- {"vacuum_truncate", RELOPT_TYPE_BOOL,
- offsetof(StdRdOptions, vacuum_truncate), offsetof(StdRdOptions, vacuum_truncate_set)},
+ {"vacuum_truncate", RELOPT_TYPE_TERNARY,
+ offsetof(StdRdOptions, vacuum_truncate)},
{"vacuum_max_eager_freeze_failure_rate", RELOPT_TYPE_REAL,
offsetof(StdRdOptions, vacuum_max_eager_freeze_failure_rate)}
};
@@ -1993,7 +2071,6 @@ build_local_reloptions(local_relopts *relopts, Datum options, bool validate)
elems[i].optname = opt->option->name;
elems[i].opttype = opt->option->type;
elems[i].offset = opt->offset;
- elems[i].isset_offset = 0; /* not supported for local relopts yet */
i++;
}
diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c
index 733ef40ae7..977babff54 100644
--- a/src/backend/commands/vacuum.c
+++ b/src/backend/commands/vacuum.c
@@ -2221,9 +2221,9 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams params,
{
StdRdOptions *opts = (StdRdOptions *) rel->rd_options;
- if (opts && opts->vacuum_truncate_set)
+ if (opts && opts->vacuum_truncate != TERNARY_UNSET)
{
- if (opts->vacuum_truncate)
+ if (opts->vacuum_truncate == TERNARY_TRUE)
params.truncate = VACOPTVALUE_ENABLED;
else
params.truncate = VACOPTVALUE_DISABLED;
diff --git a/src/include/access/reloptions.h b/src/include/access/reloptions.h
index a604a4702c..a436697658 100644
--- a/src/include/access/reloptions.h
+++ b/src/include/access/reloptions.h
@@ -29,6 +29,7 @@
typedef enum relopt_type
{
RELOPT_TYPE_BOOL,
+ RELOPT_TYPE_TERNARY, /* on, off, unset */
RELOPT_TYPE_INT,
RELOPT_TYPE_REAL,
RELOPT_TYPE_ENUM,
@@ -80,6 +81,7 @@ typedef struct relopt_value
union
{
bool bool_val;
+ ternary ternary_val;
int int_val;
double real_val;
int enum_val;
@@ -94,6 +96,12 @@ typedef struct relopt_bool
bool default_val;
} relopt_bool;
+typedef struct relopt_rernary
+{
+ relopt_gen gen;
+ int default_val;
+} relopt_ternary;
+
typedef struct relopt_int
{
relopt_gen gen;
@@ -152,19 +160,6 @@ typedef struct
const char *optname; /* option's name */
relopt_type opttype; /* option's datatype */
int offset; /* offset of field in result struct */
-
- /*
- * isset_offset is an optional offset of a field in the result struct that
- * stores whether the option is explicitly set for the relation or if it
- * just picked up the default value. In most cases, this can be
- * accomplished by giving the reloption a special out-of-range default
- * value (e.g., some integer reloptions use -2), but this isn't always
- * possible. For example, a Boolean reloption cannot be given an
- * out-of-range default, so we need another way to discover the source of
- * its value. This offset is only used if given a value greater than
- * zero.
- */
- int isset_offset;
} relopt_parse_elt;
/* Local reloption definition */
@@ -195,6 +190,8 @@ typedef struct local_relopts
extern relopt_kind add_reloption_kind(void);
extern void add_bool_reloption(bits32 kinds, const char *name, const char *desc,
bool default_val, LOCKMODE lockmode);
+extern void add_ternary_reloption(bits32 kinds, const char *name,
+ const char *desc, int default_val, LOCKMODE lockmode);
extern void add_int_reloption(bits32 kinds, const char *name, const char *desc,
int default_val, int min_val, int max_val,
LOCKMODE lockmode);
@@ -214,6 +211,9 @@ extern void register_reloptions_validator(local_relopts *relopts,
extern void add_local_bool_reloption(local_relopts *relopts, const char *name,
const char *desc, bool default_val,
int offset);
+extern void add_local_ternary_reloption(local_relopts *relopts,
+ const char *name, const char *desc,
+ ternary default_val, int offset);
extern void add_local_int_reloption(local_relopts *relopts, const char *name,
const char *desc, int default_val,
int min_val, int max_val, int offset);
diff --git a/src/include/c.h b/src/include/c.h
index 39022f8a9d..9c59ccccf8 100644
--- a/src/include/c.h
+++ b/src/include/c.h
@@ -488,6 +488,22 @@ typedef void (*pg_funcptr_t) (void);
#include <stdbool.h>
+/*
+ * ternary
+ * Boolean value with an extrea "unset" option
+ *
+ * Ternary data type is used in relation options that can be "true", "false" or
+ * "unset". Since relation options are used deep inside the PostgreSQL code,
+ * this type is declared globally.
+*/
+
+typedef enum ternary
+{
+ TERNARY_FALSE = 0,
+ TERNARY_TRUE = 1,
+ TERNARY_UNSET = -1
+} ternary;
+
/* ----------------------------------------------------------------
* Section 3: standard system types
diff --git a/src/include/utils/rel.h b/src/include/utils/rel.h
index b552359915..7346d618f9 100644
--- a/src/include/utils/rel.h
+++ b/src/include/utils/rel.h
@@ -346,8 +346,7 @@ typedef struct StdRdOptions
bool user_catalog_table; /* use as an additional catalog relation */
int parallel_workers; /* max number of parallel workers */
StdRdOptIndexCleanup vacuum_index_cleanup; /* controls index vacuuming */
- bool vacuum_truncate; /* enables vacuum to truncate a relation */
- bool vacuum_truncate_set; /* whether vacuum_truncate is set */
+ ternary vacuum_truncate; /* enables vacuum to truncate a relation */
/*
* Fraction of pages in a relation that vacuum can eagerly scan and fail
--
2.39.2
--nextPart4155600.3ZeAukHxDK
Content-Disposition: attachment;
filename="v2a-0003-Add-alias-to-be-used-as-unset-state.patch"
Content-Transfer-Encoding: 7Bit
Content-Type: text/x-patch; charset="unicode-2-0-utf-8";
name="v2a-0003-Add-alias-to-be-used-as-unset-state.patch"
^ permalink raw reply [nested|flat] 868+ messages in thread
* [PATCH v2a 2/4] Introduce ternary reloptions
@ 2025-09-04 14:41 Nikolay Shaplov <[email protected]>
0 siblings, 0 replies; 868+ messages in thread
From: Nikolay Shaplov @ 2025-09-04 14:41 UTC (permalink / raw)
Introduce ternary reloption as a replacement for current `vacuum_truncate`
implementation. Remove `vacuum_truncate_set` additional flag and using
`TERNARY_UNSET` value instead.
---
src/backend/access/common/reloptions.c | 129 ++++++++++++++++++++-----
src/backend/commands/vacuum.c | 4 +-
src/include/access/reloptions.h | 26 ++---
src/include/c.h | 16 +++
src/include/utils/rel.h | 3 +-
5 files changed, 135 insertions(+), 43 deletions(-)
diff --git a/src/backend/access/common/reloptions.c b/src/backend/access/common/reloptions.c
index 0af3fea68f..f543f11001 100644
--- a/src/backend/access/common/reloptions.c
+++ b/src/backend/access/common/reloptions.c
@@ -40,9 +40,9 @@
*
* To add an option:
*
- * (i) decide on a type (bool, integer, real, enum, string), name, default
- * value, upper and lower bounds (if applicable); for strings, consider a
- * validation routine.
+ * (i) decide on a type (bool, ternary, integer, real, enum, string), name,
+ * default value, upper and lower bounds (if applicable); for strings,
+ * consider a validation routine.
* (ii) add a record below (or use add_<type>_reloption).
* (iii) add it to the appropriate options struct (perhaps StdRdOptions)
* (iv) add it to the appropriate handling routine (perhaps
@@ -147,15 +147,6 @@ static relopt_bool boolRelOpts[] =
},
false
},
- {
- {
- "vacuum_truncate",
- "Enables vacuum to truncate empty pages at the end of this table",
- RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
- ShareUpdateExclusiveLock
- },
- true
- },
{
{
"deduplicate_items",
@@ -170,6 +161,21 @@ static relopt_bool boolRelOpts[] =
{{NULL}}
};
+static relopt_ternary ternaryRelOpts[] =
+{
+ {
+ {
+ "vacuum_truncate",
+ "Enables vacuum to truncate empty pages at the end of this table",
+ RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
+ ShareUpdateExclusiveLock
+ },
+ TERNARY_UNSET
+ },
+ /* list terminator */
+ {{NULL}}
+};
+
static relopt_int intRelOpts[] =
{
{
@@ -600,6 +606,13 @@ initialize_reloptions(void)
boolRelOpts[i].gen.lockmode));
j++;
}
+ for (i = 0; ternaryRelOpts[i].gen.name; i++)
+ {
+ Assert(DoLockModesConflict(ternaryRelOpts[i].gen.lockmode,
+ ternaryRelOpts[i].gen.lockmode));
+ j++;
+ }
+
for (i = 0; intRelOpts[i].gen.name; i++)
{
Assert(DoLockModesConflict(intRelOpts[i].gen.lockmode,
@@ -640,6 +653,14 @@ initialize_reloptions(void)
j++;
}
+ for (i = 0; ternaryRelOpts[i].gen.name; i++)
+ {
+ relOpts[j] = &ternaryRelOpts[i].gen;
+ relOpts[j]->type = RELOPT_TYPE_TERNARY;
+ relOpts[j]->namelen = strlen(relOpts[j]->name);
+ j++;
+ }
+
for (i = 0; intRelOpts[i].gen.name; i++)
{
relOpts[j] = &intRelOpts[i].gen;
@@ -800,6 +821,9 @@ allocate_reloption(bits32 kinds, int type, const char *name, const char *desc,
case RELOPT_TYPE_BOOL:
size = sizeof(relopt_bool);
break;
+ case RELOPT_TYPE_TERNARY:
+ size = sizeof(relopt_ternary);
+ break;
case RELOPT_TYPE_INT:
size = sizeof(relopt_int);
break;
@@ -883,6 +907,54 @@ add_local_bool_reloption(local_relopts *relopts, const char *name,
add_local_reloption(relopts, (relopt_gen *) newoption, offset);
}
+/*
+ * init_ternary_reloption
+ * Allocate and initialize a new ternary reloption
+ */
+static relopt_ternary *
+init_ternary_reloption(bits32 kinds, const char *name, const char *desc,
+ ternary default_val, LOCKMODE lockmode)
+{
+ relopt_ternary *newoption;
+
+ newoption = (relopt_ternary *) allocate_reloption(kinds,
+ RELOPT_TYPE_TERNARY, name, desc, lockmode);
+ newoption->default_val = default_val;
+
+ return newoption;
+}
+
+/*
+ * add_ternary_reloption
+ * Add a new ternary reloption
+ */
+void
+add_ternary_reloption(bits32 kinds, const char *name, const char *desc,
+ ternary default_val, LOCKMODE lockmode)
+{
+ relopt_ternary *newoption = init_ternary_reloption(kinds, name, desc,
+ default_val, lockmode);
+
+ add_reloption((relopt_gen *) newoption);
+}
+
+/*
+ * add_local_ternary_reloption
+ * Add a new ternary local reloption
+ *
+ * 'offset' is offset of ternary-typed field.
+ */
+void
+add_local_ternary_reloption(local_relopts *relopts, const char *name,
+ const char *desc, ternary default_val,
+ int offset)
+{
+ relopt_ternary *newoption = init_ternary_reloption(RELOPT_KIND_LOCAL,
+ name, desc,
+ default_val, 0);
+
+ add_local_reloption(relopts, (relopt_gen *) newoption, offset);
+}
/*
* init_real_reloption
@@ -1617,6 +1689,18 @@ parse_one_reloption(relopt_value *option, char *text_str, int text_len,
option->gen->name, value)));
}
break;
+ case RELOPT_TYPE_TERNARY:
+ {
+ bool b;
+ parsed = parse_bool(value, &b);
+ option->values.ternary_val = b ? TERNARY_TRUE : TERNARY_FALSE;
+ if (validate && !parsed)
+ ereport(ERROR,
+ (errcode(ERRCODE_INVALID_PARAMETER_VALUE),
+ errmsg("invalid value for ternary option \"%s\": %s",
+ option->gen->name, value)));
+ }
+ break;
case RELOPT_TYPE_INT:
{
relopt_int *optint = (relopt_int *) option->gen;
@@ -1780,17 +1864,6 @@ fillRelOptions(void *rdopts, Size basesize,
char *itempos = ((char *) rdopts) + elems[j].offset;
char *string_val;
- /*
- * If isset_offset is provided, store whether the reloption is
- * set there.
- */
- if (elems[j].isset_offset > 0)
- {
- char *setpos = ((char *) rdopts) + elems[j].isset_offset;
-
- *(bool *) setpos = options[i].isset;
- }
-
switch (options[i].gen->type)
{
case RELOPT_TYPE_BOOL:
@@ -1798,6 +1871,11 @@ fillRelOptions(void *rdopts, Size basesize,
options[i].values.bool_val :
((relopt_bool *) options[i].gen)->default_val;
break;
+ case RELOPT_TYPE_TERNARY:
+ *(ternary *) itempos = options[i].isset ?
+ options[i].values.ternary_val :
+ ((relopt_ternary *) options[i].gen)->default_val;
+ break;
case RELOPT_TYPE_INT:
*(int *) itempos = options[i].isset ?
options[i].values.int_val :
@@ -1912,8 +1990,8 @@ default_reloptions(Datum reloptions, bool validate, relopt_kind kind)
offsetof(StdRdOptions, parallel_workers)},
{"vacuum_index_cleanup", RELOPT_TYPE_ENUM,
offsetof(StdRdOptions, vacuum_index_cleanup)},
- {"vacuum_truncate", RELOPT_TYPE_BOOL,
- offsetof(StdRdOptions, vacuum_truncate), offsetof(StdRdOptions, vacuum_truncate_set)},
+ {"vacuum_truncate", RELOPT_TYPE_TERNARY,
+ offsetof(StdRdOptions, vacuum_truncate)},
{"vacuum_max_eager_freeze_failure_rate", RELOPT_TYPE_REAL,
offsetof(StdRdOptions, vacuum_max_eager_freeze_failure_rate)}
};
@@ -1993,7 +2071,6 @@ build_local_reloptions(local_relopts *relopts, Datum options, bool validate)
elems[i].optname = opt->option->name;
elems[i].opttype = opt->option->type;
elems[i].offset = opt->offset;
- elems[i].isset_offset = 0; /* not supported for local relopts yet */
i++;
}
diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c
index 733ef40ae7..977babff54 100644
--- a/src/backend/commands/vacuum.c
+++ b/src/backend/commands/vacuum.c
@@ -2221,9 +2221,9 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams params,
{
StdRdOptions *opts = (StdRdOptions *) rel->rd_options;
- if (opts && opts->vacuum_truncate_set)
+ if (opts && opts->vacuum_truncate != TERNARY_UNSET)
{
- if (opts->vacuum_truncate)
+ if (opts->vacuum_truncate == TERNARY_TRUE)
params.truncate = VACOPTVALUE_ENABLED;
else
params.truncate = VACOPTVALUE_DISABLED;
diff --git a/src/include/access/reloptions.h b/src/include/access/reloptions.h
index a604a4702c..a436697658 100644
--- a/src/include/access/reloptions.h
+++ b/src/include/access/reloptions.h
@@ -29,6 +29,7 @@
typedef enum relopt_type
{
RELOPT_TYPE_BOOL,
+ RELOPT_TYPE_TERNARY, /* on, off, unset */
RELOPT_TYPE_INT,
RELOPT_TYPE_REAL,
RELOPT_TYPE_ENUM,
@@ -80,6 +81,7 @@ typedef struct relopt_value
union
{
bool bool_val;
+ ternary ternary_val;
int int_val;
double real_val;
int enum_val;
@@ -94,6 +96,12 @@ typedef struct relopt_bool
bool default_val;
} relopt_bool;
+typedef struct relopt_rernary
+{
+ relopt_gen gen;
+ int default_val;
+} relopt_ternary;
+
typedef struct relopt_int
{
relopt_gen gen;
@@ -152,19 +160,6 @@ typedef struct
const char *optname; /* option's name */
relopt_type opttype; /* option's datatype */
int offset; /* offset of field in result struct */
-
- /*
- * isset_offset is an optional offset of a field in the result struct that
- * stores whether the option is explicitly set for the relation or if it
- * just picked up the default value. In most cases, this can be
- * accomplished by giving the reloption a special out-of-range default
- * value (e.g., some integer reloptions use -2), but this isn't always
- * possible. For example, a Boolean reloption cannot be given an
- * out-of-range default, so we need another way to discover the source of
- * its value. This offset is only used if given a value greater than
- * zero.
- */
- int isset_offset;
} relopt_parse_elt;
/* Local reloption definition */
@@ -195,6 +190,8 @@ typedef struct local_relopts
extern relopt_kind add_reloption_kind(void);
extern void add_bool_reloption(bits32 kinds, const char *name, const char *desc,
bool default_val, LOCKMODE lockmode);
+extern void add_ternary_reloption(bits32 kinds, const char *name,
+ const char *desc, int default_val, LOCKMODE lockmode);
extern void add_int_reloption(bits32 kinds, const char *name, const char *desc,
int default_val, int min_val, int max_val,
LOCKMODE lockmode);
@@ -214,6 +211,9 @@ extern void register_reloptions_validator(local_relopts *relopts,
extern void add_local_bool_reloption(local_relopts *relopts, const char *name,
const char *desc, bool default_val,
int offset);
+extern void add_local_ternary_reloption(local_relopts *relopts,
+ const char *name, const char *desc,
+ ternary default_val, int offset);
extern void add_local_int_reloption(local_relopts *relopts, const char *name,
const char *desc, int default_val,
int min_val, int max_val, int offset);
diff --git a/src/include/c.h b/src/include/c.h
index 39022f8a9d..9c59ccccf8 100644
--- a/src/include/c.h
+++ b/src/include/c.h
@@ -488,6 +488,22 @@ typedef void (*pg_funcptr_t) (void);
#include <stdbool.h>
+/*
+ * ternary
+ * Boolean value with an extrea "unset" option
+ *
+ * Ternary data type is used in relation options that can be "true", "false" or
+ * "unset". Since relation options are used deep inside the PostgreSQL code,
+ * this type is declared globally.
+*/
+
+typedef enum ternary
+{
+ TERNARY_FALSE = 0,
+ TERNARY_TRUE = 1,
+ TERNARY_UNSET = -1
+} ternary;
+
/* ----------------------------------------------------------------
* Section 3: standard system types
diff --git a/src/include/utils/rel.h b/src/include/utils/rel.h
index b552359915..7346d618f9 100644
--- a/src/include/utils/rel.h
+++ b/src/include/utils/rel.h
@@ -346,8 +346,7 @@ typedef struct StdRdOptions
bool user_catalog_table; /* use as an additional catalog relation */
int parallel_workers; /* max number of parallel workers */
StdRdOptIndexCleanup vacuum_index_cleanup; /* controls index vacuuming */
- bool vacuum_truncate; /* enables vacuum to truncate a relation */
- bool vacuum_truncate_set; /* whether vacuum_truncate is set */
+ ternary vacuum_truncate; /* enables vacuum to truncate a relation */
/*
* Fraction of pages in a relation that vacuum can eagerly scan and fail
--
2.39.2
--nextPart4155600.3ZeAukHxDK
Content-Disposition: attachment;
filename="v2a-0003-Add-alias-to-be-used-as-unset-state.patch"
Content-Transfer-Encoding: 7Bit
Content-Type: text/x-patch; charset="unicode-2-0-utf-8";
name="v2a-0003-Add-alias-to-be-used-as-unset-state.patch"
^ permalink raw reply [nested|flat] 868+ messages in thread
* [PATCH v2a 2/4] Introduce ternary reloptions
@ 2025-09-04 14:41 Nikolay Shaplov <[email protected]>
0 siblings, 0 replies; 868+ messages in thread
From: Nikolay Shaplov @ 2025-09-04 14:41 UTC (permalink / raw)
Introduce ternary reloption as a replacement for current `vacuum_truncate`
implementation. Remove `vacuum_truncate_set` additional flag and using
`TERNARY_UNSET` value instead.
---
src/backend/access/common/reloptions.c | 129 ++++++++++++++++++++-----
src/backend/commands/vacuum.c | 4 +-
src/include/access/reloptions.h | 26 ++---
src/include/c.h | 16 +++
src/include/utils/rel.h | 3 +-
5 files changed, 135 insertions(+), 43 deletions(-)
diff --git a/src/backend/access/common/reloptions.c b/src/backend/access/common/reloptions.c
index 0af3fea68f..f543f11001 100644
--- a/src/backend/access/common/reloptions.c
+++ b/src/backend/access/common/reloptions.c
@@ -40,9 +40,9 @@
*
* To add an option:
*
- * (i) decide on a type (bool, integer, real, enum, string), name, default
- * value, upper and lower bounds (if applicable); for strings, consider a
- * validation routine.
+ * (i) decide on a type (bool, ternary, integer, real, enum, string), name,
+ * default value, upper and lower bounds (if applicable); for strings,
+ * consider a validation routine.
* (ii) add a record below (or use add_<type>_reloption).
* (iii) add it to the appropriate options struct (perhaps StdRdOptions)
* (iv) add it to the appropriate handling routine (perhaps
@@ -147,15 +147,6 @@ static relopt_bool boolRelOpts[] =
},
false
},
- {
- {
- "vacuum_truncate",
- "Enables vacuum to truncate empty pages at the end of this table",
- RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
- ShareUpdateExclusiveLock
- },
- true
- },
{
{
"deduplicate_items",
@@ -170,6 +161,21 @@ static relopt_bool boolRelOpts[] =
{{NULL}}
};
+static relopt_ternary ternaryRelOpts[] =
+{
+ {
+ {
+ "vacuum_truncate",
+ "Enables vacuum to truncate empty pages at the end of this table",
+ RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
+ ShareUpdateExclusiveLock
+ },
+ TERNARY_UNSET
+ },
+ /* list terminator */
+ {{NULL}}
+};
+
static relopt_int intRelOpts[] =
{
{
@@ -600,6 +606,13 @@ initialize_reloptions(void)
boolRelOpts[i].gen.lockmode));
j++;
}
+ for (i = 0; ternaryRelOpts[i].gen.name; i++)
+ {
+ Assert(DoLockModesConflict(ternaryRelOpts[i].gen.lockmode,
+ ternaryRelOpts[i].gen.lockmode));
+ j++;
+ }
+
for (i = 0; intRelOpts[i].gen.name; i++)
{
Assert(DoLockModesConflict(intRelOpts[i].gen.lockmode,
@@ -640,6 +653,14 @@ initialize_reloptions(void)
j++;
}
+ for (i = 0; ternaryRelOpts[i].gen.name; i++)
+ {
+ relOpts[j] = &ternaryRelOpts[i].gen;
+ relOpts[j]->type = RELOPT_TYPE_TERNARY;
+ relOpts[j]->namelen = strlen(relOpts[j]->name);
+ j++;
+ }
+
for (i = 0; intRelOpts[i].gen.name; i++)
{
relOpts[j] = &intRelOpts[i].gen;
@@ -800,6 +821,9 @@ allocate_reloption(bits32 kinds, int type, const char *name, const char *desc,
case RELOPT_TYPE_BOOL:
size = sizeof(relopt_bool);
break;
+ case RELOPT_TYPE_TERNARY:
+ size = sizeof(relopt_ternary);
+ break;
case RELOPT_TYPE_INT:
size = sizeof(relopt_int);
break;
@@ -883,6 +907,54 @@ add_local_bool_reloption(local_relopts *relopts, const char *name,
add_local_reloption(relopts, (relopt_gen *) newoption, offset);
}
+/*
+ * init_ternary_reloption
+ * Allocate and initialize a new ternary reloption
+ */
+static relopt_ternary *
+init_ternary_reloption(bits32 kinds, const char *name, const char *desc,
+ ternary default_val, LOCKMODE lockmode)
+{
+ relopt_ternary *newoption;
+
+ newoption = (relopt_ternary *) allocate_reloption(kinds,
+ RELOPT_TYPE_TERNARY, name, desc, lockmode);
+ newoption->default_val = default_val;
+
+ return newoption;
+}
+
+/*
+ * add_ternary_reloption
+ * Add a new ternary reloption
+ */
+void
+add_ternary_reloption(bits32 kinds, const char *name, const char *desc,
+ ternary default_val, LOCKMODE lockmode)
+{
+ relopt_ternary *newoption = init_ternary_reloption(kinds, name, desc,
+ default_val, lockmode);
+
+ add_reloption((relopt_gen *) newoption);
+}
+
+/*
+ * add_local_ternary_reloption
+ * Add a new ternary local reloption
+ *
+ * 'offset' is offset of ternary-typed field.
+ */
+void
+add_local_ternary_reloption(local_relopts *relopts, const char *name,
+ const char *desc, ternary default_val,
+ int offset)
+{
+ relopt_ternary *newoption = init_ternary_reloption(RELOPT_KIND_LOCAL,
+ name, desc,
+ default_val, 0);
+
+ add_local_reloption(relopts, (relopt_gen *) newoption, offset);
+}
/*
* init_real_reloption
@@ -1617,6 +1689,18 @@ parse_one_reloption(relopt_value *option, char *text_str, int text_len,
option->gen->name, value)));
}
break;
+ case RELOPT_TYPE_TERNARY:
+ {
+ bool b;
+ parsed = parse_bool(value, &b);
+ option->values.ternary_val = b ? TERNARY_TRUE : TERNARY_FALSE;
+ if (validate && !parsed)
+ ereport(ERROR,
+ (errcode(ERRCODE_INVALID_PARAMETER_VALUE),
+ errmsg("invalid value for ternary option \"%s\": %s",
+ option->gen->name, value)));
+ }
+ break;
case RELOPT_TYPE_INT:
{
relopt_int *optint = (relopt_int *) option->gen;
@@ -1780,17 +1864,6 @@ fillRelOptions(void *rdopts, Size basesize,
char *itempos = ((char *) rdopts) + elems[j].offset;
char *string_val;
- /*
- * If isset_offset is provided, store whether the reloption is
- * set there.
- */
- if (elems[j].isset_offset > 0)
- {
- char *setpos = ((char *) rdopts) + elems[j].isset_offset;
-
- *(bool *) setpos = options[i].isset;
- }
-
switch (options[i].gen->type)
{
case RELOPT_TYPE_BOOL:
@@ -1798,6 +1871,11 @@ fillRelOptions(void *rdopts, Size basesize,
options[i].values.bool_val :
((relopt_bool *) options[i].gen)->default_val;
break;
+ case RELOPT_TYPE_TERNARY:
+ *(ternary *) itempos = options[i].isset ?
+ options[i].values.ternary_val :
+ ((relopt_ternary *) options[i].gen)->default_val;
+ break;
case RELOPT_TYPE_INT:
*(int *) itempos = options[i].isset ?
options[i].values.int_val :
@@ -1912,8 +1990,8 @@ default_reloptions(Datum reloptions, bool validate, relopt_kind kind)
offsetof(StdRdOptions, parallel_workers)},
{"vacuum_index_cleanup", RELOPT_TYPE_ENUM,
offsetof(StdRdOptions, vacuum_index_cleanup)},
- {"vacuum_truncate", RELOPT_TYPE_BOOL,
- offsetof(StdRdOptions, vacuum_truncate), offsetof(StdRdOptions, vacuum_truncate_set)},
+ {"vacuum_truncate", RELOPT_TYPE_TERNARY,
+ offsetof(StdRdOptions, vacuum_truncate)},
{"vacuum_max_eager_freeze_failure_rate", RELOPT_TYPE_REAL,
offsetof(StdRdOptions, vacuum_max_eager_freeze_failure_rate)}
};
@@ -1993,7 +2071,6 @@ build_local_reloptions(local_relopts *relopts, Datum options, bool validate)
elems[i].optname = opt->option->name;
elems[i].opttype = opt->option->type;
elems[i].offset = opt->offset;
- elems[i].isset_offset = 0; /* not supported for local relopts yet */
i++;
}
diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c
index 733ef40ae7..977babff54 100644
--- a/src/backend/commands/vacuum.c
+++ b/src/backend/commands/vacuum.c
@@ -2221,9 +2221,9 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams params,
{
StdRdOptions *opts = (StdRdOptions *) rel->rd_options;
- if (opts && opts->vacuum_truncate_set)
+ if (opts && opts->vacuum_truncate != TERNARY_UNSET)
{
- if (opts->vacuum_truncate)
+ if (opts->vacuum_truncate == TERNARY_TRUE)
params.truncate = VACOPTVALUE_ENABLED;
else
params.truncate = VACOPTVALUE_DISABLED;
diff --git a/src/include/access/reloptions.h b/src/include/access/reloptions.h
index a604a4702c..a436697658 100644
--- a/src/include/access/reloptions.h
+++ b/src/include/access/reloptions.h
@@ -29,6 +29,7 @@
typedef enum relopt_type
{
RELOPT_TYPE_BOOL,
+ RELOPT_TYPE_TERNARY, /* on, off, unset */
RELOPT_TYPE_INT,
RELOPT_TYPE_REAL,
RELOPT_TYPE_ENUM,
@@ -80,6 +81,7 @@ typedef struct relopt_value
union
{
bool bool_val;
+ ternary ternary_val;
int int_val;
double real_val;
int enum_val;
@@ -94,6 +96,12 @@ typedef struct relopt_bool
bool default_val;
} relopt_bool;
+typedef struct relopt_rernary
+{
+ relopt_gen gen;
+ int default_val;
+} relopt_ternary;
+
typedef struct relopt_int
{
relopt_gen gen;
@@ -152,19 +160,6 @@ typedef struct
const char *optname; /* option's name */
relopt_type opttype; /* option's datatype */
int offset; /* offset of field in result struct */
-
- /*
- * isset_offset is an optional offset of a field in the result struct that
- * stores whether the option is explicitly set for the relation or if it
- * just picked up the default value. In most cases, this can be
- * accomplished by giving the reloption a special out-of-range default
- * value (e.g., some integer reloptions use -2), but this isn't always
- * possible. For example, a Boolean reloption cannot be given an
- * out-of-range default, so we need another way to discover the source of
- * its value. This offset is only used if given a value greater than
- * zero.
- */
- int isset_offset;
} relopt_parse_elt;
/* Local reloption definition */
@@ -195,6 +190,8 @@ typedef struct local_relopts
extern relopt_kind add_reloption_kind(void);
extern void add_bool_reloption(bits32 kinds, const char *name, const char *desc,
bool default_val, LOCKMODE lockmode);
+extern void add_ternary_reloption(bits32 kinds, const char *name,
+ const char *desc, int default_val, LOCKMODE lockmode);
extern void add_int_reloption(bits32 kinds, const char *name, const char *desc,
int default_val, int min_val, int max_val,
LOCKMODE lockmode);
@@ -214,6 +211,9 @@ extern void register_reloptions_validator(local_relopts *relopts,
extern void add_local_bool_reloption(local_relopts *relopts, const char *name,
const char *desc, bool default_val,
int offset);
+extern void add_local_ternary_reloption(local_relopts *relopts,
+ const char *name, const char *desc,
+ ternary default_val, int offset);
extern void add_local_int_reloption(local_relopts *relopts, const char *name,
const char *desc, int default_val,
int min_val, int max_val, int offset);
diff --git a/src/include/c.h b/src/include/c.h
index 39022f8a9d..9c59ccccf8 100644
--- a/src/include/c.h
+++ b/src/include/c.h
@@ -488,6 +488,22 @@ typedef void (*pg_funcptr_t) (void);
#include <stdbool.h>
+/*
+ * ternary
+ * Boolean value with an extrea "unset" option
+ *
+ * Ternary data type is used in relation options that can be "true", "false" or
+ * "unset". Since relation options are used deep inside the PostgreSQL code,
+ * this type is declared globally.
+*/
+
+typedef enum ternary
+{
+ TERNARY_FALSE = 0,
+ TERNARY_TRUE = 1,
+ TERNARY_UNSET = -1
+} ternary;
+
/* ----------------------------------------------------------------
* Section 3: standard system types
diff --git a/src/include/utils/rel.h b/src/include/utils/rel.h
index b552359915..7346d618f9 100644
--- a/src/include/utils/rel.h
+++ b/src/include/utils/rel.h
@@ -346,8 +346,7 @@ typedef struct StdRdOptions
bool user_catalog_table; /* use as an additional catalog relation */
int parallel_workers; /* max number of parallel workers */
StdRdOptIndexCleanup vacuum_index_cleanup; /* controls index vacuuming */
- bool vacuum_truncate; /* enables vacuum to truncate a relation */
- bool vacuum_truncate_set; /* whether vacuum_truncate is set */
+ ternary vacuum_truncate; /* enables vacuum to truncate a relation */
/*
* Fraction of pages in a relation that vacuum can eagerly scan and fail
--
2.39.2
--nextPart4155600.3ZeAukHxDK
Content-Disposition: attachment;
filename="v2a-0003-Add-alias-to-be-used-as-unset-state.patch"
Content-Transfer-Encoding: 7Bit
Content-Type: text/x-patch; charset="unicode-2-0-utf-8";
name="v2a-0003-Add-alias-to-be-used-as-unset-state.patch"
^ permalink raw reply [nested|flat] 868+ messages in thread
* [PATCH v2 2/4] Introduce ternary reloptions
@ 2025-09-04 14:41 Nikolay Shaplov <[email protected]>
0 siblings, 0 replies; 868+ messages in thread
From: Nikolay Shaplov @ 2025-09-04 14:41 UTC (permalink / raw)
Introduce ternary reloption as a replacement for current `vacuum_truncate`
implementation. Remove `vacuum_truncate_set` additional flag and using
`TERNARY_UNSET` value instead.
---
src/backend/access/common/reloptions.c | 129 ++++++++++++++++++++-----
src/backend/commands/vacuum.c | 4 +-
src/include/access/reloptions.h | 26 ++---
src/include/c.h | 16 +++
src/include/utils/rel.h | 3 +-
5 files changed, 135 insertions(+), 43 deletions(-)
diff --git a/src/backend/access/common/reloptions.c b/src/backend/access/common/reloptions.c
index 0af3fea68f..f543f11001 100644
--- a/src/backend/access/common/reloptions.c
+++ b/src/backend/access/common/reloptions.c
@@ -40,9 +40,9 @@
*
* To add an option:
*
- * (i) decide on a type (bool, integer, real, enum, string), name, default
- * value, upper and lower bounds (if applicable); for strings, consider a
- * validation routine.
+ * (i) decide on a type (bool, ternary, integer, real, enum, string), name,
+ * default value, upper and lower bounds (if applicable); for strings,
+ * consider a validation routine.
* (ii) add a record below (or use add_<type>_reloption).
* (iii) add it to the appropriate options struct (perhaps StdRdOptions)
* (iv) add it to the appropriate handling routine (perhaps
@@ -147,15 +147,6 @@ static relopt_bool boolRelOpts[] =
},
false
},
- {
- {
- "vacuum_truncate",
- "Enables vacuum to truncate empty pages at the end of this table",
- RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
- ShareUpdateExclusiveLock
- },
- true
- },
{
{
"deduplicate_items",
@@ -170,6 +161,21 @@ static relopt_bool boolRelOpts[] =
{{NULL}}
};
+static relopt_ternary ternaryRelOpts[] =
+{
+ {
+ {
+ "vacuum_truncate",
+ "Enables vacuum to truncate empty pages at the end of this table",
+ RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
+ ShareUpdateExclusiveLock
+ },
+ TERNARY_UNSET
+ },
+ /* list terminator */
+ {{NULL}}
+};
+
static relopt_int intRelOpts[] =
{
{
@@ -600,6 +606,13 @@ initialize_reloptions(void)
boolRelOpts[i].gen.lockmode));
j++;
}
+ for (i = 0; ternaryRelOpts[i].gen.name; i++)
+ {
+ Assert(DoLockModesConflict(ternaryRelOpts[i].gen.lockmode,
+ ternaryRelOpts[i].gen.lockmode));
+ j++;
+ }
+
for (i = 0; intRelOpts[i].gen.name; i++)
{
Assert(DoLockModesConflict(intRelOpts[i].gen.lockmode,
@@ -640,6 +653,14 @@ initialize_reloptions(void)
j++;
}
+ for (i = 0; ternaryRelOpts[i].gen.name; i++)
+ {
+ relOpts[j] = &ternaryRelOpts[i].gen;
+ relOpts[j]->type = RELOPT_TYPE_TERNARY;
+ relOpts[j]->namelen = strlen(relOpts[j]->name);
+ j++;
+ }
+
for (i = 0; intRelOpts[i].gen.name; i++)
{
relOpts[j] = &intRelOpts[i].gen;
@@ -800,6 +821,9 @@ allocate_reloption(bits32 kinds, int type, const char *name, const char *desc,
case RELOPT_TYPE_BOOL:
size = sizeof(relopt_bool);
break;
+ case RELOPT_TYPE_TERNARY:
+ size = sizeof(relopt_ternary);
+ break;
case RELOPT_TYPE_INT:
size = sizeof(relopt_int);
break;
@@ -883,6 +907,54 @@ add_local_bool_reloption(local_relopts *relopts, const char *name,
add_local_reloption(relopts, (relopt_gen *) newoption, offset);
}
+/*
+ * init_ternary_reloption
+ * Allocate and initialize a new ternary reloption
+ */
+static relopt_ternary *
+init_ternary_reloption(bits32 kinds, const char *name, const char *desc,
+ ternary default_val, LOCKMODE lockmode)
+{
+ relopt_ternary *newoption;
+
+ newoption = (relopt_ternary *) allocate_reloption(kinds,
+ RELOPT_TYPE_TERNARY, name, desc, lockmode);
+ newoption->default_val = default_val;
+
+ return newoption;
+}
+
+/*
+ * add_ternary_reloption
+ * Add a new ternary reloption
+ */
+void
+add_ternary_reloption(bits32 kinds, const char *name, const char *desc,
+ ternary default_val, LOCKMODE lockmode)
+{
+ relopt_ternary *newoption = init_ternary_reloption(kinds, name, desc,
+ default_val, lockmode);
+
+ add_reloption((relopt_gen *) newoption);
+}
+
+/*
+ * add_local_ternary_reloption
+ * Add a new ternary local reloption
+ *
+ * 'offset' is offset of ternary-typed field.
+ */
+void
+add_local_ternary_reloption(local_relopts *relopts, const char *name,
+ const char *desc, ternary default_val,
+ int offset)
+{
+ relopt_ternary *newoption = init_ternary_reloption(RELOPT_KIND_LOCAL,
+ name, desc,
+ default_val, 0);
+
+ add_local_reloption(relopts, (relopt_gen *) newoption, offset);
+}
/*
* init_real_reloption
@@ -1617,6 +1689,18 @@ parse_one_reloption(relopt_value *option, char *text_str, int text_len,
option->gen->name, value)));
}
break;
+ case RELOPT_TYPE_TERNARY:
+ {
+ bool b;
+ parsed = parse_bool(value, &b);
+ option->values.ternary_val = b ? TERNARY_TRUE : TERNARY_FALSE;
+ if (validate && !parsed)
+ ereport(ERROR,
+ (errcode(ERRCODE_INVALID_PARAMETER_VALUE),
+ errmsg("invalid value for ternary option \"%s\": %s",
+ option->gen->name, value)));
+ }
+ break;
case RELOPT_TYPE_INT:
{
relopt_int *optint = (relopt_int *) option->gen;
@@ -1780,17 +1864,6 @@ fillRelOptions(void *rdopts, Size basesize,
char *itempos = ((char *) rdopts) + elems[j].offset;
char *string_val;
- /*
- * If isset_offset is provided, store whether the reloption is
- * set there.
- */
- if (elems[j].isset_offset > 0)
- {
- char *setpos = ((char *) rdopts) + elems[j].isset_offset;
-
- *(bool *) setpos = options[i].isset;
- }
-
switch (options[i].gen->type)
{
case RELOPT_TYPE_BOOL:
@@ -1798,6 +1871,11 @@ fillRelOptions(void *rdopts, Size basesize,
options[i].values.bool_val :
((relopt_bool *) options[i].gen)->default_val;
break;
+ case RELOPT_TYPE_TERNARY:
+ *(ternary *) itempos = options[i].isset ?
+ options[i].values.ternary_val :
+ ((relopt_ternary *) options[i].gen)->default_val;
+ break;
case RELOPT_TYPE_INT:
*(int *) itempos = options[i].isset ?
options[i].values.int_val :
@@ -1912,8 +1990,8 @@ default_reloptions(Datum reloptions, bool validate, relopt_kind kind)
offsetof(StdRdOptions, parallel_workers)},
{"vacuum_index_cleanup", RELOPT_TYPE_ENUM,
offsetof(StdRdOptions, vacuum_index_cleanup)},
- {"vacuum_truncate", RELOPT_TYPE_BOOL,
- offsetof(StdRdOptions, vacuum_truncate), offsetof(StdRdOptions, vacuum_truncate_set)},
+ {"vacuum_truncate", RELOPT_TYPE_TERNARY,
+ offsetof(StdRdOptions, vacuum_truncate)},
{"vacuum_max_eager_freeze_failure_rate", RELOPT_TYPE_REAL,
offsetof(StdRdOptions, vacuum_max_eager_freeze_failure_rate)}
};
@@ -1993,7 +2071,6 @@ build_local_reloptions(local_relopts *relopts, Datum options, bool validate)
elems[i].optname = opt->option->name;
elems[i].opttype = opt->option->type;
elems[i].offset = opt->offset;
- elems[i].isset_offset = 0; /* not supported for local relopts yet */
i++;
}
diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c
index 733ef40ae7..977babff54 100644
--- a/src/backend/commands/vacuum.c
+++ b/src/backend/commands/vacuum.c
@@ -2221,9 +2221,9 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams params,
{
StdRdOptions *opts = (StdRdOptions *) rel->rd_options;
- if (opts && opts->vacuum_truncate_set)
+ if (opts && opts->vacuum_truncate != TERNARY_UNSET)
{
- if (opts->vacuum_truncate)
+ if (opts->vacuum_truncate == TERNARY_TRUE)
params.truncate = VACOPTVALUE_ENABLED;
else
params.truncate = VACOPTVALUE_DISABLED;
diff --git a/src/include/access/reloptions.h b/src/include/access/reloptions.h
index a604a4702c..a436697658 100644
--- a/src/include/access/reloptions.h
+++ b/src/include/access/reloptions.h
@@ -29,6 +29,7 @@
typedef enum relopt_type
{
RELOPT_TYPE_BOOL,
+ RELOPT_TYPE_TERNARY, /* on, off, unset */
RELOPT_TYPE_INT,
RELOPT_TYPE_REAL,
RELOPT_TYPE_ENUM,
@@ -80,6 +81,7 @@ typedef struct relopt_value
union
{
bool bool_val;
+ ternary ternary_val;
int int_val;
double real_val;
int enum_val;
@@ -94,6 +96,12 @@ typedef struct relopt_bool
bool default_val;
} relopt_bool;
+typedef struct relopt_rernary
+{
+ relopt_gen gen;
+ int default_val;
+} relopt_ternary;
+
typedef struct relopt_int
{
relopt_gen gen;
@@ -152,19 +160,6 @@ typedef struct
const char *optname; /* option's name */
relopt_type opttype; /* option's datatype */
int offset; /* offset of field in result struct */
-
- /*
- * isset_offset is an optional offset of a field in the result struct that
- * stores whether the option is explicitly set for the relation or if it
- * just picked up the default value. In most cases, this can be
- * accomplished by giving the reloption a special out-of-range default
- * value (e.g., some integer reloptions use -2), but this isn't always
- * possible. For example, a Boolean reloption cannot be given an
- * out-of-range default, so we need another way to discover the source of
- * its value. This offset is only used if given a value greater than
- * zero.
- */
- int isset_offset;
} relopt_parse_elt;
/* Local reloption definition */
@@ -195,6 +190,8 @@ typedef struct local_relopts
extern relopt_kind add_reloption_kind(void);
extern void add_bool_reloption(bits32 kinds, const char *name, const char *desc,
bool default_val, LOCKMODE lockmode);
+extern void add_ternary_reloption(bits32 kinds, const char *name,
+ const char *desc, int default_val, LOCKMODE lockmode);
extern void add_int_reloption(bits32 kinds, const char *name, const char *desc,
int default_val, int min_val, int max_val,
LOCKMODE lockmode);
@@ -214,6 +211,9 @@ extern void register_reloptions_validator(local_relopts *relopts,
extern void add_local_bool_reloption(local_relopts *relopts, const char *name,
const char *desc, bool default_val,
int offset);
+extern void add_local_ternary_reloption(local_relopts *relopts,
+ const char *name, const char *desc,
+ ternary default_val, int offset);
extern void add_local_int_reloption(local_relopts *relopts, const char *name,
const char *desc, int default_val,
int min_val, int max_val, int offset);
diff --git a/src/include/c.h b/src/include/c.h
index 39022f8a9d..9c59ccccf8 100644
--- a/src/include/c.h
+++ b/src/include/c.h
@@ -488,6 +488,22 @@ typedef void (*pg_funcptr_t) (void);
#include <stdbool.h>
+/*
+ * ternary
+ * Boolean value with an extrea "unset" option
+ *
+ * Ternary data type is used in relation options that can be "true", "false" or
+ * "unset". Since relation options are used deep inside the PostgreSQL code,
+ * this type is declared globally.
+*/
+
+typedef enum ternary
+{
+ TERNARY_FALSE = 0,
+ TERNARY_TRUE = 1,
+ TERNARY_UNSET = -1
+} ternary;
+
/* ----------------------------------------------------------------
* Section 3: standard system types
diff --git a/src/include/utils/rel.h b/src/include/utils/rel.h
index b552359915..7346d618f9 100644
--- a/src/include/utils/rel.h
+++ b/src/include/utils/rel.h
@@ -346,8 +346,7 @@ typedef struct StdRdOptions
bool user_catalog_table; /* use as an additional catalog relation */
int parallel_workers; /* max number of parallel workers */
StdRdOptIndexCleanup vacuum_index_cleanup; /* controls index vacuuming */
- bool vacuum_truncate; /* enables vacuum to truncate a relation */
- bool vacuum_truncate_set; /* whether vacuum_truncate is set */
+ ternary vacuum_truncate; /* enables vacuum to truncate a relation */
/*
* Fraction of pages in a relation that vacuum can eagerly scan and fail
--
2.39.2
--nextPart5129932.5fSG56mABF
Content-Disposition: attachment;
filename="v2-0003-Add-alias-to-be-used-as-unset-state.patch"
Content-Transfer-Encoding: 7Bit
Content-Type: text/x-patch; charset="unicode-2-0-utf-8";
name="v2-0003-Add-alias-to-be-used-as-unset-state.patch"
^ permalink raw reply [nested|flat] 868+ messages in thread
* [PATCH v2 2/4] Introduce ternary reloptions
@ 2025-09-04 14:41 Nikolay Shaplov <[email protected]>
0 siblings, 0 replies; 868+ messages in thread
From: Nikolay Shaplov @ 2025-09-04 14:41 UTC (permalink / raw)
Introduce ternary reloption as a replacement for current `vacuum_truncate`
implementation. Remove `vacuum_truncate_set` additional flag and using
`TERNARY_UNSET` value instead.
---
src/backend/access/common/reloptions.c | 129 ++++++++++++++++++++-----
src/backend/commands/vacuum.c | 4 +-
src/include/access/reloptions.h | 26 ++---
src/include/c.h | 16 +++
src/include/utils/rel.h | 3 +-
5 files changed, 135 insertions(+), 43 deletions(-)
diff --git a/src/backend/access/common/reloptions.c b/src/backend/access/common/reloptions.c
index 0af3fea68f..f543f11001 100644
--- a/src/backend/access/common/reloptions.c
+++ b/src/backend/access/common/reloptions.c
@@ -40,9 +40,9 @@
*
* To add an option:
*
- * (i) decide on a type (bool, integer, real, enum, string), name, default
- * value, upper and lower bounds (if applicable); for strings, consider a
- * validation routine.
+ * (i) decide on a type (bool, ternary, integer, real, enum, string), name,
+ * default value, upper and lower bounds (if applicable); for strings,
+ * consider a validation routine.
* (ii) add a record below (or use add_<type>_reloption).
* (iii) add it to the appropriate options struct (perhaps StdRdOptions)
* (iv) add it to the appropriate handling routine (perhaps
@@ -147,15 +147,6 @@ static relopt_bool boolRelOpts[] =
},
false
},
- {
- {
- "vacuum_truncate",
- "Enables vacuum to truncate empty pages at the end of this table",
- RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
- ShareUpdateExclusiveLock
- },
- true
- },
{
{
"deduplicate_items",
@@ -170,6 +161,21 @@ static relopt_bool boolRelOpts[] =
{{NULL}}
};
+static relopt_ternary ternaryRelOpts[] =
+{
+ {
+ {
+ "vacuum_truncate",
+ "Enables vacuum to truncate empty pages at the end of this table",
+ RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
+ ShareUpdateExclusiveLock
+ },
+ TERNARY_UNSET
+ },
+ /* list terminator */
+ {{NULL}}
+};
+
static relopt_int intRelOpts[] =
{
{
@@ -600,6 +606,13 @@ initialize_reloptions(void)
boolRelOpts[i].gen.lockmode));
j++;
}
+ for (i = 0; ternaryRelOpts[i].gen.name; i++)
+ {
+ Assert(DoLockModesConflict(ternaryRelOpts[i].gen.lockmode,
+ ternaryRelOpts[i].gen.lockmode));
+ j++;
+ }
+
for (i = 0; intRelOpts[i].gen.name; i++)
{
Assert(DoLockModesConflict(intRelOpts[i].gen.lockmode,
@@ -640,6 +653,14 @@ initialize_reloptions(void)
j++;
}
+ for (i = 0; ternaryRelOpts[i].gen.name; i++)
+ {
+ relOpts[j] = &ternaryRelOpts[i].gen;
+ relOpts[j]->type = RELOPT_TYPE_TERNARY;
+ relOpts[j]->namelen = strlen(relOpts[j]->name);
+ j++;
+ }
+
for (i = 0; intRelOpts[i].gen.name; i++)
{
relOpts[j] = &intRelOpts[i].gen;
@@ -800,6 +821,9 @@ allocate_reloption(bits32 kinds, int type, const char *name, const char *desc,
case RELOPT_TYPE_BOOL:
size = sizeof(relopt_bool);
break;
+ case RELOPT_TYPE_TERNARY:
+ size = sizeof(relopt_ternary);
+ break;
case RELOPT_TYPE_INT:
size = sizeof(relopt_int);
break;
@@ -883,6 +907,54 @@ add_local_bool_reloption(local_relopts *relopts, const char *name,
add_local_reloption(relopts, (relopt_gen *) newoption, offset);
}
+/*
+ * init_ternary_reloption
+ * Allocate and initialize a new ternary reloption
+ */
+static relopt_ternary *
+init_ternary_reloption(bits32 kinds, const char *name, const char *desc,
+ ternary default_val, LOCKMODE lockmode)
+{
+ relopt_ternary *newoption;
+
+ newoption = (relopt_ternary *) allocate_reloption(kinds,
+ RELOPT_TYPE_TERNARY, name, desc, lockmode);
+ newoption->default_val = default_val;
+
+ return newoption;
+}
+
+/*
+ * add_ternary_reloption
+ * Add a new ternary reloption
+ */
+void
+add_ternary_reloption(bits32 kinds, const char *name, const char *desc,
+ ternary default_val, LOCKMODE lockmode)
+{
+ relopt_ternary *newoption = init_ternary_reloption(kinds, name, desc,
+ default_val, lockmode);
+
+ add_reloption((relopt_gen *) newoption);
+}
+
+/*
+ * add_local_ternary_reloption
+ * Add a new ternary local reloption
+ *
+ * 'offset' is offset of ternary-typed field.
+ */
+void
+add_local_ternary_reloption(local_relopts *relopts, const char *name,
+ const char *desc, ternary default_val,
+ int offset)
+{
+ relopt_ternary *newoption = init_ternary_reloption(RELOPT_KIND_LOCAL,
+ name, desc,
+ default_val, 0);
+
+ add_local_reloption(relopts, (relopt_gen *) newoption, offset);
+}
/*
* init_real_reloption
@@ -1617,6 +1689,18 @@ parse_one_reloption(relopt_value *option, char *text_str, int text_len,
option->gen->name, value)));
}
break;
+ case RELOPT_TYPE_TERNARY:
+ {
+ bool b;
+ parsed = parse_bool(value, &b);
+ option->values.ternary_val = b ? TERNARY_TRUE : TERNARY_FALSE;
+ if (validate && !parsed)
+ ereport(ERROR,
+ (errcode(ERRCODE_INVALID_PARAMETER_VALUE),
+ errmsg("invalid value for ternary option \"%s\": %s",
+ option->gen->name, value)));
+ }
+ break;
case RELOPT_TYPE_INT:
{
relopt_int *optint = (relopt_int *) option->gen;
@@ -1780,17 +1864,6 @@ fillRelOptions(void *rdopts, Size basesize,
char *itempos = ((char *) rdopts) + elems[j].offset;
char *string_val;
- /*
- * If isset_offset is provided, store whether the reloption is
- * set there.
- */
- if (elems[j].isset_offset > 0)
- {
- char *setpos = ((char *) rdopts) + elems[j].isset_offset;
-
- *(bool *) setpos = options[i].isset;
- }
-
switch (options[i].gen->type)
{
case RELOPT_TYPE_BOOL:
@@ -1798,6 +1871,11 @@ fillRelOptions(void *rdopts, Size basesize,
options[i].values.bool_val :
((relopt_bool *) options[i].gen)->default_val;
break;
+ case RELOPT_TYPE_TERNARY:
+ *(ternary *) itempos = options[i].isset ?
+ options[i].values.ternary_val :
+ ((relopt_ternary *) options[i].gen)->default_val;
+ break;
case RELOPT_TYPE_INT:
*(int *) itempos = options[i].isset ?
options[i].values.int_val :
@@ -1912,8 +1990,8 @@ default_reloptions(Datum reloptions, bool validate, relopt_kind kind)
offsetof(StdRdOptions, parallel_workers)},
{"vacuum_index_cleanup", RELOPT_TYPE_ENUM,
offsetof(StdRdOptions, vacuum_index_cleanup)},
- {"vacuum_truncate", RELOPT_TYPE_BOOL,
- offsetof(StdRdOptions, vacuum_truncate), offsetof(StdRdOptions, vacuum_truncate_set)},
+ {"vacuum_truncate", RELOPT_TYPE_TERNARY,
+ offsetof(StdRdOptions, vacuum_truncate)},
{"vacuum_max_eager_freeze_failure_rate", RELOPT_TYPE_REAL,
offsetof(StdRdOptions, vacuum_max_eager_freeze_failure_rate)}
};
@@ -1993,7 +2071,6 @@ build_local_reloptions(local_relopts *relopts, Datum options, bool validate)
elems[i].optname = opt->option->name;
elems[i].opttype = opt->option->type;
elems[i].offset = opt->offset;
- elems[i].isset_offset = 0; /* not supported for local relopts yet */
i++;
}
diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c
index 733ef40ae7..977babff54 100644
--- a/src/backend/commands/vacuum.c
+++ b/src/backend/commands/vacuum.c
@@ -2221,9 +2221,9 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams params,
{
StdRdOptions *opts = (StdRdOptions *) rel->rd_options;
- if (opts && opts->vacuum_truncate_set)
+ if (opts && opts->vacuum_truncate != TERNARY_UNSET)
{
- if (opts->vacuum_truncate)
+ if (opts->vacuum_truncate == TERNARY_TRUE)
params.truncate = VACOPTVALUE_ENABLED;
else
params.truncate = VACOPTVALUE_DISABLED;
diff --git a/src/include/access/reloptions.h b/src/include/access/reloptions.h
index a604a4702c..a436697658 100644
--- a/src/include/access/reloptions.h
+++ b/src/include/access/reloptions.h
@@ -29,6 +29,7 @@
typedef enum relopt_type
{
RELOPT_TYPE_BOOL,
+ RELOPT_TYPE_TERNARY, /* on, off, unset */
RELOPT_TYPE_INT,
RELOPT_TYPE_REAL,
RELOPT_TYPE_ENUM,
@@ -80,6 +81,7 @@ typedef struct relopt_value
union
{
bool bool_val;
+ ternary ternary_val;
int int_val;
double real_val;
int enum_val;
@@ -94,6 +96,12 @@ typedef struct relopt_bool
bool default_val;
} relopt_bool;
+typedef struct relopt_rernary
+{
+ relopt_gen gen;
+ int default_val;
+} relopt_ternary;
+
typedef struct relopt_int
{
relopt_gen gen;
@@ -152,19 +160,6 @@ typedef struct
const char *optname; /* option's name */
relopt_type opttype; /* option's datatype */
int offset; /* offset of field in result struct */
-
- /*
- * isset_offset is an optional offset of a field in the result struct that
- * stores whether the option is explicitly set for the relation or if it
- * just picked up the default value. In most cases, this can be
- * accomplished by giving the reloption a special out-of-range default
- * value (e.g., some integer reloptions use -2), but this isn't always
- * possible. For example, a Boolean reloption cannot be given an
- * out-of-range default, so we need another way to discover the source of
- * its value. This offset is only used if given a value greater than
- * zero.
- */
- int isset_offset;
} relopt_parse_elt;
/* Local reloption definition */
@@ -195,6 +190,8 @@ typedef struct local_relopts
extern relopt_kind add_reloption_kind(void);
extern void add_bool_reloption(bits32 kinds, const char *name, const char *desc,
bool default_val, LOCKMODE lockmode);
+extern void add_ternary_reloption(bits32 kinds, const char *name,
+ const char *desc, int default_val, LOCKMODE lockmode);
extern void add_int_reloption(bits32 kinds, const char *name, const char *desc,
int default_val, int min_val, int max_val,
LOCKMODE lockmode);
@@ -214,6 +211,9 @@ extern void register_reloptions_validator(local_relopts *relopts,
extern void add_local_bool_reloption(local_relopts *relopts, const char *name,
const char *desc, bool default_val,
int offset);
+extern void add_local_ternary_reloption(local_relopts *relopts,
+ const char *name, const char *desc,
+ ternary default_val, int offset);
extern void add_local_int_reloption(local_relopts *relopts, const char *name,
const char *desc, int default_val,
int min_val, int max_val, int offset);
diff --git a/src/include/c.h b/src/include/c.h
index 39022f8a9d..9c59ccccf8 100644
--- a/src/include/c.h
+++ b/src/include/c.h
@@ -488,6 +488,22 @@ typedef void (*pg_funcptr_t) (void);
#include <stdbool.h>
+/*
+ * ternary
+ * Boolean value with an extrea "unset" option
+ *
+ * Ternary data type is used in relation options that can be "true", "false" or
+ * "unset". Since relation options are used deep inside the PostgreSQL code,
+ * this type is declared globally.
+*/
+
+typedef enum ternary
+{
+ TERNARY_FALSE = 0,
+ TERNARY_TRUE = 1,
+ TERNARY_UNSET = -1
+} ternary;
+
/* ----------------------------------------------------------------
* Section 3: standard system types
diff --git a/src/include/utils/rel.h b/src/include/utils/rel.h
index b552359915..7346d618f9 100644
--- a/src/include/utils/rel.h
+++ b/src/include/utils/rel.h
@@ -346,8 +346,7 @@ typedef struct StdRdOptions
bool user_catalog_table; /* use as an additional catalog relation */
int parallel_workers; /* max number of parallel workers */
StdRdOptIndexCleanup vacuum_index_cleanup; /* controls index vacuuming */
- bool vacuum_truncate; /* enables vacuum to truncate a relation */
- bool vacuum_truncate_set; /* whether vacuum_truncate is set */
+ ternary vacuum_truncate; /* enables vacuum to truncate a relation */
/*
* Fraction of pages in a relation that vacuum can eagerly scan and fail
--
2.39.2
--nextPart5129932.5fSG56mABF
Content-Disposition: attachment;
filename="v2-0003-Add-alias-to-be-used-as-unset-state.patch"
Content-Transfer-Encoding: 7Bit
Content-Type: text/x-patch; charset="unicode-2-0-utf-8";
name="v2-0003-Add-alias-to-be-used-as-unset-state.patch"
^ permalink raw reply [nested|flat] 868+ messages in thread
* [PATCH v2a 2/4] Introduce ternary reloptions
@ 2025-09-04 14:41 Nikolay Shaplov <[email protected]>
0 siblings, 0 replies; 868+ messages in thread
From: Nikolay Shaplov @ 2025-09-04 14:41 UTC (permalink / raw)
Introduce ternary reloption as a replacement for current `vacuum_truncate`
implementation. Remove `vacuum_truncate_set` additional flag and using
`TERNARY_UNSET` value instead.
---
src/backend/access/common/reloptions.c | 129 ++++++++++++++++++++-----
src/backend/commands/vacuum.c | 4 +-
src/include/access/reloptions.h | 26 ++---
src/include/c.h | 16 +++
src/include/utils/rel.h | 3 +-
5 files changed, 135 insertions(+), 43 deletions(-)
diff --git a/src/backend/access/common/reloptions.c b/src/backend/access/common/reloptions.c
index 0af3fea68f..f543f11001 100644
--- a/src/backend/access/common/reloptions.c
+++ b/src/backend/access/common/reloptions.c
@@ -40,9 +40,9 @@
*
* To add an option:
*
- * (i) decide on a type (bool, integer, real, enum, string), name, default
- * value, upper and lower bounds (if applicable); for strings, consider a
- * validation routine.
+ * (i) decide on a type (bool, ternary, integer, real, enum, string), name,
+ * default value, upper and lower bounds (if applicable); for strings,
+ * consider a validation routine.
* (ii) add a record below (or use add_<type>_reloption).
* (iii) add it to the appropriate options struct (perhaps StdRdOptions)
* (iv) add it to the appropriate handling routine (perhaps
@@ -147,15 +147,6 @@ static relopt_bool boolRelOpts[] =
},
false
},
- {
- {
- "vacuum_truncate",
- "Enables vacuum to truncate empty pages at the end of this table",
- RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
- ShareUpdateExclusiveLock
- },
- true
- },
{
{
"deduplicate_items",
@@ -170,6 +161,21 @@ static relopt_bool boolRelOpts[] =
{{NULL}}
};
+static relopt_ternary ternaryRelOpts[] =
+{
+ {
+ {
+ "vacuum_truncate",
+ "Enables vacuum to truncate empty pages at the end of this table",
+ RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
+ ShareUpdateExclusiveLock
+ },
+ TERNARY_UNSET
+ },
+ /* list terminator */
+ {{NULL}}
+};
+
static relopt_int intRelOpts[] =
{
{
@@ -600,6 +606,13 @@ initialize_reloptions(void)
boolRelOpts[i].gen.lockmode));
j++;
}
+ for (i = 0; ternaryRelOpts[i].gen.name; i++)
+ {
+ Assert(DoLockModesConflict(ternaryRelOpts[i].gen.lockmode,
+ ternaryRelOpts[i].gen.lockmode));
+ j++;
+ }
+
for (i = 0; intRelOpts[i].gen.name; i++)
{
Assert(DoLockModesConflict(intRelOpts[i].gen.lockmode,
@@ -640,6 +653,14 @@ initialize_reloptions(void)
j++;
}
+ for (i = 0; ternaryRelOpts[i].gen.name; i++)
+ {
+ relOpts[j] = &ternaryRelOpts[i].gen;
+ relOpts[j]->type = RELOPT_TYPE_TERNARY;
+ relOpts[j]->namelen = strlen(relOpts[j]->name);
+ j++;
+ }
+
for (i = 0; intRelOpts[i].gen.name; i++)
{
relOpts[j] = &intRelOpts[i].gen;
@@ -800,6 +821,9 @@ allocate_reloption(bits32 kinds, int type, const char *name, const char *desc,
case RELOPT_TYPE_BOOL:
size = sizeof(relopt_bool);
break;
+ case RELOPT_TYPE_TERNARY:
+ size = sizeof(relopt_ternary);
+ break;
case RELOPT_TYPE_INT:
size = sizeof(relopt_int);
break;
@@ -883,6 +907,54 @@ add_local_bool_reloption(local_relopts *relopts, const char *name,
add_local_reloption(relopts, (relopt_gen *) newoption, offset);
}
+/*
+ * init_ternary_reloption
+ * Allocate and initialize a new ternary reloption
+ */
+static relopt_ternary *
+init_ternary_reloption(bits32 kinds, const char *name, const char *desc,
+ ternary default_val, LOCKMODE lockmode)
+{
+ relopt_ternary *newoption;
+
+ newoption = (relopt_ternary *) allocate_reloption(kinds,
+ RELOPT_TYPE_TERNARY, name, desc, lockmode);
+ newoption->default_val = default_val;
+
+ return newoption;
+}
+
+/*
+ * add_ternary_reloption
+ * Add a new ternary reloption
+ */
+void
+add_ternary_reloption(bits32 kinds, const char *name, const char *desc,
+ ternary default_val, LOCKMODE lockmode)
+{
+ relopt_ternary *newoption = init_ternary_reloption(kinds, name, desc,
+ default_val, lockmode);
+
+ add_reloption((relopt_gen *) newoption);
+}
+
+/*
+ * add_local_ternary_reloption
+ * Add a new ternary local reloption
+ *
+ * 'offset' is offset of ternary-typed field.
+ */
+void
+add_local_ternary_reloption(local_relopts *relopts, const char *name,
+ const char *desc, ternary default_val,
+ int offset)
+{
+ relopt_ternary *newoption = init_ternary_reloption(RELOPT_KIND_LOCAL,
+ name, desc,
+ default_val, 0);
+
+ add_local_reloption(relopts, (relopt_gen *) newoption, offset);
+}
/*
* init_real_reloption
@@ -1617,6 +1689,18 @@ parse_one_reloption(relopt_value *option, char *text_str, int text_len,
option->gen->name, value)));
}
break;
+ case RELOPT_TYPE_TERNARY:
+ {
+ bool b;
+ parsed = parse_bool(value, &b);
+ option->values.ternary_val = b ? TERNARY_TRUE : TERNARY_FALSE;
+ if (validate && !parsed)
+ ereport(ERROR,
+ (errcode(ERRCODE_INVALID_PARAMETER_VALUE),
+ errmsg("invalid value for ternary option \"%s\": %s",
+ option->gen->name, value)));
+ }
+ break;
case RELOPT_TYPE_INT:
{
relopt_int *optint = (relopt_int *) option->gen;
@@ -1780,17 +1864,6 @@ fillRelOptions(void *rdopts, Size basesize,
char *itempos = ((char *) rdopts) + elems[j].offset;
char *string_val;
- /*
- * If isset_offset is provided, store whether the reloption is
- * set there.
- */
- if (elems[j].isset_offset > 0)
- {
- char *setpos = ((char *) rdopts) + elems[j].isset_offset;
-
- *(bool *) setpos = options[i].isset;
- }
-
switch (options[i].gen->type)
{
case RELOPT_TYPE_BOOL:
@@ -1798,6 +1871,11 @@ fillRelOptions(void *rdopts, Size basesize,
options[i].values.bool_val :
((relopt_bool *) options[i].gen)->default_val;
break;
+ case RELOPT_TYPE_TERNARY:
+ *(ternary *) itempos = options[i].isset ?
+ options[i].values.ternary_val :
+ ((relopt_ternary *) options[i].gen)->default_val;
+ break;
case RELOPT_TYPE_INT:
*(int *) itempos = options[i].isset ?
options[i].values.int_val :
@@ -1912,8 +1990,8 @@ default_reloptions(Datum reloptions, bool validate, relopt_kind kind)
offsetof(StdRdOptions, parallel_workers)},
{"vacuum_index_cleanup", RELOPT_TYPE_ENUM,
offsetof(StdRdOptions, vacuum_index_cleanup)},
- {"vacuum_truncate", RELOPT_TYPE_BOOL,
- offsetof(StdRdOptions, vacuum_truncate), offsetof(StdRdOptions, vacuum_truncate_set)},
+ {"vacuum_truncate", RELOPT_TYPE_TERNARY,
+ offsetof(StdRdOptions, vacuum_truncate)},
{"vacuum_max_eager_freeze_failure_rate", RELOPT_TYPE_REAL,
offsetof(StdRdOptions, vacuum_max_eager_freeze_failure_rate)}
};
@@ -1993,7 +2071,6 @@ build_local_reloptions(local_relopts *relopts, Datum options, bool validate)
elems[i].optname = opt->option->name;
elems[i].opttype = opt->option->type;
elems[i].offset = opt->offset;
- elems[i].isset_offset = 0; /* not supported for local relopts yet */
i++;
}
diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c
index 733ef40ae7..977babff54 100644
--- a/src/backend/commands/vacuum.c
+++ b/src/backend/commands/vacuum.c
@@ -2221,9 +2221,9 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams params,
{
StdRdOptions *opts = (StdRdOptions *) rel->rd_options;
- if (opts && opts->vacuum_truncate_set)
+ if (opts && opts->vacuum_truncate != TERNARY_UNSET)
{
- if (opts->vacuum_truncate)
+ if (opts->vacuum_truncate == TERNARY_TRUE)
params.truncate = VACOPTVALUE_ENABLED;
else
params.truncate = VACOPTVALUE_DISABLED;
diff --git a/src/include/access/reloptions.h b/src/include/access/reloptions.h
index a604a4702c..a436697658 100644
--- a/src/include/access/reloptions.h
+++ b/src/include/access/reloptions.h
@@ -29,6 +29,7 @@
typedef enum relopt_type
{
RELOPT_TYPE_BOOL,
+ RELOPT_TYPE_TERNARY, /* on, off, unset */
RELOPT_TYPE_INT,
RELOPT_TYPE_REAL,
RELOPT_TYPE_ENUM,
@@ -80,6 +81,7 @@ typedef struct relopt_value
union
{
bool bool_val;
+ ternary ternary_val;
int int_val;
double real_val;
int enum_val;
@@ -94,6 +96,12 @@ typedef struct relopt_bool
bool default_val;
} relopt_bool;
+typedef struct relopt_rernary
+{
+ relopt_gen gen;
+ int default_val;
+} relopt_ternary;
+
typedef struct relopt_int
{
relopt_gen gen;
@@ -152,19 +160,6 @@ typedef struct
const char *optname; /* option's name */
relopt_type opttype; /* option's datatype */
int offset; /* offset of field in result struct */
-
- /*
- * isset_offset is an optional offset of a field in the result struct that
- * stores whether the option is explicitly set for the relation or if it
- * just picked up the default value. In most cases, this can be
- * accomplished by giving the reloption a special out-of-range default
- * value (e.g., some integer reloptions use -2), but this isn't always
- * possible. For example, a Boolean reloption cannot be given an
- * out-of-range default, so we need another way to discover the source of
- * its value. This offset is only used if given a value greater than
- * zero.
- */
- int isset_offset;
} relopt_parse_elt;
/* Local reloption definition */
@@ -195,6 +190,8 @@ typedef struct local_relopts
extern relopt_kind add_reloption_kind(void);
extern void add_bool_reloption(bits32 kinds, const char *name, const char *desc,
bool default_val, LOCKMODE lockmode);
+extern void add_ternary_reloption(bits32 kinds, const char *name,
+ const char *desc, int default_val, LOCKMODE lockmode);
extern void add_int_reloption(bits32 kinds, const char *name, const char *desc,
int default_val, int min_val, int max_val,
LOCKMODE lockmode);
@@ -214,6 +211,9 @@ extern void register_reloptions_validator(local_relopts *relopts,
extern void add_local_bool_reloption(local_relopts *relopts, const char *name,
const char *desc, bool default_val,
int offset);
+extern void add_local_ternary_reloption(local_relopts *relopts,
+ const char *name, const char *desc,
+ ternary default_val, int offset);
extern void add_local_int_reloption(local_relopts *relopts, const char *name,
const char *desc, int default_val,
int min_val, int max_val, int offset);
diff --git a/src/include/c.h b/src/include/c.h
index 39022f8a9d..9c59ccccf8 100644
--- a/src/include/c.h
+++ b/src/include/c.h
@@ -488,6 +488,22 @@ typedef void (*pg_funcptr_t) (void);
#include <stdbool.h>
+/*
+ * ternary
+ * Boolean value with an extrea "unset" option
+ *
+ * Ternary data type is used in relation options that can be "true", "false" or
+ * "unset". Since relation options are used deep inside the PostgreSQL code,
+ * this type is declared globally.
+*/
+
+typedef enum ternary
+{
+ TERNARY_FALSE = 0,
+ TERNARY_TRUE = 1,
+ TERNARY_UNSET = -1
+} ternary;
+
/* ----------------------------------------------------------------
* Section 3: standard system types
diff --git a/src/include/utils/rel.h b/src/include/utils/rel.h
index b552359915..7346d618f9 100644
--- a/src/include/utils/rel.h
+++ b/src/include/utils/rel.h
@@ -346,8 +346,7 @@ typedef struct StdRdOptions
bool user_catalog_table; /* use as an additional catalog relation */
int parallel_workers; /* max number of parallel workers */
StdRdOptIndexCleanup vacuum_index_cleanup; /* controls index vacuuming */
- bool vacuum_truncate; /* enables vacuum to truncate a relation */
- bool vacuum_truncate_set; /* whether vacuum_truncate is set */
+ ternary vacuum_truncate; /* enables vacuum to truncate a relation */
/*
* Fraction of pages in a relation that vacuum can eagerly scan and fail
--
2.39.2
--nextPart4155600.3ZeAukHxDK
Content-Disposition: attachment;
filename="v2a-0003-Add-alias-to-be-used-as-unset-state.patch"
Content-Transfer-Encoding: 7Bit
Content-Type: text/x-patch; charset="unicode-2-0-utf-8";
name="v2a-0003-Add-alias-to-be-used-as-unset-state.patch"
^ permalink raw reply [nested|flat] 868+ messages in thread
* [PATCH v2 2/4] Introduce ternary reloptions
@ 2025-09-04 14:41 Nikolay Shaplov <[email protected]>
0 siblings, 0 replies; 868+ messages in thread
From: Nikolay Shaplov @ 2025-09-04 14:41 UTC (permalink / raw)
Introduce ternary reloption as a replacement for current `vacuum_truncate`
implementation. Remove `vacuum_truncate_set` additional flag and using
`TERNARY_UNSET` value instead.
---
src/backend/access/common/reloptions.c | 129 ++++++++++++++++++++-----
src/backend/commands/vacuum.c | 4 +-
src/include/access/reloptions.h | 26 ++---
src/include/c.h | 16 +++
src/include/utils/rel.h | 3 +-
5 files changed, 135 insertions(+), 43 deletions(-)
diff --git a/src/backend/access/common/reloptions.c b/src/backend/access/common/reloptions.c
index 0af3fea68f..f543f11001 100644
--- a/src/backend/access/common/reloptions.c
+++ b/src/backend/access/common/reloptions.c
@@ -40,9 +40,9 @@
*
* To add an option:
*
- * (i) decide on a type (bool, integer, real, enum, string), name, default
- * value, upper and lower bounds (if applicable); for strings, consider a
- * validation routine.
+ * (i) decide on a type (bool, ternary, integer, real, enum, string), name,
+ * default value, upper and lower bounds (if applicable); for strings,
+ * consider a validation routine.
* (ii) add a record below (or use add_<type>_reloption).
* (iii) add it to the appropriate options struct (perhaps StdRdOptions)
* (iv) add it to the appropriate handling routine (perhaps
@@ -147,15 +147,6 @@ static relopt_bool boolRelOpts[] =
},
false
},
- {
- {
- "vacuum_truncate",
- "Enables vacuum to truncate empty pages at the end of this table",
- RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
- ShareUpdateExclusiveLock
- },
- true
- },
{
{
"deduplicate_items",
@@ -170,6 +161,21 @@ static relopt_bool boolRelOpts[] =
{{NULL}}
};
+static relopt_ternary ternaryRelOpts[] =
+{
+ {
+ {
+ "vacuum_truncate",
+ "Enables vacuum to truncate empty pages at the end of this table",
+ RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
+ ShareUpdateExclusiveLock
+ },
+ TERNARY_UNSET
+ },
+ /* list terminator */
+ {{NULL}}
+};
+
static relopt_int intRelOpts[] =
{
{
@@ -600,6 +606,13 @@ initialize_reloptions(void)
boolRelOpts[i].gen.lockmode));
j++;
}
+ for (i = 0; ternaryRelOpts[i].gen.name; i++)
+ {
+ Assert(DoLockModesConflict(ternaryRelOpts[i].gen.lockmode,
+ ternaryRelOpts[i].gen.lockmode));
+ j++;
+ }
+
for (i = 0; intRelOpts[i].gen.name; i++)
{
Assert(DoLockModesConflict(intRelOpts[i].gen.lockmode,
@@ -640,6 +653,14 @@ initialize_reloptions(void)
j++;
}
+ for (i = 0; ternaryRelOpts[i].gen.name; i++)
+ {
+ relOpts[j] = &ternaryRelOpts[i].gen;
+ relOpts[j]->type = RELOPT_TYPE_TERNARY;
+ relOpts[j]->namelen = strlen(relOpts[j]->name);
+ j++;
+ }
+
for (i = 0; intRelOpts[i].gen.name; i++)
{
relOpts[j] = &intRelOpts[i].gen;
@@ -800,6 +821,9 @@ allocate_reloption(bits32 kinds, int type, const char *name, const char *desc,
case RELOPT_TYPE_BOOL:
size = sizeof(relopt_bool);
break;
+ case RELOPT_TYPE_TERNARY:
+ size = sizeof(relopt_ternary);
+ break;
case RELOPT_TYPE_INT:
size = sizeof(relopt_int);
break;
@@ -883,6 +907,54 @@ add_local_bool_reloption(local_relopts *relopts, const char *name,
add_local_reloption(relopts, (relopt_gen *) newoption, offset);
}
+/*
+ * init_ternary_reloption
+ * Allocate and initialize a new ternary reloption
+ */
+static relopt_ternary *
+init_ternary_reloption(bits32 kinds, const char *name, const char *desc,
+ ternary default_val, LOCKMODE lockmode)
+{
+ relopt_ternary *newoption;
+
+ newoption = (relopt_ternary *) allocate_reloption(kinds,
+ RELOPT_TYPE_TERNARY, name, desc, lockmode);
+ newoption->default_val = default_val;
+
+ return newoption;
+}
+
+/*
+ * add_ternary_reloption
+ * Add a new ternary reloption
+ */
+void
+add_ternary_reloption(bits32 kinds, const char *name, const char *desc,
+ ternary default_val, LOCKMODE lockmode)
+{
+ relopt_ternary *newoption = init_ternary_reloption(kinds, name, desc,
+ default_val, lockmode);
+
+ add_reloption((relopt_gen *) newoption);
+}
+
+/*
+ * add_local_ternary_reloption
+ * Add a new ternary local reloption
+ *
+ * 'offset' is offset of ternary-typed field.
+ */
+void
+add_local_ternary_reloption(local_relopts *relopts, const char *name,
+ const char *desc, ternary default_val,
+ int offset)
+{
+ relopt_ternary *newoption = init_ternary_reloption(RELOPT_KIND_LOCAL,
+ name, desc,
+ default_val, 0);
+
+ add_local_reloption(relopts, (relopt_gen *) newoption, offset);
+}
/*
* init_real_reloption
@@ -1617,6 +1689,18 @@ parse_one_reloption(relopt_value *option, char *text_str, int text_len,
option->gen->name, value)));
}
break;
+ case RELOPT_TYPE_TERNARY:
+ {
+ bool b;
+ parsed = parse_bool(value, &b);
+ option->values.ternary_val = b ? TERNARY_TRUE : TERNARY_FALSE;
+ if (validate && !parsed)
+ ereport(ERROR,
+ (errcode(ERRCODE_INVALID_PARAMETER_VALUE),
+ errmsg("invalid value for ternary option \"%s\": %s",
+ option->gen->name, value)));
+ }
+ break;
case RELOPT_TYPE_INT:
{
relopt_int *optint = (relopt_int *) option->gen;
@@ -1780,17 +1864,6 @@ fillRelOptions(void *rdopts, Size basesize,
char *itempos = ((char *) rdopts) + elems[j].offset;
char *string_val;
- /*
- * If isset_offset is provided, store whether the reloption is
- * set there.
- */
- if (elems[j].isset_offset > 0)
- {
- char *setpos = ((char *) rdopts) + elems[j].isset_offset;
-
- *(bool *) setpos = options[i].isset;
- }
-
switch (options[i].gen->type)
{
case RELOPT_TYPE_BOOL:
@@ -1798,6 +1871,11 @@ fillRelOptions(void *rdopts, Size basesize,
options[i].values.bool_val :
((relopt_bool *) options[i].gen)->default_val;
break;
+ case RELOPT_TYPE_TERNARY:
+ *(ternary *) itempos = options[i].isset ?
+ options[i].values.ternary_val :
+ ((relopt_ternary *) options[i].gen)->default_val;
+ break;
case RELOPT_TYPE_INT:
*(int *) itempos = options[i].isset ?
options[i].values.int_val :
@@ -1912,8 +1990,8 @@ default_reloptions(Datum reloptions, bool validate, relopt_kind kind)
offsetof(StdRdOptions, parallel_workers)},
{"vacuum_index_cleanup", RELOPT_TYPE_ENUM,
offsetof(StdRdOptions, vacuum_index_cleanup)},
- {"vacuum_truncate", RELOPT_TYPE_BOOL,
- offsetof(StdRdOptions, vacuum_truncate), offsetof(StdRdOptions, vacuum_truncate_set)},
+ {"vacuum_truncate", RELOPT_TYPE_TERNARY,
+ offsetof(StdRdOptions, vacuum_truncate)},
{"vacuum_max_eager_freeze_failure_rate", RELOPT_TYPE_REAL,
offsetof(StdRdOptions, vacuum_max_eager_freeze_failure_rate)}
};
@@ -1993,7 +2071,6 @@ build_local_reloptions(local_relopts *relopts, Datum options, bool validate)
elems[i].optname = opt->option->name;
elems[i].opttype = opt->option->type;
elems[i].offset = opt->offset;
- elems[i].isset_offset = 0; /* not supported for local relopts yet */
i++;
}
diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c
index 733ef40ae7..977babff54 100644
--- a/src/backend/commands/vacuum.c
+++ b/src/backend/commands/vacuum.c
@@ -2221,9 +2221,9 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams params,
{
StdRdOptions *opts = (StdRdOptions *) rel->rd_options;
- if (opts && opts->vacuum_truncate_set)
+ if (opts && opts->vacuum_truncate != TERNARY_UNSET)
{
- if (opts->vacuum_truncate)
+ if (opts->vacuum_truncate == TERNARY_TRUE)
params.truncate = VACOPTVALUE_ENABLED;
else
params.truncate = VACOPTVALUE_DISABLED;
diff --git a/src/include/access/reloptions.h b/src/include/access/reloptions.h
index a604a4702c..a436697658 100644
--- a/src/include/access/reloptions.h
+++ b/src/include/access/reloptions.h
@@ -29,6 +29,7 @@
typedef enum relopt_type
{
RELOPT_TYPE_BOOL,
+ RELOPT_TYPE_TERNARY, /* on, off, unset */
RELOPT_TYPE_INT,
RELOPT_TYPE_REAL,
RELOPT_TYPE_ENUM,
@@ -80,6 +81,7 @@ typedef struct relopt_value
union
{
bool bool_val;
+ ternary ternary_val;
int int_val;
double real_val;
int enum_val;
@@ -94,6 +96,12 @@ typedef struct relopt_bool
bool default_val;
} relopt_bool;
+typedef struct relopt_rernary
+{
+ relopt_gen gen;
+ int default_val;
+} relopt_ternary;
+
typedef struct relopt_int
{
relopt_gen gen;
@@ -152,19 +160,6 @@ typedef struct
const char *optname; /* option's name */
relopt_type opttype; /* option's datatype */
int offset; /* offset of field in result struct */
-
- /*
- * isset_offset is an optional offset of a field in the result struct that
- * stores whether the option is explicitly set for the relation or if it
- * just picked up the default value. In most cases, this can be
- * accomplished by giving the reloption a special out-of-range default
- * value (e.g., some integer reloptions use -2), but this isn't always
- * possible. For example, a Boolean reloption cannot be given an
- * out-of-range default, so we need another way to discover the source of
- * its value. This offset is only used if given a value greater than
- * zero.
- */
- int isset_offset;
} relopt_parse_elt;
/* Local reloption definition */
@@ -195,6 +190,8 @@ typedef struct local_relopts
extern relopt_kind add_reloption_kind(void);
extern void add_bool_reloption(bits32 kinds, const char *name, const char *desc,
bool default_val, LOCKMODE lockmode);
+extern void add_ternary_reloption(bits32 kinds, const char *name,
+ const char *desc, int default_val, LOCKMODE lockmode);
extern void add_int_reloption(bits32 kinds, const char *name, const char *desc,
int default_val, int min_val, int max_val,
LOCKMODE lockmode);
@@ -214,6 +211,9 @@ extern void register_reloptions_validator(local_relopts *relopts,
extern void add_local_bool_reloption(local_relopts *relopts, const char *name,
const char *desc, bool default_val,
int offset);
+extern void add_local_ternary_reloption(local_relopts *relopts,
+ const char *name, const char *desc,
+ ternary default_val, int offset);
extern void add_local_int_reloption(local_relopts *relopts, const char *name,
const char *desc, int default_val,
int min_val, int max_val, int offset);
diff --git a/src/include/c.h b/src/include/c.h
index 39022f8a9d..9c59ccccf8 100644
--- a/src/include/c.h
+++ b/src/include/c.h
@@ -488,6 +488,22 @@ typedef void (*pg_funcptr_t) (void);
#include <stdbool.h>
+/*
+ * ternary
+ * Boolean value with an extrea "unset" option
+ *
+ * Ternary data type is used in relation options that can be "true", "false" or
+ * "unset". Since relation options are used deep inside the PostgreSQL code,
+ * this type is declared globally.
+*/
+
+typedef enum ternary
+{
+ TERNARY_FALSE = 0,
+ TERNARY_TRUE = 1,
+ TERNARY_UNSET = -1
+} ternary;
+
/* ----------------------------------------------------------------
* Section 3: standard system types
diff --git a/src/include/utils/rel.h b/src/include/utils/rel.h
index b552359915..7346d618f9 100644
--- a/src/include/utils/rel.h
+++ b/src/include/utils/rel.h
@@ -346,8 +346,7 @@ typedef struct StdRdOptions
bool user_catalog_table; /* use as an additional catalog relation */
int parallel_workers; /* max number of parallel workers */
StdRdOptIndexCleanup vacuum_index_cleanup; /* controls index vacuuming */
- bool vacuum_truncate; /* enables vacuum to truncate a relation */
- bool vacuum_truncate_set; /* whether vacuum_truncate is set */
+ ternary vacuum_truncate; /* enables vacuum to truncate a relation */
/*
* Fraction of pages in a relation that vacuum can eagerly scan and fail
--
2.39.2
--nextPart5129932.5fSG56mABF
Content-Disposition: attachment;
filename="v2-0003-Add-alias-to-be-used-as-unset-state.patch"
Content-Transfer-Encoding: 7Bit
Content-Type: text/x-patch; charset="unicode-2-0-utf-8";
name="v2-0003-Add-alias-to-be-used-as-unset-state.patch"
^ permalink raw reply [nested|flat] 868+ messages in thread
* [PATCH v2 2/4] Introduce ternary reloptions
@ 2025-09-04 14:41 Nikolay Shaplov <[email protected]>
0 siblings, 0 replies; 868+ messages in thread
From: Nikolay Shaplov @ 2025-09-04 14:41 UTC (permalink / raw)
Introduce ternary reloption as a replacement for current `vacuum_truncate`
implementation. Remove `vacuum_truncate_set` additional flag and using
`TERNARY_UNSET` value instead.
---
src/backend/access/common/reloptions.c | 129 ++++++++++++++++++++-----
src/backend/commands/vacuum.c | 4 +-
src/include/access/reloptions.h | 26 ++---
src/include/c.h | 16 +++
src/include/utils/rel.h | 3 +-
5 files changed, 135 insertions(+), 43 deletions(-)
diff --git a/src/backend/access/common/reloptions.c b/src/backend/access/common/reloptions.c
index 0af3fea68f..f543f11001 100644
--- a/src/backend/access/common/reloptions.c
+++ b/src/backend/access/common/reloptions.c
@@ -40,9 +40,9 @@
*
* To add an option:
*
- * (i) decide on a type (bool, integer, real, enum, string), name, default
- * value, upper and lower bounds (if applicable); for strings, consider a
- * validation routine.
+ * (i) decide on a type (bool, ternary, integer, real, enum, string), name,
+ * default value, upper and lower bounds (if applicable); for strings,
+ * consider a validation routine.
* (ii) add a record below (or use add_<type>_reloption).
* (iii) add it to the appropriate options struct (perhaps StdRdOptions)
* (iv) add it to the appropriate handling routine (perhaps
@@ -147,15 +147,6 @@ static relopt_bool boolRelOpts[] =
},
false
},
- {
- {
- "vacuum_truncate",
- "Enables vacuum to truncate empty pages at the end of this table",
- RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
- ShareUpdateExclusiveLock
- },
- true
- },
{
{
"deduplicate_items",
@@ -170,6 +161,21 @@ static relopt_bool boolRelOpts[] =
{{NULL}}
};
+static relopt_ternary ternaryRelOpts[] =
+{
+ {
+ {
+ "vacuum_truncate",
+ "Enables vacuum to truncate empty pages at the end of this table",
+ RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
+ ShareUpdateExclusiveLock
+ },
+ TERNARY_UNSET
+ },
+ /* list terminator */
+ {{NULL}}
+};
+
static relopt_int intRelOpts[] =
{
{
@@ -600,6 +606,13 @@ initialize_reloptions(void)
boolRelOpts[i].gen.lockmode));
j++;
}
+ for (i = 0; ternaryRelOpts[i].gen.name; i++)
+ {
+ Assert(DoLockModesConflict(ternaryRelOpts[i].gen.lockmode,
+ ternaryRelOpts[i].gen.lockmode));
+ j++;
+ }
+
for (i = 0; intRelOpts[i].gen.name; i++)
{
Assert(DoLockModesConflict(intRelOpts[i].gen.lockmode,
@@ -640,6 +653,14 @@ initialize_reloptions(void)
j++;
}
+ for (i = 0; ternaryRelOpts[i].gen.name; i++)
+ {
+ relOpts[j] = &ternaryRelOpts[i].gen;
+ relOpts[j]->type = RELOPT_TYPE_TERNARY;
+ relOpts[j]->namelen = strlen(relOpts[j]->name);
+ j++;
+ }
+
for (i = 0; intRelOpts[i].gen.name; i++)
{
relOpts[j] = &intRelOpts[i].gen;
@@ -800,6 +821,9 @@ allocate_reloption(bits32 kinds, int type, const char *name, const char *desc,
case RELOPT_TYPE_BOOL:
size = sizeof(relopt_bool);
break;
+ case RELOPT_TYPE_TERNARY:
+ size = sizeof(relopt_ternary);
+ break;
case RELOPT_TYPE_INT:
size = sizeof(relopt_int);
break;
@@ -883,6 +907,54 @@ add_local_bool_reloption(local_relopts *relopts, const char *name,
add_local_reloption(relopts, (relopt_gen *) newoption, offset);
}
+/*
+ * init_ternary_reloption
+ * Allocate and initialize a new ternary reloption
+ */
+static relopt_ternary *
+init_ternary_reloption(bits32 kinds, const char *name, const char *desc,
+ ternary default_val, LOCKMODE lockmode)
+{
+ relopt_ternary *newoption;
+
+ newoption = (relopt_ternary *) allocate_reloption(kinds,
+ RELOPT_TYPE_TERNARY, name, desc, lockmode);
+ newoption->default_val = default_val;
+
+ return newoption;
+}
+
+/*
+ * add_ternary_reloption
+ * Add a new ternary reloption
+ */
+void
+add_ternary_reloption(bits32 kinds, const char *name, const char *desc,
+ ternary default_val, LOCKMODE lockmode)
+{
+ relopt_ternary *newoption = init_ternary_reloption(kinds, name, desc,
+ default_val, lockmode);
+
+ add_reloption((relopt_gen *) newoption);
+}
+
+/*
+ * add_local_ternary_reloption
+ * Add a new ternary local reloption
+ *
+ * 'offset' is offset of ternary-typed field.
+ */
+void
+add_local_ternary_reloption(local_relopts *relopts, const char *name,
+ const char *desc, ternary default_val,
+ int offset)
+{
+ relopt_ternary *newoption = init_ternary_reloption(RELOPT_KIND_LOCAL,
+ name, desc,
+ default_val, 0);
+
+ add_local_reloption(relopts, (relopt_gen *) newoption, offset);
+}
/*
* init_real_reloption
@@ -1617,6 +1689,18 @@ parse_one_reloption(relopt_value *option, char *text_str, int text_len,
option->gen->name, value)));
}
break;
+ case RELOPT_TYPE_TERNARY:
+ {
+ bool b;
+ parsed = parse_bool(value, &b);
+ option->values.ternary_val = b ? TERNARY_TRUE : TERNARY_FALSE;
+ if (validate && !parsed)
+ ereport(ERROR,
+ (errcode(ERRCODE_INVALID_PARAMETER_VALUE),
+ errmsg("invalid value for ternary option \"%s\": %s",
+ option->gen->name, value)));
+ }
+ break;
case RELOPT_TYPE_INT:
{
relopt_int *optint = (relopt_int *) option->gen;
@@ -1780,17 +1864,6 @@ fillRelOptions(void *rdopts, Size basesize,
char *itempos = ((char *) rdopts) + elems[j].offset;
char *string_val;
- /*
- * If isset_offset is provided, store whether the reloption is
- * set there.
- */
- if (elems[j].isset_offset > 0)
- {
- char *setpos = ((char *) rdopts) + elems[j].isset_offset;
-
- *(bool *) setpos = options[i].isset;
- }
-
switch (options[i].gen->type)
{
case RELOPT_TYPE_BOOL:
@@ -1798,6 +1871,11 @@ fillRelOptions(void *rdopts, Size basesize,
options[i].values.bool_val :
((relopt_bool *) options[i].gen)->default_val;
break;
+ case RELOPT_TYPE_TERNARY:
+ *(ternary *) itempos = options[i].isset ?
+ options[i].values.ternary_val :
+ ((relopt_ternary *) options[i].gen)->default_val;
+ break;
case RELOPT_TYPE_INT:
*(int *) itempos = options[i].isset ?
options[i].values.int_val :
@@ -1912,8 +1990,8 @@ default_reloptions(Datum reloptions, bool validate, relopt_kind kind)
offsetof(StdRdOptions, parallel_workers)},
{"vacuum_index_cleanup", RELOPT_TYPE_ENUM,
offsetof(StdRdOptions, vacuum_index_cleanup)},
- {"vacuum_truncate", RELOPT_TYPE_BOOL,
- offsetof(StdRdOptions, vacuum_truncate), offsetof(StdRdOptions, vacuum_truncate_set)},
+ {"vacuum_truncate", RELOPT_TYPE_TERNARY,
+ offsetof(StdRdOptions, vacuum_truncate)},
{"vacuum_max_eager_freeze_failure_rate", RELOPT_TYPE_REAL,
offsetof(StdRdOptions, vacuum_max_eager_freeze_failure_rate)}
};
@@ -1993,7 +2071,6 @@ build_local_reloptions(local_relopts *relopts, Datum options, bool validate)
elems[i].optname = opt->option->name;
elems[i].opttype = opt->option->type;
elems[i].offset = opt->offset;
- elems[i].isset_offset = 0; /* not supported for local relopts yet */
i++;
}
diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c
index 733ef40ae7..977babff54 100644
--- a/src/backend/commands/vacuum.c
+++ b/src/backend/commands/vacuum.c
@@ -2221,9 +2221,9 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams params,
{
StdRdOptions *opts = (StdRdOptions *) rel->rd_options;
- if (opts && opts->vacuum_truncate_set)
+ if (opts && opts->vacuum_truncate != TERNARY_UNSET)
{
- if (opts->vacuum_truncate)
+ if (opts->vacuum_truncate == TERNARY_TRUE)
params.truncate = VACOPTVALUE_ENABLED;
else
params.truncate = VACOPTVALUE_DISABLED;
diff --git a/src/include/access/reloptions.h b/src/include/access/reloptions.h
index a604a4702c..a436697658 100644
--- a/src/include/access/reloptions.h
+++ b/src/include/access/reloptions.h
@@ -29,6 +29,7 @@
typedef enum relopt_type
{
RELOPT_TYPE_BOOL,
+ RELOPT_TYPE_TERNARY, /* on, off, unset */
RELOPT_TYPE_INT,
RELOPT_TYPE_REAL,
RELOPT_TYPE_ENUM,
@@ -80,6 +81,7 @@ typedef struct relopt_value
union
{
bool bool_val;
+ ternary ternary_val;
int int_val;
double real_val;
int enum_val;
@@ -94,6 +96,12 @@ typedef struct relopt_bool
bool default_val;
} relopt_bool;
+typedef struct relopt_rernary
+{
+ relopt_gen gen;
+ int default_val;
+} relopt_ternary;
+
typedef struct relopt_int
{
relopt_gen gen;
@@ -152,19 +160,6 @@ typedef struct
const char *optname; /* option's name */
relopt_type opttype; /* option's datatype */
int offset; /* offset of field in result struct */
-
- /*
- * isset_offset is an optional offset of a field in the result struct that
- * stores whether the option is explicitly set for the relation or if it
- * just picked up the default value. In most cases, this can be
- * accomplished by giving the reloption a special out-of-range default
- * value (e.g., some integer reloptions use -2), but this isn't always
- * possible. For example, a Boolean reloption cannot be given an
- * out-of-range default, so we need another way to discover the source of
- * its value. This offset is only used if given a value greater than
- * zero.
- */
- int isset_offset;
} relopt_parse_elt;
/* Local reloption definition */
@@ -195,6 +190,8 @@ typedef struct local_relopts
extern relopt_kind add_reloption_kind(void);
extern void add_bool_reloption(bits32 kinds, const char *name, const char *desc,
bool default_val, LOCKMODE lockmode);
+extern void add_ternary_reloption(bits32 kinds, const char *name,
+ const char *desc, int default_val, LOCKMODE lockmode);
extern void add_int_reloption(bits32 kinds, const char *name, const char *desc,
int default_val, int min_val, int max_val,
LOCKMODE lockmode);
@@ -214,6 +211,9 @@ extern void register_reloptions_validator(local_relopts *relopts,
extern void add_local_bool_reloption(local_relopts *relopts, const char *name,
const char *desc, bool default_val,
int offset);
+extern void add_local_ternary_reloption(local_relopts *relopts,
+ const char *name, const char *desc,
+ ternary default_val, int offset);
extern void add_local_int_reloption(local_relopts *relopts, const char *name,
const char *desc, int default_val,
int min_val, int max_val, int offset);
diff --git a/src/include/c.h b/src/include/c.h
index 39022f8a9d..9c59ccccf8 100644
--- a/src/include/c.h
+++ b/src/include/c.h
@@ -488,6 +488,22 @@ typedef void (*pg_funcptr_t) (void);
#include <stdbool.h>
+/*
+ * ternary
+ * Boolean value with an extrea "unset" option
+ *
+ * Ternary data type is used in relation options that can be "true", "false" or
+ * "unset". Since relation options are used deep inside the PostgreSQL code,
+ * this type is declared globally.
+*/
+
+typedef enum ternary
+{
+ TERNARY_FALSE = 0,
+ TERNARY_TRUE = 1,
+ TERNARY_UNSET = -1
+} ternary;
+
/* ----------------------------------------------------------------
* Section 3: standard system types
diff --git a/src/include/utils/rel.h b/src/include/utils/rel.h
index b552359915..7346d618f9 100644
--- a/src/include/utils/rel.h
+++ b/src/include/utils/rel.h
@@ -346,8 +346,7 @@ typedef struct StdRdOptions
bool user_catalog_table; /* use as an additional catalog relation */
int parallel_workers; /* max number of parallel workers */
StdRdOptIndexCleanup vacuum_index_cleanup; /* controls index vacuuming */
- bool vacuum_truncate; /* enables vacuum to truncate a relation */
- bool vacuum_truncate_set; /* whether vacuum_truncate is set */
+ ternary vacuum_truncate; /* enables vacuum to truncate a relation */
/*
* Fraction of pages in a relation that vacuum can eagerly scan and fail
--
2.39.2
--nextPart5129932.5fSG56mABF
Content-Disposition: attachment;
filename="v2-0003-Add-alias-to-be-used-as-unset-state.patch"
Content-Transfer-Encoding: 7Bit
Content-Type: text/x-patch; charset="unicode-2-0-utf-8";
name="v2-0003-Add-alias-to-be-used-as-unset-state.patch"
^ permalink raw reply [nested|flat] 868+ messages in thread
* [PATCH v2a 2/4] Introduce ternary reloptions
@ 2025-09-04 14:41 Nikolay Shaplov <[email protected]>
0 siblings, 0 replies; 868+ messages in thread
From: Nikolay Shaplov @ 2025-09-04 14:41 UTC (permalink / raw)
Introduce ternary reloption as a replacement for current `vacuum_truncate`
implementation. Remove `vacuum_truncate_set` additional flag and using
`TERNARY_UNSET` value instead.
---
src/backend/access/common/reloptions.c | 129 ++++++++++++++++++++-----
src/backend/commands/vacuum.c | 4 +-
src/include/access/reloptions.h | 26 ++---
src/include/c.h | 16 +++
src/include/utils/rel.h | 3 +-
5 files changed, 135 insertions(+), 43 deletions(-)
diff --git a/src/backend/access/common/reloptions.c b/src/backend/access/common/reloptions.c
index 0af3fea68f..f543f11001 100644
--- a/src/backend/access/common/reloptions.c
+++ b/src/backend/access/common/reloptions.c
@@ -40,9 +40,9 @@
*
* To add an option:
*
- * (i) decide on a type (bool, integer, real, enum, string), name, default
- * value, upper and lower bounds (if applicable); for strings, consider a
- * validation routine.
+ * (i) decide on a type (bool, ternary, integer, real, enum, string), name,
+ * default value, upper and lower bounds (if applicable); for strings,
+ * consider a validation routine.
* (ii) add a record below (or use add_<type>_reloption).
* (iii) add it to the appropriate options struct (perhaps StdRdOptions)
* (iv) add it to the appropriate handling routine (perhaps
@@ -147,15 +147,6 @@ static relopt_bool boolRelOpts[] =
},
false
},
- {
- {
- "vacuum_truncate",
- "Enables vacuum to truncate empty pages at the end of this table",
- RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
- ShareUpdateExclusiveLock
- },
- true
- },
{
{
"deduplicate_items",
@@ -170,6 +161,21 @@ static relopt_bool boolRelOpts[] =
{{NULL}}
};
+static relopt_ternary ternaryRelOpts[] =
+{
+ {
+ {
+ "vacuum_truncate",
+ "Enables vacuum to truncate empty pages at the end of this table",
+ RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
+ ShareUpdateExclusiveLock
+ },
+ TERNARY_UNSET
+ },
+ /* list terminator */
+ {{NULL}}
+};
+
static relopt_int intRelOpts[] =
{
{
@@ -600,6 +606,13 @@ initialize_reloptions(void)
boolRelOpts[i].gen.lockmode));
j++;
}
+ for (i = 0; ternaryRelOpts[i].gen.name; i++)
+ {
+ Assert(DoLockModesConflict(ternaryRelOpts[i].gen.lockmode,
+ ternaryRelOpts[i].gen.lockmode));
+ j++;
+ }
+
for (i = 0; intRelOpts[i].gen.name; i++)
{
Assert(DoLockModesConflict(intRelOpts[i].gen.lockmode,
@@ -640,6 +653,14 @@ initialize_reloptions(void)
j++;
}
+ for (i = 0; ternaryRelOpts[i].gen.name; i++)
+ {
+ relOpts[j] = &ternaryRelOpts[i].gen;
+ relOpts[j]->type = RELOPT_TYPE_TERNARY;
+ relOpts[j]->namelen = strlen(relOpts[j]->name);
+ j++;
+ }
+
for (i = 0; intRelOpts[i].gen.name; i++)
{
relOpts[j] = &intRelOpts[i].gen;
@@ -800,6 +821,9 @@ allocate_reloption(bits32 kinds, int type, const char *name, const char *desc,
case RELOPT_TYPE_BOOL:
size = sizeof(relopt_bool);
break;
+ case RELOPT_TYPE_TERNARY:
+ size = sizeof(relopt_ternary);
+ break;
case RELOPT_TYPE_INT:
size = sizeof(relopt_int);
break;
@@ -883,6 +907,54 @@ add_local_bool_reloption(local_relopts *relopts, const char *name,
add_local_reloption(relopts, (relopt_gen *) newoption, offset);
}
+/*
+ * init_ternary_reloption
+ * Allocate and initialize a new ternary reloption
+ */
+static relopt_ternary *
+init_ternary_reloption(bits32 kinds, const char *name, const char *desc,
+ ternary default_val, LOCKMODE lockmode)
+{
+ relopt_ternary *newoption;
+
+ newoption = (relopt_ternary *) allocate_reloption(kinds,
+ RELOPT_TYPE_TERNARY, name, desc, lockmode);
+ newoption->default_val = default_val;
+
+ return newoption;
+}
+
+/*
+ * add_ternary_reloption
+ * Add a new ternary reloption
+ */
+void
+add_ternary_reloption(bits32 kinds, const char *name, const char *desc,
+ ternary default_val, LOCKMODE lockmode)
+{
+ relopt_ternary *newoption = init_ternary_reloption(kinds, name, desc,
+ default_val, lockmode);
+
+ add_reloption((relopt_gen *) newoption);
+}
+
+/*
+ * add_local_ternary_reloption
+ * Add a new ternary local reloption
+ *
+ * 'offset' is offset of ternary-typed field.
+ */
+void
+add_local_ternary_reloption(local_relopts *relopts, const char *name,
+ const char *desc, ternary default_val,
+ int offset)
+{
+ relopt_ternary *newoption = init_ternary_reloption(RELOPT_KIND_LOCAL,
+ name, desc,
+ default_val, 0);
+
+ add_local_reloption(relopts, (relopt_gen *) newoption, offset);
+}
/*
* init_real_reloption
@@ -1617,6 +1689,18 @@ parse_one_reloption(relopt_value *option, char *text_str, int text_len,
option->gen->name, value)));
}
break;
+ case RELOPT_TYPE_TERNARY:
+ {
+ bool b;
+ parsed = parse_bool(value, &b);
+ option->values.ternary_val = b ? TERNARY_TRUE : TERNARY_FALSE;
+ if (validate && !parsed)
+ ereport(ERROR,
+ (errcode(ERRCODE_INVALID_PARAMETER_VALUE),
+ errmsg("invalid value for ternary option \"%s\": %s",
+ option->gen->name, value)));
+ }
+ break;
case RELOPT_TYPE_INT:
{
relopt_int *optint = (relopt_int *) option->gen;
@@ -1780,17 +1864,6 @@ fillRelOptions(void *rdopts, Size basesize,
char *itempos = ((char *) rdopts) + elems[j].offset;
char *string_val;
- /*
- * If isset_offset is provided, store whether the reloption is
- * set there.
- */
- if (elems[j].isset_offset > 0)
- {
- char *setpos = ((char *) rdopts) + elems[j].isset_offset;
-
- *(bool *) setpos = options[i].isset;
- }
-
switch (options[i].gen->type)
{
case RELOPT_TYPE_BOOL:
@@ -1798,6 +1871,11 @@ fillRelOptions(void *rdopts, Size basesize,
options[i].values.bool_val :
((relopt_bool *) options[i].gen)->default_val;
break;
+ case RELOPT_TYPE_TERNARY:
+ *(ternary *) itempos = options[i].isset ?
+ options[i].values.ternary_val :
+ ((relopt_ternary *) options[i].gen)->default_val;
+ break;
case RELOPT_TYPE_INT:
*(int *) itempos = options[i].isset ?
options[i].values.int_val :
@@ -1912,8 +1990,8 @@ default_reloptions(Datum reloptions, bool validate, relopt_kind kind)
offsetof(StdRdOptions, parallel_workers)},
{"vacuum_index_cleanup", RELOPT_TYPE_ENUM,
offsetof(StdRdOptions, vacuum_index_cleanup)},
- {"vacuum_truncate", RELOPT_TYPE_BOOL,
- offsetof(StdRdOptions, vacuum_truncate), offsetof(StdRdOptions, vacuum_truncate_set)},
+ {"vacuum_truncate", RELOPT_TYPE_TERNARY,
+ offsetof(StdRdOptions, vacuum_truncate)},
{"vacuum_max_eager_freeze_failure_rate", RELOPT_TYPE_REAL,
offsetof(StdRdOptions, vacuum_max_eager_freeze_failure_rate)}
};
@@ -1993,7 +2071,6 @@ build_local_reloptions(local_relopts *relopts, Datum options, bool validate)
elems[i].optname = opt->option->name;
elems[i].opttype = opt->option->type;
elems[i].offset = opt->offset;
- elems[i].isset_offset = 0; /* not supported for local relopts yet */
i++;
}
diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c
index 733ef40ae7..977babff54 100644
--- a/src/backend/commands/vacuum.c
+++ b/src/backend/commands/vacuum.c
@@ -2221,9 +2221,9 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams params,
{
StdRdOptions *opts = (StdRdOptions *) rel->rd_options;
- if (opts && opts->vacuum_truncate_set)
+ if (opts && opts->vacuum_truncate != TERNARY_UNSET)
{
- if (opts->vacuum_truncate)
+ if (opts->vacuum_truncate == TERNARY_TRUE)
params.truncate = VACOPTVALUE_ENABLED;
else
params.truncate = VACOPTVALUE_DISABLED;
diff --git a/src/include/access/reloptions.h b/src/include/access/reloptions.h
index a604a4702c..a436697658 100644
--- a/src/include/access/reloptions.h
+++ b/src/include/access/reloptions.h
@@ -29,6 +29,7 @@
typedef enum relopt_type
{
RELOPT_TYPE_BOOL,
+ RELOPT_TYPE_TERNARY, /* on, off, unset */
RELOPT_TYPE_INT,
RELOPT_TYPE_REAL,
RELOPT_TYPE_ENUM,
@@ -80,6 +81,7 @@ typedef struct relopt_value
union
{
bool bool_val;
+ ternary ternary_val;
int int_val;
double real_val;
int enum_val;
@@ -94,6 +96,12 @@ typedef struct relopt_bool
bool default_val;
} relopt_bool;
+typedef struct relopt_rernary
+{
+ relopt_gen gen;
+ int default_val;
+} relopt_ternary;
+
typedef struct relopt_int
{
relopt_gen gen;
@@ -152,19 +160,6 @@ typedef struct
const char *optname; /* option's name */
relopt_type opttype; /* option's datatype */
int offset; /* offset of field in result struct */
-
- /*
- * isset_offset is an optional offset of a field in the result struct that
- * stores whether the option is explicitly set for the relation or if it
- * just picked up the default value. In most cases, this can be
- * accomplished by giving the reloption a special out-of-range default
- * value (e.g., some integer reloptions use -2), but this isn't always
- * possible. For example, a Boolean reloption cannot be given an
- * out-of-range default, so we need another way to discover the source of
- * its value. This offset is only used if given a value greater than
- * zero.
- */
- int isset_offset;
} relopt_parse_elt;
/* Local reloption definition */
@@ -195,6 +190,8 @@ typedef struct local_relopts
extern relopt_kind add_reloption_kind(void);
extern void add_bool_reloption(bits32 kinds, const char *name, const char *desc,
bool default_val, LOCKMODE lockmode);
+extern void add_ternary_reloption(bits32 kinds, const char *name,
+ const char *desc, int default_val, LOCKMODE lockmode);
extern void add_int_reloption(bits32 kinds, const char *name, const char *desc,
int default_val, int min_val, int max_val,
LOCKMODE lockmode);
@@ -214,6 +211,9 @@ extern void register_reloptions_validator(local_relopts *relopts,
extern void add_local_bool_reloption(local_relopts *relopts, const char *name,
const char *desc, bool default_val,
int offset);
+extern void add_local_ternary_reloption(local_relopts *relopts,
+ const char *name, const char *desc,
+ ternary default_val, int offset);
extern void add_local_int_reloption(local_relopts *relopts, const char *name,
const char *desc, int default_val,
int min_val, int max_val, int offset);
diff --git a/src/include/c.h b/src/include/c.h
index 39022f8a9d..9c59ccccf8 100644
--- a/src/include/c.h
+++ b/src/include/c.h
@@ -488,6 +488,22 @@ typedef void (*pg_funcptr_t) (void);
#include <stdbool.h>
+/*
+ * ternary
+ * Boolean value with an extrea "unset" option
+ *
+ * Ternary data type is used in relation options that can be "true", "false" or
+ * "unset". Since relation options are used deep inside the PostgreSQL code,
+ * this type is declared globally.
+*/
+
+typedef enum ternary
+{
+ TERNARY_FALSE = 0,
+ TERNARY_TRUE = 1,
+ TERNARY_UNSET = -1
+} ternary;
+
/* ----------------------------------------------------------------
* Section 3: standard system types
diff --git a/src/include/utils/rel.h b/src/include/utils/rel.h
index b552359915..7346d618f9 100644
--- a/src/include/utils/rel.h
+++ b/src/include/utils/rel.h
@@ -346,8 +346,7 @@ typedef struct StdRdOptions
bool user_catalog_table; /* use as an additional catalog relation */
int parallel_workers; /* max number of parallel workers */
StdRdOptIndexCleanup vacuum_index_cleanup; /* controls index vacuuming */
- bool vacuum_truncate; /* enables vacuum to truncate a relation */
- bool vacuum_truncate_set; /* whether vacuum_truncate is set */
+ ternary vacuum_truncate; /* enables vacuum to truncate a relation */
/*
* Fraction of pages in a relation that vacuum can eagerly scan and fail
--
2.39.2
--nextPart4155600.3ZeAukHxDK
Content-Disposition: attachment;
filename="v2a-0003-Add-alias-to-be-used-as-unset-state.patch"
Content-Transfer-Encoding: 7Bit
Content-Type: text/x-patch; charset="unicode-2-0-utf-8";
name="v2a-0003-Add-alias-to-be-used-as-unset-state.patch"
^ permalink raw reply [nested|flat] 868+ messages in thread
* [PATCH v2a 2/4] Introduce ternary reloptions
@ 2025-09-04 14:41 Nikolay Shaplov <[email protected]>
0 siblings, 0 replies; 868+ messages in thread
From: Nikolay Shaplov @ 2025-09-04 14:41 UTC (permalink / raw)
Introduce ternary reloption as a replacement for current `vacuum_truncate`
implementation. Remove `vacuum_truncate_set` additional flag and using
`TERNARY_UNSET` value instead.
---
src/backend/access/common/reloptions.c | 129 ++++++++++++++++++++-----
src/backend/commands/vacuum.c | 4 +-
src/include/access/reloptions.h | 26 ++---
src/include/c.h | 16 +++
src/include/utils/rel.h | 3 +-
5 files changed, 135 insertions(+), 43 deletions(-)
diff --git a/src/backend/access/common/reloptions.c b/src/backend/access/common/reloptions.c
index 0af3fea68f..f543f11001 100644
--- a/src/backend/access/common/reloptions.c
+++ b/src/backend/access/common/reloptions.c
@@ -40,9 +40,9 @@
*
* To add an option:
*
- * (i) decide on a type (bool, integer, real, enum, string), name, default
- * value, upper and lower bounds (if applicable); for strings, consider a
- * validation routine.
+ * (i) decide on a type (bool, ternary, integer, real, enum, string), name,
+ * default value, upper and lower bounds (if applicable); for strings,
+ * consider a validation routine.
* (ii) add a record below (or use add_<type>_reloption).
* (iii) add it to the appropriate options struct (perhaps StdRdOptions)
* (iv) add it to the appropriate handling routine (perhaps
@@ -147,15 +147,6 @@ static relopt_bool boolRelOpts[] =
},
false
},
- {
- {
- "vacuum_truncate",
- "Enables vacuum to truncate empty pages at the end of this table",
- RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
- ShareUpdateExclusiveLock
- },
- true
- },
{
{
"deduplicate_items",
@@ -170,6 +161,21 @@ static relopt_bool boolRelOpts[] =
{{NULL}}
};
+static relopt_ternary ternaryRelOpts[] =
+{
+ {
+ {
+ "vacuum_truncate",
+ "Enables vacuum to truncate empty pages at the end of this table",
+ RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
+ ShareUpdateExclusiveLock
+ },
+ TERNARY_UNSET
+ },
+ /* list terminator */
+ {{NULL}}
+};
+
static relopt_int intRelOpts[] =
{
{
@@ -600,6 +606,13 @@ initialize_reloptions(void)
boolRelOpts[i].gen.lockmode));
j++;
}
+ for (i = 0; ternaryRelOpts[i].gen.name; i++)
+ {
+ Assert(DoLockModesConflict(ternaryRelOpts[i].gen.lockmode,
+ ternaryRelOpts[i].gen.lockmode));
+ j++;
+ }
+
for (i = 0; intRelOpts[i].gen.name; i++)
{
Assert(DoLockModesConflict(intRelOpts[i].gen.lockmode,
@@ -640,6 +653,14 @@ initialize_reloptions(void)
j++;
}
+ for (i = 0; ternaryRelOpts[i].gen.name; i++)
+ {
+ relOpts[j] = &ternaryRelOpts[i].gen;
+ relOpts[j]->type = RELOPT_TYPE_TERNARY;
+ relOpts[j]->namelen = strlen(relOpts[j]->name);
+ j++;
+ }
+
for (i = 0; intRelOpts[i].gen.name; i++)
{
relOpts[j] = &intRelOpts[i].gen;
@@ -800,6 +821,9 @@ allocate_reloption(bits32 kinds, int type, const char *name, const char *desc,
case RELOPT_TYPE_BOOL:
size = sizeof(relopt_bool);
break;
+ case RELOPT_TYPE_TERNARY:
+ size = sizeof(relopt_ternary);
+ break;
case RELOPT_TYPE_INT:
size = sizeof(relopt_int);
break;
@@ -883,6 +907,54 @@ add_local_bool_reloption(local_relopts *relopts, const char *name,
add_local_reloption(relopts, (relopt_gen *) newoption, offset);
}
+/*
+ * init_ternary_reloption
+ * Allocate and initialize a new ternary reloption
+ */
+static relopt_ternary *
+init_ternary_reloption(bits32 kinds, const char *name, const char *desc,
+ ternary default_val, LOCKMODE lockmode)
+{
+ relopt_ternary *newoption;
+
+ newoption = (relopt_ternary *) allocate_reloption(kinds,
+ RELOPT_TYPE_TERNARY, name, desc, lockmode);
+ newoption->default_val = default_val;
+
+ return newoption;
+}
+
+/*
+ * add_ternary_reloption
+ * Add a new ternary reloption
+ */
+void
+add_ternary_reloption(bits32 kinds, const char *name, const char *desc,
+ ternary default_val, LOCKMODE lockmode)
+{
+ relopt_ternary *newoption = init_ternary_reloption(kinds, name, desc,
+ default_val, lockmode);
+
+ add_reloption((relopt_gen *) newoption);
+}
+
+/*
+ * add_local_ternary_reloption
+ * Add a new ternary local reloption
+ *
+ * 'offset' is offset of ternary-typed field.
+ */
+void
+add_local_ternary_reloption(local_relopts *relopts, const char *name,
+ const char *desc, ternary default_val,
+ int offset)
+{
+ relopt_ternary *newoption = init_ternary_reloption(RELOPT_KIND_LOCAL,
+ name, desc,
+ default_val, 0);
+
+ add_local_reloption(relopts, (relopt_gen *) newoption, offset);
+}
/*
* init_real_reloption
@@ -1617,6 +1689,18 @@ parse_one_reloption(relopt_value *option, char *text_str, int text_len,
option->gen->name, value)));
}
break;
+ case RELOPT_TYPE_TERNARY:
+ {
+ bool b;
+ parsed = parse_bool(value, &b);
+ option->values.ternary_val = b ? TERNARY_TRUE : TERNARY_FALSE;
+ if (validate && !parsed)
+ ereport(ERROR,
+ (errcode(ERRCODE_INVALID_PARAMETER_VALUE),
+ errmsg("invalid value for ternary option \"%s\": %s",
+ option->gen->name, value)));
+ }
+ break;
case RELOPT_TYPE_INT:
{
relopt_int *optint = (relopt_int *) option->gen;
@@ -1780,17 +1864,6 @@ fillRelOptions(void *rdopts, Size basesize,
char *itempos = ((char *) rdopts) + elems[j].offset;
char *string_val;
- /*
- * If isset_offset is provided, store whether the reloption is
- * set there.
- */
- if (elems[j].isset_offset > 0)
- {
- char *setpos = ((char *) rdopts) + elems[j].isset_offset;
-
- *(bool *) setpos = options[i].isset;
- }
-
switch (options[i].gen->type)
{
case RELOPT_TYPE_BOOL:
@@ -1798,6 +1871,11 @@ fillRelOptions(void *rdopts, Size basesize,
options[i].values.bool_val :
((relopt_bool *) options[i].gen)->default_val;
break;
+ case RELOPT_TYPE_TERNARY:
+ *(ternary *) itempos = options[i].isset ?
+ options[i].values.ternary_val :
+ ((relopt_ternary *) options[i].gen)->default_val;
+ break;
case RELOPT_TYPE_INT:
*(int *) itempos = options[i].isset ?
options[i].values.int_val :
@@ -1912,8 +1990,8 @@ default_reloptions(Datum reloptions, bool validate, relopt_kind kind)
offsetof(StdRdOptions, parallel_workers)},
{"vacuum_index_cleanup", RELOPT_TYPE_ENUM,
offsetof(StdRdOptions, vacuum_index_cleanup)},
- {"vacuum_truncate", RELOPT_TYPE_BOOL,
- offsetof(StdRdOptions, vacuum_truncate), offsetof(StdRdOptions, vacuum_truncate_set)},
+ {"vacuum_truncate", RELOPT_TYPE_TERNARY,
+ offsetof(StdRdOptions, vacuum_truncate)},
{"vacuum_max_eager_freeze_failure_rate", RELOPT_TYPE_REAL,
offsetof(StdRdOptions, vacuum_max_eager_freeze_failure_rate)}
};
@@ -1993,7 +2071,6 @@ build_local_reloptions(local_relopts *relopts, Datum options, bool validate)
elems[i].optname = opt->option->name;
elems[i].opttype = opt->option->type;
elems[i].offset = opt->offset;
- elems[i].isset_offset = 0; /* not supported for local relopts yet */
i++;
}
diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c
index 733ef40ae7..977babff54 100644
--- a/src/backend/commands/vacuum.c
+++ b/src/backend/commands/vacuum.c
@@ -2221,9 +2221,9 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams params,
{
StdRdOptions *opts = (StdRdOptions *) rel->rd_options;
- if (opts && opts->vacuum_truncate_set)
+ if (opts && opts->vacuum_truncate != TERNARY_UNSET)
{
- if (opts->vacuum_truncate)
+ if (opts->vacuum_truncate == TERNARY_TRUE)
params.truncate = VACOPTVALUE_ENABLED;
else
params.truncate = VACOPTVALUE_DISABLED;
diff --git a/src/include/access/reloptions.h b/src/include/access/reloptions.h
index a604a4702c..a436697658 100644
--- a/src/include/access/reloptions.h
+++ b/src/include/access/reloptions.h
@@ -29,6 +29,7 @@
typedef enum relopt_type
{
RELOPT_TYPE_BOOL,
+ RELOPT_TYPE_TERNARY, /* on, off, unset */
RELOPT_TYPE_INT,
RELOPT_TYPE_REAL,
RELOPT_TYPE_ENUM,
@@ -80,6 +81,7 @@ typedef struct relopt_value
union
{
bool bool_val;
+ ternary ternary_val;
int int_val;
double real_val;
int enum_val;
@@ -94,6 +96,12 @@ typedef struct relopt_bool
bool default_val;
} relopt_bool;
+typedef struct relopt_rernary
+{
+ relopt_gen gen;
+ int default_val;
+} relopt_ternary;
+
typedef struct relopt_int
{
relopt_gen gen;
@@ -152,19 +160,6 @@ typedef struct
const char *optname; /* option's name */
relopt_type opttype; /* option's datatype */
int offset; /* offset of field in result struct */
-
- /*
- * isset_offset is an optional offset of a field in the result struct that
- * stores whether the option is explicitly set for the relation or if it
- * just picked up the default value. In most cases, this can be
- * accomplished by giving the reloption a special out-of-range default
- * value (e.g., some integer reloptions use -2), but this isn't always
- * possible. For example, a Boolean reloption cannot be given an
- * out-of-range default, so we need another way to discover the source of
- * its value. This offset is only used if given a value greater than
- * zero.
- */
- int isset_offset;
} relopt_parse_elt;
/* Local reloption definition */
@@ -195,6 +190,8 @@ typedef struct local_relopts
extern relopt_kind add_reloption_kind(void);
extern void add_bool_reloption(bits32 kinds, const char *name, const char *desc,
bool default_val, LOCKMODE lockmode);
+extern void add_ternary_reloption(bits32 kinds, const char *name,
+ const char *desc, int default_val, LOCKMODE lockmode);
extern void add_int_reloption(bits32 kinds, const char *name, const char *desc,
int default_val, int min_val, int max_val,
LOCKMODE lockmode);
@@ -214,6 +211,9 @@ extern void register_reloptions_validator(local_relopts *relopts,
extern void add_local_bool_reloption(local_relopts *relopts, const char *name,
const char *desc, bool default_val,
int offset);
+extern void add_local_ternary_reloption(local_relopts *relopts,
+ const char *name, const char *desc,
+ ternary default_val, int offset);
extern void add_local_int_reloption(local_relopts *relopts, const char *name,
const char *desc, int default_val,
int min_val, int max_val, int offset);
diff --git a/src/include/c.h b/src/include/c.h
index 39022f8a9d..9c59ccccf8 100644
--- a/src/include/c.h
+++ b/src/include/c.h
@@ -488,6 +488,22 @@ typedef void (*pg_funcptr_t) (void);
#include <stdbool.h>
+/*
+ * ternary
+ * Boolean value with an extrea "unset" option
+ *
+ * Ternary data type is used in relation options that can be "true", "false" or
+ * "unset". Since relation options are used deep inside the PostgreSQL code,
+ * this type is declared globally.
+*/
+
+typedef enum ternary
+{
+ TERNARY_FALSE = 0,
+ TERNARY_TRUE = 1,
+ TERNARY_UNSET = -1
+} ternary;
+
/* ----------------------------------------------------------------
* Section 3: standard system types
diff --git a/src/include/utils/rel.h b/src/include/utils/rel.h
index b552359915..7346d618f9 100644
--- a/src/include/utils/rel.h
+++ b/src/include/utils/rel.h
@@ -346,8 +346,7 @@ typedef struct StdRdOptions
bool user_catalog_table; /* use as an additional catalog relation */
int parallel_workers; /* max number of parallel workers */
StdRdOptIndexCleanup vacuum_index_cleanup; /* controls index vacuuming */
- bool vacuum_truncate; /* enables vacuum to truncate a relation */
- bool vacuum_truncate_set; /* whether vacuum_truncate is set */
+ ternary vacuum_truncate; /* enables vacuum to truncate a relation */
/*
* Fraction of pages in a relation that vacuum can eagerly scan and fail
--
2.39.2
--nextPart4155600.3ZeAukHxDK
Content-Disposition: attachment;
filename="v2a-0003-Add-alias-to-be-used-as-unset-state.patch"
Content-Transfer-Encoding: 7Bit
Content-Type: text/x-patch; charset="unicode-2-0-utf-8";
name="v2a-0003-Add-alias-to-be-used-as-unset-state.patch"
^ permalink raw reply [nested|flat] 868+ messages in thread
* [PATCH v2a 2/4] Introduce ternary reloptions
@ 2025-09-04 14:41 Nikolay Shaplov <[email protected]>
0 siblings, 0 replies; 868+ messages in thread
From: Nikolay Shaplov @ 2025-09-04 14:41 UTC (permalink / raw)
Introduce ternary reloption as a replacement for current `vacuum_truncate`
implementation. Remove `vacuum_truncate_set` additional flag and using
`TERNARY_UNSET` value instead.
---
src/backend/access/common/reloptions.c | 129 ++++++++++++++++++++-----
src/backend/commands/vacuum.c | 4 +-
src/include/access/reloptions.h | 26 ++---
src/include/c.h | 16 +++
src/include/utils/rel.h | 3 +-
5 files changed, 135 insertions(+), 43 deletions(-)
diff --git a/src/backend/access/common/reloptions.c b/src/backend/access/common/reloptions.c
index 0af3fea68f..f543f11001 100644
--- a/src/backend/access/common/reloptions.c
+++ b/src/backend/access/common/reloptions.c
@@ -40,9 +40,9 @@
*
* To add an option:
*
- * (i) decide on a type (bool, integer, real, enum, string), name, default
- * value, upper and lower bounds (if applicable); for strings, consider a
- * validation routine.
+ * (i) decide on a type (bool, ternary, integer, real, enum, string), name,
+ * default value, upper and lower bounds (if applicable); for strings,
+ * consider a validation routine.
* (ii) add a record below (or use add_<type>_reloption).
* (iii) add it to the appropriate options struct (perhaps StdRdOptions)
* (iv) add it to the appropriate handling routine (perhaps
@@ -147,15 +147,6 @@ static relopt_bool boolRelOpts[] =
},
false
},
- {
- {
- "vacuum_truncate",
- "Enables vacuum to truncate empty pages at the end of this table",
- RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
- ShareUpdateExclusiveLock
- },
- true
- },
{
{
"deduplicate_items",
@@ -170,6 +161,21 @@ static relopt_bool boolRelOpts[] =
{{NULL}}
};
+static relopt_ternary ternaryRelOpts[] =
+{
+ {
+ {
+ "vacuum_truncate",
+ "Enables vacuum to truncate empty pages at the end of this table",
+ RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
+ ShareUpdateExclusiveLock
+ },
+ TERNARY_UNSET
+ },
+ /* list terminator */
+ {{NULL}}
+};
+
static relopt_int intRelOpts[] =
{
{
@@ -600,6 +606,13 @@ initialize_reloptions(void)
boolRelOpts[i].gen.lockmode));
j++;
}
+ for (i = 0; ternaryRelOpts[i].gen.name; i++)
+ {
+ Assert(DoLockModesConflict(ternaryRelOpts[i].gen.lockmode,
+ ternaryRelOpts[i].gen.lockmode));
+ j++;
+ }
+
for (i = 0; intRelOpts[i].gen.name; i++)
{
Assert(DoLockModesConflict(intRelOpts[i].gen.lockmode,
@@ -640,6 +653,14 @@ initialize_reloptions(void)
j++;
}
+ for (i = 0; ternaryRelOpts[i].gen.name; i++)
+ {
+ relOpts[j] = &ternaryRelOpts[i].gen;
+ relOpts[j]->type = RELOPT_TYPE_TERNARY;
+ relOpts[j]->namelen = strlen(relOpts[j]->name);
+ j++;
+ }
+
for (i = 0; intRelOpts[i].gen.name; i++)
{
relOpts[j] = &intRelOpts[i].gen;
@@ -800,6 +821,9 @@ allocate_reloption(bits32 kinds, int type, const char *name, const char *desc,
case RELOPT_TYPE_BOOL:
size = sizeof(relopt_bool);
break;
+ case RELOPT_TYPE_TERNARY:
+ size = sizeof(relopt_ternary);
+ break;
case RELOPT_TYPE_INT:
size = sizeof(relopt_int);
break;
@@ -883,6 +907,54 @@ add_local_bool_reloption(local_relopts *relopts, const char *name,
add_local_reloption(relopts, (relopt_gen *) newoption, offset);
}
+/*
+ * init_ternary_reloption
+ * Allocate and initialize a new ternary reloption
+ */
+static relopt_ternary *
+init_ternary_reloption(bits32 kinds, const char *name, const char *desc,
+ ternary default_val, LOCKMODE lockmode)
+{
+ relopt_ternary *newoption;
+
+ newoption = (relopt_ternary *) allocate_reloption(kinds,
+ RELOPT_TYPE_TERNARY, name, desc, lockmode);
+ newoption->default_val = default_val;
+
+ return newoption;
+}
+
+/*
+ * add_ternary_reloption
+ * Add a new ternary reloption
+ */
+void
+add_ternary_reloption(bits32 kinds, const char *name, const char *desc,
+ ternary default_val, LOCKMODE lockmode)
+{
+ relopt_ternary *newoption = init_ternary_reloption(kinds, name, desc,
+ default_val, lockmode);
+
+ add_reloption((relopt_gen *) newoption);
+}
+
+/*
+ * add_local_ternary_reloption
+ * Add a new ternary local reloption
+ *
+ * 'offset' is offset of ternary-typed field.
+ */
+void
+add_local_ternary_reloption(local_relopts *relopts, const char *name,
+ const char *desc, ternary default_val,
+ int offset)
+{
+ relopt_ternary *newoption = init_ternary_reloption(RELOPT_KIND_LOCAL,
+ name, desc,
+ default_val, 0);
+
+ add_local_reloption(relopts, (relopt_gen *) newoption, offset);
+}
/*
* init_real_reloption
@@ -1617,6 +1689,18 @@ parse_one_reloption(relopt_value *option, char *text_str, int text_len,
option->gen->name, value)));
}
break;
+ case RELOPT_TYPE_TERNARY:
+ {
+ bool b;
+ parsed = parse_bool(value, &b);
+ option->values.ternary_val = b ? TERNARY_TRUE : TERNARY_FALSE;
+ if (validate && !parsed)
+ ereport(ERROR,
+ (errcode(ERRCODE_INVALID_PARAMETER_VALUE),
+ errmsg("invalid value for ternary option \"%s\": %s",
+ option->gen->name, value)));
+ }
+ break;
case RELOPT_TYPE_INT:
{
relopt_int *optint = (relopt_int *) option->gen;
@@ -1780,17 +1864,6 @@ fillRelOptions(void *rdopts, Size basesize,
char *itempos = ((char *) rdopts) + elems[j].offset;
char *string_val;
- /*
- * If isset_offset is provided, store whether the reloption is
- * set there.
- */
- if (elems[j].isset_offset > 0)
- {
- char *setpos = ((char *) rdopts) + elems[j].isset_offset;
-
- *(bool *) setpos = options[i].isset;
- }
-
switch (options[i].gen->type)
{
case RELOPT_TYPE_BOOL:
@@ -1798,6 +1871,11 @@ fillRelOptions(void *rdopts, Size basesize,
options[i].values.bool_val :
((relopt_bool *) options[i].gen)->default_val;
break;
+ case RELOPT_TYPE_TERNARY:
+ *(ternary *) itempos = options[i].isset ?
+ options[i].values.ternary_val :
+ ((relopt_ternary *) options[i].gen)->default_val;
+ break;
case RELOPT_TYPE_INT:
*(int *) itempos = options[i].isset ?
options[i].values.int_val :
@@ -1912,8 +1990,8 @@ default_reloptions(Datum reloptions, bool validate, relopt_kind kind)
offsetof(StdRdOptions, parallel_workers)},
{"vacuum_index_cleanup", RELOPT_TYPE_ENUM,
offsetof(StdRdOptions, vacuum_index_cleanup)},
- {"vacuum_truncate", RELOPT_TYPE_BOOL,
- offsetof(StdRdOptions, vacuum_truncate), offsetof(StdRdOptions, vacuum_truncate_set)},
+ {"vacuum_truncate", RELOPT_TYPE_TERNARY,
+ offsetof(StdRdOptions, vacuum_truncate)},
{"vacuum_max_eager_freeze_failure_rate", RELOPT_TYPE_REAL,
offsetof(StdRdOptions, vacuum_max_eager_freeze_failure_rate)}
};
@@ -1993,7 +2071,6 @@ build_local_reloptions(local_relopts *relopts, Datum options, bool validate)
elems[i].optname = opt->option->name;
elems[i].opttype = opt->option->type;
elems[i].offset = opt->offset;
- elems[i].isset_offset = 0; /* not supported for local relopts yet */
i++;
}
diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c
index 733ef40ae7..977babff54 100644
--- a/src/backend/commands/vacuum.c
+++ b/src/backend/commands/vacuum.c
@@ -2221,9 +2221,9 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams params,
{
StdRdOptions *opts = (StdRdOptions *) rel->rd_options;
- if (opts && opts->vacuum_truncate_set)
+ if (opts && opts->vacuum_truncate != TERNARY_UNSET)
{
- if (opts->vacuum_truncate)
+ if (opts->vacuum_truncate == TERNARY_TRUE)
params.truncate = VACOPTVALUE_ENABLED;
else
params.truncate = VACOPTVALUE_DISABLED;
diff --git a/src/include/access/reloptions.h b/src/include/access/reloptions.h
index a604a4702c..a436697658 100644
--- a/src/include/access/reloptions.h
+++ b/src/include/access/reloptions.h
@@ -29,6 +29,7 @@
typedef enum relopt_type
{
RELOPT_TYPE_BOOL,
+ RELOPT_TYPE_TERNARY, /* on, off, unset */
RELOPT_TYPE_INT,
RELOPT_TYPE_REAL,
RELOPT_TYPE_ENUM,
@@ -80,6 +81,7 @@ typedef struct relopt_value
union
{
bool bool_val;
+ ternary ternary_val;
int int_val;
double real_val;
int enum_val;
@@ -94,6 +96,12 @@ typedef struct relopt_bool
bool default_val;
} relopt_bool;
+typedef struct relopt_rernary
+{
+ relopt_gen gen;
+ int default_val;
+} relopt_ternary;
+
typedef struct relopt_int
{
relopt_gen gen;
@@ -152,19 +160,6 @@ typedef struct
const char *optname; /* option's name */
relopt_type opttype; /* option's datatype */
int offset; /* offset of field in result struct */
-
- /*
- * isset_offset is an optional offset of a field in the result struct that
- * stores whether the option is explicitly set for the relation or if it
- * just picked up the default value. In most cases, this can be
- * accomplished by giving the reloption a special out-of-range default
- * value (e.g., some integer reloptions use -2), but this isn't always
- * possible. For example, a Boolean reloption cannot be given an
- * out-of-range default, so we need another way to discover the source of
- * its value. This offset is only used if given a value greater than
- * zero.
- */
- int isset_offset;
} relopt_parse_elt;
/* Local reloption definition */
@@ -195,6 +190,8 @@ typedef struct local_relopts
extern relopt_kind add_reloption_kind(void);
extern void add_bool_reloption(bits32 kinds, const char *name, const char *desc,
bool default_val, LOCKMODE lockmode);
+extern void add_ternary_reloption(bits32 kinds, const char *name,
+ const char *desc, int default_val, LOCKMODE lockmode);
extern void add_int_reloption(bits32 kinds, const char *name, const char *desc,
int default_val, int min_val, int max_val,
LOCKMODE lockmode);
@@ -214,6 +211,9 @@ extern void register_reloptions_validator(local_relopts *relopts,
extern void add_local_bool_reloption(local_relopts *relopts, const char *name,
const char *desc, bool default_val,
int offset);
+extern void add_local_ternary_reloption(local_relopts *relopts,
+ const char *name, const char *desc,
+ ternary default_val, int offset);
extern void add_local_int_reloption(local_relopts *relopts, const char *name,
const char *desc, int default_val,
int min_val, int max_val, int offset);
diff --git a/src/include/c.h b/src/include/c.h
index 39022f8a9d..9c59ccccf8 100644
--- a/src/include/c.h
+++ b/src/include/c.h
@@ -488,6 +488,22 @@ typedef void (*pg_funcptr_t) (void);
#include <stdbool.h>
+/*
+ * ternary
+ * Boolean value with an extrea "unset" option
+ *
+ * Ternary data type is used in relation options that can be "true", "false" or
+ * "unset". Since relation options are used deep inside the PostgreSQL code,
+ * this type is declared globally.
+*/
+
+typedef enum ternary
+{
+ TERNARY_FALSE = 0,
+ TERNARY_TRUE = 1,
+ TERNARY_UNSET = -1
+} ternary;
+
/* ----------------------------------------------------------------
* Section 3: standard system types
diff --git a/src/include/utils/rel.h b/src/include/utils/rel.h
index b552359915..7346d618f9 100644
--- a/src/include/utils/rel.h
+++ b/src/include/utils/rel.h
@@ -346,8 +346,7 @@ typedef struct StdRdOptions
bool user_catalog_table; /* use as an additional catalog relation */
int parallel_workers; /* max number of parallel workers */
StdRdOptIndexCleanup vacuum_index_cleanup; /* controls index vacuuming */
- bool vacuum_truncate; /* enables vacuum to truncate a relation */
- bool vacuum_truncate_set; /* whether vacuum_truncate is set */
+ ternary vacuum_truncate; /* enables vacuum to truncate a relation */
/*
* Fraction of pages in a relation that vacuum can eagerly scan and fail
--
2.39.2
--nextPart4155600.3ZeAukHxDK
Content-Disposition: attachment;
filename="v2a-0003-Add-alias-to-be-used-as-unset-state.patch"
Content-Transfer-Encoding: 7Bit
Content-Type: text/x-patch; charset="unicode-2-0-utf-8";
name="v2a-0003-Add-alias-to-be-used-as-unset-state.patch"
^ permalink raw reply [nested|flat] 868+ messages in thread
* [PATCH v2a 2/4] Introduce ternary reloptions
@ 2025-09-04 14:41 Nikolay Shaplov <[email protected]>
0 siblings, 0 replies; 868+ messages in thread
From: Nikolay Shaplov @ 2025-09-04 14:41 UTC (permalink / raw)
Introduce ternary reloption as a replacement for current `vacuum_truncate`
implementation. Remove `vacuum_truncate_set` additional flag and using
`TERNARY_UNSET` value instead.
---
src/backend/access/common/reloptions.c | 129 ++++++++++++++++++++-----
src/backend/commands/vacuum.c | 4 +-
src/include/access/reloptions.h | 26 ++---
src/include/c.h | 16 +++
src/include/utils/rel.h | 3 +-
5 files changed, 135 insertions(+), 43 deletions(-)
diff --git a/src/backend/access/common/reloptions.c b/src/backend/access/common/reloptions.c
index 0af3fea68f..f543f11001 100644
--- a/src/backend/access/common/reloptions.c
+++ b/src/backend/access/common/reloptions.c
@@ -40,9 +40,9 @@
*
* To add an option:
*
- * (i) decide on a type (bool, integer, real, enum, string), name, default
- * value, upper and lower bounds (if applicable); for strings, consider a
- * validation routine.
+ * (i) decide on a type (bool, ternary, integer, real, enum, string), name,
+ * default value, upper and lower bounds (if applicable); for strings,
+ * consider a validation routine.
* (ii) add a record below (or use add_<type>_reloption).
* (iii) add it to the appropriate options struct (perhaps StdRdOptions)
* (iv) add it to the appropriate handling routine (perhaps
@@ -147,15 +147,6 @@ static relopt_bool boolRelOpts[] =
},
false
},
- {
- {
- "vacuum_truncate",
- "Enables vacuum to truncate empty pages at the end of this table",
- RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
- ShareUpdateExclusiveLock
- },
- true
- },
{
{
"deduplicate_items",
@@ -170,6 +161,21 @@ static relopt_bool boolRelOpts[] =
{{NULL}}
};
+static relopt_ternary ternaryRelOpts[] =
+{
+ {
+ {
+ "vacuum_truncate",
+ "Enables vacuum to truncate empty pages at the end of this table",
+ RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
+ ShareUpdateExclusiveLock
+ },
+ TERNARY_UNSET
+ },
+ /* list terminator */
+ {{NULL}}
+};
+
static relopt_int intRelOpts[] =
{
{
@@ -600,6 +606,13 @@ initialize_reloptions(void)
boolRelOpts[i].gen.lockmode));
j++;
}
+ for (i = 0; ternaryRelOpts[i].gen.name; i++)
+ {
+ Assert(DoLockModesConflict(ternaryRelOpts[i].gen.lockmode,
+ ternaryRelOpts[i].gen.lockmode));
+ j++;
+ }
+
for (i = 0; intRelOpts[i].gen.name; i++)
{
Assert(DoLockModesConflict(intRelOpts[i].gen.lockmode,
@@ -640,6 +653,14 @@ initialize_reloptions(void)
j++;
}
+ for (i = 0; ternaryRelOpts[i].gen.name; i++)
+ {
+ relOpts[j] = &ternaryRelOpts[i].gen;
+ relOpts[j]->type = RELOPT_TYPE_TERNARY;
+ relOpts[j]->namelen = strlen(relOpts[j]->name);
+ j++;
+ }
+
for (i = 0; intRelOpts[i].gen.name; i++)
{
relOpts[j] = &intRelOpts[i].gen;
@@ -800,6 +821,9 @@ allocate_reloption(bits32 kinds, int type, const char *name, const char *desc,
case RELOPT_TYPE_BOOL:
size = sizeof(relopt_bool);
break;
+ case RELOPT_TYPE_TERNARY:
+ size = sizeof(relopt_ternary);
+ break;
case RELOPT_TYPE_INT:
size = sizeof(relopt_int);
break;
@@ -883,6 +907,54 @@ add_local_bool_reloption(local_relopts *relopts, const char *name,
add_local_reloption(relopts, (relopt_gen *) newoption, offset);
}
+/*
+ * init_ternary_reloption
+ * Allocate and initialize a new ternary reloption
+ */
+static relopt_ternary *
+init_ternary_reloption(bits32 kinds, const char *name, const char *desc,
+ ternary default_val, LOCKMODE lockmode)
+{
+ relopt_ternary *newoption;
+
+ newoption = (relopt_ternary *) allocate_reloption(kinds,
+ RELOPT_TYPE_TERNARY, name, desc, lockmode);
+ newoption->default_val = default_val;
+
+ return newoption;
+}
+
+/*
+ * add_ternary_reloption
+ * Add a new ternary reloption
+ */
+void
+add_ternary_reloption(bits32 kinds, const char *name, const char *desc,
+ ternary default_val, LOCKMODE lockmode)
+{
+ relopt_ternary *newoption = init_ternary_reloption(kinds, name, desc,
+ default_val, lockmode);
+
+ add_reloption((relopt_gen *) newoption);
+}
+
+/*
+ * add_local_ternary_reloption
+ * Add a new ternary local reloption
+ *
+ * 'offset' is offset of ternary-typed field.
+ */
+void
+add_local_ternary_reloption(local_relopts *relopts, const char *name,
+ const char *desc, ternary default_val,
+ int offset)
+{
+ relopt_ternary *newoption = init_ternary_reloption(RELOPT_KIND_LOCAL,
+ name, desc,
+ default_val, 0);
+
+ add_local_reloption(relopts, (relopt_gen *) newoption, offset);
+}
/*
* init_real_reloption
@@ -1617,6 +1689,18 @@ parse_one_reloption(relopt_value *option, char *text_str, int text_len,
option->gen->name, value)));
}
break;
+ case RELOPT_TYPE_TERNARY:
+ {
+ bool b;
+ parsed = parse_bool(value, &b);
+ option->values.ternary_val = b ? TERNARY_TRUE : TERNARY_FALSE;
+ if (validate && !parsed)
+ ereport(ERROR,
+ (errcode(ERRCODE_INVALID_PARAMETER_VALUE),
+ errmsg("invalid value for ternary option \"%s\": %s",
+ option->gen->name, value)));
+ }
+ break;
case RELOPT_TYPE_INT:
{
relopt_int *optint = (relopt_int *) option->gen;
@@ -1780,17 +1864,6 @@ fillRelOptions(void *rdopts, Size basesize,
char *itempos = ((char *) rdopts) + elems[j].offset;
char *string_val;
- /*
- * If isset_offset is provided, store whether the reloption is
- * set there.
- */
- if (elems[j].isset_offset > 0)
- {
- char *setpos = ((char *) rdopts) + elems[j].isset_offset;
-
- *(bool *) setpos = options[i].isset;
- }
-
switch (options[i].gen->type)
{
case RELOPT_TYPE_BOOL:
@@ -1798,6 +1871,11 @@ fillRelOptions(void *rdopts, Size basesize,
options[i].values.bool_val :
((relopt_bool *) options[i].gen)->default_val;
break;
+ case RELOPT_TYPE_TERNARY:
+ *(ternary *) itempos = options[i].isset ?
+ options[i].values.ternary_val :
+ ((relopt_ternary *) options[i].gen)->default_val;
+ break;
case RELOPT_TYPE_INT:
*(int *) itempos = options[i].isset ?
options[i].values.int_val :
@@ -1912,8 +1990,8 @@ default_reloptions(Datum reloptions, bool validate, relopt_kind kind)
offsetof(StdRdOptions, parallel_workers)},
{"vacuum_index_cleanup", RELOPT_TYPE_ENUM,
offsetof(StdRdOptions, vacuum_index_cleanup)},
- {"vacuum_truncate", RELOPT_TYPE_BOOL,
- offsetof(StdRdOptions, vacuum_truncate), offsetof(StdRdOptions, vacuum_truncate_set)},
+ {"vacuum_truncate", RELOPT_TYPE_TERNARY,
+ offsetof(StdRdOptions, vacuum_truncate)},
{"vacuum_max_eager_freeze_failure_rate", RELOPT_TYPE_REAL,
offsetof(StdRdOptions, vacuum_max_eager_freeze_failure_rate)}
};
@@ -1993,7 +2071,6 @@ build_local_reloptions(local_relopts *relopts, Datum options, bool validate)
elems[i].optname = opt->option->name;
elems[i].opttype = opt->option->type;
elems[i].offset = opt->offset;
- elems[i].isset_offset = 0; /* not supported for local relopts yet */
i++;
}
diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c
index 733ef40ae7..977babff54 100644
--- a/src/backend/commands/vacuum.c
+++ b/src/backend/commands/vacuum.c
@@ -2221,9 +2221,9 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams params,
{
StdRdOptions *opts = (StdRdOptions *) rel->rd_options;
- if (opts && opts->vacuum_truncate_set)
+ if (opts && opts->vacuum_truncate != TERNARY_UNSET)
{
- if (opts->vacuum_truncate)
+ if (opts->vacuum_truncate == TERNARY_TRUE)
params.truncate = VACOPTVALUE_ENABLED;
else
params.truncate = VACOPTVALUE_DISABLED;
diff --git a/src/include/access/reloptions.h b/src/include/access/reloptions.h
index a604a4702c..a436697658 100644
--- a/src/include/access/reloptions.h
+++ b/src/include/access/reloptions.h
@@ -29,6 +29,7 @@
typedef enum relopt_type
{
RELOPT_TYPE_BOOL,
+ RELOPT_TYPE_TERNARY, /* on, off, unset */
RELOPT_TYPE_INT,
RELOPT_TYPE_REAL,
RELOPT_TYPE_ENUM,
@@ -80,6 +81,7 @@ typedef struct relopt_value
union
{
bool bool_val;
+ ternary ternary_val;
int int_val;
double real_val;
int enum_val;
@@ -94,6 +96,12 @@ typedef struct relopt_bool
bool default_val;
} relopt_bool;
+typedef struct relopt_rernary
+{
+ relopt_gen gen;
+ int default_val;
+} relopt_ternary;
+
typedef struct relopt_int
{
relopt_gen gen;
@@ -152,19 +160,6 @@ typedef struct
const char *optname; /* option's name */
relopt_type opttype; /* option's datatype */
int offset; /* offset of field in result struct */
-
- /*
- * isset_offset is an optional offset of a field in the result struct that
- * stores whether the option is explicitly set for the relation or if it
- * just picked up the default value. In most cases, this can be
- * accomplished by giving the reloption a special out-of-range default
- * value (e.g., some integer reloptions use -2), but this isn't always
- * possible. For example, a Boolean reloption cannot be given an
- * out-of-range default, so we need another way to discover the source of
- * its value. This offset is only used if given a value greater than
- * zero.
- */
- int isset_offset;
} relopt_parse_elt;
/* Local reloption definition */
@@ -195,6 +190,8 @@ typedef struct local_relopts
extern relopt_kind add_reloption_kind(void);
extern void add_bool_reloption(bits32 kinds, const char *name, const char *desc,
bool default_val, LOCKMODE lockmode);
+extern void add_ternary_reloption(bits32 kinds, const char *name,
+ const char *desc, int default_val, LOCKMODE lockmode);
extern void add_int_reloption(bits32 kinds, const char *name, const char *desc,
int default_val, int min_val, int max_val,
LOCKMODE lockmode);
@@ -214,6 +211,9 @@ extern void register_reloptions_validator(local_relopts *relopts,
extern void add_local_bool_reloption(local_relopts *relopts, const char *name,
const char *desc, bool default_val,
int offset);
+extern void add_local_ternary_reloption(local_relopts *relopts,
+ const char *name, const char *desc,
+ ternary default_val, int offset);
extern void add_local_int_reloption(local_relopts *relopts, const char *name,
const char *desc, int default_val,
int min_val, int max_val, int offset);
diff --git a/src/include/c.h b/src/include/c.h
index 39022f8a9d..9c59ccccf8 100644
--- a/src/include/c.h
+++ b/src/include/c.h
@@ -488,6 +488,22 @@ typedef void (*pg_funcptr_t) (void);
#include <stdbool.h>
+/*
+ * ternary
+ * Boolean value with an extrea "unset" option
+ *
+ * Ternary data type is used in relation options that can be "true", "false" or
+ * "unset". Since relation options are used deep inside the PostgreSQL code,
+ * this type is declared globally.
+*/
+
+typedef enum ternary
+{
+ TERNARY_FALSE = 0,
+ TERNARY_TRUE = 1,
+ TERNARY_UNSET = -1
+} ternary;
+
/* ----------------------------------------------------------------
* Section 3: standard system types
diff --git a/src/include/utils/rel.h b/src/include/utils/rel.h
index b552359915..7346d618f9 100644
--- a/src/include/utils/rel.h
+++ b/src/include/utils/rel.h
@@ -346,8 +346,7 @@ typedef struct StdRdOptions
bool user_catalog_table; /* use as an additional catalog relation */
int parallel_workers; /* max number of parallel workers */
StdRdOptIndexCleanup vacuum_index_cleanup; /* controls index vacuuming */
- bool vacuum_truncate; /* enables vacuum to truncate a relation */
- bool vacuum_truncate_set; /* whether vacuum_truncate is set */
+ ternary vacuum_truncate; /* enables vacuum to truncate a relation */
/*
* Fraction of pages in a relation that vacuum can eagerly scan and fail
--
2.39.2
--nextPart4155600.3ZeAukHxDK
Content-Disposition: attachment;
filename="v2a-0003-Add-alias-to-be-used-as-unset-state.patch"
Content-Transfer-Encoding: 7Bit
Content-Type: text/x-patch; charset="unicode-2-0-utf-8";
name="v2a-0003-Add-alias-to-be-used-as-unset-state.patch"
^ permalink raw reply [nested|flat] 868+ messages in thread
* [PATCH v2 2/4] Introduce ternary reloptions
@ 2025-09-04 14:41 Nikolay Shaplov <[email protected]>
0 siblings, 0 replies; 868+ messages in thread
From: Nikolay Shaplov @ 2025-09-04 14:41 UTC (permalink / raw)
Introduce ternary reloption as a replacement for current `vacuum_truncate`
implementation. Remove `vacuum_truncate_set` additional flag and using
`TERNARY_UNSET` value instead.
---
src/backend/access/common/reloptions.c | 129 ++++++++++++++++++++-----
src/backend/commands/vacuum.c | 4 +-
src/include/access/reloptions.h | 26 ++---
src/include/c.h | 16 +++
src/include/utils/rel.h | 3 +-
5 files changed, 135 insertions(+), 43 deletions(-)
diff --git a/src/backend/access/common/reloptions.c b/src/backend/access/common/reloptions.c
index 0af3fea68f..f543f11001 100644
--- a/src/backend/access/common/reloptions.c
+++ b/src/backend/access/common/reloptions.c
@@ -40,9 +40,9 @@
*
* To add an option:
*
- * (i) decide on a type (bool, integer, real, enum, string), name, default
- * value, upper and lower bounds (if applicable); for strings, consider a
- * validation routine.
+ * (i) decide on a type (bool, ternary, integer, real, enum, string), name,
+ * default value, upper and lower bounds (if applicable); for strings,
+ * consider a validation routine.
* (ii) add a record below (or use add_<type>_reloption).
* (iii) add it to the appropriate options struct (perhaps StdRdOptions)
* (iv) add it to the appropriate handling routine (perhaps
@@ -147,15 +147,6 @@ static relopt_bool boolRelOpts[] =
},
false
},
- {
- {
- "vacuum_truncate",
- "Enables vacuum to truncate empty pages at the end of this table",
- RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
- ShareUpdateExclusiveLock
- },
- true
- },
{
{
"deduplicate_items",
@@ -170,6 +161,21 @@ static relopt_bool boolRelOpts[] =
{{NULL}}
};
+static relopt_ternary ternaryRelOpts[] =
+{
+ {
+ {
+ "vacuum_truncate",
+ "Enables vacuum to truncate empty pages at the end of this table",
+ RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
+ ShareUpdateExclusiveLock
+ },
+ TERNARY_UNSET
+ },
+ /* list terminator */
+ {{NULL}}
+};
+
static relopt_int intRelOpts[] =
{
{
@@ -600,6 +606,13 @@ initialize_reloptions(void)
boolRelOpts[i].gen.lockmode));
j++;
}
+ for (i = 0; ternaryRelOpts[i].gen.name; i++)
+ {
+ Assert(DoLockModesConflict(ternaryRelOpts[i].gen.lockmode,
+ ternaryRelOpts[i].gen.lockmode));
+ j++;
+ }
+
for (i = 0; intRelOpts[i].gen.name; i++)
{
Assert(DoLockModesConflict(intRelOpts[i].gen.lockmode,
@@ -640,6 +653,14 @@ initialize_reloptions(void)
j++;
}
+ for (i = 0; ternaryRelOpts[i].gen.name; i++)
+ {
+ relOpts[j] = &ternaryRelOpts[i].gen;
+ relOpts[j]->type = RELOPT_TYPE_TERNARY;
+ relOpts[j]->namelen = strlen(relOpts[j]->name);
+ j++;
+ }
+
for (i = 0; intRelOpts[i].gen.name; i++)
{
relOpts[j] = &intRelOpts[i].gen;
@@ -800,6 +821,9 @@ allocate_reloption(bits32 kinds, int type, const char *name, const char *desc,
case RELOPT_TYPE_BOOL:
size = sizeof(relopt_bool);
break;
+ case RELOPT_TYPE_TERNARY:
+ size = sizeof(relopt_ternary);
+ break;
case RELOPT_TYPE_INT:
size = sizeof(relopt_int);
break;
@@ -883,6 +907,54 @@ add_local_bool_reloption(local_relopts *relopts, const char *name,
add_local_reloption(relopts, (relopt_gen *) newoption, offset);
}
+/*
+ * init_ternary_reloption
+ * Allocate and initialize a new ternary reloption
+ */
+static relopt_ternary *
+init_ternary_reloption(bits32 kinds, const char *name, const char *desc,
+ ternary default_val, LOCKMODE lockmode)
+{
+ relopt_ternary *newoption;
+
+ newoption = (relopt_ternary *) allocate_reloption(kinds,
+ RELOPT_TYPE_TERNARY, name, desc, lockmode);
+ newoption->default_val = default_val;
+
+ return newoption;
+}
+
+/*
+ * add_ternary_reloption
+ * Add a new ternary reloption
+ */
+void
+add_ternary_reloption(bits32 kinds, const char *name, const char *desc,
+ ternary default_val, LOCKMODE lockmode)
+{
+ relopt_ternary *newoption = init_ternary_reloption(kinds, name, desc,
+ default_val, lockmode);
+
+ add_reloption((relopt_gen *) newoption);
+}
+
+/*
+ * add_local_ternary_reloption
+ * Add a new ternary local reloption
+ *
+ * 'offset' is offset of ternary-typed field.
+ */
+void
+add_local_ternary_reloption(local_relopts *relopts, const char *name,
+ const char *desc, ternary default_val,
+ int offset)
+{
+ relopt_ternary *newoption = init_ternary_reloption(RELOPT_KIND_LOCAL,
+ name, desc,
+ default_val, 0);
+
+ add_local_reloption(relopts, (relopt_gen *) newoption, offset);
+}
/*
* init_real_reloption
@@ -1617,6 +1689,18 @@ parse_one_reloption(relopt_value *option, char *text_str, int text_len,
option->gen->name, value)));
}
break;
+ case RELOPT_TYPE_TERNARY:
+ {
+ bool b;
+ parsed = parse_bool(value, &b);
+ option->values.ternary_val = b ? TERNARY_TRUE : TERNARY_FALSE;
+ if (validate && !parsed)
+ ereport(ERROR,
+ (errcode(ERRCODE_INVALID_PARAMETER_VALUE),
+ errmsg("invalid value for ternary option \"%s\": %s",
+ option->gen->name, value)));
+ }
+ break;
case RELOPT_TYPE_INT:
{
relopt_int *optint = (relopt_int *) option->gen;
@@ -1780,17 +1864,6 @@ fillRelOptions(void *rdopts, Size basesize,
char *itempos = ((char *) rdopts) + elems[j].offset;
char *string_val;
- /*
- * If isset_offset is provided, store whether the reloption is
- * set there.
- */
- if (elems[j].isset_offset > 0)
- {
- char *setpos = ((char *) rdopts) + elems[j].isset_offset;
-
- *(bool *) setpos = options[i].isset;
- }
-
switch (options[i].gen->type)
{
case RELOPT_TYPE_BOOL:
@@ -1798,6 +1871,11 @@ fillRelOptions(void *rdopts, Size basesize,
options[i].values.bool_val :
((relopt_bool *) options[i].gen)->default_val;
break;
+ case RELOPT_TYPE_TERNARY:
+ *(ternary *) itempos = options[i].isset ?
+ options[i].values.ternary_val :
+ ((relopt_ternary *) options[i].gen)->default_val;
+ break;
case RELOPT_TYPE_INT:
*(int *) itempos = options[i].isset ?
options[i].values.int_val :
@@ -1912,8 +1990,8 @@ default_reloptions(Datum reloptions, bool validate, relopt_kind kind)
offsetof(StdRdOptions, parallel_workers)},
{"vacuum_index_cleanup", RELOPT_TYPE_ENUM,
offsetof(StdRdOptions, vacuum_index_cleanup)},
- {"vacuum_truncate", RELOPT_TYPE_BOOL,
- offsetof(StdRdOptions, vacuum_truncate), offsetof(StdRdOptions, vacuum_truncate_set)},
+ {"vacuum_truncate", RELOPT_TYPE_TERNARY,
+ offsetof(StdRdOptions, vacuum_truncate)},
{"vacuum_max_eager_freeze_failure_rate", RELOPT_TYPE_REAL,
offsetof(StdRdOptions, vacuum_max_eager_freeze_failure_rate)}
};
@@ -1993,7 +2071,6 @@ build_local_reloptions(local_relopts *relopts, Datum options, bool validate)
elems[i].optname = opt->option->name;
elems[i].opttype = opt->option->type;
elems[i].offset = opt->offset;
- elems[i].isset_offset = 0; /* not supported for local relopts yet */
i++;
}
diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c
index 733ef40ae7..977babff54 100644
--- a/src/backend/commands/vacuum.c
+++ b/src/backend/commands/vacuum.c
@@ -2221,9 +2221,9 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams params,
{
StdRdOptions *opts = (StdRdOptions *) rel->rd_options;
- if (opts && opts->vacuum_truncate_set)
+ if (opts && opts->vacuum_truncate != TERNARY_UNSET)
{
- if (opts->vacuum_truncate)
+ if (opts->vacuum_truncate == TERNARY_TRUE)
params.truncate = VACOPTVALUE_ENABLED;
else
params.truncate = VACOPTVALUE_DISABLED;
diff --git a/src/include/access/reloptions.h b/src/include/access/reloptions.h
index a604a4702c..a436697658 100644
--- a/src/include/access/reloptions.h
+++ b/src/include/access/reloptions.h
@@ -29,6 +29,7 @@
typedef enum relopt_type
{
RELOPT_TYPE_BOOL,
+ RELOPT_TYPE_TERNARY, /* on, off, unset */
RELOPT_TYPE_INT,
RELOPT_TYPE_REAL,
RELOPT_TYPE_ENUM,
@@ -80,6 +81,7 @@ typedef struct relopt_value
union
{
bool bool_val;
+ ternary ternary_val;
int int_val;
double real_val;
int enum_val;
@@ -94,6 +96,12 @@ typedef struct relopt_bool
bool default_val;
} relopt_bool;
+typedef struct relopt_rernary
+{
+ relopt_gen gen;
+ int default_val;
+} relopt_ternary;
+
typedef struct relopt_int
{
relopt_gen gen;
@@ -152,19 +160,6 @@ typedef struct
const char *optname; /* option's name */
relopt_type opttype; /* option's datatype */
int offset; /* offset of field in result struct */
-
- /*
- * isset_offset is an optional offset of a field in the result struct that
- * stores whether the option is explicitly set for the relation or if it
- * just picked up the default value. In most cases, this can be
- * accomplished by giving the reloption a special out-of-range default
- * value (e.g., some integer reloptions use -2), but this isn't always
- * possible. For example, a Boolean reloption cannot be given an
- * out-of-range default, so we need another way to discover the source of
- * its value. This offset is only used if given a value greater than
- * zero.
- */
- int isset_offset;
} relopt_parse_elt;
/* Local reloption definition */
@@ -195,6 +190,8 @@ typedef struct local_relopts
extern relopt_kind add_reloption_kind(void);
extern void add_bool_reloption(bits32 kinds, const char *name, const char *desc,
bool default_val, LOCKMODE lockmode);
+extern void add_ternary_reloption(bits32 kinds, const char *name,
+ const char *desc, int default_val, LOCKMODE lockmode);
extern void add_int_reloption(bits32 kinds, const char *name, const char *desc,
int default_val, int min_val, int max_val,
LOCKMODE lockmode);
@@ -214,6 +211,9 @@ extern void register_reloptions_validator(local_relopts *relopts,
extern void add_local_bool_reloption(local_relopts *relopts, const char *name,
const char *desc, bool default_val,
int offset);
+extern void add_local_ternary_reloption(local_relopts *relopts,
+ const char *name, const char *desc,
+ ternary default_val, int offset);
extern void add_local_int_reloption(local_relopts *relopts, const char *name,
const char *desc, int default_val,
int min_val, int max_val, int offset);
diff --git a/src/include/c.h b/src/include/c.h
index 39022f8a9d..9c59ccccf8 100644
--- a/src/include/c.h
+++ b/src/include/c.h
@@ -488,6 +488,22 @@ typedef void (*pg_funcptr_t) (void);
#include <stdbool.h>
+/*
+ * ternary
+ * Boolean value with an extrea "unset" option
+ *
+ * Ternary data type is used in relation options that can be "true", "false" or
+ * "unset". Since relation options are used deep inside the PostgreSQL code,
+ * this type is declared globally.
+*/
+
+typedef enum ternary
+{
+ TERNARY_FALSE = 0,
+ TERNARY_TRUE = 1,
+ TERNARY_UNSET = -1
+} ternary;
+
/* ----------------------------------------------------------------
* Section 3: standard system types
diff --git a/src/include/utils/rel.h b/src/include/utils/rel.h
index b552359915..7346d618f9 100644
--- a/src/include/utils/rel.h
+++ b/src/include/utils/rel.h
@@ -346,8 +346,7 @@ typedef struct StdRdOptions
bool user_catalog_table; /* use as an additional catalog relation */
int parallel_workers; /* max number of parallel workers */
StdRdOptIndexCleanup vacuum_index_cleanup; /* controls index vacuuming */
- bool vacuum_truncate; /* enables vacuum to truncate a relation */
- bool vacuum_truncate_set; /* whether vacuum_truncate is set */
+ ternary vacuum_truncate; /* enables vacuum to truncate a relation */
/*
* Fraction of pages in a relation that vacuum can eagerly scan and fail
--
2.39.2
--nextPart5129932.5fSG56mABF
Content-Disposition: attachment;
filename="v2-0003-Add-alias-to-be-used-as-unset-state.patch"
Content-Transfer-Encoding: 7Bit
Content-Type: text/x-patch; charset="unicode-2-0-utf-8";
name="v2-0003-Add-alias-to-be-used-as-unset-state.patch"
^ permalink raw reply [nested|flat] 868+ messages in thread
* [PATCH v2 2/4] Introduce ternary reloptions
@ 2025-09-04 14:41 Nikolay Shaplov <[email protected]>
0 siblings, 0 replies; 868+ messages in thread
From: Nikolay Shaplov @ 2025-09-04 14:41 UTC (permalink / raw)
Introduce ternary reloption as a replacement for current `vacuum_truncate`
implementation. Remove `vacuum_truncate_set` additional flag and using
`TERNARY_UNSET` value instead.
---
src/backend/access/common/reloptions.c | 129 ++++++++++++++++++++-----
src/backend/commands/vacuum.c | 4 +-
src/include/access/reloptions.h | 26 ++---
src/include/c.h | 16 +++
src/include/utils/rel.h | 3 +-
5 files changed, 135 insertions(+), 43 deletions(-)
diff --git a/src/backend/access/common/reloptions.c b/src/backend/access/common/reloptions.c
index 0af3fea68f..f543f11001 100644
--- a/src/backend/access/common/reloptions.c
+++ b/src/backend/access/common/reloptions.c
@@ -40,9 +40,9 @@
*
* To add an option:
*
- * (i) decide on a type (bool, integer, real, enum, string), name, default
- * value, upper and lower bounds (if applicable); for strings, consider a
- * validation routine.
+ * (i) decide on a type (bool, ternary, integer, real, enum, string), name,
+ * default value, upper and lower bounds (if applicable); for strings,
+ * consider a validation routine.
* (ii) add a record below (or use add_<type>_reloption).
* (iii) add it to the appropriate options struct (perhaps StdRdOptions)
* (iv) add it to the appropriate handling routine (perhaps
@@ -147,15 +147,6 @@ static relopt_bool boolRelOpts[] =
},
false
},
- {
- {
- "vacuum_truncate",
- "Enables vacuum to truncate empty pages at the end of this table",
- RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
- ShareUpdateExclusiveLock
- },
- true
- },
{
{
"deduplicate_items",
@@ -170,6 +161,21 @@ static relopt_bool boolRelOpts[] =
{{NULL}}
};
+static relopt_ternary ternaryRelOpts[] =
+{
+ {
+ {
+ "vacuum_truncate",
+ "Enables vacuum to truncate empty pages at the end of this table",
+ RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
+ ShareUpdateExclusiveLock
+ },
+ TERNARY_UNSET
+ },
+ /* list terminator */
+ {{NULL}}
+};
+
static relopt_int intRelOpts[] =
{
{
@@ -600,6 +606,13 @@ initialize_reloptions(void)
boolRelOpts[i].gen.lockmode));
j++;
}
+ for (i = 0; ternaryRelOpts[i].gen.name; i++)
+ {
+ Assert(DoLockModesConflict(ternaryRelOpts[i].gen.lockmode,
+ ternaryRelOpts[i].gen.lockmode));
+ j++;
+ }
+
for (i = 0; intRelOpts[i].gen.name; i++)
{
Assert(DoLockModesConflict(intRelOpts[i].gen.lockmode,
@@ -640,6 +653,14 @@ initialize_reloptions(void)
j++;
}
+ for (i = 0; ternaryRelOpts[i].gen.name; i++)
+ {
+ relOpts[j] = &ternaryRelOpts[i].gen;
+ relOpts[j]->type = RELOPT_TYPE_TERNARY;
+ relOpts[j]->namelen = strlen(relOpts[j]->name);
+ j++;
+ }
+
for (i = 0; intRelOpts[i].gen.name; i++)
{
relOpts[j] = &intRelOpts[i].gen;
@@ -800,6 +821,9 @@ allocate_reloption(bits32 kinds, int type, const char *name, const char *desc,
case RELOPT_TYPE_BOOL:
size = sizeof(relopt_bool);
break;
+ case RELOPT_TYPE_TERNARY:
+ size = sizeof(relopt_ternary);
+ break;
case RELOPT_TYPE_INT:
size = sizeof(relopt_int);
break;
@@ -883,6 +907,54 @@ add_local_bool_reloption(local_relopts *relopts, const char *name,
add_local_reloption(relopts, (relopt_gen *) newoption, offset);
}
+/*
+ * init_ternary_reloption
+ * Allocate and initialize a new ternary reloption
+ */
+static relopt_ternary *
+init_ternary_reloption(bits32 kinds, const char *name, const char *desc,
+ ternary default_val, LOCKMODE lockmode)
+{
+ relopt_ternary *newoption;
+
+ newoption = (relopt_ternary *) allocate_reloption(kinds,
+ RELOPT_TYPE_TERNARY, name, desc, lockmode);
+ newoption->default_val = default_val;
+
+ return newoption;
+}
+
+/*
+ * add_ternary_reloption
+ * Add a new ternary reloption
+ */
+void
+add_ternary_reloption(bits32 kinds, const char *name, const char *desc,
+ ternary default_val, LOCKMODE lockmode)
+{
+ relopt_ternary *newoption = init_ternary_reloption(kinds, name, desc,
+ default_val, lockmode);
+
+ add_reloption((relopt_gen *) newoption);
+}
+
+/*
+ * add_local_ternary_reloption
+ * Add a new ternary local reloption
+ *
+ * 'offset' is offset of ternary-typed field.
+ */
+void
+add_local_ternary_reloption(local_relopts *relopts, const char *name,
+ const char *desc, ternary default_val,
+ int offset)
+{
+ relopt_ternary *newoption = init_ternary_reloption(RELOPT_KIND_LOCAL,
+ name, desc,
+ default_val, 0);
+
+ add_local_reloption(relopts, (relopt_gen *) newoption, offset);
+}
/*
* init_real_reloption
@@ -1617,6 +1689,18 @@ parse_one_reloption(relopt_value *option, char *text_str, int text_len,
option->gen->name, value)));
}
break;
+ case RELOPT_TYPE_TERNARY:
+ {
+ bool b;
+ parsed = parse_bool(value, &b);
+ option->values.ternary_val = b ? TERNARY_TRUE : TERNARY_FALSE;
+ if (validate && !parsed)
+ ereport(ERROR,
+ (errcode(ERRCODE_INVALID_PARAMETER_VALUE),
+ errmsg("invalid value for ternary option \"%s\": %s",
+ option->gen->name, value)));
+ }
+ break;
case RELOPT_TYPE_INT:
{
relopt_int *optint = (relopt_int *) option->gen;
@@ -1780,17 +1864,6 @@ fillRelOptions(void *rdopts, Size basesize,
char *itempos = ((char *) rdopts) + elems[j].offset;
char *string_val;
- /*
- * If isset_offset is provided, store whether the reloption is
- * set there.
- */
- if (elems[j].isset_offset > 0)
- {
- char *setpos = ((char *) rdopts) + elems[j].isset_offset;
-
- *(bool *) setpos = options[i].isset;
- }
-
switch (options[i].gen->type)
{
case RELOPT_TYPE_BOOL:
@@ -1798,6 +1871,11 @@ fillRelOptions(void *rdopts, Size basesize,
options[i].values.bool_val :
((relopt_bool *) options[i].gen)->default_val;
break;
+ case RELOPT_TYPE_TERNARY:
+ *(ternary *) itempos = options[i].isset ?
+ options[i].values.ternary_val :
+ ((relopt_ternary *) options[i].gen)->default_val;
+ break;
case RELOPT_TYPE_INT:
*(int *) itempos = options[i].isset ?
options[i].values.int_val :
@@ -1912,8 +1990,8 @@ default_reloptions(Datum reloptions, bool validate, relopt_kind kind)
offsetof(StdRdOptions, parallel_workers)},
{"vacuum_index_cleanup", RELOPT_TYPE_ENUM,
offsetof(StdRdOptions, vacuum_index_cleanup)},
- {"vacuum_truncate", RELOPT_TYPE_BOOL,
- offsetof(StdRdOptions, vacuum_truncate), offsetof(StdRdOptions, vacuum_truncate_set)},
+ {"vacuum_truncate", RELOPT_TYPE_TERNARY,
+ offsetof(StdRdOptions, vacuum_truncate)},
{"vacuum_max_eager_freeze_failure_rate", RELOPT_TYPE_REAL,
offsetof(StdRdOptions, vacuum_max_eager_freeze_failure_rate)}
};
@@ -1993,7 +2071,6 @@ build_local_reloptions(local_relopts *relopts, Datum options, bool validate)
elems[i].optname = opt->option->name;
elems[i].opttype = opt->option->type;
elems[i].offset = opt->offset;
- elems[i].isset_offset = 0; /* not supported for local relopts yet */
i++;
}
diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c
index 733ef40ae7..977babff54 100644
--- a/src/backend/commands/vacuum.c
+++ b/src/backend/commands/vacuum.c
@@ -2221,9 +2221,9 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams params,
{
StdRdOptions *opts = (StdRdOptions *) rel->rd_options;
- if (opts && opts->vacuum_truncate_set)
+ if (opts && opts->vacuum_truncate != TERNARY_UNSET)
{
- if (opts->vacuum_truncate)
+ if (opts->vacuum_truncate == TERNARY_TRUE)
params.truncate = VACOPTVALUE_ENABLED;
else
params.truncate = VACOPTVALUE_DISABLED;
diff --git a/src/include/access/reloptions.h b/src/include/access/reloptions.h
index a604a4702c..a436697658 100644
--- a/src/include/access/reloptions.h
+++ b/src/include/access/reloptions.h
@@ -29,6 +29,7 @@
typedef enum relopt_type
{
RELOPT_TYPE_BOOL,
+ RELOPT_TYPE_TERNARY, /* on, off, unset */
RELOPT_TYPE_INT,
RELOPT_TYPE_REAL,
RELOPT_TYPE_ENUM,
@@ -80,6 +81,7 @@ typedef struct relopt_value
union
{
bool bool_val;
+ ternary ternary_val;
int int_val;
double real_val;
int enum_val;
@@ -94,6 +96,12 @@ typedef struct relopt_bool
bool default_val;
} relopt_bool;
+typedef struct relopt_rernary
+{
+ relopt_gen gen;
+ int default_val;
+} relopt_ternary;
+
typedef struct relopt_int
{
relopt_gen gen;
@@ -152,19 +160,6 @@ typedef struct
const char *optname; /* option's name */
relopt_type opttype; /* option's datatype */
int offset; /* offset of field in result struct */
-
- /*
- * isset_offset is an optional offset of a field in the result struct that
- * stores whether the option is explicitly set for the relation or if it
- * just picked up the default value. In most cases, this can be
- * accomplished by giving the reloption a special out-of-range default
- * value (e.g., some integer reloptions use -2), but this isn't always
- * possible. For example, a Boolean reloption cannot be given an
- * out-of-range default, so we need another way to discover the source of
- * its value. This offset is only used if given a value greater than
- * zero.
- */
- int isset_offset;
} relopt_parse_elt;
/* Local reloption definition */
@@ -195,6 +190,8 @@ typedef struct local_relopts
extern relopt_kind add_reloption_kind(void);
extern void add_bool_reloption(bits32 kinds, const char *name, const char *desc,
bool default_val, LOCKMODE lockmode);
+extern void add_ternary_reloption(bits32 kinds, const char *name,
+ const char *desc, int default_val, LOCKMODE lockmode);
extern void add_int_reloption(bits32 kinds, const char *name, const char *desc,
int default_val, int min_val, int max_val,
LOCKMODE lockmode);
@@ -214,6 +211,9 @@ extern void register_reloptions_validator(local_relopts *relopts,
extern void add_local_bool_reloption(local_relopts *relopts, const char *name,
const char *desc, bool default_val,
int offset);
+extern void add_local_ternary_reloption(local_relopts *relopts,
+ const char *name, const char *desc,
+ ternary default_val, int offset);
extern void add_local_int_reloption(local_relopts *relopts, const char *name,
const char *desc, int default_val,
int min_val, int max_val, int offset);
diff --git a/src/include/c.h b/src/include/c.h
index 39022f8a9d..9c59ccccf8 100644
--- a/src/include/c.h
+++ b/src/include/c.h
@@ -488,6 +488,22 @@ typedef void (*pg_funcptr_t) (void);
#include <stdbool.h>
+/*
+ * ternary
+ * Boolean value with an extrea "unset" option
+ *
+ * Ternary data type is used in relation options that can be "true", "false" or
+ * "unset". Since relation options are used deep inside the PostgreSQL code,
+ * this type is declared globally.
+*/
+
+typedef enum ternary
+{
+ TERNARY_FALSE = 0,
+ TERNARY_TRUE = 1,
+ TERNARY_UNSET = -1
+} ternary;
+
/* ----------------------------------------------------------------
* Section 3: standard system types
diff --git a/src/include/utils/rel.h b/src/include/utils/rel.h
index b552359915..7346d618f9 100644
--- a/src/include/utils/rel.h
+++ b/src/include/utils/rel.h
@@ -346,8 +346,7 @@ typedef struct StdRdOptions
bool user_catalog_table; /* use as an additional catalog relation */
int parallel_workers; /* max number of parallel workers */
StdRdOptIndexCleanup vacuum_index_cleanup; /* controls index vacuuming */
- bool vacuum_truncate; /* enables vacuum to truncate a relation */
- bool vacuum_truncate_set; /* whether vacuum_truncate is set */
+ ternary vacuum_truncate; /* enables vacuum to truncate a relation */
/*
* Fraction of pages in a relation that vacuum can eagerly scan and fail
--
2.39.2
--nextPart5129932.5fSG56mABF
Content-Disposition: attachment;
filename="v2-0003-Add-alias-to-be-used-as-unset-state.patch"
Content-Transfer-Encoding: 7Bit
Content-Type: text/x-patch; charset="unicode-2-0-utf-8";
name="v2-0003-Add-alias-to-be-used-as-unset-state.patch"
^ permalink raw reply [nested|flat] 868+ messages in thread
* [PATCH v2a 2/4] Introduce ternary reloptions
@ 2025-09-04 14:41 Nikolay Shaplov <[email protected]>
0 siblings, 0 replies; 868+ messages in thread
From: Nikolay Shaplov @ 2025-09-04 14:41 UTC (permalink / raw)
Introduce ternary reloption as a replacement for current `vacuum_truncate`
implementation. Remove `vacuum_truncate_set` additional flag and using
`TERNARY_UNSET` value instead.
---
src/backend/access/common/reloptions.c | 129 ++++++++++++++++++++-----
src/backend/commands/vacuum.c | 4 +-
src/include/access/reloptions.h | 26 ++---
src/include/c.h | 16 +++
src/include/utils/rel.h | 3 +-
5 files changed, 135 insertions(+), 43 deletions(-)
diff --git a/src/backend/access/common/reloptions.c b/src/backend/access/common/reloptions.c
index 0af3fea68f..f543f11001 100644
--- a/src/backend/access/common/reloptions.c
+++ b/src/backend/access/common/reloptions.c
@@ -40,9 +40,9 @@
*
* To add an option:
*
- * (i) decide on a type (bool, integer, real, enum, string), name, default
- * value, upper and lower bounds (if applicable); for strings, consider a
- * validation routine.
+ * (i) decide on a type (bool, ternary, integer, real, enum, string), name,
+ * default value, upper and lower bounds (if applicable); for strings,
+ * consider a validation routine.
* (ii) add a record below (or use add_<type>_reloption).
* (iii) add it to the appropriate options struct (perhaps StdRdOptions)
* (iv) add it to the appropriate handling routine (perhaps
@@ -147,15 +147,6 @@ static relopt_bool boolRelOpts[] =
},
false
},
- {
- {
- "vacuum_truncate",
- "Enables vacuum to truncate empty pages at the end of this table",
- RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
- ShareUpdateExclusiveLock
- },
- true
- },
{
{
"deduplicate_items",
@@ -170,6 +161,21 @@ static relopt_bool boolRelOpts[] =
{{NULL}}
};
+static relopt_ternary ternaryRelOpts[] =
+{
+ {
+ {
+ "vacuum_truncate",
+ "Enables vacuum to truncate empty pages at the end of this table",
+ RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
+ ShareUpdateExclusiveLock
+ },
+ TERNARY_UNSET
+ },
+ /* list terminator */
+ {{NULL}}
+};
+
static relopt_int intRelOpts[] =
{
{
@@ -600,6 +606,13 @@ initialize_reloptions(void)
boolRelOpts[i].gen.lockmode));
j++;
}
+ for (i = 0; ternaryRelOpts[i].gen.name; i++)
+ {
+ Assert(DoLockModesConflict(ternaryRelOpts[i].gen.lockmode,
+ ternaryRelOpts[i].gen.lockmode));
+ j++;
+ }
+
for (i = 0; intRelOpts[i].gen.name; i++)
{
Assert(DoLockModesConflict(intRelOpts[i].gen.lockmode,
@@ -640,6 +653,14 @@ initialize_reloptions(void)
j++;
}
+ for (i = 0; ternaryRelOpts[i].gen.name; i++)
+ {
+ relOpts[j] = &ternaryRelOpts[i].gen;
+ relOpts[j]->type = RELOPT_TYPE_TERNARY;
+ relOpts[j]->namelen = strlen(relOpts[j]->name);
+ j++;
+ }
+
for (i = 0; intRelOpts[i].gen.name; i++)
{
relOpts[j] = &intRelOpts[i].gen;
@@ -800,6 +821,9 @@ allocate_reloption(bits32 kinds, int type, const char *name, const char *desc,
case RELOPT_TYPE_BOOL:
size = sizeof(relopt_bool);
break;
+ case RELOPT_TYPE_TERNARY:
+ size = sizeof(relopt_ternary);
+ break;
case RELOPT_TYPE_INT:
size = sizeof(relopt_int);
break;
@@ -883,6 +907,54 @@ add_local_bool_reloption(local_relopts *relopts, const char *name,
add_local_reloption(relopts, (relopt_gen *) newoption, offset);
}
+/*
+ * init_ternary_reloption
+ * Allocate and initialize a new ternary reloption
+ */
+static relopt_ternary *
+init_ternary_reloption(bits32 kinds, const char *name, const char *desc,
+ ternary default_val, LOCKMODE lockmode)
+{
+ relopt_ternary *newoption;
+
+ newoption = (relopt_ternary *) allocate_reloption(kinds,
+ RELOPT_TYPE_TERNARY, name, desc, lockmode);
+ newoption->default_val = default_val;
+
+ return newoption;
+}
+
+/*
+ * add_ternary_reloption
+ * Add a new ternary reloption
+ */
+void
+add_ternary_reloption(bits32 kinds, const char *name, const char *desc,
+ ternary default_val, LOCKMODE lockmode)
+{
+ relopt_ternary *newoption = init_ternary_reloption(kinds, name, desc,
+ default_val, lockmode);
+
+ add_reloption((relopt_gen *) newoption);
+}
+
+/*
+ * add_local_ternary_reloption
+ * Add a new ternary local reloption
+ *
+ * 'offset' is offset of ternary-typed field.
+ */
+void
+add_local_ternary_reloption(local_relopts *relopts, const char *name,
+ const char *desc, ternary default_val,
+ int offset)
+{
+ relopt_ternary *newoption = init_ternary_reloption(RELOPT_KIND_LOCAL,
+ name, desc,
+ default_val, 0);
+
+ add_local_reloption(relopts, (relopt_gen *) newoption, offset);
+}
/*
* init_real_reloption
@@ -1617,6 +1689,18 @@ parse_one_reloption(relopt_value *option, char *text_str, int text_len,
option->gen->name, value)));
}
break;
+ case RELOPT_TYPE_TERNARY:
+ {
+ bool b;
+ parsed = parse_bool(value, &b);
+ option->values.ternary_val = b ? TERNARY_TRUE : TERNARY_FALSE;
+ if (validate && !parsed)
+ ereport(ERROR,
+ (errcode(ERRCODE_INVALID_PARAMETER_VALUE),
+ errmsg("invalid value for ternary option \"%s\": %s",
+ option->gen->name, value)));
+ }
+ break;
case RELOPT_TYPE_INT:
{
relopt_int *optint = (relopt_int *) option->gen;
@@ -1780,17 +1864,6 @@ fillRelOptions(void *rdopts, Size basesize,
char *itempos = ((char *) rdopts) + elems[j].offset;
char *string_val;
- /*
- * If isset_offset is provided, store whether the reloption is
- * set there.
- */
- if (elems[j].isset_offset > 0)
- {
- char *setpos = ((char *) rdopts) + elems[j].isset_offset;
-
- *(bool *) setpos = options[i].isset;
- }
-
switch (options[i].gen->type)
{
case RELOPT_TYPE_BOOL:
@@ -1798,6 +1871,11 @@ fillRelOptions(void *rdopts, Size basesize,
options[i].values.bool_val :
((relopt_bool *) options[i].gen)->default_val;
break;
+ case RELOPT_TYPE_TERNARY:
+ *(ternary *) itempos = options[i].isset ?
+ options[i].values.ternary_val :
+ ((relopt_ternary *) options[i].gen)->default_val;
+ break;
case RELOPT_TYPE_INT:
*(int *) itempos = options[i].isset ?
options[i].values.int_val :
@@ -1912,8 +1990,8 @@ default_reloptions(Datum reloptions, bool validate, relopt_kind kind)
offsetof(StdRdOptions, parallel_workers)},
{"vacuum_index_cleanup", RELOPT_TYPE_ENUM,
offsetof(StdRdOptions, vacuum_index_cleanup)},
- {"vacuum_truncate", RELOPT_TYPE_BOOL,
- offsetof(StdRdOptions, vacuum_truncate), offsetof(StdRdOptions, vacuum_truncate_set)},
+ {"vacuum_truncate", RELOPT_TYPE_TERNARY,
+ offsetof(StdRdOptions, vacuum_truncate)},
{"vacuum_max_eager_freeze_failure_rate", RELOPT_TYPE_REAL,
offsetof(StdRdOptions, vacuum_max_eager_freeze_failure_rate)}
};
@@ -1993,7 +2071,6 @@ build_local_reloptions(local_relopts *relopts, Datum options, bool validate)
elems[i].optname = opt->option->name;
elems[i].opttype = opt->option->type;
elems[i].offset = opt->offset;
- elems[i].isset_offset = 0; /* not supported for local relopts yet */
i++;
}
diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c
index 733ef40ae7..977babff54 100644
--- a/src/backend/commands/vacuum.c
+++ b/src/backend/commands/vacuum.c
@@ -2221,9 +2221,9 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams params,
{
StdRdOptions *opts = (StdRdOptions *) rel->rd_options;
- if (opts && opts->vacuum_truncate_set)
+ if (opts && opts->vacuum_truncate != TERNARY_UNSET)
{
- if (opts->vacuum_truncate)
+ if (opts->vacuum_truncate == TERNARY_TRUE)
params.truncate = VACOPTVALUE_ENABLED;
else
params.truncate = VACOPTVALUE_DISABLED;
diff --git a/src/include/access/reloptions.h b/src/include/access/reloptions.h
index a604a4702c..a436697658 100644
--- a/src/include/access/reloptions.h
+++ b/src/include/access/reloptions.h
@@ -29,6 +29,7 @@
typedef enum relopt_type
{
RELOPT_TYPE_BOOL,
+ RELOPT_TYPE_TERNARY, /* on, off, unset */
RELOPT_TYPE_INT,
RELOPT_TYPE_REAL,
RELOPT_TYPE_ENUM,
@@ -80,6 +81,7 @@ typedef struct relopt_value
union
{
bool bool_val;
+ ternary ternary_val;
int int_val;
double real_val;
int enum_val;
@@ -94,6 +96,12 @@ typedef struct relopt_bool
bool default_val;
} relopt_bool;
+typedef struct relopt_rernary
+{
+ relopt_gen gen;
+ int default_val;
+} relopt_ternary;
+
typedef struct relopt_int
{
relopt_gen gen;
@@ -152,19 +160,6 @@ typedef struct
const char *optname; /* option's name */
relopt_type opttype; /* option's datatype */
int offset; /* offset of field in result struct */
-
- /*
- * isset_offset is an optional offset of a field in the result struct that
- * stores whether the option is explicitly set for the relation or if it
- * just picked up the default value. In most cases, this can be
- * accomplished by giving the reloption a special out-of-range default
- * value (e.g., some integer reloptions use -2), but this isn't always
- * possible. For example, a Boolean reloption cannot be given an
- * out-of-range default, so we need another way to discover the source of
- * its value. This offset is only used if given a value greater than
- * zero.
- */
- int isset_offset;
} relopt_parse_elt;
/* Local reloption definition */
@@ -195,6 +190,8 @@ typedef struct local_relopts
extern relopt_kind add_reloption_kind(void);
extern void add_bool_reloption(bits32 kinds, const char *name, const char *desc,
bool default_val, LOCKMODE lockmode);
+extern void add_ternary_reloption(bits32 kinds, const char *name,
+ const char *desc, int default_val, LOCKMODE lockmode);
extern void add_int_reloption(bits32 kinds, const char *name, const char *desc,
int default_val, int min_val, int max_val,
LOCKMODE lockmode);
@@ -214,6 +211,9 @@ extern void register_reloptions_validator(local_relopts *relopts,
extern void add_local_bool_reloption(local_relopts *relopts, const char *name,
const char *desc, bool default_val,
int offset);
+extern void add_local_ternary_reloption(local_relopts *relopts,
+ const char *name, const char *desc,
+ ternary default_val, int offset);
extern void add_local_int_reloption(local_relopts *relopts, const char *name,
const char *desc, int default_val,
int min_val, int max_val, int offset);
diff --git a/src/include/c.h b/src/include/c.h
index 39022f8a9d..9c59ccccf8 100644
--- a/src/include/c.h
+++ b/src/include/c.h
@@ -488,6 +488,22 @@ typedef void (*pg_funcptr_t) (void);
#include <stdbool.h>
+/*
+ * ternary
+ * Boolean value with an extrea "unset" option
+ *
+ * Ternary data type is used in relation options that can be "true", "false" or
+ * "unset". Since relation options are used deep inside the PostgreSQL code,
+ * this type is declared globally.
+*/
+
+typedef enum ternary
+{
+ TERNARY_FALSE = 0,
+ TERNARY_TRUE = 1,
+ TERNARY_UNSET = -1
+} ternary;
+
/* ----------------------------------------------------------------
* Section 3: standard system types
diff --git a/src/include/utils/rel.h b/src/include/utils/rel.h
index b552359915..7346d618f9 100644
--- a/src/include/utils/rel.h
+++ b/src/include/utils/rel.h
@@ -346,8 +346,7 @@ typedef struct StdRdOptions
bool user_catalog_table; /* use as an additional catalog relation */
int parallel_workers; /* max number of parallel workers */
StdRdOptIndexCleanup vacuum_index_cleanup; /* controls index vacuuming */
- bool vacuum_truncate; /* enables vacuum to truncate a relation */
- bool vacuum_truncate_set; /* whether vacuum_truncate is set */
+ ternary vacuum_truncate; /* enables vacuum to truncate a relation */
/*
* Fraction of pages in a relation that vacuum can eagerly scan and fail
--
2.39.2
--nextPart4155600.3ZeAukHxDK
Content-Disposition: attachment;
filename="v2a-0003-Add-alias-to-be-used-as-unset-state.patch"
Content-Transfer-Encoding: 7Bit
Content-Type: text/x-patch; charset="unicode-2-0-utf-8";
name="v2a-0003-Add-alias-to-be-used-as-unset-state.patch"
^ permalink raw reply [nested|flat] 868+ messages in thread
* [PATCH v2a 2/4] Introduce ternary reloptions
@ 2025-09-04 14:41 Nikolay Shaplov <[email protected]>
0 siblings, 0 replies; 868+ messages in thread
From: Nikolay Shaplov @ 2025-09-04 14:41 UTC (permalink / raw)
Introduce ternary reloption as a replacement for current `vacuum_truncate`
implementation. Remove `vacuum_truncate_set` additional flag and using
`TERNARY_UNSET` value instead.
---
src/backend/access/common/reloptions.c | 129 ++++++++++++++++++++-----
src/backend/commands/vacuum.c | 4 +-
src/include/access/reloptions.h | 26 ++---
src/include/c.h | 16 +++
src/include/utils/rel.h | 3 +-
5 files changed, 135 insertions(+), 43 deletions(-)
diff --git a/src/backend/access/common/reloptions.c b/src/backend/access/common/reloptions.c
index 0af3fea68f..f543f11001 100644
--- a/src/backend/access/common/reloptions.c
+++ b/src/backend/access/common/reloptions.c
@@ -40,9 +40,9 @@
*
* To add an option:
*
- * (i) decide on a type (bool, integer, real, enum, string), name, default
- * value, upper and lower bounds (if applicable); for strings, consider a
- * validation routine.
+ * (i) decide on a type (bool, ternary, integer, real, enum, string), name,
+ * default value, upper and lower bounds (if applicable); for strings,
+ * consider a validation routine.
* (ii) add a record below (or use add_<type>_reloption).
* (iii) add it to the appropriate options struct (perhaps StdRdOptions)
* (iv) add it to the appropriate handling routine (perhaps
@@ -147,15 +147,6 @@ static relopt_bool boolRelOpts[] =
},
false
},
- {
- {
- "vacuum_truncate",
- "Enables vacuum to truncate empty pages at the end of this table",
- RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
- ShareUpdateExclusiveLock
- },
- true
- },
{
{
"deduplicate_items",
@@ -170,6 +161,21 @@ static relopt_bool boolRelOpts[] =
{{NULL}}
};
+static relopt_ternary ternaryRelOpts[] =
+{
+ {
+ {
+ "vacuum_truncate",
+ "Enables vacuum to truncate empty pages at the end of this table",
+ RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
+ ShareUpdateExclusiveLock
+ },
+ TERNARY_UNSET
+ },
+ /* list terminator */
+ {{NULL}}
+};
+
static relopt_int intRelOpts[] =
{
{
@@ -600,6 +606,13 @@ initialize_reloptions(void)
boolRelOpts[i].gen.lockmode));
j++;
}
+ for (i = 0; ternaryRelOpts[i].gen.name; i++)
+ {
+ Assert(DoLockModesConflict(ternaryRelOpts[i].gen.lockmode,
+ ternaryRelOpts[i].gen.lockmode));
+ j++;
+ }
+
for (i = 0; intRelOpts[i].gen.name; i++)
{
Assert(DoLockModesConflict(intRelOpts[i].gen.lockmode,
@@ -640,6 +653,14 @@ initialize_reloptions(void)
j++;
}
+ for (i = 0; ternaryRelOpts[i].gen.name; i++)
+ {
+ relOpts[j] = &ternaryRelOpts[i].gen;
+ relOpts[j]->type = RELOPT_TYPE_TERNARY;
+ relOpts[j]->namelen = strlen(relOpts[j]->name);
+ j++;
+ }
+
for (i = 0; intRelOpts[i].gen.name; i++)
{
relOpts[j] = &intRelOpts[i].gen;
@@ -800,6 +821,9 @@ allocate_reloption(bits32 kinds, int type, const char *name, const char *desc,
case RELOPT_TYPE_BOOL:
size = sizeof(relopt_bool);
break;
+ case RELOPT_TYPE_TERNARY:
+ size = sizeof(relopt_ternary);
+ break;
case RELOPT_TYPE_INT:
size = sizeof(relopt_int);
break;
@@ -883,6 +907,54 @@ add_local_bool_reloption(local_relopts *relopts, const char *name,
add_local_reloption(relopts, (relopt_gen *) newoption, offset);
}
+/*
+ * init_ternary_reloption
+ * Allocate and initialize a new ternary reloption
+ */
+static relopt_ternary *
+init_ternary_reloption(bits32 kinds, const char *name, const char *desc,
+ ternary default_val, LOCKMODE lockmode)
+{
+ relopt_ternary *newoption;
+
+ newoption = (relopt_ternary *) allocate_reloption(kinds,
+ RELOPT_TYPE_TERNARY, name, desc, lockmode);
+ newoption->default_val = default_val;
+
+ return newoption;
+}
+
+/*
+ * add_ternary_reloption
+ * Add a new ternary reloption
+ */
+void
+add_ternary_reloption(bits32 kinds, const char *name, const char *desc,
+ ternary default_val, LOCKMODE lockmode)
+{
+ relopt_ternary *newoption = init_ternary_reloption(kinds, name, desc,
+ default_val, lockmode);
+
+ add_reloption((relopt_gen *) newoption);
+}
+
+/*
+ * add_local_ternary_reloption
+ * Add a new ternary local reloption
+ *
+ * 'offset' is offset of ternary-typed field.
+ */
+void
+add_local_ternary_reloption(local_relopts *relopts, const char *name,
+ const char *desc, ternary default_val,
+ int offset)
+{
+ relopt_ternary *newoption = init_ternary_reloption(RELOPT_KIND_LOCAL,
+ name, desc,
+ default_val, 0);
+
+ add_local_reloption(relopts, (relopt_gen *) newoption, offset);
+}
/*
* init_real_reloption
@@ -1617,6 +1689,18 @@ parse_one_reloption(relopt_value *option, char *text_str, int text_len,
option->gen->name, value)));
}
break;
+ case RELOPT_TYPE_TERNARY:
+ {
+ bool b;
+ parsed = parse_bool(value, &b);
+ option->values.ternary_val = b ? TERNARY_TRUE : TERNARY_FALSE;
+ if (validate && !parsed)
+ ereport(ERROR,
+ (errcode(ERRCODE_INVALID_PARAMETER_VALUE),
+ errmsg("invalid value for ternary option \"%s\": %s",
+ option->gen->name, value)));
+ }
+ break;
case RELOPT_TYPE_INT:
{
relopt_int *optint = (relopt_int *) option->gen;
@@ -1780,17 +1864,6 @@ fillRelOptions(void *rdopts, Size basesize,
char *itempos = ((char *) rdopts) + elems[j].offset;
char *string_val;
- /*
- * If isset_offset is provided, store whether the reloption is
- * set there.
- */
- if (elems[j].isset_offset > 0)
- {
- char *setpos = ((char *) rdopts) + elems[j].isset_offset;
-
- *(bool *) setpos = options[i].isset;
- }
-
switch (options[i].gen->type)
{
case RELOPT_TYPE_BOOL:
@@ -1798,6 +1871,11 @@ fillRelOptions(void *rdopts, Size basesize,
options[i].values.bool_val :
((relopt_bool *) options[i].gen)->default_val;
break;
+ case RELOPT_TYPE_TERNARY:
+ *(ternary *) itempos = options[i].isset ?
+ options[i].values.ternary_val :
+ ((relopt_ternary *) options[i].gen)->default_val;
+ break;
case RELOPT_TYPE_INT:
*(int *) itempos = options[i].isset ?
options[i].values.int_val :
@@ -1912,8 +1990,8 @@ default_reloptions(Datum reloptions, bool validate, relopt_kind kind)
offsetof(StdRdOptions, parallel_workers)},
{"vacuum_index_cleanup", RELOPT_TYPE_ENUM,
offsetof(StdRdOptions, vacuum_index_cleanup)},
- {"vacuum_truncate", RELOPT_TYPE_BOOL,
- offsetof(StdRdOptions, vacuum_truncate), offsetof(StdRdOptions, vacuum_truncate_set)},
+ {"vacuum_truncate", RELOPT_TYPE_TERNARY,
+ offsetof(StdRdOptions, vacuum_truncate)},
{"vacuum_max_eager_freeze_failure_rate", RELOPT_TYPE_REAL,
offsetof(StdRdOptions, vacuum_max_eager_freeze_failure_rate)}
};
@@ -1993,7 +2071,6 @@ build_local_reloptions(local_relopts *relopts, Datum options, bool validate)
elems[i].optname = opt->option->name;
elems[i].opttype = opt->option->type;
elems[i].offset = opt->offset;
- elems[i].isset_offset = 0; /* not supported for local relopts yet */
i++;
}
diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c
index 733ef40ae7..977babff54 100644
--- a/src/backend/commands/vacuum.c
+++ b/src/backend/commands/vacuum.c
@@ -2221,9 +2221,9 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams params,
{
StdRdOptions *opts = (StdRdOptions *) rel->rd_options;
- if (opts && opts->vacuum_truncate_set)
+ if (opts && opts->vacuum_truncate != TERNARY_UNSET)
{
- if (opts->vacuum_truncate)
+ if (opts->vacuum_truncate == TERNARY_TRUE)
params.truncate = VACOPTVALUE_ENABLED;
else
params.truncate = VACOPTVALUE_DISABLED;
diff --git a/src/include/access/reloptions.h b/src/include/access/reloptions.h
index a604a4702c..a436697658 100644
--- a/src/include/access/reloptions.h
+++ b/src/include/access/reloptions.h
@@ -29,6 +29,7 @@
typedef enum relopt_type
{
RELOPT_TYPE_BOOL,
+ RELOPT_TYPE_TERNARY, /* on, off, unset */
RELOPT_TYPE_INT,
RELOPT_TYPE_REAL,
RELOPT_TYPE_ENUM,
@@ -80,6 +81,7 @@ typedef struct relopt_value
union
{
bool bool_val;
+ ternary ternary_val;
int int_val;
double real_val;
int enum_val;
@@ -94,6 +96,12 @@ typedef struct relopt_bool
bool default_val;
} relopt_bool;
+typedef struct relopt_rernary
+{
+ relopt_gen gen;
+ int default_val;
+} relopt_ternary;
+
typedef struct relopt_int
{
relopt_gen gen;
@@ -152,19 +160,6 @@ typedef struct
const char *optname; /* option's name */
relopt_type opttype; /* option's datatype */
int offset; /* offset of field in result struct */
-
- /*
- * isset_offset is an optional offset of a field in the result struct that
- * stores whether the option is explicitly set for the relation or if it
- * just picked up the default value. In most cases, this can be
- * accomplished by giving the reloption a special out-of-range default
- * value (e.g., some integer reloptions use -2), but this isn't always
- * possible. For example, a Boolean reloption cannot be given an
- * out-of-range default, so we need another way to discover the source of
- * its value. This offset is only used if given a value greater than
- * zero.
- */
- int isset_offset;
} relopt_parse_elt;
/* Local reloption definition */
@@ -195,6 +190,8 @@ typedef struct local_relopts
extern relopt_kind add_reloption_kind(void);
extern void add_bool_reloption(bits32 kinds, const char *name, const char *desc,
bool default_val, LOCKMODE lockmode);
+extern void add_ternary_reloption(bits32 kinds, const char *name,
+ const char *desc, int default_val, LOCKMODE lockmode);
extern void add_int_reloption(bits32 kinds, const char *name, const char *desc,
int default_val, int min_val, int max_val,
LOCKMODE lockmode);
@@ -214,6 +211,9 @@ extern void register_reloptions_validator(local_relopts *relopts,
extern void add_local_bool_reloption(local_relopts *relopts, const char *name,
const char *desc, bool default_val,
int offset);
+extern void add_local_ternary_reloption(local_relopts *relopts,
+ const char *name, const char *desc,
+ ternary default_val, int offset);
extern void add_local_int_reloption(local_relopts *relopts, const char *name,
const char *desc, int default_val,
int min_val, int max_val, int offset);
diff --git a/src/include/c.h b/src/include/c.h
index 39022f8a9d..9c59ccccf8 100644
--- a/src/include/c.h
+++ b/src/include/c.h
@@ -488,6 +488,22 @@ typedef void (*pg_funcptr_t) (void);
#include <stdbool.h>
+/*
+ * ternary
+ * Boolean value with an extrea "unset" option
+ *
+ * Ternary data type is used in relation options that can be "true", "false" or
+ * "unset". Since relation options are used deep inside the PostgreSQL code,
+ * this type is declared globally.
+*/
+
+typedef enum ternary
+{
+ TERNARY_FALSE = 0,
+ TERNARY_TRUE = 1,
+ TERNARY_UNSET = -1
+} ternary;
+
/* ----------------------------------------------------------------
* Section 3: standard system types
diff --git a/src/include/utils/rel.h b/src/include/utils/rel.h
index b552359915..7346d618f9 100644
--- a/src/include/utils/rel.h
+++ b/src/include/utils/rel.h
@@ -346,8 +346,7 @@ typedef struct StdRdOptions
bool user_catalog_table; /* use as an additional catalog relation */
int parallel_workers; /* max number of parallel workers */
StdRdOptIndexCleanup vacuum_index_cleanup; /* controls index vacuuming */
- bool vacuum_truncate; /* enables vacuum to truncate a relation */
- bool vacuum_truncate_set; /* whether vacuum_truncate is set */
+ ternary vacuum_truncate; /* enables vacuum to truncate a relation */
/*
* Fraction of pages in a relation that vacuum can eagerly scan and fail
--
2.39.2
--nextPart4155600.3ZeAukHxDK
Content-Disposition: attachment;
filename="v2a-0003-Add-alias-to-be-used-as-unset-state.patch"
Content-Transfer-Encoding: 7Bit
Content-Type: text/x-patch; charset="unicode-2-0-utf-8";
name="v2a-0003-Add-alias-to-be-used-as-unset-state.patch"
^ permalink raw reply [nested|flat] 868+ messages in thread
* [PATCH v2 2/4] Introduce ternary reloptions
@ 2025-09-04 14:41 Nikolay Shaplov <[email protected]>
0 siblings, 0 replies; 868+ messages in thread
From: Nikolay Shaplov @ 2025-09-04 14:41 UTC (permalink / raw)
Introduce ternary reloption as a replacement for current `vacuum_truncate`
implementation. Remove `vacuum_truncate_set` additional flag and using
`TERNARY_UNSET` value instead.
---
src/backend/access/common/reloptions.c | 129 ++++++++++++++++++++-----
src/backend/commands/vacuum.c | 4 +-
src/include/access/reloptions.h | 26 ++---
src/include/c.h | 16 +++
src/include/utils/rel.h | 3 +-
5 files changed, 135 insertions(+), 43 deletions(-)
diff --git a/src/backend/access/common/reloptions.c b/src/backend/access/common/reloptions.c
index 0af3fea68f..f543f11001 100644
--- a/src/backend/access/common/reloptions.c
+++ b/src/backend/access/common/reloptions.c
@@ -40,9 +40,9 @@
*
* To add an option:
*
- * (i) decide on a type (bool, integer, real, enum, string), name, default
- * value, upper and lower bounds (if applicable); for strings, consider a
- * validation routine.
+ * (i) decide on a type (bool, ternary, integer, real, enum, string), name,
+ * default value, upper and lower bounds (if applicable); for strings,
+ * consider a validation routine.
* (ii) add a record below (or use add_<type>_reloption).
* (iii) add it to the appropriate options struct (perhaps StdRdOptions)
* (iv) add it to the appropriate handling routine (perhaps
@@ -147,15 +147,6 @@ static relopt_bool boolRelOpts[] =
},
false
},
- {
- {
- "vacuum_truncate",
- "Enables vacuum to truncate empty pages at the end of this table",
- RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
- ShareUpdateExclusiveLock
- },
- true
- },
{
{
"deduplicate_items",
@@ -170,6 +161,21 @@ static relopt_bool boolRelOpts[] =
{{NULL}}
};
+static relopt_ternary ternaryRelOpts[] =
+{
+ {
+ {
+ "vacuum_truncate",
+ "Enables vacuum to truncate empty pages at the end of this table",
+ RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
+ ShareUpdateExclusiveLock
+ },
+ TERNARY_UNSET
+ },
+ /* list terminator */
+ {{NULL}}
+};
+
static relopt_int intRelOpts[] =
{
{
@@ -600,6 +606,13 @@ initialize_reloptions(void)
boolRelOpts[i].gen.lockmode));
j++;
}
+ for (i = 0; ternaryRelOpts[i].gen.name; i++)
+ {
+ Assert(DoLockModesConflict(ternaryRelOpts[i].gen.lockmode,
+ ternaryRelOpts[i].gen.lockmode));
+ j++;
+ }
+
for (i = 0; intRelOpts[i].gen.name; i++)
{
Assert(DoLockModesConflict(intRelOpts[i].gen.lockmode,
@@ -640,6 +653,14 @@ initialize_reloptions(void)
j++;
}
+ for (i = 0; ternaryRelOpts[i].gen.name; i++)
+ {
+ relOpts[j] = &ternaryRelOpts[i].gen;
+ relOpts[j]->type = RELOPT_TYPE_TERNARY;
+ relOpts[j]->namelen = strlen(relOpts[j]->name);
+ j++;
+ }
+
for (i = 0; intRelOpts[i].gen.name; i++)
{
relOpts[j] = &intRelOpts[i].gen;
@@ -800,6 +821,9 @@ allocate_reloption(bits32 kinds, int type, const char *name, const char *desc,
case RELOPT_TYPE_BOOL:
size = sizeof(relopt_bool);
break;
+ case RELOPT_TYPE_TERNARY:
+ size = sizeof(relopt_ternary);
+ break;
case RELOPT_TYPE_INT:
size = sizeof(relopt_int);
break;
@@ -883,6 +907,54 @@ add_local_bool_reloption(local_relopts *relopts, const char *name,
add_local_reloption(relopts, (relopt_gen *) newoption, offset);
}
+/*
+ * init_ternary_reloption
+ * Allocate and initialize a new ternary reloption
+ */
+static relopt_ternary *
+init_ternary_reloption(bits32 kinds, const char *name, const char *desc,
+ ternary default_val, LOCKMODE lockmode)
+{
+ relopt_ternary *newoption;
+
+ newoption = (relopt_ternary *) allocate_reloption(kinds,
+ RELOPT_TYPE_TERNARY, name, desc, lockmode);
+ newoption->default_val = default_val;
+
+ return newoption;
+}
+
+/*
+ * add_ternary_reloption
+ * Add a new ternary reloption
+ */
+void
+add_ternary_reloption(bits32 kinds, const char *name, const char *desc,
+ ternary default_val, LOCKMODE lockmode)
+{
+ relopt_ternary *newoption = init_ternary_reloption(kinds, name, desc,
+ default_val, lockmode);
+
+ add_reloption((relopt_gen *) newoption);
+}
+
+/*
+ * add_local_ternary_reloption
+ * Add a new ternary local reloption
+ *
+ * 'offset' is offset of ternary-typed field.
+ */
+void
+add_local_ternary_reloption(local_relopts *relopts, const char *name,
+ const char *desc, ternary default_val,
+ int offset)
+{
+ relopt_ternary *newoption = init_ternary_reloption(RELOPT_KIND_LOCAL,
+ name, desc,
+ default_val, 0);
+
+ add_local_reloption(relopts, (relopt_gen *) newoption, offset);
+}
/*
* init_real_reloption
@@ -1617,6 +1689,18 @@ parse_one_reloption(relopt_value *option, char *text_str, int text_len,
option->gen->name, value)));
}
break;
+ case RELOPT_TYPE_TERNARY:
+ {
+ bool b;
+ parsed = parse_bool(value, &b);
+ option->values.ternary_val = b ? TERNARY_TRUE : TERNARY_FALSE;
+ if (validate && !parsed)
+ ereport(ERROR,
+ (errcode(ERRCODE_INVALID_PARAMETER_VALUE),
+ errmsg("invalid value for ternary option \"%s\": %s",
+ option->gen->name, value)));
+ }
+ break;
case RELOPT_TYPE_INT:
{
relopt_int *optint = (relopt_int *) option->gen;
@@ -1780,17 +1864,6 @@ fillRelOptions(void *rdopts, Size basesize,
char *itempos = ((char *) rdopts) + elems[j].offset;
char *string_val;
- /*
- * If isset_offset is provided, store whether the reloption is
- * set there.
- */
- if (elems[j].isset_offset > 0)
- {
- char *setpos = ((char *) rdopts) + elems[j].isset_offset;
-
- *(bool *) setpos = options[i].isset;
- }
-
switch (options[i].gen->type)
{
case RELOPT_TYPE_BOOL:
@@ -1798,6 +1871,11 @@ fillRelOptions(void *rdopts, Size basesize,
options[i].values.bool_val :
((relopt_bool *) options[i].gen)->default_val;
break;
+ case RELOPT_TYPE_TERNARY:
+ *(ternary *) itempos = options[i].isset ?
+ options[i].values.ternary_val :
+ ((relopt_ternary *) options[i].gen)->default_val;
+ break;
case RELOPT_TYPE_INT:
*(int *) itempos = options[i].isset ?
options[i].values.int_val :
@@ -1912,8 +1990,8 @@ default_reloptions(Datum reloptions, bool validate, relopt_kind kind)
offsetof(StdRdOptions, parallel_workers)},
{"vacuum_index_cleanup", RELOPT_TYPE_ENUM,
offsetof(StdRdOptions, vacuum_index_cleanup)},
- {"vacuum_truncate", RELOPT_TYPE_BOOL,
- offsetof(StdRdOptions, vacuum_truncate), offsetof(StdRdOptions, vacuum_truncate_set)},
+ {"vacuum_truncate", RELOPT_TYPE_TERNARY,
+ offsetof(StdRdOptions, vacuum_truncate)},
{"vacuum_max_eager_freeze_failure_rate", RELOPT_TYPE_REAL,
offsetof(StdRdOptions, vacuum_max_eager_freeze_failure_rate)}
};
@@ -1993,7 +2071,6 @@ build_local_reloptions(local_relopts *relopts, Datum options, bool validate)
elems[i].optname = opt->option->name;
elems[i].opttype = opt->option->type;
elems[i].offset = opt->offset;
- elems[i].isset_offset = 0; /* not supported for local relopts yet */
i++;
}
diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c
index 733ef40ae7..977babff54 100644
--- a/src/backend/commands/vacuum.c
+++ b/src/backend/commands/vacuum.c
@@ -2221,9 +2221,9 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams params,
{
StdRdOptions *opts = (StdRdOptions *) rel->rd_options;
- if (opts && opts->vacuum_truncate_set)
+ if (opts && opts->vacuum_truncate != TERNARY_UNSET)
{
- if (opts->vacuum_truncate)
+ if (opts->vacuum_truncate == TERNARY_TRUE)
params.truncate = VACOPTVALUE_ENABLED;
else
params.truncate = VACOPTVALUE_DISABLED;
diff --git a/src/include/access/reloptions.h b/src/include/access/reloptions.h
index a604a4702c..a436697658 100644
--- a/src/include/access/reloptions.h
+++ b/src/include/access/reloptions.h
@@ -29,6 +29,7 @@
typedef enum relopt_type
{
RELOPT_TYPE_BOOL,
+ RELOPT_TYPE_TERNARY, /* on, off, unset */
RELOPT_TYPE_INT,
RELOPT_TYPE_REAL,
RELOPT_TYPE_ENUM,
@@ -80,6 +81,7 @@ typedef struct relopt_value
union
{
bool bool_val;
+ ternary ternary_val;
int int_val;
double real_val;
int enum_val;
@@ -94,6 +96,12 @@ typedef struct relopt_bool
bool default_val;
} relopt_bool;
+typedef struct relopt_rernary
+{
+ relopt_gen gen;
+ int default_val;
+} relopt_ternary;
+
typedef struct relopt_int
{
relopt_gen gen;
@@ -152,19 +160,6 @@ typedef struct
const char *optname; /* option's name */
relopt_type opttype; /* option's datatype */
int offset; /* offset of field in result struct */
-
- /*
- * isset_offset is an optional offset of a field in the result struct that
- * stores whether the option is explicitly set for the relation or if it
- * just picked up the default value. In most cases, this can be
- * accomplished by giving the reloption a special out-of-range default
- * value (e.g., some integer reloptions use -2), but this isn't always
- * possible. For example, a Boolean reloption cannot be given an
- * out-of-range default, so we need another way to discover the source of
- * its value. This offset is only used if given a value greater than
- * zero.
- */
- int isset_offset;
} relopt_parse_elt;
/* Local reloption definition */
@@ -195,6 +190,8 @@ typedef struct local_relopts
extern relopt_kind add_reloption_kind(void);
extern void add_bool_reloption(bits32 kinds, const char *name, const char *desc,
bool default_val, LOCKMODE lockmode);
+extern void add_ternary_reloption(bits32 kinds, const char *name,
+ const char *desc, int default_val, LOCKMODE lockmode);
extern void add_int_reloption(bits32 kinds, const char *name, const char *desc,
int default_val, int min_val, int max_val,
LOCKMODE lockmode);
@@ -214,6 +211,9 @@ extern void register_reloptions_validator(local_relopts *relopts,
extern void add_local_bool_reloption(local_relopts *relopts, const char *name,
const char *desc, bool default_val,
int offset);
+extern void add_local_ternary_reloption(local_relopts *relopts,
+ const char *name, const char *desc,
+ ternary default_val, int offset);
extern void add_local_int_reloption(local_relopts *relopts, const char *name,
const char *desc, int default_val,
int min_val, int max_val, int offset);
diff --git a/src/include/c.h b/src/include/c.h
index 39022f8a9d..9c59ccccf8 100644
--- a/src/include/c.h
+++ b/src/include/c.h
@@ -488,6 +488,22 @@ typedef void (*pg_funcptr_t) (void);
#include <stdbool.h>
+/*
+ * ternary
+ * Boolean value with an extrea "unset" option
+ *
+ * Ternary data type is used in relation options that can be "true", "false" or
+ * "unset". Since relation options are used deep inside the PostgreSQL code,
+ * this type is declared globally.
+*/
+
+typedef enum ternary
+{
+ TERNARY_FALSE = 0,
+ TERNARY_TRUE = 1,
+ TERNARY_UNSET = -1
+} ternary;
+
/* ----------------------------------------------------------------
* Section 3: standard system types
diff --git a/src/include/utils/rel.h b/src/include/utils/rel.h
index b552359915..7346d618f9 100644
--- a/src/include/utils/rel.h
+++ b/src/include/utils/rel.h
@@ -346,8 +346,7 @@ typedef struct StdRdOptions
bool user_catalog_table; /* use as an additional catalog relation */
int parallel_workers; /* max number of parallel workers */
StdRdOptIndexCleanup vacuum_index_cleanup; /* controls index vacuuming */
- bool vacuum_truncate; /* enables vacuum to truncate a relation */
- bool vacuum_truncate_set; /* whether vacuum_truncate is set */
+ ternary vacuum_truncate; /* enables vacuum to truncate a relation */
/*
* Fraction of pages in a relation that vacuum can eagerly scan and fail
--
2.39.2
--nextPart5129932.5fSG56mABF
Content-Disposition: attachment;
filename="v2-0003-Add-alias-to-be-used-as-unset-state.patch"
Content-Transfer-Encoding: 7Bit
Content-Type: text/x-patch; charset="unicode-2-0-utf-8";
name="v2-0003-Add-alias-to-be-used-as-unset-state.patch"
^ permalink raw reply [nested|flat] 868+ messages in thread
* [PATCH v2a 2/4] Introduce ternary reloptions
@ 2025-09-04 14:41 Nikolay Shaplov <[email protected]>
0 siblings, 0 replies; 868+ messages in thread
From: Nikolay Shaplov @ 2025-09-04 14:41 UTC (permalink / raw)
Introduce ternary reloption as a replacement for current `vacuum_truncate`
implementation. Remove `vacuum_truncate_set` additional flag and using
`TERNARY_UNSET` value instead.
---
src/backend/access/common/reloptions.c | 129 ++++++++++++++++++++-----
src/backend/commands/vacuum.c | 4 +-
src/include/access/reloptions.h | 26 ++---
src/include/c.h | 16 +++
src/include/utils/rel.h | 3 +-
5 files changed, 135 insertions(+), 43 deletions(-)
diff --git a/src/backend/access/common/reloptions.c b/src/backend/access/common/reloptions.c
index 0af3fea68f..f543f11001 100644
--- a/src/backend/access/common/reloptions.c
+++ b/src/backend/access/common/reloptions.c
@@ -40,9 +40,9 @@
*
* To add an option:
*
- * (i) decide on a type (bool, integer, real, enum, string), name, default
- * value, upper and lower bounds (if applicable); for strings, consider a
- * validation routine.
+ * (i) decide on a type (bool, ternary, integer, real, enum, string), name,
+ * default value, upper and lower bounds (if applicable); for strings,
+ * consider a validation routine.
* (ii) add a record below (or use add_<type>_reloption).
* (iii) add it to the appropriate options struct (perhaps StdRdOptions)
* (iv) add it to the appropriate handling routine (perhaps
@@ -147,15 +147,6 @@ static relopt_bool boolRelOpts[] =
},
false
},
- {
- {
- "vacuum_truncate",
- "Enables vacuum to truncate empty pages at the end of this table",
- RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
- ShareUpdateExclusiveLock
- },
- true
- },
{
{
"deduplicate_items",
@@ -170,6 +161,21 @@ static relopt_bool boolRelOpts[] =
{{NULL}}
};
+static relopt_ternary ternaryRelOpts[] =
+{
+ {
+ {
+ "vacuum_truncate",
+ "Enables vacuum to truncate empty pages at the end of this table",
+ RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
+ ShareUpdateExclusiveLock
+ },
+ TERNARY_UNSET
+ },
+ /* list terminator */
+ {{NULL}}
+};
+
static relopt_int intRelOpts[] =
{
{
@@ -600,6 +606,13 @@ initialize_reloptions(void)
boolRelOpts[i].gen.lockmode));
j++;
}
+ for (i = 0; ternaryRelOpts[i].gen.name; i++)
+ {
+ Assert(DoLockModesConflict(ternaryRelOpts[i].gen.lockmode,
+ ternaryRelOpts[i].gen.lockmode));
+ j++;
+ }
+
for (i = 0; intRelOpts[i].gen.name; i++)
{
Assert(DoLockModesConflict(intRelOpts[i].gen.lockmode,
@@ -640,6 +653,14 @@ initialize_reloptions(void)
j++;
}
+ for (i = 0; ternaryRelOpts[i].gen.name; i++)
+ {
+ relOpts[j] = &ternaryRelOpts[i].gen;
+ relOpts[j]->type = RELOPT_TYPE_TERNARY;
+ relOpts[j]->namelen = strlen(relOpts[j]->name);
+ j++;
+ }
+
for (i = 0; intRelOpts[i].gen.name; i++)
{
relOpts[j] = &intRelOpts[i].gen;
@@ -800,6 +821,9 @@ allocate_reloption(bits32 kinds, int type, const char *name, const char *desc,
case RELOPT_TYPE_BOOL:
size = sizeof(relopt_bool);
break;
+ case RELOPT_TYPE_TERNARY:
+ size = sizeof(relopt_ternary);
+ break;
case RELOPT_TYPE_INT:
size = sizeof(relopt_int);
break;
@@ -883,6 +907,54 @@ add_local_bool_reloption(local_relopts *relopts, const char *name,
add_local_reloption(relopts, (relopt_gen *) newoption, offset);
}
+/*
+ * init_ternary_reloption
+ * Allocate and initialize a new ternary reloption
+ */
+static relopt_ternary *
+init_ternary_reloption(bits32 kinds, const char *name, const char *desc,
+ ternary default_val, LOCKMODE lockmode)
+{
+ relopt_ternary *newoption;
+
+ newoption = (relopt_ternary *) allocate_reloption(kinds,
+ RELOPT_TYPE_TERNARY, name, desc, lockmode);
+ newoption->default_val = default_val;
+
+ return newoption;
+}
+
+/*
+ * add_ternary_reloption
+ * Add a new ternary reloption
+ */
+void
+add_ternary_reloption(bits32 kinds, const char *name, const char *desc,
+ ternary default_val, LOCKMODE lockmode)
+{
+ relopt_ternary *newoption = init_ternary_reloption(kinds, name, desc,
+ default_val, lockmode);
+
+ add_reloption((relopt_gen *) newoption);
+}
+
+/*
+ * add_local_ternary_reloption
+ * Add a new ternary local reloption
+ *
+ * 'offset' is offset of ternary-typed field.
+ */
+void
+add_local_ternary_reloption(local_relopts *relopts, const char *name,
+ const char *desc, ternary default_val,
+ int offset)
+{
+ relopt_ternary *newoption = init_ternary_reloption(RELOPT_KIND_LOCAL,
+ name, desc,
+ default_val, 0);
+
+ add_local_reloption(relopts, (relopt_gen *) newoption, offset);
+}
/*
* init_real_reloption
@@ -1617,6 +1689,18 @@ parse_one_reloption(relopt_value *option, char *text_str, int text_len,
option->gen->name, value)));
}
break;
+ case RELOPT_TYPE_TERNARY:
+ {
+ bool b;
+ parsed = parse_bool(value, &b);
+ option->values.ternary_val = b ? TERNARY_TRUE : TERNARY_FALSE;
+ if (validate && !parsed)
+ ereport(ERROR,
+ (errcode(ERRCODE_INVALID_PARAMETER_VALUE),
+ errmsg("invalid value for ternary option \"%s\": %s",
+ option->gen->name, value)));
+ }
+ break;
case RELOPT_TYPE_INT:
{
relopt_int *optint = (relopt_int *) option->gen;
@@ -1780,17 +1864,6 @@ fillRelOptions(void *rdopts, Size basesize,
char *itempos = ((char *) rdopts) + elems[j].offset;
char *string_val;
- /*
- * If isset_offset is provided, store whether the reloption is
- * set there.
- */
- if (elems[j].isset_offset > 0)
- {
- char *setpos = ((char *) rdopts) + elems[j].isset_offset;
-
- *(bool *) setpos = options[i].isset;
- }
-
switch (options[i].gen->type)
{
case RELOPT_TYPE_BOOL:
@@ -1798,6 +1871,11 @@ fillRelOptions(void *rdopts, Size basesize,
options[i].values.bool_val :
((relopt_bool *) options[i].gen)->default_val;
break;
+ case RELOPT_TYPE_TERNARY:
+ *(ternary *) itempos = options[i].isset ?
+ options[i].values.ternary_val :
+ ((relopt_ternary *) options[i].gen)->default_val;
+ break;
case RELOPT_TYPE_INT:
*(int *) itempos = options[i].isset ?
options[i].values.int_val :
@@ -1912,8 +1990,8 @@ default_reloptions(Datum reloptions, bool validate, relopt_kind kind)
offsetof(StdRdOptions, parallel_workers)},
{"vacuum_index_cleanup", RELOPT_TYPE_ENUM,
offsetof(StdRdOptions, vacuum_index_cleanup)},
- {"vacuum_truncate", RELOPT_TYPE_BOOL,
- offsetof(StdRdOptions, vacuum_truncate), offsetof(StdRdOptions, vacuum_truncate_set)},
+ {"vacuum_truncate", RELOPT_TYPE_TERNARY,
+ offsetof(StdRdOptions, vacuum_truncate)},
{"vacuum_max_eager_freeze_failure_rate", RELOPT_TYPE_REAL,
offsetof(StdRdOptions, vacuum_max_eager_freeze_failure_rate)}
};
@@ -1993,7 +2071,6 @@ build_local_reloptions(local_relopts *relopts, Datum options, bool validate)
elems[i].optname = opt->option->name;
elems[i].opttype = opt->option->type;
elems[i].offset = opt->offset;
- elems[i].isset_offset = 0; /* not supported for local relopts yet */
i++;
}
diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c
index 733ef40ae7..977babff54 100644
--- a/src/backend/commands/vacuum.c
+++ b/src/backend/commands/vacuum.c
@@ -2221,9 +2221,9 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams params,
{
StdRdOptions *opts = (StdRdOptions *) rel->rd_options;
- if (opts && opts->vacuum_truncate_set)
+ if (opts && opts->vacuum_truncate != TERNARY_UNSET)
{
- if (opts->vacuum_truncate)
+ if (opts->vacuum_truncate == TERNARY_TRUE)
params.truncate = VACOPTVALUE_ENABLED;
else
params.truncate = VACOPTVALUE_DISABLED;
diff --git a/src/include/access/reloptions.h b/src/include/access/reloptions.h
index a604a4702c..a436697658 100644
--- a/src/include/access/reloptions.h
+++ b/src/include/access/reloptions.h
@@ -29,6 +29,7 @@
typedef enum relopt_type
{
RELOPT_TYPE_BOOL,
+ RELOPT_TYPE_TERNARY, /* on, off, unset */
RELOPT_TYPE_INT,
RELOPT_TYPE_REAL,
RELOPT_TYPE_ENUM,
@@ -80,6 +81,7 @@ typedef struct relopt_value
union
{
bool bool_val;
+ ternary ternary_val;
int int_val;
double real_val;
int enum_val;
@@ -94,6 +96,12 @@ typedef struct relopt_bool
bool default_val;
} relopt_bool;
+typedef struct relopt_rernary
+{
+ relopt_gen gen;
+ int default_val;
+} relopt_ternary;
+
typedef struct relopt_int
{
relopt_gen gen;
@@ -152,19 +160,6 @@ typedef struct
const char *optname; /* option's name */
relopt_type opttype; /* option's datatype */
int offset; /* offset of field in result struct */
-
- /*
- * isset_offset is an optional offset of a field in the result struct that
- * stores whether the option is explicitly set for the relation or if it
- * just picked up the default value. In most cases, this can be
- * accomplished by giving the reloption a special out-of-range default
- * value (e.g., some integer reloptions use -2), but this isn't always
- * possible. For example, a Boolean reloption cannot be given an
- * out-of-range default, so we need another way to discover the source of
- * its value. This offset is only used if given a value greater than
- * zero.
- */
- int isset_offset;
} relopt_parse_elt;
/* Local reloption definition */
@@ -195,6 +190,8 @@ typedef struct local_relopts
extern relopt_kind add_reloption_kind(void);
extern void add_bool_reloption(bits32 kinds, const char *name, const char *desc,
bool default_val, LOCKMODE lockmode);
+extern void add_ternary_reloption(bits32 kinds, const char *name,
+ const char *desc, int default_val, LOCKMODE lockmode);
extern void add_int_reloption(bits32 kinds, const char *name, const char *desc,
int default_val, int min_val, int max_val,
LOCKMODE lockmode);
@@ -214,6 +211,9 @@ extern void register_reloptions_validator(local_relopts *relopts,
extern void add_local_bool_reloption(local_relopts *relopts, const char *name,
const char *desc, bool default_val,
int offset);
+extern void add_local_ternary_reloption(local_relopts *relopts,
+ const char *name, const char *desc,
+ ternary default_val, int offset);
extern void add_local_int_reloption(local_relopts *relopts, const char *name,
const char *desc, int default_val,
int min_val, int max_val, int offset);
diff --git a/src/include/c.h b/src/include/c.h
index 39022f8a9d..9c59ccccf8 100644
--- a/src/include/c.h
+++ b/src/include/c.h
@@ -488,6 +488,22 @@ typedef void (*pg_funcptr_t) (void);
#include <stdbool.h>
+/*
+ * ternary
+ * Boolean value with an extrea "unset" option
+ *
+ * Ternary data type is used in relation options that can be "true", "false" or
+ * "unset". Since relation options are used deep inside the PostgreSQL code,
+ * this type is declared globally.
+*/
+
+typedef enum ternary
+{
+ TERNARY_FALSE = 0,
+ TERNARY_TRUE = 1,
+ TERNARY_UNSET = -1
+} ternary;
+
/* ----------------------------------------------------------------
* Section 3: standard system types
diff --git a/src/include/utils/rel.h b/src/include/utils/rel.h
index b552359915..7346d618f9 100644
--- a/src/include/utils/rel.h
+++ b/src/include/utils/rel.h
@@ -346,8 +346,7 @@ typedef struct StdRdOptions
bool user_catalog_table; /* use as an additional catalog relation */
int parallel_workers; /* max number of parallel workers */
StdRdOptIndexCleanup vacuum_index_cleanup; /* controls index vacuuming */
- bool vacuum_truncate; /* enables vacuum to truncate a relation */
- bool vacuum_truncate_set; /* whether vacuum_truncate is set */
+ ternary vacuum_truncate; /* enables vacuum to truncate a relation */
/*
* Fraction of pages in a relation that vacuum can eagerly scan and fail
--
2.39.2
--nextPart4155600.3ZeAukHxDK
Content-Disposition: attachment;
filename="v2a-0003-Add-alias-to-be-used-as-unset-state.patch"
Content-Transfer-Encoding: 7Bit
Content-Type: text/x-patch; charset="unicode-2-0-utf-8";
name="v2a-0003-Add-alias-to-be-used-as-unset-state.patch"
^ permalink raw reply [nested|flat] 868+ messages in thread
* [PATCH v2 2/4] Introduce ternary reloptions
@ 2025-09-04 14:41 Nikolay Shaplov <[email protected]>
0 siblings, 0 replies; 868+ messages in thread
From: Nikolay Shaplov @ 2025-09-04 14:41 UTC (permalink / raw)
Introduce ternary reloption as a replacement for current `vacuum_truncate`
implementation. Remove `vacuum_truncate_set` additional flag and using
`TERNARY_UNSET` value instead.
---
src/backend/access/common/reloptions.c | 129 ++++++++++++++++++++-----
src/backend/commands/vacuum.c | 4 +-
src/include/access/reloptions.h | 26 ++---
src/include/c.h | 16 +++
src/include/utils/rel.h | 3 +-
5 files changed, 135 insertions(+), 43 deletions(-)
diff --git a/src/backend/access/common/reloptions.c b/src/backend/access/common/reloptions.c
index 0af3fea68f..f543f11001 100644
--- a/src/backend/access/common/reloptions.c
+++ b/src/backend/access/common/reloptions.c
@@ -40,9 +40,9 @@
*
* To add an option:
*
- * (i) decide on a type (bool, integer, real, enum, string), name, default
- * value, upper and lower bounds (if applicable); for strings, consider a
- * validation routine.
+ * (i) decide on a type (bool, ternary, integer, real, enum, string), name,
+ * default value, upper and lower bounds (if applicable); for strings,
+ * consider a validation routine.
* (ii) add a record below (or use add_<type>_reloption).
* (iii) add it to the appropriate options struct (perhaps StdRdOptions)
* (iv) add it to the appropriate handling routine (perhaps
@@ -147,15 +147,6 @@ static relopt_bool boolRelOpts[] =
},
false
},
- {
- {
- "vacuum_truncate",
- "Enables vacuum to truncate empty pages at the end of this table",
- RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
- ShareUpdateExclusiveLock
- },
- true
- },
{
{
"deduplicate_items",
@@ -170,6 +161,21 @@ static relopt_bool boolRelOpts[] =
{{NULL}}
};
+static relopt_ternary ternaryRelOpts[] =
+{
+ {
+ {
+ "vacuum_truncate",
+ "Enables vacuum to truncate empty pages at the end of this table",
+ RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
+ ShareUpdateExclusiveLock
+ },
+ TERNARY_UNSET
+ },
+ /* list terminator */
+ {{NULL}}
+};
+
static relopt_int intRelOpts[] =
{
{
@@ -600,6 +606,13 @@ initialize_reloptions(void)
boolRelOpts[i].gen.lockmode));
j++;
}
+ for (i = 0; ternaryRelOpts[i].gen.name; i++)
+ {
+ Assert(DoLockModesConflict(ternaryRelOpts[i].gen.lockmode,
+ ternaryRelOpts[i].gen.lockmode));
+ j++;
+ }
+
for (i = 0; intRelOpts[i].gen.name; i++)
{
Assert(DoLockModesConflict(intRelOpts[i].gen.lockmode,
@@ -640,6 +653,14 @@ initialize_reloptions(void)
j++;
}
+ for (i = 0; ternaryRelOpts[i].gen.name; i++)
+ {
+ relOpts[j] = &ternaryRelOpts[i].gen;
+ relOpts[j]->type = RELOPT_TYPE_TERNARY;
+ relOpts[j]->namelen = strlen(relOpts[j]->name);
+ j++;
+ }
+
for (i = 0; intRelOpts[i].gen.name; i++)
{
relOpts[j] = &intRelOpts[i].gen;
@@ -800,6 +821,9 @@ allocate_reloption(bits32 kinds, int type, const char *name, const char *desc,
case RELOPT_TYPE_BOOL:
size = sizeof(relopt_bool);
break;
+ case RELOPT_TYPE_TERNARY:
+ size = sizeof(relopt_ternary);
+ break;
case RELOPT_TYPE_INT:
size = sizeof(relopt_int);
break;
@@ -883,6 +907,54 @@ add_local_bool_reloption(local_relopts *relopts, const char *name,
add_local_reloption(relopts, (relopt_gen *) newoption, offset);
}
+/*
+ * init_ternary_reloption
+ * Allocate and initialize a new ternary reloption
+ */
+static relopt_ternary *
+init_ternary_reloption(bits32 kinds, const char *name, const char *desc,
+ ternary default_val, LOCKMODE lockmode)
+{
+ relopt_ternary *newoption;
+
+ newoption = (relopt_ternary *) allocate_reloption(kinds,
+ RELOPT_TYPE_TERNARY, name, desc, lockmode);
+ newoption->default_val = default_val;
+
+ return newoption;
+}
+
+/*
+ * add_ternary_reloption
+ * Add a new ternary reloption
+ */
+void
+add_ternary_reloption(bits32 kinds, const char *name, const char *desc,
+ ternary default_val, LOCKMODE lockmode)
+{
+ relopt_ternary *newoption = init_ternary_reloption(kinds, name, desc,
+ default_val, lockmode);
+
+ add_reloption((relopt_gen *) newoption);
+}
+
+/*
+ * add_local_ternary_reloption
+ * Add a new ternary local reloption
+ *
+ * 'offset' is offset of ternary-typed field.
+ */
+void
+add_local_ternary_reloption(local_relopts *relopts, const char *name,
+ const char *desc, ternary default_val,
+ int offset)
+{
+ relopt_ternary *newoption = init_ternary_reloption(RELOPT_KIND_LOCAL,
+ name, desc,
+ default_val, 0);
+
+ add_local_reloption(relopts, (relopt_gen *) newoption, offset);
+}
/*
* init_real_reloption
@@ -1617,6 +1689,18 @@ parse_one_reloption(relopt_value *option, char *text_str, int text_len,
option->gen->name, value)));
}
break;
+ case RELOPT_TYPE_TERNARY:
+ {
+ bool b;
+ parsed = parse_bool(value, &b);
+ option->values.ternary_val = b ? TERNARY_TRUE : TERNARY_FALSE;
+ if (validate && !parsed)
+ ereport(ERROR,
+ (errcode(ERRCODE_INVALID_PARAMETER_VALUE),
+ errmsg("invalid value for ternary option \"%s\": %s",
+ option->gen->name, value)));
+ }
+ break;
case RELOPT_TYPE_INT:
{
relopt_int *optint = (relopt_int *) option->gen;
@@ -1780,17 +1864,6 @@ fillRelOptions(void *rdopts, Size basesize,
char *itempos = ((char *) rdopts) + elems[j].offset;
char *string_val;
- /*
- * If isset_offset is provided, store whether the reloption is
- * set there.
- */
- if (elems[j].isset_offset > 0)
- {
- char *setpos = ((char *) rdopts) + elems[j].isset_offset;
-
- *(bool *) setpos = options[i].isset;
- }
-
switch (options[i].gen->type)
{
case RELOPT_TYPE_BOOL:
@@ -1798,6 +1871,11 @@ fillRelOptions(void *rdopts, Size basesize,
options[i].values.bool_val :
((relopt_bool *) options[i].gen)->default_val;
break;
+ case RELOPT_TYPE_TERNARY:
+ *(ternary *) itempos = options[i].isset ?
+ options[i].values.ternary_val :
+ ((relopt_ternary *) options[i].gen)->default_val;
+ break;
case RELOPT_TYPE_INT:
*(int *) itempos = options[i].isset ?
options[i].values.int_val :
@@ -1912,8 +1990,8 @@ default_reloptions(Datum reloptions, bool validate, relopt_kind kind)
offsetof(StdRdOptions, parallel_workers)},
{"vacuum_index_cleanup", RELOPT_TYPE_ENUM,
offsetof(StdRdOptions, vacuum_index_cleanup)},
- {"vacuum_truncate", RELOPT_TYPE_BOOL,
- offsetof(StdRdOptions, vacuum_truncate), offsetof(StdRdOptions, vacuum_truncate_set)},
+ {"vacuum_truncate", RELOPT_TYPE_TERNARY,
+ offsetof(StdRdOptions, vacuum_truncate)},
{"vacuum_max_eager_freeze_failure_rate", RELOPT_TYPE_REAL,
offsetof(StdRdOptions, vacuum_max_eager_freeze_failure_rate)}
};
@@ -1993,7 +2071,6 @@ build_local_reloptions(local_relopts *relopts, Datum options, bool validate)
elems[i].optname = opt->option->name;
elems[i].opttype = opt->option->type;
elems[i].offset = opt->offset;
- elems[i].isset_offset = 0; /* not supported for local relopts yet */
i++;
}
diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c
index 733ef40ae7..977babff54 100644
--- a/src/backend/commands/vacuum.c
+++ b/src/backend/commands/vacuum.c
@@ -2221,9 +2221,9 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams params,
{
StdRdOptions *opts = (StdRdOptions *) rel->rd_options;
- if (opts && opts->vacuum_truncate_set)
+ if (opts && opts->vacuum_truncate != TERNARY_UNSET)
{
- if (opts->vacuum_truncate)
+ if (opts->vacuum_truncate == TERNARY_TRUE)
params.truncate = VACOPTVALUE_ENABLED;
else
params.truncate = VACOPTVALUE_DISABLED;
diff --git a/src/include/access/reloptions.h b/src/include/access/reloptions.h
index a604a4702c..a436697658 100644
--- a/src/include/access/reloptions.h
+++ b/src/include/access/reloptions.h
@@ -29,6 +29,7 @@
typedef enum relopt_type
{
RELOPT_TYPE_BOOL,
+ RELOPT_TYPE_TERNARY, /* on, off, unset */
RELOPT_TYPE_INT,
RELOPT_TYPE_REAL,
RELOPT_TYPE_ENUM,
@@ -80,6 +81,7 @@ typedef struct relopt_value
union
{
bool bool_val;
+ ternary ternary_val;
int int_val;
double real_val;
int enum_val;
@@ -94,6 +96,12 @@ typedef struct relopt_bool
bool default_val;
} relopt_bool;
+typedef struct relopt_rernary
+{
+ relopt_gen gen;
+ int default_val;
+} relopt_ternary;
+
typedef struct relopt_int
{
relopt_gen gen;
@@ -152,19 +160,6 @@ typedef struct
const char *optname; /* option's name */
relopt_type opttype; /* option's datatype */
int offset; /* offset of field in result struct */
-
- /*
- * isset_offset is an optional offset of a field in the result struct that
- * stores whether the option is explicitly set for the relation or if it
- * just picked up the default value. In most cases, this can be
- * accomplished by giving the reloption a special out-of-range default
- * value (e.g., some integer reloptions use -2), but this isn't always
- * possible. For example, a Boolean reloption cannot be given an
- * out-of-range default, so we need another way to discover the source of
- * its value. This offset is only used if given a value greater than
- * zero.
- */
- int isset_offset;
} relopt_parse_elt;
/* Local reloption definition */
@@ -195,6 +190,8 @@ typedef struct local_relopts
extern relopt_kind add_reloption_kind(void);
extern void add_bool_reloption(bits32 kinds, const char *name, const char *desc,
bool default_val, LOCKMODE lockmode);
+extern void add_ternary_reloption(bits32 kinds, const char *name,
+ const char *desc, int default_val, LOCKMODE lockmode);
extern void add_int_reloption(bits32 kinds, const char *name, const char *desc,
int default_val, int min_val, int max_val,
LOCKMODE lockmode);
@@ -214,6 +211,9 @@ extern void register_reloptions_validator(local_relopts *relopts,
extern void add_local_bool_reloption(local_relopts *relopts, const char *name,
const char *desc, bool default_val,
int offset);
+extern void add_local_ternary_reloption(local_relopts *relopts,
+ const char *name, const char *desc,
+ ternary default_val, int offset);
extern void add_local_int_reloption(local_relopts *relopts, const char *name,
const char *desc, int default_val,
int min_val, int max_val, int offset);
diff --git a/src/include/c.h b/src/include/c.h
index 39022f8a9d..9c59ccccf8 100644
--- a/src/include/c.h
+++ b/src/include/c.h
@@ -488,6 +488,22 @@ typedef void (*pg_funcptr_t) (void);
#include <stdbool.h>
+/*
+ * ternary
+ * Boolean value with an extrea "unset" option
+ *
+ * Ternary data type is used in relation options that can be "true", "false" or
+ * "unset". Since relation options are used deep inside the PostgreSQL code,
+ * this type is declared globally.
+*/
+
+typedef enum ternary
+{
+ TERNARY_FALSE = 0,
+ TERNARY_TRUE = 1,
+ TERNARY_UNSET = -1
+} ternary;
+
/* ----------------------------------------------------------------
* Section 3: standard system types
diff --git a/src/include/utils/rel.h b/src/include/utils/rel.h
index b552359915..7346d618f9 100644
--- a/src/include/utils/rel.h
+++ b/src/include/utils/rel.h
@@ -346,8 +346,7 @@ typedef struct StdRdOptions
bool user_catalog_table; /* use as an additional catalog relation */
int parallel_workers; /* max number of parallel workers */
StdRdOptIndexCleanup vacuum_index_cleanup; /* controls index vacuuming */
- bool vacuum_truncate; /* enables vacuum to truncate a relation */
- bool vacuum_truncate_set; /* whether vacuum_truncate is set */
+ ternary vacuum_truncate; /* enables vacuum to truncate a relation */
/*
* Fraction of pages in a relation that vacuum can eagerly scan and fail
--
2.39.2
--nextPart5129932.5fSG56mABF
Content-Disposition: attachment;
filename="v2-0003-Add-alias-to-be-used-as-unset-state.patch"
Content-Transfer-Encoding: 7Bit
Content-Type: text/x-patch; charset="unicode-2-0-utf-8";
name="v2-0003-Add-alias-to-be-used-as-unset-state.patch"
^ permalink raw reply [nested|flat] 868+ messages in thread
* [PATCH v2a 2/4] Introduce ternary reloptions
@ 2025-09-04 14:41 Nikolay Shaplov <[email protected]>
0 siblings, 0 replies; 868+ messages in thread
From: Nikolay Shaplov @ 2025-09-04 14:41 UTC (permalink / raw)
Introduce ternary reloption as a replacement for current `vacuum_truncate`
implementation. Remove `vacuum_truncate_set` additional flag and using
`TERNARY_UNSET` value instead.
---
src/backend/access/common/reloptions.c | 129 ++++++++++++++++++++-----
src/backend/commands/vacuum.c | 4 +-
src/include/access/reloptions.h | 26 ++---
src/include/c.h | 16 +++
src/include/utils/rel.h | 3 +-
5 files changed, 135 insertions(+), 43 deletions(-)
diff --git a/src/backend/access/common/reloptions.c b/src/backend/access/common/reloptions.c
index 0af3fea68f..f543f11001 100644
--- a/src/backend/access/common/reloptions.c
+++ b/src/backend/access/common/reloptions.c
@@ -40,9 +40,9 @@
*
* To add an option:
*
- * (i) decide on a type (bool, integer, real, enum, string), name, default
- * value, upper and lower bounds (if applicable); for strings, consider a
- * validation routine.
+ * (i) decide on a type (bool, ternary, integer, real, enum, string), name,
+ * default value, upper and lower bounds (if applicable); for strings,
+ * consider a validation routine.
* (ii) add a record below (or use add_<type>_reloption).
* (iii) add it to the appropriate options struct (perhaps StdRdOptions)
* (iv) add it to the appropriate handling routine (perhaps
@@ -147,15 +147,6 @@ static relopt_bool boolRelOpts[] =
},
false
},
- {
- {
- "vacuum_truncate",
- "Enables vacuum to truncate empty pages at the end of this table",
- RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
- ShareUpdateExclusiveLock
- },
- true
- },
{
{
"deduplicate_items",
@@ -170,6 +161,21 @@ static relopt_bool boolRelOpts[] =
{{NULL}}
};
+static relopt_ternary ternaryRelOpts[] =
+{
+ {
+ {
+ "vacuum_truncate",
+ "Enables vacuum to truncate empty pages at the end of this table",
+ RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
+ ShareUpdateExclusiveLock
+ },
+ TERNARY_UNSET
+ },
+ /* list terminator */
+ {{NULL}}
+};
+
static relopt_int intRelOpts[] =
{
{
@@ -600,6 +606,13 @@ initialize_reloptions(void)
boolRelOpts[i].gen.lockmode));
j++;
}
+ for (i = 0; ternaryRelOpts[i].gen.name; i++)
+ {
+ Assert(DoLockModesConflict(ternaryRelOpts[i].gen.lockmode,
+ ternaryRelOpts[i].gen.lockmode));
+ j++;
+ }
+
for (i = 0; intRelOpts[i].gen.name; i++)
{
Assert(DoLockModesConflict(intRelOpts[i].gen.lockmode,
@@ -640,6 +653,14 @@ initialize_reloptions(void)
j++;
}
+ for (i = 0; ternaryRelOpts[i].gen.name; i++)
+ {
+ relOpts[j] = &ternaryRelOpts[i].gen;
+ relOpts[j]->type = RELOPT_TYPE_TERNARY;
+ relOpts[j]->namelen = strlen(relOpts[j]->name);
+ j++;
+ }
+
for (i = 0; intRelOpts[i].gen.name; i++)
{
relOpts[j] = &intRelOpts[i].gen;
@@ -800,6 +821,9 @@ allocate_reloption(bits32 kinds, int type, const char *name, const char *desc,
case RELOPT_TYPE_BOOL:
size = sizeof(relopt_bool);
break;
+ case RELOPT_TYPE_TERNARY:
+ size = sizeof(relopt_ternary);
+ break;
case RELOPT_TYPE_INT:
size = sizeof(relopt_int);
break;
@@ -883,6 +907,54 @@ add_local_bool_reloption(local_relopts *relopts, const char *name,
add_local_reloption(relopts, (relopt_gen *) newoption, offset);
}
+/*
+ * init_ternary_reloption
+ * Allocate and initialize a new ternary reloption
+ */
+static relopt_ternary *
+init_ternary_reloption(bits32 kinds, const char *name, const char *desc,
+ ternary default_val, LOCKMODE lockmode)
+{
+ relopt_ternary *newoption;
+
+ newoption = (relopt_ternary *) allocate_reloption(kinds,
+ RELOPT_TYPE_TERNARY, name, desc, lockmode);
+ newoption->default_val = default_val;
+
+ return newoption;
+}
+
+/*
+ * add_ternary_reloption
+ * Add a new ternary reloption
+ */
+void
+add_ternary_reloption(bits32 kinds, const char *name, const char *desc,
+ ternary default_val, LOCKMODE lockmode)
+{
+ relopt_ternary *newoption = init_ternary_reloption(kinds, name, desc,
+ default_val, lockmode);
+
+ add_reloption((relopt_gen *) newoption);
+}
+
+/*
+ * add_local_ternary_reloption
+ * Add a new ternary local reloption
+ *
+ * 'offset' is offset of ternary-typed field.
+ */
+void
+add_local_ternary_reloption(local_relopts *relopts, const char *name,
+ const char *desc, ternary default_val,
+ int offset)
+{
+ relopt_ternary *newoption = init_ternary_reloption(RELOPT_KIND_LOCAL,
+ name, desc,
+ default_val, 0);
+
+ add_local_reloption(relopts, (relopt_gen *) newoption, offset);
+}
/*
* init_real_reloption
@@ -1617,6 +1689,18 @@ parse_one_reloption(relopt_value *option, char *text_str, int text_len,
option->gen->name, value)));
}
break;
+ case RELOPT_TYPE_TERNARY:
+ {
+ bool b;
+ parsed = parse_bool(value, &b);
+ option->values.ternary_val = b ? TERNARY_TRUE : TERNARY_FALSE;
+ if (validate && !parsed)
+ ereport(ERROR,
+ (errcode(ERRCODE_INVALID_PARAMETER_VALUE),
+ errmsg("invalid value for ternary option \"%s\": %s",
+ option->gen->name, value)));
+ }
+ break;
case RELOPT_TYPE_INT:
{
relopt_int *optint = (relopt_int *) option->gen;
@@ -1780,17 +1864,6 @@ fillRelOptions(void *rdopts, Size basesize,
char *itempos = ((char *) rdopts) + elems[j].offset;
char *string_val;
- /*
- * If isset_offset is provided, store whether the reloption is
- * set there.
- */
- if (elems[j].isset_offset > 0)
- {
- char *setpos = ((char *) rdopts) + elems[j].isset_offset;
-
- *(bool *) setpos = options[i].isset;
- }
-
switch (options[i].gen->type)
{
case RELOPT_TYPE_BOOL:
@@ -1798,6 +1871,11 @@ fillRelOptions(void *rdopts, Size basesize,
options[i].values.bool_val :
((relopt_bool *) options[i].gen)->default_val;
break;
+ case RELOPT_TYPE_TERNARY:
+ *(ternary *) itempos = options[i].isset ?
+ options[i].values.ternary_val :
+ ((relopt_ternary *) options[i].gen)->default_val;
+ break;
case RELOPT_TYPE_INT:
*(int *) itempos = options[i].isset ?
options[i].values.int_val :
@@ -1912,8 +1990,8 @@ default_reloptions(Datum reloptions, bool validate, relopt_kind kind)
offsetof(StdRdOptions, parallel_workers)},
{"vacuum_index_cleanup", RELOPT_TYPE_ENUM,
offsetof(StdRdOptions, vacuum_index_cleanup)},
- {"vacuum_truncate", RELOPT_TYPE_BOOL,
- offsetof(StdRdOptions, vacuum_truncate), offsetof(StdRdOptions, vacuum_truncate_set)},
+ {"vacuum_truncate", RELOPT_TYPE_TERNARY,
+ offsetof(StdRdOptions, vacuum_truncate)},
{"vacuum_max_eager_freeze_failure_rate", RELOPT_TYPE_REAL,
offsetof(StdRdOptions, vacuum_max_eager_freeze_failure_rate)}
};
@@ -1993,7 +2071,6 @@ build_local_reloptions(local_relopts *relopts, Datum options, bool validate)
elems[i].optname = opt->option->name;
elems[i].opttype = opt->option->type;
elems[i].offset = opt->offset;
- elems[i].isset_offset = 0; /* not supported for local relopts yet */
i++;
}
diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c
index 733ef40ae7..977babff54 100644
--- a/src/backend/commands/vacuum.c
+++ b/src/backend/commands/vacuum.c
@@ -2221,9 +2221,9 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams params,
{
StdRdOptions *opts = (StdRdOptions *) rel->rd_options;
- if (opts && opts->vacuum_truncate_set)
+ if (opts && opts->vacuum_truncate != TERNARY_UNSET)
{
- if (opts->vacuum_truncate)
+ if (opts->vacuum_truncate == TERNARY_TRUE)
params.truncate = VACOPTVALUE_ENABLED;
else
params.truncate = VACOPTVALUE_DISABLED;
diff --git a/src/include/access/reloptions.h b/src/include/access/reloptions.h
index a604a4702c..a436697658 100644
--- a/src/include/access/reloptions.h
+++ b/src/include/access/reloptions.h
@@ -29,6 +29,7 @@
typedef enum relopt_type
{
RELOPT_TYPE_BOOL,
+ RELOPT_TYPE_TERNARY, /* on, off, unset */
RELOPT_TYPE_INT,
RELOPT_TYPE_REAL,
RELOPT_TYPE_ENUM,
@@ -80,6 +81,7 @@ typedef struct relopt_value
union
{
bool bool_val;
+ ternary ternary_val;
int int_val;
double real_val;
int enum_val;
@@ -94,6 +96,12 @@ typedef struct relopt_bool
bool default_val;
} relopt_bool;
+typedef struct relopt_rernary
+{
+ relopt_gen gen;
+ int default_val;
+} relopt_ternary;
+
typedef struct relopt_int
{
relopt_gen gen;
@@ -152,19 +160,6 @@ typedef struct
const char *optname; /* option's name */
relopt_type opttype; /* option's datatype */
int offset; /* offset of field in result struct */
-
- /*
- * isset_offset is an optional offset of a field in the result struct that
- * stores whether the option is explicitly set for the relation or if it
- * just picked up the default value. In most cases, this can be
- * accomplished by giving the reloption a special out-of-range default
- * value (e.g., some integer reloptions use -2), but this isn't always
- * possible. For example, a Boolean reloption cannot be given an
- * out-of-range default, so we need another way to discover the source of
- * its value. This offset is only used if given a value greater than
- * zero.
- */
- int isset_offset;
} relopt_parse_elt;
/* Local reloption definition */
@@ -195,6 +190,8 @@ typedef struct local_relopts
extern relopt_kind add_reloption_kind(void);
extern void add_bool_reloption(bits32 kinds, const char *name, const char *desc,
bool default_val, LOCKMODE lockmode);
+extern void add_ternary_reloption(bits32 kinds, const char *name,
+ const char *desc, int default_val, LOCKMODE lockmode);
extern void add_int_reloption(bits32 kinds, const char *name, const char *desc,
int default_val, int min_val, int max_val,
LOCKMODE lockmode);
@@ -214,6 +211,9 @@ extern void register_reloptions_validator(local_relopts *relopts,
extern void add_local_bool_reloption(local_relopts *relopts, const char *name,
const char *desc, bool default_val,
int offset);
+extern void add_local_ternary_reloption(local_relopts *relopts,
+ const char *name, const char *desc,
+ ternary default_val, int offset);
extern void add_local_int_reloption(local_relopts *relopts, const char *name,
const char *desc, int default_val,
int min_val, int max_val, int offset);
diff --git a/src/include/c.h b/src/include/c.h
index 39022f8a9d..9c59ccccf8 100644
--- a/src/include/c.h
+++ b/src/include/c.h
@@ -488,6 +488,22 @@ typedef void (*pg_funcptr_t) (void);
#include <stdbool.h>
+/*
+ * ternary
+ * Boolean value with an extrea "unset" option
+ *
+ * Ternary data type is used in relation options that can be "true", "false" or
+ * "unset". Since relation options are used deep inside the PostgreSQL code,
+ * this type is declared globally.
+*/
+
+typedef enum ternary
+{
+ TERNARY_FALSE = 0,
+ TERNARY_TRUE = 1,
+ TERNARY_UNSET = -1
+} ternary;
+
/* ----------------------------------------------------------------
* Section 3: standard system types
diff --git a/src/include/utils/rel.h b/src/include/utils/rel.h
index b552359915..7346d618f9 100644
--- a/src/include/utils/rel.h
+++ b/src/include/utils/rel.h
@@ -346,8 +346,7 @@ typedef struct StdRdOptions
bool user_catalog_table; /* use as an additional catalog relation */
int parallel_workers; /* max number of parallel workers */
StdRdOptIndexCleanup vacuum_index_cleanup; /* controls index vacuuming */
- bool vacuum_truncate; /* enables vacuum to truncate a relation */
- bool vacuum_truncate_set; /* whether vacuum_truncate is set */
+ ternary vacuum_truncate; /* enables vacuum to truncate a relation */
/*
* Fraction of pages in a relation that vacuum can eagerly scan and fail
--
2.39.2
--nextPart4155600.3ZeAukHxDK
Content-Disposition: attachment;
filename="v2a-0003-Add-alias-to-be-used-as-unset-state.patch"
Content-Transfer-Encoding: 7Bit
Content-Type: text/x-patch; charset="unicode-2-0-utf-8";
name="v2a-0003-Add-alias-to-be-used-as-unset-state.patch"
^ permalink raw reply [nested|flat] 868+ messages in thread
* [PATCH v2a 2/4] Introduce ternary reloptions
@ 2025-09-04 14:41 Nikolay Shaplov <[email protected]>
0 siblings, 0 replies; 868+ messages in thread
From: Nikolay Shaplov @ 2025-09-04 14:41 UTC (permalink / raw)
Introduce ternary reloption as a replacement for current `vacuum_truncate`
implementation. Remove `vacuum_truncate_set` additional flag and using
`TERNARY_UNSET` value instead.
---
src/backend/access/common/reloptions.c | 129 ++++++++++++++++++++-----
src/backend/commands/vacuum.c | 4 +-
src/include/access/reloptions.h | 26 ++---
src/include/c.h | 16 +++
src/include/utils/rel.h | 3 +-
5 files changed, 135 insertions(+), 43 deletions(-)
diff --git a/src/backend/access/common/reloptions.c b/src/backend/access/common/reloptions.c
index 0af3fea68f..f543f11001 100644
--- a/src/backend/access/common/reloptions.c
+++ b/src/backend/access/common/reloptions.c
@@ -40,9 +40,9 @@
*
* To add an option:
*
- * (i) decide on a type (bool, integer, real, enum, string), name, default
- * value, upper and lower bounds (if applicable); for strings, consider a
- * validation routine.
+ * (i) decide on a type (bool, ternary, integer, real, enum, string), name,
+ * default value, upper and lower bounds (if applicable); for strings,
+ * consider a validation routine.
* (ii) add a record below (or use add_<type>_reloption).
* (iii) add it to the appropriate options struct (perhaps StdRdOptions)
* (iv) add it to the appropriate handling routine (perhaps
@@ -147,15 +147,6 @@ static relopt_bool boolRelOpts[] =
},
false
},
- {
- {
- "vacuum_truncate",
- "Enables vacuum to truncate empty pages at the end of this table",
- RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
- ShareUpdateExclusiveLock
- },
- true
- },
{
{
"deduplicate_items",
@@ -170,6 +161,21 @@ static relopt_bool boolRelOpts[] =
{{NULL}}
};
+static relopt_ternary ternaryRelOpts[] =
+{
+ {
+ {
+ "vacuum_truncate",
+ "Enables vacuum to truncate empty pages at the end of this table",
+ RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
+ ShareUpdateExclusiveLock
+ },
+ TERNARY_UNSET
+ },
+ /* list terminator */
+ {{NULL}}
+};
+
static relopt_int intRelOpts[] =
{
{
@@ -600,6 +606,13 @@ initialize_reloptions(void)
boolRelOpts[i].gen.lockmode));
j++;
}
+ for (i = 0; ternaryRelOpts[i].gen.name; i++)
+ {
+ Assert(DoLockModesConflict(ternaryRelOpts[i].gen.lockmode,
+ ternaryRelOpts[i].gen.lockmode));
+ j++;
+ }
+
for (i = 0; intRelOpts[i].gen.name; i++)
{
Assert(DoLockModesConflict(intRelOpts[i].gen.lockmode,
@@ -640,6 +653,14 @@ initialize_reloptions(void)
j++;
}
+ for (i = 0; ternaryRelOpts[i].gen.name; i++)
+ {
+ relOpts[j] = &ternaryRelOpts[i].gen;
+ relOpts[j]->type = RELOPT_TYPE_TERNARY;
+ relOpts[j]->namelen = strlen(relOpts[j]->name);
+ j++;
+ }
+
for (i = 0; intRelOpts[i].gen.name; i++)
{
relOpts[j] = &intRelOpts[i].gen;
@@ -800,6 +821,9 @@ allocate_reloption(bits32 kinds, int type, const char *name, const char *desc,
case RELOPT_TYPE_BOOL:
size = sizeof(relopt_bool);
break;
+ case RELOPT_TYPE_TERNARY:
+ size = sizeof(relopt_ternary);
+ break;
case RELOPT_TYPE_INT:
size = sizeof(relopt_int);
break;
@@ -883,6 +907,54 @@ add_local_bool_reloption(local_relopts *relopts, const char *name,
add_local_reloption(relopts, (relopt_gen *) newoption, offset);
}
+/*
+ * init_ternary_reloption
+ * Allocate and initialize a new ternary reloption
+ */
+static relopt_ternary *
+init_ternary_reloption(bits32 kinds, const char *name, const char *desc,
+ ternary default_val, LOCKMODE lockmode)
+{
+ relopt_ternary *newoption;
+
+ newoption = (relopt_ternary *) allocate_reloption(kinds,
+ RELOPT_TYPE_TERNARY, name, desc, lockmode);
+ newoption->default_val = default_val;
+
+ return newoption;
+}
+
+/*
+ * add_ternary_reloption
+ * Add a new ternary reloption
+ */
+void
+add_ternary_reloption(bits32 kinds, const char *name, const char *desc,
+ ternary default_val, LOCKMODE lockmode)
+{
+ relopt_ternary *newoption = init_ternary_reloption(kinds, name, desc,
+ default_val, lockmode);
+
+ add_reloption((relopt_gen *) newoption);
+}
+
+/*
+ * add_local_ternary_reloption
+ * Add a new ternary local reloption
+ *
+ * 'offset' is offset of ternary-typed field.
+ */
+void
+add_local_ternary_reloption(local_relopts *relopts, const char *name,
+ const char *desc, ternary default_val,
+ int offset)
+{
+ relopt_ternary *newoption = init_ternary_reloption(RELOPT_KIND_LOCAL,
+ name, desc,
+ default_val, 0);
+
+ add_local_reloption(relopts, (relopt_gen *) newoption, offset);
+}
/*
* init_real_reloption
@@ -1617,6 +1689,18 @@ parse_one_reloption(relopt_value *option, char *text_str, int text_len,
option->gen->name, value)));
}
break;
+ case RELOPT_TYPE_TERNARY:
+ {
+ bool b;
+ parsed = parse_bool(value, &b);
+ option->values.ternary_val = b ? TERNARY_TRUE : TERNARY_FALSE;
+ if (validate && !parsed)
+ ereport(ERROR,
+ (errcode(ERRCODE_INVALID_PARAMETER_VALUE),
+ errmsg("invalid value for ternary option \"%s\": %s",
+ option->gen->name, value)));
+ }
+ break;
case RELOPT_TYPE_INT:
{
relopt_int *optint = (relopt_int *) option->gen;
@@ -1780,17 +1864,6 @@ fillRelOptions(void *rdopts, Size basesize,
char *itempos = ((char *) rdopts) + elems[j].offset;
char *string_val;
- /*
- * If isset_offset is provided, store whether the reloption is
- * set there.
- */
- if (elems[j].isset_offset > 0)
- {
- char *setpos = ((char *) rdopts) + elems[j].isset_offset;
-
- *(bool *) setpos = options[i].isset;
- }
-
switch (options[i].gen->type)
{
case RELOPT_TYPE_BOOL:
@@ -1798,6 +1871,11 @@ fillRelOptions(void *rdopts, Size basesize,
options[i].values.bool_val :
((relopt_bool *) options[i].gen)->default_val;
break;
+ case RELOPT_TYPE_TERNARY:
+ *(ternary *) itempos = options[i].isset ?
+ options[i].values.ternary_val :
+ ((relopt_ternary *) options[i].gen)->default_val;
+ break;
case RELOPT_TYPE_INT:
*(int *) itempos = options[i].isset ?
options[i].values.int_val :
@@ -1912,8 +1990,8 @@ default_reloptions(Datum reloptions, bool validate, relopt_kind kind)
offsetof(StdRdOptions, parallel_workers)},
{"vacuum_index_cleanup", RELOPT_TYPE_ENUM,
offsetof(StdRdOptions, vacuum_index_cleanup)},
- {"vacuum_truncate", RELOPT_TYPE_BOOL,
- offsetof(StdRdOptions, vacuum_truncate), offsetof(StdRdOptions, vacuum_truncate_set)},
+ {"vacuum_truncate", RELOPT_TYPE_TERNARY,
+ offsetof(StdRdOptions, vacuum_truncate)},
{"vacuum_max_eager_freeze_failure_rate", RELOPT_TYPE_REAL,
offsetof(StdRdOptions, vacuum_max_eager_freeze_failure_rate)}
};
@@ -1993,7 +2071,6 @@ build_local_reloptions(local_relopts *relopts, Datum options, bool validate)
elems[i].optname = opt->option->name;
elems[i].opttype = opt->option->type;
elems[i].offset = opt->offset;
- elems[i].isset_offset = 0; /* not supported for local relopts yet */
i++;
}
diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c
index 733ef40ae7..977babff54 100644
--- a/src/backend/commands/vacuum.c
+++ b/src/backend/commands/vacuum.c
@@ -2221,9 +2221,9 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams params,
{
StdRdOptions *opts = (StdRdOptions *) rel->rd_options;
- if (opts && opts->vacuum_truncate_set)
+ if (opts && opts->vacuum_truncate != TERNARY_UNSET)
{
- if (opts->vacuum_truncate)
+ if (opts->vacuum_truncate == TERNARY_TRUE)
params.truncate = VACOPTVALUE_ENABLED;
else
params.truncate = VACOPTVALUE_DISABLED;
diff --git a/src/include/access/reloptions.h b/src/include/access/reloptions.h
index a604a4702c..a436697658 100644
--- a/src/include/access/reloptions.h
+++ b/src/include/access/reloptions.h
@@ -29,6 +29,7 @@
typedef enum relopt_type
{
RELOPT_TYPE_BOOL,
+ RELOPT_TYPE_TERNARY, /* on, off, unset */
RELOPT_TYPE_INT,
RELOPT_TYPE_REAL,
RELOPT_TYPE_ENUM,
@@ -80,6 +81,7 @@ typedef struct relopt_value
union
{
bool bool_val;
+ ternary ternary_val;
int int_val;
double real_val;
int enum_val;
@@ -94,6 +96,12 @@ typedef struct relopt_bool
bool default_val;
} relopt_bool;
+typedef struct relopt_rernary
+{
+ relopt_gen gen;
+ int default_val;
+} relopt_ternary;
+
typedef struct relopt_int
{
relopt_gen gen;
@@ -152,19 +160,6 @@ typedef struct
const char *optname; /* option's name */
relopt_type opttype; /* option's datatype */
int offset; /* offset of field in result struct */
-
- /*
- * isset_offset is an optional offset of a field in the result struct that
- * stores whether the option is explicitly set for the relation or if it
- * just picked up the default value. In most cases, this can be
- * accomplished by giving the reloption a special out-of-range default
- * value (e.g., some integer reloptions use -2), but this isn't always
- * possible. For example, a Boolean reloption cannot be given an
- * out-of-range default, so we need another way to discover the source of
- * its value. This offset is only used if given a value greater than
- * zero.
- */
- int isset_offset;
} relopt_parse_elt;
/* Local reloption definition */
@@ -195,6 +190,8 @@ typedef struct local_relopts
extern relopt_kind add_reloption_kind(void);
extern void add_bool_reloption(bits32 kinds, const char *name, const char *desc,
bool default_val, LOCKMODE lockmode);
+extern void add_ternary_reloption(bits32 kinds, const char *name,
+ const char *desc, int default_val, LOCKMODE lockmode);
extern void add_int_reloption(bits32 kinds, const char *name, const char *desc,
int default_val, int min_val, int max_val,
LOCKMODE lockmode);
@@ -214,6 +211,9 @@ extern void register_reloptions_validator(local_relopts *relopts,
extern void add_local_bool_reloption(local_relopts *relopts, const char *name,
const char *desc, bool default_val,
int offset);
+extern void add_local_ternary_reloption(local_relopts *relopts,
+ const char *name, const char *desc,
+ ternary default_val, int offset);
extern void add_local_int_reloption(local_relopts *relopts, const char *name,
const char *desc, int default_val,
int min_val, int max_val, int offset);
diff --git a/src/include/c.h b/src/include/c.h
index 39022f8a9d..9c59ccccf8 100644
--- a/src/include/c.h
+++ b/src/include/c.h
@@ -488,6 +488,22 @@ typedef void (*pg_funcptr_t) (void);
#include <stdbool.h>
+/*
+ * ternary
+ * Boolean value with an extrea "unset" option
+ *
+ * Ternary data type is used in relation options that can be "true", "false" or
+ * "unset". Since relation options are used deep inside the PostgreSQL code,
+ * this type is declared globally.
+*/
+
+typedef enum ternary
+{
+ TERNARY_FALSE = 0,
+ TERNARY_TRUE = 1,
+ TERNARY_UNSET = -1
+} ternary;
+
/* ----------------------------------------------------------------
* Section 3: standard system types
diff --git a/src/include/utils/rel.h b/src/include/utils/rel.h
index b552359915..7346d618f9 100644
--- a/src/include/utils/rel.h
+++ b/src/include/utils/rel.h
@@ -346,8 +346,7 @@ typedef struct StdRdOptions
bool user_catalog_table; /* use as an additional catalog relation */
int parallel_workers; /* max number of parallel workers */
StdRdOptIndexCleanup vacuum_index_cleanup; /* controls index vacuuming */
- bool vacuum_truncate; /* enables vacuum to truncate a relation */
- bool vacuum_truncate_set; /* whether vacuum_truncate is set */
+ ternary vacuum_truncate; /* enables vacuum to truncate a relation */
/*
* Fraction of pages in a relation that vacuum can eagerly scan and fail
--
2.39.2
--nextPart4155600.3ZeAukHxDK
Content-Disposition: attachment;
filename="v2a-0003-Add-alias-to-be-used-as-unset-state.patch"
Content-Transfer-Encoding: 7Bit
Content-Type: text/x-patch; charset="unicode-2-0-utf-8";
name="v2a-0003-Add-alias-to-be-used-as-unset-state.patch"
^ permalink raw reply [nested|flat] 868+ messages in thread
* [PATCH v2 2/4] Introduce ternary reloptions
@ 2025-09-04 14:41 Nikolay Shaplov <[email protected]>
0 siblings, 0 replies; 868+ messages in thread
From: Nikolay Shaplov @ 2025-09-04 14:41 UTC (permalink / raw)
Introduce ternary reloption as a replacement for current `vacuum_truncate`
implementation. Remove `vacuum_truncate_set` additional flag and using
`TERNARY_UNSET` value instead.
---
src/backend/access/common/reloptions.c | 129 ++++++++++++++++++++-----
src/backend/commands/vacuum.c | 4 +-
src/include/access/reloptions.h | 26 ++---
src/include/c.h | 16 +++
src/include/utils/rel.h | 3 +-
5 files changed, 135 insertions(+), 43 deletions(-)
diff --git a/src/backend/access/common/reloptions.c b/src/backend/access/common/reloptions.c
index 0af3fea68f..f543f11001 100644
--- a/src/backend/access/common/reloptions.c
+++ b/src/backend/access/common/reloptions.c
@@ -40,9 +40,9 @@
*
* To add an option:
*
- * (i) decide on a type (bool, integer, real, enum, string), name, default
- * value, upper and lower bounds (if applicable); for strings, consider a
- * validation routine.
+ * (i) decide on a type (bool, ternary, integer, real, enum, string), name,
+ * default value, upper and lower bounds (if applicable); for strings,
+ * consider a validation routine.
* (ii) add a record below (or use add_<type>_reloption).
* (iii) add it to the appropriate options struct (perhaps StdRdOptions)
* (iv) add it to the appropriate handling routine (perhaps
@@ -147,15 +147,6 @@ static relopt_bool boolRelOpts[] =
},
false
},
- {
- {
- "vacuum_truncate",
- "Enables vacuum to truncate empty pages at the end of this table",
- RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
- ShareUpdateExclusiveLock
- },
- true
- },
{
{
"deduplicate_items",
@@ -170,6 +161,21 @@ static relopt_bool boolRelOpts[] =
{{NULL}}
};
+static relopt_ternary ternaryRelOpts[] =
+{
+ {
+ {
+ "vacuum_truncate",
+ "Enables vacuum to truncate empty pages at the end of this table",
+ RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
+ ShareUpdateExclusiveLock
+ },
+ TERNARY_UNSET
+ },
+ /* list terminator */
+ {{NULL}}
+};
+
static relopt_int intRelOpts[] =
{
{
@@ -600,6 +606,13 @@ initialize_reloptions(void)
boolRelOpts[i].gen.lockmode));
j++;
}
+ for (i = 0; ternaryRelOpts[i].gen.name; i++)
+ {
+ Assert(DoLockModesConflict(ternaryRelOpts[i].gen.lockmode,
+ ternaryRelOpts[i].gen.lockmode));
+ j++;
+ }
+
for (i = 0; intRelOpts[i].gen.name; i++)
{
Assert(DoLockModesConflict(intRelOpts[i].gen.lockmode,
@@ -640,6 +653,14 @@ initialize_reloptions(void)
j++;
}
+ for (i = 0; ternaryRelOpts[i].gen.name; i++)
+ {
+ relOpts[j] = &ternaryRelOpts[i].gen;
+ relOpts[j]->type = RELOPT_TYPE_TERNARY;
+ relOpts[j]->namelen = strlen(relOpts[j]->name);
+ j++;
+ }
+
for (i = 0; intRelOpts[i].gen.name; i++)
{
relOpts[j] = &intRelOpts[i].gen;
@@ -800,6 +821,9 @@ allocate_reloption(bits32 kinds, int type, const char *name, const char *desc,
case RELOPT_TYPE_BOOL:
size = sizeof(relopt_bool);
break;
+ case RELOPT_TYPE_TERNARY:
+ size = sizeof(relopt_ternary);
+ break;
case RELOPT_TYPE_INT:
size = sizeof(relopt_int);
break;
@@ -883,6 +907,54 @@ add_local_bool_reloption(local_relopts *relopts, const char *name,
add_local_reloption(relopts, (relopt_gen *) newoption, offset);
}
+/*
+ * init_ternary_reloption
+ * Allocate and initialize a new ternary reloption
+ */
+static relopt_ternary *
+init_ternary_reloption(bits32 kinds, const char *name, const char *desc,
+ ternary default_val, LOCKMODE lockmode)
+{
+ relopt_ternary *newoption;
+
+ newoption = (relopt_ternary *) allocate_reloption(kinds,
+ RELOPT_TYPE_TERNARY, name, desc, lockmode);
+ newoption->default_val = default_val;
+
+ return newoption;
+}
+
+/*
+ * add_ternary_reloption
+ * Add a new ternary reloption
+ */
+void
+add_ternary_reloption(bits32 kinds, const char *name, const char *desc,
+ ternary default_val, LOCKMODE lockmode)
+{
+ relopt_ternary *newoption = init_ternary_reloption(kinds, name, desc,
+ default_val, lockmode);
+
+ add_reloption((relopt_gen *) newoption);
+}
+
+/*
+ * add_local_ternary_reloption
+ * Add a new ternary local reloption
+ *
+ * 'offset' is offset of ternary-typed field.
+ */
+void
+add_local_ternary_reloption(local_relopts *relopts, const char *name,
+ const char *desc, ternary default_val,
+ int offset)
+{
+ relopt_ternary *newoption = init_ternary_reloption(RELOPT_KIND_LOCAL,
+ name, desc,
+ default_val, 0);
+
+ add_local_reloption(relopts, (relopt_gen *) newoption, offset);
+}
/*
* init_real_reloption
@@ -1617,6 +1689,18 @@ parse_one_reloption(relopt_value *option, char *text_str, int text_len,
option->gen->name, value)));
}
break;
+ case RELOPT_TYPE_TERNARY:
+ {
+ bool b;
+ parsed = parse_bool(value, &b);
+ option->values.ternary_val = b ? TERNARY_TRUE : TERNARY_FALSE;
+ if (validate && !parsed)
+ ereport(ERROR,
+ (errcode(ERRCODE_INVALID_PARAMETER_VALUE),
+ errmsg("invalid value for ternary option \"%s\": %s",
+ option->gen->name, value)));
+ }
+ break;
case RELOPT_TYPE_INT:
{
relopt_int *optint = (relopt_int *) option->gen;
@@ -1780,17 +1864,6 @@ fillRelOptions(void *rdopts, Size basesize,
char *itempos = ((char *) rdopts) + elems[j].offset;
char *string_val;
- /*
- * If isset_offset is provided, store whether the reloption is
- * set there.
- */
- if (elems[j].isset_offset > 0)
- {
- char *setpos = ((char *) rdopts) + elems[j].isset_offset;
-
- *(bool *) setpos = options[i].isset;
- }
-
switch (options[i].gen->type)
{
case RELOPT_TYPE_BOOL:
@@ -1798,6 +1871,11 @@ fillRelOptions(void *rdopts, Size basesize,
options[i].values.bool_val :
((relopt_bool *) options[i].gen)->default_val;
break;
+ case RELOPT_TYPE_TERNARY:
+ *(ternary *) itempos = options[i].isset ?
+ options[i].values.ternary_val :
+ ((relopt_ternary *) options[i].gen)->default_val;
+ break;
case RELOPT_TYPE_INT:
*(int *) itempos = options[i].isset ?
options[i].values.int_val :
@@ -1912,8 +1990,8 @@ default_reloptions(Datum reloptions, bool validate, relopt_kind kind)
offsetof(StdRdOptions, parallel_workers)},
{"vacuum_index_cleanup", RELOPT_TYPE_ENUM,
offsetof(StdRdOptions, vacuum_index_cleanup)},
- {"vacuum_truncate", RELOPT_TYPE_BOOL,
- offsetof(StdRdOptions, vacuum_truncate), offsetof(StdRdOptions, vacuum_truncate_set)},
+ {"vacuum_truncate", RELOPT_TYPE_TERNARY,
+ offsetof(StdRdOptions, vacuum_truncate)},
{"vacuum_max_eager_freeze_failure_rate", RELOPT_TYPE_REAL,
offsetof(StdRdOptions, vacuum_max_eager_freeze_failure_rate)}
};
@@ -1993,7 +2071,6 @@ build_local_reloptions(local_relopts *relopts, Datum options, bool validate)
elems[i].optname = opt->option->name;
elems[i].opttype = opt->option->type;
elems[i].offset = opt->offset;
- elems[i].isset_offset = 0; /* not supported for local relopts yet */
i++;
}
diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c
index 733ef40ae7..977babff54 100644
--- a/src/backend/commands/vacuum.c
+++ b/src/backend/commands/vacuum.c
@@ -2221,9 +2221,9 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams params,
{
StdRdOptions *opts = (StdRdOptions *) rel->rd_options;
- if (opts && opts->vacuum_truncate_set)
+ if (opts && opts->vacuum_truncate != TERNARY_UNSET)
{
- if (opts->vacuum_truncate)
+ if (opts->vacuum_truncate == TERNARY_TRUE)
params.truncate = VACOPTVALUE_ENABLED;
else
params.truncate = VACOPTVALUE_DISABLED;
diff --git a/src/include/access/reloptions.h b/src/include/access/reloptions.h
index a604a4702c..a436697658 100644
--- a/src/include/access/reloptions.h
+++ b/src/include/access/reloptions.h
@@ -29,6 +29,7 @@
typedef enum relopt_type
{
RELOPT_TYPE_BOOL,
+ RELOPT_TYPE_TERNARY, /* on, off, unset */
RELOPT_TYPE_INT,
RELOPT_TYPE_REAL,
RELOPT_TYPE_ENUM,
@@ -80,6 +81,7 @@ typedef struct relopt_value
union
{
bool bool_val;
+ ternary ternary_val;
int int_val;
double real_val;
int enum_val;
@@ -94,6 +96,12 @@ typedef struct relopt_bool
bool default_val;
} relopt_bool;
+typedef struct relopt_rernary
+{
+ relopt_gen gen;
+ int default_val;
+} relopt_ternary;
+
typedef struct relopt_int
{
relopt_gen gen;
@@ -152,19 +160,6 @@ typedef struct
const char *optname; /* option's name */
relopt_type opttype; /* option's datatype */
int offset; /* offset of field in result struct */
-
- /*
- * isset_offset is an optional offset of a field in the result struct that
- * stores whether the option is explicitly set for the relation or if it
- * just picked up the default value. In most cases, this can be
- * accomplished by giving the reloption a special out-of-range default
- * value (e.g., some integer reloptions use -2), but this isn't always
- * possible. For example, a Boolean reloption cannot be given an
- * out-of-range default, so we need another way to discover the source of
- * its value. This offset is only used if given a value greater than
- * zero.
- */
- int isset_offset;
} relopt_parse_elt;
/* Local reloption definition */
@@ -195,6 +190,8 @@ typedef struct local_relopts
extern relopt_kind add_reloption_kind(void);
extern void add_bool_reloption(bits32 kinds, const char *name, const char *desc,
bool default_val, LOCKMODE lockmode);
+extern void add_ternary_reloption(bits32 kinds, const char *name,
+ const char *desc, int default_val, LOCKMODE lockmode);
extern void add_int_reloption(bits32 kinds, const char *name, const char *desc,
int default_val, int min_val, int max_val,
LOCKMODE lockmode);
@@ -214,6 +211,9 @@ extern void register_reloptions_validator(local_relopts *relopts,
extern void add_local_bool_reloption(local_relopts *relopts, const char *name,
const char *desc, bool default_val,
int offset);
+extern void add_local_ternary_reloption(local_relopts *relopts,
+ const char *name, const char *desc,
+ ternary default_val, int offset);
extern void add_local_int_reloption(local_relopts *relopts, const char *name,
const char *desc, int default_val,
int min_val, int max_val, int offset);
diff --git a/src/include/c.h b/src/include/c.h
index 39022f8a9d..9c59ccccf8 100644
--- a/src/include/c.h
+++ b/src/include/c.h
@@ -488,6 +488,22 @@ typedef void (*pg_funcptr_t) (void);
#include <stdbool.h>
+/*
+ * ternary
+ * Boolean value with an extrea "unset" option
+ *
+ * Ternary data type is used in relation options that can be "true", "false" or
+ * "unset". Since relation options are used deep inside the PostgreSQL code,
+ * this type is declared globally.
+*/
+
+typedef enum ternary
+{
+ TERNARY_FALSE = 0,
+ TERNARY_TRUE = 1,
+ TERNARY_UNSET = -1
+} ternary;
+
/* ----------------------------------------------------------------
* Section 3: standard system types
diff --git a/src/include/utils/rel.h b/src/include/utils/rel.h
index b552359915..7346d618f9 100644
--- a/src/include/utils/rel.h
+++ b/src/include/utils/rel.h
@@ -346,8 +346,7 @@ typedef struct StdRdOptions
bool user_catalog_table; /* use as an additional catalog relation */
int parallel_workers; /* max number of parallel workers */
StdRdOptIndexCleanup vacuum_index_cleanup; /* controls index vacuuming */
- bool vacuum_truncate; /* enables vacuum to truncate a relation */
- bool vacuum_truncate_set; /* whether vacuum_truncate is set */
+ ternary vacuum_truncate; /* enables vacuum to truncate a relation */
/*
* Fraction of pages in a relation that vacuum can eagerly scan and fail
--
2.39.2
--nextPart5129932.5fSG56mABF
Content-Disposition: attachment;
filename="v2-0003-Add-alias-to-be-used-as-unset-state.patch"
Content-Transfer-Encoding: 7Bit
Content-Type: text/x-patch; charset="unicode-2-0-utf-8";
name="v2-0003-Add-alias-to-be-used-as-unset-state.patch"
^ permalink raw reply [nested|flat] 868+ messages in thread
* [PATCH v2 2/4] Introduce ternary reloptions
@ 2025-09-04 14:41 Nikolay Shaplov <[email protected]>
0 siblings, 0 replies; 868+ messages in thread
From: Nikolay Shaplov @ 2025-09-04 14:41 UTC (permalink / raw)
Introduce ternary reloption as a replacement for current `vacuum_truncate`
implementation. Remove `vacuum_truncate_set` additional flag and using
`TERNARY_UNSET` value instead.
---
src/backend/access/common/reloptions.c | 129 ++++++++++++++++++++-----
src/backend/commands/vacuum.c | 4 +-
src/include/access/reloptions.h | 26 ++---
src/include/c.h | 16 +++
src/include/utils/rel.h | 3 +-
5 files changed, 135 insertions(+), 43 deletions(-)
diff --git a/src/backend/access/common/reloptions.c b/src/backend/access/common/reloptions.c
index 0af3fea68f..f543f11001 100644
--- a/src/backend/access/common/reloptions.c
+++ b/src/backend/access/common/reloptions.c
@@ -40,9 +40,9 @@
*
* To add an option:
*
- * (i) decide on a type (bool, integer, real, enum, string), name, default
- * value, upper and lower bounds (if applicable); for strings, consider a
- * validation routine.
+ * (i) decide on a type (bool, ternary, integer, real, enum, string), name,
+ * default value, upper and lower bounds (if applicable); for strings,
+ * consider a validation routine.
* (ii) add a record below (or use add_<type>_reloption).
* (iii) add it to the appropriate options struct (perhaps StdRdOptions)
* (iv) add it to the appropriate handling routine (perhaps
@@ -147,15 +147,6 @@ static relopt_bool boolRelOpts[] =
},
false
},
- {
- {
- "vacuum_truncate",
- "Enables vacuum to truncate empty pages at the end of this table",
- RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
- ShareUpdateExclusiveLock
- },
- true
- },
{
{
"deduplicate_items",
@@ -170,6 +161,21 @@ static relopt_bool boolRelOpts[] =
{{NULL}}
};
+static relopt_ternary ternaryRelOpts[] =
+{
+ {
+ {
+ "vacuum_truncate",
+ "Enables vacuum to truncate empty pages at the end of this table",
+ RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
+ ShareUpdateExclusiveLock
+ },
+ TERNARY_UNSET
+ },
+ /* list terminator */
+ {{NULL}}
+};
+
static relopt_int intRelOpts[] =
{
{
@@ -600,6 +606,13 @@ initialize_reloptions(void)
boolRelOpts[i].gen.lockmode));
j++;
}
+ for (i = 0; ternaryRelOpts[i].gen.name; i++)
+ {
+ Assert(DoLockModesConflict(ternaryRelOpts[i].gen.lockmode,
+ ternaryRelOpts[i].gen.lockmode));
+ j++;
+ }
+
for (i = 0; intRelOpts[i].gen.name; i++)
{
Assert(DoLockModesConflict(intRelOpts[i].gen.lockmode,
@@ -640,6 +653,14 @@ initialize_reloptions(void)
j++;
}
+ for (i = 0; ternaryRelOpts[i].gen.name; i++)
+ {
+ relOpts[j] = &ternaryRelOpts[i].gen;
+ relOpts[j]->type = RELOPT_TYPE_TERNARY;
+ relOpts[j]->namelen = strlen(relOpts[j]->name);
+ j++;
+ }
+
for (i = 0; intRelOpts[i].gen.name; i++)
{
relOpts[j] = &intRelOpts[i].gen;
@@ -800,6 +821,9 @@ allocate_reloption(bits32 kinds, int type, const char *name, const char *desc,
case RELOPT_TYPE_BOOL:
size = sizeof(relopt_bool);
break;
+ case RELOPT_TYPE_TERNARY:
+ size = sizeof(relopt_ternary);
+ break;
case RELOPT_TYPE_INT:
size = sizeof(relopt_int);
break;
@@ -883,6 +907,54 @@ add_local_bool_reloption(local_relopts *relopts, const char *name,
add_local_reloption(relopts, (relopt_gen *) newoption, offset);
}
+/*
+ * init_ternary_reloption
+ * Allocate and initialize a new ternary reloption
+ */
+static relopt_ternary *
+init_ternary_reloption(bits32 kinds, const char *name, const char *desc,
+ ternary default_val, LOCKMODE lockmode)
+{
+ relopt_ternary *newoption;
+
+ newoption = (relopt_ternary *) allocate_reloption(kinds,
+ RELOPT_TYPE_TERNARY, name, desc, lockmode);
+ newoption->default_val = default_val;
+
+ return newoption;
+}
+
+/*
+ * add_ternary_reloption
+ * Add a new ternary reloption
+ */
+void
+add_ternary_reloption(bits32 kinds, const char *name, const char *desc,
+ ternary default_val, LOCKMODE lockmode)
+{
+ relopt_ternary *newoption = init_ternary_reloption(kinds, name, desc,
+ default_val, lockmode);
+
+ add_reloption((relopt_gen *) newoption);
+}
+
+/*
+ * add_local_ternary_reloption
+ * Add a new ternary local reloption
+ *
+ * 'offset' is offset of ternary-typed field.
+ */
+void
+add_local_ternary_reloption(local_relopts *relopts, const char *name,
+ const char *desc, ternary default_val,
+ int offset)
+{
+ relopt_ternary *newoption = init_ternary_reloption(RELOPT_KIND_LOCAL,
+ name, desc,
+ default_val, 0);
+
+ add_local_reloption(relopts, (relopt_gen *) newoption, offset);
+}
/*
* init_real_reloption
@@ -1617,6 +1689,18 @@ parse_one_reloption(relopt_value *option, char *text_str, int text_len,
option->gen->name, value)));
}
break;
+ case RELOPT_TYPE_TERNARY:
+ {
+ bool b;
+ parsed = parse_bool(value, &b);
+ option->values.ternary_val = b ? TERNARY_TRUE : TERNARY_FALSE;
+ if (validate && !parsed)
+ ereport(ERROR,
+ (errcode(ERRCODE_INVALID_PARAMETER_VALUE),
+ errmsg("invalid value for ternary option \"%s\": %s",
+ option->gen->name, value)));
+ }
+ break;
case RELOPT_TYPE_INT:
{
relopt_int *optint = (relopt_int *) option->gen;
@@ -1780,17 +1864,6 @@ fillRelOptions(void *rdopts, Size basesize,
char *itempos = ((char *) rdopts) + elems[j].offset;
char *string_val;
- /*
- * If isset_offset is provided, store whether the reloption is
- * set there.
- */
- if (elems[j].isset_offset > 0)
- {
- char *setpos = ((char *) rdopts) + elems[j].isset_offset;
-
- *(bool *) setpos = options[i].isset;
- }
-
switch (options[i].gen->type)
{
case RELOPT_TYPE_BOOL:
@@ -1798,6 +1871,11 @@ fillRelOptions(void *rdopts, Size basesize,
options[i].values.bool_val :
((relopt_bool *) options[i].gen)->default_val;
break;
+ case RELOPT_TYPE_TERNARY:
+ *(ternary *) itempos = options[i].isset ?
+ options[i].values.ternary_val :
+ ((relopt_ternary *) options[i].gen)->default_val;
+ break;
case RELOPT_TYPE_INT:
*(int *) itempos = options[i].isset ?
options[i].values.int_val :
@@ -1912,8 +1990,8 @@ default_reloptions(Datum reloptions, bool validate, relopt_kind kind)
offsetof(StdRdOptions, parallel_workers)},
{"vacuum_index_cleanup", RELOPT_TYPE_ENUM,
offsetof(StdRdOptions, vacuum_index_cleanup)},
- {"vacuum_truncate", RELOPT_TYPE_BOOL,
- offsetof(StdRdOptions, vacuum_truncate), offsetof(StdRdOptions, vacuum_truncate_set)},
+ {"vacuum_truncate", RELOPT_TYPE_TERNARY,
+ offsetof(StdRdOptions, vacuum_truncate)},
{"vacuum_max_eager_freeze_failure_rate", RELOPT_TYPE_REAL,
offsetof(StdRdOptions, vacuum_max_eager_freeze_failure_rate)}
};
@@ -1993,7 +2071,6 @@ build_local_reloptions(local_relopts *relopts, Datum options, bool validate)
elems[i].optname = opt->option->name;
elems[i].opttype = opt->option->type;
elems[i].offset = opt->offset;
- elems[i].isset_offset = 0; /* not supported for local relopts yet */
i++;
}
diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c
index 733ef40ae7..977babff54 100644
--- a/src/backend/commands/vacuum.c
+++ b/src/backend/commands/vacuum.c
@@ -2221,9 +2221,9 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams params,
{
StdRdOptions *opts = (StdRdOptions *) rel->rd_options;
- if (opts && opts->vacuum_truncate_set)
+ if (opts && opts->vacuum_truncate != TERNARY_UNSET)
{
- if (opts->vacuum_truncate)
+ if (opts->vacuum_truncate == TERNARY_TRUE)
params.truncate = VACOPTVALUE_ENABLED;
else
params.truncate = VACOPTVALUE_DISABLED;
diff --git a/src/include/access/reloptions.h b/src/include/access/reloptions.h
index a604a4702c..a436697658 100644
--- a/src/include/access/reloptions.h
+++ b/src/include/access/reloptions.h
@@ -29,6 +29,7 @@
typedef enum relopt_type
{
RELOPT_TYPE_BOOL,
+ RELOPT_TYPE_TERNARY, /* on, off, unset */
RELOPT_TYPE_INT,
RELOPT_TYPE_REAL,
RELOPT_TYPE_ENUM,
@@ -80,6 +81,7 @@ typedef struct relopt_value
union
{
bool bool_val;
+ ternary ternary_val;
int int_val;
double real_val;
int enum_val;
@@ -94,6 +96,12 @@ typedef struct relopt_bool
bool default_val;
} relopt_bool;
+typedef struct relopt_rernary
+{
+ relopt_gen gen;
+ int default_val;
+} relopt_ternary;
+
typedef struct relopt_int
{
relopt_gen gen;
@@ -152,19 +160,6 @@ typedef struct
const char *optname; /* option's name */
relopt_type opttype; /* option's datatype */
int offset; /* offset of field in result struct */
-
- /*
- * isset_offset is an optional offset of a field in the result struct that
- * stores whether the option is explicitly set for the relation or if it
- * just picked up the default value. In most cases, this can be
- * accomplished by giving the reloption a special out-of-range default
- * value (e.g., some integer reloptions use -2), but this isn't always
- * possible. For example, a Boolean reloption cannot be given an
- * out-of-range default, so we need another way to discover the source of
- * its value. This offset is only used if given a value greater than
- * zero.
- */
- int isset_offset;
} relopt_parse_elt;
/* Local reloption definition */
@@ -195,6 +190,8 @@ typedef struct local_relopts
extern relopt_kind add_reloption_kind(void);
extern void add_bool_reloption(bits32 kinds, const char *name, const char *desc,
bool default_val, LOCKMODE lockmode);
+extern void add_ternary_reloption(bits32 kinds, const char *name,
+ const char *desc, int default_val, LOCKMODE lockmode);
extern void add_int_reloption(bits32 kinds, const char *name, const char *desc,
int default_val, int min_val, int max_val,
LOCKMODE lockmode);
@@ -214,6 +211,9 @@ extern void register_reloptions_validator(local_relopts *relopts,
extern void add_local_bool_reloption(local_relopts *relopts, const char *name,
const char *desc, bool default_val,
int offset);
+extern void add_local_ternary_reloption(local_relopts *relopts,
+ const char *name, const char *desc,
+ ternary default_val, int offset);
extern void add_local_int_reloption(local_relopts *relopts, const char *name,
const char *desc, int default_val,
int min_val, int max_val, int offset);
diff --git a/src/include/c.h b/src/include/c.h
index 39022f8a9d..9c59ccccf8 100644
--- a/src/include/c.h
+++ b/src/include/c.h
@@ -488,6 +488,22 @@ typedef void (*pg_funcptr_t) (void);
#include <stdbool.h>
+/*
+ * ternary
+ * Boolean value with an extrea "unset" option
+ *
+ * Ternary data type is used in relation options that can be "true", "false" or
+ * "unset". Since relation options are used deep inside the PostgreSQL code,
+ * this type is declared globally.
+*/
+
+typedef enum ternary
+{
+ TERNARY_FALSE = 0,
+ TERNARY_TRUE = 1,
+ TERNARY_UNSET = -1
+} ternary;
+
/* ----------------------------------------------------------------
* Section 3: standard system types
diff --git a/src/include/utils/rel.h b/src/include/utils/rel.h
index b552359915..7346d618f9 100644
--- a/src/include/utils/rel.h
+++ b/src/include/utils/rel.h
@@ -346,8 +346,7 @@ typedef struct StdRdOptions
bool user_catalog_table; /* use as an additional catalog relation */
int parallel_workers; /* max number of parallel workers */
StdRdOptIndexCleanup vacuum_index_cleanup; /* controls index vacuuming */
- bool vacuum_truncate; /* enables vacuum to truncate a relation */
- bool vacuum_truncate_set; /* whether vacuum_truncate is set */
+ ternary vacuum_truncate; /* enables vacuum to truncate a relation */
/*
* Fraction of pages in a relation that vacuum can eagerly scan and fail
--
2.39.2
--nextPart5129932.5fSG56mABF
Content-Disposition: attachment;
filename="v2-0003-Add-alias-to-be-used-as-unset-state.patch"
Content-Transfer-Encoding: 7Bit
Content-Type: text/x-patch; charset="unicode-2-0-utf-8";
name="v2-0003-Add-alias-to-be-used-as-unset-state.patch"
^ permalink raw reply [nested|flat] 868+ messages in thread
* [PATCH v2a 2/4] Introduce ternary reloptions
@ 2025-09-04 14:41 Nikolay Shaplov <[email protected]>
0 siblings, 0 replies; 868+ messages in thread
From: Nikolay Shaplov @ 2025-09-04 14:41 UTC (permalink / raw)
Introduce ternary reloption as a replacement for current `vacuum_truncate`
implementation. Remove `vacuum_truncate_set` additional flag and using
`TERNARY_UNSET` value instead.
---
src/backend/access/common/reloptions.c | 129 ++++++++++++++++++++-----
src/backend/commands/vacuum.c | 4 +-
src/include/access/reloptions.h | 26 ++---
src/include/c.h | 16 +++
src/include/utils/rel.h | 3 +-
5 files changed, 135 insertions(+), 43 deletions(-)
diff --git a/src/backend/access/common/reloptions.c b/src/backend/access/common/reloptions.c
index 0af3fea68f..f543f11001 100644
--- a/src/backend/access/common/reloptions.c
+++ b/src/backend/access/common/reloptions.c
@@ -40,9 +40,9 @@
*
* To add an option:
*
- * (i) decide on a type (bool, integer, real, enum, string), name, default
- * value, upper and lower bounds (if applicable); for strings, consider a
- * validation routine.
+ * (i) decide on a type (bool, ternary, integer, real, enum, string), name,
+ * default value, upper and lower bounds (if applicable); for strings,
+ * consider a validation routine.
* (ii) add a record below (or use add_<type>_reloption).
* (iii) add it to the appropriate options struct (perhaps StdRdOptions)
* (iv) add it to the appropriate handling routine (perhaps
@@ -147,15 +147,6 @@ static relopt_bool boolRelOpts[] =
},
false
},
- {
- {
- "vacuum_truncate",
- "Enables vacuum to truncate empty pages at the end of this table",
- RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
- ShareUpdateExclusiveLock
- },
- true
- },
{
{
"deduplicate_items",
@@ -170,6 +161,21 @@ static relopt_bool boolRelOpts[] =
{{NULL}}
};
+static relopt_ternary ternaryRelOpts[] =
+{
+ {
+ {
+ "vacuum_truncate",
+ "Enables vacuum to truncate empty pages at the end of this table",
+ RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
+ ShareUpdateExclusiveLock
+ },
+ TERNARY_UNSET
+ },
+ /* list terminator */
+ {{NULL}}
+};
+
static relopt_int intRelOpts[] =
{
{
@@ -600,6 +606,13 @@ initialize_reloptions(void)
boolRelOpts[i].gen.lockmode));
j++;
}
+ for (i = 0; ternaryRelOpts[i].gen.name; i++)
+ {
+ Assert(DoLockModesConflict(ternaryRelOpts[i].gen.lockmode,
+ ternaryRelOpts[i].gen.lockmode));
+ j++;
+ }
+
for (i = 0; intRelOpts[i].gen.name; i++)
{
Assert(DoLockModesConflict(intRelOpts[i].gen.lockmode,
@@ -640,6 +653,14 @@ initialize_reloptions(void)
j++;
}
+ for (i = 0; ternaryRelOpts[i].gen.name; i++)
+ {
+ relOpts[j] = &ternaryRelOpts[i].gen;
+ relOpts[j]->type = RELOPT_TYPE_TERNARY;
+ relOpts[j]->namelen = strlen(relOpts[j]->name);
+ j++;
+ }
+
for (i = 0; intRelOpts[i].gen.name; i++)
{
relOpts[j] = &intRelOpts[i].gen;
@@ -800,6 +821,9 @@ allocate_reloption(bits32 kinds, int type, const char *name, const char *desc,
case RELOPT_TYPE_BOOL:
size = sizeof(relopt_bool);
break;
+ case RELOPT_TYPE_TERNARY:
+ size = sizeof(relopt_ternary);
+ break;
case RELOPT_TYPE_INT:
size = sizeof(relopt_int);
break;
@@ -883,6 +907,54 @@ add_local_bool_reloption(local_relopts *relopts, const char *name,
add_local_reloption(relopts, (relopt_gen *) newoption, offset);
}
+/*
+ * init_ternary_reloption
+ * Allocate and initialize a new ternary reloption
+ */
+static relopt_ternary *
+init_ternary_reloption(bits32 kinds, const char *name, const char *desc,
+ ternary default_val, LOCKMODE lockmode)
+{
+ relopt_ternary *newoption;
+
+ newoption = (relopt_ternary *) allocate_reloption(kinds,
+ RELOPT_TYPE_TERNARY, name, desc, lockmode);
+ newoption->default_val = default_val;
+
+ return newoption;
+}
+
+/*
+ * add_ternary_reloption
+ * Add a new ternary reloption
+ */
+void
+add_ternary_reloption(bits32 kinds, const char *name, const char *desc,
+ ternary default_val, LOCKMODE lockmode)
+{
+ relopt_ternary *newoption = init_ternary_reloption(kinds, name, desc,
+ default_val, lockmode);
+
+ add_reloption((relopt_gen *) newoption);
+}
+
+/*
+ * add_local_ternary_reloption
+ * Add a new ternary local reloption
+ *
+ * 'offset' is offset of ternary-typed field.
+ */
+void
+add_local_ternary_reloption(local_relopts *relopts, const char *name,
+ const char *desc, ternary default_val,
+ int offset)
+{
+ relopt_ternary *newoption = init_ternary_reloption(RELOPT_KIND_LOCAL,
+ name, desc,
+ default_val, 0);
+
+ add_local_reloption(relopts, (relopt_gen *) newoption, offset);
+}
/*
* init_real_reloption
@@ -1617,6 +1689,18 @@ parse_one_reloption(relopt_value *option, char *text_str, int text_len,
option->gen->name, value)));
}
break;
+ case RELOPT_TYPE_TERNARY:
+ {
+ bool b;
+ parsed = parse_bool(value, &b);
+ option->values.ternary_val = b ? TERNARY_TRUE : TERNARY_FALSE;
+ if (validate && !parsed)
+ ereport(ERROR,
+ (errcode(ERRCODE_INVALID_PARAMETER_VALUE),
+ errmsg("invalid value for ternary option \"%s\": %s",
+ option->gen->name, value)));
+ }
+ break;
case RELOPT_TYPE_INT:
{
relopt_int *optint = (relopt_int *) option->gen;
@@ -1780,17 +1864,6 @@ fillRelOptions(void *rdopts, Size basesize,
char *itempos = ((char *) rdopts) + elems[j].offset;
char *string_val;
- /*
- * If isset_offset is provided, store whether the reloption is
- * set there.
- */
- if (elems[j].isset_offset > 0)
- {
- char *setpos = ((char *) rdopts) + elems[j].isset_offset;
-
- *(bool *) setpos = options[i].isset;
- }
-
switch (options[i].gen->type)
{
case RELOPT_TYPE_BOOL:
@@ -1798,6 +1871,11 @@ fillRelOptions(void *rdopts, Size basesize,
options[i].values.bool_val :
((relopt_bool *) options[i].gen)->default_val;
break;
+ case RELOPT_TYPE_TERNARY:
+ *(ternary *) itempos = options[i].isset ?
+ options[i].values.ternary_val :
+ ((relopt_ternary *) options[i].gen)->default_val;
+ break;
case RELOPT_TYPE_INT:
*(int *) itempos = options[i].isset ?
options[i].values.int_val :
@@ -1912,8 +1990,8 @@ default_reloptions(Datum reloptions, bool validate, relopt_kind kind)
offsetof(StdRdOptions, parallel_workers)},
{"vacuum_index_cleanup", RELOPT_TYPE_ENUM,
offsetof(StdRdOptions, vacuum_index_cleanup)},
- {"vacuum_truncate", RELOPT_TYPE_BOOL,
- offsetof(StdRdOptions, vacuum_truncate), offsetof(StdRdOptions, vacuum_truncate_set)},
+ {"vacuum_truncate", RELOPT_TYPE_TERNARY,
+ offsetof(StdRdOptions, vacuum_truncate)},
{"vacuum_max_eager_freeze_failure_rate", RELOPT_TYPE_REAL,
offsetof(StdRdOptions, vacuum_max_eager_freeze_failure_rate)}
};
@@ -1993,7 +2071,6 @@ build_local_reloptions(local_relopts *relopts, Datum options, bool validate)
elems[i].optname = opt->option->name;
elems[i].opttype = opt->option->type;
elems[i].offset = opt->offset;
- elems[i].isset_offset = 0; /* not supported for local relopts yet */
i++;
}
diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c
index 733ef40ae7..977babff54 100644
--- a/src/backend/commands/vacuum.c
+++ b/src/backend/commands/vacuum.c
@@ -2221,9 +2221,9 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams params,
{
StdRdOptions *opts = (StdRdOptions *) rel->rd_options;
- if (opts && opts->vacuum_truncate_set)
+ if (opts && opts->vacuum_truncate != TERNARY_UNSET)
{
- if (opts->vacuum_truncate)
+ if (opts->vacuum_truncate == TERNARY_TRUE)
params.truncate = VACOPTVALUE_ENABLED;
else
params.truncate = VACOPTVALUE_DISABLED;
diff --git a/src/include/access/reloptions.h b/src/include/access/reloptions.h
index a604a4702c..a436697658 100644
--- a/src/include/access/reloptions.h
+++ b/src/include/access/reloptions.h
@@ -29,6 +29,7 @@
typedef enum relopt_type
{
RELOPT_TYPE_BOOL,
+ RELOPT_TYPE_TERNARY, /* on, off, unset */
RELOPT_TYPE_INT,
RELOPT_TYPE_REAL,
RELOPT_TYPE_ENUM,
@@ -80,6 +81,7 @@ typedef struct relopt_value
union
{
bool bool_val;
+ ternary ternary_val;
int int_val;
double real_val;
int enum_val;
@@ -94,6 +96,12 @@ typedef struct relopt_bool
bool default_val;
} relopt_bool;
+typedef struct relopt_rernary
+{
+ relopt_gen gen;
+ int default_val;
+} relopt_ternary;
+
typedef struct relopt_int
{
relopt_gen gen;
@@ -152,19 +160,6 @@ typedef struct
const char *optname; /* option's name */
relopt_type opttype; /* option's datatype */
int offset; /* offset of field in result struct */
-
- /*
- * isset_offset is an optional offset of a field in the result struct that
- * stores whether the option is explicitly set for the relation or if it
- * just picked up the default value. In most cases, this can be
- * accomplished by giving the reloption a special out-of-range default
- * value (e.g., some integer reloptions use -2), but this isn't always
- * possible. For example, a Boolean reloption cannot be given an
- * out-of-range default, so we need another way to discover the source of
- * its value. This offset is only used if given a value greater than
- * zero.
- */
- int isset_offset;
} relopt_parse_elt;
/* Local reloption definition */
@@ -195,6 +190,8 @@ typedef struct local_relopts
extern relopt_kind add_reloption_kind(void);
extern void add_bool_reloption(bits32 kinds, const char *name, const char *desc,
bool default_val, LOCKMODE lockmode);
+extern void add_ternary_reloption(bits32 kinds, const char *name,
+ const char *desc, int default_val, LOCKMODE lockmode);
extern void add_int_reloption(bits32 kinds, const char *name, const char *desc,
int default_val, int min_val, int max_val,
LOCKMODE lockmode);
@@ -214,6 +211,9 @@ extern void register_reloptions_validator(local_relopts *relopts,
extern void add_local_bool_reloption(local_relopts *relopts, const char *name,
const char *desc, bool default_val,
int offset);
+extern void add_local_ternary_reloption(local_relopts *relopts,
+ const char *name, const char *desc,
+ ternary default_val, int offset);
extern void add_local_int_reloption(local_relopts *relopts, const char *name,
const char *desc, int default_val,
int min_val, int max_val, int offset);
diff --git a/src/include/c.h b/src/include/c.h
index 39022f8a9d..9c59ccccf8 100644
--- a/src/include/c.h
+++ b/src/include/c.h
@@ -488,6 +488,22 @@ typedef void (*pg_funcptr_t) (void);
#include <stdbool.h>
+/*
+ * ternary
+ * Boolean value with an extrea "unset" option
+ *
+ * Ternary data type is used in relation options that can be "true", "false" or
+ * "unset". Since relation options are used deep inside the PostgreSQL code,
+ * this type is declared globally.
+*/
+
+typedef enum ternary
+{
+ TERNARY_FALSE = 0,
+ TERNARY_TRUE = 1,
+ TERNARY_UNSET = -1
+} ternary;
+
/* ----------------------------------------------------------------
* Section 3: standard system types
diff --git a/src/include/utils/rel.h b/src/include/utils/rel.h
index b552359915..7346d618f9 100644
--- a/src/include/utils/rel.h
+++ b/src/include/utils/rel.h
@@ -346,8 +346,7 @@ typedef struct StdRdOptions
bool user_catalog_table; /* use as an additional catalog relation */
int parallel_workers; /* max number of parallel workers */
StdRdOptIndexCleanup vacuum_index_cleanup; /* controls index vacuuming */
- bool vacuum_truncate; /* enables vacuum to truncate a relation */
- bool vacuum_truncate_set; /* whether vacuum_truncate is set */
+ ternary vacuum_truncate; /* enables vacuum to truncate a relation */
/*
* Fraction of pages in a relation that vacuum can eagerly scan and fail
--
2.39.2
--nextPart4155600.3ZeAukHxDK
Content-Disposition: attachment;
filename="v2a-0003-Add-alias-to-be-used-as-unset-state.patch"
Content-Transfer-Encoding: 7Bit
Content-Type: text/x-patch; charset="unicode-2-0-utf-8";
name="v2a-0003-Add-alias-to-be-used-as-unset-state.patch"
^ permalink raw reply [nested|flat] 868+ messages in thread
* [PATCH v2a 2/4] Introduce ternary reloptions
@ 2025-09-04 14:41 Nikolay Shaplov <[email protected]>
0 siblings, 0 replies; 868+ messages in thread
From: Nikolay Shaplov @ 2025-09-04 14:41 UTC (permalink / raw)
Introduce ternary reloption as a replacement for current `vacuum_truncate`
implementation. Remove `vacuum_truncate_set` additional flag and using
`TERNARY_UNSET` value instead.
---
src/backend/access/common/reloptions.c | 129 ++++++++++++++++++++-----
src/backend/commands/vacuum.c | 4 +-
src/include/access/reloptions.h | 26 ++---
src/include/c.h | 16 +++
src/include/utils/rel.h | 3 +-
5 files changed, 135 insertions(+), 43 deletions(-)
diff --git a/src/backend/access/common/reloptions.c b/src/backend/access/common/reloptions.c
index 0af3fea68f..f543f11001 100644
--- a/src/backend/access/common/reloptions.c
+++ b/src/backend/access/common/reloptions.c
@@ -40,9 +40,9 @@
*
* To add an option:
*
- * (i) decide on a type (bool, integer, real, enum, string), name, default
- * value, upper and lower bounds (if applicable); for strings, consider a
- * validation routine.
+ * (i) decide on a type (bool, ternary, integer, real, enum, string), name,
+ * default value, upper and lower bounds (if applicable); for strings,
+ * consider a validation routine.
* (ii) add a record below (or use add_<type>_reloption).
* (iii) add it to the appropriate options struct (perhaps StdRdOptions)
* (iv) add it to the appropriate handling routine (perhaps
@@ -147,15 +147,6 @@ static relopt_bool boolRelOpts[] =
},
false
},
- {
- {
- "vacuum_truncate",
- "Enables vacuum to truncate empty pages at the end of this table",
- RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
- ShareUpdateExclusiveLock
- },
- true
- },
{
{
"deduplicate_items",
@@ -170,6 +161,21 @@ static relopt_bool boolRelOpts[] =
{{NULL}}
};
+static relopt_ternary ternaryRelOpts[] =
+{
+ {
+ {
+ "vacuum_truncate",
+ "Enables vacuum to truncate empty pages at the end of this table",
+ RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
+ ShareUpdateExclusiveLock
+ },
+ TERNARY_UNSET
+ },
+ /* list terminator */
+ {{NULL}}
+};
+
static relopt_int intRelOpts[] =
{
{
@@ -600,6 +606,13 @@ initialize_reloptions(void)
boolRelOpts[i].gen.lockmode));
j++;
}
+ for (i = 0; ternaryRelOpts[i].gen.name; i++)
+ {
+ Assert(DoLockModesConflict(ternaryRelOpts[i].gen.lockmode,
+ ternaryRelOpts[i].gen.lockmode));
+ j++;
+ }
+
for (i = 0; intRelOpts[i].gen.name; i++)
{
Assert(DoLockModesConflict(intRelOpts[i].gen.lockmode,
@@ -640,6 +653,14 @@ initialize_reloptions(void)
j++;
}
+ for (i = 0; ternaryRelOpts[i].gen.name; i++)
+ {
+ relOpts[j] = &ternaryRelOpts[i].gen;
+ relOpts[j]->type = RELOPT_TYPE_TERNARY;
+ relOpts[j]->namelen = strlen(relOpts[j]->name);
+ j++;
+ }
+
for (i = 0; intRelOpts[i].gen.name; i++)
{
relOpts[j] = &intRelOpts[i].gen;
@@ -800,6 +821,9 @@ allocate_reloption(bits32 kinds, int type, const char *name, const char *desc,
case RELOPT_TYPE_BOOL:
size = sizeof(relopt_bool);
break;
+ case RELOPT_TYPE_TERNARY:
+ size = sizeof(relopt_ternary);
+ break;
case RELOPT_TYPE_INT:
size = sizeof(relopt_int);
break;
@@ -883,6 +907,54 @@ add_local_bool_reloption(local_relopts *relopts, const char *name,
add_local_reloption(relopts, (relopt_gen *) newoption, offset);
}
+/*
+ * init_ternary_reloption
+ * Allocate and initialize a new ternary reloption
+ */
+static relopt_ternary *
+init_ternary_reloption(bits32 kinds, const char *name, const char *desc,
+ ternary default_val, LOCKMODE lockmode)
+{
+ relopt_ternary *newoption;
+
+ newoption = (relopt_ternary *) allocate_reloption(kinds,
+ RELOPT_TYPE_TERNARY, name, desc, lockmode);
+ newoption->default_val = default_val;
+
+ return newoption;
+}
+
+/*
+ * add_ternary_reloption
+ * Add a new ternary reloption
+ */
+void
+add_ternary_reloption(bits32 kinds, const char *name, const char *desc,
+ ternary default_val, LOCKMODE lockmode)
+{
+ relopt_ternary *newoption = init_ternary_reloption(kinds, name, desc,
+ default_val, lockmode);
+
+ add_reloption((relopt_gen *) newoption);
+}
+
+/*
+ * add_local_ternary_reloption
+ * Add a new ternary local reloption
+ *
+ * 'offset' is offset of ternary-typed field.
+ */
+void
+add_local_ternary_reloption(local_relopts *relopts, const char *name,
+ const char *desc, ternary default_val,
+ int offset)
+{
+ relopt_ternary *newoption = init_ternary_reloption(RELOPT_KIND_LOCAL,
+ name, desc,
+ default_val, 0);
+
+ add_local_reloption(relopts, (relopt_gen *) newoption, offset);
+}
/*
* init_real_reloption
@@ -1617,6 +1689,18 @@ parse_one_reloption(relopt_value *option, char *text_str, int text_len,
option->gen->name, value)));
}
break;
+ case RELOPT_TYPE_TERNARY:
+ {
+ bool b;
+ parsed = parse_bool(value, &b);
+ option->values.ternary_val = b ? TERNARY_TRUE : TERNARY_FALSE;
+ if (validate && !parsed)
+ ereport(ERROR,
+ (errcode(ERRCODE_INVALID_PARAMETER_VALUE),
+ errmsg("invalid value for ternary option \"%s\": %s",
+ option->gen->name, value)));
+ }
+ break;
case RELOPT_TYPE_INT:
{
relopt_int *optint = (relopt_int *) option->gen;
@@ -1780,17 +1864,6 @@ fillRelOptions(void *rdopts, Size basesize,
char *itempos = ((char *) rdopts) + elems[j].offset;
char *string_val;
- /*
- * If isset_offset is provided, store whether the reloption is
- * set there.
- */
- if (elems[j].isset_offset > 0)
- {
- char *setpos = ((char *) rdopts) + elems[j].isset_offset;
-
- *(bool *) setpos = options[i].isset;
- }
-
switch (options[i].gen->type)
{
case RELOPT_TYPE_BOOL:
@@ -1798,6 +1871,11 @@ fillRelOptions(void *rdopts, Size basesize,
options[i].values.bool_val :
((relopt_bool *) options[i].gen)->default_val;
break;
+ case RELOPT_TYPE_TERNARY:
+ *(ternary *) itempos = options[i].isset ?
+ options[i].values.ternary_val :
+ ((relopt_ternary *) options[i].gen)->default_val;
+ break;
case RELOPT_TYPE_INT:
*(int *) itempos = options[i].isset ?
options[i].values.int_val :
@@ -1912,8 +1990,8 @@ default_reloptions(Datum reloptions, bool validate, relopt_kind kind)
offsetof(StdRdOptions, parallel_workers)},
{"vacuum_index_cleanup", RELOPT_TYPE_ENUM,
offsetof(StdRdOptions, vacuum_index_cleanup)},
- {"vacuum_truncate", RELOPT_TYPE_BOOL,
- offsetof(StdRdOptions, vacuum_truncate), offsetof(StdRdOptions, vacuum_truncate_set)},
+ {"vacuum_truncate", RELOPT_TYPE_TERNARY,
+ offsetof(StdRdOptions, vacuum_truncate)},
{"vacuum_max_eager_freeze_failure_rate", RELOPT_TYPE_REAL,
offsetof(StdRdOptions, vacuum_max_eager_freeze_failure_rate)}
};
@@ -1993,7 +2071,6 @@ build_local_reloptions(local_relopts *relopts, Datum options, bool validate)
elems[i].optname = opt->option->name;
elems[i].opttype = opt->option->type;
elems[i].offset = opt->offset;
- elems[i].isset_offset = 0; /* not supported for local relopts yet */
i++;
}
diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c
index 733ef40ae7..977babff54 100644
--- a/src/backend/commands/vacuum.c
+++ b/src/backend/commands/vacuum.c
@@ -2221,9 +2221,9 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams params,
{
StdRdOptions *opts = (StdRdOptions *) rel->rd_options;
- if (opts && opts->vacuum_truncate_set)
+ if (opts && opts->vacuum_truncate != TERNARY_UNSET)
{
- if (opts->vacuum_truncate)
+ if (opts->vacuum_truncate == TERNARY_TRUE)
params.truncate = VACOPTVALUE_ENABLED;
else
params.truncate = VACOPTVALUE_DISABLED;
diff --git a/src/include/access/reloptions.h b/src/include/access/reloptions.h
index a604a4702c..a436697658 100644
--- a/src/include/access/reloptions.h
+++ b/src/include/access/reloptions.h
@@ -29,6 +29,7 @@
typedef enum relopt_type
{
RELOPT_TYPE_BOOL,
+ RELOPT_TYPE_TERNARY, /* on, off, unset */
RELOPT_TYPE_INT,
RELOPT_TYPE_REAL,
RELOPT_TYPE_ENUM,
@@ -80,6 +81,7 @@ typedef struct relopt_value
union
{
bool bool_val;
+ ternary ternary_val;
int int_val;
double real_val;
int enum_val;
@@ -94,6 +96,12 @@ typedef struct relopt_bool
bool default_val;
} relopt_bool;
+typedef struct relopt_rernary
+{
+ relopt_gen gen;
+ int default_val;
+} relopt_ternary;
+
typedef struct relopt_int
{
relopt_gen gen;
@@ -152,19 +160,6 @@ typedef struct
const char *optname; /* option's name */
relopt_type opttype; /* option's datatype */
int offset; /* offset of field in result struct */
-
- /*
- * isset_offset is an optional offset of a field in the result struct that
- * stores whether the option is explicitly set for the relation or if it
- * just picked up the default value. In most cases, this can be
- * accomplished by giving the reloption a special out-of-range default
- * value (e.g., some integer reloptions use -2), but this isn't always
- * possible. For example, a Boolean reloption cannot be given an
- * out-of-range default, so we need another way to discover the source of
- * its value. This offset is only used if given a value greater than
- * zero.
- */
- int isset_offset;
} relopt_parse_elt;
/* Local reloption definition */
@@ -195,6 +190,8 @@ typedef struct local_relopts
extern relopt_kind add_reloption_kind(void);
extern void add_bool_reloption(bits32 kinds, const char *name, const char *desc,
bool default_val, LOCKMODE lockmode);
+extern void add_ternary_reloption(bits32 kinds, const char *name,
+ const char *desc, int default_val, LOCKMODE lockmode);
extern void add_int_reloption(bits32 kinds, const char *name, const char *desc,
int default_val, int min_val, int max_val,
LOCKMODE lockmode);
@@ -214,6 +211,9 @@ extern void register_reloptions_validator(local_relopts *relopts,
extern void add_local_bool_reloption(local_relopts *relopts, const char *name,
const char *desc, bool default_val,
int offset);
+extern void add_local_ternary_reloption(local_relopts *relopts,
+ const char *name, const char *desc,
+ ternary default_val, int offset);
extern void add_local_int_reloption(local_relopts *relopts, const char *name,
const char *desc, int default_val,
int min_val, int max_val, int offset);
diff --git a/src/include/c.h b/src/include/c.h
index 39022f8a9d..9c59ccccf8 100644
--- a/src/include/c.h
+++ b/src/include/c.h
@@ -488,6 +488,22 @@ typedef void (*pg_funcptr_t) (void);
#include <stdbool.h>
+/*
+ * ternary
+ * Boolean value with an extrea "unset" option
+ *
+ * Ternary data type is used in relation options that can be "true", "false" or
+ * "unset". Since relation options are used deep inside the PostgreSQL code,
+ * this type is declared globally.
+*/
+
+typedef enum ternary
+{
+ TERNARY_FALSE = 0,
+ TERNARY_TRUE = 1,
+ TERNARY_UNSET = -1
+} ternary;
+
/* ----------------------------------------------------------------
* Section 3: standard system types
diff --git a/src/include/utils/rel.h b/src/include/utils/rel.h
index b552359915..7346d618f9 100644
--- a/src/include/utils/rel.h
+++ b/src/include/utils/rel.h
@@ -346,8 +346,7 @@ typedef struct StdRdOptions
bool user_catalog_table; /* use as an additional catalog relation */
int parallel_workers; /* max number of parallel workers */
StdRdOptIndexCleanup vacuum_index_cleanup; /* controls index vacuuming */
- bool vacuum_truncate; /* enables vacuum to truncate a relation */
- bool vacuum_truncate_set; /* whether vacuum_truncate is set */
+ ternary vacuum_truncate; /* enables vacuum to truncate a relation */
/*
* Fraction of pages in a relation that vacuum can eagerly scan and fail
--
2.39.2
--nextPart4155600.3ZeAukHxDK
Content-Disposition: attachment;
filename="v2a-0003-Add-alias-to-be-used-as-unset-state.patch"
Content-Transfer-Encoding: 7Bit
Content-Type: text/x-patch; charset="unicode-2-0-utf-8";
name="v2a-0003-Add-alias-to-be-used-as-unset-state.patch"
^ permalink raw reply [nested|flat] 868+ messages in thread
* [PATCH v2 2/4] Introduce ternary reloptions
@ 2025-09-04 14:41 Nikolay Shaplov <[email protected]>
0 siblings, 0 replies; 868+ messages in thread
From: Nikolay Shaplov @ 2025-09-04 14:41 UTC (permalink / raw)
Introduce ternary reloption as a replacement for current `vacuum_truncate`
implementation. Remove `vacuum_truncate_set` additional flag and using
`TERNARY_UNSET` value instead.
---
src/backend/access/common/reloptions.c | 129 ++++++++++++++++++++-----
src/backend/commands/vacuum.c | 4 +-
src/include/access/reloptions.h | 26 ++---
src/include/c.h | 16 +++
src/include/utils/rel.h | 3 +-
5 files changed, 135 insertions(+), 43 deletions(-)
diff --git a/src/backend/access/common/reloptions.c b/src/backend/access/common/reloptions.c
index 0af3fea68f..f543f11001 100644
--- a/src/backend/access/common/reloptions.c
+++ b/src/backend/access/common/reloptions.c
@@ -40,9 +40,9 @@
*
* To add an option:
*
- * (i) decide on a type (bool, integer, real, enum, string), name, default
- * value, upper and lower bounds (if applicable); for strings, consider a
- * validation routine.
+ * (i) decide on a type (bool, ternary, integer, real, enum, string), name,
+ * default value, upper and lower bounds (if applicable); for strings,
+ * consider a validation routine.
* (ii) add a record below (or use add_<type>_reloption).
* (iii) add it to the appropriate options struct (perhaps StdRdOptions)
* (iv) add it to the appropriate handling routine (perhaps
@@ -147,15 +147,6 @@ static relopt_bool boolRelOpts[] =
},
false
},
- {
- {
- "vacuum_truncate",
- "Enables vacuum to truncate empty pages at the end of this table",
- RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
- ShareUpdateExclusiveLock
- },
- true
- },
{
{
"deduplicate_items",
@@ -170,6 +161,21 @@ static relopt_bool boolRelOpts[] =
{{NULL}}
};
+static relopt_ternary ternaryRelOpts[] =
+{
+ {
+ {
+ "vacuum_truncate",
+ "Enables vacuum to truncate empty pages at the end of this table",
+ RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
+ ShareUpdateExclusiveLock
+ },
+ TERNARY_UNSET
+ },
+ /* list terminator */
+ {{NULL}}
+};
+
static relopt_int intRelOpts[] =
{
{
@@ -600,6 +606,13 @@ initialize_reloptions(void)
boolRelOpts[i].gen.lockmode));
j++;
}
+ for (i = 0; ternaryRelOpts[i].gen.name; i++)
+ {
+ Assert(DoLockModesConflict(ternaryRelOpts[i].gen.lockmode,
+ ternaryRelOpts[i].gen.lockmode));
+ j++;
+ }
+
for (i = 0; intRelOpts[i].gen.name; i++)
{
Assert(DoLockModesConflict(intRelOpts[i].gen.lockmode,
@@ -640,6 +653,14 @@ initialize_reloptions(void)
j++;
}
+ for (i = 0; ternaryRelOpts[i].gen.name; i++)
+ {
+ relOpts[j] = &ternaryRelOpts[i].gen;
+ relOpts[j]->type = RELOPT_TYPE_TERNARY;
+ relOpts[j]->namelen = strlen(relOpts[j]->name);
+ j++;
+ }
+
for (i = 0; intRelOpts[i].gen.name; i++)
{
relOpts[j] = &intRelOpts[i].gen;
@@ -800,6 +821,9 @@ allocate_reloption(bits32 kinds, int type, const char *name, const char *desc,
case RELOPT_TYPE_BOOL:
size = sizeof(relopt_bool);
break;
+ case RELOPT_TYPE_TERNARY:
+ size = sizeof(relopt_ternary);
+ break;
case RELOPT_TYPE_INT:
size = sizeof(relopt_int);
break;
@@ -883,6 +907,54 @@ add_local_bool_reloption(local_relopts *relopts, const char *name,
add_local_reloption(relopts, (relopt_gen *) newoption, offset);
}
+/*
+ * init_ternary_reloption
+ * Allocate and initialize a new ternary reloption
+ */
+static relopt_ternary *
+init_ternary_reloption(bits32 kinds, const char *name, const char *desc,
+ ternary default_val, LOCKMODE lockmode)
+{
+ relopt_ternary *newoption;
+
+ newoption = (relopt_ternary *) allocate_reloption(kinds,
+ RELOPT_TYPE_TERNARY, name, desc, lockmode);
+ newoption->default_val = default_val;
+
+ return newoption;
+}
+
+/*
+ * add_ternary_reloption
+ * Add a new ternary reloption
+ */
+void
+add_ternary_reloption(bits32 kinds, const char *name, const char *desc,
+ ternary default_val, LOCKMODE lockmode)
+{
+ relopt_ternary *newoption = init_ternary_reloption(kinds, name, desc,
+ default_val, lockmode);
+
+ add_reloption((relopt_gen *) newoption);
+}
+
+/*
+ * add_local_ternary_reloption
+ * Add a new ternary local reloption
+ *
+ * 'offset' is offset of ternary-typed field.
+ */
+void
+add_local_ternary_reloption(local_relopts *relopts, const char *name,
+ const char *desc, ternary default_val,
+ int offset)
+{
+ relopt_ternary *newoption = init_ternary_reloption(RELOPT_KIND_LOCAL,
+ name, desc,
+ default_val, 0);
+
+ add_local_reloption(relopts, (relopt_gen *) newoption, offset);
+}
/*
* init_real_reloption
@@ -1617,6 +1689,18 @@ parse_one_reloption(relopt_value *option, char *text_str, int text_len,
option->gen->name, value)));
}
break;
+ case RELOPT_TYPE_TERNARY:
+ {
+ bool b;
+ parsed = parse_bool(value, &b);
+ option->values.ternary_val = b ? TERNARY_TRUE : TERNARY_FALSE;
+ if (validate && !parsed)
+ ereport(ERROR,
+ (errcode(ERRCODE_INVALID_PARAMETER_VALUE),
+ errmsg("invalid value for ternary option \"%s\": %s",
+ option->gen->name, value)));
+ }
+ break;
case RELOPT_TYPE_INT:
{
relopt_int *optint = (relopt_int *) option->gen;
@@ -1780,17 +1864,6 @@ fillRelOptions(void *rdopts, Size basesize,
char *itempos = ((char *) rdopts) + elems[j].offset;
char *string_val;
- /*
- * If isset_offset is provided, store whether the reloption is
- * set there.
- */
- if (elems[j].isset_offset > 0)
- {
- char *setpos = ((char *) rdopts) + elems[j].isset_offset;
-
- *(bool *) setpos = options[i].isset;
- }
-
switch (options[i].gen->type)
{
case RELOPT_TYPE_BOOL:
@@ -1798,6 +1871,11 @@ fillRelOptions(void *rdopts, Size basesize,
options[i].values.bool_val :
((relopt_bool *) options[i].gen)->default_val;
break;
+ case RELOPT_TYPE_TERNARY:
+ *(ternary *) itempos = options[i].isset ?
+ options[i].values.ternary_val :
+ ((relopt_ternary *) options[i].gen)->default_val;
+ break;
case RELOPT_TYPE_INT:
*(int *) itempos = options[i].isset ?
options[i].values.int_val :
@@ -1912,8 +1990,8 @@ default_reloptions(Datum reloptions, bool validate, relopt_kind kind)
offsetof(StdRdOptions, parallel_workers)},
{"vacuum_index_cleanup", RELOPT_TYPE_ENUM,
offsetof(StdRdOptions, vacuum_index_cleanup)},
- {"vacuum_truncate", RELOPT_TYPE_BOOL,
- offsetof(StdRdOptions, vacuum_truncate), offsetof(StdRdOptions, vacuum_truncate_set)},
+ {"vacuum_truncate", RELOPT_TYPE_TERNARY,
+ offsetof(StdRdOptions, vacuum_truncate)},
{"vacuum_max_eager_freeze_failure_rate", RELOPT_TYPE_REAL,
offsetof(StdRdOptions, vacuum_max_eager_freeze_failure_rate)}
};
@@ -1993,7 +2071,6 @@ build_local_reloptions(local_relopts *relopts, Datum options, bool validate)
elems[i].optname = opt->option->name;
elems[i].opttype = opt->option->type;
elems[i].offset = opt->offset;
- elems[i].isset_offset = 0; /* not supported for local relopts yet */
i++;
}
diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c
index 733ef40ae7..977babff54 100644
--- a/src/backend/commands/vacuum.c
+++ b/src/backend/commands/vacuum.c
@@ -2221,9 +2221,9 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams params,
{
StdRdOptions *opts = (StdRdOptions *) rel->rd_options;
- if (opts && opts->vacuum_truncate_set)
+ if (opts && opts->vacuum_truncate != TERNARY_UNSET)
{
- if (opts->vacuum_truncate)
+ if (opts->vacuum_truncate == TERNARY_TRUE)
params.truncate = VACOPTVALUE_ENABLED;
else
params.truncate = VACOPTVALUE_DISABLED;
diff --git a/src/include/access/reloptions.h b/src/include/access/reloptions.h
index a604a4702c..a436697658 100644
--- a/src/include/access/reloptions.h
+++ b/src/include/access/reloptions.h
@@ -29,6 +29,7 @@
typedef enum relopt_type
{
RELOPT_TYPE_BOOL,
+ RELOPT_TYPE_TERNARY, /* on, off, unset */
RELOPT_TYPE_INT,
RELOPT_TYPE_REAL,
RELOPT_TYPE_ENUM,
@@ -80,6 +81,7 @@ typedef struct relopt_value
union
{
bool bool_val;
+ ternary ternary_val;
int int_val;
double real_val;
int enum_val;
@@ -94,6 +96,12 @@ typedef struct relopt_bool
bool default_val;
} relopt_bool;
+typedef struct relopt_rernary
+{
+ relopt_gen gen;
+ int default_val;
+} relopt_ternary;
+
typedef struct relopt_int
{
relopt_gen gen;
@@ -152,19 +160,6 @@ typedef struct
const char *optname; /* option's name */
relopt_type opttype; /* option's datatype */
int offset; /* offset of field in result struct */
-
- /*
- * isset_offset is an optional offset of a field in the result struct that
- * stores whether the option is explicitly set for the relation or if it
- * just picked up the default value. In most cases, this can be
- * accomplished by giving the reloption a special out-of-range default
- * value (e.g., some integer reloptions use -2), but this isn't always
- * possible. For example, a Boolean reloption cannot be given an
- * out-of-range default, so we need another way to discover the source of
- * its value. This offset is only used if given a value greater than
- * zero.
- */
- int isset_offset;
} relopt_parse_elt;
/* Local reloption definition */
@@ -195,6 +190,8 @@ typedef struct local_relopts
extern relopt_kind add_reloption_kind(void);
extern void add_bool_reloption(bits32 kinds, const char *name, const char *desc,
bool default_val, LOCKMODE lockmode);
+extern void add_ternary_reloption(bits32 kinds, const char *name,
+ const char *desc, int default_val, LOCKMODE lockmode);
extern void add_int_reloption(bits32 kinds, const char *name, const char *desc,
int default_val, int min_val, int max_val,
LOCKMODE lockmode);
@@ -214,6 +211,9 @@ extern void register_reloptions_validator(local_relopts *relopts,
extern void add_local_bool_reloption(local_relopts *relopts, const char *name,
const char *desc, bool default_val,
int offset);
+extern void add_local_ternary_reloption(local_relopts *relopts,
+ const char *name, const char *desc,
+ ternary default_val, int offset);
extern void add_local_int_reloption(local_relopts *relopts, const char *name,
const char *desc, int default_val,
int min_val, int max_val, int offset);
diff --git a/src/include/c.h b/src/include/c.h
index 39022f8a9d..9c59ccccf8 100644
--- a/src/include/c.h
+++ b/src/include/c.h
@@ -488,6 +488,22 @@ typedef void (*pg_funcptr_t) (void);
#include <stdbool.h>
+/*
+ * ternary
+ * Boolean value with an extrea "unset" option
+ *
+ * Ternary data type is used in relation options that can be "true", "false" or
+ * "unset". Since relation options are used deep inside the PostgreSQL code,
+ * this type is declared globally.
+*/
+
+typedef enum ternary
+{
+ TERNARY_FALSE = 0,
+ TERNARY_TRUE = 1,
+ TERNARY_UNSET = -1
+} ternary;
+
/* ----------------------------------------------------------------
* Section 3: standard system types
diff --git a/src/include/utils/rel.h b/src/include/utils/rel.h
index b552359915..7346d618f9 100644
--- a/src/include/utils/rel.h
+++ b/src/include/utils/rel.h
@@ -346,8 +346,7 @@ typedef struct StdRdOptions
bool user_catalog_table; /* use as an additional catalog relation */
int parallel_workers; /* max number of parallel workers */
StdRdOptIndexCleanup vacuum_index_cleanup; /* controls index vacuuming */
- bool vacuum_truncate; /* enables vacuum to truncate a relation */
- bool vacuum_truncate_set; /* whether vacuum_truncate is set */
+ ternary vacuum_truncate; /* enables vacuum to truncate a relation */
/*
* Fraction of pages in a relation that vacuum can eagerly scan and fail
--
2.39.2
--nextPart5129932.5fSG56mABF
Content-Disposition: attachment;
filename="v2-0003-Add-alias-to-be-used-as-unset-state.patch"
Content-Transfer-Encoding: 7Bit
Content-Type: text/x-patch; charset="unicode-2-0-utf-8";
name="v2-0003-Add-alias-to-be-used-as-unset-state.patch"
^ permalink raw reply [nested|flat] 868+ messages in thread
* [PATCH v2 2/4] Introduce ternary reloptions
@ 2025-09-04 14:41 Nikolay Shaplov <[email protected]>
0 siblings, 0 replies; 868+ messages in thread
From: Nikolay Shaplov @ 2025-09-04 14:41 UTC (permalink / raw)
Introduce ternary reloption as a replacement for current `vacuum_truncate`
implementation. Remove `vacuum_truncate_set` additional flag and using
`TERNARY_UNSET` value instead.
---
src/backend/access/common/reloptions.c | 129 ++++++++++++++++++++-----
src/backend/commands/vacuum.c | 4 +-
src/include/access/reloptions.h | 26 ++---
src/include/c.h | 16 +++
src/include/utils/rel.h | 3 +-
5 files changed, 135 insertions(+), 43 deletions(-)
diff --git a/src/backend/access/common/reloptions.c b/src/backend/access/common/reloptions.c
index 0af3fea68f..f543f11001 100644
--- a/src/backend/access/common/reloptions.c
+++ b/src/backend/access/common/reloptions.c
@@ -40,9 +40,9 @@
*
* To add an option:
*
- * (i) decide on a type (bool, integer, real, enum, string), name, default
- * value, upper and lower bounds (if applicable); for strings, consider a
- * validation routine.
+ * (i) decide on a type (bool, ternary, integer, real, enum, string), name,
+ * default value, upper and lower bounds (if applicable); for strings,
+ * consider a validation routine.
* (ii) add a record below (or use add_<type>_reloption).
* (iii) add it to the appropriate options struct (perhaps StdRdOptions)
* (iv) add it to the appropriate handling routine (perhaps
@@ -147,15 +147,6 @@ static relopt_bool boolRelOpts[] =
},
false
},
- {
- {
- "vacuum_truncate",
- "Enables vacuum to truncate empty pages at the end of this table",
- RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
- ShareUpdateExclusiveLock
- },
- true
- },
{
{
"deduplicate_items",
@@ -170,6 +161,21 @@ static relopt_bool boolRelOpts[] =
{{NULL}}
};
+static relopt_ternary ternaryRelOpts[] =
+{
+ {
+ {
+ "vacuum_truncate",
+ "Enables vacuum to truncate empty pages at the end of this table",
+ RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
+ ShareUpdateExclusiveLock
+ },
+ TERNARY_UNSET
+ },
+ /* list terminator */
+ {{NULL}}
+};
+
static relopt_int intRelOpts[] =
{
{
@@ -600,6 +606,13 @@ initialize_reloptions(void)
boolRelOpts[i].gen.lockmode));
j++;
}
+ for (i = 0; ternaryRelOpts[i].gen.name; i++)
+ {
+ Assert(DoLockModesConflict(ternaryRelOpts[i].gen.lockmode,
+ ternaryRelOpts[i].gen.lockmode));
+ j++;
+ }
+
for (i = 0; intRelOpts[i].gen.name; i++)
{
Assert(DoLockModesConflict(intRelOpts[i].gen.lockmode,
@@ -640,6 +653,14 @@ initialize_reloptions(void)
j++;
}
+ for (i = 0; ternaryRelOpts[i].gen.name; i++)
+ {
+ relOpts[j] = &ternaryRelOpts[i].gen;
+ relOpts[j]->type = RELOPT_TYPE_TERNARY;
+ relOpts[j]->namelen = strlen(relOpts[j]->name);
+ j++;
+ }
+
for (i = 0; intRelOpts[i].gen.name; i++)
{
relOpts[j] = &intRelOpts[i].gen;
@@ -800,6 +821,9 @@ allocate_reloption(bits32 kinds, int type, const char *name, const char *desc,
case RELOPT_TYPE_BOOL:
size = sizeof(relopt_bool);
break;
+ case RELOPT_TYPE_TERNARY:
+ size = sizeof(relopt_ternary);
+ break;
case RELOPT_TYPE_INT:
size = sizeof(relopt_int);
break;
@@ -883,6 +907,54 @@ add_local_bool_reloption(local_relopts *relopts, const char *name,
add_local_reloption(relopts, (relopt_gen *) newoption, offset);
}
+/*
+ * init_ternary_reloption
+ * Allocate and initialize a new ternary reloption
+ */
+static relopt_ternary *
+init_ternary_reloption(bits32 kinds, const char *name, const char *desc,
+ ternary default_val, LOCKMODE lockmode)
+{
+ relopt_ternary *newoption;
+
+ newoption = (relopt_ternary *) allocate_reloption(kinds,
+ RELOPT_TYPE_TERNARY, name, desc, lockmode);
+ newoption->default_val = default_val;
+
+ return newoption;
+}
+
+/*
+ * add_ternary_reloption
+ * Add a new ternary reloption
+ */
+void
+add_ternary_reloption(bits32 kinds, const char *name, const char *desc,
+ ternary default_val, LOCKMODE lockmode)
+{
+ relopt_ternary *newoption = init_ternary_reloption(kinds, name, desc,
+ default_val, lockmode);
+
+ add_reloption((relopt_gen *) newoption);
+}
+
+/*
+ * add_local_ternary_reloption
+ * Add a new ternary local reloption
+ *
+ * 'offset' is offset of ternary-typed field.
+ */
+void
+add_local_ternary_reloption(local_relopts *relopts, const char *name,
+ const char *desc, ternary default_val,
+ int offset)
+{
+ relopt_ternary *newoption = init_ternary_reloption(RELOPT_KIND_LOCAL,
+ name, desc,
+ default_val, 0);
+
+ add_local_reloption(relopts, (relopt_gen *) newoption, offset);
+}
/*
* init_real_reloption
@@ -1617,6 +1689,18 @@ parse_one_reloption(relopt_value *option, char *text_str, int text_len,
option->gen->name, value)));
}
break;
+ case RELOPT_TYPE_TERNARY:
+ {
+ bool b;
+ parsed = parse_bool(value, &b);
+ option->values.ternary_val = b ? TERNARY_TRUE : TERNARY_FALSE;
+ if (validate && !parsed)
+ ereport(ERROR,
+ (errcode(ERRCODE_INVALID_PARAMETER_VALUE),
+ errmsg("invalid value for ternary option \"%s\": %s",
+ option->gen->name, value)));
+ }
+ break;
case RELOPT_TYPE_INT:
{
relopt_int *optint = (relopt_int *) option->gen;
@@ -1780,17 +1864,6 @@ fillRelOptions(void *rdopts, Size basesize,
char *itempos = ((char *) rdopts) + elems[j].offset;
char *string_val;
- /*
- * If isset_offset is provided, store whether the reloption is
- * set there.
- */
- if (elems[j].isset_offset > 0)
- {
- char *setpos = ((char *) rdopts) + elems[j].isset_offset;
-
- *(bool *) setpos = options[i].isset;
- }
-
switch (options[i].gen->type)
{
case RELOPT_TYPE_BOOL:
@@ -1798,6 +1871,11 @@ fillRelOptions(void *rdopts, Size basesize,
options[i].values.bool_val :
((relopt_bool *) options[i].gen)->default_val;
break;
+ case RELOPT_TYPE_TERNARY:
+ *(ternary *) itempos = options[i].isset ?
+ options[i].values.ternary_val :
+ ((relopt_ternary *) options[i].gen)->default_val;
+ break;
case RELOPT_TYPE_INT:
*(int *) itempos = options[i].isset ?
options[i].values.int_val :
@@ -1912,8 +1990,8 @@ default_reloptions(Datum reloptions, bool validate, relopt_kind kind)
offsetof(StdRdOptions, parallel_workers)},
{"vacuum_index_cleanup", RELOPT_TYPE_ENUM,
offsetof(StdRdOptions, vacuum_index_cleanup)},
- {"vacuum_truncate", RELOPT_TYPE_BOOL,
- offsetof(StdRdOptions, vacuum_truncate), offsetof(StdRdOptions, vacuum_truncate_set)},
+ {"vacuum_truncate", RELOPT_TYPE_TERNARY,
+ offsetof(StdRdOptions, vacuum_truncate)},
{"vacuum_max_eager_freeze_failure_rate", RELOPT_TYPE_REAL,
offsetof(StdRdOptions, vacuum_max_eager_freeze_failure_rate)}
};
@@ -1993,7 +2071,6 @@ build_local_reloptions(local_relopts *relopts, Datum options, bool validate)
elems[i].optname = opt->option->name;
elems[i].opttype = opt->option->type;
elems[i].offset = opt->offset;
- elems[i].isset_offset = 0; /* not supported for local relopts yet */
i++;
}
diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c
index 733ef40ae7..977babff54 100644
--- a/src/backend/commands/vacuum.c
+++ b/src/backend/commands/vacuum.c
@@ -2221,9 +2221,9 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams params,
{
StdRdOptions *opts = (StdRdOptions *) rel->rd_options;
- if (opts && opts->vacuum_truncate_set)
+ if (opts && opts->vacuum_truncate != TERNARY_UNSET)
{
- if (opts->vacuum_truncate)
+ if (opts->vacuum_truncate == TERNARY_TRUE)
params.truncate = VACOPTVALUE_ENABLED;
else
params.truncate = VACOPTVALUE_DISABLED;
diff --git a/src/include/access/reloptions.h b/src/include/access/reloptions.h
index a604a4702c..a436697658 100644
--- a/src/include/access/reloptions.h
+++ b/src/include/access/reloptions.h
@@ -29,6 +29,7 @@
typedef enum relopt_type
{
RELOPT_TYPE_BOOL,
+ RELOPT_TYPE_TERNARY, /* on, off, unset */
RELOPT_TYPE_INT,
RELOPT_TYPE_REAL,
RELOPT_TYPE_ENUM,
@@ -80,6 +81,7 @@ typedef struct relopt_value
union
{
bool bool_val;
+ ternary ternary_val;
int int_val;
double real_val;
int enum_val;
@@ -94,6 +96,12 @@ typedef struct relopt_bool
bool default_val;
} relopt_bool;
+typedef struct relopt_rernary
+{
+ relopt_gen gen;
+ int default_val;
+} relopt_ternary;
+
typedef struct relopt_int
{
relopt_gen gen;
@@ -152,19 +160,6 @@ typedef struct
const char *optname; /* option's name */
relopt_type opttype; /* option's datatype */
int offset; /* offset of field in result struct */
-
- /*
- * isset_offset is an optional offset of a field in the result struct that
- * stores whether the option is explicitly set for the relation or if it
- * just picked up the default value. In most cases, this can be
- * accomplished by giving the reloption a special out-of-range default
- * value (e.g., some integer reloptions use -2), but this isn't always
- * possible. For example, a Boolean reloption cannot be given an
- * out-of-range default, so we need another way to discover the source of
- * its value. This offset is only used if given a value greater than
- * zero.
- */
- int isset_offset;
} relopt_parse_elt;
/* Local reloption definition */
@@ -195,6 +190,8 @@ typedef struct local_relopts
extern relopt_kind add_reloption_kind(void);
extern void add_bool_reloption(bits32 kinds, const char *name, const char *desc,
bool default_val, LOCKMODE lockmode);
+extern void add_ternary_reloption(bits32 kinds, const char *name,
+ const char *desc, int default_val, LOCKMODE lockmode);
extern void add_int_reloption(bits32 kinds, const char *name, const char *desc,
int default_val, int min_val, int max_val,
LOCKMODE lockmode);
@@ -214,6 +211,9 @@ extern void register_reloptions_validator(local_relopts *relopts,
extern void add_local_bool_reloption(local_relopts *relopts, const char *name,
const char *desc, bool default_val,
int offset);
+extern void add_local_ternary_reloption(local_relopts *relopts,
+ const char *name, const char *desc,
+ ternary default_val, int offset);
extern void add_local_int_reloption(local_relopts *relopts, const char *name,
const char *desc, int default_val,
int min_val, int max_val, int offset);
diff --git a/src/include/c.h b/src/include/c.h
index 39022f8a9d..9c59ccccf8 100644
--- a/src/include/c.h
+++ b/src/include/c.h
@@ -488,6 +488,22 @@ typedef void (*pg_funcptr_t) (void);
#include <stdbool.h>
+/*
+ * ternary
+ * Boolean value with an extrea "unset" option
+ *
+ * Ternary data type is used in relation options that can be "true", "false" or
+ * "unset". Since relation options are used deep inside the PostgreSQL code,
+ * this type is declared globally.
+*/
+
+typedef enum ternary
+{
+ TERNARY_FALSE = 0,
+ TERNARY_TRUE = 1,
+ TERNARY_UNSET = -1
+} ternary;
+
/* ----------------------------------------------------------------
* Section 3: standard system types
diff --git a/src/include/utils/rel.h b/src/include/utils/rel.h
index b552359915..7346d618f9 100644
--- a/src/include/utils/rel.h
+++ b/src/include/utils/rel.h
@@ -346,8 +346,7 @@ typedef struct StdRdOptions
bool user_catalog_table; /* use as an additional catalog relation */
int parallel_workers; /* max number of parallel workers */
StdRdOptIndexCleanup vacuum_index_cleanup; /* controls index vacuuming */
- bool vacuum_truncate; /* enables vacuum to truncate a relation */
- bool vacuum_truncate_set; /* whether vacuum_truncate is set */
+ ternary vacuum_truncate; /* enables vacuum to truncate a relation */
/*
* Fraction of pages in a relation that vacuum can eagerly scan and fail
--
2.39.2
--nextPart5129932.5fSG56mABF
Content-Disposition: attachment;
filename="v2-0003-Add-alias-to-be-used-as-unset-state.patch"
Content-Transfer-Encoding: 7Bit
Content-Type: text/x-patch; charset="unicode-2-0-utf-8";
name="v2-0003-Add-alias-to-be-used-as-unset-state.patch"
^ permalink raw reply [nested|flat] 868+ messages in thread
* [PATCH v2a 2/4] Introduce ternary reloptions
@ 2025-09-04 14:41 Nikolay Shaplov <[email protected]>
0 siblings, 0 replies; 868+ messages in thread
From: Nikolay Shaplov @ 2025-09-04 14:41 UTC (permalink / raw)
Introduce ternary reloption as a replacement for current `vacuum_truncate`
implementation. Remove `vacuum_truncate_set` additional flag and using
`TERNARY_UNSET` value instead.
---
src/backend/access/common/reloptions.c | 129 ++++++++++++++++++++-----
src/backend/commands/vacuum.c | 4 +-
src/include/access/reloptions.h | 26 ++---
src/include/c.h | 16 +++
src/include/utils/rel.h | 3 +-
5 files changed, 135 insertions(+), 43 deletions(-)
diff --git a/src/backend/access/common/reloptions.c b/src/backend/access/common/reloptions.c
index 0af3fea68f..f543f11001 100644
--- a/src/backend/access/common/reloptions.c
+++ b/src/backend/access/common/reloptions.c
@@ -40,9 +40,9 @@
*
* To add an option:
*
- * (i) decide on a type (bool, integer, real, enum, string), name, default
- * value, upper and lower bounds (if applicable); for strings, consider a
- * validation routine.
+ * (i) decide on a type (bool, ternary, integer, real, enum, string), name,
+ * default value, upper and lower bounds (if applicable); for strings,
+ * consider a validation routine.
* (ii) add a record below (or use add_<type>_reloption).
* (iii) add it to the appropriate options struct (perhaps StdRdOptions)
* (iv) add it to the appropriate handling routine (perhaps
@@ -147,15 +147,6 @@ static relopt_bool boolRelOpts[] =
},
false
},
- {
- {
- "vacuum_truncate",
- "Enables vacuum to truncate empty pages at the end of this table",
- RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
- ShareUpdateExclusiveLock
- },
- true
- },
{
{
"deduplicate_items",
@@ -170,6 +161,21 @@ static relopt_bool boolRelOpts[] =
{{NULL}}
};
+static relopt_ternary ternaryRelOpts[] =
+{
+ {
+ {
+ "vacuum_truncate",
+ "Enables vacuum to truncate empty pages at the end of this table",
+ RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
+ ShareUpdateExclusiveLock
+ },
+ TERNARY_UNSET
+ },
+ /* list terminator */
+ {{NULL}}
+};
+
static relopt_int intRelOpts[] =
{
{
@@ -600,6 +606,13 @@ initialize_reloptions(void)
boolRelOpts[i].gen.lockmode));
j++;
}
+ for (i = 0; ternaryRelOpts[i].gen.name; i++)
+ {
+ Assert(DoLockModesConflict(ternaryRelOpts[i].gen.lockmode,
+ ternaryRelOpts[i].gen.lockmode));
+ j++;
+ }
+
for (i = 0; intRelOpts[i].gen.name; i++)
{
Assert(DoLockModesConflict(intRelOpts[i].gen.lockmode,
@@ -640,6 +653,14 @@ initialize_reloptions(void)
j++;
}
+ for (i = 0; ternaryRelOpts[i].gen.name; i++)
+ {
+ relOpts[j] = &ternaryRelOpts[i].gen;
+ relOpts[j]->type = RELOPT_TYPE_TERNARY;
+ relOpts[j]->namelen = strlen(relOpts[j]->name);
+ j++;
+ }
+
for (i = 0; intRelOpts[i].gen.name; i++)
{
relOpts[j] = &intRelOpts[i].gen;
@@ -800,6 +821,9 @@ allocate_reloption(bits32 kinds, int type, const char *name, const char *desc,
case RELOPT_TYPE_BOOL:
size = sizeof(relopt_bool);
break;
+ case RELOPT_TYPE_TERNARY:
+ size = sizeof(relopt_ternary);
+ break;
case RELOPT_TYPE_INT:
size = sizeof(relopt_int);
break;
@@ -883,6 +907,54 @@ add_local_bool_reloption(local_relopts *relopts, const char *name,
add_local_reloption(relopts, (relopt_gen *) newoption, offset);
}
+/*
+ * init_ternary_reloption
+ * Allocate and initialize a new ternary reloption
+ */
+static relopt_ternary *
+init_ternary_reloption(bits32 kinds, const char *name, const char *desc,
+ ternary default_val, LOCKMODE lockmode)
+{
+ relopt_ternary *newoption;
+
+ newoption = (relopt_ternary *) allocate_reloption(kinds,
+ RELOPT_TYPE_TERNARY, name, desc, lockmode);
+ newoption->default_val = default_val;
+
+ return newoption;
+}
+
+/*
+ * add_ternary_reloption
+ * Add a new ternary reloption
+ */
+void
+add_ternary_reloption(bits32 kinds, const char *name, const char *desc,
+ ternary default_val, LOCKMODE lockmode)
+{
+ relopt_ternary *newoption = init_ternary_reloption(kinds, name, desc,
+ default_val, lockmode);
+
+ add_reloption((relopt_gen *) newoption);
+}
+
+/*
+ * add_local_ternary_reloption
+ * Add a new ternary local reloption
+ *
+ * 'offset' is offset of ternary-typed field.
+ */
+void
+add_local_ternary_reloption(local_relopts *relopts, const char *name,
+ const char *desc, ternary default_val,
+ int offset)
+{
+ relopt_ternary *newoption = init_ternary_reloption(RELOPT_KIND_LOCAL,
+ name, desc,
+ default_val, 0);
+
+ add_local_reloption(relopts, (relopt_gen *) newoption, offset);
+}
/*
* init_real_reloption
@@ -1617,6 +1689,18 @@ parse_one_reloption(relopt_value *option, char *text_str, int text_len,
option->gen->name, value)));
}
break;
+ case RELOPT_TYPE_TERNARY:
+ {
+ bool b;
+ parsed = parse_bool(value, &b);
+ option->values.ternary_val = b ? TERNARY_TRUE : TERNARY_FALSE;
+ if (validate && !parsed)
+ ereport(ERROR,
+ (errcode(ERRCODE_INVALID_PARAMETER_VALUE),
+ errmsg("invalid value for ternary option \"%s\": %s",
+ option->gen->name, value)));
+ }
+ break;
case RELOPT_TYPE_INT:
{
relopt_int *optint = (relopt_int *) option->gen;
@@ -1780,17 +1864,6 @@ fillRelOptions(void *rdopts, Size basesize,
char *itempos = ((char *) rdopts) + elems[j].offset;
char *string_val;
- /*
- * If isset_offset is provided, store whether the reloption is
- * set there.
- */
- if (elems[j].isset_offset > 0)
- {
- char *setpos = ((char *) rdopts) + elems[j].isset_offset;
-
- *(bool *) setpos = options[i].isset;
- }
-
switch (options[i].gen->type)
{
case RELOPT_TYPE_BOOL:
@@ -1798,6 +1871,11 @@ fillRelOptions(void *rdopts, Size basesize,
options[i].values.bool_val :
((relopt_bool *) options[i].gen)->default_val;
break;
+ case RELOPT_TYPE_TERNARY:
+ *(ternary *) itempos = options[i].isset ?
+ options[i].values.ternary_val :
+ ((relopt_ternary *) options[i].gen)->default_val;
+ break;
case RELOPT_TYPE_INT:
*(int *) itempos = options[i].isset ?
options[i].values.int_val :
@@ -1912,8 +1990,8 @@ default_reloptions(Datum reloptions, bool validate, relopt_kind kind)
offsetof(StdRdOptions, parallel_workers)},
{"vacuum_index_cleanup", RELOPT_TYPE_ENUM,
offsetof(StdRdOptions, vacuum_index_cleanup)},
- {"vacuum_truncate", RELOPT_TYPE_BOOL,
- offsetof(StdRdOptions, vacuum_truncate), offsetof(StdRdOptions, vacuum_truncate_set)},
+ {"vacuum_truncate", RELOPT_TYPE_TERNARY,
+ offsetof(StdRdOptions, vacuum_truncate)},
{"vacuum_max_eager_freeze_failure_rate", RELOPT_TYPE_REAL,
offsetof(StdRdOptions, vacuum_max_eager_freeze_failure_rate)}
};
@@ -1993,7 +2071,6 @@ build_local_reloptions(local_relopts *relopts, Datum options, bool validate)
elems[i].optname = opt->option->name;
elems[i].opttype = opt->option->type;
elems[i].offset = opt->offset;
- elems[i].isset_offset = 0; /* not supported for local relopts yet */
i++;
}
diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c
index 733ef40ae7..977babff54 100644
--- a/src/backend/commands/vacuum.c
+++ b/src/backend/commands/vacuum.c
@@ -2221,9 +2221,9 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams params,
{
StdRdOptions *opts = (StdRdOptions *) rel->rd_options;
- if (opts && opts->vacuum_truncate_set)
+ if (opts && opts->vacuum_truncate != TERNARY_UNSET)
{
- if (opts->vacuum_truncate)
+ if (opts->vacuum_truncate == TERNARY_TRUE)
params.truncate = VACOPTVALUE_ENABLED;
else
params.truncate = VACOPTVALUE_DISABLED;
diff --git a/src/include/access/reloptions.h b/src/include/access/reloptions.h
index a604a4702c..a436697658 100644
--- a/src/include/access/reloptions.h
+++ b/src/include/access/reloptions.h
@@ -29,6 +29,7 @@
typedef enum relopt_type
{
RELOPT_TYPE_BOOL,
+ RELOPT_TYPE_TERNARY, /* on, off, unset */
RELOPT_TYPE_INT,
RELOPT_TYPE_REAL,
RELOPT_TYPE_ENUM,
@@ -80,6 +81,7 @@ typedef struct relopt_value
union
{
bool bool_val;
+ ternary ternary_val;
int int_val;
double real_val;
int enum_val;
@@ -94,6 +96,12 @@ typedef struct relopt_bool
bool default_val;
} relopt_bool;
+typedef struct relopt_rernary
+{
+ relopt_gen gen;
+ int default_val;
+} relopt_ternary;
+
typedef struct relopt_int
{
relopt_gen gen;
@@ -152,19 +160,6 @@ typedef struct
const char *optname; /* option's name */
relopt_type opttype; /* option's datatype */
int offset; /* offset of field in result struct */
-
- /*
- * isset_offset is an optional offset of a field in the result struct that
- * stores whether the option is explicitly set for the relation or if it
- * just picked up the default value. In most cases, this can be
- * accomplished by giving the reloption a special out-of-range default
- * value (e.g., some integer reloptions use -2), but this isn't always
- * possible. For example, a Boolean reloption cannot be given an
- * out-of-range default, so we need another way to discover the source of
- * its value. This offset is only used if given a value greater than
- * zero.
- */
- int isset_offset;
} relopt_parse_elt;
/* Local reloption definition */
@@ -195,6 +190,8 @@ typedef struct local_relopts
extern relopt_kind add_reloption_kind(void);
extern void add_bool_reloption(bits32 kinds, const char *name, const char *desc,
bool default_val, LOCKMODE lockmode);
+extern void add_ternary_reloption(bits32 kinds, const char *name,
+ const char *desc, int default_val, LOCKMODE lockmode);
extern void add_int_reloption(bits32 kinds, const char *name, const char *desc,
int default_val, int min_val, int max_val,
LOCKMODE lockmode);
@@ -214,6 +211,9 @@ extern void register_reloptions_validator(local_relopts *relopts,
extern void add_local_bool_reloption(local_relopts *relopts, const char *name,
const char *desc, bool default_val,
int offset);
+extern void add_local_ternary_reloption(local_relopts *relopts,
+ const char *name, const char *desc,
+ ternary default_val, int offset);
extern void add_local_int_reloption(local_relopts *relopts, const char *name,
const char *desc, int default_val,
int min_val, int max_val, int offset);
diff --git a/src/include/c.h b/src/include/c.h
index 39022f8a9d..9c59ccccf8 100644
--- a/src/include/c.h
+++ b/src/include/c.h
@@ -488,6 +488,22 @@ typedef void (*pg_funcptr_t) (void);
#include <stdbool.h>
+/*
+ * ternary
+ * Boolean value with an extrea "unset" option
+ *
+ * Ternary data type is used in relation options that can be "true", "false" or
+ * "unset". Since relation options are used deep inside the PostgreSQL code,
+ * this type is declared globally.
+*/
+
+typedef enum ternary
+{
+ TERNARY_FALSE = 0,
+ TERNARY_TRUE = 1,
+ TERNARY_UNSET = -1
+} ternary;
+
/* ----------------------------------------------------------------
* Section 3: standard system types
diff --git a/src/include/utils/rel.h b/src/include/utils/rel.h
index b552359915..7346d618f9 100644
--- a/src/include/utils/rel.h
+++ b/src/include/utils/rel.h
@@ -346,8 +346,7 @@ typedef struct StdRdOptions
bool user_catalog_table; /* use as an additional catalog relation */
int parallel_workers; /* max number of parallel workers */
StdRdOptIndexCleanup vacuum_index_cleanup; /* controls index vacuuming */
- bool vacuum_truncate; /* enables vacuum to truncate a relation */
- bool vacuum_truncate_set; /* whether vacuum_truncate is set */
+ ternary vacuum_truncate; /* enables vacuum to truncate a relation */
/*
* Fraction of pages in a relation that vacuum can eagerly scan and fail
--
2.39.2
--nextPart4155600.3ZeAukHxDK
Content-Disposition: attachment;
filename="v2a-0003-Add-alias-to-be-used-as-unset-state.patch"
Content-Transfer-Encoding: 7Bit
Content-Type: text/x-patch; charset="unicode-2-0-utf-8";
name="v2a-0003-Add-alias-to-be-used-as-unset-state.patch"
^ permalink raw reply [nested|flat] 868+ messages in thread
* [PATCH v2a 2/4] Introduce ternary reloptions
@ 2025-09-04 14:41 Nikolay Shaplov <[email protected]>
0 siblings, 0 replies; 868+ messages in thread
From: Nikolay Shaplov @ 2025-09-04 14:41 UTC (permalink / raw)
Introduce ternary reloption as a replacement for current `vacuum_truncate`
implementation. Remove `vacuum_truncate_set` additional flag and using
`TERNARY_UNSET` value instead.
---
src/backend/access/common/reloptions.c | 129 ++++++++++++++++++++-----
src/backend/commands/vacuum.c | 4 +-
src/include/access/reloptions.h | 26 ++---
src/include/c.h | 16 +++
src/include/utils/rel.h | 3 +-
5 files changed, 135 insertions(+), 43 deletions(-)
diff --git a/src/backend/access/common/reloptions.c b/src/backend/access/common/reloptions.c
index 0af3fea68f..f543f11001 100644
--- a/src/backend/access/common/reloptions.c
+++ b/src/backend/access/common/reloptions.c
@@ -40,9 +40,9 @@
*
* To add an option:
*
- * (i) decide on a type (bool, integer, real, enum, string), name, default
- * value, upper and lower bounds (if applicable); for strings, consider a
- * validation routine.
+ * (i) decide on a type (bool, ternary, integer, real, enum, string), name,
+ * default value, upper and lower bounds (if applicable); for strings,
+ * consider a validation routine.
* (ii) add a record below (or use add_<type>_reloption).
* (iii) add it to the appropriate options struct (perhaps StdRdOptions)
* (iv) add it to the appropriate handling routine (perhaps
@@ -147,15 +147,6 @@ static relopt_bool boolRelOpts[] =
},
false
},
- {
- {
- "vacuum_truncate",
- "Enables vacuum to truncate empty pages at the end of this table",
- RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
- ShareUpdateExclusiveLock
- },
- true
- },
{
{
"deduplicate_items",
@@ -170,6 +161,21 @@ static relopt_bool boolRelOpts[] =
{{NULL}}
};
+static relopt_ternary ternaryRelOpts[] =
+{
+ {
+ {
+ "vacuum_truncate",
+ "Enables vacuum to truncate empty pages at the end of this table",
+ RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
+ ShareUpdateExclusiveLock
+ },
+ TERNARY_UNSET
+ },
+ /* list terminator */
+ {{NULL}}
+};
+
static relopt_int intRelOpts[] =
{
{
@@ -600,6 +606,13 @@ initialize_reloptions(void)
boolRelOpts[i].gen.lockmode));
j++;
}
+ for (i = 0; ternaryRelOpts[i].gen.name; i++)
+ {
+ Assert(DoLockModesConflict(ternaryRelOpts[i].gen.lockmode,
+ ternaryRelOpts[i].gen.lockmode));
+ j++;
+ }
+
for (i = 0; intRelOpts[i].gen.name; i++)
{
Assert(DoLockModesConflict(intRelOpts[i].gen.lockmode,
@@ -640,6 +653,14 @@ initialize_reloptions(void)
j++;
}
+ for (i = 0; ternaryRelOpts[i].gen.name; i++)
+ {
+ relOpts[j] = &ternaryRelOpts[i].gen;
+ relOpts[j]->type = RELOPT_TYPE_TERNARY;
+ relOpts[j]->namelen = strlen(relOpts[j]->name);
+ j++;
+ }
+
for (i = 0; intRelOpts[i].gen.name; i++)
{
relOpts[j] = &intRelOpts[i].gen;
@@ -800,6 +821,9 @@ allocate_reloption(bits32 kinds, int type, const char *name, const char *desc,
case RELOPT_TYPE_BOOL:
size = sizeof(relopt_bool);
break;
+ case RELOPT_TYPE_TERNARY:
+ size = sizeof(relopt_ternary);
+ break;
case RELOPT_TYPE_INT:
size = sizeof(relopt_int);
break;
@@ -883,6 +907,54 @@ add_local_bool_reloption(local_relopts *relopts, const char *name,
add_local_reloption(relopts, (relopt_gen *) newoption, offset);
}
+/*
+ * init_ternary_reloption
+ * Allocate and initialize a new ternary reloption
+ */
+static relopt_ternary *
+init_ternary_reloption(bits32 kinds, const char *name, const char *desc,
+ ternary default_val, LOCKMODE lockmode)
+{
+ relopt_ternary *newoption;
+
+ newoption = (relopt_ternary *) allocate_reloption(kinds,
+ RELOPT_TYPE_TERNARY, name, desc, lockmode);
+ newoption->default_val = default_val;
+
+ return newoption;
+}
+
+/*
+ * add_ternary_reloption
+ * Add a new ternary reloption
+ */
+void
+add_ternary_reloption(bits32 kinds, const char *name, const char *desc,
+ ternary default_val, LOCKMODE lockmode)
+{
+ relopt_ternary *newoption = init_ternary_reloption(kinds, name, desc,
+ default_val, lockmode);
+
+ add_reloption((relopt_gen *) newoption);
+}
+
+/*
+ * add_local_ternary_reloption
+ * Add a new ternary local reloption
+ *
+ * 'offset' is offset of ternary-typed field.
+ */
+void
+add_local_ternary_reloption(local_relopts *relopts, const char *name,
+ const char *desc, ternary default_val,
+ int offset)
+{
+ relopt_ternary *newoption = init_ternary_reloption(RELOPT_KIND_LOCAL,
+ name, desc,
+ default_val, 0);
+
+ add_local_reloption(relopts, (relopt_gen *) newoption, offset);
+}
/*
* init_real_reloption
@@ -1617,6 +1689,18 @@ parse_one_reloption(relopt_value *option, char *text_str, int text_len,
option->gen->name, value)));
}
break;
+ case RELOPT_TYPE_TERNARY:
+ {
+ bool b;
+ parsed = parse_bool(value, &b);
+ option->values.ternary_val = b ? TERNARY_TRUE : TERNARY_FALSE;
+ if (validate && !parsed)
+ ereport(ERROR,
+ (errcode(ERRCODE_INVALID_PARAMETER_VALUE),
+ errmsg("invalid value for ternary option \"%s\": %s",
+ option->gen->name, value)));
+ }
+ break;
case RELOPT_TYPE_INT:
{
relopt_int *optint = (relopt_int *) option->gen;
@@ -1780,17 +1864,6 @@ fillRelOptions(void *rdopts, Size basesize,
char *itempos = ((char *) rdopts) + elems[j].offset;
char *string_val;
- /*
- * If isset_offset is provided, store whether the reloption is
- * set there.
- */
- if (elems[j].isset_offset > 0)
- {
- char *setpos = ((char *) rdopts) + elems[j].isset_offset;
-
- *(bool *) setpos = options[i].isset;
- }
-
switch (options[i].gen->type)
{
case RELOPT_TYPE_BOOL:
@@ -1798,6 +1871,11 @@ fillRelOptions(void *rdopts, Size basesize,
options[i].values.bool_val :
((relopt_bool *) options[i].gen)->default_val;
break;
+ case RELOPT_TYPE_TERNARY:
+ *(ternary *) itempos = options[i].isset ?
+ options[i].values.ternary_val :
+ ((relopt_ternary *) options[i].gen)->default_val;
+ break;
case RELOPT_TYPE_INT:
*(int *) itempos = options[i].isset ?
options[i].values.int_val :
@@ -1912,8 +1990,8 @@ default_reloptions(Datum reloptions, bool validate, relopt_kind kind)
offsetof(StdRdOptions, parallel_workers)},
{"vacuum_index_cleanup", RELOPT_TYPE_ENUM,
offsetof(StdRdOptions, vacuum_index_cleanup)},
- {"vacuum_truncate", RELOPT_TYPE_BOOL,
- offsetof(StdRdOptions, vacuum_truncate), offsetof(StdRdOptions, vacuum_truncate_set)},
+ {"vacuum_truncate", RELOPT_TYPE_TERNARY,
+ offsetof(StdRdOptions, vacuum_truncate)},
{"vacuum_max_eager_freeze_failure_rate", RELOPT_TYPE_REAL,
offsetof(StdRdOptions, vacuum_max_eager_freeze_failure_rate)}
};
@@ -1993,7 +2071,6 @@ build_local_reloptions(local_relopts *relopts, Datum options, bool validate)
elems[i].optname = opt->option->name;
elems[i].opttype = opt->option->type;
elems[i].offset = opt->offset;
- elems[i].isset_offset = 0; /* not supported for local relopts yet */
i++;
}
diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c
index 733ef40ae7..977babff54 100644
--- a/src/backend/commands/vacuum.c
+++ b/src/backend/commands/vacuum.c
@@ -2221,9 +2221,9 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams params,
{
StdRdOptions *opts = (StdRdOptions *) rel->rd_options;
- if (opts && opts->vacuum_truncate_set)
+ if (opts && opts->vacuum_truncate != TERNARY_UNSET)
{
- if (opts->vacuum_truncate)
+ if (opts->vacuum_truncate == TERNARY_TRUE)
params.truncate = VACOPTVALUE_ENABLED;
else
params.truncate = VACOPTVALUE_DISABLED;
diff --git a/src/include/access/reloptions.h b/src/include/access/reloptions.h
index a604a4702c..a436697658 100644
--- a/src/include/access/reloptions.h
+++ b/src/include/access/reloptions.h
@@ -29,6 +29,7 @@
typedef enum relopt_type
{
RELOPT_TYPE_BOOL,
+ RELOPT_TYPE_TERNARY, /* on, off, unset */
RELOPT_TYPE_INT,
RELOPT_TYPE_REAL,
RELOPT_TYPE_ENUM,
@@ -80,6 +81,7 @@ typedef struct relopt_value
union
{
bool bool_val;
+ ternary ternary_val;
int int_val;
double real_val;
int enum_val;
@@ -94,6 +96,12 @@ typedef struct relopt_bool
bool default_val;
} relopt_bool;
+typedef struct relopt_rernary
+{
+ relopt_gen gen;
+ int default_val;
+} relopt_ternary;
+
typedef struct relopt_int
{
relopt_gen gen;
@@ -152,19 +160,6 @@ typedef struct
const char *optname; /* option's name */
relopt_type opttype; /* option's datatype */
int offset; /* offset of field in result struct */
-
- /*
- * isset_offset is an optional offset of a field in the result struct that
- * stores whether the option is explicitly set for the relation or if it
- * just picked up the default value. In most cases, this can be
- * accomplished by giving the reloption a special out-of-range default
- * value (e.g., some integer reloptions use -2), but this isn't always
- * possible. For example, a Boolean reloption cannot be given an
- * out-of-range default, so we need another way to discover the source of
- * its value. This offset is only used if given a value greater than
- * zero.
- */
- int isset_offset;
} relopt_parse_elt;
/* Local reloption definition */
@@ -195,6 +190,8 @@ typedef struct local_relopts
extern relopt_kind add_reloption_kind(void);
extern void add_bool_reloption(bits32 kinds, const char *name, const char *desc,
bool default_val, LOCKMODE lockmode);
+extern void add_ternary_reloption(bits32 kinds, const char *name,
+ const char *desc, int default_val, LOCKMODE lockmode);
extern void add_int_reloption(bits32 kinds, const char *name, const char *desc,
int default_val, int min_val, int max_val,
LOCKMODE lockmode);
@@ -214,6 +211,9 @@ extern void register_reloptions_validator(local_relopts *relopts,
extern void add_local_bool_reloption(local_relopts *relopts, const char *name,
const char *desc, bool default_val,
int offset);
+extern void add_local_ternary_reloption(local_relopts *relopts,
+ const char *name, const char *desc,
+ ternary default_val, int offset);
extern void add_local_int_reloption(local_relopts *relopts, const char *name,
const char *desc, int default_val,
int min_val, int max_val, int offset);
diff --git a/src/include/c.h b/src/include/c.h
index 39022f8a9d..9c59ccccf8 100644
--- a/src/include/c.h
+++ b/src/include/c.h
@@ -488,6 +488,22 @@ typedef void (*pg_funcptr_t) (void);
#include <stdbool.h>
+/*
+ * ternary
+ * Boolean value with an extrea "unset" option
+ *
+ * Ternary data type is used in relation options that can be "true", "false" or
+ * "unset". Since relation options are used deep inside the PostgreSQL code,
+ * this type is declared globally.
+*/
+
+typedef enum ternary
+{
+ TERNARY_FALSE = 0,
+ TERNARY_TRUE = 1,
+ TERNARY_UNSET = -1
+} ternary;
+
/* ----------------------------------------------------------------
* Section 3: standard system types
diff --git a/src/include/utils/rel.h b/src/include/utils/rel.h
index b552359915..7346d618f9 100644
--- a/src/include/utils/rel.h
+++ b/src/include/utils/rel.h
@@ -346,8 +346,7 @@ typedef struct StdRdOptions
bool user_catalog_table; /* use as an additional catalog relation */
int parallel_workers; /* max number of parallel workers */
StdRdOptIndexCleanup vacuum_index_cleanup; /* controls index vacuuming */
- bool vacuum_truncate; /* enables vacuum to truncate a relation */
- bool vacuum_truncate_set; /* whether vacuum_truncate is set */
+ ternary vacuum_truncate; /* enables vacuum to truncate a relation */
/*
* Fraction of pages in a relation that vacuum can eagerly scan and fail
--
2.39.2
--nextPart4155600.3ZeAukHxDK
Content-Disposition: attachment;
filename="v2a-0003-Add-alias-to-be-used-as-unset-state.patch"
Content-Transfer-Encoding: 7Bit
Content-Type: text/x-patch; charset="unicode-2-0-utf-8";
name="v2a-0003-Add-alias-to-be-used-as-unset-state.patch"
^ permalink raw reply [nested|flat] 868+ messages in thread
* [PATCH v2a 2/4] Introduce ternary reloptions
@ 2025-09-04 14:41 Nikolay Shaplov <[email protected]>
0 siblings, 0 replies; 868+ messages in thread
From: Nikolay Shaplov @ 2025-09-04 14:41 UTC (permalink / raw)
Introduce ternary reloption as a replacement for current `vacuum_truncate`
implementation. Remove `vacuum_truncate_set` additional flag and using
`TERNARY_UNSET` value instead.
---
src/backend/access/common/reloptions.c | 129 ++++++++++++++++++++-----
src/backend/commands/vacuum.c | 4 +-
src/include/access/reloptions.h | 26 ++---
src/include/c.h | 16 +++
src/include/utils/rel.h | 3 +-
5 files changed, 135 insertions(+), 43 deletions(-)
diff --git a/src/backend/access/common/reloptions.c b/src/backend/access/common/reloptions.c
index 0af3fea68f..f543f11001 100644
--- a/src/backend/access/common/reloptions.c
+++ b/src/backend/access/common/reloptions.c
@@ -40,9 +40,9 @@
*
* To add an option:
*
- * (i) decide on a type (bool, integer, real, enum, string), name, default
- * value, upper and lower bounds (if applicable); for strings, consider a
- * validation routine.
+ * (i) decide on a type (bool, ternary, integer, real, enum, string), name,
+ * default value, upper and lower bounds (if applicable); for strings,
+ * consider a validation routine.
* (ii) add a record below (or use add_<type>_reloption).
* (iii) add it to the appropriate options struct (perhaps StdRdOptions)
* (iv) add it to the appropriate handling routine (perhaps
@@ -147,15 +147,6 @@ static relopt_bool boolRelOpts[] =
},
false
},
- {
- {
- "vacuum_truncate",
- "Enables vacuum to truncate empty pages at the end of this table",
- RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
- ShareUpdateExclusiveLock
- },
- true
- },
{
{
"deduplicate_items",
@@ -170,6 +161,21 @@ static relopt_bool boolRelOpts[] =
{{NULL}}
};
+static relopt_ternary ternaryRelOpts[] =
+{
+ {
+ {
+ "vacuum_truncate",
+ "Enables vacuum to truncate empty pages at the end of this table",
+ RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
+ ShareUpdateExclusiveLock
+ },
+ TERNARY_UNSET
+ },
+ /* list terminator */
+ {{NULL}}
+};
+
static relopt_int intRelOpts[] =
{
{
@@ -600,6 +606,13 @@ initialize_reloptions(void)
boolRelOpts[i].gen.lockmode));
j++;
}
+ for (i = 0; ternaryRelOpts[i].gen.name; i++)
+ {
+ Assert(DoLockModesConflict(ternaryRelOpts[i].gen.lockmode,
+ ternaryRelOpts[i].gen.lockmode));
+ j++;
+ }
+
for (i = 0; intRelOpts[i].gen.name; i++)
{
Assert(DoLockModesConflict(intRelOpts[i].gen.lockmode,
@@ -640,6 +653,14 @@ initialize_reloptions(void)
j++;
}
+ for (i = 0; ternaryRelOpts[i].gen.name; i++)
+ {
+ relOpts[j] = &ternaryRelOpts[i].gen;
+ relOpts[j]->type = RELOPT_TYPE_TERNARY;
+ relOpts[j]->namelen = strlen(relOpts[j]->name);
+ j++;
+ }
+
for (i = 0; intRelOpts[i].gen.name; i++)
{
relOpts[j] = &intRelOpts[i].gen;
@@ -800,6 +821,9 @@ allocate_reloption(bits32 kinds, int type, const char *name, const char *desc,
case RELOPT_TYPE_BOOL:
size = sizeof(relopt_bool);
break;
+ case RELOPT_TYPE_TERNARY:
+ size = sizeof(relopt_ternary);
+ break;
case RELOPT_TYPE_INT:
size = sizeof(relopt_int);
break;
@@ -883,6 +907,54 @@ add_local_bool_reloption(local_relopts *relopts, const char *name,
add_local_reloption(relopts, (relopt_gen *) newoption, offset);
}
+/*
+ * init_ternary_reloption
+ * Allocate and initialize a new ternary reloption
+ */
+static relopt_ternary *
+init_ternary_reloption(bits32 kinds, const char *name, const char *desc,
+ ternary default_val, LOCKMODE lockmode)
+{
+ relopt_ternary *newoption;
+
+ newoption = (relopt_ternary *) allocate_reloption(kinds,
+ RELOPT_TYPE_TERNARY, name, desc, lockmode);
+ newoption->default_val = default_val;
+
+ return newoption;
+}
+
+/*
+ * add_ternary_reloption
+ * Add a new ternary reloption
+ */
+void
+add_ternary_reloption(bits32 kinds, const char *name, const char *desc,
+ ternary default_val, LOCKMODE lockmode)
+{
+ relopt_ternary *newoption = init_ternary_reloption(kinds, name, desc,
+ default_val, lockmode);
+
+ add_reloption((relopt_gen *) newoption);
+}
+
+/*
+ * add_local_ternary_reloption
+ * Add a new ternary local reloption
+ *
+ * 'offset' is offset of ternary-typed field.
+ */
+void
+add_local_ternary_reloption(local_relopts *relopts, const char *name,
+ const char *desc, ternary default_val,
+ int offset)
+{
+ relopt_ternary *newoption = init_ternary_reloption(RELOPT_KIND_LOCAL,
+ name, desc,
+ default_val, 0);
+
+ add_local_reloption(relopts, (relopt_gen *) newoption, offset);
+}
/*
* init_real_reloption
@@ -1617,6 +1689,18 @@ parse_one_reloption(relopt_value *option, char *text_str, int text_len,
option->gen->name, value)));
}
break;
+ case RELOPT_TYPE_TERNARY:
+ {
+ bool b;
+ parsed = parse_bool(value, &b);
+ option->values.ternary_val = b ? TERNARY_TRUE : TERNARY_FALSE;
+ if (validate && !parsed)
+ ereport(ERROR,
+ (errcode(ERRCODE_INVALID_PARAMETER_VALUE),
+ errmsg("invalid value for ternary option \"%s\": %s",
+ option->gen->name, value)));
+ }
+ break;
case RELOPT_TYPE_INT:
{
relopt_int *optint = (relopt_int *) option->gen;
@@ -1780,17 +1864,6 @@ fillRelOptions(void *rdopts, Size basesize,
char *itempos = ((char *) rdopts) + elems[j].offset;
char *string_val;
- /*
- * If isset_offset is provided, store whether the reloption is
- * set there.
- */
- if (elems[j].isset_offset > 0)
- {
- char *setpos = ((char *) rdopts) + elems[j].isset_offset;
-
- *(bool *) setpos = options[i].isset;
- }
-
switch (options[i].gen->type)
{
case RELOPT_TYPE_BOOL:
@@ -1798,6 +1871,11 @@ fillRelOptions(void *rdopts, Size basesize,
options[i].values.bool_val :
((relopt_bool *) options[i].gen)->default_val;
break;
+ case RELOPT_TYPE_TERNARY:
+ *(ternary *) itempos = options[i].isset ?
+ options[i].values.ternary_val :
+ ((relopt_ternary *) options[i].gen)->default_val;
+ break;
case RELOPT_TYPE_INT:
*(int *) itempos = options[i].isset ?
options[i].values.int_val :
@@ -1912,8 +1990,8 @@ default_reloptions(Datum reloptions, bool validate, relopt_kind kind)
offsetof(StdRdOptions, parallel_workers)},
{"vacuum_index_cleanup", RELOPT_TYPE_ENUM,
offsetof(StdRdOptions, vacuum_index_cleanup)},
- {"vacuum_truncate", RELOPT_TYPE_BOOL,
- offsetof(StdRdOptions, vacuum_truncate), offsetof(StdRdOptions, vacuum_truncate_set)},
+ {"vacuum_truncate", RELOPT_TYPE_TERNARY,
+ offsetof(StdRdOptions, vacuum_truncate)},
{"vacuum_max_eager_freeze_failure_rate", RELOPT_TYPE_REAL,
offsetof(StdRdOptions, vacuum_max_eager_freeze_failure_rate)}
};
@@ -1993,7 +2071,6 @@ build_local_reloptions(local_relopts *relopts, Datum options, bool validate)
elems[i].optname = opt->option->name;
elems[i].opttype = opt->option->type;
elems[i].offset = opt->offset;
- elems[i].isset_offset = 0; /* not supported for local relopts yet */
i++;
}
diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c
index 733ef40ae7..977babff54 100644
--- a/src/backend/commands/vacuum.c
+++ b/src/backend/commands/vacuum.c
@@ -2221,9 +2221,9 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams params,
{
StdRdOptions *opts = (StdRdOptions *) rel->rd_options;
- if (opts && opts->vacuum_truncate_set)
+ if (opts && opts->vacuum_truncate != TERNARY_UNSET)
{
- if (opts->vacuum_truncate)
+ if (opts->vacuum_truncate == TERNARY_TRUE)
params.truncate = VACOPTVALUE_ENABLED;
else
params.truncate = VACOPTVALUE_DISABLED;
diff --git a/src/include/access/reloptions.h b/src/include/access/reloptions.h
index a604a4702c..a436697658 100644
--- a/src/include/access/reloptions.h
+++ b/src/include/access/reloptions.h
@@ -29,6 +29,7 @@
typedef enum relopt_type
{
RELOPT_TYPE_BOOL,
+ RELOPT_TYPE_TERNARY, /* on, off, unset */
RELOPT_TYPE_INT,
RELOPT_TYPE_REAL,
RELOPT_TYPE_ENUM,
@@ -80,6 +81,7 @@ typedef struct relopt_value
union
{
bool bool_val;
+ ternary ternary_val;
int int_val;
double real_val;
int enum_val;
@@ -94,6 +96,12 @@ typedef struct relopt_bool
bool default_val;
} relopt_bool;
+typedef struct relopt_rernary
+{
+ relopt_gen gen;
+ int default_val;
+} relopt_ternary;
+
typedef struct relopt_int
{
relopt_gen gen;
@@ -152,19 +160,6 @@ typedef struct
const char *optname; /* option's name */
relopt_type opttype; /* option's datatype */
int offset; /* offset of field in result struct */
-
- /*
- * isset_offset is an optional offset of a field in the result struct that
- * stores whether the option is explicitly set for the relation or if it
- * just picked up the default value. In most cases, this can be
- * accomplished by giving the reloption a special out-of-range default
- * value (e.g., some integer reloptions use -2), but this isn't always
- * possible. For example, a Boolean reloption cannot be given an
- * out-of-range default, so we need another way to discover the source of
- * its value. This offset is only used if given a value greater than
- * zero.
- */
- int isset_offset;
} relopt_parse_elt;
/* Local reloption definition */
@@ -195,6 +190,8 @@ typedef struct local_relopts
extern relopt_kind add_reloption_kind(void);
extern void add_bool_reloption(bits32 kinds, const char *name, const char *desc,
bool default_val, LOCKMODE lockmode);
+extern void add_ternary_reloption(bits32 kinds, const char *name,
+ const char *desc, int default_val, LOCKMODE lockmode);
extern void add_int_reloption(bits32 kinds, const char *name, const char *desc,
int default_val, int min_val, int max_val,
LOCKMODE lockmode);
@@ -214,6 +211,9 @@ extern void register_reloptions_validator(local_relopts *relopts,
extern void add_local_bool_reloption(local_relopts *relopts, const char *name,
const char *desc, bool default_val,
int offset);
+extern void add_local_ternary_reloption(local_relopts *relopts,
+ const char *name, const char *desc,
+ ternary default_val, int offset);
extern void add_local_int_reloption(local_relopts *relopts, const char *name,
const char *desc, int default_val,
int min_val, int max_val, int offset);
diff --git a/src/include/c.h b/src/include/c.h
index 39022f8a9d..9c59ccccf8 100644
--- a/src/include/c.h
+++ b/src/include/c.h
@@ -488,6 +488,22 @@ typedef void (*pg_funcptr_t) (void);
#include <stdbool.h>
+/*
+ * ternary
+ * Boolean value with an extrea "unset" option
+ *
+ * Ternary data type is used in relation options that can be "true", "false" or
+ * "unset". Since relation options are used deep inside the PostgreSQL code,
+ * this type is declared globally.
+*/
+
+typedef enum ternary
+{
+ TERNARY_FALSE = 0,
+ TERNARY_TRUE = 1,
+ TERNARY_UNSET = -1
+} ternary;
+
/* ----------------------------------------------------------------
* Section 3: standard system types
diff --git a/src/include/utils/rel.h b/src/include/utils/rel.h
index b552359915..7346d618f9 100644
--- a/src/include/utils/rel.h
+++ b/src/include/utils/rel.h
@@ -346,8 +346,7 @@ typedef struct StdRdOptions
bool user_catalog_table; /* use as an additional catalog relation */
int parallel_workers; /* max number of parallel workers */
StdRdOptIndexCleanup vacuum_index_cleanup; /* controls index vacuuming */
- bool vacuum_truncate; /* enables vacuum to truncate a relation */
- bool vacuum_truncate_set; /* whether vacuum_truncate is set */
+ ternary vacuum_truncate; /* enables vacuum to truncate a relation */
/*
* Fraction of pages in a relation that vacuum can eagerly scan and fail
--
2.39.2
--nextPart4155600.3ZeAukHxDK
Content-Disposition: attachment;
filename="v2a-0003-Add-alias-to-be-used-as-unset-state.patch"
Content-Transfer-Encoding: 7Bit
Content-Type: text/x-patch; charset="unicode-2-0-utf-8";
name="v2a-0003-Add-alias-to-be-used-as-unset-state.patch"
^ permalink raw reply [nested|flat] 868+ messages in thread
* [PATCH v2 2/4] Introduce ternary reloptions
@ 2025-09-04 14:41 Nikolay Shaplov <[email protected]>
0 siblings, 0 replies; 868+ messages in thread
From: Nikolay Shaplov @ 2025-09-04 14:41 UTC (permalink / raw)
Introduce ternary reloption as a replacement for current `vacuum_truncate`
implementation. Remove `vacuum_truncate_set` additional flag and using
`TERNARY_UNSET` value instead.
---
src/backend/access/common/reloptions.c | 129 ++++++++++++++++++++-----
src/backend/commands/vacuum.c | 4 +-
src/include/access/reloptions.h | 26 ++---
src/include/c.h | 16 +++
src/include/utils/rel.h | 3 +-
5 files changed, 135 insertions(+), 43 deletions(-)
diff --git a/src/backend/access/common/reloptions.c b/src/backend/access/common/reloptions.c
index 0af3fea68f..f543f11001 100644
--- a/src/backend/access/common/reloptions.c
+++ b/src/backend/access/common/reloptions.c
@@ -40,9 +40,9 @@
*
* To add an option:
*
- * (i) decide on a type (bool, integer, real, enum, string), name, default
- * value, upper and lower bounds (if applicable); for strings, consider a
- * validation routine.
+ * (i) decide on a type (bool, ternary, integer, real, enum, string), name,
+ * default value, upper and lower bounds (if applicable); for strings,
+ * consider a validation routine.
* (ii) add a record below (or use add_<type>_reloption).
* (iii) add it to the appropriate options struct (perhaps StdRdOptions)
* (iv) add it to the appropriate handling routine (perhaps
@@ -147,15 +147,6 @@ static relopt_bool boolRelOpts[] =
},
false
},
- {
- {
- "vacuum_truncate",
- "Enables vacuum to truncate empty pages at the end of this table",
- RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
- ShareUpdateExclusiveLock
- },
- true
- },
{
{
"deduplicate_items",
@@ -170,6 +161,21 @@ static relopt_bool boolRelOpts[] =
{{NULL}}
};
+static relopt_ternary ternaryRelOpts[] =
+{
+ {
+ {
+ "vacuum_truncate",
+ "Enables vacuum to truncate empty pages at the end of this table",
+ RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
+ ShareUpdateExclusiveLock
+ },
+ TERNARY_UNSET
+ },
+ /* list terminator */
+ {{NULL}}
+};
+
static relopt_int intRelOpts[] =
{
{
@@ -600,6 +606,13 @@ initialize_reloptions(void)
boolRelOpts[i].gen.lockmode));
j++;
}
+ for (i = 0; ternaryRelOpts[i].gen.name; i++)
+ {
+ Assert(DoLockModesConflict(ternaryRelOpts[i].gen.lockmode,
+ ternaryRelOpts[i].gen.lockmode));
+ j++;
+ }
+
for (i = 0; intRelOpts[i].gen.name; i++)
{
Assert(DoLockModesConflict(intRelOpts[i].gen.lockmode,
@@ -640,6 +653,14 @@ initialize_reloptions(void)
j++;
}
+ for (i = 0; ternaryRelOpts[i].gen.name; i++)
+ {
+ relOpts[j] = &ternaryRelOpts[i].gen;
+ relOpts[j]->type = RELOPT_TYPE_TERNARY;
+ relOpts[j]->namelen = strlen(relOpts[j]->name);
+ j++;
+ }
+
for (i = 0; intRelOpts[i].gen.name; i++)
{
relOpts[j] = &intRelOpts[i].gen;
@@ -800,6 +821,9 @@ allocate_reloption(bits32 kinds, int type, const char *name, const char *desc,
case RELOPT_TYPE_BOOL:
size = sizeof(relopt_bool);
break;
+ case RELOPT_TYPE_TERNARY:
+ size = sizeof(relopt_ternary);
+ break;
case RELOPT_TYPE_INT:
size = sizeof(relopt_int);
break;
@@ -883,6 +907,54 @@ add_local_bool_reloption(local_relopts *relopts, const char *name,
add_local_reloption(relopts, (relopt_gen *) newoption, offset);
}
+/*
+ * init_ternary_reloption
+ * Allocate and initialize a new ternary reloption
+ */
+static relopt_ternary *
+init_ternary_reloption(bits32 kinds, const char *name, const char *desc,
+ ternary default_val, LOCKMODE lockmode)
+{
+ relopt_ternary *newoption;
+
+ newoption = (relopt_ternary *) allocate_reloption(kinds,
+ RELOPT_TYPE_TERNARY, name, desc, lockmode);
+ newoption->default_val = default_val;
+
+ return newoption;
+}
+
+/*
+ * add_ternary_reloption
+ * Add a new ternary reloption
+ */
+void
+add_ternary_reloption(bits32 kinds, const char *name, const char *desc,
+ ternary default_val, LOCKMODE lockmode)
+{
+ relopt_ternary *newoption = init_ternary_reloption(kinds, name, desc,
+ default_val, lockmode);
+
+ add_reloption((relopt_gen *) newoption);
+}
+
+/*
+ * add_local_ternary_reloption
+ * Add a new ternary local reloption
+ *
+ * 'offset' is offset of ternary-typed field.
+ */
+void
+add_local_ternary_reloption(local_relopts *relopts, const char *name,
+ const char *desc, ternary default_val,
+ int offset)
+{
+ relopt_ternary *newoption = init_ternary_reloption(RELOPT_KIND_LOCAL,
+ name, desc,
+ default_val, 0);
+
+ add_local_reloption(relopts, (relopt_gen *) newoption, offset);
+}
/*
* init_real_reloption
@@ -1617,6 +1689,18 @@ parse_one_reloption(relopt_value *option, char *text_str, int text_len,
option->gen->name, value)));
}
break;
+ case RELOPT_TYPE_TERNARY:
+ {
+ bool b;
+ parsed = parse_bool(value, &b);
+ option->values.ternary_val = b ? TERNARY_TRUE : TERNARY_FALSE;
+ if (validate && !parsed)
+ ereport(ERROR,
+ (errcode(ERRCODE_INVALID_PARAMETER_VALUE),
+ errmsg("invalid value for ternary option \"%s\": %s",
+ option->gen->name, value)));
+ }
+ break;
case RELOPT_TYPE_INT:
{
relopt_int *optint = (relopt_int *) option->gen;
@@ -1780,17 +1864,6 @@ fillRelOptions(void *rdopts, Size basesize,
char *itempos = ((char *) rdopts) + elems[j].offset;
char *string_val;
- /*
- * If isset_offset is provided, store whether the reloption is
- * set there.
- */
- if (elems[j].isset_offset > 0)
- {
- char *setpos = ((char *) rdopts) + elems[j].isset_offset;
-
- *(bool *) setpos = options[i].isset;
- }
-
switch (options[i].gen->type)
{
case RELOPT_TYPE_BOOL:
@@ -1798,6 +1871,11 @@ fillRelOptions(void *rdopts, Size basesize,
options[i].values.bool_val :
((relopt_bool *) options[i].gen)->default_val;
break;
+ case RELOPT_TYPE_TERNARY:
+ *(ternary *) itempos = options[i].isset ?
+ options[i].values.ternary_val :
+ ((relopt_ternary *) options[i].gen)->default_val;
+ break;
case RELOPT_TYPE_INT:
*(int *) itempos = options[i].isset ?
options[i].values.int_val :
@@ -1912,8 +1990,8 @@ default_reloptions(Datum reloptions, bool validate, relopt_kind kind)
offsetof(StdRdOptions, parallel_workers)},
{"vacuum_index_cleanup", RELOPT_TYPE_ENUM,
offsetof(StdRdOptions, vacuum_index_cleanup)},
- {"vacuum_truncate", RELOPT_TYPE_BOOL,
- offsetof(StdRdOptions, vacuum_truncate), offsetof(StdRdOptions, vacuum_truncate_set)},
+ {"vacuum_truncate", RELOPT_TYPE_TERNARY,
+ offsetof(StdRdOptions, vacuum_truncate)},
{"vacuum_max_eager_freeze_failure_rate", RELOPT_TYPE_REAL,
offsetof(StdRdOptions, vacuum_max_eager_freeze_failure_rate)}
};
@@ -1993,7 +2071,6 @@ build_local_reloptions(local_relopts *relopts, Datum options, bool validate)
elems[i].optname = opt->option->name;
elems[i].opttype = opt->option->type;
elems[i].offset = opt->offset;
- elems[i].isset_offset = 0; /* not supported for local relopts yet */
i++;
}
diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c
index 733ef40ae7..977babff54 100644
--- a/src/backend/commands/vacuum.c
+++ b/src/backend/commands/vacuum.c
@@ -2221,9 +2221,9 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams params,
{
StdRdOptions *opts = (StdRdOptions *) rel->rd_options;
- if (opts && opts->vacuum_truncate_set)
+ if (opts && opts->vacuum_truncate != TERNARY_UNSET)
{
- if (opts->vacuum_truncate)
+ if (opts->vacuum_truncate == TERNARY_TRUE)
params.truncate = VACOPTVALUE_ENABLED;
else
params.truncate = VACOPTVALUE_DISABLED;
diff --git a/src/include/access/reloptions.h b/src/include/access/reloptions.h
index a604a4702c..a436697658 100644
--- a/src/include/access/reloptions.h
+++ b/src/include/access/reloptions.h
@@ -29,6 +29,7 @@
typedef enum relopt_type
{
RELOPT_TYPE_BOOL,
+ RELOPT_TYPE_TERNARY, /* on, off, unset */
RELOPT_TYPE_INT,
RELOPT_TYPE_REAL,
RELOPT_TYPE_ENUM,
@@ -80,6 +81,7 @@ typedef struct relopt_value
union
{
bool bool_val;
+ ternary ternary_val;
int int_val;
double real_val;
int enum_val;
@@ -94,6 +96,12 @@ typedef struct relopt_bool
bool default_val;
} relopt_bool;
+typedef struct relopt_rernary
+{
+ relopt_gen gen;
+ int default_val;
+} relopt_ternary;
+
typedef struct relopt_int
{
relopt_gen gen;
@@ -152,19 +160,6 @@ typedef struct
const char *optname; /* option's name */
relopt_type opttype; /* option's datatype */
int offset; /* offset of field in result struct */
-
- /*
- * isset_offset is an optional offset of a field in the result struct that
- * stores whether the option is explicitly set for the relation or if it
- * just picked up the default value. In most cases, this can be
- * accomplished by giving the reloption a special out-of-range default
- * value (e.g., some integer reloptions use -2), but this isn't always
- * possible. For example, a Boolean reloption cannot be given an
- * out-of-range default, so we need another way to discover the source of
- * its value. This offset is only used if given a value greater than
- * zero.
- */
- int isset_offset;
} relopt_parse_elt;
/* Local reloption definition */
@@ -195,6 +190,8 @@ typedef struct local_relopts
extern relopt_kind add_reloption_kind(void);
extern void add_bool_reloption(bits32 kinds, const char *name, const char *desc,
bool default_val, LOCKMODE lockmode);
+extern void add_ternary_reloption(bits32 kinds, const char *name,
+ const char *desc, int default_val, LOCKMODE lockmode);
extern void add_int_reloption(bits32 kinds, const char *name, const char *desc,
int default_val, int min_val, int max_val,
LOCKMODE lockmode);
@@ -214,6 +211,9 @@ extern void register_reloptions_validator(local_relopts *relopts,
extern void add_local_bool_reloption(local_relopts *relopts, const char *name,
const char *desc, bool default_val,
int offset);
+extern void add_local_ternary_reloption(local_relopts *relopts,
+ const char *name, const char *desc,
+ ternary default_val, int offset);
extern void add_local_int_reloption(local_relopts *relopts, const char *name,
const char *desc, int default_val,
int min_val, int max_val, int offset);
diff --git a/src/include/c.h b/src/include/c.h
index 39022f8a9d..9c59ccccf8 100644
--- a/src/include/c.h
+++ b/src/include/c.h
@@ -488,6 +488,22 @@ typedef void (*pg_funcptr_t) (void);
#include <stdbool.h>
+/*
+ * ternary
+ * Boolean value with an extrea "unset" option
+ *
+ * Ternary data type is used in relation options that can be "true", "false" or
+ * "unset". Since relation options are used deep inside the PostgreSQL code,
+ * this type is declared globally.
+*/
+
+typedef enum ternary
+{
+ TERNARY_FALSE = 0,
+ TERNARY_TRUE = 1,
+ TERNARY_UNSET = -1
+} ternary;
+
/* ----------------------------------------------------------------
* Section 3: standard system types
diff --git a/src/include/utils/rel.h b/src/include/utils/rel.h
index b552359915..7346d618f9 100644
--- a/src/include/utils/rel.h
+++ b/src/include/utils/rel.h
@@ -346,8 +346,7 @@ typedef struct StdRdOptions
bool user_catalog_table; /* use as an additional catalog relation */
int parallel_workers; /* max number of parallel workers */
StdRdOptIndexCleanup vacuum_index_cleanup; /* controls index vacuuming */
- bool vacuum_truncate; /* enables vacuum to truncate a relation */
- bool vacuum_truncate_set; /* whether vacuum_truncate is set */
+ ternary vacuum_truncate; /* enables vacuum to truncate a relation */
/*
* Fraction of pages in a relation that vacuum can eagerly scan and fail
--
2.39.2
--nextPart5129932.5fSG56mABF
Content-Disposition: attachment;
filename="v2-0003-Add-alias-to-be-used-as-unset-state.patch"
Content-Transfer-Encoding: 7Bit
Content-Type: text/x-patch; charset="unicode-2-0-utf-8";
name="v2-0003-Add-alias-to-be-used-as-unset-state.patch"
^ permalink raw reply [nested|flat] 868+ messages in thread
* [PATCH v2a 2/4] Introduce ternary reloptions
@ 2025-09-04 14:41 Nikolay Shaplov <[email protected]>
0 siblings, 0 replies; 868+ messages in thread
From: Nikolay Shaplov @ 2025-09-04 14:41 UTC (permalink / raw)
Introduce ternary reloption as a replacement for current `vacuum_truncate`
implementation. Remove `vacuum_truncate_set` additional flag and using
`TERNARY_UNSET` value instead.
---
src/backend/access/common/reloptions.c | 129 ++++++++++++++++++++-----
src/backend/commands/vacuum.c | 4 +-
src/include/access/reloptions.h | 26 ++---
src/include/c.h | 16 +++
src/include/utils/rel.h | 3 +-
5 files changed, 135 insertions(+), 43 deletions(-)
diff --git a/src/backend/access/common/reloptions.c b/src/backend/access/common/reloptions.c
index 0af3fea68f..f543f11001 100644
--- a/src/backend/access/common/reloptions.c
+++ b/src/backend/access/common/reloptions.c
@@ -40,9 +40,9 @@
*
* To add an option:
*
- * (i) decide on a type (bool, integer, real, enum, string), name, default
- * value, upper and lower bounds (if applicable); for strings, consider a
- * validation routine.
+ * (i) decide on a type (bool, ternary, integer, real, enum, string), name,
+ * default value, upper and lower bounds (if applicable); for strings,
+ * consider a validation routine.
* (ii) add a record below (or use add_<type>_reloption).
* (iii) add it to the appropriate options struct (perhaps StdRdOptions)
* (iv) add it to the appropriate handling routine (perhaps
@@ -147,15 +147,6 @@ static relopt_bool boolRelOpts[] =
},
false
},
- {
- {
- "vacuum_truncate",
- "Enables vacuum to truncate empty pages at the end of this table",
- RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
- ShareUpdateExclusiveLock
- },
- true
- },
{
{
"deduplicate_items",
@@ -170,6 +161,21 @@ static relopt_bool boolRelOpts[] =
{{NULL}}
};
+static relopt_ternary ternaryRelOpts[] =
+{
+ {
+ {
+ "vacuum_truncate",
+ "Enables vacuum to truncate empty pages at the end of this table",
+ RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
+ ShareUpdateExclusiveLock
+ },
+ TERNARY_UNSET
+ },
+ /* list terminator */
+ {{NULL}}
+};
+
static relopt_int intRelOpts[] =
{
{
@@ -600,6 +606,13 @@ initialize_reloptions(void)
boolRelOpts[i].gen.lockmode));
j++;
}
+ for (i = 0; ternaryRelOpts[i].gen.name; i++)
+ {
+ Assert(DoLockModesConflict(ternaryRelOpts[i].gen.lockmode,
+ ternaryRelOpts[i].gen.lockmode));
+ j++;
+ }
+
for (i = 0; intRelOpts[i].gen.name; i++)
{
Assert(DoLockModesConflict(intRelOpts[i].gen.lockmode,
@@ -640,6 +653,14 @@ initialize_reloptions(void)
j++;
}
+ for (i = 0; ternaryRelOpts[i].gen.name; i++)
+ {
+ relOpts[j] = &ternaryRelOpts[i].gen;
+ relOpts[j]->type = RELOPT_TYPE_TERNARY;
+ relOpts[j]->namelen = strlen(relOpts[j]->name);
+ j++;
+ }
+
for (i = 0; intRelOpts[i].gen.name; i++)
{
relOpts[j] = &intRelOpts[i].gen;
@@ -800,6 +821,9 @@ allocate_reloption(bits32 kinds, int type, const char *name, const char *desc,
case RELOPT_TYPE_BOOL:
size = sizeof(relopt_bool);
break;
+ case RELOPT_TYPE_TERNARY:
+ size = sizeof(relopt_ternary);
+ break;
case RELOPT_TYPE_INT:
size = sizeof(relopt_int);
break;
@@ -883,6 +907,54 @@ add_local_bool_reloption(local_relopts *relopts, const char *name,
add_local_reloption(relopts, (relopt_gen *) newoption, offset);
}
+/*
+ * init_ternary_reloption
+ * Allocate and initialize a new ternary reloption
+ */
+static relopt_ternary *
+init_ternary_reloption(bits32 kinds, const char *name, const char *desc,
+ ternary default_val, LOCKMODE lockmode)
+{
+ relopt_ternary *newoption;
+
+ newoption = (relopt_ternary *) allocate_reloption(kinds,
+ RELOPT_TYPE_TERNARY, name, desc, lockmode);
+ newoption->default_val = default_val;
+
+ return newoption;
+}
+
+/*
+ * add_ternary_reloption
+ * Add a new ternary reloption
+ */
+void
+add_ternary_reloption(bits32 kinds, const char *name, const char *desc,
+ ternary default_val, LOCKMODE lockmode)
+{
+ relopt_ternary *newoption = init_ternary_reloption(kinds, name, desc,
+ default_val, lockmode);
+
+ add_reloption((relopt_gen *) newoption);
+}
+
+/*
+ * add_local_ternary_reloption
+ * Add a new ternary local reloption
+ *
+ * 'offset' is offset of ternary-typed field.
+ */
+void
+add_local_ternary_reloption(local_relopts *relopts, const char *name,
+ const char *desc, ternary default_val,
+ int offset)
+{
+ relopt_ternary *newoption = init_ternary_reloption(RELOPT_KIND_LOCAL,
+ name, desc,
+ default_val, 0);
+
+ add_local_reloption(relopts, (relopt_gen *) newoption, offset);
+}
/*
* init_real_reloption
@@ -1617,6 +1689,18 @@ parse_one_reloption(relopt_value *option, char *text_str, int text_len,
option->gen->name, value)));
}
break;
+ case RELOPT_TYPE_TERNARY:
+ {
+ bool b;
+ parsed = parse_bool(value, &b);
+ option->values.ternary_val = b ? TERNARY_TRUE : TERNARY_FALSE;
+ if (validate && !parsed)
+ ereport(ERROR,
+ (errcode(ERRCODE_INVALID_PARAMETER_VALUE),
+ errmsg("invalid value for ternary option \"%s\": %s",
+ option->gen->name, value)));
+ }
+ break;
case RELOPT_TYPE_INT:
{
relopt_int *optint = (relopt_int *) option->gen;
@@ -1780,17 +1864,6 @@ fillRelOptions(void *rdopts, Size basesize,
char *itempos = ((char *) rdopts) + elems[j].offset;
char *string_val;
- /*
- * If isset_offset is provided, store whether the reloption is
- * set there.
- */
- if (elems[j].isset_offset > 0)
- {
- char *setpos = ((char *) rdopts) + elems[j].isset_offset;
-
- *(bool *) setpos = options[i].isset;
- }
-
switch (options[i].gen->type)
{
case RELOPT_TYPE_BOOL:
@@ -1798,6 +1871,11 @@ fillRelOptions(void *rdopts, Size basesize,
options[i].values.bool_val :
((relopt_bool *) options[i].gen)->default_val;
break;
+ case RELOPT_TYPE_TERNARY:
+ *(ternary *) itempos = options[i].isset ?
+ options[i].values.ternary_val :
+ ((relopt_ternary *) options[i].gen)->default_val;
+ break;
case RELOPT_TYPE_INT:
*(int *) itempos = options[i].isset ?
options[i].values.int_val :
@@ -1912,8 +1990,8 @@ default_reloptions(Datum reloptions, bool validate, relopt_kind kind)
offsetof(StdRdOptions, parallel_workers)},
{"vacuum_index_cleanup", RELOPT_TYPE_ENUM,
offsetof(StdRdOptions, vacuum_index_cleanup)},
- {"vacuum_truncate", RELOPT_TYPE_BOOL,
- offsetof(StdRdOptions, vacuum_truncate), offsetof(StdRdOptions, vacuum_truncate_set)},
+ {"vacuum_truncate", RELOPT_TYPE_TERNARY,
+ offsetof(StdRdOptions, vacuum_truncate)},
{"vacuum_max_eager_freeze_failure_rate", RELOPT_TYPE_REAL,
offsetof(StdRdOptions, vacuum_max_eager_freeze_failure_rate)}
};
@@ -1993,7 +2071,6 @@ build_local_reloptions(local_relopts *relopts, Datum options, bool validate)
elems[i].optname = opt->option->name;
elems[i].opttype = opt->option->type;
elems[i].offset = opt->offset;
- elems[i].isset_offset = 0; /* not supported for local relopts yet */
i++;
}
diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c
index 733ef40ae7..977babff54 100644
--- a/src/backend/commands/vacuum.c
+++ b/src/backend/commands/vacuum.c
@@ -2221,9 +2221,9 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams params,
{
StdRdOptions *opts = (StdRdOptions *) rel->rd_options;
- if (opts && opts->vacuum_truncate_set)
+ if (opts && opts->vacuum_truncate != TERNARY_UNSET)
{
- if (opts->vacuum_truncate)
+ if (opts->vacuum_truncate == TERNARY_TRUE)
params.truncate = VACOPTVALUE_ENABLED;
else
params.truncate = VACOPTVALUE_DISABLED;
diff --git a/src/include/access/reloptions.h b/src/include/access/reloptions.h
index a604a4702c..a436697658 100644
--- a/src/include/access/reloptions.h
+++ b/src/include/access/reloptions.h
@@ -29,6 +29,7 @@
typedef enum relopt_type
{
RELOPT_TYPE_BOOL,
+ RELOPT_TYPE_TERNARY, /* on, off, unset */
RELOPT_TYPE_INT,
RELOPT_TYPE_REAL,
RELOPT_TYPE_ENUM,
@@ -80,6 +81,7 @@ typedef struct relopt_value
union
{
bool bool_val;
+ ternary ternary_val;
int int_val;
double real_val;
int enum_val;
@@ -94,6 +96,12 @@ typedef struct relopt_bool
bool default_val;
} relopt_bool;
+typedef struct relopt_rernary
+{
+ relopt_gen gen;
+ int default_val;
+} relopt_ternary;
+
typedef struct relopt_int
{
relopt_gen gen;
@@ -152,19 +160,6 @@ typedef struct
const char *optname; /* option's name */
relopt_type opttype; /* option's datatype */
int offset; /* offset of field in result struct */
-
- /*
- * isset_offset is an optional offset of a field in the result struct that
- * stores whether the option is explicitly set for the relation or if it
- * just picked up the default value. In most cases, this can be
- * accomplished by giving the reloption a special out-of-range default
- * value (e.g., some integer reloptions use -2), but this isn't always
- * possible. For example, a Boolean reloption cannot be given an
- * out-of-range default, so we need another way to discover the source of
- * its value. This offset is only used if given a value greater than
- * zero.
- */
- int isset_offset;
} relopt_parse_elt;
/* Local reloption definition */
@@ -195,6 +190,8 @@ typedef struct local_relopts
extern relopt_kind add_reloption_kind(void);
extern void add_bool_reloption(bits32 kinds, const char *name, const char *desc,
bool default_val, LOCKMODE lockmode);
+extern void add_ternary_reloption(bits32 kinds, const char *name,
+ const char *desc, int default_val, LOCKMODE lockmode);
extern void add_int_reloption(bits32 kinds, const char *name, const char *desc,
int default_val, int min_val, int max_val,
LOCKMODE lockmode);
@@ -214,6 +211,9 @@ extern void register_reloptions_validator(local_relopts *relopts,
extern void add_local_bool_reloption(local_relopts *relopts, const char *name,
const char *desc, bool default_val,
int offset);
+extern void add_local_ternary_reloption(local_relopts *relopts,
+ const char *name, const char *desc,
+ ternary default_val, int offset);
extern void add_local_int_reloption(local_relopts *relopts, const char *name,
const char *desc, int default_val,
int min_val, int max_val, int offset);
diff --git a/src/include/c.h b/src/include/c.h
index 39022f8a9d..9c59ccccf8 100644
--- a/src/include/c.h
+++ b/src/include/c.h
@@ -488,6 +488,22 @@ typedef void (*pg_funcptr_t) (void);
#include <stdbool.h>
+/*
+ * ternary
+ * Boolean value with an extrea "unset" option
+ *
+ * Ternary data type is used in relation options that can be "true", "false" or
+ * "unset". Since relation options are used deep inside the PostgreSQL code,
+ * this type is declared globally.
+*/
+
+typedef enum ternary
+{
+ TERNARY_FALSE = 0,
+ TERNARY_TRUE = 1,
+ TERNARY_UNSET = -1
+} ternary;
+
/* ----------------------------------------------------------------
* Section 3: standard system types
diff --git a/src/include/utils/rel.h b/src/include/utils/rel.h
index b552359915..7346d618f9 100644
--- a/src/include/utils/rel.h
+++ b/src/include/utils/rel.h
@@ -346,8 +346,7 @@ typedef struct StdRdOptions
bool user_catalog_table; /* use as an additional catalog relation */
int parallel_workers; /* max number of parallel workers */
StdRdOptIndexCleanup vacuum_index_cleanup; /* controls index vacuuming */
- bool vacuum_truncate; /* enables vacuum to truncate a relation */
- bool vacuum_truncate_set; /* whether vacuum_truncate is set */
+ ternary vacuum_truncate; /* enables vacuum to truncate a relation */
/*
* Fraction of pages in a relation that vacuum can eagerly scan and fail
--
2.39.2
--nextPart4155600.3ZeAukHxDK
Content-Disposition: attachment;
filename="v2a-0003-Add-alias-to-be-used-as-unset-state.patch"
Content-Transfer-Encoding: 7Bit
Content-Type: text/x-patch; charset="unicode-2-0-utf-8";
name="v2a-0003-Add-alias-to-be-used-as-unset-state.patch"
^ permalink raw reply [nested|flat] 868+ messages in thread
* [PATCH v2a 2/4] Introduce ternary reloptions
@ 2025-09-04 14:41 Nikolay Shaplov <[email protected]>
0 siblings, 0 replies; 868+ messages in thread
From: Nikolay Shaplov @ 2025-09-04 14:41 UTC (permalink / raw)
Introduce ternary reloption as a replacement for current `vacuum_truncate`
implementation. Remove `vacuum_truncate_set` additional flag and using
`TERNARY_UNSET` value instead.
---
src/backend/access/common/reloptions.c | 129 ++++++++++++++++++++-----
src/backend/commands/vacuum.c | 4 +-
src/include/access/reloptions.h | 26 ++---
src/include/c.h | 16 +++
src/include/utils/rel.h | 3 +-
5 files changed, 135 insertions(+), 43 deletions(-)
diff --git a/src/backend/access/common/reloptions.c b/src/backend/access/common/reloptions.c
index 0af3fea68f..f543f11001 100644
--- a/src/backend/access/common/reloptions.c
+++ b/src/backend/access/common/reloptions.c
@@ -40,9 +40,9 @@
*
* To add an option:
*
- * (i) decide on a type (bool, integer, real, enum, string), name, default
- * value, upper and lower bounds (if applicable); for strings, consider a
- * validation routine.
+ * (i) decide on a type (bool, ternary, integer, real, enum, string), name,
+ * default value, upper and lower bounds (if applicable); for strings,
+ * consider a validation routine.
* (ii) add a record below (or use add_<type>_reloption).
* (iii) add it to the appropriate options struct (perhaps StdRdOptions)
* (iv) add it to the appropriate handling routine (perhaps
@@ -147,15 +147,6 @@ static relopt_bool boolRelOpts[] =
},
false
},
- {
- {
- "vacuum_truncate",
- "Enables vacuum to truncate empty pages at the end of this table",
- RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
- ShareUpdateExclusiveLock
- },
- true
- },
{
{
"deduplicate_items",
@@ -170,6 +161,21 @@ static relopt_bool boolRelOpts[] =
{{NULL}}
};
+static relopt_ternary ternaryRelOpts[] =
+{
+ {
+ {
+ "vacuum_truncate",
+ "Enables vacuum to truncate empty pages at the end of this table",
+ RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
+ ShareUpdateExclusiveLock
+ },
+ TERNARY_UNSET
+ },
+ /* list terminator */
+ {{NULL}}
+};
+
static relopt_int intRelOpts[] =
{
{
@@ -600,6 +606,13 @@ initialize_reloptions(void)
boolRelOpts[i].gen.lockmode));
j++;
}
+ for (i = 0; ternaryRelOpts[i].gen.name; i++)
+ {
+ Assert(DoLockModesConflict(ternaryRelOpts[i].gen.lockmode,
+ ternaryRelOpts[i].gen.lockmode));
+ j++;
+ }
+
for (i = 0; intRelOpts[i].gen.name; i++)
{
Assert(DoLockModesConflict(intRelOpts[i].gen.lockmode,
@@ -640,6 +653,14 @@ initialize_reloptions(void)
j++;
}
+ for (i = 0; ternaryRelOpts[i].gen.name; i++)
+ {
+ relOpts[j] = &ternaryRelOpts[i].gen;
+ relOpts[j]->type = RELOPT_TYPE_TERNARY;
+ relOpts[j]->namelen = strlen(relOpts[j]->name);
+ j++;
+ }
+
for (i = 0; intRelOpts[i].gen.name; i++)
{
relOpts[j] = &intRelOpts[i].gen;
@@ -800,6 +821,9 @@ allocate_reloption(bits32 kinds, int type, const char *name, const char *desc,
case RELOPT_TYPE_BOOL:
size = sizeof(relopt_bool);
break;
+ case RELOPT_TYPE_TERNARY:
+ size = sizeof(relopt_ternary);
+ break;
case RELOPT_TYPE_INT:
size = sizeof(relopt_int);
break;
@@ -883,6 +907,54 @@ add_local_bool_reloption(local_relopts *relopts, const char *name,
add_local_reloption(relopts, (relopt_gen *) newoption, offset);
}
+/*
+ * init_ternary_reloption
+ * Allocate and initialize a new ternary reloption
+ */
+static relopt_ternary *
+init_ternary_reloption(bits32 kinds, const char *name, const char *desc,
+ ternary default_val, LOCKMODE lockmode)
+{
+ relopt_ternary *newoption;
+
+ newoption = (relopt_ternary *) allocate_reloption(kinds,
+ RELOPT_TYPE_TERNARY, name, desc, lockmode);
+ newoption->default_val = default_val;
+
+ return newoption;
+}
+
+/*
+ * add_ternary_reloption
+ * Add a new ternary reloption
+ */
+void
+add_ternary_reloption(bits32 kinds, const char *name, const char *desc,
+ ternary default_val, LOCKMODE lockmode)
+{
+ relopt_ternary *newoption = init_ternary_reloption(kinds, name, desc,
+ default_val, lockmode);
+
+ add_reloption((relopt_gen *) newoption);
+}
+
+/*
+ * add_local_ternary_reloption
+ * Add a new ternary local reloption
+ *
+ * 'offset' is offset of ternary-typed field.
+ */
+void
+add_local_ternary_reloption(local_relopts *relopts, const char *name,
+ const char *desc, ternary default_val,
+ int offset)
+{
+ relopt_ternary *newoption = init_ternary_reloption(RELOPT_KIND_LOCAL,
+ name, desc,
+ default_val, 0);
+
+ add_local_reloption(relopts, (relopt_gen *) newoption, offset);
+}
/*
* init_real_reloption
@@ -1617,6 +1689,18 @@ parse_one_reloption(relopt_value *option, char *text_str, int text_len,
option->gen->name, value)));
}
break;
+ case RELOPT_TYPE_TERNARY:
+ {
+ bool b;
+ parsed = parse_bool(value, &b);
+ option->values.ternary_val = b ? TERNARY_TRUE : TERNARY_FALSE;
+ if (validate && !parsed)
+ ereport(ERROR,
+ (errcode(ERRCODE_INVALID_PARAMETER_VALUE),
+ errmsg("invalid value for ternary option \"%s\": %s",
+ option->gen->name, value)));
+ }
+ break;
case RELOPT_TYPE_INT:
{
relopt_int *optint = (relopt_int *) option->gen;
@@ -1780,17 +1864,6 @@ fillRelOptions(void *rdopts, Size basesize,
char *itempos = ((char *) rdopts) + elems[j].offset;
char *string_val;
- /*
- * If isset_offset is provided, store whether the reloption is
- * set there.
- */
- if (elems[j].isset_offset > 0)
- {
- char *setpos = ((char *) rdopts) + elems[j].isset_offset;
-
- *(bool *) setpos = options[i].isset;
- }
-
switch (options[i].gen->type)
{
case RELOPT_TYPE_BOOL:
@@ -1798,6 +1871,11 @@ fillRelOptions(void *rdopts, Size basesize,
options[i].values.bool_val :
((relopt_bool *) options[i].gen)->default_val;
break;
+ case RELOPT_TYPE_TERNARY:
+ *(ternary *) itempos = options[i].isset ?
+ options[i].values.ternary_val :
+ ((relopt_ternary *) options[i].gen)->default_val;
+ break;
case RELOPT_TYPE_INT:
*(int *) itempos = options[i].isset ?
options[i].values.int_val :
@@ -1912,8 +1990,8 @@ default_reloptions(Datum reloptions, bool validate, relopt_kind kind)
offsetof(StdRdOptions, parallel_workers)},
{"vacuum_index_cleanup", RELOPT_TYPE_ENUM,
offsetof(StdRdOptions, vacuum_index_cleanup)},
- {"vacuum_truncate", RELOPT_TYPE_BOOL,
- offsetof(StdRdOptions, vacuum_truncate), offsetof(StdRdOptions, vacuum_truncate_set)},
+ {"vacuum_truncate", RELOPT_TYPE_TERNARY,
+ offsetof(StdRdOptions, vacuum_truncate)},
{"vacuum_max_eager_freeze_failure_rate", RELOPT_TYPE_REAL,
offsetof(StdRdOptions, vacuum_max_eager_freeze_failure_rate)}
};
@@ -1993,7 +2071,6 @@ build_local_reloptions(local_relopts *relopts, Datum options, bool validate)
elems[i].optname = opt->option->name;
elems[i].opttype = opt->option->type;
elems[i].offset = opt->offset;
- elems[i].isset_offset = 0; /* not supported for local relopts yet */
i++;
}
diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c
index 733ef40ae7..977babff54 100644
--- a/src/backend/commands/vacuum.c
+++ b/src/backend/commands/vacuum.c
@@ -2221,9 +2221,9 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams params,
{
StdRdOptions *opts = (StdRdOptions *) rel->rd_options;
- if (opts && opts->vacuum_truncate_set)
+ if (opts && opts->vacuum_truncate != TERNARY_UNSET)
{
- if (opts->vacuum_truncate)
+ if (opts->vacuum_truncate == TERNARY_TRUE)
params.truncate = VACOPTVALUE_ENABLED;
else
params.truncate = VACOPTVALUE_DISABLED;
diff --git a/src/include/access/reloptions.h b/src/include/access/reloptions.h
index a604a4702c..a436697658 100644
--- a/src/include/access/reloptions.h
+++ b/src/include/access/reloptions.h
@@ -29,6 +29,7 @@
typedef enum relopt_type
{
RELOPT_TYPE_BOOL,
+ RELOPT_TYPE_TERNARY, /* on, off, unset */
RELOPT_TYPE_INT,
RELOPT_TYPE_REAL,
RELOPT_TYPE_ENUM,
@@ -80,6 +81,7 @@ typedef struct relopt_value
union
{
bool bool_val;
+ ternary ternary_val;
int int_val;
double real_val;
int enum_val;
@@ -94,6 +96,12 @@ typedef struct relopt_bool
bool default_val;
} relopt_bool;
+typedef struct relopt_rernary
+{
+ relopt_gen gen;
+ int default_val;
+} relopt_ternary;
+
typedef struct relopt_int
{
relopt_gen gen;
@@ -152,19 +160,6 @@ typedef struct
const char *optname; /* option's name */
relopt_type opttype; /* option's datatype */
int offset; /* offset of field in result struct */
-
- /*
- * isset_offset is an optional offset of a field in the result struct that
- * stores whether the option is explicitly set for the relation or if it
- * just picked up the default value. In most cases, this can be
- * accomplished by giving the reloption a special out-of-range default
- * value (e.g., some integer reloptions use -2), but this isn't always
- * possible. For example, a Boolean reloption cannot be given an
- * out-of-range default, so we need another way to discover the source of
- * its value. This offset is only used if given a value greater than
- * zero.
- */
- int isset_offset;
} relopt_parse_elt;
/* Local reloption definition */
@@ -195,6 +190,8 @@ typedef struct local_relopts
extern relopt_kind add_reloption_kind(void);
extern void add_bool_reloption(bits32 kinds, const char *name, const char *desc,
bool default_val, LOCKMODE lockmode);
+extern void add_ternary_reloption(bits32 kinds, const char *name,
+ const char *desc, int default_val, LOCKMODE lockmode);
extern void add_int_reloption(bits32 kinds, const char *name, const char *desc,
int default_val, int min_val, int max_val,
LOCKMODE lockmode);
@@ -214,6 +211,9 @@ extern void register_reloptions_validator(local_relopts *relopts,
extern void add_local_bool_reloption(local_relopts *relopts, const char *name,
const char *desc, bool default_val,
int offset);
+extern void add_local_ternary_reloption(local_relopts *relopts,
+ const char *name, const char *desc,
+ ternary default_val, int offset);
extern void add_local_int_reloption(local_relopts *relopts, const char *name,
const char *desc, int default_val,
int min_val, int max_val, int offset);
diff --git a/src/include/c.h b/src/include/c.h
index 39022f8a9d..9c59ccccf8 100644
--- a/src/include/c.h
+++ b/src/include/c.h
@@ -488,6 +488,22 @@ typedef void (*pg_funcptr_t) (void);
#include <stdbool.h>
+/*
+ * ternary
+ * Boolean value with an extrea "unset" option
+ *
+ * Ternary data type is used in relation options that can be "true", "false" or
+ * "unset". Since relation options are used deep inside the PostgreSQL code,
+ * this type is declared globally.
+*/
+
+typedef enum ternary
+{
+ TERNARY_FALSE = 0,
+ TERNARY_TRUE = 1,
+ TERNARY_UNSET = -1
+} ternary;
+
/* ----------------------------------------------------------------
* Section 3: standard system types
diff --git a/src/include/utils/rel.h b/src/include/utils/rel.h
index b552359915..7346d618f9 100644
--- a/src/include/utils/rel.h
+++ b/src/include/utils/rel.h
@@ -346,8 +346,7 @@ typedef struct StdRdOptions
bool user_catalog_table; /* use as an additional catalog relation */
int parallel_workers; /* max number of parallel workers */
StdRdOptIndexCleanup vacuum_index_cleanup; /* controls index vacuuming */
- bool vacuum_truncate; /* enables vacuum to truncate a relation */
- bool vacuum_truncate_set; /* whether vacuum_truncate is set */
+ ternary vacuum_truncate; /* enables vacuum to truncate a relation */
/*
* Fraction of pages in a relation that vacuum can eagerly scan and fail
--
2.39.2
--nextPart4155600.3ZeAukHxDK
Content-Disposition: attachment;
filename="v2a-0003-Add-alias-to-be-used-as-unset-state.patch"
Content-Transfer-Encoding: 7Bit
Content-Type: text/x-patch; charset="unicode-2-0-utf-8";
name="v2a-0003-Add-alias-to-be-used-as-unset-state.patch"
^ permalink raw reply [nested|flat] 868+ messages in thread
* [PATCH v2 2/4] Introduce ternary reloptions
@ 2025-09-04 14:41 Nikolay Shaplov <[email protected]>
0 siblings, 0 replies; 868+ messages in thread
From: Nikolay Shaplov @ 2025-09-04 14:41 UTC (permalink / raw)
Introduce ternary reloption as a replacement for current `vacuum_truncate`
implementation. Remove `vacuum_truncate_set` additional flag and using
`TERNARY_UNSET` value instead.
---
src/backend/access/common/reloptions.c | 129 ++++++++++++++++++++-----
src/backend/commands/vacuum.c | 4 +-
src/include/access/reloptions.h | 26 ++---
src/include/c.h | 16 +++
src/include/utils/rel.h | 3 +-
5 files changed, 135 insertions(+), 43 deletions(-)
diff --git a/src/backend/access/common/reloptions.c b/src/backend/access/common/reloptions.c
index 0af3fea68f..f543f11001 100644
--- a/src/backend/access/common/reloptions.c
+++ b/src/backend/access/common/reloptions.c
@@ -40,9 +40,9 @@
*
* To add an option:
*
- * (i) decide on a type (bool, integer, real, enum, string), name, default
- * value, upper and lower bounds (if applicable); for strings, consider a
- * validation routine.
+ * (i) decide on a type (bool, ternary, integer, real, enum, string), name,
+ * default value, upper and lower bounds (if applicable); for strings,
+ * consider a validation routine.
* (ii) add a record below (or use add_<type>_reloption).
* (iii) add it to the appropriate options struct (perhaps StdRdOptions)
* (iv) add it to the appropriate handling routine (perhaps
@@ -147,15 +147,6 @@ static relopt_bool boolRelOpts[] =
},
false
},
- {
- {
- "vacuum_truncate",
- "Enables vacuum to truncate empty pages at the end of this table",
- RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
- ShareUpdateExclusiveLock
- },
- true
- },
{
{
"deduplicate_items",
@@ -170,6 +161,21 @@ static relopt_bool boolRelOpts[] =
{{NULL}}
};
+static relopt_ternary ternaryRelOpts[] =
+{
+ {
+ {
+ "vacuum_truncate",
+ "Enables vacuum to truncate empty pages at the end of this table",
+ RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
+ ShareUpdateExclusiveLock
+ },
+ TERNARY_UNSET
+ },
+ /* list terminator */
+ {{NULL}}
+};
+
static relopt_int intRelOpts[] =
{
{
@@ -600,6 +606,13 @@ initialize_reloptions(void)
boolRelOpts[i].gen.lockmode));
j++;
}
+ for (i = 0; ternaryRelOpts[i].gen.name; i++)
+ {
+ Assert(DoLockModesConflict(ternaryRelOpts[i].gen.lockmode,
+ ternaryRelOpts[i].gen.lockmode));
+ j++;
+ }
+
for (i = 0; intRelOpts[i].gen.name; i++)
{
Assert(DoLockModesConflict(intRelOpts[i].gen.lockmode,
@@ -640,6 +653,14 @@ initialize_reloptions(void)
j++;
}
+ for (i = 0; ternaryRelOpts[i].gen.name; i++)
+ {
+ relOpts[j] = &ternaryRelOpts[i].gen;
+ relOpts[j]->type = RELOPT_TYPE_TERNARY;
+ relOpts[j]->namelen = strlen(relOpts[j]->name);
+ j++;
+ }
+
for (i = 0; intRelOpts[i].gen.name; i++)
{
relOpts[j] = &intRelOpts[i].gen;
@@ -800,6 +821,9 @@ allocate_reloption(bits32 kinds, int type, const char *name, const char *desc,
case RELOPT_TYPE_BOOL:
size = sizeof(relopt_bool);
break;
+ case RELOPT_TYPE_TERNARY:
+ size = sizeof(relopt_ternary);
+ break;
case RELOPT_TYPE_INT:
size = sizeof(relopt_int);
break;
@@ -883,6 +907,54 @@ add_local_bool_reloption(local_relopts *relopts, const char *name,
add_local_reloption(relopts, (relopt_gen *) newoption, offset);
}
+/*
+ * init_ternary_reloption
+ * Allocate and initialize a new ternary reloption
+ */
+static relopt_ternary *
+init_ternary_reloption(bits32 kinds, const char *name, const char *desc,
+ ternary default_val, LOCKMODE lockmode)
+{
+ relopt_ternary *newoption;
+
+ newoption = (relopt_ternary *) allocate_reloption(kinds,
+ RELOPT_TYPE_TERNARY, name, desc, lockmode);
+ newoption->default_val = default_val;
+
+ return newoption;
+}
+
+/*
+ * add_ternary_reloption
+ * Add a new ternary reloption
+ */
+void
+add_ternary_reloption(bits32 kinds, const char *name, const char *desc,
+ ternary default_val, LOCKMODE lockmode)
+{
+ relopt_ternary *newoption = init_ternary_reloption(kinds, name, desc,
+ default_val, lockmode);
+
+ add_reloption((relopt_gen *) newoption);
+}
+
+/*
+ * add_local_ternary_reloption
+ * Add a new ternary local reloption
+ *
+ * 'offset' is offset of ternary-typed field.
+ */
+void
+add_local_ternary_reloption(local_relopts *relopts, const char *name,
+ const char *desc, ternary default_val,
+ int offset)
+{
+ relopt_ternary *newoption = init_ternary_reloption(RELOPT_KIND_LOCAL,
+ name, desc,
+ default_val, 0);
+
+ add_local_reloption(relopts, (relopt_gen *) newoption, offset);
+}
/*
* init_real_reloption
@@ -1617,6 +1689,18 @@ parse_one_reloption(relopt_value *option, char *text_str, int text_len,
option->gen->name, value)));
}
break;
+ case RELOPT_TYPE_TERNARY:
+ {
+ bool b;
+ parsed = parse_bool(value, &b);
+ option->values.ternary_val = b ? TERNARY_TRUE : TERNARY_FALSE;
+ if (validate && !parsed)
+ ereport(ERROR,
+ (errcode(ERRCODE_INVALID_PARAMETER_VALUE),
+ errmsg("invalid value for ternary option \"%s\": %s",
+ option->gen->name, value)));
+ }
+ break;
case RELOPT_TYPE_INT:
{
relopt_int *optint = (relopt_int *) option->gen;
@@ -1780,17 +1864,6 @@ fillRelOptions(void *rdopts, Size basesize,
char *itempos = ((char *) rdopts) + elems[j].offset;
char *string_val;
- /*
- * If isset_offset is provided, store whether the reloption is
- * set there.
- */
- if (elems[j].isset_offset > 0)
- {
- char *setpos = ((char *) rdopts) + elems[j].isset_offset;
-
- *(bool *) setpos = options[i].isset;
- }
-
switch (options[i].gen->type)
{
case RELOPT_TYPE_BOOL:
@@ -1798,6 +1871,11 @@ fillRelOptions(void *rdopts, Size basesize,
options[i].values.bool_val :
((relopt_bool *) options[i].gen)->default_val;
break;
+ case RELOPT_TYPE_TERNARY:
+ *(ternary *) itempos = options[i].isset ?
+ options[i].values.ternary_val :
+ ((relopt_ternary *) options[i].gen)->default_val;
+ break;
case RELOPT_TYPE_INT:
*(int *) itempos = options[i].isset ?
options[i].values.int_val :
@@ -1912,8 +1990,8 @@ default_reloptions(Datum reloptions, bool validate, relopt_kind kind)
offsetof(StdRdOptions, parallel_workers)},
{"vacuum_index_cleanup", RELOPT_TYPE_ENUM,
offsetof(StdRdOptions, vacuum_index_cleanup)},
- {"vacuum_truncate", RELOPT_TYPE_BOOL,
- offsetof(StdRdOptions, vacuum_truncate), offsetof(StdRdOptions, vacuum_truncate_set)},
+ {"vacuum_truncate", RELOPT_TYPE_TERNARY,
+ offsetof(StdRdOptions, vacuum_truncate)},
{"vacuum_max_eager_freeze_failure_rate", RELOPT_TYPE_REAL,
offsetof(StdRdOptions, vacuum_max_eager_freeze_failure_rate)}
};
@@ -1993,7 +2071,6 @@ build_local_reloptions(local_relopts *relopts, Datum options, bool validate)
elems[i].optname = opt->option->name;
elems[i].opttype = opt->option->type;
elems[i].offset = opt->offset;
- elems[i].isset_offset = 0; /* not supported for local relopts yet */
i++;
}
diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c
index 733ef40ae7..977babff54 100644
--- a/src/backend/commands/vacuum.c
+++ b/src/backend/commands/vacuum.c
@@ -2221,9 +2221,9 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams params,
{
StdRdOptions *opts = (StdRdOptions *) rel->rd_options;
- if (opts && opts->vacuum_truncate_set)
+ if (opts && opts->vacuum_truncate != TERNARY_UNSET)
{
- if (opts->vacuum_truncate)
+ if (opts->vacuum_truncate == TERNARY_TRUE)
params.truncate = VACOPTVALUE_ENABLED;
else
params.truncate = VACOPTVALUE_DISABLED;
diff --git a/src/include/access/reloptions.h b/src/include/access/reloptions.h
index a604a4702c..a436697658 100644
--- a/src/include/access/reloptions.h
+++ b/src/include/access/reloptions.h
@@ -29,6 +29,7 @@
typedef enum relopt_type
{
RELOPT_TYPE_BOOL,
+ RELOPT_TYPE_TERNARY, /* on, off, unset */
RELOPT_TYPE_INT,
RELOPT_TYPE_REAL,
RELOPT_TYPE_ENUM,
@@ -80,6 +81,7 @@ typedef struct relopt_value
union
{
bool bool_val;
+ ternary ternary_val;
int int_val;
double real_val;
int enum_val;
@@ -94,6 +96,12 @@ typedef struct relopt_bool
bool default_val;
} relopt_bool;
+typedef struct relopt_rernary
+{
+ relopt_gen gen;
+ int default_val;
+} relopt_ternary;
+
typedef struct relopt_int
{
relopt_gen gen;
@@ -152,19 +160,6 @@ typedef struct
const char *optname; /* option's name */
relopt_type opttype; /* option's datatype */
int offset; /* offset of field in result struct */
-
- /*
- * isset_offset is an optional offset of a field in the result struct that
- * stores whether the option is explicitly set for the relation or if it
- * just picked up the default value. In most cases, this can be
- * accomplished by giving the reloption a special out-of-range default
- * value (e.g., some integer reloptions use -2), but this isn't always
- * possible. For example, a Boolean reloption cannot be given an
- * out-of-range default, so we need another way to discover the source of
- * its value. This offset is only used if given a value greater than
- * zero.
- */
- int isset_offset;
} relopt_parse_elt;
/* Local reloption definition */
@@ -195,6 +190,8 @@ typedef struct local_relopts
extern relopt_kind add_reloption_kind(void);
extern void add_bool_reloption(bits32 kinds, const char *name, const char *desc,
bool default_val, LOCKMODE lockmode);
+extern void add_ternary_reloption(bits32 kinds, const char *name,
+ const char *desc, int default_val, LOCKMODE lockmode);
extern void add_int_reloption(bits32 kinds, const char *name, const char *desc,
int default_val, int min_val, int max_val,
LOCKMODE lockmode);
@@ -214,6 +211,9 @@ extern void register_reloptions_validator(local_relopts *relopts,
extern void add_local_bool_reloption(local_relopts *relopts, const char *name,
const char *desc, bool default_val,
int offset);
+extern void add_local_ternary_reloption(local_relopts *relopts,
+ const char *name, const char *desc,
+ ternary default_val, int offset);
extern void add_local_int_reloption(local_relopts *relopts, const char *name,
const char *desc, int default_val,
int min_val, int max_val, int offset);
diff --git a/src/include/c.h b/src/include/c.h
index 39022f8a9d..9c59ccccf8 100644
--- a/src/include/c.h
+++ b/src/include/c.h
@@ -488,6 +488,22 @@ typedef void (*pg_funcptr_t) (void);
#include <stdbool.h>
+/*
+ * ternary
+ * Boolean value with an extrea "unset" option
+ *
+ * Ternary data type is used in relation options that can be "true", "false" or
+ * "unset". Since relation options are used deep inside the PostgreSQL code,
+ * this type is declared globally.
+*/
+
+typedef enum ternary
+{
+ TERNARY_FALSE = 0,
+ TERNARY_TRUE = 1,
+ TERNARY_UNSET = -1
+} ternary;
+
/* ----------------------------------------------------------------
* Section 3: standard system types
diff --git a/src/include/utils/rel.h b/src/include/utils/rel.h
index b552359915..7346d618f9 100644
--- a/src/include/utils/rel.h
+++ b/src/include/utils/rel.h
@@ -346,8 +346,7 @@ typedef struct StdRdOptions
bool user_catalog_table; /* use as an additional catalog relation */
int parallel_workers; /* max number of parallel workers */
StdRdOptIndexCleanup vacuum_index_cleanup; /* controls index vacuuming */
- bool vacuum_truncate; /* enables vacuum to truncate a relation */
- bool vacuum_truncate_set; /* whether vacuum_truncate is set */
+ ternary vacuum_truncate; /* enables vacuum to truncate a relation */
/*
* Fraction of pages in a relation that vacuum can eagerly scan and fail
--
2.39.2
--nextPart5129932.5fSG56mABF
Content-Disposition: attachment;
filename="v2-0003-Add-alias-to-be-used-as-unset-state.patch"
Content-Transfer-Encoding: 7Bit
Content-Type: text/x-patch; charset="unicode-2-0-utf-8";
name="v2-0003-Add-alias-to-be-used-as-unset-state.patch"
^ permalink raw reply [nested|flat] 868+ messages in thread
* [PATCH v2a 2/4] Introduce ternary reloptions
@ 2025-09-04 14:41 Nikolay Shaplov <[email protected]>
0 siblings, 0 replies; 868+ messages in thread
From: Nikolay Shaplov @ 2025-09-04 14:41 UTC (permalink / raw)
Introduce ternary reloption as a replacement for current `vacuum_truncate`
implementation. Remove `vacuum_truncate_set` additional flag and using
`TERNARY_UNSET` value instead.
---
src/backend/access/common/reloptions.c | 129 ++++++++++++++++++++-----
src/backend/commands/vacuum.c | 4 +-
src/include/access/reloptions.h | 26 ++---
src/include/c.h | 16 +++
src/include/utils/rel.h | 3 +-
5 files changed, 135 insertions(+), 43 deletions(-)
diff --git a/src/backend/access/common/reloptions.c b/src/backend/access/common/reloptions.c
index 0af3fea68f..f543f11001 100644
--- a/src/backend/access/common/reloptions.c
+++ b/src/backend/access/common/reloptions.c
@@ -40,9 +40,9 @@
*
* To add an option:
*
- * (i) decide on a type (bool, integer, real, enum, string), name, default
- * value, upper and lower bounds (if applicable); for strings, consider a
- * validation routine.
+ * (i) decide on a type (bool, ternary, integer, real, enum, string), name,
+ * default value, upper and lower bounds (if applicable); for strings,
+ * consider a validation routine.
* (ii) add a record below (or use add_<type>_reloption).
* (iii) add it to the appropriate options struct (perhaps StdRdOptions)
* (iv) add it to the appropriate handling routine (perhaps
@@ -147,15 +147,6 @@ static relopt_bool boolRelOpts[] =
},
false
},
- {
- {
- "vacuum_truncate",
- "Enables vacuum to truncate empty pages at the end of this table",
- RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
- ShareUpdateExclusiveLock
- },
- true
- },
{
{
"deduplicate_items",
@@ -170,6 +161,21 @@ static relopt_bool boolRelOpts[] =
{{NULL}}
};
+static relopt_ternary ternaryRelOpts[] =
+{
+ {
+ {
+ "vacuum_truncate",
+ "Enables vacuum to truncate empty pages at the end of this table",
+ RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
+ ShareUpdateExclusiveLock
+ },
+ TERNARY_UNSET
+ },
+ /* list terminator */
+ {{NULL}}
+};
+
static relopt_int intRelOpts[] =
{
{
@@ -600,6 +606,13 @@ initialize_reloptions(void)
boolRelOpts[i].gen.lockmode));
j++;
}
+ for (i = 0; ternaryRelOpts[i].gen.name; i++)
+ {
+ Assert(DoLockModesConflict(ternaryRelOpts[i].gen.lockmode,
+ ternaryRelOpts[i].gen.lockmode));
+ j++;
+ }
+
for (i = 0; intRelOpts[i].gen.name; i++)
{
Assert(DoLockModesConflict(intRelOpts[i].gen.lockmode,
@@ -640,6 +653,14 @@ initialize_reloptions(void)
j++;
}
+ for (i = 0; ternaryRelOpts[i].gen.name; i++)
+ {
+ relOpts[j] = &ternaryRelOpts[i].gen;
+ relOpts[j]->type = RELOPT_TYPE_TERNARY;
+ relOpts[j]->namelen = strlen(relOpts[j]->name);
+ j++;
+ }
+
for (i = 0; intRelOpts[i].gen.name; i++)
{
relOpts[j] = &intRelOpts[i].gen;
@@ -800,6 +821,9 @@ allocate_reloption(bits32 kinds, int type, const char *name, const char *desc,
case RELOPT_TYPE_BOOL:
size = sizeof(relopt_bool);
break;
+ case RELOPT_TYPE_TERNARY:
+ size = sizeof(relopt_ternary);
+ break;
case RELOPT_TYPE_INT:
size = sizeof(relopt_int);
break;
@@ -883,6 +907,54 @@ add_local_bool_reloption(local_relopts *relopts, const char *name,
add_local_reloption(relopts, (relopt_gen *) newoption, offset);
}
+/*
+ * init_ternary_reloption
+ * Allocate and initialize a new ternary reloption
+ */
+static relopt_ternary *
+init_ternary_reloption(bits32 kinds, const char *name, const char *desc,
+ ternary default_val, LOCKMODE lockmode)
+{
+ relopt_ternary *newoption;
+
+ newoption = (relopt_ternary *) allocate_reloption(kinds,
+ RELOPT_TYPE_TERNARY, name, desc, lockmode);
+ newoption->default_val = default_val;
+
+ return newoption;
+}
+
+/*
+ * add_ternary_reloption
+ * Add a new ternary reloption
+ */
+void
+add_ternary_reloption(bits32 kinds, const char *name, const char *desc,
+ ternary default_val, LOCKMODE lockmode)
+{
+ relopt_ternary *newoption = init_ternary_reloption(kinds, name, desc,
+ default_val, lockmode);
+
+ add_reloption((relopt_gen *) newoption);
+}
+
+/*
+ * add_local_ternary_reloption
+ * Add a new ternary local reloption
+ *
+ * 'offset' is offset of ternary-typed field.
+ */
+void
+add_local_ternary_reloption(local_relopts *relopts, const char *name,
+ const char *desc, ternary default_val,
+ int offset)
+{
+ relopt_ternary *newoption = init_ternary_reloption(RELOPT_KIND_LOCAL,
+ name, desc,
+ default_val, 0);
+
+ add_local_reloption(relopts, (relopt_gen *) newoption, offset);
+}
/*
* init_real_reloption
@@ -1617,6 +1689,18 @@ parse_one_reloption(relopt_value *option, char *text_str, int text_len,
option->gen->name, value)));
}
break;
+ case RELOPT_TYPE_TERNARY:
+ {
+ bool b;
+ parsed = parse_bool(value, &b);
+ option->values.ternary_val = b ? TERNARY_TRUE : TERNARY_FALSE;
+ if (validate && !parsed)
+ ereport(ERROR,
+ (errcode(ERRCODE_INVALID_PARAMETER_VALUE),
+ errmsg("invalid value for ternary option \"%s\": %s",
+ option->gen->name, value)));
+ }
+ break;
case RELOPT_TYPE_INT:
{
relopt_int *optint = (relopt_int *) option->gen;
@@ -1780,17 +1864,6 @@ fillRelOptions(void *rdopts, Size basesize,
char *itempos = ((char *) rdopts) + elems[j].offset;
char *string_val;
- /*
- * If isset_offset is provided, store whether the reloption is
- * set there.
- */
- if (elems[j].isset_offset > 0)
- {
- char *setpos = ((char *) rdopts) + elems[j].isset_offset;
-
- *(bool *) setpos = options[i].isset;
- }
-
switch (options[i].gen->type)
{
case RELOPT_TYPE_BOOL:
@@ -1798,6 +1871,11 @@ fillRelOptions(void *rdopts, Size basesize,
options[i].values.bool_val :
((relopt_bool *) options[i].gen)->default_val;
break;
+ case RELOPT_TYPE_TERNARY:
+ *(ternary *) itempos = options[i].isset ?
+ options[i].values.ternary_val :
+ ((relopt_ternary *) options[i].gen)->default_val;
+ break;
case RELOPT_TYPE_INT:
*(int *) itempos = options[i].isset ?
options[i].values.int_val :
@@ -1912,8 +1990,8 @@ default_reloptions(Datum reloptions, bool validate, relopt_kind kind)
offsetof(StdRdOptions, parallel_workers)},
{"vacuum_index_cleanup", RELOPT_TYPE_ENUM,
offsetof(StdRdOptions, vacuum_index_cleanup)},
- {"vacuum_truncate", RELOPT_TYPE_BOOL,
- offsetof(StdRdOptions, vacuum_truncate), offsetof(StdRdOptions, vacuum_truncate_set)},
+ {"vacuum_truncate", RELOPT_TYPE_TERNARY,
+ offsetof(StdRdOptions, vacuum_truncate)},
{"vacuum_max_eager_freeze_failure_rate", RELOPT_TYPE_REAL,
offsetof(StdRdOptions, vacuum_max_eager_freeze_failure_rate)}
};
@@ -1993,7 +2071,6 @@ build_local_reloptions(local_relopts *relopts, Datum options, bool validate)
elems[i].optname = opt->option->name;
elems[i].opttype = opt->option->type;
elems[i].offset = opt->offset;
- elems[i].isset_offset = 0; /* not supported for local relopts yet */
i++;
}
diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c
index 733ef40ae7..977babff54 100644
--- a/src/backend/commands/vacuum.c
+++ b/src/backend/commands/vacuum.c
@@ -2221,9 +2221,9 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams params,
{
StdRdOptions *opts = (StdRdOptions *) rel->rd_options;
- if (opts && opts->vacuum_truncate_set)
+ if (opts && opts->vacuum_truncate != TERNARY_UNSET)
{
- if (opts->vacuum_truncate)
+ if (opts->vacuum_truncate == TERNARY_TRUE)
params.truncate = VACOPTVALUE_ENABLED;
else
params.truncate = VACOPTVALUE_DISABLED;
diff --git a/src/include/access/reloptions.h b/src/include/access/reloptions.h
index a604a4702c..a436697658 100644
--- a/src/include/access/reloptions.h
+++ b/src/include/access/reloptions.h
@@ -29,6 +29,7 @@
typedef enum relopt_type
{
RELOPT_TYPE_BOOL,
+ RELOPT_TYPE_TERNARY, /* on, off, unset */
RELOPT_TYPE_INT,
RELOPT_TYPE_REAL,
RELOPT_TYPE_ENUM,
@@ -80,6 +81,7 @@ typedef struct relopt_value
union
{
bool bool_val;
+ ternary ternary_val;
int int_val;
double real_val;
int enum_val;
@@ -94,6 +96,12 @@ typedef struct relopt_bool
bool default_val;
} relopt_bool;
+typedef struct relopt_rernary
+{
+ relopt_gen gen;
+ int default_val;
+} relopt_ternary;
+
typedef struct relopt_int
{
relopt_gen gen;
@@ -152,19 +160,6 @@ typedef struct
const char *optname; /* option's name */
relopt_type opttype; /* option's datatype */
int offset; /* offset of field in result struct */
-
- /*
- * isset_offset is an optional offset of a field in the result struct that
- * stores whether the option is explicitly set for the relation or if it
- * just picked up the default value. In most cases, this can be
- * accomplished by giving the reloption a special out-of-range default
- * value (e.g., some integer reloptions use -2), but this isn't always
- * possible. For example, a Boolean reloption cannot be given an
- * out-of-range default, so we need another way to discover the source of
- * its value. This offset is only used if given a value greater than
- * zero.
- */
- int isset_offset;
} relopt_parse_elt;
/* Local reloption definition */
@@ -195,6 +190,8 @@ typedef struct local_relopts
extern relopt_kind add_reloption_kind(void);
extern void add_bool_reloption(bits32 kinds, const char *name, const char *desc,
bool default_val, LOCKMODE lockmode);
+extern void add_ternary_reloption(bits32 kinds, const char *name,
+ const char *desc, int default_val, LOCKMODE lockmode);
extern void add_int_reloption(bits32 kinds, const char *name, const char *desc,
int default_val, int min_val, int max_val,
LOCKMODE lockmode);
@@ -214,6 +211,9 @@ extern void register_reloptions_validator(local_relopts *relopts,
extern void add_local_bool_reloption(local_relopts *relopts, const char *name,
const char *desc, bool default_val,
int offset);
+extern void add_local_ternary_reloption(local_relopts *relopts,
+ const char *name, const char *desc,
+ ternary default_val, int offset);
extern void add_local_int_reloption(local_relopts *relopts, const char *name,
const char *desc, int default_val,
int min_val, int max_val, int offset);
diff --git a/src/include/c.h b/src/include/c.h
index 39022f8a9d..9c59ccccf8 100644
--- a/src/include/c.h
+++ b/src/include/c.h
@@ -488,6 +488,22 @@ typedef void (*pg_funcptr_t) (void);
#include <stdbool.h>
+/*
+ * ternary
+ * Boolean value with an extrea "unset" option
+ *
+ * Ternary data type is used in relation options that can be "true", "false" or
+ * "unset". Since relation options are used deep inside the PostgreSQL code,
+ * this type is declared globally.
+*/
+
+typedef enum ternary
+{
+ TERNARY_FALSE = 0,
+ TERNARY_TRUE = 1,
+ TERNARY_UNSET = -1
+} ternary;
+
/* ----------------------------------------------------------------
* Section 3: standard system types
diff --git a/src/include/utils/rel.h b/src/include/utils/rel.h
index b552359915..7346d618f9 100644
--- a/src/include/utils/rel.h
+++ b/src/include/utils/rel.h
@@ -346,8 +346,7 @@ typedef struct StdRdOptions
bool user_catalog_table; /* use as an additional catalog relation */
int parallel_workers; /* max number of parallel workers */
StdRdOptIndexCleanup vacuum_index_cleanup; /* controls index vacuuming */
- bool vacuum_truncate; /* enables vacuum to truncate a relation */
- bool vacuum_truncate_set; /* whether vacuum_truncate is set */
+ ternary vacuum_truncate; /* enables vacuum to truncate a relation */
/*
* Fraction of pages in a relation that vacuum can eagerly scan and fail
--
2.39.2
--nextPart4155600.3ZeAukHxDK
Content-Disposition: attachment;
filename="v2a-0003-Add-alias-to-be-used-as-unset-state.patch"
Content-Transfer-Encoding: 7Bit
Content-Type: text/x-patch; charset="unicode-2-0-utf-8";
name="v2a-0003-Add-alias-to-be-used-as-unset-state.patch"
^ permalink raw reply [nested|flat] 868+ messages in thread
* [PATCH v2a 2/4] Introduce ternary reloptions
@ 2025-09-04 14:41 Nikolay Shaplov <[email protected]>
0 siblings, 0 replies; 868+ messages in thread
From: Nikolay Shaplov @ 2025-09-04 14:41 UTC (permalink / raw)
Introduce ternary reloption as a replacement for current `vacuum_truncate`
implementation. Remove `vacuum_truncate_set` additional flag and using
`TERNARY_UNSET` value instead.
---
src/backend/access/common/reloptions.c | 129 ++++++++++++++++++++-----
src/backend/commands/vacuum.c | 4 +-
src/include/access/reloptions.h | 26 ++---
src/include/c.h | 16 +++
src/include/utils/rel.h | 3 +-
5 files changed, 135 insertions(+), 43 deletions(-)
diff --git a/src/backend/access/common/reloptions.c b/src/backend/access/common/reloptions.c
index 0af3fea68f..f543f11001 100644
--- a/src/backend/access/common/reloptions.c
+++ b/src/backend/access/common/reloptions.c
@@ -40,9 +40,9 @@
*
* To add an option:
*
- * (i) decide on a type (bool, integer, real, enum, string), name, default
- * value, upper and lower bounds (if applicable); for strings, consider a
- * validation routine.
+ * (i) decide on a type (bool, ternary, integer, real, enum, string), name,
+ * default value, upper and lower bounds (if applicable); for strings,
+ * consider a validation routine.
* (ii) add a record below (or use add_<type>_reloption).
* (iii) add it to the appropriate options struct (perhaps StdRdOptions)
* (iv) add it to the appropriate handling routine (perhaps
@@ -147,15 +147,6 @@ static relopt_bool boolRelOpts[] =
},
false
},
- {
- {
- "vacuum_truncate",
- "Enables vacuum to truncate empty pages at the end of this table",
- RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
- ShareUpdateExclusiveLock
- },
- true
- },
{
{
"deduplicate_items",
@@ -170,6 +161,21 @@ static relopt_bool boolRelOpts[] =
{{NULL}}
};
+static relopt_ternary ternaryRelOpts[] =
+{
+ {
+ {
+ "vacuum_truncate",
+ "Enables vacuum to truncate empty pages at the end of this table",
+ RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
+ ShareUpdateExclusiveLock
+ },
+ TERNARY_UNSET
+ },
+ /* list terminator */
+ {{NULL}}
+};
+
static relopt_int intRelOpts[] =
{
{
@@ -600,6 +606,13 @@ initialize_reloptions(void)
boolRelOpts[i].gen.lockmode));
j++;
}
+ for (i = 0; ternaryRelOpts[i].gen.name; i++)
+ {
+ Assert(DoLockModesConflict(ternaryRelOpts[i].gen.lockmode,
+ ternaryRelOpts[i].gen.lockmode));
+ j++;
+ }
+
for (i = 0; intRelOpts[i].gen.name; i++)
{
Assert(DoLockModesConflict(intRelOpts[i].gen.lockmode,
@@ -640,6 +653,14 @@ initialize_reloptions(void)
j++;
}
+ for (i = 0; ternaryRelOpts[i].gen.name; i++)
+ {
+ relOpts[j] = &ternaryRelOpts[i].gen;
+ relOpts[j]->type = RELOPT_TYPE_TERNARY;
+ relOpts[j]->namelen = strlen(relOpts[j]->name);
+ j++;
+ }
+
for (i = 0; intRelOpts[i].gen.name; i++)
{
relOpts[j] = &intRelOpts[i].gen;
@@ -800,6 +821,9 @@ allocate_reloption(bits32 kinds, int type, const char *name, const char *desc,
case RELOPT_TYPE_BOOL:
size = sizeof(relopt_bool);
break;
+ case RELOPT_TYPE_TERNARY:
+ size = sizeof(relopt_ternary);
+ break;
case RELOPT_TYPE_INT:
size = sizeof(relopt_int);
break;
@@ -883,6 +907,54 @@ add_local_bool_reloption(local_relopts *relopts, const char *name,
add_local_reloption(relopts, (relopt_gen *) newoption, offset);
}
+/*
+ * init_ternary_reloption
+ * Allocate and initialize a new ternary reloption
+ */
+static relopt_ternary *
+init_ternary_reloption(bits32 kinds, const char *name, const char *desc,
+ ternary default_val, LOCKMODE lockmode)
+{
+ relopt_ternary *newoption;
+
+ newoption = (relopt_ternary *) allocate_reloption(kinds,
+ RELOPT_TYPE_TERNARY, name, desc, lockmode);
+ newoption->default_val = default_val;
+
+ return newoption;
+}
+
+/*
+ * add_ternary_reloption
+ * Add a new ternary reloption
+ */
+void
+add_ternary_reloption(bits32 kinds, const char *name, const char *desc,
+ ternary default_val, LOCKMODE lockmode)
+{
+ relopt_ternary *newoption = init_ternary_reloption(kinds, name, desc,
+ default_val, lockmode);
+
+ add_reloption((relopt_gen *) newoption);
+}
+
+/*
+ * add_local_ternary_reloption
+ * Add a new ternary local reloption
+ *
+ * 'offset' is offset of ternary-typed field.
+ */
+void
+add_local_ternary_reloption(local_relopts *relopts, const char *name,
+ const char *desc, ternary default_val,
+ int offset)
+{
+ relopt_ternary *newoption = init_ternary_reloption(RELOPT_KIND_LOCAL,
+ name, desc,
+ default_val, 0);
+
+ add_local_reloption(relopts, (relopt_gen *) newoption, offset);
+}
/*
* init_real_reloption
@@ -1617,6 +1689,18 @@ parse_one_reloption(relopt_value *option, char *text_str, int text_len,
option->gen->name, value)));
}
break;
+ case RELOPT_TYPE_TERNARY:
+ {
+ bool b;
+ parsed = parse_bool(value, &b);
+ option->values.ternary_val = b ? TERNARY_TRUE : TERNARY_FALSE;
+ if (validate && !parsed)
+ ereport(ERROR,
+ (errcode(ERRCODE_INVALID_PARAMETER_VALUE),
+ errmsg("invalid value for ternary option \"%s\": %s",
+ option->gen->name, value)));
+ }
+ break;
case RELOPT_TYPE_INT:
{
relopt_int *optint = (relopt_int *) option->gen;
@@ -1780,17 +1864,6 @@ fillRelOptions(void *rdopts, Size basesize,
char *itempos = ((char *) rdopts) + elems[j].offset;
char *string_val;
- /*
- * If isset_offset is provided, store whether the reloption is
- * set there.
- */
- if (elems[j].isset_offset > 0)
- {
- char *setpos = ((char *) rdopts) + elems[j].isset_offset;
-
- *(bool *) setpos = options[i].isset;
- }
-
switch (options[i].gen->type)
{
case RELOPT_TYPE_BOOL:
@@ -1798,6 +1871,11 @@ fillRelOptions(void *rdopts, Size basesize,
options[i].values.bool_val :
((relopt_bool *) options[i].gen)->default_val;
break;
+ case RELOPT_TYPE_TERNARY:
+ *(ternary *) itempos = options[i].isset ?
+ options[i].values.ternary_val :
+ ((relopt_ternary *) options[i].gen)->default_val;
+ break;
case RELOPT_TYPE_INT:
*(int *) itempos = options[i].isset ?
options[i].values.int_val :
@@ -1912,8 +1990,8 @@ default_reloptions(Datum reloptions, bool validate, relopt_kind kind)
offsetof(StdRdOptions, parallel_workers)},
{"vacuum_index_cleanup", RELOPT_TYPE_ENUM,
offsetof(StdRdOptions, vacuum_index_cleanup)},
- {"vacuum_truncate", RELOPT_TYPE_BOOL,
- offsetof(StdRdOptions, vacuum_truncate), offsetof(StdRdOptions, vacuum_truncate_set)},
+ {"vacuum_truncate", RELOPT_TYPE_TERNARY,
+ offsetof(StdRdOptions, vacuum_truncate)},
{"vacuum_max_eager_freeze_failure_rate", RELOPT_TYPE_REAL,
offsetof(StdRdOptions, vacuum_max_eager_freeze_failure_rate)}
};
@@ -1993,7 +2071,6 @@ build_local_reloptions(local_relopts *relopts, Datum options, bool validate)
elems[i].optname = opt->option->name;
elems[i].opttype = opt->option->type;
elems[i].offset = opt->offset;
- elems[i].isset_offset = 0; /* not supported for local relopts yet */
i++;
}
diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c
index 733ef40ae7..977babff54 100644
--- a/src/backend/commands/vacuum.c
+++ b/src/backend/commands/vacuum.c
@@ -2221,9 +2221,9 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams params,
{
StdRdOptions *opts = (StdRdOptions *) rel->rd_options;
- if (opts && opts->vacuum_truncate_set)
+ if (opts && opts->vacuum_truncate != TERNARY_UNSET)
{
- if (opts->vacuum_truncate)
+ if (opts->vacuum_truncate == TERNARY_TRUE)
params.truncate = VACOPTVALUE_ENABLED;
else
params.truncate = VACOPTVALUE_DISABLED;
diff --git a/src/include/access/reloptions.h b/src/include/access/reloptions.h
index a604a4702c..a436697658 100644
--- a/src/include/access/reloptions.h
+++ b/src/include/access/reloptions.h
@@ -29,6 +29,7 @@
typedef enum relopt_type
{
RELOPT_TYPE_BOOL,
+ RELOPT_TYPE_TERNARY, /* on, off, unset */
RELOPT_TYPE_INT,
RELOPT_TYPE_REAL,
RELOPT_TYPE_ENUM,
@@ -80,6 +81,7 @@ typedef struct relopt_value
union
{
bool bool_val;
+ ternary ternary_val;
int int_val;
double real_val;
int enum_val;
@@ -94,6 +96,12 @@ typedef struct relopt_bool
bool default_val;
} relopt_bool;
+typedef struct relopt_rernary
+{
+ relopt_gen gen;
+ int default_val;
+} relopt_ternary;
+
typedef struct relopt_int
{
relopt_gen gen;
@@ -152,19 +160,6 @@ typedef struct
const char *optname; /* option's name */
relopt_type opttype; /* option's datatype */
int offset; /* offset of field in result struct */
-
- /*
- * isset_offset is an optional offset of a field in the result struct that
- * stores whether the option is explicitly set for the relation or if it
- * just picked up the default value. In most cases, this can be
- * accomplished by giving the reloption a special out-of-range default
- * value (e.g., some integer reloptions use -2), but this isn't always
- * possible. For example, a Boolean reloption cannot be given an
- * out-of-range default, so we need another way to discover the source of
- * its value. This offset is only used if given a value greater than
- * zero.
- */
- int isset_offset;
} relopt_parse_elt;
/* Local reloption definition */
@@ -195,6 +190,8 @@ typedef struct local_relopts
extern relopt_kind add_reloption_kind(void);
extern void add_bool_reloption(bits32 kinds, const char *name, const char *desc,
bool default_val, LOCKMODE lockmode);
+extern void add_ternary_reloption(bits32 kinds, const char *name,
+ const char *desc, int default_val, LOCKMODE lockmode);
extern void add_int_reloption(bits32 kinds, const char *name, const char *desc,
int default_val, int min_val, int max_val,
LOCKMODE lockmode);
@@ -214,6 +211,9 @@ extern void register_reloptions_validator(local_relopts *relopts,
extern void add_local_bool_reloption(local_relopts *relopts, const char *name,
const char *desc, bool default_val,
int offset);
+extern void add_local_ternary_reloption(local_relopts *relopts,
+ const char *name, const char *desc,
+ ternary default_val, int offset);
extern void add_local_int_reloption(local_relopts *relopts, const char *name,
const char *desc, int default_val,
int min_val, int max_val, int offset);
diff --git a/src/include/c.h b/src/include/c.h
index 39022f8a9d..9c59ccccf8 100644
--- a/src/include/c.h
+++ b/src/include/c.h
@@ -488,6 +488,22 @@ typedef void (*pg_funcptr_t) (void);
#include <stdbool.h>
+/*
+ * ternary
+ * Boolean value with an extrea "unset" option
+ *
+ * Ternary data type is used in relation options that can be "true", "false" or
+ * "unset". Since relation options are used deep inside the PostgreSQL code,
+ * this type is declared globally.
+*/
+
+typedef enum ternary
+{
+ TERNARY_FALSE = 0,
+ TERNARY_TRUE = 1,
+ TERNARY_UNSET = -1
+} ternary;
+
/* ----------------------------------------------------------------
* Section 3: standard system types
diff --git a/src/include/utils/rel.h b/src/include/utils/rel.h
index b552359915..7346d618f9 100644
--- a/src/include/utils/rel.h
+++ b/src/include/utils/rel.h
@@ -346,8 +346,7 @@ typedef struct StdRdOptions
bool user_catalog_table; /* use as an additional catalog relation */
int parallel_workers; /* max number of parallel workers */
StdRdOptIndexCleanup vacuum_index_cleanup; /* controls index vacuuming */
- bool vacuum_truncate; /* enables vacuum to truncate a relation */
- bool vacuum_truncate_set; /* whether vacuum_truncate is set */
+ ternary vacuum_truncate; /* enables vacuum to truncate a relation */
/*
* Fraction of pages in a relation that vacuum can eagerly scan and fail
--
2.39.2
--nextPart4155600.3ZeAukHxDK
Content-Disposition: attachment;
filename="v2a-0003-Add-alias-to-be-used-as-unset-state.patch"
Content-Transfer-Encoding: 7Bit
Content-Type: text/x-patch; charset="unicode-2-0-utf-8";
name="v2a-0003-Add-alias-to-be-used-as-unset-state.patch"
^ permalink raw reply [nested|flat] 868+ messages in thread
* [PATCH v2a 2/4] Introduce ternary reloptions
@ 2025-09-04 14:41 Nikolay Shaplov <[email protected]>
0 siblings, 0 replies; 868+ messages in thread
From: Nikolay Shaplov @ 2025-09-04 14:41 UTC (permalink / raw)
Introduce ternary reloption as a replacement for current `vacuum_truncate`
implementation. Remove `vacuum_truncate_set` additional flag and using
`TERNARY_UNSET` value instead.
---
src/backend/access/common/reloptions.c | 129 ++++++++++++++++++++-----
src/backend/commands/vacuum.c | 4 +-
src/include/access/reloptions.h | 26 ++---
src/include/c.h | 16 +++
src/include/utils/rel.h | 3 +-
5 files changed, 135 insertions(+), 43 deletions(-)
diff --git a/src/backend/access/common/reloptions.c b/src/backend/access/common/reloptions.c
index 0af3fea68f..f543f11001 100644
--- a/src/backend/access/common/reloptions.c
+++ b/src/backend/access/common/reloptions.c
@@ -40,9 +40,9 @@
*
* To add an option:
*
- * (i) decide on a type (bool, integer, real, enum, string), name, default
- * value, upper and lower bounds (if applicable); for strings, consider a
- * validation routine.
+ * (i) decide on a type (bool, ternary, integer, real, enum, string), name,
+ * default value, upper and lower bounds (if applicable); for strings,
+ * consider a validation routine.
* (ii) add a record below (or use add_<type>_reloption).
* (iii) add it to the appropriate options struct (perhaps StdRdOptions)
* (iv) add it to the appropriate handling routine (perhaps
@@ -147,15 +147,6 @@ static relopt_bool boolRelOpts[] =
},
false
},
- {
- {
- "vacuum_truncate",
- "Enables vacuum to truncate empty pages at the end of this table",
- RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
- ShareUpdateExclusiveLock
- },
- true
- },
{
{
"deduplicate_items",
@@ -170,6 +161,21 @@ static relopt_bool boolRelOpts[] =
{{NULL}}
};
+static relopt_ternary ternaryRelOpts[] =
+{
+ {
+ {
+ "vacuum_truncate",
+ "Enables vacuum to truncate empty pages at the end of this table",
+ RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
+ ShareUpdateExclusiveLock
+ },
+ TERNARY_UNSET
+ },
+ /* list terminator */
+ {{NULL}}
+};
+
static relopt_int intRelOpts[] =
{
{
@@ -600,6 +606,13 @@ initialize_reloptions(void)
boolRelOpts[i].gen.lockmode));
j++;
}
+ for (i = 0; ternaryRelOpts[i].gen.name; i++)
+ {
+ Assert(DoLockModesConflict(ternaryRelOpts[i].gen.lockmode,
+ ternaryRelOpts[i].gen.lockmode));
+ j++;
+ }
+
for (i = 0; intRelOpts[i].gen.name; i++)
{
Assert(DoLockModesConflict(intRelOpts[i].gen.lockmode,
@@ -640,6 +653,14 @@ initialize_reloptions(void)
j++;
}
+ for (i = 0; ternaryRelOpts[i].gen.name; i++)
+ {
+ relOpts[j] = &ternaryRelOpts[i].gen;
+ relOpts[j]->type = RELOPT_TYPE_TERNARY;
+ relOpts[j]->namelen = strlen(relOpts[j]->name);
+ j++;
+ }
+
for (i = 0; intRelOpts[i].gen.name; i++)
{
relOpts[j] = &intRelOpts[i].gen;
@@ -800,6 +821,9 @@ allocate_reloption(bits32 kinds, int type, const char *name, const char *desc,
case RELOPT_TYPE_BOOL:
size = sizeof(relopt_bool);
break;
+ case RELOPT_TYPE_TERNARY:
+ size = sizeof(relopt_ternary);
+ break;
case RELOPT_TYPE_INT:
size = sizeof(relopt_int);
break;
@@ -883,6 +907,54 @@ add_local_bool_reloption(local_relopts *relopts, const char *name,
add_local_reloption(relopts, (relopt_gen *) newoption, offset);
}
+/*
+ * init_ternary_reloption
+ * Allocate and initialize a new ternary reloption
+ */
+static relopt_ternary *
+init_ternary_reloption(bits32 kinds, const char *name, const char *desc,
+ ternary default_val, LOCKMODE lockmode)
+{
+ relopt_ternary *newoption;
+
+ newoption = (relopt_ternary *) allocate_reloption(kinds,
+ RELOPT_TYPE_TERNARY, name, desc, lockmode);
+ newoption->default_val = default_val;
+
+ return newoption;
+}
+
+/*
+ * add_ternary_reloption
+ * Add a new ternary reloption
+ */
+void
+add_ternary_reloption(bits32 kinds, const char *name, const char *desc,
+ ternary default_val, LOCKMODE lockmode)
+{
+ relopt_ternary *newoption = init_ternary_reloption(kinds, name, desc,
+ default_val, lockmode);
+
+ add_reloption((relopt_gen *) newoption);
+}
+
+/*
+ * add_local_ternary_reloption
+ * Add a new ternary local reloption
+ *
+ * 'offset' is offset of ternary-typed field.
+ */
+void
+add_local_ternary_reloption(local_relopts *relopts, const char *name,
+ const char *desc, ternary default_val,
+ int offset)
+{
+ relopt_ternary *newoption = init_ternary_reloption(RELOPT_KIND_LOCAL,
+ name, desc,
+ default_val, 0);
+
+ add_local_reloption(relopts, (relopt_gen *) newoption, offset);
+}
/*
* init_real_reloption
@@ -1617,6 +1689,18 @@ parse_one_reloption(relopt_value *option, char *text_str, int text_len,
option->gen->name, value)));
}
break;
+ case RELOPT_TYPE_TERNARY:
+ {
+ bool b;
+ parsed = parse_bool(value, &b);
+ option->values.ternary_val = b ? TERNARY_TRUE : TERNARY_FALSE;
+ if (validate && !parsed)
+ ereport(ERROR,
+ (errcode(ERRCODE_INVALID_PARAMETER_VALUE),
+ errmsg("invalid value for ternary option \"%s\": %s",
+ option->gen->name, value)));
+ }
+ break;
case RELOPT_TYPE_INT:
{
relopt_int *optint = (relopt_int *) option->gen;
@@ -1780,17 +1864,6 @@ fillRelOptions(void *rdopts, Size basesize,
char *itempos = ((char *) rdopts) + elems[j].offset;
char *string_val;
- /*
- * If isset_offset is provided, store whether the reloption is
- * set there.
- */
- if (elems[j].isset_offset > 0)
- {
- char *setpos = ((char *) rdopts) + elems[j].isset_offset;
-
- *(bool *) setpos = options[i].isset;
- }
-
switch (options[i].gen->type)
{
case RELOPT_TYPE_BOOL:
@@ -1798,6 +1871,11 @@ fillRelOptions(void *rdopts, Size basesize,
options[i].values.bool_val :
((relopt_bool *) options[i].gen)->default_val;
break;
+ case RELOPT_TYPE_TERNARY:
+ *(ternary *) itempos = options[i].isset ?
+ options[i].values.ternary_val :
+ ((relopt_ternary *) options[i].gen)->default_val;
+ break;
case RELOPT_TYPE_INT:
*(int *) itempos = options[i].isset ?
options[i].values.int_val :
@@ -1912,8 +1990,8 @@ default_reloptions(Datum reloptions, bool validate, relopt_kind kind)
offsetof(StdRdOptions, parallel_workers)},
{"vacuum_index_cleanup", RELOPT_TYPE_ENUM,
offsetof(StdRdOptions, vacuum_index_cleanup)},
- {"vacuum_truncate", RELOPT_TYPE_BOOL,
- offsetof(StdRdOptions, vacuum_truncate), offsetof(StdRdOptions, vacuum_truncate_set)},
+ {"vacuum_truncate", RELOPT_TYPE_TERNARY,
+ offsetof(StdRdOptions, vacuum_truncate)},
{"vacuum_max_eager_freeze_failure_rate", RELOPT_TYPE_REAL,
offsetof(StdRdOptions, vacuum_max_eager_freeze_failure_rate)}
};
@@ -1993,7 +2071,6 @@ build_local_reloptions(local_relopts *relopts, Datum options, bool validate)
elems[i].optname = opt->option->name;
elems[i].opttype = opt->option->type;
elems[i].offset = opt->offset;
- elems[i].isset_offset = 0; /* not supported for local relopts yet */
i++;
}
diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c
index 733ef40ae7..977babff54 100644
--- a/src/backend/commands/vacuum.c
+++ b/src/backend/commands/vacuum.c
@@ -2221,9 +2221,9 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams params,
{
StdRdOptions *opts = (StdRdOptions *) rel->rd_options;
- if (opts && opts->vacuum_truncate_set)
+ if (opts && opts->vacuum_truncate != TERNARY_UNSET)
{
- if (opts->vacuum_truncate)
+ if (opts->vacuum_truncate == TERNARY_TRUE)
params.truncate = VACOPTVALUE_ENABLED;
else
params.truncate = VACOPTVALUE_DISABLED;
diff --git a/src/include/access/reloptions.h b/src/include/access/reloptions.h
index a604a4702c..a436697658 100644
--- a/src/include/access/reloptions.h
+++ b/src/include/access/reloptions.h
@@ -29,6 +29,7 @@
typedef enum relopt_type
{
RELOPT_TYPE_BOOL,
+ RELOPT_TYPE_TERNARY, /* on, off, unset */
RELOPT_TYPE_INT,
RELOPT_TYPE_REAL,
RELOPT_TYPE_ENUM,
@@ -80,6 +81,7 @@ typedef struct relopt_value
union
{
bool bool_val;
+ ternary ternary_val;
int int_val;
double real_val;
int enum_val;
@@ -94,6 +96,12 @@ typedef struct relopt_bool
bool default_val;
} relopt_bool;
+typedef struct relopt_rernary
+{
+ relopt_gen gen;
+ int default_val;
+} relopt_ternary;
+
typedef struct relopt_int
{
relopt_gen gen;
@@ -152,19 +160,6 @@ typedef struct
const char *optname; /* option's name */
relopt_type opttype; /* option's datatype */
int offset; /* offset of field in result struct */
-
- /*
- * isset_offset is an optional offset of a field in the result struct that
- * stores whether the option is explicitly set for the relation or if it
- * just picked up the default value. In most cases, this can be
- * accomplished by giving the reloption a special out-of-range default
- * value (e.g., some integer reloptions use -2), but this isn't always
- * possible. For example, a Boolean reloption cannot be given an
- * out-of-range default, so we need another way to discover the source of
- * its value. This offset is only used if given a value greater than
- * zero.
- */
- int isset_offset;
} relopt_parse_elt;
/* Local reloption definition */
@@ -195,6 +190,8 @@ typedef struct local_relopts
extern relopt_kind add_reloption_kind(void);
extern void add_bool_reloption(bits32 kinds, const char *name, const char *desc,
bool default_val, LOCKMODE lockmode);
+extern void add_ternary_reloption(bits32 kinds, const char *name,
+ const char *desc, int default_val, LOCKMODE lockmode);
extern void add_int_reloption(bits32 kinds, const char *name, const char *desc,
int default_val, int min_val, int max_val,
LOCKMODE lockmode);
@@ -214,6 +211,9 @@ extern void register_reloptions_validator(local_relopts *relopts,
extern void add_local_bool_reloption(local_relopts *relopts, const char *name,
const char *desc, bool default_val,
int offset);
+extern void add_local_ternary_reloption(local_relopts *relopts,
+ const char *name, const char *desc,
+ ternary default_val, int offset);
extern void add_local_int_reloption(local_relopts *relopts, const char *name,
const char *desc, int default_val,
int min_val, int max_val, int offset);
diff --git a/src/include/c.h b/src/include/c.h
index 39022f8a9d..9c59ccccf8 100644
--- a/src/include/c.h
+++ b/src/include/c.h
@@ -488,6 +488,22 @@ typedef void (*pg_funcptr_t) (void);
#include <stdbool.h>
+/*
+ * ternary
+ * Boolean value with an extrea "unset" option
+ *
+ * Ternary data type is used in relation options that can be "true", "false" or
+ * "unset". Since relation options are used deep inside the PostgreSQL code,
+ * this type is declared globally.
+*/
+
+typedef enum ternary
+{
+ TERNARY_FALSE = 0,
+ TERNARY_TRUE = 1,
+ TERNARY_UNSET = -1
+} ternary;
+
/* ----------------------------------------------------------------
* Section 3: standard system types
diff --git a/src/include/utils/rel.h b/src/include/utils/rel.h
index b552359915..7346d618f9 100644
--- a/src/include/utils/rel.h
+++ b/src/include/utils/rel.h
@@ -346,8 +346,7 @@ typedef struct StdRdOptions
bool user_catalog_table; /* use as an additional catalog relation */
int parallel_workers; /* max number of parallel workers */
StdRdOptIndexCleanup vacuum_index_cleanup; /* controls index vacuuming */
- bool vacuum_truncate; /* enables vacuum to truncate a relation */
- bool vacuum_truncate_set; /* whether vacuum_truncate is set */
+ ternary vacuum_truncate; /* enables vacuum to truncate a relation */
/*
* Fraction of pages in a relation that vacuum can eagerly scan and fail
--
2.39.2
--nextPart4155600.3ZeAukHxDK
Content-Disposition: attachment;
filename="v2a-0003-Add-alias-to-be-used-as-unset-state.patch"
Content-Transfer-Encoding: 7Bit
Content-Type: text/x-patch; charset="unicode-2-0-utf-8";
name="v2a-0003-Add-alias-to-be-used-as-unset-state.patch"
^ permalink raw reply [nested|flat] 868+ messages in thread
* [PATCH v2 2/4] Introduce ternary reloptions
@ 2025-09-04 14:41 Nikolay Shaplov <[email protected]>
0 siblings, 0 replies; 868+ messages in thread
From: Nikolay Shaplov @ 2025-09-04 14:41 UTC (permalink / raw)
Introduce ternary reloption as a replacement for current `vacuum_truncate`
implementation. Remove `vacuum_truncate_set` additional flag and using
`TERNARY_UNSET` value instead.
---
src/backend/access/common/reloptions.c | 129 ++++++++++++++++++++-----
src/backend/commands/vacuum.c | 4 +-
src/include/access/reloptions.h | 26 ++---
src/include/c.h | 16 +++
src/include/utils/rel.h | 3 +-
5 files changed, 135 insertions(+), 43 deletions(-)
diff --git a/src/backend/access/common/reloptions.c b/src/backend/access/common/reloptions.c
index 0af3fea68f..f543f11001 100644
--- a/src/backend/access/common/reloptions.c
+++ b/src/backend/access/common/reloptions.c
@@ -40,9 +40,9 @@
*
* To add an option:
*
- * (i) decide on a type (bool, integer, real, enum, string), name, default
- * value, upper and lower bounds (if applicable); for strings, consider a
- * validation routine.
+ * (i) decide on a type (bool, ternary, integer, real, enum, string), name,
+ * default value, upper and lower bounds (if applicable); for strings,
+ * consider a validation routine.
* (ii) add a record below (or use add_<type>_reloption).
* (iii) add it to the appropriate options struct (perhaps StdRdOptions)
* (iv) add it to the appropriate handling routine (perhaps
@@ -147,15 +147,6 @@ static relopt_bool boolRelOpts[] =
},
false
},
- {
- {
- "vacuum_truncate",
- "Enables vacuum to truncate empty pages at the end of this table",
- RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
- ShareUpdateExclusiveLock
- },
- true
- },
{
{
"deduplicate_items",
@@ -170,6 +161,21 @@ static relopt_bool boolRelOpts[] =
{{NULL}}
};
+static relopt_ternary ternaryRelOpts[] =
+{
+ {
+ {
+ "vacuum_truncate",
+ "Enables vacuum to truncate empty pages at the end of this table",
+ RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
+ ShareUpdateExclusiveLock
+ },
+ TERNARY_UNSET
+ },
+ /* list terminator */
+ {{NULL}}
+};
+
static relopt_int intRelOpts[] =
{
{
@@ -600,6 +606,13 @@ initialize_reloptions(void)
boolRelOpts[i].gen.lockmode));
j++;
}
+ for (i = 0; ternaryRelOpts[i].gen.name; i++)
+ {
+ Assert(DoLockModesConflict(ternaryRelOpts[i].gen.lockmode,
+ ternaryRelOpts[i].gen.lockmode));
+ j++;
+ }
+
for (i = 0; intRelOpts[i].gen.name; i++)
{
Assert(DoLockModesConflict(intRelOpts[i].gen.lockmode,
@@ -640,6 +653,14 @@ initialize_reloptions(void)
j++;
}
+ for (i = 0; ternaryRelOpts[i].gen.name; i++)
+ {
+ relOpts[j] = &ternaryRelOpts[i].gen;
+ relOpts[j]->type = RELOPT_TYPE_TERNARY;
+ relOpts[j]->namelen = strlen(relOpts[j]->name);
+ j++;
+ }
+
for (i = 0; intRelOpts[i].gen.name; i++)
{
relOpts[j] = &intRelOpts[i].gen;
@@ -800,6 +821,9 @@ allocate_reloption(bits32 kinds, int type, const char *name, const char *desc,
case RELOPT_TYPE_BOOL:
size = sizeof(relopt_bool);
break;
+ case RELOPT_TYPE_TERNARY:
+ size = sizeof(relopt_ternary);
+ break;
case RELOPT_TYPE_INT:
size = sizeof(relopt_int);
break;
@@ -883,6 +907,54 @@ add_local_bool_reloption(local_relopts *relopts, const char *name,
add_local_reloption(relopts, (relopt_gen *) newoption, offset);
}
+/*
+ * init_ternary_reloption
+ * Allocate and initialize a new ternary reloption
+ */
+static relopt_ternary *
+init_ternary_reloption(bits32 kinds, const char *name, const char *desc,
+ ternary default_val, LOCKMODE lockmode)
+{
+ relopt_ternary *newoption;
+
+ newoption = (relopt_ternary *) allocate_reloption(kinds,
+ RELOPT_TYPE_TERNARY, name, desc, lockmode);
+ newoption->default_val = default_val;
+
+ return newoption;
+}
+
+/*
+ * add_ternary_reloption
+ * Add a new ternary reloption
+ */
+void
+add_ternary_reloption(bits32 kinds, const char *name, const char *desc,
+ ternary default_val, LOCKMODE lockmode)
+{
+ relopt_ternary *newoption = init_ternary_reloption(kinds, name, desc,
+ default_val, lockmode);
+
+ add_reloption((relopt_gen *) newoption);
+}
+
+/*
+ * add_local_ternary_reloption
+ * Add a new ternary local reloption
+ *
+ * 'offset' is offset of ternary-typed field.
+ */
+void
+add_local_ternary_reloption(local_relopts *relopts, const char *name,
+ const char *desc, ternary default_val,
+ int offset)
+{
+ relopt_ternary *newoption = init_ternary_reloption(RELOPT_KIND_LOCAL,
+ name, desc,
+ default_val, 0);
+
+ add_local_reloption(relopts, (relopt_gen *) newoption, offset);
+}
/*
* init_real_reloption
@@ -1617,6 +1689,18 @@ parse_one_reloption(relopt_value *option, char *text_str, int text_len,
option->gen->name, value)));
}
break;
+ case RELOPT_TYPE_TERNARY:
+ {
+ bool b;
+ parsed = parse_bool(value, &b);
+ option->values.ternary_val = b ? TERNARY_TRUE : TERNARY_FALSE;
+ if (validate && !parsed)
+ ereport(ERROR,
+ (errcode(ERRCODE_INVALID_PARAMETER_VALUE),
+ errmsg("invalid value for ternary option \"%s\": %s",
+ option->gen->name, value)));
+ }
+ break;
case RELOPT_TYPE_INT:
{
relopt_int *optint = (relopt_int *) option->gen;
@@ -1780,17 +1864,6 @@ fillRelOptions(void *rdopts, Size basesize,
char *itempos = ((char *) rdopts) + elems[j].offset;
char *string_val;
- /*
- * If isset_offset is provided, store whether the reloption is
- * set there.
- */
- if (elems[j].isset_offset > 0)
- {
- char *setpos = ((char *) rdopts) + elems[j].isset_offset;
-
- *(bool *) setpos = options[i].isset;
- }
-
switch (options[i].gen->type)
{
case RELOPT_TYPE_BOOL:
@@ -1798,6 +1871,11 @@ fillRelOptions(void *rdopts, Size basesize,
options[i].values.bool_val :
((relopt_bool *) options[i].gen)->default_val;
break;
+ case RELOPT_TYPE_TERNARY:
+ *(ternary *) itempos = options[i].isset ?
+ options[i].values.ternary_val :
+ ((relopt_ternary *) options[i].gen)->default_val;
+ break;
case RELOPT_TYPE_INT:
*(int *) itempos = options[i].isset ?
options[i].values.int_val :
@@ -1912,8 +1990,8 @@ default_reloptions(Datum reloptions, bool validate, relopt_kind kind)
offsetof(StdRdOptions, parallel_workers)},
{"vacuum_index_cleanup", RELOPT_TYPE_ENUM,
offsetof(StdRdOptions, vacuum_index_cleanup)},
- {"vacuum_truncate", RELOPT_TYPE_BOOL,
- offsetof(StdRdOptions, vacuum_truncate), offsetof(StdRdOptions, vacuum_truncate_set)},
+ {"vacuum_truncate", RELOPT_TYPE_TERNARY,
+ offsetof(StdRdOptions, vacuum_truncate)},
{"vacuum_max_eager_freeze_failure_rate", RELOPT_TYPE_REAL,
offsetof(StdRdOptions, vacuum_max_eager_freeze_failure_rate)}
};
@@ -1993,7 +2071,6 @@ build_local_reloptions(local_relopts *relopts, Datum options, bool validate)
elems[i].optname = opt->option->name;
elems[i].opttype = opt->option->type;
elems[i].offset = opt->offset;
- elems[i].isset_offset = 0; /* not supported for local relopts yet */
i++;
}
diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c
index 733ef40ae7..977babff54 100644
--- a/src/backend/commands/vacuum.c
+++ b/src/backend/commands/vacuum.c
@@ -2221,9 +2221,9 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams params,
{
StdRdOptions *opts = (StdRdOptions *) rel->rd_options;
- if (opts && opts->vacuum_truncate_set)
+ if (opts && opts->vacuum_truncate != TERNARY_UNSET)
{
- if (opts->vacuum_truncate)
+ if (opts->vacuum_truncate == TERNARY_TRUE)
params.truncate = VACOPTVALUE_ENABLED;
else
params.truncate = VACOPTVALUE_DISABLED;
diff --git a/src/include/access/reloptions.h b/src/include/access/reloptions.h
index a604a4702c..a436697658 100644
--- a/src/include/access/reloptions.h
+++ b/src/include/access/reloptions.h
@@ -29,6 +29,7 @@
typedef enum relopt_type
{
RELOPT_TYPE_BOOL,
+ RELOPT_TYPE_TERNARY, /* on, off, unset */
RELOPT_TYPE_INT,
RELOPT_TYPE_REAL,
RELOPT_TYPE_ENUM,
@@ -80,6 +81,7 @@ typedef struct relopt_value
union
{
bool bool_val;
+ ternary ternary_val;
int int_val;
double real_val;
int enum_val;
@@ -94,6 +96,12 @@ typedef struct relopt_bool
bool default_val;
} relopt_bool;
+typedef struct relopt_rernary
+{
+ relopt_gen gen;
+ int default_val;
+} relopt_ternary;
+
typedef struct relopt_int
{
relopt_gen gen;
@@ -152,19 +160,6 @@ typedef struct
const char *optname; /* option's name */
relopt_type opttype; /* option's datatype */
int offset; /* offset of field in result struct */
-
- /*
- * isset_offset is an optional offset of a field in the result struct that
- * stores whether the option is explicitly set for the relation or if it
- * just picked up the default value. In most cases, this can be
- * accomplished by giving the reloption a special out-of-range default
- * value (e.g., some integer reloptions use -2), but this isn't always
- * possible. For example, a Boolean reloption cannot be given an
- * out-of-range default, so we need another way to discover the source of
- * its value. This offset is only used if given a value greater than
- * zero.
- */
- int isset_offset;
} relopt_parse_elt;
/* Local reloption definition */
@@ -195,6 +190,8 @@ typedef struct local_relopts
extern relopt_kind add_reloption_kind(void);
extern void add_bool_reloption(bits32 kinds, const char *name, const char *desc,
bool default_val, LOCKMODE lockmode);
+extern void add_ternary_reloption(bits32 kinds, const char *name,
+ const char *desc, int default_val, LOCKMODE lockmode);
extern void add_int_reloption(bits32 kinds, const char *name, const char *desc,
int default_val, int min_val, int max_val,
LOCKMODE lockmode);
@@ -214,6 +211,9 @@ extern void register_reloptions_validator(local_relopts *relopts,
extern void add_local_bool_reloption(local_relopts *relopts, const char *name,
const char *desc, bool default_val,
int offset);
+extern void add_local_ternary_reloption(local_relopts *relopts,
+ const char *name, const char *desc,
+ ternary default_val, int offset);
extern void add_local_int_reloption(local_relopts *relopts, const char *name,
const char *desc, int default_val,
int min_val, int max_val, int offset);
diff --git a/src/include/c.h b/src/include/c.h
index 39022f8a9d..9c59ccccf8 100644
--- a/src/include/c.h
+++ b/src/include/c.h
@@ -488,6 +488,22 @@ typedef void (*pg_funcptr_t) (void);
#include <stdbool.h>
+/*
+ * ternary
+ * Boolean value with an extrea "unset" option
+ *
+ * Ternary data type is used in relation options that can be "true", "false" or
+ * "unset". Since relation options are used deep inside the PostgreSQL code,
+ * this type is declared globally.
+*/
+
+typedef enum ternary
+{
+ TERNARY_FALSE = 0,
+ TERNARY_TRUE = 1,
+ TERNARY_UNSET = -1
+} ternary;
+
/* ----------------------------------------------------------------
* Section 3: standard system types
diff --git a/src/include/utils/rel.h b/src/include/utils/rel.h
index b552359915..7346d618f9 100644
--- a/src/include/utils/rel.h
+++ b/src/include/utils/rel.h
@@ -346,8 +346,7 @@ typedef struct StdRdOptions
bool user_catalog_table; /* use as an additional catalog relation */
int parallel_workers; /* max number of parallel workers */
StdRdOptIndexCleanup vacuum_index_cleanup; /* controls index vacuuming */
- bool vacuum_truncate; /* enables vacuum to truncate a relation */
- bool vacuum_truncate_set; /* whether vacuum_truncate is set */
+ ternary vacuum_truncate; /* enables vacuum to truncate a relation */
/*
* Fraction of pages in a relation that vacuum can eagerly scan and fail
--
2.39.2
--nextPart5129932.5fSG56mABF
Content-Disposition: attachment;
filename="v2-0003-Add-alias-to-be-used-as-unset-state.patch"
Content-Transfer-Encoding: 7Bit
Content-Type: text/x-patch; charset="unicode-2-0-utf-8";
name="v2-0003-Add-alias-to-be-used-as-unset-state.patch"
^ permalink raw reply [nested|flat] 868+ messages in thread
* [PATCH v2 2/4] Introduce ternary reloptions
@ 2025-09-04 14:41 Nikolay Shaplov <[email protected]>
0 siblings, 0 replies; 868+ messages in thread
From: Nikolay Shaplov @ 2025-09-04 14:41 UTC (permalink / raw)
Introduce ternary reloption as a replacement for current `vacuum_truncate`
implementation. Remove `vacuum_truncate_set` additional flag and using
`TERNARY_UNSET` value instead.
---
src/backend/access/common/reloptions.c | 129 ++++++++++++++++++++-----
src/backend/commands/vacuum.c | 4 +-
src/include/access/reloptions.h | 26 ++---
src/include/c.h | 16 +++
src/include/utils/rel.h | 3 +-
5 files changed, 135 insertions(+), 43 deletions(-)
diff --git a/src/backend/access/common/reloptions.c b/src/backend/access/common/reloptions.c
index 0af3fea68f..f543f11001 100644
--- a/src/backend/access/common/reloptions.c
+++ b/src/backend/access/common/reloptions.c
@@ -40,9 +40,9 @@
*
* To add an option:
*
- * (i) decide on a type (bool, integer, real, enum, string), name, default
- * value, upper and lower bounds (if applicable); for strings, consider a
- * validation routine.
+ * (i) decide on a type (bool, ternary, integer, real, enum, string), name,
+ * default value, upper and lower bounds (if applicable); for strings,
+ * consider a validation routine.
* (ii) add a record below (or use add_<type>_reloption).
* (iii) add it to the appropriate options struct (perhaps StdRdOptions)
* (iv) add it to the appropriate handling routine (perhaps
@@ -147,15 +147,6 @@ static relopt_bool boolRelOpts[] =
},
false
},
- {
- {
- "vacuum_truncate",
- "Enables vacuum to truncate empty pages at the end of this table",
- RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
- ShareUpdateExclusiveLock
- },
- true
- },
{
{
"deduplicate_items",
@@ -170,6 +161,21 @@ static relopt_bool boolRelOpts[] =
{{NULL}}
};
+static relopt_ternary ternaryRelOpts[] =
+{
+ {
+ {
+ "vacuum_truncate",
+ "Enables vacuum to truncate empty pages at the end of this table",
+ RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
+ ShareUpdateExclusiveLock
+ },
+ TERNARY_UNSET
+ },
+ /* list terminator */
+ {{NULL}}
+};
+
static relopt_int intRelOpts[] =
{
{
@@ -600,6 +606,13 @@ initialize_reloptions(void)
boolRelOpts[i].gen.lockmode));
j++;
}
+ for (i = 0; ternaryRelOpts[i].gen.name; i++)
+ {
+ Assert(DoLockModesConflict(ternaryRelOpts[i].gen.lockmode,
+ ternaryRelOpts[i].gen.lockmode));
+ j++;
+ }
+
for (i = 0; intRelOpts[i].gen.name; i++)
{
Assert(DoLockModesConflict(intRelOpts[i].gen.lockmode,
@@ -640,6 +653,14 @@ initialize_reloptions(void)
j++;
}
+ for (i = 0; ternaryRelOpts[i].gen.name; i++)
+ {
+ relOpts[j] = &ternaryRelOpts[i].gen;
+ relOpts[j]->type = RELOPT_TYPE_TERNARY;
+ relOpts[j]->namelen = strlen(relOpts[j]->name);
+ j++;
+ }
+
for (i = 0; intRelOpts[i].gen.name; i++)
{
relOpts[j] = &intRelOpts[i].gen;
@@ -800,6 +821,9 @@ allocate_reloption(bits32 kinds, int type, const char *name, const char *desc,
case RELOPT_TYPE_BOOL:
size = sizeof(relopt_bool);
break;
+ case RELOPT_TYPE_TERNARY:
+ size = sizeof(relopt_ternary);
+ break;
case RELOPT_TYPE_INT:
size = sizeof(relopt_int);
break;
@@ -883,6 +907,54 @@ add_local_bool_reloption(local_relopts *relopts, const char *name,
add_local_reloption(relopts, (relopt_gen *) newoption, offset);
}
+/*
+ * init_ternary_reloption
+ * Allocate and initialize a new ternary reloption
+ */
+static relopt_ternary *
+init_ternary_reloption(bits32 kinds, const char *name, const char *desc,
+ ternary default_val, LOCKMODE lockmode)
+{
+ relopt_ternary *newoption;
+
+ newoption = (relopt_ternary *) allocate_reloption(kinds,
+ RELOPT_TYPE_TERNARY, name, desc, lockmode);
+ newoption->default_val = default_val;
+
+ return newoption;
+}
+
+/*
+ * add_ternary_reloption
+ * Add a new ternary reloption
+ */
+void
+add_ternary_reloption(bits32 kinds, const char *name, const char *desc,
+ ternary default_val, LOCKMODE lockmode)
+{
+ relopt_ternary *newoption = init_ternary_reloption(kinds, name, desc,
+ default_val, lockmode);
+
+ add_reloption((relopt_gen *) newoption);
+}
+
+/*
+ * add_local_ternary_reloption
+ * Add a new ternary local reloption
+ *
+ * 'offset' is offset of ternary-typed field.
+ */
+void
+add_local_ternary_reloption(local_relopts *relopts, const char *name,
+ const char *desc, ternary default_val,
+ int offset)
+{
+ relopt_ternary *newoption = init_ternary_reloption(RELOPT_KIND_LOCAL,
+ name, desc,
+ default_val, 0);
+
+ add_local_reloption(relopts, (relopt_gen *) newoption, offset);
+}
/*
* init_real_reloption
@@ -1617,6 +1689,18 @@ parse_one_reloption(relopt_value *option, char *text_str, int text_len,
option->gen->name, value)));
}
break;
+ case RELOPT_TYPE_TERNARY:
+ {
+ bool b;
+ parsed = parse_bool(value, &b);
+ option->values.ternary_val = b ? TERNARY_TRUE : TERNARY_FALSE;
+ if (validate && !parsed)
+ ereport(ERROR,
+ (errcode(ERRCODE_INVALID_PARAMETER_VALUE),
+ errmsg("invalid value for ternary option \"%s\": %s",
+ option->gen->name, value)));
+ }
+ break;
case RELOPT_TYPE_INT:
{
relopt_int *optint = (relopt_int *) option->gen;
@@ -1780,17 +1864,6 @@ fillRelOptions(void *rdopts, Size basesize,
char *itempos = ((char *) rdopts) + elems[j].offset;
char *string_val;
- /*
- * If isset_offset is provided, store whether the reloption is
- * set there.
- */
- if (elems[j].isset_offset > 0)
- {
- char *setpos = ((char *) rdopts) + elems[j].isset_offset;
-
- *(bool *) setpos = options[i].isset;
- }
-
switch (options[i].gen->type)
{
case RELOPT_TYPE_BOOL:
@@ -1798,6 +1871,11 @@ fillRelOptions(void *rdopts, Size basesize,
options[i].values.bool_val :
((relopt_bool *) options[i].gen)->default_val;
break;
+ case RELOPT_TYPE_TERNARY:
+ *(ternary *) itempos = options[i].isset ?
+ options[i].values.ternary_val :
+ ((relopt_ternary *) options[i].gen)->default_val;
+ break;
case RELOPT_TYPE_INT:
*(int *) itempos = options[i].isset ?
options[i].values.int_val :
@@ -1912,8 +1990,8 @@ default_reloptions(Datum reloptions, bool validate, relopt_kind kind)
offsetof(StdRdOptions, parallel_workers)},
{"vacuum_index_cleanup", RELOPT_TYPE_ENUM,
offsetof(StdRdOptions, vacuum_index_cleanup)},
- {"vacuum_truncate", RELOPT_TYPE_BOOL,
- offsetof(StdRdOptions, vacuum_truncate), offsetof(StdRdOptions, vacuum_truncate_set)},
+ {"vacuum_truncate", RELOPT_TYPE_TERNARY,
+ offsetof(StdRdOptions, vacuum_truncate)},
{"vacuum_max_eager_freeze_failure_rate", RELOPT_TYPE_REAL,
offsetof(StdRdOptions, vacuum_max_eager_freeze_failure_rate)}
};
@@ -1993,7 +2071,6 @@ build_local_reloptions(local_relopts *relopts, Datum options, bool validate)
elems[i].optname = opt->option->name;
elems[i].opttype = opt->option->type;
elems[i].offset = opt->offset;
- elems[i].isset_offset = 0; /* not supported for local relopts yet */
i++;
}
diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c
index 733ef40ae7..977babff54 100644
--- a/src/backend/commands/vacuum.c
+++ b/src/backend/commands/vacuum.c
@@ -2221,9 +2221,9 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams params,
{
StdRdOptions *opts = (StdRdOptions *) rel->rd_options;
- if (opts && opts->vacuum_truncate_set)
+ if (opts && opts->vacuum_truncate != TERNARY_UNSET)
{
- if (opts->vacuum_truncate)
+ if (opts->vacuum_truncate == TERNARY_TRUE)
params.truncate = VACOPTVALUE_ENABLED;
else
params.truncate = VACOPTVALUE_DISABLED;
diff --git a/src/include/access/reloptions.h b/src/include/access/reloptions.h
index a604a4702c..a436697658 100644
--- a/src/include/access/reloptions.h
+++ b/src/include/access/reloptions.h
@@ -29,6 +29,7 @@
typedef enum relopt_type
{
RELOPT_TYPE_BOOL,
+ RELOPT_TYPE_TERNARY, /* on, off, unset */
RELOPT_TYPE_INT,
RELOPT_TYPE_REAL,
RELOPT_TYPE_ENUM,
@@ -80,6 +81,7 @@ typedef struct relopt_value
union
{
bool bool_val;
+ ternary ternary_val;
int int_val;
double real_val;
int enum_val;
@@ -94,6 +96,12 @@ typedef struct relopt_bool
bool default_val;
} relopt_bool;
+typedef struct relopt_rernary
+{
+ relopt_gen gen;
+ int default_val;
+} relopt_ternary;
+
typedef struct relopt_int
{
relopt_gen gen;
@@ -152,19 +160,6 @@ typedef struct
const char *optname; /* option's name */
relopt_type opttype; /* option's datatype */
int offset; /* offset of field in result struct */
-
- /*
- * isset_offset is an optional offset of a field in the result struct that
- * stores whether the option is explicitly set for the relation or if it
- * just picked up the default value. In most cases, this can be
- * accomplished by giving the reloption a special out-of-range default
- * value (e.g., some integer reloptions use -2), but this isn't always
- * possible. For example, a Boolean reloption cannot be given an
- * out-of-range default, so we need another way to discover the source of
- * its value. This offset is only used if given a value greater than
- * zero.
- */
- int isset_offset;
} relopt_parse_elt;
/* Local reloption definition */
@@ -195,6 +190,8 @@ typedef struct local_relopts
extern relopt_kind add_reloption_kind(void);
extern void add_bool_reloption(bits32 kinds, const char *name, const char *desc,
bool default_val, LOCKMODE lockmode);
+extern void add_ternary_reloption(bits32 kinds, const char *name,
+ const char *desc, int default_val, LOCKMODE lockmode);
extern void add_int_reloption(bits32 kinds, const char *name, const char *desc,
int default_val, int min_val, int max_val,
LOCKMODE lockmode);
@@ -214,6 +211,9 @@ extern void register_reloptions_validator(local_relopts *relopts,
extern void add_local_bool_reloption(local_relopts *relopts, const char *name,
const char *desc, bool default_val,
int offset);
+extern void add_local_ternary_reloption(local_relopts *relopts,
+ const char *name, const char *desc,
+ ternary default_val, int offset);
extern void add_local_int_reloption(local_relopts *relopts, const char *name,
const char *desc, int default_val,
int min_val, int max_val, int offset);
diff --git a/src/include/c.h b/src/include/c.h
index 39022f8a9d..9c59ccccf8 100644
--- a/src/include/c.h
+++ b/src/include/c.h
@@ -488,6 +488,22 @@ typedef void (*pg_funcptr_t) (void);
#include <stdbool.h>
+/*
+ * ternary
+ * Boolean value with an extrea "unset" option
+ *
+ * Ternary data type is used in relation options that can be "true", "false" or
+ * "unset". Since relation options are used deep inside the PostgreSQL code,
+ * this type is declared globally.
+*/
+
+typedef enum ternary
+{
+ TERNARY_FALSE = 0,
+ TERNARY_TRUE = 1,
+ TERNARY_UNSET = -1
+} ternary;
+
/* ----------------------------------------------------------------
* Section 3: standard system types
diff --git a/src/include/utils/rel.h b/src/include/utils/rel.h
index b552359915..7346d618f9 100644
--- a/src/include/utils/rel.h
+++ b/src/include/utils/rel.h
@@ -346,8 +346,7 @@ typedef struct StdRdOptions
bool user_catalog_table; /* use as an additional catalog relation */
int parallel_workers; /* max number of parallel workers */
StdRdOptIndexCleanup vacuum_index_cleanup; /* controls index vacuuming */
- bool vacuum_truncate; /* enables vacuum to truncate a relation */
- bool vacuum_truncate_set; /* whether vacuum_truncate is set */
+ ternary vacuum_truncate; /* enables vacuum to truncate a relation */
/*
* Fraction of pages in a relation that vacuum can eagerly scan and fail
--
2.39.2
--nextPart5129932.5fSG56mABF
Content-Disposition: attachment;
filename="v2-0003-Add-alias-to-be-used-as-unset-state.patch"
Content-Transfer-Encoding: 7Bit
Content-Type: text/x-patch; charset="unicode-2-0-utf-8";
name="v2-0003-Add-alias-to-be-used-as-unset-state.patch"
^ permalink raw reply [nested|flat] 868+ messages in thread
* [PATCH v2a 2/4] Introduce ternary reloptions
@ 2025-09-04 14:41 Nikolay Shaplov <[email protected]>
0 siblings, 0 replies; 868+ messages in thread
From: Nikolay Shaplov @ 2025-09-04 14:41 UTC (permalink / raw)
Introduce ternary reloption as a replacement for current `vacuum_truncate`
implementation. Remove `vacuum_truncate_set` additional flag and using
`TERNARY_UNSET` value instead.
---
src/backend/access/common/reloptions.c | 129 ++++++++++++++++++++-----
src/backend/commands/vacuum.c | 4 +-
src/include/access/reloptions.h | 26 ++---
src/include/c.h | 16 +++
src/include/utils/rel.h | 3 +-
5 files changed, 135 insertions(+), 43 deletions(-)
diff --git a/src/backend/access/common/reloptions.c b/src/backend/access/common/reloptions.c
index 0af3fea68f..f543f11001 100644
--- a/src/backend/access/common/reloptions.c
+++ b/src/backend/access/common/reloptions.c
@@ -40,9 +40,9 @@
*
* To add an option:
*
- * (i) decide on a type (bool, integer, real, enum, string), name, default
- * value, upper and lower bounds (if applicable); for strings, consider a
- * validation routine.
+ * (i) decide on a type (bool, ternary, integer, real, enum, string), name,
+ * default value, upper and lower bounds (if applicable); for strings,
+ * consider a validation routine.
* (ii) add a record below (or use add_<type>_reloption).
* (iii) add it to the appropriate options struct (perhaps StdRdOptions)
* (iv) add it to the appropriate handling routine (perhaps
@@ -147,15 +147,6 @@ static relopt_bool boolRelOpts[] =
},
false
},
- {
- {
- "vacuum_truncate",
- "Enables vacuum to truncate empty pages at the end of this table",
- RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
- ShareUpdateExclusiveLock
- },
- true
- },
{
{
"deduplicate_items",
@@ -170,6 +161,21 @@ static relopt_bool boolRelOpts[] =
{{NULL}}
};
+static relopt_ternary ternaryRelOpts[] =
+{
+ {
+ {
+ "vacuum_truncate",
+ "Enables vacuum to truncate empty pages at the end of this table",
+ RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
+ ShareUpdateExclusiveLock
+ },
+ TERNARY_UNSET
+ },
+ /* list terminator */
+ {{NULL}}
+};
+
static relopt_int intRelOpts[] =
{
{
@@ -600,6 +606,13 @@ initialize_reloptions(void)
boolRelOpts[i].gen.lockmode));
j++;
}
+ for (i = 0; ternaryRelOpts[i].gen.name; i++)
+ {
+ Assert(DoLockModesConflict(ternaryRelOpts[i].gen.lockmode,
+ ternaryRelOpts[i].gen.lockmode));
+ j++;
+ }
+
for (i = 0; intRelOpts[i].gen.name; i++)
{
Assert(DoLockModesConflict(intRelOpts[i].gen.lockmode,
@@ -640,6 +653,14 @@ initialize_reloptions(void)
j++;
}
+ for (i = 0; ternaryRelOpts[i].gen.name; i++)
+ {
+ relOpts[j] = &ternaryRelOpts[i].gen;
+ relOpts[j]->type = RELOPT_TYPE_TERNARY;
+ relOpts[j]->namelen = strlen(relOpts[j]->name);
+ j++;
+ }
+
for (i = 0; intRelOpts[i].gen.name; i++)
{
relOpts[j] = &intRelOpts[i].gen;
@@ -800,6 +821,9 @@ allocate_reloption(bits32 kinds, int type, const char *name, const char *desc,
case RELOPT_TYPE_BOOL:
size = sizeof(relopt_bool);
break;
+ case RELOPT_TYPE_TERNARY:
+ size = sizeof(relopt_ternary);
+ break;
case RELOPT_TYPE_INT:
size = sizeof(relopt_int);
break;
@@ -883,6 +907,54 @@ add_local_bool_reloption(local_relopts *relopts, const char *name,
add_local_reloption(relopts, (relopt_gen *) newoption, offset);
}
+/*
+ * init_ternary_reloption
+ * Allocate and initialize a new ternary reloption
+ */
+static relopt_ternary *
+init_ternary_reloption(bits32 kinds, const char *name, const char *desc,
+ ternary default_val, LOCKMODE lockmode)
+{
+ relopt_ternary *newoption;
+
+ newoption = (relopt_ternary *) allocate_reloption(kinds,
+ RELOPT_TYPE_TERNARY, name, desc, lockmode);
+ newoption->default_val = default_val;
+
+ return newoption;
+}
+
+/*
+ * add_ternary_reloption
+ * Add a new ternary reloption
+ */
+void
+add_ternary_reloption(bits32 kinds, const char *name, const char *desc,
+ ternary default_val, LOCKMODE lockmode)
+{
+ relopt_ternary *newoption = init_ternary_reloption(kinds, name, desc,
+ default_val, lockmode);
+
+ add_reloption((relopt_gen *) newoption);
+}
+
+/*
+ * add_local_ternary_reloption
+ * Add a new ternary local reloption
+ *
+ * 'offset' is offset of ternary-typed field.
+ */
+void
+add_local_ternary_reloption(local_relopts *relopts, const char *name,
+ const char *desc, ternary default_val,
+ int offset)
+{
+ relopt_ternary *newoption = init_ternary_reloption(RELOPT_KIND_LOCAL,
+ name, desc,
+ default_val, 0);
+
+ add_local_reloption(relopts, (relopt_gen *) newoption, offset);
+}
/*
* init_real_reloption
@@ -1617,6 +1689,18 @@ parse_one_reloption(relopt_value *option, char *text_str, int text_len,
option->gen->name, value)));
}
break;
+ case RELOPT_TYPE_TERNARY:
+ {
+ bool b;
+ parsed = parse_bool(value, &b);
+ option->values.ternary_val = b ? TERNARY_TRUE : TERNARY_FALSE;
+ if (validate && !parsed)
+ ereport(ERROR,
+ (errcode(ERRCODE_INVALID_PARAMETER_VALUE),
+ errmsg("invalid value for ternary option \"%s\": %s",
+ option->gen->name, value)));
+ }
+ break;
case RELOPT_TYPE_INT:
{
relopt_int *optint = (relopt_int *) option->gen;
@@ -1780,17 +1864,6 @@ fillRelOptions(void *rdopts, Size basesize,
char *itempos = ((char *) rdopts) + elems[j].offset;
char *string_val;
- /*
- * If isset_offset is provided, store whether the reloption is
- * set there.
- */
- if (elems[j].isset_offset > 0)
- {
- char *setpos = ((char *) rdopts) + elems[j].isset_offset;
-
- *(bool *) setpos = options[i].isset;
- }
-
switch (options[i].gen->type)
{
case RELOPT_TYPE_BOOL:
@@ -1798,6 +1871,11 @@ fillRelOptions(void *rdopts, Size basesize,
options[i].values.bool_val :
((relopt_bool *) options[i].gen)->default_val;
break;
+ case RELOPT_TYPE_TERNARY:
+ *(ternary *) itempos = options[i].isset ?
+ options[i].values.ternary_val :
+ ((relopt_ternary *) options[i].gen)->default_val;
+ break;
case RELOPT_TYPE_INT:
*(int *) itempos = options[i].isset ?
options[i].values.int_val :
@@ -1912,8 +1990,8 @@ default_reloptions(Datum reloptions, bool validate, relopt_kind kind)
offsetof(StdRdOptions, parallel_workers)},
{"vacuum_index_cleanup", RELOPT_TYPE_ENUM,
offsetof(StdRdOptions, vacuum_index_cleanup)},
- {"vacuum_truncate", RELOPT_TYPE_BOOL,
- offsetof(StdRdOptions, vacuum_truncate), offsetof(StdRdOptions, vacuum_truncate_set)},
+ {"vacuum_truncate", RELOPT_TYPE_TERNARY,
+ offsetof(StdRdOptions, vacuum_truncate)},
{"vacuum_max_eager_freeze_failure_rate", RELOPT_TYPE_REAL,
offsetof(StdRdOptions, vacuum_max_eager_freeze_failure_rate)}
};
@@ -1993,7 +2071,6 @@ build_local_reloptions(local_relopts *relopts, Datum options, bool validate)
elems[i].optname = opt->option->name;
elems[i].opttype = opt->option->type;
elems[i].offset = opt->offset;
- elems[i].isset_offset = 0; /* not supported for local relopts yet */
i++;
}
diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c
index 733ef40ae7..977babff54 100644
--- a/src/backend/commands/vacuum.c
+++ b/src/backend/commands/vacuum.c
@@ -2221,9 +2221,9 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams params,
{
StdRdOptions *opts = (StdRdOptions *) rel->rd_options;
- if (opts && opts->vacuum_truncate_set)
+ if (opts && opts->vacuum_truncate != TERNARY_UNSET)
{
- if (opts->vacuum_truncate)
+ if (opts->vacuum_truncate == TERNARY_TRUE)
params.truncate = VACOPTVALUE_ENABLED;
else
params.truncate = VACOPTVALUE_DISABLED;
diff --git a/src/include/access/reloptions.h b/src/include/access/reloptions.h
index a604a4702c..a436697658 100644
--- a/src/include/access/reloptions.h
+++ b/src/include/access/reloptions.h
@@ -29,6 +29,7 @@
typedef enum relopt_type
{
RELOPT_TYPE_BOOL,
+ RELOPT_TYPE_TERNARY, /* on, off, unset */
RELOPT_TYPE_INT,
RELOPT_TYPE_REAL,
RELOPT_TYPE_ENUM,
@@ -80,6 +81,7 @@ typedef struct relopt_value
union
{
bool bool_val;
+ ternary ternary_val;
int int_val;
double real_val;
int enum_val;
@@ -94,6 +96,12 @@ typedef struct relopt_bool
bool default_val;
} relopt_bool;
+typedef struct relopt_rernary
+{
+ relopt_gen gen;
+ int default_val;
+} relopt_ternary;
+
typedef struct relopt_int
{
relopt_gen gen;
@@ -152,19 +160,6 @@ typedef struct
const char *optname; /* option's name */
relopt_type opttype; /* option's datatype */
int offset; /* offset of field in result struct */
-
- /*
- * isset_offset is an optional offset of a field in the result struct that
- * stores whether the option is explicitly set for the relation or if it
- * just picked up the default value. In most cases, this can be
- * accomplished by giving the reloption a special out-of-range default
- * value (e.g., some integer reloptions use -2), but this isn't always
- * possible. For example, a Boolean reloption cannot be given an
- * out-of-range default, so we need another way to discover the source of
- * its value. This offset is only used if given a value greater than
- * zero.
- */
- int isset_offset;
} relopt_parse_elt;
/* Local reloption definition */
@@ -195,6 +190,8 @@ typedef struct local_relopts
extern relopt_kind add_reloption_kind(void);
extern void add_bool_reloption(bits32 kinds, const char *name, const char *desc,
bool default_val, LOCKMODE lockmode);
+extern void add_ternary_reloption(bits32 kinds, const char *name,
+ const char *desc, int default_val, LOCKMODE lockmode);
extern void add_int_reloption(bits32 kinds, const char *name, const char *desc,
int default_val, int min_val, int max_val,
LOCKMODE lockmode);
@@ -214,6 +211,9 @@ extern void register_reloptions_validator(local_relopts *relopts,
extern void add_local_bool_reloption(local_relopts *relopts, const char *name,
const char *desc, bool default_val,
int offset);
+extern void add_local_ternary_reloption(local_relopts *relopts,
+ const char *name, const char *desc,
+ ternary default_val, int offset);
extern void add_local_int_reloption(local_relopts *relopts, const char *name,
const char *desc, int default_val,
int min_val, int max_val, int offset);
diff --git a/src/include/c.h b/src/include/c.h
index 39022f8a9d..9c59ccccf8 100644
--- a/src/include/c.h
+++ b/src/include/c.h
@@ -488,6 +488,22 @@ typedef void (*pg_funcptr_t) (void);
#include <stdbool.h>
+/*
+ * ternary
+ * Boolean value with an extrea "unset" option
+ *
+ * Ternary data type is used in relation options that can be "true", "false" or
+ * "unset". Since relation options are used deep inside the PostgreSQL code,
+ * this type is declared globally.
+*/
+
+typedef enum ternary
+{
+ TERNARY_FALSE = 0,
+ TERNARY_TRUE = 1,
+ TERNARY_UNSET = -1
+} ternary;
+
/* ----------------------------------------------------------------
* Section 3: standard system types
diff --git a/src/include/utils/rel.h b/src/include/utils/rel.h
index b552359915..7346d618f9 100644
--- a/src/include/utils/rel.h
+++ b/src/include/utils/rel.h
@@ -346,8 +346,7 @@ typedef struct StdRdOptions
bool user_catalog_table; /* use as an additional catalog relation */
int parallel_workers; /* max number of parallel workers */
StdRdOptIndexCleanup vacuum_index_cleanup; /* controls index vacuuming */
- bool vacuum_truncate; /* enables vacuum to truncate a relation */
- bool vacuum_truncate_set; /* whether vacuum_truncate is set */
+ ternary vacuum_truncate; /* enables vacuum to truncate a relation */
/*
* Fraction of pages in a relation that vacuum can eagerly scan and fail
--
2.39.2
--nextPart4155600.3ZeAukHxDK
Content-Disposition: attachment;
filename="v2a-0003-Add-alias-to-be-used-as-unset-state.patch"
Content-Transfer-Encoding: 7Bit
Content-Type: text/x-patch; charset="unicode-2-0-utf-8";
name="v2a-0003-Add-alias-to-be-used-as-unset-state.patch"
^ permalink raw reply [nested|flat] 868+ messages in thread
* [PATCH v2 2/4] Introduce ternary reloptions
@ 2025-09-04 14:41 Nikolay Shaplov <[email protected]>
0 siblings, 0 replies; 868+ messages in thread
From: Nikolay Shaplov @ 2025-09-04 14:41 UTC (permalink / raw)
Introduce ternary reloption as a replacement for current `vacuum_truncate`
implementation. Remove `vacuum_truncate_set` additional flag and using
`TERNARY_UNSET` value instead.
---
src/backend/access/common/reloptions.c | 129 ++++++++++++++++++++-----
src/backend/commands/vacuum.c | 4 +-
src/include/access/reloptions.h | 26 ++---
src/include/c.h | 16 +++
src/include/utils/rel.h | 3 +-
5 files changed, 135 insertions(+), 43 deletions(-)
diff --git a/src/backend/access/common/reloptions.c b/src/backend/access/common/reloptions.c
index 0af3fea68f..f543f11001 100644
--- a/src/backend/access/common/reloptions.c
+++ b/src/backend/access/common/reloptions.c
@@ -40,9 +40,9 @@
*
* To add an option:
*
- * (i) decide on a type (bool, integer, real, enum, string), name, default
- * value, upper and lower bounds (if applicable); for strings, consider a
- * validation routine.
+ * (i) decide on a type (bool, ternary, integer, real, enum, string), name,
+ * default value, upper and lower bounds (if applicable); for strings,
+ * consider a validation routine.
* (ii) add a record below (or use add_<type>_reloption).
* (iii) add it to the appropriate options struct (perhaps StdRdOptions)
* (iv) add it to the appropriate handling routine (perhaps
@@ -147,15 +147,6 @@ static relopt_bool boolRelOpts[] =
},
false
},
- {
- {
- "vacuum_truncate",
- "Enables vacuum to truncate empty pages at the end of this table",
- RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
- ShareUpdateExclusiveLock
- },
- true
- },
{
{
"deduplicate_items",
@@ -170,6 +161,21 @@ static relopt_bool boolRelOpts[] =
{{NULL}}
};
+static relopt_ternary ternaryRelOpts[] =
+{
+ {
+ {
+ "vacuum_truncate",
+ "Enables vacuum to truncate empty pages at the end of this table",
+ RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
+ ShareUpdateExclusiveLock
+ },
+ TERNARY_UNSET
+ },
+ /* list terminator */
+ {{NULL}}
+};
+
static relopt_int intRelOpts[] =
{
{
@@ -600,6 +606,13 @@ initialize_reloptions(void)
boolRelOpts[i].gen.lockmode));
j++;
}
+ for (i = 0; ternaryRelOpts[i].gen.name; i++)
+ {
+ Assert(DoLockModesConflict(ternaryRelOpts[i].gen.lockmode,
+ ternaryRelOpts[i].gen.lockmode));
+ j++;
+ }
+
for (i = 0; intRelOpts[i].gen.name; i++)
{
Assert(DoLockModesConflict(intRelOpts[i].gen.lockmode,
@@ -640,6 +653,14 @@ initialize_reloptions(void)
j++;
}
+ for (i = 0; ternaryRelOpts[i].gen.name; i++)
+ {
+ relOpts[j] = &ternaryRelOpts[i].gen;
+ relOpts[j]->type = RELOPT_TYPE_TERNARY;
+ relOpts[j]->namelen = strlen(relOpts[j]->name);
+ j++;
+ }
+
for (i = 0; intRelOpts[i].gen.name; i++)
{
relOpts[j] = &intRelOpts[i].gen;
@@ -800,6 +821,9 @@ allocate_reloption(bits32 kinds, int type, const char *name, const char *desc,
case RELOPT_TYPE_BOOL:
size = sizeof(relopt_bool);
break;
+ case RELOPT_TYPE_TERNARY:
+ size = sizeof(relopt_ternary);
+ break;
case RELOPT_TYPE_INT:
size = sizeof(relopt_int);
break;
@@ -883,6 +907,54 @@ add_local_bool_reloption(local_relopts *relopts, const char *name,
add_local_reloption(relopts, (relopt_gen *) newoption, offset);
}
+/*
+ * init_ternary_reloption
+ * Allocate and initialize a new ternary reloption
+ */
+static relopt_ternary *
+init_ternary_reloption(bits32 kinds, const char *name, const char *desc,
+ ternary default_val, LOCKMODE lockmode)
+{
+ relopt_ternary *newoption;
+
+ newoption = (relopt_ternary *) allocate_reloption(kinds,
+ RELOPT_TYPE_TERNARY, name, desc, lockmode);
+ newoption->default_val = default_val;
+
+ return newoption;
+}
+
+/*
+ * add_ternary_reloption
+ * Add a new ternary reloption
+ */
+void
+add_ternary_reloption(bits32 kinds, const char *name, const char *desc,
+ ternary default_val, LOCKMODE lockmode)
+{
+ relopt_ternary *newoption = init_ternary_reloption(kinds, name, desc,
+ default_val, lockmode);
+
+ add_reloption((relopt_gen *) newoption);
+}
+
+/*
+ * add_local_ternary_reloption
+ * Add a new ternary local reloption
+ *
+ * 'offset' is offset of ternary-typed field.
+ */
+void
+add_local_ternary_reloption(local_relopts *relopts, const char *name,
+ const char *desc, ternary default_val,
+ int offset)
+{
+ relopt_ternary *newoption = init_ternary_reloption(RELOPT_KIND_LOCAL,
+ name, desc,
+ default_val, 0);
+
+ add_local_reloption(relopts, (relopt_gen *) newoption, offset);
+}
/*
* init_real_reloption
@@ -1617,6 +1689,18 @@ parse_one_reloption(relopt_value *option, char *text_str, int text_len,
option->gen->name, value)));
}
break;
+ case RELOPT_TYPE_TERNARY:
+ {
+ bool b;
+ parsed = parse_bool(value, &b);
+ option->values.ternary_val = b ? TERNARY_TRUE : TERNARY_FALSE;
+ if (validate && !parsed)
+ ereport(ERROR,
+ (errcode(ERRCODE_INVALID_PARAMETER_VALUE),
+ errmsg("invalid value for ternary option \"%s\": %s",
+ option->gen->name, value)));
+ }
+ break;
case RELOPT_TYPE_INT:
{
relopt_int *optint = (relopt_int *) option->gen;
@@ -1780,17 +1864,6 @@ fillRelOptions(void *rdopts, Size basesize,
char *itempos = ((char *) rdopts) + elems[j].offset;
char *string_val;
- /*
- * If isset_offset is provided, store whether the reloption is
- * set there.
- */
- if (elems[j].isset_offset > 0)
- {
- char *setpos = ((char *) rdopts) + elems[j].isset_offset;
-
- *(bool *) setpos = options[i].isset;
- }
-
switch (options[i].gen->type)
{
case RELOPT_TYPE_BOOL:
@@ -1798,6 +1871,11 @@ fillRelOptions(void *rdopts, Size basesize,
options[i].values.bool_val :
((relopt_bool *) options[i].gen)->default_val;
break;
+ case RELOPT_TYPE_TERNARY:
+ *(ternary *) itempos = options[i].isset ?
+ options[i].values.ternary_val :
+ ((relopt_ternary *) options[i].gen)->default_val;
+ break;
case RELOPT_TYPE_INT:
*(int *) itempos = options[i].isset ?
options[i].values.int_val :
@@ -1912,8 +1990,8 @@ default_reloptions(Datum reloptions, bool validate, relopt_kind kind)
offsetof(StdRdOptions, parallel_workers)},
{"vacuum_index_cleanup", RELOPT_TYPE_ENUM,
offsetof(StdRdOptions, vacuum_index_cleanup)},
- {"vacuum_truncate", RELOPT_TYPE_BOOL,
- offsetof(StdRdOptions, vacuum_truncate), offsetof(StdRdOptions, vacuum_truncate_set)},
+ {"vacuum_truncate", RELOPT_TYPE_TERNARY,
+ offsetof(StdRdOptions, vacuum_truncate)},
{"vacuum_max_eager_freeze_failure_rate", RELOPT_TYPE_REAL,
offsetof(StdRdOptions, vacuum_max_eager_freeze_failure_rate)}
};
@@ -1993,7 +2071,6 @@ build_local_reloptions(local_relopts *relopts, Datum options, bool validate)
elems[i].optname = opt->option->name;
elems[i].opttype = opt->option->type;
elems[i].offset = opt->offset;
- elems[i].isset_offset = 0; /* not supported for local relopts yet */
i++;
}
diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c
index 733ef40ae7..977babff54 100644
--- a/src/backend/commands/vacuum.c
+++ b/src/backend/commands/vacuum.c
@@ -2221,9 +2221,9 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams params,
{
StdRdOptions *opts = (StdRdOptions *) rel->rd_options;
- if (opts && opts->vacuum_truncate_set)
+ if (opts && opts->vacuum_truncate != TERNARY_UNSET)
{
- if (opts->vacuum_truncate)
+ if (opts->vacuum_truncate == TERNARY_TRUE)
params.truncate = VACOPTVALUE_ENABLED;
else
params.truncate = VACOPTVALUE_DISABLED;
diff --git a/src/include/access/reloptions.h b/src/include/access/reloptions.h
index a604a4702c..a436697658 100644
--- a/src/include/access/reloptions.h
+++ b/src/include/access/reloptions.h
@@ -29,6 +29,7 @@
typedef enum relopt_type
{
RELOPT_TYPE_BOOL,
+ RELOPT_TYPE_TERNARY, /* on, off, unset */
RELOPT_TYPE_INT,
RELOPT_TYPE_REAL,
RELOPT_TYPE_ENUM,
@@ -80,6 +81,7 @@ typedef struct relopt_value
union
{
bool bool_val;
+ ternary ternary_val;
int int_val;
double real_val;
int enum_val;
@@ -94,6 +96,12 @@ typedef struct relopt_bool
bool default_val;
} relopt_bool;
+typedef struct relopt_rernary
+{
+ relopt_gen gen;
+ int default_val;
+} relopt_ternary;
+
typedef struct relopt_int
{
relopt_gen gen;
@@ -152,19 +160,6 @@ typedef struct
const char *optname; /* option's name */
relopt_type opttype; /* option's datatype */
int offset; /* offset of field in result struct */
-
- /*
- * isset_offset is an optional offset of a field in the result struct that
- * stores whether the option is explicitly set for the relation or if it
- * just picked up the default value. In most cases, this can be
- * accomplished by giving the reloption a special out-of-range default
- * value (e.g., some integer reloptions use -2), but this isn't always
- * possible. For example, a Boolean reloption cannot be given an
- * out-of-range default, so we need another way to discover the source of
- * its value. This offset is only used if given a value greater than
- * zero.
- */
- int isset_offset;
} relopt_parse_elt;
/* Local reloption definition */
@@ -195,6 +190,8 @@ typedef struct local_relopts
extern relopt_kind add_reloption_kind(void);
extern void add_bool_reloption(bits32 kinds, const char *name, const char *desc,
bool default_val, LOCKMODE lockmode);
+extern void add_ternary_reloption(bits32 kinds, const char *name,
+ const char *desc, int default_val, LOCKMODE lockmode);
extern void add_int_reloption(bits32 kinds, const char *name, const char *desc,
int default_val, int min_val, int max_val,
LOCKMODE lockmode);
@@ -214,6 +211,9 @@ extern void register_reloptions_validator(local_relopts *relopts,
extern void add_local_bool_reloption(local_relopts *relopts, const char *name,
const char *desc, bool default_val,
int offset);
+extern void add_local_ternary_reloption(local_relopts *relopts,
+ const char *name, const char *desc,
+ ternary default_val, int offset);
extern void add_local_int_reloption(local_relopts *relopts, const char *name,
const char *desc, int default_val,
int min_val, int max_val, int offset);
diff --git a/src/include/c.h b/src/include/c.h
index 39022f8a9d..9c59ccccf8 100644
--- a/src/include/c.h
+++ b/src/include/c.h
@@ -488,6 +488,22 @@ typedef void (*pg_funcptr_t) (void);
#include <stdbool.h>
+/*
+ * ternary
+ * Boolean value with an extrea "unset" option
+ *
+ * Ternary data type is used in relation options that can be "true", "false" or
+ * "unset". Since relation options are used deep inside the PostgreSQL code,
+ * this type is declared globally.
+*/
+
+typedef enum ternary
+{
+ TERNARY_FALSE = 0,
+ TERNARY_TRUE = 1,
+ TERNARY_UNSET = -1
+} ternary;
+
/* ----------------------------------------------------------------
* Section 3: standard system types
diff --git a/src/include/utils/rel.h b/src/include/utils/rel.h
index b552359915..7346d618f9 100644
--- a/src/include/utils/rel.h
+++ b/src/include/utils/rel.h
@@ -346,8 +346,7 @@ typedef struct StdRdOptions
bool user_catalog_table; /* use as an additional catalog relation */
int parallel_workers; /* max number of parallel workers */
StdRdOptIndexCleanup vacuum_index_cleanup; /* controls index vacuuming */
- bool vacuum_truncate; /* enables vacuum to truncate a relation */
- bool vacuum_truncate_set; /* whether vacuum_truncate is set */
+ ternary vacuum_truncate; /* enables vacuum to truncate a relation */
/*
* Fraction of pages in a relation that vacuum can eagerly scan and fail
--
2.39.2
--nextPart5129932.5fSG56mABF
Content-Disposition: attachment;
filename="v2-0003-Add-alias-to-be-used-as-unset-state.patch"
Content-Transfer-Encoding: 7Bit
Content-Type: text/x-patch; charset="unicode-2-0-utf-8";
name="v2-0003-Add-alias-to-be-used-as-unset-state.patch"
^ permalink raw reply [nested|flat] 868+ messages in thread
* [PATCH v2 2/4] Introduce ternary reloptions
@ 2025-09-04 14:41 Nikolay Shaplov <[email protected]>
0 siblings, 0 replies; 868+ messages in thread
From: Nikolay Shaplov @ 2025-09-04 14:41 UTC (permalink / raw)
Introduce ternary reloption as a replacement for current `vacuum_truncate`
implementation. Remove `vacuum_truncate_set` additional flag and using
`TERNARY_UNSET` value instead.
---
src/backend/access/common/reloptions.c | 129 ++++++++++++++++++++-----
src/backend/commands/vacuum.c | 4 +-
src/include/access/reloptions.h | 26 ++---
src/include/c.h | 16 +++
src/include/utils/rel.h | 3 +-
5 files changed, 135 insertions(+), 43 deletions(-)
diff --git a/src/backend/access/common/reloptions.c b/src/backend/access/common/reloptions.c
index 0af3fea68f..f543f11001 100644
--- a/src/backend/access/common/reloptions.c
+++ b/src/backend/access/common/reloptions.c
@@ -40,9 +40,9 @@
*
* To add an option:
*
- * (i) decide on a type (bool, integer, real, enum, string), name, default
- * value, upper and lower bounds (if applicable); for strings, consider a
- * validation routine.
+ * (i) decide on a type (bool, ternary, integer, real, enum, string), name,
+ * default value, upper and lower bounds (if applicable); for strings,
+ * consider a validation routine.
* (ii) add a record below (or use add_<type>_reloption).
* (iii) add it to the appropriate options struct (perhaps StdRdOptions)
* (iv) add it to the appropriate handling routine (perhaps
@@ -147,15 +147,6 @@ static relopt_bool boolRelOpts[] =
},
false
},
- {
- {
- "vacuum_truncate",
- "Enables vacuum to truncate empty pages at the end of this table",
- RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
- ShareUpdateExclusiveLock
- },
- true
- },
{
{
"deduplicate_items",
@@ -170,6 +161,21 @@ static relopt_bool boolRelOpts[] =
{{NULL}}
};
+static relopt_ternary ternaryRelOpts[] =
+{
+ {
+ {
+ "vacuum_truncate",
+ "Enables vacuum to truncate empty pages at the end of this table",
+ RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
+ ShareUpdateExclusiveLock
+ },
+ TERNARY_UNSET
+ },
+ /* list terminator */
+ {{NULL}}
+};
+
static relopt_int intRelOpts[] =
{
{
@@ -600,6 +606,13 @@ initialize_reloptions(void)
boolRelOpts[i].gen.lockmode));
j++;
}
+ for (i = 0; ternaryRelOpts[i].gen.name; i++)
+ {
+ Assert(DoLockModesConflict(ternaryRelOpts[i].gen.lockmode,
+ ternaryRelOpts[i].gen.lockmode));
+ j++;
+ }
+
for (i = 0; intRelOpts[i].gen.name; i++)
{
Assert(DoLockModesConflict(intRelOpts[i].gen.lockmode,
@@ -640,6 +653,14 @@ initialize_reloptions(void)
j++;
}
+ for (i = 0; ternaryRelOpts[i].gen.name; i++)
+ {
+ relOpts[j] = &ternaryRelOpts[i].gen;
+ relOpts[j]->type = RELOPT_TYPE_TERNARY;
+ relOpts[j]->namelen = strlen(relOpts[j]->name);
+ j++;
+ }
+
for (i = 0; intRelOpts[i].gen.name; i++)
{
relOpts[j] = &intRelOpts[i].gen;
@@ -800,6 +821,9 @@ allocate_reloption(bits32 kinds, int type, const char *name, const char *desc,
case RELOPT_TYPE_BOOL:
size = sizeof(relopt_bool);
break;
+ case RELOPT_TYPE_TERNARY:
+ size = sizeof(relopt_ternary);
+ break;
case RELOPT_TYPE_INT:
size = sizeof(relopt_int);
break;
@@ -883,6 +907,54 @@ add_local_bool_reloption(local_relopts *relopts, const char *name,
add_local_reloption(relopts, (relopt_gen *) newoption, offset);
}
+/*
+ * init_ternary_reloption
+ * Allocate and initialize a new ternary reloption
+ */
+static relopt_ternary *
+init_ternary_reloption(bits32 kinds, const char *name, const char *desc,
+ ternary default_val, LOCKMODE lockmode)
+{
+ relopt_ternary *newoption;
+
+ newoption = (relopt_ternary *) allocate_reloption(kinds,
+ RELOPT_TYPE_TERNARY, name, desc, lockmode);
+ newoption->default_val = default_val;
+
+ return newoption;
+}
+
+/*
+ * add_ternary_reloption
+ * Add a new ternary reloption
+ */
+void
+add_ternary_reloption(bits32 kinds, const char *name, const char *desc,
+ ternary default_val, LOCKMODE lockmode)
+{
+ relopt_ternary *newoption = init_ternary_reloption(kinds, name, desc,
+ default_val, lockmode);
+
+ add_reloption((relopt_gen *) newoption);
+}
+
+/*
+ * add_local_ternary_reloption
+ * Add a new ternary local reloption
+ *
+ * 'offset' is offset of ternary-typed field.
+ */
+void
+add_local_ternary_reloption(local_relopts *relopts, const char *name,
+ const char *desc, ternary default_val,
+ int offset)
+{
+ relopt_ternary *newoption = init_ternary_reloption(RELOPT_KIND_LOCAL,
+ name, desc,
+ default_val, 0);
+
+ add_local_reloption(relopts, (relopt_gen *) newoption, offset);
+}
/*
* init_real_reloption
@@ -1617,6 +1689,18 @@ parse_one_reloption(relopt_value *option, char *text_str, int text_len,
option->gen->name, value)));
}
break;
+ case RELOPT_TYPE_TERNARY:
+ {
+ bool b;
+ parsed = parse_bool(value, &b);
+ option->values.ternary_val = b ? TERNARY_TRUE : TERNARY_FALSE;
+ if (validate && !parsed)
+ ereport(ERROR,
+ (errcode(ERRCODE_INVALID_PARAMETER_VALUE),
+ errmsg("invalid value for ternary option \"%s\": %s",
+ option->gen->name, value)));
+ }
+ break;
case RELOPT_TYPE_INT:
{
relopt_int *optint = (relopt_int *) option->gen;
@@ -1780,17 +1864,6 @@ fillRelOptions(void *rdopts, Size basesize,
char *itempos = ((char *) rdopts) + elems[j].offset;
char *string_val;
- /*
- * If isset_offset is provided, store whether the reloption is
- * set there.
- */
- if (elems[j].isset_offset > 0)
- {
- char *setpos = ((char *) rdopts) + elems[j].isset_offset;
-
- *(bool *) setpos = options[i].isset;
- }
-
switch (options[i].gen->type)
{
case RELOPT_TYPE_BOOL:
@@ -1798,6 +1871,11 @@ fillRelOptions(void *rdopts, Size basesize,
options[i].values.bool_val :
((relopt_bool *) options[i].gen)->default_val;
break;
+ case RELOPT_TYPE_TERNARY:
+ *(ternary *) itempos = options[i].isset ?
+ options[i].values.ternary_val :
+ ((relopt_ternary *) options[i].gen)->default_val;
+ break;
case RELOPT_TYPE_INT:
*(int *) itempos = options[i].isset ?
options[i].values.int_val :
@@ -1912,8 +1990,8 @@ default_reloptions(Datum reloptions, bool validate, relopt_kind kind)
offsetof(StdRdOptions, parallel_workers)},
{"vacuum_index_cleanup", RELOPT_TYPE_ENUM,
offsetof(StdRdOptions, vacuum_index_cleanup)},
- {"vacuum_truncate", RELOPT_TYPE_BOOL,
- offsetof(StdRdOptions, vacuum_truncate), offsetof(StdRdOptions, vacuum_truncate_set)},
+ {"vacuum_truncate", RELOPT_TYPE_TERNARY,
+ offsetof(StdRdOptions, vacuum_truncate)},
{"vacuum_max_eager_freeze_failure_rate", RELOPT_TYPE_REAL,
offsetof(StdRdOptions, vacuum_max_eager_freeze_failure_rate)}
};
@@ -1993,7 +2071,6 @@ build_local_reloptions(local_relopts *relopts, Datum options, bool validate)
elems[i].optname = opt->option->name;
elems[i].opttype = opt->option->type;
elems[i].offset = opt->offset;
- elems[i].isset_offset = 0; /* not supported for local relopts yet */
i++;
}
diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c
index 733ef40ae7..977babff54 100644
--- a/src/backend/commands/vacuum.c
+++ b/src/backend/commands/vacuum.c
@@ -2221,9 +2221,9 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams params,
{
StdRdOptions *opts = (StdRdOptions *) rel->rd_options;
- if (opts && opts->vacuum_truncate_set)
+ if (opts && opts->vacuum_truncate != TERNARY_UNSET)
{
- if (opts->vacuum_truncate)
+ if (opts->vacuum_truncate == TERNARY_TRUE)
params.truncate = VACOPTVALUE_ENABLED;
else
params.truncate = VACOPTVALUE_DISABLED;
diff --git a/src/include/access/reloptions.h b/src/include/access/reloptions.h
index a604a4702c..a436697658 100644
--- a/src/include/access/reloptions.h
+++ b/src/include/access/reloptions.h
@@ -29,6 +29,7 @@
typedef enum relopt_type
{
RELOPT_TYPE_BOOL,
+ RELOPT_TYPE_TERNARY, /* on, off, unset */
RELOPT_TYPE_INT,
RELOPT_TYPE_REAL,
RELOPT_TYPE_ENUM,
@@ -80,6 +81,7 @@ typedef struct relopt_value
union
{
bool bool_val;
+ ternary ternary_val;
int int_val;
double real_val;
int enum_val;
@@ -94,6 +96,12 @@ typedef struct relopt_bool
bool default_val;
} relopt_bool;
+typedef struct relopt_rernary
+{
+ relopt_gen gen;
+ int default_val;
+} relopt_ternary;
+
typedef struct relopt_int
{
relopt_gen gen;
@@ -152,19 +160,6 @@ typedef struct
const char *optname; /* option's name */
relopt_type opttype; /* option's datatype */
int offset; /* offset of field in result struct */
-
- /*
- * isset_offset is an optional offset of a field in the result struct that
- * stores whether the option is explicitly set for the relation or if it
- * just picked up the default value. In most cases, this can be
- * accomplished by giving the reloption a special out-of-range default
- * value (e.g., some integer reloptions use -2), but this isn't always
- * possible. For example, a Boolean reloption cannot be given an
- * out-of-range default, so we need another way to discover the source of
- * its value. This offset is only used if given a value greater than
- * zero.
- */
- int isset_offset;
} relopt_parse_elt;
/* Local reloption definition */
@@ -195,6 +190,8 @@ typedef struct local_relopts
extern relopt_kind add_reloption_kind(void);
extern void add_bool_reloption(bits32 kinds, const char *name, const char *desc,
bool default_val, LOCKMODE lockmode);
+extern void add_ternary_reloption(bits32 kinds, const char *name,
+ const char *desc, int default_val, LOCKMODE lockmode);
extern void add_int_reloption(bits32 kinds, const char *name, const char *desc,
int default_val, int min_val, int max_val,
LOCKMODE lockmode);
@@ -214,6 +211,9 @@ extern void register_reloptions_validator(local_relopts *relopts,
extern void add_local_bool_reloption(local_relopts *relopts, const char *name,
const char *desc, bool default_val,
int offset);
+extern void add_local_ternary_reloption(local_relopts *relopts,
+ const char *name, const char *desc,
+ ternary default_val, int offset);
extern void add_local_int_reloption(local_relopts *relopts, const char *name,
const char *desc, int default_val,
int min_val, int max_val, int offset);
diff --git a/src/include/c.h b/src/include/c.h
index 39022f8a9d..9c59ccccf8 100644
--- a/src/include/c.h
+++ b/src/include/c.h
@@ -488,6 +488,22 @@ typedef void (*pg_funcptr_t) (void);
#include <stdbool.h>
+/*
+ * ternary
+ * Boolean value with an extrea "unset" option
+ *
+ * Ternary data type is used in relation options that can be "true", "false" or
+ * "unset". Since relation options are used deep inside the PostgreSQL code,
+ * this type is declared globally.
+*/
+
+typedef enum ternary
+{
+ TERNARY_FALSE = 0,
+ TERNARY_TRUE = 1,
+ TERNARY_UNSET = -1
+} ternary;
+
/* ----------------------------------------------------------------
* Section 3: standard system types
diff --git a/src/include/utils/rel.h b/src/include/utils/rel.h
index b552359915..7346d618f9 100644
--- a/src/include/utils/rel.h
+++ b/src/include/utils/rel.h
@@ -346,8 +346,7 @@ typedef struct StdRdOptions
bool user_catalog_table; /* use as an additional catalog relation */
int parallel_workers; /* max number of parallel workers */
StdRdOptIndexCleanup vacuum_index_cleanup; /* controls index vacuuming */
- bool vacuum_truncate; /* enables vacuum to truncate a relation */
- bool vacuum_truncate_set; /* whether vacuum_truncate is set */
+ ternary vacuum_truncate; /* enables vacuum to truncate a relation */
/*
* Fraction of pages in a relation that vacuum can eagerly scan and fail
--
2.39.2
--nextPart5129932.5fSG56mABF
Content-Disposition: attachment;
filename="v2-0003-Add-alias-to-be-used-as-unset-state.patch"
Content-Transfer-Encoding: 7Bit
Content-Type: text/x-patch; charset="unicode-2-0-utf-8";
name="v2-0003-Add-alias-to-be-used-as-unset-state.patch"
^ permalink raw reply [nested|flat] 868+ messages in thread
* [PATCH v2 2/4] Introduce ternary reloptions
@ 2025-09-04 14:41 Nikolay Shaplov <[email protected]>
0 siblings, 0 replies; 868+ messages in thread
From: Nikolay Shaplov @ 2025-09-04 14:41 UTC (permalink / raw)
Introduce ternary reloption as a replacement for current `vacuum_truncate`
implementation. Remove `vacuum_truncate_set` additional flag and using
`TERNARY_UNSET` value instead.
---
src/backend/access/common/reloptions.c | 129 ++++++++++++++++++++-----
src/backend/commands/vacuum.c | 4 +-
src/include/access/reloptions.h | 26 ++---
src/include/c.h | 16 +++
src/include/utils/rel.h | 3 +-
5 files changed, 135 insertions(+), 43 deletions(-)
diff --git a/src/backend/access/common/reloptions.c b/src/backend/access/common/reloptions.c
index 0af3fea68f..f543f11001 100644
--- a/src/backend/access/common/reloptions.c
+++ b/src/backend/access/common/reloptions.c
@@ -40,9 +40,9 @@
*
* To add an option:
*
- * (i) decide on a type (bool, integer, real, enum, string), name, default
- * value, upper and lower bounds (if applicable); for strings, consider a
- * validation routine.
+ * (i) decide on a type (bool, ternary, integer, real, enum, string), name,
+ * default value, upper and lower bounds (if applicable); for strings,
+ * consider a validation routine.
* (ii) add a record below (or use add_<type>_reloption).
* (iii) add it to the appropriate options struct (perhaps StdRdOptions)
* (iv) add it to the appropriate handling routine (perhaps
@@ -147,15 +147,6 @@ static relopt_bool boolRelOpts[] =
},
false
},
- {
- {
- "vacuum_truncate",
- "Enables vacuum to truncate empty pages at the end of this table",
- RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
- ShareUpdateExclusiveLock
- },
- true
- },
{
{
"deduplicate_items",
@@ -170,6 +161,21 @@ static relopt_bool boolRelOpts[] =
{{NULL}}
};
+static relopt_ternary ternaryRelOpts[] =
+{
+ {
+ {
+ "vacuum_truncate",
+ "Enables vacuum to truncate empty pages at the end of this table",
+ RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
+ ShareUpdateExclusiveLock
+ },
+ TERNARY_UNSET
+ },
+ /* list terminator */
+ {{NULL}}
+};
+
static relopt_int intRelOpts[] =
{
{
@@ -600,6 +606,13 @@ initialize_reloptions(void)
boolRelOpts[i].gen.lockmode));
j++;
}
+ for (i = 0; ternaryRelOpts[i].gen.name; i++)
+ {
+ Assert(DoLockModesConflict(ternaryRelOpts[i].gen.lockmode,
+ ternaryRelOpts[i].gen.lockmode));
+ j++;
+ }
+
for (i = 0; intRelOpts[i].gen.name; i++)
{
Assert(DoLockModesConflict(intRelOpts[i].gen.lockmode,
@@ -640,6 +653,14 @@ initialize_reloptions(void)
j++;
}
+ for (i = 0; ternaryRelOpts[i].gen.name; i++)
+ {
+ relOpts[j] = &ternaryRelOpts[i].gen;
+ relOpts[j]->type = RELOPT_TYPE_TERNARY;
+ relOpts[j]->namelen = strlen(relOpts[j]->name);
+ j++;
+ }
+
for (i = 0; intRelOpts[i].gen.name; i++)
{
relOpts[j] = &intRelOpts[i].gen;
@@ -800,6 +821,9 @@ allocate_reloption(bits32 kinds, int type, const char *name, const char *desc,
case RELOPT_TYPE_BOOL:
size = sizeof(relopt_bool);
break;
+ case RELOPT_TYPE_TERNARY:
+ size = sizeof(relopt_ternary);
+ break;
case RELOPT_TYPE_INT:
size = sizeof(relopt_int);
break;
@@ -883,6 +907,54 @@ add_local_bool_reloption(local_relopts *relopts, const char *name,
add_local_reloption(relopts, (relopt_gen *) newoption, offset);
}
+/*
+ * init_ternary_reloption
+ * Allocate and initialize a new ternary reloption
+ */
+static relopt_ternary *
+init_ternary_reloption(bits32 kinds, const char *name, const char *desc,
+ ternary default_val, LOCKMODE lockmode)
+{
+ relopt_ternary *newoption;
+
+ newoption = (relopt_ternary *) allocate_reloption(kinds,
+ RELOPT_TYPE_TERNARY, name, desc, lockmode);
+ newoption->default_val = default_val;
+
+ return newoption;
+}
+
+/*
+ * add_ternary_reloption
+ * Add a new ternary reloption
+ */
+void
+add_ternary_reloption(bits32 kinds, const char *name, const char *desc,
+ ternary default_val, LOCKMODE lockmode)
+{
+ relopt_ternary *newoption = init_ternary_reloption(kinds, name, desc,
+ default_val, lockmode);
+
+ add_reloption((relopt_gen *) newoption);
+}
+
+/*
+ * add_local_ternary_reloption
+ * Add a new ternary local reloption
+ *
+ * 'offset' is offset of ternary-typed field.
+ */
+void
+add_local_ternary_reloption(local_relopts *relopts, const char *name,
+ const char *desc, ternary default_val,
+ int offset)
+{
+ relopt_ternary *newoption = init_ternary_reloption(RELOPT_KIND_LOCAL,
+ name, desc,
+ default_val, 0);
+
+ add_local_reloption(relopts, (relopt_gen *) newoption, offset);
+}
/*
* init_real_reloption
@@ -1617,6 +1689,18 @@ parse_one_reloption(relopt_value *option, char *text_str, int text_len,
option->gen->name, value)));
}
break;
+ case RELOPT_TYPE_TERNARY:
+ {
+ bool b;
+ parsed = parse_bool(value, &b);
+ option->values.ternary_val = b ? TERNARY_TRUE : TERNARY_FALSE;
+ if (validate && !parsed)
+ ereport(ERROR,
+ (errcode(ERRCODE_INVALID_PARAMETER_VALUE),
+ errmsg("invalid value for ternary option \"%s\": %s",
+ option->gen->name, value)));
+ }
+ break;
case RELOPT_TYPE_INT:
{
relopt_int *optint = (relopt_int *) option->gen;
@@ -1780,17 +1864,6 @@ fillRelOptions(void *rdopts, Size basesize,
char *itempos = ((char *) rdopts) + elems[j].offset;
char *string_val;
- /*
- * If isset_offset is provided, store whether the reloption is
- * set there.
- */
- if (elems[j].isset_offset > 0)
- {
- char *setpos = ((char *) rdopts) + elems[j].isset_offset;
-
- *(bool *) setpos = options[i].isset;
- }
-
switch (options[i].gen->type)
{
case RELOPT_TYPE_BOOL:
@@ -1798,6 +1871,11 @@ fillRelOptions(void *rdopts, Size basesize,
options[i].values.bool_val :
((relopt_bool *) options[i].gen)->default_val;
break;
+ case RELOPT_TYPE_TERNARY:
+ *(ternary *) itempos = options[i].isset ?
+ options[i].values.ternary_val :
+ ((relopt_ternary *) options[i].gen)->default_val;
+ break;
case RELOPT_TYPE_INT:
*(int *) itempos = options[i].isset ?
options[i].values.int_val :
@@ -1912,8 +1990,8 @@ default_reloptions(Datum reloptions, bool validate, relopt_kind kind)
offsetof(StdRdOptions, parallel_workers)},
{"vacuum_index_cleanup", RELOPT_TYPE_ENUM,
offsetof(StdRdOptions, vacuum_index_cleanup)},
- {"vacuum_truncate", RELOPT_TYPE_BOOL,
- offsetof(StdRdOptions, vacuum_truncate), offsetof(StdRdOptions, vacuum_truncate_set)},
+ {"vacuum_truncate", RELOPT_TYPE_TERNARY,
+ offsetof(StdRdOptions, vacuum_truncate)},
{"vacuum_max_eager_freeze_failure_rate", RELOPT_TYPE_REAL,
offsetof(StdRdOptions, vacuum_max_eager_freeze_failure_rate)}
};
@@ -1993,7 +2071,6 @@ build_local_reloptions(local_relopts *relopts, Datum options, bool validate)
elems[i].optname = opt->option->name;
elems[i].opttype = opt->option->type;
elems[i].offset = opt->offset;
- elems[i].isset_offset = 0; /* not supported for local relopts yet */
i++;
}
diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c
index 733ef40ae7..977babff54 100644
--- a/src/backend/commands/vacuum.c
+++ b/src/backend/commands/vacuum.c
@@ -2221,9 +2221,9 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams params,
{
StdRdOptions *opts = (StdRdOptions *) rel->rd_options;
- if (opts && opts->vacuum_truncate_set)
+ if (opts && opts->vacuum_truncate != TERNARY_UNSET)
{
- if (opts->vacuum_truncate)
+ if (opts->vacuum_truncate == TERNARY_TRUE)
params.truncate = VACOPTVALUE_ENABLED;
else
params.truncate = VACOPTVALUE_DISABLED;
diff --git a/src/include/access/reloptions.h b/src/include/access/reloptions.h
index a604a4702c..a436697658 100644
--- a/src/include/access/reloptions.h
+++ b/src/include/access/reloptions.h
@@ -29,6 +29,7 @@
typedef enum relopt_type
{
RELOPT_TYPE_BOOL,
+ RELOPT_TYPE_TERNARY, /* on, off, unset */
RELOPT_TYPE_INT,
RELOPT_TYPE_REAL,
RELOPT_TYPE_ENUM,
@@ -80,6 +81,7 @@ typedef struct relopt_value
union
{
bool bool_val;
+ ternary ternary_val;
int int_val;
double real_val;
int enum_val;
@@ -94,6 +96,12 @@ typedef struct relopt_bool
bool default_val;
} relopt_bool;
+typedef struct relopt_rernary
+{
+ relopt_gen gen;
+ int default_val;
+} relopt_ternary;
+
typedef struct relopt_int
{
relopt_gen gen;
@@ -152,19 +160,6 @@ typedef struct
const char *optname; /* option's name */
relopt_type opttype; /* option's datatype */
int offset; /* offset of field in result struct */
-
- /*
- * isset_offset is an optional offset of a field in the result struct that
- * stores whether the option is explicitly set for the relation or if it
- * just picked up the default value. In most cases, this can be
- * accomplished by giving the reloption a special out-of-range default
- * value (e.g., some integer reloptions use -2), but this isn't always
- * possible. For example, a Boolean reloption cannot be given an
- * out-of-range default, so we need another way to discover the source of
- * its value. This offset is only used if given a value greater than
- * zero.
- */
- int isset_offset;
} relopt_parse_elt;
/* Local reloption definition */
@@ -195,6 +190,8 @@ typedef struct local_relopts
extern relopt_kind add_reloption_kind(void);
extern void add_bool_reloption(bits32 kinds, const char *name, const char *desc,
bool default_val, LOCKMODE lockmode);
+extern void add_ternary_reloption(bits32 kinds, const char *name,
+ const char *desc, int default_val, LOCKMODE lockmode);
extern void add_int_reloption(bits32 kinds, const char *name, const char *desc,
int default_val, int min_val, int max_val,
LOCKMODE lockmode);
@@ -214,6 +211,9 @@ extern void register_reloptions_validator(local_relopts *relopts,
extern void add_local_bool_reloption(local_relopts *relopts, const char *name,
const char *desc, bool default_val,
int offset);
+extern void add_local_ternary_reloption(local_relopts *relopts,
+ const char *name, const char *desc,
+ ternary default_val, int offset);
extern void add_local_int_reloption(local_relopts *relopts, const char *name,
const char *desc, int default_val,
int min_val, int max_val, int offset);
diff --git a/src/include/c.h b/src/include/c.h
index 39022f8a9d..9c59ccccf8 100644
--- a/src/include/c.h
+++ b/src/include/c.h
@@ -488,6 +488,22 @@ typedef void (*pg_funcptr_t) (void);
#include <stdbool.h>
+/*
+ * ternary
+ * Boolean value with an extrea "unset" option
+ *
+ * Ternary data type is used in relation options that can be "true", "false" or
+ * "unset". Since relation options are used deep inside the PostgreSQL code,
+ * this type is declared globally.
+*/
+
+typedef enum ternary
+{
+ TERNARY_FALSE = 0,
+ TERNARY_TRUE = 1,
+ TERNARY_UNSET = -1
+} ternary;
+
/* ----------------------------------------------------------------
* Section 3: standard system types
diff --git a/src/include/utils/rel.h b/src/include/utils/rel.h
index b552359915..7346d618f9 100644
--- a/src/include/utils/rel.h
+++ b/src/include/utils/rel.h
@@ -346,8 +346,7 @@ typedef struct StdRdOptions
bool user_catalog_table; /* use as an additional catalog relation */
int parallel_workers; /* max number of parallel workers */
StdRdOptIndexCleanup vacuum_index_cleanup; /* controls index vacuuming */
- bool vacuum_truncate; /* enables vacuum to truncate a relation */
- bool vacuum_truncate_set; /* whether vacuum_truncate is set */
+ ternary vacuum_truncate; /* enables vacuum to truncate a relation */
/*
* Fraction of pages in a relation that vacuum can eagerly scan and fail
--
2.39.2
--nextPart5129932.5fSG56mABF
Content-Disposition: attachment;
filename="v2-0003-Add-alias-to-be-used-as-unset-state.patch"
Content-Transfer-Encoding: 7Bit
Content-Type: text/x-patch; charset="unicode-2-0-utf-8";
name="v2-0003-Add-alias-to-be-used-as-unset-state.patch"
^ permalink raw reply [nested|flat] 868+ messages in thread
* [PATCH v2 2/4] Introduce ternary reloptions
@ 2025-09-04 14:41 Nikolay Shaplov <[email protected]>
0 siblings, 0 replies; 868+ messages in thread
From: Nikolay Shaplov @ 2025-09-04 14:41 UTC (permalink / raw)
Introduce ternary reloption as a replacement for current `vacuum_truncate`
implementation. Remove `vacuum_truncate_set` additional flag and using
`TERNARY_UNSET` value instead.
---
src/backend/access/common/reloptions.c | 129 ++++++++++++++++++++-----
src/backend/commands/vacuum.c | 4 +-
src/include/access/reloptions.h | 26 ++---
src/include/c.h | 16 +++
src/include/utils/rel.h | 3 +-
5 files changed, 135 insertions(+), 43 deletions(-)
diff --git a/src/backend/access/common/reloptions.c b/src/backend/access/common/reloptions.c
index 0af3fea68f..f543f11001 100644
--- a/src/backend/access/common/reloptions.c
+++ b/src/backend/access/common/reloptions.c
@@ -40,9 +40,9 @@
*
* To add an option:
*
- * (i) decide on a type (bool, integer, real, enum, string), name, default
- * value, upper and lower bounds (if applicable); for strings, consider a
- * validation routine.
+ * (i) decide on a type (bool, ternary, integer, real, enum, string), name,
+ * default value, upper and lower bounds (if applicable); for strings,
+ * consider a validation routine.
* (ii) add a record below (or use add_<type>_reloption).
* (iii) add it to the appropriate options struct (perhaps StdRdOptions)
* (iv) add it to the appropriate handling routine (perhaps
@@ -147,15 +147,6 @@ static relopt_bool boolRelOpts[] =
},
false
},
- {
- {
- "vacuum_truncate",
- "Enables vacuum to truncate empty pages at the end of this table",
- RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
- ShareUpdateExclusiveLock
- },
- true
- },
{
{
"deduplicate_items",
@@ -170,6 +161,21 @@ static relopt_bool boolRelOpts[] =
{{NULL}}
};
+static relopt_ternary ternaryRelOpts[] =
+{
+ {
+ {
+ "vacuum_truncate",
+ "Enables vacuum to truncate empty pages at the end of this table",
+ RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
+ ShareUpdateExclusiveLock
+ },
+ TERNARY_UNSET
+ },
+ /* list terminator */
+ {{NULL}}
+};
+
static relopt_int intRelOpts[] =
{
{
@@ -600,6 +606,13 @@ initialize_reloptions(void)
boolRelOpts[i].gen.lockmode));
j++;
}
+ for (i = 0; ternaryRelOpts[i].gen.name; i++)
+ {
+ Assert(DoLockModesConflict(ternaryRelOpts[i].gen.lockmode,
+ ternaryRelOpts[i].gen.lockmode));
+ j++;
+ }
+
for (i = 0; intRelOpts[i].gen.name; i++)
{
Assert(DoLockModesConflict(intRelOpts[i].gen.lockmode,
@@ -640,6 +653,14 @@ initialize_reloptions(void)
j++;
}
+ for (i = 0; ternaryRelOpts[i].gen.name; i++)
+ {
+ relOpts[j] = &ternaryRelOpts[i].gen;
+ relOpts[j]->type = RELOPT_TYPE_TERNARY;
+ relOpts[j]->namelen = strlen(relOpts[j]->name);
+ j++;
+ }
+
for (i = 0; intRelOpts[i].gen.name; i++)
{
relOpts[j] = &intRelOpts[i].gen;
@@ -800,6 +821,9 @@ allocate_reloption(bits32 kinds, int type, const char *name, const char *desc,
case RELOPT_TYPE_BOOL:
size = sizeof(relopt_bool);
break;
+ case RELOPT_TYPE_TERNARY:
+ size = sizeof(relopt_ternary);
+ break;
case RELOPT_TYPE_INT:
size = sizeof(relopt_int);
break;
@@ -883,6 +907,54 @@ add_local_bool_reloption(local_relopts *relopts, const char *name,
add_local_reloption(relopts, (relopt_gen *) newoption, offset);
}
+/*
+ * init_ternary_reloption
+ * Allocate and initialize a new ternary reloption
+ */
+static relopt_ternary *
+init_ternary_reloption(bits32 kinds, const char *name, const char *desc,
+ ternary default_val, LOCKMODE lockmode)
+{
+ relopt_ternary *newoption;
+
+ newoption = (relopt_ternary *) allocate_reloption(kinds,
+ RELOPT_TYPE_TERNARY, name, desc, lockmode);
+ newoption->default_val = default_val;
+
+ return newoption;
+}
+
+/*
+ * add_ternary_reloption
+ * Add a new ternary reloption
+ */
+void
+add_ternary_reloption(bits32 kinds, const char *name, const char *desc,
+ ternary default_val, LOCKMODE lockmode)
+{
+ relopt_ternary *newoption = init_ternary_reloption(kinds, name, desc,
+ default_val, lockmode);
+
+ add_reloption((relopt_gen *) newoption);
+}
+
+/*
+ * add_local_ternary_reloption
+ * Add a new ternary local reloption
+ *
+ * 'offset' is offset of ternary-typed field.
+ */
+void
+add_local_ternary_reloption(local_relopts *relopts, const char *name,
+ const char *desc, ternary default_val,
+ int offset)
+{
+ relopt_ternary *newoption = init_ternary_reloption(RELOPT_KIND_LOCAL,
+ name, desc,
+ default_val, 0);
+
+ add_local_reloption(relopts, (relopt_gen *) newoption, offset);
+}
/*
* init_real_reloption
@@ -1617,6 +1689,18 @@ parse_one_reloption(relopt_value *option, char *text_str, int text_len,
option->gen->name, value)));
}
break;
+ case RELOPT_TYPE_TERNARY:
+ {
+ bool b;
+ parsed = parse_bool(value, &b);
+ option->values.ternary_val = b ? TERNARY_TRUE : TERNARY_FALSE;
+ if (validate && !parsed)
+ ereport(ERROR,
+ (errcode(ERRCODE_INVALID_PARAMETER_VALUE),
+ errmsg("invalid value for ternary option \"%s\": %s",
+ option->gen->name, value)));
+ }
+ break;
case RELOPT_TYPE_INT:
{
relopt_int *optint = (relopt_int *) option->gen;
@@ -1780,17 +1864,6 @@ fillRelOptions(void *rdopts, Size basesize,
char *itempos = ((char *) rdopts) + elems[j].offset;
char *string_val;
- /*
- * If isset_offset is provided, store whether the reloption is
- * set there.
- */
- if (elems[j].isset_offset > 0)
- {
- char *setpos = ((char *) rdopts) + elems[j].isset_offset;
-
- *(bool *) setpos = options[i].isset;
- }
-
switch (options[i].gen->type)
{
case RELOPT_TYPE_BOOL:
@@ -1798,6 +1871,11 @@ fillRelOptions(void *rdopts, Size basesize,
options[i].values.bool_val :
((relopt_bool *) options[i].gen)->default_val;
break;
+ case RELOPT_TYPE_TERNARY:
+ *(ternary *) itempos = options[i].isset ?
+ options[i].values.ternary_val :
+ ((relopt_ternary *) options[i].gen)->default_val;
+ break;
case RELOPT_TYPE_INT:
*(int *) itempos = options[i].isset ?
options[i].values.int_val :
@@ -1912,8 +1990,8 @@ default_reloptions(Datum reloptions, bool validate, relopt_kind kind)
offsetof(StdRdOptions, parallel_workers)},
{"vacuum_index_cleanup", RELOPT_TYPE_ENUM,
offsetof(StdRdOptions, vacuum_index_cleanup)},
- {"vacuum_truncate", RELOPT_TYPE_BOOL,
- offsetof(StdRdOptions, vacuum_truncate), offsetof(StdRdOptions, vacuum_truncate_set)},
+ {"vacuum_truncate", RELOPT_TYPE_TERNARY,
+ offsetof(StdRdOptions, vacuum_truncate)},
{"vacuum_max_eager_freeze_failure_rate", RELOPT_TYPE_REAL,
offsetof(StdRdOptions, vacuum_max_eager_freeze_failure_rate)}
};
@@ -1993,7 +2071,6 @@ build_local_reloptions(local_relopts *relopts, Datum options, bool validate)
elems[i].optname = opt->option->name;
elems[i].opttype = opt->option->type;
elems[i].offset = opt->offset;
- elems[i].isset_offset = 0; /* not supported for local relopts yet */
i++;
}
diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c
index 733ef40ae7..977babff54 100644
--- a/src/backend/commands/vacuum.c
+++ b/src/backend/commands/vacuum.c
@@ -2221,9 +2221,9 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams params,
{
StdRdOptions *opts = (StdRdOptions *) rel->rd_options;
- if (opts && opts->vacuum_truncate_set)
+ if (opts && opts->vacuum_truncate != TERNARY_UNSET)
{
- if (opts->vacuum_truncate)
+ if (opts->vacuum_truncate == TERNARY_TRUE)
params.truncate = VACOPTVALUE_ENABLED;
else
params.truncate = VACOPTVALUE_DISABLED;
diff --git a/src/include/access/reloptions.h b/src/include/access/reloptions.h
index a604a4702c..a436697658 100644
--- a/src/include/access/reloptions.h
+++ b/src/include/access/reloptions.h
@@ -29,6 +29,7 @@
typedef enum relopt_type
{
RELOPT_TYPE_BOOL,
+ RELOPT_TYPE_TERNARY, /* on, off, unset */
RELOPT_TYPE_INT,
RELOPT_TYPE_REAL,
RELOPT_TYPE_ENUM,
@@ -80,6 +81,7 @@ typedef struct relopt_value
union
{
bool bool_val;
+ ternary ternary_val;
int int_val;
double real_val;
int enum_val;
@@ -94,6 +96,12 @@ typedef struct relopt_bool
bool default_val;
} relopt_bool;
+typedef struct relopt_rernary
+{
+ relopt_gen gen;
+ int default_val;
+} relopt_ternary;
+
typedef struct relopt_int
{
relopt_gen gen;
@@ -152,19 +160,6 @@ typedef struct
const char *optname; /* option's name */
relopt_type opttype; /* option's datatype */
int offset; /* offset of field in result struct */
-
- /*
- * isset_offset is an optional offset of a field in the result struct that
- * stores whether the option is explicitly set for the relation or if it
- * just picked up the default value. In most cases, this can be
- * accomplished by giving the reloption a special out-of-range default
- * value (e.g., some integer reloptions use -2), but this isn't always
- * possible. For example, a Boolean reloption cannot be given an
- * out-of-range default, so we need another way to discover the source of
- * its value. This offset is only used if given a value greater than
- * zero.
- */
- int isset_offset;
} relopt_parse_elt;
/* Local reloption definition */
@@ -195,6 +190,8 @@ typedef struct local_relopts
extern relopt_kind add_reloption_kind(void);
extern void add_bool_reloption(bits32 kinds, const char *name, const char *desc,
bool default_val, LOCKMODE lockmode);
+extern void add_ternary_reloption(bits32 kinds, const char *name,
+ const char *desc, int default_val, LOCKMODE lockmode);
extern void add_int_reloption(bits32 kinds, const char *name, const char *desc,
int default_val, int min_val, int max_val,
LOCKMODE lockmode);
@@ -214,6 +211,9 @@ extern void register_reloptions_validator(local_relopts *relopts,
extern void add_local_bool_reloption(local_relopts *relopts, const char *name,
const char *desc, bool default_val,
int offset);
+extern void add_local_ternary_reloption(local_relopts *relopts,
+ const char *name, const char *desc,
+ ternary default_val, int offset);
extern void add_local_int_reloption(local_relopts *relopts, const char *name,
const char *desc, int default_val,
int min_val, int max_val, int offset);
diff --git a/src/include/c.h b/src/include/c.h
index 39022f8a9d..9c59ccccf8 100644
--- a/src/include/c.h
+++ b/src/include/c.h
@@ -488,6 +488,22 @@ typedef void (*pg_funcptr_t) (void);
#include <stdbool.h>
+/*
+ * ternary
+ * Boolean value with an extrea "unset" option
+ *
+ * Ternary data type is used in relation options that can be "true", "false" or
+ * "unset". Since relation options are used deep inside the PostgreSQL code,
+ * this type is declared globally.
+*/
+
+typedef enum ternary
+{
+ TERNARY_FALSE = 0,
+ TERNARY_TRUE = 1,
+ TERNARY_UNSET = -1
+} ternary;
+
/* ----------------------------------------------------------------
* Section 3: standard system types
diff --git a/src/include/utils/rel.h b/src/include/utils/rel.h
index b552359915..7346d618f9 100644
--- a/src/include/utils/rel.h
+++ b/src/include/utils/rel.h
@@ -346,8 +346,7 @@ typedef struct StdRdOptions
bool user_catalog_table; /* use as an additional catalog relation */
int parallel_workers; /* max number of parallel workers */
StdRdOptIndexCleanup vacuum_index_cleanup; /* controls index vacuuming */
- bool vacuum_truncate; /* enables vacuum to truncate a relation */
- bool vacuum_truncate_set; /* whether vacuum_truncate is set */
+ ternary vacuum_truncate; /* enables vacuum to truncate a relation */
/*
* Fraction of pages in a relation that vacuum can eagerly scan and fail
--
2.39.2
--nextPart5129932.5fSG56mABF
Content-Disposition: attachment;
filename="v2-0003-Add-alias-to-be-used-as-unset-state.patch"
Content-Transfer-Encoding: 7Bit
Content-Type: text/x-patch; charset="unicode-2-0-utf-8";
name="v2-0003-Add-alias-to-be-used-as-unset-state.patch"
^ permalink raw reply [nested|flat] 868+ messages in thread
* [PATCH v2 2/4] Introduce ternary reloptions
@ 2025-09-04 14:41 Nikolay Shaplov <[email protected]>
0 siblings, 0 replies; 868+ messages in thread
From: Nikolay Shaplov @ 2025-09-04 14:41 UTC (permalink / raw)
Introduce ternary reloption as a replacement for current `vacuum_truncate`
implementation. Remove `vacuum_truncate_set` additional flag and using
`TERNARY_UNSET` value instead.
---
src/backend/access/common/reloptions.c | 129 ++++++++++++++++++++-----
src/backend/commands/vacuum.c | 4 +-
src/include/access/reloptions.h | 26 ++---
src/include/c.h | 16 +++
src/include/utils/rel.h | 3 +-
5 files changed, 135 insertions(+), 43 deletions(-)
diff --git a/src/backend/access/common/reloptions.c b/src/backend/access/common/reloptions.c
index 0af3fea68f..f543f11001 100644
--- a/src/backend/access/common/reloptions.c
+++ b/src/backend/access/common/reloptions.c
@@ -40,9 +40,9 @@
*
* To add an option:
*
- * (i) decide on a type (bool, integer, real, enum, string), name, default
- * value, upper and lower bounds (if applicable); for strings, consider a
- * validation routine.
+ * (i) decide on a type (bool, ternary, integer, real, enum, string), name,
+ * default value, upper and lower bounds (if applicable); for strings,
+ * consider a validation routine.
* (ii) add a record below (or use add_<type>_reloption).
* (iii) add it to the appropriate options struct (perhaps StdRdOptions)
* (iv) add it to the appropriate handling routine (perhaps
@@ -147,15 +147,6 @@ static relopt_bool boolRelOpts[] =
},
false
},
- {
- {
- "vacuum_truncate",
- "Enables vacuum to truncate empty pages at the end of this table",
- RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
- ShareUpdateExclusiveLock
- },
- true
- },
{
{
"deduplicate_items",
@@ -170,6 +161,21 @@ static relopt_bool boolRelOpts[] =
{{NULL}}
};
+static relopt_ternary ternaryRelOpts[] =
+{
+ {
+ {
+ "vacuum_truncate",
+ "Enables vacuum to truncate empty pages at the end of this table",
+ RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
+ ShareUpdateExclusiveLock
+ },
+ TERNARY_UNSET
+ },
+ /* list terminator */
+ {{NULL}}
+};
+
static relopt_int intRelOpts[] =
{
{
@@ -600,6 +606,13 @@ initialize_reloptions(void)
boolRelOpts[i].gen.lockmode));
j++;
}
+ for (i = 0; ternaryRelOpts[i].gen.name; i++)
+ {
+ Assert(DoLockModesConflict(ternaryRelOpts[i].gen.lockmode,
+ ternaryRelOpts[i].gen.lockmode));
+ j++;
+ }
+
for (i = 0; intRelOpts[i].gen.name; i++)
{
Assert(DoLockModesConflict(intRelOpts[i].gen.lockmode,
@@ -640,6 +653,14 @@ initialize_reloptions(void)
j++;
}
+ for (i = 0; ternaryRelOpts[i].gen.name; i++)
+ {
+ relOpts[j] = &ternaryRelOpts[i].gen;
+ relOpts[j]->type = RELOPT_TYPE_TERNARY;
+ relOpts[j]->namelen = strlen(relOpts[j]->name);
+ j++;
+ }
+
for (i = 0; intRelOpts[i].gen.name; i++)
{
relOpts[j] = &intRelOpts[i].gen;
@@ -800,6 +821,9 @@ allocate_reloption(bits32 kinds, int type, const char *name, const char *desc,
case RELOPT_TYPE_BOOL:
size = sizeof(relopt_bool);
break;
+ case RELOPT_TYPE_TERNARY:
+ size = sizeof(relopt_ternary);
+ break;
case RELOPT_TYPE_INT:
size = sizeof(relopt_int);
break;
@@ -883,6 +907,54 @@ add_local_bool_reloption(local_relopts *relopts, const char *name,
add_local_reloption(relopts, (relopt_gen *) newoption, offset);
}
+/*
+ * init_ternary_reloption
+ * Allocate and initialize a new ternary reloption
+ */
+static relopt_ternary *
+init_ternary_reloption(bits32 kinds, const char *name, const char *desc,
+ ternary default_val, LOCKMODE lockmode)
+{
+ relopt_ternary *newoption;
+
+ newoption = (relopt_ternary *) allocate_reloption(kinds,
+ RELOPT_TYPE_TERNARY, name, desc, lockmode);
+ newoption->default_val = default_val;
+
+ return newoption;
+}
+
+/*
+ * add_ternary_reloption
+ * Add a new ternary reloption
+ */
+void
+add_ternary_reloption(bits32 kinds, const char *name, const char *desc,
+ ternary default_val, LOCKMODE lockmode)
+{
+ relopt_ternary *newoption = init_ternary_reloption(kinds, name, desc,
+ default_val, lockmode);
+
+ add_reloption((relopt_gen *) newoption);
+}
+
+/*
+ * add_local_ternary_reloption
+ * Add a new ternary local reloption
+ *
+ * 'offset' is offset of ternary-typed field.
+ */
+void
+add_local_ternary_reloption(local_relopts *relopts, const char *name,
+ const char *desc, ternary default_val,
+ int offset)
+{
+ relopt_ternary *newoption = init_ternary_reloption(RELOPT_KIND_LOCAL,
+ name, desc,
+ default_val, 0);
+
+ add_local_reloption(relopts, (relopt_gen *) newoption, offset);
+}
/*
* init_real_reloption
@@ -1617,6 +1689,18 @@ parse_one_reloption(relopt_value *option, char *text_str, int text_len,
option->gen->name, value)));
}
break;
+ case RELOPT_TYPE_TERNARY:
+ {
+ bool b;
+ parsed = parse_bool(value, &b);
+ option->values.ternary_val = b ? TERNARY_TRUE : TERNARY_FALSE;
+ if (validate && !parsed)
+ ereport(ERROR,
+ (errcode(ERRCODE_INVALID_PARAMETER_VALUE),
+ errmsg("invalid value for ternary option \"%s\": %s",
+ option->gen->name, value)));
+ }
+ break;
case RELOPT_TYPE_INT:
{
relopt_int *optint = (relopt_int *) option->gen;
@@ -1780,17 +1864,6 @@ fillRelOptions(void *rdopts, Size basesize,
char *itempos = ((char *) rdopts) + elems[j].offset;
char *string_val;
- /*
- * If isset_offset is provided, store whether the reloption is
- * set there.
- */
- if (elems[j].isset_offset > 0)
- {
- char *setpos = ((char *) rdopts) + elems[j].isset_offset;
-
- *(bool *) setpos = options[i].isset;
- }
-
switch (options[i].gen->type)
{
case RELOPT_TYPE_BOOL:
@@ -1798,6 +1871,11 @@ fillRelOptions(void *rdopts, Size basesize,
options[i].values.bool_val :
((relopt_bool *) options[i].gen)->default_val;
break;
+ case RELOPT_TYPE_TERNARY:
+ *(ternary *) itempos = options[i].isset ?
+ options[i].values.ternary_val :
+ ((relopt_ternary *) options[i].gen)->default_val;
+ break;
case RELOPT_TYPE_INT:
*(int *) itempos = options[i].isset ?
options[i].values.int_val :
@@ -1912,8 +1990,8 @@ default_reloptions(Datum reloptions, bool validate, relopt_kind kind)
offsetof(StdRdOptions, parallel_workers)},
{"vacuum_index_cleanup", RELOPT_TYPE_ENUM,
offsetof(StdRdOptions, vacuum_index_cleanup)},
- {"vacuum_truncate", RELOPT_TYPE_BOOL,
- offsetof(StdRdOptions, vacuum_truncate), offsetof(StdRdOptions, vacuum_truncate_set)},
+ {"vacuum_truncate", RELOPT_TYPE_TERNARY,
+ offsetof(StdRdOptions, vacuum_truncate)},
{"vacuum_max_eager_freeze_failure_rate", RELOPT_TYPE_REAL,
offsetof(StdRdOptions, vacuum_max_eager_freeze_failure_rate)}
};
@@ -1993,7 +2071,6 @@ build_local_reloptions(local_relopts *relopts, Datum options, bool validate)
elems[i].optname = opt->option->name;
elems[i].opttype = opt->option->type;
elems[i].offset = opt->offset;
- elems[i].isset_offset = 0; /* not supported for local relopts yet */
i++;
}
diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c
index 733ef40ae7..977babff54 100644
--- a/src/backend/commands/vacuum.c
+++ b/src/backend/commands/vacuum.c
@@ -2221,9 +2221,9 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams params,
{
StdRdOptions *opts = (StdRdOptions *) rel->rd_options;
- if (opts && opts->vacuum_truncate_set)
+ if (opts && opts->vacuum_truncate != TERNARY_UNSET)
{
- if (opts->vacuum_truncate)
+ if (opts->vacuum_truncate == TERNARY_TRUE)
params.truncate = VACOPTVALUE_ENABLED;
else
params.truncate = VACOPTVALUE_DISABLED;
diff --git a/src/include/access/reloptions.h b/src/include/access/reloptions.h
index a604a4702c..a436697658 100644
--- a/src/include/access/reloptions.h
+++ b/src/include/access/reloptions.h
@@ -29,6 +29,7 @@
typedef enum relopt_type
{
RELOPT_TYPE_BOOL,
+ RELOPT_TYPE_TERNARY, /* on, off, unset */
RELOPT_TYPE_INT,
RELOPT_TYPE_REAL,
RELOPT_TYPE_ENUM,
@@ -80,6 +81,7 @@ typedef struct relopt_value
union
{
bool bool_val;
+ ternary ternary_val;
int int_val;
double real_val;
int enum_val;
@@ -94,6 +96,12 @@ typedef struct relopt_bool
bool default_val;
} relopt_bool;
+typedef struct relopt_rernary
+{
+ relopt_gen gen;
+ int default_val;
+} relopt_ternary;
+
typedef struct relopt_int
{
relopt_gen gen;
@@ -152,19 +160,6 @@ typedef struct
const char *optname; /* option's name */
relopt_type opttype; /* option's datatype */
int offset; /* offset of field in result struct */
-
- /*
- * isset_offset is an optional offset of a field in the result struct that
- * stores whether the option is explicitly set for the relation or if it
- * just picked up the default value. In most cases, this can be
- * accomplished by giving the reloption a special out-of-range default
- * value (e.g., some integer reloptions use -2), but this isn't always
- * possible. For example, a Boolean reloption cannot be given an
- * out-of-range default, so we need another way to discover the source of
- * its value. This offset is only used if given a value greater than
- * zero.
- */
- int isset_offset;
} relopt_parse_elt;
/* Local reloption definition */
@@ -195,6 +190,8 @@ typedef struct local_relopts
extern relopt_kind add_reloption_kind(void);
extern void add_bool_reloption(bits32 kinds, const char *name, const char *desc,
bool default_val, LOCKMODE lockmode);
+extern void add_ternary_reloption(bits32 kinds, const char *name,
+ const char *desc, int default_val, LOCKMODE lockmode);
extern void add_int_reloption(bits32 kinds, const char *name, const char *desc,
int default_val, int min_val, int max_val,
LOCKMODE lockmode);
@@ -214,6 +211,9 @@ extern void register_reloptions_validator(local_relopts *relopts,
extern void add_local_bool_reloption(local_relopts *relopts, const char *name,
const char *desc, bool default_val,
int offset);
+extern void add_local_ternary_reloption(local_relopts *relopts,
+ const char *name, const char *desc,
+ ternary default_val, int offset);
extern void add_local_int_reloption(local_relopts *relopts, const char *name,
const char *desc, int default_val,
int min_val, int max_val, int offset);
diff --git a/src/include/c.h b/src/include/c.h
index 39022f8a9d..9c59ccccf8 100644
--- a/src/include/c.h
+++ b/src/include/c.h
@@ -488,6 +488,22 @@ typedef void (*pg_funcptr_t) (void);
#include <stdbool.h>
+/*
+ * ternary
+ * Boolean value with an extrea "unset" option
+ *
+ * Ternary data type is used in relation options that can be "true", "false" or
+ * "unset". Since relation options are used deep inside the PostgreSQL code,
+ * this type is declared globally.
+*/
+
+typedef enum ternary
+{
+ TERNARY_FALSE = 0,
+ TERNARY_TRUE = 1,
+ TERNARY_UNSET = -1
+} ternary;
+
/* ----------------------------------------------------------------
* Section 3: standard system types
diff --git a/src/include/utils/rel.h b/src/include/utils/rel.h
index b552359915..7346d618f9 100644
--- a/src/include/utils/rel.h
+++ b/src/include/utils/rel.h
@@ -346,8 +346,7 @@ typedef struct StdRdOptions
bool user_catalog_table; /* use as an additional catalog relation */
int parallel_workers; /* max number of parallel workers */
StdRdOptIndexCleanup vacuum_index_cleanup; /* controls index vacuuming */
- bool vacuum_truncate; /* enables vacuum to truncate a relation */
- bool vacuum_truncate_set; /* whether vacuum_truncate is set */
+ ternary vacuum_truncate; /* enables vacuum to truncate a relation */
/*
* Fraction of pages in a relation that vacuum can eagerly scan and fail
--
2.39.2
--nextPart5129932.5fSG56mABF
Content-Disposition: attachment;
filename="v2-0003-Add-alias-to-be-used-as-unset-state.patch"
Content-Transfer-Encoding: 7Bit
Content-Type: text/x-patch; charset="unicode-2-0-utf-8";
name="v2-0003-Add-alias-to-be-used-as-unset-state.patch"
^ permalink raw reply [nested|flat] 868+ messages in thread
* [PATCH v2 2/4] Introduce ternary reloptions
@ 2025-09-04 14:41 Nikolay Shaplov <[email protected]>
0 siblings, 0 replies; 868+ messages in thread
From: Nikolay Shaplov @ 2025-09-04 14:41 UTC (permalink / raw)
Introduce ternary reloption as a replacement for current `vacuum_truncate`
implementation. Remove `vacuum_truncate_set` additional flag and using
`TERNARY_UNSET` value instead.
---
src/backend/access/common/reloptions.c | 129 ++++++++++++++++++++-----
src/backend/commands/vacuum.c | 4 +-
src/include/access/reloptions.h | 26 ++---
src/include/c.h | 16 +++
src/include/utils/rel.h | 3 +-
5 files changed, 135 insertions(+), 43 deletions(-)
diff --git a/src/backend/access/common/reloptions.c b/src/backend/access/common/reloptions.c
index 0af3fea68f..f543f11001 100644
--- a/src/backend/access/common/reloptions.c
+++ b/src/backend/access/common/reloptions.c
@@ -40,9 +40,9 @@
*
* To add an option:
*
- * (i) decide on a type (bool, integer, real, enum, string), name, default
- * value, upper and lower bounds (if applicable); for strings, consider a
- * validation routine.
+ * (i) decide on a type (bool, ternary, integer, real, enum, string), name,
+ * default value, upper and lower bounds (if applicable); for strings,
+ * consider a validation routine.
* (ii) add a record below (or use add_<type>_reloption).
* (iii) add it to the appropriate options struct (perhaps StdRdOptions)
* (iv) add it to the appropriate handling routine (perhaps
@@ -147,15 +147,6 @@ static relopt_bool boolRelOpts[] =
},
false
},
- {
- {
- "vacuum_truncate",
- "Enables vacuum to truncate empty pages at the end of this table",
- RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
- ShareUpdateExclusiveLock
- },
- true
- },
{
{
"deduplicate_items",
@@ -170,6 +161,21 @@ static relopt_bool boolRelOpts[] =
{{NULL}}
};
+static relopt_ternary ternaryRelOpts[] =
+{
+ {
+ {
+ "vacuum_truncate",
+ "Enables vacuum to truncate empty pages at the end of this table",
+ RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
+ ShareUpdateExclusiveLock
+ },
+ TERNARY_UNSET
+ },
+ /* list terminator */
+ {{NULL}}
+};
+
static relopt_int intRelOpts[] =
{
{
@@ -600,6 +606,13 @@ initialize_reloptions(void)
boolRelOpts[i].gen.lockmode));
j++;
}
+ for (i = 0; ternaryRelOpts[i].gen.name; i++)
+ {
+ Assert(DoLockModesConflict(ternaryRelOpts[i].gen.lockmode,
+ ternaryRelOpts[i].gen.lockmode));
+ j++;
+ }
+
for (i = 0; intRelOpts[i].gen.name; i++)
{
Assert(DoLockModesConflict(intRelOpts[i].gen.lockmode,
@@ -640,6 +653,14 @@ initialize_reloptions(void)
j++;
}
+ for (i = 0; ternaryRelOpts[i].gen.name; i++)
+ {
+ relOpts[j] = &ternaryRelOpts[i].gen;
+ relOpts[j]->type = RELOPT_TYPE_TERNARY;
+ relOpts[j]->namelen = strlen(relOpts[j]->name);
+ j++;
+ }
+
for (i = 0; intRelOpts[i].gen.name; i++)
{
relOpts[j] = &intRelOpts[i].gen;
@@ -800,6 +821,9 @@ allocate_reloption(bits32 kinds, int type, const char *name, const char *desc,
case RELOPT_TYPE_BOOL:
size = sizeof(relopt_bool);
break;
+ case RELOPT_TYPE_TERNARY:
+ size = sizeof(relopt_ternary);
+ break;
case RELOPT_TYPE_INT:
size = sizeof(relopt_int);
break;
@@ -883,6 +907,54 @@ add_local_bool_reloption(local_relopts *relopts, const char *name,
add_local_reloption(relopts, (relopt_gen *) newoption, offset);
}
+/*
+ * init_ternary_reloption
+ * Allocate and initialize a new ternary reloption
+ */
+static relopt_ternary *
+init_ternary_reloption(bits32 kinds, const char *name, const char *desc,
+ ternary default_val, LOCKMODE lockmode)
+{
+ relopt_ternary *newoption;
+
+ newoption = (relopt_ternary *) allocate_reloption(kinds,
+ RELOPT_TYPE_TERNARY, name, desc, lockmode);
+ newoption->default_val = default_val;
+
+ return newoption;
+}
+
+/*
+ * add_ternary_reloption
+ * Add a new ternary reloption
+ */
+void
+add_ternary_reloption(bits32 kinds, const char *name, const char *desc,
+ ternary default_val, LOCKMODE lockmode)
+{
+ relopt_ternary *newoption = init_ternary_reloption(kinds, name, desc,
+ default_val, lockmode);
+
+ add_reloption((relopt_gen *) newoption);
+}
+
+/*
+ * add_local_ternary_reloption
+ * Add a new ternary local reloption
+ *
+ * 'offset' is offset of ternary-typed field.
+ */
+void
+add_local_ternary_reloption(local_relopts *relopts, const char *name,
+ const char *desc, ternary default_val,
+ int offset)
+{
+ relopt_ternary *newoption = init_ternary_reloption(RELOPT_KIND_LOCAL,
+ name, desc,
+ default_val, 0);
+
+ add_local_reloption(relopts, (relopt_gen *) newoption, offset);
+}
/*
* init_real_reloption
@@ -1617,6 +1689,18 @@ parse_one_reloption(relopt_value *option, char *text_str, int text_len,
option->gen->name, value)));
}
break;
+ case RELOPT_TYPE_TERNARY:
+ {
+ bool b;
+ parsed = parse_bool(value, &b);
+ option->values.ternary_val = b ? TERNARY_TRUE : TERNARY_FALSE;
+ if (validate && !parsed)
+ ereport(ERROR,
+ (errcode(ERRCODE_INVALID_PARAMETER_VALUE),
+ errmsg("invalid value for ternary option \"%s\": %s",
+ option->gen->name, value)));
+ }
+ break;
case RELOPT_TYPE_INT:
{
relopt_int *optint = (relopt_int *) option->gen;
@@ -1780,17 +1864,6 @@ fillRelOptions(void *rdopts, Size basesize,
char *itempos = ((char *) rdopts) + elems[j].offset;
char *string_val;
- /*
- * If isset_offset is provided, store whether the reloption is
- * set there.
- */
- if (elems[j].isset_offset > 0)
- {
- char *setpos = ((char *) rdopts) + elems[j].isset_offset;
-
- *(bool *) setpos = options[i].isset;
- }
-
switch (options[i].gen->type)
{
case RELOPT_TYPE_BOOL:
@@ -1798,6 +1871,11 @@ fillRelOptions(void *rdopts, Size basesize,
options[i].values.bool_val :
((relopt_bool *) options[i].gen)->default_val;
break;
+ case RELOPT_TYPE_TERNARY:
+ *(ternary *) itempos = options[i].isset ?
+ options[i].values.ternary_val :
+ ((relopt_ternary *) options[i].gen)->default_val;
+ break;
case RELOPT_TYPE_INT:
*(int *) itempos = options[i].isset ?
options[i].values.int_val :
@@ -1912,8 +1990,8 @@ default_reloptions(Datum reloptions, bool validate, relopt_kind kind)
offsetof(StdRdOptions, parallel_workers)},
{"vacuum_index_cleanup", RELOPT_TYPE_ENUM,
offsetof(StdRdOptions, vacuum_index_cleanup)},
- {"vacuum_truncate", RELOPT_TYPE_BOOL,
- offsetof(StdRdOptions, vacuum_truncate), offsetof(StdRdOptions, vacuum_truncate_set)},
+ {"vacuum_truncate", RELOPT_TYPE_TERNARY,
+ offsetof(StdRdOptions, vacuum_truncate)},
{"vacuum_max_eager_freeze_failure_rate", RELOPT_TYPE_REAL,
offsetof(StdRdOptions, vacuum_max_eager_freeze_failure_rate)}
};
@@ -1993,7 +2071,6 @@ build_local_reloptions(local_relopts *relopts, Datum options, bool validate)
elems[i].optname = opt->option->name;
elems[i].opttype = opt->option->type;
elems[i].offset = opt->offset;
- elems[i].isset_offset = 0; /* not supported for local relopts yet */
i++;
}
diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c
index 733ef40ae7..977babff54 100644
--- a/src/backend/commands/vacuum.c
+++ b/src/backend/commands/vacuum.c
@@ -2221,9 +2221,9 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams params,
{
StdRdOptions *opts = (StdRdOptions *) rel->rd_options;
- if (opts && opts->vacuum_truncate_set)
+ if (opts && opts->vacuum_truncate != TERNARY_UNSET)
{
- if (opts->vacuum_truncate)
+ if (opts->vacuum_truncate == TERNARY_TRUE)
params.truncate = VACOPTVALUE_ENABLED;
else
params.truncate = VACOPTVALUE_DISABLED;
diff --git a/src/include/access/reloptions.h b/src/include/access/reloptions.h
index a604a4702c..a436697658 100644
--- a/src/include/access/reloptions.h
+++ b/src/include/access/reloptions.h
@@ -29,6 +29,7 @@
typedef enum relopt_type
{
RELOPT_TYPE_BOOL,
+ RELOPT_TYPE_TERNARY, /* on, off, unset */
RELOPT_TYPE_INT,
RELOPT_TYPE_REAL,
RELOPT_TYPE_ENUM,
@@ -80,6 +81,7 @@ typedef struct relopt_value
union
{
bool bool_val;
+ ternary ternary_val;
int int_val;
double real_val;
int enum_val;
@@ -94,6 +96,12 @@ typedef struct relopt_bool
bool default_val;
} relopt_bool;
+typedef struct relopt_rernary
+{
+ relopt_gen gen;
+ int default_val;
+} relopt_ternary;
+
typedef struct relopt_int
{
relopt_gen gen;
@@ -152,19 +160,6 @@ typedef struct
const char *optname; /* option's name */
relopt_type opttype; /* option's datatype */
int offset; /* offset of field in result struct */
-
- /*
- * isset_offset is an optional offset of a field in the result struct that
- * stores whether the option is explicitly set for the relation or if it
- * just picked up the default value. In most cases, this can be
- * accomplished by giving the reloption a special out-of-range default
- * value (e.g., some integer reloptions use -2), but this isn't always
- * possible. For example, a Boolean reloption cannot be given an
- * out-of-range default, so we need another way to discover the source of
- * its value. This offset is only used if given a value greater than
- * zero.
- */
- int isset_offset;
} relopt_parse_elt;
/* Local reloption definition */
@@ -195,6 +190,8 @@ typedef struct local_relopts
extern relopt_kind add_reloption_kind(void);
extern void add_bool_reloption(bits32 kinds, const char *name, const char *desc,
bool default_val, LOCKMODE lockmode);
+extern void add_ternary_reloption(bits32 kinds, const char *name,
+ const char *desc, int default_val, LOCKMODE lockmode);
extern void add_int_reloption(bits32 kinds, const char *name, const char *desc,
int default_val, int min_val, int max_val,
LOCKMODE lockmode);
@@ -214,6 +211,9 @@ extern void register_reloptions_validator(local_relopts *relopts,
extern void add_local_bool_reloption(local_relopts *relopts, const char *name,
const char *desc, bool default_val,
int offset);
+extern void add_local_ternary_reloption(local_relopts *relopts,
+ const char *name, const char *desc,
+ ternary default_val, int offset);
extern void add_local_int_reloption(local_relopts *relopts, const char *name,
const char *desc, int default_val,
int min_val, int max_val, int offset);
diff --git a/src/include/c.h b/src/include/c.h
index 39022f8a9d..9c59ccccf8 100644
--- a/src/include/c.h
+++ b/src/include/c.h
@@ -488,6 +488,22 @@ typedef void (*pg_funcptr_t) (void);
#include <stdbool.h>
+/*
+ * ternary
+ * Boolean value with an extrea "unset" option
+ *
+ * Ternary data type is used in relation options that can be "true", "false" or
+ * "unset". Since relation options are used deep inside the PostgreSQL code,
+ * this type is declared globally.
+*/
+
+typedef enum ternary
+{
+ TERNARY_FALSE = 0,
+ TERNARY_TRUE = 1,
+ TERNARY_UNSET = -1
+} ternary;
+
/* ----------------------------------------------------------------
* Section 3: standard system types
diff --git a/src/include/utils/rel.h b/src/include/utils/rel.h
index b552359915..7346d618f9 100644
--- a/src/include/utils/rel.h
+++ b/src/include/utils/rel.h
@@ -346,8 +346,7 @@ typedef struct StdRdOptions
bool user_catalog_table; /* use as an additional catalog relation */
int parallel_workers; /* max number of parallel workers */
StdRdOptIndexCleanup vacuum_index_cleanup; /* controls index vacuuming */
- bool vacuum_truncate; /* enables vacuum to truncate a relation */
- bool vacuum_truncate_set; /* whether vacuum_truncate is set */
+ ternary vacuum_truncate; /* enables vacuum to truncate a relation */
/*
* Fraction of pages in a relation that vacuum can eagerly scan and fail
--
2.39.2
--nextPart5129932.5fSG56mABF
Content-Disposition: attachment;
filename="v2-0003-Add-alias-to-be-used-as-unset-state.patch"
Content-Transfer-Encoding: 7Bit
Content-Type: text/x-patch; charset="unicode-2-0-utf-8";
name="v2-0003-Add-alias-to-be-used-as-unset-state.patch"
^ permalink raw reply [nested|flat] 868+ messages in thread
* [PATCH v2 2/4] Introduce ternary reloptions
@ 2025-09-04 14:41 Nikolay Shaplov <[email protected]>
0 siblings, 0 replies; 868+ messages in thread
From: Nikolay Shaplov @ 2025-09-04 14:41 UTC (permalink / raw)
Introduce ternary reloption as a replacement for current `vacuum_truncate`
implementation. Remove `vacuum_truncate_set` additional flag and using
`TERNARY_UNSET` value instead.
---
src/backend/access/common/reloptions.c | 129 ++++++++++++++++++++-----
src/backend/commands/vacuum.c | 4 +-
src/include/access/reloptions.h | 26 ++---
src/include/c.h | 16 +++
src/include/utils/rel.h | 3 +-
5 files changed, 135 insertions(+), 43 deletions(-)
diff --git a/src/backend/access/common/reloptions.c b/src/backend/access/common/reloptions.c
index 0af3fea68f..f543f11001 100644
--- a/src/backend/access/common/reloptions.c
+++ b/src/backend/access/common/reloptions.c
@@ -40,9 +40,9 @@
*
* To add an option:
*
- * (i) decide on a type (bool, integer, real, enum, string), name, default
- * value, upper and lower bounds (if applicable); for strings, consider a
- * validation routine.
+ * (i) decide on a type (bool, ternary, integer, real, enum, string), name,
+ * default value, upper and lower bounds (if applicable); for strings,
+ * consider a validation routine.
* (ii) add a record below (or use add_<type>_reloption).
* (iii) add it to the appropriate options struct (perhaps StdRdOptions)
* (iv) add it to the appropriate handling routine (perhaps
@@ -147,15 +147,6 @@ static relopt_bool boolRelOpts[] =
},
false
},
- {
- {
- "vacuum_truncate",
- "Enables vacuum to truncate empty pages at the end of this table",
- RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
- ShareUpdateExclusiveLock
- },
- true
- },
{
{
"deduplicate_items",
@@ -170,6 +161,21 @@ static relopt_bool boolRelOpts[] =
{{NULL}}
};
+static relopt_ternary ternaryRelOpts[] =
+{
+ {
+ {
+ "vacuum_truncate",
+ "Enables vacuum to truncate empty pages at the end of this table",
+ RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
+ ShareUpdateExclusiveLock
+ },
+ TERNARY_UNSET
+ },
+ /* list terminator */
+ {{NULL}}
+};
+
static relopt_int intRelOpts[] =
{
{
@@ -600,6 +606,13 @@ initialize_reloptions(void)
boolRelOpts[i].gen.lockmode));
j++;
}
+ for (i = 0; ternaryRelOpts[i].gen.name; i++)
+ {
+ Assert(DoLockModesConflict(ternaryRelOpts[i].gen.lockmode,
+ ternaryRelOpts[i].gen.lockmode));
+ j++;
+ }
+
for (i = 0; intRelOpts[i].gen.name; i++)
{
Assert(DoLockModesConflict(intRelOpts[i].gen.lockmode,
@@ -640,6 +653,14 @@ initialize_reloptions(void)
j++;
}
+ for (i = 0; ternaryRelOpts[i].gen.name; i++)
+ {
+ relOpts[j] = &ternaryRelOpts[i].gen;
+ relOpts[j]->type = RELOPT_TYPE_TERNARY;
+ relOpts[j]->namelen = strlen(relOpts[j]->name);
+ j++;
+ }
+
for (i = 0; intRelOpts[i].gen.name; i++)
{
relOpts[j] = &intRelOpts[i].gen;
@@ -800,6 +821,9 @@ allocate_reloption(bits32 kinds, int type, const char *name, const char *desc,
case RELOPT_TYPE_BOOL:
size = sizeof(relopt_bool);
break;
+ case RELOPT_TYPE_TERNARY:
+ size = sizeof(relopt_ternary);
+ break;
case RELOPT_TYPE_INT:
size = sizeof(relopt_int);
break;
@@ -883,6 +907,54 @@ add_local_bool_reloption(local_relopts *relopts, const char *name,
add_local_reloption(relopts, (relopt_gen *) newoption, offset);
}
+/*
+ * init_ternary_reloption
+ * Allocate and initialize a new ternary reloption
+ */
+static relopt_ternary *
+init_ternary_reloption(bits32 kinds, const char *name, const char *desc,
+ ternary default_val, LOCKMODE lockmode)
+{
+ relopt_ternary *newoption;
+
+ newoption = (relopt_ternary *) allocate_reloption(kinds,
+ RELOPT_TYPE_TERNARY, name, desc, lockmode);
+ newoption->default_val = default_val;
+
+ return newoption;
+}
+
+/*
+ * add_ternary_reloption
+ * Add a new ternary reloption
+ */
+void
+add_ternary_reloption(bits32 kinds, const char *name, const char *desc,
+ ternary default_val, LOCKMODE lockmode)
+{
+ relopt_ternary *newoption = init_ternary_reloption(kinds, name, desc,
+ default_val, lockmode);
+
+ add_reloption((relopt_gen *) newoption);
+}
+
+/*
+ * add_local_ternary_reloption
+ * Add a new ternary local reloption
+ *
+ * 'offset' is offset of ternary-typed field.
+ */
+void
+add_local_ternary_reloption(local_relopts *relopts, const char *name,
+ const char *desc, ternary default_val,
+ int offset)
+{
+ relopt_ternary *newoption = init_ternary_reloption(RELOPT_KIND_LOCAL,
+ name, desc,
+ default_val, 0);
+
+ add_local_reloption(relopts, (relopt_gen *) newoption, offset);
+}
/*
* init_real_reloption
@@ -1617,6 +1689,18 @@ parse_one_reloption(relopt_value *option, char *text_str, int text_len,
option->gen->name, value)));
}
break;
+ case RELOPT_TYPE_TERNARY:
+ {
+ bool b;
+ parsed = parse_bool(value, &b);
+ option->values.ternary_val = b ? TERNARY_TRUE : TERNARY_FALSE;
+ if (validate && !parsed)
+ ereport(ERROR,
+ (errcode(ERRCODE_INVALID_PARAMETER_VALUE),
+ errmsg("invalid value for ternary option \"%s\": %s",
+ option->gen->name, value)));
+ }
+ break;
case RELOPT_TYPE_INT:
{
relopt_int *optint = (relopt_int *) option->gen;
@@ -1780,17 +1864,6 @@ fillRelOptions(void *rdopts, Size basesize,
char *itempos = ((char *) rdopts) + elems[j].offset;
char *string_val;
- /*
- * If isset_offset is provided, store whether the reloption is
- * set there.
- */
- if (elems[j].isset_offset > 0)
- {
- char *setpos = ((char *) rdopts) + elems[j].isset_offset;
-
- *(bool *) setpos = options[i].isset;
- }
-
switch (options[i].gen->type)
{
case RELOPT_TYPE_BOOL:
@@ -1798,6 +1871,11 @@ fillRelOptions(void *rdopts, Size basesize,
options[i].values.bool_val :
((relopt_bool *) options[i].gen)->default_val;
break;
+ case RELOPT_TYPE_TERNARY:
+ *(ternary *) itempos = options[i].isset ?
+ options[i].values.ternary_val :
+ ((relopt_ternary *) options[i].gen)->default_val;
+ break;
case RELOPT_TYPE_INT:
*(int *) itempos = options[i].isset ?
options[i].values.int_val :
@@ -1912,8 +1990,8 @@ default_reloptions(Datum reloptions, bool validate, relopt_kind kind)
offsetof(StdRdOptions, parallel_workers)},
{"vacuum_index_cleanup", RELOPT_TYPE_ENUM,
offsetof(StdRdOptions, vacuum_index_cleanup)},
- {"vacuum_truncate", RELOPT_TYPE_BOOL,
- offsetof(StdRdOptions, vacuum_truncate), offsetof(StdRdOptions, vacuum_truncate_set)},
+ {"vacuum_truncate", RELOPT_TYPE_TERNARY,
+ offsetof(StdRdOptions, vacuum_truncate)},
{"vacuum_max_eager_freeze_failure_rate", RELOPT_TYPE_REAL,
offsetof(StdRdOptions, vacuum_max_eager_freeze_failure_rate)}
};
@@ -1993,7 +2071,6 @@ build_local_reloptions(local_relopts *relopts, Datum options, bool validate)
elems[i].optname = opt->option->name;
elems[i].opttype = opt->option->type;
elems[i].offset = opt->offset;
- elems[i].isset_offset = 0; /* not supported for local relopts yet */
i++;
}
diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c
index 733ef40ae7..977babff54 100644
--- a/src/backend/commands/vacuum.c
+++ b/src/backend/commands/vacuum.c
@@ -2221,9 +2221,9 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams params,
{
StdRdOptions *opts = (StdRdOptions *) rel->rd_options;
- if (opts && opts->vacuum_truncate_set)
+ if (opts && opts->vacuum_truncate != TERNARY_UNSET)
{
- if (opts->vacuum_truncate)
+ if (opts->vacuum_truncate == TERNARY_TRUE)
params.truncate = VACOPTVALUE_ENABLED;
else
params.truncate = VACOPTVALUE_DISABLED;
diff --git a/src/include/access/reloptions.h b/src/include/access/reloptions.h
index a604a4702c..a436697658 100644
--- a/src/include/access/reloptions.h
+++ b/src/include/access/reloptions.h
@@ -29,6 +29,7 @@
typedef enum relopt_type
{
RELOPT_TYPE_BOOL,
+ RELOPT_TYPE_TERNARY, /* on, off, unset */
RELOPT_TYPE_INT,
RELOPT_TYPE_REAL,
RELOPT_TYPE_ENUM,
@@ -80,6 +81,7 @@ typedef struct relopt_value
union
{
bool bool_val;
+ ternary ternary_val;
int int_val;
double real_val;
int enum_val;
@@ -94,6 +96,12 @@ typedef struct relopt_bool
bool default_val;
} relopt_bool;
+typedef struct relopt_rernary
+{
+ relopt_gen gen;
+ int default_val;
+} relopt_ternary;
+
typedef struct relopt_int
{
relopt_gen gen;
@@ -152,19 +160,6 @@ typedef struct
const char *optname; /* option's name */
relopt_type opttype; /* option's datatype */
int offset; /* offset of field in result struct */
-
- /*
- * isset_offset is an optional offset of a field in the result struct that
- * stores whether the option is explicitly set for the relation or if it
- * just picked up the default value. In most cases, this can be
- * accomplished by giving the reloption a special out-of-range default
- * value (e.g., some integer reloptions use -2), but this isn't always
- * possible. For example, a Boolean reloption cannot be given an
- * out-of-range default, so we need another way to discover the source of
- * its value. This offset is only used if given a value greater than
- * zero.
- */
- int isset_offset;
} relopt_parse_elt;
/* Local reloption definition */
@@ -195,6 +190,8 @@ typedef struct local_relopts
extern relopt_kind add_reloption_kind(void);
extern void add_bool_reloption(bits32 kinds, const char *name, const char *desc,
bool default_val, LOCKMODE lockmode);
+extern void add_ternary_reloption(bits32 kinds, const char *name,
+ const char *desc, int default_val, LOCKMODE lockmode);
extern void add_int_reloption(bits32 kinds, const char *name, const char *desc,
int default_val, int min_val, int max_val,
LOCKMODE lockmode);
@@ -214,6 +211,9 @@ extern void register_reloptions_validator(local_relopts *relopts,
extern void add_local_bool_reloption(local_relopts *relopts, const char *name,
const char *desc, bool default_val,
int offset);
+extern void add_local_ternary_reloption(local_relopts *relopts,
+ const char *name, const char *desc,
+ ternary default_val, int offset);
extern void add_local_int_reloption(local_relopts *relopts, const char *name,
const char *desc, int default_val,
int min_val, int max_val, int offset);
diff --git a/src/include/c.h b/src/include/c.h
index 39022f8a9d..9c59ccccf8 100644
--- a/src/include/c.h
+++ b/src/include/c.h
@@ -488,6 +488,22 @@ typedef void (*pg_funcptr_t) (void);
#include <stdbool.h>
+/*
+ * ternary
+ * Boolean value with an extrea "unset" option
+ *
+ * Ternary data type is used in relation options that can be "true", "false" or
+ * "unset". Since relation options are used deep inside the PostgreSQL code,
+ * this type is declared globally.
+*/
+
+typedef enum ternary
+{
+ TERNARY_FALSE = 0,
+ TERNARY_TRUE = 1,
+ TERNARY_UNSET = -1
+} ternary;
+
/* ----------------------------------------------------------------
* Section 3: standard system types
diff --git a/src/include/utils/rel.h b/src/include/utils/rel.h
index b552359915..7346d618f9 100644
--- a/src/include/utils/rel.h
+++ b/src/include/utils/rel.h
@@ -346,8 +346,7 @@ typedef struct StdRdOptions
bool user_catalog_table; /* use as an additional catalog relation */
int parallel_workers; /* max number of parallel workers */
StdRdOptIndexCleanup vacuum_index_cleanup; /* controls index vacuuming */
- bool vacuum_truncate; /* enables vacuum to truncate a relation */
- bool vacuum_truncate_set; /* whether vacuum_truncate is set */
+ ternary vacuum_truncate; /* enables vacuum to truncate a relation */
/*
* Fraction of pages in a relation that vacuum can eagerly scan and fail
--
2.39.2
--nextPart5129932.5fSG56mABF
Content-Disposition: attachment;
filename="v2-0003-Add-alias-to-be-used-as-unset-state.patch"
Content-Transfer-Encoding: 7Bit
Content-Type: text/x-patch; charset="unicode-2-0-utf-8";
name="v2-0003-Add-alias-to-be-used-as-unset-state.patch"
^ permalink raw reply [nested|flat] 868+ messages in thread
* [PATCH v2 2/4] Introduce ternary reloptions
@ 2025-09-04 14:41 Nikolay Shaplov <[email protected]>
0 siblings, 0 replies; 868+ messages in thread
From: Nikolay Shaplov @ 2025-09-04 14:41 UTC (permalink / raw)
Introduce ternary reloption as a replacement for current `vacuum_truncate`
implementation. Remove `vacuum_truncate_set` additional flag and using
`TERNARY_UNSET` value instead.
---
src/backend/access/common/reloptions.c | 129 ++++++++++++++++++++-----
src/backend/commands/vacuum.c | 4 +-
src/include/access/reloptions.h | 26 ++---
src/include/c.h | 16 +++
src/include/utils/rel.h | 3 +-
5 files changed, 135 insertions(+), 43 deletions(-)
diff --git a/src/backend/access/common/reloptions.c b/src/backend/access/common/reloptions.c
index 0af3fea68f..f543f11001 100644
--- a/src/backend/access/common/reloptions.c
+++ b/src/backend/access/common/reloptions.c
@@ -40,9 +40,9 @@
*
* To add an option:
*
- * (i) decide on a type (bool, integer, real, enum, string), name, default
- * value, upper and lower bounds (if applicable); for strings, consider a
- * validation routine.
+ * (i) decide on a type (bool, ternary, integer, real, enum, string), name,
+ * default value, upper and lower bounds (if applicable); for strings,
+ * consider a validation routine.
* (ii) add a record below (or use add_<type>_reloption).
* (iii) add it to the appropriate options struct (perhaps StdRdOptions)
* (iv) add it to the appropriate handling routine (perhaps
@@ -147,15 +147,6 @@ static relopt_bool boolRelOpts[] =
},
false
},
- {
- {
- "vacuum_truncate",
- "Enables vacuum to truncate empty pages at the end of this table",
- RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
- ShareUpdateExclusiveLock
- },
- true
- },
{
{
"deduplicate_items",
@@ -170,6 +161,21 @@ static relopt_bool boolRelOpts[] =
{{NULL}}
};
+static relopt_ternary ternaryRelOpts[] =
+{
+ {
+ {
+ "vacuum_truncate",
+ "Enables vacuum to truncate empty pages at the end of this table",
+ RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
+ ShareUpdateExclusiveLock
+ },
+ TERNARY_UNSET
+ },
+ /* list terminator */
+ {{NULL}}
+};
+
static relopt_int intRelOpts[] =
{
{
@@ -600,6 +606,13 @@ initialize_reloptions(void)
boolRelOpts[i].gen.lockmode));
j++;
}
+ for (i = 0; ternaryRelOpts[i].gen.name; i++)
+ {
+ Assert(DoLockModesConflict(ternaryRelOpts[i].gen.lockmode,
+ ternaryRelOpts[i].gen.lockmode));
+ j++;
+ }
+
for (i = 0; intRelOpts[i].gen.name; i++)
{
Assert(DoLockModesConflict(intRelOpts[i].gen.lockmode,
@@ -640,6 +653,14 @@ initialize_reloptions(void)
j++;
}
+ for (i = 0; ternaryRelOpts[i].gen.name; i++)
+ {
+ relOpts[j] = &ternaryRelOpts[i].gen;
+ relOpts[j]->type = RELOPT_TYPE_TERNARY;
+ relOpts[j]->namelen = strlen(relOpts[j]->name);
+ j++;
+ }
+
for (i = 0; intRelOpts[i].gen.name; i++)
{
relOpts[j] = &intRelOpts[i].gen;
@@ -800,6 +821,9 @@ allocate_reloption(bits32 kinds, int type, const char *name, const char *desc,
case RELOPT_TYPE_BOOL:
size = sizeof(relopt_bool);
break;
+ case RELOPT_TYPE_TERNARY:
+ size = sizeof(relopt_ternary);
+ break;
case RELOPT_TYPE_INT:
size = sizeof(relopt_int);
break;
@@ -883,6 +907,54 @@ add_local_bool_reloption(local_relopts *relopts, const char *name,
add_local_reloption(relopts, (relopt_gen *) newoption, offset);
}
+/*
+ * init_ternary_reloption
+ * Allocate and initialize a new ternary reloption
+ */
+static relopt_ternary *
+init_ternary_reloption(bits32 kinds, const char *name, const char *desc,
+ ternary default_val, LOCKMODE lockmode)
+{
+ relopt_ternary *newoption;
+
+ newoption = (relopt_ternary *) allocate_reloption(kinds,
+ RELOPT_TYPE_TERNARY, name, desc, lockmode);
+ newoption->default_val = default_val;
+
+ return newoption;
+}
+
+/*
+ * add_ternary_reloption
+ * Add a new ternary reloption
+ */
+void
+add_ternary_reloption(bits32 kinds, const char *name, const char *desc,
+ ternary default_val, LOCKMODE lockmode)
+{
+ relopt_ternary *newoption = init_ternary_reloption(kinds, name, desc,
+ default_val, lockmode);
+
+ add_reloption((relopt_gen *) newoption);
+}
+
+/*
+ * add_local_ternary_reloption
+ * Add a new ternary local reloption
+ *
+ * 'offset' is offset of ternary-typed field.
+ */
+void
+add_local_ternary_reloption(local_relopts *relopts, const char *name,
+ const char *desc, ternary default_val,
+ int offset)
+{
+ relopt_ternary *newoption = init_ternary_reloption(RELOPT_KIND_LOCAL,
+ name, desc,
+ default_val, 0);
+
+ add_local_reloption(relopts, (relopt_gen *) newoption, offset);
+}
/*
* init_real_reloption
@@ -1617,6 +1689,18 @@ parse_one_reloption(relopt_value *option, char *text_str, int text_len,
option->gen->name, value)));
}
break;
+ case RELOPT_TYPE_TERNARY:
+ {
+ bool b;
+ parsed = parse_bool(value, &b);
+ option->values.ternary_val = b ? TERNARY_TRUE : TERNARY_FALSE;
+ if (validate && !parsed)
+ ereport(ERROR,
+ (errcode(ERRCODE_INVALID_PARAMETER_VALUE),
+ errmsg("invalid value for ternary option \"%s\": %s",
+ option->gen->name, value)));
+ }
+ break;
case RELOPT_TYPE_INT:
{
relopt_int *optint = (relopt_int *) option->gen;
@@ -1780,17 +1864,6 @@ fillRelOptions(void *rdopts, Size basesize,
char *itempos = ((char *) rdopts) + elems[j].offset;
char *string_val;
- /*
- * If isset_offset is provided, store whether the reloption is
- * set there.
- */
- if (elems[j].isset_offset > 0)
- {
- char *setpos = ((char *) rdopts) + elems[j].isset_offset;
-
- *(bool *) setpos = options[i].isset;
- }
-
switch (options[i].gen->type)
{
case RELOPT_TYPE_BOOL:
@@ -1798,6 +1871,11 @@ fillRelOptions(void *rdopts, Size basesize,
options[i].values.bool_val :
((relopt_bool *) options[i].gen)->default_val;
break;
+ case RELOPT_TYPE_TERNARY:
+ *(ternary *) itempos = options[i].isset ?
+ options[i].values.ternary_val :
+ ((relopt_ternary *) options[i].gen)->default_val;
+ break;
case RELOPT_TYPE_INT:
*(int *) itempos = options[i].isset ?
options[i].values.int_val :
@@ -1912,8 +1990,8 @@ default_reloptions(Datum reloptions, bool validate, relopt_kind kind)
offsetof(StdRdOptions, parallel_workers)},
{"vacuum_index_cleanup", RELOPT_TYPE_ENUM,
offsetof(StdRdOptions, vacuum_index_cleanup)},
- {"vacuum_truncate", RELOPT_TYPE_BOOL,
- offsetof(StdRdOptions, vacuum_truncate), offsetof(StdRdOptions, vacuum_truncate_set)},
+ {"vacuum_truncate", RELOPT_TYPE_TERNARY,
+ offsetof(StdRdOptions, vacuum_truncate)},
{"vacuum_max_eager_freeze_failure_rate", RELOPT_TYPE_REAL,
offsetof(StdRdOptions, vacuum_max_eager_freeze_failure_rate)}
};
@@ -1993,7 +2071,6 @@ build_local_reloptions(local_relopts *relopts, Datum options, bool validate)
elems[i].optname = opt->option->name;
elems[i].opttype = opt->option->type;
elems[i].offset = opt->offset;
- elems[i].isset_offset = 0; /* not supported for local relopts yet */
i++;
}
diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c
index 733ef40ae7..977babff54 100644
--- a/src/backend/commands/vacuum.c
+++ b/src/backend/commands/vacuum.c
@@ -2221,9 +2221,9 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams params,
{
StdRdOptions *opts = (StdRdOptions *) rel->rd_options;
- if (opts && opts->vacuum_truncate_set)
+ if (opts && opts->vacuum_truncate != TERNARY_UNSET)
{
- if (opts->vacuum_truncate)
+ if (opts->vacuum_truncate == TERNARY_TRUE)
params.truncate = VACOPTVALUE_ENABLED;
else
params.truncate = VACOPTVALUE_DISABLED;
diff --git a/src/include/access/reloptions.h b/src/include/access/reloptions.h
index a604a4702c..a436697658 100644
--- a/src/include/access/reloptions.h
+++ b/src/include/access/reloptions.h
@@ -29,6 +29,7 @@
typedef enum relopt_type
{
RELOPT_TYPE_BOOL,
+ RELOPT_TYPE_TERNARY, /* on, off, unset */
RELOPT_TYPE_INT,
RELOPT_TYPE_REAL,
RELOPT_TYPE_ENUM,
@@ -80,6 +81,7 @@ typedef struct relopt_value
union
{
bool bool_val;
+ ternary ternary_val;
int int_val;
double real_val;
int enum_val;
@@ -94,6 +96,12 @@ typedef struct relopt_bool
bool default_val;
} relopt_bool;
+typedef struct relopt_rernary
+{
+ relopt_gen gen;
+ int default_val;
+} relopt_ternary;
+
typedef struct relopt_int
{
relopt_gen gen;
@@ -152,19 +160,6 @@ typedef struct
const char *optname; /* option's name */
relopt_type opttype; /* option's datatype */
int offset; /* offset of field in result struct */
-
- /*
- * isset_offset is an optional offset of a field in the result struct that
- * stores whether the option is explicitly set for the relation or if it
- * just picked up the default value. In most cases, this can be
- * accomplished by giving the reloption a special out-of-range default
- * value (e.g., some integer reloptions use -2), but this isn't always
- * possible. For example, a Boolean reloption cannot be given an
- * out-of-range default, so we need another way to discover the source of
- * its value. This offset is only used if given a value greater than
- * zero.
- */
- int isset_offset;
} relopt_parse_elt;
/* Local reloption definition */
@@ -195,6 +190,8 @@ typedef struct local_relopts
extern relopt_kind add_reloption_kind(void);
extern void add_bool_reloption(bits32 kinds, const char *name, const char *desc,
bool default_val, LOCKMODE lockmode);
+extern void add_ternary_reloption(bits32 kinds, const char *name,
+ const char *desc, int default_val, LOCKMODE lockmode);
extern void add_int_reloption(bits32 kinds, const char *name, const char *desc,
int default_val, int min_val, int max_val,
LOCKMODE lockmode);
@@ -214,6 +211,9 @@ extern void register_reloptions_validator(local_relopts *relopts,
extern void add_local_bool_reloption(local_relopts *relopts, const char *name,
const char *desc, bool default_val,
int offset);
+extern void add_local_ternary_reloption(local_relopts *relopts,
+ const char *name, const char *desc,
+ ternary default_val, int offset);
extern void add_local_int_reloption(local_relopts *relopts, const char *name,
const char *desc, int default_val,
int min_val, int max_val, int offset);
diff --git a/src/include/c.h b/src/include/c.h
index 39022f8a9d..9c59ccccf8 100644
--- a/src/include/c.h
+++ b/src/include/c.h
@@ -488,6 +488,22 @@ typedef void (*pg_funcptr_t) (void);
#include <stdbool.h>
+/*
+ * ternary
+ * Boolean value with an extrea "unset" option
+ *
+ * Ternary data type is used in relation options that can be "true", "false" or
+ * "unset". Since relation options are used deep inside the PostgreSQL code,
+ * this type is declared globally.
+*/
+
+typedef enum ternary
+{
+ TERNARY_FALSE = 0,
+ TERNARY_TRUE = 1,
+ TERNARY_UNSET = -1
+} ternary;
+
/* ----------------------------------------------------------------
* Section 3: standard system types
diff --git a/src/include/utils/rel.h b/src/include/utils/rel.h
index b552359915..7346d618f9 100644
--- a/src/include/utils/rel.h
+++ b/src/include/utils/rel.h
@@ -346,8 +346,7 @@ typedef struct StdRdOptions
bool user_catalog_table; /* use as an additional catalog relation */
int parallel_workers; /* max number of parallel workers */
StdRdOptIndexCleanup vacuum_index_cleanup; /* controls index vacuuming */
- bool vacuum_truncate; /* enables vacuum to truncate a relation */
- bool vacuum_truncate_set; /* whether vacuum_truncate is set */
+ ternary vacuum_truncate; /* enables vacuum to truncate a relation */
/*
* Fraction of pages in a relation that vacuum can eagerly scan and fail
--
2.39.2
--nextPart5129932.5fSG56mABF
Content-Disposition: attachment;
filename="v2-0003-Add-alias-to-be-used-as-unset-state.patch"
Content-Transfer-Encoding: 7Bit
Content-Type: text/x-patch; charset="unicode-2-0-utf-8";
name="v2-0003-Add-alias-to-be-used-as-unset-state.patch"
^ permalink raw reply [nested|flat] 868+ messages in thread
* [PATCH v2 2/4] Introduce ternary reloptions
@ 2025-09-04 14:41 Nikolay Shaplov <[email protected]>
0 siblings, 0 replies; 868+ messages in thread
From: Nikolay Shaplov @ 2025-09-04 14:41 UTC (permalink / raw)
Introduce ternary reloption as a replacement for current `vacuum_truncate`
implementation. Remove `vacuum_truncate_set` additional flag and using
`TERNARY_UNSET` value instead.
---
src/backend/access/common/reloptions.c | 129 ++++++++++++++++++++-----
src/backend/commands/vacuum.c | 4 +-
src/include/access/reloptions.h | 26 ++---
src/include/c.h | 16 +++
src/include/utils/rel.h | 3 +-
5 files changed, 135 insertions(+), 43 deletions(-)
diff --git a/src/backend/access/common/reloptions.c b/src/backend/access/common/reloptions.c
index 0af3fea68f..f543f11001 100644
--- a/src/backend/access/common/reloptions.c
+++ b/src/backend/access/common/reloptions.c
@@ -40,9 +40,9 @@
*
* To add an option:
*
- * (i) decide on a type (bool, integer, real, enum, string), name, default
- * value, upper and lower bounds (if applicable); for strings, consider a
- * validation routine.
+ * (i) decide on a type (bool, ternary, integer, real, enum, string), name,
+ * default value, upper and lower bounds (if applicable); for strings,
+ * consider a validation routine.
* (ii) add a record below (or use add_<type>_reloption).
* (iii) add it to the appropriate options struct (perhaps StdRdOptions)
* (iv) add it to the appropriate handling routine (perhaps
@@ -147,15 +147,6 @@ static relopt_bool boolRelOpts[] =
},
false
},
- {
- {
- "vacuum_truncate",
- "Enables vacuum to truncate empty pages at the end of this table",
- RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
- ShareUpdateExclusiveLock
- },
- true
- },
{
{
"deduplicate_items",
@@ -170,6 +161,21 @@ static relopt_bool boolRelOpts[] =
{{NULL}}
};
+static relopt_ternary ternaryRelOpts[] =
+{
+ {
+ {
+ "vacuum_truncate",
+ "Enables vacuum to truncate empty pages at the end of this table",
+ RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
+ ShareUpdateExclusiveLock
+ },
+ TERNARY_UNSET
+ },
+ /* list terminator */
+ {{NULL}}
+};
+
static relopt_int intRelOpts[] =
{
{
@@ -600,6 +606,13 @@ initialize_reloptions(void)
boolRelOpts[i].gen.lockmode));
j++;
}
+ for (i = 0; ternaryRelOpts[i].gen.name; i++)
+ {
+ Assert(DoLockModesConflict(ternaryRelOpts[i].gen.lockmode,
+ ternaryRelOpts[i].gen.lockmode));
+ j++;
+ }
+
for (i = 0; intRelOpts[i].gen.name; i++)
{
Assert(DoLockModesConflict(intRelOpts[i].gen.lockmode,
@@ -640,6 +653,14 @@ initialize_reloptions(void)
j++;
}
+ for (i = 0; ternaryRelOpts[i].gen.name; i++)
+ {
+ relOpts[j] = &ternaryRelOpts[i].gen;
+ relOpts[j]->type = RELOPT_TYPE_TERNARY;
+ relOpts[j]->namelen = strlen(relOpts[j]->name);
+ j++;
+ }
+
for (i = 0; intRelOpts[i].gen.name; i++)
{
relOpts[j] = &intRelOpts[i].gen;
@@ -800,6 +821,9 @@ allocate_reloption(bits32 kinds, int type, const char *name, const char *desc,
case RELOPT_TYPE_BOOL:
size = sizeof(relopt_bool);
break;
+ case RELOPT_TYPE_TERNARY:
+ size = sizeof(relopt_ternary);
+ break;
case RELOPT_TYPE_INT:
size = sizeof(relopt_int);
break;
@@ -883,6 +907,54 @@ add_local_bool_reloption(local_relopts *relopts, const char *name,
add_local_reloption(relopts, (relopt_gen *) newoption, offset);
}
+/*
+ * init_ternary_reloption
+ * Allocate and initialize a new ternary reloption
+ */
+static relopt_ternary *
+init_ternary_reloption(bits32 kinds, const char *name, const char *desc,
+ ternary default_val, LOCKMODE lockmode)
+{
+ relopt_ternary *newoption;
+
+ newoption = (relopt_ternary *) allocate_reloption(kinds,
+ RELOPT_TYPE_TERNARY, name, desc, lockmode);
+ newoption->default_val = default_val;
+
+ return newoption;
+}
+
+/*
+ * add_ternary_reloption
+ * Add a new ternary reloption
+ */
+void
+add_ternary_reloption(bits32 kinds, const char *name, const char *desc,
+ ternary default_val, LOCKMODE lockmode)
+{
+ relopt_ternary *newoption = init_ternary_reloption(kinds, name, desc,
+ default_val, lockmode);
+
+ add_reloption((relopt_gen *) newoption);
+}
+
+/*
+ * add_local_ternary_reloption
+ * Add a new ternary local reloption
+ *
+ * 'offset' is offset of ternary-typed field.
+ */
+void
+add_local_ternary_reloption(local_relopts *relopts, const char *name,
+ const char *desc, ternary default_val,
+ int offset)
+{
+ relopt_ternary *newoption = init_ternary_reloption(RELOPT_KIND_LOCAL,
+ name, desc,
+ default_val, 0);
+
+ add_local_reloption(relopts, (relopt_gen *) newoption, offset);
+}
/*
* init_real_reloption
@@ -1617,6 +1689,18 @@ parse_one_reloption(relopt_value *option, char *text_str, int text_len,
option->gen->name, value)));
}
break;
+ case RELOPT_TYPE_TERNARY:
+ {
+ bool b;
+ parsed = parse_bool(value, &b);
+ option->values.ternary_val = b ? TERNARY_TRUE : TERNARY_FALSE;
+ if (validate && !parsed)
+ ereport(ERROR,
+ (errcode(ERRCODE_INVALID_PARAMETER_VALUE),
+ errmsg("invalid value for ternary option \"%s\": %s",
+ option->gen->name, value)));
+ }
+ break;
case RELOPT_TYPE_INT:
{
relopt_int *optint = (relopt_int *) option->gen;
@@ -1780,17 +1864,6 @@ fillRelOptions(void *rdopts, Size basesize,
char *itempos = ((char *) rdopts) + elems[j].offset;
char *string_val;
- /*
- * If isset_offset is provided, store whether the reloption is
- * set there.
- */
- if (elems[j].isset_offset > 0)
- {
- char *setpos = ((char *) rdopts) + elems[j].isset_offset;
-
- *(bool *) setpos = options[i].isset;
- }
-
switch (options[i].gen->type)
{
case RELOPT_TYPE_BOOL:
@@ -1798,6 +1871,11 @@ fillRelOptions(void *rdopts, Size basesize,
options[i].values.bool_val :
((relopt_bool *) options[i].gen)->default_val;
break;
+ case RELOPT_TYPE_TERNARY:
+ *(ternary *) itempos = options[i].isset ?
+ options[i].values.ternary_val :
+ ((relopt_ternary *) options[i].gen)->default_val;
+ break;
case RELOPT_TYPE_INT:
*(int *) itempos = options[i].isset ?
options[i].values.int_val :
@@ -1912,8 +1990,8 @@ default_reloptions(Datum reloptions, bool validate, relopt_kind kind)
offsetof(StdRdOptions, parallel_workers)},
{"vacuum_index_cleanup", RELOPT_TYPE_ENUM,
offsetof(StdRdOptions, vacuum_index_cleanup)},
- {"vacuum_truncate", RELOPT_TYPE_BOOL,
- offsetof(StdRdOptions, vacuum_truncate), offsetof(StdRdOptions, vacuum_truncate_set)},
+ {"vacuum_truncate", RELOPT_TYPE_TERNARY,
+ offsetof(StdRdOptions, vacuum_truncate)},
{"vacuum_max_eager_freeze_failure_rate", RELOPT_TYPE_REAL,
offsetof(StdRdOptions, vacuum_max_eager_freeze_failure_rate)}
};
@@ -1993,7 +2071,6 @@ build_local_reloptions(local_relopts *relopts, Datum options, bool validate)
elems[i].optname = opt->option->name;
elems[i].opttype = opt->option->type;
elems[i].offset = opt->offset;
- elems[i].isset_offset = 0; /* not supported for local relopts yet */
i++;
}
diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c
index 733ef40ae7..977babff54 100644
--- a/src/backend/commands/vacuum.c
+++ b/src/backend/commands/vacuum.c
@@ -2221,9 +2221,9 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams params,
{
StdRdOptions *opts = (StdRdOptions *) rel->rd_options;
- if (opts && opts->vacuum_truncate_set)
+ if (opts && opts->vacuum_truncate != TERNARY_UNSET)
{
- if (opts->vacuum_truncate)
+ if (opts->vacuum_truncate == TERNARY_TRUE)
params.truncate = VACOPTVALUE_ENABLED;
else
params.truncate = VACOPTVALUE_DISABLED;
diff --git a/src/include/access/reloptions.h b/src/include/access/reloptions.h
index a604a4702c..a436697658 100644
--- a/src/include/access/reloptions.h
+++ b/src/include/access/reloptions.h
@@ -29,6 +29,7 @@
typedef enum relopt_type
{
RELOPT_TYPE_BOOL,
+ RELOPT_TYPE_TERNARY, /* on, off, unset */
RELOPT_TYPE_INT,
RELOPT_TYPE_REAL,
RELOPT_TYPE_ENUM,
@@ -80,6 +81,7 @@ typedef struct relopt_value
union
{
bool bool_val;
+ ternary ternary_val;
int int_val;
double real_val;
int enum_val;
@@ -94,6 +96,12 @@ typedef struct relopt_bool
bool default_val;
} relopt_bool;
+typedef struct relopt_rernary
+{
+ relopt_gen gen;
+ int default_val;
+} relopt_ternary;
+
typedef struct relopt_int
{
relopt_gen gen;
@@ -152,19 +160,6 @@ typedef struct
const char *optname; /* option's name */
relopt_type opttype; /* option's datatype */
int offset; /* offset of field in result struct */
-
- /*
- * isset_offset is an optional offset of a field in the result struct that
- * stores whether the option is explicitly set for the relation or if it
- * just picked up the default value. In most cases, this can be
- * accomplished by giving the reloption a special out-of-range default
- * value (e.g., some integer reloptions use -2), but this isn't always
- * possible. For example, a Boolean reloption cannot be given an
- * out-of-range default, so we need another way to discover the source of
- * its value. This offset is only used if given a value greater than
- * zero.
- */
- int isset_offset;
} relopt_parse_elt;
/* Local reloption definition */
@@ -195,6 +190,8 @@ typedef struct local_relopts
extern relopt_kind add_reloption_kind(void);
extern void add_bool_reloption(bits32 kinds, const char *name, const char *desc,
bool default_val, LOCKMODE lockmode);
+extern void add_ternary_reloption(bits32 kinds, const char *name,
+ const char *desc, int default_val, LOCKMODE lockmode);
extern void add_int_reloption(bits32 kinds, const char *name, const char *desc,
int default_val, int min_val, int max_val,
LOCKMODE lockmode);
@@ -214,6 +211,9 @@ extern void register_reloptions_validator(local_relopts *relopts,
extern void add_local_bool_reloption(local_relopts *relopts, const char *name,
const char *desc, bool default_val,
int offset);
+extern void add_local_ternary_reloption(local_relopts *relopts,
+ const char *name, const char *desc,
+ ternary default_val, int offset);
extern void add_local_int_reloption(local_relopts *relopts, const char *name,
const char *desc, int default_val,
int min_val, int max_val, int offset);
diff --git a/src/include/c.h b/src/include/c.h
index 39022f8a9d..9c59ccccf8 100644
--- a/src/include/c.h
+++ b/src/include/c.h
@@ -488,6 +488,22 @@ typedef void (*pg_funcptr_t) (void);
#include <stdbool.h>
+/*
+ * ternary
+ * Boolean value with an extrea "unset" option
+ *
+ * Ternary data type is used in relation options that can be "true", "false" or
+ * "unset". Since relation options are used deep inside the PostgreSQL code,
+ * this type is declared globally.
+*/
+
+typedef enum ternary
+{
+ TERNARY_FALSE = 0,
+ TERNARY_TRUE = 1,
+ TERNARY_UNSET = -1
+} ternary;
+
/* ----------------------------------------------------------------
* Section 3: standard system types
diff --git a/src/include/utils/rel.h b/src/include/utils/rel.h
index b552359915..7346d618f9 100644
--- a/src/include/utils/rel.h
+++ b/src/include/utils/rel.h
@@ -346,8 +346,7 @@ typedef struct StdRdOptions
bool user_catalog_table; /* use as an additional catalog relation */
int parallel_workers; /* max number of parallel workers */
StdRdOptIndexCleanup vacuum_index_cleanup; /* controls index vacuuming */
- bool vacuum_truncate; /* enables vacuum to truncate a relation */
- bool vacuum_truncate_set; /* whether vacuum_truncate is set */
+ ternary vacuum_truncate; /* enables vacuum to truncate a relation */
/*
* Fraction of pages in a relation that vacuum can eagerly scan and fail
--
2.39.2
--nextPart5129932.5fSG56mABF
Content-Disposition: attachment;
filename="v2-0003-Add-alias-to-be-used-as-unset-state.patch"
Content-Transfer-Encoding: 7Bit
Content-Type: text/x-patch; charset="unicode-2-0-utf-8";
name="v2-0003-Add-alias-to-be-used-as-unset-state.patch"
^ permalink raw reply [nested|flat] 868+ messages in thread
* [PATCH v2 2/4] Introduce ternary reloptions
@ 2025-09-04 14:41 Nikolay Shaplov <[email protected]>
0 siblings, 0 replies; 868+ messages in thread
From: Nikolay Shaplov @ 2025-09-04 14:41 UTC (permalink / raw)
Introduce ternary reloption as a replacement for current `vacuum_truncate`
implementation. Remove `vacuum_truncate_set` additional flag and using
`TERNARY_UNSET` value instead.
---
src/backend/access/common/reloptions.c | 129 ++++++++++++++++++++-----
src/backend/commands/vacuum.c | 4 +-
src/include/access/reloptions.h | 26 ++---
src/include/c.h | 16 +++
src/include/utils/rel.h | 3 +-
5 files changed, 135 insertions(+), 43 deletions(-)
diff --git a/src/backend/access/common/reloptions.c b/src/backend/access/common/reloptions.c
index 0af3fea68f..f543f11001 100644
--- a/src/backend/access/common/reloptions.c
+++ b/src/backend/access/common/reloptions.c
@@ -40,9 +40,9 @@
*
* To add an option:
*
- * (i) decide on a type (bool, integer, real, enum, string), name, default
- * value, upper and lower bounds (if applicable); for strings, consider a
- * validation routine.
+ * (i) decide on a type (bool, ternary, integer, real, enum, string), name,
+ * default value, upper and lower bounds (if applicable); for strings,
+ * consider a validation routine.
* (ii) add a record below (or use add_<type>_reloption).
* (iii) add it to the appropriate options struct (perhaps StdRdOptions)
* (iv) add it to the appropriate handling routine (perhaps
@@ -147,15 +147,6 @@ static relopt_bool boolRelOpts[] =
},
false
},
- {
- {
- "vacuum_truncate",
- "Enables vacuum to truncate empty pages at the end of this table",
- RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
- ShareUpdateExclusiveLock
- },
- true
- },
{
{
"deduplicate_items",
@@ -170,6 +161,21 @@ static relopt_bool boolRelOpts[] =
{{NULL}}
};
+static relopt_ternary ternaryRelOpts[] =
+{
+ {
+ {
+ "vacuum_truncate",
+ "Enables vacuum to truncate empty pages at the end of this table",
+ RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
+ ShareUpdateExclusiveLock
+ },
+ TERNARY_UNSET
+ },
+ /* list terminator */
+ {{NULL}}
+};
+
static relopt_int intRelOpts[] =
{
{
@@ -600,6 +606,13 @@ initialize_reloptions(void)
boolRelOpts[i].gen.lockmode));
j++;
}
+ for (i = 0; ternaryRelOpts[i].gen.name; i++)
+ {
+ Assert(DoLockModesConflict(ternaryRelOpts[i].gen.lockmode,
+ ternaryRelOpts[i].gen.lockmode));
+ j++;
+ }
+
for (i = 0; intRelOpts[i].gen.name; i++)
{
Assert(DoLockModesConflict(intRelOpts[i].gen.lockmode,
@@ -640,6 +653,14 @@ initialize_reloptions(void)
j++;
}
+ for (i = 0; ternaryRelOpts[i].gen.name; i++)
+ {
+ relOpts[j] = &ternaryRelOpts[i].gen;
+ relOpts[j]->type = RELOPT_TYPE_TERNARY;
+ relOpts[j]->namelen = strlen(relOpts[j]->name);
+ j++;
+ }
+
for (i = 0; intRelOpts[i].gen.name; i++)
{
relOpts[j] = &intRelOpts[i].gen;
@@ -800,6 +821,9 @@ allocate_reloption(bits32 kinds, int type, const char *name, const char *desc,
case RELOPT_TYPE_BOOL:
size = sizeof(relopt_bool);
break;
+ case RELOPT_TYPE_TERNARY:
+ size = sizeof(relopt_ternary);
+ break;
case RELOPT_TYPE_INT:
size = sizeof(relopt_int);
break;
@@ -883,6 +907,54 @@ add_local_bool_reloption(local_relopts *relopts, const char *name,
add_local_reloption(relopts, (relopt_gen *) newoption, offset);
}
+/*
+ * init_ternary_reloption
+ * Allocate and initialize a new ternary reloption
+ */
+static relopt_ternary *
+init_ternary_reloption(bits32 kinds, const char *name, const char *desc,
+ ternary default_val, LOCKMODE lockmode)
+{
+ relopt_ternary *newoption;
+
+ newoption = (relopt_ternary *) allocate_reloption(kinds,
+ RELOPT_TYPE_TERNARY, name, desc, lockmode);
+ newoption->default_val = default_val;
+
+ return newoption;
+}
+
+/*
+ * add_ternary_reloption
+ * Add a new ternary reloption
+ */
+void
+add_ternary_reloption(bits32 kinds, const char *name, const char *desc,
+ ternary default_val, LOCKMODE lockmode)
+{
+ relopt_ternary *newoption = init_ternary_reloption(kinds, name, desc,
+ default_val, lockmode);
+
+ add_reloption((relopt_gen *) newoption);
+}
+
+/*
+ * add_local_ternary_reloption
+ * Add a new ternary local reloption
+ *
+ * 'offset' is offset of ternary-typed field.
+ */
+void
+add_local_ternary_reloption(local_relopts *relopts, const char *name,
+ const char *desc, ternary default_val,
+ int offset)
+{
+ relopt_ternary *newoption = init_ternary_reloption(RELOPT_KIND_LOCAL,
+ name, desc,
+ default_val, 0);
+
+ add_local_reloption(relopts, (relopt_gen *) newoption, offset);
+}
/*
* init_real_reloption
@@ -1617,6 +1689,18 @@ parse_one_reloption(relopt_value *option, char *text_str, int text_len,
option->gen->name, value)));
}
break;
+ case RELOPT_TYPE_TERNARY:
+ {
+ bool b;
+ parsed = parse_bool(value, &b);
+ option->values.ternary_val = b ? TERNARY_TRUE : TERNARY_FALSE;
+ if (validate && !parsed)
+ ereport(ERROR,
+ (errcode(ERRCODE_INVALID_PARAMETER_VALUE),
+ errmsg("invalid value for ternary option \"%s\": %s",
+ option->gen->name, value)));
+ }
+ break;
case RELOPT_TYPE_INT:
{
relopt_int *optint = (relopt_int *) option->gen;
@@ -1780,17 +1864,6 @@ fillRelOptions(void *rdopts, Size basesize,
char *itempos = ((char *) rdopts) + elems[j].offset;
char *string_val;
- /*
- * If isset_offset is provided, store whether the reloption is
- * set there.
- */
- if (elems[j].isset_offset > 0)
- {
- char *setpos = ((char *) rdopts) + elems[j].isset_offset;
-
- *(bool *) setpos = options[i].isset;
- }
-
switch (options[i].gen->type)
{
case RELOPT_TYPE_BOOL:
@@ -1798,6 +1871,11 @@ fillRelOptions(void *rdopts, Size basesize,
options[i].values.bool_val :
((relopt_bool *) options[i].gen)->default_val;
break;
+ case RELOPT_TYPE_TERNARY:
+ *(ternary *) itempos = options[i].isset ?
+ options[i].values.ternary_val :
+ ((relopt_ternary *) options[i].gen)->default_val;
+ break;
case RELOPT_TYPE_INT:
*(int *) itempos = options[i].isset ?
options[i].values.int_val :
@@ -1912,8 +1990,8 @@ default_reloptions(Datum reloptions, bool validate, relopt_kind kind)
offsetof(StdRdOptions, parallel_workers)},
{"vacuum_index_cleanup", RELOPT_TYPE_ENUM,
offsetof(StdRdOptions, vacuum_index_cleanup)},
- {"vacuum_truncate", RELOPT_TYPE_BOOL,
- offsetof(StdRdOptions, vacuum_truncate), offsetof(StdRdOptions, vacuum_truncate_set)},
+ {"vacuum_truncate", RELOPT_TYPE_TERNARY,
+ offsetof(StdRdOptions, vacuum_truncate)},
{"vacuum_max_eager_freeze_failure_rate", RELOPT_TYPE_REAL,
offsetof(StdRdOptions, vacuum_max_eager_freeze_failure_rate)}
};
@@ -1993,7 +2071,6 @@ build_local_reloptions(local_relopts *relopts, Datum options, bool validate)
elems[i].optname = opt->option->name;
elems[i].opttype = opt->option->type;
elems[i].offset = opt->offset;
- elems[i].isset_offset = 0; /* not supported for local relopts yet */
i++;
}
diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c
index 733ef40ae7..977babff54 100644
--- a/src/backend/commands/vacuum.c
+++ b/src/backend/commands/vacuum.c
@@ -2221,9 +2221,9 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams params,
{
StdRdOptions *opts = (StdRdOptions *) rel->rd_options;
- if (opts && opts->vacuum_truncate_set)
+ if (opts && opts->vacuum_truncate != TERNARY_UNSET)
{
- if (opts->vacuum_truncate)
+ if (opts->vacuum_truncate == TERNARY_TRUE)
params.truncate = VACOPTVALUE_ENABLED;
else
params.truncate = VACOPTVALUE_DISABLED;
diff --git a/src/include/access/reloptions.h b/src/include/access/reloptions.h
index a604a4702c..a436697658 100644
--- a/src/include/access/reloptions.h
+++ b/src/include/access/reloptions.h
@@ -29,6 +29,7 @@
typedef enum relopt_type
{
RELOPT_TYPE_BOOL,
+ RELOPT_TYPE_TERNARY, /* on, off, unset */
RELOPT_TYPE_INT,
RELOPT_TYPE_REAL,
RELOPT_TYPE_ENUM,
@@ -80,6 +81,7 @@ typedef struct relopt_value
union
{
bool bool_val;
+ ternary ternary_val;
int int_val;
double real_val;
int enum_val;
@@ -94,6 +96,12 @@ typedef struct relopt_bool
bool default_val;
} relopt_bool;
+typedef struct relopt_rernary
+{
+ relopt_gen gen;
+ int default_val;
+} relopt_ternary;
+
typedef struct relopt_int
{
relopt_gen gen;
@@ -152,19 +160,6 @@ typedef struct
const char *optname; /* option's name */
relopt_type opttype; /* option's datatype */
int offset; /* offset of field in result struct */
-
- /*
- * isset_offset is an optional offset of a field in the result struct that
- * stores whether the option is explicitly set for the relation or if it
- * just picked up the default value. In most cases, this can be
- * accomplished by giving the reloption a special out-of-range default
- * value (e.g., some integer reloptions use -2), but this isn't always
- * possible. For example, a Boolean reloption cannot be given an
- * out-of-range default, so we need another way to discover the source of
- * its value. This offset is only used if given a value greater than
- * zero.
- */
- int isset_offset;
} relopt_parse_elt;
/* Local reloption definition */
@@ -195,6 +190,8 @@ typedef struct local_relopts
extern relopt_kind add_reloption_kind(void);
extern void add_bool_reloption(bits32 kinds, const char *name, const char *desc,
bool default_val, LOCKMODE lockmode);
+extern void add_ternary_reloption(bits32 kinds, const char *name,
+ const char *desc, int default_val, LOCKMODE lockmode);
extern void add_int_reloption(bits32 kinds, const char *name, const char *desc,
int default_val, int min_val, int max_val,
LOCKMODE lockmode);
@@ -214,6 +211,9 @@ extern void register_reloptions_validator(local_relopts *relopts,
extern void add_local_bool_reloption(local_relopts *relopts, const char *name,
const char *desc, bool default_val,
int offset);
+extern void add_local_ternary_reloption(local_relopts *relopts,
+ const char *name, const char *desc,
+ ternary default_val, int offset);
extern void add_local_int_reloption(local_relopts *relopts, const char *name,
const char *desc, int default_val,
int min_val, int max_val, int offset);
diff --git a/src/include/c.h b/src/include/c.h
index 39022f8a9d..9c59ccccf8 100644
--- a/src/include/c.h
+++ b/src/include/c.h
@@ -488,6 +488,22 @@ typedef void (*pg_funcptr_t) (void);
#include <stdbool.h>
+/*
+ * ternary
+ * Boolean value with an extrea "unset" option
+ *
+ * Ternary data type is used in relation options that can be "true", "false" or
+ * "unset". Since relation options are used deep inside the PostgreSQL code,
+ * this type is declared globally.
+*/
+
+typedef enum ternary
+{
+ TERNARY_FALSE = 0,
+ TERNARY_TRUE = 1,
+ TERNARY_UNSET = -1
+} ternary;
+
/* ----------------------------------------------------------------
* Section 3: standard system types
diff --git a/src/include/utils/rel.h b/src/include/utils/rel.h
index b552359915..7346d618f9 100644
--- a/src/include/utils/rel.h
+++ b/src/include/utils/rel.h
@@ -346,8 +346,7 @@ typedef struct StdRdOptions
bool user_catalog_table; /* use as an additional catalog relation */
int parallel_workers; /* max number of parallel workers */
StdRdOptIndexCleanup vacuum_index_cleanup; /* controls index vacuuming */
- bool vacuum_truncate; /* enables vacuum to truncate a relation */
- bool vacuum_truncate_set; /* whether vacuum_truncate is set */
+ ternary vacuum_truncate; /* enables vacuum to truncate a relation */
/*
* Fraction of pages in a relation that vacuum can eagerly scan and fail
--
2.39.2
--nextPart5129932.5fSG56mABF
Content-Disposition: attachment;
filename="v2-0003-Add-alias-to-be-used-as-unset-state.patch"
Content-Transfer-Encoding: 7Bit
Content-Type: text/x-patch; charset="unicode-2-0-utf-8";
name="v2-0003-Add-alias-to-be-used-as-unset-state.patch"
^ permalink raw reply [nested|flat] 868+ messages in thread
* [PATCH v2a 2/4] Introduce ternary reloptions
@ 2025-09-04 14:41 Nikolay Shaplov <[email protected]>
0 siblings, 0 replies; 868+ messages in thread
From: Nikolay Shaplov @ 2025-09-04 14:41 UTC (permalink / raw)
Introduce ternary reloption as a replacement for current `vacuum_truncate`
implementation. Remove `vacuum_truncate_set` additional flag and using
`TERNARY_UNSET` value instead.
---
src/backend/access/common/reloptions.c | 129 ++++++++++++++++++++-----
src/backend/commands/vacuum.c | 4 +-
src/include/access/reloptions.h | 26 ++---
src/include/c.h | 16 +++
src/include/utils/rel.h | 3 +-
5 files changed, 135 insertions(+), 43 deletions(-)
diff --git a/src/backend/access/common/reloptions.c b/src/backend/access/common/reloptions.c
index 0af3fea68f..f543f11001 100644
--- a/src/backend/access/common/reloptions.c
+++ b/src/backend/access/common/reloptions.c
@@ -40,9 +40,9 @@
*
* To add an option:
*
- * (i) decide on a type (bool, integer, real, enum, string), name, default
- * value, upper and lower bounds (if applicable); for strings, consider a
- * validation routine.
+ * (i) decide on a type (bool, ternary, integer, real, enum, string), name,
+ * default value, upper and lower bounds (if applicable); for strings,
+ * consider a validation routine.
* (ii) add a record below (or use add_<type>_reloption).
* (iii) add it to the appropriate options struct (perhaps StdRdOptions)
* (iv) add it to the appropriate handling routine (perhaps
@@ -147,15 +147,6 @@ static relopt_bool boolRelOpts[] =
},
false
},
- {
- {
- "vacuum_truncate",
- "Enables vacuum to truncate empty pages at the end of this table",
- RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
- ShareUpdateExclusiveLock
- },
- true
- },
{
{
"deduplicate_items",
@@ -170,6 +161,21 @@ static relopt_bool boolRelOpts[] =
{{NULL}}
};
+static relopt_ternary ternaryRelOpts[] =
+{
+ {
+ {
+ "vacuum_truncate",
+ "Enables vacuum to truncate empty pages at the end of this table",
+ RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
+ ShareUpdateExclusiveLock
+ },
+ TERNARY_UNSET
+ },
+ /* list terminator */
+ {{NULL}}
+};
+
static relopt_int intRelOpts[] =
{
{
@@ -600,6 +606,13 @@ initialize_reloptions(void)
boolRelOpts[i].gen.lockmode));
j++;
}
+ for (i = 0; ternaryRelOpts[i].gen.name; i++)
+ {
+ Assert(DoLockModesConflict(ternaryRelOpts[i].gen.lockmode,
+ ternaryRelOpts[i].gen.lockmode));
+ j++;
+ }
+
for (i = 0; intRelOpts[i].gen.name; i++)
{
Assert(DoLockModesConflict(intRelOpts[i].gen.lockmode,
@@ -640,6 +653,14 @@ initialize_reloptions(void)
j++;
}
+ for (i = 0; ternaryRelOpts[i].gen.name; i++)
+ {
+ relOpts[j] = &ternaryRelOpts[i].gen;
+ relOpts[j]->type = RELOPT_TYPE_TERNARY;
+ relOpts[j]->namelen = strlen(relOpts[j]->name);
+ j++;
+ }
+
for (i = 0; intRelOpts[i].gen.name; i++)
{
relOpts[j] = &intRelOpts[i].gen;
@@ -800,6 +821,9 @@ allocate_reloption(bits32 kinds, int type, const char *name, const char *desc,
case RELOPT_TYPE_BOOL:
size = sizeof(relopt_bool);
break;
+ case RELOPT_TYPE_TERNARY:
+ size = sizeof(relopt_ternary);
+ break;
case RELOPT_TYPE_INT:
size = sizeof(relopt_int);
break;
@@ -883,6 +907,54 @@ add_local_bool_reloption(local_relopts *relopts, const char *name,
add_local_reloption(relopts, (relopt_gen *) newoption, offset);
}
+/*
+ * init_ternary_reloption
+ * Allocate and initialize a new ternary reloption
+ */
+static relopt_ternary *
+init_ternary_reloption(bits32 kinds, const char *name, const char *desc,
+ ternary default_val, LOCKMODE lockmode)
+{
+ relopt_ternary *newoption;
+
+ newoption = (relopt_ternary *) allocate_reloption(kinds,
+ RELOPT_TYPE_TERNARY, name, desc, lockmode);
+ newoption->default_val = default_val;
+
+ return newoption;
+}
+
+/*
+ * add_ternary_reloption
+ * Add a new ternary reloption
+ */
+void
+add_ternary_reloption(bits32 kinds, const char *name, const char *desc,
+ ternary default_val, LOCKMODE lockmode)
+{
+ relopt_ternary *newoption = init_ternary_reloption(kinds, name, desc,
+ default_val, lockmode);
+
+ add_reloption((relopt_gen *) newoption);
+}
+
+/*
+ * add_local_ternary_reloption
+ * Add a new ternary local reloption
+ *
+ * 'offset' is offset of ternary-typed field.
+ */
+void
+add_local_ternary_reloption(local_relopts *relopts, const char *name,
+ const char *desc, ternary default_val,
+ int offset)
+{
+ relopt_ternary *newoption = init_ternary_reloption(RELOPT_KIND_LOCAL,
+ name, desc,
+ default_val, 0);
+
+ add_local_reloption(relopts, (relopt_gen *) newoption, offset);
+}
/*
* init_real_reloption
@@ -1617,6 +1689,18 @@ parse_one_reloption(relopt_value *option, char *text_str, int text_len,
option->gen->name, value)));
}
break;
+ case RELOPT_TYPE_TERNARY:
+ {
+ bool b;
+ parsed = parse_bool(value, &b);
+ option->values.ternary_val = b ? TERNARY_TRUE : TERNARY_FALSE;
+ if (validate && !parsed)
+ ereport(ERROR,
+ (errcode(ERRCODE_INVALID_PARAMETER_VALUE),
+ errmsg("invalid value for ternary option \"%s\": %s",
+ option->gen->name, value)));
+ }
+ break;
case RELOPT_TYPE_INT:
{
relopt_int *optint = (relopt_int *) option->gen;
@@ -1780,17 +1864,6 @@ fillRelOptions(void *rdopts, Size basesize,
char *itempos = ((char *) rdopts) + elems[j].offset;
char *string_val;
- /*
- * If isset_offset is provided, store whether the reloption is
- * set there.
- */
- if (elems[j].isset_offset > 0)
- {
- char *setpos = ((char *) rdopts) + elems[j].isset_offset;
-
- *(bool *) setpos = options[i].isset;
- }
-
switch (options[i].gen->type)
{
case RELOPT_TYPE_BOOL:
@@ -1798,6 +1871,11 @@ fillRelOptions(void *rdopts, Size basesize,
options[i].values.bool_val :
((relopt_bool *) options[i].gen)->default_val;
break;
+ case RELOPT_TYPE_TERNARY:
+ *(ternary *) itempos = options[i].isset ?
+ options[i].values.ternary_val :
+ ((relopt_ternary *) options[i].gen)->default_val;
+ break;
case RELOPT_TYPE_INT:
*(int *) itempos = options[i].isset ?
options[i].values.int_val :
@@ -1912,8 +1990,8 @@ default_reloptions(Datum reloptions, bool validate, relopt_kind kind)
offsetof(StdRdOptions, parallel_workers)},
{"vacuum_index_cleanup", RELOPT_TYPE_ENUM,
offsetof(StdRdOptions, vacuum_index_cleanup)},
- {"vacuum_truncate", RELOPT_TYPE_BOOL,
- offsetof(StdRdOptions, vacuum_truncate), offsetof(StdRdOptions, vacuum_truncate_set)},
+ {"vacuum_truncate", RELOPT_TYPE_TERNARY,
+ offsetof(StdRdOptions, vacuum_truncate)},
{"vacuum_max_eager_freeze_failure_rate", RELOPT_TYPE_REAL,
offsetof(StdRdOptions, vacuum_max_eager_freeze_failure_rate)}
};
@@ -1993,7 +2071,6 @@ build_local_reloptions(local_relopts *relopts, Datum options, bool validate)
elems[i].optname = opt->option->name;
elems[i].opttype = opt->option->type;
elems[i].offset = opt->offset;
- elems[i].isset_offset = 0; /* not supported for local relopts yet */
i++;
}
diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c
index 733ef40ae7..977babff54 100644
--- a/src/backend/commands/vacuum.c
+++ b/src/backend/commands/vacuum.c
@@ -2221,9 +2221,9 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams params,
{
StdRdOptions *opts = (StdRdOptions *) rel->rd_options;
- if (opts && opts->vacuum_truncate_set)
+ if (opts && opts->vacuum_truncate != TERNARY_UNSET)
{
- if (opts->vacuum_truncate)
+ if (opts->vacuum_truncate == TERNARY_TRUE)
params.truncate = VACOPTVALUE_ENABLED;
else
params.truncate = VACOPTVALUE_DISABLED;
diff --git a/src/include/access/reloptions.h b/src/include/access/reloptions.h
index a604a4702c..a436697658 100644
--- a/src/include/access/reloptions.h
+++ b/src/include/access/reloptions.h
@@ -29,6 +29,7 @@
typedef enum relopt_type
{
RELOPT_TYPE_BOOL,
+ RELOPT_TYPE_TERNARY, /* on, off, unset */
RELOPT_TYPE_INT,
RELOPT_TYPE_REAL,
RELOPT_TYPE_ENUM,
@@ -80,6 +81,7 @@ typedef struct relopt_value
union
{
bool bool_val;
+ ternary ternary_val;
int int_val;
double real_val;
int enum_val;
@@ -94,6 +96,12 @@ typedef struct relopt_bool
bool default_val;
} relopt_bool;
+typedef struct relopt_rernary
+{
+ relopt_gen gen;
+ int default_val;
+} relopt_ternary;
+
typedef struct relopt_int
{
relopt_gen gen;
@@ -152,19 +160,6 @@ typedef struct
const char *optname; /* option's name */
relopt_type opttype; /* option's datatype */
int offset; /* offset of field in result struct */
-
- /*
- * isset_offset is an optional offset of a field in the result struct that
- * stores whether the option is explicitly set for the relation or if it
- * just picked up the default value. In most cases, this can be
- * accomplished by giving the reloption a special out-of-range default
- * value (e.g., some integer reloptions use -2), but this isn't always
- * possible. For example, a Boolean reloption cannot be given an
- * out-of-range default, so we need another way to discover the source of
- * its value. This offset is only used if given a value greater than
- * zero.
- */
- int isset_offset;
} relopt_parse_elt;
/* Local reloption definition */
@@ -195,6 +190,8 @@ typedef struct local_relopts
extern relopt_kind add_reloption_kind(void);
extern void add_bool_reloption(bits32 kinds, const char *name, const char *desc,
bool default_val, LOCKMODE lockmode);
+extern void add_ternary_reloption(bits32 kinds, const char *name,
+ const char *desc, int default_val, LOCKMODE lockmode);
extern void add_int_reloption(bits32 kinds, const char *name, const char *desc,
int default_val, int min_val, int max_val,
LOCKMODE lockmode);
@@ -214,6 +211,9 @@ extern void register_reloptions_validator(local_relopts *relopts,
extern void add_local_bool_reloption(local_relopts *relopts, const char *name,
const char *desc, bool default_val,
int offset);
+extern void add_local_ternary_reloption(local_relopts *relopts,
+ const char *name, const char *desc,
+ ternary default_val, int offset);
extern void add_local_int_reloption(local_relopts *relopts, const char *name,
const char *desc, int default_val,
int min_val, int max_val, int offset);
diff --git a/src/include/c.h b/src/include/c.h
index 39022f8a9d..9c59ccccf8 100644
--- a/src/include/c.h
+++ b/src/include/c.h
@@ -488,6 +488,22 @@ typedef void (*pg_funcptr_t) (void);
#include <stdbool.h>
+/*
+ * ternary
+ * Boolean value with an extrea "unset" option
+ *
+ * Ternary data type is used in relation options that can be "true", "false" or
+ * "unset". Since relation options are used deep inside the PostgreSQL code,
+ * this type is declared globally.
+*/
+
+typedef enum ternary
+{
+ TERNARY_FALSE = 0,
+ TERNARY_TRUE = 1,
+ TERNARY_UNSET = -1
+} ternary;
+
/* ----------------------------------------------------------------
* Section 3: standard system types
diff --git a/src/include/utils/rel.h b/src/include/utils/rel.h
index b552359915..7346d618f9 100644
--- a/src/include/utils/rel.h
+++ b/src/include/utils/rel.h
@@ -346,8 +346,7 @@ typedef struct StdRdOptions
bool user_catalog_table; /* use as an additional catalog relation */
int parallel_workers; /* max number of parallel workers */
StdRdOptIndexCleanup vacuum_index_cleanup; /* controls index vacuuming */
- bool vacuum_truncate; /* enables vacuum to truncate a relation */
- bool vacuum_truncate_set; /* whether vacuum_truncate is set */
+ ternary vacuum_truncate; /* enables vacuum to truncate a relation */
/*
* Fraction of pages in a relation that vacuum can eagerly scan and fail
--
2.39.2
--nextPart4155600.3ZeAukHxDK
Content-Disposition: attachment;
filename="v2a-0003-Add-alias-to-be-used-as-unset-state.patch"
Content-Transfer-Encoding: 7Bit
Content-Type: text/x-patch; charset="unicode-2-0-utf-8";
name="v2a-0003-Add-alias-to-be-used-as-unset-state.patch"
^ permalink raw reply [nested|flat] 868+ messages in thread
* [PATCH v2 2/4] Introduce ternary reloptions
@ 2025-09-04 14:41 Nikolay Shaplov <[email protected]>
0 siblings, 0 replies; 868+ messages in thread
From: Nikolay Shaplov @ 2025-09-04 14:41 UTC (permalink / raw)
Introduce ternary reloption as a replacement for current `vacuum_truncate`
implementation. Remove `vacuum_truncate_set` additional flag and using
`TERNARY_UNSET` value instead.
---
src/backend/access/common/reloptions.c | 129 ++++++++++++++++++++-----
src/backend/commands/vacuum.c | 4 +-
src/include/access/reloptions.h | 26 ++---
src/include/c.h | 16 +++
src/include/utils/rel.h | 3 +-
5 files changed, 135 insertions(+), 43 deletions(-)
diff --git a/src/backend/access/common/reloptions.c b/src/backend/access/common/reloptions.c
index 0af3fea68f..f543f11001 100644
--- a/src/backend/access/common/reloptions.c
+++ b/src/backend/access/common/reloptions.c
@@ -40,9 +40,9 @@
*
* To add an option:
*
- * (i) decide on a type (bool, integer, real, enum, string), name, default
- * value, upper and lower bounds (if applicable); for strings, consider a
- * validation routine.
+ * (i) decide on a type (bool, ternary, integer, real, enum, string), name,
+ * default value, upper and lower bounds (if applicable); for strings,
+ * consider a validation routine.
* (ii) add a record below (or use add_<type>_reloption).
* (iii) add it to the appropriate options struct (perhaps StdRdOptions)
* (iv) add it to the appropriate handling routine (perhaps
@@ -147,15 +147,6 @@ static relopt_bool boolRelOpts[] =
},
false
},
- {
- {
- "vacuum_truncate",
- "Enables vacuum to truncate empty pages at the end of this table",
- RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
- ShareUpdateExclusiveLock
- },
- true
- },
{
{
"deduplicate_items",
@@ -170,6 +161,21 @@ static relopt_bool boolRelOpts[] =
{{NULL}}
};
+static relopt_ternary ternaryRelOpts[] =
+{
+ {
+ {
+ "vacuum_truncate",
+ "Enables vacuum to truncate empty pages at the end of this table",
+ RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
+ ShareUpdateExclusiveLock
+ },
+ TERNARY_UNSET
+ },
+ /* list terminator */
+ {{NULL}}
+};
+
static relopt_int intRelOpts[] =
{
{
@@ -600,6 +606,13 @@ initialize_reloptions(void)
boolRelOpts[i].gen.lockmode));
j++;
}
+ for (i = 0; ternaryRelOpts[i].gen.name; i++)
+ {
+ Assert(DoLockModesConflict(ternaryRelOpts[i].gen.lockmode,
+ ternaryRelOpts[i].gen.lockmode));
+ j++;
+ }
+
for (i = 0; intRelOpts[i].gen.name; i++)
{
Assert(DoLockModesConflict(intRelOpts[i].gen.lockmode,
@@ -640,6 +653,14 @@ initialize_reloptions(void)
j++;
}
+ for (i = 0; ternaryRelOpts[i].gen.name; i++)
+ {
+ relOpts[j] = &ternaryRelOpts[i].gen;
+ relOpts[j]->type = RELOPT_TYPE_TERNARY;
+ relOpts[j]->namelen = strlen(relOpts[j]->name);
+ j++;
+ }
+
for (i = 0; intRelOpts[i].gen.name; i++)
{
relOpts[j] = &intRelOpts[i].gen;
@@ -800,6 +821,9 @@ allocate_reloption(bits32 kinds, int type, const char *name, const char *desc,
case RELOPT_TYPE_BOOL:
size = sizeof(relopt_bool);
break;
+ case RELOPT_TYPE_TERNARY:
+ size = sizeof(relopt_ternary);
+ break;
case RELOPT_TYPE_INT:
size = sizeof(relopt_int);
break;
@@ -883,6 +907,54 @@ add_local_bool_reloption(local_relopts *relopts, const char *name,
add_local_reloption(relopts, (relopt_gen *) newoption, offset);
}
+/*
+ * init_ternary_reloption
+ * Allocate and initialize a new ternary reloption
+ */
+static relopt_ternary *
+init_ternary_reloption(bits32 kinds, const char *name, const char *desc,
+ ternary default_val, LOCKMODE lockmode)
+{
+ relopt_ternary *newoption;
+
+ newoption = (relopt_ternary *) allocate_reloption(kinds,
+ RELOPT_TYPE_TERNARY, name, desc, lockmode);
+ newoption->default_val = default_val;
+
+ return newoption;
+}
+
+/*
+ * add_ternary_reloption
+ * Add a new ternary reloption
+ */
+void
+add_ternary_reloption(bits32 kinds, const char *name, const char *desc,
+ ternary default_val, LOCKMODE lockmode)
+{
+ relopt_ternary *newoption = init_ternary_reloption(kinds, name, desc,
+ default_val, lockmode);
+
+ add_reloption((relopt_gen *) newoption);
+}
+
+/*
+ * add_local_ternary_reloption
+ * Add a new ternary local reloption
+ *
+ * 'offset' is offset of ternary-typed field.
+ */
+void
+add_local_ternary_reloption(local_relopts *relopts, const char *name,
+ const char *desc, ternary default_val,
+ int offset)
+{
+ relopt_ternary *newoption = init_ternary_reloption(RELOPT_KIND_LOCAL,
+ name, desc,
+ default_val, 0);
+
+ add_local_reloption(relopts, (relopt_gen *) newoption, offset);
+}
/*
* init_real_reloption
@@ -1617,6 +1689,18 @@ parse_one_reloption(relopt_value *option, char *text_str, int text_len,
option->gen->name, value)));
}
break;
+ case RELOPT_TYPE_TERNARY:
+ {
+ bool b;
+ parsed = parse_bool(value, &b);
+ option->values.ternary_val = b ? TERNARY_TRUE : TERNARY_FALSE;
+ if (validate && !parsed)
+ ereport(ERROR,
+ (errcode(ERRCODE_INVALID_PARAMETER_VALUE),
+ errmsg("invalid value for ternary option \"%s\": %s",
+ option->gen->name, value)));
+ }
+ break;
case RELOPT_TYPE_INT:
{
relopt_int *optint = (relopt_int *) option->gen;
@@ -1780,17 +1864,6 @@ fillRelOptions(void *rdopts, Size basesize,
char *itempos = ((char *) rdopts) + elems[j].offset;
char *string_val;
- /*
- * If isset_offset is provided, store whether the reloption is
- * set there.
- */
- if (elems[j].isset_offset > 0)
- {
- char *setpos = ((char *) rdopts) + elems[j].isset_offset;
-
- *(bool *) setpos = options[i].isset;
- }
-
switch (options[i].gen->type)
{
case RELOPT_TYPE_BOOL:
@@ -1798,6 +1871,11 @@ fillRelOptions(void *rdopts, Size basesize,
options[i].values.bool_val :
((relopt_bool *) options[i].gen)->default_val;
break;
+ case RELOPT_TYPE_TERNARY:
+ *(ternary *) itempos = options[i].isset ?
+ options[i].values.ternary_val :
+ ((relopt_ternary *) options[i].gen)->default_val;
+ break;
case RELOPT_TYPE_INT:
*(int *) itempos = options[i].isset ?
options[i].values.int_val :
@@ -1912,8 +1990,8 @@ default_reloptions(Datum reloptions, bool validate, relopt_kind kind)
offsetof(StdRdOptions, parallel_workers)},
{"vacuum_index_cleanup", RELOPT_TYPE_ENUM,
offsetof(StdRdOptions, vacuum_index_cleanup)},
- {"vacuum_truncate", RELOPT_TYPE_BOOL,
- offsetof(StdRdOptions, vacuum_truncate), offsetof(StdRdOptions, vacuum_truncate_set)},
+ {"vacuum_truncate", RELOPT_TYPE_TERNARY,
+ offsetof(StdRdOptions, vacuum_truncate)},
{"vacuum_max_eager_freeze_failure_rate", RELOPT_TYPE_REAL,
offsetof(StdRdOptions, vacuum_max_eager_freeze_failure_rate)}
};
@@ -1993,7 +2071,6 @@ build_local_reloptions(local_relopts *relopts, Datum options, bool validate)
elems[i].optname = opt->option->name;
elems[i].opttype = opt->option->type;
elems[i].offset = opt->offset;
- elems[i].isset_offset = 0; /* not supported for local relopts yet */
i++;
}
diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c
index 733ef40ae7..977babff54 100644
--- a/src/backend/commands/vacuum.c
+++ b/src/backend/commands/vacuum.c
@@ -2221,9 +2221,9 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams params,
{
StdRdOptions *opts = (StdRdOptions *) rel->rd_options;
- if (opts && opts->vacuum_truncate_set)
+ if (opts && opts->vacuum_truncate != TERNARY_UNSET)
{
- if (opts->vacuum_truncate)
+ if (opts->vacuum_truncate == TERNARY_TRUE)
params.truncate = VACOPTVALUE_ENABLED;
else
params.truncate = VACOPTVALUE_DISABLED;
diff --git a/src/include/access/reloptions.h b/src/include/access/reloptions.h
index a604a4702c..a436697658 100644
--- a/src/include/access/reloptions.h
+++ b/src/include/access/reloptions.h
@@ -29,6 +29,7 @@
typedef enum relopt_type
{
RELOPT_TYPE_BOOL,
+ RELOPT_TYPE_TERNARY, /* on, off, unset */
RELOPT_TYPE_INT,
RELOPT_TYPE_REAL,
RELOPT_TYPE_ENUM,
@@ -80,6 +81,7 @@ typedef struct relopt_value
union
{
bool bool_val;
+ ternary ternary_val;
int int_val;
double real_val;
int enum_val;
@@ -94,6 +96,12 @@ typedef struct relopt_bool
bool default_val;
} relopt_bool;
+typedef struct relopt_rernary
+{
+ relopt_gen gen;
+ int default_val;
+} relopt_ternary;
+
typedef struct relopt_int
{
relopt_gen gen;
@@ -152,19 +160,6 @@ typedef struct
const char *optname; /* option's name */
relopt_type opttype; /* option's datatype */
int offset; /* offset of field in result struct */
-
- /*
- * isset_offset is an optional offset of a field in the result struct that
- * stores whether the option is explicitly set for the relation or if it
- * just picked up the default value. In most cases, this can be
- * accomplished by giving the reloption a special out-of-range default
- * value (e.g., some integer reloptions use -2), but this isn't always
- * possible. For example, a Boolean reloption cannot be given an
- * out-of-range default, so we need another way to discover the source of
- * its value. This offset is only used if given a value greater than
- * zero.
- */
- int isset_offset;
} relopt_parse_elt;
/* Local reloption definition */
@@ -195,6 +190,8 @@ typedef struct local_relopts
extern relopt_kind add_reloption_kind(void);
extern void add_bool_reloption(bits32 kinds, const char *name, const char *desc,
bool default_val, LOCKMODE lockmode);
+extern void add_ternary_reloption(bits32 kinds, const char *name,
+ const char *desc, int default_val, LOCKMODE lockmode);
extern void add_int_reloption(bits32 kinds, const char *name, const char *desc,
int default_val, int min_val, int max_val,
LOCKMODE lockmode);
@@ -214,6 +211,9 @@ extern void register_reloptions_validator(local_relopts *relopts,
extern void add_local_bool_reloption(local_relopts *relopts, const char *name,
const char *desc, bool default_val,
int offset);
+extern void add_local_ternary_reloption(local_relopts *relopts,
+ const char *name, const char *desc,
+ ternary default_val, int offset);
extern void add_local_int_reloption(local_relopts *relopts, const char *name,
const char *desc, int default_val,
int min_val, int max_val, int offset);
diff --git a/src/include/c.h b/src/include/c.h
index 39022f8a9d..9c59ccccf8 100644
--- a/src/include/c.h
+++ b/src/include/c.h
@@ -488,6 +488,22 @@ typedef void (*pg_funcptr_t) (void);
#include <stdbool.h>
+/*
+ * ternary
+ * Boolean value with an extrea "unset" option
+ *
+ * Ternary data type is used in relation options that can be "true", "false" or
+ * "unset". Since relation options are used deep inside the PostgreSQL code,
+ * this type is declared globally.
+*/
+
+typedef enum ternary
+{
+ TERNARY_FALSE = 0,
+ TERNARY_TRUE = 1,
+ TERNARY_UNSET = -1
+} ternary;
+
/* ----------------------------------------------------------------
* Section 3: standard system types
diff --git a/src/include/utils/rel.h b/src/include/utils/rel.h
index b552359915..7346d618f9 100644
--- a/src/include/utils/rel.h
+++ b/src/include/utils/rel.h
@@ -346,8 +346,7 @@ typedef struct StdRdOptions
bool user_catalog_table; /* use as an additional catalog relation */
int parallel_workers; /* max number of parallel workers */
StdRdOptIndexCleanup vacuum_index_cleanup; /* controls index vacuuming */
- bool vacuum_truncate; /* enables vacuum to truncate a relation */
- bool vacuum_truncate_set; /* whether vacuum_truncate is set */
+ ternary vacuum_truncate; /* enables vacuum to truncate a relation */
/*
* Fraction of pages in a relation that vacuum can eagerly scan and fail
--
2.39.2
--nextPart5129932.5fSG56mABF
Content-Disposition: attachment;
filename="v2-0003-Add-alias-to-be-used-as-unset-state.patch"
Content-Transfer-Encoding: 7Bit
Content-Type: text/x-patch; charset="unicode-2-0-utf-8";
name="v2-0003-Add-alias-to-be-used-as-unset-state.patch"
^ permalink raw reply [nested|flat] 868+ messages in thread
* [PATCH v2 2/4] Introduce ternary reloptions
@ 2025-09-04 14:41 Nikolay Shaplov <[email protected]>
0 siblings, 0 replies; 868+ messages in thread
From: Nikolay Shaplov @ 2025-09-04 14:41 UTC (permalink / raw)
Introduce ternary reloption as a replacement for current `vacuum_truncate`
implementation. Remove `vacuum_truncate_set` additional flag and using
`TERNARY_UNSET` value instead.
---
src/backend/access/common/reloptions.c | 129 ++++++++++++++++++++-----
src/backend/commands/vacuum.c | 4 +-
src/include/access/reloptions.h | 26 ++---
src/include/c.h | 16 +++
src/include/utils/rel.h | 3 +-
5 files changed, 135 insertions(+), 43 deletions(-)
diff --git a/src/backend/access/common/reloptions.c b/src/backend/access/common/reloptions.c
index 0af3fea68f..f543f11001 100644
--- a/src/backend/access/common/reloptions.c
+++ b/src/backend/access/common/reloptions.c
@@ -40,9 +40,9 @@
*
* To add an option:
*
- * (i) decide on a type (bool, integer, real, enum, string), name, default
- * value, upper and lower bounds (if applicable); for strings, consider a
- * validation routine.
+ * (i) decide on a type (bool, ternary, integer, real, enum, string), name,
+ * default value, upper and lower bounds (if applicable); for strings,
+ * consider a validation routine.
* (ii) add a record below (or use add_<type>_reloption).
* (iii) add it to the appropriate options struct (perhaps StdRdOptions)
* (iv) add it to the appropriate handling routine (perhaps
@@ -147,15 +147,6 @@ static relopt_bool boolRelOpts[] =
},
false
},
- {
- {
- "vacuum_truncate",
- "Enables vacuum to truncate empty pages at the end of this table",
- RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
- ShareUpdateExclusiveLock
- },
- true
- },
{
{
"deduplicate_items",
@@ -170,6 +161,21 @@ static relopt_bool boolRelOpts[] =
{{NULL}}
};
+static relopt_ternary ternaryRelOpts[] =
+{
+ {
+ {
+ "vacuum_truncate",
+ "Enables vacuum to truncate empty pages at the end of this table",
+ RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
+ ShareUpdateExclusiveLock
+ },
+ TERNARY_UNSET
+ },
+ /* list terminator */
+ {{NULL}}
+};
+
static relopt_int intRelOpts[] =
{
{
@@ -600,6 +606,13 @@ initialize_reloptions(void)
boolRelOpts[i].gen.lockmode));
j++;
}
+ for (i = 0; ternaryRelOpts[i].gen.name; i++)
+ {
+ Assert(DoLockModesConflict(ternaryRelOpts[i].gen.lockmode,
+ ternaryRelOpts[i].gen.lockmode));
+ j++;
+ }
+
for (i = 0; intRelOpts[i].gen.name; i++)
{
Assert(DoLockModesConflict(intRelOpts[i].gen.lockmode,
@@ -640,6 +653,14 @@ initialize_reloptions(void)
j++;
}
+ for (i = 0; ternaryRelOpts[i].gen.name; i++)
+ {
+ relOpts[j] = &ternaryRelOpts[i].gen;
+ relOpts[j]->type = RELOPT_TYPE_TERNARY;
+ relOpts[j]->namelen = strlen(relOpts[j]->name);
+ j++;
+ }
+
for (i = 0; intRelOpts[i].gen.name; i++)
{
relOpts[j] = &intRelOpts[i].gen;
@@ -800,6 +821,9 @@ allocate_reloption(bits32 kinds, int type, const char *name, const char *desc,
case RELOPT_TYPE_BOOL:
size = sizeof(relopt_bool);
break;
+ case RELOPT_TYPE_TERNARY:
+ size = sizeof(relopt_ternary);
+ break;
case RELOPT_TYPE_INT:
size = sizeof(relopt_int);
break;
@@ -883,6 +907,54 @@ add_local_bool_reloption(local_relopts *relopts, const char *name,
add_local_reloption(relopts, (relopt_gen *) newoption, offset);
}
+/*
+ * init_ternary_reloption
+ * Allocate and initialize a new ternary reloption
+ */
+static relopt_ternary *
+init_ternary_reloption(bits32 kinds, const char *name, const char *desc,
+ ternary default_val, LOCKMODE lockmode)
+{
+ relopt_ternary *newoption;
+
+ newoption = (relopt_ternary *) allocate_reloption(kinds,
+ RELOPT_TYPE_TERNARY, name, desc, lockmode);
+ newoption->default_val = default_val;
+
+ return newoption;
+}
+
+/*
+ * add_ternary_reloption
+ * Add a new ternary reloption
+ */
+void
+add_ternary_reloption(bits32 kinds, const char *name, const char *desc,
+ ternary default_val, LOCKMODE lockmode)
+{
+ relopt_ternary *newoption = init_ternary_reloption(kinds, name, desc,
+ default_val, lockmode);
+
+ add_reloption((relopt_gen *) newoption);
+}
+
+/*
+ * add_local_ternary_reloption
+ * Add a new ternary local reloption
+ *
+ * 'offset' is offset of ternary-typed field.
+ */
+void
+add_local_ternary_reloption(local_relopts *relopts, const char *name,
+ const char *desc, ternary default_val,
+ int offset)
+{
+ relopt_ternary *newoption = init_ternary_reloption(RELOPT_KIND_LOCAL,
+ name, desc,
+ default_val, 0);
+
+ add_local_reloption(relopts, (relopt_gen *) newoption, offset);
+}
/*
* init_real_reloption
@@ -1617,6 +1689,18 @@ parse_one_reloption(relopt_value *option, char *text_str, int text_len,
option->gen->name, value)));
}
break;
+ case RELOPT_TYPE_TERNARY:
+ {
+ bool b;
+ parsed = parse_bool(value, &b);
+ option->values.ternary_val = b ? TERNARY_TRUE : TERNARY_FALSE;
+ if (validate && !parsed)
+ ereport(ERROR,
+ (errcode(ERRCODE_INVALID_PARAMETER_VALUE),
+ errmsg("invalid value for ternary option \"%s\": %s",
+ option->gen->name, value)));
+ }
+ break;
case RELOPT_TYPE_INT:
{
relopt_int *optint = (relopt_int *) option->gen;
@@ -1780,17 +1864,6 @@ fillRelOptions(void *rdopts, Size basesize,
char *itempos = ((char *) rdopts) + elems[j].offset;
char *string_val;
- /*
- * If isset_offset is provided, store whether the reloption is
- * set there.
- */
- if (elems[j].isset_offset > 0)
- {
- char *setpos = ((char *) rdopts) + elems[j].isset_offset;
-
- *(bool *) setpos = options[i].isset;
- }
-
switch (options[i].gen->type)
{
case RELOPT_TYPE_BOOL:
@@ -1798,6 +1871,11 @@ fillRelOptions(void *rdopts, Size basesize,
options[i].values.bool_val :
((relopt_bool *) options[i].gen)->default_val;
break;
+ case RELOPT_TYPE_TERNARY:
+ *(ternary *) itempos = options[i].isset ?
+ options[i].values.ternary_val :
+ ((relopt_ternary *) options[i].gen)->default_val;
+ break;
case RELOPT_TYPE_INT:
*(int *) itempos = options[i].isset ?
options[i].values.int_val :
@@ -1912,8 +1990,8 @@ default_reloptions(Datum reloptions, bool validate, relopt_kind kind)
offsetof(StdRdOptions, parallel_workers)},
{"vacuum_index_cleanup", RELOPT_TYPE_ENUM,
offsetof(StdRdOptions, vacuum_index_cleanup)},
- {"vacuum_truncate", RELOPT_TYPE_BOOL,
- offsetof(StdRdOptions, vacuum_truncate), offsetof(StdRdOptions, vacuum_truncate_set)},
+ {"vacuum_truncate", RELOPT_TYPE_TERNARY,
+ offsetof(StdRdOptions, vacuum_truncate)},
{"vacuum_max_eager_freeze_failure_rate", RELOPT_TYPE_REAL,
offsetof(StdRdOptions, vacuum_max_eager_freeze_failure_rate)}
};
@@ -1993,7 +2071,6 @@ build_local_reloptions(local_relopts *relopts, Datum options, bool validate)
elems[i].optname = opt->option->name;
elems[i].opttype = opt->option->type;
elems[i].offset = opt->offset;
- elems[i].isset_offset = 0; /* not supported for local relopts yet */
i++;
}
diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c
index 733ef40ae7..977babff54 100644
--- a/src/backend/commands/vacuum.c
+++ b/src/backend/commands/vacuum.c
@@ -2221,9 +2221,9 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams params,
{
StdRdOptions *opts = (StdRdOptions *) rel->rd_options;
- if (opts && opts->vacuum_truncate_set)
+ if (opts && opts->vacuum_truncate != TERNARY_UNSET)
{
- if (opts->vacuum_truncate)
+ if (opts->vacuum_truncate == TERNARY_TRUE)
params.truncate = VACOPTVALUE_ENABLED;
else
params.truncate = VACOPTVALUE_DISABLED;
diff --git a/src/include/access/reloptions.h b/src/include/access/reloptions.h
index a604a4702c..a436697658 100644
--- a/src/include/access/reloptions.h
+++ b/src/include/access/reloptions.h
@@ -29,6 +29,7 @@
typedef enum relopt_type
{
RELOPT_TYPE_BOOL,
+ RELOPT_TYPE_TERNARY, /* on, off, unset */
RELOPT_TYPE_INT,
RELOPT_TYPE_REAL,
RELOPT_TYPE_ENUM,
@@ -80,6 +81,7 @@ typedef struct relopt_value
union
{
bool bool_val;
+ ternary ternary_val;
int int_val;
double real_val;
int enum_val;
@@ -94,6 +96,12 @@ typedef struct relopt_bool
bool default_val;
} relopt_bool;
+typedef struct relopt_rernary
+{
+ relopt_gen gen;
+ int default_val;
+} relopt_ternary;
+
typedef struct relopt_int
{
relopt_gen gen;
@@ -152,19 +160,6 @@ typedef struct
const char *optname; /* option's name */
relopt_type opttype; /* option's datatype */
int offset; /* offset of field in result struct */
-
- /*
- * isset_offset is an optional offset of a field in the result struct that
- * stores whether the option is explicitly set for the relation or if it
- * just picked up the default value. In most cases, this can be
- * accomplished by giving the reloption a special out-of-range default
- * value (e.g., some integer reloptions use -2), but this isn't always
- * possible. For example, a Boolean reloption cannot be given an
- * out-of-range default, so we need another way to discover the source of
- * its value. This offset is only used if given a value greater than
- * zero.
- */
- int isset_offset;
} relopt_parse_elt;
/* Local reloption definition */
@@ -195,6 +190,8 @@ typedef struct local_relopts
extern relopt_kind add_reloption_kind(void);
extern void add_bool_reloption(bits32 kinds, const char *name, const char *desc,
bool default_val, LOCKMODE lockmode);
+extern void add_ternary_reloption(bits32 kinds, const char *name,
+ const char *desc, int default_val, LOCKMODE lockmode);
extern void add_int_reloption(bits32 kinds, const char *name, const char *desc,
int default_val, int min_val, int max_val,
LOCKMODE lockmode);
@@ -214,6 +211,9 @@ extern void register_reloptions_validator(local_relopts *relopts,
extern void add_local_bool_reloption(local_relopts *relopts, const char *name,
const char *desc, bool default_val,
int offset);
+extern void add_local_ternary_reloption(local_relopts *relopts,
+ const char *name, const char *desc,
+ ternary default_val, int offset);
extern void add_local_int_reloption(local_relopts *relopts, const char *name,
const char *desc, int default_val,
int min_val, int max_val, int offset);
diff --git a/src/include/c.h b/src/include/c.h
index 39022f8a9d..9c59ccccf8 100644
--- a/src/include/c.h
+++ b/src/include/c.h
@@ -488,6 +488,22 @@ typedef void (*pg_funcptr_t) (void);
#include <stdbool.h>
+/*
+ * ternary
+ * Boolean value with an extrea "unset" option
+ *
+ * Ternary data type is used in relation options that can be "true", "false" or
+ * "unset". Since relation options are used deep inside the PostgreSQL code,
+ * this type is declared globally.
+*/
+
+typedef enum ternary
+{
+ TERNARY_FALSE = 0,
+ TERNARY_TRUE = 1,
+ TERNARY_UNSET = -1
+} ternary;
+
/* ----------------------------------------------------------------
* Section 3: standard system types
diff --git a/src/include/utils/rel.h b/src/include/utils/rel.h
index b552359915..7346d618f9 100644
--- a/src/include/utils/rel.h
+++ b/src/include/utils/rel.h
@@ -346,8 +346,7 @@ typedef struct StdRdOptions
bool user_catalog_table; /* use as an additional catalog relation */
int parallel_workers; /* max number of parallel workers */
StdRdOptIndexCleanup vacuum_index_cleanup; /* controls index vacuuming */
- bool vacuum_truncate; /* enables vacuum to truncate a relation */
- bool vacuum_truncate_set; /* whether vacuum_truncate is set */
+ ternary vacuum_truncate; /* enables vacuum to truncate a relation */
/*
* Fraction of pages in a relation that vacuum can eagerly scan and fail
--
2.39.2
--nextPart5129932.5fSG56mABF
Content-Disposition: attachment;
filename="v2-0003-Add-alias-to-be-used-as-unset-state.patch"
Content-Transfer-Encoding: 7Bit
Content-Type: text/x-patch; charset="unicode-2-0-utf-8";
name="v2-0003-Add-alias-to-be-used-as-unset-state.patch"
^ permalink raw reply [nested|flat] 868+ messages in thread
* [PATCH v2 2/4] Introduce ternary reloptions
@ 2025-09-04 14:41 Nikolay Shaplov <[email protected]>
0 siblings, 0 replies; 868+ messages in thread
From: Nikolay Shaplov @ 2025-09-04 14:41 UTC (permalink / raw)
Introduce ternary reloption as a replacement for current `vacuum_truncate`
implementation. Remove `vacuum_truncate_set` additional flag and using
`TERNARY_UNSET` value instead.
---
src/backend/access/common/reloptions.c | 129 ++++++++++++++++++++-----
src/backend/commands/vacuum.c | 4 +-
src/include/access/reloptions.h | 26 ++---
src/include/c.h | 16 +++
src/include/utils/rel.h | 3 +-
5 files changed, 135 insertions(+), 43 deletions(-)
diff --git a/src/backend/access/common/reloptions.c b/src/backend/access/common/reloptions.c
index 0af3fea68f..f543f11001 100644
--- a/src/backend/access/common/reloptions.c
+++ b/src/backend/access/common/reloptions.c
@@ -40,9 +40,9 @@
*
* To add an option:
*
- * (i) decide on a type (bool, integer, real, enum, string), name, default
- * value, upper and lower bounds (if applicable); for strings, consider a
- * validation routine.
+ * (i) decide on a type (bool, ternary, integer, real, enum, string), name,
+ * default value, upper and lower bounds (if applicable); for strings,
+ * consider a validation routine.
* (ii) add a record below (or use add_<type>_reloption).
* (iii) add it to the appropriate options struct (perhaps StdRdOptions)
* (iv) add it to the appropriate handling routine (perhaps
@@ -147,15 +147,6 @@ static relopt_bool boolRelOpts[] =
},
false
},
- {
- {
- "vacuum_truncate",
- "Enables vacuum to truncate empty pages at the end of this table",
- RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
- ShareUpdateExclusiveLock
- },
- true
- },
{
{
"deduplicate_items",
@@ -170,6 +161,21 @@ static relopt_bool boolRelOpts[] =
{{NULL}}
};
+static relopt_ternary ternaryRelOpts[] =
+{
+ {
+ {
+ "vacuum_truncate",
+ "Enables vacuum to truncate empty pages at the end of this table",
+ RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
+ ShareUpdateExclusiveLock
+ },
+ TERNARY_UNSET
+ },
+ /* list terminator */
+ {{NULL}}
+};
+
static relopt_int intRelOpts[] =
{
{
@@ -600,6 +606,13 @@ initialize_reloptions(void)
boolRelOpts[i].gen.lockmode));
j++;
}
+ for (i = 0; ternaryRelOpts[i].gen.name; i++)
+ {
+ Assert(DoLockModesConflict(ternaryRelOpts[i].gen.lockmode,
+ ternaryRelOpts[i].gen.lockmode));
+ j++;
+ }
+
for (i = 0; intRelOpts[i].gen.name; i++)
{
Assert(DoLockModesConflict(intRelOpts[i].gen.lockmode,
@@ -640,6 +653,14 @@ initialize_reloptions(void)
j++;
}
+ for (i = 0; ternaryRelOpts[i].gen.name; i++)
+ {
+ relOpts[j] = &ternaryRelOpts[i].gen;
+ relOpts[j]->type = RELOPT_TYPE_TERNARY;
+ relOpts[j]->namelen = strlen(relOpts[j]->name);
+ j++;
+ }
+
for (i = 0; intRelOpts[i].gen.name; i++)
{
relOpts[j] = &intRelOpts[i].gen;
@@ -800,6 +821,9 @@ allocate_reloption(bits32 kinds, int type, const char *name, const char *desc,
case RELOPT_TYPE_BOOL:
size = sizeof(relopt_bool);
break;
+ case RELOPT_TYPE_TERNARY:
+ size = sizeof(relopt_ternary);
+ break;
case RELOPT_TYPE_INT:
size = sizeof(relopt_int);
break;
@@ -883,6 +907,54 @@ add_local_bool_reloption(local_relopts *relopts, const char *name,
add_local_reloption(relopts, (relopt_gen *) newoption, offset);
}
+/*
+ * init_ternary_reloption
+ * Allocate and initialize a new ternary reloption
+ */
+static relopt_ternary *
+init_ternary_reloption(bits32 kinds, const char *name, const char *desc,
+ ternary default_val, LOCKMODE lockmode)
+{
+ relopt_ternary *newoption;
+
+ newoption = (relopt_ternary *) allocate_reloption(kinds,
+ RELOPT_TYPE_TERNARY, name, desc, lockmode);
+ newoption->default_val = default_val;
+
+ return newoption;
+}
+
+/*
+ * add_ternary_reloption
+ * Add a new ternary reloption
+ */
+void
+add_ternary_reloption(bits32 kinds, const char *name, const char *desc,
+ ternary default_val, LOCKMODE lockmode)
+{
+ relopt_ternary *newoption = init_ternary_reloption(kinds, name, desc,
+ default_val, lockmode);
+
+ add_reloption((relopt_gen *) newoption);
+}
+
+/*
+ * add_local_ternary_reloption
+ * Add a new ternary local reloption
+ *
+ * 'offset' is offset of ternary-typed field.
+ */
+void
+add_local_ternary_reloption(local_relopts *relopts, const char *name,
+ const char *desc, ternary default_val,
+ int offset)
+{
+ relopt_ternary *newoption = init_ternary_reloption(RELOPT_KIND_LOCAL,
+ name, desc,
+ default_val, 0);
+
+ add_local_reloption(relopts, (relopt_gen *) newoption, offset);
+}
/*
* init_real_reloption
@@ -1617,6 +1689,18 @@ parse_one_reloption(relopt_value *option, char *text_str, int text_len,
option->gen->name, value)));
}
break;
+ case RELOPT_TYPE_TERNARY:
+ {
+ bool b;
+ parsed = parse_bool(value, &b);
+ option->values.ternary_val = b ? TERNARY_TRUE : TERNARY_FALSE;
+ if (validate && !parsed)
+ ereport(ERROR,
+ (errcode(ERRCODE_INVALID_PARAMETER_VALUE),
+ errmsg("invalid value for ternary option \"%s\": %s",
+ option->gen->name, value)));
+ }
+ break;
case RELOPT_TYPE_INT:
{
relopt_int *optint = (relopt_int *) option->gen;
@@ -1780,17 +1864,6 @@ fillRelOptions(void *rdopts, Size basesize,
char *itempos = ((char *) rdopts) + elems[j].offset;
char *string_val;
- /*
- * If isset_offset is provided, store whether the reloption is
- * set there.
- */
- if (elems[j].isset_offset > 0)
- {
- char *setpos = ((char *) rdopts) + elems[j].isset_offset;
-
- *(bool *) setpos = options[i].isset;
- }
-
switch (options[i].gen->type)
{
case RELOPT_TYPE_BOOL:
@@ -1798,6 +1871,11 @@ fillRelOptions(void *rdopts, Size basesize,
options[i].values.bool_val :
((relopt_bool *) options[i].gen)->default_val;
break;
+ case RELOPT_TYPE_TERNARY:
+ *(ternary *) itempos = options[i].isset ?
+ options[i].values.ternary_val :
+ ((relopt_ternary *) options[i].gen)->default_val;
+ break;
case RELOPT_TYPE_INT:
*(int *) itempos = options[i].isset ?
options[i].values.int_val :
@@ -1912,8 +1990,8 @@ default_reloptions(Datum reloptions, bool validate, relopt_kind kind)
offsetof(StdRdOptions, parallel_workers)},
{"vacuum_index_cleanup", RELOPT_TYPE_ENUM,
offsetof(StdRdOptions, vacuum_index_cleanup)},
- {"vacuum_truncate", RELOPT_TYPE_BOOL,
- offsetof(StdRdOptions, vacuum_truncate), offsetof(StdRdOptions, vacuum_truncate_set)},
+ {"vacuum_truncate", RELOPT_TYPE_TERNARY,
+ offsetof(StdRdOptions, vacuum_truncate)},
{"vacuum_max_eager_freeze_failure_rate", RELOPT_TYPE_REAL,
offsetof(StdRdOptions, vacuum_max_eager_freeze_failure_rate)}
};
@@ -1993,7 +2071,6 @@ build_local_reloptions(local_relopts *relopts, Datum options, bool validate)
elems[i].optname = opt->option->name;
elems[i].opttype = opt->option->type;
elems[i].offset = opt->offset;
- elems[i].isset_offset = 0; /* not supported for local relopts yet */
i++;
}
diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c
index 733ef40ae7..977babff54 100644
--- a/src/backend/commands/vacuum.c
+++ b/src/backend/commands/vacuum.c
@@ -2221,9 +2221,9 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams params,
{
StdRdOptions *opts = (StdRdOptions *) rel->rd_options;
- if (opts && opts->vacuum_truncate_set)
+ if (opts && opts->vacuum_truncate != TERNARY_UNSET)
{
- if (opts->vacuum_truncate)
+ if (opts->vacuum_truncate == TERNARY_TRUE)
params.truncate = VACOPTVALUE_ENABLED;
else
params.truncate = VACOPTVALUE_DISABLED;
diff --git a/src/include/access/reloptions.h b/src/include/access/reloptions.h
index a604a4702c..a436697658 100644
--- a/src/include/access/reloptions.h
+++ b/src/include/access/reloptions.h
@@ -29,6 +29,7 @@
typedef enum relopt_type
{
RELOPT_TYPE_BOOL,
+ RELOPT_TYPE_TERNARY, /* on, off, unset */
RELOPT_TYPE_INT,
RELOPT_TYPE_REAL,
RELOPT_TYPE_ENUM,
@@ -80,6 +81,7 @@ typedef struct relopt_value
union
{
bool bool_val;
+ ternary ternary_val;
int int_val;
double real_val;
int enum_val;
@@ -94,6 +96,12 @@ typedef struct relopt_bool
bool default_val;
} relopt_bool;
+typedef struct relopt_rernary
+{
+ relopt_gen gen;
+ int default_val;
+} relopt_ternary;
+
typedef struct relopt_int
{
relopt_gen gen;
@@ -152,19 +160,6 @@ typedef struct
const char *optname; /* option's name */
relopt_type opttype; /* option's datatype */
int offset; /* offset of field in result struct */
-
- /*
- * isset_offset is an optional offset of a field in the result struct that
- * stores whether the option is explicitly set for the relation or if it
- * just picked up the default value. In most cases, this can be
- * accomplished by giving the reloption a special out-of-range default
- * value (e.g., some integer reloptions use -2), but this isn't always
- * possible. For example, a Boolean reloption cannot be given an
- * out-of-range default, so we need another way to discover the source of
- * its value. This offset is only used if given a value greater than
- * zero.
- */
- int isset_offset;
} relopt_parse_elt;
/* Local reloption definition */
@@ -195,6 +190,8 @@ typedef struct local_relopts
extern relopt_kind add_reloption_kind(void);
extern void add_bool_reloption(bits32 kinds, const char *name, const char *desc,
bool default_val, LOCKMODE lockmode);
+extern void add_ternary_reloption(bits32 kinds, const char *name,
+ const char *desc, int default_val, LOCKMODE lockmode);
extern void add_int_reloption(bits32 kinds, const char *name, const char *desc,
int default_val, int min_val, int max_val,
LOCKMODE lockmode);
@@ -214,6 +211,9 @@ extern void register_reloptions_validator(local_relopts *relopts,
extern void add_local_bool_reloption(local_relopts *relopts, const char *name,
const char *desc, bool default_val,
int offset);
+extern void add_local_ternary_reloption(local_relopts *relopts,
+ const char *name, const char *desc,
+ ternary default_val, int offset);
extern void add_local_int_reloption(local_relopts *relopts, const char *name,
const char *desc, int default_val,
int min_val, int max_val, int offset);
diff --git a/src/include/c.h b/src/include/c.h
index 39022f8a9d..9c59ccccf8 100644
--- a/src/include/c.h
+++ b/src/include/c.h
@@ -488,6 +488,22 @@ typedef void (*pg_funcptr_t) (void);
#include <stdbool.h>
+/*
+ * ternary
+ * Boolean value with an extrea "unset" option
+ *
+ * Ternary data type is used in relation options that can be "true", "false" or
+ * "unset". Since relation options are used deep inside the PostgreSQL code,
+ * this type is declared globally.
+*/
+
+typedef enum ternary
+{
+ TERNARY_FALSE = 0,
+ TERNARY_TRUE = 1,
+ TERNARY_UNSET = -1
+} ternary;
+
/* ----------------------------------------------------------------
* Section 3: standard system types
diff --git a/src/include/utils/rel.h b/src/include/utils/rel.h
index b552359915..7346d618f9 100644
--- a/src/include/utils/rel.h
+++ b/src/include/utils/rel.h
@@ -346,8 +346,7 @@ typedef struct StdRdOptions
bool user_catalog_table; /* use as an additional catalog relation */
int parallel_workers; /* max number of parallel workers */
StdRdOptIndexCleanup vacuum_index_cleanup; /* controls index vacuuming */
- bool vacuum_truncate; /* enables vacuum to truncate a relation */
- bool vacuum_truncate_set; /* whether vacuum_truncate is set */
+ ternary vacuum_truncate; /* enables vacuum to truncate a relation */
/*
* Fraction of pages in a relation that vacuum can eagerly scan and fail
--
2.39.2
--nextPart5129932.5fSG56mABF
Content-Disposition: attachment;
filename="v2-0003-Add-alias-to-be-used-as-unset-state.patch"
Content-Transfer-Encoding: 7Bit
Content-Type: text/x-patch; charset="unicode-2-0-utf-8";
name="v2-0003-Add-alias-to-be-used-as-unset-state.patch"
^ permalink raw reply [nested|flat] 868+ messages in thread
* [PATCH v2a 2/4] Introduce ternary reloptions
@ 2025-09-04 14:41 Nikolay Shaplov <[email protected]>
0 siblings, 0 replies; 868+ messages in thread
From: Nikolay Shaplov @ 2025-09-04 14:41 UTC (permalink / raw)
Introduce ternary reloption as a replacement for current `vacuum_truncate`
implementation. Remove `vacuum_truncate_set` additional flag and using
`TERNARY_UNSET` value instead.
---
src/backend/access/common/reloptions.c | 129 ++++++++++++++++++++-----
src/backend/commands/vacuum.c | 4 +-
src/include/access/reloptions.h | 26 ++---
src/include/c.h | 16 +++
src/include/utils/rel.h | 3 +-
5 files changed, 135 insertions(+), 43 deletions(-)
diff --git a/src/backend/access/common/reloptions.c b/src/backend/access/common/reloptions.c
index 0af3fea68f..f543f11001 100644
--- a/src/backend/access/common/reloptions.c
+++ b/src/backend/access/common/reloptions.c
@@ -40,9 +40,9 @@
*
* To add an option:
*
- * (i) decide on a type (bool, integer, real, enum, string), name, default
- * value, upper and lower bounds (if applicable); for strings, consider a
- * validation routine.
+ * (i) decide on a type (bool, ternary, integer, real, enum, string), name,
+ * default value, upper and lower bounds (if applicable); for strings,
+ * consider a validation routine.
* (ii) add a record below (or use add_<type>_reloption).
* (iii) add it to the appropriate options struct (perhaps StdRdOptions)
* (iv) add it to the appropriate handling routine (perhaps
@@ -147,15 +147,6 @@ static relopt_bool boolRelOpts[] =
},
false
},
- {
- {
- "vacuum_truncate",
- "Enables vacuum to truncate empty pages at the end of this table",
- RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
- ShareUpdateExclusiveLock
- },
- true
- },
{
{
"deduplicate_items",
@@ -170,6 +161,21 @@ static relopt_bool boolRelOpts[] =
{{NULL}}
};
+static relopt_ternary ternaryRelOpts[] =
+{
+ {
+ {
+ "vacuum_truncate",
+ "Enables vacuum to truncate empty pages at the end of this table",
+ RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
+ ShareUpdateExclusiveLock
+ },
+ TERNARY_UNSET
+ },
+ /* list terminator */
+ {{NULL}}
+};
+
static relopt_int intRelOpts[] =
{
{
@@ -600,6 +606,13 @@ initialize_reloptions(void)
boolRelOpts[i].gen.lockmode));
j++;
}
+ for (i = 0; ternaryRelOpts[i].gen.name; i++)
+ {
+ Assert(DoLockModesConflict(ternaryRelOpts[i].gen.lockmode,
+ ternaryRelOpts[i].gen.lockmode));
+ j++;
+ }
+
for (i = 0; intRelOpts[i].gen.name; i++)
{
Assert(DoLockModesConflict(intRelOpts[i].gen.lockmode,
@@ -640,6 +653,14 @@ initialize_reloptions(void)
j++;
}
+ for (i = 0; ternaryRelOpts[i].gen.name; i++)
+ {
+ relOpts[j] = &ternaryRelOpts[i].gen;
+ relOpts[j]->type = RELOPT_TYPE_TERNARY;
+ relOpts[j]->namelen = strlen(relOpts[j]->name);
+ j++;
+ }
+
for (i = 0; intRelOpts[i].gen.name; i++)
{
relOpts[j] = &intRelOpts[i].gen;
@@ -800,6 +821,9 @@ allocate_reloption(bits32 kinds, int type, const char *name, const char *desc,
case RELOPT_TYPE_BOOL:
size = sizeof(relopt_bool);
break;
+ case RELOPT_TYPE_TERNARY:
+ size = sizeof(relopt_ternary);
+ break;
case RELOPT_TYPE_INT:
size = sizeof(relopt_int);
break;
@@ -883,6 +907,54 @@ add_local_bool_reloption(local_relopts *relopts, const char *name,
add_local_reloption(relopts, (relopt_gen *) newoption, offset);
}
+/*
+ * init_ternary_reloption
+ * Allocate and initialize a new ternary reloption
+ */
+static relopt_ternary *
+init_ternary_reloption(bits32 kinds, const char *name, const char *desc,
+ ternary default_val, LOCKMODE lockmode)
+{
+ relopt_ternary *newoption;
+
+ newoption = (relopt_ternary *) allocate_reloption(kinds,
+ RELOPT_TYPE_TERNARY, name, desc, lockmode);
+ newoption->default_val = default_val;
+
+ return newoption;
+}
+
+/*
+ * add_ternary_reloption
+ * Add a new ternary reloption
+ */
+void
+add_ternary_reloption(bits32 kinds, const char *name, const char *desc,
+ ternary default_val, LOCKMODE lockmode)
+{
+ relopt_ternary *newoption = init_ternary_reloption(kinds, name, desc,
+ default_val, lockmode);
+
+ add_reloption((relopt_gen *) newoption);
+}
+
+/*
+ * add_local_ternary_reloption
+ * Add a new ternary local reloption
+ *
+ * 'offset' is offset of ternary-typed field.
+ */
+void
+add_local_ternary_reloption(local_relopts *relopts, const char *name,
+ const char *desc, ternary default_val,
+ int offset)
+{
+ relopt_ternary *newoption = init_ternary_reloption(RELOPT_KIND_LOCAL,
+ name, desc,
+ default_val, 0);
+
+ add_local_reloption(relopts, (relopt_gen *) newoption, offset);
+}
/*
* init_real_reloption
@@ -1617,6 +1689,18 @@ parse_one_reloption(relopt_value *option, char *text_str, int text_len,
option->gen->name, value)));
}
break;
+ case RELOPT_TYPE_TERNARY:
+ {
+ bool b;
+ parsed = parse_bool(value, &b);
+ option->values.ternary_val = b ? TERNARY_TRUE : TERNARY_FALSE;
+ if (validate && !parsed)
+ ereport(ERROR,
+ (errcode(ERRCODE_INVALID_PARAMETER_VALUE),
+ errmsg("invalid value for ternary option \"%s\": %s",
+ option->gen->name, value)));
+ }
+ break;
case RELOPT_TYPE_INT:
{
relopt_int *optint = (relopt_int *) option->gen;
@@ -1780,17 +1864,6 @@ fillRelOptions(void *rdopts, Size basesize,
char *itempos = ((char *) rdopts) + elems[j].offset;
char *string_val;
- /*
- * If isset_offset is provided, store whether the reloption is
- * set there.
- */
- if (elems[j].isset_offset > 0)
- {
- char *setpos = ((char *) rdopts) + elems[j].isset_offset;
-
- *(bool *) setpos = options[i].isset;
- }
-
switch (options[i].gen->type)
{
case RELOPT_TYPE_BOOL:
@@ -1798,6 +1871,11 @@ fillRelOptions(void *rdopts, Size basesize,
options[i].values.bool_val :
((relopt_bool *) options[i].gen)->default_val;
break;
+ case RELOPT_TYPE_TERNARY:
+ *(ternary *) itempos = options[i].isset ?
+ options[i].values.ternary_val :
+ ((relopt_ternary *) options[i].gen)->default_val;
+ break;
case RELOPT_TYPE_INT:
*(int *) itempos = options[i].isset ?
options[i].values.int_val :
@@ -1912,8 +1990,8 @@ default_reloptions(Datum reloptions, bool validate, relopt_kind kind)
offsetof(StdRdOptions, parallel_workers)},
{"vacuum_index_cleanup", RELOPT_TYPE_ENUM,
offsetof(StdRdOptions, vacuum_index_cleanup)},
- {"vacuum_truncate", RELOPT_TYPE_BOOL,
- offsetof(StdRdOptions, vacuum_truncate), offsetof(StdRdOptions, vacuum_truncate_set)},
+ {"vacuum_truncate", RELOPT_TYPE_TERNARY,
+ offsetof(StdRdOptions, vacuum_truncate)},
{"vacuum_max_eager_freeze_failure_rate", RELOPT_TYPE_REAL,
offsetof(StdRdOptions, vacuum_max_eager_freeze_failure_rate)}
};
@@ -1993,7 +2071,6 @@ build_local_reloptions(local_relopts *relopts, Datum options, bool validate)
elems[i].optname = opt->option->name;
elems[i].opttype = opt->option->type;
elems[i].offset = opt->offset;
- elems[i].isset_offset = 0; /* not supported for local relopts yet */
i++;
}
diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c
index 733ef40ae7..977babff54 100644
--- a/src/backend/commands/vacuum.c
+++ b/src/backend/commands/vacuum.c
@@ -2221,9 +2221,9 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams params,
{
StdRdOptions *opts = (StdRdOptions *) rel->rd_options;
- if (opts && opts->vacuum_truncate_set)
+ if (opts && opts->vacuum_truncate != TERNARY_UNSET)
{
- if (opts->vacuum_truncate)
+ if (opts->vacuum_truncate == TERNARY_TRUE)
params.truncate = VACOPTVALUE_ENABLED;
else
params.truncate = VACOPTVALUE_DISABLED;
diff --git a/src/include/access/reloptions.h b/src/include/access/reloptions.h
index a604a4702c..a436697658 100644
--- a/src/include/access/reloptions.h
+++ b/src/include/access/reloptions.h
@@ -29,6 +29,7 @@
typedef enum relopt_type
{
RELOPT_TYPE_BOOL,
+ RELOPT_TYPE_TERNARY, /* on, off, unset */
RELOPT_TYPE_INT,
RELOPT_TYPE_REAL,
RELOPT_TYPE_ENUM,
@@ -80,6 +81,7 @@ typedef struct relopt_value
union
{
bool bool_val;
+ ternary ternary_val;
int int_val;
double real_val;
int enum_val;
@@ -94,6 +96,12 @@ typedef struct relopt_bool
bool default_val;
} relopt_bool;
+typedef struct relopt_rernary
+{
+ relopt_gen gen;
+ int default_val;
+} relopt_ternary;
+
typedef struct relopt_int
{
relopt_gen gen;
@@ -152,19 +160,6 @@ typedef struct
const char *optname; /* option's name */
relopt_type opttype; /* option's datatype */
int offset; /* offset of field in result struct */
-
- /*
- * isset_offset is an optional offset of a field in the result struct that
- * stores whether the option is explicitly set for the relation or if it
- * just picked up the default value. In most cases, this can be
- * accomplished by giving the reloption a special out-of-range default
- * value (e.g., some integer reloptions use -2), but this isn't always
- * possible. For example, a Boolean reloption cannot be given an
- * out-of-range default, so we need another way to discover the source of
- * its value. This offset is only used if given a value greater than
- * zero.
- */
- int isset_offset;
} relopt_parse_elt;
/* Local reloption definition */
@@ -195,6 +190,8 @@ typedef struct local_relopts
extern relopt_kind add_reloption_kind(void);
extern void add_bool_reloption(bits32 kinds, const char *name, const char *desc,
bool default_val, LOCKMODE lockmode);
+extern void add_ternary_reloption(bits32 kinds, const char *name,
+ const char *desc, int default_val, LOCKMODE lockmode);
extern void add_int_reloption(bits32 kinds, const char *name, const char *desc,
int default_val, int min_val, int max_val,
LOCKMODE lockmode);
@@ -214,6 +211,9 @@ extern void register_reloptions_validator(local_relopts *relopts,
extern void add_local_bool_reloption(local_relopts *relopts, const char *name,
const char *desc, bool default_val,
int offset);
+extern void add_local_ternary_reloption(local_relopts *relopts,
+ const char *name, const char *desc,
+ ternary default_val, int offset);
extern void add_local_int_reloption(local_relopts *relopts, const char *name,
const char *desc, int default_val,
int min_val, int max_val, int offset);
diff --git a/src/include/c.h b/src/include/c.h
index 39022f8a9d..9c59ccccf8 100644
--- a/src/include/c.h
+++ b/src/include/c.h
@@ -488,6 +488,22 @@ typedef void (*pg_funcptr_t) (void);
#include <stdbool.h>
+/*
+ * ternary
+ * Boolean value with an extrea "unset" option
+ *
+ * Ternary data type is used in relation options that can be "true", "false" or
+ * "unset". Since relation options are used deep inside the PostgreSQL code,
+ * this type is declared globally.
+*/
+
+typedef enum ternary
+{
+ TERNARY_FALSE = 0,
+ TERNARY_TRUE = 1,
+ TERNARY_UNSET = -1
+} ternary;
+
/* ----------------------------------------------------------------
* Section 3: standard system types
diff --git a/src/include/utils/rel.h b/src/include/utils/rel.h
index b552359915..7346d618f9 100644
--- a/src/include/utils/rel.h
+++ b/src/include/utils/rel.h
@@ -346,8 +346,7 @@ typedef struct StdRdOptions
bool user_catalog_table; /* use as an additional catalog relation */
int parallel_workers; /* max number of parallel workers */
StdRdOptIndexCleanup vacuum_index_cleanup; /* controls index vacuuming */
- bool vacuum_truncate; /* enables vacuum to truncate a relation */
- bool vacuum_truncate_set; /* whether vacuum_truncate is set */
+ ternary vacuum_truncate; /* enables vacuum to truncate a relation */
/*
* Fraction of pages in a relation that vacuum can eagerly scan and fail
--
2.39.2
--nextPart4155600.3ZeAukHxDK
Content-Disposition: attachment;
filename="v2a-0003-Add-alias-to-be-used-as-unset-state.patch"
Content-Transfer-Encoding: 7Bit
Content-Type: text/x-patch; charset="unicode-2-0-utf-8";
name="v2a-0003-Add-alias-to-be-used-as-unset-state.patch"
^ permalink raw reply [nested|flat] 868+ messages in thread
* [PATCH v2 2/4] Introduce ternary reloptions
@ 2025-09-04 14:41 Nikolay Shaplov <[email protected]>
0 siblings, 0 replies; 868+ messages in thread
From: Nikolay Shaplov @ 2025-09-04 14:41 UTC (permalink / raw)
Introduce ternary reloption as a replacement for current `vacuum_truncate`
implementation. Remove `vacuum_truncate_set` additional flag and using
`TERNARY_UNSET` value instead.
---
src/backend/access/common/reloptions.c | 129 ++++++++++++++++++++-----
src/backend/commands/vacuum.c | 4 +-
src/include/access/reloptions.h | 26 ++---
src/include/c.h | 16 +++
src/include/utils/rel.h | 3 +-
5 files changed, 135 insertions(+), 43 deletions(-)
diff --git a/src/backend/access/common/reloptions.c b/src/backend/access/common/reloptions.c
index 0af3fea68f..f543f11001 100644
--- a/src/backend/access/common/reloptions.c
+++ b/src/backend/access/common/reloptions.c
@@ -40,9 +40,9 @@
*
* To add an option:
*
- * (i) decide on a type (bool, integer, real, enum, string), name, default
- * value, upper and lower bounds (if applicable); for strings, consider a
- * validation routine.
+ * (i) decide on a type (bool, ternary, integer, real, enum, string), name,
+ * default value, upper and lower bounds (if applicable); for strings,
+ * consider a validation routine.
* (ii) add a record below (or use add_<type>_reloption).
* (iii) add it to the appropriate options struct (perhaps StdRdOptions)
* (iv) add it to the appropriate handling routine (perhaps
@@ -147,15 +147,6 @@ static relopt_bool boolRelOpts[] =
},
false
},
- {
- {
- "vacuum_truncate",
- "Enables vacuum to truncate empty pages at the end of this table",
- RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
- ShareUpdateExclusiveLock
- },
- true
- },
{
{
"deduplicate_items",
@@ -170,6 +161,21 @@ static relopt_bool boolRelOpts[] =
{{NULL}}
};
+static relopt_ternary ternaryRelOpts[] =
+{
+ {
+ {
+ "vacuum_truncate",
+ "Enables vacuum to truncate empty pages at the end of this table",
+ RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
+ ShareUpdateExclusiveLock
+ },
+ TERNARY_UNSET
+ },
+ /* list terminator */
+ {{NULL}}
+};
+
static relopt_int intRelOpts[] =
{
{
@@ -600,6 +606,13 @@ initialize_reloptions(void)
boolRelOpts[i].gen.lockmode));
j++;
}
+ for (i = 0; ternaryRelOpts[i].gen.name; i++)
+ {
+ Assert(DoLockModesConflict(ternaryRelOpts[i].gen.lockmode,
+ ternaryRelOpts[i].gen.lockmode));
+ j++;
+ }
+
for (i = 0; intRelOpts[i].gen.name; i++)
{
Assert(DoLockModesConflict(intRelOpts[i].gen.lockmode,
@@ -640,6 +653,14 @@ initialize_reloptions(void)
j++;
}
+ for (i = 0; ternaryRelOpts[i].gen.name; i++)
+ {
+ relOpts[j] = &ternaryRelOpts[i].gen;
+ relOpts[j]->type = RELOPT_TYPE_TERNARY;
+ relOpts[j]->namelen = strlen(relOpts[j]->name);
+ j++;
+ }
+
for (i = 0; intRelOpts[i].gen.name; i++)
{
relOpts[j] = &intRelOpts[i].gen;
@@ -800,6 +821,9 @@ allocate_reloption(bits32 kinds, int type, const char *name, const char *desc,
case RELOPT_TYPE_BOOL:
size = sizeof(relopt_bool);
break;
+ case RELOPT_TYPE_TERNARY:
+ size = sizeof(relopt_ternary);
+ break;
case RELOPT_TYPE_INT:
size = sizeof(relopt_int);
break;
@@ -883,6 +907,54 @@ add_local_bool_reloption(local_relopts *relopts, const char *name,
add_local_reloption(relopts, (relopt_gen *) newoption, offset);
}
+/*
+ * init_ternary_reloption
+ * Allocate and initialize a new ternary reloption
+ */
+static relopt_ternary *
+init_ternary_reloption(bits32 kinds, const char *name, const char *desc,
+ ternary default_val, LOCKMODE lockmode)
+{
+ relopt_ternary *newoption;
+
+ newoption = (relopt_ternary *) allocate_reloption(kinds,
+ RELOPT_TYPE_TERNARY, name, desc, lockmode);
+ newoption->default_val = default_val;
+
+ return newoption;
+}
+
+/*
+ * add_ternary_reloption
+ * Add a new ternary reloption
+ */
+void
+add_ternary_reloption(bits32 kinds, const char *name, const char *desc,
+ ternary default_val, LOCKMODE lockmode)
+{
+ relopt_ternary *newoption = init_ternary_reloption(kinds, name, desc,
+ default_val, lockmode);
+
+ add_reloption((relopt_gen *) newoption);
+}
+
+/*
+ * add_local_ternary_reloption
+ * Add a new ternary local reloption
+ *
+ * 'offset' is offset of ternary-typed field.
+ */
+void
+add_local_ternary_reloption(local_relopts *relopts, const char *name,
+ const char *desc, ternary default_val,
+ int offset)
+{
+ relopt_ternary *newoption = init_ternary_reloption(RELOPT_KIND_LOCAL,
+ name, desc,
+ default_val, 0);
+
+ add_local_reloption(relopts, (relopt_gen *) newoption, offset);
+}
/*
* init_real_reloption
@@ -1617,6 +1689,18 @@ parse_one_reloption(relopt_value *option, char *text_str, int text_len,
option->gen->name, value)));
}
break;
+ case RELOPT_TYPE_TERNARY:
+ {
+ bool b;
+ parsed = parse_bool(value, &b);
+ option->values.ternary_val = b ? TERNARY_TRUE : TERNARY_FALSE;
+ if (validate && !parsed)
+ ereport(ERROR,
+ (errcode(ERRCODE_INVALID_PARAMETER_VALUE),
+ errmsg("invalid value for ternary option \"%s\": %s",
+ option->gen->name, value)));
+ }
+ break;
case RELOPT_TYPE_INT:
{
relopt_int *optint = (relopt_int *) option->gen;
@@ -1780,17 +1864,6 @@ fillRelOptions(void *rdopts, Size basesize,
char *itempos = ((char *) rdopts) + elems[j].offset;
char *string_val;
- /*
- * If isset_offset is provided, store whether the reloption is
- * set there.
- */
- if (elems[j].isset_offset > 0)
- {
- char *setpos = ((char *) rdopts) + elems[j].isset_offset;
-
- *(bool *) setpos = options[i].isset;
- }
-
switch (options[i].gen->type)
{
case RELOPT_TYPE_BOOL:
@@ -1798,6 +1871,11 @@ fillRelOptions(void *rdopts, Size basesize,
options[i].values.bool_val :
((relopt_bool *) options[i].gen)->default_val;
break;
+ case RELOPT_TYPE_TERNARY:
+ *(ternary *) itempos = options[i].isset ?
+ options[i].values.ternary_val :
+ ((relopt_ternary *) options[i].gen)->default_val;
+ break;
case RELOPT_TYPE_INT:
*(int *) itempos = options[i].isset ?
options[i].values.int_val :
@@ -1912,8 +1990,8 @@ default_reloptions(Datum reloptions, bool validate, relopt_kind kind)
offsetof(StdRdOptions, parallel_workers)},
{"vacuum_index_cleanup", RELOPT_TYPE_ENUM,
offsetof(StdRdOptions, vacuum_index_cleanup)},
- {"vacuum_truncate", RELOPT_TYPE_BOOL,
- offsetof(StdRdOptions, vacuum_truncate), offsetof(StdRdOptions, vacuum_truncate_set)},
+ {"vacuum_truncate", RELOPT_TYPE_TERNARY,
+ offsetof(StdRdOptions, vacuum_truncate)},
{"vacuum_max_eager_freeze_failure_rate", RELOPT_TYPE_REAL,
offsetof(StdRdOptions, vacuum_max_eager_freeze_failure_rate)}
};
@@ -1993,7 +2071,6 @@ build_local_reloptions(local_relopts *relopts, Datum options, bool validate)
elems[i].optname = opt->option->name;
elems[i].opttype = opt->option->type;
elems[i].offset = opt->offset;
- elems[i].isset_offset = 0; /* not supported for local relopts yet */
i++;
}
diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c
index 733ef40ae7..977babff54 100644
--- a/src/backend/commands/vacuum.c
+++ b/src/backend/commands/vacuum.c
@@ -2221,9 +2221,9 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams params,
{
StdRdOptions *opts = (StdRdOptions *) rel->rd_options;
- if (opts && opts->vacuum_truncate_set)
+ if (opts && opts->vacuum_truncate != TERNARY_UNSET)
{
- if (opts->vacuum_truncate)
+ if (opts->vacuum_truncate == TERNARY_TRUE)
params.truncate = VACOPTVALUE_ENABLED;
else
params.truncate = VACOPTVALUE_DISABLED;
diff --git a/src/include/access/reloptions.h b/src/include/access/reloptions.h
index a604a4702c..a436697658 100644
--- a/src/include/access/reloptions.h
+++ b/src/include/access/reloptions.h
@@ -29,6 +29,7 @@
typedef enum relopt_type
{
RELOPT_TYPE_BOOL,
+ RELOPT_TYPE_TERNARY, /* on, off, unset */
RELOPT_TYPE_INT,
RELOPT_TYPE_REAL,
RELOPT_TYPE_ENUM,
@@ -80,6 +81,7 @@ typedef struct relopt_value
union
{
bool bool_val;
+ ternary ternary_val;
int int_val;
double real_val;
int enum_val;
@@ -94,6 +96,12 @@ typedef struct relopt_bool
bool default_val;
} relopt_bool;
+typedef struct relopt_rernary
+{
+ relopt_gen gen;
+ int default_val;
+} relopt_ternary;
+
typedef struct relopt_int
{
relopt_gen gen;
@@ -152,19 +160,6 @@ typedef struct
const char *optname; /* option's name */
relopt_type opttype; /* option's datatype */
int offset; /* offset of field in result struct */
-
- /*
- * isset_offset is an optional offset of a field in the result struct that
- * stores whether the option is explicitly set for the relation or if it
- * just picked up the default value. In most cases, this can be
- * accomplished by giving the reloption a special out-of-range default
- * value (e.g., some integer reloptions use -2), but this isn't always
- * possible. For example, a Boolean reloption cannot be given an
- * out-of-range default, so we need another way to discover the source of
- * its value. This offset is only used if given a value greater than
- * zero.
- */
- int isset_offset;
} relopt_parse_elt;
/* Local reloption definition */
@@ -195,6 +190,8 @@ typedef struct local_relopts
extern relopt_kind add_reloption_kind(void);
extern void add_bool_reloption(bits32 kinds, const char *name, const char *desc,
bool default_val, LOCKMODE lockmode);
+extern void add_ternary_reloption(bits32 kinds, const char *name,
+ const char *desc, int default_val, LOCKMODE lockmode);
extern void add_int_reloption(bits32 kinds, const char *name, const char *desc,
int default_val, int min_val, int max_val,
LOCKMODE lockmode);
@@ -214,6 +211,9 @@ extern void register_reloptions_validator(local_relopts *relopts,
extern void add_local_bool_reloption(local_relopts *relopts, const char *name,
const char *desc, bool default_val,
int offset);
+extern void add_local_ternary_reloption(local_relopts *relopts,
+ const char *name, const char *desc,
+ ternary default_val, int offset);
extern void add_local_int_reloption(local_relopts *relopts, const char *name,
const char *desc, int default_val,
int min_val, int max_val, int offset);
diff --git a/src/include/c.h b/src/include/c.h
index 39022f8a9d..9c59ccccf8 100644
--- a/src/include/c.h
+++ b/src/include/c.h
@@ -488,6 +488,22 @@ typedef void (*pg_funcptr_t) (void);
#include <stdbool.h>
+/*
+ * ternary
+ * Boolean value with an extrea "unset" option
+ *
+ * Ternary data type is used in relation options that can be "true", "false" or
+ * "unset". Since relation options are used deep inside the PostgreSQL code,
+ * this type is declared globally.
+*/
+
+typedef enum ternary
+{
+ TERNARY_FALSE = 0,
+ TERNARY_TRUE = 1,
+ TERNARY_UNSET = -1
+} ternary;
+
/* ----------------------------------------------------------------
* Section 3: standard system types
diff --git a/src/include/utils/rel.h b/src/include/utils/rel.h
index b552359915..7346d618f9 100644
--- a/src/include/utils/rel.h
+++ b/src/include/utils/rel.h
@@ -346,8 +346,7 @@ typedef struct StdRdOptions
bool user_catalog_table; /* use as an additional catalog relation */
int parallel_workers; /* max number of parallel workers */
StdRdOptIndexCleanup vacuum_index_cleanup; /* controls index vacuuming */
- bool vacuum_truncate; /* enables vacuum to truncate a relation */
- bool vacuum_truncate_set; /* whether vacuum_truncate is set */
+ ternary vacuum_truncate; /* enables vacuum to truncate a relation */
/*
* Fraction of pages in a relation that vacuum can eagerly scan and fail
--
2.39.2
--nextPart5129932.5fSG56mABF
Content-Disposition: attachment;
filename="v2-0003-Add-alias-to-be-used-as-unset-state.patch"
Content-Transfer-Encoding: 7Bit
Content-Type: text/x-patch; charset="unicode-2-0-utf-8";
name="v2-0003-Add-alias-to-be-used-as-unset-state.patch"
^ permalink raw reply [nested|flat] 868+ messages in thread
* [PATCH v2a 2/4] Introduce ternary reloptions
@ 2025-09-04 14:41 Nikolay Shaplov <[email protected]>
0 siblings, 0 replies; 868+ messages in thread
From: Nikolay Shaplov @ 2025-09-04 14:41 UTC (permalink / raw)
Introduce ternary reloption as a replacement for current `vacuum_truncate`
implementation. Remove `vacuum_truncate_set` additional flag and using
`TERNARY_UNSET` value instead.
---
src/backend/access/common/reloptions.c | 129 ++++++++++++++++++++-----
src/backend/commands/vacuum.c | 4 +-
src/include/access/reloptions.h | 26 ++---
src/include/c.h | 16 +++
src/include/utils/rel.h | 3 +-
5 files changed, 135 insertions(+), 43 deletions(-)
diff --git a/src/backend/access/common/reloptions.c b/src/backend/access/common/reloptions.c
index 0af3fea68f..f543f11001 100644
--- a/src/backend/access/common/reloptions.c
+++ b/src/backend/access/common/reloptions.c
@@ -40,9 +40,9 @@
*
* To add an option:
*
- * (i) decide on a type (bool, integer, real, enum, string), name, default
- * value, upper and lower bounds (if applicable); for strings, consider a
- * validation routine.
+ * (i) decide on a type (bool, ternary, integer, real, enum, string), name,
+ * default value, upper and lower bounds (if applicable); for strings,
+ * consider a validation routine.
* (ii) add a record below (or use add_<type>_reloption).
* (iii) add it to the appropriate options struct (perhaps StdRdOptions)
* (iv) add it to the appropriate handling routine (perhaps
@@ -147,15 +147,6 @@ static relopt_bool boolRelOpts[] =
},
false
},
- {
- {
- "vacuum_truncate",
- "Enables vacuum to truncate empty pages at the end of this table",
- RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
- ShareUpdateExclusiveLock
- },
- true
- },
{
{
"deduplicate_items",
@@ -170,6 +161,21 @@ static relopt_bool boolRelOpts[] =
{{NULL}}
};
+static relopt_ternary ternaryRelOpts[] =
+{
+ {
+ {
+ "vacuum_truncate",
+ "Enables vacuum to truncate empty pages at the end of this table",
+ RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
+ ShareUpdateExclusiveLock
+ },
+ TERNARY_UNSET
+ },
+ /* list terminator */
+ {{NULL}}
+};
+
static relopt_int intRelOpts[] =
{
{
@@ -600,6 +606,13 @@ initialize_reloptions(void)
boolRelOpts[i].gen.lockmode));
j++;
}
+ for (i = 0; ternaryRelOpts[i].gen.name; i++)
+ {
+ Assert(DoLockModesConflict(ternaryRelOpts[i].gen.lockmode,
+ ternaryRelOpts[i].gen.lockmode));
+ j++;
+ }
+
for (i = 0; intRelOpts[i].gen.name; i++)
{
Assert(DoLockModesConflict(intRelOpts[i].gen.lockmode,
@@ -640,6 +653,14 @@ initialize_reloptions(void)
j++;
}
+ for (i = 0; ternaryRelOpts[i].gen.name; i++)
+ {
+ relOpts[j] = &ternaryRelOpts[i].gen;
+ relOpts[j]->type = RELOPT_TYPE_TERNARY;
+ relOpts[j]->namelen = strlen(relOpts[j]->name);
+ j++;
+ }
+
for (i = 0; intRelOpts[i].gen.name; i++)
{
relOpts[j] = &intRelOpts[i].gen;
@@ -800,6 +821,9 @@ allocate_reloption(bits32 kinds, int type, const char *name, const char *desc,
case RELOPT_TYPE_BOOL:
size = sizeof(relopt_bool);
break;
+ case RELOPT_TYPE_TERNARY:
+ size = sizeof(relopt_ternary);
+ break;
case RELOPT_TYPE_INT:
size = sizeof(relopt_int);
break;
@@ -883,6 +907,54 @@ add_local_bool_reloption(local_relopts *relopts, const char *name,
add_local_reloption(relopts, (relopt_gen *) newoption, offset);
}
+/*
+ * init_ternary_reloption
+ * Allocate and initialize a new ternary reloption
+ */
+static relopt_ternary *
+init_ternary_reloption(bits32 kinds, const char *name, const char *desc,
+ ternary default_val, LOCKMODE lockmode)
+{
+ relopt_ternary *newoption;
+
+ newoption = (relopt_ternary *) allocate_reloption(kinds,
+ RELOPT_TYPE_TERNARY, name, desc, lockmode);
+ newoption->default_val = default_val;
+
+ return newoption;
+}
+
+/*
+ * add_ternary_reloption
+ * Add a new ternary reloption
+ */
+void
+add_ternary_reloption(bits32 kinds, const char *name, const char *desc,
+ ternary default_val, LOCKMODE lockmode)
+{
+ relopt_ternary *newoption = init_ternary_reloption(kinds, name, desc,
+ default_val, lockmode);
+
+ add_reloption((relopt_gen *) newoption);
+}
+
+/*
+ * add_local_ternary_reloption
+ * Add a new ternary local reloption
+ *
+ * 'offset' is offset of ternary-typed field.
+ */
+void
+add_local_ternary_reloption(local_relopts *relopts, const char *name,
+ const char *desc, ternary default_val,
+ int offset)
+{
+ relopt_ternary *newoption = init_ternary_reloption(RELOPT_KIND_LOCAL,
+ name, desc,
+ default_val, 0);
+
+ add_local_reloption(relopts, (relopt_gen *) newoption, offset);
+}
/*
* init_real_reloption
@@ -1617,6 +1689,18 @@ parse_one_reloption(relopt_value *option, char *text_str, int text_len,
option->gen->name, value)));
}
break;
+ case RELOPT_TYPE_TERNARY:
+ {
+ bool b;
+ parsed = parse_bool(value, &b);
+ option->values.ternary_val = b ? TERNARY_TRUE : TERNARY_FALSE;
+ if (validate && !parsed)
+ ereport(ERROR,
+ (errcode(ERRCODE_INVALID_PARAMETER_VALUE),
+ errmsg("invalid value for ternary option \"%s\": %s",
+ option->gen->name, value)));
+ }
+ break;
case RELOPT_TYPE_INT:
{
relopt_int *optint = (relopt_int *) option->gen;
@@ -1780,17 +1864,6 @@ fillRelOptions(void *rdopts, Size basesize,
char *itempos = ((char *) rdopts) + elems[j].offset;
char *string_val;
- /*
- * If isset_offset is provided, store whether the reloption is
- * set there.
- */
- if (elems[j].isset_offset > 0)
- {
- char *setpos = ((char *) rdopts) + elems[j].isset_offset;
-
- *(bool *) setpos = options[i].isset;
- }
-
switch (options[i].gen->type)
{
case RELOPT_TYPE_BOOL:
@@ -1798,6 +1871,11 @@ fillRelOptions(void *rdopts, Size basesize,
options[i].values.bool_val :
((relopt_bool *) options[i].gen)->default_val;
break;
+ case RELOPT_TYPE_TERNARY:
+ *(ternary *) itempos = options[i].isset ?
+ options[i].values.ternary_val :
+ ((relopt_ternary *) options[i].gen)->default_val;
+ break;
case RELOPT_TYPE_INT:
*(int *) itempos = options[i].isset ?
options[i].values.int_val :
@@ -1912,8 +1990,8 @@ default_reloptions(Datum reloptions, bool validate, relopt_kind kind)
offsetof(StdRdOptions, parallel_workers)},
{"vacuum_index_cleanup", RELOPT_TYPE_ENUM,
offsetof(StdRdOptions, vacuum_index_cleanup)},
- {"vacuum_truncate", RELOPT_TYPE_BOOL,
- offsetof(StdRdOptions, vacuum_truncate), offsetof(StdRdOptions, vacuum_truncate_set)},
+ {"vacuum_truncate", RELOPT_TYPE_TERNARY,
+ offsetof(StdRdOptions, vacuum_truncate)},
{"vacuum_max_eager_freeze_failure_rate", RELOPT_TYPE_REAL,
offsetof(StdRdOptions, vacuum_max_eager_freeze_failure_rate)}
};
@@ -1993,7 +2071,6 @@ build_local_reloptions(local_relopts *relopts, Datum options, bool validate)
elems[i].optname = opt->option->name;
elems[i].opttype = opt->option->type;
elems[i].offset = opt->offset;
- elems[i].isset_offset = 0; /* not supported for local relopts yet */
i++;
}
diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c
index 733ef40ae7..977babff54 100644
--- a/src/backend/commands/vacuum.c
+++ b/src/backend/commands/vacuum.c
@@ -2221,9 +2221,9 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams params,
{
StdRdOptions *opts = (StdRdOptions *) rel->rd_options;
- if (opts && opts->vacuum_truncate_set)
+ if (opts && opts->vacuum_truncate != TERNARY_UNSET)
{
- if (opts->vacuum_truncate)
+ if (opts->vacuum_truncate == TERNARY_TRUE)
params.truncate = VACOPTVALUE_ENABLED;
else
params.truncate = VACOPTVALUE_DISABLED;
diff --git a/src/include/access/reloptions.h b/src/include/access/reloptions.h
index a604a4702c..a436697658 100644
--- a/src/include/access/reloptions.h
+++ b/src/include/access/reloptions.h
@@ -29,6 +29,7 @@
typedef enum relopt_type
{
RELOPT_TYPE_BOOL,
+ RELOPT_TYPE_TERNARY, /* on, off, unset */
RELOPT_TYPE_INT,
RELOPT_TYPE_REAL,
RELOPT_TYPE_ENUM,
@@ -80,6 +81,7 @@ typedef struct relopt_value
union
{
bool bool_val;
+ ternary ternary_val;
int int_val;
double real_val;
int enum_val;
@@ -94,6 +96,12 @@ typedef struct relopt_bool
bool default_val;
} relopt_bool;
+typedef struct relopt_rernary
+{
+ relopt_gen gen;
+ int default_val;
+} relopt_ternary;
+
typedef struct relopt_int
{
relopt_gen gen;
@@ -152,19 +160,6 @@ typedef struct
const char *optname; /* option's name */
relopt_type opttype; /* option's datatype */
int offset; /* offset of field in result struct */
-
- /*
- * isset_offset is an optional offset of a field in the result struct that
- * stores whether the option is explicitly set for the relation or if it
- * just picked up the default value. In most cases, this can be
- * accomplished by giving the reloption a special out-of-range default
- * value (e.g., some integer reloptions use -2), but this isn't always
- * possible. For example, a Boolean reloption cannot be given an
- * out-of-range default, so we need another way to discover the source of
- * its value. This offset is only used if given a value greater than
- * zero.
- */
- int isset_offset;
} relopt_parse_elt;
/* Local reloption definition */
@@ -195,6 +190,8 @@ typedef struct local_relopts
extern relopt_kind add_reloption_kind(void);
extern void add_bool_reloption(bits32 kinds, const char *name, const char *desc,
bool default_val, LOCKMODE lockmode);
+extern void add_ternary_reloption(bits32 kinds, const char *name,
+ const char *desc, int default_val, LOCKMODE lockmode);
extern void add_int_reloption(bits32 kinds, const char *name, const char *desc,
int default_val, int min_val, int max_val,
LOCKMODE lockmode);
@@ -214,6 +211,9 @@ extern void register_reloptions_validator(local_relopts *relopts,
extern void add_local_bool_reloption(local_relopts *relopts, const char *name,
const char *desc, bool default_val,
int offset);
+extern void add_local_ternary_reloption(local_relopts *relopts,
+ const char *name, const char *desc,
+ ternary default_val, int offset);
extern void add_local_int_reloption(local_relopts *relopts, const char *name,
const char *desc, int default_val,
int min_val, int max_val, int offset);
diff --git a/src/include/c.h b/src/include/c.h
index 39022f8a9d..9c59ccccf8 100644
--- a/src/include/c.h
+++ b/src/include/c.h
@@ -488,6 +488,22 @@ typedef void (*pg_funcptr_t) (void);
#include <stdbool.h>
+/*
+ * ternary
+ * Boolean value with an extrea "unset" option
+ *
+ * Ternary data type is used in relation options that can be "true", "false" or
+ * "unset". Since relation options are used deep inside the PostgreSQL code,
+ * this type is declared globally.
+*/
+
+typedef enum ternary
+{
+ TERNARY_FALSE = 0,
+ TERNARY_TRUE = 1,
+ TERNARY_UNSET = -1
+} ternary;
+
/* ----------------------------------------------------------------
* Section 3: standard system types
diff --git a/src/include/utils/rel.h b/src/include/utils/rel.h
index b552359915..7346d618f9 100644
--- a/src/include/utils/rel.h
+++ b/src/include/utils/rel.h
@@ -346,8 +346,7 @@ typedef struct StdRdOptions
bool user_catalog_table; /* use as an additional catalog relation */
int parallel_workers; /* max number of parallel workers */
StdRdOptIndexCleanup vacuum_index_cleanup; /* controls index vacuuming */
- bool vacuum_truncate; /* enables vacuum to truncate a relation */
- bool vacuum_truncate_set; /* whether vacuum_truncate is set */
+ ternary vacuum_truncate; /* enables vacuum to truncate a relation */
/*
* Fraction of pages in a relation that vacuum can eagerly scan and fail
--
2.39.2
--nextPart4155600.3ZeAukHxDK
Content-Disposition: attachment;
filename="v2a-0003-Add-alias-to-be-used-as-unset-state.patch"
Content-Transfer-Encoding: 7Bit
Content-Type: text/x-patch; charset="unicode-2-0-utf-8";
name="v2a-0003-Add-alias-to-be-used-as-unset-state.patch"
^ permalink raw reply [nested|flat] 868+ messages in thread
* [PATCH v2a 2/4] Introduce ternary reloptions
@ 2025-09-04 14:41 Nikolay Shaplov <[email protected]>
0 siblings, 0 replies; 868+ messages in thread
From: Nikolay Shaplov @ 2025-09-04 14:41 UTC (permalink / raw)
Introduce ternary reloption as a replacement for current `vacuum_truncate`
implementation. Remove `vacuum_truncate_set` additional flag and using
`TERNARY_UNSET` value instead.
---
src/backend/access/common/reloptions.c | 129 ++++++++++++++++++++-----
src/backend/commands/vacuum.c | 4 +-
src/include/access/reloptions.h | 26 ++---
src/include/c.h | 16 +++
src/include/utils/rel.h | 3 +-
5 files changed, 135 insertions(+), 43 deletions(-)
diff --git a/src/backend/access/common/reloptions.c b/src/backend/access/common/reloptions.c
index 0af3fea68f..f543f11001 100644
--- a/src/backend/access/common/reloptions.c
+++ b/src/backend/access/common/reloptions.c
@@ -40,9 +40,9 @@
*
* To add an option:
*
- * (i) decide on a type (bool, integer, real, enum, string), name, default
- * value, upper and lower bounds (if applicable); for strings, consider a
- * validation routine.
+ * (i) decide on a type (bool, ternary, integer, real, enum, string), name,
+ * default value, upper and lower bounds (if applicable); for strings,
+ * consider a validation routine.
* (ii) add a record below (or use add_<type>_reloption).
* (iii) add it to the appropriate options struct (perhaps StdRdOptions)
* (iv) add it to the appropriate handling routine (perhaps
@@ -147,15 +147,6 @@ static relopt_bool boolRelOpts[] =
},
false
},
- {
- {
- "vacuum_truncate",
- "Enables vacuum to truncate empty pages at the end of this table",
- RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
- ShareUpdateExclusiveLock
- },
- true
- },
{
{
"deduplicate_items",
@@ -170,6 +161,21 @@ static relopt_bool boolRelOpts[] =
{{NULL}}
};
+static relopt_ternary ternaryRelOpts[] =
+{
+ {
+ {
+ "vacuum_truncate",
+ "Enables vacuum to truncate empty pages at the end of this table",
+ RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
+ ShareUpdateExclusiveLock
+ },
+ TERNARY_UNSET
+ },
+ /* list terminator */
+ {{NULL}}
+};
+
static relopt_int intRelOpts[] =
{
{
@@ -600,6 +606,13 @@ initialize_reloptions(void)
boolRelOpts[i].gen.lockmode));
j++;
}
+ for (i = 0; ternaryRelOpts[i].gen.name; i++)
+ {
+ Assert(DoLockModesConflict(ternaryRelOpts[i].gen.lockmode,
+ ternaryRelOpts[i].gen.lockmode));
+ j++;
+ }
+
for (i = 0; intRelOpts[i].gen.name; i++)
{
Assert(DoLockModesConflict(intRelOpts[i].gen.lockmode,
@@ -640,6 +653,14 @@ initialize_reloptions(void)
j++;
}
+ for (i = 0; ternaryRelOpts[i].gen.name; i++)
+ {
+ relOpts[j] = &ternaryRelOpts[i].gen;
+ relOpts[j]->type = RELOPT_TYPE_TERNARY;
+ relOpts[j]->namelen = strlen(relOpts[j]->name);
+ j++;
+ }
+
for (i = 0; intRelOpts[i].gen.name; i++)
{
relOpts[j] = &intRelOpts[i].gen;
@@ -800,6 +821,9 @@ allocate_reloption(bits32 kinds, int type, const char *name, const char *desc,
case RELOPT_TYPE_BOOL:
size = sizeof(relopt_bool);
break;
+ case RELOPT_TYPE_TERNARY:
+ size = sizeof(relopt_ternary);
+ break;
case RELOPT_TYPE_INT:
size = sizeof(relopt_int);
break;
@@ -883,6 +907,54 @@ add_local_bool_reloption(local_relopts *relopts, const char *name,
add_local_reloption(relopts, (relopt_gen *) newoption, offset);
}
+/*
+ * init_ternary_reloption
+ * Allocate and initialize a new ternary reloption
+ */
+static relopt_ternary *
+init_ternary_reloption(bits32 kinds, const char *name, const char *desc,
+ ternary default_val, LOCKMODE lockmode)
+{
+ relopt_ternary *newoption;
+
+ newoption = (relopt_ternary *) allocate_reloption(kinds,
+ RELOPT_TYPE_TERNARY, name, desc, lockmode);
+ newoption->default_val = default_val;
+
+ return newoption;
+}
+
+/*
+ * add_ternary_reloption
+ * Add a new ternary reloption
+ */
+void
+add_ternary_reloption(bits32 kinds, const char *name, const char *desc,
+ ternary default_val, LOCKMODE lockmode)
+{
+ relopt_ternary *newoption = init_ternary_reloption(kinds, name, desc,
+ default_val, lockmode);
+
+ add_reloption((relopt_gen *) newoption);
+}
+
+/*
+ * add_local_ternary_reloption
+ * Add a new ternary local reloption
+ *
+ * 'offset' is offset of ternary-typed field.
+ */
+void
+add_local_ternary_reloption(local_relopts *relopts, const char *name,
+ const char *desc, ternary default_val,
+ int offset)
+{
+ relopt_ternary *newoption = init_ternary_reloption(RELOPT_KIND_LOCAL,
+ name, desc,
+ default_val, 0);
+
+ add_local_reloption(relopts, (relopt_gen *) newoption, offset);
+}
/*
* init_real_reloption
@@ -1617,6 +1689,18 @@ parse_one_reloption(relopt_value *option, char *text_str, int text_len,
option->gen->name, value)));
}
break;
+ case RELOPT_TYPE_TERNARY:
+ {
+ bool b;
+ parsed = parse_bool(value, &b);
+ option->values.ternary_val = b ? TERNARY_TRUE : TERNARY_FALSE;
+ if (validate && !parsed)
+ ereport(ERROR,
+ (errcode(ERRCODE_INVALID_PARAMETER_VALUE),
+ errmsg("invalid value for ternary option \"%s\": %s",
+ option->gen->name, value)));
+ }
+ break;
case RELOPT_TYPE_INT:
{
relopt_int *optint = (relopt_int *) option->gen;
@@ -1780,17 +1864,6 @@ fillRelOptions(void *rdopts, Size basesize,
char *itempos = ((char *) rdopts) + elems[j].offset;
char *string_val;
- /*
- * If isset_offset is provided, store whether the reloption is
- * set there.
- */
- if (elems[j].isset_offset > 0)
- {
- char *setpos = ((char *) rdopts) + elems[j].isset_offset;
-
- *(bool *) setpos = options[i].isset;
- }
-
switch (options[i].gen->type)
{
case RELOPT_TYPE_BOOL:
@@ -1798,6 +1871,11 @@ fillRelOptions(void *rdopts, Size basesize,
options[i].values.bool_val :
((relopt_bool *) options[i].gen)->default_val;
break;
+ case RELOPT_TYPE_TERNARY:
+ *(ternary *) itempos = options[i].isset ?
+ options[i].values.ternary_val :
+ ((relopt_ternary *) options[i].gen)->default_val;
+ break;
case RELOPT_TYPE_INT:
*(int *) itempos = options[i].isset ?
options[i].values.int_val :
@@ -1912,8 +1990,8 @@ default_reloptions(Datum reloptions, bool validate, relopt_kind kind)
offsetof(StdRdOptions, parallel_workers)},
{"vacuum_index_cleanup", RELOPT_TYPE_ENUM,
offsetof(StdRdOptions, vacuum_index_cleanup)},
- {"vacuum_truncate", RELOPT_TYPE_BOOL,
- offsetof(StdRdOptions, vacuum_truncate), offsetof(StdRdOptions, vacuum_truncate_set)},
+ {"vacuum_truncate", RELOPT_TYPE_TERNARY,
+ offsetof(StdRdOptions, vacuum_truncate)},
{"vacuum_max_eager_freeze_failure_rate", RELOPT_TYPE_REAL,
offsetof(StdRdOptions, vacuum_max_eager_freeze_failure_rate)}
};
@@ -1993,7 +2071,6 @@ build_local_reloptions(local_relopts *relopts, Datum options, bool validate)
elems[i].optname = opt->option->name;
elems[i].opttype = opt->option->type;
elems[i].offset = opt->offset;
- elems[i].isset_offset = 0; /* not supported for local relopts yet */
i++;
}
diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c
index 733ef40ae7..977babff54 100644
--- a/src/backend/commands/vacuum.c
+++ b/src/backend/commands/vacuum.c
@@ -2221,9 +2221,9 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams params,
{
StdRdOptions *opts = (StdRdOptions *) rel->rd_options;
- if (opts && opts->vacuum_truncate_set)
+ if (opts && opts->vacuum_truncate != TERNARY_UNSET)
{
- if (opts->vacuum_truncate)
+ if (opts->vacuum_truncate == TERNARY_TRUE)
params.truncate = VACOPTVALUE_ENABLED;
else
params.truncate = VACOPTVALUE_DISABLED;
diff --git a/src/include/access/reloptions.h b/src/include/access/reloptions.h
index a604a4702c..a436697658 100644
--- a/src/include/access/reloptions.h
+++ b/src/include/access/reloptions.h
@@ -29,6 +29,7 @@
typedef enum relopt_type
{
RELOPT_TYPE_BOOL,
+ RELOPT_TYPE_TERNARY, /* on, off, unset */
RELOPT_TYPE_INT,
RELOPT_TYPE_REAL,
RELOPT_TYPE_ENUM,
@@ -80,6 +81,7 @@ typedef struct relopt_value
union
{
bool bool_val;
+ ternary ternary_val;
int int_val;
double real_val;
int enum_val;
@@ -94,6 +96,12 @@ typedef struct relopt_bool
bool default_val;
} relopt_bool;
+typedef struct relopt_rernary
+{
+ relopt_gen gen;
+ int default_val;
+} relopt_ternary;
+
typedef struct relopt_int
{
relopt_gen gen;
@@ -152,19 +160,6 @@ typedef struct
const char *optname; /* option's name */
relopt_type opttype; /* option's datatype */
int offset; /* offset of field in result struct */
-
- /*
- * isset_offset is an optional offset of a field in the result struct that
- * stores whether the option is explicitly set for the relation or if it
- * just picked up the default value. In most cases, this can be
- * accomplished by giving the reloption a special out-of-range default
- * value (e.g., some integer reloptions use -2), but this isn't always
- * possible. For example, a Boolean reloption cannot be given an
- * out-of-range default, so we need another way to discover the source of
- * its value. This offset is only used if given a value greater than
- * zero.
- */
- int isset_offset;
} relopt_parse_elt;
/* Local reloption definition */
@@ -195,6 +190,8 @@ typedef struct local_relopts
extern relopt_kind add_reloption_kind(void);
extern void add_bool_reloption(bits32 kinds, const char *name, const char *desc,
bool default_val, LOCKMODE lockmode);
+extern void add_ternary_reloption(bits32 kinds, const char *name,
+ const char *desc, int default_val, LOCKMODE lockmode);
extern void add_int_reloption(bits32 kinds, const char *name, const char *desc,
int default_val, int min_val, int max_val,
LOCKMODE lockmode);
@@ -214,6 +211,9 @@ extern void register_reloptions_validator(local_relopts *relopts,
extern void add_local_bool_reloption(local_relopts *relopts, const char *name,
const char *desc, bool default_val,
int offset);
+extern void add_local_ternary_reloption(local_relopts *relopts,
+ const char *name, const char *desc,
+ ternary default_val, int offset);
extern void add_local_int_reloption(local_relopts *relopts, const char *name,
const char *desc, int default_val,
int min_val, int max_val, int offset);
diff --git a/src/include/c.h b/src/include/c.h
index 39022f8a9d..9c59ccccf8 100644
--- a/src/include/c.h
+++ b/src/include/c.h
@@ -488,6 +488,22 @@ typedef void (*pg_funcptr_t) (void);
#include <stdbool.h>
+/*
+ * ternary
+ * Boolean value with an extrea "unset" option
+ *
+ * Ternary data type is used in relation options that can be "true", "false" or
+ * "unset". Since relation options are used deep inside the PostgreSQL code,
+ * this type is declared globally.
+*/
+
+typedef enum ternary
+{
+ TERNARY_FALSE = 0,
+ TERNARY_TRUE = 1,
+ TERNARY_UNSET = -1
+} ternary;
+
/* ----------------------------------------------------------------
* Section 3: standard system types
diff --git a/src/include/utils/rel.h b/src/include/utils/rel.h
index b552359915..7346d618f9 100644
--- a/src/include/utils/rel.h
+++ b/src/include/utils/rel.h
@@ -346,8 +346,7 @@ typedef struct StdRdOptions
bool user_catalog_table; /* use as an additional catalog relation */
int parallel_workers; /* max number of parallel workers */
StdRdOptIndexCleanup vacuum_index_cleanup; /* controls index vacuuming */
- bool vacuum_truncate; /* enables vacuum to truncate a relation */
- bool vacuum_truncate_set; /* whether vacuum_truncate is set */
+ ternary vacuum_truncate; /* enables vacuum to truncate a relation */
/*
* Fraction of pages in a relation that vacuum can eagerly scan and fail
--
2.39.2
--nextPart4155600.3ZeAukHxDK
Content-Disposition: attachment;
filename="v2a-0003-Add-alias-to-be-used-as-unset-state.patch"
Content-Transfer-Encoding: 7Bit
Content-Type: text/x-patch; charset="unicode-2-0-utf-8";
name="v2a-0003-Add-alias-to-be-used-as-unset-state.patch"
^ permalink raw reply [nested|flat] 868+ messages in thread
* [PATCH v2a 2/4] Introduce ternary reloptions
@ 2025-09-04 14:41 Nikolay Shaplov <[email protected]>
0 siblings, 0 replies; 868+ messages in thread
From: Nikolay Shaplov @ 2025-09-04 14:41 UTC (permalink / raw)
Introduce ternary reloption as a replacement for current `vacuum_truncate`
implementation. Remove `vacuum_truncate_set` additional flag and using
`TERNARY_UNSET` value instead.
---
src/backend/access/common/reloptions.c | 129 ++++++++++++++++++++-----
src/backend/commands/vacuum.c | 4 +-
src/include/access/reloptions.h | 26 ++---
src/include/c.h | 16 +++
src/include/utils/rel.h | 3 +-
5 files changed, 135 insertions(+), 43 deletions(-)
diff --git a/src/backend/access/common/reloptions.c b/src/backend/access/common/reloptions.c
index 0af3fea68f..f543f11001 100644
--- a/src/backend/access/common/reloptions.c
+++ b/src/backend/access/common/reloptions.c
@@ -40,9 +40,9 @@
*
* To add an option:
*
- * (i) decide on a type (bool, integer, real, enum, string), name, default
- * value, upper and lower bounds (if applicable); for strings, consider a
- * validation routine.
+ * (i) decide on a type (bool, ternary, integer, real, enum, string), name,
+ * default value, upper and lower bounds (if applicable); for strings,
+ * consider a validation routine.
* (ii) add a record below (or use add_<type>_reloption).
* (iii) add it to the appropriate options struct (perhaps StdRdOptions)
* (iv) add it to the appropriate handling routine (perhaps
@@ -147,15 +147,6 @@ static relopt_bool boolRelOpts[] =
},
false
},
- {
- {
- "vacuum_truncate",
- "Enables vacuum to truncate empty pages at the end of this table",
- RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
- ShareUpdateExclusiveLock
- },
- true
- },
{
{
"deduplicate_items",
@@ -170,6 +161,21 @@ static relopt_bool boolRelOpts[] =
{{NULL}}
};
+static relopt_ternary ternaryRelOpts[] =
+{
+ {
+ {
+ "vacuum_truncate",
+ "Enables vacuum to truncate empty pages at the end of this table",
+ RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
+ ShareUpdateExclusiveLock
+ },
+ TERNARY_UNSET
+ },
+ /* list terminator */
+ {{NULL}}
+};
+
static relopt_int intRelOpts[] =
{
{
@@ -600,6 +606,13 @@ initialize_reloptions(void)
boolRelOpts[i].gen.lockmode));
j++;
}
+ for (i = 0; ternaryRelOpts[i].gen.name; i++)
+ {
+ Assert(DoLockModesConflict(ternaryRelOpts[i].gen.lockmode,
+ ternaryRelOpts[i].gen.lockmode));
+ j++;
+ }
+
for (i = 0; intRelOpts[i].gen.name; i++)
{
Assert(DoLockModesConflict(intRelOpts[i].gen.lockmode,
@@ -640,6 +653,14 @@ initialize_reloptions(void)
j++;
}
+ for (i = 0; ternaryRelOpts[i].gen.name; i++)
+ {
+ relOpts[j] = &ternaryRelOpts[i].gen;
+ relOpts[j]->type = RELOPT_TYPE_TERNARY;
+ relOpts[j]->namelen = strlen(relOpts[j]->name);
+ j++;
+ }
+
for (i = 0; intRelOpts[i].gen.name; i++)
{
relOpts[j] = &intRelOpts[i].gen;
@@ -800,6 +821,9 @@ allocate_reloption(bits32 kinds, int type, const char *name, const char *desc,
case RELOPT_TYPE_BOOL:
size = sizeof(relopt_bool);
break;
+ case RELOPT_TYPE_TERNARY:
+ size = sizeof(relopt_ternary);
+ break;
case RELOPT_TYPE_INT:
size = sizeof(relopt_int);
break;
@@ -883,6 +907,54 @@ add_local_bool_reloption(local_relopts *relopts, const char *name,
add_local_reloption(relopts, (relopt_gen *) newoption, offset);
}
+/*
+ * init_ternary_reloption
+ * Allocate and initialize a new ternary reloption
+ */
+static relopt_ternary *
+init_ternary_reloption(bits32 kinds, const char *name, const char *desc,
+ ternary default_val, LOCKMODE lockmode)
+{
+ relopt_ternary *newoption;
+
+ newoption = (relopt_ternary *) allocate_reloption(kinds,
+ RELOPT_TYPE_TERNARY, name, desc, lockmode);
+ newoption->default_val = default_val;
+
+ return newoption;
+}
+
+/*
+ * add_ternary_reloption
+ * Add a new ternary reloption
+ */
+void
+add_ternary_reloption(bits32 kinds, const char *name, const char *desc,
+ ternary default_val, LOCKMODE lockmode)
+{
+ relopt_ternary *newoption = init_ternary_reloption(kinds, name, desc,
+ default_val, lockmode);
+
+ add_reloption((relopt_gen *) newoption);
+}
+
+/*
+ * add_local_ternary_reloption
+ * Add a new ternary local reloption
+ *
+ * 'offset' is offset of ternary-typed field.
+ */
+void
+add_local_ternary_reloption(local_relopts *relopts, const char *name,
+ const char *desc, ternary default_val,
+ int offset)
+{
+ relopt_ternary *newoption = init_ternary_reloption(RELOPT_KIND_LOCAL,
+ name, desc,
+ default_val, 0);
+
+ add_local_reloption(relopts, (relopt_gen *) newoption, offset);
+}
/*
* init_real_reloption
@@ -1617,6 +1689,18 @@ parse_one_reloption(relopt_value *option, char *text_str, int text_len,
option->gen->name, value)));
}
break;
+ case RELOPT_TYPE_TERNARY:
+ {
+ bool b;
+ parsed = parse_bool(value, &b);
+ option->values.ternary_val = b ? TERNARY_TRUE : TERNARY_FALSE;
+ if (validate && !parsed)
+ ereport(ERROR,
+ (errcode(ERRCODE_INVALID_PARAMETER_VALUE),
+ errmsg("invalid value for ternary option \"%s\": %s",
+ option->gen->name, value)));
+ }
+ break;
case RELOPT_TYPE_INT:
{
relopt_int *optint = (relopt_int *) option->gen;
@@ -1780,17 +1864,6 @@ fillRelOptions(void *rdopts, Size basesize,
char *itempos = ((char *) rdopts) + elems[j].offset;
char *string_val;
- /*
- * If isset_offset is provided, store whether the reloption is
- * set there.
- */
- if (elems[j].isset_offset > 0)
- {
- char *setpos = ((char *) rdopts) + elems[j].isset_offset;
-
- *(bool *) setpos = options[i].isset;
- }
-
switch (options[i].gen->type)
{
case RELOPT_TYPE_BOOL:
@@ -1798,6 +1871,11 @@ fillRelOptions(void *rdopts, Size basesize,
options[i].values.bool_val :
((relopt_bool *) options[i].gen)->default_val;
break;
+ case RELOPT_TYPE_TERNARY:
+ *(ternary *) itempos = options[i].isset ?
+ options[i].values.ternary_val :
+ ((relopt_ternary *) options[i].gen)->default_val;
+ break;
case RELOPT_TYPE_INT:
*(int *) itempos = options[i].isset ?
options[i].values.int_val :
@@ -1912,8 +1990,8 @@ default_reloptions(Datum reloptions, bool validate, relopt_kind kind)
offsetof(StdRdOptions, parallel_workers)},
{"vacuum_index_cleanup", RELOPT_TYPE_ENUM,
offsetof(StdRdOptions, vacuum_index_cleanup)},
- {"vacuum_truncate", RELOPT_TYPE_BOOL,
- offsetof(StdRdOptions, vacuum_truncate), offsetof(StdRdOptions, vacuum_truncate_set)},
+ {"vacuum_truncate", RELOPT_TYPE_TERNARY,
+ offsetof(StdRdOptions, vacuum_truncate)},
{"vacuum_max_eager_freeze_failure_rate", RELOPT_TYPE_REAL,
offsetof(StdRdOptions, vacuum_max_eager_freeze_failure_rate)}
};
@@ -1993,7 +2071,6 @@ build_local_reloptions(local_relopts *relopts, Datum options, bool validate)
elems[i].optname = opt->option->name;
elems[i].opttype = opt->option->type;
elems[i].offset = opt->offset;
- elems[i].isset_offset = 0; /* not supported for local relopts yet */
i++;
}
diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c
index 733ef40ae7..977babff54 100644
--- a/src/backend/commands/vacuum.c
+++ b/src/backend/commands/vacuum.c
@@ -2221,9 +2221,9 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams params,
{
StdRdOptions *opts = (StdRdOptions *) rel->rd_options;
- if (opts && opts->vacuum_truncate_set)
+ if (opts && opts->vacuum_truncate != TERNARY_UNSET)
{
- if (opts->vacuum_truncate)
+ if (opts->vacuum_truncate == TERNARY_TRUE)
params.truncate = VACOPTVALUE_ENABLED;
else
params.truncate = VACOPTVALUE_DISABLED;
diff --git a/src/include/access/reloptions.h b/src/include/access/reloptions.h
index a604a4702c..a436697658 100644
--- a/src/include/access/reloptions.h
+++ b/src/include/access/reloptions.h
@@ -29,6 +29,7 @@
typedef enum relopt_type
{
RELOPT_TYPE_BOOL,
+ RELOPT_TYPE_TERNARY, /* on, off, unset */
RELOPT_TYPE_INT,
RELOPT_TYPE_REAL,
RELOPT_TYPE_ENUM,
@@ -80,6 +81,7 @@ typedef struct relopt_value
union
{
bool bool_val;
+ ternary ternary_val;
int int_val;
double real_val;
int enum_val;
@@ -94,6 +96,12 @@ typedef struct relopt_bool
bool default_val;
} relopt_bool;
+typedef struct relopt_rernary
+{
+ relopt_gen gen;
+ int default_val;
+} relopt_ternary;
+
typedef struct relopt_int
{
relopt_gen gen;
@@ -152,19 +160,6 @@ typedef struct
const char *optname; /* option's name */
relopt_type opttype; /* option's datatype */
int offset; /* offset of field in result struct */
-
- /*
- * isset_offset is an optional offset of a field in the result struct that
- * stores whether the option is explicitly set for the relation or if it
- * just picked up the default value. In most cases, this can be
- * accomplished by giving the reloption a special out-of-range default
- * value (e.g., some integer reloptions use -2), but this isn't always
- * possible. For example, a Boolean reloption cannot be given an
- * out-of-range default, so we need another way to discover the source of
- * its value. This offset is only used if given a value greater than
- * zero.
- */
- int isset_offset;
} relopt_parse_elt;
/* Local reloption definition */
@@ -195,6 +190,8 @@ typedef struct local_relopts
extern relopt_kind add_reloption_kind(void);
extern void add_bool_reloption(bits32 kinds, const char *name, const char *desc,
bool default_val, LOCKMODE lockmode);
+extern void add_ternary_reloption(bits32 kinds, const char *name,
+ const char *desc, int default_val, LOCKMODE lockmode);
extern void add_int_reloption(bits32 kinds, const char *name, const char *desc,
int default_val, int min_val, int max_val,
LOCKMODE lockmode);
@@ -214,6 +211,9 @@ extern void register_reloptions_validator(local_relopts *relopts,
extern void add_local_bool_reloption(local_relopts *relopts, const char *name,
const char *desc, bool default_val,
int offset);
+extern void add_local_ternary_reloption(local_relopts *relopts,
+ const char *name, const char *desc,
+ ternary default_val, int offset);
extern void add_local_int_reloption(local_relopts *relopts, const char *name,
const char *desc, int default_val,
int min_val, int max_val, int offset);
diff --git a/src/include/c.h b/src/include/c.h
index 39022f8a9d..9c59ccccf8 100644
--- a/src/include/c.h
+++ b/src/include/c.h
@@ -488,6 +488,22 @@ typedef void (*pg_funcptr_t) (void);
#include <stdbool.h>
+/*
+ * ternary
+ * Boolean value with an extrea "unset" option
+ *
+ * Ternary data type is used in relation options that can be "true", "false" or
+ * "unset". Since relation options are used deep inside the PostgreSQL code,
+ * this type is declared globally.
+*/
+
+typedef enum ternary
+{
+ TERNARY_FALSE = 0,
+ TERNARY_TRUE = 1,
+ TERNARY_UNSET = -1
+} ternary;
+
/* ----------------------------------------------------------------
* Section 3: standard system types
diff --git a/src/include/utils/rel.h b/src/include/utils/rel.h
index b552359915..7346d618f9 100644
--- a/src/include/utils/rel.h
+++ b/src/include/utils/rel.h
@@ -346,8 +346,7 @@ typedef struct StdRdOptions
bool user_catalog_table; /* use as an additional catalog relation */
int parallel_workers; /* max number of parallel workers */
StdRdOptIndexCleanup vacuum_index_cleanup; /* controls index vacuuming */
- bool vacuum_truncate; /* enables vacuum to truncate a relation */
- bool vacuum_truncate_set; /* whether vacuum_truncate is set */
+ ternary vacuum_truncate; /* enables vacuum to truncate a relation */
/*
* Fraction of pages in a relation that vacuum can eagerly scan and fail
--
2.39.2
--nextPart4155600.3ZeAukHxDK
Content-Disposition: attachment;
filename="v2a-0003-Add-alias-to-be-used-as-unset-state.patch"
Content-Transfer-Encoding: 7Bit
Content-Type: text/x-patch; charset="unicode-2-0-utf-8";
name="v2a-0003-Add-alias-to-be-used-as-unset-state.patch"
^ permalink raw reply [nested|flat] 868+ messages in thread
* [PATCH v2a 2/4] Introduce ternary reloptions
@ 2025-09-04 14:41 Nikolay Shaplov <[email protected]>
0 siblings, 0 replies; 868+ messages in thread
From: Nikolay Shaplov @ 2025-09-04 14:41 UTC (permalink / raw)
Introduce ternary reloption as a replacement for current `vacuum_truncate`
implementation. Remove `vacuum_truncate_set` additional flag and using
`TERNARY_UNSET` value instead.
---
src/backend/access/common/reloptions.c | 129 ++++++++++++++++++++-----
src/backend/commands/vacuum.c | 4 +-
src/include/access/reloptions.h | 26 ++---
src/include/c.h | 16 +++
src/include/utils/rel.h | 3 +-
5 files changed, 135 insertions(+), 43 deletions(-)
diff --git a/src/backend/access/common/reloptions.c b/src/backend/access/common/reloptions.c
index 0af3fea68f..f543f11001 100644
--- a/src/backend/access/common/reloptions.c
+++ b/src/backend/access/common/reloptions.c
@@ -40,9 +40,9 @@
*
* To add an option:
*
- * (i) decide on a type (bool, integer, real, enum, string), name, default
- * value, upper and lower bounds (if applicable); for strings, consider a
- * validation routine.
+ * (i) decide on a type (bool, ternary, integer, real, enum, string), name,
+ * default value, upper and lower bounds (if applicable); for strings,
+ * consider a validation routine.
* (ii) add a record below (or use add_<type>_reloption).
* (iii) add it to the appropriate options struct (perhaps StdRdOptions)
* (iv) add it to the appropriate handling routine (perhaps
@@ -147,15 +147,6 @@ static relopt_bool boolRelOpts[] =
},
false
},
- {
- {
- "vacuum_truncate",
- "Enables vacuum to truncate empty pages at the end of this table",
- RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
- ShareUpdateExclusiveLock
- },
- true
- },
{
{
"deduplicate_items",
@@ -170,6 +161,21 @@ static relopt_bool boolRelOpts[] =
{{NULL}}
};
+static relopt_ternary ternaryRelOpts[] =
+{
+ {
+ {
+ "vacuum_truncate",
+ "Enables vacuum to truncate empty pages at the end of this table",
+ RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
+ ShareUpdateExclusiveLock
+ },
+ TERNARY_UNSET
+ },
+ /* list terminator */
+ {{NULL}}
+};
+
static relopt_int intRelOpts[] =
{
{
@@ -600,6 +606,13 @@ initialize_reloptions(void)
boolRelOpts[i].gen.lockmode));
j++;
}
+ for (i = 0; ternaryRelOpts[i].gen.name; i++)
+ {
+ Assert(DoLockModesConflict(ternaryRelOpts[i].gen.lockmode,
+ ternaryRelOpts[i].gen.lockmode));
+ j++;
+ }
+
for (i = 0; intRelOpts[i].gen.name; i++)
{
Assert(DoLockModesConflict(intRelOpts[i].gen.lockmode,
@@ -640,6 +653,14 @@ initialize_reloptions(void)
j++;
}
+ for (i = 0; ternaryRelOpts[i].gen.name; i++)
+ {
+ relOpts[j] = &ternaryRelOpts[i].gen;
+ relOpts[j]->type = RELOPT_TYPE_TERNARY;
+ relOpts[j]->namelen = strlen(relOpts[j]->name);
+ j++;
+ }
+
for (i = 0; intRelOpts[i].gen.name; i++)
{
relOpts[j] = &intRelOpts[i].gen;
@@ -800,6 +821,9 @@ allocate_reloption(bits32 kinds, int type, const char *name, const char *desc,
case RELOPT_TYPE_BOOL:
size = sizeof(relopt_bool);
break;
+ case RELOPT_TYPE_TERNARY:
+ size = sizeof(relopt_ternary);
+ break;
case RELOPT_TYPE_INT:
size = sizeof(relopt_int);
break;
@@ -883,6 +907,54 @@ add_local_bool_reloption(local_relopts *relopts, const char *name,
add_local_reloption(relopts, (relopt_gen *) newoption, offset);
}
+/*
+ * init_ternary_reloption
+ * Allocate and initialize a new ternary reloption
+ */
+static relopt_ternary *
+init_ternary_reloption(bits32 kinds, const char *name, const char *desc,
+ ternary default_val, LOCKMODE lockmode)
+{
+ relopt_ternary *newoption;
+
+ newoption = (relopt_ternary *) allocate_reloption(kinds,
+ RELOPT_TYPE_TERNARY, name, desc, lockmode);
+ newoption->default_val = default_val;
+
+ return newoption;
+}
+
+/*
+ * add_ternary_reloption
+ * Add a new ternary reloption
+ */
+void
+add_ternary_reloption(bits32 kinds, const char *name, const char *desc,
+ ternary default_val, LOCKMODE lockmode)
+{
+ relopt_ternary *newoption = init_ternary_reloption(kinds, name, desc,
+ default_val, lockmode);
+
+ add_reloption((relopt_gen *) newoption);
+}
+
+/*
+ * add_local_ternary_reloption
+ * Add a new ternary local reloption
+ *
+ * 'offset' is offset of ternary-typed field.
+ */
+void
+add_local_ternary_reloption(local_relopts *relopts, const char *name,
+ const char *desc, ternary default_val,
+ int offset)
+{
+ relopt_ternary *newoption = init_ternary_reloption(RELOPT_KIND_LOCAL,
+ name, desc,
+ default_val, 0);
+
+ add_local_reloption(relopts, (relopt_gen *) newoption, offset);
+}
/*
* init_real_reloption
@@ -1617,6 +1689,18 @@ parse_one_reloption(relopt_value *option, char *text_str, int text_len,
option->gen->name, value)));
}
break;
+ case RELOPT_TYPE_TERNARY:
+ {
+ bool b;
+ parsed = parse_bool(value, &b);
+ option->values.ternary_val = b ? TERNARY_TRUE : TERNARY_FALSE;
+ if (validate && !parsed)
+ ereport(ERROR,
+ (errcode(ERRCODE_INVALID_PARAMETER_VALUE),
+ errmsg("invalid value for ternary option \"%s\": %s",
+ option->gen->name, value)));
+ }
+ break;
case RELOPT_TYPE_INT:
{
relopt_int *optint = (relopt_int *) option->gen;
@@ -1780,17 +1864,6 @@ fillRelOptions(void *rdopts, Size basesize,
char *itempos = ((char *) rdopts) + elems[j].offset;
char *string_val;
- /*
- * If isset_offset is provided, store whether the reloption is
- * set there.
- */
- if (elems[j].isset_offset > 0)
- {
- char *setpos = ((char *) rdopts) + elems[j].isset_offset;
-
- *(bool *) setpos = options[i].isset;
- }
-
switch (options[i].gen->type)
{
case RELOPT_TYPE_BOOL:
@@ -1798,6 +1871,11 @@ fillRelOptions(void *rdopts, Size basesize,
options[i].values.bool_val :
((relopt_bool *) options[i].gen)->default_val;
break;
+ case RELOPT_TYPE_TERNARY:
+ *(ternary *) itempos = options[i].isset ?
+ options[i].values.ternary_val :
+ ((relopt_ternary *) options[i].gen)->default_val;
+ break;
case RELOPT_TYPE_INT:
*(int *) itempos = options[i].isset ?
options[i].values.int_val :
@@ -1912,8 +1990,8 @@ default_reloptions(Datum reloptions, bool validate, relopt_kind kind)
offsetof(StdRdOptions, parallel_workers)},
{"vacuum_index_cleanup", RELOPT_TYPE_ENUM,
offsetof(StdRdOptions, vacuum_index_cleanup)},
- {"vacuum_truncate", RELOPT_TYPE_BOOL,
- offsetof(StdRdOptions, vacuum_truncate), offsetof(StdRdOptions, vacuum_truncate_set)},
+ {"vacuum_truncate", RELOPT_TYPE_TERNARY,
+ offsetof(StdRdOptions, vacuum_truncate)},
{"vacuum_max_eager_freeze_failure_rate", RELOPT_TYPE_REAL,
offsetof(StdRdOptions, vacuum_max_eager_freeze_failure_rate)}
};
@@ -1993,7 +2071,6 @@ build_local_reloptions(local_relopts *relopts, Datum options, bool validate)
elems[i].optname = opt->option->name;
elems[i].opttype = opt->option->type;
elems[i].offset = opt->offset;
- elems[i].isset_offset = 0; /* not supported for local relopts yet */
i++;
}
diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c
index 733ef40ae7..977babff54 100644
--- a/src/backend/commands/vacuum.c
+++ b/src/backend/commands/vacuum.c
@@ -2221,9 +2221,9 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams params,
{
StdRdOptions *opts = (StdRdOptions *) rel->rd_options;
- if (opts && opts->vacuum_truncate_set)
+ if (opts && opts->vacuum_truncate != TERNARY_UNSET)
{
- if (opts->vacuum_truncate)
+ if (opts->vacuum_truncate == TERNARY_TRUE)
params.truncate = VACOPTVALUE_ENABLED;
else
params.truncate = VACOPTVALUE_DISABLED;
diff --git a/src/include/access/reloptions.h b/src/include/access/reloptions.h
index a604a4702c..a436697658 100644
--- a/src/include/access/reloptions.h
+++ b/src/include/access/reloptions.h
@@ -29,6 +29,7 @@
typedef enum relopt_type
{
RELOPT_TYPE_BOOL,
+ RELOPT_TYPE_TERNARY, /* on, off, unset */
RELOPT_TYPE_INT,
RELOPT_TYPE_REAL,
RELOPT_TYPE_ENUM,
@@ -80,6 +81,7 @@ typedef struct relopt_value
union
{
bool bool_val;
+ ternary ternary_val;
int int_val;
double real_val;
int enum_val;
@@ -94,6 +96,12 @@ typedef struct relopt_bool
bool default_val;
} relopt_bool;
+typedef struct relopt_rernary
+{
+ relopt_gen gen;
+ int default_val;
+} relopt_ternary;
+
typedef struct relopt_int
{
relopt_gen gen;
@@ -152,19 +160,6 @@ typedef struct
const char *optname; /* option's name */
relopt_type opttype; /* option's datatype */
int offset; /* offset of field in result struct */
-
- /*
- * isset_offset is an optional offset of a field in the result struct that
- * stores whether the option is explicitly set for the relation or if it
- * just picked up the default value. In most cases, this can be
- * accomplished by giving the reloption a special out-of-range default
- * value (e.g., some integer reloptions use -2), but this isn't always
- * possible. For example, a Boolean reloption cannot be given an
- * out-of-range default, so we need another way to discover the source of
- * its value. This offset is only used if given a value greater than
- * zero.
- */
- int isset_offset;
} relopt_parse_elt;
/* Local reloption definition */
@@ -195,6 +190,8 @@ typedef struct local_relopts
extern relopt_kind add_reloption_kind(void);
extern void add_bool_reloption(bits32 kinds, const char *name, const char *desc,
bool default_val, LOCKMODE lockmode);
+extern void add_ternary_reloption(bits32 kinds, const char *name,
+ const char *desc, int default_val, LOCKMODE lockmode);
extern void add_int_reloption(bits32 kinds, const char *name, const char *desc,
int default_val, int min_val, int max_val,
LOCKMODE lockmode);
@@ -214,6 +211,9 @@ extern void register_reloptions_validator(local_relopts *relopts,
extern void add_local_bool_reloption(local_relopts *relopts, const char *name,
const char *desc, bool default_val,
int offset);
+extern void add_local_ternary_reloption(local_relopts *relopts,
+ const char *name, const char *desc,
+ ternary default_val, int offset);
extern void add_local_int_reloption(local_relopts *relopts, const char *name,
const char *desc, int default_val,
int min_val, int max_val, int offset);
diff --git a/src/include/c.h b/src/include/c.h
index 39022f8a9d..9c59ccccf8 100644
--- a/src/include/c.h
+++ b/src/include/c.h
@@ -488,6 +488,22 @@ typedef void (*pg_funcptr_t) (void);
#include <stdbool.h>
+/*
+ * ternary
+ * Boolean value with an extrea "unset" option
+ *
+ * Ternary data type is used in relation options that can be "true", "false" or
+ * "unset". Since relation options are used deep inside the PostgreSQL code,
+ * this type is declared globally.
+*/
+
+typedef enum ternary
+{
+ TERNARY_FALSE = 0,
+ TERNARY_TRUE = 1,
+ TERNARY_UNSET = -1
+} ternary;
+
/* ----------------------------------------------------------------
* Section 3: standard system types
diff --git a/src/include/utils/rel.h b/src/include/utils/rel.h
index b552359915..7346d618f9 100644
--- a/src/include/utils/rel.h
+++ b/src/include/utils/rel.h
@@ -346,8 +346,7 @@ typedef struct StdRdOptions
bool user_catalog_table; /* use as an additional catalog relation */
int parallel_workers; /* max number of parallel workers */
StdRdOptIndexCleanup vacuum_index_cleanup; /* controls index vacuuming */
- bool vacuum_truncate; /* enables vacuum to truncate a relation */
- bool vacuum_truncate_set; /* whether vacuum_truncate is set */
+ ternary vacuum_truncate; /* enables vacuum to truncate a relation */
/*
* Fraction of pages in a relation that vacuum can eagerly scan and fail
--
2.39.2
--nextPart4155600.3ZeAukHxDK
Content-Disposition: attachment;
filename="v2a-0003-Add-alias-to-be-used-as-unset-state.patch"
Content-Transfer-Encoding: 7Bit
Content-Type: text/x-patch; charset="unicode-2-0-utf-8";
name="v2a-0003-Add-alias-to-be-used-as-unset-state.patch"
^ permalink raw reply [nested|flat] 868+ messages in thread
* [PATCH v2a 2/4] Introduce ternary reloptions
@ 2025-09-04 14:41 Nikolay Shaplov <[email protected]>
0 siblings, 0 replies; 868+ messages in thread
From: Nikolay Shaplov @ 2025-09-04 14:41 UTC (permalink / raw)
Introduce ternary reloption as a replacement for current `vacuum_truncate`
implementation. Remove `vacuum_truncate_set` additional flag and using
`TERNARY_UNSET` value instead.
---
src/backend/access/common/reloptions.c | 129 ++++++++++++++++++++-----
src/backend/commands/vacuum.c | 4 +-
src/include/access/reloptions.h | 26 ++---
src/include/c.h | 16 +++
src/include/utils/rel.h | 3 +-
5 files changed, 135 insertions(+), 43 deletions(-)
diff --git a/src/backend/access/common/reloptions.c b/src/backend/access/common/reloptions.c
index 0af3fea68f..f543f11001 100644
--- a/src/backend/access/common/reloptions.c
+++ b/src/backend/access/common/reloptions.c
@@ -40,9 +40,9 @@
*
* To add an option:
*
- * (i) decide on a type (bool, integer, real, enum, string), name, default
- * value, upper and lower bounds (if applicable); for strings, consider a
- * validation routine.
+ * (i) decide on a type (bool, ternary, integer, real, enum, string), name,
+ * default value, upper and lower bounds (if applicable); for strings,
+ * consider a validation routine.
* (ii) add a record below (or use add_<type>_reloption).
* (iii) add it to the appropriate options struct (perhaps StdRdOptions)
* (iv) add it to the appropriate handling routine (perhaps
@@ -147,15 +147,6 @@ static relopt_bool boolRelOpts[] =
},
false
},
- {
- {
- "vacuum_truncate",
- "Enables vacuum to truncate empty pages at the end of this table",
- RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
- ShareUpdateExclusiveLock
- },
- true
- },
{
{
"deduplicate_items",
@@ -170,6 +161,21 @@ static relopt_bool boolRelOpts[] =
{{NULL}}
};
+static relopt_ternary ternaryRelOpts[] =
+{
+ {
+ {
+ "vacuum_truncate",
+ "Enables vacuum to truncate empty pages at the end of this table",
+ RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
+ ShareUpdateExclusiveLock
+ },
+ TERNARY_UNSET
+ },
+ /* list terminator */
+ {{NULL}}
+};
+
static relopt_int intRelOpts[] =
{
{
@@ -600,6 +606,13 @@ initialize_reloptions(void)
boolRelOpts[i].gen.lockmode));
j++;
}
+ for (i = 0; ternaryRelOpts[i].gen.name; i++)
+ {
+ Assert(DoLockModesConflict(ternaryRelOpts[i].gen.lockmode,
+ ternaryRelOpts[i].gen.lockmode));
+ j++;
+ }
+
for (i = 0; intRelOpts[i].gen.name; i++)
{
Assert(DoLockModesConflict(intRelOpts[i].gen.lockmode,
@@ -640,6 +653,14 @@ initialize_reloptions(void)
j++;
}
+ for (i = 0; ternaryRelOpts[i].gen.name; i++)
+ {
+ relOpts[j] = &ternaryRelOpts[i].gen;
+ relOpts[j]->type = RELOPT_TYPE_TERNARY;
+ relOpts[j]->namelen = strlen(relOpts[j]->name);
+ j++;
+ }
+
for (i = 0; intRelOpts[i].gen.name; i++)
{
relOpts[j] = &intRelOpts[i].gen;
@@ -800,6 +821,9 @@ allocate_reloption(bits32 kinds, int type, const char *name, const char *desc,
case RELOPT_TYPE_BOOL:
size = sizeof(relopt_bool);
break;
+ case RELOPT_TYPE_TERNARY:
+ size = sizeof(relopt_ternary);
+ break;
case RELOPT_TYPE_INT:
size = sizeof(relopt_int);
break;
@@ -883,6 +907,54 @@ add_local_bool_reloption(local_relopts *relopts, const char *name,
add_local_reloption(relopts, (relopt_gen *) newoption, offset);
}
+/*
+ * init_ternary_reloption
+ * Allocate and initialize a new ternary reloption
+ */
+static relopt_ternary *
+init_ternary_reloption(bits32 kinds, const char *name, const char *desc,
+ ternary default_val, LOCKMODE lockmode)
+{
+ relopt_ternary *newoption;
+
+ newoption = (relopt_ternary *) allocate_reloption(kinds,
+ RELOPT_TYPE_TERNARY, name, desc, lockmode);
+ newoption->default_val = default_val;
+
+ return newoption;
+}
+
+/*
+ * add_ternary_reloption
+ * Add a new ternary reloption
+ */
+void
+add_ternary_reloption(bits32 kinds, const char *name, const char *desc,
+ ternary default_val, LOCKMODE lockmode)
+{
+ relopt_ternary *newoption = init_ternary_reloption(kinds, name, desc,
+ default_val, lockmode);
+
+ add_reloption((relopt_gen *) newoption);
+}
+
+/*
+ * add_local_ternary_reloption
+ * Add a new ternary local reloption
+ *
+ * 'offset' is offset of ternary-typed field.
+ */
+void
+add_local_ternary_reloption(local_relopts *relopts, const char *name,
+ const char *desc, ternary default_val,
+ int offset)
+{
+ relopt_ternary *newoption = init_ternary_reloption(RELOPT_KIND_LOCAL,
+ name, desc,
+ default_val, 0);
+
+ add_local_reloption(relopts, (relopt_gen *) newoption, offset);
+}
/*
* init_real_reloption
@@ -1617,6 +1689,18 @@ parse_one_reloption(relopt_value *option, char *text_str, int text_len,
option->gen->name, value)));
}
break;
+ case RELOPT_TYPE_TERNARY:
+ {
+ bool b;
+ parsed = parse_bool(value, &b);
+ option->values.ternary_val = b ? TERNARY_TRUE : TERNARY_FALSE;
+ if (validate && !parsed)
+ ereport(ERROR,
+ (errcode(ERRCODE_INVALID_PARAMETER_VALUE),
+ errmsg("invalid value for ternary option \"%s\": %s",
+ option->gen->name, value)));
+ }
+ break;
case RELOPT_TYPE_INT:
{
relopt_int *optint = (relopt_int *) option->gen;
@@ -1780,17 +1864,6 @@ fillRelOptions(void *rdopts, Size basesize,
char *itempos = ((char *) rdopts) + elems[j].offset;
char *string_val;
- /*
- * If isset_offset is provided, store whether the reloption is
- * set there.
- */
- if (elems[j].isset_offset > 0)
- {
- char *setpos = ((char *) rdopts) + elems[j].isset_offset;
-
- *(bool *) setpos = options[i].isset;
- }
-
switch (options[i].gen->type)
{
case RELOPT_TYPE_BOOL:
@@ -1798,6 +1871,11 @@ fillRelOptions(void *rdopts, Size basesize,
options[i].values.bool_val :
((relopt_bool *) options[i].gen)->default_val;
break;
+ case RELOPT_TYPE_TERNARY:
+ *(ternary *) itempos = options[i].isset ?
+ options[i].values.ternary_val :
+ ((relopt_ternary *) options[i].gen)->default_val;
+ break;
case RELOPT_TYPE_INT:
*(int *) itempos = options[i].isset ?
options[i].values.int_val :
@@ -1912,8 +1990,8 @@ default_reloptions(Datum reloptions, bool validate, relopt_kind kind)
offsetof(StdRdOptions, parallel_workers)},
{"vacuum_index_cleanup", RELOPT_TYPE_ENUM,
offsetof(StdRdOptions, vacuum_index_cleanup)},
- {"vacuum_truncate", RELOPT_TYPE_BOOL,
- offsetof(StdRdOptions, vacuum_truncate), offsetof(StdRdOptions, vacuum_truncate_set)},
+ {"vacuum_truncate", RELOPT_TYPE_TERNARY,
+ offsetof(StdRdOptions, vacuum_truncate)},
{"vacuum_max_eager_freeze_failure_rate", RELOPT_TYPE_REAL,
offsetof(StdRdOptions, vacuum_max_eager_freeze_failure_rate)}
};
@@ -1993,7 +2071,6 @@ build_local_reloptions(local_relopts *relopts, Datum options, bool validate)
elems[i].optname = opt->option->name;
elems[i].opttype = opt->option->type;
elems[i].offset = opt->offset;
- elems[i].isset_offset = 0; /* not supported for local relopts yet */
i++;
}
diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c
index 733ef40ae7..977babff54 100644
--- a/src/backend/commands/vacuum.c
+++ b/src/backend/commands/vacuum.c
@@ -2221,9 +2221,9 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams params,
{
StdRdOptions *opts = (StdRdOptions *) rel->rd_options;
- if (opts && opts->vacuum_truncate_set)
+ if (opts && opts->vacuum_truncate != TERNARY_UNSET)
{
- if (opts->vacuum_truncate)
+ if (opts->vacuum_truncate == TERNARY_TRUE)
params.truncate = VACOPTVALUE_ENABLED;
else
params.truncate = VACOPTVALUE_DISABLED;
diff --git a/src/include/access/reloptions.h b/src/include/access/reloptions.h
index a604a4702c..a436697658 100644
--- a/src/include/access/reloptions.h
+++ b/src/include/access/reloptions.h
@@ -29,6 +29,7 @@
typedef enum relopt_type
{
RELOPT_TYPE_BOOL,
+ RELOPT_TYPE_TERNARY, /* on, off, unset */
RELOPT_TYPE_INT,
RELOPT_TYPE_REAL,
RELOPT_TYPE_ENUM,
@@ -80,6 +81,7 @@ typedef struct relopt_value
union
{
bool bool_val;
+ ternary ternary_val;
int int_val;
double real_val;
int enum_val;
@@ -94,6 +96,12 @@ typedef struct relopt_bool
bool default_val;
} relopt_bool;
+typedef struct relopt_rernary
+{
+ relopt_gen gen;
+ int default_val;
+} relopt_ternary;
+
typedef struct relopt_int
{
relopt_gen gen;
@@ -152,19 +160,6 @@ typedef struct
const char *optname; /* option's name */
relopt_type opttype; /* option's datatype */
int offset; /* offset of field in result struct */
-
- /*
- * isset_offset is an optional offset of a field in the result struct that
- * stores whether the option is explicitly set for the relation or if it
- * just picked up the default value. In most cases, this can be
- * accomplished by giving the reloption a special out-of-range default
- * value (e.g., some integer reloptions use -2), but this isn't always
- * possible. For example, a Boolean reloption cannot be given an
- * out-of-range default, so we need another way to discover the source of
- * its value. This offset is only used if given a value greater than
- * zero.
- */
- int isset_offset;
} relopt_parse_elt;
/* Local reloption definition */
@@ -195,6 +190,8 @@ typedef struct local_relopts
extern relopt_kind add_reloption_kind(void);
extern void add_bool_reloption(bits32 kinds, const char *name, const char *desc,
bool default_val, LOCKMODE lockmode);
+extern void add_ternary_reloption(bits32 kinds, const char *name,
+ const char *desc, int default_val, LOCKMODE lockmode);
extern void add_int_reloption(bits32 kinds, const char *name, const char *desc,
int default_val, int min_val, int max_val,
LOCKMODE lockmode);
@@ -214,6 +211,9 @@ extern void register_reloptions_validator(local_relopts *relopts,
extern void add_local_bool_reloption(local_relopts *relopts, const char *name,
const char *desc, bool default_val,
int offset);
+extern void add_local_ternary_reloption(local_relopts *relopts,
+ const char *name, const char *desc,
+ ternary default_val, int offset);
extern void add_local_int_reloption(local_relopts *relopts, const char *name,
const char *desc, int default_val,
int min_val, int max_val, int offset);
diff --git a/src/include/c.h b/src/include/c.h
index 39022f8a9d..9c59ccccf8 100644
--- a/src/include/c.h
+++ b/src/include/c.h
@@ -488,6 +488,22 @@ typedef void (*pg_funcptr_t) (void);
#include <stdbool.h>
+/*
+ * ternary
+ * Boolean value with an extrea "unset" option
+ *
+ * Ternary data type is used in relation options that can be "true", "false" or
+ * "unset". Since relation options are used deep inside the PostgreSQL code,
+ * this type is declared globally.
+*/
+
+typedef enum ternary
+{
+ TERNARY_FALSE = 0,
+ TERNARY_TRUE = 1,
+ TERNARY_UNSET = -1
+} ternary;
+
/* ----------------------------------------------------------------
* Section 3: standard system types
diff --git a/src/include/utils/rel.h b/src/include/utils/rel.h
index b552359915..7346d618f9 100644
--- a/src/include/utils/rel.h
+++ b/src/include/utils/rel.h
@@ -346,8 +346,7 @@ typedef struct StdRdOptions
bool user_catalog_table; /* use as an additional catalog relation */
int parallel_workers; /* max number of parallel workers */
StdRdOptIndexCleanup vacuum_index_cleanup; /* controls index vacuuming */
- bool vacuum_truncate; /* enables vacuum to truncate a relation */
- bool vacuum_truncate_set; /* whether vacuum_truncate is set */
+ ternary vacuum_truncate; /* enables vacuum to truncate a relation */
/*
* Fraction of pages in a relation that vacuum can eagerly scan and fail
--
2.39.2
--nextPart4155600.3ZeAukHxDK
Content-Disposition: attachment;
filename="v2a-0003-Add-alias-to-be-used-as-unset-state.patch"
Content-Transfer-Encoding: 7Bit
Content-Type: text/x-patch; charset="unicode-2-0-utf-8";
name="v2a-0003-Add-alias-to-be-used-as-unset-state.patch"
^ permalink raw reply [nested|flat] 868+ messages in thread
* [PATCH v2 2/4] Introduce ternary reloptions
@ 2025-09-04 14:41 Nikolay Shaplov <[email protected]>
0 siblings, 0 replies; 868+ messages in thread
From: Nikolay Shaplov @ 2025-09-04 14:41 UTC (permalink / raw)
Introduce ternary reloption as a replacement for current `vacuum_truncate`
implementation. Remove `vacuum_truncate_set` additional flag and using
`TERNARY_UNSET` value instead.
---
src/backend/access/common/reloptions.c | 129 ++++++++++++++++++++-----
src/backend/commands/vacuum.c | 4 +-
src/include/access/reloptions.h | 26 ++---
src/include/c.h | 16 +++
src/include/utils/rel.h | 3 +-
5 files changed, 135 insertions(+), 43 deletions(-)
diff --git a/src/backend/access/common/reloptions.c b/src/backend/access/common/reloptions.c
index 0af3fea68f..f543f11001 100644
--- a/src/backend/access/common/reloptions.c
+++ b/src/backend/access/common/reloptions.c
@@ -40,9 +40,9 @@
*
* To add an option:
*
- * (i) decide on a type (bool, integer, real, enum, string), name, default
- * value, upper and lower bounds (if applicable); for strings, consider a
- * validation routine.
+ * (i) decide on a type (bool, ternary, integer, real, enum, string), name,
+ * default value, upper and lower bounds (if applicable); for strings,
+ * consider a validation routine.
* (ii) add a record below (or use add_<type>_reloption).
* (iii) add it to the appropriate options struct (perhaps StdRdOptions)
* (iv) add it to the appropriate handling routine (perhaps
@@ -147,15 +147,6 @@ static relopt_bool boolRelOpts[] =
},
false
},
- {
- {
- "vacuum_truncate",
- "Enables vacuum to truncate empty pages at the end of this table",
- RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
- ShareUpdateExclusiveLock
- },
- true
- },
{
{
"deduplicate_items",
@@ -170,6 +161,21 @@ static relopt_bool boolRelOpts[] =
{{NULL}}
};
+static relopt_ternary ternaryRelOpts[] =
+{
+ {
+ {
+ "vacuum_truncate",
+ "Enables vacuum to truncate empty pages at the end of this table",
+ RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
+ ShareUpdateExclusiveLock
+ },
+ TERNARY_UNSET
+ },
+ /* list terminator */
+ {{NULL}}
+};
+
static relopt_int intRelOpts[] =
{
{
@@ -600,6 +606,13 @@ initialize_reloptions(void)
boolRelOpts[i].gen.lockmode));
j++;
}
+ for (i = 0; ternaryRelOpts[i].gen.name; i++)
+ {
+ Assert(DoLockModesConflict(ternaryRelOpts[i].gen.lockmode,
+ ternaryRelOpts[i].gen.lockmode));
+ j++;
+ }
+
for (i = 0; intRelOpts[i].gen.name; i++)
{
Assert(DoLockModesConflict(intRelOpts[i].gen.lockmode,
@@ -640,6 +653,14 @@ initialize_reloptions(void)
j++;
}
+ for (i = 0; ternaryRelOpts[i].gen.name; i++)
+ {
+ relOpts[j] = &ternaryRelOpts[i].gen;
+ relOpts[j]->type = RELOPT_TYPE_TERNARY;
+ relOpts[j]->namelen = strlen(relOpts[j]->name);
+ j++;
+ }
+
for (i = 0; intRelOpts[i].gen.name; i++)
{
relOpts[j] = &intRelOpts[i].gen;
@@ -800,6 +821,9 @@ allocate_reloption(bits32 kinds, int type, const char *name, const char *desc,
case RELOPT_TYPE_BOOL:
size = sizeof(relopt_bool);
break;
+ case RELOPT_TYPE_TERNARY:
+ size = sizeof(relopt_ternary);
+ break;
case RELOPT_TYPE_INT:
size = sizeof(relopt_int);
break;
@@ -883,6 +907,54 @@ add_local_bool_reloption(local_relopts *relopts, const char *name,
add_local_reloption(relopts, (relopt_gen *) newoption, offset);
}
+/*
+ * init_ternary_reloption
+ * Allocate and initialize a new ternary reloption
+ */
+static relopt_ternary *
+init_ternary_reloption(bits32 kinds, const char *name, const char *desc,
+ ternary default_val, LOCKMODE lockmode)
+{
+ relopt_ternary *newoption;
+
+ newoption = (relopt_ternary *) allocate_reloption(kinds,
+ RELOPT_TYPE_TERNARY, name, desc, lockmode);
+ newoption->default_val = default_val;
+
+ return newoption;
+}
+
+/*
+ * add_ternary_reloption
+ * Add a new ternary reloption
+ */
+void
+add_ternary_reloption(bits32 kinds, const char *name, const char *desc,
+ ternary default_val, LOCKMODE lockmode)
+{
+ relopt_ternary *newoption = init_ternary_reloption(kinds, name, desc,
+ default_val, lockmode);
+
+ add_reloption((relopt_gen *) newoption);
+}
+
+/*
+ * add_local_ternary_reloption
+ * Add a new ternary local reloption
+ *
+ * 'offset' is offset of ternary-typed field.
+ */
+void
+add_local_ternary_reloption(local_relopts *relopts, const char *name,
+ const char *desc, ternary default_val,
+ int offset)
+{
+ relopt_ternary *newoption = init_ternary_reloption(RELOPT_KIND_LOCAL,
+ name, desc,
+ default_val, 0);
+
+ add_local_reloption(relopts, (relopt_gen *) newoption, offset);
+}
/*
* init_real_reloption
@@ -1617,6 +1689,18 @@ parse_one_reloption(relopt_value *option, char *text_str, int text_len,
option->gen->name, value)));
}
break;
+ case RELOPT_TYPE_TERNARY:
+ {
+ bool b;
+ parsed = parse_bool(value, &b);
+ option->values.ternary_val = b ? TERNARY_TRUE : TERNARY_FALSE;
+ if (validate && !parsed)
+ ereport(ERROR,
+ (errcode(ERRCODE_INVALID_PARAMETER_VALUE),
+ errmsg("invalid value for ternary option \"%s\": %s",
+ option->gen->name, value)));
+ }
+ break;
case RELOPT_TYPE_INT:
{
relopt_int *optint = (relopt_int *) option->gen;
@@ -1780,17 +1864,6 @@ fillRelOptions(void *rdopts, Size basesize,
char *itempos = ((char *) rdopts) + elems[j].offset;
char *string_val;
- /*
- * If isset_offset is provided, store whether the reloption is
- * set there.
- */
- if (elems[j].isset_offset > 0)
- {
- char *setpos = ((char *) rdopts) + elems[j].isset_offset;
-
- *(bool *) setpos = options[i].isset;
- }
-
switch (options[i].gen->type)
{
case RELOPT_TYPE_BOOL:
@@ -1798,6 +1871,11 @@ fillRelOptions(void *rdopts, Size basesize,
options[i].values.bool_val :
((relopt_bool *) options[i].gen)->default_val;
break;
+ case RELOPT_TYPE_TERNARY:
+ *(ternary *) itempos = options[i].isset ?
+ options[i].values.ternary_val :
+ ((relopt_ternary *) options[i].gen)->default_val;
+ break;
case RELOPT_TYPE_INT:
*(int *) itempos = options[i].isset ?
options[i].values.int_val :
@@ -1912,8 +1990,8 @@ default_reloptions(Datum reloptions, bool validate, relopt_kind kind)
offsetof(StdRdOptions, parallel_workers)},
{"vacuum_index_cleanup", RELOPT_TYPE_ENUM,
offsetof(StdRdOptions, vacuum_index_cleanup)},
- {"vacuum_truncate", RELOPT_TYPE_BOOL,
- offsetof(StdRdOptions, vacuum_truncate), offsetof(StdRdOptions, vacuum_truncate_set)},
+ {"vacuum_truncate", RELOPT_TYPE_TERNARY,
+ offsetof(StdRdOptions, vacuum_truncate)},
{"vacuum_max_eager_freeze_failure_rate", RELOPT_TYPE_REAL,
offsetof(StdRdOptions, vacuum_max_eager_freeze_failure_rate)}
};
@@ -1993,7 +2071,6 @@ build_local_reloptions(local_relopts *relopts, Datum options, bool validate)
elems[i].optname = opt->option->name;
elems[i].opttype = opt->option->type;
elems[i].offset = opt->offset;
- elems[i].isset_offset = 0; /* not supported for local relopts yet */
i++;
}
diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c
index 733ef40ae7..977babff54 100644
--- a/src/backend/commands/vacuum.c
+++ b/src/backend/commands/vacuum.c
@@ -2221,9 +2221,9 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams params,
{
StdRdOptions *opts = (StdRdOptions *) rel->rd_options;
- if (opts && opts->vacuum_truncate_set)
+ if (opts && opts->vacuum_truncate != TERNARY_UNSET)
{
- if (opts->vacuum_truncate)
+ if (opts->vacuum_truncate == TERNARY_TRUE)
params.truncate = VACOPTVALUE_ENABLED;
else
params.truncate = VACOPTVALUE_DISABLED;
diff --git a/src/include/access/reloptions.h b/src/include/access/reloptions.h
index a604a4702c..a436697658 100644
--- a/src/include/access/reloptions.h
+++ b/src/include/access/reloptions.h
@@ -29,6 +29,7 @@
typedef enum relopt_type
{
RELOPT_TYPE_BOOL,
+ RELOPT_TYPE_TERNARY, /* on, off, unset */
RELOPT_TYPE_INT,
RELOPT_TYPE_REAL,
RELOPT_TYPE_ENUM,
@@ -80,6 +81,7 @@ typedef struct relopt_value
union
{
bool bool_val;
+ ternary ternary_val;
int int_val;
double real_val;
int enum_val;
@@ -94,6 +96,12 @@ typedef struct relopt_bool
bool default_val;
} relopt_bool;
+typedef struct relopt_rernary
+{
+ relopt_gen gen;
+ int default_val;
+} relopt_ternary;
+
typedef struct relopt_int
{
relopt_gen gen;
@@ -152,19 +160,6 @@ typedef struct
const char *optname; /* option's name */
relopt_type opttype; /* option's datatype */
int offset; /* offset of field in result struct */
-
- /*
- * isset_offset is an optional offset of a field in the result struct that
- * stores whether the option is explicitly set for the relation or if it
- * just picked up the default value. In most cases, this can be
- * accomplished by giving the reloption a special out-of-range default
- * value (e.g., some integer reloptions use -2), but this isn't always
- * possible. For example, a Boolean reloption cannot be given an
- * out-of-range default, so we need another way to discover the source of
- * its value. This offset is only used if given a value greater than
- * zero.
- */
- int isset_offset;
} relopt_parse_elt;
/* Local reloption definition */
@@ -195,6 +190,8 @@ typedef struct local_relopts
extern relopt_kind add_reloption_kind(void);
extern void add_bool_reloption(bits32 kinds, const char *name, const char *desc,
bool default_val, LOCKMODE lockmode);
+extern void add_ternary_reloption(bits32 kinds, const char *name,
+ const char *desc, int default_val, LOCKMODE lockmode);
extern void add_int_reloption(bits32 kinds, const char *name, const char *desc,
int default_val, int min_val, int max_val,
LOCKMODE lockmode);
@@ -214,6 +211,9 @@ extern void register_reloptions_validator(local_relopts *relopts,
extern void add_local_bool_reloption(local_relopts *relopts, const char *name,
const char *desc, bool default_val,
int offset);
+extern void add_local_ternary_reloption(local_relopts *relopts,
+ const char *name, const char *desc,
+ ternary default_val, int offset);
extern void add_local_int_reloption(local_relopts *relopts, const char *name,
const char *desc, int default_val,
int min_val, int max_val, int offset);
diff --git a/src/include/c.h b/src/include/c.h
index 39022f8a9d..9c59ccccf8 100644
--- a/src/include/c.h
+++ b/src/include/c.h
@@ -488,6 +488,22 @@ typedef void (*pg_funcptr_t) (void);
#include <stdbool.h>
+/*
+ * ternary
+ * Boolean value with an extrea "unset" option
+ *
+ * Ternary data type is used in relation options that can be "true", "false" or
+ * "unset". Since relation options are used deep inside the PostgreSQL code,
+ * this type is declared globally.
+*/
+
+typedef enum ternary
+{
+ TERNARY_FALSE = 0,
+ TERNARY_TRUE = 1,
+ TERNARY_UNSET = -1
+} ternary;
+
/* ----------------------------------------------------------------
* Section 3: standard system types
diff --git a/src/include/utils/rel.h b/src/include/utils/rel.h
index b552359915..7346d618f9 100644
--- a/src/include/utils/rel.h
+++ b/src/include/utils/rel.h
@@ -346,8 +346,7 @@ typedef struct StdRdOptions
bool user_catalog_table; /* use as an additional catalog relation */
int parallel_workers; /* max number of parallel workers */
StdRdOptIndexCleanup vacuum_index_cleanup; /* controls index vacuuming */
- bool vacuum_truncate; /* enables vacuum to truncate a relation */
- bool vacuum_truncate_set; /* whether vacuum_truncate is set */
+ ternary vacuum_truncate; /* enables vacuum to truncate a relation */
/*
* Fraction of pages in a relation that vacuum can eagerly scan and fail
--
2.39.2
--nextPart5129932.5fSG56mABF
Content-Disposition: attachment;
filename="v2-0003-Add-alias-to-be-used-as-unset-state.patch"
Content-Transfer-Encoding: 7Bit
Content-Type: text/x-patch; charset="unicode-2-0-utf-8";
name="v2-0003-Add-alias-to-be-used-as-unset-state.patch"
^ permalink raw reply [nested|flat] 868+ messages in thread
* [PATCH v2 2/4] Introduce ternary reloptions
@ 2025-09-04 14:41 Nikolay Shaplov <[email protected]>
0 siblings, 0 replies; 868+ messages in thread
From: Nikolay Shaplov @ 2025-09-04 14:41 UTC (permalink / raw)
Introduce ternary reloption as a replacement for current `vacuum_truncate`
implementation. Remove `vacuum_truncate_set` additional flag and using
`TERNARY_UNSET` value instead.
---
src/backend/access/common/reloptions.c | 129 ++++++++++++++++++++-----
src/backend/commands/vacuum.c | 4 +-
src/include/access/reloptions.h | 26 ++---
src/include/c.h | 16 +++
src/include/utils/rel.h | 3 +-
5 files changed, 135 insertions(+), 43 deletions(-)
diff --git a/src/backend/access/common/reloptions.c b/src/backend/access/common/reloptions.c
index 0af3fea68f..f543f11001 100644
--- a/src/backend/access/common/reloptions.c
+++ b/src/backend/access/common/reloptions.c
@@ -40,9 +40,9 @@
*
* To add an option:
*
- * (i) decide on a type (bool, integer, real, enum, string), name, default
- * value, upper and lower bounds (if applicable); for strings, consider a
- * validation routine.
+ * (i) decide on a type (bool, ternary, integer, real, enum, string), name,
+ * default value, upper and lower bounds (if applicable); for strings,
+ * consider a validation routine.
* (ii) add a record below (or use add_<type>_reloption).
* (iii) add it to the appropriate options struct (perhaps StdRdOptions)
* (iv) add it to the appropriate handling routine (perhaps
@@ -147,15 +147,6 @@ static relopt_bool boolRelOpts[] =
},
false
},
- {
- {
- "vacuum_truncate",
- "Enables vacuum to truncate empty pages at the end of this table",
- RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
- ShareUpdateExclusiveLock
- },
- true
- },
{
{
"deduplicate_items",
@@ -170,6 +161,21 @@ static relopt_bool boolRelOpts[] =
{{NULL}}
};
+static relopt_ternary ternaryRelOpts[] =
+{
+ {
+ {
+ "vacuum_truncate",
+ "Enables vacuum to truncate empty pages at the end of this table",
+ RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
+ ShareUpdateExclusiveLock
+ },
+ TERNARY_UNSET
+ },
+ /* list terminator */
+ {{NULL}}
+};
+
static relopt_int intRelOpts[] =
{
{
@@ -600,6 +606,13 @@ initialize_reloptions(void)
boolRelOpts[i].gen.lockmode));
j++;
}
+ for (i = 0; ternaryRelOpts[i].gen.name; i++)
+ {
+ Assert(DoLockModesConflict(ternaryRelOpts[i].gen.lockmode,
+ ternaryRelOpts[i].gen.lockmode));
+ j++;
+ }
+
for (i = 0; intRelOpts[i].gen.name; i++)
{
Assert(DoLockModesConflict(intRelOpts[i].gen.lockmode,
@@ -640,6 +653,14 @@ initialize_reloptions(void)
j++;
}
+ for (i = 0; ternaryRelOpts[i].gen.name; i++)
+ {
+ relOpts[j] = &ternaryRelOpts[i].gen;
+ relOpts[j]->type = RELOPT_TYPE_TERNARY;
+ relOpts[j]->namelen = strlen(relOpts[j]->name);
+ j++;
+ }
+
for (i = 0; intRelOpts[i].gen.name; i++)
{
relOpts[j] = &intRelOpts[i].gen;
@@ -800,6 +821,9 @@ allocate_reloption(bits32 kinds, int type, const char *name, const char *desc,
case RELOPT_TYPE_BOOL:
size = sizeof(relopt_bool);
break;
+ case RELOPT_TYPE_TERNARY:
+ size = sizeof(relopt_ternary);
+ break;
case RELOPT_TYPE_INT:
size = sizeof(relopt_int);
break;
@@ -883,6 +907,54 @@ add_local_bool_reloption(local_relopts *relopts, const char *name,
add_local_reloption(relopts, (relopt_gen *) newoption, offset);
}
+/*
+ * init_ternary_reloption
+ * Allocate and initialize a new ternary reloption
+ */
+static relopt_ternary *
+init_ternary_reloption(bits32 kinds, const char *name, const char *desc,
+ ternary default_val, LOCKMODE lockmode)
+{
+ relopt_ternary *newoption;
+
+ newoption = (relopt_ternary *) allocate_reloption(kinds,
+ RELOPT_TYPE_TERNARY, name, desc, lockmode);
+ newoption->default_val = default_val;
+
+ return newoption;
+}
+
+/*
+ * add_ternary_reloption
+ * Add a new ternary reloption
+ */
+void
+add_ternary_reloption(bits32 kinds, const char *name, const char *desc,
+ ternary default_val, LOCKMODE lockmode)
+{
+ relopt_ternary *newoption = init_ternary_reloption(kinds, name, desc,
+ default_val, lockmode);
+
+ add_reloption((relopt_gen *) newoption);
+}
+
+/*
+ * add_local_ternary_reloption
+ * Add a new ternary local reloption
+ *
+ * 'offset' is offset of ternary-typed field.
+ */
+void
+add_local_ternary_reloption(local_relopts *relopts, const char *name,
+ const char *desc, ternary default_val,
+ int offset)
+{
+ relopt_ternary *newoption = init_ternary_reloption(RELOPT_KIND_LOCAL,
+ name, desc,
+ default_val, 0);
+
+ add_local_reloption(relopts, (relopt_gen *) newoption, offset);
+}
/*
* init_real_reloption
@@ -1617,6 +1689,18 @@ parse_one_reloption(relopt_value *option, char *text_str, int text_len,
option->gen->name, value)));
}
break;
+ case RELOPT_TYPE_TERNARY:
+ {
+ bool b;
+ parsed = parse_bool(value, &b);
+ option->values.ternary_val = b ? TERNARY_TRUE : TERNARY_FALSE;
+ if (validate && !parsed)
+ ereport(ERROR,
+ (errcode(ERRCODE_INVALID_PARAMETER_VALUE),
+ errmsg("invalid value for ternary option \"%s\": %s",
+ option->gen->name, value)));
+ }
+ break;
case RELOPT_TYPE_INT:
{
relopt_int *optint = (relopt_int *) option->gen;
@@ -1780,17 +1864,6 @@ fillRelOptions(void *rdopts, Size basesize,
char *itempos = ((char *) rdopts) + elems[j].offset;
char *string_val;
- /*
- * If isset_offset is provided, store whether the reloption is
- * set there.
- */
- if (elems[j].isset_offset > 0)
- {
- char *setpos = ((char *) rdopts) + elems[j].isset_offset;
-
- *(bool *) setpos = options[i].isset;
- }
-
switch (options[i].gen->type)
{
case RELOPT_TYPE_BOOL:
@@ -1798,6 +1871,11 @@ fillRelOptions(void *rdopts, Size basesize,
options[i].values.bool_val :
((relopt_bool *) options[i].gen)->default_val;
break;
+ case RELOPT_TYPE_TERNARY:
+ *(ternary *) itempos = options[i].isset ?
+ options[i].values.ternary_val :
+ ((relopt_ternary *) options[i].gen)->default_val;
+ break;
case RELOPT_TYPE_INT:
*(int *) itempos = options[i].isset ?
options[i].values.int_val :
@@ -1912,8 +1990,8 @@ default_reloptions(Datum reloptions, bool validate, relopt_kind kind)
offsetof(StdRdOptions, parallel_workers)},
{"vacuum_index_cleanup", RELOPT_TYPE_ENUM,
offsetof(StdRdOptions, vacuum_index_cleanup)},
- {"vacuum_truncate", RELOPT_TYPE_BOOL,
- offsetof(StdRdOptions, vacuum_truncate), offsetof(StdRdOptions, vacuum_truncate_set)},
+ {"vacuum_truncate", RELOPT_TYPE_TERNARY,
+ offsetof(StdRdOptions, vacuum_truncate)},
{"vacuum_max_eager_freeze_failure_rate", RELOPT_TYPE_REAL,
offsetof(StdRdOptions, vacuum_max_eager_freeze_failure_rate)}
};
@@ -1993,7 +2071,6 @@ build_local_reloptions(local_relopts *relopts, Datum options, bool validate)
elems[i].optname = opt->option->name;
elems[i].opttype = opt->option->type;
elems[i].offset = opt->offset;
- elems[i].isset_offset = 0; /* not supported for local relopts yet */
i++;
}
diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c
index 733ef40ae7..977babff54 100644
--- a/src/backend/commands/vacuum.c
+++ b/src/backend/commands/vacuum.c
@@ -2221,9 +2221,9 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams params,
{
StdRdOptions *opts = (StdRdOptions *) rel->rd_options;
- if (opts && opts->vacuum_truncate_set)
+ if (opts && opts->vacuum_truncate != TERNARY_UNSET)
{
- if (opts->vacuum_truncate)
+ if (opts->vacuum_truncate == TERNARY_TRUE)
params.truncate = VACOPTVALUE_ENABLED;
else
params.truncate = VACOPTVALUE_DISABLED;
diff --git a/src/include/access/reloptions.h b/src/include/access/reloptions.h
index a604a4702c..a436697658 100644
--- a/src/include/access/reloptions.h
+++ b/src/include/access/reloptions.h
@@ -29,6 +29,7 @@
typedef enum relopt_type
{
RELOPT_TYPE_BOOL,
+ RELOPT_TYPE_TERNARY, /* on, off, unset */
RELOPT_TYPE_INT,
RELOPT_TYPE_REAL,
RELOPT_TYPE_ENUM,
@@ -80,6 +81,7 @@ typedef struct relopt_value
union
{
bool bool_val;
+ ternary ternary_val;
int int_val;
double real_val;
int enum_val;
@@ -94,6 +96,12 @@ typedef struct relopt_bool
bool default_val;
} relopt_bool;
+typedef struct relopt_rernary
+{
+ relopt_gen gen;
+ int default_val;
+} relopt_ternary;
+
typedef struct relopt_int
{
relopt_gen gen;
@@ -152,19 +160,6 @@ typedef struct
const char *optname; /* option's name */
relopt_type opttype; /* option's datatype */
int offset; /* offset of field in result struct */
-
- /*
- * isset_offset is an optional offset of a field in the result struct that
- * stores whether the option is explicitly set for the relation or if it
- * just picked up the default value. In most cases, this can be
- * accomplished by giving the reloption a special out-of-range default
- * value (e.g., some integer reloptions use -2), but this isn't always
- * possible. For example, a Boolean reloption cannot be given an
- * out-of-range default, so we need another way to discover the source of
- * its value. This offset is only used if given a value greater than
- * zero.
- */
- int isset_offset;
} relopt_parse_elt;
/* Local reloption definition */
@@ -195,6 +190,8 @@ typedef struct local_relopts
extern relopt_kind add_reloption_kind(void);
extern void add_bool_reloption(bits32 kinds, const char *name, const char *desc,
bool default_val, LOCKMODE lockmode);
+extern void add_ternary_reloption(bits32 kinds, const char *name,
+ const char *desc, int default_val, LOCKMODE lockmode);
extern void add_int_reloption(bits32 kinds, const char *name, const char *desc,
int default_val, int min_val, int max_val,
LOCKMODE lockmode);
@@ -214,6 +211,9 @@ extern void register_reloptions_validator(local_relopts *relopts,
extern void add_local_bool_reloption(local_relopts *relopts, const char *name,
const char *desc, bool default_val,
int offset);
+extern void add_local_ternary_reloption(local_relopts *relopts,
+ const char *name, const char *desc,
+ ternary default_val, int offset);
extern void add_local_int_reloption(local_relopts *relopts, const char *name,
const char *desc, int default_val,
int min_val, int max_val, int offset);
diff --git a/src/include/c.h b/src/include/c.h
index 39022f8a9d..9c59ccccf8 100644
--- a/src/include/c.h
+++ b/src/include/c.h
@@ -488,6 +488,22 @@ typedef void (*pg_funcptr_t) (void);
#include <stdbool.h>
+/*
+ * ternary
+ * Boolean value with an extrea "unset" option
+ *
+ * Ternary data type is used in relation options that can be "true", "false" or
+ * "unset". Since relation options are used deep inside the PostgreSQL code,
+ * this type is declared globally.
+*/
+
+typedef enum ternary
+{
+ TERNARY_FALSE = 0,
+ TERNARY_TRUE = 1,
+ TERNARY_UNSET = -1
+} ternary;
+
/* ----------------------------------------------------------------
* Section 3: standard system types
diff --git a/src/include/utils/rel.h b/src/include/utils/rel.h
index b552359915..7346d618f9 100644
--- a/src/include/utils/rel.h
+++ b/src/include/utils/rel.h
@@ -346,8 +346,7 @@ typedef struct StdRdOptions
bool user_catalog_table; /* use as an additional catalog relation */
int parallel_workers; /* max number of parallel workers */
StdRdOptIndexCleanup vacuum_index_cleanup; /* controls index vacuuming */
- bool vacuum_truncate; /* enables vacuum to truncate a relation */
- bool vacuum_truncate_set; /* whether vacuum_truncate is set */
+ ternary vacuum_truncate; /* enables vacuum to truncate a relation */
/*
* Fraction of pages in a relation that vacuum can eagerly scan and fail
--
2.39.2
--nextPart5129932.5fSG56mABF
Content-Disposition: attachment;
filename="v2-0003-Add-alias-to-be-used-as-unset-state.patch"
Content-Transfer-Encoding: 7Bit
Content-Type: text/x-patch; charset="unicode-2-0-utf-8";
name="v2-0003-Add-alias-to-be-used-as-unset-state.patch"
^ permalink raw reply [nested|flat] 868+ messages in thread
* [PATCH v2a 2/4] Introduce ternary reloptions
@ 2025-09-04 14:41 Nikolay Shaplov <[email protected]>
0 siblings, 0 replies; 868+ messages in thread
From: Nikolay Shaplov @ 2025-09-04 14:41 UTC (permalink / raw)
Introduce ternary reloption as a replacement for current `vacuum_truncate`
implementation. Remove `vacuum_truncate_set` additional flag and using
`TERNARY_UNSET` value instead.
---
src/backend/access/common/reloptions.c | 129 ++++++++++++++++++++-----
src/backend/commands/vacuum.c | 4 +-
src/include/access/reloptions.h | 26 ++---
src/include/c.h | 16 +++
src/include/utils/rel.h | 3 +-
5 files changed, 135 insertions(+), 43 deletions(-)
diff --git a/src/backend/access/common/reloptions.c b/src/backend/access/common/reloptions.c
index 0af3fea68f..f543f11001 100644
--- a/src/backend/access/common/reloptions.c
+++ b/src/backend/access/common/reloptions.c
@@ -40,9 +40,9 @@
*
* To add an option:
*
- * (i) decide on a type (bool, integer, real, enum, string), name, default
- * value, upper and lower bounds (if applicable); for strings, consider a
- * validation routine.
+ * (i) decide on a type (bool, ternary, integer, real, enum, string), name,
+ * default value, upper and lower bounds (if applicable); for strings,
+ * consider a validation routine.
* (ii) add a record below (or use add_<type>_reloption).
* (iii) add it to the appropriate options struct (perhaps StdRdOptions)
* (iv) add it to the appropriate handling routine (perhaps
@@ -147,15 +147,6 @@ static relopt_bool boolRelOpts[] =
},
false
},
- {
- {
- "vacuum_truncate",
- "Enables vacuum to truncate empty pages at the end of this table",
- RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
- ShareUpdateExclusiveLock
- },
- true
- },
{
{
"deduplicate_items",
@@ -170,6 +161,21 @@ static relopt_bool boolRelOpts[] =
{{NULL}}
};
+static relopt_ternary ternaryRelOpts[] =
+{
+ {
+ {
+ "vacuum_truncate",
+ "Enables vacuum to truncate empty pages at the end of this table",
+ RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
+ ShareUpdateExclusiveLock
+ },
+ TERNARY_UNSET
+ },
+ /* list terminator */
+ {{NULL}}
+};
+
static relopt_int intRelOpts[] =
{
{
@@ -600,6 +606,13 @@ initialize_reloptions(void)
boolRelOpts[i].gen.lockmode));
j++;
}
+ for (i = 0; ternaryRelOpts[i].gen.name; i++)
+ {
+ Assert(DoLockModesConflict(ternaryRelOpts[i].gen.lockmode,
+ ternaryRelOpts[i].gen.lockmode));
+ j++;
+ }
+
for (i = 0; intRelOpts[i].gen.name; i++)
{
Assert(DoLockModesConflict(intRelOpts[i].gen.lockmode,
@@ -640,6 +653,14 @@ initialize_reloptions(void)
j++;
}
+ for (i = 0; ternaryRelOpts[i].gen.name; i++)
+ {
+ relOpts[j] = &ternaryRelOpts[i].gen;
+ relOpts[j]->type = RELOPT_TYPE_TERNARY;
+ relOpts[j]->namelen = strlen(relOpts[j]->name);
+ j++;
+ }
+
for (i = 0; intRelOpts[i].gen.name; i++)
{
relOpts[j] = &intRelOpts[i].gen;
@@ -800,6 +821,9 @@ allocate_reloption(bits32 kinds, int type, const char *name, const char *desc,
case RELOPT_TYPE_BOOL:
size = sizeof(relopt_bool);
break;
+ case RELOPT_TYPE_TERNARY:
+ size = sizeof(relopt_ternary);
+ break;
case RELOPT_TYPE_INT:
size = sizeof(relopt_int);
break;
@@ -883,6 +907,54 @@ add_local_bool_reloption(local_relopts *relopts, const char *name,
add_local_reloption(relopts, (relopt_gen *) newoption, offset);
}
+/*
+ * init_ternary_reloption
+ * Allocate and initialize a new ternary reloption
+ */
+static relopt_ternary *
+init_ternary_reloption(bits32 kinds, const char *name, const char *desc,
+ ternary default_val, LOCKMODE lockmode)
+{
+ relopt_ternary *newoption;
+
+ newoption = (relopt_ternary *) allocate_reloption(kinds,
+ RELOPT_TYPE_TERNARY, name, desc, lockmode);
+ newoption->default_val = default_val;
+
+ return newoption;
+}
+
+/*
+ * add_ternary_reloption
+ * Add a new ternary reloption
+ */
+void
+add_ternary_reloption(bits32 kinds, const char *name, const char *desc,
+ ternary default_val, LOCKMODE lockmode)
+{
+ relopt_ternary *newoption = init_ternary_reloption(kinds, name, desc,
+ default_val, lockmode);
+
+ add_reloption((relopt_gen *) newoption);
+}
+
+/*
+ * add_local_ternary_reloption
+ * Add a new ternary local reloption
+ *
+ * 'offset' is offset of ternary-typed field.
+ */
+void
+add_local_ternary_reloption(local_relopts *relopts, const char *name,
+ const char *desc, ternary default_val,
+ int offset)
+{
+ relopt_ternary *newoption = init_ternary_reloption(RELOPT_KIND_LOCAL,
+ name, desc,
+ default_val, 0);
+
+ add_local_reloption(relopts, (relopt_gen *) newoption, offset);
+}
/*
* init_real_reloption
@@ -1617,6 +1689,18 @@ parse_one_reloption(relopt_value *option, char *text_str, int text_len,
option->gen->name, value)));
}
break;
+ case RELOPT_TYPE_TERNARY:
+ {
+ bool b;
+ parsed = parse_bool(value, &b);
+ option->values.ternary_val = b ? TERNARY_TRUE : TERNARY_FALSE;
+ if (validate && !parsed)
+ ereport(ERROR,
+ (errcode(ERRCODE_INVALID_PARAMETER_VALUE),
+ errmsg("invalid value for ternary option \"%s\": %s",
+ option->gen->name, value)));
+ }
+ break;
case RELOPT_TYPE_INT:
{
relopt_int *optint = (relopt_int *) option->gen;
@@ -1780,17 +1864,6 @@ fillRelOptions(void *rdopts, Size basesize,
char *itempos = ((char *) rdopts) + elems[j].offset;
char *string_val;
- /*
- * If isset_offset is provided, store whether the reloption is
- * set there.
- */
- if (elems[j].isset_offset > 0)
- {
- char *setpos = ((char *) rdopts) + elems[j].isset_offset;
-
- *(bool *) setpos = options[i].isset;
- }
-
switch (options[i].gen->type)
{
case RELOPT_TYPE_BOOL:
@@ -1798,6 +1871,11 @@ fillRelOptions(void *rdopts, Size basesize,
options[i].values.bool_val :
((relopt_bool *) options[i].gen)->default_val;
break;
+ case RELOPT_TYPE_TERNARY:
+ *(ternary *) itempos = options[i].isset ?
+ options[i].values.ternary_val :
+ ((relopt_ternary *) options[i].gen)->default_val;
+ break;
case RELOPT_TYPE_INT:
*(int *) itempos = options[i].isset ?
options[i].values.int_val :
@@ -1912,8 +1990,8 @@ default_reloptions(Datum reloptions, bool validate, relopt_kind kind)
offsetof(StdRdOptions, parallel_workers)},
{"vacuum_index_cleanup", RELOPT_TYPE_ENUM,
offsetof(StdRdOptions, vacuum_index_cleanup)},
- {"vacuum_truncate", RELOPT_TYPE_BOOL,
- offsetof(StdRdOptions, vacuum_truncate), offsetof(StdRdOptions, vacuum_truncate_set)},
+ {"vacuum_truncate", RELOPT_TYPE_TERNARY,
+ offsetof(StdRdOptions, vacuum_truncate)},
{"vacuum_max_eager_freeze_failure_rate", RELOPT_TYPE_REAL,
offsetof(StdRdOptions, vacuum_max_eager_freeze_failure_rate)}
};
@@ -1993,7 +2071,6 @@ build_local_reloptions(local_relopts *relopts, Datum options, bool validate)
elems[i].optname = opt->option->name;
elems[i].opttype = opt->option->type;
elems[i].offset = opt->offset;
- elems[i].isset_offset = 0; /* not supported for local relopts yet */
i++;
}
diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c
index 733ef40ae7..977babff54 100644
--- a/src/backend/commands/vacuum.c
+++ b/src/backend/commands/vacuum.c
@@ -2221,9 +2221,9 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams params,
{
StdRdOptions *opts = (StdRdOptions *) rel->rd_options;
- if (opts && opts->vacuum_truncate_set)
+ if (opts && opts->vacuum_truncate != TERNARY_UNSET)
{
- if (opts->vacuum_truncate)
+ if (opts->vacuum_truncate == TERNARY_TRUE)
params.truncate = VACOPTVALUE_ENABLED;
else
params.truncate = VACOPTVALUE_DISABLED;
diff --git a/src/include/access/reloptions.h b/src/include/access/reloptions.h
index a604a4702c..a436697658 100644
--- a/src/include/access/reloptions.h
+++ b/src/include/access/reloptions.h
@@ -29,6 +29,7 @@
typedef enum relopt_type
{
RELOPT_TYPE_BOOL,
+ RELOPT_TYPE_TERNARY, /* on, off, unset */
RELOPT_TYPE_INT,
RELOPT_TYPE_REAL,
RELOPT_TYPE_ENUM,
@@ -80,6 +81,7 @@ typedef struct relopt_value
union
{
bool bool_val;
+ ternary ternary_val;
int int_val;
double real_val;
int enum_val;
@@ -94,6 +96,12 @@ typedef struct relopt_bool
bool default_val;
} relopt_bool;
+typedef struct relopt_rernary
+{
+ relopt_gen gen;
+ int default_val;
+} relopt_ternary;
+
typedef struct relopt_int
{
relopt_gen gen;
@@ -152,19 +160,6 @@ typedef struct
const char *optname; /* option's name */
relopt_type opttype; /* option's datatype */
int offset; /* offset of field in result struct */
-
- /*
- * isset_offset is an optional offset of a field in the result struct that
- * stores whether the option is explicitly set for the relation or if it
- * just picked up the default value. In most cases, this can be
- * accomplished by giving the reloption a special out-of-range default
- * value (e.g., some integer reloptions use -2), but this isn't always
- * possible. For example, a Boolean reloption cannot be given an
- * out-of-range default, so we need another way to discover the source of
- * its value. This offset is only used if given a value greater than
- * zero.
- */
- int isset_offset;
} relopt_parse_elt;
/* Local reloption definition */
@@ -195,6 +190,8 @@ typedef struct local_relopts
extern relopt_kind add_reloption_kind(void);
extern void add_bool_reloption(bits32 kinds, const char *name, const char *desc,
bool default_val, LOCKMODE lockmode);
+extern void add_ternary_reloption(bits32 kinds, const char *name,
+ const char *desc, int default_val, LOCKMODE lockmode);
extern void add_int_reloption(bits32 kinds, const char *name, const char *desc,
int default_val, int min_val, int max_val,
LOCKMODE lockmode);
@@ -214,6 +211,9 @@ extern void register_reloptions_validator(local_relopts *relopts,
extern void add_local_bool_reloption(local_relopts *relopts, const char *name,
const char *desc, bool default_val,
int offset);
+extern void add_local_ternary_reloption(local_relopts *relopts,
+ const char *name, const char *desc,
+ ternary default_val, int offset);
extern void add_local_int_reloption(local_relopts *relopts, const char *name,
const char *desc, int default_val,
int min_val, int max_val, int offset);
diff --git a/src/include/c.h b/src/include/c.h
index 39022f8a9d..9c59ccccf8 100644
--- a/src/include/c.h
+++ b/src/include/c.h
@@ -488,6 +488,22 @@ typedef void (*pg_funcptr_t) (void);
#include <stdbool.h>
+/*
+ * ternary
+ * Boolean value with an extrea "unset" option
+ *
+ * Ternary data type is used in relation options that can be "true", "false" or
+ * "unset". Since relation options are used deep inside the PostgreSQL code,
+ * this type is declared globally.
+*/
+
+typedef enum ternary
+{
+ TERNARY_FALSE = 0,
+ TERNARY_TRUE = 1,
+ TERNARY_UNSET = -1
+} ternary;
+
/* ----------------------------------------------------------------
* Section 3: standard system types
diff --git a/src/include/utils/rel.h b/src/include/utils/rel.h
index b552359915..7346d618f9 100644
--- a/src/include/utils/rel.h
+++ b/src/include/utils/rel.h
@@ -346,8 +346,7 @@ typedef struct StdRdOptions
bool user_catalog_table; /* use as an additional catalog relation */
int parallel_workers; /* max number of parallel workers */
StdRdOptIndexCleanup vacuum_index_cleanup; /* controls index vacuuming */
- bool vacuum_truncate; /* enables vacuum to truncate a relation */
- bool vacuum_truncate_set; /* whether vacuum_truncate is set */
+ ternary vacuum_truncate; /* enables vacuum to truncate a relation */
/*
* Fraction of pages in a relation that vacuum can eagerly scan and fail
--
2.39.2
--nextPart4155600.3ZeAukHxDK
Content-Disposition: attachment;
filename="v2a-0003-Add-alias-to-be-used-as-unset-state.patch"
Content-Transfer-Encoding: 7Bit
Content-Type: text/x-patch; charset="unicode-2-0-utf-8";
name="v2a-0003-Add-alias-to-be-used-as-unset-state.patch"
^ permalink raw reply [nested|flat] 868+ messages in thread
* [PATCH v2 2/4] Introduce ternary reloptions
@ 2025-09-04 14:41 Nikolay Shaplov <[email protected]>
0 siblings, 0 replies; 868+ messages in thread
From: Nikolay Shaplov @ 2025-09-04 14:41 UTC (permalink / raw)
Introduce ternary reloption as a replacement for current `vacuum_truncate`
implementation. Remove `vacuum_truncate_set` additional flag and using
`TERNARY_UNSET` value instead.
---
src/backend/access/common/reloptions.c | 129 ++++++++++++++++++++-----
src/backend/commands/vacuum.c | 4 +-
src/include/access/reloptions.h | 26 ++---
src/include/c.h | 16 +++
src/include/utils/rel.h | 3 +-
5 files changed, 135 insertions(+), 43 deletions(-)
diff --git a/src/backend/access/common/reloptions.c b/src/backend/access/common/reloptions.c
index 0af3fea68f..f543f11001 100644
--- a/src/backend/access/common/reloptions.c
+++ b/src/backend/access/common/reloptions.c
@@ -40,9 +40,9 @@
*
* To add an option:
*
- * (i) decide on a type (bool, integer, real, enum, string), name, default
- * value, upper and lower bounds (if applicable); for strings, consider a
- * validation routine.
+ * (i) decide on a type (bool, ternary, integer, real, enum, string), name,
+ * default value, upper and lower bounds (if applicable); for strings,
+ * consider a validation routine.
* (ii) add a record below (or use add_<type>_reloption).
* (iii) add it to the appropriate options struct (perhaps StdRdOptions)
* (iv) add it to the appropriate handling routine (perhaps
@@ -147,15 +147,6 @@ static relopt_bool boolRelOpts[] =
},
false
},
- {
- {
- "vacuum_truncate",
- "Enables vacuum to truncate empty pages at the end of this table",
- RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
- ShareUpdateExclusiveLock
- },
- true
- },
{
{
"deduplicate_items",
@@ -170,6 +161,21 @@ static relopt_bool boolRelOpts[] =
{{NULL}}
};
+static relopt_ternary ternaryRelOpts[] =
+{
+ {
+ {
+ "vacuum_truncate",
+ "Enables vacuum to truncate empty pages at the end of this table",
+ RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
+ ShareUpdateExclusiveLock
+ },
+ TERNARY_UNSET
+ },
+ /* list terminator */
+ {{NULL}}
+};
+
static relopt_int intRelOpts[] =
{
{
@@ -600,6 +606,13 @@ initialize_reloptions(void)
boolRelOpts[i].gen.lockmode));
j++;
}
+ for (i = 0; ternaryRelOpts[i].gen.name; i++)
+ {
+ Assert(DoLockModesConflict(ternaryRelOpts[i].gen.lockmode,
+ ternaryRelOpts[i].gen.lockmode));
+ j++;
+ }
+
for (i = 0; intRelOpts[i].gen.name; i++)
{
Assert(DoLockModesConflict(intRelOpts[i].gen.lockmode,
@@ -640,6 +653,14 @@ initialize_reloptions(void)
j++;
}
+ for (i = 0; ternaryRelOpts[i].gen.name; i++)
+ {
+ relOpts[j] = &ternaryRelOpts[i].gen;
+ relOpts[j]->type = RELOPT_TYPE_TERNARY;
+ relOpts[j]->namelen = strlen(relOpts[j]->name);
+ j++;
+ }
+
for (i = 0; intRelOpts[i].gen.name; i++)
{
relOpts[j] = &intRelOpts[i].gen;
@@ -800,6 +821,9 @@ allocate_reloption(bits32 kinds, int type, const char *name, const char *desc,
case RELOPT_TYPE_BOOL:
size = sizeof(relopt_bool);
break;
+ case RELOPT_TYPE_TERNARY:
+ size = sizeof(relopt_ternary);
+ break;
case RELOPT_TYPE_INT:
size = sizeof(relopt_int);
break;
@@ -883,6 +907,54 @@ add_local_bool_reloption(local_relopts *relopts, const char *name,
add_local_reloption(relopts, (relopt_gen *) newoption, offset);
}
+/*
+ * init_ternary_reloption
+ * Allocate and initialize a new ternary reloption
+ */
+static relopt_ternary *
+init_ternary_reloption(bits32 kinds, const char *name, const char *desc,
+ ternary default_val, LOCKMODE lockmode)
+{
+ relopt_ternary *newoption;
+
+ newoption = (relopt_ternary *) allocate_reloption(kinds,
+ RELOPT_TYPE_TERNARY, name, desc, lockmode);
+ newoption->default_val = default_val;
+
+ return newoption;
+}
+
+/*
+ * add_ternary_reloption
+ * Add a new ternary reloption
+ */
+void
+add_ternary_reloption(bits32 kinds, const char *name, const char *desc,
+ ternary default_val, LOCKMODE lockmode)
+{
+ relopt_ternary *newoption = init_ternary_reloption(kinds, name, desc,
+ default_val, lockmode);
+
+ add_reloption((relopt_gen *) newoption);
+}
+
+/*
+ * add_local_ternary_reloption
+ * Add a new ternary local reloption
+ *
+ * 'offset' is offset of ternary-typed field.
+ */
+void
+add_local_ternary_reloption(local_relopts *relopts, const char *name,
+ const char *desc, ternary default_val,
+ int offset)
+{
+ relopt_ternary *newoption = init_ternary_reloption(RELOPT_KIND_LOCAL,
+ name, desc,
+ default_val, 0);
+
+ add_local_reloption(relopts, (relopt_gen *) newoption, offset);
+}
/*
* init_real_reloption
@@ -1617,6 +1689,18 @@ parse_one_reloption(relopt_value *option, char *text_str, int text_len,
option->gen->name, value)));
}
break;
+ case RELOPT_TYPE_TERNARY:
+ {
+ bool b;
+ parsed = parse_bool(value, &b);
+ option->values.ternary_val = b ? TERNARY_TRUE : TERNARY_FALSE;
+ if (validate && !parsed)
+ ereport(ERROR,
+ (errcode(ERRCODE_INVALID_PARAMETER_VALUE),
+ errmsg("invalid value for ternary option \"%s\": %s",
+ option->gen->name, value)));
+ }
+ break;
case RELOPT_TYPE_INT:
{
relopt_int *optint = (relopt_int *) option->gen;
@@ -1780,17 +1864,6 @@ fillRelOptions(void *rdopts, Size basesize,
char *itempos = ((char *) rdopts) + elems[j].offset;
char *string_val;
- /*
- * If isset_offset is provided, store whether the reloption is
- * set there.
- */
- if (elems[j].isset_offset > 0)
- {
- char *setpos = ((char *) rdopts) + elems[j].isset_offset;
-
- *(bool *) setpos = options[i].isset;
- }
-
switch (options[i].gen->type)
{
case RELOPT_TYPE_BOOL:
@@ -1798,6 +1871,11 @@ fillRelOptions(void *rdopts, Size basesize,
options[i].values.bool_val :
((relopt_bool *) options[i].gen)->default_val;
break;
+ case RELOPT_TYPE_TERNARY:
+ *(ternary *) itempos = options[i].isset ?
+ options[i].values.ternary_val :
+ ((relopt_ternary *) options[i].gen)->default_val;
+ break;
case RELOPT_TYPE_INT:
*(int *) itempos = options[i].isset ?
options[i].values.int_val :
@@ -1912,8 +1990,8 @@ default_reloptions(Datum reloptions, bool validate, relopt_kind kind)
offsetof(StdRdOptions, parallel_workers)},
{"vacuum_index_cleanup", RELOPT_TYPE_ENUM,
offsetof(StdRdOptions, vacuum_index_cleanup)},
- {"vacuum_truncate", RELOPT_TYPE_BOOL,
- offsetof(StdRdOptions, vacuum_truncate), offsetof(StdRdOptions, vacuum_truncate_set)},
+ {"vacuum_truncate", RELOPT_TYPE_TERNARY,
+ offsetof(StdRdOptions, vacuum_truncate)},
{"vacuum_max_eager_freeze_failure_rate", RELOPT_TYPE_REAL,
offsetof(StdRdOptions, vacuum_max_eager_freeze_failure_rate)}
};
@@ -1993,7 +2071,6 @@ build_local_reloptions(local_relopts *relopts, Datum options, bool validate)
elems[i].optname = opt->option->name;
elems[i].opttype = opt->option->type;
elems[i].offset = opt->offset;
- elems[i].isset_offset = 0; /* not supported for local relopts yet */
i++;
}
diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c
index 733ef40ae7..977babff54 100644
--- a/src/backend/commands/vacuum.c
+++ b/src/backend/commands/vacuum.c
@@ -2221,9 +2221,9 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams params,
{
StdRdOptions *opts = (StdRdOptions *) rel->rd_options;
- if (opts && opts->vacuum_truncate_set)
+ if (opts && opts->vacuum_truncate != TERNARY_UNSET)
{
- if (opts->vacuum_truncate)
+ if (opts->vacuum_truncate == TERNARY_TRUE)
params.truncate = VACOPTVALUE_ENABLED;
else
params.truncate = VACOPTVALUE_DISABLED;
diff --git a/src/include/access/reloptions.h b/src/include/access/reloptions.h
index a604a4702c..a436697658 100644
--- a/src/include/access/reloptions.h
+++ b/src/include/access/reloptions.h
@@ -29,6 +29,7 @@
typedef enum relopt_type
{
RELOPT_TYPE_BOOL,
+ RELOPT_TYPE_TERNARY, /* on, off, unset */
RELOPT_TYPE_INT,
RELOPT_TYPE_REAL,
RELOPT_TYPE_ENUM,
@@ -80,6 +81,7 @@ typedef struct relopt_value
union
{
bool bool_val;
+ ternary ternary_val;
int int_val;
double real_val;
int enum_val;
@@ -94,6 +96,12 @@ typedef struct relopt_bool
bool default_val;
} relopt_bool;
+typedef struct relopt_rernary
+{
+ relopt_gen gen;
+ int default_val;
+} relopt_ternary;
+
typedef struct relopt_int
{
relopt_gen gen;
@@ -152,19 +160,6 @@ typedef struct
const char *optname; /* option's name */
relopt_type opttype; /* option's datatype */
int offset; /* offset of field in result struct */
-
- /*
- * isset_offset is an optional offset of a field in the result struct that
- * stores whether the option is explicitly set for the relation or if it
- * just picked up the default value. In most cases, this can be
- * accomplished by giving the reloption a special out-of-range default
- * value (e.g., some integer reloptions use -2), but this isn't always
- * possible. For example, a Boolean reloption cannot be given an
- * out-of-range default, so we need another way to discover the source of
- * its value. This offset is only used if given a value greater than
- * zero.
- */
- int isset_offset;
} relopt_parse_elt;
/* Local reloption definition */
@@ -195,6 +190,8 @@ typedef struct local_relopts
extern relopt_kind add_reloption_kind(void);
extern void add_bool_reloption(bits32 kinds, const char *name, const char *desc,
bool default_val, LOCKMODE lockmode);
+extern void add_ternary_reloption(bits32 kinds, const char *name,
+ const char *desc, int default_val, LOCKMODE lockmode);
extern void add_int_reloption(bits32 kinds, const char *name, const char *desc,
int default_val, int min_val, int max_val,
LOCKMODE lockmode);
@@ -214,6 +211,9 @@ extern void register_reloptions_validator(local_relopts *relopts,
extern void add_local_bool_reloption(local_relopts *relopts, const char *name,
const char *desc, bool default_val,
int offset);
+extern void add_local_ternary_reloption(local_relopts *relopts,
+ const char *name, const char *desc,
+ ternary default_val, int offset);
extern void add_local_int_reloption(local_relopts *relopts, const char *name,
const char *desc, int default_val,
int min_val, int max_val, int offset);
diff --git a/src/include/c.h b/src/include/c.h
index 39022f8a9d..9c59ccccf8 100644
--- a/src/include/c.h
+++ b/src/include/c.h
@@ -488,6 +488,22 @@ typedef void (*pg_funcptr_t) (void);
#include <stdbool.h>
+/*
+ * ternary
+ * Boolean value with an extrea "unset" option
+ *
+ * Ternary data type is used in relation options that can be "true", "false" or
+ * "unset". Since relation options are used deep inside the PostgreSQL code,
+ * this type is declared globally.
+*/
+
+typedef enum ternary
+{
+ TERNARY_FALSE = 0,
+ TERNARY_TRUE = 1,
+ TERNARY_UNSET = -1
+} ternary;
+
/* ----------------------------------------------------------------
* Section 3: standard system types
diff --git a/src/include/utils/rel.h b/src/include/utils/rel.h
index b552359915..7346d618f9 100644
--- a/src/include/utils/rel.h
+++ b/src/include/utils/rel.h
@@ -346,8 +346,7 @@ typedef struct StdRdOptions
bool user_catalog_table; /* use as an additional catalog relation */
int parallel_workers; /* max number of parallel workers */
StdRdOptIndexCleanup vacuum_index_cleanup; /* controls index vacuuming */
- bool vacuum_truncate; /* enables vacuum to truncate a relation */
- bool vacuum_truncate_set; /* whether vacuum_truncate is set */
+ ternary vacuum_truncate; /* enables vacuum to truncate a relation */
/*
* Fraction of pages in a relation that vacuum can eagerly scan and fail
--
2.39.2
--nextPart5129932.5fSG56mABF
Content-Disposition: attachment;
filename="v2-0003-Add-alias-to-be-used-as-unset-state.patch"
Content-Transfer-Encoding: 7Bit
Content-Type: text/x-patch; charset="unicode-2-0-utf-8";
name="v2-0003-Add-alias-to-be-used-as-unset-state.patch"
^ permalink raw reply [nested|flat] 868+ messages in thread
* [PATCH v2 2/4] Introduce ternary reloptions
@ 2025-09-04 14:41 Nikolay Shaplov <[email protected]>
0 siblings, 0 replies; 868+ messages in thread
From: Nikolay Shaplov @ 2025-09-04 14:41 UTC (permalink / raw)
Introduce ternary reloption as a replacement for current `vacuum_truncate`
implementation. Remove `vacuum_truncate_set` additional flag and using
`TERNARY_UNSET` value instead.
---
src/backend/access/common/reloptions.c | 129 ++++++++++++++++++++-----
src/backend/commands/vacuum.c | 4 +-
src/include/access/reloptions.h | 26 ++---
src/include/c.h | 16 +++
src/include/utils/rel.h | 3 +-
5 files changed, 135 insertions(+), 43 deletions(-)
diff --git a/src/backend/access/common/reloptions.c b/src/backend/access/common/reloptions.c
index 0af3fea68f..f543f11001 100644
--- a/src/backend/access/common/reloptions.c
+++ b/src/backend/access/common/reloptions.c
@@ -40,9 +40,9 @@
*
* To add an option:
*
- * (i) decide on a type (bool, integer, real, enum, string), name, default
- * value, upper and lower bounds (if applicable); for strings, consider a
- * validation routine.
+ * (i) decide on a type (bool, ternary, integer, real, enum, string), name,
+ * default value, upper and lower bounds (if applicable); for strings,
+ * consider a validation routine.
* (ii) add a record below (or use add_<type>_reloption).
* (iii) add it to the appropriate options struct (perhaps StdRdOptions)
* (iv) add it to the appropriate handling routine (perhaps
@@ -147,15 +147,6 @@ static relopt_bool boolRelOpts[] =
},
false
},
- {
- {
- "vacuum_truncate",
- "Enables vacuum to truncate empty pages at the end of this table",
- RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
- ShareUpdateExclusiveLock
- },
- true
- },
{
{
"deduplicate_items",
@@ -170,6 +161,21 @@ static relopt_bool boolRelOpts[] =
{{NULL}}
};
+static relopt_ternary ternaryRelOpts[] =
+{
+ {
+ {
+ "vacuum_truncate",
+ "Enables vacuum to truncate empty pages at the end of this table",
+ RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
+ ShareUpdateExclusiveLock
+ },
+ TERNARY_UNSET
+ },
+ /* list terminator */
+ {{NULL}}
+};
+
static relopt_int intRelOpts[] =
{
{
@@ -600,6 +606,13 @@ initialize_reloptions(void)
boolRelOpts[i].gen.lockmode));
j++;
}
+ for (i = 0; ternaryRelOpts[i].gen.name; i++)
+ {
+ Assert(DoLockModesConflict(ternaryRelOpts[i].gen.lockmode,
+ ternaryRelOpts[i].gen.lockmode));
+ j++;
+ }
+
for (i = 0; intRelOpts[i].gen.name; i++)
{
Assert(DoLockModesConflict(intRelOpts[i].gen.lockmode,
@@ -640,6 +653,14 @@ initialize_reloptions(void)
j++;
}
+ for (i = 0; ternaryRelOpts[i].gen.name; i++)
+ {
+ relOpts[j] = &ternaryRelOpts[i].gen;
+ relOpts[j]->type = RELOPT_TYPE_TERNARY;
+ relOpts[j]->namelen = strlen(relOpts[j]->name);
+ j++;
+ }
+
for (i = 0; intRelOpts[i].gen.name; i++)
{
relOpts[j] = &intRelOpts[i].gen;
@@ -800,6 +821,9 @@ allocate_reloption(bits32 kinds, int type, const char *name, const char *desc,
case RELOPT_TYPE_BOOL:
size = sizeof(relopt_bool);
break;
+ case RELOPT_TYPE_TERNARY:
+ size = sizeof(relopt_ternary);
+ break;
case RELOPT_TYPE_INT:
size = sizeof(relopt_int);
break;
@@ -883,6 +907,54 @@ add_local_bool_reloption(local_relopts *relopts, const char *name,
add_local_reloption(relopts, (relopt_gen *) newoption, offset);
}
+/*
+ * init_ternary_reloption
+ * Allocate and initialize a new ternary reloption
+ */
+static relopt_ternary *
+init_ternary_reloption(bits32 kinds, const char *name, const char *desc,
+ ternary default_val, LOCKMODE lockmode)
+{
+ relopt_ternary *newoption;
+
+ newoption = (relopt_ternary *) allocate_reloption(kinds,
+ RELOPT_TYPE_TERNARY, name, desc, lockmode);
+ newoption->default_val = default_val;
+
+ return newoption;
+}
+
+/*
+ * add_ternary_reloption
+ * Add a new ternary reloption
+ */
+void
+add_ternary_reloption(bits32 kinds, const char *name, const char *desc,
+ ternary default_val, LOCKMODE lockmode)
+{
+ relopt_ternary *newoption = init_ternary_reloption(kinds, name, desc,
+ default_val, lockmode);
+
+ add_reloption((relopt_gen *) newoption);
+}
+
+/*
+ * add_local_ternary_reloption
+ * Add a new ternary local reloption
+ *
+ * 'offset' is offset of ternary-typed field.
+ */
+void
+add_local_ternary_reloption(local_relopts *relopts, const char *name,
+ const char *desc, ternary default_val,
+ int offset)
+{
+ relopt_ternary *newoption = init_ternary_reloption(RELOPT_KIND_LOCAL,
+ name, desc,
+ default_val, 0);
+
+ add_local_reloption(relopts, (relopt_gen *) newoption, offset);
+}
/*
* init_real_reloption
@@ -1617,6 +1689,18 @@ parse_one_reloption(relopt_value *option, char *text_str, int text_len,
option->gen->name, value)));
}
break;
+ case RELOPT_TYPE_TERNARY:
+ {
+ bool b;
+ parsed = parse_bool(value, &b);
+ option->values.ternary_val = b ? TERNARY_TRUE : TERNARY_FALSE;
+ if (validate && !parsed)
+ ereport(ERROR,
+ (errcode(ERRCODE_INVALID_PARAMETER_VALUE),
+ errmsg("invalid value for ternary option \"%s\": %s",
+ option->gen->name, value)));
+ }
+ break;
case RELOPT_TYPE_INT:
{
relopt_int *optint = (relopt_int *) option->gen;
@@ -1780,17 +1864,6 @@ fillRelOptions(void *rdopts, Size basesize,
char *itempos = ((char *) rdopts) + elems[j].offset;
char *string_val;
- /*
- * If isset_offset is provided, store whether the reloption is
- * set there.
- */
- if (elems[j].isset_offset > 0)
- {
- char *setpos = ((char *) rdopts) + elems[j].isset_offset;
-
- *(bool *) setpos = options[i].isset;
- }
-
switch (options[i].gen->type)
{
case RELOPT_TYPE_BOOL:
@@ -1798,6 +1871,11 @@ fillRelOptions(void *rdopts, Size basesize,
options[i].values.bool_val :
((relopt_bool *) options[i].gen)->default_val;
break;
+ case RELOPT_TYPE_TERNARY:
+ *(ternary *) itempos = options[i].isset ?
+ options[i].values.ternary_val :
+ ((relopt_ternary *) options[i].gen)->default_val;
+ break;
case RELOPT_TYPE_INT:
*(int *) itempos = options[i].isset ?
options[i].values.int_val :
@@ -1912,8 +1990,8 @@ default_reloptions(Datum reloptions, bool validate, relopt_kind kind)
offsetof(StdRdOptions, parallel_workers)},
{"vacuum_index_cleanup", RELOPT_TYPE_ENUM,
offsetof(StdRdOptions, vacuum_index_cleanup)},
- {"vacuum_truncate", RELOPT_TYPE_BOOL,
- offsetof(StdRdOptions, vacuum_truncate), offsetof(StdRdOptions, vacuum_truncate_set)},
+ {"vacuum_truncate", RELOPT_TYPE_TERNARY,
+ offsetof(StdRdOptions, vacuum_truncate)},
{"vacuum_max_eager_freeze_failure_rate", RELOPT_TYPE_REAL,
offsetof(StdRdOptions, vacuum_max_eager_freeze_failure_rate)}
};
@@ -1993,7 +2071,6 @@ build_local_reloptions(local_relopts *relopts, Datum options, bool validate)
elems[i].optname = opt->option->name;
elems[i].opttype = opt->option->type;
elems[i].offset = opt->offset;
- elems[i].isset_offset = 0; /* not supported for local relopts yet */
i++;
}
diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c
index 733ef40ae7..977babff54 100644
--- a/src/backend/commands/vacuum.c
+++ b/src/backend/commands/vacuum.c
@@ -2221,9 +2221,9 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams params,
{
StdRdOptions *opts = (StdRdOptions *) rel->rd_options;
- if (opts && opts->vacuum_truncate_set)
+ if (opts && opts->vacuum_truncate != TERNARY_UNSET)
{
- if (opts->vacuum_truncate)
+ if (opts->vacuum_truncate == TERNARY_TRUE)
params.truncate = VACOPTVALUE_ENABLED;
else
params.truncate = VACOPTVALUE_DISABLED;
diff --git a/src/include/access/reloptions.h b/src/include/access/reloptions.h
index a604a4702c..a436697658 100644
--- a/src/include/access/reloptions.h
+++ b/src/include/access/reloptions.h
@@ -29,6 +29,7 @@
typedef enum relopt_type
{
RELOPT_TYPE_BOOL,
+ RELOPT_TYPE_TERNARY, /* on, off, unset */
RELOPT_TYPE_INT,
RELOPT_TYPE_REAL,
RELOPT_TYPE_ENUM,
@@ -80,6 +81,7 @@ typedef struct relopt_value
union
{
bool bool_val;
+ ternary ternary_val;
int int_val;
double real_val;
int enum_val;
@@ -94,6 +96,12 @@ typedef struct relopt_bool
bool default_val;
} relopt_bool;
+typedef struct relopt_rernary
+{
+ relopt_gen gen;
+ int default_val;
+} relopt_ternary;
+
typedef struct relopt_int
{
relopt_gen gen;
@@ -152,19 +160,6 @@ typedef struct
const char *optname; /* option's name */
relopt_type opttype; /* option's datatype */
int offset; /* offset of field in result struct */
-
- /*
- * isset_offset is an optional offset of a field in the result struct that
- * stores whether the option is explicitly set for the relation or if it
- * just picked up the default value. In most cases, this can be
- * accomplished by giving the reloption a special out-of-range default
- * value (e.g., some integer reloptions use -2), but this isn't always
- * possible. For example, a Boolean reloption cannot be given an
- * out-of-range default, so we need another way to discover the source of
- * its value. This offset is only used if given a value greater than
- * zero.
- */
- int isset_offset;
} relopt_parse_elt;
/* Local reloption definition */
@@ -195,6 +190,8 @@ typedef struct local_relopts
extern relopt_kind add_reloption_kind(void);
extern void add_bool_reloption(bits32 kinds, const char *name, const char *desc,
bool default_val, LOCKMODE lockmode);
+extern void add_ternary_reloption(bits32 kinds, const char *name,
+ const char *desc, int default_val, LOCKMODE lockmode);
extern void add_int_reloption(bits32 kinds, const char *name, const char *desc,
int default_val, int min_val, int max_val,
LOCKMODE lockmode);
@@ -214,6 +211,9 @@ extern void register_reloptions_validator(local_relopts *relopts,
extern void add_local_bool_reloption(local_relopts *relopts, const char *name,
const char *desc, bool default_val,
int offset);
+extern void add_local_ternary_reloption(local_relopts *relopts,
+ const char *name, const char *desc,
+ ternary default_val, int offset);
extern void add_local_int_reloption(local_relopts *relopts, const char *name,
const char *desc, int default_val,
int min_val, int max_val, int offset);
diff --git a/src/include/c.h b/src/include/c.h
index 39022f8a9d..9c59ccccf8 100644
--- a/src/include/c.h
+++ b/src/include/c.h
@@ -488,6 +488,22 @@ typedef void (*pg_funcptr_t) (void);
#include <stdbool.h>
+/*
+ * ternary
+ * Boolean value with an extrea "unset" option
+ *
+ * Ternary data type is used in relation options that can be "true", "false" or
+ * "unset". Since relation options are used deep inside the PostgreSQL code,
+ * this type is declared globally.
+*/
+
+typedef enum ternary
+{
+ TERNARY_FALSE = 0,
+ TERNARY_TRUE = 1,
+ TERNARY_UNSET = -1
+} ternary;
+
/* ----------------------------------------------------------------
* Section 3: standard system types
diff --git a/src/include/utils/rel.h b/src/include/utils/rel.h
index b552359915..7346d618f9 100644
--- a/src/include/utils/rel.h
+++ b/src/include/utils/rel.h
@@ -346,8 +346,7 @@ typedef struct StdRdOptions
bool user_catalog_table; /* use as an additional catalog relation */
int parallel_workers; /* max number of parallel workers */
StdRdOptIndexCleanup vacuum_index_cleanup; /* controls index vacuuming */
- bool vacuum_truncate; /* enables vacuum to truncate a relation */
- bool vacuum_truncate_set; /* whether vacuum_truncate is set */
+ ternary vacuum_truncate; /* enables vacuum to truncate a relation */
/*
* Fraction of pages in a relation that vacuum can eagerly scan and fail
--
2.39.2
--nextPart5129932.5fSG56mABF
Content-Disposition: attachment;
filename="v2-0003-Add-alias-to-be-used-as-unset-state.patch"
Content-Transfer-Encoding: 7Bit
Content-Type: text/x-patch; charset="unicode-2-0-utf-8";
name="v2-0003-Add-alias-to-be-used-as-unset-state.patch"
^ permalink raw reply [nested|flat] 868+ messages in thread
* [PATCH v2 2/4] Introduce ternary reloptions
@ 2025-09-04 14:41 Nikolay Shaplov <[email protected]>
0 siblings, 0 replies; 868+ messages in thread
From: Nikolay Shaplov @ 2025-09-04 14:41 UTC (permalink / raw)
Introduce ternary reloption as a replacement for current `vacuum_truncate`
implementation. Remove `vacuum_truncate_set` additional flag and using
`TERNARY_UNSET` value instead.
---
src/backend/access/common/reloptions.c | 129 ++++++++++++++++++++-----
src/backend/commands/vacuum.c | 4 +-
src/include/access/reloptions.h | 26 ++---
src/include/c.h | 16 +++
src/include/utils/rel.h | 3 +-
5 files changed, 135 insertions(+), 43 deletions(-)
diff --git a/src/backend/access/common/reloptions.c b/src/backend/access/common/reloptions.c
index 0af3fea68f..f543f11001 100644
--- a/src/backend/access/common/reloptions.c
+++ b/src/backend/access/common/reloptions.c
@@ -40,9 +40,9 @@
*
* To add an option:
*
- * (i) decide on a type (bool, integer, real, enum, string), name, default
- * value, upper and lower bounds (if applicable); for strings, consider a
- * validation routine.
+ * (i) decide on a type (bool, ternary, integer, real, enum, string), name,
+ * default value, upper and lower bounds (if applicable); for strings,
+ * consider a validation routine.
* (ii) add a record below (or use add_<type>_reloption).
* (iii) add it to the appropriate options struct (perhaps StdRdOptions)
* (iv) add it to the appropriate handling routine (perhaps
@@ -147,15 +147,6 @@ static relopt_bool boolRelOpts[] =
},
false
},
- {
- {
- "vacuum_truncate",
- "Enables vacuum to truncate empty pages at the end of this table",
- RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
- ShareUpdateExclusiveLock
- },
- true
- },
{
{
"deduplicate_items",
@@ -170,6 +161,21 @@ static relopt_bool boolRelOpts[] =
{{NULL}}
};
+static relopt_ternary ternaryRelOpts[] =
+{
+ {
+ {
+ "vacuum_truncate",
+ "Enables vacuum to truncate empty pages at the end of this table",
+ RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
+ ShareUpdateExclusiveLock
+ },
+ TERNARY_UNSET
+ },
+ /* list terminator */
+ {{NULL}}
+};
+
static relopt_int intRelOpts[] =
{
{
@@ -600,6 +606,13 @@ initialize_reloptions(void)
boolRelOpts[i].gen.lockmode));
j++;
}
+ for (i = 0; ternaryRelOpts[i].gen.name; i++)
+ {
+ Assert(DoLockModesConflict(ternaryRelOpts[i].gen.lockmode,
+ ternaryRelOpts[i].gen.lockmode));
+ j++;
+ }
+
for (i = 0; intRelOpts[i].gen.name; i++)
{
Assert(DoLockModesConflict(intRelOpts[i].gen.lockmode,
@@ -640,6 +653,14 @@ initialize_reloptions(void)
j++;
}
+ for (i = 0; ternaryRelOpts[i].gen.name; i++)
+ {
+ relOpts[j] = &ternaryRelOpts[i].gen;
+ relOpts[j]->type = RELOPT_TYPE_TERNARY;
+ relOpts[j]->namelen = strlen(relOpts[j]->name);
+ j++;
+ }
+
for (i = 0; intRelOpts[i].gen.name; i++)
{
relOpts[j] = &intRelOpts[i].gen;
@@ -800,6 +821,9 @@ allocate_reloption(bits32 kinds, int type, const char *name, const char *desc,
case RELOPT_TYPE_BOOL:
size = sizeof(relopt_bool);
break;
+ case RELOPT_TYPE_TERNARY:
+ size = sizeof(relopt_ternary);
+ break;
case RELOPT_TYPE_INT:
size = sizeof(relopt_int);
break;
@@ -883,6 +907,54 @@ add_local_bool_reloption(local_relopts *relopts, const char *name,
add_local_reloption(relopts, (relopt_gen *) newoption, offset);
}
+/*
+ * init_ternary_reloption
+ * Allocate and initialize a new ternary reloption
+ */
+static relopt_ternary *
+init_ternary_reloption(bits32 kinds, const char *name, const char *desc,
+ ternary default_val, LOCKMODE lockmode)
+{
+ relopt_ternary *newoption;
+
+ newoption = (relopt_ternary *) allocate_reloption(kinds,
+ RELOPT_TYPE_TERNARY, name, desc, lockmode);
+ newoption->default_val = default_val;
+
+ return newoption;
+}
+
+/*
+ * add_ternary_reloption
+ * Add a new ternary reloption
+ */
+void
+add_ternary_reloption(bits32 kinds, const char *name, const char *desc,
+ ternary default_val, LOCKMODE lockmode)
+{
+ relopt_ternary *newoption = init_ternary_reloption(kinds, name, desc,
+ default_val, lockmode);
+
+ add_reloption((relopt_gen *) newoption);
+}
+
+/*
+ * add_local_ternary_reloption
+ * Add a new ternary local reloption
+ *
+ * 'offset' is offset of ternary-typed field.
+ */
+void
+add_local_ternary_reloption(local_relopts *relopts, const char *name,
+ const char *desc, ternary default_val,
+ int offset)
+{
+ relopt_ternary *newoption = init_ternary_reloption(RELOPT_KIND_LOCAL,
+ name, desc,
+ default_val, 0);
+
+ add_local_reloption(relopts, (relopt_gen *) newoption, offset);
+}
/*
* init_real_reloption
@@ -1617,6 +1689,18 @@ parse_one_reloption(relopt_value *option, char *text_str, int text_len,
option->gen->name, value)));
}
break;
+ case RELOPT_TYPE_TERNARY:
+ {
+ bool b;
+ parsed = parse_bool(value, &b);
+ option->values.ternary_val = b ? TERNARY_TRUE : TERNARY_FALSE;
+ if (validate && !parsed)
+ ereport(ERROR,
+ (errcode(ERRCODE_INVALID_PARAMETER_VALUE),
+ errmsg("invalid value for ternary option \"%s\": %s",
+ option->gen->name, value)));
+ }
+ break;
case RELOPT_TYPE_INT:
{
relopt_int *optint = (relopt_int *) option->gen;
@@ -1780,17 +1864,6 @@ fillRelOptions(void *rdopts, Size basesize,
char *itempos = ((char *) rdopts) + elems[j].offset;
char *string_val;
- /*
- * If isset_offset is provided, store whether the reloption is
- * set there.
- */
- if (elems[j].isset_offset > 0)
- {
- char *setpos = ((char *) rdopts) + elems[j].isset_offset;
-
- *(bool *) setpos = options[i].isset;
- }
-
switch (options[i].gen->type)
{
case RELOPT_TYPE_BOOL:
@@ -1798,6 +1871,11 @@ fillRelOptions(void *rdopts, Size basesize,
options[i].values.bool_val :
((relopt_bool *) options[i].gen)->default_val;
break;
+ case RELOPT_TYPE_TERNARY:
+ *(ternary *) itempos = options[i].isset ?
+ options[i].values.ternary_val :
+ ((relopt_ternary *) options[i].gen)->default_val;
+ break;
case RELOPT_TYPE_INT:
*(int *) itempos = options[i].isset ?
options[i].values.int_val :
@@ -1912,8 +1990,8 @@ default_reloptions(Datum reloptions, bool validate, relopt_kind kind)
offsetof(StdRdOptions, parallel_workers)},
{"vacuum_index_cleanup", RELOPT_TYPE_ENUM,
offsetof(StdRdOptions, vacuum_index_cleanup)},
- {"vacuum_truncate", RELOPT_TYPE_BOOL,
- offsetof(StdRdOptions, vacuum_truncate), offsetof(StdRdOptions, vacuum_truncate_set)},
+ {"vacuum_truncate", RELOPT_TYPE_TERNARY,
+ offsetof(StdRdOptions, vacuum_truncate)},
{"vacuum_max_eager_freeze_failure_rate", RELOPT_TYPE_REAL,
offsetof(StdRdOptions, vacuum_max_eager_freeze_failure_rate)}
};
@@ -1993,7 +2071,6 @@ build_local_reloptions(local_relopts *relopts, Datum options, bool validate)
elems[i].optname = opt->option->name;
elems[i].opttype = opt->option->type;
elems[i].offset = opt->offset;
- elems[i].isset_offset = 0; /* not supported for local relopts yet */
i++;
}
diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c
index 733ef40ae7..977babff54 100644
--- a/src/backend/commands/vacuum.c
+++ b/src/backend/commands/vacuum.c
@@ -2221,9 +2221,9 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams params,
{
StdRdOptions *opts = (StdRdOptions *) rel->rd_options;
- if (opts && opts->vacuum_truncate_set)
+ if (opts && opts->vacuum_truncate != TERNARY_UNSET)
{
- if (opts->vacuum_truncate)
+ if (opts->vacuum_truncate == TERNARY_TRUE)
params.truncate = VACOPTVALUE_ENABLED;
else
params.truncate = VACOPTVALUE_DISABLED;
diff --git a/src/include/access/reloptions.h b/src/include/access/reloptions.h
index a604a4702c..a436697658 100644
--- a/src/include/access/reloptions.h
+++ b/src/include/access/reloptions.h
@@ -29,6 +29,7 @@
typedef enum relopt_type
{
RELOPT_TYPE_BOOL,
+ RELOPT_TYPE_TERNARY, /* on, off, unset */
RELOPT_TYPE_INT,
RELOPT_TYPE_REAL,
RELOPT_TYPE_ENUM,
@@ -80,6 +81,7 @@ typedef struct relopt_value
union
{
bool bool_val;
+ ternary ternary_val;
int int_val;
double real_val;
int enum_val;
@@ -94,6 +96,12 @@ typedef struct relopt_bool
bool default_val;
} relopt_bool;
+typedef struct relopt_rernary
+{
+ relopt_gen gen;
+ int default_val;
+} relopt_ternary;
+
typedef struct relopt_int
{
relopt_gen gen;
@@ -152,19 +160,6 @@ typedef struct
const char *optname; /* option's name */
relopt_type opttype; /* option's datatype */
int offset; /* offset of field in result struct */
-
- /*
- * isset_offset is an optional offset of a field in the result struct that
- * stores whether the option is explicitly set for the relation or if it
- * just picked up the default value. In most cases, this can be
- * accomplished by giving the reloption a special out-of-range default
- * value (e.g., some integer reloptions use -2), but this isn't always
- * possible. For example, a Boolean reloption cannot be given an
- * out-of-range default, so we need another way to discover the source of
- * its value. This offset is only used if given a value greater than
- * zero.
- */
- int isset_offset;
} relopt_parse_elt;
/* Local reloption definition */
@@ -195,6 +190,8 @@ typedef struct local_relopts
extern relopt_kind add_reloption_kind(void);
extern void add_bool_reloption(bits32 kinds, const char *name, const char *desc,
bool default_val, LOCKMODE lockmode);
+extern void add_ternary_reloption(bits32 kinds, const char *name,
+ const char *desc, int default_val, LOCKMODE lockmode);
extern void add_int_reloption(bits32 kinds, const char *name, const char *desc,
int default_val, int min_val, int max_val,
LOCKMODE lockmode);
@@ -214,6 +211,9 @@ extern void register_reloptions_validator(local_relopts *relopts,
extern void add_local_bool_reloption(local_relopts *relopts, const char *name,
const char *desc, bool default_val,
int offset);
+extern void add_local_ternary_reloption(local_relopts *relopts,
+ const char *name, const char *desc,
+ ternary default_val, int offset);
extern void add_local_int_reloption(local_relopts *relopts, const char *name,
const char *desc, int default_val,
int min_val, int max_val, int offset);
diff --git a/src/include/c.h b/src/include/c.h
index 39022f8a9d..9c59ccccf8 100644
--- a/src/include/c.h
+++ b/src/include/c.h
@@ -488,6 +488,22 @@ typedef void (*pg_funcptr_t) (void);
#include <stdbool.h>
+/*
+ * ternary
+ * Boolean value with an extrea "unset" option
+ *
+ * Ternary data type is used in relation options that can be "true", "false" or
+ * "unset". Since relation options are used deep inside the PostgreSQL code,
+ * this type is declared globally.
+*/
+
+typedef enum ternary
+{
+ TERNARY_FALSE = 0,
+ TERNARY_TRUE = 1,
+ TERNARY_UNSET = -1
+} ternary;
+
/* ----------------------------------------------------------------
* Section 3: standard system types
diff --git a/src/include/utils/rel.h b/src/include/utils/rel.h
index b552359915..7346d618f9 100644
--- a/src/include/utils/rel.h
+++ b/src/include/utils/rel.h
@@ -346,8 +346,7 @@ typedef struct StdRdOptions
bool user_catalog_table; /* use as an additional catalog relation */
int parallel_workers; /* max number of parallel workers */
StdRdOptIndexCleanup vacuum_index_cleanup; /* controls index vacuuming */
- bool vacuum_truncate; /* enables vacuum to truncate a relation */
- bool vacuum_truncate_set; /* whether vacuum_truncate is set */
+ ternary vacuum_truncate; /* enables vacuum to truncate a relation */
/*
* Fraction of pages in a relation that vacuum can eagerly scan and fail
--
2.39.2
--nextPart5129932.5fSG56mABF
Content-Disposition: attachment;
filename="v2-0003-Add-alias-to-be-used-as-unset-state.patch"
Content-Transfer-Encoding: 7Bit
Content-Type: text/x-patch; charset="unicode-2-0-utf-8";
name="v2-0003-Add-alias-to-be-used-as-unset-state.patch"
^ permalink raw reply [nested|flat] 868+ messages in thread
* [PATCH v2 2/4] Introduce ternary reloptions
@ 2025-09-04 14:41 Nikolay Shaplov <[email protected]>
0 siblings, 0 replies; 868+ messages in thread
From: Nikolay Shaplov @ 2025-09-04 14:41 UTC (permalink / raw)
Introduce ternary reloption as a replacement for current `vacuum_truncate`
implementation. Remove `vacuum_truncate_set` additional flag and using
`TERNARY_UNSET` value instead.
---
src/backend/access/common/reloptions.c | 129 ++++++++++++++++++++-----
src/backend/commands/vacuum.c | 4 +-
src/include/access/reloptions.h | 26 ++---
src/include/c.h | 16 +++
src/include/utils/rel.h | 3 +-
5 files changed, 135 insertions(+), 43 deletions(-)
diff --git a/src/backend/access/common/reloptions.c b/src/backend/access/common/reloptions.c
index 0af3fea68f..f543f11001 100644
--- a/src/backend/access/common/reloptions.c
+++ b/src/backend/access/common/reloptions.c
@@ -40,9 +40,9 @@
*
* To add an option:
*
- * (i) decide on a type (bool, integer, real, enum, string), name, default
- * value, upper and lower bounds (if applicable); for strings, consider a
- * validation routine.
+ * (i) decide on a type (bool, ternary, integer, real, enum, string), name,
+ * default value, upper and lower bounds (if applicable); for strings,
+ * consider a validation routine.
* (ii) add a record below (or use add_<type>_reloption).
* (iii) add it to the appropriate options struct (perhaps StdRdOptions)
* (iv) add it to the appropriate handling routine (perhaps
@@ -147,15 +147,6 @@ static relopt_bool boolRelOpts[] =
},
false
},
- {
- {
- "vacuum_truncate",
- "Enables vacuum to truncate empty pages at the end of this table",
- RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
- ShareUpdateExclusiveLock
- },
- true
- },
{
{
"deduplicate_items",
@@ -170,6 +161,21 @@ static relopt_bool boolRelOpts[] =
{{NULL}}
};
+static relopt_ternary ternaryRelOpts[] =
+{
+ {
+ {
+ "vacuum_truncate",
+ "Enables vacuum to truncate empty pages at the end of this table",
+ RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
+ ShareUpdateExclusiveLock
+ },
+ TERNARY_UNSET
+ },
+ /* list terminator */
+ {{NULL}}
+};
+
static relopt_int intRelOpts[] =
{
{
@@ -600,6 +606,13 @@ initialize_reloptions(void)
boolRelOpts[i].gen.lockmode));
j++;
}
+ for (i = 0; ternaryRelOpts[i].gen.name; i++)
+ {
+ Assert(DoLockModesConflict(ternaryRelOpts[i].gen.lockmode,
+ ternaryRelOpts[i].gen.lockmode));
+ j++;
+ }
+
for (i = 0; intRelOpts[i].gen.name; i++)
{
Assert(DoLockModesConflict(intRelOpts[i].gen.lockmode,
@@ -640,6 +653,14 @@ initialize_reloptions(void)
j++;
}
+ for (i = 0; ternaryRelOpts[i].gen.name; i++)
+ {
+ relOpts[j] = &ternaryRelOpts[i].gen;
+ relOpts[j]->type = RELOPT_TYPE_TERNARY;
+ relOpts[j]->namelen = strlen(relOpts[j]->name);
+ j++;
+ }
+
for (i = 0; intRelOpts[i].gen.name; i++)
{
relOpts[j] = &intRelOpts[i].gen;
@@ -800,6 +821,9 @@ allocate_reloption(bits32 kinds, int type, const char *name, const char *desc,
case RELOPT_TYPE_BOOL:
size = sizeof(relopt_bool);
break;
+ case RELOPT_TYPE_TERNARY:
+ size = sizeof(relopt_ternary);
+ break;
case RELOPT_TYPE_INT:
size = sizeof(relopt_int);
break;
@@ -883,6 +907,54 @@ add_local_bool_reloption(local_relopts *relopts, const char *name,
add_local_reloption(relopts, (relopt_gen *) newoption, offset);
}
+/*
+ * init_ternary_reloption
+ * Allocate and initialize a new ternary reloption
+ */
+static relopt_ternary *
+init_ternary_reloption(bits32 kinds, const char *name, const char *desc,
+ ternary default_val, LOCKMODE lockmode)
+{
+ relopt_ternary *newoption;
+
+ newoption = (relopt_ternary *) allocate_reloption(kinds,
+ RELOPT_TYPE_TERNARY, name, desc, lockmode);
+ newoption->default_val = default_val;
+
+ return newoption;
+}
+
+/*
+ * add_ternary_reloption
+ * Add a new ternary reloption
+ */
+void
+add_ternary_reloption(bits32 kinds, const char *name, const char *desc,
+ ternary default_val, LOCKMODE lockmode)
+{
+ relopt_ternary *newoption = init_ternary_reloption(kinds, name, desc,
+ default_val, lockmode);
+
+ add_reloption((relopt_gen *) newoption);
+}
+
+/*
+ * add_local_ternary_reloption
+ * Add a new ternary local reloption
+ *
+ * 'offset' is offset of ternary-typed field.
+ */
+void
+add_local_ternary_reloption(local_relopts *relopts, const char *name,
+ const char *desc, ternary default_val,
+ int offset)
+{
+ relopt_ternary *newoption = init_ternary_reloption(RELOPT_KIND_LOCAL,
+ name, desc,
+ default_val, 0);
+
+ add_local_reloption(relopts, (relopt_gen *) newoption, offset);
+}
/*
* init_real_reloption
@@ -1617,6 +1689,18 @@ parse_one_reloption(relopt_value *option, char *text_str, int text_len,
option->gen->name, value)));
}
break;
+ case RELOPT_TYPE_TERNARY:
+ {
+ bool b;
+ parsed = parse_bool(value, &b);
+ option->values.ternary_val = b ? TERNARY_TRUE : TERNARY_FALSE;
+ if (validate && !parsed)
+ ereport(ERROR,
+ (errcode(ERRCODE_INVALID_PARAMETER_VALUE),
+ errmsg("invalid value for ternary option \"%s\": %s",
+ option->gen->name, value)));
+ }
+ break;
case RELOPT_TYPE_INT:
{
relopt_int *optint = (relopt_int *) option->gen;
@@ -1780,17 +1864,6 @@ fillRelOptions(void *rdopts, Size basesize,
char *itempos = ((char *) rdopts) + elems[j].offset;
char *string_val;
- /*
- * If isset_offset is provided, store whether the reloption is
- * set there.
- */
- if (elems[j].isset_offset > 0)
- {
- char *setpos = ((char *) rdopts) + elems[j].isset_offset;
-
- *(bool *) setpos = options[i].isset;
- }
-
switch (options[i].gen->type)
{
case RELOPT_TYPE_BOOL:
@@ -1798,6 +1871,11 @@ fillRelOptions(void *rdopts, Size basesize,
options[i].values.bool_val :
((relopt_bool *) options[i].gen)->default_val;
break;
+ case RELOPT_TYPE_TERNARY:
+ *(ternary *) itempos = options[i].isset ?
+ options[i].values.ternary_val :
+ ((relopt_ternary *) options[i].gen)->default_val;
+ break;
case RELOPT_TYPE_INT:
*(int *) itempos = options[i].isset ?
options[i].values.int_val :
@@ -1912,8 +1990,8 @@ default_reloptions(Datum reloptions, bool validate, relopt_kind kind)
offsetof(StdRdOptions, parallel_workers)},
{"vacuum_index_cleanup", RELOPT_TYPE_ENUM,
offsetof(StdRdOptions, vacuum_index_cleanup)},
- {"vacuum_truncate", RELOPT_TYPE_BOOL,
- offsetof(StdRdOptions, vacuum_truncate), offsetof(StdRdOptions, vacuum_truncate_set)},
+ {"vacuum_truncate", RELOPT_TYPE_TERNARY,
+ offsetof(StdRdOptions, vacuum_truncate)},
{"vacuum_max_eager_freeze_failure_rate", RELOPT_TYPE_REAL,
offsetof(StdRdOptions, vacuum_max_eager_freeze_failure_rate)}
};
@@ -1993,7 +2071,6 @@ build_local_reloptions(local_relopts *relopts, Datum options, bool validate)
elems[i].optname = opt->option->name;
elems[i].opttype = opt->option->type;
elems[i].offset = opt->offset;
- elems[i].isset_offset = 0; /* not supported for local relopts yet */
i++;
}
diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c
index 733ef40ae7..977babff54 100644
--- a/src/backend/commands/vacuum.c
+++ b/src/backend/commands/vacuum.c
@@ -2221,9 +2221,9 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams params,
{
StdRdOptions *opts = (StdRdOptions *) rel->rd_options;
- if (opts && opts->vacuum_truncate_set)
+ if (opts && opts->vacuum_truncate != TERNARY_UNSET)
{
- if (opts->vacuum_truncate)
+ if (opts->vacuum_truncate == TERNARY_TRUE)
params.truncate = VACOPTVALUE_ENABLED;
else
params.truncate = VACOPTVALUE_DISABLED;
diff --git a/src/include/access/reloptions.h b/src/include/access/reloptions.h
index a604a4702c..a436697658 100644
--- a/src/include/access/reloptions.h
+++ b/src/include/access/reloptions.h
@@ -29,6 +29,7 @@
typedef enum relopt_type
{
RELOPT_TYPE_BOOL,
+ RELOPT_TYPE_TERNARY, /* on, off, unset */
RELOPT_TYPE_INT,
RELOPT_TYPE_REAL,
RELOPT_TYPE_ENUM,
@@ -80,6 +81,7 @@ typedef struct relopt_value
union
{
bool bool_val;
+ ternary ternary_val;
int int_val;
double real_val;
int enum_val;
@@ -94,6 +96,12 @@ typedef struct relopt_bool
bool default_val;
} relopt_bool;
+typedef struct relopt_rernary
+{
+ relopt_gen gen;
+ int default_val;
+} relopt_ternary;
+
typedef struct relopt_int
{
relopt_gen gen;
@@ -152,19 +160,6 @@ typedef struct
const char *optname; /* option's name */
relopt_type opttype; /* option's datatype */
int offset; /* offset of field in result struct */
-
- /*
- * isset_offset is an optional offset of a field in the result struct that
- * stores whether the option is explicitly set for the relation or if it
- * just picked up the default value. In most cases, this can be
- * accomplished by giving the reloption a special out-of-range default
- * value (e.g., some integer reloptions use -2), but this isn't always
- * possible. For example, a Boolean reloption cannot be given an
- * out-of-range default, so we need another way to discover the source of
- * its value. This offset is only used if given a value greater than
- * zero.
- */
- int isset_offset;
} relopt_parse_elt;
/* Local reloption definition */
@@ -195,6 +190,8 @@ typedef struct local_relopts
extern relopt_kind add_reloption_kind(void);
extern void add_bool_reloption(bits32 kinds, const char *name, const char *desc,
bool default_val, LOCKMODE lockmode);
+extern void add_ternary_reloption(bits32 kinds, const char *name,
+ const char *desc, int default_val, LOCKMODE lockmode);
extern void add_int_reloption(bits32 kinds, const char *name, const char *desc,
int default_val, int min_val, int max_val,
LOCKMODE lockmode);
@@ -214,6 +211,9 @@ extern void register_reloptions_validator(local_relopts *relopts,
extern void add_local_bool_reloption(local_relopts *relopts, const char *name,
const char *desc, bool default_val,
int offset);
+extern void add_local_ternary_reloption(local_relopts *relopts,
+ const char *name, const char *desc,
+ ternary default_val, int offset);
extern void add_local_int_reloption(local_relopts *relopts, const char *name,
const char *desc, int default_val,
int min_val, int max_val, int offset);
diff --git a/src/include/c.h b/src/include/c.h
index 39022f8a9d..9c59ccccf8 100644
--- a/src/include/c.h
+++ b/src/include/c.h
@@ -488,6 +488,22 @@ typedef void (*pg_funcptr_t) (void);
#include <stdbool.h>
+/*
+ * ternary
+ * Boolean value with an extrea "unset" option
+ *
+ * Ternary data type is used in relation options that can be "true", "false" or
+ * "unset". Since relation options are used deep inside the PostgreSQL code,
+ * this type is declared globally.
+*/
+
+typedef enum ternary
+{
+ TERNARY_FALSE = 0,
+ TERNARY_TRUE = 1,
+ TERNARY_UNSET = -1
+} ternary;
+
/* ----------------------------------------------------------------
* Section 3: standard system types
diff --git a/src/include/utils/rel.h b/src/include/utils/rel.h
index b552359915..7346d618f9 100644
--- a/src/include/utils/rel.h
+++ b/src/include/utils/rel.h
@@ -346,8 +346,7 @@ typedef struct StdRdOptions
bool user_catalog_table; /* use as an additional catalog relation */
int parallel_workers; /* max number of parallel workers */
StdRdOptIndexCleanup vacuum_index_cleanup; /* controls index vacuuming */
- bool vacuum_truncate; /* enables vacuum to truncate a relation */
- bool vacuum_truncate_set; /* whether vacuum_truncate is set */
+ ternary vacuum_truncate; /* enables vacuum to truncate a relation */
/*
* Fraction of pages in a relation that vacuum can eagerly scan and fail
--
2.39.2
--nextPart5129932.5fSG56mABF
Content-Disposition: attachment;
filename="v2-0003-Add-alias-to-be-used-as-unset-state.patch"
Content-Transfer-Encoding: 7Bit
Content-Type: text/x-patch; charset="unicode-2-0-utf-8";
name="v2-0003-Add-alias-to-be-used-as-unset-state.patch"
^ permalink raw reply [nested|flat] 868+ messages in thread
* [PATCH v2 2/4] Introduce ternary reloptions
@ 2025-09-04 14:41 Nikolay Shaplov <[email protected]>
0 siblings, 0 replies; 868+ messages in thread
From: Nikolay Shaplov @ 2025-09-04 14:41 UTC (permalink / raw)
Introduce ternary reloption as a replacement for current `vacuum_truncate`
implementation. Remove `vacuum_truncate_set` additional flag and using
`TERNARY_UNSET` value instead.
---
src/backend/access/common/reloptions.c | 129 ++++++++++++++++++++-----
src/backend/commands/vacuum.c | 4 +-
src/include/access/reloptions.h | 26 ++---
src/include/c.h | 16 +++
src/include/utils/rel.h | 3 +-
5 files changed, 135 insertions(+), 43 deletions(-)
diff --git a/src/backend/access/common/reloptions.c b/src/backend/access/common/reloptions.c
index 0af3fea68f..f543f11001 100644
--- a/src/backend/access/common/reloptions.c
+++ b/src/backend/access/common/reloptions.c
@@ -40,9 +40,9 @@
*
* To add an option:
*
- * (i) decide on a type (bool, integer, real, enum, string), name, default
- * value, upper and lower bounds (if applicable); for strings, consider a
- * validation routine.
+ * (i) decide on a type (bool, ternary, integer, real, enum, string), name,
+ * default value, upper and lower bounds (if applicable); for strings,
+ * consider a validation routine.
* (ii) add a record below (or use add_<type>_reloption).
* (iii) add it to the appropriate options struct (perhaps StdRdOptions)
* (iv) add it to the appropriate handling routine (perhaps
@@ -147,15 +147,6 @@ static relopt_bool boolRelOpts[] =
},
false
},
- {
- {
- "vacuum_truncate",
- "Enables vacuum to truncate empty pages at the end of this table",
- RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
- ShareUpdateExclusiveLock
- },
- true
- },
{
{
"deduplicate_items",
@@ -170,6 +161,21 @@ static relopt_bool boolRelOpts[] =
{{NULL}}
};
+static relopt_ternary ternaryRelOpts[] =
+{
+ {
+ {
+ "vacuum_truncate",
+ "Enables vacuum to truncate empty pages at the end of this table",
+ RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
+ ShareUpdateExclusiveLock
+ },
+ TERNARY_UNSET
+ },
+ /* list terminator */
+ {{NULL}}
+};
+
static relopt_int intRelOpts[] =
{
{
@@ -600,6 +606,13 @@ initialize_reloptions(void)
boolRelOpts[i].gen.lockmode));
j++;
}
+ for (i = 0; ternaryRelOpts[i].gen.name; i++)
+ {
+ Assert(DoLockModesConflict(ternaryRelOpts[i].gen.lockmode,
+ ternaryRelOpts[i].gen.lockmode));
+ j++;
+ }
+
for (i = 0; intRelOpts[i].gen.name; i++)
{
Assert(DoLockModesConflict(intRelOpts[i].gen.lockmode,
@@ -640,6 +653,14 @@ initialize_reloptions(void)
j++;
}
+ for (i = 0; ternaryRelOpts[i].gen.name; i++)
+ {
+ relOpts[j] = &ternaryRelOpts[i].gen;
+ relOpts[j]->type = RELOPT_TYPE_TERNARY;
+ relOpts[j]->namelen = strlen(relOpts[j]->name);
+ j++;
+ }
+
for (i = 0; intRelOpts[i].gen.name; i++)
{
relOpts[j] = &intRelOpts[i].gen;
@@ -800,6 +821,9 @@ allocate_reloption(bits32 kinds, int type, const char *name, const char *desc,
case RELOPT_TYPE_BOOL:
size = sizeof(relopt_bool);
break;
+ case RELOPT_TYPE_TERNARY:
+ size = sizeof(relopt_ternary);
+ break;
case RELOPT_TYPE_INT:
size = sizeof(relopt_int);
break;
@@ -883,6 +907,54 @@ add_local_bool_reloption(local_relopts *relopts, const char *name,
add_local_reloption(relopts, (relopt_gen *) newoption, offset);
}
+/*
+ * init_ternary_reloption
+ * Allocate and initialize a new ternary reloption
+ */
+static relopt_ternary *
+init_ternary_reloption(bits32 kinds, const char *name, const char *desc,
+ ternary default_val, LOCKMODE lockmode)
+{
+ relopt_ternary *newoption;
+
+ newoption = (relopt_ternary *) allocate_reloption(kinds,
+ RELOPT_TYPE_TERNARY, name, desc, lockmode);
+ newoption->default_val = default_val;
+
+ return newoption;
+}
+
+/*
+ * add_ternary_reloption
+ * Add a new ternary reloption
+ */
+void
+add_ternary_reloption(bits32 kinds, const char *name, const char *desc,
+ ternary default_val, LOCKMODE lockmode)
+{
+ relopt_ternary *newoption = init_ternary_reloption(kinds, name, desc,
+ default_val, lockmode);
+
+ add_reloption((relopt_gen *) newoption);
+}
+
+/*
+ * add_local_ternary_reloption
+ * Add a new ternary local reloption
+ *
+ * 'offset' is offset of ternary-typed field.
+ */
+void
+add_local_ternary_reloption(local_relopts *relopts, const char *name,
+ const char *desc, ternary default_val,
+ int offset)
+{
+ relopt_ternary *newoption = init_ternary_reloption(RELOPT_KIND_LOCAL,
+ name, desc,
+ default_val, 0);
+
+ add_local_reloption(relopts, (relopt_gen *) newoption, offset);
+}
/*
* init_real_reloption
@@ -1617,6 +1689,18 @@ parse_one_reloption(relopt_value *option, char *text_str, int text_len,
option->gen->name, value)));
}
break;
+ case RELOPT_TYPE_TERNARY:
+ {
+ bool b;
+ parsed = parse_bool(value, &b);
+ option->values.ternary_val = b ? TERNARY_TRUE : TERNARY_FALSE;
+ if (validate && !parsed)
+ ereport(ERROR,
+ (errcode(ERRCODE_INVALID_PARAMETER_VALUE),
+ errmsg("invalid value for ternary option \"%s\": %s",
+ option->gen->name, value)));
+ }
+ break;
case RELOPT_TYPE_INT:
{
relopt_int *optint = (relopt_int *) option->gen;
@@ -1780,17 +1864,6 @@ fillRelOptions(void *rdopts, Size basesize,
char *itempos = ((char *) rdopts) + elems[j].offset;
char *string_val;
- /*
- * If isset_offset is provided, store whether the reloption is
- * set there.
- */
- if (elems[j].isset_offset > 0)
- {
- char *setpos = ((char *) rdopts) + elems[j].isset_offset;
-
- *(bool *) setpos = options[i].isset;
- }
-
switch (options[i].gen->type)
{
case RELOPT_TYPE_BOOL:
@@ -1798,6 +1871,11 @@ fillRelOptions(void *rdopts, Size basesize,
options[i].values.bool_val :
((relopt_bool *) options[i].gen)->default_val;
break;
+ case RELOPT_TYPE_TERNARY:
+ *(ternary *) itempos = options[i].isset ?
+ options[i].values.ternary_val :
+ ((relopt_ternary *) options[i].gen)->default_val;
+ break;
case RELOPT_TYPE_INT:
*(int *) itempos = options[i].isset ?
options[i].values.int_val :
@@ -1912,8 +1990,8 @@ default_reloptions(Datum reloptions, bool validate, relopt_kind kind)
offsetof(StdRdOptions, parallel_workers)},
{"vacuum_index_cleanup", RELOPT_TYPE_ENUM,
offsetof(StdRdOptions, vacuum_index_cleanup)},
- {"vacuum_truncate", RELOPT_TYPE_BOOL,
- offsetof(StdRdOptions, vacuum_truncate), offsetof(StdRdOptions, vacuum_truncate_set)},
+ {"vacuum_truncate", RELOPT_TYPE_TERNARY,
+ offsetof(StdRdOptions, vacuum_truncate)},
{"vacuum_max_eager_freeze_failure_rate", RELOPT_TYPE_REAL,
offsetof(StdRdOptions, vacuum_max_eager_freeze_failure_rate)}
};
@@ -1993,7 +2071,6 @@ build_local_reloptions(local_relopts *relopts, Datum options, bool validate)
elems[i].optname = opt->option->name;
elems[i].opttype = opt->option->type;
elems[i].offset = opt->offset;
- elems[i].isset_offset = 0; /* not supported for local relopts yet */
i++;
}
diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c
index 733ef40ae7..977babff54 100644
--- a/src/backend/commands/vacuum.c
+++ b/src/backend/commands/vacuum.c
@@ -2221,9 +2221,9 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams params,
{
StdRdOptions *opts = (StdRdOptions *) rel->rd_options;
- if (opts && opts->vacuum_truncate_set)
+ if (opts && opts->vacuum_truncate != TERNARY_UNSET)
{
- if (opts->vacuum_truncate)
+ if (opts->vacuum_truncate == TERNARY_TRUE)
params.truncate = VACOPTVALUE_ENABLED;
else
params.truncate = VACOPTVALUE_DISABLED;
diff --git a/src/include/access/reloptions.h b/src/include/access/reloptions.h
index a604a4702c..a436697658 100644
--- a/src/include/access/reloptions.h
+++ b/src/include/access/reloptions.h
@@ -29,6 +29,7 @@
typedef enum relopt_type
{
RELOPT_TYPE_BOOL,
+ RELOPT_TYPE_TERNARY, /* on, off, unset */
RELOPT_TYPE_INT,
RELOPT_TYPE_REAL,
RELOPT_TYPE_ENUM,
@@ -80,6 +81,7 @@ typedef struct relopt_value
union
{
bool bool_val;
+ ternary ternary_val;
int int_val;
double real_val;
int enum_val;
@@ -94,6 +96,12 @@ typedef struct relopt_bool
bool default_val;
} relopt_bool;
+typedef struct relopt_rernary
+{
+ relopt_gen gen;
+ int default_val;
+} relopt_ternary;
+
typedef struct relopt_int
{
relopt_gen gen;
@@ -152,19 +160,6 @@ typedef struct
const char *optname; /* option's name */
relopt_type opttype; /* option's datatype */
int offset; /* offset of field in result struct */
-
- /*
- * isset_offset is an optional offset of a field in the result struct that
- * stores whether the option is explicitly set for the relation or if it
- * just picked up the default value. In most cases, this can be
- * accomplished by giving the reloption a special out-of-range default
- * value (e.g., some integer reloptions use -2), but this isn't always
- * possible. For example, a Boolean reloption cannot be given an
- * out-of-range default, so we need another way to discover the source of
- * its value. This offset is only used if given a value greater than
- * zero.
- */
- int isset_offset;
} relopt_parse_elt;
/* Local reloption definition */
@@ -195,6 +190,8 @@ typedef struct local_relopts
extern relopt_kind add_reloption_kind(void);
extern void add_bool_reloption(bits32 kinds, const char *name, const char *desc,
bool default_val, LOCKMODE lockmode);
+extern void add_ternary_reloption(bits32 kinds, const char *name,
+ const char *desc, int default_val, LOCKMODE lockmode);
extern void add_int_reloption(bits32 kinds, const char *name, const char *desc,
int default_val, int min_val, int max_val,
LOCKMODE lockmode);
@@ -214,6 +211,9 @@ extern void register_reloptions_validator(local_relopts *relopts,
extern void add_local_bool_reloption(local_relopts *relopts, const char *name,
const char *desc, bool default_val,
int offset);
+extern void add_local_ternary_reloption(local_relopts *relopts,
+ const char *name, const char *desc,
+ ternary default_val, int offset);
extern void add_local_int_reloption(local_relopts *relopts, const char *name,
const char *desc, int default_val,
int min_val, int max_val, int offset);
diff --git a/src/include/c.h b/src/include/c.h
index 39022f8a9d..9c59ccccf8 100644
--- a/src/include/c.h
+++ b/src/include/c.h
@@ -488,6 +488,22 @@ typedef void (*pg_funcptr_t) (void);
#include <stdbool.h>
+/*
+ * ternary
+ * Boolean value with an extrea "unset" option
+ *
+ * Ternary data type is used in relation options that can be "true", "false" or
+ * "unset". Since relation options are used deep inside the PostgreSQL code,
+ * this type is declared globally.
+*/
+
+typedef enum ternary
+{
+ TERNARY_FALSE = 0,
+ TERNARY_TRUE = 1,
+ TERNARY_UNSET = -1
+} ternary;
+
/* ----------------------------------------------------------------
* Section 3: standard system types
diff --git a/src/include/utils/rel.h b/src/include/utils/rel.h
index b552359915..7346d618f9 100644
--- a/src/include/utils/rel.h
+++ b/src/include/utils/rel.h
@@ -346,8 +346,7 @@ typedef struct StdRdOptions
bool user_catalog_table; /* use as an additional catalog relation */
int parallel_workers; /* max number of parallel workers */
StdRdOptIndexCleanup vacuum_index_cleanup; /* controls index vacuuming */
- bool vacuum_truncate; /* enables vacuum to truncate a relation */
- bool vacuum_truncate_set; /* whether vacuum_truncate is set */
+ ternary vacuum_truncate; /* enables vacuum to truncate a relation */
/*
* Fraction of pages in a relation that vacuum can eagerly scan and fail
--
2.39.2
--nextPart5129932.5fSG56mABF
Content-Disposition: attachment;
filename="v2-0003-Add-alias-to-be-used-as-unset-state.patch"
Content-Transfer-Encoding: 7Bit
Content-Type: text/x-patch; charset="unicode-2-0-utf-8";
name="v2-0003-Add-alias-to-be-used-as-unset-state.patch"
^ permalink raw reply [nested|flat] 868+ messages in thread
* [PATCH v2 2/4] Introduce ternary reloptions
@ 2025-09-04 14:41 Nikolay Shaplov <[email protected]>
0 siblings, 0 replies; 868+ messages in thread
From: Nikolay Shaplov @ 2025-09-04 14:41 UTC (permalink / raw)
Introduce ternary reloption as a replacement for current `vacuum_truncate`
implementation. Remove `vacuum_truncate_set` additional flag and using
`TERNARY_UNSET` value instead.
---
src/backend/access/common/reloptions.c | 129 ++++++++++++++++++++-----
src/backend/commands/vacuum.c | 4 +-
src/include/access/reloptions.h | 26 ++---
src/include/c.h | 16 +++
src/include/utils/rel.h | 3 +-
5 files changed, 135 insertions(+), 43 deletions(-)
diff --git a/src/backend/access/common/reloptions.c b/src/backend/access/common/reloptions.c
index 0af3fea68f..f543f11001 100644
--- a/src/backend/access/common/reloptions.c
+++ b/src/backend/access/common/reloptions.c
@@ -40,9 +40,9 @@
*
* To add an option:
*
- * (i) decide on a type (bool, integer, real, enum, string), name, default
- * value, upper and lower bounds (if applicable); for strings, consider a
- * validation routine.
+ * (i) decide on a type (bool, ternary, integer, real, enum, string), name,
+ * default value, upper and lower bounds (if applicable); for strings,
+ * consider a validation routine.
* (ii) add a record below (or use add_<type>_reloption).
* (iii) add it to the appropriate options struct (perhaps StdRdOptions)
* (iv) add it to the appropriate handling routine (perhaps
@@ -147,15 +147,6 @@ static relopt_bool boolRelOpts[] =
},
false
},
- {
- {
- "vacuum_truncate",
- "Enables vacuum to truncate empty pages at the end of this table",
- RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
- ShareUpdateExclusiveLock
- },
- true
- },
{
{
"deduplicate_items",
@@ -170,6 +161,21 @@ static relopt_bool boolRelOpts[] =
{{NULL}}
};
+static relopt_ternary ternaryRelOpts[] =
+{
+ {
+ {
+ "vacuum_truncate",
+ "Enables vacuum to truncate empty pages at the end of this table",
+ RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
+ ShareUpdateExclusiveLock
+ },
+ TERNARY_UNSET
+ },
+ /* list terminator */
+ {{NULL}}
+};
+
static relopt_int intRelOpts[] =
{
{
@@ -600,6 +606,13 @@ initialize_reloptions(void)
boolRelOpts[i].gen.lockmode));
j++;
}
+ for (i = 0; ternaryRelOpts[i].gen.name; i++)
+ {
+ Assert(DoLockModesConflict(ternaryRelOpts[i].gen.lockmode,
+ ternaryRelOpts[i].gen.lockmode));
+ j++;
+ }
+
for (i = 0; intRelOpts[i].gen.name; i++)
{
Assert(DoLockModesConflict(intRelOpts[i].gen.lockmode,
@@ -640,6 +653,14 @@ initialize_reloptions(void)
j++;
}
+ for (i = 0; ternaryRelOpts[i].gen.name; i++)
+ {
+ relOpts[j] = &ternaryRelOpts[i].gen;
+ relOpts[j]->type = RELOPT_TYPE_TERNARY;
+ relOpts[j]->namelen = strlen(relOpts[j]->name);
+ j++;
+ }
+
for (i = 0; intRelOpts[i].gen.name; i++)
{
relOpts[j] = &intRelOpts[i].gen;
@@ -800,6 +821,9 @@ allocate_reloption(bits32 kinds, int type, const char *name, const char *desc,
case RELOPT_TYPE_BOOL:
size = sizeof(relopt_bool);
break;
+ case RELOPT_TYPE_TERNARY:
+ size = sizeof(relopt_ternary);
+ break;
case RELOPT_TYPE_INT:
size = sizeof(relopt_int);
break;
@@ -883,6 +907,54 @@ add_local_bool_reloption(local_relopts *relopts, const char *name,
add_local_reloption(relopts, (relopt_gen *) newoption, offset);
}
+/*
+ * init_ternary_reloption
+ * Allocate and initialize a new ternary reloption
+ */
+static relopt_ternary *
+init_ternary_reloption(bits32 kinds, const char *name, const char *desc,
+ ternary default_val, LOCKMODE lockmode)
+{
+ relopt_ternary *newoption;
+
+ newoption = (relopt_ternary *) allocate_reloption(kinds,
+ RELOPT_TYPE_TERNARY, name, desc, lockmode);
+ newoption->default_val = default_val;
+
+ return newoption;
+}
+
+/*
+ * add_ternary_reloption
+ * Add a new ternary reloption
+ */
+void
+add_ternary_reloption(bits32 kinds, const char *name, const char *desc,
+ ternary default_val, LOCKMODE lockmode)
+{
+ relopt_ternary *newoption = init_ternary_reloption(kinds, name, desc,
+ default_val, lockmode);
+
+ add_reloption((relopt_gen *) newoption);
+}
+
+/*
+ * add_local_ternary_reloption
+ * Add a new ternary local reloption
+ *
+ * 'offset' is offset of ternary-typed field.
+ */
+void
+add_local_ternary_reloption(local_relopts *relopts, const char *name,
+ const char *desc, ternary default_val,
+ int offset)
+{
+ relopt_ternary *newoption = init_ternary_reloption(RELOPT_KIND_LOCAL,
+ name, desc,
+ default_val, 0);
+
+ add_local_reloption(relopts, (relopt_gen *) newoption, offset);
+}
/*
* init_real_reloption
@@ -1617,6 +1689,18 @@ parse_one_reloption(relopt_value *option, char *text_str, int text_len,
option->gen->name, value)));
}
break;
+ case RELOPT_TYPE_TERNARY:
+ {
+ bool b;
+ parsed = parse_bool(value, &b);
+ option->values.ternary_val = b ? TERNARY_TRUE : TERNARY_FALSE;
+ if (validate && !parsed)
+ ereport(ERROR,
+ (errcode(ERRCODE_INVALID_PARAMETER_VALUE),
+ errmsg("invalid value for ternary option \"%s\": %s",
+ option->gen->name, value)));
+ }
+ break;
case RELOPT_TYPE_INT:
{
relopt_int *optint = (relopt_int *) option->gen;
@@ -1780,17 +1864,6 @@ fillRelOptions(void *rdopts, Size basesize,
char *itempos = ((char *) rdopts) + elems[j].offset;
char *string_val;
- /*
- * If isset_offset is provided, store whether the reloption is
- * set there.
- */
- if (elems[j].isset_offset > 0)
- {
- char *setpos = ((char *) rdopts) + elems[j].isset_offset;
-
- *(bool *) setpos = options[i].isset;
- }
-
switch (options[i].gen->type)
{
case RELOPT_TYPE_BOOL:
@@ -1798,6 +1871,11 @@ fillRelOptions(void *rdopts, Size basesize,
options[i].values.bool_val :
((relopt_bool *) options[i].gen)->default_val;
break;
+ case RELOPT_TYPE_TERNARY:
+ *(ternary *) itempos = options[i].isset ?
+ options[i].values.ternary_val :
+ ((relopt_ternary *) options[i].gen)->default_val;
+ break;
case RELOPT_TYPE_INT:
*(int *) itempos = options[i].isset ?
options[i].values.int_val :
@@ -1912,8 +1990,8 @@ default_reloptions(Datum reloptions, bool validate, relopt_kind kind)
offsetof(StdRdOptions, parallel_workers)},
{"vacuum_index_cleanup", RELOPT_TYPE_ENUM,
offsetof(StdRdOptions, vacuum_index_cleanup)},
- {"vacuum_truncate", RELOPT_TYPE_BOOL,
- offsetof(StdRdOptions, vacuum_truncate), offsetof(StdRdOptions, vacuum_truncate_set)},
+ {"vacuum_truncate", RELOPT_TYPE_TERNARY,
+ offsetof(StdRdOptions, vacuum_truncate)},
{"vacuum_max_eager_freeze_failure_rate", RELOPT_TYPE_REAL,
offsetof(StdRdOptions, vacuum_max_eager_freeze_failure_rate)}
};
@@ -1993,7 +2071,6 @@ build_local_reloptions(local_relopts *relopts, Datum options, bool validate)
elems[i].optname = opt->option->name;
elems[i].opttype = opt->option->type;
elems[i].offset = opt->offset;
- elems[i].isset_offset = 0; /* not supported for local relopts yet */
i++;
}
diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c
index 733ef40ae7..977babff54 100644
--- a/src/backend/commands/vacuum.c
+++ b/src/backend/commands/vacuum.c
@@ -2221,9 +2221,9 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams params,
{
StdRdOptions *opts = (StdRdOptions *) rel->rd_options;
- if (opts && opts->vacuum_truncate_set)
+ if (opts && opts->vacuum_truncate != TERNARY_UNSET)
{
- if (opts->vacuum_truncate)
+ if (opts->vacuum_truncate == TERNARY_TRUE)
params.truncate = VACOPTVALUE_ENABLED;
else
params.truncate = VACOPTVALUE_DISABLED;
diff --git a/src/include/access/reloptions.h b/src/include/access/reloptions.h
index a604a4702c..a436697658 100644
--- a/src/include/access/reloptions.h
+++ b/src/include/access/reloptions.h
@@ -29,6 +29,7 @@
typedef enum relopt_type
{
RELOPT_TYPE_BOOL,
+ RELOPT_TYPE_TERNARY, /* on, off, unset */
RELOPT_TYPE_INT,
RELOPT_TYPE_REAL,
RELOPT_TYPE_ENUM,
@@ -80,6 +81,7 @@ typedef struct relopt_value
union
{
bool bool_val;
+ ternary ternary_val;
int int_val;
double real_val;
int enum_val;
@@ -94,6 +96,12 @@ typedef struct relopt_bool
bool default_val;
} relopt_bool;
+typedef struct relopt_rernary
+{
+ relopt_gen gen;
+ int default_val;
+} relopt_ternary;
+
typedef struct relopt_int
{
relopt_gen gen;
@@ -152,19 +160,6 @@ typedef struct
const char *optname; /* option's name */
relopt_type opttype; /* option's datatype */
int offset; /* offset of field in result struct */
-
- /*
- * isset_offset is an optional offset of a field in the result struct that
- * stores whether the option is explicitly set for the relation or if it
- * just picked up the default value. In most cases, this can be
- * accomplished by giving the reloption a special out-of-range default
- * value (e.g., some integer reloptions use -2), but this isn't always
- * possible. For example, a Boolean reloption cannot be given an
- * out-of-range default, so we need another way to discover the source of
- * its value. This offset is only used if given a value greater than
- * zero.
- */
- int isset_offset;
} relopt_parse_elt;
/* Local reloption definition */
@@ -195,6 +190,8 @@ typedef struct local_relopts
extern relopt_kind add_reloption_kind(void);
extern void add_bool_reloption(bits32 kinds, const char *name, const char *desc,
bool default_val, LOCKMODE lockmode);
+extern void add_ternary_reloption(bits32 kinds, const char *name,
+ const char *desc, int default_val, LOCKMODE lockmode);
extern void add_int_reloption(bits32 kinds, const char *name, const char *desc,
int default_val, int min_val, int max_val,
LOCKMODE lockmode);
@@ -214,6 +211,9 @@ extern void register_reloptions_validator(local_relopts *relopts,
extern void add_local_bool_reloption(local_relopts *relopts, const char *name,
const char *desc, bool default_val,
int offset);
+extern void add_local_ternary_reloption(local_relopts *relopts,
+ const char *name, const char *desc,
+ ternary default_val, int offset);
extern void add_local_int_reloption(local_relopts *relopts, const char *name,
const char *desc, int default_val,
int min_val, int max_val, int offset);
diff --git a/src/include/c.h b/src/include/c.h
index 39022f8a9d..9c59ccccf8 100644
--- a/src/include/c.h
+++ b/src/include/c.h
@@ -488,6 +488,22 @@ typedef void (*pg_funcptr_t) (void);
#include <stdbool.h>
+/*
+ * ternary
+ * Boolean value with an extrea "unset" option
+ *
+ * Ternary data type is used in relation options that can be "true", "false" or
+ * "unset". Since relation options are used deep inside the PostgreSQL code,
+ * this type is declared globally.
+*/
+
+typedef enum ternary
+{
+ TERNARY_FALSE = 0,
+ TERNARY_TRUE = 1,
+ TERNARY_UNSET = -1
+} ternary;
+
/* ----------------------------------------------------------------
* Section 3: standard system types
diff --git a/src/include/utils/rel.h b/src/include/utils/rel.h
index b552359915..7346d618f9 100644
--- a/src/include/utils/rel.h
+++ b/src/include/utils/rel.h
@@ -346,8 +346,7 @@ typedef struct StdRdOptions
bool user_catalog_table; /* use as an additional catalog relation */
int parallel_workers; /* max number of parallel workers */
StdRdOptIndexCleanup vacuum_index_cleanup; /* controls index vacuuming */
- bool vacuum_truncate; /* enables vacuum to truncate a relation */
- bool vacuum_truncate_set; /* whether vacuum_truncate is set */
+ ternary vacuum_truncate; /* enables vacuum to truncate a relation */
/*
* Fraction of pages in a relation that vacuum can eagerly scan and fail
--
2.39.2
--nextPart5129932.5fSG56mABF
Content-Disposition: attachment;
filename="v2-0003-Add-alias-to-be-used-as-unset-state.patch"
Content-Transfer-Encoding: 7Bit
Content-Type: text/x-patch; charset="unicode-2-0-utf-8";
name="v2-0003-Add-alias-to-be-used-as-unset-state.patch"
^ permalink raw reply [nested|flat] 868+ messages in thread
* [PATCH v2a 2/4] Introduce ternary reloptions
@ 2025-09-04 14:41 Nikolay Shaplov <[email protected]>
0 siblings, 0 replies; 868+ messages in thread
From: Nikolay Shaplov @ 2025-09-04 14:41 UTC (permalink / raw)
Introduce ternary reloption as a replacement for current `vacuum_truncate`
implementation. Remove `vacuum_truncate_set` additional flag and using
`TERNARY_UNSET` value instead.
---
src/backend/access/common/reloptions.c | 129 ++++++++++++++++++++-----
src/backend/commands/vacuum.c | 4 +-
src/include/access/reloptions.h | 26 ++---
src/include/c.h | 16 +++
src/include/utils/rel.h | 3 +-
5 files changed, 135 insertions(+), 43 deletions(-)
diff --git a/src/backend/access/common/reloptions.c b/src/backend/access/common/reloptions.c
index 0af3fea68f..f543f11001 100644
--- a/src/backend/access/common/reloptions.c
+++ b/src/backend/access/common/reloptions.c
@@ -40,9 +40,9 @@
*
* To add an option:
*
- * (i) decide on a type (bool, integer, real, enum, string), name, default
- * value, upper and lower bounds (if applicable); for strings, consider a
- * validation routine.
+ * (i) decide on a type (bool, ternary, integer, real, enum, string), name,
+ * default value, upper and lower bounds (if applicable); for strings,
+ * consider a validation routine.
* (ii) add a record below (or use add_<type>_reloption).
* (iii) add it to the appropriate options struct (perhaps StdRdOptions)
* (iv) add it to the appropriate handling routine (perhaps
@@ -147,15 +147,6 @@ static relopt_bool boolRelOpts[] =
},
false
},
- {
- {
- "vacuum_truncate",
- "Enables vacuum to truncate empty pages at the end of this table",
- RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
- ShareUpdateExclusiveLock
- },
- true
- },
{
{
"deduplicate_items",
@@ -170,6 +161,21 @@ static relopt_bool boolRelOpts[] =
{{NULL}}
};
+static relopt_ternary ternaryRelOpts[] =
+{
+ {
+ {
+ "vacuum_truncate",
+ "Enables vacuum to truncate empty pages at the end of this table",
+ RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
+ ShareUpdateExclusiveLock
+ },
+ TERNARY_UNSET
+ },
+ /* list terminator */
+ {{NULL}}
+};
+
static relopt_int intRelOpts[] =
{
{
@@ -600,6 +606,13 @@ initialize_reloptions(void)
boolRelOpts[i].gen.lockmode));
j++;
}
+ for (i = 0; ternaryRelOpts[i].gen.name; i++)
+ {
+ Assert(DoLockModesConflict(ternaryRelOpts[i].gen.lockmode,
+ ternaryRelOpts[i].gen.lockmode));
+ j++;
+ }
+
for (i = 0; intRelOpts[i].gen.name; i++)
{
Assert(DoLockModesConflict(intRelOpts[i].gen.lockmode,
@@ -640,6 +653,14 @@ initialize_reloptions(void)
j++;
}
+ for (i = 0; ternaryRelOpts[i].gen.name; i++)
+ {
+ relOpts[j] = &ternaryRelOpts[i].gen;
+ relOpts[j]->type = RELOPT_TYPE_TERNARY;
+ relOpts[j]->namelen = strlen(relOpts[j]->name);
+ j++;
+ }
+
for (i = 0; intRelOpts[i].gen.name; i++)
{
relOpts[j] = &intRelOpts[i].gen;
@@ -800,6 +821,9 @@ allocate_reloption(bits32 kinds, int type, const char *name, const char *desc,
case RELOPT_TYPE_BOOL:
size = sizeof(relopt_bool);
break;
+ case RELOPT_TYPE_TERNARY:
+ size = sizeof(relopt_ternary);
+ break;
case RELOPT_TYPE_INT:
size = sizeof(relopt_int);
break;
@@ -883,6 +907,54 @@ add_local_bool_reloption(local_relopts *relopts, const char *name,
add_local_reloption(relopts, (relopt_gen *) newoption, offset);
}
+/*
+ * init_ternary_reloption
+ * Allocate and initialize a new ternary reloption
+ */
+static relopt_ternary *
+init_ternary_reloption(bits32 kinds, const char *name, const char *desc,
+ ternary default_val, LOCKMODE lockmode)
+{
+ relopt_ternary *newoption;
+
+ newoption = (relopt_ternary *) allocate_reloption(kinds,
+ RELOPT_TYPE_TERNARY, name, desc, lockmode);
+ newoption->default_val = default_val;
+
+ return newoption;
+}
+
+/*
+ * add_ternary_reloption
+ * Add a new ternary reloption
+ */
+void
+add_ternary_reloption(bits32 kinds, const char *name, const char *desc,
+ ternary default_val, LOCKMODE lockmode)
+{
+ relopt_ternary *newoption = init_ternary_reloption(kinds, name, desc,
+ default_val, lockmode);
+
+ add_reloption((relopt_gen *) newoption);
+}
+
+/*
+ * add_local_ternary_reloption
+ * Add a new ternary local reloption
+ *
+ * 'offset' is offset of ternary-typed field.
+ */
+void
+add_local_ternary_reloption(local_relopts *relopts, const char *name,
+ const char *desc, ternary default_val,
+ int offset)
+{
+ relopt_ternary *newoption = init_ternary_reloption(RELOPT_KIND_LOCAL,
+ name, desc,
+ default_val, 0);
+
+ add_local_reloption(relopts, (relopt_gen *) newoption, offset);
+}
/*
* init_real_reloption
@@ -1617,6 +1689,18 @@ parse_one_reloption(relopt_value *option, char *text_str, int text_len,
option->gen->name, value)));
}
break;
+ case RELOPT_TYPE_TERNARY:
+ {
+ bool b;
+ parsed = parse_bool(value, &b);
+ option->values.ternary_val = b ? TERNARY_TRUE : TERNARY_FALSE;
+ if (validate && !parsed)
+ ereport(ERROR,
+ (errcode(ERRCODE_INVALID_PARAMETER_VALUE),
+ errmsg("invalid value for ternary option \"%s\": %s",
+ option->gen->name, value)));
+ }
+ break;
case RELOPT_TYPE_INT:
{
relopt_int *optint = (relopt_int *) option->gen;
@@ -1780,17 +1864,6 @@ fillRelOptions(void *rdopts, Size basesize,
char *itempos = ((char *) rdopts) + elems[j].offset;
char *string_val;
- /*
- * If isset_offset is provided, store whether the reloption is
- * set there.
- */
- if (elems[j].isset_offset > 0)
- {
- char *setpos = ((char *) rdopts) + elems[j].isset_offset;
-
- *(bool *) setpos = options[i].isset;
- }
-
switch (options[i].gen->type)
{
case RELOPT_TYPE_BOOL:
@@ -1798,6 +1871,11 @@ fillRelOptions(void *rdopts, Size basesize,
options[i].values.bool_val :
((relopt_bool *) options[i].gen)->default_val;
break;
+ case RELOPT_TYPE_TERNARY:
+ *(ternary *) itempos = options[i].isset ?
+ options[i].values.ternary_val :
+ ((relopt_ternary *) options[i].gen)->default_val;
+ break;
case RELOPT_TYPE_INT:
*(int *) itempos = options[i].isset ?
options[i].values.int_val :
@@ -1912,8 +1990,8 @@ default_reloptions(Datum reloptions, bool validate, relopt_kind kind)
offsetof(StdRdOptions, parallel_workers)},
{"vacuum_index_cleanup", RELOPT_TYPE_ENUM,
offsetof(StdRdOptions, vacuum_index_cleanup)},
- {"vacuum_truncate", RELOPT_TYPE_BOOL,
- offsetof(StdRdOptions, vacuum_truncate), offsetof(StdRdOptions, vacuum_truncate_set)},
+ {"vacuum_truncate", RELOPT_TYPE_TERNARY,
+ offsetof(StdRdOptions, vacuum_truncate)},
{"vacuum_max_eager_freeze_failure_rate", RELOPT_TYPE_REAL,
offsetof(StdRdOptions, vacuum_max_eager_freeze_failure_rate)}
};
@@ -1993,7 +2071,6 @@ build_local_reloptions(local_relopts *relopts, Datum options, bool validate)
elems[i].optname = opt->option->name;
elems[i].opttype = opt->option->type;
elems[i].offset = opt->offset;
- elems[i].isset_offset = 0; /* not supported for local relopts yet */
i++;
}
diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c
index 733ef40ae7..977babff54 100644
--- a/src/backend/commands/vacuum.c
+++ b/src/backend/commands/vacuum.c
@@ -2221,9 +2221,9 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams params,
{
StdRdOptions *opts = (StdRdOptions *) rel->rd_options;
- if (opts && opts->vacuum_truncate_set)
+ if (opts && opts->vacuum_truncate != TERNARY_UNSET)
{
- if (opts->vacuum_truncate)
+ if (opts->vacuum_truncate == TERNARY_TRUE)
params.truncate = VACOPTVALUE_ENABLED;
else
params.truncate = VACOPTVALUE_DISABLED;
diff --git a/src/include/access/reloptions.h b/src/include/access/reloptions.h
index a604a4702c..a436697658 100644
--- a/src/include/access/reloptions.h
+++ b/src/include/access/reloptions.h
@@ -29,6 +29,7 @@
typedef enum relopt_type
{
RELOPT_TYPE_BOOL,
+ RELOPT_TYPE_TERNARY, /* on, off, unset */
RELOPT_TYPE_INT,
RELOPT_TYPE_REAL,
RELOPT_TYPE_ENUM,
@@ -80,6 +81,7 @@ typedef struct relopt_value
union
{
bool bool_val;
+ ternary ternary_val;
int int_val;
double real_val;
int enum_val;
@@ -94,6 +96,12 @@ typedef struct relopt_bool
bool default_val;
} relopt_bool;
+typedef struct relopt_rernary
+{
+ relopt_gen gen;
+ int default_val;
+} relopt_ternary;
+
typedef struct relopt_int
{
relopt_gen gen;
@@ -152,19 +160,6 @@ typedef struct
const char *optname; /* option's name */
relopt_type opttype; /* option's datatype */
int offset; /* offset of field in result struct */
-
- /*
- * isset_offset is an optional offset of a field in the result struct that
- * stores whether the option is explicitly set for the relation or if it
- * just picked up the default value. In most cases, this can be
- * accomplished by giving the reloption a special out-of-range default
- * value (e.g., some integer reloptions use -2), but this isn't always
- * possible. For example, a Boolean reloption cannot be given an
- * out-of-range default, so we need another way to discover the source of
- * its value. This offset is only used if given a value greater than
- * zero.
- */
- int isset_offset;
} relopt_parse_elt;
/* Local reloption definition */
@@ -195,6 +190,8 @@ typedef struct local_relopts
extern relopt_kind add_reloption_kind(void);
extern void add_bool_reloption(bits32 kinds, const char *name, const char *desc,
bool default_val, LOCKMODE lockmode);
+extern void add_ternary_reloption(bits32 kinds, const char *name,
+ const char *desc, int default_val, LOCKMODE lockmode);
extern void add_int_reloption(bits32 kinds, const char *name, const char *desc,
int default_val, int min_val, int max_val,
LOCKMODE lockmode);
@@ -214,6 +211,9 @@ extern void register_reloptions_validator(local_relopts *relopts,
extern void add_local_bool_reloption(local_relopts *relopts, const char *name,
const char *desc, bool default_val,
int offset);
+extern void add_local_ternary_reloption(local_relopts *relopts,
+ const char *name, const char *desc,
+ ternary default_val, int offset);
extern void add_local_int_reloption(local_relopts *relopts, const char *name,
const char *desc, int default_val,
int min_val, int max_val, int offset);
diff --git a/src/include/c.h b/src/include/c.h
index 39022f8a9d..9c59ccccf8 100644
--- a/src/include/c.h
+++ b/src/include/c.h
@@ -488,6 +488,22 @@ typedef void (*pg_funcptr_t) (void);
#include <stdbool.h>
+/*
+ * ternary
+ * Boolean value with an extrea "unset" option
+ *
+ * Ternary data type is used in relation options that can be "true", "false" or
+ * "unset". Since relation options are used deep inside the PostgreSQL code,
+ * this type is declared globally.
+*/
+
+typedef enum ternary
+{
+ TERNARY_FALSE = 0,
+ TERNARY_TRUE = 1,
+ TERNARY_UNSET = -1
+} ternary;
+
/* ----------------------------------------------------------------
* Section 3: standard system types
diff --git a/src/include/utils/rel.h b/src/include/utils/rel.h
index b552359915..7346d618f9 100644
--- a/src/include/utils/rel.h
+++ b/src/include/utils/rel.h
@@ -346,8 +346,7 @@ typedef struct StdRdOptions
bool user_catalog_table; /* use as an additional catalog relation */
int parallel_workers; /* max number of parallel workers */
StdRdOptIndexCleanup vacuum_index_cleanup; /* controls index vacuuming */
- bool vacuum_truncate; /* enables vacuum to truncate a relation */
- bool vacuum_truncate_set; /* whether vacuum_truncate is set */
+ ternary vacuum_truncate; /* enables vacuum to truncate a relation */
/*
* Fraction of pages in a relation that vacuum can eagerly scan and fail
--
2.39.2
--nextPart4155600.3ZeAukHxDK
Content-Disposition: attachment;
filename="v2a-0003-Add-alias-to-be-used-as-unset-state.patch"
Content-Transfer-Encoding: 7Bit
Content-Type: text/x-patch; charset="unicode-2-0-utf-8";
name="v2a-0003-Add-alias-to-be-used-as-unset-state.patch"
^ permalink raw reply [nested|flat] 868+ messages in thread
* [PATCH v2 2/4] Introduce ternary reloptions
@ 2025-09-04 14:41 Nikolay Shaplov <[email protected]>
0 siblings, 0 replies; 868+ messages in thread
From: Nikolay Shaplov @ 2025-09-04 14:41 UTC (permalink / raw)
Introduce ternary reloption as a replacement for current `vacuum_truncate`
implementation. Remove `vacuum_truncate_set` additional flag and using
`TERNARY_UNSET` value instead.
---
src/backend/access/common/reloptions.c | 129 ++++++++++++++++++++-----
src/backend/commands/vacuum.c | 4 +-
src/include/access/reloptions.h | 26 ++---
src/include/c.h | 16 +++
src/include/utils/rel.h | 3 +-
5 files changed, 135 insertions(+), 43 deletions(-)
diff --git a/src/backend/access/common/reloptions.c b/src/backend/access/common/reloptions.c
index 0af3fea68f..f543f11001 100644
--- a/src/backend/access/common/reloptions.c
+++ b/src/backend/access/common/reloptions.c
@@ -40,9 +40,9 @@
*
* To add an option:
*
- * (i) decide on a type (bool, integer, real, enum, string), name, default
- * value, upper and lower bounds (if applicable); for strings, consider a
- * validation routine.
+ * (i) decide on a type (bool, ternary, integer, real, enum, string), name,
+ * default value, upper and lower bounds (if applicable); for strings,
+ * consider a validation routine.
* (ii) add a record below (or use add_<type>_reloption).
* (iii) add it to the appropriate options struct (perhaps StdRdOptions)
* (iv) add it to the appropriate handling routine (perhaps
@@ -147,15 +147,6 @@ static relopt_bool boolRelOpts[] =
},
false
},
- {
- {
- "vacuum_truncate",
- "Enables vacuum to truncate empty pages at the end of this table",
- RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
- ShareUpdateExclusiveLock
- },
- true
- },
{
{
"deduplicate_items",
@@ -170,6 +161,21 @@ static relopt_bool boolRelOpts[] =
{{NULL}}
};
+static relopt_ternary ternaryRelOpts[] =
+{
+ {
+ {
+ "vacuum_truncate",
+ "Enables vacuum to truncate empty pages at the end of this table",
+ RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
+ ShareUpdateExclusiveLock
+ },
+ TERNARY_UNSET
+ },
+ /* list terminator */
+ {{NULL}}
+};
+
static relopt_int intRelOpts[] =
{
{
@@ -600,6 +606,13 @@ initialize_reloptions(void)
boolRelOpts[i].gen.lockmode));
j++;
}
+ for (i = 0; ternaryRelOpts[i].gen.name; i++)
+ {
+ Assert(DoLockModesConflict(ternaryRelOpts[i].gen.lockmode,
+ ternaryRelOpts[i].gen.lockmode));
+ j++;
+ }
+
for (i = 0; intRelOpts[i].gen.name; i++)
{
Assert(DoLockModesConflict(intRelOpts[i].gen.lockmode,
@@ -640,6 +653,14 @@ initialize_reloptions(void)
j++;
}
+ for (i = 0; ternaryRelOpts[i].gen.name; i++)
+ {
+ relOpts[j] = &ternaryRelOpts[i].gen;
+ relOpts[j]->type = RELOPT_TYPE_TERNARY;
+ relOpts[j]->namelen = strlen(relOpts[j]->name);
+ j++;
+ }
+
for (i = 0; intRelOpts[i].gen.name; i++)
{
relOpts[j] = &intRelOpts[i].gen;
@@ -800,6 +821,9 @@ allocate_reloption(bits32 kinds, int type, const char *name, const char *desc,
case RELOPT_TYPE_BOOL:
size = sizeof(relopt_bool);
break;
+ case RELOPT_TYPE_TERNARY:
+ size = sizeof(relopt_ternary);
+ break;
case RELOPT_TYPE_INT:
size = sizeof(relopt_int);
break;
@@ -883,6 +907,54 @@ add_local_bool_reloption(local_relopts *relopts, const char *name,
add_local_reloption(relopts, (relopt_gen *) newoption, offset);
}
+/*
+ * init_ternary_reloption
+ * Allocate and initialize a new ternary reloption
+ */
+static relopt_ternary *
+init_ternary_reloption(bits32 kinds, const char *name, const char *desc,
+ ternary default_val, LOCKMODE lockmode)
+{
+ relopt_ternary *newoption;
+
+ newoption = (relopt_ternary *) allocate_reloption(kinds,
+ RELOPT_TYPE_TERNARY, name, desc, lockmode);
+ newoption->default_val = default_val;
+
+ return newoption;
+}
+
+/*
+ * add_ternary_reloption
+ * Add a new ternary reloption
+ */
+void
+add_ternary_reloption(bits32 kinds, const char *name, const char *desc,
+ ternary default_val, LOCKMODE lockmode)
+{
+ relopt_ternary *newoption = init_ternary_reloption(kinds, name, desc,
+ default_val, lockmode);
+
+ add_reloption((relopt_gen *) newoption);
+}
+
+/*
+ * add_local_ternary_reloption
+ * Add a new ternary local reloption
+ *
+ * 'offset' is offset of ternary-typed field.
+ */
+void
+add_local_ternary_reloption(local_relopts *relopts, const char *name,
+ const char *desc, ternary default_val,
+ int offset)
+{
+ relopt_ternary *newoption = init_ternary_reloption(RELOPT_KIND_LOCAL,
+ name, desc,
+ default_val, 0);
+
+ add_local_reloption(relopts, (relopt_gen *) newoption, offset);
+}
/*
* init_real_reloption
@@ -1617,6 +1689,18 @@ parse_one_reloption(relopt_value *option, char *text_str, int text_len,
option->gen->name, value)));
}
break;
+ case RELOPT_TYPE_TERNARY:
+ {
+ bool b;
+ parsed = parse_bool(value, &b);
+ option->values.ternary_val = b ? TERNARY_TRUE : TERNARY_FALSE;
+ if (validate && !parsed)
+ ereport(ERROR,
+ (errcode(ERRCODE_INVALID_PARAMETER_VALUE),
+ errmsg("invalid value for ternary option \"%s\": %s",
+ option->gen->name, value)));
+ }
+ break;
case RELOPT_TYPE_INT:
{
relopt_int *optint = (relopt_int *) option->gen;
@@ -1780,17 +1864,6 @@ fillRelOptions(void *rdopts, Size basesize,
char *itempos = ((char *) rdopts) + elems[j].offset;
char *string_val;
- /*
- * If isset_offset is provided, store whether the reloption is
- * set there.
- */
- if (elems[j].isset_offset > 0)
- {
- char *setpos = ((char *) rdopts) + elems[j].isset_offset;
-
- *(bool *) setpos = options[i].isset;
- }
-
switch (options[i].gen->type)
{
case RELOPT_TYPE_BOOL:
@@ -1798,6 +1871,11 @@ fillRelOptions(void *rdopts, Size basesize,
options[i].values.bool_val :
((relopt_bool *) options[i].gen)->default_val;
break;
+ case RELOPT_TYPE_TERNARY:
+ *(ternary *) itempos = options[i].isset ?
+ options[i].values.ternary_val :
+ ((relopt_ternary *) options[i].gen)->default_val;
+ break;
case RELOPT_TYPE_INT:
*(int *) itempos = options[i].isset ?
options[i].values.int_val :
@@ -1912,8 +1990,8 @@ default_reloptions(Datum reloptions, bool validate, relopt_kind kind)
offsetof(StdRdOptions, parallel_workers)},
{"vacuum_index_cleanup", RELOPT_TYPE_ENUM,
offsetof(StdRdOptions, vacuum_index_cleanup)},
- {"vacuum_truncate", RELOPT_TYPE_BOOL,
- offsetof(StdRdOptions, vacuum_truncate), offsetof(StdRdOptions, vacuum_truncate_set)},
+ {"vacuum_truncate", RELOPT_TYPE_TERNARY,
+ offsetof(StdRdOptions, vacuum_truncate)},
{"vacuum_max_eager_freeze_failure_rate", RELOPT_TYPE_REAL,
offsetof(StdRdOptions, vacuum_max_eager_freeze_failure_rate)}
};
@@ -1993,7 +2071,6 @@ build_local_reloptions(local_relopts *relopts, Datum options, bool validate)
elems[i].optname = opt->option->name;
elems[i].opttype = opt->option->type;
elems[i].offset = opt->offset;
- elems[i].isset_offset = 0; /* not supported for local relopts yet */
i++;
}
diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c
index 733ef40ae7..977babff54 100644
--- a/src/backend/commands/vacuum.c
+++ b/src/backend/commands/vacuum.c
@@ -2221,9 +2221,9 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams params,
{
StdRdOptions *opts = (StdRdOptions *) rel->rd_options;
- if (opts && opts->vacuum_truncate_set)
+ if (opts && opts->vacuum_truncate != TERNARY_UNSET)
{
- if (opts->vacuum_truncate)
+ if (opts->vacuum_truncate == TERNARY_TRUE)
params.truncate = VACOPTVALUE_ENABLED;
else
params.truncate = VACOPTVALUE_DISABLED;
diff --git a/src/include/access/reloptions.h b/src/include/access/reloptions.h
index a604a4702c..a436697658 100644
--- a/src/include/access/reloptions.h
+++ b/src/include/access/reloptions.h
@@ -29,6 +29,7 @@
typedef enum relopt_type
{
RELOPT_TYPE_BOOL,
+ RELOPT_TYPE_TERNARY, /* on, off, unset */
RELOPT_TYPE_INT,
RELOPT_TYPE_REAL,
RELOPT_TYPE_ENUM,
@@ -80,6 +81,7 @@ typedef struct relopt_value
union
{
bool bool_val;
+ ternary ternary_val;
int int_val;
double real_val;
int enum_val;
@@ -94,6 +96,12 @@ typedef struct relopt_bool
bool default_val;
} relopt_bool;
+typedef struct relopt_rernary
+{
+ relopt_gen gen;
+ int default_val;
+} relopt_ternary;
+
typedef struct relopt_int
{
relopt_gen gen;
@@ -152,19 +160,6 @@ typedef struct
const char *optname; /* option's name */
relopt_type opttype; /* option's datatype */
int offset; /* offset of field in result struct */
-
- /*
- * isset_offset is an optional offset of a field in the result struct that
- * stores whether the option is explicitly set for the relation or if it
- * just picked up the default value. In most cases, this can be
- * accomplished by giving the reloption a special out-of-range default
- * value (e.g., some integer reloptions use -2), but this isn't always
- * possible. For example, a Boolean reloption cannot be given an
- * out-of-range default, so we need another way to discover the source of
- * its value. This offset is only used if given a value greater than
- * zero.
- */
- int isset_offset;
} relopt_parse_elt;
/* Local reloption definition */
@@ -195,6 +190,8 @@ typedef struct local_relopts
extern relopt_kind add_reloption_kind(void);
extern void add_bool_reloption(bits32 kinds, const char *name, const char *desc,
bool default_val, LOCKMODE lockmode);
+extern void add_ternary_reloption(bits32 kinds, const char *name,
+ const char *desc, int default_val, LOCKMODE lockmode);
extern void add_int_reloption(bits32 kinds, const char *name, const char *desc,
int default_val, int min_val, int max_val,
LOCKMODE lockmode);
@@ -214,6 +211,9 @@ extern void register_reloptions_validator(local_relopts *relopts,
extern void add_local_bool_reloption(local_relopts *relopts, const char *name,
const char *desc, bool default_val,
int offset);
+extern void add_local_ternary_reloption(local_relopts *relopts,
+ const char *name, const char *desc,
+ ternary default_val, int offset);
extern void add_local_int_reloption(local_relopts *relopts, const char *name,
const char *desc, int default_val,
int min_val, int max_val, int offset);
diff --git a/src/include/c.h b/src/include/c.h
index 39022f8a9d..9c59ccccf8 100644
--- a/src/include/c.h
+++ b/src/include/c.h
@@ -488,6 +488,22 @@ typedef void (*pg_funcptr_t) (void);
#include <stdbool.h>
+/*
+ * ternary
+ * Boolean value with an extrea "unset" option
+ *
+ * Ternary data type is used in relation options that can be "true", "false" or
+ * "unset". Since relation options are used deep inside the PostgreSQL code,
+ * this type is declared globally.
+*/
+
+typedef enum ternary
+{
+ TERNARY_FALSE = 0,
+ TERNARY_TRUE = 1,
+ TERNARY_UNSET = -1
+} ternary;
+
/* ----------------------------------------------------------------
* Section 3: standard system types
diff --git a/src/include/utils/rel.h b/src/include/utils/rel.h
index b552359915..7346d618f9 100644
--- a/src/include/utils/rel.h
+++ b/src/include/utils/rel.h
@@ -346,8 +346,7 @@ typedef struct StdRdOptions
bool user_catalog_table; /* use as an additional catalog relation */
int parallel_workers; /* max number of parallel workers */
StdRdOptIndexCleanup vacuum_index_cleanup; /* controls index vacuuming */
- bool vacuum_truncate; /* enables vacuum to truncate a relation */
- bool vacuum_truncate_set; /* whether vacuum_truncate is set */
+ ternary vacuum_truncate; /* enables vacuum to truncate a relation */
/*
* Fraction of pages in a relation that vacuum can eagerly scan and fail
--
2.39.2
--nextPart5129932.5fSG56mABF
Content-Disposition: attachment;
filename="v2-0003-Add-alias-to-be-used-as-unset-state.patch"
Content-Transfer-Encoding: 7Bit
Content-Type: text/x-patch; charset="unicode-2-0-utf-8";
name="v2-0003-Add-alias-to-be-used-as-unset-state.patch"
^ permalink raw reply [nested|flat] 868+ messages in thread
* [PATCH v2a 2/4] Introduce ternary reloptions
@ 2025-09-04 14:41 Nikolay Shaplov <[email protected]>
0 siblings, 0 replies; 868+ messages in thread
From: Nikolay Shaplov @ 2025-09-04 14:41 UTC (permalink / raw)
Introduce ternary reloption as a replacement for current `vacuum_truncate`
implementation. Remove `vacuum_truncate_set` additional flag and using
`TERNARY_UNSET` value instead.
---
src/backend/access/common/reloptions.c | 129 ++++++++++++++++++++-----
src/backend/commands/vacuum.c | 4 +-
src/include/access/reloptions.h | 26 ++---
src/include/c.h | 16 +++
src/include/utils/rel.h | 3 +-
5 files changed, 135 insertions(+), 43 deletions(-)
diff --git a/src/backend/access/common/reloptions.c b/src/backend/access/common/reloptions.c
index 0af3fea68f..f543f11001 100644
--- a/src/backend/access/common/reloptions.c
+++ b/src/backend/access/common/reloptions.c
@@ -40,9 +40,9 @@
*
* To add an option:
*
- * (i) decide on a type (bool, integer, real, enum, string), name, default
- * value, upper and lower bounds (if applicable); for strings, consider a
- * validation routine.
+ * (i) decide on a type (bool, ternary, integer, real, enum, string), name,
+ * default value, upper and lower bounds (if applicable); for strings,
+ * consider a validation routine.
* (ii) add a record below (or use add_<type>_reloption).
* (iii) add it to the appropriate options struct (perhaps StdRdOptions)
* (iv) add it to the appropriate handling routine (perhaps
@@ -147,15 +147,6 @@ static relopt_bool boolRelOpts[] =
},
false
},
- {
- {
- "vacuum_truncate",
- "Enables vacuum to truncate empty pages at the end of this table",
- RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
- ShareUpdateExclusiveLock
- },
- true
- },
{
{
"deduplicate_items",
@@ -170,6 +161,21 @@ static relopt_bool boolRelOpts[] =
{{NULL}}
};
+static relopt_ternary ternaryRelOpts[] =
+{
+ {
+ {
+ "vacuum_truncate",
+ "Enables vacuum to truncate empty pages at the end of this table",
+ RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
+ ShareUpdateExclusiveLock
+ },
+ TERNARY_UNSET
+ },
+ /* list terminator */
+ {{NULL}}
+};
+
static relopt_int intRelOpts[] =
{
{
@@ -600,6 +606,13 @@ initialize_reloptions(void)
boolRelOpts[i].gen.lockmode));
j++;
}
+ for (i = 0; ternaryRelOpts[i].gen.name; i++)
+ {
+ Assert(DoLockModesConflict(ternaryRelOpts[i].gen.lockmode,
+ ternaryRelOpts[i].gen.lockmode));
+ j++;
+ }
+
for (i = 0; intRelOpts[i].gen.name; i++)
{
Assert(DoLockModesConflict(intRelOpts[i].gen.lockmode,
@@ -640,6 +653,14 @@ initialize_reloptions(void)
j++;
}
+ for (i = 0; ternaryRelOpts[i].gen.name; i++)
+ {
+ relOpts[j] = &ternaryRelOpts[i].gen;
+ relOpts[j]->type = RELOPT_TYPE_TERNARY;
+ relOpts[j]->namelen = strlen(relOpts[j]->name);
+ j++;
+ }
+
for (i = 0; intRelOpts[i].gen.name; i++)
{
relOpts[j] = &intRelOpts[i].gen;
@@ -800,6 +821,9 @@ allocate_reloption(bits32 kinds, int type, const char *name, const char *desc,
case RELOPT_TYPE_BOOL:
size = sizeof(relopt_bool);
break;
+ case RELOPT_TYPE_TERNARY:
+ size = sizeof(relopt_ternary);
+ break;
case RELOPT_TYPE_INT:
size = sizeof(relopt_int);
break;
@@ -883,6 +907,54 @@ add_local_bool_reloption(local_relopts *relopts, const char *name,
add_local_reloption(relopts, (relopt_gen *) newoption, offset);
}
+/*
+ * init_ternary_reloption
+ * Allocate and initialize a new ternary reloption
+ */
+static relopt_ternary *
+init_ternary_reloption(bits32 kinds, const char *name, const char *desc,
+ ternary default_val, LOCKMODE lockmode)
+{
+ relopt_ternary *newoption;
+
+ newoption = (relopt_ternary *) allocate_reloption(kinds,
+ RELOPT_TYPE_TERNARY, name, desc, lockmode);
+ newoption->default_val = default_val;
+
+ return newoption;
+}
+
+/*
+ * add_ternary_reloption
+ * Add a new ternary reloption
+ */
+void
+add_ternary_reloption(bits32 kinds, const char *name, const char *desc,
+ ternary default_val, LOCKMODE lockmode)
+{
+ relopt_ternary *newoption = init_ternary_reloption(kinds, name, desc,
+ default_val, lockmode);
+
+ add_reloption((relopt_gen *) newoption);
+}
+
+/*
+ * add_local_ternary_reloption
+ * Add a new ternary local reloption
+ *
+ * 'offset' is offset of ternary-typed field.
+ */
+void
+add_local_ternary_reloption(local_relopts *relopts, const char *name,
+ const char *desc, ternary default_val,
+ int offset)
+{
+ relopt_ternary *newoption = init_ternary_reloption(RELOPT_KIND_LOCAL,
+ name, desc,
+ default_val, 0);
+
+ add_local_reloption(relopts, (relopt_gen *) newoption, offset);
+}
/*
* init_real_reloption
@@ -1617,6 +1689,18 @@ parse_one_reloption(relopt_value *option, char *text_str, int text_len,
option->gen->name, value)));
}
break;
+ case RELOPT_TYPE_TERNARY:
+ {
+ bool b;
+ parsed = parse_bool(value, &b);
+ option->values.ternary_val = b ? TERNARY_TRUE : TERNARY_FALSE;
+ if (validate && !parsed)
+ ereport(ERROR,
+ (errcode(ERRCODE_INVALID_PARAMETER_VALUE),
+ errmsg("invalid value for ternary option \"%s\": %s",
+ option->gen->name, value)));
+ }
+ break;
case RELOPT_TYPE_INT:
{
relopt_int *optint = (relopt_int *) option->gen;
@@ -1780,17 +1864,6 @@ fillRelOptions(void *rdopts, Size basesize,
char *itempos = ((char *) rdopts) + elems[j].offset;
char *string_val;
- /*
- * If isset_offset is provided, store whether the reloption is
- * set there.
- */
- if (elems[j].isset_offset > 0)
- {
- char *setpos = ((char *) rdopts) + elems[j].isset_offset;
-
- *(bool *) setpos = options[i].isset;
- }
-
switch (options[i].gen->type)
{
case RELOPT_TYPE_BOOL:
@@ -1798,6 +1871,11 @@ fillRelOptions(void *rdopts, Size basesize,
options[i].values.bool_val :
((relopt_bool *) options[i].gen)->default_val;
break;
+ case RELOPT_TYPE_TERNARY:
+ *(ternary *) itempos = options[i].isset ?
+ options[i].values.ternary_val :
+ ((relopt_ternary *) options[i].gen)->default_val;
+ break;
case RELOPT_TYPE_INT:
*(int *) itempos = options[i].isset ?
options[i].values.int_val :
@@ -1912,8 +1990,8 @@ default_reloptions(Datum reloptions, bool validate, relopt_kind kind)
offsetof(StdRdOptions, parallel_workers)},
{"vacuum_index_cleanup", RELOPT_TYPE_ENUM,
offsetof(StdRdOptions, vacuum_index_cleanup)},
- {"vacuum_truncate", RELOPT_TYPE_BOOL,
- offsetof(StdRdOptions, vacuum_truncate), offsetof(StdRdOptions, vacuum_truncate_set)},
+ {"vacuum_truncate", RELOPT_TYPE_TERNARY,
+ offsetof(StdRdOptions, vacuum_truncate)},
{"vacuum_max_eager_freeze_failure_rate", RELOPT_TYPE_REAL,
offsetof(StdRdOptions, vacuum_max_eager_freeze_failure_rate)}
};
@@ -1993,7 +2071,6 @@ build_local_reloptions(local_relopts *relopts, Datum options, bool validate)
elems[i].optname = opt->option->name;
elems[i].opttype = opt->option->type;
elems[i].offset = opt->offset;
- elems[i].isset_offset = 0; /* not supported for local relopts yet */
i++;
}
diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c
index 733ef40ae7..977babff54 100644
--- a/src/backend/commands/vacuum.c
+++ b/src/backend/commands/vacuum.c
@@ -2221,9 +2221,9 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams params,
{
StdRdOptions *opts = (StdRdOptions *) rel->rd_options;
- if (opts && opts->vacuum_truncate_set)
+ if (opts && opts->vacuum_truncate != TERNARY_UNSET)
{
- if (opts->vacuum_truncate)
+ if (opts->vacuum_truncate == TERNARY_TRUE)
params.truncate = VACOPTVALUE_ENABLED;
else
params.truncate = VACOPTVALUE_DISABLED;
diff --git a/src/include/access/reloptions.h b/src/include/access/reloptions.h
index a604a4702c..a436697658 100644
--- a/src/include/access/reloptions.h
+++ b/src/include/access/reloptions.h
@@ -29,6 +29,7 @@
typedef enum relopt_type
{
RELOPT_TYPE_BOOL,
+ RELOPT_TYPE_TERNARY, /* on, off, unset */
RELOPT_TYPE_INT,
RELOPT_TYPE_REAL,
RELOPT_TYPE_ENUM,
@@ -80,6 +81,7 @@ typedef struct relopt_value
union
{
bool bool_val;
+ ternary ternary_val;
int int_val;
double real_val;
int enum_val;
@@ -94,6 +96,12 @@ typedef struct relopt_bool
bool default_val;
} relopt_bool;
+typedef struct relopt_rernary
+{
+ relopt_gen gen;
+ int default_val;
+} relopt_ternary;
+
typedef struct relopt_int
{
relopt_gen gen;
@@ -152,19 +160,6 @@ typedef struct
const char *optname; /* option's name */
relopt_type opttype; /* option's datatype */
int offset; /* offset of field in result struct */
-
- /*
- * isset_offset is an optional offset of a field in the result struct that
- * stores whether the option is explicitly set for the relation or if it
- * just picked up the default value. In most cases, this can be
- * accomplished by giving the reloption a special out-of-range default
- * value (e.g., some integer reloptions use -2), but this isn't always
- * possible. For example, a Boolean reloption cannot be given an
- * out-of-range default, so we need another way to discover the source of
- * its value. This offset is only used if given a value greater than
- * zero.
- */
- int isset_offset;
} relopt_parse_elt;
/* Local reloption definition */
@@ -195,6 +190,8 @@ typedef struct local_relopts
extern relopt_kind add_reloption_kind(void);
extern void add_bool_reloption(bits32 kinds, const char *name, const char *desc,
bool default_val, LOCKMODE lockmode);
+extern void add_ternary_reloption(bits32 kinds, const char *name,
+ const char *desc, int default_val, LOCKMODE lockmode);
extern void add_int_reloption(bits32 kinds, const char *name, const char *desc,
int default_val, int min_val, int max_val,
LOCKMODE lockmode);
@@ -214,6 +211,9 @@ extern void register_reloptions_validator(local_relopts *relopts,
extern void add_local_bool_reloption(local_relopts *relopts, const char *name,
const char *desc, bool default_val,
int offset);
+extern void add_local_ternary_reloption(local_relopts *relopts,
+ const char *name, const char *desc,
+ ternary default_val, int offset);
extern void add_local_int_reloption(local_relopts *relopts, const char *name,
const char *desc, int default_val,
int min_val, int max_val, int offset);
diff --git a/src/include/c.h b/src/include/c.h
index 39022f8a9d..9c59ccccf8 100644
--- a/src/include/c.h
+++ b/src/include/c.h
@@ -488,6 +488,22 @@ typedef void (*pg_funcptr_t) (void);
#include <stdbool.h>
+/*
+ * ternary
+ * Boolean value with an extrea "unset" option
+ *
+ * Ternary data type is used in relation options that can be "true", "false" or
+ * "unset". Since relation options are used deep inside the PostgreSQL code,
+ * this type is declared globally.
+*/
+
+typedef enum ternary
+{
+ TERNARY_FALSE = 0,
+ TERNARY_TRUE = 1,
+ TERNARY_UNSET = -1
+} ternary;
+
/* ----------------------------------------------------------------
* Section 3: standard system types
diff --git a/src/include/utils/rel.h b/src/include/utils/rel.h
index b552359915..7346d618f9 100644
--- a/src/include/utils/rel.h
+++ b/src/include/utils/rel.h
@@ -346,8 +346,7 @@ typedef struct StdRdOptions
bool user_catalog_table; /* use as an additional catalog relation */
int parallel_workers; /* max number of parallel workers */
StdRdOptIndexCleanup vacuum_index_cleanup; /* controls index vacuuming */
- bool vacuum_truncate; /* enables vacuum to truncate a relation */
- bool vacuum_truncate_set; /* whether vacuum_truncate is set */
+ ternary vacuum_truncate; /* enables vacuum to truncate a relation */
/*
* Fraction of pages in a relation that vacuum can eagerly scan and fail
--
2.39.2
--nextPart4155600.3ZeAukHxDK
Content-Disposition: attachment;
filename="v2a-0003-Add-alias-to-be-used-as-unset-state.patch"
Content-Transfer-Encoding: 7Bit
Content-Type: text/x-patch; charset="unicode-2-0-utf-8";
name="v2a-0003-Add-alias-to-be-used-as-unset-state.patch"
^ permalink raw reply [nested|flat] 868+ messages in thread
* [PATCH v2a 2/4] Introduce ternary reloptions
@ 2025-09-04 14:41 Nikolay Shaplov <[email protected]>
0 siblings, 0 replies; 868+ messages in thread
From: Nikolay Shaplov @ 2025-09-04 14:41 UTC (permalink / raw)
Introduce ternary reloption as a replacement for current `vacuum_truncate`
implementation. Remove `vacuum_truncate_set` additional flag and using
`TERNARY_UNSET` value instead.
---
src/backend/access/common/reloptions.c | 129 ++++++++++++++++++++-----
src/backend/commands/vacuum.c | 4 +-
src/include/access/reloptions.h | 26 ++---
src/include/c.h | 16 +++
src/include/utils/rel.h | 3 +-
5 files changed, 135 insertions(+), 43 deletions(-)
diff --git a/src/backend/access/common/reloptions.c b/src/backend/access/common/reloptions.c
index 0af3fea68f..f543f11001 100644
--- a/src/backend/access/common/reloptions.c
+++ b/src/backend/access/common/reloptions.c
@@ -40,9 +40,9 @@
*
* To add an option:
*
- * (i) decide on a type (bool, integer, real, enum, string), name, default
- * value, upper and lower bounds (if applicable); for strings, consider a
- * validation routine.
+ * (i) decide on a type (bool, ternary, integer, real, enum, string), name,
+ * default value, upper and lower bounds (if applicable); for strings,
+ * consider a validation routine.
* (ii) add a record below (or use add_<type>_reloption).
* (iii) add it to the appropriate options struct (perhaps StdRdOptions)
* (iv) add it to the appropriate handling routine (perhaps
@@ -147,15 +147,6 @@ static relopt_bool boolRelOpts[] =
},
false
},
- {
- {
- "vacuum_truncate",
- "Enables vacuum to truncate empty pages at the end of this table",
- RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
- ShareUpdateExclusiveLock
- },
- true
- },
{
{
"deduplicate_items",
@@ -170,6 +161,21 @@ static relopt_bool boolRelOpts[] =
{{NULL}}
};
+static relopt_ternary ternaryRelOpts[] =
+{
+ {
+ {
+ "vacuum_truncate",
+ "Enables vacuum to truncate empty pages at the end of this table",
+ RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
+ ShareUpdateExclusiveLock
+ },
+ TERNARY_UNSET
+ },
+ /* list terminator */
+ {{NULL}}
+};
+
static relopt_int intRelOpts[] =
{
{
@@ -600,6 +606,13 @@ initialize_reloptions(void)
boolRelOpts[i].gen.lockmode));
j++;
}
+ for (i = 0; ternaryRelOpts[i].gen.name; i++)
+ {
+ Assert(DoLockModesConflict(ternaryRelOpts[i].gen.lockmode,
+ ternaryRelOpts[i].gen.lockmode));
+ j++;
+ }
+
for (i = 0; intRelOpts[i].gen.name; i++)
{
Assert(DoLockModesConflict(intRelOpts[i].gen.lockmode,
@@ -640,6 +653,14 @@ initialize_reloptions(void)
j++;
}
+ for (i = 0; ternaryRelOpts[i].gen.name; i++)
+ {
+ relOpts[j] = &ternaryRelOpts[i].gen;
+ relOpts[j]->type = RELOPT_TYPE_TERNARY;
+ relOpts[j]->namelen = strlen(relOpts[j]->name);
+ j++;
+ }
+
for (i = 0; intRelOpts[i].gen.name; i++)
{
relOpts[j] = &intRelOpts[i].gen;
@@ -800,6 +821,9 @@ allocate_reloption(bits32 kinds, int type, const char *name, const char *desc,
case RELOPT_TYPE_BOOL:
size = sizeof(relopt_bool);
break;
+ case RELOPT_TYPE_TERNARY:
+ size = sizeof(relopt_ternary);
+ break;
case RELOPT_TYPE_INT:
size = sizeof(relopt_int);
break;
@@ -883,6 +907,54 @@ add_local_bool_reloption(local_relopts *relopts, const char *name,
add_local_reloption(relopts, (relopt_gen *) newoption, offset);
}
+/*
+ * init_ternary_reloption
+ * Allocate and initialize a new ternary reloption
+ */
+static relopt_ternary *
+init_ternary_reloption(bits32 kinds, const char *name, const char *desc,
+ ternary default_val, LOCKMODE lockmode)
+{
+ relopt_ternary *newoption;
+
+ newoption = (relopt_ternary *) allocate_reloption(kinds,
+ RELOPT_TYPE_TERNARY, name, desc, lockmode);
+ newoption->default_val = default_val;
+
+ return newoption;
+}
+
+/*
+ * add_ternary_reloption
+ * Add a new ternary reloption
+ */
+void
+add_ternary_reloption(bits32 kinds, const char *name, const char *desc,
+ ternary default_val, LOCKMODE lockmode)
+{
+ relopt_ternary *newoption = init_ternary_reloption(kinds, name, desc,
+ default_val, lockmode);
+
+ add_reloption((relopt_gen *) newoption);
+}
+
+/*
+ * add_local_ternary_reloption
+ * Add a new ternary local reloption
+ *
+ * 'offset' is offset of ternary-typed field.
+ */
+void
+add_local_ternary_reloption(local_relopts *relopts, const char *name,
+ const char *desc, ternary default_val,
+ int offset)
+{
+ relopt_ternary *newoption = init_ternary_reloption(RELOPT_KIND_LOCAL,
+ name, desc,
+ default_val, 0);
+
+ add_local_reloption(relopts, (relopt_gen *) newoption, offset);
+}
/*
* init_real_reloption
@@ -1617,6 +1689,18 @@ parse_one_reloption(relopt_value *option, char *text_str, int text_len,
option->gen->name, value)));
}
break;
+ case RELOPT_TYPE_TERNARY:
+ {
+ bool b;
+ parsed = parse_bool(value, &b);
+ option->values.ternary_val = b ? TERNARY_TRUE : TERNARY_FALSE;
+ if (validate && !parsed)
+ ereport(ERROR,
+ (errcode(ERRCODE_INVALID_PARAMETER_VALUE),
+ errmsg("invalid value for ternary option \"%s\": %s",
+ option->gen->name, value)));
+ }
+ break;
case RELOPT_TYPE_INT:
{
relopt_int *optint = (relopt_int *) option->gen;
@@ -1780,17 +1864,6 @@ fillRelOptions(void *rdopts, Size basesize,
char *itempos = ((char *) rdopts) + elems[j].offset;
char *string_val;
- /*
- * If isset_offset is provided, store whether the reloption is
- * set there.
- */
- if (elems[j].isset_offset > 0)
- {
- char *setpos = ((char *) rdopts) + elems[j].isset_offset;
-
- *(bool *) setpos = options[i].isset;
- }
-
switch (options[i].gen->type)
{
case RELOPT_TYPE_BOOL:
@@ -1798,6 +1871,11 @@ fillRelOptions(void *rdopts, Size basesize,
options[i].values.bool_val :
((relopt_bool *) options[i].gen)->default_val;
break;
+ case RELOPT_TYPE_TERNARY:
+ *(ternary *) itempos = options[i].isset ?
+ options[i].values.ternary_val :
+ ((relopt_ternary *) options[i].gen)->default_val;
+ break;
case RELOPT_TYPE_INT:
*(int *) itempos = options[i].isset ?
options[i].values.int_val :
@@ -1912,8 +1990,8 @@ default_reloptions(Datum reloptions, bool validate, relopt_kind kind)
offsetof(StdRdOptions, parallel_workers)},
{"vacuum_index_cleanup", RELOPT_TYPE_ENUM,
offsetof(StdRdOptions, vacuum_index_cleanup)},
- {"vacuum_truncate", RELOPT_TYPE_BOOL,
- offsetof(StdRdOptions, vacuum_truncate), offsetof(StdRdOptions, vacuum_truncate_set)},
+ {"vacuum_truncate", RELOPT_TYPE_TERNARY,
+ offsetof(StdRdOptions, vacuum_truncate)},
{"vacuum_max_eager_freeze_failure_rate", RELOPT_TYPE_REAL,
offsetof(StdRdOptions, vacuum_max_eager_freeze_failure_rate)}
};
@@ -1993,7 +2071,6 @@ build_local_reloptions(local_relopts *relopts, Datum options, bool validate)
elems[i].optname = opt->option->name;
elems[i].opttype = opt->option->type;
elems[i].offset = opt->offset;
- elems[i].isset_offset = 0; /* not supported for local relopts yet */
i++;
}
diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c
index 733ef40ae7..977babff54 100644
--- a/src/backend/commands/vacuum.c
+++ b/src/backend/commands/vacuum.c
@@ -2221,9 +2221,9 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams params,
{
StdRdOptions *opts = (StdRdOptions *) rel->rd_options;
- if (opts && opts->vacuum_truncate_set)
+ if (opts && opts->vacuum_truncate != TERNARY_UNSET)
{
- if (opts->vacuum_truncate)
+ if (opts->vacuum_truncate == TERNARY_TRUE)
params.truncate = VACOPTVALUE_ENABLED;
else
params.truncate = VACOPTVALUE_DISABLED;
diff --git a/src/include/access/reloptions.h b/src/include/access/reloptions.h
index a604a4702c..a436697658 100644
--- a/src/include/access/reloptions.h
+++ b/src/include/access/reloptions.h
@@ -29,6 +29,7 @@
typedef enum relopt_type
{
RELOPT_TYPE_BOOL,
+ RELOPT_TYPE_TERNARY, /* on, off, unset */
RELOPT_TYPE_INT,
RELOPT_TYPE_REAL,
RELOPT_TYPE_ENUM,
@@ -80,6 +81,7 @@ typedef struct relopt_value
union
{
bool bool_val;
+ ternary ternary_val;
int int_val;
double real_val;
int enum_val;
@@ -94,6 +96,12 @@ typedef struct relopt_bool
bool default_val;
} relopt_bool;
+typedef struct relopt_rernary
+{
+ relopt_gen gen;
+ int default_val;
+} relopt_ternary;
+
typedef struct relopt_int
{
relopt_gen gen;
@@ -152,19 +160,6 @@ typedef struct
const char *optname; /* option's name */
relopt_type opttype; /* option's datatype */
int offset; /* offset of field in result struct */
-
- /*
- * isset_offset is an optional offset of a field in the result struct that
- * stores whether the option is explicitly set for the relation or if it
- * just picked up the default value. In most cases, this can be
- * accomplished by giving the reloption a special out-of-range default
- * value (e.g., some integer reloptions use -2), but this isn't always
- * possible. For example, a Boolean reloption cannot be given an
- * out-of-range default, so we need another way to discover the source of
- * its value. This offset is only used if given a value greater than
- * zero.
- */
- int isset_offset;
} relopt_parse_elt;
/* Local reloption definition */
@@ -195,6 +190,8 @@ typedef struct local_relopts
extern relopt_kind add_reloption_kind(void);
extern void add_bool_reloption(bits32 kinds, const char *name, const char *desc,
bool default_val, LOCKMODE lockmode);
+extern void add_ternary_reloption(bits32 kinds, const char *name,
+ const char *desc, int default_val, LOCKMODE lockmode);
extern void add_int_reloption(bits32 kinds, const char *name, const char *desc,
int default_val, int min_val, int max_val,
LOCKMODE lockmode);
@@ -214,6 +211,9 @@ extern void register_reloptions_validator(local_relopts *relopts,
extern void add_local_bool_reloption(local_relopts *relopts, const char *name,
const char *desc, bool default_val,
int offset);
+extern void add_local_ternary_reloption(local_relopts *relopts,
+ const char *name, const char *desc,
+ ternary default_val, int offset);
extern void add_local_int_reloption(local_relopts *relopts, const char *name,
const char *desc, int default_val,
int min_val, int max_val, int offset);
diff --git a/src/include/c.h b/src/include/c.h
index 39022f8a9d..9c59ccccf8 100644
--- a/src/include/c.h
+++ b/src/include/c.h
@@ -488,6 +488,22 @@ typedef void (*pg_funcptr_t) (void);
#include <stdbool.h>
+/*
+ * ternary
+ * Boolean value with an extrea "unset" option
+ *
+ * Ternary data type is used in relation options that can be "true", "false" or
+ * "unset". Since relation options are used deep inside the PostgreSQL code,
+ * this type is declared globally.
+*/
+
+typedef enum ternary
+{
+ TERNARY_FALSE = 0,
+ TERNARY_TRUE = 1,
+ TERNARY_UNSET = -1
+} ternary;
+
/* ----------------------------------------------------------------
* Section 3: standard system types
diff --git a/src/include/utils/rel.h b/src/include/utils/rel.h
index b552359915..7346d618f9 100644
--- a/src/include/utils/rel.h
+++ b/src/include/utils/rel.h
@@ -346,8 +346,7 @@ typedef struct StdRdOptions
bool user_catalog_table; /* use as an additional catalog relation */
int parallel_workers; /* max number of parallel workers */
StdRdOptIndexCleanup vacuum_index_cleanup; /* controls index vacuuming */
- bool vacuum_truncate; /* enables vacuum to truncate a relation */
- bool vacuum_truncate_set; /* whether vacuum_truncate is set */
+ ternary vacuum_truncate; /* enables vacuum to truncate a relation */
/*
* Fraction of pages in a relation that vacuum can eagerly scan and fail
--
2.39.2
--nextPart4155600.3ZeAukHxDK
Content-Disposition: attachment;
filename="v2a-0003-Add-alias-to-be-used-as-unset-state.patch"
Content-Transfer-Encoding: 7Bit
Content-Type: text/x-patch; charset="unicode-2-0-utf-8";
name="v2a-0003-Add-alias-to-be-used-as-unset-state.patch"
^ permalink raw reply [nested|flat] 868+ messages in thread
* [PATCH v2 2/4] Introduce ternary reloptions
@ 2025-09-04 14:41 Nikolay Shaplov <[email protected]>
0 siblings, 0 replies; 868+ messages in thread
From: Nikolay Shaplov @ 2025-09-04 14:41 UTC (permalink / raw)
Introduce ternary reloption as a replacement for current `vacuum_truncate`
implementation. Remove `vacuum_truncate_set` additional flag and using
`TERNARY_UNSET` value instead.
---
src/backend/access/common/reloptions.c | 129 ++++++++++++++++++++-----
src/backend/commands/vacuum.c | 4 +-
src/include/access/reloptions.h | 26 ++---
src/include/c.h | 16 +++
src/include/utils/rel.h | 3 +-
5 files changed, 135 insertions(+), 43 deletions(-)
diff --git a/src/backend/access/common/reloptions.c b/src/backend/access/common/reloptions.c
index 0af3fea68f..f543f11001 100644
--- a/src/backend/access/common/reloptions.c
+++ b/src/backend/access/common/reloptions.c
@@ -40,9 +40,9 @@
*
* To add an option:
*
- * (i) decide on a type (bool, integer, real, enum, string), name, default
- * value, upper and lower bounds (if applicable); for strings, consider a
- * validation routine.
+ * (i) decide on a type (bool, ternary, integer, real, enum, string), name,
+ * default value, upper and lower bounds (if applicable); for strings,
+ * consider a validation routine.
* (ii) add a record below (or use add_<type>_reloption).
* (iii) add it to the appropriate options struct (perhaps StdRdOptions)
* (iv) add it to the appropriate handling routine (perhaps
@@ -147,15 +147,6 @@ static relopt_bool boolRelOpts[] =
},
false
},
- {
- {
- "vacuum_truncate",
- "Enables vacuum to truncate empty pages at the end of this table",
- RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
- ShareUpdateExclusiveLock
- },
- true
- },
{
{
"deduplicate_items",
@@ -170,6 +161,21 @@ static relopt_bool boolRelOpts[] =
{{NULL}}
};
+static relopt_ternary ternaryRelOpts[] =
+{
+ {
+ {
+ "vacuum_truncate",
+ "Enables vacuum to truncate empty pages at the end of this table",
+ RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
+ ShareUpdateExclusiveLock
+ },
+ TERNARY_UNSET
+ },
+ /* list terminator */
+ {{NULL}}
+};
+
static relopt_int intRelOpts[] =
{
{
@@ -600,6 +606,13 @@ initialize_reloptions(void)
boolRelOpts[i].gen.lockmode));
j++;
}
+ for (i = 0; ternaryRelOpts[i].gen.name; i++)
+ {
+ Assert(DoLockModesConflict(ternaryRelOpts[i].gen.lockmode,
+ ternaryRelOpts[i].gen.lockmode));
+ j++;
+ }
+
for (i = 0; intRelOpts[i].gen.name; i++)
{
Assert(DoLockModesConflict(intRelOpts[i].gen.lockmode,
@@ -640,6 +653,14 @@ initialize_reloptions(void)
j++;
}
+ for (i = 0; ternaryRelOpts[i].gen.name; i++)
+ {
+ relOpts[j] = &ternaryRelOpts[i].gen;
+ relOpts[j]->type = RELOPT_TYPE_TERNARY;
+ relOpts[j]->namelen = strlen(relOpts[j]->name);
+ j++;
+ }
+
for (i = 0; intRelOpts[i].gen.name; i++)
{
relOpts[j] = &intRelOpts[i].gen;
@@ -800,6 +821,9 @@ allocate_reloption(bits32 kinds, int type, const char *name, const char *desc,
case RELOPT_TYPE_BOOL:
size = sizeof(relopt_bool);
break;
+ case RELOPT_TYPE_TERNARY:
+ size = sizeof(relopt_ternary);
+ break;
case RELOPT_TYPE_INT:
size = sizeof(relopt_int);
break;
@@ -883,6 +907,54 @@ add_local_bool_reloption(local_relopts *relopts, const char *name,
add_local_reloption(relopts, (relopt_gen *) newoption, offset);
}
+/*
+ * init_ternary_reloption
+ * Allocate and initialize a new ternary reloption
+ */
+static relopt_ternary *
+init_ternary_reloption(bits32 kinds, const char *name, const char *desc,
+ ternary default_val, LOCKMODE lockmode)
+{
+ relopt_ternary *newoption;
+
+ newoption = (relopt_ternary *) allocate_reloption(kinds,
+ RELOPT_TYPE_TERNARY, name, desc, lockmode);
+ newoption->default_val = default_val;
+
+ return newoption;
+}
+
+/*
+ * add_ternary_reloption
+ * Add a new ternary reloption
+ */
+void
+add_ternary_reloption(bits32 kinds, const char *name, const char *desc,
+ ternary default_val, LOCKMODE lockmode)
+{
+ relopt_ternary *newoption = init_ternary_reloption(kinds, name, desc,
+ default_val, lockmode);
+
+ add_reloption((relopt_gen *) newoption);
+}
+
+/*
+ * add_local_ternary_reloption
+ * Add a new ternary local reloption
+ *
+ * 'offset' is offset of ternary-typed field.
+ */
+void
+add_local_ternary_reloption(local_relopts *relopts, const char *name,
+ const char *desc, ternary default_val,
+ int offset)
+{
+ relopt_ternary *newoption = init_ternary_reloption(RELOPT_KIND_LOCAL,
+ name, desc,
+ default_val, 0);
+
+ add_local_reloption(relopts, (relopt_gen *) newoption, offset);
+}
/*
* init_real_reloption
@@ -1617,6 +1689,18 @@ parse_one_reloption(relopt_value *option, char *text_str, int text_len,
option->gen->name, value)));
}
break;
+ case RELOPT_TYPE_TERNARY:
+ {
+ bool b;
+ parsed = parse_bool(value, &b);
+ option->values.ternary_val = b ? TERNARY_TRUE : TERNARY_FALSE;
+ if (validate && !parsed)
+ ereport(ERROR,
+ (errcode(ERRCODE_INVALID_PARAMETER_VALUE),
+ errmsg("invalid value for ternary option \"%s\": %s",
+ option->gen->name, value)));
+ }
+ break;
case RELOPT_TYPE_INT:
{
relopt_int *optint = (relopt_int *) option->gen;
@@ -1780,17 +1864,6 @@ fillRelOptions(void *rdopts, Size basesize,
char *itempos = ((char *) rdopts) + elems[j].offset;
char *string_val;
- /*
- * If isset_offset is provided, store whether the reloption is
- * set there.
- */
- if (elems[j].isset_offset > 0)
- {
- char *setpos = ((char *) rdopts) + elems[j].isset_offset;
-
- *(bool *) setpos = options[i].isset;
- }
-
switch (options[i].gen->type)
{
case RELOPT_TYPE_BOOL:
@@ -1798,6 +1871,11 @@ fillRelOptions(void *rdopts, Size basesize,
options[i].values.bool_val :
((relopt_bool *) options[i].gen)->default_val;
break;
+ case RELOPT_TYPE_TERNARY:
+ *(ternary *) itempos = options[i].isset ?
+ options[i].values.ternary_val :
+ ((relopt_ternary *) options[i].gen)->default_val;
+ break;
case RELOPT_TYPE_INT:
*(int *) itempos = options[i].isset ?
options[i].values.int_val :
@@ -1912,8 +1990,8 @@ default_reloptions(Datum reloptions, bool validate, relopt_kind kind)
offsetof(StdRdOptions, parallel_workers)},
{"vacuum_index_cleanup", RELOPT_TYPE_ENUM,
offsetof(StdRdOptions, vacuum_index_cleanup)},
- {"vacuum_truncate", RELOPT_TYPE_BOOL,
- offsetof(StdRdOptions, vacuum_truncate), offsetof(StdRdOptions, vacuum_truncate_set)},
+ {"vacuum_truncate", RELOPT_TYPE_TERNARY,
+ offsetof(StdRdOptions, vacuum_truncate)},
{"vacuum_max_eager_freeze_failure_rate", RELOPT_TYPE_REAL,
offsetof(StdRdOptions, vacuum_max_eager_freeze_failure_rate)}
};
@@ -1993,7 +2071,6 @@ build_local_reloptions(local_relopts *relopts, Datum options, bool validate)
elems[i].optname = opt->option->name;
elems[i].opttype = opt->option->type;
elems[i].offset = opt->offset;
- elems[i].isset_offset = 0; /* not supported for local relopts yet */
i++;
}
diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c
index 733ef40ae7..977babff54 100644
--- a/src/backend/commands/vacuum.c
+++ b/src/backend/commands/vacuum.c
@@ -2221,9 +2221,9 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams params,
{
StdRdOptions *opts = (StdRdOptions *) rel->rd_options;
- if (opts && opts->vacuum_truncate_set)
+ if (opts && opts->vacuum_truncate != TERNARY_UNSET)
{
- if (opts->vacuum_truncate)
+ if (opts->vacuum_truncate == TERNARY_TRUE)
params.truncate = VACOPTVALUE_ENABLED;
else
params.truncate = VACOPTVALUE_DISABLED;
diff --git a/src/include/access/reloptions.h b/src/include/access/reloptions.h
index a604a4702c..a436697658 100644
--- a/src/include/access/reloptions.h
+++ b/src/include/access/reloptions.h
@@ -29,6 +29,7 @@
typedef enum relopt_type
{
RELOPT_TYPE_BOOL,
+ RELOPT_TYPE_TERNARY, /* on, off, unset */
RELOPT_TYPE_INT,
RELOPT_TYPE_REAL,
RELOPT_TYPE_ENUM,
@@ -80,6 +81,7 @@ typedef struct relopt_value
union
{
bool bool_val;
+ ternary ternary_val;
int int_val;
double real_val;
int enum_val;
@@ -94,6 +96,12 @@ typedef struct relopt_bool
bool default_val;
} relopt_bool;
+typedef struct relopt_rernary
+{
+ relopt_gen gen;
+ int default_val;
+} relopt_ternary;
+
typedef struct relopt_int
{
relopt_gen gen;
@@ -152,19 +160,6 @@ typedef struct
const char *optname; /* option's name */
relopt_type opttype; /* option's datatype */
int offset; /* offset of field in result struct */
-
- /*
- * isset_offset is an optional offset of a field in the result struct that
- * stores whether the option is explicitly set for the relation or if it
- * just picked up the default value. In most cases, this can be
- * accomplished by giving the reloption a special out-of-range default
- * value (e.g., some integer reloptions use -2), but this isn't always
- * possible. For example, a Boolean reloption cannot be given an
- * out-of-range default, so we need another way to discover the source of
- * its value. This offset is only used if given a value greater than
- * zero.
- */
- int isset_offset;
} relopt_parse_elt;
/* Local reloption definition */
@@ -195,6 +190,8 @@ typedef struct local_relopts
extern relopt_kind add_reloption_kind(void);
extern void add_bool_reloption(bits32 kinds, const char *name, const char *desc,
bool default_val, LOCKMODE lockmode);
+extern void add_ternary_reloption(bits32 kinds, const char *name,
+ const char *desc, int default_val, LOCKMODE lockmode);
extern void add_int_reloption(bits32 kinds, const char *name, const char *desc,
int default_val, int min_val, int max_val,
LOCKMODE lockmode);
@@ -214,6 +211,9 @@ extern void register_reloptions_validator(local_relopts *relopts,
extern void add_local_bool_reloption(local_relopts *relopts, const char *name,
const char *desc, bool default_val,
int offset);
+extern void add_local_ternary_reloption(local_relopts *relopts,
+ const char *name, const char *desc,
+ ternary default_val, int offset);
extern void add_local_int_reloption(local_relopts *relopts, const char *name,
const char *desc, int default_val,
int min_val, int max_val, int offset);
diff --git a/src/include/c.h b/src/include/c.h
index 39022f8a9d..9c59ccccf8 100644
--- a/src/include/c.h
+++ b/src/include/c.h
@@ -488,6 +488,22 @@ typedef void (*pg_funcptr_t) (void);
#include <stdbool.h>
+/*
+ * ternary
+ * Boolean value with an extrea "unset" option
+ *
+ * Ternary data type is used in relation options that can be "true", "false" or
+ * "unset". Since relation options are used deep inside the PostgreSQL code,
+ * this type is declared globally.
+*/
+
+typedef enum ternary
+{
+ TERNARY_FALSE = 0,
+ TERNARY_TRUE = 1,
+ TERNARY_UNSET = -1
+} ternary;
+
/* ----------------------------------------------------------------
* Section 3: standard system types
diff --git a/src/include/utils/rel.h b/src/include/utils/rel.h
index b552359915..7346d618f9 100644
--- a/src/include/utils/rel.h
+++ b/src/include/utils/rel.h
@@ -346,8 +346,7 @@ typedef struct StdRdOptions
bool user_catalog_table; /* use as an additional catalog relation */
int parallel_workers; /* max number of parallel workers */
StdRdOptIndexCleanup vacuum_index_cleanup; /* controls index vacuuming */
- bool vacuum_truncate; /* enables vacuum to truncate a relation */
- bool vacuum_truncate_set; /* whether vacuum_truncate is set */
+ ternary vacuum_truncate; /* enables vacuum to truncate a relation */
/*
* Fraction of pages in a relation that vacuum can eagerly scan and fail
--
2.39.2
--nextPart5129932.5fSG56mABF
Content-Disposition: attachment;
filename="v2-0003-Add-alias-to-be-used-as-unset-state.patch"
Content-Transfer-Encoding: 7Bit
Content-Type: text/x-patch; charset="unicode-2-0-utf-8";
name="v2-0003-Add-alias-to-be-used-as-unset-state.patch"
^ permalink raw reply [nested|flat] 868+ messages in thread
* [PATCH v2 2/4] Introduce ternary reloptions
@ 2025-09-04 14:41 Nikolay Shaplov <[email protected]>
0 siblings, 0 replies; 868+ messages in thread
From: Nikolay Shaplov @ 2025-09-04 14:41 UTC (permalink / raw)
Introduce ternary reloption as a replacement for current `vacuum_truncate`
implementation. Remove `vacuum_truncate_set` additional flag and using
`TERNARY_UNSET` value instead.
---
src/backend/access/common/reloptions.c | 129 ++++++++++++++++++++-----
src/backend/commands/vacuum.c | 4 +-
src/include/access/reloptions.h | 26 ++---
src/include/c.h | 16 +++
src/include/utils/rel.h | 3 +-
5 files changed, 135 insertions(+), 43 deletions(-)
diff --git a/src/backend/access/common/reloptions.c b/src/backend/access/common/reloptions.c
index 0af3fea68f..f543f11001 100644
--- a/src/backend/access/common/reloptions.c
+++ b/src/backend/access/common/reloptions.c
@@ -40,9 +40,9 @@
*
* To add an option:
*
- * (i) decide on a type (bool, integer, real, enum, string), name, default
- * value, upper and lower bounds (if applicable); for strings, consider a
- * validation routine.
+ * (i) decide on a type (bool, ternary, integer, real, enum, string), name,
+ * default value, upper and lower bounds (if applicable); for strings,
+ * consider a validation routine.
* (ii) add a record below (or use add_<type>_reloption).
* (iii) add it to the appropriate options struct (perhaps StdRdOptions)
* (iv) add it to the appropriate handling routine (perhaps
@@ -147,15 +147,6 @@ static relopt_bool boolRelOpts[] =
},
false
},
- {
- {
- "vacuum_truncate",
- "Enables vacuum to truncate empty pages at the end of this table",
- RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
- ShareUpdateExclusiveLock
- },
- true
- },
{
{
"deduplicate_items",
@@ -170,6 +161,21 @@ static relopt_bool boolRelOpts[] =
{{NULL}}
};
+static relopt_ternary ternaryRelOpts[] =
+{
+ {
+ {
+ "vacuum_truncate",
+ "Enables vacuum to truncate empty pages at the end of this table",
+ RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
+ ShareUpdateExclusiveLock
+ },
+ TERNARY_UNSET
+ },
+ /* list terminator */
+ {{NULL}}
+};
+
static relopt_int intRelOpts[] =
{
{
@@ -600,6 +606,13 @@ initialize_reloptions(void)
boolRelOpts[i].gen.lockmode));
j++;
}
+ for (i = 0; ternaryRelOpts[i].gen.name; i++)
+ {
+ Assert(DoLockModesConflict(ternaryRelOpts[i].gen.lockmode,
+ ternaryRelOpts[i].gen.lockmode));
+ j++;
+ }
+
for (i = 0; intRelOpts[i].gen.name; i++)
{
Assert(DoLockModesConflict(intRelOpts[i].gen.lockmode,
@@ -640,6 +653,14 @@ initialize_reloptions(void)
j++;
}
+ for (i = 0; ternaryRelOpts[i].gen.name; i++)
+ {
+ relOpts[j] = &ternaryRelOpts[i].gen;
+ relOpts[j]->type = RELOPT_TYPE_TERNARY;
+ relOpts[j]->namelen = strlen(relOpts[j]->name);
+ j++;
+ }
+
for (i = 0; intRelOpts[i].gen.name; i++)
{
relOpts[j] = &intRelOpts[i].gen;
@@ -800,6 +821,9 @@ allocate_reloption(bits32 kinds, int type, const char *name, const char *desc,
case RELOPT_TYPE_BOOL:
size = sizeof(relopt_bool);
break;
+ case RELOPT_TYPE_TERNARY:
+ size = sizeof(relopt_ternary);
+ break;
case RELOPT_TYPE_INT:
size = sizeof(relopt_int);
break;
@@ -883,6 +907,54 @@ add_local_bool_reloption(local_relopts *relopts, const char *name,
add_local_reloption(relopts, (relopt_gen *) newoption, offset);
}
+/*
+ * init_ternary_reloption
+ * Allocate and initialize a new ternary reloption
+ */
+static relopt_ternary *
+init_ternary_reloption(bits32 kinds, const char *name, const char *desc,
+ ternary default_val, LOCKMODE lockmode)
+{
+ relopt_ternary *newoption;
+
+ newoption = (relopt_ternary *) allocate_reloption(kinds,
+ RELOPT_TYPE_TERNARY, name, desc, lockmode);
+ newoption->default_val = default_val;
+
+ return newoption;
+}
+
+/*
+ * add_ternary_reloption
+ * Add a new ternary reloption
+ */
+void
+add_ternary_reloption(bits32 kinds, const char *name, const char *desc,
+ ternary default_val, LOCKMODE lockmode)
+{
+ relopt_ternary *newoption = init_ternary_reloption(kinds, name, desc,
+ default_val, lockmode);
+
+ add_reloption((relopt_gen *) newoption);
+}
+
+/*
+ * add_local_ternary_reloption
+ * Add a new ternary local reloption
+ *
+ * 'offset' is offset of ternary-typed field.
+ */
+void
+add_local_ternary_reloption(local_relopts *relopts, const char *name,
+ const char *desc, ternary default_val,
+ int offset)
+{
+ relopt_ternary *newoption = init_ternary_reloption(RELOPT_KIND_LOCAL,
+ name, desc,
+ default_val, 0);
+
+ add_local_reloption(relopts, (relopt_gen *) newoption, offset);
+}
/*
* init_real_reloption
@@ -1617,6 +1689,18 @@ parse_one_reloption(relopt_value *option, char *text_str, int text_len,
option->gen->name, value)));
}
break;
+ case RELOPT_TYPE_TERNARY:
+ {
+ bool b;
+ parsed = parse_bool(value, &b);
+ option->values.ternary_val = b ? TERNARY_TRUE : TERNARY_FALSE;
+ if (validate && !parsed)
+ ereport(ERROR,
+ (errcode(ERRCODE_INVALID_PARAMETER_VALUE),
+ errmsg("invalid value for ternary option \"%s\": %s",
+ option->gen->name, value)));
+ }
+ break;
case RELOPT_TYPE_INT:
{
relopt_int *optint = (relopt_int *) option->gen;
@@ -1780,17 +1864,6 @@ fillRelOptions(void *rdopts, Size basesize,
char *itempos = ((char *) rdopts) + elems[j].offset;
char *string_val;
- /*
- * If isset_offset is provided, store whether the reloption is
- * set there.
- */
- if (elems[j].isset_offset > 0)
- {
- char *setpos = ((char *) rdopts) + elems[j].isset_offset;
-
- *(bool *) setpos = options[i].isset;
- }
-
switch (options[i].gen->type)
{
case RELOPT_TYPE_BOOL:
@@ -1798,6 +1871,11 @@ fillRelOptions(void *rdopts, Size basesize,
options[i].values.bool_val :
((relopt_bool *) options[i].gen)->default_val;
break;
+ case RELOPT_TYPE_TERNARY:
+ *(ternary *) itempos = options[i].isset ?
+ options[i].values.ternary_val :
+ ((relopt_ternary *) options[i].gen)->default_val;
+ break;
case RELOPT_TYPE_INT:
*(int *) itempos = options[i].isset ?
options[i].values.int_val :
@@ -1912,8 +1990,8 @@ default_reloptions(Datum reloptions, bool validate, relopt_kind kind)
offsetof(StdRdOptions, parallel_workers)},
{"vacuum_index_cleanup", RELOPT_TYPE_ENUM,
offsetof(StdRdOptions, vacuum_index_cleanup)},
- {"vacuum_truncate", RELOPT_TYPE_BOOL,
- offsetof(StdRdOptions, vacuum_truncate), offsetof(StdRdOptions, vacuum_truncate_set)},
+ {"vacuum_truncate", RELOPT_TYPE_TERNARY,
+ offsetof(StdRdOptions, vacuum_truncate)},
{"vacuum_max_eager_freeze_failure_rate", RELOPT_TYPE_REAL,
offsetof(StdRdOptions, vacuum_max_eager_freeze_failure_rate)}
};
@@ -1993,7 +2071,6 @@ build_local_reloptions(local_relopts *relopts, Datum options, bool validate)
elems[i].optname = opt->option->name;
elems[i].opttype = opt->option->type;
elems[i].offset = opt->offset;
- elems[i].isset_offset = 0; /* not supported for local relopts yet */
i++;
}
diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c
index 733ef40ae7..977babff54 100644
--- a/src/backend/commands/vacuum.c
+++ b/src/backend/commands/vacuum.c
@@ -2221,9 +2221,9 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams params,
{
StdRdOptions *opts = (StdRdOptions *) rel->rd_options;
- if (opts && opts->vacuum_truncate_set)
+ if (opts && opts->vacuum_truncate != TERNARY_UNSET)
{
- if (opts->vacuum_truncate)
+ if (opts->vacuum_truncate == TERNARY_TRUE)
params.truncate = VACOPTVALUE_ENABLED;
else
params.truncate = VACOPTVALUE_DISABLED;
diff --git a/src/include/access/reloptions.h b/src/include/access/reloptions.h
index a604a4702c..a436697658 100644
--- a/src/include/access/reloptions.h
+++ b/src/include/access/reloptions.h
@@ -29,6 +29,7 @@
typedef enum relopt_type
{
RELOPT_TYPE_BOOL,
+ RELOPT_TYPE_TERNARY, /* on, off, unset */
RELOPT_TYPE_INT,
RELOPT_TYPE_REAL,
RELOPT_TYPE_ENUM,
@@ -80,6 +81,7 @@ typedef struct relopt_value
union
{
bool bool_val;
+ ternary ternary_val;
int int_val;
double real_val;
int enum_val;
@@ -94,6 +96,12 @@ typedef struct relopt_bool
bool default_val;
} relopt_bool;
+typedef struct relopt_rernary
+{
+ relopt_gen gen;
+ int default_val;
+} relopt_ternary;
+
typedef struct relopt_int
{
relopt_gen gen;
@@ -152,19 +160,6 @@ typedef struct
const char *optname; /* option's name */
relopt_type opttype; /* option's datatype */
int offset; /* offset of field in result struct */
-
- /*
- * isset_offset is an optional offset of a field in the result struct that
- * stores whether the option is explicitly set for the relation or if it
- * just picked up the default value. In most cases, this can be
- * accomplished by giving the reloption a special out-of-range default
- * value (e.g., some integer reloptions use -2), but this isn't always
- * possible. For example, a Boolean reloption cannot be given an
- * out-of-range default, so we need another way to discover the source of
- * its value. This offset is only used if given a value greater than
- * zero.
- */
- int isset_offset;
} relopt_parse_elt;
/* Local reloption definition */
@@ -195,6 +190,8 @@ typedef struct local_relopts
extern relopt_kind add_reloption_kind(void);
extern void add_bool_reloption(bits32 kinds, const char *name, const char *desc,
bool default_val, LOCKMODE lockmode);
+extern void add_ternary_reloption(bits32 kinds, const char *name,
+ const char *desc, int default_val, LOCKMODE lockmode);
extern void add_int_reloption(bits32 kinds, const char *name, const char *desc,
int default_val, int min_val, int max_val,
LOCKMODE lockmode);
@@ -214,6 +211,9 @@ extern void register_reloptions_validator(local_relopts *relopts,
extern void add_local_bool_reloption(local_relopts *relopts, const char *name,
const char *desc, bool default_val,
int offset);
+extern void add_local_ternary_reloption(local_relopts *relopts,
+ const char *name, const char *desc,
+ ternary default_val, int offset);
extern void add_local_int_reloption(local_relopts *relopts, const char *name,
const char *desc, int default_val,
int min_val, int max_val, int offset);
diff --git a/src/include/c.h b/src/include/c.h
index 39022f8a9d..9c59ccccf8 100644
--- a/src/include/c.h
+++ b/src/include/c.h
@@ -488,6 +488,22 @@ typedef void (*pg_funcptr_t) (void);
#include <stdbool.h>
+/*
+ * ternary
+ * Boolean value with an extrea "unset" option
+ *
+ * Ternary data type is used in relation options that can be "true", "false" or
+ * "unset". Since relation options are used deep inside the PostgreSQL code,
+ * this type is declared globally.
+*/
+
+typedef enum ternary
+{
+ TERNARY_FALSE = 0,
+ TERNARY_TRUE = 1,
+ TERNARY_UNSET = -1
+} ternary;
+
/* ----------------------------------------------------------------
* Section 3: standard system types
diff --git a/src/include/utils/rel.h b/src/include/utils/rel.h
index b552359915..7346d618f9 100644
--- a/src/include/utils/rel.h
+++ b/src/include/utils/rel.h
@@ -346,8 +346,7 @@ typedef struct StdRdOptions
bool user_catalog_table; /* use as an additional catalog relation */
int parallel_workers; /* max number of parallel workers */
StdRdOptIndexCleanup vacuum_index_cleanup; /* controls index vacuuming */
- bool vacuum_truncate; /* enables vacuum to truncate a relation */
- bool vacuum_truncate_set; /* whether vacuum_truncate is set */
+ ternary vacuum_truncate; /* enables vacuum to truncate a relation */
/*
* Fraction of pages in a relation that vacuum can eagerly scan and fail
--
2.39.2
--nextPart5129932.5fSG56mABF
Content-Disposition: attachment;
filename="v2-0003-Add-alias-to-be-used-as-unset-state.patch"
Content-Transfer-Encoding: 7Bit
Content-Type: text/x-patch; charset="unicode-2-0-utf-8";
name="v2-0003-Add-alias-to-be-used-as-unset-state.patch"
^ permalink raw reply [nested|flat] 868+ messages in thread
* [PATCH v2a 2/4] Introduce ternary reloptions
@ 2025-09-04 14:41 Nikolay Shaplov <[email protected]>
0 siblings, 0 replies; 868+ messages in thread
From: Nikolay Shaplov @ 2025-09-04 14:41 UTC (permalink / raw)
Introduce ternary reloption as a replacement for current `vacuum_truncate`
implementation. Remove `vacuum_truncate_set` additional flag and using
`TERNARY_UNSET` value instead.
---
src/backend/access/common/reloptions.c | 129 ++++++++++++++++++++-----
src/backend/commands/vacuum.c | 4 +-
src/include/access/reloptions.h | 26 ++---
src/include/c.h | 16 +++
src/include/utils/rel.h | 3 +-
5 files changed, 135 insertions(+), 43 deletions(-)
diff --git a/src/backend/access/common/reloptions.c b/src/backend/access/common/reloptions.c
index 0af3fea68f..f543f11001 100644
--- a/src/backend/access/common/reloptions.c
+++ b/src/backend/access/common/reloptions.c
@@ -40,9 +40,9 @@
*
* To add an option:
*
- * (i) decide on a type (bool, integer, real, enum, string), name, default
- * value, upper and lower bounds (if applicable); for strings, consider a
- * validation routine.
+ * (i) decide on a type (bool, ternary, integer, real, enum, string), name,
+ * default value, upper and lower bounds (if applicable); for strings,
+ * consider a validation routine.
* (ii) add a record below (or use add_<type>_reloption).
* (iii) add it to the appropriate options struct (perhaps StdRdOptions)
* (iv) add it to the appropriate handling routine (perhaps
@@ -147,15 +147,6 @@ static relopt_bool boolRelOpts[] =
},
false
},
- {
- {
- "vacuum_truncate",
- "Enables vacuum to truncate empty pages at the end of this table",
- RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
- ShareUpdateExclusiveLock
- },
- true
- },
{
{
"deduplicate_items",
@@ -170,6 +161,21 @@ static relopt_bool boolRelOpts[] =
{{NULL}}
};
+static relopt_ternary ternaryRelOpts[] =
+{
+ {
+ {
+ "vacuum_truncate",
+ "Enables vacuum to truncate empty pages at the end of this table",
+ RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
+ ShareUpdateExclusiveLock
+ },
+ TERNARY_UNSET
+ },
+ /* list terminator */
+ {{NULL}}
+};
+
static relopt_int intRelOpts[] =
{
{
@@ -600,6 +606,13 @@ initialize_reloptions(void)
boolRelOpts[i].gen.lockmode));
j++;
}
+ for (i = 0; ternaryRelOpts[i].gen.name; i++)
+ {
+ Assert(DoLockModesConflict(ternaryRelOpts[i].gen.lockmode,
+ ternaryRelOpts[i].gen.lockmode));
+ j++;
+ }
+
for (i = 0; intRelOpts[i].gen.name; i++)
{
Assert(DoLockModesConflict(intRelOpts[i].gen.lockmode,
@@ -640,6 +653,14 @@ initialize_reloptions(void)
j++;
}
+ for (i = 0; ternaryRelOpts[i].gen.name; i++)
+ {
+ relOpts[j] = &ternaryRelOpts[i].gen;
+ relOpts[j]->type = RELOPT_TYPE_TERNARY;
+ relOpts[j]->namelen = strlen(relOpts[j]->name);
+ j++;
+ }
+
for (i = 0; intRelOpts[i].gen.name; i++)
{
relOpts[j] = &intRelOpts[i].gen;
@@ -800,6 +821,9 @@ allocate_reloption(bits32 kinds, int type, const char *name, const char *desc,
case RELOPT_TYPE_BOOL:
size = sizeof(relopt_bool);
break;
+ case RELOPT_TYPE_TERNARY:
+ size = sizeof(relopt_ternary);
+ break;
case RELOPT_TYPE_INT:
size = sizeof(relopt_int);
break;
@@ -883,6 +907,54 @@ add_local_bool_reloption(local_relopts *relopts, const char *name,
add_local_reloption(relopts, (relopt_gen *) newoption, offset);
}
+/*
+ * init_ternary_reloption
+ * Allocate and initialize a new ternary reloption
+ */
+static relopt_ternary *
+init_ternary_reloption(bits32 kinds, const char *name, const char *desc,
+ ternary default_val, LOCKMODE lockmode)
+{
+ relopt_ternary *newoption;
+
+ newoption = (relopt_ternary *) allocate_reloption(kinds,
+ RELOPT_TYPE_TERNARY, name, desc, lockmode);
+ newoption->default_val = default_val;
+
+ return newoption;
+}
+
+/*
+ * add_ternary_reloption
+ * Add a new ternary reloption
+ */
+void
+add_ternary_reloption(bits32 kinds, const char *name, const char *desc,
+ ternary default_val, LOCKMODE lockmode)
+{
+ relopt_ternary *newoption = init_ternary_reloption(kinds, name, desc,
+ default_val, lockmode);
+
+ add_reloption((relopt_gen *) newoption);
+}
+
+/*
+ * add_local_ternary_reloption
+ * Add a new ternary local reloption
+ *
+ * 'offset' is offset of ternary-typed field.
+ */
+void
+add_local_ternary_reloption(local_relopts *relopts, const char *name,
+ const char *desc, ternary default_val,
+ int offset)
+{
+ relopt_ternary *newoption = init_ternary_reloption(RELOPT_KIND_LOCAL,
+ name, desc,
+ default_val, 0);
+
+ add_local_reloption(relopts, (relopt_gen *) newoption, offset);
+}
/*
* init_real_reloption
@@ -1617,6 +1689,18 @@ parse_one_reloption(relopt_value *option, char *text_str, int text_len,
option->gen->name, value)));
}
break;
+ case RELOPT_TYPE_TERNARY:
+ {
+ bool b;
+ parsed = parse_bool(value, &b);
+ option->values.ternary_val = b ? TERNARY_TRUE : TERNARY_FALSE;
+ if (validate && !parsed)
+ ereport(ERROR,
+ (errcode(ERRCODE_INVALID_PARAMETER_VALUE),
+ errmsg("invalid value for ternary option \"%s\": %s",
+ option->gen->name, value)));
+ }
+ break;
case RELOPT_TYPE_INT:
{
relopt_int *optint = (relopt_int *) option->gen;
@@ -1780,17 +1864,6 @@ fillRelOptions(void *rdopts, Size basesize,
char *itempos = ((char *) rdopts) + elems[j].offset;
char *string_val;
- /*
- * If isset_offset is provided, store whether the reloption is
- * set there.
- */
- if (elems[j].isset_offset > 0)
- {
- char *setpos = ((char *) rdopts) + elems[j].isset_offset;
-
- *(bool *) setpos = options[i].isset;
- }
-
switch (options[i].gen->type)
{
case RELOPT_TYPE_BOOL:
@@ -1798,6 +1871,11 @@ fillRelOptions(void *rdopts, Size basesize,
options[i].values.bool_val :
((relopt_bool *) options[i].gen)->default_val;
break;
+ case RELOPT_TYPE_TERNARY:
+ *(ternary *) itempos = options[i].isset ?
+ options[i].values.ternary_val :
+ ((relopt_ternary *) options[i].gen)->default_val;
+ break;
case RELOPT_TYPE_INT:
*(int *) itempos = options[i].isset ?
options[i].values.int_val :
@@ -1912,8 +1990,8 @@ default_reloptions(Datum reloptions, bool validate, relopt_kind kind)
offsetof(StdRdOptions, parallel_workers)},
{"vacuum_index_cleanup", RELOPT_TYPE_ENUM,
offsetof(StdRdOptions, vacuum_index_cleanup)},
- {"vacuum_truncate", RELOPT_TYPE_BOOL,
- offsetof(StdRdOptions, vacuum_truncate), offsetof(StdRdOptions, vacuum_truncate_set)},
+ {"vacuum_truncate", RELOPT_TYPE_TERNARY,
+ offsetof(StdRdOptions, vacuum_truncate)},
{"vacuum_max_eager_freeze_failure_rate", RELOPT_TYPE_REAL,
offsetof(StdRdOptions, vacuum_max_eager_freeze_failure_rate)}
};
@@ -1993,7 +2071,6 @@ build_local_reloptions(local_relopts *relopts, Datum options, bool validate)
elems[i].optname = opt->option->name;
elems[i].opttype = opt->option->type;
elems[i].offset = opt->offset;
- elems[i].isset_offset = 0; /* not supported for local relopts yet */
i++;
}
diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c
index 733ef40ae7..977babff54 100644
--- a/src/backend/commands/vacuum.c
+++ b/src/backend/commands/vacuum.c
@@ -2221,9 +2221,9 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams params,
{
StdRdOptions *opts = (StdRdOptions *) rel->rd_options;
- if (opts && opts->vacuum_truncate_set)
+ if (opts && opts->vacuum_truncate != TERNARY_UNSET)
{
- if (opts->vacuum_truncate)
+ if (opts->vacuum_truncate == TERNARY_TRUE)
params.truncate = VACOPTVALUE_ENABLED;
else
params.truncate = VACOPTVALUE_DISABLED;
diff --git a/src/include/access/reloptions.h b/src/include/access/reloptions.h
index a604a4702c..a436697658 100644
--- a/src/include/access/reloptions.h
+++ b/src/include/access/reloptions.h
@@ -29,6 +29,7 @@
typedef enum relopt_type
{
RELOPT_TYPE_BOOL,
+ RELOPT_TYPE_TERNARY, /* on, off, unset */
RELOPT_TYPE_INT,
RELOPT_TYPE_REAL,
RELOPT_TYPE_ENUM,
@@ -80,6 +81,7 @@ typedef struct relopt_value
union
{
bool bool_val;
+ ternary ternary_val;
int int_val;
double real_val;
int enum_val;
@@ -94,6 +96,12 @@ typedef struct relopt_bool
bool default_val;
} relopt_bool;
+typedef struct relopt_rernary
+{
+ relopt_gen gen;
+ int default_val;
+} relopt_ternary;
+
typedef struct relopt_int
{
relopt_gen gen;
@@ -152,19 +160,6 @@ typedef struct
const char *optname; /* option's name */
relopt_type opttype; /* option's datatype */
int offset; /* offset of field in result struct */
-
- /*
- * isset_offset is an optional offset of a field in the result struct that
- * stores whether the option is explicitly set for the relation or if it
- * just picked up the default value. In most cases, this can be
- * accomplished by giving the reloption a special out-of-range default
- * value (e.g., some integer reloptions use -2), but this isn't always
- * possible. For example, a Boolean reloption cannot be given an
- * out-of-range default, so we need another way to discover the source of
- * its value. This offset is only used if given a value greater than
- * zero.
- */
- int isset_offset;
} relopt_parse_elt;
/* Local reloption definition */
@@ -195,6 +190,8 @@ typedef struct local_relopts
extern relopt_kind add_reloption_kind(void);
extern void add_bool_reloption(bits32 kinds, const char *name, const char *desc,
bool default_val, LOCKMODE lockmode);
+extern void add_ternary_reloption(bits32 kinds, const char *name,
+ const char *desc, int default_val, LOCKMODE lockmode);
extern void add_int_reloption(bits32 kinds, const char *name, const char *desc,
int default_val, int min_val, int max_val,
LOCKMODE lockmode);
@@ -214,6 +211,9 @@ extern void register_reloptions_validator(local_relopts *relopts,
extern void add_local_bool_reloption(local_relopts *relopts, const char *name,
const char *desc, bool default_val,
int offset);
+extern void add_local_ternary_reloption(local_relopts *relopts,
+ const char *name, const char *desc,
+ ternary default_val, int offset);
extern void add_local_int_reloption(local_relopts *relopts, const char *name,
const char *desc, int default_val,
int min_val, int max_val, int offset);
diff --git a/src/include/c.h b/src/include/c.h
index 39022f8a9d..9c59ccccf8 100644
--- a/src/include/c.h
+++ b/src/include/c.h
@@ -488,6 +488,22 @@ typedef void (*pg_funcptr_t) (void);
#include <stdbool.h>
+/*
+ * ternary
+ * Boolean value with an extrea "unset" option
+ *
+ * Ternary data type is used in relation options that can be "true", "false" or
+ * "unset". Since relation options are used deep inside the PostgreSQL code,
+ * this type is declared globally.
+*/
+
+typedef enum ternary
+{
+ TERNARY_FALSE = 0,
+ TERNARY_TRUE = 1,
+ TERNARY_UNSET = -1
+} ternary;
+
/* ----------------------------------------------------------------
* Section 3: standard system types
diff --git a/src/include/utils/rel.h b/src/include/utils/rel.h
index b552359915..7346d618f9 100644
--- a/src/include/utils/rel.h
+++ b/src/include/utils/rel.h
@@ -346,8 +346,7 @@ typedef struct StdRdOptions
bool user_catalog_table; /* use as an additional catalog relation */
int parallel_workers; /* max number of parallel workers */
StdRdOptIndexCleanup vacuum_index_cleanup; /* controls index vacuuming */
- bool vacuum_truncate; /* enables vacuum to truncate a relation */
- bool vacuum_truncate_set; /* whether vacuum_truncate is set */
+ ternary vacuum_truncate; /* enables vacuum to truncate a relation */
/*
* Fraction of pages in a relation that vacuum can eagerly scan and fail
--
2.39.2
--nextPart4155600.3ZeAukHxDK
Content-Disposition: attachment;
filename="v2a-0003-Add-alias-to-be-used-as-unset-state.patch"
Content-Transfer-Encoding: 7Bit
Content-Type: text/x-patch; charset="unicode-2-0-utf-8";
name="v2a-0003-Add-alias-to-be-used-as-unset-state.patch"
^ permalink raw reply [nested|flat] 868+ messages in thread
* [PATCH v2a 2/4] Introduce ternary reloptions
@ 2025-09-04 14:41 Nikolay Shaplov <[email protected]>
0 siblings, 0 replies; 868+ messages in thread
From: Nikolay Shaplov @ 2025-09-04 14:41 UTC (permalink / raw)
Introduce ternary reloption as a replacement for current `vacuum_truncate`
implementation. Remove `vacuum_truncate_set` additional flag and using
`TERNARY_UNSET` value instead.
---
src/backend/access/common/reloptions.c | 129 ++++++++++++++++++++-----
src/backend/commands/vacuum.c | 4 +-
src/include/access/reloptions.h | 26 ++---
src/include/c.h | 16 +++
src/include/utils/rel.h | 3 +-
5 files changed, 135 insertions(+), 43 deletions(-)
diff --git a/src/backend/access/common/reloptions.c b/src/backend/access/common/reloptions.c
index 0af3fea68f..f543f11001 100644
--- a/src/backend/access/common/reloptions.c
+++ b/src/backend/access/common/reloptions.c
@@ -40,9 +40,9 @@
*
* To add an option:
*
- * (i) decide on a type (bool, integer, real, enum, string), name, default
- * value, upper and lower bounds (if applicable); for strings, consider a
- * validation routine.
+ * (i) decide on a type (bool, ternary, integer, real, enum, string), name,
+ * default value, upper and lower bounds (if applicable); for strings,
+ * consider a validation routine.
* (ii) add a record below (or use add_<type>_reloption).
* (iii) add it to the appropriate options struct (perhaps StdRdOptions)
* (iv) add it to the appropriate handling routine (perhaps
@@ -147,15 +147,6 @@ static relopt_bool boolRelOpts[] =
},
false
},
- {
- {
- "vacuum_truncate",
- "Enables vacuum to truncate empty pages at the end of this table",
- RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
- ShareUpdateExclusiveLock
- },
- true
- },
{
{
"deduplicate_items",
@@ -170,6 +161,21 @@ static relopt_bool boolRelOpts[] =
{{NULL}}
};
+static relopt_ternary ternaryRelOpts[] =
+{
+ {
+ {
+ "vacuum_truncate",
+ "Enables vacuum to truncate empty pages at the end of this table",
+ RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
+ ShareUpdateExclusiveLock
+ },
+ TERNARY_UNSET
+ },
+ /* list terminator */
+ {{NULL}}
+};
+
static relopt_int intRelOpts[] =
{
{
@@ -600,6 +606,13 @@ initialize_reloptions(void)
boolRelOpts[i].gen.lockmode));
j++;
}
+ for (i = 0; ternaryRelOpts[i].gen.name; i++)
+ {
+ Assert(DoLockModesConflict(ternaryRelOpts[i].gen.lockmode,
+ ternaryRelOpts[i].gen.lockmode));
+ j++;
+ }
+
for (i = 0; intRelOpts[i].gen.name; i++)
{
Assert(DoLockModesConflict(intRelOpts[i].gen.lockmode,
@@ -640,6 +653,14 @@ initialize_reloptions(void)
j++;
}
+ for (i = 0; ternaryRelOpts[i].gen.name; i++)
+ {
+ relOpts[j] = &ternaryRelOpts[i].gen;
+ relOpts[j]->type = RELOPT_TYPE_TERNARY;
+ relOpts[j]->namelen = strlen(relOpts[j]->name);
+ j++;
+ }
+
for (i = 0; intRelOpts[i].gen.name; i++)
{
relOpts[j] = &intRelOpts[i].gen;
@@ -800,6 +821,9 @@ allocate_reloption(bits32 kinds, int type, const char *name, const char *desc,
case RELOPT_TYPE_BOOL:
size = sizeof(relopt_bool);
break;
+ case RELOPT_TYPE_TERNARY:
+ size = sizeof(relopt_ternary);
+ break;
case RELOPT_TYPE_INT:
size = sizeof(relopt_int);
break;
@@ -883,6 +907,54 @@ add_local_bool_reloption(local_relopts *relopts, const char *name,
add_local_reloption(relopts, (relopt_gen *) newoption, offset);
}
+/*
+ * init_ternary_reloption
+ * Allocate and initialize a new ternary reloption
+ */
+static relopt_ternary *
+init_ternary_reloption(bits32 kinds, const char *name, const char *desc,
+ ternary default_val, LOCKMODE lockmode)
+{
+ relopt_ternary *newoption;
+
+ newoption = (relopt_ternary *) allocate_reloption(kinds,
+ RELOPT_TYPE_TERNARY, name, desc, lockmode);
+ newoption->default_val = default_val;
+
+ return newoption;
+}
+
+/*
+ * add_ternary_reloption
+ * Add a new ternary reloption
+ */
+void
+add_ternary_reloption(bits32 kinds, const char *name, const char *desc,
+ ternary default_val, LOCKMODE lockmode)
+{
+ relopt_ternary *newoption = init_ternary_reloption(kinds, name, desc,
+ default_val, lockmode);
+
+ add_reloption((relopt_gen *) newoption);
+}
+
+/*
+ * add_local_ternary_reloption
+ * Add a new ternary local reloption
+ *
+ * 'offset' is offset of ternary-typed field.
+ */
+void
+add_local_ternary_reloption(local_relopts *relopts, const char *name,
+ const char *desc, ternary default_val,
+ int offset)
+{
+ relopt_ternary *newoption = init_ternary_reloption(RELOPT_KIND_LOCAL,
+ name, desc,
+ default_val, 0);
+
+ add_local_reloption(relopts, (relopt_gen *) newoption, offset);
+}
/*
* init_real_reloption
@@ -1617,6 +1689,18 @@ parse_one_reloption(relopt_value *option, char *text_str, int text_len,
option->gen->name, value)));
}
break;
+ case RELOPT_TYPE_TERNARY:
+ {
+ bool b;
+ parsed = parse_bool(value, &b);
+ option->values.ternary_val = b ? TERNARY_TRUE : TERNARY_FALSE;
+ if (validate && !parsed)
+ ereport(ERROR,
+ (errcode(ERRCODE_INVALID_PARAMETER_VALUE),
+ errmsg("invalid value for ternary option \"%s\": %s",
+ option->gen->name, value)));
+ }
+ break;
case RELOPT_TYPE_INT:
{
relopt_int *optint = (relopt_int *) option->gen;
@@ -1780,17 +1864,6 @@ fillRelOptions(void *rdopts, Size basesize,
char *itempos = ((char *) rdopts) + elems[j].offset;
char *string_val;
- /*
- * If isset_offset is provided, store whether the reloption is
- * set there.
- */
- if (elems[j].isset_offset > 0)
- {
- char *setpos = ((char *) rdopts) + elems[j].isset_offset;
-
- *(bool *) setpos = options[i].isset;
- }
-
switch (options[i].gen->type)
{
case RELOPT_TYPE_BOOL:
@@ -1798,6 +1871,11 @@ fillRelOptions(void *rdopts, Size basesize,
options[i].values.bool_val :
((relopt_bool *) options[i].gen)->default_val;
break;
+ case RELOPT_TYPE_TERNARY:
+ *(ternary *) itempos = options[i].isset ?
+ options[i].values.ternary_val :
+ ((relopt_ternary *) options[i].gen)->default_val;
+ break;
case RELOPT_TYPE_INT:
*(int *) itempos = options[i].isset ?
options[i].values.int_val :
@@ -1912,8 +1990,8 @@ default_reloptions(Datum reloptions, bool validate, relopt_kind kind)
offsetof(StdRdOptions, parallel_workers)},
{"vacuum_index_cleanup", RELOPT_TYPE_ENUM,
offsetof(StdRdOptions, vacuum_index_cleanup)},
- {"vacuum_truncate", RELOPT_TYPE_BOOL,
- offsetof(StdRdOptions, vacuum_truncate), offsetof(StdRdOptions, vacuum_truncate_set)},
+ {"vacuum_truncate", RELOPT_TYPE_TERNARY,
+ offsetof(StdRdOptions, vacuum_truncate)},
{"vacuum_max_eager_freeze_failure_rate", RELOPT_TYPE_REAL,
offsetof(StdRdOptions, vacuum_max_eager_freeze_failure_rate)}
};
@@ -1993,7 +2071,6 @@ build_local_reloptions(local_relopts *relopts, Datum options, bool validate)
elems[i].optname = opt->option->name;
elems[i].opttype = opt->option->type;
elems[i].offset = opt->offset;
- elems[i].isset_offset = 0; /* not supported for local relopts yet */
i++;
}
diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c
index 733ef40ae7..977babff54 100644
--- a/src/backend/commands/vacuum.c
+++ b/src/backend/commands/vacuum.c
@@ -2221,9 +2221,9 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams params,
{
StdRdOptions *opts = (StdRdOptions *) rel->rd_options;
- if (opts && opts->vacuum_truncate_set)
+ if (opts && opts->vacuum_truncate != TERNARY_UNSET)
{
- if (opts->vacuum_truncate)
+ if (opts->vacuum_truncate == TERNARY_TRUE)
params.truncate = VACOPTVALUE_ENABLED;
else
params.truncate = VACOPTVALUE_DISABLED;
diff --git a/src/include/access/reloptions.h b/src/include/access/reloptions.h
index a604a4702c..a436697658 100644
--- a/src/include/access/reloptions.h
+++ b/src/include/access/reloptions.h
@@ -29,6 +29,7 @@
typedef enum relopt_type
{
RELOPT_TYPE_BOOL,
+ RELOPT_TYPE_TERNARY, /* on, off, unset */
RELOPT_TYPE_INT,
RELOPT_TYPE_REAL,
RELOPT_TYPE_ENUM,
@@ -80,6 +81,7 @@ typedef struct relopt_value
union
{
bool bool_val;
+ ternary ternary_val;
int int_val;
double real_val;
int enum_val;
@@ -94,6 +96,12 @@ typedef struct relopt_bool
bool default_val;
} relopt_bool;
+typedef struct relopt_rernary
+{
+ relopt_gen gen;
+ int default_val;
+} relopt_ternary;
+
typedef struct relopt_int
{
relopt_gen gen;
@@ -152,19 +160,6 @@ typedef struct
const char *optname; /* option's name */
relopt_type opttype; /* option's datatype */
int offset; /* offset of field in result struct */
-
- /*
- * isset_offset is an optional offset of a field in the result struct that
- * stores whether the option is explicitly set for the relation or if it
- * just picked up the default value. In most cases, this can be
- * accomplished by giving the reloption a special out-of-range default
- * value (e.g., some integer reloptions use -2), but this isn't always
- * possible. For example, a Boolean reloption cannot be given an
- * out-of-range default, so we need another way to discover the source of
- * its value. This offset is only used if given a value greater than
- * zero.
- */
- int isset_offset;
} relopt_parse_elt;
/* Local reloption definition */
@@ -195,6 +190,8 @@ typedef struct local_relopts
extern relopt_kind add_reloption_kind(void);
extern void add_bool_reloption(bits32 kinds, const char *name, const char *desc,
bool default_val, LOCKMODE lockmode);
+extern void add_ternary_reloption(bits32 kinds, const char *name,
+ const char *desc, int default_val, LOCKMODE lockmode);
extern void add_int_reloption(bits32 kinds, const char *name, const char *desc,
int default_val, int min_val, int max_val,
LOCKMODE lockmode);
@@ -214,6 +211,9 @@ extern void register_reloptions_validator(local_relopts *relopts,
extern void add_local_bool_reloption(local_relopts *relopts, const char *name,
const char *desc, bool default_val,
int offset);
+extern void add_local_ternary_reloption(local_relopts *relopts,
+ const char *name, const char *desc,
+ ternary default_val, int offset);
extern void add_local_int_reloption(local_relopts *relopts, const char *name,
const char *desc, int default_val,
int min_val, int max_val, int offset);
diff --git a/src/include/c.h b/src/include/c.h
index 39022f8a9d..9c59ccccf8 100644
--- a/src/include/c.h
+++ b/src/include/c.h
@@ -488,6 +488,22 @@ typedef void (*pg_funcptr_t) (void);
#include <stdbool.h>
+/*
+ * ternary
+ * Boolean value with an extrea "unset" option
+ *
+ * Ternary data type is used in relation options that can be "true", "false" or
+ * "unset". Since relation options are used deep inside the PostgreSQL code,
+ * this type is declared globally.
+*/
+
+typedef enum ternary
+{
+ TERNARY_FALSE = 0,
+ TERNARY_TRUE = 1,
+ TERNARY_UNSET = -1
+} ternary;
+
/* ----------------------------------------------------------------
* Section 3: standard system types
diff --git a/src/include/utils/rel.h b/src/include/utils/rel.h
index b552359915..7346d618f9 100644
--- a/src/include/utils/rel.h
+++ b/src/include/utils/rel.h
@@ -346,8 +346,7 @@ typedef struct StdRdOptions
bool user_catalog_table; /* use as an additional catalog relation */
int parallel_workers; /* max number of parallel workers */
StdRdOptIndexCleanup vacuum_index_cleanup; /* controls index vacuuming */
- bool vacuum_truncate; /* enables vacuum to truncate a relation */
- bool vacuum_truncate_set; /* whether vacuum_truncate is set */
+ ternary vacuum_truncate; /* enables vacuum to truncate a relation */
/*
* Fraction of pages in a relation that vacuum can eagerly scan and fail
--
2.39.2
--nextPart4155600.3ZeAukHxDK
Content-Disposition: attachment;
filename="v2a-0003-Add-alias-to-be-used-as-unset-state.patch"
Content-Transfer-Encoding: 7Bit
Content-Type: text/x-patch; charset="unicode-2-0-utf-8";
name="v2a-0003-Add-alias-to-be-used-as-unset-state.patch"
^ permalink raw reply [nested|flat] 868+ messages in thread
* [PATCH v2a 2/4] Introduce ternary reloptions
@ 2025-09-04 14:41 Nikolay Shaplov <[email protected]>
0 siblings, 0 replies; 868+ messages in thread
From: Nikolay Shaplov @ 2025-09-04 14:41 UTC (permalink / raw)
Introduce ternary reloption as a replacement for current `vacuum_truncate`
implementation. Remove `vacuum_truncate_set` additional flag and using
`TERNARY_UNSET` value instead.
---
src/backend/access/common/reloptions.c | 129 ++++++++++++++++++++-----
src/backend/commands/vacuum.c | 4 +-
src/include/access/reloptions.h | 26 ++---
src/include/c.h | 16 +++
src/include/utils/rel.h | 3 +-
5 files changed, 135 insertions(+), 43 deletions(-)
diff --git a/src/backend/access/common/reloptions.c b/src/backend/access/common/reloptions.c
index 0af3fea68f..f543f11001 100644
--- a/src/backend/access/common/reloptions.c
+++ b/src/backend/access/common/reloptions.c
@@ -40,9 +40,9 @@
*
* To add an option:
*
- * (i) decide on a type (bool, integer, real, enum, string), name, default
- * value, upper and lower bounds (if applicable); for strings, consider a
- * validation routine.
+ * (i) decide on a type (bool, ternary, integer, real, enum, string), name,
+ * default value, upper and lower bounds (if applicable); for strings,
+ * consider a validation routine.
* (ii) add a record below (or use add_<type>_reloption).
* (iii) add it to the appropriate options struct (perhaps StdRdOptions)
* (iv) add it to the appropriate handling routine (perhaps
@@ -147,15 +147,6 @@ static relopt_bool boolRelOpts[] =
},
false
},
- {
- {
- "vacuum_truncate",
- "Enables vacuum to truncate empty pages at the end of this table",
- RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
- ShareUpdateExclusiveLock
- },
- true
- },
{
{
"deduplicate_items",
@@ -170,6 +161,21 @@ static relopt_bool boolRelOpts[] =
{{NULL}}
};
+static relopt_ternary ternaryRelOpts[] =
+{
+ {
+ {
+ "vacuum_truncate",
+ "Enables vacuum to truncate empty pages at the end of this table",
+ RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
+ ShareUpdateExclusiveLock
+ },
+ TERNARY_UNSET
+ },
+ /* list terminator */
+ {{NULL}}
+};
+
static relopt_int intRelOpts[] =
{
{
@@ -600,6 +606,13 @@ initialize_reloptions(void)
boolRelOpts[i].gen.lockmode));
j++;
}
+ for (i = 0; ternaryRelOpts[i].gen.name; i++)
+ {
+ Assert(DoLockModesConflict(ternaryRelOpts[i].gen.lockmode,
+ ternaryRelOpts[i].gen.lockmode));
+ j++;
+ }
+
for (i = 0; intRelOpts[i].gen.name; i++)
{
Assert(DoLockModesConflict(intRelOpts[i].gen.lockmode,
@@ -640,6 +653,14 @@ initialize_reloptions(void)
j++;
}
+ for (i = 0; ternaryRelOpts[i].gen.name; i++)
+ {
+ relOpts[j] = &ternaryRelOpts[i].gen;
+ relOpts[j]->type = RELOPT_TYPE_TERNARY;
+ relOpts[j]->namelen = strlen(relOpts[j]->name);
+ j++;
+ }
+
for (i = 0; intRelOpts[i].gen.name; i++)
{
relOpts[j] = &intRelOpts[i].gen;
@@ -800,6 +821,9 @@ allocate_reloption(bits32 kinds, int type, const char *name, const char *desc,
case RELOPT_TYPE_BOOL:
size = sizeof(relopt_bool);
break;
+ case RELOPT_TYPE_TERNARY:
+ size = sizeof(relopt_ternary);
+ break;
case RELOPT_TYPE_INT:
size = sizeof(relopt_int);
break;
@@ -883,6 +907,54 @@ add_local_bool_reloption(local_relopts *relopts, const char *name,
add_local_reloption(relopts, (relopt_gen *) newoption, offset);
}
+/*
+ * init_ternary_reloption
+ * Allocate and initialize a new ternary reloption
+ */
+static relopt_ternary *
+init_ternary_reloption(bits32 kinds, const char *name, const char *desc,
+ ternary default_val, LOCKMODE lockmode)
+{
+ relopt_ternary *newoption;
+
+ newoption = (relopt_ternary *) allocate_reloption(kinds,
+ RELOPT_TYPE_TERNARY, name, desc, lockmode);
+ newoption->default_val = default_val;
+
+ return newoption;
+}
+
+/*
+ * add_ternary_reloption
+ * Add a new ternary reloption
+ */
+void
+add_ternary_reloption(bits32 kinds, const char *name, const char *desc,
+ ternary default_val, LOCKMODE lockmode)
+{
+ relopt_ternary *newoption = init_ternary_reloption(kinds, name, desc,
+ default_val, lockmode);
+
+ add_reloption((relopt_gen *) newoption);
+}
+
+/*
+ * add_local_ternary_reloption
+ * Add a new ternary local reloption
+ *
+ * 'offset' is offset of ternary-typed field.
+ */
+void
+add_local_ternary_reloption(local_relopts *relopts, const char *name,
+ const char *desc, ternary default_val,
+ int offset)
+{
+ relopt_ternary *newoption = init_ternary_reloption(RELOPT_KIND_LOCAL,
+ name, desc,
+ default_val, 0);
+
+ add_local_reloption(relopts, (relopt_gen *) newoption, offset);
+}
/*
* init_real_reloption
@@ -1617,6 +1689,18 @@ parse_one_reloption(relopt_value *option, char *text_str, int text_len,
option->gen->name, value)));
}
break;
+ case RELOPT_TYPE_TERNARY:
+ {
+ bool b;
+ parsed = parse_bool(value, &b);
+ option->values.ternary_val = b ? TERNARY_TRUE : TERNARY_FALSE;
+ if (validate && !parsed)
+ ereport(ERROR,
+ (errcode(ERRCODE_INVALID_PARAMETER_VALUE),
+ errmsg("invalid value for ternary option \"%s\": %s",
+ option->gen->name, value)));
+ }
+ break;
case RELOPT_TYPE_INT:
{
relopt_int *optint = (relopt_int *) option->gen;
@@ -1780,17 +1864,6 @@ fillRelOptions(void *rdopts, Size basesize,
char *itempos = ((char *) rdopts) + elems[j].offset;
char *string_val;
- /*
- * If isset_offset is provided, store whether the reloption is
- * set there.
- */
- if (elems[j].isset_offset > 0)
- {
- char *setpos = ((char *) rdopts) + elems[j].isset_offset;
-
- *(bool *) setpos = options[i].isset;
- }
-
switch (options[i].gen->type)
{
case RELOPT_TYPE_BOOL:
@@ -1798,6 +1871,11 @@ fillRelOptions(void *rdopts, Size basesize,
options[i].values.bool_val :
((relopt_bool *) options[i].gen)->default_val;
break;
+ case RELOPT_TYPE_TERNARY:
+ *(ternary *) itempos = options[i].isset ?
+ options[i].values.ternary_val :
+ ((relopt_ternary *) options[i].gen)->default_val;
+ break;
case RELOPT_TYPE_INT:
*(int *) itempos = options[i].isset ?
options[i].values.int_val :
@@ -1912,8 +1990,8 @@ default_reloptions(Datum reloptions, bool validate, relopt_kind kind)
offsetof(StdRdOptions, parallel_workers)},
{"vacuum_index_cleanup", RELOPT_TYPE_ENUM,
offsetof(StdRdOptions, vacuum_index_cleanup)},
- {"vacuum_truncate", RELOPT_TYPE_BOOL,
- offsetof(StdRdOptions, vacuum_truncate), offsetof(StdRdOptions, vacuum_truncate_set)},
+ {"vacuum_truncate", RELOPT_TYPE_TERNARY,
+ offsetof(StdRdOptions, vacuum_truncate)},
{"vacuum_max_eager_freeze_failure_rate", RELOPT_TYPE_REAL,
offsetof(StdRdOptions, vacuum_max_eager_freeze_failure_rate)}
};
@@ -1993,7 +2071,6 @@ build_local_reloptions(local_relopts *relopts, Datum options, bool validate)
elems[i].optname = opt->option->name;
elems[i].opttype = opt->option->type;
elems[i].offset = opt->offset;
- elems[i].isset_offset = 0; /* not supported for local relopts yet */
i++;
}
diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c
index 733ef40ae7..977babff54 100644
--- a/src/backend/commands/vacuum.c
+++ b/src/backend/commands/vacuum.c
@@ -2221,9 +2221,9 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams params,
{
StdRdOptions *opts = (StdRdOptions *) rel->rd_options;
- if (opts && opts->vacuum_truncate_set)
+ if (opts && opts->vacuum_truncate != TERNARY_UNSET)
{
- if (opts->vacuum_truncate)
+ if (opts->vacuum_truncate == TERNARY_TRUE)
params.truncate = VACOPTVALUE_ENABLED;
else
params.truncate = VACOPTVALUE_DISABLED;
diff --git a/src/include/access/reloptions.h b/src/include/access/reloptions.h
index a604a4702c..a436697658 100644
--- a/src/include/access/reloptions.h
+++ b/src/include/access/reloptions.h
@@ -29,6 +29,7 @@
typedef enum relopt_type
{
RELOPT_TYPE_BOOL,
+ RELOPT_TYPE_TERNARY, /* on, off, unset */
RELOPT_TYPE_INT,
RELOPT_TYPE_REAL,
RELOPT_TYPE_ENUM,
@@ -80,6 +81,7 @@ typedef struct relopt_value
union
{
bool bool_val;
+ ternary ternary_val;
int int_val;
double real_val;
int enum_val;
@@ -94,6 +96,12 @@ typedef struct relopt_bool
bool default_val;
} relopt_bool;
+typedef struct relopt_rernary
+{
+ relopt_gen gen;
+ int default_val;
+} relopt_ternary;
+
typedef struct relopt_int
{
relopt_gen gen;
@@ -152,19 +160,6 @@ typedef struct
const char *optname; /* option's name */
relopt_type opttype; /* option's datatype */
int offset; /* offset of field in result struct */
-
- /*
- * isset_offset is an optional offset of a field in the result struct that
- * stores whether the option is explicitly set for the relation or if it
- * just picked up the default value. In most cases, this can be
- * accomplished by giving the reloption a special out-of-range default
- * value (e.g., some integer reloptions use -2), but this isn't always
- * possible. For example, a Boolean reloption cannot be given an
- * out-of-range default, so we need another way to discover the source of
- * its value. This offset is only used if given a value greater than
- * zero.
- */
- int isset_offset;
} relopt_parse_elt;
/* Local reloption definition */
@@ -195,6 +190,8 @@ typedef struct local_relopts
extern relopt_kind add_reloption_kind(void);
extern void add_bool_reloption(bits32 kinds, const char *name, const char *desc,
bool default_val, LOCKMODE lockmode);
+extern void add_ternary_reloption(bits32 kinds, const char *name,
+ const char *desc, int default_val, LOCKMODE lockmode);
extern void add_int_reloption(bits32 kinds, const char *name, const char *desc,
int default_val, int min_val, int max_val,
LOCKMODE lockmode);
@@ -214,6 +211,9 @@ extern void register_reloptions_validator(local_relopts *relopts,
extern void add_local_bool_reloption(local_relopts *relopts, const char *name,
const char *desc, bool default_val,
int offset);
+extern void add_local_ternary_reloption(local_relopts *relopts,
+ const char *name, const char *desc,
+ ternary default_val, int offset);
extern void add_local_int_reloption(local_relopts *relopts, const char *name,
const char *desc, int default_val,
int min_val, int max_val, int offset);
diff --git a/src/include/c.h b/src/include/c.h
index 39022f8a9d..9c59ccccf8 100644
--- a/src/include/c.h
+++ b/src/include/c.h
@@ -488,6 +488,22 @@ typedef void (*pg_funcptr_t) (void);
#include <stdbool.h>
+/*
+ * ternary
+ * Boolean value with an extrea "unset" option
+ *
+ * Ternary data type is used in relation options that can be "true", "false" or
+ * "unset". Since relation options are used deep inside the PostgreSQL code,
+ * this type is declared globally.
+*/
+
+typedef enum ternary
+{
+ TERNARY_FALSE = 0,
+ TERNARY_TRUE = 1,
+ TERNARY_UNSET = -1
+} ternary;
+
/* ----------------------------------------------------------------
* Section 3: standard system types
diff --git a/src/include/utils/rel.h b/src/include/utils/rel.h
index b552359915..7346d618f9 100644
--- a/src/include/utils/rel.h
+++ b/src/include/utils/rel.h
@@ -346,8 +346,7 @@ typedef struct StdRdOptions
bool user_catalog_table; /* use as an additional catalog relation */
int parallel_workers; /* max number of parallel workers */
StdRdOptIndexCleanup vacuum_index_cleanup; /* controls index vacuuming */
- bool vacuum_truncate; /* enables vacuum to truncate a relation */
- bool vacuum_truncate_set; /* whether vacuum_truncate is set */
+ ternary vacuum_truncate; /* enables vacuum to truncate a relation */
/*
* Fraction of pages in a relation that vacuum can eagerly scan and fail
--
2.39.2
--nextPart4155600.3ZeAukHxDK
Content-Disposition: attachment;
filename="v2a-0003-Add-alias-to-be-used-as-unset-state.patch"
Content-Transfer-Encoding: 7Bit
Content-Type: text/x-patch; charset="unicode-2-0-utf-8";
name="v2a-0003-Add-alias-to-be-used-as-unset-state.patch"
^ permalink raw reply [nested|flat] 868+ messages in thread
* [PATCH v2 2/4] Introduce ternary reloptions
@ 2025-09-04 14:41 Nikolay Shaplov <[email protected]>
0 siblings, 0 replies; 868+ messages in thread
From: Nikolay Shaplov @ 2025-09-04 14:41 UTC (permalink / raw)
Introduce ternary reloption as a replacement for current `vacuum_truncate`
implementation. Remove `vacuum_truncate_set` additional flag and using
`TERNARY_UNSET` value instead.
---
src/backend/access/common/reloptions.c | 129 ++++++++++++++++++++-----
src/backend/commands/vacuum.c | 4 +-
src/include/access/reloptions.h | 26 ++---
src/include/c.h | 16 +++
src/include/utils/rel.h | 3 +-
5 files changed, 135 insertions(+), 43 deletions(-)
diff --git a/src/backend/access/common/reloptions.c b/src/backend/access/common/reloptions.c
index 0af3fea68f..f543f11001 100644
--- a/src/backend/access/common/reloptions.c
+++ b/src/backend/access/common/reloptions.c
@@ -40,9 +40,9 @@
*
* To add an option:
*
- * (i) decide on a type (bool, integer, real, enum, string), name, default
- * value, upper and lower bounds (if applicable); for strings, consider a
- * validation routine.
+ * (i) decide on a type (bool, ternary, integer, real, enum, string), name,
+ * default value, upper and lower bounds (if applicable); for strings,
+ * consider a validation routine.
* (ii) add a record below (or use add_<type>_reloption).
* (iii) add it to the appropriate options struct (perhaps StdRdOptions)
* (iv) add it to the appropriate handling routine (perhaps
@@ -147,15 +147,6 @@ static relopt_bool boolRelOpts[] =
},
false
},
- {
- {
- "vacuum_truncate",
- "Enables vacuum to truncate empty pages at the end of this table",
- RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
- ShareUpdateExclusiveLock
- },
- true
- },
{
{
"deduplicate_items",
@@ -170,6 +161,21 @@ static relopt_bool boolRelOpts[] =
{{NULL}}
};
+static relopt_ternary ternaryRelOpts[] =
+{
+ {
+ {
+ "vacuum_truncate",
+ "Enables vacuum to truncate empty pages at the end of this table",
+ RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
+ ShareUpdateExclusiveLock
+ },
+ TERNARY_UNSET
+ },
+ /* list terminator */
+ {{NULL}}
+};
+
static relopt_int intRelOpts[] =
{
{
@@ -600,6 +606,13 @@ initialize_reloptions(void)
boolRelOpts[i].gen.lockmode));
j++;
}
+ for (i = 0; ternaryRelOpts[i].gen.name; i++)
+ {
+ Assert(DoLockModesConflict(ternaryRelOpts[i].gen.lockmode,
+ ternaryRelOpts[i].gen.lockmode));
+ j++;
+ }
+
for (i = 0; intRelOpts[i].gen.name; i++)
{
Assert(DoLockModesConflict(intRelOpts[i].gen.lockmode,
@@ -640,6 +653,14 @@ initialize_reloptions(void)
j++;
}
+ for (i = 0; ternaryRelOpts[i].gen.name; i++)
+ {
+ relOpts[j] = &ternaryRelOpts[i].gen;
+ relOpts[j]->type = RELOPT_TYPE_TERNARY;
+ relOpts[j]->namelen = strlen(relOpts[j]->name);
+ j++;
+ }
+
for (i = 0; intRelOpts[i].gen.name; i++)
{
relOpts[j] = &intRelOpts[i].gen;
@@ -800,6 +821,9 @@ allocate_reloption(bits32 kinds, int type, const char *name, const char *desc,
case RELOPT_TYPE_BOOL:
size = sizeof(relopt_bool);
break;
+ case RELOPT_TYPE_TERNARY:
+ size = sizeof(relopt_ternary);
+ break;
case RELOPT_TYPE_INT:
size = sizeof(relopt_int);
break;
@@ -883,6 +907,54 @@ add_local_bool_reloption(local_relopts *relopts, const char *name,
add_local_reloption(relopts, (relopt_gen *) newoption, offset);
}
+/*
+ * init_ternary_reloption
+ * Allocate and initialize a new ternary reloption
+ */
+static relopt_ternary *
+init_ternary_reloption(bits32 kinds, const char *name, const char *desc,
+ ternary default_val, LOCKMODE lockmode)
+{
+ relopt_ternary *newoption;
+
+ newoption = (relopt_ternary *) allocate_reloption(kinds,
+ RELOPT_TYPE_TERNARY, name, desc, lockmode);
+ newoption->default_val = default_val;
+
+ return newoption;
+}
+
+/*
+ * add_ternary_reloption
+ * Add a new ternary reloption
+ */
+void
+add_ternary_reloption(bits32 kinds, const char *name, const char *desc,
+ ternary default_val, LOCKMODE lockmode)
+{
+ relopt_ternary *newoption = init_ternary_reloption(kinds, name, desc,
+ default_val, lockmode);
+
+ add_reloption((relopt_gen *) newoption);
+}
+
+/*
+ * add_local_ternary_reloption
+ * Add a new ternary local reloption
+ *
+ * 'offset' is offset of ternary-typed field.
+ */
+void
+add_local_ternary_reloption(local_relopts *relopts, const char *name,
+ const char *desc, ternary default_val,
+ int offset)
+{
+ relopt_ternary *newoption = init_ternary_reloption(RELOPT_KIND_LOCAL,
+ name, desc,
+ default_val, 0);
+
+ add_local_reloption(relopts, (relopt_gen *) newoption, offset);
+}
/*
* init_real_reloption
@@ -1617,6 +1689,18 @@ parse_one_reloption(relopt_value *option, char *text_str, int text_len,
option->gen->name, value)));
}
break;
+ case RELOPT_TYPE_TERNARY:
+ {
+ bool b;
+ parsed = parse_bool(value, &b);
+ option->values.ternary_val = b ? TERNARY_TRUE : TERNARY_FALSE;
+ if (validate && !parsed)
+ ereport(ERROR,
+ (errcode(ERRCODE_INVALID_PARAMETER_VALUE),
+ errmsg("invalid value for ternary option \"%s\": %s",
+ option->gen->name, value)));
+ }
+ break;
case RELOPT_TYPE_INT:
{
relopt_int *optint = (relopt_int *) option->gen;
@@ -1780,17 +1864,6 @@ fillRelOptions(void *rdopts, Size basesize,
char *itempos = ((char *) rdopts) + elems[j].offset;
char *string_val;
- /*
- * If isset_offset is provided, store whether the reloption is
- * set there.
- */
- if (elems[j].isset_offset > 0)
- {
- char *setpos = ((char *) rdopts) + elems[j].isset_offset;
-
- *(bool *) setpos = options[i].isset;
- }
-
switch (options[i].gen->type)
{
case RELOPT_TYPE_BOOL:
@@ -1798,6 +1871,11 @@ fillRelOptions(void *rdopts, Size basesize,
options[i].values.bool_val :
((relopt_bool *) options[i].gen)->default_val;
break;
+ case RELOPT_TYPE_TERNARY:
+ *(ternary *) itempos = options[i].isset ?
+ options[i].values.ternary_val :
+ ((relopt_ternary *) options[i].gen)->default_val;
+ break;
case RELOPT_TYPE_INT:
*(int *) itempos = options[i].isset ?
options[i].values.int_val :
@@ -1912,8 +1990,8 @@ default_reloptions(Datum reloptions, bool validate, relopt_kind kind)
offsetof(StdRdOptions, parallel_workers)},
{"vacuum_index_cleanup", RELOPT_TYPE_ENUM,
offsetof(StdRdOptions, vacuum_index_cleanup)},
- {"vacuum_truncate", RELOPT_TYPE_BOOL,
- offsetof(StdRdOptions, vacuum_truncate), offsetof(StdRdOptions, vacuum_truncate_set)},
+ {"vacuum_truncate", RELOPT_TYPE_TERNARY,
+ offsetof(StdRdOptions, vacuum_truncate)},
{"vacuum_max_eager_freeze_failure_rate", RELOPT_TYPE_REAL,
offsetof(StdRdOptions, vacuum_max_eager_freeze_failure_rate)}
};
@@ -1993,7 +2071,6 @@ build_local_reloptions(local_relopts *relopts, Datum options, bool validate)
elems[i].optname = opt->option->name;
elems[i].opttype = opt->option->type;
elems[i].offset = opt->offset;
- elems[i].isset_offset = 0; /* not supported for local relopts yet */
i++;
}
diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c
index 733ef40ae7..977babff54 100644
--- a/src/backend/commands/vacuum.c
+++ b/src/backend/commands/vacuum.c
@@ -2221,9 +2221,9 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams params,
{
StdRdOptions *opts = (StdRdOptions *) rel->rd_options;
- if (opts && opts->vacuum_truncate_set)
+ if (opts && opts->vacuum_truncate != TERNARY_UNSET)
{
- if (opts->vacuum_truncate)
+ if (opts->vacuum_truncate == TERNARY_TRUE)
params.truncate = VACOPTVALUE_ENABLED;
else
params.truncate = VACOPTVALUE_DISABLED;
diff --git a/src/include/access/reloptions.h b/src/include/access/reloptions.h
index a604a4702c..a436697658 100644
--- a/src/include/access/reloptions.h
+++ b/src/include/access/reloptions.h
@@ -29,6 +29,7 @@
typedef enum relopt_type
{
RELOPT_TYPE_BOOL,
+ RELOPT_TYPE_TERNARY, /* on, off, unset */
RELOPT_TYPE_INT,
RELOPT_TYPE_REAL,
RELOPT_TYPE_ENUM,
@@ -80,6 +81,7 @@ typedef struct relopt_value
union
{
bool bool_val;
+ ternary ternary_val;
int int_val;
double real_val;
int enum_val;
@@ -94,6 +96,12 @@ typedef struct relopt_bool
bool default_val;
} relopt_bool;
+typedef struct relopt_rernary
+{
+ relopt_gen gen;
+ int default_val;
+} relopt_ternary;
+
typedef struct relopt_int
{
relopt_gen gen;
@@ -152,19 +160,6 @@ typedef struct
const char *optname; /* option's name */
relopt_type opttype; /* option's datatype */
int offset; /* offset of field in result struct */
-
- /*
- * isset_offset is an optional offset of a field in the result struct that
- * stores whether the option is explicitly set for the relation or if it
- * just picked up the default value. In most cases, this can be
- * accomplished by giving the reloption a special out-of-range default
- * value (e.g., some integer reloptions use -2), but this isn't always
- * possible. For example, a Boolean reloption cannot be given an
- * out-of-range default, so we need another way to discover the source of
- * its value. This offset is only used if given a value greater than
- * zero.
- */
- int isset_offset;
} relopt_parse_elt;
/* Local reloption definition */
@@ -195,6 +190,8 @@ typedef struct local_relopts
extern relopt_kind add_reloption_kind(void);
extern void add_bool_reloption(bits32 kinds, const char *name, const char *desc,
bool default_val, LOCKMODE lockmode);
+extern void add_ternary_reloption(bits32 kinds, const char *name,
+ const char *desc, int default_val, LOCKMODE lockmode);
extern void add_int_reloption(bits32 kinds, const char *name, const char *desc,
int default_val, int min_val, int max_val,
LOCKMODE lockmode);
@@ -214,6 +211,9 @@ extern void register_reloptions_validator(local_relopts *relopts,
extern void add_local_bool_reloption(local_relopts *relopts, const char *name,
const char *desc, bool default_val,
int offset);
+extern void add_local_ternary_reloption(local_relopts *relopts,
+ const char *name, const char *desc,
+ ternary default_val, int offset);
extern void add_local_int_reloption(local_relopts *relopts, const char *name,
const char *desc, int default_val,
int min_val, int max_val, int offset);
diff --git a/src/include/c.h b/src/include/c.h
index 39022f8a9d..9c59ccccf8 100644
--- a/src/include/c.h
+++ b/src/include/c.h
@@ -488,6 +488,22 @@ typedef void (*pg_funcptr_t) (void);
#include <stdbool.h>
+/*
+ * ternary
+ * Boolean value with an extrea "unset" option
+ *
+ * Ternary data type is used in relation options that can be "true", "false" or
+ * "unset". Since relation options are used deep inside the PostgreSQL code,
+ * this type is declared globally.
+*/
+
+typedef enum ternary
+{
+ TERNARY_FALSE = 0,
+ TERNARY_TRUE = 1,
+ TERNARY_UNSET = -1
+} ternary;
+
/* ----------------------------------------------------------------
* Section 3: standard system types
diff --git a/src/include/utils/rel.h b/src/include/utils/rel.h
index b552359915..7346d618f9 100644
--- a/src/include/utils/rel.h
+++ b/src/include/utils/rel.h
@@ -346,8 +346,7 @@ typedef struct StdRdOptions
bool user_catalog_table; /* use as an additional catalog relation */
int parallel_workers; /* max number of parallel workers */
StdRdOptIndexCleanup vacuum_index_cleanup; /* controls index vacuuming */
- bool vacuum_truncate; /* enables vacuum to truncate a relation */
- bool vacuum_truncate_set; /* whether vacuum_truncate is set */
+ ternary vacuum_truncate; /* enables vacuum to truncate a relation */
/*
* Fraction of pages in a relation that vacuum can eagerly scan and fail
--
2.39.2
--nextPart5129932.5fSG56mABF
Content-Disposition: attachment;
filename="v2-0003-Add-alias-to-be-used-as-unset-state.patch"
Content-Transfer-Encoding: 7Bit
Content-Type: text/x-patch; charset="unicode-2-0-utf-8";
name="v2-0003-Add-alias-to-be-used-as-unset-state.patch"
^ permalink raw reply [nested|flat] 868+ messages in thread
* [PATCH v2a 2/4] Introduce ternary reloptions
@ 2025-09-04 14:41 Nikolay Shaplov <[email protected]>
0 siblings, 0 replies; 868+ messages in thread
From: Nikolay Shaplov @ 2025-09-04 14:41 UTC (permalink / raw)
Introduce ternary reloption as a replacement for current `vacuum_truncate`
implementation. Remove `vacuum_truncate_set` additional flag and using
`TERNARY_UNSET` value instead.
---
src/backend/access/common/reloptions.c | 129 ++++++++++++++++++++-----
src/backend/commands/vacuum.c | 4 +-
src/include/access/reloptions.h | 26 ++---
src/include/c.h | 16 +++
src/include/utils/rel.h | 3 +-
5 files changed, 135 insertions(+), 43 deletions(-)
diff --git a/src/backend/access/common/reloptions.c b/src/backend/access/common/reloptions.c
index 0af3fea68f..f543f11001 100644
--- a/src/backend/access/common/reloptions.c
+++ b/src/backend/access/common/reloptions.c
@@ -40,9 +40,9 @@
*
* To add an option:
*
- * (i) decide on a type (bool, integer, real, enum, string), name, default
- * value, upper and lower bounds (if applicable); for strings, consider a
- * validation routine.
+ * (i) decide on a type (bool, ternary, integer, real, enum, string), name,
+ * default value, upper and lower bounds (if applicable); for strings,
+ * consider a validation routine.
* (ii) add a record below (or use add_<type>_reloption).
* (iii) add it to the appropriate options struct (perhaps StdRdOptions)
* (iv) add it to the appropriate handling routine (perhaps
@@ -147,15 +147,6 @@ static relopt_bool boolRelOpts[] =
},
false
},
- {
- {
- "vacuum_truncate",
- "Enables vacuum to truncate empty pages at the end of this table",
- RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
- ShareUpdateExclusiveLock
- },
- true
- },
{
{
"deduplicate_items",
@@ -170,6 +161,21 @@ static relopt_bool boolRelOpts[] =
{{NULL}}
};
+static relopt_ternary ternaryRelOpts[] =
+{
+ {
+ {
+ "vacuum_truncate",
+ "Enables vacuum to truncate empty pages at the end of this table",
+ RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
+ ShareUpdateExclusiveLock
+ },
+ TERNARY_UNSET
+ },
+ /* list terminator */
+ {{NULL}}
+};
+
static relopt_int intRelOpts[] =
{
{
@@ -600,6 +606,13 @@ initialize_reloptions(void)
boolRelOpts[i].gen.lockmode));
j++;
}
+ for (i = 0; ternaryRelOpts[i].gen.name; i++)
+ {
+ Assert(DoLockModesConflict(ternaryRelOpts[i].gen.lockmode,
+ ternaryRelOpts[i].gen.lockmode));
+ j++;
+ }
+
for (i = 0; intRelOpts[i].gen.name; i++)
{
Assert(DoLockModesConflict(intRelOpts[i].gen.lockmode,
@@ -640,6 +653,14 @@ initialize_reloptions(void)
j++;
}
+ for (i = 0; ternaryRelOpts[i].gen.name; i++)
+ {
+ relOpts[j] = &ternaryRelOpts[i].gen;
+ relOpts[j]->type = RELOPT_TYPE_TERNARY;
+ relOpts[j]->namelen = strlen(relOpts[j]->name);
+ j++;
+ }
+
for (i = 0; intRelOpts[i].gen.name; i++)
{
relOpts[j] = &intRelOpts[i].gen;
@@ -800,6 +821,9 @@ allocate_reloption(bits32 kinds, int type, const char *name, const char *desc,
case RELOPT_TYPE_BOOL:
size = sizeof(relopt_bool);
break;
+ case RELOPT_TYPE_TERNARY:
+ size = sizeof(relopt_ternary);
+ break;
case RELOPT_TYPE_INT:
size = sizeof(relopt_int);
break;
@@ -883,6 +907,54 @@ add_local_bool_reloption(local_relopts *relopts, const char *name,
add_local_reloption(relopts, (relopt_gen *) newoption, offset);
}
+/*
+ * init_ternary_reloption
+ * Allocate and initialize a new ternary reloption
+ */
+static relopt_ternary *
+init_ternary_reloption(bits32 kinds, const char *name, const char *desc,
+ ternary default_val, LOCKMODE lockmode)
+{
+ relopt_ternary *newoption;
+
+ newoption = (relopt_ternary *) allocate_reloption(kinds,
+ RELOPT_TYPE_TERNARY, name, desc, lockmode);
+ newoption->default_val = default_val;
+
+ return newoption;
+}
+
+/*
+ * add_ternary_reloption
+ * Add a new ternary reloption
+ */
+void
+add_ternary_reloption(bits32 kinds, const char *name, const char *desc,
+ ternary default_val, LOCKMODE lockmode)
+{
+ relopt_ternary *newoption = init_ternary_reloption(kinds, name, desc,
+ default_val, lockmode);
+
+ add_reloption((relopt_gen *) newoption);
+}
+
+/*
+ * add_local_ternary_reloption
+ * Add a new ternary local reloption
+ *
+ * 'offset' is offset of ternary-typed field.
+ */
+void
+add_local_ternary_reloption(local_relopts *relopts, const char *name,
+ const char *desc, ternary default_val,
+ int offset)
+{
+ relopt_ternary *newoption = init_ternary_reloption(RELOPT_KIND_LOCAL,
+ name, desc,
+ default_val, 0);
+
+ add_local_reloption(relopts, (relopt_gen *) newoption, offset);
+}
/*
* init_real_reloption
@@ -1617,6 +1689,18 @@ parse_one_reloption(relopt_value *option, char *text_str, int text_len,
option->gen->name, value)));
}
break;
+ case RELOPT_TYPE_TERNARY:
+ {
+ bool b;
+ parsed = parse_bool(value, &b);
+ option->values.ternary_val = b ? TERNARY_TRUE : TERNARY_FALSE;
+ if (validate && !parsed)
+ ereport(ERROR,
+ (errcode(ERRCODE_INVALID_PARAMETER_VALUE),
+ errmsg("invalid value for ternary option \"%s\": %s",
+ option->gen->name, value)));
+ }
+ break;
case RELOPT_TYPE_INT:
{
relopt_int *optint = (relopt_int *) option->gen;
@@ -1780,17 +1864,6 @@ fillRelOptions(void *rdopts, Size basesize,
char *itempos = ((char *) rdopts) + elems[j].offset;
char *string_val;
- /*
- * If isset_offset is provided, store whether the reloption is
- * set there.
- */
- if (elems[j].isset_offset > 0)
- {
- char *setpos = ((char *) rdopts) + elems[j].isset_offset;
-
- *(bool *) setpos = options[i].isset;
- }
-
switch (options[i].gen->type)
{
case RELOPT_TYPE_BOOL:
@@ -1798,6 +1871,11 @@ fillRelOptions(void *rdopts, Size basesize,
options[i].values.bool_val :
((relopt_bool *) options[i].gen)->default_val;
break;
+ case RELOPT_TYPE_TERNARY:
+ *(ternary *) itempos = options[i].isset ?
+ options[i].values.ternary_val :
+ ((relopt_ternary *) options[i].gen)->default_val;
+ break;
case RELOPT_TYPE_INT:
*(int *) itempos = options[i].isset ?
options[i].values.int_val :
@@ -1912,8 +1990,8 @@ default_reloptions(Datum reloptions, bool validate, relopt_kind kind)
offsetof(StdRdOptions, parallel_workers)},
{"vacuum_index_cleanup", RELOPT_TYPE_ENUM,
offsetof(StdRdOptions, vacuum_index_cleanup)},
- {"vacuum_truncate", RELOPT_TYPE_BOOL,
- offsetof(StdRdOptions, vacuum_truncate), offsetof(StdRdOptions, vacuum_truncate_set)},
+ {"vacuum_truncate", RELOPT_TYPE_TERNARY,
+ offsetof(StdRdOptions, vacuum_truncate)},
{"vacuum_max_eager_freeze_failure_rate", RELOPT_TYPE_REAL,
offsetof(StdRdOptions, vacuum_max_eager_freeze_failure_rate)}
};
@@ -1993,7 +2071,6 @@ build_local_reloptions(local_relopts *relopts, Datum options, bool validate)
elems[i].optname = opt->option->name;
elems[i].opttype = opt->option->type;
elems[i].offset = opt->offset;
- elems[i].isset_offset = 0; /* not supported for local relopts yet */
i++;
}
diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c
index 733ef40ae7..977babff54 100644
--- a/src/backend/commands/vacuum.c
+++ b/src/backend/commands/vacuum.c
@@ -2221,9 +2221,9 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams params,
{
StdRdOptions *opts = (StdRdOptions *) rel->rd_options;
- if (opts && opts->vacuum_truncate_set)
+ if (opts && opts->vacuum_truncate != TERNARY_UNSET)
{
- if (opts->vacuum_truncate)
+ if (opts->vacuum_truncate == TERNARY_TRUE)
params.truncate = VACOPTVALUE_ENABLED;
else
params.truncate = VACOPTVALUE_DISABLED;
diff --git a/src/include/access/reloptions.h b/src/include/access/reloptions.h
index a604a4702c..a436697658 100644
--- a/src/include/access/reloptions.h
+++ b/src/include/access/reloptions.h
@@ -29,6 +29,7 @@
typedef enum relopt_type
{
RELOPT_TYPE_BOOL,
+ RELOPT_TYPE_TERNARY, /* on, off, unset */
RELOPT_TYPE_INT,
RELOPT_TYPE_REAL,
RELOPT_TYPE_ENUM,
@@ -80,6 +81,7 @@ typedef struct relopt_value
union
{
bool bool_val;
+ ternary ternary_val;
int int_val;
double real_val;
int enum_val;
@@ -94,6 +96,12 @@ typedef struct relopt_bool
bool default_val;
} relopt_bool;
+typedef struct relopt_rernary
+{
+ relopt_gen gen;
+ int default_val;
+} relopt_ternary;
+
typedef struct relopt_int
{
relopt_gen gen;
@@ -152,19 +160,6 @@ typedef struct
const char *optname; /* option's name */
relopt_type opttype; /* option's datatype */
int offset; /* offset of field in result struct */
-
- /*
- * isset_offset is an optional offset of a field in the result struct that
- * stores whether the option is explicitly set for the relation or if it
- * just picked up the default value. In most cases, this can be
- * accomplished by giving the reloption a special out-of-range default
- * value (e.g., some integer reloptions use -2), but this isn't always
- * possible. For example, a Boolean reloption cannot be given an
- * out-of-range default, so we need another way to discover the source of
- * its value. This offset is only used if given a value greater than
- * zero.
- */
- int isset_offset;
} relopt_parse_elt;
/* Local reloption definition */
@@ -195,6 +190,8 @@ typedef struct local_relopts
extern relopt_kind add_reloption_kind(void);
extern void add_bool_reloption(bits32 kinds, const char *name, const char *desc,
bool default_val, LOCKMODE lockmode);
+extern void add_ternary_reloption(bits32 kinds, const char *name,
+ const char *desc, int default_val, LOCKMODE lockmode);
extern void add_int_reloption(bits32 kinds, const char *name, const char *desc,
int default_val, int min_val, int max_val,
LOCKMODE lockmode);
@@ -214,6 +211,9 @@ extern void register_reloptions_validator(local_relopts *relopts,
extern void add_local_bool_reloption(local_relopts *relopts, const char *name,
const char *desc, bool default_val,
int offset);
+extern void add_local_ternary_reloption(local_relopts *relopts,
+ const char *name, const char *desc,
+ ternary default_val, int offset);
extern void add_local_int_reloption(local_relopts *relopts, const char *name,
const char *desc, int default_val,
int min_val, int max_val, int offset);
diff --git a/src/include/c.h b/src/include/c.h
index 39022f8a9d..9c59ccccf8 100644
--- a/src/include/c.h
+++ b/src/include/c.h
@@ -488,6 +488,22 @@ typedef void (*pg_funcptr_t) (void);
#include <stdbool.h>
+/*
+ * ternary
+ * Boolean value with an extrea "unset" option
+ *
+ * Ternary data type is used in relation options that can be "true", "false" or
+ * "unset". Since relation options are used deep inside the PostgreSQL code,
+ * this type is declared globally.
+*/
+
+typedef enum ternary
+{
+ TERNARY_FALSE = 0,
+ TERNARY_TRUE = 1,
+ TERNARY_UNSET = -1
+} ternary;
+
/* ----------------------------------------------------------------
* Section 3: standard system types
diff --git a/src/include/utils/rel.h b/src/include/utils/rel.h
index b552359915..7346d618f9 100644
--- a/src/include/utils/rel.h
+++ b/src/include/utils/rel.h
@@ -346,8 +346,7 @@ typedef struct StdRdOptions
bool user_catalog_table; /* use as an additional catalog relation */
int parallel_workers; /* max number of parallel workers */
StdRdOptIndexCleanup vacuum_index_cleanup; /* controls index vacuuming */
- bool vacuum_truncate; /* enables vacuum to truncate a relation */
- bool vacuum_truncate_set; /* whether vacuum_truncate is set */
+ ternary vacuum_truncate; /* enables vacuum to truncate a relation */
/*
* Fraction of pages in a relation that vacuum can eagerly scan and fail
--
2.39.2
--nextPart4155600.3ZeAukHxDK
Content-Disposition: attachment;
filename="v2a-0003-Add-alias-to-be-used-as-unset-state.patch"
Content-Transfer-Encoding: 7Bit
Content-Type: text/x-patch; charset="unicode-2-0-utf-8";
name="v2a-0003-Add-alias-to-be-used-as-unset-state.patch"
^ permalink raw reply [nested|flat] 868+ messages in thread
* [PATCH v2 2/4] Introduce ternary reloptions
@ 2025-09-04 14:41 Nikolay Shaplov <[email protected]>
0 siblings, 0 replies; 868+ messages in thread
From: Nikolay Shaplov @ 2025-09-04 14:41 UTC (permalink / raw)
Introduce ternary reloption as a replacement for current `vacuum_truncate`
implementation. Remove `vacuum_truncate_set` additional flag and using
`TERNARY_UNSET` value instead.
---
src/backend/access/common/reloptions.c | 129 ++++++++++++++++++++-----
src/backend/commands/vacuum.c | 4 +-
src/include/access/reloptions.h | 26 ++---
src/include/c.h | 16 +++
src/include/utils/rel.h | 3 +-
5 files changed, 135 insertions(+), 43 deletions(-)
diff --git a/src/backend/access/common/reloptions.c b/src/backend/access/common/reloptions.c
index 0af3fea68f..f543f11001 100644
--- a/src/backend/access/common/reloptions.c
+++ b/src/backend/access/common/reloptions.c
@@ -40,9 +40,9 @@
*
* To add an option:
*
- * (i) decide on a type (bool, integer, real, enum, string), name, default
- * value, upper and lower bounds (if applicable); for strings, consider a
- * validation routine.
+ * (i) decide on a type (bool, ternary, integer, real, enum, string), name,
+ * default value, upper and lower bounds (if applicable); for strings,
+ * consider a validation routine.
* (ii) add a record below (or use add_<type>_reloption).
* (iii) add it to the appropriate options struct (perhaps StdRdOptions)
* (iv) add it to the appropriate handling routine (perhaps
@@ -147,15 +147,6 @@ static relopt_bool boolRelOpts[] =
},
false
},
- {
- {
- "vacuum_truncate",
- "Enables vacuum to truncate empty pages at the end of this table",
- RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
- ShareUpdateExclusiveLock
- },
- true
- },
{
{
"deduplicate_items",
@@ -170,6 +161,21 @@ static relopt_bool boolRelOpts[] =
{{NULL}}
};
+static relopt_ternary ternaryRelOpts[] =
+{
+ {
+ {
+ "vacuum_truncate",
+ "Enables vacuum to truncate empty pages at the end of this table",
+ RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
+ ShareUpdateExclusiveLock
+ },
+ TERNARY_UNSET
+ },
+ /* list terminator */
+ {{NULL}}
+};
+
static relopt_int intRelOpts[] =
{
{
@@ -600,6 +606,13 @@ initialize_reloptions(void)
boolRelOpts[i].gen.lockmode));
j++;
}
+ for (i = 0; ternaryRelOpts[i].gen.name; i++)
+ {
+ Assert(DoLockModesConflict(ternaryRelOpts[i].gen.lockmode,
+ ternaryRelOpts[i].gen.lockmode));
+ j++;
+ }
+
for (i = 0; intRelOpts[i].gen.name; i++)
{
Assert(DoLockModesConflict(intRelOpts[i].gen.lockmode,
@@ -640,6 +653,14 @@ initialize_reloptions(void)
j++;
}
+ for (i = 0; ternaryRelOpts[i].gen.name; i++)
+ {
+ relOpts[j] = &ternaryRelOpts[i].gen;
+ relOpts[j]->type = RELOPT_TYPE_TERNARY;
+ relOpts[j]->namelen = strlen(relOpts[j]->name);
+ j++;
+ }
+
for (i = 0; intRelOpts[i].gen.name; i++)
{
relOpts[j] = &intRelOpts[i].gen;
@@ -800,6 +821,9 @@ allocate_reloption(bits32 kinds, int type, const char *name, const char *desc,
case RELOPT_TYPE_BOOL:
size = sizeof(relopt_bool);
break;
+ case RELOPT_TYPE_TERNARY:
+ size = sizeof(relopt_ternary);
+ break;
case RELOPT_TYPE_INT:
size = sizeof(relopt_int);
break;
@@ -883,6 +907,54 @@ add_local_bool_reloption(local_relopts *relopts, const char *name,
add_local_reloption(relopts, (relopt_gen *) newoption, offset);
}
+/*
+ * init_ternary_reloption
+ * Allocate and initialize a new ternary reloption
+ */
+static relopt_ternary *
+init_ternary_reloption(bits32 kinds, const char *name, const char *desc,
+ ternary default_val, LOCKMODE lockmode)
+{
+ relopt_ternary *newoption;
+
+ newoption = (relopt_ternary *) allocate_reloption(kinds,
+ RELOPT_TYPE_TERNARY, name, desc, lockmode);
+ newoption->default_val = default_val;
+
+ return newoption;
+}
+
+/*
+ * add_ternary_reloption
+ * Add a new ternary reloption
+ */
+void
+add_ternary_reloption(bits32 kinds, const char *name, const char *desc,
+ ternary default_val, LOCKMODE lockmode)
+{
+ relopt_ternary *newoption = init_ternary_reloption(kinds, name, desc,
+ default_val, lockmode);
+
+ add_reloption((relopt_gen *) newoption);
+}
+
+/*
+ * add_local_ternary_reloption
+ * Add a new ternary local reloption
+ *
+ * 'offset' is offset of ternary-typed field.
+ */
+void
+add_local_ternary_reloption(local_relopts *relopts, const char *name,
+ const char *desc, ternary default_val,
+ int offset)
+{
+ relopt_ternary *newoption = init_ternary_reloption(RELOPT_KIND_LOCAL,
+ name, desc,
+ default_val, 0);
+
+ add_local_reloption(relopts, (relopt_gen *) newoption, offset);
+}
/*
* init_real_reloption
@@ -1617,6 +1689,18 @@ parse_one_reloption(relopt_value *option, char *text_str, int text_len,
option->gen->name, value)));
}
break;
+ case RELOPT_TYPE_TERNARY:
+ {
+ bool b;
+ parsed = parse_bool(value, &b);
+ option->values.ternary_val = b ? TERNARY_TRUE : TERNARY_FALSE;
+ if (validate && !parsed)
+ ereport(ERROR,
+ (errcode(ERRCODE_INVALID_PARAMETER_VALUE),
+ errmsg("invalid value for ternary option \"%s\": %s",
+ option->gen->name, value)));
+ }
+ break;
case RELOPT_TYPE_INT:
{
relopt_int *optint = (relopt_int *) option->gen;
@@ -1780,17 +1864,6 @@ fillRelOptions(void *rdopts, Size basesize,
char *itempos = ((char *) rdopts) + elems[j].offset;
char *string_val;
- /*
- * If isset_offset is provided, store whether the reloption is
- * set there.
- */
- if (elems[j].isset_offset > 0)
- {
- char *setpos = ((char *) rdopts) + elems[j].isset_offset;
-
- *(bool *) setpos = options[i].isset;
- }
-
switch (options[i].gen->type)
{
case RELOPT_TYPE_BOOL:
@@ -1798,6 +1871,11 @@ fillRelOptions(void *rdopts, Size basesize,
options[i].values.bool_val :
((relopt_bool *) options[i].gen)->default_val;
break;
+ case RELOPT_TYPE_TERNARY:
+ *(ternary *) itempos = options[i].isset ?
+ options[i].values.ternary_val :
+ ((relopt_ternary *) options[i].gen)->default_val;
+ break;
case RELOPT_TYPE_INT:
*(int *) itempos = options[i].isset ?
options[i].values.int_val :
@@ -1912,8 +1990,8 @@ default_reloptions(Datum reloptions, bool validate, relopt_kind kind)
offsetof(StdRdOptions, parallel_workers)},
{"vacuum_index_cleanup", RELOPT_TYPE_ENUM,
offsetof(StdRdOptions, vacuum_index_cleanup)},
- {"vacuum_truncate", RELOPT_TYPE_BOOL,
- offsetof(StdRdOptions, vacuum_truncate), offsetof(StdRdOptions, vacuum_truncate_set)},
+ {"vacuum_truncate", RELOPT_TYPE_TERNARY,
+ offsetof(StdRdOptions, vacuum_truncate)},
{"vacuum_max_eager_freeze_failure_rate", RELOPT_TYPE_REAL,
offsetof(StdRdOptions, vacuum_max_eager_freeze_failure_rate)}
};
@@ -1993,7 +2071,6 @@ build_local_reloptions(local_relopts *relopts, Datum options, bool validate)
elems[i].optname = opt->option->name;
elems[i].opttype = opt->option->type;
elems[i].offset = opt->offset;
- elems[i].isset_offset = 0; /* not supported for local relopts yet */
i++;
}
diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c
index 733ef40ae7..977babff54 100644
--- a/src/backend/commands/vacuum.c
+++ b/src/backend/commands/vacuum.c
@@ -2221,9 +2221,9 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams params,
{
StdRdOptions *opts = (StdRdOptions *) rel->rd_options;
- if (opts && opts->vacuum_truncate_set)
+ if (opts && opts->vacuum_truncate != TERNARY_UNSET)
{
- if (opts->vacuum_truncate)
+ if (opts->vacuum_truncate == TERNARY_TRUE)
params.truncate = VACOPTVALUE_ENABLED;
else
params.truncate = VACOPTVALUE_DISABLED;
diff --git a/src/include/access/reloptions.h b/src/include/access/reloptions.h
index a604a4702c..a436697658 100644
--- a/src/include/access/reloptions.h
+++ b/src/include/access/reloptions.h
@@ -29,6 +29,7 @@
typedef enum relopt_type
{
RELOPT_TYPE_BOOL,
+ RELOPT_TYPE_TERNARY, /* on, off, unset */
RELOPT_TYPE_INT,
RELOPT_TYPE_REAL,
RELOPT_TYPE_ENUM,
@@ -80,6 +81,7 @@ typedef struct relopt_value
union
{
bool bool_val;
+ ternary ternary_val;
int int_val;
double real_val;
int enum_val;
@@ -94,6 +96,12 @@ typedef struct relopt_bool
bool default_val;
} relopt_bool;
+typedef struct relopt_rernary
+{
+ relopt_gen gen;
+ int default_val;
+} relopt_ternary;
+
typedef struct relopt_int
{
relopt_gen gen;
@@ -152,19 +160,6 @@ typedef struct
const char *optname; /* option's name */
relopt_type opttype; /* option's datatype */
int offset; /* offset of field in result struct */
-
- /*
- * isset_offset is an optional offset of a field in the result struct that
- * stores whether the option is explicitly set for the relation or if it
- * just picked up the default value. In most cases, this can be
- * accomplished by giving the reloption a special out-of-range default
- * value (e.g., some integer reloptions use -2), but this isn't always
- * possible. For example, a Boolean reloption cannot be given an
- * out-of-range default, so we need another way to discover the source of
- * its value. This offset is only used if given a value greater than
- * zero.
- */
- int isset_offset;
} relopt_parse_elt;
/* Local reloption definition */
@@ -195,6 +190,8 @@ typedef struct local_relopts
extern relopt_kind add_reloption_kind(void);
extern void add_bool_reloption(bits32 kinds, const char *name, const char *desc,
bool default_val, LOCKMODE lockmode);
+extern void add_ternary_reloption(bits32 kinds, const char *name,
+ const char *desc, int default_val, LOCKMODE lockmode);
extern void add_int_reloption(bits32 kinds, const char *name, const char *desc,
int default_val, int min_val, int max_val,
LOCKMODE lockmode);
@@ -214,6 +211,9 @@ extern void register_reloptions_validator(local_relopts *relopts,
extern void add_local_bool_reloption(local_relopts *relopts, const char *name,
const char *desc, bool default_val,
int offset);
+extern void add_local_ternary_reloption(local_relopts *relopts,
+ const char *name, const char *desc,
+ ternary default_val, int offset);
extern void add_local_int_reloption(local_relopts *relopts, const char *name,
const char *desc, int default_val,
int min_val, int max_val, int offset);
diff --git a/src/include/c.h b/src/include/c.h
index 39022f8a9d..9c59ccccf8 100644
--- a/src/include/c.h
+++ b/src/include/c.h
@@ -488,6 +488,22 @@ typedef void (*pg_funcptr_t) (void);
#include <stdbool.h>
+/*
+ * ternary
+ * Boolean value with an extrea "unset" option
+ *
+ * Ternary data type is used in relation options that can be "true", "false" or
+ * "unset". Since relation options are used deep inside the PostgreSQL code,
+ * this type is declared globally.
+*/
+
+typedef enum ternary
+{
+ TERNARY_FALSE = 0,
+ TERNARY_TRUE = 1,
+ TERNARY_UNSET = -1
+} ternary;
+
/* ----------------------------------------------------------------
* Section 3: standard system types
diff --git a/src/include/utils/rel.h b/src/include/utils/rel.h
index b552359915..7346d618f9 100644
--- a/src/include/utils/rel.h
+++ b/src/include/utils/rel.h
@@ -346,8 +346,7 @@ typedef struct StdRdOptions
bool user_catalog_table; /* use as an additional catalog relation */
int parallel_workers; /* max number of parallel workers */
StdRdOptIndexCleanup vacuum_index_cleanup; /* controls index vacuuming */
- bool vacuum_truncate; /* enables vacuum to truncate a relation */
- bool vacuum_truncate_set; /* whether vacuum_truncate is set */
+ ternary vacuum_truncate; /* enables vacuum to truncate a relation */
/*
* Fraction of pages in a relation that vacuum can eagerly scan and fail
--
2.39.2
--nextPart5129932.5fSG56mABF
Content-Disposition: attachment;
filename="v2-0003-Add-alias-to-be-used-as-unset-state.patch"
Content-Transfer-Encoding: 7Bit
Content-Type: text/x-patch; charset="unicode-2-0-utf-8";
name="v2-0003-Add-alias-to-be-used-as-unset-state.patch"
^ permalink raw reply [nested|flat] 868+ messages in thread
* [PATCH v2 2/4] Introduce ternary reloptions
@ 2025-09-04 14:41 Nikolay Shaplov <[email protected]>
0 siblings, 0 replies; 868+ messages in thread
From: Nikolay Shaplov @ 2025-09-04 14:41 UTC (permalink / raw)
Introduce ternary reloption as a replacement for current `vacuum_truncate`
implementation. Remove `vacuum_truncate_set` additional flag and using
`TERNARY_UNSET` value instead.
---
src/backend/access/common/reloptions.c | 129 ++++++++++++++++++++-----
src/backend/commands/vacuum.c | 4 +-
src/include/access/reloptions.h | 26 ++---
src/include/c.h | 16 +++
src/include/utils/rel.h | 3 +-
5 files changed, 135 insertions(+), 43 deletions(-)
diff --git a/src/backend/access/common/reloptions.c b/src/backend/access/common/reloptions.c
index 0af3fea68f..f543f11001 100644
--- a/src/backend/access/common/reloptions.c
+++ b/src/backend/access/common/reloptions.c
@@ -40,9 +40,9 @@
*
* To add an option:
*
- * (i) decide on a type (bool, integer, real, enum, string), name, default
- * value, upper and lower bounds (if applicable); for strings, consider a
- * validation routine.
+ * (i) decide on a type (bool, ternary, integer, real, enum, string), name,
+ * default value, upper and lower bounds (if applicable); for strings,
+ * consider a validation routine.
* (ii) add a record below (or use add_<type>_reloption).
* (iii) add it to the appropriate options struct (perhaps StdRdOptions)
* (iv) add it to the appropriate handling routine (perhaps
@@ -147,15 +147,6 @@ static relopt_bool boolRelOpts[] =
},
false
},
- {
- {
- "vacuum_truncate",
- "Enables vacuum to truncate empty pages at the end of this table",
- RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
- ShareUpdateExclusiveLock
- },
- true
- },
{
{
"deduplicate_items",
@@ -170,6 +161,21 @@ static relopt_bool boolRelOpts[] =
{{NULL}}
};
+static relopt_ternary ternaryRelOpts[] =
+{
+ {
+ {
+ "vacuum_truncate",
+ "Enables vacuum to truncate empty pages at the end of this table",
+ RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
+ ShareUpdateExclusiveLock
+ },
+ TERNARY_UNSET
+ },
+ /* list terminator */
+ {{NULL}}
+};
+
static relopt_int intRelOpts[] =
{
{
@@ -600,6 +606,13 @@ initialize_reloptions(void)
boolRelOpts[i].gen.lockmode));
j++;
}
+ for (i = 0; ternaryRelOpts[i].gen.name; i++)
+ {
+ Assert(DoLockModesConflict(ternaryRelOpts[i].gen.lockmode,
+ ternaryRelOpts[i].gen.lockmode));
+ j++;
+ }
+
for (i = 0; intRelOpts[i].gen.name; i++)
{
Assert(DoLockModesConflict(intRelOpts[i].gen.lockmode,
@@ -640,6 +653,14 @@ initialize_reloptions(void)
j++;
}
+ for (i = 0; ternaryRelOpts[i].gen.name; i++)
+ {
+ relOpts[j] = &ternaryRelOpts[i].gen;
+ relOpts[j]->type = RELOPT_TYPE_TERNARY;
+ relOpts[j]->namelen = strlen(relOpts[j]->name);
+ j++;
+ }
+
for (i = 0; intRelOpts[i].gen.name; i++)
{
relOpts[j] = &intRelOpts[i].gen;
@@ -800,6 +821,9 @@ allocate_reloption(bits32 kinds, int type, const char *name, const char *desc,
case RELOPT_TYPE_BOOL:
size = sizeof(relopt_bool);
break;
+ case RELOPT_TYPE_TERNARY:
+ size = sizeof(relopt_ternary);
+ break;
case RELOPT_TYPE_INT:
size = sizeof(relopt_int);
break;
@@ -883,6 +907,54 @@ add_local_bool_reloption(local_relopts *relopts, const char *name,
add_local_reloption(relopts, (relopt_gen *) newoption, offset);
}
+/*
+ * init_ternary_reloption
+ * Allocate and initialize a new ternary reloption
+ */
+static relopt_ternary *
+init_ternary_reloption(bits32 kinds, const char *name, const char *desc,
+ ternary default_val, LOCKMODE lockmode)
+{
+ relopt_ternary *newoption;
+
+ newoption = (relopt_ternary *) allocate_reloption(kinds,
+ RELOPT_TYPE_TERNARY, name, desc, lockmode);
+ newoption->default_val = default_val;
+
+ return newoption;
+}
+
+/*
+ * add_ternary_reloption
+ * Add a new ternary reloption
+ */
+void
+add_ternary_reloption(bits32 kinds, const char *name, const char *desc,
+ ternary default_val, LOCKMODE lockmode)
+{
+ relopt_ternary *newoption = init_ternary_reloption(kinds, name, desc,
+ default_val, lockmode);
+
+ add_reloption((relopt_gen *) newoption);
+}
+
+/*
+ * add_local_ternary_reloption
+ * Add a new ternary local reloption
+ *
+ * 'offset' is offset of ternary-typed field.
+ */
+void
+add_local_ternary_reloption(local_relopts *relopts, const char *name,
+ const char *desc, ternary default_val,
+ int offset)
+{
+ relopt_ternary *newoption = init_ternary_reloption(RELOPT_KIND_LOCAL,
+ name, desc,
+ default_val, 0);
+
+ add_local_reloption(relopts, (relopt_gen *) newoption, offset);
+}
/*
* init_real_reloption
@@ -1617,6 +1689,18 @@ parse_one_reloption(relopt_value *option, char *text_str, int text_len,
option->gen->name, value)));
}
break;
+ case RELOPT_TYPE_TERNARY:
+ {
+ bool b;
+ parsed = parse_bool(value, &b);
+ option->values.ternary_val = b ? TERNARY_TRUE : TERNARY_FALSE;
+ if (validate && !parsed)
+ ereport(ERROR,
+ (errcode(ERRCODE_INVALID_PARAMETER_VALUE),
+ errmsg("invalid value for ternary option \"%s\": %s",
+ option->gen->name, value)));
+ }
+ break;
case RELOPT_TYPE_INT:
{
relopt_int *optint = (relopt_int *) option->gen;
@@ -1780,17 +1864,6 @@ fillRelOptions(void *rdopts, Size basesize,
char *itempos = ((char *) rdopts) + elems[j].offset;
char *string_val;
- /*
- * If isset_offset is provided, store whether the reloption is
- * set there.
- */
- if (elems[j].isset_offset > 0)
- {
- char *setpos = ((char *) rdopts) + elems[j].isset_offset;
-
- *(bool *) setpos = options[i].isset;
- }
-
switch (options[i].gen->type)
{
case RELOPT_TYPE_BOOL:
@@ -1798,6 +1871,11 @@ fillRelOptions(void *rdopts, Size basesize,
options[i].values.bool_val :
((relopt_bool *) options[i].gen)->default_val;
break;
+ case RELOPT_TYPE_TERNARY:
+ *(ternary *) itempos = options[i].isset ?
+ options[i].values.ternary_val :
+ ((relopt_ternary *) options[i].gen)->default_val;
+ break;
case RELOPT_TYPE_INT:
*(int *) itempos = options[i].isset ?
options[i].values.int_val :
@@ -1912,8 +1990,8 @@ default_reloptions(Datum reloptions, bool validate, relopt_kind kind)
offsetof(StdRdOptions, parallel_workers)},
{"vacuum_index_cleanup", RELOPT_TYPE_ENUM,
offsetof(StdRdOptions, vacuum_index_cleanup)},
- {"vacuum_truncate", RELOPT_TYPE_BOOL,
- offsetof(StdRdOptions, vacuum_truncate), offsetof(StdRdOptions, vacuum_truncate_set)},
+ {"vacuum_truncate", RELOPT_TYPE_TERNARY,
+ offsetof(StdRdOptions, vacuum_truncate)},
{"vacuum_max_eager_freeze_failure_rate", RELOPT_TYPE_REAL,
offsetof(StdRdOptions, vacuum_max_eager_freeze_failure_rate)}
};
@@ -1993,7 +2071,6 @@ build_local_reloptions(local_relopts *relopts, Datum options, bool validate)
elems[i].optname = opt->option->name;
elems[i].opttype = opt->option->type;
elems[i].offset = opt->offset;
- elems[i].isset_offset = 0; /* not supported for local relopts yet */
i++;
}
diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c
index 733ef40ae7..977babff54 100644
--- a/src/backend/commands/vacuum.c
+++ b/src/backend/commands/vacuum.c
@@ -2221,9 +2221,9 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams params,
{
StdRdOptions *opts = (StdRdOptions *) rel->rd_options;
- if (opts && opts->vacuum_truncate_set)
+ if (opts && opts->vacuum_truncate != TERNARY_UNSET)
{
- if (opts->vacuum_truncate)
+ if (opts->vacuum_truncate == TERNARY_TRUE)
params.truncate = VACOPTVALUE_ENABLED;
else
params.truncate = VACOPTVALUE_DISABLED;
diff --git a/src/include/access/reloptions.h b/src/include/access/reloptions.h
index a604a4702c..a436697658 100644
--- a/src/include/access/reloptions.h
+++ b/src/include/access/reloptions.h
@@ -29,6 +29,7 @@
typedef enum relopt_type
{
RELOPT_TYPE_BOOL,
+ RELOPT_TYPE_TERNARY, /* on, off, unset */
RELOPT_TYPE_INT,
RELOPT_TYPE_REAL,
RELOPT_TYPE_ENUM,
@@ -80,6 +81,7 @@ typedef struct relopt_value
union
{
bool bool_val;
+ ternary ternary_val;
int int_val;
double real_val;
int enum_val;
@@ -94,6 +96,12 @@ typedef struct relopt_bool
bool default_val;
} relopt_bool;
+typedef struct relopt_rernary
+{
+ relopt_gen gen;
+ int default_val;
+} relopt_ternary;
+
typedef struct relopt_int
{
relopt_gen gen;
@@ -152,19 +160,6 @@ typedef struct
const char *optname; /* option's name */
relopt_type opttype; /* option's datatype */
int offset; /* offset of field in result struct */
-
- /*
- * isset_offset is an optional offset of a field in the result struct that
- * stores whether the option is explicitly set for the relation or if it
- * just picked up the default value. In most cases, this can be
- * accomplished by giving the reloption a special out-of-range default
- * value (e.g., some integer reloptions use -2), but this isn't always
- * possible. For example, a Boolean reloption cannot be given an
- * out-of-range default, so we need another way to discover the source of
- * its value. This offset is only used if given a value greater than
- * zero.
- */
- int isset_offset;
} relopt_parse_elt;
/* Local reloption definition */
@@ -195,6 +190,8 @@ typedef struct local_relopts
extern relopt_kind add_reloption_kind(void);
extern void add_bool_reloption(bits32 kinds, const char *name, const char *desc,
bool default_val, LOCKMODE lockmode);
+extern void add_ternary_reloption(bits32 kinds, const char *name,
+ const char *desc, int default_val, LOCKMODE lockmode);
extern void add_int_reloption(bits32 kinds, const char *name, const char *desc,
int default_val, int min_val, int max_val,
LOCKMODE lockmode);
@@ -214,6 +211,9 @@ extern void register_reloptions_validator(local_relopts *relopts,
extern void add_local_bool_reloption(local_relopts *relopts, const char *name,
const char *desc, bool default_val,
int offset);
+extern void add_local_ternary_reloption(local_relopts *relopts,
+ const char *name, const char *desc,
+ ternary default_val, int offset);
extern void add_local_int_reloption(local_relopts *relopts, const char *name,
const char *desc, int default_val,
int min_val, int max_val, int offset);
diff --git a/src/include/c.h b/src/include/c.h
index 39022f8a9d..9c59ccccf8 100644
--- a/src/include/c.h
+++ b/src/include/c.h
@@ -488,6 +488,22 @@ typedef void (*pg_funcptr_t) (void);
#include <stdbool.h>
+/*
+ * ternary
+ * Boolean value with an extrea "unset" option
+ *
+ * Ternary data type is used in relation options that can be "true", "false" or
+ * "unset". Since relation options are used deep inside the PostgreSQL code,
+ * this type is declared globally.
+*/
+
+typedef enum ternary
+{
+ TERNARY_FALSE = 0,
+ TERNARY_TRUE = 1,
+ TERNARY_UNSET = -1
+} ternary;
+
/* ----------------------------------------------------------------
* Section 3: standard system types
diff --git a/src/include/utils/rel.h b/src/include/utils/rel.h
index b552359915..7346d618f9 100644
--- a/src/include/utils/rel.h
+++ b/src/include/utils/rel.h
@@ -346,8 +346,7 @@ typedef struct StdRdOptions
bool user_catalog_table; /* use as an additional catalog relation */
int parallel_workers; /* max number of parallel workers */
StdRdOptIndexCleanup vacuum_index_cleanup; /* controls index vacuuming */
- bool vacuum_truncate; /* enables vacuum to truncate a relation */
- bool vacuum_truncate_set; /* whether vacuum_truncate is set */
+ ternary vacuum_truncate; /* enables vacuum to truncate a relation */
/*
* Fraction of pages in a relation that vacuum can eagerly scan and fail
--
2.39.2
--nextPart5129932.5fSG56mABF
Content-Disposition: attachment;
filename="v2-0003-Add-alias-to-be-used-as-unset-state.patch"
Content-Transfer-Encoding: 7Bit
Content-Type: text/x-patch; charset="unicode-2-0-utf-8";
name="v2-0003-Add-alias-to-be-used-as-unset-state.patch"
^ permalink raw reply [nested|flat] 868+ messages in thread
* [PATCH v2a 2/4] Introduce ternary reloptions
@ 2025-09-04 14:41 Nikolay Shaplov <[email protected]>
0 siblings, 0 replies; 868+ messages in thread
From: Nikolay Shaplov @ 2025-09-04 14:41 UTC (permalink / raw)
Introduce ternary reloption as a replacement for current `vacuum_truncate`
implementation. Remove `vacuum_truncate_set` additional flag and using
`TERNARY_UNSET` value instead.
---
src/backend/access/common/reloptions.c | 129 ++++++++++++++++++++-----
src/backend/commands/vacuum.c | 4 +-
src/include/access/reloptions.h | 26 ++---
src/include/c.h | 16 +++
src/include/utils/rel.h | 3 +-
5 files changed, 135 insertions(+), 43 deletions(-)
diff --git a/src/backend/access/common/reloptions.c b/src/backend/access/common/reloptions.c
index 0af3fea68f..f543f11001 100644
--- a/src/backend/access/common/reloptions.c
+++ b/src/backend/access/common/reloptions.c
@@ -40,9 +40,9 @@
*
* To add an option:
*
- * (i) decide on a type (bool, integer, real, enum, string), name, default
- * value, upper and lower bounds (if applicable); for strings, consider a
- * validation routine.
+ * (i) decide on a type (bool, ternary, integer, real, enum, string), name,
+ * default value, upper and lower bounds (if applicable); for strings,
+ * consider a validation routine.
* (ii) add a record below (or use add_<type>_reloption).
* (iii) add it to the appropriate options struct (perhaps StdRdOptions)
* (iv) add it to the appropriate handling routine (perhaps
@@ -147,15 +147,6 @@ static relopt_bool boolRelOpts[] =
},
false
},
- {
- {
- "vacuum_truncate",
- "Enables vacuum to truncate empty pages at the end of this table",
- RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
- ShareUpdateExclusiveLock
- },
- true
- },
{
{
"deduplicate_items",
@@ -170,6 +161,21 @@ static relopt_bool boolRelOpts[] =
{{NULL}}
};
+static relopt_ternary ternaryRelOpts[] =
+{
+ {
+ {
+ "vacuum_truncate",
+ "Enables vacuum to truncate empty pages at the end of this table",
+ RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
+ ShareUpdateExclusiveLock
+ },
+ TERNARY_UNSET
+ },
+ /* list terminator */
+ {{NULL}}
+};
+
static relopt_int intRelOpts[] =
{
{
@@ -600,6 +606,13 @@ initialize_reloptions(void)
boolRelOpts[i].gen.lockmode));
j++;
}
+ for (i = 0; ternaryRelOpts[i].gen.name; i++)
+ {
+ Assert(DoLockModesConflict(ternaryRelOpts[i].gen.lockmode,
+ ternaryRelOpts[i].gen.lockmode));
+ j++;
+ }
+
for (i = 0; intRelOpts[i].gen.name; i++)
{
Assert(DoLockModesConflict(intRelOpts[i].gen.lockmode,
@@ -640,6 +653,14 @@ initialize_reloptions(void)
j++;
}
+ for (i = 0; ternaryRelOpts[i].gen.name; i++)
+ {
+ relOpts[j] = &ternaryRelOpts[i].gen;
+ relOpts[j]->type = RELOPT_TYPE_TERNARY;
+ relOpts[j]->namelen = strlen(relOpts[j]->name);
+ j++;
+ }
+
for (i = 0; intRelOpts[i].gen.name; i++)
{
relOpts[j] = &intRelOpts[i].gen;
@@ -800,6 +821,9 @@ allocate_reloption(bits32 kinds, int type, const char *name, const char *desc,
case RELOPT_TYPE_BOOL:
size = sizeof(relopt_bool);
break;
+ case RELOPT_TYPE_TERNARY:
+ size = sizeof(relopt_ternary);
+ break;
case RELOPT_TYPE_INT:
size = sizeof(relopt_int);
break;
@@ -883,6 +907,54 @@ add_local_bool_reloption(local_relopts *relopts, const char *name,
add_local_reloption(relopts, (relopt_gen *) newoption, offset);
}
+/*
+ * init_ternary_reloption
+ * Allocate and initialize a new ternary reloption
+ */
+static relopt_ternary *
+init_ternary_reloption(bits32 kinds, const char *name, const char *desc,
+ ternary default_val, LOCKMODE lockmode)
+{
+ relopt_ternary *newoption;
+
+ newoption = (relopt_ternary *) allocate_reloption(kinds,
+ RELOPT_TYPE_TERNARY, name, desc, lockmode);
+ newoption->default_val = default_val;
+
+ return newoption;
+}
+
+/*
+ * add_ternary_reloption
+ * Add a new ternary reloption
+ */
+void
+add_ternary_reloption(bits32 kinds, const char *name, const char *desc,
+ ternary default_val, LOCKMODE lockmode)
+{
+ relopt_ternary *newoption = init_ternary_reloption(kinds, name, desc,
+ default_val, lockmode);
+
+ add_reloption((relopt_gen *) newoption);
+}
+
+/*
+ * add_local_ternary_reloption
+ * Add a new ternary local reloption
+ *
+ * 'offset' is offset of ternary-typed field.
+ */
+void
+add_local_ternary_reloption(local_relopts *relopts, const char *name,
+ const char *desc, ternary default_val,
+ int offset)
+{
+ relopt_ternary *newoption = init_ternary_reloption(RELOPT_KIND_LOCAL,
+ name, desc,
+ default_val, 0);
+
+ add_local_reloption(relopts, (relopt_gen *) newoption, offset);
+}
/*
* init_real_reloption
@@ -1617,6 +1689,18 @@ parse_one_reloption(relopt_value *option, char *text_str, int text_len,
option->gen->name, value)));
}
break;
+ case RELOPT_TYPE_TERNARY:
+ {
+ bool b;
+ parsed = parse_bool(value, &b);
+ option->values.ternary_val = b ? TERNARY_TRUE : TERNARY_FALSE;
+ if (validate && !parsed)
+ ereport(ERROR,
+ (errcode(ERRCODE_INVALID_PARAMETER_VALUE),
+ errmsg("invalid value for ternary option \"%s\": %s",
+ option->gen->name, value)));
+ }
+ break;
case RELOPT_TYPE_INT:
{
relopt_int *optint = (relopt_int *) option->gen;
@@ -1780,17 +1864,6 @@ fillRelOptions(void *rdopts, Size basesize,
char *itempos = ((char *) rdopts) + elems[j].offset;
char *string_val;
- /*
- * If isset_offset is provided, store whether the reloption is
- * set there.
- */
- if (elems[j].isset_offset > 0)
- {
- char *setpos = ((char *) rdopts) + elems[j].isset_offset;
-
- *(bool *) setpos = options[i].isset;
- }
-
switch (options[i].gen->type)
{
case RELOPT_TYPE_BOOL:
@@ -1798,6 +1871,11 @@ fillRelOptions(void *rdopts, Size basesize,
options[i].values.bool_val :
((relopt_bool *) options[i].gen)->default_val;
break;
+ case RELOPT_TYPE_TERNARY:
+ *(ternary *) itempos = options[i].isset ?
+ options[i].values.ternary_val :
+ ((relopt_ternary *) options[i].gen)->default_val;
+ break;
case RELOPT_TYPE_INT:
*(int *) itempos = options[i].isset ?
options[i].values.int_val :
@@ -1912,8 +1990,8 @@ default_reloptions(Datum reloptions, bool validate, relopt_kind kind)
offsetof(StdRdOptions, parallel_workers)},
{"vacuum_index_cleanup", RELOPT_TYPE_ENUM,
offsetof(StdRdOptions, vacuum_index_cleanup)},
- {"vacuum_truncate", RELOPT_TYPE_BOOL,
- offsetof(StdRdOptions, vacuum_truncate), offsetof(StdRdOptions, vacuum_truncate_set)},
+ {"vacuum_truncate", RELOPT_TYPE_TERNARY,
+ offsetof(StdRdOptions, vacuum_truncate)},
{"vacuum_max_eager_freeze_failure_rate", RELOPT_TYPE_REAL,
offsetof(StdRdOptions, vacuum_max_eager_freeze_failure_rate)}
};
@@ -1993,7 +2071,6 @@ build_local_reloptions(local_relopts *relopts, Datum options, bool validate)
elems[i].optname = opt->option->name;
elems[i].opttype = opt->option->type;
elems[i].offset = opt->offset;
- elems[i].isset_offset = 0; /* not supported for local relopts yet */
i++;
}
diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c
index 733ef40ae7..977babff54 100644
--- a/src/backend/commands/vacuum.c
+++ b/src/backend/commands/vacuum.c
@@ -2221,9 +2221,9 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams params,
{
StdRdOptions *opts = (StdRdOptions *) rel->rd_options;
- if (opts && opts->vacuum_truncate_set)
+ if (opts && opts->vacuum_truncate != TERNARY_UNSET)
{
- if (opts->vacuum_truncate)
+ if (opts->vacuum_truncate == TERNARY_TRUE)
params.truncate = VACOPTVALUE_ENABLED;
else
params.truncate = VACOPTVALUE_DISABLED;
diff --git a/src/include/access/reloptions.h b/src/include/access/reloptions.h
index a604a4702c..a436697658 100644
--- a/src/include/access/reloptions.h
+++ b/src/include/access/reloptions.h
@@ -29,6 +29,7 @@
typedef enum relopt_type
{
RELOPT_TYPE_BOOL,
+ RELOPT_TYPE_TERNARY, /* on, off, unset */
RELOPT_TYPE_INT,
RELOPT_TYPE_REAL,
RELOPT_TYPE_ENUM,
@@ -80,6 +81,7 @@ typedef struct relopt_value
union
{
bool bool_val;
+ ternary ternary_val;
int int_val;
double real_val;
int enum_val;
@@ -94,6 +96,12 @@ typedef struct relopt_bool
bool default_val;
} relopt_bool;
+typedef struct relopt_rernary
+{
+ relopt_gen gen;
+ int default_val;
+} relopt_ternary;
+
typedef struct relopt_int
{
relopt_gen gen;
@@ -152,19 +160,6 @@ typedef struct
const char *optname; /* option's name */
relopt_type opttype; /* option's datatype */
int offset; /* offset of field in result struct */
-
- /*
- * isset_offset is an optional offset of a field in the result struct that
- * stores whether the option is explicitly set for the relation or if it
- * just picked up the default value. In most cases, this can be
- * accomplished by giving the reloption a special out-of-range default
- * value (e.g., some integer reloptions use -2), but this isn't always
- * possible. For example, a Boolean reloption cannot be given an
- * out-of-range default, so we need another way to discover the source of
- * its value. This offset is only used if given a value greater than
- * zero.
- */
- int isset_offset;
} relopt_parse_elt;
/* Local reloption definition */
@@ -195,6 +190,8 @@ typedef struct local_relopts
extern relopt_kind add_reloption_kind(void);
extern void add_bool_reloption(bits32 kinds, const char *name, const char *desc,
bool default_val, LOCKMODE lockmode);
+extern void add_ternary_reloption(bits32 kinds, const char *name,
+ const char *desc, int default_val, LOCKMODE lockmode);
extern void add_int_reloption(bits32 kinds, const char *name, const char *desc,
int default_val, int min_val, int max_val,
LOCKMODE lockmode);
@@ -214,6 +211,9 @@ extern void register_reloptions_validator(local_relopts *relopts,
extern void add_local_bool_reloption(local_relopts *relopts, const char *name,
const char *desc, bool default_val,
int offset);
+extern void add_local_ternary_reloption(local_relopts *relopts,
+ const char *name, const char *desc,
+ ternary default_val, int offset);
extern void add_local_int_reloption(local_relopts *relopts, const char *name,
const char *desc, int default_val,
int min_val, int max_val, int offset);
diff --git a/src/include/c.h b/src/include/c.h
index 39022f8a9d..9c59ccccf8 100644
--- a/src/include/c.h
+++ b/src/include/c.h
@@ -488,6 +488,22 @@ typedef void (*pg_funcptr_t) (void);
#include <stdbool.h>
+/*
+ * ternary
+ * Boolean value with an extrea "unset" option
+ *
+ * Ternary data type is used in relation options that can be "true", "false" or
+ * "unset". Since relation options are used deep inside the PostgreSQL code,
+ * this type is declared globally.
+*/
+
+typedef enum ternary
+{
+ TERNARY_FALSE = 0,
+ TERNARY_TRUE = 1,
+ TERNARY_UNSET = -1
+} ternary;
+
/* ----------------------------------------------------------------
* Section 3: standard system types
diff --git a/src/include/utils/rel.h b/src/include/utils/rel.h
index b552359915..7346d618f9 100644
--- a/src/include/utils/rel.h
+++ b/src/include/utils/rel.h
@@ -346,8 +346,7 @@ typedef struct StdRdOptions
bool user_catalog_table; /* use as an additional catalog relation */
int parallel_workers; /* max number of parallel workers */
StdRdOptIndexCleanup vacuum_index_cleanup; /* controls index vacuuming */
- bool vacuum_truncate; /* enables vacuum to truncate a relation */
- bool vacuum_truncate_set; /* whether vacuum_truncate is set */
+ ternary vacuum_truncate; /* enables vacuum to truncate a relation */
/*
* Fraction of pages in a relation that vacuum can eagerly scan and fail
--
2.39.2
--nextPart4155600.3ZeAukHxDK
Content-Disposition: attachment;
filename="v2a-0003-Add-alias-to-be-used-as-unset-state.patch"
Content-Transfer-Encoding: 7Bit
Content-Type: text/x-patch; charset="unicode-2-0-utf-8";
name="v2a-0003-Add-alias-to-be-used-as-unset-state.patch"
^ permalink raw reply [nested|flat] 868+ messages in thread
* [PATCH v2 2/4] Introduce ternary reloptions
@ 2025-09-04 14:41 Nikolay Shaplov <[email protected]>
0 siblings, 0 replies; 868+ messages in thread
From: Nikolay Shaplov @ 2025-09-04 14:41 UTC (permalink / raw)
Introduce ternary reloption as a replacement for current `vacuum_truncate`
implementation. Remove `vacuum_truncate_set` additional flag and using
`TERNARY_UNSET` value instead.
---
src/backend/access/common/reloptions.c | 129 ++++++++++++++++++++-----
src/backend/commands/vacuum.c | 4 +-
src/include/access/reloptions.h | 26 ++---
src/include/c.h | 16 +++
src/include/utils/rel.h | 3 +-
5 files changed, 135 insertions(+), 43 deletions(-)
diff --git a/src/backend/access/common/reloptions.c b/src/backend/access/common/reloptions.c
index 0af3fea68f..f543f11001 100644
--- a/src/backend/access/common/reloptions.c
+++ b/src/backend/access/common/reloptions.c
@@ -40,9 +40,9 @@
*
* To add an option:
*
- * (i) decide on a type (bool, integer, real, enum, string), name, default
- * value, upper and lower bounds (if applicable); for strings, consider a
- * validation routine.
+ * (i) decide on a type (bool, ternary, integer, real, enum, string), name,
+ * default value, upper and lower bounds (if applicable); for strings,
+ * consider a validation routine.
* (ii) add a record below (or use add_<type>_reloption).
* (iii) add it to the appropriate options struct (perhaps StdRdOptions)
* (iv) add it to the appropriate handling routine (perhaps
@@ -147,15 +147,6 @@ static relopt_bool boolRelOpts[] =
},
false
},
- {
- {
- "vacuum_truncate",
- "Enables vacuum to truncate empty pages at the end of this table",
- RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
- ShareUpdateExclusiveLock
- },
- true
- },
{
{
"deduplicate_items",
@@ -170,6 +161,21 @@ static relopt_bool boolRelOpts[] =
{{NULL}}
};
+static relopt_ternary ternaryRelOpts[] =
+{
+ {
+ {
+ "vacuum_truncate",
+ "Enables vacuum to truncate empty pages at the end of this table",
+ RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
+ ShareUpdateExclusiveLock
+ },
+ TERNARY_UNSET
+ },
+ /* list terminator */
+ {{NULL}}
+};
+
static relopt_int intRelOpts[] =
{
{
@@ -600,6 +606,13 @@ initialize_reloptions(void)
boolRelOpts[i].gen.lockmode));
j++;
}
+ for (i = 0; ternaryRelOpts[i].gen.name; i++)
+ {
+ Assert(DoLockModesConflict(ternaryRelOpts[i].gen.lockmode,
+ ternaryRelOpts[i].gen.lockmode));
+ j++;
+ }
+
for (i = 0; intRelOpts[i].gen.name; i++)
{
Assert(DoLockModesConflict(intRelOpts[i].gen.lockmode,
@@ -640,6 +653,14 @@ initialize_reloptions(void)
j++;
}
+ for (i = 0; ternaryRelOpts[i].gen.name; i++)
+ {
+ relOpts[j] = &ternaryRelOpts[i].gen;
+ relOpts[j]->type = RELOPT_TYPE_TERNARY;
+ relOpts[j]->namelen = strlen(relOpts[j]->name);
+ j++;
+ }
+
for (i = 0; intRelOpts[i].gen.name; i++)
{
relOpts[j] = &intRelOpts[i].gen;
@@ -800,6 +821,9 @@ allocate_reloption(bits32 kinds, int type, const char *name, const char *desc,
case RELOPT_TYPE_BOOL:
size = sizeof(relopt_bool);
break;
+ case RELOPT_TYPE_TERNARY:
+ size = sizeof(relopt_ternary);
+ break;
case RELOPT_TYPE_INT:
size = sizeof(relopt_int);
break;
@@ -883,6 +907,54 @@ add_local_bool_reloption(local_relopts *relopts, const char *name,
add_local_reloption(relopts, (relopt_gen *) newoption, offset);
}
+/*
+ * init_ternary_reloption
+ * Allocate and initialize a new ternary reloption
+ */
+static relopt_ternary *
+init_ternary_reloption(bits32 kinds, const char *name, const char *desc,
+ ternary default_val, LOCKMODE lockmode)
+{
+ relopt_ternary *newoption;
+
+ newoption = (relopt_ternary *) allocate_reloption(kinds,
+ RELOPT_TYPE_TERNARY, name, desc, lockmode);
+ newoption->default_val = default_val;
+
+ return newoption;
+}
+
+/*
+ * add_ternary_reloption
+ * Add a new ternary reloption
+ */
+void
+add_ternary_reloption(bits32 kinds, const char *name, const char *desc,
+ ternary default_val, LOCKMODE lockmode)
+{
+ relopt_ternary *newoption = init_ternary_reloption(kinds, name, desc,
+ default_val, lockmode);
+
+ add_reloption((relopt_gen *) newoption);
+}
+
+/*
+ * add_local_ternary_reloption
+ * Add a new ternary local reloption
+ *
+ * 'offset' is offset of ternary-typed field.
+ */
+void
+add_local_ternary_reloption(local_relopts *relopts, const char *name,
+ const char *desc, ternary default_val,
+ int offset)
+{
+ relopt_ternary *newoption = init_ternary_reloption(RELOPT_KIND_LOCAL,
+ name, desc,
+ default_val, 0);
+
+ add_local_reloption(relopts, (relopt_gen *) newoption, offset);
+}
/*
* init_real_reloption
@@ -1617,6 +1689,18 @@ parse_one_reloption(relopt_value *option, char *text_str, int text_len,
option->gen->name, value)));
}
break;
+ case RELOPT_TYPE_TERNARY:
+ {
+ bool b;
+ parsed = parse_bool(value, &b);
+ option->values.ternary_val = b ? TERNARY_TRUE : TERNARY_FALSE;
+ if (validate && !parsed)
+ ereport(ERROR,
+ (errcode(ERRCODE_INVALID_PARAMETER_VALUE),
+ errmsg("invalid value for ternary option \"%s\": %s",
+ option->gen->name, value)));
+ }
+ break;
case RELOPT_TYPE_INT:
{
relopt_int *optint = (relopt_int *) option->gen;
@@ -1780,17 +1864,6 @@ fillRelOptions(void *rdopts, Size basesize,
char *itempos = ((char *) rdopts) + elems[j].offset;
char *string_val;
- /*
- * If isset_offset is provided, store whether the reloption is
- * set there.
- */
- if (elems[j].isset_offset > 0)
- {
- char *setpos = ((char *) rdopts) + elems[j].isset_offset;
-
- *(bool *) setpos = options[i].isset;
- }
-
switch (options[i].gen->type)
{
case RELOPT_TYPE_BOOL:
@@ -1798,6 +1871,11 @@ fillRelOptions(void *rdopts, Size basesize,
options[i].values.bool_val :
((relopt_bool *) options[i].gen)->default_val;
break;
+ case RELOPT_TYPE_TERNARY:
+ *(ternary *) itempos = options[i].isset ?
+ options[i].values.ternary_val :
+ ((relopt_ternary *) options[i].gen)->default_val;
+ break;
case RELOPT_TYPE_INT:
*(int *) itempos = options[i].isset ?
options[i].values.int_val :
@@ -1912,8 +1990,8 @@ default_reloptions(Datum reloptions, bool validate, relopt_kind kind)
offsetof(StdRdOptions, parallel_workers)},
{"vacuum_index_cleanup", RELOPT_TYPE_ENUM,
offsetof(StdRdOptions, vacuum_index_cleanup)},
- {"vacuum_truncate", RELOPT_TYPE_BOOL,
- offsetof(StdRdOptions, vacuum_truncate), offsetof(StdRdOptions, vacuum_truncate_set)},
+ {"vacuum_truncate", RELOPT_TYPE_TERNARY,
+ offsetof(StdRdOptions, vacuum_truncate)},
{"vacuum_max_eager_freeze_failure_rate", RELOPT_TYPE_REAL,
offsetof(StdRdOptions, vacuum_max_eager_freeze_failure_rate)}
};
@@ -1993,7 +2071,6 @@ build_local_reloptions(local_relopts *relopts, Datum options, bool validate)
elems[i].optname = opt->option->name;
elems[i].opttype = opt->option->type;
elems[i].offset = opt->offset;
- elems[i].isset_offset = 0; /* not supported for local relopts yet */
i++;
}
diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c
index 733ef40ae7..977babff54 100644
--- a/src/backend/commands/vacuum.c
+++ b/src/backend/commands/vacuum.c
@@ -2221,9 +2221,9 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams params,
{
StdRdOptions *opts = (StdRdOptions *) rel->rd_options;
- if (opts && opts->vacuum_truncate_set)
+ if (opts && opts->vacuum_truncate != TERNARY_UNSET)
{
- if (opts->vacuum_truncate)
+ if (opts->vacuum_truncate == TERNARY_TRUE)
params.truncate = VACOPTVALUE_ENABLED;
else
params.truncate = VACOPTVALUE_DISABLED;
diff --git a/src/include/access/reloptions.h b/src/include/access/reloptions.h
index a604a4702c..a436697658 100644
--- a/src/include/access/reloptions.h
+++ b/src/include/access/reloptions.h
@@ -29,6 +29,7 @@
typedef enum relopt_type
{
RELOPT_TYPE_BOOL,
+ RELOPT_TYPE_TERNARY, /* on, off, unset */
RELOPT_TYPE_INT,
RELOPT_TYPE_REAL,
RELOPT_TYPE_ENUM,
@@ -80,6 +81,7 @@ typedef struct relopt_value
union
{
bool bool_val;
+ ternary ternary_val;
int int_val;
double real_val;
int enum_val;
@@ -94,6 +96,12 @@ typedef struct relopt_bool
bool default_val;
} relopt_bool;
+typedef struct relopt_rernary
+{
+ relopt_gen gen;
+ int default_val;
+} relopt_ternary;
+
typedef struct relopt_int
{
relopt_gen gen;
@@ -152,19 +160,6 @@ typedef struct
const char *optname; /* option's name */
relopt_type opttype; /* option's datatype */
int offset; /* offset of field in result struct */
-
- /*
- * isset_offset is an optional offset of a field in the result struct that
- * stores whether the option is explicitly set for the relation or if it
- * just picked up the default value. In most cases, this can be
- * accomplished by giving the reloption a special out-of-range default
- * value (e.g., some integer reloptions use -2), but this isn't always
- * possible. For example, a Boolean reloption cannot be given an
- * out-of-range default, so we need another way to discover the source of
- * its value. This offset is only used if given a value greater than
- * zero.
- */
- int isset_offset;
} relopt_parse_elt;
/* Local reloption definition */
@@ -195,6 +190,8 @@ typedef struct local_relopts
extern relopt_kind add_reloption_kind(void);
extern void add_bool_reloption(bits32 kinds, const char *name, const char *desc,
bool default_val, LOCKMODE lockmode);
+extern void add_ternary_reloption(bits32 kinds, const char *name,
+ const char *desc, int default_val, LOCKMODE lockmode);
extern void add_int_reloption(bits32 kinds, const char *name, const char *desc,
int default_val, int min_val, int max_val,
LOCKMODE lockmode);
@@ -214,6 +211,9 @@ extern void register_reloptions_validator(local_relopts *relopts,
extern void add_local_bool_reloption(local_relopts *relopts, const char *name,
const char *desc, bool default_val,
int offset);
+extern void add_local_ternary_reloption(local_relopts *relopts,
+ const char *name, const char *desc,
+ ternary default_val, int offset);
extern void add_local_int_reloption(local_relopts *relopts, const char *name,
const char *desc, int default_val,
int min_val, int max_val, int offset);
diff --git a/src/include/c.h b/src/include/c.h
index 39022f8a9d..9c59ccccf8 100644
--- a/src/include/c.h
+++ b/src/include/c.h
@@ -488,6 +488,22 @@ typedef void (*pg_funcptr_t) (void);
#include <stdbool.h>
+/*
+ * ternary
+ * Boolean value with an extrea "unset" option
+ *
+ * Ternary data type is used in relation options that can be "true", "false" or
+ * "unset". Since relation options are used deep inside the PostgreSQL code,
+ * this type is declared globally.
+*/
+
+typedef enum ternary
+{
+ TERNARY_FALSE = 0,
+ TERNARY_TRUE = 1,
+ TERNARY_UNSET = -1
+} ternary;
+
/* ----------------------------------------------------------------
* Section 3: standard system types
diff --git a/src/include/utils/rel.h b/src/include/utils/rel.h
index b552359915..7346d618f9 100644
--- a/src/include/utils/rel.h
+++ b/src/include/utils/rel.h
@@ -346,8 +346,7 @@ typedef struct StdRdOptions
bool user_catalog_table; /* use as an additional catalog relation */
int parallel_workers; /* max number of parallel workers */
StdRdOptIndexCleanup vacuum_index_cleanup; /* controls index vacuuming */
- bool vacuum_truncate; /* enables vacuum to truncate a relation */
- bool vacuum_truncate_set; /* whether vacuum_truncate is set */
+ ternary vacuum_truncate; /* enables vacuum to truncate a relation */
/*
* Fraction of pages in a relation that vacuum can eagerly scan and fail
--
2.39.2
--nextPart5129932.5fSG56mABF
Content-Disposition: attachment;
filename="v2-0003-Add-alias-to-be-used-as-unset-state.patch"
Content-Transfer-Encoding: 7Bit
Content-Type: text/x-patch; charset="unicode-2-0-utf-8";
name="v2-0003-Add-alias-to-be-used-as-unset-state.patch"
^ permalink raw reply [nested|flat] 868+ messages in thread
* [PATCH v2a 2/4] Introduce ternary reloptions
@ 2025-09-04 14:41 Nikolay Shaplov <[email protected]>
0 siblings, 0 replies; 868+ messages in thread
From: Nikolay Shaplov @ 2025-09-04 14:41 UTC (permalink / raw)
Introduce ternary reloption as a replacement for current `vacuum_truncate`
implementation. Remove `vacuum_truncate_set` additional flag and using
`TERNARY_UNSET` value instead.
---
src/backend/access/common/reloptions.c | 129 ++++++++++++++++++++-----
src/backend/commands/vacuum.c | 4 +-
src/include/access/reloptions.h | 26 ++---
src/include/c.h | 16 +++
src/include/utils/rel.h | 3 +-
5 files changed, 135 insertions(+), 43 deletions(-)
diff --git a/src/backend/access/common/reloptions.c b/src/backend/access/common/reloptions.c
index 0af3fea68f..f543f11001 100644
--- a/src/backend/access/common/reloptions.c
+++ b/src/backend/access/common/reloptions.c
@@ -40,9 +40,9 @@
*
* To add an option:
*
- * (i) decide on a type (bool, integer, real, enum, string), name, default
- * value, upper and lower bounds (if applicable); for strings, consider a
- * validation routine.
+ * (i) decide on a type (bool, ternary, integer, real, enum, string), name,
+ * default value, upper and lower bounds (if applicable); for strings,
+ * consider a validation routine.
* (ii) add a record below (or use add_<type>_reloption).
* (iii) add it to the appropriate options struct (perhaps StdRdOptions)
* (iv) add it to the appropriate handling routine (perhaps
@@ -147,15 +147,6 @@ static relopt_bool boolRelOpts[] =
},
false
},
- {
- {
- "vacuum_truncate",
- "Enables vacuum to truncate empty pages at the end of this table",
- RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
- ShareUpdateExclusiveLock
- },
- true
- },
{
{
"deduplicate_items",
@@ -170,6 +161,21 @@ static relopt_bool boolRelOpts[] =
{{NULL}}
};
+static relopt_ternary ternaryRelOpts[] =
+{
+ {
+ {
+ "vacuum_truncate",
+ "Enables vacuum to truncate empty pages at the end of this table",
+ RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
+ ShareUpdateExclusiveLock
+ },
+ TERNARY_UNSET
+ },
+ /* list terminator */
+ {{NULL}}
+};
+
static relopt_int intRelOpts[] =
{
{
@@ -600,6 +606,13 @@ initialize_reloptions(void)
boolRelOpts[i].gen.lockmode));
j++;
}
+ for (i = 0; ternaryRelOpts[i].gen.name; i++)
+ {
+ Assert(DoLockModesConflict(ternaryRelOpts[i].gen.lockmode,
+ ternaryRelOpts[i].gen.lockmode));
+ j++;
+ }
+
for (i = 0; intRelOpts[i].gen.name; i++)
{
Assert(DoLockModesConflict(intRelOpts[i].gen.lockmode,
@@ -640,6 +653,14 @@ initialize_reloptions(void)
j++;
}
+ for (i = 0; ternaryRelOpts[i].gen.name; i++)
+ {
+ relOpts[j] = &ternaryRelOpts[i].gen;
+ relOpts[j]->type = RELOPT_TYPE_TERNARY;
+ relOpts[j]->namelen = strlen(relOpts[j]->name);
+ j++;
+ }
+
for (i = 0; intRelOpts[i].gen.name; i++)
{
relOpts[j] = &intRelOpts[i].gen;
@@ -800,6 +821,9 @@ allocate_reloption(bits32 kinds, int type, const char *name, const char *desc,
case RELOPT_TYPE_BOOL:
size = sizeof(relopt_bool);
break;
+ case RELOPT_TYPE_TERNARY:
+ size = sizeof(relopt_ternary);
+ break;
case RELOPT_TYPE_INT:
size = sizeof(relopt_int);
break;
@@ -883,6 +907,54 @@ add_local_bool_reloption(local_relopts *relopts, const char *name,
add_local_reloption(relopts, (relopt_gen *) newoption, offset);
}
+/*
+ * init_ternary_reloption
+ * Allocate and initialize a new ternary reloption
+ */
+static relopt_ternary *
+init_ternary_reloption(bits32 kinds, const char *name, const char *desc,
+ ternary default_val, LOCKMODE lockmode)
+{
+ relopt_ternary *newoption;
+
+ newoption = (relopt_ternary *) allocate_reloption(kinds,
+ RELOPT_TYPE_TERNARY, name, desc, lockmode);
+ newoption->default_val = default_val;
+
+ return newoption;
+}
+
+/*
+ * add_ternary_reloption
+ * Add a new ternary reloption
+ */
+void
+add_ternary_reloption(bits32 kinds, const char *name, const char *desc,
+ ternary default_val, LOCKMODE lockmode)
+{
+ relopt_ternary *newoption = init_ternary_reloption(kinds, name, desc,
+ default_val, lockmode);
+
+ add_reloption((relopt_gen *) newoption);
+}
+
+/*
+ * add_local_ternary_reloption
+ * Add a new ternary local reloption
+ *
+ * 'offset' is offset of ternary-typed field.
+ */
+void
+add_local_ternary_reloption(local_relopts *relopts, const char *name,
+ const char *desc, ternary default_val,
+ int offset)
+{
+ relopt_ternary *newoption = init_ternary_reloption(RELOPT_KIND_LOCAL,
+ name, desc,
+ default_val, 0);
+
+ add_local_reloption(relopts, (relopt_gen *) newoption, offset);
+}
/*
* init_real_reloption
@@ -1617,6 +1689,18 @@ parse_one_reloption(relopt_value *option, char *text_str, int text_len,
option->gen->name, value)));
}
break;
+ case RELOPT_TYPE_TERNARY:
+ {
+ bool b;
+ parsed = parse_bool(value, &b);
+ option->values.ternary_val = b ? TERNARY_TRUE : TERNARY_FALSE;
+ if (validate && !parsed)
+ ereport(ERROR,
+ (errcode(ERRCODE_INVALID_PARAMETER_VALUE),
+ errmsg("invalid value for ternary option \"%s\": %s",
+ option->gen->name, value)));
+ }
+ break;
case RELOPT_TYPE_INT:
{
relopt_int *optint = (relopt_int *) option->gen;
@@ -1780,17 +1864,6 @@ fillRelOptions(void *rdopts, Size basesize,
char *itempos = ((char *) rdopts) + elems[j].offset;
char *string_val;
- /*
- * If isset_offset is provided, store whether the reloption is
- * set there.
- */
- if (elems[j].isset_offset > 0)
- {
- char *setpos = ((char *) rdopts) + elems[j].isset_offset;
-
- *(bool *) setpos = options[i].isset;
- }
-
switch (options[i].gen->type)
{
case RELOPT_TYPE_BOOL:
@@ -1798,6 +1871,11 @@ fillRelOptions(void *rdopts, Size basesize,
options[i].values.bool_val :
((relopt_bool *) options[i].gen)->default_val;
break;
+ case RELOPT_TYPE_TERNARY:
+ *(ternary *) itempos = options[i].isset ?
+ options[i].values.ternary_val :
+ ((relopt_ternary *) options[i].gen)->default_val;
+ break;
case RELOPT_TYPE_INT:
*(int *) itempos = options[i].isset ?
options[i].values.int_val :
@@ -1912,8 +1990,8 @@ default_reloptions(Datum reloptions, bool validate, relopt_kind kind)
offsetof(StdRdOptions, parallel_workers)},
{"vacuum_index_cleanup", RELOPT_TYPE_ENUM,
offsetof(StdRdOptions, vacuum_index_cleanup)},
- {"vacuum_truncate", RELOPT_TYPE_BOOL,
- offsetof(StdRdOptions, vacuum_truncate), offsetof(StdRdOptions, vacuum_truncate_set)},
+ {"vacuum_truncate", RELOPT_TYPE_TERNARY,
+ offsetof(StdRdOptions, vacuum_truncate)},
{"vacuum_max_eager_freeze_failure_rate", RELOPT_TYPE_REAL,
offsetof(StdRdOptions, vacuum_max_eager_freeze_failure_rate)}
};
@@ -1993,7 +2071,6 @@ build_local_reloptions(local_relopts *relopts, Datum options, bool validate)
elems[i].optname = opt->option->name;
elems[i].opttype = opt->option->type;
elems[i].offset = opt->offset;
- elems[i].isset_offset = 0; /* not supported for local relopts yet */
i++;
}
diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c
index 733ef40ae7..977babff54 100644
--- a/src/backend/commands/vacuum.c
+++ b/src/backend/commands/vacuum.c
@@ -2221,9 +2221,9 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams params,
{
StdRdOptions *opts = (StdRdOptions *) rel->rd_options;
- if (opts && opts->vacuum_truncate_set)
+ if (opts && opts->vacuum_truncate != TERNARY_UNSET)
{
- if (opts->vacuum_truncate)
+ if (opts->vacuum_truncate == TERNARY_TRUE)
params.truncate = VACOPTVALUE_ENABLED;
else
params.truncate = VACOPTVALUE_DISABLED;
diff --git a/src/include/access/reloptions.h b/src/include/access/reloptions.h
index a604a4702c..a436697658 100644
--- a/src/include/access/reloptions.h
+++ b/src/include/access/reloptions.h
@@ -29,6 +29,7 @@
typedef enum relopt_type
{
RELOPT_TYPE_BOOL,
+ RELOPT_TYPE_TERNARY, /* on, off, unset */
RELOPT_TYPE_INT,
RELOPT_TYPE_REAL,
RELOPT_TYPE_ENUM,
@@ -80,6 +81,7 @@ typedef struct relopt_value
union
{
bool bool_val;
+ ternary ternary_val;
int int_val;
double real_val;
int enum_val;
@@ -94,6 +96,12 @@ typedef struct relopt_bool
bool default_val;
} relopt_bool;
+typedef struct relopt_rernary
+{
+ relopt_gen gen;
+ int default_val;
+} relopt_ternary;
+
typedef struct relopt_int
{
relopt_gen gen;
@@ -152,19 +160,6 @@ typedef struct
const char *optname; /* option's name */
relopt_type opttype; /* option's datatype */
int offset; /* offset of field in result struct */
-
- /*
- * isset_offset is an optional offset of a field in the result struct that
- * stores whether the option is explicitly set for the relation or if it
- * just picked up the default value. In most cases, this can be
- * accomplished by giving the reloption a special out-of-range default
- * value (e.g., some integer reloptions use -2), but this isn't always
- * possible. For example, a Boolean reloption cannot be given an
- * out-of-range default, so we need another way to discover the source of
- * its value. This offset is only used if given a value greater than
- * zero.
- */
- int isset_offset;
} relopt_parse_elt;
/* Local reloption definition */
@@ -195,6 +190,8 @@ typedef struct local_relopts
extern relopt_kind add_reloption_kind(void);
extern void add_bool_reloption(bits32 kinds, const char *name, const char *desc,
bool default_val, LOCKMODE lockmode);
+extern void add_ternary_reloption(bits32 kinds, const char *name,
+ const char *desc, int default_val, LOCKMODE lockmode);
extern void add_int_reloption(bits32 kinds, const char *name, const char *desc,
int default_val, int min_val, int max_val,
LOCKMODE lockmode);
@@ -214,6 +211,9 @@ extern void register_reloptions_validator(local_relopts *relopts,
extern void add_local_bool_reloption(local_relopts *relopts, const char *name,
const char *desc, bool default_val,
int offset);
+extern void add_local_ternary_reloption(local_relopts *relopts,
+ const char *name, const char *desc,
+ ternary default_val, int offset);
extern void add_local_int_reloption(local_relopts *relopts, const char *name,
const char *desc, int default_val,
int min_val, int max_val, int offset);
diff --git a/src/include/c.h b/src/include/c.h
index 39022f8a9d..9c59ccccf8 100644
--- a/src/include/c.h
+++ b/src/include/c.h
@@ -488,6 +488,22 @@ typedef void (*pg_funcptr_t) (void);
#include <stdbool.h>
+/*
+ * ternary
+ * Boolean value with an extrea "unset" option
+ *
+ * Ternary data type is used in relation options that can be "true", "false" or
+ * "unset". Since relation options are used deep inside the PostgreSQL code,
+ * this type is declared globally.
+*/
+
+typedef enum ternary
+{
+ TERNARY_FALSE = 0,
+ TERNARY_TRUE = 1,
+ TERNARY_UNSET = -1
+} ternary;
+
/* ----------------------------------------------------------------
* Section 3: standard system types
diff --git a/src/include/utils/rel.h b/src/include/utils/rel.h
index b552359915..7346d618f9 100644
--- a/src/include/utils/rel.h
+++ b/src/include/utils/rel.h
@@ -346,8 +346,7 @@ typedef struct StdRdOptions
bool user_catalog_table; /* use as an additional catalog relation */
int parallel_workers; /* max number of parallel workers */
StdRdOptIndexCleanup vacuum_index_cleanup; /* controls index vacuuming */
- bool vacuum_truncate; /* enables vacuum to truncate a relation */
- bool vacuum_truncate_set; /* whether vacuum_truncate is set */
+ ternary vacuum_truncate; /* enables vacuum to truncate a relation */
/*
* Fraction of pages in a relation that vacuum can eagerly scan and fail
--
2.39.2
--nextPart4155600.3ZeAukHxDK
Content-Disposition: attachment;
filename="v2a-0003-Add-alias-to-be-used-as-unset-state.patch"
Content-Transfer-Encoding: 7Bit
Content-Type: text/x-patch; charset="unicode-2-0-utf-8";
name="v2a-0003-Add-alias-to-be-used-as-unset-state.patch"
^ permalink raw reply [nested|flat] 868+ messages in thread
* [PATCH v2a 2/4] Introduce ternary reloptions
@ 2025-09-04 14:41 Nikolay Shaplov <[email protected]>
0 siblings, 0 replies; 868+ messages in thread
From: Nikolay Shaplov @ 2025-09-04 14:41 UTC (permalink / raw)
Introduce ternary reloption as a replacement for current `vacuum_truncate`
implementation. Remove `vacuum_truncate_set` additional flag and using
`TERNARY_UNSET` value instead.
---
src/backend/access/common/reloptions.c | 129 ++++++++++++++++++++-----
src/backend/commands/vacuum.c | 4 +-
src/include/access/reloptions.h | 26 ++---
src/include/c.h | 16 +++
src/include/utils/rel.h | 3 +-
5 files changed, 135 insertions(+), 43 deletions(-)
diff --git a/src/backend/access/common/reloptions.c b/src/backend/access/common/reloptions.c
index 0af3fea68f..f543f11001 100644
--- a/src/backend/access/common/reloptions.c
+++ b/src/backend/access/common/reloptions.c
@@ -40,9 +40,9 @@
*
* To add an option:
*
- * (i) decide on a type (bool, integer, real, enum, string), name, default
- * value, upper and lower bounds (if applicable); for strings, consider a
- * validation routine.
+ * (i) decide on a type (bool, ternary, integer, real, enum, string), name,
+ * default value, upper and lower bounds (if applicable); for strings,
+ * consider a validation routine.
* (ii) add a record below (or use add_<type>_reloption).
* (iii) add it to the appropriate options struct (perhaps StdRdOptions)
* (iv) add it to the appropriate handling routine (perhaps
@@ -147,15 +147,6 @@ static relopt_bool boolRelOpts[] =
},
false
},
- {
- {
- "vacuum_truncate",
- "Enables vacuum to truncate empty pages at the end of this table",
- RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
- ShareUpdateExclusiveLock
- },
- true
- },
{
{
"deduplicate_items",
@@ -170,6 +161,21 @@ static relopt_bool boolRelOpts[] =
{{NULL}}
};
+static relopt_ternary ternaryRelOpts[] =
+{
+ {
+ {
+ "vacuum_truncate",
+ "Enables vacuum to truncate empty pages at the end of this table",
+ RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
+ ShareUpdateExclusiveLock
+ },
+ TERNARY_UNSET
+ },
+ /* list terminator */
+ {{NULL}}
+};
+
static relopt_int intRelOpts[] =
{
{
@@ -600,6 +606,13 @@ initialize_reloptions(void)
boolRelOpts[i].gen.lockmode));
j++;
}
+ for (i = 0; ternaryRelOpts[i].gen.name; i++)
+ {
+ Assert(DoLockModesConflict(ternaryRelOpts[i].gen.lockmode,
+ ternaryRelOpts[i].gen.lockmode));
+ j++;
+ }
+
for (i = 0; intRelOpts[i].gen.name; i++)
{
Assert(DoLockModesConflict(intRelOpts[i].gen.lockmode,
@@ -640,6 +653,14 @@ initialize_reloptions(void)
j++;
}
+ for (i = 0; ternaryRelOpts[i].gen.name; i++)
+ {
+ relOpts[j] = &ternaryRelOpts[i].gen;
+ relOpts[j]->type = RELOPT_TYPE_TERNARY;
+ relOpts[j]->namelen = strlen(relOpts[j]->name);
+ j++;
+ }
+
for (i = 0; intRelOpts[i].gen.name; i++)
{
relOpts[j] = &intRelOpts[i].gen;
@@ -800,6 +821,9 @@ allocate_reloption(bits32 kinds, int type, const char *name, const char *desc,
case RELOPT_TYPE_BOOL:
size = sizeof(relopt_bool);
break;
+ case RELOPT_TYPE_TERNARY:
+ size = sizeof(relopt_ternary);
+ break;
case RELOPT_TYPE_INT:
size = sizeof(relopt_int);
break;
@@ -883,6 +907,54 @@ add_local_bool_reloption(local_relopts *relopts, const char *name,
add_local_reloption(relopts, (relopt_gen *) newoption, offset);
}
+/*
+ * init_ternary_reloption
+ * Allocate and initialize a new ternary reloption
+ */
+static relopt_ternary *
+init_ternary_reloption(bits32 kinds, const char *name, const char *desc,
+ ternary default_val, LOCKMODE lockmode)
+{
+ relopt_ternary *newoption;
+
+ newoption = (relopt_ternary *) allocate_reloption(kinds,
+ RELOPT_TYPE_TERNARY, name, desc, lockmode);
+ newoption->default_val = default_val;
+
+ return newoption;
+}
+
+/*
+ * add_ternary_reloption
+ * Add a new ternary reloption
+ */
+void
+add_ternary_reloption(bits32 kinds, const char *name, const char *desc,
+ ternary default_val, LOCKMODE lockmode)
+{
+ relopt_ternary *newoption = init_ternary_reloption(kinds, name, desc,
+ default_val, lockmode);
+
+ add_reloption((relopt_gen *) newoption);
+}
+
+/*
+ * add_local_ternary_reloption
+ * Add a new ternary local reloption
+ *
+ * 'offset' is offset of ternary-typed field.
+ */
+void
+add_local_ternary_reloption(local_relopts *relopts, const char *name,
+ const char *desc, ternary default_val,
+ int offset)
+{
+ relopt_ternary *newoption = init_ternary_reloption(RELOPT_KIND_LOCAL,
+ name, desc,
+ default_val, 0);
+
+ add_local_reloption(relopts, (relopt_gen *) newoption, offset);
+}
/*
* init_real_reloption
@@ -1617,6 +1689,18 @@ parse_one_reloption(relopt_value *option, char *text_str, int text_len,
option->gen->name, value)));
}
break;
+ case RELOPT_TYPE_TERNARY:
+ {
+ bool b;
+ parsed = parse_bool(value, &b);
+ option->values.ternary_val = b ? TERNARY_TRUE : TERNARY_FALSE;
+ if (validate && !parsed)
+ ereport(ERROR,
+ (errcode(ERRCODE_INVALID_PARAMETER_VALUE),
+ errmsg("invalid value for ternary option \"%s\": %s",
+ option->gen->name, value)));
+ }
+ break;
case RELOPT_TYPE_INT:
{
relopt_int *optint = (relopt_int *) option->gen;
@@ -1780,17 +1864,6 @@ fillRelOptions(void *rdopts, Size basesize,
char *itempos = ((char *) rdopts) + elems[j].offset;
char *string_val;
- /*
- * If isset_offset is provided, store whether the reloption is
- * set there.
- */
- if (elems[j].isset_offset > 0)
- {
- char *setpos = ((char *) rdopts) + elems[j].isset_offset;
-
- *(bool *) setpos = options[i].isset;
- }
-
switch (options[i].gen->type)
{
case RELOPT_TYPE_BOOL:
@@ -1798,6 +1871,11 @@ fillRelOptions(void *rdopts, Size basesize,
options[i].values.bool_val :
((relopt_bool *) options[i].gen)->default_val;
break;
+ case RELOPT_TYPE_TERNARY:
+ *(ternary *) itempos = options[i].isset ?
+ options[i].values.ternary_val :
+ ((relopt_ternary *) options[i].gen)->default_val;
+ break;
case RELOPT_TYPE_INT:
*(int *) itempos = options[i].isset ?
options[i].values.int_val :
@@ -1912,8 +1990,8 @@ default_reloptions(Datum reloptions, bool validate, relopt_kind kind)
offsetof(StdRdOptions, parallel_workers)},
{"vacuum_index_cleanup", RELOPT_TYPE_ENUM,
offsetof(StdRdOptions, vacuum_index_cleanup)},
- {"vacuum_truncate", RELOPT_TYPE_BOOL,
- offsetof(StdRdOptions, vacuum_truncate), offsetof(StdRdOptions, vacuum_truncate_set)},
+ {"vacuum_truncate", RELOPT_TYPE_TERNARY,
+ offsetof(StdRdOptions, vacuum_truncate)},
{"vacuum_max_eager_freeze_failure_rate", RELOPT_TYPE_REAL,
offsetof(StdRdOptions, vacuum_max_eager_freeze_failure_rate)}
};
@@ -1993,7 +2071,6 @@ build_local_reloptions(local_relopts *relopts, Datum options, bool validate)
elems[i].optname = opt->option->name;
elems[i].opttype = opt->option->type;
elems[i].offset = opt->offset;
- elems[i].isset_offset = 0; /* not supported for local relopts yet */
i++;
}
diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c
index 733ef40ae7..977babff54 100644
--- a/src/backend/commands/vacuum.c
+++ b/src/backend/commands/vacuum.c
@@ -2221,9 +2221,9 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams params,
{
StdRdOptions *opts = (StdRdOptions *) rel->rd_options;
- if (opts && opts->vacuum_truncate_set)
+ if (opts && opts->vacuum_truncate != TERNARY_UNSET)
{
- if (opts->vacuum_truncate)
+ if (opts->vacuum_truncate == TERNARY_TRUE)
params.truncate = VACOPTVALUE_ENABLED;
else
params.truncate = VACOPTVALUE_DISABLED;
diff --git a/src/include/access/reloptions.h b/src/include/access/reloptions.h
index a604a4702c..a436697658 100644
--- a/src/include/access/reloptions.h
+++ b/src/include/access/reloptions.h
@@ -29,6 +29,7 @@
typedef enum relopt_type
{
RELOPT_TYPE_BOOL,
+ RELOPT_TYPE_TERNARY, /* on, off, unset */
RELOPT_TYPE_INT,
RELOPT_TYPE_REAL,
RELOPT_TYPE_ENUM,
@@ -80,6 +81,7 @@ typedef struct relopt_value
union
{
bool bool_val;
+ ternary ternary_val;
int int_val;
double real_val;
int enum_val;
@@ -94,6 +96,12 @@ typedef struct relopt_bool
bool default_val;
} relopt_bool;
+typedef struct relopt_rernary
+{
+ relopt_gen gen;
+ int default_val;
+} relopt_ternary;
+
typedef struct relopt_int
{
relopt_gen gen;
@@ -152,19 +160,6 @@ typedef struct
const char *optname; /* option's name */
relopt_type opttype; /* option's datatype */
int offset; /* offset of field in result struct */
-
- /*
- * isset_offset is an optional offset of a field in the result struct that
- * stores whether the option is explicitly set for the relation or if it
- * just picked up the default value. In most cases, this can be
- * accomplished by giving the reloption a special out-of-range default
- * value (e.g., some integer reloptions use -2), but this isn't always
- * possible. For example, a Boolean reloption cannot be given an
- * out-of-range default, so we need another way to discover the source of
- * its value. This offset is only used if given a value greater than
- * zero.
- */
- int isset_offset;
} relopt_parse_elt;
/* Local reloption definition */
@@ -195,6 +190,8 @@ typedef struct local_relopts
extern relopt_kind add_reloption_kind(void);
extern void add_bool_reloption(bits32 kinds, const char *name, const char *desc,
bool default_val, LOCKMODE lockmode);
+extern void add_ternary_reloption(bits32 kinds, const char *name,
+ const char *desc, int default_val, LOCKMODE lockmode);
extern void add_int_reloption(bits32 kinds, const char *name, const char *desc,
int default_val, int min_val, int max_val,
LOCKMODE lockmode);
@@ -214,6 +211,9 @@ extern void register_reloptions_validator(local_relopts *relopts,
extern void add_local_bool_reloption(local_relopts *relopts, const char *name,
const char *desc, bool default_val,
int offset);
+extern void add_local_ternary_reloption(local_relopts *relopts,
+ const char *name, const char *desc,
+ ternary default_val, int offset);
extern void add_local_int_reloption(local_relopts *relopts, const char *name,
const char *desc, int default_val,
int min_val, int max_val, int offset);
diff --git a/src/include/c.h b/src/include/c.h
index 39022f8a9d..9c59ccccf8 100644
--- a/src/include/c.h
+++ b/src/include/c.h
@@ -488,6 +488,22 @@ typedef void (*pg_funcptr_t) (void);
#include <stdbool.h>
+/*
+ * ternary
+ * Boolean value with an extrea "unset" option
+ *
+ * Ternary data type is used in relation options that can be "true", "false" or
+ * "unset". Since relation options are used deep inside the PostgreSQL code,
+ * this type is declared globally.
+*/
+
+typedef enum ternary
+{
+ TERNARY_FALSE = 0,
+ TERNARY_TRUE = 1,
+ TERNARY_UNSET = -1
+} ternary;
+
/* ----------------------------------------------------------------
* Section 3: standard system types
diff --git a/src/include/utils/rel.h b/src/include/utils/rel.h
index b552359915..7346d618f9 100644
--- a/src/include/utils/rel.h
+++ b/src/include/utils/rel.h
@@ -346,8 +346,7 @@ typedef struct StdRdOptions
bool user_catalog_table; /* use as an additional catalog relation */
int parallel_workers; /* max number of parallel workers */
StdRdOptIndexCleanup vacuum_index_cleanup; /* controls index vacuuming */
- bool vacuum_truncate; /* enables vacuum to truncate a relation */
- bool vacuum_truncate_set; /* whether vacuum_truncate is set */
+ ternary vacuum_truncate; /* enables vacuum to truncate a relation */
/*
* Fraction of pages in a relation that vacuum can eagerly scan and fail
--
2.39.2
--nextPart4155600.3ZeAukHxDK
Content-Disposition: attachment;
filename="v2a-0003-Add-alias-to-be-used-as-unset-state.patch"
Content-Transfer-Encoding: 7Bit
Content-Type: text/x-patch; charset="unicode-2-0-utf-8";
name="v2a-0003-Add-alias-to-be-used-as-unset-state.patch"
^ permalink raw reply [nested|flat] 868+ messages in thread
* [PATCH v2 2/4] Introduce ternary reloptions
@ 2025-09-04 14:41 Nikolay Shaplov <[email protected]>
0 siblings, 0 replies; 868+ messages in thread
From: Nikolay Shaplov @ 2025-09-04 14:41 UTC (permalink / raw)
Introduce ternary reloption as a replacement for current `vacuum_truncate`
implementation. Remove `vacuum_truncate_set` additional flag and using
`TERNARY_UNSET` value instead.
---
src/backend/access/common/reloptions.c | 129 ++++++++++++++++++++-----
src/backend/commands/vacuum.c | 4 +-
src/include/access/reloptions.h | 26 ++---
src/include/c.h | 16 +++
src/include/utils/rel.h | 3 +-
5 files changed, 135 insertions(+), 43 deletions(-)
diff --git a/src/backend/access/common/reloptions.c b/src/backend/access/common/reloptions.c
index 0af3fea68f..f543f11001 100644
--- a/src/backend/access/common/reloptions.c
+++ b/src/backend/access/common/reloptions.c
@@ -40,9 +40,9 @@
*
* To add an option:
*
- * (i) decide on a type (bool, integer, real, enum, string), name, default
- * value, upper and lower bounds (if applicable); for strings, consider a
- * validation routine.
+ * (i) decide on a type (bool, ternary, integer, real, enum, string), name,
+ * default value, upper and lower bounds (if applicable); for strings,
+ * consider a validation routine.
* (ii) add a record below (or use add_<type>_reloption).
* (iii) add it to the appropriate options struct (perhaps StdRdOptions)
* (iv) add it to the appropriate handling routine (perhaps
@@ -147,15 +147,6 @@ static relopt_bool boolRelOpts[] =
},
false
},
- {
- {
- "vacuum_truncate",
- "Enables vacuum to truncate empty pages at the end of this table",
- RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
- ShareUpdateExclusiveLock
- },
- true
- },
{
{
"deduplicate_items",
@@ -170,6 +161,21 @@ static relopt_bool boolRelOpts[] =
{{NULL}}
};
+static relopt_ternary ternaryRelOpts[] =
+{
+ {
+ {
+ "vacuum_truncate",
+ "Enables vacuum to truncate empty pages at the end of this table",
+ RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
+ ShareUpdateExclusiveLock
+ },
+ TERNARY_UNSET
+ },
+ /* list terminator */
+ {{NULL}}
+};
+
static relopt_int intRelOpts[] =
{
{
@@ -600,6 +606,13 @@ initialize_reloptions(void)
boolRelOpts[i].gen.lockmode));
j++;
}
+ for (i = 0; ternaryRelOpts[i].gen.name; i++)
+ {
+ Assert(DoLockModesConflict(ternaryRelOpts[i].gen.lockmode,
+ ternaryRelOpts[i].gen.lockmode));
+ j++;
+ }
+
for (i = 0; intRelOpts[i].gen.name; i++)
{
Assert(DoLockModesConflict(intRelOpts[i].gen.lockmode,
@@ -640,6 +653,14 @@ initialize_reloptions(void)
j++;
}
+ for (i = 0; ternaryRelOpts[i].gen.name; i++)
+ {
+ relOpts[j] = &ternaryRelOpts[i].gen;
+ relOpts[j]->type = RELOPT_TYPE_TERNARY;
+ relOpts[j]->namelen = strlen(relOpts[j]->name);
+ j++;
+ }
+
for (i = 0; intRelOpts[i].gen.name; i++)
{
relOpts[j] = &intRelOpts[i].gen;
@@ -800,6 +821,9 @@ allocate_reloption(bits32 kinds, int type, const char *name, const char *desc,
case RELOPT_TYPE_BOOL:
size = sizeof(relopt_bool);
break;
+ case RELOPT_TYPE_TERNARY:
+ size = sizeof(relopt_ternary);
+ break;
case RELOPT_TYPE_INT:
size = sizeof(relopt_int);
break;
@@ -883,6 +907,54 @@ add_local_bool_reloption(local_relopts *relopts, const char *name,
add_local_reloption(relopts, (relopt_gen *) newoption, offset);
}
+/*
+ * init_ternary_reloption
+ * Allocate and initialize a new ternary reloption
+ */
+static relopt_ternary *
+init_ternary_reloption(bits32 kinds, const char *name, const char *desc,
+ ternary default_val, LOCKMODE lockmode)
+{
+ relopt_ternary *newoption;
+
+ newoption = (relopt_ternary *) allocate_reloption(kinds,
+ RELOPT_TYPE_TERNARY, name, desc, lockmode);
+ newoption->default_val = default_val;
+
+ return newoption;
+}
+
+/*
+ * add_ternary_reloption
+ * Add a new ternary reloption
+ */
+void
+add_ternary_reloption(bits32 kinds, const char *name, const char *desc,
+ ternary default_val, LOCKMODE lockmode)
+{
+ relopt_ternary *newoption = init_ternary_reloption(kinds, name, desc,
+ default_val, lockmode);
+
+ add_reloption((relopt_gen *) newoption);
+}
+
+/*
+ * add_local_ternary_reloption
+ * Add a new ternary local reloption
+ *
+ * 'offset' is offset of ternary-typed field.
+ */
+void
+add_local_ternary_reloption(local_relopts *relopts, const char *name,
+ const char *desc, ternary default_val,
+ int offset)
+{
+ relopt_ternary *newoption = init_ternary_reloption(RELOPT_KIND_LOCAL,
+ name, desc,
+ default_val, 0);
+
+ add_local_reloption(relopts, (relopt_gen *) newoption, offset);
+}
/*
* init_real_reloption
@@ -1617,6 +1689,18 @@ parse_one_reloption(relopt_value *option, char *text_str, int text_len,
option->gen->name, value)));
}
break;
+ case RELOPT_TYPE_TERNARY:
+ {
+ bool b;
+ parsed = parse_bool(value, &b);
+ option->values.ternary_val = b ? TERNARY_TRUE : TERNARY_FALSE;
+ if (validate && !parsed)
+ ereport(ERROR,
+ (errcode(ERRCODE_INVALID_PARAMETER_VALUE),
+ errmsg("invalid value for ternary option \"%s\": %s",
+ option->gen->name, value)));
+ }
+ break;
case RELOPT_TYPE_INT:
{
relopt_int *optint = (relopt_int *) option->gen;
@@ -1780,17 +1864,6 @@ fillRelOptions(void *rdopts, Size basesize,
char *itempos = ((char *) rdopts) + elems[j].offset;
char *string_val;
- /*
- * If isset_offset is provided, store whether the reloption is
- * set there.
- */
- if (elems[j].isset_offset > 0)
- {
- char *setpos = ((char *) rdopts) + elems[j].isset_offset;
-
- *(bool *) setpos = options[i].isset;
- }
-
switch (options[i].gen->type)
{
case RELOPT_TYPE_BOOL:
@@ -1798,6 +1871,11 @@ fillRelOptions(void *rdopts, Size basesize,
options[i].values.bool_val :
((relopt_bool *) options[i].gen)->default_val;
break;
+ case RELOPT_TYPE_TERNARY:
+ *(ternary *) itempos = options[i].isset ?
+ options[i].values.ternary_val :
+ ((relopt_ternary *) options[i].gen)->default_val;
+ break;
case RELOPT_TYPE_INT:
*(int *) itempos = options[i].isset ?
options[i].values.int_val :
@@ -1912,8 +1990,8 @@ default_reloptions(Datum reloptions, bool validate, relopt_kind kind)
offsetof(StdRdOptions, parallel_workers)},
{"vacuum_index_cleanup", RELOPT_TYPE_ENUM,
offsetof(StdRdOptions, vacuum_index_cleanup)},
- {"vacuum_truncate", RELOPT_TYPE_BOOL,
- offsetof(StdRdOptions, vacuum_truncate), offsetof(StdRdOptions, vacuum_truncate_set)},
+ {"vacuum_truncate", RELOPT_TYPE_TERNARY,
+ offsetof(StdRdOptions, vacuum_truncate)},
{"vacuum_max_eager_freeze_failure_rate", RELOPT_TYPE_REAL,
offsetof(StdRdOptions, vacuum_max_eager_freeze_failure_rate)}
};
@@ -1993,7 +2071,6 @@ build_local_reloptions(local_relopts *relopts, Datum options, bool validate)
elems[i].optname = opt->option->name;
elems[i].opttype = opt->option->type;
elems[i].offset = opt->offset;
- elems[i].isset_offset = 0; /* not supported for local relopts yet */
i++;
}
diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c
index 733ef40ae7..977babff54 100644
--- a/src/backend/commands/vacuum.c
+++ b/src/backend/commands/vacuum.c
@@ -2221,9 +2221,9 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams params,
{
StdRdOptions *opts = (StdRdOptions *) rel->rd_options;
- if (opts && opts->vacuum_truncate_set)
+ if (opts && opts->vacuum_truncate != TERNARY_UNSET)
{
- if (opts->vacuum_truncate)
+ if (opts->vacuum_truncate == TERNARY_TRUE)
params.truncate = VACOPTVALUE_ENABLED;
else
params.truncate = VACOPTVALUE_DISABLED;
diff --git a/src/include/access/reloptions.h b/src/include/access/reloptions.h
index a604a4702c..a436697658 100644
--- a/src/include/access/reloptions.h
+++ b/src/include/access/reloptions.h
@@ -29,6 +29,7 @@
typedef enum relopt_type
{
RELOPT_TYPE_BOOL,
+ RELOPT_TYPE_TERNARY, /* on, off, unset */
RELOPT_TYPE_INT,
RELOPT_TYPE_REAL,
RELOPT_TYPE_ENUM,
@@ -80,6 +81,7 @@ typedef struct relopt_value
union
{
bool bool_val;
+ ternary ternary_val;
int int_val;
double real_val;
int enum_val;
@@ -94,6 +96,12 @@ typedef struct relopt_bool
bool default_val;
} relopt_bool;
+typedef struct relopt_rernary
+{
+ relopt_gen gen;
+ int default_val;
+} relopt_ternary;
+
typedef struct relopt_int
{
relopt_gen gen;
@@ -152,19 +160,6 @@ typedef struct
const char *optname; /* option's name */
relopt_type opttype; /* option's datatype */
int offset; /* offset of field in result struct */
-
- /*
- * isset_offset is an optional offset of a field in the result struct that
- * stores whether the option is explicitly set for the relation or if it
- * just picked up the default value. In most cases, this can be
- * accomplished by giving the reloption a special out-of-range default
- * value (e.g., some integer reloptions use -2), but this isn't always
- * possible. For example, a Boolean reloption cannot be given an
- * out-of-range default, so we need another way to discover the source of
- * its value. This offset is only used if given a value greater than
- * zero.
- */
- int isset_offset;
} relopt_parse_elt;
/* Local reloption definition */
@@ -195,6 +190,8 @@ typedef struct local_relopts
extern relopt_kind add_reloption_kind(void);
extern void add_bool_reloption(bits32 kinds, const char *name, const char *desc,
bool default_val, LOCKMODE lockmode);
+extern void add_ternary_reloption(bits32 kinds, const char *name,
+ const char *desc, int default_val, LOCKMODE lockmode);
extern void add_int_reloption(bits32 kinds, const char *name, const char *desc,
int default_val, int min_val, int max_val,
LOCKMODE lockmode);
@@ -214,6 +211,9 @@ extern void register_reloptions_validator(local_relopts *relopts,
extern void add_local_bool_reloption(local_relopts *relopts, const char *name,
const char *desc, bool default_val,
int offset);
+extern void add_local_ternary_reloption(local_relopts *relopts,
+ const char *name, const char *desc,
+ ternary default_val, int offset);
extern void add_local_int_reloption(local_relopts *relopts, const char *name,
const char *desc, int default_val,
int min_val, int max_val, int offset);
diff --git a/src/include/c.h b/src/include/c.h
index 39022f8a9d..9c59ccccf8 100644
--- a/src/include/c.h
+++ b/src/include/c.h
@@ -488,6 +488,22 @@ typedef void (*pg_funcptr_t) (void);
#include <stdbool.h>
+/*
+ * ternary
+ * Boolean value with an extrea "unset" option
+ *
+ * Ternary data type is used in relation options that can be "true", "false" or
+ * "unset". Since relation options are used deep inside the PostgreSQL code,
+ * this type is declared globally.
+*/
+
+typedef enum ternary
+{
+ TERNARY_FALSE = 0,
+ TERNARY_TRUE = 1,
+ TERNARY_UNSET = -1
+} ternary;
+
/* ----------------------------------------------------------------
* Section 3: standard system types
diff --git a/src/include/utils/rel.h b/src/include/utils/rel.h
index b552359915..7346d618f9 100644
--- a/src/include/utils/rel.h
+++ b/src/include/utils/rel.h
@@ -346,8 +346,7 @@ typedef struct StdRdOptions
bool user_catalog_table; /* use as an additional catalog relation */
int parallel_workers; /* max number of parallel workers */
StdRdOptIndexCleanup vacuum_index_cleanup; /* controls index vacuuming */
- bool vacuum_truncate; /* enables vacuum to truncate a relation */
- bool vacuum_truncate_set; /* whether vacuum_truncate is set */
+ ternary vacuum_truncate; /* enables vacuum to truncate a relation */
/*
* Fraction of pages in a relation that vacuum can eagerly scan and fail
--
2.39.2
--nextPart5129932.5fSG56mABF
Content-Disposition: attachment;
filename="v2-0003-Add-alias-to-be-used-as-unset-state.patch"
Content-Transfer-Encoding: 7Bit
Content-Type: text/x-patch; charset="unicode-2-0-utf-8";
name="v2-0003-Add-alias-to-be-used-as-unset-state.patch"
^ permalink raw reply [nested|flat] 868+ messages in thread
* [PATCH v2a 2/4] Introduce ternary reloptions
@ 2025-09-04 14:41 Nikolay Shaplov <[email protected]>
0 siblings, 0 replies; 868+ messages in thread
From: Nikolay Shaplov @ 2025-09-04 14:41 UTC (permalink / raw)
Introduce ternary reloption as a replacement for current `vacuum_truncate`
implementation. Remove `vacuum_truncate_set` additional flag and using
`TERNARY_UNSET` value instead.
---
src/backend/access/common/reloptions.c | 129 ++++++++++++++++++++-----
src/backend/commands/vacuum.c | 4 +-
src/include/access/reloptions.h | 26 ++---
src/include/c.h | 16 +++
src/include/utils/rel.h | 3 +-
5 files changed, 135 insertions(+), 43 deletions(-)
diff --git a/src/backend/access/common/reloptions.c b/src/backend/access/common/reloptions.c
index 0af3fea68f..f543f11001 100644
--- a/src/backend/access/common/reloptions.c
+++ b/src/backend/access/common/reloptions.c
@@ -40,9 +40,9 @@
*
* To add an option:
*
- * (i) decide on a type (bool, integer, real, enum, string), name, default
- * value, upper and lower bounds (if applicable); for strings, consider a
- * validation routine.
+ * (i) decide on a type (bool, ternary, integer, real, enum, string), name,
+ * default value, upper and lower bounds (if applicable); for strings,
+ * consider a validation routine.
* (ii) add a record below (or use add_<type>_reloption).
* (iii) add it to the appropriate options struct (perhaps StdRdOptions)
* (iv) add it to the appropriate handling routine (perhaps
@@ -147,15 +147,6 @@ static relopt_bool boolRelOpts[] =
},
false
},
- {
- {
- "vacuum_truncate",
- "Enables vacuum to truncate empty pages at the end of this table",
- RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
- ShareUpdateExclusiveLock
- },
- true
- },
{
{
"deduplicate_items",
@@ -170,6 +161,21 @@ static relopt_bool boolRelOpts[] =
{{NULL}}
};
+static relopt_ternary ternaryRelOpts[] =
+{
+ {
+ {
+ "vacuum_truncate",
+ "Enables vacuum to truncate empty pages at the end of this table",
+ RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
+ ShareUpdateExclusiveLock
+ },
+ TERNARY_UNSET
+ },
+ /* list terminator */
+ {{NULL}}
+};
+
static relopt_int intRelOpts[] =
{
{
@@ -600,6 +606,13 @@ initialize_reloptions(void)
boolRelOpts[i].gen.lockmode));
j++;
}
+ for (i = 0; ternaryRelOpts[i].gen.name; i++)
+ {
+ Assert(DoLockModesConflict(ternaryRelOpts[i].gen.lockmode,
+ ternaryRelOpts[i].gen.lockmode));
+ j++;
+ }
+
for (i = 0; intRelOpts[i].gen.name; i++)
{
Assert(DoLockModesConflict(intRelOpts[i].gen.lockmode,
@@ -640,6 +653,14 @@ initialize_reloptions(void)
j++;
}
+ for (i = 0; ternaryRelOpts[i].gen.name; i++)
+ {
+ relOpts[j] = &ternaryRelOpts[i].gen;
+ relOpts[j]->type = RELOPT_TYPE_TERNARY;
+ relOpts[j]->namelen = strlen(relOpts[j]->name);
+ j++;
+ }
+
for (i = 0; intRelOpts[i].gen.name; i++)
{
relOpts[j] = &intRelOpts[i].gen;
@@ -800,6 +821,9 @@ allocate_reloption(bits32 kinds, int type, const char *name, const char *desc,
case RELOPT_TYPE_BOOL:
size = sizeof(relopt_bool);
break;
+ case RELOPT_TYPE_TERNARY:
+ size = sizeof(relopt_ternary);
+ break;
case RELOPT_TYPE_INT:
size = sizeof(relopt_int);
break;
@@ -883,6 +907,54 @@ add_local_bool_reloption(local_relopts *relopts, const char *name,
add_local_reloption(relopts, (relopt_gen *) newoption, offset);
}
+/*
+ * init_ternary_reloption
+ * Allocate and initialize a new ternary reloption
+ */
+static relopt_ternary *
+init_ternary_reloption(bits32 kinds, const char *name, const char *desc,
+ ternary default_val, LOCKMODE lockmode)
+{
+ relopt_ternary *newoption;
+
+ newoption = (relopt_ternary *) allocate_reloption(kinds,
+ RELOPT_TYPE_TERNARY, name, desc, lockmode);
+ newoption->default_val = default_val;
+
+ return newoption;
+}
+
+/*
+ * add_ternary_reloption
+ * Add a new ternary reloption
+ */
+void
+add_ternary_reloption(bits32 kinds, const char *name, const char *desc,
+ ternary default_val, LOCKMODE lockmode)
+{
+ relopt_ternary *newoption = init_ternary_reloption(kinds, name, desc,
+ default_val, lockmode);
+
+ add_reloption((relopt_gen *) newoption);
+}
+
+/*
+ * add_local_ternary_reloption
+ * Add a new ternary local reloption
+ *
+ * 'offset' is offset of ternary-typed field.
+ */
+void
+add_local_ternary_reloption(local_relopts *relopts, const char *name,
+ const char *desc, ternary default_val,
+ int offset)
+{
+ relopt_ternary *newoption = init_ternary_reloption(RELOPT_KIND_LOCAL,
+ name, desc,
+ default_val, 0);
+
+ add_local_reloption(relopts, (relopt_gen *) newoption, offset);
+}
/*
* init_real_reloption
@@ -1617,6 +1689,18 @@ parse_one_reloption(relopt_value *option, char *text_str, int text_len,
option->gen->name, value)));
}
break;
+ case RELOPT_TYPE_TERNARY:
+ {
+ bool b;
+ parsed = parse_bool(value, &b);
+ option->values.ternary_val = b ? TERNARY_TRUE : TERNARY_FALSE;
+ if (validate && !parsed)
+ ereport(ERROR,
+ (errcode(ERRCODE_INVALID_PARAMETER_VALUE),
+ errmsg("invalid value for ternary option \"%s\": %s",
+ option->gen->name, value)));
+ }
+ break;
case RELOPT_TYPE_INT:
{
relopt_int *optint = (relopt_int *) option->gen;
@@ -1780,17 +1864,6 @@ fillRelOptions(void *rdopts, Size basesize,
char *itempos = ((char *) rdopts) + elems[j].offset;
char *string_val;
- /*
- * If isset_offset is provided, store whether the reloption is
- * set there.
- */
- if (elems[j].isset_offset > 0)
- {
- char *setpos = ((char *) rdopts) + elems[j].isset_offset;
-
- *(bool *) setpos = options[i].isset;
- }
-
switch (options[i].gen->type)
{
case RELOPT_TYPE_BOOL:
@@ -1798,6 +1871,11 @@ fillRelOptions(void *rdopts, Size basesize,
options[i].values.bool_val :
((relopt_bool *) options[i].gen)->default_val;
break;
+ case RELOPT_TYPE_TERNARY:
+ *(ternary *) itempos = options[i].isset ?
+ options[i].values.ternary_val :
+ ((relopt_ternary *) options[i].gen)->default_val;
+ break;
case RELOPT_TYPE_INT:
*(int *) itempos = options[i].isset ?
options[i].values.int_val :
@@ -1912,8 +1990,8 @@ default_reloptions(Datum reloptions, bool validate, relopt_kind kind)
offsetof(StdRdOptions, parallel_workers)},
{"vacuum_index_cleanup", RELOPT_TYPE_ENUM,
offsetof(StdRdOptions, vacuum_index_cleanup)},
- {"vacuum_truncate", RELOPT_TYPE_BOOL,
- offsetof(StdRdOptions, vacuum_truncate), offsetof(StdRdOptions, vacuum_truncate_set)},
+ {"vacuum_truncate", RELOPT_TYPE_TERNARY,
+ offsetof(StdRdOptions, vacuum_truncate)},
{"vacuum_max_eager_freeze_failure_rate", RELOPT_TYPE_REAL,
offsetof(StdRdOptions, vacuum_max_eager_freeze_failure_rate)}
};
@@ -1993,7 +2071,6 @@ build_local_reloptions(local_relopts *relopts, Datum options, bool validate)
elems[i].optname = opt->option->name;
elems[i].opttype = opt->option->type;
elems[i].offset = opt->offset;
- elems[i].isset_offset = 0; /* not supported for local relopts yet */
i++;
}
diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c
index 733ef40ae7..977babff54 100644
--- a/src/backend/commands/vacuum.c
+++ b/src/backend/commands/vacuum.c
@@ -2221,9 +2221,9 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams params,
{
StdRdOptions *opts = (StdRdOptions *) rel->rd_options;
- if (opts && opts->vacuum_truncate_set)
+ if (opts && opts->vacuum_truncate != TERNARY_UNSET)
{
- if (opts->vacuum_truncate)
+ if (opts->vacuum_truncate == TERNARY_TRUE)
params.truncate = VACOPTVALUE_ENABLED;
else
params.truncate = VACOPTVALUE_DISABLED;
diff --git a/src/include/access/reloptions.h b/src/include/access/reloptions.h
index a604a4702c..a436697658 100644
--- a/src/include/access/reloptions.h
+++ b/src/include/access/reloptions.h
@@ -29,6 +29,7 @@
typedef enum relopt_type
{
RELOPT_TYPE_BOOL,
+ RELOPT_TYPE_TERNARY, /* on, off, unset */
RELOPT_TYPE_INT,
RELOPT_TYPE_REAL,
RELOPT_TYPE_ENUM,
@@ -80,6 +81,7 @@ typedef struct relopt_value
union
{
bool bool_val;
+ ternary ternary_val;
int int_val;
double real_val;
int enum_val;
@@ -94,6 +96,12 @@ typedef struct relopt_bool
bool default_val;
} relopt_bool;
+typedef struct relopt_rernary
+{
+ relopt_gen gen;
+ int default_val;
+} relopt_ternary;
+
typedef struct relopt_int
{
relopt_gen gen;
@@ -152,19 +160,6 @@ typedef struct
const char *optname; /* option's name */
relopt_type opttype; /* option's datatype */
int offset; /* offset of field in result struct */
-
- /*
- * isset_offset is an optional offset of a field in the result struct that
- * stores whether the option is explicitly set for the relation or if it
- * just picked up the default value. In most cases, this can be
- * accomplished by giving the reloption a special out-of-range default
- * value (e.g., some integer reloptions use -2), but this isn't always
- * possible. For example, a Boolean reloption cannot be given an
- * out-of-range default, so we need another way to discover the source of
- * its value. This offset is only used if given a value greater than
- * zero.
- */
- int isset_offset;
} relopt_parse_elt;
/* Local reloption definition */
@@ -195,6 +190,8 @@ typedef struct local_relopts
extern relopt_kind add_reloption_kind(void);
extern void add_bool_reloption(bits32 kinds, const char *name, const char *desc,
bool default_val, LOCKMODE lockmode);
+extern void add_ternary_reloption(bits32 kinds, const char *name,
+ const char *desc, int default_val, LOCKMODE lockmode);
extern void add_int_reloption(bits32 kinds, const char *name, const char *desc,
int default_val, int min_val, int max_val,
LOCKMODE lockmode);
@@ -214,6 +211,9 @@ extern void register_reloptions_validator(local_relopts *relopts,
extern void add_local_bool_reloption(local_relopts *relopts, const char *name,
const char *desc, bool default_val,
int offset);
+extern void add_local_ternary_reloption(local_relopts *relopts,
+ const char *name, const char *desc,
+ ternary default_val, int offset);
extern void add_local_int_reloption(local_relopts *relopts, const char *name,
const char *desc, int default_val,
int min_val, int max_val, int offset);
diff --git a/src/include/c.h b/src/include/c.h
index 39022f8a9d..9c59ccccf8 100644
--- a/src/include/c.h
+++ b/src/include/c.h
@@ -488,6 +488,22 @@ typedef void (*pg_funcptr_t) (void);
#include <stdbool.h>
+/*
+ * ternary
+ * Boolean value with an extrea "unset" option
+ *
+ * Ternary data type is used in relation options that can be "true", "false" or
+ * "unset". Since relation options are used deep inside the PostgreSQL code,
+ * this type is declared globally.
+*/
+
+typedef enum ternary
+{
+ TERNARY_FALSE = 0,
+ TERNARY_TRUE = 1,
+ TERNARY_UNSET = -1
+} ternary;
+
/* ----------------------------------------------------------------
* Section 3: standard system types
diff --git a/src/include/utils/rel.h b/src/include/utils/rel.h
index b552359915..7346d618f9 100644
--- a/src/include/utils/rel.h
+++ b/src/include/utils/rel.h
@@ -346,8 +346,7 @@ typedef struct StdRdOptions
bool user_catalog_table; /* use as an additional catalog relation */
int parallel_workers; /* max number of parallel workers */
StdRdOptIndexCleanup vacuum_index_cleanup; /* controls index vacuuming */
- bool vacuum_truncate; /* enables vacuum to truncate a relation */
- bool vacuum_truncate_set; /* whether vacuum_truncate is set */
+ ternary vacuum_truncate; /* enables vacuum to truncate a relation */
/*
* Fraction of pages in a relation that vacuum can eagerly scan and fail
--
2.39.2
--nextPart4155600.3ZeAukHxDK
Content-Disposition: attachment;
filename="v2a-0003-Add-alias-to-be-used-as-unset-state.patch"
Content-Transfer-Encoding: 7Bit
Content-Type: text/x-patch; charset="unicode-2-0-utf-8";
name="v2a-0003-Add-alias-to-be-used-as-unset-state.patch"
^ permalink raw reply [nested|flat] 868+ messages in thread
* [PATCH v2a 2/4] Introduce ternary reloptions
@ 2025-09-04 14:41 Nikolay Shaplov <[email protected]>
0 siblings, 0 replies; 868+ messages in thread
From: Nikolay Shaplov @ 2025-09-04 14:41 UTC (permalink / raw)
Introduce ternary reloption as a replacement for current `vacuum_truncate`
implementation. Remove `vacuum_truncate_set` additional flag and using
`TERNARY_UNSET` value instead.
---
src/backend/access/common/reloptions.c | 129 ++++++++++++++++++++-----
src/backend/commands/vacuum.c | 4 +-
src/include/access/reloptions.h | 26 ++---
src/include/c.h | 16 +++
src/include/utils/rel.h | 3 +-
5 files changed, 135 insertions(+), 43 deletions(-)
diff --git a/src/backend/access/common/reloptions.c b/src/backend/access/common/reloptions.c
index 0af3fea68f..f543f11001 100644
--- a/src/backend/access/common/reloptions.c
+++ b/src/backend/access/common/reloptions.c
@@ -40,9 +40,9 @@
*
* To add an option:
*
- * (i) decide on a type (bool, integer, real, enum, string), name, default
- * value, upper and lower bounds (if applicable); for strings, consider a
- * validation routine.
+ * (i) decide on a type (bool, ternary, integer, real, enum, string), name,
+ * default value, upper and lower bounds (if applicable); for strings,
+ * consider a validation routine.
* (ii) add a record below (or use add_<type>_reloption).
* (iii) add it to the appropriate options struct (perhaps StdRdOptions)
* (iv) add it to the appropriate handling routine (perhaps
@@ -147,15 +147,6 @@ static relopt_bool boolRelOpts[] =
},
false
},
- {
- {
- "vacuum_truncate",
- "Enables vacuum to truncate empty pages at the end of this table",
- RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
- ShareUpdateExclusiveLock
- },
- true
- },
{
{
"deduplicate_items",
@@ -170,6 +161,21 @@ static relopt_bool boolRelOpts[] =
{{NULL}}
};
+static relopt_ternary ternaryRelOpts[] =
+{
+ {
+ {
+ "vacuum_truncate",
+ "Enables vacuum to truncate empty pages at the end of this table",
+ RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
+ ShareUpdateExclusiveLock
+ },
+ TERNARY_UNSET
+ },
+ /* list terminator */
+ {{NULL}}
+};
+
static relopt_int intRelOpts[] =
{
{
@@ -600,6 +606,13 @@ initialize_reloptions(void)
boolRelOpts[i].gen.lockmode));
j++;
}
+ for (i = 0; ternaryRelOpts[i].gen.name; i++)
+ {
+ Assert(DoLockModesConflict(ternaryRelOpts[i].gen.lockmode,
+ ternaryRelOpts[i].gen.lockmode));
+ j++;
+ }
+
for (i = 0; intRelOpts[i].gen.name; i++)
{
Assert(DoLockModesConflict(intRelOpts[i].gen.lockmode,
@@ -640,6 +653,14 @@ initialize_reloptions(void)
j++;
}
+ for (i = 0; ternaryRelOpts[i].gen.name; i++)
+ {
+ relOpts[j] = &ternaryRelOpts[i].gen;
+ relOpts[j]->type = RELOPT_TYPE_TERNARY;
+ relOpts[j]->namelen = strlen(relOpts[j]->name);
+ j++;
+ }
+
for (i = 0; intRelOpts[i].gen.name; i++)
{
relOpts[j] = &intRelOpts[i].gen;
@@ -800,6 +821,9 @@ allocate_reloption(bits32 kinds, int type, const char *name, const char *desc,
case RELOPT_TYPE_BOOL:
size = sizeof(relopt_bool);
break;
+ case RELOPT_TYPE_TERNARY:
+ size = sizeof(relopt_ternary);
+ break;
case RELOPT_TYPE_INT:
size = sizeof(relopt_int);
break;
@@ -883,6 +907,54 @@ add_local_bool_reloption(local_relopts *relopts, const char *name,
add_local_reloption(relopts, (relopt_gen *) newoption, offset);
}
+/*
+ * init_ternary_reloption
+ * Allocate and initialize a new ternary reloption
+ */
+static relopt_ternary *
+init_ternary_reloption(bits32 kinds, const char *name, const char *desc,
+ ternary default_val, LOCKMODE lockmode)
+{
+ relopt_ternary *newoption;
+
+ newoption = (relopt_ternary *) allocate_reloption(kinds,
+ RELOPT_TYPE_TERNARY, name, desc, lockmode);
+ newoption->default_val = default_val;
+
+ return newoption;
+}
+
+/*
+ * add_ternary_reloption
+ * Add a new ternary reloption
+ */
+void
+add_ternary_reloption(bits32 kinds, const char *name, const char *desc,
+ ternary default_val, LOCKMODE lockmode)
+{
+ relopt_ternary *newoption = init_ternary_reloption(kinds, name, desc,
+ default_val, lockmode);
+
+ add_reloption((relopt_gen *) newoption);
+}
+
+/*
+ * add_local_ternary_reloption
+ * Add a new ternary local reloption
+ *
+ * 'offset' is offset of ternary-typed field.
+ */
+void
+add_local_ternary_reloption(local_relopts *relopts, const char *name,
+ const char *desc, ternary default_val,
+ int offset)
+{
+ relopt_ternary *newoption = init_ternary_reloption(RELOPT_KIND_LOCAL,
+ name, desc,
+ default_val, 0);
+
+ add_local_reloption(relopts, (relopt_gen *) newoption, offset);
+}
/*
* init_real_reloption
@@ -1617,6 +1689,18 @@ parse_one_reloption(relopt_value *option, char *text_str, int text_len,
option->gen->name, value)));
}
break;
+ case RELOPT_TYPE_TERNARY:
+ {
+ bool b;
+ parsed = parse_bool(value, &b);
+ option->values.ternary_val = b ? TERNARY_TRUE : TERNARY_FALSE;
+ if (validate && !parsed)
+ ereport(ERROR,
+ (errcode(ERRCODE_INVALID_PARAMETER_VALUE),
+ errmsg("invalid value for ternary option \"%s\": %s",
+ option->gen->name, value)));
+ }
+ break;
case RELOPT_TYPE_INT:
{
relopt_int *optint = (relopt_int *) option->gen;
@@ -1780,17 +1864,6 @@ fillRelOptions(void *rdopts, Size basesize,
char *itempos = ((char *) rdopts) + elems[j].offset;
char *string_val;
- /*
- * If isset_offset is provided, store whether the reloption is
- * set there.
- */
- if (elems[j].isset_offset > 0)
- {
- char *setpos = ((char *) rdopts) + elems[j].isset_offset;
-
- *(bool *) setpos = options[i].isset;
- }
-
switch (options[i].gen->type)
{
case RELOPT_TYPE_BOOL:
@@ -1798,6 +1871,11 @@ fillRelOptions(void *rdopts, Size basesize,
options[i].values.bool_val :
((relopt_bool *) options[i].gen)->default_val;
break;
+ case RELOPT_TYPE_TERNARY:
+ *(ternary *) itempos = options[i].isset ?
+ options[i].values.ternary_val :
+ ((relopt_ternary *) options[i].gen)->default_val;
+ break;
case RELOPT_TYPE_INT:
*(int *) itempos = options[i].isset ?
options[i].values.int_val :
@@ -1912,8 +1990,8 @@ default_reloptions(Datum reloptions, bool validate, relopt_kind kind)
offsetof(StdRdOptions, parallel_workers)},
{"vacuum_index_cleanup", RELOPT_TYPE_ENUM,
offsetof(StdRdOptions, vacuum_index_cleanup)},
- {"vacuum_truncate", RELOPT_TYPE_BOOL,
- offsetof(StdRdOptions, vacuum_truncate), offsetof(StdRdOptions, vacuum_truncate_set)},
+ {"vacuum_truncate", RELOPT_TYPE_TERNARY,
+ offsetof(StdRdOptions, vacuum_truncate)},
{"vacuum_max_eager_freeze_failure_rate", RELOPT_TYPE_REAL,
offsetof(StdRdOptions, vacuum_max_eager_freeze_failure_rate)}
};
@@ -1993,7 +2071,6 @@ build_local_reloptions(local_relopts *relopts, Datum options, bool validate)
elems[i].optname = opt->option->name;
elems[i].opttype = opt->option->type;
elems[i].offset = opt->offset;
- elems[i].isset_offset = 0; /* not supported for local relopts yet */
i++;
}
diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c
index 733ef40ae7..977babff54 100644
--- a/src/backend/commands/vacuum.c
+++ b/src/backend/commands/vacuum.c
@@ -2221,9 +2221,9 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams params,
{
StdRdOptions *opts = (StdRdOptions *) rel->rd_options;
- if (opts && opts->vacuum_truncate_set)
+ if (opts && opts->vacuum_truncate != TERNARY_UNSET)
{
- if (opts->vacuum_truncate)
+ if (opts->vacuum_truncate == TERNARY_TRUE)
params.truncate = VACOPTVALUE_ENABLED;
else
params.truncate = VACOPTVALUE_DISABLED;
diff --git a/src/include/access/reloptions.h b/src/include/access/reloptions.h
index a604a4702c..a436697658 100644
--- a/src/include/access/reloptions.h
+++ b/src/include/access/reloptions.h
@@ -29,6 +29,7 @@
typedef enum relopt_type
{
RELOPT_TYPE_BOOL,
+ RELOPT_TYPE_TERNARY, /* on, off, unset */
RELOPT_TYPE_INT,
RELOPT_TYPE_REAL,
RELOPT_TYPE_ENUM,
@@ -80,6 +81,7 @@ typedef struct relopt_value
union
{
bool bool_val;
+ ternary ternary_val;
int int_val;
double real_val;
int enum_val;
@@ -94,6 +96,12 @@ typedef struct relopt_bool
bool default_val;
} relopt_bool;
+typedef struct relopt_rernary
+{
+ relopt_gen gen;
+ int default_val;
+} relopt_ternary;
+
typedef struct relopt_int
{
relopt_gen gen;
@@ -152,19 +160,6 @@ typedef struct
const char *optname; /* option's name */
relopt_type opttype; /* option's datatype */
int offset; /* offset of field in result struct */
-
- /*
- * isset_offset is an optional offset of a field in the result struct that
- * stores whether the option is explicitly set for the relation or if it
- * just picked up the default value. In most cases, this can be
- * accomplished by giving the reloption a special out-of-range default
- * value (e.g., some integer reloptions use -2), but this isn't always
- * possible. For example, a Boolean reloption cannot be given an
- * out-of-range default, so we need another way to discover the source of
- * its value. This offset is only used if given a value greater than
- * zero.
- */
- int isset_offset;
} relopt_parse_elt;
/* Local reloption definition */
@@ -195,6 +190,8 @@ typedef struct local_relopts
extern relopt_kind add_reloption_kind(void);
extern void add_bool_reloption(bits32 kinds, const char *name, const char *desc,
bool default_val, LOCKMODE lockmode);
+extern void add_ternary_reloption(bits32 kinds, const char *name,
+ const char *desc, int default_val, LOCKMODE lockmode);
extern void add_int_reloption(bits32 kinds, const char *name, const char *desc,
int default_val, int min_val, int max_val,
LOCKMODE lockmode);
@@ -214,6 +211,9 @@ extern void register_reloptions_validator(local_relopts *relopts,
extern void add_local_bool_reloption(local_relopts *relopts, const char *name,
const char *desc, bool default_val,
int offset);
+extern void add_local_ternary_reloption(local_relopts *relopts,
+ const char *name, const char *desc,
+ ternary default_val, int offset);
extern void add_local_int_reloption(local_relopts *relopts, const char *name,
const char *desc, int default_val,
int min_val, int max_val, int offset);
diff --git a/src/include/c.h b/src/include/c.h
index 39022f8a9d..9c59ccccf8 100644
--- a/src/include/c.h
+++ b/src/include/c.h
@@ -488,6 +488,22 @@ typedef void (*pg_funcptr_t) (void);
#include <stdbool.h>
+/*
+ * ternary
+ * Boolean value with an extrea "unset" option
+ *
+ * Ternary data type is used in relation options that can be "true", "false" or
+ * "unset". Since relation options are used deep inside the PostgreSQL code,
+ * this type is declared globally.
+*/
+
+typedef enum ternary
+{
+ TERNARY_FALSE = 0,
+ TERNARY_TRUE = 1,
+ TERNARY_UNSET = -1
+} ternary;
+
/* ----------------------------------------------------------------
* Section 3: standard system types
diff --git a/src/include/utils/rel.h b/src/include/utils/rel.h
index b552359915..7346d618f9 100644
--- a/src/include/utils/rel.h
+++ b/src/include/utils/rel.h
@@ -346,8 +346,7 @@ typedef struct StdRdOptions
bool user_catalog_table; /* use as an additional catalog relation */
int parallel_workers; /* max number of parallel workers */
StdRdOptIndexCleanup vacuum_index_cleanup; /* controls index vacuuming */
- bool vacuum_truncate; /* enables vacuum to truncate a relation */
- bool vacuum_truncate_set; /* whether vacuum_truncate is set */
+ ternary vacuum_truncate; /* enables vacuum to truncate a relation */
/*
* Fraction of pages in a relation that vacuum can eagerly scan and fail
--
2.39.2
--nextPart4155600.3ZeAukHxDK
Content-Disposition: attachment;
filename="v2a-0003-Add-alias-to-be-used-as-unset-state.patch"
Content-Transfer-Encoding: 7Bit
Content-Type: text/x-patch; charset="unicode-2-0-utf-8";
name="v2a-0003-Add-alias-to-be-used-as-unset-state.patch"
^ permalink raw reply [nested|flat] 868+ messages in thread
* [PATCH v2 2/4] Introduce ternary reloptions
@ 2025-09-04 14:41 Nikolay Shaplov <[email protected]>
0 siblings, 0 replies; 868+ messages in thread
From: Nikolay Shaplov @ 2025-09-04 14:41 UTC (permalink / raw)
Introduce ternary reloption as a replacement for current `vacuum_truncate`
implementation. Remove `vacuum_truncate_set` additional flag and using
`TERNARY_UNSET` value instead.
---
src/backend/access/common/reloptions.c | 129 ++++++++++++++++++++-----
src/backend/commands/vacuum.c | 4 +-
src/include/access/reloptions.h | 26 ++---
src/include/c.h | 16 +++
src/include/utils/rel.h | 3 +-
5 files changed, 135 insertions(+), 43 deletions(-)
diff --git a/src/backend/access/common/reloptions.c b/src/backend/access/common/reloptions.c
index 0af3fea68f..f543f11001 100644
--- a/src/backend/access/common/reloptions.c
+++ b/src/backend/access/common/reloptions.c
@@ -40,9 +40,9 @@
*
* To add an option:
*
- * (i) decide on a type (bool, integer, real, enum, string), name, default
- * value, upper and lower bounds (if applicable); for strings, consider a
- * validation routine.
+ * (i) decide on a type (bool, ternary, integer, real, enum, string), name,
+ * default value, upper and lower bounds (if applicable); for strings,
+ * consider a validation routine.
* (ii) add a record below (or use add_<type>_reloption).
* (iii) add it to the appropriate options struct (perhaps StdRdOptions)
* (iv) add it to the appropriate handling routine (perhaps
@@ -147,15 +147,6 @@ static relopt_bool boolRelOpts[] =
},
false
},
- {
- {
- "vacuum_truncate",
- "Enables vacuum to truncate empty pages at the end of this table",
- RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
- ShareUpdateExclusiveLock
- },
- true
- },
{
{
"deduplicate_items",
@@ -170,6 +161,21 @@ static relopt_bool boolRelOpts[] =
{{NULL}}
};
+static relopt_ternary ternaryRelOpts[] =
+{
+ {
+ {
+ "vacuum_truncate",
+ "Enables vacuum to truncate empty pages at the end of this table",
+ RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
+ ShareUpdateExclusiveLock
+ },
+ TERNARY_UNSET
+ },
+ /* list terminator */
+ {{NULL}}
+};
+
static relopt_int intRelOpts[] =
{
{
@@ -600,6 +606,13 @@ initialize_reloptions(void)
boolRelOpts[i].gen.lockmode));
j++;
}
+ for (i = 0; ternaryRelOpts[i].gen.name; i++)
+ {
+ Assert(DoLockModesConflict(ternaryRelOpts[i].gen.lockmode,
+ ternaryRelOpts[i].gen.lockmode));
+ j++;
+ }
+
for (i = 0; intRelOpts[i].gen.name; i++)
{
Assert(DoLockModesConflict(intRelOpts[i].gen.lockmode,
@@ -640,6 +653,14 @@ initialize_reloptions(void)
j++;
}
+ for (i = 0; ternaryRelOpts[i].gen.name; i++)
+ {
+ relOpts[j] = &ternaryRelOpts[i].gen;
+ relOpts[j]->type = RELOPT_TYPE_TERNARY;
+ relOpts[j]->namelen = strlen(relOpts[j]->name);
+ j++;
+ }
+
for (i = 0; intRelOpts[i].gen.name; i++)
{
relOpts[j] = &intRelOpts[i].gen;
@@ -800,6 +821,9 @@ allocate_reloption(bits32 kinds, int type, const char *name, const char *desc,
case RELOPT_TYPE_BOOL:
size = sizeof(relopt_bool);
break;
+ case RELOPT_TYPE_TERNARY:
+ size = sizeof(relopt_ternary);
+ break;
case RELOPT_TYPE_INT:
size = sizeof(relopt_int);
break;
@@ -883,6 +907,54 @@ add_local_bool_reloption(local_relopts *relopts, const char *name,
add_local_reloption(relopts, (relopt_gen *) newoption, offset);
}
+/*
+ * init_ternary_reloption
+ * Allocate and initialize a new ternary reloption
+ */
+static relopt_ternary *
+init_ternary_reloption(bits32 kinds, const char *name, const char *desc,
+ ternary default_val, LOCKMODE lockmode)
+{
+ relopt_ternary *newoption;
+
+ newoption = (relopt_ternary *) allocate_reloption(kinds,
+ RELOPT_TYPE_TERNARY, name, desc, lockmode);
+ newoption->default_val = default_val;
+
+ return newoption;
+}
+
+/*
+ * add_ternary_reloption
+ * Add a new ternary reloption
+ */
+void
+add_ternary_reloption(bits32 kinds, const char *name, const char *desc,
+ ternary default_val, LOCKMODE lockmode)
+{
+ relopt_ternary *newoption = init_ternary_reloption(kinds, name, desc,
+ default_val, lockmode);
+
+ add_reloption((relopt_gen *) newoption);
+}
+
+/*
+ * add_local_ternary_reloption
+ * Add a new ternary local reloption
+ *
+ * 'offset' is offset of ternary-typed field.
+ */
+void
+add_local_ternary_reloption(local_relopts *relopts, const char *name,
+ const char *desc, ternary default_val,
+ int offset)
+{
+ relopt_ternary *newoption = init_ternary_reloption(RELOPT_KIND_LOCAL,
+ name, desc,
+ default_val, 0);
+
+ add_local_reloption(relopts, (relopt_gen *) newoption, offset);
+}
/*
* init_real_reloption
@@ -1617,6 +1689,18 @@ parse_one_reloption(relopt_value *option, char *text_str, int text_len,
option->gen->name, value)));
}
break;
+ case RELOPT_TYPE_TERNARY:
+ {
+ bool b;
+ parsed = parse_bool(value, &b);
+ option->values.ternary_val = b ? TERNARY_TRUE : TERNARY_FALSE;
+ if (validate && !parsed)
+ ereport(ERROR,
+ (errcode(ERRCODE_INVALID_PARAMETER_VALUE),
+ errmsg("invalid value for ternary option \"%s\": %s",
+ option->gen->name, value)));
+ }
+ break;
case RELOPT_TYPE_INT:
{
relopt_int *optint = (relopt_int *) option->gen;
@@ -1780,17 +1864,6 @@ fillRelOptions(void *rdopts, Size basesize,
char *itempos = ((char *) rdopts) + elems[j].offset;
char *string_val;
- /*
- * If isset_offset is provided, store whether the reloption is
- * set there.
- */
- if (elems[j].isset_offset > 0)
- {
- char *setpos = ((char *) rdopts) + elems[j].isset_offset;
-
- *(bool *) setpos = options[i].isset;
- }
-
switch (options[i].gen->type)
{
case RELOPT_TYPE_BOOL:
@@ -1798,6 +1871,11 @@ fillRelOptions(void *rdopts, Size basesize,
options[i].values.bool_val :
((relopt_bool *) options[i].gen)->default_val;
break;
+ case RELOPT_TYPE_TERNARY:
+ *(ternary *) itempos = options[i].isset ?
+ options[i].values.ternary_val :
+ ((relopt_ternary *) options[i].gen)->default_val;
+ break;
case RELOPT_TYPE_INT:
*(int *) itempos = options[i].isset ?
options[i].values.int_val :
@@ -1912,8 +1990,8 @@ default_reloptions(Datum reloptions, bool validate, relopt_kind kind)
offsetof(StdRdOptions, parallel_workers)},
{"vacuum_index_cleanup", RELOPT_TYPE_ENUM,
offsetof(StdRdOptions, vacuum_index_cleanup)},
- {"vacuum_truncate", RELOPT_TYPE_BOOL,
- offsetof(StdRdOptions, vacuum_truncate), offsetof(StdRdOptions, vacuum_truncate_set)},
+ {"vacuum_truncate", RELOPT_TYPE_TERNARY,
+ offsetof(StdRdOptions, vacuum_truncate)},
{"vacuum_max_eager_freeze_failure_rate", RELOPT_TYPE_REAL,
offsetof(StdRdOptions, vacuum_max_eager_freeze_failure_rate)}
};
@@ -1993,7 +2071,6 @@ build_local_reloptions(local_relopts *relopts, Datum options, bool validate)
elems[i].optname = opt->option->name;
elems[i].opttype = opt->option->type;
elems[i].offset = opt->offset;
- elems[i].isset_offset = 0; /* not supported for local relopts yet */
i++;
}
diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c
index 733ef40ae7..977babff54 100644
--- a/src/backend/commands/vacuum.c
+++ b/src/backend/commands/vacuum.c
@@ -2221,9 +2221,9 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams params,
{
StdRdOptions *opts = (StdRdOptions *) rel->rd_options;
- if (opts && opts->vacuum_truncate_set)
+ if (opts && opts->vacuum_truncate != TERNARY_UNSET)
{
- if (opts->vacuum_truncate)
+ if (opts->vacuum_truncate == TERNARY_TRUE)
params.truncate = VACOPTVALUE_ENABLED;
else
params.truncate = VACOPTVALUE_DISABLED;
diff --git a/src/include/access/reloptions.h b/src/include/access/reloptions.h
index a604a4702c..a436697658 100644
--- a/src/include/access/reloptions.h
+++ b/src/include/access/reloptions.h
@@ -29,6 +29,7 @@
typedef enum relopt_type
{
RELOPT_TYPE_BOOL,
+ RELOPT_TYPE_TERNARY, /* on, off, unset */
RELOPT_TYPE_INT,
RELOPT_TYPE_REAL,
RELOPT_TYPE_ENUM,
@@ -80,6 +81,7 @@ typedef struct relopt_value
union
{
bool bool_val;
+ ternary ternary_val;
int int_val;
double real_val;
int enum_val;
@@ -94,6 +96,12 @@ typedef struct relopt_bool
bool default_val;
} relopt_bool;
+typedef struct relopt_rernary
+{
+ relopt_gen gen;
+ int default_val;
+} relopt_ternary;
+
typedef struct relopt_int
{
relopt_gen gen;
@@ -152,19 +160,6 @@ typedef struct
const char *optname; /* option's name */
relopt_type opttype; /* option's datatype */
int offset; /* offset of field in result struct */
-
- /*
- * isset_offset is an optional offset of a field in the result struct that
- * stores whether the option is explicitly set for the relation or if it
- * just picked up the default value. In most cases, this can be
- * accomplished by giving the reloption a special out-of-range default
- * value (e.g., some integer reloptions use -2), but this isn't always
- * possible. For example, a Boolean reloption cannot be given an
- * out-of-range default, so we need another way to discover the source of
- * its value. This offset is only used if given a value greater than
- * zero.
- */
- int isset_offset;
} relopt_parse_elt;
/* Local reloption definition */
@@ -195,6 +190,8 @@ typedef struct local_relopts
extern relopt_kind add_reloption_kind(void);
extern void add_bool_reloption(bits32 kinds, const char *name, const char *desc,
bool default_val, LOCKMODE lockmode);
+extern void add_ternary_reloption(bits32 kinds, const char *name,
+ const char *desc, int default_val, LOCKMODE lockmode);
extern void add_int_reloption(bits32 kinds, const char *name, const char *desc,
int default_val, int min_val, int max_val,
LOCKMODE lockmode);
@@ -214,6 +211,9 @@ extern void register_reloptions_validator(local_relopts *relopts,
extern void add_local_bool_reloption(local_relopts *relopts, const char *name,
const char *desc, bool default_val,
int offset);
+extern void add_local_ternary_reloption(local_relopts *relopts,
+ const char *name, const char *desc,
+ ternary default_val, int offset);
extern void add_local_int_reloption(local_relopts *relopts, const char *name,
const char *desc, int default_val,
int min_val, int max_val, int offset);
diff --git a/src/include/c.h b/src/include/c.h
index 39022f8a9d..9c59ccccf8 100644
--- a/src/include/c.h
+++ b/src/include/c.h
@@ -488,6 +488,22 @@ typedef void (*pg_funcptr_t) (void);
#include <stdbool.h>
+/*
+ * ternary
+ * Boolean value with an extrea "unset" option
+ *
+ * Ternary data type is used in relation options that can be "true", "false" or
+ * "unset". Since relation options are used deep inside the PostgreSQL code,
+ * this type is declared globally.
+*/
+
+typedef enum ternary
+{
+ TERNARY_FALSE = 0,
+ TERNARY_TRUE = 1,
+ TERNARY_UNSET = -1
+} ternary;
+
/* ----------------------------------------------------------------
* Section 3: standard system types
diff --git a/src/include/utils/rel.h b/src/include/utils/rel.h
index b552359915..7346d618f9 100644
--- a/src/include/utils/rel.h
+++ b/src/include/utils/rel.h
@@ -346,8 +346,7 @@ typedef struct StdRdOptions
bool user_catalog_table; /* use as an additional catalog relation */
int parallel_workers; /* max number of parallel workers */
StdRdOptIndexCleanup vacuum_index_cleanup; /* controls index vacuuming */
- bool vacuum_truncate; /* enables vacuum to truncate a relation */
- bool vacuum_truncate_set; /* whether vacuum_truncate is set */
+ ternary vacuum_truncate; /* enables vacuum to truncate a relation */
/*
* Fraction of pages in a relation that vacuum can eagerly scan and fail
--
2.39.2
--nextPart5129932.5fSG56mABF
Content-Disposition: attachment;
filename="v2-0003-Add-alias-to-be-used-as-unset-state.patch"
Content-Transfer-Encoding: 7Bit
Content-Type: text/x-patch; charset="unicode-2-0-utf-8";
name="v2-0003-Add-alias-to-be-used-as-unset-state.patch"
^ permalink raw reply [nested|flat] 868+ messages in thread
* [PATCH v2a 2/4] Introduce ternary reloptions
@ 2025-09-04 14:41 Nikolay Shaplov <[email protected]>
0 siblings, 0 replies; 868+ messages in thread
From: Nikolay Shaplov @ 2025-09-04 14:41 UTC (permalink / raw)
Introduce ternary reloption as a replacement for current `vacuum_truncate`
implementation. Remove `vacuum_truncate_set` additional flag and using
`TERNARY_UNSET` value instead.
---
src/backend/access/common/reloptions.c | 129 ++++++++++++++++++++-----
src/backend/commands/vacuum.c | 4 +-
src/include/access/reloptions.h | 26 ++---
src/include/c.h | 16 +++
src/include/utils/rel.h | 3 +-
5 files changed, 135 insertions(+), 43 deletions(-)
diff --git a/src/backend/access/common/reloptions.c b/src/backend/access/common/reloptions.c
index 0af3fea68f..f543f11001 100644
--- a/src/backend/access/common/reloptions.c
+++ b/src/backend/access/common/reloptions.c
@@ -40,9 +40,9 @@
*
* To add an option:
*
- * (i) decide on a type (bool, integer, real, enum, string), name, default
- * value, upper and lower bounds (if applicable); for strings, consider a
- * validation routine.
+ * (i) decide on a type (bool, ternary, integer, real, enum, string), name,
+ * default value, upper and lower bounds (if applicable); for strings,
+ * consider a validation routine.
* (ii) add a record below (or use add_<type>_reloption).
* (iii) add it to the appropriate options struct (perhaps StdRdOptions)
* (iv) add it to the appropriate handling routine (perhaps
@@ -147,15 +147,6 @@ static relopt_bool boolRelOpts[] =
},
false
},
- {
- {
- "vacuum_truncate",
- "Enables vacuum to truncate empty pages at the end of this table",
- RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
- ShareUpdateExclusiveLock
- },
- true
- },
{
{
"deduplicate_items",
@@ -170,6 +161,21 @@ static relopt_bool boolRelOpts[] =
{{NULL}}
};
+static relopt_ternary ternaryRelOpts[] =
+{
+ {
+ {
+ "vacuum_truncate",
+ "Enables vacuum to truncate empty pages at the end of this table",
+ RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
+ ShareUpdateExclusiveLock
+ },
+ TERNARY_UNSET
+ },
+ /* list terminator */
+ {{NULL}}
+};
+
static relopt_int intRelOpts[] =
{
{
@@ -600,6 +606,13 @@ initialize_reloptions(void)
boolRelOpts[i].gen.lockmode));
j++;
}
+ for (i = 0; ternaryRelOpts[i].gen.name; i++)
+ {
+ Assert(DoLockModesConflict(ternaryRelOpts[i].gen.lockmode,
+ ternaryRelOpts[i].gen.lockmode));
+ j++;
+ }
+
for (i = 0; intRelOpts[i].gen.name; i++)
{
Assert(DoLockModesConflict(intRelOpts[i].gen.lockmode,
@@ -640,6 +653,14 @@ initialize_reloptions(void)
j++;
}
+ for (i = 0; ternaryRelOpts[i].gen.name; i++)
+ {
+ relOpts[j] = &ternaryRelOpts[i].gen;
+ relOpts[j]->type = RELOPT_TYPE_TERNARY;
+ relOpts[j]->namelen = strlen(relOpts[j]->name);
+ j++;
+ }
+
for (i = 0; intRelOpts[i].gen.name; i++)
{
relOpts[j] = &intRelOpts[i].gen;
@@ -800,6 +821,9 @@ allocate_reloption(bits32 kinds, int type, const char *name, const char *desc,
case RELOPT_TYPE_BOOL:
size = sizeof(relopt_bool);
break;
+ case RELOPT_TYPE_TERNARY:
+ size = sizeof(relopt_ternary);
+ break;
case RELOPT_TYPE_INT:
size = sizeof(relopt_int);
break;
@@ -883,6 +907,54 @@ add_local_bool_reloption(local_relopts *relopts, const char *name,
add_local_reloption(relopts, (relopt_gen *) newoption, offset);
}
+/*
+ * init_ternary_reloption
+ * Allocate and initialize a new ternary reloption
+ */
+static relopt_ternary *
+init_ternary_reloption(bits32 kinds, const char *name, const char *desc,
+ ternary default_val, LOCKMODE lockmode)
+{
+ relopt_ternary *newoption;
+
+ newoption = (relopt_ternary *) allocate_reloption(kinds,
+ RELOPT_TYPE_TERNARY, name, desc, lockmode);
+ newoption->default_val = default_val;
+
+ return newoption;
+}
+
+/*
+ * add_ternary_reloption
+ * Add a new ternary reloption
+ */
+void
+add_ternary_reloption(bits32 kinds, const char *name, const char *desc,
+ ternary default_val, LOCKMODE lockmode)
+{
+ relopt_ternary *newoption = init_ternary_reloption(kinds, name, desc,
+ default_val, lockmode);
+
+ add_reloption((relopt_gen *) newoption);
+}
+
+/*
+ * add_local_ternary_reloption
+ * Add a new ternary local reloption
+ *
+ * 'offset' is offset of ternary-typed field.
+ */
+void
+add_local_ternary_reloption(local_relopts *relopts, const char *name,
+ const char *desc, ternary default_val,
+ int offset)
+{
+ relopt_ternary *newoption = init_ternary_reloption(RELOPT_KIND_LOCAL,
+ name, desc,
+ default_val, 0);
+
+ add_local_reloption(relopts, (relopt_gen *) newoption, offset);
+}
/*
* init_real_reloption
@@ -1617,6 +1689,18 @@ parse_one_reloption(relopt_value *option, char *text_str, int text_len,
option->gen->name, value)));
}
break;
+ case RELOPT_TYPE_TERNARY:
+ {
+ bool b;
+ parsed = parse_bool(value, &b);
+ option->values.ternary_val = b ? TERNARY_TRUE : TERNARY_FALSE;
+ if (validate && !parsed)
+ ereport(ERROR,
+ (errcode(ERRCODE_INVALID_PARAMETER_VALUE),
+ errmsg("invalid value for ternary option \"%s\": %s",
+ option->gen->name, value)));
+ }
+ break;
case RELOPT_TYPE_INT:
{
relopt_int *optint = (relopt_int *) option->gen;
@@ -1780,17 +1864,6 @@ fillRelOptions(void *rdopts, Size basesize,
char *itempos = ((char *) rdopts) + elems[j].offset;
char *string_val;
- /*
- * If isset_offset is provided, store whether the reloption is
- * set there.
- */
- if (elems[j].isset_offset > 0)
- {
- char *setpos = ((char *) rdopts) + elems[j].isset_offset;
-
- *(bool *) setpos = options[i].isset;
- }
-
switch (options[i].gen->type)
{
case RELOPT_TYPE_BOOL:
@@ -1798,6 +1871,11 @@ fillRelOptions(void *rdopts, Size basesize,
options[i].values.bool_val :
((relopt_bool *) options[i].gen)->default_val;
break;
+ case RELOPT_TYPE_TERNARY:
+ *(ternary *) itempos = options[i].isset ?
+ options[i].values.ternary_val :
+ ((relopt_ternary *) options[i].gen)->default_val;
+ break;
case RELOPT_TYPE_INT:
*(int *) itempos = options[i].isset ?
options[i].values.int_val :
@@ -1912,8 +1990,8 @@ default_reloptions(Datum reloptions, bool validate, relopt_kind kind)
offsetof(StdRdOptions, parallel_workers)},
{"vacuum_index_cleanup", RELOPT_TYPE_ENUM,
offsetof(StdRdOptions, vacuum_index_cleanup)},
- {"vacuum_truncate", RELOPT_TYPE_BOOL,
- offsetof(StdRdOptions, vacuum_truncate), offsetof(StdRdOptions, vacuum_truncate_set)},
+ {"vacuum_truncate", RELOPT_TYPE_TERNARY,
+ offsetof(StdRdOptions, vacuum_truncate)},
{"vacuum_max_eager_freeze_failure_rate", RELOPT_TYPE_REAL,
offsetof(StdRdOptions, vacuum_max_eager_freeze_failure_rate)}
};
@@ -1993,7 +2071,6 @@ build_local_reloptions(local_relopts *relopts, Datum options, bool validate)
elems[i].optname = opt->option->name;
elems[i].opttype = opt->option->type;
elems[i].offset = opt->offset;
- elems[i].isset_offset = 0; /* not supported for local relopts yet */
i++;
}
diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c
index 733ef40ae7..977babff54 100644
--- a/src/backend/commands/vacuum.c
+++ b/src/backend/commands/vacuum.c
@@ -2221,9 +2221,9 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams params,
{
StdRdOptions *opts = (StdRdOptions *) rel->rd_options;
- if (opts && opts->vacuum_truncate_set)
+ if (opts && opts->vacuum_truncate != TERNARY_UNSET)
{
- if (opts->vacuum_truncate)
+ if (opts->vacuum_truncate == TERNARY_TRUE)
params.truncate = VACOPTVALUE_ENABLED;
else
params.truncate = VACOPTVALUE_DISABLED;
diff --git a/src/include/access/reloptions.h b/src/include/access/reloptions.h
index a604a4702c..a436697658 100644
--- a/src/include/access/reloptions.h
+++ b/src/include/access/reloptions.h
@@ -29,6 +29,7 @@
typedef enum relopt_type
{
RELOPT_TYPE_BOOL,
+ RELOPT_TYPE_TERNARY, /* on, off, unset */
RELOPT_TYPE_INT,
RELOPT_TYPE_REAL,
RELOPT_TYPE_ENUM,
@@ -80,6 +81,7 @@ typedef struct relopt_value
union
{
bool bool_val;
+ ternary ternary_val;
int int_val;
double real_val;
int enum_val;
@@ -94,6 +96,12 @@ typedef struct relopt_bool
bool default_val;
} relopt_bool;
+typedef struct relopt_rernary
+{
+ relopt_gen gen;
+ int default_val;
+} relopt_ternary;
+
typedef struct relopt_int
{
relopt_gen gen;
@@ -152,19 +160,6 @@ typedef struct
const char *optname; /* option's name */
relopt_type opttype; /* option's datatype */
int offset; /* offset of field in result struct */
-
- /*
- * isset_offset is an optional offset of a field in the result struct that
- * stores whether the option is explicitly set for the relation or if it
- * just picked up the default value. In most cases, this can be
- * accomplished by giving the reloption a special out-of-range default
- * value (e.g., some integer reloptions use -2), but this isn't always
- * possible. For example, a Boolean reloption cannot be given an
- * out-of-range default, so we need another way to discover the source of
- * its value. This offset is only used if given a value greater than
- * zero.
- */
- int isset_offset;
} relopt_parse_elt;
/* Local reloption definition */
@@ -195,6 +190,8 @@ typedef struct local_relopts
extern relopt_kind add_reloption_kind(void);
extern void add_bool_reloption(bits32 kinds, const char *name, const char *desc,
bool default_val, LOCKMODE lockmode);
+extern void add_ternary_reloption(bits32 kinds, const char *name,
+ const char *desc, int default_val, LOCKMODE lockmode);
extern void add_int_reloption(bits32 kinds, const char *name, const char *desc,
int default_val, int min_val, int max_val,
LOCKMODE lockmode);
@@ -214,6 +211,9 @@ extern void register_reloptions_validator(local_relopts *relopts,
extern void add_local_bool_reloption(local_relopts *relopts, const char *name,
const char *desc, bool default_val,
int offset);
+extern void add_local_ternary_reloption(local_relopts *relopts,
+ const char *name, const char *desc,
+ ternary default_val, int offset);
extern void add_local_int_reloption(local_relopts *relopts, const char *name,
const char *desc, int default_val,
int min_val, int max_val, int offset);
diff --git a/src/include/c.h b/src/include/c.h
index 39022f8a9d..9c59ccccf8 100644
--- a/src/include/c.h
+++ b/src/include/c.h
@@ -488,6 +488,22 @@ typedef void (*pg_funcptr_t) (void);
#include <stdbool.h>
+/*
+ * ternary
+ * Boolean value with an extrea "unset" option
+ *
+ * Ternary data type is used in relation options that can be "true", "false" or
+ * "unset". Since relation options are used deep inside the PostgreSQL code,
+ * this type is declared globally.
+*/
+
+typedef enum ternary
+{
+ TERNARY_FALSE = 0,
+ TERNARY_TRUE = 1,
+ TERNARY_UNSET = -1
+} ternary;
+
/* ----------------------------------------------------------------
* Section 3: standard system types
diff --git a/src/include/utils/rel.h b/src/include/utils/rel.h
index b552359915..7346d618f9 100644
--- a/src/include/utils/rel.h
+++ b/src/include/utils/rel.h
@@ -346,8 +346,7 @@ typedef struct StdRdOptions
bool user_catalog_table; /* use as an additional catalog relation */
int parallel_workers; /* max number of parallel workers */
StdRdOptIndexCleanup vacuum_index_cleanup; /* controls index vacuuming */
- bool vacuum_truncate; /* enables vacuum to truncate a relation */
- bool vacuum_truncate_set; /* whether vacuum_truncate is set */
+ ternary vacuum_truncate; /* enables vacuum to truncate a relation */
/*
* Fraction of pages in a relation that vacuum can eagerly scan and fail
--
2.39.2
--nextPart4155600.3ZeAukHxDK
Content-Disposition: attachment;
filename="v2a-0003-Add-alias-to-be-used-as-unset-state.patch"
Content-Transfer-Encoding: 7Bit
Content-Type: text/x-patch; charset="unicode-2-0-utf-8";
name="v2a-0003-Add-alias-to-be-used-as-unset-state.patch"
^ permalink raw reply [nested|flat] 868+ messages in thread
* [PATCH v2a 2/4] Introduce ternary reloptions
@ 2025-09-04 14:41 Nikolay Shaplov <[email protected]>
0 siblings, 0 replies; 868+ messages in thread
From: Nikolay Shaplov @ 2025-09-04 14:41 UTC (permalink / raw)
Introduce ternary reloption as a replacement for current `vacuum_truncate`
implementation. Remove `vacuum_truncate_set` additional flag and using
`TERNARY_UNSET` value instead.
---
src/backend/access/common/reloptions.c | 129 ++++++++++++++++++++-----
src/backend/commands/vacuum.c | 4 +-
src/include/access/reloptions.h | 26 ++---
src/include/c.h | 16 +++
src/include/utils/rel.h | 3 +-
5 files changed, 135 insertions(+), 43 deletions(-)
diff --git a/src/backend/access/common/reloptions.c b/src/backend/access/common/reloptions.c
index 0af3fea68f..f543f11001 100644
--- a/src/backend/access/common/reloptions.c
+++ b/src/backend/access/common/reloptions.c
@@ -40,9 +40,9 @@
*
* To add an option:
*
- * (i) decide on a type (bool, integer, real, enum, string), name, default
- * value, upper and lower bounds (if applicable); for strings, consider a
- * validation routine.
+ * (i) decide on a type (bool, ternary, integer, real, enum, string), name,
+ * default value, upper and lower bounds (if applicable); for strings,
+ * consider a validation routine.
* (ii) add a record below (or use add_<type>_reloption).
* (iii) add it to the appropriate options struct (perhaps StdRdOptions)
* (iv) add it to the appropriate handling routine (perhaps
@@ -147,15 +147,6 @@ static relopt_bool boolRelOpts[] =
},
false
},
- {
- {
- "vacuum_truncate",
- "Enables vacuum to truncate empty pages at the end of this table",
- RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
- ShareUpdateExclusiveLock
- },
- true
- },
{
{
"deduplicate_items",
@@ -170,6 +161,21 @@ static relopt_bool boolRelOpts[] =
{{NULL}}
};
+static relopt_ternary ternaryRelOpts[] =
+{
+ {
+ {
+ "vacuum_truncate",
+ "Enables vacuum to truncate empty pages at the end of this table",
+ RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
+ ShareUpdateExclusiveLock
+ },
+ TERNARY_UNSET
+ },
+ /* list terminator */
+ {{NULL}}
+};
+
static relopt_int intRelOpts[] =
{
{
@@ -600,6 +606,13 @@ initialize_reloptions(void)
boolRelOpts[i].gen.lockmode));
j++;
}
+ for (i = 0; ternaryRelOpts[i].gen.name; i++)
+ {
+ Assert(DoLockModesConflict(ternaryRelOpts[i].gen.lockmode,
+ ternaryRelOpts[i].gen.lockmode));
+ j++;
+ }
+
for (i = 0; intRelOpts[i].gen.name; i++)
{
Assert(DoLockModesConflict(intRelOpts[i].gen.lockmode,
@@ -640,6 +653,14 @@ initialize_reloptions(void)
j++;
}
+ for (i = 0; ternaryRelOpts[i].gen.name; i++)
+ {
+ relOpts[j] = &ternaryRelOpts[i].gen;
+ relOpts[j]->type = RELOPT_TYPE_TERNARY;
+ relOpts[j]->namelen = strlen(relOpts[j]->name);
+ j++;
+ }
+
for (i = 0; intRelOpts[i].gen.name; i++)
{
relOpts[j] = &intRelOpts[i].gen;
@@ -800,6 +821,9 @@ allocate_reloption(bits32 kinds, int type, const char *name, const char *desc,
case RELOPT_TYPE_BOOL:
size = sizeof(relopt_bool);
break;
+ case RELOPT_TYPE_TERNARY:
+ size = sizeof(relopt_ternary);
+ break;
case RELOPT_TYPE_INT:
size = sizeof(relopt_int);
break;
@@ -883,6 +907,54 @@ add_local_bool_reloption(local_relopts *relopts, const char *name,
add_local_reloption(relopts, (relopt_gen *) newoption, offset);
}
+/*
+ * init_ternary_reloption
+ * Allocate and initialize a new ternary reloption
+ */
+static relopt_ternary *
+init_ternary_reloption(bits32 kinds, const char *name, const char *desc,
+ ternary default_val, LOCKMODE lockmode)
+{
+ relopt_ternary *newoption;
+
+ newoption = (relopt_ternary *) allocate_reloption(kinds,
+ RELOPT_TYPE_TERNARY, name, desc, lockmode);
+ newoption->default_val = default_val;
+
+ return newoption;
+}
+
+/*
+ * add_ternary_reloption
+ * Add a new ternary reloption
+ */
+void
+add_ternary_reloption(bits32 kinds, const char *name, const char *desc,
+ ternary default_val, LOCKMODE lockmode)
+{
+ relopt_ternary *newoption = init_ternary_reloption(kinds, name, desc,
+ default_val, lockmode);
+
+ add_reloption((relopt_gen *) newoption);
+}
+
+/*
+ * add_local_ternary_reloption
+ * Add a new ternary local reloption
+ *
+ * 'offset' is offset of ternary-typed field.
+ */
+void
+add_local_ternary_reloption(local_relopts *relopts, const char *name,
+ const char *desc, ternary default_val,
+ int offset)
+{
+ relopt_ternary *newoption = init_ternary_reloption(RELOPT_KIND_LOCAL,
+ name, desc,
+ default_val, 0);
+
+ add_local_reloption(relopts, (relopt_gen *) newoption, offset);
+}
/*
* init_real_reloption
@@ -1617,6 +1689,18 @@ parse_one_reloption(relopt_value *option, char *text_str, int text_len,
option->gen->name, value)));
}
break;
+ case RELOPT_TYPE_TERNARY:
+ {
+ bool b;
+ parsed = parse_bool(value, &b);
+ option->values.ternary_val = b ? TERNARY_TRUE : TERNARY_FALSE;
+ if (validate && !parsed)
+ ereport(ERROR,
+ (errcode(ERRCODE_INVALID_PARAMETER_VALUE),
+ errmsg("invalid value for ternary option \"%s\": %s",
+ option->gen->name, value)));
+ }
+ break;
case RELOPT_TYPE_INT:
{
relopt_int *optint = (relopt_int *) option->gen;
@@ -1780,17 +1864,6 @@ fillRelOptions(void *rdopts, Size basesize,
char *itempos = ((char *) rdopts) + elems[j].offset;
char *string_val;
- /*
- * If isset_offset is provided, store whether the reloption is
- * set there.
- */
- if (elems[j].isset_offset > 0)
- {
- char *setpos = ((char *) rdopts) + elems[j].isset_offset;
-
- *(bool *) setpos = options[i].isset;
- }
-
switch (options[i].gen->type)
{
case RELOPT_TYPE_BOOL:
@@ -1798,6 +1871,11 @@ fillRelOptions(void *rdopts, Size basesize,
options[i].values.bool_val :
((relopt_bool *) options[i].gen)->default_val;
break;
+ case RELOPT_TYPE_TERNARY:
+ *(ternary *) itempos = options[i].isset ?
+ options[i].values.ternary_val :
+ ((relopt_ternary *) options[i].gen)->default_val;
+ break;
case RELOPT_TYPE_INT:
*(int *) itempos = options[i].isset ?
options[i].values.int_val :
@@ -1912,8 +1990,8 @@ default_reloptions(Datum reloptions, bool validate, relopt_kind kind)
offsetof(StdRdOptions, parallel_workers)},
{"vacuum_index_cleanup", RELOPT_TYPE_ENUM,
offsetof(StdRdOptions, vacuum_index_cleanup)},
- {"vacuum_truncate", RELOPT_TYPE_BOOL,
- offsetof(StdRdOptions, vacuum_truncate), offsetof(StdRdOptions, vacuum_truncate_set)},
+ {"vacuum_truncate", RELOPT_TYPE_TERNARY,
+ offsetof(StdRdOptions, vacuum_truncate)},
{"vacuum_max_eager_freeze_failure_rate", RELOPT_TYPE_REAL,
offsetof(StdRdOptions, vacuum_max_eager_freeze_failure_rate)}
};
@@ -1993,7 +2071,6 @@ build_local_reloptions(local_relopts *relopts, Datum options, bool validate)
elems[i].optname = opt->option->name;
elems[i].opttype = opt->option->type;
elems[i].offset = opt->offset;
- elems[i].isset_offset = 0; /* not supported for local relopts yet */
i++;
}
diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c
index 733ef40ae7..977babff54 100644
--- a/src/backend/commands/vacuum.c
+++ b/src/backend/commands/vacuum.c
@@ -2221,9 +2221,9 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams params,
{
StdRdOptions *opts = (StdRdOptions *) rel->rd_options;
- if (opts && opts->vacuum_truncate_set)
+ if (opts && opts->vacuum_truncate != TERNARY_UNSET)
{
- if (opts->vacuum_truncate)
+ if (opts->vacuum_truncate == TERNARY_TRUE)
params.truncate = VACOPTVALUE_ENABLED;
else
params.truncate = VACOPTVALUE_DISABLED;
diff --git a/src/include/access/reloptions.h b/src/include/access/reloptions.h
index a604a4702c..a436697658 100644
--- a/src/include/access/reloptions.h
+++ b/src/include/access/reloptions.h
@@ -29,6 +29,7 @@
typedef enum relopt_type
{
RELOPT_TYPE_BOOL,
+ RELOPT_TYPE_TERNARY, /* on, off, unset */
RELOPT_TYPE_INT,
RELOPT_TYPE_REAL,
RELOPT_TYPE_ENUM,
@@ -80,6 +81,7 @@ typedef struct relopt_value
union
{
bool bool_val;
+ ternary ternary_val;
int int_val;
double real_val;
int enum_val;
@@ -94,6 +96,12 @@ typedef struct relopt_bool
bool default_val;
} relopt_bool;
+typedef struct relopt_rernary
+{
+ relopt_gen gen;
+ int default_val;
+} relopt_ternary;
+
typedef struct relopt_int
{
relopt_gen gen;
@@ -152,19 +160,6 @@ typedef struct
const char *optname; /* option's name */
relopt_type opttype; /* option's datatype */
int offset; /* offset of field in result struct */
-
- /*
- * isset_offset is an optional offset of a field in the result struct that
- * stores whether the option is explicitly set for the relation or if it
- * just picked up the default value. In most cases, this can be
- * accomplished by giving the reloption a special out-of-range default
- * value (e.g., some integer reloptions use -2), but this isn't always
- * possible. For example, a Boolean reloption cannot be given an
- * out-of-range default, so we need another way to discover the source of
- * its value. This offset is only used if given a value greater than
- * zero.
- */
- int isset_offset;
} relopt_parse_elt;
/* Local reloption definition */
@@ -195,6 +190,8 @@ typedef struct local_relopts
extern relopt_kind add_reloption_kind(void);
extern void add_bool_reloption(bits32 kinds, const char *name, const char *desc,
bool default_val, LOCKMODE lockmode);
+extern void add_ternary_reloption(bits32 kinds, const char *name,
+ const char *desc, int default_val, LOCKMODE lockmode);
extern void add_int_reloption(bits32 kinds, const char *name, const char *desc,
int default_val, int min_val, int max_val,
LOCKMODE lockmode);
@@ -214,6 +211,9 @@ extern void register_reloptions_validator(local_relopts *relopts,
extern void add_local_bool_reloption(local_relopts *relopts, const char *name,
const char *desc, bool default_val,
int offset);
+extern void add_local_ternary_reloption(local_relopts *relopts,
+ const char *name, const char *desc,
+ ternary default_val, int offset);
extern void add_local_int_reloption(local_relopts *relopts, const char *name,
const char *desc, int default_val,
int min_val, int max_val, int offset);
diff --git a/src/include/c.h b/src/include/c.h
index 39022f8a9d..9c59ccccf8 100644
--- a/src/include/c.h
+++ b/src/include/c.h
@@ -488,6 +488,22 @@ typedef void (*pg_funcptr_t) (void);
#include <stdbool.h>
+/*
+ * ternary
+ * Boolean value with an extrea "unset" option
+ *
+ * Ternary data type is used in relation options that can be "true", "false" or
+ * "unset". Since relation options are used deep inside the PostgreSQL code,
+ * this type is declared globally.
+*/
+
+typedef enum ternary
+{
+ TERNARY_FALSE = 0,
+ TERNARY_TRUE = 1,
+ TERNARY_UNSET = -1
+} ternary;
+
/* ----------------------------------------------------------------
* Section 3: standard system types
diff --git a/src/include/utils/rel.h b/src/include/utils/rel.h
index b552359915..7346d618f9 100644
--- a/src/include/utils/rel.h
+++ b/src/include/utils/rel.h
@@ -346,8 +346,7 @@ typedef struct StdRdOptions
bool user_catalog_table; /* use as an additional catalog relation */
int parallel_workers; /* max number of parallel workers */
StdRdOptIndexCleanup vacuum_index_cleanup; /* controls index vacuuming */
- bool vacuum_truncate; /* enables vacuum to truncate a relation */
- bool vacuum_truncate_set; /* whether vacuum_truncate is set */
+ ternary vacuum_truncate; /* enables vacuum to truncate a relation */
/*
* Fraction of pages in a relation that vacuum can eagerly scan and fail
--
2.39.2
--nextPart4155600.3ZeAukHxDK
Content-Disposition: attachment;
filename="v2a-0003-Add-alias-to-be-used-as-unset-state.patch"
Content-Transfer-Encoding: 7Bit
Content-Type: text/x-patch; charset="unicode-2-0-utf-8";
name="v2a-0003-Add-alias-to-be-used-as-unset-state.patch"
^ permalink raw reply [nested|flat] 868+ messages in thread
* [PATCH v2a 2/4] Introduce ternary reloptions
@ 2025-09-04 14:41 Nikolay Shaplov <[email protected]>
0 siblings, 0 replies; 868+ messages in thread
From: Nikolay Shaplov @ 2025-09-04 14:41 UTC (permalink / raw)
Introduce ternary reloption as a replacement for current `vacuum_truncate`
implementation. Remove `vacuum_truncate_set` additional flag and using
`TERNARY_UNSET` value instead.
---
src/backend/access/common/reloptions.c | 129 ++++++++++++++++++++-----
src/backend/commands/vacuum.c | 4 +-
src/include/access/reloptions.h | 26 ++---
src/include/c.h | 16 +++
src/include/utils/rel.h | 3 +-
5 files changed, 135 insertions(+), 43 deletions(-)
diff --git a/src/backend/access/common/reloptions.c b/src/backend/access/common/reloptions.c
index 0af3fea68f..f543f11001 100644
--- a/src/backend/access/common/reloptions.c
+++ b/src/backend/access/common/reloptions.c
@@ -40,9 +40,9 @@
*
* To add an option:
*
- * (i) decide on a type (bool, integer, real, enum, string), name, default
- * value, upper and lower bounds (if applicable); for strings, consider a
- * validation routine.
+ * (i) decide on a type (bool, ternary, integer, real, enum, string), name,
+ * default value, upper and lower bounds (if applicable); for strings,
+ * consider a validation routine.
* (ii) add a record below (or use add_<type>_reloption).
* (iii) add it to the appropriate options struct (perhaps StdRdOptions)
* (iv) add it to the appropriate handling routine (perhaps
@@ -147,15 +147,6 @@ static relopt_bool boolRelOpts[] =
},
false
},
- {
- {
- "vacuum_truncate",
- "Enables vacuum to truncate empty pages at the end of this table",
- RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
- ShareUpdateExclusiveLock
- },
- true
- },
{
{
"deduplicate_items",
@@ -170,6 +161,21 @@ static relopt_bool boolRelOpts[] =
{{NULL}}
};
+static relopt_ternary ternaryRelOpts[] =
+{
+ {
+ {
+ "vacuum_truncate",
+ "Enables vacuum to truncate empty pages at the end of this table",
+ RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
+ ShareUpdateExclusiveLock
+ },
+ TERNARY_UNSET
+ },
+ /* list terminator */
+ {{NULL}}
+};
+
static relopt_int intRelOpts[] =
{
{
@@ -600,6 +606,13 @@ initialize_reloptions(void)
boolRelOpts[i].gen.lockmode));
j++;
}
+ for (i = 0; ternaryRelOpts[i].gen.name; i++)
+ {
+ Assert(DoLockModesConflict(ternaryRelOpts[i].gen.lockmode,
+ ternaryRelOpts[i].gen.lockmode));
+ j++;
+ }
+
for (i = 0; intRelOpts[i].gen.name; i++)
{
Assert(DoLockModesConflict(intRelOpts[i].gen.lockmode,
@@ -640,6 +653,14 @@ initialize_reloptions(void)
j++;
}
+ for (i = 0; ternaryRelOpts[i].gen.name; i++)
+ {
+ relOpts[j] = &ternaryRelOpts[i].gen;
+ relOpts[j]->type = RELOPT_TYPE_TERNARY;
+ relOpts[j]->namelen = strlen(relOpts[j]->name);
+ j++;
+ }
+
for (i = 0; intRelOpts[i].gen.name; i++)
{
relOpts[j] = &intRelOpts[i].gen;
@@ -800,6 +821,9 @@ allocate_reloption(bits32 kinds, int type, const char *name, const char *desc,
case RELOPT_TYPE_BOOL:
size = sizeof(relopt_bool);
break;
+ case RELOPT_TYPE_TERNARY:
+ size = sizeof(relopt_ternary);
+ break;
case RELOPT_TYPE_INT:
size = sizeof(relopt_int);
break;
@@ -883,6 +907,54 @@ add_local_bool_reloption(local_relopts *relopts, const char *name,
add_local_reloption(relopts, (relopt_gen *) newoption, offset);
}
+/*
+ * init_ternary_reloption
+ * Allocate and initialize a new ternary reloption
+ */
+static relopt_ternary *
+init_ternary_reloption(bits32 kinds, const char *name, const char *desc,
+ ternary default_val, LOCKMODE lockmode)
+{
+ relopt_ternary *newoption;
+
+ newoption = (relopt_ternary *) allocate_reloption(kinds,
+ RELOPT_TYPE_TERNARY, name, desc, lockmode);
+ newoption->default_val = default_val;
+
+ return newoption;
+}
+
+/*
+ * add_ternary_reloption
+ * Add a new ternary reloption
+ */
+void
+add_ternary_reloption(bits32 kinds, const char *name, const char *desc,
+ ternary default_val, LOCKMODE lockmode)
+{
+ relopt_ternary *newoption = init_ternary_reloption(kinds, name, desc,
+ default_val, lockmode);
+
+ add_reloption((relopt_gen *) newoption);
+}
+
+/*
+ * add_local_ternary_reloption
+ * Add a new ternary local reloption
+ *
+ * 'offset' is offset of ternary-typed field.
+ */
+void
+add_local_ternary_reloption(local_relopts *relopts, const char *name,
+ const char *desc, ternary default_val,
+ int offset)
+{
+ relopt_ternary *newoption = init_ternary_reloption(RELOPT_KIND_LOCAL,
+ name, desc,
+ default_val, 0);
+
+ add_local_reloption(relopts, (relopt_gen *) newoption, offset);
+}
/*
* init_real_reloption
@@ -1617,6 +1689,18 @@ parse_one_reloption(relopt_value *option, char *text_str, int text_len,
option->gen->name, value)));
}
break;
+ case RELOPT_TYPE_TERNARY:
+ {
+ bool b;
+ parsed = parse_bool(value, &b);
+ option->values.ternary_val = b ? TERNARY_TRUE : TERNARY_FALSE;
+ if (validate && !parsed)
+ ereport(ERROR,
+ (errcode(ERRCODE_INVALID_PARAMETER_VALUE),
+ errmsg("invalid value for ternary option \"%s\": %s",
+ option->gen->name, value)));
+ }
+ break;
case RELOPT_TYPE_INT:
{
relopt_int *optint = (relopt_int *) option->gen;
@@ -1780,17 +1864,6 @@ fillRelOptions(void *rdopts, Size basesize,
char *itempos = ((char *) rdopts) + elems[j].offset;
char *string_val;
- /*
- * If isset_offset is provided, store whether the reloption is
- * set there.
- */
- if (elems[j].isset_offset > 0)
- {
- char *setpos = ((char *) rdopts) + elems[j].isset_offset;
-
- *(bool *) setpos = options[i].isset;
- }
-
switch (options[i].gen->type)
{
case RELOPT_TYPE_BOOL:
@@ -1798,6 +1871,11 @@ fillRelOptions(void *rdopts, Size basesize,
options[i].values.bool_val :
((relopt_bool *) options[i].gen)->default_val;
break;
+ case RELOPT_TYPE_TERNARY:
+ *(ternary *) itempos = options[i].isset ?
+ options[i].values.ternary_val :
+ ((relopt_ternary *) options[i].gen)->default_val;
+ break;
case RELOPT_TYPE_INT:
*(int *) itempos = options[i].isset ?
options[i].values.int_val :
@@ -1912,8 +1990,8 @@ default_reloptions(Datum reloptions, bool validate, relopt_kind kind)
offsetof(StdRdOptions, parallel_workers)},
{"vacuum_index_cleanup", RELOPT_TYPE_ENUM,
offsetof(StdRdOptions, vacuum_index_cleanup)},
- {"vacuum_truncate", RELOPT_TYPE_BOOL,
- offsetof(StdRdOptions, vacuum_truncate), offsetof(StdRdOptions, vacuum_truncate_set)},
+ {"vacuum_truncate", RELOPT_TYPE_TERNARY,
+ offsetof(StdRdOptions, vacuum_truncate)},
{"vacuum_max_eager_freeze_failure_rate", RELOPT_TYPE_REAL,
offsetof(StdRdOptions, vacuum_max_eager_freeze_failure_rate)}
};
@@ -1993,7 +2071,6 @@ build_local_reloptions(local_relopts *relopts, Datum options, bool validate)
elems[i].optname = opt->option->name;
elems[i].opttype = opt->option->type;
elems[i].offset = opt->offset;
- elems[i].isset_offset = 0; /* not supported for local relopts yet */
i++;
}
diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c
index 733ef40ae7..977babff54 100644
--- a/src/backend/commands/vacuum.c
+++ b/src/backend/commands/vacuum.c
@@ -2221,9 +2221,9 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams params,
{
StdRdOptions *opts = (StdRdOptions *) rel->rd_options;
- if (opts && opts->vacuum_truncate_set)
+ if (opts && opts->vacuum_truncate != TERNARY_UNSET)
{
- if (opts->vacuum_truncate)
+ if (opts->vacuum_truncate == TERNARY_TRUE)
params.truncate = VACOPTVALUE_ENABLED;
else
params.truncate = VACOPTVALUE_DISABLED;
diff --git a/src/include/access/reloptions.h b/src/include/access/reloptions.h
index a604a4702c..a436697658 100644
--- a/src/include/access/reloptions.h
+++ b/src/include/access/reloptions.h
@@ -29,6 +29,7 @@
typedef enum relopt_type
{
RELOPT_TYPE_BOOL,
+ RELOPT_TYPE_TERNARY, /* on, off, unset */
RELOPT_TYPE_INT,
RELOPT_TYPE_REAL,
RELOPT_TYPE_ENUM,
@@ -80,6 +81,7 @@ typedef struct relopt_value
union
{
bool bool_val;
+ ternary ternary_val;
int int_val;
double real_val;
int enum_val;
@@ -94,6 +96,12 @@ typedef struct relopt_bool
bool default_val;
} relopt_bool;
+typedef struct relopt_rernary
+{
+ relopt_gen gen;
+ int default_val;
+} relopt_ternary;
+
typedef struct relopt_int
{
relopt_gen gen;
@@ -152,19 +160,6 @@ typedef struct
const char *optname; /* option's name */
relopt_type opttype; /* option's datatype */
int offset; /* offset of field in result struct */
-
- /*
- * isset_offset is an optional offset of a field in the result struct that
- * stores whether the option is explicitly set for the relation or if it
- * just picked up the default value. In most cases, this can be
- * accomplished by giving the reloption a special out-of-range default
- * value (e.g., some integer reloptions use -2), but this isn't always
- * possible. For example, a Boolean reloption cannot be given an
- * out-of-range default, so we need another way to discover the source of
- * its value. This offset is only used if given a value greater than
- * zero.
- */
- int isset_offset;
} relopt_parse_elt;
/* Local reloption definition */
@@ -195,6 +190,8 @@ typedef struct local_relopts
extern relopt_kind add_reloption_kind(void);
extern void add_bool_reloption(bits32 kinds, const char *name, const char *desc,
bool default_val, LOCKMODE lockmode);
+extern void add_ternary_reloption(bits32 kinds, const char *name,
+ const char *desc, int default_val, LOCKMODE lockmode);
extern void add_int_reloption(bits32 kinds, const char *name, const char *desc,
int default_val, int min_val, int max_val,
LOCKMODE lockmode);
@@ -214,6 +211,9 @@ extern void register_reloptions_validator(local_relopts *relopts,
extern void add_local_bool_reloption(local_relopts *relopts, const char *name,
const char *desc, bool default_val,
int offset);
+extern void add_local_ternary_reloption(local_relopts *relopts,
+ const char *name, const char *desc,
+ ternary default_val, int offset);
extern void add_local_int_reloption(local_relopts *relopts, const char *name,
const char *desc, int default_val,
int min_val, int max_val, int offset);
diff --git a/src/include/c.h b/src/include/c.h
index 39022f8a9d..9c59ccccf8 100644
--- a/src/include/c.h
+++ b/src/include/c.h
@@ -488,6 +488,22 @@ typedef void (*pg_funcptr_t) (void);
#include <stdbool.h>
+/*
+ * ternary
+ * Boolean value with an extrea "unset" option
+ *
+ * Ternary data type is used in relation options that can be "true", "false" or
+ * "unset". Since relation options are used deep inside the PostgreSQL code,
+ * this type is declared globally.
+*/
+
+typedef enum ternary
+{
+ TERNARY_FALSE = 0,
+ TERNARY_TRUE = 1,
+ TERNARY_UNSET = -1
+} ternary;
+
/* ----------------------------------------------------------------
* Section 3: standard system types
diff --git a/src/include/utils/rel.h b/src/include/utils/rel.h
index b552359915..7346d618f9 100644
--- a/src/include/utils/rel.h
+++ b/src/include/utils/rel.h
@@ -346,8 +346,7 @@ typedef struct StdRdOptions
bool user_catalog_table; /* use as an additional catalog relation */
int parallel_workers; /* max number of parallel workers */
StdRdOptIndexCleanup vacuum_index_cleanup; /* controls index vacuuming */
- bool vacuum_truncate; /* enables vacuum to truncate a relation */
- bool vacuum_truncate_set; /* whether vacuum_truncate is set */
+ ternary vacuum_truncate; /* enables vacuum to truncate a relation */
/*
* Fraction of pages in a relation that vacuum can eagerly scan and fail
--
2.39.2
--nextPart4155600.3ZeAukHxDK
Content-Disposition: attachment;
filename="v2a-0003-Add-alias-to-be-used-as-unset-state.patch"
Content-Transfer-Encoding: 7Bit
Content-Type: text/x-patch; charset="unicode-2-0-utf-8";
name="v2a-0003-Add-alias-to-be-used-as-unset-state.patch"
^ permalink raw reply [nested|flat] 868+ messages in thread
* [PATCH v2 2/4] Introduce ternary reloptions
@ 2025-09-04 14:41 Nikolay Shaplov <[email protected]>
0 siblings, 0 replies; 868+ messages in thread
From: Nikolay Shaplov @ 2025-09-04 14:41 UTC (permalink / raw)
Introduce ternary reloption as a replacement for current `vacuum_truncate`
implementation. Remove `vacuum_truncate_set` additional flag and using
`TERNARY_UNSET` value instead.
---
src/backend/access/common/reloptions.c | 129 ++++++++++++++++++++-----
src/backend/commands/vacuum.c | 4 +-
src/include/access/reloptions.h | 26 ++---
src/include/c.h | 16 +++
src/include/utils/rel.h | 3 +-
5 files changed, 135 insertions(+), 43 deletions(-)
diff --git a/src/backend/access/common/reloptions.c b/src/backend/access/common/reloptions.c
index 0af3fea68f..f543f11001 100644
--- a/src/backend/access/common/reloptions.c
+++ b/src/backend/access/common/reloptions.c
@@ -40,9 +40,9 @@
*
* To add an option:
*
- * (i) decide on a type (bool, integer, real, enum, string), name, default
- * value, upper and lower bounds (if applicable); for strings, consider a
- * validation routine.
+ * (i) decide on a type (bool, ternary, integer, real, enum, string), name,
+ * default value, upper and lower bounds (if applicable); for strings,
+ * consider a validation routine.
* (ii) add a record below (or use add_<type>_reloption).
* (iii) add it to the appropriate options struct (perhaps StdRdOptions)
* (iv) add it to the appropriate handling routine (perhaps
@@ -147,15 +147,6 @@ static relopt_bool boolRelOpts[] =
},
false
},
- {
- {
- "vacuum_truncate",
- "Enables vacuum to truncate empty pages at the end of this table",
- RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
- ShareUpdateExclusiveLock
- },
- true
- },
{
{
"deduplicate_items",
@@ -170,6 +161,21 @@ static relopt_bool boolRelOpts[] =
{{NULL}}
};
+static relopt_ternary ternaryRelOpts[] =
+{
+ {
+ {
+ "vacuum_truncate",
+ "Enables vacuum to truncate empty pages at the end of this table",
+ RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
+ ShareUpdateExclusiveLock
+ },
+ TERNARY_UNSET
+ },
+ /* list terminator */
+ {{NULL}}
+};
+
static relopt_int intRelOpts[] =
{
{
@@ -600,6 +606,13 @@ initialize_reloptions(void)
boolRelOpts[i].gen.lockmode));
j++;
}
+ for (i = 0; ternaryRelOpts[i].gen.name; i++)
+ {
+ Assert(DoLockModesConflict(ternaryRelOpts[i].gen.lockmode,
+ ternaryRelOpts[i].gen.lockmode));
+ j++;
+ }
+
for (i = 0; intRelOpts[i].gen.name; i++)
{
Assert(DoLockModesConflict(intRelOpts[i].gen.lockmode,
@@ -640,6 +653,14 @@ initialize_reloptions(void)
j++;
}
+ for (i = 0; ternaryRelOpts[i].gen.name; i++)
+ {
+ relOpts[j] = &ternaryRelOpts[i].gen;
+ relOpts[j]->type = RELOPT_TYPE_TERNARY;
+ relOpts[j]->namelen = strlen(relOpts[j]->name);
+ j++;
+ }
+
for (i = 0; intRelOpts[i].gen.name; i++)
{
relOpts[j] = &intRelOpts[i].gen;
@@ -800,6 +821,9 @@ allocate_reloption(bits32 kinds, int type, const char *name, const char *desc,
case RELOPT_TYPE_BOOL:
size = sizeof(relopt_bool);
break;
+ case RELOPT_TYPE_TERNARY:
+ size = sizeof(relopt_ternary);
+ break;
case RELOPT_TYPE_INT:
size = sizeof(relopt_int);
break;
@@ -883,6 +907,54 @@ add_local_bool_reloption(local_relopts *relopts, const char *name,
add_local_reloption(relopts, (relopt_gen *) newoption, offset);
}
+/*
+ * init_ternary_reloption
+ * Allocate and initialize a new ternary reloption
+ */
+static relopt_ternary *
+init_ternary_reloption(bits32 kinds, const char *name, const char *desc,
+ ternary default_val, LOCKMODE lockmode)
+{
+ relopt_ternary *newoption;
+
+ newoption = (relopt_ternary *) allocate_reloption(kinds,
+ RELOPT_TYPE_TERNARY, name, desc, lockmode);
+ newoption->default_val = default_val;
+
+ return newoption;
+}
+
+/*
+ * add_ternary_reloption
+ * Add a new ternary reloption
+ */
+void
+add_ternary_reloption(bits32 kinds, const char *name, const char *desc,
+ ternary default_val, LOCKMODE lockmode)
+{
+ relopt_ternary *newoption = init_ternary_reloption(kinds, name, desc,
+ default_val, lockmode);
+
+ add_reloption((relopt_gen *) newoption);
+}
+
+/*
+ * add_local_ternary_reloption
+ * Add a new ternary local reloption
+ *
+ * 'offset' is offset of ternary-typed field.
+ */
+void
+add_local_ternary_reloption(local_relopts *relopts, const char *name,
+ const char *desc, ternary default_val,
+ int offset)
+{
+ relopt_ternary *newoption = init_ternary_reloption(RELOPT_KIND_LOCAL,
+ name, desc,
+ default_val, 0);
+
+ add_local_reloption(relopts, (relopt_gen *) newoption, offset);
+}
/*
* init_real_reloption
@@ -1617,6 +1689,18 @@ parse_one_reloption(relopt_value *option, char *text_str, int text_len,
option->gen->name, value)));
}
break;
+ case RELOPT_TYPE_TERNARY:
+ {
+ bool b;
+ parsed = parse_bool(value, &b);
+ option->values.ternary_val = b ? TERNARY_TRUE : TERNARY_FALSE;
+ if (validate && !parsed)
+ ereport(ERROR,
+ (errcode(ERRCODE_INVALID_PARAMETER_VALUE),
+ errmsg("invalid value for ternary option \"%s\": %s",
+ option->gen->name, value)));
+ }
+ break;
case RELOPT_TYPE_INT:
{
relopt_int *optint = (relopt_int *) option->gen;
@@ -1780,17 +1864,6 @@ fillRelOptions(void *rdopts, Size basesize,
char *itempos = ((char *) rdopts) + elems[j].offset;
char *string_val;
- /*
- * If isset_offset is provided, store whether the reloption is
- * set there.
- */
- if (elems[j].isset_offset > 0)
- {
- char *setpos = ((char *) rdopts) + elems[j].isset_offset;
-
- *(bool *) setpos = options[i].isset;
- }
-
switch (options[i].gen->type)
{
case RELOPT_TYPE_BOOL:
@@ -1798,6 +1871,11 @@ fillRelOptions(void *rdopts, Size basesize,
options[i].values.bool_val :
((relopt_bool *) options[i].gen)->default_val;
break;
+ case RELOPT_TYPE_TERNARY:
+ *(ternary *) itempos = options[i].isset ?
+ options[i].values.ternary_val :
+ ((relopt_ternary *) options[i].gen)->default_val;
+ break;
case RELOPT_TYPE_INT:
*(int *) itempos = options[i].isset ?
options[i].values.int_val :
@@ -1912,8 +1990,8 @@ default_reloptions(Datum reloptions, bool validate, relopt_kind kind)
offsetof(StdRdOptions, parallel_workers)},
{"vacuum_index_cleanup", RELOPT_TYPE_ENUM,
offsetof(StdRdOptions, vacuum_index_cleanup)},
- {"vacuum_truncate", RELOPT_TYPE_BOOL,
- offsetof(StdRdOptions, vacuum_truncate), offsetof(StdRdOptions, vacuum_truncate_set)},
+ {"vacuum_truncate", RELOPT_TYPE_TERNARY,
+ offsetof(StdRdOptions, vacuum_truncate)},
{"vacuum_max_eager_freeze_failure_rate", RELOPT_TYPE_REAL,
offsetof(StdRdOptions, vacuum_max_eager_freeze_failure_rate)}
};
@@ -1993,7 +2071,6 @@ build_local_reloptions(local_relopts *relopts, Datum options, bool validate)
elems[i].optname = opt->option->name;
elems[i].opttype = opt->option->type;
elems[i].offset = opt->offset;
- elems[i].isset_offset = 0; /* not supported for local relopts yet */
i++;
}
diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c
index 733ef40ae7..977babff54 100644
--- a/src/backend/commands/vacuum.c
+++ b/src/backend/commands/vacuum.c
@@ -2221,9 +2221,9 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams params,
{
StdRdOptions *opts = (StdRdOptions *) rel->rd_options;
- if (opts && opts->vacuum_truncate_set)
+ if (opts && opts->vacuum_truncate != TERNARY_UNSET)
{
- if (opts->vacuum_truncate)
+ if (opts->vacuum_truncate == TERNARY_TRUE)
params.truncate = VACOPTVALUE_ENABLED;
else
params.truncate = VACOPTVALUE_DISABLED;
diff --git a/src/include/access/reloptions.h b/src/include/access/reloptions.h
index a604a4702c..a436697658 100644
--- a/src/include/access/reloptions.h
+++ b/src/include/access/reloptions.h
@@ -29,6 +29,7 @@
typedef enum relopt_type
{
RELOPT_TYPE_BOOL,
+ RELOPT_TYPE_TERNARY, /* on, off, unset */
RELOPT_TYPE_INT,
RELOPT_TYPE_REAL,
RELOPT_TYPE_ENUM,
@@ -80,6 +81,7 @@ typedef struct relopt_value
union
{
bool bool_val;
+ ternary ternary_val;
int int_val;
double real_val;
int enum_val;
@@ -94,6 +96,12 @@ typedef struct relopt_bool
bool default_val;
} relopt_bool;
+typedef struct relopt_rernary
+{
+ relopt_gen gen;
+ int default_val;
+} relopt_ternary;
+
typedef struct relopt_int
{
relopt_gen gen;
@@ -152,19 +160,6 @@ typedef struct
const char *optname; /* option's name */
relopt_type opttype; /* option's datatype */
int offset; /* offset of field in result struct */
-
- /*
- * isset_offset is an optional offset of a field in the result struct that
- * stores whether the option is explicitly set for the relation or if it
- * just picked up the default value. In most cases, this can be
- * accomplished by giving the reloption a special out-of-range default
- * value (e.g., some integer reloptions use -2), but this isn't always
- * possible. For example, a Boolean reloption cannot be given an
- * out-of-range default, so we need another way to discover the source of
- * its value. This offset is only used if given a value greater than
- * zero.
- */
- int isset_offset;
} relopt_parse_elt;
/* Local reloption definition */
@@ -195,6 +190,8 @@ typedef struct local_relopts
extern relopt_kind add_reloption_kind(void);
extern void add_bool_reloption(bits32 kinds, const char *name, const char *desc,
bool default_val, LOCKMODE lockmode);
+extern void add_ternary_reloption(bits32 kinds, const char *name,
+ const char *desc, int default_val, LOCKMODE lockmode);
extern void add_int_reloption(bits32 kinds, const char *name, const char *desc,
int default_val, int min_val, int max_val,
LOCKMODE lockmode);
@@ -214,6 +211,9 @@ extern void register_reloptions_validator(local_relopts *relopts,
extern void add_local_bool_reloption(local_relopts *relopts, const char *name,
const char *desc, bool default_val,
int offset);
+extern void add_local_ternary_reloption(local_relopts *relopts,
+ const char *name, const char *desc,
+ ternary default_val, int offset);
extern void add_local_int_reloption(local_relopts *relopts, const char *name,
const char *desc, int default_val,
int min_val, int max_val, int offset);
diff --git a/src/include/c.h b/src/include/c.h
index 39022f8a9d..9c59ccccf8 100644
--- a/src/include/c.h
+++ b/src/include/c.h
@@ -488,6 +488,22 @@ typedef void (*pg_funcptr_t) (void);
#include <stdbool.h>
+/*
+ * ternary
+ * Boolean value with an extrea "unset" option
+ *
+ * Ternary data type is used in relation options that can be "true", "false" or
+ * "unset". Since relation options are used deep inside the PostgreSQL code,
+ * this type is declared globally.
+*/
+
+typedef enum ternary
+{
+ TERNARY_FALSE = 0,
+ TERNARY_TRUE = 1,
+ TERNARY_UNSET = -1
+} ternary;
+
/* ----------------------------------------------------------------
* Section 3: standard system types
diff --git a/src/include/utils/rel.h b/src/include/utils/rel.h
index b552359915..7346d618f9 100644
--- a/src/include/utils/rel.h
+++ b/src/include/utils/rel.h
@@ -346,8 +346,7 @@ typedef struct StdRdOptions
bool user_catalog_table; /* use as an additional catalog relation */
int parallel_workers; /* max number of parallel workers */
StdRdOptIndexCleanup vacuum_index_cleanup; /* controls index vacuuming */
- bool vacuum_truncate; /* enables vacuum to truncate a relation */
- bool vacuum_truncate_set; /* whether vacuum_truncate is set */
+ ternary vacuum_truncate; /* enables vacuum to truncate a relation */
/*
* Fraction of pages in a relation that vacuum can eagerly scan and fail
--
2.39.2
--nextPart5129932.5fSG56mABF
Content-Disposition: attachment;
filename="v2-0003-Add-alias-to-be-used-as-unset-state.patch"
Content-Transfer-Encoding: 7Bit
Content-Type: text/x-patch; charset="unicode-2-0-utf-8";
name="v2-0003-Add-alias-to-be-used-as-unset-state.patch"
^ permalink raw reply [nested|flat] 868+ messages in thread
* [PATCH v2a 2/4] Introduce ternary reloptions
@ 2025-09-04 14:41 Nikolay Shaplov <[email protected]>
0 siblings, 0 replies; 868+ messages in thread
From: Nikolay Shaplov @ 2025-09-04 14:41 UTC (permalink / raw)
Introduce ternary reloption as a replacement for current `vacuum_truncate`
implementation. Remove `vacuum_truncate_set` additional flag and using
`TERNARY_UNSET` value instead.
---
src/backend/access/common/reloptions.c | 129 ++++++++++++++++++++-----
src/backend/commands/vacuum.c | 4 +-
src/include/access/reloptions.h | 26 ++---
src/include/c.h | 16 +++
src/include/utils/rel.h | 3 +-
5 files changed, 135 insertions(+), 43 deletions(-)
diff --git a/src/backend/access/common/reloptions.c b/src/backend/access/common/reloptions.c
index 0af3fea68f..f543f11001 100644
--- a/src/backend/access/common/reloptions.c
+++ b/src/backend/access/common/reloptions.c
@@ -40,9 +40,9 @@
*
* To add an option:
*
- * (i) decide on a type (bool, integer, real, enum, string), name, default
- * value, upper and lower bounds (if applicable); for strings, consider a
- * validation routine.
+ * (i) decide on a type (bool, ternary, integer, real, enum, string), name,
+ * default value, upper and lower bounds (if applicable); for strings,
+ * consider a validation routine.
* (ii) add a record below (or use add_<type>_reloption).
* (iii) add it to the appropriate options struct (perhaps StdRdOptions)
* (iv) add it to the appropriate handling routine (perhaps
@@ -147,15 +147,6 @@ static relopt_bool boolRelOpts[] =
},
false
},
- {
- {
- "vacuum_truncate",
- "Enables vacuum to truncate empty pages at the end of this table",
- RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
- ShareUpdateExclusiveLock
- },
- true
- },
{
{
"deduplicate_items",
@@ -170,6 +161,21 @@ static relopt_bool boolRelOpts[] =
{{NULL}}
};
+static relopt_ternary ternaryRelOpts[] =
+{
+ {
+ {
+ "vacuum_truncate",
+ "Enables vacuum to truncate empty pages at the end of this table",
+ RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
+ ShareUpdateExclusiveLock
+ },
+ TERNARY_UNSET
+ },
+ /* list terminator */
+ {{NULL}}
+};
+
static relopt_int intRelOpts[] =
{
{
@@ -600,6 +606,13 @@ initialize_reloptions(void)
boolRelOpts[i].gen.lockmode));
j++;
}
+ for (i = 0; ternaryRelOpts[i].gen.name; i++)
+ {
+ Assert(DoLockModesConflict(ternaryRelOpts[i].gen.lockmode,
+ ternaryRelOpts[i].gen.lockmode));
+ j++;
+ }
+
for (i = 0; intRelOpts[i].gen.name; i++)
{
Assert(DoLockModesConflict(intRelOpts[i].gen.lockmode,
@@ -640,6 +653,14 @@ initialize_reloptions(void)
j++;
}
+ for (i = 0; ternaryRelOpts[i].gen.name; i++)
+ {
+ relOpts[j] = &ternaryRelOpts[i].gen;
+ relOpts[j]->type = RELOPT_TYPE_TERNARY;
+ relOpts[j]->namelen = strlen(relOpts[j]->name);
+ j++;
+ }
+
for (i = 0; intRelOpts[i].gen.name; i++)
{
relOpts[j] = &intRelOpts[i].gen;
@@ -800,6 +821,9 @@ allocate_reloption(bits32 kinds, int type, const char *name, const char *desc,
case RELOPT_TYPE_BOOL:
size = sizeof(relopt_bool);
break;
+ case RELOPT_TYPE_TERNARY:
+ size = sizeof(relopt_ternary);
+ break;
case RELOPT_TYPE_INT:
size = sizeof(relopt_int);
break;
@@ -883,6 +907,54 @@ add_local_bool_reloption(local_relopts *relopts, const char *name,
add_local_reloption(relopts, (relopt_gen *) newoption, offset);
}
+/*
+ * init_ternary_reloption
+ * Allocate and initialize a new ternary reloption
+ */
+static relopt_ternary *
+init_ternary_reloption(bits32 kinds, const char *name, const char *desc,
+ ternary default_val, LOCKMODE lockmode)
+{
+ relopt_ternary *newoption;
+
+ newoption = (relopt_ternary *) allocate_reloption(kinds,
+ RELOPT_TYPE_TERNARY, name, desc, lockmode);
+ newoption->default_val = default_val;
+
+ return newoption;
+}
+
+/*
+ * add_ternary_reloption
+ * Add a new ternary reloption
+ */
+void
+add_ternary_reloption(bits32 kinds, const char *name, const char *desc,
+ ternary default_val, LOCKMODE lockmode)
+{
+ relopt_ternary *newoption = init_ternary_reloption(kinds, name, desc,
+ default_val, lockmode);
+
+ add_reloption((relopt_gen *) newoption);
+}
+
+/*
+ * add_local_ternary_reloption
+ * Add a new ternary local reloption
+ *
+ * 'offset' is offset of ternary-typed field.
+ */
+void
+add_local_ternary_reloption(local_relopts *relopts, const char *name,
+ const char *desc, ternary default_val,
+ int offset)
+{
+ relopt_ternary *newoption = init_ternary_reloption(RELOPT_KIND_LOCAL,
+ name, desc,
+ default_val, 0);
+
+ add_local_reloption(relopts, (relopt_gen *) newoption, offset);
+}
/*
* init_real_reloption
@@ -1617,6 +1689,18 @@ parse_one_reloption(relopt_value *option, char *text_str, int text_len,
option->gen->name, value)));
}
break;
+ case RELOPT_TYPE_TERNARY:
+ {
+ bool b;
+ parsed = parse_bool(value, &b);
+ option->values.ternary_val = b ? TERNARY_TRUE : TERNARY_FALSE;
+ if (validate && !parsed)
+ ereport(ERROR,
+ (errcode(ERRCODE_INVALID_PARAMETER_VALUE),
+ errmsg("invalid value for ternary option \"%s\": %s",
+ option->gen->name, value)));
+ }
+ break;
case RELOPT_TYPE_INT:
{
relopt_int *optint = (relopt_int *) option->gen;
@@ -1780,17 +1864,6 @@ fillRelOptions(void *rdopts, Size basesize,
char *itempos = ((char *) rdopts) + elems[j].offset;
char *string_val;
- /*
- * If isset_offset is provided, store whether the reloption is
- * set there.
- */
- if (elems[j].isset_offset > 0)
- {
- char *setpos = ((char *) rdopts) + elems[j].isset_offset;
-
- *(bool *) setpos = options[i].isset;
- }
-
switch (options[i].gen->type)
{
case RELOPT_TYPE_BOOL:
@@ -1798,6 +1871,11 @@ fillRelOptions(void *rdopts, Size basesize,
options[i].values.bool_val :
((relopt_bool *) options[i].gen)->default_val;
break;
+ case RELOPT_TYPE_TERNARY:
+ *(ternary *) itempos = options[i].isset ?
+ options[i].values.ternary_val :
+ ((relopt_ternary *) options[i].gen)->default_val;
+ break;
case RELOPT_TYPE_INT:
*(int *) itempos = options[i].isset ?
options[i].values.int_val :
@@ -1912,8 +1990,8 @@ default_reloptions(Datum reloptions, bool validate, relopt_kind kind)
offsetof(StdRdOptions, parallel_workers)},
{"vacuum_index_cleanup", RELOPT_TYPE_ENUM,
offsetof(StdRdOptions, vacuum_index_cleanup)},
- {"vacuum_truncate", RELOPT_TYPE_BOOL,
- offsetof(StdRdOptions, vacuum_truncate), offsetof(StdRdOptions, vacuum_truncate_set)},
+ {"vacuum_truncate", RELOPT_TYPE_TERNARY,
+ offsetof(StdRdOptions, vacuum_truncate)},
{"vacuum_max_eager_freeze_failure_rate", RELOPT_TYPE_REAL,
offsetof(StdRdOptions, vacuum_max_eager_freeze_failure_rate)}
};
@@ -1993,7 +2071,6 @@ build_local_reloptions(local_relopts *relopts, Datum options, bool validate)
elems[i].optname = opt->option->name;
elems[i].opttype = opt->option->type;
elems[i].offset = opt->offset;
- elems[i].isset_offset = 0; /* not supported for local relopts yet */
i++;
}
diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c
index 733ef40ae7..977babff54 100644
--- a/src/backend/commands/vacuum.c
+++ b/src/backend/commands/vacuum.c
@@ -2221,9 +2221,9 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams params,
{
StdRdOptions *opts = (StdRdOptions *) rel->rd_options;
- if (opts && opts->vacuum_truncate_set)
+ if (opts && opts->vacuum_truncate != TERNARY_UNSET)
{
- if (opts->vacuum_truncate)
+ if (opts->vacuum_truncate == TERNARY_TRUE)
params.truncate = VACOPTVALUE_ENABLED;
else
params.truncate = VACOPTVALUE_DISABLED;
diff --git a/src/include/access/reloptions.h b/src/include/access/reloptions.h
index a604a4702c..a436697658 100644
--- a/src/include/access/reloptions.h
+++ b/src/include/access/reloptions.h
@@ -29,6 +29,7 @@
typedef enum relopt_type
{
RELOPT_TYPE_BOOL,
+ RELOPT_TYPE_TERNARY, /* on, off, unset */
RELOPT_TYPE_INT,
RELOPT_TYPE_REAL,
RELOPT_TYPE_ENUM,
@@ -80,6 +81,7 @@ typedef struct relopt_value
union
{
bool bool_val;
+ ternary ternary_val;
int int_val;
double real_val;
int enum_val;
@@ -94,6 +96,12 @@ typedef struct relopt_bool
bool default_val;
} relopt_bool;
+typedef struct relopt_rernary
+{
+ relopt_gen gen;
+ int default_val;
+} relopt_ternary;
+
typedef struct relopt_int
{
relopt_gen gen;
@@ -152,19 +160,6 @@ typedef struct
const char *optname; /* option's name */
relopt_type opttype; /* option's datatype */
int offset; /* offset of field in result struct */
-
- /*
- * isset_offset is an optional offset of a field in the result struct that
- * stores whether the option is explicitly set for the relation or if it
- * just picked up the default value. In most cases, this can be
- * accomplished by giving the reloption a special out-of-range default
- * value (e.g., some integer reloptions use -2), but this isn't always
- * possible. For example, a Boolean reloption cannot be given an
- * out-of-range default, so we need another way to discover the source of
- * its value. This offset is only used if given a value greater than
- * zero.
- */
- int isset_offset;
} relopt_parse_elt;
/* Local reloption definition */
@@ -195,6 +190,8 @@ typedef struct local_relopts
extern relopt_kind add_reloption_kind(void);
extern void add_bool_reloption(bits32 kinds, const char *name, const char *desc,
bool default_val, LOCKMODE lockmode);
+extern void add_ternary_reloption(bits32 kinds, const char *name,
+ const char *desc, int default_val, LOCKMODE lockmode);
extern void add_int_reloption(bits32 kinds, const char *name, const char *desc,
int default_val, int min_val, int max_val,
LOCKMODE lockmode);
@@ -214,6 +211,9 @@ extern void register_reloptions_validator(local_relopts *relopts,
extern void add_local_bool_reloption(local_relopts *relopts, const char *name,
const char *desc, bool default_val,
int offset);
+extern void add_local_ternary_reloption(local_relopts *relopts,
+ const char *name, const char *desc,
+ ternary default_val, int offset);
extern void add_local_int_reloption(local_relopts *relopts, const char *name,
const char *desc, int default_val,
int min_val, int max_val, int offset);
diff --git a/src/include/c.h b/src/include/c.h
index 39022f8a9d..9c59ccccf8 100644
--- a/src/include/c.h
+++ b/src/include/c.h
@@ -488,6 +488,22 @@ typedef void (*pg_funcptr_t) (void);
#include <stdbool.h>
+/*
+ * ternary
+ * Boolean value with an extrea "unset" option
+ *
+ * Ternary data type is used in relation options that can be "true", "false" or
+ * "unset". Since relation options are used deep inside the PostgreSQL code,
+ * this type is declared globally.
+*/
+
+typedef enum ternary
+{
+ TERNARY_FALSE = 0,
+ TERNARY_TRUE = 1,
+ TERNARY_UNSET = -1
+} ternary;
+
/* ----------------------------------------------------------------
* Section 3: standard system types
diff --git a/src/include/utils/rel.h b/src/include/utils/rel.h
index b552359915..7346d618f9 100644
--- a/src/include/utils/rel.h
+++ b/src/include/utils/rel.h
@@ -346,8 +346,7 @@ typedef struct StdRdOptions
bool user_catalog_table; /* use as an additional catalog relation */
int parallel_workers; /* max number of parallel workers */
StdRdOptIndexCleanup vacuum_index_cleanup; /* controls index vacuuming */
- bool vacuum_truncate; /* enables vacuum to truncate a relation */
- bool vacuum_truncate_set; /* whether vacuum_truncate is set */
+ ternary vacuum_truncate; /* enables vacuum to truncate a relation */
/*
* Fraction of pages in a relation that vacuum can eagerly scan and fail
--
2.39.2
--nextPart4155600.3ZeAukHxDK
Content-Disposition: attachment;
filename="v2a-0003-Add-alias-to-be-used-as-unset-state.patch"
Content-Transfer-Encoding: 7Bit
Content-Type: text/x-patch; charset="unicode-2-0-utf-8";
name="v2a-0003-Add-alias-to-be-used-as-unset-state.patch"
^ permalink raw reply [nested|flat] 868+ messages in thread
* [PATCH v2 2/4] Introduce ternary reloptions
@ 2025-09-04 14:41 Nikolay Shaplov <[email protected]>
0 siblings, 0 replies; 868+ messages in thread
From: Nikolay Shaplov @ 2025-09-04 14:41 UTC (permalink / raw)
Introduce ternary reloption as a replacement for current `vacuum_truncate`
implementation. Remove `vacuum_truncate_set` additional flag and using
`TERNARY_UNSET` value instead.
---
src/backend/access/common/reloptions.c | 129 ++++++++++++++++++++-----
src/backend/commands/vacuum.c | 4 +-
src/include/access/reloptions.h | 26 ++---
src/include/c.h | 16 +++
src/include/utils/rel.h | 3 +-
5 files changed, 135 insertions(+), 43 deletions(-)
diff --git a/src/backend/access/common/reloptions.c b/src/backend/access/common/reloptions.c
index 0af3fea68f..f543f11001 100644
--- a/src/backend/access/common/reloptions.c
+++ b/src/backend/access/common/reloptions.c
@@ -40,9 +40,9 @@
*
* To add an option:
*
- * (i) decide on a type (bool, integer, real, enum, string), name, default
- * value, upper and lower bounds (if applicable); for strings, consider a
- * validation routine.
+ * (i) decide on a type (bool, ternary, integer, real, enum, string), name,
+ * default value, upper and lower bounds (if applicable); for strings,
+ * consider a validation routine.
* (ii) add a record below (or use add_<type>_reloption).
* (iii) add it to the appropriate options struct (perhaps StdRdOptions)
* (iv) add it to the appropriate handling routine (perhaps
@@ -147,15 +147,6 @@ static relopt_bool boolRelOpts[] =
},
false
},
- {
- {
- "vacuum_truncate",
- "Enables vacuum to truncate empty pages at the end of this table",
- RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
- ShareUpdateExclusiveLock
- },
- true
- },
{
{
"deduplicate_items",
@@ -170,6 +161,21 @@ static relopt_bool boolRelOpts[] =
{{NULL}}
};
+static relopt_ternary ternaryRelOpts[] =
+{
+ {
+ {
+ "vacuum_truncate",
+ "Enables vacuum to truncate empty pages at the end of this table",
+ RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
+ ShareUpdateExclusiveLock
+ },
+ TERNARY_UNSET
+ },
+ /* list terminator */
+ {{NULL}}
+};
+
static relopt_int intRelOpts[] =
{
{
@@ -600,6 +606,13 @@ initialize_reloptions(void)
boolRelOpts[i].gen.lockmode));
j++;
}
+ for (i = 0; ternaryRelOpts[i].gen.name; i++)
+ {
+ Assert(DoLockModesConflict(ternaryRelOpts[i].gen.lockmode,
+ ternaryRelOpts[i].gen.lockmode));
+ j++;
+ }
+
for (i = 0; intRelOpts[i].gen.name; i++)
{
Assert(DoLockModesConflict(intRelOpts[i].gen.lockmode,
@@ -640,6 +653,14 @@ initialize_reloptions(void)
j++;
}
+ for (i = 0; ternaryRelOpts[i].gen.name; i++)
+ {
+ relOpts[j] = &ternaryRelOpts[i].gen;
+ relOpts[j]->type = RELOPT_TYPE_TERNARY;
+ relOpts[j]->namelen = strlen(relOpts[j]->name);
+ j++;
+ }
+
for (i = 0; intRelOpts[i].gen.name; i++)
{
relOpts[j] = &intRelOpts[i].gen;
@@ -800,6 +821,9 @@ allocate_reloption(bits32 kinds, int type, const char *name, const char *desc,
case RELOPT_TYPE_BOOL:
size = sizeof(relopt_bool);
break;
+ case RELOPT_TYPE_TERNARY:
+ size = sizeof(relopt_ternary);
+ break;
case RELOPT_TYPE_INT:
size = sizeof(relopt_int);
break;
@@ -883,6 +907,54 @@ add_local_bool_reloption(local_relopts *relopts, const char *name,
add_local_reloption(relopts, (relopt_gen *) newoption, offset);
}
+/*
+ * init_ternary_reloption
+ * Allocate and initialize a new ternary reloption
+ */
+static relopt_ternary *
+init_ternary_reloption(bits32 kinds, const char *name, const char *desc,
+ ternary default_val, LOCKMODE lockmode)
+{
+ relopt_ternary *newoption;
+
+ newoption = (relopt_ternary *) allocate_reloption(kinds,
+ RELOPT_TYPE_TERNARY, name, desc, lockmode);
+ newoption->default_val = default_val;
+
+ return newoption;
+}
+
+/*
+ * add_ternary_reloption
+ * Add a new ternary reloption
+ */
+void
+add_ternary_reloption(bits32 kinds, const char *name, const char *desc,
+ ternary default_val, LOCKMODE lockmode)
+{
+ relopt_ternary *newoption = init_ternary_reloption(kinds, name, desc,
+ default_val, lockmode);
+
+ add_reloption((relopt_gen *) newoption);
+}
+
+/*
+ * add_local_ternary_reloption
+ * Add a new ternary local reloption
+ *
+ * 'offset' is offset of ternary-typed field.
+ */
+void
+add_local_ternary_reloption(local_relopts *relopts, const char *name,
+ const char *desc, ternary default_val,
+ int offset)
+{
+ relopt_ternary *newoption = init_ternary_reloption(RELOPT_KIND_LOCAL,
+ name, desc,
+ default_val, 0);
+
+ add_local_reloption(relopts, (relopt_gen *) newoption, offset);
+}
/*
* init_real_reloption
@@ -1617,6 +1689,18 @@ parse_one_reloption(relopt_value *option, char *text_str, int text_len,
option->gen->name, value)));
}
break;
+ case RELOPT_TYPE_TERNARY:
+ {
+ bool b;
+ parsed = parse_bool(value, &b);
+ option->values.ternary_val = b ? TERNARY_TRUE : TERNARY_FALSE;
+ if (validate && !parsed)
+ ereport(ERROR,
+ (errcode(ERRCODE_INVALID_PARAMETER_VALUE),
+ errmsg("invalid value for ternary option \"%s\": %s",
+ option->gen->name, value)));
+ }
+ break;
case RELOPT_TYPE_INT:
{
relopt_int *optint = (relopt_int *) option->gen;
@@ -1780,17 +1864,6 @@ fillRelOptions(void *rdopts, Size basesize,
char *itempos = ((char *) rdopts) + elems[j].offset;
char *string_val;
- /*
- * If isset_offset is provided, store whether the reloption is
- * set there.
- */
- if (elems[j].isset_offset > 0)
- {
- char *setpos = ((char *) rdopts) + elems[j].isset_offset;
-
- *(bool *) setpos = options[i].isset;
- }
-
switch (options[i].gen->type)
{
case RELOPT_TYPE_BOOL:
@@ -1798,6 +1871,11 @@ fillRelOptions(void *rdopts, Size basesize,
options[i].values.bool_val :
((relopt_bool *) options[i].gen)->default_val;
break;
+ case RELOPT_TYPE_TERNARY:
+ *(ternary *) itempos = options[i].isset ?
+ options[i].values.ternary_val :
+ ((relopt_ternary *) options[i].gen)->default_val;
+ break;
case RELOPT_TYPE_INT:
*(int *) itempos = options[i].isset ?
options[i].values.int_val :
@@ -1912,8 +1990,8 @@ default_reloptions(Datum reloptions, bool validate, relopt_kind kind)
offsetof(StdRdOptions, parallel_workers)},
{"vacuum_index_cleanup", RELOPT_TYPE_ENUM,
offsetof(StdRdOptions, vacuum_index_cleanup)},
- {"vacuum_truncate", RELOPT_TYPE_BOOL,
- offsetof(StdRdOptions, vacuum_truncate), offsetof(StdRdOptions, vacuum_truncate_set)},
+ {"vacuum_truncate", RELOPT_TYPE_TERNARY,
+ offsetof(StdRdOptions, vacuum_truncate)},
{"vacuum_max_eager_freeze_failure_rate", RELOPT_TYPE_REAL,
offsetof(StdRdOptions, vacuum_max_eager_freeze_failure_rate)}
};
@@ -1993,7 +2071,6 @@ build_local_reloptions(local_relopts *relopts, Datum options, bool validate)
elems[i].optname = opt->option->name;
elems[i].opttype = opt->option->type;
elems[i].offset = opt->offset;
- elems[i].isset_offset = 0; /* not supported for local relopts yet */
i++;
}
diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c
index 733ef40ae7..977babff54 100644
--- a/src/backend/commands/vacuum.c
+++ b/src/backend/commands/vacuum.c
@@ -2221,9 +2221,9 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams params,
{
StdRdOptions *opts = (StdRdOptions *) rel->rd_options;
- if (opts && opts->vacuum_truncate_set)
+ if (opts && opts->vacuum_truncate != TERNARY_UNSET)
{
- if (opts->vacuum_truncate)
+ if (opts->vacuum_truncate == TERNARY_TRUE)
params.truncate = VACOPTVALUE_ENABLED;
else
params.truncate = VACOPTVALUE_DISABLED;
diff --git a/src/include/access/reloptions.h b/src/include/access/reloptions.h
index a604a4702c..a436697658 100644
--- a/src/include/access/reloptions.h
+++ b/src/include/access/reloptions.h
@@ -29,6 +29,7 @@
typedef enum relopt_type
{
RELOPT_TYPE_BOOL,
+ RELOPT_TYPE_TERNARY, /* on, off, unset */
RELOPT_TYPE_INT,
RELOPT_TYPE_REAL,
RELOPT_TYPE_ENUM,
@@ -80,6 +81,7 @@ typedef struct relopt_value
union
{
bool bool_val;
+ ternary ternary_val;
int int_val;
double real_val;
int enum_val;
@@ -94,6 +96,12 @@ typedef struct relopt_bool
bool default_val;
} relopt_bool;
+typedef struct relopt_rernary
+{
+ relopt_gen gen;
+ int default_val;
+} relopt_ternary;
+
typedef struct relopt_int
{
relopt_gen gen;
@@ -152,19 +160,6 @@ typedef struct
const char *optname; /* option's name */
relopt_type opttype; /* option's datatype */
int offset; /* offset of field in result struct */
-
- /*
- * isset_offset is an optional offset of a field in the result struct that
- * stores whether the option is explicitly set for the relation or if it
- * just picked up the default value. In most cases, this can be
- * accomplished by giving the reloption a special out-of-range default
- * value (e.g., some integer reloptions use -2), but this isn't always
- * possible. For example, a Boolean reloption cannot be given an
- * out-of-range default, so we need another way to discover the source of
- * its value. This offset is only used if given a value greater than
- * zero.
- */
- int isset_offset;
} relopt_parse_elt;
/* Local reloption definition */
@@ -195,6 +190,8 @@ typedef struct local_relopts
extern relopt_kind add_reloption_kind(void);
extern void add_bool_reloption(bits32 kinds, const char *name, const char *desc,
bool default_val, LOCKMODE lockmode);
+extern void add_ternary_reloption(bits32 kinds, const char *name,
+ const char *desc, int default_val, LOCKMODE lockmode);
extern void add_int_reloption(bits32 kinds, const char *name, const char *desc,
int default_val, int min_val, int max_val,
LOCKMODE lockmode);
@@ -214,6 +211,9 @@ extern void register_reloptions_validator(local_relopts *relopts,
extern void add_local_bool_reloption(local_relopts *relopts, const char *name,
const char *desc, bool default_val,
int offset);
+extern void add_local_ternary_reloption(local_relopts *relopts,
+ const char *name, const char *desc,
+ ternary default_val, int offset);
extern void add_local_int_reloption(local_relopts *relopts, const char *name,
const char *desc, int default_val,
int min_val, int max_val, int offset);
diff --git a/src/include/c.h b/src/include/c.h
index 39022f8a9d..9c59ccccf8 100644
--- a/src/include/c.h
+++ b/src/include/c.h
@@ -488,6 +488,22 @@ typedef void (*pg_funcptr_t) (void);
#include <stdbool.h>
+/*
+ * ternary
+ * Boolean value with an extrea "unset" option
+ *
+ * Ternary data type is used in relation options that can be "true", "false" or
+ * "unset". Since relation options are used deep inside the PostgreSQL code,
+ * this type is declared globally.
+*/
+
+typedef enum ternary
+{
+ TERNARY_FALSE = 0,
+ TERNARY_TRUE = 1,
+ TERNARY_UNSET = -1
+} ternary;
+
/* ----------------------------------------------------------------
* Section 3: standard system types
diff --git a/src/include/utils/rel.h b/src/include/utils/rel.h
index b552359915..7346d618f9 100644
--- a/src/include/utils/rel.h
+++ b/src/include/utils/rel.h
@@ -346,8 +346,7 @@ typedef struct StdRdOptions
bool user_catalog_table; /* use as an additional catalog relation */
int parallel_workers; /* max number of parallel workers */
StdRdOptIndexCleanup vacuum_index_cleanup; /* controls index vacuuming */
- bool vacuum_truncate; /* enables vacuum to truncate a relation */
- bool vacuum_truncate_set; /* whether vacuum_truncate is set */
+ ternary vacuum_truncate; /* enables vacuum to truncate a relation */
/*
* Fraction of pages in a relation that vacuum can eagerly scan and fail
--
2.39.2
--nextPart5129932.5fSG56mABF
Content-Disposition: attachment;
filename="v2-0003-Add-alias-to-be-used-as-unset-state.patch"
Content-Transfer-Encoding: 7Bit
Content-Type: text/x-patch; charset="unicode-2-0-utf-8";
name="v2-0003-Add-alias-to-be-used-as-unset-state.patch"
^ permalink raw reply [nested|flat] 868+ messages in thread
* [PATCH v2 2/4] Introduce ternary reloptions
@ 2025-09-04 14:41 Nikolay Shaplov <[email protected]>
0 siblings, 0 replies; 868+ messages in thread
From: Nikolay Shaplov @ 2025-09-04 14:41 UTC (permalink / raw)
Introduce ternary reloption as a replacement for current `vacuum_truncate`
implementation. Remove `vacuum_truncate_set` additional flag and using
`TERNARY_UNSET` value instead.
---
src/backend/access/common/reloptions.c | 129 ++++++++++++++++++++-----
src/backend/commands/vacuum.c | 4 +-
src/include/access/reloptions.h | 26 ++---
src/include/c.h | 16 +++
src/include/utils/rel.h | 3 +-
5 files changed, 135 insertions(+), 43 deletions(-)
diff --git a/src/backend/access/common/reloptions.c b/src/backend/access/common/reloptions.c
index 0af3fea68f..f543f11001 100644
--- a/src/backend/access/common/reloptions.c
+++ b/src/backend/access/common/reloptions.c
@@ -40,9 +40,9 @@
*
* To add an option:
*
- * (i) decide on a type (bool, integer, real, enum, string), name, default
- * value, upper and lower bounds (if applicable); for strings, consider a
- * validation routine.
+ * (i) decide on a type (bool, ternary, integer, real, enum, string), name,
+ * default value, upper and lower bounds (if applicable); for strings,
+ * consider a validation routine.
* (ii) add a record below (or use add_<type>_reloption).
* (iii) add it to the appropriate options struct (perhaps StdRdOptions)
* (iv) add it to the appropriate handling routine (perhaps
@@ -147,15 +147,6 @@ static relopt_bool boolRelOpts[] =
},
false
},
- {
- {
- "vacuum_truncate",
- "Enables vacuum to truncate empty pages at the end of this table",
- RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
- ShareUpdateExclusiveLock
- },
- true
- },
{
{
"deduplicate_items",
@@ -170,6 +161,21 @@ static relopt_bool boolRelOpts[] =
{{NULL}}
};
+static relopt_ternary ternaryRelOpts[] =
+{
+ {
+ {
+ "vacuum_truncate",
+ "Enables vacuum to truncate empty pages at the end of this table",
+ RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
+ ShareUpdateExclusiveLock
+ },
+ TERNARY_UNSET
+ },
+ /* list terminator */
+ {{NULL}}
+};
+
static relopt_int intRelOpts[] =
{
{
@@ -600,6 +606,13 @@ initialize_reloptions(void)
boolRelOpts[i].gen.lockmode));
j++;
}
+ for (i = 0; ternaryRelOpts[i].gen.name; i++)
+ {
+ Assert(DoLockModesConflict(ternaryRelOpts[i].gen.lockmode,
+ ternaryRelOpts[i].gen.lockmode));
+ j++;
+ }
+
for (i = 0; intRelOpts[i].gen.name; i++)
{
Assert(DoLockModesConflict(intRelOpts[i].gen.lockmode,
@@ -640,6 +653,14 @@ initialize_reloptions(void)
j++;
}
+ for (i = 0; ternaryRelOpts[i].gen.name; i++)
+ {
+ relOpts[j] = &ternaryRelOpts[i].gen;
+ relOpts[j]->type = RELOPT_TYPE_TERNARY;
+ relOpts[j]->namelen = strlen(relOpts[j]->name);
+ j++;
+ }
+
for (i = 0; intRelOpts[i].gen.name; i++)
{
relOpts[j] = &intRelOpts[i].gen;
@@ -800,6 +821,9 @@ allocate_reloption(bits32 kinds, int type, const char *name, const char *desc,
case RELOPT_TYPE_BOOL:
size = sizeof(relopt_bool);
break;
+ case RELOPT_TYPE_TERNARY:
+ size = sizeof(relopt_ternary);
+ break;
case RELOPT_TYPE_INT:
size = sizeof(relopt_int);
break;
@@ -883,6 +907,54 @@ add_local_bool_reloption(local_relopts *relopts, const char *name,
add_local_reloption(relopts, (relopt_gen *) newoption, offset);
}
+/*
+ * init_ternary_reloption
+ * Allocate and initialize a new ternary reloption
+ */
+static relopt_ternary *
+init_ternary_reloption(bits32 kinds, const char *name, const char *desc,
+ ternary default_val, LOCKMODE lockmode)
+{
+ relopt_ternary *newoption;
+
+ newoption = (relopt_ternary *) allocate_reloption(kinds,
+ RELOPT_TYPE_TERNARY, name, desc, lockmode);
+ newoption->default_val = default_val;
+
+ return newoption;
+}
+
+/*
+ * add_ternary_reloption
+ * Add a new ternary reloption
+ */
+void
+add_ternary_reloption(bits32 kinds, const char *name, const char *desc,
+ ternary default_val, LOCKMODE lockmode)
+{
+ relopt_ternary *newoption = init_ternary_reloption(kinds, name, desc,
+ default_val, lockmode);
+
+ add_reloption((relopt_gen *) newoption);
+}
+
+/*
+ * add_local_ternary_reloption
+ * Add a new ternary local reloption
+ *
+ * 'offset' is offset of ternary-typed field.
+ */
+void
+add_local_ternary_reloption(local_relopts *relopts, const char *name,
+ const char *desc, ternary default_val,
+ int offset)
+{
+ relopt_ternary *newoption = init_ternary_reloption(RELOPT_KIND_LOCAL,
+ name, desc,
+ default_val, 0);
+
+ add_local_reloption(relopts, (relopt_gen *) newoption, offset);
+}
/*
* init_real_reloption
@@ -1617,6 +1689,18 @@ parse_one_reloption(relopt_value *option, char *text_str, int text_len,
option->gen->name, value)));
}
break;
+ case RELOPT_TYPE_TERNARY:
+ {
+ bool b;
+ parsed = parse_bool(value, &b);
+ option->values.ternary_val = b ? TERNARY_TRUE : TERNARY_FALSE;
+ if (validate && !parsed)
+ ereport(ERROR,
+ (errcode(ERRCODE_INVALID_PARAMETER_VALUE),
+ errmsg("invalid value for ternary option \"%s\": %s",
+ option->gen->name, value)));
+ }
+ break;
case RELOPT_TYPE_INT:
{
relopt_int *optint = (relopt_int *) option->gen;
@@ -1780,17 +1864,6 @@ fillRelOptions(void *rdopts, Size basesize,
char *itempos = ((char *) rdopts) + elems[j].offset;
char *string_val;
- /*
- * If isset_offset is provided, store whether the reloption is
- * set there.
- */
- if (elems[j].isset_offset > 0)
- {
- char *setpos = ((char *) rdopts) + elems[j].isset_offset;
-
- *(bool *) setpos = options[i].isset;
- }
-
switch (options[i].gen->type)
{
case RELOPT_TYPE_BOOL:
@@ -1798,6 +1871,11 @@ fillRelOptions(void *rdopts, Size basesize,
options[i].values.bool_val :
((relopt_bool *) options[i].gen)->default_val;
break;
+ case RELOPT_TYPE_TERNARY:
+ *(ternary *) itempos = options[i].isset ?
+ options[i].values.ternary_val :
+ ((relopt_ternary *) options[i].gen)->default_val;
+ break;
case RELOPT_TYPE_INT:
*(int *) itempos = options[i].isset ?
options[i].values.int_val :
@@ -1912,8 +1990,8 @@ default_reloptions(Datum reloptions, bool validate, relopt_kind kind)
offsetof(StdRdOptions, parallel_workers)},
{"vacuum_index_cleanup", RELOPT_TYPE_ENUM,
offsetof(StdRdOptions, vacuum_index_cleanup)},
- {"vacuum_truncate", RELOPT_TYPE_BOOL,
- offsetof(StdRdOptions, vacuum_truncate), offsetof(StdRdOptions, vacuum_truncate_set)},
+ {"vacuum_truncate", RELOPT_TYPE_TERNARY,
+ offsetof(StdRdOptions, vacuum_truncate)},
{"vacuum_max_eager_freeze_failure_rate", RELOPT_TYPE_REAL,
offsetof(StdRdOptions, vacuum_max_eager_freeze_failure_rate)}
};
@@ -1993,7 +2071,6 @@ build_local_reloptions(local_relopts *relopts, Datum options, bool validate)
elems[i].optname = opt->option->name;
elems[i].opttype = opt->option->type;
elems[i].offset = opt->offset;
- elems[i].isset_offset = 0; /* not supported for local relopts yet */
i++;
}
diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c
index 733ef40ae7..977babff54 100644
--- a/src/backend/commands/vacuum.c
+++ b/src/backend/commands/vacuum.c
@@ -2221,9 +2221,9 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams params,
{
StdRdOptions *opts = (StdRdOptions *) rel->rd_options;
- if (opts && opts->vacuum_truncate_set)
+ if (opts && opts->vacuum_truncate != TERNARY_UNSET)
{
- if (opts->vacuum_truncate)
+ if (opts->vacuum_truncate == TERNARY_TRUE)
params.truncate = VACOPTVALUE_ENABLED;
else
params.truncate = VACOPTVALUE_DISABLED;
diff --git a/src/include/access/reloptions.h b/src/include/access/reloptions.h
index a604a4702c..a436697658 100644
--- a/src/include/access/reloptions.h
+++ b/src/include/access/reloptions.h
@@ -29,6 +29,7 @@
typedef enum relopt_type
{
RELOPT_TYPE_BOOL,
+ RELOPT_TYPE_TERNARY, /* on, off, unset */
RELOPT_TYPE_INT,
RELOPT_TYPE_REAL,
RELOPT_TYPE_ENUM,
@@ -80,6 +81,7 @@ typedef struct relopt_value
union
{
bool bool_val;
+ ternary ternary_val;
int int_val;
double real_val;
int enum_val;
@@ -94,6 +96,12 @@ typedef struct relopt_bool
bool default_val;
} relopt_bool;
+typedef struct relopt_rernary
+{
+ relopt_gen gen;
+ int default_val;
+} relopt_ternary;
+
typedef struct relopt_int
{
relopt_gen gen;
@@ -152,19 +160,6 @@ typedef struct
const char *optname; /* option's name */
relopt_type opttype; /* option's datatype */
int offset; /* offset of field in result struct */
-
- /*
- * isset_offset is an optional offset of a field in the result struct that
- * stores whether the option is explicitly set for the relation or if it
- * just picked up the default value. In most cases, this can be
- * accomplished by giving the reloption a special out-of-range default
- * value (e.g., some integer reloptions use -2), but this isn't always
- * possible. For example, a Boolean reloption cannot be given an
- * out-of-range default, so we need another way to discover the source of
- * its value. This offset is only used if given a value greater than
- * zero.
- */
- int isset_offset;
} relopt_parse_elt;
/* Local reloption definition */
@@ -195,6 +190,8 @@ typedef struct local_relopts
extern relopt_kind add_reloption_kind(void);
extern void add_bool_reloption(bits32 kinds, const char *name, const char *desc,
bool default_val, LOCKMODE lockmode);
+extern void add_ternary_reloption(bits32 kinds, const char *name,
+ const char *desc, int default_val, LOCKMODE lockmode);
extern void add_int_reloption(bits32 kinds, const char *name, const char *desc,
int default_val, int min_val, int max_val,
LOCKMODE lockmode);
@@ -214,6 +211,9 @@ extern void register_reloptions_validator(local_relopts *relopts,
extern void add_local_bool_reloption(local_relopts *relopts, const char *name,
const char *desc, bool default_val,
int offset);
+extern void add_local_ternary_reloption(local_relopts *relopts,
+ const char *name, const char *desc,
+ ternary default_val, int offset);
extern void add_local_int_reloption(local_relopts *relopts, const char *name,
const char *desc, int default_val,
int min_val, int max_val, int offset);
diff --git a/src/include/c.h b/src/include/c.h
index 39022f8a9d..9c59ccccf8 100644
--- a/src/include/c.h
+++ b/src/include/c.h
@@ -488,6 +488,22 @@ typedef void (*pg_funcptr_t) (void);
#include <stdbool.h>
+/*
+ * ternary
+ * Boolean value with an extrea "unset" option
+ *
+ * Ternary data type is used in relation options that can be "true", "false" or
+ * "unset". Since relation options are used deep inside the PostgreSQL code,
+ * this type is declared globally.
+*/
+
+typedef enum ternary
+{
+ TERNARY_FALSE = 0,
+ TERNARY_TRUE = 1,
+ TERNARY_UNSET = -1
+} ternary;
+
/* ----------------------------------------------------------------
* Section 3: standard system types
diff --git a/src/include/utils/rel.h b/src/include/utils/rel.h
index b552359915..7346d618f9 100644
--- a/src/include/utils/rel.h
+++ b/src/include/utils/rel.h
@@ -346,8 +346,7 @@ typedef struct StdRdOptions
bool user_catalog_table; /* use as an additional catalog relation */
int parallel_workers; /* max number of parallel workers */
StdRdOptIndexCleanup vacuum_index_cleanup; /* controls index vacuuming */
- bool vacuum_truncate; /* enables vacuum to truncate a relation */
- bool vacuum_truncate_set; /* whether vacuum_truncate is set */
+ ternary vacuum_truncate; /* enables vacuum to truncate a relation */
/*
* Fraction of pages in a relation that vacuum can eagerly scan and fail
--
2.39.2
--nextPart5129932.5fSG56mABF
Content-Disposition: attachment;
filename="v2-0003-Add-alias-to-be-used-as-unset-state.patch"
Content-Transfer-Encoding: 7Bit
Content-Type: text/x-patch; charset="unicode-2-0-utf-8";
name="v2-0003-Add-alias-to-be-used-as-unset-state.patch"
^ permalink raw reply [nested|flat] 868+ messages in thread
* [PATCH v2 2/4] Introduce ternary reloptions
@ 2025-09-04 14:41 Nikolay Shaplov <[email protected]>
0 siblings, 0 replies; 868+ messages in thread
From: Nikolay Shaplov @ 2025-09-04 14:41 UTC (permalink / raw)
Introduce ternary reloption as a replacement for current `vacuum_truncate`
implementation. Remove `vacuum_truncate_set` additional flag and using
`TERNARY_UNSET` value instead.
---
src/backend/access/common/reloptions.c | 129 ++++++++++++++++++++-----
src/backend/commands/vacuum.c | 4 +-
src/include/access/reloptions.h | 26 ++---
src/include/c.h | 16 +++
src/include/utils/rel.h | 3 +-
5 files changed, 135 insertions(+), 43 deletions(-)
diff --git a/src/backend/access/common/reloptions.c b/src/backend/access/common/reloptions.c
index 0af3fea68f..f543f11001 100644
--- a/src/backend/access/common/reloptions.c
+++ b/src/backend/access/common/reloptions.c
@@ -40,9 +40,9 @@
*
* To add an option:
*
- * (i) decide on a type (bool, integer, real, enum, string), name, default
- * value, upper and lower bounds (if applicable); for strings, consider a
- * validation routine.
+ * (i) decide on a type (bool, ternary, integer, real, enum, string), name,
+ * default value, upper and lower bounds (if applicable); for strings,
+ * consider a validation routine.
* (ii) add a record below (or use add_<type>_reloption).
* (iii) add it to the appropriate options struct (perhaps StdRdOptions)
* (iv) add it to the appropriate handling routine (perhaps
@@ -147,15 +147,6 @@ static relopt_bool boolRelOpts[] =
},
false
},
- {
- {
- "vacuum_truncate",
- "Enables vacuum to truncate empty pages at the end of this table",
- RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
- ShareUpdateExclusiveLock
- },
- true
- },
{
{
"deduplicate_items",
@@ -170,6 +161,21 @@ static relopt_bool boolRelOpts[] =
{{NULL}}
};
+static relopt_ternary ternaryRelOpts[] =
+{
+ {
+ {
+ "vacuum_truncate",
+ "Enables vacuum to truncate empty pages at the end of this table",
+ RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
+ ShareUpdateExclusiveLock
+ },
+ TERNARY_UNSET
+ },
+ /* list terminator */
+ {{NULL}}
+};
+
static relopt_int intRelOpts[] =
{
{
@@ -600,6 +606,13 @@ initialize_reloptions(void)
boolRelOpts[i].gen.lockmode));
j++;
}
+ for (i = 0; ternaryRelOpts[i].gen.name; i++)
+ {
+ Assert(DoLockModesConflict(ternaryRelOpts[i].gen.lockmode,
+ ternaryRelOpts[i].gen.lockmode));
+ j++;
+ }
+
for (i = 0; intRelOpts[i].gen.name; i++)
{
Assert(DoLockModesConflict(intRelOpts[i].gen.lockmode,
@@ -640,6 +653,14 @@ initialize_reloptions(void)
j++;
}
+ for (i = 0; ternaryRelOpts[i].gen.name; i++)
+ {
+ relOpts[j] = &ternaryRelOpts[i].gen;
+ relOpts[j]->type = RELOPT_TYPE_TERNARY;
+ relOpts[j]->namelen = strlen(relOpts[j]->name);
+ j++;
+ }
+
for (i = 0; intRelOpts[i].gen.name; i++)
{
relOpts[j] = &intRelOpts[i].gen;
@@ -800,6 +821,9 @@ allocate_reloption(bits32 kinds, int type, const char *name, const char *desc,
case RELOPT_TYPE_BOOL:
size = sizeof(relopt_bool);
break;
+ case RELOPT_TYPE_TERNARY:
+ size = sizeof(relopt_ternary);
+ break;
case RELOPT_TYPE_INT:
size = sizeof(relopt_int);
break;
@@ -883,6 +907,54 @@ add_local_bool_reloption(local_relopts *relopts, const char *name,
add_local_reloption(relopts, (relopt_gen *) newoption, offset);
}
+/*
+ * init_ternary_reloption
+ * Allocate and initialize a new ternary reloption
+ */
+static relopt_ternary *
+init_ternary_reloption(bits32 kinds, const char *name, const char *desc,
+ ternary default_val, LOCKMODE lockmode)
+{
+ relopt_ternary *newoption;
+
+ newoption = (relopt_ternary *) allocate_reloption(kinds,
+ RELOPT_TYPE_TERNARY, name, desc, lockmode);
+ newoption->default_val = default_val;
+
+ return newoption;
+}
+
+/*
+ * add_ternary_reloption
+ * Add a new ternary reloption
+ */
+void
+add_ternary_reloption(bits32 kinds, const char *name, const char *desc,
+ ternary default_val, LOCKMODE lockmode)
+{
+ relopt_ternary *newoption = init_ternary_reloption(kinds, name, desc,
+ default_val, lockmode);
+
+ add_reloption((relopt_gen *) newoption);
+}
+
+/*
+ * add_local_ternary_reloption
+ * Add a new ternary local reloption
+ *
+ * 'offset' is offset of ternary-typed field.
+ */
+void
+add_local_ternary_reloption(local_relopts *relopts, const char *name,
+ const char *desc, ternary default_val,
+ int offset)
+{
+ relopt_ternary *newoption = init_ternary_reloption(RELOPT_KIND_LOCAL,
+ name, desc,
+ default_val, 0);
+
+ add_local_reloption(relopts, (relopt_gen *) newoption, offset);
+}
/*
* init_real_reloption
@@ -1617,6 +1689,18 @@ parse_one_reloption(relopt_value *option, char *text_str, int text_len,
option->gen->name, value)));
}
break;
+ case RELOPT_TYPE_TERNARY:
+ {
+ bool b;
+ parsed = parse_bool(value, &b);
+ option->values.ternary_val = b ? TERNARY_TRUE : TERNARY_FALSE;
+ if (validate && !parsed)
+ ereport(ERROR,
+ (errcode(ERRCODE_INVALID_PARAMETER_VALUE),
+ errmsg("invalid value for ternary option \"%s\": %s",
+ option->gen->name, value)));
+ }
+ break;
case RELOPT_TYPE_INT:
{
relopt_int *optint = (relopt_int *) option->gen;
@@ -1780,17 +1864,6 @@ fillRelOptions(void *rdopts, Size basesize,
char *itempos = ((char *) rdopts) + elems[j].offset;
char *string_val;
- /*
- * If isset_offset is provided, store whether the reloption is
- * set there.
- */
- if (elems[j].isset_offset > 0)
- {
- char *setpos = ((char *) rdopts) + elems[j].isset_offset;
-
- *(bool *) setpos = options[i].isset;
- }
-
switch (options[i].gen->type)
{
case RELOPT_TYPE_BOOL:
@@ -1798,6 +1871,11 @@ fillRelOptions(void *rdopts, Size basesize,
options[i].values.bool_val :
((relopt_bool *) options[i].gen)->default_val;
break;
+ case RELOPT_TYPE_TERNARY:
+ *(ternary *) itempos = options[i].isset ?
+ options[i].values.ternary_val :
+ ((relopt_ternary *) options[i].gen)->default_val;
+ break;
case RELOPT_TYPE_INT:
*(int *) itempos = options[i].isset ?
options[i].values.int_val :
@@ -1912,8 +1990,8 @@ default_reloptions(Datum reloptions, bool validate, relopt_kind kind)
offsetof(StdRdOptions, parallel_workers)},
{"vacuum_index_cleanup", RELOPT_TYPE_ENUM,
offsetof(StdRdOptions, vacuum_index_cleanup)},
- {"vacuum_truncate", RELOPT_TYPE_BOOL,
- offsetof(StdRdOptions, vacuum_truncate), offsetof(StdRdOptions, vacuum_truncate_set)},
+ {"vacuum_truncate", RELOPT_TYPE_TERNARY,
+ offsetof(StdRdOptions, vacuum_truncate)},
{"vacuum_max_eager_freeze_failure_rate", RELOPT_TYPE_REAL,
offsetof(StdRdOptions, vacuum_max_eager_freeze_failure_rate)}
};
@@ -1993,7 +2071,6 @@ build_local_reloptions(local_relopts *relopts, Datum options, bool validate)
elems[i].optname = opt->option->name;
elems[i].opttype = opt->option->type;
elems[i].offset = opt->offset;
- elems[i].isset_offset = 0; /* not supported for local relopts yet */
i++;
}
diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c
index 733ef40ae7..977babff54 100644
--- a/src/backend/commands/vacuum.c
+++ b/src/backend/commands/vacuum.c
@@ -2221,9 +2221,9 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams params,
{
StdRdOptions *opts = (StdRdOptions *) rel->rd_options;
- if (opts && opts->vacuum_truncate_set)
+ if (opts && opts->vacuum_truncate != TERNARY_UNSET)
{
- if (opts->vacuum_truncate)
+ if (opts->vacuum_truncate == TERNARY_TRUE)
params.truncate = VACOPTVALUE_ENABLED;
else
params.truncate = VACOPTVALUE_DISABLED;
diff --git a/src/include/access/reloptions.h b/src/include/access/reloptions.h
index a604a4702c..a436697658 100644
--- a/src/include/access/reloptions.h
+++ b/src/include/access/reloptions.h
@@ -29,6 +29,7 @@
typedef enum relopt_type
{
RELOPT_TYPE_BOOL,
+ RELOPT_TYPE_TERNARY, /* on, off, unset */
RELOPT_TYPE_INT,
RELOPT_TYPE_REAL,
RELOPT_TYPE_ENUM,
@@ -80,6 +81,7 @@ typedef struct relopt_value
union
{
bool bool_val;
+ ternary ternary_val;
int int_val;
double real_val;
int enum_val;
@@ -94,6 +96,12 @@ typedef struct relopt_bool
bool default_val;
} relopt_bool;
+typedef struct relopt_rernary
+{
+ relopt_gen gen;
+ int default_val;
+} relopt_ternary;
+
typedef struct relopt_int
{
relopt_gen gen;
@@ -152,19 +160,6 @@ typedef struct
const char *optname; /* option's name */
relopt_type opttype; /* option's datatype */
int offset; /* offset of field in result struct */
-
- /*
- * isset_offset is an optional offset of a field in the result struct that
- * stores whether the option is explicitly set for the relation or if it
- * just picked up the default value. In most cases, this can be
- * accomplished by giving the reloption a special out-of-range default
- * value (e.g., some integer reloptions use -2), but this isn't always
- * possible. For example, a Boolean reloption cannot be given an
- * out-of-range default, so we need another way to discover the source of
- * its value. This offset is only used if given a value greater than
- * zero.
- */
- int isset_offset;
} relopt_parse_elt;
/* Local reloption definition */
@@ -195,6 +190,8 @@ typedef struct local_relopts
extern relopt_kind add_reloption_kind(void);
extern void add_bool_reloption(bits32 kinds, const char *name, const char *desc,
bool default_val, LOCKMODE lockmode);
+extern void add_ternary_reloption(bits32 kinds, const char *name,
+ const char *desc, int default_val, LOCKMODE lockmode);
extern void add_int_reloption(bits32 kinds, const char *name, const char *desc,
int default_val, int min_val, int max_val,
LOCKMODE lockmode);
@@ -214,6 +211,9 @@ extern void register_reloptions_validator(local_relopts *relopts,
extern void add_local_bool_reloption(local_relopts *relopts, const char *name,
const char *desc, bool default_val,
int offset);
+extern void add_local_ternary_reloption(local_relopts *relopts,
+ const char *name, const char *desc,
+ ternary default_val, int offset);
extern void add_local_int_reloption(local_relopts *relopts, const char *name,
const char *desc, int default_val,
int min_val, int max_val, int offset);
diff --git a/src/include/c.h b/src/include/c.h
index 39022f8a9d..9c59ccccf8 100644
--- a/src/include/c.h
+++ b/src/include/c.h
@@ -488,6 +488,22 @@ typedef void (*pg_funcptr_t) (void);
#include <stdbool.h>
+/*
+ * ternary
+ * Boolean value with an extrea "unset" option
+ *
+ * Ternary data type is used in relation options that can be "true", "false" or
+ * "unset". Since relation options are used deep inside the PostgreSQL code,
+ * this type is declared globally.
+*/
+
+typedef enum ternary
+{
+ TERNARY_FALSE = 0,
+ TERNARY_TRUE = 1,
+ TERNARY_UNSET = -1
+} ternary;
+
/* ----------------------------------------------------------------
* Section 3: standard system types
diff --git a/src/include/utils/rel.h b/src/include/utils/rel.h
index b552359915..7346d618f9 100644
--- a/src/include/utils/rel.h
+++ b/src/include/utils/rel.h
@@ -346,8 +346,7 @@ typedef struct StdRdOptions
bool user_catalog_table; /* use as an additional catalog relation */
int parallel_workers; /* max number of parallel workers */
StdRdOptIndexCleanup vacuum_index_cleanup; /* controls index vacuuming */
- bool vacuum_truncate; /* enables vacuum to truncate a relation */
- bool vacuum_truncate_set; /* whether vacuum_truncate is set */
+ ternary vacuum_truncate; /* enables vacuum to truncate a relation */
/*
* Fraction of pages in a relation that vacuum can eagerly scan and fail
--
2.39.2
--nextPart5129932.5fSG56mABF
Content-Disposition: attachment;
filename="v2-0003-Add-alias-to-be-used-as-unset-state.patch"
Content-Transfer-Encoding: 7Bit
Content-Type: text/x-patch; charset="unicode-2-0-utf-8";
name="v2-0003-Add-alias-to-be-used-as-unset-state.patch"
^ permalink raw reply [nested|flat] 868+ messages in thread
* [PATCH v2 2/4] Introduce ternary reloptions
@ 2025-09-04 14:41 Nikolay Shaplov <[email protected]>
0 siblings, 0 replies; 868+ messages in thread
From: Nikolay Shaplov @ 2025-09-04 14:41 UTC (permalink / raw)
Introduce ternary reloption as a replacement for current `vacuum_truncate`
implementation. Remove `vacuum_truncate_set` additional flag and using
`TERNARY_UNSET` value instead.
---
src/backend/access/common/reloptions.c | 129 ++++++++++++++++++++-----
src/backend/commands/vacuum.c | 4 +-
src/include/access/reloptions.h | 26 ++---
src/include/c.h | 16 +++
src/include/utils/rel.h | 3 +-
5 files changed, 135 insertions(+), 43 deletions(-)
diff --git a/src/backend/access/common/reloptions.c b/src/backend/access/common/reloptions.c
index 0af3fea68f..f543f11001 100644
--- a/src/backend/access/common/reloptions.c
+++ b/src/backend/access/common/reloptions.c
@@ -40,9 +40,9 @@
*
* To add an option:
*
- * (i) decide on a type (bool, integer, real, enum, string), name, default
- * value, upper and lower bounds (if applicable); for strings, consider a
- * validation routine.
+ * (i) decide on a type (bool, ternary, integer, real, enum, string), name,
+ * default value, upper and lower bounds (if applicable); for strings,
+ * consider a validation routine.
* (ii) add a record below (or use add_<type>_reloption).
* (iii) add it to the appropriate options struct (perhaps StdRdOptions)
* (iv) add it to the appropriate handling routine (perhaps
@@ -147,15 +147,6 @@ static relopt_bool boolRelOpts[] =
},
false
},
- {
- {
- "vacuum_truncate",
- "Enables vacuum to truncate empty pages at the end of this table",
- RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
- ShareUpdateExclusiveLock
- },
- true
- },
{
{
"deduplicate_items",
@@ -170,6 +161,21 @@ static relopt_bool boolRelOpts[] =
{{NULL}}
};
+static relopt_ternary ternaryRelOpts[] =
+{
+ {
+ {
+ "vacuum_truncate",
+ "Enables vacuum to truncate empty pages at the end of this table",
+ RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
+ ShareUpdateExclusiveLock
+ },
+ TERNARY_UNSET
+ },
+ /* list terminator */
+ {{NULL}}
+};
+
static relopt_int intRelOpts[] =
{
{
@@ -600,6 +606,13 @@ initialize_reloptions(void)
boolRelOpts[i].gen.lockmode));
j++;
}
+ for (i = 0; ternaryRelOpts[i].gen.name; i++)
+ {
+ Assert(DoLockModesConflict(ternaryRelOpts[i].gen.lockmode,
+ ternaryRelOpts[i].gen.lockmode));
+ j++;
+ }
+
for (i = 0; intRelOpts[i].gen.name; i++)
{
Assert(DoLockModesConflict(intRelOpts[i].gen.lockmode,
@@ -640,6 +653,14 @@ initialize_reloptions(void)
j++;
}
+ for (i = 0; ternaryRelOpts[i].gen.name; i++)
+ {
+ relOpts[j] = &ternaryRelOpts[i].gen;
+ relOpts[j]->type = RELOPT_TYPE_TERNARY;
+ relOpts[j]->namelen = strlen(relOpts[j]->name);
+ j++;
+ }
+
for (i = 0; intRelOpts[i].gen.name; i++)
{
relOpts[j] = &intRelOpts[i].gen;
@@ -800,6 +821,9 @@ allocate_reloption(bits32 kinds, int type, const char *name, const char *desc,
case RELOPT_TYPE_BOOL:
size = sizeof(relopt_bool);
break;
+ case RELOPT_TYPE_TERNARY:
+ size = sizeof(relopt_ternary);
+ break;
case RELOPT_TYPE_INT:
size = sizeof(relopt_int);
break;
@@ -883,6 +907,54 @@ add_local_bool_reloption(local_relopts *relopts, const char *name,
add_local_reloption(relopts, (relopt_gen *) newoption, offset);
}
+/*
+ * init_ternary_reloption
+ * Allocate and initialize a new ternary reloption
+ */
+static relopt_ternary *
+init_ternary_reloption(bits32 kinds, const char *name, const char *desc,
+ ternary default_val, LOCKMODE lockmode)
+{
+ relopt_ternary *newoption;
+
+ newoption = (relopt_ternary *) allocate_reloption(kinds,
+ RELOPT_TYPE_TERNARY, name, desc, lockmode);
+ newoption->default_val = default_val;
+
+ return newoption;
+}
+
+/*
+ * add_ternary_reloption
+ * Add a new ternary reloption
+ */
+void
+add_ternary_reloption(bits32 kinds, const char *name, const char *desc,
+ ternary default_val, LOCKMODE lockmode)
+{
+ relopt_ternary *newoption = init_ternary_reloption(kinds, name, desc,
+ default_val, lockmode);
+
+ add_reloption((relopt_gen *) newoption);
+}
+
+/*
+ * add_local_ternary_reloption
+ * Add a new ternary local reloption
+ *
+ * 'offset' is offset of ternary-typed field.
+ */
+void
+add_local_ternary_reloption(local_relopts *relopts, const char *name,
+ const char *desc, ternary default_val,
+ int offset)
+{
+ relopt_ternary *newoption = init_ternary_reloption(RELOPT_KIND_LOCAL,
+ name, desc,
+ default_val, 0);
+
+ add_local_reloption(relopts, (relopt_gen *) newoption, offset);
+}
/*
* init_real_reloption
@@ -1617,6 +1689,18 @@ parse_one_reloption(relopt_value *option, char *text_str, int text_len,
option->gen->name, value)));
}
break;
+ case RELOPT_TYPE_TERNARY:
+ {
+ bool b;
+ parsed = parse_bool(value, &b);
+ option->values.ternary_val = b ? TERNARY_TRUE : TERNARY_FALSE;
+ if (validate && !parsed)
+ ereport(ERROR,
+ (errcode(ERRCODE_INVALID_PARAMETER_VALUE),
+ errmsg("invalid value for ternary option \"%s\": %s",
+ option->gen->name, value)));
+ }
+ break;
case RELOPT_TYPE_INT:
{
relopt_int *optint = (relopt_int *) option->gen;
@@ -1780,17 +1864,6 @@ fillRelOptions(void *rdopts, Size basesize,
char *itempos = ((char *) rdopts) + elems[j].offset;
char *string_val;
- /*
- * If isset_offset is provided, store whether the reloption is
- * set there.
- */
- if (elems[j].isset_offset > 0)
- {
- char *setpos = ((char *) rdopts) + elems[j].isset_offset;
-
- *(bool *) setpos = options[i].isset;
- }
-
switch (options[i].gen->type)
{
case RELOPT_TYPE_BOOL:
@@ -1798,6 +1871,11 @@ fillRelOptions(void *rdopts, Size basesize,
options[i].values.bool_val :
((relopt_bool *) options[i].gen)->default_val;
break;
+ case RELOPT_TYPE_TERNARY:
+ *(ternary *) itempos = options[i].isset ?
+ options[i].values.ternary_val :
+ ((relopt_ternary *) options[i].gen)->default_val;
+ break;
case RELOPT_TYPE_INT:
*(int *) itempos = options[i].isset ?
options[i].values.int_val :
@@ -1912,8 +1990,8 @@ default_reloptions(Datum reloptions, bool validate, relopt_kind kind)
offsetof(StdRdOptions, parallel_workers)},
{"vacuum_index_cleanup", RELOPT_TYPE_ENUM,
offsetof(StdRdOptions, vacuum_index_cleanup)},
- {"vacuum_truncate", RELOPT_TYPE_BOOL,
- offsetof(StdRdOptions, vacuum_truncate), offsetof(StdRdOptions, vacuum_truncate_set)},
+ {"vacuum_truncate", RELOPT_TYPE_TERNARY,
+ offsetof(StdRdOptions, vacuum_truncate)},
{"vacuum_max_eager_freeze_failure_rate", RELOPT_TYPE_REAL,
offsetof(StdRdOptions, vacuum_max_eager_freeze_failure_rate)}
};
@@ -1993,7 +2071,6 @@ build_local_reloptions(local_relopts *relopts, Datum options, bool validate)
elems[i].optname = opt->option->name;
elems[i].opttype = opt->option->type;
elems[i].offset = opt->offset;
- elems[i].isset_offset = 0; /* not supported for local relopts yet */
i++;
}
diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c
index 733ef40ae7..977babff54 100644
--- a/src/backend/commands/vacuum.c
+++ b/src/backend/commands/vacuum.c
@@ -2221,9 +2221,9 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams params,
{
StdRdOptions *opts = (StdRdOptions *) rel->rd_options;
- if (opts && opts->vacuum_truncate_set)
+ if (opts && opts->vacuum_truncate != TERNARY_UNSET)
{
- if (opts->vacuum_truncate)
+ if (opts->vacuum_truncate == TERNARY_TRUE)
params.truncate = VACOPTVALUE_ENABLED;
else
params.truncate = VACOPTVALUE_DISABLED;
diff --git a/src/include/access/reloptions.h b/src/include/access/reloptions.h
index a604a4702c..a436697658 100644
--- a/src/include/access/reloptions.h
+++ b/src/include/access/reloptions.h
@@ -29,6 +29,7 @@
typedef enum relopt_type
{
RELOPT_TYPE_BOOL,
+ RELOPT_TYPE_TERNARY, /* on, off, unset */
RELOPT_TYPE_INT,
RELOPT_TYPE_REAL,
RELOPT_TYPE_ENUM,
@@ -80,6 +81,7 @@ typedef struct relopt_value
union
{
bool bool_val;
+ ternary ternary_val;
int int_val;
double real_val;
int enum_val;
@@ -94,6 +96,12 @@ typedef struct relopt_bool
bool default_val;
} relopt_bool;
+typedef struct relopt_rernary
+{
+ relopt_gen gen;
+ int default_val;
+} relopt_ternary;
+
typedef struct relopt_int
{
relopt_gen gen;
@@ -152,19 +160,6 @@ typedef struct
const char *optname; /* option's name */
relopt_type opttype; /* option's datatype */
int offset; /* offset of field in result struct */
-
- /*
- * isset_offset is an optional offset of a field in the result struct that
- * stores whether the option is explicitly set for the relation or if it
- * just picked up the default value. In most cases, this can be
- * accomplished by giving the reloption a special out-of-range default
- * value (e.g., some integer reloptions use -2), but this isn't always
- * possible. For example, a Boolean reloption cannot be given an
- * out-of-range default, so we need another way to discover the source of
- * its value. This offset is only used if given a value greater than
- * zero.
- */
- int isset_offset;
} relopt_parse_elt;
/* Local reloption definition */
@@ -195,6 +190,8 @@ typedef struct local_relopts
extern relopt_kind add_reloption_kind(void);
extern void add_bool_reloption(bits32 kinds, const char *name, const char *desc,
bool default_val, LOCKMODE lockmode);
+extern void add_ternary_reloption(bits32 kinds, const char *name,
+ const char *desc, int default_val, LOCKMODE lockmode);
extern void add_int_reloption(bits32 kinds, const char *name, const char *desc,
int default_val, int min_val, int max_val,
LOCKMODE lockmode);
@@ -214,6 +211,9 @@ extern void register_reloptions_validator(local_relopts *relopts,
extern void add_local_bool_reloption(local_relopts *relopts, const char *name,
const char *desc, bool default_val,
int offset);
+extern void add_local_ternary_reloption(local_relopts *relopts,
+ const char *name, const char *desc,
+ ternary default_val, int offset);
extern void add_local_int_reloption(local_relopts *relopts, const char *name,
const char *desc, int default_val,
int min_val, int max_val, int offset);
diff --git a/src/include/c.h b/src/include/c.h
index 39022f8a9d..9c59ccccf8 100644
--- a/src/include/c.h
+++ b/src/include/c.h
@@ -488,6 +488,22 @@ typedef void (*pg_funcptr_t) (void);
#include <stdbool.h>
+/*
+ * ternary
+ * Boolean value with an extrea "unset" option
+ *
+ * Ternary data type is used in relation options that can be "true", "false" or
+ * "unset". Since relation options are used deep inside the PostgreSQL code,
+ * this type is declared globally.
+*/
+
+typedef enum ternary
+{
+ TERNARY_FALSE = 0,
+ TERNARY_TRUE = 1,
+ TERNARY_UNSET = -1
+} ternary;
+
/* ----------------------------------------------------------------
* Section 3: standard system types
diff --git a/src/include/utils/rel.h b/src/include/utils/rel.h
index b552359915..7346d618f9 100644
--- a/src/include/utils/rel.h
+++ b/src/include/utils/rel.h
@@ -346,8 +346,7 @@ typedef struct StdRdOptions
bool user_catalog_table; /* use as an additional catalog relation */
int parallel_workers; /* max number of parallel workers */
StdRdOptIndexCleanup vacuum_index_cleanup; /* controls index vacuuming */
- bool vacuum_truncate; /* enables vacuum to truncate a relation */
- bool vacuum_truncate_set; /* whether vacuum_truncate is set */
+ ternary vacuum_truncate; /* enables vacuum to truncate a relation */
/*
* Fraction of pages in a relation that vacuum can eagerly scan and fail
--
2.39.2
--nextPart5129932.5fSG56mABF
Content-Disposition: attachment;
filename="v2-0003-Add-alias-to-be-used-as-unset-state.patch"
Content-Transfer-Encoding: 7Bit
Content-Type: text/x-patch; charset="unicode-2-0-utf-8";
name="v2-0003-Add-alias-to-be-used-as-unset-state.patch"
^ permalink raw reply [nested|flat] 868+ messages in thread
* [PATCH v2a 2/4] Introduce ternary reloptions
@ 2025-09-04 14:41 Nikolay Shaplov <[email protected]>
0 siblings, 0 replies; 868+ messages in thread
From: Nikolay Shaplov @ 2025-09-04 14:41 UTC (permalink / raw)
Introduce ternary reloption as a replacement for current `vacuum_truncate`
implementation. Remove `vacuum_truncate_set` additional flag and using
`TERNARY_UNSET` value instead.
---
src/backend/access/common/reloptions.c | 129 ++++++++++++++++++++-----
src/backend/commands/vacuum.c | 4 +-
src/include/access/reloptions.h | 26 ++---
src/include/c.h | 16 +++
src/include/utils/rel.h | 3 +-
5 files changed, 135 insertions(+), 43 deletions(-)
diff --git a/src/backend/access/common/reloptions.c b/src/backend/access/common/reloptions.c
index 0af3fea68f..f543f11001 100644
--- a/src/backend/access/common/reloptions.c
+++ b/src/backend/access/common/reloptions.c
@@ -40,9 +40,9 @@
*
* To add an option:
*
- * (i) decide on a type (bool, integer, real, enum, string), name, default
- * value, upper and lower bounds (if applicable); for strings, consider a
- * validation routine.
+ * (i) decide on a type (bool, ternary, integer, real, enum, string), name,
+ * default value, upper and lower bounds (if applicable); for strings,
+ * consider a validation routine.
* (ii) add a record below (or use add_<type>_reloption).
* (iii) add it to the appropriate options struct (perhaps StdRdOptions)
* (iv) add it to the appropriate handling routine (perhaps
@@ -147,15 +147,6 @@ static relopt_bool boolRelOpts[] =
},
false
},
- {
- {
- "vacuum_truncate",
- "Enables vacuum to truncate empty pages at the end of this table",
- RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
- ShareUpdateExclusiveLock
- },
- true
- },
{
{
"deduplicate_items",
@@ -170,6 +161,21 @@ static relopt_bool boolRelOpts[] =
{{NULL}}
};
+static relopt_ternary ternaryRelOpts[] =
+{
+ {
+ {
+ "vacuum_truncate",
+ "Enables vacuum to truncate empty pages at the end of this table",
+ RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
+ ShareUpdateExclusiveLock
+ },
+ TERNARY_UNSET
+ },
+ /* list terminator */
+ {{NULL}}
+};
+
static relopt_int intRelOpts[] =
{
{
@@ -600,6 +606,13 @@ initialize_reloptions(void)
boolRelOpts[i].gen.lockmode));
j++;
}
+ for (i = 0; ternaryRelOpts[i].gen.name; i++)
+ {
+ Assert(DoLockModesConflict(ternaryRelOpts[i].gen.lockmode,
+ ternaryRelOpts[i].gen.lockmode));
+ j++;
+ }
+
for (i = 0; intRelOpts[i].gen.name; i++)
{
Assert(DoLockModesConflict(intRelOpts[i].gen.lockmode,
@@ -640,6 +653,14 @@ initialize_reloptions(void)
j++;
}
+ for (i = 0; ternaryRelOpts[i].gen.name; i++)
+ {
+ relOpts[j] = &ternaryRelOpts[i].gen;
+ relOpts[j]->type = RELOPT_TYPE_TERNARY;
+ relOpts[j]->namelen = strlen(relOpts[j]->name);
+ j++;
+ }
+
for (i = 0; intRelOpts[i].gen.name; i++)
{
relOpts[j] = &intRelOpts[i].gen;
@@ -800,6 +821,9 @@ allocate_reloption(bits32 kinds, int type, const char *name, const char *desc,
case RELOPT_TYPE_BOOL:
size = sizeof(relopt_bool);
break;
+ case RELOPT_TYPE_TERNARY:
+ size = sizeof(relopt_ternary);
+ break;
case RELOPT_TYPE_INT:
size = sizeof(relopt_int);
break;
@@ -883,6 +907,54 @@ add_local_bool_reloption(local_relopts *relopts, const char *name,
add_local_reloption(relopts, (relopt_gen *) newoption, offset);
}
+/*
+ * init_ternary_reloption
+ * Allocate and initialize a new ternary reloption
+ */
+static relopt_ternary *
+init_ternary_reloption(bits32 kinds, const char *name, const char *desc,
+ ternary default_val, LOCKMODE lockmode)
+{
+ relopt_ternary *newoption;
+
+ newoption = (relopt_ternary *) allocate_reloption(kinds,
+ RELOPT_TYPE_TERNARY, name, desc, lockmode);
+ newoption->default_val = default_val;
+
+ return newoption;
+}
+
+/*
+ * add_ternary_reloption
+ * Add a new ternary reloption
+ */
+void
+add_ternary_reloption(bits32 kinds, const char *name, const char *desc,
+ ternary default_val, LOCKMODE lockmode)
+{
+ relopt_ternary *newoption = init_ternary_reloption(kinds, name, desc,
+ default_val, lockmode);
+
+ add_reloption((relopt_gen *) newoption);
+}
+
+/*
+ * add_local_ternary_reloption
+ * Add a new ternary local reloption
+ *
+ * 'offset' is offset of ternary-typed field.
+ */
+void
+add_local_ternary_reloption(local_relopts *relopts, const char *name,
+ const char *desc, ternary default_val,
+ int offset)
+{
+ relopt_ternary *newoption = init_ternary_reloption(RELOPT_KIND_LOCAL,
+ name, desc,
+ default_val, 0);
+
+ add_local_reloption(relopts, (relopt_gen *) newoption, offset);
+}
/*
* init_real_reloption
@@ -1617,6 +1689,18 @@ parse_one_reloption(relopt_value *option, char *text_str, int text_len,
option->gen->name, value)));
}
break;
+ case RELOPT_TYPE_TERNARY:
+ {
+ bool b;
+ parsed = parse_bool(value, &b);
+ option->values.ternary_val = b ? TERNARY_TRUE : TERNARY_FALSE;
+ if (validate && !parsed)
+ ereport(ERROR,
+ (errcode(ERRCODE_INVALID_PARAMETER_VALUE),
+ errmsg("invalid value for ternary option \"%s\": %s",
+ option->gen->name, value)));
+ }
+ break;
case RELOPT_TYPE_INT:
{
relopt_int *optint = (relopt_int *) option->gen;
@@ -1780,17 +1864,6 @@ fillRelOptions(void *rdopts, Size basesize,
char *itempos = ((char *) rdopts) + elems[j].offset;
char *string_val;
- /*
- * If isset_offset is provided, store whether the reloption is
- * set there.
- */
- if (elems[j].isset_offset > 0)
- {
- char *setpos = ((char *) rdopts) + elems[j].isset_offset;
-
- *(bool *) setpos = options[i].isset;
- }
-
switch (options[i].gen->type)
{
case RELOPT_TYPE_BOOL:
@@ -1798,6 +1871,11 @@ fillRelOptions(void *rdopts, Size basesize,
options[i].values.bool_val :
((relopt_bool *) options[i].gen)->default_val;
break;
+ case RELOPT_TYPE_TERNARY:
+ *(ternary *) itempos = options[i].isset ?
+ options[i].values.ternary_val :
+ ((relopt_ternary *) options[i].gen)->default_val;
+ break;
case RELOPT_TYPE_INT:
*(int *) itempos = options[i].isset ?
options[i].values.int_val :
@@ -1912,8 +1990,8 @@ default_reloptions(Datum reloptions, bool validate, relopt_kind kind)
offsetof(StdRdOptions, parallel_workers)},
{"vacuum_index_cleanup", RELOPT_TYPE_ENUM,
offsetof(StdRdOptions, vacuum_index_cleanup)},
- {"vacuum_truncate", RELOPT_TYPE_BOOL,
- offsetof(StdRdOptions, vacuum_truncate), offsetof(StdRdOptions, vacuum_truncate_set)},
+ {"vacuum_truncate", RELOPT_TYPE_TERNARY,
+ offsetof(StdRdOptions, vacuum_truncate)},
{"vacuum_max_eager_freeze_failure_rate", RELOPT_TYPE_REAL,
offsetof(StdRdOptions, vacuum_max_eager_freeze_failure_rate)}
};
@@ -1993,7 +2071,6 @@ build_local_reloptions(local_relopts *relopts, Datum options, bool validate)
elems[i].optname = opt->option->name;
elems[i].opttype = opt->option->type;
elems[i].offset = opt->offset;
- elems[i].isset_offset = 0; /* not supported for local relopts yet */
i++;
}
diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c
index 733ef40ae7..977babff54 100644
--- a/src/backend/commands/vacuum.c
+++ b/src/backend/commands/vacuum.c
@@ -2221,9 +2221,9 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams params,
{
StdRdOptions *opts = (StdRdOptions *) rel->rd_options;
- if (opts && opts->vacuum_truncate_set)
+ if (opts && opts->vacuum_truncate != TERNARY_UNSET)
{
- if (opts->vacuum_truncate)
+ if (opts->vacuum_truncate == TERNARY_TRUE)
params.truncate = VACOPTVALUE_ENABLED;
else
params.truncate = VACOPTVALUE_DISABLED;
diff --git a/src/include/access/reloptions.h b/src/include/access/reloptions.h
index a604a4702c..a436697658 100644
--- a/src/include/access/reloptions.h
+++ b/src/include/access/reloptions.h
@@ -29,6 +29,7 @@
typedef enum relopt_type
{
RELOPT_TYPE_BOOL,
+ RELOPT_TYPE_TERNARY, /* on, off, unset */
RELOPT_TYPE_INT,
RELOPT_TYPE_REAL,
RELOPT_TYPE_ENUM,
@@ -80,6 +81,7 @@ typedef struct relopt_value
union
{
bool bool_val;
+ ternary ternary_val;
int int_val;
double real_val;
int enum_val;
@@ -94,6 +96,12 @@ typedef struct relopt_bool
bool default_val;
} relopt_bool;
+typedef struct relopt_rernary
+{
+ relopt_gen gen;
+ int default_val;
+} relopt_ternary;
+
typedef struct relopt_int
{
relopt_gen gen;
@@ -152,19 +160,6 @@ typedef struct
const char *optname; /* option's name */
relopt_type opttype; /* option's datatype */
int offset; /* offset of field in result struct */
-
- /*
- * isset_offset is an optional offset of a field in the result struct that
- * stores whether the option is explicitly set for the relation or if it
- * just picked up the default value. In most cases, this can be
- * accomplished by giving the reloption a special out-of-range default
- * value (e.g., some integer reloptions use -2), but this isn't always
- * possible. For example, a Boolean reloption cannot be given an
- * out-of-range default, so we need another way to discover the source of
- * its value. This offset is only used if given a value greater than
- * zero.
- */
- int isset_offset;
} relopt_parse_elt;
/* Local reloption definition */
@@ -195,6 +190,8 @@ typedef struct local_relopts
extern relopt_kind add_reloption_kind(void);
extern void add_bool_reloption(bits32 kinds, const char *name, const char *desc,
bool default_val, LOCKMODE lockmode);
+extern void add_ternary_reloption(bits32 kinds, const char *name,
+ const char *desc, int default_val, LOCKMODE lockmode);
extern void add_int_reloption(bits32 kinds, const char *name, const char *desc,
int default_val, int min_val, int max_val,
LOCKMODE lockmode);
@@ -214,6 +211,9 @@ extern void register_reloptions_validator(local_relopts *relopts,
extern void add_local_bool_reloption(local_relopts *relopts, const char *name,
const char *desc, bool default_val,
int offset);
+extern void add_local_ternary_reloption(local_relopts *relopts,
+ const char *name, const char *desc,
+ ternary default_val, int offset);
extern void add_local_int_reloption(local_relopts *relopts, const char *name,
const char *desc, int default_val,
int min_val, int max_val, int offset);
diff --git a/src/include/c.h b/src/include/c.h
index 39022f8a9d..9c59ccccf8 100644
--- a/src/include/c.h
+++ b/src/include/c.h
@@ -488,6 +488,22 @@ typedef void (*pg_funcptr_t) (void);
#include <stdbool.h>
+/*
+ * ternary
+ * Boolean value with an extrea "unset" option
+ *
+ * Ternary data type is used in relation options that can be "true", "false" or
+ * "unset". Since relation options are used deep inside the PostgreSQL code,
+ * this type is declared globally.
+*/
+
+typedef enum ternary
+{
+ TERNARY_FALSE = 0,
+ TERNARY_TRUE = 1,
+ TERNARY_UNSET = -1
+} ternary;
+
/* ----------------------------------------------------------------
* Section 3: standard system types
diff --git a/src/include/utils/rel.h b/src/include/utils/rel.h
index b552359915..7346d618f9 100644
--- a/src/include/utils/rel.h
+++ b/src/include/utils/rel.h
@@ -346,8 +346,7 @@ typedef struct StdRdOptions
bool user_catalog_table; /* use as an additional catalog relation */
int parallel_workers; /* max number of parallel workers */
StdRdOptIndexCleanup vacuum_index_cleanup; /* controls index vacuuming */
- bool vacuum_truncate; /* enables vacuum to truncate a relation */
- bool vacuum_truncate_set; /* whether vacuum_truncate is set */
+ ternary vacuum_truncate; /* enables vacuum to truncate a relation */
/*
* Fraction of pages in a relation that vacuum can eagerly scan and fail
--
2.39.2
--nextPart4155600.3ZeAukHxDK
Content-Disposition: attachment;
filename="v2a-0003-Add-alias-to-be-used-as-unset-state.patch"
Content-Transfer-Encoding: 7Bit
Content-Type: text/x-patch; charset="unicode-2-0-utf-8";
name="v2a-0003-Add-alias-to-be-used-as-unset-state.patch"
^ permalink raw reply [nested|flat] 868+ messages in thread
* [PATCH v2a 2/4] Introduce ternary reloptions
@ 2025-09-04 14:41 Nikolay Shaplov <[email protected]>
0 siblings, 0 replies; 868+ messages in thread
From: Nikolay Shaplov @ 2025-09-04 14:41 UTC (permalink / raw)
Introduce ternary reloption as a replacement for current `vacuum_truncate`
implementation. Remove `vacuum_truncate_set` additional flag and using
`TERNARY_UNSET` value instead.
---
src/backend/access/common/reloptions.c | 129 ++++++++++++++++++++-----
src/backend/commands/vacuum.c | 4 +-
src/include/access/reloptions.h | 26 ++---
src/include/c.h | 16 +++
src/include/utils/rel.h | 3 +-
5 files changed, 135 insertions(+), 43 deletions(-)
diff --git a/src/backend/access/common/reloptions.c b/src/backend/access/common/reloptions.c
index 0af3fea68f..f543f11001 100644
--- a/src/backend/access/common/reloptions.c
+++ b/src/backend/access/common/reloptions.c
@@ -40,9 +40,9 @@
*
* To add an option:
*
- * (i) decide on a type (bool, integer, real, enum, string), name, default
- * value, upper and lower bounds (if applicable); for strings, consider a
- * validation routine.
+ * (i) decide on a type (bool, ternary, integer, real, enum, string), name,
+ * default value, upper and lower bounds (if applicable); for strings,
+ * consider a validation routine.
* (ii) add a record below (or use add_<type>_reloption).
* (iii) add it to the appropriate options struct (perhaps StdRdOptions)
* (iv) add it to the appropriate handling routine (perhaps
@@ -147,15 +147,6 @@ static relopt_bool boolRelOpts[] =
},
false
},
- {
- {
- "vacuum_truncate",
- "Enables vacuum to truncate empty pages at the end of this table",
- RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
- ShareUpdateExclusiveLock
- },
- true
- },
{
{
"deduplicate_items",
@@ -170,6 +161,21 @@ static relopt_bool boolRelOpts[] =
{{NULL}}
};
+static relopt_ternary ternaryRelOpts[] =
+{
+ {
+ {
+ "vacuum_truncate",
+ "Enables vacuum to truncate empty pages at the end of this table",
+ RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
+ ShareUpdateExclusiveLock
+ },
+ TERNARY_UNSET
+ },
+ /* list terminator */
+ {{NULL}}
+};
+
static relopt_int intRelOpts[] =
{
{
@@ -600,6 +606,13 @@ initialize_reloptions(void)
boolRelOpts[i].gen.lockmode));
j++;
}
+ for (i = 0; ternaryRelOpts[i].gen.name; i++)
+ {
+ Assert(DoLockModesConflict(ternaryRelOpts[i].gen.lockmode,
+ ternaryRelOpts[i].gen.lockmode));
+ j++;
+ }
+
for (i = 0; intRelOpts[i].gen.name; i++)
{
Assert(DoLockModesConflict(intRelOpts[i].gen.lockmode,
@@ -640,6 +653,14 @@ initialize_reloptions(void)
j++;
}
+ for (i = 0; ternaryRelOpts[i].gen.name; i++)
+ {
+ relOpts[j] = &ternaryRelOpts[i].gen;
+ relOpts[j]->type = RELOPT_TYPE_TERNARY;
+ relOpts[j]->namelen = strlen(relOpts[j]->name);
+ j++;
+ }
+
for (i = 0; intRelOpts[i].gen.name; i++)
{
relOpts[j] = &intRelOpts[i].gen;
@@ -800,6 +821,9 @@ allocate_reloption(bits32 kinds, int type, const char *name, const char *desc,
case RELOPT_TYPE_BOOL:
size = sizeof(relopt_bool);
break;
+ case RELOPT_TYPE_TERNARY:
+ size = sizeof(relopt_ternary);
+ break;
case RELOPT_TYPE_INT:
size = sizeof(relopt_int);
break;
@@ -883,6 +907,54 @@ add_local_bool_reloption(local_relopts *relopts, const char *name,
add_local_reloption(relopts, (relopt_gen *) newoption, offset);
}
+/*
+ * init_ternary_reloption
+ * Allocate and initialize a new ternary reloption
+ */
+static relopt_ternary *
+init_ternary_reloption(bits32 kinds, const char *name, const char *desc,
+ ternary default_val, LOCKMODE lockmode)
+{
+ relopt_ternary *newoption;
+
+ newoption = (relopt_ternary *) allocate_reloption(kinds,
+ RELOPT_TYPE_TERNARY, name, desc, lockmode);
+ newoption->default_val = default_val;
+
+ return newoption;
+}
+
+/*
+ * add_ternary_reloption
+ * Add a new ternary reloption
+ */
+void
+add_ternary_reloption(bits32 kinds, const char *name, const char *desc,
+ ternary default_val, LOCKMODE lockmode)
+{
+ relopt_ternary *newoption = init_ternary_reloption(kinds, name, desc,
+ default_val, lockmode);
+
+ add_reloption((relopt_gen *) newoption);
+}
+
+/*
+ * add_local_ternary_reloption
+ * Add a new ternary local reloption
+ *
+ * 'offset' is offset of ternary-typed field.
+ */
+void
+add_local_ternary_reloption(local_relopts *relopts, const char *name,
+ const char *desc, ternary default_val,
+ int offset)
+{
+ relopt_ternary *newoption = init_ternary_reloption(RELOPT_KIND_LOCAL,
+ name, desc,
+ default_val, 0);
+
+ add_local_reloption(relopts, (relopt_gen *) newoption, offset);
+}
/*
* init_real_reloption
@@ -1617,6 +1689,18 @@ parse_one_reloption(relopt_value *option, char *text_str, int text_len,
option->gen->name, value)));
}
break;
+ case RELOPT_TYPE_TERNARY:
+ {
+ bool b;
+ parsed = parse_bool(value, &b);
+ option->values.ternary_val = b ? TERNARY_TRUE : TERNARY_FALSE;
+ if (validate && !parsed)
+ ereport(ERROR,
+ (errcode(ERRCODE_INVALID_PARAMETER_VALUE),
+ errmsg("invalid value for ternary option \"%s\": %s",
+ option->gen->name, value)));
+ }
+ break;
case RELOPT_TYPE_INT:
{
relopt_int *optint = (relopt_int *) option->gen;
@@ -1780,17 +1864,6 @@ fillRelOptions(void *rdopts, Size basesize,
char *itempos = ((char *) rdopts) + elems[j].offset;
char *string_val;
- /*
- * If isset_offset is provided, store whether the reloption is
- * set there.
- */
- if (elems[j].isset_offset > 0)
- {
- char *setpos = ((char *) rdopts) + elems[j].isset_offset;
-
- *(bool *) setpos = options[i].isset;
- }
-
switch (options[i].gen->type)
{
case RELOPT_TYPE_BOOL:
@@ -1798,6 +1871,11 @@ fillRelOptions(void *rdopts, Size basesize,
options[i].values.bool_val :
((relopt_bool *) options[i].gen)->default_val;
break;
+ case RELOPT_TYPE_TERNARY:
+ *(ternary *) itempos = options[i].isset ?
+ options[i].values.ternary_val :
+ ((relopt_ternary *) options[i].gen)->default_val;
+ break;
case RELOPT_TYPE_INT:
*(int *) itempos = options[i].isset ?
options[i].values.int_val :
@@ -1912,8 +1990,8 @@ default_reloptions(Datum reloptions, bool validate, relopt_kind kind)
offsetof(StdRdOptions, parallel_workers)},
{"vacuum_index_cleanup", RELOPT_TYPE_ENUM,
offsetof(StdRdOptions, vacuum_index_cleanup)},
- {"vacuum_truncate", RELOPT_TYPE_BOOL,
- offsetof(StdRdOptions, vacuum_truncate), offsetof(StdRdOptions, vacuum_truncate_set)},
+ {"vacuum_truncate", RELOPT_TYPE_TERNARY,
+ offsetof(StdRdOptions, vacuum_truncate)},
{"vacuum_max_eager_freeze_failure_rate", RELOPT_TYPE_REAL,
offsetof(StdRdOptions, vacuum_max_eager_freeze_failure_rate)}
};
@@ -1993,7 +2071,6 @@ build_local_reloptions(local_relopts *relopts, Datum options, bool validate)
elems[i].optname = opt->option->name;
elems[i].opttype = opt->option->type;
elems[i].offset = opt->offset;
- elems[i].isset_offset = 0; /* not supported for local relopts yet */
i++;
}
diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c
index 733ef40ae7..977babff54 100644
--- a/src/backend/commands/vacuum.c
+++ b/src/backend/commands/vacuum.c
@@ -2221,9 +2221,9 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams params,
{
StdRdOptions *opts = (StdRdOptions *) rel->rd_options;
- if (opts && opts->vacuum_truncate_set)
+ if (opts && opts->vacuum_truncate != TERNARY_UNSET)
{
- if (opts->vacuum_truncate)
+ if (opts->vacuum_truncate == TERNARY_TRUE)
params.truncate = VACOPTVALUE_ENABLED;
else
params.truncate = VACOPTVALUE_DISABLED;
diff --git a/src/include/access/reloptions.h b/src/include/access/reloptions.h
index a604a4702c..a436697658 100644
--- a/src/include/access/reloptions.h
+++ b/src/include/access/reloptions.h
@@ -29,6 +29,7 @@
typedef enum relopt_type
{
RELOPT_TYPE_BOOL,
+ RELOPT_TYPE_TERNARY, /* on, off, unset */
RELOPT_TYPE_INT,
RELOPT_TYPE_REAL,
RELOPT_TYPE_ENUM,
@@ -80,6 +81,7 @@ typedef struct relopt_value
union
{
bool bool_val;
+ ternary ternary_val;
int int_val;
double real_val;
int enum_val;
@@ -94,6 +96,12 @@ typedef struct relopt_bool
bool default_val;
} relopt_bool;
+typedef struct relopt_rernary
+{
+ relopt_gen gen;
+ int default_val;
+} relopt_ternary;
+
typedef struct relopt_int
{
relopt_gen gen;
@@ -152,19 +160,6 @@ typedef struct
const char *optname; /* option's name */
relopt_type opttype; /* option's datatype */
int offset; /* offset of field in result struct */
-
- /*
- * isset_offset is an optional offset of a field in the result struct that
- * stores whether the option is explicitly set for the relation or if it
- * just picked up the default value. In most cases, this can be
- * accomplished by giving the reloption a special out-of-range default
- * value (e.g., some integer reloptions use -2), but this isn't always
- * possible. For example, a Boolean reloption cannot be given an
- * out-of-range default, so we need another way to discover the source of
- * its value. This offset is only used if given a value greater than
- * zero.
- */
- int isset_offset;
} relopt_parse_elt;
/* Local reloption definition */
@@ -195,6 +190,8 @@ typedef struct local_relopts
extern relopt_kind add_reloption_kind(void);
extern void add_bool_reloption(bits32 kinds, const char *name, const char *desc,
bool default_val, LOCKMODE lockmode);
+extern void add_ternary_reloption(bits32 kinds, const char *name,
+ const char *desc, int default_val, LOCKMODE lockmode);
extern void add_int_reloption(bits32 kinds, const char *name, const char *desc,
int default_val, int min_val, int max_val,
LOCKMODE lockmode);
@@ -214,6 +211,9 @@ extern void register_reloptions_validator(local_relopts *relopts,
extern void add_local_bool_reloption(local_relopts *relopts, const char *name,
const char *desc, bool default_val,
int offset);
+extern void add_local_ternary_reloption(local_relopts *relopts,
+ const char *name, const char *desc,
+ ternary default_val, int offset);
extern void add_local_int_reloption(local_relopts *relopts, const char *name,
const char *desc, int default_val,
int min_val, int max_val, int offset);
diff --git a/src/include/c.h b/src/include/c.h
index 39022f8a9d..9c59ccccf8 100644
--- a/src/include/c.h
+++ b/src/include/c.h
@@ -488,6 +488,22 @@ typedef void (*pg_funcptr_t) (void);
#include <stdbool.h>
+/*
+ * ternary
+ * Boolean value with an extrea "unset" option
+ *
+ * Ternary data type is used in relation options that can be "true", "false" or
+ * "unset". Since relation options are used deep inside the PostgreSQL code,
+ * this type is declared globally.
+*/
+
+typedef enum ternary
+{
+ TERNARY_FALSE = 0,
+ TERNARY_TRUE = 1,
+ TERNARY_UNSET = -1
+} ternary;
+
/* ----------------------------------------------------------------
* Section 3: standard system types
diff --git a/src/include/utils/rel.h b/src/include/utils/rel.h
index b552359915..7346d618f9 100644
--- a/src/include/utils/rel.h
+++ b/src/include/utils/rel.h
@@ -346,8 +346,7 @@ typedef struct StdRdOptions
bool user_catalog_table; /* use as an additional catalog relation */
int parallel_workers; /* max number of parallel workers */
StdRdOptIndexCleanup vacuum_index_cleanup; /* controls index vacuuming */
- bool vacuum_truncate; /* enables vacuum to truncate a relation */
- bool vacuum_truncate_set; /* whether vacuum_truncate is set */
+ ternary vacuum_truncate; /* enables vacuum to truncate a relation */
/*
* Fraction of pages in a relation that vacuum can eagerly scan and fail
--
2.39.2
--nextPart4155600.3ZeAukHxDK
Content-Disposition: attachment;
filename="v2a-0003-Add-alias-to-be-used-as-unset-state.patch"
Content-Transfer-Encoding: 7Bit
Content-Type: text/x-patch; charset="unicode-2-0-utf-8";
name="v2a-0003-Add-alias-to-be-used-as-unset-state.patch"
^ permalink raw reply [nested|flat] 868+ messages in thread
* [PATCH v2 2/4] Introduce ternary reloptions
@ 2025-09-04 14:41 Nikolay Shaplov <[email protected]>
0 siblings, 0 replies; 868+ messages in thread
From: Nikolay Shaplov @ 2025-09-04 14:41 UTC (permalink / raw)
Introduce ternary reloption as a replacement for current `vacuum_truncate`
implementation. Remove `vacuum_truncate_set` additional flag and using
`TERNARY_UNSET` value instead.
---
src/backend/access/common/reloptions.c | 129 ++++++++++++++++++++-----
src/backend/commands/vacuum.c | 4 +-
src/include/access/reloptions.h | 26 ++---
src/include/c.h | 16 +++
src/include/utils/rel.h | 3 +-
5 files changed, 135 insertions(+), 43 deletions(-)
diff --git a/src/backend/access/common/reloptions.c b/src/backend/access/common/reloptions.c
index 0af3fea68f..f543f11001 100644
--- a/src/backend/access/common/reloptions.c
+++ b/src/backend/access/common/reloptions.c
@@ -40,9 +40,9 @@
*
* To add an option:
*
- * (i) decide on a type (bool, integer, real, enum, string), name, default
- * value, upper and lower bounds (if applicable); for strings, consider a
- * validation routine.
+ * (i) decide on a type (bool, ternary, integer, real, enum, string), name,
+ * default value, upper and lower bounds (if applicable); for strings,
+ * consider a validation routine.
* (ii) add a record below (or use add_<type>_reloption).
* (iii) add it to the appropriate options struct (perhaps StdRdOptions)
* (iv) add it to the appropriate handling routine (perhaps
@@ -147,15 +147,6 @@ static relopt_bool boolRelOpts[] =
},
false
},
- {
- {
- "vacuum_truncate",
- "Enables vacuum to truncate empty pages at the end of this table",
- RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
- ShareUpdateExclusiveLock
- },
- true
- },
{
{
"deduplicate_items",
@@ -170,6 +161,21 @@ static relopt_bool boolRelOpts[] =
{{NULL}}
};
+static relopt_ternary ternaryRelOpts[] =
+{
+ {
+ {
+ "vacuum_truncate",
+ "Enables vacuum to truncate empty pages at the end of this table",
+ RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
+ ShareUpdateExclusiveLock
+ },
+ TERNARY_UNSET
+ },
+ /* list terminator */
+ {{NULL}}
+};
+
static relopt_int intRelOpts[] =
{
{
@@ -600,6 +606,13 @@ initialize_reloptions(void)
boolRelOpts[i].gen.lockmode));
j++;
}
+ for (i = 0; ternaryRelOpts[i].gen.name; i++)
+ {
+ Assert(DoLockModesConflict(ternaryRelOpts[i].gen.lockmode,
+ ternaryRelOpts[i].gen.lockmode));
+ j++;
+ }
+
for (i = 0; intRelOpts[i].gen.name; i++)
{
Assert(DoLockModesConflict(intRelOpts[i].gen.lockmode,
@@ -640,6 +653,14 @@ initialize_reloptions(void)
j++;
}
+ for (i = 0; ternaryRelOpts[i].gen.name; i++)
+ {
+ relOpts[j] = &ternaryRelOpts[i].gen;
+ relOpts[j]->type = RELOPT_TYPE_TERNARY;
+ relOpts[j]->namelen = strlen(relOpts[j]->name);
+ j++;
+ }
+
for (i = 0; intRelOpts[i].gen.name; i++)
{
relOpts[j] = &intRelOpts[i].gen;
@@ -800,6 +821,9 @@ allocate_reloption(bits32 kinds, int type, const char *name, const char *desc,
case RELOPT_TYPE_BOOL:
size = sizeof(relopt_bool);
break;
+ case RELOPT_TYPE_TERNARY:
+ size = sizeof(relopt_ternary);
+ break;
case RELOPT_TYPE_INT:
size = sizeof(relopt_int);
break;
@@ -883,6 +907,54 @@ add_local_bool_reloption(local_relopts *relopts, const char *name,
add_local_reloption(relopts, (relopt_gen *) newoption, offset);
}
+/*
+ * init_ternary_reloption
+ * Allocate and initialize a new ternary reloption
+ */
+static relopt_ternary *
+init_ternary_reloption(bits32 kinds, const char *name, const char *desc,
+ ternary default_val, LOCKMODE lockmode)
+{
+ relopt_ternary *newoption;
+
+ newoption = (relopt_ternary *) allocate_reloption(kinds,
+ RELOPT_TYPE_TERNARY, name, desc, lockmode);
+ newoption->default_val = default_val;
+
+ return newoption;
+}
+
+/*
+ * add_ternary_reloption
+ * Add a new ternary reloption
+ */
+void
+add_ternary_reloption(bits32 kinds, const char *name, const char *desc,
+ ternary default_val, LOCKMODE lockmode)
+{
+ relopt_ternary *newoption = init_ternary_reloption(kinds, name, desc,
+ default_val, lockmode);
+
+ add_reloption((relopt_gen *) newoption);
+}
+
+/*
+ * add_local_ternary_reloption
+ * Add a new ternary local reloption
+ *
+ * 'offset' is offset of ternary-typed field.
+ */
+void
+add_local_ternary_reloption(local_relopts *relopts, const char *name,
+ const char *desc, ternary default_val,
+ int offset)
+{
+ relopt_ternary *newoption = init_ternary_reloption(RELOPT_KIND_LOCAL,
+ name, desc,
+ default_val, 0);
+
+ add_local_reloption(relopts, (relopt_gen *) newoption, offset);
+}
/*
* init_real_reloption
@@ -1617,6 +1689,18 @@ parse_one_reloption(relopt_value *option, char *text_str, int text_len,
option->gen->name, value)));
}
break;
+ case RELOPT_TYPE_TERNARY:
+ {
+ bool b;
+ parsed = parse_bool(value, &b);
+ option->values.ternary_val = b ? TERNARY_TRUE : TERNARY_FALSE;
+ if (validate && !parsed)
+ ereport(ERROR,
+ (errcode(ERRCODE_INVALID_PARAMETER_VALUE),
+ errmsg("invalid value for ternary option \"%s\": %s",
+ option->gen->name, value)));
+ }
+ break;
case RELOPT_TYPE_INT:
{
relopt_int *optint = (relopt_int *) option->gen;
@@ -1780,17 +1864,6 @@ fillRelOptions(void *rdopts, Size basesize,
char *itempos = ((char *) rdopts) + elems[j].offset;
char *string_val;
- /*
- * If isset_offset is provided, store whether the reloption is
- * set there.
- */
- if (elems[j].isset_offset > 0)
- {
- char *setpos = ((char *) rdopts) + elems[j].isset_offset;
-
- *(bool *) setpos = options[i].isset;
- }
-
switch (options[i].gen->type)
{
case RELOPT_TYPE_BOOL:
@@ -1798,6 +1871,11 @@ fillRelOptions(void *rdopts, Size basesize,
options[i].values.bool_val :
((relopt_bool *) options[i].gen)->default_val;
break;
+ case RELOPT_TYPE_TERNARY:
+ *(ternary *) itempos = options[i].isset ?
+ options[i].values.ternary_val :
+ ((relopt_ternary *) options[i].gen)->default_val;
+ break;
case RELOPT_TYPE_INT:
*(int *) itempos = options[i].isset ?
options[i].values.int_val :
@@ -1912,8 +1990,8 @@ default_reloptions(Datum reloptions, bool validate, relopt_kind kind)
offsetof(StdRdOptions, parallel_workers)},
{"vacuum_index_cleanup", RELOPT_TYPE_ENUM,
offsetof(StdRdOptions, vacuum_index_cleanup)},
- {"vacuum_truncate", RELOPT_TYPE_BOOL,
- offsetof(StdRdOptions, vacuum_truncate), offsetof(StdRdOptions, vacuum_truncate_set)},
+ {"vacuum_truncate", RELOPT_TYPE_TERNARY,
+ offsetof(StdRdOptions, vacuum_truncate)},
{"vacuum_max_eager_freeze_failure_rate", RELOPT_TYPE_REAL,
offsetof(StdRdOptions, vacuum_max_eager_freeze_failure_rate)}
};
@@ -1993,7 +2071,6 @@ build_local_reloptions(local_relopts *relopts, Datum options, bool validate)
elems[i].optname = opt->option->name;
elems[i].opttype = opt->option->type;
elems[i].offset = opt->offset;
- elems[i].isset_offset = 0; /* not supported for local relopts yet */
i++;
}
diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c
index 733ef40ae7..977babff54 100644
--- a/src/backend/commands/vacuum.c
+++ b/src/backend/commands/vacuum.c
@@ -2221,9 +2221,9 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams params,
{
StdRdOptions *opts = (StdRdOptions *) rel->rd_options;
- if (opts && opts->vacuum_truncate_set)
+ if (opts && opts->vacuum_truncate != TERNARY_UNSET)
{
- if (opts->vacuum_truncate)
+ if (opts->vacuum_truncate == TERNARY_TRUE)
params.truncate = VACOPTVALUE_ENABLED;
else
params.truncate = VACOPTVALUE_DISABLED;
diff --git a/src/include/access/reloptions.h b/src/include/access/reloptions.h
index a604a4702c..a436697658 100644
--- a/src/include/access/reloptions.h
+++ b/src/include/access/reloptions.h
@@ -29,6 +29,7 @@
typedef enum relopt_type
{
RELOPT_TYPE_BOOL,
+ RELOPT_TYPE_TERNARY, /* on, off, unset */
RELOPT_TYPE_INT,
RELOPT_TYPE_REAL,
RELOPT_TYPE_ENUM,
@@ -80,6 +81,7 @@ typedef struct relopt_value
union
{
bool bool_val;
+ ternary ternary_val;
int int_val;
double real_val;
int enum_val;
@@ -94,6 +96,12 @@ typedef struct relopt_bool
bool default_val;
} relopt_bool;
+typedef struct relopt_rernary
+{
+ relopt_gen gen;
+ int default_val;
+} relopt_ternary;
+
typedef struct relopt_int
{
relopt_gen gen;
@@ -152,19 +160,6 @@ typedef struct
const char *optname; /* option's name */
relopt_type opttype; /* option's datatype */
int offset; /* offset of field in result struct */
-
- /*
- * isset_offset is an optional offset of a field in the result struct that
- * stores whether the option is explicitly set for the relation or if it
- * just picked up the default value. In most cases, this can be
- * accomplished by giving the reloption a special out-of-range default
- * value (e.g., some integer reloptions use -2), but this isn't always
- * possible. For example, a Boolean reloption cannot be given an
- * out-of-range default, so we need another way to discover the source of
- * its value. This offset is only used if given a value greater than
- * zero.
- */
- int isset_offset;
} relopt_parse_elt;
/* Local reloption definition */
@@ -195,6 +190,8 @@ typedef struct local_relopts
extern relopt_kind add_reloption_kind(void);
extern void add_bool_reloption(bits32 kinds, const char *name, const char *desc,
bool default_val, LOCKMODE lockmode);
+extern void add_ternary_reloption(bits32 kinds, const char *name,
+ const char *desc, int default_val, LOCKMODE lockmode);
extern void add_int_reloption(bits32 kinds, const char *name, const char *desc,
int default_val, int min_val, int max_val,
LOCKMODE lockmode);
@@ -214,6 +211,9 @@ extern void register_reloptions_validator(local_relopts *relopts,
extern void add_local_bool_reloption(local_relopts *relopts, const char *name,
const char *desc, bool default_val,
int offset);
+extern void add_local_ternary_reloption(local_relopts *relopts,
+ const char *name, const char *desc,
+ ternary default_val, int offset);
extern void add_local_int_reloption(local_relopts *relopts, const char *name,
const char *desc, int default_val,
int min_val, int max_val, int offset);
diff --git a/src/include/c.h b/src/include/c.h
index 39022f8a9d..9c59ccccf8 100644
--- a/src/include/c.h
+++ b/src/include/c.h
@@ -488,6 +488,22 @@ typedef void (*pg_funcptr_t) (void);
#include <stdbool.h>
+/*
+ * ternary
+ * Boolean value with an extrea "unset" option
+ *
+ * Ternary data type is used in relation options that can be "true", "false" or
+ * "unset". Since relation options are used deep inside the PostgreSQL code,
+ * this type is declared globally.
+*/
+
+typedef enum ternary
+{
+ TERNARY_FALSE = 0,
+ TERNARY_TRUE = 1,
+ TERNARY_UNSET = -1
+} ternary;
+
/* ----------------------------------------------------------------
* Section 3: standard system types
diff --git a/src/include/utils/rel.h b/src/include/utils/rel.h
index b552359915..7346d618f9 100644
--- a/src/include/utils/rel.h
+++ b/src/include/utils/rel.h
@@ -346,8 +346,7 @@ typedef struct StdRdOptions
bool user_catalog_table; /* use as an additional catalog relation */
int parallel_workers; /* max number of parallel workers */
StdRdOptIndexCleanup vacuum_index_cleanup; /* controls index vacuuming */
- bool vacuum_truncate; /* enables vacuum to truncate a relation */
- bool vacuum_truncate_set; /* whether vacuum_truncate is set */
+ ternary vacuum_truncate; /* enables vacuum to truncate a relation */
/*
* Fraction of pages in a relation that vacuum can eagerly scan and fail
--
2.39.2
--nextPart5129932.5fSG56mABF
Content-Disposition: attachment;
filename="v2-0003-Add-alias-to-be-used-as-unset-state.patch"
Content-Transfer-Encoding: 7Bit
Content-Type: text/x-patch; charset="unicode-2-0-utf-8";
name="v2-0003-Add-alias-to-be-used-as-unset-state.patch"
^ permalink raw reply [nested|flat] 868+ messages in thread
* [PATCH v2a 2/4] Introduce ternary reloptions
@ 2025-09-04 14:41 Nikolay Shaplov <[email protected]>
0 siblings, 0 replies; 868+ messages in thread
From: Nikolay Shaplov @ 2025-09-04 14:41 UTC (permalink / raw)
Introduce ternary reloption as a replacement for current `vacuum_truncate`
implementation. Remove `vacuum_truncate_set` additional flag and using
`TERNARY_UNSET` value instead.
---
src/backend/access/common/reloptions.c | 129 ++++++++++++++++++++-----
src/backend/commands/vacuum.c | 4 +-
src/include/access/reloptions.h | 26 ++---
src/include/c.h | 16 +++
src/include/utils/rel.h | 3 +-
5 files changed, 135 insertions(+), 43 deletions(-)
diff --git a/src/backend/access/common/reloptions.c b/src/backend/access/common/reloptions.c
index 0af3fea68f..f543f11001 100644
--- a/src/backend/access/common/reloptions.c
+++ b/src/backend/access/common/reloptions.c
@@ -40,9 +40,9 @@
*
* To add an option:
*
- * (i) decide on a type (bool, integer, real, enum, string), name, default
- * value, upper and lower bounds (if applicable); for strings, consider a
- * validation routine.
+ * (i) decide on a type (bool, ternary, integer, real, enum, string), name,
+ * default value, upper and lower bounds (if applicable); for strings,
+ * consider a validation routine.
* (ii) add a record below (or use add_<type>_reloption).
* (iii) add it to the appropriate options struct (perhaps StdRdOptions)
* (iv) add it to the appropriate handling routine (perhaps
@@ -147,15 +147,6 @@ static relopt_bool boolRelOpts[] =
},
false
},
- {
- {
- "vacuum_truncate",
- "Enables vacuum to truncate empty pages at the end of this table",
- RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
- ShareUpdateExclusiveLock
- },
- true
- },
{
{
"deduplicate_items",
@@ -170,6 +161,21 @@ static relopt_bool boolRelOpts[] =
{{NULL}}
};
+static relopt_ternary ternaryRelOpts[] =
+{
+ {
+ {
+ "vacuum_truncate",
+ "Enables vacuum to truncate empty pages at the end of this table",
+ RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
+ ShareUpdateExclusiveLock
+ },
+ TERNARY_UNSET
+ },
+ /* list terminator */
+ {{NULL}}
+};
+
static relopt_int intRelOpts[] =
{
{
@@ -600,6 +606,13 @@ initialize_reloptions(void)
boolRelOpts[i].gen.lockmode));
j++;
}
+ for (i = 0; ternaryRelOpts[i].gen.name; i++)
+ {
+ Assert(DoLockModesConflict(ternaryRelOpts[i].gen.lockmode,
+ ternaryRelOpts[i].gen.lockmode));
+ j++;
+ }
+
for (i = 0; intRelOpts[i].gen.name; i++)
{
Assert(DoLockModesConflict(intRelOpts[i].gen.lockmode,
@@ -640,6 +653,14 @@ initialize_reloptions(void)
j++;
}
+ for (i = 0; ternaryRelOpts[i].gen.name; i++)
+ {
+ relOpts[j] = &ternaryRelOpts[i].gen;
+ relOpts[j]->type = RELOPT_TYPE_TERNARY;
+ relOpts[j]->namelen = strlen(relOpts[j]->name);
+ j++;
+ }
+
for (i = 0; intRelOpts[i].gen.name; i++)
{
relOpts[j] = &intRelOpts[i].gen;
@@ -800,6 +821,9 @@ allocate_reloption(bits32 kinds, int type, const char *name, const char *desc,
case RELOPT_TYPE_BOOL:
size = sizeof(relopt_bool);
break;
+ case RELOPT_TYPE_TERNARY:
+ size = sizeof(relopt_ternary);
+ break;
case RELOPT_TYPE_INT:
size = sizeof(relopt_int);
break;
@@ -883,6 +907,54 @@ add_local_bool_reloption(local_relopts *relopts, const char *name,
add_local_reloption(relopts, (relopt_gen *) newoption, offset);
}
+/*
+ * init_ternary_reloption
+ * Allocate and initialize a new ternary reloption
+ */
+static relopt_ternary *
+init_ternary_reloption(bits32 kinds, const char *name, const char *desc,
+ ternary default_val, LOCKMODE lockmode)
+{
+ relopt_ternary *newoption;
+
+ newoption = (relopt_ternary *) allocate_reloption(kinds,
+ RELOPT_TYPE_TERNARY, name, desc, lockmode);
+ newoption->default_val = default_val;
+
+ return newoption;
+}
+
+/*
+ * add_ternary_reloption
+ * Add a new ternary reloption
+ */
+void
+add_ternary_reloption(bits32 kinds, const char *name, const char *desc,
+ ternary default_val, LOCKMODE lockmode)
+{
+ relopt_ternary *newoption = init_ternary_reloption(kinds, name, desc,
+ default_val, lockmode);
+
+ add_reloption((relopt_gen *) newoption);
+}
+
+/*
+ * add_local_ternary_reloption
+ * Add a new ternary local reloption
+ *
+ * 'offset' is offset of ternary-typed field.
+ */
+void
+add_local_ternary_reloption(local_relopts *relopts, const char *name,
+ const char *desc, ternary default_val,
+ int offset)
+{
+ relopt_ternary *newoption = init_ternary_reloption(RELOPT_KIND_LOCAL,
+ name, desc,
+ default_val, 0);
+
+ add_local_reloption(relopts, (relopt_gen *) newoption, offset);
+}
/*
* init_real_reloption
@@ -1617,6 +1689,18 @@ parse_one_reloption(relopt_value *option, char *text_str, int text_len,
option->gen->name, value)));
}
break;
+ case RELOPT_TYPE_TERNARY:
+ {
+ bool b;
+ parsed = parse_bool(value, &b);
+ option->values.ternary_val = b ? TERNARY_TRUE : TERNARY_FALSE;
+ if (validate && !parsed)
+ ereport(ERROR,
+ (errcode(ERRCODE_INVALID_PARAMETER_VALUE),
+ errmsg("invalid value for ternary option \"%s\": %s",
+ option->gen->name, value)));
+ }
+ break;
case RELOPT_TYPE_INT:
{
relopt_int *optint = (relopt_int *) option->gen;
@@ -1780,17 +1864,6 @@ fillRelOptions(void *rdopts, Size basesize,
char *itempos = ((char *) rdopts) + elems[j].offset;
char *string_val;
- /*
- * If isset_offset is provided, store whether the reloption is
- * set there.
- */
- if (elems[j].isset_offset > 0)
- {
- char *setpos = ((char *) rdopts) + elems[j].isset_offset;
-
- *(bool *) setpos = options[i].isset;
- }
-
switch (options[i].gen->type)
{
case RELOPT_TYPE_BOOL:
@@ -1798,6 +1871,11 @@ fillRelOptions(void *rdopts, Size basesize,
options[i].values.bool_val :
((relopt_bool *) options[i].gen)->default_val;
break;
+ case RELOPT_TYPE_TERNARY:
+ *(ternary *) itempos = options[i].isset ?
+ options[i].values.ternary_val :
+ ((relopt_ternary *) options[i].gen)->default_val;
+ break;
case RELOPT_TYPE_INT:
*(int *) itempos = options[i].isset ?
options[i].values.int_val :
@@ -1912,8 +1990,8 @@ default_reloptions(Datum reloptions, bool validate, relopt_kind kind)
offsetof(StdRdOptions, parallel_workers)},
{"vacuum_index_cleanup", RELOPT_TYPE_ENUM,
offsetof(StdRdOptions, vacuum_index_cleanup)},
- {"vacuum_truncate", RELOPT_TYPE_BOOL,
- offsetof(StdRdOptions, vacuum_truncate), offsetof(StdRdOptions, vacuum_truncate_set)},
+ {"vacuum_truncate", RELOPT_TYPE_TERNARY,
+ offsetof(StdRdOptions, vacuum_truncate)},
{"vacuum_max_eager_freeze_failure_rate", RELOPT_TYPE_REAL,
offsetof(StdRdOptions, vacuum_max_eager_freeze_failure_rate)}
};
@@ -1993,7 +2071,6 @@ build_local_reloptions(local_relopts *relopts, Datum options, bool validate)
elems[i].optname = opt->option->name;
elems[i].opttype = opt->option->type;
elems[i].offset = opt->offset;
- elems[i].isset_offset = 0; /* not supported for local relopts yet */
i++;
}
diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c
index 733ef40ae7..977babff54 100644
--- a/src/backend/commands/vacuum.c
+++ b/src/backend/commands/vacuum.c
@@ -2221,9 +2221,9 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams params,
{
StdRdOptions *opts = (StdRdOptions *) rel->rd_options;
- if (opts && opts->vacuum_truncate_set)
+ if (opts && opts->vacuum_truncate != TERNARY_UNSET)
{
- if (opts->vacuum_truncate)
+ if (opts->vacuum_truncate == TERNARY_TRUE)
params.truncate = VACOPTVALUE_ENABLED;
else
params.truncate = VACOPTVALUE_DISABLED;
diff --git a/src/include/access/reloptions.h b/src/include/access/reloptions.h
index a604a4702c..a436697658 100644
--- a/src/include/access/reloptions.h
+++ b/src/include/access/reloptions.h
@@ -29,6 +29,7 @@
typedef enum relopt_type
{
RELOPT_TYPE_BOOL,
+ RELOPT_TYPE_TERNARY, /* on, off, unset */
RELOPT_TYPE_INT,
RELOPT_TYPE_REAL,
RELOPT_TYPE_ENUM,
@@ -80,6 +81,7 @@ typedef struct relopt_value
union
{
bool bool_val;
+ ternary ternary_val;
int int_val;
double real_val;
int enum_val;
@@ -94,6 +96,12 @@ typedef struct relopt_bool
bool default_val;
} relopt_bool;
+typedef struct relopt_rernary
+{
+ relopt_gen gen;
+ int default_val;
+} relopt_ternary;
+
typedef struct relopt_int
{
relopt_gen gen;
@@ -152,19 +160,6 @@ typedef struct
const char *optname; /* option's name */
relopt_type opttype; /* option's datatype */
int offset; /* offset of field in result struct */
-
- /*
- * isset_offset is an optional offset of a field in the result struct that
- * stores whether the option is explicitly set for the relation or if it
- * just picked up the default value. In most cases, this can be
- * accomplished by giving the reloption a special out-of-range default
- * value (e.g., some integer reloptions use -2), but this isn't always
- * possible. For example, a Boolean reloption cannot be given an
- * out-of-range default, so we need another way to discover the source of
- * its value. This offset is only used if given a value greater than
- * zero.
- */
- int isset_offset;
} relopt_parse_elt;
/* Local reloption definition */
@@ -195,6 +190,8 @@ typedef struct local_relopts
extern relopt_kind add_reloption_kind(void);
extern void add_bool_reloption(bits32 kinds, const char *name, const char *desc,
bool default_val, LOCKMODE lockmode);
+extern void add_ternary_reloption(bits32 kinds, const char *name,
+ const char *desc, int default_val, LOCKMODE lockmode);
extern void add_int_reloption(bits32 kinds, const char *name, const char *desc,
int default_val, int min_val, int max_val,
LOCKMODE lockmode);
@@ -214,6 +211,9 @@ extern void register_reloptions_validator(local_relopts *relopts,
extern void add_local_bool_reloption(local_relopts *relopts, const char *name,
const char *desc, bool default_val,
int offset);
+extern void add_local_ternary_reloption(local_relopts *relopts,
+ const char *name, const char *desc,
+ ternary default_val, int offset);
extern void add_local_int_reloption(local_relopts *relopts, const char *name,
const char *desc, int default_val,
int min_val, int max_val, int offset);
diff --git a/src/include/c.h b/src/include/c.h
index 39022f8a9d..9c59ccccf8 100644
--- a/src/include/c.h
+++ b/src/include/c.h
@@ -488,6 +488,22 @@ typedef void (*pg_funcptr_t) (void);
#include <stdbool.h>
+/*
+ * ternary
+ * Boolean value with an extrea "unset" option
+ *
+ * Ternary data type is used in relation options that can be "true", "false" or
+ * "unset". Since relation options are used deep inside the PostgreSQL code,
+ * this type is declared globally.
+*/
+
+typedef enum ternary
+{
+ TERNARY_FALSE = 0,
+ TERNARY_TRUE = 1,
+ TERNARY_UNSET = -1
+} ternary;
+
/* ----------------------------------------------------------------
* Section 3: standard system types
diff --git a/src/include/utils/rel.h b/src/include/utils/rel.h
index b552359915..7346d618f9 100644
--- a/src/include/utils/rel.h
+++ b/src/include/utils/rel.h
@@ -346,8 +346,7 @@ typedef struct StdRdOptions
bool user_catalog_table; /* use as an additional catalog relation */
int parallel_workers; /* max number of parallel workers */
StdRdOptIndexCleanup vacuum_index_cleanup; /* controls index vacuuming */
- bool vacuum_truncate; /* enables vacuum to truncate a relation */
- bool vacuum_truncate_set; /* whether vacuum_truncate is set */
+ ternary vacuum_truncate; /* enables vacuum to truncate a relation */
/*
* Fraction of pages in a relation that vacuum can eagerly scan and fail
--
2.39.2
--nextPart4155600.3ZeAukHxDK
Content-Disposition: attachment;
filename="v2a-0003-Add-alias-to-be-used-as-unset-state.patch"
Content-Transfer-Encoding: 7Bit
Content-Type: text/x-patch; charset="unicode-2-0-utf-8";
name="v2a-0003-Add-alias-to-be-used-as-unset-state.patch"
^ permalink raw reply [nested|flat] 868+ messages in thread
* [PATCH v2 2/4] Introduce ternary reloptions
@ 2025-09-04 14:41 Nikolay Shaplov <[email protected]>
0 siblings, 0 replies; 868+ messages in thread
From: Nikolay Shaplov @ 2025-09-04 14:41 UTC (permalink / raw)
Introduce ternary reloption as a replacement for current `vacuum_truncate`
implementation. Remove `vacuum_truncate_set` additional flag and using
`TERNARY_UNSET` value instead.
---
src/backend/access/common/reloptions.c | 129 ++++++++++++++++++++-----
src/backend/commands/vacuum.c | 4 +-
src/include/access/reloptions.h | 26 ++---
src/include/c.h | 16 +++
src/include/utils/rel.h | 3 +-
5 files changed, 135 insertions(+), 43 deletions(-)
diff --git a/src/backend/access/common/reloptions.c b/src/backend/access/common/reloptions.c
index 0af3fea68f..f543f11001 100644
--- a/src/backend/access/common/reloptions.c
+++ b/src/backend/access/common/reloptions.c
@@ -40,9 +40,9 @@
*
* To add an option:
*
- * (i) decide on a type (bool, integer, real, enum, string), name, default
- * value, upper and lower bounds (if applicable); for strings, consider a
- * validation routine.
+ * (i) decide on a type (bool, ternary, integer, real, enum, string), name,
+ * default value, upper and lower bounds (if applicable); for strings,
+ * consider a validation routine.
* (ii) add a record below (or use add_<type>_reloption).
* (iii) add it to the appropriate options struct (perhaps StdRdOptions)
* (iv) add it to the appropriate handling routine (perhaps
@@ -147,15 +147,6 @@ static relopt_bool boolRelOpts[] =
},
false
},
- {
- {
- "vacuum_truncate",
- "Enables vacuum to truncate empty pages at the end of this table",
- RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
- ShareUpdateExclusiveLock
- },
- true
- },
{
{
"deduplicate_items",
@@ -170,6 +161,21 @@ static relopt_bool boolRelOpts[] =
{{NULL}}
};
+static relopt_ternary ternaryRelOpts[] =
+{
+ {
+ {
+ "vacuum_truncate",
+ "Enables vacuum to truncate empty pages at the end of this table",
+ RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
+ ShareUpdateExclusiveLock
+ },
+ TERNARY_UNSET
+ },
+ /* list terminator */
+ {{NULL}}
+};
+
static relopt_int intRelOpts[] =
{
{
@@ -600,6 +606,13 @@ initialize_reloptions(void)
boolRelOpts[i].gen.lockmode));
j++;
}
+ for (i = 0; ternaryRelOpts[i].gen.name; i++)
+ {
+ Assert(DoLockModesConflict(ternaryRelOpts[i].gen.lockmode,
+ ternaryRelOpts[i].gen.lockmode));
+ j++;
+ }
+
for (i = 0; intRelOpts[i].gen.name; i++)
{
Assert(DoLockModesConflict(intRelOpts[i].gen.lockmode,
@@ -640,6 +653,14 @@ initialize_reloptions(void)
j++;
}
+ for (i = 0; ternaryRelOpts[i].gen.name; i++)
+ {
+ relOpts[j] = &ternaryRelOpts[i].gen;
+ relOpts[j]->type = RELOPT_TYPE_TERNARY;
+ relOpts[j]->namelen = strlen(relOpts[j]->name);
+ j++;
+ }
+
for (i = 0; intRelOpts[i].gen.name; i++)
{
relOpts[j] = &intRelOpts[i].gen;
@@ -800,6 +821,9 @@ allocate_reloption(bits32 kinds, int type, const char *name, const char *desc,
case RELOPT_TYPE_BOOL:
size = sizeof(relopt_bool);
break;
+ case RELOPT_TYPE_TERNARY:
+ size = sizeof(relopt_ternary);
+ break;
case RELOPT_TYPE_INT:
size = sizeof(relopt_int);
break;
@@ -883,6 +907,54 @@ add_local_bool_reloption(local_relopts *relopts, const char *name,
add_local_reloption(relopts, (relopt_gen *) newoption, offset);
}
+/*
+ * init_ternary_reloption
+ * Allocate and initialize a new ternary reloption
+ */
+static relopt_ternary *
+init_ternary_reloption(bits32 kinds, const char *name, const char *desc,
+ ternary default_val, LOCKMODE lockmode)
+{
+ relopt_ternary *newoption;
+
+ newoption = (relopt_ternary *) allocate_reloption(kinds,
+ RELOPT_TYPE_TERNARY, name, desc, lockmode);
+ newoption->default_val = default_val;
+
+ return newoption;
+}
+
+/*
+ * add_ternary_reloption
+ * Add a new ternary reloption
+ */
+void
+add_ternary_reloption(bits32 kinds, const char *name, const char *desc,
+ ternary default_val, LOCKMODE lockmode)
+{
+ relopt_ternary *newoption = init_ternary_reloption(kinds, name, desc,
+ default_val, lockmode);
+
+ add_reloption((relopt_gen *) newoption);
+}
+
+/*
+ * add_local_ternary_reloption
+ * Add a new ternary local reloption
+ *
+ * 'offset' is offset of ternary-typed field.
+ */
+void
+add_local_ternary_reloption(local_relopts *relopts, const char *name,
+ const char *desc, ternary default_val,
+ int offset)
+{
+ relopt_ternary *newoption = init_ternary_reloption(RELOPT_KIND_LOCAL,
+ name, desc,
+ default_val, 0);
+
+ add_local_reloption(relopts, (relopt_gen *) newoption, offset);
+}
/*
* init_real_reloption
@@ -1617,6 +1689,18 @@ parse_one_reloption(relopt_value *option, char *text_str, int text_len,
option->gen->name, value)));
}
break;
+ case RELOPT_TYPE_TERNARY:
+ {
+ bool b;
+ parsed = parse_bool(value, &b);
+ option->values.ternary_val = b ? TERNARY_TRUE : TERNARY_FALSE;
+ if (validate && !parsed)
+ ereport(ERROR,
+ (errcode(ERRCODE_INVALID_PARAMETER_VALUE),
+ errmsg("invalid value for ternary option \"%s\": %s",
+ option->gen->name, value)));
+ }
+ break;
case RELOPT_TYPE_INT:
{
relopt_int *optint = (relopt_int *) option->gen;
@@ -1780,17 +1864,6 @@ fillRelOptions(void *rdopts, Size basesize,
char *itempos = ((char *) rdopts) + elems[j].offset;
char *string_val;
- /*
- * If isset_offset is provided, store whether the reloption is
- * set there.
- */
- if (elems[j].isset_offset > 0)
- {
- char *setpos = ((char *) rdopts) + elems[j].isset_offset;
-
- *(bool *) setpos = options[i].isset;
- }
-
switch (options[i].gen->type)
{
case RELOPT_TYPE_BOOL:
@@ -1798,6 +1871,11 @@ fillRelOptions(void *rdopts, Size basesize,
options[i].values.bool_val :
((relopt_bool *) options[i].gen)->default_val;
break;
+ case RELOPT_TYPE_TERNARY:
+ *(ternary *) itempos = options[i].isset ?
+ options[i].values.ternary_val :
+ ((relopt_ternary *) options[i].gen)->default_val;
+ break;
case RELOPT_TYPE_INT:
*(int *) itempos = options[i].isset ?
options[i].values.int_val :
@@ -1912,8 +1990,8 @@ default_reloptions(Datum reloptions, bool validate, relopt_kind kind)
offsetof(StdRdOptions, parallel_workers)},
{"vacuum_index_cleanup", RELOPT_TYPE_ENUM,
offsetof(StdRdOptions, vacuum_index_cleanup)},
- {"vacuum_truncate", RELOPT_TYPE_BOOL,
- offsetof(StdRdOptions, vacuum_truncate), offsetof(StdRdOptions, vacuum_truncate_set)},
+ {"vacuum_truncate", RELOPT_TYPE_TERNARY,
+ offsetof(StdRdOptions, vacuum_truncate)},
{"vacuum_max_eager_freeze_failure_rate", RELOPT_TYPE_REAL,
offsetof(StdRdOptions, vacuum_max_eager_freeze_failure_rate)}
};
@@ -1993,7 +2071,6 @@ build_local_reloptions(local_relopts *relopts, Datum options, bool validate)
elems[i].optname = opt->option->name;
elems[i].opttype = opt->option->type;
elems[i].offset = opt->offset;
- elems[i].isset_offset = 0; /* not supported for local relopts yet */
i++;
}
diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c
index 733ef40ae7..977babff54 100644
--- a/src/backend/commands/vacuum.c
+++ b/src/backend/commands/vacuum.c
@@ -2221,9 +2221,9 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams params,
{
StdRdOptions *opts = (StdRdOptions *) rel->rd_options;
- if (opts && opts->vacuum_truncate_set)
+ if (opts && opts->vacuum_truncate != TERNARY_UNSET)
{
- if (opts->vacuum_truncate)
+ if (opts->vacuum_truncate == TERNARY_TRUE)
params.truncate = VACOPTVALUE_ENABLED;
else
params.truncate = VACOPTVALUE_DISABLED;
diff --git a/src/include/access/reloptions.h b/src/include/access/reloptions.h
index a604a4702c..a436697658 100644
--- a/src/include/access/reloptions.h
+++ b/src/include/access/reloptions.h
@@ -29,6 +29,7 @@
typedef enum relopt_type
{
RELOPT_TYPE_BOOL,
+ RELOPT_TYPE_TERNARY, /* on, off, unset */
RELOPT_TYPE_INT,
RELOPT_TYPE_REAL,
RELOPT_TYPE_ENUM,
@@ -80,6 +81,7 @@ typedef struct relopt_value
union
{
bool bool_val;
+ ternary ternary_val;
int int_val;
double real_val;
int enum_val;
@@ -94,6 +96,12 @@ typedef struct relopt_bool
bool default_val;
} relopt_bool;
+typedef struct relopt_rernary
+{
+ relopt_gen gen;
+ int default_val;
+} relopt_ternary;
+
typedef struct relopt_int
{
relopt_gen gen;
@@ -152,19 +160,6 @@ typedef struct
const char *optname; /* option's name */
relopt_type opttype; /* option's datatype */
int offset; /* offset of field in result struct */
-
- /*
- * isset_offset is an optional offset of a field in the result struct that
- * stores whether the option is explicitly set for the relation or if it
- * just picked up the default value. In most cases, this can be
- * accomplished by giving the reloption a special out-of-range default
- * value (e.g., some integer reloptions use -2), but this isn't always
- * possible. For example, a Boolean reloption cannot be given an
- * out-of-range default, so we need another way to discover the source of
- * its value. This offset is only used if given a value greater than
- * zero.
- */
- int isset_offset;
} relopt_parse_elt;
/* Local reloption definition */
@@ -195,6 +190,8 @@ typedef struct local_relopts
extern relopt_kind add_reloption_kind(void);
extern void add_bool_reloption(bits32 kinds, const char *name, const char *desc,
bool default_val, LOCKMODE lockmode);
+extern void add_ternary_reloption(bits32 kinds, const char *name,
+ const char *desc, int default_val, LOCKMODE lockmode);
extern void add_int_reloption(bits32 kinds, const char *name, const char *desc,
int default_val, int min_val, int max_val,
LOCKMODE lockmode);
@@ -214,6 +211,9 @@ extern void register_reloptions_validator(local_relopts *relopts,
extern void add_local_bool_reloption(local_relopts *relopts, const char *name,
const char *desc, bool default_val,
int offset);
+extern void add_local_ternary_reloption(local_relopts *relopts,
+ const char *name, const char *desc,
+ ternary default_val, int offset);
extern void add_local_int_reloption(local_relopts *relopts, const char *name,
const char *desc, int default_val,
int min_val, int max_val, int offset);
diff --git a/src/include/c.h b/src/include/c.h
index 39022f8a9d..9c59ccccf8 100644
--- a/src/include/c.h
+++ b/src/include/c.h
@@ -488,6 +488,22 @@ typedef void (*pg_funcptr_t) (void);
#include <stdbool.h>
+/*
+ * ternary
+ * Boolean value with an extrea "unset" option
+ *
+ * Ternary data type is used in relation options that can be "true", "false" or
+ * "unset". Since relation options are used deep inside the PostgreSQL code,
+ * this type is declared globally.
+*/
+
+typedef enum ternary
+{
+ TERNARY_FALSE = 0,
+ TERNARY_TRUE = 1,
+ TERNARY_UNSET = -1
+} ternary;
+
/* ----------------------------------------------------------------
* Section 3: standard system types
diff --git a/src/include/utils/rel.h b/src/include/utils/rel.h
index b552359915..7346d618f9 100644
--- a/src/include/utils/rel.h
+++ b/src/include/utils/rel.h
@@ -346,8 +346,7 @@ typedef struct StdRdOptions
bool user_catalog_table; /* use as an additional catalog relation */
int parallel_workers; /* max number of parallel workers */
StdRdOptIndexCleanup vacuum_index_cleanup; /* controls index vacuuming */
- bool vacuum_truncate; /* enables vacuum to truncate a relation */
- bool vacuum_truncate_set; /* whether vacuum_truncate is set */
+ ternary vacuum_truncate; /* enables vacuum to truncate a relation */
/*
* Fraction of pages in a relation that vacuum can eagerly scan and fail
--
2.39.2
--nextPart5129932.5fSG56mABF
Content-Disposition: attachment;
filename="v2-0003-Add-alias-to-be-used-as-unset-state.patch"
Content-Transfer-Encoding: 7Bit
Content-Type: text/x-patch; charset="unicode-2-0-utf-8";
name="v2-0003-Add-alias-to-be-used-as-unset-state.patch"
^ permalink raw reply [nested|flat] 868+ messages in thread
* [PATCH v2 2/4] Introduce ternary reloptions
@ 2025-09-04 14:41 Nikolay Shaplov <[email protected]>
0 siblings, 0 replies; 868+ messages in thread
From: Nikolay Shaplov @ 2025-09-04 14:41 UTC (permalink / raw)
Introduce ternary reloption as a replacement for current `vacuum_truncate`
implementation. Remove `vacuum_truncate_set` additional flag and using
`TERNARY_UNSET` value instead.
---
src/backend/access/common/reloptions.c | 129 ++++++++++++++++++++-----
src/backend/commands/vacuum.c | 4 +-
src/include/access/reloptions.h | 26 ++---
src/include/c.h | 16 +++
src/include/utils/rel.h | 3 +-
5 files changed, 135 insertions(+), 43 deletions(-)
diff --git a/src/backend/access/common/reloptions.c b/src/backend/access/common/reloptions.c
index 0af3fea68f..f543f11001 100644
--- a/src/backend/access/common/reloptions.c
+++ b/src/backend/access/common/reloptions.c
@@ -40,9 +40,9 @@
*
* To add an option:
*
- * (i) decide on a type (bool, integer, real, enum, string), name, default
- * value, upper and lower bounds (if applicable); for strings, consider a
- * validation routine.
+ * (i) decide on a type (bool, ternary, integer, real, enum, string), name,
+ * default value, upper and lower bounds (if applicable); for strings,
+ * consider a validation routine.
* (ii) add a record below (or use add_<type>_reloption).
* (iii) add it to the appropriate options struct (perhaps StdRdOptions)
* (iv) add it to the appropriate handling routine (perhaps
@@ -147,15 +147,6 @@ static relopt_bool boolRelOpts[] =
},
false
},
- {
- {
- "vacuum_truncate",
- "Enables vacuum to truncate empty pages at the end of this table",
- RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
- ShareUpdateExclusiveLock
- },
- true
- },
{
{
"deduplicate_items",
@@ -170,6 +161,21 @@ static relopt_bool boolRelOpts[] =
{{NULL}}
};
+static relopt_ternary ternaryRelOpts[] =
+{
+ {
+ {
+ "vacuum_truncate",
+ "Enables vacuum to truncate empty pages at the end of this table",
+ RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
+ ShareUpdateExclusiveLock
+ },
+ TERNARY_UNSET
+ },
+ /* list terminator */
+ {{NULL}}
+};
+
static relopt_int intRelOpts[] =
{
{
@@ -600,6 +606,13 @@ initialize_reloptions(void)
boolRelOpts[i].gen.lockmode));
j++;
}
+ for (i = 0; ternaryRelOpts[i].gen.name; i++)
+ {
+ Assert(DoLockModesConflict(ternaryRelOpts[i].gen.lockmode,
+ ternaryRelOpts[i].gen.lockmode));
+ j++;
+ }
+
for (i = 0; intRelOpts[i].gen.name; i++)
{
Assert(DoLockModesConflict(intRelOpts[i].gen.lockmode,
@@ -640,6 +653,14 @@ initialize_reloptions(void)
j++;
}
+ for (i = 0; ternaryRelOpts[i].gen.name; i++)
+ {
+ relOpts[j] = &ternaryRelOpts[i].gen;
+ relOpts[j]->type = RELOPT_TYPE_TERNARY;
+ relOpts[j]->namelen = strlen(relOpts[j]->name);
+ j++;
+ }
+
for (i = 0; intRelOpts[i].gen.name; i++)
{
relOpts[j] = &intRelOpts[i].gen;
@@ -800,6 +821,9 @@ allocate_reloption(bits32 kinds, int type, const char *name, const char *desc,
case RELOPT_TYPE_BOOL:
size = sizeof(relopt_bool);
break;
+ case RELOPT_TYPE_TERNARY:
+ size = sizeof(relopt_ternary);
+ break;
case RELOPT_TYPE_INT:
size = sizeof(relopt_int);
break;
@@ -883,6 +907,54 @@ add_local_bool_reloption(local_relopts *relopts, const char *name,
add_local_reloption(relopts, (relopt_gen *) newoption, offset);
}
+/*
+ * init_ternary_reloption
+ * Allocate and initialize a new ternary reloption
+ */
+static relopt_ternary *
+init_ternary_reloption(bits32 kinds, const char *name, const char *desc,
+ ternary default_val, LOCKMODE lockmode)
+{
+ relopt_ternary *newoption;
+
+ newoption = (relopt_ternary *) allocate_reloption(kinds,
+ RELOPT_TYPE_TERNARY, name, desc, lockmode);
+ newoption->default_val = default_val;
+
+ return newoption;
+}
+
+/*
+ * add_ternary_reloption
+ * Add a new ternary reloption
+ */
+void
+add_ternary_reloption(bits32 kinds, const char *name, const char *desc,
+ ternary default_val, LOCKMODE lockmode)
+{
+ relopt_ternary *newoption = init_ternary_reloption(kinds, name, desc,
+ default_val, lockmode);
+
+ add_reloption((relopt_gen *) newoption);
+}
+
+/*
+ * add_local_ternary_reloption
+ * Add a new ternary local reloption
+ *
+ * 'offset' is offset of ternary-typed field.
+ */
+void
+add_local_ternary_reloption(local_relopts *relopts, const char *name,
+ const char *desc, ternary default_val,
+ int offset)
+{
+ relopt_ternary *newoption = init_ternary_reloption(RELOPT_KIND_LOCAL,
+ name, desc,
+ default_val, 0);
+
+ add_local_reloption(relopts, (relopt_gen *) newoption, offset);
+}
/*
* init_real_reloption
@@ -1617,6 +1689,18 @@ parse_one_reloption(relopt_value *option, char *text_str, int text_len,
option->gen->name, value)));
}
break;
+ case RELOPT_TYPE_TERNARY:
+ {
+ bool b;
+ parsed = parse_bool(value, &b);
+ option->values.ternary_val = b ? TERNARY_TRUE : TERNARY_FALSE;
+ if (validate && !parsed)
+ ereport(ERROR,
+ (errcode(ERRCODE_INVALID_PARAMETER_VALUE),
+ errmsg("invalid value for ternary option \"%s\": %s",
+ option->gen->name, value)));
+ }
+ break;
case RELOPT_TYPE_INT:
{
relopt_int *optint = (relopt_int *) option->gen;
@@ -1780,17 +1864,6 @@ fillRelOptions(void *rdopts, Size basesize,
char *itempos = ((char *) rdopts) + elems[j].offset;
char *string_val;
- /*
- * If isset_offset is provided, store whether the reloption is
- * set there.
- */
- if (elems[j].isset_offset > 0)
- {
- char *setpos = ((char *) rdopts) + elems[j].isset_offset;
-
- *(bool *) setpos = options[i].isset;
- }
-
switch (options[i].gen->type)
{
case RELOPT_TYPE_BOOL:
@@ -1798,6 +1871,11 @@ fillRelOptions(void *rdopts, Size basesize,
options[i].values.bool_val :
((relopt_bool *) options[i].gen)->default_val;
break;
+ case RELOPT_TYPE_TERNARY:
+ *(ternary *) itempos = options[i].isset ?
+ options[i].values.ternary_val :
+ ((relopt_ternary *) options[i].gen)->default_val;
+ break;
case RELOPT_TYPE_INT:
*(int *) itempos = options[i].isset ?
options[i].values.int_val :
@@ -1912,8 +1990,8 @@ default_reloptions(Datum reloptions, bool validate, relopt_kind kind)
offsetof(StdRdOptions, parallel_workers)},
{"vacuum_index_cleanup", RELOPT_TYPE_ENUM,
offsetof(StdRdOptions, vacuum_index_cleanup)},
- {"vacuum_truncate", RELOPT_TYPE_BOOL,
- offsetof(StdRdOptions, vacuum_truncate), offsetof(StdRdOptions, vacuum_truncate_set)},
+ {"vacuum_truncate", RELOPT_TYPE_TERNARY,
+ offsetof(StdRdOptions, vacuum_truncate)},
{"vacuum_max_eager_freeze_failure_rate", RELOPT_TYPE_REAL,
offsetof(StdRdOptions, vacuum_max_eager_freeze_failure_rate)}
};
@@ -1993,7 +2071,6 @@ build_local_reloptions(local_relopts *relopts, Datum options, bool validate)
elems[i].optname = opt->option->name;
elems[i].opttype = opt->option->type;
elems[i].offset = opt->offset;
- elems[i].isset_offset = 0; /* not supported for local relopts yet */
i++;
}
diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c
index 733ef40ae7..977babff54 100644
--- a/src/backend/commands/vacuum.c
+++ b/src/backend/commands/vacuum.c
@@ -2221,9 +2221,9 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams params,
{
StdRdOptions *opts = (StdRdOptions *) rel->rd_options;
- if (opts && opts->vacuum_truncate_set)
+ if (opts && opts->vacuum_truncate != TERNARY_UNSET)
{
- if (opts->vacuum_truncate)
+ if (opts->vacuum_truncate == TERNARY_TRUE)
params.truncate = VACOPTVALUE_ENABLED;
else
params.truncate = VACOPTVALUE_DISABLED;
diff --git a/src/include/access/reloptions.h b/src/include/access/reloptions.h
index a604a4702c..a436697658 100644
--- a/src/include/access/reloptions.h
+++ b/src/include/access/reloptions.h
@@ -29,6 +29,7 @@
typedef enum relopt_type
{
RELOPT_TYPE_BOOL,
+ RELOPT_TYPE_TERNARY, /* on, off, unset */
RELOPT_TYPE_INT,
RELOPT_TYPE_REAL,
RELOPT_TYPE_ENUM,
@@ -80,6 +81,7 @@ typedef struct relopt_value
union
{
bool bool_val;
+ ternary ternary_val;
int int_val;
double real_val;
int enum_val;
@@ -94,6 +96,12 @@ typedef struct relopt_bool
bool default_val;
} relopt_bool;
+typedef struct relopt_rernary
+{
+ relopt_gen gen;
+ int default_val;
+} relopt_ternary;
+
typedef struct relopt_int
{
relopt_gen gen;
@@ -152,19 +160,6 @@ typedef struct
const char *optname; /* option's name */
relopt_type opttype; /* option's datatype */
int offset; /* offset of field in result struct */
-
- /*
- * isset_offset is an optional offset of a field in the result struct that
- * stores whether the option is explicitly set for the relation or if it
- * just picked up the default value. In most cases, this can be
- * accomplished by giving the reloption a special out-of-range default
- * value (e.g., some integer reloptions use -2), but this isn't always
- * possible. For example, a Boolean reloption cannot be given an
- * out-of-range default, so we need another way to discover the source of
- * its value. This offset is only used if given a value greater than
- * zero.
- */
- int isset_offset;
} relopt_parse_elt;
/* Local reloption definition */
@@ -195,6 +190,8 @@ typedef struct local_relopts
extern relopt_kind add_reloption_kind(void);
extern void add_bool_reloption(bits32 kinds, const char *name, const char *desc,
bool default_val, LOCKMODE lockmode);
+extern void add_ternary_reloption(bits32 kinds, const char *name,
+ const char *desc, int default_val, LOCKMODE lockmode);
extern void add_int_reloption(bits32 kinds, const char *name, const char *desc,
int default_val, int min_val, int max_val,
LOCKMODE lockmode);
@@ -214,6 +211,9 @@ extern void register_reloptions_validator(local_relopts *relopts,
extern void add_local_bool_reloption(local_relopts *relopts, const char *name,
const char *desc, bool default_val,
int offset);
+extern void add_local_ternary_reloption(local_relopts *relopts,
+ const char *name, const char *desc,
+ ternary default_val, int offset);
extern void add_local_int_reloption(local_relopts *relopts, const char *name,
const char *desc, int default_val,
int min_val, int max_val, int offset);
diff --git a/src/include/c.h b/src/include/c.h
index 39022f8a9d..9c59ccccf8 100644
--- a/src/include/c.h
+++ b/src/include/c.h
@@ -488,6 +488,22 @@ typedef void (*pg_funcptr_t) (void);
#include <stdbool.h>
+/*
+ * ternary
+ * Boolean value with an extrea "unset" option
+ *
+ * Ternary data type is used in relation options that can be "true", "false" or
+ * "unset". Since relation options are used deep inside the PostgreSQL code,
+ * this type is declared globally.
+*/
+
+typedef enum ternary
+{
+ TERNARY_FALSE = 0,
+ TERNARY_TRUE = 1,
+ TERNARY_UNSET = -1
+} ternary;
+
/* ----------------------------------------------------------------
* Section 3: standard system types
diff --git a/src/include/utils/rel.h b/src/include/utils/rel.h
index b552359915..7346d618f9 100644
--- a/src/include/utils/rel.h
+++ b/src/include/utils/rel.h
@@ -346,8 +346,7 @@ typedef struct StdRdOptions
bool user_catalog_table; /* use as an additional catalog relation */
int parallel_workers; /* max number of parallel workers */
StdRdOptIndexCleanup vacuum_index_cleanup; /* controls index vacuuming */
- bool vacuum_truncate; /* enables vacuum to truncate a relation */
- bool vacuum_truncate_set; /* whether vacuum_truncate is set */
+ ternary vacuum_truncate; /* enables vacuum to truncate a relation */
/*
* Fraction of pages in a relation that vacuum can eagerly scan and fail
--
2.39.2
--nextPart5129932.5fSG56mABF
Content-Disposition: attachment;
filename="v2-0003-Add-alias-to-be-used-as-unset-state.patch"
Content-Transfer-Encoding: 7Bit
Content-Type: text/x-patch; charset="unicode-2-0-utf-8";
name="v2-0003-Add-alias-to-be-used-as-unset-state.patch"
^ permalink raw reply [nested|flat] 868+ messages in thread
* [PATCH v2a 2/4] Introduce ternary reloptions
@ 2025-09-04 14:41 Nikolay Shaplov <[email protected]>
0 siblings, 0 replies; 868+ messages in thread
From: Nikolay Shaplov @ 2025-09-04 14:41 UTC (permalink / raw)
Introduce ternary reloption as a replacement for current `vacuum_truncate`
implementation. Remove `vacuum_truncate_set` additional flag and using
`TERNARY_UNSET` value instead.
---
src/backend/access/common/reloptions.c | 129 ++++++++++++++++++++-----
src/backend/commands/vacuum.c | 4 +-
src/include/access/reloptions.h | 26 ++---
src/include/c.h | 16 +++
src/include/utils/rel.h | 3 +-
5 files changed, 135 insertions(+), 43 deletions(-)
diff --git a/src/backend/access/common/reloptions.c b/src/backend/access/common/reloptions.c
index 0af3fea68f..f543f11001 100644
--- a/src/backend/access/common/reloptions.c
+++ b/src/backend/access/common/reloptions.c
@@ -40,9 +40,9 @@
*
* To add an option:
*
- * (i) decide on a type (bool, integer, real, enum, string), name, default
- * value, upper and lower bounds (if applicable); for strings, consider a
- * validation routine.
+ * (i) decide on a type (bool, ternary, integer, real, enum, string), name,
+ * default value, upper and lower bounds (if applicable); for strings,
+ * consider a validation routine.
* (ii) add a record below (or use add_<type>_reloption).
* (iii) add it to the appropriate options struct (perhaps StdRdOptions)
* (iv) add it to the appropriate handling routine (perhaps
@@ -147,15 +147,6 @@ static relopt_bool boolRelOpts[] =
},
false
},
- {
- {
- "vacuum_truncate",
- "Enables vacuum to truncate empty pages at the end of this table",
- RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
- ShareUpdateExclusiveLock
- },
- true
- },
{
{
"deduplicate_items",
@@ -170,6 +161,21 @@ static relopt_bool boolRelOpts[] =
{{NULL}}
};
+static relopt_ternary ternaryRelOpts[] =
+{
+ {
+ {
+ "vacuum_truncate",
+ "Enables vacuum to truncate empty pages at the end of this table",
+ RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
+ ShareUpdateExclusiveLock
+ },
+ TERNARY_UNSET
+ },
+ /* list terminator */
+ {{NULL}}
+};
+
static relopt_int intRelOpts[] =
{
{
@@ -600,6 +606,13 @@ initialize_reloptions(void)
boolRelOpts[i].gen.lockmode));
j++;
}
+ for (i = 0; ternaryRelOpts[i].gen.name; i++)
+ {
+ Assert(DoLockModesConflict(ternaryRelOpts[i].gen.lockmode,
+ ternaryRelOpts[i].gen.lockmode));
+ j++;
+ }
+
for (i = 0; intRelOpts[i].gen.name; i++)
{
Assert(DoLockModesConflict(intRelOpts[i].gen.lockmode,
@@ -640,6 +653,14 @@ initialize_reloptions(void)
j++;
}
+ for (i = 0; ternaryRelOpts[i].gen.name; i++)
+ {
+ relOpts[j] = &ternaryRelOpts[i].gen;
+ relOpts[j]->type = RELOPT_TYPE_TERNARY;
+ relOpts[j]->namelen = strlen(relOpts[j]->name);
+ j++;
+ }
+
for (i = 0; intRelOpts[i].gen.name; i++)
{
relOpts[j] = &intRelOpts[i].gen;
@@ -800,6 +821,9 @@ allocate_reloption(bits32 kinds, int type, const char *name, const char *desc,
case RELOPT_TYPE_BOOL:
size = sizeof(relopt_bool);
break;
+ case RELOPT_TYPE_TERNARY:
+ size = sizeof(relopt_ternary);
+ break;
case RELOPT_TYPE_INT:
size = sizeof(relopt_int);
break;
@@ -883,6 +907,54 @@ add_local_bool_reloption(local_relopts *relopts, const char *name,
add_local_reloption(relopts, (relopt_gen *) newoption, offset);
}
+/*
+ * init_ternary_reloption
+ * Allocate and initialize a new ternary reloption
+ */
+static relopt_ternary *
+init_ternary_reloption(bits32 kinds, const char *name, const char *desc,
+ ternary default_val, LOCKMODE lockmode)
+{
+ relopt_ternary *newoption;
+
+ newoption = (relopt_ternary *) allocate_reloption(kinds,
+ RELOPT_TYPE_TERNARY, name, desc, lockmode);
+ newoption->default_val = default_val;
+
+ return newoption;
+}
+
+/*
+ * add_ternary_reloption
+ * Add a new ternary reloption
+ */
+void
+add_ternary_reloption(bits32 kinds, const char *name, const char *desc,
+ ternary default_val, LOCKMODE lockmode)
+{
+ relopt_ternary *newoption = init_ternary_reloption(kinds, name, desc,
+ default_val, lockmode);
+
+ add_reloption((relopt_gen *) newoption);
+}
+
+/*
+ * add_local_ternary_reloption
+ * Add a new ternary local reloption
+ *
+ * 'offset' is offset of ternary-typed field.
+ */
+void
+add_local_ternary_reloption(local_relopts *relopts, const char *name,
+ const char *desc, ternary default_val,
+ int offset)
+{
+ relopt_ternary *newoption = init_ternary_reloption(RELOPT_KIND_LOCAL,
+ name, desc,
+ default_val, 0);
+
+ add_local_reloption(relopts, (relopt_gen *) newoption, offset);
+}
/*
* init_real_reloption
@@ -1617,6 +1689,18 @@ parse_one_reloption(relopt_value *option, char *text_str, int text_len,
option->gen->name, value)));
}
break;
+ case RELOPT_TYPE_TERNARY:
+ {
+ bool b;
+ parsed = parse_bool(value, &b);
+ option->values.ternary_val = b ? TERNARY_TRUE : TERNARY_FALSE;
+ if (validate && !parsed)
+ ereport(ERROR,
+ (errcode(ERRCODE_INVALID_PARAMETER_VALUE),
+ errmsg("invalid value for ternary option \"%s\": %s",
+ option->gen->name, value)));
+ }
+ break;
case RELOPT_TYPE_INT:
{
relopt_int *optint = (relopt_int *) option->gen;
@@ -1780,17 +1864,6 @@ fillRelOptions(void *rdopts, Size basesize,
char *itempos = ((char *) rdopts) + elems[j].offset;
char *string_val;
- /*
- * If isset_offset is provided, store whether the reloption is
- * set there.
- */
- if (elems[j].isset_offset > 0)
- {
- char *setpos = ((char *) rdopts) + elems[j].isset_offset;
-
- *(bool *) setpos = options[i].isset;
- }
-
switch (options[i].gen->type)
{
case RELOPT_TYPE_BOOL:
@@ -1798,6 +1871,11 @@ fillRelOptions(void *rdopts, Size basesize,
options[i].values.bool_val :
((relopt_bool *) options[i].gen)->default_val;
break;
+ case RELOPT_TYPE_TERNARY:
+ *(ternary *) itempos = options[i].isset ?
+ options[i].values.ternary_val :
+ ((relopt_ternary *) options[i].gen)->default_val;
+ break;
case RELOPT_TYPE_INT:
*(int *) itempos = options[i].isset ?
options[i].values.int_val :
@@ -1912,8 +1990,8 @@ default_reloptions(Datum reloptions, bool validate, relopt_kind kind)
offsetof(StdRdOptions, parallel_workers)},
{"vacuum_index_cleanup", RELOPT_TYPE_ENUM,
offsetof(StdRdOptions, vacuum_index_cleanup)},
- {"vacuum_truncate", RELOPT_TYPE_BOOL,
- offsetof(StdRdOptions, vacuum_truncate), offsetof(StdRdOptions, vacuum_truncate_set)},
+ {"vacuum_truncate", RELOPT_TYPE_TERNARY,
+ offsetof(StdRdOptions, vacuum_truncate)},
{"vacuum_max_eager_freeze_failure_rate", RELOPT_TYPE_REAL,
offsetof(StdRdOptions, vacuum_max_eager_freeze_failure_rate)}
};
@@ -1993,7 +2071,6 @@ build_local_reloptions(local_relopts *relopts, Datum options, bool validate)
elems[i].optname = opt->option->name;
elems[i].opttype = opt->option->type;
elems[i].offset = opt->offset;
- elems[i].isset_offset = 0; /* not supported for local relopts yet */
i++;
}
diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c
index 733ef40ae7..977babff54 100644
--- a/src/backend/commands/vacuum.c
+++ b/src/backend/commands/vacuum.c
@@ -2221,9 +2221,9 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams params,
{
StdRdOptions *opts = (StdRdOptions *) rel->rd_options;
- if (opts && opts->vacuum_truncate_set)
+ if (opts && opts->vacuum_truncate != TERNARY_UNSET)
{
- if (opts->vacuum_truncate)
+ if (opts->vacuum_truncate == TERNARY_TRUE)
params.truncate = VACOPTVALUE_ENABLED;
else
params.truncate = VACOPTVALUE_DISABLED;
diff --git a/src/include/access/reloptions.h b/src/include/access/reloptions.h
index a604a4702c..a436697658 100644
--- a/src/include/access/reloptions.h
+++ b/src/include/access/reloptions.h
@@ -29,6 +29,7 @@
typedef enum relopt_type
{
RELOPT_TYPE_BOOL,
+ RELOPT_TYPE_TERNARY, /* on, off, unset */
RELOPT_TYPE_INT,
RELOPT_TYPE_REAL,
RELOPT_TYPE_ENUM,
@@ -80,6 +81,7 @@ typedef struct relopt_value
union
{
bool bool_val;
+ ternary ternary_val;
int int_val;
double real_val;
int enum_val;
@@ -94,6 +96,12 @@ typedef struct relopt_bool
bool default_val;
} relopt_bool;
+typedef struct relopt_rernary
+{
+ relopt_gen gen;
+ int default_val;
+} relopt_ternary;
+
typedef struct relopt_int
{
relopt_gen gen;
@@ -152,19 +160,6 @@ typedef struct
const char *optname; /* option's name */
relopt_type opttype; /* option's datatype */
int offset; /* offset of field in result struct */
-
- /*
- * isset_offset is an optional offset of a field in the result struct that
- * stores whether the option is explicitly set for the relation or if it
- * just picked up the default value. In most cases, this can be
- * accomplished by giving the reloption a special out-of-range default
- * value (e.g., some integer reloptions use -2), but this isn't always
- * possible. For example, a Boolean reloption cannot be given an
- * out-of-range default, so we need another way to discover the source of
- * its value. This offset is only used if given a value greater than
- * zero.
- */
- int isset_offset;
} relopt_parse_elt;
/* Local reloption definition */
@@ -195,6 +190,8 @@ typedef struct local_relopts
extern relopt_kind add_reloption_kind(void);
extern void add_bool_reloption(bits32 kinds, const char *name, const char *desc,
bool default_val, LOCKMODE lockmode);
+extern void add_ternary_reloption(bits32 kinds, const char *name,
+ const char *desc, int default_val, LOCKMODE lockmode);
extern void add_int_reloption(bits32 kinds, const char *name, const char *desc,
int default_val, int min_val, int max_val,
LOCKMODE lockmode);
@@ -214,6 +211,9 @@ extern void register_reloptions_validator(local_relopts *relopts,
extern void add_local_bool_reloption(local_relopts *relopts, const char *name,
const char *desc, bool default_val,
int offset);
+extern void add_local_ternary_reloption(local_relopts *relopts,
+ const char *name, const char *desc,
+ ternary default_val, int offset);
extern void add_local_int_reloption(local_relopts *relopts, const char *name,
const char *desc, int default_val,
int min_val, int max_val, int offset);
diff --git a/src/include/c.h b/src/include/c.h
index 39022f8a9d..9c59ccccf8 100644
--- a/src/include/c.h
+++ b/src/include/c.h
@@ -488,6 +488,22 @@ typedef void (*pg_funcptr_t) (void);
#include <stdbool.h>
+/*
+ * ternary
+ * Boolean value with an extrea "unset" option
+ *
+ * Ternary data type is used in relation options that can be "true", "false" or
+ * "unset". Since relation options are used deep inside the PostgreSQL code,
+ * this type is declared globally.
+*/
+
+typedef enum ternary
+{
+ TERNARY_FALSE = 0,
+ TERNARY_TRUE = 1,
+ TERNARY_UNSET = -1
+} ternary;
+
/* ----------------------------------------------------------------
* Section 3: standard system types
diff --git a/src/include/utils/rel.h b/src/include/utils/rel.h
index b552359915..7346d618f9 100644
--- a/src/include/utils/rel.h
+++ b/src/include/utils/rel.h
@@ -346,8 +346,7 @@ typedef struct StdRdOptions
bool user_catalog_table; /* use as an additional catalog relation */
int parallel_workers; /* max number of parallel workers */
StdRdOptIndexCleanup vacuum_index_cleanup; /* controls index vacuuming */
- bool vacuum_truncate; /* enables vacuum to truncate a relation */
- bool vacuum_truncate_set; /* whether vacuum_truncate is set */
+ ternary vacuum_truncate; /* enables vacuum to truncate a relation */
/*
* Fraction of pages in a relation that vacuum can eagerly scan and fail
--
2.39.2
--nextPart4155600.3ZeAukHxDK
Content-Disposition: attachment;
filename="v2a-0003-Add-alias-to-be-used-as-unset-state.patch"
Content-Transfer-Encoding: 7Bit
Content-Type: text/x-patch; charset="unicode-2-0-utf-8";
name="v2a-0003-Add-alias-to-be-used-as-unset-state.patch"
^ permalink raw reply [nested|flat] 868+ messages in thread
* [PATCH v2 2/4] Introduce ternary reloptions
@ 2025-09-04 14:41 Nikolay Shaplov <[email protected]>
0 siblings, 0 replies; 868+ messages in thread
From: Nikolay Shaplov @ 2025-09-04 14:41 UTC (permalink / raw)
Introduce ternary reloption as a replacement for current `vacuum_truncate`
implementation. Remove `vacuum_truncate_set` additional flag and using
`TERNARY_UNSET` value instead.
---
src/backend/access/common/reloptions.c | 129 ++++++++++++++++++++-----
src/backend/commands/vacuum.c | 4 +-
src/include/access/reloptions.h | 26 ++---
src/include/c.h | 16 +++
src/include/utils/rel.h | 3 +-
5 files changed, 135 insertions(+), 43 deletions(-)
diff --git a/src/backend/access/common/reloptions.c b/src/backend/access/common/reloptions.c
index 0af3fea68f..f543f11001 100644
--- a/src/backend/access/common/reloptions.c
+++ b/src/backend/access/common/reloptions.c
@@ -40,9 +40,9 @@
*
* To add an option:
*
- * (i) decide on a type (bool, integer, real, enum, string), name, default
- * value, upper and lower bounds (if applicable); for strings, consider a
- * validation routine.
+ * (i) decide on a type (bool, ternary, integer, real, enum, string), name,
+ * default value, upper and lower bounds (if applicable); for strings,
+ * consider a validation routine.
* (ii) add a record below (or use add_<type>_reloption).
* (iii) add it to the appropriate options struct (perhaps StdRdOptions)
* (iv) add it to the appropriate handling routine (perhaps
@@ -147,15 +147,6 @@ static relopt_bool boolRelOpts[] =
},
false
},
- {
- {
- "vacuum_truncate",
- "Enables vacuum to truncate empty pages at the end of this table",
- RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
- ShareUpdateExclusiveLock
- },
- true
- },
{
{
"deduplicate_items",
@@ -170,6 +161,21 @@ static relopt_bool boolRelOpts[] =
{{NULL}}
};
+static relopt_ternary ternaryRelOpts[] =
+{
+ {
+ {
+ "vacuum_truncate",
+ "Enables vacuum to truncate empty pages at the end of this table",
+ RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
+ ShareUpdateExclusiveLock
+ },
+ TERNARY_UNSET
+ },
+ /* list terminator */
+ {{NULL}}
+};
+
static relopt_int intRelOpts[] =
{
{
@@ -600,6 +606,13 @@ initialize_reloptions(void)
boolRelOpts[i].gen.lockmode));
j++;
}
+ for (i = 0; ternaryRelOpts[i].gen.name; i++)
+ {
+ Assert(DoLockModesConflict(ternaryRelOpts[i].gen.lockmode,
+ ternaryRelOpts[i].gen.lockmode));
+ j++;
+ }
+
for (i = 0; intRelOpts[i].gen.name; i++)
{
Assert(DoLockModesConflict(intRelOpts[i].gen.lockmode,
@@ -640,6 +653,14 @@ initialize_reloptions(void)
j++;
}
+ for (i = 0; ternaryRelOpts[i].gen.name; i++)
+ {
+ relOpts[j] = &ternaryRelOpts[i].gen;
+ relOpts[j]->type = RELOPT_TYPE_TERNARY;
+ relOpts[j]->namelen = strlen(relOpts[j]->name);
+ j++;
+ }
+
for (i = 0; intRelOpts[i].gen.name; i++)
{
relOpts[j] = &intRelOpts[i].gen;
@@ -800,6 +821,9 @@ allocate_reloption(bits32 kinds, int type, const char *name, const char *desc,
case RELOPT_TYPE_BOOL:
size = sizeof(relopt_bool);
break;
+ case RELOPT_TYPE_TERNARY:
+ size = sizeof(relopt_ternary);
+ break;
case RELOPT_TYPE_INT:
size = sizeof(relopt_int);
break;
@@ -883,6 +907,54 @@ add_local_bool_reloption(local_relopts *relopts, const char *name,
add_local_reloption(relopts, (relopt_gen *) newoption, offset);
}
+/*
+ * init_ternary_reloption
+ * Allocate and initialize a new ternary reloption
+ */
+static relopt_ternary *
+init_ternary_reloption(bits32 kinds, const char *name, const char *desc,
+ ternary default_val, LOCKMODE lockmode)
+{
+ relopt_ternary *newoption;
+
+ newoption = (relopt_ternary *) allocate_reloption(kinds,
+ RELOPT_TYPE_TERNARY, name, desc, lockmode);
+ newoption->default_val = default_val;
+
+ return newoption;
+}
+
+/*
+ * add_ternary_reloption
+ * Add a new ternary reloption
+ */
+void
+add_ternary_reloption(bits32 kinds, const char *name, const char *desc,
+ ternary default_val, LOCKMODE lockmode)
+{
+ relopt_ternary *newoption = init_ternary_reloption(kinds, name, desc,
+ default_val, lockmode);
+
+ add_reloption((relopt_gen *) newoption);
+}
+
+/*
+ * add_local_ternary_reloption
+ * Add a new ternary local reloption
+ *
+ * 'offset' is offset of ternary-typed field.
+ */
+void
+add_local_ternary_reloption(local_relopts *relopts, const char *name,
+ const char *desc, ternary default_val,
+ int offset)
+{
+ relopt_ternary *newoption = init_ternary_reloption(RELOPT_KIND_LOCAL,
+ name, desc,
+ default_val, 0);
+
+ add_local_reloption(relopts, (relopt_gen *) newoption, offset);
+}
/*
* init_real_reloption
@@ -1617,6 +1689,18 @@ parse_one_reloption(relopt_value *option, char *text_str, int text_len,
option->gen->name, value)));
}
break;
+ case RELOPT_TYPE_TERNARY:
+ {
+ bool b;
+ parsed = parse_bool(value, &b);
+ option->values.ternary_val = b ? TERNARY_TRUE : TERNARY_FALSE;
+ if (validate && !parsed)
+ ereport(ERROR,
+ (errcode(ERRCODE_INVALID_PARAMETER_VALUE),
+ errmsg("invalid value for ternary option \"%s\": %s",
+ option->gen->name, value)));
+ }
+ break;
case RELOPT_TYPE_INT:
{
relopt_int *optint = (relopt_int *) option->gen;
@@ -1780,17 +1864,6 @@ fillRelOptions(void *rdopts, Size basesize,
char *itempos = ((char *) rdopts) + elems[j].offset;
char *string_val;
- /*
- * If isset_offset is provided, store whether the reloption is
- * set there.
- */
- if (elems[j].isset_offset > 0)
- {
- char *setpos = ((char *) rdopts) + elems[j].isset_offset;
-
- *(bool *) setpos = options[i].isset;
- }
-
switch (options[i].gen->type)
{
case RELOPT_TYPE_BOOL:
@@ -1798,6 +1871,11 @@ fillRelOptions(void *rdopts, Size basesize,
options[i].values.bool_val :
((relopt_bool *) options[i].gen)->default_val;
break;
+ case RELOPT_TYPE_TERNARY:
+ *(ternary *) itempos = options[i].isset ?
+ options[i].values.ternary_val :
+ ((relopt_ternary *) options[i].gen)->default_val;
+ break;
case RELOPT_TYPE_INT:
*(int *) itempos = options[i].isset ?
options[i].values.int_val :
@@ -1912,8 +1990,8 @@ default_reloptions(Datum reloptions, bool validate, relopt_kind kind)
offsetof(StdRdOptions, parallel_workers)},
{"vacuum_index_cleanup", RELOPT_TYPE_ENUM,
offsetof(StdRdOptions, vacuum_index_cleanup)},
- {"vacuum_truncate", RELOPT_TYPE_BOOL,
- offsetof(StdRdOptions, vacuum_truncate), offsetof(StdRdOptions, vacuum_truncate_set)},
+ {"vacuum_truncate", RELOPT_TYPE_TERNARY,
+ offsetof(StdRdOptions, vacuum_truncate)},
{"vacuum_max_eager_freeze_failure_rate", RELOPT_TYPE_REAL,
offsetof(StdRdOptions, vacuum_max_eager_freeze_failure_rate)}
};
@@ -1993,7 +2071,6 @@ build_local_reloptions(local_relopts *relopts, Datum options, bool validate)
elems[i].optname = opt->option->name;
elems[i].opttype = opt->option->type;
elems[i].offset = opt->offset;
- elems[i].isset_offset = 0; /* not supported for local relopts yet */
i++;
}
diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c
index 733ef40ae7..977babff54 100644
--- a/src/backend/commands/vacuum.c
+++ b/src/backend/commands/vacuum.c
@@ -2221,9 +2221,9 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams params,
{
StdRdOptions *opts = (StdRdOptions *) rel->rd_options;
- if (opts && opts->vacuum_truncate_set)
+ if (opts && opts->vacuum_truncate != TERNARY_UNSET)
{
- if (opts->vacuum_truncate)
+ if (opts->vacuum_truncate == TERNARY_TRUE)
params.truncate = VACOPTVALUE_ENABLED;
else
params.truncate = VACOPTVALUE_DISABLED;
diff --git a/src/include/access/reloptions.h b/src/include/access/reloptions.h
index a604a4702c..a436697658 100644
--- a/src/include/access/reloptions.h
+++ b/src/include/access/reloptions.h
@@ -29,6 +29,7 @@
typedef enum relopt_type
{
RELOPT_TYPE_BOOL,
+ RELOPT_TYPE_TERNARY, /* on, off, unset */
RELOPT_TYPE_INT,
RELOPT_TYPE_REAL,
RELOPT_TYPE_ENUM,
@@ -80,6 +81,7 @@ typedef struct relopt_value
union
{
bool bool_val;
+ ternary ternary_val;
int int_val;
double real_val;
int enum_val;
@@ -94,6 +96,12 @@ typedef struct relopt_bool
bool default_val;
} relopt_bool;
+typedef struct relopt_rernary
+{
+ relopt_gen gen;
+ int default_val;
+} relopt_ternary;
+
typedef struct relopt_int
{
relopt_gen gen;
@@ -152,19 +160,6 @@ typedef struct
const char *optname; /* option's name */
relopt_type opttype; /* option's datatype */
int offset; /* offset of field in result struct */
-
- /*
- * isset_offset is an optional offset of a field in the result struct that
- * stores whether the option is explicitly set for the relation or if it
- * just picked up the default value. In most cases, this can be
- * accomplished by giving the reloption a special out-of-range default
- * value (e.g., some integer reloptions use -2), but this isn't always
- * possible. For example, a Boolean reloption cannot be given an
- * out-of-range default, so we need another way to discover the source of
- * its value. This offset is only used if given a value greater than
- * zero.
- */
- int isset_offset;
} relopt_parse_elt;
/* Local reloption definition */
@@ -195,6 +190,8 @@ typedef struct local_relopts
extern relopt_kind add_reloption_kind(void);
extern void add_bool_reloption(bits32 kinds, const char *name, const char *desc,
bool default_val, LOCKMODE lockmode);
+extern void add_ternary_reloption(bits32 kinds, const char *name,
+ const char *desc, int default_val, LOCKMODE lockmode);
extern void add_int_reloption(bits32 kinds, const char *name, const char *desc,
int default_val, int min_val, int max_val,
LOCKMODE lockmode);
@@ -214,6 +211,9 @@ extern void register_reloptions_validator(local_relopts *relopts,
extern void add_local_bool_reloption(local_relopts *relopts, const char *name,
const char *desc, bool default_val,
int offset);
+extern void add_local_ternary_reloption(local_relopts *relopts,
+ const char *name, const char *desc,
+ ternary default_val, int offset);
extern void add_local_int_reloption(local_relopts *relopts, const char *name,
const char *desc, int default_val,
int min_val, int max_val, int offset);
diff --git a/src/include/c.h b/src/include/c.h
index 39022f8a9d..9c59ccccf8 100644
--- a/src/include/c.h
+++ b/src/include/c.h
@@ -488,6 +488,22 @@ typedef void (*pg_funcptr_t) (void);
#include <stdbool.h>
+/*
+ * ternary
+ * Boolean value with an extrea "unset" option
+ *
+ * Ternary data type is used in relation options that can be "true", "false" or
+ * "unset". Since relation options are used deep inside the PostgreSQL code,
+ * this type is declared globally.
+*/
+
+typedef enum ternary
+{
+ TERNARY_FALSE = 0,
+ TERNARY_TRUE = 1,
+ TERNARY_UNSET = -1
+} ternary;
+
/* ----------------------------------------------------------------
* Section 3: standard system types
diff --git a/src/include/utils/rel.h b/src/include/utils/rel.h
index b552359915..7346d618f9 100644
--- a/src/include/utils/rel.h
+++ b/src/include/utils/rel.h
@@ -346,8 +346,7 @@ typedef struct StdRdOptions
bool user_catalog_table; /* use as an additional catalog relation */
int parallel_workers; /* max number of parallel workers */
StdRdOptIndexCleanup vacuum_index_cleanup; /* controls index vacuuming */
- bool vacuum_truncate; /* enables vacuum to truncate a relation */
- bool vacuum_truncate_set; /* whether vacuum_truncate is set */
+ ternary vacuum_truncate; /* enables vacuum to truncate a relation */
/*
* Fraction of pages in a relation that vacuum can eagerly scan and fail
--
2.39.2
--nextPart5129932.5fSG56mABF
Content-Disposition: attachment;
filename="v2-0003-Add-alias-to-be-used-as-unset-state.patch"
Content-Transfer-Encoding: 7Bit
Content-Type: text/x-patch; charset="unicode-2-0-utf-8";
name="v2-0003-Add-alias-to-be-used-as-unset-state.patch"
^ permalink raw reply [nested|flat] 868+ messages in thread
* [PATCH v2a 2/4] Introduce ternary reloptions
@ 2025-09-04 14:41 Nikolay Shaplov <[email protected]>
0 siblings, 0 replies; 868+ messages in thread
From: Nikolay Shaplov @ 2025-09-04 14:41 UTC (permalink / raw)
Introduce ternary reloption as a replacement for current `vacuum_truncate`
implementation. Remove `vacuum_truncate_set` additional flag and using
`TERNARY_UNSET` value instead.
---
src/backend/access/common/reloptions.c | 129 ++++++++++++++++++++-----
src/backend/commands/vacuum.c | 4 +-
src/include/access/reloptions.h | 26 ++---
src/include/c.h | 16 +++
src/include/utils/rel.h | 3 +-
5 files changed, 135 insertions(+), 43 deletions(-)
diff --git a/src/backend/access/common/reloptions.c b/src/backend/access/common/reloptions.c
index 0af3fea68f..f543f11001 100644
--- a/src/backend/access/common/reloptions.c
+++ b/src/backend/access/common/reloptions.c
@@ -40,9 +40,9 @@
*
* To add an option:
*
- * (i) decide on a type (bool, integer, real, enum, string), name, default
- * value, upper and lower bounds (if applicable); for strings, consider a
- * validation routine.
+ * (i) decide on a type (bool, ternary, integer, real, enum, string), name,
+ * default value, upper and lower bounds (if applicable); for strings,
+ * consider a validation routine.
* (ii) add a record below (or use add_<type>_reloption).
* (iii) add it to the appropriate options struct (perhaps StdRdOptions)
* (iv) add it to the appropriate handling routine (perhaps
@@ -147,15 +147,6 @@ static relopt_bool boolRelOpts[] =
},
false
},
- {
- {
- "vacuum_truncate",
- "Enables vacuum to truncate empty pages at the end of this table",
- RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
- ShareUpdateExclusiveLock
- },
- true
- },
{
{
"deduplicate_items",
@@ -170,6 +161,21 @@ static relopt_bool boolRelOpts[] =
{{NULL}}
};
+static relopt_ternary ternaryRelOpts[] =
+{
+ {
+ {
+ "vacuum_truncate",
+ "Enables vacuum to truncate empty pages at the end of this table",
+ RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
+ ShareUpdateExclusiveLock
+ },
+ TERNARY_UNSET
+ },
+ /* list terminator */
+ {{NULL}}
+};
+
static relopt_int intRelOpts[] =
{
{
@@ -600,6 +606,13 @@ initialize_reloptions(void)
boolRelOpts[i].gen.lockmode));
j++;
}
+ for (i = 0; ternaryRelOpts[i].gen.name; i++)
+ {
+ Assert(DoLockModesConflict(ternaryRelOpts[i].gen.lockmode,
+ ternaryRelOpts[i].gen.lockmode));
+ j++;
+ }
+
for (i = 0; intRelOpts[i].gen.name; i++)
{
Assert(DoLockModesConflict(intRelOpts[i].gen.lockmode,
@@ -640,6 +653,14 @@ initialize_reloptions(void)
j++;
}
+ for (i = 0; ternaryRelOpts[i].gen.name; i++)
+ {
+ relOpts[j] = &ternaryRelOpts[i].gen;
+ relOpts[j]->type = RELOPT_TYPE_TERNARY;
+ relOpts[j]->namelen = strlen(relOpts[j]->name);
+ j++;
+ }
+
for (i = 0; intRelOpts[i].gen.name; i++)
{
relOpts[j] = &intRelOpts[i].gen;
@@ -800,6 +821,9 @@ allocate_reloption(bits32 kinds, int type, const char *name, const char *desc,
case RELOPT_TYPE_BOOL:
size = sizeof(relopt_bool);
break;
+ case RELOPT_TYPE_TERNARY:
+ size = sizeof(relopt_ternary);
+ break;
case RELOPT_TYPE_INT:
size = sizeof(relopt_int);
break;
@@ -883,6 +907,54 @@ add_local_bool_reloption(local_relopts *relopts, const char *name,
add_local_reloption(relopts, (relopt_gen *) newoption, offset);
}
+/*
+ * init_ternary_reloption
+ * Allocate and initialize a new ternary reloption
+ */
+static relopt_ternary *
+init_ternary_reloption(bits32 kinds, const char *name, const char *desc,
+ ternary default_val, LOCKMODE lockmode)
+{
+ relopt_ternary *newoption;
+
+ newoption = (relopt_ternary *) allocate_reloption(kinds,
+ RELOPT_TYPE_TERNARY, name, desc, lockmode);
+ newoption->default_val = default_val;
+
+ return newoption;
+}
+
+/*
+ * add_ternary_reloption
+ * Add a new ternary reloption
+ */
+void
+add_ternary_reloption(bits32 kinds, const char *name, const char *desc,
+ ternary default_val, LOCKMODE lockmode)
+{
+ relopt_ternary *newoption = init_ternary_reloption(kinds, name, desc,
+ default_val, lockmode);
+
+ add_reloption((relopt_gen *) newoption);
+}
+
+/*
+ * add_local_ternary_reloption
+ * Add a new ternary local reloption
+ *
+ * 'offset' is offset of ternary-typed field.
+ */
+void
+add_local_ternary_reloption(local_relopts *relopts, const char *name,
+ const char *desc, ternary default_val,
+ int offset)
+{
+ relopt_ternary *newoption = init_ternary_reloption(RELOPT_KIND_LOCAL,
+ name, desc,
+ default_val, 0);
+
+ add_local_reloption(relopts, (relopt_gen *) newoption, offset);
+}
/*
* init_real_reloption
@@ -1617,6 +1689,18 @@ parse_one_reloption(relopt_value *option, char *text_str, int text_len,
option->gen->name, value)));
}
break;
+ case RELOPT_TYPE_TERNARY:
+ {
+ bool b;
+ parsed = parse_bool(value, &b);
+ option->values.ternary_val = b ? TERNARY_TRUE : TERNARY_FALSE;
+ if (validate && !parsed)
+ ereport(ERROR,
+ (errcode(ERRCODE_INVALID_PARAMETER_VALUE),
+ errmsg("invalid value for ternary option \"%s\": %s",
+ option->gen->name, value)));
+ }
+ break;
case RELOPT_TYPE_INT:
{
relopt_int *optint = (relopt_int *) option->gen;
@@ -1780,17 +1864,6 @@ fillRelOptions(void *rdopts, Size basesize,
char *itempos = ((char *) rdopts) + elems[j].offset;
char *string_val;
- /*
- * If isset_offset is provided, store whether the reloption is
- * set there.
- */
- if (elems[j].isset_offset > 0)
- {
- char *setpos = ((char *) rdopts) + elems[j].isset_offset;
-
- *(bool *) setpos = options[i].isset;
- }
-
switch (options[i].gen->type)
{
case RELOPT_TYPE_BOOL:
@@ -1798,6 +1871,11 @@ fillRelOptions(void *rdopts, Size basesize,
options[i].values.bool_val :
((relopt_bool *) options[i].gen)->default_val;
break;
+ case RELOPT_TYPE_TERNARY:
+ *(ternary *) itempos = options[i].isset ?
+ options[i].values.ternary_val :
+ ((relopt_ternary *) options[i].gen)->default_val;
+ break;
case RELOPT_TYPE_INT:
*(int *) itempos = options[i].isset ?
options[i].values.int_val :
@@ -1912,8 +1990,8 @@ default_reloptions(Datum reloptions, bool validate, relopt_kind kind)
offsetof(StdRdOptions, parallel_workers)},
{"vacuum_index_cleanup", RELOPT_TYPE_ENUM,
offsetof(StdRdOptions, vacuum_index_cleanup)},
- {"vacuum_truncate", RELOPT_TYPE_BOOL,
- offsetof(StdRdOptions, vacuum_truncate), offsetof(StdRdOptions, vacuum_truncate_set)},
+ {"vacuum_truncate", RELOPT_TYPE_TERNARY,
+ offsetof(StdRdOptions, vacuum_truncate)},
{"vacuum_max_eager_freeze_failure_rate", RELOPT_TYPE_REAL,
offsetof(StdRdOptions, vacuum_max_eager_freeze_failure_rate)}
};
@@ -1993,7 +2071,6 @@ build_local_reloptions(local_relopts *relopts, Datum options, bool validate)
elems[i].optname = opt->option->name;
elems[i].opttype = opt->option->type;
elems[i].offset = opt->offset;
- elems[i].isset_offset = 0; /* not supported for local relopts yet */
i++;
}
diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c
index 733ef40ae7..977babff54 100644
--- a/src/backend/commands/vacuum.c
+++ b/src/backend/commands/vacuum.c
@@ -2221,9 +2221,9 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams params,
{
StdRdOptions *opts = (StdRdOptions *) rel->rd_options;
- if (opts && opts->vacuum_truncate_set)
+ if (opts && opts->vacuum_truncate != TERNARY_UNSET)
{
- if (opts->vacuum_truncate)
+ if (opts->vacuum_truncate == TERNARY_TRUE)
params.truncate = VACOPTVALUE_ENABLED;
else
params.truncate = VACOPTVALUE_DISABLED;
diff --git a/src/include/access/reloptions.h b/src/include/access/reloptions.h
index a604a4702c..a436697658 100644
--- a/src/include/access/reloptions.h
+++ b/src/include/access/reloptions.h
@@ -29,6 +29,7 @@
typedef enum relopt_type
{
RELOPT_TYPE_BOOL,
+ RELOPT_TYPE_TERNARY, /* on, off, unset */
RELOPT_TYPE_INT,
RELOPT_TYPE_REAL,
RELOPT_TYPE_ENUM,
@@ -80,6 +81,7 @@ typedef struct relopt_value
union
{
bool bool_val;
+ ternary ternary_val;
int int_val;
double real_val;
int enum_val;
@@ -94,6 +96,12 @@ typedef struct relopt_bool
bool default_val;
} relopt_bool;
+typedef struct relopt_rernary
+{
+ relopt_gen gen;
+ int default_val;
+} relopt_ternary;
+
typedef struct relopt_int
{
relopt_gen gen;
@@ -152,19 +160,6 @@ typedef struct
const char *optname; /* option's name */
relopt_type opttype; /* option's datatype */
int offset; /* offset of field in result struct */
-
- /*
- * isset_offset is an optional offset of a field in the result struct that
- * stores whether the option is explicitly set for the relation or if it
- * just picked up the default value. In most cases, this can be
- * accomplished by giving the reloption a special out-of-range default
- * value (e.g., some integer reloptions use -2), but this isn't always
- * possible. For example, a Boolean reloption cannot be given an
- * out-of-range default, so we need another way to discover the source of
- * its value. This offset is only used if given a value greater than
- * zero.
- */
- int isset_offset;
} relopt_parse_elt;
/* Local reloption definition */
@@ -195,6 +190,8 @@ typedef struct local_relopts
extern relopt_kind add_reloption_kind(void);
extern void add_bool_reloption(bits32 kinds, const char *name, const char *desc,
bool default_val, LOCKMODE lockmode);
+extern void add_ternary_reloption(bits32 kinds, const char *name,
+ const char *desc, int default_val, LOCKMODE lockmode);
extern void add_int_reloption(bits32 kinds, const char *name, const char *desc,
int default_val, int min_val, int max_val,
LOCKMODE lockmode);
@@ -214,6 +211,9 @@ extern void register_reloptions_validator(local_relopts *relopts,
extern void add_local_bool_reloption(local_relopts *relopts, const char *name,
const char *desc, bool default_val,
int offset);
+extern void add_local_ternary_reloption(local_relopts *relopts,
+ const char *name, const char *desc,
+ ternary default_val, int offset);
extern void add_local_int_reloption(local_relopts *relopts, const char *name,
const char *desc, int default_val,
int min_val, int max_val, int offset);
diff --git a/src/include/c.h b/src/include/c.h
index 39022f8a9d..9c59ccccf8 100644
--- a/src/include/c.h
+++ b/src/include/c.h
@@ -488,6 +488,22 @@ typedef void (*pg_funcptr_t) (void);
#include <stdbool.h>
+/*
+ * ternary
+ * Boolean value with an extrea "unset" option
+ *
+ * Ternary data type is used in relation options that can be "true", "false" or
+ * "unset". Since relation options are used deep inside the PostgreSQL code,
+ * this type is declared globally.
+*/
+
+typedef enum ternary
+{
+ TERNARY_FALSE = 0,
+ TERNARY_TRUE = 1,
+ TERNARY_UNSET = -1
+} ternary;
+
/* ----------------------------------------------------------------
* Section 3: standard system types
diff --git a/src/include/utils/rel.h b/src/include/utils/rel.h
index b552359915..7346d618f9 100644
--- a/src/include/utils/rel.h
+++ b/src/include/utils/rel.h
@@ -346,8 +346,7 @@ typedef struct StdRdOptions
bool user_catalog_table; /* use as an additional catalog relation */
int parallel_workers; /* max number of parallel workers */
StdRdOptIndexCleanup vacuum_index_cleanup; /* controls index vacuuming */
- bool vacuum_truncate; /* enables vacuum to truncate a relation */
- bool vacuum_truncate_set; /* whether vacuum_truncate is set */
+ ternary vacuum_truncate; /* enables vacuum to truncate a relation */
/*
* Fraction of pages in a relation that vacuum can eagerly scan and fail
--
2.39.2
--nextPart4155600.3ZeAukHxDK
Content-Disposition: attachment;
filename="v2a-0003-Add-alias-to-be-used-as-unset-state.patch"
Content-Transfer-Encoding: 7Bit
Content-Type: text/x-patch; charset="unicode-2-0-utf-8";
name="v2a-0003-Add-alias-to-be-used-as-unset-state.patch"
^ permalink raw reply [nested|flat] 868+ messages in thread
* [PATCH v2 2/4] Introduce ternary reloptions
@ 2025-09-04 14:41 Nikolay Shaplov <[email protected]>
0 siblings, 0 replies; 868+ messages in thread
From: Nikolay Shaplov @ 2025-09-04 14:41 UTC (permalink / raw)
Introduce ternary reloption as a replacement for current `vacuum_truncate`
implementation. Remove `vacuum_truncate_set` additional flag and using
`TERNARY_UNSET` value instead.
---
src/backend/access/common/reloptions.c | 129 ++++++++++++++++++++-----
src/backend/commands/vacuum.c | 4 +-
src/include/access/reloptions.h | 26 ++---
src/include/c.h | 16 +++
src/include/utils/rel.h | 3 +-
5 files changed, 135 insertions(+), 43 deletions(-)
diff --git a/src/backend/access/common/reloptions.c b/src/backend/access/common/reloptions.c
index 0af3fea68f..f543f11001 100644
--- a/src/backend/access/common/reloptions.c
+++ b/src/backend/access/common/reloptions.c
@@ -40,9 +40,9 @@
*
* To add an option:
*
- * (i) decide on a type (bool, integer, real, enum, string), name, default
- * value, upper and lower bounds (if applicable); for strings, consider a
- * validation routine.
+ * (i) decide on a type (bool, ternary, integer, real, enum, string), name,
+ * default value, upper and lower bounds (if applicable); for strings,
+ * consider a validation routine.
* (ii) add a record below (or use add_<type>_reloption).
* (iii) add it to the appropriate options struct (perhaps StdRdOptions)
* (iv) add it to the appropriate handling routine (perhaps
@@ -147,15 +147,6 @@ static relopt_bool boolRelOpts[] =
},
false
},
- {
- {
- "vacuum_truncate",
- "Enables vacuum to truncate empty pages at the end of this table",
- RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
- ShareUpdateExclusiveLock
- },
- true
- },
{
{
"deduplicate_items",
@@ -170,6 +161,21 @@ static relopt_bool boolRelOpts[] =
{{NULL}}
};
+static relopt_ternary ternaryRelOpts[] =
+{
+ {
+ {
+ "vacuum_truncate",
+ "Enables vacuum to truncate empty pages at the end of this table",
+ RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
+ ShareUpdateExclusiveLock
+ },
+ TERNARY_UNSET
+ },
+ /* list terminator */
+ {{NULL}}
+};
+
static relopt_int intRelOpts[] =
{
{
@@ -600,6 +606,13 @@ initialize_reloptions(void)
boolRelOpts[i].gen.lockmode));
j++;
}
+ for (i = 0; ternaryRelOpts[i].gen.name; i++)
+ {
+ Assert(DoLockModesConflict(ternaryRelOpts[i].gen.lockmode,
+ ternaryRelOpts[i].gen.lockmode));
+ j++;
+ }
+
for (i = 0; intRelOpts[i].gen.name; i++)
{
Assert(DoLockModesConflict(intRelOpts[i].gen.lockmode,
@@ -640,6 +653,14 @@ initialize_reloptions(void)
j++;
}
+ for (i = 0; ternaryRelOpts[i].gen.name; i++)
+ {
+ relOpts[j] = &ternaryRelOpts[i].gen;
+ relOpts[j]->type = RELOPT_TYPE_TERNARY;
+ relOpts[j]->namelen = strlen(relOpts[j]->name);
+ j++;
+ }
+
for (i = 0; intRelOpts[i].gen.name; i++)
{
relOpts[j] = &intRelOpts[i].gen;
@@ -800,6 +821,9 @@ allocate_reloption(bits32 kinds, int type, const char *name, const char *desc,
case RELOPT_TYPE_BOOL:
size = sizeof(relopt_bool);
break;
+ case RELOPT_TYPE_TERNARY:
+ size = sizeof(relopt_ternary);
+ break;
case RELOPT_TYPE_INT:
size = sizeof(relopt_int);
break;
@@ -883,6 +907,54 @@ add_local_bool_reloption(local_relopts *relopts, const char *name,
add_local_reloption(relopts, (relopt_gen *) newoption, offset);
}
+/*
+ * init_ternary_reloption
+ * Allocate and initialize a new ternary reloption
+ */
+static relopt_ternary *
+init_ternary_reloption(bits32 kinds, const char *name, const char *desc,
+ ternary default_val, LOCKMODE lockmode)
+{
+ relopt_ternary *newoption;
+
+ newoption = (relopt_ternary *) allocate_reloption(kinds,
+ RELOPT_TYPE_TERNARY, name, desc, lockmode);
+ newoption->default_val = default_val;
+
+ return newoption;
+}
+
+/*
+ * add_ternary_reloption
+ * Add a new ternary reloption
+ */
+void
+add_ternary_reloption(bits32 kinds, const char *name, const char *desc,
+ ternary default_val, LOCKMODE lockmode)
+{
+ relopt_ternary *newoption = init_ternary_reloption(kinds, name, desc,
+ default_val, lockmode);
+
+ add_reloption((relopt_gen *) newoption);
+}
+
+/*
+ * add_local_ternary_reloption
+ * Add a new ternary local reloption
+ *
+ * 'offset' is offset of ternary-typed field.
+ */
+void
+add_local_ternary_reloption(local_relopts *relopts, const char *name,
+ const char *desc, ternary default_val,
+ int offset)
+{
+ relopt_ternary *newoption = init_ternary_reloption(RELOPT_KIND_LOCAL,
+ name, desc,
+ default_val, 0);
+
+ add_local_reloption(relopts, (relopt_gen *) newoption, offset);
+}
/*
* init_real_reloption
@@ -1617,6 +1689,18 @@ parse_one_reloption(relopt_value *option, char *text_str, int text_len,
option->gen->name, value)));
}
break;
+ case RELOPT_TYPE_TERNARY:
+ {
+ bool b;
+ parsed = parse_bool(value, &b);
+ option->values.ternary_val = b ? TERNARY_TRUE : TERNARY_FALSE;
+ if (validate && !parsed)
+ ereport(ERROR,
+ (errcode(ERRCODE_INVALID_PARAMETER_VALUE),
+ errmsg("invalid value for ternary option \"%s\": %s",
+ option->gen->name, value)));
+ }
+ break;
case RELOPT_TYPE_INT:
{
relopt_int *optint = (relopt_int *) option->gen;
@@ -1780,17 +1864,6 @@ fillRelOptions(void *rdopts, Size basesize,
char *itempos = ((char *) rdopts) + elems[j].offset;
char *string_val;
- /*
- * If isset_offset is provided, store whether the reloption is
- * set there.
- */
- if (elems[j].isset_offset > 0)
- {
- char *setpos = ((char *) rdopts) + elems[j].isset_offset;
-
- *(bool *) setpos = options[i].isset;
- }
-
switch (options[i].gen->type)
{
case RELOPT_TYPE_BOOL:
@@ -1798,6 +1871,11 @@ fillRelOptions(void *rdopts, Size basesize,
options[i].values.bool_val :
((relopt_bool *) options[i].gen)->default_val;
break;
+ case RELOPT_TYPE_TERNARY:
+ *(ternary *) itempos = options[i].isset ?
+ options[i].values.ternary_val :
+ ((relopt_ternary *) options[i].gen)->default_val;
+ break;
case RELOPT_TYPE_INT:
*(int *) itempos = options[i].isset ?
options[i].values.int_val :
@@ -1912,8 +1990,8 @@ default_reloptions(Datum reloptions, bool validate, relopt_kind kind)
offsetof(StdRdOptions, parallel_workers)},
{"vacuum_index_cleanup", RELOPT_TYPE_ENUM,
offsetof(StdRdOptions, vacuum_index_cleanup)},
- {"vacuum_truncate", RELOPT_TYPE_BOOL,
- offsetof(StdRdOptions, vacuum_truncate), offsetof(StdRdOptions, vacuum_truncate_set)},
+ {"vacuum_truncate", RELOPT_TYPE_TERNARY,
+ offsetof(StdRdOptions, vacuum_truncate)},
{"vacuum_max_eager_freeze_failure_rate", RELOPT_TYPE_REAL,
offsetof(StdRdOptions, vacuum_max_eager_freeze_failure_rate)}
};
@@ -1993,7 +2071,6 @@ build_local_reloptions(local_relopts *relopts, Datum options, bool validate)
elems[i].optname = opt->option->name;
elems[i].opttype = opt->option->type;
elems[i].offset = opt->offset;
- elems[i].isset_offset = 0; /* not supported for local relopts yet */
i++;
}
diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c
index 733ef40ae7..977babff54 100644
--- a/src/backend/commands/vacuum.c
+++ b/src/backend/commands/vacuum.c
@@ -2221,9 +2221,9 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams params,
{
StdRdOptions *opts = (StdRdOptions *) rel->rd_options;
- if (opts && opts->vacuum_truncate_set)
+ if (opts && opts->vacuum_truncate != TERNARY_UNSET)
{
- if (opts->vacuum_truncate)
+ if (opts->vacuum_truncate == TERNARY_TRUE)
params.truncate = VACOPTVALUE_ENABLED;
else
params.truncate = VACOPTVALUE_DISABLED;
diff --git a/src/include/access/reloptions.h b/src/include/access/reloptions.h
index a604a4702c..a436697658 100644
--- a/src/include/access/reloptions.h
+++ b/src/include/access/reloptions.h
@@ -29,6 +29,7 @@
typedef enum relopt_type
{
RELOPT_TYPE_BOOL,
+ RELOPT_TYPE_TERNARY, /* on, off, unset */
RELOPT_TYPE_INT,
RELOPT_TYPE_REAL,
RELOPT_TYPE_ENUM,
@@ -80,6 +81,7 @@ typedef struct relopt_value
union
{
bool bool_val;
+ ternary ternary_val;
int int_val;
double real_val;
int enum_val;
@@ -94,6 +96,12 @@ typedef struct relopt_bool
bool default_val;
} relopt_bool;
+typedef struct relopt_rernary
+{
+ relopt_gen gen;
+ int default_val;
+} relopt_ternary;
+
typedef struct relopt_int
{
relopt_gen gen;
@@ -152,19 +160,6 @@ typedef struct
const char *optname; /* option's name */
relopt_type opttype; /* option's datatype */
int offset; /* offset of field in result struct */
-
- /*
- * isset_offset is an optional offset of a field in the result struct that
- * stores whether the option is explicitly set for the relation or if it
- * just picked up the default value. In most cases, this can be
- * accomplished by giving the reloption a special out-of-range default
- * value (e.g., some integer reloptions use -2), but this isn't always
- * possible. For example, a Boolean reloption cannot be given an
- * out-of-range default, so we need another way to discover the source of
- * its value. This offset is only used if given a value greater than
- * zero.
- */
- int isset_offset;
} relopt_parse_elt;
/* Local reloption definition */
@@ -195,6 +190,8 @@ typedef struct local_relopts
extern relopt_kind add_reloption_kind(void);
extern void add_bool_reloption(bits32 kinds, const char *name, const char *desc,
bool default_val, LOCKMODE lockmode);
+extern void add_ternary_reloption(bits32 kinds, const char *name,
+ const char *desc, int default_val, LOCKMODE lockmode);
extern void add_int_reloption(bits32 kinds, const char *name, const char *desc,
int default_val, int min_val, int max_val,
LOCKMODE lockmode);
@@ -214,6 +211,9 @@ extern void register_reloptions_validator(local_relopts *relopts,
extern void add_local_bool_reloption(local_relopts *relopts, const char *name,
const char *desc, bool default_val,
int offset);
+extern void add_local_ternary_reloption(local_relopts *relopts,
+ const char *name, const char *desc,
+ ternary default_val, int offset);
extern void add_local_int_reloption(local_relopts *relopts, const char *name,
const char *desc, int default_val,
int min_val, int max_val, int offset);
diff --git a/src/include/c.h b/src/include/c.h
index 39022f8a9d..9c59ccccf8 100644
--- a/src/include/c.h
+++ b/src/include/c.h
@@ -488,6 +488,22 @@ typedef void (*pg_funcptr_t) (void);
#include <stdbool.h>
+/*
+ * ternary
+ * Boolean value with an extrea "unset" option
+ *
+ * Ternary data type is used in relation options that can be "true", "false" or
+ * "unset". Since relation options are used deep inside the PostgreSQL code,
+ * this type is declared globally.
+*/
+
+typedef enum ternary
+{
+ TERNARY_FALSE = 0,
+ TERNARY_TRUE = 1,
+ TERNARY_UNSET = -1
+} ternary;
+
/* ----------------------------------------------------------------
* Section 3: standard system types
diff --git a/src/include/utils/rel.h b/src/include/utils/rel.h
index b552359915..7346d618f9 100644
--- a/src/include/utils/rel.h
+++ b/src/include/utils/rel.h
@@ -346,8 +346,7 @@ typedef struct StdRdOptions
bool user_catalog_table; /* use as an additional catalog relation */
int parallel_workers; /* max number of parallel workers */
StdRdOptIndexCleanup vacuum_index_cleanup; /* controls index vacuuming */
- bool vacuum_truncate; /* enables vacuum to truncate a relation */
- bool vacuum_truncate_set; /* whether vacuum_truncate is set */
+ ternary vacuum_truncate; /* enables vacuum to truncate a relation */
/*
* Fraction of pages in a relation that vacuum can eagerly scan and fail
--
2.39.2
--nextPart5129932.5fSG56mABF
Content-Disposition: attachment;
filename="v2-0003-Add-alias-to-be-used-as-unset-state.patch"
Content-Transfer-Encoding: 7Bit
Content-Type: text/x-patch; charset="unicode-2-0-utf-8";
name="v2-0003-Add-alias-to-be-used-as-unset-state.patch"
^ permalink raw reply [nested|flat] 868+ messages in thread
* [PATCH v2a 2/4] Introduce ternary reloptions
@ 2025-09-04 14:41 Nikolay Shaplov <[email protected]>
0 siblings, 0 replies; 868+ messages in thread
From: Nikolay Shaplov @ 2025-09-04 14:41 UTC (permalink / raw)
Introduce ternary reloption as a replacement for current `vacuum_truncate`
implementation. Remove `vacuum_truncate_set` additional flag and using
`TERNARY_UNSET` value instead.
---
src/backend/access/common/reloptions.c | 129 ++++++++++++++++++++-----
src/backend/commands/vacuum.c | 4 +-
src/include/access/reloptions.h | 26 ++---
src/include/c.h | 16 +++
src/include/utils/rel.h | 3 +-
5 files changed, 135 insertions(+), 43 deletions(-)
diff --git a/src/backend/access/common/reloptions.c b/src/backend/access/common/reloptions.c
index 0af3fea68f..f543f11001 100644
--- a/src/backend/access/common/reloptions.c
+++ b/src/backend/access/common/reloptions.c
@@ -40,9 +40,9 @@
*
* To add an option:
*
- * (i) decide on a type (bool, integer, real, enum, string), name, default
- * value, upper and lower bounds (if applicable); for strings, consider a
- * validation routine.
+ * (i) decide on a type (bool, ternary, integer, real, enum, string), name,
+ * default value, upper and lower bounds (if applicable); for strings,
+ * consider a validation routine.
* (ii) add a record below (or use add_<type>_reloption).
* (iii) add it to the appropriate options struct (perhaps StdRdOptions)
* (iv) add it to the appropriate handling routine (perhaps
@@ -147,15 +147,6 @@ static relopt_bool boolRelOpts[] =
},
false
},
- {
- {
- "vacuum_truncate",
- "Enables vacuum to truncate empty pages at the end of this table",
- RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
- ShareUpdateExclusiveLock
- },
- true
- },
{
{
"deduplicate_items",
@@ -170,6 +161,21 @@ static relopt_bool boolRelOpts[] =
{{NULL}}
};
+static relopt_ternary ternaryRelOpts[] =
+{
+ {
+ {
+ "vacuum_truncate",
+ "Enables vacuum to truncate empty pages at the end of this table",
+ RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
+ ShareUpdateExclusiveLock
+ },
+ TERNARY_UNSET
+ },
+ /* list terminator */
+ {{NULL}}
+};
+
static relopt_int intRelOpts[] =
{
{
@@ -600,6 +606,13 @@ initialize_reloptions(void)
boolRelOpts[i].gen.lockmode));
j++;
}
+ for (i = 0; ternaryRelOpts[i].gen.name; i++)
+ {
+ Assert(DoLockModesConflict(ternaryRelOpts[i].gen.lockmode,
+ ternaryRelOpts[i].gen.lockmode));
+ j++;
+ }
+
for (i = 0; intRelOpts[i].gen.name; i++)
{
Assert(DoLockModesConflict(intRelOpts[i].gen.lockmode,
@@ -640,6 +653,14 @@ initialize_reloptions(void)
j++;
}
+ for (i = 0; ternaryRelOpts[i].gen.name; i++)
+ {
+ relOpts[j] = &ternaryRelOpts[i].gen;
+ relOpts[j]->type = RELOPT_TYPE_TERNARY;
+ relOpts[j]->namelen = strlen(relOpts[j]->name);
+ j++;
+ }
+
for (i = 0; intRelOpts[i].gen.name; i++)
{
relOpts[j] = &intRelOpts[i].gen;
@@ -800,6 +821,9 @@ allocate_reloption(bits32 kinds, int type, const char *name, const char *desc,
case RELOPT_TYPE_BOOL:
size = sizeof(relopt_bool);
break;
+ case RELOPT_TYPE_TERNARY:
+ size = sizeof(relopt_ternary);
+ break;
case RELOPT_TYPE_INT:
size = sizeof(relopt_int);
break;
@@ -883,6 +907,54 @@ add_local_bool_reloption(local_relopts *relopts, const char *name,
add_local_reloption(relopts, (relopt_gen *) newoption, offset);
}
+/*
+ * init_ternary_reloption
+ * Allocate and initialize a new ternary reloption
+ */
+static relopt_ternary *
+init_ternary_reloption(bits32 kinds, const char *name, const char *desc,
+ ternary default_val, LOCKMODE lockmode)
+{
+ relopt_ternary *newoption;
+
+ newoption = (relopt_ternary *) allocate_reloption(kinds,
+ RELOPT_TYPE_TERNARY, name, desc, lockmode);
+ newoption->default_val = default_val;
+
+ return newoption;
+}
+
+/*
+ * add_ternary_reloption
+ * Add a new ternary reloption
+ */
+void
+add_ternary_reloption(bits32 kinds, const char *name, const char *desc,
+ ternary default_val, LOCKMODE lockmode)
+{
+ relopt_ternary *newoption = init_ternary_reloption(kinds, name, desc,
+ default_val, lockmode);
+
+ add_reloption((relopt_gen *) newoption);
+}
+
+/*
+ * add_local_ternary_reloption
+ * Add a new ternary local reloption
+ *
+ * 'offset' is offset of ternary-typed field.
+ */
+void
+add_local_ternary_reloption(local_relopts *relopts, const char *name,
+ const char *desc, ternary default_val,
+ int offset)
+{
+ relopt_ternary *newoption = init_ternary_reloption(RELOPT_KIND_LOCAL,
+ name, desc,
+ default_val, 0);
+
+ add_local_reloption(relopts, (relopt_gen *) newoption, offset);
+}
/*
* init_real_reloption
@@ -1617,6 +1689,18 @@ parse_one_reloption(relopt_value *option, char *text_str, int text_len,
option->gen->name, value)));
}
break;
+ case RELOPT_TYPE_TERNARY:
+ {
+ bool b;
+ parsed = parse_bool(value, &b);
+ option->values.ternary_val = b ? TERNARY_TRUE : TERNARY_FALSE;
+ if (validate && !parsed)
+ ereport(ERROR,
+ (errcode(ERRCODE_INVALID_PARAMETER_VALUE),
+ errmsg("invalid value for ternary option \"%s\": %s",
+ option->gen->name, value)));
+ }
+ break;
case RELOPT_TYPE_INT:
{
relopt_int *optint = (relopt_int *) option->gen;
@@ -1780,17 +1864,6 @@ fillRelOptions(void *rdopts, Size basesize,
char *itempos = ((char *) rdopts) + elems[j].offset;
char *string_val;
- /*
- * If isset_offset is provided, store whether the reloption is
- * set there.
- */
- if (elems[j].isset_offset > 0)
- {
- char *setpos = ((char *) rdopts) + elems[j].isset_offset;
-
- *(bool *) setpos = options[i].isset;
- }
-
switch (options[i].gen->type)
{
case RELOPT_TYPE_BOOL:
@@ -1798,6 +1871,11 @@ fillRelOptions(void *rdopts, Size basesize,
options[i].values.bool_val :
((relopt_bool *) options[i].gen)->default_val;
break;
+ case RELOPT_TYPE_TERNARY:
+ *(ternary *) itempos = options[i].isset ?
+ options[i].values.ternary_val :
+ ((relopt_ternary *) options[i].gen)->default_val;
+ break;
case RELOPT_TYPE_INT:
*(int *) itempos = options[i].isset ?
options[i].values.int_val :
@@ -1912,8 +1990,8 @@ default_reloptions(Datum reloptions, bool validate, relopt_kind kind)
offsetof(StdRdOptions, parallel_workers)},
{"vacuum_index_cleanup", RELOPT_TYPE_ENUM,
offsetof(StdRdOptions, vacuum_index_cleanup)},
- {"vacuum_truncate", RELOPT_TYPE_BOOL,
- offsetof(StdRdOptions, vacuum_truncate), offsetof(StdRdOptions, vacuum_truncate_set)},
+ {"vacuum_truncate", RELOPT_TYPE_TERNARY,
+ offsetof(StdRdOptions, vacuum_truncate)},
{"vacuum_max_eager_freeze_failure_rate", RELOPT_TYPE_REAL,
offsetof(StdRdOptions, vacuum_max_eager_freeze_failure_rate)}
};
@@ -1993,7 +2071,6 @@ build_local_reloptions(local_relopts *relopts, Datum options, bool validate)
elems[i].optname = opt->option->name;
elems[i].opttype = opt->option->type;
elems[i].offset = opt->offset;
- elems[i].isset_offset = 0; /* not supported for local relopts yet */
i++;
}
diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c
index 733ef40ae7..977babff54 100644
--- a/src/backend/commands/vacuum.c
+++ b/src/backend/commands/vacuum.c
@@ -2221,9 +2221,9 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams params,
{
StdRdOptions *opts = (StdRdOptions *) rel->rd_options;
- if (opts && opts->vacuum_truncate_set)
+ if (opts && opts->vacuum_truncate != TERNARY_UNSET)
{
- if (opts->vacuum_truncate)
+ if (opts->vacuum_truncate == TERNARY_TRUE)
params.truncate = VACOPTVALUE_ENABLED;
else
params.truncate = VACOPTVALUE_DISABLED;
diff --git a/src/include/access/reloptions.h b/src/include/access/reloptions.h
index a604a4702c..a436697658 100644
--- a/src/include/access/reloptions.h
+++ b/src/include/access/reloptions.h
@@ -29,6 +29,7 @@
typedef enum relopt_type
{
RELOPT_TYPE_BOOL,
+ RELOPT_TYPE_TERNARY, /* on, off, unset */
RELOPT_TYPE_INT,
RELOPT_TYPE_REAL,
RELOPT_TYPE_ENUM,
@@ -80,6 +81,7 @@ typedef struct relopt_value
union
{
bool bool_val;
+ ternary ternary_val;
int int_val;
double real_val;
int enum_val;
@@ -94,6 +96,12 @@ typedef struct relopt_bool
bool default_val;
} relopt_bool;
+typedef struct relopt_rernary
+{
+ relopt_gen gen;
+ int default_val;
+} relopt_ternary;
+
typedef struct relopt_int
{
relopt_gen gen;
@@ -152,19 +160,6 @@ typedef struct
const char *optname; /* option's name */
relopt_type opttype; /* option's datatype */
int offset; /* offset of field in result struct */
-
- /*
- * isset_offset is an optional offset of a field in the result struct that
- * stores whether the option is explicitly set for the relation or if it
- * just picked up the default value. In most cases, this can be
- * accomplished by giving the reloption a special out-of-range default
- * value (e.g., some integer reloptions use -2), but this isn't always
- * possible. For example, a Boolean reloption cannot be given an
- * out-of-range default, so we need another way to discover the source of
- * its value. This offset is only used if given a value greater than
- * zero.
- */
- int isset_offset;
} relopt_parse_elt;
/* Local reloption definition */
@@ -195,6 +190,8 @@ typedef struct local_relopts
extern relopt_kind add_reloption_kind(void);
extern void add_bool_reloption(bits32 kinds, const char *name, const char *desc,
bool default_val, LOCKMODE lockmode);
+extern void add_ternary_reloption(bits32 kinds, const char *name,
+ const char *desc, int default_val, LOCKMODE lockmode);
extern void add_int_reloption(bits32 kinds, const char *name, const char *desc,
int default_val, int min_val, int max_val,
LOCKMODE lockmode);
@@ -214,6 +211,9 @@ extern void register_reloptions_validator(local_relopts *relopts,
extern void add_local_bool_reloption(local_relopts *relopts, const char *name,
const char *desc, bool default_val,
int offset);
+extern void add_local_ternary_reloption(local_relopts *relopts,
+ const char *name, const char *desc,
+ ternary default_val, int offset);
extern void add_local_int_reloption(local_relopts *relopts, const char *name,
const char *desc, int default_val,
int min_val, int max_val, int offset);
diff --git a/src/include/c.h b/src/include/c.h
index 39022f8a9d..9c59ccccf8 100644
--- a/src/include/c.h
+++ b/src/include/c.h
@@ -488,6 +488,22 @@ typedef void (*pg_funcptr_t) (void);
#include <stdbool.h>
+/*
+ * ternary
+ * Boolean value with an extrea "unset" option
+ *
+ * Ternary data type is used in relation options that can be "true", "false" or
+ * "unset". Since relation options are used deep inside the PostgreSQL code,
+ * this type is declared globally.
+*/
+
+typedef enum ternary
+{
+ TERNARY_FALSE = 0,
+ TERNARY_TRUE = 1,
+ TERNARY_UNSET = -1
+} ternary;
+
/* ----------------------------------------------------------------
* Section 3: standard system types
diff --git a/src/include/utils/rel.h b/src/include/utils/rel.h
index b552359915..7346d618f9 100644
--- a/src/include/utils/rel.h
+++ b/src/include/utils/rel.h
@@ -346,8 +346,7 @@ typedef struct StdRdOptions
bool user_catalog_table; /* use as an additional catalog relation */
int parallel_workers; /* max number of parallel workers */
StdRdOptIndexCleanup vacuum_index_cleanup; /* controls index vacuuming */
- bool vacuum_truncate; /* enables vacuum to truncate a relation */
- bool vacuum_truncate_set; /* whether vacuum_truncate is set */
+ ternary vacuum_truncate; /* enables vacuum to truncate a relation */
/*
* Fraction of pages in a relation that vacuum can eagerly scan and fail
--
2.39.2
--nextPart4155600.3ZeAukHxDK
Content-Disposition: attachment;
filename="v2a-0003-Add-alias-to-be-used-as-unset-state.patch"
Content-Transfer-Encoding: 7Bit
Content-Type: text/x-patch; charset="unicode-2-0-utf-8";
name="v2a-0003-Add-alias-to-be-used-as-unset-state.patch"
^ permalink raw reply [nested|flat] 868+ messages in thread
* [PATCH v2 2/4] Introduce ternary reloptions
@ 2025-09-04 14:41 Nikolay Shaplov <[email protected]>
0 siblings, 0 replies; 868+ messages in thread
From: Nikolay Shaplov @ 2025-09-04 14:41 UTC (permalink / raw)
Introduce ternary reloption as a replacement for current `vacuum_truncate`
implementation. Remove `vacuum_truncate_set` additional flag and using
`TERNARY_UNSET` value instead.
---
src/backend/access/common/reloptions.c | 129 ++++++++++++++++++++-----
src/backend/commands/vacuum.c | 4 +-
src/include/access/reloptions.h | 26 ++---
src/include/c.h | 16 +++
src/include/utils/rel.h | 3 +-
5 files changed, 135 insertions(+), 43 deletions(-)
diff --git a/src/backend/access/common/reloptions.c b/src/backend/access/common/reloptions.c
index 0af3fea68f..f543f11001 100644
--- a/src/backend/access/common/reloptions.c
+++ b/src/backend/access/common/reloptions.c
@@ -40,9 +40,9 @@
*
* To add an option:
*
- * (i) decide on a type (bool, integer, real, enum, string), name, default
- * value, upper and lower bounds (if applicable); for strings, consider a
- * validation routine.
+ * (i) decide on a type (bool, ternary, integer, real, enum, string), name,
+ * default value, upper and lower bounds (if applicable); for strings,
+ * consider a validation routine.
* (ii) add a record below (or use add_<type>_reloption).
* (iii) add it to the appropriate options struct (perhaps StdRdOptions)
* (iv) add it to the appropriate handling routine (perhaps
@@ -147,15 +147,6 @@ static relopt_bool boolRelOpts[] =
},
false
},
- {
- {
- "vacuum_truncate",
- "Enables vacuum to truncate empty pages at the end of this table",
- RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
- ShareUpdateExclusiveLock
- },
- true
- },
{
{
"deduplicate_items",
@@ -170,6 +161,21 @@ static relopt_bool boolRelOpts[] =
{{NULL}}
};
+static relopt_ternary ternaryRelOpts[] =
+{
+ {
+ {
+ "vacuum_truncate",
+ "Enables vacuum to truncate empty pages at the end of this table",
+ RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
+ ShareUpdateExclusiveLock
+ },
+ TERNARY_UNSET
+ },
+ /* list terminator */
+ {{NULL}}
+};
+
static relopt_int intRelOpts[] =
{
{
@@ -600,6 +606,13 @@ initialize_reloptions(void)
boolRelOpts[i].gen.lockmode));
j++;
}
+ for (i = 0; ternaryRelOpts[i].gen.name; i++)
+ {
+ Assert(DoLockModesConflict(ternaryRelOpts[i].gen.lockmode,
+ ternaryRelOpts[i].gen.lockmode));
+ j++;
+ }
+
for (i = 0; intRelOpts[i].gen.name; i++)
{
Assert(DoLockModesConflict(intRelOpts[i].gen.lockmode,
@@ -640,6 +653,14 @@ initialize_reloptions(void)
j++;
}
+ for (i = 0; ternaryRelOpts[i].gen.name; i++)
+ {
+ relOpts[j] = &ternaryRelOpts[i].gen;
+ relOpts[j]->type = RELOPT_TYPE_TERNARY;
+ relOpts[j]->namelen = strlen(relOpts[j]->name);
+ j++;
+ }
+
for (i = 0; intRelOpts[i].gen.name; i++)
{
relOpts[j] = &intRelOpts[i].gen;
@@ -800,6 +821,9 @@ allocate_reloption(bits32 kinds, int type, const char *name, const char *desc,
case RELOPT_TYPE_BOOL:
size = sizeof(relopt_bool);
break;
+ case RELOPT_TYPE_TERNARY:
+ size = sizeof(relopt_ternary);
+ break;
case RELOPT_TYPE_INT:
size = sizeof(relopt_int);
break;
@@ -883,6 +907,54 @@ add_local_bool_reloption(local_relopts *relopts, const char *name,
add_local_reloption(relopts, (relopt_gen *) newoption, offset);
}
+/*
+ * init_ternary_reloption
+ * Allocate and initialize a new ternary reloption
+ */
+static relopt_ternary *
+init_ternary_reloption(bits32 kinds, const char *name, const char *desc,
+ ternary default_val, LOCKMODE lockmode)
+{
+ relopt_ternary *newoption;
+
+ newoption = (relopt_ternary *) allocate_reloption(kinds,
+ RELOPT_TYPE_TERNARY, name, desc, lockmode);
+ newoption->default_val = default_val;
+
+ return newoption;
+}
+
+/*
+ * add_ternary_reloption
+ * Add a new ternary reloption
+ */
+void
+add_ternary_reloption(bits32 kinds, const char *name, const char *desc,
+ ternary default_val, LOCKMODE lockmode)
+{
+ relopt_ternary *newoption = init_ternary_reloption(kinds, name, desc,
+ default_val, lockmode);
+
+ add_reloption((relopt_gen *) newoption);
+}
+
+/*
+ * add_local_ternary_reloption
+ * Add a new ternary local reloption
+ *
+ * 'offset' is offset of ternary-typed field.
+ */
+void
+add_local_ternary_reloption(local_relopts *relopts, const char *name,
+ const char *desc, ternary default_val,
+ int offset)
+{
+ relopt_ternary *newoption = init_ternary_reloption(RELOPT_KIND_LOCAL,
+ name, desc,
+ default_val, 0);
+
+ add_local_reloption(relopts, (relopt_gen *) newoption, offset);
+}
/*
* init_real_reloption
@@ -1617,6 +1689,18 @@ parse_one_reloption(relopt_value *option, char *text_str, int text_len,
option->gen->name, value)));
}
break;
+ case RELOPT_TYPE_TERNARY:
+ {
+ bool b;
+ parsed = parse_bool(value, &b);
+ option->values.ternary_val = b ? TERNARY_TRUE : TERNARY_FALSE;
+ if (validate && !parsed)
+ ereport(ERROR,
+ (errcode(ERRCODE_INVALID_PARAMETER_VALUE),
+ errmsg("invalid value for ternary option \"%s\": %s",
+ option->gen->name, value)));
+ }
+ break;
case RELOPT_TYPE_INT:
{
relopt_int *optint = (relopt_int *) option->gen;
@@ -1780,17 +1864,6 @@ fillRelOptions(void *rdopts, Size basesize,
char *itempos = ((char *) rdopts) + elems[j].offset;
char *string_val;
- /*
- * If isset_offset is provided, store whether the reloption is
- * set there.
- */
- if (elems[j].isset_offset > 0)
- {
- char *setpos = ((char *) rdopts) + elems[j].isset_offset;
-
- *(bool *) setpos = options[i].isset;
- }
-
switch (options[i].gen->type)
{
case RELOPT_TYPE_BOOL:
@@ -1798,6 +1871,11 @@ fillRelOptions(void *rdopts, Size basesize,
options[i].values.bool_val :
((relopt_bool *) options[i].gen)->default_val;
break;
+ case RELOPT_TYPE_TERNARY:
+ *(ternary *) itempos = options[i].isset ?
+ options[i].values.ternary_val :
+ ((relopt_ternary *) options[i].gen)->default_val;
+ break;
case RELOPT_TYPE_INT:
*(int *) itempos = options[i].isset ?
options[i].values.int_val :
@@ -1912,8 +1990,8 @@ default_reloptions(Datum reloptions, bool validate, relopt_kind kind)
offsetof(StdRdOptions, parallel_workers)},
{"vacuum_index_cleanup", RELOPT_TYPE_ENUM,
offsetof(StdRdOptions, vacuum_index_cleanup)},
- {"vacuum_truncate", RELOPT_TYPE_BOOL,
- offsetof(StdRdOptions, vacuum_truncate), offsetof(StdRdOptions, vacuum_truncate_set)},
+ {"vacuum_truncate", RELOPT_TYPE_TERNARY,
+ offsetof(StdRdOptions, vacuum_truncate)},
{"vacuum_max_eager_freeze_failure_rate", RELOPT_TYPE_REAL,
offsetof(StdRdOptions, vacuum_max_eager_freeze_failure_rate)}
};
@@ -1993,7 +2071,6 @@ build_local_reloptions(local_relopts *relopts, Datum options, bool validate)
elems[i].optname = opt->option->name;
elems[i].opttype = opt->option->type;
elems[i].offset = opt->offset;
- elems[i].isset_offset = 0; /* not supported for local relopts yet */
i++;
}
diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c
index 733ef40ae7..977babff54 100644
--- a/src/backend/commands/vacuum.c
+++ b/src/backend/commands/vacuum.c
@@ -2221,9 +2221,9 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams params,
{
StdRdOptions *opts = (StdRdOptions *) rel->rd_options;
- if (opts && opts->vacuum_truncate_set)
+ if (opts && opts->vacuum_truncate != TERNARY_UNSET)
{
- if (opts->vacuum_truncate)
+ if (opts->vacuum_truncate == TERNARY_TRUE)
params.truncate = VACOPTVALUE_ENABLED;
else
params.truncate = VACOPTVALUE_DISABLED;
diff --git a/src/include/access/reloptions.h b/src/include/access/reloptions.h
index a604a4702c..a436697658 100644
--- a/src/include/access/reloptions.h
+++ b/src/include/access/reloptions.h
@@ -29,6 +29,7 @@
typedef enum relopt_type
{
RELOPT_TYPE_BOOL,
+ RELOPT_TYPE_TERNARY, /* on, off, unset */
RELOPT_TYPE_INT,
RELOPT_TYPE_REAL,
RELOPT_TYPE_ENUM,
@@ -80,6 +81,7 @@ typedef struct relopt_value
union
{
bool bool_val;
+ ternary ternary_val;
int int_val;
double real_val;
int enum_val;
@@ -94,6 +96,12 @@ typedef struct relopt_bool
bool default_val;
} relopt_bool;
+typedef struct relopt_rernary
+{
+ relopt_gen gen;
+ int default_val;
+} relopt_ternary;
+
typedef struct relopt_int
{
relopt_gen gen;
@@ -152,19 +160,6 @@ typedef struct
const char *optname; /* option's name */
relopt_type opttype; /* option's datatype */
int offset; /* offset of field in result struct */
-
- /*
- * isset_offset is an optional offset of a field in the result struct that
- * stores whether the option is explicitly set for the relation or if it
- * just picked up the default value. In most cases, this can be
- * accomplished by giving the reloption a special out-of-range default
- * value (e.g., some integer reloptions use -2), but this isn't always
- * possible. For example, a Boolean reloption cannot be given an
- * out-of-range default, so we need another way to discover the source of
- * its value. This offset is only used if given a value greater than
- * zero.
- */
- int isset_offset;
} relopt_parse_elt;
/* Local reloption definition */
@@ -195,6 +190,8 @@ typedef struct local_relopts
extern relopt_kind add_reloption_kind(void);
extern void add_bool_reloption(bits32 kinds, const char *name, const char *desc,
bool default_val, LOCKMODE lockmode);
+extern void add_ternary_reloption(bits32 kinds, const char *name,
+ const char *desc, int default_val, LOCKMODE lockmode);
extern void add_int_reloption(bits32 kinds, const char *name, const char *desc,
int default_val, int min_val, int max_val,
LOCKMODE lockmode);
@@ -214,6 +211,9 @@ extern void register_reloptions_validator(local_relopts *relopts,
extern void add_local_bool_reloption(local_relopts *relopts, const char *name,
const char *desc, bool default_val,
int offset);
+extern void add_local_ternary_reloption(local_relopts *relopts,
+ const char *name, const char *desc,
+ ternary default_val, int offset);
extern void add_local_int_reloption(local_relopts *relopts, const char *name,
const char *desc, int default_val,
int min_val, int max_val, int offset);
diff --git a/src/include/c.h b/src/include/c.h
index 39022f8a9d..9c59ccccf8 100644
--- a/src/include/c.h
+++ b/src/include/c.h
@@ -488,6 +488,22 @@ typedef void (*pg_funcptr_t) (void);
#include <stdbool.h>
+/*
+ * ternary
+ * Boolean value with an extrea "unset" option
+ *
+ * Ternary data type is used in relation options that can be "true", "false" or
+ * "unset". Since relation options are used deep inside the PostgreSQL code,
+ * this type is declared globally.
+*/
+
+typedef enum ternary
+{
+ TERNARY_FALSE = 0,
+ TERNARY_TRUE = 1,
+ TERNARY_UNSET = -1
+} ternary;
+
/* ----------------------------------------------------------------
* Section 3: standard system types
diff --git a/src/include/utils/rel.h b/src/include/utils/rel.h
index b552359915..7346d618f9 100644
--- a/src/include/utils/rel.h
+++ b/src/include/utils/rel.h
@@ -346,8 +346,7 @@ typedef struct StdRdOptions
bool user_catalog_table; /* use as an additional catalog relation */
int parallel_workers; /* max number of parallel workers */
StdRdOptIndexCleanup vacuum_index_cleanup; /* controls index vacuuming */
- bool vacuum_truncate; /* enables vacuum to truncate a relation */
- bool vacuum_truncate_set; /* whether vacuum_truncate is set */
+ ternary vacuum_truncate; /* enables vacuum to truncate a relation */
/*
* Fraction of pages in a relation that vacuum can eagerly scan and fail
--
2.39.2
--nextPart5129932.5fSG56mABF
Content-Disposition: attachment;
filename="v2-0003-Add-alias-to-be-used-as-unset-state.patch"
Content-Transfer-Encoding: 7Bit
Content-Type: text/x-patch; charset="unicode-2-0-utf-8";
name="v2-0003-Add-alias-to-be-used-as-unset-state.patch"
^ permalink raw reply [nested|flat] 868+ messages in thread
* [PATCH v2 2/4] Introduce ternary reloptions
@ 2025-09-04 14:41 Nikolay Shaplov <[email protected]>
0 siblings, 0 replies; 868+ messages in thread
From: Nikolay Shaplov @ 2025-09-04 14:41 UTC (permalink / raw)
Introduce ternary reloption as a replacement for current `vacuum_truncate`
implementation. Remove `vacuum_truncate_set` additional flag and using
`TERNARY_UNSET` value instead.
---
src/backend/access/common/reloptions.c | 129 ++++++++++++++++++++-----
src/backend/commands/vacuum.c | 4 +-
src/include/access/reloptions.h | 26 ++---
src/include/c.h | 16 +++
src/include/utils/rel.h | 3 +-
5 files changed, 135 insertions(+), 43 deletions(-)
diff --git a/src/backend/access/common/reloptions.c b/src/backend/access/common/reloptions.c
index 0af3fea68f..f543f11001 100644
--- a/src/backend/access/common/reloptions.c
+++ b/src/backend/access/common/reloptions.c
@@ -40,9 +40,9 @@
*
* To add an option:
*
- * (i) decide on a type (bool, integer, real, enum, string), name, default
- * value, upper and lower bounds (if applicable); for strings, consider a
- * validation routine.
+ * (i) decide on a type (bool, ternary, integer, real, enum, string), name,
+ * default value, upper and lower bounds (if applicable); for strings,
+ * consider a validation routine.
* (ii) add a record below (or use add_<type>_reloption).
* (iii) add it to the appropriate options struct (perhaps StdRdOptions)
* (iv) add it to the appropriate handling routine (perhaps
@@ -147,15 +147,6 @@ static relopt_bool boolRelOpts[] =
},
false
},
- {
- {
- "vacuum_truncate",
- "Enables vacuum to truncate empty pages at the end of this table",
- RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
- ShareUpdateExclusiveLock
- },
- true
- },
{
{
"deduplicate_items",
@@ -170,6 +161,21 @@ static relopt_bool boolRelOpts[] =
{{NULL}}
};
+static relopt_ternary ternaryRelOpts[] =
+{
+ {
+ {
+ "vacuum_truncate",
+ "Enables vacuum to truncate empty pages at the end of this table",
+ RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
+ ShareUpdateExclusiveLock
+ },
+ TERNARY_UNSET
+ },
+ /* list terminator */
+ {{NULL}}
+};
+
static relopt_int intRelOpts[] =
{
{
@@ -600,6 +606,13 @@ initialize_reloptions(void)
boolRelOpts[i].gen.lockmode));
j++;
}
+ for (i = 0; ternaryRelOpts[i].gen.name; i++)
+ {
+ Assert(DoLockModesConflict(ternaryRelOpts[i].gen.lockmode,
+ ternaryRelOpts[i].gen.lockmode));
+ j++;
+ }
+
for (i = 0; intRelOpts[i].gen.name; i++)
{
Assert(DoLockModesConflict(intRelOpts[i].gen.lockmode,
@@ -640,6 +653,14 @@ initialize_reloptions(void)
j++;
}
+ for (i = 0; ternaryRelOpts[i].gen.name; i++)
+ {
+ relOpts[j] = &ternaryRelOpts[i].gen;
+ relOpts[j]->type = RELOPT_TYPE_TERNARY;
+ relOpts[j]->namelen = strlen(relOpts[j]->name);
+ j++;
+ }
+
for (i = 0; intRelOpts[i].gen.name; i++)
{
relOpts[j] = &intRelOpts[i].gen;
@@ -800,6 +821,9 @@ allocate_reloption(bits32 kinds, int type, const char *name, const char *desc,
case RELOPT_TYPE_BOOL:
size = sizeof(relopt_bool);
break;
+ case RELOPT_TYPE_TERNARY:
+ size = sizeof(relopt_ternary);
+ break;
case RELOPT_TYPE_INT:
size = sizeof(relopt_int);
break;
@@ -883,6 +907,54 @@ add_local_bool_reloption(local_relopts *relopts, const char *name,
add_local_reloption(relopts, (relopt_gen *) newoption, offset);
}
+/*
+ * init_ternary_reloption
+ * Allocate and initialize a new ternary reloption
+ */
+static relopt_ternary *
+init_ternary_reloption(bits32 kinds, const char *name, const char *desc,
+ ternary default_val, LOCKMODE lockmode)
+{
+ relopt_ternary *newoption;
+
+ newoption = (relopt_ternary *) allocate_reloption(kinds,
+ RELOPT_TYPE_TERNARY, name, desc, lockmode);
+ newoption->default_val = default_val;
+
+ return newoption;
+}
+
+/*
+ * add_ternary_reloption
+ * Add a new ternary reloption
+ */
+void
+add_ternary_reloption(bits32 kinds, const char *name, const char *desc,
+ ternary default_val, LOCKMODE lockmode)
+{
+ relopt_ternary *newoption = init_ternary_reloption(kinds, name, desc,
+ default_val, lockmode);
+
+ add_reloption((relopt_gen *) newoption);
+}
+
+/*
+ * add_local_ternary_reloption
+ * Add a new ternary local reloption
+ *
+ * 'offset' is offset of ternary-typed field.
+ */
+void
+add_local_ternary_reloption(local_relopts *relopts, const char *name,
+ const char *desc, ternary default_val,
+ int offset)
+{
+ relopt_ternary *newoption = init_ternary_reloption(RELOPT_KIND_LOCAL,
+ name, desc,
+ default_val, 0);
+
+ add_local_reloption(relopts, (relopt_gen *) newoption, offset);
+}
/*
* init_real_reloption
@@ -1617,6 +1689,18 @@ parse_one_reloption(relopt_value *option, char *text_str, int text_len,
option->gen->name, value)));
}
break;
+ case RELOPT_TYPE_TERNARY:
+ {
+ bool b;
+ parsed = parse_bool(value, &b);
+ option->values.ternary_val = b ? TERNARY_TRUE : TERNARY_FALSE;
+ if (validate && !parsed)
+ ereport(ERROR,
+ (errcode(ERRCODE_INVALID_PARAMETER_VALUE),
+ errmsg("invalid value for ternary option \"%s\": %s",
+ option->gen->name, value)));
+ }
+ break;
case RELOPT_TYPE_INT:
{
relopt_int *optint = (relopt_int *) option->gen;
@@ -1780,17 +1864,6 @@ fillRelOptions(void *rdopts, Size basesize,
char *itempos = ((char *) rdopts) + elems[j].offset;
char *string_val;
- /*
- * If isset_offset is provided, store whether the reloption is
- * set there.
- */
- if (elems[j].isset_offset > 0)
- {
- char *setpos = ((char *) rdopts) + elems[j].isset_offset;
-
- *(bool *) setpos = options[i].isset;
- }
-
switch (options[i].gen->type)
{
case RELOPT_TYPE_BOOL:
@@ -1798,6 +1871,11 @@ fillRelOptions(void *rdopts, Size basesize,
options[i].values.bool_val :
((relopt_bool *) options[i].gen)->default_val;
break;
+ case RELOPT_TYPE_TERNARY:
+ *(ternary *) itempos = options[i].isset ?
+ options[i].values.ternary_val :
+ ((relopt_ternary *) options[i].gen)->default_val;
+ break;
case RELOPT_TYPE_INT:
*(int *) itempos = options[i].isset ?
options[i].values.int_val :
@@ -1912,8 +1990,8 @@ default_reloptions(Datum reloptions, bool validate, relopt_kind kind)
offsetof(StdRdOptions, parallel_workers)},
{"vacuum_index_cleanup", RELOPT_TYPE_ENUM,
offsetof(StdRdOptions, vacuum_index_cleanup)},
- {"vacuum_truncate", RELOPT_TYPE_BOOL,
- offsetof(StdRdOptions, vacuum_truncate), offsetof(StdRdOptions, vacuum_truncate_set)},
+ {"vacuum_truncate", RELOPT_TYPE_TERNARY,
+ offsetof(StdRdOptions, vacuum_truncate)},
{"vacuum_max_eager_freeze_failure_rate", RELOPT_TYPE_REAL,
offsetof(StdRdOptions, vacuum_max_eager_freeze_failure_rate)}
};
@@ -1993,7 +2071,6 @@ build_local_reloptions(local_relopts *relopts, Datum options, bool validate)
elems[i].optname = opt->option->name;
elems[i].opttype = opt->option->type;
elems[i].offset = opt->offset;
- elems[i].isset_offset = 0; /* not supported for local relopts yet */
i++;
}
diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c
index 733ef40ae7..977babff54 100644
--- a/src/backend/commands/vacuum.c
+++ b/src/backend/commands/vacuum.c
@@ -2221,9 +2221,9 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams params,
{
StdRdOptions *opts = (StdRdOptions *) rel->rd_options;
- if (opts && opts->vacuum_truncate_set)
+ if (opts && opts->vacuum_truncate != TERNARY_UNSET)
{
- if (opts->vacuum_truncate)
+ if (opts->vacuum_truncate == TERNARY_TRUE)
params.truncate = VACOPTVALUE_ENABLED;
else
params.truncate = VACOPTVALUE_DISABLED;
diff --git a/src/include/access/reloptions.h b/src/include/access/reloptions.h
index a604a4702c..a436697658 100644
--- a/src/include/access/reloptions.h
+++ b/src/include/access/reloptions.h
@@ -29,6 +29,7 @@
typedef enum relopt_type
{
RELOPT_TYPE_BOOL,
+ RELOPT_TYPE_TERNARY, /* on, off, unset */
RELOPT_TYPE_INT,
RELOPT_TYPE_REAL,
RELOPT_TYPE_ENUM,
@@ -80,6 +81,7 @@ typedef struct relopt_value
union
{
bool bool_val;
+ ternary ternary_val;
int int_val;
double real_val;
int enum_val;
@@ -94,6 +96,12 @@ typedef struct relopt_bool
bool default_val;
} relopt_bool;
+typedef struct relopt_rernary
+{
+ relopt_gen gen;
+ int default_val;
+} relopt_ternary;
+
typedef struct relopt_int
{
relopt_gen gen;
@@ -152,19 +160,6 @@ typedef struct
const char *optname; /* option's name */
relopt_type opttype; /* option's datatype */
int offset; /* offset of field in result struct */
-
- /*
- * isset_offset is an optional offset of a field in the result struct that
- * stores whether the option is explicitly set for the relation or if it
- * just picked up the default value. In most cases, this can be
- * accomplished by giving the reloption a special out-of-range default
- * value (e.g., some integer reloptions use -2), but this isn't always
- * possible. For example, a Boolean reloption cannot be given an
- * out-of-range default, so we need another way to discover the source of
- * its value. This offset is only used if given a value greater than
- * zero.
- */
- int isset_offset;
} relopt_parse_elt;
/* Local reloption definition */
@@ -195,6 +190,8 @@ typedef struct local_relopts
extern relopt_kind add_reloption_kind(void);
extern void add_bool_reloption(bits32 kinds, const char *name, const char *desc,
bool default_val, LOCKMODE lockmode);
+extern void add_ternary_reloption(bits32 kinds, const char *name,
+ const char *desc, int default_val, LOCKMODE lockmode);
extern void add_int_reloption(bits32 kinds, const char *name, const char *desc,
int default_val, int min_val, int max_val,
LOCKMODE lockmode);
@@ -214,6 +211,9 @@ extern void register_reloptions_validator(local_relopts *relopts,
extern void add_local_bool_reloption(local_relopts *relopts, const char *name,
const char *desc, bool default_val,
int offset);
+extern void add_local_ternary_reloption(local_relopts *relopts,
+ const char *name, const char *desc,
+ ternary default_val, int offset);
extern void add_local_int_reloption(local_relopts *relopts, const char *name,
const char *desc, int default_val,
int min_val, int max_val, int offset);
diff --git a/src/include/c.h b/src/include/c.h
index 39022f8a9d..9c59ccccf8 100644
--- a/src/include/c.h
+++ b/src/include/c.h
@@ -488,6 +488,22 @@ typedef void (*pg_funcptr_t) (void);
#include <stdbool.h>
+/*
+ * ternary
+ * Boolean value with an extrea "unset" option
+ *
+ * Ternary data type is used in relation options that can be "true", "false" or
+ * "unset". Since relation options are used deep inside the PostgreSQL code,
+ * this type is declared globally.
+*/
+
+typedef enum ternary
+{
+ TERNARY_FALSE = 0,
+ TERNARY_TRUE = 1,
+ TERNARY_UNSET = -1
+} ternary;
+
/* ----------------------------------------------------------------
* Section 3: standard system types
diff --git a/src/include/utils/rel.h b/src/include/utils/rel.h
index b552359915..7346d618f9 100644
--- a/src/include/utils/rel.h
+++ b/src/include/utils/rel.h
@@ -346,8 +346,7 @@ typedef struct StdRdOptions
bool user_catalog_table; /* use as an additional catalog relation */
int parallel_workers; /* max number of parallel workers */
StdRdOptIndexCleanup vacuum_index_cleanup; /* controls index vacuuming */
- bool vacuum_truncate; /* enables vacuum to truncate a relation */
- bool vacuum_truncate_set; /* whether vacuum_truncate is set */
+ ternary vacuum_truncate; /* enables vacuum to truncate a relation */
/*
* Fraction of pages in a relation that vacuum can eagerly scan and fail
--
2.39.2
--nextPart5129932.5fSG56mABF
Content-Disposition: attachment;
filename="v2-0003-Add-alias-to-be-used-as-unset-state.patch"
Content-Transfer-Encoding: 7Bit
Content-Type: text/x-patch; charset="unicode-2-0-utf-8";
name="v2-0003-Add-alias-to-be-used-as-unset-state.patch"
^ permalink raw reply [nested|flat] 868+ messages in thread
* [PATCH v2 2/4] Introduce ternary reloptions
@ 2025-09-04 14:41 Nikolay Shaplov <[email protected]>
0 siblings, 0 replies; 868+ messages in thread
From: Nikolay Shaplov @ 2025-09-04 14:41 UTC (permalink / raw)
Introduce ternary reloption as a replacement for current `vacuum_truncate`
implementation. Remove `vacuum_truncate_set` additional flag and using
`TERNARY_UNSET` value instead.
---
src/backend/access/common/reloptions.c | 129 ++++++++++++++++++++-----
src/backend/commands/vacuum.c | 4 +-
src/include/access/reloptions.h | 26 ++---
src/include/c.h | 16 +++
src/include/utils/rel.h | 3 +-
5 files changed, 135 insertions(+), 43 deletions(-)
diff --git a/src/backend/access/common/reloptions.c b/src/backend/access/common/reloptions.c
index 0af3fea68f..f543f11001 100644
--- a/src/backend/access/common/reloptions.c
+++ b/src/backend/access/common/reloptions.c
@@ -40,9 +40,9 @@
*
* To add an option:
*
- * (i) decide on a type (bool, integer, real, enum, string), name, default
- * value, upper and lower bounds (if applicable); for strings, consider a
- * validation routine.
+ * (i) decide on a type (bool, ternary, integer, real, enum, string), name,
+ * default value, upper and lower bounds (if applicable); for strings,
+ * consider a validation routine.
* (ii) add a record below (or use add_<type>_reloption).
* (iii) add it to the appropriate options struct (perhaps StdRdOptions)
* (iv) add it to the appropriate handling routine (perhaps
@@ -147,15 +147,6 @@ static relopt_bool boolRelOpts[] =
},
false
},
- {
- {
- "vacuum_truncate",
- "Enables vacuum to truncate empty pages at the end of this table",
- RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
- ShareUpdateExclusiveLock
- },
- true
- },
{
{
"deduplicate_items",
@@ -170,6 +161,21 @@ static relopt_bool boolRelOpts[] =
{{NULL}}
};
+static relopt_ternary ternaryRelOpts[] =
+{
+ {
+ {
+ "vacuum_truncate",
+ "Enables vacuum to truncate empty pages at the end of this table",
+ RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
+ ShareUpdateExclusiveLock
+ },
+ TERNARY_UNSET
+ },
+ /* list terminator */
+ {{NULL}}
+};
+
static relopt_int intRelOpts[] =
{
{
@@ -600,6 +606,13 @@ initialize_reloptions(void)
boolRelOpts[i].gen.lockmode));
j++;
}
+ for (i = 0; ternaryRelOpts[i].gen.name; i++)
+ {
+ Assert(DoLockModesConflict(ternaryRelOpts[i].gen.lockmode,
+ ternaryRelOpts[i].gen.lockmode));
+ j++;
+ }
+
for (i = 0; intRelOpts[i].gen.name; i++)
{
Assert(DoLockModesConflict(intRelOpts[i].gen.lockmode,
@@ -640,6 +653,14 @@ initialize_reloptions(void)
j++;
}
+ for (i = 0; ternaryRelOpts[i].gen.name; i++)
+ {
+ relOpts[j] = &ternaryRelOpts[i].gen;
+ relOpts[j]->type = RELOPT_TYPE_TERNARY;
+ relOpts[j]->namelen = strlen(relOpts[j]->name);
+ j++;
+ }
+
for (i = 0; intRelOpts[i].gen.name; i++)
{
relOpts[j] = &intRelOpts[i].gen;
@@ -800,6 +821,9 @@ allocate_reloption(bits32 kinds, int type, const char *name, const char *desc,
case RELOPT_TYPE_BOOL:
size = sizeof(relopt_bool);
break;
+ case RELOPT_TYPE_TERNARY:
+ size = sizeof(relopt_ternary);
+ break;
case RELOPT_TYPE_INT:
size = sizeof(relopt_int);
break;
@@ -883,6 +907,54 @@ add_local_bool_reloption(local_relopts *relopts, const char *name,
add_local_reloption(relopts, (relopt_gen *) newoption, offset);
}
+/*
+ * init_ternary_reloption
+ * Allocate and initialize a new ternary reloption
+ */
+static relopt_ternary *
+init_ternary_reloption(bits32 kinds, const char *name, const char *desc,
+ ternary default_val, LOCKMODE lockmode)
+{
+ relopt_ternary *newoption;
+
+ newoption = (relopt_ternary *) allocate_reloption(kinds,
+ RELOPT_TYPE_TERNARY, name, desc, lockmode);
+ newoption->default_val = default_val;
+
+ return newoption;
+}
+
+/*
+ * add_ternary_reloption
+ * Add a new ternary reloption
+ */
+void
+add_ternary_reloption(bits32 kinds, const char *name, const char *desc,
+ ternary default_val, LOCKMODE lockmode)
+{
+ relopt_ternary *newoption = init_ternary_reloption(kinds, name, desc,
+ default_val, lockmode);
+
+ add_reloption((relopt_gen *) newoption);
+}
+
+/*
+ * add_local_ternary_reloption
+ * Add a new ternary local reloption
+ *
+ * 'offset' is offset of ternary-typed field.
+ */
+void
+add_local_ternary_reloption(local_relopts *relopts, const char *name,
+ const char *desc, ternary default_val,
+ int offset)
+{
+ relopt_ternary *newoption = init_ternary_reloption(RELOPT_KIND_LOCAL,
+ name, desc,
+ default_val, 0);
+
+ add_local_reloption(relopts, (relopt_gen *) newoption, offset);
+}
/*
* init_real_reloption
@@ -1617,6 +1689,18 @@ parse_one_reloption(relopt_value *option, char *text_str, int text_len,
option->gen->name, value)));
}
break;
+ case RELOPT_TYPE_TERNARY:
+ {
+ bool b;
+ parsed = parse_bool(value, &b);
+ option->values.ternary_val = b ? TERNARY_TRUE : TERNARY_FALSE;
+ if (validate && !parsed)
+ ereport(ERROR,
+ (errcode(ERRCODE_INVALID_PARAMETER_VALUE),
+ errmsg("invalid value for ternary option \"%s\": %s",
+ option->gen->name, value)));
+ }
+ break;
case RELOPT_TYPE_INT:
{
relopt_int *optint = (relopt_int *) option->gen;
@@ -1780,17 +1864,6 @@ fillRelOptions(void *rdopts, Size basesize,
char *itempos = ((char *) rdopts) + elems[j].offset;
char *string_val;
- /*
- * If isset_offset is provided, store whether the reloption is
- * set there.
- */
- if (elems[j].isset_offset > 0)
- {
- char *setpos = ((char *) rdopts) + elems[j].isset_offset;
-
- *(bool *) setpos = options[i].isset;
- }
-
switch (options[i].gen->type)
{
case RELOPT_TYPE_BOOL:
@@ -1798,6 +1871,11 @@ fillRelOptions(void *rdopts, Size basesize,
options[i].values.bool_val :
((relopt_bool *) options[i].gen)->default_val;
break;
+ case RELOPT_TYPE_TERNARY:
+ *(ternary *) itempos = options[i].isset ?
+ options[i].values.ternary_val :
+ ((relopt_ternary *) options[i].gen)->default_val;
+ break;
case RELOPT_TYPE_INT:
*(int *) itempos = options[i].isset ?
options[i].values.int_val :
@@ -1912,8 +1990,8 @@ default_reloptions(Datum reloptions, bool validate, relopt_kind kind)
offsetof(StdRdOptions, parallel_workers)},
{"vacuum_index_cleanup", RELOPT_TYPE_ENUM,
offsetof(StdRdOptions, vacuum_index_cleanup)},
- {"vacuum_truncate", RELOPT_TYPE_BOOL,
- offsetof(StdRdOptions, vacuum_truncate), offsetof(StdRdOptions, vacuum_truncate_set)},
+ {"vacuum_truncate", RELOPT_TYPE_TERNARY,
+ offsetof(StdRdOptions, vacuum_truncate)},
{"vacuum_max_eager_freeze_failure_rate", RELOPT_TYPE_REAL,
offsetof(StdRdOptions, vacuum_max_eager_freeze_failure_rate)}
};
@@ -1993,7 +2071,6 @@ build_local_reloptions(local_relopts *relopts, Datum options, bool validate)
elems[i].optname = opt->option->name;
elems[i].opttype = opt->option->type;
elems[i].offset = opt->offset;
- elems[i].isset_offset = 0; /* not supported for local relopts yet */
i++;
}
diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c
index 733ef40ae7..977babff54 100644
--- a/src/backend/commands/vacuum.c
+++ b/src/backend/commands/vacuum.c
@@ -2221,9 +2221,9 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams params,
{
StdRdOptions *opts = (StdRdOptions *) rel->rd_options;
- if (opts && opts->vacuum_truncate_set)
+ if (opts && opts->vacuum_truncate != TERNARY_UNSET)
{
- if (opts->vacuum_truncate)
+ if (opts->vacuum_truncate == TERNARY_TRUE)
params.truncate = VACOPTVALUE_ENABLED;
else
params.truncate = VACOPTVALUE_DISABLED;
diff --git a/src/include/access/reloptions.h b/src/include/access/reloptions.h
index a604a4702c..a436697658 100644
--- a/src/include/access/reloptions.h
+++ b/src/include/access/reloptions.h
@@ -29,6 +29,7 @@
typedef enum relopt_type
{
RELOPT_TYPE_BOOL,
+ RELOPT_TYPE_TERNARY, /* on, off, unset */
RELOPT_TYPE_INT,
RELOPT_TYPE_REAL,
RELOPT_TYPE_ENUM,
@@ -80,6 +81,7 @@ typedef struct relopt_value
union
{
bool bool_val;
+ ternary ternary_val;
int int_val;
double real_val;
int enum_val;
@@ -94,6 +96,12 @@ typedef struct relopt_bool
bool default_val;
} relopt_bool;
+typedef struct relopt_rernary
+{
+ relopt_gen gen;
+ int default_val;
+} relopt_ternary;
+
typedef struct relopt_int
{
relopt_gen gen;
@@ -152,19 +160,6 @@ typedef struct
const char *optname; /* option's name */
relopt_type opttype; /* option's datatype */
int offset; /* offset of field in result struct */
-
- /*
- * isset_offset is an optional offset of a field in the result struct that
- * stores whether the option is explicitly set for the relation or if it
- * just picked up the default value. In most cases, this can be
- * accomplished by giving the reloption a special out-of-range default
- * value (e.g., some integer reloptions use -2), but this isn't always
- * possible. For example, a Boolean reloption cannot be given an
- * out-of-range default, so we need another way to discover the source of
- * its value. This offset is only used if given a value greater than
- * zero.
- */
- int isset_offset;
} relopt_parse_elt;
/* Local reloption definition */
@@ -195,6 +190,8 @@ typedef struct local_relopts
extern relopt_kind add_reloption_kind(void);
extern void add_bool_reloption(bits32 kinds, const char *name, const char *desc,
bool default_val, LOCKMODE lockmode);
+extern void add_ternary_reloption(bits32 kinds, const char *name,
+ const char *desc, int default_val, LOCKMODE lockmode);
extern void add_int_reloption(bits32 kinds, const char *name, const char *desc,
int default_val, int min_val, int max_val,
LOCKMODE lockmode);
@@ -214,6 +211,9 @@ extern void register_reloptions_validator(local_relopts *relopts,
extern void add_local_bool_reloption(local_relopts *relopts, const char *name,
const char *desc, bool default_val,
int offset);
+extern void add_local_ternary_reloption(local_relopts *relopts,
+ const char *name, const char *desc,
+ ternary default_val, int offset);
extern void add_local_int_reloption(local_relopts *relopts, const char *name,
const char *desc, int default_val,
int min_val, int max_val, int offset);
diff --git a/src/include/c.h b/src/include/c.h
index 39022f8a9d..9c59ccccf8 100644
--- a/src/include/c.h
+++ b/src/include/c.h
@@ -488,6 +488,22 @@ typedef void (*pg_funcptr_t) (void);
#include <stdbool.h>
+/*
+ * ternary
+ * Boolean value with an extrea "unset" option
+ *
+ * Ternary data type is used in relation options that can be "true", "false" or
+ * "unset". Since relation options are used deep inside the PostgreSQL code,
+ * this type is declared globally.
+*/
+
+typedef enum ternary
+{
+ TERNARY_FALSE = 0,
+ TERNARY_TRUE = 1,
+ TERNARY_UNSET = -1
+} ternary;
+
/* ----------------------------------------------------------------
* Section 3: standard system types
diff --git a/src/include/utils/rel.h b/src/include/utils/rel.h
index b552359915..7346d618f9 100644
--- a/src/include/utils/rel.h
+++ b/src/include/utils/rel.h
@@ -346,8 +346,7 @@ typedef struct StdRdOptions
bool user_catalog_table; /* use as an additional catalog relation */
int parallel_workers; /* max number of parallel workers */
StdRdOptIndexCleanup vacuum_index_cleanup; /* controls index vacuuming */
- bool vacuum_truncate; /* enables vacuum to truncate a relation */
- bool vacuum_truncate_set; /* whether vacuum_truncate is set */
+ ternary vacuum_truncate; /* enables vacuum to truncate a relation */
/*
* Fraction of pages in a relation that vacuum can eagerly scan and fail
--
2.39.2
--nextPart5129932.5fSG56mABF
Content-Disposition: attachment;
filename="v2-0003-Add-alias-to-be-used-as-unset-state.patch"
Content-Transfer-Encoding: 7Bit
Content-Type: text/x-patch; charset="unicode-2-0-utf-8";
name="v2-0003-Add-alias-to-be-used-as-unset-state.patch"
^ permalink raw reply [nested|flat] 868+ messages in thread
* [PATCH v2a 2/4] Introduce ternary reloptions
@ 2025-09-04 14:41 Nikolay Shaplov <[email protected]>
0 siblings, 0 replies; 868+ messages in thread
From: Nikolay Shaplov @ 2025-09-04 14:41 UTC (permalink / raw)
Introduce ternary reloption as a replacement for current `vacuum_truncate`
implementation. Remove `vacuum_truncate_set` additional flag and using
`TERNARY_UNSET` value instead.
---
src/backend/access/common/reloptions.c | 129 ++++++++++++++++++++-----
src/backend/commands/vacuum.c | 4 +-
src/include/access/reloptions.h | 26 ++---
src/include/c.h | 16 +++
src/include/utils/rel.h | 3 +-
5 files changed, 135 insertions(+), 43 deletions(-)
diff --git a/src/backend/access/common/reloptions.c b/src/backend/access/common/reloptions.c
index 0af3fea68f..f543f11001 100644
--- a/src/backend/access/common/reloptions.c
+++ b/src/backend/access/common/reloptions.c
@@ -40,9 +40,9 @@
*
* To add an option:
*
- * (i) decide on a type (bool, integer, real, enum, string), name, default
- * value, upper and lower bounds (if applicable); for strings, consider a
- * validation routine.
+ * (i) decide on a type (bool, ternary, integer, real, enum, string), name,
+ * default value, upper and lower bounds (if applicable); for strings,
+ * consider a validation routine.
* (ii) add a record below (or use add_<type>_reloption).
* (iii) add it to the appropriate options struct (perhaps StdRdOptions)
* (iv) add it to the appropriate handling routine (perhaps
@@ -147,15 +147,6 @@ static relopt_bool boolRelOpts[] =
},
false
},
- {
- {
- "vacuum_truncate",
- "Enables vacuum to truncate empty pages at the end of this table",
- RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
- ShareUpdateExclusiveLock
- },
- true
- },
{
{
"deduplicate_items",
@@ -170,6 +161,21 @@ static relopt_bool boolRelOpts[] =
{{NULL}}
};
+static relopt_ternary ternaryRelOpts[] =
+{
+ {
+ {
+ "vacuum_truncate",
+ "Enables vacuum to truncate empty pages at the end of this table",
+ RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
+ ShareUpdateExclusiveLock
+ },
+ TERNARY_UNSET
+ },
+ /* list terminator */
+ {{NULL}}
+};
+
static relopt_int intRelOpts[] =
{
{
@@ -600,6 +606,13 @@ initialize_reloptions(void)
boolRelOpts[i].gen.lockmode));
j++;
}
+ for (i = 0; ternaryRelOpts[i].gen.name; i++)
+ {
+ Assert(DoLockModesConflict(ternaryRelOpts[i].gen.lockmode,
+ ternaryRelOpts[i].gen.lockmode));
+ j++;
+ }
+
for (i = 0; intRelOpts[i].gen.name; i++)
{
Assert(DoLockModesConflict(intRelOpts[i].gen.lockmode,
@@ -640,6 +653,14 @@ initialize_reloptions(void)
j++;
}
+ for (i = 0; ternaryRelOpts[i].gen.name; i++)
+ {
+ relOpts[j] = &ternaryRelOpts[i].gen;
+ relOpts[j]->type = RELOPT_TYPE_TERNARY;
+ relOpts[j]->namelen = strlen(relOpts[j]->name);
+ j++;
+ }
+
for (i = 0; intRelOpts[i].gen.name; i++)
{
relOpts[j] = &intRelOpts[i].gen;
@@ -800,6 +821,9 @@ allocate_reloption(bits32 kinds, int type, const char *name, const char *desc,
case RELOPT_TYPE_BOOL:
size = sizeof(relopt_bool);
break;
+ case RELOPT_TYPE_TERNARY:
+ size = sizeof(relopt_ternary);
+ break;
case RELOPT_TYPE_INT:
size = sizeof(relopt_int);
break;
@@ -883,6 +907,54 @@ add_local_bool_reloption(local_relopts *relopts, const char *name,
add_local_reloption(relopts, (relopt_gen *) newoption, offset);
}
+/*
+ * init_ternary_reloption
+ * Allocate and initialize a new ternary reloption
+ */
+static relopt_ternary *
+init_ternary_reloption(bits32 kinds, const char *name, const char *desc,
+ ternary default_val, LOCKMODE lockmode)
+{
+ relopt_ternary *newoption;
+
+ newoption = (relopt_ternary *) allocate_reloption(kinds,
+ RELOPT_TYPE_TERNARY, name, desc, lockmode);
+ newoption->default_val = default_val;
+
+ return newoption;
+}
+
+/*
+ * add_ternary_reloption
+ * Add a new ternary reloption
+ */
+void
+add_ternary_reloption(bits32 kinds, const char *name, const char *desc,
+ ternary default_val, LOCKMODE lockmode)
+{
+ relopt_ternary *newoption = init_ternary_reloption(kinds, name, desc,
+ default_val, lockmode);
+
+ add_reloption((relopt_gen *) newoption);
+}
+
+/*
+ * add_local_ternary_reloption
+ * Add a new ternary local reloption
+ *
+ * 'offset' is offset of ternary-typed field.
+ */
+void
+add_local_ternary_reloption(local_relopts *relopts, const char *name,
+ const char *desc, ternary default_val,
+ int offset)
+{
+ relopt_ternary *newoption = init_ternary_reloption(RELOPT_KIND_LOCAL,
+ name, desc,
+ default_val, 0);
+
+ add_local_reloption(relopts, (relopt_gen *) newoption, offset);
+}
/*
* init_real_reloption
@@ -1617,6 +1689,18 @@ parse_one_reloption(relopt_value *option, char *text_str, int text_len,
option->gen->name, value)));
}
break;
+ case RELOPT_TYPE_TERNARY:
+ {
+ bool b;
+ parsed = parse_bool(value, &b);
+ option->values.ternary_val = b ? TERNARY_TRUE : TERNARY_FALSE;
+ if (validate && !parsed)
+ ereport(ERROR,
+ (errcode(ERRCODE_INVALID_PARAMETER_VALUE),
+ errmsg("invalid value for ternary option \"%s\": %s",
+ option->gen->name, value)));
+ }
+ break;
case RELOPT_TYPE_INT:
{
relopt_int *optint = (relopt_int *) option->gen;
@@ -1780,17 +1864,6 @@ fillRelOptions(void *rdopts, Size basesize,
char *itempos = ((char *) rdopts) + elems[j].offset;
char *string_val;
- /*
- * If isset_offset is provided, store whether the reloption is
- * set there.
- */
- if (elems[j].isset_offset > 0)
- {
- char *setpos = ((char *) rdopts) + elems[j].isset_offset;
-
- *(bool *) setpos = options[i].isset;
- }
-
switch (options[i].gen->type)
{
case RELOPT_TYPE_BOOL:
@@ -1798,6 +1871,11 @@ fillRelOptions(void *rdopts, Size basesize,
options[i].values.bool_val :
((relopt_bool *) options[i].gen)->default_val;
break;
+ case RELOPT_TYPE_TERNARY:
+ *(ternary *) itempos = options[i].isset ?
+ options[i].values.ternary_val :
+ ((relopt_ternary *) options[i].gen)->default_val;
+ break;
case RELOPT_TYPE_INT:
*(int *) itempos = options[i].isset ?
options[i].values.int_val :
@@ -1912,8 +1990,8 @@ default_reloptions(Datum reloptions, bool validate, relopt_kind kind)
offsetof(StdRdOptions, parallel_workers)},
{"vacuum_index_cleanup", RELOPT_TYPE_ENUM,
offsetof(StdRdOptions, vacuum_index_cleanup)},
- {"vacuum_truncate", RELOPT_TYPE_BOOL,
- offsetof(StdRdOptions, vacuum_truncate), offsetof(StdRdOptions, vacuum_truncate_set)},
+ {"vacuum_truncate", RELOPT_TYPE_TERNARY,
+ offsetof(StdRdOptions, vacuum_truncate)},
{"vacuum_max_eager_freeze_failure_rate", RELOPT_TYPE_REAL,
offsetof(StdRdOptions, vacuum_max_eager_freeze_failure_rate)}
};
@@ -1993,7 +2071,6 @@ build_local_reloptions(local_relopts *relopts, Datum options, bool validate)
elems[i].optname = opt->option->name;
elems[i].opttype = opt->option->type;
elems[i].offset = opt->offset;
- elems[i].isset_offset = 0; /* not supported for local relopts yet */
i++;
}
diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c
index 733ef40ae7..977babff54 100644
--- a/src/backend/commands/vacuum.c
+++ b/src/backend/commands/vacuum.c
@@ -2221,9 +2221,9 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams params,
{
StdRdOptions *opts = (StdRdOptions *) rel->rd_options;
- if (opts && opts->vacuum_truncate_set)
+ if (opts && opts->vacuum_truncate != TERNARY_UNSET)
{
- if (opts->vacuum_truncate)
+ if (opts->vacuum_truncate == TERNARY_TRUE)
params.truncate = VACOPTVALUE_ENABLED;
else
params.truncate = VACOPTVALUE_DISABLED;
diff --git a/src/include/access/reloptions.h b/src/include/access/reloptions.h
index a604a4702c..a436697658 100644
--- a/src/include/access/reloptions.h
+++ b/src/include/access/reloptions.h
@@ -29,6 +29,7 @@
typedef enum relopt_type
{
RELOPT_TYPE_BOOL,
+ RELOPT_TYPE_TERNARY, /* on, off, unset */
RELOPT_TYPE_INT,
RELOPT_TYPE_REAL,
RELOPT_TYPE_ENUM,
@@ -80,6 +81,7 @@ typedef struct relopt_value
union
{
bool bool_val;
+ ternary ternary_val;
int int_val;
double real_val;
int enum_val;
@@ -94,6 +96,12 @@ typedef struct relopt_bool
bool default_val;
} relopt_bool;
+typedef struct relopt_rernary
+{
+ relopt_gen gen;
+ int default_val;
+} relopt_ternary;
+
typedef struct relopt_int
{
relopt_gen gen;
@@ -152,19 +160,6 @@ typedef struct
const char *optname; /* option's name */
relopt_type opttype; /* option's datatype */
int offset; /* offset of field in result struct */
-
- /*
- * isset_offset is an optional offset of a field in the result struct that
- * stores whether the option is explicitly set for the relation or if it
- * just picked up the default value. In most cases, this can be
- * accomplished by giving the reloption a special out-of-range default
- * value (e.g., some integer reloptions use -2), but this isn't always
- * possible. For example, a Boolean reloption cannot be given an
- * out-of-range default, so we need another way to discover the source of
- * its value. This offset is only used if given a value greater than
- * zero.
- */
- int isset_offset;
} relopt_parse_elt;
/* Local reloption definition */
@@ -195,6 +190,8 @@ typedef struct local_relopts
extern relopt_kind add_reloption_kind(void);
extern void add_bool_reloption(bits32 kinds, const char *name, const char *desc,
bool default_val, LOCKMODE lockmode);
+extern void add_ternary_reloption(bits32 kinds, const char *name,
+ const char *desc, int default_val, LOCKMODE lockmode);
extern void add_int_reloption(bits32 kinds, const char *name, const char *desc,
int default_val, int min_val, int max_val,
LOCKMODE lockmode);
@@ -214,6 +211,9 @@ extern void register_reloptions_validator(local_relopts *relopts,
extern void add_local_bool_reloption(local_relopts *relopts, const char *name,
const char *desc, bool default_val,
int offset);
+extern void add_local_ternary_reloption(local_relopts *relopts,
+ const char *name, const char *desc,
+ ternary default_val, int offset);
extern void add_local_int_reloption(local_relopts *relopts, const char *name,
const char *desc, int default_val,
int min_val, int max_val, int offset);
diff --git a/src/include/c.h b/src/include/c.h
index 39022f8a9d..9c59ccccf8 100644
--- a/src/include/c.h
+++ b/src/include/c.h
@@ -488,6 +488,22 @@ typedef void (*pg_funcptr_t) (void);
#include <stdbool.h>
+/*
+ * ternary
+ * Boolean value with an extrea "unset" option
+ *
+ * Ternary data type is used in relation options that can be "true", "false" or
+ * "unset". Since relation options are used deep inside the PostgreSQL code,
+ * this type is declared globally.
+*/
+
+typedef enum ternary
+{
+ TERNARY_FALSE = 0,
+ TERNARY_TRUE = 1,
+ TERNARY_UNSET = -1
+} ternary;
+
/* ----------------------------------------------------------------
* Section 3: standard system types
diff --git a/src/include/utils/rel.h b/src/include/utils/rel.h
index b552359915..7346d618f9 100644
--- a/src/include/utils/rel.h
+++ b/src/include/utils/rel.h
@@ -346,8 +346,7 @@ typedef struct StdRdOptions
bool user_catalog_table; /* use as an additional catalog relation */
int parallel_workers; /* max number of parallel workers */
StdRdOptIndexCleanup vacuum_index_cleanup; /* controls index vacuuming */
- bool vacuum_truncate; /* enables vacuum to truncate a relation */
- bool vacuum_truncate_set; /* whether vacuum_truncate is set */
+ ternary vacuum_truncate; /* enables vacuum to truncate a relation */
/*
* Fraction of pages in a relation that vacuum can eagerly scan and fail
--
2.39.2
--nextPart4155600.3ZeAukHxDK
Content-Disposition: attachment;
filename="v2a-0003-Add-alias-to-be-used-as-unset-state.patch"
Content-Transfer-Encoding: 7Bit
Content-Type: text/x-patch; charset="unicode-2-0-utf-8";
name="v2a-0003-Add-alias-to-be-used-as-unset-state.patch"
^ permalink raw reply [nested|flat] 868+ messages in thread
* [PATCH v2 2/4] Introduce ternary reloptions
@ 2025-09-04 14:41 Nikolay Shaplov <[email protected]>
0 siblings, 0 replies; 868+ messages in thread
From: Nikolay Shaplov @ 2025-09-04 14:41 UTC (permalink / raw)
Introduce ternary reloption as a replacement for current `vacuum_truncate`
implementation. Remove `vacuum_truncate_set` additional flag and using
`TERNARY_UNSET` value instead.
---
src/backend/access/common/reloptions.c | 129 ++++++++++++++++++++-----
src/backend/commands/vacuum.c | 4 +-
src/include/access/reloptions.h | 26 ++---
src/include/c.h | 16 +++
src/include/utils/rel.h | 3 +-
5 files changed, 135 insertions(+), 43 deletions(-)
diff --git a/src/backend/access/common/reloptions.c b/src/backend/access/common/reloptions.c
index 0af3fea68f..f543f11001 100644
--- a/src/backend/access/common/reloptions.c
+++ b/src/backend/access/common/reloptions.c
@@ -40,9 +40,9 @@
*
* To add an option:
*
- * (i) decide on a type (bool, integer, real, enum, string), name, default
- * value, upper and lower bounds (if applicable); for strings, consider a
- * validation routine.
+ * (i) decide on a type (bool, ternary, integer, real, enum, string), name,
+ * default value, upper and lower bounds (if applicable); for strings,
+ * consider a validation routine.
* (ii) add a record below (or use add_<type>_reloption).
* (iii) add it to the appropriate options struct (perhaps StdRdOptions)
* (iv) add it to the appropriate handling routine (perhaps
@@ -147,15 +147,6 @@ static relopt_bool boolRelOpts[] =
},
false
},
- {
- {
- "vacuum_truncate",
- "Enables vacuum to truncate empty pages at the end of this table",
- RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
- ShareUpdateExclusiveLock
- },
- true
- },
{
{
"deduplicate_items",
@@ -170,6 +161,21 @@ static relopt_bool boolRelOpts[] =
{{NULL}}
};
+static relopt_ternary ternaryRelOpts[] =
+{
+ {
+ {
+ "vacuum_truncate",
+ "Enables vacuum to truncate empty pages at the end of this table",
+ RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
+ ShareUpdateExclusiveLock
+ },
+ TERNARY_UNSET
+ },
+ /* list terminator */
+ {{NULL}}
+};
+
static relopt_int intRelOpts[] =
{
{
@@ -600,6 +606,13 @@ initialize_reloptions(void)
boolRelOpts[i].gen.lockmode));
j++;
}
+ for (i = 0; ternaryRelOpts[i].gen.name; i++)
+ {
+ Assert(DoLockModesConflict(ternaryRelOpts[i].gen.lockmode,
+ ternaryRelOpts[i].gen.lockmode));
+ j++;
+ }
+
for (i = 0; intRelOpts[i].gen.name; i++)
{
Assert(DoLockModesConflict(intRelOpts[i].gen.lockmode,
@@ -640,6 +653,14 @@ initialize_reloptions(void)
j++;
}
+ for (i = 0; ternaryRelOpts[i].gen.name; i++)
+ {
+ relOpts[j] = &ternaryRelOpts[i].gen;
+ relOpts[j]->type = RELOPT_TYPE_TERNARY;
+ relOpts[j]->namelen = strlen(relOpts[j]->name);
+ j++;
+ }
+
for (i = 0; intRelOpts[i].gen.name; i++)
{
relOpts[j] = &intRelOpts[i].gen;
@@ -800,6 +821,9 @@ allocate_reloption(bits32 kinds, int type, const char *name, const char *desc,
case RELOPT_TYPE_BOOL:
size = sizeof(relopt_bool);
break;
+ case RELOPT_TYPE_TERNARY:
+ size = sizeof(relopt_ternary);
+ break;
case RELOPT_TYPE_INT:
size = sizeof(relopt_int);
break;
@@ -883,6 +907,54 @@ add_local_bool_reloption(local_relopts *relopts, const char *name,
add_local_reloption(relopts, (relopt_gen *) newoption, offset);
}
+/*
+ * init_ternary_reloption
+ * Allocate and initialize a new ternary reloption
+ */
+static relopt_ternary *
+init_ternary_reloption(bits32 kinds, const char *name, const char *desc,
+ ternary default_val, LOCKMODE lockmode)
+{
+ relopt_ternary *newoption;
+
+ newoption = (relopt_ternary *) allocate_reloption(kinds,
+ RELOPT_TYPE_TERNARY, name, desc, lockmode);
+ newoption->default_val = default_val;
+
+ return newoption;
+}
+
+/*
+ * add_ternary_reloption
+ * Add a new ternary reloption
+ */
+void
+add_ternary_reloption(bits32 kinds, const char *name, const char *desc,
+ ternary default_val, LOCKMODE lockmode)
+{
+ relopt_ternary *newoption = init_ternary_reloption(kinds, name, desc,
+ default_val, lockmode);
+
+ add_reloption((relopt_gen *) newoption);
+}
+
+/*
+ * add_local_ternary_reloption
+ * Add a new ternary local reloption
+ *
+ * 'offset' is offset of ternary-typed field.
+ */
+void
+add_local_ternary_reloption(local_relopts *relopts, const char *name,
+ const char *desc, ternary default_val,
+ int offset)
+{
+ relopt_ternary *newoption = init_ternary_reloption(RELOPT_KIND_LOCAL,
+ name, desc,
+ default_val, 0);
+
+ add_local_reloption(relopts, (relopt_gen *) newoption, offset);
+}
/*
* init_real_reloption
@@ -1617,6 +1689,18 @@ parse_one_reloption(relopt_value *option, char *text_str, int text_len,
option->gen->name, value)));
}
break;
+ case RELOPT_TYPE_TERNARY:
+ {
+ bool b;
+ parsed = parse_bool(value, &b);
+ option->values.ternary_val = b ? TERNARY_TRUE : TERNARY_FALSE;
+ if (validate && !parsed)
+ ereport(ERROR,
+ (errcode(ERRCODE_INVALID_PARAMETER_VALUE),
+ errmsg("invalid value for ternary option \"%s\": %s",
+ option->gen->name, value)));
+ }
+ break;
case RELOPT_TYPE_INT:
{
relopt_int *optint = (relopt_int *) option->gen;
@@ -1780,17 +1864,6 @@ fillRelOptions(void *rdopts, Size basesize,
char *itempos = ((char *) rdopts) + elems[j].offset;
char *string_val;
- /*
- * If isset_offset is provided, store whether the reloption is
- * set there.
- */
- if (elems[j].isset_offset > 0)
- {
- char *setpos = ((char *) rdopts) + elems[j].isset_offset;
-
- *(bool *) setpos = options[i].isset;
- }
-
switch (options[i].gen->type)
{
case RELOPT_TYPE_BOOL:
@@ -1798,6 +1871,11 @@ fillRelOptions(void *rdopts, Size basesize,
options[i].values.bool_val :
((relopt_bool *) options[i].gen)->default_val;
break;
+ case RELOPT_TYPE_TERNARY:
+ *(ternary *) itempos = options[i].isset ?
+ options[i].values.ternary_val :
+ ((relopt_ternary *) options[i].gen)->default_val;
+ break;
case RELOPT_TYPE_INT:
*(int *) itempos = options[i].isset ?
options[i].values.int_val :
@@ -1912,8 +1990,8 @@ default_reloptions(Datum reloptions, bool validate, relopt_kind kind)
offsetof(StdRdOptions, parallel_workers)},
{"vacuum_index_cleanup", RELOPT_TYPE_ENUM,
offsetof(StdRdOptions, vacuum_index_cleanup)},
- {"vacuum_truncate", RELOPT_TYPE_BOOL,
- offsetof(StdRdOptions, vacuum_truncate), offsetof(StdRdOptions, vacuum_truncate_set)},
+ {"vacuum_truncate", RELOPT_TYPE_TERNARY,
+ offsetof(StdRdOptions, vacuum_truncate)},
{"vacuum_max_eager_freeze_failure_rate", RELOPT_TYPE_REAL,
offsetof(StdRdOptions, vacuum_max_eager_freeze_failure_rate)}
};
@@ -1993,7 +2071,6 @@ build_local_reloptions(local_relopts *relopts, Datum options, bool validate)
elems[i].optname = opt->option->name;
elems[i].opttype = opt->option->type;
elems[i].offset = opt->offset;
- elems[i].isset_offset = 0; /* not supported for local relopts yet */
i++;
}
diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c
index 733ef40ae7..977babff54 100644
--- a/src/backend/commands/vacuum.c
+++ b/src/backend/commands/vacuum.c
@@ -2221,9 +2221,9 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams params,
{
StdRdOptions *opts = (StdRdOptions *) rel->rd_options;
- if (opts && opts->vacuum_truncate_set)
+ if (opts && opts->vacuum_truncate != TERNARY_UNSET)
{
- if (opts->vacuum_truncate)
+ if (opts->vacuum_truncate == TERNARY_TRUE)
params.truncate = VACOPTVALUE_ENABLED;
else
params.truncate = VACOPTVALUE_DISABLED;
diff --git a/src/include/access/reloptions.h b/src/include/access/reloptions.h
index a604a4702c..a436697658 100644
--- a/src/include/access/reloptions.h
+++ b/src/include/access/reloptions.h
@@ -29,6 +29,7 @@
typedef enum relopt_type
{
RELOPT_TYPE_BOOL,
+ RELOPT_TYPE_TERNARY, /* on, off, unset */
RELOPT_TYPE_INT,
RELOPT_TYPE_REAL,
RELOPT_TYPE_ENUM,
@@ -80,6 +81,7 @@ typedef struct relopt_value
union
{
bool bool_val;
+ ternary ternary_val;
int int_val;
double real_val;
int enum_val;
@@ -94,6 +96,12 @@ typedef struct relopt_bool
bool default_val;
} relopt_bool;
+typedef struct relopt_rernary
+{
+ relopt_gen gen;
+ int default_val;
+} relopt_ternary;
+
typedef struct relopt_int
{
relopt_gen gen;
@@ -152,19 +160,6 @@ typedef struct
const char *optname; /* option's name */
relopt_type opttype; /* option's datatype */
int offset; /* offset of field in result struct */
-
- /*
- * isset_offset is an optional offset of a field in the result struct that
- * stores whether the option is explicitly set for the relation or if it
- * just picked up the default value. In most cases, this can be
- * accomplished by giving the reloption a special out-of-range default
- * value (e.g., some integer reloptions use -2), but this isn't always
- * possible. For example, a Boolean reloption cannot be given an
- * out-of-range default, so we need another way to discover the source of
- * its value. This offset is only used if given a value greater than
- * zero.
- */
- int isset_offset;
} relopt_parse_elt;
/* Local reloption definition */
@@ -195,6 +190,8 @@ typedef struct local_relopts
extern relopt_kind add_reloption_kind(void);
extern void add_bool_reloption(bits32 kinds, const char *name, const char *desc,
bool default_val, LOCKMODE lockmode);
+extern void add_ternary_reloption(bits32 kinds, const char *name,
+ const char *desc, int default_val, LOCKMODE lockmode);
extern void add_int_reloption(bits32 kinds, const char *name, const char *desc,
int default_val, int min_val, int max_val,
LOCKMODE lockmode);
@@ -214,6 +211,9 @@ extern void register_reloptions_validator(local_relopts *relopts,
extern void add_local_bool_reloption(local_relopts *relopts, const char *name,
const char *desc, bool default_val,
int offset);
+extern void add_local_ternary_reloption(local_relopts *relopts,
+ const char *name, const char *desc,
+ ternary default_val, int offset);
extern void add_local_int_reloption(local_relopts *relopts, const char *name,
const char *desc, int default_val,
int min_val, int max_val, int offset);
diff --git a/src/include/c.h b/src/include/c.h
index 39022f8a9d..9c59ccccf8 100644
--- a/src/include/c.h
+++ b/src/include/c.h
@@ -488,6 +488,22 @@ typedef void (*pg_funcptr_t) (void);
#include <stdbool.h>
+/*
+ * ternary
+ * Boolean value with an extrea "unset" option
+ *
+ * Ternary data type is used in relation options that can be "true", "false" or
+ * "unset". Since relation options are used deep inside the PostgreSQL code,
+ * this type is declared globally.
+*/
+
+typedef enum ternary
+{
+ TERNARY_FALSE = 0,
+ TERNARY_TRUE = 1,
+ TERNARY_UNSET = -1
+} ternary;
+
/* ----------------------------------------------------------------
* Section 3: standard system types
diff --git a/src/include/utils/rel.h b/src/include/utils/rel.h
index b552359915..7346d618f9 100644
--- a/src/include/utils/rel.h
+++ b/src/include/utils/rel.h
@@ -346,8 +346,7 @@ typedef struct StdRdOptions
bool user_catalog_table; /* use as an additional catalog relation */
int parallel_workers; /* max number of parallel workers */
StdRdOptIndexCleanup vacuum_index_cleanup; /* controls index vacuuming */
- bool vacuum_truncate; /* enables vacuum to truncate a relation */
- bool vacuum_truncate_set; /* whether vacuum_truncate is set */
+ ternary vacuum_truncate; /* enables vacuum to truncate a relation */
/*
* Fraction of pages in a relation that vacuum can eagerly scan and fail
--
2.39.2
--nextPart5129932.5fSG56mABF
Content-Disposition: attachment;
filename="v2-0003-Add-alias-to-be-used-as-unset-state.patch"
Content-Transfer-Encoding: 7Bit
Content-Type: text/x-patch; charset="unicode-2-0-utf-8";
name="v2-0003-Add-alias-to-be-used-as-unset-state.patch"
^ permalink raw reply [nested|flat] 868+ messages in thread
* [PATCH v2a 2/4] Introduce ternary reloptions
@ 2025-09-04 14:41 Nikolay Shaplov <[email protected]>
0 siblings, 0 replies; 868+ messages in thread
From: Nikolay Shaplov @ 2025-09-04 14:41 UTC (permalink / raw)
Introduce ternary reloption as a replacement for current `vacuum_truncate`
implementation. Remove `vacuum_truncate_set` additional flag and using
`TERNARY_UNSET` value instead.
---
src/backend/access/common/reloptions.c | 129 ++++++++++++++++++++-----
src/backend/commands/vacuum.c | 4 +-
src/include/access/reloptions.h | 26 ++---
src/include/c.h | 16 +++
src/include/utils/rel.h | 3 +-
5 files changed, 135 insertions(+), 43 deletions(-)
diff --git a/src/backend/access/common/reloptions.c b/src/backend/access/common/reloptions.c
index 0af3fea68f..f543f11001 100644
--- a/src/backend/access/common/reloptions.c
+++ b/src/backend/access/common/reloptions.c
@@ -40,9 +40,9 @@
*
* To add an option:
*
- * (i) decide on a type (bool, integer, real, enum, string), name, default
- * value, upper and lower bounds (if applicable); for strings, consider a
- * validation routine.
+ * (i) decide on a type (bool, ternary, integer, real, enum, string), name,
+ * default value, upper and lower bounds (if applicable); for strings,
+ * consider a validation routine.
* (ii) add a record below (or use add_<type>_reloption).
* (iii) add it to the appropriate options struct (perhaps StdRdOptions)
* (iv) add it to the appropriate handling routine (perhaps
@@ -147,15 +147,6 @@ static relopt_bool boolRelOpts[] =
},
false
},
- {
- {
- "vacuum_truncate",
- "Enables vacuum to truncate empty pages at the end of this table",
- RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
- ShareUpdateExclusiveLock
- },
- true
- },
{
{
"deduplicate_items",
@@ -170,6 +161,21 @@ static relopt_bool boolRelOpts[] =
{{NULL}}
};
+static relopt_ternary ternaryRelOpts[] =
+{
+ {
+ {
+ "vacuum_truncate",
+ "Enables vacuum to truncate empty pages at the end of this table",
+ RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
+ ShareUpdateExclusiveLock
+ },
+ TERNARY_UNSET
+ },
+ /* list terminator */
+ {{NULL}}
+};
+
static relopt_int intRelOpts[] =
{
{
@@ -600,6 +606,13 @@ initialize_reloptions(void)
boolRelOpts[i].gen.lockmode));
j++;
}
+ for (i = 0; ternaryRelOpts[i].gen.name; i++)
+ {
+ Assert(DoLockModesConflict(ternaryRelOpts[i].gen.lockmode,
+ ternaryRelOpts[i].gen.lockmode));
+ j++;
+ }
+
for (i = 0; intRelOpts[i].gen.name; i++)
{
Assert(DoLockModesConflict(intRelOpts[i].gen.lockmode,
@@ -640,6 +653,14 @@ initialize_reloptions(void)
j++;
}
+ for (i = 0; ternaryRelOpts[i].gen.name; i++)
+ {
+ relOpts[j] = &ternaryRelOpts[i].gen;
+ relOpts[j]->type = RELOPT_TYPE_TERNARY;
+ relOpts[j]->namelen = strlen(relOpts[j]->name);
+ j++;
+ }
+
for (i = 0; intRelOpts[i].gen.name; i++)
{
relOpts[j] = &intRelOpts[i].gen;
@@ -800,6 +821,9 @@ allocate_reloption(bits32 kinds, int type, const char *name, const char *desc,
case RELOPT_TYPE_BOOL:
size = sizeof(relopt_bool);
break;
+ case RELOPT_TYPE_TERNARY:
+ size = sizeof(relopt_ternary);
+ break;
case RELOPT_TYPE_INT:
size = sizeof(relopt_int);
break;
@@ -883,6 +907,54 @@ add_local_bool_reloption(local_relopts *relopts, const char *name,
add_local_reloption(relopts, (relopt_gen *) newoption, offset);
}
+/*
+ * init_ternary_reloption
+ * Allocate and initialize a new ternary reloption
+ */
+static relopt_ternary *
+init_ternary_reloption(bits32 kinds, const char *name, const char *desc,
+ ternary default_val, LOCKMODE lockmode)
+{
+ relopt_ternary *newoption;
+
+ newoption = (relopt_ternary *) allocate_reloption(kinds,
+ RELOPT_TYPE_TERNARY, name, desc, lockmode);
+ newoption->default_val = default_val;
+
+ return newoption;
+}
+
+/*
+ * add_ternary_reloption
+ * Add a new ternary reloption
+ */
+void
+add_ternary_reloption(bits32 kinds, const char *name, const char *desc,
+ ternary default_val, LOCKMODE lockmode)
+{
+ relopt_ternary *newoption = init_ternary_reloption(kinds, name, desc,
+ default_val, lockmode);
+
+ add_reloption((relopt_gen *) newoption);
+}
+
+/*
+ * add_local_ternary_reloption
+ * Add a new ternary local reloption
+ *
+ * 'offset' is offset of ternary-typed field.
+ */
+void
+add_local_ternary_reloption(local_relopts *relopts, const char *name,
+ const char *desc, ternary default_val,
+ int offset)
+{
+ relopt_ternary *newoption = init_ternary_reloption(RELOPT_KIND_LOCAL,
+ name, desc,
+ default_val, 0);
+
+ add_local_reloption(relopts, (relopt_gen *) newoption, offset);
+}
/*
* init_real_reloption
@@ -1617,6 +1689,18 @@ parse_one_reloption(relopt_value *option, char *text_str, int text_len,
option->gen->name, value)));
}
break;
+ case RELOPT_TYPE_TERNARY:
+ {
+ bool b;
+ parsed = parse_bool(value, &b);
+ option->values.ternary_val = b ? TERNARY_TRUE : TERNARY_FALSE;
+ if (validate && !parsed)
+ ereport(ERROR,
+ (errcode(ERRCODE_INVALID_PARAMETER_VALUE),
+ errmsg("invalid value for ternary option \"%s\": %s",
+ option->gen->name, value)));
+ }
+ break;
case RELOPT_TYPE_INT:
{
relopt_int *optint = (relopt_int *) option->gen;
@@ -1780,17 +1864,6 @@ fillRelOptions(void *rdopts, Size basesize,
char *itempos = ((char *) rdopts) + elems[j].offset;
char *string_val;
- /*
- * If isset_offset is provided, store whether the reloption is
- * set there.
- */
- if (elems[j].isset_offset > 0)
- {
- char *setpos = ((char *) rdopts) + elems[j].isset_offset;
-
- *(bool *) setpos = options[i].isset;
- }
-
switch (options[i].gen->type)
{
case RELOPT_TYPE_BOOL:
@@ -1798,6 +1871,11 @@ fillRelOptions(void *rdopts, Size basesize,
options[i].values.bool_val :
((relopt_bool *) options[i].gen)->default_val;
break;
+ case RELOPT_TYPE_TERNARY:
+ *(ternary *) itempos = options[i].isset ?
+ options[i].values.ternary_val :
+ ((relopt_ternary *) options[i].gen)->default_val;
+ break;
case RELOPT_TYPE_INT:
*(int *) itempos = options[i].isset ?
options[i].values.int_val :
@@ -1912,8 +1990,8 @@ default_reloptions(Datum reloptions, bool validate, relopt_kind kind)
offsetof(StdRdOptions, parallel_workers)},
{"vacuum_index_cleanup", RELOPT_TYPE_ENUM,
offsetof(StdRdOptions, vacuum_index_cleanup)},
- {"vacuum_truncate", RELOPT_TYPE_BOOL,
- offsetof(StdRdOptions, vacuum_truncate), offsetof(StdRdOptions, vacuum_truncate_set)},
+ {"vacuum_truncate", RELOPT_TYPE_TERNARY,
+ offsetof(StdRdOptions, vacuum_truncate)},
{"vacuum_max_eager_freeze_failure_rate", RELOPT_TYPE_REAL,
offsetof(StdRdOptions, vacuum_max_eager_freeze_failure_rate)}
};
@@ -1993,7 +2071,6 @@ build_local_reloptions(local_relopts *relopts, Datum options, bool validate)
elems[i].optname = opt->option->name;
elems[i].opttype = opt->option->type;
elems[i].offset = opt->offset;
- elems[i].isset_offset = 0; /* not supported for local relopts yet */
i++;
}
diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c
index 733ef40ae7..977babff54 100644
--- a/src/backend/commands/vacuum.c
+++ b/src/backend/commands/vacuum.c
@@ -2221,9 +2221,9 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams params,
{
StdRdOptions *opts = (StdRdOptions *) rel->rd_options;
- if (opts && opts->vacuum_truncate_set)
+ if (opts && opts->vacuum_truncate != TERNARY_UNSET)
{
- if (opts->vacuum_truncate)
+ if (opts->vacuum_truncate == TERNARY_TRUE)
params.truncate = VACOPTVALUE_ENABLED;
else
params.truncate = VACOPTVALUE_DISABLED;
diff --git a/src/include/access/reloptions.h b/src/include/access/reloptions.h
index a604a4702c..a436697658 100644
--- a/src/include/access/reloptions.h
+++ b/src/include/access/reloptions.h
@@ -29,6 +29,7 @@
typedef enum relopt_type
{
RELOPT_TYPE_BOOL,
+ RELOPT_TYPE_TERNARY, /* on, off, unset */
RELOPT_TYPE_INT,
RELOPT_TYPE_REAL,
RELOPT_TYPE_ENUM,
@@ -80,6 +81,7 @@ typedef struct relopt_value
union
{
bool bool_val;
+ ternary ternary_val;
int int_val;
double real_val;
int enum_val;
@@ -94,6 +96,12 @@ typedef struct relopt_bool
bool default_val;
} relopt_bool;
+typedef struct relopt_rernary
+{
+ relopt_gen gen;
+ int default_val;
+} relopt_ternary;
+
typedef struct relopt_int
{
relopt_gen gen;
@@ -152,19 +160,6 @@ typedef struct
const char *optname; /* option's name */
relopt_type opttype; /* option's datatype */
int offset; /* offset of field in result struct */
-
- /*
- * isset_offset is an optional offset of a field in the result struct that
- * stores whether the option is explicitly set for the relation or if it
- * just picked up the default value. In most cases, this can be
- * accomplished by giving the reloption a special out-of-range default
- * value (e.g., some integer reloptions use -2), but this isn't always
- * possible. For example, a Boolean reloption cannot be given an
- * out-of-range default, so we need another way to discover the source of
- * its value. This offset is only used if given a value greater than
- * zero.
- */
- int isset_offset;
} relopt_parse_elt;
/* Local reloption definition */
@@ -195,6 +190,8 @@ typedef struct local_relopts
extern relopt_kind add_reloption_kind(void);
extern void add_bool_reloption(bits32 kinds, const char *name, const char *desc,
bool default_val, LOCKMODE lockmode);
+extern void add_ternary_reloption(bits32 kinds, const char *name,
+ const char *desc, int default_val, LOCKMODE lockmode);
extern void add_int_reloption(bits32 kinds, const char *name, const char *desc,
int default_val, int min_val, int max_val,
LOCKMODE lockmode);
@@ -214,6 +211,9 @@ extern void register_reloptions_validator(local_relopts *relopts,
extern void add_local_bool_reloption(local_relopts *relopts, const char *name,
const char *desc, bool default_val,
int offset);
+extern void add_local_ternary_reloption(local_relopts *relopts,
+ const char *name, const char *desc,
+ ternary default_val, int offset);
extern void add_local_int_reloption(local_relopts *relopts, const char *name,
const char *desc, int default_val,
int min_val, int max_val, int offset);
diff --git a/src/include/c.h b/src/include/c.h
index 39022f8a9d..9c59ccccf8 100644
--- a/src/include/c.h
+++ b/src/include/c.h
@@ -488,6 +488,22 @@ typedef void (*pg_funcptr_t) (void);
#include <stdbool.h>
+/*
+ * ternary
+ * Boolean value with an extrea "unset" option
+ *
+ * Ternary data type is used in relation options that can be "true", "false" or
+ * "unset". Since relation options are used deep inside the PostgreSQL code,
+ * this type is declared globally.
+*/
+
+typedef enum ternary
+{
+ TERNARY_FALSE = 0,
+ TERNARY_TRUE = 1,
+ TERNARY_UNSET = -1
+} ternary;
+
/* ----------------------------------------------------------------
* Section 3: standard system types
diff --git a/src/include/utils/rel.h b/src/include/utils/rel.h
index b552359915..7346d618f9 100644
--- a/src/include/utils/rel.h
+++ b/src/include/utils/rel.h
@@ -346,8 +346,7 @@ typedef struct StdRdOptions
bool user_catalog_table; /* use as an additional catalog relation */
int parallel_workers; /* max number of parallel workers */
StdRdOptIndexCleanup vacuum_index_cleanup; /* controls index vacuuming */
- bool vacuum_truncate; /* enables vacuum to truncate a relation */
- bool vacuum_truncate_set; /* whether vacuum_truncate is set */
+ ternary vacuum_truncate; /* enables vacuum to truncate a relation */
/*
* Fraction of pages in a relation that vacuum can eagerly scan and fail
--
2.39.2
--nextPart4155600.3ZeAukHxDK
Content-Disposition: attachment;
filename="v2a-0003-Add-alias-to-be-used-as-unset-state.patch"
Content-Transfer-Encoding: 7Bit
Content-Type: text/x-patch; charset="unicode-2-0-utf-8";
name="v2a-0003-Add-alias-to-be-used-as-unset-state.patch"
^ permalink raw reply [nested|flat] 868+ messages in thread
* [PATCH v2a 2/4] Introduce ternary reloptions
@ 2025-09-04 14:41 Nikolay Shaplov <[email protected]>
0 siblings, 0 replies; 868+ messages in thread
From: Nikolay Shaplov @ 2025-09-04 14:41 UTC (permalink / raw)
Introduce ternary reloption as a replacement for current `vacuum_truncate`
implementation. Remove `vacuum_truncate_set` additional flag and using
`TERNARY_UNSET` value instead.
---
src/backend/access/common/reloptions.c | 129 ++++++++++++++++++++-----
src/backend/commands/vacuum.c | 4 +-
src/include/access/reloptions.h | 26 ++---
src/include/c.h | 16 +++
src/include/utils/rel.h | 3 +-
5 files changed, 135 insertions(+), 43 deletions(-)
diff --git a/src/backend/access/common/reloptions.c b/src/backend/access/common/reloptions.c
index 0af3fea68f..f543f11001 100644
--- a/src/backend/access/common/reloptions.c
+++ b/src/backend/access/common/reloptions.c
@@ -40,9 +40,9 @@
*
* To add an option:
*
- * (i) decide on a type (bool, integer, real, enum, string), name, default
- * value, upper and lower bounds (if applicable); for strings, consider a
- * validation routine.
+ * (i) decide on a type (bool, ternary, integer, real, enum, string), name,
+ * default value, upper and lower bounds (if applicable); for strings,
+ * consider a validation routine.
* (ii) add a record below (or use add_<type>_reloption).
* (iii) add it to the appropriate options struct (perhaps StdRdOptions)
* (iv) add it to the appropriate handling routine (perhaps
@@ -147,15 +147,6 @@ static relopt_bool boolRelOpts[] =
},
false
},
- {
- {
- "vacuum_truncate",
- "Enables vacuum to truncate empty pages at the end of this table",
- RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
- ShareUpdateExclusiveLock
- },
- true
- },
{
{
"deduplicate_items",
@@ -170,6 +161,21 @@ static relopt_bool boolRelOpts[] =
{{NULL}}
};
+static relopt_ternary ternaryRelOpts[] =
+{
+ {
+ {
+ "vacuum_truncate",
+ "Enables vacuum to truncate empty pages at the end of this table",
+ RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
+ ShareUpdateExclusiveLock
+ },
+ TERNARY_UNSET
+ },
+ /* list terminator */
+ {{NULL}}
+};
+
static relopt_int intRelOpts[] =
{
{
@@ -600,6 +606,13 @@ initialize_reloptions(void)
boolRelOpts[i].gen.lockmode));
j++;
}
+ for (i = 0; ternaryRelOpts[i].gen.name; i++)
+ {
+ Assert(DoLockModesConflict(ternaryRelOpts[i].gen.lockmode,
+ ternaryRelOpts[i].gen.lockmode));
+ j++;
+ }
+
for (i = 0; intRelOpts[i].gen.name; i++)
{
Assert(DoLockModesConflict(intRelOpts[i].gen.lockmode,
@@ -640,6 +653,14 @@ initialize_reloptions(void)
j++;
}
+ for (i = 0; ternaryRelOpts[i].gen.name; i++)
+ {
+ relOpts[j] = &ternaryRelOpts[i].gen;
+ relOpts[j]->type = RELOPT_TYPE_TERNARY;
+ relOpts[j]->namelen = strlen(relOpts[j]->name);
+ j++;
+ }
+
for (i = 0; intRelOpts[i].gen.name; i++)
{
relOpts[j] = &intRelOpts[i].gen;
@@ -800,6 +821,9 @@ allocate_reloption(bits32 kinds, int type, const char *name, const char *desc,
case RELOPT_TYPE_BOOL:
size = sizeof(relopt_bool);
break;
+ case RELOPT_TYPE_TERNARY:
+ size = sizeof(relopt_ternary);
+ break;
case RELOPT_TYPE_INT:
size = sizeof(relopt_int);
break;
@@ -883,6 +907,54 @@ add_local_bool_reloption(local_relopts *relopts, const char *name,
add_local_reloption(relopts, (relopt_gen *) newoption, offset);
}
+/*
+ * init_ternary_reloption
+ * Allocate and initialize a new ternary reloption
+ */
+static relopt_ternary *
+init_ternary_reloption(bits32 kinds, const char *name, const char *desc,
+ ternary default_val, LOCKMODE lockmode)
+{
+ relopt_ternary *newoption;
+
+ newoption = (relopt_ternary *) allocate_reloption(kinds,
+ RELOPT_TYPE_TERNARY, name, desc, lockmode);
+ newoption->default_val = default_val;
+
+ return newoption;
+}
+
+/*
+ * add_ternary_reloption
+ * Add a new ternary reloption
+ */
+void
+add_ternary_reloption(bits32 kinds, const char *name, const char *desc,
+ ternary default_val, LOCKMODE lockmode)
+{
+ relopt_ternary *newoption = init_ternary_reloption(kinds, name, desc,
+ default_val, lockmode);
+
+ add_reloption((relopt_gen *) newoption);
+}
+
+/*
+ * add_local_ternary_reloption
+ * Add a new ternary local reloption
+ *
+ * 'offset' is offset of ternary-typed field.
+ */
+void
+add_local_ternary_reloption(local_relopts *relopts, const char *name,
+ const char *desc, ternary default_val,
+ int offset)
+{
+ relopt_ternary *newoption = init_ternary_reloption(RELOPT_KIND_LOCAL,
+ name, desc,
+ default_val, 0);
+
+ add_local_reloption(relopts, (relopt_gen *) newoption, offset);
+}
/*
* init_real_reloption
@@ -1617,6 +1689,18 @@ parse_one_reloption(relopt_value *option, char *text_str, int text_len,
option->gen->name, value)));
}
break;
+ case RELOPT_TYPE_TERNARY:
+ {
+ bool b;
+ parsed = parse_bool(value, &b);
+ option->values.ternary_val = b ? TERNARY_TRUE : TERNARY_FALSE;
+ if (validate && !parsed)
+ ereport(ERROR,
+ (errcode(ERRCODE_INVALID_PARAMETER_VALUE),
+ errmsg("invalid value for ternary option \"%s\": %s",
+ option->gen->name, value)));
+ }
+ break;
case RELOPT_TYPE_INT:
{
relopt_int *optint = (relopt_int *) option->gen;
@@ -1780,17 +1864,6 @@ fillRelOptions(void *rdopts, Size basesize,
char *itempos = ((char *) rdopts) + elems[j].offset;
char *string_val;
- /*
- * If isset_offset is provided, store whether the reloption is
- * set there.
- */
- if (elems[j].isset_offset > 0)
- {
- char *setpos = ((char *) rdopts) + elems[j].isset_offset;
-
- *(bool *) setpos = options[i].isset;
- }
-
switch (options[i].gen->type)
{
case RELOPT_TYPE_BOOL:
@@ -1798,6 +1871,11 @@ fillRelOptions(void *rdopts, Size basesize,
options[i].values.bool_val :
((relopt_bool *) options[i].gen)->default_val;
break;
+ case RELOPT_TYPE_TERNARY:
+ *(ternary *) itempos = options[i].isset ?
+ options[i].values.ternary_val :
+ ((relopt_ternary *) options[i].gen)->default_val;
+ break;
case RELOPT_TYPE_INT:
*(int *) itempos = options[i].isset ?
options[i].values.int_val :
@@ -1912,8 +1990,8 @@ default_reloptions(Datum reloptions, bool validate, relopt_kind kind)
offsetof(StdRdOptions, parallel_workers)},
{"vacuum_index_cleanup", RELOPT_TYPE_ENUM,
offsetof(StdRdOptions, vacuum_index_cleanup)},
- {"vacuum_truncate", RELOPT_TYPE_BOOL,
- offsetof(StdRdOptions, vacuum_truncate), offsetof(StdRdOptions, vacuum_truncate_set)},
+ {"vacuum_truncate", RELOPT_TYPE_TERNARY,
+ offsetof(StdRdOptions, vacuum_truncate)},
{"vacuum_max_eager_freeze_failure_rate", RELOPT_TYPE_REAL,
offsetof(StdRdOptions, vacuum_max_eager_freeze_failure_rate)}
};
@@ -1993,7 +2071,6 @@ build_local_reloptions(local_relopts *relopts, Datum options, bool validate)
elems[i].optname = opt->option->name;
elems[i].opttype = opt->option->type;
elems[i].offset = opt->offset;
- elems[i].isset_offset = 0; /* not supported for local relopts yet */
i++;
}
diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c
index 733ef40ae7..977babff54 100644
--- a/src/backend/commands/vacuum.c
+++ b/src/backend/commands/vacuum.c
@@ -2221,9 +2221,9 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams params,
{
StdRdOptions *opts = (StdRdOptions *) rel->rd_options;
- if (opts && opts->vacuum_truncate_set)
+ if (opts && opts->vacuum_truncate != TERNARY_UNSET)
{
- if (opts->vacuum_truncate)
+ if (opts->vacuum_truncate == TERNARY_TRUE)
params.truncate = VACOPTVALUE_ENABLED;
else
params.truncate = VACOPTVALUE_DISABLED;
diff --git a/src/include/access/reloptions.h b/src/include/access/reloptions.h
index a604a4702c..a436697658 100644
--- a/src/include/access/reloptions.h
+++ b/src/include/access/reloptions.h
@@ -29,6 +29,7 @@
typedef enum relopt_type
{
RELOPT_TYPE_BOOL,
+ RELOPT_TYPE_TERNARY, /* on, off, unset */
RELOPT_TYPE_INT,
RELOPT_TYPE_REAL,
RELOPT_TYPE_ENUM,
@@ -80,6 +81,7 @@ typedef struct relopt_value
union
{
bool bool_val;
+ ternary ternary_val;
int int_val;
double real_val;
int enum_val;
@@ -94,6 +96,12 @@ typedef struct relopt_bool
bool default_val;
} relopt_bool;
+typedef struct relopt_rernary
+{
+ relopt_gen gen;
+ int default_val;
+} relopt_ternary;
+
typedef struct relopt_int
{
relopt_gen gen;
@@ -152,19 +160,6 @@ typedef struct
const char *optname; /* option's name */
relopt_type opttype; /* option's datatype */
int offset; /* offset of field in result struct */
-
- /*
- * isset_offset is an optional offset of a field in the result struct that
- * stores whether the option is explicitly set for the relation or if it
- * just picked up the default value. In most cases, this can be
- * accomplished by giving the reloption a special out-of-range default
- * value (e.g., some integer reloptions use -2), but this isn't always
- * possible. For example, a Boolean reloption cannot be given an
- * out-of-range default, so we need another way to discover the source of
- * its value. This offset is only used if given a value greater than
- * zero.
- */
- int isset_offset;
} relopt_parse_elt;
/* Local reloption definition */
@@ -195,6 +190,8 @@ typedef struct local_relopts
extern relopt_kind add_reloption_kind(void);
extern void add_bool_reloption(bits32 kinds, const char *name, const char *desc,
bool default_val, LOCKMODE lockmode);
+extern void add_ternary_reloption(bits32 kinds, const char *name,
+ const char *desc, int default_val, LOCKMODE lockmode);
extern void add_int_reloption(bits32 kinds, const char *name, const char *desc,
int default_val, int min_val, int max_val,
LOCKMODE lockmode);
@@ -214,6 +211,9 @@ extern void register_reloptions_validator(local_relopts *relopts,
extern void add_local_bool_reloption(local_relopts *relopts, const char *name,
const char *desc, bool default_val,
int offset);
+extern void add_local_ternary_reloption(local_relopts *relopts,
+ const char *name, const char *desc,
+ ternary default_val, int offset);
extern void add_local_int_reloption(local_relopts *relopts, const char *name,
const char *desc, int default_val,
int min_val, int max_val, int offset);
diff --git a/src/include/c.h b/src/include/c.h
index 39022f8a9d..9c59ccccf8 100644
--- a/src/include/c.h
+++ b/src/include/c.h
@@ -488,6 +488,22 @@ typedef void (*pg_funcptr_t) (void);
#include <stdbool.h>
+/*
+ * ternary
+ * Boolean value with an extrea "unset" option
+ *
+ * Ternary data type is used in relation options that can be "true", "false" or
+ * "unset". Since relation options are used deep inside the PostgreSQL code,
+ * this type is declared globally.
+*/
+
+typedef enum ternary
+{
+ TERNARY_FALSE = 0,
+ TERNARY_TRUE = 1,
+ TERNARY_UNSET = -1
+} ternary;
+
/* ----------------------------------------------------------------
* Section 3: standard system types
diff --git a/src/include/utils/rel.h b/src/include/utils/rel.h
index b552359915..7346d618f9 100644
--- a/src/include/utils/rel.h
+++ b/src/include/utils/rel.h
@@ -346,8 +346,7 @@ typedef struct StdRdOptions
bool user_catalog_table; /* use as an additional catalog relation */
int parallel_workers; /* max number of parallel workers */
StdRdOptIndexCleanup vacuum_index_cleanup; /* controls index vacuuming */
- bool vacuum_truncate; /* enables vacuum to truncate a relation */
- bool vacuum_truncate_set; /* whether vacuum_truncate is set */
+ ternary vacuum_truncate; /* enables vacuum to truncate a relation */
/*
* Fraction of pages in a relation that vacuum can eagerly scan and fail
--
2.39.2
--nextPart4155600.3ZeAukHxDK
Content-Disposition: attachment;
filename="v2a-0003-Add-alias-to-be-used-as-unset-state.patch"
Content-Transfer-Encoding: 7Bit
Content-Type: text/x-patch; charset="unicode-2-0-utf-8";
name="v2a-0003-Add-alias-to-be-used-as-unset-state.patch"
^ permalink raw reply [nested|flat] 868+ messages in thread
* [PATCH v2a 2/4] Introduce ternary reloptions
@ 2025-09-04 14:41 Nikolay Shaplov <[email protected]>
0 siblings, 0 replies; 868+ messages in thread
From: Nikolay Shaplov @ 2025-09-04 14:41 UTC (permalink / raw)
Introduce ternary reloption as a replacement for current `vacuum_truncate`
implementation. Remove `vacuum_truncate_set` additional flag and using
`TERNARY_UNSET` value instead.
---
src/backend/access/common/reloptions.c | 129 ++++++++++++++++++++-----
src/backend/commands/vacuum.c | 4 +-
src/include/access/reloptions.h | 26 ++---
src/include/c.h | 16 +++
src/include/utils/rel.h | 3 +-
5 files changed, 135 insertions(+), 43 deletions(-)
diff --git a/src/backend/access/common/reloptions.c b/src/backend/access/common/reloptions.c
index 0af3fea68f..f543f11001 100644
--- a/src/backend/access/common/reloptions.c
+++ b/src/backend/access/common/reloptions.c
@@ -40,9 +40,9 @@
*
* To add an option:
*
- * (i) decide on a type (bool, integer, real, enum, string), name, default
- * value, upper and lower bounds (if applicable); for strings, consider a
- * validation routine.
+ * (i) decide on a type (bool, ternary, integer, real, enum, string), name,
+ * default value, upper and lower bounds (if applicable); for strings,
+ * consider a validation routine.
* (ii) add a record below (or use add_<type>_reloption).
* (iii) add it to the appropriate options struct (perhaps StdRdOptions)
* (iv) add it to the appropriate handling routine (perhaps
@@ -147,15 +147,6 @@ static relopt_bool boolRelOpts[] =
},
false
},
- {
- {
- "vacuum_truncate",
- "Enables vacuum to truncate empty pages at the end of this table",
- RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
- ShareUpdateExclusiveLock
- },
- true
- },
{
{
"deduplicate_items",
@@ -170,6 +161,21 @@ static relopt_bool boolRelOpts[] =
{{NULL}}
};
+static relopt_ternary ternaryRelOpts[] =
+{
+ {
+ {
+ "vacuum_truncate",
+ "Enables vacuum to truncate empty pages at the end of this table",
+ RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
+ ShareUpdateExclusiveLock
+ },
+ TERNARY_UNSET
+ },
+ /* list terminator */
+ {{NULL}}
+};
+
static relopt_int intRelOpts[] =
{
{
@@ -600,6 +606,13 @@ initialize_reloptions(void)
boolRelOpts[i].gen.lockmode));
j++;
}
+ for (i = 0; ternaryRelOpts[i].gen.name; i++)
+ {
+ Assert(DoLockModesConflict(ternaryRelOpts[i].gen.lockmode,
+ ternaryRelOpts[i].gen.lockmode));
+ j++;
+ }
+
for (i = 0; intRelOpts[i].gen.name; i++)
{
Assert(DoLockModesConflict(intRelOpts[i].gen.lockmode,
@@ -640,6 +653,14 @@ initialize_reloptions(void)
j++;
}
+ for (i = 0; ternaryRelOpts[i].gen.name; i++)
+ {
+ relOpts[j] = &ternaryRelOpts[i].gen;
+ relOpts[j]->type = RELOPT_TYPE_TERNARY;
+ relOpts[j]->namelen = strlen(relOpts[j]->name);
+ j++;
+ }
+
for (i = 0; intRelOpts[i].gen.name; i++)
{
relOpts[j] = &intRelOpts[i].gen;
@@ -800,6 +821,9 @@ allocate_reloption(bits32 kinds, int type, const char *name, const char *desc,
case RELOPT_TYPE_BOOL:
size = sizeof(relopt_bool);
break;
+ case RELOPT_TYPE_TERNARY:
+ size = sizeof(relopt_ternary);
+ break;
case RELOPT_TYPE_INT:
size = sizeof(relopt_int);
break;
@@ -883,6 +907,54 @@ add_local_bool_reloption(local_relopts *relopts, const char *name,
add_local_reloption(relopts, (relopt_gen *) newoption, offset);
}
+/*
+ * init_ternary_reloption
+ * Allocate and initialize a new ternary reloption
+ */
+static relopt_ternary *
+init_ternary_reloption(bits32 kinds, const char *name, const char *desc,
+ ternary default_val, LOCKMODE lockmode)
+{
+ relopt_ternary *newoption;
+
+ newoption = (relopt_ternary *) allocate_reloption(kinds,
+ RELOPT_TYPE_TERNARY, name, desc, lockmode);
+ newoption->default_val = default_val;
+
+ return newoption;
+}
+
+/*
+ * add_ternary_reloption
+ * Add a new ternary reloption
+ */
+void
+add_ternary_reloption(bits32 kinds, const char *name, const char *desc,
+ ternary default_val, LOCKMODE lockmode)
+{
+ relopt_ternary *newoption = init_ternary_reloption(kinds, name, desc,
+ default_val, lockmode);
+
+ add_reloption((relopt_gen *) newoption);
+}
+
+/*
+ * add_local_ternary_reloption
+ * Add a new ternary local reloption
+ *
+ * 'offset' is offset of ternary-typed field.
+ */
+void
+add_local_ternary_reloption(local_relopts *relopts, const char *name,
+ const char *desc, ternary default_val,
+ int offset)
+{
+ relopt_ternary *newoption = init_ternary_reloption(RELOPT_KIND_LOCAL,
+ name, desc,
+ default_val, 0);
+
+ add_local_reloption(relopts, (relopt_gen *) newoption, offset);
+}
/*
* init_real_reloption
@@ -1617,6 +1689,18 @@ parse_one_reloption(relopt_value *option, char *text_str, int text_len,
option->gen->name, value)));
}
break;
+ case RELOPT_TYPE_TERNARY:
+ {
+ bool b;
+ parsed = parse_bool(value, &b);
+ option->values.ternary_val = b ? TERNARY_TRUE : TERNARY_FALSE;
+ if (validate && !parsed)
+ ereport(ERROR,
+ (errcode(ERRCODE_INVALID_PARAMETER_VALUE),
+ errmsg("invalid value for ternary option \"%s\": %s",
+ option->gen->name, value)));
+ }
+ break;
case RELOPT_TYPE_INT:
{
relopt_int *optint = (relopt_int *) option->gen;
@@ -1780,17 +1864,6 @@ fillRelOptions(void *rdopts, Size basesize,
char *itempos = ((char *) rdopts) + elems[j].offset;
char *string_val;
- /*
- * If isset_offset is provided, store whether the reloption is
- * set there.
- */
- if (elems[j].isset_offset > 0)
- {
- char *setpos = ((char *) rdopts) + elems[j].isset_offset;
-
- *(bool *) setpos = options[i].isset;
- }
-
switch (options[i].gen->type)
{
case RELOPT_TYPE_BOOL:
@@ -1798,6 +1871,11 @@ fillRelOptions(void *rdopts, Size basesize,
options[i].values.bool_val :
((relopt_bool *) options[i].gen)->default_val;
break;
+ case RELOPT_TYPE_TERNARY:
+ *(ternary *) itempos = options[i].isset ?
+ options[i].values.ternary_val :
+ ((relopt_ternary *) options[i].gen)->default_val;
+ break;
case RELOPT_TYPE_INT:
*(int *) itempos = options[i].isset ?
options[i].values.int_val :
@@ -1912,8 +1990,8 @@ default_reloptions(Datum reloptions, bool validate, relopt_kind kind)
offsetof(StdRdOptions, parallel_workers)},
{"vacuum_index_cleanup", RELOPT_TYPE_ENUM,
offsetof(StdRdOptions, vacuum_index_cleanup)},
- {"vacuum_truncate", RELOPT_TYPE_BOOL,
- offsetof(StdRdOptions, vacuum_truncate), offsetof(StdRdOptions, vacuum_truncate_set)},
+ {"vacuum_truncate", RELOPT_TYPE_TERNARY,
+ offsetof(StdRdOptions, vacuum_truncate)},
{"vacuum_max_eager_freeze_failure_rate", RELOPT_TYPE_REAL,
offsetof(StdRdOptions, vacuum_max_eager_freeze_failure_rate)}
};
@@ -1993,7 +2071,6 @@ build_local_reloptions(local_relopts *relopts, Datum options, bool validate)
elems[i].optname = opt->option->name;
elems[i].opttype = opt->option->type;
elems[i].offset = opt->offset;
- elems[i].isset_offset = 0; /* not supported for local relopts yet */
i++;
}
diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c
index 733ef40ae7..977babff54 100644
--- a/src/backend/commands/vacuum.c
+++ b/src/backend/commands/vacuum.c
@@ -2221,9 +2221,9 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams params,
{
StdRdOptions *opts = (StdRdOptions *) rel->rd_options;
- if (opts && opts->vacuum_truncate_set)
+ if (opts && opts->vacuum_truncate != TERNARY_UNSET)
{
- if (opts->vacuum_truncate)
+ if (opts->vacuum_truncate == TERNARY_TRUE)
params.truncate = VACOPTVALUE_ENABLED;
else
params.truncate = VACOPTVALUE_DISABLED;
diff --git a/src/include/access/reloptions.h b/src/include/access/reloptions.h
index a604a4702c..a436697658 100644
--- a/src/include/access/reloptions.h
+++ b/src/include/access/reloptions.h
@@ -29,6 +29,7 @@
typedef enum relopt_type
{
RELOPT_TYPE_BOOL,
+ RELOPT_TYPE_TERNARY, /* on, off, unset */
RELOPT_TYPE_INT,
RELOPT_TYPE_REAL,
RELOPT_TYPE_ENUM,
@@ -80,6 +81,7 @@ typedef struct relopt_value
union
{
bool bool_val;
+ ternary ternary_val;
int int_val;
double real_val;
int enum_val;
@@ -94,6 +96,12 @@ typedef struct relopt_bool
bool default_val;
} relopt_bool;
+typedef struct relopt_rernary
+{
+ relopt_gen gen;
+ int default_val;
+} relopt_ternary;
+
typedef struct relopt_int
{
relopt_gen gen;
@@ -152,19 +160,6 @@ typedef struct
const char *optname; /* option's name */
relopt_type opttype; /* option's datatype */
int offset; /* offset of field in result struct */
-
- /*
- * isset_offset is an optional offset of a field in the result struct that
- * stores whether the option is explicitly set for the relation or if it
- * just picked up the default value. In most cases, this can be
- * accomplished by giving the reloption a special out-of-range default
- * value (e.g., some integer reloptions use -2), but this isn't always
- * possible. For example, a Boolean reloption cannot be given an
- * out-of-range default, so we need another way to discover the source of
- * its value. This offset is only used if given a value greater than
- * zero.
- */
- int isset_offset;
} relopt_parse_elt;
/* Local reloption definition */
@@ -195,6 +190,8 @@ typedef struct local_relopts
extern relopt_kind add_reloption_kind(void);
extern void add_bool_reloption(bits32 kinds, const char *name, const char *desc,
bool default_val, LOCKMODE lockmode);
+extern void add_ternary_reloption(bits32 kinds, const char *name,
+ const char *desc, int default_val, LOCKMODE lockmode);
extern void add_int_reloption(bits32 kinds, const char *name, const char *desc,
int default_val, int min_val, int max_val,
LOCKMODE lockmode);
@@ -214,6 +211,9 @@ extern void register_reloptions_validator(local_relopts *relopts,
extern void add_local_bool_reloption(local_relopts *relopts, const char *name,
const char *desc, bool default_val,
int offset);
+extern void add_local_ternary_reloption(local_relopts *relopts,
+ const char *name, const char *desc,
+ ternary default_val, int offset);
extern void add_local_int_reloption(local_relopts *relopts, const char *name,
const char *desc, int default_val,
int min_val, int max_val, int offset);
diff --git a/src/include/c.h b/src/include/c.h
index 39022f8a9d..9c59ccccf8 100644
--- a/src/include/c.h
+++ b/src/include/c.h
@@ -488,6 +488,22 @@ typedef void (*pg_funcptr_t) (void);
#include <stdbool.h>
+/*
+ * ternary
+ * Boolean value with an extrea "unset" option
+ *
+ * Ternary data type is used in relation options that can be "true", "false" or
+ * "unset". Since relation options are used deep inside the PostgreSQL code,
+ * this type is declared globally.
+*/
+
+typedef enum ternary
+{
+ TERNARY_FALSE = 0,
+ TERNARY_TRUE = 1,
+ TERNARY_UNSET = -1
+} ternary;
+
/* ----------------------------------------------------------------
* Section 3: standard system types
diff --git a/src/include/utils/rel.h b/src/include/utils/rel.h
index b552359915..7346d618f9 100644
--- a/src/include/utils/rel.h
+++ b/src/include/utils/rel.h
@@ -346,8 +346,7 @@ typedef struct StdRdOptions
bool user_catalog_table; /* use as an additional catalog relation */
int parallel_workers; /* max number of parallel workers */
StdRdOptIndexCleanup vacuum_index_cleanup; /* controls index vacuuming */
- bool vacuum_truncate; /* enables vacuum to truncate a relation */
- bool vacuum_truncate_set; /* whether vacuum_truncate is set */
+ ternary vacuum_truncate; /* enables vacuum to truncate a relation */
/*
* Fraction of pages in a relation that vacuum can eagerly scan and fail
--
2.39.2
--nextPart4155600.3ZeAukHxDK
Content-Disposition: attachment;
filename="v2a-0003-Add-alias-to-be-used-as-unset-state.patch"
Content-Transfer-Encoding: 7Bit
Content-Type: text/x-patch; charset="unicode-2-0-utf-8";
name="v2a-0003-Add-alias-to-be-used-as-unset-state.patch"
^ permalink raw reply [nested|flat] 868+ messages in thread
* [PATCH v2a 2/4] Introduce ternary reloptions
@ 2025-09-04 14:41 Nikolay Shaplov <[email protected]>
0 siblings, 0 replies; 868+ messages in thread
From: Nikolay Shaplov @ 2025-09-04 14:41 UTC (permalink / raw)
Introduce ternary reloption as a replacement for current `vacuum_truncate`
implementation. Remove `vacuum_truncate_set` additional flag and using
`TERNARY_UNSET` value instead.
---
src/backend/access/common/reloptions.c | 129 ++++++++++++++++++++-----
src/backend/commands/vacuum.c | 4 +-
src/include/access/reloptions.h | 26 ++---
src/include/c.h | 16 +++
src/include/utils/rel.h | 3 +-
5 files changed, 135 insertions(+), 43 deletions(-)
diff --git a/src/backend/access/common/reloptions.c b/src/backend/access/common/reloptions.c
index 0af3fea68f..f543f11001 100644
--- a/src/backend/access/common/reloptions.c
+++ b/src/backend/access/common/reloptions.c
@@ -40,9 +40,9 @@
*
* To add an option:
*
- * (i) decide on a type (bool, integer, real, enum, string), name, default
- * value, upper and lower bounds (if applicable); for strings, consider a
- * validation routine.
+ * (i) decide on a type (bool, ternary, integer, real, enum, string), name,
+ * default value, upper and lower bounds (if applicable); for strings,
+ * consider a validation routine.
* (ii) add a record below (or use add_<type>_reloption).
* (iii) add it to the appropriate options struct (perhaps StdRdOptions)
* (iv) add it to the appropriate handling routine (perhaps
@@ -147,15 +147,6 @@ static relopt_bool boolRelOpts[] =
},
false
},
- {
- {
- "vacuum_truncate",
- "Enables vacuum to truncate empty pages at the end of this table",
- RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
- ShareUpdateExclusiveLock
- },
- true
- },
{
{
"deduplicate_items",
@@ -170,6 +161,21 @@ static relopt_bool boolRelOpts[] =
{{NULL}}
};
+static relopt_ternary ternaryRelOpts[] =
+{
+ {
+ {
+ "vacuum_truncate",
+ "Enables vacuum to truncate empty pages at the end of this table",
+ RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
+ ShareUpdateExclusiveLock
+ },
+ TERNARY_UNSET
+ },
+ /* list terminator */
+ {{NULL}}
+};
+
static relopt_int intRelOpts[] =
{
{
@@ -600,6 +606,13 @@ initialize_reloptions(void)
boolRelOpts[i].gen.lockmode));
j++;
}
+ for (i = 0; ternaryRelOpts[i].gen.name; i++)
+ {
+ Assert(DoLockModesConflict(ternaryRelOpts[i].gen.lockmode,
+ ternaryRelOpts[i].gen.lockmode));
+ j++;
+ }
+
for (i = 0; intRelOpts[i].gen.name; i++)
{
Assert(DoLockModesConflict(intRelOpts[i].gen.lockmode,
@@ -640,6 +653,14 @@ initialize_reloptions(void)
j++;
}
+ for (i = 0; ternaryRelOpts[i].gen.name; i++)
+ {
+ relOpts[j] = &ternaryRelOpts[i].gen;
+ relOpts[j]->type = RELOPT_TYPE_TERNARY;
+ relOpts[j]->namelen = strlen(relOpts[j]->name);
+ j++;
+ }
+
for (i = 0; intRelOpts[i].gen.name; i++)
{
relOpts[j] = &intRelOpts[i].gen;
@@ -800,6 +821,9 @@ allocate_reloption(bits32 kinds, int type, const char *name, const char *desc,
case RELOPT_TYPE_BOOL:
size = sizeof(relopt_bool);
break;
+ case RELOPT_TYPE_TERNARY:
+ size = sizeof(relopt_ternary);
+ break;
case RELOPT_TYPE_INT:
size = sizeof(relopt_int);
break;
@@ -883,6 +907,54 @@ add_local_bool_reloption(local_relopts *relopts, const char *name,
add_local_reloption(relopts, (relopt_gen *) newoption, offset);
}
+/*
+ * init_ternary_reloption
+ * Allocate and initialize a new ternary reloption
+ */
+static relopt_ternary *
+init_ternary_reloption(bits32 kinds, const char *name, const char *desc,
+ ternary default_val, LOCKMODE lockmode)
+{
+ relopt_ternary *newoption;
+
+ newoption = (relopt_ternary *) allocate_reloption(kinds,
+ RELOPT_TYPE_TERNARY, name, desc, lockmode);
+ newoption->default_val = default_val;
+
+ return newoption;
+}
+
+/*
+ * add_ternary_reloption
+ * Add a new ternary reloption
+ */
+void
+add_ternary_reloption(bits32 kinds, const char *name, const char *desc,
+ ternary default_val, LOCKMODE lockmode)
+{
+ relopt_ternary *newoption = init_ternary_reloption(kinds, name, desc,
+ default_val, lockmode);
+
+ add_reloption((relopt_gen *) newoption);
+}
+
+/*
+ * add_local_ternary_reloption
+ * Add a new ternary local reloption
+ *
+ * 'offset' is offset of ternary-typed field.
+ */
+void
+add_local_ternary_reloption(local_relopts *relopts, const char *name,
+ const char *desc, ternary default_val,
+ int offset)
+{
+ relopt_ternary *newoption = init_ternary_reloption(RELOPT_KIND_LOCAL,
+ name, desc,
+ default_val, 0);
+
+ add_local_reloption(relopts, (relopt_gen *) newoption, offset);
+}
/*
* init_real_reloption
@@ -1617,6 +1689,18 @@ parse_one_reloption(relopt_value *option, char *text_str, int text_len,
option->gen->name, value)));
}
break;
+ case RELOPT_TYPE_TERNARY:
+ {
+ bool b;
+ parsed = parse_bool(value, &b);
+ option->values.ternary_val = b ? TERNARY_TRUE : TERNARY_FALSE;
+ if (validate && !parsed)
+ ereport(ERROR,
+ (errcode(ERRCODE_INVALID_PARAMETER_VALUE),
+ errmsg("invalid value for ternary option \"%s\": %s",
+ option->gen->name, value)));
+ }
+ break;
case RELOPT_TYPE_INT:
{
relopt_int *optint = (relopt_int *) option->gen;
@@ -1780,17 +1864,6 @@ fillRelOptions(void *rdopts, Size basesize,
char *itempos = ((char *) rdopts) + elems[j].offset;
char *string_val;
- /*
- * If isset_offset is provided, store whether the reloption is
- * set there.
- */
- if (elems[j].isset_offset > 0)
- {
- char *setpos = ((char *) rdopts) + elems[j].isset_offset;
-
- *(bool *) setpos = options[i].isset;
- }
-
switch (options[i].gen->type)
{
case RELOPT_TYPE_BOOL:
@@ -1798,6 +1871,11 @@ fillRelOptions(void *rdopts, Size basesize,
options[i].values.bool_val :
((relopt_bool *) options[i].gen)->default_val;
break;
+ case RELOPT_TYPE_TERNARY:
+ *(ternary *) itempos = options[i].isset ?
+ options[i].values.ternary_val :
+ ((relopt_ternary *) options[i].gen)->default_val;
+ break;
case RELOPT_TYPE_INT:
*(int *) itempos = options[i].isset ?
options[i].values.int_val :
@@ -1912,8 +1990,8 @@ default_reloptions(Datum reloptions, bool validate, relopt_kind kind)
offsetof(StdRdOptions, parallel_workers)},
{"vacuum_index_cleanup", RELOPT_TYPE_ENUM,
offsetof(StdRdOptions, vacuum_index_cleanup)},
- {"vacuum_truncate", RELOPT_TYPE_BOOL,
- offsetof(StdRdOptions, vacuum_truncate), offsetof(StdRdOptions, vacuum_truncate_set)},
+ {"vacuum_truncate", RELOPT_TYPE_TERNARY,
+ offsetof(StdRdOptions, vacuum_truncate)},
{"vacuum_max_eager_freeze_failure_rate", RELOPT_TYPE_REAL,
offsetof(StdRdOptions, vacuum_max_eager_freeze_failure_rate)}
};
@@ -1993,7 +2071,6 @@ build_local_reloptions(local_relopts *relopts, Datum options, bool validate)
elems[i].optname = opt->option->name;
elems[i].opttype = opt->option->type;
elems[i].offset = opt->offset;
- elems[i].isset_offset = 0; /* not supported for local relopts yet */
i++;
}
diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c
index 733ef40ae7..977babff54 100644
--- a/src/backend/commands/vacuum.c
+++ b/src/backend/commands/vacuum.c
@@ -2221,9 +2221,9 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams params,
{
StdRdOptions *opts = (StdRdOptions *) rel->rd_options;
- if (opts && opts->vacuum_truncate_set)
+ if (opts && opts->vacuum_truncate != TERNARY_UNSET)
{
- if (opts->vacuum_truncate)
+ if (opts->vacuum_truncate == TERNARY_TRUE)
params.truncate = VACOPTVALUE_ENABLED;
else
params.truncate = VACOPTVALUE_DISABLED;
diff --git a/src/include/access/reloptions.h b/src/include/access/reloptions.h
index a604a4702c..a436697658 100644
--- a/src/include/access/reloptions.h
+++ b/src/include/access/reloptions.h
@@ -29,6 +29,7 @@
typedef enum relopt_type
{
RELOPT_TYPE_BOOL,
+ RELOPT_TYPE_TERNARY, /* on, off, unset */
RELOPT_TYPE_INT,
RELOPT_TYPE_REAL,
RELOPT_TYPE_ENUM,
@@ -80,6 +81,7 @@ typedef struct relopt_value
union
{
bool bool_val;
+ ternary ternary_val;
int int_val;
double real_val;
int enum_val;
@@ -94,6 +96,12 @@ typedef struct relopt_bool
bool default_val;
} relopt_bool;
+typedef struct relopt_rernary
+{
+ relopt_gen gen;
+ int default_val;
+} relopt_ternary;
+
typedef struct relopt_int
{
relopt_gen gen;
@@ -152,19 +160,6 @@ typedef struct
const char *optname; /* option's name */
relopt_type opttype; /* option's datatype */
int offset; /* offset of field in result struct */
-
- /*
- * isset_offset is an optional offset of a field in the result struct that
- * stores whether the option is explicitly set for the relation or if it
- * just picked up the default value. In most cases, this can be
- * accomplished by giving the reloption a special out-of-range default
- * value (e.g., some integer reloptions use -2), but this isn't always
- * possible. For example, a Boolean reloption cannot be given an
- * out-of-range default, so we need another way to discover the source of
- * its value. This offset is only used if given a value greater than
- * zero.
- */
- int isset_offset;
} relopt_parse_elt;
/* Local reloption definition */
@@ -195,6 +190,8 @@ typedef struct local_relopts
extern relopt_kind add_reloption_kind(void);
extern void add_bool_reloption(bits32 kinds, const char *name, const char *desc,
bool default_val, LOCKMODE lockmode);
+extern void add_ternary_reloption(bits32 kinds, const char *name,
+ const char *desc, int default_val, LOCKMODE lockmode);
extern void add_int_reloption(bits32 kinds, const char *name, const char *desc,
int default_val, int min_val, int max_val,
LOCKMODE lockmode);
@@ -214,6 +211,9 @@ extern void register_reloptions_validator(local_relopts *relopts,
extern void add_local_bool_reloption(local_relopts *relopts, const char *name,
const char *desc, bool default_val,
int offset);
+extern void add_local_ternary_reloption(local_relopts *relopts,
+ const char *name, const char *desc,
+ ternary default_val, int offset);
extern void add_local_int_reloption(local_relopts *relopts, const char *name,
const char *desc, int default_val,
int min_val, int max_val, int offset);
diff --git a/src/include/c.h b/src/include/c.h
index 39022f8a9d..9c59ccccf8 100644
--- a/src/include/c.h
+++ b/src/include/c.h
@@ -488,6 +488,22 @@ typedef void (*pg_funcptr_t) (void);
#include <stdbool.h>
+/*
+ * ternary
+ * Boolean value with an extrea "unset" option
+ *
+ * Ternary data type is used in relation options that can be "true", "false" or
+ * "unset". Since relation options are used deep inside the PostgreSQL code,
+ * this type is declared globally.
+*/
+
+typedef enum ternary
+{
+ TERNARY_FALSE = 0,
+ TERNARY_TRUE = 1,
+ TERNARY_UNSET = -1
+} ternary;
+
/* ----------------------------------------------------------------
* Section 3: standard system types
diff --git a/src/include/utils/rel.h b/src/include/utils/rel.h
index b552359915..7346d618f9 100644
--- a/src/include/utils/rel.h
+++ b/src/include/utils/rel.h
@@ -346,8 +346,7 @@ typedef struct StdRdOptions
bool user_catalog_table; /* use as an additional catalog relation */
int parallel_workers; /* max number of parallel workers */
StdRdOptIndexCleanup vacuum_index_cleanup; /* controls index vacuuming */
- bool vacuum_truncate; /* enables vacuum to truncate a relation */
- bool vacuum_truncate_set; /* whether vacuum_truncate is set */
+ ternary vacuum_truncate; /* enables vacuum to truncate a relation */
/*
* Fraction of pages in a relation that vacuum can eagerly scan and fail
--
2.39.2
--nextPart4155600.3ZeAukHxDK
Content-Disposition: attachment;
filename="v2a-0003-Add-alias-to-be-used-as-unset-state.patch"
Content-Transfer-Encoding: 7Bit
Content-Type: text/x-patch; charset="unicode-2-0-utf-8";
name="v2a-0003-Add-alias-to-be-used-as-unset-state.patch"
^ permalink raw reply [nested|flat] 868+ messages in thread
* [PATCH v2 2/4] Introduce ternary reloptions
@ 2025-09-04 14:41 Nikolay Shaplov <[email protected]>
0 siblings, 0 replies; 868+ messages in thread
From: Nikolay Shaplov @ 2025-09-04 14:41 UTC (permalink / raw)
Introduce ternary reloption as a replacement for current `vacuum_truncate`
implementation. Remove `vacuum_truncate_set` additional flag and using
`TERNARY_UNSET` value instead.
---
src/backend/access/common/reloptions.c | 129 ++++++++++++++++++++-----
src/backend/commands/vacuum.c | 4 +-
src/include/access/reloptions.h | 26 ++---
src/include/c.h | 16 +++
src/include/utils/rel.h | 3 +-
5 files changed, 135 insertions(+), 43 deletions(-)
diff --git a/src/backend/access/common/reloptions.c b/src/backend/access/common/reloptions.c
index 0af3fea68f..f543f11001 100644
--- a/src/backend/access/common/reloptions.c
+++ b/src/backend/access/common/reloptions.c
@@ -40,9 +40,9 @@
*
* To add an option:
*
- * (i) decide on a type (bool, integer, real, enum, string), name, default
- * value, upper and lower bounds (if applicable); for strings, consider a
- * validation routine.
+ * (i) decide on a type (bool, ternary, integer, real, enum, string), name,
+ * default value, upper and lower bounds (if applicable); for strings,
+ * consider a validation routine.
* (ii) add a record below (or use add_<type>_reloption).
* (iii) add it to the appropriate options struct (perhaps StdRdOptions)
* (iv) add it to the appropriate handling routine (perhaps
@@ -147,15 +147,6 @@ static relopt_bool boolRelOpts[] =
},
false
},
- {
- {
- "vacuum_truncate",
- "Enables vacuum to truncate empty pages at the end of this table",
- RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
- ShareUpdateExclusiveLock
- },
- true
- },
{
{
"deduplicate_items",
@@ -170,6 +161,21 @@ static relopt_bool boolRelOpts[] =
{{NULL}}
};
+static relopt_ternary ternaryRelOpts[] =
+{
+ {
+ {
+ "vacuum_truncate",
+ "Enables vacuum to truncate empty pages at the end of this table",
+ RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
+ ShareUpdateExclusiveLock
+ },
+ TERNARY_UNSET
+ },
+ /* list terminator */
+ {{NULL}}
+};
+
static relopt_int intRelOpts[] =
{
{
@@ -600,6 +606,13 @@ initialize_reloptions(void)
boolRelOpts[i].gen.lockmode));
j++;
}
+ for (i = 0; ternaryRelOpts[i].gen.name; i++)
+ {
+ Assert(DoLockModesConflict(ternaryRelOpts[i].gen.lockmode,
+ ternaryRelOpts[i].gen.lockmode));
+ j++;
+ }
+
for (i = 0; intRelOpts[i].gen.name; i++)
{
Assert(DoLockModesConflict(intRelOpts[i].gen.lockmode,
@@ -640,6 +653,14 @@ initialize_reloptions(void)
j++;
}
+ for (i = 0; ternaryRelOpts[i].gen.name; i++)
+ {
+ relOpts[j] = &ternaryRelOpts[i].gen;
+ relOpts[j]->type = RELOPT_TYPE_TERNARY;
+ relOpts[j]->namelen = strlen(relOpts[j]->name);
+ j++;
+ }
+
for (i = 0; intRelOpts[i].gen.name; i++)
{
relOpts[j] = &intRelOpts[i].gen;
@@ -800,6 +821,9 @@ allocate_reloption(bits32 kinds, int type, const char *name, const char *desc,
case RELOPT_TYPE_BOOL:
size = sizeof(relopt_bool);
break;
+ case RELOPT_TYPE_TERNARY:
+ size = sizeof(relopt_ternary);
+ break;
case RELOPT_TYPE_INT:
size = sizeof(relopt_int);
break;
@@ -883,6 +907,54 @@ add_local_bool_reloption(local_relopts *relopts, const char *name,
add_local_reloption(relopts, (relopt_gen *) newoption, offset);
}
+/*
+ * init_ternary_reloption
+ * Allocate and initialize a new ternary reloption
+ */
+static relopt_ternary *
+init_ternary_reloption(bits32 kinds, const char *name, const char *desc,
+ ternary default_val, LOCKMODE lockmode)
+{
+ relopt_ternary *newoption;
+
+ newoption = (relopt_ternary *) allocate_reloption(kinds,
+ RELOPT_TYPE_TERNARY, name, desc, lockmode);
+ newoption->default_val = default_val;
+
+ return newoption;
+}
+
+/*
+ * add_ternary_reloption
+ * Add a new ternary reloption
+ */
+void
+add_ternary_reloption(bits32 kinds, const char *name, const char *desc,
+ ternary default_val, LOCKMODE lockmode)
+{
+ relopt_ternary *newoption = init_ternary_reloption(kinds, name, desc,
+ default_val, lockmode);
+
+ add_reloption((relopt_gen *) newoption);
+}
+
+/*
+ * add_local_ternary_reloption
+ * Add a new ternary local reloption
+ *
+ * 'offset' is offset of ternary-typed field.
+ */
+void
+add_local_ternary_reloption(local_relopts *relopts, const char *name,
+ const char *desc, ternary default_val,
+ int offset)
+{
+ relopt_ternary *newoption = init_ternary_reloption(RELOPT_KIND_LOCAL,
+ name, desc,
+ default_val, 0);
+
+ add_local_reloption(relopts, (relopt_gen *) newoption, offset);
+}
/*
* init_real_reloption
@@ -1617,6 +1689,18 @@ parse_one_reloption(relopt_value *option, char *text_str, int text_len,
option->gen->name, value)));
}
break;
+ case RELOPT_TYPE_TERNARY:
+ {
+ bool b;
+ parsed = parse_bool(value, &b);
+ option->values.ternary_val = b ? TERNARY_TRUE : TERNARY_FALSE;
+ if (validate && !parsed)
+ ereport(ERROR,
+ (errcode(ERRCODE_INVALID_PARAMETER_VALUE),
+ errmsg("invalid value for ternary option \"%s\": %s",
+ option->gen->name, value)));
+ }
+ break;
case RELOPT_TYPE_INT:
{
relopt_int *optint = (relopt_int *) option->gen;
@@ -1780,17 +1864,6 @@ fillRelOptions(void *rdopts, Size basesize,
char *itempos = ((char *) rdopts) + elems[j].offset;
char *string_val;
- /*
- * If isset_offset is provided, store whether the reloption is
- * set there.
- */
- if (elems[j].isset_offset > 0)
- {
- char *setpos = ((char *) rdopts) + elems[j].isset_offset;
-
- *(bool *) setpos = options[i].isset;
- }
-
switch (options[i].gen->type)
{
case RELOPT_TYPE_BOOL:
@@ -1798,6 +1871,11 @@ fillRelOptions(void *rdopts, Size basesize,
options[i].values.bool_val :
((relopt_bool *) options[i].gen)->default_val;
break;
+ case RELOPT_TYPE_TERNARY:
+ *(ternary *) itempos = options[i].isset ?
+ options[i].values.ternary_val :
+ ((relopt_ternary *) options[i].gen)->default_val;
+ break;
case RELOPT_TYPE_INT:
*(int *) itempos = options[i].isset ?
options[i].values.int_val :
@@ -1912,8 +1990,8 @@ default_reloptions(Datum reloptions, bool validate, relopt_kind kind)
offsetof(StdRdOptions, parallel_workers)},
{"vacuum_index_cleanup", RELOPT_TYPE_ENUM,
offsetof(StdRdOptions, vacuum_index_cleanup)},
- {"vacuum_truncate", RELOPT_TYPE_BOOL,
- offsetof(StdRdOptions, vacuum_truncate), offsetof(StdRdOptions, vacuum_truncate_set)},
+ {"vacuum_truncate", RELOPT_TYPE_TERNARY,
+ offsetof(StdRdOptions, vacuum_truncate)},
{"vacuum_max_eager_freeze_failure_rate", RELOPT_TYPE_REAL,
offsetof(StdRdOptions, vacuum_max_eager_freeze_failure_rate)}
};
@@ -1993,7 +2071,6 @@ build_local_reloptions(local_relopts *relopts, Datum options, bool validate)
elems[i].optname = opt->option->name;
elems[i].opttype = opt->option->type;
elems[i].offset = opt->offset;
- elems[i].isset_offset = 0; /* not supported for local relopts yet */
i++;
}
diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c
index 733ef40ae7..977babff54 100644
--- a/src/backend/commands/vacuum.c
+++ b/src/backend/commands/vacuum.c
@@ -2221,9 +2221,9 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams params,
{
StdRdOptions *opts = (StdRdOptions *) rel->rd_options;
- if (opts && opts->vacuum_truncate_set)
+ if (opts && opts->vacuum_truncate != TERNARY_UNSET)
{
- if (opts->vacuum_truncate)
+ if (opts->vacuum_truncate == TERNARY_TRUE)
params.truncate = VACOPTVALUE_ENABLED;
else
params.truncate = VACOPTVALUE_DISABLED;
diff --git a/src/include/access/reloptions.h b/src/include/access/reloptions.h
index a604a4702c..a436697658 100644
--- a/src/include/access/reloptions.h
+++ b/src/include/access/reloptions.h
@@ -29,6 +29,7 @@
typedef enum relopt_type
{
RELOPT_TYPE_BOOL,
+ RELOPT_TYPE_TERNARY, /* on, off, unset */
RELOPT_TYPE_INT,
RELOPT_TYPE_REAL,
RELOPT_TYPE_ENUM,
@@ -80,6 +81,7 @@ typedef struct relopt_value
union
{
bool bool_val;
+ ternary ternary_val;
int int_val;
double real_val;
int enum_val;
@@ -94,6 +96,12 @@ typedef struct relopt_bool
bool default_val;
} relopt_bool;
+typedef struct relopt_rernary
+{
+ relopt_gen gen;
+ int default_val;
+} relopt_ternary;
+
typedef struct relopt_int
{
relopt_gen gen;
@@ -152,19 +160,6 @@ typedef struct
const char *optname; /* option's name */
relopt_type opttype; /* option's datatype */
int offset; /* offset of field in result struct */
-
- /*
- * isset_offset is an optional offset of a field in the result struct that
- * stores whether the option is explicitly set for the relation or if it
- * just picked up the default value. In most cases, this can be
- * accomplished by giving the reloption a special out-of-range default
- * value (e.g., some integer reloptions use -2), but this isn't always
- * possible. For example, a Boolean reloption cannot be given an
- * out-of-range default, so we need another way to discover the source of
- * its value. This offset is only used if given a value greater than
- * zero.
- */
- int isset_offset;
} relopt_parse_elt;
/* Local reloption definition */
@@ -195,6 +190,8 @@ typedef struct local_relopts
extern relopt_kind add_reloption_kind(void);
extern void add_bool_reloption(bits32 kinds, const char *name, const char *desc,
bool default_val, LOCKMODE lockmode);
+extern void add_ternary_reloption(bits32 kinds, const char *name,
+ const char *desc, int default_val, LOCKMODE lockmode);
extern void add_int_reloption(bits32 kinds, const char *name, const char *desc,
int default_val, int min_val, int max_val,
LOCKMODE lockmode);
@@ -214,6 +211,9 @@ extern void register_reloptions_validator(local_relopts *relopts,
extern void add_local_bool_reloption(local_relopts *relopts, const char *name,
const char *desc, bool default_val,
int offset);
+extern void add_local_ternary_reloption(local_relopts *relopts,
+ const char *name, const char *desc,
+ ternary default_val, int offset);
extern void add_local_int_reloption(local_relopts *relopts, const char *name,
const char *desc, int default_val,
int min_val, int max_val, int offset);
diff --git a/src/include/c.h b/src/include/c.h
index 39022f8a9d..9c59ccccf8 100644
--- a/src/include/c.h
+++ b/src/include/c.h
@@ -488,6 +488,22 @@ typedef void (*pg_funcptr_t) (void);
#include <stdbool.h>
+/*
+ * ternary
+ * Boolean value with an extrea "unset" option
+ *
+ * Ternary data type is used in relation options that can be "true", "false" or
+ * "unset". Since relation options are used deep inside the PostgreSQL code,
+ * this type is declared globally.
+*/
+
+typedef enum ternary
+{
+ TERNARY_FALSE = 0,
+ TERNARY_TRUE = 1,
+ TERNARY_UNSET = -1
+} ternary;
+
/* ----------------------------------------------------------------
* Section 3: standard system types
diff --git a/src/include/utils/rel.h b/src/include/utils/rel.h
index b552359915..7346d618f9 100644
--- a/src/include/utils/rel.h
+++ b/src/include/utils/rel.h
@@ -346,8 +346,7 @@ typedef struct StdRdOptions
bool user_catalog_table; /* use as an additional catalog relation */
int parallel_workers; /* max number of parallel workers */
StdRdOptIndexCleanup vacuum_index_cleanup; /* controls index vacuuming */
- bool vacuum_truncate; /* enables vacuum to truncate a relation */
- bool vacuum_truncate_set; /* whether vacuum_truncate is set */
+ ternary vacuum_truncate; /* enables vacuum to truncate a relation */
/*
* Fraction of pages in a relation that vacuum can eagerly scan and fail
--
2.39.2
--nextPart5129932.5fSG56mABF
Content-Disposition: attachment;
filename="v2-0003-Add-alias-to-be-used-as-unset-state.patch"
Content-Transfer-Encoding: 7Bit
Content-Type: text/x-patch; charset="unicode-2-0-utf-8";
name="v2-0003-Add-alias-to-be-used-as-unset-state.patch"
^ permalink raw reply [nested|flat] 868+ messages in thread
* [PATCH v2 2/4] Introduce ternary reloptions
@ 2025-09-04 14:41 Nikolay Shaplov <[email protected]>
0 siblings, 0 replies; 868+ messages in thread
From: Nikolay Shaplov @ 2025-09-04 14:41 UTC (permalink / raw)
Introduce ternary reloption as a replacement for current `vacuum_truncate`
implementation. Remove `vacuum_truncate_set` additional flag and using
`TERNARY_UNSET` value instead.
---
src/backend/access/common/reloptions.c | 129 ++++++++++++++++++++-----
src/backend/commands/vacuum.c | 4 +-
src/include/access/reloptions.h | 26 ++---
src/include/c.h | 16 +++
src/include/utils/rel.h | 3 +-
5 files changed, 135 insertions(+), 43 deletions(-)
diff --git a/src/backend/access/common/reloptions.c b/src/backend/access/common/reloptions.c
index 0af3fea68f..f543f11001 100644
--- a/src/backend/access/common/reloptions.c
+++ b/src/backend/access/common/reloptions.c
@@ -40,9 +40,9 @@
*
* To add an option:
*
- * (i) decide on a type (bool, integer, real, enum, string), name, default
- * value, upper and lower bounds (if applicable); for strings, consider a
- * validation routine.
+ * (i) decide on a type (bool, ternary, integer, real, enum, string), name,
+ * default value, upper and lower bounds (if applicable); for strings,
+ * consider a validation routine.
* (ii) add a record below (or use add_<type>_reloption).
* (iii) add it to the appropriate options struct (perhaps StdRdOptions)
* (iv) add it to the appropriate handling routine (perhaps
@@ -147,15 +147,6 @@ static relopt_bool boolRelOpts[] =
},
false
},
- {
- {
- "vacuum_truncate",
- "Enables vacuum to truncate empty pages at the end of this table",
- RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
- ShareUpdateExclusiveLock
- },
- true
- },
{
{
"deduplicate_items",
@@ -170,6 +161,21 @@ static relopt_bool boolRelOpts[] =
{{NULL}}
};
+static relopt_ternary ternaryRelOpts[] =
+{
+ {
+ {
+ "vacuum_truncate",
+ "Enables vacuum to truncate empty pages at the end of this table",
+ RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
+ ShareUpdateExclusiveLock
+ },
+ TERNARY_UNSET
+ },
+ /* list terminator */
+ {{NULL}}
+};
+
static relopt_int intRelOpts[] =
{
{
@@ -600,6 +606,13 @@ initialize_reloptions(void)
boolRelOpts[i].gen.lockmode));
j++;
}
+ for (i = 0; ternaryRelOpts[i].gen.name; i++)
+ {
+ Assert(DoLockModesConflict(ternaryRelOpts[i].gen.lockmode,
+ ternaryRelOpts[i].gen.lockmode));
+ j++;
+ }
+
for (i = 0; intRelOpts[i].gen.name; i++)
{
Assert(DoLockModesConflict(intRelOpts[i].gen.lockmode,
@@ -640,6 +653,14 @@ initialize_reloptions(void)
j++;
}
+ for (i = 0; ternaryRelOpts[i].gen.name; i++)
+ {
+ relOpts[j] = &ternaryRelOpts[i].gen;
+ relOpts[j]->type = RELOPT_TYPE_TERNARY;
+ relOpts[j]->namelen = strlen(relOpts[j]->name);
+ j++;
+ }
+
for (i = 0; intRelOpts[i].gen.name; i++)
{
relOpts[j] = &intRelOpts[i].gen;
@@ -800,6 +821,9 @@ allocate_reloption(bits32 kinds, int type, const char *name, const char *desc,
case RELOPT_TYPE_BOOL:
size = sizeof(relopt_bool);
break;
+ case RELOPT_TYPE_TERNARY:
+ size = sizeof(relopt_ternary);
+ break;
case RELOPT_TYPE_INT:
size = sizeof(relopt_int);
break;
@@ -883,6 +907,54 @@ add_local_bool_reloption(local_relopts *relopts, const char *name,
add_local_reloption(relopts, (relopt_gen *) newoption, offset);
}
+/*
+ * init_ternary_reloption
+ * Allocate and initialize a new ternary reloption
+ */
+static relopt_ternary *
+init_ternary_reloption(bits32 kinds, const char *name, const char *desc,
+ ternary default_val, LOCKMODE lockmode)
+{
+ relopt_ternary *newoption;
+
+ newoption = (relopt_ternary *) allocate_reloption(kinds,
+ RELOPT_TYPE_TERNARY, name, desc, lockmode);
+ newoption->default_val = default_val;
+
+ return newoption;
+}
+
+/*
+ * add_ternary_reloption
+ * Add a new ternary reloption
+ */
+void
+add_ternary_reloption(bits32 kinds, const char *name, const char *desc,
+ ternary default_val, LOCKMODE lockmode)
+{
+ relopt_ternary *newoption = init_ternary_reloption(kinds, name, desc,
+ default_val, lockmode);
+
+ add_reloption((relopt_gen *) newoption);
+}
+
+/*
+ * add_local_ternary_reloption
+ * Add a new ternary local reloption
+ *
+ * 'offset' is offset of ternary-typed field.
+ */
+void
+add_local_ternary_reloption(local_relopts *relopts, const char *name,
+ const char *desc, ternary default_val,
+ int offset)
+{
+ relopt_ternary *newoption = init_ternary_reloption(RELOPT_KIND_LOCAL,
+ name, desc,
+ default_val, 0);
+
+ add_local_reloption(relopts, (relopt_gen *) newoption, offset);
+}
/*
* init_real_reloption
@@ -1617,6 +1689,18 @@ parse_one_reloption(relopt_value *option, char *text_str, int text_len,
option->gen->name, value)));
}
break;
+ case RELOPT_TYPE_TERNARY:
+ {
+ bool b;
+ parsed = parse_bool(value, &b);
+ option->values.ternary_val = b ? TERNARY_TRUE : TERNARY_FALSE;
+ if (validate && !parsed)
+ ereport(ERROR,
+ (errcode(ERRCODE_INVALID_PARAMETER_VALUE),
+ errmsg("invalid value for ternary option \"%s\": %s",
+ option->gen->name, value)));
+ }
+ break;
case RELOPT_TYPE_INT:
{
relopt_int *optint = (relopt_int *) option->gen;
@@ -1780,17 +1864,6 @@ fillRelOptions(void *rdopts, Size basesize,
char *itempos = ((char *) rdopts) + elems[j].offset;
char *string_val;
- /*
- * If isset_offset is provided, store whether the reloption is
- * set there.
- */
- if (elems[j].isset_offset > 0)
- {
- char *setpos = ((char *) rdopts) + elems[j].isset_offset;
-
- *(bool *) setpos = options[i].isset;
- }
-
switch (options[i].gen->type)
{
case RELOPT_TYPE_BOOL:
@@ -1798,6 +1871,11 @@ fillRelOptions(void *rdopts, Size basesize,
options[i].values.bool_val :
((relopt_bool *) options[i].gen)->default_val;
break;
+ case RELOPT_TYPE_TERNARY:
+ *(ternary *) itempos = options[i].isset ?
+ options[i].values.ternary_val :
+ ((relopt_ternary *) options[i].gen)->default_val;
+ break;
case RELOPT_TYPE_INT:
*(int *) itempos = options[i].isset ?
options[i].values.int_val :
@@ -1912,8 +1990,8 @@ default_reloptions(Datum reloptions, bool validate, relopt_kind kind)
offsetof(StdRdOptions, parallel_workers)},
{"vacuum_index_cleanup", RELOPT_TYPE_ENUM,
offsetof(StdRdOptions, vacuum_index_cleanup)},
- {"vacuum_truncate", RELOPT_TYPE_BOOL,
- offsetof(StdRdOptions, vacuum_truncate), offsetof(StdRdOptions, vacuum_truncate_set)},
+ {"vacuum_truncate", RELOPT_TYPE_TERNARY,
+ offsetof(StdRdOptions, vacuum_truncate)},
{"vacuum_max_eager_freeze_failure_rate", RELOPT_TYPE_REAL,
offsetof(StdRdOptions, vacuum_max_eager_freeze_failure_rate)}
};
@@ -1993,7 +2071,6 @@ build_local_reloptions(local_relopts *relopts, Datum options, bool validate)
elems[i].optname = opt->option->name;
elems[i].opttype = opt->option->type;
elems[i].offset = opt->offset;
- elems[i].isset_offset = 0; /* not supported for local relopts yet */
i++;
}
diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c
index 733ef40ae7..977babff54 100644
--- a/src/backend/commands/vacuum.c
+++ b/src/backend/commands/vacuum.c
@@ -2221,9 +2221,9 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams params,
{
StdRdOptions *opts = (StdRdOptions *) rel->rd_options;
- if (opts && opts->vacuum_truncate_set)
+ if (opts && opts->vacuum_truncate != TERNARY_UNSET)
{
- if (opts->vacuum_truncate)
+ if (opts->vacuum_truncate == TERNARY_TRUE)
params.truncate = VACOPTVALUE_ENABLED;
else
params.truncate = VACOPTVALUE_DISABLED;
diff --git a/src/include/access/reloptions.h b/src/include/access/reloptions.h
index a604a4702c..a436697658 100644
--- a/src/include/access/reloptions.h
+++ b/src/include/access/reloptions.h
@@ -29,6 +29,7 @@
typedef enum relopt_type
{
RELOPT_TYPE_BOOL,
+ RELOPT_TYPE_TERNARY, /* on, off, unset */
RELOPT_TYPE_INT,
RELOPT_TYPE_REAL,
RELOPT_TYPE_ENUM,
@@ -80,6 +81,7 @@ typedef struct relopt_value
union
{
bool bool_val;
+ ternary ternary_val;
int int_val;
double real_val;
int enum_val;
@@ -94,6 +96,12 @@ typedef struct relopt_bool
bool default_val;
} relopt_bool;
+typedef struct relopt_rernary
+{
+ relopt_gen gen;
+ int default_val;
+} relopt_ternary;
+
typedef struct relopt_int
{
relopt_gen gen;
@@ -152,19 +160,6 @@ typedef struct
const char *optname; /* option's name */
relopt_type opttype; /* option's datatype */
int offset; /* offset of field in result struct */
-
- /*
- * isset_offset is an optional offset of a field in the result struct that
- * stores whether the option is explicitly set for the relation or if it
- * just picked up the default value. In most cases, this can be
- * accomplished by giving the reloption a special out-of-range default
- * value (e.g., some integer reloptions use -2), but this isn't always
- * possible. For example, a Boolean reloption cannot be given an
- * out-of-range default, so we need another way to discover the source of
- * its value. This offset is only used if given a value greater than
- * zero.
- */
- int isset_offset;
} relopt_parse_elt;
/* Local reloption definition */
@@ -195,6 +190,8 @@ typedef struct local_relopts
extern relopt_kind add_reloption_kind(void);
extern void add_bool_reloption(bits32 kinds, const char *name, const char *desc,
bool default_val, LOCKMODE lockmode);
+extern void add_ternary_reloption(bits32 kinds, const char *name,
+ const char *desc, int default_val, LOCKMODE lockmode);
extern void add_int_reloption(bits32 kinds, const char *name, const char *desc,
int default_val, int min_val, int max_val,
LOCKMODE lockmode);
@@ -214,6 +211,9 @@ extern void register_reloptions_validator(local_relopts *relopts,
extern void add_local_bool_reloption(local_relopts *relopts, const char *name,
const char *desc, bool default_val,
int offset);
+extern void add_local_ternary_reloption(local_relopts *relopts,
+ const char *name, const char *desc,
+ ternary default_val, int offset);
extern void add_local_int_reloption(local_relopts *relopts, const char *name,
const char *desc, int default_val,
int min_val, int max_val, int offset);
diff --git a/src/include/c.h b/src/include/c.h
index 39022f8a9d..9c59ccccf8 100644
--- a/src/include/c.h
+++ b/src/include/c.h
@@ -488,6 +488,22 @@ typedef void (*pg_funcptr_t) (void);
#include <stdbool.h>
+/*
+ * ternary
+ * Boolean value with an extrea "unset" option
+ *
+ * Ternary data type is used in relation options that can be "true", "false" or
+ * "unset". Since relation options are used deep inside the PostgreSQL code,
+ * this type is declared globally.
+*/
+
+typedef enum ternary
+{
+ TERNARY_FALSE = 0,
+ TERNARY_TRUE = 1,
+ TERNARY_UNSET = -1
+} ternary;
+
/* ----------------------------------------------------------------
* Section 3: standard system types
diff --git a/src/include/utils/rel.h b/src/include/utils/rel.h
index b552359915..7346d618f9 100644
--- a/src/include/utils/rel.h
+++ b/src/include/utils/rel.h
@@ -346,8 +346,7 @@ typedef struct StdRdOptions
bool user_catalog_table; /* use as an additional catalog relation */
int parallel_workers; /* max number of parallel workers */
StdRdOptIndexCleanup vacuum_index_cleanup; /* controls index vacuuming */
- bool vacuum_truncate; /* enables vacuum to truncate a relation */
- bool vacuum_truncate_set; /* whether vacuum_truncate is set */
+ ternary vacuum_truncate; /* enables vacuum to truncate a relation */
/*
* Fraction of pages in a relation that vacuum can eagerly scan and fail
--
2.39.2
--nextPart5129932.5fSG56mABF
Content-Disposition: attachment;
filename="v2-0003-Add-alias-to-be-used-as-unset-state.patch"
Content-Transfer-Encoding: 7Bit
Content-Type: text/x-patch; charset="unicode-2-0-utf-8";
name="v2-0003-Add-alias-to-be-used-as-unset-state.patch"
^ permalink raw reply [nested|flat] 868+ messages in thread
* [PATCH v2a 2/4] Introduce ternary reloptions
@ 2025-09-04 14:41 Nikolay Shaplov <[email protected]>
0 siblings, 0 replies; 868+ messages in thread
From: Nikolay Shaplov @ 2025-09-04 14:41 UTC (permalink / raw)
Introduce ternary reloption as a replacement for current `vacuum_truncate`
implementation. Remove `vacuum_truncate_set` additional flag and using
`TERNARY_UNSET` value instead.
---
src/backend/access/common/reloptions.c | 129 ++++++++++++++++++++-----
src/backend/commands/vacuum.c | 4 +-
src/include/access/reloptions.h | 26 ++---
src/include/c.h | 16 +++
src/include/utils/rel.h | 3 +-
5 files changed, 135 insertions(+), 43 deletions(-)
diff --git a/src/backend/access/common/reloptions.c b/src/backend/access/common/reloptions.c
index 0af3fea68f..f543f11001 100644
--- a/src/backend/access/common/reloptions.c
+++ b/src/backend/access/common/reloptions.c
@@ -40,9 +40,9 @@
*
* To add an option:
*
- * (i) decide on a type (bool, integer, real, enum, string), name, default
- * value, upper and lower bounds (if applicable); for strings, consider a
- * validation routine.
+ * (i) decide on a type (bool, ternary, integer, real, enum, string), name,
+ * default value, upper and lower bounds (if applicable); for strings,
+ * consider a validation routine.
* (ii) add a record below (or use add_<type>_reloption).
* (iii) add it to the appropriate options struct (perhaps StdRdOptions)
* (iv) add it to the appropriate handling routine (perhaps
@@ -147,15 +147,6 @@ static relopt_bool boolRelOpts[] =
},
false
},
- {
- {
- "vacuum_truncate",
- "Enables vacuum to truncate empty pages at the end of this table",
- RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
- ShareUpdateExclusiveLock
- },
- true
- },
{
{
"deduplicate_items",
@@ -170,6 +161,21 @@ static relopt_bool boolRelOpts[] =
{{NULL}}
};
+static relopt_ternary ternaryRelOpts[] =
+{
+ {
+ {
+ "vacuum_truncate",
+ "Enables vacuum to truncate empty pages at the end of this table",
+ RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
+ ShareUpdateExclusiveLock
+ },
+ TERNARY_UNSET
+ },
+ /* list terminator */
+ {{NULL}}
+};
+
static relopt_int intRelOpts[] =
{
{
@@ -600,6 +606,13 @@ initialize_reloptions(void)
boolRelOpts[i].gen.lockmode));
j++;
}
+ for (i = 0; ternaryRelOpts[i].gen.name; i++)
+ {
+ Assert(DoLockModesConflict(ternaryRelOpts[i].gen.lockmode,
+ ternaryRelOpts[i].gen.lockmode));
+ j++;
+ }
+
for (i = 0; intRelOpts[i].gen.name; i++)
{
Assert(DoLockModesConflict(intRelOpts[i].gen.lockmode,
@@ -640,6 +653,14 @@ initialize_reloptions(void)
j++;
}
+ for (i = 0; ternaryRelOpts[i].gen.name; i++)
+ {
+ relOpts[j] = &ternaryRelOpts[i].gen;
+ relOpts[j]->type = RELOPT_TYPE_TERNARY;
+ relOpts[j]->namelen = strlen(relOpts[j]->name);
+ j++;
+ }
+
for (i = 0; intRelOpts[i].gen.name; i++)
{
relOpts[j] = &intRelOpts[i].gen;
@@ -800,6 +821,9 @@ allocate_reloption(bits32 kinds, int type, const char *name, const char *desc,
case RELOPT_TYPE_BOOL:
size = sizeof(relopt_bool);
break;
+ case RELOPT_TYPE_TERNARY:
+ size = sizeof(relopt_ternary);
+ break;
case RELOPT_TYPE_INT:
size = sizeof(relopt_int);
break;
@@ -883,6 +907,54 @@ add_local_bool_reloption(local_relopts *relopts, const char *name,
add_local_reloption(relopts, (relopt_gen *) newoption, offset);
}
+/*
+ * init_ternary_reloption
+ * Allocate and initialize a new ternary reloption
+ */
+static relopt_ternary *
+init_ternary_reloption(bits32 kinds, const char *name, const char *desc,
+ ternary default_val, LOCKMODE lockmode)
+{
+ relopt_ternary *newoption;
+
+ newoption = (relopt_ternary *) allocate_reloption(kinds,
+ RELOPT_TYPE_TERNARY, name, desc, lockmode);
+ newoption->default_val = default_val;
+
+ return newoption;
+}
+
+/*
+ * add_ternary_reloption
+ * Add a new ternary reloption
+ */
+void
+add_ternary_reloption(bits32 kinds, const char *name, const char *desc,
+ ternary default_val, LOCKMODE lockmode)
+{
+ relopt_ternary *newoption = init_ternary_reloption(kinds, name, desc,
+ default_val, lockmode);
+
+ add_reloption((relopt_gen *) newoption);
+}
+
+/*
+ * add_local_ternary_reloption
+ * Add a new ternary local reloption
+ *
+ * 'offset' is offset of ternary-typed field.
+ */
+void
+add_local_ternary_reloption(local_relopts *relopts, const char *name,
+ const char *desc, ternary default_val,
+ int offset)
+{
+ relopt_ternary *newoption = init_ternary_reloption(RELOPT_KIND_LOCAL,
+ name, desc,
+ default_val, 0);
+
+ add_local_reloption(relopts, (relopt_gen *) newoption, offset);
+}
/*
* init_real_reloption
@@ -1617,6 +1689,18 @@ parse_one_reloption(relopt_value *option, char *text_str, int text_len,
option->gen->name, value)));
}
break;
+ case RELOPT_TYPE_TERNARY:
+ {
+ bool b;
+ parsed = parse_bool(value, &b);
+ option->values.ternary_val = b ? TERNARY_TRUE : TERNARY_FALSE;
+ if (validate && !parsed)
+ ereport(ERROR,
+ (errcode(ERRCODE_INVALID_PARAMETER_VALUE),
+ errmsg("invalid value for ternary option \"%s\": %s",
+ option->gen->name, value)));
+ }
+ break;
case RELOPT_TYPE_INT:
{
relopt_int *optint = (relopt_int *) option->gen;
@@ -1780,17 +1864,6 @@ fillRelOptions(void *rdopts, Size basesize,
char *itempos = ((char *) rdopts) + elems[j].offset;
char *string_val;
- /*
- * If isset_offset is provided, store whether the reloption is
- * set there.
- */
- if (elems[j].isset_offset > 0)
- {
- char *setpos = ((char *) rdopts) + elems[j].isset_offset;
-
- *(bool *) setpos = options[i].isset;
- }
-
switch (options[i].gen->type)
{
case RELOPT_TYPE_BOOL:
@@ -1798,6 +1871,11 @@ fillRelOptions(void *rdopts, Size basesize,
options[i].values.bool_val :
((relopt_bool *) options[i].gen)->default_val;
break;
+ case RELOPT_TYPE_TERNARY:
+ *(ternary *) itempos = options[i].isset ?
+ options[i].values.ternary_val :
+ ((relopt_ternary *) options[i].gen)->default_val;
+ break;
case RELOPT_TYPE_INT:
*(int *) itempos = options[i].isset ?
options[i].values.int_val :
@@ -1912,8 +1990,8 @@ default_reloptions(Datum reloptions, bool validate, relopt_kind kind)
offsetof(StdRdOptions, parallel_workers)},
{"vacuum_index_cleanup", RELOPT_TYPE_ENUM,
offsetof(StdRdOptions, vacuum_index_cleanup)},
- {"vacuum_truncate", RELOPT_TYPE_BOOL,
- offsetof(StdRdOptions, vacuum_truncate), offsetof(StdRdOptions, vacuum_truncate_set)},
+ {"vacuum_truncate", RELOPT_TYPE_TERNARY,
+ offsetof(StdRdOptions, vacuum_truncate)},
{"vacuum_max_eager_freeze_failure_rate", RELOPT_TYPE_REAL,
offsetof(StdRdOptions, vacuum_max_eager_freeze_failure_rate)}
};
@@ -1993,7 +2071,6 @@ build_local_reloptions(local_relopts *relopts, Datum options, bool validate)
elems[i].optname = opt->option->name;
elems[i].opttype = opt->option->type;
elems[i].offset = opt->offset;
- elems[i].isset_offset = 0; /* not supported for local relopts yet */
i++;
}
diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c
index 733ef40ae7..977babff54 100644
--- a/src/backend/commands/vacuum.c
+++ b/src/backend/commands/vacuum.c
@@ -2221,9 +2221,9 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams params,
{
StdRdOptions *opts = (StdRdOptions *) rel->rd_options;
- if (opts && opts->vacuum_truncate_set)
+ if (opts && opts->vacuum_truncate != TERNARY_UNSET)
{
- if (opts->vacuum_truncate)
+ if (opts->vacuum_truncate == TERNARY_TRUE)
params.truncate = VACOPTVALUE_ENABLED;
else
params.truncate = VACOPTVALUE_DISABLED;
diff --git a/src/include/access/reloptions.h b/src/include/access/reloptions.h
index a604a4702c..a436697658 100644
--- a/src/include/access/reloptions.h
+++ b/src/include/access/reloptions.h
@@ -29,6 +29,7 @@
typedef enum relopt_type
{
RELOPT_TYPE_BOOL,
+ RELOPT_TYPE_TERNARY, /* on, off, unset */
RELOPT_TYPE_INT,
RELOPT_TYPE_REAL,
RELOPT_TYPE_ENUM,
@@ -80,6 +81,7 @@ typedef struct relopt_value
union
{
bool bool_val;
+ ternary ternary_val;
int int_val;
double real_val;
int enum_val;
@@ -94,6 +96,12 @@ typedef struct relopt_bool
bool default_val;
} relopt_bool;
+typedef struct relopt_rernary
+{
+ relopt_gen gen;
+ int default_val;
+} relopt_ternary;
+
typedef struct relopt_int
{
relopt_gen gen;
@@ -152,19 +160,6 @@ typedef struct
const char *optname; /* option's name */
relopt_type opttype; /* option's datatype */
int offset; /* offset of field in result struct */
-
- /*
- * isset_offset is an optional offset of a field in the result struct that
- * stores whether the option is explicitly set for the relation or if it
- * just picked up the default value. In most cases, this can be
- * accomplished by giving the reloption a special out-of-range default
- * value (e.g., some integer reloptions use -2), but this isn't always
- * possible. For example, a Boolean reloption cannot be given an
- * out-of-range default, so we need another way to discover the source of
- * its value. This offset is only used if given a value greater than
- * zero.
- */
- int isset_offset;
} relopt_parse_elt;
/* Local reloption definition */
@@ -195,6 +190,8 @@ typedef struct local_relopts
extern relopt_kind add_reloption_kind(void);
extern void add_bool_reloption(bits32 kinds, const char *name, const char *desc,
bool default_val, LOCKMODE lockmode);
+extern void add_ternary_reloption(bits32 kinds, const char *name,
+ const char *desc, int default_val, LOCKMODE lockmode);
extern void add_int_reloption(bits32 kinds, const char *name, const char *desc,
int default_val, int min_val, int max_val,
LOCKMODE lockmode);
@@ -214,6 +211,9 @@ extern void register_reloptions_validator(local_relopts *relopts,
extern void add_local_bool_reloption(local_relopts *relopts, const char *name,
const char *desc, bool default_val,
int offset);
+extern void add_local_ternary_reloption(local_relopts *relopts,
+ const char *name, const char *desc,
+ ternary default_val, int offset);
extern void add_local_int_reloption(local_relopts *relopts, const char *name,
const char *desc, int default_val,
int min_val, int max_val, int offset);
diff --git a/src/include/c.h b/src/include/c.h
index 39022f8a9d..9c59ccccf8 100644
--- a/src/include/c.h
+++ b/src/include/c.h
@@ -488,6 +488,22 @@ typedef void (*pg_funcptr_t) (void);
#include <stdbool.h>
+/*
+ * ternary
+ * Boolean value with an extrea "unset" option
+ *
+ * Ternary data type is used in relation options that can be "true", "false" or
+ * "unset". Since relation options are used deep inside the PostgreSQL code,
+ * this type is declared globally.
+*/
+
+typedef enum ternary
+{
+ TERNARY_FALSE = 0,
+ TERNARY_TRUE = 1,
+ TERNARY_UNSET = -1
+} ternary;
+
/* ----------------------------------------------------------------
* Section 3: standard system types
diff --git a/src/include/utils/rel.h b/src/include/utils/rel.h
index b552359915..7346d618f9 100644
--- a/src/include/utils/rel.h
+++ b/src/include/utils/rel.h
@@ -346,8 +346,7 @@ typedef struct StdRdOptions
bool user_catalog_table; /* use as an additional catalog relation */
int parallel_workers; /* max number of parallel workers */
StdRdOptIndexCleanup vacuum_index_cleanup; /* controls index vacuuming */
- bool vacuum_truncate; /* enables vacuum to truncate a relation */
- bool vacuum_truncate_set; /* whether vacuum_truncate is set */
+ ternary vacuum_truncate; /* enables vacuum to truncate a relation */
/*
* Fraction of pages in a relation that vacuum can eagerly scan and fail
--
2.39.2
--nextPart4155600.3ZeAukHxDK
Content-Disposition: attachment;
filename="v2a-0003-Add-alias-to-be-used-as-unset-state.patch"
Content-Transfer-Encoding: 7Bit
Content-Type: text/x-patch; charset="unicode-2-0-utf-8";
name="v2a-0003-Add-alias-to-be-used-as-unset-state.patch"
^ permalink raw reply [nested|flat] 868+ messages in thread
* [PATCH v2 2/4] Introduce ternary reloptions
@ 2025-09-04 14:41 Nikolay Shaplov <[email protected]>
0 siblings, 0 replies; 868+ messages in thread
From: Nikolay Shaplov @ 2025-09-04 14:41 UTC (permalink / raw)
Introduce ternary reloption as a replacement for current `vacuum_truncate`
implementation. Remove `vacuum_truncate_set` additional flag and using
`TERNARY_UNSET` value instead.
---
src/backend/access/common/reloptions.c | 129 ++++++++++++++++++++-----
src/backend/commands/vacuum.c | 4 +-
src/include/access/reloptions.h | 26 ++---
src/include/c.h | 16 +++
src/include/utils/rel.h | 3 +-
5 files changed, 135 insertions(+), 43 deletions(-)
diff --git a/src/backend/access/common/reloptions.c b/src/backend/access/common/reloptions.c
index 0af3fea68f..f543f11001 100644
--- a/src/backend/access/common/reloptions.c
+++ b/src/backend/access/common/reloptions.c
@@ -40,9 +40,9 @@
*
* To add an option:
*
- * (i) decide on a type (bool, integer, real, enum, string), name, default
- * value, upper and lower bounds (if applicable); for strings, consider a
- * validation routine.
+ * (i) decide on a type (bool, ternary, integer, real, enum, string), name,
+ * default value, upper and lower bounds (if applicable); for strings,
+ * consider a validation routine.
* (ii) add a record below (or use add_<type>_reloption).
* (iii) add it to the appropriate options struct (perhaps StdRdOptions)
* (iv) add it to the appropriate handling routine (perhaps
@@ -147,15 +147,6 @@ static relopt_bool boolRelOpts[] =
},
false
},
- {
- {
- "vacuum_truncate",
- "Enables vacuum to truncate empty pages at the end of this table",
- RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
- ShareUpdateExclusiveLock
- },
- true
- },
{
{
"deduplicate_items",
@@ -170,6 +161,21 @@ static relopt_bool boolRelOpts[] =
{{NULL}}
};
+static relopt_ternary ternaryRelOpts[] =
+{
+ {
+ {
+ "vacuum_truncate",
+ "Enables vacuum to truncate empty pages at the end of this table",
+ RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
+ ShareUpdateExclusiveLock
+ },
+ TERNARY_UNSET
+ },
+ /* list terminator */
+ {{NULL}}
+};
+
static relopt_int intRelOpts[] =
{
{
@@ -600,6 +606,13 @@ initialize_reloptions(void)
boolRelOpts[i].gen.lockmode));
j++;
}
+ for (i = 0; ternaryRelOpts[i].gen.name; i++)
+ {
+ Assert(DoLockModesConflict(ternaryRelOpts[i].gen.lockmode,
+ ternaryRelOpts[i].gen.lockmode));
+ j++;
+ }
+
for (i = 0; intRelOpts[i].gen.name; i++)
{
Assert(DoLockModesConflict(intRelOpts[i].gen.lockmode,
@@ -640,6 +653,14 @@ initialize_reloptions(void)
j++;
}
+ for (i = 0; ternaryRelOpts[i].gen.name; i++)
+ {
+ relOpts[j] = &ternaryRelOpts[i].gen;
+ relOpts[j]->type = RELOPT_TYPE_TERNARY;
+ relOpts[j]->namelen = strlen(relOpts[j]->name);
+ j++;
+ }
+
for (i = 0; intRelOpts[i].gen.name; i++)
{
relOpts[j] = &intRelOpts[i].gen;
@@ -800,6 +821,9 @@ allocate_reloption(bits32 kinds, int type, const char *name, const char *desc,
case RELOPT_TYPE_BOOL:
size = sizeof(relopt_bool);
break;
+ case RELOPT_TYPE_TERNARY:
+ size = sizeof(relopt_ternary);
+ break;
case RELOPT_TYPE_INT:
size = sizeof(relopt_int);
break;
@@ -883,6 +907,54 @@ add_local_bool_reloption(local_relopts *relopts, const char *name,
add_local_reloption(relopts, (relopt_gen *) newoption, offset);
}
+/*
+ * init_ternary_reloption
+ * Allocate and initialize a new ternary reloption
+ */
+static relopt_ternary *
+init_ternary_reloption(bits32 kinds, const char *name, const char *desc,
+ ternary default_val, LOCKMODE lockmode)
+{
+ relopt_ternary *newoption;
+
+ newoption = (relopt_ternary *) allocate_reloption(kinds,
+ RELOPT_TYPE_TERNARY, name, desc, lockmode);
+ newoption->default_val = default_val;
+
+ return newoption;
+}
+
+/*
+ * add_ternary_reloption
+ * Add a new ternary reloption
+ */
+void
+add_ternary_reloption(bits32 kinds, const char *name, const char *desc,
+ ternary default_val, LOCKMODE lockmode)
+{
+ relopt_ternary *newoption = init_ternary_reloption(kinds, name, desc,
+ default_val, lockmode);
+
+ add_reloption((relopt_gen *) newoption);
+}
+
+/*
+ * add_local_ternary_reloption
+ * Add a new ternary local reloption
+ *
+ * 'offset' is offset of ternary-typed field.
+ */
+void
+add_local_ternary_reloption(local_relopts *relopts, const char *name,
+ const char *desc, ternary default_val,
+ int offset)
+{
+ relopt_ternary *newoption = init_ternary_reloption(RELOPT_KIND_LOCAL,
+ name, desc,
+ default_val, 0);
+
+ add_local_reloption(relopts, (relopt_gen *) newoption, offset);
+}
/*
* init_real_reloption
@@ -1617,6 +1689,18 @@ parse_one_reloption(relopt_value *option, char *text_str, int text_len,
option->gen->name, value)));
}
break;
+ case RELOPT_TYPE_TERNARY:
+ {
+ bool b;
+ parsed = parse_bool(value, &b);
+ option->values.ternary_val = b ? TERNARY_TRUE : TERNARY_FALSE;
+ if (validate && !parsed)
+ ereport(ERROR,
+ (errcode(ERRCODE_INVALID_PARAMETER_VALUE),
+ errmsg("invalid value for ternary option \"%s\": %s",
+ option->gen->name, value)));
+ }
+ break;
case RELOPT_TYPE_INT:
{
relopt_int *optint = (relopt_int *) option->gen;
@@ -1780,17 +1864,6 @@ fillRelOptions(void *rdopts, Size basesize,
char *itempos = ((char *) rdopts) + elems[j].offset;
char *string_val;
- /*
- * If isset_offset is provided, store whether the reloption is
- * set there.
- */
- if (elems[j].isset_offset > 0)
- {
- char *setpos = ((char *) rdopts) + elems[j].isset_offset;
-
- *(bool *) setpos = options[i].isset;
- }
-
switch (options[i].gen->type)
{
case RELOPT_TYPE_BOOL:
@@ -1798,6 +1871,11 @@ fillRelOptions(void *rdopts, Size basesize,
options[i].values.bool_val :
((relopt_bool *) options[i].gen)->default_val;
break;
+ case RELOPT_TYPE_TERNARY:
+ *(ternary *) itempos = options[i].isset ?
+ options[i].values.ternary_val :
+ ((relopt_ternary *) options[i].gen)->default_val;
+ break;
case RELOPT_TYPE_INT:
*(int *) itempos = options[i].isset ?
options[i].values.int_val :
@@ -1912,8 +1990,8 @@ default_reloptions(Datum reloptions, bool validate, relopt_kind kind)
offsetof(StdRdOptions, parallel_workers)},
{"vacuum_index_cleanup", RELOPT_TYPE_ENUM,
offsetof(StdRdOptions, vacuum_index_cleanup)},
- {"vacuum_truncate", RELOPT_TYPE_BOOL,
- offsetof(StdRdOptions, vacuum_truncate), offsetof(StdRdOptions, vacuum_truncate_set)},
+ {"vacuum_truncate", RELOPT_TYPE_TERNARY,
+ offsetof(StdRdOptions, vacuum_truncate)},
{"vacuum_max_eager_freeze_failure_rate", RELOPT_TYPE_REAL,
offsetof(StdRdOptions, vacuum_max_eager_freeze_failure_rate)}
};
@@ -1993,7 +2071,6 @@ build_local_reloptions(local_relopts *relopts, Datum options, bool validate)
elems[i].optname = opt->option->name;
elems[i].opttype = opt->option->type;
elems[i].offset = opt->offset;
- elems[i].isset_offset = 0; /* not supported for local relopts yet */
i++;
}
diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c
index 733ef40ae7..977babff54 100644
--- a/src/backend/commands/vacuum.c
+++ b/src/backend/commands/vacuum.c
@@ -2221,9 +2221,9 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams params,
{
StdRdOptions *opts = (StdRdOptions *) rel->rd_options;
- if (opts && opts->vacuum_truncate_set)
+ if (opts && opts->vacuum_truncate != TERNARY_UNSET)
{
- if (opts->vacuum_truncate)
+ if (opts->vacuum_truncate == TERNARY_TRUE)
params.truncate = VACOPTVALUE_ENABLED;
else
params.truncate = VACOPTVALUE_DISABLED;
diff --git a/src/include/access/reloptions.h b/src/include/access/reloptions.h
index a604a4702c..a436697658 100644
--- a/src/include/access/reloptions.h
+++ b/src/include/access/reloptions.h
@@ -29,6 +29,7 @@
typedef enum relopt_type
{
RELOPT_TYPE_BOOL,
+ RELOPT_TYPE_TERNARY, /* on, off, unset */
RELOPT_TYPE_INT,
RELOPT_TYPE_REAL,
RELOPT_TYPE_ENUM,
@@ -80,6 +81,7 @@ typedef struct relopt_value
union
{
bool bool_val;
+ ternary ternary_val;
int int_val;
double real_val;
int enum_val;
@@ -94,6 +96,12 @@ typedef struct relopt_bool
bool default_val;
} relopt_bool;
+typedef struct relopt_rernary
+{
+ relopt_gen gen;
+ int default_val;
+} relopt_ternary;
+
typedef struct relopt_int
{
relopt_gen gen;
@@ -152,19 +160,6 @@ typedef struct
const char *optname; /* option's name */
relopt_type opttype; /* option's datatype */
int offset; /* offset of field in result struct */
-
- /*
- * isset_offset is an optional offset of a field in the result struct that
- * stores whether the option is explicitly set for the relation or if it
- * just picked up the default value. In most cases, this can be
- * accomplished by giving the reloption a special out-of-range default
- * value (e.g., some integer reloptions use -2), but this isn't always
- * possible. For example, a Boolean reloption cannot be given an
- * out-of-range default, so we need another way to discover the source of
- * its value. This offset is only used if given a value greater than
- * zero.
- */
- int isset_offset;
} relopt_parse_elt;
/* Local reloption definition */
@@ -195,6 +190,8 @@ typedef struct local_relopts
extern relopt_kind add_reloption_kind(void);
extern void add_bool_reloption(bits32 kinds, const char *name, const char *desc,
bool default_val, LOCKMODE lockmode);
+extern void add_ternary_reloption(bits32 kinds, const char *name,
+ const char *desc, int default_val, LOCKMODE lockmode);
extern void add_int_reloption(bits32 kinds, const char *name, const char *desc,
int default_val, int min_val, int max_val,
LOCKMODE lockmode);
@@ -214,6 +211,9 @@ extern void register_reloptions_validator(local_relopts *relopts,
extern void add_local_bool_reloption(local_relopts *relopts, const char *name,
const char *desc, bool default_val,
int offset);
+extern void add_local_ternary_reloption(local_relopts *relopts,
+ const char *name, const char *desc,
+ ternary default_val, int offset);
extern void add_local_int_reloption(local_relopts *relopts, const char *name,
const char *desc, int default_val,
int min_val, int max_val, int offset);
diff --git a/src/include/c.h b/src/include/c.h
index 39022f8a9d..9c59ccccf8 100644
--- a/src/include/c.h
+++ b/src/include/c.h
@@ -488,6 +488,22 @@ typedef void (*pg_funcptr_t) (void);
#include <stdbool.h>
+/*
+ * ternary
+ * Boolean value with an extrea "unset" option
+ *
+ * Ternary data type is used in relation options that can be "true", "false" or
+ * "unset". Since relation options are used deep inside the PostgreSQL code,
+ * this type is declared globally.
+*/
+
+typedef enum ternary
+{
+ TERNARY_FALSE = 0,
+ TERNARY_TRUE = 1,
+ TERNARY_UNSET = -1
+} ternary;
+
/* ----------------------------------------------------------------
* Section 3: standard system types
diff --git a/src/include/utils/rel.h b/src/include/utils/rel.h
index b552359915..7346d618f9 100644
--- a/src/include/utils/rel.h
+++ b/src/include/utils/rel.h
@@ -346,8 +346,7 @@ typedef struct StdRdOptions
bool user_catalog_table; /* use as an additional catalog relation */
int parallel_workers; /* max number of parallel workers */
StdRdOptIndexCleanup vacuum_index_cleanup; /* controls index vacuuming */
- bool vacuum_truncate; /* enables vacuum to truncate a relation */
- bool vacuum_truncate_set; /* whether vacuum_truncate is set */
+ ternary vacuum_truncate; /* enables vacuum to truncate a relation */
/*
* Fraction of pages in a relation that vacuum can eagerly scan and fail
--
2.39.2
--nextPart5129932.5fSG56mABF
Content-Disposition: attachment;
filename="v2-0003-Add-alias-to-be-used-as-unset-state.patch"
Content-Transfer-Encoding: 7Bit
Content-Type: text/x-patch; charset="unicode-2-0-utf-8";
name="v2-0003-Add-alias-to-be-used-as-unset-state.patch"
^ permalink raw reply [nested|flat] 868+ messages in thread
* [PATCH v2a 2/4] Introduce ternary reloptions
@ 2025-09-04 14:41 Nikolay Shaplov <[email protected]>
0 siblings, 0 replies; 868+ messages in thread
From: Nikolay Shaplov @ 2025-09-04 14:41 UTC (permalink / raw)
Introduce ternary reloption as a replacement for current `vacuum_truncate`
implementation. Remove `vacuum_truncate_set` additional flag and using
`TERNARY_UNSET` value instead.
---
src/backend/access/common/reloptions.c | 129 ++++++++++++++++++++-----
src/backend/commands/vacuum.c | 4 +-
src/include/access/reloptions.h | 26 ++---
src/include/c.h | 16 +++
src/include/utils/rel.h | 3 +-
5 files changed, 135 insertions(+), 43 deletions(-)
diff --git a/src/backend/access/common/reloptions.c b/src/backend/access/common/reloptions.c
index 0af3fea68f..f543f11001 100644
--- a/src/backend/access/common/reloptions.c
+++ b/src/backend/access/common/reloptions.c
@@ -40,9 +40,9 @@
*
* To add an option:
*
- * (i) decide on a type (bool, integer, real, enum, string), name, default
- * value, upper and lower bounds (if applicable); for strings, consider a
- * validation routine.
+ * (i) decide on a type (bool, ternary, integer, real, enum, string), name,
+ * default value, upper and lower bounds (if applicable); for strings,
+ * consider a validation routine.
* (ii) add a record below (or use add_<type>_reloption).
* (iii) add it to the appropriate options struct (perhaps StdRdOptions)
* (iv) add it to the appropriate handling routine (perhaps
@@ -147,15 +147,6 @@ static relopt_bool boolRelOpts[] =
},
false
},
- {
- {
- "vacuum_truncate",
- "Enables vacuum to truncate empty pages at the end of this table",
- RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
- ShareUpdateExclusiveLock
- },
- true
- },
{
{
"deduplicate_items",
@@ -170,6 +161,21 @@ static relopt_bool boolRelOpts[] =
{{NULL}}
};
+static relopt_ternary ternaryRelOpts[] =
+{
+ {
+ {
+ "vacuum_truncate",
+ "Enables vacuum to truncate empty pages at the end of this table",
+ RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
+ ShareUpdateExclusiveLock
+ },
+ TERNARY_UNSET
+ },
+ /* list terminator */
+ {{NULL}}
+};
+
static relopt_int intRelOpts[] =
{
{
@@ -600,6 +606,13 @@ initialize_reloptions(void)
boolRelOpts[i].gen.lockmode));
j++;
}
+ for (i = 0; ternaryRelOpts[i].gen.name; i++)
+ {
+ Assert(DoLockModesConflict(ternaryRelOpts[i].gen.lockmode,
+ ternaryRelOpts[i].gen.lockmode));
+ j++;
+ }
+
for (i = 0; intRelOpts[i].gen.name; i++)
{
Assert(DoLockModesConflict(intRelOpts[i].gen.lockmode,
@@ -640,6 +653,14 @@ initialize_reloptions(void)
j++;
}
+ for (i = 0; ternaryRelOpts[i].gen.name; i++)
+ {
+ relOpts[j] = &ternaryRelOpts[i].gen;
+ relOpts[j]->type = RELOPT_TYPE_TERNARY;
+ relOpts[j]->namelen = strlen(relOpts[j]->name);
+ j++;
+ }
+
for (i = 0; intRelOpts[i].gen.name; i++)
{
relOpts[j] = &intRelOpts[i].gen;
@@ -800,6 +821,9 @@ allocate_reloption(bits32 kinds, int type, const char *name, const char *desc,
case RELOPT_TYPE_BOOL:
size = sizeof(relopt_bool);
break;
+ case RELOPT_TYPE_TERNARY:
+ size = sizeof(relopt_ternary);
+ break;
case RELOPT_TYPE_INT:
size = sizeof(relopt_int);
break;
@@ -883,6 +907,54 @@ add_local_bool_reloption(local_relopts *relopts, const char *name,
add_local_reloption(relopts, (relopt_gen *) newoption, offset);
}
+/*
+ * init_ternary_reloption
+ * Allocate and initialize a new ternary reloption
+ */
+static relopt_ternary *
+init_ternary_reloption(bits32 kinds, const char *name, const char *desc,
+ ternary default_val, LOCKMODE lockmode)
+{
+ relopt_ternary *newoption;
+
+ newoption = (relopt_ternary *) allocate_reloption(kinds,
+ RELOPT_TYPE_TERNARY, name, desc, lockmode);
+ newoption->default_val = default_val;
+
+ return newoption;
+}
+
+/*
+ * add_ternary_reloption
+ * Add a new ternary reloption
+ */
+void
+add_ternary_reloption(bits32 kinds, const char *name, const char *desc,
+ ternary default_val, LOCKMODE lockmode)
+{
+ relopt_ternary *newoption = init_ternary_reloption(kinds, name, desc,
+ default_val, lockmode);
+
+ add_reloption((relopt_gen *) newoption);
+}
+
+/*
+ * add_local_ternary_reloption
+ * Add a new ternary local reloption
+ *
+ * 'offset' is offset of ternary-typed field.
+ */
+void
+add_local_ternary_reloption(local_relopts *relopts, const char *name,
+ const char *desc, ternary default_val,
+ int offset)
+{
+ relopt_ternary *newoption = init_ternary_reloption(RELOPT_KIND_LOCAL,
+ name, desc,
+ default_val, 0);
+
+ add_local_reloption(relopts, (relopt_gen *) newoption, offset);
+}
/*
* init_real_reloption
@@ -1617,6 +1689,18 @@ parse_one_reloption(relopt_value *option, char *text_str, int text_len,
option->gen->name, value)));
}
break;
+ case RELOPT_TYPE_TERNARY:
+ {
+ bool b;
+ parsed = parse_bool(value, &b);
+ option->values.ternary_val = b ? TERNARY_TRUE : TERNARY_FALSE;
+ if (validate && !parsed)
+ ereport(ERROR,
+ (errcode(ERRCODE_INVALID_PARAMETER_VALUE),
+ errmsg("invalid value for ternary option \"%s\": %s",
+ option->gen->name, value)));
+ }
+ break;
case RELOPT_TYPE_INT:
{
relopt_int *optint = (relopt_int *) option->gen;
@@ -1780,17 +1864,6 @@ fillRelOptions(void *rdopts, Size basesize,
char *itempos = ((char *) rdopts) + elems[j].offset;
char *string_val;
- /*
- * If isset_offset is provided, store whether the reloption is
- * set there.
- */
- if (elems[j].isset_offset > 0)
- {
- char *setpos = ((char *) rdopts) + elems[j].isset_offset;
-
- *(bool *) setpos = options[i].isset;
- }
-
switch (options[i].gen->type)
{
case RELOPT_TYPE_BOOL:
@@ -1798,6 +1871,11 @@ fillRelOptions(void *rdopts, Size basesize,
options[i].values.bool_val :
((relopt_bool *) options[i].gen)->default_val;
break;
+ case RELOPT_TYPE_TERNARY:
+ *(ternary *) itempos = options[i].isset ?
+ options[i].values.ternary_val :
+ ((relopt_ternary *) options[i].gen)->default_val;
+ break;
case RELOPT_TYPE_INT:
*(int *) itempos = options[i].isset ?
options[i].values.int_val :
@@ -1912,8 +1990,8 @@ default_reloptions(Datum reloptions, bool validate, relopt_kind kind)
offsetof(StdRdOptions, parallel_workers)},
{"vacuum_index_cleanup", RELOPT_TYPE_ENUM,
offsetof(StdRdOptions, vacuum_index_cleanup)},
- {"vacuum_truncate", RELOPT_TYPE_BOOL,
- offsetof(StdRdOptions, vacuum_truncate), offsetof(StdRdOptions, vacuum_truncate_set)},
+ {"vacuum_truncate", RELOPT_TYPE_TERNARY,
+ offsetof(StdRdOptions, vacuum_truncate)},
{"vacuum_max_eager_freeze_failure_rate", RELOPT_TYPE_REAL,
offsetof(StdRdOptions, vacuum_max_eager_freeze_failure_rate)}
};
@@ -1993,7 +2071,6 @@ build_local_reloptions(local_relopts *relopts, Datum options, bool validate)
elems[i].optname = opt->option->name;
elems[i].opttype = opt->option->type;
elems[i].offset = opt->offset;
- elems[i].isset_offset = 0; /* not supported for local relopts yet */
i++;
}
diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c
index 733ef40ae7..977babff54 100644
--- a/src/backend/commands/vacuum.c
+++ b/src/backend/commands/vacuum.c
@@ -2221,9 +2221,9 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams params,
{
StdRdOptions *opts = (StdRdOptions *) rel->rd_options;
- if (opts && opts->vacuum_truncate_set)
+ if (opts && opts->vacuum_truncate != TERNARY_UNSET)
{
- if (opts->vacuum_truncate)
+ if (opts->vacuum_truncate == TERNARY_TRUE)
params.truncate = VACOPTVALUE_ENABLED;
else
params.truncate = VACOPTVALUE_DISABLED;
diff --git a/src/include/access/reloptions.h b/src/include/access/reloptions.h
index a604a4702c..a436697658 100644
--- a/src/include/access/reloptions.h
+++ b/src/include/access/reloptions.h
@@ -29,6 +29,7 @@
typedef enum relopt_type
{
RELOPT_TYPE_BOOL,
+ RELOPT_TYPE_TERNARY, /* on, off, unset */
RELOPT_TYPE_INT,
RELOPT_TYPE_REAL,
RELOPT_TYPE_ENUM,
@@ -80,6 +81,7 @@ typedef struct relopt_value
union
{
bool bool_val;
+ ternary ternary_val;
int int_val;
double real_val;
int enum_val;
@@ -94,6 +96,12 @@ typedef struct relopt_bool
bool default_val;
} relopt_bool;
+typedef struct relopt_rernary
+{
+ relopt_gen gen;
+ int default_val;
+} relopt_ternary;
+
typedef struct relopt_int
{
relopt_gen gen;
@@ -152,19 +160,6 @@ typedef struct
const char *optname; /* option's name */
relopt_type opttype; /* option's datatype */
int offset; /* offset of field in result struct */
-
- /*
- * isset_offset is an optional offset of a field in the result struct that
- * stores whether the option is explicitly set for the relation or if it
- * just picked up the default value. In most cases, this can be
- * accomplished by giving the reloption a special out-of-range default
- * value (e.g., some integer reloptions use -2), but this isn't always
- * possible. For example, a Boolean reloption cannot be given an
- * out-of-range default, so we need another way to discover the source of
- * its value. This offset is only used if given a value greater than
- * zero.
- */
- int isset_offset;
} relopt_parse_elt;
/* Local reloption definition */
@@ -195,6 +190,8 @@ typedef struct local_relopts
extern relopt_kind add_reloption_kind(void);
extern void add_bool_reloption(bits32 kinds, const char *name, const char *desc,
bool default_val, LOCKMODE lockmode);
+extern void add_ternary_reloption(bits32 kinds, const char *name,
+ const char *desc, int default_val, LOCKMODE lockmode);
extern void add_int_reloption(bits32 kinds, const char *name, const char *desc,
int default_val, int min_val, int max_val,
LOCKMODE lockmode);
@@ -214,6 +211,9 @@ extern void register_reloptions_validator(local_relopts *relopts,
extern void add_local_bool_reloption(local_relopts *relopts, const char *name,
const char *desc, bool default_val,
int offset);
+extern void add_local_ternary_reloption(local_relopts *relopts,
+ const char *name, const char *desc,
+ ternary default_val, int offset);
extern void add_local_int_reloption(local_relopts *relopts, const char *name,
const char *desc, int default_val,
int min_val, int max_val, int offset);
diff --git a/src/include/c.h b/src/include/c.h
index 39022f8a9d..9c59ccccf8 100644
--- a/src/include/c.h
+++ b/src/include/c.h
@@ -488,6 +488,22 @@ typedef void (*pg_funcptr_t) (void);
#include <stdbool.h>
+/*
+ * ternary
+ * Boolean value with an extrea "unset" option
+ *
+ * Ternary data type is used in relation options that can be "true", "false" or
+ * "unset". Since relation options are used deep inside the PostgreSQL code,
+ * this type is declared globally.
+*/
+
+typedef enum ternary
+{
+ TERNARY_FALSE = 0,
+ TERNARY_TRUE = 1,
+ TERNARY_UNSET = -1
+} ternary;
+
/* ----------------------------------------------------------------
* Section 3: standard system types
diff --git a/src/include/utils/rel.h b/src/include/utils/rel.h
index b552359915..7346d618f9 100644
--- a/src/include/utils/rel.h
+++ b/src/include/utils/rel.h
@@ -346,8 +346,7 @@ typedef struct StdRdOptions
bool user_catalog_table; /* use as an additional catalog relation */
int parallel_workers; /* max number of parallel workers */
StdRdOptIndexCleanup vacuum_index_cleanup; /* controls index vacuuming */
- bool vacuum_truncate; /* enables vacuum to truncate a relation */
- bool vacuum_truncate_set; /* whether vacuum_truncate is set */
+ ternary vacuum_truncate; /* enables vacuum to truncate a relation */
/*
* Fraction of pages in a relation that vacuum can eagerly scan and fail
--
2.39.2
--nextPart4155600.3ZeAukHxDK
Content-Disposition: attachment;
filename="v2a-0003-Add-alias-to-be-used-as-unset-state.patch"
Content-Transfer-Encoding: 7Bit
Content-Type: text/x-patch; charset="unicode-2-0-utf-8";
name="v2a-0003-Add-alias-to-be-used-as-unset-state.patch"
^ permalink raw reply [nested|flat] 868+ messages in thread
* [PATCH v3] Introduce ternary reloptions
@ 2026-01-16 15:04 Álvaro Herrera <[email protected]>
0 siblings, 0 replies; 868+ messages in thread
From: Álvaro Herrera @ 2026-01-16 15:04 UTC (permalink / raw)
Introduce ternary reloption as a replacement for current
`vacuum_truncate` implementation. Remove the `vacuum_truncate_set`
separate flag and use TERNARY_UNSET instead.
This could also be used for other options such as `vacuum_index_cleanup`
and `buffering`, but lets get the scaffolding in first.
Discussion: https://postgr.es/m/3474141.usfYGdeWWP@thinkpad-pgpro
---
src/backend/access/common/reloptions.c | 137 ++++++++++++++++++-----
src/backend/commands/vacuum.c | 4 +-
src/include/access/reloptions.h | 27 ++---
src/include/postgres.h | 15 +++
src/include/utils/rel.h | 3 +-
src/test/regress/expected/reloptions.out | 36 ++++++
src/test/regress/sql/reloptions.sql | 21 ++++
src/tools/pgindent/typedefs.list | 2 +
8 files changed, 202 insertions(+), 43 deletions(-)
diff --git a/src/backend/access/common/reloptions.c b/src/backend/access/common/reloptions.c
index 0b83f98ed5f..6f1f577581a 100644
--- a/src/backend/access/common/reloptions.c
+++ b/src/backend/access/common/reloptions.c
@@ -40,9 +40,9 @@
*
* To add an option:
*
- * (i) decide on a type (bool, integer, real, enum, string), name, default
- * value, upper and lower bounds (if applicable); for strings, consider a
- * validation routine.
+ * (i) decide on a type (bool, ternary, integer, real, enum, string), name,
+ * default value, upper and lower bounds (if applicable); for strings,
+ * consider a validation routine.
* (ii) add a record below (or use add_<type>_reloption).
* (iii) add it to the appropriate options struct (perhaps StdRdOptions)
* (iv) add it to the appropriate handling routine (perhaps
@@ -147,15 +147,6 @@ static relopt_bool boolRelOpts[] =
},
false
},
- {
- {
- "vacuum_truncate",
- "Enables vacuum to truncate empty pages at the end of this table",
- RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
- ShareUpdateExclusiveLock
- },
- true
- },
{
{
"deduplicate_items",
@@ -170,6 +161,25 @@ static relopt_bool boolRelOpts[] =
{{NULL}}
};
+static relopt_ternary ternaryRelOpts[] =
+{
+ {
+ {
+ "vacuum_truncate",
+ "Enables vacuum to truncate empty pages at the end of this table",
+ RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
+ ShareUpdateExclusiveLock
+ },
+ TERNARY_UNSET
+ },
+ /* list terminator */
+ {
+ {
+ NULL
+ }
+ }
+};
+
static relopt_int intRelOpts[] =
{
{
@@ -609,6 +619,13 @@ initialize_reloptions(void)
boolRelOpts[i].gen.lockmode));
j++;
}
+ for (i = 0; ternaryRelOpts[i].gen.name; i++)
+ {
+ Assert(DoLockModesConflict(ternaryRelOpts[i].gen.lockmode,
+ ternaryRelOpts[i].gen.lockmode));
+ j++;
+ }
+
for (i = 0; intRelOpts[i].gen.name; i++)
{
Assert(DoLockModesConflict(intRelOpts[i].gen.lockmode,
@@ -649,6 +666,14 @@ initialize_reloptions(void)
j++;
}
+ for (i = 0; ternaryRelOpts[i].gen.name; i++)
+ {
+ relOpts[j] = &ternaryRelOpts[i].gen;
+ relOpts[j]->type = RELOPT_TYPE_TERNARY;
+ relOpts[j]->namelen = strlen(relOpts[j]->name);
+ j++;
+ }
+
for (i = 0; intRelOpts[i].gen.name; i++)
{
relOpts[j] = &intRelOpts[i].gen;
@@ -809,6 +834,9 @@ allocate_reloption(bits32 kinds, int type, const char *name, const char *desc,
case RELOPT_TYPE_BOOL:
size = sizeof(relopt_bool);
break;
+ case RELOPT_TYPE_TERNARY:
+ size = sizeof(relopt_ternary);
+ break;
case RELOPT_TYPE_INT:
size = sizeof(relopt_int);
break;
@@ -892,6 +920,57 @@ add_local_bool_reloption(local_relopts *relopts, const char *name,
add_local_reloption(relopts, (relopt_gen *) newoption, offset);
}
+/*
+ * init_ternary_reloption
+ * Allocate and initialize a new ternary reloption
+ */
+static relopt_ternary *
+init_ternary_reloption(bits32 kinds, const char *name, const char *desc,
+ pg_ternary default_val, LOCKMODE lockmode)
+{
+ relopt_ternary *newoption;
+
+ newoption = (relopt_ternary *)
+ allocate_reloption(kinds, RELOPT_TYPE_TERNARY, name, desc, lockmode);
+ newoption->default_val = default_val;
+
+ return newoption;
+}
+
+/*
+ * add_ternary_reloption
+ * Add a new ternary reloption
+ */
+void
+add_ternary_reloption(bits32 kinds, const char *name, const char *desc,
+ pg_ternary default_val, LOCKMODE lockmode)
+{
+ relopt_ternary *newoption;
+
+ newoption =
+ init_ternary_reloption(kinds, name, desc, default_val, lockmode);
+
+ add_reloption((relopt_gen *) newoption);
+}
+
+/*
+ * add_local_ternary_reloption
+ * Add a new ternary local reloption
+ *
+ * 'offset' is offset of ternary-typed field.
+ */
+void
+add_local_ternary_reloption(local_relopts *relopts, const char *name,
+ const char *desc, pg_ternary default_val,
+ int offset)
+{
+ relopt_ternary *newoption;
+
+ newoption =
+ init_ternary_reloption(RELOPT_KIND_LOCAL, name, desc, default_val, 0);
+
+ add_local_reloption(relopts, (relopt_gen *) newoption, offset);
+}
/*
* init_real_reloption
@@ -1626,6 +1705,19 @@ parse_one_reloption(relopt_value *option, char *text_str, int text_len,
option->gen->name, value)));
}
break;
+ case RELOPT_TYPE_TERNARY:
+ {
+ bool b;
+
+ parsed = parse_bool(value, &b);
+ option->values.ternary_val = b ? TERNARY_TRUE : TERNARY_FALSE;
+ if (validate && !parsed)
+ ereport(ERROR,
+ (errcode(ERRCODE_INVALID_PARAMETER_VALUE),
+ errmsg("invalid value for ternary option \"%s\": %s",
+ option->gen->name, value)));
+ }
+ break;
case RELOPT_TYPE_INT:
{
relopt_int *optint = (relopt_int *) option->gen;
@@ -1789,17 +1881,6 @@ fillRelOptions(void *rdopts, Size basesize,
char *itempos = ((char *) rdopts) + elems[j].offset;
char *string_val;
- /*
- * If isset_offset is provided, store whether the reloption is
- * set there.
- */
- if (elems[j].isset_offset > 0)
- {
- char *setpos = ((char *) rdopts) + elems[j].isset_offset;
-
- *(bool *) setpos = options[i].isset;
- }
-
switch (options[i].gen->type)
{
case RELOPT_TYPE_BOOL:
@@ -1807,6 +1888,11 @@ fillRelOptions(void *rdopts, Size basesize,
options[i].values.bool_val :
((relopt_bool *) options[i].gen)->default_val;
break;
+ case RELOPT_TYPE_TERNARY:
+ *(pg_ternary *) itempos = options[i].isset ?
+ options[i].values.ternary_val :
+ ((relopt_ternary *) options[i].gen)->default_val;
+ break;
case RELOPT_TYPE_INT:
*(int *) itempos = options[i].isset ?
options[i].values.int_val :
@@ -1923,8 +2009,8 @@ default_reloptions(Datum reloptions, bool validate, relopt_kind kind)
offsetof(StdRdOptions, parallel_workers)},
{"vacuum_index_cleanup", RELOPT_TYPE_ENUM,
offsetof(StdRdOptions, vacuum_index_cleanup)},
- {"vacuum_truncate", RELOPT_TYPE_BOOL,
- offsetof(StdRdOptions, vacuum_truncate), offsetof(StdRdOptions, vacuum_truncate_set)},
+ {"vacuum_truncate", RELOPT_TYPE_TERNARY,
+ offsetof(StdRdOptions, vacuum_truncate)},
{"vacuum_max_eager_freeze_failure_rate", RELOPT_TYPE_REAL,
offsetof(StdRdOptions, vacuum_max_eager_freeze_failure_rate)}
};
@@ -2004,7 +2090,6 @@ build_local_reloptions(local_relopts *relopts, Datum options, bool validate)
elems[i].optname = opt->option->name;
elems[i].opttype = opt->option->type;
elems[i].offset = opt->offset;
- elems[i].isset_offset = 0; /* not supported for local relopts yet */
i++;
}
diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c
index aa4fbec143f..696eab9bd97 100644
--- a/src/backend/commands/vacuum.c
+++ b/src/backend/commands/vacuum.c
@@ -2224,9 +2224,9 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams params,
{
StdRdOptions *opts = (StdRdOptions *) rel->rd_options;
- if (opts && opts->vacuum_truncate_set)
+ if (opts && opts->vacuum_truncate != TERNARY_UNSET)
{
- if (opts->vacuum_truncate)
+ if (opts->vacuum_truncate == TERNARY_TRUE)
params.truncate = VACOPTVALUE_ENABLED;
else
params.truncate = VACOPTVALUE_DISABLED;
diff --git a/src/include/access/reloptions.h b/src/include/access/reloptions.h
index 2f08e1b0cf0..dfbef2babf2 100644
--- a/src/include/access/reloptions.h
+++ b/src/include/access/reloptions.h
@@ -29,6 +29,7 @@
typedef enum relopt_type
{
RELOPT_TYPE_BOOL,
+ RELOPT_TYPE_TERNARY, /* on, off, unset */
RELOPT_TYPE_INT,
RELOPT_TYPE_REAL,
RELOPT_TYPE_ENUM,
@@ -80,6 +81,7 @@ typedef struct relopt_value
union
{
bool bool_val;
+ pg_ternary ternary_val;
int int_val;
double real_val;
int enum_val;
@@ -94,6 +96,12 @@ typedef struct relopt_bool
bool default_val;
} relopt_bool;
+typedef struct relopt_ternary
+{
+ relopt_gen gen;
+ int default_val;
+} relopt_ternary;
+
typedef struct relopt_int
{
relopt_gen gen;
@@ -152,19 +160,6 @@ typedef struct
const char *optname; /* option's name */
relopt_type opttype; /* option's datatype */
int offset; /* offset of field in result struct */
-
- /*
- * isset_offset is an optional offset of a field in the result struct that
- * stores whether the option is explicitly set for the relation or if it
- * just picked up the default value. In most cases, this can be
- * accomplished by giving the reloption a special out-of-range default
- * value (e.g., some integer reloptions use -2), but this isn't always
- * possible. For example, a Boolean reloption cannot be given an
- * out-of-range default, so we need another way to discover the source of
- * its value. This offset is only used if given a value greater than
- * zero.
- */
- int isset_offset;
} relopt_parse_elt;
/* Local reloption definition */
@@ -195,6 +190,9 @@ typedef struct local_relopts
extern relopt_kind add_reloption_kind(void);
extern void add_bool_reloption(bits32 kinds, const char *name, const char *desc,
bool default_val, LOCKMODE lockmode);
+extern void add_ternary_reloption(bits32 kinds, const char *name,
+ const char *desc, pg_ternary default_val,
+ LOCKMODE lockmode);
extern void add_int_reloption(bits32 kinds, const char *name, const char *desc,
int default_val, int min_val, int max_val,
LOCKMODE lockmode);
@@ -214,6 +212,9 @@ extern void register_reloptions_validator(local_relopts *relopts,
extern void add_local_bool_reloption(local_relopts *relopts, const char *name,
const char *desc, bool default_val,
int offset);
+extern void add_local_ternary_reloption(local_relopts *relopts,
+ const char *name, const char *desc,
+ pg_ternary default_val, int offset);
extern void add_local_int_reloption(local_relopts *relopts, const char *name,
const char *desc, int default_val,
int min_val, int max_val, int offset);
diff --git a/src/include/postgres.h b/src/include/postgres.h
index 7d93fbce709..47c88780776 100644
--- a/src/include/postgres.h
+++ b/src/include/postgres.h
@@ -543,6 +543,21 @@ Float8GetDatum(float8 X)
* ----------------------------------------------------------------
*/
+/*
+ * pg_ternary
+ * Boolean value with an extra "unset" value
+ *
+ * This enum can be used for values that want to distinguish between true,
+ * false, and unset.
+*/
+
+typedef enum pg_ternary
+{
+ TERNARY_FALSE = 0,
+ TERNARY_TRUE = 1,
+ TERNARY_UNSET = -1
+} pg_ternary;
+
/*
* NON_EXEC_STATIC: It's sometimes useful to define a variable or function
* that is normally static but extern when using EXEC_BACKEND (see
diff --git a/src/include/utils/rel.h b/src/include/utils/rel.h
index d03ab247788..5feb18a5373 100644
--- a/src/include/utils/rel.h
+++ b/src/include/utils/rel.h
@@ -347,8 +347,7 @@ typedef struct StdRdOptions
bool user_catalog_table; /* use as an additional catalog relation */
int parallel_workers; /* max number of parallel workers */
StdRdOptIndexCleanup vacuum_index_cleanup; /* controls index vacuuming */
- bool vacuum_truncate; /* enables vacuum to truncate a relation */
- bool vacuum_truncate_set; /* whether vacuum_truncate is set */
+ pg_ternary vacuum_truncate; /* enables vacuum to truncate a relation */
/*
* Fraction of pages in a relation that vacuum can eagerly scan and fail
diff --git a/src/test/regress/expected/reloptions.out b/src/test/regress/expected/reloptions.out
index 9de19b4e3f1..1c99f79ab01 100644
--- a/src/test/regress/expected/reloptions.out
+++ b/src/test/regress/expected/reloptions.out
@@ -98,6 +98,42 @@ SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass;
{fillfactor=13,autovacuum_enabled=false}
(1 row)
+-- Tests for future (FIXME) ternary options
+-- behave as boolean option: accept unassigned name and truncated value
+DROP TABLE reloptions_test;
+CREATE TABLE reloptions_test(i INT) WITH (vacuum_truncate);
+SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass;
+ reloptions
+------------------------
+ {vacuum_truncate=true}
+(1 row)
+
+DROP TABLE reloptions_test;
+CREATE TABLE reloptions_test(i INT) WITH (vacuum_truncate=FaLS);
+SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass;
+ reloptions
+------------------------
+ {vacuum_truncate=fals}
+(1 row)
+
+-- preferred "true" alias is used when storing
+DROP TABLE reloptions_test;
+CREATE TABLE reloptions_test(i INT) WITH (vacuum_index_cleanup=on);
+SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass;
+ reloptions
+---------------------------
+ {vacuum_index_cleanup=on}
+(1 row)
+
+-- custom "third" value is available
+DROP TABLE reloptions_test;
+CREATE TABLE reloptions_test(i INT) WITH (vacuum_index_cleanup=auto);
+SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass;
+ reloptions
+-----------------------------
+ {vacuum_index_cleanup=auto}
+(1 row)
+
-- Test vacuum_truncate option
DROP TABLE reloptions_test;
CREATE TEMP TABLE reloptions_test(i INT NOT NULL, j text)
diff --git a/src/test/regress/sql/reloptions.sql b/src/test/regress/sql/reloptions.sql
index 24fbe0b478d..f5980dafcbc 100644
--- a/src/test/regress/sql/reloptions.sql
+++ b/src/test/regress/sql/reloptions.sql
@@ -59,6 +59,27 @@ UPDATE pg_class
ALTER TABLE reloptions_test RESET (illegal_option);
SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass;
+-- Tests for future (FIXME) ternary options
+
+-- behave as boolean option: accept unassigned name and truncated value
+DROP TABLE reloptions_test;
+CREATE TABLE reloptions_test(i INT) WITH (vacuum_truncate);
+SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass;
+
+DROP TABLE reloptions_test;
+CREATE TABLE reloptions_test(i INT) WITH (vacuum_truncate=FaLS);
+SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass;
+
+-- preferred "true" alias is used when storing
+DROP TABLE reloptions_test;
+CREATE TABLE reloptions_test(i INT) WITH (vacuum_index_cleanup=on);
+SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass;
+
+-- custom "third" value is available
+DROP TABLE reloptions_test;
+CREATE TABLE reloptions_test(i INT) WITH (vacuum_index_cleanup=auto);
+SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass;
+
-- Test vacuum_truncate option
DROP TABLE reloptions_test;
diff --git a/src/tools/pgindent/typedefs.list b/src/tools/pgindent/typedefs.list
index 3f3a888fd0e..1c8610fd46c 100644
--- a/src/tools/pgindent/typedefs.list
+++ b/src/tools/pgindent/typedefs.list
@@ -3952,6 +3952,7 @@ pg_sha512_ctx
pg_snapshot
pg_special_case
pg_stack_base_t
+pg_ternary
pg_time_t
pg_time_usec_t
pg_tz
@@ -4079,6 +4080,7 @@ relopt_kind
relopt_parse_elt
relopt_real
relopt_string
+relopt_ternary
relopt_type
relopt_value
relopts_validator
--
2.47.3
--c36t3fndxhkzbl5z--
^ permalink raw reply [nested|flat] 868+ messages in thread
* [PATCH v3] Introduce ternary reloptions
@ 2026-01-16 15:04 Álvaro Herrera <[email protected]>
0 siblings, 0 replies; 868+ messages in thread
From: Álvaro Herrera @ 2026-01-16 15:04 UTC (permalink / raw)
Introduce ternary reloption as a replacement for current
`vacuum_truncate` implementation. Remove the `vacuum_truncate_set`
separate flag and use TERNARY_UNSET instead.
This could also be used for other options such as `vacuum_index_cleanup`
and `buffering`, but lets get the scaffolding in first.
Discussion: https://postgr.es/m/3474141.usfYGdeWWP@thinkpad-pgpro
---
src/backend/access/common/reloptions.c | 137 ++++++++++++++++++-----
src/backend/commands/vacuum.c | 4 +-
src/include/access/reloptions.h | 27 ++---
src/include/postgres.h | 15 +++
src/include/utils/rel.h | 3 +-
src/test/regress/expected/reloptions.out | 36 ++++++
src/test/regress/sql/reloptions.sql | 21 ++++
src/tools/pgindent/typedefs.list | 2 +
8 files changed, 202 insertions(+), 43 deletions(-)
diff --git a/src/backend/access/common/reloptions.c b/src/backend/access/common/reloptions.c
index 0b83f98ed5f..6f1f577581a 100644
--- a/src/backend/access/common/reloptions.c
+++ b/src/backend/access/common/reloptions.c
@@ -40,9 +40,9 @@
*
* To add an option:
*
- * (i) decide on a type (bool, integer, real, enum, string), name, default
- * value, upper and lower bounds (if applicable); for strings, consider a
- * validation routine.
+ * (i) decide on a type (bool, ternary, integer, real, enum, string), name,
+ * default value, upper and lower bounds (if applicable); for strings,
+ * consider a validation routine.
* (ii) add a record below (or use add_<type>_reloption).
* (iii) add it to the appropriate options struct (perhaps StdRdOptions)
* (iv) add it to the appropriate handling routine (perhaps
@@ -147,15 +147,6 @@ static relopt_bool boolRelOpts[] =
},
false
},
- {
- {
- "vacuum_truncate",
- "Enables vacuum to truncate empty pages at the end of this table",
- RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
- ShareUpdateExclusiveLock
- },
- true
- },
{
{
"deduplicate_items",
@@ -170,6 +161,25 @@ static relopt_bool boolRelOpts[] =
{{NULL}}
};
+static relopt_ternary ternaryRelOpts[] =
+{
+ {
+ {
+ "vacuum_truncate",
+ "Enables vacuum to truncate empty pages at the end of this table",
+ RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
+ ShareUpdateExclusiveLock
+ },
+ TERNARY_UNSET
+ },
+ /* list terminator */
+ {
+ {
+ NULL
+ }
+ }
+};
+
static relopt_int intRelOpts[] =
{
{
@@ -609,6 +619,13 @@ initialize_reloptions(void)
boolRelOpts[i].gen.lockmode));
j++;
}
+ for (i = 0; ternaryRelOpts[i].gen.name; i++)
+ {
+ Assert(DoLockModesConflict(ternaryRelOpts[i].gen.lockmode,
+ ternaryRelOpts[i].gen.lockmode));
+ j++;
+ }
+
for (i = 0; intRelOpts[i].gen.name; i++)
{
Assert(DoLockModesConflict(intRelOpts[i].gen.lockmode,
@@ -649,6 +666,14 @@ initialize_reloptions(void)
j++;
}
+ for (i = 0; ternaryRelOpts[i].gen.name; i++)
+ {
+ relOpts[j] = &ternaryRelOpts[i].gen;
+ relOpts[j]->type = RELOPT_TYPE_TERNARY;
+ relOpts[j]->namelen = strlen(relOpts[j]->name);
+ j++;
+ }
+
for (i = 0; intRelOpts[i].gen.name; i++)
{
relOpts[j] = &intRelOpts[i].gen;
@@ -809,6 +834,9 @@ allocate_reloption(bits32 kinds, int type, const char *name, const char *desc,
case RELOPT_TYPE_BOOL:
size = sizeof(relopt_bool);
break;
+ case RELOPT_TYPE_TERNARY:
+ size = sizeof(relopt_ternary);
+ break;
case RELOPT_TYPE_INT:
size = sizeof(relopt_int);
break;
@@ -892,6 +920,57 @@ add_local_bool_reloption(local_relopts *relopts, const char *name,
add_local_reloption(relopts, (relopt_gen *) newoption, offset);
}
+/*
+ * init_ternary_reloption
+ * Allocate and initialize a new ternary reloption
+ */
+static relopt_ternary *
+init_ternary_reloption(bits32 kinds, const char *name, const char *desc,
+ pg_ternary default_val, LOCKMODE lockmode)
+{
+ relopt_ternary *newoption;
+
+ newoption = (relopt_ternary *)
+ allocate_reloption(kinds, RELOPT_TYPE_TERNARY, name, desc, lockmode);
+ newoption->default_val = default_val;
+
+ return newoption;
+}
+
+/*
+ * add_ternary_reloption
+ * Add a new ternary reloption
+ */
+void
+add_ternary_reloption(bits32 kinds, const char *name, const char *desc,
+ pg_ternary default_val, LOCKMODE lockmode)
+{
+ relopt_ternary *newoption;
+
+ newoption =
+ init_ternary_reloption(kinds, name, desc, default_val, lockmode);
+
+ add_reloption((relopt_gen *) newoption);
+}
+
+/*
+ * add_local_ternary_reloption
+ * Add a new ternary local reloption
+ *
+ * 'offset' is offset of ternary-typed field.
+ */
+void
+add_local_ternary_reloption(local_relopts *relopts, const char *name,
+ const char *desc, pg_ternary default_val,
+ int offset)
+{
+ relopt_ternary *newoption;
+
+ newoption =
+ init_ternary_reloption(RELOPT_KIND_LOCAL, name, desc, default_val, 0);
+
+ add_local_reloption(relopts, (relopt_gen *) newoption, offset);
+}
/*
* init_real_reloption
@@ -1626,6 +1705,19 @@ parse_one_reloption(relopt_value *option, char *text_str, int text_len,
option->gen->name, value)));
}
break;
+ case RELOPT_TYPE_TERNARY:
+ {
+ bool b;
+
+ parsed = parse_bool(value, &b);
+ option->values.ternary_val = b ? TERNARY_TRUE : TERNARY_FALSE;
+ if (validate && !parsed)
+ ereport(ERROR,
+ (errcode(ERRCODE_INVALID_PARAMETER_VALUE),
+ errmsg("invalid value for ternary option \"%s\": %s",
+ option->gen->name, value)));
+ }
+ break;
case RELOPT_TYPE_INT:
{
relopt_int *optint = (relopt_int *) option->gen;
@@ -1789,17 +1881,6 @@ fillRelOptions(void *rdopts, Size basesize,
char *itempos = ((char *) rdopts) + elems[j].offset;
char *string_val;
- /*
- * If isset_offset is provided, store whether the reloption is
- * set there.
- */
- if (elems[j].isset_offset > 0)
- {
- char *setpos = ((char *) rdopts) + elems[j].isset_offset;
-
- *(bool *) setpos = options[i].isset;
- }
-
switch (options[i].gen->type)
{
case RELOPT_TYPE_BOOL:
@@ -1807,6 +1888,11 @@ fillRelOptions(void *rdopts, Size basesize,
options[i].values.bool_val :
((relopt_bool *) options[i].gen)->default_val;
break;
+ case RELOPT_TYPE_TERNARY:
+ *(pg_ternary *) itempos = options[i].isset ?
+ options[i].values.ternary_val :
+ ((relopt_ternary *) options[i].gen)->default_val;
+ break;
case RELOPT_TYPE_INT:
*(int *) itempos = options[i].isset ?
options[i].values.int_val :
@@ -1923,8 +2009,8 @@ default_reloptions(Datum reloptions, bool validate, relopt_kind kind)
offsetof(StdRdOptions, parallel_workers)},
{"vacuum_index_cleanup", RELOPT_TYPE_ENUM,
offsetof(StdRdOptions, vacuum_index_cleanup)},
- {"vacuum_truncate", RELOPT_TYPE_BOOL,
- offsetof(StdRdOptions, vacuum_truncate), offsetof(StdRdOptions, vacuum_truncate_set)},
+ {"vacuum_truncate", RELOPT_TYPE_TERNARY,
+ offsetof(StdRdOptions, vacuum_truncate)},
{"vacuum_max_eager_freeze_failure_rate", RELOPT_TYPE_REAL,
offsetof(StdRdOptions, vacuum_max_eager_freeze_failure_rate)}
};
@@ -2004,7 +2090,6 @@ build_local_reloptions(local_relopts *relopts, Datum options, bool validate)
elems[i].optname = opt->option->name;
elems[i].opttype = opt->option->type;
elems[i].offset = opt->offset;
- elems[i].isset_offset = 0; /* not supported for local relopts yet */
i++;
}
diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c
index aa4fbec143f..696eab9bd97 100644
--- a/src/backend/commands/vacuum.c
+++ b/src/backend/commands/vacuum.c
@@ -2224,9 +2224,9 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams params,
{
StdRdOptions *opts = (StdRdOptions *) rel->rd_options;
- if (opts && opts->vacuum_truncate_set)
+ if (opts && opts->vacuum_truncate != TERNARY_UNSET)
{
- if (opts->vacuum_truncate)
+ if (opts->vacuum_truncate == TERNARY_TRUE)
params.truncate = VACOPTVALUE_ENABLED;
else
params.truncate = VACOPTVALUE_DISABLED;
diff --git a/src/include/access/reloptions.h b/src/include/access/reloptions.h
index 2f08e1b0cf0..dfbef2babf2 100644
--- a/src/include/access/reloptions.h
+++ b/src/include/access/reloptions.h
@@ -29,6 +29,7 @@
typedef enum relopt_type
{
RELOPT_TYPE_BOOL,
+ RELOPT_TYPE_TERNARY, /* on, off, unset */
RELOPT_TYPE_INT,
RELOPT_TYPE_REAL,
RELOPT_TYPE_ENUM,
@@ -80,6 +81,7 @@ typedef struct relopt_value
union
{
bool bool_val;
+ pg_ternary ternary_val;
int int_val;
double real_val;
int enum_val;
@@ -94,6 +96,12 @@ typedef struct relopt_bool
bool default_val;
} relopt_bool;
+typedef struct relopt_ternary
+{
+ relopt_gen gen;
+ int default_val;
+} relopt_ternary;
+
typedef struct relopt_int
{
relopt_gen gen;
@@ -152,19 +160,6 @@ typedef struct
const char *optname; /* option's name */
relopt_type opttype; /* option's datatype */
int offset; /* offset of field in result struct */
-
- /*
- * isset_offset is an optional offset of a field in the result struct that
- * stores whether the option is explicitly set for the relation or if it
- * just picked up the default value. In most cases, this can be
- * accomplished by giving the reloption a special out-of-range default
- * value (e.g., some integer reloptions use -2), but this isn't always
- * possible. For example, a Boolean reloption cannot be given an
- * out-of-range default, so we need another way to discover the source of
- * its value. This offset is only used if given a value greater than
- * zero.
- */
- int isset_offset;
} relopt_parse_elt;
/* Local reloption definition */
@@ -195,6 +190,9 @@ typedef struct local_relopts
extern relopt_kind add_reloption_kind(void);
extern void add_bool_reloption(bits32 kinds, const char *name, const char *desc,
bool default_val, LOCKMODE lockmode);
+extern void add_ternary_reloption(bits32 kinds, const char *name,
+ const char *desc, pg_ternary default_val,
+ LOCKMODE lockmode);
extern void add_int_reloption(bits32 kinds, const char *name, const char *desc,
int default_val, int min_val, int max_val,
LOCKMODE lockmode);
@@ -214,6 +212,9 @@ extern void register_reloptions_validator(local_relopts *relopts,
extern void add_local_bool_reloption(local_relopts *relopts, const char *name,
const char *desc, bool default_val,
int offset);
+extern void add_local_ternary_reloption(local_relopts *relopts,
+ const char *name, const char *desc,
+ pg_ternary default_val, int offset);
extern void add_local_int_reloption(local_relopts *relopts, const char *name,
const char *desc, int default_val,
int min_val, int max_val, int offset);
diff --git a/src/include/postgres.h b/src/include/postgres.h
index 7d93fbce709..47c88780776 100644
--- a/src/include/postgres.h
+++ b/src/include/postgres.h
@@ -543,6 +543,21 @@ Float8GetDatum(float8 X)
* ----------------------------------------------------------------
*/
+/*
+ * pg_ternary
+ * Boolean value with an extra "unset" value
+ *
+ * This enum can be used for values that want to distinguish between true,
+ * false, and unset.
+*/
+
+typedef enum pg_ternary
+{
+ TERNARY_FALSE = 0,
+ TERNARY_TRUE = 1,
+ TERNARY_UNSET = -1
+} pg_ternary;
+
/*
* NON_EXEC_STATIC: It's sometimes useful to define a variable or function
* that is normally static but extern when using EXEC_BACKEND (see
diff --git a/src/include/utils/rel.h b/src/include/utils/rel.h
index d03ab247788..5feb18a5373 100644
--- a/src/include/utils/rel.h
+++ b/src/include/utils/rel.h
@@ -347,8 +347,7 @@ typedef struct StdRdOptions
bool user_catalog_table; /* use as an additional catalog relation */
int parallel_workers; /* max number of parallel workers */
StdRdOptIndexCleanup vacuum_index_cleanup; /* controls index vacuuming */
- bool vacuum_truncate; /* enables vacuum to truncate a relation */
- bool vacuum_truncate_set; /* whether vacuum_truncate is set */
+ pg_ternary vacuum_truncate; /* enables vacuum to truncate a relation */
/*
* Fraction of pages in a relation that vacuum can eagerly scan and fail
diff --git a/src/test/regress/expected/reloptions.out b/src/test/regress/expected/reloptions.out
index 9de19b4e3f1..1c99f79ab01 100644
--- a/src/test/regress/expected/reloptions.out
+++ b/src/test/regress/expected/reloptions.out
@@ -98,6 +98,42 @@ SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass;
{fillfactor=13,autovacuum_enabled=false}
(1 row)
+-- Tests for future (FIXME) ternary options
+-- behave as boolean option: accept unassigned name and truncated value
+DROP TABLE reloptions_test;
+CREATE TABLE reloptions_test(i INT) WITH (vacuum_truncate);
+SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass;
+ reloptions
+------------------------
+ {vacuum_truncate=true}
+(1 row)
+
+DROP TABLE reloptions_test;
+CREATE TABLE reloptions_test(i INT) WITH (vacuum_truncate=FaLS);
+SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass;
+ reloptions
+------------------------
+ {vacuum_truncate=fals}
+(1 row)
+
+-- preferred "true" alias is used when storing
+DROP TABLE reloptions_test;
+CREATE TABLE reloptions_test(i INT) WITH (vacuum_index_cleanup=on);
+SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass;
+ reloptions
+---------------------------
+ {vacuum_index_cleanup=on}
+(1 row)
+
+-- custom "third" value is available
+DROP TABLE reloptions_test;
+CREATE TABLE reloptions_test(i INT) WITH (vacuum_index_cleanup=auto);
+SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass;
+ reloptions
+-----------------------------
+ {vacuum_index_cleanup=auto}
+(1 row)
+
-- Test vacuum_truncate option
DROP TABLE reloptions_test;
CREATE TEMP TABLE reloptions_test(i INT NOT NULL, j text)
diff --git a/src/test/regress/sql/reloptions.sql b/src/test/regress/sql/reloptions.sql
index 24fbe0b478d..f5980dafcbc 100644
--- a/src/test/regress/sql/reloptions.sql
+++ b/src/test/regress/sql/reloptions.sql
@@ -59,6 +59,27 @@ UPDATE pg_class
ALTER TABLE reloptions_test RESET (illegal_option);
SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass;
+-- Tests for future (FIXME) ternary options
+
+-- behave as boolean option: accept unassigned name and truncated value
+DROP TABLE reloptions_test;
+CREATE TABLE reloptions_test(i INT) WITH (vacuum_truncate);
+SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass;
+
+DROP TABLE reloptions_test;
+CREATE TABLE reloptions_test(i INT) WITH (vacuum_truncate=FaLS);
+SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass;
+
+-- preferred "true" alias is used when storing
+DROP TABLE reloptions_test;
+CREATE TABLE reloptions_test(i INT) WITH (vacuum_index_cleanup=on);
+SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass;
+
+-- custom "third" value is available
+DROP TABLE reloptions_test;
+CREATE TABLE reloptions_test(i INT) WITH (vacuum_index_cleanup=auto);
+SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass;
+
-- Test vacuum_truncate option
DROP TABLE reloptions_test;
diff --git a/src/tools/pgindent/typedefs.list b/src/tools/pgindent/typedefs.list
index 3f3a888fd0e..1c8610fd46c 100644
--- a/src/tools/pgindent/typedefs.list
+++ b/src/tools/pgindent/typedefs.list
@@ -3952,6 +3952,7 @@ pg_sha512_ctx
pg_snapshot
pg_special_case
pg_stack_base_t
+pg_ternary
pg_time_t
pg_time_usec_t
pg_tz
@@ -4079,6 +4080,7 @@ relopt_kind
relopt_parse_elt
relopt_real
relopt_string
+relopt_ternary
relopt_type
relopt_value
relopts_validator
--
2.47.3
--c36t3fndxhkzbl5z--
^ permalink raw reply [nested|flat] 868+ messages in thread
* [PATCH v3] Introduce ternary reloptions
@ 2026-01-16 15:04 Álvaro Herrera <[email protected]>
0 siblings, 0 replies; 868+ messages in thread
From: Álvaro Herrera @ 2026-01-16 15:04 UTC (permalink / raw)
Introduce ternary reloption as a replacement for current
`vacuum_truncate` implementation. Remove the `vacuum_truncate_set`
separate flag and use TERNARY_UNSET instead.
This could also be used for other options such as `vacuum_index_cleanup`
and `buffering`, but lets get the scaffolding in first.
Discussion: https://postgr.es/m/3474141.usfYGdeWWP@thinkpad-pgpro
---
src/backend/access/common/reloptions.c | 137 ++++++++++++++++++-----
src/backend/commands/vacuum.c | 4 +-
src/include/access/reloptions.h | 27 ++---
src/include/postgres.h | 15 +++
src/include/utils/rel.h | 3 +-
src/test/regress/expected/reloptions.out | 36 ++++++
src/test/regress/sql/reloptions.sql | 21 ++++
src/tools/pgindent/typedefs.list | 2 +
8 files changed, 202 insertions(+), 43 deletions(-)
diff --git a/src/backend/access/common/reloptions.c b/src/backend/access/common/reloptions.c
index 0b83f98ed5f..6f1f577581a 100644
--- a/src/backend/access/common/reloptions.c
+++ b/src/backend/access/common/reloptions.c
@@ -40,9 +40,9 @@
*
* To add an option:
*
- * (i) decide on a type (bool, integer, real, enum, string), name, default
- * value, upper and lower bounds (if applicable); for strings, consider a
- * validation routine.
+ * (i) decide on a type (bool, ternary, integer, real, enum, string), name,
+ * default value, upper and lower bounds (if applicable); for strings,
+ * consider a validation routine.
* (ii) add a record below (or use add_<type>_reloption).
* (iii) add it to the appropriate options struct (perhaps StdRdOptions)
* (iv) add it to the appropriate handling routine (perhaps
@@ -147,15 +147,6 @@ static relopt_bool boolRelOpts[] =
},
false
},
- {
- {
- "vacuum_truncate",
- "Enables vacuum to truncate empty pages at the end of this table",
- RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
- ShareUpdateExclusiveLock
- },
- true
- },
{
{
"deduplicate_items",
@@ -170,6 +161,25 @@ static relopt_bool boolRelOpts[] =
{{NULL}}
};
+static relopt_ternary ternaryRelOpts[] =
+{
+ {
+ {
+ "vacuum_truncate",
+ "Enables vacuum to truncate empty pages at the end of this table",
+ RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
+ ShareUpdateExclusiveLock
+ },
+ TERNARY_UNSET
+ },
+ /* list terminator */
+ {
+ {
+ NULL
+ }
+ }
+};
+
static relopt_int intRelOpts[] =
{
{
@@ -609,6 +619,13 @@ initialize_reloptions(void)
boolRelOpts[i].gen.lockmode));
j++;
}
+ for (i = 0; ternaryRelOpts[i].gen.name; i++)
+ {
+ Assert(DoLockModesConflict(ternaryRelOpts[i].gen.lockmode,
+ ternaryRelOpts[i].gen.lockmode));
+ j++;
+ }
+
for (i = 0; intRelOpts[i].gen.name; i++)
{
Assert(DoLockModesConflict(intRelOpts[i].gen.lockmode,
@@ -649,6 +666,14 @@ initialize_reloptions(void)
j++;
}
+ for (i = 0; ternaryRelOpts[i].gen.name; i++)
+ {
+ relOpts[j] = &ternaryRelOpts[i].gen;
+ relOpts[j]->type = RELOPT_TYPE_TERNARY;
+ relOpts[j]->namelen = strlen(relOpts[j]->name);
+ j++;
+ }
+
for (i = 0; intRelOpts[i].gen.name; i++)
{
relOpts[j] = &intRelOpts[i].gen;
@@ -809,6 +834,9 @@ allocate_reloption(bits32 kinds, int type, const char *name, const char *desc,
case RELOPT_TYPE_BOOL:
size = sizeof(relopt_bool);
break;
+ case RELOPT_TYPE_TERNARY:
+ size = sizeof(relopt_ternary);
+ break;
case RELOPT_TYPE_INT:
size = sizeof(relopt_int);
break;
@@ -892,6 +920,57 @@ add_local_bool_reloption(local_relopts *relopts, const char *name,
add_local_reloption(relopts, (relopt_gen *) newoption, offset);
}
+/*
+ * init_ternary_reloption
+ * Allocate and initialize a new ternary reloption
+ */
+static relopt_ternary *
+init_ternary_reloption(bits32 kinds, const char *name, const char *desc,
+ pg_ternary default_val, LOCKMODE lockmode)
+{
+ relopt_ternary *newoption;
+
+ newoption = (relopt_ternary *)
+ allocate_reloption(kinds, RELOPT_TYPE_TERNARY, name, desc, lockmode);
+ newoption->default_val = default_val;
+
+ return newoption;
+}
+
+/*
+ * add_ternary_reloption
+ * Add a new ternary reloption
+ */
+void
+add_ternary_reloption(bits32 kinds, const char *name, const char *desc,
+ pg_ternary default_val, LOCKMODE lockmode)
+{
+ relopt_ternary *newoption;
+
+ newoption =
+ init_ternary_reloption(kinds, name, desc, default_val, lockmode);
+
+ add_reloption((relopt_gen *) newoption);
+}
+
+/*
+ * add_local_ternary_reloption
+ * Add a new ternary local reloption
+ *
+ * 'offset' is offset of ternary-typed field.
+ */
+void
+add_local_ternary_reloption(local_relopts *relopts, const char *name,
+ const char *desc, pg_ternary default_val,
+ int offset)
+{
+ relopt_ternary *newoption;
+
+ newoption =
+ init_ternary_reloption(RELOPT_KIND_LOCAL, name, desc, default_val, 0);
+
+ add_local_reloption(relopts, (relopt_gen *) newoption, offset);
+}
/*
* init_real_reloption
@@ -1626,6 +1705,19 @@ parse_one_reloption(relopt_value *option, char *text_str, int text_len,
option->gen->name, value)));
}
break;
+ case RELOPT_TYPE_TERNARY:
+ {
+ bool b;
+
+ parsed = parse_bool(value, &b);
+ option->values.ternary_val = b ? TERNARY_TRUE : TERNARY_FALSE;
+ if (validate && !parsed)
+ ereport(ERROR,
+ (errcode(ERRCODE_INVALID_PARAMETER_VALUE),
+ errmsg("invalid value for ternary option \"%s\": %s",
+ option->gen->name, value)));
+ }
+ break;
case RELOPT_TYPE_INT:
{
relopt_int *optint = (relopt_int *) option->gen;
@@ -1789,17 +1881,6 @@ fillRelOptions(void *rdopts, Size basesize,
char *itempos = ((char *) rdopts) + elems[j].offset;
char *string_val;
- /*
- * If isset_offset is provided, store whether the reloption is
- * set there.
- */
- if (elems[j].isset_offset > 0)
- {
- char *setpos = ((char *) rdopts) + elems[j].isset_offset;
-
- *(bool *) setpos = options[i].isset;
- }
-
switch (options[i].gen->type)
{
case RELOPT_TYPE_BOOL:
@@ -1807,6 +1888,11 @@ fillRelOptions(void *rdopts, Size basesize,
options[i].values.bool_val :
((relopt_bool *) options[i].gen)->default_val;
break;
+ case RELOPT_TYPE_TERNARY:
+ *(pg_ternary *) itempos = options[i].isset ?
+ options[i].values.ternary_val :
+ ((relopt_ternary *) options[i].gen)->default_val;
+ break;
case RELOPT_TYPE_INT:
*(int *) itempos = options[i].isset ?
options[i].values.int_val :
@@ -1923,8 +2009,8 @@ default_reloptions(Datum reloptions, bool validate, relopt_kind kind)
offsetof(StdRdOptions, parallel_workers)},
{"vacuum_index_cleanup", RELOPT_TYPE_ENUM,
offsetof(StdRdOptions, vacuum_index_cleanup)},
- {"vacuum_truncate", RELOPT_TYPE_BOOL,
- offsetof(StdRdOptions, vacuum_truncate), offsetof(StdRdOptions, vacuum_truncate_set)},
+ {"vacuum_truncate", RELOPT_TYPE_TERNARY,
+ offsetof(StdRdOptions, vacuum_truncate)},
{"vacuum_max_eager_freeze_failure_rate", RELOPT_TYPE_REAL,
offsetof(StdRdOptions, vacuum_max_eager_freeze_failure_rate)}
};
@@ -2004,7 +2090,6 @@ build_local_reloptions(local_relopts *relopts, Datum options, bool validate)
elems[i].optname = opt->option->name;
elems[i].opttype = opt->option->type;
elems[i].offset = opt->offset;
- elems[i].isset_offset = 0; /* not supported for local relopts yet */
i++;
}
diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c
index aa4fbec143f..696eab9bd97 100644
--- a/src/backend/commands/vacuum.c
+++ b/src/backend/commands/vacuum.c
@@ -2224,9 +2224,9 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams params,
{
StdRdOptions *opts = (StdRdOptions *) rel->rd_options;
- if (opts && opts->vacuum_truncate_set)
+ if (opts && opts->vacuum_truncate != TERNARY_UNSET)
{
- if (opts->vacuum_truncate)
+ if (opts->vacuum_truncate == TERNARY_TRUE)
params.truncate = VACOPTVALUE_ENABLED;
else
params.truncate = VACOPTVALUE_DISABLED;
diff --git a/src/include/access/reloptions.h b/src/include/access/reloptions.h
index 2f08e1b0cf0..dfbef2babf2 100644
--- a/src/include/access/reloptions.h
+++ b/src/include/access/reloptions.h
@@ -29,6 +29,7 @@
typedef enum relopt_type
{
RELOPT_TYPE_BOOL,
+ RELOPT_TYPE_TERNARY, /* on, off, unset */
RELOPT_TYPE_INT,
RELOPT_TYPE_REAL,
RELOPT_TYPE_ENUM,
@@ -80,6 +81,7 @@ typedef struct relopt_value
union
{
bool bool_val;
+ pg_ternary ternary_val;
int int_val;
double real_val;
int enum_val;
@@ -94,6 +96,12 @@ typedef struct relopt_bool
bool default_val;
} relopt_bool;
+typedef struct relopt_ternary
+{
+ relopt_gen gen;
+ int default_val;
+} relopt_ternary;
+
typedef struct relopt_int
{
relopt_gen gen;
@@ -152,19 +160,6 @@ typedef struct
const char *optname; /* option's name */
relopt_type opttype; /* option's datatype */
int offset; /* offset of field in result struct */
-
- /*
- * isset_offset is an optional offset of a field in the result struct that
- * stores whether the option is explicitly set for the relation or if it
- * just picked up the default value. In most cases, this can be
- * accomplished by giving the reloption a special out-of-range default
- * value (e.g., some integer reloptions use -2), but this isn't always
- * possible. For example, a Boolean reloption cannot be given an
- * out-of-range default, so we need another way to discover the source of
- * its value. This offset is only used if given a value greater than
- * zero.
- */
- int isset_offset;
} relopt_parse_elt;
/* Local reloption definition */
@@ -195,6 +190,9 @@ typedef struct local_relopts
extern relopt_kind add_reloption_kind(void);
extern void add_bool_reloption(bits32 kinds, const char *name, const char *desc,
bool default_val, LOCKMODE lockmode);
+extern void add_ternary_reloption(bits32 kinds, const char *name,
+ const char *desc, pg_ternary default_val,
+ LOCKMODE lockmode);
extern void add_int_reloption(bits32 kinds, const char *name, const char *desc,
int default_val, int min_val, int max_val,
LOCKMODE lockmode);
@@ -214,6 +212,9 @@ extern void register_reloptions_validator(local_relopts *relopts,
extern void add_local_bool_reloption(local_relopts *relopts, const char *name,
const char *desc, bool default_val,
int offset);
+extern void add_local_ternary_reloption(local_relopts *relopts,
+ const char *name, const char *desc,
+ pg_ternary default_val, int offset);
extern void add_local_int_reloption(local_relopts *relopts, const char *name,
const char *desc, int default_val,
int min_val, int max_val, int offset);
diff --git a/src/include/postgres.h b/src/include/postgres.h
index 7d93fbce709..47c88780776 100644
--- a/src/include/postgres.h
+++ b/src/include/postgres.h
@@ -543,6 +543,21 @@ Float8GetDatum(float8 X)
* ----------------------------------------------------------------
*/
+/*
+ * pg_ternary
+ * Boolean value with an extra "unset" value
+ *
+ * This enum can be used for values that want to distinguish between true,
+ * false, and unset.
+*/
+
+typedef enum pg_ternary
+{
+ TERNARY_FALSE = 0,
+ TERNARY_TRUE = 1,
+ TERNARY_UNSET = -1
+} pg_ternary;
+
/*
* NON_EXEC_STATIC: It's sometimes useful to define a variable or function
* that is normally static but extern when using EXEC_BACKEND (see
diff --git a/src/include/utils/rel.h b/src/include/utils/rel.h
index d03ab247788..5feb18a5373 100644
--- a/src/include/utils/rel.h
+++ b/src/include/utils/rel.h
@@ -347,8 +347,7 @@ typedef struct StdRdOptions
bool user_catalog_table; /* use as an additional catalog relation */
int parallel_workers; /* max number of parallel workers */
StdRdOptIndexCleanup vacuum_index_cleanup; /* controls index vacuuming */
- bool vacuum_truncate; /* enables vacuum to truncate a relation */
- bool vacuum_truncate_set; /* whether vacuum_truncate is set */
+ pg_ternary vacuum_truncate; /* enables vacuum to truncate a relation */
/*
* Fraction of pages in a relation that vacuum can eagerly scan and fail
diff --git a/src/test/regress/expected/reloptions.out b/src/test/regress/expected/reloptions.out
index 9de19b4e3f1..1c99f79ab01 100644
--- a/src/test/regress/expected/reloptions.out
+++ b/src/test/regress/expected/reloptions.out
@@ -98,6 +98,42 @@ SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass;
{fillfactor=13,autovacuum_enabled=false}
(1 row)
+-- Tests for future (FIXME) ternary options
+-- behave as boolean option: accept unassigned name and truncated value
+DROP TABLE reloptions_test;
+CREATE TABLE reloptions_test(i INT) WITH (vacuum_truncate);
+SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass;
+ reloptions
+------------------------
+ {vacuum_truncate=true}
+(1 row)
+
+DROP TABLE reloptions_test;
+CREATE TABLE reloptions_test(i INT) WITH (vacuum_truncate=FaLS);
+SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass;
+ reloptions
+------------------------
+ {vacuum_truncate=fals}
+(1 row)
+
+-- preferred "true" alias is used when storing
+DROP TABLE reloptions_test;
+CREATE TABLE reloptions_test(i INT) WITH (vacuum_index_cleanup=on);
+SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass;
+ reloptions
+---------------------------
+ {vacuum_index_cleanup=on}
+(1 row)
+
+-- custom "third" value is available
+DROP TABLE reloptions_test;
+CREATE TABLE reloptions_test(i INT) WITH (vacuum_index_cleanup=auto);
+SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass;
+ reloptions
+-----------------------------
+ {vacuum_index_cleanup=auto}
+(1 row)
+
-- Test vacuum_truncate option
DROP TABLE reloptions_test;
CREATE TEMP TABLE reloptions_test(i INT NOT NULL, j text)
diff --git a/src/test/regress/sql/reloptions.sql b/src/test/regress/sql/reloptions.sql
index 24fbe0b478d..f5980dafcbc 100644
--- a/src/test/regress/sql/reloptions.sql
+++ b/src/test/regress/sql/reloptions.sql
@@ -59,6 +59,27 @@ UPDATE pg_class
ALTER TABLE reloptions_test RESET (illegal_option);
SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass;
+-- Tests for future (FIXME) ternary options
+
+-- behave as boolean option: accept unassigned name and truncated value
+DROP TABLE reloptions_test;
+CREATE TABLE reloptions_test(i INT) WITH (vacuum_truncate);
+SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass;
+
+DROP TABLE reloptions_test;
+CREATE TABLE reloptions_test(i INT) WITH (vacuum_truncate=FaLS);
+SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass;
+
+-- preferred "true" alias is used when storing
+DROP TABLE reloptions_test;
+CREATE TABLE reloptions_test(i INT) WITH (vacuum_index_cleanup=on);
+SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass;
+
+-- custom "third" value is available
+DROP TABLE reloptions_test;
+CREATE TABLE reloptions_test(i INT) WITH (vacuum_index_cleanup=auto);
+SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass;
+
-- Test vacuum_truncate option
DROP TABLE reloptions_test;
diff --git a/src/tools/pgindent/typedefs.list b/src/tools/pgindent/typedefs.list
index 3f3a888fd0e..1c8610fd46c 100644
--- a/src/tools/pgindent/typedefs.list
+++ b/src/tools/pgindent/typedefs.list
@@ -3952,6 +3952,7 @@ pg_sha512_ctx
pg_snapshot
pg_special_case
pg_stack_base_t
+pg_ternary
pg_time_t
pg_time_usec_t
pg_tz
@@ -4079,6 +4080,7 @@ relopt_kind
relopt_parse_elt
relopt_real
relopt_string
+relopt_ternary
relopt_type
relopt_value
relopts_validator
--
2.47.3
--c36t3fndxhkzbl5z--
^ permalink raw reply [nested|flat] 868+ messages in thread
* [PATCH v3] Introduce ternary reloptions
@ 2026-01-16 15:04 Álvaro Herrera <[email protected]>
0 siblings, 0 replies; 868+ messages in thread
From: Álvaro Herrera @ 2026-01-16 15:04 UTC (permalink / raw)
Introduce ternary reloption as a replacement for current
`vacuum_truncate` implementation. Remove the `vacuum_truncate_set`
separate flag and use TERNARY_UNSET instead.
This could also be used for other options such as `vacuum_index_cleanup`
and `buffering`, but lets get the scaffolding in first.
Discussion: https://postgr.es/m/3474141.usfYGdeWWP@thinkpad-pgpro
---
src/backend/access/common/reloptions.c | 137 ++++++++++++++++++-----
src/backend/commands/vacuum.c | 4 +-
src/include/access/reloptions.h | 27 ++---
src/include/postgres.h | 15 +++
src/include/utils/rel.h | 3 +-
src/test/regress/expected/reloptions.out | 36 ++++++
src/test/regress/sql/reloptions.sql | 21 ++++
src/tools/pgindent/typedefs.list | 2 +
8 files changed, 202 insertions(+), 43 deletions(-)
diff --git a/src/backend/access/common/reloptions.c b/src/backend/access/common/reloptions.c
index 0b83f98ed5f..6f1f577581a 100644
--- a/src/backend/access/common/reloptions.c
+++ b/src/backend/access/common/reloptions.c
@@ -40,9 +40,9 @@
*
* To add an option:
*
- * (i) decide on a type (bool, integer, real, enum, string), name, default
- * value, upper and lower bounds (if applicable); for strings, consider a
- * validation routine.
+ * (i) decide on a type (bool, ternary, integer, real, enum, string), name,
+ * default value, upper and lower bounds (if applicable); for strings,
+ * consider a validation routine.
* (ii) add a record below (or use add_<type>_reloption).
* (iii) add it to the appropriate options struct (perhaps StdRdOptions)
* (iv) add it to the appropriate handling routine (perhaps
@@ -147,15 +147,6 @@ static relopt_bool boolRelOpts[] =
},
false
},
- {
- {
- "vacuum_truncate",
- "Enables vacuum to truncate empty pages at the end of this table",
- RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
- ShareUpdateExclusiveLock
- },
- true
- },
{
{
"deduplicate_items",
@@ -170,6 +161,25 @@ static relopt_bool boolRelOpts[] =
{{NULL}}
};
+static relopt_ternary ternaryRelOpts[] =
+{
+ {
+ {
+ "vacuum_truncate",
+ "Enables vacuum to truncate empty pages at the end of this table",
+ RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
+ ShareUpdateExclusiveLock
+ },
+ TERNARY_UNSET
+ },
+ /* list terminator */
+ {
+ {
+ NULL
+ }
+ }
+};
+
static relopt_int intRelOpts[] =
{
{
@@ -609,6 +619,13 @@ initialize_reloptions(void)
boolRelOpts[i].gen.lockmode));
j++;
}
+ for (i = 0; ternaryRelOpts[i].gen.name; i++)
+ {
+ Assert(DoLockModesConflict(ternaryRelOpts[i].gen.lockmode,
+ ternaryRelOpts[i].gen.lockmode));
+ j++;
+ }
+
for (i = 0; intRelOpts[i].gen.name; i++)
{
Assert(DoLockModesConflict(intRelOpts[i].gen.lockmode,
@@ -649,6 +666,14 @@ initialize_reloptions(void)
j++;
}
+ for (i = 0; ternaryRelOpts[i].gen.name; i++)
+ {
+ relOpts[j] = &ternaryRelOpts[i].gen;
+ relOpts[j]->type = RELOPT_TYPE_TERNARY;
+ relOpts[j]->namelen = strlen(relOpts[j]->name);
+ j++;
+ }
+
for (i = 0; intRelOpts[i].gen.name; i++)
{
relOpts[j] = &intRelOpts[i].gen;
@@ -809,6 +834,9 @@ allocate_reloption(bits32 kinds, int type, const char *name, const char *desc,
case RELOPT_TYPE_BOOL:
size = sizeof(relopt_bool);
break;
+ case RELOPT_TYPE_TERNARY:
+ size = sizeof(relopt_ternary);
+ break;
case RELOPT_TYPE_INT:
size = sizeof(relopt_int);
break;
@@ -892,6 +920,57 @@ add_local_bool_reloption(local_relopts *relopts, const char *name,
add_local_reloption(relopts, (relopt_gen *) newoption, offset);
}
+/*
+ * init_ternary_reloption
+ * Allocate and initialize a new ternary reloption
+ */
+static relopt_ternary *
+init_ternary_reloption(bits32 kinds, const char *name, const char *desc,
+ pg_ternary default_val, LOCKMODE lockmode)
+{
+ relopt_ternary *newoption;
+
+ newoption = (relopt_ternary *)
+ allocate_reloption(kinds, RELOPT_TYPE_TERNARY, name, desc, lockmode);
+ newoption->default_val = default_val;
+
+ return newoption;
+}
+
+/*
+ * add_ternary_reloption
+ * Add a new ternary reloption
+ */
+void
+add_ternary_reloption(bits32 kinds, const char *name, const char *desc,
+ pg_ternary default_val, LOCKMODE lockmode)
+{
+ relopt_ternary *newoption;
+
+ newoption =
+ init_ternary_reloption(kinds, name, desc, default_val, lockmode);
+
+ add_reloption((relopt_gen *) newoption);
+}
+
+/*
+ * add_local_ternary_reloption
+ * Add a new ternary local reloption
+ *
+ * 'offset' is offset of ternary-typed field.
+ */
+void
+add_local_ternary_reloption(local_relopts *relopts, const char *name,
+ const char *desc, pg_ternary default_val,
+ int offset)
+{
+ relopt_ternary *newoption;
+
+ newoption =
+ init_ternary_reloption(RELOPT_KIND_LOCAL, name, desc, default_val, 0);
+
+ add_local_reloption(relopts, (relopt_gen *) newoption, offset);
+}
/*
* init_real_reloption
@@ -1626,6 +1705,19 @@ parse_one_reloption(relopt_value *option, char *text_str, int text_len,
option->gen->name, value)));
}
break;
+ case RELOPT_TYPE_TERNARY:
+ {
+ bool b;
+
+ parsed = parse_bool(value, &b);
+ option->values.ternary_val = b ? TERNARY_TRUE : TERNARY_FALSE;
+ if (validate && !parsed)
+ ereport(ERROR,
+ (errcode(ERRCODE_INVALID_PARAMETER_VALUE),
+ errmsg("invalid value for ternary option \"%s\": %s",
+ option->gen->name, value)));
+ }
+ break;
case RELOPT_TYPE_INT:
{
relopt_int *optint = (relopt_int *) option->gen;
@@ -1789,17 +1881,6 @@ fillRelOptions(void *rdopts, Size basesize,
char *itempos = ((char *) rdopts) + elems[j].offset;
char *string_val;
- /*
- * If isset_offset is provided, store whether the reloption is
- * set there.
- */
- if (elems[j].isset_offset > 0)
- {
- char *setpos = ((char *) rdopts) + elems[j].isset_offset;
-
- *(bool *) setpos = options[i].isset;
- }
-
switch (options[i].gen->type)
{
case RELOPT_TYPE_BOOL:
@@ -1807,6 +1888,11 @@ fillRelOptions(void *rdopts, Size basesize,
options[i].values.bool_val :
((relopt_bool *) options[i].gen)->default_val;
break;
+ case RELOPT_TYPE_TERNARY:
+ *(pg_ternary *) itempos = options[i].isset ?
+ options[i].values.ternary_val :
+ ((relopt_ternary *) options[i].gen)->default_val;
+ break;
case RELOPT_TYPE_INT:
*(int *) itempos = options[i].isset ?
options[i].values.int_val :
@@ -1923,8 +2009,8 @@ default_reloptions(Datum reloptions, bool validate, relopt_kind kind)
offsetof(StdRdOptions, parallel_workers)},
{"vacuum_index_cleanup", RELOPT_TYPE_ENUM,
offsetof(StdRdOptions, vacuum_index_cleanup)},
- {"vacuum_truncate", RELOPT_TYPE_BOOL,
- offsetof(StdRdOptions, vacuum_truncate), offsetof(StdRdOptions, vacuum_truncate_set)},
+ {"vacuum_truncate", RELOPT_TYPE_TERNARY,
+ offsetof(StdRdOptions, vacuum_truncate)},
{"vacuum_max_eager_freeze_failure_rate", RELOPT_TYPE_REAL,
offsetof(StdRdOptions, vacuum_max_eager_freeze_failure_rate)}
};
@@ -2004,7 +2090,6 @@ build_local_reloptions(local_relopts *relopts, Datum options, bool validate)
elems[i].optname = opt->option->name;
elems[i].opttype = opt->option->type;
elems[i].offset = opt->offset;
- elems[i].isset_offset = 0; /* not supported for local relopts yet */
i++;
}
diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c
index aa4fbec143f..696eab9bd97 100644
--- a/src/backend/commands/vacuum.c
+++ b/src/backend/commands/vacuum.c
@@ -2224,9 +2224,9 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams params,
{
StdRdOptions *opts = (StdRdOptions *) rel->rd_options;
- if (opts && opts->vacuum_truncate_set)
+ if (opts && opts->vacuum_truncate != TERNARY_UNSET)
{
- if (opts->vacuum_truncate)
+ if (opts->vacuum_truncate == TERNARY_TRUE)
params.truncate = VACOPTVALUE_ENABLED;
else
params.truncate = VACOPTVALUE_DISABLED;
diff --git a/src/include/access/reloptions.h b/src/include/access/reloptions.h
index 2f08e1b0cf0..dfbef2babf2 100644
--- a/src/include/access/reloptions.h
+++ b/src/include/access/reloptions.h
@@ -29,6 +29,7 @@
typedef enum relopt_type
{
RELOPT_TYPE_BOOL,
+ RELOPT_TYPE_TERNARY, /* on, off, unset */
RELOPT_TYPE_INT,
RELOPT_TYPE_REAL,
RELOPT_TYPE_ENUM,
@@ -80,6 +81,7 @@ typedef struct relopt_value
union
{
bool bool_val;
+ pg_ternary ternary_val;
int int_val;
double real_val;
int enum_val;
@@ -94,6 +96,12 @@ typedef struct relopt_bool
bool default_val;
} relopt_bool;
+typedef struct relopt_ternary
+{
+ relopt_gen gen;
+ int default_val;
+} relopt_ternary;
+
typedef struct relopt_int
{
relopt_gen gen;
@@ -152,19 +160,6 @@ typedef struct
const char *optname; /* option's name */
relopt_type opttype; /* option's datatype */
int offset; /* offset of field in result struct */
-
- /*
- * isset_offset is an optional offset of a field in the result struct that
- * stores whether the option is explicitly set for the relation or if it
- * just picked up the default value. In most cases, this can be
- * accomplished by giving the reloption a special out-of-range default
- * value (e.g., some integer reloptions use -2), but this isn't always
- * possible. For example, a Boolean reloption cannot be given an
- * out-of-range default, so we need another way to discover the source of
- * its value. This offset is only used if given a value greater than
- * zero.
- */
- int isset_offset;
} relopt_parse_elt;
/* Local reloption definition */
@@ -195,6 +190,9 @@ typedef struct local_relopts
extern relopt_kind add_reloption_kind(void);
extern void add_bool_reloption(bits32 kinds, const char *name, const char *desc,
bool default_val, LOCKMODE lockmode);
+extern void add_ternary_reloption(bits32 kinds, const char *name,
+ const char *desc, pg_ternary default_val,
+ LOCKMODE lockmode);
extern void add_int_reloption(bits32 kinds, const char *name, const char *desc,
int default_val, int min_val, int max_val,
LOCKMODE lockmode);
@@ -214,6 +212,9 @@ extern void register_reloptions_validator(local_relopts *relopts,
extern void add_local_bool_reloption(local_relopts *relopts, const char *name,
const char *desc, bool default_val,
int offset);
+extern void add_local_ternary_reloption(local_relopts *relopts,
+ const char *name, const char *desc,
+ pg_ternary default_val, int offset);
extern void add_local_int_reloption(local_relopts *relopts, const char *name,
const char *desc, int default_val,
int min_val, int max_val, int offset);
diff --git a/src/include/postgres.h b/src/include/postgres.h
index 7d93fbce709..47c88780776 100644
--- a/src/include/postgres.h
+++ b/src/include/postgres.h
@@ -543,6 +543,21 @@ Float8GetDatum(float8 X)
* ----------------------------------------------------------------
*/
+/*
+ * pg_ternary
+ * Boolean value with an extra "unset" value
+ *
+ * This enum can be used for values that want to distinguish between true,
+ * false, and unset.
+*/
+
+typedef enum pg_ternary
+{
+ TERNARY_FALSE = 0,
+ TERNARY_TRUE = 1,
+ TERNARY_UNSET = -1
+} pg_ternary;
+
/*
* NON_EXEC_STATIC: It's sometimes useful to define a variable or function
* that is normally static but extern when using EXEC_BACKEND (see
diff --git a/src/include/utils/rel.h b/src/include/utils/rel.h
index d03ab247788..5feb18a5373 100644
--- a/src/include/utils/rel.h
+++ b/src/include/utils/rel.h
@@ -347,8 +347,7 @@ typedef struct StdRdOptions
bool user_catalog_table; /* use as an additional catalog relation */
int parallel_workers; /* max number of parallel workers */
StdRdOptIndexCleanup vacuum_index_cleanup; /* controls index vacuuming */
- bool vacuum_truncate; /* enables vacuum to truncate a relation */
- bool vacuum_truncate_set; /* whether vacuum_truncate is set */
+ pg_ternary vacuum_truncate; /* enables vacuum to truncate a relation */
/*
* Fraction of pages in a relation that vacuum can eagerly scan and fail
diff --git a/src/test/regress/expected/reloptions.out b/src/test/regress/expected/reloptions.out
index 9de19b4e3f1..1c99f79ab01 100644
--- a/src/test/regress/expected/reloptions.out
+++ b/src/test/regress/expected/reloptions.out
@@ -98,6 +98,42 @@ SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass;
{fillfactor=13,autovacuum_enabled=false}
(1 row)
+-- Tests for future (FIXME) ternary options
+-- behave as boolean option: accept unassigned name and truncated value
+DROP TABLE reloptions_test;
+CREATE TABLE reloptions_test(i INT) WITH (vacuum_truncate);
+SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass;
+ reloptions
+------------------------
+ {vacuum_truncate=true}
+(1 row)
+
+DROP TABLE reloptions_test;
+CREATE TABLE reloptions_test(i INT) WITH (vacuum_truncate=FaLS);
+SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass;
+ reloptions
+------------------------
+ {vacuum_truncate=fals}
+(1 row)
+
+-- preferred "true" alias is used when storing
+DROP TABLE reloptions_test;
+CREATE TABLE reloptions_test(i INT) WITH (vacuum_index_cleanup=on);
+SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass;
+ reloptions
+---------------------------
+ {vacuum_index_cleanup=on}
+(1 row)
+
+-- custom "third" value is available
+DROP TABLE reloptions_test;
+CREATE TABLE reloptions_test(i INT) WITH (vacuum_index_cleanup=auto);
+SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass;
+ reloptions
+-----------------------------
+ {vacuum_index_cleanup=auto}
+(1 row)
+
-- Test vacuum_truncate option
DROP TABLE reloptions_test;
CREATE TEMP TABLE reloptions_test(i INT NOT NULL, j text)
diff --git a/src/test/regress/sql/reloptions.sql b/src/test/regress/sql/reloptions.sql
index 24fbe0b478d..f5980dafcbc 100644
--- a/src/test/regress/sql/reloptions.sql
+++ b/src/test/regress/sql/reloptions.sql
@@ -59,6 +59,27 @@ UPDATE pg_class
ALTER TABLE reloptions_test RESET (illegal_option);
SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass;
+-- Tests for future (FIXME) ternary options
+
+-- behave as boolean option: accept unassigned name and truncated value
+DROP TABLE reloptions_test;
+CREATE TABLE reloptions_test(i INT) WITH (vacuum_truncate);
+SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass;
+
+DROP TABLE reloptions_test;
+CREATE TABLE reloptions_test(i INT) WITH (vacuum_truncate=FaLS);
+SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass;
+
+-- preferred "true" alias is used when storing
+DROP TABLE reloptions_test;
+CREATE TABLE reloptions_test(i INT) WITH (vacuum_index_cleanup=on);
+SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass;
+
+-- custom "third" value is available
+DROP TABLE reloptions_test;
+CREATE TABLE reloptions_test(i INT) WITH (vacuum_index_cleanup=auto);
+SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass;
+
-- Test vacuum_truncate option
DROP TABLE reloptions_test;
diff --git a/src/tools/pgindent/typedefs.list b/src/tools/pgindent/typedefs.list
index 3f3a888fd0e..1c8610fd46c 100644
--- a/src/tools/pgindent/typedefs.list
+++ b/src/tools/pgindent/typedefs.list
@@ -3952,6 +3952,7 @@ pg_sha512_ctx
pg_snapshot
pg_special_case
pg_stack_base_t
+pg_ternary
pg_time_t
pg_time_usec_t
pg_tz
@@ -4079,6 +4080,7 @@ relopt_kind
relopt_parse_elt
relopt_real
relopt_string
+relopt_ternary
relopt_type
relopt_value
relopts_validator
--
2.47.3
--c36t3fndxhkzbl5z--
^ permalink raw reply [nested|flat] 868+ messages in thread
* [PATCH v3] Introduce ternary reloptions
@ 2026-01-16 15:04 Álvaro Herrera <[email protected]>
0 siblings, 0 replies; 868+ messages in thread
From: Álvaro Herrera @ 2026-01-16 15:04 UTC (permalink / raw)
Introduce ternary reloption as a replacement for current
`vacuum_truncate` implementation. Remove the `vacuum_truncate_set`
separate flag and use TERNARY_UNSET instead.
This could also be used for other options such as `vacuum_index_cleanup`
and `buffering`, but lets get the scaffolding in first.
Discussion: https://postgr.es/m/3474141.usfYGdeWWP@thinkpad-pgpro
---
src/backend/access/common/reloptions.c | 137 ++++++++++++++++++-----
src/backend/commands/vacuum.c | 4 +-
src/include/access/reloptions.h | 27 ++---
src/include/postgres.h | 15 +++
src/include/utils/rel.h | 3 +-
src/test/regress/expected/reloptions.out | 36 ++++++
src/test/regress/sql/reloptions.sql | 21 ++++
src/tools/pgindent/typedefs.list | 2 +
8 files changed, 202 insertions(+), 43 deletions(-)
diff --git a/src/backend/access/common/reloptions.c b/src/backend/access/common/reloptions.c
index 0b83f98ed5f..6f1f577581a 100644
--- a/src/backend/access/common/reloptions.c
+++ b/src/backend/access/common/reloptions.c
@@ -40,9 +40,9 @@
*
* To add an option:
*
- * (i) decide on a type (bool, integer, real, enum, string), name, default
- * value, upper and lower bounds (if applicable); for strings, consider a
- * validation routine.
+ * (i) decide on a type (bool, ternary, integer, real, enum, string), name,
+ * default value, upper and lower bounds (if applicable); for strings,
+ * consider a validation routine.
* (ii) add a record below (or use add_<type>_reloption).
* (iii) add it to the appropriate options struct (perhaps StdRdOptions)
* (iv) add it to the appropriate handling routine (perhaps
@@ -147,15 +147,6 @@ static relopt_bool boolRelOpts[] =
},
false
},
- {
- {
- "vacuum_truncate",
- "Enables vacuum to truncate empty pages at the end of this table",
- RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
- ShareUpdateExclusiveLock
- },
- true
- },
{
{
"deduplicate_items",
@@ -170,6 +161,25 @@ static relopt_bool boolRelOpts[] =
{{NULL}}
};
+static relopt_ternary ternaryRelOpts[] =
+{
+ {
+ {
+ "vacuum_truncate",
+ "Enables vacuum to truncate empty pages at the end of this table",
+ RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
+ ShareUpdateExclusiveLock
+ },
+ TERNARY_UNSET
+ },
+ /* list terminator */
+ {
+ {
+ NULL
+ }
+ }
+};
+
static relopt_int intRelOpts[] =
{
{
@@ -609,6 +619,13 @@ initialize_reloptions(void)
boolRelOpts[i].gen.lockmode));
j++;
}
+ for (i = 0; ternaryRelOpts[i].gen.name; i++)
+ {
+ Assert(DoLockModesConflict(ternaryRelOpts[i].gen.lockmode,
+ ternaryRelOpts[i].gen.lockmode));
+ j++;
+ }
+
for (i = 0; intRelOpts[i].gen.name; i++)
{
Assert(DoLockModesConflict(intRelOpts[i].gen.lockmode,
@@ -649,6 +666,14 @@ initialize_reloptions(void)
j++;
}
+ for (i = 0; ternaryRelOpts[i].gen.name; i++)
+ {
+ relOpts[j] = &ternaryRelOpts[i].gen;
+ relOpts[j]->type = RELOPT_TYPE_TERNARY;
+ relOpts[j]->namelen = strlen(relOpts[j]->name);
+ j++;
+ }
+
for (i = 0; intRelOpts[i].gen.name; i++)
{
relOpts[j] = &intRelOpts[i].gen;
@@ -809,6 +834,9 @@ allocate_reloption(bits32 kinds, int type, const char *name, const char *desc,
case RELOPT_TYPE_BOOL:
size = sizeof(relopt_bool);
break;
+ case RELOPT_TYPE_TERNARY:
+ size = sizeof(relopt_ternary);
+ break;
case RELOPT_TYPE_INT:
size = sizeof(relopt_int);
break;
@@ -892,6 +920,57 @@ add_local_bool_reloption(local_relopts *relopts, const char *name,
add_local_reloption(relopts, (relopt_gen *) newoption, offset);
}
+/*
+ * init_ternary_reloption
+ * Allocate and initialize a new ternary reloption
+ */
+static relopt_ternary *
+init_ternary_reloption(bits32 kinds, const char *name, const char *desc,
+ pg_ternary default_val, LOCKMODE lockmode)
+{
+ relopt_ternary *newoption;
+
+ newoption = (relopt_ternary *)
+ allocate_reloption(kinds, RELOPT_TYPE_TERNARY, name, desc, lockmode);
+ newoption->default_val = default_val;
+
+ return newoption;
+}
+
+/*
+ * add_ternary_reloption
+ * Add a new ternary reloption
+ */
+void
+add_ternary_reloption(bits32 kinds, const char *name, const char *desc,
+ pg_ternary default_val, LOCKMODE lockmode)
+{
+ relopt_ternary *newoption;
+
+ newoption =
+ init_ternary_reloption(kinds, name, desc, default_val, lockmode);
+
+ add_reloption((relopt_gen *) newoption);
+}
+
+/*
+ * add_local_ternary_reloption
+ * Add a new ternary local reloption
+ *
+ * 'offset' is offset of ternary-typed field.
+ */
+void
+add_local_ternary_reloption(local_relopts *relopts, const char *name,
+ const char *desc, pg_ternary default_val,
+ int offset)
+{
+ relopt_ternary *newoption;
+
+ newoption =
+ init_ternary_reloption(RELOPT_KIND_LOCAL, name, desc, default_val, 0);
+
+ add_local_reloption(relopts, (relopt_gen *) newoption, offset);
+}
/*
* init_real_reloption
@@ -1626,6 +1705,19 @@ parse_one_reloption(relopt_value *option, char *text_str, int text_len,
option->gen->name, value)));
}
break;
+ case RELOPT_TYPE_TERNARY:
+ {
+ bool b;
+
+ parsed = parse_bool(value, &b);
+ option->values.ternary_val = b ? TERNARY_TRUE : TERNARY_FALSE;
+ if (validate && !parsed)
+ ereport(ERROR,
+ (errcode(ERRCODE_INVALID_PARAMETER_VALUE),
+ errmsg("invalid value for ternary option \"%s\": %s",
+ option->gen->name, value)));
+ }
+ break;
case RELOPT_TYPE_INT:
{
relopt_int *optint = (relopt_int *) option->gen;
@@ -1789,17 +1881,6 @@ fillRelOptions(void *rdopts, Size basesize,
char *itempos = ((char *) rdopts) + elems[j].offset;
char *string_val;
- /*
- * If isset_offset is provided, store whether the reloption is
- * set there.
- */
- if (elems[j].isset_offset > 0)
- {
- char *setpos = ((char *) rdopts) + elems[j].isset_offset;
-
- *(bool *) setpos = options[i].isset;
- }
-
switch (options[i].gen->type)
{
case RELOPT_TYPE_BOOL:
@@ -1807,6 +1888,11 @@ fillRelOptions(void *rdopts, Size basesize,
options[i].values.bool_val :
((relopt_bool *) options[i].gen)->default_val;
break;
+ case RELOPT_TYPE_TERNARY:
+ *(pg_ternary *) itempos = options[i].isset ?
+ options[i].values.ternary_val :
+ ((relopt_ternary *) options[i].gen)->default_val;
+ break;
case RELOPT_TYPE_INT:
*(int *) itempos = options[i].isset ?
options[i].values.int_val :
@@ -1923,8 +2009,8 @@ default_reloptions(Datum reloptions, bool validate, relopt_kind kind)
offsetof(StdRdOptions, parallel_workers)},
{"vacuum_index_cleanup", RELOPT_TYPE_ENUM,
offsetof(StdRdOptions, vacuum_index_cleanup)},
- {"vacuum_truncate", RELOPT_TYPE_BOOL,
- offsetof(StdRdOptions, vacuum_truncate), offsetof(StdRdOptions, vacuum_truncate_set)},
+ {"vacuum_truncate", RELOPT_TYPE_TERNARY,
+ offsetof(StdRdOptions, vacuum_truncate)},
{"vacuum_max_eager_freeze_failure_rate", RELOPT_TYPE_REAL,
offsetof(StdRdOptions, vacuum_max_eager_freeze_failure_rate)}
};
@@ -2004,7 +2090,6 @@ build_local_reloptions(local_relopts *relopts, Datum options, bool validate)
elems[i].optname = opt->option->name;
elems[i].opttype = opt->option->type;
elems[i].offset = opt->offset;
- elems[i].isset_offset = 0; /* not supported for local relopts yet */
i++;
}
diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c
index aa4fbec143f..696eab9bd97 100644
--- a/src/backend/commands/vacuum.c
+++ b/src/backend/commands/vacuum.c
@@ -2224,9 +2224,9 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams params,
{
StdRdOptions *opts = (StdRdOptions *) rel->rd_options;
- if (opts && opts->vacuum_truncate_set)
+ if (opts && opts->vacuum_truncate != TERNARY_UNSET)
{
- if (opts->vacuum_truncate)
+ if (opts->vacuum_truncate == TERNARY_TRUE)
params.truncate = VACOPTVALUE_ENABLED;
else
params.truncate = VACOPTVALUE_DISABLED;
diff --git a/src/include/access/reloptions.h b/src/include/access/reloptions.h
index 2f08e1b0cf0..dfbef2babf2 100644
--- a/src/include/access/reloptions.h
+++ b/src/include/access/reloptions.h
@@ -29,6 +29,7 @@
typedef enum relopt_type
{
RELOPT_TYPE_BOOL,
+ RELOPT_TYPE_TERNARY, /* on, off, unset */
RELOPT_TYPE_INT,
RELOPT_TYPE_REAL,
RELOPT_TYPE_ENUM,
@@ -80,6 +81,7 @@ typedef struct relopt_value
union
{
bool bool_val;
+ pg_ternary ternary_val;
int int_val;
double real_val;
int enum_val;
@@ -94,6 +96,12 @@ typedef struct relopt_bool
bool default_val;
} relopt_bool;
+typedef struct relopt_ternary
+{
+ relopt_gen gen;
+ int default_val;
+} relopt_ternary;
+
typedef struct relopt_int
{
relopt_gen gen;
@@ -152,19 +160,6 @@ typedef struct
const char *optname; /* option's name */
relopt_type opttype; /* option's datatype */
int offset; /* offset of field in result struct */
-
- /*
- * isset_offset is an optional offset of a field in the result struct that
- * stores whether the option is explicitly set for the relation or if it
- * just picked up the default value. In most cases, this can be
- * accomplished by giving the reloption a special out-of-range default
- * value (e.g., some integer reloptions use -2), but this isn't always
- * possible. For example, a Boolean reloption cannot be given an
- * out-of-range default, so we need another way to discover the source of
- * its value. This offset is only used if given a value greater than
- * zero.
- */
- int isset_offset;
} relopt_parse_elt;
/* Local reloption definition */
@@ -195,6 +190,9 @@ typedef struct local_relopts
extern relopt_kind add_reloption_kind(void);
extern void add_bool_reloption(bits32 kinds, const char *name, const char *desc,
bool default_val, LOCKMODE lockmode);
+extern void add_ternary_reloption(bits32 kinds, const char *name,
+ const char *desc, pg_ternary default_val,
+ LOCKMODE lockmode);
extern void add_int_reloption(bits32 kinds, const char *name, const char *desc,
int default_val, int min_val, int max_val,
LOCKMODE lockmode);
@@ -214,6 +212,9 @@ extern void register_reloptions_validator(local_relopts *relopts,
extern void add_local_bool_reloption(local_relopts *relopts, const char *name,
const char *desc, bool default_val,
int offset);
+extern void add_local_ternary_reloption(local_relopts *relopts,
+ const char *name, const char *desc,
+ pg_ternary default_val, int offset);
extern void add_local_int_reloption(local_relopts *relopts, const char *name,
const char *desc, int default_val,
int min_val, int max_val, int offset);
diff --git a/src/include/postgres.h b/src/include/postgres.h
index 7d93fbce709..47c88780776 100644
--- a/src/include/postgres.h
+++ b/src/include/postgres.h
@@ -543,6 +543,21 @@ Float8GetDatum(float8 X)
* ----------------------------------------------------------------
*/
+/*
+ * pg_ternary
+ * Boolean value with an extra "unset" value
+ *
+ * This enum can be used for values that want to distinguish between true,
+ * false, and unset.
+*/
+
+typedef enum pg_ternary
+{
+ TERNARY_FALSE = 0,
+ TERNARY_TRUE = 1,
+ TERNARY_UNSET = -1
+} pg_ternary;
+
/*
* NON_EXEC_STATIC: It's sometimes useful to define a variable or function
* that is normally static but extern when using EXEC_BACKEND (see
diff --git a/src/include/utils/rel.h b/src/include/utils/rel.h
index d03ab247788..5feb18a5373 100644
--- a/src/include/utils/rel.h
+++ b/src/include/utils/rel.h
@@ -347,8 +347,7 @@ typedef struct StdRdOptions
bool user_catalog_table; /* use as an additional catalog relation */
int parallel_workers; /* max number of parallel workers */
StdRdOptIndexCleanup vacuum_index_cleanup; /* controls index vacuuming */
- bool vacuum_truncate; /* enables vacuum to truncate a relation */
- bool vacuum_truncate_set; /* whether vacuum_truncate is set */
+ pg_ternary vacuum_truncate; /* enables vacuum to truncate a relation */
/*
* Fraction of pages in a relation that vacuum can eagerly scan and fail
diff --git a/src/test/regress/expected/reloptions.out b/src/test/regress/expected/reloptions.out
index 9de19b4e3f1..1c99f79ab01 100644
--- a/src/test/regress/expected/reloptions.out
+++ b/src/test/regress/expected/reloptions.out
@@ -98,6 +98,42 @@ SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass;
{fillfactor=13,autovacuum_enabled=false}
(1 row)
+-- Tests for future (FIXME) ternary options
+-- behave as boolean option: accept unassigned name and truncated value
+DROP TABLE reloptions_test;
+CREATE TABLE reloptions_test(i INT) WITH (vacuum_truncate);
+SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass;
+ reloptions
+------------------------
+ {vacuum_truncate=true}
+(1 row)
+
+DROP TABLE reloptions_test;
+CREATE TABLE reloptions_test(i INT) WITH (vacuum_truncate=FaLS);
+SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass;
+ reloptions
+------------------------
+ {vacuum_truncate=fals}
+(1 row)
+
+-- preferred "true" alias is used when storing
+DROP TABLE reloptions_test;
+CREATE TABLE reloptions_test(i INT) WITH (vacuum_index_cleanup=on);
+SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass;
+ reloptions
+---------------------------
+ {vacuum_index_cleanup=on}
+(1 row)
+
+-- custom "third" value is available
+DROP TABLE reloptions_test;
+CREATE TABLE reloptions_test(i INT) WITH (vacuum_index_cleanup=auto);
+SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass;
+ reloptions
+-----------------------------
+ {vacuum_index_cleanup=auto}
+(1 row)
+
-- Test vacuum_truncate option
DROP TABLE reloptions_test;
CREATE TEMP TABLE reloptions_test(i INT NOT NULL, j text)
diff --git a/src/test/regress/sql/reloptions.sql b/src/test/regress/sql/reloptions.sql
index 24fbe0b478d..f5980dafcbc 100644
--- a/src/test/regress/sql/reloptions.sql
+++ b/src/test/regress/sql/reloptions.sql
@@ -59,6 +59,27 @@ UPDATE pg_class
ALTER TABLE reloptions_test RESET (illegal_option);
SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass;
+-- Tests for future (FIXME) ternary options
+
+-- behave as boolean option: accept unassigned name and truncated value
+DROP TABLE reloptions_test;
+CREATE TABLE reloptions_test(i INT) WITH (vacuum_truncate);
+SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass;
+
+DROP TABLE reloptions_test;
+CREATE TABLE reloptions_test(i INT) WITH (vacuum_truncate=FaLS);
+SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass;
+
+-- preferred "true" alias is used when storing
+DROP TABLE reloptions_test;
+CREATE TABLE reloptions_test(i INT) WITH (vacuum_index_cleanup=on);
+SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass;
+
+-- custom "third" value is available
+DROP TABLE reloptions_test;
+CREATE TABLE reloptions_test(i INT) WITH (vacuum_index_cleanup=auto);
+SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass;
+
-- Test vacuum_truncate option
DROP TABLE reloptions_test;
diff --git a/src/tools/pgindent/typedefs.list b/src/tools/pgindent/typedefs.list
index 3f3a888fd0e..1c8610fd46c 100644
--- a/src/tools/pgindent/typedefs.list
+++ b/src/tools/pgindent/typedefs.list
@@ -3952,6 +3952,7 @@ pg_sha512_ctx
pg_snapshot
pg_special_case
pg_stack_base_t
+pg_ternary
pg_time_t
pg_time_usec_t
pg_tz
@@ -4079,6 +4080,7 @@ relopt_kind
relopt_parse_elt
relopt_real
relopt_string
+relopt_ternary
relopt_type
relopt_value
relopts_validator
--
2.47.3
--c36t3fndxhkzbl5z--
^ permalink raw reply [nested|flat] 868+ messages in thread
* [PATCH v3] Introduce ternary reloptions
@ 2026-01-16 15:04 Álvaro Herrera <[email protected]>
0 siblings, 0 replies; 868+ messages in thread
From: Álvaro Herrera @ 2026-01-16 15:04 UTC (permalink / raw)
Introduce ternary reloption as a replacement for current
`vacuum_truncate` implementation. Remove the `vacuum_truncate_set`
separate flag and use TERNARY_UNSET instead.
This could also be used for other options such as `vacuum_index_cleanup`
and `buffering`, but lets get the scaffolding in first.
Discussion: https://postgr.es/m/3474141.usfYGdeWWP@thinkpad-pgpro
---
src/backend/access/common/reloptions.c | 137 ++++++++++++++++++-----
src/backend/commands/vacuum.c | 4 +-
src/include/access/reloptions.h | 27 ++---
src/include/postgres.h | 15 +++
src/include/utils/rel.h | 3 +-
src/test/regress/expected/reloptions.out | 36 ++++++
src/test/regress/sql/reloptions.sql | 21 ++++
src/tools/pgindent/typedefs.list | 2 +
8 files changed, 202 insertions(+), 43 deletions(-)
diff --git a/src/backend/access/common/reloptions.c b/src/backend/access/common/reloptions.c
index 0b83f98ed5f..6f1f577581a 100644
--- a/src/backend/access/common/reloptions.c
+++ b/src/backend/access/common/reloptions.c
@@ -40,9 +40,9 @@
*
* To add an option:
*
- * (i) decide on a type (bool, integer, real, enum, string), name, default
- * value, upper and lower bounds (if applicable); for strings, consider a
- * validation routine.
+ * (i) decide on a type (bool, ternary, integer, real, enum, string), name,
+ * default value, upper and lower bounds (if applicable); for strings,
+ * consider a validation routine.
* (ii) add a record below (or use add_<type>_reloption).
* (iii) add it to the appropriate options struct (perhaps StdRdOptions)
* (iv) add it to the appropriate handling routine (perhaps
@@ -147,15 +147,6 @@ static relopt_bool boolRelOpts[] =
},
false
},
- {
- {
- "vacuum_truncate",
- "Enables vacuum to truncate empty pages at the end of this table",
- RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
- ShareUpdateExclusiveLock
- },
- true
- },
{
{
"deduplicate_items",
@@ -170,6 +161,25 @@ static relopt_bool boolRelOpts[] =
{{NULL}}
};
+static relopt_ternary ternaryRelOpts[] =
+{
+ {
+ {
+ "vacuum_truncate",
+ "Enables vacuum to truncate empty pages at the end of this table",
+ RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
+ ShareUpdateExclusiveLock
+ },
+ TERNARY_UNSET
+ },
+ /* list terminator */
+ {
+ {
+ NULL
+ }
+ }
+};
+
static relopt_int intRelOpts[] =
{
{
@@ -609,6 +619,13 @@ initialize_reloptions(void)
boolRelOpts[i].gen.lockmode));
j++;
}
+ for (i = 0; ternaryRelOpts[i].gen.name; i++)
+ {
+ Assert(DoLockModesConflict(ternaryRelOpts[i].gen.lockmode,
+ ternaryRelOpts[i].gen.lockmode));
+ j++;
+ }
+
for (i = 0; intRelOpts[i].gen.name; i++)
{
Assert(DoLockModesConflict(intRelOpts[i].gen.lockmode,
@@ -649,6 +666,14 @@ initialize_reloptions(void)
j++;
}
+ for (i = 0; ternaryRelOpts[i].gen.name; i++)
+ {
+ relOpts[j] = &ternaryRelOpts[i].gen;
+ relOpts[j]->type = RELOPT_TYPE_TERNARY;
+ relOpts[j]->namelen = strlen(relOpts[j]->name);
+ j++;
+ }
+
for (i = 0; intRelOpts[i].gen.name; i++)
{
relOpts[j] = &intRelOpts[i].gen;
@@ -809,6 +834,9 @@ allocate_reloption(bits32 kinds, int type, const char *name, const char *desc,
case RELOPT_TYPE_BOOL:
size = sizeof(relopt_bool);
break;
+ case RELOPT_TYPE_TERNARY:
+ size = sizeof(relopt_ternary);
+ break;
case RELOPT_TYPE_INT:
size = sizeof(relopt_int);
break;
@@ -892,6 +920,57 @@ add_local_bool_reloption(local_relopts *relopts, const char *name,
add_local_reloption(relopts, (relopt_gen *) newoption, offset);
}
+/*
+ * init_ternary_reloption
+ * Allocate and initialize a new ternary reloption
+ */
+static relopt_ternary *
+init_ternary_reloption(bits32 kinds, const char *name, const char *desc,
+ pg_ternary default_val, LOCKMODE lockmode)
+{
+ relopt_ternary *newoption;
+
+ newoption = (relopt_ternary *)
+ allocate_reloption(kinds, RELOPT_TYPE_TERNARY, name, desc, lockmode);
+ newoption->default_val = default_val;
+
+ return newoption;
+}
+
+/*
+ * add_ternary_reloption
+ * Add a new ternary reloption
+ */
+void
+add_ternary_reloption(bits32 kinds, const char *name, const char *desc,
+ pg_ternary default_val, LOCKMODE lockmode)
+{
+ relopt_ternary *newoption;
+
+ newoption =
+ init_ternary_reloption(kinds, name, desc, default_val, lockmode);
+
+ add_reloption((relopt_gen *) newoption);
+}
+
+/*
+ * add_local_ternary_reloption
+ * Add a new ternary local reloption
+ *
+ * 'offset' is offset of ternary-typed field.
+ */
+void
+add_local_ternary_reloption(local_relopts *relopts, const char *name,
+ const char *desc, pg_ternary default_val,
+ int offset)
+{
+ relopt_ternary *newoption;
+
+ newoption =
+ init_ternary_reloption(RELOPT_KIND_LOCAL, name, desc, default_val, 0);
+
+ add_local_reloption(relopts, (relopt_gen *) newoption, offset);
+}
/*
* init_real_reloption
@@ -1626,6 +1705,19 @@ parse_one_reloption(relopt_value *option, char *text_str, int text_len,
option->gen->name, value)));
}
break;
+ case RELOPT_TYPE_TERNARY:
+ {
+ bool b;
+
+ parsed = parse_bool(value, &b);
+ option->values.ternary_val = b ? TERNARY_TRUE : TERNARY_FALSE;
+ if (validate && !parsed)
+ ereport(ERROR,
+ (errcode(ERRCODE_INVALID_PARAMETER_VALUE),
+ errmsg("invalid value for ternary option \"%s\": %s",
+ option->gen->name, value)));
+ }
+ break;
case RELOPT_TYPE_INT:
{
relopt_int *optint = (relopt_int *) option->gen;
@@ -1789,17 +1881,6 @@ fillRelOptions(void *rdopts, Size basesize,
char *itempos = ((char *) rdopts) + elems[j].offset;
char *string_val;
- /*
- * If isset_offset is provided, store whether the reloption is
- * set there.
- */
- if (elems[j].isset_offset > 0)
- {
- char *setpos = ((char *) rdopts) + elems[j].isset_offset;
-
- *(bool *) setpos = options[i].isset;
- }
-
switch (options[i].gen->type)
{
case RELOPT_TYPE_BOOL:
@@ -1807,6 +1888,11 @@ fillRelOptions(void *rdopts, Size basesize,
options[i].values.bool_val :
((relopt_bool *) options[i].gen)->default_val;
break;
+ case RELOPT_TYPE_TERNARY:
+ *(pg_ternary *) itempos = options[i].isset ?
+ options[i].values.ternary_val :
+ ((relopt_ternary *) options[i].gen)->default_val;
+ break;
case RELOPT_TYPE_INT:
*(int *) itempos = options[i].isset ?
options[i].values.int_val :
@@ -1923,8 +2009,8 @@ default_reloptions(Datum reloptions, bool validate, relopt_kind kind)
offsetof(StdRdOptions, parallel_workers)},
{"vacuum_index_cleanup", RELOPT_TYPE_ENUM,
offsetof(StdRdOptions, vacuum_index_cleanup)},
- {"vacuum_truncate", RELOPT_TYPE_BOOL,
- offsetof(StdRdOptions, vacuum_truncate), offsetof(StdRdOptions, vacuum_truncate_set)},
+ {"vacuum_truncate", RELOPT_TYPE_TERNARY,
+ offsetof(StdRdOptions, vacuum_truncate)},
{"vacuum_max_eager_freeze_failure_rate", RELOPT_TYPE_REAL,
offsetof(StdRdOptions, vacuum_max_eager_freeze_failure_rate)}
};
@@ -2004,7 +2090,6 @@ build_local_reloptions(local_relopts *relopts, Datum options, bool validate)
elems[i].optname = opt->option->name;
elems[i].opttype = opt->option->type;
elems[i].offset = opt->offset;
- elems[i].isset_offset = 0; /* not supported for local relopts yet */
i++;
}
diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c
index aa4fbec143f..696eab9bd97 100644
--- a/src/backend/commands/vacuum.c
+++ b/src/backend/commands/vacuum.c
@@ -2224,9 +2224,9 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams params,
{
StdRdOptions *opts = (StdRdOptions *) rel->rd_options;
- if (opts && opts->vacuum_truncate_set)
+ if (opts && opts->vacuum_truncate != TERNARY_UNSET)
{
- if (opts->vacuum_truncate)
+ if (opts->vacuum_truncate == TERNARY_TRUE)
params.truncate = VACOPTVALUE_ENABLED;
else
params.truncate = VACOPTVALUE_DISABLED;
diff --git a/src/include/access/reloptions.h b/src/include/access/reloptions.h
index 2f08e1b0cf0..dfbef2babf2 100644
--- a/src/include/access/reloptions.h
+++ b/src/include/access/reloptions.h
@@ -29,6 +29,7 @@
typedef enum relopt_type
{
RELOPT_TYPE_BOOL,
+ RELOPT_TYPE_TERNARY, /* on, off, unset */
RELOPT_TYPE_INT,
RELOPT_TYPE_REAL,
RELOPT_TYPE_ENUM,
@@ -80,6 +81,7 @@ typedef struct relopt_value
union
{
bool bool_val;
+ pg_ternary ternary_val;
int int_val;
double real_val;
int enum_val;
@@ -94,6 +96,12 @@ typedef struct relopt_bool
bool default_val;
} relopt_bool;
+typedef struct relopt_ternary
+{
+ relopt_gen gen;
+ int default_val;
+} relopt_ternary;
+
typedef struct relopt_int
{
relopt_gen gen;
@@ -152,19 +160,6 @@ typedef struct
const char *optname; /* option's name */
relopt_type opttype; /* option's datatype */
int offset; /* offset of field in result struct */
-
- /*
- * isset_offset is an optional offset of a field in the result struct that
- * stores whether the option is explicitly set for the relation or if it
- * just picked up the default value. In most cases, this can be
- * accomplished by giving the reloption a special out-of-range default
- * value (e.g., some integer reloptions use -2), but this isn't always
- * possible. For example, a Boolean reloption cannot be given an
- * out-of-range default, so we need another way to discover the source of
- * its value. This offset is only used if given a value greater than
- * zero.
- */
- int isset_offset;
} relopt_parse_elt;
/* Local reloption definition */
@@ -195,6 +190,9 @@ typedef struct local_relopts
extern relopt_kind add_reloption_kind(void);
extern void add_bool_reloption(bits32 kinds, const char *name, const char *desc,
bool default_val, LOCKMODE lockmode);
+extern void add_ternary_reloption(bits32 kinds, const char *name,
+ const char *desc, pg_ternary default_val,
+ LOCKMODE lockmode);
extern void add_int_reloption(bits32 kinds, const char *name, const char *desc,
int default_val, int min_val, int max_val,
LOCKMODE lockmode);
@@ -214,6 +212,9 @@ extern void register_reloptions_validator(local_relopts *relopts,
extern void add_local_bool_reloption(local_relopts *relopts, const char *name,
const char *desc, bool default_val,
int offset);
+extern void add_local_ternary_reloption(local_relopts *relopts,
+ const char *name, const char *desc,
+ pg_ternary default_val, int offset);
extern void add_local_int_reloption(local_relopts *relopts, const char *name,
const char *desc, int default_val,
int min_val, int max_val, int offset);
diff --git a/src/include/postgres.h b/src/include/postgres.h
index 7d93fbce709..47c88780776 100644
--- a/src/include/postgres.h
+++ b/src/include/postgres.h
@@ -543,6 +543,21 @@ Float8GetDatum(float8 X)
* ----------------------------------------------------------------
*/
+/*
+ * pg_ternary
+ * Boolean value with an extra "unset" value
+ *
+ * This enum can be used for values that want to distinguish between true,
+ * false, and unset.
+*/
+
+typedef enum pg_ternary
+{
+ TERNARY_FALSE = 0,
+ TERNARY_TRUE = 1,
+ TERNARY_UNSET = -1
+} pg_ternary;
+
/*
* NON_EXEC_STATIC: It's sometimes useful to define a variable or function
* that is normally static but extern when using EXEC_BACKEND (see
diff --git a/src/include/utils/rel.h b/src/include/utils/rel.h
index d03ab247788..5feb18a5373 100644
--- a/src/include/utils/rel.h
+++ b/src/include/utils/rel.h
@@ -347,8 +347,7 @@ typedef struct StdRdOptions
bool user_catalog_table; /* use as an additional catalog relation */
int parallel_workers; /* max number of parallel workers */
StdRdOptIndexCleanup vacuum_index_cleanup; /* controls index vacuuming */
- bool vacuum_truncate; /* enables vacuum to truncate a relation */
- bool vacuum_truncate_set; /* whether vacuum_truncate is set */
+ pg_ternary vacuum_truncate; /* enables vacuum to truncate a relation */
/*
* Fraction of pages in a relation that vacuum can eagerly scan and fail
diff --git a/src/test/regress/expected/reloptions.out b/src/test/regress/expected/reloptions.out
index 9de19b4e3f1..1c99f79ab01 100644
--- a/src/test/regress/expected/reloptions.out
+++ b/src/test/regress/expected/reloptions.out
@@ -98,6 +98,42 @@ SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass;
{fillfactor=13,autovacuum_enabled=false}
(1 row)
+-- Tests for future (FIXME) ternary options
+-- behave as boolean option: accept unassigned name and truncated value
+DROP TABLE reloptions_test;
+CREATE TABLE reloptions_test(i INT) WITH (vacuum_truncate);
+SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass;
+ reloptions
+------------------------
+ {vacuum_truncate=true}
+(1 row)
+
+DROP TABLE reloptions_test;
+CREATE TABLE reloptions_test(i INT) WITH (vacuum_truncate=FaLS);
+SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass;
+ reloptions
+------------------------
+ {vacuum_truncate=fals}
+(1 row)
+
+-- preferred "true" alias is used when storing
+DROP TABLE reloptions_test;
+CREATE TABLE reloptions_test(i INT) WITH (vacuum_index_cleanup=on);
+SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass;
+ reloptions
+---------------------------
+ {vacuum_index_cleanup=on}
+(1 row)
+
+-- custom "third" value is available
+DROP TABLE reloptions_test;
+CREATE TABLE reloptions_test(i INT) WITH (vacuum_index_cleanup=auto);
+SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass;
+ reloptions
+-----------------------------
+ {vacuum_index_cleanup=auto}
+(1 row)
+
-- Test vacuum_truncate option
DROP TABLE reloptions_test;
CREATE TEMP TABLE reloptions_test(i INT NOT NULL, j text)
diff --git a/src/test/regress/sql/reloptions.sql b/src/test/regress/sql/reloptions.sql
index 24fbe0b478d..f5980dafcbc 100644
--- a/src/test/regress/sql/reloptions.sql
+++ b/src/test/regress/sql/reloptions.sql
@@ -59,6 +59,27 @@ UPDATE pg_class
ALTER TABLE reloptions_test RESET (illegal_option);
SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass;
+-- Tests for future (FIXME) ternary options
+
+-- behave as boolean option: accept unassigned name and truncated value
+DROP TABLE reloptions_test;
+CREATE TABLE reloptions_test(i INT) WITH (vacuum_truncate);
+SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass;
+
+DROP TABLE reloptions_test;
+CREATE TABLE reloptions_test(i INT) WITH (vacuum_truncate=FaLS);
+SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass;
+
+-- preferred "true" alias is used when storing
+DROP TABLE reloptions_test;
+CREATE TABLE reloptions_test(i INT) WITH (vacuum_index_cleanup=on);
+SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass;
+
+-- custom "third" value is available
+DROP TABLE reloptions_test;
+CREATE TABLE reloptions_test(i INT) WITH (vacuum_index_cleanup=auto);
+SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass;
+
-- Test vacuum_truncate option
DROP TABLE reloptions_test;
diff --git a/src/tools/pgindent/typedefs.list b/src/tools/pgindent/typedefs.list
index 3f3a888fd0e..1c8610fd46c 100644
--- a/src/tools/pgindent/typedefs.list
+++ b/src/tools/pgindent/typedefs.list
@@ -3952,6 +3952,7 @@ pg_sha512_ctx
pg_snapshot
pg_special_case
pg_stack_base_t
+pg_ternary
pg_time_t
pg_time_usec_t
pg_tz
@@ -4079,6 +4080,7 @@ relopt_kind
relopt_parse_elt
relopt_real
relopt_string
+relopt_ternary
relopt_type
relopt_value
relopts_validator
--
2.47.3
--c36t3fndxhkzbl5z--
^ permalink raw reply [nested|flat] 868+ messages in thread
* [PATCH v3] Introduce ternary reloptions
@ 2026-01-16 15:04 Álvaro Herrera <[email protected]>
0 siblings, 0 replies; 868+ messages in thread
From: Álvaro Herrera @ 2026-01-16 15:04 UTC (permalink / raw)
Introduce ternary reloption as a replacement for current
`vacuum_truncate` implementation. Remove the `vacuum_truncate_set`
separate flag and use TERNARY_UNSET instead.
This could also be used for other options such as `vacuum_index_cleanup`
and `buffering`, but lets get the scaffolding in first.
Discussion: https://postgr.es/m/3474141.usfYGdeWWP@thinkpad-pgpro
---
src/backend/access/common/reloptions.c | 137 ++++++++++++++++++-----
src/backend/commands/vacuum.c | 4 +-
src/include/access/reloptions.h | 27 ++---
src/include/postgres.h | 15 +++
src/include/utils/rel.h | 3 +-
src/test/regress/expected/reloptions.out | 36 ++++++
src/test/regress/sql/reloptions.sql | 21 ++++
src/tools/pgindent/typedefs.list | 2 +
8 files changed, 202 insertions(+), 43 deletions(-)
diff --git a/src/backend/access/common/reloptions.c b/src/backend/access/common/reloptions.c
index 0b83f98ed5f..6f1f577581a 100644
--- a/src/backend/access/common/reloptions.c
+++ b/src/backend/access/common/reloptions.c
@@ -40,9 +40,9 @@
*
* To add an option:
*
- * (i) decide on a type (bool, integer, real, enum, string), name, default
- * value, upper and lower bounds (if applicable); for strings, consider a
- * validation routine.
+ * (i) decide on a type (bool, ternary, integer, real, enum, string), name,
+ * default value, upper and lower bounds (if applicable); for strings,
+ * consider a validation routine.
* (ii) add a record below (or use add_<type>_reloption).
* (iii) add it to the appropriate options struct (perhaps StdRdOptions)
* (iv) add it to the appropriate handling routine (perhaps
@@ -147,15 +147,6 @@ static relopt_bool boolRelOpts[] =
},
false
},
- {
- {
- "vacuum_truncate",
- "Enables vacuum to truncate empty pages at the end of this table",
- RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
- ShareUpdateExclusiveLock
- },
- true
- },
{
{
"deduplicate_items",
@@ -170,6 +161,25 @@ static relopt_bool boolRelOpts[] =
{{NULL}}
};
+static relopt_ternary ternaryRelOpts[] =
+{
+ {
+ {
+ "vacuum_truncate",
+ "Enables vacuum to truncate empty pages at the end of this table",
+ RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
+ ShareUpdateExclusiveLock
+ },
+ TERNARY_UNSET
+ },
+ /* list terminator */
+ {
+ {
+ NULL
+ }
+ }
+};
+
static relopt_int intRelOpts[] =
{
{
@@ -609,6 +619,13 @@ initialize_reloptions(void)
boolRelOpts[i].gen.lockmode));
j++;
}
+ for (i = 0; ternaryRelOpts[i].gen.name; i++)
+ {
+ Assert(DoLockModesConflict(ternaryRelOpts[i].gen.lockmode,
+ ternaryRelOpts[i].gen.lockmode));
+ j++;
+ }
+
for (i = 0; intRelOpts[i].gen.name; i++)
{
Assert(DoLockModesConflict(intRelOpts[i].gen.lockmode,
@@ -649,6 +666,14 @@ initialize_reloptions(void)
j++;
}
+ for (i = 0; ternaryRelOpts[i].gen.name; i++)
+ {
+ relOpts[j] = &ternaryRelOpts[i].gen;
+ relOpts[j]->type = RELOPT_TYPE_TERNARY;
+ relOpts[j]->namelen = strlen(relOpts[j]->name);
+ j++;
+ }
+
for (i = 0; intRelOpts[i].gen.name; i++)
{
relOpts[j] = &intRelOpts[i].gen;
@@ -809,6 +834,9 @@ allocate_reloption(bits32 kinds, int type, const char *name, const char *desc,
case RELOPT_TYPE_BOOL:
size = sizeof(relopt_bool);
break;
+ case RELOPT_TYPE_TERNARY:
+ size = sizeof(relopt_ternary);
+ break;
case RELOPT_TYPE_INT:
size = sizeof(relopt_int);
break;
@@ -892,6 +920,57 @@ add_local_bool_reloption(local_relopts *relopts, const char *name,
add_local_reloption(relopts, (relopt_gen *) newoption, offset);
}
+/*
+ * init_ternary_reloption
+ * Allocate and initialize a new ternary reloption
+ */
+static relopt_ternary *
+init_ternary_reloption(bits32 kinds, const char *name, const char *desc,
+ pg_ternary default_val, LOCKMODE lockmode)
+{
+ relopt_ternary *newoption;
+
+ newoption = (relopt_ternary *)
+ allocate_reloption(kinds, RELOPT_TYPE_TERNARY, name, desc, lockmode);
+ newoption->default_val = default_val;
+
+ return newoption;
+}
+
+/*
+ * add_ternary_reloption
+ * Add a new ternary reloption
+ */
+void
+add_ternary_reloption(bits32 kinds, const char *name, const char *desc,
+ pg_ternary default_val, LOCKMODE lockmode)
+{
+ relopt_ternary *newoption;
+
+ newoption =
+ init_ternary_reloption(kinds, name, desc, default_val, lockmode);
+
+ add_reloption((relopt_gen *) newoption);
+}
+
+/*
+ * add_local_ternary_reloption
+ * Add a new ternary local reloption
+ *
+ * 'offset' is offset of ternary-typed field.
+ */
+void
+add_local_ternary_reloption(local_relopts *relopts, const char *name,
+ const char *desc, pg_ternary default_val,
+ int offset)
+{
+ relopt_ternary *newoption;
+
+ newoption =
+ init_ternary_reloption(RELOPT_KIND_LOCAL, name, desc, default_val, 0);
+
+ add_local_reloption(relopts, (relopt_gen *) newoption, offset);
+}
/*
* init_real_reloption
@@ -1626,6 +1705,19 @@ parse_one_reloption(relopt_value *option, char *text_str, int text_len,
option->gen->name, value)));
}
break;
+ case RELOPT_TYPE_TERNARY:
+ {
+ bool b;
+
+ parsed = parse_bool(value, &b);
+ option->values.ternary_val = b ? TERNARY_TRUE : TERNARY_FALSE;
+ if (validate && !parsed)
+ ereport(ERROR,
+ (errcode(ERRCODE_INVALID_PARAMETER_VALUE),
+ errmsg("invalid value for ternary option \"%s\": %s",
+ option->gen->name, value)));
+ }
+ break;
case RELOPT_TYPE_INT:
{
relopt_int *optint = (relopt_int *) option->gen;
@@ -1789,17 +1881,6 @@ fillRelOptions(void *rdopts, Size basesize,
char *itempos = ((char *) rdopts) + elems[j].offset;
char *string_val;
- /*
- * If isset_offset is provided, store whether the reloption is
- * set there.
- */
- if (elems[j].isset_offset > 0)
- {
- char *setpos = ((char *) rdopts) + elems[j].isset_offset;
-
- *(bool *) setpos = options[i].isset;
- }
-
switch (options[i].gen->type)
{
case RELOPT_TYPE_BOOL:
@@ -1807,6 +1888,11 @@ fillRelOptions(void *rdopts, Size basesize,
options[i].values.bool_val :
((relopt_bool *) options[i].gen)->default_val;
break;
+ case RELOPT_TYPE_TERNARY:
+ *(pg_ternary *) itempos = options[i].isset ?
+ options[i].values.ternary_val :
+ ((relopt_ternary *) options[i].gen)->default_val;
+ break;
case RELOPT_TYPE_INT:
*(int *) itempos = options[i].isset ?
options[i].values.int_val :
@@ -1923,8 +2009,8 @@ default_reloptions(Datum reloptions, bool validate, relopt_kind kind)
offsetof(StdRdOptions, parallel_workers)},
{"vacuum_index_cleanup", RELOPT_TYPE_ENUM,
offsetof(StdRdOptions, vacuum_index_cleanup)},
- {"vacuum_truncate", RELOPT_TYPE_BOOL,
- offsetof(StdRdOptions, vacuum_truncate), offsetof(StdRdOptions, vacuum_truncate_set)},
+ {"vacuum_truncate", RELOPT_TYPE_TERNARY,
+ offsetof(StdRdOptions, vacuum_truncate)},
{"vacuum_max_eager_freeze_failure_rate", RELOPT_TYPE_REAL,
offsetof(StdRdOptions, vacuum_max_eager_freeze_failure_rate)}
};
@@ -2004,7 +2090,6 @@ build_local_reloptions(local_relopts *relopts, Datum options, bool validate)
elems[i].optname = opt->option->name;
elems[i].opttype = opt->option->type;
elems[i].offset = opt->offset;
- elems[i].isset_offset = 0; /* not supported for local relopts yet */
i++;
}
diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c
index aa4fbec143f..696eab9bd97 100644
--- a/src/backend/commands/vacuum.c
+++ b/src/backend/commands/vacuum.c
@@ -2224,9 +2224,9 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams params,
{
StdRdOptions *opts = (StdRdOptions *) rel->rd_options;
- if (opts && opts->vacuum_truncate_set)
+ if (opts && opts->vacuum_truncate != TERNARY_UNSET)
{
- if (opts->vacuum_truncate)
+ if (opts->vacuum_truncate == TERNARY_TRUE)
params.truncate = VACOPTVALUE_ENABLED;
else
params.truncate = VACOPTVALUE_DISABLED;
diff --git a/src/include/access/reloptions.h b/src/include/access/reloptions.h
index 2f08e1b0cf0..dfbef2babf2 100644
--- a/src/include/access/reloptions.h
+++ b/src/include/access/reloptions.h
@@ -29,6 +29,7 @@
typedef enum relopt_type
{
RELOPT_TYPE_BOOL,
+ RELOPT_TYPE_TERNARY, /* on, off, unset */
RELOPT_TYPE_INT,
RELOPT_TYPE_REAL,
RELOPT_TYPE_ENUM,
@@ -80,6 +81,7 @@ typedef struct relopt_value
union
{
bool bool_val;
+ pg_ternary ternary_val;
int int_val;
double real_val;
int enum_val;
@@ -94,6 +96,12 @@ typedef struct relopt_bool
bool default_val;
} relopt_bool;
+typedef struct relopt_ternary
+{
+ relopt_gen gen;
+ int default_val;
+} relopt_ternary;
+
typedef struct relopt_int
{
relopt_gen gen;
@@ -152,19 +160,6 @@ typedef struct
const char *optname; /* option's name */
relopt_type opttype; /* option's datatype */
int offset; /* offset of field in result struct */
-
- /*
- * isset_offset is an optional offset of a field in the result struct that
- * stores whether the option is explicitly set for the relation or if it
- * just picked up the default value. In most cases, this can be
- * accomplished by giving the reloption a special out-of-range default
- * value (e.g., some integer reloptions use -2), but this isn't always
- * possible. For example, a Boolean reloption cannot be given an
- * out-of-range default, so we need another way to discover the source of
- * its value. This offset is only used if given a value greater than
- * zero.
- */
- int isset_offset;
} relopt_parse_elt;
/* Local reloption definition */
@@ -195,6 +190,9 @@ typedef struct local_relopts
extern relopt_kind add_reloption_kind(void);
extern void add_bool_reloption(bits32 kinds, const char *name, const char *desc,
bool default_val, LOCKMODE lockmode);
+extern void add_ternary_reloption(bits32 kinds, const char *name,
+ const char *desc, pg_ternary default_val,
+ LOCKMODE lockmode);
extern void add_int_reloption(bits32 kinds, const char *name, const char *desc,
int default_val, int min_val, int max_val,
LOCKMODE lockmode);
@@ -214,6 +212,9 @@ extern void register_reloptions_validator(local_relopts *relopts,
extern void add_local_bool_reloption(local_relopts *relopts, const char *name,
const char *desc, bool default_val,
int offset);
+extern void add_local_ternary_reloption(local_relopts *relopts,
+ const char *name, const char *desc,
+ pg_ternary default_val, int offset);
extern void add_local_int_reloption(local_relopts *relopts, const char *name,
const char *desc, int default_val,
int min_val, int max_val, int offset);
diff --git a/src/include/postgres.h b/src/include/postgres.h
index 7d93fbce709..47c88780776 100644
--- a/src/include/postgres.h
+++ b/src/include/postgres.h
@@ -543,6 +543,21 @@ Float8GetDatum(float8 X)
* ----------------------------------------------------------------
*/
+/*
+ * pg_ternary
+ * Boolean value with an extra "unset" value
+ *
+ * This enum can be used for values that want to distinguish between true,
+ * false, and unset.
+*/
+
+typedef enum pg_ternary
+{
+ TERNARY_FALSE = 0,
+ TERNARY_TRUE = 1,
+ TERNARY_UNSET = -1
+} pg_ternary;
+
/*
* NON_EXEC_STATIC: It's sometimes useful to define a variable or function
* that is normally static but extern when using EXEC_BACKEND (see
diff --git a/src/include/utils/rel.h b/src/include/utils/rel.h
index d03ab247788..5feb18a5373 100644
--- a/src/include/utils/rel.h
+++ b/src/include/utils/rel.h
@@ -347,8 +347,7 @@ typedef struct StdRdOptions
bool user_catalog_table; /* use as an additional catalog relation */
int parallel_workers; /* max number of parallel workers */
StdRdOptIndexCleanup vacuum_index_cleanup; /* controls index vacuuming */
- bool vacuum_truncate; /* enables vacuum to truncate a relation */
- bool vacuum_truncate_set; /* whether vacuum_truncate is set */
+ pg_ternary vacuum_truncate; /* enables vacuum to truncate a relation */
/*
* Fraction of pages in a relation that vacuum can eagerly scan and fail
diff --git a/src/test/regress/expected/reloptions.out b/src/test/regress/expected/reloptions.out
index 9de19b4e3f1..1c99f79ab01 100644
--- a/src/test/regress/expected/reloptions.out
+++ b/src/test/regress/expected/reloptions.out
@@ -98,6 +98,42 @@ SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass;
{fillfactor=13,autovacuum_enabled=false}
(1 row)
+-- Tests for future (FIXME) ternary options
+-- behave as boolean option: accept unassigned name and truncated value
+DROP TABLE reloptions_test;
+CREATE TABLE reloptions_test(i INT) WITH (vacuum_truncate);
+SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass;
+ reloptions
+------------------------
+ {vacuum_truncate=true}
+(1 row)
+
+DROP TABLE reloptions_test;
+CREATE TABLE reloptions_test(i INT) WITH (vacuum_truncate=FaLS);
+SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass;
+ reloptions
+------------------------
+ {vacuum_truncate=fals}
+(1 row)
+
+-- preferred "true" alias is used when storing
+DROP TABLE reloptions_test;
+CREATE TABLE reloptions_test(i INT) WITH (vacuum_index_cleanup=on);
+SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass;
+ reloptions
+---------------------------
+ {vacuum_index_cleanup=on}
+(1 row)
+
+-- custom "third" value is available
+DROP TABLE reloptions_test;
+CREATE TABLE reloptions_test(i INT) WITH (vacuum_index_cleanup=auto);
+SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass;
+ reloptions
+-----------------------------
+ {vacuum_index_cleanup=auto}
+(1 row)
+
-- Test vacuum_truncate option
DROP TABLE reloptions_test;
CREATE TEMP TABLE reloptions_test(i INT NOT NULL, j text)
diff --git a/src/test/regress/sql/reloptions.sql b/src/test/regress/sql/reloptions.sql
index 24fbe0b478d..f5980dafcbc 100644
--- a/src/test/regress/sql/reloptions.sql
+++ b/src/test/regress/sql/reloptions.sql
@@ -59,6 +59,27 @@ UPDATE pg_class
ALTER TABLE reloptions_test RESET (illegal_option);
SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass;
+-- Tests for future (FIXME) ternary options
+
+-- behave as boolean option: accept unassigned name and truncated value
+DROP TABLE reloptions_test;
+CREATE TABLE reloptions_test(i INT) WITH (vacuum_truncate);
+SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass;
+
+DROP TABLE reloptions_test;
+CREATE TABLE reloptions_test(i INT) WITH (vacuum_truncate=FaLS);
+SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass;
+
+-- preferred "true" alias is used when storing
+DROP TABLE reloptions_test;
+CREATE TABLE reloptions_test(i INT) WITH (vacuum_index_cleanup=on);
+SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass;
+
+-- custom "third" value is available
+DROP TABLE reloptions_test;
+CREATE TABLE reloptions_test(i INT) WITH (vacuum_index_cleanup=auto);
+SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass;
+
-- Test vacuum_truncate option
DROP TABLE reloptions_test;
diff --git a/src/tools/pgindent/typedefs.list b/src/tools/pgindent/typedefs.list
index 3f3a888fd0e..1c8610fd46c 100644
--- a/src/tools/pgindent/typedefs.list
+++ b/src/tools/pgindent/typedefs.list
@@ -3952,6 +3952,7 @@ pg_sha512_ctx
pg_snapshot
pg_special_case
pg_stack_base_t
+pg_ternary
pg_time_t
pg_time_usec_t
pg_tz
@@ -4079,6 +4080,7 @@ relopt_kind
relopt_parse_elt
relopt_real
relopt_string
+relopt_ternary
relopt_type
relopt_value
relopts_validator
--
2.47.3
--c36t3fndxhkzbl5z--
^ permalink raw reply [nested|flat] 868+ messages in thread
* [PATCH v3] Introduce ternary reloptions
@ 2026-01-16 15:04 Álvaro Herrera <[email protected]>
0 siblings, 0 replies; 868+ messages in thread
From: Álvaro Herrera @ 2026-01-16 15:04 UTC (permalink / raw)
Introduce ternary reloption as a replacement for current
`vacuum_truncate` implementation. Remove the `vacuum_truncate_set`
separate flag and use TERNARY_UNSET instead.
This could also be used for other options such as `vacuum_index_cleanup`
and `buffering`, but lets get the scaffolding in first.
Discussion: https://postgr.es/m/3474141.usfYGdeWWP@thinkpad-pgpro
---
src/backend/access/common/reloptions.c | 137 ++++++++++++++++++-----
src/backend/commands/vacuum.c | 4 +-
src/include/access/reloptions.h | 27 ++---
src/include/postgres.h | 15 +++
src/include/utils/rel.h | 3 +-
src/test/regress/expected/reloptions.out | 36 ++++++
src/test/regress/sql/reloptions.sql | 21 ++++
src/tools/pgindent/typedefs.list | 2 +
8 files changed, 202 insertions(+), 43 deletions(-)
diff --git a/src/backend/access/common/reloptions.c b/src/backend/access/common/reloptions.c
index 0b83f98ed5f..6f1f577581a 100644
--- a/src/backend/access/common/reloptions.c
+++ b/src/backend/access/common/reloptions.c
@@ -40,9 +40,9 @@
*
* To add an option:
*
- * (i) decide on a type (bool, integer, real, enum, string), name, default
- * value, upper and lower bounds (if applicable); for strings, consider a
- * validation routine.
+ * (i) decide on a type (bool, ternary, integer, real, enum, string), name,
+ * default value, upper and lower bounds (if applicable); for strings,
+ * consider a validation routine.
* (ii) add a record below (or use add_<type>_reloption).
* (iii) add it to the appropriate options struct (perhaps StdRdOptions)
* (iv) add it to the appropriate handling routine (perhaps
@@ -147,15 +147,6 @@ static relopt_bool boolRelOpts[] =
},
false
},
- {
- {
- "vacuum_truncate",
- "Enables vacuum to truncate empty pages at the end of this table",
- RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
- ShareUpdateExclusiveLock
- },
- true
- },
{
{
"deduplicate_items",
@@ -170,6 +161,25 @@ static relopt_bool boolRelOpts[] =
{{NULL}}
};
+static relopt_ternary ternaryRelOpts[] =
+{
+ {
+ {
+ "vacuum_truncate",
+ "Enables vacuum to truncate empty pages at the end of this table",
+ RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
+ ShareUpdateExclusiveLock
+ },
+ TERNARY_UNSET
+ },
+ /* list terminator */
+ {
+ {
+ NULL
+ }
+ }
+};
+
static relopt_int intRelOpts[] =
{
{
@@ -609,6 +619,13 @@ initialize_reloptions(void)
boolRelOpts[i].gen.lockmode));
j++;
}
+ for (i = 0; ternaryRelOpts[i].gen.name; i++)
+ {
+ Assert(DoLockModesConflict(ternaryRelOpts[i].gen.lockmode,
+ ternaryRelOpts[i].gen.lockmode));
+ j++;
+ }
+
for (i = 0; intRelOpts[i].gen.name; i++)
{
Assert(DoLockModesConflict(intRelOpts[i].gen.lockmode,
@@ -649,6 +666,14 @@ initialize_reloptions(void)
j++;
}
+ for (i = 0; ternaryRelOpts[i].gen.name; i++)
+ {
+ relOpts[j] = &ternaryRelOpts[i].gen;
+ relOpts[j]->type = RELOPT_TYPE_TERNARY;
+ relOpts[j]->namelen = strlen(relOpts[j]->name);
+ j++;
+ }
+
for (i = 0; intRelOpts[i].gen.name; i++)
{
relOpts[j] = &intRelOpts[i].gen;
@@ -809,6 +834,9 @@ allocate_reloption(bits32 kinds, int type, const char *name, const char *desc,
case RELOPT_TYPE_BOOL:
size = sizeof(relopt_bool);
break;
+ case RELOPT_TYPE_TERNARY:
+ size = sizeof(relopt_ternary);
+ break;
case RELOPT_TYPE_INT:
size = sizeof(relopt_int);
break;
@@ -892,6 +920,57 @@ add_local_bool_reloption(local_relopts *relopts, const char *name,
add_local_reloption(relopts, (relopt_gen *) newoption, offset);
}
+/*
+ * init_ternary_reloption
+ * Allocate and initialize a new ternary reloption
+ */
+static relopt_ternary *
+init_ternary_reloption(bits32 kinds, const char *name, const char *desc,
+ pg_ternary default_val, LOCKMODE lockmode)
+{
+ relopt_ternary *newoption;
+
+ newoption = (relopt_ternary *)
+ allocate_reloption(kinds, RELOPT_TYPE_TERNARY, name, desc, lockmode);
+ newoption->default_val = default_val;
+
+ return newoption;
+}
+
+/*
+ * add_ternary_reloption
+ * Add a new ternary reloption
+ */
+void
+add_ternary_reloption(bits32 kinds, const char *name, const char *desc,
+ pg_ternary default_val, LOCKMODE lockmode)
+{
+ relopt_ternary *newoption;
+
+ newoption =
+ init_ternary_reloption(kinds, name, desc, default_val, lockmode);
+
+ add_reloption((relopt_gen *) newoption);
+}
+
+/*
+ * add_local_ternary_reloption
+ * Add a new ternary local reloption
+ *
+ * 'offset' is offset of ternary-typed field.
+ */
+void
+add_local_ternary_reloption(local_relopts *relopts, const char *name,
+ const char *desc, pg_ternary default_val,
+ int offset)
+{
+ relopt_ternary *newoption;
+
+ newoption =
+ init_ternary_reloption(RELOPT_KIND_LOCAL, name, desc, default_val, 0);
+
+ add_local_reloption(relopts, (relopt_gen *) newoption, offset);
+}
/*
* init_real_reloption
@@ -1626,6 +1705,19 @@ parse_one_reloption(relopt_value *option, char *text_str, int text_len,
option->gen->name, value)));
}
break;
+ case RELOPT_TYPE_TERNARY:
+ {
+ bool b;
+
+ parsed = parse_bool(value, &b);
+ option->values.ternary_val = b ? TERNARY_TRUE : TERNARY_FALSE;
+ if (validate && !parsed)
+ ereport(ERROR,
+ (errcode(ERRCODE_INVALID_PARAMETER_VALUE),
+ errmsg("invalid value for ternary option \"%s\": %s",
+ option->gen->name, value)));
+ }
+ break;
case RELOPT_TYPE_INT:
{
relopt_int *optint = (relopt_int *) option->gen;
@@ -1789,17 +1881,6 @@ fillRelOptions(void *rdopts, Size basesize,
char *itempos = ((char *) rdopts) + elems[j].offset;
char *string_val;
- /*
- * If isset_offset is provided, store whether the reloption is
- * set there.
- */
- if (elems[j].isset_offset > 0)
- {
- char *setpos = ((char *) rdopts) + elems[j].isset_offset;
-
- *(bool *) setpos = options[i].isset;
- }
-
switch (options[i].gen->type)
{
case RELOPT_TYPE_BOOL:
@@ -1807,6 +1888,11 @@ fillRelOptions(void *rdopts, Size basesize,
options[i].values.bool_val :
((relopt_bool *) options[i].gen)->default_val;
break;
+ case RELOPT_TYPE_TERNARY:
+ *(pg_ternary *) itempos = options[i].isset ?
+ options[i].values.ternary_val :
+ ((relopt_ternary *) options[i].gen)->default_val;
+ break;
case RELOPT_TYPE_INT:
*(int *) itempos = options[i].isset ?
options[i].values.int_val :
@@ -1923,8 +2009,8 @@ default_reloptions(Datum reloptions, bool validate, relopt_kind kind)
offsetof(StdRdOptions, parallel_workers)},
{"vacuum_index_cleanup", RELOPT_TYPE_ENUM,
offsetof(StdRdOptions, vacuum_index_cleanup)},
- {"vacuum_truncate", RELOPT_TYPE_BOOL,
- offsetof(StdRdOptions, vacuum_truncate), offsetof(StdRdOptions, vacuum_truncate_set)},
+ {"vacuum_truncate", RELOPT_TYPE_TERNARY,
+ offsetof(StdRdOptions, vacuum_truncate)},
{"vacuum_max_eager_freeze_failure_rate", RELOPT_TYPE_REAL,
offsetof(StdRdOptions, vacuum_max_eager_freeze_failure_rate)}
};
@@ -2004,7 +2090,6 @@ build_local_reloptions(local_relopts *relopts, Datum options, bool validate)
elems[i].optname = opt->option->name;
elems[i].opttype = opt->option->type;
elems[i].offset = opt->offset;
- elems[i].isset_offset = 0; /* not supported for local relopts yet */
i++;
}
diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c
index aa4fbec143f..696eab9bd97 100644
--- a/src/backend/commands/vacuum.c
+++ b/src/backend/commands/vacuum.c
@@ -2224,9 +2224,9 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams params,
{
StdRdOptions *opts = (StdRdOptions *) rel->rd_options;
- if (opts && opts->vacuum_truncate_set)
+ if (opts && opts->vacuum_truncate != TERNARY_UNSET)
{
- if (opts->vacuum_truncate)
+ if (opts->vacuum_truncate == TERNARY_TRUE)
params.truncate = VACOPTVALUE_ENABLED;
else
params.truncate = VACOPTVALUE_DISABLED;
diff --git a/src/include/access/reloptions.h b/src/include/access/reloptions.h
index 2f08e1b0cf0..dfbef2babf2 100644
--- a/src/include/access/reloptions.h
+++ b/src/include/access/reloptions.h
@@ -29,6 +29,7 @@
typedef enum relopt_type
{
RELOPT_TYPE_BOOL,
+ RELOPT_TYPE_TERNARY, /* on, off, unset */
RELOPT_TYPE_INT,
RELOPT_TYPE_REAL,
RELOPT_TYPE_ENUM,
@@ -80,6 +81,7 @@ typedef struct relopt_value
union
{
bool bool_val;
+ pg_ternary ternary_val;
int int_val;
double real_val;
int enum_val;
@@ -94,6 +96,12 @@ typedef struct relopt_bool
bool default_val;
} relopt_bool;
+typedef struct relopt_ternary
+{
+ relopt_gen gen;
+ int default_val;
+} relopt_ternary;
+
typedef struct relopt_int
{
relopt_gen gen;
@@ -152,19 +160,6 @@ typedef struct
const char *optname; /* option's name */
relopt_type opttype; /* option's datatype */
int offset; /* offset of field in result struct */
-
- /*
- * isset_offset is an optional offset of a field in the result struct that
- * stores whether the option is explicitly set for the relation or if it
- * just picked up the default value. In most cases, this can be
- * accomplished by giving the reloption a special out-of-range default
- * value (e.g., some integer reloptions use -2), but this isn't always
- * possible. For example, a Boolean reloption cannot be given an
- * out-of-range default, so we need another way to discover the source of
- * its value. This offset is only used if given a value greater than
- * zero.
- */
- int isset_offset;
} relopt_parse_elt;
/* Local reloption definition */
@@ -195,6 +190,9 @@ typedef struct local_relopts
extern relopt_kind add_reloption_kind(void);
extern void add_bool_reloption(bits32 kinds, const char *name, const char *desc,
bool default_val, LOCKMODE lockmode);
+extern void add_ternary_reloption(bits32 kinds, const char *name,
+ const char *desc, pg_ternary default_val,
+ LOCKMODE lockmode);
extern void add_int_reloption(bits32 kinds, const char *name, const char *desc,
int default_val, int min_val, int max_val,
LOCKMODE lockmode);
@@ -214,6 +212,9 @@ extern void register_reloptions_validator(local_relopts *relopts,
extern void add_local_bool_reloption(local_relopts *relopts, const char *name,
const char *desc, bool default_val,
int offset);
+extern void add_local_ternary_reloption(local_relopts *relopts,
+ const char *name, const char *desc,
+ pg_ternary default_val, int offset);
extern void add_local_int_reloption(local_relopts *relopts, const char *name,
const char *desc, int default_val,
int min_val, int max_val, int offset);
diff --git a/src/include/postgres.h b/src/include/postgres.h
index 7d93fbce709..47c88780776 100644
--- a/src/include/postgres.h
+++ b/src/include/postgres.h
@@ -543,6 +543,21 @@ Float8GetDatum(float8 X)
* ----------------------------------------------------------------
*/
+/*
+ * pg_ternary
+ * Boolean value with an extra "unset" value
+ *
+ * This enum can be used for values that want to distinguish between true,
+ * false, and unset.
+*/
+
+typedef enum pg_ternary
+{
+ TERNARY_FALSE = 0,
+ TERNARY_TRUE = 1,
+ TERNARY_UNSET = -1
+} pg_ternary;
+
/*
* NON_EXEC_STATIC: It's sometimes useful to define a variable or function
* that is normally static but extern when using EXEC_BACKEND (see
diff --git a/src/include/utils/rel.h b/src/include/utils/rel.h
index d03ab247788..5feb18a5373 100644
--- a/src/include/utils/rel.h
+++ b/src/include/utils/rel.h
@@ -347,8 +347,7 @@ typedef struct StdRdOptions
bool user_catalog_table; /* use as an additional catalog relation */
int parallel_workers; /* max number of parallel workers */
StdRdOptIndexCleanup vacuum_index_cleanup; /* controls index vacuuming */
- bool vacuum_truncate; /* enables vacuum to truncate a relation */
- bool vacuum_truncate_set; /* whether vacuum_truncate is set */
+ pg_ternary vacuum_truncate; /* enables vacuum to truncate a relation */
/*
* Fraction of pages in a relation that vacuum can eagerly scan and fail
diff --git a/src/test/regress/expected/reloptions.out b/src/test/regress/expected/reloptions.out
index 9de19b4e3f1..1c99f79ab01 100644
--- a/src/test/regress/expected/reloptions.out
+++ b/src/test/regress/expected/reloptions.out
@@ -98,6 +98,42 @@ SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass;
{fillfactor=13,autovacuum_enabled=false}
(1 row)
+-- Tests for future (FIXME) ternary options
+-- behave as boolean option: accept unassigned name and truncated value
+DROP TABLE reloptions_test;
+CREATE TABLE reloptions_test(i INT) WITH (vacuum_truncate);
+SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass;
+ reloptions
+------------------------
+ {vacuum_truncate=true}
+(1 row)
+
+DROP TABLE reloptions_test;
+CREATE TABLE reloptions_test(i INT) WITH (vacuum_truncate=FaLS);
+SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass;
+ reloptions
+------------------------
+ {vacuum_truncate=fals}
+(1 row)
+
+-- preferred "true" alias is used when storing
+DROP TABLE reloptions_test;
+CREATE TABLE reloptions_test(i INT) WITH (vacuum_index_cleanup=on);
+SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass;
+ reloptions
+---------------------------
+ {vacuum_index_cleanup=on}
+(1 row)
+
+-- custom "third" value is available
+DROP TABLE reloptions_test;
+CREATE TABLE reloptions_test(i INT) WITH (vacuum_index_cleanup=auto);
+SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass;
+ reloptions
+-----------------------------
+ {vacuum_index_cleanup=auto}
+(1 row)
+
-- Test vacuum_truncate option
DROP TABLE reloptions_test;
CREATE TEMP TABLE reloptions_test(i INT NOT NULL, j text)
diff --git a/src/test/regress/sql/reloptions.sql b/src/test/regress/sql/reloptions.sql
index 24fbe0b478d..f5980dafcbc 100644
--- a/src/test/regress/sql/reloptions.sql
+++ b/src/test/regress/sql/reloptions.sql
@@ -59,6 +59,27 @@ UPDATE pg_class
ALTER TABLE reloptions_test RESET (illegal_option);
SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass;
+-- Tests for future (FIXME) ternary options
+
+-- behave as boolean option: accept unassigned name and truncated value
+DROP TABLE reloptions_test;
+CREATE TABLE reloptions_test(i INT) WITH (vacuum_truncate);
+SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass;
+
+DROP TABLE reloptions_test;
+CREATE TABLE reloptions_test(i INT) WITH (vacuum_truncate=FaLS);
+SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass;
+
+-- preferred "true" alias is used when storing
+DROP TABLE reloptions_test;
+CREATE TABLE reloptions_test(i INT) WITH (vacuum_index_cleanup=on);
+SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass;
+
+-- custom "third" value is available
+DROP TABLE reloptions_test;
+CREATE TABLE reloptions_test(i INT) WITH (vacuum_index_cleanup=auto);
+SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass;
+
-- Test vacuum_truncate option
DROP TABLE reloptions_test;
diff --git a/src/tools/pgindent/typedefs.list b/src/tools/pgindent/typedefs.list
index 3f3a888fd0e..1c8610fd46c 100644
--- a/src/tools/pgindent/typedefs.list
+++ b/src/tools/pgindent/typedefs.list
@@ -3952,6 +3952,7 @@ pg_sha512_ctx
pg_snapshot
pg_special_case
pg_stack_base_t
+pg_ternary
pg_time_t
pg_time_usec_t
pg_tz
@@ -4079,6 +4080,7 @@ relopt_kind
relopt_parse_elt
relopt_real
relopt_string
+relopt_ternary
relopt_type
relopt_value
relopts_validator
--
2.47.3
--c36t3fndxhkzbl5z--
^ permalink raw reply [nested|flat] 868+ messages in thread
* [PATCH v3] Introduce ternary reloptions
@ 2026-01-16 15:04 Álvaro Herrera <[email protected]>
0 siblings, 0 replies; 868+ messages in thread
From: Álvaro Herrera @ 2026-01-16 15:04 UTC (permalink / raw)
Introduce ternary reloption as a replacement for current
`vacuum_truncate` implementation. Remove the `vacuum_truncate_set`
separate flag and use TERNARY_UNSET instead.
This could also be used for other options such as `vacuum_index_cleanup`
and `buffering`, but lets get the scaffolding in first.
Discussion: https://postgr.es/m/3474141.usfYGdeWWP@thinkpad-pgpro
---
src/backend/access/common/reloptions.c | 137 ++++++++++++++++++-----
src/backend/commands/vacuum.c | 4 +-
src/include/access/reloptions.h | 27 ++---
src/include/postgres.h | 15 +++
src/include/utils/rel.h | 3 +-
src/test/regress/expected/reloptions.out | 36 ++++++
src/test/regress/sql/reloptions.sql | 21 ++++
src/tools/pgindent/typedefs.list | 2 +
8 files changed, 202 insertions(+), 43 deletions(-)
diff --git a/src/backend/access/common/reloptions.c b/src/backend/access/common/reloptions.c
index 0b83f98ed5f..6f1f577581a 100644
--- a/src/backend/access/common/reloptions.c
+++ b/src/backend/access/common/reloptions.c
@@ -40,9 +40,9 @@
*
* To add an option:
*
- * (i) decide on a type (bool, integer, real, enum, string), name, default
- * value, upper and lower bounds (if applicable); for strings, consider a
- * validation routine.
+ * (i) decide on a type (bool, ternary, integer, real, enum, string), name,
+ * default value, upper and lower bounds (if applicable); for strings,
+ * consider a validation routine.
* (ii) add a record below (or use add_<type>_reloption).
* (iii) add it to the appropriate options struct (perhaps StdRdOptions)
* (iv) add it to the appropriate handling routine (perhaps
@@ -147,15 +147,6 @@ static relopt_bool boolRelOpts[] =
},
false
},
- {
- {
- "vacuum_truncate",
- "Enables vacuum to truncate empty pages at the end of this table",
- RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
- ShareUpdateExclusiveLock
- },
- true
- },
{
{
"deduplicate_items",
@@ -170,6 +161,25 @@ static relopt_bool boolRelOpts[] =
{{NULL}}
};
+static relopt_ternary ternaryRelOpts[] =
+{
+ {
+ {
+ "vacuum_truncate",
+ "Enables vacuum to truncate empty pages at the end of this table",
+ RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
+ ShareUpdateExclusiveLock
+ },
+ TERNARY_UNSET
+ },
+ /* list terminator */
+ {
+ {
+ NULL
+ }
+ }
+};
+
static relopt_int intRelOpts[] =
{
{
@@ -609,6 +619,13 @@ initialize_reloptions(void)
boolRelOpts[i].gen.lockmode));
j++;
}
+ for (i = 0; ternaryRelOpts[i].gen.name; i++)
+ {
+ Assert(DoLockModesConflict(ternaryRelOpts[i].gen.lockmode,
+ ternaryRelOpts[i].gen.lockmode));
+ j++;
+ }
+
for (i = 0; intRelOpts[i].gen.name; i++)
{
Assert(DoLockModesConflict(intRelOpts[i].gen.lockmode,
@@ -649,6 +666,14 @@ initialize_reloptions(void)
j++;
}
+ for (i = 0; ternaryRelOpts[i].gen.name; i++)
+ {
+ relOpts[j] = &ternaryRelOpts[i].gen;
+ relOpts[j]->type = RELOPT_TYPE_TERNARY;
+ relOpts[j]->namelen = strlen(relOpts[j]->name);
+ j++;
+ }
+
for (i = 0; intRelOpts[i].gen.name; i++)
{
relOpts[j] = &intRelOpts[i].gen;
@@ -809,6 +834,9 @@ allocate_reloption(bits32 kinds, int type, const char *name, const char *desc,
case RELOPT_TYPE_BOOL:
size = sizeof(relopt_bool);
break;
+ case RELOPT_TYPE_TERNARY:
+ size = sizeof(relopt_ternary);
+ break;
case RELOPT_TYPE_INT:
size = sizeof(relopt_int);
break;
@@ -892,6 +920,57 @@ add_local_bool_reloption(local_relopts *relopts, const char *name,
add_local_reloption(relopts, (relopt_gen *) newoption, offset);
}
+/*
+ * init_ternary_reloption
+ * Allocate and initialize a new ternary reloption
+ */
+static relopt_ternary *
+init_ternary_reloption(bits32 kinds, const char *name, const char *desc,
+ pg_ternary default_val, LOCKMODE lockmode)
+{
+ relopt_ternary *newoption;
+
+ newoption = (relopt_ternary *)
+ allocate_reloption(kinds, RELOPT_TYPE_TERNARY, name, desc, lockmode);
+ newoption->default_val = default_val;
+
+ return newoption;
+}
+
+/*
+ * add_ternary_reloption
+ * Add a new ternary reloption
+ */
+void
+add_ternary_reloption(bits32 kinds, const char *name, const char *desc,
+ pg_ternary default_val, LOCKMODE lockmode)
+{
+ relopt_ternary *newoption;
+
+ newoption =
+ init_ternary_reloption(kinds, name, desc, default_val, lockmode);
+
+ add_reloption((relopt_gen *) newoption);
+}
+
+/*
+ * add_local_ternary_reloption
+ * Add a new ternary local reloption
+ *
+ * 'offset' is offset of ternary-typed field.
+ */
+void
+add_local_ternary_reloption(local_relopts *relopts, const char *name,
+ const char *desc, pg_ternary default_val,
+ int offset)
+{
+ relopt_ternary *newoption;
+
+ newoption =
+ init_ternary_reloption(RELOPT_KIND_LOCAL, name, desc, default_val, 0);
+
+ add_local_reloption(relopts, (relopt_gen *) newoption, offset);
+}
/*
* init_real_reloption
@@ -1626,6 +1705,19 @@ parse_one_reloption(relopt_value *option, char *text_str, int text_len,
option->gen->name, value)));
}
break;
+ case RELOPT_TYPE_TERNARY:
+ {
+ bool b;
+
+ parsed = parse_bool(value, &b);
+ option->values.ternary_val = b ? TERNARY_TRUE : TERNARY_FALSE;
+ if (validate && !parsed)
+ ereport(ERROR,
+ (errcode(ERRCODE_INVALID_PARAMETER_VALUE),
+ errmsg("invalid value for ternary option \"%s\": %s",
+ option->gen->name, value)));
+ }
+ break;
case RELOPT_TYPE_INT:
{
relopt_int *optint = (relopt_int *) option->gen;
@@ -1789,17 +1881,6 @@ fillRelOptions(void *rdopts, Size basesize,
char *itempos = ((char *) rdopts) + elems[j].offset;
char *string_val;
- /*
- * If isset_offset is provided, store whether the reloption is
- * set there.
- */
- if (elems[j].isset_offset > 0)
- {
- char *setpos = ((char *) rdopts) + elems[j].isset_offset;
-
- *(bool *) setpos = options[i].isset;
- }
-
switch (options[i].gen->type)
{
case RELOPT_TYPE_BOOL:
@@ -1807,6 +1888,11 @@ fillRelOptions(void *rdopts, Size basesize,
options[i].values.bool_val :
((relopt_bool *) options[i].gen)->default_val;
break;
+ case RELOPT_TYPE_TERNARY:
+ *(pg_ternary *) itempos = options[i].isset ?
+ options[i].values.ternary_val :
+ ((relopt_ternary *) options[i].gen)->default_val;
+ break;
case RELOPT_TYPE_INT:
*(int *) itempos = options[i].isset ?
options[i].values.int_val :
@@ -1923,8 +2009,8 @@ default_reloptions(Datum reloptions, bool validate, relopt_kind kind)
offsetof(StdRdOptions, parallel_workers)},
{"vacuum_index_cleanup", RELOPT_TYPE_ENUM,
offsetof(StdRdOptions, vacuum_index_cleanup)},
- {"vacuum_truncate", RELOPT_TYPE_BOOL,
- offsetof(StdRdOptions, vacuum_truncate), offsetof(StdRdOptions, vacuum_truncate_set)},
+ {"vacuum_truncate", RELOPT_TYPE_TERNARY,
+ offsetof(StdRdOptions, vacuum_truncate)},
{"vacuum_max_eager_freeze_failure_rate", RELOPT_TYPE_REAL,
offsetof(StdRdOptions, vacuum_max_eager_freeze_failure_rate)}
};
@@ -2004,7 +2090,6 @@ build_local_reloptions(local_relopts *relopts, Datum options, bool validate)
elems[i].optname = opt->option->name;
elems[i].opttype = opt->option->type;
elems[i].offset = opt->offset;
- elems[i].isset_offset = 0; /* not supported for local relopts yet */
i++;
}
diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c
index aa4fbec143f..696eab9bd97 100644
--- a/src/backend/commands/vacuum.c
+++ b/src/backend/commands/vacuum.c
@@ -2224,9 +2224,9 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams params,
{
StdRdOptions *opts = (StdRdOptions *) rel->rd_options;
- if (opts && opts->vacuum_truncate_set)
+ if (opts && opts->vacuum_truncate != TERNARY_UNSET)
{
- if (opts->vacuum_truncate)
+ if (opts->vacuum_truncate == TERNARY_TRUE)
params.truncate = VACOPTVALUE_ENABLED;
else
params.truncate = VACOPTVALUE_DISABLED;
diff --git a/src/include/access/reloptions.h b/src/include/access/reloptions.h
index 2f08e1b0cf0..dfbef2babf2 100644
--- a/src/include/access/reloptions.h
+++ b/src/include/access/reloptions.h
@@ -29,6 +29,7 @@
typedef enum relopt_type
{
RELOPT_TYPE_BOOL,
+ RELOPT_TYPE_TERNARY, /* on, off, unset */
RELOPT_TYPE_INT,
RELOPT_TYPE_REAL,
RELOPT_TYPE_ENUM,
@@ -80,6 +81,7 @@ typedef struct relopt_value
union
{
bool bool_val;
+ pg_ternary ternary_val;
int int_val;
double real_val;
int enum_val;
@@ -94,6 +96,12 @@ typedef struct relopt_bool
bool default_val;
} relopt_bool;
+typedef struct relopt_ternary
+{
+ relopt_gen gen;
+ int default_val;
+} relopt_ternary;
+
typedef struct relopt_int
{
relopt_gen gen;
@@ -152,19 +160,6 @@ typedef struct
const char *optname; /* option's name */
relopt_type opttype; /* option's datatype */
int offset; /* offset of field in result struct */
-
- /*
- * isset_offset is an optional offset of a field in the result struct that
- * stores whether the option is explicitly set for the relation or if it
- * just picked up the default value. In most cases, this can be
- * accomplished by giving the reloption a special out-of-range default
- * value (e.g., some integer reloptions use -2), but this isn't always
- * possible. For example, a Boolean reloption cannot be given an
- * out-of-range default, so we need another way to discover the source of
- * its value. This offset is only used if given a value greater than
- * zero.
- */
- int isset_offset;
} relopt_parse_elt;
/* Local reloption definition */
@@ -195,6 +190,9 @@ typedef struct local_relopts
extern relopt_kind add_reloption_kind(void);
extern void add_bool_reloption(bits32 kinds, const char *name, const char *desc,
bool default_val, LOCKMODE lockmode);
+extern void add_ternary_reloption(bits32 kinds, const char *name,
+ const char *desc, pg_ternary default_val,
+ LOCKMODE lockmode);
extern void add_int_reloption(bits32 kinds, const char *name, const char *desc,
int default_val, int min_val, int max_val,
LOCKMODE lockmode);
@@ -214,6 +212,9 @@ extern void register_reloptions_validator(local_relopts *relopts,
extern void add_local_bool_reloption(local_relopts *relopts, const char *name,
const char *desc, bool default_val,
int offset);
+extern void add_local_ternary_reloption(local_relopts *relopts,
+ const char *name, const char *desc,
+ pg_ternary default_val, int offset);
extern void add_local_int_reloption(local_relopts *relopts, const char *name,
const char *desc, int default_val,
int min_val, int max_val, int offset);
diff --git a/src/include/postgres.h b/src/include/postgres.h
index 7d93fbce709..47c88780776 100644
--- a/src/include/postgres.h
+++ b/src/include/postgres.h
@@ -543,6 +543,21 @@ Float8GetDatum(float8 X)
* ----------------------------------------------------------------
*/
+/*
+ * pg_ternary
+ * Boolean value with an extra "unset" value
+ *
+ * This enum can be used for values that want to distinguish between true,
+ * false, and unset.
+*/
+
+typedef enum pg_ternary
+{
+ TERNARY_FALSE = 0,
+ TERNARY_TRUE = 1,
+ TERNARY_UNSET = -1
+} pg_ternary;
+
/*
* NON_EXEC_STATIC: It's sometimes useful to define a variable or function
* that is normally static but extern when using EXEC_BACKEND (see
diff --git a/src/include/utils/rel.h b/src/include/utils/rel.h
index d03ab247788..5feb18a5373 100644
--- a/src/include/utils/rel.h
+++ b/src/include/utils/rel.h
@@ -347,8 +347,7 @@ typedef struct StdRdOptions
bool user_catalog_table; /* use as an additional catalog relation */
int parallel_workers; /* max number of parallel workers */
StdRdOptIndexCleanup vacuum_index_cleanup; /* controls index vacuuming */
- bool vacuum_truncate; /* enables vacuum to truncate a relation */
- bool vacuum_truncate_set; /* whether vacuum_truncate is set */
+ pg_ternary vacuum_truncate; /* enables vacuum to truncate a relation */
/*
* Fraction of pages in a relation that vacuum can eagerly scan and fail
diff --git a/src/test/regress/expected/reloptions.out b/src/test/regress/expected/reloptions.out
index 9de19b4e3f1..1c99f79ab01 100644
--- a/src/test/regress/expected/reloptions.out
+++ b/src/test/regress/expected/reloptions.out
@@ -98,6 +98,42 @@ SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass;
{fillfactor=13,autovacuum_enabled=false}
(1 row)
+-- Tests for future (FIXME) ternary options
+-- behave as boolean option: accept unassigned name and truncated value
+DROP TABLE reloptions_test;
+CREATE TABLE reloptions_test(i INT) WITH (vacuum_truncate);
+SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass;
+ reloptions
+------------------------
+ {vacuum_truncate=true}
+(1 row)
+
+DROP TABLE reloptions_test;
+CREATE TABLE reloptions_test(i INT) WITH (vacuum_truncate=FaLS);
+SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass;
+ reloptions
+------------------------
+ {vacuum_truncate=fals}
+(1 row)
+
+-- preferred "true" alias is used when storing
+DROP TABLE reloptions_test;
+CREATE TABLE reloptions_test(i INT) WITH (vacuum_index_cleanup=on);
+SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass;
+ reloptions
+---------------------------
+ {vacuum_index_cleanup=on}
+(1 row)
+
+-- custom "third" value is available
+DROP TABLE reloptions_test;
+CREATE TABLE reloptions_test(i INT) WITH (vacuum_index_cleanup=auto);
+SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass;
+ reloptions
+-----------------------------
+ {vacuum_index_cleanup=auto}
+(1 row)
+
-- Test vacuum_truncate option
DROP TABLE reloptions_test;
CREATE TEMP TABLE reloptions_test(i INT NOT NULL, j text)
diff --git a/src/test/regress/sql/reloptions.sql b/src/test/regress/sql/reloptions.sql
index 24fbe0b478d..f5980dafcbc 100644
--- a/src/test/regress/sql/reloptions.sql
+++ b/src/test/regress/sql/reloptions.sql
@@ -59,6 +59,27 @@ UPDATE pg_class
ALTER TABLE reloptions_test RESET (illegal_option);
SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass;
+-- Tests for future (FIXME) ternary options
+
+-- behave as boolean option: accept unassigned name and truncated value
+DROP TABLE reloptions_test;
+CREATE TABLE reloptions_test(i INT) WITH (vacuum_truncate);
+SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass;
+
+DROP TABLE reloptions_test;
+CREATE TABLE reloptions_test(i INT) WITH (vacuum_truncate=FaLS);
+SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass;
+
+-- preferred "true" alias is used when storing
+DROP TABLE reloptions_test;
+CREATE TABLE reloptions_test(i INT) WITH (vacuum_index_cleanup=on);
+SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass;
+
+-- custom "third" value is available
+DROP TABLE reloptions_test;
+CREATE TABLE reloptions_test(i INT) WITH (vacuum_index_cleanup=auto);
+SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass;
+
-- Test vacuum_truncate option
DROP TABLE reloptions_test;
diff --git a/src/tools/pgindent/typedefs.list b/src/tools/pgindent/typedefs.list
index 3f3a888fd0e..1c8610fd46c 100644
--- a/src/tools/pgindent/typedefs.list
+++ b/src/tools/pgindent/typedefs.list
@@ -3952,6 +3952,7 @@ pg_sha512_ctx
pg_snapshot
pg_special_case
pg_stack_base_t
+pg_ternary
pg_time_t
pg_time_usec_t
pg_tz
@@ -4079,6 +4080,7 @@ relopt_kind
relopt_parse_elt
relopt_real
relopt_string
+relopt_ternary
relopt_type
relopt_value
relopts_validator
--
2.47.3
--c36t3fndxhkzbl5z--
^ permalink raw reply [nested|flat] 868+ messages in thread
* [PATCH v3] Introduce ternary reloptions
@ 2026-01-16 15:04 Álvaro Herrera <[email protected]>
0 siblings, 0 replies; 868+ messages in thread
From: Álvaro Herrera @ 2026-01-16 15:04 UTC (permalink / raw)
Introduce ternary reloption as a replacement for current
`vacuum_truncate` implementation. Remove the `vacuum_truncate_set`
separate flag and use TERNARY_UNSET instead.
This could also be used for other options such as `vacuum_index_cleanup`
and `buffering`, but lets get the scaffolding in first.
Discussion: https://postgr.es/m/3474141.usfYGdeWWP@thinkpad-pgpro
---
src/backend/access/common/reloptions.c | 137 ++++++++++++++++++-----
src/backend/commands/vacuum.c | 4 +-
src/include/access/reloptions.h | 27 ++---
src/include/postgres.h | 15 +++
src/include/utils/rel.h | 3 +-
src/test/regress/expected/reloptions.out | 36 ++++++
src/test/regress/sql/reloptions.sql | 21 ++++
src/tools/pgindent/typedefs.list | 2 +
8 files changed, 202 insertions(+), 43 deletions(-)
diff --git a/src/backend/access/common/reloptions.c b/src/backend/access/common/reloptions.c
index 0b83f98ed5f..6f1f577581a 100644
--- a/src/backend/access/common/reloptions.c
+++ b/src/backend/access/common/reloptions.c
@@ -40,9 +40,9 @@
*
* To add an option:
*
- * (i) decide on a type (bool, integer, real, enum, string), name, default
- * value, upper and lower bounds (if applicable); for strings, consider a
- * validation routine.
+ * (i) decide on a type (bool, ternary, integer, real, enum, string), name,
+ * default value, upper and lower bounds (if applicable); for strings,
+ * consider a validation routine.
* (ii) add a record below (or use add_<type>_reloption).
* (iii) add it to the appropriate options struct (perhaps StdRdOptions)
* (iv) add it to the appropriate handling routine (perhaps
@@ -147,15 +147,6 @@ static relopt_bool boolRelOpts[] =
},
false
},
- {
- {
- "vacuum_truncate",
- "Enables vacuum to truncate empty pages at the end of this table",
- RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
- ShareUpdateExclusiveLock
- },
- true
- },
{
{
"deduplicate_items",
@@ -170,6 +161,25 @@ static relopt_bool boolRelOpts[] =
{{NULL}}
};
+static relopt_ternary ternaryRelOpts[] =
+{
+ {
+ {
+ "vacuum_truncate",
+ "Enables vacuum to truncate empty pages at the end of this table",
+ RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
+ ShareUpdateExclusiveLock
+ },
+ TERNARY_UNSET
+ },
+ /* list terminator */
+ {
+ {
+ NULL
+ }
+ }
+};
+
static relopt_int intRelOpts[] =
{
{
@@ -609,6 +619,13 @@ initialize_reloptions(void)
boolRelOpts[i].gen.lockmode));
j++;
}
+ for (i = 0; ternaryRelOpts[i].gen.name; i++)
+ {
+ Assert(DoLockModesConflict(ternaryRelOpts[i].gen.lockmode,
+ ternaryRelOpts[i].gen.lockmode));
+ j++;
+ }
+
for (i = 0; intRelOpts[i].gen.name; i++)
{
Assert(DoLockModesConflict(intRelOpts[i].gen.lockmode,
@@ -649,6 +666,14 @@ initialize_reloptions(void)
j++;
}
+ for (i = 0; ternaryRelOpts[i].gen.name; i++)
+ {
+ relOpts[j] = &ternaryRelOpts[i].gen;
+ relOpts[j]->type = RELOPT_TYPE_TERNARY;
+ relOpts[j]->namelen = strlen(relOpts[j]->name);
+ j++;
+ }
+
for (i = 0; intRelOpts[i].gen.name; i++)
{
relOpts[j] = &intRelOpts[i].gen;
@@ -809,6 +834,9 @@ allocate_reloption(bits32 kinds, int type, const char *name, const char *desc,
case RELOPT_TYPE_BOOL:
size = sizeof(relopt_bool);
break;
+ case RELOPT_TYPE_TERNARY:
+ size = sizeof(relopt_ternary);
+ break;
case RELOPT_TYPE_INT:
size = sizeof(relopt_int);
break;
@@ -892,6 +920,57 @@ add_local_bool_reloption(local_relopts *relopts, const char *name,
add_local_reloption(relopts, (relopt_gen *) newoption, offset);
}
+/*
+ * init_ternary_reloption
+ * Allocate and initialize a new ternary reloption
+ */
+static relopt_ternary *
+init_ternary_reloption(bits32 kinds, const char *name, const char *desc,
+ pg_ternary default_val, LOCKMODE lockmode)
+{
+ relopt_ternary *newoption;
+
+ newoption = (relopt_ternary *)
+ allocate_reloption(kinds, RELOPT_TYPE_TERNARY, name, desc, lockmode);
+ newoption->default_val = default_val;
+
+ return newoption;
+}
+
+/*
+ * add_ternary_reloption
+ * Add a new ternary reloption
+ */
+void
+add_ternary_reloption(bits32 kinds, const char *name, const char *desc,
+ pg_ternary default_val, LOCKMODE lockmode)
+{
+ relopt_ternary *newoption;
+
+ newoption =
+ init_ternary_reloption(kinds, name, desc, default_val, lockmode);
+
+ add_reloption((relopt_gen *) newoption);
+}
+
+/*
+ * add_local_ternary_reloption
+ * Add a new ternary local reloption
+ *
+ * 'offset' is offset of ternary-typed field.
+ */
+void
+add_local_ternary_reloption(local_relopts *relopts, const char *name,
+ const char *desc, pg_ternary default_val,
+ int offset)
+{
+ relopt_ternary *newoption;
+
+ newoption =
+ init_ternary_reloption(RELOPT_KIND_LOCAL, name, desc, default_val, 0);
+
+ add_local_reloption(relopts, (relopt_gen *) newoption, offset);
+}
/*
* init_real_reloption
@@ -1626,6 +1705,19 @@ parse_one_reloption(relopt_value *option, char *text_str, int text_len,
option->gen->name, value)));
}
break;
+ case RELOPT_TYPE_TERNARY:
+ {
+ bool b;
+
+ parsed = parse_bool(value, &b);
+ option->values.ternary_val = b ? TERNARY_TRUE : TERNARY_FALSE;
+ if (validate && !parsed)
+ ereport(ERROR,
+ (errcode(ERRCODE_INVALID_PARAMETER_VALUE),
+ errmsg("invalid value for ternary option \"%s\": %s",
+ option->gen->name, value)));
+ }
+ break;
case RELOPT_TYPE_INT:
{
relopt_int *optint = (relopt_int *) option->gen;
@@ -1789,17 +1881,6 @@ fillRelOptions(void *rdopts, Size basesize,
char *itempos = ((char *) rdopts) + elems[j].offset;
char *string_val;
- /*
- * If isset_offset is provided, store whether the reloption is
- * set there.
- */
- if (elems[j].isset_offset > 0)
- {
- char *setpos = ((char *) rdopts) + elems[j].isset_offset;
-
- *(bool *) setpos = options[i].isset;
- }
-
switch (options[i].gen->type)
{
case RELOPT_TYPE_BOOL:
@@ -1807,6 +1888,11 @@ fillRelOptions(void *rdopts, Size basesize,
options[i].values.bool_val :
((relopt_bool *) options[i].gen)->default_val;
break;
+ case RELOPT_TYPE_TERNARY:
+ *(pg_ternary *) itempos = options[i].isset ?
+ options[i].values.ternary_val :
+ ((relopt_ternary *) options[i].gen)->default_val;
+ break;
case RELOPT_TYPE_INT:
*(int *) itempos = options[i].isset ?
options[i].values.int_val :
@@ -1923,8 +2009,8 @@ default_reloptions(Datum reloptions, bool validate, relopt_kind kind)
offsetof(StdRdOptions, parallel_workers)},
{"vacuum_index_cleanup", RELOPT_TYPE_ENUM,
offsetof(StdRdOptions, vacuum_index_cleanup)},
- {"vacuum_truncate", RELOPT_TYPE_BOOL,
- offsetof(StdRdOptions, vacuum_truncate), offsetof(StdRdOptions, vacuum_truncate_set)},
+ {"vacuum_truncate", RELOPT_TYPE_TERNARY,
+ offsetof(StdRdOptions, vacuum_truncate)},
{"vacuum_max_eager_freeze_failure_rate", RELOPT_TYPE_REAL,
offsetof(StdRdOptions, vacuum_max_eager_freeze_failure_rate)}
};
@@ -2004,7 +2090,6 @@ build_local_reloptions(local_relopts *relopts, Datum options, bool validate)
elems[i].optname = opt->option->name;
elems[i].opttype = opt->option->type;
elems[i].offset = opt->offset;
- elems[i].isset_offset = 0; /* not supported for local relopts yet */
i++;
}
diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c
index aa4fbec143f..696eab9bd97 100644
--- a/src/backend/commands/vacuum.c
+++ b/src/backend/commands/vacuum.c
@@ -2224,9 +2224,9 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams params,
{
StdRdOptions *opts = (StdRdOptions *) rel->rd_options;
- if (opts && opts->vacuum_truncate_set)
+ if (opts && opts->vacuum_truncate != TERNARY_UNSET)
{
- if (opts->vacuum_truncate)
+ if (opts->vacuum_truncate == TERNARY_TRUE)
params.truncate = VACOPTVALUE_ENABLED;
else
params.truncate = VACOPTVALUE_DISABLED;
diff --git a/src/include/access/reloptions.h b/src/include/access/reloptions.h
index 2f08e1b0cf0..dfbef2babf2 100644
--- a/src/include/access/reloptions.h
+++ b/src/include/access/reloptions.h
@@ -29,6 +29,7 @@
typedef enum relopt_type
{
RELOPT_TYPE_BOOL,
+ RELOPT_TYPE_TERNARY, /* on, off, unset */
RELOPT_TYPE_INT,
RELOPT_TYPE_REAL,
RELOPT_TYPE_ENUM,
@@ -80,6 +81,7 @@ typedef struct relopt_value
union
{
bool bool_val;
+ pg_ternary ternary_val;
int int_val;
double real_val;
int enum_val;
@@ -94,6 +96,12 @@ typedef struct relopt_bool
bool default_val;
} relopt_bool;
+typedef struct relopt_ternary
+{
+ relopt_gen gen;
+ int default_val;
+} relopt_ternary;
+
typedef struct relopt_int
{
relopt_gen gen;
@@ -152,19 +160,6 @@ typedef struct
const char *optname; /* option's name */
relopt_type opttype; /* option's datatype */
int offset; /* offset of field in result struct */
-
- /*
- * isset_offset is an optional offset of a field in the result struct that
- * stores whether the option is explicitly set for the relation or if it
- * just picked up the default value. In most cases, this can be
- * accomplished by giving the reloption a special out-of-range default
- * value (e.g., some integer reloptions use -2), but this isn't always
- * possible. For example, a Boolean reloption cannot be given an
- * out-of-range default, so we need another way to discover the source of
- * its value. This offset is only used if given a value greater than
- * zero.
- */
- int isset_offset;
} relopt_parse_elt;
/* Local reloption definition */
@@ -195,6 +190,9 @@ typedef struct local_relopts
extern relopt_kind add_reloption_kind(void);
extern void add_bool_reloption(bits32 kinds, const char *name, const char *desc,
bool default_val, LOCKMODE lockmode);
+extern void add_ternary_reloption(bits32 kinds, const char *name,
+ const char *desc, pg_ternary default_val,
+ LOCKMODE lockmode);
extern void add_int_reloption(bits32 kinds, const char *name, const char *desc,
int default_val, int min_val, int max_val,
LOCKMODE lockmode);
@@ -214,6 +212,9 @@ extern void register_reloptions_validator(local_relopts *relopts,
extern void add_local_bool_reloption(local_relopts *relopts, const char *name,
const char *desc, bool default_val,
int offset);
+extern void add_local_ternary_reloption(local_relopts *relopts,
+ const char *name, const char *desc,
+ pg_ternary default_val, int offset);
extern void add_local_int_reloption(local_relopts *relopts, const char *name,
const char *desc, int default_val,
int min_val, int max_val, int offset);
diff --git a/src/include/postgres.h b/src/include/postgres.h
index 7d93fbce709..47c88780776 100644
--- a/src/include/postgres.h
+++ b/src/include/postgres.h
@@ -543,6 +543,21 @@ Float8GetDatum(float8 X)
* ----------------------------------------------------------------
*/
+/*
+ * pg_ternary
+ * Boolean value with an extra "unset" value
+ *
+ * This enum can be used for values that want to distinguish between true,
+ * false, and unset.
+*/
+
+typedef enum pg_ternary
+{
+ TERNARY_FALSE = 0,
+ TERNARY_TRUE = 1,
+ TERNARY_UNSET = -1
+} pg_ternary;
+
/*
* NON_EXEC_STATIC: It's sometimes useful to define a variable or function
* that is normally static but extern when using EXEC_BACKEND (see
diff --git a/src/include/utils/rel.h b/src/include/utils/rel.h
index d03ab247788..5feb18a5373 100644
--- a/src/include/utils/rel.h
+++ b/src/include/utils/rel.h
@@ -347,8 +347,7 @@ typedef struct StdRdOptions
bool user_catalog_table; /* use as an additional catalog relation */
int parallel_workers; /* max number of parallel workers */
StdRdOptIndexCleanup vacuum_index_cleanup; /* controls index vacuuming */
- bool vacuum_truncate; /* enables vacuum to truncate a relation */
- bool vacuum_truncate_set; /* whether vacuum_truncate is set */
+ pg_ternary vacuum_truncate; /* enables vacuum to truncate a relation */
/*
* Fraction of pages in a relation that vacuum can eagerly scan and fail
diff --git a/src/test/regress/expected/reloptions.out b/src/test/regress/expected/reloptions.out
index 9de19b4e3f1..1c99f79ab01 100644
--- a/src/test/regress/expected/reloptions.out
+++ b/src/test/regress/expected/reloptions.out
@@ -98,6 +98,42 @@ SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass;
{fillfactor=13,autovacuum_enabled=false}
(1 row)
+-- Tests for future (FIXME) ternary options
+-- behave as boolean option: accept unassigned name and truncated value
+DROP TABLE reloptions_test;
+CREATE TABLE reloptions_test(i INT) WITH (vacuum_truncate);
+SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass;
+ reloptions
+------------------------
+ {vacuum_truncate=true}
+(1 row)
+
+DROP TABLE reloptions_test;
+CREATE TABLE reloptions_test(i INT) WITH (vacuum_truncate=FaLS);
+SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass;
+ reloptions
+------------------------
+ {vacuum_truncate=fals}
+(1 row)
+
+-- preferred "true" alias is used when storing
+DROP TABLE reloptions_test;
+CREATE TABLE reloptions_test(i INT) WITH (vacuum_index_cleanup=on);
+SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass;
+ reloptions
+---------------------------
+ {vacuum_index_cleanup=on}
+(1 row)
+
+-- custom "third" value is available
+DROP TABLE reloptions_test;
+CREATE TABLE reloptions_test(i INT) WITH (vacuum_index_cleanup=auto);
+SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass;
+ reloptions
+-----------------------------
+ {vacuum_index_cleanup=auto}
+(1 row)
+
-- Test vacuum_truncate option
DROP TABLE reloptions_test;
CREATE TEMP TABLE reloptions_test(i INT NOT NULL, j text)
diff --git a/src/test/regress/sql/reloptions.sql b/src/test/regress/sql/reloptions.sql
index 24fbe0b478d..f5980dafcbc 100644
--- a/src/test/regress/sql/reloptions.sql
+++ b/src/test/regress/sql/reloptions.sql
@@ -59,6 +59,27 @@ UPDATE pg_class
ALTER TABLE reloptions_test RESET (illegal_option);
SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass;
+-- Tests for future (FIXME) ternary options
+
+-- behave as boolean option: accept unassigned name and truncated value
+DROP TABLE reloptions_test;
+CREATE TABLE reloptions_test(i INT) WITH (vacuum_truncate);
+SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass;
+
+DROP TABLE reloptions_test;
+CREATE TABLE reloptions_test(i INT) WITH (vacuum_truncate=FaLS);
+SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass;
+
+-- preferred "true" alias is used when storing
+DROP TABLE reloptions_test;
+CREATE TABLE reloptions_test(i INT) WITH (vacuum_index_cleanup=on);
+SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass;
+
+-- custom "third" value is available
+DROP TABLE reloptions_test;
+CREATE TABLE reloptions_test(i INT) WITH (vacuum_index_cleanup=auto);
+SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass;
+
-- Test vacuum_truncate option
DROP TABLE reloptions_test;
diff --git a/src/tools/pgindent/typedefs.list b/src/tools/pgindent/typedefs.list
index 3f3a888fd0e..1c8610fd46c 100644
--- a/src/tools/pgindent/typedefs.list
+++ b/src/tools/pgindent/typedefs.list
@@ -3952,6 +3952,7 @@ pg_sha512_ctx
pg_snapshot
pg_special_case
pg_stack_base_t
+pg_ternary
pg_time_t
pg_time_usec_t
pg_tz
@@ -4079,6 +4080,7 @@ relopt_kind
relopt_parse_elt
relopt_real
relopt_string
+relopt_ternary
relopt_type
relopt_value
relopts_validator
--
2.47.3
--c36t3fndxhkzbl5z--
^ permalink raw reply [nested|flat] 868+ messages in thread
* [PATCH v3] Introduce ternary reloptions
@ 2026-01-16 15:04 Álvaro Herrera <[email protected]>
0 siblings, 0 replies; 868+ messages in thread
From: Álvaro Herrera @ 2026-01-16 15:04 UTC (permalink / raw)
Introduce ternary reloption as a replacement for current
`vacuum_truncate` implementation. Remove the `vacuum_truncate_set`
separate flag and use TERNARY_UNSET instead.
This could also be used for other options such as `vacuum_index_cleanup`
and `buffering`, but lets get the scaffolding in first.
Discussion: https://postgr.es/m/3474141.usfYGdeWWP@thinkpad-pgpro
---
src/backend/access/common/reloptions.c | 137 ++++++++++++++++++-----
src/backend/commands/vacuum.c | 4 +-
src/include/access/reloptions.h | 27 ++---
src/include/postgres.h | 15 +++
src/include/utils/rel.h | 3 +-
src/test/regress/expected/reloptions.out | 36 ++++++
src/test/regress/sql/reloptions.sql | 21 ++++
src/tools/pgindent/typedefs.list | 2 +
8 files changed, 202 insertions(+), 43 deletions(-)
diff --git a/src/backend/access/common/reloptions.c b/src/backend/access/common/reloptions.c
index 0b83f98ed5f..6f1f577581a 100644
--- a/src/backend/access/common/reloptions.c
+++ b/src/backend/access/common/reloptions.c
@@ -40,9 +40,9 @@
*
* To add an option:
*
- * (i) decide on a type (bool, integer, real, enum, string), name, default
- * value, upper and lower bounds (if applicable); for strings, consider a
- * validation routine.
+ * (i) decide on a type (bool, ternary, integer, real, enum, string), name,
+ * default value, upper and lower bounds (if applicable); for strings,
+ * consider a validation routine.
* (ii) add a record below (or use add_<type>_reloption).
* (iii) add it to the appropriate options struct (perhaps StdRdOptions)
* (iv) add it to the appropriate handling routine (perhaps
@@ -147,15 +147,6 @@ static relopt_bool boolRelOpts[] =
},
false
},
- {
- {
- "vacuum_truncate",
- "Enables vacuum to truncate empty pages at the end of this table",
- RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
- ShareUpdateExclusiveLock
- },
- true
- },
{
{
"deduplicate_items",
@@ -170,6 +161,25 @@ static relopt_bool boolRelOpts[] =
{{NULL}}
};
+static relopt_ternary ternaryRelOpts[] =
+{
+ {
+ {
+ "vacuum_truncate",
+ "Enables vacuum to truncate empty pages at the end of this table",
+ RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
+ ShareUpdateExclusiveLock
+ },
+ TERNARY_UNSET
+ },
+ /* list terminator */
+ {
+ {
+ NULL
+ }
+ }
+};
+
static relopt_int intRelOpts[] =
{
{
@@ -609,6 +619,13 @@ initialize_reloptions(void)
boolRelOpts[i].gen.lockmode));
j++;
}
+ for (i = 0; ternaryRelOpts[i].gen.name; i++)
+ {
+ Assert(DoLockModesConflict(ternaryRelOpts[i].gen.lockmode,
+ ternaryRelOpts[i].gen.lockmode));
+ j++;
+ }
+
for (i = 0; intRelOpts[i].gen.name; i++)
{
Assert(DoLockModesConflict(intRelOpts[i].gen.lockmode,
@@ -649,6 +666,14 @@ initialize_reloptions(void)
j++;
}
+ for (i = 0; ternaryRelOpts[i].gen.name; i++)
+ {
+ relOpts[j] = &ternaryRelOpts[i].gen;
+ relOpts[j]->type = RELOPT_TYPE_TERNARY;
+ relOpts[j]->namelen = strlen(relOpts[j]->name);
+ j++;
+ }
+
for (i = 0; intRelOpts[i].gen.name; i++)
{
relOpts[j] = &intRelOpts[i].gen;
@@ -809,6 +834,9 @@ allocate_reloption(bits32 kinds, int type, const char *name, const char *desc,
case RELOPT_TYPE_BOOL:
size = sizeof(relopt_bool);
break;
+ case RELOPT_TYPE_TERNARY:
+ size = sizeof(relopt_ternary);
+ break;
case RELOPT_TYPE_INT:
size = sizeof(relopt_int);
break;
@@ -892,6 +920,57 @@ add_local_bool_reloption(local_relopts *relopts, const char *name,
add_local_reloption(relopts, (relopt_gen *) newoption, offset);
}
+/*
+ * init_ternary_reloption
+ * Allocate and initialize a new ternary reloption
+ */
+static relopt_ternary *
+init_ternary_reloption(bits32 kinds, const char *name, const char *desc,
+ pg_ternary default_val, LOCKMODE lockmode)
+{
+ relopt_ternary *newoption;
+
+ newoption = (relopt_ternary *)
+ allocate_reloption(kinds, RELOPT_TYPE_TERNARY, name, desc, lockmode);
+ newoption->default_val = default_val;
+
+ return newoption;
+}
+
+/*
+ * add_ternary_reloption
+ * Add a new ternary reloption
+ */
+void
+add_ternary_reloption(bits32 kinds, const char *name, const char *desc,
+ pg_ternary default_val, LOCKMODE lockmode)
+{
+ relopt_ternary *newoption;
+
+ newoption =
+ init_ternary_reloption(kinds, name, desc, default_val, lockmode);
+
+ add_reloption((relopt_gen *) newoption);
+}
+
+/*
+ * add_local_ternary_reloption
+ * Add a new ternary local reloption
+ *
+ * 'offset' is offset of ternary-typed field.
+ */
+void
+add_local_ternary_reloption(local_relopts *relopts, const char *name,
+ const char *desc, pg_ternary default_val,
+ int offset)
+{
+ relopt_ternary *newoption;
+
+ newoption =
+ init_ternary_reloption(RELOPT_KIND_LOCAL, name, desc, default_val, 0);
+
+ add_local_reloption(relopts, (relopt_gen *) newoption, offset);
+}
/*
* init_real_reloption
@@ -1626,6 +1705,19 @@ parse_one_reloption(relopt_value *option, char *text_str, int text_len,
option->gen->name, value)));
}
break;
+ case RELOPT_TYPE_TERNARY:
+ {
+ bool b;
+
+ parsed = parse_bool(value, &b);
+ option->values.ternary_val = b ? TERNARY_TRUE : TERNARY_FALSE;
+ if (validate && !parsed)
+ ereport(ERROR,
+ (errcode(ERRCODE_INVALID_PARAMETER_VALUE),
+ errmsg("invalid value for ternary option \"%s\": %s",
+ option->gen->name, value)));
+ }
+ break;
case RELOPT_TYPE_INT:
{
relopt_int *optint = (relopt_int *) option->gen;
@@ -1789,17 +1881,6 @@ fillRelOptions(void *rdopts, Size basesize,
char *itempos = ((char *) rdopts) + elems[j].offset;
char *string_val;
- /*
- * If isset_offset is provided, store whether the reloption is
- * set there.
- */
- if (elems[j].isset_offset > 0)
- {
- char *setpos = ((char *) rdopts) + elems[j].isset_offset;
-
- *(bool *) setpos = options[i].isset;
- }
-
switch (options[i].gen->type)
{
case RELOPT_TYPE_BOOL:
@@ -1807,6 +1888,11 @@ fillRelOptions(void *rdopts, Size basesize,
options[i].values.bool_val :
((relopt_bool *) options[i].gen)->default_val;
break;
+ case RELOPT_TYPE_TERNARY:
+ *(pg_ternary *) itempos = options[i].isset ?
+ options[i].values.ternary_val :
+ ((relopt_ternary *) options[i].gen)->default_val;
+ break;
case RELOPT_TYPE_INT:
*(int *) itempos = options[i].isset ?
options[i].values.int_val :
@@ -1923,8 +2009,8 @@ default_reloptions(Datum reloptions, bool validate, relopt_kind kind)
offsetof(StdRdOptions, parallel_workers)},
{"vacuum_index_cleanup", RELOPT_TYPE_ENUM,
offsetof(StdRdOptions, vacuum_index_cleanup)},
- {"vacuum_truncate", RELOPT_TYPE_BOOL,
- offsetof(StdRdOptions, vacuum_truncate), offsetof(StdRdOptions, vacuum_truncate_set)},
+ {"vacuum_truncate", RELOPT_TYPE_TERNARY,
+ offsetof(StdRdOptions, vacuum_truncate)},
{"vacuum_max_eager_freeze_failure_rate", RELOPT_TYPE_REAL,
offsetof(StdRdOptions, vacuum_max_eager_freeze_failure_rate)}
};
@@ -2004,7 +2090,6 @@ build_local_reloptions(local_relopts *relopts, Datum options, bool validate)
elems[i].optname = opt->option->name;
elems[i].opttype = opt->option->type;
elems[i].offset = opt->offset;
- elems[i].isset_offset = 0; /* not supported for local relopts yet */
i++;
}
diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c
index aa4fbec143f..696eab9bd97 100644
--- a/src/backend/commands/vacuum.c
+++ b/src/backend/commands/vacuum.c
@@ -2224,9 +2224,9 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams params,
{
StdRdOptions *opts = (StdRdOptions *) rel->rd_options;
- if (opts && opts->vacuum_truncate_set)
+ if (opts && opts->vacuum_truncate != TERNARY_UNSET)
{
- if (opts->vacuum_truncate)
+ if (opts->vacuum_truncate == TERNARY_TRUE)
params.truncate = VACOPTVALUE_ENABLED;
else
params.truncate = VACOPTVALUE_DISABLED;
diff --git a/src/include/access/reloptions.h b/src/include/access/reloptions.h
index 2f08e1b0cf0..dfbef2babf2 100644
--- a/src/include/access/reloptions.h
+++ b/src/include/access/reloptions.h
@@ -29,6 +29,7 @@
typedef enum relopt_type
{
RELOPT_TYPE_BOOL,
+ RELOPT_TYPE_TERNARY, /* on, off, unset */
RELOPT_TYPE_INT,
RELOPT_TYPE_REAL,
RELOPT_TYPE_ENUM,
@@ -80,6 +81,7 @@ typedef struct relopt_value
union
{
bool bool_val;
+ pg_ternary ternary_val;
int int_val;
double real_val;
int enum_val;
@@ -94,6 +96,12 @@ typedef struct relopt_bool
bool default_val;
} relopt_bool;
+typedef struct relopt_ternary
+{
+ relopt_gen gen;
+ int default_val;
+} relopt_ternary;
+
typedef struct relopt_int
{
relopt_gen gen;
@@ -152,19 +160,6 @@ typedef struct
const char *optname; /* option's name */
relopt_type opttype; /* option's datatype */
int offset; /* offset of field in result struct */
-
- /*
- * isset_offset is an optional offset of a field in the result struct that
- * stores whether the option is explicitly set for the relation or if it
- * just picked up the default value. In most cases, this can be
- * accomplished by giving the reloption a special out-of-range default
- * value (e.g., some integer reloptions use -2), but this isn't always
- * possible. For example, a Boolean reloption cannot be given an
- * out-of-range default, so we need another way to discover the source of
- * its value. This offset is only used if given a value greater than
- * zero.
- */
- int isset_offset;
} relopt_parse_elt;
/* Local reloption definition */
@@ -195,6 +190,9 @@ typedef struct local_relopts
extern relopt_kind add_reloption_kind(void);
extern void add_bool_reloption(bits32 kinds, const char *name, const char *desc,
bool default_val, LOCKMODE lockmode);
+extern void add_ternary_reloption(bits32 kinds, const char *name,
+ const char *desc, pg_ternary default_val,
+ LOCKMODE lockmode);
extern void add_int_reloption(bits32 kinds, const char *name, const char *desc,
int default_val, int min_val, int max_val,
LOCKMODE lockmode);
@@ -214,6 +212,9 @@ extern void register_reloptions_validator(local_relopts *relopts,
extern void add_local_bool_reloption(local_relopts *relopts, const char *name,
const char *desc, bool default_val,
int offset);
+extern void add_local_ternary_reloption(local_relopts *relopts,
+ const char *name, const char *desc,
+ pg_ternary default_val, int offset);
extern void add_local_int_reloption(local_relopts *relopts, const char *name,
const char *desc, int default_val,
int min_val, int max_val, int offset);
diff --git a/src/include/postgres.h b/src/include/postgres.h
index 7d93fbce709..47c88780776 100644
--- a/src/include/postgres.h
+++ b/src/include/postgres.h
@@ -543,6 +543,21 @@ Float8GetDatum(float8 X)
* ----------------------------------------------------------------
*/
+/*
+ * pg_ternary
+ * Boolean value with an extra "unset" value
+ *
+ * This enum can be used for values that want to distinguish between true,
+ * false, and unset.
+*/
+
+typedef enum pg_ternary
+{
+ TERNARY_FALSE = 0,
+ TERNARY_TRUE = 1,
+ TERNARY_UNSET = -1
+} pg_ternary;
+
/*
* NON_EXEC_STATIC: It's sometimes useful to define a variable or function
* that is normally static but extern when using EXEC_BACKEND (see
diff --git a/src/include/utils/rel.h b/src/include/utils/rel.h
index d03ab247788..5feb18a5373 100644
--- a/src/include/utils/rel.h
+++ b/src/include/utils/rel.h
@@ -347,8 +347,7 @@ typedef struct StdRdOptions
bool user_catalog_table; /* use as an additional catalog relation */
int parallel_workers; /* max number of parallel workers */
StdRdOptIndexCleanup vacuum_index_cleanup; /* controls index vacuuming */
- bool vacuum_truncate; /* enables vacuum to truncate a relation */
- bool vacuum_truncate_set; /* whether vacuum_truncate is set */
+ pg_ternary vacuum_truncate; /* enables vacuum to truncate a relation */
/*
* Fraction of pages in a relation that vacuum can eagerly scan and fail
diff --git a/src/test/regress/expected/reloptions.out b/src/test/regress/expected/reloptions.out
index 9de19b4e3f1..1c99f79ab01 100644
--- a/src/test/regress/expected/reloptions.out
+++ b/src/test/regress/expected/reloptions.out
@@ -98,6 +98,42 @@ SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass;
{fillfactor=13,autovacuum_enabled=false}
(1 row)
+-- Tests for future (FIXME) ternary options
+-- behave as boolean option: accept unassigned name and truncated value
+DROP TABLE reloptions_test;
+CREATE TABLE reloptions_test(i INT) WITH (vacuum_truncate);
+SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass;
+ reloptions
+------------------------
+ {vacuum_truncate=true}
+(1 row)
+
+DROP TABLE reloptions_test;
+CREATE TABLE reloptions_test(i INT) WITH (vacuum_truncate=FaLS);
+SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass;
+ reloptions
+------------------------
+ {vacuum_truncate=fals}
+(1 row)
+
+-- preferred "true" alias is used when storing
+DROP TABLE reloptions_test;
+CREATE TABLE reloptions_test(i INT) WITH (vacuum_index_cleanup=on);
+SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass;
+ reloptions
+---------------------------
+ {vacuum_index_cleanup=on}
+(1 row)
+
+-- custom "third" value is available
+DROP TABLE reloptions_test;
+CREATE TABLE reloptions_test(i INT) WITH (vacuum_index_cleanup=auto);
+SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass;
+ reloptions
+-----------------------------
+ {vacuum_index_cleanup=auto}
+(1 row)
+
-- Test vacuum_truncate option
DROP TABLE reloptions_test;
CREATE TEMP TABLE reloptions_test(i INT NOT NULL, j text)
diff --git a/src/test/regress/sql/reloptions.sql b/src/test/regress/sql/reloptions.sql
index 24fbe0b478d..f5980dafcbc 100644
--- a/src/test/regress/sql/reloptions.sql
+++ b/src/test/regress/sql/reloptions.sql
@@ -59,6 +59,27 @@ UPDATE pg_class
ALTER TABLE reloptions_test RESET (illegal_option);
SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass;
+-- Tests for future (FIXME) ternary options
+
+-- behave as boolean option: accept unassigned name and truncated value
+DROP TABLE reloptions_test;
+CREATE TABLE reloptions_test(i INT) WITH (vacuum_truncate);
+SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass;
+
+DROP TABLE reloptions_test;
+CREATE TABLE reloptions_test(i INT) WITH (vacuum_truncate=FaLS);
+SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass;
+
+-- preferred "true" alias is used when storing
+DROP TABLE reloptions_test;
+CREATE TABLE reloptions_test(i INT) WITH (vacuum_index_cleanup=on);
+SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass;
+
+-- custom "third" value is available
+DROP TABLE reloptions_test;
+CREATE TABLE reloptions_test(i INT) WITH (vacuum_index_cleanup=auto);
+SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass;
+
-- Test vacuum_truncate option
DROP TABLE reloptions_test;
diff --git a/src/tools/pgindent/typedefs.list b/src/tools/pgindent/typedefs.list
index 3f3a888fd0e..1c8610fd46c 100644
--- a/src/tools/pgindent/typedefs.list
+++ b/src/tools/pgindent/typedefs.list
@@ -3952,6 +3952,7 @@ pg_sha512_ctx
pg_snapshot
pg_special_case
pg_stack_base_t
+pg_ternary
pg_time_t
pg_time_usec_t
pg_tz
@@ -4079,6 +4080,7 @@ relopt_kind
relopt_parse_elt
relopt_real
relopt_string
+relopt_ternary
relopt_type
relopt_value
relopts_validator
--
2.47.3
--c36t3fndxhkzbl5z--
^ permalink raw reply [nested|flat] 868+ messages in thread
* [PATCH v3] Introduce ternary reloptions
@ 2026-01-16 15:04 Álvaro Herrera <[email protected]>
0 siblings, 0 replies; 868+ messages in thread
From: Álvaro Herrera @ 2026-01-16 15:04 UTC (permalink / raw)
Introduce ternary reloption as a replacement for current
`vacuum_truncate` implementation. Remove the `vacuum_truncate_set`
separate flag and use TERNARY_UNSET instead.
This could also be used for other options such as `vacuum_index_cleanup`
and `buffering`, but lets get the scaffolding in first.
Discussion: https://postgr.es/m/3474141.usfYGdeWWP@thinkpad-pgpro
---
src/backend/access/common/reloptions.c | 137 ++++++++++++++++++-----
src/backend/commands/vacuum.c | 4 +-
src/include/access/reloptions.h | 27 ++---
src/include/postgres.h | 15 +++
src/include/utils/rel.h | 3 +-
src/test/regress/expected/reloptions.out | 36 ++++++
src/test/regress/sql/reloptions.sql | 21 ++++
src/tools/pgindent/typedefs.list | 2 +
8 files changed, 202 insertions(+), 43 deletions(-)
diff --git a/src/backend/access/common/reloptions.c b/src/backend/access/common/reloptions.c
index 0b83f98ed5f..6f1f577581a 100644
--- a/src/backend/access/common/reloptions.c
+++ b/src/backend/access/common/reloptions.c
@@ -40,9 +40,9 @@
*
* To add an option:
*
- * (i) decide on a type (bool, integer, real, enum, string), name, default
- * value, upper and lower bounds (if applicable); for strings, consider a
- * validation routine.
+ * (i) decide on a type (bool, ternary, integer, real, enum, string), name,
+ * default value, upper and lower bounds (if applicable); for strings,
+ * consider a validation routine.
* (ii) add a record below (or use add_<type>_reloption).
* (iii) add it to the appropriate options struct (perhaps StdRdOptions)
* (iv) add it to the appropriate handling routine (perhaps
@@ -147,15 +147,6 @@ static relopt_bool boolRelOpts[] =
},
false
},
- {
- {
- "vacuum_truncate",
- "Enables vacuum to truncate empty pages at the end of this table",
- RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
- ShareUpdateExclusiveLock
- },
- true
- },
{
{
"deduplicate_items",
@@ -170,6 +161,25 @@ static relopt_bool boolRelOpts[] =
{{NULL}}
};
+static relopt_ternary ternaryRelOpts[] =
+{
+ {
+ {
+ "vacuum_truncate",
+ "Enables vacuum to truncate empty pages at the end of this table",
+ RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
+ ShareUpdateExclusiveLock
+ },
+ TERNARY_UNSET
+ },
+ /* list terminator */
+ {
+ {
+ NULL
+ }
+ }
+};
+
static relopt_int intRelOpts[] =
{
{
@@ -609,6 +619,13 @@ initialize_reloptions(void)
boolRelOpts[i].gen.lockmode));
j++;
}
+ for (i = 0; ternaryRelOpts[i].gen.name; i++)
+ {
+ Assert(DoLockModesConflict(ternaryRelOpts[i].gen.lockmode,
+ ternaryRelOpts[i].gen.lockmode));
+ j++;
+ }
+
for (i = 0; intRelOpts[i].gen.name; i++)
{
Assert(DoLockModesConflict(intRelOpts[i].gen.lockmode,
@@ -649,6 +666,14 @@ initialize_reloptions(void)
j++;
}
+ for (i = 0; ternaryRelOpts[i].gen.name; i++)
+ {
+ relOpts[j] = &ternaryRelOpts[i].gen;
+ relOpts[j]->type = RELOPT_TYPE_TERNARY;
+ relOpts[j]->namelen = strlen(relOpts[j]->name);
+ j++;
+ }
+
for (i = 0; intRelOpts[i].gen.name; i++)
{
relOpts[j] = &intRelOpts[i].gen;
@@ -809,6 +834,9 @@ allocate_reloption(bits32 kinds, int type, const char *name, const char *desc,
case RELOPT_TYPE_BOOL:
size = sizeof(relopt_bool);
break;
+ case RELOPT_TYPE_TERNARY:
+ size = sizeof(relopt_ternary);
+ break;
case RELOPT_TYPE_INT:
size = sizeof(relopt_int);
break;
@@ -892,6 +920,57 @@ add_local_bool_reloption(local_relopts *relopts, const char *name,
add_local_reloption(relopts, (relopt_gen *) newoption, offset);
}
+/*
+ * init_ternary_reloption
+ * Allocate and initialize a new ternary reloption
+ */
+static relopt_ternary *
+init_ternary_reloption(bits32 kinds, const char *name, const char *desc,
+ pg_ternary default_val, LOCKMODE lockmode)
+{
+ relopt_ternary *newoption;
+
+ newoption = (relopt_ternary *)
+ allocate_reloption(kinds, RELOPT_TYPE_TERNARY, name, desc, lockmode);
+ newoption->default_val = default_val;
+
+ return newoption;
+}
+
+/*
+ * add_ternary_reloption
+ * Add a new ternary reloption
+ */
+void
+add_ternary_reloption(bits32 kinds, const char *name, const char *desc,
+ pg_ternary default_val, LOCKMODE lockmode)
+{
+ relopt_ternary *newoption;
+
+ newoption =
+ init_ternary_reloption(kinds, name, desc, default_val, lockmode);
+
+ add_reloption((relopt_gen *) newoption);
+}
+
+/*
+ * add_local_ternary_reloption
+ * Add a new ternary local reloption
+ *
+ * 'offset' is offset of ternary-typed field.
+ */
+void
+add_local_ternary_reloption(local_relopts *relopts, const char *name,
+ const char *desc, pg_ternary default_val,
+ int offset)
+{
+ relopt_ternary *newoption;
+
+ newoption =
+ init_ternary_reloption(RELOPT_KIND_LOCAL, name, desc, default_val, 0);
+
+ add_local_reloption(relopts, (relopt_gen *) newoption, offset);
+}
/*
* init_real_reloption
@@ -1626,6 +1705,19 @@ parse_one_reloption(relopt_value *option, char *text_str, int text_len,
option->gen->name, value)));
}
break;
+ case RELOPT_TYPE_TERNARY:
+ {
+ bool b;
+
+ parsed = parse_bool(value, &b);
+ option->values.ternary_val = b ? TERNARY_TRUE : TERNARY_FALSE;
+ if (validate && !parsed)
+ ereport(ERROR,
+ (errcode(ERRCODE_INVALID_PARAMETER_VALUE),
+ errmsg("invalid value for ternary option \"%s\": %s",
+ option->gen->name, value)));
+ }
+ break;
case RELOPT_TYPE_INT:
{
relopt_int *optint = (relopt_int *) option->gen;
@@ -1789,17 +1881,6 @@ fillRelOptions(void *rdopts, Size basesize,
char *itempos = ((char *) rdopts) + elems[j].offset;
char *string_val;
- /*
- * If isset_offset is provided, store whether the reloption is
- * set there.
- */
- if (elems[j].isset_offset > 0)
- {
- char *setpos = ((char *) rdopts) + elems[j].isset_offset;
-
- *(bool *) setpos = options[i].isset;
- }
-
switch (options[i].gen->type)
{
case RELOPT_TYPE_BOOL:
@@ -1807,6 +1888,11 @@ fillRelOptions(void *rdopts, Size basesize,
options[i].values.bool_val :
((relopt_bool *) options[i].gen)->default_val;
break;
+ case RELOPT_TYPE_TERNARY:
+ *(pg_ternary *) itempos = options[i].isset ?
+ options[i].values.ternary_val :
+ ((relopt_ternary *) options[i].gen)->default_val;
+ break;
case RELOPT_TYPE_INT:
*(int *) itempos = options[i].isset ?
options[i].values.int_val :
@@ -1923,8 +2009,8 @@ default_reloptions(Datum reloptions, bool validate, relopt_kind kind)
offsetof(StdRdOptions, parallel_workers)},
{"vacuum_index_cleanup", RELOPT_TYPE_ENUM,
offsetof(StdRdOptions, vacuum_index_cleanup)},
- {"vacuum_truncate", RELOPT_TYPE_BOOL,
- offsetof(StdRdOptions, vacuum_truncate), offsetof(StdRdOptions, vacuum_truncate_set)},
+ {"vacuum_truncate", RELOPT_TYPE_TERNARY,
+ offsetof(StdRdOptions, vacuum_truncate)},
{"vacuum_max_eager_freeze_failure_rate", RELOPT_TYPE_REAL,
offsetof(StdRdOptions, vacuum_max_eager_freeze_failure_rate)}
};
@@ -2004,7 +2090,6 @@ build_local_reloptions(local_relopts *relopts, Datum options, bool validate)
elems[i].optname = opt->option->name;
elems[i].opttype = opt->option->type;
elems[i].offset = opt->offset;
- elems[i].isset_offset = 0; /* not supported for local relopts yet */
i++;
}
diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c
index aa4fbec143f..696eab9bd97 100644
--- a/src/backend/commands/vacuum.c
+++ b/src/backend/commands/vacuum.c
@@ -2224,9 +2224,9 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams params,
{
StdRdOptions *opts = (StdRdOptions *) rel->rd_options;
- if (opts && opts->vacuum_truncate_set)
+ if (opts && opts->vacuum_truncate != TERNARY_UNSET)
{
- if (opts->vacuum_truncate)
+ if (opts->vacuum_truncate == TERNARY_TRUE)
params.truncate = VACOPTVALUE_ENABLED;
else
params.truncate = VACOPTVALUE_DISABLED;
diff --git a/src/include/access/reloptions.h b/src/include/access/reloptions.h
index 2f08e1b0cf0..dfbef2babf2 100644
--- a/src/include/access/reloptions.h
+++ b/src/include/access/reloptions.h
@@ -29,6 +29,7 @@
typedef enum relopt_type
{
RELOPT_TYPE_BOOL,
+ RELOPT_TYPE_TERNARY, /* on, off, unset */
RELOPT_TYPE_INT,
RELOPT_TYPE_REAL,
RELOPT_TYPE_ENUM,
@@ -80,6 +81,7 @@ typedef struct relopt_value
union
{
bool bool_val;
+ pg_ternary ternary_val;
int int_val;
double real_val;
int enum_val;
@@ -94,6 +96,12 @@ typedef struct relopt_bool
bool default_val;
} relopt_bool;
+typedef struct relopt_ternary
+{
+ relopt_gen gen;
+ int default_val;
+} relopt_ternary;
+
typedef struct relopt_int
{
relopt_gen gen;
@@ -152,19 +160,6 @@ typedef struct
const char *optname; /* option's name */
relopt_type opttype; /* option's datatype */
int offset; /* offset of field in result struct */
-
- /*
- * isset_offset is an optional offset of a field in the result struct that
- * stores whether the option is explicitly set for the relation or if it
- * just picked up the default value. In most cases, this can be
- * accomplished by giving the reloption a special out-of-range default
- * value (e.g., some integer reloptions use -2), but this isn't always
- * possible. For example, a Boolean reloption cannot be given an
- * out-of-range default, so we need another way to discover the source of
- * its value. This offset is only used if given a value greater than
- * zero.
- */
- int isset_offset;
} relopt_parse_elt;
/* Local reloption definition */
@@ -195,6 +190,9 @@ typedef struct local_relopts
extern relopt_kind add_reloption_kind(void);
extern void add_bool_reloption(bits32 kinds, const char *name, const char *desc,
bool default_val, LOCKMODE lockmode);
+extern void add_ternary_reloption(bits32 kinds, const char *name,
+ const char *desc, pg_ternary default_val,
+ LOCKMODE lockmode);
extern void add_int_reloption(bits32 kinds, const char *name, const char *desc,
int default_val, int min_val, int max_val,
LOCKMODE lockmode);
@@ -214,6 +212,9 @@ extern void register_reloptions_validator(local_relopts *relopts,
extern void add_local_bool_reloption(local_relopts *relopts, const char *name,
const char *desc, bool default_val,
int offset);
+extern void add_local_ternary_reloption(local_relopts *relopts,
+ const char *name, const char *desc,
+ pg_ternary default_val, int offset);
extern void add_local_int_reloption(local_relopts *relopts, const char *name,
const char *desc, int default_val,
int min_val, int max_val, int offset);
diff --git a/src/include/postgres.h b/src/include/postgres.h
index 7d93fbce709..47c88780776 100644
--- a/src/include/postgres.h
+++ b/src/include/postgres.h
@@ -543,6 +543,21 @@ Float8GetDatum(float8 X)
* ----------------------------------------------------------------
*/
+/*
+ * pg_ternary
+ * Boolean value with an extra "unset" value
+ *
+ * This enum can be used for values that want to distinguish between true,
+ * false, and unset.
+*/
+
+typedef enum pg_ternary
+{
+ TERNARY_FALSE = 0,
+ TERNARY_TRUE = 1,
+ TERNARY_UNSET = -1
+} pg_ternary;
+
/*
* NON_EXEC_STATIC: It's sometimes useful to define a variable or function
* that is normally static but extern when using EXEC_BACKEND (see
diff --git a/src/include/utils/rel.h b/src/include/utils/rel.h
index d03ab247788..5feb18a5373 100644
--- a/src/include/utils/rel.h
+++ b/src/include/utils/rel.h
@@ -347,8 +347,7 @@ typedef struct StdRdOptions
bool user_catalog_table; /* use as an additional catalog relation */
int parallel_workers; /* max number of parallel workers */
StdRdOptIndexCleanup vacuum_index_cleanup; /* controls index vacuuming */
- bool vacuum_truncate; /* enables vacuum to truncate a relation */
- bool vacuum_truncate_set; /* whether vacuum_truncate is set */
+ pg_ternary vacuum_truncate; /* enables vacuum to truncate a relation */
/*
* Fraction of pages in a relation that vacuum can eagerly scan and fail
diff --git a/src/test/regress/expected/reloptions.out b/src/test/regress/expected/reloptions.out
index 9de19b4e3f1..1c99f79ab01 100644
--- a/src/test/regress/expected/reloptions.out
+++ b/src/test/regress/expected/reloptions.out
@@ -98,6 +98,42 @@ SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass;
{fillfactor=13,autovacuum_enabled=false}
(1 row)
+-- Tests for future (FIXME) ternary options
+-- behave as boolean option: accept unassigned name and truncated value
+DROP TABLE reloptions_test;
+CREATE TABLE reloptions_test(i INT) WITH (vacuum_truncate);
+SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass;
+ reloptions
+------------------------
+ {vacuum_truncate=true}
+(1 row)
+
+DROP TABLE reloptions_test;
+CREATE TABLE reloptions_test(i INT) WITH (vacuum_truncate=FaLS);
+SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass;
+ reloptions
+------------------------
+ {vacuum_truncate=fals}
+(1 row)
+
+-- preferred "true" alias is used when storing
+DROP TABLE reloptions_test;
+CREATE TABLE reloptions_test(i INT) WITH (vacuum_index_cleanup=on);
+SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass;
+ reloptions
+---------------------------
+ {vacuum_index_cleanup=on}
+(1 row)
+
+-- custom "third" value is available
+DROP TABLE reloptions_test;
+CREATE TABLE reloptions_test(i INT) WITH (vacuum_index_cleanup=auto);
+SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass;
+ reloptions
+-----------------------------
+ {vacuum_index_cleanup=auto}
+(1 row)
+
-- Test vacuum_truncate option
DROP TABLE reloptions_test;
CREATE TEMP TABLE reloptions_test(i INT NOT NULL, j text)
diff --git a/src/test/regress/sql/reloptions.sql b/src/test/regress/sql/reloptions.sql
index 24fbe0b478d..f5980dafcbc 100644
--- a/src/test/regress/sql/reloptions.sql
+++ b/src/test/regress/sql/reloptions.sql
@@ -59,6 +59,27 @@ UPDATE pg_class
ALTER TABLE reloptions_test RESET (illegal_option);
SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass;
+-- Tests for future (FIXME) ternary options
+
+-- behave as boolean option: accept unassigned name and truncated value
+DROP TABLE reloptions_test;
+CREATE TABLE reloptions_test(i INT) WITH (vacuum_truncate);
+SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass;
+
+DROP TABLE reloptions_test;
+CREATE TABLE reloptions_test(i INT) WITH (vacuum_truncate=FaLS);
+SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass;
+
+-- preferred "true" alias is used when storing
+DROP TABLE reloptions_test;
+CREATE TABLE reloptions_test(i INT) WITH (vacuum_index_cleanup=on);
+SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass;
+
+-- custom "third" value is available
+DROP TABLE reloptions_test;
+CREATE TABLE reloptions_test(i INT) WITH (vacuum_index_cleanup=auto);
+SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass;
+
-- Test vacuum_truncate option
DROP TABLE reloptions_test;
diff --git a/src/tools/pgindent/typedefs.list b/src/tools/pgindent/typedefs.list
index 3f3a888fd0e..1c8610fd46c 100644
--- a/src/tools/pgindent/typedefs.list
+++ b/src/tools/pgindent/typedefs.list
@@ -3952,6 +3952,7 @@ pg_sha512_ctx
pg_snapshot
pg_special_case
pg_stack_base_t
+pg_ternary
pg_time_t
pg_time_usec_t
pg_tz
@@ -4079,6 +4080,7 @@ relopt_kind
relopt_parse_elt
relopt_real
relopt_string
+relopt_ternary
relopt_type
relopt_value
relopts_validator
--
2.47.3
--c36t3fndxhkzbl5z--
^ permalink raw reply [nested|flat] 868+ messages in thread
* [PATCH v3] Introduce ternary reloptions
@ 2026-01-16 15:04 Álvaro Herrera <[email protected]>
0 siblings, 0 replies; 868+ messages in thread
From: Álvaro Herrera @ 2026-01-16 15:04 UTC (permalink / raw)
Introduce ternary reloption as a replacement for current
`vacuum_truncate` implementation. Remove the `vacuum_truncate_set`
separate flag and use TERNARY_UNSET instead.
This could also be used for other options such as `vacuum_index_cleanup`
and `buffering`, but lets get the scaffolding in first.
Discussion: https://postgr.es/m/3474141.usfYGdeWWP@thinkpad-pgpro
---
src/backend/access/common/reloptions.c | 137 ++++++++++++++++++-----
src/backend/commands/vacuum.c | 4 +-
src/include/access/reloptions.h | 27 ++---
src/include/postgres.h | 15 +++
src/include/utils/rel.h | 3 +-
src/test/regress/expected/reloptions.out | 36 ++++++
src/test/regress/sql/reloptions.sql | 21 ++++
src/tools/pgindent/typedefs.list | 2 +
8 files changed, 202 insertions(+), 43 deletions(-)
diff --git a/src/backend/access/common/reloptions.c b/src/backend/access/common/reloptions.c
index 0b83f98ed5f..6f1f577581a 100644
--- a/src/backend/access/common/reloptions.c
+++ b/src/backend/access/common/reloptions.c
@@ -40,9 +40,9 @@
*
* To add an option:
*
- * (i) decide on a type (bool, integer, real, enum, string), name, default
- * value, upper and lower bounds (if applicable); for strings, consider a
- * validation routine.
+ * (i) decide on a type (bool, ternary, integer, real, enum, string), name,
+ * default value, upper and lower bounds (if applicable); for strings,
+ * consider a validation routine.
* (ii) add a record below (or use add_<type>_reloption).
* (iii) add it to the appropriate options struct (perhaps StdRdOptions)
* (iv) add it to the appropriate handling routine (perhaps
@@ -147,15 +147,6 @@ static relopt_bool boolRelOpts[] =
},
false
},
- {
- {
- "vacuum_truncate",
- "Enables vacuum to truncate empty pages at the end of this table",
- RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
- ShareUpdateExclusiveLock
- },
- true
- },
{
{
"deduplicate_items",
@@ -170,6 +161,25 @@ static relopt_bool boolRelOpts[] =
{{NULL}}
};
+static relopt_ternary ternaryRelOpts[] =
+{
+ {
+ {
+ "vacuum_truncate",
+ "Enables vacuum to truncate empty pages at the end of this table",
+ RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
+ ShareUpdateExclusiveLock
+ },
+ TERNARY_UNSET
+ },
+ /* list terminator */
+ {
+ {
+ NULL
+ }
+ }
+};
+
static relopt_int intRelOpts[] =
{
{
@@ -609,6 +619,13 @@ initialize_reloptions(void)
boolRelOpts[i].gen.lockmode));
j++;
}
+ for (i = 0; ternaryRelOpts[i].gen.name; i++)
+ {
+ Assert(DoLockModesConflict(ternaryRelOpts[i].gen.lockmode,
+ ternaryRelOpts[i].gen.lockmode));
+ j++;
+ }
+
for (i = 0; intRelOpts[i].gen.name; i++)
{
Assert(DoLockModesConflict(intRelOpts[i].gen.lockmode,
@@ -649,6 +666,14 @@ initialize_reloptions(void)
j++;
}
+ for (i = 0; ternaryRelOpts[i].gen.name; i++)
+ {
+ relOpts[j] = &ternaryRelOpts[i].gen;
+ relOpts[j]->type = RELOPT_TYPE_TERNARY;
+ relOpts[j]->namelen = strlen(relOpts[j]->name);
+ j++;
+ }
+
for (i = 0; intRelOpts[i].gen.name; i++)
{
relOpts[j] = &intRelOpts[i].gen;
@@ -809,6 +834,9 @@ allocate_reloption(bits32 kinds, int type, const char *name, const char *desc,
case RELOPT_TYPE_BOOL:
size = sizeof(relopt_bool);
break;
+ case RELOPT_TYPE_TERNARY:
+ size = sizeof(relopt_ternary);
+ break;
case RELOPT_TYPE_INT:
size = sizeof(relopt_int);
break;
@@ -892,6 +920,57 @@ add_local_bool_reloption(local_relopts *relopts, const char *name,
add_local_reloption(relopts, (relopt_gen *) newoption, offset);
}
+/*
+ * init_ternary_reloption
+ * Allocate and initialize a new ternary reloption
+ */
+static relopt_ternary *
+init_ternary_reloption(bits32 kinds, const char *name, const char *desc,
+ pg_ternary default_val, LOCKMODE lockmode)
+{
+ relopt_ternary *newoption;
+
+ newoption = (relopt_ternary *)
+ allocate_reloption(kinds, RELOPT_TYPE_TERNARY, name, desc, lockmode);
+ newoption->default_val = default_val;
+
+ return newoption;
+}
+
+/*
+ * add_ternary_reloption
+ * Add a new ternary reloption
+ */
+void
+add_ternary_reloption(bits32 kinds, const char *name, const char *desc,
+ pg_ternary default_val, LOCKMODE lockmode)
+{
+ relopt_ternary *newoption;
+
+ newoption =
+ init_ternary_reloption(kinds, name, desc, default_val, lockmode);
+
+ add_reloption((relopt_gen *) newoption);
+}
+
+/*
+ * add_local_ternary_reloption
+ * Add a new ternary local reloption
+ *
+ * 'offset' is offset of ternary-typed field.
+ */
+void
+add_local_ternary_reloption(local_relopts *relopts, const char *name,
+ const char *desc, pg_ternary default_val,
+ int offset)
+{
+ relopt_ternary *newoption;
+
+ newoption =
+ init_ternary_reloption(RELOPT_KIND_LOCAL, name, desc, default_val, 0);
+
+ add_local_reloption(relopts, (relopt_gen *) newoption, offset);
+}
/*
* init_real_reloption
@@ -1626,6 +1705,19 @@ parse_one_reloption(relopt_value *option, char *text_str, int text_len,
option->gen->name, value)));
}
break;
+ case RELOPT_TYPE_TERNARY:
+ {
+ bool b;
+
+ parsed = parse_bool(value, &b);
+ option->values.ternary_val = b ? TERNARY_TRUE : TERNARY_FALSE;
+ if (validate && !parsed)
+ ereport(ERROR,
+ (errcode(ERRCODE_INVALID_PARAMETER_VALUE),
+ errmsg("invalid value for ternary option \"%s\": %s",
+ option->gen->name, value)));
+ }
+ break;
case RELOPT_TYPE_INT:
{
relopt_int *optint = (relopt_int *) option->gen;
@@ -1789,17 +1881,6 @@ fillRelOptions(void *rdopts, Size basesize,
char *itempos = ((char *) rdopts) + elems[j].offset;
char *string_val;
- /*
- * If isset_offset is provided, store whether the reloption is
- * set there.
- */
- if (elems[j].isset_offset > 0)
- {
- char *setpos = ((char *) rdopts) + elems[j].isset_offset;
-
- *(bool *) setpos = options[i].isset;
- }
-
switch (options[i].gen->type)
{
case RELOPT_TYPE_BOOL:
@@ -1807,6 +1888,11 @@ fillRelOptions(void *rdopts, Size basesize,
options[i].values.bool_val :
((relopt_bool *) options[i].gen)->default_val;
break;
+ case RELOPT_TYPE_TERNARY:
+ *(pg_ternary *) itempos = options[i].isset ?
+ options[i].values.ternary_val :
+ ((relopt_ternary *) options[i].gen)->default_val;
+ break;
case RELOPT_TYPE_INT:
*(int *) itempos = options[i].isset ?
options[i].values.int_val :
@@ -1923,8 +2009,8 @@ default_reloptions(Datum reloptions, bool validate, relopt_kind kind)
offsetof(StdRdOptions, parallel_workers)},
{"vacuum_index_cleanup", RELOPT_TYPE_ENUM,
offsetof(StdRdOptions, vacuum_index_cleanup)},
- {"vacuum_truncate", RELOPT_TYPE_BOOL,
- offsetof(StdRdOptions, vacuum_truncate), offsetof(StdRdOptions, vacuum_truncate_set)},
+ {"vacuum_truncate", RELOPT_TYPE_TERNARY,
+ offsetof(StdRdOptions, vacuum_truncate)},
{"vacuum_max_eager_freeze_failure_rate", RELOPT_TYPE_REAL,
offsetof(StdRdOptions, vacuum_max_eager_freeze_failure_rate)}
};
@@ -2004,7 +2090,6 @@ build_local_reloptions(local_relopts *relopts, Datum options, bool validate)
elems[i].optname = opt->option->name;
elems[i].opttype = opt->option->type;
elems[i].offset = opt->offset;
- elems[i].isset_offset = 0; /* not supported for local relopts yet */
i++;
}
diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c
index aa4fbec143f..696eab9bd97 100644
--- a/src/backend/commands/vacuum.c
+++ b/src/backend/commands/vacuum.c
@@ -2224,9 +2224,9 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams params,
{
StdRdOptions *opts = (StdRdOptions *) rel->rd_options;
- if (opts && opts->vacuum_truncate_set)
+ if (opts && opts->vacuum_truncate != TERNARY_UNSET)
{
- if (opts->vacuum_truncate)
+ if (opts->vacuum_truncate == TERNARY_TRUE)
params.truncate = VACOPTVALUE_ENABLED;
else
params.truncate = VACOPTVALUE_DISABLED;
diff --git a/src/include/access/reloptions.h b/src/include/access/reloptions.h
index 2f08e1b0cf0..dfbef2babf2 100644
--- a/src/include/access/reloptions.h
+++ b/src/include/access/reloptions.h
@@ -29,6 +29,7 @@
typedef enum relopt_type
{
RELOPT_TYPE_BOOL,
+ RELOPT_TYPE_TERNARY, /* on, off, unset */
RELOPT_TYPE_INT,
RELOPT_TYPE_REAL,
RELOPT_TYPE_ENUM,
@@ -80,6 +81,7 @@ typedef struct relopt_value
union
{
bool bool_val;
+ pg_ternary ternary_val;
int int_val;
double real_val;
int enum_val;
@@ -94,6 +96,12 @@ typedef struct relopt_bool
bool default_val;
} relopt_bool;
+typedef struct relopt_ternary
+{
+ relopt_gen gen;
+ int default_val;
+} relopt_ternary;
+
typedef struct relopt_int
{
relopt_gen gen;
@@ -152,19 +160,6 @@ typedef struct
const char *optname; /* option's name */
relopt_type opttype; /* option's datatype */
int offset; /* offset of field in result struct */
-
- /*
- * isset_offset is an optional offset of a field in the result struct that
- * stores whether the option is explicitly set for the relation or if it
- * just picked up the default value. In most cases, this can be
- * accomplished by giving the reloption a special out-of-range default
- * value (e.g., some integer reloptions use -2), but this isn't always
- * possible. For example, a Boolean reloption cannot be given an
- * out-of-range default, so we need another way to discover the source of
- * its value. This offset is only used if given a value greater than
- * zero.
- */
- int isset_offset;
} relopt_parse_elt;
/* Local reloption definition */
@@ -195,6 +190,9 @@ typedef struct local_relopts
extern relopt_kind add_reloption_kind(void);
extern void add_bool_reloption(bits32 kinds, const char *name, const char *desc,
bool default_val, LOCKMODE lockmode);
+extern void add_ternary_reloption(bits32 kinds, const char *name,
+ const char *desc, pg_ternary default_val,
+ LOCKMODE lockmode);
extern void add_int_reloption(bits32 kinds, const char *name, const char *desc,
int default_val, int min_val, int max_val,
LOCKMODE lockmode);
@@ -214,6 +212,9 @@ extern void register_reloptions_validator(local_relopts *relopts,
extern void add_local_bool_reloption(local_relopts *relopts, const char *name,
const char *desc, bool default_val,
int offset);
+extern void add_local_ternary_reloption(local_relopts *relopts,
+ const char *name, const char *desc,
+ pg_ternary default_val, int offset);
extern void add_local_int_reloption(local_relopts *relopts, const char *name,
const char *desc, int default_val,
int min_val, int max_val, int offset);
diff --git a/src/include/postgres.h b/src/include/postgres.h
index 7d93fbce709..47c88780776 100644
--- a/src/include/postgres.h
+++ b/src/include/postgres.h
@@ -543,6 +543,21 @@ Float8GetDatum(float8 X)
* ----------------------------------------------------------------
*/
+/*
+ * pg_ternary
+ * Boolean value with an extra "unset" value
+ *
+ * This enum can be used for values that want to distinguish between true,
+ * false, and unset.
+*/
+
+typedef enum pg_ternary
+{
+ TERNARY_FALSE = 0,
+ TERNARY_TRUE = 1,
+ TERNARY_UNSET = -1
+} pg_ternary;
+
/*
* NON_EXEC_STATIC: It's sometimes useful to define a variable or function
* that is normally static but extern when using EXEC_BACKEND (see
diff --git a/src/include/utils/rel.h b/src/include/utils/rel.h
index d03ab247788..5feb18a5373 100644
--- a/src/include/utils/rel.h
+++ b/src/include/utils/rel.h
@@ -347,8 +347,7 @@ typedef struct StdRdOptions
bool user_catalog_table; /* use as an additional catalog relation */
int parallel_workers; /* max number of parallel workers */
StdRdOptIndexCleanup vacuum_index_cleanup; /* controls index vacuuming */
- bool vacuum_truncate; /* enables vacuum to truncate a relation */
- bool vacuum_truncate_set; /* whether vacuum_truncate is set */
+ pg_ternary vacuum_truncate; /* enables vacuum to truncate a relation */
/*
* Fraction of pages in a relation that vacuum can eagerly scan and fail
diff --git a/src/test/regress/expected/reloptions.out b/src/test/regress/expected/reloptions.out
index 9de19b4e3f1..1c99f79ab01 100644
--- a/src/test/regress/expected/reloptions.out
+++ b/src/test/regress/expected/reloptions.out
@@ -98,6 +98,42 @@ SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass;
{fillfactor=13,autovacuum_enabled=false}
(1 row)
+-- Tests for future (FIXME) ternary options
+-- behave as boolean option: accept unassigned name and truncated value
+DROP TABLE reloptions_test;
+CREATE TABLE reloptions_test(i INT) WITH (vacuum_truncate);
+SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass;
+ reloptions
+------------------------
+ {vacuum_truncate=true}
+(1 row)
+
+DROP TABLE reloptions_test;
+CREATE TABLE reloptions_test(i INT) WITH (vacuum_truncate=FaLS);
+SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass;
+ reloptions
+------------------------
+ {vacuum_truncate=fals}
+(1 row)
+
+-- preferred "true" alias is used when storing
+DROP TABLE reloptions_test;
+CREATE TABLE reloptions_test(i INT) WITH (vacuum_index_cleanup=on);
+SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass;
+ reloptions
+---------------------------
+ {vacuum_index_cleanup=on}
+(1 row)
+
+-- custom "third" value is available
+DROP TABLE reloptions_test;
+CREATE TABLE reloptions_test(i INT) WITH (vacuum_index_cleanup=auto);
+SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass;
+ reloptions
+-----------------------------
+ {vacuum_index_cleanup=auto}
+(1 row)
+
-- Test vacuum_truncate option
DROP TABLE reloptions_test;
CREATE TEMP TABLE reloptions_test(i INT NOT NULL, j text)
diff --git a/src/test/regress/sql/reloptions.sql b/src/test/regress/sql/reloptions.sql
index 24fbe0b478d..f5980dafcbc 100644
--- a/src/test/regress/sql/reloptions.sql
+++ b/src/test/regress/sql/reloptions.sql
@@ -59,6 +59,27 @@ UPDATE pg_class
ALTER TABLE reloptions_test RESET (illegal_option);
SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass;
+-- Tests for future (FIXME) ternary options
+
+-- behave as boolean option: accept unassigned name and truncated value
+DROP TABLE reloptions_test;
+CREATE TABLE reloptions_test(i INT) WITH (vacuum_truncate);
+SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass;
+
+DROP TABLE reloptions_test;
+CREATE TABLE reloptions_test(i INT) WITH (vacuum_truncate=FaLS);
+SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass;
+
+-- preferred "true" alias is used when storing
+DROP TABLE reloptions_test;
+CREATE TABLE reloptions_test(i INT) WITH (vacuum_index_cleanup=on);
+SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass;
+
+-- custom "third" value is available
+DROP TABLE reloptions_test;
+CREATE TABLE reloptions_test(i INT) WITH (vacuum_index_cleanup=auto);
+SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass;
+
-- Test vacuum_truncate option
DROP TABLE reloptions_test;
diff --git a/src/tools/pgindent/typedefs.list b/src/tools/pgindent/typedefs.list
index 3f3a888fd0e..1c8610fd46c 100644
--- a/src/tools/pgindent/typedefs.list
+++ b/src/tools/pgindent/typedefs.list
@@ -3952,6 +3952,7 @@ pg_sha512_ctx
pg_snapshot
pg_special_case
pg_stack_base_t
+pg_ternary
pg_time_t
pg_time_usec_t
pg_tz
@@ -4079,6 +4080,7 @@ relopt_kind
relopt_parse_elt
relopt_real
relopt_string
+relopt_ternary
relopt_type
relopt_value
relopts_validator
--
2.47.3
--c36t3fndxhkzbl5z--
^ permalink raw reply [nested|flat] 868+ messages in thread
* [PATCH v3] Introduce ternary reloptions
@ 2026-01-16 15:04 Álvaro Herrera <[email protected]>
0 siblings, 0 replies; 868+ messages in thread
From: Álvaro Herrera @ 2026-01-16 15:04 UTC (permalink / raw)
Introduce ternary reloption as a replacement for current
`vacuum_truncate` implementation. Remove the `vacuum_truncate_set`
separate flag and use TERNARY_UNSET instead.
This could also be used for other options such as `vacuum_index_cleanup`
and `buffering`, but lets get the scaffolding in first.
Discussion: https://postgr.es/m/3474141.usfYGdeWWP@thinkpad-pgpro
---
src/backend/access/common/reloptions.c | 137 ++++++++++++++++++-----
src/backend/commands/vacuum.c | 4 +-
src/include/access/reloptions.h | 27 ++---
src/include/postgres.h | 15 +++
src/include/utils/rel.h | 3 +-
src/test/regress/expected/reloptions.out | 36 ++++++
src/test/regress/sql/reloptions.sql | 21 ++++
src/tools/pgindent/typedefs.list | 2 +
8 files changed, 202 insertions(+), 43 deletions(-)
diff --git a/src/backend/access/common/reloptions.c b/src/backend/access/common/reloptions.c
index 0b83f98ed5f..6f1f577581a 100644
--- a/src/backend/access/common/reloptions.c
+++ b/src/backend/access/common/reloptions.c
@@ -40,9 +40,9 @@
*
* To add an option:
*
- * (i) decide on a type (bool, integer, real, enum, string), name, default
- * value, upper and lower bounds (if applicable); for strings, consider a
- * validation routine.
+ * (i) decide on a type (bool, ternary, integer, real, enum, string), name,
+ * default value, upper and lower bounds (if applicable); for strings,
+ * consider a validation routine.
* (ii) add a record below (or use add_<type>_reloption).
* (iii) add it to the appropriate options struct (perhaps StdRdOptions)
* (iv) add it to the appropriate handling routine (perhaps
@@ -147,15 +147,6 @@ static relopt_bool boolRelOpts[] =
},
false
},
- {
- {
- "vacuum_truncate",
- "Enables vacuum to truncate empty pages at the end of this table",
- RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
- ShareUpdateExclusiveLock
- },
- true
- },
{
{
"deduplicate_items",
@@ -170,6 +161,25 @@ static relopt_bool boolRelOpts[] =
{{NULL}}
};
+static relopt_ternary ternaryRelOpts[] =
+{
+ {
+ {
+ "vacuum_truncate",
+ "Enables vacuum to truncate empty pages at the end of this table",
+ RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
+ ShareUpdateExclusiveLock
+ },
+ TERNARY_UNSET
+ },
+ /* list terminator */
+ {
+ {
+ NULL
+ }
+ }
+};
+
static relopt_int intRelOpts[] =
{
{
@@ -609,6 +619,13 @@ initialize_reloptions(void)
boolRelOpts[i].gen.lockmode));
j++;
}
+ for (i = 0; ternaryRelOpts[i].gen.name; i++)
+ {
+ Assert(DoLockModesConflict(ternaryRelOpts[i].gen.lockmode,
+ ternaryRelOpts[i].gen.lockmode));
+ j++;
+ }
+
for (i = 0; intRelOpts[i].gen.name; i++)
{
Assert(DoLockModesConflict(intRelOpts[i].gen.lockmode,
@@ -649,6 +666,14 @@ initialize_reloptions(void)
j++;
}
+ for (i = 0; ternaryRelOpts[i].gen.name; i++)
+ {
+ relOpts[j] = &ternaryRelOpts[i].gen;
+ relOpts[j]->type = RELOPT_TYPE_TERNARY;
+ relOpts[j]->namelen = strlen(relOpts[j]->name);
+ j++;
+ }
+
for (i = 0; intRelOpts[i].gen.name; i++)
{
relOpts[j] = &intRelOpts[i].gen;
@@ -809,6 +834,9 @@ allocate_reloption(bits32 kinds, int type, const char *name, const char *desc,
case RELOPT_TYPE_BOOL:
size = sizeof(relopt_bool);
break;
+ case RELOPT_TYPE_TERNARY:
+ size = sizeof(relopt_ternary);
+ break;
case RELOPT_TYPE_INT:
size = sizeof(relopt_int);
break;
@@ -892,6 +920,57 @@ add_local_bool_reloption(local_relopts *relopts, const char *name,
add_local_reloption(relopts, (relopt_gen *) newoption, offset);
}
+/*
+ * init_ternary_reloption
+ * Allocate and initialize a new ternary reloption
+ */
+static relopt_ternary *
+init_ternary_reloption(bits32 kinds, const char *name, const char *desc,
+ pg_ternary default_val, LOCKMODE lockmode)
+{
+ relopt_ternary *newoption;
+
+ newoption = (relopt_ternary *)
+ allocate_reloption(kinds, RELOPT_TYPE_TERNARY, name, desc, lockmode);
+ newoption->default_val = default_val;
+
+ return newoption;
+}
+
+/*
+ * add_ternary_reloption
+ * Add a new ternary reloption
+ */
+void
+add_ternary_reloption(bits32 kinds, const char *name, const char *desc,
+ pg_ternary default_val, LOCKMODE lockmode)
+{
+ relopt_ternary *newoption;
+
+ newoption =
+ init_ternary_reloption(kinds, name, desc, default_val, lockmode);
+
+ add_reloption((relopt_gen *) newoption);
+}
+
+/*
+ * add_local_ternary_reloption
+ * Add a new ternary local reloption
+ *
+ * 'offset' is offset of ternary-typed field.
+ */
+void
+add_local_ternary_reloption(local_relopts *relopts, const char *name,
+ const char *desc, pg_ternary default_val,
+ int offset)
+{
+ relopt_ternary *newoption;
+
+ newoption =
+ init_ternary_reloption(RELOPT_KIND_LOCAL, name, desc, default_val, 0);
+
+ add_local_reloption(relopts, (relopt_gen *) newoption, offset);
+}
/*
* init_real_reloption
@@ -1626,6 +1705,19 @@ parse_one_reloption(relopt_value *option, char *text_str, int text_len,
option->gen->name, value)));
}
break;
+ case RELOPT_TYPE_TERNARY:
+ {
+ bool b;
+
+ parsed = parse_bool(value, &b);
+ option->values.ternary_val = b ? TERNARY_TRUE : TERNARY_FALSE;
+ if (validate && !parsed)
+ ereport(ERROR,
+ (errcode(ERRCODE_INVALID_PARAMETER_VALUE),
+ errmsg("invalid value for ternary option \"%s\": %s",
+ option->gen->name, value)));
+ }
+ break;
case RELOPT_TYPE_INT:
{
relopt_int *optint = (relopt_int *) option->gen;
@@ -1789,17 +1881,6 @@ fillRelOptions(void *rdopts, Size basesize,
char *itempos = ((char *) rdopts) + elems[j].offset;
char *string_val;
- /*
- * If isset_offset is provided, store whether the reloption is
- * set there.
- */
- if (elems[j].isset_offset > 0)
- {
- char *setpos = ((char *) rdopts) + elems[j].isset_offset;
-
- *(bool *) setpos = options[i].isset;
- }
-
switch (options[i].gen->type)
{
case RELOPT_TYPE_BOOL:
@@ -1807,6 +1888,11 @@ fillRelOptions(void *rdopts, Size basesize,
options[i].values.bool_val :
((relopt_bool *) options[i].gen)->default_val;
break;
+ case RELOPT_TYPE_TERNARY:
+ *(pg_ternary *) itempos = options[i].isset ?
+ options[i].values.ternary_val :
+ ((relopt_ternary *) options[i].gen)->default_val;
+ break;
case RELOPT_TYPE_INT:
*(int *) itempos = options[i].isset ?
options[i].values.int_val :
@@ -1923,8 +2009,8 @@ default_reloptions(Datum reloptions, bool validate, relopt_kind kind)
offsetof(StdRdOptions, parallel_workers)},
{"vacuum_index_cleanup", RELOPT_TYPE_ENUM,
offsetof(StdRdOptions, vacuum_index_cleanup)},
- {"vacuum_truncate", RELOPT_TYPE_BOOL,
- offsetof(StdRdOptions, vacuum_truncate), offsetof(StdRdOptions, vacuum_truncate_set)},
+ {"vacuum_truncate", RELOPT_TYPE_TERNARY,
+ offsetof(StdRdOptions, vacuum_truncate)},
{"vacuum_max_eager_freeze_failure_rate", RELOPT_TYPE_REAL,
offsetof(StdRdOptions, vacuum_max_eager_freeze_failure_rate)}
};
@@ -2004,7 +2090,6 @@ build_local_reloptions(local_relopts *relopts, Datum options, bool validate)
elems[i].optname = opt->option->name;
elems[i].opttype = opt->option->type;
elems[i].offset = opt->offset;
- elems[i].isset_offset = 0; /* not supported for local relopts yet */
i++;
}
diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c
index aa4fbec143f..696eab9bd97 100644
--- a/src/backend/commands/vacuum.c
+++ b/src/backend/commands/vacuum.c
@@ -2224,9 +2224,9 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams params,
{
StdRdOptions *opts = (StdRdOptions *) rel->rd_options;
- if (opts && opts->vacuum_truncate_set)
+ if (opts && opts->vacuum_truncate != TERNARY_UNSET)
{
- if (opts->vacuum_truncate)
+ if (opts->vacuum_truncate == TERNARY_TRUE)
params.truncate = VACOPTVALUE_ENABLED;
else
params.truncate = VACOPTVALUE_DISABLED;
diff --git a/src/include/access/reloptions.h b/src/include/access/reloptions.h
index 2f08e1b0cf0..dfbef2babf2 100644
--- a/src/include/access/reloptions.h
+++ b/src/include/access/reloptions.h
@@ -29,6 +29,7 @@
typedef enum relopt_type
{
RELOPT_TYPE_BOOL,
+ RELOPT_TYPE_TERNARY, /* on, off, unset */
RELOPT_TYPE_INT,
RELOPT_TYPE_REAL,
RELOPT_TYPE_ENUM,
@@ -80,6 +81,7 @@ typedef struct relopt_value
union
{
bool bool_val;
+ pg_ternary ternary_val;
int int_val;
double real_val;
int enum_val;
@@ -94,6 +96,12 @@ typedef struct relopt_bool
bool default_val;
} relopt_bool;
+typedef struct relopt_ternary
+{
+ relopt_gen gen;
+ int default_val;
+} relopt_ternary;
+
typedef struct relopt_int
{
relopt_gen gen;
@@ -152,19 +160,6 @@ typedef struct
const char *optname; /* option's name */
relopt_type opttype; /* option's datatype */
int offset; /* offset of field in result struct */
-
- /*
- * isset_offset is an optional offset of a field in the result struct that
- * stores whether the option is explicitly set for the relation or if it
- * just picked up the default value. In most cases, this can be
- * accomplished by giving the reloption a special out-of-range default
- * value (e.g., some integer reloptions use -2), but this isn't always
- * possible. For example, a Boolean reloption cannot be given an
- * out-of-range default, so we need another way to discover the source of
- * its value. This offset is only used if given a value greater than
- * zero.
- */
- int isset_offset;
} relopt_parse_elt;
/* Local reloption definition */
@@ -195,6 +190,9 @@ typedef struct local_relopts
extern relopt_kind add_reloption_kind(void);
extern void add_bool_reloption(bits32 kinds, const char *name, const char *desc,
bool default_val, LOCKMODE lockmode);
+extern void add_ternary_reloption(bits32 kinds, const char *name,
+ const char *desc, pg_ternary default_val,
+ LOCKMODE lockmode);
extern void add_int_reloption(bits32 kinds, const char *name, const char *desc,
int default_val, int min_val, int max_val,
LOCKMODE lockmode);
@@ -214,6 +212,9 @@ extern void register_reloptions_validator(local_relopts *relopts,
extern void add_local_bool_reloption(local_relopts *relopts, const char *name,
const char *desc, bool default_val,
int offset);
+extern void add_local_ternary_reloption(local_relopts *relopts,
+ const char *name, const char *desc,
+ pg_ternary default_val, int offset);
extern void add_local_int_reloption(local_relopts *relopts, const char *name,
const char *desc, int default_val,
int min_val, int max_val, int offset);
diff --git a/src/include/postgres.h b/src/include/postgres.h
index 7d93fbce709..47c88780776 100644
--- a/src/include/postgres.h
+++ b/src/include/postgres.h
@@ -543,6 +543,21 @@ Float8GetDatum(float8 X)
* ----------------------------------------------------------------
*/
+/*
+ * pg_ternary
+ * Boolean value with an extra "unset" value
+ *
+ * This enum can be used for values that want to distinguish between true,
+ * false, and unset.
+*/
+
+typedef enum pg_ternary
+{
+ TERNARY_FALSE = 0,
+ TERNARY_TRUE = 1,
+ TERNARY_UNSET = -1
+} pg_ternary;
+
/*
* NON_EXEC_STATIC: It's sometimes useful to define a variable or function
* that is normally static but extern when using EXEC_BACKEND (see
diff --git a/src/include/utils/rel.h b/src/include/utils/rel.h
index d03ab247788..5feb18a5373 100644
--- a/src/include/utils/rel.h
+++ b/src/include/utils/rel.h
@@ -347,8 +347,7 @@ typedef struct StdRdOptions
bool user_catalog_table; /* use as an additional catalog relation */
int parallel_workers; /* max number of parallel workers */
StdRdOptIndexCleanup vacuum_index_cleanup; /* controls index vacuuming */
- bool vacuum_truncate; /* enables vacuum to truncate a relation */
- bool vacuum_truncate_set; /* whether vacuum_truncate is set */
+ pg_ternary vacuum_truncate; /* enables vacuum to truncate a relation */
/*
* Fraction of pages in a relation that vacuum can eagerly scan and fail
diff --git a/src/test/regress/expected/reloptions.out b/src/test/regress/expected/reloptions.out
index 9de19b4e3f1..1c99f79ab01 100644
--- a/src/test/regress/expected/reloptions.out
+++ b/src/test/regress/expected/reloptions.out
@@ -98,6 +98,42 @@ SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass;
{fillfactor=13,autovacuum_enabled=false}
(1 row)
+-- Tests for future (FIXME) ternary options
+-- behave as boolean option: accept unassigned name and truncated value
+DROP TABLE reloptions_test;
+CREATE TABLE reloptions_test(i INT) WITH (vacuum_truncate);
+SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass;
+ reloptions
+------------------------
+ {vacuum_truncate=true}
+(1 row)
+
+DROP TABLE reloptions_test;
+CREATE TABLE reloptions_test(i INT) WITH (vacuum_truncate=FaLS);
+SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass;
+ reloptions
+------------------------
+ {vacuum_truncate=fals}
+(1 row)
+
+-- preferred "true" alias is used when storing
+DROP TABLE reloptions_test;
+CREATE TABLE reloptions_test(i INT) WITH (vacuum_index_cleanup=on);
+SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass;
+ reloptions
+---------------------------
+ {vacuum_index_cleanup=on}
+(1 row)
+
+-- custom "third" value is available
+DROP TABLE reloptions_test;
+CREATE TABLE reloptions_test(i INT) WITH (vacuum_index_cleanup=auto);
+SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass;
+ reloptions
+-----------------------------
+ {vacuum_index_cleanup=auto}
+(1 row)
+
-- Test vacuum_truncate option
DROP TABLE reloptions_test;
CREATE TEMP TABLE reloptions_test(i INT NOT NULL, j text)
diff --git a/src/test/regress/sql/reloptions.sql b/src/test/regress/sql/reloptions.sql
index 24fbe0b478d..f5980dafcbc 100644
--- a/src/test/regress/sql/reloptions.sql
+++ b/src/test/regress/sql/reloptions.sql
@@ -59,6 +59,27 @@ UPDATE pg_class
ALTER TABLE reloptions_test RESET (illegal_option);
SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass;
+-- Tests for future (FIXME) ternary options
+
+-- behave as boolean option: accept unassigned name and truncated value
+DROP TABLE reloptions_test;
+CREATE TABLE reloptions_test(i INT) WITH (vacuum_truncate);
+SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass;
+
+DROP TABLE reloptions_test;
+CREATE TABLE reloptions_test(i INT) WITH (vacuum_truncate=FaLS);
+SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass;
+
+-- preferred "true" alias is used when storing
+DROP TABLE reloptions_test;
+CREATE TABLE reloptions_test(i INT) WITH (vacuum_index_cleanup=on);
+SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass;
+
+-- custom "third" value is available
+DROP TABLE reloptions_test;
+CREATE TABLE reloptions_test(i INT) WITH (vacuum_index_cleanup=auto);
+SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass;
+
-- Test vacuum_truncate option
DROP TABLE reloptions_test;
diff --git a/src/tools/pgindent/typedefs.list b/src/tools/pgindent/typedefs.list
index 3f3a888fd0e..1c8610fd46c 100644
--- a/src/tools/pgindent/typedefs.list
+++ b/src/tools/pgindent/typedefs.list
@@ -3952,6 +3952,7 @@ pg_sha512_ctx
pg_snapshot
pg_special_case
pg_stack_base_t
+pg_ternary
pg_time_t
pg_time_usec_t
pg_tz
@@ -4079,6 +4080,7 @@ relopt_kind
relopt_parse_elt
relopt_real
relopt_string
+relopt_ternary
relopt_type
relopt_value
relopts_validator
--
2.47.3
--c36t3fndxhkzbl5z--
^ permalink raw reply [nested|flat] 868+ messages in thread
* [PATCH v3] Introduce ternary reloptions
@ 2026-01-16 15:04 Álvaro Herrera <[email protected]>
0 siblings, 0 replies; 868+ messages in thread
From: Álvaro Herrera @ 2026-01-16 15:04 UTC (permalink / raw)
Introduce ternary reloption as a replacement for current
`vacuum_truncate` implementation. Remove the `vacuum_truncate_set`
separate flag and use TERNARY_UNSET instead.
This could also be used for other options such as `vacuum_index_cleanup`
and `buffering`, but lets get the scaffolding in first.
Discussion: https://postgr.es/m/3474141.usfYGdeWWP@thinkpad-pgpro
---
src/backend/access/common/reloptions.c | 137 ++++++++++++++++++-----
src/backend/commands/vacuum.c | 4 +-
src/include/access/reloptions.h | 27 ++---
src/include/postgres.h | 15 +++
src/include/utils/rel.h | 3 +-
src/test/regress/expected/reloptions.out | 36 ++++++
src/test/regress/sql/reloptions.sql | 21 ++++
src/tools/pgindent/typedefs.list | 2 +
8 files changed, 202 insertions(+), 43 deletions(-)
diff --git a/src/backend/access/common/reloptions.c b/src/backend/access/common/reloptions.c
index 0b83f98ed5f..6f1f577581a 100644
--- a/src/backend/access/common/reloptions.c
+++ b/src/backend/access/common/reloptions.c
@@ -40,9 +40,9 @@
*
* To add an option:
*
- * (i) decide on a type (bool, integer, real, enum, string), name, default
- * value, upper and lower bounds (if applicable); for strings, consider a
- * validation routine.
+ * (i) decide on a type (bool, ternary, integer, real, enum, string), name,
+ * default value, upper and lower bounds (if applicable); for strings,
+ * consider a validation routine.
* (ii) add a record below (or use add_<type>_reloption).
* (iii) add it to the appropriate options struct (perhaps StdRdOptions)
* (iv) add it to the appropriate handling routine (perhaps
@@ -147,15 +147,6 @@ static relopt_bool boolRelOpts[] =
},
false
},
- {
- {
- "vacuum_truncate",
- "Enables vacuum to truncate empty pages at the end of this table",
- RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
- ShareUpdateExclusiveLock
- },
- true
- },
{
{
"deduplicate_items",
@@ -170,6 +161,25 @@ static relopt_bool boolRelOpts[] =
{{NULL}}
};
+static relopt_ternary ternaryRelOpts[] =
+{
+ {
+ {
+ "vacuum_truncate",
+ "Enables vacuum to truncate empty pages at the end of this table",
+ RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
+ ShareUpdateExclusiveLock
+ },
+ TERNARY_UNSET
+ },
+ /* list terminator */
+ {
+ {
+ NULL
+ }
+ }
+};
+
static relopt_int intRelOpts[] =
{
{
@@ -609,6 +619,13 @@ initialize_reloptions(void)
boolRelOpts[i].gen.lockmode));
j++;
}
+ for (i = 0; ternaryRelOpts[i].gen.name; i++)
+ {
+ Assert(DoLockModesConflict(ternaryRelOpts[i].gen.lockmode,
+ ternaryRelOpts[i].gen.lockmode));
+ j++;
+ }
+
for (i = 0; intRelOpts[i].gen.name; i++)
{
Assert(DoLockModesConflict(intRelOpts[i].gen.lockmode,
@@ -649,6 +666,14 @@ initialize_reloptions(void)
j++;
}
+ for (i = 0; ternaryRelOpts[i].gen.name; i++)
+ {
+ relOpts[j] = &ternaryRelOpts[i].gen;
+ relOpts[j]->type = RELOPT_TYPE_TERNARY;
+ relOpts[j]->namelen = strlen(relOpts[j]->name);
+ j++;
+ }
+
for (i = 0; intRelOpts[i].gen.name; i++)
{
relOpts[j] = &intRelOpts[i].gen;
@@ -809,6 +834,9 @@ allocate_reloption(bits32 kinds, int type, const char *name, const char *desc,
case RELOPT_TYPE_BOOL:
size = sizeof(relopt_bool);
break;
+ case RELOPT_TYPE_TERNARY:
+ size = sizeof(relopt_ternary);
+ break;
case RELOPT_TYPE_INT:
size = sizeof(relopt_int);
break;
@@ -892,6 +920,57 @@ add_local_bool_reloption(local_relopts *relopts, const char *name,
add_local_reloption(relopts, (relopt_gen *) newoption, offset);
}
+/*
+ * init_ternary_reloption
+ * Allocate and initialize a new ternary reloption
+ */
+static relopt_ternary *
+init_ternary_reloption(bits32 kinds, const char *name, const char *desc,
+ pg_ternary default_val, LOCKMODE lockmode)
+{
+ relopt_ternary *newoption;
+
+ newoption = (relopt_ternary *)
+ allocate_reloption(kinds, RELOPT_TYPE_TERNARY, name, desc, lockmode);
+ newoption->default_val = default_val;
+
+ return newoption;
+}
+
+/*
+ * add_ternary_reloption
+ * Add a new ternary reloption
+ */
+void
+add_ternary_reloption(bits32 kinds, const char *name, const char *desc,
+ pg_ternary default_val, LOCKMODE lockmode)
+{
+ relopt_ternary *newoption;
+
+ newoption =
+ init_ternary_reloption(kinds, name, desc, default_val, lockmode);
+
+ add_reloption((relopt_gen *) newoption);
+}
+
+/*
+ * add_local_ternary_reloption
+ * Add a new ternary local reloption
+ *
+ * 'offset' is offset of ternary-typed field.
+ */
+void
+add_local_ternary_reloption(local_relopts *relopts, const char *name,
+ const char *desc, pg_ternary default_val,
+ int offset)
+{
+ relopt_ternary *newoption;
+
+ newoption =
+ init_ternary_reloption(RELOPT_KIND_LOCAL, name, desc, default_val, 0);
+
+ add_local_reloption(relopts, (relopt_gen *) newoption, offset);
+}
/*
* init_real_reloption
@@ -1626,6 +1705,19 @@ parse_one_reloption(relopt_value *option, char *text_str, int text_len,
option->gen->name, value)));
}
break;
+ case RELOPT_TYPE_TERNARY:
+ {
+ bool b;
+
+ parsed = parse_bool(value, &b);
+ option->values.ternary_val = b ? TERNARY_TRUE : TERNARY_FALSE;
+ if (validate && !parsed)
+ ereport(ERROR,
+ (errcode(ERRCODE_INVALID_PARAMETER_VALUE),
+ errmsg("invalid value for ternary option \"%s\": %s",
+ option->gen->name, value)));
+ }
+ break;
case RELOPT_TYPE_INT:
{
relopt_int *optint = (relopt_int *) option->gen;
@@ -1789,17 +1881,6 @@ fillRelOptions(void *rdopts, Size basesize,
char *itempos = ((char *) rdopts) + elems[j].offset;
char *string_val;
- /*
- * If isset_offset is provided, store whether the reloption is
- * set there.
- */
- if (elems[j].isset_offset > 0)
- {
- char *setpos = ((char *) rdopts) + elems[j].isset_offset;
-
- *(bool *) setpos = options[i].isset;
- }
-
switch (options[i].gen->type)
{
case RELOPT_TYPE_BOOL:
@@ -1807,6 +1888,11 @@ fillRelOptions(void *rdopts, Size basesize,
options[i].values.bool_val :
((relopt_bool *) options[i].gen)->default_val;
break;
+ case RELOPT_TYPE_TERNARY:
+ *(pg_ternary *) itempos = options[i].isset ?
+ options[i].values.ternary_val :
+ ((relopt_ternary *) options[i].gen)->default_val;
+ break;
case RELOPT_TYPE_INT:
*(int *) itempos = options[i].isset ?
options[i].values.int_val :
@@ -1923,8 +2009,8 @@ default_reloptions(Datum reloptions, bool validate, relopt_kind kind)
offsetof(StdRdOptions, parallel_workers)},
{"vacuum_index_cleanup", RELOPT_TYPE_ENUM,
offsetof(StdRdOptions, vacuum_index_cleanup)},
- {"vacuum_truncate", RELOPT_TYPE_BOOL,
- offsetof(StdRdOptions, vacuum_truncate), offsetof(StdRdOptions, vacuum_truncate_set)},
+ {"vacuum_truncate", RELOPT_TYPE_TERNARY,
+ offsetof(StdRdOptions, vacuum_truncate)},
{"vacuum_max_eager_freeze_failure_rate", RELOPT_TYPE_REAL,
offsetof(StdRdOptions, vacuum_max_eager_freeze_failure_rate)}
};
@@ -2004,7 +2090,6 @@ build_local_reloptions(local_relopts *relopts, Datum options, bool validate)
elems[i].optname = opt->option->name;
elems[i].opttype = opt->option->type;
elems[i].offset = opt->offset;
- elems[i].isset_offset = 0; /* not supported for local relopts yet */
i++;
}
diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c
index aa4fbec143f..696eab9bd97 100644
--- a/src/backend/commands/vacuum.c
+++ b/src/backend/commands/vacuum.c
@@ -2224,9 +2224,9 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams params,
{
StdRdOptions *opts = (StdRdOptions *) rel->rd_options;
- if (opts && opts->vacuum_truncate_set)
+ if (opts && opts->vacuum_truncate != TERNARY_UNSET)
{
- if (opts->vacuum_truncate)
+ if (opts->vacuum_truncate == TERNARY_TRUE)
params.truncate = VACOPTVALUE_ENABLED;
else
params.truncate = VACOPTVALUE_DISABLED;
diff --git a/src/include/access/reloptions.h b/src/include/access/reloptions.h
index 2f08e1b0cf0..dfbef2babf2 100644
--- a/src/include/access/reloptions.h
+++ b/src/include/access/reloptions.h
@@ -29,6 +29,7 @@
typedef enum relopt_type
{
RELOPT_TYPE_BOOL,
+ RELOPT_TYPE_TERNARY, /* on, off, unset */
RELOPT_TYPE_INT,
RELOPT_TYPE_REAL,
RELOPT_TYPE_ENUM,
@@ -80,6 +81,7 @@ typedef struct relopt_value
union
{
bool bool_val;
+ pg_ternary ternary_val;
int int_val;
double real_val;
int enum_val;
@@ -94,6 +96,12 @@ typedef struct relopt_bool
bool default_val;
} relopt_bool;
+typedef struct relopt_ternary
+{
+ relopt_gen gen;
+ int default_val;
+} relopt_ternary;
+
typedef struct relopt_int
{
relopt_gen gen;
@@ -152,19 +160,6 @@ typedef struct
const char *optname; /* option's name */
relopt_type opttype; /* option's datatype */
int offset; /* offset of field in result struct */
-
- /*
- * isset_offset is an optional offset of a field in the result struct that
- * stores whether the option is explicitly set for the relation or if it
- * just picked up the default value. In most cases, this can be
- * accomplished by giving the reloption a special out-of-range default
- * value (e.g., some integer reloptions use -2), but this isn't always
- * possible. For example, a Boolean reloption cannot be given an
- * out-of-range default, so we need another way to discover the source of
- * its value. This offset is only used if given a value greater than
- * zero.
- */
- int isset_offset;
} relopt_parse_elt;
/* Local reloption definition */
@@ -195,6 +190,9 @@ typedef struct local_relopts
extern relopt_kind add_reloption_kind(void);
extern void add_bool_reloption(bits32 kinds, const char *name, const char *desc,
bool default_val, LOCKMODE lockmode);
+extern void add_ternary_reloption(bits32 kinds, const char *name,
+ const char *desc, pg_ternary default_val,
+ LOCKMODE lockmode);
extern void add_int_reloption(bits32 kinds, const char *name, const char *desc,
int default_val, int min_val, int max_val,
LOCKMODE lockmode);
@@ -214,6 +212,9 @@ extern void register_reloptions_validator(local_relopts *relopts,
extern void add_local_bool_reloption(local_relopts *relopts, const char *name,
const char *desc, bool default_val,
int offset);
+extern void add_local_ternary_reloption(local_relopts *relopts,
+ const char *name, const char *desc,
+ pg_ternary default_val, int offset);
extern void add_local_int_reloption(local_relopts *relopts, const char *name,
const char *desc, int default_val,
int min_val, int max_val, int offset);
diff --git a/src/include/postgres.h b/src/include/postgres.h
index 7d93fbce709..47c88780776 100644
--- a/src/include/postgres.h
+++ b/src/include/postgres.h
@@ -543,6 +543,21 @@ Float8GetDatum(float8 X)
* ----------------------------------------------------------------
*/
+/*
+ * pg_ternary
+ * Boolean value with an extra "unset" value
+ *
+ * This enum can be used for values that want to distinguish between true,
+ * false, and unset.
+*/
+
+typedef enum pg_ternary
+{
+ TERNARY_FALSE = 0,
+ TERNARY_TRUE = 1,
+ TERNARY_UNSET = -1
+} pg_ternary;
+
/*
* NON_EXEC_STATIC: It's sometimes useful to define a variable or function
* that is normally static but extern when using EXEC_BACKEND (see
diff --git a/src/include/utils/rel.h b/src/include/utils/rel.h
index d03ab247788..5feb18a5373 100644
--- a/src/include/utils/rel.h
+++ b/src/include/utils/rel.h
@@ -347,8 +347,7 @@ typedef struct StdRdOptions
bool user_catalog_table; /* use as an additional catalog relation */
int parallel_workers; /* max number of parallel workers */
StdRdOptIndexCleanup vacuum_index_cleanup; /* controls index vacuuming */
- bool vacuum_truncate; /* enables vacuum to truncate a relation */
- bool vacuum_truncate_set; /* whether vacuum_truncate is set */
+ pg_ternary vacuum_truncate; /* enables vacuum to truncate a relation */
/*
* Fraction of pages in a relation that vacuum can eagerly scan and fail
diff --git a/src/test/regress/expected/reloptions.out b/src/test/regress/expected/reloptions.out
index 9de19b4e3f1..1c99f79ab01 100644
--- a/src/test/regress/expected/reloptions.out
+++ b/src/test/regress/expected/reloptions.out
@@ -98,6 +98,42 @@ SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass;
{fillfactor=13,autovacuum_enabled=false}
(1 row)
+-- Tests for future (FIXME) ternary options
+-- behave as boolean option: accept unassigned name and truncated value
+DROP TABLE reloptions_test;
+CREATE TABLE reloptions_test(i INT) WITH (vacuum_truncate);
+SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass;
+ reloptions
+------------------------
+ {vacuum_truncate=true}
+(1 row)
+
+DROP TABLE reloptions_test;
+CREATE TABLE reloptions_test(i INT) WITH (vacuum_truncate=FaLS);
+SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass;
+ reloptions
+------------------------
+ {vacuum_truncate=fals}
+(1 row)
+
+-- preferred "true" alias is used when storing
+DROP TABLE reloptions_test;
+CREATE TABLE reloptions_test(i INT) WITH (vacuum_index_cleanup=on);
+SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass;
+ reloptions
+---------------------------
+ {vacuum_index_cleanup=on}
+(1 row)
+
+-- custom "third" value is available
+DROP TABLE reloptions_test;
+CREATE TABLE reloptions_test(i INT) WITH (vacuum_index_cleanup=auto);
+SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass;
+ reloptions
+-----------------------------
+ {vacuum_index_cleanup=auto}
+(1 row)
+
-- Test vacuum_truncate option
DROP TABLE reloptions_test;
CREATE TEMP TABLE reloptions_test(i INT NOT NULL, j text)
diff --git a/src/test/regress/sql/reloptions.sql b/src/test/regress/sql/reloptions.sql
index 24fbe0b478d..f5980dafcbc 100644
--- a/src/test/regress/sql/reloptions.sql
+++ b/src/test/regress/sql/reloptions.sql
@@ -59,6 +59,27 @@ UPDATE pg_class
ALTER TABLE reloptions_test RESET (illegal_option);
SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass;
+-- Tests for future (FIXME) ternary options
+
+-- behave as boolean option: accept unassigned name and truncated value
+DROP TABLE reloptions_test;
+CREATE TABLE reloptions_test(i INT) WITH (vacuum_truncate);
+SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass;
+
+DROP TABLE reloptions_test;
+CREATE TABLE reloptions_test(i INT) WITH (vacuum_truncate=FaLS);
+SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass;
+
+-- preferred "true" alias is used when storing
+DROP TABLE reloptions_test;
+CREATE TABLE reloptions_test(i INT) WITH (vacuum_index_cleanup=on);
+SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass;
+
+-- custom "third" value is available
+DROP TABLE reloptions_test;
+CREATE TABLE reloptions_test(i INT) WITH (vacuum_index_cleanup=auto);
+SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass;
+
-- Test vacuum_truncate option
DROP TABLE reloptions_test;
diff --git a/src/tools/pgindent/typedefs.list b/src/tools/pgindent/typedefs.list
index 3f3a888fd0e..1c8610fd46c 100644
--- a/src/tools/pgindent/typedefs.list
+++ b/src/tools/pgindent/typedefs.list
@@ -3952,6 +3952,7 @@ pg_sha512_ctx
pg_snapshot
pg_special_case
pg_stack_base_t
+pg_ternary
pg_time_t
pg_time_usec_t
pg_tz
@@ -4079,6 +4080,7 @@ relopt_kind
relopt_parse_elt
relopt_real
relopt_string
+relopt_ternary
relopt_type
relopt_value
relopts_validator
--
2.47.3
--c36t3fndxhkzbl5z--
^ permalink raw reply [nested|flat] 868+ messages in thread
* [PATCH v3] Introduce ternary reloptions
@ 2026-01-16 15:04 Álvaro Herrera <[email protected]>
0 siblings, 0 replies; 868+ messages in thread
From: Álvaro Herrera @ 2026-01-16 15:04 UTC (permalink / raw)
Introduce ternary reloption as a replacement for current
`vacuum_truncate` implementation. Remove the `vacuum_truncate_set`
separate flag and use TERNARY_UNSET instead.
This could also be used for other options such as `vacuum_index_cleanup`
and `buffering`, but lets get the scaffolding in first.
Discussion: https://postgr.es/m/3474141.usfYGdeWWP@thinkpad-pgpro
---
src/backend/access/common/reloptions.c | 137 ++++++++++++++++++-----
src/backend/commands/vacuum.c | 4 +-
src/include/access/reloptions.h | 27 ++---
src/include/postgres.h | 15 +++
src/include/utils/rel.h | 3 +-
src/test/regress/expected/reloptions.out | 36 ++++++
src/test/regress/sql/reloptions.sql | 21 ++++
src/tools/pgindent/typedefs.list | 2 +
8 files changed, 202 insertions(+), 43 deletions(-)
diff --git a/src/backend/access/common/reloptions.c b/src/backend/access/common/reloptions.c
index 0b83f98ed5f..6f1f577581a 100644
--- a/src/backend/access/common/reloptions.c
+++ b/src/backend/access/common/reloptions.c
@@ -40,9 +40,9 @@
*
* To add an option:
*
- * (i) decide on a type (bool, integer, real, enum, string), name, default
- * value, upper and lower bounds (if applicable); for strings, consider a
- * validation routine.
+ * (i) decide on a type (bool, ternary, integer, real, enum, string), name,
+ * default value, upper and lower bounds (if applicable); for strings,
+ * consider a validation routine.
* (ii) add a record below (or use add_<type>_reloption).
* (iii) add it to the appropriate options struct (perhaps StdRdOptions)
* (iv) add it to the appropriate handling routine (perhaps
@@ -147,15 +147,6 @@ static relopt_bool boolRelOpts[] =
},
false
},
- {
- {
- "vacuum_truncate",
- "Enables vacuum to truncate empty pages at the end of this table",
- RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
- ShareUpdateExclusiveLock
- },
- true
- },
{
{
"deduplicate_items",
@@ -170,6 +161,25 @@ static relopt_bool boolRelOpts[] =
{{NULL}}
};
+static relopt_ternary ternaryRelOpts[] =
+{
+ {
+ {
+ "vacuum_truncate",
+ "Enables vacuum to truncate empty pages at the end of this table",
+ RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
+ ShareUpdateExclusiveLock
+ },
+ TERNARY_UNSET
+ },
+ /* list terminator */
+ {
+ {
+ NULL
+ }
+ }
+};
+
static relopt_int intRelOpts[] =
{
{
@@ -609,6 +619,13 @@ initialize_reloptions(void)
boolRelOpts[i].gen.lockmode));
j++;
}
+ for (i = 0; ternaryRelOpts[i].gen.name; i++)
+ {
+ Assert(DoLockModesConflict(ternaryRelOpts[i].gen.lockmode,
+ ternaryRelOpts[i].gen.lockmode));
+ j++;
+ }
+
for (i = 0; intRelOpts[i].gen.name; i++)
{
Assert(DoLockModesConflict(intRelOpts[i].gen.lockmode,
@@ -649,6 +666,14 @@ initialize_reloptions(void)
j++;
}
+ for (i = 0; ternaryRelOpts[i].gen.name; i++)
+ {
+ relOpts[j] = &ternaryRelOpts[i].gen;
+ relOpts[j]->type = RELOPT_TYPE_TERNARY;
+ relOpts[j]->namelen = strlen(relOpts[j]->name);
+ j++;
+ }
+
for (i = 0; intRelOpts[i].gen.name; i++)
{
relOpts[j] = &intRelOpts[i].gen;
@@ -809,6 +834,9 @@ allocate_reloption(bits32 kinds, int type, const char *name, const char *desc,
case RELOPT_TYPE_BOOL:
size = sizeof(relopt_bool);
break;
+ case RELOPT_TYPE_TERNARY:
+ size = sizeof(relopt_ternary);
+ break;
case RELOPT_TYPE_INT:
size = sizeof(relopt_int);
break;
@@ -892,6 +920,57 @@ add_local_bool_reloption(local_relopts *relopts, const char *name,
add_local_reloption(relopts, (relopt_gen *) newoption, offset);
}
+/*
+ * init_ternary_reloption
+ * Allocate and initialize a new ternary reloption
+ */
+static relopt_ternary *
+init_ternary_reloption(bits32 kinds, const char *name, const char *desc,
+ pg_ternary default_val, LOCKMODE lockmode)
+{
+ relopt_ternary *newoption;
+
+ newoption = (relopt_ternary *)
+ allocate_reloption(kinds, RELOPT_TYPE_TERNARY, name, desc, lockmode);
+ newoption->default_val = default_val;
+
+ return newoption;
+}
+
+/*
+ * add_ternary_reloption
+ * Add a new ternary reloption
+ */
+void
+add_ternary_reloption(bits32 kinds, const char *name, const char *desc,
+ pg_ternary default_val, LOCKMODE lockmode)
+{
+ relopt_ternary *newoption;
+
+ newoption =
+ init_ternary_reloption(kinds, name, desc, default_val, lockmode);
+
+ add_reloption((relopt_gen *) newoption);
+}
+
+/*
+ * add_local_ternary_reloption
+ * Add a new ternary local reloption
+ *
+ * 'offset' is offset of ternary-typed field.
+ */
+void
+add_local_ternary_reloption(local_relopts *relopts, const char *name,
+ const char *desc, pg_ternary default_val,
+ int offset)
+{
+ relopt_ternary *newoption;
+
+ newoption =
+ init_ternary_reloption(RELOPT_KIND_LOCAL, name, desc, default_val, 0);
+
+ add_local_reloption(relopts, (relopt_gen *) newoption, offset);
+}
/*
* init_real_reloption
@@ -1626,6 +1705,19 @@ parse_one_reloption(relopt_value *option, char *text_str, int text_len,
option->gen->name, value)));
}
break;
+ case RELOPT_TYPE_TERNARY:
+ {
+ bool b;
+
+ parsed = parse_bool(value, &b);
+ option->values.ternary_val = b ? TERNARY_TRUE : TERNARY_FALSE;
+ if (validate && !parsed)
+ ereport(ERROR,
+ (errcode(ERRCODE_INVALID_PARAMETER_VALUE),
+ errmsg("invalid value for ternary option \"%s\": %s",
+ option->gen->name, value)));
+ }
+ break;
case RELOPT_TYPE_INT:
{
relopt_int *optint = (relopt_int *) option->gen;
@@ -1789,17 +1881,6 @@ fillRelOptions(void *rdopts, Size basesize,
char *itempos = ((char *) rdopts) + elems[j].offset;
char *string_val;
- /*
- * If isset_offset is provided, store whether the reloption is
- * set there.
- */
- if (elems[j].isset_offset > 0)
- {
- char *setpos = ((char *) rdopts) + elems[j].isset_offset;
-
- *(bool *) setpos = options[i].isset;
- }
-
switch (options[i].gen->type)
{
case RELOPT_TYPE_BOOL:
@@ -1807,6 +1888,11 @@ fillRelOptions(void *rdopts, Size basesize,
options[i].values.bool_val :
((relopt_bool *) options[i].gen)->default_val;
break;
+ case RELOPT_TYPE_TERNARY:
+ *(pg_ternary *) itempos = options[i].isset ?
+ options[i].values.ternary_val :
+ ((relopt_ternary *) options[i].gen)->default_val;
+ break;
case RELOPT_TYPE_INT:
*(int *) itempos = options[i].isset ?
options[i].values.int_val :
@@ -1923,8 +2009,8 @@ default_reloptions(Datum reloptions, bool validate, relopt_kind kind)
offsetof(StdRdOptions, parallel_workers)},
{"vacuum_index_cleanup", RELOPT_TYPE_ENUM,
offsetof(StdRdOptions, vacuum_index_cleanup)},
- {"vacuum_truncate", RELOPT_TYPE_BOOL,
- offsetof(StdRdOptions, vacuum_truncate), offsetof(StdRdOptions, vacuum_truncate_set)},
+ {"vacuum_truncate", RELOPT_TYPE_TERNARY,
+ offsetof(StdRdOptions, vacuum_truncate)},
{"vacuum_max_eager_freeze_failure_rate", RELOPT_TYPE_REAL,
offsetof(StdRdOptions, vacuum_max_eager_freeze_failure_rate)}
};
@@ -2004,7 +2090,6 @@ build_local_reloptions(local_relopts *relopts, Datum options, bool validate)
elems[i].optname = opt->option->name;
elems[i].opttype = opt->option->type;
elems[i].offset = opt->offset;
- elems[i].isset_offset = 0; /* not supported for local relopts yet */
i++;
}
diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c
index aa4fbec143f..696eab9bd97 100644
--- a/src/backend/commands/vacuum.c
+++ b/src/backend/commands/vacuum.c
@@ -2224,9 +2224,9 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams params,
{
StdRdOptions *opts = (StdRdOptions *) rel->rd_options;
- if (opts && opts->vacuum_truncate_set)
+ if (opts && opts->vacuum_truncate != TERNARY_UNSET)
{
- if (opts->vacuum_truncate)
+ if (opts->vacuum_truncate == TERNARY_TRUE)
params.truncate = VACOPTVALUE_ENABLED;
else
params.truncate = VACOPTVALUE_DISABLED;
diff --git a/src/include/access/reloptions.h b/src/include/access/reloptions.h
index 2f08e1b0cf0..dfbef2babf2 100644
--- a/src/include/access/reloptions.h
+++ b/src/include/access/reloptions.h
@@ -29,6 +29,7 @@
typedef enum relopt_type
{
RELOPT_TYPE_BOOL,
+ RELOPT_TYPE_TERNARY, /* on, off, unset */
RELOPT_TYPE_INT,
RELOPT_TYPE_REAL,
RELOPT_TYPE_ENUM,
@@ -80,6 +81,7 @@ typedef struct relopt_value
union
{
bool bool_val;
+ pg_ternary ternary_val;
int int_val;
double real_val;
int enum_val;
@@ -94,6 +96,12 @@ typedef struct relopt_bool
bool default_val;
} relopt_bool;
+typedef struct relopt_ternary
+{
+ relopt_gen gen;
+ int default_val;
+} relopt_ternary;
+
typedef struct relopt_int
{
relopt_gen gen;
@@ -152,19 +160,6 @@ typedef struct
const char *optname; /* option's name */
relopt_type opttype; /* option's datatype */
int offset; /* offset of field in result struct */
-
- /*
- * isset_offset is an optional offset of a field in the result struct that
- * stores whether the option is explicitly set for the relation or if it
- * just picked up the default value. In most cases, this can be
- * accomplished by giving the reloption a special out-of-range default
- * value (e.g., some integer reloptions use -2), but this isn't always
- * possible. For example, a Boolean reloption cannot be given an
- * out-of-range default, so we need another way to discover the source of
- * its value. This offset is only used if given a value greater than
- * zero.
- */
- int isset_offset;
} relopt_parse_elt;
/* Local reloption definition */
@@ -195,6 +190,9 @@ typedef struct local_relopts
extern relopt_kind add_reloption_kind(void);
extern void add_bool_reloption(bits32 kinds, const char *name, const char *desc,
bool default_val, LOCKMODE lockmode);
+extern void add_ternary_reloption(bits32 kinds, const char *name,
+ const char *desc, pg_ternary default_val,
+ LOCKMODE lockmode);
extern void add_int_reloption(bits32 kinds, const char *name, const char *desc,
int default_val, int min_val, int max_val,
LOCKMODE lockmode);
@@ -214,6 +212,9 @@ extern void register_reloptions_validator(local_relopts *relopts,
extern void add_local_bool_reloption(local_relopts *relopts, const char *name,
const char *desc, bool default_val,
int offset);
+extern void add_local_ternary_reloption(local_relopts *relopts,
+ const char *name, const char *desc,
+ pg_ternary default_val, int offset);
extern void add_local_int_reloption(local_relopts *relopts, const char *name,
const char *desc, int default_val,
int min_val, int max_val, int offset);
diff --git a/src/include/postgres.h b/src/include/postgres.h
index 7d93fbce709..47c88780776 100644
--- a/src/include/postgres.h
+++ b/src/include/postgres.h
@@ -543,6 +543,21 @@ Float8GetDatum(float8 X)
* ----------------------------------------------------------------
*/
+/*
+ * pg_ternary
+ * Boolean value with an extra "unset" value
+ *
+ * This enum can be used for values that want to distinguish between true,
+ * false, and unset.
+*/
+
+typedef enum pg_ternary
+{
+ TERNARY_FALSE = 0,
+ TERNARY_TRUE = 1,
+ TERNARY_UNSET = -1
+} pg_ternary;
+
/*
* NON_EXEC_STATIC: It's sometimes useful to define a variable or function
* that is normally static but extern when using EXEC_BACKEND (see
diff --git a/src/include/utils/rel.h b/src/include/utils/rel.h
index d03ab247788..5feb18a5373 100644
--- a/src/include/utils/rel.h
+++ b/src/include/utils/rel.h
@@ -347,8 +347,7 @@ typedef struct StdRdOptions
bool user_catalog_table; /* use as an additional catalog relation */
int parallel_workers; /* max number of parallel workers */
StdRdOptIndexCleanup vacuum_index_cleanup; /* controls index vacuuming */
- bool vacuum_truncate; /* enables vacuum to truncate a relation */
- bool vacuum_truncate_set; /* whether vacuum_truncate is set */
+ pg_ternary vacuum_truncate; /* enables vacuum to truncate a relation */
/*
* Fraction of pages in a relation that vacuum can eagerly scan and fail
diff --git a/src/test/regress/expected/reloptions.out b/src/test/regress/expected/reloptions.out
index 9de19b4e3f1..1c99f79ab01 100644
--- a/src/test/regress/expected/reloptions.out
+++ b/src/test/regress/expected/reloptions.out
@@ -98,6 +98,42 @@ SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass;
{fillfactor=13,autovacuum_enabled=false}
(1 row)
+-- Tests for future (FIXME) ternary options
+-- behave as boolean option: accept unassigned name and truncated value
+DROP TABLE reloptions_test;
+CREATE TABLE reloptions_test(i INT) WITH (vacuum_truncate);
+SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass;
+ reloptions
+------------------------
+ {vacuum_truncate=true}
+(1 row)
+
+DROP TABLE reloptions_test;
+CREATE TABLE reloptions_test(i INT) WITH (vacuum_truncate=FaLS);
+SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass;
+ reloptions
+------------------------
+ {vacuum_truncate=fals}
+(1 row)
+
+-- preferred "true" alias is used when storing
+DROP TABLE reloptions_test;
+CREATE TABLE reloptions_test(i INT) WITH (vacuum_index_cleanup=on);
+SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass;
+ reloptions
+---------------------------
+ {vacuum_index_cleanup=on}
+(1 row)
+
+-- custom "third" value is available
+DROP TABLE reloptions_test;
+CREATE TABLE reloptions_test(i INT) WITH (vacuum_index_cleanup=auto);
+SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass;
+ reloptions
+-----------------------------
+ {vacuum_index_cleanup=auto}
+(1 row)
+
-- Test vacuum_truncate option
DROP TABLE reloptions_test;
CREATE TEMP TABLE reloptions_test(i INT NOT NULL, j text)
diff --git a/src/test/regress/sql/reloptions.sql b/src/test/regress/sql/reloptions.sql
index 24fbe0b478d..f5980dafcbc 100644
--- a/src/test/regress/sql/reloptions.sql
+++ b/src/test/regress/sql/reloptions.sql
@@ -59,6 +59,27 @@ UPDATE pg_class
ALTER TABLE reloptions_test RESET (illegal_option);
SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass;
+-- Tests for future (FIXME) ternary options
+
+-- behave as boolean option: accept unassigned name and truncated value
+DROP TABLE reloptions_test;
+CREATE TABLE reloptions_test(i INT) WITH (vacuum_truncate);
+SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass;
+
+DROP TABLE reloptions_test;
+CREATE TABLE reloptions_test(i INT) WITH (vacuum_truncate=FaLS);
+SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass;
+
+-- preferred "true" alias is used when storing
+DROP TABLE reloptions_test;
+CREATE TABLE reloptions_test(i INT) WITH (vacuum_index_cleanup=on);
+SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass;
+
+-- custom "third" value is available
+DROP TABLE reloptions_test;
+CREATE TABLE reloptions_test(i INT) WITH (vacuum_index_cleanup=auto);
+SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass;
+
-- Test vacuum_truncate option
DROP TABLE reloptions_test;
diff --git a/src/tools/pgindent/typedefs.list b/src/tools/pgindent/typedefs.list
index 3f3a888fd0e..1c8610fd46c 100644
--- a/src/tools/pgindent/typedefs.list
+++ b/src/tools/pgindent/typedefs.list
@@ -3952,6 +3952,7 @@ pg_sha512_ctx
pg_snapshot
pg_special_case
pg_stack_base_t
+pg_ternary
pg_time_t
pg_time_usec_t
pg_tz
@@ -4079,6 +4080,7 @@ relopt_kind
relopt_parse_elt
relopt_real
relopt_string
+relopt_ternary
relopt_type
relopt_value
relopts_validator
--
2.47.3
--c36t3fndxhkzbl5z--
^ permalink raw reply [nested|flat] 868+ messages in thread
* [PATCH v3] Introduce ternary reloptions
@ 2026-01-16 15:04 Álvaro Herrera <[email protected]>
0 siblings, 0 replies; 868+ messages in thread
From: Álvaro Herrera @ 2026-01-16 15:04 UTC (permalink / raw)
Introduce ternary reloption as a replacement for current
`vacuum_truncate` implementation. Remove the `vacuum_truncate_set`
separate flag and use TERNARY_UNSET instead.
This could also be used for other options such as `vacuum_index_cleanup`
and `buffering`, but lets get the scaffolding in first.
Discussion: https://postgr.es/m/3474141.usfYGdeWWP@thinkpad-pgpro
---
src/backend/access/common/reloptions.c | 137 ++++++++++++++++++-----
src/backend/commands/vacuum.c | 4 +-
src/include/access/reloptions.h | 27 ++---
src/include/postgres.h | 15 +++
src/include/utils/rel.h | 3 +-
src/test/regress/expected/reloptions.out | 36 ++++++
src/test/regress/sql/reloptions.sql | 21 ++++
src/tools/pgindent/typedefs.list | 2 +
8 files changed, 202 insertions(+), 43 deletions(-)
diff --git a/src/backend/access/common/reloptions.c b/src/backend/access/common/reloptions.c
index 0b83f98ed5f..6f1f577581a 100644
--- a/src/backend/access/common/reloptions.c
+++ b/src/backend/access/common/reloptions.c
@@ -40,9 +40,9 @@
*
* To add an option:
*
- * (i) decide on a type (bool, integer, real, enum, string), name, default
- * value, upper and lower bounds (if applicable); for strings, consider a
- * validation routine.
+ * (i) decide on a type (bool, ternary, integer, real, enum, string), name,
+ * default value, upper and lower bounds (if applicable); for strings,
+ * consider a validation routine.
* (ii) add a record below (or use add_<type>_reloption).
* (iii) add it to the appropriate options struct (perhaps StdRdOptions)
* (iv) add it to the appropriate handling routine (perhaps
@@ -147,15 +147,6 @@ static relopt_bool boolRelOpts[] =
},
false
},
- {
- {
- "vacuum_truncate",
- "Enables vacuum to truncate empty pages at the end of this table",
- RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
- ShareUpdateExclusiveLock
- },
- true
- },
{
{
"deduplicate_items",
@@ -170,6 +161,25 @@ static relopt_bool boolRelOpts[] =
{{NULL}}
};
+static relopt_ternary ternaryRelOpts[] =
+{
+ {
+ {
+ "vacuum_truncate",
+ "Enables vacuum to truncate empty pages at the end of this table",
+ RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
+ ShareUpdateExclusiveLock
+ },
+ TERNARY_UNSET
+ },
+ /* list terminator */
+ {
+ {
+ NULL
+ }
+ }
+};
+
static relopt_int intRelOpts[] =
{
{
@@ -609,6 +619,13 @@ initialize_reloptions(void)
boolRelOpts[i].gen.lockmode));
j++;
}
+ for (i = 0; ternaryRelOpts[i].gen.name; i++)
+ {
+ Assert(DoLockModesConflict(ternaryRelOpts[i].gen.lockmode,
+ ternaryRelOpts[i].gen.lockmode));
+ j++;
+ }
+
for (i = 0; intRelOpts[i].gen.name; i++)
{
Assert(DoLockModesConflict(intRelOpts[i].gen.lockmode,
@@ -649,6 +666,14 @@ initialize_reloptions(void)
j++;
}
+ for (i = 0; ternaryRelOpts[i].gen.name; i++)
+ {
+ relOpts[j] = &ternaryRelOpts[i].gen;
+ relOpts[j]->type = RELOPT_TYPE_TERNARY;
+ relOpts[j]->namelen = strlen(relOpts[j]->name);
+ j++;
+ }
+
for (i = 0; intRelOpts[i].gen.name; i++)
{
relOpts[j] = &intRelOpts[i].gen;
@@ -809,6 +834,9 @@ allocate_reloption(bits32 kinds, int type, const char *name, const char *desc,
case RELOPT_TYPE_BOOL:
size = sizeof(relopt_bool);
break;
+ case RELOPT_TYPE_TERNARY:
+ size = sizeof(relopt_ternary);
+ break;
case RELOPT_TYPE_INT:
size = sizeof(relopt_int);
break;
@@ -892,6 +920,57 @@ add_local_bool_reloption(local_relopts *relopts, const char *name,
add_local_reloption(relopts, (relopt_gen *) newoption, offset);
}
+/*
+ * init_ternary_reloption
+ * Allocate and initialize a new ternary reloption
+ */
+static relopt_ternary *
+init_ternary_reloption(bits32 kinds, const char *name, const char *desc,
+ pg_ternary default_val, LOCKMODE lockmode)
+{
+ relopt_ternary *newoption;
+
+ newoption = (relopt_ternary *)
+ allocate_reloption(kinds, RELOPT_TYPE_TERNARY, name, desc, lockmode);
+ newoption->default_val = default_val;
+
+ return newoption;
+}
+
+/*
+ * add_ternary_reloption
+ * Add a new ternary reloption
+ */
+void
+add_ternary_reloption(bits32 kinds, const char *name, const char *desc,
+ pg_ternary default_val, LOCKMODE lockmode)
+{
+ relopt_ternary *newoption;
+
+ newoption =
+ init_ternary_reloption(kinds, name, desc, default_val, lockmode);
+
+ add_reloption((relopt_gen *) newoption);
+}
+
+/*
+ * add_local_ternary_reloption
+ * Add a new ternary local reloption
+ *
+ * 'offset' is offset of ternary-typed field.
+ */
+void
+add_local_ternary_reloption(local_relopts *relopts, const char *name,
+ const char *desc, pg_ternary default_val,
+ int offset)
+{
+ relopt_ternary *newoption;
+
+ newoption =
+ init_ternary_reloption(RELOPT_KIND_LOCAL, name, desc, default_val, 0);
+
+ add_local_reloption(relopts, (relopt_gen *) newoption, offset);
+}
/*
* init_real_reloption
@@ -1626,6 +1705,19 @@ parse_one_reloption(relopt_value *option, char *text_str, int text_len,
option->gen->name, value)));
}
break;
+ case RELOPT_TYPE_TERNARY:
+ {
+ bool b;
+
+ parsed = parse_bool(value, &b);
+ option->values.ternary_val = b ? TERNARY_TRUE : TERNARY_FALSE;
+ if (validate && !parsed)
+ ereport(ERROR,
+ (errcode(ERRCODE_INVALID_PARAMETER_VALUE),
+ errmsg("invalid value for ternary option \"%s\": %s",
+ option->gen->name, value)));
+ }
+ break;
case RELOPT_TYPE_INT:
{
relopt_int *optint = (relopt_int *) option->gen;
@@ -1789,17 +1881,6 @@ fillRelOptions(void *rdopts, Size basesize,
char *itempos = ((char *) rdopts) + elems[j].offset;
char *string_val;
- /*
- * If isset_offset is provided, store whether the reloption is
- * set there.
- */
- if (elems[j].isset_offset > 0)
- {
- char *setpos = ((char *) rdopts) + elems[j].isset_offset;
-
- *(bool *) setpos = options[i].isset;
- }
-
switch (options[i].gen->type)
{
case RELOPT_TYPE_BOOL:
@@ -1807,6 +1888,11 @@ fillRelOptions(void *rdopts, Size basesize,
options[i].values.bool_val :
((relopt_bool *) options[i].gen)->default_val;
break;
+ case RELOPT_TYPE_TERNARY:
+ *(pg_ternary *) itempos = options[i].isset ?
+ options[i].values.ternary_val :
+ ((relopt_ternary *) options[i].gen)->default_val;
+ break;
case RELOPT_TYPE_INT:
*(int *) itempos = options[i].isset ?
options[i].values.int_val :
@@ -1923,8 +2009,8 @@ default_reloptions(Datum reloptions, bool validate, relopt_kind kind)
offsetof(StdRdOptions, parallel_workers)},
{"vacuum_index_cleanup", RELOPT_TYPE_ENUM,
offsetof(StdRdOptions, vacuum_index_cleanup)},
- {"vacuum_truncate", RELOPT_TYPE_BOOL,
- offsetof(StdRdOptions, vacuum_truncate), offsetof(StdRdOptions, vacuum_truncate_set)},
+ {"vacuum_truncate", RELOPT_TYPE_TERNARY,
+ offsetof(StdRdOptions, vacuum_truncate)},
{"vacuum_max_eager_freeze_failure_rate", RELOPT_TYPE_REAL,
offsetof(StdRdOptions, vacuum_max_eager_freeze_failure_rate)}
};
@@ -2004,7 +2090,6 @@ build_local_reloptions(local_relopts *relopts, Datum options, bool validate)
elems[i].optname = opt->option->name;
elems[i].opttype = opt->option->type;
elems[i].offset = opt->offset;
- elems[i].isset_offset = 0; /* not supported for local relopts yet */
i++;
}
diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c
index aa4fbec143f..696eab9bd97 100644
--- a/src/backend/commands/vacuum.c
+++ b/src/backend/commands/vacuum.c
@@ -2224,9 +2224,9 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams params,
{
StdRdOptions *opts = (StdRdOptions *) rel->rd_options;
- if (opts && opts->vacuum_truncate_set)
+ if (opts && opts->vacuum_truncate != TERNARY_UNSET)
{
- if (opts->vacuum_truncate)
+ if (opts->vacuum_truncate == TERNARY_TRUE)
params.truncate = VACOPTVALUE_ENABLED;
else
params.truncate = VACOPTVALUE_DISABLED;
diff --git a/src/include/access/reloptions.h b/src/include/access/reloptions.h
index 2f08e1b0cf0..dfbef2babf2 100644
--- a/src/include/access/reloptions.h
+++ b/src/include/access/reloptions.h
@@ -29,6 +29,7 @@
typedef enum relopt_type
{
RELOPT_TYPE_BOOL,
+ RELOPT_TYPE_TERNARY, /* on, off, unset */
RELOPT_TYPE_INT,
RELOPT_TYPE_REAL,
RELOPT_TYPE_ENUM,
@@ -80,6 +81,7 @@ typedef struct relopt_value
union
{
bool bool_val;
+ pg_ternary ternary_val;
int int_val;
double real_val;
int enum_val;
@@ -94,6 +96,12 @@ typedef struct relopt_bool
bool default_val;
} relopt_bool;
+typedef struct relopt_ternary
+{
+ relopt_gen gen;
+ int default_val;
+} relopt_ternary;
+
typedef struct relopt_int
{
relopt_gen gen;
@@ -152,19 +160,6 @@ typedef struct
const char *optname; /* option's name */
relopt_type opttype; /* option's datatype */
int offset; /* offset of field in result struct */
-
- /*
- * isset_offset is an optional offset of a field in the result struct that
- * stores whether the option is explicitly set for the relation or if it
- * just picked up the default value. In most cases, this can be
- * accomplished by giving the reloption a special out-of-range default
- * value (e.g., some integer reloptions use -2), but this isn't always
- * possible. For example, a Boolean reloption cannot be given an
- * out-of-range default, so we need another way to discover the source of
- * its value. This offset is only used if given a value greater than
- * zero.
- */
- int isset_offset;
} relopt_parse_elt;
/* Local reloption definition */
@@ -195,6 +190,9 @@ typedef struct local_relopts
extern relopt_kind add_reloption_kind(void);
extern void add_bool_reloption(bits32 kinds, const char *name, const char *desc,
bool default_val, LOCKMODE lockmode);
+extern void add_ternary_reloption(bits32 kinds, const char *name,
+ const char *desc, pg_ternary default_val,
+ LOCKMODE lockmode);
extern void add_int_reloption(bits32 kinds, const char *name, const char *desc,
int default_val, int min_val, int max_val,
LOCKMODE lockmode);
@@ -214,6 +212,9 @@ extern void register_reloptions_validator(local_relopts *relopts,
extern void add_local_bool_reloption(local_relopts *relopts, const char *name,
const char *desc, bool default_val,
int offset);
+extern void add_local_ternary_reloption(local_relopts *relopts,
+ const char *name, const char *desc,
+ pg_ternary default_val, int offset);
extern void add_local_int_reloption(local_relopts *relopts, const char *name,
const char *desc, int default_val,
int min_val, int max_val, int offset);
diff --git a/src/include/postgres.h b/src/include/postgres.h
index 7d93fbce709..47c88780776 100644
--- a/src/include/postgres.h
+++ b/src/include/postgres.h
@@ -543,6 +543,21 @@ Float8GetDatum(float8 X)
* ----------------------------------------------------------------
*/
+/*
+ * pg_ternary
+ * Boolean value with an extra "unset" value
+ *
+ * This enum can be used for values that want to distinguish between true,
+ * false, and unset.
+*/
+
+typedef enum pg_ternary
+{
+ TERNARY_FALSE = 0,
+ TERNARY_TRUE = 1,
+ TERNARY_UNSET = -1
+} pg_ternary;
+
/*
* NON_EXEC_STATIC: It's sometimes useful to define a variable or function
* that is normally static but extern when using EXEC_BACKEND (see
diff --git a/src/include/utils/rel.h b/src/include/utils/rel.h
index d03ab247788..5feb18a5373 100644
--- a/src/include/utils/rel.h
+++ b/src/include/utils/rel.h
@@ -347,8 +347,7 @@ typedef struct StdRdOptions
bool user_catalog_table; /* use as an additional catalog relation */
int parallel_workers; /* max number of parallel workers */
StdRdOptIndexCleanup vacuum_index_cleanup; /* controls index vacuuming */
- bool vacuum_truncate; /* enables vacuum to truncate a relation */
- bool vacuum_truncate_set; /* whether vacuum_truncate is set */
+ pg_ternary vacuum_truncate; /* enables vacuum to truncate a relation */
/*
* Fraction of pages in a relation that vacuum can eagerly scan and fail
diff --git a/src/test/regress/expected/reloptions.out b/src/test/regress/expected/reloptions.out
index 9de19b4e3f1..1c99f79ab01 100644
--- a/src/test/regress/expected/reloptions.out
+++ b/src/test/regress/expected/reloptions.out
@@ -98,6 +98,42 @@ SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass;
{fillfactor=13,autovacuum_enabled=false}
(1 row)
+-- Tests for future (FIXME) ternary options
+-- behave as boolean option: accept unassigned name and truncated value
+DROP TABLE reloptions_test;
+CREATE TABLE reloptions_test(i INT) WITH (vacuum_truncate);
+SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass;
+ reloptions
+------------------------
+ {vacuum_truncate=true}
+(1 row)
+
+DROP TABLE reloptions_test;
+CREATE TABLE reloptions_test(i INT) WITH (vacuum_truncate=FaLS);
+SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass;
+ reloptions
+------------------------
+ {vacuum_truncate=fals}
+(1 row)
+
+-- preferred "true" alias is used when storing
+DROP TABLE reloptions_test;
+CREATE TABLE reloptions_test(i INT) WITH (vacuum_index_cleanup=on);
+SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass;
+ reloptions
+---------------------------
+ {vacuum_index_cleanup=on}
+(1 row)
+
+-- custom "third" value is available
+DROP TABLE reloptions_test;
+CREATE TABLE reloptions_test(i INT) WITH (vacuum_index_cleanup=auto);
+SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass;
+ reloptions
+-----------------------------
+ {vacuum_index_cleanup=auto}
+(1 row)
+
-- Test vacuum_truncate option
DROP TABLE reloptions_test;
CREATE TEMP TABLE reloptions_test(i INT NOT NULL, j text)
diff --git a/src/test/regress/sql/reloptions.sql b/src/test/regress/sql/reloptions.sql
index 24fbe0b478d..f5980dafcbc 100644
--- a/src/test/regress/sql/reloptions.sql
+++ b/src/test/regress/sql/reloptions.sql
@@ -59,6 +59,27 @@ UPDATE pg_class
ALTER TABLE reloptions_test RESET (illegal_option);
SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass;
+-- Tests for future (FIXME) ternary options
+
+-- behave as boolean option: accept unassigned name and truncated value
+DROP TABLE reloptions_test;
+CREATE TABLE reloptions_test(i INT) WITH (vacuum_truncate);
+SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass;
+
+DROP TABLE reloptions_test;
+CREATE TABLE reloptions_test(i INT) WITH (vacuum_truncate=FaLS);
+SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass;
+
+-- preferred "true" alias is used when storing
+DROP TABLE reloptions_test;
+CREATE TABLE reloptions_test(i INT) WITH (vacuum_index_cleanup=on);
+SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass;
+
+-- custom "third" value is available
+DROP TABLE reloptions_test;
+CREATE TABLE reloptions_test(i INT) WITH (vacuum_index_cleanup=auto);
+SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass;
+
-- Test vacuum_truncate option
DROP TABLE reloptions_test;
diff --git a/src/tools/pgindent/typedefs.list b/src/tools/pgindent/typedefs.list
index 3f3a888fd0e..1c8610fd46c 100644
--- a/src/tools/pgindent/typedefs.list
+++ b/src/tools/pgindent/typedefs.list
@@ -3952,6 +3952,7 @@ pg_sha512_ctx
pg_snapshot
pg_special_case
pg_stack_base_t
+pg_ternary
pg_time_t
pg_time_usec_t
pg_tz
@@ -4079,6 +4080,7 @@ relopt_kind
relopt_parse_elt
relopt_real
relopt_string
+relopt_ternary
relopt_type
relopt_value
relopts_validator
--
2.47.3
--c36t3fndxhkzbl5z--
^ permalink raw reply [nested|flat] 868+ messages in thread
* [PATCH v3] Introduce ternary reloptions
@ 2026-01-16 15:04 Álvaro Herrera <[email protected]>
0 siblings, 0 replies; 868+ messages in thread
From: Álvaro Herrera @ 2026-01-16 15:04 UTC (permalink / raw)
Introduce ternary reloption as a replacement for current
`vacuum_truncate` implementation. Remove the `vacuum_truncate_set`
separate flag and use TERNARY_UNSET instead.
This could also be used for other options such as `vacuum_index_cleanup`
and `buffering`, but lets get the scaffolding in first.
Discussion: https://postgr.es/m/3474141.usfYGdeWWP@thinkpad-pgpro
---
src/backend/access/common/reloptions.c | 137 ++++++++++++++++++-----
src/backend/commands/vacuum.c | 4 +-
src/include/access/reloptions.h | 27 ++---
src/include/postgres.h | 15 +++
src/include/utils/rel.h | 3 +-
src/test/regress/expected/reloptions.out | 36 ++++++
src/test/regress/sql/reloptions.sql | 21 ++++
src/tools/pgindent/typedefs.list | 2 +
8 files changed, 202 insertions(+), 43 deletions(-)
diff --git a/src/backend/access/common/reloptions.c b/src/backend/access/common/reloptions.c
index 0b83f98ed5f..6f1f577581a 100644
--- a/src/backend/access/common/reloptions.c
+++ b/src/backend/access/common/reloptions.c
@@ -40,9 +40,9 @@
*
* To add an option:
*
- * (i) decide on a type (bool, integer, real, enum, string), name, default
- * value, upper and lower bounds (if applicable); for strings, consider a
- * validation routine.
+ * (i) decide on a type (bool, ternary, integer, real, enum, string), name,
+ * default value, upper and lower bounds (if applicable); for strings,
+ * consider a validation routine.
* (ii) add a record below (or use add_<type>_reloption).
* (iii) add it to the appropriate options struct (perhaps StdRdOptions)
* (iv) add it to the appropriate handling routine (perhaps
@@ -147,15 +147,6 @@ static relopt_bool boolRelOpts[] =
},
false
},
- {
- {
- "vacuum_truncate",
- "Enables vacuum to truncate empty pages at the end of this table",
- RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
- ShareUpdateExclusiveLock
- },
- true
- },
{
{
"deduplicate_items",
@@ -170,6 +161,25 @@ static relopt_bool boolRelOpts[] =
{{NULL}}
};
+static relopt_ternary ternaryRelOpts[] =
+{
+ {
+ {
+ "vacuum_truncate",
+ "Enables vacuum to truncate empty pages at the end of this table",
+ RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
+ ShareUpdateExclusiveLock
+ },
+ TERNARY_UNSET
+ },
+ /* list terminator */
+ {
+ {
+ NULL
+ }
+ }
+};
+
static relopt_int intRelOpts[] =
{
{
@@ -609,6 +619,13 @@ initialize_reloptions(void)
boolRelOpts[i].gen.lockmode));
j++;
}
+ for (i = 0; ternaryRelOpts[i].gen.name; i++)
+ {
+ Assert(DoLockModesConflict(ternaryRelOpts[i].gen.lockmode,
+ ternaryRelOpts[i].gen.lockmode));
+ j++;
+ }
+
for (i = 0; intRelOpts[i].gen.name; i++)
{
Assert(DoLockModesConflict(intRelOpts[i].gen.lockmode,
@@ -649,6 +666,14 @@ initialize_reloptions(void)
j++;
}
+ for (i = 0; ternaryRelOpts[i].gen.name; i++)
+ {
+ relOpts[j] = &ternaryRelOpts[i].gen;
+ relOpts[j]->type = RELOPT_TYPE_TERNARY;
+ relOpts[j]->namelen = strlen(relOpts[j]->name);
+ j++;
+ }
+
for (i = 0; intRelOpts[i].gen.name; i++)
{
relOpts[j] = &intRelOpts[i].gen;
@@ -809,6 +834,9 @@ allocate_reloption(bits32 kinds, int type, const char *name, const char *desc,
case RELOPT_TYPE_BOOL:
size = sizeof(relopt_bool);
break;
+ case RELOPT_TYPE_TERNARY:
+ size = sizeof(relopt_ternary);
+ break;
case RELOPT_TYPE_INT:
size = sizeof(relopt_int);
break;
@@ -892,6 +920,57 @@ add_local_bool_reloption(local_relopts *relopts, const char *name,
add_local_reloption(relopts, (relopt_gen *) newoption, offset);
}
+/*
+ * init_ternary_reloption
+ * Allocate and initialize a new ternary reloption
+ */
+static relopt_ternary *
+init_ternary_reloption(bits32 kinds, const char *name, const char *desc,
+ pg_ternary default_val, LOCKMODE lockmode)
+{
+ relopt_ternary *newoption;
+
+ newoption = (relopt_ternary *)
+ allocate_reloption(kinds, RELOPT_TYPE_TERNARY, name, desc, lockmode);
+ newoption->default_val = default_val;
+
+ return newoption;
+}
+
+/*
+ * add_ternary_reloption
+ * Add a new ternary reloption
+ */
+void
+add_ternary_reloption(bits32 kinds, const char *name, const char *desc,
+ pg_ternary default_val, LOCKMODE lockmode)
+{
+ relopt_ternary *newoption;
+
+ newoption =
+ init_ternary_reloption(kinds, name, desc, default_val, lockmode);
+
+ add_reloption((relopt_gen *) newoption);
+}
+
+/*
+ * add_local_ternary_reloption
+ * Add a new ternary local reloption
+ *
+ * 'offset' is offset of ternary-typed field.
+ */
+void
+add_local_ternary_reloption(local_relopts *relopts, const char *name,
+ const char *desc, pg_ternary default_val,
+ int offset)
+{
+ relopt_ternary *newoption;
+
+ newoption =
+ init_ternary_reloption(RELOPT_KIND_LOCAL, name, desc, default_val, 0);
+
+ add_local_reloption(relopts, (relopt_gen *) newoption, offset);
+}
/*
* init_real_reloption
@@ -1626,6 +1705,19 @@ parse_one_reloption(relopt_value *option, char *text_str, int text_len,
option->gen->name, value)));
}
break;
+ case RELOPT_TYPE_TERNARY:
+ {
+ bool b;
+
+ parsed = parse_bool(value, &b);
+ option->values.ternary_val = b ? TERNARY_TRUE : TERNARY_FALSE;
+ if (validate && !parsed)
+ ereport(ERROR,
+ (errcode(ERRCODE_INVALID_PARAMETER_VALUE),
+ errmsg("invalid value for ternary option \"%s\": %s",
+ option->gen->name, value)));
+ }
+ break;
case RELOPT_TYPE_INT:
{
relopt_int *optint = (relopt_int *) option->gen;
@@ -1789,17 +1881,6 @@ fillRelOptions(void *rdopts, Size basesize,
char *itempos = ((char *) rdopts) + elems[j].offset;
char *string_val;
- /*
- * If isset_offset is provided, store whether the reloption is
- * set there.
- */
- if (elems[j].isset_offset > 0)
- {
- char *setpos = ((char *) rdopts) + elems[j].isset_offset;
-
- *(bool *) setpos = options[i].isset;
- }
-
switch (options[i].gen->type)
{
case RELOPT_TYPE_BOOL:
@@ -1807,6 +1888,11 @@ fillRelOptions(void *rdopts, Size basesize,
options[i].values.bool_val :
((relopt_bool *) options[i].gen)->default_val;
break;
+ case RELOPT_TYPE_TERNARY:
+ *(pg_ternary *) itempos = options[i].isset ?
+ options[i].values.ternary_val :
+ ((relopt_ternary *) options[i].gen)->default_val;
+ break;
case RELOPT_TYPE_INT:
*(int *) itempos = options[i].isset ?
options[i].values.int_val :
@@ -1923,8 +2009,8 @@ default_reloptions(Datum reloptions, bool validate, relopt_kind kind)
offsetof(StdRdOptions, parallel_workers)},
{"vacuum_index_cleanup", RELOPT_TYPE_ENUM,
offsetof(StdRdOptions, vacuum_index_cleanup)},
- {"vacuum_truncate", RELOPT_TYPE_BOOL,
- offsetof(StdRdOptions, vacuum_truncate), offsetof(StdRdOptions, vacuum_truncate_set)},
+ {"vacuum_truncate", RELOPT_TYPE_TERNARY,
+ offsetof(StdRdOptions, vacuum_truncate)},
{"vacuum_max_eager_freeze_failure_rate", RELOPT_TYPE_REAL,
offsetof(StdRdOptions, vacuum_max_eager_freeze_failure_rate)}
};
@@ -2004,7 +2090,6 @@ build_local_reloptions(local_relopts *relopts, Datum options, bool validate)
elems[i].optname = opt->option->name;
elems[i].opttype = opt->option->type;
elems[i].offset = opt->offset;
- elems[i].isset_offset = 0; /* not supported for local relopts yet */
i++;
}
diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c
index aa4fbec143f..696eab9bd97 100644
--- a/src/backend/commands/vacuum.c
+++ b/src/backend/commands/vacuum.c
@@ -2224,9 +2224,9 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams params,
{
StdRdOptions *opts = (StdRdOptions *) rel->rd_options;
- if (opts && opts->vacuum_truncate_set)
+ if (opts && opts->vacuum_truncate != TERNARY_UNSET)
{
- if (opts->vacuum_truncate)
+ if (opts->vacuum_truncate == TERNARY_TRUE)
params.truncate = VACOPTVALUE_ENABLED;
else
params.truncate = VACOPTVALUE_DISABLED;
diff --git a/src/include/access/reloptions.h b/src/include/access/reloptions.h
index 2f08e1b0cf0..dfbef2babf2 100644
--- a/src/include/access/reloptions.h
+++ b/src/include/access/reloptions.h
@@ -29,6 +29,7 @@
typedef enum relopt_type
{
RELOPT_TYPE_BOOL,
+ RELOPT_TYPE_TERNARY, /* on, off, unset */
RELOPT_TYPE_INT,
RELOPT_TYPE_REAL,
RELOPT_TYPE_ENUM,
@@ -80,6 +81,7 @@ typedef struct relopt_value
union
{
bool bool_val;
+ pg_ternary ternary_val;
int int_val;
double real_val;
int enum_val;
@@ -94,6 +96,12 @@ typedef struct relopt_bool
bool default_val;
} relopt_bool;
+typedef struct relopt_ternary
+{
+ relopt_gen gen;
+ int default_val;
+} relopt_ternary;
+
typedef struct relopt_int
{
relopt_gen gen;
@@ -152,19 +160,6 @@ typedef struct
const char *optname; /* option's name */
relopt_type opttype; /* option's datatype */
int offset; /* offset of field in result struct */
-
- /*
- * isset_offset is an optional offset of a field in the result struct that
- * stores whether the option is explicitly set for the relation or if it
- * just picked up the default value. In most cases, this can be
- * accomplished by giving the reloption a special out-of-range default
- * value (e.g., some integer reloptions use -2), but this isn't always
- * possible. For example, a Boolean reloption cannot be given an
- * out-of-range default, so we need another way to discover the source of
- * its value. This offset is only used if given a value greater than
- * zero.
- */
- int isset_offset;
} relopt_parse_elt;
/* Local reloption definition */
@@ -195,6 +190,9 @@ typedef struct local_relopts
extern relopt_kind add_reloption_kind(void);
extern void add_bool_reloption(bits32 kinds, const char *name, const char *desc,
bool default_val, LOCKMODE lockmode);
+extern void add_ternary_reloption(bits32 kinds, const char *name,
+ const char *desc, pg_ternary default_val,
+ LOCKMODE lockmode);
extern void add_int_reloption(bits32 kinds, const char *name, const char *desc,
int default_val, int min_val, int max_val,
LOCKMODE lockmode);
@@ -214,6 +212,9 @@ extern void register_reloptions_validator(local_relopts *relopts,
extern void add_local_bool_reloption(local_relopts *relopts, const char *name,
const char *desc, bool default_val,
int offset);
+extern void add_local_ternary_reloption(local_relopts *relopts,
+ const char *name, const char *desc,
+ pg_ternary default_val, int offset);
extern void add_local_int_reloption(local_relopts *relopts, const char *name,
const char *desc, int default_val,
int min_val, int max_val, int offset);
diff --git a/src/include/postgres.h b/src/include/postgres.h
index 7d93fbce709..47c88780776 100644
--- a/src/include/postgres.h
+++ b/src/include/postgres.h
@@ -543,6 +543,21 @@ Float8GetDatum(float8 X)
* ----------------------------------------------------------------
*/
+/*
+ * pg_ternary
+ * Boolean value with an extra "unset" value
+ *
+ * This enum can be used for values that want to distinguish between true,
+ * false, and unset.
+*/
+
+typedef enum pg_ternary
+{
+ TERNARY_FALSE = 0,
+ TERNARY_TRUE = 1,
+ TERNARY_UNSET = -1
+} pg_ternary;
+
/*
* NON_EXEC_STATIC: It's sometimes useful to define a variable or function
* that is normally static but extern when using EXEC_BACKEND (see
diff --git a/src/include/utils/rel.h b/src/include/utils/rel.h
index d03ab247788..5feb18a5373 100644
--- a/src/include/utils/rel.h
+++ b/src/include/utils/rel.h
@@ -347,8 +347,7 @@ typedef struct StdRdOptions
bool user_catalog_table; /* use as an additional catalog relation */
int parallel_workers; /* max number of parallel workers */
StdRdOptIndexCleanup vacuum_index_cleanup; /* controls index vacuuming */
- bool vacuum_truncate; /* enables vacuum to truncate a relation */
- bool vacuum_truncate_set; /* whether vacuum_truncate is set */
+ pg_ternary vacuum_truncate; /* enables vacuum to truncate a relation */
/*
* Fraction of pages in a relation that vacuum can eagerly scan and fail
diff --git a/src/test/regress/expected/reloptions.out b/src/test/regress/expected/reloptions.out
index 9de19b4e3f1..1c99f79ab01 100644
--- a/src/test/regress/expected/reloptions.out
+++ b/src/test/regress/expected/reloptions.out
@@ -98,6 +98,42 @@ SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass;
{fillfactor=13,autovacuum_enabled=false}
(1 row)
+-- Tests for future (FIXME) ternary options
+-- behave as boolean option: accept unassigned name and truncated value
+DROP TABLE reloptions_test;
+CREATE TABLE reloptions_test(i INT) WITH (vacuum_truncate);
+SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass;
+ reloptions
+------------------------
+ {vacuum_truncate=true}
+(1 row)
+
+DROP TABLE reloptions_test;
+CREATE TABLE reloptions_test(i INT) WITH (vacuum_truncate=FaLS);
+SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass;
+ reloptions
+------------------------
+ {vacuum_truncate=fals}
+(1 row)
+
+-- preferred "true" alias is used when storing
+DROP TABLE reloptions_test;
+CREATE TABLE reloptions_test(i INT) WITH (vacuum_index_cleanup=on);
+SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass;
+ reloptions
+---------------------------
+ {vacuum_index_cleanup=on}
+(1 row)
+
+-- custom "third" value is available
+DROP TABLE reloptions_test;
+CREATE TABLE reloptions_test(i INT) WITH (vacuum_index_cleanup=auto);
+SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass;
+ reloptions
+-----------------------------
+ {vacuum_index_cleanup=auto}
+(1 row)
+
-- Test vacuum_truncate option
DROP TABLE reloptions_test;
CREATE TEMP TABLE reloptions_test(i INT NOT NULL, j text)
diff --git a/src/test/regress/sql/reloptions.sql b/src/test/regress/sql/reloptions.sql
index 24fbe0b478d..f5980dafcbc 100644
--- a/src/test/regress/sql/reloptions.sql
+++ b/src/test/regress/sql/reloptions.sql
@@ -59,6 +59,27 @@ UPDATE pg_class
ALTER TABLE reloptions_test RESET (illegal_option);
SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass;
+-- Tests for future (FIXME) ternary options
+
+-- behave as boolean option: accept unassigned name and truncated value
+DROP TABLE reloptions_test;
+CREATE TABLE reloptions_test(i INT) WITH (vacuum_truncate);
+SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass;
+
+DROP TABLE reloptions_test;
+CREATE TABLE reloptions_test(i INT) WITH (vacuum_truncate=FaLS);
+SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass;
+
+-- preferred "true" alias is used when storing
+DROP TABLE reloptions_test;
+CREATE TABLE reloptions_test(i INT) WITH (vacuum_index_cleanup=on);
+SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass;
+
+-- custom "third" value is available
+DROP TABLE reloptions_test;
+CREATE TABLE reloptions_test(i INT) WITH (vacuum_index_cleanup=auto);
+SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass;
+
-- Test vacuum_truncate option
DROP TABLE reloptions_test;
diff --git a/src/tools/pgindent/typedefs.list b/src/tools/pgindent/typedefs.list
index 3f3a888fd0e..1c8610fd46c 100644
--- a/src/tools/pgindent/typedefs.list
+++ b/src/tools/pgindent/typedefs.list
@@ -3952,6 +3952,7 @@ pg_sha512_ctx
pg_snapshot
pg_special_case
pg_stack_base_t
+pg_ternary
pg_time_t
pg_time_usec_t
pg_tz
@@ -4079,6 +4080,7 @@ relopt_kind
relopt_parse_elt
relopt_real
relopt_string
+relopt_ternary
relopt_type
relopt_value
relopts_validator
--
2.47.3
--c36t3fndxhkzbl5z--
^ permalink raw reply [nested|flat] 868+ messages in thread
* [PATCH v3] Introduce ternary reloptions
@ 2026-01-16 15:04 Álvaro Herrera <[email protected]>
0 siblings, 0 replies; 868+ messages in thread
From: Álvaro Herrera @ 2026-01-16 15:04 UTC (permalink / raw)
Introduce ternary reloption as a replacement for current
`vacuum_truncate` implementation. Remove the `vacuum_truncate_set`
separate flag and use TERNARY_UNSET instead.
This could also be used for other options such as `vacuum_index_cleanup`
and `buffering`, but lets get the scaffolding in first.
Discussion: https://postgr.es/m/3474141.usfYGdeWWP@thinkpad-pgpro
---
src/backend/access/common/reloptions.c | 137 ++++++++++++++++++-----
src/backend/commands/vacuum.c | 4 +-
src/include/access/reloptions.h | 27 ++---
src/include/postgres.h | 15 +++
src/include/utils/rel.h | 3 +-
src/test/regress/expected/reloptions.out | 36 ++++++
src/test/regress/sql/reloptions.sql | 21 ++++
src/tools/pgindent/typedefs.list | 2 +
8 files changed, 202 insertions(+), 43 deletions(-)
diff --git a/src/backend/access/common/reloptions.c b/src/backend/access/common/reloptions.c
index 0b83f98ed5f..6f1f577581a 100644
--- a/src/backend/access/common/reloptions.c
+++ b/src/backend/access/common/reloptions.c
@@ -40,9 +40,9 @@
*
* To add an option:
*
- * (i) decide on a type (bool, integer, real, enum, string), name, default
- * value, upper and lower bounds (if applicable); for strings, consider a
- * validation routine.
+ * (i) decide on a type (bool, ternary, integer, real, enum, string), name,
+ * default value, upper and lower bounds (if applicable); for strings,
+ * consider a validation routine.
* (ii) add a record below (or use add_<type>_reloption).
* (iii) add it to the appropriate options struct (perhaps StdRdOptions)
* (iv) add it to the appropriate handling routine (perhaps
@@ -147,15 +147,6 @@ static relopt_bool boolRelOpts[] =
},
false
},
- {
- {
- "vacuum_truncate",
- "Enables vacuum to truncate empty pages at the end of this table",
- RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
- ShareUpdateExclusiveLock
- },
- true
- },
{
{
"deduplicate_items",
@@ -170,6 +161,25 @@ static relopt_bool boolRelOpts[] =
{{NULL}}
};
+static relopt_ternary ternaryRelOpts[] =
+{
+ {
+ {
+ "vacuum_truncate",
+ "Enables vacuum to truncate empty pages at the end of this table",
+ RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
+ ShareUpdateExclusiveLock
+ },
+ TERNARY_UNSET
+ },
+ /* list terminator */
+ {
+ {
+ NULL
+ }
+ }
+};
+
static relopt_int intRelOpts[] =
{
{
@@ -609,6 +619,13 @@ initialize_reloptions(void)
boolRelOpts[i].gen.lockmode));
j++;
}
+ for (i = 0; ternaryRelOpts[i].gen.name; i++)
+ {
+ Assert(DoLockModesConflict(ternaryRelOpts[i].gen.lockmode,
+ ternaryRelOpts[i].gen.lockmode));
+ j++;
+ }
+
for (i = 0; intRelOpts[i].gen.name; i++)
{
Assert(DoLockModesConflict(intRelOpts[i].gen.lockmode,
@@ -649,6 +666,14 @@ initialize_reloptions(void)
j++;
}
+ for (i = 0; ternaryRelOpts[i].gen.name; i++)
+ {
+ relOpts[j] = &ternaryRelOpts[i].gen;
+ relOpts[j]->type = RELOPT_TYPE_TERNARY;
+ relOpts[j]->namelen = strlen(relOpts[j]->name);
+ j++;
+ }
+
for (i = 0; intRelOpts[i].gen.name; i++)
{
relOpts[j] = &intRelOpts[i].gen;
@@ -809,6 +834,9 @@ allocate_reloption(bits32 kinds, int type, const char *name, const char *desc,
case RELOPT_TYPE_BOOL:
size = sizeof(relopt_bool);
break;
+ case RELOPT_TYPE_TERNARY:
+ size = sizeof(relopt_ternary);
+ break;
case RELOPT_TYPE_INT:
size = sizeof(relopt_int);
break;
@@ -892,6 +920,57 @@ add_local_bool_reloption(local_relopts *relopts, const char *name,
add_local_reloption(relopts, (relopt_gen *) newoption, offset);
}
+/*
+ * init_ternary_reloption
+ * Allocate and initialize a new ternary reloption
+ */
+static relopt_ternary *
+init_ternary_reloption(bits32 kinds, const char *name, const char *desc,
+ pg_ternary default_val, LOCKMODE lockmode)
+{
+ relopt_ternary *newoption;
+
+ newoption = (relopt_ternary *)
+ allocate_reloption(kinds, RELOPT_TYPE_TERNARY, name, desc, lockmode);
+ newoption->default_val = default_val;
+
+ return newoption;
+}
+
+/*
+ * add_ternary_reloption
+ * Add a new ternary reloption
+ */
+void
+add_ternary_reloption(bits32 kinds, const char *name, const char *desc,
+ pg_ternary default_val, LOCKMODE lockmode)
+{
+ relopt_ternary *newoption;
+
+ newoption =
+ init_ternary_reloption(kinds, name, desc, default_val, lockmode);
+
+ add_reloption((relopt_gen *) newoption);
+}
+
+/*
+ * add_local_ternary_reloption
+ * Add a new ternary local reloption
+ *
+ * 'offset' is offset of ternary-typed field.
+ */
+void
+add_local_ternary_reloption(local_relopts *relopts, const char *name,
+ const char *desc, pg_ternary default_val,
+ int offset)
+{
+ relopt_ternary *newoption;
+
+ newoption =
+ init_ternary_reloption(RELOPT_KIND_LOCAL, name, desc, default_val, 0);
+
+ add_local_reloption(relopts, (relopt_gen *) newoption, offset);
+}
/*
* init_real_reloption
@@ -1626,6 +1705,19 @@ parse_one_reloption(relopt_value *option, char *text_str, int text_len,
option->gen->name, value)));
}
break;
+ case RELOPT_TYPE_TERNARY:
+ {
+ bool b;
+
+ parsed = parse_bool(value, &b);
+ option->values.ternary_val = b ? TERNARY_TRUE : TERNARY_FALSE;
+ if (validate && !parsed)
+ ereport(ERROR,
+ (errcode(ERRCODE_INVALID_PARAMETER_VALUE),
+ errmsg("invalid value for ternary option \"%s\": %s",
+ option->gen->name, value)));
+ }
+ break;
case RELOPT_TYPE_INT:
{
relopt_int *optint = (relopt_int *) option->gen;
@@ -1789,17 +1881,6 @@ fillRelOptions(void *rdopts, Size basesize,
char *itempos = ((char *) rdopts) + elems[j].offset;
char *string_val;
- /*
- * If isset_offset is provided, store whether the reloption is
- * set there.
- */
- if (elems[j].isset_offset > 0)
- {
- char *setpos = ((char *) rdopts) + elems[j].isset_offset;
-
- *(bool *) setpos = options[i].isset;
- }
-
switch (options[i].gen->type)
{
case RELOPT_TYPE_BOOL:
@@ -1807,6 +1888,11 @@ fillRelOptions(void *rdopts, Size basesize,
options[i].values.bool_val :
((relopt_bool *) options[i].gen)->default_val;
break;
+ case RELOPT_TYPE_TERNARY:
+ *(pg_ternary *) itempos = options[i].isset ?
+ options[i].values.ternary_val :
+ ((relopt_ternary *) options[i].gen)->default_val;
+ break;
case RELOPT_TYPE_INT:
*(int *) itempos = options[i].isset ?
options[i].values.int_val :
@@ -1923,8 +2009,8 @@ default_reloptions(Datum reloptions, bool validate, relopt_kind kind)
offsetof(StdRdOptions, parallel_workers)},
{"vacuum_index_cleanup", RELOPT_TYPE_ENUM,
offsetof(StdRdOptions, vacuum_index_cleanup)},
- {"vacuum_truncate", RELOPT_TYPE_BOOL,
- offsetof(StdRdOptions, vacuum_truncate), offsetof(StdRdOptions, vacuum_truncate_set)},
+ {"vacuum_truncate", RELOPT_TYPE_TERNARY,
+ offsetof(StdRdOptions, vacuum_truncate)},
{"vacuum_max_eager_freeze_failure_rate", RELOPT_TYPE_REAL,
offsetof(StdRdOptions, vacuum_max_eager_freeze_failure_rate)}
};
@@ -2004,7 +2090,6 @@ build_local_reloptions(local_relopts *relopts, Datum options, bool validate)
elems[i].optname = opt->option->name;
elems[i].opttype = opt->option->type;
elems[i].offset = opt->offset;
- elems[i].isset_offset = 0; /* not supported for local relopts yet */
i++;
}
diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c
index aa4fbec143f..696eab9bd97 100644
--- a/src/backend/commands/vacuum.c
+++ b/src/backend/commands/vacuum.c
@@ -2224,9 +2224,9 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams params,
{
StdRdOptions *opts = (StdRdOptions *) rel->rd_options;
- if (opts && opts->vacuum_truncate_set)
+ if (opts && opts->vacuum_truncate != TERNARY_UNSET)
{
- if (opts->vacuum_truncate)
+ if (opts->vacuum_truncate == TERNARY_TRUE)
params.truncate = VACOPTVALUE_ENABLED;
else
params.truncate = VACOPTVALUE_DISABLED;
diff --git a/src/include/access/reloptions.h b/src/include/access/reloptions.h
index 2f08e1b0cf0..dfbef2babf2 100644
--- a/src/include/access/reloptions.h
+++ b/src/include/access/reloptions.h
@@ -29,6 +29,7 @@
typedef enum relopt_type
{
RELOPT_TYPE_BOOL,
+ RELOPT_TYPE_TERNARY, /* on, off, unset */
RELOPT_TYPE_INT,
RELOPT_TYPE_REAL,
RELOPT_TYPE_ENUM,
@@ -80,6 +81,7 @@ typedef struct relopt_value
union
{
bool bool_val;
+ pg_ternary ternary_val;
int int_val;
double real_val;
int enum_val;
@@ -94,6 +96,12 @@ typedef struct relopt_bool
bool default_val;
} relopt_bool;
+typedef struct relopt_ternary
+{
+ relopt_gen gen;
+ int default_val;
+} relopt_ternary;
+
typedef struct relopt_int
{
relopt_gen gen;
@@ -152,19 +160,6 @@ typedef struct
const char *optname; /* option's name */
relopt_type opttype; /* option's datatype */
int offset; /* offset of field in result struct */
-
- /*
- * isset_offset is an optional offset of a field in the result struct that
- * stores whether the option is explicitly set for the relation or if it
- * just picked up the default value. In most cases, this can be
- * accomplished by giving the reloption a special out-of-range default
- * value (e.g., some integer reloptions use -2), but this isn't always
- * possible. For example, a Boolean reloption cannot be given an
- * out-of-range default, so we need another way to discover the source of
- * its value. This offset is only used if given a value greater than
- * zero.
- */
- int isset_offset;
} relopt_parse_elt;
/* Local reloption definition */
@@ -195,6 +190,9 @@ typedef struct local_relopts
extern relopt_kind add_reloption_kind(void);
extern void add_bool_reloption(bits32 kinds, const char *name, const char *desc,
bool default_val, LOCKMODE lockmode);
+extern void add_ternary_reloption(bits32 kinds, const char *name,
+ const char *desc, pg_ternary default_val,
+ LOCKMODE lockmode);
extern void add_int_reloption(bits32 kinds, const char *name, const char *desc,
int default_val, int min_val, int max_val,
LOCKMODE lockmode);
@@ -214,6 +212,9 @@ extern void register_reloptions_validator(local_relopts *relopts,
extern void add_local_bool_reloption(local_relopts *relopts, const char *name,
const char *desc, bool default_val,
int offset);
+extern void add_local_ternary_reloption(local_relopts *relopts,
+ const char *name, const char *desc,
+ pg_ternary default_val, int offset);
extern void add_local_int_reloption(local_relopts *relopts, const char *name,
const char *desc, int default_val,
int min_val, int max_val, int offset);
diff --git a/src/include/postgres.h b/src/include/postgres.h
index 7d93fbce709..47c88780776 100644
--- a/src/include/postgres.h
+++ b/src/include/postgres.h
@@ -543,6 +543,21 @@ Float8GetDatum(float8 X)
* ----------------------------------------------------------------
*/
+/*
+ * pg_ternary
+ * Boolean value with an extra "unset" value
+ *
+ * This enum can be used for values that want to distinguish between true,
+ * false, and unset.
+*/
+
+typedef enum pg_ternary
+{
+ TERNARY_FALSE = 0,
+ TERNARY_TRUE = 1,
+ TERNARY_UNSET = -1
+} pg_ternary;
+
/*
* NON_EXEC_STATIC: It's sometimes useful to define a variable or function
* that is normally static but extern when using EXEC_BACKEND (see
diff --git a/src/include/utils/rel.h b/src/include/utils/rel.h
index d03ab247788..5feb18a5373 100644
--- a/src/include/utils/rel.h
+++ b/src/include/utils/rel.h
@@ -347,8 +347,7 @@ typedef struct StdRdOptions
bool user_catalog_table; /* use as an additional catalog relation */
int parallel_workers; /* max number of parallel workers */
StdRdOptIndexCleanup vacuum_index_cleanup; /* controls index vacuuming */
- bool vacuum_truncate; /* enables vacuum to truncate a relation */
- bool vacuum_truncate_set; /* whether vacuum_truncate is set */
+ pg_ternary vacuum_truncate; /* enables vacuum to truncate a relation */
/*
* Fraction of pages in a relation that vacuum can eagerly scan and fail
diff --git a/src/test/regress/expected/reloptions.out b/src/test/regress/expected/reloptions.out
index 9de19b4e3f1..1c99f79ab01 100644
--- a/src/test/regress/expected/reloptions.out
+++ b/src/test/regress/expected/reloptions.out
@@ -98,6 +98,42 @@ SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass;
{fillfactor=13,autovacuum_enabled=false}
(1 row)
+-- Tests for future (FIXME) ternary options
+-- behave as boolean option: accept unassigned name and truncated value
+DROP TABLE reloptions_test;
+CREATE TABLE reloptions_test(i INT) WITH (vacuum_truncate);
+SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass;
+ reloptions
+------------------------
+ {vacuum_truncate=true}
+(1 row)
+
+DROP TABLE reloptions_test;
+CREATE TABLE reloptions_test(i INT) WITH (vacuum_truncate=FaLS);
+SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass;
+ reloptions
+------------------------
+ {vacuum_truncate=fals}
+(1 row)
+
+-- preferred "true" alias is used when storing
+DROP TABLE reloptions_test;
+CREATE TABLE reloptions_test(i INT) WITH (vacuum_index_cleanup=on);
+SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass;
+ reloptions
+---------------------------
+ {vacuum_index_cleanup=on}
+(1 row)
+
+-- custom "third" value is available
+DROP TABLE reloptions_test;
+CREATE TABLE reloptions_test(i INT) WITH (vacuum_index_cleanup=auto);
+SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass;
+ reloptions
+-----------------------------
+ {vacuum_index_cleanup=auto}
+(1 row)
+
-- Test vacuum_truncate option
DROP TABLE reloptions_test;
CREATE TEMP TABLE reloptions_test(i INT NOT NULL, j text)
diff --git a/src/test/regress/sql/reloptions.sql b/src/test/regress/sql/reloptions.sql
index 24fbe0b478d..f5980dafcbc 100644
--- a/src/test/regress/sql/reloptions.sql
+++ b/src/test/regress/sql/reloptions.sql
@@ -59,6 +59,27 @@ UPDATE pg_class
ALTER TABLE reloptions_test RESET (illegal_option);
SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass;
+-- Tests for future (FIXME) ternary options
+
+-- behave as boolean option: accept unassigned name and truncated value
+DROP TABLE reloptions_test;
+CREATE TABLE reloptions_test(i INT) WITH (vacuum_truncate);
+SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass;
+
+DROP TABLE reloptions_test;
+CREATE TABLE reloptions_test(i INT) WITH (vacuum_truncate=FaLS);
+SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass;
+
+-- preferred "true" alias is used when storing
+DROP TABLE reloptions_test;
+CREATE TABLE reloptions_test(i INT) WITH (vacuum_index_cleanup=on);
+SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass;
+
+-- custom "third" value is available
+DROP TABLE reloptions_test;
+CREATE TABLE reloptions_test(i INT) WITH (vacuum_index_cleanup=auto);
+SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass;
+
-- Test vacuum_truncate option
DROP TABLE reloptions_test;
diff --git a/src/tools/pgindent/typedefs.list b/src/tools/pgindent/typedefs.list
index 3f3a888fd0e..1c8610fd46c 100644
--- a/src/tools/pgindent/typedefs.list
+++ b/src/tools/pgindent/typedefs.list
@@ -3952,6 +3952,7 @@ pg_sha512_ctx
pg_snapshot
pg_special_case
pg_stack_base_t
+pg_ternary
pg_time_t
pg_time_usec_t
pg_tz
@@ -4079,6 +4080,7 @@ relopt_kind
relopt_parse_elt
relopt_real
relopt_string
+relopt_ternary
relopt_type
relopt_value
relopts_validator
--
2.47.3
--c36t3fndxhkzbl5z--
^ permalink raw reply [nested|flat] 868+ messages in thread
* [PATCH v3] Introduce ternary reloptions
@ 2026-01-16 15:04 Álvaro Herrera <[email protected]>
0 siblings, 0 replies; 868+ messages in thread
From: Álvaro Herrera @ 2026-01-16 15:04 UTC (permalink / raw)
Introduce ternary reloption as a replacement for current
`vacuum_truncate` implementation. Remove the `vacuum_truncate_set`
separate flag and use TERNARY_UNSET instead.
This could also be used for other options such as `vacuum_index_cleanup`
and `buffering`, but lets get the scaffolding in first.
Discussion: https://postgr.es/m/3474141.usfYGdeWWP@thinkpad-pgpro
---
src/backend/access/common/reloptions.c | 137 ++++++++++++++++++-----
src/backend/commands/vacuum.c | 4 +-
src/include/access/reloptions.h | 27 ++---
src/include/postgres.h | 15 +++
src/include/utils/rel.h | 3 +-
src/test/regress/expected/reloptions.out | 36 ++++++
src/test/regress/sql/reloptions.sql | 21 ++++
src/tools/pgindent/typedefs.list | 2 +
8 files changed, 202 insertions(+), 43 deletions(-)
diff --git a/src/backend/access/common/reloptions.c b/src/backend/access/common/reloptions.c
index 0b83f98ed5f..6f1f577581a 100644
--- a/src/backend/access/common/reloptions.c
+++ b/src/backend/access/common/reloptions.c
@@ -40,9 +40,9 @@
*
* To add an option:
*
- * (i) decide on a type (bool, integer, real, enum, string), name, default
- * value, upper and lower bounds (if applicable); for strings, consider a
- * validation routine.
+ * (i) decide on a type (bool, ternary, integer, real, enum, string), name,
+ * default value, upper and lower bounds (if applicable); for strings,
+ * consider a validation routine.
* (ii) add a record below (or use add_<type>_reloption).
* (iii) add it to the appropriate options struct (perhaps StdRdOptions)
* (iv) add it to the appropriate handling routine (perhaps
@@ -147,15 +147,6 @@ static relopt_bool boolRelOpts[] =
},
false
},
- {
- {
- "vacuum_truncate",
- "Enables vacuum to truncate empty pages at the end of this table",
- RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
- ShareUpdateExclusiveLock
- },
- true
- },
{
{
"deduplicate_items",
@@ -170,6 +161,25 @@ static relopt_bool boolRelOpts[] =
{{NULL}}
};
+static relopt_ternary ternaryRelOpts[] =
+{
+ {
+ {
+ "vacuum_truncate",
+ "Enables vacuum to truncate empty pages at the end of this table",
+ RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
+ ShareUpdateExclusiveLock
+ },
+ TERNARY_UNSET
+ },
+ /* list terminator */
+ {
+ {
+ NULL
+ }
+ }
+};
+
static relopt_int intRelOpts[] =
{
{
@@ -609,6 +619,13 @@ initialize_reloptions(void)
boolRelOpts[i].gen.lockmode));
j++;
}
+ for (i = 0; ternaryRelOpts[i].gen.name; i++)
+ {
+ Assert(DoLockModesConflict(ternaryRelOpts[i].gen.lockmode,
+ ternaryRelOpts[i].gen.lockmode));
+ j++;
+ }
+
for (i = 0; intRelOpts[i].gen.name; i++)
{
Assert(DoLockModesConflict(intRelOpts[i].gen.lockmode,
@@ -649,6 +666,14 @@ initialize_reloptions(void)
j++;
}
+ for (i = 0; ternaryRelOpts[i].gen.name; i++)
+ {
+ relOpts[j] = &ternaryRelOpts[i].gen;
+ relOpts[j]->type = RELOPT_TYPE_TERNARY;
+ relOpts[j]->namelen = strlen(relOpts[j]->name);
+ j++;
+ }
+
for (i = 0; intRelOpts[i].gen.name; i++)
{
relOpts[j] = &intRelOpts[i].gen;
@@ -809,6 +834,9 @@ allocate_reloption(bits32 kinds, int type, const char *name, const char *desc,
case RELOPT_TYPE_BOOL:
size = sizeof(relopt_bool);
break;
+ case RELOPT_TYPE_TERNARY:
+ size = sizeof(relopt_ternary);
+ break;
case RELOPT_TYPE_INT:
size = sizeof(relopt_int);
break;
@@ -892,6 +920,57 @@ add_local_bool_reloption(local_relopts *relopts, const char *name,
add_local_reloption(relopts, (relopt_gen *) newoption, offset);
}
+/*
+ * init_ternary_reloption
+ * Allocate and initialize a new ternary reloption
+ */
+static relopt_ternary *
+init_ternary_reloption(bits32 kinds, const char *name, const char *desc,
+ pg_ternary default_val, LOCKMODE lockmode)
+{
+ relopt_ternary *newoption;
+
+ newoption = (relopt_ternary *)
+ allocate_reloption(kinds, RELOPT_TYPE_TERNARY, name, desc, lockmode);
+ newoption->default_val = default_val;
+
+ return newoption;
+}
+
+/*
+ * add_ternary_reloption
+ * Add a new ternary reloption
+ */
+void
+add_ternary_reloption(bits32 kinds, const char *name, const char *desc,
+ pg_ternary default_val, LOCKMODE lockmode)
+{
+ relopt_ternary *newoption;
+
+ newoption =
+ init_ternary_reloption(kinds, name, desc, default_val, lockmode);
+
+ add_reloption((relopt_gen *) newoption);
+}
+
+/*
+ * add_local_ternary_reloption
+ * Add a new ternary local reloption
+ *
+ * 'offset' is offset of ternary-typed field.
+ */
+void
+add_local_ternary_reloption(local_relopts *relopts, const char *name,
+ const char *desc, pg_ternary default_val,
+ int offset)
+{
+ relopt_ternary *newoption;
+
+ newoption =
+ init_ternary_reloption(RELOPT_KIND_LOCAL, name, desc, default_val, 0);
+
+ add_local_reloption(relopts, (relopt_gen *) newoption, offset);
+}
/*
* init_real_reloption
@@ -1626,6 +1705,19 @@ parse_one_reloption(relopt_value *option, char *text_str, int text_len,
option->gen->name, value)));
}
break;
+ case RELOPT_TYPE_TERNARY:
+ {
+ bool b;
+
+ parsed = parse_bool(value, &b);
+ option->values.ternary_val = b ? TERNARY_TRUE : TERNARY_FALSE;
+ if (validate && !parsed)
+ ereport(ERROR,
+ (errcode(ERRCODE_INVALID_PARAMETER_VALUE),
+ errmsg("invalid value for ternary option \"%s\": %s",
+ option->gen->name, value)));
+ }
+ break;
case RELOPT_TYPE_INT:
{
relopt_int *optint = (relopt_int *) option->gen;
@@ -1789,17 +1881,6 @@ fillRelOptions(void *rdopts, Size basesize,
char *itempos = ((char *) rdopts) + elems[j].offset;
char *string_val;
- /*
- * If isset_offset is provided, store whether the reloption is
- * set there.
- */
- if (elems[j].isset_offset > 0)
- {
- char *setpos = ((char *) rdopts) + elems[j].isset_offset;
-
- *(bool *) setpos = options[i].isset;
- }
-
switch (options[i].gen->type)
{
case RELOPT_TYPE_BOOL:
@@ -1807,6 +1888,11 @@ fillRelOptions(void *rdopts, Size basesize,
options[i].values.bool_val :
((relopt_bool *) options[i].gen)->default_val;
break;
+ case RELOPT_TYPE_TERNARY:
+ *(pg_ternary *) itempos = options[i].isset ?
+ options[i].values.ternary_val :
+ ((relopt_ternary *) options[i].gen)->default_val;
+ break;
case RELOPT_TYPE_INT:
*(int *) itempos = options[i].isset ?
options[i].values.int_val :
@@ -1923,8 +2009,8 @@ default_reloptions(Datum reloptions, bool validate, relopt_kind kind)
offsetof(StdRdOptions, parallel_workers)},
{"vacuum_index_cleanup", RELOPT_TYPE_ENUM,
offsetof(StdRdOptions, vacuum_index_cleanup)},
- {"vacuum_truncate", RELOPT_TYPE_BOOL,
- offsetof(StdRdOptions, vacuum_truncate), offsetof(StdRdOptions, vacuum_truncate_set)},
+ {"vacuum_truncate", RELOPT_TYPE_TERNARY,
+ offsetof(StdRdOptions, vacuum_truncate)},
{"vacuum_max_eager_freeze_failure_rate", RELOPT_TYPE_REAL,
offsetof(StdRdOptions, vacuum_max_eager_freeze_failure_rate)}
};
@@ -2004,7 +2090,6 @@ build_local_reloptions(local_relopts *relopts, Datum options, bool validate)
elems[i].optname = opt->option->name;
elems[i].opttype = opt->option->type;
elems[i].offset = opt->offset;
- elems[i].isset_offset = 0; /* not supported for local relopts yet */
i++;
}
diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c
index aa4fbec143f..696eab9bd97 100644
--- a/src/backend/commands/vacuum.c
+++ b/src/backend/commands/vacuum.c
@@ -2224,9 +2224,9 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams params,
{
StdRdOptions *opts = (StdRdOptions *) rel->rd_options;
- if (opts && opts->vacuum_truncate_set)
+ if (opts && opts->vacuum_truncate != TERNARY_UNSET)
{
- if (opts->vacuum_truncate)
+ if (opts->vacuum_truncate == TERNARY_TRUE)
params.truncate = VACOPTVALUE_ENABLED;
else
params.truncate = VACOPTVALUE_DISABLED;
diff --git a/src/include/access/reloptions.h b/src/include/access/reloptions.h
index 2f08e1b0cf0..dfbef2babf2 100644
--- a/src/include/access/reloptions.h
+++ b/src/include/access/reloptions.h
@@ -29,6 +29,7 @@
typedef enum relopt_type
{
RELOPT_TYPE_BOOL,
+ RELOPT_TYPE_TERNARY, /* on, off, unset */
RELOPT_TYPE_INT,
RELOPT_TYPE_REAL,
RELOPT_TYPE_ENUM,
@@ -80,6 +81,7 @@ typedef struct relopt_value
union
{
bool bool_val;
+ pg_ternary ternary_val;
int int_val;
double real_val;
int enum_val;
@@ -94,6 +96,12 @@ typedef struct relopt_bool
bool default_val;
} relopt_bool;
+typedef struct relopt_ternary
+{
+ relopt_gen gen;
+ int default_val;
+} relopt_ternary;
+
typedef struct relopt_int
{
relopt_gen gen;
@@ -152,19 +160,6 @@ typedef struct
const char *optname; /* option's name */
relopt_type opttype; /* option's datatype */
int offset; /* offset of field in result struct */
-
- /*
- * isset_offset is an optional offset of a field in the result struct that
- * stores whether the option is explicitly set for the relation or if it
- * just picked up the default value. In most cases, this can be
- * accomplished by giving the reloption a special out-of-range default
- * value (e.g., some integer reloptions use -2), but this isn't always
- * possible. For example, a Boolean reloption cannot be given an
- * out-of-range default, so we need another way to discover the source of
- * its value. This offset is only used if given a value greater than
- * zero.
- */
- int isset_offset;
} relopt_parse_elt;
/* Local reloption definition */
@@ -195,6 +190,9 @@ typedef struct local_relopts
extern relopt_kind add_reloption_kind(void);
extern void add_bool_reloption(bits32 kinds, const char *name, const char *desc,
bool default_val, LOCKMODE lockmode);
+extern void add_ternary_reloption(bits32 kinds, const char *name,
+ const char *desc, pg_ternary default_val,
+ LOCKMODE lockmode);
extern void add_int_reloption(bits32 kinds, const char *name, const char *desc,
int default_val, int min_val, int max_val,
LOCKMODE lockmode);
@@ -214,6 +212,9 @@ extern void register_reloptions_validator(local_relopts *relopts,
extern void add_local_bool_reloption(local_relopts *relopts, const char *name,
const char *desc, bool default_val,
int offset);
+extern void add_local_ternary_reloption(local_relopts *relopts,
+ const char *name, const char *desc,
+ pg_ternary default_val, int offset);
extern void add_local_int_reloption(local_relopts *relopts, const char *name,
const char *desc, int default_val,
int min_val, int max_val, int offset);
diff --git a/src/include/postgres.h b/src/include/postgres.h
index 7d93fbce709..47c88780776 100644
--- a/src/include/postgres.h
+++ b/src/include/postgres.h
@@ -543,6 +543,21 @@ Float8GetDatum(float8 X)
* ----------------------------------------------------------------
*/
+/*
+ * pg_ternary
+ * Boolean value with an extra "unset" value
+ *
+ * This enum can be used for values that want to distinguish between true,
+ * false, and unset.
+*/
+
+typedef enum pg_ternary
+{
+ TERNARY_FALSE = 0,
+ TERNARY_TRUE = 1,
+ TERNARY_UNSET = -1
+} pg_ternary;
+
/*
* NON_EXEC_STATIC: It's sometimes useful to define a variable or function
* that is normally static but extern when using EXEC_BACKEND (see
diff --git a/src/include/utils/rel.h b/src/include/utils/rel.h
index d03ab247788..5feb18a5373 100644
--- a/src/include/utils/rel.h
+++ b/src/include/utils/rel.h
@@ -347,8 +347,7 @@ typedef struct StdRdOptions
bool user_catalog_table; /* use as an additional catalog relation */
int parallel_workers; /* max number of parallel workers */
StdRdOptIndexCleanup vacuum_index_cleanup; /* controls index vacuuming */
- bool vacuum_truncate; /* enables vacuum to truncate a relation */
- bool vacuum_truncate_set; /* whether vacuum_truncate is set */
+ pg_ternary vacuum_truncate; /* enables vacuum to truncate a relation */
/*
* Fraction of pages in a relation that vacuum can eagerly scan and fail
diff --git a/src/test/regress/expected/reloptions.out b/src/test/regress/expected/reloptions.out
index 9de19b4e3f1..1c99f79ab01 100644
--- a/src/test/regress/expected/reloptions.out
+++ b/src/test/regress/expected/reloptions.out
@@ -98,6 +98,42 @@ SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass;
{fillfactor=13,autovacuum_enabled=false}
(1 row)
+-- Tests for future (FIXME) ternary options
+-- behave as boolean option: accept unassigned name and truncated value
+DROP TABLE reloptions_test;
+CREATE TABLE reloptions_test(i INT) WITH (vacuum_truncate);
+SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass;
+ reloptions
+------------------------
+ {vacuum_truncate=true}
+(1 row)
+
+DROP TABLE reloptions_test;
+CREATE TABLE reloptions_test(i INT) WITH (vacuum_truncate=FaLS);
+SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass;
+ reloptions
+------------------------
+ {vacuum_truncate=fals}
+(1 row)
+
+-- preferred "true" alias is used when storing
+DROP TABLE reloptions_test;
+CREATE TABLE reloptions_test(i INT) WITH (vacuum_index_cleanup=on);
+SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass;
+ reloptions
+---------------------------
+ {vacuum_index_cleanup=on}
+(1 row)
+
+-- custom "third" value is available
+DROP TABLE reloptions_test;
+CREATE TABLE reloptions_test(i INT) WITH (vacuum_index_cleanup=auto);
+SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass;
+ reloptions
+-----------------------------
+ {vacuum_index_cleanup=auto}
+(1 row)
+
-- Test vacuum_truncate option
DROP TABLE reloptions_test;
CREATE TEMP TABLE reloptions_test(i INT NOT NULL, j text)
diff --git a/src/test/regress/sql/reloptions.sql b/src/test/regress/sql/reloptions.sql
index 24fbe0b478d..f5980dafcbc 100644
--- a/src/test/regress/sql/reloptions.sql
+++ b/src/test/regress/sql/reloptions.sql
@@ -59,6 +59,27 @@ UPDATE pg_class
ALTER TABLE reloptions_test RESET (illegal_option);
SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass;
+-- Tests for future (FIXME) ternary options
+
+-- behave as boolean option: accept unassigned name and truncated value
+DROP TABLE reloptions_test;
+CREATE TABLE reloptions_test(i INT) WITH (vacuum_truncate);
+SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass;
+
+DROP TABLE reloptions_test;
+CREATE TABLE reloptions_test(i INT) WITH (vacuum_truncate=FaLS);
+SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass;
+
+-- preferred "true" alias is used when storing
+DROP TABLE reloptions_test;
+CREATE TABLE reloptions_test(i INT) WITH (vacuum_index_cleanup=on);
+SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass;
+
+-- custom "third" value is available
+DROP TABLE reloptions_test;
+CREATE TABLE reloptions_test(i INT) WITH (vacuum_index_cleanup=auto);
+SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass;
+
-- Test vacuum_truncate option
DROP TABLE reloptions_test;
diff --git a/src/tools/pgindent/typedefs.list b/src/tools/pgindent/typedefs.list
index 3f3a888fd0e..1c8610fd46c 100644
--- a/src/tools/pgindent/typedefs.list
+++ b/src/tools/pgindent/typedefs.list
@@ -3952,6 +3952,7 @@ pg_sha512_ctx
pg_snapshot
pg_special_case
pg_stack_base_t
+pg_ternary
pg_time_t
pg_time_usec_t
pg_tz
@@ -4079,6 +4080,7 @@ relopt_kind
relopt_parse_elt
relopt_real
relopt_string
+relopt_ternary
relopt_type
relopt_value
relopts_validator
--
2.47.3
--c36t3fndxhkzbl5z--
^ permalink raw reply [nested|flat] 868+ messages in thread
* [PATCH v3] Introduce ternary reloptions
@ 2026-01-16 15:04 Álvaro Herrera <[email protected]>
0 siblings, 0 replies; 868+ messages in thread
From: Álvaro Herrera @ 2026-01-16 15:04 UTC (permalink / raw)
Introduce ternary reloption as a replacement for current
`vacuum_truncate` implementation. Remove the `vacuum_truncate_set`
separate flag and use TERNARY_UNSET instead.
This could also be used for other options such as `vacuum_index_cleanup`
and `buffering`, but lets get the scaffolding in first.
Discussion: https://postgr.es/m/3474141.usfYGdeWWP@thinkpad-pgpro
---
src/backend/access/common/reloptions.c | 137 ++++++++++++++++++-----
src/backend/commands/vacuum.c | 4 +-
src/include/access/reloptions.h | 27 ++---
src/include/postgres.h | 15 +++
src/include/utils/rel.h | 3 +-
src/test/regress/expected/reloptions.out | 36 ++++++
src/test/regress/sql/reloptions.sql | 21 ++++
src/tools/pgindent/typedefs.list | 2 +
8 files changed, 202 insertions(+), 43 deletions(-)
diff --git a/src/backend/access/common/reloptions.c b/src/backend/access/common/reloptions.c
index 0b83f98ed5f..6f1f577581a 100644
--- a/src/backend/access/common/reloptions.c
+++ b/src/backend/access/common/reloptions.c
@@ -40,9 +40,9 @@
*
* To add an option:
*
- * (i) decide on a type (bool, integer, real, enum, string), name, default
- * value, upper and lower bounds (if applicable); for strings, consider a
- * validation routine.
+ * (i) decide on a type (bool, ternary, integer, real, enum, string), name,
+ * default value, upper and lower bounds (if applicable); for strings,
+ * consider a validation routine.
* (ii) add a record below (or use add_<type>_reloption).
* (iii) add it to the appropriate options struct (perhaps StdRdOptions)
* (iv) add it to the appropriate handling routine (perhaps
@@ -147,15 +147,6 @@ static relopt_bool boolRelOpts[] =
},
false
},
- {
- {
- "vacuum_truncate",
- "Enables vacuum to truncate empty pages at the end of this table",
- RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
- ShareUpdateExclusiveLock
- },
- true
- },
{
{
"deduplicate_items",
@@ -170,6 +161,25 @@ static relopt_bool boolRelOpts[] =
{{NULL}}
};
+static relopt_ternary ternaryRelOpts[] =
+{
+ {
+ {
+ "vacuum_truncate",
+ "Enables vacuum to truncate empty pages at the end of this table",
+ RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
+ ShareUpdateExclusiveLock
+ },
+ TERNARY_UNSET
+ },
+ /* list terminator */
+ {
+ {
+ NULL
+ }
+ }
+};
+
static relopt_int intRelOpts[] =
{
{
@@ -609,6 +619,13 @@ initialize_reloptions(void)
boolRelOpts[i].gen.lockmode));
j++;
}
+ for (i = 0; ternaryRelOpts[i].gen.name; i++)
+ {
+ Assert(DoLockModesConflict(ternaryRelOpts[i].gen.lockmode,
+ ternaryRelOpts[i].gen.lockmode));
+ j++;
+ }
+
for (i = 0; intRelOpts[i].gen.name; i++)
{
Assert(DoLockModesConflict(intRelOpts[i].gen.lockmode,
@@ -649,6 +666,14 @@ initialize_reloptions(void)
j++;
}
+ for (i = 0; ternaryRelOpts[i].gen.name; i++)
+ {
+ relOpts[j] = &ternaryRelOpts[i].gen;
+ relOpts[j]->type = RELOPT_TYPE_TERNARY;
+ relOpts[j]->namelen = strlen(relOpts[j]->name);
+ j++;
+ }
+
for (i = 0; intRelOpts[i].gen.name; i++)
{
relOpts[j] = &intRelOpts[i].gen;
@@ -809,6 +834,9 @@ allocate_reloption(bits32 kinds, int type, const char *name, const char *desc,
case RELOPT_TYPE_BOOL:
size = sizeof(relopt_bool);
break;
+ case RELOPT_TYPE_TERNARY:
+ size = sizeof(relopt_ternary);
+ break;
case RELOPT_TYPE_INT:
size = sizeof(relopt_int);
break;
@@ -892,6 +920,57 @@ add_local_bool_reloption(local_relopts *relopts, const char *name,
add_local_reloption(relopts, (relopt_gen *) newoption, offset);
}
+/*
+ * init_ternary_reloption
+ * Allocate and initialize a new ternary reloption
+ */
+static relopt_ternary *
+init_ternary_reloption(bits32 kinds, const char *name, const char *desc,
+ pg_ternary default_val, LOCKMODE lockmode)
+{
+ relopt_ternary *newoption;
+
+ newoption = (relopt_ternary *)
+ allocate_reloption(kinds, RELOPT_TYPE_TERNARY, name, desc, lockmode);
+ newoption->default_val = default_val;
+
+ return newoption;
+}
+
+/*
+ * add_ternary_reloption
+ * Add a new ternary reloption
+ */
+void
+add_ternary_reloption(bits32 kinds, const char *name, const char *desc,
+ pg_ternary default_val, LOCKMODE lockmode)
+{
+ relopt_ternary *newoption;
+
+ newoption =
+ init_ternary_reloption(kinds, name, desc, default_val, lockmode);
+
+ add_reloption((relopt_gen *) newoption);
+}
+
+/*
+ * add_local_ternary_reloption
+ * Add a new ternary local reloption
+ *
+ * 'offset' is offset of ternary-typed field.
+ */
+void
+add_local_ternary_reloption(local_relopts *relopts, const char *name,
+ const char *desc, pg_ternary default_val,
+ int offset)
+{
+ relopt_ternary *newoption;
+
+ newoption =
+ init_ternary_reloption(RELOPT_KIND_LOCAL, name, desc, default_val, 0);
+
+ add_local_reloption(relopts, (relopt_gen *) newoption, offset);
+}
/*
* init_real_reloption
@@ -1626,6 +1705,19 @@ parse_one_reloption(relopt_value *option, char *text_str, int text_len,
option->gen->name, value)));
}
break;
+ case RELOPT_TYPE_TERNARY:
+ {
+ bool b;
+
+ parsed = parse_bool(value, &b);
+ option->values.ternary_val = b ? TERNARY_TRUE : TERNARY_FALSE;
+ if (validate && !parsed)
+ ereport(ERROR,
+ (errcode(ERRCODE_INVALID_PARAMETER_VALUE),
+ errmsg("invalid value for ternary option \"%s\": %s",
+ option->gen->name, value)));
+ }
+ break;
case RELOPT_TYPE_INT:
{
relopt_int *optint = (relopt_int *) option->gen;
@@ -1789,17 +1881,6 @@ fillRelOptions(void *rdopts, Size basesize,
char *itempos = ((char *) rdopts) + elems[j].offset;
char *string_val;
- /*
- * If isset_offset is provided, store whether the reloption is
- * set there.
- */
- if (elems[j].isset_offset > 0)
- {
- char *setpos = ((char *) rdopts) + elems[j].isset_offset;
-
- *(bool *) setpos = options[i].isset;
- }
-
switch (options[i].gen->type)
{
case RELOPT_TYPE_BOOL:
@@ -1807,6 +1888,11 @@ fillRelOptions(void *rdopts, Size basesize,
options[i].values.bool_val :
((relopt_bool *) options[i].gen)->default_val;
break;
+ case RELOPT_TYPE_TERNARY:
+ *(pg_ternary *) itempos = options[i].isset ?
+ options[i].values.ternary_val :
+ ((relopt_ternary *) options[i].gen)->default_val;
+ break;
case RELOPT_TYPE_INT:
*(int *) itempos = options[i].isset ?
options[i].values.int_val :
@@ -1923,8 +2009,8 @@ default_reloptions(Datum reloptions, bool validate, relopt_kind kind)
offsetof(StdRdOptions, parallel_workers)},
{"vacuum_index_cleanup", RELOPT_TYPE_ENUM,
offsetof(StdRdOptions, vacuum_index_cleanup)},
- {"vacuum_truncate", RELOPT_TYPE_BOOL,
- offsetof(StdRdOptions, vacuum_truncate), offsetof(StdRdOptions, vacuum_truncate_set)},
+ {"vacuum_truncate", RELOPT_TYPE_TERNARY,
+ offsetof(StdRdOptions, vacuum_truncate)},
{"vacuum_max_eager_freeze_failure_rate", RELOPT_TYPE_REAL,
offsetof(StdRdOptions, vacuum_max_eager_freeze_failure_rate)}
};
@@ -2004,7 +2090,6 @@ build_local_reloptions(local_relopts *relopts, Datum options, bool validate)
elems[i].optname = opt->option->name;
elems[i].opttype = opt->option->type;
elems[i].offset = opt->offset;
- elems[i].isset_offset = 0; /* not supported for local relopts yet */
i++;
}
diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c
index aa4fbec143f..696eab9bd97 100644
--- a/src/backend/commands/vacuum.c
+++ b/src/backend/commands/vacuum.c
@@ -2224,9 +2224,9 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams params,
{
StdRdOptions *opts = (StdRdOptions *) rel->rd_options;
- if (opts && opts->vacuum_truncate_set)
+ if (opts && opts->vacuum_truncate != TERNARY_UNSET)
{
- if (opts->vacuum_truncate)
+ if (opts->vacuum_truncate == TERNARY_TRUE)
params.truncate = VACOPTVALUE_ENABLED;
else
params.truncate = VACOPTVALUE_DISABLED;
diff --git a/src/include/access/reloptions.h b/src/include/access/reloptions.h
index 2f08e1b0cf0..dfbef2babf2 100644
--- a/src/include/access/reloptions.h
+++ b/src/include/access/reloptions.h
@@ -29,6 +29,7 @@
typedef enum relopt_type
{
RELOPT_TYPE_BOOL,
+ RELOPT_TYPE_TERNARY, /* on, off, unset */
RELOPT_TYPE_INT,
RELOPT_TYPE_REAL,
RELOPT_TYPE_ENUM,
@@ -80,6 +81,7 @@ typedef struct relopt_value
union
{
bool bool_val;
+ pg_ternary ternary_val;
int int_val;
double real_val;
int enum_val;
@@ -94,6 +96,12 @@ typedef struct relopt_bool
bool default_val;
} relopt_bool;
+typedef struct relopt_ternary
+{
+ relopt_gen gen;
+ int default_val;
+} relopt_ternary;
+
typedef struct relopt_int
{
relopt_gen gen;
@@ -152,19 +160,6 @@ typedef struct
const char *optname; /* option's name */
relopt_type opttype; /* option's datatype */
int offset; /* offset of field in result struct */
-
- /*
- * isset_offset is an optional offset of a field in the result struct that
- * stores whether the option is explicitly set for the relation or if it
- * just picked up the default value. In most cases, this can be
- * accomplished by giving the reloption a special out-of-range default
- * value (e.g., some integer reloptions use -2), but this isn't always
- * possible. For example, a Boolean reloption cannot be given an
- * out-of-range default, so we need another way to discover the source of
- * its value. This offset is only used if given a value greater than
- * zero.
- */
- int isset_offset;
} relopt_parse_elt;
/* Local reloption definition */
@@ -195,6 +190,9 @@ typedef struct local_relopts
extern relopt_kind add_reloption_kind(void);
extern void add_bool_reloption(bits32 kinds, const char *name, const char *desc,
bool default_val, LOCKMODE lockmode);
+extern void add_ternary_reloption(bits32 kinds, const char *name,
+ const char *desc, pg_ternary default_val,
+ LOCKMODE lockmode);
extern void add_int_reloption(bits32 kinds, const char *name, const char *desc,
int default_val, int min_val, int max_val,
LOCKMODE lockmode);
@@ -214,6 +212,9 @@ extern void register_reloptions_validator(local_relopts *relopts,
extern void add_local_bool_reloption(local_relopts *relopts, const char *name,
const char *desc, bool default_val,
int offset);
+extern void add_local_ternary_reloption(local_relopts *relopts,
+ const char *name, const char *desc,
+ pg_ternary default_val, int offset);
extern void add_local_int_reloption(local_relopts *relopts, const char *name,
const char *desc, int default_val,
int min_val, int max_val, int offset);
diff --git a/src/include/postgres.h b/src/include/postgres.h
index 7d93fbce709..47c88780776 100644
--- a/src/include/postgres.h
+++ b/src/include/postgres.h
@@ -543,6 +543,21 @@ Float8GetDatum(float8 X)
* ----------------------------------------------------------------
*/
+/*
+ * pg_ternary
+ * Boolean value with an extra "unset" value
+ *
+ * This enum can be used for values that want to distinguish between true,
+ * false, and unset.
+*/
+
+typedef enum pg_ternary
+{
+ TERNARY_FALSE = 0,
+ TERNARY_TRUE = 1,
+ TERNARY_UNSET = -1
+} pg_ternary;
+
/*
* NON_EXEC_STATIC: It's sometimes useful to define a variable or function
* that is normally static but extern when using EXEC_BACKEND (see
diff --git a/src/include/utils/rel.h b/src/include/utils/rel.h
index d03ab247788..5feb18a5373 100644
--- a/src/include/utils/rel.h
+++ b/src/include/utils/rel.h
@@ -347,8 +347,7 @@ typedef struct StdRdOptions
bool user_catalog_table; /* use as an additional catalog relation */
int parallel_workers; /* max number of parallel workers */
StdRdOptIndexCleanup vacuum_index_cleanup; /* controls index vacuuming */
- bool vacuum_truncate; /* enables vacuum to truncate a relation */
- bool vacuum_truncate_set; /* whether vacuum_truncate is set */
+ pg_ternary vacuum_truncate; /* enables vacuum to truncate a relation */
/*
* Fraction of pages in a relation that vacuum can eagerly scan and fail
diff --git a/src/test/regress/expected/reloptions.out b/src/test/regress/expected/reloptions.out
index 9de19b4e3f1..1c99f79ab01 100644
--- a/src/test/regress/expected/reloptions.out
+++ b/src/test/regress/expected/reloptions.out
@@ -98,6 +98,42 @@ SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass;
{fillfactor=13,autovacuum_enabled=false}
(1 row)
+-- Tests for future (FIXME) ternary options
+-- behave as boolean option: accept unassigned name and truncated value
+DROP TABLE reloptions_test;
+CREATE TABLE reloptions_test(i INT) WITH (vacuum_truncate);
+SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass;
+ reloptions
+------------------------
+ {vacuum_truncate=true}
+(1 row)
+
+DROP TABLE reloptions_test;
+CREATE TABLE reloptions_test(i INT) WITH (vacuum_truncate=FaLS);
+SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass;
+ reloptions
+------------------------
+ {vacuum_truncate=fals}
+(1 row)
+
+-- preferred "true" alias is used when storing
+DROP TABLE reloptions_test;
+CREATE TABLE reloptions_test(i INT) WITH (vacuum_index_cleanup=on);
+SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass;
+ reloptions
+---------------------------
+ {vacuum_index_cleanup=on}
+(1 row)
+
+-- custom "third" value is available
+DROP TABLE reloptions_test;
+CREATE TABLE reloptions_test(i INT) WITH (vacuum_index_cleanup=auto);
+SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass;
+ reloptions
+-----------------------------
+ {vacuum_index_cleanup=auto}
+(1 row)
+
-- Test vacuum_truncate option
DROP TABLE reloptions_test;
CREATE TEMP TABLE reloptions_test(i INT NOT NULL, j text)
diff --git a/src/test/regress/sql/reloptions.sql b/src/test/regress/sql/reloptions.sql
index 24fbe0b478d..f5980dafcbc 100644
--- a/src/test/regress/sql/reloptions.sql
+++ b/src/test/regress/sql/reloptions.sql
@@ -59,6 +59,27 @@ UPDATE pg_class
ALTER TABLE reloptions_test RESET (illegal_option);
SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass;
+-- Tests for future (FIXME) ternary options
+
+-- behave as boolean option: accept unassigned name and truncated value
+DROP TABLE reloptions_test;
+CREATE TABLE reloptions_test(i INT) WITH (vacuum_truncate);
+SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass;
+
+DROP TABLE reloptions_test;
+CREATE TABLE reloptions_test(i INT) WITH (vacuum_truncate=FaLS);
+SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass;
+
+-- preferred "true" alias is used when storing
+DROP TABLE reloptions_test;
+CREATE TABLE reloptions_test(i INT) WITH (vacuum_index_cleanup=on);
+SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass;
+
+-- custom "third" value is available
+DROP TABLE reloptions_test;
+CREATE TABLE reloptions_test(i INT) WITH (vacuum_index_cleanup=auto);
+SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass;
+
-- Test vacuum_truncate option
DROP TABLE reloptions_test;
diff --git a/src/tools/pgindent/typedefs.list b/src/tools/pgindent/typedefs.list
index 3f3a888fd0e..1c8610fd46c 100644
--- a/src/tools/pgindent/typedefs.list
+++ b/src/tools/pgindent/typedefs.list
@@ -3952,6 +3952,7 @@ pg_sha512_ctx
pg_snapshot
pg_special_case
pg_stack_base_t
+pg_ternary
pg_time_t
pg_time_usec_t
pg_tz
@@ -4079,6 +4080,7 @@ relopt_kind
relopt_parse_elt
relopt_real
relopt_string
+relopt_ternary
relopt_type
relopt_value
relopts_validator
--
2.47.3
--c36t3fndxhkzbl5z--
^ permalink raw reply [nested|flat] 868+ messages in thread
* [PATCH v3] Introduce ternary reloptions
@ 2026-01-16 15:04 Álvaro Herrera <[email protected]>
0 siblings, 0 replies; 868+ messages in thread
From: Álvaro Herrera @ 2026-01-16 15:04 UTC (permalink / raw)
Introduce ternary reloption as a replacement for current
`vacuum_truncate` implementation. Remove the `vacuum_truncate_set`
separate flag and use TERNARY_UNSET instead.
This could also be used for other options such as `vacuum_index_cleanup`
and `buffering`, but lets get the scaffolding in first.
Discussion: https://postgr.es/m/3474141.usfYGdeWWP@thinkpad-pgpro
---
src/backend/access/common/reloptions.c | 137 ++++++++++++++++++-----
src/backend/commands/vacuum.c | 4 +-
src/include/access/reloptions.h | 27 ++---
src/include/postgres.h | 15 +++
src/include/utils/rel.h | 3 +-
src/test/regress/expected/reloptions.out | 36 ++++++
src/test/regress/sql/reloptions.sql | 21 ++++
src/tools/pgindent/typedefs.list | 2 +
8 files changed, 202 insertions(+), 43 deletions(-)
diff --git a/src/backend/access/common/reloptions.c b/src/backend/access/common/reloptions.c
index 0b83f98ed5f..6f1f577581a 100644
--- a/src/backend/access/common/reloptions.c
+++ b/src/backend/access/common/reloptions.c
@@ -40,9 +40,9 @@
*
* To add an option:
*
- * (i) decide on a type (bool, integer, real, enum, string), name, default
- * value, upper and lower bounds (if applicable); for strings, consider a
- * validation routine.
+ * (i) decide on a type (bool, ternary, integer, real, enum, string), name,
+ * default value, upper and lower bounds (if applicable); for strings,
+ * consider a validation routine.
* (ii) add a record below (or use add_<type>_reloption).
* (iii) add it to the appropriate options struct (perhaps StdRdOptions)
* (iv) add it to the appropriate handling routine (perhaps
@@ -147,15 +147,6 @@ static relopt_bool boolRelOpts[] =
},
false
},
- {
- {
- "vacuum_truncate",
- "Enables vacuum to truncate empty pages at the end of this table",
- RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
- ShareUpdateExclusiveLock
- },
- true
- },
{
{
"deduplicate_items",
@@ -170,6 +161,25 @@ static relopt_bool boolRelOpts[] =
{{NULL}}
};
+static relopt_ternary ternaryRelOpts[] =
+{
+ {
+ {
+ "vacuum_truncate",
+ "Enables vacuum to truncate empty pages at the end of this table",
+ RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
+ ShareUpdateExclusiveLock
+ },
+ TERNARY_UNSET
+ },
+ /* list terminator */
+ {
+ {
+ NULL
+ }
+ }
+};
+
static relopt_int intRelOpts[] =
{
{
@@ -609,6 +619,13 @@ initialize_reloptions(void)
boolRelOpts[i].gen.lockmode));
j++;
}
+ for (i = 0; ternaryRelOpts[i].gen.name; i++)
+ {
+ Assert(DoLockModesConflict(ternaryRelOpts[i].gen.lockmode,
+ ternaryRelOpts[i].gen.lockmode));
+ j++;
+ }
+
for (i = 0; intRelOpts[i].gen.name; i++)
{
Assert(DoLockModesConflict(intRelOpts[i].gen.lockmode,
@@ -649,6 +666,14 @@ initialize_reloptions(void)
j++;
}
+ for (i = 0; ternaryRelOpts[i].gen.name; i++)
+ {
+ relOpts[j] = &ternaryRelOpts[i].gen;
+ relOpts[j]->type = RELOPT_TYPE_TERNARY;
+ relOpts[j]->namelen = strlen(relOpts[j]->name);
+ j++;
+ }
+
for (i = 0; intRelOpts[i].gen.name; i++)
{
relOpts[j] = &intRelOpts[i].gen;
@@ -809,6 +834,9 @@ allocate_reloption(bits32 kinds, int type, const char *name, const char *desc,
case RELOPT_TYPE_BOOL:
size = sizeof(relopt_bool);
break;
+ case RELOPT_TYPE_TERNARY:
+ size = sizeof(relopt_ternary);
+ break;
case RELOPT_TYPE_INT:
size = sizeof(relopt_int);
break;
@@ -892,6 +920,57 @@ add_local_bool_reloption(local_relopts *relopts, const char *name,
add_local_reloption(relopts, (relopt_gen *) newoption, offset);
}
+/*
+ * init_ternary_reloption
+ * Allocate and initialize a new ternary reloption
+ */
+static relopt_ternary *
+init_ternary_reloption(bits32 kinds, const char *name, const char *desc,
+ pg_ternary default_val, LOCKMODE lockmode)
+{
+ relopt_ternary *newoption;
+
+ newoption = (relopt_ternary *)
+ allocate_reloption(kinds, RELOPT_TYPE_TERNARY, name, desc, lockmode);
+ newoption->default_val = default_val;
+
+ return newoption;
+}
+
+/*
+ * add_ternary_reloption
+ * Add a new ternary reloption
+ */
+void
+add_ternary_reloption(bits32 kinds, const char *name, const char *desc,
+ pg_ternary default_val, LOCKMODE lockmode)
+{
+ relopt_ternary *newoption;
+
+ newoption =
+ init_ternary_reloption(kinds, name, desc, default_val, lockmode);
+
+ add_reloption((relopt_gen *) newoption);
+}
+
+/*
+ * add_local_ternary_reloption
+ * Add a new ternary local reloption
+ *
+ * 'offset' is offset of ternary-typed field.
+ */
+void
+add_local_ternary_reloption(local_relopts *relopts, const char *name,
+ const char *desc, pg_ternary default_val,
+ int offset)
+{
+ relopt_ternary *newoption;
+
+ newoption =
+ init_ternary_reloption(RELOPT_KIND_LOCAL, name, desc, default_val, 0);
+
+ add_local_reloption(relopts, (relopt_gen *) newoption, offset);
+}
/*
* init_real_reloption
@@ -1626,6 +1705,19 @@ parse_one_reloption(relopt_value *option, char *text_str, int text_len,
option->gen->name, value)));
}
break;
+ case RELOPT_TYPE_TERNARY:
+ {
+ bool b;
+
+ parsed = parse_bool(value, &b);
+ option->values.ternary_val = b ? TERNARY_TRUE : TERNARY_FALSE;
+ if (validate && !parsed)
+ ereport(ERROR,
+ (errcode(ERRCODE_INVALID_PARAMETER_VALUE),
+ errmsg("invalid value for ternary option \"%s\": %s",
+ option->gen->name, value)));
+ }
+ break;
case RELOPT_TYPE_INT:
{
relopt_int *optint = (relopt_int *) option->gen;
@@ -1789,17 +1881,6 @@ fillRelOptions(void *rdopts, Size basesize,
char *itempos = ((char *) rdopts) + elems[j].offset;
char *string_val;
- /*
- * If isset_offset is provided, store whether the reloption is
- * set there.
- */
- if (elems[j].isset_offset > 0)
- {
- char *setpos = ((char *) rdopts) + elems[j].isset_offset;
-
- *(bool *) setpos = options[i].isset;
- }
-
switch (options[i].gen->type)
{
case RELOPT_TYPE_BOOL:
@@ -1807,6 +1888,11 @@ fillRelOptions(void *rdopts, Size basesize,
options[i].values.bool_val :
((relopt_bool *) options[i].gen)->default_val;
break;
+ case RELOPT_TYPE_TERNARY:
+ *(pg_ternary *) itempos = options[i].isset ?
+ options[i].values.ternary_val :
+ ((relopt_ternary *) options[i].gen)->default_val;
+ break;
case RELOPT_TYPE_INT:
*(int *) itempos = options[i].isset ?
options[i].values.int_val :
@@ -1923,8 +2009,8 @@ default_reloptions(Datum reloptions, bool validate, relopt_kind kind)
offsetof(StdRdOptions, parallel_workers)},
{"vacuum_index_cleanup", RELOPT_TYPE_ENUM,
offsetof(StdRdOptions, vacuum_index_cleanup)},
- {"vacuum_truncate", RELOPT_TYPE_BOOL,
- offsetof(StdRdOptions, vacuum_truncate), offsetof(StdRdOptions, vacuum_truncate_set)},
+ {"vacuum_truncate", RELOPT_TYPE_TERNARY,
+ offsetof(StdRdOptions, vacuum_truncate)},
{"vacuum_max_eager_freeze_failure_rate", RELOPT_TYPE_REAL,
offsetof(StdRdOptions, vacuum_max_eager_freeze_failure_rate)}
};
@@ -2004,7 +2090,6 @@ build_local_reloptions(local_relopts *relopts, Datum options, bool validate)
elems[i].optname = opt->option->name;
elems[i].opttype = opt->option->type;
elems[i].offset = opt->offset;
- elems[i].isset_offset = 0; /* not supported for local relopts yet */
i++;
}
diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c
index aa4fbec143f..696eab9bd97 100644
--- a/src/backend/commands/vacuum.c
+++ b/src/backend/commands/vacuum.c
@@ -2224,9 +2224,9 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams params,
{
StdRdOptions *opts = (StdRdOptions *) rel->rd_options;
- if (opts && opts->vacuum_truncate_set)
+ if (opts && opts->vacuum_truncate != TERNARY_UNSET)
{
- if (opts->vacuum_truncate)
+ if (opts->vacuum_truncate == TERNARY_TRUE)
params.truncate = VACOPTVALUE_ENABLED;
else
params.truncate = VACOPTVALUE_DISABLED;
diff --git a/src/include/access/reloptions.h b/src/include/access/reloptions.h
index 2f08e1b0cf0..dfbef2babf2 100644
--- a/src/include/access/reloptions.h
+++ b/src/include/access/reloptions.h
@@ -29,6 +29,7 @@
typedef enum relopt_type
{
RELOPT_TYPE_BOOL,
+ RELOPT_TYPE_TERNARY, /* on, off, unset */
RELOPT_TYPE_INT,
RELOPT_TYPE_REAL,
RELOPT_TYPE_ENUM,
@@ -80,6 +81,7 @@ typedef struct relopt_value
union
{
bool bool_val;
+ pg_ternary ternary_val;
int int_val;
double real_val;
int enum_val;
@@ -94,6 +96,12 @@ typedef struct relopt_bool
bool default_val;
} relopt_bool;
+typedef struct relopt_ternary
+{
+ relopt_gen gen;
+ int default_val;
+} relopt_ternary;
+
typedef struct relopt_int
{
relopt_gen gen;
@@ -152,19 +160,6 @@ typedef struct
const char *optname; /* option's name */
relopt_type opttype; /* option's datatype */
int offset; /* offset of field in result struct */
-
- /*
- * isset_offset is an optional offset of a field in the result struct that
- * stores whether the option is explicitly set for the relation or if it
- * just picked up the default value. In most cases, this can be
- * accomplished by giving the reloption a special out-of-range default
- * value (e.g., some integer reloptions use -2), but this isn't always
- * possible. For example, a Boolean reloption cannot be given an
- * out-of-range default, so we need another way to discover the source of
- * its value. This offset is only used if given a value greater than
- * zero.
- */
- int isset_offset;
} relopt_parse_elt;
/* Local reloption definition */
@@ -195,6 +190,9 @@ typedef struct local_relopts
extern relopt_kind add_reloption_kind(void);
extern void add_bool_reloption(bits32 kinds, const char *name, const char *desc,
bool default_val, LOCKMODE lockmode);
+extern void add_ternary_reloption(bits32 kinds, const char *name,
+ const char *desc, pg_ternary default_val,
+ LOCKMODE lockmode);
extern void add_int_reloption(bits32 kinds, const char *name, const char *desc,
int default_val, int min_val, int max_val,
LOCKMODE lockmode);
@@ -214,6 +212,9 @@ extern void register_reloptions_validator(local_relopts *relopts,
extern void add_local_bool_reloption(local_relopts *relopts, const char *name,
const char *desc, bool default_val,
int offset);
+extern void add_local_ternary_reloption(local_relopts *relopts,
+ const char *name, const char *desc,
+ pg_ternary default_val, int offset);
extern void add_local_int_reloption(local_relopts *relopts, const char *name,
const char *desc, int default_val,
int min_val, int max_val, int offset);
diff --git a/src/include/postgres.h b/src/include/postgres.h
index 7d93fbce709..47c88780776 100644
--- a/src/include/postgres.h
+++ b/src/include/postgres.h
@@ -543,6 +543,21 @@ Float8GetDatum(float8 X)
* ----------------------------------------------------------------
*/
+/*
+ * pg_ternary
+ * Boolean value with an extra "unset" value
+ *
+ * This enum can be used for values that want to distinguish between true,
+ * false, and unset.
+*/
+
+typedef enum pg_ternary
+{
+ TERNARY_FALSE = 0,
+ TERNARY_TRUE = 1,
+ TERNARY_UNSET = -1
+} pg_ternary;
+
/*
* NON_EXEC_STATIC: It's sometimes useful to define a variable or function
* that is normally static but extern when using EXEC_BACKEND (see
diff --git a/src/include/utils/rel.h b/src/include/utils/rel.h
index d03ab247788..5feb18a5373 100644
--- a/src/include/utils/rel.h
+++ b/src/include/utils/rel.h
@@ -347,8 +347,7 @@ typedef struct StdRdOptions
bool user_catalog_table; /* use as an additional catalog relation */
int parallel_workers; /* max number of parallel workers */
StdRdOptIndexCleanup vacuum_index_cleanup; /* controls index vacuuming */
- bool vacuum_truncate; /* enables vacuum to truncate a relation */
- bool vacuum_truncate_set; /* whether vacuum_truncate is set */
+ pg_ternary vacuum_truncate; /* enables vacuum to truncate a relation */
/*
* Fraction of pages in a relation that vacuum can eagerly scan and fail
diff --git a/src/test/regress/expected/reloptions.out b/src/test/regress/expected/reloptions.out
index 9de19b4e3f1..1c99f79ab01 100644
--- a/src/test/regress/expected/reloptions.out
+++ b/src/test/regress/expected/reloptions.out
@@ -98,6 +98,42 @@ SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass;
{fillfactor=13,autovacuum_enabled=false}
(1 row)
+-- Tests for future (FIXME) ternary options
+-- behave as boolean option: accept unassigned name and truncated value
+DROP TABLE reloptions_test;
+CREATE TABLE reloptions_test(i INT) WITH (vacuum_truncate);
+SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass;
+ reloptions
+------------------------
+ {vacuum_truncate=true}
+(1 row)
+
+DROP TABLE reloptions_test;
+CREATE TABLE reloptions_test(i INT) WITH (vacuum_truncate=FaLS);
+SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass;
+ reloptions
+------------------------
+ {vacuum_truncate=fals}
+(1 row)
+
+-- preferred "true" alias is used when storing
+DROP TABLE reloptions_test;
+CREATE TABLE reloptions_test(i INT) WITH (vacuum_index_cleanup=on);
+SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass;
+ reloptions
+---------------------------
+ {vacuum_index_cleanup=on}
+(1 row)
+
+-- custom "third" value is available
+DROP TABLE reloptions_test;
+CREATE TABLE reloptions_test(i INT) WITH (vacuum_index_cleanup=auto);
+SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass;
+ reloptions
+-----------------------------
+ {vacuum_index_cleanup=auto}
+(1 row)
+
-- Test vacuum_truncate option
DROP TABLE reloptions_test;
CREATE TEMP TABLE reloptions_test(i INT NOT NULL, j text)
diff --git a/src/test/regress/sql/reloptions.sql b/src/test/regress/sql/reloptions.sql
index 24fbe0b478d..f5980dafcbc 100644
--- a/src/test/regress/sql/reloptions.sql
+++ b/src/test/regress/sql/reloptions.sql
@@ -59,6 +59,27 @@ UPDATE pg_class
ALTER TABLE reloptions_test RESET (illegal_option);
SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass;
+-- Tests for future (FIXME) ternary options
+
+-- behave as boolean option: accept unassigned name and truncated value
+DROP TABLE reloptions_test;
+CREATE TABLE reloptions_test(i INT) WITH (vacuum_truncate);
+SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass;
+
+DROP TABLE reloptions_test;
+CREATE TABLE reloptions_test(i INT) WITH (vacuum_truncate=FaLS);
+SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass;
+
+-- preferred "true" alias is used when storing
+DROP TABLE reloptions_test;
+CREATE TABLE reloptions_test(i INT) WITH (vacuum_index_cleanup=on);
+SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass;
+
+-- custom "third" value is available
+DROP TABLE reloptions_test;
+CREATE TABLE reloptions_test(i INT) WITH (vacuum_index_cleanup=auto);
+SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass;
+
-- Test vacuum_truncate option
DROP TABLE reloptions_test;
diff --git a/src/tools/pgindent/typedefs.list b/src/tools/pgindent/typedefs.list
index 3f3a888fd0e..1c8610fd46c 100644
--- a/src/tools/pgindent/typedefs.list
+++ b/src/tools/pgindent/typedefs.list
@@ -3952,6 +3952,7 @@ pg_sha512_ctx
pg_snapshot
pg_special_case
pg_stack_base_t
+pg_ternary
pg_time_t
pg_time_usec_t
pg_tz
@@ -4079,6 +4080,7 @@ relopt_kind
relopt_parse_elt
relopt_real
relopt_string
+relopt_ternary
relopt_type
relopt_value
relopts_validator
--
2.47.3
--c36t3fndxhkzbl5z--
^ permalink raw reply [nested|flat] 868+ messages in thread
* [PATCH v3] Introduce ternary reloptions
@ 2026-01-16 15:04 Álvaro Herrera <[email protected]>
0 siblings, 0 replies; 868+ messages in thread
From: Álvaro Herrera @ 2026-01-16 15:04 UTC (permalink / raw)
Introduce ternary reloption as a replacement for current
`vacuum_truncate` implementation. Remove the `vacuum_truncate_set`
separate flag and use TERNARY_UNSET instead.
This could also be used for other options such as `vacuum_index_cleanup`
and `buffering`, but lets get the scaffolding in first.
Discussion: https://postgr.es/m/3474141.usfYGdeWWP@thinkpad-pgpro
---
src/backend/access/common/reloptions.c | 137 ++++++++++++++++++-----
src/backend/commands/vacuum.c | 4 +-
src/include/access/reloptions.h | 27 ++---
src/include/postgres.h | 15 +++
src/include/utils/rel.h | 3 +-
src/test/regress/expected/reloptions.out | 36 ++++++
src/test/regress/sql/reloptions.sql | 21 ++++
src/tools/pgindent/typedefs.list | 2 +
8 files changed, 202 insertions(+), 43 deletions(-)
diff --git a/src/backend/access/common/reloptions.c b/src/backend/access/common/reloptions.c
index 0b83f98ed5f..6f1f577581a 100644
--- a/src/backend/access/common/reloptions.c
+++ b/src/backend/access/common/reloptions.c
@@ -40,9 +40,9 @@
*
* To add an option:
*
- * (i) decide on a type (bool, integer, real, enum, string), name, default
- * value, upper and lower bounds (if applicable); for strings, consider a
- * validation routine.
+ * (i) decide on a type (bool, ternary, integer, real, enum, string), name,
+ * default value, upper and lower bounds (if applicable); for strings,
+ * consider a validation routine.
* (ii) add a record below (or use add_<type>_reloption).
* (iii) add it to the appropriate options struct (perhaps StdRdOptions)
* (iv) add it to the appropriate handling routine (perhaps
@@ -147,15 +147,6 @@ static relopt_bool boolRelOpts[] =
},
false
},
- {
- {
- "vacuum_truncate",
- "Enables vacuum to truncate empty pages at the end of this table",
- RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
- ShareUpdateExclusiveLock
- },
- true
- },
{
{
"deduplicate_items",
@@ -170,6 +161,25 @@ static relopt_bool boolRelOpts[] =
{{NULL}}
};
+static relopt_ternary ternaryRelOpts[] =
+{
+ {
+ {
+ "vacuum_truncate",
+ "Enables vacuum to truncate empty pages at the end of this table",
+ RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
+ ShareUpdateExclusiveLock
+ },
+ TERNARY_UNSET
+ },
+ /* list terminator */
+ {
+ {
+ NULL
+ }
+ }
+};
+
static relopt_int intRelOpts[] =
{
{
@@ -609,6 +619,13 @@ initialize_reloptions(void)
boolRelOpts[i].gen.lockmode));
j++;
}
+ for (i = 0; ternaryRelOpts[i].gen.name; i++)
+ {
+ Assert(DoLockModesConflict(ternaryRelOpts[i].gen.lockmode,
+ ternaryRelOpts[i].gen.lockmode));
+ j++;
+ }
+
for (i = 0; intRelOpts[i].gen.name; i++)
{
Assert(DoLockModesConflict(intRelOpts[i].gen.lockmode,
@@ -649,6 +666,14 @@ initialize_reloptions(void)
j++;
}
+ for (i = 0; ternaryRelOpts[i].gen.name; i++)
+ {
+ relOpts[j] = &ternaryRelOpts[i].gen;
+ relOpts[j]->type = RELOPT_TYPE_TERNARY;
+ relOpts[j]->namelen = strlen(relOpts[j]->name);
+ j++;
+ }
+
for (i = 0; intRelOpts[i].gen.name; i++)
{
relOpts[j] = &intRelOpts[i].gen;
@@ -809,6 +834,9 @@ allocate_reloption(bits32 kinds, int type, const char *name, const char *desc,
case RELOPT_TYPE_BOOL:
size = sizeof(relopt_bool);
break;
+ case RELOPT_TYPE_TERNARY:
+ size = sizeof(relopt_ternary);
+ break;
case RELOPT_TYPE_INT:
size = sizeof(relopt_int);
break;
@@ -892,6 +920,57 @@ add_local_bool_reloption(local_relopts *relopts, const char *name,
add_local_reloption(relopts, (relopt_gen *) newoption, offset);
}
+/*
+ * init_ternary_reloption
+ * Allocate and initialize a new ternary reloption
+ */
+static relopt_ternary *
+init_ternary_reloption(bits32 kinds, const char *name, const char *desc,
+ pg_ternary default_val, LOCKMODE lockmode)
+{
+ relopt_ternary *newoption;
+
+ newoption = (relopt_ternary *)
+ allocate_reloption(kinds, RELOPT_TYPE_TERNARY, name, desc, lockmode);
+ newoption->default_val = default_val;
+
+ return newoption;
+}
+
+/*
+ * add_ternary_reloption
+ * Add a new ternary reloption
+ */
+void
+add_ternary_reloption(bits32 kinds, const char *name, const char *desc,
+ pg_ternary default_val, LOCKMODE lockmode)
+{
+ relopt_ternary *newoption;
+
+ newoption =
+ init_ternary_reloption(kinds, name, desc, default_val, lockmode);
+
+ add_reloption((relopt_gen *) newoption);
+}
+
+/*
+ * add_local_ternary_reloption
+ * Add a new ternary local reloption
+ *
+ * 'offset' is offset of ternary-typed field.
+ */
+void
+add_local_ternary_reloption(local_relopts *relopts, const char *name,
+ const char *desc, pg_ternary default_val,
+ int offset)
+{
+ relopt_ternary *newoption;
+
+ newoption =
+ init_ternary_reloption(RELOPT_KIND_LOCAL, name, desc, default_val, 0);
+
+ add_local_reloption(relopts, (relopt_gen *) newoption, offset);
+}
/*
* init_real_reloption
@@ -1626,6 +1705,19 @@ parse_one_reloption(relopt_value *option, char *text_str, int text_len,
option->gen->name, value)));
}
break;
+ case RELOPT_TYPE_TERNARY:
+ {
+ bool b;
+
+ parsed = parse_bool(value, &b);
+ option->values.ternary_val = b ? TERNARY_TRUE : TERNARY_FALSE;
+ if (validate && !parsed)
+ ereport(ERROR,
+ (errcode(ERRCODE_INVALID_PARAMETER_VALUE),
+ errmsg("invalid value for ternary option \"%s\": %s",
+ option->gen->name, value)));
+ }
+ break;
case RELOPT_TYPE_INT:
{
relopt_int *optint = (relopt_int *) option->gen;
@@ -1789,17 +1881,6 @@ fillRelOptions(void *rdopts, Size basesize,
char *itempos = ((char *) rdopts) + elems[j].offset;
char *string_val;
- /*
- * If isset_offset is provided, store whether the reloption is
- * set there.
- */
- if (elems[j].isset_offset > 0)
- {
- char *setpos = ((char *) rdopts) + elems[j].isset_offset;
-
- *(bool *) setpos = options[i].isset;
- }
-
switch (options[i].gen->type)
{
case RELOPT_TYPE_BOOL:
@@ -1807,6 +1888,11 @@ fillRelOptions(void *rdopts, Size basesize,
options[i].values.bool_val :
((relopt_bool *) options[i].gen)->default_val;
break;
+ case RELOPT_TYPE_TERNARY:
+ *(pg_ternary *) itempos = options[i].isset ?
+ options[i].values.ternary_val :
+ ((relopt_ternary *) options[i].gen)->default_val;
+ break;
case RELOPT_TYPE_INT:
*(int *) itempos = options[i].isset ?
options[i].values.int_val :
@@ -1923,8 +2009,8 @@ default_reloptions(Datum reloptions, bool validate, relopt_kind kind)
offsetof(StdRdOptions, parallel_workers)},
{"vacuum_index_cleanup", RELOPT_TYPE_ENUM,
offsetof(StdRdOptions, vacuum_index_cleanup)},
- {"vacuum_truncate", RELOPT_TYPE_BOOL,
- offsetof(StdRdOptions, vacuum_truncate), offsetof(StdRdOptions, vacuum_truncate_set)},
+ {"vacuum_truncate", RELOPT_TYPE_TERNARY,
+ offsetof(StdRdOptions, vacuum_truncate)},
{"vacuum_max_eager_freeze_failure_rate", RELOPT_TYPE_REAL,
offsetof(StdRdOptions, vacuum_max_eager_freeze_failure_rate)}
};
@@ -2004,7 +2090,6 @@ build_local_reloptions(local_relopts *relopts, Datum options, bool validate)
elems[i].optname = opt->option->name;
elems[i].opttype = opt->option->type;
elems[i].offset = opt->offset;
- elems[i].isset_offset = 0; /* not supported for local relopts yet */
i++;
}
diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c
index aa4fbec143f..696eab9bd97 100644
--- a/src/backend/commands/vacuum.c
+++ b/src/backend/commands/vacuum.c
@@ -2224,9 +2224,9 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams params,
{
StdRdOptions *opts = (StdRdOptions *) rel->rd_options;
- if (opts && opts->vacuum_truncate_set)
+ if (opts && opts->vacuum_truncate != TERNARY_UNSET)
{
- if (opts->vacuum_truncate)
+ if (opts->vacuum_truncate == TERNARY_TRUE)
params.truncate = VACOPTVALUE_ENABLED;
else
params.truncate = VACOPTVALUE_DISABLED;
diff --git a/src/include/access/reloptions.h b/src/include/access/reloptions.h
index 2f08e1b0cf0..dfbef2babf2 100644
--- a/src/include/access/reloptions.h
+++ b/src/include/access/reloptions.h
@@ -29,6 +29,7 @@
typedef enum relopt_type
{
RELOPT_TYPE_BOOL,
+ RELOPT_TYPE_TERNARY, /* on, off, unset */
RELOPT_TYPE_INT,
RELOPT_TYPE_REAL,
RELOPT_TYPE_ENUM,
@@ -80,6 +81,7 @@ typedef struct relopt_value
union
{
bool bool_val;
+ pg_ternary ternary_val;
int int_val;
double real_val;
int enum_val;
@@ -94,6 +96,12 @@ typedef struct relopt_bool
bool default_val;
} relopt_bool;
+typedef struct relopt_ternary
+{
+ relopt_gen gen;
+ int default_val;
+} relopt_ternary;
+
typedef struct relopt_int
{
relopt_gen gen;
@@ -152,19 +160,6 @@ typedef struct
const char *optname; /* option's name */
relopt_type opttype; /* option's datatype */
int offset; /* offset of field in result struct */
-
- /*
- * isset_offset is an optional offset of a field in the result struct that
- * stores whether the option is explicitly set for the relation or if it
- * just picked up the default value. In most cases, this can be
- * accomplished by giving the reloption a special out-of-range default
- * value (e.g., some integer reloptions use -2), but this isn't always
- * possible. For example, a Boolean reloption cannot be given an
- * out-of-range default, so we need another way to discover the source of
- * its value. This offset is only used if given a value greater than
- * zero.
- */
- int isset_offset;
} relopt_parse_elt;
/* Local reloption definition */
@@ -195,6 +190,9 @@ typedef struct local_relopts
extern relopt_kind add_reloption_kind(void);
extern void add_bool_reloption(bits32 kinds, const char *name, const char *desc,
bool default_val, LOCKMODE lockmode);
+extern void add_ternary_reloption(bits32 kinds, const char *name,
+ const char *desc, pg_ternary default_val,
+ LOCKMODE lockmode);
extern void add_int_reloption(bits32 kinds, const char *name, const char *desc,
int default_val, int min_val, int max_val,
LOCKMODE lockmode);
@@ -214,6 +212,9 @@ extern void register_reloptions_validator(local_relopts *relopts,
extern void add_local_bool_reloption(local_relopts *relopts, const char *name,
const char *desc, bool default_val,
int offset);
+extern void add_local_ternary_reloption(local_relopts *relopts,
+ const char *name, const char *desc,
+ pg_ternary default_val, int offset);
extern void add_local_int_reloption(local_relopts *relopts, const char *name,
const char *desc, int default_val,
int min_val, int max_val, int offset);
diff --git a/src/include/postgres.h b/src/include/postgres.h
index 7d93fbce709..47c88780776 100644
--- a/src/include/postgres.h
+++ b/src/include/postgres.h
@@ -543,6 +543,21 @@ Float8GetDatum(float8 X)
* ----------------------------------------------------------------
*/
+/*
+ * pg_ternary
+ * Boolean value with an extra "unset" value
+ *
+ * This enum can be used for values that want to distinguish between true,
+ * false, and unset.
+*/
+
+typedef enum pg_ternary
+{
+ TERNARY_FALSE = 0,
+ TERNARY_TRUE = 1,
+ TERNARY_UNSET = -1
+} pg_ternary;
+
/*
* NON_EXEC_STATIC: It's sometimes useful to define a variable or function
* that is normally static but extern when using EXEC_BACKEND (see
diff --git a/src/include/utils/rel.h b/src/include/utils/rel.h
index d03ab247788..5feb18a5373 100644
--- a/src/include/utils/rel.h
+++ b/src/include/utils/rel.h
@@ -347,8 +347,7 @@ typedef struct StdRdOptions
bool user_catalog_table; /* use as an additional catalog relation */
int parallel_workers; /* max number of parallel workers */
StdRdOptIndexCleanup vacuum_index_cleanup; /* controls index vacuuming */
- bool vacuum_truncate; /* enables vacuum to truncate a relation */
- bool vacuum_truncate_set; /* whether vacuum_truncate is set */
+ pg_ternary vacuum_truncate; /* enables vacuum to truncate a relation */
/*
* Fraction of pages in a relation that vacuum can eagerly scan and fail
diff --git a/src/test/regress/expected/reloptions.out b/src/test/regress/expected/reloptions.out
index 9de19b4e3f1..1c99f79ab01 100644
--- a/src/test/regress/expected/reloptions.out
+++ b/src/test/regress/expected/reloptions.out
@@ -98,6 +98,42 @@ SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass;
{fillfactor=13,autovacuum_enabled=false}
(1 row)
+-- Tests for future (FIXME) ternary options
+-- behave as boolean option: accept unassigned name and truncated value
+DROP TABLE reloptions_test;
+CREATE TABLE reloptions_test(i INT) WITH (vacuum_truncate);
+SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass;
+ reloptions
+------------------------
+ {vacuum_truncate=true}
+(1 row)
+
+DROP TABLE reloptions_test;
+CREATE TABLE reloptions_test(i INT) WITH (vacuum_truncate=FaLS);
+SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass;
+ reloptions
+------------------------
+ {vacuum_truncate=fals}
+(1 row)
+
+-- preferred "true" alias is used when storing
+DROP TABLE reloptions_test;
+CREATE TABLE reloptions_test(i INT) WITH (vacuum_index_cleanup=on);
+SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass;
+ reloptions
+---------------------------
+ {vacuum_index_cleanup=on}
+(1 row)
+
+-- custom "third" value is available
+DROP TABLE reloptions_test;
+CREATE TABLE reloptions_test(i INT) WITH (vacuum_index_cleanup=auto);
+SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass;
+ reloptions
+-----------------------------
+ {vacuum_index_cleanup=auto}
+(1 row)
+
-- Test vacuum_truncate option
DROP TABLE reloptions_test;
CREATE TEMP TABLE reloptions_test(i INT NOT NULL, j text)
diff --git a/src/test/regress/sql/reloptions.sql b/src/test/regress/sql/reloptions.sql
index 24fbe0b478d..f5980dafcbc 100644
--- a/src/test/regress/sql/reloptions.sql
+++ b/src/test/regress/sql/reloptions.sql
@@ -59,6 +59,27 @@ UPDATE pg_class
ALTER TABLE reloptions_test RESET (illegal_option);
SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass;
+-- Tests for future (FIXME) ternary options
+
+-- behave as boolean option: accept unassigned name and truncated value
+DROP TABLE reloptions_test;
+CREATE TABLE reloptions_test(i INT) WITH (vacuum_truncate);
+SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass;
+
+DROP TABLE reloptions_test;
+CREATE TABLE reloptions_test(i INT) WITH (vacuum_truncate=FaLS);
+SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass;
+
+-- preferred "true" alias is used when storing
+DROP TABLE reloptions_test;
+CREATE TABLE reloptions_test(i INT) WITH (vacuum_index_cleanup=on);
+SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass;
+
+-- custom "third" value is available
+DROP TABLE reloptions_test;
+CREATE TABLE reloptions_test(i INT) WITH (vacuum_index_cleanup=auto);
+SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass;
+
-- Test vacuum_truncate option
DROP TABLE reloptions_test;
diff --git a/src/tools/pgindent/typedefs.list b/src/tools/pgindent/typedefs.list
index 3f3a888fd0e..1c8610fd46c 100644
--- a/src/tools/pgindent/typedefs.list
+++ b/src/tools/pgindent/typedefs.list
@@ -3952,6 +3952,7 @@ pg_sha512_ctx
pg_snapshot
pg_special_case
pg_stack_base_t
+pg_ternary
pg_time_t
pg_time_usec_t
pg_tz
@@ -4079,6 +4080,7 @@ relopt_kind
relopt_parse_elt
relopt_real
relopt_string
+relopt_ternary
relopt_type
relopt_value
relopts_validator
--
2.47.3
--c36t3fndxhkzbl5z--
^ permalink raw reply [nested|flat] 868+ messages in thread
* [PATCH v3] Introduce ternary reloptions
@ 2026-01-16 15:04 Álvaro Herrera <[email protected]>
0 siblings, 0 replies; 868+ messages in thread
From: Álvaro Herrera @ 2026-01-16 15:04 UTC (permalink / raw)
Introduce ternary reloption as a replacement for current
`vacuum_truncate` implementation. Remove the `vacuum_truncate_set`
separate flag and use TERNARY_UNSET instead.
This could also be used for other options such as `vacuum_index_cleanup`
and `buffering`, but lets get the scaffolding in first.
Discussion: https://postgr.es/m/3474141.usfYGdeWWP@thinkpad-pgpro
---
src/backend/access/common/reloptions.c | 137 ++++++++++++++++++-----
src/backend/commands/vacuum.c | 4 +-
src/include/access/reloptions.h | 27 ++---
src/include/postgres.h | 15 +++
src/include/utils/rel.h | 3 +-
src/test/regress/expected/reloptions.out | 36 ++++++
src/test/regress/sql/reloptions.sql | 21 ++++
src/tools/pgindent/typedefs.list | 2 +
8 files changed, 202 insertions(+), 43 deletions(-)
diff --git a/src/backend/access/common/reloptions.c b/src/backend/access/common/reloptions.c
index 0b83f98ed5f..6f1f577581a 100644
--- a/src/backend/access/common/reloptions.c
+++ b/src/backend/access/common/reloptions.c
@@ -40,9 +40,9 @@
*
* To add an option:
*
- * (i) decide on a type (bool, integer, real, enum, string), name, default
- * value, upper and lower bounds (if applicable); for strings, consider a
- * validation routine.
+ * (i) decide on a type (bool, ternary, integer, real, enum, string), name,
+ * default value, upper and lower bounds (if applicable); for strings,
+ * consider a validation routine.
* (ii) add a record below (or use add_<type>_reloption).
* (iii) add it to the appropriate options struct (perhaps StdRdOptions)
* (iv) add it to the appropriate handling routine (perhaps
@@ -147,15 +147,6 @@ static relopt_bool boolRelOpts[] =
},
false
},
- {
- {
- "vacuum_truncate",
- "Enables vacuum to truncate empty pages at the end of this table",
- RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
- ShareUpdateExclusiveLock
- },
- true
- },
{
{
"deduplicate_items",
@@ -170,6 +161,25 @@ static relopt_bool boolRelOpts[] =
{{NULL}}
};
+static relopt_ternary ternaryRelOpts[] =
+{
+ {
+ {
+ "vacuum_truncate",
+ "Enables vacuum to truncate empty pages at the end of this table",
+ RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
+ ShareUpdateExclusiveLock
+ },
+ TERNARY_UNSET
+ },
+ /* list terminator */
+ {
+ {
+ NULL
+ }
+ }
+};
+
static relopt_int intRelOpts[] =
{
{
@@ -609,6 +619,13 @@ initialize_reloptions(void)
boolRelOpts[i].gen.lockmode));
j++;
}
+ for (i = 0; ternaryRelOpts[i].gen.name; i++)
+ {
+ Assert(DoLockModesConflict(ternaryRelOpts[i].gen.lockmode,
+ ternaryRelOpts[i].gen.lockmode));
+ j++;
+ }
+
for (i = 0; intRelOpts[i].gen.name; i++)
{
Assert(DoLockModesConflict(intRelOpts[i].gen.lockmode,
@@ -649,6 +666,14 @@ initialize_reloptions(void)
j++;
}
+ for (i = 0; ternaryRelOpts[i].gen.name; i++)
+ {
+ relOpts[j] = &ternaryRelOpts[i].gen;
+ relOpts[j]->type = RELOPT_TYPE_TERNARY;
+ relOpts[j]->namelen = strlen(relOpts[j]->name);
+ j++;
+ }
+
for (i = 0; intRelOpts[i].gen.name; i++)
{
relOpts[j] = &intRelOpts[i].gen;
@@ -809,6 +834,9 @@ allocate_reloption(bits32 kinds, int type, const char *name, const char *desc,
case RELOPT_TYPE_BOOL:
size = sizeof(relopt_bool);
break;
+ case RELOPT_TYPE_TERNARY:
+ size = sizeof(relopt_ternary);
+ break;
case RELOPT_TYPE_INT:
size = sizeof(relopt_int);
break;
@@ -892,6 +920,57 @@ add_local_bool_reloption(local_relopts *relopts, const char *name,
add_local_reloption(relopts, (relopt_gen *) newoption, offset);
}
+/*
+ * init_ternary_reloption
+ * Allocate and initialize a new ternary reloption
+ */
+static relopt_ternary *
+init_ternary_reloption(bits32 kinds, const char *name, const char *desc,
+ pg_ternary default_val, LOCKMODE lockmode)
+{
+ relopt_ternary *newoption;
+
+ newoption = (relopt_ternary *)
+ allocate_reloption(kinds, RELOPT_TYPE_TERNARY, name, desc, lockmode);
+ newoption->default_val = default_val;
+
+ return newoption;
+}
+
+/*
+ * add_ternary_reloption
+ * Add a new ternary reloption
+ */
+void
+add_ternary_reloption(bits32 kinds, const char *name, const char *desc,
+ pg_ternary default_val, LOCKMODE lockmode)
+{
+ relopt_ternary *newoption;
+
+ newoption =
+ init_ternary_reloption(kinds, name, desc, default_val, lockmode);
+
+ add_reloption((relopt_gen *) newoption);
+}
+
+/*
+ * add_local_ternary_reloption
+ * Add a new ternary local reloption
+ *
+ * 'offset' is offset of ternary-typed field.
+ */
+void
+add_local_ternary_reloption(local_relopts *relopts, const char *name,
+ const char *desc, pg_ternary default_val,
+ int offset)
+{
+ relopt_ternary *newoption;
+
+ newoption =
+ init_ternary_reloption(RELOPT_KIND_LOCAL, name, desc, default_val, 0);
+
+ add_local_reloption(relopts, (relopt_gen *) newoption, offset);
+}
/*
* init_real_reloption
@@ -1626,6 +1705,19 @@ parse_one_reloption(relopt_value *option, char *text_str, int text_len,
option->gen->name, value)));
}
break;
+ case RELOPT_TYPE_TERNARY:
+ {
+ bool b;
+
+ parsed = parse_bool(value, &b);
+ option->values.ternary_val = b ? TERNARY_TRUE : TERNARY_FALSE;
+ if (validate && !parsed)
+ ereport(ERROR,
+ (errcode(ERRCODE_INVALID_PARAMETER_VALUE),
+ errmsg("invalid value for ternary option \"%s\": %s",
+ option->gen->name, value)));
+ }
+ break;
case RELOPT_TYPE_INT:
{
relopt_int *optint = (relopt_int *) option->gen;
@@ -1789,17 +1881,6 @@ fillRelOptions(void *rdopts, Size basesize,
char *itempos = ((char *) rdopts) + elems[j].offset;
char *string_val;
- /*
- * If isset_offset is provided, store whether the reloption is
- * set there.
- */
- if (elems[j].isset_offset > 0)
- {
- char *setpos = ((char *) rdopts) + elems[j].isset_offset;
-
- *(bool *) setpos = options[i].isset;
- }
-
switch (options[i].gen->type)
{
case RELOPT_TYPE_BOOL:
@@ -1807,6 +1888,11 @@ fillRelOptions(void *rdopts, Size basesize,
options[i].values.bool_val :
((relopt_bool *) options[i].gen)->default_val;
break;
+ case RELOPT_TYPE_TERNARY:
+ *(pg_ternary *) itempos = options[i].isset ?
+ options[i].values.ternary_val :
+ ((relopt_ternary *) options[i].gen)->default_val;
+ break;
case RELOPT_TYPE_INT:
*(int *) itempos = options[i].isset ?
options[i].values.int_val :
@@ -1923,8 +2009,8 @@ default_reloptions(Datum reloptions, bool validate, relopt_kind kind)
offsetof(StdRdOptions, parallel_workers)},
{"vacuum_index_cleanup", RELOPT_TYPE_ENUM,
offsetof(StdRdOptions, vacuum_index_cleanup)},
- {"vacuum_truncate", RELOPT_TYPE_BOOL,
- offsetof(StdRdOptions, vacuum_truncate), offsetof(StdRdOptions, vacuum_truncate_set)},
+ {"vacuum_truncate", RELOPT_TYPE_TERNARY,
+ offsetof(StdRdOptions, vacuum_truncate)},
{"vacuum_max_eager_freeze_failure_rate", RELOPT_TYPE_REAL,
offsetof(StdRdOptions, vacuum_max_eager_freeze_failure_rate)}
};
@@ -2004,7 +2090,6 @@ build_local_reloptions(local_relopts *relopts, Datum options, bool validate)
elems[i].optname = opt->option->name;
elems[i].opttype = opt->option->type;
elems[i].offset = opt->offset;
- elems[i].isset_offset = 0; /* not supported for local relopts yet */
i++;
}
diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c
index aa4fbec143f..696eab9bd97 100644
--- a/src/backend/commands/vacuum.c
+++ b/src/backend/commands/vacuum.c
@@ -2224,9 +2224,9 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams params,
{
StdRdOptions *opts = (StdRdOptions *) rel->rd_options;
- if (opts && opts->vacuum_truncate_set)
+ if (opts && opts->vacuum_truncate != TERNARY_UNSET)
{
- if (opts->vacuum_truncate)
+ if (opts->vacuum_truncate == TERNARY_TRUE)
params.truncate = VACOPTVALUE_ENABLED;
else
params.truncate = VACOPTVALUE_DISABLED;
diff --git a/src/include/access/reloptions.h b/src/include/access/reloptions.h
index 2f08e1b0cf0..dfbef2babf2 100644
--- a/src/include/access/reloptions.h
+++ b/src/include/access/reloptions.h
@@ -29,6 +29,7 @@
typedef enum relopt_type
{
RELOPT_TYPE_BOOL,
+ RELOPT_TYPE_TERNARY, /* on, off, unset */
RELOPT_TYPE_INT,
RELOPT_TYPE_REAL,
RELOPT_TYPE_ENUM,
@@ -80,6 +81,7 @@ typedef struct relopt_value
union
{
bool bool_val;
+ pg_ternary ternary_val;
int int_val;
double real_val;
int enum_val;
@@ -94,6 +96,12 @@ typedef struct relopt_bool
bool default_val;
} relopt_bool;
+typedef struct relopt_ternary
+{
+ relopt_gen gen;
+ int default_val;
+} relopt_ternary;
+
typedef struct relopt_int
{
relopt_gen gen;
@@ -152,19 +160,6 @@ typedef struct
const char *optname; /* option's name */
relopt_type opttype; /* option's datatype */
int offset; /* offset of field in result struct */
-
- /*
- * isset_offset is an optional offset of a field in the result struct that
- * stores whether the option is explicitly set for the relation or if it
- * just picked up the default value. In most cases, this can be
- * accomplished by giving the reloption a special out-of-range default
- * value (e.g., some integer reloptions use -2), but this isn't always
- * possible. For example, a Boolean reloption cannot be given an
- * out-of-range default, so we need another way to discover the source of
- * its value. This offset is only used if given a value greater than
- * zero.
- */
- int isset_offset;
} relopt_parse_elt;
/* Local reloption definition */
@@ -195,6 +190,9 @@ typedef struct local_relopts
extern relopt_kind add_reloption_kind(void);
extern void add_bool_reloption(bits32 kinds, const char *name, const char *desc,
bool default_val, LOCKMODE lockmode);
+extern void add_ternary_reloption(bits32 kinds, const char *name,
+ const char *desc, pg_ternary default_val,
+ LOCKMODE lockmode);
extern void add_int_reloption(bits32 kinds, const char *name, const char *desc,
int default_val, int min_val, int max_val,
LOCKMODE lockmode);
@@ -214,6 +212,9 @@ extern void register_reloptions_validator(local_relopts *relopts,
extern void add_local_bool_reloption(local_relopts *relopts, const char *name,
const char *desc, bool default_val,
int offset);
+extern void add_local_ternary_reloption(local_relopts *relopts,
+ const char *name, const char *desc,
+ pg_ternary default_val, int offset);
extern void add_local_int_reloption(local_relopts *relopts, const char *name,
const char *desc, int default_val,
int min_val, int max_val, int offset);
diff --git a/src/include/postgres.h b/src/include/postgres.h
index 7d93fbce709..47c88780776 100644
--- a/src/include/postgres.h
+++ b/src/include/postgres.h
@@ -543,6 +543,21 @@ Float8GetDatum(float8 X)
* ----------------------------------------------------------------
*/
+/*
+ * pg_ternary
+ * Boolean value with an extra "unset" value
+ *
+ * This enum can be used for values that want to distinguish between true,
+ * false, and unset.
+*/
+
+typedef enum pg_ternary
+{
+ TERNARY_FALSE = 0,
+ TERNARY_TRUE = 1,
+ TERNARY_UNSET = -1
+} pg_ternary;
+
/*
* NON_EXEC_STATIC: It's sometimes useful to define a variable or function
* that is normally static but extern when using EXEC_BACKEND (see
diff --git a/src/include/utils/rel.h b/src/include/utils/rel.h
index d03ab247788..5feb18a5373 100644
--- a/src/include/utils/rel.h
+++ b/src/include/utils/rel.h
@@ -347,8 +347,7 @@ typedef struct StdRdOptions
bool user_catalog_table; /* use as an additional catalog relation */
int parallel_workers; /* max number of parallel workers */
StdRdOptIndexCleanup vacuum_index_cleanup; /* controls index vacuuming */
- bool vacuum_truncate; /* enables vacuum to truncate a relation */
- bool vacuum_truncate_set; /* whether vacuum_truncate is set */
+ pg_ternary vacuum_truncate; /* enables vacuum to truncate a relation */
/*
* Fraction of pages in a relation that vacuum can eagerly scan and fail
diff --git a/src/test/regress/expected/reloptions.out b/src/test/regress/expected/reloptions.out
index 9de19b4e3f1..1c99f79ab01 100644
--- a/src/test/regress/expected/reloptions.out
+++ b/src/test/regress/expected/reloptions.out
@@ -98,6 +98,42 @@ SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass;
{fillfactor=13,autovacuum_enabled=false}
(1 row)
+-- Tests for future (FIXME) ternary options
+-- behave as boolean option: accept unassigned name and truncated value
+DROP TABLE reloptions_test;
+CREATE TABLE reloptions_test(i INT) WITH (vacuum_truncate);
+SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass;
+ reloptions
+------------------------
+ {vacuum_truncate=true}
+(1 row)
+
+DROP TABLE reloptions_test;
+CREATE TABLE reloptions_test(i INT) WITH (vacuum_truncate=FaLS);
+SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass;
+ reloptions
+------------------------
+ {vacuum_truncate=fals}
+(1 row)
+
+-- preferred "true" alias is used when storing
+DROP TABLE reloptions_test;
+CREATE TABLE reloptions_test(i INT) WITH (vacuum_index_cleanup=on);
+SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass;
+ reloptions
+---------------------------
+ {vacuum_index_cleanup=on}
+(1 row)
+
+-- custom "third" value is available
+DROP TABLE reloptions_test;
+CREATE TABLE reloptions_test(i INT) WITH (vacuum_index_cleanup=auto);
+SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass;
+ reloptions
+-----------------------------
+ {vacuum_index_cleanup=auto}
+(1 row)
+
-- Test vacuum_truncate option
DROP TABLE reloptions_test;
CREATE TEMP TABLE reloptions_test(i INT NOT NULL, j text)
diff --git a/src/test/regress/sql/reloptions.sql b/src/test/regress/sql/reloptions.sql
index 24fbe0b478d..f5980dafcbc 100644
--- a/src/test/regress/sql/reloptions.sql
+++ b/src/test/regress/sql/reloptions.sql
@@ -59,6 +59,27 @@ UPDATE pg_class
ALTER TABLE reloptions_test RESET (illegal_option);
SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass;
+-- Tests for future (FIXME) ternary options
+
+-- behave as boolean option: accept unassigned name and truncated value
+DROP TABLE reloptions_test;
+CREATE TABLE reloptions_test(i INT) WITH (vacuum_truncate);
+SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass;
+
+DROP TABLE reloptions_test;
+CREATE TABLE reloptions_test(i INT) WITH (vacuum_truncate=FaLS);
+SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass;
+
+-- preferred "true" alias is used when storing
+DROP TABLE reloptions_test;
+CREATE TABLE reloptions_test(i INT) WITH (vacuum_index_cleanup=on);
+SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass;
+
+-- custom "third" value is available
+DROP TABLE reloptions_test;
+CREATE TABLE reloptions_test(i INT) WITH (vacuum_index_cleanup=auto);
+SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass;
+
-- Test vacuum_truncate option
DROP TABLE reloptions_test;
diff --git a/src/tools/pgindent/typedefs.list b/src/tools/pgindent/typedefs.list
index 3f3a888fd0e..1c8610fd46c 100644
--- a/src/tools/pgindent/typedefs.list
+++ b/src/tools/pgindent/typedefs.list
@@ -3952,6 +3952,7 @@ pg_sha512_ctx
pg_snapshot
pg_special_case
pg_stack_base_t
+pg_ternary
pg_time_t
pg_time_usec_t
pg_tz
@@ -4079,6 +4080,7 @@ relopt_kind
relopt_parse_elt
relopt_real
relopt_string
+relopt_ternary
relopt_type
relopt_value
relopts_validator
--
2.47.3
--c36t3fndxhkzbl5z--
^ permalink raw reply [nested|flat] 868+ messages in thread
* [PATCH v3] Introduce ternary reloptions
@ 2026-01-16 15:04 Álvaro Herrera <[email protected]>
0 siblings, 0 replies; 868+ messages in thread
From: Álvaro Herrera @ 2026-01-16 15:04 UTC (permalink / raw)
Introduce ternary reloption as a replacement for current
`vacuum_truncate` implementation. Remove the `vacuum_truncate_set`
separate flag and use TERNARY_UNSET instead.
This could also be used for other options such as `vacuum_index_cleanup`
and `buffering`, but lets get the scaffolding in first.
Discussion: https://postgr.es/m/3474141.usfYGdeWWP@thinkpad-pgpro
---
src/backend/access/common/reloptions.c | 137 ++++++++++++++++++-----
src/backend/commands/vacuum.c | 4 +-
src/include/access/reloptions.h | 27 ++---
src/include/postgres.h | 15 +++
src/include/utils/rel.h | 3 +-
src/test/regress/expected/reloptions.out | 36 ++++++
src/test/regress/sql/reloptions.sql | 21 ++++
src/tools/pgindent/typedefs.list | 2 +
8 files changed, 202 insertions(+), 43 deletions(-)
diff --git a/src/backend/access/common/reloptions.c b/src/backend/access/common/reloptions.c
index 0b83f98ed5f..6f1f577581a 100644
--- a/src/backend/access/common/reloptions.c
+++ b/src/backend/access/common/reloptions.c
@@ -40,9 +40,9 @@
*
* To add an option:
*
- * (i) decide on a type (bool, integer, real, enum, string), name, default
- * value, upper and lower bounds (if applicable); for strings, consider a
- * validation routine.
+ * (i) decide on a type (bool, ternary, integer, real, enum, string), name,
+ * default value, upper and lower bounds (if applicable); for strings,
+ * consider a validation routine.
* (ii) add a record below (or use add_<type>_reloption).
* (iii) add it to the appropriate options struct (perhaps StdRdOptions)
* (iv) add it to the appropriate handling routine (perhaps
@@ -147,15 +147,6 @@ static relopt_bool boolRelOpts[] =
},
false
},
- {
- {
- "vacuum_truncate",
- "Enables vacuum to truncate empty pages at the end of this table",
- RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
- ShareUpdateExclusiveLock
- },
- true
- },
{
{
"deduplicate_items",
@@ -170,6 +161,25 @@ static relopt_bool boolRelOpts[] =
{{NULL}}
};
+static relopt_ternary ternaryRelOpts[] =
+{
+ {
+ {
+ "vacuum_truncate",
+ "Enables vacuum to truncate empty pages at the end of this table",
+ RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
+ ShareUpdateExclusiveLock
+ },
+ TERNARY_UNSET
+ },
+ /* list terminator */
+ {
+ {
+ NULL
+ }
+ }
+};
+
static relopt_int intRelOpts[] =
{
{
@@ -609,6 +619,13 @@ initialize_reloptions(void)
boolRelOpts[i].gen.lockmode));
j++;
}
+ for (i = 0; ternaryRelOpts[i].gen.name; i++)
+ {
+ Assert(DoLockModesConflict(ternaryRelOpts[i].gen.lockmode,
+ ternaryRelOpts[i].gen.lockmode));
+ j++;
+ }
+
for (i = 0; intRelOpts[i].gen.name; i++)
{
Assert(DoLockModesConflict(intRelOpts[i].gen.lockmode,
@@ -649,6 +666,14 @@ initialize_reloptions(void)
j++;
}
+ for (i = 0; ternaryRelOpts[i].gen.name; i++)
+ {
+ relOpts[j] = &ternaryRelOpts[i].gen;
+ relOpts[j]->type = RELOPT_TYPE_TERNARY;
+ relOpts[j]->namelen = strlen(relOpts[j]->name);
+ j++;
+ }
+
for (i = 0; intRelOpts[i].gen.name; i++)
{
relOpts[j] = &intRelOpts[i].gen;
@@ -809,6 +834,9 @@ allocate_reloption(bits32 kinds, int type, const char *name, const char *desc,
case RELOPT_TYPE_BOOL:
size = sizeof(relopt_bool);
break;
+ case RELOPT_TYPE_TERNARY:
+ size = sizeof(relopt_ternary);
+ break;
case RELOPT_TYPE_INT:
size = sizeof(relopt_int);
break;
@@ -892,6 +920,57 @@ add_local_bool_reloption(local_relopts *relopts, const char *name,
add_local_reloption(relopts, (relopt_gen *) newoption, offset);
}
+/*
+ * init_ternary_reloption
+ * Allocate and initialize a new ternary reloption
+ */
+static relopt_ternary *
+init_ternary_reloption(bits32 kinds, const char *name, const char *desc,
+ pg_ternary default_val, LOCKMODE lockmode)
+{
+ relopt_ternary *newoption;
+
+ newoption = (relopt_ternary *)
+ allocate_reloption(kinds, RELOPT_TYPE_TERNARY, name, desc, lockmode);
+ newoption->default_val = default_val;
+
+ return newoption;
+}
+
+/*
+ * add_ternary_reloption
+ * Add a new ternary reloption
+ */
+void
+add_ternary_reloption(bits32 kinds, const char *name, const char *desc,
+ pg_ternary default_val, LOCKMODE lockmode)
+{
+ relopt_ternary *newoption;
+
+ newoption =
+ init_ternary_reloption(kinds, name, desc, default_val, lockmode);
+
+ add_reloption((relopt_gen *) newoption);
+}
+
+/*
+ * add_local_ternary_reloption
+ * Add a new ternary local reloption
+ *
+ * 'offset' is offset of ternary-typed field.
+ */
+void
+add_local_ternary_reloption(local_relopts *relopts, const char *name,
+ const char *desc, pg_ternary default_val,
+ int offset)
+{
+ relopt_ternary *newoption;
+
+ newoption =
+ init_ternary_reloption(RELOPT_KIND_LOCAL, name, desc, default_val, 0);
+
+ add_local_reloption(relopts, (relopt_gen *) newoption, offset);
+}
/*
* init_real_reloption
@@ -1626,6 +1705,19 @@ parse_one_reloption(relopt_value *option, char *text_str, int text_len,
option->gen->name, value)));
}
break;
+ case RELOPT_TYPE_TERNARY:
+ {
+ bool b;
+
+ parsed = parse_bool(value, &b);
+ option->values.ternary_val = b ? TERNARY_TRUE : TERNARY_FALSE;
+ if (validate && !parsed)
+ ereport(ERROR,
+ (errcode(ERRCODE_INVALID_PARAMETER_VALUE),
+ errmsg("invalid value for ternary option \"%s\": %s",
+ option->gen->name, value)));
+ }
+ break;
case RELOPT_TYPE_INT:
{
relopt_int *optint = (relopt_int *) option->gen;
@@ -1789,17 +1881,6 @@ fillRelOptions(void *rdopts, Size basesize,
char *itempos = ((char *) rdopts) + elems[j].offset;
char *string_val;
- /*
- * If isset_offset is provided, store whether the reloption is
- * set there.
- */
- if (elems[j].isset_offset > 0)
- {
- char *setpos = ((char *) rdopts) + elems[j].isset_offset;
-
- *(bool *) setpos = options[i].isset;
- }
-
switch (options[i].gen->type)
{
case RELOPT_TYPE_BOOL:
@@ -1807,6 +1888,11 @@ fillRelOptions(void *rdopts, Size basesize,
options[i].values.bool_val :
((relopt_bool *) options[i].gen)->default_val;
break;
+ case RELOPT_TYPE_TERNARY:
+ *(pg_ternary *) itempos = options[i].isset ?
+ options[i].values.ternary_val :
+ ((relopt_ternary *) options[i].gen)->default_val;
+ break;
case RELOPT_TYPE_INT:
*(int *) itempos = options[i].isset ?
options[i].values.int_val :
@@ -1923,8 +2009,8 @@ default_reloptions(Datum reloptions, bool validate, relopt_kind kind)
offsetof(StdRdOptions, parallel_workers)},
{"vacuum_index_cleanup", RELOPT_TYPE_ENUM,
offsetof(StdRdOptions, vacuum_index_cleanup)},
- {"vacuum_truncate", RELOPT_TYPE_BOOL,
- offsetof(StdRdOptions, vacuum_truncate), offsetof(StdRdOptions, vacuum_truncate_set)},
+ {"vacuum_truncate", RELOPT_TYPE_TERNARY,
+ offsetof(StdRdOptions, vacuum_truncate)},
{"vacuum_max_eager_freeze_failure_rate", RELOPT_TYPE_REAL,
offsetof(StdRdOptions, vacuum_max_eager_freeze_failure_rate)}
};
@@ -2004,7 +2090,6 @@ build_local_reloptions(local_relopts *relopts, Datum options, bool validate)
elems[i].optname = opt->option->name;
elems[i].opttype = opt->option->type;
elems[i].offset = opt->offset;
- elems[i].isset_offset = 0; /* not supported for local relopts yet */
i++;
}
diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c
index aa4fbec143f..696eab9bd97 100644
--- a/src/backend/commands/vacuum.c
+++ b/src/backend/commands/vacuum.c
@@ -2224,9 +2224,9 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams params,
{
StdRdOptions *opts = (StdRdOptions *) rel->rd_options;
- if (opts && opts->vacuum_truncate_set)
+ if (opts && opts->vacuum_truncate != TERNARY_UNSET)
{
- if (opts->vacuum_truncate)
+ if (opts->vacuum_truncate == TERNARY_TRUE)
params.truncate = VACOPTVALUE_ENABLED;
else
params.truncate = VACOPTVALUE_DISABLED;
diff --git a/src/include/access/reloptions.h b/src/include/access/reloptions.h
index 2f08e1b0cf0..dfbef2babf2 100644
--- a/src/include/access/reloptions.h
+++ b/src/include/access/reloptions.h
@@ -29,6 +29,7 @@
typedef enum relopt_type
{
RELOPT_TYPE_BOOL,
+ RELOPT_TYPE_TERNARY, /* on, off, unset */
RELOPT_TYPE_INT,
RELOPT_TYPE_REAL,
RELOPT_TYPE_ENUM,
@@ -80,6 +81,7 @@ typedef struct relopt_value
union
{
bool bool_val;
+ pg_ternary ternary_val;
int int_val;
double real_val;
int enum_val;
@@ -94,6 +96,12 @@ typedef struct relopt_bool
bool default_val;
} relopt_bool;
+typedef struct relopt_ternary
+{
+ relopt_gen gen;
+ int default_val;
+} relopt_ternary;
+
typedef struct relopt_int
{
relopt_gen gen;
@@ -152,19 +160,6 @@ typedef struct
const char *optname; /* option's name */
relopt_type opttype; /* option's datatype */
int offset; /* offset of field in result struct */
-
- /*
- * isset_offset is an optional offset of a field in the result struct that
- * stores whether the option is explicitly set for the relation or if it
- * just picked up the default value. In most cases, this can be
- * accomplished by giving the reloption a special out-of-range default
- * value (e.g., some integer reloptions use -2), but this isn't always
- * possible. For example, a Boolean reloption cannot be given an
- * out-of-range default, so we need another way to discover the source of
- * its value. This offset is only used if given a value greater than
- * zero.
- */
- int isset_offset;
} relopt_parse_elt;
/* Local reloption definition */
@@ -195,6 +190,9 @@ typedef struct local_relopts
extern relopt_kind add_reloption_kind(void);
extern void add_bool_reloption(bits32 kinds, const char *name, const char *desc,
bool default_val, LOCKMODE lockmode);
+extern void add_ternary_reloption(bits32 kinds, const char *name,
+ const char *desc, pg_ternary default_val,
+ LOCKMODE lockmode);
extern void add_int_reloption(bits32 kinds, const char *name, const char *desc,
int default_val, int min_val, int max_val,
LOCKMODE lockmode);
@@ -214,6 +212,9 @@ extern void register_reloptions_validator(local_relopts *relopts,
extern void add_local_bool_reloption(local_relopts *relopts, const char *name,
const char *desc, bool default_val,
int offset);
+extern void add_local_ternary_reloption(local_relopts *relopts,
+ const char *name, const char *desc,
+ pg_ternary default_val, int offset);
extern void add_local_int_reloption(local_relopts *relopts, const char *name,
const char *desc, int default_val,
int min_val, int max_val, int offset);
diff --git a/src/include/postgres.h b/src/include/postgres.h
index 7d93fbce709..47c88780776 100644
--- a/src/include/postgres.h
+++ b/src/include/postgres.h
@@ -543,6 +543,21 @@ Float8GetDatum(float8 X)
* ----------------------------------------------------------------
*/
+/*
+ * pg_ternary
+ * Boolean value with an extra "unset" value
+ *
+ * This enum can be used for values that want to distinguish between true,
+ * false, and unset.
+*/
+
+typedef enum pg_ternary
+{
+ TERNARY_FALSE = 0,
+ TERNARY_TRUE = 1,
+ TERNARY_UNSET = -1
+} pg_ternary;
+
/*
* NON_EXEC_STATIC: It's sometimes useful to define a variable or function
* that is normally static but extern when using EXEC_BACKEND (see
diff --git a/src/include/utils/rel.h b/src/include/utils/rel.h
index d03ab247788..5feb18a5373 100644
--- a/src/include/utils/rel.h
+++ b/src/include/utils/rel.h
@@ -347,8 +347,7 @@ typedef struct StdRdOptions
bool user_catalog_table; /* use as an additional catalog relation */
int parallel_workers; /* max number of parallel workers */
StdRdOptIndexCleanup vacuum_index_cleanup; /* controls index vacuuming */
- bool vacuum_truncate; /* enables vacuum to truncate a relation */
- bool vacuum_truncate_set; /* whether vacuum_truncate is set */
+ pg_ternary vacuum_truncate; /* enables vacuum to truncate a relation */
/*
* Fraction of pages in a relation that vacuum can eagerly scan and fail
diff --git a/src/test/regress/expected/reloptions.out b/src/test/regress/expected/reloptions.out
index 9de19b4e3f1..1c99f79ab01 100644
--- a/src/test/regress/expected/reloptions.out
+++ b/src/test/regress/expected/reloptions.out
@@ -98,6 +98,42 @@ SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass;
{fillfactor=13,autovacuum_enabled=false}
(1 row)
+-- Tests for future (FIXME) ternary options
+-- behave as boolean option: accept unassigned name and truncated value
+DROP TABLE reloptions_test;
+CREATE TABLE reloptions_test(i INT) WITH (vacuum_truncate);
+SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass;
+ reloptions
+------------------------
+ {vacuum_truncate=true}
+(1 row)
+
+DROP TABLE reloptions_test;
+CREATE TABLE reloptions_test(i INT) WITH (vacuum_truncate=FaLS);
+SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass;
+ reloptions
+------------------------
+ {vacuum_truncate=fals}
+(1 row)
+
+-- preferred "true" alias is used when storing
+DROP TABLE reloptions_test;
+CREATE TABLE reloptions_test(i INT) WITH (vacuum_index_cleanup=on);
+SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass;
+ reloptions
+---------------------------
+ {vacuum_index_cleanup=on}
+(1 row)
+
+-- custom "third" value is available
+DROP TABLE reloptions_test;
+CREATE TABLE reloptions_test(i INT) WITH (vacuum_index_cleanup=auto);
+SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass;
+ reloptions
+-----------------------------
+ {vacuum_index_cleanup=auto}
+(1 row)
+
-- Test vacuum_truncate option
DROP TABLE reloptions_test;
CREATE TEMP TABLE reloptions_test(i INT NOT NULL, j text)
diff --git a/src/test/regress/sql/reloptions.sql b/src/test/regress/sql/reloptions.sql
index 24fbe0b478d..f5980dafcbc 100644
--- a/src/test/regress/sql/reloptions.sql
+++ b/src/test/regress/sql/reloptions.sql
@@ -59,6 +59,27 @@ UPDATE pg_class
ALTER TABLE reloptions_test RESET (illegal_option);
SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass;
+-- Tests for future (FIXME) ternary options
+
+-- behave as boolean option: accept unassigned name and truncated value
+DROP TABLE reloptions_test;
+CREATE TABLE reloptions_test(i INT) WITH (vacuum_truncate);
+SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass;
+
+DROP TABLE reloptions_test;
+CREATE TABLE reloptions_test(i INT) WITH (vacuum_truncate=FaLS);
+SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass;
+
+-- preferred "true" alias is used when storing
+DROP TABLE reloptions_test;
+CREATE TABLE reloptions_test(i INT) WITH (vacuum_index_cleanup=on);
+SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass;
+
+-- custom "third" value is available
+DROP TABLE reloptions_test;
+CREATE TABLE reloptions_test(i INT) WITH (vacuum_index_cleanup=auto);
+SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass;
+
-- Test vacuum_truncate option
DROP TABLE reloptions_test;
diff --git a/src/tools/pgindent/typedefs.list b/src/tools/pgindent/typedefs.list
index 3f3a888fd0e..1c8610fd46c 100644
--- a/src/tools/pgindent/typedefs.list
+++ b/src/tools/pgindent/typedefs.list
@@ -3952,6 +3952,7 @@ pg_sha512_ctx
pg_snapshot
pg_special_case
pg_stack_base_t
+pg_ternary
pg_time_t
pg_time_usec_t
pg_tz
@@ -4079,6 +4080,7 @@ relopt_kind
relopt_parse_elt
relopt_real
relopt_string
+relopt_ternary
relopt_type
relopt_value
relopts_validator
--
2.47.3
--c36t3fndxhkzbl5z--
^ permalink raw reply [nested|flat] 868+ messages in thread
* [PATCH v3] Introduce ternary reloptions
@ 2026-01-16 15:04 Álvaro Herrera <[email protected]>
0 siblings, 0 replies; 868+ messages in thread
From: Álvaro Herrera @ 2026-01-16 15:04 UTC (permalink / raw)
Introduce ternary reloption as a replacement for current
`vacuum_truncate` implementation. Remove the `vacuum_truncate_set`
separate flag and use TERNARY_UNSET instead.
This could also be used for other options such as `vacuum_index_cleanup`
and `buffering`, but lets get the scaffolding in first.
Discussion: https://postgr.es/m/3474141.usfYGdeWWP@thinkpad-pgpro
---
src/backend/access/common/reloptions.c | 137 ++++++++++++++++++-----
src/backend/commands/vacuum.c | 4 +-
src/include/access/reloptions.h | 27 ++---
src/include/postgres.h | 15 +++
src/include/utils/rel.h | 3 +-
src/test/regress/expected/reloptions.out | 36 ++++++
src/test/regress/sql/reloptions.sql | 21 ++++
src/tools/pgindent/typedefs.list | 2 +
8 files changed, 202 insertions(+), 43 deletions(-)
diff --git a/src/backend/access/common/reloptions.c b/src/backend/access/common/reloptions.c
index 0b83f98ed5f..6f1f577581a 100644
--- a/src/backend/access/common/reloptions.c
+++ b/src/backend/access/common/reloptions.c
@@ -40,9 +40,9 @@
*
* To add an option:
*
- * (i) decide on a type (bool, integer, real, enum, string), name, default
- * value, upper and lower bounds (if applicable); for strings, consider a
- * validation routine.
+ * (i) decide on a type (bool, ternary, integer, real, enum, string), name,
+ * default value, upper and lower bounds (if applicable); for strings,
+ * consider a validation routine.
* (ii) add a record below (or use add_<type>_reloption).
* (iii) add it to the appropriate options struct (perhaps StdRdOptions)
* (iv) add it to the appropriate handling routine (perhaps
@@ -147,15 +147,6 @@ static relopt_bool boolRelOpts[] =
},
false
},
- {
- {
- "vacuum_truncate",
- "Enables vacuum to truncate empty pages at the end of this table",
- RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
- ShareUpdateExclusiveLock
- },
- true
- },
{
{
"deduplicate_items",
@@ -170,6 +161,25 @@ static relopt_bool boolRelOpts[] =
{{NULL}}
};
+static relopt_ternary ternaryRelOpts[] =
+{
+ {
+ {
+ "vacuum_truncate",
+ "Enables vacuum to truncate empty pages at the end of this table",
+ RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
+ ShareUpdateExclusiveLock
+ },
+ TERNARY_UNSET
+ },
+ /* list terminator */
+ {
+ {
+ NULL
+ }
+ }
+};
+
static relopt_int intRelOpts[] =
{
{
@@ -609,6 +619,13 @@ initialize_reloptions(void)
boolRelOpts[i].gen.lockmode));
j++;
}
+ for (i = 0; ternaryRelOpts[i].gen.name; i++)
+ {
+ Assert(DoLockModesConflict(ternaryRelOpts[i].gen.lockmode,
+ ternaryRelOpts[i].gen.lockmode));
+ j++;
+ }
+
for (i = 0; intRelOpts[i].gen.name; i++)
{
Assert(DoLockModesConflict(intRelOpts[i].gen.lockmode,
@@ -649,6 +666,14 @@ initialize_reloptions(void)
j++;
}
+ for (i = 0; ternaryRelOpts[i].gen.name; i++)
+ {
+ relOpts[j] = &ternaryRelOpts[i].gen;
+ relOpts[j]->type = RELOPT_TYPE_TERNARY;
+ relOpts[j]->namelen = strlen(relOpts[j]->name);
+ j++;
+ }
+
for (i = 0; intRelOpts[i].gen.name; i++)
{
relOpts[j] = &intRelOpts[i].gen;
@@ -809,6 +834,9 @@ allocate_reloption(bits32 kinds, int type, const char *name, const char *desc,
case RELOPT_TYPE_BOOL:
size = sizeof(relopt_bool);
break;
+ case RELOPT_TYPE_TERNARY:
+ size = sizeof(relopt_ternary);
+ break;
case RELOPT_TYPE_INT:
size = sizeof(relopt_int);
break;
@@ -892,6 +920,57 @@ add_local_bool_reloption(local_relopts *relopts, const char *name,
add_local_reloption(relopts, (relopt_gen *) newoption, offset);
}
+/*
+ * init_ternary_reloption
+ * Allocate and initialize a new ternary reloption
+ */
+static relopt_ternary *
+init_ternary_reloption(bits32 kinds, const char *name, const char *desc,
+ pg_ternary default_val, LOCKMODE lockmode)
+{
+ relopt_ternary *newoption;
+
+ newoption = (relopt_ternary *)
+ allocate_reloption(kinds, RELOPT_TYPE_TERNARY, name, desc, lockmode);
+ newoption->default_val = default_val;
+
+ return newoption;
+}
+
+/*
+ * add_ternary_reloption
+ * Add a new ternary reloption
+ */
+void
+add_ternary_reloption(bits32 kinds, const char *name, const char *desc,
+ pg_ternary default_val, LOCKMODE lockmode)
+{
+ relopt_ternary *newoption;
+
+ newoption =
+ init_ternary_reloption(kinds, name, desc, default_val, lockmode);
+
+ add_reloption((relopt_gen *) newoption);
+}
+
+/*
+ * add_local_ternary_reloption
+ * Add a new ternary local reloption
+ *
+ * 'offset' is offset of ternary-typed field.
+ */
+void
+add_local_ternary_reloption(local_relopts *relopts, const char *name,
+ const char *desc, pg_ternary default_val,
+ int offset)
+{
+ relopt_ternary *newoption;
+
+ newoption =
+ init_ternary_reloption(RELOPT_KIND_LOCAL, name, desc, default_val, 0);
+
+ add_local_reloption(relopts, (relopt_gen *) newoption, offset);
+}
/*
* init_real_reloption
@@ -1626,6 +1705,19 @@ parse_one_reloption(relopt_value *option, char *text_str, int text_len,
option->gen->name, value)));
}
break;
+ case RELOPT_TYPE_TERNARY:
+ {
+ bool b;
+
+ parsed = parse_bool(value, &b);
+ option->values.ternary_val = b ? TERNARY_TRUE : TERNARY_FALSE;
+ if (validate && !parsed)
+ ereport(ERROR,
+ (errcode(ERRCODE_INVALID_PARAMETER_VALUE),
+ errmsg("invalid value for ternary option \"%s\": %s",
+ option->gen->name, value)));
+ }
+ break;
case RELOPT_TYPE_INT:
{
relopt_int *optint = (relopt_int *) option->gen;
@@ -1789,17 +1881,6 @@ fillRelOptions(void *rdopts, Size basesize,
char *itempos = ((char *) rdopts) + elems[j].offset;
char *string_val;
- /*
- * If isset_offset is provided, store whether the reloption is
- * set there.
- */
- if (elems[j].isset_offset > 0)
- {
- char *setpos = ((char *) rdopts) + elems[j].isset_offset;
-
- *(bool *) setpos = options[i].isset;
- }
-
switch (options[i].gen->type)
{
case RELOPT_TYPE_BOOL:
@@ -1807,6 +1888,11 @@ fillRelOptions(void *rdopts, Size basesize,
options[i].values.bool_val :
((relopt_bool *) options[i].gen)->default_val;
break;
+ case RELOPT_TYPE_TERNARY:
+ *(pg_ternary *) itempos = options[i].isset ?
+ options[i].values.ternary_val :
+ ((relopt_ternary *) options[i].gen)->default_val;
+ break;
case RELOPT_TYPE_INT:
*(int *) itempos = options[i].isset ?
options[i].values.int_val :
@@ -1923,8 +2009,8 @@ default_reloptions(Datum reloptions, bool validate, relopt_kind kind)
offsetof(StdRdOptions, parallel_workers)},
{"vacuum_index_cleanup", RELOPT_TYPE_ENUM,
offsetof(StdRdOptions, vacuum_index_cleanup)},
- {"vacuum_truncate", RELOPT_TYPE_BOOL,
- offsetof(StdRdOptions, vacuum_truncate), offsetof(StdRdOptions, vacuum_truncate_set)},
+ {"vacuum_truncate", RELOPT_TYPE_TERNARY,
+ offsetof(StdRdOptions, vacuum_truncate)},
{"vacuum_max_eager_freeze_failure_rate", RELOPT_TYPE_REAL,
offsetof(StdRdOptions, vacuum_max_eager_freeze_failure_rate)}
};
@@ -2004,7 +2090,6 @@ build_local_reloptions(local_relopts *relopts, Datum options, bool validate)
elems[i].optname = opt->option->name;
elems[i].opttype = opt->option->type;
elems[i].offset = opt->offset;
- elems[i].isset_offset = 0; /* not supported for local relopts yet */
i++;
}
diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c
index aa4fbec143f..696eab9bd97 100644
--- a/src/backend/commands/vacuum.c
+++ b/src/backend/commands/vacuum.c
@@ -2224,9 +2224,9 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams params,
{
StdRdOptions *opts = (StdRdOptions *) rel->rd_options;
- if (opts && opts->vacuum_truncate_set)
+ if (opts && opts->vacuum_truncate != TERNARY_UNSET)
{
- if (opts->vacuum_truncate)
+ if (opts->vacuum_truncate == TERNARY_TRUE)
params.truncate = VACOPTVALUE_ENABLED;
else
params.truncate = VACOPTVALUE_DISABLED;
diff --git a/src/include/access/reloptions.h b/src/include/access/reloptions.h
index 2f08e1b0cf0..dfbef2babf2 100644
--- a/src/include/access/reloptions.h
+++ b/src/include/access/reloptions.h
@@ -29,6 +29,7 @@
typedef enum relopt_type
{
RELOPT_TYPE_BOOL,
+ RELOPT_TYPE_TERNARY, /* on, off, unset */
RELOPT_TYPE_INT,
RELOPT_TYPE_REAL,
RELOPT_TYPE_ENUM,
@@ -80,6 +81,7 @@ typedef struct relopt_value
union
{
bool bool_val;
+ pg_ternary ternary_val;
int int_val;
double real_val;
int enum_val;
@@ -94,6 +96,12 @@ typedef struct relopt_bool
bool default_val;
} relopt_bool;
+typedef struct relopt_ternary
+{
+ relopt_gen gen;
+ int default_val;
+} relopt_ternary;
+
typedef struct relopt_int
{
relopt_gen gen;
@@ -152,19 +160,6 @@ typedef struct
const char *optname; /* option's name */
relopt_type opttype; /* option's datatype */
int offset; /* offset of field in result struct */
-
- /*
- * isset_offset is an optional offset of a field in the result struct that
- * stores whether the option is explicitly set for the relation or if it
- * just picked up the default value. In most cases, this can be
- * accomplished by giving the reloption a special out-of-range default
- * value (e.g., some integer reloptions use -2), but this isn't always
- * possible. For example, a Boolean reloption cannot be given an
- * out-of-range default, so we need another way to discover the source of
- * its value. This offset is only used if given a value greater than
- * zero.
- */
- int isset_offset;
} relopt_parse_elt;
/* Local reloption definition */
@@ -195,6 +190,9 @@ typedef struct local_relopts
extern relopt_kind add_reloption_kind(void);
extern void add_bool_reloption(bits32 kinds, const char *name, const char *desc,
bool default_val, LOCKMODE lockmode);
+extern void add_ternary_reloption(bits32 kinds, const char *name,
+ const char *desc, pg_ternary default_val,
+ LOCKMODE lockmode);
extern void add_int_reloption(bits32 kinds, const char *name, const char *desc,
int default_val, int min_val, int max_val,
LOCKMODE lockmode);
@@ -214,6 +212,9 @@ extern void register_reloptions_validator(local_relopts *relopts,
extern void add_local_bool_reloption(local_relopts *relopts, const char *name,
const char *desc, bool default_val,
int offset);
+extern void add_local_ternary_reloption(local_relopts *relopts,
+ const char *name, const char *desc,
+ pg_ternary default_val, int offset);
extern void add_local_int_reloption(local_relopts *relopts, const char *name,
const char *desc, int default_val,
int min_val, int max_val, int offset);
diff --git a/src/include/postgres.h b/src/include/postgres.h
index 7d93fbce709..47c88780776 100644
--- a/src/include/postgres.h
+++ b/src/include/postgres.h
@@ -543,6 +543,21 @@ Float8GetDatum(float8 X)
* ----------------------------------------------------------------
*/
+/*
+ * pg_ternary
+ * Boolean value with an extra "unset" value
+ *
+ * This enum can be used for values that want to distinguish between true,
+ * false, and unset.
+*/
+
+typedef enum pg_ternary
+{
+ TERNARY_FALSE = 0,
+ TERNARY_TRUE = 1,
+ TERNARY_UNSET = -1
+} pg_ternary;
+
/*
* NON_EXEC_STATIC: It's sometimes useful to define a variable or function
* that is normally static but extern when using EXEC_BACKEND (see
diff --git a/src/include/utils/rel.h b/src/include/utils/rel.h
index d03ab247788..5feb18a5373 100644
--- a/src/include/utils/rel.h
+++ b/src/include/utils/rel.h
@@ -347,8 +347,7 @@ typedef struct StdRdOptions
bool user_catalog_table; /* use as an additional catalog relation */
int parallel_workers; /* max number of parallel workers */
StdRdOptIndexCleanup vacuum_index_cleanup; /* controls index vacuuming */
- bool vacuum_truncate; /* enables vacuum to truncate a relation */
- bool vacuum_truncate_set; /* whether vacuum_truncate is set */
+ pg_ternary vacuum_truncate; /* enables vacuum to truncate a relation */
/*
* Fraction of pages in a relation that vacuum can eagerly scan and fail
diff --git a/src/test/regress/expected/reloptions.out b/src/test/regress/expected/reloptions.out
index 9de19b4e3f1..1c99f79ab01 100644
--- a/src/test/regress/expected/reloptions.out
+++ b/src/test/regress/expected/reloptions.out
@@ -98,6 +98,42 @@ SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass;
{fillfactor=13,autovacuum_enabled=false}
(1 row)
+-- Tests for future (FIXME) ternary options
+-- behave as boolean option: accept unassigned name and truncated value
+DROP TABLE reloptions_test;
+CREATE TABLE reloptions_test(i INT) WITH (vacuum_truncate);
+SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass;
+ reloptions
+------------------------
+ {vacuum_truncate=true}
+(1 row)
+
+DROP TABLE reloptions_test;
+CREATE TABLE reloptions_test(i INT) WITH (vacuum_truncate=FaLS);
+SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass;
+ reloptions
+------------------------
+ {vacuum_truncate=fals}
+(1 row)
+
+-- preferred "true" alias is used when storing
+DROP TABLE reloptions_test;
+CREATE TABLE reloptions_test(i INT) WITH (vacuum_index_cleanup=on);
+SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass;
+ reloptions
+---------------------------
+ {vacuum_index_cleanup=on}
+(1 row)
+
+-- custom "third" value is available
+DROP TABLE reloptions_test;
+CREATE TABLE reloptions_test(i INT) WITH (vacuum_index_cleanup=auto);
+SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass;
+ reloptions
+-----------------------------
+ {vacuum_index_cleanup=auto}
+(1 row)
+
-- Test vacuum_truncate option
DROP TABLE reloptions_test;
CREATE TEMP TABLE reloptions_test(i INT NOT NULL, j text)
diff --git a/src/test/regress/sql/reloptions.sql b/src/test/regress/sql/reloptions.sql
index 24fbe0b478d..f5980dafcbc 100644
--- a/src/test/regress/sql/reloptions.sql
+++ b/src/test/regress/sql/reloptions.sql
@@ -59,6 +59,27 @@ UPDATE pg_class
ALTER TABLE reloptions_test RESET (illegal_option);
SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass;
+-- Tests for future (FIXME) ternary options
+
+-- behave as boolean option: accept unassigned name and truncated value
+DROP TABLE reloptions_test;
+CREATE TABLE reloptions_test(i INT) WITH (vacuum_truncate);
+SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass;
+
+DROP TABLE reloptions_test;
+CREATE TABLE reloptions_test(i INT) WITH (vacuum_truncate=FaLS);
+SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass;
+
+-- preferred "true" alias is used when storing
+DROP TABLE reloptions_test;
+CREATE TABLE reloptions_test(i INT) WITH (vacuum_index_cleanup=on);
+SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass;
+
+-- custom "third" value is available
+DROP TABLE reloptions_test;
+CREATE TABLE reloptions_test(i INT) WITH (vacuum_index_cleanup=auto);
+SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass;
+
-- Test vacuum_truncate option
DROP TABLE reloptions_test;
diff --git a/src/tools/pgindent/typedefs.list b/src/tools/pgindent/typedefs.list
index 3f3a888fd0e..1c8610fd46c 100644
--- a/src/tools/pgindent/typedefs.list
+++ b/src/tools/pgindent/typedefs.list
@@ -3952,6 +3952,7 @@ pg_sha512_ctx
pg_snapshot
pg_special_case
pg_stack_base_t
+pg_ternary
pg_time_t
pg_time_usec_t
pg_tz
@@ -4079,6 +4080,7 @@ relopt_kind
relopt_parse_elt
relopt_real
relopt_string
+relopt_ternary
relopt_type
relopt_value
relopts_validator
--
2.47.3
--c36t3fndxhkzbl5z--
^ permalink raw reply [nested|flat] 868+ messages in thread
* [PATCH v3] Introduce ternary reloptions
@ 2026-01-16 15:04 Álvaro Herrera <[email protected]>
0 siblings, 0 replies; 868+ messages in thread
From: Álvaro Herrera @ 2026-01-16 15:04 UTC (permalink / raw)
Introduce ternary reloption as a replacement for current
`vacuum_truncate` implementation. Remove the `vacuum_truncate_set`
separate flag and use TERNARY_UNSET instead.
This could also be used for other options such as `vacuum_index_cleanup`
and `buffering`, but lets get the scaffolding in first.
Discussion: https://postgr.es/m/3474141.usfYGdeWWP@thinkpad-pgpro
---
src/backend/access/common/reloptions.c | 137 ++++++++++++++++++-----
src/backend/commands/vacuum.c | 4 +-
src/include/access/reloptions.h | 27 ++---
src/include/postgres.h | 15 +++
src/include/utils/rel.h | 3 +-
src/test/regress/expected/reloptions.out | 36 ++++++
src/test/regress/sql/reloptions.sql | 21 ++++
src/tools/pgindent/typedefs.list | 2 +
8 files changed, 202 insertions(+), 43 deletions(-)
diff --git a/src/backend/access/common/reloptions.c b/src/backend/access/common/reloptions.c
index 0b83f98ed5f..6f1f577581a 100644
--- a/src/backend/access/common/reloptions.c
+++ b/src/backend/access/common/reloptions.c
@@ -40,9 +40,9 @@
*
* To add an option:
*
- * (i) decide on a type (bool, integer, real, enum, string), name, default
- * value, upper and lower bounds (if applicable); for strings, consider a
- * validation routine.
+ * (i) decide on a type (bool, ternary, integer, real, enum, string), name,
+ * default value, upper and lower bounds (if applicable); for strings,
+ * consider a validation routine.
* (ii) add a record below (or use add_<type>_reloption).
* (iii) add it to the appropriate options struct (perhaps StdRdOptions)
* (iv) add it to the appropriate handling routine (perhaps
@@ -147,15 +147,6 @@ static relopt_bool boolRelOpts[] =
},
false
},
- {
- {
- "vacuum_truncate",
- "Enables vacuum to truncate empty pages at the end of this table",
- RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
- ShareUpdateExclusiveLock
- },
- true
- },
{
{
"deduplicate_items",
@@ -170,6 +161,25 @@ static relopt_bool boolRelOpts[] =
{{NULL}}
};
+static relopt_ternary ternaryRelOpts[] =
+{
+ {
+ {
+ "vacuum_truncate",
+ "Enables vacuum to truncate empty pages at the end of this table",
+ RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
+ ShareUpdateExclusiveLock
+ },
+ TERNARY_UNSET
+ },
+ /* list terminator */
+ {
+ {
+ NULL
+ }
+ }
+};
+
static relopt_int intRelOpts[] =
{
{
@@ -609,6 +619,13 @@ initialize_reloptions(void)
boolRelOpts[i].gen.lockmode));
j++;
}
+ for (i = 0; ternaryRelOpts[i].gen.name; i++)
+ {
+ Assert(DoLockModesConflict(ternaryRelOpts[i].gen.lockmode,
+ ternaryRelOpts[i].gen.lockmode));
+ j++;
+ }
+
for (i = 0; intRelOpts[i].gen.name; i++)
{
Assert(DoLockModesConflict(intRelOpts[i].gen.lockmode,
@@ -649,6 +666,14 @@ initialize_reloptions(void)
j++;
}
+ for (i = 0; ternaryRelOpts[i].gen.name; i++)
+ {
+ relOpts[j] = &ternaryRelOpts[i].gen;
+ relOpts[j]->type = RELOPT_TYPE_TERNARY;
+ relOpts[j]->namelen = strlen(relOpts[j]->name);
+ j++;
+ }
+
for (i = 0; intRelOpts[i].gen.name; i++)
{
relOpts[j] = &intRelOpts[i].gen;
@@ -809,6 +834,9 @@ allocate_reloption(bits32 kinds, int type, const char *name, const char *desc,
case RELOPT_TYPE_BOOL:
size = sizeof(relopt_bool);
break;
+ case RELOPT_TYPE_TERNARY:
+ size = sizeof(relopt_ternary);
+ break;
case RELOPT_TYPE_INT:
size = sizeof(relopt_int);
break;
@@ -892,6 +920,57 @@ add_local_bool_reloption(local_relopts *relopts, const char *name,
add_local_reloption(relopts, (relopt_gen *) newoption, offset);
}
+/*
+ * init_ternary_reloption
+ * Allocate and initialize a new ternary reloption
+ */
+static relopt_ternary *
+init_ternary_reloption(bits32 kinds, const char *name, const char *desc,
+ pg_ternary default_val, LOCKMODE lockmode)
+{
+ relopt_ternary *newoption;
+
+ newoption = (relopt_ternary *)
+ allocate_reloption(kinds, RELOPT_TYPE_TERNARY, name, desc, lockmode);
+ newoption->default_val = default_val;
+
+ return newoption;
+}
+
+/*
+ * add_ternary_reloption
+ * Add a new ternary reloption
+ */
+void
+add_ternary_reloption(bits32 kinds, const char *name, const char *desc,
+ pg_ternary default_val, LOCKMODE lockmode)
+{
+ relopt_ternary *newoption;
+
+ newoption =
+ init_ternary_reloption(kinds, name, desc, default_val, lockmode);
+
+ add_reloption((relopt_gen *) newoption);
+}
+
+/*
+ * add_local_ternary_reloption
+ * Add a new ternary local reloption
+ *
+ * 'offset' is offset of ternary-typed field.
+ */
+void
+add_local_ternary_reloption(local_relopts *relopts, const char *name,
+ const char *desc, pg_ternary default_val,
+ int offset)
+{
+ relopt_ternary *newoption;
+
+ newoption =
+ init_ternary_reloption(RELOPT_KIND_LOCAL, name, desc, default_val, 0);
+
+ add_local_reloption(relopts, (relopt_gen *) newoption, offset);
+}
/*
* init_real_reloption
@@ -1626,6 +1705,19 @@ parse_one_reloption(relopt_value *option, char *text_str, int text_len,
option->gen->name, value)));
}
break;
+ case RELOPT_TYPE_TERNARY:
+ {
+ bool b;
+
+ parsed = parse_bool(value, &b);
+ option->values.ternary_val = b ? TERNARY_TRUE : TERNARY_FALSE;
+ if (validate && !parsed)
+ ereport(ERROR,
+ (errcode(ERRCODE_INVALID_PARAMETER_VALUE),
+ errmsg("invalid value for ternary option \"%s\": %s",
+ option->gen->name, value)));
+ }
+ break;
case RELOPT_TYPE_INT:
{
relopt_int *optint = (relopt_int *) option->gen;
@@ -1789,17 +1881,6 @@ fillRelOptions(void *rdopts, Size basesize,
char *itempos = ((char *) rdopts) + elems[j].offset;
char *string_val;
- /*
- * If isset_offset is provided, store whether the reloption is
- * set there.
- */
- if (elems[j].isset_offset > 0)
- {
- char *setpos = ((char *) rdopts) + elems[j].isset_offset;
-
- *(bool *) setpos = options[i].isset;
- }
-
switch (options[i].gen->type)
{
case RELOPT_TYPE_BOOL:
@@ -1807,6 +1888,11 @@ fillRelOptions(void *rdopts, Size basesize,
options[i].values.bool_val :
((relopt_bool *) options[i].gen)->default_val;
break;
+ case RELOPT_TYPE_TERNARY:
+ *(pg_ternary *) itempos = options[i].isset ?
+ options[i].values.ternary_val :
+ ((relopt_ternary *) options[i].gen)->default_val;
+ break;
case RELOPT_TYPE_INT:
*(int *) itempos = options[i].isset ?
options[i].values.int_val :
@@ -1923,8 +2009,8 @@ default_reloptions(Datum reloptions, bool validate, relopt_kind kind)
offsetof(StdRdOptions, parallel_workers)},
{"vacuum_index_cleanup", RELOPT_TYPE_ENUM,
offsetof(StdRdOptions, vacuum_index_cleanup)},
- {"vacuum_truncate", RELOPT_TYPE_BOOL,
- offsetof(StdRdOptions, vacuum_truncate), offsetof(StdRdOptions, vacuum_truncate_set)},
+ {"vacuum_truncate", RELOPT_TYPE_TERNARY,
+ offsetof(StdRdOptions, vacuum_truncate)},
{"vacuum_max_eager_freeze_failure_rate", RELOPT_TYPE_REAL,
offsetof(StdRdOptions, vacuum_max_eager_freeze_failure_rate)}
};
@@ -2004,7 +2090,6 @@ build_local_reloptions(local_relopts *relopts, Datum options, bool validate)
elems[i].optname = opt->option->name;
elems[i].opttype = opt->option->type;
elems[i].offset = opt->offset;
- elems[i].isset_offset = 0; /* not supported for local relopts yet */
i++;
}
diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c
index aa4fbec143f..696eab9bd97 100644
--- a/src/backend/commands/vacuum.c
+++ b/src/backend/commands/vacuum.c
@@ -2224,9 +2224,9 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams params,
{
StdRdOptions *opts = (StdRdOptions *) rel->rd_options;
- if (opts && opts->vacuum_truncate_set)
+ if (opts && opts->vacuum_truncate != TERNARY_UNSET)
{
- if (opts->vacuum_truncate)
+ if (opts->vacuum_truncate == TERNARY_TRUE)
params.truncate = VACOPTVALUE_ENABLED;
else
params.truncate = VACOPTVALUE_DISABLED;
diff --git a/src/include/access/reloptions.h b/src/include/access/reloptions.h
index 2f08e1b0cf0..dfbef2babf2 100644
--- a/src/include/access/reloptions.h
+++ b/src/include/access/reloptions.h
@@ -29,6 +29,7 @@
typedef enum relopt_type
{
RELOPT_TYPE_BOOL,
+ RELOPT_TYPE_TERNARY, /* on, off, unset */
RELOPT_TYPE_INT,
RELOPT_TYPE_REAL,
RELOPT_TYPE_ENUM,
@@ -80,6 +81,7 @@ typedef struct relopt_value
union
{
bool bool_val;
+ pg_ternary ternary_val;
int int_val;
double real_val;
int enum_val;
@@ -94,6 +96,12 @@ typedef struct relopt_bool
bool default_val;
} relopt_bool;
+typedef struct relopt_ternary
+{
+ relopt_gen gen;
+ int default_val;
+} relopt_ternary;
+
typedef struct relopt_int
{
relopt_gen gen;
@@ -152,19 +160,6 @@ typedef struct
const char *optname; /* option's name */
relopt_type opttype; /* option's datatype */
int offset; /* offset of field in result struct */
-
- /*
- * isset_offset is an optional offset of a field in the result struct that
- * stores whether the option is explicitly set for the relation or if it
- * just picked up the default value. In most cases, this can be
- * accomplished by giving the reloption a special out-of-range default
- * value (e.g., some integer reloptions use -2), but this isn't always
- * possible. For example, a Boolean reloption cannot be given an
- * out-of-range default, so we need another way to discover the source of
- * its value. This offset is only used if given a value greater than
- * zero.
- */
- int isset_offset;
} relopt_parse_elt;
/* Local reloption definition */
@@ -195,6 +190,9 @@ typedef struct local_relopts
extern relopt_kind add_reloption_kind(void);
extern void add_bool_reloption(bits32 kinds, const char *name, const char *desc,
bool default_val, LOCKMODE lockmode);
+extern void add_ternary_reloption(bits32 kinds, const char *name,
+ const char *desc, pg_ternary default_val,
+ LOCKMODE lockmode);
extern void add_int_reloption(bits32 kinds, const char *name, const char *desc,
int default_val, int min_val, int max_val,
LOCKMODE lockmode);
@@ -214,6 +212,9 @@ extern void register_reloptions_validator(local_relopts *relopts,
extern void add_local_bool_reloption(local_relopts *relopts, const char *name,
const char *desc, bool default_val,
int offset);
+extern void add_local_ternary_reloption(local_relopts *relopts,
+ const char *name, const char *desc,
+ pg_ternary default_val, int offset);
extern void add_local_int_reloption(local_relopts *relopts, const char *name,
const char *desc, int default_val,
int min_val, int max_val, int offset);
diff --git a/src/include/postgres.h b/src/include/postgres.h
index 7d93fbce709..47c88780776 100644
--- a/src/include/postgres.h
+++ b/src/include/postgres.h
@@ -543,6 +543,21 @@ Float8GetDatum(float8 X)
* ----------------------------------------------------------------
*/
+/*
+ * pg_ternary
+ * Boolean value with an extra "unset" value
+ *
+ * This enum can be used for values that want to distinguish between true,
+ * false, and unset.
+*/
+
+typedef enum pg_ternary
+{
+ TERNARY_FALSE = 0,
+ TERNARY_TRUE = 1,
+ TERNARY_UNSET = -1
+} pg_ternary;
+
/*
* NON_EXEC_STATIC: It's sometimes useful to define a variable or function
* that is normally static but extern when using EXEC_BACKEND (see
diff --git a/src/include/utils/rel.h b/src/include/utils/rel.h
index d03ab247788..5feb18a5373 100644
--- a/src/include/utils/rel.h
+++ b/src/include/utils/rel.h
@@ -347,8 +347,7 @@ typedef struct StdRdOptions
bool user_catalog_table; /* use as an additional catalog relation */
int parallel_workers; /* max number of parallel workers */
StdRdOptIndexCleanup vacuum_index_cleanup; /* controls index vacuuming */
- bool vacuum_truncate; /* enables vacuum to truncate a relation */
- bool vacuum_truncate_set; /* whether vacuum_truncate is set */
+ pg_ternary vacuum_truncate; /* enables vacuum to truncate a relation */
/*
* Fraction of pages in a relation that vacuum can eagerly scan and fail
diff --git a/src/test/regress/expected/reloptions.out b/src/test/regress/expected/reloptions.out
index 9de19b4e3f1..1c99f79ab01 100644
--- a/src/test/regress/expected/reloptions.out
+++ b/src/test/regress/expected/reloptions.out
@@ -98,6 +98,42 @@ SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass;
{fillfactor=13,autovacuum_enabled=false}
(1 row)
+-- Tests for future (FIXME) ternary options
+-- behave as boolean option: accept unassigned name and truncated value
+DROP TABLE reloptions_test;
+CREATE TABLE reloptions_test(i INT) WITH (vacuum_truncate);
+SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass;
+ reloptions
+------------------------
+ {vacuum_truncate=true}
+(1 row)
+
+DROP TABLE reloptions_test;
+CREATE TABLE reloptions_test(i INT) WITH (vacuum_truncate=FaLS);
+SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass;
+ reloptions
+------------------------
+ {vacuum_truncate=fals}
+(1 row)
+
+-- preferred "true" alias is used when storing
+DROP TABLE reloptions_test;
+CREATE TABLE reloptions_test(i INT) WITH (vacuum_index_cleanup=on);
+SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass;
+ reloptions
+---------------------------
+ {vacuum_index_cleanup=on}
+(1 row)
+
+-- custom "third" value is available
+DROP TABLE reloptions_test;
+CREATE TABLE reloptions_test(i INT) WITH (vacuum_index_cleanup=auto);
+SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass;
+ reloptions
+-----------------------------
+ {vacuum_index_cleanup=auto}
+(1 row)
+
-- Test vacuum_truncate option
DROP TABLE reloptions_test;
CREATE TEMP TABLE reloptions_test(i INT NOT NULL, j text)
diff --git a/src/test/regress/sql/reloptions.sql b/src/test/regress/sql/reloptions.sql
index 24fbe0b478d..f5980dafcbc 100644
--- a/src/test/regress/sql/reloptions.sql
+++ b/src/test/regress/sql/reloptions.sql
@@ -59,6 +59,27 @@ UPDATE pg_class
ALTER TABLE reloptions_test RESET (illegal_option);
SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass;
+-- Tests for future (FIXME) ternary options
+
+-- behave as boolean option: accept unassigned name and truncated value
+DROP TABLE reloptions_test;
+CREATE TABLE reloptions_test(i INT) WITH (vacuum_truncate);
+SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass;
+
+DROP TABLE reloptions_test;
+CREATE TABLE reloptions_test(i INT) WITH (vacuum_truncate=FaLS);
+SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass;
+
+-- preferred "true" alias is used when storing
+DROP TABLE reloptions_test;
+CREATE TABLE reloptions_test(i INT) WITH (vacuum_index_cleanup=on);
+SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass;
+
+-- custom "third" value is available
+DROP TABLE reloptions_test;
+CREATE TABLE reloptions_test(i INT) WITH (vacuum_index_cleanup=auto);
+SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass;
+
-- Test vacuum_truncate option
DROP TABLE reloptions_test;
diff --git a/src/tools/pgindent/typedefs.list b/src/tools/pgindent/typedefs.list
index 3f3a888fd0e..1c8610fd46c 100644
--- a/src/tools/pgindent/typedefs.list
+++ b/src/tools/pgindent/typedefs.list
@@ -3952,6 +3952,7 @@ pg_sha512_ctx
pg_snapshot
pg_special_case
pg_stack_base_t
+pg_ternary
pg_time_t
pg_time_usec_t
pg_tz
@@ -4079,6 +4080,7 @@ relopt_kind
relopt_parse_elt
relopt_real
relopt_string
+relopt_ternary
relopt_type
relopt_value
relopts_validator
--
2.47.3
--c36t3fndxhkzbl5z--
^ permalink raw reply [nested|flat] 868+ messages in thread
* [PATCH v3] Introduce ternary reloptions
@ 2026-01-16 15:04 Álvaro Herrera <[email protected]>
0 siblings, 0 replies; 868+ messages in thread
From: Álvaro Herrera @ 2026-01-16 15:04 UTC (permalink / raw)
Introduce ternary reloption as a replacement for current
`vacuum_truncate` implementation. Remove the `vacuum_truncate_set`
separate flag and use TERNARY_UNSET instead.
This could also be used for other options such as `vacuum_index_cleanup`
and `buffering`, but lets get the scaffolding in first.
Discussion: https://postgr.es/m/3474141.usfYGdeWWP@thinkpad-pgpro
---
src/backend/access/common/reloptions.c | 137 ++++++++++++++++++-----
src/backend/commands/vacuum.c | 4 +-
src/include/access/reloptions.h | 27 ++---
src/include/postgres.h | 15 +++
src/include/utils/rel.h | 3 +-
src/test/regress/expected/reloptions.out | 36 ++++++
src/test/regress/sql/reloptions.sql | 21 ++++
src/tools/pgindent/typedefs.list | 2 +
8 files changed, 202 insertions(+), 43 deletions(-)
diff --git a/src/backend/access/common/reloptions.c b/src/backend/access/common/reloptions.c
index 0b83f98ed5f..6f1f577581a 100644
--- a/src/backend/access/common/reloptions.c
+++ b/src/backend/access/common/reloptions.c
@@ -40,9 +40,9 @@
*
* To add an option:
*
- * (i) decide on a type (bool, integer, real, enum, string), name, default
- * value, upper and lower bounds (if applicable); for strings, consider a
- * validation routine.
+ * (i) decide on a type (bool, ternary, integer, real, enum, string), name,
+ * default value, upper and lower bounds (if applicable); for strings,
+ * consider a validation routine.
* (ii) add a record below (or use add_<type>_reloption).
* (iii) add it to the appropriate options struct (perhaps StdRdOptions)
* (iv) add it to the appropriate handling routine (perhaps
@@ -147,15 +147,6 @@ static relopt_bool boolRelOpts[] =
},
false
},
- {
- {
- "vacuum_truncate",
- "Enables vacuum to truncate empty pages at the end of this table",
- RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
- ShareUpdateExclusiveLock
- },
- true
- },
{
{
"deduplicate_items",
@@ -170,6 +161,25 @@ static relopt_bool boolRelOpts[] =
{{NULL}}
};
+static relopt_ternary ternaryRelOpts[] =
+{
+ {
+ {
+ "vacuum_truncate",
+ "Enables vacuum to truncate empty pages at the end of this table",
+ RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
+ ShareUpdateExclusiveLock
+ },
+ TERNARY_UNSET
+ },
+ /* list terminator */
+ {
+ {
+ NULL
+ }
+ }
+};
+
static relopt_int intRelOpts[] =
{
{
@@ -609,6 +619,13 @@ initialize_reloptions(void)
boolRelOpts[i].gen.lockmode));
j++;
}
+ for (i = 0; ternaryRelOpts[i].gen.name; i++)
+ {
+ Assert(DoLockModesConflict(ternaryRelOpts[i].gen.lockmode,
+ ternaryRelOpts[i].gen.lockmode));
+ j++;
+ }
+
for (i = 0; intRelOpts[i].gen.name; i++)
{
Assert(DoLockModesConflict(intRelOpts[i].gen.lockmode,
@@ -649,6 +666,14 @@ initialize_reloptions(void)
j++;
}
+ for (i = 0; ternaryRelOpts[i].gen.name; i++)
+ {
+ relOpts[j] = &ternaryRelOpts[i].gen;
+ relOpts[j]->type = RELOPT_TYPE_TERNARY;
+ relOpts[j]->namelen = strlen(relOpts[j]->name);
+ j++;
+ }
+
for (i = 0; intRelOpts[i].gen.name; i++)
{
relOpts[j] = &intRelOpts[i].gen;
@@ -809,6 +834,9 @@ allocate_reloption(bits32 kinds, int type, const char *name, const char *desc,
case RELOPT_TYPE_BOOL:
size = sizeof(relopt_bool);
break;
+ case RELOPT_TYPE_TERNARY:
+ size = sizeof(relopt_ternary);
+ break;
case RELOPT_TYPE_INT:
size = sizeof(relopt_int);
break;
@@ -892,6 +920,57 @@ add_local_bool_reloption(local_relopts *relopts, const char *name,
add_local_reloption(relopts, (relopt_gen *) newoption, offset);
}
+/*
+ * init_ternary_reloption
+ * Allocate and initialize a new ternary reloption
+ */
+static relopt_ternary *
+init_ternary_reloption(bits32 kinds, const char *name, const char *desc,
+ pg_ternary default_val, LOCKMODE lockmode)
+{
+ relopt_ternary *newoption;
+
+ newoption = (relopt_ternary *)
+ allocate_reloption(kinds, RELOPT_TYPE_TERNARY, name, desc, lockmode);
+ newoption->default_val = default_val;
+
+ return newoption;
+}
+
+/*
+ * add_ternary_reloption
+ * Add a new ternary reloption
+ */
+void
+add_ternary_reloption(bits32 kinds, const char *name, const char *desc,
+ pg_ternary default_val, LOCKMODE lockmode)
+{
+ relopt_ternary *newoption;
+
+ newoption =
+ init_ternary_reloption(kinds, name, desc, default_val, lockmode);
+
+ add_reloption((relopt_gen *) newoption);
+}
+
+/*
+ * add_local_ternary_reloption
+ * Add a new ternary local reloption
+ *
+ * 'offset' is offset of ternary-typed field.
+ */
+void
+add_local_ternary_reloption(local_relopts *relopts, const char *name,
+ const char *desc, pg_ternary default_val,
+ int offset)
+{
+ relopt_ternary *newoption;
+
+ newoption =
+ init_ternary_reloption(RELOPT_KIND_LOCAL, name, desc, default_val, 0);
+
+ add_local_reloption(relopts, (relopt_gen *) newoption, offset);
+}
/*
* init_real_reloption
@@ -1626,6 +1705,19 @@ parse_one_reloption(relopt_value *option, char *text_str, int text_len,
option->gen->name, value)));
}
break;
+ case RELOPT_TYPE_TERNARY:
+ {
+ bool b;
+
+ parsed = parse_bool(value, &b);
+ option->values.ternary_val = b ? TERNARY_TRUE : TERNARY_FALSE;
+ if (validate && !parsed)
+ ereport(ERROR,
+ (errcode(ERRCODE_INVALID_PARAMETER_VALUE),
+ errmsg("invalid value for ternary option \"%s\": %s",
+ option->gen->name, value)));
+ }
+ break;
case RELOPT_TYPE_INT:
{
relopt_int *optint = (relopt_int *) option->gen;
@@ -1789,17 +1881,6 @@ fillRelOptions(void *rdopts, Size basesize,
char *itempos = ((char *) rdopts) + elems[j].offset;
char *string_val;
- /*
- * If isset_offset is provided, store whether the reloption is
- * set there.
- */
- if (elems[j].isset_offset > 0)
- {
- char *setpos = ((char *) rdopts) + elems[j].isset_offset;
-
- *(bool *) setpos = options[i].isset;
- }
-
switch (options[i].gen->type)
{
case RELOPT_TYPE_BOOL:
@@ -1807,6 +1888,11 @@ fillRelOptions(void *rdopts, Size basesize,
options[i].values.bool_val :
((relopt_bool *) options[i].gen)->default_val;
break;
+ case RELOPT_TYPE_TERNARY:
+ *(pg_ternary *) itempos = options[i].isset ?
+ options[i].values.ternary_val :
+ ((relopt_ternary *) options[i].gen)->default_val;
+ break;
case RELOPT_TYPE_INT:
*(int *) itempos = options[i].isset ?
options[i].values.int_val :
@@ -1923,8 +2009,8 @@ default_reloptions(Datum reloptions, bool validate, relopt_kind kind)
offsetof(StdRdOptions, parallel_workers)},
{"vacuum_index_cleanup", RELOPT_TYPE_ENUM,
offsetof(StdRdOptions, vacuum_index_cleanup)},
- {"vacuum_truncate", RELOPT_TYPE_BOOL,
- offsetof(StdRdOptions, vacuum_truncate), offsetof(StdRdOptions, vacuum_truncate_set)},
+ {"vacuum_truncate", RELOPT_TYPE_TERNARY,
+ offsetof(StdRdOptions, vacuum_truncate)},
{"vacuum_max_eager_freeze_failure_rate", RELOPT_TYPE_REAL,
offsetof(StdRdOptions, vacuum_max_eager_freeze_failure_rate)}
};
@@ -2004,7 +2090,6 @@ build_local_reloptions(local_relopts *relopts, Datum options, bool validate)
elems[i].optname = opt->option->name;
elems[i].opttype = opt->option->type;
elems[i].offset = opt->offset;
- elems[i].isset_offset = 0; /* not supported for local relopts yet */
i++;
}
diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c
index aa4fbec143f..696eab9bd97 100644
--- a/src/backend/commands/vacuum.c
+++ b/src/backend/commands/vacuum.c
@@ -2224,9 +2224,9 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams params,
{
StdRdOptions *opts = (StdRdOptions *) rel->rd_options;
- if (opts && opts->vacuum_truncate_set)
+ if (opts && opts->vacuum_truncate != TERNARY_UNSET)
{
- if (opts->vacuum_truncate)
+ if (opts->vacuum_truncate == TERNARY_TRUE)
params.truncate = VACOPTVALUE_ENABLED;
else
params.truncate = VACOPTVALUE_DISABLED;
diff --git a/src/include/access/reloptions.h b/src/include/access/reloptions.h
index 2f08e1b0cf0..dfbef2babf2 100644
--- a/src/include/access/reloptions.h
+++ b/src/include/access/reloptions.h
@@ -29,6 +29,7 @@
typedef enum relopt_type
{
RELOPT_TYPE_BOOL,
+ RELOPT_TYPE_TERNARY, /* on, off, unset */
RELOPT_TYPE_INT,
RELOPT_TYPE_REAL,
RELOPT_TYPE_ENUM,
@@ -80,6 +81,7 @@ typedef struct relopt_value
union
{
bool bool_val;
+ pg_ternary ternary_val;
int int_val;
double real_val;
int enum_val;
@@ -94,6 +96,12 @@ typedef struct relopt_bool
bool default_val;
} relopt_bool;
+typedef struct relopt_ternary
+{
+ relopt_gen gen;
+ int default_val;
+} relopt_ternary;
+
typedef struct relopt_int
{
relopt_gen gen;
@@ -152,19 +160,6 @@ typedef struct
const char *optname; /* option's name */
relopt_type opttype; /* option's datatype */
int offset; /* offset of field in result struct */
-
- /*
- * isset_offset is an optional offset of a field in the result struct that
- * stores whether the option is explicitly set for the relation or if it
- * just picked up the default value. In most cases, this can be
- * accomplished by giving the reloption a special out-of-range default
- * value (e.g., some integer reloptions use -2), but this isn't always
- * possible. For example, a Boolean reloption cannot be given an
- * out-of-range default, so we need another way to discover the source of
- * its value. This offset is only used if given a value greater than
- * zero.
- */
- int isset_offset;
} relopt_parse_elt;
/* Local reloption definition */
@@ -195,6 +190,9 @@ typedef struct local_relopts
extern relopt_kind add_reloption_kind(void);
extern void add_bool_reloption(bits32 kinds, const char *name, const char *desc,
bool default_val, LOCKMODE lockmode);
+extern void add_ternary_reloption(bits32 kinds, const char *name,
+ const char *desc, pg_ternary default_val,
+ LOCKMODE lockmode);
extern void add_int_reloption(bits32 kinds, const char *name, const char *desc,
int default_val, int min_val, int max_val,
LOCKMODE lockmode);
@@ -214,6 +212,9 @@ extern void register_reloptions_validator(local_relopts *relopts,
extern void add_local_bool_reloption(local_relopts *relopts, const char *name,
const char *desc, bool default_val,
int offset);
+extern void add_local_ternary_reloption(local_relopts *relopts,
+ const char *name, const char *desc,
+ pg_ternary default_val, int offset);
extern void add_local_int_reloption(local_relopts *relopts, const char *name,
const char *desc, int default_val,
int min_val, int max_val, int offset);
diff --git a/src/include/postgres.h b/src/include/postgres.h
index 7d93fbce709..47c88780776 100644
--- a/src/include/postgres.h
+++ b/src/include/postgres.h
@@ -543,6 +543,21 @@ Float8GetDatum(float8 X)
* ----------------------------------------------------------------
*/
+/*
+ * pg_ternary
+ * Boolean value with an extra "unset" value
+ *
+ * This enum can be used for values that want to distinguish between true,
+ * false, and unset.
+*/
+
+typedef enum pg_ternary
+{
+ TERNARY_FALSE = 0,
+ TERNARY_TRUE = 1,
+ TERNARY_UNSET = -1
+} pg_ternary;
+
/*
* NON_EXEC_STATIC: It's sometimes useful to define a variable or function
* that is normally static but extern when using EXEC_BACKEND (see
diff --git a/src/include/utils/rel.h b/src/include/utils/rel.h
index d03ab247788..5feb18a5373 100644
--- a/src/include/utils/rel.h
+++ b/src/include/utils/rel.h
@@ -347,8 +347,7 @@ typedef struct StdRdOptions
bool user_catalog_table; /* use as an additional catalog relation */
int parallel_workers; /* max number of parallel workers */
StdRdOptIndexCleanup vacuum_index_cleanup; /* controls index vacuuming */
- bool vacuum_truncate; /* enables vacuum to truncate a relation */
- bool vacuum_truncate_set; /* whether vacuum_truncate is set */
+ pg_ternary vacuum_truncate; /* enables vacuum to truncate a relation */
/*
* Fraction of pages in a relation that vacuum can eagerly scan and fail
diff --git a/src/test/regress/expected/reloptions.out b/src/test/regress/expected/reloptions.out
index 9de19b4e3f1..1c99f79ab01 100644
--- a/src/test/regress/expected/reloptions.out
+++ b/src/test/regress/expected/reloptions.out
@@ -98,6 +98,42 @@ SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass;
{fillfactor=13,autovacuum_enabled=false}
(1 row)
+-- Tests for future (FIXME) ternary options
+-- behave as boolean option: accept unassigned name and truncated value
+DROP TABLE reloptions_test;
+CREATE TABLE reloptions_test(i INT) WITH (vacuum_truncate);
+SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass;
+ reloptions
+------------------------
+ {vacuum_truncate=true}
+(1 row)
+
+DROP TABLE reloptions_test;
+CREATE TABLE reloptions_test(i INT) WITH (vacuum_truncate=FaLS);
+SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass;
+ reloptions
+------------------------
+ {vacuum_truncate=fals}
+(1 row)
+
+-- preferred "true" alias is used when storing
+DROP TABLE reloptions_test;
+CREATE TABLE reloptions_test(i INT) WITH (vacuum_index_cleanup=on);
+SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass;
+ reloptions
+---------------------------
+ {vacuum_index_cleanup=on}
+(1 row)
+
+-- custom "third" value is available
+DROP TABLE reloptions_test;
+CREATE TABLE reloptions_test(i INT) WITH (vacuum_index_cleanup=auto);
+SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass;
+ reloptions
+-----------------------------
+ {vacuum_index_cleanup=auto}
+(1 row)
+
-- Test vacuum_truncate option
DROP TABLE reloptions_test;
CREATE TEMP TABLE reloptions_test(i INT NOT NULL, j text)
diff --git a/src/test/regress/sql/reloptions.sql b/src/test/regress/sql/reloptions.sql
index 24fbe0b478d..f5980dafcbc 100644
--- a/src/test/regress/sql/reloptions.sql
+++ b/src/test/regress/sql/reloptions.sql
@@ -59,6 +59,27 @@ UPDATE pg_class
ALTER TABLE reloptions_test RESET (illegal_option);
SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass;
+-- Tests for future (FIXME) ternary options
+
+-- behave as boolean option: accept unassigned name and truncated value
+DROP TABLE reloptions_test;
+CREATE TABLE reloptions_test(i INT) WITH (vacuum_truncate);
+SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass;
+
+DROP TABLE reloptions_test;
+CREATE TABLE reloptions_test(i INT) WITH (vacuum_truncate=FaLS);
+SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass;
+
+-- preferred "true" alias is used when storing
+DROP TABLE reloptions_test;
+CREATE TABLE reloptions_test(i INT) WITH (vacuum_index_cleanup=on);
+SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass;
+
+-- custom "third" value is available
+DROP TABLE reloptions_test;
+CREATE TABLE reloptions_test(i INT) WITH (vacuum_index_cleanup=auto);
+SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass;
+
-- Test vacuum_truncate option
DROP TABLE reloptions_test;
diff --git a/src/tools/pgindent/typedefs.list b/src/tools/pgindent/typedefs.list
index 3f3a888fd0e..1c8610fd46c 100644
--- a/src/tools/pgindent/typedefs.list
+++ b/src/tools/pgindent/typedefs.list
@@ -3952,6 +3952,7 @@ pg_sha512_ctx
pg_snapshot
pg_special_case
pg_stack_base_t
+pg_ternary
pg_time_t
pg_time_usec_t
pg_tz
@@ -4079,6 +4080,7 @@ relopt_kind
relopt_parse_elt
relopt_real
relopt_string
+relopt_ternary
relopt_type
relopt_value
relopts_validator
--
2.47.3
--c36t3fndxhkzbl5z--
^ permalink raw reply [nested|flat] 868+ messages in thread
* [PATCH v3] Introduce ternary reloptions
@ 2026-01-16 15:04 Álvaro Herrera <[email protected]>
0 siblings, 0 replies; 868+ messages in thread
From: Álvaro Herrera @ 2026-01-16 15:04 UTC (permalink / raw)
Introduce ternary reloption as a replacement for current
`vacuum_truncate` implementation. Remove the `vacuum_truncate_set`
separate flag and use TERNARY_UNSET instead.
This could also be used for other options such as `vacuum_index_cleanup`
and `buffering`, but lets get the scaffolding in first.
Discussion: https://postgr.es/m/3474141.usfYGdeWWP@thinkpad-pgpro
---
src/backend/access/common/reloptions.c | 137 ++++++++++++++++++-----
src/backend/commands/vacuum.c | 4 +-
src/include/access/reloptions.h | 27 ++---
src/include/postgres.h | 15 +++
src/include/utils/rel.h | 3 +-
src/test/regress/expected/reloptions.out | 36 ++++++
src/test/regress/sql/reloptions.sql | 21 ++++
src/tools/pgindent/typedefs.list | 2 +
8 files changed, 202 insertions(+), 43 deletions(-)
diff --git a/src/backend/access/common/reloptions.c b/src/backend/access/common/reloptions.c
index 0b83f98ed5f..6f1f577581a 100644
--- a/src/backend/access/common/reloptions.c
+++ b/src/backend/access/common/reloptions.c
@@ -40,9 +40,9 @@
*
* To add an option:
*
- * (i) decide on a type (bool, integer, real, enum, string), name, default
- * value, upper and lower bounds (if applicable); for strings, consider a
- * validation routine.
+ * (i) decide on a type (bool, ternary, integer, real, enum, string), name,
+ * default value, upper and lower bounds (if applicable); for strings,
+ * consider a validation routine.
* (ii) add a record below (or use add_<type>_reloption).
* (iii) add it to the appropriate options struct (perhaps StdRdOptions)
* (iv) add it to the appropriate handling routine (perhaps
@@ -147,15 +147,6 @@ static relopt_bool boolRelOpts[] =
},
false
},
- {
- {
- "vacuum_truncate",
- "Enables vacuum to truncate empty pages at the end of this table",
- RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
- ShareUpdateExclusiveLock
- },
- true
- },
{
{
"deduplicate_items",
@@ -170,6 +161,25 @@ static relopt_bool boolRelOpts[] =
{{NULL}}
};
+static relopt_ternary ternaryRelOpts[] =
+{
+ {
+ {
+ "vacuum_truncate",
+ "Enables vacuum to truncate empty pages at the end of this table",
+ RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
+ ShareUpdateExclusiveLock
+ },
+ TERNARY_UNSET
+ },
+ /* list terminator */
+ {
+ {
+ NULL
+ }
+ }
+};
+
static relopt_int intRelOpts[] =
{
{
@@ -609,6 +619,13 @@ initialize_reloptions(void)
boolRelOpts[i].gen.lockmode));
j++;
}
+ for (i = 0; ternaryRelOpts[i].gen.name; i++)
+ {
+ Assert(DoLockModesConflict(ternaryRelOpts[i].gen.lockmode,
+ ternaryRelOpts[i].gen.lockmode));
+ j++;
+ }
+
for (i = 0; intRelOpts[i].gen.name; i++)
{
Assert(DoLockModesConflict(intRelOpts[i].gen.lockmode,
@@ -649,6 +666,14 @@ initialize_reloptions(void)
j++;
}
+ for (i = 0; ternaryRelOpts[i].gen.name; i++)
+ {
+ relOpts[j] = &ternaryRelOpts[i].gen;
+ relOpts[j]->type = RELOPT_TYPE_TERNARY;
+ relOpts[j]->namelen = strlen(relOpts[j]->name);
+ j++;
+ }
+
for (i = 0; intRelOpts[i].gen.name; i++)
{
relOpts[j] = &intRelOpts[i].gen;
@@ -809,6 +834,9 @@ allocate_reloption(bits32 kinds, int type, const char *name, const char *desc,
case RELOPT_TYPE_BOOL:
size = sizeof(relopt_bool);
break;
+ case RELOPT_TYPE_TERNARY:
+ size = sizeof(relopt_ternary);
+ break;
case RELOPT_TYPE_INT:
size = sizeof(relopt_int);
break;
@@ -892,6 +920,57 @@ add_local_bool_reloption(local_relopts *relopts, const char *name,
add_local_reloption(relopts, (relopt_gen *) newoption, offset);
}
+/*
+ * init_ternary_reloption
+ * Allocate and initialize a new ternary reloption
+ */
+static relopt_ternary *
+init_ternary_reloption(bits32 kinds, const char *name, const char *desc,
+ pg_ternary default_val, LOCKMODE lockmode)
+{
+ relopt_ternary *newoption;
+
+ newoption = (relopt_ternary *)
+ allocate_reloption(kinds, RELOPT_TYPE_TERNARY, name, desc, lockmode);
+ newoption->default_val = default_val;
+
+ return newoption;
+}
+
+/*
+ * add_ternary_reloption
+ * Add a new ternary reloption
+ */
+void
+add_ternary_reloption(bits32 kinds, const char *name, const char *desc,
+ pg_ternary default_val, LOCKMODE lockmode)
+{
+ relopt_ternary *newoption;
+
+ newoption =
+ init_ternary_reloption(kinds, name, desc, default_val, lockmode);
+
+ add_reloption((relopt_gen *) newoption);
+}
+
+/*
+ * add_local_ternary_reloption
+ * Add a new ternary local reloption
+ *
+ * 'offset' is offset of ternary-typed field.
+ */
+void
+add_local_ternary_reloption(local_relopts *relopts, const char *name,
+ const char *desc, pg_ternary default_val,
+ int offset)
+{
+ relopt_ternary *newoption;
+
+ newoption =
+ init_ternary_reloption(RELOPT_KIND_LOCAL, name, desc, default_val, 0);
+
+ add_local_reloption(relopts, (relopt_gen *) newoption, offset);
+}
/*
* init_real_reloption
@@ -1626,6 +1705,19 @@ parse_one_reloption(relopt_value *option, char *text_str, int text_len,
option->gen->name, value)));
}
break;
+ case RELOPT_TYPE_TERNARY:
+ {
+ bool b;
+
+ parsed = parse_bool(value, &b);
+ option->values.ternary_val = b ? TERNARY_TRUE : TERNARY_FALSE;
+ if (validate && !parsed)
+ ereport(ERROR,
+ (errcode(ERRCODE_INVALID_PARAMETER_VALUE),
+ errmsg("invalid value for ternary option \"%s\": %s",
+ option->gen->name, value)));
+ }
+ break;
case RELOPT_TYPE_INT:
{
relopt_int *optint = (relopt_int *) option->gen;
@@ -1789,17 +1881,6 @@ fillRelOptions(void *rdopts, Size basesize,
char *itempos = ((char *) rdopts) + elems[j].offset;
char *string_val;
- /*
- * If isset_offset is provided, store whether the reloption is
- * set there.
- */
- if (elems[j].isset_offset > 0)
- {
- char *setpos = ((char *) rdopts) + elems[j].isset_offset;
-
- *(bool *) setpos = options[i].isset;
- }
-
switch (options[i].gen->type)
{
case RELOPT_TYPE_BOOL:
@@ -1807,6 +1888,11 @@ fillRelOptions(void *rdopts, Size basesize,
options[i].values.bool_val :
((relopt_bool *) options[i].gen)->default_val;
break;
+ case RELOPT_TYPE_TERNARY:
+ *(pg_ternary *) itempos = options[i].isset ?
+ options[i].values.ternary_val :
+ ((relopt_ternary *) options[i].gen)->default_val;
+ break;
case RELOPT_TYPE_INT:
*(int *) itempos = options[i].isset ?
options[i].values.int_val :
@@ -1923,8 +2009,8 @@ default_reloptions(Datum reloptions, bool validate, relopt_kind kind)
offsetof(StdRdOptions, parallel_workers)},
{"vacuum_index_cleanup", RELOPT_TYPE_ENUM,
offsetof(StdRdOptions, vacuum_index_cleanup)},
- {"vacuum_truncate", RELOPT_TYPE_BOOL,
- offsetof(StdRdOptions, vacuum_truncate), offsetof(StdRdOptions, vacuum_truncate_set)},
+ {"vacuum_truncate", RELOPT_TYPE_TERNARY,
+ offsetof(StdRdOptions, vacuum_truncate)},
{"vacuum_max_eager_freeze_failure_rate", RELOPT_TYPE_REAL,
offsetof(StdRdOptions, vacuum_max_eager_freeze_failure_rate)}
};
@@ -2004,7 +2090,6 @@ build_local_reloptions(local_relopts *relopts, Datum options, bool validate)
elems[i].optname = opt->option->name;
elems[i].opttype = opt->option->type;
elems[i].offset = opt->offset;
- elems[i].isset_offset = 0; /* not supported for local relopts yet */
i++;
}
diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c
index aa4fbec143f..696eab9bd97 100644
--- a/src/backend/commands/vacuum.c
+++ b/src/backend/commands/vacuum.c
@@ -2224,9 +2224,9 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams params,
{
StdRdOptions *opts = (StdRdOptions *) rel->rd_options;
- if (opts && opts->vacuum_truncate_set)
+ if (opts && opts->vacuum_truncate != TERNARY_UNSET)
{
- if (opts->vacuum_truncate)
+ if (opts->vacuum_truncate == TERNARY_TRUE)
params.truncate = VACOPTVALUE_ENABLED;
else
params.truncate = VACOPTVALUE_DISABLED;
diff --git a/src/include/access/reloptions.h b/src/include/access/reloptions.h
index 2f08e1b0cf0..dfbef2babf2 100644
--- a/src/include/access/reloptions.h
+++ b/src/include/access/reloptions.h
@@ -29,6 +29,7 @@
typedef enum relopt_type
{
RELOPT_TYPE_BOOL,
+ RELOPT_TYPE_TERNARY, /* on, off, unset */
RELOPT_TYPE_INT,
RELOPT_TYPE_REAL,
RELOPT_TYPE_ENUM,
@@ -80,6 +81,7 @@ typedef struct relopt_value
union
{
bool bool_val;
+ pg_ternary ternary_val;
int int_val;
double real_val;
int enum_val;
@@ -94,6 +96,12 @@ typedef struct relopt_bool
bool default_val;
} relopt_bool;
+typedef struct relopt_ternary
+{
+ relopt_gen gen;
+ int default_val;
+} relopt_ternary;
+
typedef struct relopt_int
{
relopt_gen gen;
@@ -152,19 +160,6 @@ typedef struct
const char *optname; /* option's name */
relopt_type opttype; /* option's datatype */
int offset; /* offset of field in result struct */
-
- /*
- * isset_offset is an optional offset of a field in the result struct that
- * stores whether the option is explicitly set for the relation or if it
- * just picked up the default value. In most cases, this can be
- * accomplished by giving the reloption a special out-of-range default
- * value (e.g., some integer reloptions use -2), but this isn't always
- * possible. For example, a Boolean reloption cannot be given an
- * out-of-range default, so we need another way to discover the source of
- * its value. This offset is only used if given a value greater than
- * zero.
- */
- int isset_offset;
} relopt_parse_elt;
/* Local reloption definition */
@@ -195,6 +190,9 @@ typedef struct local_relopts
extern relopt_kind add_reloption_kind(void);
extern void add_bool_reloption(bits32 kinds, const char *name, const char *desc,
bool default_val, LOCKMODE lockmode);
+extern void add_ternary_reloption(bits32 kinds, const char *name,
+ const char *desc, pg_ternary default_val,
+ LOCKMODE lockmode);
extern void add_int_reloption(bits32 kinds, const char *name, const char *desc,
int default_val, int min_val, int max_val,
LOCKMODE lockmode);
@@ -214,6 +212,9 @@ extern void register_reloptions_validator(local_relopts *relopts,
extern void add_local_bool_reloption(local_relopts *relopts, const char *name,
const char *desc, bool default_val,
int offset);
+extern void add_local_ternary_reloption(local_relopts *relopts,
+ const char *name, const char *desc,
+ pg_ternary default_val, int offset);
extern void add_local_int_reloption(local_relopts *relopts, const char *name,
const char *desc, int default_val,
int min_val, int max_val, int offset);
diff --git a/src/include/postgres.h b/src/include/postgres.h
index 7d93fbce709..47c88780776 100644
--- a/src/include/postgres.h
+++ b/src/include/postgres.h
@@ -543,6 +543,21 @@ Float8GetDatum(float8 X)
* ----------------------------------------------------------------
*/
+/*
+ * pg_ternary
+ * Boolean value with an extra "unset" value
+ *
+ * This enum can be used for values that want to distinguish between true,
+ * false, and unset.
+*/
+
+typedef enum pg_ternary
+{
+ TERNARY_FALSE = 0,
+ TERNARY_TRUE = 1,
+ TERNARY_UNSET = -1
+} pg_ternary;
+
/*
* NON_EXEC_STATIC: It's sometimes useful to define a variable or function
* that is normally static but extern when using EXEC_BACKEND (see
diff --git a/src/include/utils/rel.h b/src/include/utils/rel.h
index d03ab247788..5feb18a5373 100644
--- a/src/include/utils/rel.h
+++ b/src/include/utils/rel.h
@@ -347,8 +347,7 @@ typedef struct StdRdOptions
bool user_catalog_table; /* use as an additional catalog relation */
int parallel_workers; /* max number of parallel workers */
StdRdOptIndexCleanup vacuum_index_cleanup; /* controls index vacuuming */
- bool vacuum_truncate; /* enables vacuum to truncate a relation */
- bool vacuum_truncate_set; /* whether vacuum_truncate is set */
+ pg_ternary vacuum_truncate; /* enables vacuum to truncate a relation */
/*
* Fraction of pages in a relation that vacuum can eagerly scan and fail
diff --git a/src/test/regress/expected/reloptions.out b/src/test/regress/expected/reloptions.out
index 9de19b4e3f1..1c99f79ab01 100644
--- a/src/test/regress/expected/reloptions.out
+++ b/src/test/regress/expected/reloptions.out
@@ -98,6 +98,42 @@ SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass;
{fillfactor=13,autovacuum_enabled=false}
(1 row)
+-- Tests for future (FIXME) ternary options
+-- behave as boolean option: accept unassigned name and truncated value
+DROP TABLE reloptions_test;
+CREATE TABLE reloptions_test(i INT) WITH (vacuum_truncate);
+SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass;
+ reloptions
+------------------------
+ {vacuum_truncate=true}
+(1 row)
+
+DROP TABLE reloptions_test;
+CREATE TABLE reloptions_test(i INT) WITH (vacuum_truncate=FaLS);
+SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass;
+ reloptions
+------------------------
+ {vacuum_truncate=fals}
+(1 row)
+
+-- preferred "true" alias is used when storing
+DROP TABLE reloptions_test;
+CREATE TABLE reloptions_test(i INT) WITH (vacuum_index_cleanup=on);
+SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass;
+ reloptions
+---------------------------
+ {vacuum_index_cleanup=on}
+(1 row)
+
+-- custom "third" value is available
+DROP TABLE reloptions_test;
+CREATE TABLE reloptions_test(i INT) WITH (vacuum_index_cleanup=auto);
+SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass;
+ reloptions
+-----------------------------
+ {vacuum_index_cleanup=auto}
+(1 row)
+
-- Test vacuum_truncate option
DROP TABLE reloptions_test;
CREATE TEMP TABLE reloptions_test(i INT NOT NULL, j text)
diff --git a/src/test/regress/sql/reloptions.sql b/src/test/regress/sql/reloptions.sql
index 24fbe0b478d..f5980dafcbc 100644
--- a/src/test/regress/sql/reloptions.sql
+++ b/src/test/regress/sql/reloptions.sql
@@ -59,6 +59,27 @@ UPDATE pg_class
ALTER TABLE reloptions_test RESET (illegal_option);
SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass;
+-- Tests for future (FIXME) ternary options
+
+-- behave as boolean option: accept unassigned name and truncated value
+DROP TABLE reloptions_test;
+CREATE TABLE reloptions_test(i INT) WITH (vacuum_truncate);
+SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass;
+
+DROP TABLE reloptions_test;
+CREATE TABLE reloptions_test(i INT) WITH (vacuum_truncate=FaLS);
+SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass;
+
+-- preferred "true" alias is used when storing
+DROP TABLE reloptions_test;
+CREATE TABLE reloptions_test(i INT) WITH (vacuum_index_cleanup=on);
+SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass;
+
+-- custom "third" value is available
+DROP TABLE reloptions_test;
+CREATE TABLE reloptions_test(i INT) WITH (vacuum_index_cleanup=auto);
+SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass;
+
-- Test vacuum_truncate option
DROP TABLE reloptions_test;
diff --git a/src/tools/pgindent/typedefs.list b/src/tools/pgindent/typedefs.list
index 3f3a888fd0e..1c8610fd46c 100644
--- a/src/tools/pgindent/typedefs.list
+++ b/src/tools/pgindent/typedefs.list
@@ -3952,6 +3952,7 @@ pg_sha512_ctx
pg_snapshot
pg_special_case
pg_stack_base_t
+pg_ternary
pg_time_t
pg_time_usec_t
pg_tz
@@ -4079,6 +4080,7 @@ relopt_kind
relopt_parse_elt
relopt_real
relopt_string
+relopt_ternary
relopt_type
relopt_value
relopts_validator
--
2.47.3
--c36t3fndxhkzbl5z--
^ permalink raw reply [nested|flat] 868+ messages in thread
* [PATCH v3] Introduce ternary reloptions
@ 2026-01-16 15:04 Álvaro Herrera <[email protected]>
0 siblings, 0 replies; 868+ messages in thread
From: Álvaro Herrera @ 2026-01-16 15:04 UTC (permalink / raw)
Introduce ternary reloption as a replacement for current
`vacuum_truncate` implementation. Remove the `vacuum_truncate_set`
separate flag and use TERNARY_UNSET instead.
This could also be used for other options such as `vacuum_index_cleanup`
and `buffering`, but lets get the scaffolding in first.
Discussion: https://postgr.es/m/3474141.usfYGdeWWP@thinkpad-pgpro
---
src/backend/access/common/reloptions.c | 137 ++++++++++++++++++-----
src/backend/commands/vacuum.c | 4 +-
src/include/access/reloptions.h | 27 ++---
src/include/postgres.h | 15 +++
src/include/utils/rel.h | 3 +-
src/test/regress/expected/reloptions.out | 36 ++++++
src/test/regress/sql/reloptions.sql | 21 ++++
src/tools/pgindent/typedefs.list | 2 +
8 files changed, 202 insertions(+), 43 deletions(-)
diff --git a/src/backend/access/common/reloptions.c b/src/backend/access/common/reloptions.c
index 0b83f98ed5f..6f1f577581a 100644
--- a/src/backend/access/common/reloptions.c
+++ b/src/backend/access/common/reloptions.c
@@ -40,9 +40,9 @@
*
* To add an option:
*
- * (i) decide on a type (bool, integer, real, enum, string), name, default
- * value, upper and lower bounds (if applicable); for strings, consider a
- * validation routine.
+ * (i) decide on a type (bool, ternary, integer, real, enum, string), name,
+ * default value, upper and lower bounds (if applicable); for strings,
+ * consider a validation routine.
* (ii) add a record below (or use add_<type>_reloption).
* (iii) add it to the appropriate options struct (perhaps StdRdOptions)
* (iv) add it to the appropriate handling routine (perhaps
@@ -147,15 +147,6 @@ static relopt_bool boolRelOpts[] =
},
false
},
- {
- {
- "vacuum_truncate",
- "Enables vacuum to truncate empty pages at the end of this table",
- RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
- ShareUpdateExclusiveLock
- },
- true
- },
{
{
"deduplicate_items",
@@ -170,6 +161,25 @@ static relopt_bool boolRelOpts[] =
{{NULL}}
};
+static relopt_ternary ternaryRelOpts[] =
+{
+ {
+ {
+ "vacuum_truncate",
+ "Enables vacuum to truncate empty pages at the end of this table",
+ RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
+ ShareUpdateExclusiveLock
+ },
+ TERNARY_UNSET
+ },
+ /* list terminator */
+ {
+ {
+ NULL
+ }
+ }
+};
+
static relopt_int intRelOpts[] =
{
{
@@ -609,6 +619,13 @@ initialize_reloptions(void)
boolRelOpts[i].gen.lockmode));
j++;
}
+ for (i = 0; ternaryRelOpts[i].gen.name; i++)
+ {
+ Assert(DoLockModesConflict(ternaryRelOpts[i].gen.lockmode,
+ ternaryRelOpts[i].gen.lockmode));
+ j++;
+ }
+
for (i = 0; intRelOpts[i].gen.name; i++)
{
Assert(DoLockModesConflict(intRelOpts[i].gen.lockmode,
@@ -649,6 +666,14 @@ initialize_reloptions(void)
j++;
}
+ for (i = 0; ternaryRelOpts[i].gen.name; i++)
+ {
+ relOpts[j] = &ternaryRelOpts[i].gen;
+ relOpts[j]->type = RELOPT_TYPE_TERNARY;
+ relOpts[j]->namelen = strlen(relOpts[j]->name);
+ j++;
+ }
+
for (i = 0; intRelOpts[i].gen.name; i++)
{
relOpts[j] = &intRelOpts[i].gen;
@@ -809,6 +834,9 @@ allocate_reloption(bits32 kinds, int type, const char *name, const char *desc,
case RELOPT_TYPE_BOOL:
size = sizeof(relopt_bool);
break;
+ case RELOPT_TYPE_TERNARY:
+ size = sizeof(relopt_ternary);
+ break;
case RELOPT_TYPE_INT:
size = sizeof(relopt_int);
break;
@@ -892,6 +920,57 @@ add_local_bool_reloption(local_relopts *relopts, const char *name,
add_local_reloption(relopts, (relopt_gen *) newoption, offset);
}
+/*
+ * init_ternary_reloption
+ * Allocate and initialize a new ternary reloption
+ */
+static relopt_ternary *
+init_ternary_reloption(bits32 kinds, const char *name, const char *desc,
+ pg_ternary default_val, LOCKMODE lockmode)
+{
+ relopt_ternary *newoption;
+
+ newoption = (relopt_ternary *)
+ allocate_reloption(kinds, RELOPT_TYPE_TERNARY, name, desc, lockmode);
+ newoption->default_val = default_val;
+
+ return newoption;
+}
+
+/*
+ * add_ternary_reloption
+ * Add a new ternary reloption
+ */
+void
+add_ternary_reloption(bits32 kinds, const char *name, const char *desc,
+ pg_ternary default_val, LOCKMODE lockmode)
+{
+ relopt_ternary *newoption;
+
+ newoption =
+ init_ternary_reloption(kinds, name, desc, default_val, lockmode);
+
+ add_reloption((relopt_gen *) newoption);
+}
+
+/*
+ * add_local_ternary_reloption
+ * Add a new ternary local reloption
+ *
+ * 'offset' is offset of ternary-typed field.
+ */
+void
+add_local_ternary_reloption(local_relopts *relopts, const char *name,
+ const char *desc, pg_ternary default_val,
+ int offset)
+{
+ relopt_ternary *newoption;
+
+ newoption =
+ init_ternary_reloption(RELOPT_KIND_LOCAL, name, desc, default_val, 0);
+
+ add_local_reloption(relopts, (relopt_gen *) newoption, offset);
+}
/*
* init_real_reloption
@@ -1626,6 +1705,19 @@ parse_one_reloption(relopt_value *option, char *text_str, int text_len,
option->gen->name, value)));
}
break;
+ case RELOPT_TYPE_TERNARY:
+ {
+ bool b;
+
+ parsed = parse_bool(value, &b);
+ option->values.ternary_val = b ? TERNARY_TRUE : TERNARY_FALSE;
+ if (validate && !parsed)
+ ereport(ERROR,
+ (errcode(ERRCODE_INVALID_PARAMETER_VALUE),
+ errmsg("invalid value for ternary option \"%s\": %s",
+ option->gen->name, value)));
+ }
+ break;
case RELOPT_TYPE_INT:
{
relopt_int *optint = (relopt_int *) option->gen;
@@ -1789,17 +1881,6 @@ fillRelOptions(void *rdopts, Size basesize,
char *itempos = ((char *) rdopts) + elems[j].offset;
char *string_val;
- /*
- * If isset_offset is provided, store whether the reloption is
- * set there.
- */
- if (elems[j].isset_offset > 0)
- {
- char *setpos = ((char *) rdopts) + elems[j].isset_offset;
-
- *(bool *) setpos = options[i].isset;
- }
-
switch (options[i].gen->type)
{
case RELOPT_TYPE_BOOL:
@@ -1807,6 +1888,11 @@ fillRelOptions(void *rdopts, Size basesize,
options[i].values.bool_val :
((relopt_bool *) options[i].gen)->default_val;
break;
+ case RELOPT_TYPE_TERNARY:
+ *(pg_ternary *) itempos = options[i].isset ?
+ options[i].values.ternary_val :
+ ((relopt_ternary *) options[i].gen)->default_val;
+ break;
case RELOPT_TYPE_INT:
*(int *) itempos = options[i].isset ?
options[i].values.int_val :
@@ -1923,8 +2009,8 @@ default_reloptions(Datum reloptions, bool validate, relopt_kind kind)
offsetof(StdRdOptions, parallel_workers)},
{"vacuum_index_cleanup", RELOPT_TYPE_ENUM,
offsetof(StdRdOptions, vacuum_index_cleanup)},
- {"vacuum_truncate", RELOPT_TYPE_BOOL,
- offsetof(StdRdOptions, vacuum_truncate), offsetof(StdRdOptions, vacuum_truncate_set)},
+ {"vacuum_truncate", RELOPT_TYPE_TERNARY,
+ offsetof(StdRdOptions, vacuum_truncate)},
{"vacuum_max_eager_freeze_failure_rate", RELOPT_TYPE_REAL,
offsetof(StdRdOptions, vacuum_max_eager_freeze_failure_rate)}
};
@@ -2004,7 +2090,6 @@ build_local_reloptions(local_relopts *relopts, Datum options, bool validate)
elems[i].optname = opt->option->name;
elems[i].opttype = opt->option->type;
elems[i].offset = opt->offset;
- elems[i].isset_offset = 0; /* not supported for local relopts yet */
i++;
}
diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c
index aa4fbec143f..696eab9bd97 100644
--- a/src/backend/commands/vacuum.c
+++ b/src/backend/commands/vacuum.c
@@ -2224,9 +2224,9 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams params,
{
StdRdOptions *opts = (StdRdOptions *) rel->rd_options;
- if (opts && opts->vacuum_truncate_set)
+ if (opts && opts->vacuum_truncate != TERNARY_UNSET)
{
- if (opts->vacuum_truncate)
+ if (opts->vacuum_truncate == TERNARY_TRUE)
params.truncate = VACOPTVALUE_ENABLED;
else
params.truncate = VACOPTVALUE_DISABLED;
diff --git a/src/include/access/reloptions.h b/src/include/access/reloptions.h
index 2f08e1b0cf0..dfbef2babf2 100644
--- a/src/include/access/reloptions.h
+++ b/src/include/access/reloptions.h
@@ -29,6 +29,7 @@
typedef enum relopt_type
{
RELOPT_TYPE_BOOL,
+ RELOPT_TYPE_TERNARY, /* on, off, unset */
RELOPT_TYPE_INT,
RELOPT_TYPE_REAL,
RELOPT_TYPE_ENUM,
@@ -80,6 +81,7 @@ typedef struct relopt_value
union
{
bool bool_val;
+ pg_ternary ternary_val;
int int_val;
double real_val;
int enum_val;
@@ -94,6 +96,12 @@ typedef struct relopt_bool
bool default_val;
} relopt_bool;
+typedef struct relopt_ternary
+{
+ relopt_gen gen;
+ int default_val;
+} relopt_ternary;
+
typedef struct relopt_int
{
relopt_gen gen;
@@ -152,19 +160,6 @@ typedef struct
const char *optname; /* option's name */
relopt_type opttype; /* option's datatype */
int offset; /* offset of field in result struct */
-
- /*
- * isset_offset is an optional offset of a field in the result struct that
- * stores whether the option is explicitly set for the relation or if it
- * just picked up the default value. In most cases, this can be
- * accomplished by giving the reloption a special out-of-range default
- * value (e.g., some integer reloptions use -2), but this isn't always
- * possible. For example, a Boolean reloption cannot be given an
- * out-of-range default, so we need another way to discover the source of
- * its value. This offset is only used if given a value greater than
- * zero.
- */
- int isset_offset;
} relopt_parse_elt;
/* Local reloption definition */
@@ -195,6 +190,9 @@ typedef struct local_relopts
extern relopt_kind add_reloption_kind(void);
extern void add_bool_reloption(bits32 kinds, const char *name, const char *desc,
bool default_val, LOCKMODE lockmode);
+extern void add_ternary_reloption(bits32 kinds, const char *name,
+ const char *desc, pg_ternary default_val,
+ LOCKMODE lockmode);
extern void add_int_reloption(bits32 kinds, const char *name, const char *desc,
int default_val, int min_val, int max_val,
LOCKMODE lockmode);
@@ -214,6 +212,9 @@ extern void register_reloptions_validator(local_relopts *relopts,
extern void add_local_bool_reloption(local_relopts *relopts, const char *name,
const char *desc, bool default_val,
int offset);
+extern void add_local_ternary_reloption(local_relopts *relopts,
+ const char *name, const char *desc,
+ pg_ternary default_val, int offset);
extern void add_local_int_reloption(local_relopts *relopts, const char *name,
const char *desc, int default_val,
int min_val, int max_val, int offset);
diff --git a/src/include/postgres.h b/src/include/postgres.h
index 7d93fbce709..47c88780776 100644
--- a/src/include/postgres.h
+++ b/src/include/postgres.h
@@ -543,6 +543,21 @@ Float8GetDatum(float8 X)
* ----------------------------------------------------------------
*/
+/*
+ * pg_ternary
+ * Boolean value with an extra "unset" value
+ *
+ * This enum can be used for values that want to distinguish between true,
+ * false, and unset.
+*/
+
+typedef enum pg_ternary
+{
+ TERNARY_FALSE = 0,
+ TERNARY_TRUE = 1,
+ TERNARY_UNSET = -1
+} pg_ternary;
+
/*
* NON_EXEC_STATIC: It's sometimes useful to define a variable or function
* that is normally static but extern when using EXEC_BACKEND (see
diff --git a/src/include/utils/rel.h b/src/include/utils/rel.h
index d03ab247788..5feb18a5373 100644
--- a/src/include/utils/rel.h
+++ b/src/include/utils/rel.h
@@ -347,8 +347,7 @@ typedef struct StdRdOptions
bool user_catalog_table; /* use as an additional catalog relation */
int parallel_workers; /* max number of parallel workers */
StdRdOptIndexCleanup vacuum_index_cleanup; /* controls index vacuuming */
- bool vacuum_truncate; /* enables vacuum to truncate a relation */
- bool vacuum_truncate_set; /* whether vacuum_truncate is set */
+ pg_ternary vacuum_truncate; /* enables vacuum to truncate a relation */
/*
* Fraction of pages in a relation that vacuum can eagerly scan and fail
diff --git a/src/test/regress/expected/reloptions.out b/src/test/regress/expected/reloptions.out
index 9de19b4e3f1..1c99f79ab01 100644
--- a/src/test/regress/expected/reloptions.out
+++ b/src/test/regress/expected/reloptions.out
@@ -98,6 +98,42 @@ SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass;
{fillfactor=13,autovacuum_enabled=false}
(1 row)
+-- Tests for future (FIXME) ternary options
+-- behave as boolean option: accept unassigned name and truncated value
+DROP TABLE reloptions_test;
+CREATE TABLE reloptions_test(i INT) WITH (vacuum_truncate);
+SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass;
+ reloptions
+------------------------
+ {vacuum_truncate=true}
+(1 row)
+
+DROP TABLE reloptions_test;
+CREATE TABLE reloptions_test(i INT) WITH (vacuum_truncate=FaLS);
+SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass;
+ reloptions
+------------------------
+ {vacuum_truncate=fals}
+(1 row)
+
+-- preferred "true" alias is used when storing
+DROP TABLE reloptions_test;
+CREATE TABLE reloptions_test(i INT) WITH (vacuum_index_cleanup=on);
+SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass;
+ reloptions
+---------------------------
+ {vacuum_index_cleanup=on}
+(1 row)
+
+-- custom "third" value is available
+DROP TABLE reloptions_test;
+CREATE TABLE reloptions_test(i INT) WITH (vacuum_index_cleanup=auto);
+SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass;
+ reloptions
+-----------------------------
+ {vacuum_index_cleanup=auto}
+(1 row)
+
-- Test vacuum_truncate option
DROP TABLE reloptions_test;
CREATE TEMP TABLE reloptions_test(i INT NOT NULL, j text)
diff --git a/src/test/regress/sql/reloptions.sql b/src/test/regress/sql/reloptions.sql
index 24fbe0b478d..f5980dafcbc 100644
--- a/src/test/regress/sql/reloptions.sql
+++ b/src/test/regress/sql/reloptions.sql
@@ -59,6 +59,27 @@ UPDATE pg_class
ALTER TABLE reloptions_test RESET (illegal_option);
SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass;
+-- Tests for future (FIXME) ternary options
+
+-- behave as boolean option: accept unassigned name and truncated value
+DROP TABLE reloptions_test;
+CREATE TABLE reloptions_test(i INT) WITH (vacuum_truncate);
+SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass;
+
+DROP TABLE reloptions_test;
+CREATE TABLE reloptions_test(i INT) WITH (vacuum_truncate=FaLS);
+SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass;
+
+-- preferred "true" alias is used when storing
+DROP TABLE reloptions_test;
+CREATE TABLE reloptions_test(i INT) WITH (vacuum_index_cleanup=on);
+SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass;
+
+-- custom "third" value is available
+DROP TABLE reloptions_test;
+CREATE TABLE reloptions_test(i INT) WITH (vacuum_index_cleanup=auto);
+SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass;
+
-- Test vacuum_truncate option
DROP TABLE reloptions_test;
diff --git a/src/tools/pgindent/typedefs.list b/src/tools/pgindent/typedefs.list
index 3f3a888fd0e..1c8610fd46c 100644
--- a/src/tools/pgindent/typedefs.list
+++ b/src/tools/pgindent/typedefs.list
@@ -3952,6 +3952,7 @@ pg_sha512_ctx
pg_snapshot
pg_special_case
pg_stack_base_t
+pg_ternary
pg_time_t
pg_time_usec_t
pg_tz
@@ -4079,6 +4080,7 @@ relopt_kind
relopt_parse_elt
relopt_real
relopt_string
+relopt_ternary
relopt_type
relopt_value
relopts_validator
--
2.47.3
--c36t3fndxhkzbl5z--
^ permalink raw reply [nested|flat] 868+ messages in thread
* [PATCH v3] Introduce ternary reloptions
@ 2026-01-16 15:04 Álvaro Herrera <[email protected]>
0 siblings, 0 replies; 868+ messages in thread
From: Álvaro Herrera @ 2026-01-16 15:04 UTC (permalink / raw)
Introduce ternary reloption as a replacement for current
`vacuum_truncate` implementation. Remove the `vacuum_truncate_set`
separate flag and use TERNARY_UNSET instead.
This could also be used for other options such as `vacuum_index_cleanup`
and `buffering`, but lets get the scaffolding in first.
Discussion: https://postgr.es/m/3474141.usfYGdeWWP@thinkpad-pgpro
---
src/backend/access/common/reloptions.c | 137 ++++++++++++++++++-----
src/backend/commands/vacuum.c | 4 +-
src/include/access/reloptions.h | 27 ++---
src/include/postgres.h | 15 +++
src/include/utils/rel.h | 3 +-
src/test/regress/expected/reloptions.out | 36 ++++++
src/test/regress/sql/reloptions.sql | 21 ++++
src/tools/pgindent/typedefs.list | 2 +
8 files changed, 202 insertions(+), 43 deletions(-)
diff --git a/src/backend/access/common/reloptions.c b/src/backend/access/common/reloptions.c
index 0b83f98ed5f..6f1f577581a 100644
--- a/src/backend/access/common/reloptions.c
+++ b/src/backend/access/common/reloptions.c
@@ -40,9 +40,9 @@
*
* To add an option:
*
- * (i) decide on a type (bool, integer, real, enum, string), name, default
- * value, upper and lower bounds (if applicable); for strings, consider a
- * validation routine.
+ * (i) decide on a type (bool, ternary, integer, real, enum, string), name,
+ * default value, upper and lower bounds (if applicable); for strings,
+ * consider a validation routine.
* (ii) add a record below (or use add_<type>_reloption).
* (iii) add it to the appropriate options struct (perhaps StdRdOptions)
* (iv) add it to the appropriate handling routine (perhaps
@@ -147,15 +147,6 @@ static relopt_bool boolRelOpts[] =
},
false
},
- {
- {
- "vacuum_truncate",
- "Enables vacuum to truncate empty pages at the end of this table",
- RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
- ShareUpdateExclusiveLock
- },
- true
- },
{
{
"deduplicate_items",
@@ -170,6 +161,25 @@ static relopt_bool boolRelOpts[] =
{{NULL}}
};
+static relopt_ternary ternaryRelOpts[] =
+{
+ {
+ {
+ "vacuum_truncate",
+ "Enables vacuum to truncate empty pages at the end of this table",
+ RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
+ ShareUpdateExclusiveLock
+ },
+ TERNARY_UNSET
+ },
+ /* list terminator */
+ {
+ {
+ NULL
+ }
+ }
+};
+
static relopt_int intRelOpts[] =
{
{
@@ -609,6 +619,13 @@ initialize_reloptions(void)
boolRelOpts[i].gen.lockmode));
j++;
}
+ for (i = 0; ternaryRelOpts[i].gen.name; i++)
+ {
+ Assert(DoLockModesConflict(ternaryRelOpts[i].gen.lockmode,
+ ternaryRelOpts[i].gen.lockmode));
+ j++;
+ }
+
for (i = 0; intRelOpts[i].gen.name; i++)
{
Assert(DoLockModesConflict(intRelOpts[i].gen.lockmode,
@@ -649,6 +666,14 @@ initialize_reloptions(void)
j++;
}
+ for (i = 0; ternaryRelOpts[i].gen.name; i++)
+ {
+ relOpts[j] = &ternaryRelOpts[i].gen;
+ relOpts[j]->type = RELOPT_TYPE_TERNARY;
+ relOpts[j]->namelen = strlen(relOpts[j]->name);
+ j++;
+ }
+
for (i = 0; intRelOpts[i].gen.name; i++)
{
relOpts[j] = &intRelOpts[i].gen;
@@ -809,6 +834,9 @@ allocate_reloption(bits32 kinds, int type, const char *name, const char *desc,
case RELOPT_TYPE_BOOL:
size = sizeof(relopt_bool);
break;
+ case RELOPT_TYPE_TERNARY:
+ size = sizeof(relopt_ternary);
+ break;
case RELOPT_TYPE_INT:
size = sizeof(relopt_int);
break;
@@ -892,6 +920,57 @@ add_local_bool_reloption(local_relopts *relopts, const char *name,
add_local_reloption(relopts, (relopt_gen *) newoption, offset);
}
+/*
+ * init_ternary_reloption
+ * Allocate and initialize a new ternary reloption
+ */
+static relopt_ternary *
+init_ternary_reloption(bits32 kinds, const char *name, const char *desc,
+ pg_ternary default_val, LOCKMODE lockmode)
+{
+ relopt_ternary *newoption;
+
+ newoption = (relopt_ternary *)
+ allocate_reloption(kinds, RELOPT_TYPE_TERNARY, name, desc, lockmode);
+ newoption->default_val = default_val;
+
+ return newoption;
+}
+
+/*
+ * add_ternary_reloption
+ * Add a new ternary reloption
+ */
+void
+add_ternary_reloption(bits32 kinds, const char *name, const char *desc,
+ pg_ternary default_val, LOCKMODE lockmode)
+{
+ relopt_ternary *newoption;
+
+ newoption =
+ init_ternary_reloption(kinds, name, desc, default_val, lockmode);
+
+ add_reloption((relopt_gen *) newoption);
+}
+
+/*
+ * add_local_ternary_reloption
+ * Add a new ternary local reloption
+ *
+ * 'offset' is offset of ternary-typed field.
+ */
+void
+add_local_ternary_reloption(local_relopts *relopts, const char *name,
+ const char *desc, pg_ternary default_val,
+ int offset)
+{
+ relopt_ternary *newoption;
+
+ newoption =
+ init_ternary_reloption(RELOPT_KIND_LOCAL, name, desc, default_val, 0);
+
+ add_local_reloption(relopts, (relopt_gen *) newoption, offset);
+}
/*
* init_real_reloption
@@ -1626,6 +1705,19 @@ parse_one_reloption(relopt_value *option, char *text_str, int text_len,
option->gen->name, value)));
}
break;
+ case RELOPT_TYPE_TERNARY:
+ {
+ bool b;
+
+ parsed = parse_bool(value, &b);
+ option->values.ternary_val = b ? TERNARY_TRUE : TERNARY_FALSE;
+ if (validate && !parsed)
+ ereport(ERROR,
+ (errcode(ERRCODE_INVALID_PARAMETER_VALUE),
+ errmsg("invalid value for ternary option \"%s\": %s",
+ option->gen->name, value)));
+ }
+ break;
case RELOPT_TYPE_INT:
{
relopt_int *optint = (relopt_int *) option->gen;
@@ -1789,17 +1881,6 @@ fillRelOptions(void *rdopts, Size basesize,
char *itempos = ((char *) rdopts) + elems[j].offset;
char *string_val;
- /*
- * If isset_offset is provided, store whether the reloption is
- * set there.
- */
- if (elems[j].isset_offset > 0)
- {
- char *setpos = ((char *) rdopts) + elems[j].isset_offset;
-
- *(bool *) setpos = options[i].isset;
- }
-
switch (options[i].gen->type)
{
case RELOPT_TYPE_BOOL:
@@ -1807,6 +1888,11 @@ fillRelOptions(void *rdopts, Size basesize,
options[i].values.bool_val :
((relopt_bool *) options[i].gen)->default_val;
break;
+ case RELOPT_TYPE_TERNARY:
+ *(pg_ternary *) itempos = options[i].isset ?
+ options[i].values.ternary_val :
+ ((relopt_ternary *) options[i].gen)->default_val;
+ break;
case RELOPT_TYPE_INT:
*(int *) itempos = options[i].isset ?
options[i].values.int_val :
@@ -1923,8 +2009,8 @@ default_reloptions(Datum reloptions, bool validate, relopt_kind kind)
offsetof(StdRdOptions, parallel_workers)},
{"vacuum_index_cleanup", RELOPT_TYPE_ENUM,
offsetof(StdRdOptions, vacuum_index_cleanup)},
- {"vacuum_truncate", RELOPT_TYPE_BOOL,
- offsetof(StdRdOptions, vacuum_truncate), offsetof(StdRdOptions, vacuum_truncate_set)},
+ {"vacuum_truncate", RELOPT_TYPE_TERNARY,
+ offsetof(StdRdOptions, vacuum_truncate)},
{"vacuum_max_eager_freeze_failure_rate", RELOPT_TYPE_REAL,
offsetof(StdRdOptions, vacuum_max_eager_freeze_failure_rate)}
};
@@ -2004,7 +2090,6 @@ build_local_reloptions(local_relopts *relopts, Datum options, bool validate)
elems[i].optname = opt->option->name;
elems[i].opttype = opt->option->type;
elems[i].offset = opt->offset;
- elems[i].isset_offset = 0; /* not supported for local relopts yet */
i++;
}
diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c
index aa4fbec143f..696eab9bd97 100644
--- a/src/backend/commands/vacuum.c
+++ b/src/backend/commands/vacuum.c
@@ -2224,9 +2224,9 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams params,
{
StdRdOptions *opts = (StdRdOptions *) rel->rd_options;
- if (opts && opts->vacuum_truncate_set)
+ if (opts && opts->vacuum_truncate != TERNARY_UNSET)
{
- if (opts->vacuum_truncate)
+ if (opts->vacuum_truncate == TERNARY_TRUE)
params.truncate = VACOPTVALUE_ENABLED;
else
params.truncate = VACOPTVALUE_DISABLED;
diff --git a/src/include/access/reloptions.h b/src/include/access/reloptions.h
index 2f08e1b0cf0..dfbef2babf2 100644
--- a/src/include/access/reloptions.h
+++ b/src/include/access/reloptions.h
@@ -29,6 +29,7 @@
typedef enum relopt_type
{
RELOPT_TYPE_BOOL,
+ RELOPT_TYPE_TERNARY, /* on, off, unset */
RELOPT_TYPE_INT,
RELOPT_TYPE_REAL,
RELOPT_TYPE_ENUM,
@@ -80,6 +81,7 @@ typedef struct relopt_value
union
{
bool bool_val;
+ pg_ternary ternary_val;
int int_val;
double real_val;
int enum_val;
@@ -94,6 +96,12 @@ typedef struct relopt_bool
bool default_val;
} relopt_bool;
+typedef struct relopt_ternary
+{
+ relopt_gen gen;
+ int default_val;
+} relopt_ternary;
+
typedef struct relopt_int
{
relopt_gen gen;
@@ -152,19 +160,6 @@ typedef struct
const char *optname; /* option's name */
relopt_type opttype; /* option's datatype */
int offset; /* offset of field in result struct */
-
- /*
- * isset_offset is an optional offset of a field in the result struct that
- * stores whether the option is explicitly set for the relation or if it
- * just picked up the default value. In most cases, this can be
- * accomplished by giving the reloption a special out-of-range default
- * value (e.g., some integer reloptions use -2), but this isn't always
- * possible. For example, a Boolean reloption cannot be given an
- * out-of-range default, so we need another way to discover the source of
- * its value. This offset is only used if given a value greater than
- * zero.
- */
- int isset_offset;
} relopt_parse_elt;
/* Local reloption definition */
@@ -195,6 +190,9 @@ typedef struct local_relopts
extern relopt_kind add_reloption_kind(void);
extern void add_bool_reloption(bits32 kinds, const char *name, const char *desc,
bool default_val, LOCKMODE lockmode);
+extern void add_ternary_reloption(bits32 kinds, const char *name,
+ const char *desc, pg_ternary default_val,
+ LOCKMODE lockmode);
extern void add_int_reloption(bits32 kinds, const char *name, const char *desc,
int default_val, int min_val, int max_val,
LOCKMODE lockmode);
@@ -214,6 +212,9 @@ extern void register_reloptions_validator(local_relopts *relopts,
extern void add_local_bool_reloption(local_relopts *relopts, const char *name,
const char *desc, bool default_val,
int offset);
+extern void add_local_ternary_reloption(local_relopts *relopts,
+ const char *name, const char *desc,
+ pg_ternary default_val, int offset);
extern void add_local_int_reloption(local_relopts *relopts, const char *name,
const char *desc, int default_val,
int min_val, int max_val, int offset);
diff --git a/src/include/postgres.h b/src/include/postgres.h
index 7d93fbce709..47c88780776 100644
--- a/src/include/postgres.h
+++ b/src/include/postgres.h
@@ -543,6 +543,21 @@ Float8GetDatum(float8 X)
* ----------------------------------------------------------------
*/
+/*
+ * pg_ternary
+ * Boolean value with an extra "unset" value
+ *
+ * This enum can be used for values that want to distinguish between true,
+ * false, and unset.
+*/
+
+typedef enum pg_ternary
+{
+ TERNARY_FALSE = 0,
+ TERNARY_TRUE = 1,
+ TERNARY_UNSET = -1
+} pg_ternary;
+
/*
* NON_EXEC_STATIC: It's sometimes useful to define a variable or function
* that is normally static but extern when using EXEC_BACKEND (see
diff --git a/src/include/utils/rel.h b/src/include/utils/rel.h
index d03ab247788..5feb18a5373 100644
--- a/src/include/utils/rel.h
+++ b/src/include/utils/rel.h
@@ -347,8 +347,7 @@ typedef struct StdRdOptions
bool user_catalog_table; /* use as an additional catalog relation */
int parallel_workers; /* max number of parallel workers */
StdRdOptIndexCleanup vacuum_index_cleanup; /* controls index vacuuming */
- bool vacuum_truncate; /* enables vacuum to truncate a relation */
- bool vacuum_truncate_set; /* whether vacuum_truncate is set */
+ pg_ternary vacuum_truncate; /* enables vacuum to truncate a relation */
/*
* Fraction of pages in a relation that vacuum can eagerly scan and fail
diff --git a/src/test/regress/expected/reloptions.out b/src/test/regress/expected/reloptions.out
index 9de19b4e3f1..1c99f79ab01 100644
--- a/src/test/regress/expected/reloptions.out
+++ b/src/test/regress/expected/reloptions.out
@@ -98,6 +98,42 @@ SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass;
{fillfactor=13,autovacuum_enabled=false}
(1 row)
+-- Tests for future (FIXME) ternary options
+-- behave as boolean option: accept unassigned name and truncated value
+DROP TABLE reloptions_test;
+CREATE TABLE reloptions_test(i INT) WITH (vacuum_truncate);
+SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass;
+ reloptions
+------------------------
+ {vacuum_truncate=true}
+(1 row)
+
+DROP TABLE reloptions_test;
+CREATE TABLE reloptions_test(i INT) WITH (vacuum_truncate=FaLS);
+SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass;
+ reloptions
+------------------------
+ {vacuum_truncate=fals}
+(1 row)
+
+-- preferred "true" alias is used when storing
+DROP TABLE reloptions_test;
+CREATE TABLE reloptions_test(i INT) WITH (vacuum_index_cleanup=on);
+SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass;
+ reloptions
+---------------------------
+ {vacuum_index_cleanup=on}
+(1 row)
+
+-- custom "third" value is available
+DROP TABLE reloptions_test;
+CREATE TABLE reloptions_test(i INT) WITH (vacuum_index_cleanup=auto);
+SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass;
+ reloptions
+-----------------------------
+ {vacuum_index_cleanup=auto}
+(1 row)
+
-- Test vacuum_truncate option
DROP TABLE reloptions_test;
CREATE TEMP TABLE reloptions_test(i INT NOT NULL, j text)
diff --git a/src/test/regress/sql/reloptions.sql b/src/test/regress/sql/reloptions.sql
index 24fbe0b478d..f5980dafcbc 100644
--- a/src/test/regress/sql/reloptions.sql
+++ b/src/test/regress/sql/reloptions.sql
@@ -59,6 +59,27 @@ UPDATE pg_class
ALTER TABLE reloptions_test RESET (illegal_option);
SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass;
+-- Tests for future (FIXME) ternary options
+
+-- behave as boolean option: accept unassigned name and truncated value
+DROP TABLE reloptions_test;
+CREATE TABLE reloptions_test(i INT) WITH (vacuum_truncate);
+SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass;
+
+DROP TABLE reloptions_test;
+CREATE TABLE reloptions_test(i INT) WITH (vacuum_truncate=FaLS);
+SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass;
+
+-- preferred "true" alias is used when storing
+DROP TABLE reloptions_test;
+CREATE TABLE reloptions_test(i INT) WITH (vacuum_index_cleanup=on);
+SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass;
+
+-- custom "third" value is available
+DROP TABLE reloptions_test;
+CREATE TABLE reloptions_test(i INT) WITH (vacuum_index_cleanup=auto);
+SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass;
+
-- Test vacuum_truncate option
DROP TABLE reloptions_test;
diff --git a/src/tools/pgindent/typedefs.list b/src/tools/pgindent/typedefs.list
index 3f3a888fd0e..1c8610fd46c 100644
--- a/src/tools/pgindent/typedefs.list
+++ b/src/tools/pgindent/typedefs.list
@@ -3952,6 +3952,7 @@ pg_sha512_ctx
pg_snapshot
pg_special_case
pg_stack_base_t
+pg_ternary
pg_time_t
pg_time_usec_t
pg_tz
@@ -4079,6 +4080,7 @@ relopt_kind
relopt_parse_elt
relopt_real
relopt_string
+relopt_ternary
relopt_type
relopt_value
relopts_validator
--
2.47.3
--c36t3fndxhkzbl5z--
^ permalink raw reply [nested|flat] 868+ messages in thread
* [PATCH v3] Introduce ternary reloptions
@ 2026-01-16 15:04 Álvaro Herrera <[email protected]>
0 siblings, 0 replies; 868+ messages in thread
From: Álvaro Herrera @ 2026-01-16 15:04 UTC (permalink / raw)
Introduce ternary reloption as a replacement for current
`vacuum_truncate` implementation. Remove the `vacuum_truncate_set`
separate flag and use TERNARY_UNSET instead.
This could also be used for other options such as `vacuum_index_cleanup`
and `buffering`, but lets get the scaffolding in first.
Discussion: https://postgr.es/m/3474141.usfYGdeWWP@thinkpad-pgpro
---
src/backend/access/common/reloptions.c | 137 ++++++++++++++++++-----
src/backend/commands/vacuum.c | 4 +-
src/include/access/reloptions.h | 27 ++---
src/include/postgres.h | 15 +++
src/include/utils/rel.h | 3 +-
src/test/regress/expected/reloptions.out | 36 ++++++
src/test/regress/sql/reloptions.sql | 21 ++++
src/tools/pgindent/typedefs.list | 2 +
8 files changed, 202 insertions(+), 43 deletions(-)
diff --git a/src/backend/access/common/reloptions.c b/src/backend/access/common/reloptions.c
index 0b83f98ed5f..6f1f577581a 100644
--- a/src/backend/access/common/reloptions.c
+++ b/src/backend/access/common/reloptions.c
@@ -40,9 +40,9 @@
*
* To add an option:
*
- * (i) decide on a type (bool, integer, real, enum, string), name, default
- * value, upper and lower bounds (if applicable); for strings, consider a
- * validation routine.
+ * (i) decide on a type (bool, ternary, integer, real, enum, string), name,
+ * default value, upper and lower bounds (if applicable); for strings,
+ * consider a validation routine.
* (ii) add a record below (or use add_<type>_reloption).
* (iii) add it to the appropriate options struct (perhaps StdRdOptions)
* (iv) add it to the appropriate handling routine (perhaps
@@ -147,15 +147,6 @@ static relopt_bool boolRelOpts[] =
},
false
},
- {
- {
- "vacuum_truncate",
- "Enables vacuum to truncate empty pages at the end of this table",
- RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
- ShareUpdateExclusiveLock
- },
- true
- },
{
{
"deduplicate_items",
@@ -170,6 +161,25 @@ static relopt_bool boolRelOpts[] =
{{NULL}}
};
+static relopt_ternary ternaryRelOpts[] =
+{
+ {
+ {
+ "vacuum_truncate",
+ "Enables vacuum to truncate empty pages at the end of this table",
+ RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
+ ShareUpdateExclusiveLock
+ },
+ TERNARY_UNSET
+ },
+ /* list terminator */
+ {
+ {
+ NULL
+ }
+ }
+};
+
static relopt_int intRelOpts[] =
{
{
@@ -609,6 +619,13 @@ initialize_reloptions(void)
boolRelOpts[i].gen.lockmode));
j++;
}
+ for (i = 0; ternaryRelOpts[i].gen.name; i++)
+ {
+ Assert(DoLockModesConflict(ternaryRelOpts[i].gen.lockmode,
+ ternaryRelOpts[i].gen.lockmode));
+ j++;
+ }
+
for (i = 0; intRelOpts[i].gen.name; i++)
{
Assert(DoLockModesConflict(intRelOpts[i].gen.lockmode,
@@ -649,6 +666,14 @@ initialize_reloptions(void)
j++;
}
+ for (i = 0; ternaryRelOpts[i].gen.name; i++)
+ {
+ relOpts[j] = &ternaryRelOpts[i].gen;
+ relOpts[j]->type = RELOPT_TYPE_TERNARY;
+ relOpts[j]->namelen = strlen(relOpts[j]->name);
+ j++;
+ }
+
for (i = 0; intRelOpts[i].gen.name; i++)
{
relOpts[j] = &intRelOpts[i].gen;
@@ -809,6 +834,9 @@ allocate_reloption(bits32 kinds, int type, const char *name, const char *desc,
case RELOPT_TYPE_BOOL:
size = sizeof(relopt_bool);
break;
+ case RELOPT_TYPE_TERNARY:
+ size = sizeof(relopt_ternary);
+ break;
case RELOPT_TYPE_INT:
size = sizeof(relopt_int);
break;
@@ -892,6 +920,57 @@ add_local_bool_reloption(local_relopts *relopts, const char *name,
add_local_reloption(relopts, (relopt_gen *) newoption, offset);
}
+/*
+ * init_ternary_reloption
+ * Allocate and initialize a new ternary reloption
+ */
+static relopt_ternary *
+init_ternary_reloption(bits32 kinds, const char *name, const char *desc,
+ pg_ternary default_val, LOCKMODE lockmode)
+{
+ relopt_ternary *newoption;
+
+ newoption = (relopt_ternary *)
+ allocate_reloption(kinds, RELOPT_TYPE_TERNARY, name, desc, lockmode);
+ newoption->default_val = default_val;
+
+ return newoption;
+}
+
+/*
+ * add_ternary_reloption
+ * Add a new ternary reloption
+ */
+void
+add_ternary_reloption(bits32 kinds, const char *name, const char *desc,
+ pg_ternary default_val, LOCKMODE lockmode)
+{
+ relopt_ternary *newoption;
+
+ newoption =
+ init_ternary_reloption(kinds, name, desc, default_val, lockmode);
+
+ add_reloption((relopt_gen *) newoption);
+}
+
+/*
+ * add_local_ternary_reloption
+ * Add a new ternary local reloption
+ *
+ * 'offset' is offset of ternary-typed field.
+ */
+void
+add_local_ternary_reloption(local_relopts *relopts, const char *name,
+ const char *desc, pg_ternary default_val,
+ int offset)
+{
+ relopt_ternary *newoption;
+
+ newoption =
+ init_ternary_reloption(RELOPT_KIND_LOCAL, name, desc, default_val, 0);
+
+ add_local_reloption(relopts, (relopt_gen *) newoption, offset);
+}
/*
* init_real_reloption
@@ -1626,6 +1705,19 @@ parse_one_reloption(relopt_value *option, char *text_str, int text_len,
option->gen->name, value)));
}
break;
+ case RELOPT_TYPE_TERNARY:
+ {
+ bool b;
+
+ parsed = parse_bool(value, &b);
+ option->values.ternary_val = b ? TERNARY_TRUE : TERNARY_FALSE;
+ if (validate && !parsed)
+ ereport(ERROR,
+ (errcode(ERRCODE_INVALID_PARAMETER_VALUE),
+ errmsg("invalid value for ternary option \"%s\": %s",
+ option->gen->name, value)));
+ }
+ break;
case RELOPT_TYPE_INT:
{
relopt_int *optint = (relopt_int *) option->gen;
@@ -1789,17 +1881,6 @@ fillRelOptions(void *rdopts, Size basesize,
char *itempos = ((char *) rdopts) + elems[j].offset;
char *string_val;
- /*
- * If isset_offset is provided, store whether the reloption is
- * set there.
- */
- if (elems[j].isset_offset > 0)
- {
- char *setpos = ((char *) rdopts) + elems[j].isset_offset;
-
- *(bool *) setpos = options[i].isset;
- }
-
switch (options[i].gen->type)
{
case RELOPT_TYPE_BOOL:
@@ -1807,6 +1888,11 @@ fillRelOptions(void *rdopts, Size basesize,
options[i].values.bool_val :
((relopt_bool *) options[i].gen)->default_val;
break;
+ case RELOPT_TYPE_TERNARY:
+ *(pg_ternary *) itempos = options[i].isset ?
+ options[i].values.ternary_val :
+ ((relopt_ternary *) options[i].gen)->default_val;
+ break;
case RELOPT_TYPE_INT:
*(int *) itempos = options[i].isset ?
options[i].values.int_val :
@@ -1923,8 +2009,8 @@ default_reloptions(Datum reloptions, bool validate, relopt_kind kind)
offsetof(StdRdOptions, parallel_workers)},
{"vacuum_index_cleanup", RELOPT_TYPE_ENUM,
offsetof(StdRdOptions, vacuum_index_cleanup)},
- {"vacuum_truncate", RELOPT_TYPE_BOOL,
- offsetof(StdRdOptions, vacuum_truncate), offsetof(StdRdOptions, vacuum_truncate_set)},
+ {"vacuum_truncate", RELOPT_TYPE_TERNARY,
+ offsetof(StdRdOptions, vacuum_truncate)},
{"vacuum_max_eager_freeze_failure_rate", RELOPT_TYPE_REAL,
offsetof(StdRdOptions, vacuum_max_eager_freeze_failure_rate)}
};
@@ -2004,7 +2090,6 @@ build_local_reloptions(local_relopts *relopts, Datum options, bool validate)
elems[i].optname = opt->option->name;
elems[i].opttype = opt->option->type;
elems[i].offset = opt->offset;
- elems[i].isset_offset = 0; /* not supported for local relopts yet */
i++;
}
diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c
index aa4fbec143f..696eab9bd97 100644
--- a/src/backend/commands/vacuum.c
+++ b/src/backend/commands/vacuum.c
@@ -2224,9 +2224,9 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams params,
{
StdRdOptions *opts = (StdRdOptions *) rel->rd_options;
- if (opts && opts->vacuum_truncate_set)
+ if (opts && opts->vacuum_truncate != TERNARY_UNSET)
{
- if (opts->vacuum_truncate)
+ if (opts->vacuum_truncate == TERNARY_TRUE)
params.truncate = VACOPTVALUE_ENABLED;
else
params.truncate = VACOPTVALUE_DISABLED;
diff --git a/src/include/access/reloptions.h b/src/include/access/reloptions.h
index 2f08e1b0cf0..dfbef2babf2 100644
--- a/src/include/access/reloptions.h
+++ b/src/include/access/reloptions.h
@@ -29,6 +29,7 @@
typedef enum relopt_type
{
RELOPT_TYPE_BOOL,
+ RELOPT_TYPE_TERNARY, /* on, off, unset */
RELOPT_TYPE_INT,
RELOPT_TYPE_REAL,
RELOPT_TYPE_ENUM,
@@ -80,6 +81,7 @@ typedef struct relopt_value
union
{
bool bool_val;
+ pg_ternary ternary_val;
int int_val;
double real_val;
int enum_val;
@@ -94,6 +96,12 @@ typedef struct relopt_bool
bool default_val;
} relopt_bool;
+typedef struct relopt_ternary
+{
+ relopt_gen gen;
+ int default_val;
+} relopt_ternary;
+
typedef struct relopt_int
{
relopt_gen gen;
@@ -152,19 +160,6 @@ typedef struct
const char *optname; /* option's name */
relopt_type opttype; /* option's datatype */
int offset; /* offset of field in result struct */
-
- /*
- * isset_offset is an optional offset of a field in the result struct that
- * stores whether the option is explicitly set for the relation or if it
- * just picked up the default value. In most cases, this can be
- * accomplished by giving the reloption a special out-of-range default
- * value (e.g., some integer reloptions use -2), but this isn't always
- * possible. For example, a Boolean reloption cannot be given an
- * out-of-range default, so we need another way to discover the source of
- * its value. This offset is only used if given a value greater than
- * zero.
- */
- int isset_offset;
} relopt_parse_elt;
/* Local reloption definition */
@@ -195,6 +190,9 @@ typedef struct local_relopts
extern relopt_kind add_reloption_kind(void);
extern void add_bool_reloption(bits32 kinds, const char *name, const char *desc,
bool default_val, LOCKMODE lockmode);
+extern void add_ternary_reloption(bits32 kinds, const char *name,
+ const char *desc, pg_ternary default_val,
+ LOCKMODE lockmode);
extern void add_int_reloption(bits32 kinds, const char *name, const char *desc,
int default_val, int min_val, int max_val,
LOCKMODE lockmode);
@@ -214,6 +212,9 @@ extern void register_reloptions_validator(local_relopts *relopts,
extern void add_local_bool_reloption(local_relopts *relopts, const char *name,
const char *desc, bool default_val,
int offset);
+extern void add_local_ternary_reloption(local_relopts *relopts,
+ const char *name, const char *desc,
+ pg_ternary default_val, int offset);
extern void add_local_int_reloption(local_relopts *relopts, const char *name,
const char *desc, int default_val,
int min_val, int max_val, int offset);
diff --git a/src/include/postgres.h b/src/include/postgres.h
index 7d93fbce709..47c88780776 100644
--- a/src/include/postgres.h
+++ b/src/include/postgres.h
@@ -543,6 +543,21 @@ Float8GetDatum(float8 X)
* ----------------------------------------------------------------
*/
+/*
+ * pg_ternary
+ * Boolean value with an extra "unset" value
+ *
+ * This enum can be used for values that want to distinguish between true,
+ * false, and unset.
+*/
+
+typedef enum pg_ternary
+{
+ TERNARY_FALSE = 0,
+ TERNARY_TRUE = 1,
+ TERNARY_UNSET = -1
+} pg_ternary;
+
/*
* NON_EXEC_STATIC: It's sometimes useful to define a variable or function
* that is normally static but extern when using EXEC_BACKEND (see
diff --git a/src/include/utils/rel.h b/src/include/utils/rel.h
index d03ab247788..5feb18a5373 100644
--- a/src/include/utils/rel.h
+++ b/src/include/utils/rel.h
@@ -347,8 +347,7 @@ typedef struct StdRdOptions
bool user_catalog_table; /* use as an additional catalog relation */
int parallel_workers; /* max number of parallel workers */
StdRdOptIndexCleanup vacuum_index_cleanup; /* controls index vacuuming */
- bool vacuum_truncate; /* enables vacuum to truncate a relation */
- bool vacuum_truncate_set; /* whether vacuum_truncate is set */
+ pg_ternary vacuum_truncate; /* enables vacuum to truncate a relation */
/*
* Fraction of pages in a relation that vacuum can eagerly scan and fail
diff --git a/src/test/regress/expected/reloptions.out b/src/test/regress/expected/reloptions.out
index 9de19b4e3f1..1c99f79ab01 100644
--- a/src/test/regress/expected/reloptions.out
+++ b/src/test/regress/expected/reloptions.out
@@ -98,6 +98,42 @@ SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass;
{fillfactor=13,autovacuum_enabled=false}
(1 row)
+-- Tests for future (FIXME) ternary options
+-- behave as boolean option: accept unassigned name and truncated value
+DROP TABLE reloptions_test;
+CREATE TABLE reloptions_test(i INT) WITH (vacuum_truncate);
+SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass;
+ reloptions
+------------------------
+ {vacuum_truncate=true}
+(1 row)
+
+DROP TABLE reloptions_test;
+CREATE TABLE reloptions_test(i INT) WITH (vacuum_truncate=FaLS);
+SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass;
+ reloptions
+------------------------
+ {vacuum_truncate=fals}
+(1 row)
+
+-- preferred "true" alias is used when storing
+DROP TABLE reloptions_test;
+CREATE TABLE reloptions_test(i INT) WITH (vacuum_index_cleanup=on);
+SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass;
+ reloptions
+---------------------------
+ {vacuum_index_cleanup=on}
+(1 row)
+
+-- custom "third" value is available
+DROP TABLE reloptions_test;
+CREATE TABLE reloptions_test(i INT) WITH (vacuum_index_cleanup=auto);
+SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass;
+ reloptions
+-----------------------------
+ {vacuum_index_cleanup=auto}
+(1 row)
+
-- Test vacuum_truncate option
DROP TABLE reloptions_test;
CREATE TEMP TABLE reloptions_test(i INT NOT NULL, j text)
diff --git a/src/test/regress/sql/reloptions.sql b/src/test/regress/sql/reloptions.sql
index 24fbe0b478d..f5980dafcbc 100644
--- a/src/test/regress/sql/reloptions.sql
+++ b/src/test/regress/sql/reloptions.sql
@@ -59,6 +59,27 @@ UPDATE pg_class
ALTER TABLE reloptions_test RESET (illegal_option);
SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass;
+-- Tests for future (FIXME) ternary options
+
+-- behave as boolean option: accept unassigned name and truncated value
+DROP TABLE reloptions_test;
+CREATE TABLE reloptions_test(i INT) WITH (vacuum_truncate);
+SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass;
+
+DROP TABLE reloptions_test;
+CREATE TABLE reloptions_test(i INT) WITH (vacuum_truncate=FaLS);
+SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass;
+
+-- preferred "true" alias is used when storing
+DROP TABLE reloptions_test;
+CREATE TABLE reloptions_test(i INT) WITH (vacuum_index_cleanup=on);
+SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass;
+
+-- custom "third" value is available
+DROP TABLE reloptions_test;
+CREATE TABLE reloptions_test(i INT) WITH (vacuum_index_cleanup=auto);
+SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass;
+
-- Test vacuum_truncate option
DROP TABLE reloptions_test;
diff --git a/src/tools/pgindent/typedefs.list b/src/tools/pgindent/typedefs.list
index 3f3a888fd0e..1c8610fd46c 100644
--- a/src/tools/pgindent/typedefs.list
+++ b/src/tools/pgindent/typedefs.list
@@ -3952,6 +3952,7 @@ pg_sha512_ctx
pg_snapshot
pg_special_case
pg_stack_base_t
+pg_ternary
pg_time_t
pg_time_usec_t
pg_tz
@@ -4079,6 +4080,7 @@ relopt_kind
relopt_parse_elt
relopt_real
relopt_string
+relopt_ternary
relopt_type
relopt_value
relopts_validator
--
2.47.3
--c36t3fndxhkzbl5z--
^ permalink raw reply [nested|flat] 868+ messages in thread
* [PATCH v3] Introduce ternary reloptions
@ 2026-01-16 15:04 Álvaro Herrera <[email protected]>
0 siblings, 0 replies; 868+ messages in thread
From: Álvaro Herrera @ 2026-01-16 15:04 UTC (permalink / raw)
Introduce ternary reloption as a replacement for current
`vacuum_truncate` implementation. Remove the `vacuum_truncate_set`
separate flag and use TERNARY_UNSET instead.
This could also be used for other options such as `vacuum_index_cleanup`
and `buffering`, but lets get the scaffolding in first.
Discussion: https://postgr.es/m/3474141.usfYGdeWWP@thinkpad-pgpro
---
src/backend/access/common/reloptions.c | 137 ++++++++++++++++++-----
src/backend/commands/vacuum.c | 4 +-
src/include/access/reloptions.h | 27 ++---
src/include/postgres.h | 15 +++
src/include/utils/rel.h | 3 +-
src/test/regress/expected/reloptions.out | 36 ++++++
src/test/regress/sql/reloptions.sql | 21 ++++
src/tools/pgindent/typedefs.list | 2 +
8 files changed, 202 insertions(+), 43 deletions(-)
diff --git a/src/backend/access/common/reloptions.c b/src/backend/access/common/reloptions.c
index 0b83f98ed5f..6f1f577581a 100644
--- a/src/backend/access/common/reloptions.c
+++ b/src/backend/access/common/reloptions.c
@@ -40,9 +40,9 @@
*
* To add an option:
*
- * (i) decide on a type (bool, integer, real, enum, string), name, default
- * value, upper and lower bounds (if applicable); for strings, consider a
- * validation routine.
+ * (i) decide on a type (bool, ternary, integer, real, enum, string), name,
+ * default value, upper and lower bounds (if applicable); for strings,
+ * consider a validation routine.
* (ii) add a record below (or use add_<type>_reloption).
* (iii) add it to the appropriate options struct (perhaps StdRdOptions)
* (iv) add it to the appropriate handling routine (perhaps
@@ -147,15 +147,6 @@ static relopt_bool boolRelOpts[] =
},
false
},
- {
- {
- "vacuum_truncate",
- "Enables vacuum to truncate empty pages at the end of this table",
- RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
- ShareUpdateExclusiveLock
- },
- true
- },
{
{
"deduplicate_items",
@@ -170,6 +161,25 @@ static relopt_bool boolRelOpts[] =
{{NULL}}
};
+static relopt_ternary ternaryRelOpts[] =
+{
+ {
+ {
+ "vacuum_truncate",
+ "Enables vacuum to truncate empty pages at the end of this table",
+ RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
+ ShareUpdateExclusiveLock
+ },
+ TERNARY_UNSET
+ },
+ /* list terminator */
+ {
+ {
+ NULL
+ }
+ }
+};
+
static relopt_int intRelOpts[] =
{
{
@@ -609,6 +619,13 @@ initialize_reloptions(void)
boolRelOpts[i].gen.lockmode));
j++;
}
+ for (i = 0; ternaryRelOpts[i].gen.name; i++)
+ {
+ Assert(DoLockModesConflict(ternaryRelOpts[i].gen.lockmode,
+ ternaryRelOpts[i].gen.lockmode));
+ j++;
+ }
+
for (i = 0; intRelOpts[i].gen.name; i++)
{
Assert(DoLockModesConflict(intRelOpts[i].gen.lockmode,
@@ -649,6 +666,14 @@ initialize_reloptions(void)
j++;
}
+ for (i = 0; ternaryRelOpts[i].gen.name; i++)
+ {
+ relOpts[j] = &ternaryRelOpts[i].gen;
+ relOpts[j]->type = RELOPT_TYPE_TERNARY;
+ relOpts[j]->namelen = strlen(relOpts[j]->name);
+ j++;
+ }
+
for (i = 0; intRelOpts[i].gen.name; i++)
{
relOpts[j] = &intRelOpts[i].gen;
@@ -809,6 +834,9 @@ allocate_reloption(bits32 kinds, int type, const char *name, const char *desc,
case RELOPT_TYPE_BOOL:
size = sizeof(relopt_bool);
break;
+ case RELOPT_TYPE_TERNARY:
+ size = sizeof(relopt_ternary);
+ break;
case RELOPT_TYPE_INT:
size = sizeof(relopt_int);
break;
@@ -892,6 +920,57 @@ add_local_bool_reloption(local_relopts *relopts, const char *name,
add_local_reloption(relopts, (relopt_gen *) newoption, offset);
}
+/*
+ * init_ternary_reloption
+ * Allocate and initialize a new ternary reloption
+ */
+static relopt_ternary *
+init_ternary_reloption(bits32 kinds, const char *name, const char *desc,
+ pg_ternary default_val, LOCKMODE lockmode)
+{
+ relopt_ternary *newoption;
+
+ newoption = (relopt_ternary *)
+ allocate_reloption(kinds, RELOPT_TYPE_TERNARY, name, desc, lockmode);
+ newoption->default_val = default_val;
+
+ return newoption;
+}
+
+/*
+ * add_ternary_reloption
+ * Add a new ternary reloption
+ */
+void
+add_ternary_reloption(bits32 kinds, const char *name, const char *desc,
+ pg_ternary default_val, LOCKMODE lockmode)
+{
+ relopt_ternary *newoption;
+
+ newoption =
+ init_ternary_reloption(kinds, name, desc, default_val, lockmode);
+
+ add_reloption((relopt_gen *) newoption);
+}
+
+/*
+ * add_local_ternary_reloption
+ * Add a new ternary local reloption
+ *
+ * 'offset' is offset of ternary-typed field.
+ */
+void
+add_local_ternary_reloption(local_relopts *relopts, const char *name,
+ const char *desc, pg_ternary default_val,
+ int offset)
+{
+ relopt_ternary *newoption;
+
+ newoption =
+ init_ternary_reloption(RELOPT_KIND_LOCAL, name, desc, default_val, 0);
+
+ add_local_reloption(relopts, (relopt_gen *) newoption, offset);
+}
/*
* init_real_reloption
@@ -1626,6 +1705,19 @@ parse_one_reloption(relopt_value *option, char *text_str, int text_len,
option->gen->name, value)));
}
break;
+ case RELOPT_TYPE_TERNARY:
+ {
+ bool b;
+
+ parsed = parse_bool(value, &b);
+ option->values.ternary_val = b ? TERNARY_TRUE : TERNARY_FALSE;
+ if (validate && !parsed)
+ ereport(ERROR,
+ (errcode(ERRCODE_INVALID_PARAMETER_VALUE),
+ errmsg("invalid value for ternary option \"%s\": %s",
+ option->gen->name, value)));
+ }
+ break;
case RELOPT_TYPE_INT:
{
relopt_int *optint = (relopt_int *) option->gen;
@@ -1789,17 +1881,6 @@ fillRelOptions(void *rdopts, Size basesize,
char *itempos = ((char *) rdopts) + elems[j].offset;
char *string_val;
- /*
- * If isset_offset is provided, store whether the reloption is
- * set there.
- */
- if (elems[j].isset_offset > 0)
- {
- char *setpos = ((char *) rdopts) + elems[j].isset_offset;
-
- *(bool *) setpos = options[i].isset;
- }
-
switch (options[i].gen->type)
{
case RELOPT_TYPE_BOOL:
@@ -1807,6 +1888,11 @@ fillRelOptions(void *rdopts, Size basesize,
options[i].values.bool_val :
((relopt_bool *) options[i].gen)->default_val;
break;
+ case RELOPT_TYPE_TERNARY:
+ *(pg_ternary *) itempos = options[i].isset ?
+ options[i].values.ternary_val :
+ ((relopt_ternary *) options[i].gen)->default_val;
+ break;
case RELOPT_TYPE_INT:
*(int *) itempos = options[i].isset ?
options[i].values.int_val :
@@ -1923,8 +2009,8 @@ default_reloptions(Datum reloptions, bool validate, relopt_kind kind)
offsetof(StdRdOptions, parallel_workers)},
{"vacuum_index_cleanup", RELOPT_TYPE_ENUM,
offsetof(StdRdOptions, vacuum_index_cleanup)},
- {"vacuum_truncate", RELOPT_TYPE_BOOL,
- offsetof(StdRdOptions, vacuum_truncate), offsetof(StdRdOptions, vacuum_truncate_set)},
+ {"vacuum_truncate", RELOPT_TYPE_TERNARY,
+ offsetof(StdRdOptions, vacuum_truncate)},
{"vacuum_max_eager_freeze_failure_rate", RELOPT_TYPE_REAL,
offsetof(StdRdOptions, vacuum_max_eager_freeze_failure_rate)}
};
@@ -2004,7 +2090,6 @@ build_local_reloptions(local_relopts *relopts, Datum options, bool validate)
elems[i].optname = opt->option->name;
elems[i].opttype = opt->option->type;
elems[i].offset = opt->offset;
- elems[i].isset_offset = 0; /* not supported for local relopts yet */
i++;
}
diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c
index aa4fbec143f..696eab9bd97 100644
--- a/src/backend/commands/vacuum.c
+++ b/src/backend/commands/vacuum.c
@@ -2224,9 +2224,9 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams params,
{
StdRdOptions *opts = (StdRdOptions *) rel->rd_options;
- if (opts && opts->vacuum_truncate_set)
+ if (opts && opts->vacuum_truncate != TERNARY_UNSET)
{
- if (opts->vacuum_truncate)
+ if (opts->vacuum_truncate == TERNARY_TRUE)
params.truncate = VACOPTVALUE_ENABLED;
else
params.truncate = VACOPTVALUE_DISABLED;
diff --git a/src/include/access/reloptions.h b/src/include/access/reloptions.h
index 2f08e1b0cf0..dfbef2babf2 100644
--- a/src/include/access/reloptions.h
+++ b/src/include/access/reloptions.h
@@ -29,6 +29,7 @@
typedef enum relopt_type
{
RELOPT_TYPE_BOOL,
+ RELOPT_TYPE_TERNARY, /* on, off, unset */
RELOPT_TYPE_INT,
RELOPT_TYPE_REAL,
RELOPT_TYPE_ENUM,
@@ -80,6 +81,7 @@ typedef struct relopt_value
union
{
bool bool_val;
+ pg_ternary ternary_val;
int int_val;
double real_val;
int enum_val;
@@ -94,6 +96,12 @@ typedef struct relopt_bool
bool default_val;
} relopt_bool;
+typedef struct relopt_ternary
+{
+ relopt_gen gen;
+ int default_val;
+} relopt_ternary;
+
typedef struct relopt_int
{
relopt_gen gen;
@@ -152,19 +160,6 @@ typedef struct
const char *optname; /* option's name */
relopt_type opttype; /* option's datatype */
int offset; /* offset of field in result struct */
-
- /*
- * isset_offset is an optional offset of a field in the result struct that
- * stores whether the option is explicitly set for the relation or if it
- * just picked up the default value. In most cases, this can be
- * accomplished by giving the reloption a special out-of-range default
- * value (e.g., some integer reloptions use -2), but this isn't always
- * possible. For example, a Boolean reloption cannot be given an
- * out-of-range default, so we need another way to discover the source of
- * its value. This offset is only used if given a value greater than
- * zero.
- */
- int isset_offset;
} relopt_parse_elt;
/* Local reloption definition */
@@ -195,6 +190,9 @@ typedef struct local_relopts
extern relopt_kind add_reloption_kind(void);
extern void add_bool_reloption(bits32 kinds, const char *name, const char *desc,
bool default_val, LOCKMODE lockmode);
+extern void add_ternary_reloption(bits32 kinds, const char *name,
+ const char *desc, pg_ternary default_val,
+ LOCKMODE lockmode);
extern void add_int_reloption(bits32 kinds, const char *name, const char *desc,
int default_val, int min_val, int max_val,
LOCKMODE lockmode);
@@ -214,6 +212,9 @@ extern void register_reloptions_validator(local_relopts *relopts,
extern void add_local_bool_reloption(local_relopts *relopts, const char *name,
const char *desc, bool default_val,
int offset);
+extern void add_local_ternary_reloption(local_relopts *relopts,
+ const char *name, const char *desc,
+ pg_ternary default_val, int offset);
extern void add_local_int_reloption(local_relopts *relopts, const char *name,
const char *desc, int default_val,
int min_val, int max_val, int offset);
diff --git a/src/include/postgres.h b/src/include/postgres.h
index 7d93fbce709..47c88780776 100644
--- a/src/include/postgres.h
+++ b/src/include/postgres.h
@@ -543,6 +543,21 @@ Float8GetDatum(float8 X)
* ----------------------------------------------------------------
*/
+/*
+ * pg_ternary
+ * Boolean value with an extra "unset" value
+ *
+ * This enum can be used for values that want to distinguish between true,
+ * false, and unset.
+*/
+
+typedef enum pg_ternary
+{
+ TERNARY_FALSE = 0,
+ TERNARY_TRUE = 1,
+ TERNARY_UNSET = -1
+} pg_ternary;
+
/*
* NON_EXEC_STATIC: It's sometimes useful to define a variable or function
* that is normally static but extern when using EXEC_BACKEND (see
diff --git a/src/include/utils/rel.h b/src/include/utils/rel.h
index d03ab247788..5feb18a5373 100644
--- a/src/include/utils/rel.h
+++ b/src/include/utils/rel.h
@@ -347,8 +347,7 @@ typedef struct StdRdOptions
bool user_catalog_table; /* use as an additional catalog relation */
int parallel_workers; /* max number of parallel workers */
StdRdOptIndexCleanup vacuum_index_cleanup; /* controls index vacuuming */
- bool vacuum_truncate; /* enables vacuum to truncate a relation */
- bool vacuum_truncate_set; /* whether vacuum_truncate is set */
+ pg_ternary vacuum_truncate; /* enables vacuum to truncate a relation */
/*
* Fraction of pages in a relation that vacuum can eagerly scan and fail
diff --git a/src/test/regress/expected/reloptions.out b/src/test/regress/expected/reloptions.out
index 9de19b4e3f1..1c99f79ab01 100644
--- a/src/test/regress/expected/reloptions.out
+++ b/src/test/regress/expected/reloptions.out
@@ -98,6 +98,42 @@ SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass;
{fillfactor=13,autovacuum_enabled=false}
(1 row)
+-- Tests for future (FIXME) ternary options
+-- behave as boolean option: accept unassigned name and truncated value
+DROP TABLE reloptions_test;
+CREATE TABLE reloptions_test(i INT) WITH (vacuum_truncate);
+SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass;
+ reloptions
+------------------------
+ {vacuum_truncate=true}
+(1 row)
+
+DROP TABLE reloptions_test;
+CREATE TABLE reloptions_test(i INT) WITH (vacuum_truncate=FaLS);
+SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass;
+ reloptions
+------------------------
+ {vacuum_truncate=fals}
+(1 row)
+
+-- preferred "true" alias is used when storing
+DROP TABLE reloptions_test;
+CREATE TABLE reloptions_test(i INT) WITH (vacuum_index_cleanup=on);
+SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass;
+ reloptions
+---------------------------
+ {vacuum_index_cleanup=on}
+(1 row)
+
+-- custom "third" value is available
+DROP TABLE reloptions_test;
+CREATE TABLE reloptions_test(i INT) WITH (vacuum_index_cleanup=auto);
+SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass;
+ reloptions
+-----------------------------
+ {vacuum_index_cleanup=auto}
+(1 row)
+
-- Test vacuum_truncate option
DROP TABLE reloptions_test;
CREATE TEMP TABLE reloptions_test(i INT NOT NULL, j text)
diff --git a/src/test/regress/sql/reloptions.sql b/src/test/regress/sql/reloptions.sql
index 24fbe0b478d..f5980dafcbc 100644
--- a/src/test/regress/sql/reloptions.sql
+++ b/src/test/regress/sql/reloptions.sql
@@ -59,6 +59,27 @@ UPDATE pg_class
ALTER TABLE reloptions_test RESET (illegal_option);
SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass;
+-- Tests for future (FIXME) ternary options
+
+-- behave as boolean option: accept unassigned name and truncated value
+DROP TABLE reloptions_test;
+CREATE TABLE reloptions_test(i INT) WITH (vacuum_truncate);
+SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass;
+
+DROP TABLE reloptions_test;
+CREATE TABLE reloptions_test(i INT) WITH (vacuum_truncate=FaLS);
+SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass;
+
+-- preferred "true" alias is used when storing
+DROP TABLE reloptions_test;
+CREATE TABLE reloptions_test(i INT) WITH (vacuum_index_cleanup=on);
+SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass;
+
+-- custom "third" value is available
+DROP TABLE reloptions_test;
+CREATE TABLE reloptions_test(i INT) WITH (vacuum_index_cleanup=auto);
+SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass;
+
-- Test vacuum_truncate option
DROP TABLE reloptions_test;
diff --git a/src/tools/pgindent/typedefs.list b/src/tools/pgindent/typedefs.list
index 3f3a888fd0e..1c8610fd46c 100644
--- a/src/tools/pgindent/typedefs.list
+++ b/src/tools/pgindent/typedefs.list
@@ -3952,6 +3952,7 @@ pg_sha512_ctx
pg_snapshot
pg_special_case
pg_stack_base_t
+pg_ternary
pg_time_t
pg_time_usec_t
pg_tz
@@ -4079,6 +4080,7 @@ relopt_kind
relopt_parse_elt
relopt_real
relopt_string
+relopt_ternary
relopt_type
relopt_value
relopts_validator
--
2.47.3
--c36t3fndxhkzbl5z--
^ permalink raw reply [nested|flat] 868+ messages in thread
* [PATCH v3] Introduce ternary reloptions
@ 2026-01-16 15:04 Álvaro Herrera <[email protected]>
0 siblings, 0 replies; 868+ messages in thread
From: Álvaro Herrera @ 2026-01-16 15:04 UTC (permalink / raw)
Introduce ternary reloption as a replacement for current
`vacuum_truncate` implementation. Remove the `vacuum_truncate_set`
separate flag and use TERNARY_UNSET instead.
This could also be used for other options such as `vacuum_index_cleanup`
and `buffering`, but lets get the scaffolding in first.
Discussion: https://postgr.es/m/3474141.usfYGdeWWP@thinkpad-pgpro
---
src/backend/access/common/reloptions.c | 137 ++++++++++++++++++-----
src/backend/commands/vacuum.c | 4 +-
src/include/access/reloptions.h | 27 ++---
src/include/postgres.h | 15 +++
src/include/utils/rel.h | 3 +-
src/test/regress/expected/reloptions.out | 36 ++++++
src/test/regress/sql/reloptions.sql | 21 ++++
src/tools/pgindent/typedefs.list | 2 +
8 files changed, 202 insertions(+), 43 deletions(-)
diff --git a/src/backend/access/common/reloptions.c b/src/backend/access/common/reloptions.c
index 0b83f98ed5f..6f1f577581a 100644
--- a/src/backend/access/common/reloptions.c
+++ b/src/backend/access/common/reloptions.c
@@ -40,9 +40,9 @@
*
* To add an option:
*
- * (i) decide on a type (bool, integer, real, enum, string), name, default
- * value, upper and lower bounds (if applicable); for strings, consider a
- * validation routine.
+ * (i) decide on a type (bool, ternary, integer, real, enum, string), name,
+ * default value, upper and lower bounds (if applicable); for strings,
+ * consider a validation routine.
* (ii) add a record below (or use add_<type>_reloption).
* (iii) add it to the appropriate options struct (perhaps StdRdOptions)
* (iv) add it to the appropriate handling routine (perhaps
@@ -147,15 +147,6 @@ static relopt_bool boolRelOpts[] =
},
false
},
- {
- {
- "vacuum_truncate",
- "Enables vacuum to truncate empty pages at the end of this table",
- RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
- ShareUpdateExclusiveLock
- },
- true
- },
{
{
"deduplicate_items",
@@ -170,6 +161,25 @@ static relopt_bool boolRelOpts[] =
{{NULL}}
};
+static relopt_ternary ternaryRelOpts[] =
+{
+ {
+ {
+ "vacuum_truncate",
+ "Enables vacuum to truncate empty pages at the end of this table",
+ RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
+ ShareUpdateExclusiveLock
+ },
+ TERNARY_UNSET
+ },
+ /* list terminator */
+ {
+ {
+ NULL
+ }
+ }
+};
+
static relopt_int intRelOpts[] =
{
{
@@ -609,6 +619,13 @@ initialize_reloptions(void)
boolRelOpts[i].gen.lockmode));
j++;
}
+ for (i = 0; ternaryRelOpts[i].gen.name; i++)
+ {
+ Assert(DoLockModesConflict(ternaryRelOpts[i].gen.lockmode,
+ ternaryRelOpts[i].gen.lockmode));
+ j++;
+ }
+
for (i = 0; intRelOpts[i].gen.name; i++)
{
Assert(DoLockModesConflict(intRelOpts[i].gen.lockmode,
@@ -649,6 +666,14 @@ initialize_reloptions(void)
j++;
}
+ for (i = 0; ternaryRelOpts[i].gen.name; i++)
+ {
+ relOpts[j] = &ternaryRelOpts[i].gen;
+ relOpts[j]->type = RELOPT_TYPE_TERNARY;
+ relOpts[j]->namelen = strlen(relOpts[j]->name);
+ j++;
+ }
+
for (i = 0; intRelOpts[i].gen.name; i++)
{
relOpts[j] = &intRelOpts[i].gen;
@@ -809,6 +834,9 @@ allocate_reloption(bits32 kinds, int type, const char *name, const char *desc,
case RELOPT_TYPE_BOOL:
size = sizeof(relopt_bool);
break;
+ case RELOPT_TYPE_TERNARY:
+ size = sizeof(relopt_ternary);
+ break;
case RELOPT_TYPE_INT:
size = sizeof(relopt_int);
break;
@@ -892,6 +920,57 @@ add_local_bool_reloption(local_relopts *relopts, const char *name,
add_local_reloption(relopts, (relopt_gen *) newoption, offset);
}
+/*
+ * init_ternary_reloption
+ * Allocate and initialize a new ternary reloption
+ */
+static relopt_ternary *
+init_ternary_reloption(bits32 kinds, const char *name, const char *desc,
+ pg_ternary default_val, LOCKMODE lockmode)
+{
+ relopt_ternary *newoption;
+
+ newoption = (relopt_ternary *)
+ allocate_reloption(kinds, RELOPT_TYPE_TERNARY, name, desc, lockmode);
+ newoption->default_val = default_val;
+
+ return newoption;
+}
+
+/*
+ * add_ternary_reloption
+ * Add a new ternary reloption
+ */
+void
+add_ternary_reloption(bits32 kinds, const char *name, const char *desc,
+ pg_ternary default_val, LOCKMODE lockmode)
+{
+ relopt_ternary *newoption;
+
+ newoption =
+ init_ternary_reloption(kinds, name, desc, default_val, lockmode);
+
+ add_reloption((relopt_gen *) newoption);
+}
+
+/*
+ * add_local_ternary_reloption
+ * Add a new ternary local reloption
+ *
+ * 'offset' is offset of ternary-typed field.
+ */
+void
+add_local_ternary_reloption(local_relopts *relopts, const char *name,
+ const char *desc, pg_ternary default_val,
+ int offset)
+{
+ relopt_ternary *newoption;
+
+ newoption =
+ init_ternary_reloption(RELOPT_KIND_LOCAL, name, desc, default_val, 0);
+
+ add_local_reloption(relopts, (relopt_gen *) newoption, offset);
+}
/*
* init_real_reloption
@@ -1626,6 +1705,19 @@ parse_one_reloption(relopt_value *option, char *text_str, int text_len,
option->gen->name, value)));
}
break;
+ case RELOPT_TYPE_TERNARY:
+ {
+ bool b;
+
+ parsed = parse_bool(value, &b);
+ option->values.ternary_val = b ? TERNARY_TRUE : TERNARY_FALSE;
+ if (validate && !parsed)
+ ereport(ERROR,
+ (errcode(ERRCODE_INVALID_PARAMETER_VALUE),
+ errmsg("invalid value for ternary option \"%s\": %s",
+ option->gen->name, value)));
+ }
+ break;
case RELOPT_TYPE_INT:
{
relopt_int *optint = (relopt_int *) option->gen;
@@ -1789,17 +1881,6 @@ fillRelOptions(void *rdopts, Size basesize,
char *itempos = ((char *) rdopts) + elems[j].offset;
char *string_val;
- /*
- * If isset_offset is provided, store whether the reloption is
- * set there.
- */
- if (elems[j].isset_offset > 0)
- {
- char *setpos = ((char *) rdopts) + elems[j].isset_offset;
-
- *(bool *) setpos = options[i].isset;
- }
-
switch (options[i].gen->type)
{
case RELOPT_TYPE_BOOL:
@@ -1807,6 +1888,11 @@ fillRelOptions(void *rdopts, Size basesize,
options[i].values.bool_val :
((relopt_bool *) options[i].gen)->default_val;
break;
+ case RELOPT_TYPE_TERNARY:
+ *(pg_ternary *) itempos = options[i].isset ?
+ options[i].values.ternary_val :
+ ((relopt_ternary *) options[i].gen)->default_val;
+ break;
case RELOPT_TYPE_INT:
*(int *) itempos = options[i].isset ?
options[i].values.int_val :
@@ -1923,8 +2009,8 @@ default_reloptions(Datum reloptions, bool validate, relopt_kind kind)
offsetof(StdRdOptions, parallel_workers)},
{"vacuum_index_cleanup", RELOPT_TYPE_ENUM,
offsetof(StdRdOptions, vacuum_index_cleanup)},
- {"vacuum_truncate", RELOPT_TYPE_BOOL,
- offsetof(StdRdOptions, vacuum_truncate), offsetof(StdRdOptions, vacuum_truncate_set)},
+ {"vacuum_truncate", RELOPT_TYPE_TERNARY,
+ offsetof(StdRdOptions, vacuum_truncate)},
{"vacuum_max_eager_freeze_failure_rate", RELOPT_TYPE_REAL,
offsetof(StdRdOptions, vacuum_max_eager_freeze_failure_rate)}
};
@@ -2004,7 +2090,6 @@ build_local_reloptions(local_relopts *relopts, Datum options, bool validate)
elems[i].optname = opt->option->name;
elems[i].opttype = opt->option->type;
elems[i].offset = opt->offset;
- elems[i].isset_offset = 0; /* not supported for local relopts yet */
i++;
}
diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c
index aa4fbec143f..696eab9bd97 100644
--- a/src/backend/commands/vacuum.c
+++ b/src/backend/commands/vacuum.c
@@ -2224,9 +2224,9 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams params,
{
StdRdOptions *opts = (StdRdOptions *) rel->rd_options;
- if (opts && opts->vacuum_truncate_set)
+ if (opts && opts->vacuum_truncate != TERNARY_UNSET)
{
- if (opts->vacuum_truncate)
+ if (opts->vacuum_truncate == TERNARY_TRUE)
params.truncate = VACOPTVALUE_ENABLED;
else
params.truncate = VACOPTVALUE_DISABLED;
diff --git a/src/include/access/reloptions.h b/src/include/access/reloptions.h
index 2f08e1b0cf0..dfbef2babf2 100644
--- a/src/include/access/reloptions.h
+++ b/src/include/access/reloptions.h
@@ -29,6 +29,7 @@
typedef enum relopt_type
{
RELOPT_TYPE_BOOL,
+ RELOPT_TYPE_TERNARY, /* on, off, unset */
RELOPT_TYPE_INT,
RELOPT_TYPE_REAL,
RELOPT_TYPE_ENUM,
@@ -80,6 +81,7 @@ typedef struct relopt_value
union
{
bool bool_val;
+ pg_ternary ternary_val;
int int_val;
double real_val;
int enum_val;
@@ -94,6 +96,12 @@ typedef struct relopt_bool
bool default_val;
} relopt_bool;
+typedef struct relopt_ternary
+{
+ relopt_gen gen;
+ int default_val;
+} relopt_ternary;
+
typedef struct relopt_int
{
relopt_gen gen;
@@ -152,19 +160,6 @@ typedef struct
const char *optname; /* option's name */
relopt_type opttype; /* option's datatype */
int offset; /* offset of field in result struct */
-
- /*
- * isset_offset is an optional offset of a field in the result struct that
- * stores whether the option is explicitly set for the relation or if it
- * just picked up the default value. In most cases, this can be
- * accomplished by giving the reloption a special out-of-range default
- * value (e.g., some integer reloptions use -2), but this isn't always
- * possible. For example, a Boolean reloption cannot be given an
- * out-of-range default, so we need another way to discover the source of
- * its value. This offset is only used if given a value greater than
- * zero.
- */
- int isset_offset;
} relopt_parse_elt;
/* Local reloption definition */
@@ -195,6 +190,9 @@ typedef struct local_relopts
extern relopt_kind add_reloption_kind(void);
extern void add_bool_reloption(bits32 kinds, const char *name, const char *desc,
bool default_val, LOCKMODE lockmode);
+extern void add_ternary_reloption(bits32 kinds, const char *name,
+ const char *desc, pg_ternary default_val,
+ LOCKMODE lockmode);
extern void add_int_reloption(bits32 kinds, const char *name, const char *desc,
int default_val, int min_val, int max_val,
LOCKMODE lockmode);
@@ -214,6 +212,9 @@ extern void register_reloptions_validator(local_relopts *relopts,
extern void add_local_bool_reloption(local_relopts *relopts, const char *name,
const char *desc, bool default_val,
int offset);
+extern void add_local_ternary_reloption(local_relopts *relopts,
+ const char *name, const char *desc,
+ pg_ternary default_val, int offset);
extern void add_local_int_reloption(local_relopts *relopts, const char *name,
const char *desc, int default_val,
int min_val, int max_val, int offset);
diff --git a/src/include/postgres.h b/src/include/postgres.h
index 7d93fbce709..47c88780776 100644
--- a/src/include/postgres.h
+++ b/src/include/postgres.h
@@ -543,6 +543,21 @@ Float8GetDatum(float8 X)
* ----------------------------------------------------------------
*/
+/*
+ * pg_ternary
+ * Boolean value with an extra "unset" value
+ *
+ * This enum can be used for values that want to distinguish between true,
+ * false, and unset.
+*/
+
+typedef enum pg_ternary
+{
+ TERNARY_FALSE = 0,
+ TERNARY_TRUE = 1,
+ TERNARY_UNSET = -1
+} pg_ternary;
+
/*
* NON_EXEC_STATIC: It's sometimes useful to define a variable or function
* that is normally static but extern when using EXEC_BACKEND (see
diff --git a/src/include/utils/rel.h b/src/include/utils/rel.h
index d03ab247788..5feb18a5373 100644
--- a/src/include/utils/rel.h
+++ b/src/include/utils/rel.h
@@ -347,8 +347,7 @@ typedef struct StdRdOptions
bool user_catalog_table; /* use as an additional catalog relation */
int parallel_workers; /* max number of parallel workers */
StdRdOptIndexCleanup vacuum_index_cleanup; /* controls index vacuuming */
- bool vacuum_truncate; /* enables vacuum to truncate a relation */
- bool vacuum_truncate_set; /* whether vacuum_truncate is set */
+ pg_ternary vacuum_truncate; /* enables vacuum to truncate a relation */
/*
* Fraction of pages in a relation that vacuum can eagerly scan and fail
diff --git a/src/test/regress/expected/reloptions.out b/src/test/regress/expected/reloptions.out
index 9de19b4e3f1..1c99f79ab01 100644
--- a/src/test/regress/expected/reloptions.out
+++ b/src/test/regress/expected/reloptions.out
@@ -98,6 +98,42 @@ SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass;
{fillfactor=13,autovacuum_enabled=false}
(1 row)
+-- Tests for future (FIXME) ternary options
+-- behave as boolean option: accept unassigned name and truncated value
+DROP TABLE reloptions_test;
+CREATE TABLE reloptions_test(i INT) WITH (vacuum_truncate);
+SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass;
+ reloptions
+------------------------
+ {vacuum_truncate=true}
+(1 row)
+
+DROP TABLE reloptions_test;
+CREATE TABLE reloptions_test(i INT) WITH (vacuum_truncate=FaLS);
+SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass;
+ reloptions
+------------------------
+ {vacuum_truncate=fals}
+(1 row)
+
+-- preferred "true" alias is used when storing
+DROP TABLE reloptions_test;
+CREATE TABLE reloptions_test(i INT) WITH (vacuum_index_cleanup=on);
+SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass;
+ reloptions
+---------------------------
+ {vacuum_index_cleanup=on}
+(1 row)
+
+-- custom "third" value is available
+DROP TABLE reloptions_test;
+CREATE TABLE reloptions_test(i INT) WITH (vacuum_index_cleanup=auto);
+SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass;
+ reloptions
+-----------------------------
+ {vacuum_index_cleanup=auto}
+(1 row)
+
-- Test vacuum_truncate option
DROP TABLE reloptions_test;
CREATE TEMP TABLE reloptions_test(i INT NOT NULL, j text)
diff --git a/src/test/regress/sql/reloptions.sql b/src/test/regress/sql/reloptions.sql
index 24fbe0b478d..f5980dafcbc 100644
--- a/src/test/regress/sql/reloptions.sql
+++ b/src/test/regress/sql/reloptions.sql
@@ -59,6 +59,27 @@ UPDATE pg_class
ALTER TABLE reloptions_test RESET (illegal_option);
SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass;
+-- Tests for future (FIXME) ternary options
+
+-- behave as boolean option: accept unassigned name and truncated value
+DROP TABLE reloptions_test;
+CREATE TABLE reloptions_test(i INT) WITH (vacuum_truncate);
+SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass;
+
+DROP TABLE reloptions_test;
+CREATE TABLE reloptions_test(i INT) WITH (vacuum_truncate=FaLS);
+SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass;
+
+-- preferred "true" alias is used when storing
+DROP TABLE reloptions_test;
+CREATE TABLE reloptions_test(i INT) WITH (vacuum_index_cleanup=on);
+SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass;
+
+-- custom "third" value is available
+DROP TABLE reloptions_test;
+CREATE TABLE reloptions_test(i INT) WITH (vacuum_index_cleanup=auto);
+SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass;
+
-- Test vacuum_truncate option
DROP TABLE reloptions_test;
diff --git a/src/tools/pgindent/typedefs.list b/src/tools/pgindent/typedefs.list
index 3f3a888fd0e..1c8610fd46c 100644
--- a/src/tools/pgindent/typedefs.list
+++ b/src/tools/pgindent/typedefs.list
@@ -3952,6 +3952,7 @@ pg_sha512_ctx
pg_snapshot
pg_special_case
pg_stack_base_t
+pg_ternary
pg_time_t
pg_time_usec_t
pg_tz
@@ -4079,6 +4080,7 @@ relopt_kind
relopt_parse_elt
relopt_real
relopt_string
+relopt_ternary
relopt_type
relopt_value
relopts_validator
--
2.47.3
--c36t3fndxhkzbl5z--
^ permalink raw reply [nested|flat] 868+ messages in thread
* [PATCH v3] Introduce ternary reloptions
@ 2026-01-16 15:04 Álvaro Herrera <[email protected]>
0 siblings, 0 replies; 868+ messages in thread
From: Álvaro Herrera @ 2026-01-16 15:04 UTC (permalink / raw)
Introduce ternary reloption as a replacement for current
`vacuum_truncate` implementation. Remove the `vacuum_truncate_set`
separate flag and use TERNARY_UNSET instead.
This could also be used for other options such as `vacuum_index_cleanup`
and `buffering`, but lets get the scaffolding in first.
Discussion: https://postgr.es/m/3474141.usfYGdeWWP@thinkpad-pgpro
---
src/backend/access/common/reloptions.c | 137 ++++++++++++++++++-----
src/backend/commands/vacuum.c | 4 +-
src/include/access/reloptions.h | 27 ++---
src/include/postgres.h | 15 +++
src/include/utils/rel.h | 3 +-
src/test/regress/expected/reloptions.out | 36 ++++++
src/test/regress/sql/reloptions.sql | 21 ++++
src/tools/pgindent/typedefs.list | 2 +
8 files changed, 202 insertions(+), 43 deletions(-)
diff --git a/src/backend/access/common/reloptions.c b/src/backend/access/common/reloptions.c
index 0b83f98ed5f..6f1f577581a 100644
--- a/src/backend/access/common/reloptions.c
+++ b/src/backend/access/common/reloptions.c
@@ -40,9 +40,9 @@
*
* To add an option:
*
- * (i) decide on a type (bool, integer, real, enum, string), name, default
- * value, upper and lower bounds (if applicable); for strings, consider a
- * validation routine.
+ * (i) decide on a type (bool, ternary, integer, real, enum, string), name,
+ * default value, upper and lower bounds (if applicable); for strings,
+ * consider a validation routine.
* (ii) add a record below (or use add_<type>_reloption).
* (iii) add it to the appropriate options struct (perhaps StdRdOptions)
* (iv) add it to the appropriate handling routine (perhaps
@@ -147,15 +147,6 @@ static relopt_bool boolRelOpts[] =
},
false
},
- {
- {
- "vacuum_truncate",
- "Enables vacuum to truncate empty pages at the end of this table",
- RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
- ShareUpdateExclusiveLock
- },
- true
- },
{
{
"deduplicate_items",
@@ -170,6 +161,25 @@ static relopt_bool boolRelOpts[] =
{{NULL}}
};
+static relopt_ternary ternaryRelOpts[] =
+{
+ {
+ {
+ "vacuum_truncate",
+ "Enables vacuum to truncate empty pages at the end of this table",
+ RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
+ ShareUpdateExclusiveLock
+ },
+ TERNARY_UNSET
+ },
+ /* list terminator */
+ {
+ {
+ NULL
+ }
+ }
+};
+
static relopt_int intRelOpts[] =
{
{
@@ -609,6 +619,13 @@ initialize_reloptions(void)
boolRelOpts[i].gen.lockmode));
j++;
}
+ for (i = 0; ternaryRelOpts[i].gen.name; i++)
+ {
+ Assert(DoLockModesConflict(ternaryRelOpts[i].gen.lockmode,
+ ternaryRelOpts[i].gen.lockmode));
+ j++;
+ }
+
for (i = 0; intRelOpts[i].gen.name; i++)
{
Assert(DoLockModesConflict(intRelOpts[i].gen.lockmode,
@@ -649,6 +666,14 @@ initialize_reloptions(void)
j++;
}
+ for (i = 0; ternaryRelOpts[i].gen.name; i++)
+ {
+ relOpts[j] = &ternaryRelOpts[i].gen;
+ relOpts[j]->type = RELOPT_TYPE_TERNARY;
+ relOpts[j]->namelen = strlen(relOpts[j]->name);
+ j++;
+ }
+
for (i = 0; intRelOpts[i].gen.name; i++)
{
relOpts[j] = &intRelOpts[i].gen;
@@ -809,6 +834,9 @@ allocate_reloption(bits32 kinds, int type, const char *name, const char *desc,
case RELOPT_TYPE_BOOL:
size = sizeof(relopt_bool);
break;
+ case RELOPT_TYPE_TERNARY:
+ size = sizeof(relopt_ternary);
+ break;
case RELOPT_TYPE_INT:
size = sizeof(relopt_int);
break;
@@ -892,6 +920,57 @@ add_local_bool_reloption(local_relopts *relopts, const char *name,
add_local_reloption(relopts, (relopt_gen *) newoption, offset);
}
+/*
+ * init_ternary_reloption
+ * Allocate and initialize a new ternary reloption
+ */
+static relopt_ternary *
+init_ternary_reloption(bits32 kinds, const char *name, const char *desc,
+ pg_ternary default_val, LOCKMODE lockmode)
+{
+ relopt_ternary *newoption;
+
+ newoption = (relopt_ternary *)
+ allocate_reloption(kinds, RELOPT_TYPE_TERNARY, name, desc, lockmode);
+ newoption->default_val = default_val;
+
+ return newoption;
+}
+
+/*
+ * add_ternary_reloption
+ * Add a new ternary reloption
+ */
+void
+add_ternary_reloption(bits32 kinds, const char *name, const char *desc,
+ pg_ternary default_val, LOCKMODE lockmode)
+{
+ relopt_ternary *newoption;
+
+ newoption =
+ init_ternary_reloption(kinds, name, desc, default_val, lockmode);
+
+ add_reloption((relopt_gen *) newoption);
+}
+
+/*
+ * add_local_ternary_reloption
+ * Add a new ternary local reloption
+ *
+ * 'offset' is offset of ternary-typed field.
+ */
+void
+add_local_ternary_reloption(local_relopts *relopts, const char *name,
+ const char *desc, pg_ternary default_val,
+ int offset)
+{
+ relopt_ternary *newoption;
+
+ newoption =
+ init_ternary_reloption(RELOPT_KIND_LOCAL, name, desc, default_val, 0);
+
+ add_local_reloption(relopts, (relopt_gen *) newoption, offset);
+}
/*
* init_real_reloption
@@ -1626,6 +1705,19 @@ parse_one_reloption(relopt_value *option, char *text_str, int text_len,
option->gen->name, value)));
}
break;
+ case RELOPT_TYPE_TERNARY:
+ {
+ bool b;
+
+ parsed = parse_bool(value, &b);
+ option->values.ternary_val = b ? TERNARY_TRUE : TERNARY_FALSE;
+ if (validate && !parsed)
+ ereport(ERROR,
+ (errcode(ERRCODE_INVALID_PARAMETER_VALUE),
+ errmsg("invalid value for ternary option \"%s\": %s",
+ option->gen->name, value)));
+ }
+ break;
case RELOPT_TYPE_INT:
{
relopt_int *optint = (relopt_int *) option->gen;
@@ -1789,17 +1881,6 @@ fillRelOptions(void *rdopts, Size basesize,
char *itempos = ((char *) rdopts) + elems[j].offset;
char *string_val;
- /*
- * If isset_offset is provided, store whether the reloption is
- * set there.
- */
- if (elems[j].isset_offset > 0)
- {
- char *setpos = ((char *) rdopts) + elems[j].isset_offset;
-
- *(bool *) setpos = options[i].isset;
- }
-
switch (options[i].gen->type)
{
case RELOPT_TYPE_BOOL:
@@ -1807,6 +1888,11 @@ fillRelOptions(void *rdopts, Size basesize,
options[i].values.bool_val :
((relopt_bool *) options[i].gen)->default_val;
break;
+ case RELOPT_TYPE_TERNARY:
+ *(pg_ternary *) itempos = options[i].isset ?
+ options[i].values.ternary_val :
+ ((relopt_ternary *) options[i].gen)->default_val;
+ break;
case RELOPT_TYPE_INT:
*(int *) itempos = options[i].isset ?
options[i].values.int_val :
@@ -1923,8 +2009,8 @@ default_reloptions(Datum reloptions, bool validate, relopt_kind kind)
offsetof(StdRdOptions, parallel_workers)},
{"vacuum_index_cleanup", RELOPT_TYPE_ENUM,
offsetof(StdRdOptions, vacuum_index_cleanup)},
- {"vacuum_truncate", RELOPT_TYPE_BOOL,
- offsetof(StdRdOptions, vacuum_truncate), offsetof(StdRdOptions, vacuum_truncate_set)},
+ {"vacuum_truncate", RELOPT_TYPE_TERNARY,
+ offsetof(StdRdOptions, vacuum_truncate)},
{"vacuum_max_eager_freeze_failure_rate", RELOPT_TYPE_REAL,
offsetof(StdRdOptions, vacuum_max_eager_freeze_failure_rate)}
};
@@ -2004,7 +2090,6 @@ build_local_reloptions(local_relopts *relopts, Datum options, bool validate)
elems[i].optname = opt->option->name;
elems[i].opttype = opt->option->type;
elems[i].offset = opt->offset;
- elems[i].isset_offset = 0; /* not supported for local relopts yet */
i++;
}
diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c
index aa4fbec143f..696eab9bd97 100644
--- a/src/backend/commands/vacuum.c
+++ b/src/backend/commands/vacuum.c
@@ -2224,9 +2224,9 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams params,
{
StdRdOptions *opts = (StdRdOptions *) rel->rd_options;
- if (opts && opts->vacuum_truncate_set)
+ if (opts && opts->vacuum_truncate != TERNARY_UNSET)
{
- if (opts->vacuum_truncate)
+ if (opts->vacuum_truncate == TERNARY_TRUE)
params.truncate = VACOPTVALUE_ENABLED;
else
params.truncate = VACOPTVALUE_DISABLED;
diff --git a/src/include/access/reloptions.h b/src/include/access/reloptions.h
index 2f08e1b0cf0..dfbef2babf2 100644
--- a/src/include/access/reloptions.h
+++ b/src/include/access/reloptions.h
@@ -29,6 +29,7 @@
typedef enum relopt_type
{
RELOPT_TYPE_BOOL,
+ RELOPT_TYPE_TERNARY, /* on, off, unset */
RELOPT_TYPE_INT,
RELOPT_TYPE_REAL,
RELOPT_TYPE_ENUM,
@@ -80,6 +81,7 @@ typedef struct relopt_value
union
{
bool bool_val;
+ pg_ternary ternary_val;
int int_val;
double real_val;
int enum_val;
@@ -94,6 +96,12 @@ typedef struct relopt_bool
bool default_val;
} relopt_bool;
+typedef struct relopt_ternary
+{
+ relopt_gen gen;
+ int default_val;
+} relopt_ternary;
+
typedef struct relopt_int
{
relopt_gen gen;
@@ -152,19 +160,6 @@ typedef struct
const char *optname; /* option's name */
relopt_type opttype; /* option's datatype */
int offset; /* offset of field in result struct */
-
- /*
- * isset_offset is an optional offset of a field in the result struct that
- * stores whether the option is explicitly set for the relation or if it
- * just picked up the default value. In most cases, this can be
- * accomplished by giving the reloption a special out-of-range default
- * value (e.g., some integer reloptions use -2), but this isn't always
- * possible. For example, a Boolean reloption cannot be given an
- * out-of-range default, so we need another way to discover the source of
- * its value. This offset is only used if given a value greater than
- * zero.
- */
- int isset_offset;
} relopt_parse_elt;
/* Local reloption definition */
@@ -195,6 +190,9 @@ typedef struct local_relopts
extern relopt_kind add_reloption_kind(void);
extern void add_bool_reloption(bits32 kinds, const char *name, const char *desc,
bool default_val, LOCKMODE lockmode);
+extern void add_ternary_reloption(bits32 kinds, const char *name,
+ const char *desc, pg_ternary default_val,
+ LOCKMODE lockmode);
extern void add_int_reloption(bits32 kinds, const char *name, const char *desc,
int default_val, int min_val, int max_val,
LOCKMODE lockmode);
@@ -214,6 +212,9 @@ extern void register_reloptions_validator(local_relopts *relopts,
extern void add_local_bool_reloption(local_relopts *relopts, const char *name,
const char *desc, bool default_val,
int offset);
+extern void add_local_ternary_reloption(local_relopts *relopts,
+ const char *name, const char *desc,
+ pg_ternary default_val, int offset);
extern void add_local_int_reloption(local_relopts *relopts, const char *name,
const char *desc, int default_val,
int min_val, int max_val, int offset);
diff --git a/src/include/postgres.h b/src/include/postgres.h
index 7d93fbce709..47c88780776 100644
--- a/src/include/postgres.h
+++ b/src/include/postgres.h
@@ -543,6 +543,21 @@ Float8GetDatum(float8 X)
* ----------------------------------------------------------------
*/
+/*
+ * pg_ternary
+ * Boolean value with an extra "unset" value
+ *
+ * This enum can be used for values that want to distinguish between true,
+ * false, and unset.
+*/
+
+typedef enum pg_ternary
+{
+ TERNARY_FALSE = 0,
+ TERNARY_TRUE = 1,
+ TERNARY_UNSET = -1
+} pg_ternary;
+
/*
* NON_EXEC_STATIC: It's sometimes useful to define a variable or function
* that is normally static but extern when using EXEC_BACKEND (see
diff --git a/src/include/utils/rel.h b/src/include/utils/rel.h
index d03ab247788..5feb18a5373 100644
--- a/src/include/utils/rel.h
+++ b/src/include/utils/rel.h
@@ -347,8 +347,7 @@ typedef struct StdRdOptions
bool user_catalog_table; /* use as an additional catalog relation */
int parallel_workers; /* max number of parallel workers */
StdRdOptIndexCleanup vacuum_index_cleanup; /* controls index vacuuming */
- bool vacuum_truncate; /* enables vacuum to truncate a relation */
- bool vacuum_truncate_set; /* whether vacuum_truncate is set */
+ pg_ternary vacuum_truncate; /* enables vacuum to truncate a relation */
/*
* Fraction of pages in a relation that vacuum can eagerly scan and fail
diff --git a/src/test/regress/expected/reloptions.out b/src/test/regress/expected/reloptions.out
index 9de19b4e3f1..1c99f79ab01 100644
--- a/src/test/regress/expected/reloptions.out
+++ b/src/test/regress/expected/reloptions.out
@@ -98,6 +98,42 @@ SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass;
{fillfactor=13,autovacuum_enabled=false}
(1 row)
+-- Tests for future (FIXME) ternary options
+-- behave as boolean option: accept unassigned name and truncated value
+DROP TABLE reloptions_test;
+CREATE TABLE reloptions_test(i INT) WITH (vacuum_truncate);
+SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass;
+ reloptions
+------------------------
+ {vacuum_truncate=true}
+(1 row)
+
+DROP TABLE reloptions_test;
+CREATE TABLE reloptions_test(i INT) WITH (vacuum_truncate=FaLS);
+SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass;
+ reloptions
+------------------------
+ {vacuum_truncate=fals}
+(1 row)
+
+-- preferred "true" alias is used when storing
+DROP TABLE reloptions_test;
+CREATE TABLE reloptions_test(i INT) WITH (vacuum_index_cleanup=on);
+SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass;
+ reloptions
+---------------------------
+ {vacuum_index_cleanup=on}
+(1 row)
+
+-- custom "third" value is available
+DROP TABLE reloptions_test;
+CREATE TABLE reloptions_test(i INT) WITH (vacuum_index_cleanup=auto);
+SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass;
+ reloptions
+-----------------------------
+ {vacuum_index_cleanup=auto}
+(1 row)
+
-- Test vacuum_truncate option
DROP TABLE reloptions_test;
CREATE TEMP TABLE reloptions_test(i INT NOT NULL, j text)
diff --git a/src/test/regress/sql/reloptions.sql b/src/test/regress/sql/reloptions.sql
index 24fbe0b478d..f5980dafcbc 100644
--- a/src/test/regress/sql/reloptions.sql
+++ b/src/test/regress/sql/reloptions.sql
@@ -59,6 +59,27 @@ UPDATE pg_class
ALTER TABLE reloptions_test RESET (illegal_option);
SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass;
+-- Tests for future (FIXME) ternary options
+
+-- behave as boolean option: accept unassigned name and truncated value
+DROP TABLE reloptions_test;
+CREATE TABLE reloptions_test(i INT) WITH (vacuum_truncate);
+SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass;
+
+DROP TABLE reloptions_test;
+CREATE TABLE reloptions_test(i INT) WITH (vacuum_truncate=FaLS);
+SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass;
+
+-- preferred "true" alias is used when storing
+DROP TABLE reloptions_test;
+CREATE TABLE reloptions_test(i INT) WITH (vacuum_index_cleanup=on);
+SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass;
+
+-- custom "third" value is available
+DROP TABLE reloptions_test;
+CREATE TABLE reloptions_test(i INT) WITH (vacuum_index_cleanup=auto);
+SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass;
+
-- Test vacuum_truncate option
DROP TABLE reloptions_test;
diff --git a/src/tools/pgindent/typedefs.list b/src/tools/pgindent/typedefs.list
index 3f3a888fd0e..1c8610fd46c 100644
--- a/src/tools/pgindent/typedefs.list
+++ b/src/tools/pgindent/typedefs.list
@@ -3952,6 +3952,7 @@ pg_sha512_ctx
pg_snapshot
pg_special_case
pg_stack_base_t
+pg_ternary
pg_time_t
pg_time_usec_t
pg_tz
@@ -4079,6 +4080,7 @@ relopt_kind
relopt_parse_elt
relopt_real
relopt_string
+relopt_ternary
relopt_type
relopt_value
relopts_validator
--
2.47.3
--c36t3fndxhkzbl5z--
^ permalink raw reply [nested|flat] 868+ messages in thread
* [PATCH v3] Introduce ternary reloptions
@ 2026-01-16 15:04 Álvaro Herrera <[email protected]>
0 siblings, 0 replies; 868+ messages in thread
From: Álvaro Herrera @ 2026-01-16 15:04 UTC (permalink / raw)
Introduce ternary reloption as a replacement for current
`vacuum_truncate` implementation. Remove the `vacuum_truncate_set`
separate flag and use TERNARY_UNSET instead.
This could also be used for other options such as `vacuum_index_cleanup`
and `buffering`, but lets get the scaffolding in first.
Discussion: https://postgr.es/m/3474141.usfYGdeWWP@thinkpad-pgpro
---
src/backend/access/common/reloptions.c | 137 ++++++++++++++++++-----
src/backend/commands/vacuum.c | 4 +-
src/include/access/reloptions.h | 27 ++---
src/include/postgres.h | 15 +++
src/include/utils/rel.h | 3 +-
src/test/regress/expected/reloptions.out | 36 ++++++
src/test/regress/sql/reloptions.sql | 21 ++++
src/tools/pgindent/typedefs.list | 2 +
8 files changed, 202 insertions(+), 43 deletions(-)
diff --git a/src/backend/access/common/reloptions.c b/src/backend/access/common/reloptions.c
index 0b83f98ed5f..6f1f577581a 100644
--- a/src/backend/access/common/reloptions.c
+++ b/src/backend/access/common/reloptions.c
@@ -40,9 +40,9 @@
*
* To add an option:
*
- * (i) decide on a type (bool, integer, real, enum, string), name, default
- * value, upper and lower bounds (if applicable); for strings, consider a
- * validation routine.
+ * (i) decide on a type (bool, ternary, integer, real, enum, string), name,
+ * default value, upper and lower bounds (if applicable); for strings,
+ * consider a validation routine.
* (ii) add a record below (or use add_<type>_reloption).
* (iii) add it to the appropriate options struct (perhaps StdRdOptions)
* (iv) add it to the appropriate handling routine (perhaps
@@ -147,15 +147,6 @@ static relopt_bool boolRelOpts[] =
},
false
},
- {
- {
- "vacuum_truncate",
- "Enables vacuum to truncate empty pages at the end of this table",
- RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
- ShareUpdateExclusiveLock
- },
- true
- },
{
{
"deduplicate_items",
@@ -170,6 +161,25 @@ static relopt_bool boolRelOpts[] =
{{NULL}}
};
+static relopt_ternary ternaryRelOpts[] =
+{
+ {
+ {
+ "vacuum_truncate",
+ "Enables vacuum to truncate empty pages at the end of this table",
+ RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
+ ShareUpdateExclusiveLock
+ },
+ TERNARY_UNSET
+ },
+ /* list terminator */
+ {
+ {
+ NULL
+ }
+ }
+};
+
static relopt_int intRelOpts[] =
{
{
@@ -609,6 +619,13 @@ initialize_reloptions(void)
boolRelOpts[i].gen.lockmode));
j++;
}
+ for (i = 0; ternaryRelOpts[i].gen.name; i++)
+ {
+ Assert(DoLockModesConflict(ternaryRelOpts[i].gen.lockmode,
+ ternaryRelOpts[i].gen.lockmode));
+ j++;
+ }
+
for (i = 0; intRelOpts[i].gen.name; i++)
{
Assert(DoLockModesConflict(intRelOpts[i].gen.lockmode,
@@ -649,6 +666,14 @@ initialize_reloptions(void)
j++;
}
+ for (i = 0; ternaryRelOpts[i].gen.name; i++)
+ {
+ relOpts[j] = &ternaryRelOpts[i].gen;
+ relOpts[j]->type = RELOPT_TYPE_TERNARY;
+ relOpts[j]->namelen = strlen(relOpts[j]->name);
+ j++;
+ }
+
for (i = 0; intRelOpts[i].gen.name; i++)
{
relOpts[j] = &intRelOpts[i].gen;
@@ -809,6 +834,9 @@ allocate_reloption(bits32 kinds, int type, const char *name, const char *desc,
case RELOPT_TYPE_BOOL:
size = sizeof(relopt_bool);
break;
+ case RELOPT_TYPE_TERNARY:
+ size = sizeof(relopt_ternary);
+ break;
case RELOPT_TYPE_INT:
size = sizeof(relopt_int);
break;
@@ -892,6 +920,57 @@ add_local_bool_reloption(local_relopts *relopts, const char *name,
add_local_reloption(relopts, (relopt_gen *) newoption, offset);
}
+/*
+ * init_ternary_reloption
+ * Allocate and initialize a new ternary reloption
+ */
+static relopt_ternary *
+init_ternary_reloption(bits32 kinds, const char *name, const char *desc,
+ pg_ternary default_val, LOCKMODE lockmode)
+{
+ relopt_ternary *newoption;
+
+ newoption = (relopt_ternary *)
+ allocate_reloption(kinds, RELOPT_TYPE_TERNARY, name, desc, lockmode);
+ newoption->default_val = default_val;
+
+ return newoption;
+}
+
+/*
+ * add_ternary_reloption
+ * Add a new ternary reloption
+ */
+void
+add_ternary_reloption(bits32 kinds, const char *name, const char *desc,
+ pg_ternary default_val, LOCKMODE lockmode)
+{
+ relopt_ternary *newoption;
+
+ newoption =
+ init_ternary_reloption(kinds, name, desc, default_val, lockmode);
+
+ add_reloption((relopt_gen *) newoption);
+}
+
+/*
+ * add_local_ternary_reloption
+ * Add a new ternary local reloption
+ *
+ * 'offset' is offset of ternary-typed field.
+ */
+void
+add_local_ternary_reloption(local_relopts *relopts, const char *name,
+ const char *desc, pg_ternary default_val,
+ int offset)
+{
+ relopt_ternary *newoption;
+
+ newoption =
+ init_ternary_reloption(RELOPT_KIND_LOCAL, name, desc, default_val, 0);
+
+ add_local_reloption(relopts, (relopt_gen *) newoption, offset);
+}
/*
* init_real_reloption
@@ -1626,6 +1705,19 @@ parse_one_reloption(relopt_value *option, char *text_str, int text_len,
option->gen->name, value)));
}
break;
+ case RELOPT_TYPE_TERNARY:
+ {
+ bool b;
+
+ parsed = parse_bool(value, &b);
+ option->values.ternary_val = b ? TERNARY_TRUE : TERNARY_FALSE;
+ if (validate && !parsed)
+ ereport(ERROR,
+ (errcode(ERRCODE_INVALID_PARAMETER_VALUE),
+ errmsg("invalid value for ternary option \"%s\": %s",
+ option->gen->name, value)));
+ }
+ break;
case RELOPT_TYPE_INT:
{
relopt_int *optint = (relopt_int *) option->gen;
@@ -1789,17 +1881,6 @@ fillRelOptions(void *rdopts, Size basesize,
char *itempos = ((char *) rdopts) + elems[j].offset;
char *string_val;
- /*
- * If isset_offset is provided, store whether the reloption is
- * set there.
- */
- if (elems[j].isset_offset > 0)
- {
- char *setpos = ((char *) rdopts) + elems[j].isset_offset;
-
- *(bool *) setpos = options[i].isset;
- }
-
switch (options[i].gen->type)
{
case RELOPT_TYPE_BOOL:
@@ -1807,6 +1888,11 @@ fillRelOptions(void *rdopts, Size basesize,
options[i].values.bool_val :
((relopt_bool *) options[i].gen)->default_val;
break;
+ case RELOPT_TYPE_TERNARY:
+ *(pg_ternary *) itempos = options[i].isset ?
+ options[i].values.ternary_val :
+ ((relopt_ternary *) options[i].gen)->default_val;
+ break;
case RELOPT_TYPE_INT:
*(int *) itempos = options[i].isset ?
options[i].values.int_val :
@@ -1923,8 +2009,8 @@ default_reloptions(Datum reloptions, bool validate, relopt_kind kind)
offsetof(StdRdOptions, parallel_workers)},
{"vacuum_index_cleanup", RELOPT_TYPE_ENUM,
offsetof(StdRdOptions, vacuum_index_cleanup)},
- {"vacuum_truncate", RELOPT_TYPE_BOOL,
- offsetof(StdRdOptions, vacuum_truncate), offsetof(StdRdOptions, vacuum_truncate_set)},
+ {"vacuum_truncate", RELOPT_TYPE_TERNARY,
+ offsetof(StdRdOptions, vacuum_truncate)},
{"vacuum_max_eager_freeze_failure_rate", RELOPT_TYPE_REAL,
offsetof(StdRdOptions, vacuum_max_eager_freeze_failure_rate)}
};
@@ -2004,7 +2090,6 @@ build_local_reloptions(local_relopts *relopts, Datum options, bool validate)
elems[i].optname = opt->option->name;
elems[i].opttype = opt->option->type;
elems[i].offset = opt->offset;
- elems[i].isset_offset = 0; /* not supported for local relopts yet */
i++;
}
diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c
index aa4fbec143f..696eab9bd97 100644
--- a/src/backend/commands/vacuum.c
+++ b/src/backend/commands/vacuum.c
@@ -2224,9 +2224,9 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams params,
{
StdRdOptions *opts = (StdRdOptions *) rel->rd_options;
- if (opts && opts->vacuum_truncate_set)
+ if (opts && opts->vacuum_truncate != TERNARY_UNSET)
{
- if (opts->vacuum_truncate)
+ if (opts->vacuum_truncate == TERNARY_TRUE)
params.truncate = VACOPTVALUE_ENABLED;
else
params.truncate = VACOPTVALUE_DISABLED;
diff --git a/src/include/access/reloptions.h b/src/include/access/reloptions.h
index 2f08e1b0cf0..dfbef2babf2 100644
--- a/src/include/access/reloptions.h
+++ b/src/include/access/reloptions.h
@@ -29,6 +29,7 @@
typedef enum relopt_type
{
RELOPT_TYPE_BOOL,
+ RELOPT_TYPE_TERNARY, /* on, off, unset */
RELOPT_TYPE_INT,
RELOPT_TYPE_REAL,
RELOPT_TYPE_ENUM,
@@ -80,6 +81,7 @@ typedef struct relopt_value
union
{
bool bool_val;
+ pg_ternary ternary_val;
int int_val;
double real_val;
int enum_val;
@@ -94,6 +96,12 @@ typedef struct relopt_bool
bool default_val;
} relopt_bool;
+typedef struct relopt_ternary
+{
+ relopt_gen gen;
+ int default_val;
+} relopt_ternary;
+
typedef struct relopt_int
{
relopt_gen gen;
@@ -152,19 +160,6 @@ typedef struct
const char *optname; /* option's name */
relopt_type opttype; /* option's datatype */
int offset; /* offset of field in result struct */
-
- /*
- * isset_offset is an optional offset of a field in the result struct that
- * stores whether the option is explicitly set for the relation or if it
- * just picked up the default value. In most cases, this can be
- * accomplished by giving the reloption a special out-of-range default
- * value (e.g., some integer reloptions use -2), but this isn't always
- * possible. For example, a Boolean reloption cannot be given an
- * out-of-range default, so we need another way to discover the source of
- * its value. This offset is only used if given a value greater than
- * zero.
- */
- int isset_offset;
} relopt_parse_elt;
/* Local reloption definition */
@@ -195,6 +190,9 @@ typedef struct local_relopts
extern relopt_kind add_reloption_kind(void);
extern void add_bool_reloption(bits32 kinds, const char *name, const char *desc,
bool default_val, LOCKMODE lockmode);
+extern void add_ternary_reloption(bits32 kinds, const char *name,
+ const char *desc, pg_ternary default_val,
+ LOCKMODE lockmode);
extern void add_int_reloption(bits32 kinds, const char *name, const char *desc,
int default_val, int min_val, int max_val,
LOCKMODE lockmode);
@@ -214,6 +212,9 @@ extern void register_reloptions_validator(local_relopts *relopts,
extern void add_local_bool_reloption(local_relopts *relopts, const char *name,
const char *desc, bool default_val,
int offset);
+extern void add_local_ternary_reloption(local_relopts *relopts,
+ const char *name, const char *desc,
+ pg_ternary default_val, int offset);
extern void add_local_int_reloption(local_relopts *relopts, const char *name,
const char *desc, int default_val,
int min_val, int max_val, int offset);
diff --git a/src/include/postgres.h b/src/include/postgres.h
index 7d93fbce709..47c88780776 100644
--- a/src/include/postgres.h
+++ b/src/include/postgres.h
@@ -543,6 +543,21 @@ Float8GetDatum(float8 X)
* ----------------------------------------------------------------
*/
+/*
+ * pg_ternary
+ * Boolean value with an extra "unset" value
+ *
+ * This enum can be used for values that want to distinguish between true,
+ * false, and unset.
+*/
+
+typedef enum pg_ternary
+{
+ TERNARY_FALSE = 0,
+ TERNARY_TRUE = 1,
+ TERNARY_UNSET = -1
+} pg_ternary;
+
/*
* NON_EXEC_STATIC: It's sometimes useful to define a variable or function
* that is normally static but extern when using EXEC_BACKEND (see
diff --git a/src/include/utils/rel.h b/src/include/utils/rel.h
index d03ab247788..5feb18a5373 100644
--- a/src/include/utils/rel.h
+++ b/src/include/utils/rel.h
@@ -347,8 +347,7 @@ typedef struct StdRdOptions
bool user_catalog_table; /* use as an additional catalog relation */
int parallel_workers; /* max number of parallel workers */
StdRdOptIndexCleanup vacuum_index_cleanup; /* controls index vacuuming */
- bool vacuum_truncate; /* enables vacuum to truncate a relation */
- bool vacuum_truncate_set; /* whether vacuum_truncate is set */
+ pg_ternary vacuum_truncate; /* enables vacuum to truncate a relation */
/*
* Fraction of pages in a relation that vacuum can eagerly scan and fail
diff --git a/src/test/regress/expected/reloptions.out b/src/test/regress/expected/reloptions.out
index 9de19b4e3f1..1c99f79ab01 100644
--- a/src/test/regress/expected/reloptions.out
+++ b/src/test/regress/expected/reloptions.out
@@ -98,6 +98,42 @@ SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass;
{fillfactor=13,autovacuum_enabled=false}
(1 row)
+-- Tests for future (FIXME) ternary options
+-- behave as boolean option: accept unassigned name and truncated value
+DROP TABLE reloptions_test;
+CREATE TABLE reloptions_test(i INT) WITH (vacuum_truncate);
+SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass;
+ reloptions
+------------------------
+ {vacuum_truncate=true}
+(1 row)
+
+DROP TABLE reloptions_test;
+CREATE TABLE reloptions_test(i INT) WITH (vacuum_truncate=FaLS);
+SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass;
+ reloptions
+------------------------
+ {vacuum_truncate=fals}
+(1 row)
+
+-- preferred "true" alias is used when storing
+DROP TABLE reloptions_test;
+CREATE TABLE reloptions_test(i INT) WITH (vacuum_index_cleanup=on);
+SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass;
+ reloptions
+---------------------------
+ {vacuum_index_cleanup=on}
+(1 row)
+
+-- custom "third" value is available
+DROP TABLE reloptions_test;
+CREATE TABLE reloptions_test(i INT) WITH (vacuum_index_cleanup=auto);
+SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass;
+ reloptions
+-----------------------------
+ {vacuum_index_cleanup=auto}
+(1 row)
+
-- Test vacuum_truncate option
DROP TABLE reloptions_test;
CREATE TEMP TABLE reloptions_test(i INT NOT NULL, j text)
diff --git a/src/test/regress/sql/reloptions.sql b/src/test/regress/sql/reloptions.sql
index 24fbe0b478d..f5980dafcbc 100644
--- a/src/test/regress/sql/reloptions.sql
+++ b/src/test/regress/sql/reloptions.sql
@@ -59,6 +59,27 @@ UPDATE pg_class
ALTER TABLE reloptions_test RESET (illegal_option);
SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass;
+-- Tests for future (FIXME) ternary options
+
+-- behave as boolean option: accept unassigned name and truncated value
+DROP TABLE reloptions_test;
+CREATE TABLE reloptions_test(i INT) WITH (vacuum_truncate);
+SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass;
+
+DROP TABLE reloptions_test;
+CREATE TABLE reloptions_test(i INT) WITH (vacuum_truncate=FaLS);
+SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass;
+
+-- preferred "true" alias is used when storing
+DROP TABLE reloptions_test;
+CREATE TABLE reloptions_test(i INT) WITH (vacuum_index_cleanup=on);
+SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass;
+
+-- custom "third" value is available
+DROP TABLE reloptions_test;
+CREATE TABLE reloptions_test(i INT) WITH (vacuum_index_cleanup=auto);
+SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass;
+
-- Test vacuum_truncate option
DROP TABLE reloptions_test;
diff --git a/src/tools/pgindent/typedefs.list b/src/tools/pgindent/typedefs.list
index 3f3a888fd0e..1c8610fd46c 100644
--- a/src/tools/pgindent/typedefs.list
+++ b/src/tools/pgindent/typedefs.list
@@ -3952,6 +3952,7 @@ pg_sha512_ctx
pg_snapshot
pg_special_case
pg_stack_base_t
+pg_ternary
pg_time_t
pg_time_usec_t
pg_tz
@@ -4079,6 +4080,7 @@ relopt_kind
relopt_parse_elt
relopt_real
relopt_string
+relopt_ternary
relopt_type
relopt_value
relopts_validator
--
2.47.3
--c36t3fndxhkzbl5z--
^ permalink raw reply [nested|flat] 868+ messages in thread
* [PATCH v3] Introduce ternary reloptions
@ 2026-01-16 15:04 Álvaro Herrera <[email protected]>
0 siblings, 0 replies; 868+ messages in thread
From: Álvaro Herrera @ 2026-01-16 15:04 UTC (permalink / raw)
Introduce ternary reloption as a replacement for current
`vacuum_truncate` implementation. Remove the `vacuum_truncate_set`
separate flag and use TERNARY_UNSET instead.
This could also be used for other options such as `vacuum_index_cleanup`
and `buffering`, but lets get the scaffolding in first.
Discussion: https://postgr.es/m/3474141.usfYGdeWWP@thinkpad-pgpro
---
src/backend/access/common/reloptions.c | 137 ++++++++++++++++++-----
src/backend/commands/vacuum.c | 4 +-
src/include/access/reloptions.h | 27 ++---
src/include/postgres.h | 15 +++
src/include/utils/rel.h | 3 +-
src/test/regress/expected/reloptions.out | 36 ++++++
src/test/regress/sql/reloptions.sql | 21 ++++
src/tools/pgindent/typedefs.list | 2 +
8 files changed, 202 insertions(+), 43 deletions(-)
diff --git a/src/backend/access/common/reloptions.c b/src/backend/access/common/reloptions.c
index 0b83f98ed5f..6f1f577581a 100644
--- a/src/backend/access/common/reloptions.c
+++ b/src/backend/access/common/reloptions.c
@@ -40,9 +40,9 @@
*
* To add an option:
*
- * (i) decide on a type (bool, integer, real, enum, string), name, default
- * value, upper and lower bounds (if applicable); for strings, consider a
- * validation routine.
+ * (i) decide on a type (bool, ternary, integer, real, enum, string), name,
+ * default value, upper and lower bounds (if applicable); for strings,
+ * consider a validation routine.
* (ii) add a record below (or use add_<type>_reloption).
* (iii) add it to the appropriate options struct (perhaps StdRdOptions)
* (iv) add it to the appropriate handling routine (perhaps
@@ -147,15 +147,6 @@ static relopt_bool boolRelOpts[] =
},
false
},
- {
- {
- "vacuum_truncate",
- "Enables vacuum to truncate empty pages at the end of this table",
- RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
- ShareUpdateExclusiveLock
- },
- true
- },
{
{
"deduplicate_items",
@@ -170,6 +161,25 @@ static relopt_bool boolRelOpts[] =
{{NULL}}
};
+static relopt_ternary ternaryRelOpts[] =
+{
+ {
+ {
+ "vacuum_truncate",
+ "Enables vacuum to truncate empty pages at the end of this table",
+ RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
+ ShareUpdateExclusiveLock
+ },
+ TERNARY_UNSET
+ },
+ /* list terminator */
+ {
+ {
+ NULL
+ }
+ }
+};
+
static relopt_int intRelOpts[] =
{
{
@@ -609,6 +619,13 @@ initialize_reloptions(void)
boolRelOpts[i].gen.lockmode));
j++;
}
+ for (i = 0; ternaryRelOpts[i].gen.name; i++)
+ {
+ Assert(DoLockModesConflict(ternaryRelOpts[i].gen.lockmode,
+ ternaryRelOpts[i].gen.lockmode));
+ j++;
+ }
+
for (i = 0; intRelOpts[i].gen.name; i++)
{
Assert(DoLockModesConflict(intRelOpts[i].gen.lockmode,
@@ -649,6 +666,14 @@ initialize_reloptions(void)
j++;
}
+ for (i = 0; ternaryRelOpts[i].gen.name; i++)
+ {
+ relOpts[j] = &ternaryRelOpts[i].gen;
+ relOpts[j]->type = RELOPT_TYPE_TERNARY;
+ relOpts[j]->namelen = strlen(relOpts[j]->name);
+ j++;
+ }
+
for (i = 0; intRelOpts[i].gen.name; i++)
{
relOpts[j] = &intRelOpts[i].gen;
@@ -809,6 +834,9 @@ allocate_reloption(bits32 kinds, int type, const char *name, const char *desc,
case RELOPT_TYPE_BOOL:
size = sizeof(relopt_bool);
break;
+ case RELOPT_TYPE_TERNARY:
+ size = sizeof(relopt_ternary);
+ break;
case RELOPT_TYPE_INT:
size = sizeof(relopt_int);
break;
@@ -892,6 +920,57 @@ add_local_bool_reloption(local_relopts *relopts, const char *name,
add_local_reloption(relopts, (relopt_gen *) newoption, offset);
}
+/*
+ * init_ternary_reloption
+ * Allocate and initialize a new ternary reloption
+ */
+static relopt_ternary *
+init_ternary_reloption(bits32 kinds, const char *name, const char *desc,
+ pg_ternary default_val, LOCKMODE lockmode)
+{
+ relopt_ternary *newoption;
+
+ newoption = (relopt_ternary *)
+ allocate_reloption(kinds, RELOPT_TYPE_TERNARY, name, desc, lockmode);
+ newoption->default_val = default_val;
+
+ return newoption;
+}
+
+/*
+ * add_ternary_reloption
+ * Add a new ternary reloption
+ */
+void
+add_ternary_reloption(bits32 kinds, const char *name, const char *desc,
+ pg_ternary default_val, LOCKMODE lockmode)
+{
+ relopt_ternary *newoption;
+
+ newoption =
+ init_ternary_reloption(kinds, name, desc, default_val, lockmode);
+
+ add_reloption((relopt_gen *) newoption);
+}
+
+/*
+ * add_local_ternary_reloption
+ * Add a new ternary local reloption
+ *
+ * 'offset' is offset of ternary-typed field.
+ */
+void
+add_local_ternary_reloption(local_relopts *relopts, const char *name,
+ const char *desc, pg_ternary default_val,
+ int offset)
+{
+ relopt_ternary *newoption;
+
+ newoption =
+ init_ternary_reloption(RELOPT_KIND_LOCAL, name, desc, default_val, 0);
+
+ add_local_reloption(relopts, (relopt_gen *) newoption, offset);
+}
/*
* init_real_reloption
@@ -1626,6 +1705,19 @@ parse_one_reloption(relopt_value *option, char *text_str, int text_len,
option->gen->name, value)));
}
break;
+ case RELOPT_TYPE_TERNARY:
+ {
+ bool b;
+
+ parsed = parse_bool(value, &b);
+ option->values.ternary_val = b ? TERNARY_TRUE : TERNARY_FALSE;
+ if (validate && !parsed)
+ ereport(ERROR,
+ (errcode(ERRCODE_INVALID_PARAMETER_VALUE),
+ errmsg("invalid value for ternary option \"%s\": %s",
+ option->gen->name, value)));
+ }
+ break;
case RELOPT_TYPE_INT:
{
relopt_int *optint = (relopt_int *) option->gen;
@@ -1789,17 +1881,6 @@ fillRelOptions(void *rdopts, Size basesize,
char *itempos = ((char *) rdopts) + elems[j].offset;
char *string_val;
- /*
- * If isset_offset is provided, store whether the reloption is
- * set there.
- */
- if (elems[j].isset_offset > 0)
- {
- char *setpos = ((char *) rdopts) + elems[j].isset_offset;
-
- *(bool *) setpos = options[i].isset;
- }
-
switch (options[i].gen->type)
{
case RELOPT_TYPE_BOOL:
@@ -1807,6 +1888,11 @@ fillRelOptions(void *rdopts, Size basesize,
options[i].values.bool_val :
((relopt_bool *) options[i].gen)->default_val;
break;
+ case RELOPT_TYPE_TERNARY:
+ *(pg_ternary *) itempos = options[i].isset ?
+ options[i].values.ternary_val :
+ ((relopt_ternary *) options[i].gen)->default_val;
+ break;
case RELOPT_TYPE_INT:
*(int *) itempos = options[i].isset ?
options[i].values.int_val :
@@ -1923,8 +2009,8 @@ default_reloptions(Datum reloptions, bool validate, relopt_kind kind)
offsetof(StdRdOptions, parallel_workers)},
{"vacuum_index_cleanup", RELOPT_TYPE_ENUM,
offsetof(StdRdOptions, vacuum_index_cleanup)},
- {"vacuum_truncate", RELOPT_TYPE_BOOL,
- offsetof(StdRdOptions, vacuum_truncate), offsetof(StdRdOptions, vacuum_truncate_set)},
+ {"vacuum_truncate", RELOPT_TYPE_TERNARY,
+ offsetof(StdRdOptions, vacuum_truncate)},
{"vacuum_max_eager_freeze_failure_rate", RELOPT_TYPE_REAL,
offsetof(StdRdOptions, vacuum_max_eager_freeze_failure_rate)}
};
@@ -2004,7 +2090,6 @@ build_local_reloptions(local_relopts *relopts, Datum options, bool validate)
elems[i].optname = opt->option->name;
elems[i].opttype = opt->option->type;
elems[i].offset = opt->offset;
- elems[i].isset_offset = 0; /* not supported for local relopts yet */
i++;
}
diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c
index aa4fbec143f..696eab9bd97 100644
--- a/src/backend/commands/vacuum.c
+++ b/src/backend/commands/vacuum.c
@@ -2224,9 +2224,9 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams params,
{
StdRdOptions *opts = (StdRdOptions *) rel->rd_options;
- if (opts && opts->vacuum_truncate_set)
+ if (opts && opts->vacuum_truncate != TERNARY_UNSET)
{
- if (opts->vacuum_truncate)
+ if (opts->vacuum_truncate == TERNARY_TRUE)
params.truncate = VACOPTVALUE_ENABLED;
else
params.truncate = VACOPTVALUE_DISABLED;
diff --git a/src/include/access/reloptions.h b/src/include/access/reloptions.h
index 2f08e1b0cf0..dfbef2babf2 100644
--- a/src/include/access/reloptions.h
+++ b/src/include/access/reloptions.h
@@ -29,6 +29,7 @@
typedef enum relopt_type
{
RELOPT_TYPE_BOOL,
+ RELOPT_TYPE_TERNARY, /* on, off, unset */
RELOPT_TYPE_INT,
RELOPT_TYPE_REAL,
RELOPT_TYPE_ENUM,
@@ -80,6 +81,7 @@ typedef struct relopt_value
union
{
bool bool_val;
+ pg_ternary ternary_val;
int int_val;
double real_val;
int enum_val;
@@ -94,6 +96,12 @@ typedef struct relopt_bool
bool default_val;
} relopt_bool;
+typedef struct relopt_ternary
+{
+ relopt_gen gen;
+ int default_val;
+} relopt_ternary;
+
typedef struct relopt_int
{
relopt_gen gen;
@@ -152,19 +160,6 @@ typedef struct
const char *optname; /* option's name */
relopt_type opttype; /* option's datatype */
int offset; /* offset of field in result struct */
-
- /*
- * isset_offset is an optional offset of a field in the result struct that
- * stores whether the option is explicitly set for the relation or if it
- * just picked up the default value. In most cases, this can be
- * accomplished by giving the reloption a special out-of-range default
- * value (e.g., some integer reloptions use -2), but this isn't always
- * possible. For example, a Boolean reloption cannot be given an
- * out-of-range default, so we need another way to discover the source of
- * its value. This offset is only used if given a value greater than
- * zero.
- */
- int isset_offset;
} relopt_parse_elt;
/* Local reloption definition */
@@ -195,6 +190,9 @@ typedef struct local_relopts
extern relopt_kind add_reloption_kind(void);
extern void add_bool_reloption(bits32 kinds, const char *name, const char *desc,
bool default_val, LOCKMODE lockmode);
+extern void add_ternary_reloption(bits32 kinds, const char *name,
+ const char *desc, pg_ternary default_val,
+ LOCKMODE lockmode);
extern void add_int_reloption(bits32 kinds, const char *name, const char *desc,
int default_val, int min_val, int max_val,
LOCKMODE lockmode);
@@ -214,6 +212,9 @@ extern void register_reloptions_validator(local_relopts *relopts,
extern void add_local_bool_reloption(local_relopts *relopts, const char *name,
const char *desc, bool default_val,
int offset);
+extern void add_local_ternary_reloption(local_relopts *relopts,
+ const char *name, const char *desc,
+ pg_ternary default_val, int offset);
extern void add_local_int_reloption(local_relopts *relopts, const char *name,
const char *desc, int default_val,
int min_val, int max_val, int offset);
diff --git a/src/include/postgres.h b/src/include/postgres.h
index 7d93fbce709..47c88780776 100644
--- a/src/include/postgres.h
+++ b/src/include/postgres.h
@@ -543,6 +543,21 @@ Float8GetDatum(float8 X)
* ----------------------------------------------------------------
*/
+/*
+ * pg_ternary
+ * Boolean value with an extra "unset" value
+ *
+ * This enum can be used for values that want to distinguish between true,
+ * false, and unset.
+*/
+
+typedef enum pg_ternary
+{
+ TERNARY_FALSE = 0,
+ TERNARY_TRUE = 1,
+ TERNARY_UNSET = -1
+} pg_ternary;
+
/*
* NON_EXEC_STATIC: It's sometimes useful to define a variable or function
* that is normally static but extern when using EXEC_BACKEND (see
diff --git a/src/include/utils/rel.h b/src/include/utils/rel.h
index d03ab247788..5feb18a5373 100644
--- a/src/include/utils/rel.h
+++ b/src/include/utils/rel.h
@@ -347,8 +347,7 @@ typedef struct StdRdOptions
bool user_catalog_table; /* use as an additional catalog relation */
int parallel_workers; /* max number of parallel workers */
StdRdOptIndexCleanup vacuum_index_cleanup; /* controls index vacuuming */
- bool vacuum_truncate; /* enables vacuum to truncate a relation */
- bool vacuum_truncate_set; /* whether vacuum_truncate is set */
+ pg_ternary vacuum_truncate; /* enables vacuum to truncate a relation */
/*
* Fraction of pages in a relation that vacuum can eagerly scan and fail
diff --git a/src/test/regress/expected/reloptions.out b/src/test/regress/expected/reloptions.out
index 9de19b4e3f1..1c99f79ab01 100644
--- a/src/test/regress/expected/reloptions.out
+++ b/src/test/regress/expected/reloptions.out
@@ -98,6 +98,42 @@ SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass;
{fillfactor=13,autovacuum_enabled=false}
(1 row)
+-- Tests for future (FIXME) ternary options
+-- behave as boolean option: accept unassigned name and truncated value
+DROP TABLE reloptions_test;
+CREATE TABLE reloptions_test(i INT) WITH (vacuum_truncate);
+SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass;
+ reloptions
+------------------------
+ {vacuum_truncate=true}
+(1 row)
+
+DROP TABLE reloptions_test;
+CREATE TABLE reloptions_test(i INT) WITH (vacuum_truncate=FaLS);
+SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass;
+ reloptions
+------------------------
+ {vacuum_truncate=fals}
+(1 row)
+
+-- preferred "true" alias is used when storing
+DROP TABLE reloptions_test;
+CREATE TABLE reloptions_test(i INT) WITH (vacuum_index_cleanup=on);
+SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass;
+ reloptions
+---------------------------
+ {vacuum_index_cleanup=on}
+(1 row)
+
+-- custom "third" value is available
+DROP TABLE reloptions_test;
+CREATE TABLE reloptions_test(i INT) WITH (vacuum_index_cleanup=auto);
+SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass;
+ reloptions
+-----------------------------
+ {vacuum_index_cleanup=auto}
+(1 row)
+
-- Test vacuum_truncate option
DROP TABLE reloptions_test;
CREATE TEMP TABLE reloptions_test(i INT NOT NULL, j text)
diff --git a/src/test/regress/sql/reloptions.sql b/src/test/regress/sql/reloptions.sql
index 24fbe0b478d..f5980dafcbc 100644
--- a/src/test/regress/sql/reloptions.sql
+++ b/src/test/regress/sql/reloptions.sql
@@ -59,6 +59,27 @@ UPDATE pg_class
ALTER TABLE reloptions_test RESET (illegal_option);
SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass;
+-- Tests for future (FIXME) ternary options
+
+-- behave as boolean option: accept unassigned name and truncated value
+DROP TABLE reloptions_test;
+CREATE TABLE reloptions_test(i INT) WITH (vacuum_truncate);
+SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass;
+
+DROP TABLE reloptions_test;
+CREATE TABLE reloptions_test(i INT) WITH (vacuum_truncate=FaLS);
+SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass;
+
+-- preferred "true" alias is used when storing
+DROP TABLE reloptions_test;
+CREATE TABLE reloptions_test(i INT) WITH (vacuum_index_cleanup=on);
+SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass;
+
+-- custom "third" value is available
+DROP TABLE reloptions_test;
+CREATE TABLE reloptions_test(i INT) WITH (vacuum_index_cleanup=auto);
+SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass;
+
-- Test vacuum_truncate option
DROP TABLE reloptions_test;
diff --git a/src/tools/pgindent/typedefs.list b/src/tools/pgindent/typedefs.list
index 3f3a888fd0e..1c8610fd46c 100644
--- a/src/tools/pgindent/typedefs.list
+++ b/src/tools/pgindent/typedefs.list
@@ -3952,6 +3952,7 @@ pg_sha512_ctx
pg_snapshot
pg_special_case
pg_stack_base_t
+pg_ternary
pg_time_t
pg_time_usec_t
pg_tz
@@ -4079,6 +4080,7 @@ relopt_kind
relopt_parse_elt
relopt_real
relopt_string
+relopt_ternary
relopt_type
relopt_value
relopts_validator
--
2.47.3
--c36t3fndxhkzbl5z--
^ permalink raw reply [nested|flat] 868+ messages in thread
* [PATCH v3] Introduce ternary reloptions
@ 2026-01-16 15:04 Álvaro Herrera <[email protected]>
0 siblings, 0 replies; 868+ messages in thread
From: Álvaro Herrera @ 2026-01-16 15:04 UTC (permalink / raw)
Introduce ternary reloption as a replacement for current
`vacuum_truncate` implementation. Remove the `vacuum_truncate_set`
separate flag and use TERNARY_UNSET instead.
This could also be used for other options such as `vacuum_index_cleanup`
and `buffering`, but lets get the scaffolding in first.
Discussion: https://postgr.es/m/3474141.usfYGdeWWP@thinkpad-pgpro
---
src/backend/access/common/reloptions.c | 137 ++++++++++++++++++-----
src/backend/commands/vacuum.c | 4 +-
src/include/access/reloptions.h | 27 ++---
src/include/postgres.h | 15 +++
src/include/utils/rel.h | 3 +-
src/test/regress/expected/reloptions.out | 36 ++++++
src/test/regress/sql/reloptions.sql | 21 ++++
src/tools/pgindent/typedefs.list | 2 +
8 files changed, 202 insertions(+), 43 deletions(-)
diff --git a/src/backend/access/common/reloptions.c b/src/backend/access/common/reloptions.c
index 0b83f98ed5f..6f1f577581a 100644
--- a/src/backend/access/common/reloptions.c
+++ b/src/backend/access/common/reloptions.c
@@ -40,9 +40,9 @@
*
* To add an option:
*
- * (i) decide on a type (bool, integer, real, enum, string), name, default
- * value, upper and lower bounds (if applicable); for strings, consider a
- * validation routine.
+ * (i) decide on a type (bool, ternary, integer, real, enum, string), name,
+ * default value, upper and lower bounds (if applicable); for strings,
+ * consider a validation routine.
* (ii) add a record below (or use add_<type>_reloption).
* (iii) add it to the appropriate options struct (perhaps StdRdOptions)
* (iv) add it to the appropriate handling routine (perhaps
@@ -147,15 +147,6 @@ static relopt_bool boolRelOpts[] =
},
false
},
- {
- {
- "vacuum_truncate",
- "Enables vacuum to truncate empty pages at the end of this table",
- RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
- ShareUpdateExclusiveLock
- },
- true
- },
{
{
"deduplicate_items",
@@ -170,6 +161,25 @@ static relopt_bool boolRelOpts[] =
{{NULL}}
};
+static relopt_ternary ternaryRelOpts[] =
+{
+ {
+ {
+ "vacuum_truncate",
+ "Enables vacuum to truncate empty pages at the end of this table",
+ RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
+ ShareUpdateExclusiveLock
+ },
+ TERNARY_UNSET
+ },
+ /* list terminator */
+ {
+ {
+ NULL
+ }
+ }
+};
+
static relopt_int intRelOpts[] =
{
{
@@ -609,6 +619,13 @@ initialize_reloptions(void)
boolRelOpts[i].gen.lockmode));
j++;
}
+ for (i = 0; ternaryRelOpts[i].gen.name; i++)
+ {
+ Assert(DoLockModesConflict(ternaryRelOpts[i].gen.lockmode,
+ ternaryRelOpts[i].gen.lockmode));
+ j++;
+ }
+
for (i = 0; intRelOpts[i].gen.name; i++)
{
Assert(DoLockModesConflict(intRelOpts[i].gen.lockmode,
@@ -649,6 +666,14 @@ initialize_reloptions(void)
j++;
}
+ for (i = 0; ternaryRelOpts[i].gen.name; i++)
+ {
+ relOpts[j] = &ternaryRelOpts[i].gen;
+ relOpts[j]->type = RELOPT_TYPE_TERNARY;
+ relOpts[j]->namelen = strlen(relOpts[j]->name);
+ j++;
+ }
+
for (i = 0; intRelOpts[i].gen.name; i++)
{
relOpts[j] = &intRelOpts[i].gen;
@@ -809,6 +834,9 @@ allocate_reloption(bits32 kinds, int type, const char *name, const char *desc,
case RELOPT_TYPE_BOOL:
size = sizeof(relopt_bool);
break;
+ case RELOPT_TYPE_TERNARY:
+ size = sizeof(relopt_ternary);
+ break;
case RELOPT_TYPE_INT:
size = sizeof(relopt_int);
break;
@@ -892,6 +920,57 @@ add_local_bool_reloption(local_relopts *relopts, const char *name,
add_local_reloption(relopts, (relopt_gen *) newoption, offset);
}
+/*
+ * init_ternary_reloption
+ * Allocate and initialize a new ternary reloption
+ */
+static relopt_ternary *
+init_ternary_reloption(bits32 kinds, const char *name, const char *desc,
+ pg_ternary default_val, LOCKMODE lockmode)
+{
+ relopt_ternary *newoption;
+
+ newoption = (relopt_ternary *)
+ allocate_reloption(kinds, RELOPT_TYPE_TERNARY, name, desc, lockmode);
+ newoption->default_val = default_val;
+
+ return newoption;
+}
+
+/*
+ * add_ternary_reloption
+ * Add a new ternary reloption
+ */
+void
+add_ternary_reloption(bits32 kinds, const char *name, const char *desc,
+ pg_ternary default_val, LOCKMODE lockmode)
+{
+ relopt_ternary *newoption;
+
+ newoption =
+ init_ternary_reloption(kinds, name, desc, default_val, lockmode);
+
+ add_reloption((relopt_gen *) newoption);
+}
+
+/*
+ * add_local_ternary_reloption
+ * Add a new ternary local reloption
+ *
+ * 'offset' is offset of ternary-typed field.
+ */
+void
+add_local_ternary_reloption(local_relopts *relopts, const char *name,
+ const char *desc, pg_ternary default_val,
+ int offset)
+{
+ relopt_ternary *newoption;
+
+ newoption =
+ init_ternary_reloption(RELOPT_KIND_LOCAL, name, desc, default_val, 0);
+
+ add_local_reloption(relopts, (relopt_gen *) newoption, offset);
+}
/*
* init_real_reloption
@@ -1626,6 +1705,19 @@ parse_one_reloption(relopt_value *option, char *text_str, int text_len,
option->gen->name, value)));
}
break;
+ case RELOPT_TYPE_TERNARY:
+ {
+ bool b;
+
+ parsed = parse_bool(value, &b);
+ option->values.ternary_val = b ? TERNARY_TRUE : TERNARY_FALSE;
+ if (validate && !parsed)
+ ereport(ERROR,
+ (errcode(ERRCODE_INVALID_PARAMETER_VALUE),
+ errmsg("invalid value for ternary option \"%s\": %s",
+ option->gen->name, value)));
+ }
+ break;
case RELOPT_TYPE_INT:
{
relopt_int *optint = (relopt_int *) option->gen;
@@ -1789,17 +1881,6 @@ fillRelOptions(void *rdopts, Size basesize,
char *itempos = ((char *) rdopts) + elems[j].offset;
char *string_val;
- /*
- * If isset_offset is provided, store whether the reloption is
- * set there.
- */
- if (elems[j].isset_offset > 0)
- {
- char *setpos = ((char *) rdopts) + elems[j].isset_offset;
-
- *(bool *) setpos = options[i].isset;
- }
-
switch (options[i].gen->type)
{
case RELOPT_TYPE_BOOL:
@@ -1807,6 +1888,11 @@ fillRelOptions(void *rdopts, Size basesize,
options[i].values.bool_val :
((relopt_bool *) options[i].gen)->default_val;
break;
+ case RELOPT_TYPE_TERNARY:
+ *(pg_ternary *) itempos = options[i].isset ?
+ options[i].values.ternary_val :
+ ((relopt_ternary *) options[i].gen)->default_val;
+ break;
case RELOPT_TYPE_INT:
*(int *) itempos = options[i].isset ?
options[i].values.int_val :
@@ -1923,8 +2009,8 @@ default_reloptions(Datum reloptions, bool validate, relopt_kind kind)
offsetof(StdRdOptions, parallel_workers)},
{"vacuum_index_cleanup", RELOPT_TYPE_ENUM,
offsetof(StdRdOptions, vacuum_index_cleanup)},
- {"vacuum_truncate", RELOPT_TYPE_BOOL,
- offsetof(StdRdOptions, vacuum_truncate), offsetof(StdRdOptions, vacuum_truncate_set)},
+ {"vacuum_truncate", RELOPT_TYPE_TERNARY,
+ offsetof(StdRdOptions, vacuum_truncate)},
{"vacuum_max_eager_freeze_failure_rate", RELOPT_TYPE_REAL,
offsetof(StdRdOptions, vacuum_max_eager_freeze_failure_rate)}
};
@@ -2004,7 +2090,6 @@ build_local_reloptions(local_relopts *relopts, Datum options, bool validate)
elems[i].optname = opt->option->name;
elems[i].opttype = opt->option->type;
elems[i].offset = opt->offset;
- elems[i].isset_offset = 0; /* not supported for local relopts yet */
i++;
}
diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c
index aa4fbec143f..696eab9bd97 100644
--- a/src/backend/commands/vacuum.c
+++ b/src/backend/commands/vacuum.c
@@ -2224,9 +2224,9 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams params,
{
StdRdOptions *opts = (StdRdOptions *) rel->rd_options;
- if (opts && opts->vacuum_truncate_set)
+ if (opts && opts->vacuum_truncate != TERNARY_UNSET)
{
- if (opts->vacuum_truncate)
+ if (opts->vacuum_truncate == TERNARY_TRUE)
params.truncate = VACOPTVALUE_ENABLED;
else
params.truncate = VACOPTVALUE_DISABLED;
diff --git a/src/include/access/reloptions.h b/src/include/access/reloptions.h
index 2f08e1b0cf0..dfbef2babf2 100644
--- a/src/include/access/reloptions.h
+++ b/src/include/access/reloptions.h
@@ -29,6 +29,7 @@
typedef enum relopt_type
{
RELOPT_TYPE_BOOL,
+ RELOPT_TYPE_TERNARY, /* on, off, unset */
RELOPT_TYPE_INT,
RELOPT_TYPE_REAL,
RELOPT_TYPE_ENUM,
@@ -80,6 +81,7 @@ typedef struct relopt_value
union
{
bool bool_val;
+ pg_ternary ternary_val;
int int_val;
double real_val;
int enum_val;
@@ -94,6 +96,12 @@ typedef struct relopt_bool
bool default_val;
} relopt_bool;
+typedef struct relopt_ternary
+{
+ relopt_gen gen;
+ int default_val;
+} relopt_ternary;
+
typedef struct relopt_int
{
relopt_gen gen;
@@ -152,19 +160,6 @@ typedef struct
const char *optname; /* option's name */
relopt_type opttype; /* option's datatype */
int offset; /* offset of field in result struct */
-
- /*
- * isset_offset is an optional offset of a field in the result struct that
- * stores whether the option is explicitly set for the relation or if it
- * just picked up the default value. In most cases, this can be
- * accomplished by giving the reloption a special out-of-range default
- * value (e.g., some integer reloptions use -2), but this isn't always
- * possible. For example, a Boolean reloption cannot be given an
- * out-of-range default, so we need another way to discover the source of
- * its value. This offset is only used if given a value greater than
- * zero.
- */
- int isset_offset;
} relopt_parse_elt;
/* Local reloption definition */
@@ -195,6 +190,9 @@ typedef struct local_relopts
extern relopt_kind add_reloption_kind(void);
extern void add_bool_reloption(bits32 kinds, const char *name, const char *desc,
bool default_val, LOCKMODE lockmode);
+extern void add_ternary_reloption(bits32 kinds, const char *name,
+ const char *desc, pg_ternary default_val,
+ LOCKMODE lockmode);
extern void add_int_reloption(bits32 kinds, const char *name, const char *desc,
int default_val, int min_val, int max_val,
LOCKMODE lockmode);
@@ -214,6 +212,9 @@ extern void register_reloptions_validator(local_relopts *relopts,
extern void add_local_bool_reloption(local_relopts *relopts, const char *name,
const char *desc, bool default_val,
int offset);
+extern void add_local_ternary_reloption(local_relopts *relopts,
+ const char *name, const char *desc,
+ pg_ternary default_val, int offset);
extern void add_local_int_reloption(local_relopts *relopts, const char *name,
const char *desc, int default_val,
int min_val, int max_val, int offset);
diff --git a/src/include/postgres.h b/src/include/postgres.h
index 7d93fbce709..47c88780776 100644
--- a/src/include/postgres.h
+++ b/src/include/postgres.h
@@ -543,6 +543,21 @@ Float8GetDatum(float8 X)
* ----------------------------------------------------------------
*/
+/*
+ * pg_ternary
+ * Boolean value with an extra "unset" value
+ *
+ * This enum can be used for values that want to distinguish between true,
+ * false, and unset.
+*/
+
+typedef enum pg_ternary
+{
+ TERNARY_FALSE = 0,
+ TERNARY_TRUE = 1,
+ TERNARY_UNSET = -1
+} pg_ternary;
+
/*
* NON_EXEC_STATIC: It's sometimes useful to define a variable or function
* that is normally static but extern when using EXEC_BACKEND (see
diff --git a/src/include/utils/rel.h b/src/include/utils/rel.h
index d03ab247788..5feb18a5373 100644
--- a/src/include/utils/rel.h
+++ b/src/include/utils/rel.h
@@ -347,8 +347,7 @@ typedef struct StdRdOptions
bool user_catalog_table; /* use as an additional catalog relation */
int parallel_workers; /* max number of parallel workers */
StdRdOptIndexCleanup vacuum_index_cleanup; /* controls index vacuuming */
- bool vacuum_truncate; /* enables vacuum to truncate a relation */
- bool vacuum_truncate_set; /* whether vacuum_truncate is set */
+ pg_ternary vacuum_truncate; /* enables vacuum to truncate a relation */
/*
* Fraction of pages in a relation that vacuum can eagerly scan and fail
diff --git a/src/test/regress/expected/reloptions.out b/src/test/regress/expected/reloptions.out
index 9de19b4e3f1..1c99f79ab01 100644
--- a/src/test/regress/expected/reloptions.out
+++ b/src/test/regress/expected/reloptions.out
@@ -98,6 +98,42 @@ SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass;
{fillfactor=13,autovacuum_enabled=false}
(1 row)
+-- Tests for future (FIXME) ternary options
+-- behave as boolean option: accept unassigned name and truncated value
+DROP TABLE reloptions_test;
+CREATE TABLE reloptions_test(i INT) WITH (vacuum_truncate);
+SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass;
+ reloptions
+------------------------
+ {vacuum_truncate=true}
+(1 row)
+
+DROP TABLE reloptions_test;
+CREATE TABLE reloptions_test(i INT) WITH (vacuum_truncate=FaLS);
+SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass;
+ reloptions
+------------------------
+ {vacuum_truncate=fals}
+(1 row)
+
+-- preferred "true" alias is used when storing
+DROP TABLE reloptions_test;
+CREATE TABLE reloptions_test(i INT) WITH (vacuum_index_cleanup=on);
+SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass;
+ reloptions
+---------------------------
+ {vacuum_index_cleanup=on}
+(1 row)
+
+-- custom "third" value is available
+DROP TABLE reloptions_test;
+CREATE TABLE reloptions_test(i INT) WITH (vacuum_index_cleanup=auto);
+SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass;
+ reloptions
+-----------------------------
+ {vacuum_index_cleanup=auto}
+(1 row)
+
-- Test vacuum_truncate option
DROP TABLE reloptions_test;
CREATE TEMP TABLE reloptions_test(i INT NOT NULL, j text)
diff --git a/src/test/regress/sql/reloptions.sql b/src/test/regress/sql/reloptions.sql
index 24fbe0b478d..f5980dafcbc 100644
--- a/src/test/regress/sql/reloptions.sql
+++ b/src/test/regress/sql/reloptions.sql
@@ -59,6 +59,27 @@ UPDATE pg_class
ALTER TABLE reloptions_test RESET (illegal_option);
SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass;
+-- Tests for future (FIXME) ternary options
+
+-- behave as boolean option: accept unassigned name and truncated value
+DROP TABLE reloptions_test;
+CREATE TABLE reloptions_test(i INT) WITH (vacuum_truncate);
+SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass;
+
+DROP TABLE reloptions_test;
+CREATE TABLE reloptions_test(i INT) WITH (vacuum_truncate=FaLS);
+SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass;
+
+-- preferred "true" alias is used when storing
+DROP TABLE reloptions_test;
+CREATE TABLE reloptions_test(i INT) WITH (vacuum_index_cleanup=on);
+SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass;
+
+-- custom "third" value is available
+DROP TABLE reloptions_test;
+CREATE TABLE reloptions_test(i INT) WITH (vacuum_index_cleanup=auto);
+SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass;
+
-- Test vacuum_truncate option
DROP TABLE reloptions_test;
diff --git a/src/tools/pgindent/typedefs.list b/src/tools/pgindent/typedefs.list
index 3f3a888fd0e..1c8610fd46c 100644
--- a/src/tools/pgindent/typedefs.list
+++ b/src/tools/pgindent/typedefs.list
@@ -3952,6 +3952,7 @@ pg_sha512_ctx
pg_snapshot
pg_special_case
pg_stack_base_t
+pg_ternary
pg_time_t
pg_time_usec_t
pg_tz
@@ -4079,6 +4080,7 @@ relopt_kind
relopt_parse_elt
relopt_real
relopt_string
+relopt_ternary
relopt_type
relopt_value
relopts_validator
--
2.47.3
--c36t3fndxhkzbl5z--
^ permalink raw reply [nested|flat] 868+ messages in thread
* [PATCH v3] Introduce ternary reloptions
@ 2026-01-16 15:04 Álvaro Herrera <[email protected]>
0 siblings, 0 replies; 868+ messages in thread
From: Álvaro Herrera @ 2026-01-16 15:04 UTC (permalink / raw)
Introduce ternary reloption as a replacement for current
`vacuum_truncate` implementation. Remove the `vacuum_truncate_set`
separate flag and use TERNARY_UNSET instead.
This could also be used for other options such as `vacuum_index_cleanup`
and `buffering`, but lets get the scaffolding in first.
Discussion: https://postgr.es/m/3474141.usfYGdeWWP@thinkpad-pgpro
---
src/backend/access/common/reloptions.c | 137 ++++++++++++++++++-----
src/backend/commands/vacuum.c | 4 +-
src/include/access/reloptions.h | 27 ++---
src/include/postgres.h | 15 +++
src/include/utils/rel.h | 3 +-
src/test/regress/expected/reloptions.out | 36 ++++++
src/test/regress/sql/reloptions.sql | 21 ++++
src/tools/pgindent/typedefs.list | 2 +
8 files changed, 202 insertions(+), 43 deletions(-)
diff --git a/src/backend/access/common/reloptions.c b/src/backend/access/common/reloptions.c
index 0b83f98ed5f..6f1f577581a 100644
--- a/src/backend/access/common/reloptions.c
+++ b/src/backend/access/common/reloptions.c
@@ -40,9 +40,9 @@
*
* To add an option:
*
- * (i) decide on a type (bool, integer, real, enum, string), name, default
- * value, upper and lower bounds (if applicable); for strings, consider a
- * validation routine.
+ * (i) decide on a type (bool, ternary, integer, real, enum, string), name,
+ * default value, upper and lower bounds (if applicable); for strings,
+ * consider a validation routine.
* (ii) add a record below (or use add_<type>_reloption).
* (iii) add it to the appropriate options struct (perhaps StdRdOptions)
* (iv) add it to the appropriate handling routine (perhaps
@@ -147,15 +147,6 @@ static relopt_bool boolRelOpts[] =
},
false
},
- {
- {
- "vacuum_truncate",
- "Enables vacuum to truncate empty pages at the end of this table",
- RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
- ShareUpdateExclusiveLock
- },
- true
- },
{
{
"deduplicate_items",
@@ -170,6 +161,25 @@ static relopt_bool boolRelOpts[] =
{{NULL}}
};
+static relopt_ternary ternaryRelOpts[] =
+{
+ {
+ {
+ "vacuum_truncate",
+ "Enables vacuum to truncate empty pages at the end of this table",
+ RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
+ ShareUpdateExclusiveLock
+ },
+ TERNARY_UNSET
+ },
+ /* list terminator */
+ {
+ {
+ NULL
+ }
+ }
+};
+
static relopt_int intRelOpts[] =
{
{
@@ -609,6 +619,13 @@ initialize_reloptions(void)
boolRelOpts[i].gen.lockmode));
j++;
}
+ for (i = 0; ternaryRelOpts[i].gen.name; i++)
+ {
+ Assert(DoLockModesConflict(ternaryRelOpts[i].gen.lockmode,
+ ternaryRelOpts[i].gen.lockmode));
+ j++;
+ }
+
for (i = 0; intRelOpts[i].gen.name; i++)
{
Assert(DoLockModesConflict(intRelOpts[i].gen.lockmode,
@@ -649,6 +666,14 @@ initialize_reloptions(void)
j++;
}
+ for (i = 0; ternaryRelOpts[i].gen.name; i++)
+ {
+ relOpts[j] = &ternaryRelOpts[i].gen;
+ relOpts[j]->type = RELOPT_TYPE_TERNARY;
+ relOpts[j]->namelen = strlen(relOpts[j]->name);
+ j++;
+ }
+
for (i = 0; intRelOpts[i].gen.name; i++)
{
relOpts[j] = &intRelOpts[i].gen;
@@ -809,6 +834,9 @@ allocate_reloption(bits32 kinds, int type, const char *name, const char *desc,
case RELOPT_TYPE_BOOL:
size = sizeof(relopt_bool);
break;
+ case RELOPT_TYPE_TERNARY:
+ size = sizeof(relopt_ternary);
+ break;
case RELOPT_TYPE_INT:
size = sizeof(relopt_int);
break;
@@ -892,6 +920,57 @@ add_local_bool_reloption(local_relopts *relopts, const char *name,
add_local_reloption(relopts, (relopt_gen *) newoption, offset);
}
+/*
+ * init_ternary_reloption
+ * Allocate and initialize a new ternary reloption
+ */
+static relopt_ternary *
+init_ternary_reloption(bits32 kinds, const char *name, const char *desc,
+ pg_ternary default_val, LOCKMODE lockmode)
+{
+ relopt_ternary *newoption;
+
+ newoption = (relopt_ternary *)
+ allocate_reloption(kinds, RELOPT_TYPE_TERNARY, name, desc, lockmode);
+ newoption->default_val = default_val;
+
+ return newoption;
+}
+
+/*
+ * add_ternary_reloption
+ * Add a new ternary reloption
+ */
+void
+add_ternary_reloption(bits32 kinds, const char *name, const char *desc,
+ pg_ternary default_val, LOCKMODE lockmode)
+{
+ relopt_ternary *newoption;
+
+ newoption =
+ init_ternary_reloption(kinds, name, desc, default_val, lockmode);
+
+ add_reloption((relopt_gen *) newoption);
+}
+
+/*
+ * add_local_ternary_reloption
+ * Add a new ternary local reloption
+ *
+ * 'offset' is offset of ternary-typed field.
+ */
+void
+add_local_ternary_reloption(local_relopts *relopts, const char *name,
+ const char *desc, pg_ternary default_val,
+ int offset)
+{
+ relopt_ternary *newoption;
+
+ newoption =
+ init_ternary_reloption(RELOPT_KIND_LOCAL, name, desc, default_val, 0);
+
+ add_local_reloption(relopts, (relopt_gen *) newoption, offset);
+}
/*
* init_real_reloption
@@ -1626,6 +1705,19 @@ parse_one_reloption(relopt_value *option, char *text_str, int text_len,
option->gen->name, value)));
}
break;
+ case RELOPT_TYPE_TERNARY:
+ {
+ bool b;
+
+ parsed = parse_bool(value, &b);
+ option->values.ternary_val = b ? TERNARY_TRUE : TERNARY_FALSE;
+ if (validate && !parsed)
+ ereport(ERROR,
+ (errcode(ERRCODE_INVALID_PARAMETER_VALUE),
+ errmsg("invalid value for ternary option \"%s\": %s",
+ option->gen->name, value)));
+ }
+ break;
case RELOPT_TYPE_INT:
{
relopt_int *optint = (relopt_int *) option->gen;
@@ -1789,17 +1881,6 @@ fillRelOptions(void *rdopts, Size basesize,
char *itempos = ((char *) rdopts) + elems[j].offset;
char *string_val;
- /*
- * If isset_offset is provided, store whether the reloption is
- * set there.
- */
- if (elems[j].isset_offset > 0)
- {
- char *setpos = ((char *) rdopts) + elems[j].isset_offset;
-
- *(bool *) setpos = options[i].isset;
- }
-
switch (options[i].gen->type)
{
case RELOPT_TYPE_BOOL:
@@ -1807,6 +1888,11 @@ fillRelOptions(void *rdopts, Size basesize,
options[i].values.bool_val :
((relopt_bool *) options[i].gen)->default_val;
break;
+ case RELOPT_TYPE_TERNARY:
+ *(pg_ternary *) itempos = options[i].isset ?
+ options[i].values.ternary_val :
+ ((relopt_ternary *) options[i].gen)->default_val;
+ break;
case RELOPT_TYPE_INT:
*(int *) itempos = options[i].isset ?
options[i].values.int_val :
@@ -1923,8 +2009,8 @@ default_reloptions(Datum reloptions, bool validate, relopt_kind kind)
offsetof(StdRdOptions, parallel_workers)},
{"vacuum_index_cleanup", RELOPT_TYPE_ENUM,
offsetof(StdRdOptions, vacuum_index_cleanup)},
- {"vacuum_truncate", RELOPT_TYPE_BOOL,
- offsetof(StdRdOptions, vacuum_truncate), offsetof(StdRdOptions, vacuum_truncate_set)},
+ {"vacuum_truncate", RELOPT_TYPE_TERNARY,
+ offsetof(StdRdOptions, vacuum_truncate)},
{"vacuum_max_eager_freeze_failure_rate", RELOPT_TYPE_REAL,
offsetof(StdRdOptions, vacuum_max_eager_freeze_failure_rate)}
};
@@ -2004,7 +2090,6 @@ build_local_reloptions(local_relopts *relopts, Datum options, bool validate)
elems[i].optname = opt->option->name;
elems[i].opttype = opt->option->type;
elems[i].offset = opt->offset;
- elems[i].isset_offset = 0; /* not supported for local relopts yet */
i++;
}
diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c
index aa4fbec143f..696eab9bd97 100644
--- a/src/backend/commands/vacuum.c
+++ b/src/backend/commands/vacuum.c
@@ -2224,9 +2224,9 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams params,
{
StdRdOptions *opts = (StdRdOptions *) rel->rd_options;
- if (opts && opts->vacuum_truncate_set)
+ if (opts && opts->vacuum_truncate != TERNARY_UNSET)
{
- if (opts->vacuum_truncate)
+ if (opts->vacuum_truncate == TERNARY_TRUE)
params.truncate = VACOPTVALUE_ENABLED;
else
params.truncate = VACOPTVALUE_DISABLED;
diff --git a/src/include/access/reloptions.h b/src/include/access/reloptions.h
index 2f08e1b0cf0..dfbef2babf2 100644
--- a/src/include/access/reloptions.h
+++ b/src/include/access/reloptions.h
@@ -29,6 +29,7 @@
typedef enum relopt_type
{
RELOPT_TYPE_BOOL,
+ RELOPT_TYPE_TERNARY, /* on, off, unset */
RELOPT_TYPE_INT,
RELOPT_TYPE_REAL,
RELOPT_TYPE_ENUM,
@@ -80,6 +81,7 @@ typedef struct relopt_value
union
{
bool bool_val;
+ pg_ternary ternary_val;
int int_val;
double real_val;
int enum_val;
@@ -94,6 +96,12 @@ typedef struct relopt_bool
bool default_val;
} relopt_bool;
+typedef struct relopt_ternary
+{
+ relopt_gen gen;
+ int default_val;
+} relopt_ternary;
+
typedef struct relopt_int
{
relopt_gen gen;
@@ -152,19 +160,6 @@ typedef struct
const char *optname; /* option's name */
relopt_type opttype; /* option's datatype */
int offset; /* offset of field in result struct */
-
- /*
- * isset_offset is an optional offset of a field in the result struct that
- * stores whether the option is explicitly set for the relation or if it
- * just picked up the default value. In most cases, this can be
- * accomplished by giving the reloption a special out-of-range default
- * value (e.g., some integer reloptions use -2), but this isn't always
- * possible. For example, a Boolean reloption cannot be given an
- * out-of-range default, so we need another way to discover the source of
- * its value. This offset is only used if given a value greater than
- * zero.
- */
- int isset_offset;
} relopt_parse_elt;
/* Local reloption definition */
@@ -195,6 +190,9 @@ typedef struct local_relopts
extern relopt_kind add_reloption_kind(void);
extern void add_bool_reloption(bits32 kinds, const char *name, const char *desc,
bool default_val, LOCKMODE lockmode);
+extern void add_ternary_reloption(bits32 kinds, const char *name,
+ const char *desc, pg_ternary default_val,
+ LOCKMODE lockmode);
extern void add_int_reloption(bits32 kinds, const char *name, const char *desc,
int default_val, int min_val, int max_val,
LOCKMODE lockmode);
@@ -214,6 +212,9 @@ extern void register_reloptions_validator(local_relopts *relopts,
extern void add_local_bool_reloption(local_relopts *relopts, const char *name,
const char *desc, bool default_val,
int offset);
+extern void add_local_ternary_reloption(local_relopts *relopts,
+ const char *name, const char *desc,
+ pg_ternary default_val, int offset);
extern void add_local_int_reloption(local_relopts *relopts, const char *name,
const char *desc, int default_val,
int min_val, int max_val, int offset);
diff --git a/src/include/postgres.h b/src/include/postgres.h
index 7d93fbce709..47c88780776 100644
--- a/src/include/postgres.h
+++ b/src/include/postgres.h
@@ -543,6 +543,21 @@ Float8GetDatum(float8 X)
* ----------------------------------------------------------------
*/
+/*
+ * pg_ternary
+ * Boolean value with an extra "unset" value
+ *
+ * This enum can be used for values that want to distinguish between true,
+ * false, and unset.
+*/
+
+typedef enum pg_ternary
+{
+ TERNARY_FALSE = 0,
+ TERNARY_TRUE = 1,
+ TERNARY_UNSET = -1
+} pg_ternary;
+
/*
* NON_EXEC_STATIC: It's sometimes useful to define a variable or function
* that is normally static but extern when using EXEC_BACKEND (see
diff --git a/src/include/utils/rel.h b/src/include/utils/rel.h
index d03ab247788..5feb18a5373 100644
--- a/src/include/utils/rel.h
+++ b/src/include/utils/rel.h
@@ -347,8 +347,7 @@ typedef struct StdRdOptions
bool user_catalog_table; /* use as an additional catalog relation */
int parallel_workers; /* max number of parallel workers */
StdRdOptIndexCleanup vacuum_index_cleanup; /* controls index vacuuming */
- bool vacuum_truncate; /* enables vacuum to truncate a relation */
- bool vacuum_truncate_set; /* whether vacuum_truncate is set */
+ pg_ternary vacuum_truncate; /* enables vacuum to truncate a relation */
/*
* Fraction of pages in a relation that vacuum can eagerly scan and fail
diff --git a/src/test/regress/expected/reloptions.out b/src/test/regress/expected/reloptions.out
index 9de19b4e3f1..1c99f79ab01 100644
--- a/src/test/regress/expected/reloptions.out
+++ b/src/test/regress/expected/reloptions.out
@@ -98,6 +98,42 @@ SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass;
{fillfactor=13,autovacuum_enabled=false}
(1 row)
+-- Tests for future (FIXME) ternary options
+-- behave as boolean option: accept unassigned name and truncated value
+DROP TABLE reloptions_test;
+CREATE TABLE reloptions_test(i INT) WITH (vacuum_truncate);
+SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass;
+ reloptions
+------------------------
+ {vacuum_truncate=true}
+(1 row)
+
+DROP TABLE reloptions_test;
+CREATE TABLE reloptions_test(i INT) WITH (vacuum_truncate=FaLS);
+SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass;
+ reloptions
+------------------------
+ {vacuum_truncate=fals}
+(1 row)
+
+-- preferred "true" alias is used when storing
+DROP TABLE reloptions_test;
+CREATE TABLE reloptions_test(i INT) WITH (vacuum_index_cleanup=on);
+SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass;
+ reloptions
+---------------------------
+ {vacuum_index_cleanup=on}
+(1 row)
+
+-- custom "third" value is available
+DROP TABLE reloptions_test;
+CREATE TABLE reloptions_test(i INT) WITH (vacuum_index_cleanup=auto);
+SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass;
+ reloptions
+-----------------------------
+ {vacuum_index_cleanup=auto}
+(1 row)
+
-- Test vacuum_truncate option
DROP TABLE reloptions_test;
CREATE TEMP TABLE reloptions_test(i INT NOT NULL, j text)
diff --git a/src/test/regress/sql/reloptions.sql b/src/test/regress/sql/reloptions.sql
index 24fbe0b478d..f5980dafcbc 100644
--- a/src/test/regress/sql/reloptions.sql
+++ b/src/test/regress/sql/reloptions.sql
@@ -59,6 +59,27 @@ UPDATE pg_class
ALTER TABLE reloptions_test RESET (illegal_option);
SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass;
+-- Tests for future (FIXME) ternary options
+
+-- behave as boolean option: accept unassigned name and truncated value
+DROP TABLE reloptions_test;
+CREATE TABLE reloptions_test(i INT) WITH (vacuum_truncate);
+SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass;
+
+DROP TABLE reloptions_test;
+CREATE TABLE reloptions_test(i INT) WITH (vacuum_truncate=FaLS);
+SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass;
+
+-- preferred "true" alias is used when storing
+DROP TABLE reloptions_test;
+CREATE TABLE reloptions_test(i INT) WITH (vacuum_index_cleanup=on);
+SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass;
+
+-- custom "third" value is available
+DROP TABLE reloptions_test;
+CREATE TABLE reloptions_test(i INT) WITH (vacuum_index_cleanup=auto);
+SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass;
+
-- Test vacuum_truncate option
DROP TABLE reloptions_test;
diff --git a/src/tools/pgindent/typedefs.list b/src/tools/pgindent/typedefs.list
index 3f3a888fd0e..1c8610fd46c 100644
--- a/src/tools/pgindent/typedefs.list
+++ b/src/tools/pgindent/typedefs.list
@@ -3952,6 +3952,7 @@ pg_sha512_ctx
pg_snapshot
pg_special_case
pg_stack_base_t
+pg_ternary
pg_time_t
pg_time_usec_t
pg_tz
@@ -4079,6 +4080,7 @@ relopt_kind
relopt_parse_elt
relopt_real
relopt_string
+relopt_ternary
relopt_type
relopt_value
relopts_validator
--
2.47.3
--c36t3fndxhkzbl5z--
^ permalink raw reply [nested|flat] 868+ messages in thread
* [PATCH v3] Introduce ternary reloptions
@ 2026-01-16 15:04 Álvaro Herrera <[email protected]>
0 siblings, 0 replies; 868+ messages in thread
From: Álvaro Herrera @ 2026-01-16 15:04 UTC (permalink / raw)
Introduce ternary reloption as a replacement for current
`vacuum_truncate` implementation. Remove the `vacuum_truncate_set`
separate flag and use TERNARY_UNSET instead.
This could also be used for other options such as `vacuum_index_cleanup`
and `buffering`, but lets get the scaffolding in first.
Discussion: https://postgr.es/m/3474141.usfYGdeWWP@thinkpad-pgpro
---
src/backend/access/common/reloptions.c | 137 ++++++++++++++++++-----
src/backend/commands/vacuum.c | 4 +-
src/include/access/reloptions.h | 27 ++---
src/include/postgres.h | 15 +++
src/include/utils/rel.h | 3 +-
src/test/regress/expected/reloptions.out | 36 ++++++
src/test/regress/sql/reloptions.sql | 21 ++++
src/tools/pgindent/typedefs.list | 2 +
8 files changed, 202 insertions(+), 43 deletions(-)
diff --git a/src/backend/access/common/reloptions.c b/src/backend/access/common/reloptions.c
index 0b83f98ed5f..6f1f577581a 100644
--- a/src/backend/access/common/reloptions.c
+++ b/src/backend/access/common/reloptions.c
@@ -40,9 +40,9 @@
*
* To add an option:
*
- * (i) decide on a type (bool, integer, real, enum, string), name, default
- * value, upper and lower bounds (if applicable); for strings, consider a
- * validation routine.
+ * (i) decide on a type (bool, ternary, integer, real, enum, string), name,
+ * default value, upper and lower bounds (if applicable); for strings,
+ * consider a validation routine.
* (ii) add a record below (or use add_<type>_reloption).
* (iii) add it to the appropriate options struct (perhaps StdRdOptions)
* (iv) add it to the appropriate handling routine (perhaps
@@ -147,15 +147,6 @@ static relopt_bool boolRelOpts[] =
},
false
},
- {
- {
- "vacuum_truncate",
- "Enables vacuum to truncate empty pages at the end of this table",
- RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
- ShareUpdateExclusiveLock
- },
- true
- },
{
{
"deduplicate_items",
@@ -170,6 +161,25 @@ static relopt_bool boolRelOpts[] =
{{NULL}}
};
+static relopt_ternary ternaryRelOpts[] =
+{
+ {
+ {
+ "vacuum_truncate",
+ "Enables vacuum to truncate empty pages at the end of this table",
+ RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
+ ShareUpdateExclusiveLock
+ },
+ TERNARY_UNSET
+ },
+ /* list terminator */
+ {
+ {
+ NULL
+ }
+ }
+};
+
static relopt_int intRelOpts[] =
{
{
@@ -609,6 +619,13 @@ initialize_reloptions(void)
boolRelOpts[i].gen.lockmode));
j++;
}
+ for (i = 0; ternaryRelOpts[i].gen.name; i++)
+ {
+ Assert(DoLockModesConflict(ternaryRelOpts[i].gen.lockmode,
+ ternaryRelOpts[i].gen.lockmode));
+ j++;
+ }
+
for (i = 0; intRelOpts[i].gen.name; i++)
{
Assert(DoLockModesConflict(intRelOpts[i].gen.lockmode,
@@ -649,6 +666,14 @@ initialize_reloptions(void)
j++;
}
+ for (i = 0; ternaryRelOpts[i].gen.name; i++)
+ {
+ relOpts[j] = &ternaryRelOpts[i].gen;
+ relOpts[j]->type = RELOPT_TYPE_TERNARY;
+ relOpts[j]->namelen = strlen(relOpts[j]->name);
+ j++;
+ }
+
for (i = 0; intRelOpts[i].gen.name; i++)
{
relOpts[j] = &intRelOpts[i].gen;
@@ -809,6 +834,9 @@ allocate_reloption(bits32 kinds, int type, const char *name, const char *desc,
case RELOPT_TYPE_BOOL:
size = sizeof(relopt_bool);
break;
+ case RELOPT_TYPE_TERNARY:
+ size = sizeof(relopt_ternary);
+ break;
case RELOPT_TYPE_INT:
size = sizeof(relopt_int);
break;
@@ -892,6 +920,57 @@ add_local_bool_reloption(local_relopts *relopts, const char *name,
add_local_reloption(relopts, (relopt_gen *) newoption, offset);
}
+/*
+ * init_ternary_reloption
+ * Allocate and initialize a new ternary reloption
+ */
+static relopt_ternary *
+init_ternary_reloption(bits32 kinds, const char *name, const char *desc,
+ pg_ternary default_val, LOCKMODE lockmode)
+{
+ relopt_ternary *newoption;
+
+ newoption = (relopt_ternary *)
+ allocate_reloption(kinds, RELOPT_TYPE_TERNARY, name, desc, lockmode);
+ newoption->default_val = default_val;
+
+ return newoption;
+}
+
+/*
+ * add_ternary_reloption
+ * Add a new ternary reloption
+ */
+void
+add_ternary_reloption(bits32 kinds, const char *name, const char *desc,
+ pg_ternary default_val, LOCKMODE lockmode)
+{
+ relopt_ternary *newoption;
+
+ newoption =
+ init_ternary_reloption(kinds, name, desc, default_val, lockmode);
+
+ add_reloption((relopt_gen *) newoption);
+}
+
+/*
+ * add_local_ternary_reloption
+ * Add a new ternary local reloption
+ *
+ * 'offset' is offset of ternary-typed field.
+ */
+void
+add_local_ternary_reloption(local_relopts *relopts, const char *name,
+ const char *desc, pg_ternary default_val,
+ int offset)
+{
+ relopt_ternary *newoption;
+
+ newoption =
+ init_ternary_reloption(RELOPT_KIND_LOCAL, name, desc, default_val, 0);
+
+ add_local_reloption(relopts, (relopt_gen *) newoption, offset);
+}
/*
* init_real_reloption
@@ -1626,6 +1705,19 @@ parse_one_reloption(relopt_value *option, char *text_str, int text_len,
option->gen->name, value)));
}
break;
+ case RELOPT_TYPE_TERNARY:
+ {
+ bool b;
+
+ parsed = parse_bool(value, &b);
+ option->values.ternary_val = b ? TERNARY_TRUE : TERNARY_FALSE;
+ if (validate && !parsed)
+ ereport(ERROR,
+ (errcode(ERRCODE_INVALID_PARAMETER_VALUE),
+ errmsg("invalid value for ternary option \"%s\": %s",
+ option->gen->name, value)));
+ }
+ break;
case RELOPT_TYPE_INT:
{
relopt_int *optint = (relopt_int *) option->gen;
@@ -1789,17 +1881,6 @@ fillRelOptions(void *rdopts, Size basesize,
char *itempos = ((char *) rdopts) + elems[j].offset;
char *string_val;
- /*
- * If isset_offset is provided, store whether the reloption is
- * set there.
- */
- if (elems[j].isset_offset > 0)
- {
- char *setpos = ((char *) rdopts) + elems[j].isset_offset;
-
- *(bool *) setpos = options[i].isset;
- }
-
switch (options[i].gen->type)
{
case RELOPT_TYPE_BOOL:
@@ -1807,6 +1888,11 @@ fillRelOptions(void *rdopts, Size basesize,
options[i].values.bool_val :
((relopt_bool *) options[i].gen)->default_val;
break;
+ case RELOPT_TYPE_TERNARY:
+ *(pg_ternary *) itempos = options[i].isset ?
+ options[i].values.ternary_val :
+ ((relopt_ternary *) options[i].gen)->default_val;
+ break;
case RELOPT_TYPE_INT:
*(int *) itempos = options[i].isset ?
options[i].values.int_val :
@@ -1923,8 +2009,8 @@ default_reloptions(Datum reloptions, bool validate, relopt_kind kind)
offsetof(StdRdOptions, parallel_workers)},
{"vacuum_index_cleanup", RELOPT_TYPE_ENUM,
offsetof(StdRdOptions, vacuum_index_cleanup)},
- {"vacuum_truncate", RELOPT_TYPE_BOOL,
- offsetof(StdRdOptions, vacuum_truncate), offsetof(StdRdOptions, vacuum_truncate_set)},
+ {"vacuum_truncate", RELOPT_TYPE_TERNARY,
+ offsetof(StdRdOptions, vacuum_truncate)},
{"vacuum_max_eager_freeze_failure_rate", RELOPT_TYPE_REAL,
offsetof(StdRdOptions, vacuum_max_eager_freeze_failure_rate)}
};
@@ -2004,7 +2090,6 @@ build_local_reloptions(local_relopts *relopts, Datum options, bool validate)
elems[i].optname = opt->option->name;
elems[i].opttype = opt->option->type;
elems[i].offset = opt->offset;
- elems[i].isset_offset = 0; /* not supported for local relopts yet */
i++;
}
diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c
index aa4fbec143f..696eab9bd97 100644
--- a/src/backend/commands/vacuum.c
+++ b/src/backend/commands/vacuum.c
@@ -2224,9 +2224,9 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams params,
{
StdRdOptions *opts = (StdRdOptions *) rel->rd_options;
- if (opts && opts->vacuum_truncate_set)
+ if (opts && opts->vacuum_truncate != TERNARY_UNSET)
{
- if (opts->vacuum_truncate)
+ if (opts->vacuum_truncate == TERNARY_TRUE)
params.truncate = VACOPTVALUE_ENABLED;
else
params.truncate = VACOPTVALUE_DISABLED;
diff --git a/src/include/access/reloptions.h b/src/include/access/reloptions.h
index 2f08e1b0cf0..dfbef2babf2 100644
--- a/src/include/access/reloptions.h
+++ b/src/include/access/reloptions.h
@@ -29,6 +29,7 @@
typedef enum relopt_type
{
RELOPT_TYPE_BOOL,
+ RELOPT_TYPE_TERNARY, /* on, off, unset */
RELOPT_TYPE_INT,
RELOPT_TYPE_REAL,
RELOPT_TYPE_ENUM,
@@ -80,6 +81,7 @@ typedef struct relopt_value
union
{
bool bool_val;
+ pg_ternary ternary_val;
int int_val;
double real_val;
int enum_val;
@@ -94,6 +96,12 @@ typedef struct relopt_bool
bool default_val;
} relopt_bool;
+typedef struct relopt_ternary
+{
+ relopt_gen gen;
+ int default_val;
+} relopt_ternary;
+
typedef struct relopt_int
{
relopt_gen gen;
@@ -152,19 +160,6 @@ typedef struct
const char *optname; /* option's name */
relopt_type opttype; /* option's datatype */
int offset; /* offset of field in result struct */
-
- /*
- * isset_offset is an optional offset of a field in the result struct that
- * stores whether the option is explicitly set for the relation or if it
- * just picked up the default value. In most cases, this can be
- * accomplished by giving the reloption a special out-of-range default
- * value (e.g., some integer reloptions use -2), but this isn't always
- * possible. For example, a Boolean reloption cannot be given an
- * out-of-range default, so we need another way to discover the source of
- * its value. This offset is only used if given a value greater than
- * zero.
- */
- int isset_offset;
} relopt_parse_elt;
/* Local reloption definition */
@@ -195,6 +190,9 @@ typedef struct local_relopts
extern relopt_kind add_reloption_kind(void);
extern void add_bool_reloption(bits32 kinds, const char *name, const char *desc,
bool default_val, LOCKMODE lockmode);
+extern void add_ternary_reloption(bits32 kinds, const char *name,
+ const char *desc, pg_ternary default_val,
+ LOCKMODE lockmode);
extern void add_int_reloption(bits32 kinds, const char *name, const char *desc,
int default_val, int min_val, int max_val,
LOCKMODE lockmode);
@@ -214,6 +212,9 @@ extern void register_reloptions_validator(local_relopts *relopts,
extern void add_local_bool_reloption(local_relopts *relopts, const char *name,
const char *desc, bool default_val,
int offset);
+extern void add_local_ternary_reloption(local_relopts *relopts,
+ const char *name, const char *desc,
+ pg_ternary default_val, int offset);
extern void add_local_int_reloption(local_relopts *relopts, const char *name,
const char *desc, int default_val,
int min_val, int max_val, int offset);
diff --git a/src/include/postgres.h b/src/include/postgres.h
index 7d93fbce709..47c88780776 100644
--- a/src/include/postgres.h
+++ b/src/include/postgres.h
@@ -543,6 +543,21 @@ Float8GetDatum(float8 X)
* ----------------------------------------------------------------
*/
+/*
+ * pg_ternary
+ * Boolean value with an extra "unset" value
+ *
+ * This enum can be used for values that want to distinguish between true,
+ * false, and unset.
+*/
+
+typedef enum pg_ternary
+{
+ TERNARY_FALSE = 0,
+ TERNARY_TRUE = 1,
+ TERNARY_UNSET = -1
+} pg_ternary;
+
/*
* NON_EXEC_STATIC: It's sometimes useful to define a variable or function
* that is normally static but extern when using EXEC_BACKEND (see
diff --git a/src/include/utils/rel.h b/src/include/utils/rel.h
index d03ab247788..5feb18a5373 100644
--- a/src/include/utils/rel.h
+++ b/src/include/utils/rel.h
@@ -347,8 +347,7 @@ typedef struct StdRdOptions
bool user_catalog_table; /* use as an additional catalog relation */
int parallel_workers; /* max number of parallel workers */
StdRdOptIndexCleanup vacuum_index_cleanup; /* controls index vacuuming */
- bool vacuum_truncate; /* enables vacuum to truncate a relation */
- bool vacuum_truncate_set; /* whether vacuum_truncate is set */
+ pg_ternary vacuum_truncate; /* enables vacuum to truncate a relation */
/*
* Fraction of pages in a relation that vacuum can eagerly scan and fail
diff --git a/src/test/regress/expected/reloptions.out b/src/test/regress/expected/reloptions.out
index 9de19b4e3f1..1c99f79ab01 100644
--- a/src/test/regress/expected/reloptions.out
+++ b/src/test/regress/expected/reloptions.out
@@ -98,6 +98,42 @@ SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass;
{fillfactor=13,autovacuum_enabled=false}
(1 row)
+-- Tests for future (FIXME) ternary options
+-- behave as boolean option: accept unassigned name and truncated value
+DROP TABLE reloptions_test;
+CREATE TABLE reloptions_test(i INT) WITH (vacuum_truncate);
+SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass;
+ reloptions
+------------------------
+ {vacuum_truncate=true}
+(1 row)
+
+DROP TABLE reloptions_test;
+CREATE TABLE reloptions_test(i INT) WITH (vacuum_truncate=FaLS);
+SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass;
+ reloptions
+------------------------
+ {vacuum_truncate=fals}
+(1 row)
+
+-- preferred "true" alias is used when storing
+DROP TABLE reloptions_test;
+CREATE TABLE reloptions_test(i INT) WITH (vacuum_index_cleanup=on);
+SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass;
+ reloptions
+---------------------------
+ {vacuum_index_cleanup=on}
+(1 row)
+
+-- custom "third" value is available
+DROP TABLE reloptions_test;
+CREATE TABLE reloptions_test(i INT) WITH (vacuum_index_cleanup=auto);
+SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass;
+ reloptions
+-----------------------------
+ {vacuum_index_cleanup=auto}
+(1 row)
+
-- Test vacuum_truncate option
DROP TABLE reloptions_test;
CREATE TEMP TABLE reloptions_test(i INT NOT NULL, j text)
diff --git a/src/test/regress/sql/reloptions.sql b/src/test/regress/sql/reloptions.sql
index 24fbe0b478d..f5980dafcbc 100644
--- a/src/test/regress/sql/reloptions.sql
+++ b/src/test/regress/sql/reloptions.sql
@@ -59,6 +59,27 @@ UPDATE pg_class
ALTER TABLE reloptions_test RESET (illegal_option);
SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass;
+-- Tests for future (FIXME) ternary options
+
+-- behave as boolean option: accept unassigned name and truncated value
+DROP TABLE reloptions_test;
+CREATE TABLE reloptions_test(i INT) WITH (vacuum_truncate);
+SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass;
+
+DROP TABLE reloptions_test;
+CREATE TABLE reloptions_test(i INT) WITH (vacuum_truncate=FaLS);
+SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass;
+
+-- preferred "true" alias is used when storing
+DROP TABLE reloptions_test;
+CREATE TABLE reloptions_test(i INT) WITH (vacuum_index_cleanup=on);
+SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass;
+
+-- custom "third" value is available
+DROP TABLE reloptions_test;
+CREATE TABLE reloptions_test(i INT) WITH (vacuum_index_cleanup=auto);
+SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass;
+
-- Test vacuum_truncate option
DROP TABLE reloptions_test;
diff --git a/src/tools/pgindent/typedefs.list b/src/tools/pgindent/typedefs.list
index 3f3a888fd0e..1c8610fd46c 100644
--- a/src/tools/pgindent/typedefs.list
+++ b/src/tools/pgindent/typedefs.list
@@ -3952,6 +3952,7 @@ pg_sha512_ctx
pg_snapshot
pg_special_case
pg_stack_base_t
+pg_ternary
pg_time_t
pg_time_usec_t
pg_tz
@@ -4079,6 +4080,7 @@ relopt_kind
relopt_parse_elt
relopt_real
relopt_string
+relopt_ternary
relopt_type
relopt_value
relopts_validator
--
2.47.3
--c36t3fndxhkzbl5z--
^ permalink raw reply [nested|flat] 868+ messages in thread
* [PATCH v3] Introduce ternary reloptions
@ 2026-01-16 15:04 Álvaro Herrera <[email protected]>
0 siblings, 0 replies; 868+ messages in thread
From: Álvaro Herrera @ 2026-01-16 15:04 UTC (permalink / raw)
Introduce ternary reloption as a replacement for current
`vacuum_truncate` implementation. Remove the `vacuum_truncate_set`
separate flag and use TERNARY_UNSET instead.
This could also be used for other options such as `vacuum_index_cleanup`
and `buffering`, but lets get the scaffolding in first.
Discussion: https://postgr.es/m/3474141.usfYGdeWWP@thinkpad-pgpro
---
src/backend/access/common/reloptions.c | 137 ++++++++++++++++++-----
src/backend/commands/vacuum.c | 4 +-
src/include/access/reloptions.h | 27 ++---
src/include/postgres.h | 15 +++
src/include/utils/rel.h | 3 +-
src/test/regress/expected/reloptions.out | 36 ++++++
src/test/regress/sql/reloptions.sql | 21 ++++
src/tools/pgindent/typedefs.list | 2 +
8 files changed, 202 insertions(+), 43 deletions(-)
diff --git a/src/backend/access/common/reloptions.c b/src/backend/access/common/reloptions.c
index 0b83f98ed5f..6f1f577581a 100644
--- a/src/backend/access/common/reloptions.c
+++ b/src/backend/access/common/reloptions.c
@@ -40,9 +40,9 @@
*
* To add an option:
*
- * (i) decide on a type (bool, integer, real, enum, string), name, default
- * value, upper and lower bounds (if applicable); for strings, consider a
- * validation routine.
+ * (i) decide on a type (bool, ternary, integer, real, enum, string), name,
+ * default value, upper and lower bounds (if applicable); for strings,
+ * consider a validation routine.
* (ii) add a record below (or use add_<type>_reloption).
* (iii) add it to the appropriate options struct (perhaps StdRdOptions)
* (iv) add it to the appropriate handling routine (perhaps
@@ -147,15 +147,6 @@ static relopt_bool boolRelOpts[] =
},
false
},
- {
- {
- "vacuum_truncate",
- "Enables vacuum to truncate empty pages at the end of this table",
- RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
- ShareUpdateExclusiveLock
- },
- true
- },
{
{
"deduplicate_items",
@@ -170,6 +161,25 @@ static relopt_bool boolRelOpts[] =
{{NULL}}
};
+static relopt_ternary ternaryRelOpts[] =
+{
+ {
+ {
+ "vacuum_truncate",
+ "Enables vacuum to truncate empty pages at the end of this table",
+ RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
+ ShareUpdateExclusiveLock
+ },
+ TERNARY_UNSET
+ },
+ /* list terminator */
+ {
+ {
+ NULL
+ }
+ }
+};
+
static relopt_int intRelOpts[] =
{
{
@@ -609,6 +619,13 @@ initialize_reloptions(void)
boolRelOpts[i].gen.lockmode));
j++;
}
+ for (i = 0; ternaryRelOpts[i].gen.name; i++)
+ {
+ Assert(DoLockModesConflict(ternaryRelOpts[i].gen.lockmode,
+ ternaryRelOpts[i].gen.lockmode));
+ j++;
+ }
+
for (i = 0; intRelOpts[i].gen.name; i++)
{
Assert(DoLockModesConflict(intRelOpts[i].gen.lockmode,
@@ -649,6 +666,14 @@ initialize_reloptions(void)
j++;
}
+ for (i = 0; ternaryRelOpts[i].gen.name; i++)
+ {
+ relOpts[j] = &ternaryRelOpts[i].gen;
+ relOpts[j]->type = RELOPT_TYPE_TERNARY;
+ relOpts[j]->namelen = strlen(relOpts[j]->name);
+ j++;
+ }
+
for (i = 0; intRelOpts[i].gen.name; i++)
{
relOpts[j] = &intRelOpts[i].gen;
@@ -809,6 +834,9 @@ allocate_reloption(bits32 kinds, int type, const char *name, const char *desc,
case RELOPT_TYPE_BOOL:
size = sizeof(relopt_bool);
break;
+ case RELOPT_TYPE_TERNARY:
+ size = sizeof(relopt_ternary);
+ break;
case RELOPT_TYPE_INT:
size = sizeof(relopt_int);
break;
@@ -892,6 +920,57 @@ add_local_bool_reloption(local_relopts *relopts, const char *name,
add_local_reloption(relopts, (relopt_gen *) newoption, offset);
}
+/*
+ * init_ternary_reloption
+ * Allocate and initialize a new ternary reloption
+ */
+static relopt_ternary *
+init_ternary_reloption(bits32 kinds, const char *name, const char *desc,
+ pg_ternary default_val, LOCKMODE lockmode)
+{
+ relopt_ternary *newoption;
+
+ newoption = (relopt_ternary *)
+ allocate_reloption(kinds, RELOPT_TYPE_TERNARY, name, desc, lockmode);
+ newoption->default_val = default_val;
+
+ return newoption;
+}
+
+/*
+ * add_ternary_reloption
+ * Add a new ternary reloption
+ */
+void
+add_ternary_reloption(bits32 kinds, const char *name, const char *desc,
+ pg_ternary default_val, LOCKMODE lockmode)
+{
+ relopt_ternary *newoption;
+
+ newoption =
+ init_ternary_reloption(kinds, name, desc, default_val, lockmode);
+
+ add_reloption((relopt_gen *) newoption);
+}
+
+/*
+ * add_local_ternary_reloption
+ * Add a new ternary local reloption
+ *
+ * 'offset' is offset of ternary-typed field.
+ */
+void
+add_local_ternary_reloption(local_relopts *relopts, const char *name,
+ const char *desc, pg_ternary default_val,
+ int offset)
+{
+ relopt_ternary *newoption;
+
+ newoption =
+ init_ternary_reloption(RELOPT_KIND_LOCAL, name, desc, default_val, 0);
+
+ add_local_reloption(relopts, (relopt_gen *) newoption, offset);
+}
/*
* init_real_reloption
@@ -1626,6 +1705,19 @@ parse_one_reloption(relopt_value *option, char *text_str, int text_len,
option->gen->name, value)));
}
break;
+ case RELOPT_TYPE_TERNARY:
+ {
+ bool b;
+
+ parsed = parse_bool(value, &b);
+ option->values.ternary_val = b ? TERNARY_TRUE : TERNARY_FALSE;
+ if (validate && !parsed)
+ ereport(ERROR,
+ (errcode(ERRCODE_INVALID_PARAMETER_VALUE),
+ errmsg("invalid value for ternary option \"%s\": %s",
+ option->gen->name, value)));
+ }
+ break;
case RELOPT_TYPE_INT:
{
relopt_int *optint = (relopt_int *) option->gen;
@@ -1789,17 +1881,6 @@ fillRelOptions(void *rdopts, Size basesize,
char *itempos = ((char *) rdopts) + elems[j].offset;
char *string_val;
- /*
- * If isset_offset is provided, store whether the reloption is
- * set there.
- */
- if (elems[j].isset_offset > 0)
- {
- char *setpos = ((char *) rdopts) + elems[j].isset_offset;
-
- *(bool *) setpos = options[i].isset;
- }
-
switch (options[i].gen->type)
{
case RELOPT_TYPE_BOOL:
@@ -1807,6 +1888,11 @@ fillRelOptions(void *rdopts, Size basesize,
options[i].values.bool_val :
((relopt_bool *) options[i].gen)->default_val;
break;
+ case RELOPT_TYPE_TERNARY:
+ *(pg_ternary *) itempos = options[i].isset ?
+ options[i].values.ternary_val :
+ ((relopt_ternary *) options[i].gen)->default_val;
+ break;
case RELOPT_TYPE_INT:
*(int *) itempos = options[i].isset ?
options[i].values.int_val :
@@ -1923,8 +2009,8 @@ default_reloptions(Datum reloptions, bool validate, relopt_kind kind)
offsetof(StdRdOptions, parallel_workers)},
{"vacuum_index_cleanup", RELOPT_TYPE_ENUM,
offsetof(StdRdOptions, vacuum_index_cleanup)},
- {"vacuum_truncate", RELOPT_TYPE_BOOL,
- offsetof(StdRdOptions, vacuum_truncate), offsetof(StdRdOptions, vacuum_truncate_set)},
+ {"vacuum_truncate", RELOPT_TYPE_TERNARY,
+ offsetof(StdRdOptions, vacuum_truncate)},
{"vacuum_max_eager_freeze_failure_rate", RELOPT_TYPE_REAL,
offsetof(StdRdOptions, vacuum_max_eager_freeze_failure_rate)}
};
@@ -2004,7 +2090,6 @@ build_local_reloptions(local_relopts *relopts, Datum options, bool validate)
elems[i].optname = opt->option->name;
elems[i].opttype = opt->option->type;
elems[i].offset = opt->offset;
- elems[i].isset_offset = 0; /* not supported for local relopts yet */
i++;
}
diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c
index aa4fbec143f..696eab9bd97 100644
--- a/src/backend/commands/vacuum.c
+++ b/src/backend/commands/vacuum.c
@@ -2224,9 +2224,9 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams params,
{
StdRdOptions *opts = (StdRdOptions *) rel->rd_options;
- if (opts && opts->vacuum_truncate_set)
+ if (opts && opts->vacuum_truncate != TERNARY_UNSET)
{
- if (opts->vacuum_truncate)
+ if (opts->vacuum_truncate == TERNARY_TRUE)
params.truncate = VACOPTVALUE_ENABLED;
else
params.truncate = VACOPTVALUE_DISABLED;
diff --git a/src/include/access/reloptions.h b/src/include/access/reloptions.h
index 2f08e1b0cf0..dfbef2babf2 100644
--- a/src/include/access/reloptions.h
+++ b/src/include/access/reloptions.h
@@ -29,6 +29,7 @@
typedef enum relopt_type
{
RELOPT_TYPE_BOOL,
+ RELOPT_TYPE_TERNARY, /* on, off, unset */
RELOPT_TYPE_INT,
RELOPT_TYPE_REAL,
RELOPT_TYPE_ENUM,
@@ -80,6 +81,7 @@ typedef struct relopt_value
union
{
bool bool_val;
+ pg_ternary ternary_val;
int int_val;
double real_val;
int enum_val;
@@ -94,6 +96,12 @@ typedef struct relopt_bool
bool default_val;
} relopt_bool;
+typedef struct relopt_ternary
+{
+ relopt_gen gen;
+ int default_val;
+} relopt_ternary;
+
typedef struct relopt_int
{
relopt_gen gen;
@@ -152,19 +160,6 @@ typedef struct
const char *optname; /* option's name */
relopt_type opttype; /* option's datatype */
int offset; /* offset of field in result struct */
-
- /*
- * isset_offset is an optional offset of a field in the result struct that
- * stores whether the option is explicitly set for the relation or if it
- * just picked up the default value. In most cases, this can be
- * accomplished by giving the reloption a special out-of-range default
- * value (e.g., some integer reloptions use -2), but this isn't always
- * possible. For example, a Boolean reloption cannot be given an
- * out-of-range default, so we need another way to discover the source of
- * its value. This offset is only used if given a value greater than
- * zero.
- */
- int isset_offset;
} relopt_parse_elt;
/* Local reloption definition */
@@ -195,6 +190,9 @@ typedef struct local_relopts
extern relopt_kind add_reloption_kind(void);
extern void add_bool_reloption(bits32 kinds, const char *name, const char *desc,
bool default_val, LOCKMODE lockmode);
+extern void add_ternary_reloption(bits32 kinds, const char *name,
+ const char *desc, pg_ternary default_val,
+ LOCKMODE lockmode);
extern void add_int_reloption(bits32 kinds, const char *name, const char *desc,
int default_val, int min_val, int max_val,
LOCKMODE lockmode);
@@ -214,6 +212,9 @@ extern void register_reloptions_validator(local_relopts *relopts,
extern void add_local_bool_reloption(local_relopts *relopts, const char *name,
const char *desc, bool default_val,
int offset);
+extern void add_local_ternary_reloption(local_relopts *relopts,
+ const char *name, const char *desc,
+ pg_ternary default_val, int offset);
extern void add_local_int_reloption(local_relopts *relopts, const char *name,
const char *desc, int default_val,
int min_val, int max_val, int offset);
diff --git a/src/include/postgres.h b/src/include/postgres.h
index 7d93fbce709..47c88780776 100644
--- a/src/include/postgres.h
+++ b/src/include/postgres.h
@@ -543,6 +543,21 @@ Float8GetDatum(float8 X)
* ----------------------------------------------------------------
*/
+/*
+ * pg_ternary
+ * Boolean value with an extra "unset" value
+ *
+ * This enum can be used for values that want to distinguish between true,
+ * false, and unset.
+*/
+
+typedef enum pg_ternary
+{
+ TERNARY_FALSE = 0,
+ TERNARY_TRUE = 1,
+ TERNARY_UNSET = -1
+} pg_ternary;
+
/*
* NON_EXEC_STATIC: It's sometimes useful to define a variable or function
* that is normally static but extern when using EXEC_BACKEND (see
diff --git a/src/include/utils/rel.h b/src/include/utils/rel.h
index d03ab247788..5feb18a5373 100644
--- a/src/include/utils/rel.h
+++ b/src/include/utils/rel.h
@@ -347,8 +347,7 @@ typedef struct StdRdOptions
bool user_catalog_table; /* use as an additional catalog relation */
int parallel_workers; /* max number of parallel workers */
StdRdOptIndexCleanup vacuum_index_cleanup; /* controls index vacuuming */
- bool vacuum_truncate; /* enables vacuum to truncate a relation */
- bool vacuum_truncate_set; /* whether vacuum_truncate is set */
+ pg_ternary vacuum_truncate; /* enables vacuum to truncate a relation */
/*
* Fraction of pages in a relation that vacuum can eagerly scan and fail
diff --git a/src/test/regress/expected/reloptions.out b/src/test/regress/expected/reloptions.out
index 9de19b4e3f1..1c99f79ab01 100644
--- a/src/test/regress/expected/reloptions.out
+++ b/src/test/regress/expected/reloptions.out
@@ -98,6 +98,42 @@ SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass;
{fillfactor=13,autovacuum_enabled=false}
(1 row)
+-- Tests for future (FIXME) ternary options
+-- behave as boolean option: accept unassigned name and truncated value
+DROP TABLE reloptions_test;
+CREATE TABLE reloptions_test(i INT) WITH (vacuum_truncate);
+SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass;
+ reloptions
+------------------------
+ {vacuum_truncate=true}
+(1 row)
+
+DROP TABLE reloptions_test;
+CREATE TABLE reloptions_test(i INT) WITH (vacuum_truncate=FaLS);
+SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass;
+ reloptions
+------------------------
+ {vacuum_truncate=fals}
+(1 row)
+
+-- preferred "true" alias is used when storing
+DROP TABLE reloptions_test;
+CREATE TABLE reloptions_test(i INT) WITH (vacuum_index_cleanup=on);
+SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass;
+ reloptions
+---------------------------
+ {vacuum_index_cleanup=on}
+(1 row)
+
+-- custom "third" value is available
+DROP TABLE reloptions_test;
+CREATE TABLE reloptions_test(i INT) WITH (vacuum_index_cleanup=auto);
+SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass;
+ reloptions
+-----------------------------
+ {vacuum_index_cleanup=auto}
+(1 row)
+
-- Test vacuum_truncate option
DROP TABLE reloptions_test;
CREATE TEMP TABLE reloptions_test(i INT NOT NULL, j text)
diff --git a/src/test/regress/sql/reloptions.sql b/src/test/regress/sql/reloptions.sql
index 24fbe0b478d..f5980dafcbc 100644
--- a/src/test/regress/sql/reloptions.sql
+++ b/src/test/regress/sql/reloptions.sql
@@ -59,6 +59,27 @@ UPDATE pg_class
ALTER TABLE reloptions_test RESET (illegal_option);
SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass;
+-- Tests for future (FIXME) ternary options
+
+-- behave as boolean option: accept unassigned name and truncated value
+DROP TABLE reloptions_test;
+CREATE TABLE reloptions_test(i INT) WITH (vacuum_truncate);
+SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass;
+
+DROP TABLE reloptions_test;
+CREATE TABLE reloptions_test(i INT) WITH (vacuum_truncate=FaLS);
+SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass;
+
+-- preferred "true" alias is used when storing
+DROP TABLE reloptions_test;
+CREATE TABLE reloptions_test(i INT) WITH (vacuum_index_cleanup=on);
+SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass;
+
+-- custom "third" value is available
+DROP TABLE reloptions_test;
+CREATE TABLE reloptions_test(i INT) WITH (vacuum_index_cleanup=auto);
+SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass;
+
-- Test vacuum_truncate option
DROP TABLE reloptions_test;
diff --git a/src/tools/pgindent/typedefs.list b/src/tools/pgindent/typedefs.list
index 3f3a888fd0e..1c8610fd46c 100644
--- a/src/tools/pgindent/typedefs.list
+++ b/src/tools/pgindent/typedefs.list
@@ -3952,6 +3952,7 @@ pg_sha512_ctx
pg_snapshot
pg_special_case
pg_stack_base_t
+pg_ternary
pg_time_t
pg_time_usec_t
pg_tz
@@ -4079,6 +4080,7 @@ relopt_kind
relopt_parse_elt
relopt_real
relopt_string
+relopt_ternary
relopt_type
relopt_value
relopts_validator
--
2.47.3
--c36t3fndxhkzbl5z--
^ permalink raw reply [nested|flat] 868+ messages in thread
* [PATCH v3] Introduce ternary reloptions
@ 2026-01-16 15:04 Álvaro Herrera <[email protected]>
0 siblings, 0 replies; 868+ messages in thread
From: Álvaro Herrera @ 2026-01-16 15:04 UTC (permalink / raw)
Introduce ternary reloption as a replacement for current
`vacuum_truncate` implementation. Remove the `vacuum_truncate_set`
separate flag and use TERNARY_UNSET instead.
This could also be used for other options such as `vacuum_index_cleanup`
and `buffering`, but lets get the scaffolding in first.
Discussion: https://postgr.es/m/3474141.usfYGdeWWP@thinkpad-pgpro
---
src/backend/access/common/reloptions.c | 137 ++++++++++++++++++-----
src/backend/commands/vacuum.c | 4 +-
src/include/access/reloptions.h | 27 ++---
src/include/postgres.h | 15 +++
src/include/utils/rel.h | 3 +-
src/test/regress/expected/reloptions.out | 36 ++++++
src/test/regress/sql/reloptions.sql | 21 ++++
src/tools/pgindent/typedefs.list | 2 +
8 files changed, 202 insertions(+), 43 deletions(-)
diff --git a/src/backend/access/common/reloptions.c b/src/backend/access/common/reloptions.c
index 0b83f98ed5f..6f1f577581a 100644
--- a/src/backend/access/common/reloptions.c
+++ b/src/backend/access/common/reloptions.c
@@ -40,9 +40,9 @@
*
* To add an option:
*
- * (i) decide on a type (bool, integer, real, enum, string), name, default
- * value, upper and lower bounds (if applicable); for strings, consider a
- * validation routine.
+ * (i) decide on a type (bool, ternary, integer, real, enum, string), name,
+ * default value, upper and lower bounds (if applicable); for strings,
+ * consider a validation routine.
* (ii) add a record below (or use add_<type>_reloption).
* (iii) add it to the appropriate options struct (perhaps StdRdOptions)
* (iv) add it to the appropriate handling routine (perhaps
@@ -147,15 +147,6 @@ static relopt_bool boolRelOpts[] =
},
false
},
- {
- {
- "vacuum_truncate",
- "Enables vacuum to truncate empty pages at the end of this table",
- RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
- ShareUpdateExclusiveLock
- },
- true
- },
{
{
"deduplicate_items",
@@ -170,6 +161,25 @@ static relopt_bool boolRelOpts[] =
{{NULL}}
};
+static relopt_ternary ternaryRelOpts[] =
+{
+ {
+ {
+ "vacuum_truncate",
+ "Enables vacuum to truncate empty pages at the end of this table",
+ RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
+ ShareUpdateExclusiveLock
+ },
+ TERNARY_UNSET
+ },
+ /* list terminator */
+ {
+ {
+ NULL
+ }
+ }
+};
+
static relopt_int intRelOpts[] =
{
{
@@ -609,6 +619,13 @@ initialize_reloptions(void)
boolRelOpts[i].gen.lockmode));
j++;
}
+ for (i = 0; ternaryRelOpts[i].gen.name; i++)
+ {
+ Assert(DoLockModesConflict(ternaryRelOpts[i].gen.lockmode,
+ ternaryRelOpts[i].gen.lockmode));
+ j++;
+ }
+
for (i = 0; intRelOpts[i].gen.name; i++)
{
Assert(DoLockModesConflict(intRelOpts[i].gen.lockmode,
@@ -649,6 +666,14 @@ initialize_reloptions(void)
j++;
}
+ for (i = 0; ternaryRelOpts[i].gen.name; i++)
+ {
+ relOpts[j] = &ternaryRelOpts[i].gen;
+ relOpts[j]->type = RELOPT_TYPE_TERNARY;
+ relOpts[j]->namelen = strlen(relOpts[j]->name);
+ j++;
+ }
+
for (i = 0; intRelOpts[i].gen.name; i++)
{
relOpts[j] = &intRelOpts[i].gen;
@@ -809,6 +834,9 @@ allocate_reloption(bits32 kinds, int type, const char *name, const char *desc,
case RELOPT_TYPE_BOOL:
size = sizeof(relopt_bool);
break;
+ case RELOPT_TYPE_TERNARY:
+ size = sizeof(relopt_ternary);
+ break;
case RELOPT_TYPE_INT:
size = sizeof(relopt_int);
break;
@@ -892,6 +920,57 @@ add_local_bool_reloption(local_relopts *relopts, const char *name,
add_local_reloption(relopts, (relopt_gen *) newoption, offset);
}
+/*
+ * init_ternary_reloption
+ * Allocate and initialize a new ternary reloption
+ */
+static relopt_ternary *
+init_ternary_reloption(bits32 kinds, const char *name, const char *desc,
+ pg_ternary default_val, LOCKMODE lockmode)
+{
+ relopt_ternary *newoption;
+
+ newoption = (relopt_ternary *)
+ allocate_reloption(kinds, RELOPT_TYPE_TERNARY, name, desc, lockmode);
+ newoption->default_val = default_val;
+
+ return newoption;
+}
+
+/*
+ * add_ternary_reloption
+ * Add a new ternary reloption
+ */
+void
+add_ternary_reloption(bits32 kinds, const char *name, const char *desc,
+ pg_ternary default_val, LOCKMODE lockmode)
+{
+ relopt_ternary *newoption;
+
+ newoption =
+ init_ternary_reloption(kinds, name, desc, default_val, lockmode);
+
+ add_reloption((relopt_gen *) newoption);
+}
+
+/*
+ * add_local_ternary_reloption
+ * Add a new ternary local reloption
+ *
+ * 'offset' is offset of ternary-typed field.
+ */
+void
+add_local_ternary_reloption(local_relopts *relopts, const char *name,
+ const char *desc, pg_ternary default_val,
+ int offset)
+{
+ relopt_ternary *newoption;
+
+ newoption =
+ init_ternary_reloption(RELOPT_KIND_LOCAL, name, desc, default_val, 0);
+
+ add_local_reloption(relopts, (relopt_gen *) newoption, offset);
+}
/*
* init_real_reloption
@@ -1626,6 +1705,19 @@ parse_one_reloption(relopt_value *option, char *text_str, int text_len,
option->gen->name, value)));
}
break;
+ case RELOPT_TYPE_TERNARY:
+ {
+ bool b;
+
+ parsed = parse_bool(value, &b);
+ option->values.ternary_val = b ? TERNARY_TRUE : TERNARY_FALSE;
+ if (validate && !parsed)
+ ereport(ERROR,
+ (errcode(ERRCODE_INVALID_PARAMETER_VALUE),
+ errmsg("invalid value for ternary option \"%s\": %s",
+ option->gen->name, value)));
+ }
+ break;
case RELOPT_TYPE_INT:
{
relopt_int *optint = (relopt_int *) option->gen;
@@ -1789,17 +1881,6 @@ fillRelOptions(void *rdopts, Size basesize,
char *itempos = ((char *) rdopts) + elems[j].offset;
char *string_val;
- /*
- * If isset_offset is provided, store whether the reloption is
- * set there.
- */
- if (elems[j].isset_offset > 0)
- {
- char *setpos = ((char *) rdopts) + elems[j].isset_offset;
-
- *(bool *) setpos = options[i].isset;
- }
-
switch (options[i].gen->type)
{
case RELOPT_TYPE_BOOL:
@@ -1807,6 +1888,11 @@ fillRelOptions(void *rdopts, Size basesize,
options[i].values.bool_val :
((relopt_bool *) options[i].gen)->default_val;
break;
+ case RELOPT_TYPE_TERNARY:
+ *(pg_ternary *) itempos = options[i].isset ?
+ options[i].values.ternary_val :
+ ((relopt_ternary *) options[i].gen)->default_val;
+ break;
case RELOPT_TYPE_INT:
*(int *) itempos = options[i].isset ?
options[i].values.int_val :
@@ -1923,8 +2009,8 @@ default_reloptions(Datum reloptions, bool validate, relopt_kind kind)
offsetof(StdRdOptions, parallel_workers)},
{"vacuum_index_cleanup", RELOPT_TYPE_ENUM,
offsetof(StdRdOptions, vacuum_index_cleanup)},
- {"vacuum_truncate", RELOPT_TYPE_BOOL,
- offsetof(StdRdOptions, vacuum_truncate), offsetof(StdRdOptions, vacuum_truncate_set)},
+ {"vacuum_truncate", RELOPT_TYPE_TERNARY,
+ offsetof(StdRdOptions, vacuum_truncate)},
{"vacuum_max_eager_freeze_failure_rate", RELOPT_TYPE_REAL,
offsetof(StdRdOptions, vacuum_max_eager_freeze_failure_rate)}
};
@@ -2004,7 +2090,6 @@ build_local_reloptions(local_relopts *relopts, Datum options, bool validate)
elems[i].optname = opt->option->name;
elems[i].opttype = opt->option->type;
elems[i].offset = opt->offset;
- elems[i].isset_offset = 0; /* not supported for local relopts yet */
i++;
}
diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c
index aa4fbec143f..696eab9bd97 100644
--- a/src/backend/commands/vacuum.c
+++ b/src/backend/commands/vacuum.c
@@ -2224,9 +2224,9 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams params,
{
StdRdOptions *opts = (StdRdOptions *) rel->rd_options;
- if (opts && opts->vacuum_truncate_set)
+ if (opts && opts->vacuum_truncate != TERNARY_UNSET)
{
- if (opts->vacuum_truncate)
+ if (opts->vacuum_truncate == TERNARY_TRUE)
params.truncate = VACOPTVALUE_ENABLED;
else
params.truncate = VACOPTVALUE_DISABLED;
diff --git a/src/include/access/reloptions.h b/src/include/access/reloptions.h
index 2f08e1b0cf0..dfbef2babf2 100644
--- a/src/include/access/reloptions.h
+++ b/src/include/access/reloptions.h
@@ -29,6 +29,7 @@
typedef enum relopt_type
{
RELOPT_TYPE_BOOL,
+ RELOPT_TYPE_TERNARY, /* on, off, unset */
RELOPT_TYPE_INT,
RELOPT_TYPE_REAL,
RELOPT_TYPE_ENUM,
@@ -80,6 +81,7 @@ typedef struct relopt_value
union
{
bool bool_val;
+ pg_ternary ternary_val;
int int_val;
double real_val;
int enum_val;
@@ -94,6 +96,12 @@ typedef struct relopt_bool
bool default_val;
} relopt_bool;
+typedef struct relopt_ternary
+{
+ relopt_gen gen;
+ int default_val;
+} relopt_ternary;
+
typedef struct relopt_int
{
relopt_gen gen;
@@ -152,19 +160,6 @@ typedef struct
const char *optname; /* option's name */
relopt_type opttype; /* option's datatype */
int offset; /* offset of field in result struct */
-
- /*
- * isset_offset is an optional offset of a field in the result struct that
- * stores whether the option is explicitly set for the relation or if it
- * just picked up the default value. In most cases, this can be
- * accomplished by giving the reloption a special out-of-range default
- * value (e.g., some integer reloptions use -2), but this isn't always
- * possible. For example, a Boolean reloption cannot be given an
- * out-of-range default, so we need another way to discover the source of
- * its value. This offset is only used if given a value greater than
- * zero.
- */
- int isset_offset;
} relopt_parse_elt;
/* Local reloption definition */
@@ -195,6 +190,9 @@ typedef struct local_relopts
extern relopt_kind add_reloption_kind(void);
extern void add_bool_reloption(bits32 kinds, const char *name, const char *desc,
bool default_val, LOCKMODE lockmode);
+extern void add_ternary_reloption(bits32 kinds, const char *name,
+ const char *desc, pg_ternary default_val,
+ LOCKMODE lockmode);
extern void add_int_reloption(bits32 kinds, const char *name, const char *desc,
int default_val, int min_val, int max_val,
LOCKMODE lockmode);
@@ -214,6 +212,9 @@ extern void register_reloptions_validator(local_relopts *relopts,
extern void add_local_bool_reloption(local_relopts *relopts, const char *name,
const char *desc, bool default_val,
int offset);
+extern void add_local_ternary_reloption(local_relopts *relopts,
+ const char *name, const char *desc,
+ pg_ternary default_val, int offset);
extern void add_local_int_reloption(local_relopts *relopts, const char *name,
const char *desc, int default_val,
int min_val, int max_val, int offset);
diff --git a/src/include/postgres.h b/src/include/postgres.h
index 7d93fbce709..47c88780776 100644
--- a/src/include/postgres.h
+++ b/src/include/postgres.h
@@ -543,6 +543,21 @@ Float8GetDatum(float8 X)
* ----------------------------------------------------------------
*/
+/*
+ * pg_ternary
+ * Boolean value with an extra "unset" value
+ *
+ * This enum can be used for values that want to distinguish between true,
+ * false, and unset.
+*/
+
+typedef enum pg_ternary
+{
+ TERNARY_FALSE = 0,
+ TERNARY_TRUE = 1,
+ TERNARY_UNSET = -1
+} pg_ternary;
+
/*
* NON_EXEC_STATIC: It's sometimes useful to define a variable or function
* that is normally static but extern when using EXEC_BACKEND (see
diff --git a/src/include/utils/rel.h b/src/include/utils/rel.h
index d03ab247788..5feb18a5373 100644
--- a/src/include/utils/rel.h
+++ b/src/include/utils/rel.h
@@ -347,8 +347,7 @@ typedef struct StdRdOptions
bool user_catalog_table; /* use as an additional catalog relation */
int parallel_workers; /* max number of parallel workers */
StdRdOptIndexCleanup vacuum_index_cleanup; /* controls index vacuuming */
- bool vacuum_truncate; /* enables vacuum to truncate a relation */
- bool vacuum_truncate_set; /* whether vacuum_truncate is set */
+ pg_ternary vacuum_truncate; /* enables vacuum to truncate a relation */
/*
* Fraction of pages in a relation that vacuum can eagerly scan and fail
diff --git a/src/test/regress/expected/reloptions.out b/src/test/regress/expected/reloptions.out
index 9de19b4e3f1..1c99f79ab01 100644
--- a/src/test/regress/expected/reloptions.out
+++ b/src/test/regress/expected/reloptions.out
@@ -98,6 +98,42 @@ SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass;
{fillfactor=13,autovacuum_enabled=false}
(1 row)
+-- Tests for future (FIXME) ternary options
+-- behave as boolean option: accept unassigned name and truncated value
+DROP TABLE reloptions_test;
+CREATE TABLE reloptions_test(i INT) WITH (vacuum_truncate);
+SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass;
+ reloptions
+------------------------
+ {vacuum_truncate=true}
+(1 row)
+
+DROP TABLE reloptions_test;
+CREATE TABLE reloptions_test(i INT) WITH (vacuum_truncate=FaLS);
+SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass;
+ reloptions
+------------------------
+ {vacuum_truncate=fals}
+(1 row)
+
+-- preferred "true" alias is used when storing
+DROP TABLE reloptions_test;
+CREATE TABLE reloptions_test(i INT) WITH (vacuum_index_cleanup=on);
+SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass;
+ reloptions
+---------------------------
+ {vacuum_index_cleanup=on}
+(1 row)
+
+-- custom "third" value is available
+DROP TABLE reloptions_test;
+CREATE TABLE reloptions_test(i INT) WITH (vacuum_index_cleanup=auto);
+SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass;
+ reloptions
+-----------------------------
+ {vacuum_index_cleanup=auto}
+(1 row)
+
-- Test vacuum_truncate option
DROP TABLE reloptions_test;
CREATE TEMP TABLE reloptions_test(i INT NOT NULL, j text)
diff --git a/src/test/regress/sql/reloptions.sql b/src/test/regress/sql/reloptions.sql
index 24fbe0b478d..f5980dafcbc 100644
--- a/src/test/regress/sql/reloptions.sql
+++ b/src/test/regress/sql/reloptions.sql
@@ -59,6 +59,27 @@ UPDATE pg_class
ALTER TABLE reloptions_test RESET (illegal_option);
SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass;
+-- Tests for future (FIXME) ternary options
+
+-- behave as boolean option: accept unassigned name and truncated value
+DROP TABLE reloptions_test;
+CREATE TABLE reloptions_test(i INT) WITH (vacuum_truncate);
+SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass;
+
+DROP TABLE reloptions_test;
+CREATE TABLE reloptions_test(i INT) WITH (vacuum_truncate=FaLS);
+SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass;
+
+-- preferred "true" alias is used when storing
+DROP TABLE reloptions_test;
+CREATE TABLE reloptions_test(i INT) WITH (vacuum_index_cleanup=on);
+SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass;
+
+-- custom "third" value is available
+DROP TABLE reloptions_test;
+CREATE TABLE reloptions_test(i INT) WITH (vacuum_index_cleanup=auto);
+SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass;
+
-- Test vacuum_truncate option
DROP TABLE reloptions_test;
diff --git a/src/tools/pgindent/typedefs.list b/src/tools/pgindent/typedefs.list
index 3f3a888fd0e..1c8610fd46c 100644
--- a/src/tools/pgindent/typedefs.list
+++ b/src/tools/pgindent/typedefs.list
@@ -3952,6 +3952,7 @@ pg_sha512_ctx
pg_snapshot
pg_special_case
pg_stack_base_t
+pg_ternary
pg_time_t
pg_time_usec_t
pg_tz
@@ -4079,6 +4080,7 @@ relopt_kind
relopt_parse_elt
relopt_real
relopt_string
+relopt_ternary
relopt_type
relopt_value
relopts_validator
--
2.47.3
--c36t3fndxhkzbl5z--
^ permalink raw reply [nested|flat] 868+ messages in thread
* [PATCH v3] Introduce ternary reloptions
@ 2026-01-16 15:04 Álvaro Herrera <[email protected]>
0 siblings, 0 replies; 868+ messages in thread
From: Álvaro Herrera @ 2026-01-16 15:04 UTC (permalink / raw)
Introduce ternary reloption as a replacement for current
`vacuum_truncate` implementation. Remove the `vacuum_truncate_set`
separate flag and use TERNARY_UNSET instead.
This could also be used for other options such as `vacuum_index_cleanup`
and `buffering`, but lets get the scaffolding in first.
Discussion: https://postgr.es/m/3474141.usfYGdeWWP@thinkpad-pgpro
---
src/backend/access/common/reloptions.c | 137 ++++++++++++++++++-----
src/backend/commands/vacuum.c | 4 +-
src/include/access/reloptions.h | 27 ++---
src/include/postgres.h | 15 +++
src/include/utils/rel.h | 3 +-
src/test/regress/expected/reloptions.out | 36 ++++++
src/test/regress/sql/reloptions.sql | 21 ++++
src/tools/pgindent/typedefs.list | 2 +
8 files changed, 202 insertions(+), 43 deletions(-)
diff --git a/src/backend/access/common/reloptions.c b/src/backend/access/common/reloptions.c
index 0b83f98ed5f..6f1f577581a 100644
--- a/src/backend/access/common/reloptions.c
+++ b/src/backend/access/common/reloptions.c
@@ -40,9 +40,9 @@
*
* To add an option:
*
- * (i) decide on a type (bool, integer, real, enum, string), name, default
- * value, upper and lower bounds (if applicable); for strings, consider a
- * validation routine.
+ * (i) decide on a type (bool, ternary, integer, real, enum, string), name,
+ * default value, upper and lower bounds (if applicable); for strings,
+ * consider a validation routine.
* (ii) add a record below (or use add_<type>_reloption).
* (iii) add it to the appropriate options struct (perhaps StdRdOptions)
* (iv) add it to the appropriate handling routine (perhaps
@@ -147,15 +147,6 @@ static relopt_bool boolRelOpts[] =
},
false
},
- {
- {
- "vacuum_truncate",
- "Enables vacuum to truncate empty pages at the end of this table",
- RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
- ShareUpdateExclusiveLock
- },
- true
- },
{
{
"deduplicate_items",
@@ -170,6 +161,25 @@ static relopt_bool boolRelOpts[] =
{{NULL}}
};
+static relopt_ternary ternaryRelOpts[] =
+{
+ {
+ {
+ "vacuum_truncate",
+ "Enables vacuum to truncate empty pages at the end of this table",
+ RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
+ ShareUpdateExclusiveLock
+ },
+ TERNARY_UNSET
+ },
+ /* list terminator */
+ {
+ {
+ NULL
+ }
+ }
+};
+
static relopt_int intRelOpts[] =
{
{
@@ -609,6 +619,13 @@ initialize_reloptions(void)
boolRelOpts[i].gen.lockmode));
j++;
}
+ for (i = 0; ternaryRelOpts[i].gen.name; i++)
+ {
+ Assert(DoLockModesConflict(ternaryRelOpts[i].gen.lockmode,
+ ternaryRelOpts[i].gen.lockmode));
+ j++;
+ }
+
for (i = 0; intRelOpts[i].gen.name; i++)
{
Assert(DoLockModesConflict(intRelOpts[i].gen.lockmode,
@@ -649,6 +666,14 @@ initialize_reloptions(void)
j++;
}
+ for (i = 0; ternaryRelOpts[i].gen.name; i++)
+ {
+ relOpts[j] = &ternaryRelOpts[i].gen;
+ relOpts[j]->type = RELOPT_TYPE_TERNARY;
+ relOpts[j]->namelen = strlen(relOpts[j]->name);
+ j++;
+ }
+
for (i = 0; intRelOpts[i].gen.name; i++)
{
relOpts[j] = &intRelOpts[i].gen;
@@ -809,6 +834,9 @@ allocate_reloption(bits32 kinds, int type, const char *name, const char *desc,
case RELOPT_TYPE_BOOL:
size = sizeof(relopt_bool);
break;
+ case RELOPT_TYPE_TERNARY:
+ size = sizeof(relopt_ternary);
+ break;
case RELOPT_TYPE_INT:
size = sizeof(relopt_int);
break;
@@ -892,6 +920,57 @@ add_local_bool_reloption(local_relopts *relopts, const char *name,
add_local_reloption(relopts, (relopt_gen *) newoption, offset);
}
+/*
+ * init_ternary_reloption
+ * Allocate and initialize a new ternary reloption
+ */
+static relopt_ternary *
+init_ternary_reloption(bits32 kinds, const char *name, const char *desc,
+ pg_ternary default_val, LOCKMODE lockmode)
+{
+ relopt_ternary *newoption;
+
+ newoption = (relopt_ternary *)
+ allocate_reloption(kinds, RELOPT_TYPE_TERNARY, name, desc, lockmode);
+ newoption->default_val = default_val;
+
+ return newoption;
+}
+
+/*
+ * add_ternary_reloption
+ * Add a new ternary reloption
+ */
+void
+add_ternary_reloption(bits32 kinds, const char *name, const char *desc,
+ pg_ternary default_val, LOCKMODE lockmode)
+{
+ relopt_ternary *newoption;
+
+ newoption =
+ init_ternary_reloption(kinds, name, desc, default_val, lockmode);
+
+ add_reloption((relopt_gen *) newoption);
+}
+
+/*
+ * add_local_ternary_reloption
+ * Add a new ternary local reloption
+ *
+ * 'offset' is offset of ternary-typed field.
+ */
+void
+add_local_ternary_reloption(local_relopts *relopts, const char *name,
+ const char *desc, pg_ternary default_val,
+ int offset)
+{
+ relopt_ternary *newoption;
+
+ newoption =
+ init_ternary_reloption(RELOPT_KIND_LOCAL, name, desc, default_val, 0);
+
+ add_local_reloption(relopts, (relopt_gen *) newoption, offset);
+}
/*
* init_real_reloption
@@ -1626,6 +1705,19 @@ parse_one_reloption(relopt_value *option, char *text_str, int text_len,
option->gen->name, value)));
}
break;
+ case RELOPT_TYPE_TERNARY:
+ {
+ bool b;
+
+ parsed = parse_bool(value, &b);
+ option->values.ternary_val = b ? TERNARY_TRUE : TERNARY_FALSE;
+ if (validate && !parsed)
+ ereport(ERROR,
+ (errcode(ERRCODE_INVALID_PARAMETER_VALUE),
+ errmsg("invalid value for ternary option \"%s\": %s",
+ option->gen->name, value)));
+ }
+ break;
case RELOPT_TYPE_INT:
{
relopt_int *optint = (relopt_int *) option->gen;
@@ -1789,17 +1881,6 @@ fillRelOptions(void *rdopts, Size basesize,
char *itempos = ((char *) rdopts) + elems[j].offset;
char *string_val;
- /*
- * If isset_offset is provided, store whether the reloption is
- * set there.
- */
- if (elems[j].isset_offset > 0)
- {
- char *setpos = ((char *) rdopts) + elems[j].isset_offset;
-
- *(bool *) setpos = options[i].isset;
- }
-
switch (options[i].gen->type)
{
case RELOPT_TYPE_BOOL:
@@ -1807,6 +1888,11 @@ fillRelOptions(void *rdopts, Size basesize,
options[i].values.bool_val :
((relopt_bool *) options[i].gen)->default_val;
break;
+ case RELOPT_TYPE_TERNARY:
+ *(pg_ternary *) itempos = options[i].isset ?
+ options[i].values.ternary_val :
+ ((relopt_ternary *) options[i].gen)->default_val;
+ break;
case RELOPT_TYPE_INT:
*(int *) itempos = options[i].isset ?
options[i].values.int_val :
@@ -1923,8 +2009,8 @@ default_reloptions(Datum reloptions, bool validate, relopt_kind kind)
offsetof(StdRdOptions, parallel_workers)},
{"vacuum_index_cleanup", RELOPT_TYPE_ENUM,
offsetof(StdRdOptions, vacuum_index_cleanup)},
- {"vacuum_truncate", RELOPT_TYPE_BOOL,
- offsetof(StdRdOptions, vacuum_truncate), offsetof(StdRdOptions, vacuum_truncate_set)},
+ {"vacuum_truncate", RELOPT_TYPE_TERNARY,
+ offsetof(StdRdOptions, vacuum_truncate)},
{"vacuum_max_eager_freeze_failure_rate", RELOPT_TYPE_REAL,
offsetof(StdRdOptions, vacuum_max_eager_freeze_failure_rate)}
};
@@ -2004,7 +2090,6 @@ build_local_reloptions(local_relopts *relopts, Datum options, bool validate)
elems[i].optname = opt->option->name;
elems[i].opttype = opt->option->type;
elems[i].offset = opt->offset;
- elems[i].isset_offset = 0; /* not supported for local relopts yet */
i++;
}
diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c
index aa4fbec143f..696eab9bd97 100644
--- a/src/backend/commands/vacuum.c
+++ b/src/backend/commands/vacuum.c
@@ -2224,9 +2224,9 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams params,
{
StdRdOptions *opts = (StdRdOptions *) rel->rd_options;
- if (opts && opts->vacuum_truncate_set)
+ if (opts && opts->vacuum_truncate != TERNARY_UNSET)
{
- if (opts->vacuum_truncate)
+ if (opts->vacuum_truncate == TERNARY_TRUE)
params.truncate = VACOPTVALUE_ENABLED;
else
params.truncate = VACOPTVALUE_DISABLED;
diff --git a/src/include/access/reloptions.h b/src/include/access/reloptions.h
index 2f08e1b0cf0..dfbef2babf2 100644
--- a/src/include/access/reloptions.h
+++ b/src/include/access/reloptions.h
@@ -29,6 +29,7 @@
typedef enum relopt_type
{
RELOPT_TYPE_BOOL,
+ RELOPT_TYPE_TERNARY, /* on, off, unset */
RELOPT_TYPE_INT,
RELOPT_TYPE_REAL,
RELOPT_TYPE_ENUM,
@@ -80,6 +81,7 @@ typedef struct relopt_value
union
{
bool bool_val;
+ pg_ternary ternary_val;
int int_val;
double real_val;
int enum_val;
@@ -94,6 +96,12 @@ typedef struct relopt_bool
bool default_val;
} relopt_bool;
+typedef struct relopt_ternary
+{
+ relopt_gen gen;
+ int default_val;
+} relopt_ternary;
+
typedef struct relopt_int
{
relopt_gen gen;
@@ -152,19 +160,6 @@ typedef struct
const char *optname; /* option's name */
relopt_type opttype; /* option's datatype */
int offset; /* offset of field in result struct */
-
- /*
- * isset_offset is an optional offset of a field in the result struct that
- * stores whether the option is explicitly set for the relation or if it
- * just picked up the default value. In most cases, this can be
- * accomplished by giving the reloption a special out-of-range default
- * value (e.g., some integer reloptions use -2), but this isn't always
- * possible. For example, a Boolean reloption cannot be given an
- * out-of-range default, so we need another way to discover the source of
- * its value. This offset is only used if given a value greater than
- * zero.
- */
- int isset_offset;
} relopt_parse_elt;
/* Local reloption definition */
@@ -195,6 +190,9 @@ typedef struct local_relopts
extern relopt_kind add_reloption_kind(void);
extern void add_bool_reloption(bits32 kinds, const char *name, const char *desc,
bool default_val, LOCKMODE lockmode);
+extern void add_ternary_reloption(bits32 kinds, const char *name,
+ const char *desc, pg_ternary default_val,
+ LOCKMODE lockmode);
extern void add_int_reloption(bits32 kinds, const char *name, const char *desc,
int default_val, int min_val, int max_val,
LOCKMODE lockmode);
@@ -214,6 +212,9 @@ extern void register_reloptions_validator(local_relopts *relopts,
extern void add_local_bool_reloption(local_relopts *relopts, const char *name,
const char *desc, bool default_val,
int offset);
+extern void add_local_ternary_reloption(local_relopts *relopts,
+ const char *name, const char *desc,
+ pg_ternary default_val, int offset);
extern void add_local_int_reloption(local_relopts *relopts, const char *name,
const char *desc, int default_val,
int min_val, int max_val, int offset);
diff --git a/src/include/postgres.h b/src/include/postgres.h
index 7d93fbce709..47c88780776 100644
--- a/src/include/postgres.h
+++ b/src/include/postgres.h
@@ -543,6 +543,21 @@ Float8GetDatum(float8 X)
* ----------------------------------------------------------------
*/
+/*
+ * pg_ternary
+ * Boolean value with an extra "unset" value
+ *
+ * This enum can be used for values that want to distinguish between true,
+ * false, and unset.
+*/
+
+typedef enum pg_ternary
+{
+ TERNARY_FALSE = 0,
+ TERNARY_TRUE = 1,
+ TERNARY_UNSET = -1
+} pg_ternary;
+
/*
* NON_EXEC_STATIC: It's sometimes useful to define a variable or function
* that is normally static but extern when using EXEC_BACKEND (see
diff --git a/src/include/utils/rel.h b/src/include/utils/rel.h
index d03ab247788..5feb18a5373 100644
--- a/src/include/utils/rel.h
+++ b/src/include/utils/rel.h
@@ -347,8 +347,7 @@ typedef struct StdRdOptions
bool user_catalog_table; /* use as an additional catalog relation */
int parallel_workers; /* max number of parallel workers */
StdRdOptIndexCleanup vacuum_index_cleanup; /* controls index vacuuming */
- bool vacuum_truncate; /* enables vacuum to truncate a relation */
- bool vacuum_truncate_set; /* whether vacuum_truncate is set */
+ pg_ternary vacuum_truncate; /* enables vacuum to truncate a relation */
/*
* Fraction of pages in a relation that vacuum can eagerly scan and fail
diff --git a/src/test/regress/expected/reloptions.out b/src/test/regress/expected/reloptions.out
index 9de19b4e3f1..1c99f79ab01 100644
--- a/src/test/regress/expected/reloptions.out
+++ b/src/test/regress/expected/reloptions.out
@@ -98,6 +98,42 @@ SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass;
{fillfactor=13,autovacuum_enabled=false}
(1 row)
+-- Tests for future (FIXME) ternary options
+-- behave as boolean option: accept unassigned name and truncated value
+DROP TABLE reloptions_test;
+CREATE TABLE reloptions_test(i INT) WITH (vacuum_truncate);
+SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass;
+ reloptions
+------------------------
+ {vacuum_truncate=true}
+(1 row)
+
+DROP TABLE reloptions_test;
+CREATE TABLE reloptions_test(i INT) WITH (vacuum_truncate=FaLS);
+SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass;
+ reloptions
+------------------------
+ {vacuum_truncate=fals}
+(1 row)
+
+-- preferred "true" alias is used when storing
+DROP TABLE reloptions_test;
+CREATE TABLE reloptions_test(i INT) WITH (vacuum_index_cleanup=on);
+SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass;
+ reloptions
+---------------------------
+ {vacuum_index_cleanup=on}
+(1 row)
+
+-- custom "third" value is available
+DROP TABLE reloptions_test;
+CREATE TABLE reloptions_test(i INT) WITH (vacuum_index_cleanup=auto);
+SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass;
+ reloptions
+-----------------------------
+ {vacuum_index_cleanup=auto}
+(1 row)
+
-- Test vacuum_truncate option
DROP TABLE reloptions_test;
CREATE TEMP TABLE reloptions_test(i INT NOT NULL, j text)
diff --git a/src/test/regress/sql/reloptions.sql b/src/test/regress/sql/reloptions.sql
index 24fbe0b478d..f5980dafcbc 100644
--- a/src/test/regress/sql/reloptions.sql
+++ b/src/test/regress/sql/reloptions.sql
@@ -59,6 +59,27 @@ UPDATE pg_class
ALTER TABLE reloptions_test RESET (illegal_option);
SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass;
+-- Tests for future (FIXME) ternary options
+
+-- behave as boolean option: accept unassigned name and truncated value
+DROP TABLE reloptions_test;
+CREATE TABLE reloptions_test(i INT) WITH (vacuum_truncate);
+SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass;
+
+DROP TABLE reloptions_test;
+CREATE TABLE reloptions_test(i INT) WITH (vacuum_truncate=FaLS);
+SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass;
+
+-- preferred "true" alias is used when storing
+DROP TABLE reloptions_test;
+CREATE TABLE reloptions_test(i INT) WITH (vacuum_index_cleanup=on);
+SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass;
+
+-- custom "third" value is available
+DROP TABLE reloptions_test;
+CREATE TABLE reloptions_test(i INT) WITH (vacuum_index_cleanup=auto);
+SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass;
+
-- Test vacuum_truncate option
DROP TABLE reloptions_test;
diff --git a/src/tools/pgindent/typedefs.list b/src/tools/pgindent/typedefs.list
index 3f3a888fd0e..1c8610fd46c 100644
--- a/src/tools/pgindent/typedefs.list
+++ b/src/tools/pgindent/typedefs.list
@@ -3952,6 +3952,7 @@ pg_sha512_ctx
pg_snapshot
pg_special_case
pg_stack_base_t
+pg_ternary
pg_time_t
pg_time_usec_t
pg_tz
@@ -4079,6 +4080,7 @@ relopt_kind
relopt_parse_elt
relopt_real
relopt_string
+relopt_ternary
relopt_type
relopt_value
relopts_validator
--
2.47.3
--c36t3fndxhkzbl5z--
^ permalink raw reply [nested|flat] 868+ messages in thread
* [PATCH v3] Introduce ternary reloptions
@ 2026-01-16 15:04 Álvaro Herrera <[email protected]>
0 siblings, 0 replies; 868+ messages in thread
From: Álvaro Herrera @ 2026-01-16 15:04 UTC (permalink / raw)
Introduce ternary reloption as a replacement for current
`vacuum_truncate` implementation. Remove the `vacuum_truncate_set`
separate flag and use TERNARY_UNSET instead.
This could also be used for other options such as `vacuum_index_cleanup`
and `buffering`, but lets get the scaffolding in first.
Discussion: https://postgr.es/m/3474141.usfYGdeWWP@thinkpad-pgpro
---
src/backend/access/common/reloptions.c | 137 ++++++++++++++++++-----
src/backend/commands/vacuum.c | 4 +-
src/include/access/reloptions.h | 27 ++---
src/include/postgres.h | 15 +++
src/include/utils/rel.h | 3 +-
src/test/regress/expected/reloptions.out | 36 ++++++
src/test/regress/sql/reloptions.sql | 21 ++++
src/tools/pgindent/typedefs.list | 2 +
8 files changed, 202 insertions(+), 43 deletions(-)
diff --git a/src/backend/access/common/reloptions.c b/src/backend/access/common/reloptions.c
index 0b83f98ed5f..6f1f577581a 100644
--- a/src/backend/access/common/reloptions.c
+++ b/src/backend/access/common/reloptions.c
@@ -40,9 +40,9 @@
*
* To add an option:
*
- * (i) decide on a type (bool, integer, real, enum, string), name, default
- * value, upper and lower bounds (if applicable); for strings, consider a
- * validation routine.
+ * (i) decide on a type (bool, ternary, integer, real, enum, string), name,
+ * default value, upper and lower bounds (if applicable); for strings,
+ * consider a validation routine.
* (ii) add a record below (or use add_<type>_reloption).
* (iii) add it to the appropriate options struct (perhaps StdRdOptions)
* (iv) add it to the appropriate handling routine (perhaps
@@ -147,15 +147,6 @@ static relopt_bool boolRelOpts[] =
},
false
},
- {
- {
- "vacuum_truncate",
- "Enables vacuum to truncate empty pages at the end of this table",
- RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
- ShareUpdateExclusiveLock
- },
- true
- },
{
{
"deduplicate_items",
@@ -170,6 +161,25 @@ static relopt_bool boolRelOpts[] =
{{NULL}}
};
+static relopt_ternary ternaryRelOpts[] =
+{
+ {
+ {
+ "vacuum_truncate",
+ "Enables vacuum to truncate empty pages at the end of this table",
+ RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
+ ShareUpdateExclusiveLock
+ },
+ TERNARY_UNSET
+ },
+ /* list terminator */
+ {
+ {
+ NULL
+ }
+ }
+};
+
static relopt_int intRelOpts[] =
{
{
@@ -609,6 +619,13 @@ initialize_reloptions(void)
boolRelOpts[i].gen.lockmode));
j++;
}
+ for (i = 0; ternaryRelOpts[i].gen.name; i++)
+ {
+ Assert(DoLockModesConflict(ternaryRelOpts[i].gen.lockmode,
+ ternaryRelOpts[i].gen.lockmode));
+ j++;
+ }
+
for (i = 0; intRelOpts[i].gen.name; i++)
{
Assert(DoLockModesConflict(intRelOpts[i].gen.lockmode,
@@ -649,6 +666,14 @@ initialize_reloptions(void)
j++;
}
+ for (i = 0; ternaryRelOpts[i].gen.name; i++)
+ {
+ relOpts[j] = &ternaryRelOpts[i].gen;
+ relOpts[j]->type = RELOPT_TYPE_TERNARY;
+ relOpts[j]->namelen = strlen(relOpts[j]->name);
+ j++;
+ }
+
for (i = 0; intRelOpts[i].gen.name; i++)
{
relOpts[j] = &intRelOpts[i].gen;
@@ -809,6 +834,9 @@ allocate_reloption(bits32 kinds, int type, const char *name, const char *desc,
case RELOPT_TYPE_BOOL:
size = sizeof(relopt_bool);
break;
+ case RELOPT_TYPE_TERNARY:
+ size = sizeof(relopt_ternary);
+ break;
case RELOPT_TYPE_INT:
size = sizeof(relopt_int);
break;
@@ -892,6 +920,57 @@ add_local_bool_reloption(local_relopts *relopts, const char *name,
add_local_reloption(relopts, (relopt_gen *) newoption, offset);
}
+/*
+ * init_ternary_reloption
+ * Allocate and initialize a new ternary reloption
+ */
+static relopt_ternary *
+init_ternary_reloption(bits32 kinds, const char *name, const char *desc,
+ pg_ternary default_val, LOCKMODE lockmode)
+{
+ relopt_ternary *newoption;
+
+ newoption = (relopt_ternary *)
+ allocate_reloption(kinds, RELOPT_TYPE_TERNARY, name, desc, lockmode);
+ newoption->default_val = default_val;
+
+ return newoption;
+}
+
+/*
+ * add_ternary_reloption
+ * Add a new ternary reloption
+ */
+void
+add_ternary_reloption(bits32 kinds, const char *name, const char *desc,
+ pg_ternary default_val, LOCKMODE lockmode)
+{
+ relopt_ternary *newoption;
+
+ newoption =
+ init_ternary_reloption(kinds, name, desc, default_val, lockmode);
+
+ add_reloption((relopt_gen *) newoption);
+}
+
+/*
+ * add_local_ternary_reloption
+ * Add a new ternary local reloption
+ *
+ * 'offset' is offset of ternary-typed field.
+ */
+void
+add_local_ternary_reloption(local_relopts *relopts, const char *name,
+ const char *desc, pg_ternary default_val,
+ int offset)
+{
+ relopt_ternary *newoption;
+
+ newoption =
+ init_ternary_reloption(RELOPT_KIND_LOCAL, name, desc, default_val, 0);
+
+ add_local_reloption(relopts, (relopt_gen *) newoption, offset);
+}
/*
* init_real_reloption
@@ -1626,6 +1705,19 @@ parse_one_reloption(relopt_value *option, char *text_str, int text_len,
option->gen->name, value)));
}
break;
+ case RELOPT_TYPE_TERNARY:
+ {
+ bool b;
+
+ parsed = parse_bool(value, &b);
+ option->values.ternary_val = b ? TERNARY_TRUE : TERNARY_FALSE;
+ if (validate && !parsed)
+ ereport(ERROR,
+ (errcode(ERRCODE_INVALID_PARAMETER_VALUE),
+ errmsg("invalid value for ternary option \"%s\": %s",
+ option->gen->name, value)));
+ }
+ break;
case RELOPT_TYPE_INT:
{
relopt_int *optint = (relopt_int *) option->gen;
@@ -1789,17 +1881,6 @@ fillRelOptions(void *rdopts, Size basesize,
char *itempos = ((char *) rdopts) + elems[j].offset;
char *string_val;
- /*
- * If isset_offset is provided, store whether the reloption is
- * set there.
- */
- if (elems[j].isset_offset > 0)
- {
- char *setpos = ((char *) rdopts) + elems[j].isset_offset;
-
- *(bool *) setpos = options[i].isset;
- }
-
switch (options[i].gen->type)
{
case RELOPT_TYPE_BOOL:
@@ -1807,6 +1888,11 @@ fillRelOptions(void *rdopts, Size basesize,
options[i].values.bool_val :
((relopt_bool *) options[i].gen)->default_val;
break;
+ case RELOPT_TYPE_TERNARY:
+ *(pg_ternary *) itempos = options[i].isset ?
+ options[i].values.ternary_val :
+ ((relopt_ternary *) options[i].gen)->default_val;
+ break;
case RELOPT_TYPE_INT:
*(int *) itempos = options[i].isset ?
options[i].values.int_val :
@@ -1923,8 +2009,8 @@ default_reloptions(Datum reloptions, bool validate, relopt_kind kind)
offsetof(StdRdOptions, parallel_workers)},
{"vacuum_index_cleanup", RELOPT_TYPE_ENUM,
offsetof(StdRdOptions, vacuum_index_cleanup)},
- {"vacuum_truncate", RELOPT_TYPE_BOOL,
- offsetof(StdRdOptions, vacuum_truncate), offsetof(StdRdOptions, vacuum_truncate_set)},
+ {"vacuum_truncate", RELOPT_TYPE_TERNARY,
+ offsetof(StdRdOptions, vacuum_truncate)},
{"vacuum_max_eager_freeze_failure_rate", RELOPT_TYPE_REAL,
offsetof(StdRdOptions, vacuum_max_eager_freeze_failure_rate)}
};
@@ -2004,7 +2090,6 @@ build_local_reloptions(local_relopts *relopts, Datum options, bool validate)
elems[i].optname = opt->option->name;
elems[i].opttype = opt->option->type;
elems[i].offset = opt->offset;
- elems[i].isset_offset = 0; /* not supported for local relopts yet */
i++;
}
diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c
index aa4fbec143f..696eab9bd97 100644
--- a/src/backend/commands/vacuum.c
+++ b/src/backend/commands/vacuum.c
@@ -2224,9 +2224,9 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams params,
{
StdRdOptions *opts = (StdRdOptions *) rel->rd_options;
- if (opts && opts->vacuum_truncate_set)
+ if (opts && opts->vacuum_truncate != TERNARY_UNSET)
{
- if (opts->vacuum_truncate)
+ if (opts->vacuum_truncate == TERNARY_TRUE)
params.truncate = VACOPTVALUE_ENABLED;
else
params.truncate = VACOPTVALUE_DISABLED;
diff --git a/src/include/access/reloptions.h b/src/include/access/reloptions.h
index 2f08e1b0cf0..dfbef2babf2 100644
--- a/src/include/access/reloptions.h
+++ b/src/include/access/reloptions.h
@@ -29,6 +29,7 @@
typedef enum relopt_type
{
RELOPT_TYPE_BOOL,
+ RELOPT_TYPE_TERNARY, /* on, off, unset */
RELOPT_TYPE_INT,
RELOPT_TYPE_REAL,
RELOPT_TYPE_ENUM,
@@ -80,6 +81,7 @@ typedef struct relopt_value
union
{
bool bool_val;
+ pg_ternary ternary_val;
int int_val;
double real_val;
int enum_val;
@@ -94,6 +96,12 @@ typedef struct relopt_bool
bool default_val;
} relopt_bool;
+typedef struct relopt_ternary
+{
+ relopt_gen gen;
+ int default_val;
+} relopt_ternary;
+
typedef struct relopt_int
{
relopt_gen gen;
@@ -152,19 +160,6 @@ typedef struct
const char *optname; /* option's name */
relopt_type opttype; /* option's datatype */
int offset; /* offset of field in result struct */
-
- /*
- * isset_offset is an optional offset of a field in the result struct that
- * stores whether the option is explicitly set for the relation or if it
- * just picked up the default value. In most cases, this can be
- * accomplished by giving the reloption a special out-of-range default
- * value (e.g., some integer reloptions use -2), but this isn't always
- * possible. For example, a Boolean reloption cannot be given an
- * out-of-range default, so we need another way to discover the source of
- * its value. This offset is only used if given a value greater than
- * zero.
- */
- int isset_offset;
} relopt_parse_elt;
/* Local reloption definition */
@@ -195,6 +190,9 @@ typedef struct local_relopts
extern relopt_kind add_reloption_kind(void);
extern void add_bool_reloption(bits32 kinds, const char *name, const char *desc,
bool default_val, LOCKMODE lockmode);
+extern void add_ternary_reloption(bits32 kinds, const char *name,
+ const char *desc, pg_ternary default_val,
+ LOCKMODE lockmode);
extern void add_int_reloption(bits32 kinds, const char *name, const char *desc,
int default_val, int min_val, int max_val,
LOCKMODE lockmode);
@@ -214,6 +212,9 @@ extern void register_reloptions_validator(local_relopts *relopts,
extern void add_local_bool_reloption(local_relopts *relopts, const char *name,
const char *desc, bool default_val,
int offset);
+extern void add_local_ternary_reloption(local_relopts *relopts,
+ const char *name, const char *desc,
+ pg_ternary default_val, int offset);
extern void add_local_int_reloption(local_relopts *relopts, const char *name,
const char *desc, int default_val,
int min_val, int max_val, int offset);
diff --git a/src/include/postgres.h b/src/include/postgres.h
index 7d93fbce709..47c88780776 100644
--- a/src/include/postgres.h
+++ b/src/include/postgres.h
@@ -543,6 +543,21 @@ Float8GetDatum(float8 X)
* ----------------------------------------------------------------
*/
+/*
+ * pg_ternary
+ * Boolean value with an extra "unset" value
+ *
+ * This enum can be used for values that want to distinguish between true,
+ * false, and unset.
+*/
+
+typedef enum pg_ternary
+{
+ TERNARY_FALSE = 0,
+ TERNARY_TRUE = 1,
+ TERNARY_UNSET = -1
+} pg_ternary;
+
/*
* NON_EXEC_STATIC: It's sometimes useful to define a variable or function
* that is normally static but extern when using EXEC_BACKEND (see
diff --git a/src/include/utils/rel.h b/src/include/utils/rel.h
index d03ab247788..5feb18a5373 100644
--- a/src/include/utils/rel.h
+++ b/src/include/utils/rel.h
@@ -347,8 +347,7 @@ typedef struct StdRdOptions
bool user_catalog_table; /* use as an additional catalog relation */
int parallel_workers; /* max number of parallel workers */
StdRdOptIndexCleanup vacuum_index_cleanup; /* controls index vacuuming */
- bool vacuum_truncate; /* enables vacuum to truncate a relation */
- bool vacuum_truncate_set; /* whether vacuum_truncate is set */
+ pg_ternary vacuum_truncate; /* enables vacuum to truncate a relation */
/*
* Fraction of pages in a relation that vacuum can eagerly scan and fail
diff --git a/src/test/regress/expected/reloptions.out b/src/test/regress/expected/reloptions.out
index 9de19b4e3f1..1c99f79ab01 100644
--- a/src/test/regress/expected/reloptions.out
+++ b/src/test/regress/expected/reloptions.out
@@ -98,6 +98,42 @@ SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass;
{fillfactor=13,autovacuum_enabled=false}
(1 row)
+-- Tests for future (FIXME) ternary options
+-- behave as boolean option: accept unassigned name and truncated value
+DROP TABLE reloptions_test;
+CREATE TABLE reloptions_test(i INT) WITH (vacuum_truncate);
+SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass;
+ reloptions
+------------------------
+ {vacuum_truncate=true}
+(1 row)
+
+DROP TABLE reloptions_test;
+CREATE TABLE reloptions_test(i INT) WITH (vacuum_truncate=FaLS);
+SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass;
+ reloptions
+------------------------
+ {vacuum_truncate=fals}
+(1 row)
+
+-- preferred "true" alias is used when storing
+DROP TABLE reloptions_test;
+CREATE TABLE reloptions_test(i INT) WITH (vacuum_index_cleanup=on);
+SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass;
+ reloptions
+---------------------------
+ {vacuum_index_cleanup=on}
+(1 row)
+
+-- custom "third" value is available
+DROP TABLE reloptions_test;
+CREATE TABLE reloptions_test(i INT) WITH (vacuum_index_cleanup=auto);
+SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass;
+ reloptions
+-----------------------------
+ {vacuum_index_cleanup=auto}
+(1 row)
+
-- Test vacuum_truncate option
DROP TABLE reloptions_test;
CREATE TEMP TABLE reloptions_test(i INT NOT NULL, j text)
diff --git a/src/test/regress/sql/reloptions.sql b/src/test/regress/sql/reloptions.sql
index 24fbe0b478d..f5980dafcbc 100644
--- a/src/test/regress/sql/reloptions.sql
+++ b/src/test/regress/sql/reloptions.sql
@@ -59,6 +59,27 @@ UPDATE pg_class
ALTER TABLE reloptions_test RESET (illegal_option);
SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass;
+-- Tests for future (FIXME) ternary options
+
+-- behave as boolean option: accept unassigned name and truncated value
+DROP TABLE reloptions_test;
+CREATE TABLE reloptions_test(i INT) WITH (vacuum_truncate);
+SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass;
+
+DROP TABLE reloptions_test;
+CREATE TABLE reloptions_test(i INT) WITH (vacuum_truncate=FaLS);
+SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass;
+
+-- preferred "true" alias is used when storing
+DROP TABLE reloptions_test;
+CREATE TABLE reloptions_test(i INT) WITH (vacuum_index_cleanup=on);
+SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass;
+
+-- custom "third" value is available
+DROP TABLE reloptions_test;
+CREATE TABLE reloptions_test(i INT) WITH (vacuum_index_cleanup=auto);
+SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass;
+
-- Test vacuum_truncate option
DROP TABLE reloptions_test;
diff --git a/src/tools/pgindent/typedefs.list b/src/tools/pgindent/typedefs.list
index 3f3a888fd0e..1c8610fd46c 100644
--- a/src/tools/pgindent/typedefs.list
+++ b/src/tools/pgindent/typedefs.list
@@ -3952,6 +3952,7 @@ pg_sha512_ctx
pg_snapshot
pg_special_case
pg_stack_base_t
+pg_ternary
pg_time_t
pg_time_usec_t
pg_tz
@@ -4079,6 +4080,7 @@ relopt_kind
relopt_parse_elt
relopt_real
relopt_string
+relopt_ternary
relopt_type
relopt_value
relopts_validator
--
2.47.3
--c36t3fndxhkzbl5z--
^ permalink raw reply [nested|flat] 868+ messages in thread
* [PATCH v3] Introduce ternary reloptions
@ 2026-01-16 15:04 Álvaro Herrera <[email protected]>
0 siblings, 0 replies; 868+ messages in thread
From: Álvaro Herrera @ 2026-01-16 15:04 UTC (permalink / raw)
Introduce ternary reloption as a replacement for current
`vacuum_truncate` implementation. Remove the `vacuum_truncate_set`
separate flag and use TERNARY_UNSET instead.
This could also be used for other options such as `vacuum_index_cleanup`
and `buffering`, but lets get the scaffolding in first.
Discussion: https://postgr.es/m/3474141.usfYGdeWWP@thinkpad-pgpro
---
src/backend/access/common/reloptions.c | 137 ++++++++++++++++++-----
src/backend/commands/vacuum.c | 4 +-
src/include/access/reloptions.h | 27 ++---
src/include/postgres.h | 15 +++
src/include/utils/rel.h | 3 +-
src/test/regress/expected/reloptions.out | 36 ++++++
src/test/regress/sql/reloptions.sql | 21 ++++
src/tools/pgindent/typedefs.list | 2 +
8 files changed, 202 insertions(+), 43 deletions(-)
diff --git a/src/backend/access/common/reloptions.c b/src/backend/access/common/reloptions.c
index 0b83f98ed5f..6f1f577581a 100644
--- a/src/backend/access/common/reloptions.c
+++ b/src/backend/access/common/reloptions.c
@@ -40,9 +40,9 @@
*
* To add an option:
*
- * (i) decide on a type (bool, integer, real, enum, string), name, default
- * value, upper and lower bounds (if applicable); for strings, consider a
- * validation routine.
+ * (i) decide on a type (bool, ternary, integer, real, enum, string), name,
+ * default value, upper and lower bounds (if applicable); for strings,
+ * consider a validation routine.
* (ii) add a record below (or use add_<type>_reloption).
* (iii) add it to the appropriate options struct (perhaps StdRdOptions)
* (iv) add it to the appropriate handling routine (perhaps
@@ -147,15 +147,6 @@ static relopt_bool boolRelOpts[] =
},
false
},
- {
- {
- "vacuum_truncate",
- "Enables vacuum to truncate empty pages at the end of this table",
- RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
- ShareUpdateExclusiveLock
- },
- true
- },
{
{
"deduplicate_items",
@@ -170,6 +161,25 @@ static relopt_bool boolRelOpts[] =
{{NULL}}
};
+static relopt_ternary ternaryRelOpts[] =
+{
+ {
+ {
+ "vacuum_truncate",
+ "Enables vacuum to truncate empty pages at the end of this table",
+ RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
+ ShareUpdateExclusiveLock
+ },
+ TERNARY_UNSET
+ },
+ /* list terminator */
+ {
+ {
+ NULL
+ }
+ }
+};
+
static relopt_int intRelOpts[] =
{
{
@@ -609,6 +619,13 @@ initialize_reloptions(void)
boolRelOpts[i].gen.lockmode));
j++;
}
+ for (i = 0; ternaryRelOpts[i].gen.name; i++)
+ {
+ Assert(DoLockModesConflict(ternaryRelOpts[i].gen.lockmode,
+ ternaryRelOpts[i].gen.lockmode));
+ j++;
+ }
+
for (i = 0; intRelOpts[i].gen.name; i++)
{
Assert(DoLockModesConflict(intRelOpts[i].gen.lockmode,
@@ -649,6 +666,14 @@ initialize_reloptions(void)
j++;
}
+ for (i = 0; ternaryRelOpts[i].gen.name; i++)
+ {
+ relOpts[j] = &ternaryRelOpts[i].gen;
+ relOpts[j]->type = RELOPT_TYPE_TERNARY;
+ relOpts[j]->namelen = strlen(relOpts[j]->name);
+ j++;
+ }
+
for (i = 0; intRelOpts[i].gen.name; i++)
{
relOpts[j] = &intRelOpts[i].gen;
@@ -809,6 +834,9 @@ allocate_reloption(bits32 kinds, int type, const char *name, const char *desc,
case RELOPT_TYPE_BOOL:
size = sizeof(relopt_bool);
break;
+ case RELOPT_TYPE_TERNARY:
+ size = sizeof(relopt_ternary);
+ break;
case RELOPT_TYPE_INT:
size = sizeof(relopt_int);
break;
@@ -892,6 +920,57 @@ add_local_bool_reloption(local_relopts *relopts, const char *name,
add_local_reloption(relopts, (relopt_gen *) newoption, offset);
}
+/*
+ * init_ternary_reloption
+ * Allocate and initialize a new ternary reloption
+ */
+static relopt_ternary *
+init_ternary_reloption(bits32 kinds, const char *name, const char *desc,
+ pg_ternary default_val, LOCKMODE lockmode)
+{
+ relopt_ternary *newoption;
+
+ newoption = (relopt_ternary *)
+ allocate_reloption(kinds, RELOPT_TYPE_TERNARY, name, desc, lockmode);
+ newoption->default_val = default_val;
+
+ return newoption;
+}
+
+/*
+ * add_ternary_reloption
+ * Add a new ternary reloption
+ */
+void
+add_ternary_reloption(bits32 kinds, const char *name, const char *desc,
+ pg_ternary default_val, LOCKMODE lockmode)
+{
+ relopt_ternary *newoption;
+
+ newoption =
+ init_ternary_reloption(kinds, name, desc, default_val, lockmode);
+
+ add_reloption((relopt_gen *) newoption);
+}
+
+/*
+ * add_local_ternary_reloption
+ * Add a new ternary local reloption
+ *
+ * 'offset' is offset of ternary-typed field.
+ */
+void
+add_local_ternary_reloption(local_relopts *relopts, const char *name,
+ const char *desc, pg_ternary default_val,
+ int offset)
+{
+ relopt_ternary *newoption;
+
+ newoption =
+ init_ternary_reloption(RELOPT_KIND_LOCAL, name, desc, default_val, 0);
+
+ add_local_reloption(relopts, (relopt_gen *) newoption, offset);
+}
/*
* init_real_reloption
@@ -1626,6 +1705,19 @@ parse_one_reloption(relopt_value *option, char *text_str, int text_len,
option->gen->name, value)));
}
break;
+ case RELOPT_TYPE_TERNARY:
+ {
+ bool b;
+
+ parsed = parse_bool(value, &b);
+ option->values.ternary_val = b ? TERNARY_TRUE : TERNARY_FALSE;
+ if (validate && !parsed)
+ ereport(ERROR,
+ (errcode(ERRCODE_INVALID_PARAMETER_VALUE),
+ errmsg("invalid value for ternary option \"%s\": %s",
+ option->gen->name, value)));
+ }
+ break;
case RELOPT_TYPE_INT:
{
relopt_int *optint = (relopt_int *) option->gen;
@@ -1789,17 +1881,6 @@ fillRelOptions(void *rdopts, Size basesize,
char *itempos = ((char *) rdopts) + elems[j].offset;
char *string_val;
- /*
- * If isset_offset is provided, store whether the reloption is
- * set there.
- */
- if (elems[j].isset_offset > 0)
- {
- char *setpos = ((char *) rdopts) + elems[j].isset_offset;
-
- *(bool *) setpos = options[i].isset;
- }
-
switch (options[i].gen->type)
{
case RELOPT_TYPE_BOOL:
@@ -1807,6 +1888,11 @@ fillRelOptions(void *rdopts, Size basesize,
options[i].values.bool_val :
((relopt_bool *) options[i].gen)->default_val;
break;
+ case RELOPT_TYPE_TERNARY:
+ *(pg_ternary *) itempos = options[i].isset ?
+ options[i].values.ternary_val :
+ ((relopt_ternary *) options[i].gen)->default_val;
+ break;
case RELOPT_TYPE_INT:
*(int *) itempos = options[i].isset ?
options[i].values.int_val :
@@ -1923,8 +2009,8 @@ default_reloptions(Datum reloptions, bool validate, relopt_kind kind)
offsetof(StdRdOptions, parallel_workers)},
{"vacuum_index_cleanup", RELOPT_TYPE_ENUM,
offsetof(StdRdOptions, vacuum_index_cleanup)},
- {"vacuum_truncate", RELOPT_TYPE_BOOL,
- offsetof(StdRdOptions, vacuum_truncate), offsetof(StdRdOptions, vacuum_truncate_set)},
+ {"vacuum_truncate", RELOPT_TYPE_TERNARY,
+ offsetof(StdRdOptions, vacuum_truncate)},
{"vacuum_max_eager_freeze_failure_rate", RELOPT_TYPE_REAL,
offsetof(StdRdOptions, vacuum_max_eager_freeze_failure_rate)}
};
@@ -2004,7 +2090,6 @@ build_local_reloptions(local_relopts *relopts, Datum options, bool validate)
elems[i].optname = opt->option->name;
elems[i].opttype = opt->option->type;
elems[i].offset = opt->offset;
- elems[i].isset_offset = 0; /* not supported for local relopts yet */
i++;
}
diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c
index aa4fbec143f..696eab9bd97 100644
--- a/src/backend/commands/vacuum.c
+++ b/src/backend/commands/vacuum.c
@@ -2224,9 +2224,9 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams params,
{
StdRdOptions *opts = (StdRdOptions *) rel->rd_options;
- if (opts && opts->vacuum_truncate_set)
+ if (opts && opts->vacuum_truncate != TERNARY_UNSET)
{
- if (opts->vacuum_truncate)
+ if (opts->vacuum_truncate == TERNARY_TRUE)
params.truncate = VACOPTVALUE_ENABLED;
else
params.truncate = VACOPTVALUE_DISABLED;
diff --git a/src/include/access/reloptions.h b/src/include/access/reloptions.h
index 2f08e1b0cf0..dfbef2babf2 100644
--- a/src/include/access/reloptions.h
+++ b/src/include/access/reloptions.h
@@ -29,6 +29,7 @@
typedef enum relopt_type
{
RELOPT_TYPE_BOOL,
+ RELOPT_TYPE_TERNARY, /* on, off, unset */
RELOPT_TYPE_INT,
RELOPT_TYPE_REAL,
RELOPT_TYPE_ENUM,
@@ -80,6 +81,7 @@ typedef struct relopt_value
union
{
bool bool_val;
+ pg_ternary ternary_val;
int int_val;
double real_val;
int enum_val;
@@ -94,6 +96,12 @@ typedef struct relopt_bool
bool default_val;
} relopt_bool;
+typedef struct relopt_ternary
+{
+ relopt_gen gen;
+ int default_val;
+} relopt_ternary;
+
typedef struct relopt_int
{
relopt_gen gen;
@@ -152,19 +160,6 @@ typedef struct
const char *optname; /* option's name */
relopt_type opttype; /* option's datatype */
int offset; /* offset of field in result struct */
-
- /*
- * isset_offset is an optional offset of a field in the result struct that
- * stores whether the option is explicitly set for the relation or if it
- * just picked up the default value. In most cases, this can be
- * accomplished by giving the reloption a special out-of-range default
- * value (e.g., some integer reloptions use -2), but this isn't always
- * possible. For example, a Boolean reloption cannot be given an
- * out-of-range default, so we need another way to discover the source of
- * its value. This offset is only used if given a value greater than
- * zero.
- */
- int isset_offset;
} relopt_parse_elt;
/* Local reloption definition */
@@ -195,6 +190,9 @@ typedef struct local_relopts
extern relopt_kind add_reloption_kind(void);
extern void add_bool_reloption(bits32 kinds, const char *name, const char *desc,
bool default_val, LOCKMODE lockmode);
+extern void add_ternary_reloption(bits32 kinds, const char *name,
+ const char *desc, pg_ternary default_val,
+ LOCKMODE lockmode);
extern void add_int_reloption(bits32 kinds, const char *name, const char *desc,
int default_val, int min_val, int max_val,
LOCKMODE lockmode);
@@ -214,6 +212,9 @@ extern void register_reloptions_validator(local_relopts *relopts,
extern void add_local_bool_reloption(local_relopts *relopts, const char *name,
const char *desc, bool default_val,
int offset);
+extern void add_local_ternary_reloption(local_relopts *relopts,
+ const char *name, const char *desc,
+ pg_ternary default_val, int offset);
extern void add_local_int_reloption(local_relopts *relopts, const char *name,
const char *desc, int default_val,
int min_val, int max_val, int offset);
diff --git a/src/include/postgres.h b/src/include/postgres.h
index 7d93fbce709..47c88780776 100644
--- a/src/include/postgres.h
+++ b/src/include/postgres.h
@@ -543,6 +543,21 @@ Float8GetDatum(float8 X)
* ----------------------------------------------------------------
*/
+/*
+ * pg_ternary
+ * Boolean value with an extra "unset" value
+ *
+ * This enum can be used for values that want to distinguish between true,
+ * false, and unset.
+*/
+
+typedef enum pg_ternary
+{
+ TERNARY_FALSE = 0,
+ TERNARY_TRUE = 1,
+ TERNARY_UNSET = -1
+} pg_ternary;
+
/*
* NON_EXEC_STATIC: It's sometimes useful to define a variable or function
* that is normally static but extern when using EXEC_BACKEND (see
diff --git a/src/include/utils/rel.h b/src/include/utils/rel.h
index d03ab247788..5feb18a5373 100644
--- a/src/include/utils/rel.h
+++ b/src/include/utils/rel.h
@@ -347,8 +347,7 @@ typedef struct StdRdOptions
bool user_catalog_table; /* use as an additional catalog relation */
int parallel_workers; /* max number of parallel workers */
StdRdOptIndexCleanup vacuum_index_cleanup; /* controls index vacuuming */
- bool vacuum_truncate; /* enables vacuum to truncate a relation */
- bool vacuum_truncate_set; /* whether vacuum_truncate is set */
+ pg_ternary vacuum_truncate; /* enables vacuum to truncate a relation */
/*
* Fraction of pages in a relation that vacuum can eagerly scan and fail
diff --git a/src/test/regress/expected/reloptions.out b/src/test/regress/expected/reloptions.out
index 9de19b4e3f1..1c99f79ab01 100644
--- a/src/test/regress/expected/reloptions.out
+++ b/src/test/regress/expected/reloptions.out
@@ -98,6 +98,42 @@ SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass;
{fillfactor=13,autovacuum_enabled=false}
(1 row)
+-- Tests for future (FIXME) ternary options
+-- behave as boolean option: accept unassigned name and truncated value
+DROP TABLE reloptions_test;
+CREATE TABLE reloptions_test(i INT) WITH (vacuum_truncate);
+SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass;
+ reloptions
+------------------------
+ {vacuum_truncate=true}
+(1 row)
+
+DROP TABLE reloptions_test;
+CREATE TABLE reloptions_test(i INT) WITH (vacuum_truncate=FaLS);
+SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass;
+ reloptions
+------------------------
+ {vacuum_truncate=fals}
+(1 row)
+
+-- preferred "true" alias is used when storing
+DROP TABLE reloptions_test;
+CREATE TABLE reloptions_test(i INT) WITH (vacuum_index_cleanup=on);
+SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass;
+ reloptions
+---------------------------
+ {vacuum_index_cleanup=on}
+(1 row)
+
+-- custom "third" value is available
+DROP TABLE reloptions_test;
+CREATE TABLE reloptions_test(i INT) WITH (vacuum_index_cleanup=auto);
+SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass;
+ reloptions
+-----------------------------
+ {vacuum_index_cleanup=auto}
+(1 row)
+
-- Test vacuum_truncate option
DROP TABLE reloptions_test;
CREATE TEMP TABLE reloptions_test(i INT NOT NULL, j text)
diff --git a/src/test/regress/sql/reloptions.sql b/src/test/regress/sql/reloptions.sql
index 24fbe0b478d..f5980dafcbc 100644
--- a/src/test/regress/sql/reloptions.sql
+++ b/src/test/regress/sql/reloptions.sql
@@ -59,6 +59,27 @@ UPDATE pg_class
ALTER TABLE reloptions_test RESET (illegal_option);
SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass;
+-- Tests for future (FIXME) ternary options
+
+-- behave as boolean option: accept unassigned name and truncated value
+DROP TABLE reloptions_test;
+CREATE TABLE reloptions_test(i INT) WITH (vacuum_truncate);
+SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass;
+
+DROP TABLE reloptions_test;
+CREATE TABLE reloptions_test(i INT) WITH (vacuum_truncate=FaLS);
+SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass;
+
+-- preferred "true" alias is used when storing
+DROP TABLE reloptions_test;
+CREATE TABLE reloptions_test(i INT) WITH (vacuum_index_cleanup=on);
+SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass;
+
+-- custom "third" value is available
+DROP TABLE reloptions_test;
+CREATE TABLE reloptions_test(i INT) WITH (vacuum_index_cleanup=auto);
+SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass;
+
-- Test vacuum_truncate option
DROP TABLE reloptions_test;
diff --git a/src/tools/pgindent/typedefs.list b/src/tools/pgindent/typedefs.list
index 3f3a888fd0e..1c8610fd46c 100644
--- a/src/tools/pgindent/typedefs.list
+++ b/src/tools/pgindent/typedefs.list
@@ -3952,6 +3952,7 @@ pg_sha512_ctx
pg_snapshot
pg_special_case
pg_stack_base_t
+pg_ternary
pg_time_t
pg_time_usec_t
pg_tz
@@ -4079,6 +4080,7 @@ relopt_kind
relopt_parse_elt
relopt_real
relopt_string
+relopt_ternary
relopt_type
relopt_value
relopts_validator
--
2.47.3
--c36t3fndxhkzbl5z--
^ permalink raw reply [nested|flat] 868+ messages in thread
* [PATCH v3] Introduce ternary reloptions
@ 2026-01-16 15:04 Álvaro Herrera <[email protected]>
0 siblings, 0 replies; 868+ messages in thread
From: Álvaro Herrera @ 2026-01-16 15:04 UTC (permalink / raw)
Introduce ternary reloption as a replacement for current
`vacuum_truncate` implementation. Remove the `vacuum_truncate_set`
separate flag and use TERNARY_UNSET instead.
This could also be used for other options such as `vacuum_index_cleanup`
and `buffering`, but lets get the scaffolding in first.
Discussion: https://postgr.es/m/3474141.usfYGdeWWP@thinkpad-pgpro
---
src/backend/access/common/reloptions.c | 137 ++++++++++++++++++-----
src/backend/commands/vacuum.c | 4 +-
src/include/access/reloptions.h | 27 ++---
src/include/postgres.h | 15 +++
src/include/utils/rel.h | 3 +-
src/test/regress/expected/reloptions.out | 36 ++++++
src/test/regress/sql/reloptions.sql | 21 ++++
src/tools/pgindent/typedefs.list | 2 +
8 files changed, 202 insertions(+), 43 deletions(-)
diff --git a/src/backend/access/common/reloptions.c b/src/backend/access/common/reloptions.c
index 0b83f98ed5f..6f1f577581a 100644
--- a/src/backend/access/common/reloptions.c
+++ b/src/backend/access/common/reloptions.c
@@ -40,9 +40,9 @@
*
* To add an option:
*
- * (i) decide on a type (bool, integer, real, enum, string), name, default
- * value, upper and lower bounds (if applicable); for strings, consider a
- * validation routine.
+ * (i) decide on a type (bool, ternary, integer, real, enum, string), name,
+ * default value, upper and lower bounds (if applicable); for strings,
+ * consider a validation routine.
* (ii) add a record below (or use add_<type>_reloption).
* (iii) add it to the appropriate options struct (perhaps StdRdOptions)
* (iv) add it to the appropriate handling routine (perhaps
@@ -147,15 +147,6 @@ static relopt_bool boolRelOpts[] =
},
false
},
- {
- {
- "vacuum_truncate",
- "Enables vacuum to truncate empty pages at the end of this table",
- RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
- ShareUpdateExclusiveLock
- },
- true
- },
{
{
"deduplicate_items",
@@ -170,6 +161,25 @@ static relopt_bool boolRelOpts[] =
{{NULL}}
};
+static relopt_ternary ternaryRelOpts[] =
+{
+ {
+ {
+ "vacuum_truncate",
+ "Enables vacuum to truncate empty pages at the end of this table",
+ RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
+ ShareUpdateExclusiveLock
+ },
+ TERNARY_UNSET
+ },
+ /* list terminator */
+ {
+ {
+ NULL
+ }
+ }
+};
+
static relopt_int intRelOpts[] =
{
{
@@ -609,6 +619,13 @@ initialize_reloptions(void)
boolRelOpts[i].gen.lockmode));
j++;
}
+ for (i = 0; ternaryRelOpts[i].gen.name; i++)
+ {
+ Assert(DoLockModesConflict(ternaryRelOpts[i].gen.lockmode,
+ ternaryRelOpts[i].gen.lockmode));
+ j++;
+ }
+
for (i = 0; intRelOpts[i].gen.name; i++)
{
Assert(DoLockModesConflict(intRelOpts[i].gen.lockmode,
@@ -649,6 +666,14 @@ initialize_reloptions(void)
j++;
}
+ for (i = 0; ternaryRelOpts[i].gen.name; i++)
+ {
+ relOpts[j] = &ternaryRelOpts[i].gen;
+ relOpts[j]->type = RELOPT_TYPE_TERNARY;
+ relOpts[j]->namelen = strlen(relOpts[j]->name);
+ j++;
+ }
+
for (i = 0; intRelOpts[i].gen.name; i++)
{
relOpts[j] = &intRelOpts[i].gen;
@@ -809,6 +834,9 @@ allocate_reloption(bits32 kinds, int type, const char *name, const char *desc,
case RELOPT_TYPE_BOOL:
size = sizeof(relopt_bool);
break;
+ case RELOPT_TYPE_TERNARY:
+ size = sizeof(relopt_ternary);
+ break;
case RELOPT_TYPE_INT:
size = sizeof(relopt_int);
break;
@@ -892,6 +920,57 @@ add_local_bool_reloption(local_relopts *relopts, const char *name,
add_local_reloption(relopts, (relopt_gen *) newoption, offset);
}
+/*
+ * init_ternary_reloption
+ * Allocate and initialize a new ternary reloption
+ */
+static relopt_ternary *
+init_ternary_reloption(bits32 kinds, const char *name, const char *desc,
+ pg_ternary default_val, LOCKMODE lockmode)
+{
+ relopt_ternary *newoption;
+
+ newoption = (relopt_ternary *)
+ allocate_reloption(kinds, RELOPT_TYPE_TERNARY, name, desc, lockmode);
+ newoption->default_val = default_val;
+
+ return newoption;
+}
+
+/*
+ * add_ternary_reloption
+ * Add a new ternary reloption
+ */
+void
+add_ternary_reloption(bits32 kinds, const char *name, const char *desc,
+ pg_ternary default_val, LOCKMODE lockmode)
+{
+ relopt_ternary *newoption;
+
+ newoption =
+ init_ternary_reloption(kinds, name, desc, default_val, lockmode);
+
+ add_reloption((relopt_gen *) newoption);
+}
+
+/*
+ * add_local_ternary_reloption
+ * Add a new ternary local reloption
+ *
+ * 'offset' is offset of ternary-typed field.
+ */
+void
+add_local_ternary_reloption(local_relopts *relopts, const char *name,
+ const char *desc, pg_ternary default_val,
+ int offset)
+{
+ relopt_ternary *newoption;
+
+ newoption =
+ init_ternary_reloption(RELOPT_KIND_LOCAL, name, desc, default_val, 0);
+
+ add_local_reloption(relopts, (relopt_gen *) newoption, offset);
+}
/*
* init_real_reloption
@@ -1626,6 +1705,19 @@ parse_one_reloption(relopt_value *option, char *text_str, int text_len,
option->gen->name, value)));
}
break;
+ case RELOPT_TYPE_TERNARY:
+ {
+ bool b;
+
+ parsed = parse_bool(value, &b);
+ option->values.ternary_val = b ? TERNARY_TRUE : TERNARY_FALSE;
+ if (validate && !parsed)
+ ereport(ERROR,
+ (errcode(ERRCODE_INVALID_PARAMETER_VALUE),
+ errmsg("invalid value for ternary option \"%s\": %s",
+ option->gen->name, value)));
+ }
+ break;
case RELOPT_TYPE_INT:
{
relopt_int *optint = (relopt_int *) option->gen;
@@ -1789,17 +1881,6 @@ fillRelOptions(void *rdopts, Size basesize,
char *itempos = ((char *) rdopts) + elems[j].offset;
char *string_val;
- /*
- * If isset_offset is provided, store whether the reloption is
- * set there.
- */
- if (elems[j].isset_offset > 0)
- {
- char *setpos = ((char *) rdopts) + elems[j].isset_offset;
-
- *(bool *) setpos = options[i].isset;
- }
-
switch (options[i].gen->type)
{
case RELOPT_TYPE_BOOL:
@@ -1807,6 +1888,11 @@ fillRelOptions(void *rdopts, Size basesize,
options[i].values.bool_val :
((relopt_bool *) options[i].gen)->default_val;
break;
+ case RELOPT_TYPE_TERNARY:
+ *(pg_ternary *) itempos = options[i].isset ?
+ options[i].values.ternary_val :
+ ((relopt_ternary *) options[i].gen)->default_val;
+ break;
case RELOPT_TYPE_INT:
*(int *) itempos = options[i].isset ?
options[i].values.int_val :
@@ -1923,8 +2009,8 @@ default_reloptions(Datum reloptions, bool validate, relopt_kind kind)
offsetof(StdRdOptions, parallel_workers)},
{"vacuum_index_cleanup", RELOPT_TYPE_ENUM,
offsetof(StdRdOptions, vacuum_index_cleanup)},
- {"vacuum_truncate", RELOPT_TYPE_BOOL,
- offsetof(StdRdOptions, vacuum_truncate), offsetof(StdRdOptions, vacuum_truncate_set)},
+ {"vacuum_truncate", RELOPT_TYPE_TERNARY,
+ offsetof(StdRdOptions, vacuum_truncate)},
{"vacuum_max_eager_freeze_failure_rate", RELOPT_TYPE_REAL,
offsetof(StdRdOptions, vacuum_max_eager_freeze_failure_rate)}
};
@@ -2004,7 +2090,6 @@ build_local_reloptions(local_relopts *relopts, Datum options, bool validate)
elems[i].optname = opt->option->name;
elems[i].opttype = opt->option->type;
elems[i].offset = opt->offset;
- elems[i].isset_offset = 0; /* not supported for local relopts yet */
i++;
}
diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c
index aa4fbec143f..696eab9bd97 100644
--- a/src/backend/commands/vacuum.c
+++ b/src/backend/commands/vacuum.c
@@ -2224,9 +2224,9 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams params,
{
StdRdOptions *opts = (StdRdOptions *) rel->rd_options;
- if (opts && opts->vacuum_truncate_set)
+ if (opts && opts->vacuum_truncate != TERNARY_UNSET)
{
- if (opts->vacuum_truncate)
+ if (opts->vacuum_truncate == TERNARY_TRUE)
params.truncate = VACOPTVALUE_ENABLED;
else
params.truncate = VACOPTVALUE_DISABLED;
diff --git a/src/include/access/reloptions.h b/src/include/access/reloptions.h
index 2f08e1b0cf0..dfbef2babf2 100644
--- a/src/include/access/reloptions.h
+++ b/src/include/access/reloptions.h
@@ -29,6 +29,7 @@
typedef enum relopt_type
{
RELOPT_TYPE_BOOL,
+ RELOPT_TYPE_TERNARY, /* on, off, unset */
RELOPT_TYPE_INT,
RELOPT_TYPE_REAL,
RELOPT_TYPE_ENUM,
@@ -80,6 +81,7 @@ typedef struct relopt_value
union
{
bool bool_val;
+ pg_ternary ternary_val;
int int_val;
double real_val;
int enum_val;
@@ -94,6 +96,12 @@ typedef struct relopt_bool
bool default_val;
} relopt_bool;
+typedef struct relopt_ternary
+{
+ relopt_gen gen;
+ int default_val;
+} relopt_ternary;
+
typedef struct relopt_int
{
relopt_gen gen;
@@ -152,19 +160,6 @@ typedef struct
const char *optname; /* option's name */
relopt_type opttype; /* option's datatype */
int offset; /* offset of field in result struct */
-
- /*
- * isset_offset is an optional offset of a field in the result struct that
- * stores whether the option is explicitly set for the relation or if it
- * just picked up the default value. In most cases, this can be
- * accomplished by giving the reloption a special out-of-range default
- * value (e.g., some integer reloptions use -2), but this isn't always
- * possible. For example, a Boolean reloption cannot be given an
- * out-of-range default, so we need another way to discover the source of
- * its value. This offset is only used if given a value greater than
- * zero.
- */
- int isset_offset;
} relopt_parse_elt;
/* Local reloption definition */
@@ -195,6 +190,9 @@ typedef struct local_relopts
extern relopt_kind add_reloption_kind(void);
extern void add_bool_reloption(bits32 kinds, const char *name, const char *desc,
bool default_val, LOCKMODE lockmode);
+extern void add_ternary_reloption(bits32 kinds, const char *name,
+ const char *desc, pg_ternary default_val,
+ LOCKMODE lockmode);
extern void add_int_reloption(bits32 kinds, const char *name, const char *desc,
int default_val, int min_val, int max_val,
LOCKMODE lockmode);
@@ -214,6 +212,9 @@ extern void register_reloptions_validator(local_relopts *relopts,
extern void add_local_bool_reloption(local_relopts *relopts, const char *name,
const char *desc, bool default_val,
int offset);
+extern void add_local_ternary_reloption(local_relopts *relopts,
+ const char *name, const char *desc,
+ pg_ternary default_val, int offset);
extern void add_local_int_reloption(local_relopts *relopts, const char *name,
const char *desc, int default_val,
int min_val, int max_val, int offset);
diff --git a/src/include/postgres.h b/src/include/postgres.h
index 7d93fbce709..47c88780776 100644
--- a/src/include/postgres.h
+++ b/src/include/postgres.h
@@ -543,6 +543,21 @@ Float8GetDatum(float8 X)
* ----------------------------------------------------------------
*/
+/*
+ * pg_ternary
+ * Boolean value with an extra "unset" value
+ *
+ * This enum can be used for values that want to distinguish between true,
+ * false, and unset.
+*/
+
+typedef enum pg_ternary
+{
+ TERNARY_FALSE = 0,
+ TERNARY_TRUE = 1,
+ TERNARY_UNSET = -1
+} pg_ternary;
+
/*
* NON_EXEC_STATIC: It's sometimes useful to define a variable or function
* that is normally static but extern when using EXEC_BACKEND (see
diff --git a/src/include/utils/rel.h b/src/include/utils/rel.h
index d03ab247788..5feb18a5373 100644
--- a/src/include/utils/rel.h
+++ b/src/include/utils/rel.h
@@ -347,8 +347,7 @@ typedef struct StdRdOptions
bool user_catalog_table; /* use as an additional catalog relation */
int parallel_workers; /* max number of parallel workers */
StdRdOptIndexCleanup vacuum_index_cleanup; /* controls index vacuuming */
- bool vacuum_truncate; /* enables vacuum to truncate a relation */
- bool vacuum_truncate_set; /* whether vacuum_truncate is set */
+ pg_ternary vacuum_truncate; /* enables vacuum to truncate a relation */
/*
* Fraction of pages in a relation that vacuum can eagerly scan and fail
diff --git a/src/test/regress/expected/reloptions.out b/src/test/regress/expected/reloptions.out
index 9de19b4e3f1..1c99f79ab01 100644
--- a/src/test/regress/expected/reloptions.out
+++ b/src/test/regress/expected/reloptions.out
@@ -98,6 +98,42 @@ SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass;
{fillfactor=13,autovacuum_enabled=false}
(1 row)
+-- Tests for future (FIXME) ternary options
+-- behave as boolean option: accept unassigned name and truncated value
+DROP TABLE reloptions_test;
+CREATE TABLE reloptions_test(i INT) WITH (vacuum_truncate);
+SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass;
+ reloptions
+------------------------
+ {vacuum_truncate=true}
+(1 row)
+
+DROP TABLE reloptions_test;
+CREATE TABLE reloptions_test(i INT) WITH (vacuum_truncate=FaLS);
+SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass;
+ reloptions
+------------------------
+ {vacuum_truncate=fals}
+(1 row)
+
+-- preferred "true" alias is used when storing
+DROP TABLE reloptions_test;
+CREATE TABLE reloptions_test(i INT) WITH (vacuum_index_cleanup=on);
+SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass;
+ reloptions
+---------------------------
+ {vacuum_index_cleanup=on}
+(1 row)
+
+-- custom "third" value is available
+DROP TABLE reloptions_test;
+CREATE TABLE reloptions_test(i INT) WITH (vacuum_index_cleanup=auto);
+SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass;
+ reloptions
+-----------------------------
+ {vacuum_index_cleanup=auto}
+(1 row)
+
-- Test vacuum_truncate option
DROP TABLE reloptions_test;
CREATE TEMP TABLE reloptions_test(i INT NOT NULL, j text)
diff --git a/src/test/regress/sql/reloptions.sql b/src/test/regress/sql/reloptions.sql
index 24fbe0b478d..f5980dafcbc 100644
--- a/src/test/regress/sql/reloptions.sql
+++ b/src/test/regress/sql/reloptions.sql
@@ -59,6 +59,27 @@ UPDATE pg_class
ALTER TABLE reloptions_test RESET (illegal_option);
SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass;
+-- Tests for future (FIXME) ternary options
+
+-- behave as boolean option: accept unassigned name and truncated value
+DROP TABLE reloptions_test;
+CREATE TABLE reloptions_test(i INT) WITH (vacuum_truncate);
+SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass;
+
+DROP TABLE reloptions_test;
+CREATE TABLE reloptions_test(i INT) WITH (vacuum_truncate=FaLS);
+SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass;
+
+-- preferred "true" alias is used when storing
+DROP TABLE reloptions_test;
+CREATE TABLE reloptions_test(i INT) WITH (vacuum_index_cleanup=on);
+SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass;
+
+-- custom "third" value is available
+DROP TABLE reloptions_test;
+CREATE TABLE reloptions_test(i INT) WITH (vacuum_index_cleanup=auto);
+SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass;
+
-- Test vacuum_truncate option
DROP TABLE reloptions_test;
diff --git a/src/tools/pgindent/typedefs.list b/src/tools/pgindent/typedefs.list
index 3f3a888fd0e..1c8610fd46c 100644
--- a/src/tools/pgindent/typedefs.list
+++ b/src/tools/pgindent/typedefs.list
@@ -3952,6 +3952,7 @@ pg_sha512_ctx
pg_snapshot
pg_special_case
pg_stack_base_t
+pg_ternary
pg_time_t
pg_time_usec_t
pg_tz
@@ -4079,6 +4080,7 @@ relopt_kind
relopt_parse_elt
relopt_real
relopt_string
+relopt_ternary
relopt_type
relopt_value
relopts_validator
--
2.47.3
--c36t3fndxhkzbl5z--
^ permalink raw reply [nested|flat] 868+ messages in thread
* [PATCH v3] Introduce ternary reloptions
@ 2026-01-16 15:04 Álvaro Herrera <[email protected]>
0 siblings, 0 replies; 868+ messages in thread
From: Álvaro Herrera @ 2026-01-16 15:04 UTC (permalink / raw)
Introduce ternary reloption as a replacement for current
`vacuum_truncate` implementation. Remove the `vacuum_truncate_set`
separate flag and use TERNARY_UNSET instead.
This could also be used for other options such as `vacuum_index_cleanup`
and `buffering`, but lets get the scaffolding in first.
Discussion: https://postgr.es/m/3474141.usfYGdeWWP@thinkpad-pgpro
---
src/backend/access/common/reloptions.c | 137 ++++++++++++++++++-----
src/backend/commands/vacuum.c | 4 +-
src/include/access/reloptions.h | 27 ++---
src/include/postgres.h | 15 +++
src/include/utils/rel.h | 3 +-
src/test/regress/expected/reloptions.out | 36 ++++++
src/test/regress/sql/reloptions.sql | 21 ++++
src/tools/pgindent/typedefs.list | 2 +
8 files changed, 202 insertions(+), 43 deletions(-)
diff --git a/src/backend/access/common/reloptions.c b/src/backend/access/common/reloptions.c
index 0b83f98ed5f..6f1f577581a 100644
--- a/src/backend/access/common/reloptions.c
+++ b/src/backend/access/common/reloptions.c
@@ -40,9 +40,9 @@
*
* To add an option:
*
- * (i) decide on a type (bool, integer, real, enum, string), name, default
- * value, upper and lower bounds (if applicable); for strings, consider a
- * validation routine.
+ * (i) decide on a type (bool, ternary, integer, real, enum, string), name,
+ * default value, upper and lower bounds (if applicable); for strings,
+ * consider a validation routine.
* (ii) add a record below (or use add_<type>_reloption).
* (iii) add it to the appropriate options struct (perhaps StdRdOptions)
* (iv) add it to the appropriate handling routine (perhaps
@@ -147,15 +147,6 @@ static relopt_bool boolRelOpts[] =
},
false
},
- {
- {
- "vacuum_truncate",
- "Enables vacuum to truncate empty pages at the end of this table",
- RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
- ShareUpdateExclusiveLock
- },
- true
- },
{
{
"deduplicate_items",
@@ -170,6 +161,25 @@ static relopt_bool boolRelOpts[] =
{{NULL}}
};
+static relopt_ternary ternaryRelOpts[] =
+{
+ {
+ {
+ "vacuum_truncate",
+ "Enables vacuum to truncate empty pages at the end of this table",
+ RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
+ ShareUpdateExclusiveLock
+ },
+ TERNARY_UNSET
+ },
+ /* list terminator */
+ {
+ {
+ NULL
+ }
+ }
+};
+
static relopt_int intRelOpts[] =
{
{
@@ -609,6 +619,13 @@ initialize_reloptions(void)
boolRelOpts[i].gen.lockmode));
j++;
}
+ for (i = 0; ternaryRelOpts[i].gen.name; i++)
+ {
+ Assert(DoLockModesConflict(ternaryRelOpts[i].gen.lockmode,
+ ternaryRelOpts[i].gen.lockmode));
+ j++;
+ }
+
for (i = 0; intRelOpts[i].gen.name; i++)
{
Assert(DoLockModesConflict(intRelOpts[i].gen.lockmode,
@@ -649,6 +666,14 @@ initialize_reloptions(void)
j++;
}
+ for (i = 0; ternaryRelOpts[i].gen.name; i++)
+ {
+ relOpts[j] = &ternaryRelOpts[i].gen;
+ relOpts[j]->type = RELOPT_TYPE_TERNARY;
+ relOpts[j]->namelen = strlen(relOpts[j]->name);
+ j++;
+ }
+
for (i = 0; intRelOpts[i].gen.name; i++)
{
relOpts[j] = &intRelOpts[i].gen;
@@ -809,6 +834,9 @@ allocate_reloption(bits32 kinds, int type, const char *name, const char *desc,
case RELOPT_TYPE_BOOL:
size = sizeof(relopt_bool);
break;
+ case RELOPT_TYPE_TERNARY:
+ size = sizeof(relopt_ternary);
+ break;
case RELOPT_TYPE_INT:
size = sizeof(relopt_int);
break;
@@ -892,6 +920,57 @@ add_local_bool_reloption(local_relopts *relopts, const char *name,
add_local_reloption(relopts, (relopt_gen *) newoption, offset);
}
+/*
+ * init_ternary_reloption
+ * Allocate and initialize a new ternary reloption
+ */
+static relopt_ternary *
+init_ternary_reloption(bits32 kinds, const char *name, const char *desc,
+ pg_ternary default_val, LOCKMODE lockmode)
+{
+ relopt_ternary *newoption;
+
+ newoption = (relopt_ternary *)
+ allocate_reloption(kinds, RELOPT_TYPE_TERNARY, name, desc, lockmode);
+ newoption->default_val = default_val;
+
+ return newoption;
+}
+
+/*
+ * add_ternary_reloption
+ * Add a new ternary reloption
+ */
+void
+add_ternary_reloption(bits32 kinds, const char *name, const char *desc,
+ pg_ternary default_val, LOCKMODE lockmode)
+{
+ relopt_ternary *newoption;
+
+ newoption =
+ init_ternary_reloption(kinds, name, desc, default_val, lockmode);
+
+ add_reloption((relopt_gen *) newoption);
+}
+
+/*
+ * add_local_ternary_reloption
+ * Add a new ternary local reloption
+ *
+ * 'offset' is offset of ternary-typed field.
+ */
+void
+add_local_ternary_reloption(local_relopts *relopts, const char *name,
+ const char *desc, pg_ternary default_val,
+ int offset)
+{
+ relopt_ternary *newoption;
+
+ newoption =
+ init_ternary_reloption(RELOPT_KIND_LOCAL, name, desc, default_val, 0);
+
+ add_local_reloption(relopts, (relopt_gen *) newoption, offset);
+}
/*
* init_real_reloption
@@ -1626,6 +1705,19 @@ parse_one_reloption(relopt_value *option, char *text_str, int text_len,
option->gen->name, value)));
}
break;
+ case RELOPT_TYPE_TERNARY:
+ {
+ bool b;
+
+ parsed = parse_bool(value, &b);
+ option->values.ternary_val = b ? TERNARY_TRUE : TERNARY_FALSE;
+ if (validate && !parsed)
+ ereport(ERROR,
+ (errcode(ERRCODE_INVALID_PARAMETER_VALUE),
+ errmsg("invalid value for ternary option \"%s\": %s",
+ option->gen->name, value)));
+ }
+ break;
case RELOPT_TYPE_INT:
{
relopt_int *optint = (relopt_int *) option->gen;
@@ -1789,17 +1881,6 @@ fillRelOptions(void *rdopts, Size basesize,
char *itempos = ((char *) rdopts) + elems[j].offset;
char *string_val;
- /*
- * If isset_offset is provided, store whether the reloption is
- * set there.
- */
- if (elems[j].isset_offset > 0)
- {
- char *setpos = ((char *) rdopts) + elems[j].isset_offset;
-
- *(bool *) setpos = options[i].isset;
- }
-
switch (options[i].gen->type)
{
case RELOPT_TYPE_BOOL:
@@ -1807,6 +1888,11 @@ fillRelOptions(void *rdopts, Size basesize,
options[i].values.bool_val :
((relopt_bool *) options[i].gen)->default_val;
break;
+ case RELOPT_TYPE_TERNARY:
+ *(pg_ternary *) itempos = options[i].isset ?
+ options[i].values.ternary_val :
+ ((relopt_ternary *) options[i].gen)->default_val;
+ break;
case RELOPT_TYPE_INT:
*(int *) itempos = options[i].isset ?
options[i].values.int_val :
@@ -1923,8 +2009,8 @@ default_reloptions(Datum reloptions, bool validate, relopt_kind kind)
offsetof(StdRdOptions, parallel_workers)},
{"vacuum_index_cleanup", RELOPT_TYPE_ENUM,
offsetof(StdRdOptions, vacuum_index_cleanup)},
- {"vacuum_truncate", RELOPT_TYPE_BOOL,
- offsetof(StdRdOptions, vacuum_truncate), offsetof(StdRdOptions, vacuum_truncate_set)},
+ {"vacuum_truncate", RELOPT_TYPE_TERNARY,
+ offsetof(StdRdOptions, vacuum_truncate)},
{"vacuum_max_eager_freeze_failure_rate", RELOPT_TYPE_REAL,
offsetof(StdRdOptions, vacuum_max_eager_freeze_failure_rate)}
};
@@ -2004,7 +2090,6 @@ build_local_reloptions(local_relopts *relopts, Datum options, bool validate)
elems[i].optname = opt->option->name;
elems[i].opttype = opt->option->type;
elems[i].offset = opt->offset;
- elems[i].isset_offset = 0; /* not supported for local relopts yet */
i++;
}
diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c
index aa4fbec143f..696eab9bd97 100644
--- a/src/backend/commands/vacuum.c
+++ b/src/backend/commands/vacuum.c
@@ -2224,9 +2224,9 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams params,
{
StdRdOptions *opts = (StdRdOptions *) rel->rd_options;
- if (opts && opts->vacuum_truncate_set)
+ if (opts && opts->vacuum_truncate != TERNARY_UNSET)
{
- if (opts->vacuum_truncate)
+ if (opts->vacuum_truncate == TERNARY_TRUE)
params.truncate = VACOPTVALUE_ENABLED;
else
params.truncate = VACOPTVALUE_DISABLED;
diff --git a/src/include/access/reloptions.h b/src/include/access/reloptions.h
index 2f08e1b0cf0..dfbef2babf2 100644
--- a/src/include/access/reloptions.h
+++ b/src/include/access/reloptions.h
@@ -29,6 +29,7 @@
typedef enum relopt_type
{
RELOPT_TYPE_BOOL,
+ RELOPT_TYPE_TERNARY, /* on, off, unset */
RELOPT_TYPE_INT,
RELOPT_TYPE_REAL,
RELOPT_TYPE_ENUM,
@@ -80,6 +81,7 @@ typedef struct relopt_value
union
{
bool bool_val;
+ pg_ternary ternary_val;
int int_val;
double real_val;
int enum_val;
@@ -94,6 +96,12 @@ typedef struct relopt_bool
bool default_val;
} relopt_bool;
+typedef struct relopt_ternary
+{
+ relopt_gen gen;
+ int default_val;
+} relopt_ternary;
+
typedef struct relopt_int
{
relopt_gen gen;
@@ -152,19 +160,6 @@ typedef struct
const char *optname; /* option's name */
relopt_type opttype; /* option's datatype */
int offset; /* offset of field in result struct */
-
- /*
- * isset_offset is an optional offset of a field in the result struct that
- * stores whether the option is explicitly set for the relation or if it
- * just picked up the default value. In most cases, this can be
- * accomplished by giving the reloption a special out-of-range default
- * value (e.g., some integer reloptions use -2), but this isn't always
- * possible. For example, a Boolean reloption cannot be given an
- * out-of-range default, so we need another way to discover the source of
- * its value. This offset is only used if given a value greater than
- * zero.
- */
- int isset_offset;
} relopt_parse_elt;
/* Local reloption definition */
@@ -195,6 +190,9 @@ typedef struct local_relopts
extern relopt_kind add_reloption_kind(void);
extern void add_bool_reloption(bits32 kinds, const char *name, const char *desc,
bool default_val, LOCKMODE lockmode);
+extern void add_ternary_reloption(bits32 kinds, const char *name,
+ const char *desc, pg_ternary default_val,
+ LOCKMODE lockmode);
extern void add_int_reloption(bits32 kinds, const char *name, const char *desc,
int default_val, int min_val, int max_val,
LOCKMODE lockmode);
@@ -214,6 +212,9 @@ extern void register_reloptions_validator(local_relopts *relopts,
extern void add_local_bool_reloption(local_relopts *relopts, const char *name,
const char *desc, bool default_val,
int offset);
+extern void add_local_ternary_reloption(local_relopts *relopts,
+ const char *name, const char *desc,
+ pg_ternary default_val, int offset);
extern void add_local_int_reloption(local_relopts *relopts, const char *name,
const char *desc, int default_val,
int min_val, int max_val, int offset);
diff --git a/src/include/postgres.h b/src/include/postgres.h
index 7d93fbce709..47c88780776 100644
--- a/src/include/postgres.h
+++ b/src/include/postgres.h
@@ -543,6 +543,21 @@ Float8GetDatum(float8 X)
* ----------------------------------------------------------------
*/
+/*
+ * pg_ternary
+ * Boolean value with an extra "unset" value
+ *
+ * This enum can be used for values that want to distinguish between true,
+ * false, and unset.
+*/
+
+typedef enum pg_ternary
+{
+ TERNARY_FALSE = 0,
+ TERNARY_TRUE = 1,
+ TERNARY_UNSET = -1
+} pg_ternary;
+
/*
* NON_EXEC_STATIC: It's sometimes useful to define a variable or function
* that is normally static but extern when using EXEC_BACKEND (see
diff --git a/src/include/utils/rel.h b/src/include/utils/rel.h
index d03ab247788..5feb18a5373 100644
--- a/src/include/utils/rel.h
+++ b/src/include/utils/rel.h
@@ -347,8 +347,7 @@ typedef struct StdRdOptions
bool user_catalog_table; /* use as an additional catalog relation */
int parallel_workers; /* max number of parallel workers */
StdRdOptIndexCleanup vacuum_index_cleanup; /* controls index vacuuming */
- bool vacuum_truncate; /* enables vacuum to truncate a relation */
- bool vacuum_truncate_set; /* whether vacuum_truncate is set */
+ pg_ternary vacuum_truncate; /* enables vacuum to truncate a relation */
/*
* Fraction of pages in a relation that vacuum can eagerly scan and fail
diff --git a/src/test/regress/expected/reloptions.out b/src/test/regress/expected/reloptions.out
index 9de19b4e3f1..1c99f79ab01 100644
--- a/src/test/regress/expected/reloptions.out
+++ b/src/test/regress/expected/reloptions.out
@@ -98,6 +98,42 @@ SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass;
{fillfactor=13,autovacuum_enabled=false}
(1 row)
+-- Tests for future (FIXME) ternary options
+-- behave as boolean option: accept unassigned name and truncated value
+DROP TABLE reloptions_test;
+CREATE TABLE reloptions_test(i INT) WITH (vacuum_truncate);
+SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass;
+ reloptions
+------------------------
+ {vacuum_truncate=true}
+(1 row)
+
+DROP TABLE reloptions_test;
+CREATE TABLE reloptions_test(i INT) WITH (vacuum_truncate=FaLS);
+SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass;
+ reloptions
+------------------------
+ {vacuum_truncate=fals}
+(1 row)
+
+-- preferred "true" alias is used when storing
+DROP TABLE reloptions_test;
+CREATE TABLE reloptions_test(i INT) WITH (vacuum_index_cleanup=on);
+SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass;
+ reloptions
+---------------------------
+ {vacuum_index_cleanup=on}
+(1 row)
+
+-- custom "third" value is available
+DROP TABLE reloptions_test;
+CREATE TABLE reloptions_test(i INT) WITH (vacuum_index_cleanup=auto);
+SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass;
+ reloptions
+-----------------------------
+ {vacuum_index_cleanup=auto}
+(1 row)
+
-- Test vacuum_truncate option
DROP TABLE reloptions_test;
CREATE TEMP TABLE reloptions_test(i INT NOT NULL, j text)
diff --git a/src/test/regress/sql/reloptions.sql b/src/test/regress/sql/reloptions.sql
index 24fbe0b478d..f5980dafcbc 100644
--- a/src/test/regress/sql/reloptions.sql
+++ b/src/test/regress/sql/reloptions.sql
@@ -59,6 +59,27 @@ UPDATE pg_class
ALTER TABLE reloptions_test RESET (illegal_option);
SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass;
+-- Tests for future (FIXME) ternary options
+
+-- behave as boolean option: accept unassigned name and truncated value
+DROP TABLE reloptions_test;
+CREATE TABLE reloptions_test(i INT) WITH (vacuum_truncate);
+SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass;
+
+DROP TABLE reloptions_test;
+CREATE TABLE reloptions_test(i INT) WITH (vacuum_truncate=FaLS);
+SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass;
+
+-- preferred "true" alias is used when storing
+DROP TABLE reloptions_test;
+CREATE TABLE reloptions_test(i INT) WITH (vacuum_index_cleanup=on);
+SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass;
+
+-- custom "third" value is available
+DROP TABLE reloptions_test;
+CREATE TABLE reloptions_test(i INT) WITH (vacuum_index_cleanup=auto);
+SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass;
+
-- Test vacuum_truncate option
DROP TABLE reloptions_test;
diff --git a/src/tools/pgindent/typedefs.list b/src/tools/pgindent/typedefs.list
index 3f3a888fd0e..1c8610fd46c 100644
--- a/src/tools/pgindent/typedefs.list
+++ b/src/tools/pgindent/typedefs.list
@@ -3952,6 +3952,7 @@ pg_sha512_ctx
pg_snapshot
pg_special_case
pg_stack_base_t
+pg_ternary
pg_time_t
pg_time_usec_t
pg_tz
@@ -4079,6 +4080,7 @@ relopt_kind
relopt_parse_elt
relopt_real
relopt_string
+relopt_ternary
relopt_type
relopt_value
relopts_validator
--
2.47.3
--c36t3fndxhkzbl5z--
^ permalink raw reply [nested|flat] 868+ messages in thread
* [PATCH v3] Introduce ternary reloptions
@ 2026-01-16 15:04 Álvaro Herrera <[email protected]>
0 siblings, 0 replies; 868+ messages in thread
From: Álvaro Herrera @ 2026-01-16 15:04 UTC (permalink / raw)
Introduce ternary reloption as a replacement for current
`vacuum_truncate` implementation. Remove the `vacuum_truncate_set`
separate flag and use TERNARY_UNSET instead.
This could also be used for other options such as `vacuum_index_cleanup`
and `buffering`, but lets get the scaffolding in first.
Discussion: https://postgr.es/m/3474141.usfYGdeWWP@thinkpad-pgpro
---
src/backend/access/common/reloptions.c | 137 ++++++++++++++++++-----
src/backend/commands/vacuum.c | 4 +-
src/include/access/reloptions.h | 27 ++---
src/include/postgres.h | 15 +++
src/include/utils/rel.h | 3 +-
src/test/regress/expected/reloptions.out | 36 ++++++
src/test/regress/sql/reloptions.sql | 21 ++++
src/tools/pgindent/typedefs.list | 2 +
8 files changed, 202 insertions(+), 43 deletions(-)
diff --git a/src/backend/access/common/reloptions.c b/src/backend/access/common/reloptions.c
index 0b83f98ed5f..6f1f577581a 100644
--- a/src/backend/access/common/reloptions.c
+++ b/src/backend/access/common/reloptions.c
@@ -40,9 +40,9 @@
*
* To add an option:
*
- * (i) decide on a type (bool, integer, real, enum, string), name, default
- * value, upper and lower bounds (if applicable); for strings, consider a
- * validation routine.
+ * (i) decide on a type (bool, ternary, integer, real, enum, string), name,
+ * default value, upper and lower bounds (if applicable); for strings,
+ * consider a validation routine.
* (ii) add a record below (or use add_<type>_reloption).
* (iii) add it to the appropriate options struct (perhaps StdRdOptions)
* (iv) add it to the appropriate handling routine (perhaps
@@ -147,15 +147,6 @@ static relopt_bool boolRelOpts[] =
},
false
},
- {
- {
- "vacuum_truncate",
- "Enables vacuum to truncate empty pages at the end of this table",
- RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
- ShareUpdateExclusiveLock
- },
- true
- },
{
{
"deduplicate_items",
@@ -170,6 +161,25 @@ static relopt_bool boolRelOpts[] =
{{NULL}}
};
+static relopt_ternary ternaryRelOpts[] =
+{
+ {
+ {
+ "vacuum_truncate",
+ "Enables vacuum to truncate empty pages at the end of this table",
+ RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
+ ShareUpdateExclusiveLock
+ },
+ TERNARY_UNSET
+ },
+ /* list terminator */
+ {
+ {
+ NULL
+ }
+ }
+};
+
static relopt_int intRelOpts[] =
{
{
@@ -609,6 +619,13 @@ initialize_reloptions(void)
boolRelOpts[i].gen.lockmode));
j++;
}
+ for (i = 0; ternaryRelOpts[i].gen.name; i++)
+ {
+ Assert(DoLockModesConflict(ternaryRelOpts[i].gen.lockmode,
+ ternaryRelOpts[i].gen.lockmode));
+ j++;
+ }
+
for (i = 0; intRelOpts[i].gen.name; i++)
{
Assert(DoLockModesConflict(intRelOpts[i].gen.lockmode,
@@ -649,6 +666,14 @@ initialize_reloptions(void)
j++;
}
+ for (i = 0; ternaryRelOpts[i].gen.name; i++)
+ {
+ relOpts[j] = &ternaryRelOpts[i].gen;
+ relOpts[j]->type = RELOPT_TYPE_TERNARY;
+ relOpts[j]->namelen = strlen(relOpts[j]->name);
+ j++;
+ }
+
for (i = 0; intRelOpts[i].gen.name; i++)
{
relOpts[j] = &intRelOpts[i].gen;
@@ -809,6 +834,9 @@ allocate_reloption(bits32 kinds, int type, const char *name, const char *desc,
case RELOPT_TYPE_BOOL:
size = sizeof(relopt_bool);
break;
+ case RELOPT_TYPE_TERNARY:
+ size = sizeof(relopt_ternary);
+ break;
case RELOPT_TYPE_INT:
size = sizeof(relopt_int);
break;
@@ -892,6 +920,57 @@ add_local_bool_reloption(local_relopts *relopts, const char *name,
add_local_reloption(relopts, (relopt_gen *) newoption, offset);
}
+/*
+ * init_ternary_reloption
+ * Allocate and initialize a new ternary reloption
+ */
+static relopt_ternary *
+init_ternary_reloption(bits32 kinds, const char *name, const char *desc,
+ pg_ternary default_val, LOCKMODE lockmode)
+{
+ relopt_ternary *newoption;
+
+ newoption = (relopt_ternary *)
+ allocate_reloption(kinds, RELOPT_TYPE_TERNARY, name, desc, lockmode);
+ newoption->default_val = default_val;
+
+ return newoption;
+}
+
+/*
+ * add_ternary_reloption
+ * Add a new ternary reloption
+ */
+void
+add_ternary_reloption(bits32 kinds, const char *name, const char *desc,
+ pg_ternary default_val, LOCKMODE lockmode)
+{
+ relopt_ternary *newoption;
+
+ newoption =
+ init_ternary_reloption(kinds, name, desc, default_val, lockmode);
+
+ add_reloption((relopt_gen *) newoption);
+}
+
+/*
+ * add_local_ternary_reloption
+ * Add a new ternary local reloption
+ *
+ * 'offset' is offset of ternary-typed field.
+ */
+void
+add_local_ternary_reloption(local_relopts *relopts, const char *name,
+ const char *desc, pg_ternary default_val,
+ int offset)
+{
+ relopt_ternary *newoption;
+
+ newoption =
+ init_ternary_reloption(RELOPT_KIND_LOCAL, name, desc, default_val, 0);
+
+ add_local_reloption(relopts, (relopt_gen *) newoption, offset);
+}
/*
* init_real_reloption
@@ -1626,6 +1705,19 @@ parse_one_reloption(relopt_value *option, char *text_str, int text_len,
option->gen->name, value)));
}
break;
+ case RELOPT_TYPE_TERNARY:
+ {
+ bool b;
+
+ parsed = parse_bool(value, &b);
+ option->values.ternary_val = b ? TERNARY_TRUE : TERNARY_FALSE;
+ if (validate && !parsed)
+ ereport(ERROR,
+ (errcode(ERRCODE_INVALID_PARAMETER_VALUE),
+ errmsg("invalid value for ternary option \"%s\": %s",
+ option->gen->name, value)));
+ }
+ break;
case RELOPT_TYPE_INT:
{
relopt_int *optint = (relopt_int *) option->gen;
@@ -1789,17 +1881,6 @@ fillRelOptions(void *rdopts, Size basesize,
char *itempos = ((char *) rdopts) + elems[j].offset;
char *string_val;
- /*
- * If isset_offset is provided, store whether the reloption is
- * set there.
- */
- if (elems[j].isset_offset > 0)
- {
- char *setpos = ((char *) rdopts) + elems[j].isset_offset;
-
- *(bool *) setpos = options[i].isset;
- }
-
switch (options[i].gen->type)
{
case RELOPT_TYPE_BOOL:
@@ -1807,6 +1888,11 @@ fillRelOptions(void *rdopts, Size basesize,
options[i].values.bool_val :
((relopt_bool *) options[i].gen)->default_val;
break;
+ case RELOPT_TYPE_TERNARY:
+ *(pg_ternary *) itempos = options[i].isset ?
+ options[i].values.ternary_val :
+ ((relopt_ternary *) options[i].gen)->default_val;
+ break;
case RELOPT_TYPE_INT:
*(int *) itempos = options[i].isset ?
options[i].values.int_val :
@@ -1923,8 +2009,8 @@ default_reloptions(Datum reloptions, bool validate, relopt_kind kind)
offsetof(StdRdOptions, parallel_workers)},
{"vacuum_index_cleanup", RELOPT_TYPE_ENUM,
offsetof(StdRdOptions, vacuum_index_cleanup)},
- {"vacuum_truncate", RELOPT_TYPE_BOOL,
- offsetof(StdRdOptions, vacuum_truncate), offsetof(StdRdOptions, vacuum_truncate_set)},
+ {"vacuum_truncate", RELOPT_TYPE_TERNARY,
+ offsetof(StdRdOptions, vacuum_truncate)},
{"vacuum_max_eager_freeze_failure_rate", RELOPT_TYPE_REAL,
offsetof(StdRdOptions, vacuum_max_eager_freeze_failure_rate)}
};
@@ -2004,7 +2090,6 @@ build_local_reloptions(local_relopts *relopts, Datum options, bool validate)
elems[i].optname = opt->option->name;
elems[i].opttype = opt->option->type;
elems[i].offset = opt->offset;
- elems[i].isset_offset = 0; /* not supported for local relopts yet */
i++;
}
diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c
index aa4fbec143f..696eab9bd97 100644
--- a/src/backend/commands/vacuum.c
+++ b/src/backend/commands/vacuum.c
@@ -2224,9 +2224,9 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams params,
{
StdRdOptions *opts = (StdRdOptions *) rel->rd_options;
- if (opts && opts->vacuum_truncate_set)
+ if (opts && opts->vacuum_truncate != TERNARY_UNSET)
{
- if (opts->vacuum_truncate)
+ if (opts->vacuum_truncate == TERNARY_TRUE)
params.truncate = VACOPTVALUE_ENABLED;
else
params.truncate = VACOPTVALUE_DISABLED;
diff --git a/src/include/access/reloptions.h b/src/include/access/reloptions.h
index 2f08e1b0cf0..dfbef2babf2 100644
--- a/src/include/access/reloptions.h
+++ b/src/include/access/reloptions.h
@@ -29,6 +29,7 @@
typedef enum relopt_type
{
RELOPT_TYPE_BOOL,
+ RELOPT_TYPE_TERNARY, /* on, off, unset */
RELOPT_TYPE_INT,
RELOPT_TYPE_REAL,
RELOPT_TYPE_ENUM,
@@ -80,6 +81,7 @@ typedef struct relopt_value
union
{
bool bool_val;
+ pg_ternary ternary_val;
int int_val;
double real_val;
int enum_val;
@@ -94,6 +96,12 @@ typedef struct relopt_bool
bool default_val;
} relopt_bool;
+typedef struct relopt_ternary
+{
+ relopt_gen gen;
+ int default_val;
+} relopt_ternary;
+
typedef struct relopt_int
{
relopt_gen gen;
@@ -152,19 +160,6 @@ typedef struct
const char *optname; /* option's name */
relopt_type opttype; /* option's datatype */
int offset; /* offset of field in result struct */
-
- /*
- * isset_offset is an optional offset of a field in the result struct that
- * stores whether the option is explicitly set for the relation or if it
- * just picked up the default value. In most cases, this can be
- * accomplished by giving the reloption a special out-of-range default
- * value (e.g., some integer reloptions use -2), but this isn't always
- * possible. For example, a Boolean reloption cannot be given an
- * out-of-range default, so we need another way to discover the source of
- * its value. This offset is only used if given a value greater than
- * zero.
- */
- int isset_offset;
} relopt_parse_elt;
/* Local reloption definition */
@@ -195,6 +190,9 @@ typedef struct local_relopts
extern relopt_kind add_reloption_kind(void);
extern void add_bool_reloption(bits32 kinds, const char *name, const char *desc,
bool default_val, LOCKMODE lockmode);
+extern void add_ternary_reloption(bits32 kinds, const char *name,
+ const char *desc, pg_ternary default_val,
+ LOCKMODE lockmode);
extern void add_int_reloption(bits32 kinds, const char *name, const char *desc,
int default_val, int min_val, int max_val,
LOCKMODE lockmode);
@@ -214,6 +212,9 @@ extern void register_reloptions_validator(local_relopts *relopts,
extern void add_local_bool_reloption(local_relopts *relopts, const char *name,
const char *desc, bool default_val,
int offset);
+extern void add_local_ternary_reloption(local_relopts *relopts,
+ const char *name, const char *desc,
+ pg_ternary default_val, int offset);
extern void add_local_int_reloption(local_relopts *relopts, const char *name,
const char *desc, int default_val,
int min_val, int max_val, int offset);
diff --git a/src/include/postgres.h b/src/include/postgres.h
index 7d93fbce709..47c88780776 100644
--- a/src/include/postgres.h
+++ b/src/include/postgres.h
@@ -543,6 +543,21 @@ Float8GetDatum(float8 X)
* ----------------------------------------------------------------
*/
+/*
+ * pg_ternary
+ * Boolean value with an extra "unset" value
+ *
+ * This enum can be used for values that want to distinguish between true,
+ * false, and unset.
+*/
+
+typedef enum pg_ternary
+{
+ TERNARY_FALSE = 0,
+ TERNARY_TRUE = 1,
+ TERNARY_UNSET = -1
+} pg_ternary;
+
/*
* NON_EXEC_STATIC: It's sometimes useful to define a variable or function
* that is normally static but extern when using EXEC_BACKEND (see
diff --git a/src/include/utils/rel.h b/src/include/utils/rel.h
index d03ab247788..5feb18a5373 100644
--- a/src/include/utils/rel.h
+++ b/src/include/utils/rel.h
@@ -347,8 +347,7 @@ typedef struct StdRdOptions
bool user_catalog_table; /* use as an additional catalog relation */
int parallel_workers; /* max number of parallel workers */
StdRdOptIndexCleanup vacuum_index_cleanup; /* controls index vacuuming */
- bool vacuum_truncate; /* enables vacuum to truncate a relation */
- bool vacuum_truncate_set; /* whether vacuum_truncate is set */
+ pg_ternary vacuum_truncate; /* enables vacuum to truncate a relation */
/*
* Fraction of pages in a relation that vacuum can eagerly scan and fail
diff --git a/src/test/regress/expected/reloptions.out b/src/test/regress/expected/reloptions.out
index 9de19b4e3f1..1c99f79ab01 100644
--- a/src/test/regress/expected/reloptions.out
+++ b/src/test/regress/expected/reloptions.out
@@ -98,6 +98,42 @@ SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass;
{fillfactor=13,autovacuum_enabled=false}
(1 row)
+-- Tests for future (FIXME) ternary options
+-- behave as boolean option: accept unassigned name and truncated value
+DROP TABLE reloptions_test;
+CREATE TABLE reloptions_test(i INT) WITH (vacuum_truncate);
+SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass;
+ reloptions
+------------------------
+ {vacuum_truncate=true}
+(1 row)
+
+DROP TABLE reloptions_test;
+CREATE TABLE reloptions_test(i INT) WITH (vacuum_truncate=FaLS);
+SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass;
+ reloptions
+------------------------
+ {vacuum_truncate=fals}
+(1 row)
+
+-- preferred "true" alias is used when storing
+DROP TABLE reloptions_test;
+CREATE TABLE reloptions_test(i INT) WITH (vacuum_index_cleanup=on);
+SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass;
+ reloptions
+---------------------------
+ {vacuum_index_cleanup=on}
+(1 row)
+
+-- custom "third" value is available
+DROP TABLE reloptions_test;
+CREATE TABLE reloptions_test(i INT) WITH (vacuum_index_cleanup=auto);
+SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass;
+ reloptions
+-----------------------------
+ {vacuum_index_cleanup=auto}
+(1 row)
+
-- Test vacuum_truncate option
DROP TABLE reloptions_test;
CREATE TEMP TABLE reloptions_test(i INT NOT NULL, j text)
diff --git a/src/test/regress/sql/reloptions.sql b/src/test/regress/sql/reloptions.sql
index 24fbe0b478d..f5980dafcbc 100644
--- a/src/test/regress/sql/reloptions.sql
+++ b/src/test/regress/sql/reloptions.sql
@@ -59,6 +59,27 @@ UPDATE pg_class
ALTER TABLE reloptions_test RESET (illegal_option);
SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass;
+-- Tests for future (FIXME) ternary options
+
+-- behave as boolean option: accept unassigned name and truncated value
+DROP TABLE reloptions_test;
+CREATE TABLE reloptions_test(i INT) WITH (vacuum_truncate);
+SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass;
+
+DROP TABLE reloptions_test;
+CREATE TABLE reloptions_test(i INT) WITH (vacuum_truncate=FaLS);
+SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass;
+
+-- preferred "true" alias is used when storing
+DROP TABLE reloptions_test;
+CREATE TABLE reloptions_test(i INT) WITH (vacuum_index_cleanup=on);
+SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass;
+
+-- custom "third" value is available
+DROP TABLE reloptions_test;
+CREATE TABLE reloptions_test(i INT) WITH (vacuum_index_cleanup=auto);
+SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass;
+
-- Test vacuum_truncate option
DROP TABLE reloptions_test;
diff --git a/src/tools/pgindent/typedefs.list b/src/tools/pgindent/typedefs.list
index 3f3a888fd0e..1c8610fd46c 100644
--- a/src/tools/pgindent/typedefs.list
+++ b/src/tools/pgindent/typedefs.list
@@ -3952,6 +3952,7 @@ pg_sha512_ctx
pg_snapshot
pg_special_case
pg_stack_base_t
+pg_ternary
pg_time_t
pg_time_usec_t
pg_tz
@@ -4079,6 +4080,7 @@ relopt_kind
relopt_parse_elt
relopt_real
relopt_string
+relopt_ternary
relopt_type
relopt_value
relopts_validator
--
2.47.3
--c36t3fndxhkzbl5z--
^ permalink raw reply [nested|flat] 868+ messages in thread
* [PATCH v3] Introduce ternary reloptions
@ 2026-01-16 15:04 Álvaro Herrera <[email protected]>
0 siblings, 0 replies; 868+ messages in thread
From: Álvaro Herrera @ 2026-01-16 15:04 UTC (permalink / raw)
Introduce ternary reloption as a replacement for current
`vacuum_truncate` implementation. Remove the `vacuum_truncate_set`
separate flag and use TERNARY_UNSET instead.
This could also be used for other options such as `vacuum_index_cleanup`
and `buffering`, but lets get the scaffolding in first.
Discussion: https://postgr.es/m/3474141.usfYGdeWWP@thinkpad-pgpro
---
src/backend/access/common/reloptions.c | 137 ++++++++++++++++++-----
src/backend/commands/vacuum.c | 4 +-
src/include/access/reloptions.h | 27 ++---
src/include/postgres.h | 15 +++
src/include/utils/rel.h | 3 +-
src/test/regress/expected/reloptions.out | 36 ++++++
src/test/regress/sql/reloptions.sql | 21 ++++
src/tools/pgindent/typedefs.list | 2 +
8 files changed, 202 insertions(+), 43 deletions(-)
diff --git a/src/backend/access/common/reloptions.c b/src/backend/access/common/reloptions.c
index 0b83f98ed5f..6f1f577581a 100644
--- a/src/backend/access/common/reloptions.c
+++ b/src/backend/access/common/reloptions.c
@@ -40,9 +40,9 @@
*
* To add an option:
*
- * (i) decide on a type (bool, integer, real, enum, string), name, default
- * value, upper and lower bounds (if applicable); for strings, consider a
- * validation routine.
+ * (i) decide on a type (bool, ternary, integer, real, enum, string), name,
+ * default value, upper and lower bounds (if applicable); for strings,
+ * consider a validation routine.
* (ii) add a record below (or use add_<type>_reloption).
* (iii) add it to the appropriate options struct (perhaps StdRdOptions)
* (iv) add it to the appropriate handling routine (perhaps
@@ -147,15 +147,6 @@ static relopt_bool boolRelOpts[] =
},
false
},
- {
- {
- "vacuum_truncate",
- "Enables vacuum to truncate empty pages at the end of this table",
- RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
- ShareUpdateExclusiveLock
- },
- true
- },
{
{
"deduplicate_items",
@@ -170,6 +161,25 @@ static relopt_bool boolRelOpts[] =
{{NULL}}
};
+static relopt_ternary ternaryRelOpts[] =
+{
+ {
+ {
+ "vacuum_truncate",
+ "Enables vacuum to truncate empty pages at the end of this table",
+ RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
+ ShareUpdateExclusiveLock
+ },
+ TERNARY_UNSET
+ },
+ /* list terminator */
+ {
+ {
+ NULL
+ }
+ }
+};
+
static relopt_int intRelOpts[] =
{
{
@@ -609,6 +619,13 @@ initialize_reloptions(void)
boolRelOpts[i].gen.lockmode));
j++;
}
+ for (i = 0; ternaryRelOpts[i].gen.name; i++)
+ {
+ Assert(DoLockModesConflict(ternaryRelOpts[i].gen.lockmode,
+ ternaryRelOpts[i].gen.lockmode));
+ j++;
+ }
+
for (i = 0; intRelOpts[i].gen.name; i++)
{
Assert(DoLockModesConflict(intRelOpts[i].gen.lockmode,
@@ -649,6 +666,14 @@ initialize_reloptions(void)
j++;
}
+ for (i = 0; ternaryRelOpts[i].gen.name; i++)
+ {
+ relOpts[j] = &ternaryRelOpts[i].gen;
+ relOpts[j]->type = RELOPT_TYPE_TERNARY;
+ relOpts[j]->namelen = strlen(relOpts[j]->name);
+ j++;
+ }
+
for (i = 0; intRelOpts[i].gen.name; i++)
{
relOpts[j] = &intRelOpts[i].gen;
@@ -809,6 +834,9 @@ allocate_reloption(bits32 kinds, int type, const char *name, const char *desc,
case RELOPT_TYPE_BOOL:
size = sizeof(relopt_bool);
break;
+ case RELOPT_TYPE_TERNARY:
+ size = sizeof(relopt_ternary);
+ break;
case RELOPT_TYPE_INT:
size = sizeof(relopt_int);
break;
@@ -892,6 +920,57 @@ add_local_bool_reloption(local_relopts *relopts, const char *name,
add_local_reloption(relopts, (relopt_gen *) newoption, offset);
}
+/*
+ * init_ternary_reloption
+ * Allocate and initialize a new ternary reloption
+ */
+static relopt_ternary *
+init_ternary_reloption(bits32 kinds, const char *name, const char *desc,
+ pg_ternary default_val, LOCKMODE lockmode)
+{
+ relopt_ternary *newoption;
+
+ newoption = (relopt_ternary *)
+ allocate_reloption(kinds, RELOPT_TYPE_TERNARY, name, desc, lockmode);
+ newoption->default_val = default_val;
+
+ return newoption;
+}
+
+/*
+ * add_ternary_reloption
+ * Add a new ternary reloption
+ */
+void
+add_ternary_reloption(bits32 kinds, const char *name, const char *desc,
+ pg_ternary default_val, LOCKMODE lockmode)
+{
+ relopt_ternary *newoption;
+
+ newoption =
+ init_ternary_reloption(kinds, name, desc, default_val, lockmode);
+
+ add_reloption((relopt_gen *) newoption);
+}
+
+/*
+ * add_local_ternary_reloption
+ * Add a new ternary local reloption
+ *
+ * 'offset' is offset of ternary-typed field.
+ */
+void
+add_local_ternary_reloption(local_relopts *relopts, const char *name,
+ const char *desc, pg_ternary default_val,
+ int offset)
+{
+ relopt_ternary *newoption;
+
+ newoption =
+ init_ternary_reloption(RELOPT_KIND_LOCAL, name, desc, default_val, 0);
+
+ add_local_reloption(relopts, (relopt_gen *) newoption, offset);
+}
/*
* init_real_reloption
@@ -1626,6 +1705,19 @@ parse_one_reloption(relopt_value *option, char *text_str, int text_len,
option->gen->name, value)));
}
break;
+ case RELOPT_TYPE_TERNARY:
+ {
+ bool b;
+
+ parsed = parse_bool(value, &b);
+ option->values.ternary_val = b ? TERNARY_TRUE : TERNARY_FALSE;
+ if (validate && !parsed)
+ ereport(ERROR,
+ (errcode(ERRCODE_INVALID_PARAMETER_VALUE),
+ errmsg("invalid value for ternary option \"%s\": %s",
+ option->gen->name, value)));
+ }
+ break;
case RELOPT_TYPE_INT:
{
relopt_int *optint = (relopt_int *) option->gen;
@@ -1789,17 +1881,6 @@ fillRelOptions(void *rdopts, Size basesize,
char *itempos = ((char *) rdopts) + elems[j].offset;
char *string_val;
- /*
- * If isset_offset is provided, store whether the reloption is
- * set there.
- */
- if (elems[j].isset_offset > 0)
- {
- char *setpos = ((char *) rdopts) + elems[j].isset_offset;
-
- *(bool *) setpos = options[i].isset;
- }
-
switch (options[i].gen->type)
{
case RELOPT_TYPE_BOOL:
@@ -1807,6 +1888,11 @@ fillRelOptions(void *rdopts, Size basesize,
options[i].values.bool_val :
((relopt_bool *) options[i].gen)->default_val;
break;
+ case RELOPT_TYPE_TERNARY:
+ *(pg_ternary *) itempos = options[i].isset ?
+ options[i].values.ternary_val :
+ ((relopt_ternary *) options[i].gen)->default_val;
+ break;
case RELOPT_TYPE_INT:
*(int *) itempos = options[i].isset ?
options[i].values.int_val :
@@ -1923,8 +2009,8 @@ default_reloptions(Datum reloptions, bool validate, relopt_kind kind)
offsetof(StdRdOptions, parallel_workers)},
{"vacuum_index_cleanup", RELOPT_TYPE_ENUM,
offsetof(StdRdOptions, vacuum_index_cleanup)},
- {"vacuum_truncate", RELOPT_TYPE_BOOL,
- offsetof(StdRdOptions, vacuum_truncate), offsetof(StdRdOptions, vacuum_truncate_set)},
+ {"vacuum_truncate", RELOPT_TYPE_TERNARY,
+ offsetof(StdRdOptions, vacuum_truncate)},
{"vacuum_max_eager_freeze_failure_rate", RELOPT_TYPE_REAL,
offsetof(StdRdOptions, vacuum_max_eager_freeze_failure_rate)}
};
@@ -2004,7 +2090,6 @@ build_local_reloptions(local_relopts *relopts, Datum options, bool validate)
elems[i].optname = opt->option->name;
elems[i].opttype = opt->option->type;
elems[i].offset = opt->offset;
- elems[i].isset_offset = 0; /* not supported for local relopts yet */
i++;
}
diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c
index aa4fbec143f..696eab9bd97 100644
--- a/src/backend/commands/vacuum.c
+++ b/src/backend/commands/vacuum.c
@@ -2224,9 +2224,9 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams params,
{
StdRdOptions *opts = (StdRdOptions *) rel->rd_options;
- if (opts && opts->vacuum_truncate_set)
+ if (opts && opts->vacuum_truncate != TERNARY_UNSET)
{
- if (opts->vacuum_truncate)
+ if (opts->vacuum_truncate == TERNARY_TRUE)
params.truncate = VACOPTVALUE_ENABLED;
else
params.truncate = VACOPTVALUE_DISABLED;
diff --git a/src/include/access/reloptions.h b/src/include/access/reloptions.h
index 2f08e1b0cf0..dfbef2babf2 100644
--- a/src/include/access/reloptions.h
+++ b/src/include/access/reloptions.h
@@ -29,6 +29,7 @@
typedef enum relopt_type
{
RELOPT_TYPE_BOOL,
+ RELOPT_TYPE_TERNARY, /* on, off, unset */
RELOPT_TYPE_INT,
RELOPT_TYPE_REAL,
RELOPT_TYPE_ENUM,
@@ -80,6 +81,7 @@ typedef struct relopt_value
union
{
bool bool_val;
+ pg_ternary ternary_val;
int int_val;
double real_val;
int enum_val;
@@ -94,6 +96,12 @@ typedef struct relopt_bool
bool default_val;
} relopt_bool;
+typedef struct relopt_ternary
+{
+ relopt_gen gen;
+ int default_val;
+} relopt_ternary;
+
typedef struct relopt_int
{
relopt_gen gen;
@@ -152,19 +160,6 @@ typedef struct
const char *optname; /* option's name */
relopt_type opttype; /* option's datatype */
int offset; /* offset of field in result struct */
-
- /*
- * isset_offset is an optional offset of a field in the result struct that
- * stores whether the option is explicitly set for the relation or if it
- * just picked up the default value. In most cases, this can be
- * accomplished by giving the reloption a special out-of-range default
- * value (e.g., some integer reloptions use -2), but this isn't always
- * possible. For example, a Boolean reloption cannot be given an
- * out-of-range default, so we need another way to discover the source of
- * its value. This offset is only used if given a value greater than
- * zero.
- */
- int isset_offset;
} relopt_parse_elt;
/* Local reloption definition */
@@ -195,6 +190,9 @@ typedef struct local_relopts
extern relopt_kind add_reloption_kind(void);
extern void add_bool_reloption(bits32 kinds, const char *name, const char *desc,
bool default_val, LOCKMODE lockmode);
+extern void add_ternary_reloption(bits32 kinds, const char *name,
+ const char *desc, pg_ternary default_val,
+ LOCKMODE lockmode);
extern void add_int_reloption(bits32 kinds, const char *name, const char *desc,
int default_val, int min_val, int max_val,
LOCKMODE lockmode);
@@ -214,6 +212,9 @@ extern void register_reloptions_validator(local_relopts *relopts,
extern void add_local_bool_reloption(local_relopts *relopts, const char *name,
const char *desc, bool default_val,
int offset);
+extern void add_local_ternary_reloption(local_relopts *relopts,
+ const char *name, const char *desc,
+ pg_ternary default_val, int offset);
extern void add_local_int_reloption(local_relopts *relopts, const char *name,
const char *desc, int default_val,
int min_val, int max_val, int offset);
diff --git a/src/include/postgres.h b/src/include/postgres.h
index 7d93fbce709..47c88780776 100644
--- a/src/include/postgres.h
+++ b/src/include/postgres.h
@@ -543,6 +543,21 @@ Float8GetDatum(float8 X)
* ----------------------------------------------------------------
*/
+/*
+ * pg_ternary
+ * Boolean value with an extra "unset" value
+ *
+ * This enum can be used for values that want to distinguish between true,
+ * false, and unset.
+*/
+
+typedef enum pg_ternary
+{
+ TERNARY_FALSE = 0,
+ TERNARY_TRUE = 1,
+ TERNARY_UNSET = -1
+} pg_ternary;
+
/*
* NON_EXEC_STATIC: It's sometimes useful to define a variable or function
* that is normally static but extern when using EXEC_BACKEND (see
diff --git a/src/include/utils/rel.h b/src/include/utils/rel.h
index d03ab247788..5feb18a5373 100644
--- a/src/include/utils/rel.h
+++ b/src/include/utils/rel.h
@@ -347,8 +347,7 @@ typedef struct StdRdOptions
bool user_catalog_table; /* use as an additional catalog relation */
int parallel_workers; /* max number of parallel workers */
StdRdOptIndexCleanup vacuum_index_cleanup; /* controls index vacuuming */
- bool vacuum_truncate; /* enables vacuum to truncate a relation */
- bool vacuum_truncate_set; /* whether vacuum_truncate is set */
+ pg_ternary vacuum_truncate; /* enables vacuum to truncate a relation */
/*
* Fraction of pages in a relation that vacuum can eagerly scan and fail
diff --git a/src/test/regress/expected/reloptions.out b/src/test/regress/expected/reloptions.out
index 9de19b4e3f1..1c99f79ab01 100644
--- a/src/test/regress/expected/reloptions.out
+++ b/src/test/regress/expected/reloptions.out
@@ -98,6 +98,42 @@ SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass;
{fillfactor=13,autovacuum_enabled=false}
(1 row)
+-- Tests for future (FIXME) ternary options
+-- behave as boolean option: accept unassigned name and truncated value
+DROP TABLE reloptions_test;
+CREATE TABLE reloptions_test(i INT) WITH (vacuum_truncate);
+SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass;
+ reloptions
+------------------------
+ {vacuum_truncate=true}
+(1 row)
+
+DROP TABLE reloptions_test;
+CREATE TABLE reloptions_test(i INT) WITH (vacuum_truncate=FaLS);
+SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass;
+ reloptions
+------------------------
+ {vacuum_truncate=fals}
+(1 row)
+
+-- preferred "true" alias is used when storing
+DROP TABLE reloptions_test;
+CREATE TABLE reloptions_test(i INT) WITH (vacuum_index_cleanup=on);
+SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass;
+ reloptions
+---------------------------
+ {vacuum_index_cleanup=on}
+(1 row)
+
+-- custom "third" value is available
+DROP TABLE reloptions_test;
+CREATE TABLE reloptions_test(i INT) WITH (vacuum_index_cleanup=auto);
+SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass;
+ reloptions
+-----------------------------
+ {vacuum_index_cleanup=auto}
+(1 row)
+
-- Test vacuum_truncate option
DROP TABLE reloptions_test;
CREATE TEMP TABLE reloptions_test(i INT NOT NULL, j text)
diff --git a/src/test/regress/sql/reloptions.sql b/src/test/regress/sql/reloptions.sql
index 24fbe0b478d..f5980dafcbc 100644
--- a/src/test/regress/sql/reloptions.sql
+++ b/src/test/regress/sql/reloptions.sql
@@ -59,6 +59,27 @@ UPDATE pg_class
ALTER TABLE reloptions_test RESET (illegal_option);
SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass;
+-- Tests for future (FIXME) ternary options
+
+-- behave as boolean option: accept unassigned name and truncated value
+DROP TABLE reloptions_test;
+CREATE TABLE reloptions_test(i INT) WITH (vacuum_truncate);
+SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass;
+
+DROP TABLE reloptions_test;
+CREATE TABLE reloptions_test(i INT) WITH (vacuum_truncate=FaLS);
+SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass;
+
+-- preferred "true" alias is used when storing
+DROP TABLE reloptions_test;
+CREATE TABLE reloptions_test(i INT) WITH (vacuum_index_cleanup=on);
+SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass;
+
+-- custom "third" value is available
+DROP TABLE reloptions_test;
+CREATE TABLE reloptions_test(i INT) WITH (vacuum_index_cleanup=auto);
+SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass;
+
-- Test vacuum_truncate option
DROP TABLE reloptions_test;
diff --git a/src/tools/pgindent/typedefs.list b/src/tools/pgindent/typedefs.list
index 3f3a888fd0e..1c8610fd46c 100644
--- a/src/tools/pgindent/typedefs.list
+++ b/src/tools/pgindent/typedefs.list
@@ -3952,6 +3952,7 @@ pg_sha512_ctx
pg_snapshot
pg_special_case
pg_stack_base_t
+pg_ternary
pg_time_t
pg_time_usec_t
pg_tz
@@ -4079,6 +4080,7 @@ relopt_kind
relopt_parse_elt
relopt_real
relopt_string
+relopt_ternary
relopt_type
relopt_value
relopts_validator
--
2.47.3
--c36t3fndxhkzbl5z--
^ permalink raw reply [nested|flat] 868+ messages in thread
* [PATCH v3] Introduce ternary reloptions
@ 2026-01-16 15:04 Álvaro Herrera <[email protected]>
0 siblings, 0 replies; 868+ messages in thread
From: Álvaro Herrera @ 2026-01-16 15:04 UTC (permalink / raw)
Introduce ternary reloption as a replacement for current
`vacuum_truncate` implementation. Remove the `vacuum_truncate_set`
separate flag and use TERNARY_UNSET instead.
This could also be used for other options such as `vacuum_index_cleanup`
and `buffering`, but lets get the scaffolding in first.
Discussion: https://postgr.es/m/3474141.usfYGdeWWP@thinkpad-pgpro
---
src/backend/access/common/reloptions.c | 137 ++++++++++++++++++-----
src/backend/commands/vacuum.c | 4 +-
src/include/access/reloptions.h | 27 ++---
src/include/postgres.h | 15 +++
src/include/utils/rel.h | 3 +-
src/test/regress/expected/reloptions.out | 36 ++++++
src/test/regress/sql/reloptions.sql | 21 ++++
src/tools/pgindent/typedefs.list | 2 +
8 files changed, 202 insertions(+), 43 deletions(-)
diff --git a/src/backend/access/common/reloptions.c b/src/backend/access/common/reloptions.c
index 0b83f98ed5f..6f1f577581a 100644
--- a/src/backend/access/common/reloptions.c
+++ b/src/backend/access/common/reloptions.c
@@ -40,9 +40,9 @@
*
* To add an option:
*
- * (i) decide on a type (bool, integer, real, enum, string), name, default
- * value, upper and lower bounds (if applicable); for strings, consider a
- * validation routine.
+ * (i) decide on a type (bool, ternary, integer, real, enum, string), name,
+ * default value, upper and lower bounds (if applicable); for strings,
+ * consider a validation routine.
* (ii) add a record below (or use add_<type>_reloption).
* (iii) add it to the appropriate options struct (perhaps StdRdOptions)
* (iv) add it to the appropriate handling routine (perhaps
@@ -147,15 +147,6 @@ static relopt_bool boolRelOpts[] =
},
false
},
- {
- {
- "vacuum_truncate",
- "Enables vacuum to truncate empty pages at the end of this table",
- RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
- ShareUpdateExclusiveLock
- },
- true
- },
{
{
"deduplicate_items",
@@ -170,6 +161,25 @@ static relopt_bool boolRelOpts[] =
{{NULL}}
};
+static relopt_ternary ternaryRelOpts[] =
+{
+ {
+ {
+ "vacuum_truncate",
+ "Enables vacuum to truncate empty pages at the end of this table",
+ RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
+ ShareUpdateExclusiveLock
+ },
+ TERNARY_UNSET
+ },
+ /* list terminator */
+ {
+ {
+ NULL
+ }
+ }
+};
+
static relopt_int intRelOpts[] =
{
{
@@ -609,6 +619,13 @@ initialize_reloptions(void)
boolRelOpts[i].gen.lockmode));
j++;
}
+ for (i = 0; ternaryRelOpts[i].gen.name; i++)
+ {
+ Assert(DoLockModesConflict(ternaryRelOpts[i].gen.lockmode,
+ ternaryRelOpts[i].gen.lockmode));
+ j++;
+ }
+
for (i = 0; intRelOpts[i].gen.name; i++)
{
Assert(DoLockModesConflict(intRelOpts[i].gen.lockmode,
@@ -649,6 +666,14 @@ initialize_reloptions(void)
j++;
}
+ for (i = 0; ternaryRelOpts[i].gen.name; i++)
+ {
+ relOpts[j] = &ternaryRelOpts[i].gen;
+ relOpts[j]->type = RELOPT_TYPE_TERNARY;
+ relOpts[j]->namelen = strlen(relOpts[j]->name);
+ j++;
+ }
+
for (i = 0; intRelOpts[i].gen.name; i++)
{
relOpts[j] = &intRelOpts[i].gen;
@@ -809,6 +834,9 @@ allocate_reloption(bits32 kinds, int type, const char *name, const char *desc,
case RELOPT_TYPE_BOOL:
size = sizeof(relopt_bool);
break;
+ case RELOPT_TYPE_TERNARY:
+ size = sizeof(relopt_ternary);
+ break;
case RELOPT_TYPE_INT:
size = sizeof(relopt_int);
break;
@@ -892,6 +920,57 @@ add_local_bool_reloption(local_relopts *relopts, const char *name,
add_local_reloption(relopts, (relopt_gen *) newoption, offset);
}
+/*
+ * init_ternary_reloption
+ * Allocate and initialize a new ternary reloption
+ */
+static relopt_ternary *
+init_ternary_reloption(bits32 kinds, const char *name, const char *desc,
+ pg_ternary default_val, LOCKMODE lockmode)
+{
+ relopt_ternary *newoption;
+
+ newoption = (relopt_ternary *)
+ allocate_reloption(kinds, RELOPT_TYPE_TERNARY, name, desc, lockmode);
+ newoption->default_val = default_val;
+
+ return newoption;
+}
+
+/*
+ * add_ternary_reloption
+ * Add a new ternary reloption
+ */
+void
+add_ternary_reloption(bits32 kinds, const char *name, const char *desc,
+ pg_ternary default_val, LOCKMODE lockmode)
+{
+ relopt_ternary *newoption;
+
+ newoption =
+ init_ternary_reloption(kinds, name, desc, default_val, lockmode);
+
+ add_reloption((relopt_gen *) newoption);
+}
+
+/*
+ * add_local_ternary_reloption
+ * Add a new ternary local reloption
+ *
+ * 'offset' is offset of ternary-typed field.
+ */
+void
+add_local_ternary_reloption(local_relopts *relopts, const char *name,
+ const char *desc, pg_ternary default_val,
+ int offset)
+{
+ relopt_ternary *newoption;
+
+ newoption =
+ init_ternary_reloption(RELOPT_KIND_LOCAL, name, desc, default_val, 0);
+
+ add_local_reloption(relopts, (relopt_gen *) newoption, offset);
+}
/*
* init_real_reloption
@@ -1626,6 +1705,19 @@ parse_one_reloption(relopt_value *option, char *text_str, int text_len,
option->gen->name, value)));
}
break;
+ case RELOPT_TYPE_TERNARY:
+ {
+ bool b;
+
+ parsed = parse_bool(value, &b);
+ option->values.ternary_val = b ? TERNARY_TRUE : TERNARY_FALSE;
+ if (validate && !parsed)
+ ereport(ERROR,
+ (errcode(ERRCODE_INVALID_PARAMETER_VALUE),
+ errmsg("invalid value for ternary option \"%s\": %s",
+ option->gen->name, value)));
+ }
+ break;
case RELOPT_TYPE_INT:
{
relopt_int *optint = (relopt_int *) option->gen;
@@ -1789,17 +1881,6 @@ fillRelOptions(void *rdopts, Size basesize,
char *itempos = ((char *) rdopts) + elems[j].offset;
char *string_val;
- /*
- * If isset_offset is provided, store whether the reloption is
- * set there.
- */
- if (elems[j].isset_offset > 0)
- {
- char *setpos = ((char *) rdopts) + elems[j].isset_offset;
-
- *(bool *) setpos = options[i].isset;
- }
-
switch (options[i].gen->type)
{
case RELOPT_TYPE_BOOL:
@@ -1807,6 +1888,11 @@ fillRelOptions(void *rdopts, Size basesize,
options[i].values.bool_val :
((relopt_bool *) options[i].gen)->default_val;
break;
+ case RELOPT_TYPE_TERNARY:
+ *(pg_ternary *) itempos = options[i].isset ?
+ options[i].values.ternary_val :
+ ((relopt_ternary *) options[i].gen)->default_val;
+ break;
case RELOPT_TYPE_INT:
*(int *) itempos = options[i].isset ?
options[i].values.int_val :
@@ -1923,8 +2009,8 @@ default_reloptions(Datum reloptions, bool validate, relopt_kind kind)
offsetof(StdRdOptions, parallel_workers)},
{"vacuum_index_cleanup", RELOPT_TYPE_ENUM,
offsetof(StdRdOptions, vacuum_index_cleanup)},
- {"vacuum_truncate", RELOPT_TYPE_BOOL,
- offsetof(StdRdOptions, vacuum_truncate), offsetof(StdRdOptions, vacuum_truncate_set)},
+ {"vacuum_truncate", RELOPT_TYPE_TERNARY,
+ offsetof(StdRdOptions, vacuum_truncate)},
{"vacuum_max_eager_freeze_failure_rate", RELOPT_TYPE_REAL,
offsetof(StdRdOptions, vacuum_max_eager_freeze_failure_rate)}
};
@@ -2004,7 +2090,6 @@ build_local_reloptions(local_relopts *relopts, Datum options, bool validate)
elems[i].optname = opt->option->name;
elems[i].opttype = opt->option->type;
elems[i].offset = opt->offset;
- elems[i].isset_offset = 0; /* not supported for local relopts yet */
i++;
}
diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c
index aa4fbec143f..696eab9bd97 100644
--- a/src/backend/commands/vacuum.c
+++ b/src/backend/commands/vacuum.c
@@ -2224,9 +2224,9 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams params,
{
StdRdOptions *opts = (StdRdOptions *) rel->rd_options;
- if (opts && opts->vacuum_truncate_set)
+ if (opts && opts->vacuum_truncate != TERNARY_UNSET)
{
- if (opts->vacuum_truncate)
+ if (opts->vacuum_truncate == TERNARY_TRUE)
params.truncate = VACOPTVALUE_ENABLED;
else
params.truncate = VACOPTVALUE_DISABLED;
diff --git a/src/include/access/reloptions.h b/src/include/access/reloptions.h
index 2f08e1b0cf0..dfbef2babf2 100644
--- a/src/include/access/reloptions.h
+++ b/src/include/access/reloptions.h
@@ -29,6 +29,7 @@
typedef enum relopt_type
{
RELOPT_TYPE_BOOL,
+ RELOPT_TYPE_TERNARY, /* on, off, unset */
RELOPT_TYPE_INT,
RELOPT_TYPE_REAL,
RELOPT_TYPE_ENUM,
@@ -80,6 +81,7 @@ typedef struct relopt_value
union
{
bool bool_val;
+ pg_ternary ternary_val;
int int_val;
double real_val;
int enum_val;
@@ -94,6 +96,12 @@ typedef struct relopt_bool
bool default_val;
} relopt_bool;
+typedef struct relopt_ternary
+{
+ relopt_gen gen;
+ int default_val;
+} relopt_ternary;
+
typedef struct relopt_int
{
relopt_gen gen;
@@ -152,19 +160,6 @@ typedef struct
const char *optname; /* option's name */
relopt_type opttype; /* option's datatype */
int offset; /* offset of field in result struct */
-
- /*
- * isset_offset is an optional offset of a field in the result struct that
- * stores whether the option is explicitly set for the relation or if it
- * just picked up the default value. In most cases, this can be
- * accomplished by giving the reloption a special out-of-range default
- * value (e.g., some integer reloptions use -2), but this isn't always
- * possible. For example, a Boolean reloption cannot be given an
- * out-of-range default, so we need another way to discover the source of
- * its value. This offset is only used if given a value greater than
- * zero.
- */
- int isset_offset;
} relopt_parse_elt;
/* Local reloption definition */
@@ -195,6 +190,9 @@ typedef struct local_relopts
extern relopt_kind add_reloption_kind(void);
extern void add_bool_reloption(bits32 kinds, const char *name, const char *desc,
bool default_val, LOCKMODE lockmode);
+extern void add_ternary_reloption(bits32 kinds, const char *name,
+ const char *desc, pg_ternary default_val,
+ LOCKMODE lockmode);
extern void add_int_reloption(bits32 kinds, const char *name, const char *desc,
int default_val, int min_val, int max_val,
LOCKMODE lockmode);
@@ -214,6 +212,9 @@ extern void register_reloptions_validator(local_relopts *relopts,
extern void add_local_bool_reloption(local_relopts *relopts, const char *name,
const char *desc, bool default_val,
int offset);
+extern void add_local_ternary_reloption(local_relopts *relopts,
+ const char *name, const char *desc,
+ pg_ternary default_val, int offset);
extern void add_local_int_reloption(local_relopts *relopts, const char *name,
const char *desc, int default_val,
int min_val, int max_val, int offset);
diff --git a/src/include/postgres.h b/src/include/postgres.h
index 7d93fbce709..47c88780776 100644
--- a/src/include/postgres.h
+++ b/src/include/postgres.h
@@ -543,6 +543,21 @@ Float8GetDatum(float8 X)
* ----------------------------------------------------------------
*/
+/*
+ * pg_ternary
+ * Boolean value with an extra "unset" value
+ *
+ * This enum can be used for values that want to distinguish between true,
+ * false, and unset.
+*/
+
+typedef enum pg_ternary
+{
+ TERNARY_FALSE = 0,
+ TERNARY_TRUE = 1,
+ TERNARY_UNSET = -1
+} pg_ternary;
+
/*
* NON_EXEC_STATIC: It's sometimes useful to define a variable or function
* that is normally static but extern when using EXEC_BACKEND (see
diff --git a/src/include/utils/rel.h b/src/include/utils/rel.h
index d03ab247788..5feb18a5373 100644
--- a/src/include/utils/rel.h
+++ b/src/include/utils/rel.h
@@ -347,8 +347,7 @@ typedef struct StdRdOptions
bool user_catalog_table; /* use as an additional catalog relation */
int parallel_workers; /* max number of parallel workers */
StdRdOptIndexCleanup vacuum_index_cleanup; /* controls index vacuuming */
- bool vacuum_truncate; /* enables vacuum to truncate a relation */
- bool vacuum_truncate_set; /* whether vacuum_truncate is set */
+ pg_ternary vacuum_truncate; /* enables vacuum to truncate a relation */
/*
* Fraction of pages in a relation that vacuum can eagerly scan and fail
diff --git a/src/test/regress/expected/reloptions.out b/src/test/regress/expected/reloptions.out
index 9de19b4e3f1..1c99f79ab01 100644
--- a/src/test/regress/expected/reloptions.out
+++ b/src/test/regress/expected/reloptions.out
@@ -98,6 +98,42 @@ SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass;
{fillfactor=13,autovacuum_enabled=false}
(1 row)
+-- Tests for future (FIXME) ternary options
+-- behave as boolean option: accept unassigned name and truncated value
+DROP TABLE reloptions_test;
+CREATE TABLE reloptions_test(i INT) WITH (vacuum_truncate);
+SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass;
+ reloptions
+------------------------
+ {vacuum_truncate=true}
+(1 row)
+
+DROP TABLE reloptions_test;
+CREATE TABLE reloptions_test(i INT) WITH (vacuum_truncate=FaLS);
+SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass;
+ reloptions
+------------------------
+ {vacuum_truncate=fals}
+(1 row)
+
+-- preferred "true" alias is used when storing
+DROP TABLE reloptions_test;
+CREATE TABLE reloptions_test(i INT) WITH (vacuum_index_cleanup=on);
+SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass;
+ reloptions
+---------------------------
+ {vacuum_index_cleanup=on}
+(1 row)
+
+-- custom "third" value is available
+DROP TABLE reloptions_test;
+CREATE TABLE reloptions_test(i INT) WITH (vacuum_index_cleanup=auto);
+SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass;
+ reloptions
+-----------------------------
+ {vacuum_index_cleanup=auto}
+(1 row)
+
-- Test vacuum_truncate option
DROP TABLE reloptions_test;
CREATE TEMP TABLE reloptions_test(i INT NOT NULL, j text)
diff --git a/src/test/regress/sql/reloptions.sql b/src/test/regress/sql/reloptions.sql
index 24fbe0b478d..f5980dafcbc 100644
--- a/src/test/regress/sql/reloptions.sql
+++ b/src/test/regress/sql/reloptions.sql
@@ -59,6 +59,27 @@ UPDATE pg_class
ALTER TABLE reloptions_test RESET (illegal_option);
SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass;
+-- Tests for future (FIXME) ternary options
+
+-- behave as boolean option: accept unassigned name and truncated value
+DROP TABLE reloptions_test;
+CREATE TABLE reloptions_test(i INT) WITH (vacuum_truncate);
+SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass;
+
+DROP TABLE reloptions_test;
+CREATE TABLE reloptions_test(i INT) WITH (vacuum_truncate=FaLS);
+SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass;
+
+-- preferred "true" alias is used when storing
+DROP TABLE reloptions_test;
+CREATE TABLE reloptions_test(i INT) WITH (vacuum_index_cleanup=on);
+SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass;
+
+-- custom "third" value is available
+DROP TABLE reloptions_test;
+CREATE TABLE reloptions_test(i INT) WITH (vacuum_index_cleanup=auto);
+SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass;
+
-- Test vacuum_truncate option
DROP TABLE reloptions_test;
diff --git a/src/tools/pgindent/typedefs.list b/src/tools/pgindent/typedefs.list
index 3f3a888fd0e..1c8610fd46c 100644
--- a/src/tools/pgindent/typedefs.list
+++ b/src/tools/pgindent/typedefs.list
@@ -3952,6 +3952,7 @@ pg_sha512_ctx
pg_snapshot
pg_special_case
pg_stack_base_t
+pg_ternary
pg_time_t
pg_time_usec_t
pg_tz
@@ -4079,6 +4080,7 @@ relopt_kind
relopt_parse_elt
relopt_real
relopt_string
+relopt_ternary
relopt_type
relopt_value
relopts_validator
--
2.47.3
--c36t3fndxhkzbl5z--
^ permalink raw reply [nested|flat] 868+ messages in thread
* [PATCH v3] Introduce ternary reloptions
@ 2026-01-16 15:04 Álvaro Herrera <[email protected]>
0 siblings, 0 replies; 868+ messages in thread
From: Álvaro Herrera @ 2026-01-16 15:04 UTC (permalink / raw)
Introduce ternary reloption as a replacement for current
`vacuum_truncate` implementation. Remove the `vacuum_truncate_set`
separate flag and use TERNARY_UNSET instead.
This could also be used for other options such as `vacuum_index_cleanup`
and `buffering`, but lets get the scaffolding in first.
Discussion: https://postgr.es/m/3474141.usfYGdeWWP@thinkpad-pgpro
---
src/backend/access/common/reloptions.c | 137 ++++++++++++++++++-----
src/backend/commands/vacuum.c | 4 +-
src/include/access/reloptions.h | 27 ++---
src/include/postgres.h | 15 +++
src/include/utils/rel.h | 3 +-
src/test/regress/expected/reloptions.out | 36 ++++++
src/test/regress/sql/reloptions.sql | 21 ++++
src/tools/pgindent/typedefs.list | 2 +
8 files changed, 202 insertions(+), 43 deletions(-)
diff --git a/src/backend/access/common/reloptions.c b/src/backend/access/common/reloptions.c
index 0b83f98ed5f..6f1f577581a 100644
--- a/src/backend/access/common/reloptions.c
+++ b/src/backend/access/common/reloptions.c
@@ -40,9 +40,9 @@
*
* To add an option:
*
- * (i) decide on a type (bool, integer, real, enum, string), name, default
- * value, upper and lower bounds (if applicable); for strings, consider a
- * validation routine.
+ * (i) decide on a type (bool, ternary, integer, real, enum, string), name,
+ * default value, upper and lower bounds (if applicable); for strings,
+ * consider a validation routine.
* (ii) add a record below (or use add_<type>_reloption).
* (iii) add it to the appropriate options struct (perhaps StdRdOptions)
* (iv) add it to the appropriate handling routine (perhaps
@@ -147,15 +147,6 @@ static relopt_bool boolRelOpts[] =
},
false
},
- {
- {
- "vacuum_truncate",
- "Enables vacuum to truncate empty pages at the end of this table",
- RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
- ShareUpdateExclusiveLock
- },
- true
- },
{
{
"deduplicate_items",
@@ -170,6 +161,25 @@ static relopt_bool boolRelOpts[] =
{{NULL}}
};
+static relopt_ternary ternaryRelOpts[] =
+{
+ {
+ {
+ "vacuum_truncate",
+ "Enables vacuum to truncate empty pages at the end of this table",
+ RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
+ ShareUpdateExclusiveLock
+ },
+ TERNARY_UNSET
+ },
+ /* list terminator */
+ {
+ {
+ NULL
+ }
+ }
+};
+
static relopt_int intRelOpts[] =
{
{
@@ -609,6 +619,13 @@ initialize_reloptions(void)
boolRelOpts[i].gen.lockmode));
j++;
}
+ for (i = 0; ternaryRelOpts[i].gen.name; i++)
+ {
+ Assert(DoLockModesConflict(ternaryRelOpts[i].gen.lockmode,
+ ternaryRelOpts[i].gen.lockmode));
+ j++;
+ }
+
for (i = 0; intRelOpts[i].gen.name; i++)
{
Assert(DoLockModesConflict(intRelOpts[i].gen.lockmode,
@@ -649,6 +666,14 @@ initialize_reloptions(void)
j++;
}
+ for (i = 0; ternaryRelOpts[i].gen.name; i++)
+ {
+ relOpts[j] = &ternaryRelOpts[i].gen;
+ relOpts[j]->type = RELOPT_TYPE_TERNARY;
+ relOpts[j]->namelen = strlen(relOpts[j]->name);
+ j++;
+ }
+
for (i = 0; intRelOpts[i].gen.name; i++)
{
relOpts[j] = &intRelOpts[i].gen;
@@ -809,6 +834,9 @@ allocate_reloption(bits32 kinds, int type, const char *name, const char *desc,
case RELOPT_TYPE_BOOL:
size = sizeof(relopt_bool);
break;
+ case RELOPT_TYPE_TERNARY:
+ size = sizeof(relopt_ternary);
+ break;
case RELOPT_TYPE_INT:
size = sizeof(relopt_int);
break;
@@ -892,6 +920,57 @@ add_local_bool_reloption(local_relopts *relopts, const char *name,
add_local_reloption(relopts, (relopt_gen *) newoption, offset);
}
+/*
+ * init_ternary_reloption
+ * Allocate and initialize a new ternary reloption
+ */
+static relopt_ternary *
+init_ternary_reloption(bits32 kinds, const char *name, const char *desc,
+ pg_ternary default_val, LOCKMODE lockmode)
+{
+ relopt_ternary *newoption;
+
+ newoption = (relopt_ternary *)
+ allocate_reloption(kinds, RELOPT_TYPE_TERNARY, name, desc, lockmode);
+ newoption->default_val = default_val;
+
+ return newoption;
+}
+
+/*
+ * add_ternary_reloption
+ * Add a new ternary reloption
+ */
+void
+add_ternary_reloption(bits32 kinds, const char *name, const char *desc,
+ pg_ternary default_val, LOCKMODE lockmode)
+{
+ relopt_ternary *newoption;
+
+ newoption =
+ init_ternary_reloption(kinds, name, desc, default_val, lockmode);
+
+ add_reloption((relopt_gen *) newoption);
+}
+
+/*
+ * add_local_ternary_reloption
+ * Add a new ternary local reloption
+ *
+ * 'offset' is offset of ternary-typed field.
+ */
+void
+add_local_ternary_reloption(local_relopts *relopts, const char *name,
+ const char *desc, pg_ternary default_val,
+ int offset)
+{
+ relopt_ternary *newoption;
+
+ newoption =
+ init_ternary_reloption(RELOPT_KIND_LOCAL, name, desc, default_val, 0);
+
+ add_local_reloption(relopts, (relopt_gen *) newoption, offset);
+}
/*
* init_real_reloption
@@ -1626,6 +1705,19 @@ parse_one_reloption(relopt_value *option, char *text_str, int text_len,
option->gen->name, value)));
}
break;
+ case RELOPT_TYPE_TERNARY:
+ {
+ bool b;
+
+ parsed = parse_bool(value, &b);
+ option->values.ternary_val = b ? TERNARY_TRUE : TERNARY_FALSE;
+ if (validate && !parsed)
+ ereport(ERROR,
+ (errcode(ERRCODE_INVALID_PARAMETER_VALUE),
+ errmsg("invalid value for ternary option \"%s\": %s",
+ option->gen->name, value)));
+ }
+ break;
case RELOPT_TYPE_INT:
{
relopt_int *optint = (relopt_int *) option->gen;
@@ -1789,17 +1881,6 @@ fillRelOptions(void *rdopts, Size basesize,
char *itempos = ((char *) rdopts) + elems[j].offset;
char *string_val;
- /*
- * If isset_offset is provided, store whether the reloption is
- * set there.
- */
- if (elems[j].isset_offset > 0)
- {
- char *setpos = ((char *) rdopts) + elems[j].isset_offset;
-
- *(bool *) setpos = options[i].isset;
- }
-
switch (options[i].gen->type)
{
case RELOPT_TYPE_BOOL:
@@ -1807,6 +1888,11 @@ fillRelOptions(void *rdopts, Size basesize,
options[i].values.bool_val :
((relopt_bool *) options[i].gen)->default_val;
break;
+ case RELOPT_TYPE_TERNARY:
+ *(pg_ternary *) itempos = options[i].isset ?
+ options[i].values.ternary_val :
+ ((relopt_ternary *) options[i].gen)->default_val;
+ break;
case RELOPT_TYPE_INT:
*(int *) itempos = options[i].isset ?
options[i].values.int_val :
@@ -1923,8 +2009,8 @@ default_reloptions(Datum reloptions, bool validate, relopt_kind kind)
offsetof(StdRdOptions, parallel_workers)},
{"vacuum_index_cleanup", RELOPT_TYPE_ENUM,
offsetof(StdRdOptions, vacuum_index_cleanup)},
- {"vacuum_truncate", RELOPT_TYPE_BOOL,
- offsetof(StdRdOptions, vacuum_truncate), offsetof(StdRdOptions, vacuum_truncate_set)},
+ {"vacuum_truncate", RELOPT_TYPE_TERNARY,
+ offsetof(StdRdOptions, vacuum_truncate)},
{"vacuum_max_eager_freeze_failure_rate", RELOPT_TYPE_REAL,
offsetof(StdRdOptions, vacuum_max_eager_freeze_failure_rate)}
};
@@ -2004,7 +2090,6 @@ build_local_reloptions(local_relopts *relopts, Datum options, bool validate)
elems[i].optname = opt->option->name;
elems[i].opttype = opt->option->type;
elems[i].offset = opt->offset;
- elems[i].isset_offset = 0; /* not supported for local relopts yet */
i++;
}
diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c
index aa4fbec143f..696eab9bd97 100644
--- a/src/backend/commands/vacuum.c
+++ b/src/backend/commands/vacuum.c
@@ -2224,9 +2224,9 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams params,
{
StdRdOptions *opts = (StdRdOptions *) rel->rd_options;
- if (opts && opts->vacuum_truncate_set)
+ if (opts && opts->vacuum_truncate != TERNARY_UNSET)
{
- if (opts->vacuum_truncate)
+ if (opts->vacuum_truncate == TERNARY_TRUE)
params.truncate = VACOPTVALUE_ENABLED;
else
params.truncate = VACOPTVALUE_DISABLED;
diff --git a/src/include/access/reloptions.h b/src/include/access/reloptions.h
index 2f08e1b0cf0..dfbef2babf2 100644
--- a/src/include/access/reloptions.h
+++ b/src/include/access/reloptions.h
@@ -29,6 +29,7 @@
typedef enum relopt_type
{
RELOPT_TYPE_BOOL,
+ RELOPT_TYPE_TERNARY, /* on, off, unset */
RELOPT_TYPE_INT,
RELOPT_TYPE_REAL,
RELOPT_TYPE_ENUM,
@@ -80,6 +81,7 @@ typedef struct relopt_value
union
{
bool bool_val;
+ pg_ternary ternary_val;
int int_val;
double real_val;
int enum_val;
@@ -94,6 +96,12 @@ typedef struct relopt_bool
bool default_val;
} relopt_bool;
+typedef struct relopt_ternary
+{
+ relopt_gen gen;
+ int default_val;
+} relopt_ternary;
+
typedef struct relopt_int
{
relopt_gen gen;
@@ -152,19 +160,6 @@ typedef struct
const char *optname; /* option's name */
relopt_type opttype; /* option's datatype */
int offset; /* offset of field in result struct */
-
- /*
- * isset_offset is an optional offset of a field in the result struct that
- * stores whether the option is explicitly set for the relation or if it
- * just picked up the default value. In most cases, this can be
- * accomplished by giving the reloption a special out-of-range default
- * value (e.g., some integer reloptions use -2), but this isn't always
- * possible. For example, a Boolean reloption cannot be given an
- * out-of-range default, so we need another way to discover the source of
- * its value. This offset is only used if given a value greater than
- * zero.
- */
- int isset_offset;
} relopt_parse_elt;
/* Local reloption definition */
@@ -195,6 +190,9 @@ typedef struct local_relopts
extern relopt_kind add_reloption_kind(void);
extern void add_bool_reloption(bits32 kinds, const char *name, const char *desc,
bool default_val, LOCKMODE lockmode);
+extern void add_ternary_reloption(bits32 kinds, const char *name,
+ const char *desc, pg_ternary default_val,
+ LOCKMODE lockmode);
extern void add_int_reloption(bits32 kinds, const char *name, const char *desc,
int default_val, int min_val, int max_val,
LOCKMODE lockmode);
@@ -214,6 +212,9 @@ extern void register_reloptions_validator(local_relopts *relopts,
extern void add_local_bool_reloption(local_relopts *relopts, const char *name,
const char *desc, bool default_val,
int offset);
+extern void add_local_ternary_reloption(local_relopts *relopts,
+ const char *name, const char *desc,
+ pg_ternary default_val, int offset);
extern void add_local_int_reloption(local_relopts *relopts, const char *name,
const char *desc, int default_val,
int min_val, int max_val, int offset);
diff --git a/src/include/postgres.h b/src/include/postgres.h
index 7d93fbce709..47c88780776 100644
--- a/src/include/postgres.h
+++ b/src/include/postgres.h
@@ -543,6 +543,21 @@ Float8GetDatum(float8 X)
* ----------------------------------------------------------------
*/
+/*
+ * pg_ternary
+ * Boolean value with an extra "unset" value
+ *
+ * This enum can be used for values that want to distinguish between true,
+ * false, and unset.
+*/
+
+typedef enum pg_ternary
+{
+ TERNARY_FALSE = 0,
+ TERNARY_TRUE = 1,
+ TERNARY_UNSET = -1
+} pg_ternary;
+
/*
* NON_EXEC_STATIC: It's sometimes useful to define a variable or function
* that is normally static but extern when using EXEC_BACKEND (see
diff --git a/src/include/utils/rel.h b/src/include/utils/rel.h
index d03ab247788..5feb18a5373 100644
--- a/src/include/utils/rel.h
+++ b/src/include/utils/rel.h
@@ -347,8 +347,7 @@ typedef struct StdRdOptions
bool user_catalog_table; /* use as an additional catalog relation */
int parallel_workers; /* max number of parallel workers */
StdRdOptIndexCleanup vacuum_index_cleanup; /* controls index vacuuming */
- bool vacuum_truncate; /* enables vacuum to truncate a relation */
- bool vacuum_truncate_set; /* whether vacuum_truncate is set */
+ pg_ternary vacuum_truncate; /* enables vacuum to truncate a relation */
/*
* Fraction of pages in a relation that vacuum can eagerly scan and fail
diff --git a/src/test/regress/expected/reloptions.out b/src/test/regress/expected/reloptions.out
index 9de19b4e3f1..1c99f79ab01 100644
--- a/src/test/regress/expected/reloptions.out
+++ b/src/test/regress/expected/reloptions.out
@@ -98,6 +98,42 @@ SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass;
{fillfactor=13,autovacuum_enabled=false}
(1 row)
+-- Tests for future (FIXME) ternary options
+-- behave as boolean option: accept unassigned name and truncated value
+DROP TABLE reloptions_test;
+CREATE TABLE reloptions_test(i INT) WITH (vacuum_truncate);
+SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass;
+ reloptions
+------------------------
+ {vacuum_truncate=true}
+(1 row)
+
+DROP TABLE reloptions_test;
+CREATE TABLE reloptions_test(i INT) WITH (vacuum_truncate=FaLS);
+SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass;
+ reloptions
+------------------------
+ {vacuum_truncate=fals}
+(1 row)
+
+-- preferred "true" alias is used when storing
+DROP TABLE reloptions_test;
+CREATE TABLE reloptions_test(i INT) WITH (vacuum_index_cleanup=on);
+SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass;
+ reloptions
+---------------------------
+ {vacuum_index_cleanup=on}
+(1 row)
+
+-- custom "third" value is available
+DROP TABLE reloptions_test;
+CREATE TABLE reloptions_test(i INT) WITH (vacuum_index_cleanup=auto);
+SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass;
+ reloptions
+-----------------------------
+ {vacuum_index_cleanup=auto}
+(1 row)
+
-- Test vacuum_truncate option
DROP TABLE reloptions_test;
CREATE TEMP TABLE reloptions_test(i INT NOT NULL, j text)
diff --git a/src/test/regress/sql/reloptions.sql b/src/test/regress/sql/reloptions.sql
index 24fbe0b478d..f5980dafcbc 100644
--- a/src/test/regress/sql/reloptions.sql
+++ b/src/test/regress/sql/reloptions.sql
@@ -59,6 +59,27 @@ UPDATE pg_class
ALTER TABLE reloptions_test RESET (illegal_option);
SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass;
+-- Tests for future (FIXME) ternary options
+
+-- behave as boolean option: accept unassigned name and truncated value
+DROP TABLE reloptions_test;
+CREATE TABLE reloptions_test(i INT) WITH (vacuum_truncate);
+SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass;
+
+DROP TABLE reloptions_test;
+CREATE TABLE reloptions_test(i INT) WITH (vacuum_truncate=FaLS);
+SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass;
+
+-- preferred "true" alias is used when storing
+DROP TABLE reloptions_test;
+CREATE TABLE reloptions_test(i INT) WITH (vacuum_index_cleanup=on);
+SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass;
+
+-- custom "third" value is available
+DROP TABLE reloptions_test;
+CREATE TABLE reloptions_test(i INT) WITH (vacuum_index_cleanup=auto);
+SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass;
+
-- Test vacuum_truncate option
DROP TABLE reloptions_test;
diff --git a/src/tools/pgindent/typedefs.list b/src/tools/pgindent/typedefs.list
index 3f3a888fd0e..1c8610fd46c 100644
--- a/src/tools/pgindent/typedefs.list
+++ b/src/tools/pgindent/typedefs.list
@@ -3952,6 +3952,7 @@ pg_sha512_ctx
pg_snapshot
pg_special_case
pg_stack_base_t
+pg_ternary
pg_time_t
pg_time_usec_t
pg_tz
@@ -4079,6 +4080,7 @@ relopt_kind
relopt_parse_elt
relopt_real
relopt_string
+relopt_ternary
relopt_type
relopt_value
relopts_validator
--
2.47.3
--c36t3fndxhkzbl5z--
^ permalink raw reply [nested|flat] 868+ messages in thread
* [PATCH v3] Introduce ternary reloptions
@ 2026-01-16 15:04 Álvaro Herrera <[email protected]>
0 siblings, 0 replies; 868+ messages in thread
From: Álvaro Herrera @ 2026-01-16 15:04 UTC (permalink / raw)
Introduce ternary reloption as a replacement for current
`vacuum_truncate` implementation. Remove the `vacuum_truncate_set`
separate flag and use TERNARY_UNSET instead.
This could also be used for other options such as `vacuum_index_cleanup`
and `buffering`, but lets get the scaffolding in first.
Discussion: https://postgr.es/m/3474141.usfYGdeWWP@thinkpad-pgpro
---
src/backend/access/common/reloptions.c | 137 ++++++++++++++++++-----
src/backend/commands/vacuum.c | 4 +-
src/include/access/reloptions.h | 27 ++---
src/include/postgres.h | 15 +++
src/include/utils/rel.h | 3 +-
src/test/regress/expected/reloptions.out | 36 ++++++
src/test/regress/sql/reloptions.sql | 21 ++++
src/tools/pgindent/typedefs.list | 2 +
8 files changed, 202 insertions(+), 43 deletions(-)
diff --git a/src/backend/access/common/reloptions.c b/src/backend/access/common/reloptions.c
index 0b83f98ed5f..6f1f577581a 100644
--- a/src/backend/access/common/reloptions.c
+++ b/src/backend/access/common/reloptions.c
@@ -40,9 +40,9 @@
*
* To add an option:
*
- * (i) decide on a type (bool, integer, real, enum, string), name, default
- * value, upper and lower bounds (if applicable); for strings, consider a
- * validation routine.
+ * (i) decide on a type (bool, ternary, integer, real, enum, string), name,
+ * default value, upper and lower bounds (if applicable); for strings,
+ * consider a validation routine.
* (ii) add a record below (or use add_<type>_reloption).
* (iii) add it to the appropriate options struct (perhaps StdRdOptions)
* (iv) add it to the appropriate handling routine (perhaps
@@ -147,15 +147,6 @@ static relopt_bool boolRelOpts[] =
},
false
},
- {
- {
- "vacuum_truncate",
- "Enables vacuum to truncate empty pages at the end of this table",
- RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
- ShareUpdateExclusiveLock
- },
- true
- },
{
{
"deduplicate_items",
@@ -170,6 +161,25 @@ static relopt_bool boolRelOpts[] =
{{NULL}}
};
+static relopt_ternary ternaryRelOpts[] =
+{
+ {
+ {
+ "vacuum_truncate",
+ "Enables vacuum to truncate empty pages at the end of this table",
+ RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
+ ShareUpdateExclusiveLock
+ },
+ TERNARY_UNSET
+ },
+ /* list terminator */
+ {
+ {
+ NULL
+ }
+ }
+};
+
static relopt_int intRelOpts[] =
{
{
@@ -609,6 +619,13 @@ initialize_reloptions(void)
boolRelOpts[i].gen.lockmode));
j++;
}
+ for (i = 0; ternaryRelOpts[i].gen.name; i++)
+ {
+ Assert(DoLockModesConflict(ternaryRelOpts[i].gen.lockmode,
+ ternaryRelOpts[i].gen.lockmode));
+ j++;
+ }
+
for (i = 0; intRelOpts[i].gen.name; i++)
{
Assert(DoLockModesConflict(intRelOpts[i].gen.lockmode,
@@ -649,6 +666,14 @@ initialize_reloptions(void)
j++;
}
+ for (i = 0; ternaryRelOpts[i].gen.name; i++)
+ {
+ relOpts[j] = &ternaryRelOpts[i].gen;
+ relOpts[j]->type = RELOPT_TYPE_TERNARY;
+ relOpts[j]->namelen = strlen(relOpts[j]->name);
+ j++;
+ }
+
for (i = 0; intRelOpts[i].gen.name; i++)
{
relOpts[j] = &intRelOpts[i].gen;
@@ -809,6 +834,9 @@ allocate_reloption(bits32 kinds, int type, const char *name, const char *desc,
case RELOPT_TYPE_BOOL:
size = sizeof(relopt_bool);
break;
+ case RELOPT_TYPE_TERNARY:
+ size = sizeof(relopt_ternary);
+ break;
case RELOPT_TYPE_INT:
size = sizeof(relopt_int);
break;
@@ -892,6 +920,57 @@ add_local_bool_reloption(local_relopts *relopts, const char *name,
add_local_reloption(relopts, (relopt_gen *) newoption, offset);
}
+/*
+ * init_ternary_reloption
+ * Allocate and initialize a new ternary reloption
+ */
+static relopt_ternary *
+init_ternary_reloption(bits32 kinds, const char *name, const char *desc,
+ pg_ternary default_val, LOCKMODE lockmode)
+{
+ relopt_ternary *newoption;
+
+ newoption = (relopt_ternary *)
+ allocate_reloption(kinds, RELOPT_TYPE_TERNARY, name, desc, lockmode);
+ newoption->default_val = default_val;
+
+ return newoption;
+}
+
+/*
+ * add_ternary_reloption
+ * Add a new ternary reloption
+ */
+void
+add_ternary_reloption(bits32 kinds, const char *name, const char *desc,
+ pg_ternary default_val, LOCKMODE lockmode)
+{
+ relopt_ternary *newoption;
+
+ newoption =
+ init_ternary_reloption(kinds, name, desc, default_val, lockmode);
+
+ add_reloption((relopt_gen *) newoption);
+}
+
+/*
+ * add_local_ternary_reloption
+ * Add a new ternary local reloption
+ *
+ * 'offset' is offset of ternary-typed field.
+ */
+void
+add_local_ternary_reloption(local_relopts *relopts, const char *name,
+ const char *desc, pg_ternary default_val,
+ int offset)
+{
+ relopt_ternary *newoption;
+
+ newoption =
+ init_ternary_reloption(RELOPT_KIND_LOCAL, name, desc, default_val, 0);
+
+ add_local_reloption(relopts, (relopt_gen *) newoption, offset);
+}
/*
* init_real_reloption
@@ -1626,6 +1705,19 @@ parse_one_reloption(relopt_value *option, char *text_str, int text_len,
option->gen->name, value)));
}
break;
+ case RELOPT_TYPE_TERNARY:
+ {
+ bool b;
+
+ parsed = parse_bool(value, &b);
+ option->values.ternary_val = b ? TERNARY_TRUE : TERNARY_FALSE;
+ if (validate && !parsed)
+ ereport(ERROR,
+ (errcode(ERRCODE_INVALID_PARAMETER_VALUE),
+ errmsg("invalid value for ternary option \"%s\": %s",
+ option->gen->name, value)));
+ }
+ break;
case RELOPT_TYPE_INT:
{
relopt_int *optint = (relopt_int *) option->gen;
@@ -1789,17 +1881,6 @@ fillRelOptions(void *rdopts, Size basesize,
char *itempos = ((char *) rdopts) + elems[j].offset;
char *string_val;
- /*
- * If isset_offset is provided, store whether the reloption is
- * set there.
- */
- if (elems[j].isset_offset > 0)
- {
- char *setpos = ((char *) rdopts) + elems[j].isset_offset;
-
- *(bool *) setpos = options[i].isset;
- }
-
switch (options[i].gen->type)
{
case RELOPT_TYPE_BOOL:
@@ -1807,6 +1888,11 @@ fillRelOptions(void *rdopts, Size basesize,
options[i].values.bool_val :
((relopt_bool *) options[i].gen)->default_val;
break;
+ case RELOPT_TYPE_TERNARY:
+ *(pg_ternary *) itempos = options[i].isset ?
+ options[i].values.ternary_val :
+ ((relopt_ternary *) options[i].gen)->default_val;
+ break;
case RELOPT_TYPE_INT:
*(int *) itempos = options[i].isset ?
options[i].values.int_val :
@@ -1923,8 +2009,8 @@ default_reloptions(Datum reloptions, bool validate, relopt_kind kind)
offsetof(StdRdOptions, parallel_workers)},
{"vacuum_index_cleanup", RELOPT_TYPE_ENUM,
offsetof(StdRdOptions, vacuum_index_cleanup)},
- {"vacuum_truncate", RELOPT_TYPE_BOOL,
- offsetof(StdRdOptions, vacuum_truncate), offsetof(StdRdOptions, vacuum_truncate_set)},
+ {"vacuum_truncate", RELOPT_TYPE_TERNARY,
+ offsetof(StdRdOptions, vacuum_truncate)},
{"vacuum_max_eager_freeze_failure_rate", RELOPT_TYPE_REAL,
offsetof(StdRdOptions, vacuum_max_eager_freeze_failure_rate)}
};
@@ -2004,7 +2090,6 @@ build_local_reloptions(local_relopts *relopts, Datum options, bool validate)
elems[i].optname = opt->option->name;
elems[i].opttype = opt->option->type;
elems[i].offset = opt->offset;
- elems[i].isset_offset = 0; /* not supported for local relopts yet */
i++;
}
diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c
index aa4fbec143f..696eab9bd97 100644
--- a/src/backend/commands/vacuum.c
+++ b/src/backend/commands/vacuum.c
@@ -2224,9 +2224,9 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams params,
{
StdRdOptions *opts = (StdRdOptions *) rel->rd_options;
- if (opts && opts->vacuum_truncate_set)
+ if (opts && opts->vacuum_truncate != TERNARY_UNSET)
{
- if (opts->vacuum_truncate)
+ if (opts->vacuum_truncate == TERNARY_TRUE)
params.truncate = VACOPTVALUE_ENABLED;
else
params.truncate = VACOPTVALUE_DISABLED;
diff --git a/src/include/access/reloptions.h b/src/include/access/reloptions.h
index 2f08e1b0cf0..dfbef2babf2 100644
--- a/src/include/access/reloptions.h
+++ b/src/include/access/reloptions.h
@@ -29,6 +29,7 @@
typedef enum relopt_type
{
RELOPT_TYPE_BOOL,
+ RELOPT_TYPE_TERNARY, /* on, off, unset */
RELOPT_TYPE_INT,
RELOPT_TYPE_REAL,
RELOPT_TYPE_ENUM,
@@ -80,6 +81,7 @@ typedef struct relopt_value
union
{
bool bool_val;
+ pg_ternary ternary_val;
int int_val;
double real_val;
int enum_val;
@@ -94,6 +96,12 @@ typedef struct relopt_bool
bool default_val;
} relopt_bool;
+typedef struct relopt_ternary
+{
+ relopt_gen gen;
+ int default_val;
+} relopt_ternary;
+
typedef struct relopt_int
{
relopt_gen gen;
@@ -152,19 +160,6 @@ typedef struct
const char *optname; /* option's name */
relopt_type opttype; /* option's datatype */
int offset; /* offset of field in result struct */
-
- /*
- * isset_offset is an optional offset of a field in the result struct that
- * stores whether the option is explicitly set for the relation or if it
- * just picked up the default value. In most cases, this can be
- * accomplished by giving the reloption a special out-of-range default
- * value (e.g., some integer reloptions use -2), but this isn't always
- * possible. For example, a Boolean reloption cannot be given an
- * out-of-range default, so we need another way to discover the source of
- * its value. This offset is only used if given a value greater than
- * zero.
- */
- int isset_offset;
} relopt_parse_elt;
/* Local reloption definition */
@@ -195,6 +190,9 @@ typedef struct local_relopts
extern relopt_kind add_reloption_kind(void);
extern void add_bool_reloption(bits32 kinds, const char *name, const char *desc,
bool default_val, LOCKMODE lockmode);
+extern void add_ternary_reloption(bits32 kinds, const char *name,
+ const char *desc, pg_ternary default_val,
+ LOCKMODE lockmode);
extern void add_int_reloption(bits32 kinds, const char *name, const char *desc,
int default_val, int min_val, int max_val,
LOCKMODE lockmode);
@@ -214,6 +212,9 @@ extern void register_reloptions_validator(local_relopts *relopts,
extern void add_local_bool_reloption(local_relopts *relopts, const char *name,
const char *desc, bool default_val,
int offset);
+extern void add_local_ternary_reloption(local_relopts *relopts,
+ const char *name, const char *desc,
+ pg_ternary default_val, int offset);
extern void add_local_int_reloption(local_relopts *relopts, const char *name,
const char *desc, int default_val,
int min_val, int max_val, int offset);
diff --git a/src/include/postgres.h b/src/include/postgres.h
index 7d93fbce709..47c88780776 100644
--- a/src/include/postgres.h
+++ b/src/include/postgres.h
@@ -543,6 +543,21 @@ Float8GetDatum(float8 X)
* ----------------------------------------------------------------
*/
+/*
+ * pg_ternary
+ * Boolean value with an extra "unset" value
+ *
+ * This enum can be used for values that want to distinguish between true,
+ * false, and unset.
+*/
+
+typedef enum pg_ternary
+{
+ TERNARY_FALSE = 0,
+ TERNARY_TRUE = 1,
+ TERNARY_UNSET = -1
+} pg_ternary;
+
/*
* NON_EXEC_STATIC: It's sometimes useful to define a variable or function
* that is normally static but extern when using EXEC_BACKEND (see
diff --git a/src/include/utils/rel.h b/src/include/utils/rel.h
index d03ab247788..5feb18a5373 100644
--- a/src/include/utils/rel.h
+++ b/src/include/utils/rel.h
@@ -347,8 +347,7 @@ typedef struct StdRdOptions
bool user_catalog_table; /* use as an additional catalog relation */
int parallel_workers; /* max number of parallel workers */
StdRdOptIndexCleanup vacuum_index_cleanup; /* controls index vacuuming */
- bool vacuum_truncate; /* enables vacuum to truncate a relation */
- bool vacuum_truncate_set; /* whether vacuum_truncate is set */
+ pg_ternary vacuum_truncate; /* enables vacuum to truncate a relation */
/*
* Fraction of pages in a relation that vacuum can eagerly scan and fail
diff --git a/src/test/regress/expected/reloptions.out b/src/test/regress/expected/reloptions.out
index 9de19b4e3f1..1c99f79ab01 100644
--- a/src/test/regress/expected/reloptions.out
+++ b/src/test/regress/expected/reloptions.out
@@ -98,6 +98,42 @@ SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass;
{fillfactor=13,autovacuum_enabled=false}
(1 row)
+-- Tests for future (FIXME) ternary options
+-- behave as boolean option: accept unassigned name and truncated value
+DROP TABLE reloptions_test;
+CREATE TABLE reloptions_test(i INT) WITH (vacuum_truncate);
+SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass;
+ reloptions
+------------------------
+ {vacuum_truncate=true}
+(1 row)
+
+DROP TABLE reloptions_test;
+CREATE TABLE reloptions_test(i INT) WITH (vacuum_truncate=FaLS);
+SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass;
+ reloptions
+------------------------
+ {vacuum_truncate=fals}
+(1 row)
+
+-- preferred "true" alias is used when storing
+DROP TABLE reloptions_test;
+CREATE TABLE reloptions_test(i INT) WITH (vacuum_index_cleanup=on);
+SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass;
+ reloptions
+---------------------------
+ {vacuum_index_cleanup=on}
+(1 row)
+
+-- custom "third" value is available
+DROP TABLE reloptions_test;
+CREATE TABLE reloptions_test(i INT) WITH (vacuum_index_cleanup=auto);
+SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass;
+ reloptions
+-----------------------------
+ {vacuum_index_cleanup=auto}
+(1 row)
+
-- Test vacuum_truncate option
DROP TABLE reloptions_test;
CREATE TEMP TABLE reloptions_test(i INT NOT NULL, j text)
diff --git a/src/test/regress/sql/reloptions.sql b/src/test/regress/sql/reloptions.sql
index 24fbe0b478d..f5980dafcbc 100644
--- a/src/test/regress/sql/reloptions.sql
+++ b/src/test/regress/sql/reloptions.sql
@@ -59,6 +59,27 @@ UPDATE pg_class
ALTER TABLE reloptions_test RESET (illegal_option);
SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass;
+-- Tests for future (FIXME) ternary options
+
+-- behave as boolean option: accept unassigned name and truncated value
+DROP TABLE reloptions_test;
+CREATE TABLE reloptions_test(i INT) WITH (vacuum_truncate);
+SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass;
+
+DROP TABLE reloptions_test;
+CREATE TABLE reloptions_test(i INT) WITH (vacuum_truncate=FaLS);
+SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass;
+
+-- preferred "true" alias is used when storing
+DROP TABLE reloptions_test;
+CREATE TABLE reloptions_test(i INT) WITH (vacuum_index_cleanup=on);
+SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass;
+
+-- custom "third" value is available
+DROP TABLE reloptions_test;
+CREATE TABLE reloptions_test(i INT) WITH (vacuum_index_cleanup=auto);
+SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass;
+
-- Test vacuum_truncate option
DROP TABLE reloptions_test;
diff --git a/src/tools/pgindent/typedefs.list b/src/tools/pgindent/typedefs.list
index 3f3a888fd0e..1c8610fd46c 100644
--- a/src/tools/pgindent/typedefs.list
+++ b/src/tools/pgindent/typedefs.list
@@ -3952,6 +3952,7 @@ pg_sha512_ctx
pg_snapshot
pg_special_case
pg_stack_base_t
+pg_ternary
pg_time_t
pg_time_usec_t
pg_tz
@@ -4079,6 +4080,7 @@ relopt_kind
relopt_parse_elt
relopt_real
relopt_string
+relopt_ternary
relopt_type
relopt_value
relopts_validator
--
2.47.3
--c36t3fndxhkzbl5z--
^ permalink raw reply [nested|flat] 868+ messages in thread
* [PATCH v3] Introduce ternary reloptions
@ 2026-01-16 15:04 Álvaro Herrera <[email protected]>
0 siblings, 0 replies; 868+ messages in thread
From: Álvaro Herrera @ 2026-01-16 15:04 UTC (permalink / raw)
Introduce ternary reloption as a replacement for current
`vacuum_truncate` implementation. Remove the `vacuum_truncate_set`
separate flag and use TERNARY_UNSET instead.
This could also be used for other options such as `vacuum_index_cleanup`
and `buffering`, but lets get the scaffolding in first.
Discussion: https://postgr.es/m/3474141.usfYGdeWWP@thinkpad-pgpro
---
src/backend/access/common/reloptions.c | 137 ++++++++++++++++++-----
src/backend/commands/vacuum.c | 4 +-
src/include/access/reloptions.h | 27 ++---
src/include/postgres.h | 15 +++
src/include/utils/rel.h | 3 +-
src/test/regress/expected/reloptions.out | 36 ++++++
src/test/regress/sql/reloptions.sql | 21 ++++
src/tools/pgindent/typedefs.list | 2 +
8 files changed, 202 insertions(+), 43 deletions(-)
diff --git a/src/backend/access/common/reloptions.c b/src/backend/access/common/reloptions.c
index 0b83f98ed5f..6f1f577581a 100644
--- a/src/backend/access/common/reloptions.c
+++ b/src/backend/access/common/reloptions.c
@@ -40,9 +40,9 @@
*
* To add an option:
*
- * (i) decide on a type (bool, integer, real, enum, string), name, default
- * value, upper and lower bounds (if applicable); for strings, consider a
- * validation routine.
+ * (i) decide on a type (bool, ternary, integer, real, enum, string), name,
+ * default value, upper and lower bounds (if applicable); for strings,
+ * consider a validation routine.
* (ii) add a record below (or use add_<type>_reloption).
* (iii) add it to the appropriate options struct (perhaps StdRdOptions)
* (iv) add it to the appropriate handling routine (perhaps
@@ -147,15 +147,6 @@ static relopt_bool boolRelOpts[] =
},
false
},
- {
- {
- "vacuum_truncate",
- "Enables vacuum to truncate empty pages at the end of this table",
- RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
- ShareUpdateExclusiveLock
- },
- true
- },
{
{
"deduplicate_items",
@@ -170,6 +161,25 @@ static relopt_bool boolRelOpts[] =
{{NULL}}
};
+static relopt_ternary ternaryRelOpts[] =
+{
+ {
+ {
+ "vacuum_truncate",
+ "Enables vacuum to truncate empty pages at the end of this table",
+ RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
+ ShareUpdateExclusiveLock
+ },
+ TERNARY_UNSET
+ },
+ /* list terminator */
+ {
+ {
+ NULL
+ }
+ }
+};
+
static relopt_int intRelOpts[] =
{
{
@@ -609,6 +619,13 @@ initialize_reloptions(void)
boolRelOpts[i].gen.lockmode));
j++;
}
+ for (i = 0; ternaryRelOpts[i].gen.name; i++)
+ {
+ Assert(DoLockModesConflict(ternaryRelOpts[i].gen.lockmode,
+ ternaryRelOpts[i].gen.lockmode));
+ j++;
+ }
+
for (i = 0; intRelOpts[i].gen.name; i++)
{
Assert(DoLockModesConflict(intRelOpts[i].gen.lockmode,
@@ -649,6 +666,14 @@ initialize_reloptions(void)
j++;
}
+ for (i = 0; ternaryRelOpts[i].gen.name; i++)
+ {
+ relOpts[j] = &ternaryRelOpts[i].gen;
+ relOpts[j]->type = RELOPT_TYPE_TERNARY;
+ relOpts[j]->namelen = strlen(relOpts[j]->name);
+ j++;
+ }
+
for (i = 0; intRelOpts[i].gen.name; i++)
{
relOpts[j] = &intRelOpts[i].gen;
@@ -809,6 +834,9 @@ allocate_reloption(bits32 kinds, int type, const char *name, const char *desc,
case RELOPT_TYPE_BOOL:
size = sizeof(relopt_bool);
break;
+ case RELOPT_TYPE_TERNARY:
+ size = sizeof(relopt_ternary);
+ break;
case RELOPT_TYPE_INT:
size = sizeof(relopt_int);
break;
@@ -892,6 +920,57 @@ add_local_bool_reloption(local_relopts *relopts, const char *name,
add_local_reloption(relopts, (relopt_gen *) newoption, offset);
}
+/*
+ * init_ternary_reloption
+ * Allocate and initialize a new ternary reloption
+ */
+static relopt_ternary *
+init_ternary_reloption(bits32 kinds, const char *name, const char *desc,
+ pg_ternary default_val, LOCKMODE lockmode)
+{
+ relopt_ternary *newoption;
+
+ newoption = (relopt_ternary *)
+ allocate_reloption(kinds, RELOPT_TYPE_TERNARY, name, desc, lockmode);
+ newoption->default_val = default_val;
+
+ return newoption;
+}
+
+/*
+ * add_ternary_reloption
+ * Add a new ternary reloption
+ */
+void
+add_ternary_reloption(bits32 kinds, const char *name, const char *desc,
+ pg_ternary default_val, LOCKMODE lockmode)
+{
+ relopt_ternary *newoption;
+
+ newoption =
+ init_ternary_reloption(kinds, name, desc, default_val, lockmode);
+
+ add_reloption((relopt_gen *) newoption);
+}
+
+/*
+ * add_local_ternary_reloption
+ * Add a new ternary local reloption
+ *
+ * 'offset' is offset of ternary-typed field.
+ */
+void
+add_local_ternary_reloption(local_relopts *relopts, const char *name,
+ const char *desc, pg_ternary default_val,
+ int offset)
+{
+ relopt_ternary *newoption;
+
+ newoption =
+ init_ternary_reloption(RELOPT_KIND_LOCAL, name, desc, default_val, 0);
+
+ add_local_reloption(relopts, (relopt_gen *) newoption, offset);
+}
/*
* init_real_reloption
@@ -1626,6 +1705,19 @@ parse_one_reloption(relopt_value *option, char *text_str, int text_len,
option->gen->name, value)));
}
break;
+ case RELOPT_TYPE_TERNARY:
+ {
+ bool b;
+
+ parsed = parse_bool(value, &b);
+ option->values.ternary_val = b ? TERNARY_TRUE : TERNARY_FALSE;
+ if (validate && !parsed)
+ ereport(ERROR,
+ (errcode(ERRCODE_INVALID_PARAMETER_VALUE),
+ errmsg("invalid value for ternary option \"%s\": %s",
+ option->gen->name, value)));
+ }
+ break;
case RELOPT_TYPE_INT:
{
relopt_int *optint = (relopt_int *) option->gen;
@@ -1789,17 +1881,6 @@ fillRelOptions(void *rdopts, Size basesize,
char *itempos = ((char *) rdopts) + elems[j].offset;
char *string_val;
- /*
- * If isset_offset is provided, store whether the reloption is
- * set there.
- */
- if (elems[j].isset_offset > 0)
- {
- char *setpos = ((char *) rdopts) + elems[j].isset_offset;
-
- *(bool *) setpos = options[i].isset;
- }
-
switch (options[i].gen->type)
{
case RELOPT_TYPE_BOOL:
@@ -1807,6 +1888,11 @@ fillRelOptions(void *rdopts, Size basesize,
options[i].values.bool_val :
((relopt_bool *) options[i].gen)->default_val;
break;
+ case RELOPT_TYPE_TERNARY:
+ *(pg_ternary *) itempos = options[i].isset ?
+ options[i].values.ternary_val :
+ ((relopt_ternary *) options[i].gen)->default_val;
+ break;
case RELOPT_TYPE_INT:
*(int *) itempos = options[i].isset ?
options[i].values.int_val :
@@ -1923,8 +2009,8 @@ default_reloptions(Datum reloptions, bool validate, relopt_kind kind)
offsetof(StdRdOptions, parallel_workers)},
{"vacuum_index_cleanup", RELOPT_TYPE_ENUM,
offsetof(StdRdOptions, vacuum_index_cleanup)},
- {"vacuum_truncate", RELOPT_TYPE_BOOL,
- offsetof(StdRdOptions, vacuum_truncate), offsetof(StdRdOptions, vacuum_truncate_set)},
+ {"vacuum_truncate", RELOPT_TYPE_TERNARY,
+ offsetof(StdRdOptions, vacuum_truncate)},
{"vacuum_max_eager_freeze_failure_rate", RELOPT_TYPE_REAL,
offsetof(StdRdOptions, vacuum_max_eager_freeze_failure_rate)}
};
@@ -2004,7 +2090,6 @@ build_local_reloptions(local_relopts *relopts, Datum options, bool validate)
elems[i].optname = opt->option->name;
elems[i].opttype = opt->option->type;
elems[i].offset = opt->offset;
- elems[i].isset_offset = 0; /* not supported for local relopts yet */
i++;
}
diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c
index aa4fbec143f..696eab9bd97 100644
--- a/src/backend/commands/vacuum.c
+++ b/src/backend/commands/vacuum.c
@@ -2224,9 +2224,9 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams params,
{
StdRdOptions *opts = (StdRdOptions *) rel->rd_options;
- if (opts && opts->vacuum_truncate_set)
+ if (opts && opts->vacuum_truncate != TERNARY_UNSET)
{
- if (opts->vacuum_truncate)
+ if (opts->vacuum_truncate == TERNARY_TRUE)
params.truncate = VACOPTVALUE_ENABLED;
else
params.truncate = VACOPTVALUE_DISABLED;
diff --git a/src/include/access/reloptions.h b/src/include/access/reloptions.h
index 2f08e1b0cf0..dfbef2babf2 100644
--- a/src/include/access/reloptions.h
+++ b/src/include/access/reloptions.h
@@ -29,6 +29,7 @@
typedef enum relopt_type
{
RELOPT_TYPE_BOOL,
+ RELOPT_TYPE_TERNARY, /* on, off, unset */
RELOPT_TYPE_INT,
RELOPT_TYPE_REAL,
RELOPT_TYPE_ENUM,
@@ -80,6 +81,7 @@ typedef struct relopt_value
union
{
bool bool_val;
+ pg_ternary ternary_val;
int int_val;
double real_val;
int enum_val;
@@ -94,6 +96,12 @@ typedef struct relopt_bool
bool default_val;
} relopt_bool;
+typedef struct relopt_ternary
+{
+ relopt_gen gen;
+ int default_val;
+} relopt_ternary;
+
typedef struct relopt_int
{
relopt_gen gen;
@@ -152,19 +160,6 @@ typedef struct
const char *optname; /* option's name */
relopt_type opttype; /* option's datatype */
int offset; /* offset of field in result struct */
-
- /*
- * isset_offset is an optional offset of a field in the result struct that
- * stores whether the option is explicitly set for the relation or if it
- * just picked up the default value. In most cases, this can be
- * accomplished by giving the reloption a special out-of-range default
- * value (e.g., some integer reloptions use -2), but this isn't always
- * possible. For example, a Boolean reloption cannot be given an
- * out-of-range default, so we need another way to discover the source of
- * its value. This offset is only used if given a value greater than
- * zero.
- */
- int isset_offset;
} relopt_parse_elt;
/* Local reloption definition */
@@ -195,6 +190,9 @@ typedef struct local_relopts
extern relopt_kind add_reloption_kind(void);
extern void add_bool_reloption(bits32 kinds, const char *name, const char *desc,
bool default_val, LOCKMODE lockmode);
+extern void add_ternary_reloption(bits32 kinds, const char *name,
+ const char *desc, pg_ternary default_val,
+ LOCKMODE lockmode);
extern void add_int_reloption(bits32 kinds, const char *name, const char *desc,
int default_val, int min_val, int max_val,
LOCKMODE lockmode);
@@ -214,6 +212,9 @@ extern void register_reloptions_validator(local_relopts *relopts,
extern void add_local_bool_reloption(local_relopts *relopts, const char *name,
const char *desc, bool default_val,
int offset);
+extern void add_local_ternary_reloption(local_relopts *relopts,
+ const char *name, const char *desc,
+ pg_ternary default_val, int offset);
extern void add_local_int_reloption(local_relopts *relopts, const char *name,
const char *desc, int default_val,
int min_val, int max_val, int offset);
diff --git a/src/include/postgres.h b/src/include/postgres.h
index 7d93fbce709..47c88780776 100644
--- a/src/include/postgres.h
+++ b/src/include/postgres.h
@@ -543,6 +543,21 @@ Float8GetDatum(float8 X)
* ----------------------------------------------------------------
*/
+/*
+ * pg_ternary
+ * Boolean value with an extra "unset" value
+ *
+ * This enum can be used for values that want to distinguish between true,
+ * false, and unset.
+*/
+
+typedef enum pg_ternary
+{
+ TERNARY_FALSE = 0,
+ TERNARY_TRUE = 1,
+ TERNARY_UNSET = -1
+} pg_ternary;
+
/*
* NON_EXEC_STATIC: It's sometimes useful to define a variable or function
* that is normally static but extern when using EXEC_BACKEND (see
diff --git a/src/include/utils/rel.h b/src/include/utils/rel.h
index d03ab247788..5feb18a5373 100644
--- a/src/include/utils/rel.h
+++ b/src/include/utils/rel.h
@@ -347,8 +347,7 @@ typedef struct StdRdOptions
bool user_catalog_table; /* use as an additional catalog relation */
int parallel_workers; /* max number of parallel workers */
StdRdOptIndexCleanup vacuum_index_cleanup; /* controls index vacuuming */
- bool vacuum_truncate; /* enables vacuum to truncate a relation */
- bool vacuum_truncate_set; /* whether vacuum_truncate is set */
+ pg_ternary vacuum_truncate; /* enables vacuum to truncate a relation */
/*
* Fraction of pages in a relation that vacuum can eagerly scan and fail
diff --git a/src/test/regress/expected/reloptions.out b/src/test/regress/expected/reloptions.out
index 9de19b4e3f1..1c99f79ab01 100644
--- a/src/test/regress/expected/reloptions.out
+++ b/src/test/regress/expected/reloptions.out
@@ -98,6 +98,42 @@ SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass;
{fillfactor=13,autovacuum_enabled=false}
(1 row)
+-- Tests for future (FIXME) ternary options
+-- behave as boolean option: accept unassigned name and truncated value
+DROP TABLE reloptions_test;
+CREATE TABLE reloptions_test(i INT) WITH (vacuum_truncate);
+SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass;
+ reloptions
+------------------------
+ {vacuum_truncate=true}
+(1 row)
+
+DROP TABLE reloptions_test;
+CREATE TABLE reloptions_test(i INT) WITH (vacuum_truncate=FaLS);
+SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass;
+ reloptions
+------------------------
+ {vacuum_truncate=fals}
+(1 row)
+
+-- preferred "true" alias is used when storing
+DROP TABLE reloptions_test;
+CREATE TABLE reloptions_test(i INT) WITH (vacuum_index_cleanup=on);
+SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass;
+ reloptions
+---------------------------
+ {vacuum_index_cleanup=on}
+(1 row)
+
+-- custom "third" value is available
+DROP TABLE reloptions_test;
+CREATE TABLE reloptions_test(i INT) WITH (vacuum_index_cleanup=auto);
+SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass;
+ reloptions
+-----------------------------
+ {vacuum_index_cleanup=auto}
+(1 row)
+
-- Test vacuum_truncate option
DROP TABLE reloptions_test;
CREATE TEMP TABLE reloptions_test(i INT NOT NULL, j text)
diff --git a/src/test/regress/sql/reloptions.sql b/src/test/regress/sql/reloptions.sql
index 24fbe0b478d..f5980dafcbc 100644
--- a/src/test/regress/sql/reloptions.sql
+++ b/src/test/regress/sql/reloptions.sql
@@ -59,6 +59,27 @@ UPDATE pg_class
ALTER TABLE reloptions_test RESET (illegal_option);
SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass;
+-- Tests for future (FIXME) ternary options
+
+-- behave as boolean option: accept unassigned name and truncated value
+DROP TABLE reloptions_test;
+CREATE TABLE reloptions_test(i INT) WITH (vacuum_truncate);
+SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass;
+
+DROP TABLE reloptions_test;
+CREATE TABLE reloptions_test(i INT) WITH (vacuum_truncate=FaLS);
+SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass;
+
+-- preferred "true" alias is used when storing
+DROP TABLE reloptions_test;
+CREATE TABLE reloptions_test(i INT) WITH (vacuum_index_cleanup=on);
+SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass;
+
+-- custom "third" value is available
+DROP TABLE reloptions_test;
+CREATE TABLE reloptions_test(i INT) WITH (vacuum_index_cleanup=auto);
+SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass;
+
-- Test vacuum_truncate option
DROP TABLE reloptions_test;
diff --git a/src/tools/pgindent/typedefs.list b/src/tools/pgindent/typedefs.list
index 3f3a888fd0e..1c8610fd46c 100644
--- a/src/tools/pgindent/typedefs.list
+++ b/src/tools/pgindent/typedefs.list
@@ -3952,6 +3952,7 @@ pg_sha512_ctx
pg_snapshot
pg_special_case
pg_stack_base_t
+pg_ternary
pg_time_t
pg_time_usec_t
pg_tz
@@ -4079,6 +4080,7 @@ relopt_kind
relopt_parse_elt
relopt_real
relopt_string
+relopt_ternary
relopt_type
relopt_value
relopts_validator
--
2.47.3
--c36t3fndxhkzbl5z--
^ permalink raw reply [nested|flat] 868+ messages in thread
* [PATCH v3] Introduce ternary reloptions
@ 2026-01-16 15:04 Álvaro Herrera <[email protected]>
0 siblings, 0 replies; 868+ messages in thread
From: Álvaro Herrera @ 2026-01-16 15:04 UTC (permalink / raw)
Introduce ternary reloption as a replacement for current
`vacuum_truncate` implementation. Remove the `vacuum_truncate_set`
separate flag and use TERNARY_UNSET instead.
This could also be used for other options such as `vacuum_index_cleanup`
and `buffering`, but lets get the scaffolding in first.
Discussion: https://postgr.es/m/3474141.usfYGdeWWP@thinkpad-pgpro
---
src/backend/access/common/reloptions.c | 137 ++++++++++++++++++-----
src/backend/commands/vacuum.c | 4 +-
src/include/access/reloptions.h | 27 ++---
src/include/postgres.h | 15 +++
src/include/utils/rel.h | 3 +-
src/test/regress/expected/reloptions.out | 36 ++++++
src/test/regress/sql/reloptions.sql | 21 ++++
src/tools/pgindent/typedefs.list | 2 +
8 files changed, 202 insertions(+), 43 deletions(-)
diff --git a/src/backend/access/common/reloptions.c b/src/backend/access/common/reloptions.c
index 0b83f98ed5f..6f1f577581a 100644
--- a/src/backend/access/common/reloptions.c
+++ b/src/backend/access/common/reloptions.c
@@ -40,9 +40,9 @@
*
* To add an option:
*
- * (i) decide on a type (bool, integer, real, enum, string), name, default
- * value, upper and lower bounds (if applicable); for strings, consider a
- * validation routine.
+ * (i) decide on a type (bool, ternary, integer, real, enum, string), name,
+ * default value, upper and lower bounds (if applicable); for strings,
+ * consider a validation routine.
* (ii) add a record below (or use add_<type>_reloption).
* (iii) add it to the appropriate options struct (perhaps StdRdOptions)
* (iv) add it to the appropriate handling routine (perhaps
@@ -147,15 +147,6 @@ static relopt_bool boolRelOpts[] =
},
false
},
- {
- {
- "vacuum_truncate",
- "Enables vacuum to truncate empty pages at the end of this table",
- RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
- ShareUpdateExclusiveLock
- },
- true
- },
{
{
"deduplicate_items",
@@ -170,6 +161,25 @@ static relopt_bool boolRelOpts[] =
{{NULL}}
};
+static relopt_ternary ternaryRelOpts[] =
+{
+ {
+ {
+ "vacuum_truncate",
+ "Enables vacuum to truncate empty pages at the end of this table",
+ RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
+ ShareUpdateExclusiveLock
+ },
+ TERNARY_UNSET
+ },
+ /* list terminator */
+ {
+ {
+ NULL
+ }
+ }
+};
+
static relopt_int intRelOpts[] =
{
{
@@ -609,6 +619,13 @@ initialize_reloptions(void)
boolRelOpts[i].gen.lockmode));
j++;
}
+ for (i = 0; ternaryRelOpts[i].gen.name; i++)
+ {
+ Assert(DoLockModesConflict(ternaryRelOpts[i].gen.lockmode,
+ ternaryRelOpts[i].gen.lockmode));
+ j++;
+ }
+
for (i = 0; intRelOpts[i].gen.name; i++)
{
Assert(DoLockModesConflict(intRelOpts[i].gen.lockmode,
@@ -649,6 +666,14 @@ initialize_reloptions(void)
j++;
}
+ for (i = 0; ternaryRelOpts[i].gen.name; i++)
+ {
+ relOpts[j] = &ternaryRelOpts[i].gen;
+ relOpts[j]->type = RELOPT_TYPE_TERNARY;
+ relOpts[j]->namelen = strlen(relOpts[j]->name);
+ j++;
+ }
+
for (i = 0; intRelOpts[i].gen.name; i++)
{
relOpts[j] = &intRelOpts[i].gen;
@@ -809,6 +834,9 @@ allocate_reloption(bits32 kinds, int type, const char *name, const char *desc,
case RELOPT_TYPE_BOOL:
size = sizeof(relopt_bool);
break;
+ case RELOPT_TYPE_TERNARY:
+ size = sizeof(relopt_ternary);
+ break;
case RELOPT_TYPE_INT:
size = sizeof(relopt_int);
break;
@@ -892,6 +920,57 @@ add_local_bool_reloption(local_relopts *relopts, const char *name,
add_local_reloption(relopts, (relopt_gen *) newoption, offset);
}
+/*
+ * init_ternary_reloption
+ * Allocate and initialize a new ternary reloption
+ */
+static relopt_ternary *
+init_ternary_reloption(bits32 kinds, const char *name, const char *desc,
+ pg_ternary default_val, LOCKMODE lockmode)
+{
+ relopt_ternary *newoption;
+
+ newoption = (relopt_ternary *)
+ allocate_reloption(kinds, RELOPT_TYPE_TERNARY, name, desc, lockmode);
+ newoption->default_val = default_val;
+
+ return newoption;
+}
+
+/*
+ * add_ternary_reloption
+ * Add a new ternary reloption
+ */
+void
+add_ternary_reloption(bits32 kinds, const char *name, const char *desc,
+ pg_ternary default_val, LOCKMODE lockmode)
+{
+ relopt_ternary *newoption;
+
+ newoption =
+ init_ternary_reloption(kinds, name, desc, default_val, lockmode);
+
+ add_reloption((relopt_gen *) newoption);
+}
+
+/*
+ * add_local_ternary_reloption
+ * Add a new ternary local reloption
+ *
+ * 'offset' is offset of ternary-typed field.
+ */
+void
+add_local_ternary_reloption(local_relopts *relopts, const char *name,
+ const char *desc, pg_ternary default_val,
+ int offset)
+{
+ relopt_ternary *newoption;
+
+ newoption =
+ init_ternary_reloption(RELOPT_KIND_LOCAL, name, desc, default_val, 0);
+
+ add_local_reloption(relopts, (relopt_gen *) newoption, offset);
+}
/*
* init_real_reloption
@@ -1626,6 +1705,19 @@ parse_one_reloption(relopt_value *option, char *text_str, int text_len,
option->gen->name, value)));
}
break;
+ case RELOPT_TYPE_TERNARY:
+ {
+ bool b;
+
+ parsed = parse_bool(value, &b);
+ option->values.ternary_val = b ? TERNARY_TRUE : TERNARY_FALSE;
+ if (validate && !parsed)
+ ereport(ERROR,
+ (errcode(ERRCODE_INVALID_PARAMETER_VALUE),
+ errmsg("invalid value for ternary option \"%s\": %s",
+ option->gen->name, value)));
+ }
+ break;
case RELOPT_TYPE_INT:
{
relopt_int *optint = (relopt_int *) option->gen;
@@ -1789,17 +1881,6 @@ fillRelOptions(void *rdopts, Size basesize,
char *itempos = ((char *) rdopts) + elems[j].offset;
char *string_val;
- /*
- * If isset_offset is provided, store whether the reloption is
- * set there.
- */
- if (elems[j].isset_offset > 0)
- {
- char *setpos = ((char *) rdopts) + elems[j].isset_offset;
-
- *(bool *) setpos = options[i].isset;
- }
-
switch (options[i].gen->type)
{
case RELOPT_TYPE_BOOL:
@@ -1807,6 +1888,11 @@ fillRelOptions(void *rdopts, Size basesize,
options[i].values.bool_val :
((relopt_bool *) options[i].gen)->default_val;
break;
+ case RELOPT_TYPE_TERNARY:
+ *(pg_ternary *) itempos = options[i].isset ?
+ options[i].values.ternary_val :
+ ((relopt_ternary *) options[i].gen)->default_val;
+ break;
case RELOPT_TYPE_INT:
*(int *) itempos = options[i].isset ?
options[i].values.int_val :
@@ -1923,8 +2009,8 @@ default_reloptions(Datum reloptions, bool validate, relopt_kind kind)
offsetof(StdRdOptions, parallel_workers)},
{"vacuum_index_cleanup", RELOPT_TYPE_ENUM,
offsetof(StdRdOptions, vacuum_index_cleanup)},
- {"vacuum_truncate", RELOPT_TYPE_BOOL,
- offsetof(StdRdOptions, vacuum_truncate), offsetof(StdRdOptions, vacuum_truncate_set)},
+ {"vacuum_truncate", RELOPT_TYPE_TERNARY,
+ offsetof(StdRdOptions, vacuum_truncate)},
{"vacuum_max_eager_freeze_failure_rate", RELOPT_TYPE_REAL,
offsetof(StdRdOptions, vacuum_max_eager_freeze_failure_rate)}
};
@@ -2004,7 +2090,6 @@ build_local_reloptions(local_relopts *relopts, Datum options, bool validate)
elems[i].optname = opt->option->name;
elems[i].opttype = opt->option->type;
elems[i].offset = opt->offset;
- elems[i].isset_offset = 0; /* not supported for local relopts yet */
i++;
}
diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c
index aa4fbec143f..696eab9bd97 100644
--- a/src/backend/commands/vacuum.c
+++ b/src/backend/commands/vacuum.c
@@ -2224,9 +2224,9 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams params,
{
StdRdOptions *opts = (StdRdOptions *) rel->rd_options;
- if (opts && opts->vacuum_truncate_set)
+ if (opts && opts->vacuum_truncate != TERNARY_UNSET)
{
- if (opts->vacuum_truncate)
+ if (opts->vacuum_truncate == TERNARY_TRUE)
params.truncate = VACOPTVALUE_ENABLED;
else
params.truncate = VACOPTVALUE_DISABLED;
diff --git a/src/include/access/reloptions.h b/src/include/access/reloptions.h
index 2f08e1b0cf0..dfbef2babf2 100644
--- a/src/include/access/reloptions.h
+++ b/src/include/access/reloptions.h
@@ -29,6 +29,7 @@
typedef enum relopt_type
{
RELOPT_TYPE_BOOL,
+ RELOPT_TYPE_TERNARY, /* on, off, unset */
RELOPT_TYPE_INT,
RELOPT_TYPE_REAL,
RELOPT_TYPE_ENUM,
@@ -80,6 +81,7 @@ typedef struct relopt_value
union
{
bool bool_val;
+ pg_ternary ternary_val;
int int_val;
double real_val;
int enum_val;
@@ -94,6 +96,12 @@ typedef struct relopt_bool
bool default_val;
} relopt_bool;
+typedef struct relopt_ternary
+{
+ relopt_gen gen;
+ int default_val;
+} relopt_ternary;
+
typedef struct relopt_int
{
relopt_gen gen;
@@ -152,19 +160,6 @@ typedef struct
const char *optname; /* option's name */
relopt_type opttype; /* option's datatype */
int offset; /* offset of field in result struct */
-
- /*
- * isset_offset is an optional offset of a field in the result struct that
- * stores whether the option is explicitly set for the relation or if it
- * just picked up the default value. In most cases, this can be
- * accomplished by giving the reloption a special out-of-range default
- * value (e.g., some integer reloptions use -2), but this isn't always
- * possible. For example, a Boolean reloption cannot be given an
- * out-of-range default, so we need another way to discover the source of
- * its value. This offset is only used if given a value greater than
- * zero.
- */
- int isset_offset;
} relopt_parse_elt;
/* Local reloption definition */
@@ -195,6 +190,9 @@ typedef struct local_relopts
extern relopt_kind add_reloption_kind(void);
extern void add_bool_reloption(bits32 kinds, const char *name, const char *desc,
bool default_val, LOCKMODE lockmode);
+extern void add_ternary_reloption(bits32 kinds, const char *name,
+ const char *desc, pg_ternary default_val,
+ LOCKMODE lockmode);
extern void add_int_reloption(bits32 kinds, const char *name, const char *desc,
int default_val, int min_val, int max_val,
LOCKMODE lockmode);
@@ -214,6 +212,9 @@ extern void register_reloptions_validator(local_relopts *relopts,
extern void add_local_bool_reloption(local_relopts *relopts, const char *name,
const char *desc, bool default_val,
int offset);
+extern void add_local_ternary_reloption(local_relopts *relopts,
+ const char *name, const char *desc,
+ pg_ternary default_val, int offset);
extern void add_local_int_reloption(local_relopts *relopts, const char *name,
const char *desc, int default_val,
int min_val, int max_val, int offset);
diff --git a/src/include/postgres.h b/src/include/postgres.h
index 7d93fbce709..47c88780776 100644
--- a/src/include/postgres.h
+++ b/src/include/postgres.h
@@ -543,6 +543,21 @@ Float8GetDatum(float8 X)
* ----------------------------------------------------------------
*/
+/*
+ * pg_ternary
+ * Boolean value with an extra "unset" value
+ *
+ * This enum can be used for values that want to distinguish between true,
+ * false, and unset.
+*/
+
+typedef enum pg_ternary
+{
+ TERNARY_FALSE = 0,
+ TERNARY_TRUE = 1,
+ TERNARY_UNSET = -1
+} pg_ternary;
+
/*
* NON_EXEC_STATIC: It's sometimes useful to define a variable or function
* that is normally static but extern when using EXEC_BACKEND (see
diff --git a/src/include/utils/rel.h b/src/include/utils/rel.h
index d03ab247788..5feb18a5373 100644
--- a/src/include/utils/rel.h
+++ b/src/include/utils/rel.h
@@ -347,8 +347,7 @@ typedef struct StdRdOptions
bool user_catalog_table; /* use as an additional catalog relation */
int parallel_workers; /* max number of parallel workers */
StdRdOptIndexCleanup vacuum_index_cleanup; /* controls index vacuuming */
- bool vacuum_truncate; /* enables vacuum to truncate a relation */
- bool vacuum_truncate_set; /* whether vacuum_truncate is set */
+ pg_ternary vacuum_truncate; /* enables vacuum to truncate a relation */
/*
* Fraction of pages in a relation that vacuum can eagerly scan and fail
diff --git a/src/test/regress/expected/reloptions.out b/src/test/regress/expected/reloptions.out
index 9de19b4e3f1..1c99f79ab01 100644
--- a/src/test/regress/expected/reloptions.out
+++ b/src/test/regress/expected/reloptions.out
@@ -98,6 +98,42 @@ SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass;
{fillfactor=13,autovacuum_enabled=false}
(1 row)
+-- Tests for future (FIXME) ternary options
+-- behave as boolean option: accept unassigned name and truncated value
+DROP TABLE reloptions_test;
+CREATE TABLE reloptions_test(i INT) WITH (vacuum_truncate);
+SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass;
+ reloptions
+------------------------
+ {vacuum_truncate=true}
+(1 row)
+
+DROP TABLE reloptions_test;
+CREATE TABLE reloptions_test(i INT) WITH (vacuum_truncate=FaLS);
+SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass;
+ reloptions
+------------------------
+ {vacuum_truncate=fals}
+(1 row)
+
+-- preferred "true" alias is used when storing
+DROP TABLE reloptions_test;
+CREATE TABLE reloptions_test(i INT) WITH (vacuum_index_cleanup=on);
+SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass;
+ reloptions
+---------------------------
+ {vacuum_index_cleanup=on}
+(1 row)
+
+-- custom "third" value is available
+DROP TABLE reloptions_test;
+CREATE TABLE reloptions_test(i INT) WITH (vacuum_index_cleanup=auto);
+SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass;
+ reloptions
+-----------------------------
+ {vacuum_index_cleanup=auto}
+(1 row)
+
-- Test vacuum_truncate option
DROP TABLE reloptions_test;
CREATE TEMP TABLE reloptions_test(i INT NOT NULL, j text)
diff --git a/src/test/regress/sql/reloptions.sql b/src/test/regress/sql/reloptions.sql
index 24fbe0b478d..f5980dafcbc 100644
--- a/src/test/regress/sql/reloptions.sql
+++ b/src/test/regress/sql/reloptions.sql
@@ -59,6 +59,27 @@ UPDATE pg_class
ALTER TABLE reloptions_test RESET (illegal_option);
SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass;
+-- Tests for future (FIXME) ternary options
+
+-- behave as boolean option: accept unassigned name and truncated value
+DROP TABLE reloptions_test;
+CREATE TABLE reloptions_test(i INT) WITH (vacuum_truncate);
+SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass;
+
+DROP TABLE reloptions_test;
+CREATE TABLE reloptions_test(i INT) WITH (vacuum_truncate=FaLS);
+SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass;
+
+-- preferred "true" alias is used when storing
+DROP TABLE reloptions_test;
+CREATE TABLE reloptions_test(i INT) WITH (vacuum_index_cleanup=on);
+SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass;
+
+-- custom "third" value is available
+DROP TABLE reloptions_test;
+CREATE TABLE reloptions_test(i INT) WITH (vacuum_index_cleanup=auto);
+SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass;
+
-- Test vacuum_truncate option
DROP TABLE reloptions_test;
diff --git a/src/tools/pgindent/typedefs.list b/src/tools/pgindent/typedefs.list
index 3f3a888fd0e..1c8610fd46c 100644
--- a/src/tools/pgindent/typedefs.list
+++ b/src/tools/pgindent/typedefs.list
@@ -3952,6 +3952,7 @@ pg_sha512_ctx
pg_snapshot
pg_special_case
pg_stack_base_t
+pg_ternary
pg_time_t
pg_time_usec_t
pg_tz
@@ -4079,6 +4080,7 @@ relopt_kind
relopt_parse_elt
relopt_real
relopt_string
+relopt_ternary
relopt_type
relopt_value
relopts_validator
--
2.47.3
--c36t3fndxhkzbl5z--
^ permalink raw reply [nested|flat] 868+ messages in thread
* [PATCH v3] Introduce ternary reloptions
@ 2026-01-16 15:04 Álvaro Herrera <[email protected]>
0 siblings, 0 replies; 868+ messages in thread
From: Álvaro Herrera @ 2026-01-16 15:04 UTC (permalink / raw)
Introduce ternary reloption as a replacement for current
`vacuum_truncate` implementation. Remove the `vacuum_truncate_set`
separate flag and use TERNARY_UNSET instead.
This could also be used for other options such as `vacuum_index_cleanup`
and `buffering`, but lets get the scaffolding in first.
Discussion: https://postgr.es/m/3474141.usfYGdeWWP@thinkpad-pgpro
---
src/backend/access/common/reloptions.c | 137 ++++++++++++++++++-----
src/backend/commands/vacuum.c | 4 +-
src/include/access/reloptions.h | 27 ++---
src/include/postgres.h | 15 +++
src/include/utils/rel.h | 3 +-
src/test/regress/expected/reloptions.out | 36 ++++++
src/test/regress/sql/reloptions.sql | 21 ++++
src/tools/pgindent/typedefs.list | 2 +
8 files changed, 202 insertions(+), 43 deletions(-)
diff --git a/src/backend/access/common/reloptions.c b/src/backend/access/common/reloptions.c
index 0b83f98ed5f..6f1f577581a 100644
--- a/src/backend/access/common/reloptions.c
+++ b/src/backend/access/common/reloptions.c
@@ -40,9 +40,9 @@
*
* To add an option:
*
- * (i) decide on a type (bool, integer, real, enum, string), name, default
- * value, upper and lower bounds (if applicable); for strings, consider a
- * validation routine.
+ * (i) decide on a type (bool, ternary, integer, real, enum, string), name,
+ * default value, upper and lower bounds (if applicable); for strings,
+ * consider a validation routine.
* (ii) add a record below (or use add_<type>_reloption).
* (iii) add it to the appropriate options struct (perhaps StdRdOptions)
* (iv) add it to the appropriate handling routine (perhaps
@@ -147,15 +147,6 @@ static relopt_bool boolRelOpts[] =
},
false
},
- {
- {
- "vacuum_truncate",
- "Enables vacuum to truncate empty pages at the end of this table",
- RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
- ShareUpdateExclusiveLock
- },
- true
- },
{
{
"deduplicate_items",
@@ -170,6 +161,25 @@ static relopt_bool boolRelOpts[] =
{{NULL}}
};
+static relopt_ternary ternaryRelOpts[] =
+{
+ {
+ {
+ "vacuum_truncate",
+ "Enables vacuum to truncate empty pages at the end of this table",
+ RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
+ ShareUpdateExclusiveLock
+ },
+ TERNARY_UNSET
+ },
+ /* list terminator */
+ {
+ {
+ NULL
+ }
+ }
+};
+
static relopt_int intRelOpts[] =
{
{
@@ -609,6 +619,13 @@ initialize_reloptions(void)
boolRelOpts[i].gen.lockmode));
j++;
}
+ for (i = 0; ternaryRelOpts[i].gen.name; i++)
+ {
+ Assert(DoLockModesConflict(ternaryRelOpts[i].gen.lockmode,
+ ternaryRelOpts[i].gen.lockmode));
+ j++;
+ }
+
for (i = 0; intRelOpts[i].gen.name; i++)
{
Assert(DoLockModesConflict(intRelOpts[i].gen.lockmode,
@@ -649,6 +666,14 @@ initialize_reloptions(void)
j++;
}
+ for (i = 0; ternaryRelOpts[i].gen.name; i++)
+ {
+ relOpts[j] = &ternaryRelOpts[i].gen;
+ relOpts[j]->type = RELOPT_TYPE_TERNARY;
+ relOpts[j]->namelen = strlen(relOpts[j]->name);
+ j++;
+ }
+
for (i = 0; intRelOpts[i].gen.name; i++)
{
relOpts[j] = &intRelOpts[i].gen;
@@ -809,6 +834,9 @@ allocate_reloption(bits32 kinds, int type, const char *name, const char *desc,
case RELOPT_TYPE_BOOL:
size = sizeof(relopt_bool);
break;
+ case RELOPT_TYPE_TERNARY:
+ size = sizeof(relopt_ternary);
+ break;
case RELOPT_TYPE_INT:
size = sizeof(relopt_int);
break;
@@ -892,6 +920,57 @@ add_local_bool_reloption(local_relopts *relopts, const char *name,
add_local_reloption(relopts, (relopt_gen *) newoption, offset);
}
+/*
+ * init_ternary_reloption
+ * Allocate and initialize a new ternary reloption
+ */
+static relopt_ternary *
+init_ternary_reloption(bits32 kinds, const char *name, const char *desc,
+ pg_ternary default_val, LOCKMODE lockmode)
+{
+ relopt_ternary *newoption;
+
+ newoption = (relopt_ternary *)
+ allocate_reloption(kinds, RELOPT_TYPE_TERNARY, name, desc, lockmode);
+ newoption->default_val = default_val;
+
+ return newoption;
+}
+
+/*
+ * add_ternary_reloption
+ * Add a new ternary reloption
+ */
+void
+add_ternary_reloption(bits32 kinds, const char *name, const char *desc,
+ pg_ternary default_val, LOCKMODE lockmode)
+{
+ relopt_ternary *newoption;
+
+ newoption =
+ init_ternary_reloption(kinds, name, desc, default_val, lockmode);
+
+ add_reloption((relopt_gen *) newoption);
+}
+
+/*
+ * add_local_ternary_reloption
+ * Add a new ternary local reloption
+ *
+ * 'offset' is offset of ternary-typed field.
+ */
+void
+add_local_ternary_reloption(local_relopts *relopts, const char *name,
+ const char *desc, pg_ternary default_val,
+ int offset)
+{
+ relopt_ternary *newoption;
+
+ newoption =
+ init_ternary_reloption(RELOPT_KIND_LOCAL, name, desc, default_val, 0);
+
+ add_local_reloption(relopts, (relopt_gen *) newoption, offset);
+}
/*
* init_real_reloption
@@ -1626,6 +1705,19 @@ parse_one_reloption(relopt_value *option, char *text_str, int text_len,
option->gen->name, value)));
}
break;
+ case RELOPT_TYPE_TERNARY:
+ {
+ bool b;
+
+ parsed = parse_bool(value, &b);
+ option->values.ternary_val = b ? TERNARY_TRUE : TERNARY_FALSE;
+ if (validate && !parsed)
+ ereport(ERROR,
+ (errcode(ERRCODE_INVALID_PARAMETER_VALUE),
+ errmsg("invalid value for ternary option \"%s\": %s",
+ option->gen->name, value)));
+ }
+ break;
case RELOPT_TYPE_INT:
{
relopt_int *optint = (relopt_int *) option->gen;
@@ -1789,17 +1881,6 @@ fillRelOptions(void *rdopts, Size basesize,
char *itempos = ((char *) rdopts) + elems[j].offset;
char *string_val;
- /*
- * If isset_offset is provided, store whether the reloption is
- * set there.
- */
- if (elems[j].isset_offset > 0)
- {
- char *setpos = ((char *) rdopts) + elems[j].isset_offset;
-
- *(bool *) setpos = options[i].isset;
- }
-
switch (options[i].gen->type)
{
case RELOPT_TYPE_BOOL:
@@ -1807,6 +1888,11 @@ fillRelOptions(void *rdopts, Size basesize,
options[i].values.bool_val :
((relopt_bool *) options[i].gen)->default_val;
break;
+ case RELOPT_TYPE_TERNARY:
+ *(pg_ternary *) itempos = options[i].isset ?
+ options[i].values.ternary_val :
+ ((relopt_ternary *) options[i].gen)->default_val;
+ break;
case RELOPT_TYPE_INT:
*(int *) itempos = options[i].isset ?
options[i].values.int_val :
@@ -1923,8 +2009,8 @@ default_reloptions(Datum reloptions, bool validate, relopt_kind kind)
offsetof(StdRdOptions, parallel_workers)},
{"vacuum_index_cleanup", RELOPT_TYPE_ENUM,
offsetof(StdRdOptions, vacuum_index_cleanup)},
- {"vacuum_truncate", RELOPT_TYPE_BOOL,
- offsetof(StdRdOptions, vacuum_truncate), offsetof(StdRdOptions, vacuum_truncate_set)},
+ {"vacuum_truncate", RELOPT_TYPE_TERNARY,
+ offsetof(StdRdOptions, vacuum_truncate)},
{"vacuum_max_eager_freeze_failure_rate", RELOPT_TYPE_REAL,
offsetof(StdRdOptions, vacuum_max_eager_freeze_failure_rate)}
};
@@ -2004,7 +2090,6 @@ build_local_reloptions(local_relopts *relopts, Datum options, bool validate)
elems[i].optname = opt->option->name;
elems[i].opttype = opt->option->type;
elems[i].offset = opt->offset;
- elems[i].isset_offset = 0; /* not supported for local relopts yet */
i++;
}
diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c
index aa4fbec143f..696eab9bd97 100644
--- a/src/backend/commands/vacuum.c
+++ b/src/backend/commands/vacuum.c
@@ -2224,9 +2224,9 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams params,
{
StdRdOptions *opts = (StdRdOptions *) rel->rd_options;
- if (opts && opts->vacuum_truncate_set)
+ if (opts && opts->vacuum_truncate != TERNARY_UNSET)
{
- if (opts->vacuum_truncate)
+ if (opts->vacuum_truncate == TERNARY_TRUE)
params.truncate = VACOPTVALUE_ENABLED;
else
params.truncate = VACOPTVALUE_DISABLED;
diff --git a/src/include/access/reloptions.h b/src/include/access/reloptions.h
index 2f08e1b0cf0..dfbef2babf2 100644
--- a/src/include/access/reloptions.h
+++ b/src/include/access/reloptions.h
@@ -29,6 +29,7 @@
typedef enum relopt_type
{
RELOPT_TYPE_BOOL,
+ RELOPT_TYPE_TERNARY, /* on, off, unset */
RELOPT_TYPE_INT,
RELOPT_TYPE_REAL,
RELOPT_TYPE_ENUM,
@@ -80,6 +81,7 @@ typedef struct relopt_value
union
{
bool bool_val;
+ pg_ternary ternary_val;
int int_val;
double real_val;
int enum_val;
@@ -94,6 +96,12 @@ typedef struct relopt_bool
bool default_val;
} relopt_bool;
+typedef struct relopt_ternary
+{
+ relopt_gen gen;
+ int default_val;
+} relopt_ternary;
+
typedef struct relopt_int
{
relopt_gen gen;
@@ -152,19 +160,6 @@ typedef struct
const char *optname; /* option's name */
relopt_type opttype; /* option's datatype */
int offset; /* offset of field in result struct */
-
- /*
- * isset_offset is an optional offset of a field in the result struct that
- * stores whether the option is explicitly set for the relation or if it
- * just picked up the default value. In most cases, this can be
- * accomplished by giving the reloption a special out-of-range default
- * value (e.g., some integer reloptions use -2), but this isn't always
- * possible. For example, a Boolean reloption cannot be given an
- * out-of-range default, so we need another way to discover the source of
- * its value. This offset is only used if given a value greater than
- * zero.
- */
- int isset_offset;
} relopt_parse_elt;
/* Local reloption definition */
@@ -195,6 +190,9 @@ typedef struct local_relopts
extern relopt_kind add_reloption_kind(void);
extern void add_bool_reloption(bits32 kinds, const char *name, const char *desc,
bool default_val, LOCKMODE lockmode);
+extern void add_ternary_reloption(bits32 kinds, const char *name,
+ const char *desc, pg_ternary default_val,
+ LOCKMODE lockmode);
extern void add_int_reloption(bits32 kinds, const char *name, const char *desc,
int default_val, int min_val, int max_val,
LOCKMODE lockmode);
@@ -214,6 +212,9 @@ extern void register_reloptions_validator(local_relopts *relopts,
extern void add_local_bool_reloption(local_relopts *relopts, const char *name,
const char *desc, bool default_val,
int offset);
+extern void add_local_ternary_reloption(local_relopts *relopts,
+ const char *name, const char *desc,
+ pg_ternary default_val, int offset);
extern void add_local_int_reloption(local_relopts *relopts, const char *name,
const char *desc, int default_val,
int min_val, int max_val, int offset);
diff --git a/src/include/postgres.h b/src/include/postgres.h
index 7d93fbce709..47c88780776 100644
--- a/src/include/postgres.h
+++ b/src/include/postgres.h
@@ -543,6 +543,21 @@ Float8GetDatum(float8 X)
* ----------------------------------------------------------------
*/
+/*
+ * pg_ternary
+ * Boolean value with an extra "unset" value
+ *
+ * This enum can be used for values that want to distinguish between true,
+ * false, and unset.
+*/
+
+typedef enum pg_ternary
+{
+ TERNARY_FALSE = 0,
+ TERNARY_TRUE = 1,
+ TERNARY_UNSET = -1
+} pg_ternary;
+
/*
* NON_EXEC_STATIC: It's sometimes useful to define a variable or function
* that is normally static but extern when using EXEC_BACKEND (see
diff --git a/src/include/utils/rel.h b/src/include/utils/rel.h
index d03ab247788..5feb18a5373 100644
--- a/src/include/utils/rel.h
+++ b/src/include/utils/rel.h
@@ -347,8 +347,7 @@ typedef struct StdRdOptions
bool user_catalog_table; /* use as an additional catalog relation */
int parallel_workers; /* max number of parallel workers */
StdRdOptIndexCleanup vacuum_index_cleanup; /* controls index vacuuming */
- bool vacuum_truncate; /* enables vacuum to truncate a relation */
- bool vacuum_truncate_set; /* whether vacuum_truncate is set */
+ pg_ternary vacuum_truncate; /* enables vacuum to truncate a relation */
/*
* Fraction of pages in a relation that vacuum can eagerly scan and fail
diff --git a/src/test/regress/expected/reloptions.out b/src/test/regress/expected/reloptions.out
index 9de19b4e3f1..1c99f79ab01 100644
--- a/src/test/regress/expected/reloptions.out
+++ b/src/test/regress/expected/reloptions.out
@@ -98,6 +98,42 @@ SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass;
{fillfactor=13,autovacuum_enabled=false}
(1 row)
+-- Tests for future (FIXME) ternary options
+-- behave as boolean option: accept unassigned name and truncated value
+DROP TABLE reloptions_test;
+CREATE TABLE reloptions_test(i INT) WITH (vacuum_truncate);
+SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass;
+ reloptions
+------------------------
+ {vacuum_truncate=true}
+(1 row)
+
+DROP TABLE reloptions_test;
+CREATE TABLE reloptions_test(i INT) WITH (vacuum_truncate=FaLS);
+SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass;
+ reloptions
+------------------------
+ {vacuum_truncate=fals}
+(1 row)
+
+-- preferred "true" alias is used when storing
+DROP TABLE reloptions_test;
+CREATE TABLE reloptions_test(i INT) WITH (vacuum_index_cleanup=on);
+SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass;
+ reloptions
+---------------------------
+ {vacuum_index_cleanup=on}
+(1 row)
+
+-- custom "third" value is available
+DROP TABLE reloptions_test;
+CREATE TABLE reloptions_test(i INT) WITH (vacuum_index_cleanup=auto);
+SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass;
+ reloptions
+-----------------------------
+ {vacuum_index_cleanup=auto}
+(1 row)
+
-- Test vacuum_truncate option
DROP TABLE reloptions_test;
CREATE TEMP TABLE reloptions_test(i INT NOT NULL, j text)
diff --git a/src/test/regress/sql/reloptions.sql b/src/test/regress/sql/reloptions.sql
index 24fbe0b478d..f5980dafcbc 100644
--- a/src/test/regress/sql/reloptions.sql
+++ b/src/test/regress/sql/reloptions.sql
@@ -59,6 +59,27 @@ UPDATE pg_class
ALTER TABLE reloptions_test RESET (illegal_option);
SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass;
+-- Tests for future (FIXME) ternary options
+
+-- behave as boolean option: accept unassigned name and truncated value
+DROP TABLE reloptions_test;
+CREATE TABLE reloptions_test(i INT) WITH (vacuum_truncate);
+SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass;
+
+DROP TABLE reloptions_test;
+CREATE TABLE reloptions_test(i INT) WITH (vacuum_truncate=FaLS);
+SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass;
+
+-- preferred "true" alias is used when storing
+DROP TABLE reloptions_test;
+CREATE TABLE reloptions_test(i INT) WITH (vacuum_index_cleanup=on);
+SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass;
+
+-- custom "third" value is available
+DROP TABLE reloptions_test;
+CREATE TABLE reloptions_test(i INT) WITH (vacuum_index_cleanup=auto);
+SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass;
+
-- Test vacuum_truncate option
DROP TABLE reloptions_test;
diff --git a/src/tools/pgindent/typedefs.list b/src/tools/pgindent/typedefs.list
index 3f3a888fd0e..1c8610fd46c 100644
--- a/src/tools/pgindent/typedefs.list
+++ b/src/tools/pgindent/typedefs.list
@@ -3952,6 +3952,7 @@ pg_sha512_ctx
pg_snapshot
pg_special_case
pg_stack_base_t
+pg_ternary
pg_time_t
pg_time_usec_t
pg_tz
@@ -4079,6 +4080,7 @@ relopt_kind
relopt_parse_elt
relopt_real
relopt_string
+relopt_ternary
relopt_type
relopt_value
relopts_validator
--
2.47.3
--c36t3fndxhkzbl5z--
^ permalink raw reply [nested|flat] 868+ messages in thread
* [PATCH v3] Introduce ternary reloptions
@ 2026-01-16 15:04 Álvaro Herrera <[email protected]>
0 siblings, 0 replies; 868+ messages in thread
From: Álvaro Herrera @ 2026-01-16 15:04 UTC (permalink / raw)
Introduce ternary reloption as a replacement for current
`vacuum_truncate` implementation. Remove the `vacuum_truncate_set`
separate flag and use TERNARY_UNSET instead.
This could also be used for other options such as `vacuum_index_cleanup`
and `buffering`, but lets get the scaffolding in first.
Discussion: https://postgr.es/m/3474141.usfYGdeWWP@thinkpad-pgpro
---
src/backend/access/common/reloptions.c | 137 ++++++++++++++++++-----
src/backend/commands/vacuum.c | 4 +-
src/include/access/reloptions.h | 27 ++---
src/include/postgres.h | 15 +++
src/include/utils/rel.h | 3 +-
src/test/regress/expected/reloptions.out | 36 ++++++
src/test/regress/sql/reloptions.sql | 21 ++++
src/tools/pgindent/typedefs.list | 2 +
8 files changed, 202 insertions(+), 43 deletions(-)
diff --git a/src/backend/access/common/reloptions.c b/src/backend/access/common/reloptions.c
index 0b83f98ed5f..6f1f577581a 100644
--- a/src/backend/access/common/reloptions.c
+++ b/src/backend/access/common/reloptions.c
@@ -40,9 +40,9 @@
*
* To add an option:
*
- * (i) decide on a type (bool, integer, real, enum, string), name, default
- * value, upper and lower bounds (if applicable); for strings, consider a
- * validation routine.
+ * (i) decide on a type (bool, ternary, integer, real, enum, string), name,
+ * default value, upper and lower bounds (if applicable); for strings,
+ * consider a validation routine.
* (ii) add a record below (or use add_<type>_reloption).
* (iii) add it to the appropriate options struct (perhaps StdRdOptions)
* (iv) add it to the appropriate handling routine (perhaps
@@ -147,15 +147,6 @@ static relopt_bool boolRelOpts[] =
},
false
},
- {
- {
- "vacuum_truncate",
- "Enables vacuum to truncate empty pages at the end of this table",
- RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
- ShareUpdateExclusiveLock
- },
- true
- },
{
{
"deduplicate_items",
@@ -170,6 +161,25 @@ static relopt_bool boolRelOpts[] =
{{NULL}}
};
+static relopt_ternary ternaryRelOpts[] =
+{
+ {
+ {
+ "vacuum_truncate",
+ "Enables vacuum to truncate empty pages at the end of this table",
+ RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
+ ShareUpdateExclusiveLock
+ },
+ TERNARY_UNSET
+ },
+ /* list terminator */
+ {
+ {
+ NULL
+ }
+ }
+};
+
static relopt_int intRelOpts[] =
{
{
@@ -609,6 +619,13 @@ initialize_reloptions(void)
boolRelOpts[i].gen.lockmode));
j++;
}
+ for (i = 0; ternaryRelOpts[i].gen.name; i++)
+ {
+ Assert(DoLockModesConflict(ternaryRelOpts[i].gen.lockmode,
+ ternaryRelOpts[i].gen.lockmode));
+ j++;
+ }
+
for (i = 0; intRelOpts[i].gen.name; i++)
{
Assert(DoLockModesConflict(intRelOpts[i].gen.lockmode,
@@ -649,6 +666,14 @@ initialize_reloptions(void)
j++;
}
+ for (i = 0; ternaryRelOpts[i].gen.name; i++)
+ {
+ relOpts[j] = &ternaryRelOpts[i].gen;
+ relOpts[j]->type = RELOPT_TYPE_TERNARY;
+ relOpts[j]->namelen = strlen(relOpts[j]->name);
+ j++;
+ }
+
for (i = 0; intRelOpts[i].gen.name; i++)
{
relOpts[j] = &intRelOpts[i].gen;
@@ -809,6 +834,9 @@ allocate_reloption(bits32 kinds, int type, const char *name, const char *desc,
case RELOPT_TYPE_BOOL:
size = sizeof(relopt_bool);
break;
+ case RELOPT_TYPE_TERNARY:
+ size = sizeof(relopt_ternary);
+ break;
case RELOPT_TYPE_INT:
size = sizeof(relopt_int);
break;
@@ -892,6 +920,57 @@ add_local_bool_reloption(local_relopts *relopts, const char *name,
add_local_reloption(relopts, (relopt_gen *) newoption, offset);
}
+/*
+ * init_ternary_reloption
+ * Allocate and initialize a new ternary reloption
+ */
+static relopt_ternary *
+init_ternary_reloption(bits32 kinds, const char *name, const char *desc,
+ pg_ternary default_val, LOCKMODE lockmode)
+{
+ relopt_ternary *newoption;
+
+ newoption = (relopt_ternary *)
+ allocate_reloption(kinds, RELOPT_TYPE_TERNARY, name, desc, lockmode);
+ newoption->default_val = default_val;
+
+ return newoption;
+}
+
+/*
+ * add_ternary_reloption
+ * Add a new ternary reloption
+ */
+void
+add_ternary_reloption(bits32 kinds, const char *name, const char *desc,
+ pg_ternary default_val, LOCKMODE lockmode)
+{
+ relopt_ternary *newoption;
+
+ newoption =
+ init_ternary_reloption(kinds, name, desc, default_val, lockmode);
+
+ add_reloption((relopt_gen *) newoption);
+}
+
+/*
+ * add_local_ternary_reloption
+ * Add a new ternary local reloption
+ *
+ * 'offset' is offset of ternary-typed field.
+ */
+void
+add_local_ternary_reloption(local_relopts *relopts, const char *name,
+ const char *desc, pg_ternary default_val,
+ int offset)
+{
+ relopt_ternary *newoption;
+
+ newoption =
+ init_ternary_reloption(RELOPT_KIND_LOCAL, name, desc, default_val, 0);
+
+ add_local_reloption(relopts, (relopt_gen *) newoption, offset);
+}
/*
* init_real_reloption
@@ -1626,6 +1705,19 @@ parse_one_reloption(relopt_value *option, char *text_str, int text_len,
option->gen->name, value)));
}
break;
+ case RELOPT_TYPE_TERNARY:
+ {
+ bool b;
+
+ parsed = parse_bool(value, &b);
+ option->values.ternary_val = b ? TERNARY_TRUE : TERNARY_FALSE;
+ if (validate && !parsed)
+ ereport(ERROR,
+ (errcode(ERRCODE_INVALID_PARAMETER_VALUE),
+ errmsg("invalid value for ternary option \"%s\": %s",
+ option->gen->name, value)));
+ }
+ break;
case RELOPT_TYPE_INT:
{
relopt_int *optint = (relopt_int *) option->gen;
@@ -1789,17 +1881,6 @@ fillRelOptions(void *rdopts, Size basesize,
char *itempos = ((char *) rdopts) + elems[j].offset;
char *string_val;
- /*
- * If isset_offset is provided, store whether the reloption is
- * set there.
- */
- if (elems[j].isset_offset > 0)
- {
- char *setpos = ((char *) rdopts) + elems[j].isset_offset;
-
- *(bool *) setpos = options[i].isset;
- }
-
switch (options[i].gen->type)
{
case RELOPT_TYPE_BOOL:
@@ -1807,6 +1888,11 @@ fillRelOptions(void *rdopts, Size basesize,
options[i].values.bool_val :
((relopt_bool *) options[i].gen)->default_val;
break;
+ case RELOPT_TYPE_TERNARY:
+ *(pg_ternary *) itempos = options[i].isset ?
+ options[i].values.ternary_val :
+ ((relopt_ternary *) options[i].gen)->default_val;
+ break;
case RELOPT_TYPE_INT:
*(int *) itempos = options[i].isset ?
options[i].values.int_val :
@@ -1923,8 +2009,8 @@ default_reloptions(Datum reloptions, bool validate, relopt_kind kind)
offsetof(StdRdOptions, parallel_workers)},
{"vacuum_index_cleanup", RELOPT_TYPE_ENUM,
offsetof(StdRdOptions, vacuum_index_cleanup)},
- {"vacuum_truncate", RELOPT_TYPE_BOOL,
- offsetof(StdRdOptions, vacuum_truncate), offsetof(StdRdOptions, vacuum_truncate_set)},
+ {"vacuum_truncate", RELOPT_TYPE_TERNARY,
+ offsetof(StdRdOptions, vacuum_truncate)},
{"vacuum_max_eager_freeze_failure_rate", RELOPT_TYPE_REAL,
offsetof(StdRdOptions, vacuum_max_eager_freeze_failure_rate)}
};
@@ -2004,7 +2090,6 @@ build_local_reloptions(local_relopts *relopts, Datum options, bool validate)
elems[i].optname = opt->option->name;
elems[i].opttype = opt->option->type;
elems[i].offset = opt->offset;
- elems[i].isset_offset = 0; /* not supported for local relopts yet */
i++;
}
diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c
index aa4fbec143f..696eab9bd97 100644
--- a/src/backend/commands/vacuum.c
+++ b/src/backend/commands/vacuum.c
@@ -2224,9 +2224,9 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams params,
{
StdRdOptions *opts = (StdRdOptions *) rel->rd_options;
- if (opts && opts->vacuum_truncate_set)
+ if (opts && opts->vacuum_truncate != TERNARY_UNSET)
{
- if (opts->vacuum_truncate)
+ if (opts->vacuum_truncate == TERNARY_TRUE)
params.truncate = VACOPTVALUE_ENABLED;
else
params.truncate = VACOPTVALUE_DISABLED;
diff --git a/src/include/access/reloptions.h b/src/include/access/reloptions.h
index 2f08e1b0cf0..dfbef2babf2 100644
--- a/src/include/access/reloptions.h
+++ b/src/include/access/reloptions.h
@@ -29,6 +29,7 @@
typedef enum relopt_type
{
RELOPT_TYPE_BOOL,
+ RELOPT_TYPE_TERNARY, /* on, off, unset */
RELOPT_TYPE_INT,
RELOPT_TYPE_REAL,
RELOPT_TYPE_ENUM,
@@ -80,6 +81,7 @@ typedef struct relopt_value
union
{
bool bool_val;
+ pg_ternary ternary_val;
int int_val;
double real_val;
int enum_val;
@@ -94,6 +96,12 @@ typedef struct relopt_bool
bool default_val;
} relopt_bool;
+typedef struct relopt_ternary
+{
+ relopt_gen gen;
+ int default_val;
+} relopt_ternary;
+
typedef struct relopt_int
{
relopt_gen gen;
@@ -152,19 +160,6 @@ typedef struct
const char *optname; /* option's name */
relopt_type opttype; /* option's datatype */
int offset; /* offset of field in result struct */
-
- /*
- * isset_offset is an optional offset of a field in the result struct that
- * stores whether the option is explicitly set for the relation or if it
- * just picked up the default value. In most cases, this can be
- * accomplished by giving the reloption a special out-of-range default
- * value (e.g., some integer reloptions use -2), but this isn't always
- * possible. For example, a Boolean reloption cannot be given an
- * out-of-range default, so we need another way to discover the source of
- * its value. This offset is only used if given a value greater than
- * zero.
- */
- int isset_offset;
} relopt_parse_elt;
/* Local reloption definition */
@@ -195,6 +190,9 @@ typedef struct local_relopts
extern relopt_kind add_reloption_kind(void);
extern void add_bool_reloption(bits32 kinds, const char *name, const char *desc,
bool default_val, LOCKMODE lockmode);
+extern void add_ternary_reloption(bits32 kinds, const char *name,
+ const char *desc, pg_ternary default_val,
+ LOCKMODE lockmode);
extern void add_int_reloption(bits32 kinds, const char *name, const char *desc,
int default_val, int min_val, int max_val,
LOCKMODE lockmode);
@@ -214,6 +212,9 @@ extern void register_reloptions_validator(local_relopts *relopts,
extern void add_local_bool_reloption(local_relopts *relopts, const char *name,
const char *desc, bool default_val,
int offset);
+extern void add_local_ternary_reloption(local_relopts *relopts,
+ const char *name, const char *desc,
+ pg_ternary default_val, int offset);
extern void add_local_int_reloption(local_relopts *relopts, const char *name,
const char *desc, int default_val,
int min_val, int max_val, int offset);
diff --git a/src/include/postgres.h b/src/include/postgres.h
index 7d93fbce709..47c88780776 100644
--- a/src/include/postgres.h
+++ b/src/include/postgres.h
@@ -543,6 +543,21 @@ Float8GetDatum(float8 X)
* ----------------------------------------------------------------
*/
+/*
+ * pg_ternary
+ * Boolean value with an extra "unset" value
+ *
+ * This enum can be used for values that want to distinguish between true,
+ * false, and unset.
+*/
+
+typedef enum pg_ternary
+{
+ TERNARY_FALSE = 0,
+ TERNARY_TRUE = 1,
+ TERNARY_UNSET = -1
+} pg_ternary;
+
/*
* NON_EXEC_STATIC: It's sometimes useful to define a variable or function
* that is normally static but extern when using EXEC_BACKEND (see
diff --git a/src/include/utils/rel.h b/src/include/utils/rel.h
index d03ab247788..5feb18a5373 100644
--- a/src/include/utils/rel.h
+++ b/src/include/utils/rel.h
@@ -347,8 +347,7 @@ typedef struct StdRdOptions
bool user_catalog_table; /* use as an additional catalog relation */
int parallel_workers; /* max number of parallel workers */
StdRdOptIndexCleanup vacuum_index_cleanup; /* controls index vacuuming */
- bool vacuum_truncate; /* enables vacuum to truncate a relation */
- bool vacuum_truncate_set; /* whether vacuum_truncate is set */
+ pg_ternary vacuum_truncate; /* enables vacuum to truncate a relation */
/*
* Fraction of pages in a relation that vacuum can eagerly scan and fail
diff --git a/src/test/regress/expected/reloptions.out b/src/test/regress/expected/reloptions.out
index 9de19b4e3f1..1c99f79ab01 100644
--- a/src/test/regress/expected/reloptions.out
+++ b/src/test/regress/expected/reloptions.out
@@ -98,6 +98,42 @@ SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass;
{fillfactor=13,autovacuum_enabled=false}
(1 row)
+-- Tests for future (FIXME) ternary options
+-- behave as boolean option: accept unassigned name and truncated value
+DROP TABLE reloptions_test;
+CREATE TABLE reloptions_test(i INT) WITH (vacuum_truncate);
+SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass;
+ reloptions
+------------------------
+ {vacuum_truncate=true}
+(1 row)
+
+DROP TABLE reloptions_test;
+CREATE TABLE reloptions_test(i INT) WITH (vacuum_truncate=FaLS);
+SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass;
+ reloptions
+------------------------
+ {vacuum_truncate=fals}
+(1 row)
+
+-- preferred "true" alias is used when storing
+DROP TABLE reloptions_test;
+CREATE TABLE reloptions_test(i INT) WITH (vacuum_index_cleanup=on);
+SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass;
+ reloptions
+---------------------------
+ {vacuum_index_cleanup=on}
+(1 row)
+
+-- custom "third" value is available
+DROP TABLE reloptions_test;
+CREATE TABLE reloptions_test(i INT) WITH (vacuum_index_cleanup=auto);
+SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass;
+ reloptions
+-----------------------------
+ {vacuum_index_cleanup=auto}
+(1 row)
+
-- Test vacuum_truncate option
DROP TABLE reloptions_test;
CREATE TEMP TABLE reloptions_test(i INT NOT NULL, j text)
diff --git a/src/test/regress/sql/reloptions.sql b/src/test/regress/sql/reloptions.sql
index 24fbe0b478d..f5980dafcbc 100644
--- a/src/test/regress/sql/reloptions.sql
+++ b/src/test/regress/sql/reloptions.sql
@@ -59,6 +59,27 @@ UPDATE pg_class
ALTER TABLE reloptions_test RESET (illegal_option);
SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass;
+-- Tests for future (FIXME) ternary options
+
+-- behave as boolean option: accept unassigned name and truncated value
+DROP TABLE reloptions_test;
+CREATE TABLE reloptions_test(i INT) WITH (vacuum_truncate);
+SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass;
+
+DROP TABLE reloptions_test;
+CREATE TABLE reloptions_test(i INT) WITH (vacuum_truncate=FaLS);
+SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass;
+
+-- preferred "true" alias is used when storing
+DROP TABLE reloptions_test;
+CREATE TABLE reloptions_test(i INT) WITH (vacuum_index_cleanup=on);
+SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass;
+
+-- custom "third" value is available
+DROP TABLE reloptions_test;
+CREATE TABLE reloptions_test(i INT) WITH (vacuum_index_cleanup=auto);
+SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass;
+
-- Test vacuum_truncate option
DROP TABLE reloptions_test;
diff --git a/src/tools/pgindent/typedefs.list b/src/tools/pgindent/typedefs.list
index 3f3a888fd0e..1c8610fd46c 100644
--- a/src/tools/pgindent/typedefs.list
+++ b/src/tools/pgindent/typedefs.list
@@ -3952,6 +3952,7 @@ pg_sha512_ctx
pg_snapshot
pg_special_case
pg_stack_base_t
+pg_ternary
pg_time_t
pg_time_usec_t
pg_tz
@@ -4079,6 +4080,7 @@ relopt_kind
relopt_parse_elt
relopt_real
relopt_string
+relopt_ternary
relopt_type
relopt_value
relopts_validator
--
2.47.3
--c36t3fndxhkzbl5z--
^ permalink raw reply [nested|flat] 868+ messages in thread
* [PATCH v3] Introduce ternary reloptions
@ 2026-01-16 15:04 Álvaro Herrera <[email protected]>
0 siblings, 0 replies; 868+ messages in thread
From: Álvaro Herrera @ 2026-01-16 15:04 UTC (permalink / raw)
Introduce ternary reloption as a replacement for current
`vacuum_truncate` implementation. Remove the `vacuum_truncate_set`
separate flag and use TERNARY_UNSET instead.
This could also be used for other options such as `vacuum_index_cleanup`
and `buffering`, but lets get the scaffolding in first.
Discussion: https://postgr.es/m/3474141.usfYGdeWWP@thinkpad-pgpro
---
src/backend/access/common/reloptions.c | 137 ++++++++++++++++++-----
src/backend/commands/vacuum.c | 4 +-
src/include/access/reloptions.h | 27 ++---
src/include/postgres.h | 15 +++
src/include/utils/rel.h | 3 +-
src/test/regress/expected/reloptions.out | 36 ++++++
src/test/regress/sql/reloptions.sql | 21 ++++
src/tools/pgindent/typedefs.list | 2 +
8 files changed, 202 insertions(+), 43 deletions(-)
diff --git a/src/backend/access/common/reloptions.c b/src/backend/access/common/reloptions.c
index 0b83f98ed5f..6f1f577581a 100644
--- a/src/backend/access/common/reloptions.c
+++ b/src/backend/access/common/reloptions.c
@@ -40,9 +40,9 @@
*
* To add an option:
*
- * (i) decide on a type (bool, integer, real, enum, string), name, default
- * value, upper and lower bounds (if applicable); for strings, consider a
- * validation routine.
+ * (i) decide on a type (bool, ternary, integer, real, enum, string), name,
+ * default value, upper and lower bounds (if applicable); for strings,
+ * consider a validation routine.
* (ii) add a record below (or use add_<type>_reloption).
* (iii) add it to the appropriate options struct (perhaps StdRdOptions)
* (iv) add it to the appropriate handling routine (perhaps
@@ -147,15 +147,6 @@ static relopt_bool boolRelOpts[] =
},
false
},
- {
- {
- "vacuum_truncate",
- "Enables vacuum to truncate empty pages at the end of this table",
- RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
- ShareUpdateExclusiveLock
- },
- true
- },
{
{
"deduplicate_items",
@@ -170,6 +161,25 @@ static relopt_bool boolRelOpts[] =
{{NULL}}
};
+static relopt_ternary ternaryRelOpts[] =
+{
+ {
+ {
+ "vacuum_truncate",
+ "Enables vacuum to truncate empty pages at the end of this table",
+ RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
+ ShareUpdateExclusiveLock
+ },
+ TERNARY_UNSET
+ },
+ /* list terminator */
+ {
+ {
+ NULL
+ }
+ }
+};
+
static relopt_int intRelOpts[] =
{
{
@@ -609,6 +619,13 @@ initialize_reloptions(void)
boolRelOpts[i].gen.lockmode));
j++;
}
+ for (i = 0; ternaryRelOpts[i].gen.name; i++)
+ {
+ Assert(DoLockModesConflict(ternaryRelOpts[i].gen.lockmode,
+ ternaryRelOpts[i].gen.lockmode));
+ j++;
+ }
+
for (i = 0; intRelOpts[i].gen.name; i++)
{
Assert(DoLockModesConflict(intRelOpts[i].gen.lockmode,
@@ -649,6 +666,14 @@ initialize_reloptions(void)
j++;
}
+ for (i = 0; ternaryRelOpts[i].gen.name; i++)
+ {
+ relOpts[j] = &ternaryRelOpts[i].gen;
+ relOpts[j]->type = RELOPT_TYPE_TERNARY;
+ relOpts[j]->namelen = strlen(relOpts[j]->name);
+ j++;
+ }
+
for (i = 0; intRelOpts[i].gen.name; i++)
{
relOpts[j] = &intRelOpts[i].gen;
@@ -809,6 +834,9 @@ allocate_reloption(bits32 kinds, int type, const char *name, const char *desc,
case RELOPT_TYPE_BOOL:
size = sizeof(relopt_bool);
break;
+ case RELOPT_TYPE_TERNARY:
+ size = sizeof(relopt_ternary);
+ break;
case RELOPT_TYPE_INT:
size = sizeof(relopt_int);
break;
@@ -892,6 +920,57 @@ add_local_bool_reloption(local_relopts *relopts, const char *name,
add_local_reloption(relopts, (relopt_gen *) newoption, offset);
}
+/*
+ * init_ternary_reloption
+ * Allocate and initialize a new ternary reloption
+ */
+static relopt_ternary *
+init_ternary_reloption(bits32 kinds, const char *name, const char *desc,
+ pg_ternary default_val, LOCKMODE lockmode)
+{
+ relopt_ternary *newoption;
+
+ newoption = (relopt_ternary *)
+ allocate_reloption(kinds, RELOPT_TYPE_TERNARY, name, desc, lockmode);
+ newoption->default_val = default_val;
+
+ return newoption;
+}
+
+/*
+ * add_ternary_reloption
+ * Add a new ternary reloption
+ */
+void
+add_ternary_reloption(bits32 kinds, const char *name, const char *desc,
+ pg_ternary default_val, LOCKMODE lockmode)
+{
+ relopt_ternary *newoption;
+
+ newoption =
+ init_ternary_reloption(kinds, name, desc, default_val, lockmode);
+
+ add_reloption((relopt_gen *) newoption);
+}
+
+/*
+ * add_local_ternary_reloption
+ * Add a new ternary local reloption
+ *
+ * 'offset' is offset of ternary-typed field.
+ */
+void
+add_local_ternary_reloption(local_relopts *relopts, const char *name,
+ const char *desc, pg_ternary default_val,
+ int offset)
+{
+ relopt_ternary *newoption;
+
+ newoption =
+ init_ternary_reloption(RELOPT_KIND_LOCAL, name, desc, default_val, 0);
+
+ add_local_reloption(relopts, (relopt_gen *) newoption, offset);
+}
/*
* init_real_reloption
@@ -1626,6 +1705,19 @@ parse_one_reloption(relopt_value *option, char *text_str, int text_len,
option->gen->name, value)));
}
break;
+ case RELOPT_TYPE_TERNARY:
+ {
+ bool b;
+
+ parsed = parse_bool(value, &b);
+ option->values.ternary_val = b ? TERNARY_TRUE : TERNARY_FALSE;
+ if (validate && !parsed)
+ ereport(ERROR,
+ (errcode(ERRCODE_INVALID_PARAMETER_VALUE),
+ errmsg("invalid value for ternary option \"%s\": %s",
+ option->gen->name, value)));
+ }
+ break;
case RELOPT_TYPE_INT:
{
relopt_int *optint = (relopt_int *) option->gen;
@@ -1789,17 +1881,6 @@ fillRelOptions(void *rdopts, Size basesize,
char *itempos = ((char *) rdopts) + elems[j].offset;
char *string_val;
- /*
- * If isset_offset is provided, store whether the reloption is
- * set there.
- */
- if (elems[j].isset_offset > 0)
- {
- char *setpos = ((char *) rdopts) + elems[j].isset_offset;
-
- *(bool *) setpos = options[i].isset;
- }
-
switch (options[i].gen->type)
{
case RELOPT_TYPE_BOOL:
@@ -1807,6 +1888,11 @@ fillRelOptions(void *rdopts, Size basesize,
options[i].values.bool_val :
((relopt_bool *) options[i].gen)->default_val;
break;
+ case RELOPT_TYPE_TERNARY:
+ *(pg_ternary *) itempos = options[i].isset ?
+ options[i].values.ternary_val :
+ ((relopt_ternary *) options[i].gen)->default_val;
+ break;
case RELOPT_TYPE_INT:
*(int *) itempos = options[i].isset ?
options[i].values.int_val :
@@ -1923,8 +2009,8 @@ default_reloptions(Datum reloptions, bool validate, relopt_kind kind)
offsetof(StdRdOptions, parallel_workers)},
{"vacuum_index_cleanup", RELOPT_TYPE_ENUM,
offsetof(StdRdOptions, vacuum_index_cleanup)},
- {"vacuum_truncate", RELOPT_TYPE_BOOL,
- offsetof(StdRdOptions, vacuum_truncate), offsetof(StdRdOptions, vacuum_truncate_set)},
+ {"vacuum_truncate", RELOPT_TYPE_TERNARY,
+ offsetof(StdRdOptions, vacuum_truncate)},
{"vacuum_max_eager_freeze_failure_rate", RELOPT_TYPE_REAL,
offsetof(StdRdOptions, vacuum_max_eager_freeze_failure_rate)}
};
@@ -2004,7 +2090,6 @@ build_local_reloptions(local_relopts *relopts, Datum options, bool validate)
elems[i].optname = opt->option->name;
elems[i].opttype = opt->option->type;
elems[i].offset = opt->offset;
- elems[i].isset_offset = 0; /* not supported for local relopts yet */
i++;
}
diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c
index aa4fbec143f..696eab9bd97 100644
--- a/src/backend/commands/vacuum.c
+++ b/src/backend/commands/vacuum.c
@@ -2224,9 +2224,9 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams params,
{
StdRdOptions *opts = (StdRdOptions *) rel->rd_options;
- if (opts && opts->vacuum_truncate_set)
+ if (opts && opts->vacuum_truncate != TERNARY_UNSET)
{
- if (opts->vacuum_truncate)
+ if (opts->vacuum_truncate == TERNARY_TRUE)
params.truncate = VACOPTVALUE_ENABLED;
else
params.truncate = VACOPTVALUE_DISABLED;
diff --git a/src/include/access/reloptions.h b/src/include/access/reloptions.h
index 2f08e1b0cf0..dfbef2babf2 100644
--- a/src/include/access/reloptions.h
+++ b/src/include/access/reloptions.h
@@ -29,6 +29,7 @@
typedef enum relopt_type
{
RELOPT_TYPE_BOOL,
+ RELOPT_TYPE_TERNARY, /* on, off, unset */
RELOPT_TYPE_INT,
RELOPT_TYPE_REAL,
RELOPT_TYPE_ENUM,
@@ -80,6 +81,7 @@ typedef struct relopt_value
union
{
bool bool_val;
+ pg_ternary ternary_val;
int int_val;
double real_val;
int enum_val;
@@ -94,6 +96,12 @@ typedef struct relopt_bool
bool default_val;
} relopt_bool;
+typedef struct relopt_ternary
+{
+ relopt_gen gen;
+ int default_val;
+} relopt_ternary;
+
typedef struct relopt_int
{
relopt_gen gen;
@@ -152,19 +160,6 @@ typedef struct
const char *optname; /* option's name */
relopt_type opttype; /* option's datatype */
int offset; /* offset of field in result struct */
-
- /*
- * isset_offset is an optional offset of a field in the result struct that
- * stores whether the option is explicitly set for the relation or if it
- * just picked up the default value. In most cases, this can be
- * accomplished by giving the reloption a special out-of-range default
- * value (e.g., some integer reloptions use -2), but this isn't always
- * possible. For example, a Boolean reloption cannot be given an
- * out-of-range default, so we need another way to discover the source of
- * its value. This offset is only used if given a value greater than
- * zero.
- */
- int isset_offset;
} relopt_parse_elt;
/* Local reloption definition */
@@ -195,6 +190,9 @@ typedef struct local_relopts
extern relopt_kind add_reloption_kind(void);
extern void add_bool_reloption(bits32 kinds, const char *name, const char *desc,
bool default_val, LOCKMODE lockmode);
+extern void add_ternary_reloption(bits32 kinds, const char *name,
+ const char *desc, pg_ternary default_val,
+ LOCKMODE lockmode);
extern void add_int_reloption(bits32 kinds, const char *name, const char *desc,
int default_val, int min_val, int max_val,
LOCKMODE lockmode);
@@ -214,6 +212,9 @@ extern void register_reloptions_validator(local_relopts *relopts,
extern void add_local_bool_reloption(local_relopts *relopts, const char *name,
const char *desc, bool default_val,
int offset);
+extern void add_local_ternary_reloption(local_relopts *relopts,
+ const char *name, const char *desc,
+ pg_ternary default_val, int offset);
extern void add_local_int_reloption(local_relopts *relopts, const char *name,
const char *desc, int default_val,
int min_val, int max_val, int offset);
diff --git a/src/include/postgres.h b/src/include/postgres.h
index 7d93fbce709..47c88780776 100644
--- a/src/include/postgres.h
+++ b/src/include/postgres.h
@@ -543,6 +543,21 @@ Float8GetDatum(float8 X)
* ----------------------------------------------------------------
*/
+/*
+ * pg_ternary
+ * Boolean value with an extra "unset" value
+ *
+ * This enum can be used for values that want to distinguish between true,
+ * false, and unset.
+*/
+
+typedef enum pg_ternary
+{
+ TERNARY_FALSE = 0,
+ TERNARY_TRUE = 1,
+ TERNARY_UNSET = -1
+} pg_ternary;
+
/*
* NON_EXEC_STATIC: It's sometimes useful to define a variable or function
* that is normally static but extern when using EXEC_BACKEND (see
diff --git a/src/include/utils/rel.h b/src/include/utils/rel.h
index d03ab247788..5feb18a5373 100644
--- a/src/include/utils/rel.h
+++ b/src/include/utils/rel.h
@@ -347,8 +347,7 @@ typedef struct StdRdOptions
bool user_catalog_table; /* use as an additional catalog relation */
int parallel_workers; /* max number of parallel workers */
StdRdOptIndexCleanup vacuum_index_cleanup; /* controls index vacuuming */
- bool vacuum_truncate; /* enables vacuum to truncate a relation */
- bool vacuum_truncate_set; /* whether vacuum_truncate is set */
+ pg_ternary vacuum_truncate; /* enables vacuum to truncate a relation */
/*
* Fraction of pages in a relation that vacuum can eagerly scan and fail
diff --git a/src/test/regress/expected/reloptions.out b/src/test/regress/expected/reloptions.out
index 9de19b4e3f1..1c99f79ab01 100644
--- a/src/test/regress/expected/reloptions.out
+++ b/src/test/regress/expected/reloptions.out
@@ -98,6 +98,42 @@ SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass;
{fillfactor=13,autovacuum_enabled=false}
(1 row)
+-- Tests for future (FIXME) ternary options
+-- behave as boolean option: accept unassigned name and truncated value
+DROP TABLE reloptions_test;
+CREATE TABLE reloptions_test(i INT) WITH (vacuum_truncate);
+SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass;
+ reloptions
+------------------------
+ {vacuum_truncate=true}
+(1 row)
+
+DROP TABLE reloptions_test;
+CREATE TABLE reloptions_test(i INT) WITH (vacuum_truncate=FaLS);
+SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass;
+ reloptions
+------------------------
+ {vacuum_truncate=fals}
+(1 row)
+
+-- preferred "true" alias is used when storing
+DROP TABLE reloptions_test;
+CREATE TABLE reloptions_test(i INT) WITH (vacuum_index_cleanup=on);
+SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass;
+ reloptions
+---------------------------
+ {vacuum_index_cleanup=on}
+(1 row)
+
+-- custom "third" value is available
+DROP TABLE reloptions_test;
+CREATE TABLE reloptions_test(i INT) WITH (vacuum_index_cleanup=auto);
+SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass;
+ reloptions
+-----------------------------
+ {vacuum_index_cleanup=auto}
+(1 row)
+
-- Test vacuum_truncate option
DROP TABLE reloptions_test;
CREATE TEMP TABLE reloptions_test(i INT NOT NULL, j text)
diff --git a/src/test/regress/sql/reloptions.sql b/src/test/regress/sql/reloptions.sql
index 24fbe0b478d..f5980dafcbc 100644
--- a/src/test/regress/sql/reloptions.sql
+++ b/src/test/regress/sql/reloptions.sql
@@ -59,6 +59,27 @@ UPDATE pg_class
ALTER TABLE reloptions_test RESET (illegal_option);
SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass;
+-- Tests for future (FIXME) ternary options
+
+-- behave as boolean option: accept unassigned name and truncated value
+DROP TABLE reloptions_test;
+CREATE TABLE reloptions_test(i INT) WITH (vacuum_truncate);
+SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass;
+
+DROP TABLE reloptions_test;
+CREATE TABLE reloptions_test(i INT) WITH (vacuum_truncate=FaLS);
+SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass;
+
+-- preferred "true" alias is used when storing
+DROP TABLE reloptions_test;
+CREATE TABLE reloptions_test(i INT) WITH (vacuum_index_cleanup=on);
+SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass;
+
+-- custom "third" value is available
+DROP TABLE reloptions_test;
+CREATE TABLE reloptions_test(i INT) WITH (vacuum_index_cleanup=auto);
+SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass;
+
-- Test vacuum_truncate option
DROP TABLE reloptions_test;
diff --git a/src/tools/pgindent/typedefs.list b/src/tools/pgindent/typedefs.list
index 3f3a888fd0e..1c8610fd46c 100644
--- a/src/tools/pgindent/typedefs.list
+++ b/src/tools/pgindent/typedefs.list
@@ -3952,6 +3952,7 @@ pg_sha512_ctx
pg_snapshot
pg_special_case
pg_stack_base_t
+pg_ternary
pg_time_t
pg_time_usec_t
pg_tz
@@ -4079,6 +4080,7 @@ relopt_kind
relopt_parse_elt
relopt_real
relopt_string
+relopt_ternary
relopt_type
relopt_value
relopts_validator
--
2.47.3
--c36t3fndxhkzbl5z--
^ permalink raw reply [nested|flat] 868+ messages in thread
* [PATCH v3] Introduce ternary reloptions
@ 2026-01-16 15:04 Álvaro Herrera <[email protected]>
0 siblings, 0 replies; 868+ messages in thread
From: Álvaro Herrera @ 2026-01-16 15:04 UTC (permalink / raw)
Introduce ternary reloption as a replacement for current
`vacuum_truncate` implementation. Remove the `vacuum_truncate_set`
separate flag and use TERNARY_UNSET instead.
This could also be used for other options such as `vacuum_index_cleanup`
and `buffering`, but lets get the scaffolding in first.
Discussion: https://postgr.es/m/3474141.usfYGdeWWP@thinkpad-pgpro
---
src/backend/access/common/reloptions.c | 137 ++++++++++++++++++-----
src/backend/commands/vacuum.c | 4 +-
src/include/access/reloptions.h | 27 ++---
src/include/postgres.h | 15 +++
src/include/utils/rel.h | 3 +-
src/test/regress/expected/reloptions.out | 36 ++++++
src/test/regress/sql/reloptions.sql | 21 ++++
src/tools/pgindent/typedefs.list | 2 +
8 files changed, 202 insertions(+), 43 deletions(-)
diff --git a/src/backend/access/common/reloptions.c b/src/backend/access/common/reloptions.c
index 0b83f98ed5f..6f1f577581a 100644
--- a/src/backend/access/common/reloptions.c
+++ b/src/backend/access/common/reloptions.c
@@ -40,9 +40,9 @@
*
* To add an option:
*
- * (i) decide on a type (bool, integer, real, enum, string), name, default
- * value, upper and lower bounds (if applicable); for strings, consider a
- * validation routine.
+ * (i) decide on a type (bool, ternary, integer, real, enum, string), name,
+ * default value, upper and lower bounds (if applicable); for strings,
+ * consider a validation routine.
* (ii) add a record below (or use add_<type>_reloption).
* (iii) add it to the appropriate options struct (perhaps StdRdOptions)
* (iv) add it to the appropriate handling routine (perhaps
@@ -147,15 +147,6 @@ static relopt_bool boolRelOpts[] =
},
false
},
- {
- {
- "vacuum_truncate",
- "Enables vacuum to truncate empty pages at the end of this table",
- RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
- ShareUpdateExclusiveLock
- },
- true
- },
{
{
"deduplicate_items",
@@ -170,6 +161,25 @@ static relopt_bool boolRelOpts[] =
{{NULL}}
};
+static relopt_ternary ternaryRelOpts[] =
+{
+ {
+ {
+ "vacuum_truncate",
+ "Enables vacuum to truncate empty pages at the end of this table",
+ RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
+ ShareUpdateExclusiveLock
+ },
+ TERNARY_UNSET
+ },
+ /* list terminator */
+ {
+ {
+ NULL
+ }
+ }
+};
+
static relopt_int intRelOpts[] =
{
{
@@ -609,6 +619,13 @@ initialize_reloptions(void)
boolRelOpts[i].gen.lockmode));
j++;
}
+ for (i = 0; ternaryRelOpts[i].gen.name; i++)
+ {
+ Assert(DoLockModesConflict(ternaryRelOpts[i].gen.lockmode,
+ ternaryRelOpts[i].gen.lockmode));
+ j++;
+ }
+
for (i = 0; intRelOpts[i].gen.name; i++)
{
Assert(DoLockModesConflict(intRelOpts[i].gen.lockmode,
@@ -649,6 +666,14 @@ initialize_reloptions(void)
j++;
}
+ for (i = 0; ternaryRelOpts[i].gen.name; i++)
+ {
+ relOpts[j] = &ternaryRelOpts[i].gen;
+ relOpts[j]->type = RELOPT_TYPE_TERNARY;
+ relOpts[j]->namelen = strlen(relOpts[j]->name);
+ j++;
+ }
+
for (i = 0; intRelOpts[i].gen.name; i++)
{
relOpts[j] = &intRelOpts[i].gen;
@@ -809,6 +834,9 @@ allocate_reloption(bits32 kinds, int type, const char *name, const char *desc,
case RELOPT_TYPE_BOOL:
size = sizeof(relopt_bool);
break;
+ case RELOPT_TYPE_TERNARY:
+ size = sizeof(relopt_ternary);
+ break;
case RELOPT_TYPE_INT:
size = sizeof(relopt_int);
break;
@@ -892,6 +920,57 @@ add_local_bool_reloption(local_relopts *relopts, const char *name,
add_local_reloption(relopts, (relopt_gen *) newoption, offset);
}
+/*
+ * init_ternary_reloption
+ * Allocate and initialize a new ternary reloption
+ */
+static relopt_ternary *
+init_ternary_reloption(bits32 kinds, const char *name, const char *desc,
+ pg_ternary default_val, LOCKMODE lockmode)
+{
+ relopt_ternary *newoption;
+
+ newoption = (relopt_ternary *)
+ allocate_reloption(kinds, RELOPT_TYPE_TERNARY, name, desc, lockmode);
+ newoption->default_val = default_val;
+
+ return newoption;
+}
+
+/*
+ * add_ternary_reloption
+ * Add a new ternary reloption
+ */
+void
+add_ternary_reloption(bits32 kinds, const char *name, const char *desc,
+ pg_ternary default_val, LOCKMODE lockmode)
+{
+ relopt_ternary *newoption;
+
+ newoption =
+ init_ternary_reloption(kinds, name, desc, default_val, lockmode);
+
+ add_reloption((relopt_gen *) newoption);
+}
+
+/*
+ * add_local_ternary_reloption
+ * Add a new ternary local reloption
+ *
+ * 'offset' is offset of ternary-typed field.
+ */
+void
+add_local_ternary_reloption(local_relopts *relopts, const char *name,
+ const char *desc, pg_ternary default_val,
+ int offset)
+{
+ relopt_ternary *newoption;
+
+ newoption =
+ init_ternary_reloption(RELOPT_KIND_LOCAL, name, desc, default_val, 0);
+
+ add_local_reloption(relopts, (relopt_gen *) newoption, offset);
+}
/*
* init_real_reloption
@@ -1626,6 +1705,19 @@ parse_one_reloption(relopt_value *option, char *text_str, int text_len,
option->gen->name, value)));
}
break;
+ case RELOPT_TYPE_TERNARY:
+ {
+ bool b;
+
+ parsed = parse_bool(value, &b);
+ option->values.ternary_val = b ? TERNARY_TRUE : TERNARY_FALSE;
+ if (validate && !parsed)
+ ereport(ERROR,
+ (errcode(ERRCODE_INVALID_PARAMETER_VALUE),
+ errmsg("invalid value for ternary option \"%s\": %s",
+ option->gen->name, value)));
+ }
+ break;
case RELOPT_TYPE_INT:
{
relopt_int *optint = (relopt_int *) option->gen;
@@ -1789,17 +1881,6 @@ fillRelOptions(void *rdopts, Size basesize,
char *itempos = ((char *) rdopts) + elems[j].offset;
char *string_val;
- /*
- * If isset_offset is provided, store whether the reloption is
- * set there.
- */
- if (elems[j].isset_offset > 0)
- {
- char *setpos = ((char *) rdopts) + elems[j].isset_offset;
-
- *(bool *) setpos = options[i].isset;
- }
-
switch (options[i].gen->type)
{
case RELOPT_TYPE_BOOL:
@@ -1807,6 +1888,11 @@ fillRelOptions(void *rdopts, Size basesize,
options[i].values.bool_val :
((relopt_bool *) options[i].gen)->default_val;
break;
+ case RELOPT_TYPE_TERNARY:
+ *(pg_ternary *) itempos = options[i].isset ?
+ options[i].values.ternary_val :
+ ((relopt_ternary *) options[i].gen)->default_val;
+ break;
case RELOPT_TYPE_INT:
*(int *) itempos = options[i].isset ?
options[i].values.int_val :
@@ -1923,8 +2009,8 @@ default_reloptions(Datum reloptions, bool validate, relopt_kind kind)
offsetof(StdRdOptions, parallel_workers)},
{"vacuum_index_cleanup", RELOPT_TYPE_ENUM,
offsetof(StdRdOptions, vacuum_index_cleanup)},
- {"vacuum_truncate", RELOPT_TYPE_BOOL,
- offsetof(StdRdOptions, vacuum_truncate), offsetof(StdRdOptions, vacuum_truncate_set)},
+ {"vacuum_truncate", RELOPT_TYPE_TERNARY,
+ offsetof(StdRdOptions, vacuum_truncate)},
{"vacuum_max_eager_freeze_failure_rate", RELOPT_TYPE_REAL,
offsetof(StdRdOptions, vacuum_max_eager_freeze_failure_rate)}
};
@@ -2004,7 +2090,6 @@ build_local_reloptions(local_relopts *relopts, Datum options, bool validate)
elems[i].optname = opt->option->name;
elems[i].opttype = opt->option->type;
elems[i].offset = opt->offset;
- elems[i].isset_offset = 0; /* not supported for local relopts yet */
i++;
}
diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c
index aa4fbec143f..696eab9bd97 100644
--- a/src/backend/commands/vacuum.c
+++ b/src/backend/commands/vacuum.c
@@ -2224,9 +2224,9 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams params,
{
StdRdOptions *opts = (StdRdOptions *) rel->rd_options;
- if (opts && opts->vacuum_truncate_set)
+ if (opts && opts->vacuum_truncate != TERNARY_UNSET)
{
- if (opts->vacuum_truncate)
+ if (opts->vacuum_truncate == TERNARY_TRUE)
params.truncate = VACOPTVALUE_ENABLED;
else
params.truncate = VACOPTVALUE_DISABLED;
diff --git a/src/include/access/reloptions.h b/src/include/access/reloptions.h
index 2f08e1b0cf0..dfbef2babf2 100644
--- a/src/include/access/reloptions.h
+++ b/src/include/access/reloptions.h
@@ -29,6 +29,7 @@
typedef enum relopt_type
{
RELOPT_TYPE_BOOL,
+ RELOPT_TYPE_TERNARY, /* on, off, unset */
RELOPT_TYPE_INT,
RELOPT_TYPE_REAL,
RELOPT_TYPE_ENUM,
@@ -80,6 +81,7 @@ typedef struct relopt_value
union
{
bool bool_val;
+ pg_ternary ternary_val;
int int_val;
double real_val;
int enum_val;
@@ -94,6 +96,12 @@ typedef struct relopt_bool
bool default_val;
} relopt_bool;
+typedef struct relopt_ternary
+{
+ relopt_gen gen;
+ int default_val;
+} relopt_ternary;
+
typedef struct relopt_int
{
relopt_gen gen;
@@ -152,19 +160,6 @@ typedef struct
const char *optname; /* option's name */
relopt_type opttype; /* option's datatype */
int offset; /* offset of field in result struct */
-
- /*
- * isset_offset is an optional offset of a field in the result struct that
- * stores whether the option is explicitly set for the relation or if it
- * just picked up the default value. In most cases, this can be
- * accomplished by giving the reloption a special out-of-range default
- * value (e.g., some integer reloptions use -2), but this isn't always
- * possible. For example, a Boolean reloption cannot be given an
- * out-of-range default, so we need another way to discover the source of
- * its value. This offset is only used if given a value greater than
- * zero.
- */
- int isset_offset;
} relopt_parse_elt;
/* Local reloption definition */
@@ -195,6 +190,9 @@ typedef struct local_relopts
extern relopt_kind add_reloption_kind(void);
extern void add_bool_reloption(bits32 kinds, const char *name, const char *desc,
bool default_val, LOCKMODE lockmode);
+extern void add_ternary_reloption(bits32 kinds, const char *name,
+ const char *desc, pg_ternary default_val,
+ LOCKMODE lockmode);
extern void add_int_reloption(bits32 kinds, const char *name, const char *desc,
int default_val, int min_val, int max_val,
LOCKMODE lockmode);
@@ -214,6 +212,9 @@ extern void register_reloptions_validator(local_relopts *relopts,
extern void add_local_bool_reloption(local_relopts *relopts, const char *name,
const char *desc, bool default_val,
int offset);
+extern void add_local_ternary_reloption(local_relopts *relopts,
+ const char *name, const char *desc,
+ pg_ternary default_val, int offset);
extern void add_local_int_reloption(local_relopts *relopts, const char *name,
const char *desc, int default_val,
int min_val, int max_val, int offset);
diff --git a/src/include/postgres.h b/src/include/postgres.h
index 7d93fbce709..47c88780776 100644
--- a/src/include/postgres.h
+++ b/src/include/postgres.h
@@ -543,6 +543,21 @@ Float8GetDatum(float8 X)
* ----------------------------------------------------------------
*/
+/*
+ * pg_ternary
+ * Boolean value with an extra "unset" value
+ *
+ * This enum can be used for values that want to distinguish between true,
+ * false, and unset.
+*/
+
+typedef enum pg_ternary
+{
+ TERNARY_FALSE = 0,
+ TERNARY_TRUE = 1,
+ TERNARY_UNSET = -1
+} pg_ternary;
+
/*
* NON_EXEC_STATIC: It's sometimes useful to define a variable or function
* that is normally static but extern when using EXEC_BACKEND (see
diff --git a/src/include/utils/rel.h b/src/include/utils/rel.h
index d03ab247788..5feb18a5373 100644
--- a/src/include/utils/rel.h
+++ b/src/include/utils/rel.h
@@ -347,8 +347,7 @@ typedef struct StdRdOptions
bool user_catalog_table; /* use as an additional catalog relation */
int parallel_workers; /* max number of parallel workers */
StdRdOptIndexCleanup vacuum_index_cleanup; /* controls index vacuuming */
- bool vacuum_truncate; /* enables vacuum to truncate a relation */
- bool vacuum_truncate_set; /* whether vacuum_truncate is set */
+ pg_ternary vacuum_truncate; /* enables vacuum to truncate a relation */
/*
* Fraction of pages in a relation that vacuum can eagerly scan and fail
diff --git a/src/test/regress/expected/reloptions.out b/src/test/regress/expected/reloptions.out
index 9de19b4e3f1..1c99f79ab01 100644
--- a/src/test/regress/expected/reloptions.out
+++ b/src/test/regress/expected/reloptions.out
@@ -98,6 +98,42 @@ SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass;
{fillfactor=13,autovacuum_enabled=false}
(1 row)
+-- Tests for future (FIXME) ternary options
+-- behave as boolean option: accept unassigned name and truncated value
+DROP TABLE reloptions_test;
+CREATE TABLE reloptions_test(i INT) WITH (vacuum_truncate);
+SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass;
+ reloptions
+------------------------
+ {vacuum_truncate=true}
+(1 row)
+
+DROP TABLE reloptions_test;
+CREATE TABLE reloptions_test(i INT) WITH (vacuum_truncate=FaLS);
+SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass;
+ reloptions
+------------------------
+ {vacuum_truncate=fals}
+(1 row)
+
+-- preferred "true" alias is used when storing
+DROP TABLE reloptions_test;
+CREATE TABLE reloptions_test(i INT) WITH (vacuum_index_cleanup=on);
+SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass;
+ reloptions
+---------------------------
+ {vacuum_index_cleanup=on}
+(1 row)
+
+-- custom "third" value is available
+DROP TABLE reloptions_test;
+CREATE TABLE reloptions_test(i INT) WITH (vacuum_index_cleanup=auto);
+SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass;
+ reloptions
+-----------------------------
+ {vacuum_index_cleanup=auto}
+(1 row)
+
-- Test vacuum_truncate option
DROP TABLE reloptions_test;
CREATE TEMP TABLE reloptions_test(i INT NOT NULL, j text)
diff --git a/src/test/regress/sql/reloptions.sql b/src/test/regress/sql/reloptions.sql
index 24fbe0b478d..f5980dafcbc 100644
--- a/src/test/regress/sql/reloptions.sql
+++ b/src/test/regress/sql/reloptions.sql
@@ -59,6 +59,27 @@ UPDATE pg_class
ALTER TABLE reloptions_test RESET (illegal_option);
SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass;
+-- Tests for future (FIXME) ternary options
+
+-- behave as boolean option: accept unassigned name and truncated value
+DROP TABLE reloptions_test;
+CREATE TABLE reloptions_test(i INT) WITH (vacuum_truncate);
+SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass;
+
+DROP TABLE reloptions_test;
+CREATE TABLE reloptions_test(i INT) WITH (vacuum_truncate=FaLS);
+SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass;
+
+-- preferred "true" alias is used when storing
+DROP TABLE reloptions_test;
+CREATE TABLE reloptions_test(i INT) WITH (vacuum_index_cleanup=on);
+SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass;
+
+-- custom "third" value is available
+DROP TABLE reloptions_test;
+CREATE TABLE reloptions_test(i INT) WITH (vacuum_index_cleanup=auto);
+SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass;
+
-- Test vacuum_truncate option
DROP TABLE reloptions_test;
diff --git a/src/tools/pgindent/typedefs.list b/src/tools/pgindent/typedefs.list
index 3f3a888fd0e..1c8610fd46c 100644
--- a/src/tools/pgindent/typedefs.list
+++ b/src/tools/pgindent/typedefs.list
@@ -3952,6 +3952,7 @@ pg_sha512_ctx
pg_snapshot
pg_special_case
pg_stack_base_t
+pg_ternary
pg_time_t
pg_time_usec_t
pg_tz
@@ -4079,6 +4080,7 @@ relopt_kind
relopt_parse_elt
relopt_real
relopt_string
+relopt_ternary
relopt_type
relopt_value
relopts_validator
--
2.47.3
--c36t3fndxhkzbl5z--
^ permalink raw reply [nested|flat] 868+ messages in thread
* [PATCH v3] Introduce ternary reloptions
@ 2026-01-16 15:04 Álvaro Herrera <[email protected]>
0 siblings, 0 replies; 868+ messages in thread
From: Álvaro Herrera @ 2026-01-16 15:04 UTC (permalink / raw)
Introduce ternary reloption as a replacement for current
`vacuum_truncate` implementation. Remove the `vacuum_truncate_set`
separate flag and use TERNARY_UNSET instead.
This could also be used for other options such as `vacuum_index_cleanup`
and `buffering`, but lets get the scaffolding in first.
Discussion: https://postgr.es/m/3474141.usfYGdeWWP@thinkpad-pgpro
---
src/backend/access/common/reloptions.c | 137 ++++++++++++++++++-----
src/backend/commands/vacuum.c | 4 +-
src/include/access/reloptions.h | 27 ++---
src/include/postgres.h | 15 +++
src/include/utils/rel.h | 3 +-
src/test/regress/expected/reloptions.out | 36 ++++++
src/test/regress/sql/reloptions.sql | 21 ++++
src/tools/pgindent/typedefs.list | 2 +
8 files changed, 202 insertions(+), 43 deletions(-)
diff --git a/src/backend/access/common/reloptions.c b/src/backend/access/common/reloptions.c
index 0b83f98ed5f..6f1f577581a 100644
--- a/src/backend/access/common/reloptions.c
+++ b/src/backend/access/common/reloptions.c
@@ -40,9 +40,9 @@
*
* To add an option:
*
- * (i) decide on a type (bool, integer, real, enum, string), name, default
- * value, upper and lower bounds (if applicable); for strings, consider a
- * validation routine.
+ * (i) decide on a type (bool, ternary, integer, real, enum, string), name,
+ * default value, upper and lower bounds (if applicable); for strings,
+ * consider a validation routine.
* (ii) add a record below (or use add_<type>_reloption).
* (iii) add it to the appropriate options struct (perhaps StdRdOptions)
* (iv) add it to the appropriate handling routine (perhaps
@@ -147,15 +147,6 @@ static relopt_bool boolRelOpts[] =
},
false
},
- {
- {
- "vacuum_truncate",
- "Enables vacuum to truncate empty pages at the end of this table",
- RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
- ShareUpdateExclusiveLock
- },
- true
- },
{
{
"deduplicate_items",
@@ -170,6 +161,25 @@ static relopt_bool boolRelOpts[] =
{{NULL}}
};
+static relopt_ternary ternaryRelOpts[] =
+{
+ {
+ {
+ "vacuum_truncate",
+ "Enables vacuum to truncate empty pages at the end of this table",
+ RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
+ ShareUpdateExclusiveLock
+ },
+ TERNARY_UNSET
+ },
+ /* list terminator */
+ {
+ {
+ NULL
+ }
+ }
+};
+
static relopt_int intRelOpts[] =
{
{
@@ -609,6 +619,13 @@ initialize_reloptions(void)
boolRelOpts[i].gen.lockmode));
j++;
}
+ for (i = 0; ternaryRelOpts[i].gen.name; i++)
+ {
+ Assert(DoLockModesConflict(ternaryRelOpts[i].gen.lockmode,
+ ternaryRelOpts[i].gen.lockmode));
+ j++;
+ }
+
for (i = 0; intRelOpts[i].gen.name; i++)
{
Assert(DoLockModesConflict(intRelOpts[i].gen.lockmode,
@@ -649,6 +666,14 @@ initialize_reloptions(void)
j++;
}
+ for (i = 0; ternaryRelOpts[i].gen.name; i++)
+ {
+ relOpts[j] = &ternaryRelOpts[i].gen;
+ relOpts[j]->type = RELOPT_TYPE_TERNARY;
+ relOpts[j]->namelen = strlen(relOpts[j]->name);
+ j++;
+ }
+
for (i = 0; intRelOpts[i].gen.name; i++)
{
relOpts[j] = &intRelOpts[i].gen;
@@ -809,6 +834,9 @@ allocate_reloption(bits32 kinds, int type, const char *name, const char *desc,
case RELOPT_TYPE_BOOL:
size = sizeof(relopt_bool);
break;
+ case RELOPT_TYPE_TERNARY:
+ size = sizeof(relopt_ternary);
+ break;
case RELOPT_TYPE_INT:
size = sizeof(relopt_int);
break;
@@ -892,6 +920,57 @@ add_local_bool_reloption(local_relopts *relopts, const char *name,
add_local_reloption(relopts, (relopt_gen *) newoption, offset);
}
+/*
+ * init_ternary_reloption
+ * Allocate and initialize a new ternary reloption
+ */
+static relopt_ternary *
+init_ternary_reloption(bits32 kinds, const char *name, const char *desc,
+ pg_ternary default_val, LOCKMODE lockmode)
+{
+ relopt_ternary *newoption;
+
+ newoption = (relopt_ternary *)
+ allocate_reloption(kinds, RELOPT_TYPE_TERNARY, name, desc, lockmode);
+ newoption->default_val = default_val;
+
+ return newoption;
+}
+
+/*
+ * add_ternary_reloption
+ * Add a new ternary reloption
+ */
+void
+add_ternary_reloption(bits32 kinds, const char *name, const char *desc,
+ pg_ternary default_val, LOCKMODE lockmode)
+{
+ relopt_ternary *newoption;
+
+ newoption =
+ init_ternary_reloption(kinds, name, desc, default_val, lockmode);
+
+ add_reloption((relopt_gen *) newoption);
+}
+
+/*
+ * add_local_ternary_reloption
+ * Add a new ternary local reloption
+ *
+ * 'offset' is offset of ternary-typed field.
+ */
+void
+add_local_ternary_reloption(local_relopts *relopts, const char *name,
+ const char *desc, pg_ternary default_val,
+ int offset)
+{
+ relopt_ternary *newoption;
+
+ newoption =
+ init_ternary_reloption(RELOPT_KIND_LOCAL, name, desc, default_val, 0);
+
+ add_local_reloption(relopts, (relopt_gen *) newoption, offset);
+}
/*
* init_real_reloption
@@ -1626,6 +1705,19 @@ parse_one_reloption(relopt_value *option, char *text_str, int text_len,
option->gen->name, value)));
}
break;
+ case RELOPT_TYPE_TERNARY:
+ {
+ bool b;
+
+ parsed = parse_bool(value, &b);
+ option->values.ternary_val = b ? TERNARY_TRUE : TERNARY_FALSE;
+ if (validate && !parsed)
+ ereport(ERROR,
+ (errcode(ERRCODE_INVALID_PARAMETER_VALUE),
+ errmsg("invalid value for ternary option \"%s\": %s",
+ option->gen->name, value)));
+ }
+ break;
case RELOPT_TYPE_INT:
{
relopt_int *optint = (relopt_int *) option->gen;
@@ -1789,17 +1881,6 @@ fillRelOptions(void *rdopts, Size basesize,
char *itempos = ((char *) rdopts) + elems[j].offset;
char *string_val;
- /*
- * If isset_offset is provided, store whether the reloption is
- * set there.
- */
- if (elems[j].isset_offset > 0)
- {
- char *setpos = ((char *) rdopts) + elems[j].isset_offset;
-
- *(bool *) setpos = options[i].isset;
- }
-
switch (options[i].gen->type)
{
case RELOPT_TYPE_BOOL:
@@ -1807,6 +1888,11 @@ fillRelOptions(void *rdopts, Size basesize,
options[i].values.bool_val :
((relopt_bool *) options[i].gen)->default_val;
break;
+ case RELOPT_TYPE_TERNARY:
+ *(pg_ternary *) itempos = options[i].isset ?
+ options[i].values.ternary_val :
+ ((relopt_ternary *) options[i].gen)->default_val;
+ break;
case RELOPT_TYPE_INT:
*(int *) itempos = options[i].isset ?
options[i].values.int_val :
@@ -1923,8 +2009,8 @@ default_reloptions(Datum reloptions, bool validate, relopt_kind kind)
offsetof(StdRdOptions, parallel_workers)},
{"vacuum_index_cleanup", RELOPT_TYPE_ENUM,
offsetof(StdRdOptions, vacuum_index_cleanup)},
- {"vacuum_truncate", RELOPT_TYPE_BOOL,
- offsetof(StdRdOptions, vacuum_truncate), offsetof(StdRdOptions, vacuum_truncate_set)},
+ {"vacuum_truncate", RELOPT_TYPE_TERNARY,
+ offsetof(StdRdOptions, vacuum_truncate)},
{"vacuum_max_eager_freeze_failure_rate", RELOPT_TYPE_REAL,
offsetof(StdRdOptions, vacuum_max_eager_freeze_failure_rate)}
};
@@ -2004,7 +2090,6 @@ build_local_reloptions(local_relopts *relopts, Datum options, bool validate)
elems[i].optname = opt->option->name;
elems[i].opttype = opt->option->type;
elems[i].offset = opt->offset;
- elems[i].isset_offset = 0; /* not supported for local relopts yet */
i++;
}
diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c
index aa4fbec143f..696eab9bd97 100644
--- a/src/backend/commands/vacuum.c
+++ b/src/backend/commands/vacuum.c
@@ -2224,9 +2224,9 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams params,
{
StdRdOptions *opts = (StdRdOptions *) rel->rd_options;
- if (opts && opts->vacuum_truncate_set)
+ if (opts && opts->vacuum_truncate != TERNARY_UNSET)
{
- if (opts->vacuum_truncate)
+ if (opts->vacuum_truncate == TERNARY_TRUE)
params.truncate = VACOPTVALUE_ENABLED;
else
params.truncate = VACOPTVALUE_DISABLED;
diff --git a/src/include/access/reloptions.h b/src/include/access/reloptions.h
index 2f08e1b0cf0..dfbef2babf2 100644
--- a/src/include/access/reloptions.h
+++ b/src/include/access/reloptions.h
@@ -29,6 +29,7 @@
typedef enum relopt_type
{
RELOPT_TYPE_BOOL,
+ RELOPT_TYPE_TERNARY, /* on, off, unset */
RELOPT_TYPE_INT,
RELOPT_TYPE_REAL,
RELOPT_TYPE_ENUM,
@@ -80,6 +81,7 @@ typedef struct relopt_value
union
{
bool bool_val;
+ pg_ternary ternary_val;
int int_val;
double real_val;
int enum_val;
@@ -94,6 +96,12 @@ typedef struct relopt_bool
bool default_val;
} relopt_bool;
+typedef struct relopt_ternary
+{
+ relopt_gen gen;
+ int default_val;
+} relopt_ternary;
+
typedef struct relopt_int
{
relopt_gen gen;
@@ -152,19 +160,6 @@ typedef struct
const char *optname; /* option's name */
relopt_type opttype; /* option's datatype */
int offset; /* offset of field in result struct */
-
- /*
- * isset_offset is an optional offset of a field in the result struct that
- * stores whether the option is explicitly set for the relation or if it
- * just picked up the default value. In most cases, this can be
- * accomplished by giving the reloption a special out-of-range default
- * value (e.g., some integer reloptions use -2), but this isn't always
- * possible. For example, a Boolean reloption cannot be given an
- * out-of-range default, so we need another way to discover the source of
- * its value. This offset is only used if given a value greater than
- * zero.
- */
- int isset_offset;
} relopt_parse_elt;
/* Local reloption definition */
@@ -195,6 +190,9 @@ typedef struct local_relopts
extern relopt_kind add_reloption_kind(void);
extern void add_bool_reloption(bits32 kinds, const char *name, const char *desc,
bool default_val, LOCKMODE lockmode);
+extern void add_ternary_reloption(bits32 kinds, const char *name,
+ const char *desc, pg_ternary default_val,
+ LOCKMODE lockmode);
extern void add_int_reloption(bits32 kinds, const char *name, const char *desc,
int default_val, int min_val, int max_val,
LOCKMODE lockmode);
@@ -214,6 +212,9 @@ extern void register_reloptions_validator(local_relopts *relopts,
extern void add_local_bool_reloption(local_relopts *relopts, const char *name,
const char *desc, bool default_val,
int offset);
+extern void add_local_ternary_reloption(local_relopts *relopts,
+ const char *name, const char *desc,
+ pg_ternary default_val, int offset);
extern void add_local_int_reloption(local_relopts *relopts, const char *name,
const char *desc, int default_val,
int min_val, int max_val, int offset);
diff --git a/src/include/postgres.h b/src/include/postgres.h
index 7d93fbce709..47c88780776 100644
--- a/src/include/postgres.h
+++ b/src/include/postgres.h
@@ -543,6 +543,21 @@ Float8GetDatum(float8 X)
* ----------------------------------------------------------------
*/
+/*
+ * pg_ternary
+ * Boolean value with an extra "unset" value
+ *
+ * This enum can be used for values that want to distinguish between true,
+ * false, and unset.
+*/
+
+typedef enum pg_ternary
+{
+ TERNARY_FALSE = 0,
+ TERNARY_TRUE = 1,
+ TERNARY_UNSET = -1
+} pg_ternary;
+
/*
* NON_EXEC_STATIC: It's sometimes useful to define a variable or function
* that is normally static but extern when using EXEC_BACKEND (see
diff --git a/src/include/utils/rel.h b/src/include/utils/rel.h
index d03ab247788..5feb18a5373 100644
--- a/src/include/utils/rel.h
+++ b/src/include/utils/rel.h
@@ -347,8 +347,7 @@ typedef struct StdRdOptions
bool user_catalog_table; /* use as an additional catalog relation */
int parallel_workers; /* max number of parallel workers */
StdRdOptIndexCleanup vacuum_index_cleanup; /* controls index vacuuming */
- bool vacuum_truncate; /* enables vacuum to truncate a relation */
- bool vacuum_truncate_set; /* whether vacuum_truncate is set */
+ pg_ternary vacuum_truncate; /* enables vacuum to truncate a relation */
/*
* Fraction of pages in a relation that vacuum can eagerly scan and fail
diff --git a/src/test/regress/expected/reloptions.out b/src/test/regress/expected/reloptions.out
index 9de19b4e3f1..1c99f79ab01 100644
--- a/src/test/regress/expected/reloptions.out
+++ b/src/test/regress/expected/reloptions.out
@@ -98,6 +98,42 @@ SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass;
{fillfactor=13,autovacuum_enabled=false}
(1 row)
+-- Tests for future (FIXME) ternary options
+-- behave as boolean option: accept unassigned name and truncated value
+DROP TABLE reloptions_test;
+CREATE TABLE reloptions_test(i INT) WITH (vacuum_truncate);
+SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass;
+ reloptions
+------------------------
+ {vacuum_truncate=true}
+(1 row)
+
+DROP TABLE reloptions_test;
+CREATE TABLE reloptions_test(i INT) WITH (vacuum_truncate=FaLS);
+SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass;
+ reloptions
+------------------------
+ {vacuum_truncate=fals}
+(1 row)
+
+-- preferred "true" alias is used when storing
+DROP TABLE reloptions_test;
+CREATE TABLE reloptions_test(i INT) WITH (vacuum_index_cleanup=on);
+SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass;
+ reloptions
+---------------------------
+ {vacuum_index_cleanup=on}
+(1 row)
+
+-- custom "third" value is available
+DROP TABLE reloptions_test;
+CREATE TABLE reloptions_test(i INT) WITH (vacuum_index_cleanup=auto);
+SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass;
+ reloptions
+-----------------------------
+ {vacuum_index_cleanup=auto}
+(1 row)
+
-- Test vacuum_truncate option
DROP TABLE reloptions_test;
CREATE TEMP TABLE reloptions_test(i INT NOT NULL, j text)
diff --git a/src/test/regress/sql/reloptions.sql b/src/test/regress/sql/reloptions.sql
index 24fbe0b478d..f5980dafcbc 100644
--- a/src/test/regress/sql/reloptions.sql
+++ b/src/test/regress/sql/reloptions.sql
@@ -59,6 +59,27 @@ UPDATE pg_class
ALTER TABLE reloptions_test RESET (illegal_option);
SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass;
+-- Tests for future (FIXME) ternary options
+
+-- behave as boolean option: accept unassigned name and truncated value
+DROP TABLE reloptions_test;
+CREATE TABLE reloptions_test(i INT) WITH (vacuum_truncate);
+SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass;
+
+DROP TABLE reloptions_test;
+CREATE TABLE reloptions_test(i INT) WITH (vacuum_truncate=FaLS);
+SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass;
+
+-- preferred "true" alias is used when storing
+DROP TABLE reloptions_test;
+CREATE TABLE reloptions_test(i INT) WITH (vacuum_index_cleanup=on);
+SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass;
+
+-- custom "third" value is available
+DROP TABLE reloptions_test;
+CREATE TABLE reloptions_test(i INT) WITH (vacuum_index_cleanup=auto);
+SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass;
+
-- Test vacuum_truncate option
DROP TABLE reloptions_test;
diff --git a/src/tools/pgindent/typedefs.list b/src/tools/pgindent/typedefs.list
index 3f3a888fd0e..1c8610fd46c 100644
--- a/src/tools/pgindent/typedefs.list
+++ b/src/tools/pgindent/typedefs.list
@@ -3952,6 +3952,7 @@ pg_sha512_ctx
pg_snapshot
pg_special_case
pg_stack_base_t
+pg_ternary
pg_time_t
pg_time_usec_t
pg_tz
@@ -4079,6 +4080,7 @@ relopt_kind
relopt_parse_elt
relopt_real
relopt_string
+relopt_ternary
relopt_type
relopt_value
relopts_validator
--
2.47.3
--c36t3fndxhkzbl5z--
^ permalink raw reply [nested|flat] 868+ messages in thread
* [PATCH v3] Introduce ternary reloptions
@ 2026-01-16 15:04 Álvaro Herrera <[email protected]>
0 siblings, 0 replies; 868+ messages in thread
From: Álvaro Herrera @ 2026-01-16 15:04 UTC (permalink / raw)
Introduce ternary reloption as a replacement for current
`vacuum_truncate` implementation. Remove the `vacuum_truncate_set`
separate flag and use TERNARY_UNSET instead.
This could also be used for other options such as `vacuum_index_cleanup`
and `buffering`, but lets get the scaffolding in first.
Discussion: https://postgr.es/m/3474141.usfYGdeWWP@thinkpad-pgpro
---
src/backend/access/common/reloptions.c | 137 ++++++++++++++++++-----
src/backend/commands/vacuum.c | 4 +-
src/include/access/reloptions.h | 27 ++---
src/include/postgres.h | 15 +++
src/include/utils/rel.h | 3 +-
src/test/regress/expected/reloptions.out | 36 ++++++
src/test/regress/sql/reloptions.sql | 21 ++++
src/tools/pgindent/typedefs.list | 2 +
8 files changed, 202 insertions(+), 43 deletions(-)
diff --git a/src/backend/access/common/reloptions.c b/src/backend/access/common/reloptions.c
index 0b83f98ed5f..6f1f577581a 100644
--- a/src/backend/access/common/reloptions.c
+++ b/src/backend/access/common/reloptions.c
@@ -40,9 +40,9 @@
*
* To add an option:
*
- * (i) decide on a type (bool, integer, real, enum, string), name, default
- * value, upper and lower bounds (if applicable); for strings, consider a
- * validation routine.
+ * (i) decide on a type (bool, ternary, integer, real, enum, string), name,
+ * default value, upper and lower bounds (if applicable); for strings,
+ * consider a validation routine.
* (ii) add a record below (or use add_<type>_reloption).
* (iii) add it to the appropriate options struct (perhaps StdRdOptions)
* (iv) add it to the appropriate handling routine (perhaps
@@ -147,15 +147,6 @@ static relopt_bool boolRelOpts[] =
},
false
},
- {
- {
- "vacuum_truncate",
- "Enables vacuum to truncate empty pages at the end of this table",
- RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
- ShareUpdateExclusiveLock
- },
- true
- },
{
{
"deduplicate_items",
@@ -170,6 +161,25 @@ static relopt_bool boolRelOpts[] =
{{NULL}}
};
+static relopt_ternary ternaryRelOpts[] =
+{
+ {
+ {
+ "vacuum_truncate",
+ "Enables vacuum to truncate empty pages at the end of this table",
+ RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
+ ShareUpdateExclusiveLock
+ },
+ TERNARY_UNSET
+ },
+ /* list terminator */
+ {
+ {
+ NULL
+ }
+ }
+};
+
static relopt_int intRelOpts[] =
{
{
@@ -609,6 +619,13 @@ initialize_reloptions(void)
boolRelOpts[i].gen.lockmode));
j++;
}
+ for (i = 0; ternaryRelOpts[i].gen.name; i++)
+ {
+ Assert(DoLockModesConflict(ternaryRelOpts[i].gen.lockmode,
+ ternaryRelOpts[i].gen.lockmode));
+ j++;
+ }
+
for (i = 0; intRelOpts[i].gen.name; i++)
{
Assert(DoLockModesConflict(intRelOpts[i].gen.lockmode,
@@ -649,6 +666,14 @@ initialize_reloptions(void)
j++;
}
+ for (i = 0; ternaryRelOpts[i].gen.name; i++)
+ {
+ relOpts[j] = &ternaryRelOpts[i].gen;
+ relOpts[j]->type = RELOPT_TYPE_TERNARY;
+ relOpts[j]->namelen = strlen(relOpts[j]->name);
+ j++;
+ }
+
for (i = 0; intRelOpts[i].gen.name; i++)
{
relOpts[j] = &intRelOpts[i].gen;
@@ -809,6 +834,9 @@ allocate_reloption(bits32 kinds, int type, const char *name, const char *desc,
case RELOPT_TYPE_BOOL:
size = sizeof(relopt_bool);
break;
+ case RELOPT_TYPE_TERNARY:
+ size = sizeof(relopt_ternary);
+ break;
case RELOPT_TYPE_INT:
size = sizeof(relopt_int);
break;
@@ -892,6 +920,57 @@ add_local_bool_reloption(local_relopts *relopts, const char *name,
add_local_reloption(relopts, (relopt_gen *) newoption, offset);
}
+/*
+ * init_ternary_reloption
+ * Allocate and initialize a new ternary reloption
+ */
+static relopt_ternary *
+init_ternary_reloption(bits32 kinds, const char *name, const char *desc,
+ pg_ternary default_val, LOCKMODE lockmode)
+{
+ relopt_ternary *newoption;
+
+ newoption = (relopt_ternary *)
+ allocate_reloption(kinds, RELOPT_TYPE_TERNARY, name, desc, lockmode);
+ newoption->default_val = default_val;
+
+ return newoption;
+}
+
+/*
+ * add_ternary_reloption
+ * Add a new ternary reloption
+ */
+void
+add_ternary_reloption(bits32 kinds, const char *name, const char *desc,
+ pg_ternary default_val, LOCKMODE lockmode)
+{
+ relopt_ternary *newoption;
+
+ newoption =
+ init_ternary_reloption(kinds, name, desc, default_val, lockmode);
+
+ add_reloption((relopt_gen *) newoption);
+}
+
+/*
+ * add_local_ternary_reloption
+ * Add a new ternary local reloption
+ *
+ * 'offset' is offset of ternary-typed field.
+ */
+void
+add_local_ternary_reloption(local_relopts *relopts, const char *name,
+ const char *desc, pg_ternary default_val,
+ int offset)
+{
+ relopt_ternary *newoption;
+
+ newoption =
+ init_ternary_reloption(RELOPT_KIND_LOCAL, name, desc, default_val, 0);
+
+ add_local_reloption(relopts, (relopt_gen *) newoption, offset);
+}
/*
* init_real_reloption
@@ -1626,6 +1705,19 @@ parse_one_reloption(relopt_value *option, char *text_str, int text_len,
option->gen->name, value)));
}
break;
+ case RELOPT_TYPE_TERNARY:
+ {
+ bool b;
+
+ parsed = parse_bool(value, &b);
+ option->values.ternary_val = b ? TERNARY_TRUE : TERNARY_FALSE;
+ if (validate && !parsed)
+ ereport(ERROR,
+ (errcode(ERRCODE_INVALID_PARAMETER_VALUE),
+ errmsg("invalid value for ternary option \"%s\": %s",
+ option->gen->name, value)));
+ }
+ break;
case RELOPT_TYPE_INT:
{
relopt_int *optint = (relopt_int *) option->gen;
@@ -1789,17 +1881,6 @@ fillRelOptions(void *rdopts, Size basesize,
char *itempos = ((char *) rdopts) + elems[j].offset;
char *string_val;
- /*
- * If isset_offset is provided, store whether the reloption is
- * set there.
- */
- if (elems[j].isset_offset > 0)
- {
- char *setpos = ((char *) rdopts) + elems[j].isset_offset;
-
- *(bool *) setpos = options[i].isset;
- }
-
switch (options[i].gen->type)
{
case RELOPT_TYPE_BOOL:
@@ -1807,6 +1888,11 @@ fillRelOptions(void *rdopts, Size basesize,
options[i].values.bool_val :
((relopt_bool *) options[i].gen)->default_val;
break;
+ case RELOPT_TYPE_TERNARY:
+ *(pg_ternary *) itempos = options[i].isset ?
+ options[i].values.ternary_val :
+ ((relopt_ternary *) options[i].gen)->default_val;
+ break;
case RELOPT_TYPE_INT:
*(int *) itempos = options[i].isset ?
options[i].values.int_val :
@@ -1923,8 +2009,8 @@ default_reloptions(Datum reloptions, bool validate, relopt_kind kind)
offsetof(StdRdOptions, parallel_workers)},
{"vacuum_index_cleanup", RELOPT_TYPE_ENUM,
offsetof(StdRdOptions, vacuum_index_cleanup)},
- {"vacuum_truncate", RELOPT_TYPE_BOOL,
- offsetof(StdRdOptions, vacuum_truncate), offsetof(StdRdOptions, vacuum_truncate_set)},
+ {"vacuum_truncate", RELOPT_TYPE_TERNARY,
+ offsetof(StdRdOptions, vacuum_truncate)},
{"vacuum_max_eager_freeze_failure_rate", RELOPT_TYPE_REAL,
offsetof(StdRdOptions, vacuum_max_eager_freeze_failure_rate)}
};
@@ -2004,7 +2090,6 @@ build_local_reloptions(local_relopts *relopts, Datum options, bool validate)
elems[i].optname = opt->option->name;
elems[i].opttype = opt->option->type;
elems[i].offset = opt->offset;
- elems[i].isset_offset = 0; /* not supported for local relopts yet */
i++;
}
diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c
index aa4fbec143f..696eab9bd97 100644
--- a/src/backend/commands/vacuum.c
+++ b/src/backend/commands/vacuum.c
@@ -2224,9 +2224,9 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams params,
{
StdRdOptions *opts = (StdRdOptions *) rel->rd_options;
- if (opts && opts->vacuum_truncate_set)
+ if (opts && opts->vacuum_truncate != TERNARY_UNSET)
{
- if (opts->vacuum_truncate)
+ if (opts->vacuum_truncate == TERNARY_TRUE)
params.truncate = VACOPTVALUE_ENABLED;
else
params.truncate = VACOPTVALUE_DISABLED;
diff --git a/src/include/access/reloptions.h b/src/include/access/reloptions.h
index 2f08e1b0cf0..dfbef2babf2 100644
--- a/src/include/access/reloptions.h
+++ b/src/include/access/reloptions.h
@@ -29,6 +29,7 @@
typedef enum relopt_type
{
RELOPT_TYPE_BOOL,
+ RELOPT_TYPE_TERNARY, /* on, off, unset */
RELOPT_TYPE_INT,
RELOPT_TYPE_REAL,
RELOPT_TYPE_ENUM,
@@ -80,6 +81,7 @@ typedef struct relopt_value
union
{
bool bool_val;
+ pg_ternary ternary_val;
int int_val;
double real_val;
int enum_val;
@@ -94,6 +96,12 @@ typedef struct relopt_bool
bool default_val;
} relopt_bool;
+typedef struct relopt_ternary
+{
+ relopt_gen gen;
+ int default_val;
+} relopt_ternary;
+
typedef struct relopt_int
{
relopt_gen gen;
@@ -152,19 +160,6 @@ typedef struct
const char *optname; /* option's name */
relopt_type opttype; /* option's datatype */
int offset; /* offset of field in result struct */
-
- /*
- * isset_offset is an optional offset of a field in the result struct that
- * stores whether the option is explicitly set for the relation or if it
- * just picked up the default value. In most cases, this can be
- * accomplished by giving the reloption a special out-of-range default
- * value (e.g., some integer reloptions use -2), but this isn't always
- * possible. For example, a Boolean reloption cannot be given an
- * out-of-range default, so we need another way to discover the source of
- * its value. This offset is only used if given a value greater than
- * zero.
- */
- int isset_offset;
} relopt_parse_elt;
/* Local reloption definition */
@@ -195,6 +190,9 @@ typedef struct local_relopts
extern relopt_kind add_reloption_kind(void);
extern void add_bool_reloption(bits32 kinds, const char *name, const char *desc,
bool default_val, LOCKMODE lockmode);
+extern void add_ternary_reloption(bits32 kinds, const char *name,
+ const char *desc, pg_ternary default_val,
+ LOCKMODE lockmode);
extern void add_int_reloption(bits32 kinds, const char *name, const char *desc,
int default_val, int min_val, int max_val,
LOCKMODE lockmode);
@@ -214,6 +212,9 @@ extern void register_reloptions_validator(local_relopts *relopts,
extern void add_local_bool_reloption(local_relopts *relopts, const char *name,
const char *desc, bool default_val,
int offset);
+extern void add_local_ternary_reloption(local_relopts *relopts,
+ const char *name, const char *desc,
+ pg_ternary default_val, int offset);
extern void add_local_int_reloption(local_relopts *relopts, const char *name,
const char *desc, int default_val,
int min_val, int max_val, int offset);
diff --git a/src/include/postgres.h b/src/include/postgres.h
index 7d93fbce709..47c88780776 100644
--- a/src/include/postgres.h
+++ b/src/include/postgres.h
@@ -543,6 +543,21 @@ Float8GetDatum(float8 X)
* ----------------------------------------------------------------
*/
+/*
+ * pg_ternary
+ * Boolean value with an extra "unset" value
+ *
+ * This enum can be used for values that want to distinguish between true,
+ * false, and unset.
+*/
+
+typedef enum pg_ternary
+{
+ TERNARY_FALSE = 0,
+ TERNARY_TRUE = 1,
+ TERNARY_UNSET = -1
+} pg_ternary;
+
/*
* NON_EXEC_STATIC: It's sometimes useful to define a variable or function
* that is normally static but extern when using EXEC_BACKEND (see
diff --git a/src/include/utils/rel.h b/src/include/utils/rel.h
index d03ab247788..5feb18a5373 100644
--- a/src/include/utils/rel.h
+++ b/src/include/utils/rel.h
@@ -347,8 +347,7 @@ typedef struct StdRdOptions
bool user_catalog_table; /* use as an additional catalog relation */
int parallel_workers; /* max number of parallel workers */
StdRdOptIndexCleanup vacuum_index_cleanup; /* controls index vacuuming */
- bool vacuum_truncate; /* enables vacuum to truncate a relation */
- bool vacuum_truncate_set; /* whether vacuum_truncate is set */
+ pg_ternary vacuum_truncate; /* enables vacuum to truncate a relation */
/*
* Fraction of pages in a relation that vacuum can eagerly scan and fail
diff --git a/src/test/regress/expected/reloptions.out b/src/test/regress/expected/reloptions.out
index 9de19b4e3f1..1c99f79ab01 100644
--- a/src/test/regress/expected/reloptions.out
+++ b/src/test/regress/expected/reloptions.out
@@ -98,6 +98,42 @@ SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass;
{fillfactor=13,autovacuum_enabled=false}
(1 row)
+-- Tests for future (FIXME) ternary options
+-- behave as boolean option: accept unassigned name and truncated value
+DROP TABLE reloptions_test;
+CREATE TABLE reloptions_test(i INT) WITH (vacuum_truncate);
+SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass;
+ reloptions
+------------------------
+ {vacuum_truncate=true}
+(1 row)
+
+DROP TABLE reloptions_test;
+CREATE TABLE reloptions_test(i INT) WITH (vacuum_truncate=FaLS);
+SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass;
+ reloptions
+------------------------
+ {vacuum_truncate=fals}
+(1 row)
+
+-- preferred "true" alias is used when storing
+DROP TABLE reloptions_test;
+CREATE TABLE reloptions_test(i INT) WITH (vacuum_index_cleanup=on);
+SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass;
+ reloptions
+---------------------------
+ {vacuum_index_cleanup=on}
+(1 row)
+
+-- custom "third" value is available
+DROP TABLE reloptions_test;
+CREATE TABLE reloptions_test(i INT) WITH (vacuum_index_cleanup=auto);
+SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass;
+ reloptions
+-----------------------------
+ {vacuum_index_cleanup=auto}
+(1 row)
+
-- Test vacuum_truncate option
DROP TABLE reloptions_test;
CREATE TEMP TABLE reloptions_test(i INT NOT NULL, j text)
diff --git a/src/test/regress/sql/reloptions.sql b/src/test/regress/sql/reloptions.sql
index 24fbe0b478d..f5980dafcbc 100644
--- a/src/test/regress/sql/reloptions.sql
+++ b/src/test/regress/sql/reloptions.sql
@@ -59,6 +59,27 @@ UPDATE pg_class
ALTER TABLE reloptions_test RESET (illegal_option);
SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass;
+-- Tests for future (FIXME) ternary options
+
+-- behave as boolean option: accept unassigned name and truncated value
+DROP TABLE reloptions_test;
+CREATE TABLE reloptions_test(i INT) WITH (vacuum_truncate);
+SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass;
+
+DROP TABLE reloptions_test;
+CREATE TABLE reloptions_test(i INT) WITH (vacuum_truncate=FaLS);
+SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass;
+
+-- preferred "true" alias is used when storing
+DROP TABLE reloptions_test;
+CREATE TABLE reloptions_test(i INT) WITH (vacuum_index_cleanup=on);
+SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass;
+
+-- custom "third" value is available
+DROP TABLE reloptions_test;
+CREATE TABLE reloptions_test(i INT) WITH (vacuum_index_cleanup=auto);
+SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass;
+
-- Test vacuum_truncate option
DROP TABLE reloptions_test;
diff --git a/src/tools/pgindent/typedefs.list b/src/tools/pgindent/typedefs.list
index 3f3a888fd0e..1c8610fd46c 100644
--- a/src/tools/pgindent/typedefs.list
+++ b/src/tools/pgindent/typedefs.list
@@ -3952,6 +3952,7 @@ pg_sha512_ctx
pg_snapshot
pg_special_case
pg_stack_base_t
+pg_ternary
pg_time_t
pg_time_usec_t
pg_tz
@@ -4079,6 +4080,7 @@ relopt_kind
relopt_parse_elt
relopt_real
relopt_string
+relopt_ternary
relopt_type
relopt_value
relopts_validator
--
2.47.3
--c36t3fndxhkzbl5z--
^ permalink raw reply [nested|flat] 868+ messages in thread
* [PATCH v3] Introduce ternary reloptions
@ 2026-01-16 15:04 Álvaro Herrera <[email protected]>
0 siblings, 0 replies; 868+ messages in thread
From: Álvaro Herrera @ 2026-01-16 15:04 UTC (permalink / raw)
Introduce ternary reloption as a replacement for current
`vacuum_truncate` implementation. Remove the `vacuum_truncate_set`
separate flag and use TERNARY_UNSET instead.
This could also be used for other options such as `vacuum_index_cleanup`
and `buffering`, but lets get the scaffolding in first.
Discussion: https://postgr.es/m/3474141.usfYGdeWWP@thinkpad-pgpro
---
src/backend/access/common/reloptions.c | 137 ++++++++++++++++++-----
src/backend/commands/vacuum.c | 4 +-
src/include/access/reloptions.h | 27 ++---
src/include/postgres.h | 15 +++
src/include/utils/rel.h | 3 +-
src/test/regress/expected/reloptions.out | 36 ++++++
src/test/regress/sql/reloptions.sql | 21 ++++
src/tools/pgindent/typedefs.list | 2 +
8 files changed, 202 insertions(+), 43 deletions(-)
diff --git a/src/backend/access/common/reloptions.c b/src/backend/access/common/reloptions.c
index 0b83f98ed5f..6f1f577581a 100644
--- a/src/backend/access/common/reloptions.c
+++ b/src/backend/access/common/reloptions.c
@@ -40,9 +40,9 @@
*
* To add an option:
*
- * (i) decide on a type (bool, integer, real, enum, string), name, default
- * value, upper and lower bounds (if applicable); for strings, consider a
- * validation routine.
+ * (i) decide on a type (bool, ternary, integer, real, enum, string), name,
+ * default value, upper and lower bounds (if applicable); for strings,
+ * consider a validation routine.
* (ii) add a record below (or use add_<type>_reloption).
* (iii) add it to the appropriate options struct (perhaps StdRdOptions)
* (iv) add it to the appropriate handling routine (perhaps
@@ -147,15 +147,6 @@ static relopt_bool boolRelOpts[] =
},
false
},
- {
- {
- "vacuum_truncate",
- "Enables vacuum to truncate empty pages at the end of this table",
- RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
- ShareUpdateExclusiveLock
- },
- true
- },
{
{
"deduplicate_items",
@@ -170,6 +161,25 @@ static relopt_bool boolRelOpts[] =
{{NULL}}
};
+static relopt_ternary ternaryRelOpts[] =
+{
+ {
+ {
+ "vacuum_truncate",
+ "Enables vacuum to truncate empty pages at the end of this table",
+ RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
+ ShareUpdateExclusiveLock
+ },
+ TERNARY_UNSET
+ },
+ /* list terminator */
+ {
+ {
+ NULL
+ }
+ }
+};
+
static relopt_int intRelOpts[] =
{
{
@@ -609,6 +619,13 @@ initialize_reloptions(void)
boolRelOpts[i].gen.lockmode));
j++;
}
+ for (i = 0; ternaryRelOpts[i].gen.name; i++)
+ {
+ Assert(DoLockModesConflict(ternaryRelOpts[i].gen.lockmode,
+ ternaryRelOpts[i].gen.lockmode));
+ j++;
+ }
+
for (i = 0; intRelOpts[i].gen.name; i++)
{
Assert(DoLockModesConflict(intRelOpts[i].gen.lockmode,
@@ -649,6 +666,14 @@ initialize_reloptions(void)
j++;
}
+ for (i = 0; ternaryRelOpts[i].gen.name; i++)
+ {
+ relOpts[j] = &ternaryRelOpts[i].gen;
+ relOpts[j]->type = RELOPT_TYPE_TERNARY;
+ relOpts[j]->namelen = strlen(relOpts[j]->name);
+ j++;
+ }
+
for (i = 0; intRelOpts[i].gen.name; i++)
{
relOpts[j] = &intRelOpts[i].gen;
@@ -809,6 +834,9 @@ allocate_reloption(bits32 kinds, int type, const char *name, const char *desc,
case RELOPT_TYPE_BOOL:
size = sizeof(relopt_bool);
break;
+ case RELOPT_TYPE_TERNARY:
+ size = sizeof(relopt_ternary);
+ break;
case RELOPT_TYPE_INT:
size = sizeof(relopt_int);
break;
@@ -892,6 +920,57 @@ add_local_bool_reloption(local_relopts *relopts, const char *name,
add_local_reloption(relopts, (relopt_gen *) newoption, offset);
}
+/*
+ * init_ternary_reloption
+ * Allocate and initialize a new ternary reloption
+ */
+static relopt_ternary *
+init_ternary_reloption(bits32 kinds, const char *name, const char *desc,
+ pg_ternary default_val, LOCKMODE lockmode)
+{
+ relopt_ternary *newoption;
+
+ newoption = (relopt_ternary *)
+ allocate_reloption(kinds, RELOPT_TYPE_TERNARY, name, desc, lockmode);
+ newoption->default_val = default_val;
+
+ return newoption;
+}
+
+/*
+ * add_ternary_reloption
+ * Add a new ternary reloption
+ */
+void
+add_ternary_reloption(bits32 kinds, const char *name, const char *desc,
+ pg_ternary default_val, LOCKMODE lockmode)
+{
+ relopt_ternary *newoption;
+
+ newoption =
+ init_ternary_reloption(kinds, name, desc, default_val, lockmode);
+
+ add_reloption((relopt_gen *) newoption);
+}
+
+/*
+ * add_local_ternary_reloption
+ * Add a new ternary local reloption
+ *
+ * 'offset' is offset of ternary-typed field.
+ */
+void
+add_local_ternary_reloption(local_relopts *relopts, const char *name,
+ const char *desc, pg_ternary default_val,
+ int offset)
+{
+ relopt_ternary *newoption;
+
+ newoption =
+ init_ternary_reloption(RELOPT_KIND_LOCAL, name, desc, default_val, 0);
+
+ add_local_reloption(relopts, (relopt_gen *) newoption, offset);
+}
/*
* init_real_reloption
@@ -1626,6 +1705,19 @@ parse_one_reloption(relopt_value *option, char *text_str, int text_len,
option->gen->name, value)));
}
break;
+ case RELOPT_TYPE_TERNARY:
+ {
+ bool b;
+
+ parsed = parse_bool(value, &b);
+ option->values.ternary_val = b ? TERNARY_TRUE : TERNARY_FALSE;
+ if (validate && !parsed)
+ ereport(ERROR,
+ (errcode(ERRCODE_INVALID_PARAMETER_VALUE),
+ errmsg("invalid value for ternary option \"%s\": %s",
+ option->gen->name, value)));
+ }
+ break;
case RELOPT_TYPE_INT:
{
relopt_int *optint = (relopt_int *) option->gen;
@@ -1789,17 +1881,6 @@ fillRelOptions(void *rdopts, Size basesize,
char *itempos = ((char *) rdopts) + elems[j].offset;
char *string_val;
- /*
- * If isset_offset is provided, store whether the reloption is
- * set there.
- */
- if (elems[j].isset_offset > 0)
- {
- char *setpos = ((char *) rdopts) + elems[j].isset_offset;
-
- *(bool *) setpos = options[i].isset;
- }
-
switch (options[i].gen->type)
{
case RELOPT_TYPE_BOOL:
@@ -1807,6 +1888,11 @@ fillRelOptions(void *rdopts, Size basesize,
options[i].values.bool_val :
((relopt_bool *) options[i].gen)->default_val;
break;
+ case RELOPT_TYPE_TERNARY:
+ *(pg_ternary *) itempos = options[i].isset ?
+ options[i].values.ternary_val :
+ ((relopt_ternary *) options[i].gen)->default_val;
+ break;
case RELOPT_TYPE_INT:
*(int *) itempos = options[i].isset ?
options[i].values.int_val :
@@ -1923,8 +2009,8 @@ default_reloptions(Datum reloptions, bool validate, relopt_kind kind)
offsetof(StdRdOptions, parallel_workers)},
{"vacuum_index_cleanup", RELOPT_TYPE_ENUM,
offsetof(StdRdOptions, vacuum_index_cleanup)},
- {"vacuum_truncate", RELOPT_TYPE_BOOL,
- offsetof(StdRdOptions, vacuum_truncate), offsetof(StdRdOptions, vacuum_truncate_set)},
+ {"vacuum_truncate", RELOPT_TYPE_TERNARY,
+ offsetof(StdRdOptions, vacuum_truncate)},
{"vacuum_max_eager_freeze_failure_rate", RELOPT_TYPE_REAL,
offsetof(StdRdOptions, vacuum_max_eager_freeze_failure_rate)}
};
@@ -2004,7 +2090,6 @@ build_local_reloptions(local_relopts *relopts, Datum options, bool validate)
elems[i].optname = opt->option->name;
elems[i].opttype = opt->option->type;
elems[i].offset = opt->offset;
- elems[i].isset_offset = 0; /* not supported for local relopts yet */
i++;
}
diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c
index aa4fbec143f..696eab9bd97 100644
--- a/src/backend/commands/vacuum.c
+++ b/src/backend/commands/vacuum.c
@@ -2224,9 +2224,9 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams params,
{
StdRdOptions *opts = (StdRdOptions *) rel->rd_options;
- if (opts && opts->vacuum_truncate_set)
+ if (opts && opts->vacuum_truncate != TERNARY_UNSET)
{
- if (opts->vacuum_truncate)
+ if (opts->vacuum_truncate == TERNARY_TRUE)
params.truncate = VACOPTVALUE_ENABLED;
else
params.truncate = VACOPTVALUE_DISABLED;
diff --git a/src/include/access/reloptions.h b/src/include/access/reloptions.h
index 2f08e1b0cf0..dfbef2babf2 100644
--- a/src/include/access/reloptions.h
+++ b/src/include/access/reloptions.h
@@ -29,6 +29,7 @@
typedef enum relopt_type
{
RELOPT_TYPE_BOOL,
+ RELOPT_TYPE_TERNARY, /* on, off, unset */
RELOPT_TYPE_INT,
RELOPT_TYPE_REAL,
RELOPT_TYPE_ENUM,
@@ -80,6 +81,7 @@ typedef struct relopt_value
union
{
bool bool_val;
+ pg_ternary ternary_val;
int int_val;
double real_val;
int enum_val;
@@ -94,6 +96,12 @@ typedef struct relopt_bool
bool default_val;
} relopt_bool;
+typedef struct relopt_ternary
+{
+ relopt_gen gen;
+ int default_val;
+} relopt_ternary;
+
typedef struct relopt_int
{
relopt_gen gen;
@@ -152,19 +160,6 @@ typedef struct
const char *optname; /* option's name */
relopt_type opttype; /* option's datatype */
int offset; /* offset of field in result struct */
-
- /*
- * isset_offset is an optional offset of a field in the result struct that
- * stores whether the option is explicitly set for the relation or if it
- * just picked up the default value. In most cases, this can be
- * accomplished by giving the reloption a special out-of-range default
- * value (e.g., some integer reloptions use -2), but this isn't always
- * possible. For example, a Boolean reloption cannot be given an
- * out-of-range default, so we need another way to discover the source of
- * its value. This offset is only used if given a value greater than
- * zero.
- */
- int isset_offset;
} relopt_parse_elt;
/* Local reloption definition */
@@ -195,6 +190,9 @@ typedef struct local_relopts
extern relopt_kind add_reloption_kind(void);
extern void add_bool_reloption(bits32 kinds, const char *name, const char *desc,
bool default_val, LOCKMODE lockmode);
+extern void add_ternary_reloption(bits32 kinds, const char *name,
+ const char *desc, pg_ternary default_val,
+ LOCKMODE lockmode);
extern void add_int_reloption(bits32 kinds, const char *name, const char *desc,
int default_val, int min_val, int max_val,
LOCKMODE lockmode);
@@ -214,6 +212,9 @@ extern void register_reloptions_validator(local_relopts *relopts,
extern void add_local_bool_reloption(local_relopts *relopts, const char *name,
const char *desc, bool default_val,
int offset);
+extern void add_local_ternary_reloption(local_relopts *relopts,
+ const char *name, const char *desc,
+ pg_ternary default_val, int offset);
extern void add_local_int_reloption(local_relopts *relopts, const char *name,
const char *desc, int default_val,
int min_val, int max_val, int offset);
diff --git a/src/include/postgres.h b/src/include/postgres.h
index 7d93fbce709..47c88780776 100644
--- a/src/include/postgres.h
+++ b/src/include/postgres.h
@@ -543,6 +543,21 @@ Float8GetDatum(float8 X)
* ----------------------------------------------------------------
*/
+/*
+ * pg_ternary
+ * Boolean value with an extra "unset" value
+ *
+ * This enum can be used for values that want to distinguish between true,
+ * false, and unset.
+*/
+
+typedef enum pg_ternary
+{
+ TERNARY_FALSE = 0,
+ TERNARY_TRUE = 1,
+ TERNARY_UNSET = -1
+} pg_ternary;
+
/*
* NON_EXEC_STATIC: It's sometimes useful to define a variable or function
* that is normally static but extern when using EXEC_BACKEND (see
diff --git a/src/include/utils/rel.h b/src/include/utils/rel.h
index d03ab247788..5feb18a5373 100644
--- a/src/include/utils/rel.h
+++ b/src/include/utils/rel.h
@@ -347,8 +347,7 @@ typedef struct StdRdOptions
bool user_catalog_table; /* use as an additional catalog relation */
int parallel_workers; /* max number of parallel workers */
StdRdOptIndexCleanup vacuum_index_cleanup; /* controls index vacuuming */
- bool vacuum_truncate; /* enables vacuum to truncate a relation */
- bool vacuum_truncate_set; /* whether vacuum_truncate is set */
+ pg_ternary vacuum_truncate; /* enables vacuum to truncate a relation */
/*
* Fraction of pages in a relation that vacuum can eagerly scan and fail
diff --git a/src/test/regress/expected/reloptions.out b/src/test/regress/expected/reloptions.out
index 9de19b4e3f1..1c99f79ab01 100644
--- a/src/test/regress/expected/reloptions.out
+++ b/src/test/regress/expected/reloptions.out
@@ -98,6 +98,42 @@ SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass;
{fillfactor=13,autovacuum_enabled=false}
(1 row)
+-- Tests for future (FIXME) ternary options
+-- behave as boolean option: accept unassigned name and truncated value
+DROP TABLE reloptions_test;
+CREATE TABLE reloptions_test(i INT) WITH (vacuum_truncate);
+SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass;
+ reloptions
+------------------------
+ {vacuum_truncate=true}
+(1 row)
+
+DROP TABLE reloptions_test;
+CREATE TABLE reloptions_test(i INT) WITH (vacuum_truncate=FaLS);
+SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass;
+ reloptions
+------------------------
+ {vacuum_truncate=fals}
+(1 row)
+
+-- preferred "true" alias is used when storing
+DROP TABLE reloptions_test;
+CREATE TABLE reloptions_test(i INT) WITH (vacuum_index_cleanup=on);
+SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass;
+ reloptions
+---------------------------
+ {vacuum_index_cleanup=on}
+(1 row)
+
+-- custom "third" value is available
+DROP TABLE reloptions_test;
+CREATE TABLE reloptions_test(i INT) WITH (vacuum_index_cleanup=auto);
+SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass;
+ reloptions
+-----------------------------
+ {vacuum_index_cleanup=auto}
+(1 row)
+
-- Test vacuum_truncate option
DROP TABLE reloptions_test;
CREATE TEMP TABLE reloptions_test(i INT NOT NULL, j text)
diff --git a/src/test/regress/sql/reloptions.sql b/src/test/regress/sql/reloptions.sql
index 24fbe0b478d..f5980dafcbc 100644
--- a/src/test/regress/sql/reloptions.sql
+++ b/src/test/regress/sql/reloptions.sql
@@ -59,6 +59,27 @@ UPDATE pg_class
ALTER TABLE reloptions_test RESET (illegal_option);
SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass;
+-- Tests for future (FIXME) ternary options
+
+-- behave as boolean option: accept unassigned name and truncated value
+DROP TABLE reloptions_test;
+CREATE TABLE reloptions_test(i INT) WITH (vacuum_truncate);
+SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass;
+
+DROP TABLE reloptions_test;
+CREATE TABLE reloptions_test(i INT) WITH (vacuum_truncate=FaLS);
+SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass;
+
+-- preferred "true" alias is used when storing
+DROP TABLE reloptions_test;
+CREATE TABLE reloptions_test(i INT) WITH (vacuum_index_cleanup=on);
+SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass;
+
+-- custom "third" value is available
+DROP TABLE reloptions_test;
+CREATE TABLE reloptions_test(i INT) WITH (vacuum_index_cleanup=auto);
+SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass;
+
-- Test vacuum_truncate option
DROP TABLE reloptions_test;
diff --git a/src/tools/pgindent/typedefs.list b/src/tools/pgindent/typedefs.list
index 3f3a888fd0e..1c8610fd46c 100644
--- a/src/tools/pgindent/typedefs.list
+++ b/src/tools/pgindent/typedefs.list
@@ -3952,6 +3952,7 @@ pg_sha512_ctx
pg_snapshot
pg_special_case
pg_stack_base_t
+pg_ternary
pg_time_t
pg_time_usec_t
pg_tz
@@ -4079,6 +4080,7 @@ relopt_kind
relopt_parse_elt
relopt_real
relopt_string
+relopt_ternary
relopt_type
relopt_value
relopts_validator
--
2.47.3
--c36t3fndxhkzbl5z--
^ permalink raw reply [nested|flat] 868+ messages in thread
* [PATCH v3] Introduce ternary reloptions
@ 2026-01-16 15:04 Álvaro Herrera <[email protected]>
0 siblings, 0 replies; 868+ messages in thread
From: Álvaro Herrera @ 2026-01-16 15:04 UTC (permalink / raw)
Introduce ternary reloption as a replacement for current
`vacuum_truncate` implementation. Remove the `vacuum_truncate_set`
separate flag and use TERNARY_UNSET instead.
This could also be used for other options such as `vacuum_index_cleanup`
and `buffering`, but lets get the scaffolding in first.
Discussion: https://postgr.es/m/3474141.usfYGdeWWP@thinkpad-pgpro
---
src/backend/access/common/reloptions.c | 137 ++++++++++++++++++-----
src/backend/commands/vacuum.c | 4 +-
src/include/access/reloptions.h | 27 ++---
src/include/postgres.h | 15 +++
src/include/utils/rel.h | 3 +-
src/test/regress/expected/reloptions.out | 36 ++++++
src/test/regress/sql/reloptions.sql | 21 ++++
src/tools/pgindent/typedefs.list | 2 +
8 files changed, 202 insertions(+), 43 deletions(-)
diff --git a/src/backend/access/common/reloptions.c b/src/backend/access/common/reloptions.c
index 0b83f98ed5f..6f1f577581a 100644
--- a/src/backend/access/common/reloptions.c
+++ b/src/backend/access/common/reloptions.c
@@ -40,9 +40,9 @@
*
* To add an option:
*
- * (i) decide on a type (bool, integer, real, enum, string), name, default
- * value, upper and lower bounds (if applicable); for strings, consider a
- * validation routine.
+ * (i) decide on a type (bool, ternary, integer, real, enum, string), name,
+ * default value, upper and lower bounds (if applicable); for strings,
+ * consider a validation routine.
* (ii) add a record below (or use add_<type>_reloption).
* (iii) add it to the appropriate options struct (perhaps StdRdOptions)
* (iv) add it to the appropriate handling routine (perhaps
@@ -147,15 +147,6 @@ static relopt_bool boolRelOpts[] =
},
false
},
- {
- {
- "vacuum_truncate",
- "Enables vacuum to truncate empty pages at the end of this table",
- RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
- ShareUpdateExclusiveLock
- },
- true
- },
{
{
"deduplicate_items",
@@ -170,6 +161,25 @@ static relopt_bool boolRelOpts[] =
{{NULL}}
};
+static relopt_ternary ternaryRelOpts[] =
+{
+ {
+ {
+ "vacuum_truncate",
+ "Enables vacuum to truncate empty pages at the end of this table",
+ RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
+ ShareUpdateExclusiveLock
+ },
+ TERNARY_UNSET
+ },
+ /* list terminator */
+ {
+ {
+ NULL
+ }
+ }
+};
+
static relopt_int intRelOpts[] =
{
{
@@ -609,6 +619,13 @@ initialize_reloptions(void)
boolRelOpts[i].gen.lockmode));
j++;
}
+ for (i = 0; ternaryRelOpts[i].gen.name; i++)
+ {
+ Assert(DoLockModesConflict(ternaryRelOpts[i].gen.lockmode,
+ ternaryRelOpts[i].gen.lockmode));
+ j++;
+ }
+
for (i = 0; intRelOpts[i].gen.name; i++)
{
Assert(DoLockModesConflict(intRelOpts[i].gen.lockmode,
@@ -649,6 +666,14 @@ initialize_reloptions(void)
j++;
}
+ for (i = 0; ternaryRelOpts[i].gen.name; i++)
+ {
+ relOpts[j] = &ternaryRelOpts[i].gen;
+ relOpts[j]->type = RELOPT_TYPE_TERNARY;
+ relOpts[j]->namelen = strlen(relOpts[j]->name);
+ j++;
+ }
+
for (i = 0; intRelOpts[i].gen.name; i++)
{
relOpts[j] = &intRelOpts[i].gen;
@@ -809,6 +834,9 @@ allocate_reloption(bits32 kinds, int type, const char *name, const char *desc,
case RELOPT_TYPE_BOOL:
size = sizeof(relopt_bool);
break;
+ case RELOPT_TYPE_TERNARY:
+ size = sizeof(relopt_ternary);
+ break;
case RELOPT_TYPE_INT:
size = sizeof(relopt_int);
break;
@@ -892,6 +920,57 @@ add_local_bool_reloption(local_relopts *relopts, const char *name,
add_local_reloption(relopts, (relopt_gen *) newoption, offset);
}
+/*
+ * init_ternary_reloption
+ * Allocate and initialize a new ternary reloption
+ */
+static relopt_ternary *
+init_ternary_reloption(bits32 kinds, const char *name, const char *desc,
+ pg_ternary default_val, LOCKMODE lockmode)
+{
+ relopt_ternary *newoption;
+
+ newoption = (relopt_ternary *)
+ allocate_reloption(kinds, RELOPT_TYPE_TERNARY, name, desc, lockmode);
+ newoption->default_val = default_val;
+
+ return newoption;
+}
+
+/*
+ * add_ternary_reloption
+ * Add a new ternary reloption
+ */
+void
+add_ternary_reloption(bits32 kinds, const char *name, const char *desc,
+ pg_ternary default_val, LOCKMODE lockmode)
+{
+ relopt_ternary *newoption;
+
+ newoption =
+ init_ternary_reloption(kinds, name, desc, default_val, lockmode);
+
+ add_reloption((relopt_gen *) newoption);
+}
+
+/*
+ * add_local_ternary_reloption
+ * Add a new ternary local reloption
+ *
+ * 'offset' is offset of ternary-typed field.
+ */
+void
+add_local_ternary_reloption(local_relopts *relopts, const char *name,
+ const char *desc, pg_ternary default_val,
+ int offset)
+{
+ relopt_ternary *newoption;
+
+ newoption =
+ init_ternary_reloption(RELOPT_KIND_LOCAL, name, desc, default_val, 0);
+
+ add_local_reloption(relopts, (relopt_gen *) newoption, offset);
+}
/*
* init_real_reloption
@@ -1626,6 +1705,19 @@ parse_one_reloption(relopt_value *option, char *text_str, int text_len,
option->gen->name, value)));
}
break;
+ case RELOPT_TYPE_TERNARY:
+ {
+ bool b;
+
+ parsed = parse_bool(value, &b);
+ option->values.ternary_val = b ? TERNARY_TRUE : TERNARY_FALSE;
+ if (validate && !parsed)
+ ereport(ERROR,
+ (errcode(ERRCODE_INVALID_PARAMETER_VALUE),
+ errmsg("invalid value for ternary option \"%s\": %s",
+ option->gen->name, value)));
+ }
+ break;
case RELOPT_TYPE_INT:
{
relopt_int *optint = (relopt_int *) option->gen;
@@ -1789,17 +1881,6 @@ fillRelOptions(void *rdopts, Size basesize,
char *itempos = ((char *) rdopts) + elems[j].offset;
char *string_val;
- /*
- * If isset_offset is provided, store whether the reloption is
- * set there.
- */
- if (elems[j].isset_offset > 0)
- {
- char *setpos = ((char *) rdopts) + elems[j].isset_offset;
-
- *(bool *) setpos = options[i].isset;
- }
-
switch (options[i].gen->type)
{
case RELOPT_TYPE_BOOL:
@@ -1807,6 +1888,11 @@ fillRelOptions(void *rdopts, Size basesize,
options[i].values.bool_val :
((relopt_bool *) options[i].gen)->default_val;
break;
+ case RELOPT_TYPE_TERNARY:
+ *(pg_ternary *) itempos = options[i].isset ?
+ options[i].values.ternary_val :
+ ((relopt_ternary *) options[i].gen)->default_val;
+ break;
case RELOPT_TYPE_INT:
*(int *) itempos = options[i].isset ?
options[i].values.int_val :
@@ -1923,8 +2009,8 @@ default_reloptions(Datum reloptions, bool validate, relopt_kind kind)
offsetof(StdRdOptions, parallel_workers)},
{"vacuum_index_cleanup", RELOPT_TYPE_ENUM,
offsetof(StdRdOptions, vacuum_index_cleanup)},
- {"vacuum_truncate", RELOPT_TYPE_BOOL,
- offsetof(StdRdOptions, vacuum_truncate), offsetof(StdRdOptions, vacuum_truncate_set)},
+ {"vacuum_truncate", RELOPT_TYPE_TERNARY,
+ offsetof(StdRdOptions, vacuum_truncate)},
{"vacuum_max_eager_freeze_failure_rate", RELOPT_TYPE_REAL,
offsetof(StdRdOptions, vacuum_max_eager_freeze_failure_rate)}
};
@@ -2004,7 +2090,6 @@ build_local_reloptions(local_relopts *relopts, Datum options, bool validate)
elems[i].optname = opt->option->name;
elems[i].opttype = opt->option->type;
elems[i].offset = opt->offset;
- elems[i].isset_offset = 0; /* not supported for local relopts yet */
i++;
}
diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c
index aa4fbec143f..696eab9bd97 100644
--- a/src/backend/commands/vacuum.c
+++ b/src/backend/commands/vacuum.c
@@ -2224,9 +2224,9 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams params,
{
StdRdOptions *opts = (StdRdOptions *) rel->rd_options;
- if (opts && opts->vacuum_truncate_set)
+ if (opts && opts->vacuum_truncate != TERNARY_UNSET)
{
- if (opts->vacuum_truncate)
+ if (opts->vacuum_truncate == TERNARY_TRUE)
params.truncate = VACOPTVALUE_ENABLED;
else
params.truncate = VACOPTVALUE_DISABLED;
diff --git a/src/include/access/reloptions.h b/src/include/access/reloptions.h
index 2f08e1b0cf0..dfbef2babf2 100644
--- a/src/include/access/reloptions.h
+++ b/src/include/access/reloptions.h
@@ -29,6 +29,7 @@
typedef enum relopt_type
{
RELOPT_TYPE_BOOL,
+ RELOPT_TYPE_TERNARY, /* on, off, unset */
RELOPT_TYPE_INT,
RELOPT_TYPE_REAL,
RELOPT_TYPE_ENUM,
@@ -80,6 +81,7 @@ typedef struct relopt_value
union
{
bool bool_val;
+ pg_ternary ternary_val;
int int_val;
double real_val;
int enum_val;
@@ -94,6 +96,12 @@ typedef struct relopt_bool
bool default_val;
} relopt_bool;
+typedef struct relopt_ternary
+{
+ relopt_gen gen;
+ int default_val;
+} relopt_ternary;
+
typedef struct relopt_int
{
relopt_gen gen;
@@ -152,19 +160,6 @@ typedef struct
const char *optname; /* option's name */
relopt_type opttype; /* option's datatype */
int offset; /* offset of field in result struct */
-
- /*
- * isset_offset is an optional offset of a field in the result struct that
- * stores whether the option is explicitly set for the relation or if it
- * just picked up the default value. In most cases, this can be
- * accomplished by giving the reloption a special out-of-range default
- * value (e.g., some integer reloptions use -2), but this isn't always
- * possible. For example, a Boolean reloption cannot be given an
- * out-of-range default, so we need another way to discover the source of
- * its value. This offset is only used if given a value greater than
- * zero.
- */
- int isset_offset;
} relopt_parse_elt;
/* Local reloption definition */
@@ -195,6 +190,9 @@ typedef struct local_relopts
extern relopt_kind add_reloption_kind(void);
extern void add_bool_reloption(bits32 kinds, const char *name, const char *desc,
bool default_val, LOCKMODE lockmode);
+extern void add_ternary_reloption(bits32 kinds, const char *name,
+ const char *desc, pg_ternary default_val,
+ LOCKMODE lockmode);
extern void add_int_reloption(bits32 kinds, const char *name, const char *desc,
int default_val, int min_val, int max_val,
LOCKMODE lockmode);
@@ -214,6 +212,9 @@ extern void register_reloptions_validator(local_relopts *relopts,
extern void add_local_bool_reloption(local_relopts *relopts, const char *name,
const char *desc, bool default_val,
int offset);
+extern void add_local_ternary_reloption(local_relopts *relopts,
+ const char *name, const char *desc,
+ pg_ternary default_val, int offset);
extern void add_local_int_reloption(local_relopts *relopts, const char *name,
const char *desc, int default_val,
int min_val, int max_val, int offset);
diff --git a/src/include/postgres.h b/src/include/postgres.h
index 7d93fbce709..47c88780776 100644
--- a/src/include/postgres.h
+++ b/src/include/postgres.h
@@ -543,6 +543,21 @@ Float8GetDatum(float8 X)
* ----------------------------------------------------------------
*/
+/*
+ * pg_ternary
+ * Boolean value with an extra "unset" value
+ *
+ * This enum can be used for values that want to distinguish between true,
+ * false, and unset.
+*/
+
+typedef enum pg_ternary
+{
+ TERNARY_FALSE = 0,
+ TERNARY_TRUE = 1,
+ TERNARY_UNSET = -1
+} pg_ternary;
+
/*
* NON_EXEC_STATIC: It's sometimes useful to define a variable or function
* that is normally static but extern when using EXEC_BACKEND (see
diff --git a/src/include/utils/rel.h b/src/include/utils/rel.h
index d03ab247788..5feb18a5373 100644
--- a/src/include/utils/rel.h
+++ b/src/include/utils/rel.h
@@ -347,8 +347,7 @@ typedef struct StdRdOptions
bool user_catalog_table; /* use as an additional catalog relation */
int parallel_workers; /* max number of parallel workers */
StdRdOptIndexCleanup vacuum_index_cleanup; /* controls index vacuuming */
- bool vacuum_truncate; /* enables vacuum to truncate a relation */
- bool vacuum_truncate_set; /* whether vacuum_truncate is set */
+ pg_ternary vacuum_truncate; /* enables vacuum to truncate a relation */
/*
* Fraction of pages in a relation that vacuum can eagerly scan and fail
diff --git a/src/test/regress/expected/reloptions.out b/src/test/regress/expected/reloptions.out
index 9de19b4e3f1..1c99f79ab01 100644
--- a/src/test/regress/expected/reloptions.out
+++ b/src/test/regress/expected/reloptions.out
@@ -98,6 +98,42 @@ SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass;
{fillfactor=13,autovacuum_enabled=false}
(1 row)
+-- Tests for future (FIXME) ternary options
+-- behave as boolean option: accept unassigned name and truncated value
+DROP TABLE reloptions_test;
+CREATE TABLE reloptions_test(i INT) WITH (vacuum_truncate);
+SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass;
+ reloptions
+------------------------
+ {vacuum_truncate=true}
+(1 row)
+
+DROP TABLE reloptions_test;
+CREATE TABLE reloptions_test(i INT) WITH (vacuum_truncate=FaLS);
+SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass;
+ reloptions
+------------------------
+ {vacuum_truncate=fals}
+(1 row)
+
+-- preferred "true" alias is used when storing
+DROP TABLE reloptions_test;
+CREATE TABLE reloptions_test(i INT) WITH (vacuum_index_cleanup=on);
+SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass;
+ reloptions
+---------------------------
+ {vacuum_index_cleanup=on}
+(1 row)
+
+-- custom "third" value is available
+DROP TABLE reloptions_test;
+CREATE TABLE reloptions_test(i INT) WITH (vacuum_index_cleanup=auto);
+SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass;
+ reloptions
+-----------------------------
+ {vacuum_index_cleanup=auto}
+(1 row)
+
-- Test vacuum_truncate option
DROP TABLE reloptions_test;
CREATE TEMP TABLE reloptions_test(i INT NOT NULL, j text)
diff --git a/src/test/regress/sql/reloptions.sql b/src/test/regress/sql/reloptions.sql
index 24fbe0b478d..f5980dafcbc 100644
--- a/src/test/regress/sql/reloptions.sql
+++ b/src/test/regress/sql/reloptions.sql
@@ -59,6 +59,27 @@ UPDATE pg_class
ALTER TABLE reloptions_test RESET (illegal_option);
SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass;
+-- Tests for future (FIXME) ternary options
+
+-- behave as boolean option: accept unassigned name and truncated value
+DROP TABLE reloptions_test;
+CREATE TABLE reloptions_test(i INT) WITH (vacuum_truncate);
+SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass;
+
+DROP TABLE reloptions_test;
+CREATE TABLE reloptions_test(i INT) WITH (vacuum_truncate=FaLS);
+SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass;
+
+-- preferred "true" alias is used when storing
+DROP TABLE reloptions_test;
+CREATE TABLE reloptions_test(i INT) WITH (vacuum_index_cleanup=on);
+SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass;
+
+-- custom "third" value is available
+DROP TABLE reloptions_test;
+CREATE TABLE reloptions_test(i INT) WITH (vacuum_index_cleanup=auto);
+SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass;
+
-- Test vacuum_truncate option
DROP TABLE reloptions_test;
diff --git a/src/tools/pgindent/typedefs.list b/src/tools/pgindent/typedefs.list
index 3f3a888fd0e..1c8610fd46c 100644
--- a/src/tools/pgindent/typedefs.list
+++ b/src/tools/pgindent/typedefs.list
@@ -3952,6 +3952,7 @@ pg_sha512_ctx
pg_snapshot
pg_special_case
pg_stack_base_t
+pg_ternary
pg_time_t
pg_time_usec_t
pg_tz
@@ -4079,6 +4080,7 @@ relopt_kind
relopt_parse_elt
relopt_real
relopt_string
+relopt_ternary
relopt_type
relopt_value
relopts_validator
--
2.47.3
--c36t3fndxhkzbl5z--
^ permalink raw reply [nested|flat] 868+ messages in thread
* [PATCH v3] Introduce ternary reloptions
@ 2026-01-16 15:04 Álvaro Herrera <[email protected]>
0 siblings, 0 replies; 868+ messages in thread
From: Álvaro Herrera @ 2026-01-16 15:04 UTC (permalink / raw)
Introduce ternary reloption as a replacement for current
`vacuum_truncate` implementation. Remove the `vacuum_truncate_set`
separate flag and use TERNARY_UNSET instead.
This could also be used for other options such as `vacuum_index_cleanup`
and `buffering`, but lets get the scaffolding in first.
Discussion: https://postgr.es/m/3474141.usfYGdeWWP@thinkpad-pgpro
---
src/backend/access/common/reloptions.c | 137 ++++++++++++++++++-----
src/backend/commands/vacuum.c | 4 +-
src/include/access/reloptions.h | 27 ++---
src/include/postgres.h | 15 +++
src/include/utils/rel.h | 3 +-
src/test/regress/expected/reloptions.out | 36 ++++++
src/test/regress/sql/reloptions.sql | 21 ++++
src/tools/pgindent/typedefs.list | 2 +
8 files changed, 202 insertions(+), 43 deletions(-)
diff --git a/src/backend/access/common/reloptions.c b/src/backend/access/common/reloptions.c
index 0b83f98ed5f..6f1f577581a 100644
--- a/src/backend/access/common/reloptions.c
+++ b/src/backend/access/common/reloptions.c
@@ -40,9 +40,9 @@
*
* To add an option:
*
- * (i) decide on a type (bool, integer, real, enum, string), name, default
- * value, upper and lower bounds (if applicable); for strings, consider a
- * validation routine.
+ * (i) decide on a type (bool, ternary, integer, real, enum, string), name,
+ * default value, upper and lower bounds (if applicable); for strings,
+ * consider a validation routine.
* (ii) add a record below (or use add_<type>_reloption).
* (iii) add it to the appropriate options struct (perhaps StdRdOptions)
* (iv) add it to the appropriate handling routine (perhaps
@@ -147,15 +147,6 @@ static relopt_bool boolRelOpts[] =
},
false
},
- {
- {
- "vacuum_truncate",
- "Enables vacuum to truncate empty pages at the end of this table",
- RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
- ShareUpdateExclusiveLock
- },
- true
- },
{
{
"deduplicate_items",
@@ -170,6 +161,25 @@ static relopt_bool boolRelOpts[] =
{{NULL}}
};
+static relopt_ternary ternaryRelOpts[] =
+{
+ {
+ {
+ "vacuum_truncate",
+ "Enables vacuum to truncate empty pages at the end of this table",
+ RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
+ ShareUpdateExclusiveLock
+ },
+ TERNARY_UNSET
+ },
+ /* list terminator */
+ {
+ {
+ NULL
+ }
+ }
+};
+
static relopt_int intRelOpts[] =
{
{
@@ -609,6 +619,13 @@ initialize_reloptions(void)
boolRelOpts[i].gen.lockmode));
j++;
}
+ for (i = 0; ternaryRelOpts[i].gen.name; i++)
+ {
+ Assert(DoLockModesConflict(ternaryRelOpts[i].gen.lockmode,
+ ternaryRelOpts[i].gen.lockmode));
+ j++;
+ }
+
for (i = 0; intRelOpts[i].gen.name; i++)
{
Assert(DoLockModesConflict(intRelOpts[i].gen.lockmode,
@@ -649,6 +666,14 @@ initialize_reloptions(void)
j++;
}
+ for (i = 0; ternaryRelOpts[i].gen.name; i++)
+ {
+ relOpts[j] = &ternaryRelOpts[i].gen;
+ relOpts[j]->type = RELOPT_TYPE_TERNARY;
+ relOpts[j]->namelen = strlen(relOpts[j]->name);
+ j++;
+ }
+
for (i = 0; intRelOpts[i].gen.name; i++)
{
relOpts[j] = &intRelOpts[i].gen;
@@ -809,6 +834,9 @@ allocate_reloption(bits32 kinds, int type, const char *name, const char *desc,
case RELOPT_TYPE_BOOL:
size = sizeof(relopt_bool);
break;
+ case RELOPT_TYPE_TERNARY:
+ size = sizeof(relopt_ternary);
+ break;
case RELOPT_TYPE_INT:
size = sizeof(relopt_int);
break;
@@ -892,6 +920,57 @@ add_local_bool_reloption(local_relopts *relopts, const char *name,
add_local_reloption(relopts, (relopt_gen *) newoption, offset);
}
+/*
+ * init_ternary_reloption
+ * Allocate and initialize a new ternary reloption
+ */
+static relopt_ternary *
+init_ternary_reloption(bits32 kinds, const char *name, const char *desc,
+ pg_ternary default_val, LOCKMODE lockmode)
+{
+ relopt_ternary *newoption;
+
+ newoption = (relopt_ternary *)
+ allocate_reloption(kinds, RELOPT_TYPE_TERNARY, name, desc, lockmode);
+ newoption->default_val = default_val;
+
+ return newoption;
+}
+
+/*
+ * add_ternary_reloption
+ * Add a new ternary reloption
+ */
+void
+add_ternary_reloption(bits32 kinds, const char *name, const char *desc,
+ pg_ternary default_val, LOCKMODE lockmode)
+{
+ relopt_ternary *newoption;
+
+ newoption =
+ init_ternary_reloption(kinds, name, desc, default_val, lockmode);
+
+ add_reloption((relopt_gen *) newoption);
+}
+
+/*
+ * add_local_ternary_reloption
+ * Add a new ternary local reloption
+ *
+ * 'offset' is offset of ternary-typed field.
+ */
+void
+add_local_ternary_reloption(local_relopts *relopts, const char *name,
+ const char *desc, pg_ternary default_val,
+ int offset)
+{
+ relopt_ternary *newoption;
+
+ newoption =
+ init_ternary_reloption(RELOPT_KIND_LOCAL, name, desc, default_val, 0);
+
+ add_local_reloption(relopts, (relopt_gen *) newoption, offset);
+}
/*
* init_real_reloption
@@ -1626,6 +1705,19 @@ parse_one_reloption(relopt_value *option, char *text_str, int text_len,
option->gen->name, value)));
}
break;
+ case RELOPT_TYPE_TERNARY:
+ {
+ bool b;
+
+ parsed = parse_bool(value, &b);
+ option->values.ternary_val = b ? TERNARY_TRUE : TERNARY_FALSE;
+ if (validate && !parsed)
+ ereport(ERROR,
+ (errcode(ERRCODE_INVALID_PARAMETER_VALUE),
+ errmsg("invalid value for ternary option \"%s\": %s",
+ option->gen->name, value)));
+ }
+ break;
case RELOPT_TYPE_INT:
{
relopt_int *optint = (relopt_int *) option->gen;
@@ -1789,17 +1881,6 @@ fillRelOptions(void *rdopts, Size basesize,
char *itempos = ((char *) rdopts) + elems[j].offset;
char *string_val;
- /*
- * If isset_offset is provided, store whether the reloption is
- * set there.
- */
- if (elems[j].isset_offset > 0)
- {
- char *setpos = ((char *) rdopts) + elems[j].isset_offset;
-
- *(bool *) setpos = options[i].isset;
- }
-
switch (options[i].gen->type)
{
case RELOPT_TYPE_BOOL:
@@ -1807,6 +1888,11 @@ fillRelOptions(void *rdopts, Size basesize,
options[i].values.bool_val :
((relopt_bool *) options[i].gen)->default_val;
break;
+ case RELOPT_TYPE_TERNARY:
+ *(pg_ternary *) itempos = options[i].isset ?
+ options[i].values.ternary_val :
+ ((relopt_ternary *) options[i].gen)->default_val;
+ break;
case RELOPT_TYPE_INT:
*(int *) itempos = options[i].isset ?
options[i].values.int_val :
@@ -1923,8 +2009,8 @@ default_reloptions(Datum reloptions, bool validate, relopt_kind kind)
offsetof(StdRdOptions, parallel_workers)},
{"vacuum_index_cleanup", RELOPT_TYPE_ENUM,
offsetof(StdRdOptions, vacuum_index_cleanup)},
- {"vacuum_truncate", RELOPT_TYPE_BOOL,
- offsetof(StdRdOptions, vacuum_truncate), offsetof(StdRdOptions, vacuum_truncate_set)},
+ {"vacuum_truncate", RELOPT_TYPE_TERNARY,
+ offsetof(StdRdOptions, vacuum_truncate)},
{"vacuum_max_eager_freeze_failure_rate", RELOPT_TYPE_REAL,
offsetof(StdRdOptions, vacuum_max_eager_freeze_failure_rate)}
};
@@ -2004,7 +2090,6 @@ build_local_reloptions(local_relopts *relopts, Datum options, bool validate)
elems[i].optname = opt->option->name;
elems[i].opttype = opt->option->type;
elems[i].offset = opt->offset;
- elems[i].isset_offset = 0; /* not supported for local relopts yet */
i++;
}
diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c
index aa4fbec143f..696eab9bd97 100644
--- a/src/backend/commands/vacuum.c
+++ b/src/backend/commands/vacuum.c
@@ -2224,9 +2224,9 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams params,
{
StdRdOptions *opts = (StdRdOptions *) rel->rd_options;
- if (opts && opts->vacuum_truncate_set)
+ if (opts && opts->vacuum_truncate != TERNARY_UNSET)
{
- if (opts->vacuum_truncate)
+ if (opts->vacuum_truncate == TERNARY_TRUE)
params.truncate = VACOPTVALUE_ENABLED;
else
params.truncate = VACOPTVALUE_DISABLED;
diff --git a/src/include/access/reloptions.h b/src/include/access/reloptions.h
index 2f08e1b0cf0..dfbef2babf2 100644
--- a/src/include/access/reloptions.h
+++ b/src/include/access/reloptions.h
@@ -29,6 +29,7 @@
typedef enum relopt_type
{
RELOPT_TYPE_BOOL,
+ RELOPT_TYPE_TERNARY, /* on, off, unset */
RELOPT_TYPE_INT,
RELOPT_TYPE_REAL,
RELOPT_TYPE_ENUM,
@@ -80,6 +81,7 @@ typedef struct relopt_value
union
{
bool bool_val;
+ pg_ternary ternary_val;
int int_val;
double real_val;
int enum_val;
@@ -94,6 +96,12 @@ typedef struct relopt_bool
bool default_val;
} relopt_bool;
+typedef struct relopt_ternary
+{
+ relopt_gen gen;
+ int default_val;
+} relopt_ternary;
+
typedef struct relopt_int
{
relopt_gen gen;
@@ -152,19 +160,6 @@ typedef struct
const char *optname; /* option's name */
relopt_type opttype; /* option's datatype */
int offset; /* offset of field in result struct */
-
- /*
- * isset_offset is an optional offset of a field in the result struct that
- * stores whether the option is explicitly set for the relation or if it
- * just picked up the default value. In most cases, this can be
- * accomplished by giving the reloption a special out-of-range default
- * value (e.g., some integer reloptions use -2), but this isn't always
- * possible. For example, a Boolean reloption cannot be given an
- * out-of-range default, so we need another way to discover the source of
- * its value. This offset is only used if given a value greater than
- * zero.
- */
- int isset_offset;
} relopt_parse_elt;
/* Local reloption definition */
@@ -195,6 +190,9 @@ typedef struct local_relopts
extern relopt_kind add_reloption_kind(void);
extern void add_bool_reloption(bits32 kinds, const char *name, const char *desc,
bool default_val, LOCKMODE lockmode);
+extern void add_ternary_reloption(bits32 kinds, const char *name,
+ const char *desc, pg_ternary default_val,
+ LOCKMODE lockmode);
extern void add_int_reloption(bits32 kinds, const char *name, const char *desc,
int default_val, int min_val, int max_val,
LOCKMODE lockmode);
@@ -214,6 +212,9 @@ extern void register_reloptions_validator(local_relopts *relopts,
extern void add_local_bool_reloption(local_relopts *relopts, const char *name,
const char *desc, bool default_val,
int offset);
+extern void add_local_ternary_reloption(local_relopts *relopts,
+ const char *name, const char *desc,
+ pg_ternary default_val, int offset);
extern void add_local_int_reloption(local_relopts *relopts, const char *name,
const char *desc, int default_val,
int min_val, int max_val, int offset);
diff --git a/src/include/postgres.h b/src/include/postgres.h
index 7d93fbce709..47c88780776 100644
--- a/src/include/postgres.h
+++ b/src/include/postgres.h
@@ -543,6 +543,21 @@ Float8GetDatum(float8 X)
* ----------------------------------------------------------------
*/
+/*
+ * pg_ternary
+ * Boolean value with an extra "unset" value
+ *
+ * This enum can be used for values that want to distinguish between true,
+ * false, and unset.
+*/
+
+typedef enum pg_ternary
+{
+ TERNARY_FALSE = 0,
+ TERNARY_TRUE = 1,
+ TERNARY_UNSET = -1
+} pg_ternary;
+
/*
* NON_EXEC_STATIC: It's sometimes useful to define a variable or function
* that is normally static but extern when using EXEC_BACKEND (see
diff --git a/src/include/utils/rel.h b/src/include/utils/rel.h
index d03ab247788..5feb18a5373 100644
--- a/src/include/utils/rel.h
+++ b/src/include/utils/rel.h
@@ -347,8 +347,7 @@ typedef struct StdRdOptions
bool user_catalog_table; /* use as an additional catalog relation */
int parallel_workers; /* max number of parallel workers */
StdRdOptIndexCleanup vacuum_index_cleanup; /* controls index vacuuming */
- bool vacuum_truncate; /* enables vacuum to truncate a relation */
- bool vacuum_truncate_set; /* whether vacuum_truncate is set */
+ pg_ternary vacuum_truncate; /* enables vacuum to truncate a relation */
/*
* Fraction of pages in a relation that vacuum can eagerly scan and fail
diff --git a/src/test/regress/expected/reloptions.out b/src/test/regress/expected/reloptions.out
index 9de19b4e3f1..1c99f79ab01 100644
--- a/src/test/regress/expected/reloptions.out
+++ b/src/test/regress/expected/reloptions.out
@@ -98,6 +98,42 @@ SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass;
{fillfactor=13,autovacuum_enabled=false}
(1 row)
+-- Tests for future (FIXME) ternary options
+-- behave as boolean option: accept unassigned name and truncated value
+DROP TABLE reloptions_test;
+CREATE TABLE reloptions_test(i INT) WITH (vacuum_truncate);
+SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass;
+ reloptions
+------------------------
+ {vacuum_truncate=true}
+(1 row)
+
+DROP TABLE reloptions_test;
+CREATE TABLE reloptions_test(i INT) WITH (vacuum_truncate=FaLS);
+SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass;
+ reloptions
+------------------------
+ {vacuum_truncate=fals}
+(1 row)
+
+-- preferred "true" alias is used when storing
+DROP TABLE reloptions_test;
+CREATE TABLE reloptions_test(i INT) WITH (vacuum_index_cleanup=on);
+SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass;
+ reloptions
+---------------------------
+ {vacuum_index_cleanup=on}
+(1 row)
+
+-- custom "third" value is available
+DROP TABLE reloptions_test;
+CREATE TABLE reloptions_test(i INT) WITH (vacuum_index_cleanup=auto);
+SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass;
+ reloptions
+-----------------------------
+ {vacuum_index_cleanup=auto}
+(1 row)
+
-- Test vacuum_truncate option
DROP TABLE reloptions_test;
CREATE TEMP TABLE reloptions_test(i INT NOT NULL, j text)
diff --git a/src/test/regress/sql/reloptions.sql b/src/test/regress/sql/reloptions.sql
index 24fbe0b478d..f5980dafcbc 100644
--- a/src/test/regress/sql/reloptions.sql
+++ b/src/test/regress/sql/reloptions.sql
@@ -59,6 +59,27 @@ UPDATE pg_class
ALTER TABLE reloptions_test RESET (illegal_option);
SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass;
+-- Tests for future (FIXME) ternary options
+
+-- behave as boolean option: accept unassigned name and truncated value
+DROP TABLE reloptions_test;
+CREATE TABLE reloptions_test(i INT) WITH (vacuum_truncate);
+SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass;
+
+DROP TABLE reloptions_test;
+CREATE TABLE reloptions_test(i INT) WITH (vacuum_truncate=FaLS);
+SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass;
+
+-- preferred "true" alias is used when storing
+DROP TABLE reloptions_test;
+CREATE TABLE reloptions_test(i INT) WITH (vacuum_index_cleanup=on);
+SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass;
+
+-- custom "third" value is available
+DROP TABLE reloptions_test;
+CREATE TABLE reloptions_test(i INT) WITH (vacuum_index_cleanup=auto);
+SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass;
+
-- Test vacuum_truncate option
DROP TABLE reloptions_test;
diff --git a/src/tools/pgindent/typedefs.list b/src/tools/pgindent/typedefs.list
index 3f3a888fd0e..1c8610fd46c 100644
--- a/src/tools/pgindent/typedefs.list
+++ b/src/tools/pgindent/typedefs.list
@@ -3952,6 +3952,7 @@ pg_sha512_ctx
pg_snapshot
pg_special_case
pg_stack_base_t
+pg_ternary
pg_time_t
pg_time_usec_t
pg_tz
@@ -4079,6 +4080,7 @@ relopt_kind
relopt_parse_elt
relopt_real
relopt_string
+relopt_ternary
relopt_type
relopt_value
relopts_validator
--
2.47.3
--c36t3fndxhkzbl5z--
^ permalink raw reply [nested|flat] 868+ messages in thread
* [PATCH v3] Introduce ternary reloptions
@ 2026-01-16 15:04 Álvaro Herrera <[email protected]>
0 siblings, 0 replies; 868+ messages in thread
From: Álvaro Herrera @ 2026-01-16 15:04 UTC (permalink / raw)
Introduce ternary reloption as a replacement for current
`vacuum_truncate` implementation. Remove the `vacuum_truncate_set`
separate flag and use TERNARY_UNSET instead.
This could also be used for other options such as `vacuum_index_cleanup`
and `buffering`, but lets get the scaffolding in first.
Discussion: https://postgr.es/m/3474141.usfYGdeWWP@thinkpad-pgpro
---
src/backend/access/common/reloptions.c | 137 ++++++++++++++++++-----
src/backend/commands/vacuum.c | 4 +-
src/include/access/reloptions.h | 27 ++---
src/include/postgres.h | 15 +++
src/include/utils/rel.h | 3 +-
src/test/regress/expected/reloptions.out | 36 ++++++
src/test/regress/sql/reloptions.sql | 21 ++++
src/tools/pgindent/typedefs.list | 2 +
8 files changed, 202 insertions(+), 43 deletions(-)
diff --git a/src/backend/access/common/reloptions.c b/src/backend/access/common/reloptions.c
index 0b83f98ed5f..6f1f577581a 100644
--- a/src/backend/access/common/reloptions.c
+++ b/src/backend/access/common/reloptions.c
@@ -40,9 +40,9 @@
*
* To add an option:
*
- * (i) decide on a type (bool, integer, real, enum, string), name, default
- * value, upper and lower bounds (if applicable); for strings, consider a
- * validation routine.
+ * (i) decide on a type (bool, ternary, integer, real, enum, string), name,
+ * default value, upper and lower bounds (if applicable); for strings,
+ * consider a validation routine.
* (ii) add a record below (or use add_<type>_reloption).
* (iii) add it to the appropriate options struct (perhaps StdRdOptions)
* (iv) add it to the appropriate handling routine (perhaps
@@ -147,15 +147,6 @@ static relopt_bool boolRelOpts[] =
},
false
},
- {
- {
- "vacuum_truncate",
- "Enables vacuum to truncate empty pages at the end of this table",
- RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
- ShareUpdateExclusiveLock
- },
- true
- },
{
{
"deduplicate_items",
@@ -170,6 +161,25 @@ static relopt_bool boolRelOpts[] =
{{NULL}}
};
+static relopt_ternary ternaryRelOpts[] =
+{
+ {
+ {
+ "vacuum_truncate",
+ "Enables vacuum to truncate empty pages at the end of this table",
+ RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
+ ShareUpdateExclusiveLock
+ },
+ TERNARY_UNSET
+ },
+ /* list terminator */
+ {
+ {
+ NULL
+ }
+ }
+};
+
static relopt_int intRelOpts[] =
{
{
@@ -609,6 +619,13 @@ initialize_reloptions(void)
boolRelOpts[i].gen.lockmode));
j++;
}
+ for (i = 0; ternaryRelOpts[i].gen.name; i++)
+ {
+ Assert(DoLockModesConflict(ternaryRelOpts[i].gen.lockmode,
+ ternaryRelOpts[i].gen.lockmode));
+ j++;
+ }
+
for (i = 0; intRelOpts[i].gen.name; i++)
{
Assert(DoLockModesConflict(intRelOpts[i].gen.lockmode,
@@ -649,6 +666,14 @@ initialize_reloptions(void)
j++;
}
+ for (i = 0; ternaryRelOpts[i].gen.name; i++)
+ {
+ relOpts[j] = &ternaryRelOpts[i].gen;
+ relOpts[j]->type = RELOPT_TYPE_TERNARY;
+ relOpts[j]->namelen = strlen(relOpts[j]->name);
+ j++;
+ }
+
for (i = 0; intRelOpts[i].gen.name; i++)
{
relOpts[j] = &intRelOpts[i].gen;
@@ -809,6 +834,9 @@ allocate_reloption(bits32 kinds, int type, const char *name, const char *desc,
case RELOPT_TYPE_BOOL:
size = sizeof(relopt_bool);
break;
+ case RELOPT_TYPE_TERNARY:
+ size = sizeof(relopt_ternary);
+ break;
case RELOPT_TYPE_INT:
size = sizeof(relopt_int);
break;
@@ -892,6 +920,57 @@ add_local_bool_reloption(local_relopts *relopts, const char *name,
add_local_reloption(relopts, (relopt_gen *) newoption, offset);
}
+/*
+ * init_ternary_reloption
+ * Allocate and initialize a new ternary reloption
+ */
+static relopt_ternary *
+init_ternary_reloption(bits32 kinds, const char *name, const char *desc,
+ pg_ternary default_val, LOCKMODE lockmode)
+{
+ relopt_ternary *newoption;
+
+ newoption = (relopt_ternary *)
+ allocate_reloption(kinds, RELOPT_TYPE_TERNARY, name, desc, lockmode);
+ newoption->default_val = default_val;
+
+ return newoption;
+}
+
+/*
+ * add_ternary_reloption
+ * Add a new ternary reloption
+ */
+void
+add_ternary_reloption(bits32 kinds, const char *name, const char *desc,
+ pg_ternary default_val, LOCKMODE lockmode)
+{
+ relopt_ternary *newoption;
+
+ newoption =
+ init_ternary_reloption(kinds, name, desc, default_val, lockmode);
+
+ add_reloption((relopt_gen *) newoption);
+}
+
+/*
+ * add_local_ternary_reloption
+ * Add a new ternary local reloption
+ *
+ * 'offset' is offset of ternary-typed field.
+ */
+void
+add_local_ternary_reloption(local_relopts *relopts, const char *name,
+ const char *desc, pg_ternary default_val,
+ int offset)
+{
+ relopt_ternary *newoption;
+
+ newoption =
+ init_ternary_reloption(RELOPT_KIND_LOCAL, name, desc, default_val, 0);
+
+ add_local_reloption(relopts, (relopt_gen *) newoption, offset);
+}
/*
* init_real_reloption
@@ -1626,6 +1705,19 @@ parse_one_reloption(relopt_value *option, char *text_str, int text_len,
option->gen->name, value)));
}
break;
+ case RELOPT_TYPE_TERNARY:
+ {
+ bool b;
+
+ parsed = parse_bool(value, &b);
+ option->values.ternary_val = b ? TERNARY_TRUE : TERNARY_FALSE;
+ if (validate && !parsed)
+ ereport(ERROR,
+ (errcode(ERRCODE_INVALID_PARAMETER_VALUE),
+ errmsg("invalid value for ternary option \"%s\": %s",
+ option->gen->name, value)));
+ }
+ break;
case RELOPT_TYPE_INT:
{
relopt_int *optint = (relopt_int *) option->gen;
@@ -1789,17 +1881,6 @@ fillRelOptions(void *rdopts, Size basesize,
char *itempos = ((char *) rdopts) + elems[j].offset;
char *string_val;
- /*
- * If isset_offset is provided, store whether the reloption is
- * set there.
- */
- if (elems[j].isset_offset > 0)
- {
- char *setpos = ((char *) rdopts) + elems[j].isset_offset;
-
- *(bool *) setpos = options[i].isset;
- }
-
switch (options[i].gen->type)
{
case RELOPT_TYPE_BOOL:
@@ -1807,6 +1888,11 @@ fillRelOptions(void *rdopts, Size basesize,
options[i].values.bool_val :
((relopt_bool *) options[i].gen)->default_val;
break;
+ case RELOPT_TYPE_TERNARY:
+ *(pg_ternary *) itempos = options[i].isset ?
+ options[i].values.ternary_val :
+ ((relopt_ternary *) options[i].gen)->default_val;
+ break;
case RELOPT_TYPE_INT:
*(int *) itempos = options[i].isset ?
options[i].values.int_val :
@@ -1923,8 +2009,8 @@ default_reloptions(Datum reloptions, bool validate, relopt_kind kind)
offsetof(StdRdOptions, parallel_workers)},
{"vacuum_index_cleanup", RELOPT_TYPE_ENUM,
offsetof(StdRdOptions, vacuum_index_cleanup)},
- {"vacuum_truncate", RELOPT_TYPE_BOOL,
- offsetof(StdRdOptions, vacuum_truncate), offsetof(StdRdOptions, vacuum_truncate_set)},
+ {"vacuum_truncate", RELOPT_TYPE_TERNARY,
+ offsetof(StdRdOptions, vacuum_truncate)},
{"vacuum_max_eager_freeze_failure_rate", RELOPT_TYPE_REAL,
offsetof(StdRdOptions, vacuum_max_eager_freeze_failure_rate)}
};
@@ -2004,7 +2090,6 @@ build_local_reloptions(local_relopts *relopts, Datum options, bool validate)
elems[i].optname = opt->option->name;
elems[i].opttype = opt->option->type;
elems[i].offset = opt->offset;
- elems[i].isset_offset = 0; /* not supported for local relopts yet */
i++;
}
diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c
index aa4fbec143f..696eab9bd97 100644
--- a/src/backend/commands/vacuum.c
+++ b/src/backend/commands/vacuum.c
@@ -2224,9 +2224,9 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams params,
{
StdRdOptions *opts = (StdRdOptions *) rel->rd_options;
- if (opts && opts->vacuum_truncate_set)
+ if (opts && opts->vacuum_truncate != TERNARY_UNSET)
{
- if (opts->vacuum_truncate)
+ if (opts->vacuum_truncate == TERNARY_TRUE)
params.truncate = VACOPTVALUE_ENABLED;
else
params.truncate = VACOPTVALUE_DISABLED;
diff --git a/src/include/access/reloptions.h b/src/include/access/reloptions.h
index 2f08e1b0cf0..dfbef2babf2 100644
--- a/src/include/access/reloptions.h
+++ b/src/include/access/reloptions.h
@@ -29,6 +29,7 @@
typedef enum relopt_type
{
RELOPT_TYPE_BOOL,
+ RELOPT_TYPE_TERNARY, /* on, off, unset */
RELOPT_TYPE_INT,
RELOPT_TYPE_REAL,
RELOPT_TYPE_ENUM,
@@ -80,6 +81,7 @@ typedef struct relopt_value
union
{
bool bool_val;
+ pg_ternary ternary_val;
int int_val;
double real_val;
int enum_val;
@@ -94,6 +96,12 @@ typedef struct relopt_bool
bool default_val;
} relopt_bool;
+typedef struct relopt_ternary
+{
+ relopt_gen gen;
+ int default_val;
+} relopt_ternary;
+
typedef struct relopt_int
{
relopt_gen gen;
@@ -152,19 +160,6 @@ typedef struct
const char *optname; /* option's name */
relopt_type opttype; /* option's datatype */
int offset; /* offset of field in result struct */
-
- /*
- * isset_offset is an optional offset of a field in the result struct that
- * stores whether the option is explicitly set for the relation or if it
- * just picked up the default value. In most cases, this can be
- * accomplished by giving the reloption a special out-of-range default
- * value (e.g., some integer reloptions use -2), but this isn't always
- * possible. For example, a Boolean reloption cannot be given an
- * out-of-range default, so we need another way to discover the source of
- * its value. This offset is only used if given a value greater than
- * zero.
- */
- int isset_offset;
} relopt_parse_elt;
/* Local reloption definition */
@@ -195,6 +190,9 @@ typedef struct local_relopts
extern relopt_kind add_reloption_kind(void);
extern void add_bool_reloption(bits32 kinds, const char *name, const char *desc,
bool default_val, LOCKMODE lockmode);
+extern void add_ternary_reloption(bits32 kinds, const char *name,
+ const char *desc, pg_ternary default_val,
+ LOCKMODE lockmode);
extern void add_int_reloption(bits32 kinds, const char *name, const char *desc,
int default_val, int min_val, int max_val,
LOCKMODE lockmode);
@@ -214,6 +212,9 @@ extern void register_reloptions_validator(local_relopts *relopts,
extern void add_local_bool_reloption(local_relopts *relopts, const char *name,
const char *desc, bool default_val,
int offset);
+extern void add_local_ternary_reloption(local_relopts *relopts,
+ const char *name, const char *desc,
+ pg_ternary default_val, int offset);
extern void add_local_int_reloption(local_relopts *relopts, const char *name,
const char *desc, int default_val,
int min_val, int max_val, int offset);
diff --git a/src/include/postgres.h b/src/include/postgres.h
index 7d93fbce709..47c88780776 100644
--- a/src/include/postgres.h
+++ b/src/include/postgres.h
@@ -543,6 +543,21 @@ Float8GetDatum(float8 X)
* ----------------------------------------------------------------
*/
+/*
+ * pg_ternary
+ * Boolean value with an extra "unset" value
+ *
+ * This enum can be used for values that want to distinguish between true,
+ * false, and unset.
+*/
+
+typedef enum pg_ternary
+{
+ TERNARY_FALSE = 0,
+ TERNARY_TRUE = 1,
+ TERNARY_UNSET = -1
+} pg_ternary;
+
/*
* NON_EXEC_STATIC: It's sometimes useful to define a variable or function
* that is normally static but extern when using EXEC_BACKEND (see
diff --git a/src/include/utils/rel.h b/src/include/utils/rel.h
index d03ab247788..5feb18a5373 100644
--- a/src/include/utils/rel.h
+++ b/src/include/utils/rel.h
@@ -347,8 +347,7 @@ typedef struct StdRdOptions
bool user_catalog_table; /* use as an additional catalog relation */
int parallel_workers; /* max number of parallel workers */
StdRdOptIndexCleanup vacuum_index_cleanup; /* controls index vacuuming */
- bool vacuum_truncate; /* enables vacuum to truncate a relation */
- bool vacuum_truncate_set; /* whether vacuum_truncate is set */
+ pg_ternary vacuum_truncate; /* enables vacuum to truncate a relation */
/*
* Fraction of pages in a relation that vacuum can eagerly scan and fail
diff --git a/src/test/regress/expected/reloptions.out b/src/test/regress/expected/reloptions.out
index 9de19b4e3f1..1c99f79ab01 100644
--- a/src/test/regress/expected/reloptions.out
+++ b/src/test/regress/expected/reloptions.out
@@ -98,6 +98,42 @@ SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass;
{fillfactor=13,autovacuum_enabled=false}
(1 row)
+-- Tests for future (FIXME) ternary options
+-- behave as boolean option: accept unassigned name and truncated value
+DROP TABLE reloptions_test;
+CREATE TABLE reloptions_test(i INT) WITH (vacuum_truncate);
+SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass;
+ reloptions
+------------------------
+ {vacuum_truncate=true}
+(1 row)
+
+DROP TABLE reloptions_test;
+CREATE TABLE reloptions_test(i INT) WITH (vacuum_truncate=FaLS);
+SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass;
+ reloptions
+------------------------
+ {vacuum_truncate=fals}
+(1 row)
+
+-- preferred "true" alias is used when storing
+DROP TABLE reloptions_test;
+CREATE TABLE reloptions_test(i INT) WITH (vacuum_index_cleanup=on);
+SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass;
+ reloptions
+---------------------------
+ {vacuum_index_cleanup=on}
+(1 row)
+
+-- custom "third" value is available
+DROP TABLE reloptions_test;
+CREATE TABLE reloptions_test(i INT) WITH (vacuum_index_cleanup=auto);
+SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass;
+ reloptions
+-----------------------------
+ {vacuum_index_cleanup=auto}
+(1 row)
+
-- Test vacuum_truncate option
DROP TABLE reloptions_test;
CREATE TEMP TABLE reloptions_test(i INT NOT NULL, j text)
diff --git a/src/test/regress/sql/reloptions.sql b/src/test/regress/sql/reloptions.sql
index 24fbe0b478d..f5980dafcbc 100644
--- a/src/test/regress/sql/reloptions.sql
+++ b/src/test/regress/sql/reloptions.sql
@@ -59,6 +59,27 @@ UPDATE pg_class
ALTER TABLE reloptions_test RESET (illegal_option);
SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass;
+-- Tests for future (FIXME) ternary options
+
+-- behave as boolean option: accept unassigned name and truncated value
+DROP TABLE reloptions_test;
+CREATE TABLE reloptions_test(i INT) WITH (vacuum_truncate);
+SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass;
+
+DROP TABLE reloptions_test;
+CREATE TABLE reloptions_test(i INT) WITH (vacuum_truncate=FaLS);
+SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass;
+
+-- preferred "true" alias is used when storing
+DROP TABLE reloptions_test;
+CREATE TABLE reloptions_test(i INT) WITH (vacuum_index_cleanup=on);
+SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass;
+
+-- custom "third" value is available
+DROP TABLE reloptions_test;
+CREATE TABLE reloptions_test(i INT) WITH (vacuum_index_cleanup=auto);
+SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass;
+
-- Test vacuum_truncate option
DROP TABLE reloptions_test;
diff --git a/src/tools/pgindent/typedefs.list b/src/tools/pgindent/typedefs.list
index 3f3a888fd0e..1c8610fd46c 100644
--- a/src/tools/pgindent/typedefs.list
+++ b/src/tools/pgindent/typedefs.list
@@ -3952,6 +3952,7 @@ pg_sha512_ctx
pg_snapshot
pg_special_case
pg_stack_base_t
+pg_ternary
pg_time_t
pg_time_usec_t
pg_tz
@@ -4079,6 +4080,7 @@ relopt_kind
relopt_parse_elt
relopt_real
relopt_string
+relopt_ternary
relopt_type
relopt_value
relopts_validator
--
2.47.3
--c36t3fndxhkzbl5z--
^ permalink raw reply [nested|flat] 868+ messages in thread
* [PATCH v3] Introduce ternary reloptions
@ 2026-01-16 15:04 Álvaro Herrera <[email protected]>
0 siblings, 0 replies; 868+ messages in thread
From: Álvaro Herrera @ 2026-01-16 15:04 UTC (permalink / raw)
Introduce ternary reloption as a replacement for current
`vacuum_truncate` implementation. Remove the `vacuum_truncate_set`
separate flag and use TERNARY_UNSET instead.
This could also be used for other options such as `vacuum_index_cleanup`
and `buffering`, but lets get the scaffolding in first.
Discussion: https://postgr.es/m/3474141.usfYGdeWWP@thinkpad-pgpro
---
src/backend/access/common/reloptions.c | 137 ++++++++++++++++++-----
src/backend/commands/vacuum.c | 4 +-
src/include/access/reloptions.h | 27 ++---
src/include/postgres.h | 15 +++
src/include/utils/rel.h | 3 +-
src/test/regress/expected/reloptions.out | 36 ++++++
src/test/regress/sql/reloptions.sql | 21 ++++
src/tools/pgindent/typedefs.list | 2 +
8 files changed, 202 insertions(+), 43 deletions(-)
diff --git a/src/backend/access/common/reloptions.c b/src/backend/access/common/reloptions.c
index 0b83f98ed5f..6f1f577581a 100644
--- a/src/backend/access/common/reloptions.c
+++ b/src/backend/access/common/reloptions.c
@@ -40,9 +40,9 @@
*
* To add an option:
*
- * (i) decide on a type (bool, integer, real, enum, string), name, default
- * value, upper and lower bounds (if applicable); for strings, consider a
- * validation routine.
+ * (i) decide on a type (bool, ternary, integer, real, enum, string), name,
+ * default value, upper and lower bounds (if applicable); for strings,
+ * consider a validation routine.
* (ii) add a record below (or use add_<type>_reloption).
* (iii) add it to the appropriate options struct (perhaps StdRdOptions)
* (iv) add it to the appropriate handling routine (perhaps
@@ -147,15 +147,6 @@ static relopt_bool boolRelOpts[] =
},
false
},
- {
- {
- "vacuum_truncate",
- "Enables vacuum to truncate empty pages at the end of this table",
- RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
- ShareUpdateExclusiveLock
- },
- true
- },
{
{
"deduplicate_items",
@@ -170,6 +161,25 @@ static relopt_bool boolRelOpts[] =
{{NULL}}
};
+static relopt_ternary ternaryRelOpts[] =
+{
+ {
+ {
+ "vacuum_truncate",
+ "Enables vacuum to truncate empty pages at the end of this table",
+ RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
+ ShareUpdateExclusiveLock
+ },
+ TERNARY_UNSET
+ },
+ /* list terminator */
+ {
+ {
+ NULL
+ }
+ }
+};
+
static relopt_int intRelOpts[] =
{
{
@@ -609,6 +619,13 @@ initialize_reloptions(void)
boolRelOpts[i].gen.lockmode));
j++;
}
+ for (i = 0; ternaryRelOpts[i].gen.name; i++)
+ {
+ Assert(DoLockModesConflict(ternaryRelOpts[i].gen.lockmode,
+ ternaryRelOpts[i].gen.lockmode));
+ j++;
+ }
+
for (i = 0; intRelOpts[i].gen.name; i++)
{
Assert(DoLockModesConflict(intRelOpts[i].gen.lockmode,
@@ -649,6 +666,14 @@ initialize_reloptions(void)
j++;
}
+ for (i = 0; ternaryRelOpts[i].gen.name; i++)
+ {
+ relOpts[j] = &ternaryRelOpts[i].gen;
+ relOpts[j]->type = RELOPT_TYPE_TERNARY;
+ relOpts[j]->namelen = strlen(relOpts[j]->name);
+ j++;
+ }
+
for (i = 0; intRelOpts[i].gen.name; i++)
{
relOpts[j] = &intRelOpts[i].gen;
@@ -809,6 +834,9 @@ allocate_reloption(bits32 kinds, int type, const char *name, const char *desc,
case RELOPT_TYPE_BOOL:
size = sizeof(relopt_bool);
break;
+ case RELOPT_TYPE_TERNARY:
+ size = sizeof(relopt_ternary);
+ break;
case RELOPT_TYPE_INT:
size = sizeof(relopt_int);
break;
@@ -892,6 +920,57 @@ add_local_bool_reloption(local_relopts *relopts, const char *name,
add_local_reloption(relopts, (relopt_gen *) newoption, offset);
}
+/*
+ * init_ternary_reloption
+ * Allocate and initialize a new ternary reloption
+ */
+static relopt_ternary *
+init_ternary_reloption(bits32 kinds, const char *name, const char *desc,
+ pg_ternary default_val, LOCKMODE lockmode)
+{
+ relopt_ternary *newoption;
+
+ newoption = (relopt_ternary *)
+ allocate_reloption(kinds, RELOPT_TYPE_TERNARY, name, desc, lockmode);
+ newoption->default_val = default_val;
+
+ return newoption;
+}
+
+/*
+ * add_ternary_reloption
+ * Add a new ternary reloption
+ */
+void
+add_ternary_reloption(bits32 kinds, const char *name, const char *desc,
+ pg_ternary default_val, LOCKMODE lockmode)
+{
+ relopt_ternary *newoption;
+
+ newoption =
+ init_ternary_reloption(kinds, name, desc, default_val, lockmode);
+
+ add_reloption((relopt_gen *) newoption);
+}
+
+/*
+ * add_local_ternary_reloption
+ * Add a new ternary local reloption
+ *
+ * 'offset' is offset of ternary-typed field.
+ */
+void
+add_local_ternary_reloption(local_relopts *relopts, const char *name,
+ const char *desc, pg_ternary default_val,
+ int offset)
+{
+ relopt_ternary *newoption;
+
+ newoption =
+ init_ternary_reloption(RELOPT_KIND_LOCAL, name, desc, default_val, 0);
+
+ add_local_reloption(relopts, (relopt_gen *) newoption, offset);
+}
/*
* init_real_reloption
@@ -1626,6 +1705,19 @@ parse_one_reloption(relopt_value *option, char *text_str, int text_len,
option->gen->name, value)));
}
break;
+ case RELOPT_TYPE_TERNARY:
+ {
+ bool b;
+
+ parsed = parse_bool(value, &b);
+ option->values.ternary_val = b ? TERNARY_TRUE : TERNARY_FALSE;
+ if (validate && !parsed)
+ ereport(ERROR,
+ (errcode(ERRCODE_INVALID_PARAMETER_VALUE),
+ errmsg("invalid value for ternary option \"%s\": %s",
+ option->gen->name, value)));
+ }
+ break;
case RELOPT_TYPE_INT:
{
relopt_int *optint = (relopt_int *) option->gen;
@@ -1789,17 +1881,6 @@ fillRelOptions(void *rdopts, Size basesize,
char *itempos = ((char *) rdopts) + elems[j].offset;
char *string_val;
- /*
- * If isset_offset is provided, store whether the reloption is
- * set there.
- */
- if (elems[j].isset_offset > 0)
- {
- char *setpos = ((char *) rdopts) + elems[j].isset_offset;
-
- *(bool *) setpos = options[i].isset;
- }
-
switch (options[i].gen->type)
{
case RELOPT_TYPE_BOOL:
@@ -1807,6 +1888,11 @@ fillRelOptions(void *rdopts, Size basesize,
options[i].values.bool_val :
((relopt_bool *) options[i].gen)->default_val;
break;
+ case RELOPT_TYPE_TERNARY:
+ *(pg_ternary *) itempos = options[i].isset ?
+ options[i].values.ternary_val :
+ ((relopt_ternary *) options[i].gen)->default_val;
+ break;
case RELOPT_TYPE_INT:
*(int *) itempos = options[i].isset ?
options[i].values.int_val :
@@ -1923,8 +2009,8 @@ default_reloptions(Datum reloptions, bool validate, relopt_kind kind)
offsetof(StdRdOptions, parallel_workers)},
{"vacuum_index_cleanup", RELOPT_TYPE_ENUM,
offsetof(StdRdOptions, vacuum_index_cleanup)},
- {"vacuum_truncate", RELOPT_TYPE_BOOL,
- offsetof(StdRdOptions, vacuum_truncate), offsetof(StdRdOptions, vacuum_truncate_set)},
+ {"vacuum_truncate", RELOPT_TYPE_TERNARY,
+ offsetof(StdRdOptions, vacuum_truncate)},
{"vacuum_max_eager_freeze_failure_rate", RELOPT_TYPE_REAL,
offsetof(StdRdOptions, vacuum_max_eager_freeze_failure_rate)}
};
@@ -2004,7 +2090,6 @@ build_local_reloptions(local_relopts *relopts, Datum options, bool validate)
elems[i].optname = opt->option->name;
elems[i].opttype = opt->option->type;
elems[i].offset = opt->offset;
- elems[i].isset_offset = 0; /* not supported for local relopts yet */
i++;
}
diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c
index aa4fbec143f..696eab9bd97 100644
--- a/src/backend/commands/vacuum.c
+++ b/src/backend/commands/vacuum.c
@@ -2224,9 +2224,9 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams params,
{
StdRdOptions *opts = (StdRdOptions *) rel->rd_options;
- if (opts && opts->vacuum_truncate_set)
+ if (opts && opts->vacuum_truncate != TERNARY_UNSET)
{
- if (opts->vacuum_truncate)
+ if (opts->vacuum_truncate == TERNARY_TRUE)
params.truncate = VACOPTVALUE_ENABLED;
else
params.truncate = VACOPTVALUE_DISABLED;
diff --git a/src/include/access/reloptions.h b/src/include/access/reloptions.h
index 2f08e1b0cf0..dfbef2babf2 100644
--- a/src/include/access/reloptions.h
+++ b/src/include/access/reloptions.h
@@ -29,6 +29,7 @@
typedef enum relopt_type
{
RELOPT_TYPE_BOOL,
+ RELOPT_TYPE_TERNARY, /* on, off, unset */
RELOPT_TYPE_INT,
RELOPT_TYPE_REAL,
RELOPT_TYPE_ENUM,
@@ -80,6 +81,7 @@ typedef struct relopt_value
union
{
bool bool_val;
+ pg_ternary ternary_val;
int int_val;
double real_val;
int enum_val;
@@ -94,6 +96,12 @@ typedef struct relopt_bool
bool default_val;
} relopt_bool;
+typedef struct relopt_ternary
+{
+ relopt_gen gen;
+ int default_val;
+} relopt_ternary;
+
typedef struct relopt_int
{
relopt_gen gen;
@@ -152,19 +160,6 @@ typedef struct
const char *optname; /* option's name */
relopt_type opttype; /* option's datatype */
int offset; /* offset of field in result struct */
-
- /*
- * isset_offset is an optional offset of a field in the result struct that
- * stores whether the option is explicitly set for the relation or if it
- * just picked up the default value. In most cases, this can be
- * accomplished by giving the reloption a special out-of-range default
- * value (e.g., some integer reloptions use -2), but this isn't always
- * possible. For example, a Boolean reloption cannot be given an
- * out-of-range default, so we need another way to discover the source of
- * its value. This offset is only used if given a value greater than
- * zero.
- */
- int isset_offset;
} relopt_parse_elt;
/* Local reloption definition */
@@ -195,6 +190,9 @@ typedef struct local_relopts
extern relopt_kind add_reloption_kind(void);
extern void add_bool_reloption(bits32 kinds, const char *name, const char *desc,
bool default_val, LOCKMODE lockmode);
+extern void add_ternary_reloption(bits32 kinds, const char *name,
+ const char *desc, pg_ternary default_val,
+ LOCKMODE lockmode);
extern void add_int_reloption(bits32 kinds, const char *name, const char *desc,
int default_val, int min_val, int max_val,
LOCKMODE lockmode);
@@ -214,6 +212,9 @@ extern void register_reloptions_validator(local_relopts *relopts,
extern void add_local_bool_reloption(local_relopts *relopts, const char *name,
const char *desc, bool default_val,
int offset);
+extern void add_local_ternary_reloption(local_relopts *relopts,
+ const char *name, const char *desc,
+ pg_ternary default_val, int offset);
extern void add_local_int_reloption(local_relopts *relopts, const char *name,
const char *desc, int default_val,
int min_val, int max_val, int offset);
diff --git a/src/include/postgres.h b/src/include/postgres.h
index 7d93fbce709..47c88780776 100644
--- a/src/include/postgres.h
+++ b/src/include/postgres.h
@@ -543,6 +543,21 @@ Float8GetDatum(float8 X)
* ----------------------------------------------------------------
*/
+/*
+ * pg_ternary
+ * Boolean value with an extra "unset" value
+ *
+ * This enum can be used for values that want to distinguish between true,
+ * false, and unset.
+*/
+
+typedef enum pg_ternary
+{
+ TERNARY_FALSE = 0,
+ TERNARY_TRUE = 1,
+ TERNARY_UNSET = -1
+} pg_ternary;
+
/*
* NON_EXEC_STATIC: It's sometimes useful to define a variable or function
* that is normally static but extern when using EXEC_BACKEND (see
diff --git a/src/include/utils/rel.h b/src/include/utils/rel.h
index d03ab247788..5feb18a5373 100644
--- a/src/include/utils/rel.h
+++ b/src/include/utils/rel.h
@@ -347,8 +347,7 @@ typedef struct StdRdOptions
bool user_catalog_table; /* use as an additional catalog relation */
int parallel_workers; /* max number of parallel workers */
StdRdOptIndexCleanup vacuum_index_cleanup; /* controls index vacuuming */
- bool vacuum_truncate; /* enables vacuum to truncate a relation */
- bool vacuum_truncate_set; /* whether vacuum_truncate is set */
+ pg_ternary vacuum_truncate; /* enables vacuum to truncate a relation */
/*
* Fraction of pages in a relation that vacuum can eagerly scan and fail
diff --git a/src/test/regress/expected/reloptions.out b/src/test/regress/expected/reloptions.out
index 9de19b4e3f1..1c99f79ab01 100644
--- a/src/test/regress/expected/reloptions.out
+++ b/src/test/regress/expected/reloptions.out
@@ -98,6 +98,42 @@ SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass;
{fillfactor=13,autovacuum_enabled=false}
(1 row)
+-- Tests for future (FIXME) ternary options
+-- behave as boolean option: accept unassigned name and truncated value
+DROP TABLE reloptions_test;
+CREATE TABLE reloptions_test(i INT) WITH (vacuum_truncate);
+SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass;
+ reloptions
+------------------------
+ {vacuum_truncate=true}
+(1 row)
+
+DROP TABLE reloptions_test;
+CREATE TABLE reloptions_test(i INT) WITH (vacuum_truncate=FaLS);
+SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass;
+ reloptions
+------------------------
+ {vacuum_truncate=fals}
+(1 row)
+
+-- preferred "true" alias is used when storing
+DROP TABLE reloptions_test;
+CREATE TABLE reloptions_test(i INT) WITH (vacuum_index_cleanup=on);
+SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass;
+ reloptions
+---------------------------
+ {vacuum_index_cleanup=on}
+(1 row)
+
+-- custom "third" value is available
+DROP TABLE reloptions_test;
+CREATE TABLE reloptions_test(i INT) WITH (vacuum_index_cleanup=auto);
+SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass;
+ reloptions
+-----------------------------
+ {vacuum_index_cleanup=auto}
+(1 row)
+
-- Test vacuum_truncate option
DROP TABLE reloptions_test;
CREATE TEMP TABLE reloptions_test(i INT NOT NULL, j text)
diff --git a/src/test/regress/sql/reloptions.sql b/src/test/regress/sql/reloptions.sql
index 24fbe0b478d..f5980dafcbc 100644
--- a/src/test/regress/sql/reloptions.sql
+++ b/src/test/regress/sql/reloptions.sql
@@ -59,6 +59,27 @@ UPDATE pg_class
ALTER TABLE reloptions_test RESET (illegal_option);
SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass;
+-- Tests for future (FIXME) ternary options
+
+-- behave as boolean option: accept unassigned name and truncated value
+DROP TABLE reloptions_test;
+CREATE TABLE reloptions_test(i INT) WITH (vacuum_truncate);
+SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass;
+
+DROP TABLE reloptions_test;
+CREATE TABLE reloptions_test(i INT) WITH (vacuum_truncate=FaLS);
+SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass;
+
+-- preferred "true" alias is used when storing
+DROP TABLE reloptions_test;
+CREATE TABLE reloptions_test(i INT) WITH (vacuum_index_cleanup=on);
+SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass;
+
+-- custom "third" value is available
+DROP TABLE reloptions_test;
+CREATE TABLE reloptions_test(i INT) WITH (vacuum_index_cleanup=auto);
+SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass;
+
-- Test vacuum_truncate option
DROP TABLE reloptions_test;
diff --git a/src/tools/pgindent/typedefs.list b/src/tools/pgindent/typedefs.list
index 3f3a888fd0e..1c8610fd46c 100644
--- a/src/tools/pgindent/typedefs.list
+++ b/src/tools/pgindent/typedefs.list
@@ -3952,6 +3952,7 @@ pg_sha512_ctx
pg_snapshot
pg_special_case
pg_stack_base_t
+pg_ternary
pg_time_t
pg_time_usec_t
pg_tz
@@ -4079,6 +4080,7 @@ relopt_kind
relopt_parse_elt
relopt_real
relopt_string
+relopt_ternary
relopt_type
relopt_value
relopts_validator
--
2.47.3
--c36t3fndxhkzbl5z--
^ permalink raw reply [nested|flat] 868+ messages in thread
* [PATCH v3] Introduce ternary reloptions
@ 2026-01-16 15:04 Álvaro Herrera <[email protected]>
0 siblings, 0 replies; 868+ messages in thread
From: Álvaro Herrera @ 2026-01-16 15:04 UTC (permalink / raw)
Introduce ternary reloption as a replacement for current
`vacuum_truncate` implementation. Remove the `vacuum_truncate_set`
separate flag and use TERNARY_UNSET instead.
This could also be used for other options such as `vacuum_index_cleanup`
and `buffering`, but lets get the scaffolding in first.
Discussion: https://postgr.es/m/3474141.usfYGdeWWP@thinkpad-pgpro
---
src/backend/access/common/reloptions.c | 137 ++++++++++++++++++-----
src/backend/commands/vacuum.c | 4 +-
src/include/access/reloptions.h | 27 ++---
src/include/postgres.h | 15 +++
src/include/utils/rel.h | 3 +-
src/test/regress/expected/reloptions.out | 36 ++++++
src/test/regress/sql/reloptions.sql | 21 ++++
src/tools/pgindent/typedefs.list | 2 +
8 files changed, 202 insertions(+), 43 deletions(-)
diff --git a/src/backend/access/common/reloptions.c b/src/backend/access/common/reloptions.c
index 0b83f98ed5f..6f1f577581a 100644
--- a/src/backend/access/common/reloptions.c
+++ b/src/backend/access/common/reloptions.c
@@ -40,9 +40,9 @@
*
* To add an option:
*
- * (i) decide on a type (bool, integer, real, enum, string), name, default
- * value, upper and lower bounds (if applicable); for strings, consider a
- * validation routine.
+ * (i) decide on a type (bool, ternary, integer, real, enum, string), name,
+ * default value, upper and lower bounds (if applicable); for strings,
+ * consider a validation routine.
* (ii) add a record below (or use add_<type>_reloption).
* (iii) add it to the appropriate options struct (perhaps StdRdOptions)
* (iv) add it to the appropriate handling routine (perhaps
@@ -147,15 +147,6 @@ static relopt_bool boolRelOpts[] =
},
false
},
- {
- {
- "vacuum_truncate",
- "Enables vacuum to truncate empty pages at the end of this table",
- RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
- ShareUpdateExclusiveLock
- },
- true
- },
{
{
"deduplicate_items",
@@ -170,6 +161,25 @@ static relopt_bool boolRelOpts[] =
{{NULL}}
};
+static relopt_ternary ternaryRelOpts[] =
+{
+ {
+ {
+ "vacuum_truncate",
+ "Enables vacuum to truncate empty pages at the end of this table",
+ RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
+ ShareUpdateExclusiveLock
+ },
+ TERNARY_UNSET
+ },
+ /* list terminator */
+ {
+ {
+ NULL
+ }
+ }
+};
+
static relopt_int intRelOpts[] =
{
{
@@ -609,6 +619,13 @@ initialize_reloptions(void)
boolRelOpts[i].gen.lockmode));
j++;
}
+ for (i = 0; ternaryRelOpts[i].gen.name; i++)
+ {
+ Assert(DoLockModesConflict(ternaryRelOpts[i].gen.lockmode,
+ ternaryRelOpts[i].gen.lockmode));
+ j++;
+ }
+
for (i = 0; intRelOpts[i].gen.name; i++)
{
Assert(DoLockModesConflict(intRelOpts[i].gen.lockmode,
@@ -649,6 +666,14 @@ initialize_reloptions(void)
j++;
}
+ for (i = 0; ternaryRelOpts[i].gen.name; i++)
+ {
+ relOpts[j] = &ternaryRelOpts[i].gen;
+ relOpts[j]->type = RELOPT_TYPE_TERNARY;
+ relOpts[j]->namelen = strlen(relOpts[j]->name);
+ j++;
+ }
+
for (i = 0; intRelOpts[i].gen.name; i++)
{
relOpts[j] = &intRelOpts[i].gen;
@@ -809,6 +834,9 @@ allocate_reloption(bits32 kinds, int type, const char *name, const char *desc,
case RELOPT_TYPE_BOOL:
size = sizeof(relopt_bool);
break;
+ case RELOPT_TYPE_TERNARY:
+ size = sizeof(relopt_ternary);
+ break;
case RELOPT_TYPE_INT:
size = sizeof(relopt_int);
break;
@@ -892,6 +920,57 @@ add_local_bool_reloption(local_relopts *relopts, const char *name,
add_local_reloption(relopts, (relopt_gen *) newoption, offset);
}
+/*
+ * init_ternary_reloption
+ * Allocate and initialize a new ternary reloption
+ */
+static relopt_ternary *
+init_ternary_reloption(bits32 kinds, const char *name, const char *desc,
+ pg_ternary default_val, LOCKMODE lockmode)
+{
+ relopt_ternary *newoption;
+
+ newoption = (relopt_ternary *)
+ allocate_reloption(kinds, RELOPT_TYPE_TERNARY, name, desc, lockmode);
+ newoption->default_val = default_val;
+
+ return newoption;
+}
+
+/*
+ * add_ternary_reloption
+ * Add a new ternary reloption
+ */
+void
+add_ternary_reloption(bits32 kinds, const char *name, const char *desc,
+ pg_ternary default_val, LOCKMODE lockmode)
+{
+ relopt_ternary *newoption;
+
+ newoption =
+ init_ternary_reloption(kinds, name, desc, default_val, lockmode);
+
+ add_reloption((relopt_gen *) newoption);
+}
+
+/*
+ * add_local_ternary_reloption
+ * Add a new ternary local reloption
+ *
+ * 'offset' is offset of ternary-typed field.
+ */
+void
+add_local_ternary_reloption(local_relopts *relopts, const char *name,
+ const char *desc, pg_ternary default_val,
+ int offset)
+{
+ relopt_ternary *newoption;
+
+ newoption =
+ init_ternary_reloption(RELOPT_KIND_LOCAL, name, desc, default_val, 0);
+
+ add_local_reloption(relopts, (relopt_gen *) newoption, offset);
+}
/*
* init_real_reloption
@@ -1626,6 +1705,19 @@ parse_one_reloption(relopt_value *option, char *text_str, int text_len,
option->gen->name, value)));
}
break;
+ case RELOPT_TYPE_TERNARY:
+ {
+ bool b;
+
+ parsed = parse_bool(value, &b);
+ option->values.ternary_val = b ? TERNARY_TRUE : TERNARY_FALSE;
+ if (validate && !parsed)
+ ereport(ERROR,
+ (errcode(ERRCODE_INVALID_PARAMETER_VALUE),
+ errmsg("invalid value for ternary option \"%s\": %s",
+ option->gen->name, value)));
+ }
+ break;
case RELOPT_TYPE_INT:
{
relopt_int *optint = (relopt_int *) option->gen;
@@ -1789,17 +1881,6 @@ fillRelOptions(void *rdopts, Size basesize,
char *itempos = ((char *) rdopts) + elems[j].offset;
char *string_val;
- /*
- * If isset_offset is provided, store whether the reloption is
- * set there.
- */
- if (elems[j].isset_offset > 0)
- {
- char *setpos = ((char *) rdopts) + elems[j].isset_offset;
-
- *(bool *) setpos = options[i].isset;
- }
-
switch (options[i].gen->type)
{
case RELOPT_TYPE_BOOL:
@@ -1807,6 +1888,11 @@ fillRelOptions(void *rdopts, Size basesize,
options[i].values.bool_val :
((relopt_bool *) options[i].gen)->default_val;
break;
+ case RELOPT_TYPE_TERNARY:
+ *(pg_ternary *) itempos = options[i].isset ?
+ options[i].values.ternary_val :
+ ((relopt_ternary *) options[i].gen)->default_val;
+ break;
case RELOPT_TYPE_INT:
*(int *) itempos = options[i].isset ?
options[i].values.int_val :
@@ -1923,8 +2009,8 @@ default_reloptions(Datum reloptions, bool validate, relopt_kind kind)
offsetof(StdRdOptions, parallel_workers)},
{"vacuum_index_cleanup", RELOPT_TYPE_ENUM,
offsetof(StdRdOptions, vacuum_index_cleanup)},
- {"vacuum_truncate", RELOPT_TYPE_BOOL,
- offsetof(StdRdOptions, vacuum_truncate), offsetof(StdRdOptions, vacuum_truncate_set)},
+ {"vacuum_truncate", RELOPT_TYPE_TERNARY,
+ offsetof(StdRdOptions, vacuum_truncate)},
{"vacuum_max_eager_freeze_failure_rate", RELOPT_TYPE_REAL,
offsetof(StdRdOptions, vacuum_max_eager_freeze_failure_rate)}
};
@@ -2004,7 +2090,6 @@ build_local_reloptions(local_relopts *relopts, Datum options, bool validate)
elems[i].optname = opt->option->name;
elems[i].opttype = opt->option->type;
elems[i].offset = opt->offset;
- elems[i].isset_offset = 0; /* not supported for local relopts yet */
i++;
}
diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c
index aa4fbec143f..696eab9bd97 100644
--- a/src/backend/commands/vacuum.c
+++ b/src/backend/commands/vacuum.c
@@ -2224,9 +2224,9 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams params,
{
StdRdOptions *opts = (StdRdOptions *) rel->rd_options;
- if (opts && opts->vacuum_truncate_set)
+ if (opts && opts->vacuum_truncate != TERNARY_UNSET)
{
- if (opts->vacuum_truncate)
+ if (opts->vacuum_truncate == TERNARY_TRUE)
params.truncate = VACOPTVALUE_ENABLED;
else
params.truncate = VACOPTVALUE_DISABLED;
diff --git a/src/include/access/reloptions.h b/src/include/access/reloptions.h
index 2f08e1b0cf0..dfbef2babf2 100644
--- a/src/include/access/reloptions.h
+++ b/src/include/access/reloptions.h
@@ -29,6 +29,7 @@
typedef enum relopt_type
{
RELOPT_TYPE_BOOL,
+ RELOPT_TYPE_TERNARY, /* on, off, unset */
RELOPT_TYPE_INT,
RELOPT_TYPE_REAL,
RELOPT_TYPE_ENUM,
@@ -80,6 +81,7 @@ typedef struct relopt_value
union
{
bool bool_val;
+ pg_ternary ternary_val;
int int_val;
double real_val;
int enum_val;
@@ -94,6 +96,12 @@ typedef struct relopt_bool
bool default_val;
} relopt_bool;
+typedef struct relopt_ternary
+{
+ relopt_gen gen;
+ int default_val;
+} relopt_ternary;
+
typedef struct relopt_int
{
relopt_gen gen;
@@ -152,19 +160,6 @@ typedef struct
const char *optname; /* option's name */
relopt_type opttype; /* option's datatype */
int offset; /* offset of field in result struct */
-
- /*
- * isset_offset is an optional offset of a field in the result struct that
- * stores whether the option is explicitly set for the relation or if it
- * just picked up the default value. In most cases, this can be
- * accomplished by giving the reloption a special out-of-range default
- * value (e.g., some integer reloptions use -2), but this isn't always
- * possible. For example, a Boolean reloption cannot be given an
- * out-of-range default, so we need another way to discover the source of
- * its value. This offset is only used if given a value greater than
- * zero.
- */
- int isset_offset;
} relopt_parse_elt;
/* Local reloption definition */
@@ -195,6 +190,9 @@ typedef struct local_relopts
extern relopt_kind add_reloption_kind(void);
extern void add_bool_reloption(bits32 kinds, const char *name, const char *desc,
bool default_val, LOCKMODE lockmode);
+extern void add_ternary_reloption(bits32 kinds, const char *name,
+ const char *desc, pg_ternary default_val,
+ LOCKMODE lockmode);
extern void add_int_reloption(bits32 kinds, const char *name, const char *desc,
int default_val, int min_val, int max_val,
LOCKMODE lockmode);
@@ -214,6 +212,9 @@ extern void register_reloptions_validator(local_relopts *relopts,
extern void add_local_bool_reloption(local_relopts *relopts, const char *name,
const char *desc, bool default_val,
int offset);
+extern void add_local_ternary_reloption(local_relopts *relopts,
+ const char *name, const char *desc,
+ pg_ternary default_val, int offset);
extern void add_local_int_reloption(local_relopts *relopts, const char *name,
const char *desc, int default_val,
int min_val, int max_val, int offset);
diff --git a/src/include/postgres.h b/src/include/postgres.h
index 7d93fbce709..47c88780776 100644
--- a/src/include/postgres.h
+++ b/src/include/postgres.h
@@ -543,6 +543,21 @@ Float8GetDatum(float8 X)
* ----------------------------------------------------------------
*/
+/*
+ * pg_ternary
+ * Boolean value with an extra "unset" value
+ *
+ * This enum can be used for values that want to distinguish between true,
+ * false, and unset.
+*/
+
+typedef enum pg_ternary
+{
+ TERNARY_FALSE = 0,
+ TERNARY_TRUE = 1,
+ TERNARY_UNSET = -1
+} pg_ternary;
+
/*
* NON_EXEC_STATIC: It's sometimes useful to define a variable or function
* that is normally static but extern when using EXEC_BACKEND (see
diff --git a/src/include/utils/rel.h b/src/include/utils/rel.h
index d03ab247788..5feb18a5373 100644
--- a/src/include/utils/rel.h
+++ b/src/include/utils/rel.h
@@ -347,8 +347,7 @@ typedef struct StdRdOptions
bool user_catalog_table; /* use as an additional catalog relation */
int parallel_workers; /* max number of parallel workers */
StdRdOptIndexCleanup vacuum_index_cleanup; /* controls index vacuuming */
- bool vacuum_truncate; /* enables vacuum to truncate a relation */
- bool vacuum_truncate_set; /* whether vacuum_truncate is set */
+ pg_ternary vacuum_truncate; /* enables vacuum to truncate a relation */
/*
* Fraction of pages in a relation that vacuum can eagerly scan and fail
diff --git a/src/test/regress/expected/reloptions.out b/src/test/regress/expected/reloptions.out
index 9de19b4e3f1..1c99f79ab01 100644
--- a/src/test/regress/expected/reloptions.out
+++ b/src/test/regress/expected/reloptions.out
@@ -98,6 +98,42 @@ SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass;
{fillfactor=13,autovacuum_enabled=false}
(1 row)
+-- Tests for future (FIXME) ternary options
+-- behave as boolean option: accept unassigned name and truncated value
+DROP TABLE reloptions_test;
+CREATE TABLE reloptions_test(i INT) WITH (vacuum_truncate);
+SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass;
+ reloptions
+------------------------
+ {vacuum_truncate=true}
+(1 row)
+
+DROP TABLE reloptions_test;
+CREATE TABLE reloptions_test(i INT) WITH (vacuum_truncate=FaLS);
+SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass;
+ reloptions
+------------------------
+ {vacuum_truncate=fals}
+(1 row)
+
+-- preferred "true" alias is used when storing
+DROP TABLE reloptions_test;
+CREATE TABLE reloptions_test(i INT) WITH (vacuum_index_cleanup=on);
+SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass;
+ reloptions
+---------------------------
+ {vacuum_index_cleanup=on}
+(1 row)
+
+-- custom "third" value is available
+DROP TABLE reloptions_test;
+CREATE TABLE reloptions_test(i INT) WITH (vacuum_index_cleanup=auto);
+SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass;
+ reloptions
+-----------------------------
+ {vacuum_index_cleanup=auto}
+(1 row)
+
-- Test vacuum_truncate option
DROP TABLE reloptions_test;
CREATE TEMP TABLE reloptions_test(i INT NOT NULL, j text)
diff --git a/src/test/regress/sql/reloptions.sql b/src/test/regress/sql/reloptions.sql
index 24fbe0b478d..f5980dafcbc 100644
--- a/src/test/regress/sql/reloptions.sql
+++ b/src/test/regress/sql/reloptions.sql
@@ -59,6 +59,27 @@ UPDATE pg_class
ALTER TABLE reloptions_test RESET (illegal_option);
SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass;
+-- Tests for future (FIXME) ternary options
+
+-- behave as boolean option: accept unassigned name and truncated value
+DROP TABLE reloptions_test;
+CREATE TABLE reloptions_test(i INT) WITH (vacuum_truncate);
+SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass;
+
+DROP TABLE reloptions_test;
+CREATE TABLE reloptions_test(i INT) WITH (vacuum_truncate=FaLS);
+SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass;
+
+-- preferred "true" alias is used when storing
+DROP TABLE reloptions_test;
+CREATE TABLE reloptions_test(i INT) WITH (vacuum_index_cleanup=on);
+SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass;
+
+-- custom "third" value is available
+DROP TABLE reloptions_test;
+CREATE TABLE reloptions_test(i INT) WITH (vacuum_index_cleanup=auto);
+SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass;
+
-- Test vacuum_truncate option
DROP TABLE reloptions_test;
diff --git a/src/tools/pgindent/typedefs.list b/src/tools/pgindent/typedefs.list
index 3f3a888fd0e..1c8610fd46c 100644
--- a/src/tools/pgindent/typedefs.list
+++ b/src/tools/pgindent/typedefs.list
@@ -3952,6 +3952,7 @@ pg_sha512_ctx
pg_snapshot
pg_special_case
pg_stack_base_t
+pg_ternary
pg_time_t
pg_time_usec_t
pg_tz
@@ -4079,6 +4080,7 @@ relopt_kind
relopt_parse_elt
relopt_real
relopt_string
+relopt_ternary
relopt_type
relopt_value
relopts_validator
--
2.47.3
--c36t3fndxhkzbl5z--
^ permalink raw reply [nested|flat] 868+ messages in thread
* [PATCH v3] Introduce ternary reloptions
@ 2026-01-16 15:04 Álvaro Herrera <[email protected]>
0 siblings, 0 replies; 868+ messages in thread
From: Álvaro Herrera @ 2026-01-16 15:04 UTC (permalink / raw)
Introduce ternary reloption as a replacement for current
`vacuum_truncate` implementation. Remove the `vacuum_truncate_set`
separate flag and use TERNARY_UNSET instead.
This could also be used for other options such as `vacuum_index_cleanup`
and `buffering`, but lets get the scaffolding in first.
Discussion: https://postgr.es/m/3474141.usfYGdeWWP@thinkpad-pgpro
---
src/backend/access/common/reloptions.c | 137 ++++++++++++++++++-----
src/backend/commands/vacuum.c | 4 +-
src/include/access/reloptions.h | 27 ++---
src/include/postgres.h | 15 +++
src/include/utils/rel.h | 3 +-
src/test/regress/expected/reloptions.out | 36 ++++++
src/test/regress/sql/reloptions.sql | 21 ++++
src/tools/pgindent/typedefs.list | 2 +
8 files changed, 202 insertions(+), 43 deletions(-)
diff --git a/src/backend/access/common/reloptions.c b/src/backend/access/common/reloptions.c
index 0b83f98ed5f..6f1f577581a 100644
--- a/src/backend/access/common/reloptions.c
+++ b/src/backend/access/common/reloptions.c
@@ -40,9 +40,9 @@
*
* To add an option:
*
- * (i) decide on a type (bool, integer, real, enum, string), name, default
- * value, upper and lower bounds (if applicable); for strings, consider a
- * validation routine.
+ * (i) decide on a type (bool, ternary, integer, real, enum, string), name,
+ * default value, upper and lower bounds (if applicable); for strings,
+ * consider a validation routine.
* (ii) add a record below (or use add_<type>_reloption).
* (iii) add it to the appropriate options struct (perhaps StdRdOptions)
* (iv) add it to the appropriate handling routine (perhaps
@@ -147,15 +147,6 @@ static relopt_bool boolRelOpts[] =
},
false
},
- {
- {
- "vacuum_truncate",
- "Enables vacuum to truncate empty pages at the end of this table",
- RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
- ShareUpdateExclusiveLock
- },
- true
- },
{
{
"deduplicate_items",
@@ -170,6 +161,25 @@ static relopt_bool boolRelOpts[] =
{{NULL}}
};
+static relopt_ternary ternaryRelOpts[] =
+{
+ {
+ {
+ "vacuum_truncate",
+ "Enables vacuum to truncate empty pages at the end of this table",
+ RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
+ ShareUpdateExclusiveLock
+ },
+ TERNARY_UNSET
+ },
+ /* list terminator */
+ {
+ {
+ NULL
+ }
+ }
+};
+
static relopt_int intRelOpts[] =
{
{
@@ -609,6 +619,13 @@ initialize_reloptions(void)
boolRelOpts[i].gen.lockmode));
j++;
}
+ for (i = 0; ternaryRelOpts[i].gen.name; i++)
+ {
+ Assert(DoLockModesConflict(ternaryRelOpts[i].gen.lockmode,
+ ternaryRelOpts[i].gen.lockmode));
+ j++;
+ }
+
for (i = 0; intRelOpts[i].gen.name; i++)
{
Assert(DoLockModesConflict(intRelOpts[i].gen.lockmode,
@@ -649,6 +666,14 @@ initialize_reloptions(void)
j++;
}
+ for (i = 0; ternaryRelOpts[i].gen.name; i++)
+ {
+ relOpts[j] = &ternaryRelOpts[i].gen;
+ relOpts[j]->type = RELOPT_TYPE_TERNARY;
+ relOpts[j]->namelen = strlen(relOpts[j]->name);
+ j++;
+ }
+
for (i = 0; intRelOpts[i].gen.name; i++)
{
relOpts[j] = &intRelOpts[i].gen;
@@ -809,6 +834,9 @@ allocate_reloption(bits32 kinds, int type, const char *name, const char *desc,
case RELOPT_TYPE_BOOL:
size = sizeof(relopt_bool);
break;
+ case RELOPT_TYPE_TERNARY:
+ size = sizeof(relopt_ternary);
+ break;
case RELOPT_TYPE_INT:
size = sizeof(relopt_int);
break;
@@ -892,6 +920,57 @@ add_local_bool_reloption(local_relopts *relopts, const char *name,
add_local_reloption(relopts, (relopt_gen *) newoption, offset);
}
+/*
+ * init_ternary_reloption
+ * Allocate and initialize a new ternary reloption
+ */
+static relopt_ternary *
+init_ternary_reloption(bits32 kinds, const char *name, const char *desc,
+ pg_ternary default_val, LOCKMODE lockmode)
+{
+ relopt_ternary *newoption;
+
+ newoption = (relopt_ternary *)
+ allocate_reloption(kinds, RELOPT_TYPE_TERNARY, name, desc, lockmode);
+ newoption->default_val = default_val;
+
+ return newoption;
+}
+
+/*
+ * add_ternary_reloption
+ * Add a new ternary reloption
+ */
+void
+add_ternary_reloption(bits32 kinds, const char *name, const char *desc,
+ pg_ternary default_val, LOCKMODE lockmode)
+{
+ relopt_ternary *newoption;
+
+ newoption =
+ init_ternary_reloption(kinds, name, desc, default_val, lockmode);
+
+ add_reloption((relopt_gen *) newoption);
+}
+
+/*
+ * add_local_ternary_reloption
+ * Add a new ternary local reloption
+ *
+ * 'offset' is offset of ternary-typed field.
+ */
+void
+add_local_ternary_reloption(local_relopts *relopts, const char *name,
+ const char *desc, pg_ternary default_val,
+ int offset)
+{
+ relopt_ternary *newoption;
+
+ newoption =
+ init_ternary_reloption(RELOPT_KIND_LOCAL, name, desc, default_val, 0);
+
+ add_local_reloption(relopts, (relopt_gen *) newoption, offset);
+}
/*
* init_real_reloption
@@ -1626,6 +1705,19 @@ parse_one_reloption(relopt_value *option, char *text_str, int text_len,
option->gen->name, value)));
}
break;
+ case RELOPT_TYPE_TERNARY:
+ {
+ bool b;
+
+ parsed = parse_bool(value, &b);
+ option->values.ternary_val = b ? TERNARY_TRUE : TERNARY_FALSE;
+ if (validate && !parsed)
+ ereport(ERROR,
+ (errcode(ERRCODE_INVALID_PARAMETER_VALUE),
+ errmsg("invalid value for ternary option \"%s\": %s",
+ option->gen->name, value)));
+ }
+ break;
case RELOPT_TYPE_INT:
{
relopt_int *optint = (relopt_int *) option->gen;
@@ -1789,17 +1881,6 @@ fillRelOptions(void *rdopts, Size basesize,
char *itempos = ((char *) rdopts) + elems[j].offset;
char *string_val;
- /*
- * If isset_offset is provided, store whether the reloption is
- * set there.
- */
- if (elems[j].isset_offset > 0)
- {
- char *setpos = ((char *) rdopts) + elems[j].isset_offset;
-
- *(bool *) setpos = options[i].isset;
- }
-
switch (options[i].gen->type)
{
case RELOPT_TYPE_BOOL:
@@ -1807,6 +1888,11 @@ fillRelOptions(void *rdopts, Size basesize,
options[i].values.bool_val :
((relopt_bool *) options[i].gen)->default_val;
break;
+ case RELOPT_TYPE_TERNARY:
+ *(pg_ternary *) itempos = options[i].isset ?
+ options[i].values.ternary_val :
+ ((relopt_ternary *) options[i].gen)->default_val;
+ break;
case RELOPT_TYPE_INT:
*(int *) itempos = options[i].isset ?
options[i].values.int_val :
@@ -1923,8 +2009,8 @@ default_reloptions(Datum reloptions, bool validate, relopt_kind kind)
offsetof(StdRdOptions, parallel_workers)},
{"vacuum_index_cleanup", RELOPT_TYPE_ENUM,
offsetof(StdRdOptions, vacuum_index_cleanup)},
- {"vacuum_truncate", RELOPT_TYPE_BOOL,
- offsetof(StdRdOptions, vacuum_truncate), offsetof(StdRdOptions, vacuum_truncate_set)},
+ {"vacuum_truncate", RELOPT_TYPE_TERNARY,
+ offsetof(StdRdOptions, vacuum_truncate)},
{"vacuum_max_eager_freeze_failure_rate", RELOPT_TYPE_REAL,
offsetof(StdRdOptions, vacuum_max_eager_freeze_failure_rate)}
};
@@ -2004,7 +2090,6 @@ build_local_reloptions(local_relopts *relopts, Datum options, bool validate)
elems[i].optname = opt->option->name;
elems[i].opttype = opt->option->type;
elems[i].offset = opt->offset;
- elems[i].isset_offset = 0; /* not supported for local relopts yet */
i++;
}
diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c
index aa4fbec143f..696eab9bd97 100644
--- a/src/backend/commands/vacuum.c
+++ b/src/backend/commands/vacuum.c
@@ -2224,9 +2224,9 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams params,
{
StdRdOptions *opts = (StdRdOptions *) rel->rd_options;
- if (opts && opts->vacuum_truncate_set)
+ if (opts && opts->vacuum_truncate != TERNARY_UNSET)
{
- if (opts->vacuum_truncate)
+ if (opts->vacuum_truncate == TERNARY_TRUE)
params.truncate = VACOPTVALUE_ENABLED;
else
params.truncate = VACOPTVALUE_DISABLED;
diff --git a/src/include/access/reloptions.h b/src/include/access/reloptions.h
index 2f08e1b0cf0..dfbef2babf2 100644
--- a/src/include/access/reloptions.h
+++ b/src/include/access/reloptions.h
@@ -29,6 +29,7 @@
typedef enum relopt_type
{
RELOPT_TYPE_BOOL,
+ RELOPT_TYPE_TERNARY, /* on, off, unset */
RELOPT_TYPE_INT,
RELOPT_TYPE_REAL,
RELOPT_TYPE_ENUM,
@@ -80,6 +81,7 @@ typedef struct relopt_value
union
{
bool bool_val;
+ pg_ternary ternary_val;
int int_val;
double real_val;
int enum_val;
@@ -94,6 +96,12 @@ typedef struct relopt_bool
bool default_val;
} relopt_bool;
+typedef struct relopt_ternary
+{
+ relopt_gen gen;
+ int default_val;
+} relopt_ternary;
+
typedef struct relopt_int
{
relopt_gen gen;
@@ -152,19 +160,6 @@ typedef struct
const char *optname; /* option's name */
relopt_type opttype; /* option's datatype */
int offset; /* offset of field in result struct */
-
- /*
- * isset_offset is an optional offset of a field in the result struct that
- * stores whether the option is explicitly set for the relation or if it
- * just picked up the default value. In most cases, this can be
- * accomplished by giving the reloption a special out-of-range default
- * value (e.g., some integer reloptions use -2), but this isn't always
- * possible. For example, a Boolean reloption cannot be given an
- * out-of-range default, so we need another way to discover the source of
- * its value. This offset is only used if given a value greater than
- * zero.
- */
- int isset_offset;
} relopt_parse_elt;
/* Local reloption definition */
@@ -195,6 +190,9 @@ typedef struct local_relopts
extern relopt_kind add_reloption_kind(void);
extern void add_bool_reloption(bits32 kinds, const char *name, const char *desc,
bool default_val, LOCKMODE lockmode);
+extern void add_ternary_reloption(bits32 kinds, const char *name,
+ const char *desc, pg_ternary default_val,
+ LOCKMODE lockmode);
extern void add_int_reloption(bits32 kinds, const char *name, const char *desc,
int default_val, int min_val, int max_val,
LOCKMODE lockmode);
@@ -214,6 +212,9 @@ extern void register_reloptions_validator(local_relopts *relopts,
extern void add_local_bool_reloption(local_relopts *relopts, const char *name,
const char *desc, bool default_val,
int offset);
+extern void add_local_ternary_reloption(local_relopts *relopts,
+ const char *name, const char *desc,
+ pg_ternary default_val, int offset);
extern void add_local_int_reloption(local_relopts *relopts, const char *name,
const char *desc, int default_val,
int min_val, int max_val, int offset);
diff --git a/src/include/postgres.h b/src/include/postgres.h
index 7d93fbce709..47c88780776 100644
--- a/src/include/postgres.h
+++ b/src/include/postgres.h
@@ -543,6 +543,21 @@ Float8GetDatum(float8 X)
* ----------------------------------------------------------------
*/
+/*
+ * pg_ternary
+ * Boolean value with an extra "unset" value
+ *
+ * This enum can be used for values that want to distinguish between true,
+ * false, and unset.
+*/
+
+typedef enum pg_ternary
+{
+ TERNARY_FALSE = 0,
+ TERNARY_TRUE = 1,
+ TERNARY_UNSET = -1
+} pg_ternary;
+
/*
* NON_EXEC_STATIC: It's sometimes useful to define a variable or function
* that is normally static but extern when using EXEC_BACKEND (see
diff --git a/src/include/utils/rel.h b/src/include/utils/rel.h
index d03ab247788..5feb18a5373 100644
--- a/src/include/utils/rel.h
+++ b/src/include/utils/rel.h
@@ -347,8 +347,7 @@ typedef struct StdRdOptions
bool user_catalog_table; /* use as an additional catalog relation */
int parallel_workers; /* max number of parallel workers */
StdRdOptIndexCleanup vacuum_index_cleanup; /* controls index vacuuming */
- bool vacuum_truncate; /* enables vacuum to truncate a relation */
- bool vacuum_truncate_set; /* whether vacuum_truncate is set */
+ pg_ternary vacuum_truncate; /* enables vacuum to truncate a relation */
/*
* Fraction of pages in a relation that vacuum can eagerly scan and fail
diff --git a/src/test/regress/expected/reloptions.out b/src/test/regress/expected/reloptions.out
index 9de19b4e3f1..1c99f79ab01 100644
--- a/src/test/regress/expected/reloptions.out
+++ b/src/test/regress/expected/reloptions.out
@@ -98,6 +98,42 @@ SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass;
{fillfactor=13,autovacuum_enabled=false}
(1 row)
+-- Tests for future (FIXME) ternary options
+-- behave as boolean option: accept unassigned name and truncated value
+DROP TABLE reloptions_test;
+CREATE TABLE reloptions_test(i INT) WITH (vacuum_truncate);
+SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass;
+ reloptions
+------------------------
+ {vacuum_truncate=true}
+(1 row)
+
+DROP TABLE reloptions_test;
+CREATE TABLE reloptions_test(i INT) WITH (vacuum_truncate=FaLS);
+SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass;
+ reloptions
+------------------------
+ {vacuum_truncate=fals}
+(1 row)
+
+-- preferred "true" alias is used when storing
+DROP TABLE reloptions_test;
+CREATE TABLE reloptions_test(i INT) WITH (vacuum_index_cleanup=on);
+SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass;
+ reloptions
+---------------------------
+ {vacuum_index_cleanup=on}
+(1 row)
+
+-- custom "third" value is available
+DROP TABLE reloptions_test;
+CREATE TABLE reloptions_test(i INT) WITH (vacuum_index_cleanup=auto);
+SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass;
+ reloptions
+-----------------------------
+ {vacuum_index_cleanup=auto}
+(1 row)
+
-- Test vacuum_truncate option
DROP TABLE reloptions_test;
CREATE TEMP TABLE reloptions_test(i INT NOT NULL, j text)
diff --git a/src/test/regress/sql/reloptions.sql b/src/test/regress/sql/reloptions.sql
index 24fbe0b478d..f5980dafcbc 100644
--- a/src/test/regress/sql/reloptions.sql
+++ b/src/test/regress/sql/reloptions.sql
@@ -59,6 +59,27 @@ UPDATE pg_class
ALTER TABLE reloptions_test RESET (illegal_option);
SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass;
+-- Tests for future (FIXME) ternary options
+
+-- behave as boolean option: accept unassigned name and truncated value
+DROP TABLE reloptions_test;
+CREATE TABLE reloptions_test(i INT) WITH (vacuum_truncate);
+SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass;
+
+DROP TABLE reloptions_test;
+CREATE TABLE reloptions_test(i INT) WITH (vacuum_truncate=FaLS);
+SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass;
+
+-- preferred "true" alias is used when storing
+DROP TABLE reloptions_test;
+CREATE TABLE reloptions_test(i INT) WITH (vacuum_index_cleanup=on);
+SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass;
+
+-- custom "third" value is available
+DROP TABLE reloptions_test;
+CREATE TABLE reloptions_test(i INT) WITH (vacuum_index_cleanup=auto);
+SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass;
+
-- Test vacuum_truncate option
DROP TABLE reloptions_test;
diff --git a/src/tools/pgindent/typedefs.list b/src/tools/pgindent/typedefs.list
index 3f3a888fd0e..1c8610fd46c 100644
--- a/src/tools/pgindent/typedefs.list
+++ b/src/tools/pgindent/typedefs.list
@@ -3952,6 +3952,7 @@ pg_sha512_ctx
pg_snapshot
pg_special_case
pg_stack_base_t
+pg_ternary
pg_time_t
pg_time_usec_t
pg_tz
@@ -4079,6 +4080,7 @@ relopt_kind
relopt_parse_elt
relopt_real
relopt_string
+relopt_ternary
relopt_type
relopt_value
relopts_validator
--
2.47.3
--c36t3fndxhkzbl5z--
^ permalink raw reply [nested|flat] 868+ messages in thread
* [PATCH v3] Introduce ternary reloptions
@ 2026-01-16 15:04 Álvaro Herrera <[email protected]>
0 siblings, 0 replies; 868+ messages in thread
From: Álvaro Herrera @ 2026-01-16 15:04 UTC (permalink / raw)
Introduce ternary reloption as a replacement for current
`vacuum_truncate` implementation. Remove the `vacuum_truncate_set`
separate flag and use TERNARY_UNSET instead.
This could also be used for other options such as `vacuum_index_cleanup`
and `buffering`, but lets get the scaffolding in first.
Discussion: https://postgr.es/m/3474141.usfYGdeWWP@thinkpad-pgpro
---
src/backend/access/common/reloptions.c | 137 ++++++++++++++++++-----
src/backend/commands/vacuum.c | 4 +-
src/include/access/reloptions.h | 27 ++---
src/include/postgres.h | 15 +++
src/include/utils/rel.h | 3 +-
src/test/regress/expected/reloptions.out | 36 ++++++
src/test/regress/sql/reloptions.sql | 21 ++++
src/tools/pgindent/typedefs.list | 2 +
8 files changed, 202 insertions(+), 43 deletions(-)
diff --git a/src/backend/access/common/reloptions.c b/src/backend/access/common/reloptions.c
index 0b83f98ed5f..6f1f577581a 100644
--- a/src/backend/access/common/reloptions.c
+++ b/src/backend/access/common/reloptions.c
@@ -40,9 +40,9 @@
*
* To add an option:
*
- * (i) decide on a type (bool, integer, real, enum, string), name, default
- * value, upper and lower bounds (if applicable); for strings, consider a
- * validation routine.
+ * (i) decide on a type (bool, ternary, integer, real, enum, string), name,
+ * default value, upper and lower bounds (if applicable); for strings,
+ * consider a validation routine.
* (ii) add a record below (or use add_<type>_reloption).
* (iii) add it to the appropriate options struct (perhaps StdRdOptions)
* (iv) add it to the appropriate handling routine (perhaps
@@ -147,15 +147,6 @@ static relopt_bool boolRelOpts[] =
},
false
},
- {
- {
- "vacuum_truncate",
- "Enables vacuum to truncate empty pages at the end of this table",
- RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
- ShareUpdateExclusiveLock
- },
- true
- },
{
{
"deduplicate_items",
@@ -170,6 +161,25 @@ static relopt_bool boolRelOpts[] =
{{NULL}}
};
+static relopt_ternary ternaryRelOpts[] =
+{
+ {
+ {
+ "vacuum_truncate",
+ "Enables vacuum to truncate empty pages at the end of this table",
+ RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
+ ShareUpdateExclusiveLock
+ },
+ TERNARY_UNSET
+ },
+ /* list terminator */
+ {
+ {
+ NULL
+ }
+ }
+};
+
static relopt_int intRelOpts[] =
{
{
@@ -609,6 +619,13 @@ initialize_reloptions(void)
boolRelOpts[i].gen.lockmode));
j++;
}
+ for (i = 0; ternaryRelOpts[i].gen.name; i++)
+ {
+ Assert(DoLockModesConflict(ternaryRelOpts[i].gen.lockmode,
+ ternaryRelOpts[i].gen.lockmode));
+ j++;
+ }
+
for (i = 0; intRelOpts[i].gen.name; i++)
{
Assert(DoLockModesConflict(intRelOpts[i].gen.lockmode,
@@ -649,6 +666,14 @@ initialize_reloptions(void)
j++;
}
+ for (i = 0; ternaryRelOpts[i].gen.name; i++)
+ {
+ relOpts[j] = &ternaryRelOpts[i].gen;
+ relOpts[j]->type = RELOPT_TYPE_TERNARY;
+ relOpts[j]->namelen = strlen(relOpts[j]->name);
+ j++;
+ }
+
for (i = 0; intRelOpts[i].gen.name; i++)
{
relOpts[j] = &intRelOpts[i].gen;
@@ -809,6 +834,9 @@ allocate_reloption(bits32 kinds, int type, const char *name, const char *desc,
case RELOPT_TYPE_BOOL:
size = sizeof(relopt_bool);
break;
+ case RELOPT_TYPE_TERNARY:
+ size = sizeof(relopt_ternary);
+ break;
case RELOPT_TYPE_INT:
size = sizeof(relopt_int);
break;
@@ -892,6 +920,57 @@ add_local_bool_reloption(local_relopts *relopts, const char *name,
add_local_reloption(relopts, (relopt_gen *) newoption, offset);
}
+/*
+ * init_ternary_reloption
+ * Allocate and initialize a new ternary reloption
+ */
+static relopt_ternary *
+init_ternary_reloption(bits32 kinds, const char *name, const char *desc,
+ pg_ternary default_val, LOCKMODE lockmode)
+{
+ relopt_ternary *newoption;
+
+ newoption = (relopt_ternary *)
+ allocate_reloption(kinds, RELOPT_TYPE_TERNARY, name, desc, lockmode);
+ newoption->default_val = default_val;
+
+ return newoption;
+}
+
+/*
+ * add_ternary_reloption
+ * Add a new ternary reloption
+ */
+void
+add_ternary_reloption(bits32 kinds, const char *name, const char *desc,
+ pg_ternary default_val, LOCKMODE lockmode)
+{
+ relopt_ternary *newoption;
+
+ newoption =
+ init_ternary_reloption(kinds, name, desc, default_val, lockmode);
+
+ add_reloption((relopt_gen *) newoption);
+}
+
+/*
+ * add_local_ternary_reloption
+ * Add a new ternary local reloption
+ *
+ * 'offset' is offset of ternary-typed field.
+ */
+void
+add_local_ternary_reloption(local_relopts *relopts, const char *name,
+ const char *desc, pg_ternary default_val,
+ int offset)
+{
+ relopt_ternary *newoption;
+
+ newoption =
+ init_ternary_reloption(RELOPT_KIND_LOCAL, name, desc, default_val, 0);
+
+ add_local_reloption(relopts, (relopt_gen *) newoption, offset);
+}
/*
* init_real_reloption
@@ -1626,6 +1705,19 @@ parse_one_reloption(relopt_value *option, char *text_str, int text_len,
option->gen->name, value)));
}
break;
+ case RELOPT_TYPE_TERNARY:
+ {
+ bool b;
+
+ parsed = parse_bool(value, &b);
+ option->values.ternary_val = b ? TERNARY_TRUE : TERNARY_FALSE;
+ if (validate && !parsed)
+ ereport(ERROR,
+ (errcode(ERRCODE_INVALID_PARAMETER_VALUE),
+ errmsg("invalid value for ternary option \"%s\": %s",
+ option->gen->name, value)));
+ }
+ break;
case RELOPT_TYPE_INT:
{
relopt_int *optint = (relopt_int *) option->gen;
@@ -1789,17 +1881,6 @@ fillRelOptions(void *rdopts, Size basesize,
char *itempos = ((char *) rdopts) + elems[j].offset;
char *string_val;
- /*
- * If isset_offset is provided, store whether the reloption is
- * set there.
- */
- if (elems[j].isset_offset > 0)
- {
- char *setpos = ((char *) rdopts) + elems[j].isset_offset;
-
- *(bool *) setpos = options[i].isset;
- }
-
switch (options[i].gen->type)
{
case RELOPT_TYPE_BOOL:
@@ -1807,6 +1888,11 @@ fillRelOptions(void *rdopts, Size basesize,
options[i].values.bool_val :
((relopt_bool *) options[i].gen)->default_val;
break;
+ case RELOPT_TYPE_TERNARY:
+ *(pg_ternary *) itempos = options[i].isset ?
+ options[i].values.ternary_val :
+ ((relopt_ternary *) options[i].gen)->default_val;
+ break;
case RELOPT_TYPE_INT:
*(int *) itempos = options[i].isset ?
options[i].values.int_val :
@@ -1923,8 +2009,8 @@ default_reloptions(Datum reloptions, bool validate, relopt_kind kind)
offsetof(StdRdOptions, parallel_workers)},
{"vacuum_index_cleanup", RELOPT_TYPE_ENUM,
offsetof(StdRdOptions, vacuum_index_cleanup)},
- {"vacuum_truncate", RELOPT_TYPE_BOOL,
- offsetof(StdRdOptions, vacuum_truncate), offsetof(StdRdOptions, vacuum_truncate_set)},
+ {"vacuum_truncate", RELOPT_TYPE_TERNARY,
+ offsetof(StdRdOptions, vacuum_truncate)},
{"vacuum_max_eager_freeze_failure_rate", RELOPT_TYPE_REAL,
offsetof(StdRdOptions, vacuum_max_eager_freeze_failure_rate)}
};
@@ -2004,7 +2090,6 @@ build_local_reloptions(local_relopts *relopts, Datum options, bool validate)
elems[i].optname = opt->option->name;
elems[i].opttype = opt->option->type;
elems[i].offset = opt->offset;
- elems[i].isset_offset = 0; /* not supported for local relopts yet */
i++;
}
diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c
index aa4fbec143f..696eab9bd97 100644
--- a/src/backend/commands/vacuum.c
+++ b/src/backend/commands/vacuum.c
@@ -2224,9 +2224,9 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams params,
{
StdRdOptions *opts = (StdRdOptions *) rel->rd_options;
- if (opts && opts->vacuum_truncate_set)
+ if (opts && opts->vacuum_truncate != TERNARY_UNSET)
{
- if (opts->vacuum_truncate)
+ if (opts->vacuum_truncate == TERNARY_TRUE)
params.truncate = VACOPTVALUE_ENABLED;
else
params.truncate = VACOPTVALUE_DISABLED;
diff --git a/src/include/access/reloptions.h b/src/include/access/reloptions.h
index 2f08e1b0cf0..dfbef2babf2 100644
--- a/src/include/access/reloptions.h
+++ b/src/include/access/reloptions.h
@@ -29,6 +29,7 @@
typedef enum relopt_type
{
RELOPT_TYPE_BOOL,
+ RELOPT_TYPE_TERNARY, /* on, off, unset */
RELOPT_TYPE_INT,
RELOPT_TYPE_REAL,
RELOPT_TYPE_ENUM,
@@ -80,6 +81,7 @@ typedef struct relopt_value
union
{
bool bool_val;
+ pg_ternary ternary_val;
int int_val;
double real_val;
int enum_val;
@@ -94,6 +96,12 @@ typedef struct relopt_bool
bool default_val;
} relopt_bool;
+typedef struct relopt_ternary
+{
+ relopt_gen gen;
+ int default_val;
+} relopt_ternary;
+
typedef struct relopt_int
{
relopt_gen gen;
@@ -152,19 +160,6 @@ typedef struct
const char *optname; /* option's name */
relopt_type opttype; /* option's datatype */
int offset; /* offset of field in result struct */
-
- /*
- * isset_offset is an optional offset of a field in the result struct that
- * stores whether the option is explicitly set for the relation or if it
- * just picked up the default value. In most cases, this can be
- * accomplished by giving the reloption a special out-of-range default
- * value (e.g., some integer reloptions use -2), but this isn't always
- * possible. For example, a Boolean reloption cannot be given an
- * out-of-range default, so we need another way to discover the source of
- * its value. This offset is only used if given a value greater than
- * zero.
- */
- int isset_offset;
} relopt_parse_elt;
/* Local reloption definition */
@@ -195,6 +190,9 @@ typedef struct local_relopts
extern relopt_kind add_reloption_kind(void);
extern void add_bool_reloption(bits32 kinds, const char *name, const char *desc,
bool default_val, LOCKMODE lockmode);
+extern void add_ternary_reloption(bits32 kinds, const char *name,
+ const char *desc, pg_ternary default_val,
+ LOCKMODE lockmode);
extern void add_int_reloption(bits32 kinds, const char *name, const char *desc,
int default_val, int min_val, int max_val,
LOCKMODE lockmode);
@@ -214,6 +212,9 @@ extern void register_reloptions_validator(local_relopts *relopts,
extern void add_local_bool_reloption(local_relopts *relopts, const char *name,
const char *desc, bool default_val,
int offset);
+extern void add_local_ternary_reloption(local_relopts *relopts,
+ const char *name, const char *desc,
+ pg_ternary default_val, int offset);
extern void add_local_int_reloption(local_relopts *relopts, const char *name,
const char *desc, int default_val,
int min_val, int max_val, int offset);
diff --git a/src/include/postgres.h b/src/include/postgres.h
index 7d93fbce709..47c88780776 100644
--- a/src/include/postgres.h
+++ b/src/include/postgres.h
@@ -543,6 +543,21 @@ Float8GetDatum(float8 X)
* ----------------------------------------------------------------
*/
+/*
+ * pg_ternary
+ * Boolean value with an extra "unset" value
+ *
+ * This enum can be used for values that want to distinguish between true,
+ * false, and unset.
+*/
+
+typedef enum pg_ternary
+{
+ TERNARY_FALSE = 0,
+ TERNARY_TRUE = 1,
+ TERNARY_UNSET = -1
+} pg_ternary;
+
/*
* NON_EXEC_STATIC: It's sometimes useful to define a variable or function
* that is normally static but extern when using EXEC_BACKEND (see
diff --git a/src/include/utils/rel.h b/src/include/utils/rel.h
index d03ab247788..5feb18a5373 100644
--- a/src/include/utils/rel.h
+++ b/src/include/utils/rel.h
@@ -347,8 +347,7 @@ typedef struct StdRdOptions
bool user_catalog_table; /* use as an additional catalog relation */
int parallel_workers; /* max number of parallel workers */
StdRdOptIndexCleanup vacuum_index_cleanup; /* controls index vacuuming */
- bool vacuum_truncate; /* enables vacuum to truncate a relation */
- bool vacuum_truncate_set; /* whether vacuum_truncate is set */
+ pg_ternary vacuum_truncate; /* enables vacuum to truncate a relation */
/*
* Fraction of pages in a relation that vacuum can eagerly scan and fail
diff --git a/src/test/regress/expected/reloptions.out b/src/test/regress/expected/reloptions.out
index 9de19b4e3f1..1c99f79ab01 100644
--- a/src/test/regress/expected/reloptions.out
+++ b/src/test/regress/expected/reloptions.out
@@ -98,6 +98,42 @@ SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass;
{fillfactor=13,autovacuum_enabled=false}
(1 row)
+-- Tests for future (FIXME) ternary options
+-- behave as boolean option: accept unassigned name and truncated value
+DROP TABLE reloptions_test;
+CREATE TABLE reloptions_test(i INT) WITH (vacuum_truncate);
+SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass;
+ reloptions
+------------------------
+ {vacuum_truncate=true}
+(1 row)
+
+DROP TABLE reloptions_test;
+CREATE TABLE reloptions_test(i INT) WITH (vacuum_truncate=FaLS);
+SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass;
+ reloptions
+------------------------
+ {vacuum_truncate=fals}
+(1 row)
+
+-- preferred "true" alias is used when storing
+DROP TABLE reloptions_test;
+CREATE TABLE reloptions_test(i INT) WITH (vacuum_index_cleanup=on);
+SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass;
+ reloptions
+---------------------------
+ {vacuum_index_cleanup=on}
+(1 row)
+
+-- custom "third" value is available
+DROP TABLE reloptions_test;
+CREATE TABLE reloptions_test(i INT) WITH (vacuum_index_cleanup=auto);
+SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass;
+ reloptions
+-----------------------------
+ {vacuum_index_cleanup=auto}
+(1 row)
+
-- Test vacuum_truncate option
DROP TABLE reloptions_test;
CREATE TEMP TABLE reloptions_test(i INT NOT NULL, j text)
diff --git a/src/test/regress/sql/reloptions.sql b/src/test/regress/sql/reloptions.sql
index 24fbe0b478d..f5980dafcbc 100644
--- a/src/test/regress/sql/reloptions.sql
+++ b/src/test/regress/sql/reloptions.sql
@@ -59,6 +59,27 @@ UPDATE pg_class
ALTER TABLE reloptions_test RESET (illegal_option);
SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass;
+-- Tests for future (FIXME) ternary options
+
+-- behave as boolean option: accept unassigned name and truncated value
+DROP TABLE reloptions_test;
+CREATE TABLE reloptions_test(i INT) WITH (vacuum_truncate);
+SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass;
+
+DROP TABLE reloptions_test;
+CREATE TABLE reloptions_test(i INT) WITH (vacuum_truncate=FaLS);
+SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass;
+
+-- preferred "true" alias is used when storing
+DROP TABLE reloptions_test;
+CREATE TABLE reloptions_test(i INT) WITH (vacuum_index_cleanup=on);
+SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass;
+
+-- custom "third" value is available
+DROP TABLE reloptions_test;
+CREATE TABLE reloptions_test(i INT) WITH (vacuum_index_cleanup=auto);
+SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass;
+
-- Test vacuum_truncate option
DROP TABLE reloptions_test;
diff --git a/src/tools/pgindent/typedefs.list b/src/tools/pgindent/typedefs.list
index 3f3a888fd0e..1c8610fd46c 100644
--- a/src/tools/pgindent/typedefs.list
+++ b/src/tools/pgindent/typedefs.list
@@ -3952,6 +3952,7 @@ pg_sha512_ctx
pg_snapshot
pg_special_case
pg_stack_base_t
+pg_ternary
pg_time_t
pg_time_usec_t
pg_tz
@@ -4079,6 +4080,7 @@ relopt_kind
relopt_parse_elt
relopt_real
relopt_string
+relopt_ternary
relopt_type
relopt_value
relopts_validator
--
2.47.3
--c36t3fndxhkzbl5z--
^ permalink raw reply [nested|flat] 868+ messages in thread
* [PATCH v3] Introduce ternary reloptions
@ 2026-01-16 15:04 Álvaro Herrera <[email protected]>
0 siblings, 0 replies; 868+ messages in thread
From: Álvaro Herrera @ 2026-01-16 15:04 UTC (permalink / raw)
Introduce ternary reloption as a replacement for current
`vacuum_truncate` implementation. Remove the `vacuum_truncate_set`
separate flag and use TERNARY_UNSET instead.
This could also be used for other options such as `vacuum_index_cleanup`
and `buffering`, but lets get the scaffolding in first.
Discussion: https://postgr.es/m/3474141.usfYGdeWWP@thinkpad-pgpro
---
src/backend/access/common/reloptions.c | 137 ++++++++++++++++++-----
src/backend/commands/vacuum.c | 4 +-
src/include/access/reloptions.h | 27 ++---
src/include/postgres.h | 15 +++
src/include/utils/rel.h | 3 +-
src/test/regress/expected/reloptions.out | 36 ++++++
src/test/regress/sql/reloptions.sql | 21 ++++
src/tools/pgindent/typedefs.list | 2 +
8 files changed, 202 insertions(+), 43 deletions(-)
diff --git a/src/backend/access/common/reloptions.c b/src/backend/access/common/reloptions.c
index 0b83f98ed5f..6f1f577581a 100644
--- a/src/backend/access/common/reloptions.c
+++ b/src/backend/access/common/reloptions.c
@@ -40,9 +40,9 @@
*
* To add an option:
*
- * (i) decide on a type (bool, integer, real, enum, string), name, default
- * value, upper and lower bounds (if applicable); for strings, consider a
- * validation routine.
+ * (i) decide on a type (bool, ternary, integer, real, enum, string), name,
+ * default value, upper and lower bounds (if applicable); for strings,
+ * consider a validation routine.
* (ii) add a record below (or use add_<type>_reloption).
* (iii) add it to the appropriate options struct (perhaps StdRdOptions)
* (iv) add it to the appropriate handling routine (perhaps
@@ -147,15 +147,6 @@ static relopt_bool boolRelOpts[] =
},
false
},
- {
- {
- "vacuum_truncate",
- "Enables vacuum to truncate empty pages at the end of this table",
- RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
- ShareUpdateExclusiveLock
- },
- true
- },
{
{
"deduplicate_items",
@@ -170,6 +161,25 @@ static relopt_bool boolRelOpts[] =
{{NULL}}
};
+static relopt_ternary ternaryRelOpts[] =
+{
+ {
+ {
+ "vacuum_truncate",
+ "Enables vacuum to truncate empty pages at the end of this table",
+ RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
+ ShareUpdateExclusiveLock
+ },
+ TERNARY_UNSET
+ },
+ /* list terminator */
+ {
+ {
+ NULL
+ }
+ }
+};
+
static relopt_int intRelOpts[] =
{
{
@@ -609,6 +619,13 @@ initialize_reloptions(void)
boolRelOpts[i].gen.lockmode));
j++;
}
+ for (i = 0; ternaryRelOpts[i].gen.name; i++)
+ {
+ Assert(DoLockModesConflict(ternaryRelOpts[i].gen.lockmode,
+ ternaryRelOpts[i].gen.lockmode));
+ j++;
+ }
+
for (i = 0; intRelOpts[i].gen.name; i++)
{
Assert(DoLockModesConflict(intRelOpts[i].gen.lockmode,
@@ -649,6 +666,14 @@ initialize_reloptions(void)
j++;
}
+ for (i = 0; ternaryRelOpts[i].gen.name; i++)
+ {
+ relOpts[j] = &ternaryRelOpts[i].gen;
+ relOpts[j]->type = RELOPT_TYPE_TERNARY;
+ relOpts[j]->namelen = strlen(relOpts[j]->name);
+ j++;
+ }
+
for (i = 0; intRelOpts[i].gen.name; i++)
{
relOpts[j] = &intRelOpts[i].gen;
@@ -809,6 +834,9 @@ allocate_reloption(bits32 kinds, int type, const char *name, const char *desc,
case RELOPT_TYPE_BOOL:
size = sizeof(relopt_bool);
break;
+ case RELOPT_TYPE_TERNARY:
+ size = sizeof(relopt_ternary);
+ break;
case RELOPT_TYPE_INT:
size = sizeof(relopt_int);
break;
@@ -892,6 +920,57 @@ add_local_bool_reloption(local_relopts *relopts, const char *name,
add_local_reloption(relopts, (relopt_gen *) newoption, offset);
}
+/*
+ * init_ternary_reloption
+ * Allocate and initialize a new ternary reloption
+ */
+static relopt_ternary *
+init_ternary_reloption(bits32 kinds, const char *name, const char *desc,
+ pg_ternary default_val, LOCKMODE lockmode)
+{
+ relopt_ternary *newoption;
+
+ newoption = (relopt_ternary *)
+ allocate_reloption(kinds, RELOPT_TYPE_TERNARY, name, desc, lockmode);
+ newoption->default_val = default_val;
+
+ return newoption;
+}
+
+/*
+ * add_ternary_reloption
+ * Add a new ternary reloption
+ */
+void
+add_ternary_reloption(bits32 kinds, const char *name, const char *desc,
+ pg_ternary default_val, LOCKMODE lockmode)
+{
+ relopt_ternary *newoption;
+
+ newoption =
+ init_ternary_reloption(kinds, name, desc, default_val, lockmode);
+
+ add_reloption((relopt_gen *) newoption);
+}
+
+/*
+ * add_local_ternary_reloption
+ * Add a new ternary local reloption
+ *
+ * 'offset' is offset of ternary-typed field.
+ */
+void
+add_local_ternary_reloption(local_relopts *relopts, const char *name,
+ const char *desc, pg_ternary default_val,
+ int offset)
+{
+ relopt_ternary *newoption;
+
+ newoption =
+ init_ternary_reloption(RELOPT_KIND_LOCAL, name, desc, default_val, 0);
+
+ add_local_reloption(relopts, (relopt_gen *) newoption, offset);
+}
/*
* init_real_reloption
@@ -1626,6 +1705,19 @@ parse_one_reloption(relopt_value *option, char *text_str, int text_len,
option->gen->name, value)));
}
break;
+ case RELOPT_TYPE_TERNARY:
+ {
+ bool b;
+
+ parsed = parse_bool(value, &b);
+ option->values.ternary_val = b ? TERNARY_TRUE : TERNARY_FALSE;
+ if (validate && !parsed)
+ ereport(ERROR,
+ (errcode(ERRCODE_INVALID_PARAMETER_VALUE),
+ errmsg("invalid value for ternary option \"%s\": %s",
+ option->gen->name, value)));
+ }
+ break;
case RELOPT_TYPE_INT:
{
relopt_int *optint = (relopt_int *) option->gen;
@@ -1789,17 +1881,6 @@ fillRelOptions(void *rdopts, Size basesize,
char *itempos = ((char *) rdopts) + elems[j].offset;
char *string_val;
- /*
- * If isset_offset is provided, store whether the reloption is
- * set there.
- */
- if (elems[j].isset_offset > 0)
- {
- char *setpos = ((char *) rdopts) + elems[j].isset_offset;
-
- *(bool *) setpos = options[i].isset;
- }
-
switch (options[i].gen->type)
{
case RELOPT_TYPE_BOOL:
@@ -1807,6 +1888,11 @@ fillRelOptions(void *rdopts, Size basesize,
options[i].values.bool_val :
((relopt_bool *) options[i].gen)->default_val;
break;
+ case RELOPT_TYPE_TERNARY:
+ *(pg_ternary *) itempos = options[i].isset ?
+ options[i].values.ternary_val :
+ ((relopt_ternary *) options[i].gen)->default_val;
+ break;
case RELOPT_TYPE_INT:
*(int *) itempos = options[i].isset ?
options[i].values.int_val :
@@ -1923,8 +2009,8 @@ default_reloptions(Datum reloptions, bool validate, relopt_kind kind)
offsetof(StdRdOptions, parallel_workers)},
{"vacuum_index_cleanup", RELOPT_TYPE_ENUM,
offsetof(StdRdOptions, vacuum_index_cleanup)},
- {"vacuum_truncate", RELOPT_TYPE_BOOL,
- offsetof(StdRdOptions, vacuum_truncate), offsetof(StdRdOptions, vacuum_truncate_set)},
+ {"vacuum_truncate", RELOPT_TYPE_TERNARY,
+ offsetof(StdRdOptions, vacuum_truncate)},
{"vacuum_max_eager_freeze_failure_rate", RELOPT_TYPE_REAL,
offsetof(StdRdOptions, vacuum_max_eager_freeze_failure_rate)}
};
@@ -2004,7 +2090,6 @@ build_local_reloptions(local_relopts *relopts, Datum options, bool validate)
elems[i].optname = opt->option->name;
elems[i].opttype = opt->option->type;
elems[i].offset = opt->offset;
- elems[i].isset_offset = 0; /* not supported for local relopts yet */
i++;
}
diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c
index aa4fbec143f..696eab9bd97 100644
--- a/src/backend/commands/vacuum.c
+++ b/src/backend/commands/vacuum.c
@@ -2224,9 +2224,9 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams params,
{
StdRdOptions *opts = (StdRdOptions *) rel->rd_options;
- if (opts && opts->vacuum_truncate_set)
+ if (opts && opts->vacuum_truncate != TERNARY_UNSET)
{
- if (opts->vacuum_truncate)
+ if (opts->vacuum_truncate == TERNARY_TRUE)
params.truncate = VACOPTVALUE_ENABLED;
else
params.truncate = VACOPTVALUE_DISABLED;
diff --git a/src/include/access/reloptions.h b/src/include/access/reloptions.h
index 2f08e1b0cf0..dfbef2babf2 100644
--- a/src/include/access/reloptions.h
+++ b/src/include/access/reloptions.h
@@ -29,6 +29,7 @@
typedef enum relopt_type
{
RELOPT_TYPE_BOOL,
+ RELOPT_TYPE_TERNARY, /* on, off, unset */
RELOPT_TYPE_INT,
RELOPT_TYPE_REAL,
RELOPT_TYPE_ENUM,
@@ -80,6 +81,7 @@ typedef struct relopt_value
union
{
bool bool_val;
+ pg_ternary ternary_val;
int int_val;
double real_val;
int enum_val;
@@ -94,6 +96,12 @@ typedef struct relopt_bool
bool default_val;
} relopt_bool;
+typedef struct relopt_ternary
+{
+ relopt_gen gen;
+ int default_val;
+} relopt_ternary;
+
typedef struct relopt_int
{
relopt_gen gen;
@@ -152,19 +160,6 @@ typedef struct
const char *optname; /* option's name */
relopt_type opttype; /* option's datatype */
int offset; /* offset of field in result struct */
-
- /*
- * isset_offset is an optional offset of a field in the result struct that
- * stores whether the option is explicitly set for the relation or if it
- * just picked up the default value. In most cases, this can be
- * accomplished by giving the reloption a special out-of-range default
- * value (e.g., some integer reloptions use -2), but this isn't always
- * possible. For example, a Boolean reloption cannot be given an
- * out-of-range default, so we need another way to discover the source of
- * its value. This offset is only used if given a value greater than
- * zero.
- */
- int isset_offset;
} relopt_parse_elt;
/* Local reloption definition */
@@ -195,6 +190,9 @@ typedef struct local_relopts
extern relopt_kind add_reloption_kind(void);
extern void add_bool_reloption(bits32 kinds, const char *name, const char *desc,
bool default_val, LOCKMODE lockmode);
+extern void add_ternary_reloption(bits32 kinds, const char *name,
+ const char *desc, pg_ternary default_val,
+ LOCKMODE lockmode);
extern void add_int_reloption(bits32 kinds, const char *name, const char *desc,
int default_val, int min_val, int max_val,
LOCKMODE lockmode);
@@ -214,6 +212,9 @@ extern void register_reloptions_validator(local_relopts *relopts,
extern void add_local_bool_reloption(local_relopts *relopts, const char *name,
const char *desc, bool default_val,
int offset);
+extern void add_local_ternary_reloption(local_relopts *relopts,
+ const char *name, const char *desc,
+ pg_ternary default_val, int offset);
extern void add_local_int_reloption(local_relopts *relopts, const char *name,
const char *desc, int default_val,
int min_val, int max_val, int offset);
diff --git a/src/include/postgres.h b/src/include/postgres.h
index 7d93fbce709..47c88780776 100644
--- a/src/include/postgres.h
+++ b/src/include/postgres.h
@@ -543,6 +543,21 @@ Float8GetDatum(float8 X)
* ----------------------------------------------------------------
*/
+/*
+ * pg_ternary
+ * Boolean value with an extra "unset" value
+ *
+ * This enum can be used for values that want to distinguish between true,
+ * false, and unset.
+*/
+
+typedef enum pg_ternary
+{
+ TERNARY_FALSE = 0,
+ TERNARY_TRUE = 1,
+ TERNARY_UNSET = -1
+} pg_ternary;
+
/*
* NON_EXEC_STATIC: It's sometimes useful to define a variable or function
* that is normally static but extern when using EXEC_BACKEND (see
diff --git a/src/include/utils/rel.h b/src/include/utils/rel.h
index d03ab247788..5feb18a5373 100644
--- a/src/include/utils/rel.h
+++ b/src/include/utils/rel.h
@@ -347,8 +347,7 @@ typedef struct StdRdOptions
bool user_catalog_table; /* use as an additional catalog relation */
int parallel_workers; /* max number of parallel workers */
StdRdOptIndexCleanup vacuum_index_cleanup; /* controls index vacuuming */
- bool vacuum_truncate; /* enables vacuum to truncate a relation */
- bool vacuum_truncate_set; /* whether vacuum_truncate is set */
+ pg_ternary vacuum_truncate; /* enables vacuum to truncate a relation */
/*
* Fraction of pages in a relation that vacuum can eagerly scan and fail
diff --git a/src/test/regress/expected/reloptions.out b/src/test/regress/expected/reloptions.out
index 9de19b4e3f1..1c99f79ab01 100644
--- a/src/test/regress/expected/reloptions.out
+++ b/src/test/regress/expected/reloptions.out
@@ -98,6 +98,42 @@ SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass;
{fillfactor=13,autovacuum_enabled=false}
(1 row)
+-- Tests for future (FIXME) ternary options
+-- behave as boolean option: accept unassigned name and truncated value
+DROP TABLE reloptions_test;
+CREATE TABLE reloptions_test(i INT) WITH (vacuum_truncate);
+SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass;
+ reloptions
+------------------------
+ {vacuum_truncate=true}
+(1 row)
+
+DROP TABLE reloptions_test;
+CREATE TABLE reloptions_test(i INT) WITH (vacuum_truncate=FaLS);
+SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass;
+ reloptions
+------------------------
+ {vacuum_truncate=fals}
+(1 row)
+
+-- preferred "true" alias is used when storing
+DROP TABLE reloptions_test;
+CREATE TABLE reloptions_test(i INT) WITH (vacuum_index_cleanup=on);
+SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass;
+ reloptions
+---------------------------
+ {vacuum_index_cleanup=on}
+(1 row)
+
+-- custom "third" value is available
+DROP TABLE reloptions_test;
+CREATE TABLE reloptions_test(i INT) WITH (vacuum_index_cleanup=auto);
+SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass;
+ reloptions
+-----------------------------
+ {vacuum_index_cleanup=auto}
+(1 row)
+
-- Test vacuum_truncate option
DROP TABLE reloptions_test;
CREATE TEMP TABLE reloptions_test(i INT NOT NULL, j text)
diff --git a/src/test/regress/sql/reloptions.sql b/src/test/regress/sql/reloptions.sql
index 24fbe0b478d..f5980dafcbc 100644
--- a/src/test/regress/sql/reloptions.sql
+++ b/src/test/regress/sql/reloptions.sql
@@ -59,6 +59,27 @@ UPDATE pg_class
ALTER TABLE reloptions_test RESET (illegal_option);
SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass;
+-- Tests for future (FIXME) ternary options
+
+-- behave as boolean option: accept unassigned name and truncated value
+DROP TABLE reloptions_test;
+CREATE TABLE reloptions_test(i INT) WITH (vacuum_truncate);
+SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass;
+
+DROP TABLE reloptions_test;
+CREATE TABLE reloptions_test(i INT) WITH (vacuum_truncate=FaLS);
+SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass;
+
+-- preferred "true" alias is used when storing
+DROP TABLE reloptions_test;
+CREATE TABLE reloptions_test(i INT) WITH (vacuum_index_cleanup=on);
+SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass;
+
+-- custom "third" value is available
+DROP TABLE reloptions_test;
+CREATE TABLE reloptions_test(i INT) WITH (vacuum_index_cleanup=auto);
+SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass;
+
-- Test vacuum_truncate option
DROP TABLE reloptions_test;
diff --git a/src/tools/pgindent/typedefs.list b/src/tools/pgindent/typedefs.list
index 3f3a888fd0e..1c8610fd46c 100644
--- a/src/tools/pgindent/typedefs.list
+++ b/src/tools/pgindent/typedefs.list
@@ -3952,6 +3952,7 @@ pg_sha512_ctx
pg_snapshot
pg_special_case
pg_stack_base_t
+pg_ternary
pg_time_t
pg_time_usec_t
pg_tz
@@ -4079,6 +4080,7 @@ relopt_kind
relopt_parse_elt
relopt_real
relopt_string
+relopt_ternary
relopt_type
relopt_value
relopts_validator
--
2.47.3
--c36t3fndxhkzbl5z--
^ permalink raw reply [nested|flat] 868+ messages in thread
* [PATCH v3] Introduce ternary reloptions
@ 2026-01-16 15:04 Álvaro Herrera <[email protected]>
0 siblings, 0 replies; 868+ messages in thread
From: Álvaro Herrera @ 2026-01-16 15:04 UTC (permalink / raw)
Introduce ternary reloption as a replacement for current
`vacuum_truncate` implementation. Remove the `vacuum_truncate_set`
separate flag and use TERNARY_UNSET instead.
This could also be used for other options such as `vacuum_index_cleanup`
and `buffering`, but lets get the scaffolding in first.
Discussion: https://postgr.es/m/3474141.usfYGdeWWP@thinkpad-pgpro
---
src/backend/access/common/reloptions.c | 137 ++++++++++++++++++-----
src/backend/commands/vacuum.c | 4 +-
src/include/access/reloptions.h | 27 ++---
src/include/postgres.h | 15 +++
src/include/utils/rel.h | 3 +-
src/test/regress/expected/reloptions.out | 36 ++++++
src/test/regress/sql/reloptions.sql | 21 ++++
src/tools/pgindent/typedefs.list | 2 +
8 files changed, 202 insertions(+), 43 deletions(-)
diff --git a/src/backend/access/common/reloptions.c b/src/backend/access/common/reloptions.c
index 0b83f98ed5f..6f1f577581a 100644
--- a/src/backend/access/common/reloptions.c
+++ b/src/backend/access/common/reloptions.c
@@ -40,9 +40,9 @@
*
* To add an option:
*
- * (i) decide on a type (bool, integer, real, enum, string), name, default
- * value, upper and lower bounds (if applicable); for strings, consider a
- * validation routine.
+ * (i) decide on a type (bool, ternary, integer, real, enum, string), name,
+ * default value, upper and lower bounds (if applicable); for strings,
+ * consider a validation routine.
* (ii) add a record below (or use add_<type>_reloption).
* (iii) add it to the appropriate options struct (perhaps StdRdOptions)
* (iv) add it to the appropriate handling routine (perhaps
@@ -147,15 +147,6 @@ static relopt_bool boolRelOpts[] =
},
false
},
- {
- {
- "vacuum_truncate",
- "Enables vacuum to truncate empty pages at the end of this table",
- RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
- ShareUpdateExclusiveLock
- },
- true
- },
{
{
"deduplicate_items",
@@ -170,6 +161,25 @@ static relopt_bool boolRelOpts[] =
{{NULL}}
};
+static relopt_ternary ternaryRelOpts[] =
+{
+ {
+ {
+ "vacuum_truncate",
+ "Enables vacuum to truncate empty pages at the end of this table",
+ RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
+ ShareUpdateExclusiveLock
+ },
+ TERNARY_UNSET
+ },
+ /* list terminator */
+ {
+ {
+ NULL
+ }
+ }
+};
+
static relopt_int intRelOpts[] =
{
{
@@ -609,6 +619,13 @@ initialize_reloptions(void)
boolRelOpts[i].gen.lockmode));
j++;
}
+ for (i = 0; ternaryRelOpts[i].gen.name; i++)
+ {
+ Assert(DoLockModesConflict(ternaryRelOpts[i].gen.lockmode,
+ ternaryRelOpts[i].gen.lockmode));
+ j++;
+ }
+
for (i = 0; intRelOpts[i].gen.name; i++)
{
Assert(DoLockModesConflict(intRelOpts[i].gen.lockmode,
@@ -649,6 +666,14 @@ initialize_reloptions(void)
j++;
}
+ for (i = 0; ternaryRelOpts[i].gen.name; i++)
+ {
+ relOpts[j] = &ternaryRelOpts[i].gen;
+ relOpts[j]->type = RELOPT_TYPE_TERNARY;
+ relOpts[j]->namelen = strlen(relOpts[j]->name);
+ j++;
+ }
+
for (i = 0; intRelOpts[i].gen.name; i++)
{
relOpts[j] = &intRelOpts[i].gen;
@@ -809,6 +834,9 @@ allocate_reloption(bits32 kinds, int type, const char *name, const char *desc,
case RELOPT_TYPE_BOOL:
size = sizeof(relopt_bool);
break;
+ case RELOPT_TYPE_TERNARY:
+ size = sizeof(relopt_ternary);
+ break;
case RELOPT_TYPE_INT:
size = sizeof(relopt_int);
break;
@@ -892,6 +920,57 @@ add_local_bool_reloption(local_relopts *relopts, const char *name,
add_local_reloption(relopts, (relopt_gen *) newoption, offset);
}
+/*
+ * init_ternary_reloption
+ * Allocate and initialize a new ternary reloption
+ */
+static relopt_ternary *
+init_ternary_reloption(bits32 kinds, const char *name, const char *desc,
+ pg_ternary default_val, LOCKMODE lockmode)
+{
+ relopt_ternary *newoption;
+
+ newoption = (relopt_ternary *)
+ allocate_reloption(kinds, RELOPT_TYPE_TERNARY, name, desc, lockmode);
+ newoption->default_val = default_val;
+
+ return newoption;
+}
+
+/*
+ * add_ternary_reloption
+ * Add a new ternary reloption
+ */
+void
+add_ternary_reloption(bits32 kinds, const char *name, const char *desc,
+ pg_ternary default_val, LOCKMODE lockmode)
+{
+ relopt_ternary *newoption;
+
+ newoption =
+ init_ternary_reloption(kinds, name, desc, default_val, lockmode);
+
+ add_reloption((relopt_gen *) newoption);
+}
+
+/*
+ * add_local_ternary_reloption
+ * Add a new ternary local reloption
+ *
+ * 'offset' is offset of ternary-typed field.
+ */
+void
+add_local_ternary_reloption(local_relopts *relopts, const char *name,
+ const char *desc, pg_ternary default_val,
+ int offset)
+{
+ relopt_ternary *newoption;
+
+ newoption =
+ init_ternary_reloption(RELOPT_KIND_LOCAL, name, desc, default_val, 0);
+
+ add_local_reloption(relopts, (relopt_gen *) newoption, offset);
+}
/*
* init_real_reloption
@@ -1626,6 +1705,19 @@ parse_one_reloption(relopt_value *option, char *text_str, int text_len,
option->gen->name, value)));
}
break;
+ case RELOPT_TYPE_TERNARY:
+ {
+ bool b;
+
+ parsed = parse_bool(value, &b);
+ option->values.ternary_val = b ? TERNARY_TRUE : TERNARY_FALSE;
+ if (validate && !parsed)
+ ereport(ERROR,
+ (errcode(ERRCODE_INVALID_PARAMETER_VALUE),
+ errmsg("invalid value for ternary option \"%s\": %s",
+ option->gen->name, value)));
+ }
+ break;
case RELOPT_TYPE_INT:
{
relopt_int *optint = (relopt_int *) option->gen;
@@ -1789,17 +1881,6 @@ fillRelOptions(void *rdopts, Size basesize,
char *itempos = ((char *) rdopts) + elems[j].offset;
char *string_val;
- /*
- * If isset_offset is provided, store whether the reloption is
- * set there.
- */
- if (elems[j].isset_offset > 0)
- {
- char *setpos = ((char *) rdopts) + elems[j].isset_offset;
-
- *(bool *) setpos = options[i].isset;
- }
-
switch (options[i].gen->type)
{
case RELOPT_TYPE_BOOL:
@@ -1807,6 +1888,11 @@ fillRelOptions(void *rdopts, Size basesize,
options[i].values.bool_val :
((relopt_bool *) options[i].gen)->default_val;
break;
+ case RELOPT_TYPE_TERNARY:
+ *(pg_ternary *) itempos = options[i].isset ?
+ options[i].values.ternary_val :
+ ((relopt_ternary *) options[i].gen)->default_val;
+ break;
case RELOPT_TYPE_INT:
*(int *) itempos = options[i].isset ?
options[i].values.int_val :
@@ -1923,8 +2009,8 @@ default_reloptions(Datum reloptions, bool validate, relopt_kind kind)
offsetof(StdRdOptions, parallel_workers)},
{"vacuum_index_cleanup", RELOPT_TYPE_ENUM,
offsetof(StdRdOptions, vacuum_index_cleanup)},
- {"vacuum_truncate", RELOPT_TYPE_BOOL,
- offsetof(StdRdOptions, vacuum_truncate), offsetof(StdRdOptions, vacuum_truncate_set)},
+ {"vacuum_truncate", RELOPT_TYPE_TERNARY,
+ offsetof(StdRdOptions, vacuum_truncate)},
{"vacuum_max_eager_freeze_failure_rate", RELOPT_TYPE_REAL,
offsetof(StdRdOptions, vacuum_max_eager_freeze_failure_rate)}
};
@@ -2004,7 +2090,6 @@ build_local_reloptions(local_relopts *relopts, Datum options, bool validate)
elems[i].optname = opt->option->name;
elems[i].opttype = opt->option->type;
elems[i].offset = opt->offset;
- elems[i].isset_offset = 0; /* not supported for local relopts yet */
i++;
}
diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c
index aa4fbec143f..696eab9bd97 100644
--- a/src/backend/commands/vacuum.c
+++ b/src/backend/commands/vacuum.c
@@ -2224,9 +2224,9 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams params,
{
StdRdOptions *opts = (StdRdOptions *) rel->rd_options;
- if (opts && opts->vacuum_truncate_set)
+ if (opts && opts->vacuum_truncate != TERNARY_UNSET)
{
- if (opts->vacuum_truncate)
+ if (opts->vacuum_truncate == TERNARY_TRUE)
params.truncate = VACOPTVALUE_ENABLED;
else
params.truncate = VACOPTVALUE_DISABLED;
diff --git a/src/include/access/reloptions.h b/src/include/access/reloptions.h
index 2f08e1b0cf0..dfbef2babf2 100644
--- a/src/include/access/reloptions.h
+++ b/src/include/access/reloptions.h
@@ -29,6 +29,7 @@
typedef enum relopt_type
{
RELOPT_TYPE_BOOL,
+ RELOPT_TYPE_TERNARY, /* on, off, unset */
RELOPT_TYPE_INT,
RELOPT_TYPE_REAL,
RELOPT_TYPE_ENUM,
@@ -80,6 +81,7 @@ typedef struct relopt_value
union
{
bool bool_val;
+ pg_ternary ternary_val;
int int_val;
double real_val;
int enum_val;
@@ -94,6 +96,12 @@ typedef struct relopt_bool
bool default_val;
} relopt_bool;
+typedef struct relopt_ternary
+{
+ relopt_gen gen;
+ int default_val;
+} relopt_ternary;
+
typedef struct relopt_int
{
relopt_gen gen;
@@ -152,19 +160,6 @@ typedef struct
const char *optname; /* option's name */
relopt_type opttype; /* option's datatype */
int offset; /* offset of field in result struct */
-
- /*
- * isset_offset is an optional offset of a field in the result struct that
- * stores whether the option is explicitly set for the relation or if it
- * just picked up the default value. In most cases, this can be
- * accomplished by giving the reloption a special out-of-range default
- * value (e.g., some integer reloptions use -2), but this isn't always
- * possible. For example, a Boolean reloption cannot be given an
- * out-of-range default, so we need another way to discover the source of
- * its value. This offset is only used if given a value greater than
- * zero.
- */
- int isset_offset;
} relopt_parse_elt;
/* Local reloption definition */
@@ -195,6 +190,9 @@ typedef struct local_relopts
extern relopt_kind add_reloption_kind(void);
extern void add_bool_reloption(bits32 kinds, const char *name, const char *desc,
bool default_val, LOCKMODE lockmode);
+extern void add_ternary_reloption(bits32 kinds, const char *name,
+ const char *desc, pg_ternary default_val,
+ LOCKMODE lockmode);
extern void add_int_reloption(bits32 kinds, const char *name, const char *desc,
int default_val, int min_val, int max_val,
LOCKMODE lockmode);
@@ -214,6 +212,9 @@ extern void register_reloptions_validator(local_relopts *relopts,
extern void add_local_bool_reloption(local_relopts *relopts, const char *name,
const char *desc, bool default_val,
int offset);
+extern void add_local_ternary_reloption(local_relopts *relopts,
+ const char *name, const char *desc,
+ pg_ternary default_val, int offset);
extern void add_local_int_reloption(local_relopts *relopts, const char *name,
const char *desc, int default_val,
int min_val, int max_val, int offset);
diff --git a/src/include/postgres.h b/src/include/postgres.h
index 7d93fbce709..47c88780776 100644
--- a/src/include/postgres.h
+++ b/src/include/postgres.h
@@ -543,6 +543,21 @@ Float8GetDatum(float8 X)
* ----------------------------------------------------------------
*/
+/*
+ * pg_ternary
+ * Boolean value with an extra "unset" value
+ *
+ * This enum can be used for values that want to distinguish between true,
+ * false, and unset.
+*/
+
+typedef enum pg_ternary
+{
+ TERNARY_FALSE = 0,
+ TERNARY_TRUE = 1,
+ TERNARY_UNSET = -1
+} pg_ternary;
+
/*
* NON_EXEC_STATIC: It's sometimes useful to define a variable or function
* that is normally static but extern when using EXEC_BACKEND (see
diff --git a/src/include/utils/rel.h b/src/include/utils/rel.h
index d03ab247788..5feb18a5373 100644
--- a/src/include/utils/rel.h
+++ b/src/include/utils/rel.h
@@ -347,8 +347,7 @@ typedef struct StdRdOptions
bool user_catalog_table; /* use as an additional catalog relation */
int parallel_workers; /* max number of parallel workers */
StdRdOptIndexCleanup vacuum_index_cleanup; /* controls index vacuuming */
- bool vacuum_truncate; /* enables vacuum to truncate a relation */
- bool vacuum_truncate_set; /* whether vacuum_truncate is set */
+ pg_ternary vacuum_truncate; /* enables vacuum to truncate a relation */
/*
* Fraction of pages in a relation that vacuum can eagerly scan and fail
diff --git a/src/test/regress/expected/reloptions.out b/src/test/regress/expected/reloptions.out
index 9de19b4e3f1..1c99f79ab01 100644
--- a/src/test/regress/expected/reloptions.out
+++ b/src/test/regress/expected/reloptions.out
@@ -98,6 +98,42 @@ SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass;
{fillfactor=13,autovacuum_enabled=false}
(1 row)
+-- Tests for future (FIXME) ternary options
+-- behave as boolean option: accept unassigned name and truncated value
+DROP TABLE reloptions_test;
+CREATE TABLE reloptions_test(i INT) WITH (vacuum_truncate);
+SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass;
+ reloptions
+------------------------
+ {vacuum_truncate=true}
+(1 row)
+
+DROP TABLE reloptions_test;
+CREATE TABLE reloptions_test(i INT) WITH (vacuum_truncate=FaLS);
+SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass;
+ reloptions
+------------------------
+ {vacuum_truncate=fals}
+(1 row)
+
+-- preferred "true" alias is used when storing
+DROP TABLE reloptions_test;
+CREATE TABLE reloptions_test(i INT) WITH (vacuum_index_cleanup=on);
+SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass;
+ reloptions
+---------------------------
+ {vacuum_index_cleanup=on}
+(1 row)
+
+-- custom "third" value is available
+DROP TABLE reloptions_test;
+CREATE TABLE reloptions_test(i INT) WITH (vacuum_index_cleanup=auto);
+SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass;
+ reloptions
+-----------------------------
+ {vacuum_index_cleanup=auto}
+(1 row)
+
-- Test vacuum_truncate option
DROP TABLE reloptions_test;
CREATE TEMP TABLE reloptions_test(i INT NOT NULL, j text)
diff --git a/src/test/regress/sql/reloptions.sql b/src/test/regress/sql/reloptions.sql
index 24fbe0b478d..f5980dafcbc 100644
--- a/src/test/regress/sql/reloptions.sql
+++ b/src/test/regress/sql/reloptions.sql
@@ -59,6 +59,27 @@ UPDATE pg_class
ALTER TABLE reloptions_test RESET (illegal_option);
SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass;
+-- Tests for future (FIXME) ternary options
+
+-- behave as boolean option: accept unassigned name and truncated value
+DROP TABLE reloptions_test;
+CREATE TABLE reloptions_test(i INT) WITH (vacuum_truncate);
+SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass;
+
+DROP TABLE reloptions_test;
+CREATE TABLE reloptions_test(i INT) WITH (vacuum_truncate=FaLS);
+SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass;
+
+-- preferred "true" alias is used when storing
+DROP TABLE reloptions_test;
+CREATE TABLE reloptions_test(i INT) WITH (vacuum_index_cleanup=on);
+SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass;
+
+-- custom "third" value is available
+DROP TABLE reloptions_test;
+CREATE TABLE reloptions_test(i INT) WITH (vacuum_index_cleanup=auto);
+SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass;
+
-- Test vacuum_truncate option
DROP TABLE reloptions_test;
diff --git a/src/tools/pgindent/typedefs.list b/src/tools/pgindent/typedefs.list
index 3f3a888fd0e..1c8610fd46c 100644
--- a/src/tools/pgindent/typedefs.list
+++ b/src/tools/pgindent/typedefs.list
@@ -3952,6 +3952,7 @@ pg_sha512_ctx
pg_snapshot
pg_special_case
pg_stack_base_t
+pg_ternary
pg_time_t
pg_time_usec_t
pg_tz
@@ -4079,6 +4080,7 @@ relopt_kind
relopt_parse_elt
relopt_real
relopt_string
+relopt_ternary
relopt_type
relopt_value
relopts_validator
--
2.47.3
--c36t3fndxhkzbl5z--
^ permalink raw reply [nested|flat] 868+ messages in thread
* [PATCH v3] Introduce ternary reloptions
@ 2026-01-16 15:04 Álvaro Herrera <[email protected]>
0 siblings, 0 replies; 868+ messages in thread
From: Álvaro Herrera @ 2026-01-16 15:04 UTC (permalink / raw)
Introduce ternary reloption as a replacement for current
`vacuum_truncate` implementation. Remove the `vacuum_truncate_set`
separate flag and use TERNARY_UNSET instead.
This could also be used for other options such as `vacuum_index_cleanup`
and `buffering`, but lets get the scaffolding in first.
Discussion: https://postgr.es/m/3474141.usfYGdeWWP@thinkpad-pgpro
---
src/backend/access/common/reloptions.c | 137 ++++++++++++++++++-----
src/backend/commands/vacuum.c | 4 +-
src/include/access/reloptions.h | 27 ++---
src/include/postgres.h | 15 +++
src/include/utils/rel.h | 3 +-
src/test/regress/expected/reloptions.out | 36 ++++++
src/test/regress/sql/reloptions.sql | 21 ++++
src/tools/pgindent/typedefs.list | 2 +
8 files changed, 202 insertions(+), 43 deletions(-)
diff --git a/src/backend/access/common/reloptions.c b/src/backend/access/common/reloptions.c
index 0b83f98ed5f..6f1f577581a 100644
--- a/src/backend/access/common/reloptions.c
+++ b/src/backend/access/common/reloptions.c
@@ -40,9 +40,9 @@
*
* To add an option:
*
- * (i) decide on a type (bool, integer, real, enum, string), name, default
- * value, upper and lower bounds (if applicable); for strings, consider a
- * validation routine.
+ * (i) decide on a type (bool, ternary, integer, real, enum, string), name,
+ * default value, upper and lower bounds (if applicable); for strings,
+ * consider a validation routine.
* (ii) add a record below (or use add_<type>_reloption).
* (iii) add it to the appropriate options struct (perhaps StdRdOptions)
* (iv) add it to the appropriate handling routine (perhaps
@@ -147,15 +147,6 @@ static relopt_bool boolRelOpts[] =
},
false
},
- {
- {
- "vacuum_truncate",
- "Enables vacuum to truncate empty pages at the end of this table",
- RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
- ShareUpdateExclusiveLock
- },
- true
- },
{
{
"deduplicate_items",
@@ -170,6 +161,25 @@ static relopt_bool boolRelOpts[] =
{{NULL}}
};
+static relopt_ternary ternaryRelOpts[] =
+{
+ {
+ {
+ "vacuum_truncate",
+ "Enables vacuum to truncate empty pages at the end of this table",
+ RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
+ ShareUpdateExclusiveLock
+ },
+ TERNARY_UNSET
+ },
+ /* list terminator */
+ {
+ {
+ NULL
+ }
+ }
+};
+
static relopt_int intRelOpts[] =
{
{
@@ -609,6 +619,13 @@ initialize_reloptions(void)
boolRelOpts[i].gen.lockmode));
j++;
}
+ for (i = 0; ternaryRelOpts[i].gen.name; i++)
+ {
+ Assert(DoLockModesConflict(ternaryRelOpts[i].gen.lockmode,
+ ternaryRelOpts[i].gen.lockmode));
+ j++;
+ }
+
for (i = 0; intRelOpts[i].gen.name; i++)
{
Assert(DoLockModesConflict(intRelOpts[i].gen.lockmode,
@@ -649,6 +666,14 @@ initialize_reloptions(void)
j++;
}
+ for (i = 0; ternaryRelOpts[i].gen.name; i++)
+ {
+ relOpts[j] = &ternaryRelOpts[i].gen;
+ relOpts[j]->type = RELOPT_TYPE_TERNARY;
+ relOpts[j]->namelen = strlen(relOpts[j]->name);
+ j++;
+ }
+
for (i = 0; intRelOpts[i].gen.name; i++)
{
relOpts[j] = &intRelOpts[i].gen;
@@ -809,6 +834,9 @@ allocate_reloption(bits32 kinds, int type, const char *name, const char *desc,
case RELOPT_TYPE_BOOL:
size = sizeof(relopt_bool);
break;
+ case RELOPT_TYPE_TERNARY:
+ size = sizeof(relopt_ternary);
+ break;
case RELOPT_TYPE_INT:
size = sizeof(relopt_int);
break;
@@ -892,6 +920,57 @@ add_local_bool_reloption(local_relopts *relopts, const char *name,
add_local_reloption(relopts, (relopt_gen *) newoption, offset);
}
+/*
+ * init_ternary_reloption
+ * Allocate and initialize a new ternary reloption
+ */
+static relopt_ternary *
+init_ternary_reloption(bits32 kinds, const char *name, const char *desc,
+ pg_ternary default_val, LOCKMODE lockmode)
+{
+ relopt_ternary *newoption;
+
+ newoption = (relopt_ternary *)
+ allocate_reloption(kinds, RELOPT_TYPE_TERNARY, name, desc, lockmode);
+ newoption->default_val = default_val;
+
+ return newoption;
+}
+
+/*
+ * add_ternary_reloption
+ * Add a new ternary reloption
+ */
+void
+add_ternary_reloption(bits32 kinds, const char *name, const char *desc,
+ pg_ternary default_val, LOCKMODE lockmode)
+{
+ relopt_ternary *newoption;
+
+ newoption =
+ init_ternary_reloption(kinds, name, desc, default_val, lockmode);
+
+ add_reloption((relopt_gen *) newoption);
+}
+
+/*
+ * add_local_ternary_reloption
+ * Add a new ternary local reloption
+ *
+ * 'offset' is offset of ternary-typed field.
+ */
+void
+add_local_ternary_reloption(local_relopts *relopts, const char *name,
+ const char *desc, pg_ternary default_val,
+ int offset)
+{
+ relopt_ternary *newoption;
+
+ newoption =
+ init_ternary_reloption(RELOPT_KIND_LOCAL, name, desc, default_val, 0);
+
+ add_local_reloption(relopts, (relopt_gen *) newoption, offset);
+}
/*
* init_real_reloption
@@ -1626,6 +1705,19 @@ parse_one_reloption(relopt_value *option, char *text_str, int text_len,
option->gen->name, value)));
}
break;
+ case RELOPT_TYPE_TERNARY:
+ {
+ bool b;
+
+ parsed = parse_bool(value, &b);
+ option->values.ternary_val = b ? TERNARY_TRUE : TERNARY_FALSE;
+ if (validate && !parsed)
+ ereport(ERROR,
+ (errcode(ERRCODE_INVALID_PARAMETER_VALUE),
+ errmsg("invalid value for ternary option \"%s\": %s",
+ option->gen->name, value)));
+ }
+ break;
case RELOPT_TYPE_INT:
{
relopt_int *optint = (relopt_int *) option->gen;
@@ -1789,17 +1881,6 @@ fillRelOptions(void *rdopts, Size basesize,
char *itempos = ((char *) rdopts) + elems[j].offset;
char *string_val;
- /*
- * If isset_offset is provided, store whether the reloption is
- * set there.
- */
- if (elems[j].isset_offset > 0)
- {
- char *setpos = ((char *) rdopts) + elems[j].isset_offset;
-
- *(bool *) setpos = options[i].isset;
- }
-
switch (options[i].gen->type)
{
case RELOPT_TYPE_BOOL:
@@ -1807,6 +1888,11 @@ fillRelOptions(void *rdopts, Size basesize,
options[i].values.bool_val :
((relopt_bool *) options[i].gen)->default_val;
break;
+ case RELOPT_TYPE_TERNARY:
+ *(pg_ternary *) itempos = options[i].isset ?
+ options[i].values.ternary_val :
+ ((relopt_ternary *) options[i].gen)->default_val;
+ break;
case RELOPT_TYPE_INT:
*(int *) itempos = options[i].isset ?
options[i].values.int_val :
@@ -1923,8 +2009,8 @@ default_reloptions(Datum reloptions, bool validate, relopt_kind kind)
offsetof(StdRdOptions, parallel_workers)},
{"vacuum_index_cleanup", RELOPT_TYPE_ENUM,
offsetof(StdRdOptions, vacuum_index_cleanup)},
- {"vacuum_truncate", RELOPT_TYPE_BOOL,
- offsetof(StdRdOptions, vacuum_truncate), offsetof(StdRdOptions, vacuum_truncate_set)},
+ {"vacuum_truncate", RELOPT_TYPE_TERNARY,
+ offsetof(StdRdOptions, vacuum_truncate)},
{"vacuum_max_eager_freeze_failure_rate", RELOPT_TYPE_REAL,
offsetof(StdRdOptions, vacuum_max_eager_freeze_failure_rate)}
};
@@ -2004,7 +2090,6 @@ build_local_reloptions(local_relopts *relopts, Datum options, bool validate)
elems[i].optname = opt->option->name;
elems[i].opttype = opt->option->type;
elems[i].offset = opt->offset;
- elems[i].isset_offset = 0; /* not supported for local relopts yet */
i++;
}
diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c
index aa4fbec143f..696eab9bd97 100644
--- a/src/backend/commands/vacuum.c
+++ b/src/backend/commands/vacuum.c
@@ -2224,9 +2224,9 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams params,
{
StdRdOptions *opts = (StdRdOptions *) rel->rd_options;
- if (opts && opts->vacuum_truncate_set)
+ if (opts && opts->vacuum_truncate != TERNARY_UNSET)
{
- if (opts->vacuum_truncate)
+ if (opts->vacuum_truncate == TERNARY_TRUE)
params.truncate = VACOPTVALUE_ENABLED;
else
params.truncate = VACOPTVALUE_DISABLED;
diff --git a/src/include/access/reloptions.h b/src/include/access/reloptions.h
index 2f08e1b0cf0..dfbef2babf2 100644
--- a/src/include/access/reloptions.h
+++ b/src/include/access/reloptions.h
@@ -29,6 +29,7 @@
typedef enum relopt_type
{
RELOPT_TYPE_BOOL,
+ RELOPT_TYPE_TERNARY, /* on, off, unset */
RELOPT_TYPE_INT,
RELOPT_TYPE_REAL,
RELOPT_TYPE_ENUM,
@@ -80,6 +81,7 @@ typedef struct relopt_value
union
{
bool bool_val;
+ pg_ternary ternary_val;
int int_val;
double real_val;
int enum_val;
@@ -94,6 +96,12 @@ typedef struct relopt_bool
bool default_val;
} relopt_bool;
+typedef struct relopt_ternary
+{
+ relopt_gen gen;
+ int default_val;
+} relopt_ternary;
+
typedef struct relopt_int
{
relopt_gen gen;
@@ -152,19 +160,6 @@ typedef struct
const char *optname; /* option's name */
relopt_type opttype; /* option's datatype */
int offset; /* offset of field in result struct */
-
- /*
- * isset_offset is an optional offset of a field in the result struct that
- * stores whether the option is explicitly set for the relation or if it
- * just picked up the default value. In most cases, this can be
- * accomplished by giving the reloption a special out-of-range default
- * value (e.g., some integer reloptions use -2), but this isn't always
- * possible. For example, a Boolean reloption cannot be given an
- * out-of-range default, so we need another way to discover the source of
- * its value. This offset is only used if given a value greater than
- * zero.
- */
- int isset_offset;
} relopt_parse_elt;
/* Local reloption definition */
@@ -195,6 +190,9 @@ typedef struct local_relopts
extern relopt_kind add_reloption_kind(void);
extern void add_bool_reloption(bits32 kinds, const char *name, const char *desc,
bool default_val, LOCKMODE lockmode);
+extern void add_ternary_reloption(bits32 kinds, const char *name,
+ const char *desc, pg_ternary default_val,
+ LOCKMODE lockmode);
extern void add_int_reloption(bits32 kinds, const char *name, const char *desc,
int default_val, int min_val, int max_val,
LOCKMODE lockmode);
@@ -214,6 +212,9 @@ extern void register_reloptions_validator(local_relopts *relopts,
extern void add_local_bool_reloption(local_relopts *relopts, const char *name,
const char *desc, bool default_val,
int offset);
+extern void add_local_ternary_reloption(local_relopts *relopts,
+ const char *name, const char *desc,
+ pg_ternary default_val, int offset);
extern void add_local_int_reloption(local_relopts *relopts, const char *name,
const char *desc, int default_val,
int min_val, int max_val, int offset);
diff --git a/src/include/postgres.h b/src/include/postgres.h
index 7d93fbce709..47c88780776 100644
--- a/src/include/postgres.h
+++ b/src/include/postgres.h
@@ -543,6 +543,21 @@ Float8GetDatum(float8 X)
* ----------------------------------------------------------------
*/
+/*
+ * pg_ternary
+ * Boolean value with an extra "unset" value
+ *
+ * This enum can be used for values that want to distinguish between true,
+ * false, and unset.
+*/
+
+typedef enum pg_ternary
+{
+ TERNARY_FALSE = 0,
+ TERNARY_TRUE = 1,
+ TERNARY_UNSET = -1
+} pg_ternary;
+
/*
* NON_EXEC_STATIC: It's sometimes useful to define a variable or function
* that is normally static but extern when using EXEC_BACKEND (see
diff --git a/src/include/utils/rel.h b/src/include/utils/rel.h
index d03ab247788..5feb18a5373 100644
--- a/src/include/utils/rel.h
+++ b/src/include/utils/rel.h
@@ -347,8 +347,7 @@ typedef struct StdRdOptions
bool user_catalog_table; /* use as an additional catalog relation */
int parallel_workers; /* max number of parallel workers */
StdRdOptIndexCleanup vacuum_index_cleanup; /* controls index vacuuming */
- bool vacuum_truncate; /* enables vacuum to truncate a relation */
- bool vacuum_truncate_set; /* whether vacuum_truncate is set */
+ pg_ternary vacuum_truncate; /* enables vacuum to truncate a relation */
/*
* Fraction of pages in a relation that vacuum can eagerly scan and fail
diff --git a/src/test/regress/expected/reloptions.out b/src/test/regress/expected/reloptions.out
index 9de19b4e3f1..1c99f79ab01 100644
--- a/src/test/regress/expected/reloptions.out
+++ b/src/test/regress/expected/reloptions.out
@@ -98,6 +98,42 @@ SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass;
{fillfactor=13,autovacuum_enabled=false}
(1 row)
+-- Tests for future (FIXME) ternary options
+-- behave as boolean option: accept unassigned name and truncated value
+DROP TABLE reloptions_test;
+CREATE TABLE reloptions_test(i INT) WITH (vacuum_truncate);
+SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass;
+ reloptions
+------------------------
+ {vacuum_truncate=true}
+(1 row)
+
+DROP TABLE reloptions_test;
+CREATE TABLE reloptions_test(i INT) WITH (vacuum_truncate=FaLS);
+SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass;
+ reloptions
+------------------------
+ {vacuum_truncate=fals}
+(1 row)
+
+-- preferred "true" alias is used when storing
+DROP TABLE reloptions_test;
+CREATE TABLE reloptions_test(i INT) WITH (vacuum_index_cleanup=on);
+SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass;
+ reloptions
+---------------------------
+ {vacuum_index_cleanup=on}
+(1 row)
+
+-- custom "third" value is available
+DROP TABLE reloptions_test;
+CREATE TABLE reloptions_test(i INT) WITH (vacuum_index_cleanup=auto);
+SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass;
+ reloptions
+-----------------------------
+ {vacuum_index_cleanup=auto}
+(1 row)
+
-- Test vacuum_truncate option
DROP TABLE reloptions_test;
CREATE TEMP TABLE reloptions_test(i INT NOT NULL, j text)
diff --git a/src/test/regress/sql/reloptions.sql b/src/test/regress/sql/reloptions.sql
index 24fbe0b478d..f5980dafcbc 100644
--- a/src/test/regress/sql/reloptions.sql
+++ b/src/test/regress/sql/reloptions.sql
@@ -59,6 +59,27 @@ UPDATE pg_class
ALTER TABLE reloptions_test RESET (illegal_option);
SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass;
+-- Tests for future (FIXME) ternary options
+
+-- behave as boolean option: accept unassigned name and truncated value
+DROP TABLE reloptions_test;
+CREATE TABLE reloptions_test(i INT) WITH (vacuum_truncate);
+SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass;
+
+DROP TABLE reloptions_test;
+CREATE TABLE reloptions_test(i INT) WITH (vacuum_truncate=FaLS);
+SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass;
+
+-- preferred "true" alias is used when storing
+DROP TABLE reloptions_test;
+CREATE TABLE reloptions_test(i INT) WITH (vacuum_index_cleanup=on);
+SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass;
+
+-- custom "third" value is available
+DROP TABLE reloptions_test;
+CREATE TABLE reloptions_test(i INT) WITH (vacuum_index_cleanup=auto);
+SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass;
+
-- Test vacuum_truncate option
DROP TABLE reloptions_test;
diff --git a/src/tools/pgindent/typedefs.list b/src/tools/pgindent/typedefs.list
index 3f3a888fd0e..1c8610fd46c 100644
--- a/src/tools/pgindent/typedefs.list
+++ b/src/tools/pgindent/typedefs.list
@@ -3952,6 +3952,7 @@ pg_sha512_ctx
pg_snapshot
pg_special_case
pg_stack_base_t
+pg_ternary
pg_time_t
pg_time_usec_t
pg_tz
@@ -4079,6 +4080,7 @@ relopt_kind
relopt_parse_elt
relopt_real
relopt_string
+relopt_ternary
relopt_type
relopt_value
relopts_validator
--
2.47.3
--c36t3fndxhkzbl5z--
^ permalink raw reply [nested|flat] 868+ messages in thread
* [PATCH v3] Introduce ternary reloptions
@ 2026-01-16 15:04 Álvaro Herrera <[email protected]>
0 siblings, 0 replies; 868+ messages in thread
From: Álvaro Herrera @ 2026-01-16 15:04 UTC (permalink / raw)
Introduce ternary reloption as a replacement for current
`vacuum_truncate` implementation. Remove the `vacuum_truncate_set`
separate flag and use TERNARY_UNSET instead.
This could also be used for other options such as `vacuum_index_cleanup`
and `buffering`, but lets get the scaffolding in first.
Discussion: https://postgr.es/m/3474141.usfYGdeWWP@thinkpad-pgpro
---
src/backend/access/common/reloptions.c | 137 ++++++++++++++++++-----
src/backend/commands/vacuum.c | 4 +-
src/include/access/reloptions.h | 27 ++---
src/include/postgres.h | 15 +++
src/include/utils/rel.h | 3 +-
src/test/regress/expected/reloptions.out | 36 ++++++
src/test/regress/sql/reloptions.sql | 21 ++++
src/tools/pgindent/typedefs.list | 2 +
8 files changed, 202 insertions(+), 43 deletions(-)
diff --git a/src/backend/access/common/reloptions.c b/src/backend/access/common/reloptions.c
index 0b83f98ed5f..6f1f577581a 100644
--- a/src/backend/access/common/reloptions.c
+++ b/src/backend/access/common/reloptions.c
@@ -40,9 +40,9 @@
*
* To add an option:
*
- * (i) decide on a type (bool, integer, real, enum, string), name, default
- * value, upper and lower bounds (if applicable); for strings, consider a
- * validation routine.
+ * (i) decide on a type (bool, ternary, integer, real, enum, string), name,
+ * default value, upper and lower bounds (if applicable); for strings,
+ * consider a validation routine.
* (ii) add a record below (or use add_<type>_reloption).
* (iii) add it to the appropriate options struct (perhaps StdRdOptions)
* (iv) add it to the appropriate handling routine (perhaps
@@ -147,15 +147,6 @@ static relopt_bool boolRelOpts[] =
},
false
},
- {
- {
- "vacuum_truncate",
- "Enables vacuum to truncate empty pages at the end of this table",
- RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
- ShareUpdateExclusiveLock
- },
- true
- },
{
{
"deduplicate_items",
@@ -170,6 +161,25 @@ static relopt_bool boolRelOpts[] =
{{NULL}}
};
+static relopt_ternary ternaryRelOpts[] =
+{
+ {
+ {
+ "vacuum_truncate",
+ "Enables vacuum to truncate empty pages at the end of this table",
+ RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
+ ShareUpdateExclusiveLock
+ },
+ TERNARY_UNSET
+ },
+ /* list terminator */
+ {
+ {
+ NULL
+ }
+ }
+};
+
static relopt_int intRelOpts[] =
{
{
@@ -609,6 +619,13 @@ initialize_reloptions(void)
boolRelOpts[i].gen.lockmode));
j++;
}
+ for (i = 0; ternaryRelOpts[i].gen.name; i++)
+ {
+ Assert(DoLockModesConflict(ternaryRelOpts[i].gen.lockmode,
+ ternaryRelOpts[i].gen.lockmode));
+ j++;
+ }
+
for (i = 0; intRelOpts[i].gen.name; i++)
{
Assert(DoLockModesConflict(intRelOpts[i].gen.lockmode,
@@ -649,6 +666,14 @@ initialize_reloptions(void)
j++;
}
+ for (i = 0; ternaryRelOpts[i].gen.name; i++)
+ {
+ relOpts[j] = &ternaryRelOpts[i].gen;
+ relOpts[j]->type = RELOPT_TYPE_TERNARY;
+ relOpts[j]->namelen = strlen(relOpts[j]->name);
+ j++;
+ }
+
for (i = 0; intRelOpts[i].gen.name; i++)
{
relOpts[j] = &intRelOpts[i].gen;
@@ -809,6 +834,9 @@ allocate_reloption(bits32 kinds, int type, const char *name, const char *desc,
case RELOPT_TYPE_BOOL:
size = sizeof(relopt_bool);
break;
+ case RELOPT_TYPE_TERNARY:
+ size = sizeof(relopt_ternary);
+ break;
case RELOPT_TYPE_INT:
size = sizeof(relopt_int);
break;
@@ -892,6 +920,57 @@ add_local_bool_reloption(local_relopts *relopts, const char *name,
add_local_reloption(relopts, (relopt_gen *) newoption, offset);
}
+/*
+ * init_ternary_reloption
+ * Allocate and initialize a new ternary reloption
+ */
+static relopt_ternary *
+init_ternary_reloption(bits32 kinds, const char *name, const char *desc,
+ pg_ternary default_val, LOCKMODE lockmode)
+{
+ relopt_ternary *newoption;
+
+ newoption = (relopt_ternary *)
+ allocate_reloption(kinds, RELOPT_TYPE_TERNARY, name, desc, lockmode);
+ newoption->default_val = default_val;
+
+ return newoption;
+}
+
+/*
+ * add_ternary_reloption
+ * Add a new ternary reloption
+ */
+void
+add_ternary_reloption(bits32 kinds, const char *name, const char *desc,
+ pg_ternary default_val, LOCKMODE lockmode)
+{
+ relopt_ternary *newoption;
+
+ newoption =
+ init_ternary_reloption(kinds, name, desc, default_val, lockmode);
+
+ add_reloption((relopt_gen *) newoption);
+}
+
+/*
+ * add_local_ternary_reloption
+ * Add a new ternary local reloption
+ *
+ * 'offset' is offset of ternary-typed field.
+ */
+void
+add_local_ternary_reloption(local_relopts *relopts, const char *name,
+ const char *desc, pg_ternary default_val,
+ int offset)
+{
+ relopt_ternary *newoption;
+
+ newoption =
+ init_ternary_reloption(RELOPT_KIND_LOCAL, name, desc, default_val, 0);
+
+ add_local_reloption(relopts, (relopt_gen *) newoption, offset);
+}
/*
* init_real_reloption
@@ -1626,6 +1705,19 @@ parse_one_reloption(relopt_value *option, char *text_str, int text_len,
option->gen->name, value)));
}
break;
+ case RELOPT_TYPE_TERNARY:
+ {
+ bool b;
+
+ parsed = parse_bool(value, &b);
+ option->values.ternary_val = b ? TERNARY_TRUE : TERNARY_FALSE;
+ if (validate && !parsed)
+ ereport(ERROR,
+ (errcode(ERRCODE_INVALID_PARAMETER_VALUE),
+ errmsg("invalid value for ternary option \"%s\": %s",
+ option->gen->name, value)));
+ }
+ break;
case RELOPT_TYPE_INT:
{
relopt_int *optint = (relopt_int *) option->gen;
@@ -1789,17 +1881,6 @@ fillRelOptions(void *rdopts, Size basesize,
char *itempos = ((char *) rdopts) + elems[j].offset;
char *string_val;
- /*
- * If isset_offset is provided, store whether the reloption is
- * set there.
- */
- if (elems[j].isset_offset > 0)
- {
- char *setpos = ((char *) rdopts) + elems[j].isset_offset;
-
- *(bool *) setpos = options[i].isset;
- }
-
switch (options[i].gen->type)
{
case RELOPT_TYPE_BOOL:
@@ -1807,6 +1888,11 @@ fillRelOptions(void *rdopts, Size basesize,
options[i].values.bool_val :
((relopt_bool *) options[i].gen)->default_val;
break;
+ case RELOPT_TYPE_TERNARY:
+ *(pg_ternary *) itempos = options[i].isset ?
+ options[i].values.ternary_val :
+ ((relopt_ternary *) options[i].gen)->default_val;
+ break;
case RELOPT_TYPE_INT:
*(int *) itempos = options[i].isset ?
options[i].values.int_val :
@@ -1923,8 +2009,8 @@ default_reloptions(Datum reloptions, bool validate, relopt_kind kind)
offsetof(StdRdOptions, parallel_workers)},
{"vacuum_index_cleanup", RELOPT_TYPE_ENUM,
offsetof(StdRdOptions, vacuum_index_cleanup)},
- {"vacuum_truncate", RELOPT_TYPE_BOOL,
- offsetof(StdRdOptions, vacuum_truncate), offsetof(StdRdOptions, vacuum_truncate_set)},
+ {"vacuum_truncate", RELOPT_TYPE_TERNARY,
+ offsetof(StdRdOptions, vacuum_truncate)},
{"vacuum_max_eager_freeze_failure_rate", RELOPT_TYPE_REAL,
offsetof(StdRdOptions, vacuum_max_eager_freeze_failure_rate)}
};
@@ -2004,7 +2090,6 @@ build_local_reloptions(local_relopts *relopts, Datum options, bool validate)
elems[i].optname = opt->option->name;
elems[i].opttype = opt->option->type;
elems[i].offset = opt->offset;
- elems[i].isset_offset = 0; /* not supported for local relopts yet */
i++;
}
diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c
index aa4fbec143f..696eab9bd97 100644
--- a/src/backend/commands/vacuum.c
+++ b/src/backend/commands/vacuum.c
@@ -2224,9 +2224,9 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams params,
{
StdRdOptions *opts = (StdRdOptions *) rel->rd_options;
- if (opts && opts->vacuum_truncate_set)
+ if (opts && opts->vacuum_truncate != TERNARY_UNSET)
{
- if (opts->vacuum_truncate)
+ if (opts->vacuum_truncate == TERNARY_TRUE)
params.truncate = VACOPTVALUE_ENABLED;
else
params.truncate = VACOPTVALUE_DISABLED;
diff --git a/src/include/access/reloptions.h b/src/include/access/reloptions.h
index 2f08e1b0cf0..dfbef2babf2 100644
--- a/src/include/access/reloptions.h
+++ b/src/include/access/reloptions.h
@@ -29,6 +29,7 @@
typedef enum relopt_type
{
RELOPT_TYPE_BOOL,
+ RELOPT_TYPE_TERNARY, /* on, off, unset */
RELOPT_TYPE_INT,
RELOPT_TYPE_REAL,
RELOPT_TYPE_ENUM,
@@ -80,6 +81,7 @@ typedef struct relopt_value
union
{
bool bool_val;
+ pg_ternary ternary_val;
int int_val;
double real_val;
int enum_val;
@@ -94,6 +96,12 @@ typedef struct relopt_bool
bool default_val;
} relopt_bool;
+typedef struct relopt_ternary
+{
+ relopt_gen gen;
+ int default_val;
+} relopt_ternary;
+
typedef struct relopt_int
{
relopt_gen gen;
@@ -152,19 +160,6 @@ typedef struct
const char *optname; /* option's name */
relopt_type opttype; /* option's datatype */
int offset; /* offset of field in result struct */
-
- /*
- * isset_offset is an optional offset of a field in the result struct that
- * stores whether the option is explicitly set for the relation or if it
- * just picked up the default value. In most cases, this can be
- * accomplished by giving the reloption a special out-of-range default
- * value (e.g., some integer reloptions use -2), but this isn't always
- * possible. For example, a Boolean reloption cannot be given an
- * out-of-range default, so we need another way to discover the source of
- * its value. This offset is only used if given a value greater than
- * zero.
- */
- int isset_offset;
} relopt_parse_elt;
/* Local reloption definition */
@@ -195,6 +190,9 @@ typedef struct local_relopts
extern relopt_kind add_reloption_kind(void);
extern void add_bool_reloption(bits32 kinds, const char *name, const char *desc,
bool default_val, LOCKMODE lockmode);
+extern void add_ternary_reloption(bits32 kinds, const char *name,
+ const char *desc, pg_ternary default_val,
+ LOCKMODE lockmode);
extern void add_int_reloption(bits32 kinds, const char *name, const char *desc,
int default_val, int min_val, int max_val,
LOCKMODE lockmode);
@@ -214,6 +212,9 @@ extern void register_reloptions_validator(local_relopts *relopts,
extern void add_local_bool_reloption(local_relopts *relopts, const char *name,
const char *desc, bool default_val,
int offset);
+extern void add_local_ternary_reloption(local_relopts *relopts,
+ const char *name, const char *desc,
+ pg_ternary default_val, int offset);
extern void add_local_int_reloption(local_relopts *relopts, const char *name,
const char *desc, int default_val,
int min_val, int max_val, int offset);
diff --git a/src/include/postgres.h b/src/include/postgres.h
index 7d93fbce709..47c88780776 100644
--- a/src/include/postgres.h
+++ b/src/include/postgres.h
@@ -543,6 +543,21 @@ Float8GetDatum(float8 X)
* ----------------------------------------------------------------
*/
+/*
+ * pg_ternary
+ * Boolean value with an extra "unset" value
+ *
+ * This enum can be used for values that want to distinguish between true,
+ * false, and unset.
+*/
+
+typedef enum pg_ternary
+{
+ TERNARY_FALSE = 0,
+ TERNARY_TRUE = 1,
+ TERNARY_UNSET = -1
+} pg_ternary;
+
/*
* NON_EXEC_STATIC: It's sometimes useful to define a variable or function
* that is normally static but extern when using EXEC_BACKEND (see
diff --git a/src/include/utils/rel.h b/src/include/utils/rel.h
index d03ab247788..5feb18a5373 100644
--- a/src/include/utils/rel.h
+++ b/src/include/utils/rel.h
@@ -347,8 +347,7 @@ typedef struct StdRdOptions
bool user_catalog_table; /* use as an additional catalog relation */
int parallel_workers; /* max number of parallel workers */
StdRdOptIndexCleanup vacuum_index_cleanup; /* controls index vacuuming */
- bool vacuum_truncate; /* enables vacuum to truncate a relation */
- bool vacuum_truncate_set; /* whether vacuum_truncate is set */
+ pg_ternary vacuum_truncate; /* enables vacuum to truncate a relation */
/*
* Fraction of pages in a relation that vacuum can eagerly scan and fail
diff --git a/src/test/regress/expected/reloptions.out b/src/test/regress/expected/reloptions.out
index 9de19b4e3f1..1c99f79ab01 100644
--- a/src/test/regress/expected/reloptions.out
+++ b/src/test/regress/expected/reloptions.out
@@ -98,6 +98,42 @@ SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass;
{fillfactor=13,autovacuum_enabled=false}
(1 row)
+-- Tests for future (FIXME) ternary options
+-- behave as boolean option: accept unassigned name and truncated value
+DROP TABLE reloptions_test;
+CREATE TABLE reloptions_test(i INT) WITH (vacuum_truncate);
+SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass;
+ reloptions
+------------------------
+ {vacuum_truncate=true}
+(1 row)
+
+DROP TABLE reloptions_test;
+CREATE TABLE reloptions_test(i INT) WITH (vacuum_truncate=FaLS);
+SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass;
+ reloptions
+------------------------
+ {vacuum_truncate=fals}
+(1 row)
+
+-- preferred "true" alias is used when storing
+DROP TABLE reloptions_test;
+CREATE TABLE reloptions_test(i INT) WITH (vacuum_index_cleanup=on);
+SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass;
+ reloptions
+---------------------------
+ {vacuum_index_cleanup=on}
+(1 row)
+
+-- custom "third" value is available
+DROP TABLE reloptions_test;
+CREATE TABLE reloptions_test(i INT) WITH (vacuum_index_cleanup=auto);
+SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass;
+ reloptions
+-----------------------------
+ {vacuum_index_cleanup=auto}
+(1 row)
+
-- Test vacuum_truncate option
DROP TABLE reloptions_test;
CREATE TEMP TABLE reloptions_test(i INT NOT NULL, j text)
diff --git a/src/test/regress/sql/reloptions.sql b/src/test/regress/sql/reloptions.sql
index 24fbe0b478d..f5980dafcbc 100644
--- a/src/test/regress/sql/reloptions.sql
+++ b/src/test/regress/sql/reloptions.sql
@@ -59,6 +59,27 @@ UPDATE pg_class
ALTER TABLE reloptions_test RESET (illegal_option);
SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass;
+-- Tests for future (FIXME) ternary options
+
+-- behave as boolean option: accept unassigned name and truncated value
+DROP TABLE reloptions_test;
+CREATE TABLE reloptions_test(i INT) WITH (vacuum_truncate);
+SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass;
+
+DROP TABLE reloptions_test;
+CREATE TABLE reloptions_test(i INT) WITH (vacuum_truncate=FaLS);
+SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass;
+
+-- preferred "true" alias is used when storing
+DROP TABLE reloptions_test;
+CREATE TABLE reloptions_test(i INT) WITH (vacuum_index_cleanup=on);
+SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass;
+
+-- custom "third" value is available
+DROP TABLE reloptions_test;
+CREATE TABLE reloptions_test(i INT) WITH (vacuum_index_cleanup=auto);
+SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass;
+
-- Test vacuum_truncate option
DROP TABLE reloptions_test;
diff --git a/src/tools/pgindent/typedefs.list b/src/tools/pgindent/typedefs.list
index 3f3a888fd0e..1c8610fd46c 100644
--- a/src/tools/pgindent/typedefs.list
+++ b/src/tools/pgindent/typedefs.list
@@ -3952,6 +3952,7 @@ pg_sha512_ctx
pg_snapshot
pg_special_case
pg_stack_base_t
+pg_ternary
pg_time_t
pg_time_usec_t
pg_tz
@@ -4079,6 +4080,7 @@ relopt_kind
relopt_parse_elt
relopt_real
relopt_string
+relopt_ternary
relopt_type
relopt_value
relopts_validator
--
2.47.3
--c36t3fndxhkzbl5z--
^ permalink raw reply [nested|flat] 868+ messages in thread
* [PATCH v3] Introduce ternary reloptions
@ 2026-01-16 15:04 Álvaro Herrera <[email protected]>
0 siblings, 0 replies; 868+ messages in thread
From: Álvaro Herrera @ 2026-01-16 15:04 UTC (permalink / raw)
Introduce ternary reloption as a replacement for current
`vacuum_truncate` implementation. Remove the `vacuum_truncate_set`
separate flag and use TERNARY_UNSET instead.
This could also be used for other options such as `vacuum_index_cleanup`
and `buffering`, but lets get the scaffolding in first.
Discussion: https://postgr.es/m/3474141.usfYGdeWWP@thinkpad-pgpro
---
src/backend/access/common/reloptions.c | 137 ++++++++++++++++++-----
src/backend/commands/vacuum.c | 4 +-
src/include/access/reloptions.h | 27 ++---
src/include/postgres.h | 15 +++
src/include/utils/rel.h | 3 +-
src/test/regress/expected/reloptions.out | 36 ++++++
src/test/regress/sql/reloptions.sql | 21 ++++
src/tools/pgindent/typedefs.list | 2 +
8 files changed, 202 insertions(+), 43 deletions(-)
diff --git a/src/backend/access/common/reloptions.c b/src/backend/access/common/reloptions.c
index 0b83f98ed5f..6f1f577581a 100644
--- a/src/backend/access/common/reloptions.c
+++ b/src/backend/access/common/reloptions.c
@@ -40,9 +40,9 @@
*
* To add an option:
*
- * (i) decide on a type (bool, integer, real, enum, string), name, default
- * value, upper and lower bounds (if applicable); for strings, consider a
- * validation routine.
+ * (i) decide on a type (bool, ternary, integer, real, enum, string), name,
+ * default value, upper and lower bounds (if applicable); for strings,
+ * consider a validation routine.
* (ii) add a record below (or use add_<type>_reloption).
* (iii) add it to the appropriate options struct (perhaps StdRdOptions)
* (iv) add it to the appropriate handling routine (perhaps
@@ -147,15 +147,6 @@ static relopt_bool boolRelOpts[] =
},
false
},
- {
- {
- "vacuum_truncate",
- "Enables vacuum to truncate empty pages at the end of this table",
- RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
- ShareUpdateExclusiveLock
- },
- true
- },
{
{
"deduplicate_items",
@@ -170,6 +161,25 @@ static relopt_bool boolRelOpts[] =
{{NULL}}
};
+static relopt_ternary ternaryRelOpts[] =
+{
+ {
+ {
+ "vacuum_truncate",
+ "Enables vacuum to truncate empty pages at the end of this table",
+ RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
+ ShareUpdateExclusiveLock
+ },
+ TERNARY_UNSET
+ },
+ /* list terminator */
+ {
+ {
+ NULL
+ }
+ }
+};
+
static relopt_int intRelOpts[] =
{
{
@@ -609,6 +619,13 @@ initialize_reloptions(void)
boolRelOpts[i].gen.lockmode));
j++;
}
+ for (i = 0; ternaryRelOpts[i].gen.name; i++)
+ {
+ Assert(DoLockModesConflict(ternaryRelOpts[i].gen.lockmode,
+ ternaryRelOpts[i].gen.lockmode));
+ j++;
+ }
+
for (i = 0; intRelOpts[i].gen.name; i++)
{
Assert(DoLockModesConflict(intRelOpts[i].gen.lockmode,
@@ -649,6 +666,14 @@ initialize_reloptions(void)
j++;
}
+ for (i = 0; ternaryRelOpts[i].gen.name; i++)
+ {
+ relOpts[j] = &ternaryRelOpts[i].gen;
+ relOpts[j]->type = RELOPT_TYPE_TERNARY;
+ relOpts[j]->namelen = strlen(relOpts[j]->name);
+ j++;
+ }
+
for (i = 0; intRelOpts[i].gen.name; i++)
{
relOpts[j] = &intRelOpts[i].gen;
@@ -809,6 +834,9 @@ allocate_reloption(bits32 kinds, int type, const char *name, const char *desc,
case RELOPT_TYPE_BOOL:
size = sizeof(relopt_bool);
break;
+ case RELOPT_TYPE_TERNARY:
+ size = sizeof(relopt_ternary);
+ break;
case RELOPT_TYPE_INT:
size = sizeof(relopt_int);
break;
@@ -892,6 +920,57 @@ add_local_bool_reloption(local_relopts *relopts, const char *name,
add_local_reloption(relopts, (relopt_gen *) newoption, offset);
}
+/*
+ * init_ternary_reloption
+ * Allocate and initialize a new ternary reloption
+ */
+static relopt_ternary *
+init_ternary_reloption(bits32 kinds, const char *name, const char *desc,
+ pg_ternary default_val, LOCKMODE lockmode)
+{
+ relopt_ternary *newoption;
+
+ newoption = (relopt_ternary *)
+ allocate_reloption(kinds, RELOPT_TYPE_TERNARY, name, desc, lockmode);
+ newoption->default_val = default_val;
+
+ return newoption;
+}
+
+/*
+ * add_ternary_reloption
+ * Add a new ternary reloption
+ */
+void
+add_ternary_reloption(bits32 kinds, const char *name, const char *desc,
+ pg_ternary default_val, LOCKMODE lockmode)
+{
+ relopt_ternary *newoption;
+
+ newoption =
+ init_ternary_reloption(kinds, name, desc, default_val, lockmode);
+
+ add_reloption((relopt_gen *) newoption);
+}
+
+/*
+ * add_local_ternary_reloption
+ * Add a new ternary local reloption
+ *
+ * 'offset' is offset of ternary-typed field.
+ */
+void
+add_local_ternary_reloption(local_relopts *relopts, const char *name,
+ const char *desc, pg_ternary default_val,
+ int offset)
+{
+ relopt_ternary *newoption;
+
+ newoption =
+ init_ternary_reloption(RELOPT_KIND_LOCAL, name, desc, default_val, 0);
+
+ add_local_reloption(relopts, (relopt_gen *) newoption, offset);
+}
/*
* init_real_reloption
@@ -1626,6 +1705,19 @@ parse_one_reloption(relopt_value *option, char *text_str, int text_len,
option->gen->name, value)));
}
break;
+ case RELOPT_TYPE_TERNARY:
+ {
+ bool b;
+
+ parsed = parse_bool(value, &b);
+ option->values.ternary_val = b ? TERNARY_TRUE : TERNARY_FALSE;
+ if (validate && !parsed)
+ ereport(ERROR,
+ (errcode(ERRCODE_INVALID_PARAMETER_VALUE),
+ errmsg("invalid value for ternary option \"%s\": %s",
+ option->gen->name, value)));
+ }
+ break;
case RELOPT_TYPE_INT:
{
relopt_int *optint = (relopt_int *) option->gen;
@@ -1789,17 +1881,6 @@ fillRelOptions(void *rdopts, Size basesize,
char *itempos = ((char *) rdopts) + elems[j].offset;
char *string_val;
- /*
- * If isset_offset is provided, store whether the reloption is
- * set there.
- */
- if (elems[j].isset_offset > 0)
- {
- char *setpos = ((char *) rdopts) + elems[j].isset_offset;
-
- *(bool *) setpos = options[i].isset;
- }
-
switch (options[i].gen->type)
{
case RELOPT_TYPE_BOOL:
@@ -1807,6 +1888,11 @@ fillRelOptions(void *rdopts, Size basesize,
options[i].values.bool_val :
((relopt_bool *) options[i].gen)->default_val;
break;
+ case RELOPT_TYPE_TERNARY:
+ *(pg_ternary *) itempos = options[i].isset ?
+ options[i].values.ternary_val :
+ ((relopt_ternary *) options[i].gen)->default_val;
+ break;
case RELOPT_TYPE_INT:
*(int *) itempos = options[i].isset ?
options[i].values.int_val :
@@ -1923,8 +2009,8 @@ default_reloptions(Datum reloptions, bool validate, relopt_kind kind)
offsetof(StdRdOptions, parallel_workers)},
{"vacuum_index_cleanup", RELOPT_TYPE_ENUM,
offsetof(StdRdOptions, vacuum_index_cleanup)},
- {"vacuum_truncate", RELOPT_TYPE_BOOL,
- offsetof(StdRdOptions, vacuum_truncate), offsetof(StdRdOptions, vacuum_truncate_set)},
+ {"vacuum_truncate", RELOPT_TYPE_TERNARY,
+ offsetof(StdRdOptions, vacuum_truncate)},
{"vacuum_max_eager_freeze_failure_rate", RELOPT_TYPE_REAL,
offsetof(StdRdOptions, vacuum_max_eager_freeze_failure_rate)}
};
@@ -2004,7 +2090,6 @@ build_local_reloptions(local_relopts *relopts, Datum options, bool validate)
elems[i].optname = opt->option->name;
elems[i].opttype = opt->option->type;
elems[i].offset = opt->offset;
- elems[i].isset_offset = 0; /* not supported for local relopts yet */
i++;
}
diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c
index aa4fbec143f..696eab9bd97 100644
--- a/src/backend/commands/vacuum.c
+++ b/src/backend/commands/vacuum.c
@@ -2224,9 +2224,9 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams params,
{
StdRdOptions *opts = (StdRdOptions *) rel->rd_options;
- if (opts && opts->vacuum_truncate_set)
+ if (opts && opts->vacuum_truncate != TERNARY_UNSET)
{
- if (opts->vacuum_truncate)
+ if (opts->vacuum_truncate == TERNARY_TRUE)
params.truncate = VACOPTVALUE_ENABLED;
else
params.truncate = VACOPTVALUE_DISABLED;
diff --git a/src/include/access/reloptions.h b/src/include/access/reloptions.h
index 2f08e1b0cf0..dfbef2babf2 100644
--- a/src/include/access/reloptions.h
+++ b/src/include/access/reloptions.h
@@ -29,6 +29,7 @@
typedef enum relopt_type
{
RELOPT_TYPE_BOOL,
+ RELOPT_TYPE_TERNARY, /* on, off, unset */
RELOPT_TYPE_INT,
RELOPT_TYPE_REAL,
RELOPT_TYPE_ENUM,
@@ -80,6 +81,7 @@ typedef struct relopt_value
union
{
bool bool_val;
+ pg_ternary ternary_val;
int int_val;
double real_val;
int enum_val;
@@ -94,6 +96,12 @@ typedef struct relopt_bool
bool default_val;
} relopt_bool;
+typedef struct relopt_ternary
+{
+ relopt_gen gen;
+ int default_val;
+} relopt_ternary;
+
typedef struct relopt_int
{
relopt_gen gen;
@@ -152,19 +160,6 @@ typedef struct
const char *optname; /* option's name */
relopt_type opttype; /* option's datatype */
int offset; /* offset of field in result struct */
-
- /*
- * isset_offset is an optional offset of a field in the result struct that
- * stores whether the option is explicitly set for the relation or if it
- * just picked up the default value. In most cases, this can be
- * accomplished by giving the reloption a special out-of-range default
- * value (e.g., some integer reloptions use -2), but this isn't always
- * possible. For example, a Boolean reloption cannot be given an
- * out-of-range default, so we need another way to discover the source of
- * its value. This offset is only used if given a value greater than
- * zero.
- */
- int isset_offset;
} relopt_parse_elt;
/* Local reloption definition */
@@ -195,6 +190,9 @@ typedef struct local_relopts
extern relopt_kind add_reloption_kind(void);
extern void add_bool_reloption(bits32 kinds, const char *name, const char *desc,
bool default_val, LOCKMODE lockmode);
+extern void add_ternary_reloption(bits32 kinds, const char *name,
+ const char *desc, pg_ternary default_val,
+ LOCKMODE lockmode);
extern void add_int_reloption(bits32 kinds, const char *name, const char *desc,
int default_val, int min_val, int max_val,
LOCKMODE lockmode);
@@ -214,6 +212,9 @@ extern void register_reloptions_validator(local_relopts *relopts,
extern void add_local_bool_reloption(local_relopts *relopts, const char *name,
const char *desc, bool default_val,
int offset);
+extern void add_local_ternary_reloption(local_relopts *relopts,
+ const char *name, const char *desc,
+ pg_ternary default_val, int offset);
extern void add_local_int_reloption(local_relopts *relopts, const char *name,
const char *desc, int default_val,
int min_val, int max_val, int offset);
diff --git a/src/include/postgres.h b/src/include/postgres.h
index 7d93fbce709..47c88780776 100644
--- a/src/include/postgres.h
+++ b/src/include/postgres.h
@@ -543,6 +543,21 @@ Float8GetDatum(float8 X)
* ----------------------------------------------------------------
*/
+/*
+ * pg_ternary
+ * Boolean value with an extra "unset" value
+ *
+ * This enum can be used for values that want to distinguish between true,
+ * false, and unset.
+*/
+
+typedef enum pg_ternary
+{
+ TERNARY_FALSE = 0,
+ TERNARY_TRUE = 1,
+ TERNARY_UNSET = -1
+} pg_ternary;
+
/*
* NON_EXEC_STATIC: It's sometimes useful to define a variable or function
* that is normally static but extern when using EXEC_BACKEND (see
diff --git a/src/include/utils/rel.h b/src/include/utils/rel.h
index d03ab247788..5feb18a5373 100644
--- a/src/include/utils/rel.h
+++ b/src/include/utils/rel.h
@@ -347,8 +347,7 @@ typedef struct StdRdOptions
bool user_catalog_table; /* use as an additional catalog relation */
int parallel_workers; /* max number of parallel workers */
StdRdOptIndexCleanup vacuum_index_cleanup; /* controls index vacuuming */
- bool vacuum_truncate; /* enables vacuum to truncate a relation */
- bool vacuum_truncate_set; /* whether vacuum_truncate is set */
+ pg_ternary vacuum_truncate; /* enables vacuum to truncate a relation */
/*
* Fraction of pages in a relation that vacuum can eagerly scan and fail
diff --git a/src/test/regress/expected/reloptions.out b/src/test/regress/expected/reloptions.out
index 9de19b4e3f1..1c99f79ab01 100644
--- a/src/test/regress/expected/reloptions.out
+++ b/src/test/regress/expected/reloptions.out
@@ -98,6 +98,42 @@ SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass;
{fillfactor=13,autovacuum_enabled=false}
(1 row)
+-- Tests for future (FIXME) ternary options
+-- behave as boolean option: accept unassigned name and truncated value
+DROP TABLE reloptions_test;
+CREATE TABLE reloptions_test(i INT) WITH (vacuum_truncate);
+SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass;
+ reloptions
+------------------------
+ {vacuum_truncate=true}
+(1 row)
+
+DROP TABLE reloptions_test;
+CREATE TABLE reloptions_test(i INT) WITH (vacuum_truncate=FaLS);
+SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass;
+ reloptions
+------------------------
+ {vacuum_truncate=fals}
+(1 row)
+
+-- preferred "true" alias is used when storing
+DROP TABLE reloptions_test;
+CREATE TABLE reloptions_test(i INT) WITH (vacuum_index_cleanup=on);
+SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass;
+ reloptions
+---------------------------
+ {vacuum_index_cleanup=on}
+(1 row)
+
+-- custom "third" value is available
+DROP TABLE reloptions_test;
+CREATE TABLE reloptions_test(i INT) WITH (vacuum_index_cleanup=auto);
+SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass;
+ reloptions
+-----------------------------
+ {vacuum_index_cleanup=auto}
+(1 row)
+
-- Test vacuum_truncate option
DROP TABLE reloptions_test;
CREATE TEMP TABLE reloptions_test(i INT NOT NULL, j text)
diff --git a/src/test/regress/sql/reloptions.sql b/src/test/regress/sql/reloptions.sql
index 24fbe0b478d..f5980dafcbc 100644
--- a/src/test/regress/sql/reloptions.sql
+++ b/src/test/regress/sql/reloptions.sql
@@ -59,6 +59,27 @@ UPDATE pg_class
ALTER TABLE reloptions_test RESET (illegal_option);
SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass;
+-- Tests for future (FIXME) ternary options
+
+-- behave as boolean option: accept unassigned name and truncated value
+DROP TABLE reloptions_test;
+CREATE TABLE reloptions_test(i INT) WITH (vacuum_truncate);
+SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass;
+
+DROP TABLE reloptions_test;
+CREATE TABLE reloptions_test(i INT) WITH (vacuum_truncate=FaLS);
+SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass;
+
+-- preferred "true" alias is used when storing
+DROP TABLE reloptions_test;
+CREATE TABLE reloptions_test(i INT) WITH (vacuum_index_cleanup=on);
+SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass;
+
+-- custom "third" value is available
+DROP TABLE reloptions_test;
+CREATE TABLE reloptions_test(i INT) WITH (vacuum_index_cleanup=auto);
+SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass;
+
-- Test vacuum_truncate option
DROP TABLE reloptions_test;
diff --git a/src/tools/pgindent/typedefs.list b/src/tools/pgindent/typedefs.list
index 3f3a888fd0e..1c8610fd46c 100644
--- a/src/tools/pgindent/typedefs.list
+++ b/src/tools/pgindent/typedefs.list
@@ -3952,6 +3952,7 @@ pg_sha512_ctx
pg_snapshot
pg_special_case
pg_stack_base_t
+pg_ternary
pg_time_t
pg_time_usec_t
pg_tz
@@ -4079,6 +4080,7 @@ relopt_kind
relopt_parse_elt
relopt_real
relopt_string
+relopt_ternary
relopt_type
relopt_value
relopts_validator
--
2.47.3
--c36t3fndxhkzbl5z--
^ permalink raw reply [nested|flat] 868+ messages in thread
* [PATCH v3] Introduce ternary reloptions
@ 2026-01-16 15:04 Álvaro Herrera <[email protected]>
0 siblings, 0 replies; 868+ messages in thread
From: Álvaro Herrera @ 2026-01-16 15:04 UTC (permalink / raw)
Introduce ternary reloption as a replacement for current
`vacuum_truncate` implementation. Remove the `vacuum_truncate_set`
separate flag and use TERNARY_UNSET instead.
This could also be used for other options such as `vacuum_index_cleanup`
and `buffering`, but lets get the scaffolding in first.
Discussion: https://postgr.es/m/3474141.usfYGdeWWP@thinkpad-pgpro
---
src/backend/access/common/reloptions.c | 137 ++++++++++++++++++-----
src/backend/commands/vacuum.c | 4 +-
src/include/access/reloptions.h | 27 ++---
src/include/postgres.h | 15 +++
src/include/utils/rel.h | 3 +-
src/test/regress/expected/reloptions.out | 36 ++++++
src/test/regress/sql/reloptions.sql | 21 ++++
src/tools/pgindent/typedefs.list | 2 +
8 files changed, 202 insertions(+), 43 deletions(-)
diff --git a/src/backend/access/common/reloptions.c b/src/backend/access/common/reloptions.c
index 0b83f98ed5f..6f1f577581a 100644
--- a/src/backend/access/common/reloptions.c
+++ b/src/backend/access/common/reloptions.c
@@ -40,9 +40,9 @@
*
* To add an option:
*
- * (i) decide on a type (bool, integer, real, enum, string), name, default
- * value, upper and lower bounds (if applicable); for strings, consider a
- * validation routine.
+ * (i) decide on a type (bool, ternary, integer, real, enum, string), name,
+ * default value, upper and lower bounds (if applicable); for strings,
+ * consider a validation routine.
* (ii) add a record below (or use add_<type>_reloption).
* (iii) add it to the appropriate options struct (perhaps StdRdOptions)
* (iv) add it to the appropriate handling routine (perhaps
@@ -147,15 +147,6 @@ static relopt_bool boolRelOpts[] =
},
false
},
- {
- {
- "vacuum_truncate",
- "Enables vacuum to truncate empty pages at the end of this table",
- RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
- ShareUpdateExclusiveLock
- },
- true
- },
{
{
"deduplicate_items",
@@ -170,6 +161,25 @@ static relopt_bool boolRelOpts[] =
{{NULL}}
};
+static relopt_ternary ternaryRelOpts[] =
+{
+ {
+ {
+ "vacuum_truncate",
+ "Enables vacuum to truncate empty pages at the end of this table",
+ RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
+ ShareUpdateExclusiveLock
+ },
+ TERNARY_UNSET
+ },
+ /* list terminator */
+ {
+ {
+ NULL
+ }
+ }
+};
+
static relopt_int intRelOpts[] =
{
{
@@ -609,6 +619,13 @@ initialize_reloptions(void)
boolRelOpts[i].gen.lockmode));
j++;
}
+ for (i = 0; ternaryRelOpts[i].gen.name; i++)
+ {
+ Assert(DoLockModesConflict(ternaryRelOpts[i].gen.lockmode,
+ ternaryRelOpts[i].gen.lockmode));
+ j++;
+ }
+
for (i = 0; intRelOpts[i].gen.name; i++)
{
Assert(DoLockModesConflict(intRelOpts[i].gen.lockmode,
@@ -649,6 +666,14 @@ initialize_reloptions(void)
j++;
}
+ for (i = 0; ternaryRelOpts[i].gen.name; i++)
+ {
+ relOpts[j] = &ternaryRelOpts[i].gen;
+ relOpts[j]->type = RELOPT_TYPE_TERNARY;
+ relOpts[j]->namelen = strlen(relOpts[j]->name);
+ j++;
+ }
+
for (i = 0; intRelOpts[i].gen.name; i++)
{
relOpts[j] = &intRelOpts[i].gen;
@@ -809,6 +834,9 @@ allocate_reloption(bits32 kinds, int type, const char *name, const char *desc,
case RELOPT_TYPE_BOOL:
size = sizeof(relopt_bool);
break;
+ case RELOPT_TYPE_TERNARY:
+ size = sizeof(relopt_ternary);
+ break;
case RELOPT_TYPE_INT:
size = sizeof(relopt_int);
break;
@@ -892,6 +920,57 @@ add_local_bool_reloption(local_relopts *relopts, const char *name,
add_local_reloption(relopts, (relopt_gen *) newoption, offset);
}
+/*
+ * init_ternary_reloption
+ * Allocate and initialize a new ternary reloption
+ */
+static relopt_ternary *
+init_ternary_reloption(bits32 kinds, const char *name, const char *desc,
+ pg_ternary default_val, LOCKMODE lockmode)
+{
+ relopt_ternary *newoption;
+
+ newoption = (relopt_ternary *)
+ allocate_reloption(kinds, RELOPT_TYPE_TERNARY, name, desc, lockmode);
+ newoption->default_val = default_val;
+
+ return newoption;
+}
+
+/*
+ * add_ternary_reloption
+ * Add a new ternary reloption
+ */
+void
+add_ternary_reloption(bits32 kinds, const char *name, const char *desc,
+ pg_ternary default_val, LOCKMODE lockmode)
+{
+ relopt_ternary *newoption;
+
+ newoption =
+ init_ternary_reloption(kinds, name, desc, default_val, lockmode);
+
+ add_reloption((relopt_gen *) newoption);
+}
+
+/*
+ * add_local_ternary_reloption
+ * Add a new ternary local reloption
+ *
+ * 'offset' is offset of ternary-typed field.
+ */
+void
+add_local_ternary_reloption(local_relopts *relopts, const char *name,
+ const char *desc, pg_ternary default_val,
+ int offset)
+{
+ relopt_ternary *newoption;
+
+ newoption =
+ init_ternary_reloption(RELOPT_KIND_LOCAL, name, desc, default_val, 0);
+
+ add_local_reloption(relopts, (relopt_gen *) newoption, offset);
+}
/*
* init_real_reloption
@@ -1626,6 +1705,19 @@ parse_one_reloption(relopt_value *option, char *text_str, int text_len,
option->gen->name, value)));
}
break;
+ case RELOPT_TYPE_TERNARY:
+ {
+ bool b;
+
+ parsed = parse_bool(value, &b);
+ option->values.ternary_val = b ? TERNARY_TRUE : TERNARY_FALSE;
+ if (validate && !parsed)
+ ereport(ERROR,
+ (errcode(ERRCODE_INVALID_PARAMETER_VALUE),
+ errmsg("invalid value for ternary option \"%s\": %s",
+ option->gen->name, value)));
+ }
+ break;
case RELOPT_TYPE_INT:
{
relopt_int *optint = (relopt_int *) option->gen;
@@ -1789,17 +1881,6 @@ fillRelOptions(void *rdopts, Size basesize,
char *itempos = ((char *) rdopts) + elems[j].offset;
char *string_val;
- /*
- * If isset_offset is provided, store whether the reloption is
- * set there.
- */
- if (elems[j].isset_offset > 0)
- {
- char *setpos = ((char *) rdopts) + elems[j].isset_offset;
-
- *(bool *) setpos = options[i].isset;
- }
-
switch (options[i].gen->type)
{
case RELOPT_TYPE_BOOL:
@@ -1807,6 +1888,11 @@ fillRelOptions(void *rdopts, Size basesize,
options[i].values.bool_val :
((relopt_bool *) options[i].gen)->default_val;
break;
+ case RELOPT_TYPE_TERNARY:
+ *(pg_ternary *) itempos = options[i].isset ?
+ options[i].values.ternary_val :
+ ((relopt_ternary *) options[i].gen)->default_val;
+ break;
case RELOPT_TYPE_INT:
*(int *) itempos = options[i].isset ?
options[i].values.int_val :
@@ -1923,8 +2009,8 @@ default_reloptions(Datum reloptions, bool validate, relopt_kind kind)
offsetof(StdRdOptions, parallel_workers)},
{"vacuum_index_cleanup", RELOPT_TYPE_ENUM,
offsetof(StdRdOptions, vacuum_index_cleanup)},
- {"vacuum_truncate", RELOPT_TYPE_BOOL,
- offsetof(StdRdOptions, vacuum_truncate), offsetof(StdRdOptions, vacuum_truncate_set)},
+ {"vacuum_truncate", RELOPT_TYPE_TERNARY,
+ offsetof(StdRdOptions, vacuum_truncate)},
{"vacuum_max_eager_freeze_failure_rate", RELOPT_TYPE_REAL,
offsetof(StdRdOptions, vacuum_max_eager_freeze_failure_rate)}
};
@@ -2004,7 +2090,6 @@ build_local_reloptions(local_relopts *relopts, Datum options, bool validate)
elems[i].optname = opt->option->name;
elems[i].opttype = opt->option->type;
elems[i].offset = opt->offset;
- elems[i].isset_offset = 0; /* not supported for local relopts yet */
i++;
}
diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c
index aa4fbec143f..696eab9bd97 100644
--- a/src/backend/commands/vacuum.c
+++ b/src/backend/commands/vacuum.c
@@ -2224,9 +2224,9 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams params,
{
StdRdOptions *opts = (StdRdOptions *) rel->rd_options;
- if (opts && opts->vacuum_truncate_set)
+ if (opts && opts->vacuum_truncate != TERNARY_UNSET)
{
- if (opts->vacuum_truncate)
+ if (opts->vacuum_truncate == TERNARY_TRUE)
params.truncate = VACOPTVALUE_ENABLED;
else
params.truncate = VACOPTVALUE_DISABLED;
diff --git a/src/include/access/reloptions.h b/src/include/access/reloptions.h
index 2f08e1b0cf0..dfbef2babf2 100644
--- a/src/include/access/reloptions.h
+++ b/src/include/access/reloptions.h
@@ -29,6 +29,7 @@
typedef enum relopt_type
{
RELOPT_TYPE_BOOL,
+ RELOPT_TYPE_TERNARY, /* on, off, unset */
RELOPT_TYPE_INT,
RELOPT_TYPE_REAL,
RELOPT_TYPE_ENUM,
@@ -80,6 +81,7 @@ typedef struct relopt_value
union
{
bool bool_val;
+ pg_ternary ternary_val;
int int_val;
double real_val;
int enum_val;
@@ -94,6 +96,12 @@ typedef struct relopt_bool
bool default_val;
} relopt_bool;
+typedef struct relopt_ternary
+{
+ relopt_gen gen;
+ int default_val;
+} relopt_ternary;
+
typedef struct relopt_int
{
relopt_gen gen;
@@ -152,19 +160,6 @@ typedef struct
const char *optname; /* option's name */
relopt_type opttype; /* option's datatype */
int offset; /* offset of field in result struct */
-
- /*
- * isset_offset is an optional offset of a field in the result struct that
- * stores whether the option is explicitly set for the relation or if it
- * just picked up the default value. In most cases, this can be
- * accomplished by giving the reloption a special out-of-range default
- * value (e.g., some integer reloptions use -2), but this isn't always
- * possible. For example, a Boolean reloption cannot be given an
- * out-of-range default, so we need another way to discover the source of
- * its value. This offset is only used if given a value greater than
- * zero.
- */
- int isset_offset;
} relopt_parse_elt;
/* Local reloption definition */
@@ -195,6 +190,9 @@ typedef struct local_relopts
extern relopt_kind add_reloption_kind(void);
extern void add_bool_reloption(bits32 kinds, const char *name, const char *desc,
bool default_val, LOCKMODE lockmode);
+extern void add_ternary_reloption(bits32 kinds, const char *name,
+ const char *desc, pg_ternary default_val,
+ LOCKMODE lockmode);
extern void add_int_reloption(bits32 kinds, const char *name, const char *desc,
int default_val, int min_val, int max_val,
LOCKMODE lockmode);
@@ -214,6 +212,9 @@ extern void register_reloptions_validator(local_relopts *relopts,
extern void add_local_bool_reloption(local_relopts *relopts, const char *name,
const char *desc, bool default_val,
int offset);
+extern void add_local_ternary_reloption(local_relopts *relopts,
+ const char *name, const char *desc,
+ pg_ternary default_val, int offset);
extern void add_local_int_reloption(local_relopts *relopts, const char *name,
const char *desc, int default_val,
int min_val, int max_val, int offset);
diff --git a/src/include/postgres.h b/src/include/postgres.h
index 7d93fbce709..47c88780776 100644
--- a/src/include/postgres.h
+++ b/src/include/postgres.h
@@ -543,6 +543,21 @@ Float8GetDatum(float8 X)
* ----------------------------------------------------------------
*/
+/*
+ * pg_ternary
+ * Boolean value with an extra "unset" value
+ *
+ * This enum can be used for values that want to distinguish between true,
+ * false, and unset.
+*/
+
+typedef enum pg_ternary
+{
+ TERNARY_FALSE = 0,
+ TERNARY_TRUE = 1,
+ TERNARY_UNSET = -1
+} pg_ternary;
+
/*
* NON_EXEC_STATIC: It's sometimes useful to define a variable or function
* that is normally static but extern when using EXEC_BACKEND (see
diff --git a/src/include/utils/rel.h b/src/include/utils/rel.h
index d03ab247788..5feb18a5373 100644
--- a/src/include/utils/rel.h
+++ b/src/include/utils/rel.h
@@ -347,8 +347,7 @@ typedef struct StdRdOptions
bool user_catalog_table; /* use as an additional catalog relation */
int parallel_workers; /* max number of parallel workers */
StdRdOptIndexCleanup vacuum_index_cleanup; /* controls index vacuuming */
- bool vacuum_truncate; /* enables vacuum to truncate a relation */
- bool vacuum_truncate_set; /* whether vacuum_truncate is set */
+ pg_ternary vacuum_truncate; /* enables vacuum to truncate a relation */
/*
* Fraction of pages in a relation that vacuum can eagerly scan and fail
diff --git a/src/test/regress/expected/reloptions.out b/src/test/regress/expected/reloptions.out
index 9de19b4e3f1..1c99f79ab01 100644
--- a/src/test/regress/expected/reloptions.out
+++ b/src/test/regress/expected/reloptions.out
@@ -98,6 +98,42 @@ SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass;
{fillfactor=13,autovacuum_enabled=false}
(1 row)
+-- Tests for future (FIXME) ternary options
+-- behave as boolean option: accept unassigned name and truncated value
+DROP TABLE reloptions_test;
+CREATE TABLE reloptions_test(i INT) WITH (vacuum_truncate);
+SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass;
+ reloptions
+------------------------
+ {vacuum_truncate=true}
+(1 row)
+
+DROP TABLE reloptions_test;
+CREATE TABLE reloptions_test(i INT) WITH (vacuum_truncate=FaLS);
+SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass;
+ reloptions
+------------------------
+ {vacuum_truncate=fals}
+(1 row)
+
+-- preferred "true" alias is used when storing
+DROP TABLE reloptions_test;
+CREATE TABLE reloptions_test(i INT) WITH (vacuum_index_cleanup=on);
+SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass;
+ reloptions
+---------------------------
+ {vacuum_index_cleanup=on}
+(1 row)
+
+-- custom "third" value is available
+DROP TABLE reloptions_test;
+CREATE TABLE reloptions_test(i INT) WITH (vacuum_index_cleanup=auto);
+SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass;
+ reloptions
+-----------------------------
+ {vacuum_index_cleanup=auto}
+(1 row)
+
-- Test vacuum_truncate option
DROP TABLE reloptions_test;
CREATE TEMP TABLE reloptions_test(i INT NOT NULL, j text)
diff --git a/src/test/regress/sql/reloptions.sql b/src/test/regress/sql/reloptions.sql
index 24fbe0b478d..f5980dafcbc 100644
--- a/src/test/regress/sql/reloptions.sql
+++ b/src/test/regress/sql/reloptions.sql
@@ -59,6 +59,27 @@ UPDATE pg_class
ALTER TABLE reloptions_test RESET (illegal_option);
SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass;
+-- Tests for future (FIXME) ternary options
+
+-- behave as boolean option: accept unassigned name and truncated value
+DROP TABLE reloptions_test;
+CREATE TABLE reloptions_test(i INT) WITH (vacuum_truncate);
+SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass;
+
+DROP TABLE reloptions_test;
+CREATE TABLE reloptions_test(i INT) WITH (vacuum_truncate=FaLS);
+SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass;
+
+-- preferred "true" alias is used when storing
+DROP TABLE reloptions_test;
+CREATE TABLE reloptions_test(i INT) WITH (vacuum_index_cleanup=on);
+SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass;
+
+-- custom "third" value is available
+DROP TABLE reloptions_test;
+CREATE TABLE reloptions_test(i INT) WITH (vacuum_index_cleanup=auto);
+SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass;
+
-- Test vacuum_truncate option
DROP TABLE reloptions_test;
diff --git a/src/tools/pgindent/typedefs.list b/src/tools/pgindent/typedefs.list
index 3f3a888fd0e..1c8610fd46c 100644
--- a/src/tools/pgindent/typedefs.list
+++ b/src/tools/pgindent/typedefs.list
@@ -3952,6 +3952,7 @@ pg_sha512_ctx
pg_snapshot
pg_special_case
pg_stack_base_t
+pg_ternary
pg_time_t
pg_time_usec_t
pg_tz
@@ -4079,6 +4080,7 @@ relopt_kind
relopt_parse_elt
relopt_real
relopt_string
+relopt_ternary
relopt_type
relopt_value
relopts_validator
--
2.47.3
--c36t3fndxhkzbl5z--
^ permalink raw reply [nested|flat] 868+ messages in thread
* [PATCH v3] Introduce ternary reloptions
@ 2026-01-16 15:04 Álvaro Herrera <[email protected]>
0 siblings, 0 replies; 868+ messages in thread
From: Álvaro Herrera @ 2026-01-16 15:04 UTC (permalink / raw)
Introduce ternary reloption as a replacement for current
`vacuum_truncate` implementation. Remove the `vacuum_truncate_set`
separate flag and use TERNARY_UNSET instead.
This could also be used for other options such as `vacuum_index_cleanup`
and `buffering`, but lets get the scaffolding in first.
Discussion: https://postgr.es/m/3474141.usfYGdeWWP@thinkpad-pgpro
---
src/backend/access/common/reloptions.c | 137 ++++++++++++++++++-----
src/backend/commands/vacuum.c | 4 +-
src/include/access/reloptions.h | 27 ++---
src/include/postgres.h | 15 +++
src/include/utils/rel.h | 3 +-
src/test/regress/expected/reloptions.out | 36 ++++++
src/test/regress/sql/reloptions.sql | 21 ++++
src/tools/pgindent/typedefs.list | 2 +
8 files changed, 202 insertions(+), 43 deletions(-)
diff --git a/src/backend/access/common/reloptions.c b/src/backend/access/common/reloptions.c
index 0b83f98ed5f..6f1f577581a 100644
--- a/src/backend/access/common/reloptions.c
+++ b/src/backend/access/common/reloptions.c
@@ -40,9 +40,9 @@
*
* To add an option:
*
- * (i) decide on a type (bool, integer, real, enum, string), name, default
- * value, upper and lower bounds (if applicable); for strings, consider a
- * validation routine.
+ * (i) decide on a type (bool, ternary, integer, real, enum, string), name,
+ * default value, upper and lower bounds (if applicable); for strings,
+ * consider a validation routine.
* (ii) add a record below (or use add_<type>_reloption).
* (iii) add it to the appropriate options struct (perhaps StdRdOptions)
* (iv) add it to the appropriate handling routine (perhaps
@@ -147,15 +147,6 @@ static relopt_bool boolRelOpts[] =
},
false
},
- {
- {
- "vacuum_truncate",
- "Enables vacuum to truncate empty pages at the end of this table",
- RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
- ShareUpdateExclusiveLock
- },
- true
- },
{
{
"deduplicate_items",
@@ -170,6 +161,25 @@ static relopt_bool boolRelOpts[] =
{{NULL}}
};
+static relopt_ternary ternaryRelOpts[] =
+{
+ {
+ {
+ "vacuum_truncate",
+ "Enables vacuum to truncate empty pages at the end of this table",
+ RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
+ ShareUpdateExclusiveLock
+ },
+ TERNARY_UNSET
+ },
+ /* list terminator */
+ {
+ {
+ NULL
+ }
+ }
+};
+
static relopt_int intRelOpts[] =
{
{
@@ -609,6 +619,13 @@ initialize_reloptions(void)
boolRelOpts[i].gen.lockmode));
j++;
}
+ for (i = 0; ternaryRelOpts[i].gen.name; i++)
+ {
+ Assert(DoLockModesConflict(ternaryRelOpts[i].gen.lockmode,
+ ternaryRelOpts[i].gen.lockmode));
+ j++;
+ }
+
for (i = 0; intRelOpts[i].gen.name; i++)
{
Assert(DoLockModesConflict(intRelOpts[i].gen.lockmode,
@@ -649,6 +666,14 @@ initialize_reloptions(void)
j++;
}
+ for (i = 0; ternaryRelOpts[i].gen.name; i++)
+ {
+ relOpts[j] = &ternaryRelOpts[i].gen;
+ relOpts[j]->type = RELOPT_TYPE_TERNARY;
+ relOpts[j]->namelen = strlen(relOpts[j]->name);
+ j++;
+ }
+
for (i = 0; intRelOpts[i].gen.name; i++)
{
relOpts[j] = &intRelOpts[i].gen;
@@ -809,6 +834,9 @@ allocate_reloption(bits32 kinds, int type, const char *name, const char *desc,
case RELOPT_TYPE_BOOL:
size = sizeof(relopt_bool);
break;
+ case RELOPT_TYPE_TERNARY:
+ size = sizeof(relopt_ternary);
+ break;
case RELOPT_TYPE_INT:
size = sizeof(relopt_int);
break;
@@ -892,6 +920,57 @@ add_local_bool_reloption(local_relopts *relopts, const char *name,
add_local_reloption(relopts, (relopt_gen *) newoption, offset);
}
+/*
+ * init_ternary_reloption
+ * Allocate and initialize a new ternary reloption
+ */
+static relopt_ternary *
+init_ternary_reloption(bits32 kinds, const char *name, const char *desc,
+ pg_ternary default_val, LOCKMODE lockmode)
+{
+ relopt_ternary *newoption;
+
+ newoption = (relopt_ternary *)
+ allocate_reloption(kinds, RELOPT_TYPE_TERNARY, name, desc, lockmode);
+ newoption->default_val = default_val;
+
+ return newoption;
+}
+
+/*
+ * add_ternary_reloption
+ * Add a new ternary reloption
+ */
+void
+add_ternary_reloption(bits32 kinds, const char *name, const char *desc,
+ pg_ternary default_val, LOCKMODE lockmode)
+{
+ relopt_ternary *newoption;
+
+ newoption =
+ init_ternary_reloption(kinds, name, desc, default_val, lockmode);
+
+ add_reloption((relopt_gen *) newoption);
+}
+
+/*
+ * add_local_ternary_reloption
+ * Add a new ternary local reloption
+ *
+ * 'offset' is offset of ternary-typed field.
+ */
+void
+add_local_ternary_reloption(local_relopts *relopts, const char *name,
+ const char *desc, pg_ternary default_val,
+ int offset)
+{
+ relopt_ternary *newoption;
+
+ newoption =
+ init_ternary_reloption(RELOPT_KIND_LOCAL, name, desc, default_val, 0);
+
+ add_local_reloption(relopts, (relopt_gen *) newoption, offset);
+}
/*
* init_real_reloption
@@ -1626,6 +1705,19 @@ parse_one_reloption(relopt_value *option, char *text_str, int text_len,
option->gen->name, value)));
}
break;
+ case RELOPT_TYPE_TERNARY:
+ {
+ bool b;
+
+ parsed = parse_bool(value, &b);
+ option->values.ternary_val = b ? TERNARY_TRUE : TERNARY_FALSE;
+ if (validate && !parsed)
+ ereport(ERROR,
+ (errcode(ERRCODE_INVALID_PARAMETER_VALUE),
+ errmsg("invalid value for ternary option \"%s\": %s",
+ option->gen->name, value)));
+ }
+ break;
case RELOPT_TYPE_INT:
{
relopt_int *optint = (relopt_int *) option->gen;
@@ -1789,17 +1881,6 @@ fillRelOptions(void *rdopts, Size basesize,
char *itempos = ((char *) rdopts) + elems[j].offset;
char *string_val;
- /*
- * If isset_offset is provided, store whether the reloption is
- * set there.
- */
- if (elems[j].isset_offset > 0)
- {
- char *setpos = ((char *) rdopts) + elems[j].isset_offset;
-
- *(bool *) setpos = options[i].isset;
- }
-
switch (options[i].gen->type)
{
case RELOPT_TYPE_BOOL:
@@ -1807,6 +1888,11 @@ fillRelOptions(void *rdopts, Size basesize,
options[i].values.bool_val :
((relopt_bool *) options[i].gen)->default_val;
break;
+ case RELOPT_TYPE_TERNARY:
+ *(pg_ternary *) itempos = options[i].isset ?
+ options[i].values.ternary_val :
+ ((relopt_ternary *) options[i].gen)->default_val;
+ break;
case RELOPT_TYPE_INT:
*(int *) itempos = options[i].isset ?
options[i].values.int_val :
@@ -1923,8 +2009,8 @@ default_reloptions(Datum reloptions, bool validate, relopt_kind kind)
offsetof(StdRdOptions, parallel_workers)},
{"vacuum_index_cleanup", RELOPT_TYPE_ENUM,
offsetof(StdRdOptions, vacuum_index_cleanup)},
- {"vacuum_truncate", RELOPT_TYPE_BOOL,
- offsetof(StdRdOptions, vacuum_truncate), offsetof(StdRdOptions, vacuum_truncate_set)},
+ {"vacuum_truncate", RELOPT_TYPE_TERNARY,
+ offsetof(StdRdOptions, vacuum_truncate)},
{"vacuum_max_eager_freeze_failure_rate", RELOPT_TYPE_REAL,
offsetof(StdRdOptions, vacuum_max_eager_freeze_failure_rate)}
};
@@ -2004,7 +2090,6 @@ build_local_reloptions(local_relopts *relopts, Datum options, bool validate)
elems[i].optname = opt->option->name;
elems[i].opttype = opt->option->type;
elems[i].offset = opt->offset;
- elems[i].isset_offset = 0; /* not supported for local relopts yet */
i++;
}
diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c
index aa4fbec143f..696eab9bd97 100644
--- a/src/backend/commands/vacuum.c
+++ b/src/backend/commands/vacuum.c
@@ -2224,9 +2224,9 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams params,
{
StdRdOptions *opts = (StdRdOptions *) rel->rd_options;
- if (opts && opts->vacuum_truncate_set)
+ if (opts && opts->vacuum_truncate != TERNARY_UNSET)
{
- if (opts->vacuum_truncate)
+ if (opts->vacuum_truncate == TERNARY_TRUE)
params.truncate = VACOPTVALUE_ENABLED;
else
params.truncate = VACOPTVALUE_DISABLED;
diff --git a/src/include/access/reloptions.h b/src/include/access/reloptions.h
index 2f08e1b0cf0..dfbef2babf2 100644
--- a/src/include/access/reloptions.h
+++ b/src/include/access/reloptions.h
@@ -29,6 +29,7 @@
typedef enum relopt_type
{
RELOPT_TYPE_BOOL,
+ RELOPT_TYPE_TERNARY, /* on, off, unset */
RELOPT_TYPE_INT,
RELOPT_TYPE_REAL,
RELOPT_TYPE_ENUM,
@@ -80,6 +81,7 @@ typedef struct relopt_value
union
{
bool bool_val;
+ pg_ternary ternary_val;
int int_val;
double real_val;
int enum_val;
@@ -94,6 +96,12 @@ typedef struct relopt_bool
bool default_val;
} relopt_bool;
+typedef struct relopt_ternary
+{
+ relopt_gen gen;
+ int default_val;
+} relopt_ternary;
+
typedef struct relopt_int
{
relopt_gen gen;
@@ -152,19 +160,6 @@ typedef struct
const char *optname; /* option's name */
relopt_type opttype; /* option's datatype */
int offset; /* offset of field in result struct */
-
- /*
- * isset_offset is an optional offset of a field in the result struct that
- * stores whether the option is explicitly set for the relation or if it
- * just picked up the default value. In most cases, this can be
- * accomplished by giving the reloption a special out-of-range default
- * value (e.g., some integer reloptions use -2), but this isn't always
- * possible. For example, a Boolean reloption cannot be given an
- * out-of-range default, so we need another way to discover the source of
- * its value. This offset is only used if given a value greater than
- * zero.
- */
- int isset_offset;
} relopt_parse_elt;
/* Local reloption definition */
@@ -195,6 +190,9 @@ typedef struct local_relopts
extern relopt_kind add_reloption_kind(void);
extern void add_bool_reloption(bits32 kinds, const char *name, const char *desc,
bool default_val, LOCKMODE lockmode);
+extern void add_ternary_reloption(bits32 kinds, const char *name,
+ const char *desc, pg_ternary default_val,
+ LOCKMODE lockmode);
extern void add_int_reloption(bits32 kinds, const char *name, const char *desc,
int default_val, int min_val, int max_val,
LOCKMODE lockmode);
@@ -214,6 +212,9 @@ extern void register_reloptions_validator(local_relopts *relopts,
extern void add_local_bool_reloption(local_relopts *relopts, const char *name,
const char *desc, bool default_val,
int offset);
+extern void add_local_ternary_reloption(local_relopts *relopts,
+ const char *name, const char *desc,
+ pg_ternary default_val, int offset);
extern void add_local_int_reloption(local_relopts *relopts, const char *name,
const char *desc, int default_val,
int min_val, int max_val, int offset);
diff --git a/src/include/postgres.h b/src/include/postgres.h
index 7d93fbce709..47c88780776 100644
--- a/src/include/postgres.h
+++ b/src/include/postgres.h
@@ -543,6 +543,21 @@ Float8GetDatum(float8 X)
* ----------------------------------------------------------------
*/
+/*
+ * pg_ternary
+ * Boolean value with an extra "unset" value
+ *
+ * This enum can be used for values that want to distinguish between true,
+ * false, and unset.
+*/
+
+typedef enum pg_ternary
+{
+ TERNARY_FALSE = 0,
+ TERNARY_TRUE = 1,
+ TERNARY_UNSET = -1
+} pg_ternary;
+
/*
* NON_EXEC_STATIC: It's sometimes useful to define a variable or function
* that is normally static but extern when using EXEC_BACKEND (see
diff --git a/src/include/utils/rel.h b/src/include/utils/rel.h
index d03ab247788..5feb18a5373 100644
--- a/src/include/utils/rel.h
+++ b/src/include/utils/rel.h
@@ -347,8 +347,7 @@ typedef struct StdRdOptions
bool user_catalog_table; /* use as an additional catalog relation */
int parallel_workers; /* max number of parallel workers */
StdRdOptIndexCleanup vacuum_index_cleanup; /* controls index vacuuming */
- bool vacuum_truncate; /* enables vacuum to truncate a relation */
- bool vacuum_truncate_set; /* whether vacuum_truncate is set */
+ pg_ternary vacuum_truncate; /* enables vacuum to truncate a relation */
/*
* Fraction of pages in a relation that vacuum can eagerly scan and fail
diff --git a/src/test/regress/expected/reloptions.out b/src/test/regress/expected/reloptions.out
index 9de19b4e3f1..1c99f79ab01 100644
--- a/src/test/regress/expected/reloptions.out
+++ b/src/test/regress/expected/reloptions.out
@@ -98,6 +98,42 @@ SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass;
{fillfactor=13,autovacuum_enabled=false}
(1 row)
+-- Tests for future (FIXME) ternary options
+-- behave as boolean option: accept unassigned name and truncated value
+DROP TABLE reloptions_test;
+CREATE TABLE reloptions_test(i INT) WITH (vacuum_truncate);
+SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass;
+ reloptions
+------------------------
+ {vacuum_truncate=true}
+(1 row)
+
+DROP TABLE reloptions_test;
+CREATE TABLE reloptions_test(i INT) WITH (vacuum_truncate=FaLS);
+SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass;
+ reloptions
+------------------------
+ {vacuum_truncate=fals}
+(1 row)
+
+-- preferred "true" alias is used when storing
+DROP TABLE reloptions_test;
+CREATE TABLE reloptions_test(i INT) WITH (vacuum_index_cleanup=on);
+SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass;
+ reloptions
+---------------------------
+ {vacuum_index_cleanup=on}
+(1 row)
+
+-- custom "third" value is available
+DROP TABLE reloptions_test;
+CREATE TABLE reloptions_test(i INT) WITH (vacuum_index_cleanup=auto);
+SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass;
+ reloptions
+-----------------------------
+ {vacuum_index_cleanup=auto}
+(1 row)
+
-- Test vacuum_truncate option
DROP TABLE reloptions_test;
CREATE TEMP TABLE reloptions_test(i INT NOT NULL, j text)
diff --git a/src/test/regress/sql/reloptions.sql b/src/test/regress/sql/reloptions.sql
index 24fbe0b478d..f5980dafcbc 100644
--- a/src/test/regress/sql/reloptions.sql
+++ b/src/test/regress/sql/reloptions.sql
@@ -59,6 +59,27 @@ UPDATE pg_class
ALTER TABLE reloptions_test RESET (illegal_option);
SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass;
+-- Tests for future (FIXME) ternary options
+
+-- behave as boolean option: accept unassigned name and truncated value
+DROP TABLE reloptions_test;
+CREATE TABLE reloptions_test(i INT) WITH (vacuum_truncate);
+SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass;
+
+DROP TABLE reloptions_test;
+CREATE TABLE reloptions_test(i INT) WITH (vacuum_truncate=FaLS);
+SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass;
+
+-- preferred "true" alias is used when storing
+DROP TABLE reloptions_test;
+CREATE TABLE reloptions_test(i INT) WITH (vacuum_index_cleanup=on);
+SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass;
+
+-- custom "third" value is available
+DROP TABLE reloptions_test;
+CREATE TABLE reloptions_test(i INT) WITH (vacuum_index_cleanup=auto);
+SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass;
+
-- Test vacuum_truncate option
DROP TABLE reloptions_test;
diff --git a/src/tools/pgindent/typedefs.list b/src/tools/pgindent/typedefs.list
index 3f3a888fd0e..1c8610fd46c 100644
--- a/src/tools/pgindent/typedefs.list
+++ b/src/tools/pgindent/typedefs.list
@@ -3952,6 +3952,7 @@ pg_sha512_ctx
pg_snapshot
pg_special_case
pg_stack_base_t
+pg_ternary
pg_time_t
pg_time_usec_t
pg_tz
@@ -4079,6 +4080,7 @@ relopt_kind
relopt_parse_elt
relopt_real
relopt_string
+relopt_ternary
relopt_type
relopt_value
relopts_validator
--
2.47.3
--c36t3fndxhkzbl5z--
^ permalink raw reply [nested|flat] 868+ messages in thread
* [PATCH v3] Introduce ternary reloptions
@ 2026-01-16 15:04 Álvaro Herrera <[email protected]>
0 siblings, 0 replies; 868+ messages in thread
From: Álvaro Herrera @ 2026-01-16 15:04 UTC (permalink / raw)
Introduce ternary reloption as a replacement for current
`vacuum_truncate` implementation. Remove the `vacuum_truncate_set`
separate flag and use TERNARY_UNSET instead.
This could also be used for other options such as `vacuum_index_cleanup`
and `buffering`, but lets get the scaffolding in first.
Discussion: https://postgr.es/m/3474141.usfYGdeWWP@thinkpad-pgpro
---
src/backend/access/common/reloptions.c | 137 ++++++++++++++++++-----
src/backend/commands/vacuum.c | 4 +-
src/include/access/reloptions.h | 27 ++---
src/include/postgres.h | 15 +++
src/include/utils/rel.h | 3 +-
src/test/regress/expected/reloptions.out | 36 ++++++
src/test/regress/sql/reloptions.sql | 21 ++++
src/tools/pgindent/typedefs.list | 2 +
8 files changed, 202 insertions(+), 43 deletions(-)
diff --git a/src/backend/access/common/reloptions.c b/src/backend/access/common/reloptions.c
index 0b83f98ed5f..6f1f577581a 100644
--- a/src/backend/access/common/reloptions.c
+++ b/src/backend/access/common/reloptions.c
@@ -40,9 +40,9 @@
*
* To add an option:
*
- * (i) decide on a type (bool, integer, real, enum, string), name, default
- * value, upper and lower bounds (if applicable); for strings, consider a
- * validation routine.
+ * (i) decide on a type (bool, ternary, integer, real, enum, string), name,
+ * default value, upper and lower bounds (if applicable); for strings,
+ * consider a validation routine.
* (ii) add a record below (or use add_<type>_reloption).
* (iii) add it to the appropriate options struct (perhaps StdRdOptions)
* (iv) add it to the appropriate handling routine (perhaps
@@ -147,15 +147,6 @@ static relopt_bool boolRelOpts[] =
},
false
},
- {
- {
- "vacuum_truncate",
- "Enables vacuum to truncate empty pages at the end of this table",
- RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
- ShareUpdateExclusiveLock
- },
- true
- },
{
{
"deduplicate_items",
@@ -170,6 +161,25 @@ static relopt_bool boolRelOpts[] =
{{NULL}}
};
+static relopt_ternary ternaryRelOpts[] =
+{
+ {
+ {
+ "vacuum_truncate",
+ "Enables vacuum to truncate empty pages at the end of this table",
+ RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
+ ShareUpdateExclusiveLock
+ },
+ TERNARY_UNSET
+ },
+ /* list terminator */
+ {
+ {
+ NULL
+ }
+ }
+};
+
static relopt_int intRelOpts[] =
{
{
@@ -609,6 +619,13 @@ initialize_reloptions(void)
boolRelOpts[i].gen.lockmode));
j++;
}
+ for (i = 0; ternaryRelOpts[i].gen.name; i++)
+ {
+ Assert(DoLockModesConflict(ternaryRelOpts[i].gen.lockmode,
+ ternaryRelOpts[i].gen.lockmode));
+ j++;
+ }
+
for (i = 0; intRelOpts[i].gen.name; i++)
{
Assert(DoLockModesConflict(intRelOpts[i].gen.lockmode,
@@ -649,6 +666,14 @@ initialize_reloptions(void)
j++;
}
+ for (i = 0; ternaryRelOpts[i].gen.name; i++)
+ {
+ relOpts[j] = &ternaryRelOpts[i].gen;
+ relOpts[j]->type = RELOPT_TYPE_TERNARY;
+ relOpts[j]->namelen = strlen(relOpts[j]->name);
+ j++;
+ }
+
for (i = 0; intRelOpts[i].gen.name; i++)
{
relOpts[j] = &intRelOpts[i].gen;
@@ -809,6 +834,9 @@ allocate_reloption(bits32 kinds, int type, const char *name, const char *desc,
case RELOPT_TYPE_BOOL:
size = sizeof(relopt_bool);
break;
+ case RELOPT_TYPE_TERNARY:
+ size = sizeof(relopt_ternary);
+ break;
case RELOPT_TYPE_INT:
size = sizeof(relopt_int);
break;
@@ -892,6 +920,57 @@ add_local_bool_reloption(local_relopts *relopts, const char *name,
add_local_reloption(relopts, (relopt_gen *) newoption, offset);
}
+/*
+ * init_ternary_reloption
+ * Allocate and initialize a new ternary reloption
+ */
+static relopt_ternary *
+init_ternary_reloption(bits32 kinds, const char *name, const char *desc,
+ pg_ternary default_val, LOCKMODE lockmode)
+{
+ relopt_ternary *newoption;
+
+ newoption = (relopt_ternary *)
+ allocate_reloption(kinds, RELOPT_TYPE_TERNARY, name, desc, lockmode);
+ newoption->default_val = default_val;
+
+ return newoption;
+}
+
+/*
+ * add_ternary_reloption
+ * Add a new ternary reloption
+ */
+void
+add_ternary_reloption(bits32 kinds, const char *name, const char *desc,
+ pg_ternary default_val, LOCKMODE lockmode)
+{
+ relopt_ternary *newoption;
+
+ newoption =
+ init_ternary_reloption(kinds, name, desc, default_val, lockmode);
+
+ add_reloption((relopt_gen *) newoption);
+}
+
+/*
+ * add_local_ternary_reloption
+ * Add a new ternary local reloption
+ *
+ * 'offset' is offset of ternary-typed field.
+ */
+void
+add_local_ternary_reloption(local_relopts *relopts, const char *name,
+ const char *desc, pg_ternary default_val,
+ int offset)
+{
+ relopt_ternary *newoption;
+
+ newoption =
+ init_ternary_reloption(RELOPT_KIND_LOCAL, name, desc, default_val, 0);
+
+ add_local_reloption(relopts, (relopt_gen *) newoption, offset);
+}
/*
* init_real_reloption
@@ -1626,6 +1705,19 @@ parse_one_reloption(relopt_value *option, char *text_str, int text_len,
option->gen->name, value)));
}
break;
+ case RELOPT_TYPE_TERNARY:
+ {
+ bool b;
+
+ parsed = parse_bool(value, &b);
+ option->values.ternary_val = b ? TERNARY_TRUE : TERNARY_FALSE;
+ if (validate && !parsed)
+ ereport(ERROR,
+ (errcode(ERRCODE_INVALID_PARAMETER_VALUE),
+ errmsg("invalid value for ternary option \"%s\": %s",
+ option->gen->name, value)));
+ }
+ break;
case RELOPT_TYPE_INT:
{
relopt_int *optint = (relopt_int *) option->gen;
@@ -1789,17 +1881,6 @@ fillRelOptions(void *rdopts, Size basesize,
char *itempos = ((char *) rdopts) + elems[j].offset;
char *string_val;
- /*
- * If isset_offset is provided, store whether the reloption is
- * set there.
- */
- if (elems[j].isset_offset > 0)
- {
- char *setpos = ((char *) rdopts) + elems[j].isset_offset;
-
- *(bool *) setpos = options[i].isset;
- }
-
switch (options[i].gen->type)
{
case RELOPT_TYPE_BOOL:
@@ -1807,6 +1888,11 @@ fillRelOptions(void *rdopts, Size basesize,
options[i].values.bool_val :
((relopt_bool *) options[i].gen)->default_val;
break;
+ case RELOPT_TYPE_TERNARY:
+ *(pg_ternary *) itempos = options[i].isset ?
+ options[i].values.ternary_val :
+ ((relopt_ternary *) options[i].gen)->default_val;
+ break;
case RELOPT_TYPE_INT:
*(int *) itempos = options[i].isset ?
options[i].values.int_val :
@@ -1923,8 +2009,8 @@ default_reloptions(Datum reloptions, bool validate, relopt_kind kind)
offsetof(StdRdOptions, parallel_workers)},
{"vacuum_index_cleanup", RELOPT_TYPE_ENUM,
offsetof(StdRdOptions, vacuum_index_cleanup)},
- {"vacuum_truncate", RELOPT_TYPE_BOOL,
- offsetof(StdRdOptions, vacuum_truncate), offsetof(StdRdOptions, vacuum_truncate_set)},
+ {"vacuum_truncate", RELOPT_TYPE_TERNARY,
+ offsetof(StdRdOptions, vacuum_truncate)},
{"vacuum_max_eager_freeze_failure_rate", RELOPT_TYPE_REAL,
offsetof(StdRdOptions, vacuum_max_eager_freeze_failure_rate)}
};
@@ -2004,7 +2090,6 @@ build_local_reloptions(local_relopts *relopts, Datum options, bool validate)
elems[i].optname = opt->option->name;
elems[i].opttype = opt->option->type;
elems[i].offset = opt->offset;
- elems[i].isset_offset = 0; /* not supported for local relopts yet */
i++;
}
diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c
index aa4fbec143f..696eab9bd97 100644
--- a/src/backend/commands/vacuum.c
+++ b/src/backend/commands/vacuum.c
@@ -2224,9 +2224,9 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams params,
{
StdRdOptions *opts = (StdRdOptions *) rel->rd_options;
- if (opts && opts->vacuum_truncate_set)
+ if (opts && opts->vacuum_truncate != TERNARY_UNSET)
{
- if (opts->vacuum_truncate)
+ if (opts->vacuum_truncate == TERNARY_TRUE)
params.truncate = VACOPTVALUE_ENABLED;
else
params.truncate = VACOPTVALUE_DISABLED;
diff --git a/src/include/access/reloptions.h b/src/include/access/reloptions.h
index 2f08e1b0cf0..dfbef2babf2 100644
--- a/src/include/access/reloptions.h
+++ b/src/include/access/reloptions.h
@@ -29,6 +29,7 @@
typedef enum relopt_type
{
RELOPT_TYPE_BOOL,
+ RELOPT_TYPE_TERNARY, /* on, off, unset */
RELOPT_TYPE_INT,
RELOPT_TYPE_REAL,
RELOPT_TYPE_ENUM,
@@ -80,6 +81,7 @@ typedef struct relopt_value
union
{
bool bool_val;
+ pg_ternary ternary_val;
int int_val;
double real_val;
int enum_val;
@@ -94,6 +96,12 @@ typedef struct relopt_bool
bool default_val;
} relopt_bool;
+typedef struct relopt_ternary
+{
+ relopt_gen gen;
+ int default_val;
+} relopt_ternary;
+
typedef struct relopt_int
{
relopt_gen gen;
@@ -152,19 +160,6 @@ typedef struct
const char *optname; /* option's name */
relopt_type opttype; /* option's datatype */
int offset; /* offset of field in result struct */
-
- /*
- * isset_offset is an optional offset of a field in the result struct that
- * stores whether the option is explicitly set for the relation or if it
- * just picked up the default value. In most cases, this can be
- * accomplished by giving the reloption a special out-of-range default
- * value (e.g., some integer reloptions use -2), but this isn't always
- * possible. For example, a Boolean reloption cannot be given an
- * out-of-range default, so we need another way to discover the source of
- * its value. This offset is only used if given a value greater than
- * zero.
- */
- int isset_offset;
} relopt_parse_elt;
/* Local reloption definition */
@@ -195,6 +190,9 @@ typedef struct local_relopts
extern relopt_kind add_reloption_kind(void);
extern void add_bool_reloption(bits32 kinds, const char *name, const char *desc,
bool default_val, LOCKMODE lockmode);
+extern void add_ternary_reloption(bits32 kinds, const char *name,
+ const char *desc, pg_ternary default_val,
+ LOCKMODE lockmode);
extern void add_int_reloption(bits32 kinds, const char *name, const char *desc,
int default_val, int min_val, int max_val,
LOCKMODE lockmode);
@@ -214,6 +212,9 @@ extern void register_reloptions_validator(local_relopts *relopts,
extern void add_local_bool_reloption(local_relopts *relopts, const char *name,
const char *desc, bool default_val,
int offset);
+extern void add_local_ternary_reloption(local_relopts *relopts,
+ const char *name, const char *desc,
+ pg_ternary default_val, int offset);
extern void add_local_int_reloption(local_relopts *relopts, const char *name,
const char *desc, int default_val,
int min_val, int max_val, int offset);
diff --git a/src/include/postgres.h b/src/include/postgres.h
index 7d93fbce709..47c88780776 100644
--- a/src/include/postgres.h
+++ b/src/include/postgres.h
@@ -543,6 +543,21 @@ Float8GetDatum(float8 X)
* ----------------------------------------------------------------
*/
+/*
+ * pg_ternary
+ * Boolean value with an extra "unset" value
+ *
+ * This enum can be used for values that want to distinguish between true,
+ * false, and unset.
+*/
+
+typedef enum pg_ternary
+{
+ TERNARY_FALSE = 0,
+ TERNARY_TRUE = 1,
+ TERNARY_UNSET = -1
+} pg_ternary;
+
/*
* NON_EXEC_STATIC: It's sometimes useful to define a variable or function
* that is normally static but extern when using EXEC_BACKEND (see
diff --git a/src/include/utils/rel.h b/src/include/utils/rel.h
index d03ab247788..5feb18a5373 100644
--- a/src/include/utils/rel.h
+++ b/src/include/utils/rel.h
@@ -347,8 +347,7 @@ typedef struct StdRdOptions
bool user_catalog_table; /* use as an additional catalog relation */
int parallel_workers; /* max number of parallel workers */
StdRdOptIndexCleanup vacuum_index_cleanup; /* controls index vacuuming */
- bool vacuum_truncate; /* enables vacuum to truncate a relation */
- bool vacuum_truncate_set; /* whether vacuum_truncate is set */
+ pg_ternary vacuum_truncate; /* enables vacuum to truncate a relation */
/*
* Fraction of pages in a relation that vacuum can eagerly scan and fail
diff --git a/src/test/regress/expected/reloptions.out b/src/test/regress/expected/reloptions.out
index 9de19b4e3f1..1c99f79ab01 100644
--- a/src/test/regress/expected/reloptions.out
+++ b/src/test/regress/expected/reloptions.out
@@ -98,6 +98,42 @@ SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass;
{fillfactor=13,autovacuum_enabled=false}
(1 row)
+-- Tests for future (FIXME) ternary options
+-- behave as boolean option: accept unassigned name and truncated value
+DROP TABLE reloptions_test;
+CREATE TABLE reloptions_test(i INT) WITH (vacuum_truncate);
+SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass;
+ reloptions
+------------------------
+ {vacuum_truncate=true}
+(1 row)
+
+DROP TABLE reloptions_test;
+CREATE TABLE reloptions_test(i INT) WITH (vacuum_truncate=FaLS);
+SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass;
+ reloptions
+------------------------
+ {vacuum_truncate=fals}
+(1 row)
+
+-- preferred "true" alias is used when storing
+DROP TABLE reloptions_test;
+CREATE TABLE reloptions_test(i INT) WITH (vacuum_index_cleanup=on);
+SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass;
+ reloptions
+---------------------------
+ {vacuum_index_cleanup=on}
+(1 row)
+
+-- custom "third" value is available
+DROP TABLE reloptions_test;
+CREATE TABLE reloptions_test(i INT) WITH (vacuum_index_cleanup=auto);
+SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass;
+ reloptions
+-----------------------------
+ {vacuum_index_cleanup=auto}
+(1 row)
+
-- Test vacuum_truncate option
DROP TABLE reloptions_test;
CREATE TEMP TABLE reloptions_test(i INT NOT NULL, j text)
diff --git a/src/test/regress/sql/reloptions.sql b/src/test/regress/sql/reloptions.sql
index 24fbe0b478d..f5980dafcbc 100644
--- a/src/test/regress/sql/reloptions.sql
+++ b/src/test/regress/sql/reloptions.sql
@@ -59,6 +59,27 @@ UPDATE pg_class
ALTER TABLE reloptions_test RESET (illegal_option);
SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass;
+-- Tests for future (FIXME) ternary options
+
+-- behave as boolean option: accept unassigned name and truncated value
+DROP TABLE reloptions_test;
+CREATE TABLE reloptions_test(i INT) WITH (vacuum_truncate);
+SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass;
+
+DROP TABLE reloptions_test;
+CREATE TABLE reloptions_test(i INT) WITH (vacuum_truncate=FaLS);
+SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass;
+
+-- preferred "true" alias is used when storing
+DROP TABLE reloptions_test;
+CREATE TABLE reloptions_test(i INT) WITH (vacuum_index_cleanup=on);
+SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass;
+
+-- custom "third" value is available
+DROP TABLE reloptions_test;
+CREATE TABLE reloptions_test(i INT) WITH (vacuum_index_cleanup=auto);
+SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass;
+
-- Test vacuum_truncate option
DROP TABLE reloptions_test;
diff --git a/src/tools/pgindent/typedefs.list b/src/tools/pgindent/typedefs.list
index 3f3a888fd0e..1c8610fd46c 100644
--- a/src/tools/pgindent/typedefs.list
+++ b/src/tools/pgindent/typedefs.list
@@ -3952,6 +3952,7 @@ pg_sha512_ctx
pg_snapshot
pg_special_case
pg_stack_base_t
+pg_ternary
pg_time_t
pg_time_usec_t
pg_tz
@@ -4079,6 +4080,7 @@ relopt_kind
relopt_parse_elt
relopt_real
relopt_string
+relopt_ternary
relopt_type
relopt_value
relopts_validator
--
2.47.3
--c36t3fndxhkzbl5z--
^ permalink raw reply [nested|flat] 868+ messages in thread
* [PATCH v3] Introduce ternary reloptions
@ 2026-01-16 15:04 Álvaro Herrera <[email protected]>
0 siblings, 0 replies; 868+ messages in thread
From: Álvaro Herrera @ 2026-01-16 15:04 UTC (permalink / raw)
Introduce ternary reloption as a replacement for current
`vacuum_truncate` implementation. Remove the `vacuum_truncate_set`
separate flag and use TERNARY_UNSET instead.
This could also be used for other options such as `vacuum_index_cleanup`
and `buffering`, but lets get the scaffolding in first.
Discussion: https://postgr.es/m/3474141.usfYGdeWWP@thinkpad-pgpro
---
src/backend/access/common/reloptions.c | 137 ++++++++++++++++++-----
src/backend/commands/vacuum.c | 4 +-
src/include/access/reloptions.h | 27 ++---
src/include/postgres.h | 15 +++
src/include/utils/rel.h | 3 +-
src/test/regress/expected/reloptions.out | 36 ++++++
src/test/regress/sql/reloptions.sql | 21 ++++
src/tools/pgindent/typedefs.list | 2 +
8 files changed, 202 insertions(+), 43 deletions(-)
diff --git a/src/backend/access/common/reloptions.c b/src/backend/access/common/reloptions.c
index 0b83f98ed5f..6f1f577581a 100644
--- a/src/backend/access/common/reloptions.c
+++ b/src/backend/access/common/reloptions.c
@@ -40,9 +40,9 @@
*
* To add an option:
*
- * (i) decide on a type (bool, integer, real, enum, string), name, default
- * value, upper and lower bounds (if applicable); for strings, consider a
- * validation routine.
+ * (i) decide on a type (bool, ternary, integer, real, enum, string), name,
+ * default value, upper and lower bounds (if applicable); for strings,
+ * consider a validation routine.
* (ii) add a record below (or use add_<type>_reloption).
* (iii) add it to the appropriate options struct (perhaps StdRdOptions)
* (iv) add it to the appropriate handling routine (perhaps
@@ -147,15 +147,6 @@ static relopt_bool boolRelOpts[] =
},
false
},
- {
- {
- "vacuum_truncate",
- "Enables vacuum to truncate empty pages at the end of this table",
- RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
- ShareUpdateExclusiveLock
- },
- true
- },
{
{
"deduplicate_items",
@@ -170,6 +161,25 @@ static relopt_bool boolRelOpts[] =
{{NULL}}
};
+static relopt_ternary ternaryRelOpts[] =
+{
+ {
+ {
+ "vacuum_truncate",
+ "Enables vacuum to truncate empty pages at the end of this table",
+ RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
+ ShareUpdateExclusiveLock
+ },
+ TERNARY_UNSET
+ },
+ /* list terminator */
+ {
+ {
+ NULL
+ }
+ }
+};
+
static relopt_int intRelOpts[] =
{
{
@@ -609,6 +619,13 @@ initialize_reloptions(void)
boolRelOpts[i].gen.lockmode));
j++;
}
+ for (i = 0; ternaryRelOpts[i].gen.name; i++)
+ {
+ Assert(DoLockModesConflict(ternaryRelOpts[i].gen.lockmode,
+ ternaryRelOpts[i].gen.lockmode));
+ j++;
+ }
+
for (i = 0; intRelOpts[i].gen.name; i++)
{
Assert(DoLockModesConflict(intRelOpts[i].gen.lockmode,
@@ -649,6 +666,14 @@ initialize_reloptions(void)
j++;
}
+ for (i = 0; ternaryRelOpts[i].gen.name; i++)
+ {
+ relOpts[j] = &ternaryRelOpts[i].gen;
+ relOpts[j]->type = RELOPT_TYPE_TERNARY;
+ relOpts[j]->namelen = strlen(relOpts[j]->name);
+ j++;
+ }
+
for (i = 0; intRelOpts[i].gen.name; i++)
{
relOpts[j] = &intRelOpts[i].gen;
@@ -809,6 +834,9 @@ allocate_reloption(bits32 kinds, int type, const char *name, const char *desc,
case RELOPT_TYPE_BOOL:
size = sizeof(relopt_bool);
break;
+ case RELOPT_TYPE_TERNARY:
+ size = sizeof(relopt_ternary);
+ break;
case RELOPT_TYPE_INT:
size = sizeof(relopt_int);
break;
@@ -892,6 +920,57 @@ add_local_bool_reloption(local_relopts *relopts, const char *name,
add_local_reloption(relopts, (relopt_gen *) newoption, offset);
}
+/*
+ * init_ternary_reloption
+ * Allocate and initialize a new ternary reloption
+ */
+static relopt_ternary *
+init_ternary_reloption(bits32 kinds, const char *name, const char *desc,
+ pg_ternary default_val, LOCKMODE lockmode)
+{
+ relopt_ternary *newoption;
+
+ newoption = (relopt_ternary *)
+ allocate_reloption(kinds, RELOPT_TYPE_TERNARY, name, desc, lockmode);
+ newoption->default_val = default_val;
+
+ return newoption;
+}
+
+/*
+ * add_ternary_reloption
+ * Add a new ternary reloption
+ */
+void
+add_ternary_reloption(bits32 kinds, const char *name, const char *desc,
+ pg_ternary default_val, LOCKMODE lockmode)
+{
+ relopt_ternary *newoption;
+
+ newoption =
+ init_ternary_reloption(kinds, name, desc, default_val, lockmode);
+
+ add_reloption((relopt_gen *) newoption);
+}
+
+/*
+ * add_local_ternary_reloption
+ * Add a new ternary local reloption
+ *
+ * 'offset' is offset of ternary-typed field.
+ */
+void
+add_local_ternary_reloption(local_relopts *relopts, const char *name,
+ const char *desc, pg_ternary default_val,
+ int offset)
+{
+ relopt_ternary *newoption;
+
+ newoption =
+ init_ternary_reloption(RELOPT_KIND_LOCAL, name, desc, default_val, 0);
+
+ add_local_reloption(relopts, (relopt_gen *) newoption, offset);
+}
/*
* init_real_reloption
@@ -1626,6 +1705,19 @@ parse_one_reloption(relopt_value *option, char *text_str, int text_len,
option->gen->name, value)));
}
break;
+ case RELOPT_TYPE_TERNARY:
+ {
+ bool b;
+
+ parsed = parse_bool(value, &b);
+ option->values.ternary_val = b ? TERNARY_TRUE : TERNARY_FALSE;
+ if (validate && !parsed)
+ ereport(ERROR,
+ (errcode(ERRCODE_INVALID_PARAMETER_VALUE),
+ errmsg("invalid value for ternary option \"%s\": %s",
+ option->gen->name, value)));
+ }
+ break;
case RELOPT_TYPE_INT:
{
relopt_int *optint = (relopt_int *) option->gen;
@@ -1789,17 +1881,6 @@ fillRelOptions(void *rdopts, Size basesize,
char *itempos = ((char *) rdopts) + elems[j].offset;
char *string_val;
- /*
- * If isset_offset is provided, store whether the reloption is
- * set there.
- */
- if (elems[j].isset_offset > 0)
- {
- char *setpos = ((char *) rdopts) + elems[j].isset_offset;
-
- *(bool *) setpos = options[i].isset;
- }
-
switch (options[i].gen->type)
{
case RELOPT_TYPE_BOOL:
@@ -1807,6 +1888,11 @@ fillRelOptions(void *rdopts, Size basesize,
options[i].values.bool_val :
((relopt_bool *) options[i].gen)->default_val;
break;
+ case RELOPT_TYPE_TERNARY:
+ *(pg_ternary *) itempos = options[i].isset ?
+ options[i].values.ternary_val :
+ ((relopt_ternary *) options[i].gen)->default_val;
+ break;
case RELOPT_TYPE_INT:
*(int *) itempos = options[i].isset ?
options[i].values.int_val :
@@ -1923,8 +2009,8 @@ default_reloptions(Datum reloptions, bool validate, relopt_kind kind)
offsetof(StdRdOptions, parallel_workers)},
{"vacuum_index_cleanup", RELOPT_TYPE_ENUM,
offsetof(StdRdOptions, vacuum_index_cleanup)},
- {"vacuum_truncate", RELOPT_TYPE_BOOL,
- offsetof(StdRdOptions, vacuum_truncate), offsetof(StdRdOptions, vacuum_truncate_set)},
+ {"vacuum_truncate", RELOPT_TYPE_TERNARY,
+ offsetof(StdRdOptions, vacuum_truncate)},
{"vacuum_max_eager_freeze_failure_rate", RELOPT_TYPE_REAL,
offsetof(StdRdOptions, vacuum_max_eager_freeze_failure_rate)}
};
@@ -2004,7 +2090,6 @@ build_local_reloptions(local_relopts *relopts, Datum options, bool validate)
elems[i].optname = opt->option->name;
elems[i].opttype = opt->option->type;
elems[i].offset = opt->offset;
- elems[i].isset_offset = 0; /* not supported for local relopts yet */
i++;
}
diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c
index aa4fbec143f..696eab9bd97 100644
--- a/src/backend/commands/vacuum.c
+++ b/src/backend/commands/vacuum.c
@@ -2224,9 +2224,9 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams params,
{
StdRdOptions *opts = (StdRdOptions *) rel->rd_options;
- if (opts && opts->vacuum_truncate_set)
+ if (opts && opts->vacuum_truncate != TERNARY_UNSET)
{
- if (opts->vacuum_truncate)
+ if (opts->vacuum_truncate == TERNARY_TRUE)
params.truncate = VACOPTVALUE_ENABLED;
else
params.truncate = VACOPTVALUE_DISABLED;
diff --git a/src/include/access/reloptions.h b/src/include/access/reloptions.h
index 2f08e1b0cf0..dfbef2babf2 100644
--- a/src/include/access/reloptions.h
+++ b/src/include/access/reloptions.h
@@ -29,6 +29,7 @@
typedef enum relopt_type
{
RELOPT_TYPE_BOOL,
+ RELOPT_TYPE_TERNARY, /* on, off, unset */
RELOPT_TYPE_INT,
RELOPT_TYPE_REAL,
RELOPT_TYPE_ENUM,
@@ -80,6 +81,7 @@ typedef struct relopt_value
union
{
bool bool_val;
+ pg_ternary ternary_val;
int int_val;
double real_val;
int enum_val;
@@ -94,6 +96,12 @@ typedef struct relopt_bool
bool default_val;
} relopt_bool;
+typedef struct relopt_ternary
+{
+ relopt_gen gen;
+ int default_val;
+} relopt_ternary;
+
typedef struct relopt_int
{
relopt_gen gen;
@@ -152,19 +160,6 @@ typedef struct
const char *optname; /* option's name */
relopt_type opttype; /* option's datatype */
int offset; /* offset of field in result struct */
-
- /*
- * isset_offset is an optional offset of a field in the result struct that
- * stores whether the option is explicitly set for the relation or if it
- * just picked up the default value. In most cases, this can be
- * accomplished by giving the reloption a special out-of-range default
- * value (e.g., some integer reloptions use -2), but this isn't always
- * possible. For example, a Boolean reloption cannot be given an
- * out-of-range default, so we need another way to discover the source of
- * its value. This offset is only used if given a value greater than
- * zero.
- */
- int isset_offset;
} relopt_parse_elt;
/* Local reloption definition */
@@ -195,6 +190,9 @@ typedef struct local_relopts
extern relopt_kind add_reloption_kind(void);
extern void add_bool_reloption(bits32 kinds, const char *name, const char *desc,
bool default_val, LOCKMODE lockmode);
+extern void add_ternary_reloption(bits32 kinds, const char *name,
+ const char *desc, pg_ternary default_val,
+ LOCKMODE lockmode);
extern void add_int_reloption(bits32 kinds, const char *name, const char *desc,
int default_val, int min_val, int max_val,
LOCKMODE lockmode);
@@ -214,6 +212,9 @@ extern void register_reloptions_validator(local_relopts *relopts,
extern void add_local_bool_reloption(local_relopts *relopts, const char *name,
const char *desc, bool default_val,
int offset);
+extern void add_local_ternary_reloption(local_relopts *relopts,
+ const char *name, const char *desc,
+ pg_ternary default_val, int offset);
extern void add_local_int_reloption(local_relopts *relopts, const char *name,
const char *desc, int default_val,
int min_val, int max_val, int offset);
diff --git a/src/include/postgres.h b/src/include/postgres.h
index 7d93fbce709..47c88780776 100644
--- a/src/include/postgres.h
+++ b/src/include/postgres.h
@@ -543,6 +543,21 @@ Float8GetDatum(float8 X)
* ----------------------------------------------------------------
*/
+/*
+ * pg_ternary
+ * Boolean value with an extra "unset" value
+ *
+ * This enum can be used for values that want to distinguish between true,
+ * false, and unset.
+*/
+
+typedef enum pg_ternary
+{
+ TERNARY_FALSE = 0,
+ TERNARY_TRUE = 1,
+ TERNARY_UNSET = -1
+} pg_ternary;
+
/*
* NON_EXEC_STATIC: It's sometimes useful to define a variable or function
* that is normally static but extern when using EXEC_BACKEND (see
diff --git a/src/include/utils/rel.h b/src/include/utils/rel.h
index d03ab247788..5feb18a5373 100644
--- a/src/include/utils/rel.h
+++ b/src/include/utils/rel.h
@@ -347,8 +347,7 @@ typedef struct StdRdOptions
bool user_catalog_table; /* use as an additional catalog relation */
int parallel_workers; /* max number of parallel workers */
StdRdOptIndexCleanup vacuum_index_cleanup; /* controls index vacuuming */
- bool vacuum_truncate; /* enables vacuum to truncate a relation */
- bool vacuum_truncate_set; /* whether vacuum_truncate is set */
+ pg_ternary vacuum_truncate; /* enables vacuum to truncate a relation */
/*
* Fraction of pages in a relation that vacuum can eagerly scan and fail
diff --git a/src/test/regress/expected/reloptions.out b/src/test/regress/expected/reloptions.out
index 9de19b4e3f1..1c99f79ab01 100644
--- a/src/test/regress/expected/reloptions.out
+++ b/src/test/regress/expected/reloptions.out
@@ -98,6 +98,42 @@ SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass;
{fillfactor=13,autovacuum_enabled=false}
(1 row)
+-- Tests for future (FIXME) ternary options
+-- behave as boolean option: accept unassigned name and truncated value
+DROP TABLE reloptions_test;
+CREATE TABLE reloptions_test(i INT) WITH (vacuum_truncate);
+SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass;
+ reloptions
+------------------------
+ {vacuum_truncate=true}
+(1 row)
+
+DROP TABLE reloptions_test;
+CREATE TABLE reloptions_test(i INT) WITH (vacuum_truncate=FaLS);
+SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass;
+ reloptions
+------------------------
+ {vacuum_truncate=fals}
+(1 row)
+
+-- preferred "true" alias is used when storing
+DROP TABLE reloptions_test;
+CREATE TABLE reloptions_test(i INT) WITH (vacuum_index_cleanup=on);
+SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass;
+ reloptions
+---------------------------
+ {vacuum_index_cleanup=on}
+(1 row)
+
+-- custom "third" value is available
+DROP TABLE reloptions_test;
+CREATE TABLE reloptions_test(i INT) WITH (vacuum_index_cleanup=auto);
+SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass;
+ reloptions
+-----------------------------
+ {vacuum_index_cleanup=auto}
+(1 row)
+
-- Test vacuum_truncate option
DROP TABLE reloptions_test;
CREATE TEMP TABLE reloptions_test(i INT NOT NULL, j text)
diff --git a/src/test/regress/sql/reloptions.sql b/src/test/regress/sql/reloptions.sql
index 24fbe0b478d..f5980dafcbc 100644
--- a/src/test/regress/sql/reloptions.sql
+++ b/src/test/regress/sql/reloptions.sql
@@ -59,6 +59,27 @@ UPDATE pg_class
ALTER TABLE reloptions_test RESET (illegal_option);
SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass;
+-- Tests for future (FIXME) ternary options
+
+-- behave as boolean option: accept unassigned name and truncated value
+DROP TABLE reloptions_test;
+CREATE TABLE reloptions_test(i INT) WITH (vacuum_truncate);
+SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass;
+
+DROP TABLE reloptions_test;
+CREATE TABLE reloptions_test(i INT) WITH (vacuum_truncate=FaLS);
+SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass;
+
+-- preferred "true" alias is used when storing
+DROP TABLE reloptions_test;
+CREATE TABLE reloptions_test(i INT) WITH (vacuum_index_cleanup=on);
+SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass;
+
+-- custom "third" value is available
+DROP TABLE reloptions_test;
+CREATE TABLE reloptions_test(i INT) WITH (vacuum_index_cleanup=auto);
+SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass;
+
-- Test vacuum_truncate option
DROP TABLE reloptions_test;
diff --git a/src/tools/pgindent/typedefs.list b/src/tools/pgindent/typedefs.list
index 3f3a888fd0e..1c8610fd46c 100644
--- a/src/tools/pgindent/typedefs.list
+++ b/src/tools/pgindent/typedefs.list
@@ -3952,6 +3952,7 @@ pg_sha512_ctx
pg_snapshot
pg_special_case
pg_stack_base_t
+pg_ternary
pg_time_t
pg_time_usec_t
pg_tz
@@ -4079,6 +4080,7 @@ relopt_kind
relopt_parse_elt
relopt_real
relopt_string
+relopt_ternary
relopt_type
relopt_value
relopts_validator
--
2.47.3
--c36t3fndxhkzbl5z--
^ permalink raw reply [nested|flat] 868+ messages in thread
* [PATCH v3] Introduce ternary reloptions
@ 2026-01-16 15:04 Álvaro Herrera <[email protected]>
0 siblings, 0 replies; 868+ messages in thread
From: Álvaro Herrera @ 2026-01-16 15:04 UTC (permalink / raw)
Introduce ternary reloption as a replacement for current
`vacuum_truncate` implementation. Remove the `vacuum_truncate_set`
separate flag and use TERNARY_UNSET instead.
This could also be used for other options such as `vacuum_index_cleanup`
and `buffering`, but lets get the scaffolding in first.
Discussion: https://postgr.es/m/3474141.usfYGdeWWP@thinkpad-pgpro
---
src/backend/access/common/reloptions.c | 137 ++++++++++++++++++-----
src/backend/commands/vacuum.c | 4 +-
src/include/access/reloptions.h | 27 ++---
src/include/postgres.h | 15 +++
src/include/utils/rel.h | 3 +-
src/test/regress/expected/reloptions.out | 36 ++++++
src/test/regress/sql/reloptions.sql | 21 ++++
src/tools/pgindent/typedefs.list | 2 +
8 files changed, 202 insertions(+), 43 deletions(-)
diff --git a/src/backend/access/common/reloptions.c b/src/backend/access/common/reloptions.c
index 0b83f98ed5f..6f1f577581a 100644
--- a/src/backend/access/common/reloptions.c
+++ b/src/backend/access/common/reloptions.c
@@ -40,9 +40,9 @@
*
* To add an option:
*
- * (i) decide on a type (bool, integer, real, enum, string), name, default
- * value, upper and lower bounds (if applicable); for strings, consider a
- * validation routine.
+ * (i) decide on a type (bool, ternary, integer, real, enum, string), name,
+ * default value, upper and lower bounds (if applicable); for strings,
+ * consider a validation routine.
* (ii) add a record below (or use add_<type>_reloption).
* (iii) add it to the appropriate options struct (perhaps StdRdOptions)
* (iv) add it to the appropriate handling routine (perhaps
@@ -147,15 +147,6 @@ static relopt_bool boolRelOpts[] =
},
false
},
- {
- {
- "vacuum_truncate",
- "Enables vacuum to truncate empty pages at the end of this table",
- RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
- ShareUpdateExclusiveLock
- },
- true
- },
{
{
"deduplicate_items",
@@ -170,6 +161,25 @@ static relopt_bool boolRelOpts[] =
{{NULL}}
};
+static relopt_ternary ternaryRelOpts[] =
+{
+ {
+ {
+ "vacuum_truncate",
+ "Enables vacuum to truncate empty pages at the end of this table",
+ RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
+ ShareUpdateExclusiveLock
+ },
+ TERNARY_UNSET
+ },
+ /* list terminator */
+ {
+ {
+ NULL
+ }
+ }
+};
+
static relopt_int intRelOpts[] =
{
{
@@ -609,6 +619,13 @@ initialize_reloptions(void)
boolRelOpts[i].gen.lockmode));
j++;
}
+ for (i = 0; ternaryRelOpts[i].gen.name; i++)
+ {
+ Assert(DoLockModesConflict(ternaryRelOpts[i].gen.lockmode,
+ ternaryRelOpts[i].gen.lockmode));
+ j++;
+ }
+
for (i = 0; intRelOpts[i].gen.name; i++)
{
Assert(DoLockModesConflict(intRelOpts[i].gen.lockmode,
@@ -649,6 +666,14 @@ initialize_reloptions(void)
j++;
}
+ for (i = 0; ternaryRelOpts[i].gen.name; i++)
+ {
+ relOpts[j] = &ternaryRelOpts[i].gen;
+ relOpts[j]->type = RELOPT_TYPE_TERNARY;
+ relOpts[j]->namelen = strlen(relOpts[j]->name);
+ j++;
+ }
+
for (i = 0; intRelOpts[i].gen.name; i++)
{
relOpts[j] = &intRelOpts[i].gen;
@@ -809,6 +834,9 @@ allocate_reloption(bits32 kinds, int type, const char *name, const char *desc,
case RELOPT_TYPE_BOOL:
size = sizeof(relopt_bool);
break;
+ case RELOPT_TYPE_TERNARY:
+ size = sizeof(relopt_ternary);
+ break;
case RELOPT_TYPE_INT:
size = sizeof(relopt_int);
break;
@@ -892,6 +920,57 @@ add_local_bool_reloption(local_relopts *relopts, const char *name,
add_local_reloption(relopts, (relopt_gen *) newoption, offset);
}
+/*
+ * init_ternary_reloption
+ * Allocate and initialize a new ternary reloption
+ */
+static relopt_ternary *
+init_ternary_reloption(bits32 kinds, const char *name, const char *desc,
+ pg_ternary default_val, LOCKMODE lockmode)
+{
+ relopt_ternary *newoption;
+
+ newoption = (relopt_ternary *)
+ allocate_reloption(kinds, RELOPT_TYPE_TERNARY, name, desc, lockmode);
+ newoption->default_val = default_val;
+
+ return newoption;
+}
+
+/*
+ * add_ternary_reloption
+ * Add a new ternary reloption
+ */
+void
+add_ternary_reloption(bits32 kinds, const char *name, const char *desc,
+ pg_ternary default_val, LOCKMODE lockmode)
+{
+ relopt_ternary *newoption;
+
+ newoption =
+ init_ternary_reloption(kinds, name, desc, default_val, lockmode);
+
+ add_reloption((relopt_gen *) newoption);
+}
+
+/*
+ * add_local_ternary_reloption
+ * Add a new ternary local reloption
+ *
+ * 'offset' is offset of ternary-typed field.
+ */
+void
+add_local_ternary_reloption(local_relopts *relopts, const char *name,
+ const char *desc, pg_ternary default_val,
+ int offset)
+{
+ relopt_ternary *newoption;
+
+ newoption =
+ init_ternary_reloption(RELOPT_KIND_LOCAL, name, desc, default_val, 0);
+
+ add_local_reloption(relopts, (relopt_gen *) newoption, offset);
+}
/*
* init_real_reloption
@@ -1626,6 +1705,19 @@ parse_one_reloption(relopt_value *option, char *text_str, int text_len,
option->gen->name, value)));
}
break;
+ case RELOPT_TYPE_TERNARY:
+ {
+ bool b;
+
+ parsed = parse_bool(value, &b);
+ option->values.ternary_val = b ? TERNARY_TRUE : TERNARY_FALSE;
+ if (validate && !parsed)
+ ereport(ERROR,
+ (errcode(ERRCODE_INVALID_PARAMETER_VALUE),
+ errmsg("invalid value for ternary option \"%s\": %s",
+ option->gen->name, value)));
+ }
+ break;
case RELOPT_TYPE_INT:
{
relopt_int *optint = (relopt_int *) option->gen;
@@ -1789,17 +1881,6 @@ fillRelOptions(void *rdopts, Size basesize,
char *itempos = ((char *) rdopts) + elems[j].offset;
char *string_val;
- /*
- * If isset_offset is provided, store whether the reloption is
- * set there.
- */
- if (elems[j].isset_offset > 0)
- {
- char *setpos = ((char *) rdopts) + elems[j].isset_offset;
-
- *(bool *) setpos = options[i].isset;
- }
-
switch (options[i].gen->type)
{
case RELOPT_TYPE_BOOL:
@@ -1807,6 +1888,11 @@ fillRelOptions(void *rdopts, Size basesize,
options[i].values.bool_val :
((relopt_bool *) options[i].gen)->default_val;
break;
+ case RELOPT_TYPE_TERNARY:
+ *(pg_ternary *) itempos = options[i].isset ?
+ options[i].values.ternary_val :
+ ((relopt_ternary *) options[i].gen)->default_val;
+ break;
case RELOPT_TYPE_INT:
*(int *) itempos = options[i].isset ?
options[i].values.int_val :
@@ -1923,8 +2009,8 @@ default_reloptions(Datum reloptions, bool validate, relopt_kind kind)
offsetof(StdRdOptions, parallel_workers)},
{"vacuum_index_cleanup", RELOPT_TYPE_ENUM,
offsetof(StdRdOptions, vacuum_index_cleanup)},
- {"vacuum_truncate", RELOPT_TYPE_BOOL,
- offsetof(StdRdOptions, vacuum_truncate), offsetof(StdRdOptions, vacuum_truncate_set)},
+ {"vacuum_truncate", RELOPT_TYPE_TERNARY,
+ offsetof(StdRdOptions, vacuum_truncate)},
{"vacuum_max_eager_freeze_failure_rate", RELOPT_TYPE_REAL,
offsetof(StdRdOptions, vacuum_max_eager_freeze_failure_rate)}
};
@@ -2004,7 +2090,6 @@ build_local_reloptions(local_relopts *relopts, Datum options, bool validate)
elems[i].optname = opt->option->name;
elems[i].opttype = opt->option->type;
elems[i].offset = opt->offset;
- elems[i].isset_offset = 0; /* not supported for local relopts yet */
i++;
}
diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c
index aa4fbec143f..696eab9bd97 100644
--- a/src/backend/commands/vacuum.c
+++ b/src/backend/commands/vacuum.c
@@ -2224,9 +2224,9 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams params,
{
StdRdOptions *opts = (StdRdOptions *) rel->rd_options;
- if (opts && opts->vacuum_truncate_set)
+ if (opts && opts->vacuum_truncate != TERNARY_UNSET)
{
- if (opts->vacuum_truncate)
+ if (opts->vacuum_truncate == TERNARY_TRUE)
params.truncate = VACOPTVALUE_ENABLED;
else
params.truncate = VACOPTVALUE_DISABLED;
diff --git a/src/include/access/reloptions.h b/src/include/access/reloptions.h
index 2f08e1b0cf0..dfbef2babf2 100644
--- a/src/include/access/reloptions.h
+++ b/src/include/access/reloptions.h
@@ -29,6 +29,7 @@
typedef enum relopt_type
{
RELOPT_TYPE_BOOL,
+ RELOPT_TYPE_TERNARY, /* on, off, unset */
RELOPT_TYPE_INT,
RELOPT_TYPE_REAL,
RELOPT_TYPE_ENUM,
@@ -80,6 +81,7 @@ typedef struct relopt_value
union
{
bool bool_val;
+ pg_ternary ternary_val;
int int_val;
double real_val;
int enum_val;
@@ -94,6 +96,12 @@ typedef struct relopt_bool
bool default_val;
} relopt_bool;
+typedef struct relopt_ternary
+{
+ relopt_gen gen;
+ int default_val;
+} relopt_ternary;
+
typedef struct relopt_int
{
relopt_gen gen;
@@ -152,19 +160,6 @@ typedef struct
const char *optname; /* option's name */
relopt_type opttype; /* option's datatype */
int offset; /* offset of field in result struct */
-
- /*
- * isset_offset is an optional offset of a field in the result struct that
- * stores whether the option is explicitly set for the relation or if it
- * just picked up the default value. In most cases, this can be
- * accomplished by giving the reloption a special out-of-range default
- * value (e.g., some integer reloptions use -2), but this isn't always
- * possible. For example, a Boolean reloption cannot be given an
- * out-of-range default, so we need another way to discover the source of
- * its value. This offset is only used if given a value greater than
- * zero.
- */
- int isset_offset;
} relopt_parse_elt;
/* Local reloption definition */
@@ -195,6 +190,9 @@ typedef struct local_relopts
extern relopt_kind add_reloption_kind(void);
extern void add_bool_reloption(bits32 kinds, const char *name, const char *desc,
bool default_val, LOCKMODE lockmode);
+extern void add_ternary_reloption(bits32 kinds, const char *name,
+ const char *desc, pg_ternary default_val,
+ LOCKMODE lockmode);
extern void add_int_reloption(bits32 kinds, const char *name, const char *desc,
int default_val, int min_val, int max_val,
LOCKMODE lockmode);
@@ -214,6 +212,9 @@ extern void register_reloptions_validator(local_relopts *relopts,
extern void add_local_bool_reloption(local_relopts *relopts, const char *name,
const char *desc, bool default_val,
int offset);
+extern void add_local_ternary_reloption(local_relopts *relopts,
+ const char *name, const char *desc,
+ pg_ternary default_val, int offset);
extern void add_local_int_reloption(local_relopts *relopts, const char *name,
const char *desc, int default_val,
int min_val, int max_val, int offset);
diff --git a/src/include/postgres.h b/src/include/postgres.h
index 7d93fbce709..47c88780776 100644
--- a/src/include/postgres.h
+++ b/src/include/postgres.h
@@ -543,6 +543,21 @@ Float8GetDatum(float8 X)
* ----------------------------------------------------------------
*/
+/*
+ * pg_ternary
+ * Boolean value with an extra "unset" value
+ *
+ * This enum can be used for values that want to distinguish between true,
+ * false, and unset.
+*/
+
+typedef enum pg_ternary
+{
+ TERNARY_FALSE = 0,
+ TERNARY_TRUE = 1,
+ TERNARY_UNSET = -1
+} pg_ternary;
+
/*
* NON_EXEC_STATIC: It's sometimes useful to define a variable or function
* that is normally static but extern when using EXEC_BACKEND (see
diff --git a/src/include/utils/rel.h b/src/include/utils/rel.h
index d03ab247788..5feb18a5373 100644
--- a/src/include/utils/rel.h
+++ b/src/include/utils/rel.h
@@ -347,8 +347,7 @@ typedef struct StdRdOptions
bool user_catalog_table; /* use as an additional catalog relation */
int parallel_workers; /* max number of parallel workers */
StdRdOptIndexCleanup vacuum_index_cleanup; /* controls index vacuuming */
- bool vacuum_truncate; /* enables vacuum to truncate a relation */
- bool vacuum_truncate_set; /* whether vacuum_truncate is set */
+ pg_ternary vacuum_truncate; /* enables vacuum to truncate a relation */
/*
* Fraction of pages in a relation that vacuum can eagerly scan and fail
diff --git a/src/test/regress/expected/reloptions.out b/src/test/regress/expected/reloptions.out
index 9de19b4e3f1..1c99f79ab01 100644
--- a/src/test/regress/expected/reloptions.out
+++ b/src/test/regress/expected/reloptions.out
@@ -98,6 +98,42 @@ SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass;
{fillfactor=13,autovacuum_enabled=false}
(1 row)
+-- Tests for future (FIXME) ternary options
+-- behave as boolean option: accept unassigned name and truncated value
+DROP TABLE reloptions_test;
+CREATE TABLE reloptions_test(i INT) WITH (vacuum_truncate);
+SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass;
+ reloptions
+------------------------
+ {vacuum_truncate=true}
+(1 row)
+
+DROP TABLE reloptions_test;
+CREATE TABLE reloptions_test(i INT) WITH (vacuum_truncate=FaLS);
+SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass;
+ reloptions
+------------------------
+ {vacuum_truncate=fals}
+(1 row)
+
+-- preferred "true" alias is used when storing
+DROP TABLE reloptions_test;
+CREATE TABLE reloptions_test(i INT) WITH (vacuum_index_cleanup=on);
+SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass;
+ reloptions
+---------------------------
+ {vacuum_index_cleanup=on}
+(1 row)
+
+-- custom "third" value is available
+DROP TABLE reloptions_test;
+CREATE TABLE reloptions_test(i INT) WITH (vacuum_index_cleanup=auto);
+SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass;
+ reloptions
+-----------------------------
+ {vacuum_index_cleanup=auto}
+(1 row)
+
-- Test vacuum_truncate option
DROP TABLE reloptions_test;
CREATE TEMP TABLE reloptions_test(i INT NOT NULL, j text)
diff --git a/src/test/regress/sql/reloptions.sql b/src/test/regress/sql/reloptions.sql
index 24fbe0b478d..f5980dafcbc 100644
--- a/src/test/regress/sql/reloptions.sql
+++ b/src/test/regress/sql/reloptions.sql
@@ -59,6 +59,27 @@ UPDATE pg_class
ALTER TABLE reloptions_test RESET (illegal_option);
SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass;
+-- Tests for future (FIXME) ternary options
+
+-- behave as boolean option: accept unassigned name and truncated value
+DROP TABLE reloptions_test;
+CREATE TABLE reloptions_test(i INT) WITH (vacuum_truncate);
+SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass;
+
+DROP TABLE reloptions_test;
+CREATE TABLE reloptions_test(i INT) WITH (vacuum_truncate=FaLS);
+SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass;
+
+-- preferred "true" alias is used when storing
+DROP TABLE reloptions_test;
+CREATE TABLE reloptions_test(i INT) WITH (vacuum_index_cleanup=on);
+SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass;
+
+-- custom "third" value is available
+DROP TABLE reloptions_test;
+CREATE TABLE reloptions_test(i INT) WITH (vacuum_index_cleanup=auto);
+SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass;
+
-- Test vacuum_truncate option
DROP TABLE reloptions_test;
diff --git a/src/tools/pgindent/typedefs.list b/src/tools/pgindent/typedefs.list
index 3f3a888fd0e..1c8610fd46c 100644
--- a/src/tools/pgindent/typedefs.list
+++ b/src/tools/pgindent/typedefs.list
@@ -3952,6 +3952,7 @@ pg_sha512_ctx
pg_snapshot
pg_special_case
pg_stack_base_t
+pg_ternary
pg_time_t
pg_time_usec_t
pg_tz
@@ -4079,6 +4080,7 @@ relopt_kind
relopt_parse_elt
relopt_real
relopt_string
+relopt_ternary
relopt_type
relopt_value
relopts_validator
--
2.47.3
--c36t3fndxhkzbl5z--
^ permalink raw reply [nested|flat] 868+ messages in thread
* [PATCH v3] Introduce ternary reloptions
@ 2026-01-16 15:04 Álvaro Herrera <[email protected]>
0 siblings, 0 replies; 868+ messages in thread
From: Álvaro Herrera @ 2026-01-16 15:04 UTC (permalink / raw)
Introduce ternary reloption as a replacement for current
`vacuum_truncate` implementation. Remove the `vacuum_truncate_set`
separate flag and use TERNARY_UNSET instead.
This could also be used for other options such as `vacuum_index_cleanup`
and `buffering`, but lets get the scaffolding in first.
Discussion: https://postgr.es/m/3474141.usfYGdeWWP@thinkpad-pgpro
---
src/backend/access/common/reloptions.c | 137 ++++++++++++++++++-----
src/backend/commands/vacuum.c | 4 +-
src/include/access/reloptions.h | 27 ++---
src/include/postgres.h | 15 +++
src/include/utils/rel.h | 3 +-
src/test/regress/expected/reloptions.out | 36 ++++++
src/test/regress/sql/reloptions.sql | 21 ++++
src/tools/pgindent/typedefs.list | 2 +
8 files changed, 202 insertions(+), 43 deletions(-)
diff --git a/src/backend/access/common/reloptions.c b/src/backend/access/common/reloptions.c
index 0b83f98ed5f..6f1f577581a 100644
--- a/src/backend/access/common/reloptions.c
+++ b/src/backend/access/common/reloptions.c
@@ -40,9 +40,9 @@
*
* To add an option:
*
- * (i) decide on a type (bool, integer, real, enum, string), name, default
- * value, upper and lower bounds (if applicable); for strings, consider a
- * validation routine.
+ * (i) decide on a type (bool, ternary, integer, real, enum, string), name,
+ * default value, upper and lower bounds (if applicable); for strings,
+ * consider a validation routine.
* (ii) add a record below (or use add_<type>_reloption).
* (iii) add it to the appropriate options struct (perhaps StdRdOptions)
* (iv) add it to the appropriate handling routine (perhaps
@@ -147,15 +147,6 @@ static relopt_bool boolRelOpts[] =
},
false
},
- {
- {
- "vacuum_truncate",
- "Enables vacuum to truncate empty pages at the end of this table",
- RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
- ShareUpdateExclusiveLock
- },
- true
- },
{
{
"deduplicate_items",
@@ -170,6 +161,25 @@ static relopt_bool boolRelOpts[] =
{{NULL}}
};
+static relopt_ternary ternaryRelOpts[] =
+{
+ {
+ {
+ "vacuum_truncate",
+ "Enables vacuum to truncate empty pages at the end of this table",
+ RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
+ ShareUpdateExclusiveLock
+ },
+ TERNARY_UNSET
+ },
+ /* list terminator */
+ {
+ {
+ NULL
+ }
+ }
+};
+
static relopt_int intRelOpts[] =
{
{
@@ -609,6 +619,13 @@ initialize_reloptions(void)
boolRelOpts[i].gen.lockmode));
j++;
}
+ for (i = 0; ternaryRelOpts[i].gen.name; i++)
+ {
+ Assert(DoLockModesConflict(ternaryRelOpts[i].gen.lockmode,
+ ternaryRelOpts[i].gen.lockmode));
+ j++;
+ }
+
for (i = 0; intRelOpts[i].gen.name; i++)
{
Assert(DoLockModesConflict(intRelOpts[i].gen.lockmode,
@@ -649,6 +666,14 @@ initialize_reloptions(void)
j++;
}
+ for (i = 0; ternaryRelOpts[i].gen.name; i++)
+ {
+ relOpts[j] = &ternaryRelOpts[i].gen;
+ relOpts[j]->type = RELOPT_TYPE_TERNARY;
+ relOpts[j]->namelen = strlen(relOpts[j]->name);
+ j++;
+ }
+
for (i = 0; intRelOpts[i].gen.name; i++)
{
relOpts[j] = &intRelOpts[i].gen;
@@ -809,6 +834,9 @@ allocate_reloption(bits32 kinds, int type, const char *name, const char *desc,
case RELOPT_TYPE_BOOL:
size = sizeof(relopt_bool);
break;
+ case RELOPT_TYPE_TERNARY:
+ size = sizeof(relopt_ternary);
+ break;
case RELOPT_TYPE_INT:
size = sizeof(relopt_int);
break;
@@ -892,6 +920,57 @@ add_local_bool_reloption(local_relopts *relopts, const char *name,
add_local_reloption(relopts, (relopt_gen *) newoption, offset);
}
+/*
+ * init_ternary_reloption
+ * Allocate and initialize a new ternary reloption
+ */
+static relopt_ternary *
+init_ternary_reloption(bits32 kinds, const char *name, const char *desc,
+ pg_ternary default_val, LOCKMODE lockmode)
+{
+ relopt_ternary *newoption;
+
+ newoption = (relopt_ternary *)
+ allocate_reloption(kinds, RELOPT_TYPE_TERNARY, name, desc, lockmode);
+ newoption->default_val = default_val;
+
+ return newoption;
+}
+
+/*
+ * add_ternary_reloption
+ * Add a new ternary reloption
+ */
+void
+add_ternary_reloption(bits32 kinds, const char *name, const char *desc,
+ pg_ternary default_val, LOCKMODE lockmode)
+{
+ relopt_ternary *newoption;
+
+ newoption =
+ init_ternary_reloption(kinds, name, desc, default_val, lockmode);
+
+ add_reloption((relopt_gen *) newoption);
+}
+
+/*
+ * add_local_ternary_reloption
+ * Add a new ternary local reloption
+ *
+ * 'offset' is offset of ternary-typed field.
+ */
+void
+add_local_ternary_reloption(local_relopts *relopts, const char *name,
+ const char *desc, pg_ternary default_val,
+ int offset)
+{
+ relopt_ternary *newoption;
+
+ newoption =
+ init_ternary_reloption(RELOPT_KIND_LOCAL, name, desc, default_val, 0);
+
+ add_local_reloption(relopts, (relopt_gen *) newoption, offset);
+}
/*
* init_real_reloption
@@ -1626,6 +1705,19 @@ parse_one_reloption(relopt_value *option, char *text_str, int text_len,
option->gen->name, value)));
}
break;
+ case RELOPT_TYPE_TERNARY:
+ {
+ bool b;
+
+ parsed = parse_bool(value, &b);
+ option->values.ternary_val = b ? TERNARY_TRUE : TERNARY_FALSE;
+ if (validate && !parsed)
+ ereport(ERROR,
+ (errcode(ERRCODE_INVALID_PARAMETER_VALUE),
+ errmsg("invalid value for ternary option \"%s\": %s",
+ option->gen->name, value)));
+ }
+ break;
case RELOPT_TYPE_INT:
{
relopt_int *optint = (relopt_int *) option->gen;
@@ -1789,17 +1881,6 @@ fillRelOptions(void *rdopts, Size basesize,
char *itempos = ((char *) rdopts) + elems[j].offset;
char *string_val;
- /*
- * If isset_offset is provided, store whether the reloption is
- * set there.
- */
- if (elems[j].isset_offset > 0)
- {
- char *setpos = ((char *) rdopts) + elems[j].isset_offset;
-
- *(bool *) setpos = options[i].isset;
- }
-
switch (options[i].gen->type)
{
case RELOPT_TYPE_BOOL:
@@ -1807,6 +1888,11 @@ fillRelOptions(void *rdopts, Size basesize,
options[i].values.bool_val :
((relopt_bool *) options[i].gen)->default_val;
break;
+ case RELOPT_TYPE_TERNARY:
+ *(pg_ternary *) itempos = options[i].isset ?
+ options[i].values.ternary_val :
+ ((relopt_ternary *) options[i].gen)->default_val;
+ break;
case RELOPT_TYPE_INT:
*(int *) itempos = options[i].isset ?
options[i].values.int_val :
@@ -1923,8 +2009,8 @@ default_reloptions(Datum reloptions, bool validate, relopt_kind kind)
offsetof(StdRdOptions, parallel_workers)},
{"vacuum_index_cleanup", RELOPT_TYPE_ENUM,
offsetof(StdRdOptions, vacuum_index_cleanup)},
- {"vacuum_truncate", RELOPT_TYPE_BOOL,
- offsetof(StdRdOptions, vacuum_truncate), offsetof(StdRdOptions, vacuum_truncate_set)},
+ {"vacuum_truncate", RELOPT_TYPE_TERNARY,
+ offsetof(StdRdOptions, vacuum_truncate)},
{"vacuum_max_eager_freeze_failure_rate", RELOPT_TYPE_REAL,
offsetof(StdRdOptions, vacuum_max_eager_freeze_failure_rate)}
};
@@ -2004,7 +2090,6 @@ build_local_reloptions(local_relopts *relopts, Datum options, bool validate)
elems[i].optname = opt->option->name;
elems[i].opttype = opt->option->type;
elems[i].offset = opt->offset;
- elems[i].isset_offset = 0; /* not supported for local relopts yet */
i++;
}
diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c
index aa4fbec143f..696eab9bd97 100644
--- a/src/backend/commands/vacuum.c
+++ b/src/backend/commands/vacuum.c
@@ -2224,9 +2224,9 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams params,
{
StdRdOptions *opts = (StdRdOptions *) rel->rd_options;
- if (opts && opts->vacuum_truncate_set)
+ if (opts && opts->vacuum_truncate != TERNARY_UNSET)
{
- if (opts->vacuum_truncate)
+ if (opts->vacuum_truncate == TERNARY_TRUE)
params.truncate = VACOPTVALUE_ENABLED;
else
params.truncate = VACOPTVALUE_DISABLED;
diff --git a/src/include/access/reloptions.h b/src/include/access/reloptions.h
index 2f08e1b0cf0..dfbef2babf2 100644
--- a/src/include/access/reloptions.h
+++ b/src/include/access/reloptions.h
@@ -29,6 +29,7 @@
typedef enum relopt_type
{
RELOPT_TYPE_BOOL,
+ RELOPT_TYPE_TERNARY, /* on, off, unset */
RELOPT_TYPE_INT,
RELOPT_TYPE_REAL,
RELOPT_TYPE_ENUM,
@@ -80,6 +81,7 @@ typedef struct relopt_value
union
{
bool bool_val;
+ pg_ternary ternary_val;
int int_val;
double real_val;
int enum_val;
@@ -94,6 +96,12 @@ typedef struct relopt_bool
bool default_val;
} relopt_bool;
+typedef struct relopt_ternary
+{
+ relopt_gen gen;
+ int default_val;
+} relopt_ternary;
+
typedef struct relopt_int
{
relopt_gen gen;
@@ -152,19 +160,6 @@ typedef struct
const char *optname; /* option's name */
relopt_type opttype; /* option's datatype */
int offset; /* offset of field in result struct */
-
- /*
- * isset_offset is an optional offset of a field in the result struct that
- * stores whether the option is explicitly set for the relation or if it
- * just picked up the default value. In most cases, this can be
- * accomplished by giving the reloption a special out-of-range default
- * value (e.g., some integer reloptions use -2), but this isn't always
- * possible. For example, a Boolean reloption cannot be given an
- * out-of-range default, so we need another way to discover the source of
- * its value. This offset is only used if given a value greater than
- * zero.
- */
- int isset_offset;
} relopt_parse_elt;
/* Local reloption definition */
@@ -195,6 +190,9 @@ typedef struct local_relopts
extern relopt_kind add_reloption_kind(void);
extern void add_bool_reloption(bits32 kinds, const char *name, const char *desc,
bool default_val, LOCKMODE lockmode);
+extern void add_ternary_reloption(bits32 kinds, const char *name,
+ const char *desc, pg_ternary default_val,
+ LOCKMODE lockmode);
extern void add_int_reloption(bits32 kinds, const char *name, const char *desc,
int default_val, int min_val, int max_val,
LOCKMODE lockmode);
@@ -214,6 +212,9 @@ extern void register_reloptions_validator(local_relopts *relopts,
extern void add_local_bool_reloption(local_relopts *relopts, const char *name,
const char *desc, bool default_val,
int offset);
+extern void add_local_ternary_reloption(local_relopts *relopts,
+ const char *name, const char *desc,
+ pg_ternary default_val, int offset);
extern void add_local_int_reloption(local_relopts *relopts, const char *name,
const char *desc, int default_val,
int min_val, int max_val, int offset);
diff --git a/src/include/postgres.h b/src/include/postgres.h
index 7d93fbce709..47c88780776 100644
--- a/src/include/postgres.h
+++ b/src/include/postgres.h
@@ -543,6 +543,21 @@ Float8GetDatum(float8 X)
* ----------------------------------------------------------------
*/
+/*
+ * pg_ternary
+ * Boolean value with an extra "unset" value
+ *
+ * This enum can be used for values that want to distinguish between true,
+ * false, and unset.
+*/
+
+typedef enum pg_ternary
+{
+ TERNARY_FALSE = 0,
+ TERNARY_TRUE = 1,
+ TERNARY_UNSET = -1
+} pg_ternary;
+
/*
* NON_EXEC_STATIC: It's sometimes useful to define a variable or function
* that is normally static but extern when using EXEC_BACKEND (see
diff --git a/src/include/utils/rel.h b/src/include/utils/rel.h
index d03ab247788..5feb18a5373 100644
--- a/src/include/utils/rel.h
+++ b/src/include/utils/rel.h
@@ -347,8 +347,7 @@ typedef struct StdRdOptions
bool user_catalog_table; /* use as an additional catalog relation */
int parallel_workers; /* max number of parallel workers */
StdRdOptIndexCleanup vacuum_index_cleanup; /* controls index vacuuming */
- bool vacuum_truncate; /* enables vacuum to truncate a relation */
- bool vacuum_truncate_set; /* whether vacuum_truncate is set */
+ pg_ternary vacuum_truncate; /* enables vacuum to truncate a relation */
/*
* Fraction of pages in a relation that vacuum can eagerly scan and fail
diff --git a/src/test/regress/expected/reloptions.out b/src/test/regress/expected/reloptions.out
index 9de19b4e3f1..1c99f79ab01 100644
--- a/src/test/regress/expected/reloptions.out
+++ b/src/test/regress/expected/reloptions.out
@@ -98,6 +98,42 @@ SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass;
{fillfactor=13,autovacuum_enabled=false}
(1 row)
+-- Tests for future (FIXME) ternary options
+-- behave as boolean option: accept unassigned name and truncated value
+DROP TABLE reloptions_test;
+CREATE TABLE reloptions_test(i INT) WITH (vacuum_truncate);
+SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass;
+ reloptions
+------------------------
+ {vacuum_truncate=true}
+(1 row)
+
+DROP TABLE reloptions_test;
+CREATE TABLE reloptions_test(i INT) WITH (vacuum_truncate=FaLS);
+SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass;
+ reloptions
+------------------------
+ {vacuum_truncate=fals}
+(1 row)
+
+-- preferred "true" alias is used when storing
+DROP TABLE reloptions_test;
+CREATE TABLE reloptions_test(i INT) WITH (vacuum_index_cleanup=on);
+SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass;
+ reloptions
+---------------------------
+ {vacuum_index_cleanup=on}
+(1 row)
+
+-- custom "third" value is available
+DROP TABLE reloptions_test;
+CREATE TABLE reloptions_test(i INT) WITH (vacuum_index_cleanup=auto);
+SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass;
+ reloptions
+-----------------------------
+ {vacuum_index_cleanup=auto}
+(1 row)
+
-- Test vacuum_truncate option
DROP TABLE reloptions_test;
CREATE TEMP TABLE reloptions_test(i INT NOT NULL, j text)
diff --git a/src/test/regress/sql/reloptions.sql b/src/test/regress/sql/reloptions.sql
index 24fbe0b478d..f5980dafcbc 100644
--- a/src/test/regress/sql/reloptions.sql
+++ b/src/test/regress/sql/reloptions.sql
@@ -59,6 +59,27 @@ UPDATE pg_class
ALTER TABLE reloptions_test RESET (illegal_option);
SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass;
+-- Tests for future (FIXME) ternary options
+
+-- behave as boolean option: accept unassigned name and truncated value
+DROP TABLE reloptions_test;
+CREATE TABLE reloptions_test(i INT) WITH (vacuum_truncate);
+SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass;
+
+DROP TABLE reloptions_test;
+CREATE TABLE reloptions_test(i INT) WITH (vacuum_truncate=FaLS);
+SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass;
+
+-- preferred "true" alias is used when storing
+DROP TABLE reloptions_test;
+CREATE TABLE reloptions_test(i INT) WITH (vacuum_index_cleanup=on);
+SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass;
+
+-- custom "third" value is available
+DROP TABLE reloptions_test;
+CREATE TABLE reloptions_test(i INT) WITH (vacuum_index_cleanup=auto);
+SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass;
+
-- Test vacuum_truncate option
DROP TABLE reloptions_test;
diff --git a/src/tools/pgindent/typedefs.list b/src/tools/pgindent/typedefs.list
index 3f3a888fd0e..1c8610fd46c 100644
--- a/src/tools/pgindent/typedefs.list
+++ b/src/tools/pgindent/typedefs.list
@@ -3952,6 +3952,7 @@ pg_sha512_ctx
pg_snapshot
pg_special_case
pg_stack_base_t
+pg_ternary
pg_time_t
pg_time_usec_t
pg_tz
@@ -4079,6 +4080,7 @@ relopt_kind
relopt_parse_elt
relopt_real
relopt_string
+relopt_ternary
relopt_type
relopt_value
relopts_validator
--
2.47.3
--c36t3fndxhkzbl5z--
^ permalink raw reply [nested|flat] 868+ messages in thread
* [PATCH v3] Introduce ternary reloptions
@ 2026-01-16 15:04 Álvaro Herrera <[email protected]>
0 siblings, 0 replies; 868+ messages in thread
From: Álvaro Herrera @ 2026-01-16 15:04 UTC (permalink / raw)
Introduce ternary reloption as a replacement for current
`vacuum_truncate` implementation. Remove the `vacuum_truncate_set`
separate flag and use TERNARY_UNSET instead.
This could also be used for other options such as `vacuum_index_cleanup`
and `buffering`, but lets get the scaffolding in first.
Discussion: https://postgr.es/m/3474141.usfYGdeWWP@thinkpad-pgpro
---
src/backend/access/common/reloptions.c | 137 ++++++++++++++++++-----
src/backend/commands/vacuum.c | 4 +-
src/include/access/reloptions.h | 27 ++---
src/include/postgres.h | 15 +++
src/include/utils/rel.h | 3 +-
src/test/regress/expected/reloptions.out | 36 ++++++
src/test/regress/sql/reloptions.sql | 21 ++++
src/tools/pgindent/typedefs.list | 2 +
8 files changed, 202 insertions(+), 43 deletions(-)
diff --git a/src/backend/access/common/reloptions.c b/src/backend/access/common/reloptions.c
index 0b83f98ed5f..6f1f577581a 100644
--- a/src/backend/access/common/reloptions.c
+++ b/src/backend/access/common/reloptions.c
@@ -40,9 +40,9 @@
*
* To add an option:
*
- * (i) decide on a type (bool, integer, real, enum, string), name, default
- * value, upper and lower bounds (if applicable); for strings, consider a
- * validation routine.
+ * (i) decide on a type (bool, ternary, integer, real, enum, string), name,
+ * default value, upper and lower bounds (if applicable); for strings,
+ * consider a validation routine.
* (ii) add a record below (or use add_<type>_reloption).
* (iii) add it to the appropriate options struct (perhaps StdRdOptions)
* (iv) add it to the appropriate handling routine (perhaps
@@ -147,15 +147,6 @@ static relopt_bool boolRelOpts[] =
},
false
},
- {
- {
- "vacuum_truncate",
- "Enables vacuum to truncate empty pages at the end of this table",
- RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
- ShareUpdateExclusiveLock
- },
- true
- },
{
{
"deduplicate_items",
@@ -170,6 +161,25 @@ static relopt_bool boolRelOpts[] =
{{NULL}}
};
+static relopt_ternary ternaryRelOpts[] =
+{
+ {
+ {
+ "vacuum_truncate",
+ "Enables vacuum to truncate empty pages at the end of this table",
+ RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
+ ShareUpdateExclusiveLock
+ },
+ TERNARY_UNSET
+ },
+ /* list terminator */
+ {
+ {
+ NULL
+ }
+ }
+};
+
static relopt_int intRelOpts[] =
{
{
@@ -609,6 +619,13 @@ initialize_reloptions(void)
boolRelOpts[i].gen.lockmode));
j++;
}
+ for (i = 0; ternaryRelOpts[i].gen.name; i++)
+ {
+ Assert(DoLockModesConflict(ternaryRelOpts[i].gen.lockmode,
+ ternaryRelOpts[i].gen.lockmode));
+ j++;
+ }
+
for (i = 0; intRelOpts[i].gen.name; i++)
{
Assert(DoLockModesConflict(intRelOpts[i].gen.lockmode,
@@ -649,6 +666,14 @@ initialize_reloptions(void)
j++;
}
+ for (i = 0; ternaryRelOpts[i].gen.name; i++)
+ {
+ relOpts[j] = &ternaryRelOpts[i].gen;
+ relOpts[j]->type = RELOPT_TYPE_TERNARY;
+ relOpts[j]->namelen = strlen(relOpts[j]->name);
+ j++;
+ }
+
for (i = 0; intRelOpts[i].gen.name; i++)
{
relOpts[j] = &intRelOpts[i].gen;
@@ -809,6 +834,9 @@ allocate_reloption(bits32 kinds, int type, const char *name, const char *desc,
case RELOPT_TYPE_BOOL:
size = sizeof(relopt_bool);
break;
+ case RELOPT_TYPE_TERNARY:
+ size = sizeof(relopt_ternary);
+ break;
case RELOPT_TYPE_INT:
size = sizeof(relopt_int);
break;
@@ -892,6 +920,57 @@ add_local_bool_reloption(local_relopts *relopts, const char *name,
add_local_reloption(relopts, (relopt_gen *) newoption, offset);
}
+/*
+ * init_ternary_reloption
+ * Allocate and initialize a new ternary reloption
+ */
+static relopt_ternary *
+init_ternary_reloption(bits32 kinds, const char *name, const char *desc,
+ pg_ternary default_val, LOCKMODE lockmode)
+{
+ relopt_ternary *newoption;
+
+ newoption = (relopt_ternary *)
+ allocate_reloption(kinds, RELOPT_TYPE_TERNARY, name, desc, lockmode);
+ newoption->default_val = default_val;
+
+ return newoption;
+}
+
+/*
+ * add_ternary_reloption
+ * Add a new ternary reloption
+ */
+void
+add_ternary_reloption(bits32 kinds, const char *name, const char *desc,
+ pg_ternary default_val, LOCKMODE lockmode)
+{
+ relopt_ternary *newoption;
+
+ newoption =
+ init_ternary_reloption(kinds, name, desc, default_val, lockmode);
+
+ add_reloption((relopt_gen *) newoption);
+}
+
+/*
+ * add_local_ternary_reloption
+ * Add a new ternary local reloption
+ *
+ * 'offset' is offset of ternary-typed field.
+ */
+void
+add_local_ternary_reloption(local_relopts *relopts, const char *name,
+ const char *desc, pg_ternary default_val,
+ int offset)
+{
+ relopt_ternary *newoption;
+
+ newoption =
+ init_ternary_reloption(RELOPT_KIND_LOCAL, name, desc, default_val, 0);
+
+ add_local_reloption(relopts, (relopt_gen *) newoption, offset);
+}
/*
* init_real_reloption
@@ -1626,6 +1705,19 @@ parse_one_reloption(relopt_value *option, char *text_str, int text_len,
option->gen->name, value)));
}
break;
+ case RELOPT_TYPE_TERNARY:
+ {
+ bool b;
+
+ parsed = parse_bool(value, &b);
+ option->values.ternary_val = b ? TERNARY_TRUE : TERNARY_FALSE;
+ if (validate && !parsed)
+ ereport(ERROR,
+ (errcode(ERRCODE_INVALID_PARAMETER_VALUE),
+ errmsg("invalid value for ternary option \"%s\": %s",
+ option->gen->name, value)));
+ }
+ break;
case RELOPT_TYPE_INT:
{
relopt_int *optint = (relopt_int *) option->gen;
@@ -1789,17 +1881,6 @@ fillRelOptions(void *rdopts, Size basesize,
char *itempos = ((char *) rdopts) + elems[j].offset;
char *string_val;
- /*
- * If isset_offset is provided, store whether the reloption is
- * set there.
- */
- if (elems[j].isset_offset > 0)
- {
- char *setpos = ((char *) rdopts) + elems[j].isset_offset;
-
- *(bool *) setpos = options[i].isset;
- }
-
switch (options[i].gen->type)
{
case RELOPT_TYPE_BOOL:
@@ -1807,6 +1888,11 @@ fillRelOptions(void *rdopts, Size basesize,
options[i].values.bool_val :
((relopt_bool *) options[i].gen)->default_val;
break;
+ case RELOPT_TYPE_TERNARY:
+ *(pg_ternary *) itempos = options[i].isset ?
+ options[i].values.ternary_val :
+ ((relopt_ternary *) options[i].gen)->default_val;
+ break;
case RELOPT_TYPE_INT:
*(int *) itempos = options[i].isset ?
options[i].values.int_val :
@@ -1923,8 +2009,8 @@ default_reloptions(Datum reloptions, bool validate, relopt_kind kind)
offsetof(StdRdOptions, parallel_workers)},
{"vacuum_index_cleanup", RELOPT_TYPE_ENUM,
offsetof(StdRdOptions, vacuum_index_cleanup)},
- {"vacuum_truncate", RELOPT_TYPE_BOOL,
- offsetof(StdRdOptions, vacuum_truncate), offsetof(StdRdOptions, vacuum_truncate_set)},
+ {"vacuum_truncate", RELOPT_TYPE_TERNARY,
+ offsetof(StdRdOptions, vacuum_truncate)},
{"vacuum_max_eager_freeze_failure_rate", RELOPT_TYPE_REAL,
offsetof(StdRdOptions, vacuum_max_eager_freeze_failure_rate)}
};
@@ -2004,7 +2090,6 @@ build_local_reloptions(local_relopts *relopts, Datum options, bool validate)
elems[i].optname = opt->option->name;
elems[i].opttype = opt->option->type;
elems[i].offset = opt->offset;
- elems[i].isset_offset = 0; /* not supported for local relopts yet */
i++;
}
diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c
index aa4fbec143f..696eab9bd97 100644
--- a/src/backend/commands/vacuum.c
+++ b/src/backend/commands/vacuum.c
@@ -2224,9 +2224,9 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams params,
{
StdRdOptions *opts = (StdRdOptions *) rel->rd_options;
- if (opts && opts->vacuum_truncate_set)
+ if (opts && opts->vacuum_truncate != TERNARY_UNSET)
{
- if (opts->vacuum_truncate)
+ if (opts->vacuum_truncate == TERNARY_TRUE)
params.truncate = VACOPTVALUE_ENABLED;
else
params.truncate = VACOPTVALUE_DISABLED;
diff --git a/src/include/access/reloptions.h b/src/include/access/reloptions.h
index 2f08e1b0cf0..dfbef2babf2 100644
--- a/src/include/access/reloptions.h
+++ b/src/include/access/reloptions.h
@@ -29,6 +29,7 @@
typedef enum relopt_type
{
RELOPT_TYPE_BOOL,
+ RELOPT_TYPE_TERNARY, /* on, off, unset */
RELOPT_TYPE_INT,
RELOPT_TYPE_REAL,
RELOPT_TYPE_ENUM,
@@ -80,6 +81,7 @@ typedef struct relopt_value
union
{
bool bool_val;
+ pg_ternary ternary_val;
int int_val;
double real_val;
int enum_val;
@@ -94,6 +96,12 @@ typedef struct relopt_bool
bool default_val;
} relopt_bool;
+typedef struct relopt_ternary
+{
+ relopt_gen gen;
+ int default_val;
+} relopt_ternary;
+
typedef struct relopt_int
{
relopt_gen gen;
@@ -152,19 +160,6 @@ typedef struct
const char *optname; /* option's name */
relopt_type opttype; /* option's datatype */
int offset; /* offset of field in result struct */
-
- /*
- * isset_offset is an optional offset of a field in the result struct that
- * stores whether the option is explicitly set for the relation or if it
- * just picked up the default value. In most cases, this can be
- * accomplished by giving the reloption a special out-of-range default
- * value (e.g., some integer reloptions use -2), but this isn't always
- * possible. For example, a Boolean reloption cannot be given an
- * out-of-range default, so we need another way to discover the source of
- * its value. This offset is only used if given a value greater than
- * zero.
- */
- int isset_offset;
} relopt_parse_elt;
/* Local reloption definition */
@@ -195,6 +190,9 @@ typedef struct local_relopts
extern relopt_kind add_reloption_kind(void);
extern void add_bool_reloption(bits32 kinds, const char *name, const char *desc,
bool default_val, LOCKMODE lockmode);
+extern void add_ternary_reloption(bits32 kinds, const char *name,
+ const char *desc, pg_ternary default_val,
+ LOCKMODE lockmode);
extern void add_int_reloption(bits32 kinds, const char *name, const char *desc,
int default_val, int min_val, int max_val,
LOCKMODE lockmode);
@@ -214,6 +212,9 @@ extern void register_reloptions_validator(local_relopts *relopts,
extern void add_local_bool_reloption(local_relopts *relopts, const char *name,
const char *desc, bool default_val,
int offset);
+extern void add_local_ternary_reloption(local_relopts *relopts,
+ const char *name, const char *desc,
+ pg_ternary default_val, int offset);
extern void add_local_int_reloption(local_relopts *relopts, const char *name,
const char *desc, int default_val,
int min_val, int max_val, int offset);
diff --git a/src/include/postgres.h b/src/include/postgres.h
index 7d93fbce709..47c88780776 100644
--- a/src/include/postgres.h
+++ b/src/include/postgres.h
@@ -543,6 +543,21 @@ Float8GetDatum(float8 X)
* ----------------------------------------------------------------
*/
+/*
+ * pg_ternary
+ * Boolean value with an extra "unset" value
+ *
+ * This enum can be used for values that want to distinguish between true,
+ * false, and unset.
+*/
+
+typedef enum pg_ternary
+{
+ TERNARY_FALSE = 0,
+ TERNARY_TRUE = 1,
+ TERNARY_UNSET = -1
+} pg_ternary;
+
/*
* NON_EXEC_STATIC: It's sometimes useful to define a variable or function
* that is normally static but extern when using EXEC_BACKEND (see
diff --git a/src/include/utils/rel.h b/src/include/utils/rel.h
index d03ab247788..5feb18a5373 100644
--- a/src/include/utils/rel.h
+++ b/src/include/utils/rel.h
@@ -347,8 +347,7 @@ typedef struct StdRdOptions
bool user_catalog_table; /* use as an additional catalog relation */
int parallel_workers; /* max number of parallel workers */
StdRdOptIndexCleanup vacuum_index_cleanup; /* controls index vacuuming */
- bool vacuum_truncate; /* enables vacuum to truncate a relation */
- bool vacuum_truncate_set; /* whether vacuum_truncate is set */
+ pg_ternary vacuum_truncate; /* enables vacuum to truncate a relation */
/*
* Fraction of pages in a relation that vacuum can eagerly scan and fail
diff --git a/src/test/regress/expected/reloptions.out b/src/test/regress/expected/reloptions.out
index 9de19b4e3f1..1c99f79ab01 100644
--- a/src/test/regress/expected/reloptions.out
+++ b/src/test/regress/expected/reloptions.out
@@ -98,6 +98,42 @@ SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass;
{fillfactor=13,autovacuum_enabled=false}
(1 row)
+-- Tests for future (FIXME) ternary options
+-- behave as boolean option: accept unassigned name and truncated value
+DROP TABLE reloptions_test;
+CREATE TABLE reloptions_test(i INT) WITH (vacuum_truncate);
+SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass;
+ reloptions
+------------------------
+ {vacuum_truncate=true}
+(1 row)
+
+DROP TABLE reloptions_test;
+CREATE TABLE reloptions_test(i INT) WITH (vacuum_truncate=FaLS);
+SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass;
+ reloptions
+------------------------
+ {vacuum_truncate=fals}
+(1 row)
+
+-- preferred "true" alias is used when storing
+DROP TABLE reloptions_test;
+CREATE TABLE reloptions_test(i INT) WITH (vacuum_index_cleanup=on);
+SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass;
+ reloptions
+---------------------------
+ {vacuum_index_cleanup=on}
+(1 row)
+
+-- custom "third" value is available
+DROP TABLE reloptions_test;
+CREATE TABLE reloptions_test(i INT) WITH (vacuum_index_cleanup=auto);
+SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass;
+ reloptions
+-----------------------------
+ {vacuum_index_cleanup=auto}
+(1 row)
+
-- Test vacuum_truncate option
DROP TABLE reloptions_test;
CREATE TEMP TABLE reloptions_test(i INT NOT NULL, j text)
diff --git a/src/test/regress/sql/reloptions.sql b/src/test/regress/sql/reloptions.sql
index 24fbe0b478d..f5980dafcbc 100644
--- a/src/test/regress/sql/reloptions.sql
+++ b/src/test/regress/sql/reloptions.sql
@@ -59,6 +59,27 @@ UPDATE pg_class
ALTER TABLE reloptions_test RESET (illegal_option);
SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass;
+-- Tests for future (FIXME) ternary options
+
+-- behave as boolean option: accept unassigned name and truncated value
+DROP TABLE reloptions_test;
+CREATE TABLE reloptions_test(i INT) WITH (vacuum_truncate);
+SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass;
+
+DROP TABLE reloptions_test;
+CREATE TABLE reloptions_test(i INT) WITH (vacuum_truncate=FaLS);
+SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass;
+
+-- preferred "true" alias is used when storing
+DROP TABLE reloptions_test;
+CREATE TABLE reloptions_test(i INT) WITH (vacuum_index_cleanup=on);
+SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass;
+
+-- custom "third" value is available
+DROP TABLE reloptions_test;
+CREATE TABLE reloptions_test(i INT) WITH (vacuum_index_cleanup=auto);
+SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass;
+
-- Test vacuum_truncate option
DROP TABLE reloptions_test;
diff --git a/src/tools/pgindent/typedefs.list b/src/tools/pgindent/typedefs.list
index 3f3a888fd0e..1c8610fd46c 100644
--- a/src/tools/pgindent/typedefs.list
+++ b/src/tools/pgindent/typedefs.list
@@ -3952,6 +3952,7 @@ pg_sha512_ctx
pg_snapshot
pg_special_case
pg_stack_base_t
+pg_ternary
pg_time_t
pg_time_usec_t
pg_tz
@@ -4079,6 +4080,7 @@ relopt_kind
relopt_parse_elt
relopt_real
relopt_string
+relopt_ternary
relopt_type
relopt_value
relopts_validator
--
2.47.3
--c36t3fndxhkzbl5z--
^ permalink raw reply [nested|flat] 868+ messages in thread
* [PATCH v3] Introduce ternary reloptions
@ 2026-01-16 15:04 Álvaro Herrera <[email protected]>
0 siblings, 0 replies; 868+ messages in thread
From: Álvaro Herrera @ 2026-01-16 15:04 UTC (permalink / raw)
Introduce ternary reloption as a replacement for current
`vacuum_truncate` implementation. Remove the `vacuum_truncate_set`
separate flag and use TERNARY_UNSET instead.
This could also be used for other options such as `vacuum_index_cleanup`
and `buffering`, but lets get the scaffolding in first.
Discussion: https://postgr.es/m/3474141.usfYGdeWWP@thinkpad-pgpro
---
src/backend/access/common/reloptions.c | 137 ++++++++++++++++++-----
src/backend/commands/vacuum.c | 4 +-
src/include/access/reloptions.h | 27 ++---
src/include/postgres.h | 15 +++
src/include/utils/rel.h | 3 +-
src/test/regress/expected/reloptions.out | 36 ++++++
src/test/regress/sql/reloptions.sql | 21 ++++
src/tools/pgindent/typedefs.list | 2 +
8 files changed, 202 insertions(+), 43 deletions(-)
diff --git a/src/backend/access/common/reloptions.c b/src/backend/access/common/reloptions.c
index 0b83f98ed5f..6f1f577581a 100644
--- a/src/backend/access/common/reloptions.c
+++ b/src/backend/access/common/reloptions.c
@@ -40,9 +40,9 @@
*
* To add an option:
*
- * (i) decide on a type (bool, integer, real, enum, string), name, default
- * value, upper and lower bounds (if applicable); for strings, consider a
- * validation routine.
+ * (i) decide on a type (bool, ternary, integer, real, enum, string), name,
+ * default value, upper and lower bounds (if applicable); for strings,
+ * consider a validation routine.
* (ii) add a record below (or use add_<type>_reloption).
* (iii) add it to the appropriate options struct (perhaps StdRdOptions)
* (iv) add it to the appropriate handling routine (perhaps
@@ -147,15 +147,6 @@ static relopt_bool boolRelOpts[] =
},
false
},
- {
- {
- "vacuum_truncate",
- "Enables vacuum to truncate empty pages at the end of this table",
- RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
- ShareUpdateExclusiveLock
- },
- true
- },
{
{
"deduplicate_items",
@@ -170,6 +161,25 @@ static relopt_bool boolRelOpts[] =
{{NULL}}
};
+static relopt_ternary ternaryRelOpts[] =
+{
+ {
+ {
+ "vacuum_truncate",
+ "Enables vacuum to truncate empty pages at the end of this table",
+ RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
+ ShareUpdateExclusiveLock
+ },
+ TERNARY_UNSET
+ },
+ /* list terminator */
+ {
+ {
+ NULL
+ }
+ }
+};
+
static relopt_int intRelOpts[] =
{
{
@@ -609,6 +619,13 @@ initialize_reloptions(void)
boolRelOpts[i].gen.lockmode));
j++;
}
+ for (i = 0; ternaryRelOpts[i].gen.name; i++)
+ {
+ Assert(DoLockModesConflict(ternaryRelOpts[i].gen.lockmode,
+ ternaryRelOpts[i].gen.lockmode));
+ j++;
+ }
+
for (i = 0; intRelOpts[i].gen.name; i++)
{
Assert(DoLockModesConflict(intRelOpts[i].gen.lockmode,
@@ -649,6 +666,14 @@ initialize_reloptions(void)
j++;
}
+ for (i = 0; ternaryRelOpts[i].gen.name; i++)
+ {
+ relOpts[j] = &ternaryRelOpts[i].gen;
+ relOpts[j]->type = RELOPT_TYPE_TERNARY;
+ relOpts[j]->namelen = strlen(relOpts[j]->name);
+ j++;
+ }
+
for (i = 0; intRelOpts[i].gen.name; i++)
{
relOpts[j] = &intRelOpts[i].gen;
@@ -809,6 +834,9 @@ allocate_reloption(bits32 kinds, int type, const char *name, const char *desc,
case RELOPT_TYPE_BOOL:
size = sizeof(relopt_bool);
break;
+ case RELOPT_TYPE_TERNARY:
+ size = sizeof(relopt_ternary);
+ break;
case RELOPT_TYPE_INT:
size = sizeof(relopt_int);
break;
@@ -892,6 +920,57 @@ add_local_bool_reloption(local_relopts *relopts, const char *name,
add_local_reloption(relopts, (relopt_gen *) newoption, offset);
}
+/*
+ * init_ternary_reloption
+ * Allocate and initialize a new ternary reloption
+ */
+static relopt_ternary *
+init_ternary_reloption(bits32 kinds, const char *name, const char *desc,
+ pg_ternary default_val, LOCKMODE lockmode)
+{
+ relopt_ternary *newoption;
+
+ newoption = (relopt_ternary *)
+ allocate_reloption(kinds, RELOPT_TYPE_TERNARY, name, desc, lockmode);
+ newoption->default_val = default_val;
+
+ return newoption;
+}
+
+/*
+ * add_ternary_reloption
+ * Add a new ternary reloption
+ */
+void
+add_ternary_reloption(bits32 kinds, const char *name, const char *desc,
+ pg_ternary default_val, LOCKMODE lockmode)
+{
+ relopt_ternary *newoption;
+
+ newoption =
+ init_ternary_reloption(kinds, name, desc, default_val, lockmode);
+
+ add_reloption((relopt_gen *) newoption);
+}
+
+/*
+ * add_local_ternary_reloption
+ * Add a new ternary local reloption
+ *
+ * 'offset' is offset of ternary-typed field.
+ */
+void
+add_local_ternary_reloption(local_relopts *relopts, const char *name,
+ const char *desc, pg_ternary default_val,
+ int offset)
+{
+ relopt_ternary *newoption;
+
+ newoption =
+ init_ternary_reloption(RELOPT_KIND_LOCAL, name, desc, default_val, 0);
+
+ add_local_reloption(relopts, (relopt_gen *) newoption, offset);
+}
/*
* init_real_reloption
@@ -1626,6 +1705,19 @@ parse_one_reloption(relopt_value *option, char *text_str, int text_len,
option->gen->name, value)));
}
break;
+ case RELOPT_TYPE_TERNARY:
+ {
+ bool b;
+
+ parsed = parse_bool(value, &b);
+ option->values.ternary_val = b ? TERNARY_TRUE : TERNARY_FALSE;
+ if (validate && !parsed)
+ ereport(ERROR,
+ (errcode(ERRCODE_INVALID_PARAMETER_VALUE),
+ errmsg("invalid value for ternary option \"%s\": %s",
+ option->gen->name, value)));
+ }
+ break;
case RELOPT_TYPE_INT:
{
relopt_int *optint = (relopt_int *) option->gen;
@@ -1789,17 +1881,6 @@ fillRelOptions(void *rdopts, Size basesize,
char *itempos = ((char *) rdopts) + elems[j].offset;
char *string_val;
- /*
- * If isset_offset is provided, store whether the reloption is
- * set there.
- */
- if (elems[j].isset_offset > 0)
- {
- char *setpos = ((char *) rdopts) + elems[j].isset_offset;
-
- *(bool *) setpos = options[i].isset;
- }
-
switch (options[i].gen->type)
{
case RELOPT_TYPE_BOOL:
@@ -1807,6 +1888,11 @@ fillRelOptions(void *rdopts, Size basesize,
options[i].values.bool_val :
((relopt_bool *) options[i].gen)->default_val;
break;
+ case RELOPT_TYPE_TERNARY:
+ *(pg_ternary *) itempos = options[i].isset ?
+ options[i].values.ternary_val :
+ ((relopt_ternary *) options[i].gen)->default_val;
+ break;
case RELOPT_TYPE_INT:
*(int *) itempos = options[i].isset ?
options[i].values.int_val :
@@ -1923,8 +2009,8 @@ default_reloptions(Datum reloptions, bool validate, relopt_kind kind)
offsetof(StdRdOptions, parallel_workers)},
{"vacuum_index_cleanup", RELOPT_TYPE_ENUM,
offsetof(StdRdOptions, vacuum_index_cleanup)},
- {"vacuum_truncate", RELOPT_TYPE_BOOL,
- offsetof(StdRdOptions, vacuum_truncate), offsetof(StdRdOptions, vacuum_truncate_set)},
+ {"vacuum_truncate", RELOPT_TYPE_TERNARY,
+ offsetof(StdRdOptions, vacuum_truncate)},
{"vacuum_max_eager_freeze_failure_rate", RELOPT_TYPE_REAL,
offsetof(StdRdOptions, vacuum_max_eager_freeze_failure_rate)}
};
@@ -2004,7 +2090,6 @@ build_local_reloptions(local_relopts *relopts, Datum options, bool validate)
elems[i].optname = opt->option->name;
elems[i].opttype = opt->option->type;
elems[i].offset = opt->offset;
- elems[i].isset_offset = 0; /* not supported for local relopts yet */
i++;
}
diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c
index aa4fbec143f..696eab9bd97 100644
--- a/src/backend/commands/vacuum.c
+++ b/src/backend/commands/vacuum.c
@@ -2224,9 +2224,9 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams params,
{
StdRdOptions *opts = (StdRdOptions *) rel->rd_options;
- if (opts && opts->vacuum_truncate_set)
+ if (opts && opts->vacuum_truncate != TERNARY_UNSET)
{
- if (opts->vacuum_truncate)
+ if (opts->vacuum_truncate == TERNARY_TRUE)
params.truncate = VACOPTVALUE_ENABLED;
else
params.truncate = VACOPTVALUE_DISABLED;
diff --git a/src/include/access/reloptions.h b/src/include/access/reloptions.h
index 2f08e1b0cf0..dfbef2babf2 100644
--- a/src/include/access/reloptions.h
+++ b/src/include/access/reloptions.h
@@ -29,6 +29,7 @@
typedef enum relopt_type
{
RELOPT_TYPE_BOOL,
+ RELOPT_TYPE_TERNARY, /* on, off, unset */
RELOPT_TYPE_INT,
RELOPT_TYPE_REAL,
RELOPT_TYPE_ENUM,
@@ -80,6 +81,7 @@ typedef struct relopt_value
union
{
bool bool_val;
+ pg_ternary ternary_val;
int int_val;
double real_val;
int enum_val;
@@ -94,6 +96,12 @@ typedef struct relopt_bool
bool default_val;
} relopt_bool;
+typedef struct relopt_ternary
+{
+ relopt_gen gen;
+ int default_val;
+} relopt_ternary;
+
typedef struct relopt_int
{
relopt_gen gen;
@@ -152,19 +160,6 @@ typedef struct
const char *optname; /* option's name */
relopt_type opttype; /* option's datatype */
int offset; /* offset of field in result struct */
-
- /*
- * isset_offset is an optional offset of a field in the result struct that
- * stores whether the option is explicitly set for the relation or if it
- * just picked up the default value. In most cases, this can be
- * accomplished by giving the reloption a special out-of-range default
- * value (e.g., some integer reloptions use -2), but this isn't always
- * possible. For example, a Boolean reloption cannot be given an
- * out-of-range default, so we need another way to discover the source of
- * its value. This offset is only used if given a value greater than
- * zero.
- */
- int isset_offset;
} relopt_parse_elt;
/* Local reloption definition */
@@ -195,6 +190,9 @@ typedef struct local_relopts
extern relopt_kind add_reloption_kind(void);
extern void add_bool_reloption(bits32 kinds, const char *name, const char *desc,
bool default_val, LOCKMODE lockmode);
+extern void add_ternary_reloption(bits32 kinds, const char *name,
+ const char *desc, pg_ternary default_val,
+ LOCKMODE lockmode);
extern void add_int_reloption(bits32 kinds, const char *name, const char *desc,
int default_val, int min_val, int max_val,
LOCKMODE lockmode);
@@ -214,6 +212,9 @@ extern void register_reloptions_validator(local_relopts *relopts,
extern void add_local_bool_reloption(local_relopts *relopts, const char *name,
const char *desc, bool default_val,
int offset);
+extern void add_local_ternary_reloption(local_relopts *relopts,
+ const char *name, const char *desc,
+ pg_ternary default_val, int offset);
extern void add_local_int_reloption(local_relopts *relopts, const char *name,
const char *desc, int default_val,
int min_val, int max_val, int offset);
diff --git a/src/include/postgres.h b/src/include/postgres.h
index 7d93fbce709..47c88780776 100644
--- a/src/include/postgres.h
+++ b/src/include/postgres.h
@@ -543,6 +543,21 @@ Float8GetDatum(float8 X)
* ----------------------------------------------------------------
*/
+/*
+ * pg_ternary
+ * Boolean value with an extra "unset" value
+ *
+ * This enum can be used for values that want to distinguish between true,
+ * false, and unset.
+*/
+
+typedef enum pg_ternary
+{
+ TERNARY_FALSE = 0,
+ TERNARY_TRUE = 1,
+ TERNARY_UNSET = -1
+} pg_ternary;
+
/*
* NON_EXEC_STATIC: It's sometimes useful to define a variable or function
* that is normally static but extern when using EXEC_BACKEND (see
diff --git a/src/include/utils/rel.h b/src/include/utils/rel.h
index d03ab247788..5feb18a5373 100644
--- a/src/include/utils/rel.h
+++ b/src/include/utils/rel.h
@@ -347,8 +347,7 @@ typedef struct StdRdOptions
bool user_catalog_table; /* use as an additional catalog relation */
int parallel_workers; /* max number of parallel workers */
StdRdOptIndexCleanup vacuum_index_cleanup; /* controls index vacuuming */
- bool vacuum_truncate; /* enables vacuum to truncate a relation */
- bool vacuum_truncate_set; /* whether vacuum_truncate is set */
+ pg_ternary vacuum_truncate; /* enables vacuum to truncate a relation */
/*
* Fraction of pages in a relation that vacuum can eagerly scan and fail
diff --git a/src/test/regress/expected/reloptions.out b/src/test/regress/expected/reloptions.out
index 9de19b4e3f1..1c99f79ab01 100644
--- a/src/test/regress/expected/reloptions.out
+++ b/src/test/regress/expected/reloptions.out
@@ -98,6 +98,42 @@ SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass;
{fillfactor=13,autovacuum_enabled=false}
(1 row)
+-- Tests for future (FIXME) ternary options
+-- behave as boolean option: accept unassigned name and truncated value
+DROP TABLE reloptions_test;
+CREATE TABLE reloptions_test(i INT) WITH (vacuum_truncate);
+SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass;
+ reloptions
+------------------------
+ {vacuum_truncate=true}
+(1 row)
+
+DROP TABLE reloptions_test;
+CREATE TABLE reloptions_test(i INT) WITH (vacuum_truncate=FaLS);
+SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass;
+ reloptions
+------------------------
+ {vacuum_truncate=fals}
+(1 row)
+
+-- preferred "true" alias is used when storing
+DROP TABLE reloptions_test;
+CREATE TABLE reloptions_test(i INT) WITH (vacuum_index_cleanup=on);
+SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass;
+ reloptions
+---------------------------
+ {vacuum_index_cleanup=on}
+(1 row)
+
+-- custom "third" value is available
+DROP TABLE reloptions_test;
+CREATE TABLE reloptions_test(i INT) WITH (vacuum_index_cleanup=auto);
+SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass;
+ reloptions
+-----------------------------
+ {vacuum_index_cleanup=auto}
+(1 row)
+
-- Test vacuum_truncate option
DROP TABLE reloptions_test;
CREATE TEMP TABLE reloptions_test(i INT NOT NULL, j text)
diff --git a/src/test/regress/sql/reloptions.sql b/src/test/regress/sql/reloptions.sql
index 24fbe0b478d..f5980dafcbc 100644
--- a/src/test/regress/sql/reloptions.sql
+++ b/src/test/regress/sql/reloptions.sql
@@ -59,6 +59,27 @@ UPDATE pg_class
ALTER TABLE reloptions_test RESET (illegal_option);
SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass;
+-- Tests for future (FIXME) ternary options
+
+-- behave as boolean option: accept unassigned name and truncated value
+DROP TABLE reloptions_test;
+CREATE TABLE reloptions_test(i INT) WITH (vacuum_truncate);
+SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass;
+
+DROP TABLE reloptions_test;
+CREATE TABLE reloptions_test(i INT) WITH (vacuum_truncate=FaLS);
+SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass;
+
+-- preferred "true" alias is used when storing
+DROP TABLE reloptions_test;
+CREATE TABLE reloptions_test(i INT) WITH (vacuum_index_cleanup=on);
+SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass;
+
+-- custom "third" value is available
+DROP TABLE reloptions_test;
+CREATE TABLE reloptions_test(i INT) WITH (vacuum_index_cleanup=auto);
+SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass;
+
-- Test vacuum_truncate option
DROP TABLE reloptions_test;
diff --git a/src/tools/pgindent/typedefs.list b/src/tools/pgindent/typedefs.list
index 3f3a888fd0e..1c8610fd46c 100644
--- a/src/tools/pgindent/typedefs.list
+++ b/src/tools/pgindent/typedefs.list
@@ -3952,6 +3952,7 @@ pg_sha512_ctx
pg_snapshot
pg_special_case
pg_stack_base_t
+pg_ternary
pg_time_t
pg_time_usec_t
pg_tz
@@ -4079,6 +4080,7 @@ relopt_kind
relopt_parse_elt
relopt_real
relopt_string
+relopt_ternary
relopt_type
relopt_value
relopts_validator
--
2.47.3
--c36t3fndxhkzbl5z--
^ permalink raw reply [nested|flat] 868+ messages in thread
* [PATCH v3] Introduce ternary reloptions
@ 2026-01-16 15:04 Álvaro Herrera <[email protected]>
0 siblings, 0 replies; 868+ messages in thread
From: Álvaro Herrera @ 2026-01-16 15:04 UTC (permalink / raw)
Introduce ternary reloption as a replacement for current
`vacuum_truncate` implementation. Remove the `vacuum_truncate_set`
separate flag and use TERNARY_UNSET instead.
This could also be used for other options such as `vacuum_index_cleanup`
and `buffering`, but lets get the scaffolding in first.
Discussion: https://postgr.es/m/3474141.usfYGdeWWP@thinkpad-pgpro
---
src/backend/access/common/reloptions.c | 137 ++++++++++++++++++-----
src/backend/commands/vacuum.c | 4 +-
src/include/access/reloptions.h | 27 ++---
src/include/postgres.h | 15 +++
src/include/utils/rel.h | 3 +-
src/test/regress/expected/reloptions.out | 36 ++++++
src/test/regress/sql/reloptions.sql | 21 ++++
src/tools/pgindent/typedefs.list | 2 +
8 files changed, 202 insertions(+), 43 deletions(-)
diff --git a/src/backend/access/common/reloptions.c b/src/backend/access/common/reloptions.c
index 0b83f98ed5f..6f1f577581a 100644
--- a/src/backend/access/common/reloptions.c
+++ b/src/backend/access/common/reloptions.c
@@ -40,9 +40,9 @@
*
* To add an option:
*
- * (i) decide on a type (bool, integer, real, enum, string), name, default
- * value, upper and lower bounds (if applicable); for strings, consider a
- * validation routine.
+ * (i) decide on a type (bool, ternary, integer, real, enum, string), name,
+ * default value, upper and lower bounds (if applicable); for strings,
+ * consider a validation routine.
* (ii) add a record below (or use add_<type>_reloption).
* (iii) add it to the appropriate options struct (perhaps StdRdOptions)
* (iv) add it to the appropriate handling routine (perhaps
@@ -147,15 +147,6 @@ static relopt_bool boolRelOpts[] =
},
false
},
- {
- {
- "vacuum_truncate",
- "Enables vacuum to truncate empty pages at the end of this table",
- RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
- ShareUpdateExclusiveLock
- },
- true
- },
{
{
"deduplicate_items",
@@ -170,6 +161,25 @@ static relopt_bool boolRelOpts[] =
{{NULL}}
};
+static relopt_ternary ternaryRelOpts[] =
+{
+ {
+ {
+ "vacuum_truncate",
+ "Enables vacuum to truncate empty pages at the end of this table",
+ RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
+ ShareUpdateExclusiveLock
+ },
+ TERNARY_UNSET
+ },
+ /* list terminator */
+ {
+ {
+ NULL
+ }
+ }
+};
+
static relopt_int intRelOpts[] =
{
{
@@ -609,6 +619,13 @@ initialize_reloptions(void)
boolRelOpts[i].gen.lockmode));
j++;
}
+ for (i = 0; ternaryRelOpts[i].gen.name; i++)
+ {
+ Assert(DoLockModesConflict(ternaryRelOpts[i].gen.lockmode,
+ ternaryRelOpts[i].gen.lockmode));
+ j++;
+ }
+
for (i = 0; intRelOpts[i].gen.name; i++)
{
Assert(DoLockModesConflict(intRelOpts[i].gen.lockmode,
@@ -649,6 +666,14 @@ initialize_reloptions(void)
j++;
}
+ for (i = 0; ternaryRelOpts[i].gen.name; i++)
+ {
+ relOpts[j] = &ternaryRelOpts[i].gen;
+ relOpts[j]->type = RELOPT_TYPE_TERNARY;
+ relOpts[j]->namelen = strlen(relOpts[j]->name);
+ j++;
+ }
+
for (i = 0; intRelOpts[i].gen.name; i++)
{
relOpts[j] = &intRelOpts[i].gen;
@@ -809,6 +834,9 @@ allocate_reloption(bits32 kinds, int type, const char *name, const char *desc,
case RELOPT_TYPE_BOOL:
size = sizeof(relopt_bool);
break;
+ case RELOPT_TYPE_TERNARY:
+ size = sizeof(relopt_ternary);
+ break;
case RELOPT_TYPE_INT:
size = sizeof(relopt_int);
break;
@@ -892,6 +920,57 @@ add_local_bool_reloption(local_relopts *relopts, const char *name,
add_local_reloption(relopts, (relopt_gen *) newoption, offset);
}
+/*
+ * init_ternary_reloption
+ * Allocate and initialize a new ternary reloption
+ */
+static relopt_ternary *
+init_ternary_reloption(bits32 kinds, const char *name, const char *desc,
+ pg_ternary default_val, LOCKMODE lockmode)
+{
+ relopt_ternary *newoption;
+
+ newoption = (relopt_ternary *)
+ allocate_reloption(kinds, RELOPT_TYPE_TERNARY, name, desc, lockmode);
+ newoption->default_val = default_val;
+
+ return newoption;
+}
+
+/*
+ * add_ternary_reloption
+ * Add a new ternary reloption
+ */
+void
+add_ternary_reloption(bits32 kinds, const char *name, const char *desc,
+ pg_ternary default_val, LOCKMODE lockmode)
+{
+ relopt_ternary *newoption;
+
+ newoption =
+ init_ternary_reloption(kinds, name, desc, default_val, lockmode);
+
+ add_reloption((relopt_gen *) newoption);
+}
+
+/*
+ * add_local_ternary_reloption
+ * Add a new ternary local reloption
+ *
+ * 'offset' is offset of ternary-typed field.
+ */
+void
+add_local_ternary_reloption(local_relopts *relopts, const char *name,
+ const char *desc, pg_ternary default_val,
+ int offset)
+{
+ relopt_ternary *newoption;
+
+ newoption =
+ init_ternary_reloption(RELOPT_KIND_LOCAL, name, desc, default_val, 0);
+
+ add_local_reloption(relopts, (relopt_gen *) newoption, offset);
+}
/*
* init_real_reloption
@@ -1626,6 +1705,19 @@ parse_one_reloption(relopt_value *option, char *text_str, int text_len,
option->gen->name, value)));
}
break;
+ case RELOPT_TYPE_TERNARY:
+ {
+ bool b;
+
+ parsed = parse_bool(value, &b);
+ option->values.ternary_val = b ? TERNARY_TRUE : TERNARY_FALSE;
+ if (validate && !parsed)
+ ereport(ERROR,
+ (errcode(ERRCODE_INVALID_PARAMETER_VALUE),
+ errmsg("invalid value for ternary option \"%s\": %s",
+ option->gen->name, value)));
+ }
+ break;
case RELOPT_TYPE_INT:
{
relopt_int *optint = (relopt_int *) option->gen;
@@ -1789,17 +1881,6 @@ fillRelOptions(void *rdopts, Size basesize,
char *itempos = ((char *) rdopts) + elems[j].offset;
char *string_val;
- /*
- * If isset_offset is provided, store whether the reloption is
- * set there.
- */
- if (elems[j].isset_offset > 0)
- {
- char *setpos = ((char *) rdopts) + elems[j].isset_offset;
-
- *(bool *) setpos = options[i].isset;
- }
-
switch (options[i].gen->type)
{
case RELOPT_TYPE_BOOL:
@@ -1807,6 +1888,11 @@ fillRelOptions(void *rdopts, Size basesize,
options[i].values.bool_val :
((relopt_bool *) options[i].gen)->default_val;
break;
+ case RELOPT_TYPE_TERNARY:
+ *(pg_ternary *) itempos = options[i].isset ?
+ options[i].values.ternary_val :
+ ((relopt_ternary *) options[i].gen)->default_val;
+ break;
case RELOPT_TYPE_INT:
*(int *) itempos = options[i].isset ?
options[i].values.int_val :
@@ -1923,8 +2009,8 @@ default_reloptions(Datum reloptions, bool validate, relopt_kind kind)
offsetof(StdRdOptions, parallel_workers)},
{"vacuum_index_cleanup", RELOPT_TYPE_ENUM,
offsetof(StdRdOptions, vacuum_index_cleanup)},
- {"vacuum_truncate", RELOPT_TYPE_BOOL,
- offsetof(StdRdOptions, vacuum_truncate), offsetof(StdRdOptions, vacuum_truncate_set)},
+ {"vacuum_truncate", RELOPT_TYPE_TERNARY,
+ offsetof(StdRdOptions, vacuum_truncate)},
{"vacuum_max_eager_freeze_failure_rate", RELOPT_TYPE_REAL,
offsetof(StdRdOptions, vacuum_max_eager_freeze_failure_rate)}
};
@@ -2004,7 +2090,6 @@ build_local_reloptions(local_relopts *relopts, Datum options, bool validate)
elems[i].optname = opt->option->name;
elems[i].opttype = opt->option->type;
elems[i].offset = opt->offset;
- elems[i].isset_offset = 0; /* not supported for local relopts yet */
i++;
}
diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c
index aa4fbec143f..696eab9bd97 100644
--- a/src/backend/commands/vacuum.c
+++ b/src/backend/commands/vacuum.c
@@ -2224,9 +2224,9 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams params,
{
StdRdOptions *opts = (StdRdOptions *) rel->rd_options;
- if (opts && opts->vacuum_truncate_set)
+ if (opts && opts->vacuum_truncate != TERNARY_UNSET)
{
- if (opts->vacuum_truncate)
+ if (opts->vacuum_truncate == TERNARY_TRUE)
params.truncate = VACOPTVALUE_ENABLED;
else
params.truncate = VACOPTVALUE_DISABLED;
diff --git a/src/include/access/reloptions.h b/src/include/access/reloptions.h
index 2f08e1b0cf0..dfbef2babf2 100644
--- a/src/include/access/reloptions.h
+++ b/src/include/access/reloptions.h
@@ -29,6 +29,7 @@
typedef enum relopt_type
{
RELOPT_TYPE_BOOL,
+ RELOPT_TYPE_TERNARY, /* on, off, unset */
RELOPT_TYPE_INT,
RELOPT_TYPE_REAL,
RELOPT_TYPE_ENUM,
@@ -80,6 +81,7 @@ typedef struct relopt_value
union
{
bool bool_val;
+ pg_ternary ternary_val;
int int_val;
double real_val;
int enum_val;
@@ -94,6 +96,12 @@ typedef struct relopt_bool
bool default_val;
} relopt_bool;
+typedef struct relopt_ternary
+{
+ relopt_gen gen;
+ int default_val;
+} relopt_ternary;
+
typedef struct relopt_int
{
relopt_gen gen;
@@ -152,19 +160,6 @@ typedef struct
const char *optname; /* option's name */
relopt_type opttype; /* option's datatype */
int offset; /* offset of field in result struct */
-
- /*
- * isset_offset is an optional offset of a field in the result struct that
- * stores whether the option is explicitly set for the relation or if it
- * just picked up the default value. In most cases, this can be
- * accomplished by giving the reloption a special out-of-range default
- * value (e.g., some integer reloptions use -2), but this isn't always
- * possible. For example, a Boolean reloption cannot be given an
- * out-of-range default, so we need another way to discover the source of
- * its value. This offset is only used if given a value greater than
- * zero.
- */
- int isset_offset;
} relopt_parse_elt;
/* Local reloption definition */
@@ -195,6 +190,9 @@ typedef struct local_relopts
extern relopt_kind add_reloption_kind(void);
extern void add_bool_reloption(bits32 kinds, const char *name, const char *desc,
bool default_val, LOCKMODE lockmode);
+extern void add_ternary_reloption(bits32 kinds, const char *name,
+ const char *desc, pg_ternary default_val,
+ LOCKMODE lockmode);
extern void add_int_reloption(bits32 kinds, const char *name, const char *desc,
int default_val, int min_val, int max_val,
LOCKMODE lockmode);
@@ -214,6 +212,9 @@ extern void register_reloptions_validator(local_relopts *relopts,
extern void add_local_bool_reloption(local_relopts *relopts, const char *name,
const char *desc, bool default_val,
int offset);
+extern void add_local_ternary_reloption(local_relopts *relopts,
+ const char *name, const char *desc,
+ pg_ternary default_val, int offset);
extern void add_local_int_reloption(local_relopts *relopts, const char *name,
const char *desc, int default_val,
int min_val, int max_val, int offset);
diff --git a/src/include/postgres.h b/src/include/postgres.h
index 7d93fbce709..47c88780776 100644
--- a/src/include/postgres.h
+++ b/src/include/postgres.h
@@ -543,6 +543,21 @@ Float8GetDatum(float8 X)
* ----------------------------------------------------------------
*/
+/*
+ * pg_ternary
+ * Boolean value with an extra "unset" value
+ *
+ * This enum can be used for values that want to distinguish between true,
+ * false, and unset.
+*/
+
+typedef enum pg_ternary
+{
+ TERNARY_FALSE = 0,
+ TERNARY_TRUE = 1,
+ TERNARY_UNSET = -1
+} pg_ternary;
+
/*
* NON_EXEC_STATIC: It's sometimes useful to define a variable or function
* that is normally static but extern when using EXEC_BACKEND (see
diff --git a/src/include/utils/rel.h b/src/include/utils/rel.h
index d03ab247788..5feb18a5373 100644
--- a/src/include/utils/rel.h
+++ b/src/include/utils/rel.h
@@ -347,8 +347,7 @@ typedef struct StdRdOptions
bool user_catalog_table; /* use as an additional catalog relation */
int parallel_workers; /* max number of parallel workers */
StdRdOptIndexCleanup vacuum_index_cleanup; /* controls index vacuuming */
- bool vacuum_truncate; /* enables vacuum to truncate a relation */
- bool vacuum_truncate_set; /* whether vacuum_truncate is set */
+ pg_ternary vacuum_truncate; /* enables vacuum to truncate a relation */
/*
* Fraction of pages in a relation that vacuum can eagerly scan and fail
diff --git a/src/test/regress/expected/reloptions.out b/src/test/regress/expected/reloptions.out
index 9de19b4e3f1..1c99f79ab01 100644
--- a/src/test/regress/expected/reloptions.out
+++ b/src/test/regress/expected/reloptions.out
@@ -98,6 +98,42 @@ SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass;
{fillfactor=13,autovacuum_enabled=false}
(1 row)
+-- Tests for future (FIXME) ternary options
+-- behave as boolean option: accept unassigned name and truncated value
+DROP TABLE reloptions_test;
+CREATE TABLE reloptions_test(i INT) WITH (vacuum_truncate);
+SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass;
+ reloptions
+------------------------
+ {vacuum_truncate=true}
+(1 row)
+
+DROP TABLE reloptions_test;
+CREATE TABLE reloptions_test(i INT) WITH (vacuum_truncate=FaLS);
+SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass;
+ reloptions
+------------------------
+ {vacuum_truncate=fals}
+(1 row)
+
+-- preferred "true" alias is used when storing
+DROP TABLE reloptions_test;
+CREATE TABLE reloptions_test(i INT) WITH (vacuum_index_cleanup=on);
+SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass;
+ reloptions
+---------------------------
+ {vacuum_index_cleanup=on}
+(1 row)
+
+-- custom "third" value is available
+DROP TABLE reloptions_test;
+CREATE TABLE reloptions_test(i INT) WITH (vacuum_index_cleanup=auto);
+SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass;
+ reloptions
+-----------------------------
+ {vacuum_index_cleanup=auto}
+(1 row)
+
-- Test vacuum_truncate option
DROP TABLE reloptions_test;
CREATE TEMP TABLE reloptions_test(i INT NOT NULL, j text)
diff --git a/src/test/regress/sql/reloptions.sql b/src/test/regress/sql/reloptions.sql
index 24fbe0b478d..f5980dafcbc 100644
--- a/src/test/regress/sql/reloptions.sql
+++ b/src/test/regress/sql/reloptions.sql
@@ -59,6 +59,27 @@ UPDATE pg_class
ALTER TABLE reloptions_test RESET (illegal_option);
SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass;
+-- Tests for future (FIXME) ternary options
+
+-- behave as boolean option: accept unassigned name and truncated value
+DROP TABLE reloptions_test;
+CREATE TABLE reloptions_test(i INT) WITH (vacuum_truncate);
+SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass;
+
+DROP TABLE reloptions_test;
+CREATE TABLE reloptions_test(i INT) WITH (vacuum_truncate=FaLS);
+SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass;
+
+-- preferred "true" alias is used when storing
+DROP TABLE reloptions_test;
+CREATE TABLE reloptions_test(i INT) WITH (vacuum_index_cleanup=on);
+SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass;
+
+-- custom "third" value is available
+DROP TABLE reloptions_test;
+CREATE TABLE reloptions_test(i INT) WITH (vacuum_index_cleanup=auto);
+SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass;
+
-- Test vacuum_truncate option
DROP TABLE reloptions_test;
diff --git a/src/tools/pgindent/typedefs.list b/src/tools/pgindent/typedefs.list
index 3f3a888fd0e..1c8610fd46c 100644
--- a/src/tools/pgindent/typedefs.list
+++ b/src/tools/pgindent/typedefs.list
@@ -3952,6 +3952,7 @@ pg_sha512_ctx
pg_snapshot
pg_special_case
pg_stack_base_t
+pg_ternary
pg_time_t
pg_time_usec_t
pg_tz
@@ -4079,6 +4080,7 @@ relopt_kind
relopt_parse_elt
relopt_real
relopt_string
+relopt_ternary
relopt_type
relopt_value
relopts_validator
--
2.47.3
--c36t3fndxhkzbl5z--
^ permalink raw reply [nested|flat] 868+ messages in thread
* [PATCH v3] Introduce ternary reloptions
@ 2026-01-16 15:04 Álvaro Herrera <[email protected]>
0 siblings, 0 replies; 868+ messages in thread
From: Álvaro Herrera @ 2026-01-16 15:04 UTC (permalink / raw)
Introduce ternary reloption as a replacement for current
`vacuum_truncate` implementation. Remove the `vacuum_truncate_set`
separate flag and use TERNARY_UNSET instead.
This could also be used for other options such as `vacuum_index_cleanup`
and `buffering`, but lets get the scaffolding in first.
Discussion: https://postgr.es/m/3474141.usfYGdeWWP@thinkpad-pgpro
---
src/backend/access/common/reloptions.c | 137 ++++++++++++++++++-----
src/backend/commands/vacuum.c | 4 +-
src/include/access/reloptions.h | 27 ++---
src/include/postgres.h | 15 +++
src/include/utils/rel.h | 3 +-
src/test/regress/expected/reloptions.out | 36 ++++++
src/test/regress/sql/reloptions.sql | 21 ++++
src/tools/pgindent/typedefs.list | 2 +
8 files changed, 202 insertions(+), 43 deletions(-)
diff --git a/src/backend/access/common/reloptions.c b/src/backend/access/common/reloptions.c
index 0b83f98ed5f..6f1f577581a 100644
--- a/src/backend/access/common/reloptions.c
+++ b/src/backend/access/common/reloptions.c
@@ -40,9 +40,9 @@
*
* To add an option:
*
- * (i) decide on a type (bool, integer, real, enum, string), name, default
- * value, upper and lower bounds (if applicable); for strings, consider a
- * validation routine.
+ * (i) decide on a type (bool, ternary, integer, real, enum, string), name,
+ * default value, upper and lower bounds (if applicable); for strings,
+ * consider a validation routine.
* (ii) add a record below (or use add_<type>_reloption).
* (iii) add it to the appropriate options struct (perhaps StdRdOptions)
* (iv) add it to the appropriate handling routine (perhaps
@@ -147,15 +147,6 @@ static relopt_bool boolRelOpts[] =
},
false
},
- {
- {
- "vacuum_truncate",
- "Enables vacuum to truncate empty pages at the end of this table",
- RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
- ShareUpdateExclusiveLock
- },
- true
- },
{
{
"deduplicate_items",
@@ -170,6 +161,25 @@ static relopt_bool boolRelOpts[] =
{{NULL}}
};
+static relopt_ternary ternaryRelOpts[] =
+{
+ {
+ {
+ "vacuum_truncate",
+ "Enables vacuum to truncate empty pages at the end of this table",
+ RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
+ ShareUpdateExclusiveLock
+ },
+ TERNARY_UNSET
+ },
+ /* list terminator */
+ {
+ {
+ NULL
+ }
+ }
+};
+
static relopt_int intRelOpts[] =
{
{
@@ -609,6 +619,13 @@ initialize_reloptions(void)
boolRelOpts[i].gen.lockmode));
j++;
}
+ for (i = 0; ternaryRelOpts[i].gen.name; i++)
+ {
+ Assert(DoLockModesConflict(ternaryRelOpts[i].gen.lockmode,
+ ternaryRelOpts[i].gen.lockmode));
+ j++;
+ }
+
for (i = 0; intRelOpts[i].gen.name; i++)
{
Assert(DoLockModesConflict(intRelOpts[i].gen.lockmode,
@@ -649,6 +666,14 @@ initialize_reloptions(void)
j++;
}
+ for (i = 0; ternaryRelOpts[i].gen.name; i++)
+ {
+ relOpts[j] = &ternaryRelOpts[i].gen;
+ relOpts[j]->type = RELOPT_TYPE_TERNARY;
+ relOpts[j]->namelen = strlen(relOpts[j]->name);
+ j++;
+ }
+
for (i = 0; intRelOpts[i].gen.name; i++)
{
relOpts[j] = &intRelOpts[i].gen;
@@ -809,6 +834,9 @@ allocate_reloption(bits32 kinds, int type, const char *name, const char *desc,
case RELOPT_TYPE_BOOL:
size = sizeof(relopt_bool);
break;
+ case RELOPT_TYPE_TERNARY:
+ size = sizeof(relopt_ternary);
+ break;
case RELOPT_TYPE_INT:
size = sizeof(relopt_int);
break;
@@ -892,6 +920,57 @@ add_local_bool_reloption(local_relopts *relopts, const char *name,
add_local_reloption(relopts, (relopt_gen *) newoption, offset);
}
+/*
+ * init_ternary_reloption
+ * Allocate and initialize a new ternary reloption
+ */
+static relopt_ternary *
+init_ternary_reloption(bits32 kinds, const char *name, const char *desc,
+ pg_ternary default_val, LOCKMODE lockmode)
+{
+ relopt_ternary *newoption;
+
+ newoption = (relopt_ternary *)
+ allocate_reloption(kinds, RELOPT_TYPE_TERNARY, name, desc, lockmode);
+ newoption->default_val = default_val;
+
+ return newoption;
+}
+
+/*
+ * add_ternary_reloption
+ * Add a new ternary reloption
+ */
+void
+add_ternary_reloption(bits32 kinds, const char *name, const char *desc,
+ pg_ternary default_val, LOCKMODE lockmode)
+{
+ relopt_ternary *newoption;
+
+ newoption =
+ init_ternary_reloption(kinds, name, desc, default_val, lockmode);
+
+ add_reloption((relopt_gen *) newoption);
+}
+
+/*
+ * add_local_ternary_reloption
+ * Add a new ternary local reloption
+ *
+ * 'offset' is offset of ternary-typed field.
+ */
+void
+add_local_ternary_reloption(local_relopts *relopts, const char *name,
+ const char *desc, pg_ternary default_val,
+ int offset)
+{
+ relopt_ternary *newoption;
+
+ newoption =
+ init_ternary_reloption(RELOPT_KIND_LOCAL, name, desc, default_val, 0);
+
+ add_local_reloption(relopts, (relopt_gen *) newoption, offset);
+}
/*
* init_real_reloption
@@ -1626,6 +1705,19 @@ parse_one_reloption(relopt_value *option, char *text_str, int text_len,
option->gen->name, value)));
}
break;
+ case RELOPT_TYPE_TERNARY:
+ {
+ bool b;
+
+ parsed = parse_bool(value, &b);
+ option->values.ternary_val = b ? TERNARY_TRUE : TERNARY_FALSE;
+ if (validate && !parsed)
+ ereport(ERROR,
+ (errcode(ERRCODE_INVALID_PARAMETER_VALUE),
+ errmsg("invalid value for ternary option \"%s\": %s",
+ option->gen->name, value)));
+ }
+ break;
case RELOPT_TYPE_INT:
{
relopt_int *optint = (relopt_int *) option->gen;
@@ -1789,17 +1881,6 @@ fillRelOptions(void *rdopts, Size basesize,
char *itempos = ((char *) rdopts) + elems[j].offset;
char *string_val;
- /*
- * If isset_offset is provided, store whether the reloption is
- * set there.
- */
- if (elems[j].isset_offset > 0)
- {
- char *setpos = ((char *) rdopts) + elems[j].isset_offset;
-
- *(bool *) setpos = options[i].isset;
- }
-
switch (options[i].gen->type)
{
case RELOPT_TYPE_BOOL:
@@ -1807,6 +1888,11 @@ fillRelOptions(void *rdopts, Size basesize,
options[i].values.bool_val :
((relopt_bool *) options[i].gen)->default_val;
break;
+ case RELOPT_TYPE_TERNARY:
+ *(pg_ternary *) itempos = options[i].isset ?
+ options[i].values.ternary_val :
+ ((relopt_ternary *) options[i].gen)->default_val;
+ break;
case RELOPT_TYPE_INT:
*(int *) itempos = options[i].isset ?
options[i].values.int_val :
@@ -1923,8 +2009,8 @@ default_reloptions(Datum reloptions, bool validate, relopt_kind kind)
offsetof(StdRdOptions, parallel_workers)},
{"vacuum_index_cleanup", RELOPT_TYPE_ENUM,
offsetof(StdRdOptions, vacuum_index_cleanup)},
- {"vacuum_truncate", RELOPT_TYPE_BOOL,
- offsetof(StdRdOptions, vacuum_truncate), offsetof(StdRdOptions, vacuum_truncate_set)},
+ {"vacuum_truncate", RELOPT_TYPE_TERNARY,
+ offsetof(StdRdOptions, vacuum_truncate)},
{"vacuum_max_eager_freeze_failure_rate", RELOPT_TYPE_REAL,
offsetof(StdRdOptions, vacuum_max_eager_freeze_failure_rate)}
};
@@ -2004,7 +2090,6 @@ build_local_reloptions(local_relopts *relopts, Datum options, bool validate)
elems[i].optname = opt->option->name;
elems[i].opttype = opt->option->type;
elems[i].offset = opt->offset;
- elems[i].isset_offset = 0; /* not supported for local relopts yet */
i++;
}
diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c
index aa4fbec143f..696eab9bd97 100644
--- a/src/backend/commands/vacuum.c
+++ b/src/backend/commands/vacuum.c
@@ -2224,9 +2224,9 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams params,
{
StdRdOptions *opts = (StdRdOptions *) rel->rd_options;
- if (opts && opts->vacuum_truncate_set)
+ if (opts && opts->vacuum_truncate != TERNARY_UNSET)
{
- if (opts->vacuum_truncate)
+ if (opts->vacuum_truncate == TERNARY_TRUE)
params.truncate = VACOPTVALUE_ENABLED;
else
params.truncate = VACOPTVALUE_DISABLED;
diff --git a/src/include/access/reloptions.h b/src/include/access/reloptions.h
index 2f08e1b0cf0..dfbef2babf2 100644
--- a/src/include/access/reloptions.h
+++ b/src/include/access/reloptions.h
@@ -29,6 +29,7 @@
typedef enum relopt_type
{
RELOPT_TYPE_BOOL,
+ RELOPT_TYPE_TERNARY, /* on, off, unset */
RELOPT_TYPE_INT,
RELOPT_TYPE_REAL,
RELOPT_TYPE_ENUM,
@@ -80,6 +81,7 @@ typedef struct relopt_value
union
{
bool bool_val;
+ pg_ternary ternary_val;
int int_val;
double real_val;
int enum_val;
@@ -94,6 +96,12 @@ typedef struct relopt_bool
bool default_val;
} relopt_bool;
+typedef struct relopt_ternary
+{
+ relopt_gen gen;
+ int default_val;
+} relopt_ternary;
+
typedef struct relopt_int
{
relopt_gen gen;
@@ -152,19 +160,6 @@ typedef struct
const char *optname; /* option's name */
relopt_type opttype; /* option's datatype */
int offset; /* offset of field in result struct */
-
- /*
- * isset_offset is an optional offset of a field in the result struct that
- * stores whether the option is explicitly set for the relation or if it
- * just picked up the default value. In most cases, this can be
- * accomplished by giving the reloption a special out-of-range default
- * value (e.g., some integer reloptions use -2), but this isn't always
- * possible. For example, a Boolean reloption cannot be given an
- * out-of-range default, so we need another way to discover the source of
- * its value. This offset is only used if given a value greater than
- * zero.
- */
- int isset_offset;
} relopt_parse_elt;
/* Local reloption definition */
@@ -195,6 +190,9 @@ typedef struct local_relopts
extern relopt_kind add_reloption_kind(void);
extern void add_bool_reloption(bits32 kinds, const char *name, const char *desc,
bool default_val, LOCKMODE lockmode);
+extern void add_ternary_reloption(bits32 kinds, const char *name,
+ const char *desc, pg_ternary default_val,
+ LOCKMODE lockmode);
extern void add_int_reloption(bits32 kinds, const char *name, const char *desc,
int default_val, int min_val, int max_val,
LOCKMODE lockmode);
@@ -214,6 +212,9 @@ extern void register_reloptions_validator(local_relopts *relopts,
extern void add_local_bool_reloption(local_relopts *relopts, const char *name,
const char *desc, bool default_val,
int offset);
+extern void add_local_ternary_reloption(local_relopts *relopts,
+ const char *name, const char *desc,
+ pg_ternary default_val, int offset);
extern void add_local_int_reloption(local_relopts *relopts, const char *name,
const char *desc, int default_val,
int min_val, int max_val, int offset);
diff --git a/src/include/postgres.h b/src/include/postgres.h
index 7d93fbce709..47c88780776 100644
--- a/src/include/postgres.h
+++ b/src/include/postgres.h
@@ -543,6 +543,21 @@ Float8GetDatum(float8 X)
* ----------------------------------------------------------------
*/
+/*
+ * pg_ternary
+ * Boolean value with an extra "unset" value
+ *
+ * This enum can be used for values that want to distinguish between true,
+ * false, and unset.
+*/
+
+typedef enum pg_ternary
+{
+ TERNARY_FALSE = 0,
+ TERNARY_TRUE = 1,
+ TERNARY_UNSET = -1
+} pg_ternary;
+
/*
* NON_EXEC_STATIC: It's sometimes useful to define a variable or function
* that is normally static but extern when using EXEC_BACKEND (see
diff --git a/src/include/utils/rel.h b/src/include/utils/rel.h
index d03ab247788..5feb18a5373 100644
--- a/src/include/utils/rel.h
+++ b/src/include/utils/rel.h
@@ -347,8 +347,7 @@ typedef struct StdRdOptions
bool user_catalog_table; /* use as an additional catalog relation */
int parallel_workers; /* max number of parallel workers */
StdRdOptIndexCleanup vacuum_index_cleanup; /* controls index vacuuming */
- bool vacuum_truncate; /* enables vacuum to truncate a relation */
- bool vacuum_truncate_set; /* whether vacuum_truncate is set */
+ pg_ternary vacuum_truncate; /* enables vacuum to truncate a relation */
/*
* Fraction of pages in a relation that vacuum can eagerly scan and fail
diff --git a/src/test/regress/expected/reloptions.out b/src/test/regress/expected/reloptions.out
index 9de19b4e3f1..1c99f79ab01 100644
--- a/src/test/regress/expected/reloptions.out
+++ b/src/test/regress/expected/reloptions.out
@@ -98,6 +98,42 @@ SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass;
{fillfactor=13,autovacuum_enabled=false}
(1 row)
+-- Tests for future (FIXME) ternary options
+-- behave as boolean option: accept unassigned name and truncated value
+DROP TABLE reloptions_test;
+CREATE TABLE reloptions_test(i INT) WITH (vacuum_truncate);
+SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass;
+ reloptions
+------------------------
+ {vacuum_truncate=true}
+(1 row)
+
+DROP TABLE reloptions_test;
+CREATE TABLE reloptions_test(i INT) WITH (vacuum_truncate=FaLS);
+SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass;
+ reloptions
+------------------------
+ {vacuum_truncate=fals}
+(1 row)
+
+-- preferred "true" alias is used when storing
+DROP TABLE reloptions_test;
+CREATE TABLE reloptions_test(i INT) WITH (vacuum_index_cleanup=on);
+SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass;
+ reloptions
+---------------------------
+ {vacuum_index_cleanup=on}
+(1 row)
+
+-- custom "third" value is available
+DROP TABLE reloptions_test;
+CREATE TABLE reloptions_test(i INT) WITH (vacuum_index_cleanup=auto);
+SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass;
+ reloptions
+-----------------------------
+ {vacuum_index_cleanup=auto}
+(1 row)
+
-- Test vacuum_truncate option
DROP TABLE reloptions_test;
CREATE TEMP TABLE reloptions_test(i INT NOT NULL, j text)
diff --git a/src/test/regress/sql/reloptions.sql b/src/test/regress/sql/reloptions.sql
index 24fbe0b478d..f5980dafcbc 100644
--- a/src/test/regress/sql/reloptions.sql
+++ b/src/test/regress/sql/reloptions.sql
@@ -59,6 +59,27 @@ UPDATE pg_class
ALTER TABLE reloptions_test RESET (illegal_option);
SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass;
+-- Tests for future (FIXME) ternary options
+
+-- behave as boolean option: accept unassigned name and truncated value
+DROP TABLE reloptions_test;
+CREATE TABLE reloptions_test(i INT) WITH (vacuum_truncate);
+SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass;
+
+DROP TABLE reloptions_test;
+CREATE TABLE reloptions_test(i INT) WITH (vacuum_truncate=FaLS);
+SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass;
+
+-- preferred "true" alias is used when storing
+DROP TABLE reloptions_test;
+CREATE TABLE reloptions_test(i INT) WITH (vacuum_index_cleanup=on);
+SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass;
+
+-- custom "third" value is available
+DROP TABLE reloptions_test;
+CREATE TABLE reloptions_test(i INT) WITH (vacuum_index_cleanup=auto);
+SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass;
+
-- Test vacuum_truncate option
DROP TABLE reloptions_test;
diff --git a/src/tools/pgindent/typedefs.list b/src/tools/pgindent/typedefs.list
index 3f3a888fd0e..1c8610fd46c 100644
--- a/src/tools/pgindent/typedefs.list
+++ b/src/tools/pgindent/typedefs.list
@@ -3952,6 +3952,7 @@ pg_sha512_ctx
pg_snapshot
pg_special_case
pg_stack_base_t
+pg_ternary
pg_time_t
pg_time_usec_t
pg_tz
@@ -4079,6 +4080,7 @@ relopt_kind
relopt_parse_elt
relopt_real
relopt_string
+relopt_ternary
relopt_type
relopt_value
relopts_validator
--
2.47.3
--c36t3fndxhkzbl5z--
^ permalink raw reply [nested|flat] 868+ messages in thread
* [PATCH v3] Introduce ternary reloptions
@ 2026-01-16 15:04 Álvaro Herrera <[email protected]>
0 siblings, 0 replies; 868+ messages in thread
From: Álvaro Herrera @ 2026-01-16 15:04 UTC (permalink / raw)
Introduce ternary reloption as a replacement for current
`vacuum_truncate` implementation. Remove the `vacuum_truncate_set`
separate flag and use TERNARY_UNSET instead.
This could also be used for other options such as `vacuum_index_cleanup`
and `buffering`, but lets get the scaffolding in first.
Discussion: https://postgr.es/m/3474141.usfYGdeWWP@thinkpad-pgpro
---
src/backend/access/common/reloptions.c | 137 ++++++++++++++++++-----
src/backend/commands/vacuum.c | 4 +-
src/include/access/reloptions.h | 27 ++---
src/include/postgres.h | 15 +++
src/include/utils/rel.h | 3 +-
src/test/regress/expected/reloptions.out | 36 ++++++
src/test/regress/sql/reloptions.sql | 21 ++++
src/tools/pgindent/typedefs.list | 2 +
8 files changed, 202 insertions(+), 43 deletions(-)
diff --git a/src/backend/access/common/reloptions.c b/src/backend/access/common/reloptions.c
index 0b83f98ed5f..6f1f577581a 100644
--- a/src/backend/access/common/reloptions.c
+++ b/src/backend/access/common/reloptions.c
@@ -40,9 +40,9 @@
*
* To add an option:
*
- * (i) decide on a type (bool, integer, real, enum, string), name, default
- * value, upper and lower bounds (if applicable); for strings, consider a
- * validation routine.
+ * (i) decide on a type (bool, ternary, integer, real, enum, string), name,
+ * default value, upper and lower bounds (if applicable); for strings,
+ * consider a validation routine.
* (ii) add a record below (or use add_<type>_reloption).
* (iii) add it to the appropriate options struct (perhaps StdRdOptions)
* (iv) add it to the appropriate handling routine (perhaps
@@ -147,15 +147,6 @@ static relopt_bool boolRelOpts[] =
},
false
},
- {
- {
- "vacuum_truncate",
- "Enables vacuum to truncate empty pages at the end of this table",
- RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
- ShareUpdateExclusiveLock
- },
- true
- },
{
{
"deduplicate_items",
@@ -170,6 +161,25 @@ static relopt_bool boolRelOpts[] =
{{NULL}}
};
+static relopt_ternary ternaryRelOpts[] =
+{
+ {
+ {
+ "vacuum_truncate",
+ "Enables vacuum to truncate empty pages at the end of this table",
+ RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
+ ShareUpdateExclusiveLock
+ },
+ TERNARY_UNSET
+ },
+ /* list terminator */
+ {
+ {
+ NULL
+ }
+ }
+};
+
static relopt_int intRelOpts[] =
{
{
@@ -609,6 +619,13 @@ initialize_reloptions(void)
boolRelOpts[i].gen.lockmode));
j++;
}
+ for (i = 0; ternaryRelOpts[i].gen.name; i++)
+ {
+ Assert(DoLockModesConflict(ternaryRelOpts[i].gen.lockmode,
+ ternaryRelOpts[i].gen.lockmode));
+ j++;
+ }
+
for (i = 0; intRelOpts[i].gen.name; i++)
{
Assert(DoLockModesConflict(intRelOpts[i].gen.lockmode,
@@ -649,6 +666,14 @@ initialize_reloptions(void)
j++;
}
+ for (i = 0; ternaryRelOpts[i].gen.name; i++)
+ {
+ relOpts[j] = &ternaryRelOpts[i].gen;
+ relOpts[j]->type = RELOPT_TYPE_TERNARY;
+ relOpts[j]->namelen = strlen(relOpts[j]->name);
+ j++;
+ }
+
for (i = 0; intRelOpts[i].gen.name; i++)
{
relOpts[j] = &intRelOpts[i].gen;
@@ -809,6 +834,9 @@ allocate_reloption(bits32 kinds, int type, const char *name, const char *desc,
case RELOPT_TYPE_BOOL:
size = sizeof(relopt_bool);
break;
+ case RELOPT_TYPE_TERNARY:
+ size = sizeof(relopt_ternary);
+ break;
case RELOPT_TYPE_INT:
size = sizeof(relopt_int);
break;
@@ -892,6 +920,57 @@ add_local_bool_reloption(local_relopts *relopts, const char *name,
add_local_reloption(relopts, (relopt_gen *) newoption, offset);
}
+/*
+ * init_ternary_reloption
+ * Allocate and initialize a new ternary reloption
+ */
+static relopt_ternary *
+init_ternary_reloption(bits32 kinds, const char *name, const char *desc,
+ pg_ternary default_val, LOCKMODE lockmode)
+{
+ relopt_ternary *newoption;
+
+ newoption = (relopt_ternary *)
+ allocate_reloption(kinds, RELOPT_TYPE_TERNARY, name, desc, lockmode);
+ newoption->default_val = default_val;
+
+ return newoption;
+}
+
+/*
+ * add_ternary_reloption
+ * Add a new ternary reloption
+ */
+void
+add_ternary_reloption(bits32 kinds, const char *name, const char *desc,
+ pg_ternary default_val, LOCKMODE lockmode)
+{
+ relopt_ternary *newoption;
+
+ newoption =
+ init_ternary_reloption(kinds, name, desc, default_val, lockmode);
+
+ add_reloption((relopt_gen *) newoption);
+}
+
+/*
+ * add_local_ternary_reloption
+ * Add a new ternary local reloption
+ *
+ * 'offset' is offset of ternary-typed field.
+ */
+void
+add_local_ternary_reloption(local_relopts *relopts, const char *name,
+ const char *desc, pg_ternary default_val,
+ int offset)
+{
+ relopt_ternary *newoption;
+
+ newoption =
+ init_ternary_reloption(RELOPT_KIND_LOCAL, name, desc, default_val, 0);
+
+ add_local_reloption(relopts, (relopt_gen *) newoption, offset);
+}
/*
* init_real_reloption
@@ -1626,6 +1705,19 @@ parse_one_reloption(relopt_value *option, char *text_str, int text_len,
option->gen->name, value)));
}
break;
+ case RELOPT_TYPE_TERNARY:
+ {
+ bool b;
+
+ parsed = parse_bool(value, &b);
+ option->values.ternary_val = b ? TERNARY_TRUE : TERNARY_FALSE;
+ if (validate && !parsed)
+ ereport(ERROR,
+ (errcode(ERRCODE_INVALID_PARAMETER_VALUE),
+ errmsg("invalid value for ternary option \"%s\": %s",
+ option->gen->name, value)));
+ }
+ break;
case RELOPT_TYPE_INT:
{
relopt_int *optint = (relopt_int *) option->gen;
@@ -1789,17 +1881,6 @@ fillRelOptions(void *rdopts, Size basesize,
char *itempos = ((char *) rdopts) + elems[j].offset;
char *string_val;
- /*
- * If isset_offset is provided, store whether the reloption is
- * set there.
- */
- if (elems[j].isset_offset > 0)
- {
- char *setpos = ((char *) rdopts) + elems[j].isset_offset;
-
- *(bool *) setpos = options[i].isset;
- }
-
switch (options[i].gen->type)
{
case RELOPT_TYPE_BOOL:
@@ -1807,6 +1888,11 @@ fillRelOptions(void *rdopts, Size basesize,
options[i].values.bool_val :
((relopt_bool *) options[i].gen)->default_val;
break;
+ case RELOPT_TYPE_TERNARY:
+ *(pg_ternary *) itempos = options[i].isset ?
+ options[i].values.ternary_val :
+ ((relopt_ternary *) options[i].gen)->default_val;
+ break;
case RELOPT_TYPE_INT:
*(int *) itempos = options[i].isset ?
options[i].values.int_val :
@@ -1923,8 +2009,8 @@ default_reloptions(Datum reloptions, bool validate, relopt_kind kind)
offsetof(StdRdOptions, parallel_workers)},
{"vacuum_index_cleanup", RELOPT_TYPE_ENUM,
offsetof(StdRdOptions, vacuum_index_cleanup)},
- {"vacuum_truncate", RELOPT_TYPE_BOOL,
- offsetof(StdRdOptions, vacuum_truncate), offsetof(StdRdOptions, vacuum_truncate_set)},
+ {"vacuum_truncate", RELOPT_TYPE_TERNARY,
+ offsetof(StdRdOptions, vacuum_truncate)},
{"vacuum_max_eager_freeze_failure_rate", RELOPT_TYPE_REAL,
offsetof(StdRdOptions, vacuum_max_eager_freeze_failure_rate)}
};
@@ -2004,7 +2090,6 @@ build_local_reloptions(local_relopts *relopts, Datum options, bool validate)
elems[i].optname = opt->option->name;
elems[i].opttype = opt->option->type;
elems[i].offset = opt->offset;
- elems[i].isset_offset = 0; /* not supported for local relopts yet */
i++;
}
diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c
index aa4fbec143f..696eab9bd97 100644
--- a/src/backend/commands/vacuum.c
+++ b/src/backend/commands/vacuum.c
@@ -2224,9 +2224,9 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams params,
{
StdRdOptions *opts = (StdRdOptions *) rel->rd_options;
- if (opts && opts->vacuum_truncate_set)
+ if (opts && opts->vacuum_truncate != TERNARY_UNSET)
{
- if (opts->vacuum_truncate)
+ if (opts->vacuum_truncate == TERNARY_TRUE)
params.truncate = VACOPTVALUE_ENABLED;
else
params.truncate = VACOPTVALUE_DISABLED;
diff --git a/src/include/access/reloptions.h b/src/include/access/reloptions.h
index 2f08e1b0cf0..dfbef2babf2 100644
--- a/src/include/access/reloptions.h
+++ b/src/include/access/reloptions.h
@@ -29,6 +29,7 @@
typedef enum relopt_type
{
RELOPT_TYPE_BOOL,
+ RELOPT_TYPE_TERNARY, /* on, off, unset */
RELOPT_TYPE_INT,
RELOPT_TYPE_REAL,
RELOPT_TYPE_ENUM,
@@ -80,6 +81,7 @@ typedef struct relopt_value
union
{
bool bool_val;
+ pg_ternary ternary_val;
int int_val;
double real_val;
int enum_val;
@@ -94,6 +96,12 @@ typedef struct relopt_bool
bool default_val;
} relopt_bool;
+typedef struct relopt_ternary
+{
+ relopt_gen gen;
+ int default_val;
+} relopt_ternary;
+
typedef struct relopt_int
{
relopt_gen gen;
@@ -152,19 +160,6 @@ typedef struct
const char *optname; /* option's name */
relopt_type opttype; /* option's datatype */
int offset; /* offset of field in result struct */
-
- /*
- * isset_offset is an optional offset of a field in the result struct that
- * stores whether the option is explicitly set for the relation or if it
- * just picked up the default value. In most cases, this can be
- * accomplished by giving the reloption a special out-of-range default
- * value (e.g., some integer reloptions use -2), but this isn't always
- * possible. For example, a Boolean reloption cannot be given an
- * out-of-range default, so we need another way to discover the source of
- * its value. This offset is only used if given a value greater than
- * zero.
- */
- int isset_offset;
} relopt_parse_elt;
/* Local reloption definition */
@@ -195,6 +190,9 @@ typedef struct local_relopts
extern relopt_kind add_reloption_kind(void);
extern void add_bool_reloption(bits32 kinds, const char *name, const char *desc,
bool default_val, LOCKMODE lockmode);
+extern void add_ternary_reloption(bits32 kinds, const char *name,
+ const char *desc, pg_ternary default_val,
+ LOCKMODE lockmode);
extern void add_int_reloption(bits32 kinds, const char *name, const char *desc,
int default_val, int min_val, int max_val,
LOCKMODE lockmode);
@@ -214,6 +212,9 @@ extern void register_reloptions_validator(local_relopts *relopts,
extern void add_local_bool_reloption(local_relopts *relopts, const char *name,
const char *desc, bool default_val,
int offset);
+extern void add_local_ternary_reloption(local_relopts *relopts,
+ const char *name, const char *desc,
+ pg_ternary default_val, int offset);
extern void add_local_int_reloption(local_relopts *relopts, const char *name,
const char *desc, int default_val,
int min_val, int max_val, int offset);
diff --git a/src/include/postgres.h b/src/include/postgres.h
index 7d93fbce709..47c88780776 100644
--- a/src/include/postgres.h
+++ b/src/include/postgres.h
@@ -543,6 +543,21 @@ Float8GetDatum(float8 X)
* ----------------------------------------------------------------
*/
+/*
+ * pg_ternary
+ * Boolean value with an extra "unset" value
+ *
+ * This enum can be used for values that want to distinguish between true,
+ * false, and unset.
+*/
+
+typedef enum pg_ternary
+{
+ TERNARY_FALSE = 0,
+ TERNARY_TRUE = 1,
+ TERNARY_UNSET = -1
+} pg_ternary;
+
/*
* NON_EXEC_STATIC: It's sometimes useful to define a variable or function
* that is normally static but extern when using EXEC_BACKEND (see
diff --git a/src/include/utils/rel.h b/src/include/utils/rel.h
index d03ab247788..5feb18a5373 100644
--- a/src/include/utils/rel.h
+++ b/src/include/utils/rel.h
@@ -347,8 +347,7 @@ typedef struct StdRdOptions
bool user_catalog_table; /* use as an additional catalog relation */
int parallel_workers; /* max number of parallel workers */
StdRdOptIndexCleanup vacuum_index_cleanup; /* controls index vacuuming */
- bool vacuum_truncate; /* enables vacuum to truncate a relation */
- bool vacuum_truncate_set; /* whether vacuum_truncate is set */
+ pg_ternary vacuum_truncate; /* enables vacuum to truncate a relation */
/*
* Fraction of pages in a relation that vacuum can eagerly scan and fail
diff --git a/src/test/regress/expected/reloptions.out b/src/test/regress/expected/reloptions.out
index 9de19b4e3f1..1c99f79ab01 100644
--- a/src/test/regress/expected/reloptions.out
+++ b/src/test/regress/expected/reloptions.out
@@ -98,6 +98,42 @@ SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass;
{fillfactor=13,autovacuum_enabled=false}
(1 row)
+-- Tests for future (FIXME) ternary options
+-- behave as boolean option: accept unassigned name and truncated value
+DROP TABLE reloptions_test;
+CREATE TABLE reloptions_test(i INT) WITH (vacuum_truncate);
+SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass;
+ reloptions
+------------------------
+ {vacuum_truncate=true}
+(1 row)
+
+DROP TABLE reloptions_test;
+CREATE TABLE reloptions_test(i INT) WITH (vacuum_truncate=FaLS);
+SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass;
+ reloptions
+------------------------
+ {vacuum_truncate=fals}
+(1 row)
+
+-- preferred "true" alias is used when storing
+DROP TABLE reloptions_test;
+CREATE TABLE reloptions_test(i INT) WITH (vacuum_index_cleanup=on);
+SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass;
+ reloptions
+---------------------------
+ {vacuum_index_cleanup=on}
+(1 row)
+
+-- custom "third" value is available
+DROP TABLE reloptions_test;
+CREATE TABLE reloptions_test(i INT) WITH (vacuum_index_cleanup=auto);
+SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass;
+ reloptions
+-----------------------------
+ {vacuum_index_cleanup=auto}
+(1 row)
+
-- Test vacuum_truncate option
DROP TABLE reloptions_test;
CREATE TEMP TABLE reloptions_test(i INT NOT NULL, j text)
diff --git a/src/test/regress/sql/reloptions.sql b/src/test/regress/sql/reloptions.sql
index 24fbe0b478d..f5980dafcbc 100644
--- a/src/test/regress/sql/reloptions.sql
+++ b/src/test/regress/sql/reloptions.sql
@@ -59,6 +59,27 @@ UPDATE pg_class
ALTER TABLE reloptions_test RESET (illegal_option);
SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass;
+-- Tests for future (FIXME) ternary options
+
+-- behave as boolean option: accept unassigned name and truncated value
+DROP TABLE reloptions_test;
+CREATE TABLE reloptions_test(i INT) WITH (vacuum_truncate);
+SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass;
+
+DROP TABLE reloptions_test;
+CREATE TABLE reloptions_test(i INT) WITH (vacuum_truncate=FaLS);
+SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass;
+
+-- preferred "true" alias is used when storing
+DROP TABLE reloptions_test;
+CREATE TABLE reloptions_test(i INT) WITH (vacuum_index_cleanup=on);
+SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass;
+
+-- custom "third" value is available
+DROP TABLE reloptions_test;
+CREATE TABLE reloptions_test(i INT) WITH (vacuum_index_cleanup=auto);
+SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass;
+
-- Test vacuum_truncate option
DROP TABLE reloptions_test;
diff --git a/src/tools/pgindent/typedefs.list b/src/tools/pgindent/typedefs.list
index 3f3a888fd0e..1c8610fd46c 100644
--- a/src/tools/pgindent/typedefs.list
+++ b/src/tools/pgindent/typedefs.list
@@ -3952,6 +3952,7 @@ pg_sha512_ctx
pg_snapshot
pg_special_case
pg_stack_base_t
+pg_ternary
pg_time_t
pg_time_usec_t
pg_tz
@@ -4079,6 +4080,7 @@ relopt_kind
relopt_parse_elt
relopt_real
relopt_string
+relopt_ternary
relopt_type
relopt_value
relopts_validator
--
2.47.3
--c36t3fndxhkzbl5z--
^ permalink raw reply [nested|flat] 868+ messages in thread
* [PATCH v3] Introduce ternary reloptions
@ 2026-01-16 15:04 Álvaro Herrera <[email protected]>
0 siblings, 0 replies; 868+ messages in thread
From: Álvaro Herrera @ 2026-01-16 15:04 UTC (permalink / raw)
Introduce ternary reloption as a replacement for current
`vacuum_truncate` implementation. Remove the `vacuum_truncate_set`
separate flag and use TERNARY_UNSET instead.
This could also be used for other options such as `vacuum_index_cleanup`
and `buffering`, but lets get the scaffolding in first.
Discussion: https://postgr.es/m/3474141.usfYGdeWWP@thinkpad-pgpro
---
src/backend/access/common/reloptions.c | 137 ++++++++++++++++++-----
src/backend/commands/vacuum.c | 4 +-
src/include/access/reloptions.h | 27 ++---
src/include/postgres.h | 15 +++
src/include/utils/rel.h | 3 +-
src/test/regress/expected/reloptions.out | 36 ++++++
src/test/regress/sql/reloptions.sql | 21 ++++
src/tools/pgindent/typedefs.list | 2 +
8 files changed, 202 insertions(+), 43 deletions(-)
diff --git a/src/backend/access/common/reloptions.c b/src/backend/access/common/reloptions.c
index 0b83f98ed5f..6f1f577581a 100644
--- a/src/backend/access/common/reloptions.c
+++ b/src/backend/access/common/reloptions.c
@@ -40,9 +40,9 @@
*
* To add an option:
*
- * (i) decide on a type (bool, integer, real, enum, string), name, default
- * value, upper and lower bounds (if applicable); for strings, consider a
- * validation routine.
+ * (i) decide on a type (bool, ternary, integer, real, enum, string), name,
+ * default value, upper and lower bounds (if applicable); for strings,
+ * consider a validation routine.
* (ii) add a record below (or use add_<type>_reloption).
* (iii) add it to the appropriate options struct (perhaps StdRdOptions)
* (iv) add it to the appropriate handling routine (perhaps
@@ -147,15 +147,6 @@ static relopt_bool boolRelOpts[] =
},
false
},
- {
- {
- "vacuum_truncate",
- "Enables vacuum to truncate empty pages at the end of this table",
- RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
- ShareUpdateExclusiveLock
- },
- true
- },
{
{
"deduplicate_items",
@@ -170,6 +161,25 @@ static relopt_bool boolRelOpts[] =
{{NULL}}
};
+static relopt_ternary ternaryRelOpts[] =
+{
+ {
+ {
+ "vacuum_truncate",
+ "Enables vacuum to truncate empty pages at the end of this table",
+ RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
+ ShareUpdateExclusiveLock
+ },
+ TERNARY_UNSET
+ },
+ /* list terminator */
+ {
+ {
+ NULL
+ }
+ }
+};
+
static relopt_int intRelOpts[] =
{
{
@@ -609,6 +619,13 @@ initialize_reloptions(void)
boolRelOpts[i].gen.lockmode));
j++;
}
+ for (i = 0; ternaryRelOpts[i].gen.name; i++)
+ {
+ Assert(DoLockModesConflict(ternaryRelOpts[i].gen.lockmode,
+ ternaryRelOpts[i].gen.lockmode));
+ j++;
+ }
+
for (i = 0; intRelOpts[i].gen.name; i++)
{
Assert(DoLockModesConflict(intRelOpts[i].gen.lockmode,
@@ -649,6 +666,14 @@ initialize_reloptions(void)
j++;
}
+ for (i = 0; ternaryRelOpts[i].gen.name; i++)
+ {
+ relOpts[j] = &ternaryRelOpts[i].gen;
+ relOpts[j]->type = RELOPT_TYPE_TERNARY;
+ relOpts[j]->namelen = strlen(relOpts[j]->name);
+ j++;
+ }
+
for (i = 0; intRelOpts[i].gen.name; i++)
{
relOpts[j] = &intRelOpts[i].gen;
@@ -809,6 +834,9 @@ allocate_reloption(bits32 kinds, int type, const char *name, const char *desc,
case RELOPT_TYPE_BOOL:
size = sizeof(relopt_bool);
break;
+ case RELOPT_TYPE_TERNARY:
+ size = sizeof(relopt_ternary);
+ break;
case RELOPT_TYPE_INT:
size = sizeof(relopt_int);
break;
@@ -892,6 +920,57 @@ add_local_bool_reloption(local_relopts *relopts, const char *name,
add_local_reloption(relopts, (relopt_gen *) newoption, offset);
}
+/*
+ * init_ternary_reloption
+ * Allocate and initialize a new ternary reloption
+ */
+static relopt_ternary *
+init_ternary_reloption(bits32 kinds, const char *name, const char *desc,
+ pg_ternary default_val, LOCKMODE lockmode)
+{
+ relopt_ternary *newoption;
+
+ newoption = (relopt_ternary *)
+ allocate_reloption(kinds, RELOPT_TYPE_TERNARY, name, desc, lockmode);
+ newoption->default_val = default_val;
+
+ return newoption;
+}
+
+/*
+ * add_ternary_reloption
+ * Add a new ternary reloption
+ */
+void
+add_ternary_reloption(bits32 kinds, const char *name, const char *desc,
+ pg_ternary default_val, LOCKMODE lockmode)
+{
+ relopt_ternary *newoption;
+
+ newoption =
+ init_ternary_reloption(kinds, name, desc, default_val, lockmode);
+
+ add_reloption((relopt_gen *) newoption);
+}
+
+/*
+ * add_local_ternary_reloption
+ * Add a new ternary local reloption
+ *
+ * 'offset' is offset of ternary-typed field.
+ */
+void
+add_local_ternary_reloption(local_relopts *relopts, const char *name,
+ const char *desc, pg_ternary default_val,
+ int offset)
+{
+ relopt_ternary *newoption;
+
+ newoption =
+ init_ternary_reloption(RELOPT_KIND_LOCAL, name, desc, default_val, 0);
+
+ add_local_reloption(relopts, (relopt_gen *) newoption, offset);
+}
/*
* init_real_reloption
@@ -1626,6 +1705,19 @@ parse_one_reloption(relopt_value *option, char *text_str, int text_len,
option->gen->name, value)));
}
break;
+ case RELOPT_TYPE_TERNARY:
+ {
+ bool b;
+
+ parsed = parse_bool(value, &b);
+ option->values.ternary_val = b ? TERNARY_TRUE : TERNARY_FALSE;
+ if (validate && !parsed)
+ ereport(ERROR,
+ (errcode(ERRCODE_INVALID_PARAMETER_VALUE),
+ errmsg("invalid value for ternary option \"%s\": %s",
+ option->gen->name, value)));
+ }
+ break;
case RELOPT_TYPE_INT:
{
relopt_int *optint = (relopt_int *) option->gen;
@@ -1789,17 +1881,6 @@ fillRelOptions(void *rdopts, Size basesize,
char *itempos = ((char *) rdopts) + elems[j].offset;
char *string_val;
- /*
- * If isset_offset is provided, store whether the reloption is
- * set there.
- */
- if (elems[j].isset_offset > 0)
- {
- char *setpos = ((char *) rdopts) + elems[j].isset_offset;
-
- *(bool *) setpos = options[i].isset;
- }
-
switch (options[i].gen->type)
{
case RELOPT_TYPE_BOOL:
@@ -1807,6 +1888,11 @@ fillRelOptions(void *rdopts, Size basesize,
options[i].values.bool_val :
((relopt_bool *) options[i].gen)->default_val;
break;
+ case RELOPT_TYPE_TERNARY:
+ *(pg_ternary *) itempos = options[i].isset ?
+ options[i].values.ternary_val :
+ ((relopt_ternary *) options[i].gen)->default_val;
+ break;
case RELOPT_TYPE_INT:
*(int *) itempos = options[i].isset ?
options[i].values.int_val :
@@ -1923,8 +2009,8 @@ default_reloptions(Datum reloptions, bool validate, relopt_kind kind)
offsetof(StdRdOptions, parallel_workers)},
{"vacuum_index_cleanup", RELOPT_TYPE_ENUM,
offsetof(StdRdOptions, vacuum_index_cleanup)},
- {"vacuum_truncate", RELOPT_TYPE_BOOL,
- offsetof(StdRdOptions, vacuum_truncate), offsetof(StdRdOptions, vacuum_truncate_set)},
+ {"vacuum_truncate", RELOPT_TYPE_TERNARY,
+ offsetof(StdRdOptions, vacuum_truncate)},
{"vacuum_max_eager_freeze_failure_rate", RELOPT_TYPE_REAL,
offsetof(StdRdOptions, vacuum_max_eager_freeze_failure_rate)}
};
@@ -2004,7 +2090,6 @@ build_local_reloptions(local_relopts *relopts, Datum options, bool validate)
elems[i].optname = opt->option->name;
elems[i].opttype = opt->option->type;
elems[i].offset = opt->offset;
- elems[i].isset_offset = 0; /* not supported for local relopts yet */
i++;
}
diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c
index aa4fbec143f..696eab9bd97 100644
--- a/src/backend/commands/vacuum.c
+++ b/src/backend/commands/vacuum.c
@@ -2224,9 +2224,9 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams params,
{
StdRdOptions *opts = (StdRdOptions *) rel->rd_options;
- if (opts && opts->vacuum_truncate_set)
+ if (opts && opts->vacuum_truncate != TERNARY_UNSET)
{
- if (opts->vacuum_truncate)
+ if (opts->vacuum_truncate == TERNARY_TRUE)
params.truncate = VACOPTVALUE_ENABLED;
else
params.truncate = VACOPTVALUE_DISABLED;
diff --git a/src/include/access/reloptions.h b/src/include/access/reloptions.h
index 2f08e1b0cf0..dfbef2babf2 100644
--- a/src/include/access/reloptions.h
+++ b/src/include/access/reloptions.h
@@ -29,6 +29,7 @@
typedef enum relopt_type
{
RELOPT_TYPE_BOOL,
+ RELOPT_TYPE_TERNARY, /* on, off, unset */
RELOPT_TYPE_INT,
RELOPT_TYPE_REAL,
RELOPT_TYPE_ENUM,
@@ -80,6 +81,7 @@ typedef struct relopt_value
union
{
bool bool_val;
+ pg_ternary ternary_val;
int int_val;
double real_val;
int enum_val;
@@ -94,6 +96,12 @@ typedef struct relopt_bool
bool default_val;
} relopt_bool;
+typedef struct relopt_ternary
+{
+ relopt_gen gen;
+ int default_val;
+} relopt_ternary;
+
typedef struct relopt_int
{
relopt_gen gen;
@@ -152,19 +160,6 @@ typedef struct
const char *optname; /* option's name */
relopt_type opttype; /* option's datatype */
int offset; /* offset of field in result struct */
-
- /*
- * isset_offset is an optional offset of a field in the result struct that
- * stores whether the option is explicitly set for the relation or if it
- * just picked up the default value. In most cases, this can be
- * accomplished by giving the reloption a special out-of-range default
- * value (e.g., some integer reloptions use -2), but this isn't always
- * possible. For example, a Boolean reloption cannot be given an
- * out-of-range default, so we need another way to discover the source of
- * its value. This offset is only used if given a value greater than
- * zero.
- */
- int isset_offset;
} relopt_parse_elt;
/* Local reloption definition */
@@ -195,6 +190,9 @@ typedef struct local_relopts
extern relopt_kind add_reloption_kind(void);
extern void add_bool_reloption(bits32 kinds, const char *name, const char *desc,
bool default_val, LOCKMODE lockmode);
+extern void add_ternary_reloption(bits32 kinds, const char *name,
+ const char *desc, pg_ternary default_val,
+ LOCKMODE lockmode);
extern void add_int_reloption(bits32 kinds, const char *name, const char *desc,
int default_val, int min_val, int max_val,
LOCKMODE lockmode);
@@ -214,6 +212,9 @@ extern void register_reloptions_validator(local_relopts *relopts,
extern void add_local_bool_reloption(local_relopts *relopts, const char *name,
const char *desc, bool default_val,
int offset);
+extern void add_local_ternary_reloption(local_relopts *relopts,
+ const char *name, const char *desc,
+ pg_ternary default_val, int offset);
extern void add_local_int_reloption(local_relopts *relopts, const char *name,
const char *desc, int default_val,
int min_val, int max_val, int offset);
diff --git a/src/include/postgres.h b/src/include/postgres.h
index 7d93fbce709..47c88780776 100644
--- a/src/include/postgres.h
+++ b/src/include/postgres.h
@@ -543,6 +543,21 @@ Float8GetDatum(float8 X)
* ----------------------------------------------------------------
*/
+/*
+ * pg_ternary
+ * Boolean value with an extra "unset" value
+ *
+ * This enum can be used for values that want to distinguish between true,
+ * false, and unset.
+*/
+
+typedef enum pg_ternary
+{
+ TERNARY_FALSE = 0,
+ TERNARY_TRUE = 1,
+ TERNARY_UNSET = -1
+} pg_ternary;
+
/*
* NON_EXEC_STATIC: It's sometimes useful to define a variable or function
* that is normally static but extern when using EXEC_BACKEND (see
diff --git a/src/include/utils/rel.h b/src/include/utils/rel.h
index d03ab247788..5feb18a5373 100644
--- a/src/include/utils/rel.h
+++ b/src/include/utils/rel.h
@@ -347,8 +347,7 @@ typedef struct StdRdOptions
bool user_catalog_table; /* use as an additional catalog relation */
int parallel_workers; /* max number of parallel workers */
StdRdOptIndexCleanup vacuum_index_cleanup; /* controls index vacuuming */
- bool vacuum_truncate; /* enables vacuum to truncate a relation */
- bool vacuum_truncate_set; /* whether vacuum_truncate is set */
+ pg_ternary vacuum_truncate; /* enables vacuum to truncate a relation */
/*
* Fraction of pages in a relation that vacuum can eagerly scan and fail
diff --git a/src/test/regress/expected/reloptions.out b/src/test/regress/expected/reloptions.out
index 9de19b4e3f1..1c99f79ab01 100644
--- a/src/test/regress/expected/reloptions.out
+++ b/src/test/regress/expected/reloptions.out
@@ -98,6 +98,42 @@ SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass;
{fillfactor=13,autovacuum_enabled=false}
(1 row)
+-- Tests for future (FIXME) ternary options
+-- behave as boolean option: accept unassigned name and truncated value
+DROP TABLE reloptions_test;
+CREATE TABLE reloptions_test(i INT) WITH (vacuum_truncate);
+SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass;
+ reloptions
+------------------------
+ {vacuum_truncate=true}
+(1 row)
+
+DROP TABLE reloptions_test;
+CREATE TABLE reloptions_test(i INT) WITH (vacuum_truncate=FaLS);
+SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass;
+ reloptions
+------------------------
+ {vacuum_truncate=fals}
+(1 row)
+
+-- preferred "true" alias is used when storing
+DROP TABLE reloptions_test;
+CREATE TABLE reloptions_test(i INT) WITH (vacuum_index_cleanup=on);
+SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass;
+ reloptions
+---------------------------
+ {vacuum_index_cleanup=on}
+(1 row)
+
+-- custom "third" value is available
+DROP TABLE reloptions_test;
+CREATE TABLE reloptions_test(i INT) WITH (vacuum_index_cleanup=auto);
+SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass;
+ reloptions
+-----------------------------
+ {vacuum_index_cleanup=auto}
+(1 row)
+
-- Test vacuum_truncate option
DROP TABLE reloptions_test;
CREATE TEMP TABLE reloptions_test(i INT NOT NULL, j text)
diff --git a/src/test/regress/sql/reloptions.sql b/src/test/regress/sql/reloptions.sql
index 24fbe0b478d..f5980dafcbc 100644
--- a/src/test/regress/sql/reloptions.sql
+++ b/src/test/regress/sql/reloptions.sql
@@ -59,6 +59,27 @@ UPDATE pg_class
ALTER TABLE reloptions_test RESET (illegal_option);
SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass;
+-- Tests for future (FIXME) ternary options
+
+-- behave as boolean option: accept unassigned name and truncated value
+DROP TABLE reloptions_test;
+CREATE TABLE reloptions_test(i INT) WITH (vacuum_truncate);
+SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass;
+
+DROP TABLE reloptions_test;
+CREATE TABLE reloptions_test(i INT) WITH (vacuum_truncate=FaLS);
+SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass;
+
+-- preferred "true" alias is used when storing
+DROP TABLE reloptions_test;
+CREATE TABLE reloptions_test(i INT) WITH (vacuum_index_cleanup=on);
+SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass;
+
+-- custom "third" value is available
+DROP TABLE reloptions_test;
+CREATE TABLE reloptions_test(i INT) WITH (vacuum_index_cleanup=auto);
+SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass;
+
-- Test vacuum_truncate option
DROP TABLE reloptions_test;
diff --git a/src/tools/pgindent/typedefs.list b/src/tools/pgindent/typedefs.list
index 3f3a888fd0e..1c8610fd46c 100644
--- a/src/tools/pgindent/typedefs.list
+++ b/src/tools/pgindent/typedefs.list
@@ -3952,6 +3952,7 @@ pg_sha512_ctx
pg_snapshot
pg_special_case
pg_stack_base_t
+pg_ternary
pg_time_t
pg_time_usec_t
pg_tz
@@ -4079,6 +4080,7 @@ relopt_kind
relopt_parse_elt
relopt_real
relopt_string
+relopt_ternary
relopt_type
relopt_value
relopts_validator
--
2.47.3
--c36t3fndxhkzbl5z--
^ permalink raw reply [nested|flat] 868+ messages in thread
* [PATCH v3] Introduce ternary reloptions
@ 2026-01-16 15:04 Álvaro Herrera <[email protected]>
0 siblings, 0 replies; 868+ messages in thread
From: Álvaro Herrera @ 2026-01-16 15:04 UTC (permalink / raw)
Introduce ternary reloption as a replacement for current
`vacuum_truncate` implementation. Remove the `vacuum_truncate_set`
separate flag and use TERNARY_UNSET instead.
This could also be used for other options such as `vacuum_index_cleanup`
and `buffering`, but lets get the scaffolding in first.
Discussion: https://postgr.es/m/3474141.usfYGdeWWP@thinkpad-pgpro
---
src/backend/access/common/reloptions.c | 137 ++++++++++++++++++-----
src/backend/commands/vacuum.c | 4 +-
src/include/access/reloptions.h | 27 ++---
src/include/postgres.h | 15 +++
src/include/utils/rel.h | 3 +-
src/test/regress/expected/reloptions.out | 36 ++++++
src/test/regress/sql/reloptions.sql | 21 ++++
src/tools/pgindent/typedefs.list | 2 +
8 files changed, 202 insertions(+), 43 deletions(-)
diff --git a/src/backend/access/common/reloptions.c b/src/backend/access/common/reloptions.c
index 0b83f98ed5f..6f1f577581a 100644
--- a/src/backend/access/common/reloptions.c
+++ b/src/backend/access/common/reloptions.c
@@ -40,9 +40,9 @@
*
* To add an option:
*
- * (i) decide on a type (bool, integer, real, enum, string), name, default
- * value, upper and lower bounds (if applicable); for strings, consider a
- * validation routine.
+ * (i) decide on a type (bool, ternary, integer, real, enum, string), name,
+ * default value, upper and lower bounds (if applicable); for strings,
+ * consider a validation routine.
* (ii) add a record below (or use add_<type>_reloption).
* (iii) add it to the appropriate options struct (perhaps StdRdOptions)
* (iv) add it to the appropriate handling routine (perhaps
@@ -147,15 +147,6 @@ static relopt_bool boolRelOpts[] =
},
false
},
- {
- {
- "vacuum_truncate",
- "Enables vacuum to truncate empty pages at the end of this table",
- RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
- ShareUpdateExclusiveLock
- },
- true
- },
{
{
"deduplicate_items",
@@ -170,6 +161,25 @@ static relopt_bool boolRelOpts[] =
{{NULL}}
};
+static relopt_ternary ternaryRelOpts[] =
+{
+ {
+ {
+ "vacuum_truncate",
+ "Enables vacuum to truncate empty pages at the end of this table",
+ RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
+ ShareUpdateExclusiveLock
+ },
+ TERNARY_UNSET
+ },
+ /* list terminator */
+ {
+ {
+ NULL
+ }
+ }
+};
+
static relopt_int intRelOpts[] =
{
{
@@ -609,6 +619,13 @@ initialize_reloptions(void)
boolRelOpts[i].gen.lockmode));
j++;
}
+ for (i = 0; ternaryRelOpts[i].gen.name; i++)
+ {
+ Assert(DoLockModesConflict(ternaryRelOpts[i].gen.lockmode,
+ ternaryRelOpts[i].gen.lockmode));
+ j++;
+ }
+
for (i = 0; intRelOpts[i].gen.name; i++)
{
Assert(DoLockModesConflict(intRelOpts[i].gen.lockmode,
@@ -649,6 +666,14 @@ initialize_reloptions(void)
j++;
}
+ for (i = 0; ternaryRelOpts[i].gen.name; i++)
+ {
+ relOpts[j] = &ternaryRelOpts[i].gen;
+ relOpts[j]->type = RELOPT_TYPE_TERNARY;
+ relOpts[j]->namelen = strlen(relOpts[j]->name);
+ j++;
+ }
+
for (i = 0; intRelOpts[i].gen.name; i++)
{
relOpts[j] = &intRelOpts[i].gen;
@@ -809,6 +834,9 @@ allocate_reloption(bits32 kinds, int type, const char *name, const char *desc,
case RELOPT_TYPE_BOOL:
size = sizeof(relopt_bool);
break;
+ case RELOPT_TYPE_TERNARY:
+ size = sizeof(relopt_ternary);
+ break;
case RELOPT_TYPE_INT:
size = sizeof(relopt_int);
break;
@@ -892,6 +920,57 @@ add_local_bool_reloption(local_relopts *relopts, const char *name,
add_local_reloption(relopts, (relopt_gen *) newoption, offset);
}
+/*
+ * init_ternary_reloption
+ * Allocate and initialize a new ternary reloption
+ */
+static relopt_ternary *
+init_ternary_reloption(bits32 kinds, const char *name, const char *desc,
+ pg_ternary default_val, LOCKMODE lockmode)
+{
+ relopt_ternary *newoption;
+
+ newoption = (relopt_ternary *)
+ allocate_reloption(kinds, RELOPT_TYPE_TERNARY, name, desc, lockmode);
+ newoption->default_val = default_val;
+
+ return newoption;
+}
+
+/*
+ * add_ternary_reloption
+ * Add a new ternary reloption
+ */
+void
+add_ternary_reloption(bits32 kinds, const char *name, const char *desc,
+ pg_ternary default_val, LOCKMODE lockmode)
+{
+ relopt_ternary *newoption;
+
+ newoption =
+ init_ternary_reloption(kinds, name, desc, default_val, lockmode);
+
+ add_reloption((relopt_gen *) newoption);
+}
+
+/*
+ * add_local_ternary_reloption
+ * Add a new ternary local reloption
+ *
+ * 'offset' is offset of ternary-typed field.
+ */
+void
+add_local_ternary_reloption(local_relopts *relopts, const char *name,
+ const char *desc, pg_ternary default_val,
+ int offset)
+{
+ relopt_ternary *newoption;
+
+ newoption =
+ init_ternary_reloption(RELOPT_KIND_LOCAL, name, desc, default_val, 0);
+
+ add_local_reloption(relopts, (relopt_gen *) newoption, offset);
+}
/*
* init_real_reloption
@@ -1626,6 +1705,19 @@ parse_one_reloption(relopt_value *option, char *text_str, int text_len,
option->gen->name, value)));
}
break;
+ case RELOPT_TYPE_TERNARY:
+ {
+ bool b;
+
+ parsed = parse_bool(value, &b);
+ option->values.ternary_val = b ? TERNARY_TRUE : TERNARY_FALSE;
+ if (validate && !parsed)
+ ereport(ERROR,
+ (errcode(ERRCODE_INVALID_PARAMETER_VALUE),
+ errmsg("invalid value for ternary option \"%s\": %s",
+ option->gen->name, value)));
+ }
+ break;
case RELOPT_TYPE_INT:
{
relopt_int *optint = (relopt_int *) option->gen;
@@ -1789,17 +1881,6 @@ fillRelOptions(void *rdopts, Size basesize,
char *itempos = ((char *) rdopts) + elems[j].offset;
char *string_val;
- /*
- * If isset_offset is provided, store whether the reloption is
- * set there.
- */
- if (elems[j].isset_offset > 0)
- {
- char *setpos = ((char *) rdopts) + elems[j].isset_offset;
-
- *(bool *) setpos = options[i].isset;
- }
-
switch (options[i].gen->type)
{
case RELOPT_TYPE_BOOL:
@@ -1807,6 +1888,11 @@ fillRelOptions(void *rdopts, Size basesize,
options[i].values.bool_val :
((relopt_bool *) options[i].gen)->default_val;
break;
+ case RELOPT_TYPE_TERNARY:
+ *(pg_ternary *) itempos = options[i].isset ?
+ options[i].values.ternary_val :
+ ((relopt_ternary *) options[i].gen)->default_val;
+ break;
case RELOPT_TYPE_INT:
*(int *) itempos = options[i].isset ?
options[i].values.int_val :
@@ -1923,8 +2009,8 @@ default_reloptions(Datum reloptions, bool validate, relopt_kind kind)
offsetof(StdRdOptions, parallel_workers)},
{"vacuum_index_cleanup", RELOPT_TYPE_ENUM,
offsetof(StdRdOptions, vacuum_index_cleanup)},
- {"vacuum_truncate", RELOPT_TYPE_BOOL,
- offsetof(StdRdOptions, vacuum_truncate), offsetof(StdRdOptions, vacuum_truncate_set)},
+ {"vacuum_truncate", RELOPT_TYPE_TERNARY,
+ offsetof(StdRdOptions, vacuum_truncate)},
{"vacuum_max_eager_freeze_failure_rate", RELOPT_TYPE_REAL,
offsetof(StdRdOptions, vacuum_max_eager_freeze_failure_rate)}
};
@@ -2004,7 +2090,6 @@ build_local_reloptions(local_relopts *relopts, Datum options, bool validate)
elems[i].optname = opt->option->name;
elems[i].opttype = opt->option->type;
elems[i].offset = opt->offset;
- elems[i].isset_offset = 0; /* not supported for local relopts yet */
i++;
}
diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c
index aa4fbec143f..696eab9bd97 100644
--- a/src/backend/commands/vacuum.c
+++ b/src/backend/commands/vacuum.c
@@ -2224,9 +2224,9 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams params,
{
StdRdOptions *opts = (StdRdOptions *) rel->rd_options;
- if (opts && opts->vacuum_truncate_set)
+ if (opts && opts->vacuum_truncate != TERNARY_UNSET)
{
- if (opts->vacuum_truncate)
+ if (opts->vacuum_truncate == TERNARY_TRUE)
params.truncate = VACOPTVALUE_ENABLED;
else
params.truncate = VACOPTVALUE_DISABLED;
diff --git a/src/include/access/reloptions.h b/src/include/access/reloptions.h
index 2f08e1b0cf0..dfbef2babf2 100644
--- a/src/include/access/reloptions.h
+++ b/src/include/access/reloptions.h
@@ -29,6 +29,7 @@
typedef enum relopt_type
{
RELOPT_TYPE_BOOL,
+ RELOPT_TYPE_TERNARY, /* on, off, unset */
RELOPT_TYPE_INT,
RELOPT_TYPE_REAL,
RELOPT_TYPE_ENUM,
@@ -80,6 +81,7 @@ typedef struct relopt_value
union
{
bool bool_val;
+ pg_ternary ternary_val;
int int_val;
double real_val;
int enum_val;
@@ -94,6 +96,12 @@ typedef struct relopt_bool
bool default_val;
} relopt_bool;
+typedef struct relopt_ternary
+{
+ relopt_gen gen;
+ int default_val;
+} relopt_ternary;
+
typedef struct relopt_int
{
relopt_gen gen;
@@ -152,19 +160,6 @@ typedef struct
const char *optname; /* option's name */
relopt_type opttype; /* option's datatype */
int offset; /* offset of field in result struct */
-
- /*
- * isset_offset is an optional offset of a field in the result struct that
- * stores whether the option is explicitly set for the relation or if it
- * just picked up the default value. In most cases, this can be
- * accomplished by giving the reloption a special out-of-range default
- * value (e.g., some integer reloptions use -2), but this isn't always
- * possible. For example, a Boolean reloption cannot be given an
- * out-of-range default, so we need another way to discover the source of
- * its value. This offset is only used if given a value greater than
- * zero.
- */
- int isset_offset;
} relopt_parse_elt;
/* Local reloption definition */
@@ -195,6 +190,9 @@ typedef struct local_relopts
extern relopt_kind add_reloption_kind(void);
extern void add_bool_reloption(bits32 kinds, const char *name, const char *desc,
bool default_val, LOCKMODE lockmode);
+extern void add_ternary_reloption(bits32 kinds, const char *name,
+ const char *desc, pg_ternary default_val,
+ LOCKMODE lockmode);
extern void add_int_reloption(bits32 kinds, const char *name, const char *desc,
int default_val, int min_val, int max_val,
LOCKMODE lockmode);
@@ -214,6 +212,9 @@ extern void register_reloptions_validator(local_relopts *relopts,
extern void add_local_bool_reloption(local_relopts *relopts, const char *name,
const char *desc, bool default_val,
int offset);
+extern void add_local_ternary_reloption(local_relopts *relopts,
+ const char *name, const char *desc,
+ pg_ternary default_val, int offset);
extern void add_local_int_reloption(local_relopts *relopts, const char *name,
const char *desc, int default_val,
int min_val, int max_val, int offset);
diff --git a/src/include/postgres.h b/src/include/postgres.h
index 7d93fbce709..47c88780776 100644
--- a/src/include/postgres.h
+++ b/src/include/postgres.h
@@ -543,6 +543,21 @@ Float8GetDatum(float8 X)
* ----------------------------------------------------------------
*/
+/*
+ * pg_ternary
+ * Boolean value with an extra "unset" value
+ *
+ * This enum can be used for values that want to distinguish between true,
+ * false, and unset.
+*/
+
+typedef enum pg_ternary
+{
+ TERNARY_FALSE = 0,
+ TERNARY_TRUE = 1,
+ TERNARY_UNSET = -1
+} pg_ternary;
+
/*
* NON_EXEC_STATIC: It's sometimes useful to define a variable or function
* that is normally static but extern when using EXEC_BACKEND (see
diff --git a/src/include/utils/rel.h b/src/include/utils/rel.h
index d03ab247788..5feb18a5373 100644
--- a/src/include/utils/rel.h
+++ b/src/include/utils/rel.h
@@ -347,8 +347,7 @@ typedef struct StdRdOptions
bool user_catalog_table; /* use as an additional catalog relation */
int parallel_workers; /* max number of parallel workers */
StdRdOptIndexCleanup vacuum_index_cleanup; /* controls index vacuuming */
- bool vacuum_truncate; /* enables vacuum to truncate a relation */
- bool vacuum_truncate_set; /* whether vacuum_truncate is set */
+ pg_ternary vacuum_truncate; /* enables vacuum to truncate a relation */
/*
* Fraction of pages in a relation that vacuum can eagerly scan and fail
diff --git a/src/test/regress/expected/reloptions.out b/src/test/regress/expected/reloptions.out
index 9de19b4e3f1..1c99f79ab01 100644
--- a/src/test/regress/expected/reloptions.out
+++ b/src/test/regress/expected/reloptions.out
@@ -98,6 +98,42 @@ SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass;
{fillfactor=13,autovacuum_enabled=false}
(1 row)
+-- Tests for future (FIXME) ternary options
+-- behave as boolean option: accept unassigned name and truncated value
+DROP TABLE reloptions_test;
+CREATE TABLE reloptions_test(i INT) WITH (vacuum_truncate);
+SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass;
+ reloptions
+------------------------
+ {vacuum_truncate=true}
+(1 row)
+
+DROP TABLE reloptions_test;
+CREATE TABLE reloptions_test(i INT) WITH (vacuum_truncate=FaLS);
+SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass;
+ reloptions
+------------------------
+ {vacuum_truncate=fals}
+(1 row)
+
+-- preferred "true" alias is used when storing
+DROP TABLE reloptions_test;
+CREATE TABLE reloptions_test(i INT) WITH (vacuum_index_cleanup=on);
+SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass;
+ reloptions
+---------------------------
+ {vacuum_index_cleanup=on}
+(1 row)
+
+-- custom "third" value is available
+DROP TABLE reloptions_test;
+CREATE TABLE reloptions_test(i INT) WITH (vacuum_index_cleanup=auto);
+SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass;
+ reloptions
+-----------------------------
+ {vacuum_index_cleanup=auto}
+(1 row)
+
-- Test vacuum_truncate option
DROP TABLE reloptions_test;
CREATE TEMP TABLE reloptions_test(i INT NOT NULL, j text)
diff --git a/src/test/regress/sql/reloptions.sql b/src/test/regress/sql/reloptions.sql
index 24fbe0b478d..f5980dafcbc 100644
--- a/src/test/regress/sql/reloptions.sql
+++ b/src/test/regress/sql/reloptions.sql
@@ -59,6 +59,27 @@ UPDATE pg_class
ALTER TABLE reloptions_test RESET (illegal_option);
SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass;
+-- Tests for future (FIXME) ternary options
+
+-- behave as boolean option: accept unassigned name and truncated value
+DROP TABLE reloptions_test;
+CREATE TABLE reloptions_test(i INT) WITH (vacuum_truncate);
+SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass;
+
+DROP TABLE reloptions_test;
+CREATE TABLE reloptions_test(i INT) WITH (vacuum_truncate=FaLS);
+SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass;
+
+-- preferred "true" alias is used when storing
+DROP TABLE reloptions_test;
+CREATE TABLE reloptions_test(i INT) WITH (vacuum_index_cleanup=on);
+SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass;
+
+-- custom "third" value is available
+DROP TABLE reloptions_test;
+CREATE TABLE reloptions_test(i INT) WITH (vacuum_index_cleanup=auto);
+SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass;
+
-- Test vacuum_truncate option
DROP TABLE reloptions_test;
diff --git a/src/tools/pgindent/typedefs.list b/src/tools/pgindent/typedefs.list
index 3f3a888fd0e..1c8610fd46c 100644
--- a/src/tools/pgindent/typedefs.list
+++ b/src/tools/pgindent/typedefs.list
@@ -3952,6 +3952,7 @@ pg_sha512_ctx
pg_snapshot
pg_special_case
pg_stack_base_t
+pg_ternary
pg_time_t
pg_time_usec_t
pg_tz
@@ -4079,6 +4080,7 @@ relopt_kind
relopt_parse_elt
relopt_real
relopt_string
+relopt_ternary
relopt_type
relopt_value
relopts_validator
--
2.47.3
--c36t3fndxhkzbl5z--
^ permalink raw reply [nested|flat] 868+ messages in thread
* [PATCH v3] Introduce ternary reloptions
@ 2026-01-16 15:04 Álvaro Herrera <[email protected]>
0 siblings, 0 replies; 868+ messages in thread
From: Álvaro Herrera @ 2026-01-16 15:04 UTC (permalink / raw)
Introduce ternary reloption as a replacement for current
`vacuum_truncate` implementation. Remove the `vacuum_truncate_set`
separate flag and use TERNARY_UNSET instead.
This could also be used for other options such as `vacuum_index_cleanup`
and `buffering`, but lets get the scaffolding in first.
Discussion: https://postgr.es/m/3474141.usfYGdeWWP@thinkpad-pgpro
---
src/backend/access/common/reloptions.c | 137 ++++++++++++++++++-----
src/backend/commands/vacuum.c | 4 +-
src/include/access/reloptions.h | 27 ++---
src/include/postgres.h | 15 +++
src/include/utils/rel.h | 3 +-
src/test/regress/expected/reloptions.out | 36 ++++++
src/test/regress/sql/reloptions.sql | 21 ++++
src/tools/pgindent/typedefs.list | 2 +
8 files changed, 202 insertions(+), 43 deletions(-)
diff --git a/src/backend/access/common/reloptions.c b/src/backend/access/common/reloptions.c
index 0b83f98ed5f..6f1f577581a 100644
--- a/src/backend/access/common/reloptions.c
+++ b/src/backend/access/common/reloptions.c
@@ -40,9 +40,9 @@
*
* To add an option:
*
- * (i) decide on a type (bool, integer, real, enum, string), name, default
- * value, upper and lower bounds (if applicable); for strings, consider a
- * validation routine.
+ * (i) decide on a type (bool, ternary, integer, real, enum, string), name,
+ * default value, upper and lower bounds (if applicable); for strings,
+ * consider a validation routine.
* (ii) add a record below (or use add_<type>_reloption).
* (iii) add it to the appropriate options struct (perhaps StdRdOptions)
* (iv) add it to the appropriate handling routine (perhaps
@@ -147,15 +147,6 @@ static relopt_bool boolRelOpts[] =
},
false
},
- {
- {
- "vacuum_truncate",
- "Enables vacuum to truncate empty pages at the end of this table",
- RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
- ShareUpdateExclusiveLock
- },
- true
- },
{
{
"deduplicate_items",
@@ -170,6 +161,25 @@ static relopt_bool boolRelOpts[] =
{{NULL}}
};
+static relopt_ternary ternaryRelOpts[] =
+{
+ {
+ {
+ "vacuum_truncate",
+ "Enables vacuum to truncate empty pages at the end of this table",
+ RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
+ ShareUpdateExclusiveLock
+ },
+ TERNARY_UNSET
+ },
+ /* list terminator */
+ {
+ {
+ NULL
+ }
+ }
+};
+
static relopt_int intRelOpts[] =
{
{
@@ -609,6 +619,13 @@ initialize_reloptions(void)
boolRelOpts[i].gen.lockmode));
j++;
}
+ for (i = 0; ternaryRelOpts[i].gen.name; i++)
+ {
+ Assert(DoLockModesConflict(ternaryRelOpts[i].gen.lockmode,
+ ternaryRelOpts[i].gen.lockmode));
+ j++;
+ }
+
for (i = 0; intRelOpts[i].gen.name; i++)
{
Assert(DoLockModesConflict(intRelOpts[i].gen.lockmode,
@@ -649,6 +666,14 @@ initialize_reloptions(void)
j++;
}
+ for (i = 0; ternaryRelOpts[i].gen.name; i++)
+ {
+ relOpts[j] = &ternaryRelOpts[i].gen;
+ relOpts[j]->type = RELOPT_TYPE_TERNARY;
+ relOpts[j]->namelen = strlen(relOpts[j]->name);
+ j++;
+ }
+
for (i = 0; intRelOpts[i].gen.name; i++)
{
relOpts[j] = &intRelOpts[i].gen;
@@ -809,6 +834,9 @@ allocate_reloption(bits32 kinds, int type, const char *name, const char *desc,
case RELOPT_TYPE_BOOL:
size = sizeof(relopt_bool);
break;
+ case RELOPT_TYPE_TERNARY:
+ size = sizeof(relopt_ternary);
+ break;
case RELOPT_TYPE_INT:
size = sizeof(relopt_int);
break;
@@ -892,6 +920,57 @@ add_local_bool_reloption(local_relopts *relopts, const char *name,
add_local_reloption(relopts, (relopt_gen *) newoption, offset);
}
+/*
+ * init_ternary_reloption
+ * Allocate and initialize a new ternary reloption
+ */
+static relopt_ternary *
+init_ternary_reloption(bits32 kinds, const char *name, const char *desc,
+ pg_ternary default_val, LOCKMODE lockmode)
+{
+ relopt_ternary *newoption;
+
+ newoption = (relopt_ternary *)
+ allocate_reloption(kinds, RELOPT_TYPE_TERNARY, name, desc, lockmode);
+ newoption->default_val = default_val;
+
+ return newoption;
+}
+
+/*
+ * add_ternary_reloption
+ * Add a new ternary reloption
+ */
+void
+add_ternary_reloption(bits32 kinds, const char *name, const char *desc,
+ pg_ternary default_val, LOCKMODE lockmode)
+{
+ relopt_ternary *newoption;
+
+ newoption =
+ init_ternary_reloption(kinds, name, desc, default_val, lockmode);
+
+ add_reloption((relopt_gen *) newoption);
+}
+
+/*
+ * add_local_ternary_reloption
+ * Add a new ternary local reloption
+ *
+ * 'offset' is offset of ternary-typed field.
+ */
+void
+add_local_ternary_reloption(local_relopts *relopts, const char *name,
+ const char *desc, pg_ternary default_val,
+ int offset)
+{
+ relopt_ternary *newoption;
+
+ newoption =
+ init_ternary_reloption(RELOPT_KIND_LOCAL, name, desc, default_val, 0);
+
+ add_local_reloption(relopts, (relopt_gen *) newoption, offset);
+}
/*
* init_real_reloption
@@ -1626,6 +1705,19 @@ parse_one_reloption(relopt_value *option, char *text_str, int text_len,
option->gen->name, value)));
}
break;
+ case RELOPT_TYPE_TERNARY:
+ {
+ bool b;
+
+ parsed = parse_bool(value, &b);
+ option->values.ternary_val = b ? TERNARY_TRUE : TERNARY_FALSE;
+ if (validate && !parsed)
+ ereport(ERROR,
+ (errcode(ERRCODE_INVALID_PARAMETER_VALUE),
+ errmsg("invalid value for ternary option \"%s\": %s",
+ option->gen->name, value)));
+ }
+ break;
case RELOPT_TYPE_INT:
{
relopt_int *optint = (relopt_int *) option->gen;
@@ -1789,17 +1881,6 @@ fillRelOptions(void *rdopts, Size basesize,
char *itempos = ((char *) rdopts) + elems[j].offset;
char *string_val;
- /*
- * If isset_offset is provided, store whether the reloption is
- * set there.
- */
- if (elems[j].isset_offset > 0)
- {
- char *setpos = ((char *) rdopts) + elems[j].isset_offset;
-
- *(bool *) setpos = options[i].isset;
- }
-
switch (options[i].gen->type)
{
case RELOPT_TYPE_BOOL:
@@ -1807,6 +1888,11 @@ fillRelOptions(void *rdopts, Size basesize,
options[i].values.bool_val :
((relopt_bool *) options[i].gen)->default_val;
break;
+ case RELOPT_TYPE_TERNARY:
+ *(pg_ternary *) itempos = options[i].isset ?
+ options[i].values.ternary_val :
+ ((relopt_ternary *) options[i].gen)->default_val;
+ break;
case RELOPT_TYPE_INT:
*(int *) itempos = options[i].isset ?
options[i].values.int_val :
@@ -1923,8 +2009,8 @@ default_reloptions(Datum reloptions, bool validate, relopt_kind kind)
offsetof(StdRdOptions, parallel_workers)},
{"vacuum_index_cleanup", RELOPT_TYPE_ENUM,
offsetof(StdRdOptions, vacuum_index_cleanup)},
- {"vacuum_truncate", RELOPT_TYPE_BOOL,
- offsetof(StdRdOptions, vacuum_truncate), offsetof(StdRdOptions, vacuum_truncate_set)},
+ {"vacuum_truncate", RELOPT_TYPE_TERNARY,
+ offsetof(StdRdOptions, vacuum_truncate)},
{"vacuum_max_eager_freeze_failure_rate", RELOPT_TYPE_REAL,
offsetof(StdRdOptions, vacuum_max_eager_freeze_failure_rate)}
};
@@ -2004,7 +2090,6 @@ build_local_reloptions(local_relopts *relopts, Datum options, bool validate)
elems[i].optname = opt->option->name;
elems[i].opttype = opt->option->type;
elems[i].offset = opt->offset;
- elems[i].isset_offset = 0; /* not supported for local relopts yet */
i++;
}
diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c
index aa4fbec143f..696eab9bd97 100644
--- a/src/backend/commands/vacuum.c
+++ b/src/backend/commands/vacuum.c
@@ -2224,9 +2224,9 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams params,
{
StdRdOptions *opts = (StdRdOptions *) rel->rd_options;
- if (opts && opts->vacuum_truncate_set)
+ if (opts && opts->vacuum_truncate != TERNARY_UNSET)
{
- if (opts->vacuum_truncate)
+ if (opts->vacuum_truncate == TERNARY_TRUE)
params.truncate = VACOPTVALUE_ENABLED;
else
params.truncate = VACOPTVALUE_DISABLED;
diff --git a/src/include/access/reloptions.h b/src/include/access/reloptions.h
index 2f08e1b0cf0..dfbef2babf2 100644
--- a/src/include/access/reloptions.h
+++ b/src/include/access/reloptions.h
@@ -29,6 +29,7 @@
typedef enum relopt_type
{
RELOPT_TYPE_BOOL,
+ RELOPT_TYPE_TERNARY, /* on, off, unset */
RELOPT_TYPE_INT,
RELOPT_TYPE_REAL,
RELOPT_TYPE_ENUM,
@@ -80,6 +81,7 @@ typedef struct relopt_value
union
{
bool bool_val;
+ pg_ternary ternary_val;
int int_val;
double real_val;
int enum_val;
@@ -94,6 +96,12 @@ typedef struct relopt_bool
bool default_val;
} relopt_bool;
+typedef struct relopt_ternary
+{
+ relopt_gen gen;
+ int default_val;
+} relopt_ternary;
+
typedef struct relopt_int
{
relopt_gen gen;
@@ -152,19 +160,6 @@ typedef struct
const char *optname; /* option's name */
relopt_type opttype; /* option's datatype */
int offset; /* offset of field in result struct */
-
- /*
- * isset_offset is an optional offset of a field in the result struct that
- * stores whether the option is explicitly set for the relation or if it
- * just picked up the default value. In most cases, this can be
- * accomplished by giving the reloption a special out-of-range default
- * value (e.g., some integer reloptions use -2), but this isn't always
- * possible. For example, a Boolean reloption cannot be given an
- * out-of-range default, so we need another way to discover the source of
- * its value. This offset is only used if given a value greater than
- * zero.
- */
- int isset_offset;
} relopt_parse_elt;
/* Local reloption definition */
@@ -195,6 +190,9 @@ typedef struct local_relopts
extern relopt_kind add_reloption_kind(void);
extern void add_bool_reloption(bits32 kinds, const char *name, const char *desc,
bool default_val, LOCKMODE lockmode);
+extern void add_ternary_reloption(bits32 kinds, const char *name,
+ const char *desc, pg_ternary default_val,
+ LOCKMODE lockmode);
extern void add_int_reloption(bits32 kinds, const char *name, const char *desc,
int default_val, int min_val, int max_val,
LOCKMODE lockmode);
@@ -214,6 +212,9 @@ extern void register_reloptions_validator(local_relopts *relopts,
extern void add_local_bool_reloption(local_relopts *relopts, const char *name,
const char *desc, bool default_val,
int offset);
+extern void add_local_ternary_reloption(local_relopts *relopts,
+ const char *name, const char *desc,
+ pg_ternary default_val, int offset);
extern void add_local_int_reloption(local_relopts *relopts, const char *name,
const char *desc, int default_val,
int min_val, int max_val, int offset);
diff --git a/src/include/postgres.h b/src/include/postgres.h
index 7d93fbce709..47c88780776 100644
--- a/src/include/postgres.h
+++ b/src/include/postgres.h
@@ -543,6 +543,21 @@ Float8GetDatum(float8 X)
* ----------------------------------------------------------------
*/
+/*
+ * pg_ternary
+ * Boolean value with an extra "unset" value
+ *
+ * This enum can be used for values that want to distinguish between true,
+ * false, and unset.
+*/
+
+typedef enum pg_ternary
+{
+ TERNARY_FALSE = 0,
+ TERNARY_TRUE = 1,
+ TERNARY_UNSET = -1
+} pg_ternary;
+
/*
* NON_EXEC_STATIC: It's sometimes useful to define a variable or function
* that is normally static but extern when using EXEC_BACKEND (see
diff --git a/src/include/utils/rel.h b/src/include/utils/rel.h
index d03ab247788..5feb18a5373 100644
--- a/src/include/utils/rel.h
+++ b/src/include/utils/rel.h
@@ -347,8 +347,7 @@ typedef struct StdRdOptions
bool user_catalog_table; /* use as an additional catalog relation */
int parallel_workers; /* max number of parallel workers */
StdRdOptIndexCleanup vacuum_index_cleanup; /* controls index vacuuming */
- bool vacuum_truncate; /* enables vacuum to truncate a relation */
- bool vacuum_truncate_set; /* whether vacuum_truncate is set */
+ pg_ternary vacuum_truncate; /* enables vacuum to truncate a relation */
/*
* Fraction of pages in a relation that vacuum can eagerly scan and fail
diff --git a/src/test/regress/expected/reloptions.out b/src/test/regress/expected/reloptions.out
index 9de19b4e3f1..1c99f79ab01 100644
--- a/src/test/regress/expected/reloptions.out
+++ b/src/test/regress/expected/reloptions.out
@@ -98,6 +98,42 @@ SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass;
{fillfactor=13,autovacuum_enabled=false}
(1 row)
+-- Tests for future (FIXME) ternary options
+-- behave as boolean option: accept unassigned name and truncated value
+DROP TABLE reloptions_test;
+CREATE TABLE reloptions_test(i INT) WITH (vacuum_truncate);
+SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass;
+ reloptions
+------------------------
+ {vacuum_truncate=true}
+(1 row)
+
+DROP TABLE reloptions_test;
+CREATE TABLE reloptions_test(i INT) WITH (vacuum_truncate=FaLS);
+SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass;
+ reloptions
+------------------------
+ {vacuum_truncate=fals}
+(1 row)
+
+-- preferred "true" alias is used when storing
+DROP TABLE reloptions_test;
+CREATE TABLE reloptions_test(i INT) WITH (vacuum_index_cleanup=on);
+SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass;
+ reloptions
+---------------------------
+ {vacuum_index_cleanup=on}
+(1 row)
+
+-- custom "third" value is available
+DROP TABLE reloptions_test;
+CREATE TABLE reloptions_test(i INT) WITH (vacuum_index_cleanup=auto);
+SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass;
+ reloptions
+-----------------------------
+ {vacuum_index_cleanup=auto}
+(1 row)
+
-- Test vacuum_truncate option
DROP TABLE reloptions_test;
CREATE TEMP TABLE reloptions_test(i INT NOT NULL, j text)
diff --git a/src/test/regress/sql/reloptions.sql b/src/test/regress/sql/reloptions.sql
index 24fbe0b478d..f5980dafcbc 100644
--- a/src/test/regress/sql/reloptions.sql
+++ b/src/test/regress/sql/reloptions.sql
@@ -59,6 +59,27 @@ UPDATE pg_class
ALTER TABLE reloptions_test RESET (illegal_option);
SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass;
+-- Tests for future (FIXME) ternary options
+
+-- behave as boolean option: accept unassigned name and truncated value
+DROP TABLE reloptions_test;
+CREATE TABLE reloptions_test(i INT) WITH (vacuum_truncate);
+SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass;
+
+DROP TABLE reloptions_test;
+CREATE TABLE reloptions_test(i INT) WITH (vacuum_truncate=FaLS);
+SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass;
+
+-- preferred "true" alias is used when storing
+DROP TABLE reloptions_test;
+CREATE TABLE reloptions_test(i INT) WITH (vacuum_index_cleanup=on);
+SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass;
+
+-- custom "third" value is available
+DROP TABLE reloptions_test;
+CREATE TABLE reloptions_test(i INT) WITH (vacuum_index_cleanup=auto);
+SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass;
+
-- Test vacuum_truncate option
DROP TABLE reloptions_test;
diff --git a/src/tools/pgindent/typedefs.list b/src/tools/pgindent/typedefs.list
index 3f3a888fd0e..1c8610fd46c 100644
--- a/src/tools/pgindent/typedefs.list
+++ b/src/tools/pgindent/typedefs.list
@@ -3952,6 +3952,7 @@ pg_sha512_ctx
pg_snapshot
pg_special_case
pg_stack_base_t
+pg_ternary
pg_time_t
pg_time_usec_t
pg_tz
@@ -4079,6 +4080,7 @@ relopt_kind
relopt_parse_elt
relopt_real
relopt_string
+relopt_ternary
relopt_type
relopt_value
relopts_validator
--
2.47.3
--c36t3fndxhkzbl5z--
^ permalink raw reply [nested|flat] 868+ messages in thread
* [PATCH v3] Introduce ternary reloptions
@ 2026-01-16 15:04 Álvaro Herrera <[email protected]>
0 siblings, 0 replies; 868+ messages in thread
From: Álvaro Herrera @ 2026-01-16 15:04 UTC (permalink / raw)
Introduce ternary reloption as a replacement for current
`vacuum_truncate` implementation. Remove the `vacuum_truncate_set`
separate flag and use TERNARY_UNSET instead.
This could also be used for other options such as `vacuum_index_cleanup`
and `buffering`, but lets get the scaffolding in first.
Discussion: https://postgr.es/m/3474141.usfYGdeWWP@thinkpad-pgpro
---
src/backend/access/common/reloptions.c | 137 ++++++++++++++++++-----
src/backend/commands/vacuum.c | 4 +-
src/include/access/reloptions.h | 27 ++---
src/include/postgres.h | 15 +++
src/include/utils/rel.h | 3 +-
src/test/regress/expected/reloptions.out | 36 ++++++
src/test/regress/sql/reloptions.sql | 21 ++++
src/tools/pgindent/typedefs.list | 2 +
8 files changed, 202 insertions(+), 43 deletions(-)
diff --git a/src/backend/access/common/reloptions.c b/src/backend/access/common/reloptions.c
index 0b83f98ed5f..6f1f577581a 100644
--- a/src/backend/access/common/reloptions.c
+++ b/src/backend/access/common/reloptions.c
@@ -40,9 +40,9 @@
*
* To add an option:
*
- * (i) decide on a type (bool, integer, real, enum, string), name, default
- * value, upper and lower bounds (if applicable); for strings, consider a
- * validation routine.
+ * (i) decide on a type (bool, ternary, integer, real, enum, string), name,
+ * default value, upper and lower bounds (if applicable); for strings,
+ * consider a validation routine.
* (ii) add a record below (or use add_<type>_reloption).
* (iii) add it to the appropriate options struct (perhaps StdRdOptions)
* (iv) add it to the appropriate handling routine (perhaps
@@ -147,15 +147,6 @@ static relopt_bool boolRelOpts[] =
},
false
},
- {
- {
- "vacuum_truncate",
- "Enables vacuum to truncate empty pages at the end of this table",
- RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
- ShareUpdateExclusiveLock
- },
- true
- },
{
{
"deduplicate_items",
@@ -170,6 +161,25 @@ static relopt_bool boolRelOpts[] =
{{NULL}}
};
+static relopt_ternary ternaryRelOpts[] =
+{
+ {
+ {
+ "vacuum_truncate",
+ "Enables vacuum to truncate empty pages at the end of this table",
+ RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
+ ShareUpdateExclusiveLock
+ },
+ TERNARY_UNSET
+ },
+ /* list terminator */
+ {
+ {
+ NULL
+ }
+ }
+};
+
static relopt_int intRelOpts[] =
{
{
@@ -609,6 +619,13 @@ initialize_reloptions(void)
boolRelOpts[i].gen.lockmode));
j++;
}
+ for (i = 0; ternaryRelOpts[i].gen.name; i++)
+ {
+ Assert(DoLockModesConflict(ternaryRelOpts[i].gen.lockmode,
+ ternaryRelOpts[i].gen.lockmode));
+ j++;
+ }
+
for (i = 0; intRelOpts[i].gen.name; i++)
{
Assert(DoLockModesConflict(intRelOpts[i].gen.lockmode,
@@ -649,6 +666,14 @@ initialize_reloptions(void)
j++;
}
+ for (i = 0; ternaryRelOpts[i].gen.name; i++)
+ {
+ relOpts[j] = &ternaryRelOpts[i].gen;
+ relOpts[j]->type = RELOPT_TYPE_TERNARY;
+ relOpts[j]->namelen = strlen(relOpts[j]->name);
+ j++;
+ }
+
for (i = 0; intRelOpts[i].gen.name; i++)
{
relOpts[j] = &intRelOpts[i].gen;
@@ -809,6 +834,9 @@ allocate_reloption(bits32 kinds, int type, const char *name, const char *desc,
case RELOPT_TYPE_BOOL:
size = sizeof(relopt_bool);
break;
+ case RELOPT_TYPE_TERNARY:
+ size = sizeof(relopt_ternary);
+ break;
case RELOPT_TYPE_INT:
size = sizeof(relopt_int);
break;
@@ -892,6 +920,57 @@ add_local_bool_reloption(local_relopts *relopts, const char *name,
add_local_reloption(relopts, (relopt_gen *) newoption, offset);
}
+/*
+ * init_ternary_reloption
+ * Allocate and initialize a new ternary reloption
+ */
+static relopt_ternary *
+init_ternary_reloption(bits32 kinds, const char *name, const char *desc,
+ pg_ternary default_val, LOCKMODE lockmode)
+{
+ relopt_ternary *newoption;
+
+ newoption = (relopt_ternary *)
+ allocate_reloption(kinds, RELOPT_TYPE_TERNARY, name, desc, lockmode);
+ newoption->default_val = default_val;
+
+ return newoption;
+}
+
+/*
+ * add_ternary_reloption
+ * Add a new ternary reloption
+ */
+void
+add_ternary_reloption(bits32 kinds, const char *name, const char *desc,
+ pg_ternary default_val, LOCKMODE lockmode)
+{
+ relopt_ternary *newoption;
+
+ newoption =
+ init_ternary_reloption(kinds, name, desc, default_val, lockmode);
+
+ add_reloption((relopt_gen *) newoption);
+}
+
+/*
+ * add_local_ternary_reloption
+ * Add a new ternary local reloption
+ *
+ * 'offset' is offset of ternary-typed field.
+ */
+void
+add_local_ternary_reloption(local_relopts *relopts, const char *name,
+ const char *desc, pg_ternary default_val,
+ int offset)
+{
+ relopt_ternary *newoption;
+
+ newoption =
+ init_ternary_reloption(RELOPT_KIND_LOCAL, name, desc, default_val, 0);
+
+ add_local_reloption(relopts, (relopt_gen *) newoption, offset);
+}
/*
* init_real_reloption
@@ -1626,6 +1705,19 @@ parse_one_reloption(relopt_value *option, char *text_str, int text_len,
option->gen->name, value)));
}
break;
+ case RELOPT_TYPE_TERNARY:
+ {
+ bool b;
+
+ parsed = parse_bool(value, &b);
+ option->values.ternary_val = b ? TERNARY_TRUE : TERNARY_FALSE;
+ if (validate && !parsed)
+ ereport(ERROR,
+ (errcode(ERRCODE_INVALID_PARAMETER_VALUE),
+ errmsg("invalid value for ternary option \"%s\": %s",
+ option->gen->name, value)));
+ }
+ break;
case RELOPT_TYPE_INT:
{
relopt_int *optint = (relopt_int *) option->gen;
@@ -1789,17 +1881,6 @@ fillRelOptions(void *rdopts, Size basesize,
char *itempos = ((char *) rdopts) + elems[j].offset;
char *string_val;
- /*
- * If isset_offset is provided, store whether the reloption is
- * set there.
- */
- if (elems[j].isset_offset > 0)
- {
- char *setpos = ((char *) rdopts) + elems[j].isset_offset;
-
- *(bool *) setpos = options[i].isset;
- }
-
switch (options[i].gen->type)
{
case RELOPT_TYPE_BOOL:
@@ -1807,6 +1888,11 @@ fillRelOptions(void *rdopts, Size basesize,
options[i].values.bool_val :
((relopt_bool *) options[i].gen)->default_val;
break;
+ case RELOPT_TYPE_TERNARY:
+ *(pg_ternary *) itempos = options[i].isset ?
+ options[i].values.ternary_val :
+ ((relopt_ternary *) options[i].gen)->default_val;
+ break;
case RELOPT_TYPE_INT:
*(int *) itempos = options[i].isset ?
options[i].values.int_val :
@@ -1923,8 +2009,8 @@ default_reloptions(Datum reloptions, bool validate, relopt_kind kind)
offsetof(StdRdOptions, parallel_workers)},
{"vacuum_index_cleanup", RELOPT_TYPE_ENUM,
offsetof(StdRdOptions, vacuum_index_cleanup)},
- {"vacuum_truncate", RELOPT_TYPE_BOOL,
- offsetof(StdRdOptions, vacuum_truncate), offsetof(StdRdOptions, vacuum_truncate_set)},
+ {"vacuum_truncate", RELOPT_TYPE_TERNARY,
+ offsetof(StdRdOptions, vacuum_truncate)},
{"vacuum_max_eager_freeze_failure_rate", RELOPT_TYPE_REAL,
offsetof(StdRdOptions, vacuum_max_eager_freeze_failure_rate)}
};
@@ -2004,7 +2090,6 @@ build_local_reloptions(local_relopts *relopts, Datum options, bool validate)
elems[i].optname = opt->option->name;
elems[i].opttype = opt->option->type;
elems[i].offset = opt->offset;
- elems[i].isset_offset = 0; /* not supported for local relopts yet */
i++;
}
diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c
index aa4fbec143f..696eab9bd97 100644
--- a/src/backend/commands/vacuum.c
+++ b/src/backend/commands/vacuum.c
@@ -2224,9 +2224,9 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams params,
{
StdRdOptions *opts = (StdRdOptions *) rel->rd_options;
- if (opts && opts->vacuum_truncate_set)
+ if (opts && opts->vacuum_truncate != TERNARY_UNSET)
{
- if (opts->vacuum_truncate)
+ if (opts->vacuum_truncate == TERNARY_TRUE)
params.truncate = VACOPTVALUE_ENABLED;
else
params.truncate = VACOPTVALUE_DISABLED;
diff --git a/src/include/access/reloptions.h b/src/include/access/reloptions.h
index 2f08e1b0cf0..dfbef2babf2 100644
--- a/src/include/access/reloptions.h
+++ b/src/include/access/reloptions.h
@@ -29,6 +29,7 @@
typedef enum relopt_type
{
RELOPT_TYPE_BOOL,
+ RELOPT_TYPE_TERNARY, /* on, off, unset */
RELOPT_TYPE_INT,
RELOPT_TYPE_REAL,
RELOPT_TYPE_ENUM,
@@ -80,6 +81,7 @@ typedef struct relopt_value
union
{
bool bool_val;
+ pg_ternary ternary_val;
int int_val;
double real_val;
int enum_val;
@@ -94,6 +96,12 @@ typedef struct relopt_bool
bool default_val;
} relopt_bool;
+typedef struct relopt_ternary
+{
+ relopt_gen gen;
+ int default_val;
+} relopt_ternary;
+
typedef struct relopt_int
{
relopt_gen gen;
@@ -152,19 +160,6 @@ typedef struct
const char *optname; /* option's name */
relopt_type opttype; /* option's datatype */
int offset; /* offset of field in result struct */
-
- /*
- * isset_offset is an optional offset of a field in the result struct that
- * stores whether the option is explicitly set for the relation or if it
- * just picked up the default value. In most cases, this can be
- * accomplished by giving the reloption a special out-of-range default
- * value (e.g., some integer reloptions use -2), but this isn't always
- * possible. For example, a Boolean reloption cannot be given an
- * out-of-range default, so we need another way to discover the source of
- * its value. This offset is only used if given a value greater than
- * zero.
- */
- int isset_offset;
} relopt_parse_elt;
/* Local reloption definition */
@@ -195,6 +190,9 @@ typedef struct local_relopts
extern relopt_kind add_reloption_kind(void);
extern void add_bool_reloption(bits32 kinds, const char *name, const char *desc,
bool default_val, LOCKMODE lockmode);
+extern void add_ternary_reloption(bits32 kinds, const char *name,
+ const char *desc, pg_ternary default_val,
+ LOCKMODE lockmode);
extern void add_int_reloption(bits32 kinds, const char *name, const char *desc,
int default_val, int min_val, int max_val,
LOCKMODE lockmode);
@@ -214,6 +212,9 @@ extern void register_reloptions_validator(local_relopts *relopts,
extern void add_local_bool_reloption(local_relopts *relopts, const char *name,
const char *desc, bool default_val,
int offset);
+extern void add_local_ternary_reloption(local_relopts *relopts,
+ const char *name, const char *desc,
+ pg_ternary default_val, int offset);
extern void add_local_int_reloption(local_relopts *relopts, const char *name,
const char *desc, int default_val,
int min_val, int max_val, int offset);
diff --git a/src/include/postgres.h b/src/include/postgres.h
index 7d93fbce709..47c88780776 100644
--- a/src/include/postgres.h
+++ b/src/include/postgres.h
@@ -543,6 +543,21 @@ Float8GetDatum(float8 X)
* ----------------------------------------------------------------
*/
+/*
+ * pg_ternary
+ * Boolean value with an extra "unset" value
+ *
+ * This enum can be used for values that want to distinguish between true,
+ * false, and unset.
+*/
+
+typedef enum pg_ternary
+{
+ TERNARY_FALSE = 0,
+ TERNARY_TRUE = 1,
+ TERNARY_UNSET = -1
+} pg_ternary;
+
/*
* NON_EXEC_STATIC: It's sometimes useful to define a variable or function
* that is normally static but extern when using EXEC_BACKEND (see
diff --git a/src/include/utils/rel.h b/src/include/utils/rel.h
index d03ab247788..5feb18a5373 100644
--- a/src/include/utils/rel.h
+++ b/src/include/utils/rel.h
@@ -347,8 +347,7 @@ typedef struct StdRdOptions
bool user_catalog_table; /* use as an additional catalog relation */
int parallel_workers; /* max number of parallel workers */
StdRdOptIndexCleanup vacuum_index_cleanup; /* controls index vacuuming */
- bool vacuum_truncate; /* enables vacuum to truncate a relation */
- bool vacuum_truncate_set; /* whether vacuum_truncate is set */
+ pg_ternary vacuum_truncate; /* enables vacuum to truncate a relation */
/*
* Fraction of pages in a relation that vacuum can eagerly scan and fail
diff --git a/src/test/regress/expected/reloptions.out b/src/test/regress/expected/reloptions.out
index 9de19b4e3f1..1c99f79ab01 100644
--- a/src/test/regress/expected/reloptions.out
+++ b/src/test/regress/expected/reloptions.out
@@ -98,6 +98,42 @@ SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass;
{fillfactor=13,autovacuum_enabled=false}
(1 row)
+-- Tests for future (FIXME) ternary options
+-- behave as boolean option: accept unassigned name and truncated value
+DROP TABLE reloptions_test;
+CREATE TABLE reloptions_test(i INT) WITH (vacuum_truncate);
+SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass;
+ reloptions
+------------------------
+ {vacuum_truncate=true}
+(1 row)
+
+DROP TABLE reloptions_test;
+CREATE TABLE reloptions_test(i INT) WITH (vacuum_truncate=FaLS);
+SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass;
+ reloptions
+------------------------
+ {vacuum_truncate=fals}
+(1 row)
+
+-- preferred "true" alias is used when storing
+DROP TABLE reloptions_test;
+CREATE TABLE reloptions_test(i INT) WITH (vacuum_index_cleanup=on);
+SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass;
+ reloptions
+---------------------------
+ {vacuum_index_cleanup=on}
+(1 row)
+
+-- custom "third" value is available
+DROP TABLE reloptions_test;
+CREATE TABLE reloptions_test(i INT) WITH (vacuum_index_cleanup=auto);
+SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass;
+ reloptions
+-----------------------------
+ {vacuum_index_cleanup=auto}
+(1 row)
+
-- Test vacuum_truncate option
DROP TABLE reloptions_test;
CREATE TEMP TABLE reloptions_test(i INT NOT NULL, j text)
diff --git a/src/test/regress/sql/reloptions.sql b/src/test/regress/sql/reloptions.sql
index 24fbe0b478d..f5980dafcbc 100644
--- a/src/test/regress/sql/reloptions.sql
+++ b/src/test/regress/sql/reloptions.sql
@@ -59,6 +59,27 @@ UPDATE pg_class
ALTER TABLE reloptions_test RESET (illegal_option);
SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass;
+-- Tests for future (FIXME) ternary options
+
+-- behave as boolean option: accept unassigned name and truncated value
+DROP TABLE reloptions_test;
+CREATE TABLE reloptions_test(i INT) WITH (vacuum_truncate);
+SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass;
+
+DROP TABLE reloptions_test;
+CREATE TABLE reloptions_test(i INT) WITH (vacuum_truncate=FaLS);
+SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass;
+
+-- preferred "true" alias is used when storing
+DROP TABLE reloptions_test;
+CREATE TABLE reloptions_test(i INT) WITH (vacuum_index_cleanup=on);
+SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass;
+
+-- custom "third" value is available
+DROP TABLE reloptions_test;
+CREATE TABLE reloptions_test(i INT) WITH (vacuum_index_cleanup=auto);
+SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass;
+
-- Test vacuum_truncate option
DROP TABLE reloptions_test;
diff --git a/src/tools/pgindent/typedefs.list b/src/tools/pgindent/typedefs.list
index 3f3a888fd0e..1c8610fd46c 100644
--- a/src/tools/pgindent/typedefs.list
+++ b/src/tools/pgindent/typedefs.list
@@ -3952,6 +3952,7 @@ pg_sha512_ctx
pg_snapshot
pg_special_case
pg_stack_base_t
+pg_ternary
pg_time_t
pg_time_usec_t
pg_tz
@@ -4079,6 +4080,7 @@ relopt_kind
relopt_parse_elt
relopt_real
relopt_string
+relopt_ternary
relopt_type
relopt_value
relopts_validator
--
2.47.3
--c36t3fndxhkzbl5z--
^ permalink raw reply [nested|flat] 868+ messages in thread
* [PATCH v3] Introduce ternary reloptions
@ 2026-01-16 15:04 Álvaro Herrera <[email protected]>
0 siblings, 0 replies; 868+ messages in thread
From: Álvaro Herrera @ 2026-01-16 15:04 UTC (permalink / raw)
Introduce ternary reloption as a replacement for current
`vacuum_truncate` implementation. Remove the `vacuum_truncate_set`
separate flag and use TERNARY_UNSET instead.
This could also be used for other options such as `vacuum_index_cleanup`
and `buffering`, but lets get the scaffolding in first.
Discussion: https://postgr.es/m/3474141.usfYGdeWWP@thinkpad-pgpro
---
src/backend/access/common/reloptions.c | 137 ++++++++++++++++++-----
src/backend/commands/vacuum.c | 4 +-
src/include/access/reloptions.h | 27 ++---
src/include/postgres.h | 15 +++
src/include/utils/rel.h | 3 +-
src/test/regress/expected/reloptions.out | 36 ++++++
src/test/regress/sql/reloptions.sql | 21 ++++
src/tools/pgindent/typedefs.list | 2 +
8 files changed, 202 insertions(+), 43 deletions(-)
diff --git a/src/backend/access/common/reloptions.c b/src/backend/access/common/reloptions.c
index 0b83f98ed5f..6f1f577581a 100644
--- a/src/backend/access/common/reloptions.c
+++ b/src/backend/access/common/reloptions.c
@@ -40,9 +40,9 @@
*
* To add an option:
*
- * (i) decide on a type (bool, integer, real, enum, string), name, default
- * value, upper and lower bounds (if applicable); for strings, consider a
- * validation routine.
+ * (i) decide on a type (bool, ternary, integer, real, enum, string), name,
+ * default value, upper and lower bounds (if applicable); for strings,
+ * consider a validation routine.
* (ii) add a record below (or use add_<type>_reloption).
* (iii) add it to the appropriate options struct (perhaps StdRdOptions)
* (iv) add it to the appropriate handling routine (perhaps
@@ -147,15 +147,6 @@ static relopt_bool boolRelOpts[] =
},
false
},
- {
- {
- "vacuum_truncate",
- "Enables vacuum to truncate empty pages at the end of this table",
- RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
- ShareUpdateExclusiveLock
- },
- true
- },
{
{
"deduplicate_items",
@@ -170,6 +161,25 @@ static relopt_bool boolRelOpts[] =
{{NULL}}
};
+static relopt_ternary ternaryRelOpts[] =
+{
+ {
+ {
+ "vacuum_truncate",
+ "Enables vacuum to truncate empty pages at the end of this table",
+ RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
+ ShareUpdateExclusiveLock
+ },
+ TERNARY_UNSET
+ },
+ /* list terminator */
+ {
+ {
+ NULL
+ }
+ }
+};
+
static relopt_int intRelOpts[] =
{
{
@@ -609,6 +619,13 @@ initialize_reloptions(void)
boolRelOpts[i].gen.lockmode));
j++;
}
+ for (i = 0; ternaryRelOpts[i].gen.name; i++)
+ {
+ Assert(DoLockModesConflict(ternaryRelOpts[i].gen.lockmode,
+ ternaryRelOpts[i].gen.lockmode));
+ j++;
+ }
+
for (i = 0; intRelOpts[i].gen.name; i++)
{
Assert(DoLockModesConflict(intRelOpts[i].gen.lockmode,
@@ -649,6 +666,14 @@ initialize_reloptions(void)
j++;
}
+ for (i = 0; ternaryRelOpts[i].gen.name; i++)
+ {
+ relOpts[j] = &ternaryRelOpts[i].gen;
+ relOpts[j]->type = RELOPT_TYPE_TERNARY;
+ relOpts[j]->namelen = strlen(relOpts[j]->name);
+ j++;
+ }
+
for (i = 0; intRelOpts[i].gen.name; i++)
{
relOpts[j] = &intRelOpts[i].gen;
@@ -809,6 +834,9 @@ allocate_reloption(bits32 kinds, int type, const char *name, const char *desc,
case RELOPT_TYPE_BOOL:
size = sizeof(relopt_bool);
break;
+ case RELOPT_TYPE_TERNARY:
+ size = sizeof(relopt_ternary);
+ break;
case RELOPT_TYPE_INT:
size = sizeof(relopt_int);
break;
@@ -892,6 +920,57 @@ add_local_bool_reloption(local_relopts *relopts, const char *name,
add_local_reloption(relopts, (relopt_gen *) newoption, offset);
}
+/*
+ * init_ternary_reloption
+ * Allocate and initialize a new ternary reloption
+ */
+static relopt_ternary *
+init_ternary_reloption(bits32 kinds, const char *name, const char *desc,
+ pg_ternary default_val, LOCKMODE lockmode)
+{
+ relopt_ternary *newoption;
+
+ newoption = (relopt_ternary *)
+ allocate_reloption(kinds, RELOPT_TYPE_TERNARY, name, desc, lockmode);
+ newoption->default_val = default_val;
+
+ return newoption;
+}
+
+/*
+ * add_ternary_reloption
+ * Add a new ternary reloption
+ */
+void
+add_ternary_reloption(bits32 kinds, const char *name, const char *desc,
+ pg_ternary default_val, LOCKMODE lockmode)
+{
+ relopt_ternary *newoption;
+
+ newoption =
+ init_ternary_reloption(kinds, name, desc, default_val, lockmode);
+
+ add_reloption((relopt_gen *) newoption);
+}
+
+/*
+ * add_local_ternary_reloption
+ * Add a new ternary local reloption
+ *
+ * 'offset' is offset of ternary-typed field.
+ */
+void
+add_local_ternary_reloption(local_relopts *relopts, const char *name,
+ const char *desc, pg_ternary default_val,
+ int offset)
+{
+ relopt_ternary *newoption;
+
+ newoption =
+ init_ternary_reloption(RELOPT_KIND_LOCAL, name, desc, default_val, 0);
+
+ add_local_reloption(relopts, (relopt_gen *) newoption, offset);
+}
/*
* init_real_reloption
@@ -1626,6 +1705,19 @@ parse_one_reloption(relopt_value *option, char *text_str, int text_len,
option->gen->name, value)));
}
break;
+ case RELOPT_TYPE_TERNARY:
+ {
+ bool b;
+
+ parsed = parse_bool(value, &b);
+ option->values.ternary_val = b ? TERNARY_TRUE : TERNARY_FALSE;
+ if (validate && !parsed)
+ ereport(ERROR,
+ (errcode(ERRCODE_INVALID_PARAMETER_VALUE),
+ errmsg("invalid value for ternary option \"%s\": %s",
+ option->gen->name, value)));
+ }
+ break;
case RELOPT_TYPE_INT:
{
relopt_int *optint = (relopt_int *) option->gen;
@@ -1789,17 +1881,6 @@ fillRelOptions(void *rdopts, Size basesize,
char *itempos = ((char *) rdopts) + elems[j].offset;
char *string_val;
- /*
- * If isset_offset is provided, store whether the reloption is
- * set there.
- */
- if (elems[j].isset_offset > 0)
- {
- char *setpos = ((char *) rdopts) + elems[j].isset_offset;
-
- *(bool *) setpos = options[i].isset;
- }
-
switch (options[i].gen->type)
{
case RELOPT_TYPE_BOOL:
@@ -1807,6 +1888,11 @@ fillRelOptions(void *rdopts, Size basesize,
options[i].values.bool_val :
((relopt_bool *) options[i].gen)->default_val;
break;
+ case RELOPT_TYPE_TERNARY:
+ *(pg_ternary *) itempos = options[i].isset ?
+ options[i].values.ternary_val :
+ ((relopt_ternary *) options[i].gen)->default_val;
+ break;
case RELOPT_TYPE_INT:
*(int *) itempos = options[i].isset ?
options[i].values.int_val :
@@ -1923,8 +2009,8 @@ default_reloptions(Datum reloptions, bool validate, relopt_kind kind)
offsetof(StdRdOptions, parallel_workers)},
{"vacuum_index_cleanup", RELOPT_TYPE_ENUM,
offsetof(StdRdOptions, vacuum_index_cleanup)},
- {"vacuum_truncate", RELOPT_TYPE_BOOL,
- offsetof(StdRdOptions, vacuum_truncate), offsetof(StdRdOptions, vacuum_truncate_set)},
+ {"vacuum_truncate", RELOPT_TYPE_TERNARY,
+ offsetof(StdRdOptions, vacuum_truncate)},
{"vacuum_max_eager_freeze_failure_rate", RELOPT_TYPE_REAL,
offsetof(StdRdOptions, vacuum_max_eager_freeze_failure_rate)}
};
@@ -2004,7 +2090,6 @@ build_local_reloptions(local_relopts *relopts, Datum options, bool validate)
elems[i].optname = opt->option->name;
elems[i].opttype = opt->option->type;
elems[i].offset = opt->offset;
- elems[i].isset_offset = 0; /* not supported for local relopts yet */
i++;
}
diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c
index aa4fbec143f..696eab9bd97 100644
--- a/src/backend/commands/vacuum.c
+++ b/src/backend/commands/vacuum.c
@@ -2224,9 +2224,9 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams params,
{
StdRdOptions *opts = (StdRdOptions *) rel->rd_options;
- if (opts && opts->vacuum_truncate_set)
+ if (opts && opts->vacuum_truncate != TERNARY_UNSET)
{
- if (opts->vacuum_truncate)
+ if (opts->vacuum_truncate == TERNARY_TRUE)
params.truncate = VACOPTVALUE_ENABLED;
else
params.truncate = VACOPTVALUE_DISABLED;
diff --git a/src/include/access/reloptions.h b/src/include/access/reloptions.h
index 2f08e1b0cf0..dfbef2babf2 100644
--- a/src/include/access/reloptions.h
+++ b/src/include/access/reloptions.h
@@ -29,6 +29,7 @@
typedef enum relopt_type
{
RELOPT_TYPE_BOOL,
+ RELOPT_TYPE_TERNARY, /* on, off, unset */
RELOPT_TYPE_INT,
RELOPT_TYPE_REAL,
RELOPT_TYPE_ENUM,
@@ -80,6 +81,7 @@ typedef struct relopt_value
union
{
bool bool_val;
+ pg_ternary ternary_val;
int int_val;
double real_val;
int enum_val;
@@ -94,6 +96,12 @@ typedef struct relopt_bool
bool default_val;
} relopt_bool;
+typedef struct relopt_ternary
+{
+ relopt_gen gen;
+ int default_val;
+} relopt_ternary;
+
typedef struct relopt_int
{
relopt_gen gen;
@@ -152,19 +160,6 @@ typedef struct
const char *optname; /* option's name */
relopt_type opttype; /* option's datatype */
int offset; /* offset of field in result struct */
-
- /*
- * isset_offset is an optional offset of a field in the result struct that
- * stores whether the option is explicitly set for the relation or if it
- * just picked up the default value. In most cases, this can be
- * accomplished by giving the reloption a special out-of-range default
- * value (e.g., some integer reloptions use -2), but this isn't always
- * possible. For example, a Boolean reloption cannot be given an
- * out-of-range default, so we need another way to discover the source of
- * its value. This offset is only used if given a value greater than
- * zero.
- */
- int isset_offset;
} relopt_parse_elt;
/* Local reloption definition */
@@ -195,6 +190,9 @@ typedef struct local_relopts
extern relopt_kind add_reloption_kind(void);
extern void add_bool_reloption(bits32 kinds, const char *name, const char *desc,
bool default_val, LOCKMODE lockmode);
+extern void add_ternary_reloption(bits32 kinds, const char *name,
+ const char *desc, pg_ternary default_val,
+ LOCKMODE lockmode);
extern void add_int_reloption(bits32 kinds, const char *name, const char *desc,
int default_val, int min_val, int max_val,
LOCKMODE lockmode);
@@ -214,6 +212,9 @@ extern void register_reloptions_validator(local_relopts *relopts,
extern void add_local_bool_reloption(local_relopts *relopts, const char *name,
const char *desc, bool default_val,
int offset);
+extern void add_local_ternary_reloption(local_relopts *relopts,
+ const char *name, const char *desc,
+ pg_ternary default_val, int offset);
extern void add_local_int_reloption(local_relopts *relopts, const char *name,
const char *desc, int default_val,
int min_val, int max_val, int offset);
diff --git a/src/include/postgres.h b/src/include/postgres.h
index 7d93fbce709..47c88780776 100644
--- a/src/include/postgres.h
+++ b/src/include/postgres.h
@@ -543,6 +543,21 @@ Float8GetDatum(float8 X)
* ----------------------------------------------------------------
*/
+/*
+ * pg_ternary
+ * Boolean value with an extra "unset" value
+ *
+ * This enum can be used for values that want to distinguish between true,
+ * false, and unset.
+*/
+
+typedef enum pg_ternary
+{
+ TERNARY_FALSE = 0,
+ TERNARY_TRUE = 1,
+ TERNARY_UNSET = -1
+} pg_ternary;
+
/*
* NON_EXEC_STATIC: It's sometimes useful to define a variable or function
* that is normally static but extern when using EXEC_BACKEND (see
diff --git a/src/include/utils/rel.h b/src/include/utils/rel.h
index d03ab247788..5feb18a5373 100644
--- a/src/include/utils/rel.h
+++ b/src/include/utils/rel.h
@@ -347,8 +347,7 @@ typedef struct StdRdOptions
bool user_catalog_table; /* use as an additional catalog relation */
int parallel_workers; /* max number of parallel workers */
StdRdOptIndexCleanup vacuum_index_cleanup; /* controls index vacuuming */
- bool vacuum_truncate; /* enables vacuum to truncate a relation */
- bool vacuum_truncate_set; /* whether vacuum_truncate is set */
+ pg_ternary vacuum_truncate; /* enables vacuum to truncate a relation */
/*
* Fraction of pages in a relation that vacuum can eagerly scan and fail
diff --git a/src/test/regress/expected/reloptions.out b/src/test/regress/expected/reloptions.out
index 9de19b4e3f1..1c99f79ab01 100644
--- a/src/test/regress/expected/reloptions.out
+++ b/src/test/regress/expected/reloptions.out
@@ -98,6 +98,42 @@ SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass;
{fillfactor=13,autovacuum_enabled=false}
(1 row)
+-- Tests for future (FIXME) ternary options
+-- behave as boolean option: accept unassigned name and truncated value
+DROP TABLE reloptions_test;
+CREATE TABLE reloptions_test(i INT) WITH (vacuum_truncate);
+SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass;
+ reloptions
+------------------------
+ {vacuum_truncate=true}
+(1 row)
+
+DROP TABLE reloptions_test;
+CREATE TABLE reloptions_test(i INT) WITH (vacuum_truncate=FaLS);
+SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass;
+ reloptions
+------------------------
+ {vacuum_truncate=fals}
+(1 row)
+
+-- preferred "true" alias is used when storing
+DROP TABLE reloptions_test;
+CREATE TABLE reloptions_test(i INT) WITH (vacuum_index_cleanup=on);
+SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass;
+ reloptions
+---------------------------
+ {vacuum_index_cleanup=on}
+(1 row)
+
+-- custom "third" value is available
+DROP TABLE reloptions_test;
+CREATE TABLE reloptions_test(i INT) WITH (vacuum_index_cleanup=auto);
+SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass;
+ reloptions
+-----------------------------
+ {vacuum_index_cleanup=auto}
+(1 row)
+
-- Test vacuum_truncate option
DROP TABLE reloptions_test;
CREATE TEMP TABLE reloptions_test(i INT NOT NULL, j text)
diff --git a/src/test/regress/sql/reloptions.sql b/src/test/regress/sql/reloptions.sql
index 24fbe0b478d..f5980dafcbc 100644
--- a/src/test/regress/sql/reloptions.sql
+++ b/src/test/regress/sql/reloptions.sql
@@ -59,6 +59,27 @@ UPDATE pg_class
ALTER TABLE reloptions_test RESET (illegal_option);
SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass;
+-- Tests for future (FIXME) ternary options
+
+-- behave as boolean option: accept unassigned name and truncated value
+DROP TABLE reloptions_test;
+CREATE TABLE reloptions_test(i INT) WITH (vacuum_truncate);
+SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass;
+
+DROP TABLE reloptions_test;
+CREATE TABLE reloptions_test(i INT) WITH (vacuum_truncate=FaLS);
+SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass;
+
+-- preferred "true" alias is used when storing
+DROP TABLE reloptions_test;
+CREATE TABLE reloptions_test(i INT) WITH (vacuum_index_cleanup=on);
+SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass;
+
+-- custom "third" value is available
+DROP TABLE reloptions_test;
+CREATE TABLE reloptions_test(i INT) WITH (vacuum_index_cleanup=auto);
+SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass;
+
-- Test vacuum_truncate option
DROP TABLE reloptions_test;
diff --git a/src/tools/pgindent/typedefs.list b/src/tools/pgindent/typedefs.list
index 3f3a888fd0e..1c8610fd46c 100644
--- a/src/tools/pgindent/typedefs.list
+++ b/src/tools/pgindent/typedefs.list
@@ -3952,6 +3952,7 @@ pg_sha512_ctx
pg_snapshot
pg_special_case
pg_stack_base_t
+pg_ternary
pg_time_t
pg_time_usec_t
pg_tz
@@ -4079,6 +4080,7 @@ relopt_kind
relopt_parse_elt
relopt_real
relopt_string
+relopt_ternary
relopt_type
relopt_value
relopts_validator
--
2.47.3
--c36t3fndxhkzbl5z--
^ permalink raw reply [nested|flat] 868+ messages in thread
* [PATCH v3] Introduce ternary reloptions
@ 2026-01-16 15:04 Álvaro Herrera <[email protected]>
0 siblings, 0 replies; 868+ messages in thread
From: Álvaro Herrera @ 2026-01-16 15:04 UTC (permalink / raw)
Introduce ternary reloption as a replacement for current
`vacuum_truncate` implementation. Remove the `vacuum_truncate_set`
separate flag and use TERNARY_UNSET instead.
This could also be used for other options such as `vacuum_index_cleanup`
and `buffering`, but lets get the scaffolding in first.
Discussion: https://postgr.es/m/3474141.usfYGdeWWP@thinkpad-pgpro
---
src/backend/access/common/reloptions.c | 137 ++++++++++++++++++-----
src/backend/commands/vacuum.c | 4 +-
src/include/access/reloptions.h | 27 ++---
src/include/postgres.h | 15 +++
src/include/utils/rel.h | 3 +-
src/test/regress/expected/reloptions.out | 36 ++++++
src/test/regress/sql/reloptions.sql | 21 ++++
src/tools/pgindent/typedefs.list | 2 +
8 files changed, 202 insertions(+), 43 deletions(-)
diff --git a/src/backend/access/common/reloptions.c b/src/backend/access/common/reloptions.c
index 0b83f98ed5f..6f1f577581a 100644
--- a/src/backend/access/common/reloptions.c
+++ b/src/backend/access/common/reloptions.c
@@ -40,9 +40,9 @@
*
* To add an option:
*
- * (i) decide on a type (bool, integer, real, enum, string), name, default
- * value, upper and lower bounds (if applicable); for strings, consider a
- * validation routine.
+ * (i) decide on a type (bool, ternary, integer, real, enum, string), name,
+ * default value, upper and lower bounds (if applicable); for strings,
+ * consider a validation routine.
* (ii) add a record below (or use add_<type>_reloption).
* (iii) add it to the appropriate options struct (perhaps StdRdOptions)
* (iv) add it to the appropriate handling routine (perhaps
@@ -147,15 +147,6 @@ static relopt_bool boolRelOpts[] =
},
false
},
- {
- {
- "vacuum_truncate",
- "Enables vacuum to truncate empty pages at the end of this table",
- RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
- ShareUpdateExclusiveLock
- },
- true
- },
{
{
"deduplicate_items",
@@ -170,6 +161,25 @@ static relopt_bool boolRelOpts[] =
{{NULL}}
};
+static relopt_ternary ternaryRelOpts[] =
+{
+ {
+ {
+ "vacuum_truncate",
+ "Enables vacuum to truncate empty pages at the end of this table",
+ RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
+ ShareUpdateExclusiveLock
+ },
+ TERNARY_UNSET
+ },
+ /* list terminator */
+ {
+ {
+ NULL
+ }
+ }
+};
+
static relopt_int intRelOpts[] =
{
{
@@ -609,6 +619,13 @@ initialize_reloptions(void)
boolRelOpts[i].gen.lockmode));
j++;
}
+ for (i = 0; ternaryRelOpts[i].gen.name; i++)
+ {
+ Assert(DoLockModesConflict(ternaryRelOpts[i].gen.lockmode,
+ ternaryRelOpts[i].gen.lockmode));
+ j++;
+ }
+
for (i = 0; intRelOpts[i].gen.name; i++)
{
Assert(DoLockModesConflict(intRelOpts[i].gen.lockmode,
@@ -649,6 +666,14 @@ initialize_reloptions(void)
j++;
}
+ for (i = 0; ternaryRelOpts[i].gen.name; i++)
+ {
+ relOpts[j] = &ternaryRelOpts[i].gen;
+ relOpts[j]->type = RELOPT_TYPE_TERNARY;
+ relOpts[j]->namelen = strlen(relOpts[j]->name);
+ j++;
+ }
+
for (i = 0; intRelOpts[i].gen.name; i++)
{
relOpts[j] = &intRelOpts[i].gen;
@@ -809,6 +834,9 @@ allocate_reloption(bits32 kinds, int type, const char *name, const char *desc,
case RELOPT_TYPE_BOOL:
size = sizeof(relopt_bool);
break;
+ case RELOPT_TYPE_TERNARY:
+ size = sizeof(relopt_ternary);
+ break;
case RELOPT_TYPE_INT:
size = sizeof(relopt_int);
break;
@@ -892,6 +920,57 @@ add_local_bool_reloption(local_relopts *relopts, const char *name,
add_local_reloption(relopts, (relopt_gen *) newoption, offset);
}
+/*
+ * init_ternary_reloption
+ * Allocate and initialize a new ternary reloption
+ */
+static relopt_ternary *
+init_ternary_reloption(bits32 kinds, const char *name, const char *desc,
+ pg_ternary default_val, LOCKMODE lockmode)
+{
+ relopt_ternary *newoption;
+
+ newoption = (relopt_ternary *)
+ allocate_reloption(kinds, RELOPT_TYPE_TERNARY, name, desc, lockmode);
+ newoption->default_val = default_val;
+
+ return newoption;
+}
+
+/*
+ * add_ternary_reloption
+ * Add a new ternary reloption
+ */
+void
+add_ternary_reloption(bits32 kinds, const char *name, const char *desc,
+ pg_ternary default_val, LOCKMODE lockmode)
+{
+ relopt_ternary *newoption;
+
+ newoption =
+ init_ternary_reloption(kinds, name, desc, default_val, lockmode);
+
+ add_reloption((relopt_gen *) newoption);
+}
+
+/*
+ * add_local_ternary_reloption
+ * Add a new ternary local reloption
+ *
+ * 'offset' is offset of ternary-typed field.
+ */
+void
+add_local_ternary_reloption(local_relopts *relopts, const char *name,
+ const char *desc, pg_ternary default_val,
+ int offset)
+{
+ relopt_ternary *newoption;
+
+ newoption =
+ init_ternary_reloption(RELOPT_KIND_LOCAL, name, desc, default_val, 0);
+
+ add_local_reloption(relopts, (relopt_gen *) newoption, offset);
+}
/*
* init_real_reloption
@@ -1626,6 +1705,19 @@ parse_one_reloption(relopt_value *option, char *text_str, int text_len,
option->gen->name, value)));
}
break;
+ case RELOPT_TYPE_TERNARY:
+ {
+ bool b;
+
+ parsed = parse_bool(value, &b);
+ option->values.ternary_val = b ? TERNARY_TRUE : TERNARY_FALSE;
+ if (validate && !parsed)
+ ereport(ERROR,
+ (errcode(ERRCODE_INVALID_PARAMETER_VALUE),
+ errmsg("invalid value for ternary option \"%s\": %s",
+ option->gen->name, value)));
+ }
+ break;
case RELOPT_TYPE_INT:
{
relopt_int *optint = (relopt_int *) option->gen;
@@ -1789,17 +1881,6 @@ fillRelOptions(void *rdopts, Size basesize,
char *itempos = ((char *) rdopts) + elems[j].offset;
char *string_val;
- /*
- * If isset_offset is provided, store whether the reloption is
- * set there.
- */
- if (elems[j].isset_offset > 0)
- {
- char *setpos = ((char *) rdopts) + elems[j].isset_offset;
-
- *(bool *) setpos = options[i].isset;
- }
-
switch (options[i].gen->type)
{
case RELOPT_TYPE_BOOL:
@@ -1807,6 +1888,11 @@ fillRelOptions(void *rdopts, Size basesize,
options[i].values.bool_val :
((relopt_bool *) options[i].gen)->default_val;
break;
+ case RELOPT_TYPE_TERNARY:
+ *(pg_ternary *) itempos = options[i].isset ?
+ options[i].values.ternary_val :
+ ((relopt_ternary *) options[i].gen)->default_val;
+ break;
case RELOPT_TYPE_INT:
*(int *) itempos = options[i].isset ?
options[i].values.int_val :
@@ -1923,8 +2009,8 @@ default_reloptions(Datum reloptions, bool validate, relopt_kind kind)
offsetof(StdRdOptions, parallel_workers)},
{"vacuum_index_cleanup", RELOPT_TYPE_ENUM,
offsetof(StdRdOptions, vacuum_index_cleanup)},
- {"vacuum_truncate", RELOPT_TYPE_BOOL,
- offsetof(StdRdOptions, vacuum_truncate), offsetof(StdRdOptions, vacuum_truncate_set)},
+ {"vacuum_truncate", RELOPT_TYPE_TERNARY,
+ offsetof(StdRdOptions, vacuum_truncate)},
{"vacuum_max_eager_freeze_failure_rate", RELOPT_TYPE_REAL,
offsetof(StdRdOptions, vacuum_max_eager_freeze_failure_rate)}
};
@@ -2004,7 +2090,6 @@ build_local_reloptions(local_relopts *relopts, Datum options, bool validate)
elems[i].optname = opt->option->name;
elems[i].opttype = opt->option->type;
elems[i].offset = opt->offset;
- elems[i].isset_offset = 0; /* not supported for local relopts yet */
i++;
}
diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c
index aa4fbec143f..696eab9bd97 100644
--- a/src/backend/commands/vacuum.c
+++ b/src/backend/commands/vacuum.c
@@ -2224,9 +2224,9 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams params,
{
StdRdOptions *opts = (StdRdOptions *) rel->rd_options;
- if (opts && opts->vacuum_truncate_set)
+ if (opts && opts->vacuum_truncate != TERNARY_UNSET)
{
- if (opts->vacuum_truncate)
+ if (opts->vacuum_truncate == TERNARY_TRUE)
params.truncate = VACOPTVALUE_ENABLED;
else
params.truncate = VACOPTVALUE_DISABLED;
diff --git a/src/include/access/reloptions.h b/src/include/access/reloptions.h
index 2f08e1b0cf0..dfbef2babf2 100644
--- a/src/include/access/reloptions.h
+++ b/src/include/access/reloptions.h
@@ -29,6 +29,7 @@
typedef enum relopt_type
{
RELOPT_TYPE_BOOL,
+ RELOPT_TYPE_TERNARY, /* on, off, unset */
RELOPT_TYPE_INT,
RELOPT_TYPE_REAL,
RELOPT_TYPE_ENUM,
@@ -80,6 +81,7 @@ typedef struct relopt_value
union
{
bool bool_val;
+ pg_ternary ternary_val;
int int_val;
double real_val;
int enum_val;
@@ -94,6 +96,12 @@ typedef struct relopt_bool
bool default_val;
} relopt_bool;
+typedef struct relopt_ternary
+{
+ relopt_gen gen;
+ int default_val;
+} relopt_ternary;
+
typedef struct relopt_int
{
relopt_gen gen;
@@ -152,19 +160,6 @@ typedef struct
const char *optname; /* option's name */
relopt_type opttype; /* option's datatype */
int offset; /* offset of field in result struct */
-
- /*
- * isset_offset is an optional offset of a field in the result struct that
- * stores whether the option is explicitly set for the relation or if it
- * just picked up the default value. In most cases, this can be
- * accomplished by giving the reloption a special out-of-range default
- * value (e.g., some integer reloptions use -2), but this isn't always
- * possible. For example, a Boolean reloption cannot be given an
- * out-of-range default, so we need another way to discover the source of
- * its value. This offset is only used if given a value greater than
- * zero.
- */
- int isset_offset;
} relopt_parse_elt;
/* Local reloption definition */
@@ -195,6 +190,9 @@ typedef struct local_relopts
extern relopt_kind add_reloption_kind(void);
extern void add_bool_reloption(bits32 kinds, const char *name, const char *desc,
bool default_val, LOCKMODE lockmode);
+extern void add_ternary_reloption(bits32 kinds, const char *name,
+ const char *desc, pg_ternary default_val,
+ LOCKMODE lockmode);
extern void add_int_reloption(bits32 kinds, const char *name, const char *desc,
int default_val, int min_val, int max_val,
LOCKMODE lockmode);
@@ -214,6 +212,9 @@ extern void register_reloptions_validator(local_relopts *relopts,
extern void add_local_bool_reloption(local_relopts *relopts, const char *name,
const char *desc, bool default_val,
int offset);
+extern void add_local_ternary_reloption(local_relopts *relopts,
+ const char *name, const char *desc,
+ pg_ternary default_val, int offset);
extern void add_local_int_reloption(local_relopts *relopts, const char *name,
const char *desc, int default_val,
int min_val, int max_val, int offset);
diff --git a/src/include/postgres.h b/src/include/postgres.h
index 7d93fbce709..47c88780776 100644
--- a/src/include/postgres.h
+++ b/src/include/postgres.h
@@ -543,6 +543,21 @@ Float8GetDatum(float8 X)
* ----------------------------------------------------------------
*/
+/*
+ * pg_ternary
+ * Boolean value with an extra "unset" value
+ *
+ * This enum can be used for values that want to distinguish between true,
+ * false, and unset.
+*/
+
+typedef enum pg_ternary
+{
+ TERNARY_FALSE = 0,
+ TERNARY_TRUE = 1,
+ TERNARY_UNSET = -1
+} pg_ternary;
+
/*
* NON_EXEC_STATIC: It's sometimes useful to define a variable or function
* that is normally static but extern when using EXEC_BACKEND (see
diff --git a/src/include/utils/rel.h b/src/include/utils/rel.h
index d03ab247788..5feb18a5373 100644
--- a/src/include/utils/rel.h
+++ b/src/include/utils/rel.h
@@ -347,8 +347,7 @@ typedef struct StdRdOptions
bool user_catalog_table; /* use as an additional catalog relation */
int parallel_workers; /* max number of parallel workers */
StdRdOptIndexCleanup vacuum_index_cleanup; /* controls index vacuuming */
- bool vacuum_truncate; /* enables vacuum to truncate a relation */
- bool vacuum_truncate_set; /* whether vacuum_truncate is set */
+ pg_ternary vacuum_truncate; /* enables vacuum to truncate a relation */
/*
* Fraction of pages in a relation that vacuum can eagerly scan and fail
diff --git a/src/test/regress/expected/reloptions.out b/src/test/regress/expected/reloptions.out
index 9de19b4e3f1..1c99f79ab01 100644
--- a/src/test/regress/expected/reloptions.out
+++ b/src/test/regress/expected/reloptions.out
@@ -98,6 +98,42 @@ SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass;
{fillfactor=13,autovacuum_enabled=false}
(1 row)
+-- Tests for future (FIXME) ternary options
+-- behave as boolean option: accept unassigned name and truncated value
+DROP TABLE reloptions_test;
+CREATE TABLE reloptions_test(i INT) WITH (vacuum_truncate);
+SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass;
+ reloptions
+------------------------
+ {vacuum_truncate=true}
+(1 row)
+
+DROP TABLE reloptions_test;
+CREATE TABLE reloptions_test(i INT) WITH (vacuum_truncate=FaLS);
+SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass;
+ reloptions
+------------------------
+ {vacuum_truncate=fals}
+(1 row)
+
+-- preferred "true" alias is used when storing
+DROP TABLE reloptions_test;
+CREATE TABLE reloptions_test(i INT) WITH (vacuum_index_cleanup=on);
+SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass;
+ reloptions
+---------------------------
+ {vacuum_index_cleanup=on}
+(1 row)
+
+-- custom "third" value is available
+DROP TABLE reloptions_test;
+CREATE TABLE reloptions_test(i INT) WITH (vacuum_index_cleanup=auto);
+SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass;
+ reloptions
+-----------------------------
+ {vacuum_index_cleanup=auto}
+(1 row)
+
-- Test vacuum_truncate option
DROP TABLE reloptions_test;
CREATE TEMP TABLE reloptions_test(i INT NOT NULL, j text)
diff --git a/src/test/regress/sql/reloptions.sql b/src/test/regress/sql/reloptions.sql
index 24fbe0b478d..f5980dafcbc 100644
--- a/src/test/regress/sql/reloptions.sql
+++ b/src/test/regress/sql/reloptions.sql
@@ -59,6 +59,27 @@ UPDATE pg_class
ALTER TABLE reloptions_test RESET (illegal_option);
SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass;
+-- Tests for future (FIXME) ternary options
+
+-- behave as boolean option: accept unassigned name and truncated value
+DROP TABLE reloptions_test;
+CREATE TABLE reloptions_test(i INT) WITH (vacuum_truncate);
+SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass;
+
+DROP TABLE reloptions_test;
+CREATE TABLE reloptions_test(i INT) WITH (vacuum_truncate=FaLS);
+SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass;
+
+-- preferred "true" alias is used when storing
+DROP TABLE reloptions_test;
+CREATE TABLE reloptions_test(i INT) WITH (vacuum_index_cleanup=on);
+SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass;
+
+-- custom "third" value is available
+DROP TABLE reloptions_test;
+CREATE TABLE reloptions_test(i INT) WITH (vacuum_index_cleanup=auto);
+SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass;
+
-- Test vacuum_truncate option
DROP TABLE reloptions_test;
diff --git a/src/tools/pgindent/typedefs.list b/src/tools/pgindent/typedefs.list
index 3f3a888fd0e..1c8610fd46c 100644
--- a/src/tools/pgindent/typedefs.list
+++ b/src/tools/pgindent/typedefs.list
@@ -3952,6 +3952,7 @@ pg_sha512_ctx
pg_snapshot
pg_special_case
pg_stack_base_t
+pg_ternary
pg_time_t
pg_time_usec_t
pg_tz
@@ -4079,6 +4080,7 @@ relopt_kind
relopt_parse_elt
relopt_real
relopt_string
+relopt_ternary
relopt_type
relopt_value
relopts_validator
--
2.47.3
--c36t3fndxhkzbl5z--
^ permalink raw reply [nested|flat] 868+ messages in thread
* [PATCH v3] Introduce ternary reloptions
@ 2026-01-16 15:04 Álvaro Herrera <[email protected]>
0 siblings, 0 replies; 868+ messages in thread
From: Álvaro Herrera @ 2026-01-16 15:04 UTC (permalink / raw)
Introduce ternary reloption as a replacement for current
`vacuum_truncate` implementation. Remove the `vacuum_truncate_set`
separate flag and use TERNARY_UNSET instead.
This could also be used for other options such as `vacuum_index_cleanup`
and `buffering`, but lets get the scaffolding in first.
Discussion: https://postgr.es/m/3474141.usfYGdeWWP@thinkpad-pgpro
---
src/backend/access/common/reloptions.c | 137 ++++++++++++++++++-----
src/backend/commands/vacuum.c | 4 +-
src/include/access/reloptions.h | 27 ++---
src/include/postgres.h | 15 +++
src/include/utils/rel.h | 3 +-
src/test/regress/expected/reloptions.out | 36 ++++++
src/test/regress/sql/reloptions.sql | 21 ++++
src/tools/pgindent/typedefs.list | 2 +
8 files changed, 202 insertions(+), 43 deletions(-)
diff --git a/src/backend/access/common/reloptions.c b/src/backend/access/common/reloptions.c
index 0b83f98ed5f..6f1f577581a 100644
--- a/src/backend/access/common/reloptions.c
+++ b/src/backend/access/common/reloptions.c
@@ -40,9 +40,9 @@
*
* To add an option:
*
- * (i) decide on a type (bool, integer, real, enum, string), name, default
- * value, upper and lower bounds (if applicable); for strings, consider a
- * validation routine.
+ * (i) decide on a type (bool, ternary, integer, real, enum, string), name,
+ * default value, upper and lower bounds (if applicable); for strings,
+ * consider a validation routine.
* (ii) add a record below (or use add_<type>_reloption).
* (iii) add it to the appropriate options struct (perhaps StdRdOptions)
* (iv) add it to the appropriate handling routine (perhaps
@@ -147,15 +147,6 @@ static relopt_bool boolRelOpts[] =
},
false
},
- {
- {
- "vacuum_truncate",
- "Enables vacuum to truncate empty pages at the end of this table",
- RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
- ShareUpdateExclusiveLock
- },
- true
- },
{
{
"deduplicate_items",
@@ -170,6 +161,25 @@ static relopt_bool boolRelOpts[] =
{{NULL}}
};
+static relopt_ternary ternaryRelOpts[] =
+{
+ {
+ {
+ "vacuum_truncate",
+ "Enables vacuum to truncate empty pages at the end of this table",
+ RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
+ ShareUpdateExclusiveLock
+ },
+ TERNARY_UNSET
+ },
+ /* list terminator */
+ {
+ {
+ NULL
+ }
+ }
+};
+
static relopt_int intRelOpts[] =
{
{
@@ -609,6 +619,13 @@ initialize_reloptions(void)
boolRelOpts[i].gen.lockmode));
j++;
}
+ for (i = 0; ternaryRelOpts[i].gen.name; i++)
+ {
+ Assert(DoLockModesConflict(ternaryRelOpts[i].gen.lockmode,
+ ternaryRelOpts[i].gen.lockmode));
+ j++;
+ }
+
for (i = 0; intRelOpts[i].gen.name; i++)
{
Assert(DoLockModesConflict(intRelOpts[i].gen.lockmode,
@@ -649,6 +666,14 @@ initialize_reloptions(void)
j++;
}
+ for (i = 0; ternaryRelOpts[i].gen.name; i++)
+ {
+ relOpts[j] = &ternaryRelOpts[i].gen;
+ relOpts[j]->type = RELOPT_TYPE_TERNARY;
+ relOpts[j]->namelen = strlen(relOpts[j]->name);
+ j++;
+ }
+
for (i = 0; intRelOpts[i].gen.name; i++)
{
relOpts[j] = &intRelOpts[i].gen;
@@ -809,6 +834,9 @@ allocate_reloption(bits32 kinds, int type, const char *name, const char *desc,
case RELOPT_TYPE_BOOL:
size = sizeof(relopt_bool);
break;
+ case RELOPT_TYPE_TERNARY:
+ size = sizeof(relopt_ternary);
+ break;
case RELOPT_TYPE_INT:
size = sizeof(relopt_int);
break;
@@ -892,6 +920,57 @@ add_local_bool_reloption(local_relopts *relopts, const char *name,
add_local_reloption(relopts, (relopt_gen *) newoption, offset);
}
+/*
+ * init_ternary_reloption
+ * Allocate and initialize a new ternary reloption
+ */
+static relopt_ternary *
+init_ternary_reloption(bits32 kinds, const char *name, const char *desc,
+ pg_ternary default_val, LOCKMODE lockmode)
+{
+ relopt_ternary *newoption;
+
+ newoption = (relopt_ternary *)
+ allocate_reloption(kinds, RELOPT_TYPE_TERNARY, name, desc, lockmode);
+ newoption->default_val = default_val;
+
+ return newoption;
+}
+
+/*
+ * add_ternary_reloption
+ * Add a new ternary reloption
+ */
+void
+add_ternary_reloption(bits32 kinds, const char *name, const char *desc,
+ pg_ternary default_val, LOCKMODE lockmode)
+{
+ relopt_ternary *newoption;
+
+ newoption =
+ init_ternary_reloption(kinds, name, desc, default_val, lockmode);
+
+ add_reloption((relopt_gen *) newoption);
+}
+
+/*
+ * add_local_ternary_reloption
+ * Add a new ternary local reloption
+ *
+ * 'offset' is offset of ternary-typed field.
+ */
+void
+add_local_ternary_reloption(local_relopts *relopts, const char *name,
+ const char *desc, pg_ternary default_val,
+ int offset)
+{
+ relopt_ternary *newoption;
+
+ newoption =
+ init_ternary_reloption(RELOPT_KIND_LOCAL, name, desc, default_val, 0);
+
+ add_local_reloption(relopts, (relopt_gen *) newoption, offset);
+}
/*
* init_real_reloption
@@ -1626,6 +1705,19 @@ parse_one_reloption(relopt_value *option, char *text_str, int text_len,
option->gen->name, value)));
}
break;
+ case RELOPT_TYPE_TERNARY:
+ {
+ bool b;
+
+ parsed = parse_bool(value, &b);
+ option->values.ternary_val = b ? TERNARY_TRUE : TERNARY_FALSE;
+ if (validate && !parsed)
+ ereport(ERROR,
+ (errcode(ERRCODE_INVALID_PARAMETER_VALUE),
+ errmsg("invalid value for ternary option \"%s\": %s",
+ option->gen->name, value)));
+ }
+ break;
case RELOPT_TYPE_INT:
{
relopt_int *optint = (relopt_int *) option->gen;
@@ -1789,17 +1881,6 @@ fillRelOptions(void *rdopts, Size basesize,
char *itempos = ((char *) rdopts) + elems[j].offset;
char *string_val;
- /*
- * If isset_offset is provided, store whether the reloption is
- * set there.
- */
- if (elems[j].isset_offset > 0)
- {
- char *setpos = ((char *) rdopts) + elems[j].isset_offset;
-
- *(bool *) setpos = options[i].isset;
- }
-
switch (options[i].gen->type)
{
case RELOPT_TYPE_BOOL:
@@ -1807,6 +1888,11 @@ fillRelOptions(void *rdopts, Size basesize,
options[i].values.bool_val :
((relopt_bool *) options[i].gen)->default_val;
break;
+ case RELOPT_TYPE_TERNARY:
+ *(pg_ternary *) itempos = options[i].isset ?
+ options[i].values.ternary_val :
+ ((relopt_ternary *) options[i].gen)->default_val;
+ break;
case RELOPT_TYPE_INT:
*(int *) itempos = options[i].isset ?
options[i].values.int_val :
@@ -1923,8 +2009,8 @@ default_reloptions(Datum reloptions, bool validate, relopt_kind kind)
offsetof(StdRdOptions, parallel_workers)},
{"vacuum_index_cleanup", RELOPT_TYPE_ENUM,
offsetof(StdRdOptions, vacuum_index_cleanup)},
- {"vacuum_truncate", RELOPT_TYPE_BOOL,
- offsetof(StdRdOptions, vacuum_truncate), offsetof(StdRdOptions, vacuum_truncate_set)},
+ {"vacuum_truncate", RELOPT_TYPE_TERNARY,
+ offsetof(StdRdOptions, vacuum_truncate)},
{"vacuum_max_eager_freeze_failure_rate", RELOPT_TYPE_REAL,
offsetof(StdRdOptions, vacuum_max_eager_freeze_failure_rate)}
};
@@ -2004,7 +2090,6 @@ build_local_reloptions(local_relopts *relopts, Datum options, bool validate)
elems[i].optname = opt->option->name;
elems[i].opttype = opt->option->type;
elems[i].offset = opt->offset;
- elems[i].isset_offset = 0; /* not supported for local relopts yet */
i++;
}
diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c
index aa4fbec143f..696eab9bd97 100644
--- a/src/backend/commands/vacuum.c
+++ b/src/backend/commands/vacuum.c
@@ -2224,9 +2224,9 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams params,
{
StdRdOptions *opts = (StdRdOptions *) rel->rd_options;
- if (opts && opts->vacuum_truncate_set)
+ if (opts && opts->vacuum_truncate != TERNARY_UNSET)
{
- if (opts->vacuum_truncate)
+ if (opts->vacuum_truncate == TERNARY_TRUE)
params.truncate = VACOPTVALUE_ENABLED;
else
params.truncate = VACOPTVALUE_DISABLED;
diff --git a/src/include/access/reloptions.h b/src/include/access/reloptions.h
index 2f08e1b0cf0..dfbef2babf2 100644
--- a/src/include/access/reloptions.h
+++ b/src/include/access/reloptions.h
@@ -29,6 +29,7 @@
typedef enum relopt_type
{
RELOPT_TYPE_BOOL,
+ RELOPT_TYPE_TERNARY, /* on, off, unset */
RELOPT_TYPE_INT,
RELOPT_TYPE_REAL,
RELOPT_TYPE_ENUM,
@@ -80,6 +81,7 @@ typedef struct relopt_value
union
{
bool bool_val;
+ pg_ternary ternary_val;
int int_val;
double real_val;
int enum_val;
@@ -94,6 +96,12 @@ typedef struct relopt_bool
bool default_val;
} relopt_bool;
+typedef struct relopt_ternary
+{
+ relopt_gen gen;
+ int default_val;
+} relopt_ternary;
+
typedef struct relopt_int
{
relopt_gen gen;
@@ -152,19 +160,6 @@ typedef struct
const char *optname; /* option's name */
relopt_type opttype; /* option's datatype */
int offset; /* offset of field in result struct */
-
- /*
- * isset_offset is an optional offset of a field in the result struct that
- * stores whether the option is explicitly set for the relation or if it
- * just picked up the default value. In most cases, this can be
- * accomplished by giving the reloption a special out-of-range default
- * value (e.g., some integer reloptions use -2), but this isn't always
- * possible. For example, a Boolean reloption cannot be given an
- * out-of-range default, so we need another way to discover the source of
- * its value. This offset is only used if given a value greater than
- * zero.
- */
- int isset_offset;
} relopt_parse_elt;
/* Local reloption definition */
@@ -195,6 +190,9 @@ typedef struct local_relopts
extern relopt_kind add_reloption_kind(void);
extern void add_bool_reloption(bits32 kinds, const char *name, const char *desc,
bool default_val, LOCKMODE lockmode);
+extern void add_ternary_reloption(bits32 kinds, const char *name,
+ const char *desc, pg_ternary default_val,
+ LOCKMODE lockmode);
extern void add_int_reloption(bits32 kinds, const char *name, const char *desc,
int default_val, int min_val, int max_val,
LOCKMODE lockmode);
@@ -214,6 +212,9 @@ extern void register_reloptions_validator(local_relopts *relopts,
extern void add_local_bool_reloption(local_relopts *relopts, const char *name,
const char *desc, bool default_val,
int offset);
+extern void add_local_ternary_reloption(local_relopts *relopts,
+ const char *name, const char *desc,
+ pg_ternary default_val, int offset);
extern void add_local_int_reloption(local_relopts *relopts, const char *name,
const char *desc, int default_val,
int min_val, int max_val, int offset);
diff --git a/src/include/postgres.h b/src/include/postgres.h
index 7d93fbce709..47c88780776 100644
--- a/src/include/postgres.h
+++ b/src/include/postgres.h
@@ -543,6 +543,21 @@ Float8GetDatum(float8 X)
* ----------------------------------------------------------------
*/
+/*
+ * pg_ternary
+ * Boolean value with an extra "unset" value
+ *
+ * This enum can be used for values that want to distinguish between true,
+ * false, and unset.
+*/
+
+typedef enum pg_ternary
+{
+ TERNARY_FALSE = 0,
+ TERNARY_TRUE = 1,
+ TERNARY_UNSET = -1
+} pg_ternary;
+
/*
* NON_EXEC_STATIC: It's sometimes useful to define a variable or function
* that is normally static but extern when using EXEC_BACKEND (see
diff --git a/src/include/utils/rel.h b/src/include/utils/rel.h
index d03ab247788..5feb18a5373 100644
--- a/src/include/utils/rel.h
+++ b/src/include/utils/rel.h
@@ -347,8 +347,7 @@ typedef struct StdRdOptions
bool user_catalog_table; /* use as an additional catalog relation */
int parallel_workers; /* max number of parallel workers */
StdRdOptIndexCleanup vacuum_index_cleanup; /* controls index vacuuming */
- bool vacuum_truncate; /* enables vacuum to truncate a relation */
- bool vacuum_truncate_set; /* whether vacuum_truncate is set */
+ pg_ternary vacuum_truncate; /* enables vacuum to truncate a relation */
/*
* Fraction of pages in a relation that vacuum can eagerly scan and fail
diff --git a/src/test/regress/expected/reloptions.out b/src/test/regress/expected/reloptions.out
index 9de19b4e3f1..1c99f79ab01 100644
--- a/src/test/regress/expected/reloptions.out
+++ b/src/test/regress/expected/reloptions.out
@@ -98,6 +98,42 @@ SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass;
{fillfactor=13,autovacuum_enabled=false}
(1 row)
+-- Tests for future (FIXME) ternary options
+-- behave as boolean option: accept unassigned name and truncated value
+DROP TABLE reloptions_test;
+CREATE TABLE reloptions_test(i INT) WITH (vacuum_truncate);
+SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass;
+ reloptions
+------------------------
+ {vacuum_truncate=true}
+(1 row)
+
+DROP TABLE reloptions_test;
+CREATE TABLE reloptions_test(i INT) WITH (vacuum_truncate=FaLS);
+SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass;
+ reloptions
+------------------------
+ {vacuum_truncate=fals}
+(1 row)
+
+-- preferred "true" alias is used when storing
+DROP TABLE reloptions_test;
+CREATE TABLE reloptions_test(i INT) WITH (vacuum_index_cleanup=on);
+SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass;
+ reloptions
+---------------------------
+ {vacuum_index_cleanup=on}
+(1 row)
+
+-- custom "third" value is available
+DROP TABLE reloptions_test;
+CREATE TABLE reloptions_test(i INT) WITH (vacuum_index_cleanup=auto);
+SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass;
+ reloptions
+-----------------------------
+ {vacuum_index_cleanup=auto}
+(1 row)
+
-- Test vacuum_truncate option
DROP TABLE reloptions_test;
CREATE TEMP TABLE reloptions_test(i INT NOT NULL, j text)
diff --git a/src/test/regress/sql/reloptions.sql b/src/test/regress/sql/reloptions.sql
index 24fbe0b478d..f5980dafcbc 100644
--- a/src/test/regress/sql/reloptions.sql
+++ b/src/test/regress/sql/reloptions.sql
@@ -59,6 +59,27 @@ UPDATE pg_class
ALTER TABLE reloptions_test RESET (illegal_option);
SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass;
+-- Tests for future (FIXME) ternary options
+
+-- behave as boolean option: accept unassigned name and truncated value
+DROP TABLE reloptions_test;
+CREATE TABLE reloptions_test(i INT) WITH (vacuum_truncate);
+SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass;
+
+DROP TABLE reloptions_test;
+CREATE TABLE reloptions_test(i INT) WITH (vacuum_truncate=FaLS);
+SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass;
+
+-- preferred "true" alias is used when storing
+DROP TABLE reloptions_test;
+CREATE TABLE reloptions_test(i INT) WITH (vacuum_index_cleanup=on);
+SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass;
+
+-- custom "third" value is available
+DROP TABLE reloptions_test;
+CREATE TABLE reloptions_test(i INT) WITH (vacuum_index_cleanup=auto);
+SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass;
+
-- Test vacuum_truncate option
DROP TABLE reloptions_test;
diff --git a/src/tools/pgindent/typedefs.list b/src/tools/pgindent/typedefs.list
index 3f3a888fd0e..1c8610fd46c 100644
--- a/src/tools/pgindent/typedefs.list
+++ b/src/tools/pgindent/typedefs.list
@@ -3952,6 +3952,7 @@ pg_sha512_ctx
pg_snapshot
pg_special_case
pg_stack_base_t
+pg_ternary
pg_time_t
pg_time_usec_t
pg_tz
@@ -4079,6 +4080,7 @@ relopt_kind
relopt_parse_elt
relopt_real
relopt_string
+relopt_ternary
relopt_type
relopt_value
relopts_validator
--
2.47.3
--c36t3fndxhkzbl5z--
^ permalink raw reply [nested|flat] 868+ messages in thread
* [PATCH v3] Introduce ternary reloptions
@ 2026-01-16 15:04 Álvaro Herrera <[email protected]>
0 siblings, 0 replies; 868+ messages in thread
From: Álvaro Herrera @ 2026-01-16 15:04 UTC (permalink / raw)
Introduce ternary reloption as a replacement for current
`vacuum_truncate` implementation. Remove the `vacuum_truncate_set`
separate flag and use TERNARY_UNSET instead.
This could also be used for other options such as `vacuum_index_cleanup`
and `buffering`, but lets get the scaffolding in first.
Discussion: https://postgr.es/m/3474141.usfYGdeWWP@thinkpad-pgpro
---
src/backend/access/common/reloptions.c | 137 ++++++++++++++++++-----
src/backend/commands/vacuum.c | 4 +-
src/include/access/reloptions.h | 27 ++---
src/include/postgres.h | 15 +++
src/include/utils/rel.h | 3 +-
src/test/regress/expected/reloptions.out | 36 ++++++
src/test/regress/sql/reloptions.sql | 21 ++++
src/tools/pgindent/typedefs.list | 2 +
8 files changed, 202 insertions(+), 43 deletions(-)
diff --git a/src/backend/access/common/reloptions.c b/src/backend/access/common/reloptions.c
index 0b83f98ed5f..6f1f577581a 100644
--- a/src/backend/access/common/reloptions.c
+++ b/src/backend/access/common/reloptions.c
@@ -40,9 +40,9 @@
*
* To add an option:
*
- * (i) decide on a type (bool, integer, real, enum, string), name, default
- * value, upper and lower bounds (if applicable); for strings, consider a
- * validation routine.
+ * (i) decide on a type (bool, ternary, integer, real, enum, string), name,
+ * default value, upper and lower bounds (if applicable); for strings,
+ * consider a validation routine.
* (ii) add a record below (or use add_<type>_reloption).
* (iii) add it to the appropriate options struct (perhaps StdRdOptions)
* (iv) add it to the appropriate handling routine (perhaps
@@ -147,15 +147,6 @@ static relopt_bool boolRelOpts[] =
},
false
},
- {
- {
- "vacuum_truncate",
- "Enables vacuum to truncate empty pages at the end of this table",
- RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
- ShareUpdateExclusiveLock
- },
- true
- },
{
{
"deduplicate_items",
@@ -170,6 +161,25 @@ static relopt_bool boolRelOpts[] =
{{NULL}}
};
+static relopt_ternary ternaryRelOpts[] =
+{
+ {
+ {
+ "vacuum_truncate",
+ "Enables vacuum to truncate empty pages at the end of this table",
+ RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
+ ShareUpdateExclusiveLock
+ },
+ TERNARY_UNSET
+ },
+ /* list terminator */
+ {
+ {
+ NULL
+ }
+ }
+};
+
static relopt_int intRelOpts[] =
{
{
@@ -609,6 +619,13 @@ initialize_reloptions(void)
boolRelOpts[i].gen.lockmode));
j++;
}
+ for (i = 0; ternaryRelOpts[i].gen.name; i++)
+ {
+ Assert(DoLockModesConflict(ternaryRelOpts[i].gen.lockmode,
+ ternaryRelOpts[i].gen.lockmode));
+ j++;
+ }
+
for (i = 0; intRelOpts[i].gen.name; i++)
{
Assert(DoLockModesConflict(intRelOpts[i].gen.lockmode,
@@ -649,6 +666,14 @@ initialize_reloptions(void)
j++;
}
+ for (i = 0; ternaryRelOpts[i].gen.name; i++)
+ {
+ relOpts[j] = &ternaryRelOpts[i].gen;
+ relOpts[j]->type = RELOPT_TYPE_TERNARY;
+ relOpts[j]->namelen = strlen(relOpts[j]->name);
+ j++;
+ }
+
for (i = 0; intRelOpts[i].gen.name; i++)
{
relOpts[j] = &intRelOpts[i].gen;
@@ -809,6 +834,9 @@ allocate_reloption(bits32 kinds, int type, const char *name, const char *desc,
case RELOPT_TYPE_BOOL:
size = sizeof(relopt_bool);
break;
+ case RELOPT_TYPE_TERNARY:
+ size = sizeof(relopt_ternary);
+ break;
case RELOPT_TYPE_INT:
size = sizeof(relopt_int);
break;
@@ -892,6 +920,57 @@ add_local_bool_reloption(local_relopts *relopts, const char *name,
add_local_reloption(relopts, (relopt_gen *) newoption, offset);
}
+/*
+ * init_ternary_reloption
+ * Allocate and initialize a new ternary reloption
+ */
+static relopt_ternary *
+init_ternary_reloption(bits32 kinds, const char *name, const char *desc,
+ pg_ternary default_val, LOCKMODE lockmode)
+{
+ relopt_ternary *newoption;
+
+ newoption = (relopt_ternary *)
+ allocate_reloption(kinds, RELOPT_TYPE_TERNARY, name, desc, lockmode);
+ newoption->default_val = default_val;
+
+ return newoption;
+}
+
+/*
+ * add_ternary_reloption
+ * Add a new ternary reloption
+ */
+void
+add_ternary_reloption(bits32 kinds, const char *name, const char *desc,
+ pg_ternary default_val, LOCKMODE lockmode)
+{
+ relopt_ternary *newoption;
+
+ newoption =
+ init_ternary_reloption(kinds, name, desc, default_val, lockmode);
+
+ add_reloption((relopt_gen *) newoption);
+}
+
+/*
+ * add_local_ternary_reloption
+ * Add a new ternary local reloption
+ *
+ * 'offset' is offset of ternary-typed field.
+ */
+void
+add_local_ternary_reloption(local_relopts *relopts, const char *name,
+ const char *desc, pg_ternary default_val,
+ int offset)
+{
+ relopt_ternary *newoption;
+
+ newoption =
+ init_ternary_reloption(RELOPT_KIND_LOCAL, name, desc, default_val, 0);
+
+ add_local_reloption(relopts, (relopt_gen *) newoption, offset);
+}
/*
* init_real_reloption
@@ -1626,6 +1705,19 @@ parse_one_reloption(relopt_value *option, char *text_str, int text_len,
option->gen->name, value)));
}
break;
+ case RELOPT_TYPE_TERNARY:
+ {
+ bool b;
+
+ parsed = parse_bool(value, &b);
+ option->values.ternary_val = b ? TERNARY_TRUE : TERNARY_FALSE;
+ if (validate && !parsed)
+ ereport(ERROR,
+ (errcode(ERRCODE_INVALID_PARAMETER_VALUE),
+ errmsg("invalid value for ternary option \"%s\": %s",
+ option->gen->name, value)));
+ }
+ break;
case RELOPT_TYPE_INT:
{
relopt_int *optint = (relopt_int *) option->gen;
@@ -1789,17 +1881,6 @@ fillRelOptions(void *rdopts, Size basesize,
char *itempos = ((char *) rdopts) + elems[j].offset;
char *string_val;
- /*
- * If isset_offset is provided, store whether the reloption is
- * set there.
- */
- if (elems[j].isset_offset > 0)
- {
- char *setpos = ((char *) rdopts) + elems[j].isset_offset;
-
- *(bool *) setpos = options[i].isset;
- }
-
switch (options[i].gen->type)
{
case RELOPT_TYPE_BOOL:
@@ -1807,6 +1888,11 @@ fillRelOptions(void *rdopts, Size basesize,
options[i].values.bool_val :
((relopt_bool *) options[i].gen)->default_val;
break;
+ case RELOPT_TYPE_TERNARY:
+ *(pg_ternary *) itempos = options[i].isset ?
+ options[i].values.ternary_val :
+ ((relopt_ternary *) options[i].gen)->default_val;
+ break;
case RELOPT_TYPE_INT:
*(int *) itempos = options[i].isset ?
options[i].values.int_val :
@@ -1923,8 +2009,8 @@ default_reloptions(Datum reloptions, bool validate, relopt_kind kind)
offsetof(StdRdOptions, parallel_workers)},
{"vacuum_index_cleanup", RELOPT_TYPE_ENUM,
offsetof(StdRdOptions, vacuum_index_cleanup)},
- {"vacuum_truncate", RELOPT_TYPE_BOOL,
- offsetof(StdRdOptions, vacuum_truncate), offsetof(StdRdOptions, vacuum_truncate_set)},
+ {"vacuum_truncate", RELOPT_TYPE_TERNARY,
+ offsetof(StdRdOptions, vacuum_truncate)},
{"vacuum_max_eager_freeze_failure_rate", RELOPT_TYPE_REAL,
offsetof(StdRdOptions, vacuum_max_eager_freeze_failure_rate)}
};
@@ -2004,7 +2090,6 @@ build_local_reloptions(local_relopts *relopts, Datum options, bool validate)
elems[i].optname = opt->option->name;
elems[i].opttype = opt->option->type;
elems[i].offset = opt->offset;
- elems[i].isset_offset = 0; /* not supported for local relopts yet */
i++;
}
diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c
index aa4fbec143f..696eab9bd97 100644
--- a/src/backend/commands/vacuum.c
+++ b/src/backend/commands/vacuum.c
@@ -2224,9 +2224,9 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams params,
{
StdRdOptions *opts = (StdRdOptions *) rel->rd_options;
- if (opts && opts->vacuum_truncate_set)
+ if (opts && opts->vacuum_truncate != TERNARY_UNSET)
{
- if (opts->vacuum_truncate)
+ if (opts->vacuum_truncate == TERNARY_TRUE)
params.truncate = VACOPTVALUE_ENABLED;
else
params.truncate = VACOPTVALUE_DISABLED;
diff --git a/src/include/access/reloptions.h b/src/include/access/reloptions.h
index 2f08e1b0cf0..dfbef2babf2 100644
--- a/src/include/access/reloptions.h
+++ b/src/include/access/reloptions.h
@@ -29,6 +29,7 @@
typedef enum relopt_type
{
RELOPT_TYPE_BOOL,
+ RELOPT_TYPE_TERNARY, /* on, off, unset */
RELOPT_TYPE_INT,
RELOPT_TYPE_REAL,
RELOPT_TYPE_ENUM,
@@ -80,6 +81,7 @@ typedef struct relopt_value
union
{
bool bool_val;
+ pg_ternary ternary_val;
int int_val;
double real_val;
int enum_val;
@@ -94,6 +96,12 @@ typedef struct relopt_bool
bool default_val;
} relopt_bool;
+typedef struct relopt_ternary
+{
+ relopt_gen gen;
+ int default_val;
+} relopt_ternary;
+
typedef struct relopt_int
{
relopt_gen gen;
@@ -152,19 +160,6 @@ typedef struct
const char *optname; /* option's name */
relopt_type opttype; /* option's datatype */
int offset; /* offset of field in result struct */
-
- /*
- * isset_offset is an optional offset of a field in the result struct that
- * stores whether the option is explicitly set for the relation or if it
- * just picked up the default value. In most cases, this can be
- * accomplished by giving the reloption a special out-of-range default
- * value (e.g., some integer reloptions use -2), but this isn't always
- * possible. For example, a Boolean reloption cannot be given an
- * out-of-range default, so we need another way to discover the source of
- * its value. This offset is only used if given a value greater than
- * zero.
- */
- int isset_offset;
} relopt_parse_elt;
/* Local reloption definition */
@@ -195,6 +190,9 @@ typedef struct local_relopts
extern relopt_kind add_reloption_kind(void);
extern void add_bool_reloption(bits32 kinds, const char *name, const char *desc,
bool default_val, LOCKMODE lockmode);
+extern void add_ternary_reloption(bits32 kinds, const char *name,
+ const char *desc, pg_ternary default_val,
+ LOCKMODE lockmode);
extern void add_int_reloption(bits32 kinds, const char *name, const char *desc,
int default_val, int min_val, int max_val,
LOCKMODE lockmode);
@@ -214,6 +212,9 @@ extern void register_reloptions_validator(local_relopts *relopts,
extern void add_local_bool_reloption(local_relopts *relopts, const char *name,
const char *desc, bool default_val,
int offset);
+extern void add_local_ternary_reloption(local_relopts *relopts,
+ const char *name, const char *desc,
+ pg_ternary default_val, int offset);
extern void add_local_int_reloption(local_relopts *relopts, const char *name,
const char *desc, int default_val,
int min_val, int max_val, int offset);
diff --git a/src/include/postgres.h b/src/include/postgres.h
index 7d93fbce709..47c88780776 100644
--- a/src/include/postgres.h
+++ b/src/include/postgres.h
@@ -543,6 +543,21 @@ Float8GetDatum(float8 X)
* ----------------------------------------------------------------
*/
+/*
+ * pg_ternary
+ * Boolean value with an extra "unset" value
+ *
+ * This enum can be used for values that want to distinguish between true,
+ * false, and unset.
+*/
+
+typedef enum pg_ternary
+{
+ TERNARY_FALSE = 0,
+ TERNARY_TRUE = 1,
+ TERNARY_UNSET = -1
+} pg_ternary;
+
/*
* NON_EXEC_STATIC: It's sometimes useful to define a variable or function
* that is normally static but extern when using EXEC_BACKEND (see
diff --git a/src/include/utils/rel.h b/src/include/utils/rel.h
index d03ab247788..5feb18a5373 100644
--- a/src/include/utils/rel.h
+++ b/src/include/utils/rel.h
@@ -347,8 +347,7 @@ typedef struct StdRdOptions
bool user_catalog_table; /* use as an additional catalog relation */
int parallel_workers; /* max number of parallel workers */
StdRdOptIndexCleanup vacuum_index_cleanup; /* controls index vacuuming */
- bool vacuum_truncate; /* enables vacuum to truncate a relation */
- bool vacuum_truncate_set; /* whether vacuum_truncate is set */
+ pg_ternary vacuum_truncate; /* enables vacuum to truncate a relation */
/*
* Fraction of pages in a relation that vacuum can eagerly scan and fail
diff --git a/src/test/regress/expected/reloptions.out b/src/test/regress/expected/reloptions.out
index 9de19b4e3f1..1c99f79ab01 100644
--- a/src/test/regress/expected/reloptions.out
+++ b/src/test/regress/expected/reloptions.out
@@ -98,6 +98,42 @@ SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass;
{fillfactor=13,autovacuum_enabled=false}
(1 row)
+-- Tests for future (FIXME) ternary options
+-- behave as boolean option: accept unassigned name and truncated value
+DROP TABLE reloptions_test;
+CREATE TABLE reloptions_test(i INT) WITH (vacuum_truncate);
+SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass;
+ reloptions
+------------------------
+ {vacuum_truncate=true}
+(1 row)
+
+DROP TABLE reloptions_test;
+CREATE TABLE reloptions_test(i INT) WITH (vacuum_truncate=FaLS);
+SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass;
+ reloptions
+------------------------
+ {vacuum_truncate=fals}
+(1 row)
+
+-- preferred "true" alias is used when storing
+DROP TABLE reloptions_test;
+CREATE TABLE reloptions_test(i INT) WITH (vacuum_index_cleanup=on);
+SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass;
+ reloptions
+---------------------------
+ {vacuum_index_cleanup=on}
+(1 row)
+
+-- custom "third" value is available
+DROP TABLE reloptions_test;
+CREATE TABLE reloptions_test(i INT) WITH (vacuum_index_cleanup=auto);
+SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass;
+ reloptions
+-----------------------------
+ {vacuum_index_cleanup=auto}
+(1 row)
+
-- Test vacuum_truncate option
DROP TABLE reloptions_test;
CREATE TEMP TABLE reloptions_test(i INT NOT NULL, j text)
diff --git a/src/test/regress/sql/reloptions.sql b/src/test/regress/sql/reloptions.sql
index 24fbe0b478d..f5980dafcbc 100644
--- a/src/test/regress/sql/reloptions.sql
+++ b/src/test/regress/sql/reloptions.sql
@@ -59,6 +59,27 @@ UPDATE pg_class
ALTER TABLE reloptions_test RESET (illegal_option);
SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass;
+-- Tests for future (FIXME) ternary options
+
+-- behave as boolean option: accept unassigned name and truncated value
+DROP TABLE reloptions_test;
+CREATE TABLE reloptions_test(i INT) WITH (vacuum_truncate);
+SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass;
+
+DROP TABLE reloptions_test;
+CREATE TABLE reloptions_test(i INT) WITH (vacuum_truncate=FaLS);
+SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass;
+
+-- preferred "true" alias is used when storing
+DROP TABLE reloptions_test;
+CREATE TABLE reloptions_test(i INT) WITH (vacuum_index_cleanup=on);
+SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass;
+
+-- custom "third" value is available
+DROP TABLE reloptions_test;
+CREATE TABLE reloptions_test(i INT) WITH (vacuum_index_cleanup=auto);
+SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass;
+
-- Test vacuum_truncate option
DROP TABLE reloptions_test;
diff --git a/src/tools/pgindent/typedefs.list b/src/tools/pgindent/typedefs.list
index 3f3a888fd0e..1c8610fd46c 100644
--- a/src/tools/pgindent/typedefs.list
+++ b/src/tools/pgindent/typedefs.list
@@ -3952,6 +3952,7 @@ pg_sha512_ctx
pg_snapshot
pg_special_case
pg_stack_base_t
+pg_ternary
pg_time_t
pg_time_usec_t
pg_tz
@@ -4079,6 +4080,7 @@ relopt_kind
relopt_parse_elt
relopt_real
relopt_string
+relopt_ternary
relopt_type
relopt_value
relopts_validator
--
2.47.3
--c36t3fndxhkzbl5z--
^ permalink raw reply [nested|flat] 868+ messages in thread
* [PATCH v3] Introduce ternary reloptions
@ 2026-01-16 15:04 Álvaro Herrera <[email protected]>
0 siblings, 0 replies; 868+ messages in thread
From: Álvaro Herrera @ 2026-01-16 15:04 UTC (permalink / raw)
Introduce ternary reloption as a replacement for current
`vacuum_truncate` implementation. Remove the `vacuum_truncate_set`
separate flag and use TERNARY_UNSET instead.
This could also be used for other options such as `vacuum_index_cleanup`
and `buffering`, but lets get the scaffolding in first.
Discussion: https://postgr.es/m/3474141.usfYGdeWWP@thinkpad-pgpro
---
src/backend/access/common/reloptions.c | 137 ++++++++++++++++++-----
src/backend/commands/vacuum.c | 4 +-
src/include/access/reloptions.h | 27 ++---
src/include/postgres.h | 15 +++
src/include/utils/rel.h | 3 +-
src/test/regress/expected/reloptions.out | 36 ++++++
src/test/regress/sql/reloptions.sql | 21 ++++
src/tools/pgindent/typedefs.list | 2 +
8 files changed, 202 insertions(+), 43 deletions(-)
diff --git a/src/backend/access/common/reloptions.c b/src/backend/access/common/reloptions.c
index 0b83f98ed5f..6f1f577581a 100644
--- a/src/backend/access/common/reloptions.c
+++ b/src/backend/access/common/reloptions.c
@@ -40,9 +40,9 @@
*
* To add an option:
*
- * (i) decide on a type (bool, integer, real, enum, string), name, default
- * value, upper and lower bounds (if applicable); for strings, consider a
- * validation routine.
+ * (i) decide on a type (bool, ternary, integer, real, enum, string), name,
+ * default value, upper and lower bounds (if applicable); for strings,
+ * consider a validation routine.
* (ii) add a record below (or use add_<type>_reloption).
* (iii) add it to the appropriate options struct (perhaps StdRdOptions)
* (iv) add it to the appropriate handling routine (perhaps
@@ -147,15 +147,6 @@ static relopt_bool boolRelOpts[] =
},
false
},
- {
- {
- "vacuum_truncate",
- "Enables vacuum to truncate empty pages at the end of this table",
- RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
- ShareUpdateExclusiveLock
- },
- true
- },
{
{
"deduplicate_items",
@@ -170,6 +161,25 @@ static relopt_bool boolRelOpts[] =
{{NULL}}
};
+static relopt_ternary ternaryRelOpts[] =
+{
+ {
+ {
+ "vacuum_truncate",
+ "Enables vacuum to truncate empty pages at the end of this table",
+ RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
+ ShareUpdateExclusiveLock
+ },
+ TERNARY_UNSET
+ },
+ /* list terminator */
+ {
+ {
+ NULL
+ }
+ }
+};
+
static relopt_int intRelOpts[] =
{
{
@@ -609,6 +619,13 @@ initialize_reloptions(void)
boolRelOpts[i].gen.lockmode));
j++;
}
+ for (i = 0; ternaryRelOpts[i].gen.name; i++)
+ {
+ Assert(DoLockModesConflict(ternaryRelOpts[i].gen.lockmode,
+ ternaryRelOpts[i].gen.lockmode));
+ j++;
+ }
+
for (i = 0; intRelOpts[i].gen.name; i++)
{
Assert(DoLockModesConflict(intRelOpts[i].gen.lockmode,
@@ -649,6 +666,14 @@ initialize_reloptions(void)
j++;
}
+ for (i = 0; ternaryRelOpts[i].gen.name; i++)
+ {
+ relOpts[j] = &ternaryRelOpts[i].gen;
+ relOpts[j]->type = RELOPT_TYPE_TERNARY;
+ relOpts[j]->namelen = strlen(relOpts[j]->name);
+ j++;
+ }
+
for (i = 0; intRelOpts[i].gen.name; i++)
{
relOpts[j] = &intRelOpts[i].gen;
@@ -809,6 +834,9 @@ allocate_reloption(bits32 kinds, int type, const char *name, const char *desc,
case RELOPT_TYPE_BOOL:
size = sizeof(relopt_bool);
break;
+ case RELOPT_TYPE_TERNARY:
+ size = sizeof(relopt_ternary);
+ break;
case RELOPT_TYPE_INT:
size = sizeof(relopt_int);
break;
@@ -892,6 +920,57 @@ add_local_bool_reloption(local_relopts *relopts, const char *name,
add_local_reloption(relopts, (relopt_gen *) newoption, offset);
}
+/*
+ * init_ternary_reloption
+ * Allocate and initialize a new ternary reloption
+ */
+static relopt_ternary *
+init_ternary_reloption(bits32 kinds, const char *name, const char *desc,
+ pg_ternary default_val, LOCKMODE lockmode)
+{
+ relopt_ternary *newoption;
+
+ newoption = (relopt_ternary *)
+ allocate_reloption(kinds, RELOPT_TYPE_TERNARY, name, desc, lockmode);
+ newoption->default_val = default_val;
+
+ return newoption;
+}
+
+/*
+ * add_ternary_reloption
+ * Add a new ternary reloption
+ */
+void
+add_ternary_reloption(bits32 kinds, const char *name, const char *desc,
+ pg_ternary default_val, LOCKMODE lockmode)
+{
+ relopt_ternary *newoption;
+
+ newoption =
+ init_ternary_reloption(kinds, name, desc, default_val, lockmode);
+
+ add_reloption((relopt_gen *) newoption);
+}
+
+/*
+ * add_local_ternary_reloption
+ * Add a new ternary local reloption
+ *
+ * 'offset' is offset of ternary-typed field.
+ */
+void
+add_local_ternary_reloption(local_relopts *relopts, const char *name,
+ const char *desc, pg_ternary default_val,
+ int offset)
+{
+ relopt_ternary *newoption;
+
+ newoption =
+ init_ternary_reloption(RELOPT_KIND_LOCAL, name, desc, default_val, 0);
+
+ add_local_reloption(relopts, (relopt_gen *) newoption, offset);
+}
/*
* init_real_reloption
@@ -1626,6 +1705,19 @@ parse_one_reloption(relopt_value *option, char *text_str, int text_len,
option->gen->name, value)));
}
break;
+ case RELOPT_TYPE_TERNARY:
+ {
+ bool b;
+
+ parsed = parse_bool(value, &b);
+ option->values.ternary_val = b ? TERNARY_TRUE : TERNARY_FALSE;
+ if (validate && !parsed)
+ ereport(ERROR,
+ (errcode(ERRCODE_INVALID_PARAMETER_VALUE),
+ errmsg("invalid value for ternary option \"%s\": %s",
+ option->gen->name, value)));
+ }
+ break;
case RELOPT_TYPE_INT:
{
relopt_int *optint = (relopt_int *) option->gen;
@@ -1789,17 +1881,6 @@ fillRelOptions(void *rdopts, Size basesize,
char *itempos = ((char *) rdopts) + elems[j].offset;
char *string_val;
- /*
- * If isset_offset is provided, store whether the reloption is
- * set there.
- */
- if (elems[j].isset_offset > 0)
- {
- char *setpos = ((char *) rdopts) + elems[j].isset_offset;
-
- *(bool *) setpos = options[i].isset;
- }
-
switch (options[i].gen->type)
{
case RELOPT_TYPE_BOOL:
@@ -1807,6 +1888,11 @@ fillRelOptions(void *rdopts, Size basesize,
options[i].values.bool_val :
((relopt_bool *) options[i].gen)->default_val;
break;
+ case RELOPT_TYPE_TERNARY:
+ *(pg_ternary *) itempos = options[i].isset ?
+ options[i].values.ternary_val :
+ ((relopt_ternary *) options[i].gen)->default_val;
+ break;
case RELOPT_TYPE_INT:
*(int *) itempos = options[i].isset ?
options[i].values.int_val :
@@ -1923,8 +2009,8 @@ default_reloptions(Datum reloptions, bool validate, relopt_kind kind)
offsetof(StdRdOptions, parallel_workers)},
{"vacuum_index_cleanup", RELOPT_TYPE_ENUM,
offsetof(StdRdOptions, vacuum_index_cleanup)},
- {"vacuum_truncate", RELOPT_TYPE_BOOL,
- offsetof(StdRdOptions, vacuum_truncate), offsetof(StdRdOptions, vacuum_truncate_set)},
+ {"vacuum_truncate", RELOPT_TYPE_TERNARY,
+ offsetof(StdRdOptions, vacuum_truncate)},
{"vacuum_max_eager_freeze_failure_rate", RELOPT_TYPE_REAL,
offsetof(StdRdOptions, vacuum_max_eager_freeze_failure_rate)}
};
@@ -2004,7 +2090,6 @@ build_local_reloptions(local_relopts *relopts, Datum options, bool validate)
elems[i].optname = opt->option->name;
elems[i].opttype = opt->option->type;
elems[i].offset = opt->offset;
- elems[i].isset_offset = 0; /* not supported for local relopts yet */
i++;
}
diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c
index aa4fbec143f..696eab9bd97 100644
--- a/src/backend/commands/vacuum.c
+++ b/src/backend/commands/vacuum.c
@@ -2224,9 +2224,9 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams params,
{
StdRdOptions *opts = (StdRdOptions *) rel->rd_options;
- if (opts && opts->vacuum_truncate_set)
+ if (opts && opts->vacuum_truncate != TERNARY_UNSET)
{
- if (opts->vacuum_truncate)
+ if (opts->vacuum_truncate == TERNARY_TRUE)
params.truncate = VACOPTVALUE_ENABLED;
else
params.truncate = VACOPTVALUE_DISABLED;
diff --git a/src/include/access/reloptions.h b/src/include/access/reloptions.h
index 2f08e1b0cf0..dfbef2babf2 100644
--- a/src/include/access/reloptions.h
+++ b/src/include/access/reloptions.h
@@ -29,6 +29,7 @@
typedef enum relopt_type
{
RELOPT_TYPE_BOOL,
+ RELOPT_TYPE_TERNARY, /* on, off, unset */
RELOPT_TYPE_INT,
RELOPT_TYPE_REAL,
RELOPT_TYPE_ENUM,
@@ -80,6 +81,7 @@ typedef struct relopt_value
union
{
bool bool_val;
+ pg_ternary ternary_val;
int int_val;
double real_val;
int enum_val;
@@ -94,6 +96,12 @@ typedef struct relopt_bool
bool default_val;
} relopt_bool;
+typedef struct relopt_ternary
+{
+ relopt_gen gen;
+ int default_val;
+} relopt_ternary;
+
typedef struct relopt_int
{
relopt_gen gen;
@@ -152,19 +160,6 @@ typedef struct
const char *optname; /* option's name */
relopt_type opttype; /* option's datatype */
int offset; /* offset of field in result struct */
-
- /*
- * isset_offset is an optional offset of a field in the result struct that
- * stores whether the option is explicitly set for the relation or if it
- * just picked up the default value. In most cases, this can be
- * accomplished by giving the reloption a special out-of-range default
- * value (e.g., some integer reloptions use -2), but this isn't always
- * possible. For example, a Boolean reloption cannot be given an
- * out-of-range default, so we need another way to discover the source of
- * its value. This offset is only used if given a value greater than
- * zero.
- */
- int isset_offset;
} relopt_parse_elt;
/* Local reloption definition */
@@ -195,6 +190,9 @@ typedef struct local_relopts
extern relopt_kind add_reloption_kind(void);
extern void add_bool_reloption(bits32 kinds, const char *name, const char *desc,
bool default_val, LOCKMODE lockmode);
+extern void add_ternary_reloption(bits32 kinds, const char *name,
+ const char *desc, pg_ternary default_val,
+ LOCKMODE lockmode);
extern void add_int_reloption(bits32 kinds, const char *name, const char *desc,
int default_val, int min_val, int max_val,
LOCKMODE lockmode);
@@ -214,6 +212,9 @@ extern void register_reloptions_validator(local_relopts *relopts,
extern void add_local_bool_reloption(local_relopts *relopts, const char *name,
const char *desc, bool default_val,
int offset);
+extern void add_local_ternary_reloption(local_relopts *relopts,
+ const char *name, const char *desc,
+ pg_ternary default_val, int offset);
extern void add_local_int_reloption(local_relopts *relopts, const char *name,
const char *desc, int default_val,
int min_val, int max_val, int offset);
diff --git a/src/include/postgres.h b/src/include/postgres.h
index 7d93fbce709..47c88780776 100644
--- a/src/include/postgres.h
+++ b/src/include/postgres.h
@@ -543,6 +543,21 @@ Float8GetDatum(float8 X)
* ----------------------------------------------------------------
*/
+/*
+ * pg_ternary
+ * Boolean value with an extra "unset" value
+ *
+ * This enum can be used for values that want to distinguish between true,
+ * false, and unset.
+*/
+
+typedef enum pg_ternary
+{
+ TERNARY_FALSE = 0,
+ TERNARY_TRUE = 1,
+ TERNARY_UNSET = -1
+} pg_ternary;
+
/*
* NON_EXEC_STATIC: It's sometimes useful to define a variable or function
* that is normally static but extern when using EXEC_BACKEND (see
diff --git a/src/include/utils/rel.h b/src/include/utils/rel.h
index d03ab247788..5feb18a5373 100644
--- a/src/include/utils/rel.h
+++ b/src/include/utils/rel.h
@@ -347,8 +347,7 @@ typedef struct StdRdOptions
bool user_catalog_table; /* use as an additional catalog relation */
int parallel_workers; /* max number of parallel workers */
StdRdOptIndexCleanup vacuum_index_cleanup; /* controls index vacuuming */
- bool vacuum_truncate; /* enables vacuum to truncate a relation */
- bool vacuum_truncate_set; /* whether vacuum_truncate is set */
+ pg_ternary vacuum_truncate; /* enables vacuum to truncate a relation */
/*
* Fraction of pages in a relation that vacuum can eagerly scan and fail
diff --git a/src/test/regress/expected/reloptions.out b/src/test/regress/expected/reloptions.out
index 9de19b4e3f1..1c99f79ab01 100644
--- a/src/test/regress/expected/reloptions.out
+++ b/src/test/regress/expected/reloptions.out
@@ -98,6 +98,42 @@ SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass;
{fillfactor=13,autovacuum_enabled=false}
(1 row)
+-- Tests for future (FIXME) ternary options
+-- behave as boolean option: accept unassigned name and truncated value
+DROP TABLE reloptions_test;
+CREATE TABLE reloptions_test(i INT) WITH (vacuum_truncate);
+SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass;
+ reloptions
+------------------------
+ {vacuum_truncate=true}
+(1 row)
+
+DROP TABLE reloptions_test;
+CREATE TABLE reloptions_test(i INT) WITH (vacuum_truncate=FaLS);
+SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass;
+ reloptions
+------------------------
+ {vacuum_truncate=fals}
+(1 row)
+
+-- preferred "true" alias is used when storing
+DROP TABLE reloptions_test;
+CREATE TABLE reloptions_test(i INT) WITH (vacuum_index_cleanup=on);
+SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass;
+ reloptions
+---------------------------
+ {vacuum_index_cleanup=on}
+(1 row)
+
+-- custom "third" value is available
+DROP TABLE reloptions_test;
+CREATE TABLE reloptions_test(i INT) WITH (vacuum_index_cleanup=auto);
+SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass;
+ reloptions
+-----------------------------
+ {vacuum_index_cleanup=auto}
+(1 row)
+
-- Test vacuum_truncate option
DROP TABLE reloptions_test;
CREATE TEMP TABLE reloptions_test(i INT NOT NULL, j text)
diff --git a/src/test/regress/sql/reloptions.sql b/src/test/regress/sql/reloptions.sql
index 24fbe0b478d..f5980dafcbc 100644
--- a/src/test/regress/sql/reloptions.sql
+++ b/src/test/regress/sql/reloptions.sql
@@ -59,6 +59,27 @@ UPDATE pg_class
ALTER TABLE reloptions_test RESET (illegal_option);
SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass;
+-- Tests for future (FIXME) ternary options
+
+-- behave as boolean option: accept unassigned name and truncated value
+DROP TABLE reloptions_test;
+CREATE TABLE reloptions_test(i INT) WITH (vacuum_truncate);
+SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass;
+
+DROP TABLE reloptions_test;
+CREATE TABLE reloptions_test(i INT) WITH (vacuum_truncate=FaLS);
+SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass;
+
+-- preferred "true" alias is used when storing
+DROP TABLE reloptions_test;
+CREATE TABLE reloptions_test(i INT) WITH (vacuum_index_cleanup=on);
+SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass;
+
+-- custom "third" value is available
+DROP TABLE reloptions_test;
+CREATE TABLE reloptions_test(i INT) WITH (vacuum_index_cleanup=auto);
+SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass;
+
-- Test vacuum_truncate option
DROP TABLE reloptions_test;
diff --git a/src/tools/pgindent/typedefs.list b/src/tools/pgindent/typedefs.list
index 3f3a888fd0e..1c8610fd46c 100644
--- a/src/tools/pgindent/typedefs.list
+++ b/src/tools/pgindent/typedefs.list
@@ -3952,6 +3952,7 @@ pg_sha512_ctx
pg_snapshot
pg_special_case
pg_stack_base_t
+pg_ternary
pg_time_t
pg_time_usec_t
pg_tz
@@ -4079,6 +4080,7 @@ relopt_kind
relopt_parse_elt
relopt_real
relopt_string
+relopt_ternary
relopt_type
relopt_value
relopts_validator
--
2.47.3
--c36t3fndxhkzbl5z--
^ permalink raw reply [nested|flat] 868+ messages in thread
* [PATCH v3] Introduce ternary reloptions
@ 2026-01-16 15:04 Álvaro Herrera <[email protected]>
0 siblings, 0 replies; 868+ messages in thread
From: Álvaro Herrera @ 2026-01-16 15:04 UTC (permalink / raw)
Introduce ternary reloption as a replacement for current
`vacuum_truncate` implementation. Remove the `vacuum_truncate_set`
separate flag and use TERNARY_UNSET instead.
This could also be used for other options such as `vacuum_index_cleanup`
and `buffering`, but lets get the scaffolding in first.
Discussion: https://postgr.es/m/3474141.usfYGdeWWP@thinkpad-pgpro
---
src/backend/access/common/reloptions.c | 137 ++++++++++++++++++-----
src/backend/commands/vacuum.c | 4 +-
src/include/access/reloptions.h | 27 ++---
src/include/postgres.h | 15 +++
src/include/utils/rel.h | 3 +-
src/test/regress/expected/reloptions.out | 36 ++++++
src/test/regress/sql/reloptions.sql | 21 ++++
src/tools/pgindent/typedefs.list | 2 +
8 files changed, 202 insertions(+), 43 deletions(-)
diff --git a/src/backend/access/common/reloptions.c b/src/backend/access/common/reloptions.c
index 0b83f98ed5f..6f1f577581a 100644
--- a/src/backend/access/common/reloptions.c
+++ b/src/backend/access/common/reloptions.c
@@ -40,9 +40,9 @@
*
* To add an option:
*
- * (i) decide on a type (bool, integer, real, enum, string), name, default
- * value, upper and lower bounds (if applicable); for strings, consider a
- * validation routine.
+ * (i) decide on a type (bool, ternary, integer, real, enum, string), name,
+ * default value, upper and lower bounds (if applicable); for strings,
+ * consider a validation routine.
* (ii) add a record below (or use add_<type>_reloption).
* (iii) add it to the appropriate options struct (perhaps StdRdOptions)
* (iv) add it to the appropriate handling routine (perhaps
@@ -147,15 +147,6 @@ static relopt_bool boolRelOpts[] =
},
false
},
- {
- {
- "vacuum_truncate",
- "Enables vacuum to truncate empty pages at the end of this table",
- RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
- ShareUpdateExclusiveLock
- },
- true
- },
{
{
"deduplicate_items",
@@ -170,6 +161,25 @@ static relopt_bool boolRelOpts[] =
{{NULL}}
};
+static relopt_ternary ternaryRelOpts[] =
+{
+ {
+ {
+ "vacuum_truncate",
+ "Enables vacuum to truncate empty pages at the end of this table",
+ RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
+ ShareUpdateExclusiveLock
+ },
+ TERNARY_UNSET
+ },
+ /* list terminator */
+ {
+ {
+ NULL
+ }
+ }
+};
+
static relopt_int intRelOpts[] =
{
{
@@ -609,6 +619,13 @@ initialize_reloptions(void)
boolRelOpts[i].gen.lockmode));
j++;
}
+ for (i = 0; ternaryRelOpts[i].gen.name; i++)
+ {
+ Assert(DoLockModesConflict(ternaryRelOpts[i].gen.lockmode,
+ ternaryRelOpts[i].gen.lockmode));
+ j++;
+ }
+
for (i = 0; intRelOpts[i].gen.name; i++)
{
Assert(DoLockModesConflict(intRelOpts[i].gen.lockmode,
@@ -649,6 +666,14 @@ initialize_reloptions(void)
j++;
}
+ for (i = 0; ternaryRelOpts[i].gen.name; i++)
+ {
+ relOpts[j] = &ternaryRelOpts[i].gen;
+ relOpts[j]->type = RELOPT_TYPE_TERNARY;
+ relOpts[j]->namelen = strlen(relOpts[j]->name);
+ j++;
+ }
+
for (i = 0; intRelOpts[i].gen.name; i++)
{
relOpts[j] = &intRelOpts[i].gen;
@@ -809,6 +834,9 @@ allocate_reloption(bits32 kinds, int type, const char *name, const char *desc,
case RELOPT_TYPE_BOOL:
size = sizeof(relopt_bool);
break;
+ case RELOPT_TYPE_TERNARY:
+ size = sizeof(relopt_ternary);
+ break;
case RELOPT_TYPE_INT:
size = sizeof(relopt_int);
break;
@@ -892,6 +920,57 @@ add_local_bool_reloption(local_relopts *relopts, const char *name,
add_local_reloption(relopts, (relopt_gen *) newoption, offset);
}
+/*
+ * init_ternary_reloption
+ * Allocate and initialize a new ternary reloption
+ */
+static relopt_ternary *
+init_ternary_reloption(bits32 kinds, const char *name, const char *desc,
+ pg_ternary default_val, LOCKMODE lockmode)
+{
+ relopt_ternary *newoption;
+
+ newoption = (relopt_ternary *)
+ allocate_reloption(kinds, RELOPT_TYPE_TERNARY, name, desc, lockmode);
+ newoption->default_val = default_val;
+
+ return newoption;
+}
+
+/*
+ * add_ternary_reloption
+ * Add a new ternary reloption
+ */
+void
+add_ternary_reloption(bits32 kinds, const char *name, const char *desc,
+ pg_ternary default_val, LOCKMODE lockmode)
+{
+ relopt_ternary *newoption;
+
+ newoption =
+ init_ternary_reloption(kinds, name, desc, default_val, lockmode);
+
+ add_reloption((relopt_gen *) newoption);
+}
+
+/*
+ * add_local_ternary_reloption
+ * Add a new ternary local reloption
+ *
+ * 'offset' is offset of ternary-typed field.
+ */
+void
+add_local_ternary_reloption(local_relopts *relopts, const char *name,
+ const char *desc, pg_ternary default_val,
+ int offset)
+{
+ relopt_ternary *newoption;
+
+ newoption =
+ init_ternary_reloption(RELOPT_KIND_LOCAL, name, desc, default_val, 0);
+
+ add_local_reloption(relopts, (relopt_gen *) newoption, offset);
+}
/*
* init_real_reloption
@@ -1626,6 +1705,19 @@ parse_one_reloption(relopt_value *option, char *text_str, int text_len,
option->gen->name, value)));
}
break;
+ case RELOPT_TYPE_TERNARY:
+ {
+ bool b;
+
+ parsed = parse_bool(value, &b);
+ option->values.ternary_val = b ? TERNARY_TRUE : TERNARY_FALSE;
+ if (validate && !parsed)
+ ereport(ERROR,
+ (errcode(ERRCODE_INVALID_PARAMETER_VALUE),
+ errmsg("invalid value for ternary option \"%s\": %s",
+ option->gen->name, value)));
+ }
+ break;
case RELOPT_TYPE_INT:
{
relopt_int *optint = (relopt_int *) option->gen;
@@ -1789,17 +1881,6 @@ fillRelOptions(void *rdopts, Size basesize,
char *itempos = ((char *) rdopts) + elems[j].offset;
char *string_val;
- /*
- * If isset_offset is provided, store whether the reloption is
- * set there.
- */
- if (elems[j].isset_offset > 0)
- {
- char *setpos = ((char *) rdopts) + elems[j].isset_offset;
-
- *(bool *) setpos = options[i].isset;
- }
-
switch (options[i].gen->type)
{
case RELOPT_TYPE_BOOL:
@@ -1807,6 +1888,11 @@ fillRelOptions(void *rdopts, Size basesize,
options[i].values.bool_val :
((relopt_bool *) options[i].gen)->default_val;
break;
+ case RELOPT_TYPE_TERNARY:
+ *(pg_ternary *) itempos = options[i].isset ?
+ options[i].values.ternary_val :
+ ((relopt_ternary *) options[i].gen)->default_val;
+ break;
case RELOPT_TYPE_INT:
*(int *) itempos = options[i].isset ?
options[i].values.int_val :
@@ -1923,8 +2009,8 @@ default_reloptions(Datum reloptions, bool validate, relopt_kind kind)
offsetof(StdRdOptions, parallel_workers)},
{"vacuum_index_cleanup", RELOPT_TYPE_ENUM,
offsetof(StdRdOptions, vacuum_index_cleanup)},
- {"vacuum_truncate", RELOPT_TYPE_BOOL,
- offsetof(StdRdOptions, vacuum_truncate), offsetof(StdRdOptions, vacuum_truncate_set)},
+ {"vacuum_truncate", RELOPT_TYPE_TERNARY,
+ offsetof(StdRdOptions, vacuum_truncate)},
{"vacuum_max_eager_freeze_failure_rate", RELOPT_TYPE_REAL,
offsetof(StdRdOptions, vacuum_max_eager_freeze_failure_rate)}
};
@@ -2004,7 +2090,6 @@ build_local_reloptions(local_relopts *relopts, Datum options, bool validate)
elems[i].optname = opt->option->name;
elems[i].opttype = opt->option->type;
elems[i].offset = opt->offset;
- elems[i].isset_offset = 0; /* not supported for local relopts yet */
i++;
}
diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c
index aa4fbec143f..696eab9bd97 100644
--- a/src/backend/commands/vacuum.c
+++ b/src/backend/commands/vacuum.c
@@ -2224,9 +2224,9 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams params,
{
StdRdOptions *opts = (StdRdOptions *) rel->rd_options;
- if (opts && opts->vacuum_truncate_set)
+ if (opts && opts->vacuum_truncate != TERNARY_UNSET)
{
- if (opts->vacuum_truncate)
+ if (opts->vacuum_truncate == TERNARY_TRUE)
params.truncate = VACOPTVALUE_ENABLED;
else
params.truncate = VACOPTVALUE_DISABLED;
diff --git a/src/include/access/reloptions.h b/src/include/access/reloptions.h
index 2f08e1b0cf0..dfbef2babf2 100644
--- a/src/include/access/reloptions.h
+++ b/src/include/access/reloptions.h
@@ -29,6 +29,7 @@
typedef enum relopt_type
{
RELOPT_TYPE_BOOL,
+ RELOPT_TYPE_TERNARY, /* on, off, unset */
RELOPT_TYPE_INT,
RELOPT_TYPE_REAL,
RELOPT_TYPE_ENUM,
@@ -80,6 +81,7 @@ typedef struct relopt_value
union
{
bool bool_val;
+ pg_ternary ternary_val;
int int_val;
double real_val;
int enum_val;
@@ -94,6 +96,12 @@ typedef struct relopt_bool
bool default_val;
} relopt_bool;
+typedef struct relopt_ternary
+{
+ relopt_gen gen;
+ int default_val;
+} relopt_ternary;
+
typedef struct relopt_int
{
relopt_gen gen;
@@ -152,19 +160,6 @@ typedef struct
const char *optname; /* option's name */
relopt_type opttype; /* option's datatype */
int offset; /* offset of field in result struct */
-
- /*
- * isset_offset is an optional offset of a field in the result struct that
- * stores whether the option is explicitly set for the relation or if it
- * just picked up the default value. In most cases, this can be
- * accomplished by giving the reloption a special out-of-range default
- * value (e.g., some integer reloptions use -2), but this isn't always
- * possible. For example, a Boolean reloption cannot be given an
- * out-of-range default, so we need another way to discover the source of
- * its value. This offset is only used if given a value greater than
- * zero.
- */
- int isset_offset;
} relopt_parse_elt;
/* Local reloption definition */
@@ -195,6 +190,9 @@ typedef struct local_relopts
extern relopt_kind add_reloption_kind(void);
extern void add_bool_reloption(bits32 kinds, const char *name, const char *desc,
bool default_val, LOCKMODE lockmode);
+extern void add_ternary_reloption(bits32 kinds, const char *name,
+ const char *desc, pg_ternary default_val,
+ LOCKMODE lockmode);
extern void add_int_reloption(bits32 kinds, const char *name, const char *desc,
int default_val, int min_val, int max_val,
LOCKMODE lockmode);
@@ -214,6 +212,9 @@ extern void register_reloptions_validator(local_relopts *relopts,
extern void add_local_bool_reloption(local_relopts *relopts, const char *name,
const char *desc, bool default_val,
int offset);
+extern void add_local_ternary_reloption(local_relopts *relopts,
+ const char *name, const char *desc,
+ pg_ternary default_val, int offset);
extern void add_local_int_reloption(local_relopts *relopts, const char *name,
const char *desc, int default_val,
int min_val, int max_val, int offset);
diff --git a/src/include/postgres.h b/src/include/postgres.h
index 7d93fbce709..47c88780776 100644
--- a/src/include/postgres.h
+++ b/src/include/postgres.h
@@ -543,6 +543,21 @@ Float8GetDatum(float8 X)
* ----------------------------------------------------------------
*/
+/*
+ * pg_ternary
+ * Boolean value with an extra "unset" value
+ *
+ * This enum can be used for values that want to distinguish between true,
+ * false, and unset.
+*/
+
+typedef enum pg_ternary
+{
+ TERNARY_FALSE = 0,
+ TERNARY_TRUE = 1,
+ TERNARY_UNSET = -1
+} pg_ternary;
+
/*
* NON_EXEC_STATIC: It's sometimes useful to define a variable or function
* that is normally static but extern when using EXEC_BACKEND (see
diff --git a/src/include/utils/rel.h b/src/include/utils/rel.h
index d03ab247788..5feb18a5373 100644
--- a/src/include/utils/rel.h
+++ b/src/include/utils/rel.h
@@ -347,8 +347,7 @@ typedef struct StdRdOptions
bool user_catalog_table; /* use as an additional catalog relation */
int parallel_workers; /* max number of parallel workers */
StdRdOptIndexCleanup vacuum_index_cleanup; /* controls index vacuuming */
- bool vacuum_truncate; /* enables vacuum to truncate a relation */
- bool vacuum_truncate_set; /* whether vacuum_truncate is set */
+ pg_ternary vacuum_truncate; /* enables vacuum to truncate a relation */
/*
* Fraction of pages in a relation that vacuum can eagerly scan and fail
diff --git a/src/test/regress/expected/reloptions.out b/src/test/regress/expected/reloptions.out
index 9de19b4e3f1..1c99f79ab01 100644
--- a/src/test/regress/expected/reloptions.out
+++ b/src/test/regress/expected/reloptions.out
@@ -98,6 +98,42 @@ SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass;
{fillfactor=13,autovacuum_enabled=false}
(1 row)
+-- Tests for future (FIXME) ternary options
+-- behave as boolean option: accept unassigned name and truncated value
+DROP TABLE reloptions_test;
+CREATE TABLE reloptions_test(i INT) WITH (vacuum_truncate);
+SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass;
+ reloptions
+------------------------
+ {vacuum_truncate=true}
+(1 row)
+
+DROP TABLE reloptions_test;
+CREATE TABLE reloptions_test(i INT) WITH (vacuum_truncate=FaLS);
+SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass;
+ reloptions
+------------------------
+ {vacuum_truncate=fals}
+(1 row)
+
+-- preferred "true" alias is used when storing
+DROP TABLE reloptions_test;
+CREATE TABLE reloptions_test(i INT) WITH (vacuum_index_cleanup=on);
+SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass;
+ reloptions
+---------------------------
+ {vacuum_index_cleanup=on}
+(1 row)
+
+-- custom "third" value is available
+DROP TABLE reloptions_test;
+CREATE TABLE reloptions_test(i INT) WITH (vacuum_index_cleanup=auto);
+SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass;
+ reloptions
+-----------------------------
+ {vacuum_index_cleanup=auto}
+(1 row)
+
-- Test vacuum_truncate option
DROP TABLE reloptions_test;
CREATE TEMP TABLE reloptions_test(i INT NOT NULL, j text)
diff --git a/src/test/regress/sql/reloptions.sql b/src/test/regress/sql/reloptions.sql
index 24fbe0b478d..f5980dafcbc 100644
--- a/src/test/regress/sql/reloptions.sql
+++ b/src/test/regress/sql/reloptions.sql
@@ -59,6 +59,27 @@ UPDATE pg_class
ALTER TABLE reloptions_test RESET (illegal_option);
SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass;
+-- Tests for future (FIXME) ternary options
+
+-- behave as boolean option: accept unassigned name and truncated value
+DROP TABLE reloptions_test;
+CREATE TABLE reloptions_test(i INT) WITH (vacuum_truncate);
+SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass;
+
+DROP TABLE reloptions_test;
+CREATE TABLE reloptions_test(i INT) WITH (vacuum_truncate=FaLS);
+SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass;
+
+-- preferred "true" alias is used when storing
+DROP TABLE reloptions_test;
+CREATE TABLE reloptions_test(i INT) WITH (vacuum_index_cleanup=on);
+SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass;
+
+-- custom "third" value is available
+DROP TABLE reloptions_test;
+CREATE TABLE reloptions_test(i INT) WITH (vacuum_index_cleanup=auto);
+SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass;
+
-- Test vacuum_truncate option
DROP TABLE reloptions_test;
diff --git a/src/tools/pgindent/typedefs.list b/src/tools/pgindent/typedefs.list
index 3f3a888fd0e..1c8610fd46c 100644
--- a/src/tools/pgindent/typedefs.list
+++ b/src/tools/pgindent/typedefs.list
@@ -3952,6 +3952,7 @@ pg_sha512_ctx
pg_snapshot
pg_special_case
pg_stack_base_t
+pg_ternary
pg_time_t
pg_time_usec_t
pg_tz
@@ -4079,6 +4080,7 @@ relopt_kind
relopt_parse_elt
relopt_real
relopt_string
+relopt_ternary
relopt_type
relopt_value
relopts_validator
--
2.47.3
--c36t3fndxhkzbl5z--
^ permalink raw reply [nested|flat] 868+ messages in thread
* [PATCH v3] Introduce ternary reloptions
@ 2026-01-16 15:04 Álvaro Herrera <[email protected]>
0 siblings, 0 replies; 868+ messages in thread
From: Álvaro Herrera @ 2026-01-16 15:04 UTC (permalink / raw)
Introduce ternary reloption as a replacement for current
`vacuum_truncate` implementation. Remove the `vacuum_truncate_set`
separate flag and use TERNARY_UNSET instead.
This could also be used for other options such as `vacuum_index_cleanup`
and `buffering`, but lets get the scaffolding in first.
Discussion: https://postgr.es/m/3474141.usfYGdeWWP@thinkpad-pgpro
---
src/backend/access/common/reloptions.c | 137 ++++++++++++++++++-----
src/backend/commands/vacuum.c | 4 +-
src/include/access/reloptions.h | 27 ++---
src/include/postgres.h | 15 +++
src/include/utils/rel.h | 3 +-
src/test/regress/expected/reloptions.out | 36 ++++++
src/test/regress/sql/reloptions.sql | 21 ++++
src/tools/pgindent/typedefs.list | 2 +
8 files changed, 202 insertions(+), 43 deletions(-)
diff --git a/src/backend/access/common/reloptions.c b/src/backend/access/common/reloptions.c
index 0b83f98ed5f..6f1f577581a 100644
--- a/src/backend/access/common/reloptions.c
+++ b/src/backend/access/common/reloptions.c
@@ -40,9 +40,9 @@
*
* To add an option:
*
- * (i) decide on a type (bool, integer, real, enum, string), name, default
- * value, upper and lower bounds (if applicable); for strings, consider a
- * validation routine.
+ * (i) decide on a type (bool, ternary, integer, real, enum, string), name,
+ * default value, upper and lower bounds (if applicable); for strings,
+ * consider a validation routine.
* (ii) add a record below (or use add_<type>_reloption).
* (iii) add it to the appropriate options struct (perhaps StdRdOptions)
* (iv) add it to the appropriate handling routine (perhaps
@@ -147,15 +147,6 @@ static relopt_bool boolRelOpts[] =
},
false
},
- {
- {
- "vacuum_truncate",
- "Enables vacuum to truncate empty pages at the end of this table",
- RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
- ShareUpdateExclusiveLock
- },
- true
- },
{
{
"deduplicate_items",
@@ -170,6 +161,25 @@ static relopt_bool boolRelOpts[] =
{{NULL}}
};
+static relopt_ternary ternaryRelOpts[] =
+{
+ {
+ {
+ "vacuum_truncate",
+ "Enables vacuum to truncate empty pages at the end of this table",
+ RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
+ ShareUpdateExclusiveLock
+ },
+ TERNARY_UNSET
+ },
+ /* list terminator */
+ {
+ {
+ NULL
+ }
+ }
+};
+
static relopt_int intRelOpts[] =
{
{
@@ -609,6 +619,13 @@ initialize_reloptions(void)
boolRelOpts[i].gen.lockmode));
j++;
}
+ for (i = 0; ternaryRelOpts[i].gen.name; i++)
+ {
+ Assert(DoLockModesConflict(ternaryRelOpts[i].gen.lockmode,
+ ternaryRelOpts[i].gen.lockmode));
+ j++;
+ }
+
for (i = 0; intRelOpts[i].gen.name; i++)
{
Assert(DoLockModesConflict(intRelOpts[i].gen.lockmode,
@@ -649,6 +666,14 @@ initialize_reloptions(void)
j++;
}
+ for (i = 0; ternaryRelOpts[i].gen.name; i++)
+ {
+ relOpts[j] = &ternaryRelOpts[i].gen;
+ relOpts[j]->type = RELOPT_TYPE_TERNARY;
+ relOpts[j]->namelen = strlen(relOpts[j]->name);
+ j++;
+ }
+
for (i = 0; intRelOpts[i].gen.name; i++)
{
relOpts[j] = &intRelOpts[i].gen;
@@ -809,6 +834,9 @@ allocate_reloption(bits32 kinds, int type, const char *name, const char *desc,
case RELOPT_TYPE_BOOL:
size = sizeof(relopt_bool);
break;
+ case RELOPT_TYPE_TERNARY:
+ size = sizeof(relopt_ternary);
+ break;
case RELOPT_TYPE_INT:
size = sizeof(relopt_int);
break;
@@ -892,6 +920,57 @@ add_local_bool_reloption(local_relopts *relopts, const char *name,
add_local_reloption(relopts, (relopt_gen *) newoption, offset);
}
+/*
+ * init_ternary_reloption
+ * Allocate and initialize a new ternary reloption
+ */
+static relopt_ternary *
+init_ternary_reloption(bits32 kinds, const char *name, const char *desc,
+ pg_ternary default_val, LOCKMODE lockmode)
+{
+ relopt_ternary *newoption;
+
+ newoption = (relopt_ternary *)
+ allocate_reloption(kinds, RELOPT_TYPE_TERNARY, name, desc, lockmode);
+ newoption->default_val = default_val;
+
+ return newoption;
+}
+
+/*
+ * add_ternary_reloption
+ * Add a new ternary reloption
+ */
+void
+add_ternary_reloption(bits32 kinds, const char *name, const char *desc,
+ pg_ternary default_val, LOCKMODE lockmode)
+{
+ relopt_ternary *newoption;
+
+ newoption =
+ init_ternary_reloption(kinds, name, desc, default_val, lockmode);
+
+ add_reloption((relopt_gen *) newoption);
+}
+
+/*
+ * add_local_ternary_reloption
+ * Add a new ternary local reloption
+ *
+ * 'offset' is offset of ternary-typed field.
+ */
+void
+add_local_ternary_reloption(local_relopts *relopts, const char *name,
+ const char *desc, pg_ternary default_val,
+ int offset)
+{
+ relopt_ternary *newoption;
+
+ newoption =
+ init_ternary_reloption(RELOPT_KIND_LOCAL, name, desc, default_val, 0);
+
+ add_local_reloption(relopts, (relopt_gen *) newoption, offset);
+}
/*
* init_real_reloption
@@ -1626,6 +1705,19 @@ parse_one_reloption(relopt_value *option, char *text_str, int text_len,
option->gen->name, value)));
}
break;
+ case RELOPT_TYPE_TERNARY:
+ {
+ bool b;
+
+ parsed = parse_bool(value, &b);
+ option->values.ternary_val = b ? TERNARY_TRUE : TERNARY_FALSE;
+ if (validate && !parsed)
+ ereport(ERROR,
+ (errcode(ERRCODE_INVALID_PARAMETER_VALUE),
+ errmsg("invalid value for ternary option \"%s\": %s",
+ option->gen->name, value)));
+ }
+ break;
case RELOPT_TYPE_INT:
{
relopt_int *optint = (relopt_int *) option->gen;
@@ -1789,17 +1881,6 @@ fillRelOptions(void *rdopts, Size basesize,
char *itempos = ((char *) rdopts) + elems[j].offset;
char *string_val;
- /*
- * If isset_offset is provided, store whether the reloption is
- * set there.
- */
- if (elems[j].isset_offset > 0)
- {
- char *setpos = ((char *) rdopts) + elems[j].isset_offset;
-
- *(bool *) setpos = options[i].isset;
- }
-
switch (options[i].gen->type)
{
case RELOPT_TYPE_BOOL:
@@ -1807,6 +1888,11 @@ fillRelOptions(void *rdopts, Size basesize,
options[i].values.bool_val :
((relopt_bool *) options[i].gen)->default_val;
break;
+ case RELOPT_TYPE_TERNARY:
+ *(pg_ternary *) itempos = options[i].isset ?
+ options[i].values.ternary_val :
+ ((relopt_ternary *) options[i].gen)->default_val;
+ break;
case RELOPT_TYPE_INT:
*(int *) itempos = options[i].isset ?
options[i].values.int_val :
@@ -1923,8 +2009,8 @@ default_reloptions(Datum reloptions, bool validate, relopt_kind kind)
offsetof(StdRdOptions, parallel_workers)},
{"vacuum_index_cleanup", RELOPT_TYPE_ENUM,
offsetof(StdRdOptions, vacuum_index_cleanup)},
- {"vacuum_truncate", RELOPT_TYPE_BOOL,
- offsetof(StdRdOptions, vacuum_truncate), offsetof(StdRdOptions, vacuum_truncate_set)},
+ {"vacuum_truncate", RELOPT_TYPE_TERNARY,
+ offsetof(StdRdOptions, vacuum_truncate)},
{"vacuum_max_eager_freeze_failure_rate", RELOPT_TYPE_REAL,
offsetof(StdRdOptions, vacuum_max_eager_freeze_failure_rate)}
};
@@ -2004,7 +2090,6 @@ build_local_reloptions(local_relopts *relopts, Datum options, bool validate)
elems[i].optname = opt->option->name;
elems[i].opttype = opt->option->type;
elems[i].offset = opt->offset;
- elems[i].isset_offset = 0; /* not supported for local relopts yet */
i++;
}
diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c
index aa4fbec143f..696eab9bd97 100644
--- a/src/backend/commands/vacuum.c
+++ b/src/backend/commands/vacuum.c
@@ -2224,9 +2224,9 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams params,
{
StdRdOptions *opts = (StdRdOptions *) rel->rd_options;
- if (opts && opts->vacuum_truncate_set)
+ if (opts && opts->vacuum_truncate != TERNARY_UNSET)
{
- if (opts->vacuum_truncate)
+ if (opts->vacuum_truncate == TERNARY_TRUE)
params.truncate = VACOPTVALUE_ENABLED;
else
params.truncate = VACOPTVALUE_DISABLED;
diff --git a/src/include/access/reloptions.h b/src/include/access/reloptions.h
index 2f08e1b0cf0..dfbef2babf2 100644
--- a/src/include/access/reloptions.h
+++ b/src/include/access/reloptions.h
@@ -29,6 +29,7 @@
typedef enum relopt_type
{
RELOPT_TYPE_BOOL,
+ RELOPT_TYPE_TERNARY, /* on, off, unset */
RELOPT_TYPE_INT,
RELOPT_TYPE_REAL,
RELOPT_TYPE_ENUM,
@@ -80,6 +81,7 @@ typedef struct relopt_value
union
{
bool bool_val;
+ pg_ternary ternary_val;
int int_val;
double real_val;
int enum_val;
@@ -94,6 +96,12 @@ typedef struct relopt_bool
bool default_val;
} relopt_bool;
+typedef struct relopt_ternary
+{
+ relopt_gen gen;
+ int default_val;
+} relopt_ternary;
+
typedef struct relopt_int
{
relopt_gen gen;
@@ -152,19 +160,6 @@ typedef struct
const char *optname; /* option's name */
relopt_type opttype; /* option's datatype */
int offset; /* offset of field in result struct */
-
- /*
- * isset_offset is an optional offset of a field in the result struct that
- * stores whether the option is explicitly set for the relation or if it
- * just picked up the default value. In most cases, this can be
- * accomplished by giving the reloption a special out-of-range default
- * value (e.g., some integer reloptions use -2), but this isn't always
- * possible. For example, a Boolean reloption cannot be given an
- * out-of-range default, so we need another way to discover the source of
- * its value. This offset is only used if given a value greater than
- * zero.
- */
- int isset_offset;
} relopt_parse_elt;
/* Local reloption definition */
@@ -195,6 +190,9 @@ typedef struct local_relopts
extern relopt_kind add_reloption_kind(void);
extern void add_bool_reloption(bits32 kinds, const char *name, const char *desc,
bool default_val, LOCKMODE lockmode);
+extern void add_ternary_reloption(bits32 kinds, const char *name,
+ const char *desc, pg_ternary default_val,
+ LOCKMODE lockmode);
extern void add_int_reloption(bits32 kinds, const char *name, const char *desc,
int default_val, int min_val, int max_val,
LOCKMODE lockmode);
@@ -214,6 +212,9 @@ extern void register_reloptions_validator(local_relopts *relopts,
extern void add_local_bool_reloption(local_relopts *relopts, const char *name,
const char *desc, bool default_val,
int offset);
+extern void add_local_ternary_reloption(local_relopts *relopts,
+ const char *name, const char *desc,
+ pg_ternary default_val, int offset);
extern void add_local_int_reloption(local_relopts *relopts, const char *name,
const char *desc, int default_val,
int min_val, int max_val, int offset);
diff --git a/src/include/postgres.h b/src/include/postgres.h
index 7d93fbce709..47c88780776 100644
--- a/src/include/postgres.h
+++ b/src/include/postgres.h
@@ -543,6 +543,21 @@ Float8GetDatum(float8 X)
* ----------------------------------------------------------------
*/
+/*
+ * pg_ternary
+ * Boolean value with an extra "unset" value
+ *
+ * This enum can be used for values that want to distinguish between true,
+ * false, and unset.
+*/
+
+typedef enum pg_ternary
+{
+ TERNARY_FALSE = 0,
+ TERNARY_TRUE = 1,
+ TERNARY_UNSET = -1
+} pg_ternary;
+
/*
* NON_EXEC_STATIC: It's sometimes useful to define a variable or function
* that is normally static but extern when using EXEC_BACKEND (see
diff --git a/src/include/utils/rel.h b/src/include/utils/rel.h
index d03ab247788..5feb18a5373 100644
--- a/src/include/utils/rel.h
+++ b/src/include/utils/rel.h
@@ -347,8 +347,7 @@ typedef struct StdRdOptions
bool user_catalog_table; /* use as an additional catalog relation */
int parallel_workers; /* max number of parallel workers */
StdRdOptIndexCleanup vacuum_index_cleanup; /* controls index vacuuming */
- bool vacuum_truncate; /* enables vacuum to truncate a relation */
- bool vacuum_truncate_set; /* whether vacuum_truncate is set */
+ pg_ternary vacuum_truncate; /* enables vacuum to truncate a relation */
/*
* Fraction of pages in a relation that vacuum can eagerly scan and fail
diff --git a/src/test/regress/expected/reloptions.out b/src/test/regress/expected/reloptions.out
index 9de19b4e3f1..1c99f79ab01 100644
--- a/src/test/regress/expected/reloptions.out
+++ b/src/test/regress/expected/reloptions.out
@@ -98,6 +98,42 @@ SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass;
{fillfactor=13,autovacuum_enabled=false}
(1 row)
+-- Tests for future (FIXME) ternary options
+-- behave as boolean option: accept unassigned name and truncated value
+DROP TABLE reloptions_test;
+CREATE TABLE reloptions_test(i INT) WITH (vacuum_truncate);
+SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass;
+ reloptions
+------------------------
+ {vacuum_truncate=true}
+(1 row)
+
+DROP TABLE reloptions_test;
+CREATE TABLE reloptions_test(i INT) WITH (vacuum_truncate=FaLS);
+SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass;
+ reloptions
+------------------------
+ {vacuum_truncate=fals}
+(1 row)
+
+-- preferred "true" alias is used when storing
+DROP TABLE reloptions_test;
+CREATE TABLE reloptions_test(i INT) WITH (vacuum_index_cleanup=on);
+SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass;
+ reloptions
+---------------------------
+ {vacuum_index_cleanup=on}
+(1 row)
+
+-- custom "third" value is available
+DROP TABLE reloptions_test;
+CREATE TABLE reloptions_test(i INT) WITH (vacuum_index_cleanup=auto);
+SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass;
+ reloptions
+-----------------------------
+ {vacuum_index_cleanup=auto}
+(1 row)
+
-- Test vacuum_truncate option
DROP TABLE reloptions_test;
CREATE TEMP TABLE reloptions_test(i INT NOT NULL, j text)
diff --git a/src/test/regress/sql/reloptions.sql b/src/test/regress/sql/reloptions.sql
index 24fbe0b478d..f5980dafcbc 100644
--- a/src/test/regress/sql/reloptions.sql
+++ b/src/test/regress/sql/reloptions.sql
@@ -59,6 +59,27 @@ UPDATE pg_class
ALTER TABLE reloptions_test RESET (illegal_option);
SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass;
+-- Tests for future (FIXME) ternary options
+
+-- behave as boolean option: accept unassigned name and truncated value
+DROP TABLE reloptions_test;
+CREATE TABLE reloptions_test(i INT) WITH (vacuum_truncate);
+SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass;
+
+DROP TABLE reloptions_test;
+CREATE TABLE reloptions_test(i INT) WITH (vacuum_truncate=FaLS);
+SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass;
+
+-- preferred "true" alias is used when storing
+DROP TABLE reloptions_test;
+CREATE TABLE reloptions_test(i INT) WITH (vacuum_index_cleanup=on);
+SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass;
+
+-- custom "third" value is available
+DROP TABLE reloptions_test;
+CREATE TABLE reloptions_test(i INT) WITH (vacuum_index_cleanup=auto);
+SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass;
+
-- Test vacuum_truncate option
DROP TABLE reloptions_test;
diff --git a/src/tools/pgindent/typedefs.list b/src/tools/pgindent/typedefs.list
index 3f3a888fd0e..1c8610fd46c 100644
--- a/src/tools/pgindent/typedefs.list
+++ b/src/tools/pgindent/typedefs.list
@@ -3952,6 +3952,7 @@ pg_sha512_ctx
pg_snapshot
pg_special_case
pg_stack_base_t
+pg_ternary
pg_time_t
pg_time_usec_t
pg_tz
@@ -4079,6 +4080,7 @@ relopt_kind
relopt_parse_elt
relopt_real
relopt_string
+relopt_ternary
relopt_type
relopt_value
relopts_validator
--
2.47.3
--c36t3fndxhkzbl5z--
^ permalink raw reply [nested|flat] 868+ messages in thread
* [PATCH v3] Introduce ternary reloptions
@ 2026-01-16 15:04 Álvaro Herrera <[email protected]>
0 siblings, 0 replies; 868+ messages in thread
From: Álvaro Herrera @ 2026-01-16 15:04 UTC (permalink / raw)
Introduce ternary reloption as a replacement for current
`vacuum_truncate` implementation. Remove the `vacuum_truncate_set`
separate flag and use TERNARY_UNSET instead.
This could also be used for other options such as `vacuum_index_cleanup`
and `buffering`, but lets get the scaffolding in first.
Discussion: https://postgr.es/m/3474141.usfYGdeWWP@thinkpad-pgpro
---
src/backend/access/common/reloptions.c | 137 ++++++++++++++++++-----
src/backend/commands/vacuum.c | 4 +-
src/include/access/reloptions.h | 27 ++---
src/include/postgres.h | 15 +++
src/include/utils/rel.h | 3 +-
src/test/regress/expected/reloptions.out | 36 ++++++
src/test/regress/sql/reloptions.sql | 21 ++++
src/tools/pgindent/typedefs.list | 2 +
8 files changed, 202 insertions(+), 43 deletions(-)
diff --git a/src/backend/access/common/reloptions.c b/src/backend/access/common/reloptions.c
index 0b83f98ed5f..6f1f577581a 100644
--- a/src/backend/access/common/reloptions.c
+++ b/src/backend/access/common/reloptions.c
@@ -40,9 +40,9 @@
*
* To add an option:
*
- * (i) decide on a type (bool, integer, real, enum, string), name, default
- * value, upper and lower bounds (if applicable); for strings, consider a
- * validation routine.
+ * (i) decide on a type (bool, ternary, integer, real, enum, string), name,
+ * default value, upper and lower bounds (if applicable); for strings,
+ * consider a validation routine.
* (ii) add a record below (or use add_<type>_reloption).
* (iii) add it to the appropriate options struct (perhaps StdRdOptions)
* (iv) add it to the appropriate handling routine (perhaps
@@ -147,15 +147,6 @@ static relopt_bool boolRelOpts[] =
},
false
},
- {
- {
- "vacuum_truncate",
- "Enables vacuum to truncate empty pages at the end of this table",
- RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
- ShareUpdateExclusiveLock
- },
- true
- },
{
{
"deduplicate_items",
@@ -170,6 +161,25 @@ static relopt_bool boolRelOpts[] =
{{NULL}}
};
+static relopt_ternary ternaryRelOpts[] =
+{
+ {
+ {
+ "vacuum_truncate",
+ "Enables vacuum to truncate empty pages at the end of this table",
+ RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
+ ShareUpdateExclusiveLock
+ },
+ TERNARY_UNSET
+ },
+ /* list terminator */
+ {
+ {
+ NULL
+ }
+ }
+};
+
static relopt_int intRelOpts[] =
{
{
@@ -609,6 +619,13 @@ initialize_reloptions(void)
boolRelOpts[i].gen.lockmode));
j++;
}
+ for (i = 0; ternaryRelOpts[i].gen.name; i++)
+ {
+ Assert(DoLockModesConflict(ternaryRelOpts[i].gen.lockmode,
+ ternaryRelOpts[i].gen.lockmode));
+ j++;
+ }
+
for (i = 0; intRelOpts[i].gen.name; i++)
{
Assert(DoLockModesConflict(intRelOpts[i].gen.lockmode,
@@ -649,6 +666,14 @@ initialize_reloptions(void)
j++;
}
+ for (i = 0; ternaryRelOpts[i].gen.name; i++)
+ {
+ relOpts[j] = &ternaryRelOpts[i].gen;
+ relOpts[j]->type = RELOPT_TYPE_TERNARY;
+ relOpts[j]->namelen = strlen(relOpts[j]->name);
+ j++;
+ }
+
for (i = 0; intRelOpts[i].gen.name; i++)
{
relOpts[j] = &intRelOpts[i].gen;
@@ -809,6 +834,9 @@ allocate_reloption(bits32 kinds, int type, const char *name, const char *desc,
case RELOPT_TYPE_BOOL:
size = sizeof(relopt_bool);
break;
+ case RELOPT_TYPE_TERNARY:
+ size = sizeof(relopt_ternary);
+ break;
case RELOPT_TYPE_INT:
size = sizeof(relopt_int);
break;
@@ -892,6 +920,57 @@ add_local_bool_reloption(local_relopts *relopts, const char *name,
add_local_reloption(relopts, (relopt_gen *) newoption, offset);
}
+/*
+ * init_ternary_reloption
+ * Allocate and initialize a new ternary reloption
+ */
+static relopt_ternary *
+init_ternary_reloption(bits32 kinds, const char *name, const char *desc,
+ pg_ternary default_val, LOCKMODE lockmode)
+{
+ relopt_ternary *newoption;
+
+ newoption = (relopt_ternary *)
+ allocate_reloption(kinds, RELOPT_TYPE_TERNARY, name, desc, lockmode);
+ newoption->default_val = default_val;
+
+ return newoption;
+}
+
+/*
+ * add_ternary_reloption
+ * Add a new ternary reloption
+ */
+void
+add_ternary_reloption(bits32 kinds, const char *name, const char *desc,
+ pg_ternary default_val, LOCKMODE lockmode)
+{
+ relopt_ternary *newoption;
+
+ newoption =
+ init_ternary_reloption(kinds, name, desc, default_val, lockmode);
+
+ add_reloption((relopt_gen *) newoption);
+}
+
+/*
+ * add_local_ternary_reloption
+ * Add a new ternary local reloption
+ *
+ * 'offset' is offset of ternary-typed field.
+ */
+void
+add_local_ternary_reloption(local_relopts *relopts, const char *name,
+ const char *desc, pg_ternary default_val,
+ int offset)
+{
+ relopt_ternary *newoption;
+
+ newoption =
+ init_ternary_reloption(RELOPT_KIND_LOCAL, name, desc, default_val, 0);
+
+ add_local_reloption(relopts, (relopt_gen *) newoption, offset);
+}
/*
* init_real_reloption
@@ -1626,6 +1705,19 @@ parse_one_reloption(relopt_value *option, char *text_str, int text_len,
option->gen->name, value)));
}
break;
+ case RELOPT_TYPE_TERNARY:
+ {
+ bool b;
+
+ parsed = parse_bool(value, &b);
+ option->values.ternary_val = b ? TERNARY_TRUE : TERNARY_FALSE;
+ if (validate && !parsed)
+ ereport(ERROR,
+ (errcode(ERRCODE_INVALID_PARAMETER_VALUE),
+ errmsg("invalid value for ternary option \"%s\": %s",
+ option->gen->name, value)));
+ }
+ break;
case RELOPT_TYPE_INT:
{
relopt_int *optint = (relopt_int *) option->gen;
@@ -1789,17 +1881,6 @@ fillRelOptions(void *rdopts, Size basesize,
char *itempos = ((char *) rdopts) + elems[j].offset;
char *string_val;
- /*
- * If isset_offset is provided, store whether the reloption is
- * set there.
- */
- if (elems[j].isset_offset > 0)
- {
- char *setpos = ((char *) rdopts) + elems[j].isset_offset;
-
- *(bool *) setpos = options[i].isset;
- }
-
switch (options[i].gen->type)
{
case RELOPT_TYPE_BOOL:
@@ -1807,6 +1888,11 @@ fillRelOptions(void *rdopts, Size basesize,
options[i].values.bool_val :
((relopt_bool *) options[i].gen)->default_val;
break;
+ case RELOPT_TYPE_TERNARY:
+ *(pg_ternary *) itempos = options[i].isset ?
+ options[i].values.ternary_val :
+ ((relopt_ternary *) options[i].gen)->default_val;
+ break;
case RELOPT_TYPE_INT:
*(int *) itempos = options[i].isset ?
options[i].values.int_val :
@@ -1923,8 +2009,8 @@ default_reloptions(Datum reloptions, bool validate, relopt_kind kind)
offsetof(StdRdOptions, parallel_workers)},
{"vacuum_index_cleanup", RELOPT_TYPE_ENUM,
offsetof(StdRdOptions, vacuum_index_cleanup)},
- {"vacuum_truncate", RELOPT_TYPE_BOOL,
- offsetof(StdRdOptions, vacuum_truncate), offsetof(StdRdOptions, vacuum_truncate_set)},
+ {"vacuum_truncate", RELOPT_TYPE_TERNARY,
+ offsetof(StdRdOptions, vacuum_truncate)},
{"vacuum_max_eager_freeze_failure_rate", RELOPT_TYPE_REAL,
offsetof(StdRdOptions, vacuum_max_eager_freeze_failure_rate)}
};
@@ -2004,7 +2090,6 @@ build_local_reloptions(local_relopts *relopts, Datum options, bool validate)
elems[i].optname = opt->option->name;
elems[i].opttype = opt->option->type;
elems[i].offset = opt->offset;
- elems[i].isset_offset = 0; /* not supported for local relopts yet */
i++;
}
diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c
index aa4fbec143f..696eab9bd97 100644
--- a/src/backend/commands/vacuum.c
+++ b/src/backend/commands/vacuum.c
@@ -2224,9 +2224,9 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams params,
{
StdRdOptions *opts = (StdRdOptions *) rel->rd_options;
- if (opts && opts->vacuum_truncate_set)
+ if (opts && opts->vacuum_truncate != TERNARY_UNSET)
{
- if (opts->vacuum_truncate)
+ if (opts->vacuum_truncate == TERNARY_TRUE)
params.truncate = VACOPTVALUE_ENABLED;
else
params.truncate = VACOPTVALUE_DISABLED;
diff --git a/src/include/access/reloptions.h b/src/include/access/reloptions.h
index 2f08e1b0cf0..dfbef2babf2 100644
--- a/src/include/access/reloptions.h
+++ b/src/include/access/reloptions.h
@@ -29,6 +29,7 @@
typedef enum relopt_type
{
RELOPT_TYPE_BOOL,
+ RELOPT_TYPE_TERNARY, /* on, off, unset */
RELOPT_TYPE_INT,
RELOPT_TYPE_REAL,
RELOPT_TYPE_ENUM,
@@ -80,6 +81,7 @@ typedef struct relopt_value
union
{
bool bool_val;
+ pg_ternary ternary_val;
int int_val;
double real_val;
int enum_val;
@@ -94,6 +96,12 @@ typedef struct relopt_bool
bool default_val;
} relopt_bool;
+typedef struct relopt_ternary
+{
+ relopt_gen gen;
+ int default_val;
+} relopt_ternary;
+
typedef struct relopt_int
{
relopt_gen gen;
@@ -152,19 +160,6 @@ typedef struct
const char *optname; /* option's name */
relopt_type opttype; /* option's datatype */
int offset; /* offset of field in result struct */
-
- /*
- * isset_offset is an optional offset of a field in the result struct that
- * stores whether the option is explicitly set for the relation or if it
- * just picked up the default value. In most cases, this can be
- * accomplished by giving the reloption a special out-of-range default
- * value (e.g., some integer reloptions use -2), but this isn't always
- * possible. For example, a Boolean reloption cannot be given an
- * out-of-range default, so we need another way to discover the source of
- * its value. This offset is only used if given a value greater than
- * zero.
- */
- int isset_offset;
} relopt_parse_elt;
/* Local reloption definition */
@@ -195,6 +190,9 @@ typedef struct local_relopts
extern relopt_kind add_reloption_kind(void);
extern void add_bool_reloption(bits32 kinds, const char *name, const char *desc,
bool default_val, LOCKMODE lockmode);
+extern void add_ternary_reloption(bits32 kinds, const char *name,
+ const char *desc, pg_ternary default_val,
+ LOCKMODE lockmode);
extern void add_int_reloption(bits32 kinds, const char *name, const char *desc,
int default_val, int min_val, int max_val,
LOCKMODE lockmode);
@@ -214,6 +212,9 @@ extern void register_reloptions_validator(local_relopts *relopts,
extern void add_local_bool_reloption(local_relopts *relopts, const char *name,
const char *desc, bool default_val,
int offset);
+extern void add_local_ternary_reloption(local_relopts *relopts,
+ const char *name, const char *desc,
+ pg_ternary default_val, int offset);
extern void add_local_int_reloption(local_relopts *relopts, const char *name,
const char *desc, int default_val,
int min_val, int max_val, int offset);
diff --git a/src/include/postgres.h b/src/include/postgres.h
index 7d93fbce709..47c88780776 100644
--- a/src/include/postgres.h
+++ b/src/include/postgres.h
@@ -543,6 +543,21 @@ Float8GetDatum(float8 X)
* ----------------------------------------------------------------
*/
+/*
+ * pg_ternary
+ * Boolean value with an extra "unset" value
+ *
+ * This enum can be used for values that want to distinguish between true,
+ * false, and unset.
+*/
+
+typedef enum pg_ternary
+{
+ TERNARY_FALSE = 0,
+ TERNARY_TRUE = 1,
+ TERNARY_UNSET = -1
+} pg_ternary;
+
/*
* NON_EXEC_STATIC: It's sometimes useful to define a variable or function
* that is normally static but extern when using EXEC_BACKEND (see
diff --git a/src/include/utils/rel.h b/src/include/utils/rel.h
index d03ab247788..5feb18a5373 100644
--- a/src/include/utils/rel.h
+++ b/src/include/utils/rel.h
@@ -347,8 +347,7 @@ typedef struct StdRdOptions
bool user_catalog_table; /* use as an additional catalog relation */
int parallel_workers; /* max number of parallel workers */
StdRdOptIndexCleanup vacuum_index_cleanup; /* controls index vacuuming */
- bool vacuum_truncate; /* enables vacuum to truncate a relation */
- bool vacuum_truncate_set; /* whether vacuum_truncate is set */
+ pg_ternary vacuum_truncate; /* enables vacuum to truncate a relation */
/*
* Fraction of pages in a relation that vacuum can eagerly scan and fail
diff --git a/src/test/regress/expected/reloptions.out b/src/test/regress/expected/reloptions.out
index 9de19b4e3f1..1c99f79ab01 100644
--- a/src/test/regress/expected/reloptions.out
+++ b/src/test/regress/expected/reloptions.out
@@ -98,6 +98,42 @@ SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass;
{fillfactor=13,autovacuum_enabled=false}
(1 row)
+-- Tests for future (FIXME) ternary options
+-- behave as boolean option: accept unassigned name and truncated value
+DROP TABLE reloptions_test;
+CREATE TABLE reloptions_test(i INT) WITH (vacuum_truncate);
+SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass;
+ reloptions
+------------------------
+ {vacuum_truncate=true}
+(1 row)
+
+DROP TABLE reloptions_test;
+CREATE TABLE reloptions_test(i INT) WITH (vacuum_truncate=FaLS);
+SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass;
+ reloptions
+------------------------
+ {vacuum_truncate=fals}
+(1 row)
+
+-- preferred "true" alias is used when storing
+DROP TABLE reloptions_test;
+CREATE TABLE reloptions_test(i INT) WITH (vacuum_index_cleanup=on);
+SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass;
+ reloptions
+---------------------------
+ {vacuum_index_cleanup=on}
+(1 row)
+
+-- custom "third" value is available
+DROP TABLE reloptions_test;
+CREATE TABLE reloptions_test(i INT) WITH (vacuum_index_cleanup=auto);
+SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass;
+ reloptions
+-----------------------------
+ {vacuum_index_cleanup=auto}
+(1 row)
+
-- Test vacuum_truncate option
DROP TABLE reloptions_test;
CREATE TEMP TABLE reloptions_test(i INT NOT NULL, j text)
diff --git a/src/test/regress/sql/reloptions.sql b/src/test/regress/sql/reloptions.sql
index 24fbe0b478d..f5980dafcbc 100644
--- a/src/test/regress/sql/reloptions.sql
+++ b/src/test/regress/sql/reloptions.sql
@@ -59,6 +59,27 @@ UPDATE pg_class
ALTER TABLE reloptions_test RESET (illegal_option);
SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass;
+-- Tests for future (FIXME) ternary options
+
+-- behave as boolean option: accept unassigned name and truncated value
+DROP TABLE reloptions_test;
+CREATE TABLE reloptions_test(i INT) WITH (vacuum_truncate);
+SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass;
+
+DROP TABLE reloptions_test;
+CREATE TABLE reloptions_test(i INT) WITH (vacuum_truncate=FaLS);
+SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass;
+
+-- preferred "true" alias is used when storing
+DROP TABLE reloptions_test;
+CREATE TABLE reloptions_test(i INT) WITH (vacuum_index_cleanup=on);
+SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass;
+
+-- custom "third" value is available
+DROP TABLE reloptions_test;
+CREATE TABLE reloptions_test(i INT) WITH (vacuum_index_cleanup=auto);
+SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass;
+
-- Test vacuum_truncate option
DROP TABLE reloptions_test;
diff --git a/src/tools/pgindent/typedefs.list b/src/tools/pgindent/typedefs.list
index 3f3a888fd0e..1c8610fd46c 100644
--- a/src/tools/pgindent/typedefs.list
+++ b/src/tools/pgindent/typedefs.list
@@ -3952,6 +3952,7 @@ pg_sha512_ctx
pg_snapshot
pg_special_case
pg_stack_base_t
+pg_ternary
pg_time_t
pg_time_usec_t
pg_tz
@@ -4079,6 +4080,7 @@ relopt_kind
relopt_parse_elt
relopt_real
relopt_string
+relopt_ternary
relopt_type
relopt_value
relopts_validator
--
2.47.3
--c36t3fndxhkzbl5z--
^ permalink raw reply [nested|flat] 868+ messages in thread
* [PATCH v3] Introduce ternary reloptions
@ 2026-01-16 15:04 Álvaro Herrera <[email protected]>
0 siblings, 0 replies; 868+ messages in thread
From: Álvaro Herrera @ 2026-01-16 15:04 UTC (permalink / raw)
Introduce ternary reloption as a replacement for current
`vacuum_truncate` implementation. Remove the `vacuum_truncate_set`
separate flag and use TERNARY_UNSET instead.
This could also be used for other options such as `vacuum_index_cleanup`
and `buffering`, but lets get the scaffolding in first.
Discussion: https://postgr.es/m/3474141.usfYGdeWWP@thinkpad-pgpro
---
src/backend/access/common/reloptions.c | 137 ++++++++++++++++++-----
src/backend/commands/vacuum.c | 4 +-
src/include/access/reloptions.h | 27 ++---
src/include/postgres.h | 15 +++
src/include/utils/rel.h | 3 +-
src/test/regress/expected/reloptions.out | 36 ++++++
src/test/regress/sql/reloptions.sql | 21 ++++
src/tools/pgindent/typedefs.list | 2 +
8 files changed, 202 insertions(+), 43 deletions(-)
diff --git a/src/backend/access/common/reloptions.c b/src/backend/access/common/reloptions.c
index 0b83f98ed5f..6f1f577581a 100644
--- a/src/backend/access/common/reloptions.c
+++ b/src/backend/access/common/reloptions.c
@@ -40,9 +40,9 @@
*
* To add an option:
*
- * (i) decide on a type (bool, integer, real, enum, string), name, default
- * value, upper and lower bounds (if applicable); for strings, consider a
- * validation routine.
+ * (i) decide on a type (bool, ternary, integer, real, enum, string), name,
+ * default value, upper and lower bounds (if applicable); for strings,
+ * consider a validation routine.
* (ii) add a record below (or use add_<type>_reloption).
* (iii) add it to the appropriate options struct (perhaps StdRdOptions)
* (iv) add it to the appropriate handling routine (perhaps
@@ -147,15 +147,6 @@ static relopt_bool boolRelOpts[] =
},
false
},
- {
- {
- "vacuum_truncate",
- "Enables vacuum to truncate empty pages at the end of this table",
- RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
- ShareUpdateExclusiveLock
- },
- true
- },
{
{
"deduplicate_items",
@@ -170,6 +161,25 @@ static relopt_bool boolRelOpts[] =
{{NULL}}
};
+static relopt_ternary ternaryRelOpts[] =
+{
+ {
+ {
+ "vacuum_truncate",
+ "Enables vacuum to truncate empty pages at the end of this table",
+ RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
+ ShareUpdateExclusiveLock
+ },
+ TERNARY_UNSET
+ },
+ /* list terminator */
+ {
+ {
+ NULL
+ }
+ }
+};
+
static relopt_int intRelOpts[] =
{
{
@@ -609,6 +619,13 @@ initialize_reloptions(void)
boolRelOpts[i].gen.lockmode));
j++;
}
+ for (i = 0; ternaryRelOpts[i].gen.name; i++)
+ {
+ Assert(DoLockModesConflict(ternaryRelOpts[i].gen.lockmode,
+ ternaryRelOpts[i].gen.lockmode));
+ j++;
+ }
+
for (i = 0; intRelOpts[i].gen.name; i++)
{
Assert(DoLockModesConflict(intRelOpts[i].gen.lockmode,
@@ -649,6 +666,14 @@ initialize_reloptions(void)
j++;
}
+ for (i = 0; ternaryRelOpts[i].gen.name; i++)
+ {
+ relOpts[j] = &ternaryRelOpts[i].gen;
+ relOpts[j]->type = RELOPT_TYPE_TERNARY;
+ relOpts[j]->namelen = strlen(relOpts[j]->name);
+ j++;
+ }
+
for (i = 0; intRelOpts[i].gen.name; i++)
{
relOpts[j] = &intRelOpts[i].gen;
@@ -809,6 +834,9 @@ allocate_reloption(bits32 kinds, int type, const char *name, const char *desc,
case RELOPT_TYPE_BOOL:
size = sizeof(relopt_bool);
break;
+ case RELOPT_TYPE_TERNARY:
+ size = sizeof(relopt_ternary);
+ break;
case RELOPT_TYPE_INT:
size = sizeof(relopt_int);
break;
@@ -892,6 +920,57 @@ add_local_bool_reloption(local_relopts *relopts, const char *name,
add_local_reloption(relopts, (relopt_gen *) newoption, offset);
}
+/*
+ * init_ternary_reloption
+ * Allocate and initialize a new ternary reloption
+ */
+static relopt_ternary *
+init_ternary_reloption(bits32 kinds, const char *name, const char *desc,
+ pg_ternary default_val, LOCKMODE lockmode)
+{
+ relopt_ternary *newoption;
+
+ newoption = (relopt_ternary *)
+ allocate_reloption(kinds, RELOPT_TYPE_TERNARY, name, desc, lockmode);
+ newoption->default_val = default_val;
+
+ return newoption;
+}
+
+/*
+ * add_ternary_reloption
+ * Add a new ternary reloption
+ */
+void
+add_ternary_reloption(bits32 kinds, const char *name, const char *desc,
+ pg_ternary default_val, LOCKMODE lockmode)
+{
+ relopt_ternary *newoption;
+
+ newoption =
+ init_ternary_reloption(kinds, name, desc, default_val, lockmode);
+
+ add_reloption((relopt_gen *) newoption);
+}
+
+/*
+ * add_local_ternary_reloption
+ * Add a new ternary local reloption
+ *
+ * 'offset' is offset of ternary-typed field.
+ */
+void
+add_local_ternary_reloption(local_relopts *relopts, const char *name,
+ const char *desc, pg_ternary default_val,
+ int offset)
+{
+ relopt_ternary *newoption;
+
+ newoption =
+ init_ternary_reloption(RELOPT_KIND_LOCAL, name, desc, default_val, 0);
+
+ add_local_reloption(relopts, (relopt_gen *) newoption, offset);
+}
/*
* init_real_reloption
@@ -1626,6 +1705,19 @@ parse_one_reloption(relopt_value *option, char *text_str, int text_len,
option->gen->name, value)));
}
break;
+ case RELOPT_TYPE_TERNARY:
+ {
+ bool b;
+
+ parsed = parse_bool(value, &b);
+ option->values.ternary_val = b ? TERNARY_TRUE : TERNARY_FALSE;
+ if (validate && !parsed)
+ ereport(ERROR,
+ (errcode(ERRCODE_INVALID_PARAMETER_VALUE),
+ errmsg("invalid value for ternary option \"%s\": %s",
+ option->gen->name, value)));
+ }
+ break;
case RELOPT_TYPE_INT:
{
relopt_int *optint = (relopt_int *) option->gen;
@@ -1789,17 +1881,6 @@ fillRelOptions(void *rdopts, Size basesize,
char *itempos = ((char *) rdopts) + elems[j].offset;
char *string_val;
- /*
- * If isset_offset is provided, store whether the reloption is
- * set there.
- */
- if (elems[j].isset_offset > 0)
- {
- char *setpos = ((char *) rdopts) + elems[j].isset_offset;
-
- *(bool *) setpos = options[i].isset;
- }
-
switch (options[i].gen->type)
{
case RELOPT_TYPE_BOOL:
@@ -1807,6 +1888,11 @@ fillRelOptions(void *rdopts, Size basesize,
options[i].values.bool_val :
((relopt_bool *) options[i].gen)->default_val;
break;
+ case RELOPT_TYPE_TERNARY:
+ *(pg_ternary *) itempos = options[i].isset ?
+ options[i].values.ternary_val :
+ ((relopt_ternary *) options[i].gen)->default_val;
+ break;
case RELOPT_TYPE_INT:
*(int *) itempos = options[i].isset ?
options[i].values.int_val :
@@ -1923,8 +2009,8 @@ default_reloptions(Datum reloptions, bool validate, relopt_kind kind)
offsetof(StdRdOptions, parallel_workers)},
{"vacuum_index_cleanup", RELOPT_TYPE_ENUM,
offsetof(StdRdOptions, vacuum_index_cleanup)},
- {"vacuum_truncate", RELOPT_TYPE_BOOL,
- offsetof(StdRdOptions, vacuum_truncate), offsetof(StdRdOptions, vacuum_truncate_set)},
+ {"vacuum_truncate", RELOPT_TYPE_TERNARY,
+ offsetof(StdRdOptions, vacuum_truncate)},
{"vacuum_max_eager_freeze_failure_rate", RELOPT_TYPE_REAL,
offsetof(StdRdOptions, vacuum_max_eager_freeze_failure_rate)}
};
@@ -2004,7 +2090,6 @@ build_local_reloptions(local_relopts *relopts, Datum options, bool validate)
elems[i].optname = opt->option->name;
elems[i].opttype = opt->option->type;
elems[i].offset = opt->offset;
- elems[i].isset_offset = 0; /* not supported for local relopts yet */
i++;
}
diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c
index aa4fbec143f..696eab9bd97 100644
--- a/src/backend/commands/vacuum.c
+++ b/src/backend/commands/vacuum.c
@@ -2224,9 +2224,9 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams params,
{
StdRdOptions *opts = (StdRdOptions *) rel->rd_options;
- if (opts && opts->vacuum_truncate_set)
+ if (opts && opts->vacuum_truncate != TERNARY_UNSET)
{
- if (opts->vacuum_truncate)
+ if (opts->vacuum_truncate == TERNARY_TRUE)
params.truncate = VACOPTVALUE_ENABLED;
else
params.truncate = VACOPTVALUE_DISABLED;
diff --git a/src/include/access/reloptions.h b/src/include/access/reloptions.h
index 2f08e1b0cf0..dfbef2babf2 100644
--- a/src/include/access/reloptions.h
+++ b/src/include/access/reloptions.h
@@ -29,6 +29,7 @@
typedef enum relopt_type
{
RELOPT_TYPE_BOOL,
+ RELOPT_TYPE_TERNARY, /* on, off, unset */
RELOPT_TYPE_INT,
RELOPT_TYPE_REAL,
RELOPT_TYPE_ENUM,
@@ -80,6 +81,7 @@ typedef struct relopt_value
union
{
bool bool_val;
+ pg_ternary ternary_val;
int int_val;
double real_val;
int enum_val;
@@ -94,6 +96,12 @@ typedef struct relopt_bool
bool default_val;
} relopt_bool;
+typedef struct relopt_ternary
+{
+ relopt_gen gen;
+ int default_val;
+} relopt_ternary;
+
typedef struct relopt_int
{
relopt_gen gen;
@@ -152,19 +160,6 @@ typedef struct
const char *optname; /* option's name */
relopt_type opttype; /* option's datatype */
int offset; /* offset of field in result struct */
-
- /*
- * isset_offset is an optional offset of a field in the result struct that
- * stores whether the option is explicitly set for the relation or if it
- * just picked up the default value. In most cases, this can be
- * accomplished by giving the reloption a special out-of-range default
- * value (e.g., some integer reloptions use -2), but this isn't always
- * possible. For example, a Boolean reloption cannot be given an
- * out-of-range default, so we need another way to discover the source of
- * its value. This offset is only used if given a value greater than
- * zero.
- */
- int isset_offset;
} relopt_parse_elt;
/* Local reloption definition */
@@ -195,6 +190,9 @@ typedef struct local_relopts
extern relopt_kind add_reloption_kind(void);
extern void add_bool_reloption(bits32 kinds, const char *name, const char *desc,
bool default_val, LOCKMODE lockmode);
+extern void add_ternary_reloption(bits32 kinds, const char *name,
+ const char *desc, pg_ternary default_val,
+ LOCKMODE lockmode);
extern void add_int_reloption(bits32 kinds, const char *name, const char *desc,
int default_val, int min_val, int max_val,
LOCKMODE lockmode);
@@ -214,6 +212,9 @@ extern void register_reloptions_validator(local_relopts *relopts,
extern void add_local_bool_reloption(local_relopts *relopts, const char *name,
const char *desc, bool default_val,
int offset);
+extern void add_local_ternary_reloption(local_relopts *relopts,
+ const char *name, const char *desc,
+ pg_ternary default_val, int offset);
extern void add_local_int_reloption(local_relopts *relopts, const char *name,
const char *desc, int default_val,
int min_val, int max_val, int offset);
diff --git a/src/include/postgres.h b/src/include/postgres.h
index 7d93fbce709..47c88780776 100644
--- a/src/include/postgres.h
+++ b/src/include/postgres.h
@@ -543,6 +543,21 @@ Float8GetDatum(float8 X)
* ----------------------------------------------------------------
*/
+/*
+ * pg_ternary
+ * Boolean value with an extra "unset" value
+ *
+ * This enum can be used for values that want to distinguish between true,
+ * false, and unset.
+*/
+
+typedef enum pg_ternary
+{
+ TERNARY_FALSE = 0,
+ TERNARY_TRUE = 1,
+ TERNARY_UNSET = -1
+} pg_ternary;
+
/*
* NON_EXEC_STATIC: It's sometimes useful to define a variable or function
* that is normally static but extern when using EXEC_BACKEND (see
diff --git a/src/include/utils/rel.h b/src/include/utils/rel.h
index d03ab247788..5feb18a5373 100644
--- a/src/include/utils/rel.h
+++ b/src/include/utils/rel.h
@@ -347,8 +347,7 @@ typedef struct StdRdOptions
bool user_catalog_table; /* use as an additional catalog relation */
int parallel_workers; /* max number of parallel workers */
StdRdOptIndexCleanup vacuum_index_cleanup; /* controls index vacuuming */
- bool vacuum_truncate; /* enables vacuum to truncate a relation */
- bool vacuum_truncate_set; /* whether vacuum_truncate is set */
+ pg_ternary vacuum_truncate; /* enables vacuum to truncate a relation */
/*
* Fraction of pages in a relation that vacuum can eagerly scan and fail
diff --git a/src/test/regress/expected/reloptions.out b/src/test/regress/expected/reloptions.out
index 9de19b4e3f1..1c99f79ab01 100644
--- a/src/test/regress/expected/reloptions.out
+++ b/src/test/regress/expected/reloptions.out
@@ -98,6 +98,42 @@ SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass;
{fillfactor=13,autovacuum_enabled=false}
(1 row)
+-- Tests for future (FIXME) ternary options
+-- behave as boolean option: accept unassigned name and truncated value
+DROP TABLE reloptions_test;
+CREATE TABLE reloptions_test(i INT) WITH (vacuum_truncate);
+SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass;
+ reloptions
+------------------------
+ {vacuum_truncate=true}
+(1 row)
+
+DROP TABLE reloptions_test;
+CREATE TABLE reloptions_test(i INT) WITH (vacuum_truncate=FaLS);
+SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass;
+ reloptions
+------------------------
+ {vacuum_truncate=fals}
+(1 row)
+
+-- preferred "true" alias is used when storing
+DROP TABLE reloptions_test;
+CREATE TABLE reloptions_test(i INT) WITH (vacuum_index_cleanup=on);
+SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass;
+ reloptions
+---------------------------
+ {vacuum_index_cleanup=on}
+(1 row)
+
+-- custom "third" value is available
+DROP TABLE reloptions_test;
+CREATE TABLE reloptions_test(i INT) WITH (vacuum_index_cleanup=auto);
+SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass;
+ reloptions
+-----------------------------
+ {vacuum_index_cleanup=auto}
+(1 row)
+
-- Test vacuum_truncate option
DROP TABLE reloptions_test;
CREATE TEMP TABLE reloptions_test(i INT NOT NULL, j text)
diff --git a/src/test/regress/sql/reloptions.sql b/src/test/regress/sql/reloptions.sql
index 24fbe0b478d..f5980dafcbc 100644
--- a/src/test/regress/sql/reloptions.sql
+++ b/src/test/regress/sql/reloptions.sql
@@ -59,6 +59,27 @@ UPDATE pg_class
ALTER TABLE reloptions_test RESET (illegal_option);
SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass;
+-- Tests for future (FIXME) ternary options
+
+-- behave as boolean option: accept unassigned name and truncated value
+DROP TABLE reloptions_test;
+CREATE TABLE reloptions_test(i INT) WITH (vacuum_truncate);
+SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass;
+
+DROP TABLE reloptions_test;
+CREATE TABLE reloptions_test(i INT) WITH (vacuum_truncate=FaLS);
+SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass;
+
+-- preferred "true" alias is used when storing
+DROP TABLE reloptions_test;
+CREATE TABLE reloptions_test(i INT) WITH (vacuum_index_cleanup=on);
+SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass;
+
+-- custom "third" value is available
+DROP TABLE reloptions_test;
+CREATE TABLE reloptions_test(i INT) WITH (vacuum_index_cleanup=auto);
+SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass;
+
-- Test vacuum_truncate option
DROP TABLE reloptions_test;
diff --git a/src/tools/pgindent/typedefs.list b/src/tools/pgindent/typedefs.list
index 3f3a888fd0e..1c8610fd46c 100644
--- a/src/tools/pgindent/typedefs.list
+++ b/src/tools/pgindent/typedefs.list
@@ -3952,6 +3952,7 @@ pg_sha512_ctx
pg_snapshot
pg_special_case
pg_stack_base_t
+pg_ternary
pg_time_t
pg_time_usec_t
pg_tz
@@ -4079,6 +4080,7 @@ relopt_kind
relopt_parse_elt
relopt_real
relopt_string
+relopt_ternary
relopt_type
relopt_value
relopts_validator
--
2.47.3
--c36t3fndxhkzbl5z--
^ permalink raw reply [nested|flat] 868+ messages in thread
* [PATCH v3] Introduce ternary reloptions
@ 2026-01-16 15:04 Álvaro Herrera <[email protected]>
0 siblings, 0 replies; 868+ messages in thread
From: Álvaro Herrera @ 2026-01-16 15:04 UTC (permalink / raw)
Introduce ternary reloption as a replacement for current
`vacuum_truncate` implementation. Remove the `vacuum_truncate_set`
separate flag and use TERNARY_UNSET instead.
This could also be used for other options such as `vacuum_index_cleanup`
and `buffering`, but lets get the scaffolding in first.
Discussion: https://postgr.es/m/3474141.usfYGdeWWP@thinkpad-pgpro
---
src/backend/access/common/reloptions.c | 137 ++++++++++++++++++-----
src/backend/commands/vacuum.c | 4 +-
src/include/access/reloptions.h | 27 ++---
src/include/postgres.h | 15 +++
src/include/utils/rel.h | 3 +-
src/test/regress/expected/reloptions.out | 36 ++++++
src/test/regress/sql/reloptions.sql | 21 ++++
src/tools/pgindent/typedefs.list | 2 +
8 files changed, 202 insertions(+), 43 deletions(-)
diff --git a/src/backend/access/common/reloptions.c b/src/backend/access/common/reloptions.c
index 0b83f98ed5f..6f1f577581a 100644
--- a/src/backend/access/common/reloptions.c
+++ b/src/backend/access/common/reloptions.c
@@ -40,9 +40,9 @@
*
* To add an option:
*
- * (i) decide on a type (bool, integer, real, enum, string), name, default
- * value, upper and lower bounds (if applicable); for strings, consider a
- * validation routine.
+ * (i) decide on a type (bool, ternary, integer, real, enum, string), name,
+ * default value, upper and lower bounds (if applicable); for strings,
+ * consider a validation routine.
* (ii) add a record below (or use add_<type>_reloption).
* (iii) add it to the appropriate options struct (perhaps StdRdOptions)
* (iv) add it to the appropriate handling routine (perhaps
@@ -147,15 +147,6 @@ static relopt_bool boolRelOpts[] =
},
false
},
- {
- {
- "vacuum_truncate",
- "Enables vacuum to truncate empty pages at the end of this table",
- RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
- ShareUpdateExclusiveLock
- },
- true
- },
{
{
"deduplicate_items",
@@ -170,6 +161,25 @@ static relopt_bool boolRelOpts[] =
{{NULL}}
};
+static relopt_ternary ternaryRelOpts[] =
+{
+ {
+ {
+ "vacuum_truncate",
+ "Enables vacuum to truncate empty pages at the end of this table",
+ RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
+ ShareUpdateExclusiveLock
+ },
+ TERNARY_UNSET
+ },
+ /* list terminator */
+ {
+ {
+ NULL
+ }
+ }
+};
+
static relopt_int intRelOpts[] =
{
{
@@ -609,6 +619,13 @@ initialize_reloptions(void)
boolRelOpts[i].gen.lockmode));
j++;
}
+ for (i = 0; ternaryRelOpts[i].gen.name; i++)
+ {
+ Assert(DoLockModesConflict(ternaryRelOpts[i].gen.lockmode,
+ ternaryRelOpts[i].gen.lockmode));
+ j++;
+ }
+
for (i = 0; intRelOpts[i].gen.name; i++)
{
Assert(DoLockModesConflict(intRelOpts[i].gen.lockmode,
@@ -649,6 +666,14 @@ initialize_reloptions(void)
j++;
}
+ for (i = 0; ternaryRelOpts[i].gen.name; i++)
+ {
+ relOpts[j] = &ternaryRelOpts[i].gen;
+ relOpts[j]->type = RELOPT_TYPE_TERNARY;
+ relOpts[j]->namelen = strlen(relOpts[j]->name);
+ j++;
+ }
+
for (i = 0; intRelOpts[i].gen.name; i++)
{
relOpts[j] = &intRelOpts[i].gen;
@@ -809,6 +834,9 @@ allocate_reloption(bits32 kinds, int type, const char *name, const char *desc,
case RELOPT_TYPE_BOOL:
size = sizeof(relopt_bool);
break;
+ case RELOPT_TYPE_TERNARY:
+ size = sizeof(relopt_ternary);
+ break;
case RELOPT_TYPE_INT:
size = sizeof(relopt_int);
break;
@@ -892,6 +920,57 @@ add_local_bool_reloption(local_relopts *relopts, const char *name,
add_local_reloption(relopts, (relopt_gen *) newoption, offset);
}
+/*
+ * init_ternary_reloption
+ * Allocate and initialize a new ternary reloption
+ */
+static relopt_ternary *
+init_ternary_reloption(bits32 kinds, const char *name, const char *desc,
+ pg_ternary default_val, LOCKMODE lockmode)
+{
+ relopt_ternary *newoption;
+
+ newoption = (relopt_ternary *)
+ allocate_reloption(kinds, RELOPT_TYPE_TERNARY, name, desc, lockmode);
+ newoption->default_val = default_val;
+
+ return newoption;
+}
+
+/*
+ * add_ternary_reloption
+ * Add a new ternary reloption
+ */
+void
+add_ternary_reloption(bits32 kinds, const char *name, const char *desc,
+ pg_ternary default_val, LOCKMODE lockmode)
+{
+ relopt_ternary *newoption;
+
+ newoption =
+ init_ternary_reloption(kinds, name, desc, default_val, lockmode);
+
+ add_reloption((relopt_gen *) newoption);
+}
+
+/*
+ * add_local_ternary_reloption
+ * Add a new ternary local reloption
+ *
+ * 'offset' is offset of ternary-typed field.
+ */
+void
+add_local_ternary_reloption(local_relopts *relopts, const char *name,
+ const char *desc, pg_ternary default_val,
+ int offset)
+{
+ relopt_ternary *newoption;
+
+ newoption =
+ init_ternary_reloption(RELOPT_KIND_LOCAL, name, desc, default_val, 0);
+
+ add_local_reloption(relopts, (relopt_gen *) newoption, offset);
+}
/*
* init_real_reloption
@@ -1626,6 +1705,19 @@ parse_one_reloption(relopt_value *option, char *text_str, int text_len,
option->gen->name, value)));
}
break;
+ case RELOPT_TYPE_TERNARY:
+ {
+ bool b;
+
+ parsed = parse_bool(value, &b);
+ option->values.ternary_val = b ? TERNARY_TRUE : TERNARY_FALSE;
+ if (validate && !parsed)
+ ereport(ERROR,
+ (errcode(ERRCODE_INVALID_PARAMETER_VALUE),
+ errmsg("invalid value for ternary option \"%s\": %s",
+ option->gen->name, value)));
+ }
+ break;
case RELOPT_TYPE_INT:
{
relopt_int *optint = (relopt_int *) option->gen;
@@ -1789,17 +1881,6 @@ fillRelOptions(void *rdopts, Size basesize,
char *itempos = ((char *) rdopts) + elems[j].offset;
char *string_val;
- /*
- * If isset_offset is provided, store whether the reloption is
- * set there.
- */
- if (elems[j].isset_offset > 0)
- {
- char *setpos = ((char *) rdopts) + elems[j].isset_offset;
-
- *(bool *) setpos = options[i].isset;
- }
-
switch (options[i].gen->type)
{
case RELOPT_TYPE_BOOL:
@@ -1807,6 +1888,11 @@ fillRelOptions(void *rdopts, Size basesize,
options[i].values.bool_val :
((relopt_bool *) options[i].gen)->default_val;
break;
+ case RELOPT_TYPE_TERNARY:
+ *(pg_ternary *) itempos = options[i].isset ?
+ options[i].values.ternary_val :
+ ((relopt_ternary *) options[i].gen)->default_val;
+ break;
case RELOPT_TYPE_INT:
*(int *) itempos = options[i].isset ?
options[i].values.int_val :
@@ -1923,8 +2009,8 @@ default_reloptions(Datum reloptions, bool validate, relopt_kind kind)
offsetof(StdRdOptions, parallel_workers)},
{"vacuum_index_cleanup", RELOPT_TYPE_ENUM,
offsetof(StdRdOptions, vacuum_index_cleanup)},
- {"vacuum_truncate", RELOPT_TYPE_BOOL,
- offsetof(StdRdOptions, vacuum_truncate), offsetof(StdRdOptions, vacuum_truncate_set)},
+ {"vacuum_truncate", RELOPT_TYPE_TERNARY,
+ offsetof(StdRdOptions, vacuum_truncate)},
{"vacuum_max_eager_freeze_failure_rate", RELOPT_TYPE_REAL,
offsetof(StdRdOptions, vacuum_max_eager_freeze_failure_rate)}
};
@@ -2004,7 +2090,6 @@ build_local_reloptions(local_relopts *relopts, Datum options, bool validate)
elems[i].optname = opt->option->name;
elems[i].opttype = opt->option->type;
elems[i].offset = opt->offset;
- elems[i].isset_offset = 0; /* not supported for local relopts yet */
i++;
}
diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c
index aa4fbec143f..696eab9bd97 100644
--- a/src/backend/commands/vacuum.c
+++ b/src/backend/commands/vacuum.c
@@ -2224,9 +2224,9 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams params,
{
StdRdOptions *opts = (StdRdOptions *) rel->rd_options;
- if (opts && opts->vacuum_truncate_set)
+ if (opts && opts->vacuum_truncate != TERNARY_UNSET)
{
- if (opts->vacuum_truncate)
+ if (opts->vacuum_truncate == TERNARY_TRUE)
params.truncate = VACOPTVALUE_ENABLED;
else
params.truncate = VACOPTVALUE_DISABLED;
diff --git a/src/include/access/reloptions.h b/src/include/access/reloptions.h
index 2f08e1b0cf0..dfbef2babf2 100644
--- a/src/include/access/reloptions.h
+++ b/src/include/access/reloptions.h
@@ -29,6 +29,7 @@
typedef enum relopt_type
{
RELOPT_TYPE_BOOL,
+ RELOPT_TYPE_TERNARY, /* on, off, unset */
RELOPT_TYPE_INT,
RELOPT_TYPE_REAL,
RELOPT_TYPE_ENUM,
@@ -80,6 +81,7 @@ typedef struct relopt_value
union
{
bool bool_val;
+ pg_ternary ternary_val;
int int_val;
double real_val;
int enum_val;
@@ -94,6 +96,12 @@ typedef struct relopt_bool
bool default_val;
} relopt_bool;
+typedef struct relopt_ternary
+{
+ relopt_gen gen;
+ int default_val;
+} relopt_ternary;
+
typedef struct relopt_int
{
relopt_gen gen;
@@ -152,19 +160,6 @@ typedef struct
const char *optname; /* option's name */
relopt_type opttype; /* option's datatype */
int offset; /* offset of field in result struct */
-
- /*
- * isset_offset is an optional offset of a field in the result struct that
- * stores whether the option is explicitly set for the relation or if it
- * just picked up the default value. In most cases, this can be
- * accomplished by giving the reloption a special out-of-range default
- * value (e.g., some integer reloptions use -2), but this isn't always
- * possible. For example, a Boolean reloption cannot be given an
- * out-of-range default, so we need another way to discover the source of
- * its value. This offset is only used if given a value greater than
- * zero.
- */
- int isset_offset;
} relopt_parse_elt;
/* Local reloption definition */
@@ -195,6 +190,9 @@ typedef struct local_relopts
extern relopt_kind add_reloption_kind(void);
extern void add_bool_reloption(bits32 kinds, const char *name, const char *desc,
bool default_val, LOCKMODE lockmode);
+extern void add_ternary_reloption(bits32 kinds, const char *name,
+ const char *desc, pg_ternary default_val,
+ LOCKMODE lockmode);
extern void add_int_reloption(bits32 kinds, const char *name, const char *desc,
int default_val, int min_val, int max_val,
LOCKMODE lockmode);
@@ -214,6 +212,9 @@ extern void register_reloptions_validator(local_relopts *relopts,
extern void add_local_bool_reloption(local_relopts *relopts, const char *name,
const char *desc, bool default_val,
int offset);
+extern void add_local_ternary_reloption(local_relopts *relopts,
+ const char *name, const char *desc,
+ pg_ternary default_val, int offset);
extern void add_local_int_reloption(local_relopts *relopts, const char *name,
const char *desc, int default_val,
int min_val, int max_val, int offset);
diff --git a/src/include/postgres.h b/src/include/postgres.h
index 7d93fbce709..47c88780776 100644
--- a/src/include/postgres.h
+++ b/src/include/postgres.h
@@ -543,6 +543,21 @@ Float8GetDatum(float8 X)
* ----------------------------------------------------------------
*/
+/*
+ * pg_ternary
+ * Boolean value with an extra "unset" value
+ *
+ * This enum can be used for values that want to distinguish between true,
+ * false, and unset.
+*/
+
+typedef enum pg_ternary
+{
+ TERNARY_FALSE = 0,
+ TERNARY_TRUE = 1,
+ TERNARY_UNSET = -1
+} pg_ternary;
+
/*
* NON_EXEC_STATIC: It's sometimes useful to define a variable or function
* that is normally static but extern when using EXEC_BACKEND (see
diff --git a/src/include/utils/rel.h b/src/include/utils/rel.h
index d03ab247788..5feb18a5373 100644
--- a/src/include/utils/rel.h
+++ b/src/include/utils/rel.h
@@ -347,8 +347,7 @@ typedef struct StdRdOptions
bool user_catalog_table; /* use as an additional catalog relation */
int parallel_workers; /* max number of parallel workers */
StdRdOptIndexCleanup vacuum_index_cleanup; /* controls index vacuuming */
- bool vacuum_truncate; /* enables vacuum to truncate a relation */
- bool vacuum_truncate_set; /* whether vacuum_truncate is set */
+ pg_ternary vacuum_truncate; /* enables vacuum to truncate a relation */
/*
* Fraction of pages in a relation that vacuum can eagerly scan and fail
diff --git a/src/test/regress/expected/reloptions.out b/src/test/regress/expected/reloptions.out
index 9de19b4e3f1..1c99f79ab01 100644
--- a/src/test/regress/expected/reloptions.out
+++ b/src/test/regress/expected/reloptions.out
@@ -98,6 +98,42 @@ SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass;
{fillfactor=13,autovacuum_enabled=false}
(1 row)
+-- Tests for future (FIXME) ternary options
+-- behave as boolean option: accept unassigned name and truncated value
+DROP TABLE reloptions_test;
+CREATE TABLE reloptions_test(i INT) WITH (vacuum_truncate);
+SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass;
+ reloptions
+------------------------
+ {vacuum_truncate=true}
+(1 row)
+
+DROP TABLE reloptions_test;
+CREATE TABLE reloptions_test(i INT) WITH (vacuum_truncate=FaLS);
+SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass;
+ reloptions
+------------------------
+ {vacuum_truncate=fals}
+(1 row)
+
+-- preferred "true" alias is used when storing
+DROP TABLE reloptions_test;
+CREATE TABLE reloptions_test(i INT) WITH (vacuum_index_cleanup=on);
+SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass;
+ reloptions
+---------------------------
+ {vacuum_index_cleanup=on}
+(1 row)
+
+-- custom "third" value is available
+DROP TABLE reloptions_test;
+CREATE TABLE reloptions_test(i INT) WITH (vacuum_index_cleanup=auto);
+SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass;
+ reloptions
+-----------------------------
+ {vacuum_index_cleanup=auto}
+(1 row)
+
-- Test vacuum_truncate option
DROP TABLE reloptions_test;
CREATE TEMP TABLE reloptions_test(i INT NOT NULL, j text)
diff --git a/src/test/regress/sql/reloptions.sql b/src/test/regress/sql/reloptions.sql
index 24fbe0b478d..f5980dafcbc 100644
--- a/src/test/regress/sql/reloptions.sql
+++ b/src/test/regress/sql/reloptions.sql
@@ -59,6 +59,27 @@ UPDATE pg_class
ALTER TABLE reloptions_test RESET (illegal_option);
SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass;
+-- Tests for future (FIXME) ternary options
+
+-- behave as boolean option: accept unassigned name and truncated value
+DROP TABLE reloptions_test;
+CREATE TABLE reloptions_test(i INT) WITH (vacuum_truncate);
+SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass;
+
+DROP TABLE reloptions_test;
+CREATE TABLE reloptions_test(i INT) WITH (vacuum_truncate=FaLS);
+SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass;
+
+-- preferred "true" alias is used when storing
+DROP TABLE reloptions_test;
+CREATE TABLE reloptions_test(i INT) WITH (vacuum_index_cleanup=on);
+SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass;
+
+-- custom "third" value is available
+DROP TABLE reloptions_test;
+CREATE TABLE reloptions_test(i INT) WITH (vacuum_index_cleanup=auto);
+SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass;
+
-- Test vacuum_truncate option
DROP TABLE reloptions_test;
diff --git a/src/tools/pgindent/typedefs.list b/src/tools/pgindent/typedefs.list
index 3f3a888fd0e..1c8610fd46c 100644
--- a/src/tools/pgindent/typedefs.list
+++ b/src/tools/pgindent/typedefs.list
@@ -3952,6 +3952,7 @@ pg_sha512_ctx
pg_snapshot
pg_special_case
pg_stack_base_t
+pg_ternary
pg_time_t
pg_time_usec_t
pg_tz
@@ -4079,6 +4080,7 @@ relopt_kind
relopt_parse_elt
relopt_real
relopt_string
+relopt_ternary
relopt_type
relopt_value
relopts_validator
--
2.47.3
--c36t3fndxhkzbl5z--
^ permalink raw reply [nested|flat] 868+ messages in thread
* [PATCH v3] Introduce ternary reloptions
@ 2026-01-16 15:04 Álvaro Herrera <[email protected]>
0 siblings, 0 replies; 868+ messages in thread
From: Álvaro Herrera @ 2026-01-16 15:04 UTC (permalink / raw)
Introduce ternary reloption as a replacement for current
`vacuum_truncate` implementation. Remove the `vacuum_truncate_set`
separate flag and use TERNARY_UNSET instead.
This could also be used for other options such as `vacuum_index_cleanup`
and `buffering`, but lets get the scaffolding in first.
Discussion: https://postgr.es/m/3474141.usfYGdeWWP@thinkpad-pgpro
---
src/backend/access/common/reloptions.c | 137 ++++++++++++++++++-----
src/backend/commands/vacuum.c | 4 +-
src/include/access/reloptions.h | 27 ++---
src/include/postgres.h | 15 +++
src/include/utils/rel.h | 3 +-
src/test/regress/expected/reloptions.out | 36 ++++++
src/test/regress/sql/reloptions.sql | 21 ++++
src/tools/pgindent/typedefs.list | 2 +
8 files changed, 202 insertions(+), 43 deletions(-)
diff --git a/src/backend/access/common/reloptions.c b/src/backend/access/common/reloptions.c
index 0b83f98ed5f..6f1f577581a 100644
--- a/src/backend/access/common/reloptions.c
+++ b/src/backend/access/common/reloptions.c
@@ -40,9 +40,9 @@
*
* To add an option:
*
- * (i) decide on a type (bool, integer, real, enum, string), name, default
- * value, upper and lower bounds (if applicable); for strings, consider a
- * validation routine.
+ * (i) decide on a type (bool, ternary, integer, real, enum, string), name,
+ * default value, upper and lower bounds (if applicable); for strings,
+ * consider a validation routine.
* (ii) add a record below (or use add_<type>_reloption).
* (iii) add it to the appropriate options struct (perhaps StdRdOptions)
* (iv) add it to the appropriate handling routine (perhaps
@@ -147,15 +147,6 @@ static relopt_bool boolRelOpts[] =
},
false
},
- {
- {
- "vacuum_truncate",
- "Enables vacuum to truncate empty pages at the end of this table",
- RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
- ShareUpdateExclusiveLock
- },
- true
- },
{
{
"deduplicate_items",
@@ -170,6 +161,25 @@ static relopt_bool boolRelOpts[] =
{{NULL}}
};
+static relopt_ternary ternaryRelOpts[] =
+{
+ {
+ {
+ "vacuum_truncate",
+ "Enables vacuum to truncate empty pages at the end of this table",
+ RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
+ ShareUpdateExclusiveLock
+ },
+ TERNARY_UNSET
+ },
+ /* list terminator */
+ {
+ {
+ NULL
+ }
+ }
+};
+
static relopt_int intRelOpts[] =
{
{
@@ -609,6 +619,13 @@ initialize_reloptions(void)
boolRelOpts[i].gen.lockmode));
j++;
}
+ for (i = 0; ternaryRelOpts[i].gen.name; i++)
+ {
+ Assert(DoLockModesConflict(ternaryRelOpts[i].gen.lockmode,
+ ternaryRelOpts[i].gen.lockmode));
+ j++;
+ }
+
for (i = 0; intRelOpts[i].gen.name; i++)
{
Assert(DoLockModesConflict(intRelOpts[i].gen.lockmode,
@@ -649,6 +666,14 @@ initialize_reloptions(void)
j++;
}
+ for (i = 0; ternaryRelOpts[i].gen.name; i++)
+ {
+ relOpts[j] = &ternaryRelOpts[i].gen;
+ relOpts[j]->type = RELOPT_TYPE_TERNARY;
+ relOpts[j]->namelen = strlen(relOpts[j]->name);
+ j++;
+ }
+
for (i = 0; intRelOpts[i].gen.name; i++)
{
relOpts[j] = &intRelOpts[i].gen;
@@ -809,6 +834,9 @@ allocate_reloption(bits32 kinds, int type, const char *name, const char *desc,
case RELOPT_TYPE_BOOL:
size = sizeof(relopt_bool);
break;
+ case RELOPT_TYPE_TERNARY:
+ size = sizeof(relopt_ternary);
+ break;
case RELOPT_TYPE_INT:
size = sizeof(relopt_int);
break;
@@ -892,6 +920,57 @@ add_local_bool_reloption(local_relopts *relopts, const char *name,
add_local_reloption(relopts, (relopt_gen *) newoption, offset);
}
+/*
+ * init_ternary_reloption
+ * Allocate and initialize a new ternary reloption
+ */
+static relopt_ternary *
+init_ternary_reloption(bits32 kinds, const char *name, const char *desc,
+ pg_ternary default_val, LOCKMODE lockmode)
+{
+ relopt_ternary *newoption;
+
+ newoption = (relopt_ternary *)
+ allocate_reloption(kinds, RELOPT_TYPE_TERNARY, name, desc, lockmode);
+ newoption->default_val = default_val;
+
+ return newoption;
+}
+
+/*
+ * add_ternary_reloption
+ * Add a new ternary reloption
+ */
+void
+add_ternary_reloption(bits32 kinds, const char *name, const char *desc,
+ pg_ternary default_val, LOCKMODE lockmode)
+{
+ relopt_ternary *newoption;
+
+ newoption =
+ init_ternary_reloption(kinds, name, desc, default_val, lockmode);
+
+ add_reloption((relopt_gen *) newoption);
+}
+
+/*
+ * add_local_ternary_reloption
+ * Add a new ternary local reloption
+ *
+ * 'offset' is offset of ternary-typed field.
+ */
+void
+add_local_ternary_reloption(local_relopts *relopts, const char *name,
+ const char *desc, pg_ternary default_val,
+ int offset)
+{
+ relopt_ternary *newoption;
+
+ newoption =
+ init_ternary_reloption(RELOPT_KIND_LOCAL, name, desc, default_val, 0);
+
+ add_local_reloption(relopts, (relopt_gen *) newoption, offset);
+}
/*
* init_real_reloption
@@ -1626,6 +1705,19 @@ parse_one_reloption(relopt_value *option, char *text_str, int text_len,
option->gen->name, value)));
}
break;
+ case RELOPT_TYPE_TERNARY:
+ {
+ bool b;
+
+ parsed = parse_bool(value, &b);
+ option->values.ternary_val = b ? TERNARY_TRUE : TERNARY_FALSE;
+ if (validate && !parsed)
+ ereport(ERROR,
+ (errcode(ERRCODE_INVALID_PARAMETER_VALUE),
+ errmsg("invalid value for ternary option \"%s\": %s",
+ option->gen->name, value)));
+ }
+ break;
case RELOPT_TYPE_INT:
{
relopt_int *optint = (relopt_int *) option->gen;
@@ -1789,17 +1881,6 @@ fillRelOptions(void *rdopts, Size basesize,
char *itempos = ((char *) rdopts) + elems[j].offset;
char *string_val;
- /*
- * If isset_offset is provided, store whether the reloption is
- * set there.
- */
- if (elems[j].isset_offset > 0)
- {
- char *setpos = ((char *) rdopts) + elems[j].isset_offset;
-
- *(bool *) setpos = options[i].isset;
- }
-
switch (options[i].gen->type)
{
case RELOPT_TYPE_BOOL:
@@ -1807,6 +1888,11 @@ fillRelOptions(void *rdopts, Size basesize,
options[i].values.bool_val :
((relopt_bool *) options[i].gen)->default_val;
break;
+ case RELOPT_TYPE_TERNARY:
+ *(pg_ternary *) itempos = options[i].isset ?
+ options[i].values.ternary_val :
+ ((relopt_ternary *) options[i].gen)->default_val;
+ break;
case RELOPT_TYPE_INT:
*(int *) itempos = options[i].isset ?
options[i].values.int_val :
@@ -1923,8 +2009,8 @@ default_reloptions(Datum reloptions, bool validate, relopt_kind kind)
offsetof(StdRdOptions, parallel_workers)},
{"vacuum_index_cleanup", RELOPT_TYPE_ENUM,
offsetof(StdRdOptions, vacuum_index_cleanup)},
- {"vacuum_truncate", RELOPT_TYPE_BOOL,
- offsetof(StdRdOptions, vacuum_truncate), offsetof(StdRdOptions, vacuum_truncate_set)},
+ {"vacuum_truncate", RELOPT_TYPE_TERNARY,
+ offsetof(StdRdOptions, vacuum_truncate)},
{"vacuum_max_eager_freeze_failure_rate", RELOPT_TYPE_REAL,
offsetof(StdRdOptions, vacuum_max_eager_freeze_failure_rate)}
};
@@ -2004,7 +2090,6 @@ build_local_reloptions(local_relopts *relopts, Datum options, bool validate)
elems[i].optname = opt->option->name;
elems[i].opttype = opt->option->type;
elems[i].offset = opt->offset;
- elems[i].isset_offset = 0; /* not supported for local relopts yet */
i++;
}
diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c
index aa4fbec143f..696eab9bd97 100644
--- a/src/backend/commands/vacuum.c
+++ b/src/backend/commands/vacuum.c
@@ -2224,9 +2224,9 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams params,
{
StdRdOptions *opts = (StdRdOptions *) rel->rd_options;
- if (opts && opts->vacuum_truncate_set)
+ if (opts && opts->vacuum_truncate != TERNARY_UNSET)
{
- if (opts->vacuum_truncate)
+ if (opts->vacuum_truncate == TERNARY_TRUE)
params.truncate = VACOPTVALUE_ENABLED;
else
params.truncate = VACOPTVALUE_DISABLED;
diff --git a/src/include/access/reloptions.h b/src/include/access/reloptions.h
index 2f08e1b0cf0..dfbef2babf2 100644
--- a/src/include/access/reloptions.h
+++ b/src/include/access/reloptions.h
@@ -29,6 +29,7 @@
typedef enum relopt_type
{
RELOPT_TYPE_BOOL,
+ RELOPT_TYPE_TERNARY, /* on, off, unset */
RELOPT_TYPE_INT,
RELOPT_TYPE_REAL,
RELOPT_TYPE_ENUM,
@@ -80,6 +81,7 @@ typedef struct relopt_value
union
{
bool bool_val;
+ pg_ternary ternary_val;
int int_val;
double real_val;
int enum_val;
@@ -94,6 +96,12 @@ typedef struct relopt_bool
bool default_val;
} relopt_bool;
+typedef struct relopt_ternary
+{
+ relopt_gen gen;
+ int default_val;
+} relopt_ternary;
+
typedef struct relopt_int
{
relopt_gen gen;
@@ -152,19 +160,6 @@ typedef struct
const char *optname; /* option's name */
relopt_type opttype; /* option's datatype */
int offset; /* offset of field in result struct */
-
- /*
- * isset_offset is an optional offset of a field in the result struct that
- * stores whether the option is explicitly set for the relation or if it
- * just picked up the default value. In most cases, this can be
- * accomplished by giving the reloption a special out-of-range default
- * value (e.g., some integer reloptions use -2), but this isn't always
- * possible. For example, a Boolean reloption cannot be given an
- * out-of-range default, so we need another way to discover the source of
- * its value. This offset is only used if given a value greater than
- * zero.
- */
- int isset_offset;
} relopt_parse_elt;
/* Local reloption definition */
@@ -195,6 +190,9 @@ typedef struct local_relopts
extern relopt_kind add_reloption_kind(void);
extern void add_bool_reloption(bits32 kinds, const char *name, const char *desc,
bool default_val, LOCKMODE lockmode);
+extern void add_ternary_reloption(bits32 kinds, const char *name,
+ const char *desc, pg_ternary default_val,
+ LOCKMODE lockmode);
extern void add_int_reloption(bits32 kinds, const char *name, const char *desc,
int default_val, int min_val, int max_val,
LOCKMODE lockmode);
@@ -214,6 +212,9 @@ extern void register_reloptions_validator(local_relopts *relopts,
extern void add_local_bool_reloption(local_relopts *relopts, const char *name,
const char *desc, bool default_val,
int offset);
+extern void add_local_ternary_reloption(local_relopts *relopts,
+ const char *name, const char *desc,
+ pg_ternary default_val, int offset);
extern void add_local_int_reloption(local_relopts *relopts, const char *name,
const char *desc, int default_val,
int min_val, int max_val, int offset);
diff --git a/src/include/postgres.h b/src/include/postgres.h
index 7d93fbce709..47c88780776 100644
--- a/src/include/postgres.h
+++ b/src/include/postgres.h
@@ -543,6 +543,21 @@ Float8GetDatum(float8 X)
* ----------------------------------------------------------------
*/
+/*
+ * pg_ternary
+ * Boolean value with an extra "unset" value
+ *
+ * This enum can be used for values that want to distinguish between true,
+ * false, and unset.
+*/
+
+typedef enum pg_ternary
+{
+ TERNARY_FALSE = 0,
+ TERNARY_TRUE = 1,
+ TERNARY_UNSET = -1
+} pg_ternary;
+
/*
* NON_EXEC_STATIC: It's sometimes useful to define a variable or function
* that is normally static but extern when using EXEC_BACKEND (see
diff --git a/src/include/utils/rel.h b/src/include/utils/rel.h
index d03ab247788..5feb18a5373 100644
--- a/src/include/utils/rel.h
+++ b/src/include/utils/rel.h
@@ -347,8 +347,7 @@ typedef struct StdRdOptions
bool user_catalog_table; /* use as an additional catalog relation */
int parallel_workers; /* max number of parallel workers */
StdRdOptIndexCleanup vacuum_index_cleanup; /* controls index vacuuming */
- bool vacuum_truncate; /* enables vacuum to truncate a relation */
- bool vacuum_truncate_set; /* whether vacuum_truncate is set */
+ pg_ternary vacuum_truncate; /* enables vacuum to truncate a relation */
/*
* Fraction of pages in a relation that vacuum can eagerly scan and fail
diff --git a/src/test/regress/expected/reloptions.out b/src/test/regress/expected/reloptions.out
index 9de19b4e3f1..1c99f79ab01 100644
--- a/src/test/regress/expected/reloptions.out
+++ b/src/test/regress/expected/reloptions.out
@@ -98,6 +98,42 @@ SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass;
{fillfactor=13,autovacuum_enabled=false}
(1 row)
+-- Tests for future (FIXME) ternary options
+-- behave as boolean option: accept unassigned name and truncated value
+DROP TABLE reloptions_test;
+CREATE TABLE reloptions_test(i INT) WITH (vacuum_truncate);
+SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass;
+ reloptions
+------------------------
+ {vacuum_truncate=true}
+(1 row)
+
+DROP TABLE reloptions_test;
+CREATE TABLE reloptions_test(i INT) WITH (vacuum_truncate=FaLS);
+SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass;
+ reloptions
+------------------------
+ {vacuum_truncate=fals}
+(1 row)
+
+-- preferred "true" alias is used when storing
+DROP TABLE reloptions_test;
+CREATE TABLE reloptions_test(i INT) WITH (vacuum_index_cleanup=on);
+SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass;
+ reloptions
+---------------------------
+ {vacuum_index_cleanup=on}
+(1 row)
+
+-- custom "third" value is available
+DROP TABLE reloptions_test;
+CREATE TABLE reloptions_test(i INT) WITH (vacuum_index_cleanup=auto);
+SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass;
+ reloptions
+-----------------------------
+ {vacuum_index_cleanup=auto}
+(1 row)
+
-- Test vacuum_truncate option
DROP TABLE reloptions_test;
CREATE TEMP TABLE reloptions_test(i INT NOT NULL, j text)
diff --git a/src/test/regress/sql/reloptions.sql b/src/test/regress/sql/reloptions.sql
index 24fbe0b478d..f5980dafcbc 100644
--- a/src/test/regress/sql/reloptions.sql
+++ b/src/test/regress/sql/reloptions.sql
@@ -59,6 +59,27 @@ UPDATE pg_class
ALTER TABLE reloptions_test RESET (illegal_option);
SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass;
+-- Tests for future (FIXME) ternary options
+
+-- behave as boolean option: accept unassigned name and truncated value
+DROP TABLE reloptions_test;
+CREATE TABLE reloptions_test(i INT) WITH (vacuum_truncate);
+SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass;
+
+DROP TABLE reloptions_test;
+CREATE TABLE reloptions_test(i INT) WITH (vacuum_truncate=FaLS);
+SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass;
+
+-- preferred "true" alias is used when storing
+DROP TABLE reloptions_test;
+CREATE TABLE reloptions_test(i INT) WITH (vacuum_index_cleanup=on);
+SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass;
+
+-- custom "third" value is available
+DROP TABLE reloptions_test;
+CREATE TABLE reloptions_test(i INT) WITH (vacuum_index_cleanup=auto);
+SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass;
+
-- Test vacuum_truncate option
DROP TABLE reloptions_test;
diff --git a/src/tools/pgindent/typedefs.list b/src/tools/pgindent/typedefs.list
index 3f3a888fd0e..1c8610fd46c 100644
--- a/src/tools/pgindent/typedefs.list
+++ b/src/tools/pgindent/typedefs.list
@@ -3952,6 +3952,7 @@ pg_sha512_ctx
pg_snapshot
pg_special_case
pg_stack_base_t
+pg_ternary
pg_time_t
pg_time_usec_t
pg_tz
@@ -4079,6 +4080,7 @@ relopt_kind
relopt_parse_elt
relopt_real
relopt_string
+relopt_ternary
relopt_type
relopt_value
relopts_validator
--
2.47.3
--c36t3fndxhkzbl5z--
^ permalink raw reply [nested|flat] 868+ messages in thread
* [PATCH v3] Introduce ternary reloptions
@ 2026-01-16 15:04 Álvaro Herrera <[email protected]>
0 siblings, 0 replies; 868+ messages in thread
From: Álvaro Herrera @ 2026-01-16 15:04 UTC (permalink / raw)
Introduce ternary reloption as a replacement for current
`vacuum_truncate` implementation. Remove the `vacuum_truncate_set`
separate flag and use TERNARY_UNSET instead.
This could also be used for other options such as `vacuum_index_cleanup`
and `buffering`, but lets get the scaffolding in first.
Discussion: https://postgr.es/m/3474141.usfYGdeWWP@thinkpad-pgpro
---
src/backend/access/common/reloptions.c | 137 ++++++++++++++++++-----
src/backend/commands/vacuum.c | 4 +-
src/include/access/reloptions.h | 27 ++---
src/include/postgres.h | 15 +++
src/include/utils/rel.h | 3 +-
src/test/regress/expected/reloptions.out | 36 ++++++
src/test/regress/sql/reloptions.sql | 21 ++++
src/tools/pgindent/typedefs.list | 2 +
8 files changed, 202 insertions(+), 43 deletions(-)
diff --git a/src/backend/access/common/reloptions.c b/src/backend/access/common/reloptions.c
index 0b83f98ed5f..6f1f577581a 100644
--- a/src/backend/access/common/reloptions.c
+++ b/src/backend/access/common/reloptions.c
@@ -40,9 +40,9 @@
*
* To add an option:
*
- * (i) decide on a type (bool, integer, real, enum, string), name, default
- * value, upper and lower bounds (if applicable); for strings, consider a
- * validation routine.
+ * (i) decide on a type (bool, ternary, integer, real, enum, string), name,
+ * default value, upper and lower bounds (if applicable); for strings,
+ * consider a validation routine.
* (ii) add a record below (or use add_<type>_reloption).
* (iii) add it to the appropriate options struct (perhaps StdRdOptions)
* (iv) add it to the appropriate handling routine (perhaps
@@ -147,15 +147,6 @@ static relopt_bool boolRelOpts[] =
},
false
},
- {
- {
- "vacuum_truncate",
- "Enables vacuum to truncate empty pages at the end of this table",
- RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
- ShareUpdateExclusiveLock
- },
- true
- },
{
{
"deduplicate_items",
@@ -170,6 +161,25 @@ static relopt_bool boolRelOpts[] =
{{NULL}}
};
+static relopt_ternary ternaryRelOpts[] =
+{
+ {
+ {
+ "vacuum_truncate",
+ "Enables vacuum to truncate empty pages at the end of this table",
+ RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
+ ShareUpdateExclusiveLock
+ },
+ TERNARY_UNSET
+ },
+ /* list terminator */
+ {
+ {
+ NULL
+ }
+ }
+};
+
static relopt_int intRelOpts[] =
{
{
@@ -609,6 +619,13 @@ initialize_reloptions(void)
boolRelOpts[i].gen.lockmode));
j++;
}
+ for (i = 0; ternaryRelOpts[i].gen.name; i++)
+ {
+ Assert(DoLockModesConflict(ternaryRelOpts[i].gen.lockmode,
+ ternaryRelOpts[i].gen.lockmode));
+ j++;
+ }
+
for (i = 0; intRelOpts[i].gen.name; i++)
{
Assert(DoLockModesConflict(intRelOpts[i].gen.lockmode,
@@ -649,6 +666,14 @@ initialize_reloptions(void)
j++;
}
+ for (i = 0; ternaryRelOpts[i].gen.name; i++)
+ {
+ relOpts[j] = &ternaryRelOpts[i].gen;
+ relOpts[j]->type = RELOPT_TYPE_TERNARY;
+ relOpts[j]->namelen = strlen(relOpts[j]->name);
+ j++;
+ }
+
for (i = 0; intRelOpts[i].gen.name; i++)
{
relOpts[j] = &intRelOpts[i].gen;
@@ -809,6 +834,9 @@ allocate_reloption(bits32 kinds, int type, const char *name, const char *desc,
case RELOPT_TYPE_BOOL:
size = sizeof(relopt_bool);
break;
+ case RELOPT_TYPE_TERNARY:
+ size = sizeof(relopt_ternary);
+ break;
case RELOPT_TYPE_INT:
size = sizeof(relopt_int);
break;
@@ -892,6 +920,57 @@ add_local_bool_reloption(local_relopts *relopts, const char *name,
add_local_reloption(relopts, (relopt_gen *) newoption, offset);
}
+/*
+ * init_ternary_reloption
+ * Allocate and initialize a new ternary reloption
+ */
+static relopt_ternary *
+init_ternary_reloption(bits32 kinds, const char *name, const char *desc,
+ pg_ternary default_val, LOCKMODE lockmode)
+{
+ relopt_ternary *newoption;
+
+ newoption = (relopt_ternary *)
+ allocate_reloption(kinds, RELOPT_TYPE_TERNARY, name, desc, lockmode);
+ newoption->default_val = default_val;
+
+ return newoption;
+}
+
+/*
+ * add_ternary_reloption
+ * Add a new ternary reloption
+ */
+void
+add_ternary_reloption(bits32 kinds, const char *name, const char *desc,
+ pg_ternary default_val, LOCKMODE lockmode)
+{
+ relopt_ternary *newoption;
+
+ newoption =
+ init_ternary_reloption(kinds, name, desc, default_val, lockmode);
+
+ add_reloption((relopt_gen *) newoption);
+}
+
+/*
+ * add_local_ternary_reloption
+ * Add a new ternary local reloption
+ *
+ * 'offset' is offset of ternary-typed field.
+ */
+void
+add_local_ternary_reloption(local_relopts *relopts, const char *name,
+ const char *desc, pg_ternary default_val,
+ int offset)
+{
+ relopt_ternary *newoption;
+
+ newoption =
+ init_ternary_reloption(RELOPT_KIND_LOCAL, name, desc, default_val, 0);
+
+ add_local_reloption(relopts, (relopt_gen *) newoption, offset);
+}
/*
* init_real_reloption
@@ -1626,6 +1705,19 @@ parse_one_reloption(relopt_value *option, char *text_str, int text_len,
option->gen->name, value)));
}
break;
+ case RELOPT_TYPE_TERNARY:
+ {
+ bool b;
+
+ parsed = parse_bool(value, &b);
+ option->values.ternary_val = b ? TERNARY_TRUE : TERNARY_FALSE;
+ if (validate && !parsed)
+ ereport(ERROR,
+ (errcode(ERRCODE_INVALID_PARAMETER_VALUE),
+ errmsg("invalid value for ternary option \"%s\": %s",
+ option->gen->name, value)));
+ }
+ break;
case RELOPT_TYPE_INT:
{
relopt_int *optint = (relopt_int *) option->gen;
@@ -1789,17 +1881,6 @@ fillRelOptions(void *rdopts, Size basesize,
char *itempos = ((char *) rdopts) + elems[j].offset;
char *string_val;
- /*
- * If isset_offset is provided, store whether the reloption is
- * set there.
- */
- if (elems[j].isset_offset > 0)
- {
- char *setpos = ((char *) rdopts) + elems[j].isset_offset;
-
- *(bool *) setpos = options[i].isset;
- }
-
switch (options[i].gen->type)
{
case RELOPT_TYPE_BOOL:
@@ -1807,6 +1888,11 @@ fillRelOptions(void *rdopts, Size basesize,
options[i].values.bool_val :
((relopt_bool *) options[i].gen)->default_val;
break;
+ case RELOPT_TYPE_TERNARY:
+ *(pg_ternary *) itempos = options[i].isset ?
+ options[i].values.ternary_val :
+ ((relopt_ternary *) options[i].gen)->default_val;
+ break;
case RELOPT_TYPE_INT:
*(int *) itempos = options[i].isset ?
options[i].values.int_val :
@@ -1923,8 +2009,8 @@ default_reloptions(Datum reloptions, bool validate, relopt_kind kind)
offsetof(StdRdOptions, parallel_workers)},
{"vacuum_index_cleanup", RELOPT_TYPE_ENUM,
offsetof(StdRdOptions, vacuum_index_cleanup)},
- {"vacuum_truncate", RELOPT_TYPE_BOOL,
- offsetof(StdRdOptions, vacuum_truncate), offsetof(StdRdOptions, vacuum_truncate_set)},
+ {"vacuum_truncate", RELOPT_TYPE_TERNARY,
+ offsetof(StdRdOptions, vacuum_truncate)},
{"vacuum_max_eager_freeze_failure_rate", RELOPT_TYPE_REAL,
offsetof(StdRdOptions, vacuum_max_eager_freeze_failure_rate)}
};
@@ -2004,7 +2090,6 @@ build_local_reloptions(local_relopts *relopts, Datum options, bool validate)
elems[i].optname = opt->option->name;
elems[i].opttype = opt->option->type;
elems[i].offset = opt->offset;
- elems[i].isset_offset = 0; /* not supported for local relopts yet */
i++;
}
diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c
index aa4fbec143f..696eab9bd97 100644
--- a/src/backend/commands/vacuum.c
+++ b/src/backend/commands/vacuum.c
@@ -2224,9 +2224,9 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams params,
{
StdRdOptions *opts = (StdRdOptions *) rel->rd_options;
- if (opts && opts->vacuum_truncate_set)
+ if (opts && opts->vacuum_truncate != TERNARY_UNSET)
{
- if (opts->vacuum_truncate)
+ if (opts->vacuum_truncate == TERNARY_TRUE)
params.truncate = VACOPTVALUE_ENABLED;
else
params.truncate = VACOPTVALUE_DISABLED;
diff --git a/src/include/access/reloptions.h b/src/include/access/reloptions.h
index 2f08e1b0cf0..dfbef2babf2 100644
--- a/src/include/access/reloptions.h
+++ b/src/include/access/reloptions.h
@@ -29,6 +29,7 @@
typedef enum relopt_type
{
RELOPT_TYPE_BOOL,
+ RELOPT_TYPE_TERNARY, /* on, off, unset */
RELOPT_TYPE_INT,
RELOPT_TYPE_REAL,
RELOPT_TYPE_ENUM,
@@ -80,6 +81,7 @@ typedef struct relopt_value
union
{
bool bool_val;
+ pg_ternary ternary_val;
int int_val;
double real_val;
int enum_val;
@@ -94,6 +96,12 @@ typedef struct relopt_bool
bool default_val;
} relopt_bool;
+typedef struct relopt_ternary
+{
+ relopt_gen gen;
+ int default_val;
+} relopt_ternary;
+
typedef struct relopt_int
{
relopt_gen gen;
@@ -152,19 +160,6 @@ typedef struct
const char *optname; /* option's name */
relopt_type opttype; /* option's datatype */
int offset; /* offset of field in result struct */
-
- /*
- * isset_offset is an optional offset of a field in the result struct that
- * stores whether the option is explicitly set for the relation or if it
- * just picked up the default value. In most cases, this can be
- * accomplished by giving the reloption a special out-of-range default
- * value (e.g., some integer reloptions use -2), but this isn't always
- * possible. For example, a Boolean reloption cannot be given an
- * out-of-range default, so we need another way to discover the source of
- * its value. This offset is only used if given a value greater than
- * zero.
- */
- int isset_offset;
} relopt_parse_elt;
/* Local reloption definition */
@@ -195,6 +190,9 @@ typedef struct local_relopts
extern relopt_kind add_reloption_kind(void);
extern void add_bool_reloption(bits32 kinds, const char *name, const char *desc,
bool default_val, LOCKMODE lockmode);
+extern void add_ternary_reloption(bits32 kinds, const char *name,
+ const char *desc, pg_ternary default_val,
+ LOCKMODE lockmode);
extern void add_int_reloption(bits32 kinds, const char *name, const char *desc,
int default_val, int min_val, int max_val,
LOCKMODE lockmode);
@@ -214,6 +212,9 @@ extern void register_reloptions_validator(local_relopts *relopts,
extern void add_local_bool_reloption(local_relopts *relopts, const char *name,
const char *desc, bool default_val,
int offset);
+extern void add_local_ternary_reloption(local_relopts *relopts,
+ const char *name, const char *desc,
+ pg_ternary default_val, int offset);
extern void add_local_int_reloption(local_relopts *relopts, const char *name,
const char *desc, int default_val,
int min_val, int max_val, int offset);
diff --git a/src/include/postgres.h b/src/include/postgres.h
index 7d93fbce709..47c88780776 100644
--- a/src/include/postgres.h
+++ b/src/include/postgres.h
@@ -543,6 +543,21 @@ Float8GetDatum(float8 X)
* ----------------------------------------------------------------
*/
+/*
+ * pg_ternary
+ * Boolean value with an extra "unset" value
+ *
+ * This enum can be used for values that want to distinguish between true,
+ * false, and unset.
+*/
+
+typedef enum pg_ternary
+{
+ TERNARY_FALSE = 0,
+ TERNARY_TRUE = 1,
+ TERNARY_UNSET = -1
+} pg_ternary;
+
/*
* NON_EXEC_STATIC: It's sometimes useful to define a variable or function
* that is normally static but extern when using EXEC_BACKEND (see
diff --git a/src/include/utils/rel.h b/src/include/utils/rel.h
index d03ab247788..5feb18a5373 100644
--- a/src/include/utils/rel.h
+++ b/src/include/utils/rel.h
@@ -347,8 +347,7 @@ typedef struct StdRdOptions
bool user_catalog_table; /* use as an additional catalog relation */
int parallel_workers; /* max number of parallel workers */
StdRdOptIndexCleanup vacuum_index_cleanup; /* controls index vacuuming */
- bool vacuum_truncate; /* enables vacuum to truncate a relation */
- bool vacuum_truncate_set; /* whether vacuum_truncate is set */
+ pg_ternary vacuum_truncate; /* enables vacuum to truncate a relation */
/*
* Fraction of pages in a relation that vacuum can eagerly scan and fail
diff --git a/src/test/regress/expected/reloptions.out b/src/test/regress/expected/reloptions.out
index 9de19b4e3f1..1c99f79ab01 100644
--- a/src/test/regress/expected/reloptions.out
+++ b/src/test/regress/expected/reloptions.out
@@ -98,6 +98,42 @@ SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass;
{fillfactor=13,autovacuum_enabled=false}
(1 row)
+-- Tests for future (FIXME) ternary options
+-- behave as boolean option: accept unassigned name and truncated value
+DROP TABLE reloptions_test;
+CREATE TABLE reloptions_test(i INT) WITH (vacuum_truncate);
+SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass;
+ reloptions
+------------------------
+ {vacuum_truncate=true}
+(1 row)
+
+DROP TABLE reloptions_test;
+CREATE TABLE reloptions_test(i INT) WITH (vacuum_truncate=FaLS);
+SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass;
+ reloptions
+------------------------
+ {vacuum_truncate=fals}
+(1 row)
+
+-- preferred "true" alias is used when storing
+DROP TABLE reloptions_test;
+CREATE TABLE reloptions_test(i INT) WITH (vacuum_index_cleanup=on);
+SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass;
+ reloptions
+---------------------------
+ {vacuum_index_cleanup=on}
+(1 row)
+
+-- custom "third" value is available
+DROP TABLE reloptions_test;
+CREATE TABLE reloptions_test(i INT) WITH (vacuum_index_cleanup=auto);
+SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass;
+ reloptions
+-----------------------------
+ {vacuum_index_cleanup=auto}
+(1 row)
+
-- Test vacuum_truncate option
DROP TABLE reloptions_test;
CREATE TEMP TABLE reloptions_test(i INT NOT NULL, j text)
diff --git a/src/test/regress/sql/reloptions.sql b/src/test/regress/sql/reloptions.sql
index 24fbe0b478d..f5980dafcbc 100644
--- a/src/test/regress/sql/reloptions.sql
+++ b/src/test/regress/sql/reloptions.sql
@@ -59,6 +59,27 @@ UPDATE pg_class
ALTER TABLE reloptions_test RESET (illegal_option);
SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass;
+-- Tests for future (FIXME) ternary options
+
+-- behave as boolean option: accept unassigned name and truncated value
+DROP TABLE reloptions_test;
+CREATE TABLE reloptions_test(i INT) WITH (vacuum_truncate);
+SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass;
+
+DROP TABLE reloptions_test;
+CREATE TABLE reloptions_test(i INT) WITH (vacuum_truncate=FaLS);
+SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass;
+
+-- preferred "true" alias is used when storing
+DROP TABLE reloptions_test;
+CREATE TABLE reloptions_test(i INT) WITH (vacuum_index_cleanup=on);
+SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass;
+
+-- custom "third" value is available
+DROP TABLE reloptions_test;
+CREATE TABLE reloptions_test(i INT) WITH (vacuum_index_cleanup=auto);
+SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass;
+
-- Test vacuum_truncate option
DROP TABLE reloptions_test;
diff --git a/src/tools/pgindent/typedefs.list b/src/tools/pgindent/typedefs.list
index 3f3a888fd0e..1c8610fd46c 100644
--- a/src/tools/pgindent/typedefs.list
+++ b/src/tools/pgindent/typedefs.list
@@ -3952,6 +3952,7 @@ pg_sha512_ctx
pg_snapshot
pg_special_case
pg_stack_base_t
+pg_ternary
pg_time_t
pg_time_usec_t
pg_tz
@@ -4079,6 +4080,7 @@ relopt_kind
relopt_parse_elt
relopt_real
relopt_string
+relopt_ternary
relopt_type
relopt_value
relopts_validator
--
2.47.3
--c36t3fndxhkzbl5z--
^ permalink raw reply [nested|flat] 868+ messages in thread
* [PATCH v3] Introduce ternary reloptions
@ 2026-01-16 15:04 Álvaro Herrera <[email protected]>
0 siblings, 0 replies; 868+ messages in thread
From: Álvaro Herrera @ 2026-01-16 15:04 UTC (permalink / raw)
Introduce ternary reloption as a replacement for current
`vacuum_truncate` implementation. Remove the `vacuum_truncate_set`
separate flag and use TERNARY_UNSET instead.
This could also be used for other options such as `vacuum_index_cleanup`
and `buffering`, but lets get the scaffolding in first.
Discussion: https://postgr.es/m/3474141.usfYGdeWWP@thinkpad-pgpro
---
src/backend/access/common/reloptions.c | 137 ++++++++++++++++++-----
src/backend/commands/vacuum.c | 4 +-
src/include/access/reloptions.h | 27 ++---
src/include/postgres.h | 15 +++
src/include/utils/rel.h | 3 +-
src/test/regress/expected/reloptions.out | 36 ++++++
src/test/regress/sql/reloptions.sql | 21 ++++
src/tools/pgindent/typedefs.list | 2 +
8 files changed, 202 insertions(+), 43 deletions(-)
diff --git a/src/backend/access/common/reloptions.c b/src/backend/access/common/reloptions.c
index 0b83f98ed5f..6f1f577581a 100644
--- a/src/backend/access/common/reloptions.c
+++ b/src/backend/access/common/reloptions.c
@@ -40,9 +40,9 @@
*
* To add an option:
*
- * (i) decide on a type (bool, integer, real, enum, string), name, default
- * value, upper and lower bounds (if applicable); for strings, consider a
- * validation routine.
+ * (i) decide on a type (bool, ternary, integer, real, enum, string), name,
+ * default value, upper and lower bounds (if applicable); for strings,
+ * consider a validation routine.
* (ii) add a record below (or use add_<type>_reloption).
* (iii) add it to the appropriate options struct (perhaps StdRdOptions)
* (iv) add it to the appropriate handling routine (perhaps
@@ -147,15 +147,6 @@ static relopt_bool boolRelOpts[] =
},
false
},
- {
- {
- "vacuum_truncate",
- "Enables vacuum to truncate empty pages at the end of this table",
- RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
- ShareUpdateExclusiveLock
- },
- true
- },
{
{
"deduplicate_items",
@@ -170,6 +161,25 @@ static relopt_bool boolRelOpts[] =
{{NULL}}
};
+static relopt_ternary ternaryRelOpts[] =
+{
+ {
+ {
+ "vacuum_truncate",
+ "Enables vacuum to truncate empty pages at the end of this table",
+ RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
+ ShareUpdateExclusiveLock
+ },
+ TERNARY_UNSET
+ },
+ /* list terminator */
+ {
+ {
+ NULL
+ }
+ }
+};
+
static relopt_int intRelOpts[] =
{
{
@@ -609,6 +619,13 @@ initialize_reloptions(void)
boolRelOpts[i].gen.lockmode));
j++;
}
+ for (i = 0; ternaryRelOpts[i].gen.name; i++)
+ {
+ Assert(DoLockModesConflict(ternaryRelOpts[i].gen.lockmode,
+ ternaryRelOpts[i].gen.lockmode));
+ j++;
+ }
+
for (i = 0; intRelOpts[i].gen.name; i++)
{
Assert(DoLockModesConflict(intRelOpts[i].gen.lockmode,
@@ -649,6 +666,14 @@ initialize_reloptions(void)
j++;
}
+ for (i = 0; ternaryRelOpts[i].gen.name; i++)
+ {
+ relOpts[j] = &ternaryRelOpts[i].gen;
+ relOpts[j]->type = RELOPT_TYPE_TERNARY;
+ relOpts[j]->namelen = strlen(relOpts[j]->name);
+ j++;
+ }
+
for (i = 0; intRelOpts[i].gen.name; i++)
{
relOpts[j] = &intRelOpts[i].gen;
@@ -809,6 +834,9 @@ allocate_reloption(bits32 kinds, int type, const char *name, const char *desc,
case RELOPT_TYPE_BOOL:
size = sizeof(relopt_bool);
break;
+ case RELOPT_TYPE_TERNARY:
+ size = sizeof(relopt_ternary);
+ break;
case RELOPT_TYPE_INT:
size = sizeof(relopt_int);
break;
@@ -892,6 +920,57 @@ add_local_bool_reloption(local_relopts *relopts, const char *name,
add_local_reloption(relopts, (relopt_gen *) newoption, offset);
}
+/*
+ * init_ternary_reloption
+ * Allocate and initialize a new ternary reloption
+ */
+static relopt_ternary *
+init_ternary_reloption(bits32 kinds, const char *name, const char *desc,
+ pg_ternary default_val, LOCKMODE lockmode)
+{
+ relopt_ternary *newoption;
+
+ newoption = (relopt_ternary *)
+ allocate_reloption(kinds, RELOPT_TYPE_TERNARY, name, desc, lockmode);
+ newoption->default_val = default_val;
+
+ return newoption;
+}
+
+/*
+ * add_ternary_reloption
+ * Add a new ternary reloption
+ */
+void
+add_ternary_reloption(bits32 kinds, const char *name, const char *desc,
+ pg_ternary default_val, LOCKMODE lockmode)
+{
+ relopt_ternary *newoption;
+
+ newoption =
+ init_ternary_reloption(kinds, name, desc, default_val, lockmode);
+
+ add_reloption((relopt_gen *) newoption);
+}
+
+/*
+ * add_local_ternary_reloption
+ * Add a new ternary local reloption
+ *
+ * 'offset' is offset of ternary-typed field.
+ */
+void
+add_local_ternary_reloption(local_relopts *relopts, const char *name,
+ const char *desc, pg_ternary default_val,
+ int offset)
+{
+ relopt_ternary *newoption;
+
+ newoption =
+ init_ternary_reloption(RELOPT_KIND_LOCAL, name, desc, default_val, 0);
+
+ add_local_reloption(relopts, (relopt_gen *) newoption, offset);
+}
/*
* init_real_reloption
@@ -1626,6 +1705,19 @@ parse_one_reloption(relopt_value *option, char *text_str, int text_len,
option->gen->name, value)));
}
break;
+ case RELOPT_TYPE_TERNARY:
+ {
+ bool b;
+
+ parsed = parse_bool(value, &b);
+ option->values.ternary_val = b ? TERNARY_TRUE : TERNARY_FALSE;
+ if (validate && !parsed)
+ ereport(ERROR,
+ (errcode(ERRCODE_INVALID_PARAMETER_VALUE),
+ errmsg("invalid value for ternary option \"%s\": %s",
+ option->gen->name, value)));
+ }
+ break;
case RELOPT_TYPE_INT:
{
relopt_int *optint = (relopt_int *) option->gen;
@@ -1789,17 +1881,6 @@ fillRelOptions(void *rdopts, Size basesize,
char *itempos = ((char *) rdopts) + elems[j].offset;
char *string_val;
- /*
- * If isset_offset is provided, store whether the reloption is
- * set there.
- */
- if (elems[j].isset_offset > 0)
- {
- char *setpos = ((char *) rdopts) + elems[j].isset_offset;
-
- *(bool *) setpos = options[i].isset;
- }
-
switch (options[i].gen->type)
{
case RELOPT_TYPE_BOOL:
@@ -1807,6 +1888,11 @@ fillRelOptions(void *rdopts, Size basesize,
options[i].values.bool_val :
((relopt_bool *) options[i].gen)->default_val;
break;
+ case RELOPT_TYPE_TERNARY:
+ *(pg_ternary *) itempos = options[i].isset ?
+ options[i].values.ternary_val :
+ ((relopt_ternary *) options[i].gen)->default_val;
+ break;
case RELOPT_TYPE_INT:
*(int *) itempos = options[i].isset ?
options[i].values.int_val :
@@ -1923,8 +2009,8 @@ default_reloptions(Datum reloptions, bool validate, relopt_kind kind)
offsetof(StdRdOptions, parallel_workers)},
{"vacuum_index_cleanup", RELOPT_TYPE_ENUM,
offsetof(StdRdOptions, vacuum_index_cleanup)},
- {"vacuum_truncate", RELOPT_TYPE_BOOL,
- offsetof(StdRdOptions, vacuum_truncate), offsetof(StdRdOptions, vacuum_truncate_set)},
+ {"vacuum_truncate", RELOPT_TYPE_TERNARY,
+ offsetof(StdRdOptions, vacuum_truncate)},
{"vacuum_max_eager_freeze_failure_rate", RELOPT_TYPE_REAL,
offsetof(StdRdOptions, vacuum_max_eager_freeze_failure_rate)}
};
@@ -2004,7 +2090,6 @@ build_local_reloptions(local_relopts *relopts, Datum options, bool validate)
elems[i].optname = opt->option->name;
elems[i].opttype = opt->option->type;
elems[i].offset = opt->offset;
- elems[i].isset_offset = 0; /* not supported for local relopts yet */
i++;
}
diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c
index aa4fbec143f..696eab9bd97 100644
--- a/src/backend/commands/vacuum.c
+++ b/src/backend/commands/vacuum.c
@@ -2224,9 +2224,9 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams params,
{
StdRdOptions *opts = (StdRdOptions *) rel->rd_options;
- if (opts && opts->vacuum_truncate_set)
+ if (opts && opts->vacuum_truncate != TERNARY_UNSET)
{
- if (opts->vacuum_truncate)
+ if (opts->vacuum_truncate == TERNARY_TRUE)
params.truncate = VACOPTVALUE_ENABLED;
else
params.truncate = VACOPTVALUE_DISABLED;
diff --git a/src/include/access/reloptions.h b/src/include/access/reloptions.h
index 2f08e1b0cf0..dfbef2babf2 100644
--- a/src/include/access/reloptions.h
+++ b/src/include/access/reloptions.h
@@ -29,6 +29,7 @@
typedef enum relopt_type
{
RELOPT_TYPE_BOOL,
+ RELOPT_TYPE_TERNARY, /* on, off, unset */
RELOPT_TYPE_INT,
RELOPT_TYPE_REAL,
RELOPT_TYPE_ENUM,
@@ -80,6 +81,7 @@ typedef struct relopt_value
union
{
bool bool_val;
+ pg_ternary ternary_val;
int int_val;
double real_val;
int enum_val;
@@ -94,6 +96,12 @@ typedef struct relopt_bool
bool default_val;
} relopt_bool;
+typedef struct relopt_ternary
+{
+ relopt_gen gen;
+ int default_val;
+} relopt_ternary;
+
typedef struct relopt_int
{
relopt_gen gen;
@@ -152,19 +160,6 @@ typedef struct
const char *optname; /* option's name */
relopt_type opttype; /* option's datatype */
int offset; /* offset of field in result struct */
-
- /*
- * isset_offset is an optional offset of a field in the result struct that
- * stores whether the option is explicitly set for the relation or if it
- * just picked up the default value. In most cases, this can be
- * accomplished by giving the reloption a special out-of-range default
- * value (e.g., some integer reloptions use -2), but this isn't always
- * possible. For example, a Boolean reloption cannot be given an
- * out-of-range default, so we need another way to discover the source of
- * its value. This offset is only used if given a value greater than
- * zero.
- */
- int isset_offset;
} relopt_parse_elt;
/* Local reloption definition */
@@ -195,6 +190,9 @@ typedef struct local_relopts
extern relopt_kind add_reloption_kind(void);
extern void add_bool_reloption(bits32 kinds, const char *name, const char *desc,
bool default_val, LOCKMODE lockmode);
+extern void add_ternary_reloption(bits32 kinds, const char *name,
+ const char *desc, pg_ternary default_val,
+ LOCKMODE lockmode);
extern void add_int_reloption(bits32 kinds, const char *name, const char *desc,
int default_val, int min_val, int max_val,
LOCKMODE lockmode);
@@ -214,6 +212,9 @@ extern void register_reloptions_validator(local_relopts *relopts,
extern void add_local_bool_reloption(local_relopts *relopts, const char *name,
const char *desc, bool default_val,
int offset);
+extern void add_local_ternary_reloption(local_relopts *relopts,
+ const char *name, const char *desc,
+ pg_ternary default_val, int offset);
extern void add_local_int_reloption(local_relopts *relopts, const char *name,
const char *desc, int default_val,
int min_val, int max_val, int offset);
diff --git a/src/include/postgres.h b/src/include/postgres.h
index 7d93fbce709..47c88780776 100644
--- a/src/include/postgres.h
+++ b/src/include/postgres.h
@@ -543,6 +543,21 @@ Float8GetDatum(float8 X)
* ----------------------------------------------------------------
*/
+/*
+ * pg_ternary
+ * Boolean value with an extra "unset" value
+ *
+ * This enum can be used for values that want to distinguish between true,
+ * false, and unset.
+*/
+
+typedef enum pg_ternary
+{
+ TERNARY_FALSE = 0,
+ TERNARY_TRUE = 1,
+ TERNARY_UNSET = -1
+} pg_ternary;
+
/*
* NON_EXEC_STATIC: It's sometimes useful to define a variable or function
* that is normally static but extern when using EXEC_BACKEND (see
diff --git a/src/include/utils/rel.h b/src/include/utils/rel.h
index d03ab247788..5feb18a5373 100644
--- a/src/include/utils/rel.h
+++ b/src/include/utils/rel.h
@@ -347,8 +347,7 @@ typedef struct StdRdOptions
bool user_catalog_table; /* use as an additional catalog relation */
int parallel_workers; /* max number of parallel workers */
StdRdOptIndexCleanup vacuum_index_cleanup; /* controls index vacuuming */
- bool vacuum_truncate; /* enables vacuum to truncate a relation */
- bool vacuum_truncate_set; /* whether vacuum_truncate is set */
+ pg_ternary vacuum_truncate; /* enables vacuum to truncate a relation */
/*
* Fraction of pages in a relation that vacuum can eagerly scan and fail
diff --git a/src/test/regress/expected/reloptions.out b/src/test/regress/expected/reloptions.out
index 9de19b4e3f1..1c99f79ab01 100644
--- a/src/test/regress/expected/reloptions.out
+++ b/src/test/regress/expected/reloptions.out
@@ -98,6 +98,42 @@ SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass;
{fillfactor=13,autovacuum_enabled=false}
(1 row)
+-- Tests for future (FIXME) ternary options
+-- behave as boolean option: accept unassigned name and truncated value
+DROP TABLE reloptions_test;
+CREATE TABLE reloptions_test(i INT) WITH (vacuum_truncate);
+SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass;
+ reloptions
+------------------------
+ {vacuum_truncate=true}
+(1 row)
+
+DROP TABLE reloptions_test;
+CREATE TABLE reloptions_test(i INT) WITH (vacuum_truncate=FaLS);
+SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass;
+ reloptions
+------------------------
+ {vacuum_truncate=fals}
+(1 row)
+
+-- preferred "true" alias is used when storing
+DROP TABLE reloptions_test;
+CREATE TABLE reloptions_test(i INT) WITH (vacuum_index_cleanup=on);
+SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass;
+ reloptions
+---------------------------
+ {vacuum_index_cleanup=on}
+(1 row)
+
+-- custom "third" value is available
+DROP TABLE reloptions_test;
+CREATE TABLE reloptions_test(i INT) WITH (vacuum_index_cleanup=auto);
+SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass;
+ reloptions
+-----------------------------
+ {vacuum_index_cleanup=auto}
+(1 row)
+
-- Test vacuum_truncate option
DROP TABLE reloptions_test;
CREATE TEMP TABLE reloptions_test(i INT NOT NULL, j text)
diff --git a/src/test/regress/sql/reloptions.sql b/src/test/regress/sql/reloptions.sql
index 24fbe0b478d..f5980dafcbc 100644
--- a/src/test/regress/sql/reloptions.sql
+++ b/src/test/regress/sql/reloptions.sql
@@ -59,6 +59,27 @@ UPDATE pg_class
ALTER TABLE reloptions_test RESET (illegal_option);
SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass;
+-- Tests for future (FIXME) ternary options
+
+-- behave as boolean option: accept unassigned name and truncated value
+DROP TABLE reloptions_test;
+CREATE TABLE reloptions_test(i INT) WITH (vacuum_truncate);
+SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass;
+
+DROP TABLE reloptions_test;
+CREATE TABLE reloptions_test(i INT) WITH (vacuum_truncate=FaLS);
+SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass;
+
+-- preferred "true" alias is used when storing
+DROP TABLE reloptions_test;
+CREATE TABLE reloptions_test(i INT) WITH (vacuum_index_cleanup=on);
+SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass;
+
+-- custom "third" value is available
+DROP TABLE reloptions_test;
+CREATE TABLE reloptions_test(i INT) WITH (vacuum_index_cleanup=auto);
+SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass;
+
-- Test vacuum_truncate option
DROP TABLE reloptions_test;
diff --git a/src/tools/pgindent/typedefs.list b/src/tools/pgindent/typedefs.list
index 3f3a888fd0e..1c8610fd46c 100644
--- a/src/tools/pgindent/typedefs.list
+++ b/src/tools/pgindent/typedefs.list
@@ -3952,6 +3952,7 @@ pg_sha512_ctx
pg_snapshot
pg_special_case
pg_stack_base_t
+pg_ternary
pg_time_t
pg_time_usec_t
pg_tz
@@ -4079,6 +4080,7 @@ relopt_kind
relopt_parse_elt
relopt_real
relopt_string
+relopt_ternary
relopt_type
relopt_value
relopts_validator
--
2.47.3
--c36t3fndxhkzbl5z--
^ permalink raw reply [nested|flat] 868+ messages in thread
* [PATCH v3] Introduce ternary reloptions
@ 2026-01-16 15:04 Álvaro Herrera <[email protected]>
0 siblings, 0 replies; 868+ messages in thread
From: Álvaro Herrera @ 2026-01-16 15:04 UTC (permalink / raw)
Introduce ternary reloption as a replacement for current
`vacuum_truncate` implementation. Remove the `vacuum_truncate_set`
separate flag and use TERNARY_UNSET instead.
This could also be used for other options such as `vacuum_index_cleanup`
and `buffering`, but lets get the scaffolding in first.
Discussion: https://postgr.es/m/3474141.usfYGdeWWP@thinkpad-pgpro
---
src/backend/access/common/reloptions.c | 137 ++++++++++++++++++-----
src/backend/commands/vacuum.c | 4 +-
src/include/access/reloptions.h | 27 ++---
src/include/postgres.h | 15 +++
src/include/utils/rel.h | 3 +-
src/test/regress/expected/reloptions.out | 36 ++++++
src/test/regress/sql/reloptions.sql | 21 ++++
src/tools/pgindent/typedefs.list | 2 +
8 files changed, 202 insertions(+), 43 deletions(-)
diff --git a/src/backend/access/common/reloptions.c b/src/backend/access/common/reloptions.c
index 0b83f98ed5f..6f1f577581a 100644
--- a/src/backend/access/common/reloptions.c
+++ b/src/backend/access/common/reloptions.c
@@ -40,9 +40,9 @@
*
* To add an option:
*
- * (i) decide on a type (bool, integer, real, enum, string), name, default
- * value, upper and lower bounds (if applicable); for strings, consider a
- * validation routine.
+ * (i) decide on a type (bool, ternary, integer, real, enum, string), name,
+ * default value, upper and lower bounds (if applicable); for strings,
+ * consider a validation routine.
* (ii) add a record below (or use add_<type>_reloption).
* (iii) add it to the appropriate options struct (perhaps StdRdOptions)
* (iv) add it to the appropriate handling routine (perhaps
@@ -147,15 +147,6 @@ static relopt_bool boolRelOpts[] =
},
false
},
- {
- {
- "vacuum_truncate",
- "Enables vacuum to truncate empty pages at the end of this table",
- RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
- ShareUpdateExclusiveLock
- },
- true
- },
{
{
"deduplicate_items",
@@ -170,6 +161,25 @@ static relopt_bool boolRelOpts[] =
{{NULL}}
};
+static relopt_ternary ternaryRelOpts[] =
+{
+ {
+ {
+ "vacuum_truncate",
+ "Enables vacuum to truncate empty pages at the end of this table",
+ RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
+ ShareUpdateExclusiveLock
+ },
+ TERNARY_UNSET
+ },
+ /* list terminator */
+ {
+ {
+ NULL
+ }
+ }
+};
+
static relopt_int intRelOpts[] =
{
{
@@ -609,6 +619,13 @@ initialize_reloptions(void)
boolRelOpts[i].gen.lockmode));
j++;
}
+ for (i = 0; ternaryRelOpts[i].gen.name; i++)
+ {
+ Assert(DoLockModesConflict(ternaryRelOpts[i].gen.lockmode,
+ ternaryRelOpts[i].gen.lockmode));
+ j++;
+ }
+
for (i = 0; intRelOpts[i].gen.name; i++)
{
Assert(DoLockModesConflict(intRelOpts[i].gen.lockmode,
@@ -649,6 +666,14 @@ initialize_reloptions(void)
j++;
}
+ for (i = 0; ternaryRelOpts[i].gen.name; i++)
+ {
+ relOpts[j] = &ternaryRelOpts[i].gen;
+ relOpts[j]->type = RELOPT_TYPE_TERNARY;
+ relOpts[j]->namelen = strlen(relOpts[j]->name);
+ j++;
+ }
+
for (i = 0; intRelOpts[i].gen.name; i++)
{
relOpts[j] = &intRelOpts[i].gen;
@@ -809,6 +834,9 @@ allocate_reloption(bits32 kinds, int type, const char *name, const char *desc,
case RELOPT_TYPE_BOOL:
size = sizeof(relopt_bool);
break;
+ case RELOPT_TYPE_TERNARY:
+ size = sizeof(relopt_ternary);
+ break;
case RELOPT_TYPE_INT:
size = sizeof(relopt_int);
break;
@@ -892,6 +920,57 @@ add_local_bool_reloption(local_relopts *relopts, const char *name,
add_local_reloption(relopts, (relopt_gen *) newoption, offset);
}
+/*
+ * init_ternary_reloption
+ * Allocate and initialize a new ternary reloption
+ */
+static relopt_ternary *
+init_ternary_reloption(bits32 kinds, const char *name, const char *desc,
+ pg_ternary default_val, LOCKMODE lockmode)
+{
+ relopt_ternary *newoption;
+
+ newoption = (relopt_ternary *)
+ allocate_reloption(kinds, RELOPT_TYPE_TERNARY, name, desc, lockmode);
+ newoption->default_val = default_val;
+
+ return newoption;
+}
+
+/*
+ * add_ternary_reloption
+ * Add a new ternary reloption
+ */
+void
+add_ternary_reloption(bits32 kinds, const char *name, const char *desc,
+ pg_ternary default_val, LOCKMODE lockmode)
+{
+ relopt_ternary *newoption;
+
+ newoption =
+ init_ternary_reloption(kinds, name, desc, default_val, lockmode);
+
+ add_reloption((relopt_gen *) newoption);
+}
+
+/*
+ * add_local_ternary_reloption
+ * Add a new ternary local reloption
+ *
+ * 'offset' is offset of ternary-typed field.
+ */
+void
+add_local_ternary_reloption(local_relopts *relopts, const char *name,
+ const char *desc, pg_ternary default_val,
+ int offset)
+{
+ relopt_ternary *newoption;
+
+ newoption =
+ init_ternary_reloption(RELOPT_KIND_LOCAL, name, desc, default_val, 0);
+
+ add_local_reloption(relopts, (relopt_gen *) newoption, offset);
+}
/*
* init_real_reloption
@@ -1626,6 +1705,19 @@ parse_one_reloption(relopt_value *option, char *text_str, int text_len,
option->gen->name, value)));
}
break;
+ case RELOPT_TYPE_TERNARY:
+ {
+ bool b;
+
+ parsed = parse_bool(value, &b);
+ option->values.ternary_val = b ? TERNARY_TRUE : TERNARY_FALSE;
+ if (validate && !parsed)
+ ereport(ERROR,
+ (errcode(ERRCODE_INVALID_PARAMETER_VALUE),
+ errmsg("invalid value for ternary option \"%s\": %s",
+ option->gen->name, value)));
+ }
+ break;
case RELOPT_TYPE_INT:
{
relopt_int *optint = (relopt_int *) option->gen;
@@ -1789,17 +1881,6 @@ fillRelOptions(void *rdopts, Size basesize,
char *itempos = ((char *) rdopts) + elems[j].offset;
char *string_val;
- /*
- * If isset_offset is provided, store whether the reloption is
- * set there.
- */
- if (elems[j].isset_offset > 0)
- {
- char *setpos = ((char *) rdopts) + elems[j].isset_offset;
-
- *(bool *) setpos = options[i].isset;
- }
-
switch (options[i].gen->type)
{
case RELOPT_TYPE_BOOL:
@@ -1807,6 +1888,11 @@ fillRelOptions(void *rdopts, Size basesize,
options[i].values.bool_val :
((relopt_bool *) options[i].gen)->default_val;
break;
+ case RELOPT_TYPE_TERNARY:
+ *(pg_ternary *) itempos = options[i].isset ?
+ options[i].values.ternary_val :
+ ((relopt_ternary *) options[i].gen)->default_val;
+ break;
case RELOPT_TYPE_INT:
*(int *) itempos = options[i].isset ?
options[i].values.int_val :
@@ -1923,8 +2009,8 @@ default_reloptions(Datum reloptions, bool validate, relopt_kind kind)
offsetof(StdRdOptions, parallel_workers)},
{"vacuum_index_cleanup", RELOPT_TYPE_ENUM,
offsetof(StdRdOptions, vacuum_index_cleanup)},
- {"vacuum_truncate", RELOPT_TYPE_BOOL,
- offsetof(StdRdOptions, vacuum_truncate), offsetof(StdRdOptions, vacuum_truncate_set)},
+ {"vacuum_truncate", RELOPT_TYPE_TERNARY,
+ offsetof(StdRdOptions, vacuum_truncate)},
{"vacuum_max_eager_freeze_failure_rate", RELOPT_TYPE_REAL,
offsetof(StdRdOptions, vacuum_max_eager_freeze_failure_rate)}
};
@@ -2004,7 +2090,6 @@ build_local_reloptions(local_relopts *relopts, Datum options, bool validate)
elems[i].optname = opt->option->name;
elems[i].opttype = opt->option->type;
elems[i].offset = opt->offset;
- elems[i].isset_offset = 0; /* not supported for local relopts yet */
i++;
}
diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c
index aa4fbec143f..696eab9bd97 100644
--- a/src/backend/commands/vacuum.c
+++ b/src/backend/commands/vacuum.c
@@ -2224,9 +2224,9 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams params,
{
StdRdOptions *opts = (StdRdOptions *) rel->rd_options;
- if (opts && opts->vacuum_truncate_set)
+ if (opts && opts->vacuum_truncate != TERNARY_UNSET)
{
- if (opts->vacuum_truncate)
+ if (opts->vacuum_truncate == TERNARY_TRUE)
params.truncate = VACOPTVALUE_ENABLED;
else
params.truncate = VACOPTVALUE_DISABLED;
diff --git a/src/include/access/reloptions.h b/src/include/access/reloptions.h
index 2f08e1b0cf0..dfbef2babf2 100644
--- a/src/include/access/reloptions.h
+++ b/src/include/access/reloptions.h
@@ -29,6 +29,7 @@
typedef enum relopt_type
{
RELOPT_TYPE_BOOL,
+ RELOPT_TYPE_TERNARY, /* on, off, unset */
RELOPT_TYPE_INT,
RELOPT_TYPE_REAL,
RELOPT_TYPE_ENUM,
@@ -80,6 +81,7 @@ typedef struct relopt_value
union
{
bool bool_val;
+ pg_ternary ternary_val;
int int_val;
double real_val;
int enum_val;
@@ -94,6 +96,12 @@ typedef struct relopt_bool
bool default_val;
} relopt_bool;
+typedef struct relopt_ternary
+{
+ relopt_gen gen;
+ int default_val;
+} relopt_ternary;
+
typedef struct relopt_int
{
relopt_gen gen;
@@ -152,19 +160,6 @@ typedef struct
const char *optname; /* option's name */
relopt_type opttype; /* option's datatype */
int offset; /* offset of field in result struct */
-
- /*
- * isset_offset is an optional offset of a field in the result struct that
- * stores whether the option is explicitly set for the relation or if it
- * just picked up the default value. In most cases, this can be
- * accomplished by giving the reloption a special out-of-range default
- * value (e.g., some integer reloptions use -2), but this isn't always
- * possible. For example, a Boolean reloption cannot be given an
- * out-of-range default, so we need another way to discover the source of
- * its value. This offset is only used if given a value greater than
- * zero.
- */
- int isset_offset;
} relopt_parse_elt;
/* Local reloption definition */
@@ -195,6 +190,9 @@ typedef struct local_relopts
extern relopt_kind add_reloption_kind(void);
extern void add_bool_reloption(bits32 kinds, const char *name, const char *desc,
bool default_val, LOCKMODE lockmode);
+extern void add_ternary_reloption(bits32 kinds, const char *name,
+ const char *desc, pg_ternary default_val,
+ LOCKMODE lockmode);
extern void add_int_reloption(bits32 kinds, const char *name, const char *desc,
int default_val, int min_val, int max_val,
LOCKMODE lockmode);
@@ -214,6 +212,9 @@ extern void register_reloptions_validator(local_relopts *relopts,
extern void add_local_bool_reloption(local_relopts *relopts, const char *name,
const char *desc, bool default_val,
int offset);
+extern void add_local_ternary_reloption(local_relopts *relopts,
+ const char *name, const char *desc,
+ pg_ternary default_val, int offset);
extern void add_local_int_reloption(local_relopts *relopts, const char *name,
const char *desc, int default_val,
int min_val, int max_val, int offset);
diff --git a/src/include/postgres.h b/src/include/postgres.h
index 7d93fbce709..47c88780776 100644
--- a/src/include/postgres.h
+++ b/src/include/postgres.h
@@ -543,6 +543,21 @@ Float8GetDatum(float8 X)
* ----------------------------------------------------------------
*/
+/*
+ * pg_ternary
+ * Boolean value with an extra "unset" value
+ *
+ * This enum can be used for values that want to distinguish between true,
+ * false, and unset.
+*/
+
+typedef enum pg_ternary
+{
+ TERNARY_FALSE = 0,
+ TERNARY_TRUE = 1,
+ TERNARY_UNSET = -1
+} pg_ternary;
+
/*
* NON_EXEC_STATIC: It's sometimes useful to define a variable or function
* that is normally static but extern when using EXEC_BACKEND (see
diff --git a/src/include/utils/rel.h b/src/include/utils/rel.h
index d03ab247788..5feb18a5373 100644
--- a/src/include/utils/rel.h
+++ b/src/include/utils/rel.h
@@ -347,8 +347,7 @@ typedef struct StdRdOptions
bool user_catalog_table; /* use as an additional catalog relation */
int parallel_workers; /* max number of parallel workers */
StdRdOptIndexCleanup vacuum_index_cleanup; /* controls index vacuuming */
- bool vacuum_truncate; /* enables vacuum to truncate a relation */
- bool vacuum_truncate_set; /* whether vacuum_truncate is set */
+ pg_ternary vacuum_truncate; /* enables vacuum to truncate a relation */
/*
* Fraction of pages in a relation that vacuum can eagerly scan and fail
diff --git a/src/test/regress/expected/reloptions.out b/src/test/regress/expected/reloptions.out
index 9de19b4e3f1..1c99f79ab01 100644
--- a/src/test/regress/expected/reloptions.out
+++ b/src/test/regress/expected/reloptions.out
@@ -98,6 +98,42 @@ SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass;
{fillfactor=13,autovacuum_enabled=false}
(1 row)
+-- Tests for future (FIXME) ternary options
+-- behave as boolean option: accept unassigned name and truncated value
+DROP TABLE reloptions_test;
+CREATE TABLE reloptions_test(i INT) WITH (vacuum_truncate);
+SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass;
+ reloptions
+------------------------
+ {vacuum_truncate=true}
+(1 row)
+
+DROP TABLE reloptions_test;
+CREATE TABLE reloptions_test(i INT) WITH (vacuum_truncate=FaLS);
+SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass;
+ reloptions
+------------------------
+ {vacuum_truncate=fals}
+(1 row)
+
+-- preferred "true" alias is used when storing
+DROP TABLE reloptions_test;
+CREATE TABLE reloptions_test(i INT) WITH (vacuum_index_cleanup=on);
+SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass;
+ reloptions
+---------------------------
+ {vacuum_index_cleanup=on}
+(1 row)
+
+-- custom "third" value is available
+DROP TABLE reloptions_test;
+CREATE TABLE reloptions_test(i INT) WITH (vacuum_index_cleanup=auto);
+SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass;
+ reloptions
+-----------------------------
+ {vacuum_index_cleanup=auto}
+(1 row)
+
-- Test vacuum_truncate option
DROP TABLE reloptions_test;
CREATE TEMP TABLE reloptions_test(i INT NOT NULL, j text)
diff --git a/src/test/regress/sql/reloptions.sql b/src/test/regress/sql/reloptions.sql
index 24fbe0b478d..f5980dafcbc 100644
--- a/src/test/regress/sql/reloptions.sql
+++ b/src/test/regress/sql/reloptions.sql
@@ -59,6 +59,27 @@ UPDATE pg_class
ALTER TABLE reloptions_test RESET (illegal_option);
SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass;
+-- Tests for future (FIXME) ternary options
+
+-- behave as boolean option: accept unassigned name and truncated value
+DROP TABLE reloptions_test;
+CREATE TABLE reloptions_test(i INT) WITH (vacuum_truncate);
+SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass;
+
+DROP TABLE reloptions_test;
+CREATE TABLE reloptions_test(i INT) WITH (vacuum_truncate=FaLS);
+SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass;
+
+-- preferred "true" alias is used when storing
+DROP TABLE reloptions_test;
+CREATE TABLE reloptions_test(i INT) WITH (vacuum_index_cleanup=on);
+SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass;
+
+-- custom "third" value is available
+DROP TABLE reloptions_test;
+CREATE TABLE reloptions_test(i INT) WITH (vacuum_index_cleanup=auto);
+SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass;
+
-- Test vacuum_truncate option
DROP TABLE reloptions_test;
diff --git a/src/tools/pgindent/typedefs.list b/src/tools/pgindent/typedefs.list
index 3f3a888fd0e..1c8610fd46c 100644
--- a/src/tools/pgindent/typedefs.list
+++ b/src/tools/pgindent/typedefs.list
@@ -3952,6 +3952,7 @@ pg_sha512_ctx
pg_snapshot
pg_special_case
pg_stack_base_t
+pg_ternary
pg_time_t
pg_time_usec_t
pg_tz
@@ -4079,6 +4080,7 @@ relopt_kind
relopt_parse_elt
relopt_real
relopt_string
+relopt_ternary
relopt_type
relopt_value
relopts_validator
--
2.47.3
--c36t3fndxhkzbl5z--
^ permalink raw reply [nested|flat] 868+ messages in thread
* [PATCH v3] Introduce ternary reloptions
@ 2026-01-16 15:04 Álvaro Herrera <[email protected]>
0 siblings, 0 replies; 868+ messages in thread
From: Álvaro Herrera @ 2026-01-16 15:04 UTC (permalink / raw)
Introduce ternary reloption as a replacement for current
`vacuum_truncate` implementation. Remove the `vacuum_truncate_set`
separate flag and use TERNARY_UNSET instead.
This could also be used for other options such as `vacuum_index_cleanup`
and `buffering`, but lets get the scaffolding in first.
Discussion: https://postgr.es/m/3474141.usfYGdeWWP@thinkpad-pgpro
---
src/backend/access/common/reloptions.c | 137 ++++++++++++++++++-----
src/backend/commands/vacuum.c | 4 +-
src/include/access/reloptions.h | 27 ++---
src/include/postgres.h | 15 +++
src/include/utils/rel.h | 3 +-
src/test/regress/expected/reloptions.out | 36 ++++++
src/test/regress/sql/reloptions.sql | 21 ++++
src/tools/pgindent/typedefs.list | 2 +
8 files changed, 202 insertions(+), 43 deletions(-)
diff --git a/src/backend/access/common/reloptions.c b/src/backend/access/common/reloptions.c
index 0b83f98ed5f..6f1f577581a 100644
--- a/src/backend/access/common/reloptions.c
+++ b/src/backend/access/common/reloptions.c
@@ -40,9 +40,9 @@
*
* To add an option:
*
- * (i) decide on a type (bool, integer, real, enum, string), name, default
- * value, upper and lower bounds (if applicable); for strings, consider a
- * validation routine.
+ * (i) decide on a type (bool, ternary, integer, real, enum, string), name,
+ * default value, upper and lower bounds (if applicable); for strings,
+ * consider a validation routine.
* (ii) add a record below (or use add_<type>_reloption).
* (iii) add it to the appropriate options struct (perhaps StdRdOptions)
* (iv) add it to the appropriate handling routine (perhaps
@@ -147,15 +147,6 @@ static relopt_bool boolRelOpts[] =
},
false
},
- {
- {
- "vacuum_truncate",
- "Enables vacuum to truncate empty pages at the end of this table",
- RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
- ShareUpdateExclusiveLock
- },
- true
- },
{
{
"deduplicate_items",
@@ -170,6 +161,25 @@ static relopt_bool boolRelOpts[] =
{{NULL}}
};
+static relopt_ternary ternaryRelOpts[] =
+{
+ {
+ {
+ "vacuum_truncate",
+ "Enables vacuum to truncate empty pages at the end of this table",
+ RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
+ ShareUpdateExclusiveLock
+ },
+ TERNARY_UNSET
+ },
+ /* list terminator */
+ {
+ {
+ NULL
+ }
+ }
+};
+
static relopt_int intRelOpts[] =
{
{
@@ -609,6 +619,13 @@ initialize_reloptions(void)
boolRelOpts[i].gen.lockmode));
j++;
}
+ for (i = 0; ternaryRelOpts[i].gen.name; i++)
+ {
+ Assert(DoLockModesConflict(ternaryRelOpts[i].gen.lockmode,
+ ternaryRelOpts[i].gen.lockmode));
+ j++;
+ }
+
for (i = 0; intRelOpts[i].gen.name; i++)
{
Assert(DoLockModesConflict(intRelOpts[i].gen.lockmode,
@@ -649,6 +666,14 @@ initialize_reloptions(void)
j++;
}
+ for (i = 0; ternaryRelOpts[i].gen.name; i++)
+ {
+ relOpts[j] = &ternaryRelOpts[i].gen;
+ relOpts[j]->type = RELOPT_TYPE_TERNARY;
+ relOpts[j]->namelen = strlen(relOpts[j]->name);
+ j++;
+ }
+
for (i = 0; intRelOpts[i].gen.name; i++)
{
relOpts[j] = &intRelOpts[i].gen;
@@ -809,6 +834,9 @@ allocate_reloption(bits32 kinds, int type, const char *name, const char *desc,
case RELOPT_TYPE_BOOL:
size = sizeof(relopt_bool);
break;
+ case RELOPT_TYPE_TERNARY:
+ size = sizeof(relopt_ternary);
+ break;
case RELOPT_TYPE_INT:
size = sizeof(relopt_int);
break;
@@ -892,6 +920,57 @@ add_local_bool_reloption(local_relopts *relopts, const char *name,
add_local_reloption(relopts, (relopt_gen *) newoption, offset);
}
+/*
+ * init_ternary_reloption
+ * Allocate and initialize a new ternary reloption
+ */
+static relopt_ternary *
+init_ternary_reloption(bits32 kinds, const char *name, const char *desc,
+ pg_ternary default_val, LOCKMODE lockmode)
+{
+ relopt_ternary *newoption;
+
+ newoption = (relopt_ternary *)
+ allocate_reloption(kinds, RELOPT_TYPE_TERNARY, name, desc, lockmode);
+ newoption->default_val = default_val;
+
+ return newoption;
+}
+
+/*
+ * add_ternary_reloption
+ * Add a new ternary reloption
+ */
+void
+add_ternary_reloption(bits32 kinds, const char *name, const char *desc,
+ pg_ternary default_val, LOCKMODE lockmode)
+{
+ relopt_ternary *newoption;
+
+ newoption =
+ init_ternary_reloption(kinds, name, desc, default_val, lockmode);
+
+ add_reloption((relopt_gen *) newoption);
+}
+
+/*
+ * add_local_ternary_reloption
+ * Add a new ternary local reloption
+ *
+ * 'offset' is offset of ternary-typed field.
+ */
+void
+add_local_ternary_reloption(local_relopts *relopts, const char *name,
+ const char *desc, pg_ternary default_val,
+ int offset)
+{
+ relopt_ternary *newoption;
+
+ newoption =
+ init_ternary_reloption(RELOPT_KIND_LOCAL, name, desc, default_val, 0);
+
+ add_local_reloption(relopts, (relopt_gen *) newoption, offset);
+}
/*
* init_real_reloption
@@ -1626,6 +1705,19 @@ parse_one_reloption(relopt_value *option, char *text_str, int text_len,
option->gen->name, value)));
}
break;
+ case RELOPT_TYPE_TERNARY:
+ {
+ bool b;
+
+ parsed = parse_bool(value, &b);
+ option->values.ternary_val = b ? TERNARY_TRUE : TERNARY_FALSE;
+ if (validate && !parsed)
+ ereport(ERROR,
+ (errcode(ERRCODE_INVALID_PARAMETER_VALUE),
+ errmsg("invalid value for ternary option \"%s\": %s",
+ option->gen->name, value)));
+ }
+ break;
case RELOPT_TYPE_INT:
{
relopt_int *optint = (relopt_int *) option->gen;
@@ -1789,17 +1881,6 @@ fillRelOptions(void *rdopts, Size basesize,
char *itempos = ((char *) rdopts) + elems[j].offset;
char *string_val;
- /*
- * If isset_offset is provided, store whether the reloption is
- * set there.
- */
- if (elems[j].isset_offset > 0)
- {
- char *setpos = ((char *) rdopts) + elems[j].isset_offset;
-
- *(bool *) setpos = options[i].isset;
- }
-
switch (options[i].gen->type)
{
case RELOPT_TYPE_BOOL:
@@ -1807,6 +1888,11 @@ fillRelOptions(void *rdopts, Size basesize,
options[i].values.bool_val :
((relopt_bool *) options[i].gen)->default_val;
break;
+ case RELOPT_TYPE_TERNARY:
+ *(pg_ternary *) itempos = options[i].isset ?
+ options[i].values.ternary_val :
+ ((relopt_ternary *) options[i].gen)->default_val;
+ break;
case RELOPT_TYPE_INT:
*(int *) itempos = options[i].isset ?
options[i].values.int_val :
@@ -1923,8 +2009,8 @@ default_reloptions(Datum reloptions, bool validate, relopt_kind kind)
offsetof(StdRdOptions, parallel_workers)},
{"vacuum_index_cleanup", RELOPT_TYPE_ENUM,
offsetof(StdRdOptions, vacuum_index_cleanup)},
- {"vacuum_truncate", RELOPT_TYPE_BOOL,
- offsetof(StdRdOptions, vacuum_truncate), offsetof(StdRdOptions, vacuum_truncate_set)},
+ {"vacuum_truncate", RELOPT_TYPE_TERNARY,
+ offsetof(StdRdOptions, vacuum_truncate)},
{"vacuum_max_eager_freeze_failure_rate", RELOPT_TYPE_REAL,
offsetof(StdRdOptions, vacuum_max_eager_freeze_failure_rate)}
};
@@ -2004,7 +2090,6 @@ build_local_reloptions(local_relopts *relopts, Datum options, bool validate)
elems[i].optname = opt->option->name;
elems[i].opttype = opt->option->type;
elems[i].offset = opt->offset;
- elems[i].isset_offset = 0; /* not supported for local relopts yet */
i++;
}
diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c
index aa4fbec143f..696eab9bd97 100644
--- a/src/backend/commands/vacuum.c
+++ b/src/backend/commands/vacuum.c
@@ -2224,9 +2224,9 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams params,
{
StdRdOptions *opts = (StdRdOptions *) rel->rd_options;
- if (opts && opts->vacuum_truncate_set)
+ if (opts && opts->vacuum_truncate != TERNARY_UNSET)
{
- if (opts->vacuum_truncate)
+ if (opts->vacuum_truncate == TERNARY_TRUE)
params.truncate = VACOPTVALUE_ENABLED;
else
params.truncate = VACOPTVALUE_DISABLED;
diff --git a/src/include/access/reloptions.h b/src/include/access/reloptions.h
index 2f08e1b0cf0..dfbef2babf2 100644
--- a/src/include/access/reloptions.h
+++ b/src/include/access/reloptions.h
@@ -29,6 +29,7 @@
typedef enum relopt_type
{
RELOPT_TYPE_BOOL,
+ RELOPT_TYPE_TERNARY, /* on, off, unset */
RELOPT_TYPE_INT,
RELOPT_TYPE_REAL,
RELOPT_TYPE_ENUM,
@@ -80,6 +81,7 @@ typedef struct relopt_value
union
{
bool bool_val;
+ pg_ternary ternary_val;
int int_val;
double real_val;
int enum_val;
@@ -94,6 +96,12 @@ typedef struct relopt_bool
bool default_val;
} relopt_bool;
+typedef struct relopt_ternary
+{
+ relopt_gen gen;
+ int default_val;
+} relopt_ternary;
+
typedef struct relopt_int
{
relopt_gen gen;
@@ -152,19 +160,6 @@ typedef struct
const char *optname; /* option's name */
relopt_type opttype; /* option's datatype */
int offset; /* offset of field in result struct */
-
- /*
- * isset_offset is an optional offset of a field in the result struct that
- * stores whether the option is explicitly set for the relation or if it
- * just picked up the default value. In most cases, this can be
- * accomplished by giving the reloption a special out-of-range default
- * value (e.g., some integer reloptions use -2), but this isn't always
- * possible. For example, a Boolean reloption cannot be given an
- * out-of-range default, so we need another way to discover the source of
- * its value. This offset is only used if given a value greater than
- * zero.
- */
- int isset_offset;
} relopt_parse_elt;
/* Local reloption definition */
@@ -195,6 +190,9 @@ typedef struct local_relopts
extern relopt_kind add_reloption_kind(void);
extern void add_bool_reloption(bits32 kinds, const char *name, const char *desc,
bool default_val, LOCKMODE lockmode);
+extern void add_ternary_reloption(bits32 kinds, const char *name,
+ const char *desc, pg_ternary default_val,
+ LOCKMODE lockmode);
extern void add_int_reloption(bits32 kinds, const char *name, const char *desc,
int default_val, int min_val, int max_val,
LOCKMODE lockmode);
@@ -214,6 +212,9 @@ extern void register_reloptions_validator(local_relopts *relopts,
extern void add_local_bool_reloption(local_relopts *relopts, const char *name,
const char *desc, bool default_val,
int offset);
+extern void add_local_ternary_reloption(local_relopts *relopts,
+ const char *name, const char *desc,
+ pg_ternary default_val, int offset);
extern void add_local_int_reloption(local_relopts *relopts, const char *name,
const char *desc, int default_val,
int min_val, int max_val, int offset);
diff --git a/src/include/postgres.h b/src/include/postgres.h
index 7d93fbce709..47c88780776 100644
--- a/src/include/postgres.h
+++ b/src/include/postgres.h
@@ -543,6 +543,21 @@ Float8GetDatum(float8 X)
* ----------------------------------------------------------------
*/
+/*
+ * pg_ternary
+ * Boolean value with an extra "unset" value
+ *
+ * This enum can be used for values that want to distinguish between true,
+ * false, and unset.
+*/
+
+typedef enum pg_ternary
+{
+ TERNARY_FALSE = 0,
+ TERNARY_TRUE = 1,
+ TERNARY_UNSET = -1
+} pg_ternary;
+
/*
* NON_EXEC_STATIC: It's sometimes useful to define a variable or function
* that is normally static but extern when using EXEC_BACKEND (see
diff --git a/src/include/utils/rel.h b/src/include/utils/rel.h
index d03ab247788..5feb18a5373 100644
--- a/src/include/utils/rel.h
+++ b/src/include/utils/rel.h
@@ -347,8 +347,7 @@ typedef struct StdRdOptions
bool user_catalog_table; /* use as an additional catalog relation */
int parallel_workers; /* max number of parallel workers */
StdRdOptIndexCleanup vacuum_index_cleanup; /* controls index vacuuming */
- bool vacuum_truncate; /* enables vacuum to truncate a relation */
- bool vacuum_truncate_set; /* whether vacuum_truncate is set */
+ pg_ternary vacuum_truncate; /* enables vacuum to truncate a relation */
/*
* Fraction of pages in a relation that vacuum can eagerly scan and fail
diff --git a/src/test/regress/expected/reloptions.out b/src/test/regress/expected/reloptions.out
index 9de19b4e3f1..1c99f79ab01 100644
--- a/src/test/regress/expected/reloptions.out
+++ b/src/test/regress/expected/reloptions.out
@@ -98,6 +98,42 @@ SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass;
{fillfactor=13,autovacuum_enabled=false}
(1 row)
+-- Tests for future (FIXME) ternary options
+-- behave as boolean option: accept unassigned name and truncated value
+DROP TABLE reloptions_test;
+CREATE TABLE reloptions_test(i INT) WITH (vacuum_truncate);
+SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass;
+ reloptions
+------------------------
+ {vacuum_truncate=true}
+(1 row)
+
+DROP TABLE reloptions_test;
+CREATE TABLE reloptions_test(i INT) WITH (vacuum_truncate=FaLS);
+SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass;
+ reloptions
+------------------------
+ {vacuum_truncate=fals}
+(1 row)
+
+-- preferred "true" alias is used when storing
+DROP TABLE reloptions_test;
+CREATE TABLE reloptions_test(i INT) WITH (vacuum_index_cleanup=on);
+SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass;
+ reloptions
+---------------------------
+ {vacuum_index_cleanup=on}
+(1 row)
+
+-- custom "third" value is available
+DROP TABLE reloptions_test;
+CREATE TABLE reloptions_test(i INT) WITH (vacuum_index_cleanup=auto);
+SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass;
+ reloptions
+-----------------------------
+ {vacuum_index_cleanup=auto}
+(1 row)
+
-- Test vacuum_truncate option
DROP TABLE reloptions_test;
CREATE TEMP TABLE reloptions_test(i INT NOT NULL, j text)
diff --git a/src/test/regress/sql/reloptions.sql b/src/test/regress/sql/reloptions.sql
index 24fbe0b478d..f5980dafcbc 100644
--- a/src/test/regress/sql/reloptions.sql
+++ b/src/test/regress/sql/reloptions.sql
@@ -59,6 +59,27 @@ UPDATE pg_class
ALTER TABLE reloptions_test RESET (illegal_option);
SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass;
+-- Tests for future (FIXME) ternary options
+
+-- behave as boolean option: accept unassigned name and truncated value
+DROP TABLE reloptions_test;
+CREATE TABLE reloptions_test(i INT) WITH (vacuum_truncate);
+SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass;
+
+DROP TABLE reloptions_test;
+CREATE TABLE reloptions_test(i INT) WITH (vacuum_truncate=FaLS);
+SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass;
+
+-- preferred "true" alias is used when storing
+DROP TABLE reloptions_test;
+CREATE TABLE reloptions_test(i INT) WITH (vacuum_index_cleanup=on);
+SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass;
+
+-- custom "third" value is available
+DROP TABLE reloptions_test;
+CREATE TABLE reloptions_test(i INT) WITH (vacuum_index_cleanup=auto);
+SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass;
+
-- Test vacuum_truncate option
DROP TABLE reloptions_test;
diff --git a/src/tools/pgindent/typedefs.list b/src/tools/pgindent/typedefs.list
index 3f3a888fd0e..1c8610fd46c 100644
--- a/src/tools/pgindent/typedefs.list
+++ b/src/tools/pgindent/typedefs.list
@@ -3952,6 +3952,7 @@ pg_sha512_ctx
pg_snapshot
pg_special_case
pg_stack_base_t
+pg_ternary
pg_time_t
pg_time_usec_t
pg_tz
@@ -4079,6 +4080,7 @@ relopt_kind
relopt_parse_elt
relopt_real
relopt_string
+relopt_ternary
relopt_type
relopt_value
relopts_validator
--
2.47.3
--c36t3fndxhkzbl5z--
^ permalink raw reply [nested|flat] 868+ messages in thread
* [PATCH v3] Introduce ternary reloptions
@ 2026-01-16 15:04 Álvaro Herrera <[email protected]>
0 siblings, 0 replies; 868+ messages in thread
From: Álvaro Herrera @ 2026-01-16 15:04 UTC (permalink / raw)
Introduce ternary reloption as a replacement for current
`vacuum_truncate` implementation. Remove the `vacuum_truncate_set`
separate flag and use TERNARY_UNSET instead.
This could also be used for other options such as `vacuum_index_cleanup`
and `buffering`, but lets get the scaffolding in first.
Discussion: https://postgr.es/m/3474141.usfYGdeWWP@thinkpad-pgpro
---
src/backend/access/common/reloptions.c | 137 ++++++++++++++++++-----
src/backend/commands/vacuum.c | 4 +-
src/include/access/reloptions.h | 27 ++---
src/include/postgres.h | 15 +++
src/include/utils/rel.h | 3 +-
src/test/regress/expected/reloptions.out | 36 ++++++
src/test/regress/sql/reloptions.sql | 21 ++++
src/tools/pgindent/typedefs.list | 2 +
8 files changed, 202 insertions(+), 43 deletions(-)
diff --git a/src/backend/access/common/reloptions.c b/src/backend/access/common/reloptions.c
index 0b83f98ed5f..6f1f577581a 100644
--- a/src/backend/access/common/reloptions.c
+++ b/src/backend/access/common/reloptions.c
@@ -40,9 +40,9 @@
*
* To add an option:
*
- * (i) decide on a type (bool, integer, real, enum, string), name, default
- * value, upper and lower bounds (if applicable); for strings, consider a
- * validation routine.
+ * (i) decide on a type (bool, ternary, integer, real, enum, string), name,
+ * default value, upper and lower bounds (if applicable); for strings,
+ * consider a validation routine.
* (ii) add a record below (or use add_<type>_reloption).
* (iii) add it to the appropriate options struct (perhaps StdRdOptions)
* (iv) add it to the appropriate handling routine (perhaps
@@ -147,15 +147,6 @@ static relopt_bool boolRelOpts[] =
},
false
},
- {
- {
- "vacuum_truncate",
- "Enables vacuum to truncate empty pages at the end of this table",
- RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
- ShareUpdateExclusiveLock
- },
- true
- },
{
{
"deduplicate_items",
@@ -170,6 +161,25 @@ static relopt_bool boolRelOpts[] =
{{NULL}}
};
+static relopt_ternary ternaryRelOpts[] =
+{
+ {
+ {
+ "vacuum_truncate",
+ "Enables vacuum to truncate empty pages at the end of this table",
+ RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
+ ShareUpdateExclusiveLock
+ },
+ TERNARY_UNSET
+ },
+ /* list terminator */
+ {
+ {
+ NULL
+ }
+ }
+};
+
static relopt_int intRelOpts[] =
{
{
@@ -609,6 +619,13 @@ initialize_reloptions(void)
boolRelOpts[i].gen.lockmode));
j++;
}
+ for (i = 0; ternaryRelOpts[i].gen.name; i++)
+ {
+ Assert(DoLockModesConflict(ternaryRelOpts[i].gen.lockmode,
+ ternaryRelOpts[i].gen.lockmode));
+ j++;
+ }
+
for (i = 0; intRelOpts[i].gen.name; i++)
{
Assert(DoLockModesConflict(intRelOpts[i].gen.lockmode,
@@ -649,6 +666,14 @@ initialize_reloptions(void)
j++;
}
+ for (i = 0; ternaryRelOpts[i].gen.name; i++)
+ {
+ relOpts[j] = &ternaryRelOpts[i].gen;
+ relOpts[j]->type = RELOPT_TYPE_TERNARY;
+ relOpts[j]->namelen = strlen(relOpts[j]->name);
+ j++;
+ }
+
for (i = 0; intRelOpts[i].gen.name; i++)
{
relOpts[j] = &intRelOpts[i].gen;
@@ -809,6 +834,9 @@ allocate_reloption(bits32 kinds, int type, const char *name, const char *desc,
case RELOPT_TYPE_BOOL:
size = sizeof(relopt_bool);
break;
+ case RELOPT_TYPE_TERNARY:
+ size = sizeof(relopt_ternary);
+ break;
case RELOPT_TYPE_INT:
size = sizeof(relopt_int);
break;
@@ -892,6 +920,57 @@ add_local_bool_reloption(local_relopts *relopts, const char *name,
add_local_reloption(relopts, (relopt_gen *) newoption, offset);
}
+/*
+ * init_ternary_reloption
+ * Allocate and initialize a new ternary reloption
+ */
+static relopt_ternary *
+init_ternary_reloption(bits32 kinds, const char *name, const char *desc,
+ pg_ternary default_val, LOCKMODE lockmode)
+{
+ relopt_ternary *newoption;
+
+ newoption = (relopt_ternary *)
+ allocate_reloption(kinds, RELOPT_TYPE_TERNARY, name, desc, lockmode);
+ newoption->default_val = default_val;
+
+ return newoption;
+}
+
+/*
+ * add_ternary_reloption
+ * Add a new ternary reloption
+ */
+void
+add_ternary_reloption(bits32 kinds, const char *name, const char *desc,
+ pg_ternary default_val, LOCKMODE lockmode)
+{
+ relopt_ternary *newoption;
+
+ newoption =
+ init_ternary_reloption(kinds, name, desc, default_val, lockmode);
+
+ add_reloption((relopt_gen *) newoption);
+}
+
+/*
+ * add_local_ternary_reloption
+ * Add a new ternary local reloption
+ *
+ * 'offset' is offset of ternary-typed field.
+ */
+void
+add_local_ternary_reloption(local_relopts *relopts, const char *name,
+ const char *desc, pg_ternary default_val,
+ int offset)
+{
+ relopt_ternary *newoption;
+
+ newoption =
+ init_ternary_reloption(RELOPT_KIND_LOCAL, name, desc, default_val, 0);
+
+ add_local_reloption(relopts, (relopt_gen *) newoption, offset);
+}
/*
* init_real_reloption
@@ -1626,6 +1705,19 @@ parse_one_reloption(relopt_value *option, char *text_str, int text_len,
option->gen->name, value)));
}
break;
+ case RELOPT_TYPE_TERNARY:
+ {
+ bool b;
+
+ parsed = parse_bool(value, &b);
+ option->values.ternary_val = b ? TERNARY_TRUE : TERNARY_FALSE;
+ if (validate && !parsed)
+ ereport(ERROR,
+ (errcode(ERRCODE_INVALID_PARAMETER_VALUE),
+ errmsg("invalid value for ternary option \"%s\": %s",
+ option->gen->name, value)));
+ }
+ break;
case RELOPT_TYPE_INT:
{
relopt_int *optint = (relopt_int *) option->gen;
@@ -1789,17 +1881,6 @@ fillRelOptions(void *rdopts, Size basesize,
char *itempos = ((char *) rdopts) + elems[j].offset;
char *string_val;
- /*
- * If isset_offset is provided, store whether the reloption is
- * set there.
- */
- if (elems[j].isset_offset > 0)
- {
- char *setpos = ((char *) rdopts) + elems[j].isset_offset;
-
- *(bool *) setpos = options[i].isset;
- }
-
switch (options[i].gen->type)
{
case RELOPT_TYPE_BOOL:
@@ -1807,6 +1888,11 @@ fillRelOptions(void *rdopts, Size basesize,
options[i].values.bool_val :
((relopt_bool *) options[i].gen)->default_val;
break;
+ case RELOPT_TYPE_TERNARY:
+ *(pg_ternary *) itempos = options[i].isset ?
+ options[i].values.ternary_val :
+ ((relopt_ternary *) options[i].gen)->default_val;
+ break;
case RELOPT_TYPE_INT:
*(int *) itempos = options[i].isset ?
options[i].values.int_val :
@@ -1923,8 +2009,8 @@ default_reloptions(Datum reloptions, bool validate, relopt_kind kind)
offsetof(StdRdOptions, parallel_workers)},
{"vacuum_index_cleanup", RELOPT_TYPE_ENUM,
offsetof(StdRdOptions, vacuum_index_cleanup)},
- {"vacuum_truncate", RELOPT_TYPE_BOOL,
- offsetof(StdRdOptions, vacuum_truncate), offsetof(StdRdOptions, vacuum_truncate_set)},
+ {"vacuum_truncate", RELOPT_TYPE_TERNARY,
+ offsetof(StdRdOptions, vacuum_truncate)},
{"vacuum_max_eager_freeze_failure_rate", RELOPT_TYPE_REAL,
offsetof(StdRdOptions, vacuum_max_eager_freeze_failure_rate)}
};
@@ -2004,7 +2090,6 @@ build_local_reloptions(local_relopts *relopts, Datum options, bool validate)
elems[i].optname = opt->option->name;
elems[i].opttype = opt->option->type;
elems[i].offset = opt->offset;
- elems[i].isset_offset = 0; /* not supported for local relopts yet */
i++;
}
diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c
index aa4fbec143f..696eab9bd97 100644
--- a/src/backend/commands/vacuum.c
+++ b/src/backend/commands/vacuum.c
@@ -2224,9 +2224,9 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams params,
{
StdRdOptions *opts = (StdRdOptions *) rel->rd_options;
- if (opts && opts->vacuum_truncate_set)
+ if (opts && opts->vacuum_truncate != TERNARY_UNSET)
{
- if (opts->vacuum_truncate)
+ if (opts->vacuum_truncate == TERNARY_TRUE)
params.truncate = VACOPTVALUE_ENABLED;
else
params.truncate = VACOPTVALUE_DISABLED;
diff --git a/src/include/access/reloptions.h b/src/include/access/reloptions.h
index 2f08e1b0cf0..dfbef2babf2 100644
--- a/src/include/access/reloptions.h
+++ b/src/include/access/reloptions.h
@@ -29,6 +29,7 @@
typedef enum relopt_type
{
RELOPT_TYPE_BOOL,
+ RELOPT_TYPE_TERNARY, /* on, off, unset */
RELOPT_TYPE_INT,
RELOPT_TYPE_REAL,
RELOPT_TYPE_ENUM,
@@ -80,6 +81,7 @@ typedef struct relopt_value
union
{
bool bool_val;
+ pg_ternary ternary_val;
int int_val;
double real_val;
int enum_val;
@@ -94,6 +96,12 @@ typedef struct relopt_bool
bool default_val;
} relopt_bool;
+typedef struct relopt_ternary
+{
+ relopt_gen gen;
+ int default_val;
+} relopt_ternary;
+
typedef struct relopt_int
{
relopt_gen gen;
@@ -152,19 +160,6 @@ typedef struct
const char *optname; /* option's name */
relopt_type opttype; /* option's datatype */
int offset; /* offset of field in result struct */
-
- /*
- * isset_offset is an optional offset of a field in the result struct that
- * stores whether the option is explicitly set for the relation or if it
- * just picked up the default value. In most cases, this can be
- * accomplished by giving the reloption a special out-of-range default
- * value (e.g., some integer reloptions use -2), but this isn't always
- * possible. For example, a Boolean reloption cannot be given an
- * out-of-range default, so we need another way to discover the source of
- * its value. This offset is only used if given a value greater than
- * zero.
- */
- int isset_offset;
} relopt_parse_elt;
/* Local reloption definition */
@@ -195,6 +190,9 @@ typedef struct local_relopts
extern relopt_kind add_reloption_kind(void);
extern void add_bool_reloption(bits32 kinds, const char *name, const char *desc,
bool default_val, LOCKMODE lockmode);
+extern void add_ternary_reloption(bits32 kinds, const char *name,
+ const char *desc, pg_ternary default_val,
+ LOCKMODE lockmode);
extern void add_int_reloption(bits32 kinds, const char *name, const char *desc,
int default_val, int min_val, int max_val,
LOCKMODE lockmode);
@@ -214,6 +212,9 @@ extern void register_reloptions_validator(local_relopts *relopts,
extern void add_local_bool_reloption(local_relopts *relopts, const char *name,
const char *desc, bool default_val,
int offset);
+extern void add_local_ternary_reloption(local_relopts *relopts,
+ const char *name, const char *desc,
+ pg_ternary default_val, int offset);
extern void add_local_int_reloption(local_relopts *relopts, const char *name,
const char *desc, int default_val,
int min_val, int max_val, int offset);
diff --git a/src/include/postgres.h b/src/include/postgres.h
index 7d93fbce709..47c88780776 100644
--- a/src/include/postgres.h
+++ b/src/include/postgres.h
@@ -543,6 +543,21 @@ Float8GetDatum(float8 X)
* ----------------------------------------------------------------
*/
+/*
+ * pg_ternary
+ * Boolean value with an extra "unset" value
+ *
+ * This enum can be used for values that want to distinguish between true,
+ * false, and unset.
+*/
+
+typedef enum pg_ternary
+{
+ TERNARY_FALSE = 0,
+ TERNARY_TRUE = 1,
+ TERNARY_UNSET = -1
+} pg_ternary;
+
/*
* NON_EXEC_STATIC: It's sometimes useful to define a variable or function
* that is normally static but extern when using EXEC_BACKEND (see
diff --git a/src/include/utils/rel.h b/src/include/utils/rel.h
index d03ab247788..5feb18a5373 100644
--- a/src/include/utils/rel.h
+++ b/src/include/utils/rel.h
@@ -347,8 +347,7 @@ typedef struct StdRdOptions
bool user_catalog_table; /* use as an additional catalog relation */
int parallel_workers; /* max number of parallel workers */
StdRdOptIndexCleanup vacuum_index_cleanup; /* controls index vacuuming */
- bool vacuum_truncate; /* enables vacuum to truncate a relation */
- bool vacuum_truncate_set; /* whether vacuum_truncate is set */
+ pg_ternary vacuum_truncate; /* enables vacuum to truncate a relation */
/*
* Fraction of pages in a relation that vacuum can eagerly scan and fail
diff --git a/src/test/regress/expected/reloptions.out b/src/test/regress/expected/reloptions.out
index 9de19b4e3f1..1c99f79ab01 100644
--- a/src/test/regress/expected/reloptions.out
+++ b/src/test/regress/expected/reloptions.out
@@ -98,6 +98,42 @@ SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass;
{fillfactor=13,autovacuum_enabled=false}
(1 row)
+-- Tests for future (FIXME) ternary options
+-- behave as boolean option: accept unassigned name and truncated value
+DROP TABLE reloptions_test;
+CREATE TABLE reloptions_test(i INT) WITH (vacuum_truncate);
+SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass;
+ reloptions
+------------------------
+ {vacuum_truncate=true}
+(1 row)
+
+DROP TABLE reloptions_test;
+CREATE TABLE reloptions_test(i INT) WITH (vacuum_truncate=FaLS);
+SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass;
+ reloptions
+------------------------
+ {vacuum_truncate=fals}
+(1 row)
+
+-- preferred "true" alias is used when storing
+DROP TABLE reloptions_test;
+CREATE TABLE reloptions_test(i INT) WITH (vacuum_index_cleanup=on);
+SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass;
+ reloptions
+---------------------------
+ {vacuum_index_cleanup=on}
+(1 row)
+
+-- custom "third" value is available
+DROP TABLE reloptions_test;
+CREATE TABLE reloptions_test(i INT) WITH (vacuum_index_cleanup=auto);
+SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass;
+ reloptions
+-----------------------------
+ {vacuum_index_cleanup=auto}
+(1 row)
+
-- Test vacuum_truncate option
DROP TABLE reloptions_test;
CREATE TEMP TABLE reloptions_test(i INT NOT NULL, j text)
diff --git a/src/test/regress/sql/reloptions.sql b/src/test/regress/sql/reloptions.sql
index 24fbe0b478d..f5980dafcbc 100644
--- a/src/test/regress/sql/reloptions.sql
+++ b/src/test/regress/sql/reloptions.sql
@@ -59,6 +59,27 @@ UPDATE pg_class
ALTER TABLE reloptions_test RESET (illegal_option);
SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass;
+-- Tests for future (FIXME) ternary options
+
+-- behave as boolean option: accept unassigned name and truncated value
+DROP TABLE reloptions_test;
+CREATE TABLE reloptions_test(i INT) WITH (vacuum_truncate);
+SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass;
+
+DROP TABLE reloptions_test;
+CREATE TABLE reloptions_test(i INT) WITH (vacuum_truncate=FaLS);
+SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass;
+
+-- preferred "true" alias is used when storing
+DROP TABLE reloptions_test;
+CREATE TABLE reloptions_test(i INT) WITH (vacuum_index_cleanup=on);
+SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass;
+
+-- custom "third" value is available
+DROP TABLE reloptions_test;
+CREATE TABLE reloptions_test(i INT) WITH (vacuum_index_cleanup=auto);
+SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass;
+
-- Test vacuum_truncate option
DROP TABLE reloptions_test;
diff --git a/src/tools/pgindent/typedefs.list b/src/tools/pgindent/typedefs.list
index 3f3a888fd0e..1c8610fd46c 100644
--- a/src/tools/pgindent/typedefs.list
+++ b/src/tools/pgindent/typedefs.list
@@ -3952,6 +3952,7 @@ pg_sha512_ctx
pg_snapshot
pg_special_case
pg_stack_base_t
+pg_ternary
pg_time_t
pg_time_usec_t
pg_tz
@@ -4079,6 +4080,7 @@ relopt_kind
relopt_parse_elt
relopt_real
relopt_string
+relopt_ternary
relopt_type
relopt_value
relopts_validator
--
2.47.3
--c36t3fndxhkzbl5z--
^ permalink raw reply [nested|flat] 868+ messages in thread
* [PATCH v3] Introduce ternary reloptions
@ 2026-01-16 15:04 Álvaro Herrera <[email protected]>
0 siblings, 0 replies; 868+ messages in thread
From: Álvaro Herrera @ 2026-01-16 15:04 UTC (permalink / raw)
Introduce ternary reloption as a replacement for current
`vacuum_truncate` implementation. Remove the `vacuum_truncate_set`
separate flag and use TERNARY_UNSET instead.
This could also be used for other options such as `vacuum_index_cleanup`
and `buffering`, but lets get the scaffolding in first.
Discussion: https://postgr.es/m/3474141.usfYGdeWWP@thinkpad-pgpro
---
src/backend/access/common/reloptions.c | 137 ++++++++++++++++++-----
src/backend/commands/vacuum.c | 4 +-
src/include/access/reloptions.h | 27 ++---
src/include/postgres.h | 15 +++
src/include/utils/rel.h | 3 +-
src/test/regress/expected/reloptions.out | 36 ++++++
src/test/regress/sql/reloptions.sql | 21 ++++
src/tools/pgindent/typedefs.list | 2 +
8 files changed, 202 insertions(+), 43 deletions(-)
diff --git a/src/backend/access/common/reloptions.c b/src/backend/access/common/reloptions.c
index 0b83f98ed5f..6f1f577581a 100644
--- a/src/backend/access/common/reloptions.c
+++ b/src/backend/access/common/reloptions.c
@@ -40,9 +40,9 @@
*
* To add an option:
*
- * (i) decide on a type (bool, integer, real, enum, string), name, default
- * value, upper and lower bounds (if applicable); for strings, consider a
- * validation routine.
+ * (i) decide on a type (bool, ternary, integer, real, enum, string), name,
+ * default value, upper and lower bounds (if applicable); for strings,
+ * consider a validation routine.
* (ii) add a record below (or use add_<type>_reloption).
* (iii) add it to the appropriate options struct (perhaps StdRdOptions)
* (iv) add it to the appropriate handling routine (perhaps
@@ -147,15 +147,6 @@ static relopt_bool boolRelOpts[] =
},
false
},
- {
- {
- "vacuum_truncate",
- "Enables vacuum to truncate empty pages at the end of this table",
- RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
- ShareUpdateExclusiveLock
- },
- true
- },
{
{
"deduplicate_items",
@@ -170,6 +161,25 @@ static relopt_bool boolRelOpts[] =
{{NULL}}
};
+static relopt_ternary ternaryRelOpts[] =
+{
+ {
+ {
+ "vacuum_truncate",
+ "Enables vacuum to truncate empty pages at the end of this table",
+ RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
+ ShareUpdateExclusiveLock
+ },
+ TERNARY_UNSET
+ },
+ /* list terminator */
+ {
+ {
+ NULL
+ }
+ }
+};
+
static relopt_int intRelOpts[] =
{
{
@@ -609,6 +619,13 @@ initialize_reloptions(void)
boolRelOpts[i].gen.lockmode));
j++;
}
+ for (i = 0; ternaryRelOpts[i].gen.name; i++)
+ {
+ Assert(DoLockModesConflict(ternaryRelOpts[i].gen.lockmode,
+ ternaryRelOpts[i].gen.lockmode));
+ j++;
+ }
+
for (i = 0; intRelOpts[i].gen.name; i++)
{
Assert(DoLockModesConflict(intRelOpts[i].gen.lockmode,
@@ -649,6 +666,14 @@ initialize_reloptions(void)
j++;
}
+ for (i = 0; ternaryRelOpts[i].gen.name; i++)
+ {
+ relOpts[j] = &ternaryRelOpts[i].gen;
+ relOpts[j]->type = RELOPT_TYPE_TERNARY;
+ relOpts[j]->namelen = strlen(relOpts[j]->name);
+ j++;
+ }
+
for (i = 0; intRelOpts[i].gen.name; i++)
{
relOpts[j] = &intRelOpts[i].gen;
@@ -809,6 +834,9 @@ allocate_reloption(bits32 kinds, int type, const char *name, const char *desc,
case RELOPT_TYPE_BOOL:
size = sizeof(relopt_bool);
break;
+ case RELOPT_TYPE_TERNARY:
+ size = sizeof(relopt_ternary);
+ break;
case RELOPT_TYPE_INT:
size = sizeof(relopt_int);
break;
@@ -892,6 +920,57 @@ add_local_bool_reloption(local_relopts *relopts, const char *name,
add_local_reloption(relopts, (relopt_gen *) newoption, offset);
}
+/*
+ * init_ternary_reloption
+ * Allocate and initialize a new ternary reloption
+ */
+static relopt_ternary *
+init_ternary_reloption(bits32 kinds, const char *name, const char *desc,
+ pg_ternary default_val, LOCKMODE lockmode)
+{
+ relopt_ternary *newoption;
+
+ newoption = (relopt_ternary *)
+ allocate_reloption(kinds, RELOPT_TYPE_TERNARY, name, desc, lockmode);
+ newoption->default_val = default_val;
+
+ return newoption;
+}
+
+/*
+ * add_ternary_reloption
+ * Add a new ternary reloption
+ */
+void
+add_ternary_reloption(bits32 kinds, const char *name, const char *desc,
+ pg_ternary default_val, LOCKMODE lockmode)
+{
+ relopt_ternary *newoption;
+
+ newoption =
+ init_ternary_reloption(kinds, name, desc, default_val, lockmode);
+
+ add_reloption((relopt_gen *) newoption);
+}
+
+/*
+ * add_local_ternary_reloption
+ * Add a new ternary local reloption
+ *
+ * 'offset' is offset of ternary-typed field.
+ */
+void
+add_local_ternary_reloption(local_relopts *relopts, const char *name,
+ const char *desc, pg_ternary default_val,
+ int offset)
+{
+ relopt_ternary *newoption;
+
+ newoption =
+ init_ternary_reloption(RELOPT_KIND_LOCAL, name, desc, default_val, 0);
+
+ add_local_reloption(relopts, (relopt_gen *) newoption, offset);
+}
/*
* init_real_reloption
@@ -1626,6 +1705,19 @@ parse_one_reloption(relopt_value *option, char *text_str, int text_len,
option->gen->name, value)));
}
break;
+ case RELOPT_TYPE_TERNARY:
+ {
+ bool b;
+
+ parsed = parse_bool(value, &b);
+ option->values.ternary_val = b ? TERNARY_TRUE : TERNARY_FALSE;
+ if (validate && !parsed)
+ ereport(ERROR,
+ (errcode(ERRCODE_INVALID_PARAMETER_VALUE),
+ errmsg("invalid value for ternary option \"%s\": %s",
+ option->gen->name, value)));
+ }
+ break;
case RELOPT_TYPE_INT:
{
relopt_int *optint = (relopt_int *) option->gen;
@@ -1789,17 +1881,6 @@ fillRelOptions(void *rdopts, Size basesize,
char *itempos = ((char *) rdopts) + elems[j].offset;
char *string_val;
- /*
- * If isset_offset is provided, store whether the reloption is
- * set there.
- */
- if (elems[j].isset_offset > 0)
- {
- char *setpos = ((char *) rdopts) + elems[j].isset_offset;
-
- *(bool *) setpos = options[i].isset;
- }
-
switch (options[i].gen->type)
{
case RELOPT_TYPE_BOOL:
@@ -1807,6 +1888,11 @@ fillRelOptions(void *rdopts, Size basesize,
options[i].values.bool_val :
((relopt_bool *) options[i].gen)->default_val;
break;
+ case RELOPT_TYPE_TERNARY:
+ *(pg_ternary *) itempos = options[i].isset ?
+ options[i].values.ternary_val :
+ ((relopt_ternary *) options[i].gen)->default_val;
+ break;
case RELOPT_TYPE_INT:
*(int *) itempos = options[i].isset ?
options[i].values.int_val :
@@ -1923,8 +2009,8 @@ default_reloptions(Datum reloptions, bool validate, relopt_kind kind)
offsetof(StdRdOptions, parallel_workers)},
{"vacuum_index_cleanup", RELOPT_TYPE_ENUM,
offsetof(StdRdOptions, vacuum_index_cleanup)},
- {"vacuum_truncate", RELOPT_TYPE_BOOL,
- offsetof(StdRdOptions, vacuum_truncate), offsetof(StdRdOptions, vacuum_truncate_set)},
+ {"vacuum_truncate", RELOPT_TYPE_TERNARY,
+ offsetof(StdRdOptions, vacuum_truncate)},
{"vacuum_max_eager_freeze_failure_rate", RELOPT_TYPE_REAL,
offsetof(StdRdOptions, vacuum_max_eager_freeze_failure_rate)}
};
@@ -2004,7 +2090,6 @@ build_local_reloptions(local_relopts *relopts, Datum options, bool validate)
elems[i].optname = opt->option->name;
elems[i].opttype = opt->option->type;
elems[i].offset = opt->offset;
- elems[i].isset_offset = 0; /* not supported for local relopts yet */
i++;
}
diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c
index aa4fbec143f..696eab9bd97 100644
--- a/src/backend/commands/vacuum.c
+++ b/src/backend/commands/vacuum.c
@@ -2224,9 +2224,9 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams params,
{
StdRdOptions *opts = (StdRdOptions *) rel->rd_options;
- if (opts && opts->vacuum_truncate_set)
+ if (opts && opts->vacuum_truncate != TERNARY_UNSET)
{
- if (opts->vacuum_truncate)
+ if (opts->vacuum_truncate == TERNARY_TRUE)
params.truncate = VACOPTVALUE_ENABLED;
else
params.truncate = VACOPTVALUE_DISABLED;
diff --git a/src/include/access/reloptions.h b/src/include/access/reloptions.h
index 2f08e1b0cf0..dfbef2babf2 100644
--- a/src/include/access/reloptions.h
+++ b/src/include/access/reloptions.h
@@ -29,6 +29,7 @@
typedef enum relopt_type
{
RELOPT_TYPE_BOOL,
+ RELOPT_TYPE_TERNARY, /* on, off, unset */
RELOPT_TYPE_INT,
RELOPT_TYPE_REAL,
RELOPT_TYPE_ENUM,
@@ -80,6 +81,7 @@ typedef struct relopt_value
union
{
bool bool_val;
+ pg_ternary ternary_val;
int int_val;
double real_val;
int enum_val;
@@ -94,6 +96,12 @@ typedef struct relopt_bool
bool default_val;
} relopt_bool;
+typedef struct relopt_ternary
+{
+ relopt_gen gen;
+ int default_val;
+} relopt_ternary;
+
typedef struct relopt_int
{
relopt_gen gen;
@@ -152,19 +160,6 @@ typedef struct
const char *optname; /* option's name */
relopt_type opttype; /* option's datatype */
int offset; /* offset of field in result struct */
-
- /*
- * isset_offset is an optional offset of a field in the result struct that
- * stores whether the option is explicitly set for the relation or if it
- * just picked up the default value. In most cases, this can be
- * accomplished by giving the reloption a special out-of-range default
- * value (e.g., some integer reloptions use -2), but this isn't always
- * possible. For example, a Boolean reloption cannot be given an
- * out-of-range default, so we need another way to discover the source of
- * its value. This offset is only used if given a value greater than
- * zero.
- */
- int isset_offset;
} relopt_parse_elt;
/* Local reloption definition */
@@ -195,6 +190,9 @@ typedef struct local_relopts
extern relopt_kind add_reloption_kind(void);
extern void add_bool_reloption(bits32 kinds, const char *name, const char *desc,
bool default_val, LOCKMODE lockmode);
+extern void add_ternary_reloption(bits32 kinds, const char *name,
+ const char *desc, pg_ternary default_val,
+ LOCKMODE lockmode);
extern void add_int_reloption(bits32 kinds, const char *name, const char *desc,
int default_val, int min_val, int max_val,
LOCKMODE lockmode);
@@ -214,6 +212,9 @@ extern void register_reloptions_validator(local_relopts *relopts,
extern void add_local_bool_reloption(local_relopts *relopts, const char *name,
const char *desc, bool default_val,
int offset);
+extern void add_local_ternary_reloption(local_relopts *relopts,
+ const char *name, const char *desc,
+ pg_ternary default_val, int offset);
extern void add_local_int_reloption(local_relopts *relopts, const char *name,
const char *desc, int default_val,
int min_val, int max_val, int offset);
diff --git a/src/include/postgres.h b/src/include/postgres.h
index 7d93fbce709..47c88780776 100644
--- a/src/include/postgres.h
+++ b/src/include/postgres.h
@@ -543,6 +543,21 @@ Float8GetDatum(float8 X)
* ----------------------------------------------------------------
*/
+/*
+ * pg_ternary
+ * Boolean value with an extra "unset" value
+ *
+ * This enum can be used for values that want to distinguish between true,
+ * false, and unset.
+*/
+
+typedef enum pg_ternary
+{
+ TERNARY_FALSE = 0,
+ TERNARY_TRUE = 1,
+ TERNARY_UNSET = -1
+} pg_ternary;
+
/*
* NON_EXEC_STATIC: It's sometimes useful to define a variable or function
* that is normally static but extern when using EXEC_BACKEND (see
diff --git a/src/include/utils/rel.h b/src/include/utils/rel.h
index d03ab247788..5feb18a5373 100644
--- a/src/include/utils/rel.h
+++ b/src/include/utils/rel.h
@@ -347,8 +347,7 @@ typedef struct StdRdOptions
bool user_catalog_table; /* use as an additional catalog relation */
int parallel_workers; /* max number of parallel workers */
StdRdOptIndexCleanup vacuum_index_cleanup; /* controls index vacuuming */
- bool vacuum_truncate; /* enables vacuum to truncate a relation */
- bool vacuum_truncate_set; /* whether vacuum_truncate is set */
+ pg_ternary vacuum_truncate; /* enables vacuum to truncate a relation */
/*
* Fraction of pages in a relation that vacuum can eagerly scan and fail
diff --git a/src/test/regress/expected/reloptions.out b/src/test/regress/expected/reloptions.out
index 9de19b4e3f1..1c99f79ab01 100644
--- a/src/test/regress/expected/reloptions.out
+++ b/src/test/regress/expected/reloptions.out
@@ -98,6 +98,42 @@ SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass;
{fillfactor=13,autovacuum_enabled=false}
(1 row)
+-- Tests for future (FIXME) ternary options
+-- behave as boolean option: accept unassigned name and truncated value
+DROP TABLE reloptions_test;
+CREATE TABLE reloptions_test(i INT) WITH (vacuum_truncate);
+SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass;
+ reloptions
+------------------------
+ {vacuum_truncate=true}
+(1 row)
+
+DROP TABLE reloptions_test;
+CREATE TABLE reloptions_test(i INT) WITH (vacuum_truncate=FaLS);
+SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass;
+ reloptions
+------------------------
+ {vacuum_truncate=fals}
+(1 row)
+
+-- preferred "true" alias is used when storing
+DROP TABLE reloptions_test;
+CREATE TABLE reloptions_test(i INT) WITH (vacuum_index_cleanup=on);
+SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass;
+ reloptions
+---------------------------
+ {vacuum_index_cleanup=on}
+(1 row)
+
+-- custom "third" value is available
+DROP TABLE reloptions_test;
+CREATE TABLE reloptions_test(i INT) WITH (vacuum_index_cleanup=auto);
+SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass;
+ reloptions
+-----------------------------
+ {vacuum_index_cleanup=auto}
+(1 row)
+
-- Test vacuum_truncate option
DROP TABLE reloptions_test;
CREATE TEMP TABLE reloptions_test(i INT NOT NULL, j text)
diff --git a/src/test/regress/sql/reloptions.sql b/src/test/regress/sql/reloptions.sql
index 24fbe0b478d..f5980dafcbc 100644
--- a/src/test/regress/sql/reloptions.sql
+++ b/src/test/regress/sql/reloptions.sql
@@ -59,6 +59,27 @@ UPDATE pg_class
ALTER TABLE reloptions_test RESET (illegal_option);
SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass;
+-- Tests for future (FIXME) ternary options
+
+-- behave as boolean option: accept unassigned name and truncated value
+DROP TABLE reloptions_test;
+CREATE TABLE reloptions_test(i INT) WITH (vacuum_truncate);
+SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass;
+
+DROP TABLE reloptions_test;
+CREATE TABLE reloptions_test(i INT) WITH (vacuum_truncate=FaLS);
+SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass;
+
+-- preferred "true" alias is used when storing
+DROP TABLE reloptions_test;
+CREATE TABLE reloptions_test(i INT) WITH (vacuum_index_cleanup=on);
+SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass;
+
+-- custom "third" value is available
+DROP TABLE reloptions_test;
+CREATE TABLE reloptions_test(i INT) WITH (vacuum_index_cleanup=auto);
+SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass;
+
-- Test vacuum_truncate option
DROP TABLE reloptions_test;
diff --git a/src/tools/pgindent/typedefs.list b/src/tools/pgindent/typedefs.list
index 3f3a888fd0e..1c8610fd46c 100644
--- a/src/tools/pgindent/typedefs.list
+++ b/src/tools/pgindent/typedefs.list
@@ -3952,6 +3952,7 @@ pg_sha512_ctx
pg_snapshot
pg_special_case
pg_stack_base_t
+pg_ternary
pg_time_t
pg_time_usec_t
pg_tz
@@ -4079,6 +4080,7 @@ relopt_kind
relopt_parse_elt
relopt_real
relopt_string
+relopt_ternary
relopt_type
relopt_value
relopts_validator
--
2.47.3
--c36t3fndxhkzbl5z--
^ permalink raw reply [nested|flat] 868+ messages in thread
* [PATCH v3] Introduce ternary reloptions
@ 2026-01-16 15:04 Álvaro Herrera <[email protected]>
0 siblings, 0 replies; 868+ messages in thread
From: Álvaro Herrera @ 2026-01-16 15:04 UTC (permalink / raw)
Introduce ternary reloption as a replacement for current
`vacuum_truncate` implementation. Remove the `vacuum_truncate_set`
separate flag and use TERNARY_UNSET instead.
This could also be used for other options such as `vacuum_index_cleanup`
and `buffering`, but lets get the scaffolding in first.
Discussion: https://postgr.es/m/3474141.usfYGdeWWP@thinkpad-pgpro
---
src/backend/access/common/reloptions.c | 137 ++++++++++++++++++-----
src/backend/commands/vacuum.c | 4 +-
src/include/access/reloptions.h | 27 ++---
src/include/postgres.h | 15 +++
src/include/utils/rel.h | 3 +-
src/test/regress/expected/reloptions.out | 36 ++++++
src/test/regress/sql/reloptions.sql | 21 ++++
src/tools/pgindent/typedefs.list | 2 +
8 files changed, 202 insertions(+), 43 deletions(-)
diff --git a/src/backend/access/common/reloptions.c b/src/backend/access/common/reloptions.c
index 0b83f98ed5f..6f1f577581a 100644
--- a/src/backend/access/common/reloptions.c
+++ b/src/backend/access/common/reloptions.c
@@ -40,9 +40,9 @@
*
* To add an option:
*
- * (i) decide on a type (bool, integer, real, enum, string), name, default
- * value, upper and lower bounds (if applicable); for strings, consider a
- * validation routine.
+ * (i) decide on a type (bool, ternary, integer, real, enum, string), name,
+ * default value, upper and lower bounds (if applicable); for strings,
+ * consider a validation routine.
* (ii) add a record below (or use add_<type>_reloption).
* (iii) add it to the appropriate options struct (perhaps StdRdOptions)
* (iv) add it to the appropriate handling routine (perhaps
@@ -147,15 +147,6 @@ static relopt_bool boolRelOpts[] =
},
false
},
- {
- {
- "vacuum_truncate",
- "Enables vacuum to truncate empty pages at the end of this table",
- RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
- ShareUpdateExclusiveLock
- },
- true
- },
{
{
"deduplicate_items",
@@ -170,6 +161,25 @@ static relopt_bool boolRelOpts[] =
{{NULL}}
};
+static relopt_ternary ternaryRelOpts[] =
+{
+ {
+ {
+ "vacuum_truncate",
+ "Enables vacuum to truncate empty pages at the end of this table",
+ RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
+ ShareUpdateExclusiveLock
+ },
+ TERNARY_UNSET
+ },
+ /* list terminator */
+ {
+ {
+ NULL
+ }
+ }
+};
+
static relopt_int intRelOpts[] =
{
{
@@ -609,6 +619,13 @@ initialize_reloptions(void)
boolRelOpts[i].gen.lockmode));
j++;
}
+ for (i = 0; ternaryRelOpts[i].gen.name; i++)
+ {
+ Assert(DoLockModesConflict(ternaryRelOpts[i].gen.lockmode,
+ ternaryRelOpts[i].gen.lockmode));
+ j++;
+ }
+
for (i = 0; intRelOpts[i].gen.name; i++)
{
Assert(DoLockModesConflict(intRelOpts[i].gen.lockmode,
@@ -649,6 +666,14 @@ initialize_reloptions(void)
j++;
}
+ for (i = 0; ternaryRelOpts[i].gen.name; i++)
+ {
+ relOpts[j] = &ternaryRelOpts[i].gen;
+ relOpts[j]->type = RELOPT_TYPE_TERNARY;
+ relOpts[j]->namelen = strlen(relOpts[j]->name);
+ j++;
+ }
+
for (i = 0; intRelOpts[i].gen.name; i++)
{
relOpts[j] = &intRelOpts[i].gen;
@@ -809,6 +834,9 @@ allocate_reloption(bits32 kinds, int type, const char *name, const char *desc,
case RELOPT_TYPE_BOOL:
size = sizeof(relopt_bool);
break;
+ case RELOPT_TYPE_TERNARY:
+ size = sizeof(relopt_ternary);
+ break;
case RELOPT_TYPE_INT:
size = sizeof(relopt_int);
break;
@@ -892,6 +920,57 @@ add_local_bool_reloption(local_relopts *relopts, const char *name,
add_local_reloption(relopts, (relopt_gen *) newoption, offset);
}
+/*
+ * init_ternary_reloption
+ * Allocate and initialize a new ternary reloption
+ */
+static relopt_ternary *
+init_ternary_reloption(bits32 kinds, const char *name, const char *desc,
+ pg_ternary default_val, LOCKMODE lockmode)
+{
+ relopt_ternary *newoption;
+
+ newoption = (relopt_ternary *)
+ allocate_reloption(kinds, RELOPT_TYPE_TERNARY, name, desc, lockmode);
+ newoption->default_val = default_val;
+
+ return newoption;
+}
+
+/*
+ * add_ternary_reloption
+ * Add a new ternary reloption
+ */
+void
+add_ternary_reloption(bits32 kinds, const char *name, const char *desc,
+ pg_ternary default_val, LOCKMODE lockmode)
+{
+ relopt_ternary *newoption;
+
+ newoption =
+ init_ternary_reloption(kinds, name, desc, default_val, lockmode);
+
+ add_reloption((relopt_gen *) newoption);
+}
+
+/*
+ * add_local_ternary_reloption
+ * Add a new ternary local reloption
+ *
+ * 'offset' is offset of ternary-typed field.
+ */
+void
+add_local_ternary_reloption(local_relopts *relopts, const char *name,
+ const char *desc, pg_ternary default_val,
+ int offset)
+{
+ relopt_ternary *newoption;
+
+ newoption =
+ init_ternary_reloption(RELOPT_KIND_LOCAL, name, desc, default_val, 0);
+
+ add_local_reloption(relopts, (relopt_gen *) newoption, offset);
+}
/*
* init_real_reloption
@@ -1626,6 +1705,19 @@ parse_one_reloption(relopt_value *option, char *text_str, int text_len,
option->gen->name, value)));
}
break;
+ case RELOPT_TYPE_TERNARY:
+ {
+ bool b;
+
+ parsed = parse_bool(value, &b);
+ option->values.ternary_val = b ? TERNARY_TRUE : TERNARY_FALSE;
+ if (validate && !parsed)
+ ereport(ERROR,
+ (errcode(ERRCODE_INVALID_PARAMETER_VALUE),
+ errmsg("invalid value for ternary option \"%s\": %s",
+ option->gen->name, value)));
+ }
+ break;
case RELOPT_TYPE_INT:
{
relopt_int *optint = (relopt_int *) option->gen;
@@ -1789,17 +1881,6 @@ fillRelOptions(void *rdopts, Size basesize,
char *itempos = ((char *) rdopts) + elems[j].offset;
char *string_val;
- /*
- * If isset_offset is provided, store whether the reloption is
- * set there.
- */
- if (elems[j].isset_offset > 0)
- {
- char *setpos = ((char *) rdopts) + elems[j].isset_offset;
-
- *(bool *) setpos = options[i].isset;
- }
-
switch (options[i].gen->type)
{
case RELOPT_TYPE_BOOL:
@@ -1807,6 +1888,11 @@ fillRelOptions(void *rdopts, Size basesize,
options[i].values.bool_val :
((relopt_bool *) options[i].gen)->default_val;
break;
+ case RELOPT_TYPE_TERNARY:
+ *(pg_ternary *) itempos = options[i].isset ?
+ options[i].values.ternary_val :
+ ((relopt_ternary *) options[i].gen)->default_val;
+ break;
case RELOPT_TYPE_INT:
*(int *) itempos = options[i].isset ?
options[i].values.int_val :
@@ -1923,8 +2009,8 @@ default_reloptions(Datum reloptions, bool validate, relopt_kind kind)
offsetof(StdRdOptions, parallel_workers)},
{"vacuum_index_cleanup", RELOPT_TYPE_ENUM,
offsetof(StdRdOptions, vacuum_index_cleanup)},
- {"vacuum_truncate", RELOPT_TYPE_BOOL,
- offsetof(StdRdOptions, vacuum_truncate), offsetof(StdRdOptions, vacuum_truncate_set)},
+ {"vacuum_truncate", RELOPT_TYPE_TERNARY,
+ offsetof(StdRdOptions, vacuum_truncate)},
{"vacuum_max_eager_freeze_failure_rate", RELOPT_TYPE_REAL,
offsetof(StdRdOptions, vacuum_max_eager_freeze_failure_rate)}
};
@@ -2004,7 +2090,6 @@ build_local_reloptions(local_relopts *relopts, Datum options, bool validate)
elems[i].optname = opt->option->name;
elems[i].opttype = opt->option->type;
elems[i].offset = opt->offset;
- elems[i].isset_offset = 0; /* not supported for local relopts yet */
i++;
}
diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c
index aa4fbec143f..696eab9bd97 100644
--- a/src/backend/commands/vacuum.c
+++ b/src/backend/commands/vacuum.c
@@ -2224,9 +2224,9 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams params,
{
StdRdOptions *opts = (StdRdOptions *) rel->rd_options;
- if (opts && opts->vacuum_truncate_set)
+ if (opts && opts->vacuum_truncate != TERNARY_UNSET)
{
- if (opts->vacuum_truncate)
+ if (opts->vacuum_truncate == TERNARY_TRUE)
params.truncate = VACOPTVALUE_ENABLED;
else
params.truncate = VACOPTVALUE_DISABLED;
diff --git a/src/include/access/reloptions.h b/src/include/access/reloptions.h
index 2f08e1b0cf0..dfbef2babf2 100644
--- a/src/include/access/reloptions.h
+++ b/src/include/access/reloptions.h
@@ -29,6 +29,7 @@
typedef enum relopt_type
{
RELOPT_TYPE_BOOL,
+ RELOPT_TYPE_TERNARY, /* on, off, unset */
RELOPT_TYPE_INT,
RELOPT_TYPE_REAL,
RELOPT_TYPE_ENUM,
@@ -80,6 +81,7 @@ typedef struct relopt_value
union
{
bool bool_val;
+ pg_ternary ternary_val;
int int_val;
double real_val;
int enum_val;
@@ -94,6 +96,12 @@ typedef struct relopt_bool
bool default_val;
} relopt_bool;
+typedef struct relopt_ternary
+{
+ relopt_gen gen;
+ int default_val;
+} relopt_ternary;
+
typedef struct relopt_int
{
relopt_gen gen;
@@ -152,19 +160,6 @@ typedef struct
const char *optname; /* option's name */
relopt_type opttype; /* option's datatype */
int offset; /* offset of field in result struct */
-
- /*
- * isset_offset is an optional offset of a field in the result struct that
- * stores whether the option is explicitly set for the relation or if it
- * just picked up the default value. In most cases, this can be
- * accomplished by giving the reloption a special out-of-range default
- * value (e.g., some integer reloptions use -2), but this isn't always
- * possible. For example, a Boolean reloption cannot be given an
- * out-of-range default, so we need another way to discover the source of
- * its value. This offset is only used if given a value greater than
- * zero.
- */
- int isset_offset;
} relopt_parse_elt;
/* Local reloption definition */
@@ -195,6 +190,9 @@ typedef struct local_relopts
extern relopt_kind add_reloption_kind(void);
extern void add_bool_reloption(bits32 kinds, const char *name, const char *desc,
bool default_val, LOCKMODE lockmode);
+extern void add_ternary_reloption(bits32 kinds, const char *name,
+ const char *desc, pg_ternary default_val,
+ LOCKMODE lockmode);
extern void add_int_reloption(bits32 kinds, const char *name, const char *desc,
int default_val, int min_val, int max_val,
LOCKMODE lockmode);
@@ -214,6 +212,9 @@ extern void register_reloptions_validator(local_relopts *relopts,
extern void add_local_bool_reloption(local_relopts *relopts, const char *name,
const char *desc, bool default_val,
int offset);
+extern void add_local_ternary_reloption(local_relopts *relopts,
+ const char *name, const char *desc,
+ pg_ternary default_val, int offset);
extern void add_local_int_reloption(local_relopts *relopts, const char *name,
const char *desc, int default_val,
int min_val, int max_val, int offset);
diff --git a/src/include/postgres.h b/src/include/postgres.h
index 7d93fbce709..47c88780776 100644
--- a/src/include/postgres.h
+++ b/src/include/postgres.h
@@ -543,6 +543,21 @@ Float8GetDatum(float8 X)
* ----------------------------------------------------------------
*/
+/*
+ * pg_ternary
+ * Boolean value with an extra "unset" value
+ *
+ * This enum can be used for values that want to distinguish between true,
+ * false, and unset.
+*/
+
+typedef enum pg_ternary
+{
+ TERNARY_FALSE = 0,
+ TERNARY_TRUE = 1,
+ TERNARY_UNSET = -1
+} pg_ternary;
+
/*
* NON_EXEC_STATIC: It's sometimes useful to define a variable or function
* that is normally static but extern when using EXEC_BACKEND (see
diff --git a/src/include/utils/rel.h b/src/include/utils/rel.h
index d03ab247788..5feb18a5373 100644
--- a/src/include/utils/rel.h
+++ b/src/include/utils/rel.h
@@ -347,8 +347,7 @@ typedef struct StdRdOptions
bool user_catalog_table; /* use as an additional catalog relation */
int parallel_workers; /* max number of parallel workers */
StdRdOptIndexCleanup vacuum_index_cleanup; /* controls index vacuuming */
- bool vacuum_truncate; /* enables vacuum to truncate a relation */
- bool vacuum_truncate_set; /* whether vacuum_truncate is set */
+ pg_ternary vacuum_truncate; /* enables vacuum to truncate a relation */
/*
* Fraction of pages in a relation that vacuum can eagerly scan and fail
diff --git a/src/test/regress/expected/reloptions.out b/src/test/regress/expected/reloptions.out
index 9de19b4e3f1..1c99f79ab01 100644
--- a/src/test/regress/expected/reloptions.out
+++ b/src/test/regress/expected/reloptions.out
@@ -98,6 +98,42 @@ SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass;
{fillfactor=13,autovacuum_enabled=false}
(1 row)
+-- Tests for future (FIXME) ternary options
+-- behave as boolean option: accept unassigned name and truncated value
+DROP TABLE reloptions_test;
+CREATE TABLE reloptions_test(i INT) WITH (vacuum_truncate);
+SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass;
+ reloptions
+------------------------
+ {vacuum_truncate=true}
+(1 row)
+
+DROP TABLE reloptions_test;
+CREATE TABLE reloptions_test(i INT) WITH (vacuum_truncate=FaLS);
+SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass;
+ reloptions
+------------------------
+ {vacuum_truncate=fals}
+(1 row)
+
+-- preferred "true" alias is used when storing
+DROP TABLE reloptions_test;
+CREATE TABLE reloptions_test(i INT) WITH (vacuum_index_cleanup=on);
+SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass;
+ reloptions
+---------------------------
+ {vacuum_index_cleanup=on}
+(1 row)
+
+-- custom "third" value is available
+DROP TABLE reloptions_test;
+CREATE TABLE reloptions_test(i INT) WITH (vacuum_index_cleanup=auto);
+SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass;
+ reloptions
+-----------------------------
+ {vacuum_index_cleanup=auto}
+(1 row)
+
-- Test vacuum_truncate option
DROP TABLE reloptions_test;
CREATE TEMP TABLE reloptions_test(i INT NOT NULL, j text)
diff --git a/src/test/regress/sql/reloptions.sql b/src/test/regress/sql/reloptions.sql
index 24fbe0b478d..f5980dafcbc 100644
--- a/src/test/regress/sql/reloptions.sql
+++ b/src/test/regress/sql/reloptions.sql
@@ -59,6 +59,27 @@ UPDATE pg_class
ALTER TABLE reloptions_test RESET (illegal_option);
SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass;
+-- Tests for future (FIXME) ternary options
+
+-- behave as boolean option: accept unassigned name and truncated value
+DROP TABLE reloptions_test;
+CREATE TABLE reloptions_test(i INT) WITH (vacuum_truncate);
+SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass;
+
+DROP TABLE reloptions_test;
+CREATE TABLE reloptions_test(i INT) WITH (vacuum_truncate=FaLS);
+SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass;
+
+-- preferred "true" alias is used when storing
+DROP TABLE reloptions_test;
+CREATE TABLE reloptions_test(i INT) WITH (vacuum_index_cleanup=on);
+SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass;
+
+-- custom "third" value is available
+DROP TABLE reloptions_test;
+CREATE TABLE reloptions_test(i INT) WITH (vacuum_index_cleanup=auto);
+SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass;
+
-- Test vacuum_truncate option
DROP TABLE reloptions_test;
diff --git a/src/tools/pgindent/typedefs.list b/src/tools/pgindent/typedefs.list
index 3f3a888fd0e..1c8610fd46c 100644
--- a/src/tools/pgindent/typedefs.list
+++ b/src/tools/pgindent/typedefs.list
@@ -3952,6 +3952,7 @@ pg_sha512_ctx
pg_snapshot
pg_special_case
pg_stack_base_t
+pg_ternary
pg_time_t
pg_time_usec_t
pg_tz
@@ -4079,6 +4080,7 @@ relopt_kind
relopt_parse_elt
relopt_real
relopt_string
+relopt_ternary
relopt_type
relopt_value
relopts_validator
--
2.47.3
--c36t3fndxhkzbl5z--
^ permalink raw reply [nested|flat] 868+ messages in thread
* [PATCH v3] Introduce ternary reloptions
@ 2026-01-16 15:04 Álvaro Herrera <[email protected]>
0 siblings, 0 replies; 868+ messages in thread
From: Álvaro Herrera @ 2026-01-16 15:04 UTC (permalink / raw)
Introduce ternary reloption as a replacement for current
`vacuum_truncate` implementation. Remove the `vacuum_truncate_set`
separate flag and use TERNARY_UNSET instead.
This could also be used for other options such as `vacuum_index_cleanup`
and `buffering`, but lets get the scaffolding in first.
Discussion: https://postgr.es/m/3474141.usfYGdeWWP@thinkpad-pgpro
---
src/backend/access/common/reloptions.c | 137 ++++++++++++++++++-----
src/backend/commands/vacuum.c | 4 +-
src/include/access/reloptions.h | 27 ++---
src/include/postgres.h | 15 +++
src/include/utils/rel.h | 3 +-
src/test/regress/expected/reloptions.out | 36 ++++++
src/test/regress/sql/reloptions.sql | 21 ++++
src/tools/pgindent/typedefs.list | 2 +
8 files changed, 202 insertions(+), 43 deletions(-)
diff --git a/src/backend/access/common/reloptions.c b/src/backend/access/common/reloptions.c
index 0b83f98ed5f..6f1f577581a 100644
--- a/src/backend/access/common/reloptions.c
+++ b/src/backend/access/common/reloptions.c
@@ -40,9 +40,9 @@
*
* To add an option:
*
- * (i) decide on a type (bool, integer, real, enum, string), name, default
- * value, upper and lower bounds (if applicable); for strings, consider a
- * validation routine.
+ * (i) decide on a type (bool, ternary, integer, real, enum, string), name,
+ * default value, upper and lower bounds (if applicable); for strings,
+ * consider a validation routine.
* (ii) add a record below (or use add_<type>_reloption).
* (iii) add it to the appropriate options struct (perhaps StdRdOptions)
* (iv) add it to the appropriate handling routine (perhaps
@@ -147,15 +147,6 @@ static relopt_bool boolRelOpts[] =
},
false
},
- {
- {
- "vacuum_truncate",
- "Enables vacuum to truncate empty pages at the end of this table",
- RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
- ShareUpdateExclusiveLock
- },
- true
- },
{
{
"deduplicate_items",
@@ -170,6 +161,25 @@ static relopt_bool boolRelOpts[] =
{{NULL}}
};
+static relopt_ternary ternaryRelOpts[] =
+{
+ {
+ {
+ "vacuum_truncate",
+ "Enables vacuum to truncate empty pages at the end of this table",
+ RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
+ ShareUpdateExclusiveLock
+ },
+ TERNARY_UNSET
+ },
+ /* list terminator */
+ {
+ {
+ NULL
+ }
+ }
+};
+
static relopt_int intRelOpts[] =
{
{
@@ -609,6 +619,13 @@ initialize_reloptions(void)
boolRelOpts[i].gen.lockmode));
j++;
}
+ for (i = 0; ternaryRelOpts[i].gen.name; i++)
+ {
+ Assert(DoLockModesConflict(ternaryRelOpts[i].gen.lockmode,
+ ternaryRelOpts[i].gen.lockmode));
+ j++;
+ }
+
for (i = 0; intRelOpts[i].gen.name; i++)
{
Assert(DoLockModesConflict(intRelOpts[i].gen.lockmode,
@@ -649,6 +666,14 @@ initialize_reloptions(void)
j++;
}
+ for (i = 0; ternaryRelOpts[i].gen.name; i++)
+ {
+ relOpts[j] = &ternaryRelOpts[i].gen;
+ relOpts[j]->type = RELOPT_TYPE_TERNARY;
+ relOpts[j]->namelen = strlen(relOpts[j]->name);
+ j++;
+ }
+
for (i = 0; intRelOpts[i].gen.name; i++)
{
relOpts[j] = &intRelOpts[i].gen;
@@ -809,6 +834,9 @@ allocate_reloption(bits32 kinds, int type, const char *name, const char *desc,
case RELOPT_TYPE_BOOL:
size = sizeof(relopt_bool);
break;
+ case RELOPT_TYPE_TERNARY:
+ size = sizeof(relopt_ternary);
+ break;
case RELOPT_TYPE_INT:
size = sizeof(relopt_int);
break;
@@ -892,6 +920,57 @@ add_local_bool_reloption(local_relopts *relopts, const char *name,
add_local_reloption(relopts, (relopt_gen *) newoption, offset);
}
+/*
+ * init_ternary_reloption
+ * Allocate and initialize a new ternary reloption
+ */
+static relopt_ternary *
+init_ternary_reloption(bits32 kinds, const char *name, const char *desc,
+ pg_ternary default_val, LOCKMODE lockmode)
+{
+ relopt_ternary *newoption;
+
+ newoption = (relopt_ternary *)
+ allocate_reloption(kinds, RELOPT_TYPE_TERNARY, name, desc, lockmode);
+ newoption->default_val = default_val;
+
+ return newoption;
+}
+
+/*
+ * add_ternary_reloption
+ * Add a new ternary reloption
+ */
+void
+add_ternary_reloption(bits32 kinds, const char *name, const char *desc,
+ pg_ternary default_val, LOCKMODE lockmode)
+{
+ relopt_ternary *newoption;
+
+ newoption =
+ init_ternary_reloption(kinds, name, desc, default_val, lockmode);
+
+ add_reloption((relopt_gen *) newoption);
+}
+
+/*
+ * add_local_ternary_reloption
+ * Add a new ternary local reloption
+ *
+ * 'offset' is offset of ternary-typed field.
+ */
+void
+add_local_ternary_reloption(local_relopts *relopts, const char *name,
+ const char *desc, pg_ternary default_val,
+ int offset)
+{
+ relopt_ternary *newoption;
+
+ newoption =
+ init_ternary_reloption(RELOPT_KIND_LOCAL, name, desc, default_val, 0);
+
+ add_local_reloption(relopts, (relopt_gen *) newoption, offset);
+}
/*
* init_real_reloption
@@ -1626,6 +1705,19 @@ parse_one_reloption(relopt_value *option, char *text_str, int text_len,
option->gen->name, value)));
}
break;
+ case RELOPT_TYPE_TERNARY:
+ {
+ bool b;
+
+ parsed = parse_bool(value, &b);
+ option->values.ternary_val = b ? TERNARY_TRUE : TERNARY_FALSE;
+ if (validate && !parsed)
+ ereport(ERROR,
+ (errcode(ERRCODE_INVALID_PARAMETER_VALUE),
+ errmsg("invalid value for ternary option \"%s\": %s",
+ option->gen->name, value)));
+ }
+ break;
case RELOPT_TYPE_INT:
{
relopt_int *optint = (relopt_int *) option->gen;
@@ -1789,17 +1881,6 @@ fillRelOptions(void *rdopts, Size basesize,
char *itempos = ((char *) rdopts) + elems[j].offset;
char *string_val;
- /*
- * If isset_offset is provided, store whether the reloption is
- * set there.
- */
- if (elems[j].isset_offset > 0)
- {
- char *setpos = ((char *) rdopts) + elems[j].isset_offset;
-
- *(bool *) setpos = options[i].isset;
- }
-
switch (options[i].gen->type)
{
case RELOPT_TYPE_BOOL:
@@ -1807,6 +1888,11 @@ fillRelOptions(void *rdopts, Size basesize,
options[i].values.bool_val :
((relopt_bool *) options[i].gen)->default_val;
break;
+ case RELOPT_TYPE_TERNARY:
+ *(pg_ternary *) itempos = options[i].isset ?
+ options[i].values.ternary_val :
+ ((relopt_ternary *) options[i].gen)->default_val;
+ break;
case RELOPT_TYPE_INT:
*(int *) itempos = options[i].isset ?
options[i].values.int_val :
@@ -1923,8 +2009,8 @@ default_reloptions(Datum reloptions, bool validate, relopt_kind kind)
offsetof(StdRdOptions, parallel_workers)},
{"vacuum_index_cleanup", RELOPT_TYPE_ENUM,
offsetof(StdRdOptions, vacuum_index_cleanup)},
- {"vacuum_truncate", RELOPT_TYPE_BOOL,
- offsetof(StdRdOptions, vacuum_truncate), offsetof(StdRdOptions, vacuum_truncate_set)},
+ {"vacuum_truncate", RELOPT_TYPE_TERNARY,
+ offsetof(StdRdOptions, vacuum_truncate)},
{"vacuum_max_eager_freeze_failure_rate", RELOPT_TYPE_REAL,
offsetof(StdRdOptions, vacuum_max_eager_freeze_failure_rate)}
};
@@ -2004,7 +2090,6 @@ build_local_reloptions(local_relopts *relopts, Datum options, bool validate)
elems[i].optname = opt->option->name;
elems[i].opttype = opt->option->type;
elems[i].offset = opt->offset;
- elems[i].isset_offset = 0; /* not supported for local relopts yet */
i++;
}
diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c
index aa4fbec143f..696eab9bd97 100644
--- a/src/backend/commands/vacuum.c
+++ b/src/backend/commands/vacuum.c
@@ -2224,9 +2224,9 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams params,
{
StdRdOptions *opts = (StdRdOptions *) rel->rd_options;
- if (opts && opts->vacuum_truncate_set)
+ if (opts && opts->vacuum_truncate != TERNARY_UNSET)
{
- if (opts->vacuum_truncate)
+ if (opts->vacuum_truncate == TERNARY_TRUE)
params.truncate = VACOPTVALUE_ENABLED;
else
params.truncate = VACOPTVALUE_DISABLED;
diff --git a/src/include/access/reloptions.h b/src/include/access/reloptions.h
index 2f08e1b0cf0..dfbef2babf2 100644
--- a/src/include/access/reloptions.h
+++ b/src/include/access/reloptions.h
@@ -29,6 +29,7 @@
typedef enum relopt_type
{
RELOPT_TYPE_BOOL,
+ RELOPT_TYPE_TERNARY, /* on, off, unset */
RELOPT_TYPE_INT,
RELOPT_TYPE_REAL,
RELOPT_TYPE_ENUM,
@@ -80,6 +81,7 @@ typedef struct relopt_value
union
{
bool bool_val;
+ pg_ternary ternary_val;
int int_val;
double real_val;
int enum_val;
@@ -94,6 +96,12 @@ typedef struct relopt_bool
bool default_val;
} relopt_bool;
+typedef struct relopt_ternary
+{
+ relopt_gen gen;
+ int default_val;
+} relopt_ternary;
+
typedef struct relopt_int
{
relopt_gen gen;
@@ -152,19 +160,6 @@ typedef struct
const char *optname; /* option's name */
relopt_type opttype; /* option's datatype */
int offset; /* offset of field in result struct */
-
- /*
- * isset_offset is an optional offset of a field in the result struct that
- * stores whether the option is explicitly set for the relation or if it
- * just picked up the default value. In most cases, this can be
- * accomplished by giving the reloption a special out-of-range default
- * value (e.g., some integer reloptions use -2), but this isn't always
- * possible. For example, a Boolean reloption cannot be given an
- * out-of-range default, so we need another way to discover the source of
- * its value. This offset is only used if given a value greater than
- * zero.
- */
- int isset_offset;
} relopt_parse_elt;
/* Local reloption definition */
@@ -195,6 +190,9 @@ typedef struct local_relopts
extern relopt_kind add_reloption_kind(void);
extern void add_bool_reloption(bits32 kinds, const char *name, const char *desc,
bool default_val, LOCKMODE lockmode);
+extern void add_ternary_reloption(bits32 kinds, const char *name,
+ const char *desc, pg_ternary default_val,
+ LOCKMODE lockmode);
extern void add_int_reloption(bits32 kinds, const char *name, const char *desc,
int default_val, int min_val, int max_val,
LOCKMODE lockmode);
@@ -214,6 +212,9 @@ extern void register_reloptions_validator(local_relopts *relopts,
extern void add_local_bool_reloption(local_relopts *relopts, const char *name,
const char *desc, bool default_val,
int offset);
+extern void add_local_ternary_reloption(local_relopts *relopts,
+ const char *name, const char *desc,
+ pg_ternary default_val, int offset);
extern void add_local_int_reloption(local_relopts *relopts, const char *name,
const char *desc, int default_val,
int min_val, int max_val, int offset);
diff --git a/src/include/postgres.h b/src/include/postgres.h
index 7d93fbce709..47c88780776 100644
--- a/src/include/postgres.h
+++ b/src/include/postgres.h
@@ -543,6 +543,21 @@ Float8GetDatum(float8 X)
* ----------------------------------------------------------------
*/
+/*
+ * pg_ternary
+ * Boolean value with an extra "unset" value
+ *
+ * This enum can be used for values that want to distinguish between true,
+ * false, and unset.
+*/
+
+typedef enum pg_ternary
+{
+ TERNARY_FALSE = 0,
+ TERNARY_TRUE = 1,
+ TERNARY_UNSET = -1
+} pg_ternary;
+
/*
* NON_EXEC_STATIC: It's sometimes useful to define a variable or function
* that is normally static but extern when using EXEC_BACKEND (see
diff --git a/src/include/utils/rel.h b/src/include/utils/rel.h
index d03ab247788..5feb18a5373 100644
--- a/src/include/utils/rel.h
+++ b/src/include/utils/rel.h
@@ -347,8 +347,7 @@ typedef struct StdRdOptions
bool user_catalog_table; /* use as an additional catalog relation */
int parallel_workers; /* max number of parallel workers */
StdRdOptIndexCleanup vacuum_index_cleanup; /* controls index vacuuming */
- bool vacuum_truncate; /* enables vacuum to truncate a relation */
- bool vacuum_truncate_set; /* whether vacuum_truncate is set */
+ pg_ternary vacuum_truncate; /* enables vacuum to truncate a relation */
/*
* Fraction of pages in a relation that vacuum can eagerly scan and fail
diff --git a/src/test/regress/expected/reloptions.out b/src/test/regress/expected/reloptions.out
index 9de19b4e3f1..1c99f79ab01 100644
--- a/src/test/regress/expected/reloptions.out
+++ b/src/test/regress/expected/reloptions.out
@@ -98,6 +98,42 @@ SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass;
{fillfactor=13,autovacuum_enabled=false}
(1 row)
+-- Tests for future (FIXME) ternary options
+-- behave as boolean option: accept unassigned name and truncated value
+DROP TABLE reloptions_test;
+CREATE TABLE reloptions_test(i INT) WITH (vacuum_truncate);
+SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass;
+ reloptions
+------------------------
+ {vacuum_truncate=true}
+(1 row)
+
+DROP TABLE reloptions_test;
+CREATE TABLE reloptions_test(i INT) WITH (vacuum_truncate=FaLS);
+SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass;
+ reloptions
+------------------------
+ {vacuum_truncate=fals}
+(1 row)
+
+-- preferred "true" alias is used when storing
+DROP TABLE reloptions_test;
+CREATE TABLE reloptions_test(i INT) WITH (vacuum_index_cleanup=on);
+SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass;
+ reloptions
+---------------------------
+ {vacuum_index_cleanup=on}
+(1 row)
+
+-- custom "third" value is available
+DROP TABLE reloptions_test;
+CREATE TABLE reloptions_test(i INT) WITH (vacuum_index_cleanup=auto);
+SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass;
+ reloptions
+-----------------------------
+ {vacuum_index_cleanup=auto}
+(1 row)
+
-- Test vacuum_truncate option
DROP TABLE reloptions_test;
CREATE TEMP TABLE reloptions_test(i INT NOT NULL, j text)
diff --git a/src/test/regress/sql/reloptions.sql b/src/test/regress/sql/reloptions.sql
index 24fbe0b478d..f5980dafcbc 100644
--- a/src/test/regress/sql/reloptions.sql
+++ b/src/test/regress/sql/reloptions.sql
@@ -59,6 +59,27 @@ UPDATE pg_class
ALTER TABLE reloptions_test RESET (illegal_option);
SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass;
+-- Tests for future (FIXME) ternary options
+
+-- behave as boolean option: accept unassigned name and truncated value
+DROP TABLE reloptions_test;
+CREATE TABLE reloptions_test(i INT) WITH (vacuum_truncate);
+SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass;
+
+DROP TABLE reloptions_test;
+CREATE TABLE reloptions_test(i INT) WITH (vacuum_truncate=FaLS);
+SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass;
+
+-- preferred "true" alias is used when storing
+DROP TABLE reloptions_test;
+CREATE TABLE reloptions_test(i INT) WITH (vacuum_index_cleanup=on);
+SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass;
+
+-- custom "third" value is available
+DROP TABLE reloptions_test;
+CREATE TABLE reloptions_test(i INT) WITH (vacuum_index_cleanup=auto);
+SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass;
+
-- Test vacuum_truncate option
DROP TABLE reloptions_test;
diff --git a/src/tools/pgindent/typedefs.list b/src/tools/pgindent/typedefs.list
index 3f3a888fd0e..1c8610fd46c 100644
--- a/src/tools/pgindent/typedefs.list
+++ b/src/tools/pgindent/typedefs.list
@@ -3952,6 +3952,7 @@ pg_sha512_ctx
pg_snapshot
pg_special_case
pg_stack_base_t
+pg_ternary
pg_time_t
pg_time_usec_t
pg_tz
@@ -4079,6 +4080,7 @@ relopt_kind
relopt_parse_elt
relopt_real
relopt_string
+relopt_ternary
relopt_type
relopt_value
relopts_validator
--
2.47.3
--c36t3fndxhkzbl5z--
^ permalink raw reply [nested|flat] 868+ messages in thread
* [PATCH v3] Introduce ternary reloptions
@ 2026-01-16 15:04 Álvaro Herrera <[email protected]>
0 siblings, 0 replies; 868+ messages in thread
From: Álvaro Herrera @ 2026-01-16 15:04 UTC (permalink / raw)
Introduce ternary reloption as a replacement for current
`vacuum_truncate` implementation. Remove the `vacuum_truncate_set`
separate flag and use TERNARY_UNSET instead.
This could also be used for other options such as `vacuum_index_cleanup`
and `buffering`, but lets get the scaffolding in first.
Discussion: https://postgr.es/m/3474141.usfYGdeWWP@thinkpad-pgpro
---
src/backend/access/common/reloptions.c | 137 ++++++++++++++++++-----
src/backend/commands/vacuum.c | 4 +-
src/include/access/reloptions.h | 27 ++---
src/include/postgres.h | 15 +++
src/include/utils/rel.h | 3 +-
src/test/regress/expected/reloptions.out | 36 ++++++
src/test/regress/sql/reloptions.sql | 21 ++++
src/tools/pgindent/typedefs.list | 2 +
8 files changed, 202 insertions(+), 43 deletions(-)
diff --git a/src/backend/access/common/reloptions.c b/src/backend/access/common/reloptions.c
index 0b83f98ed5f..6f1f577581a 100644
--- a/src/backend/access/common/reloptions.c
+++ b/src/backend/access/common/reloptions.c
@@ -40,9 +40,9 @@
*
* To add an option:
*
- * (i) decide on a type (bool, integer, real, enum, string), name, default
- * value, upper and lower bounds (if applicable); for strings, consider a
- * validation routine.
+ * (i) decide on a type (bool, ternary, integer, real, enum, string), name,
+ * default value, upper and lower bounds (if applicable); for strings,
+ * consider a validation routine.
* (ii) add a record below (or use add_<type>_reloption).
* (iii) add it to the appropriate options struct (perhaps StdRdOptions)
* (iv) add it to the appropriate handling routine (perhaps
@@ -147,15 +147,6 @@ static relopt_bool boolRelOpts[] =
},
false
},
- {
- {
- "vacuum_truncate",
- "Enables vacuum to truncate empty pages at the end of this table",
- RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
- ShareUpdateExclusiveLock
- },
- true
- },
{
{
"deduplicate_items",
@@ -170,6 +161,25 @@ static relopt_bool boolRelOpts[] =
{{NULL}}
};
+static relopt_ternary ternaryRelOpts[] =
+{
+ {
+ {
+ "vacuum_truncate",
+ "Enables vacuum to truncate empty pages at the end of this table",
+ RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
+ ShareUpdateExclusiveLock
+ },
+ TERNARY_UNSET
+ },
+ /* list terminator */
+ {
+ {
+ NULL
+ }
+ }
+};
+
static relopt_int intRelOpts[] =
{
{
@@ -609,6 +619,13 @@ initialize_reloptions(void)
boolRelOpts[i].gen.lockmode));
j++;
}
+ for (i = 0; ternaryRelOpts[i].gen.name; i++)
+ {
+ Assert(DoLockModesConflict(ternaryRelOpts[i].gen.lockmode,
+ ternaryRelOpts[i].gen.lockmode));
+ j++;
+ }
+
for (i = 0; intRelOpts[i].gen.name; i++)
{
Assert(DoLockModesConflict(intRelOpts[i].gen.lockmode,
@@ -649,6 +666,14 @@ initialize_reloptions(void)
j++;
}
+ for (i = 0; ternaryRelOpts[i].gen.name; i++)
+ {
+ relOpts[j] = &ternaryRelOpts[i].gen;
+ relOpts[j]->type = RELOPT_TYPE_TERNARY;
+ relOpts[j]->namelen = strlen(relOpts[j]->name);
+ j++;
+ }
+
for (i = 0; intRelOpts[i].gen.name; i++)
{
relOpts[j] = &intRelOpts[i].gen;
@@ -809,6 +834,9 @@ allocate_reloption(bits32 kinds, int type, const char *name, const char *desc,
case RELOPT_TYPE_BOOL:
size = sizeof(relopt_bool);
break;
+ case RELOPT_TYPE_TERNARY:
+ size = sizeof(relopt_ternary);
+ break;
case RELOPT_TYPE_INT:
size = sizeof(relopt_int);
break;
@@ -892,6 +920,57 @@ add_local_bool_reloption(local_relopts *relopts, const char *name,
add_local_reloption(relopts, (relopt_gen *) newoption, offset);
}
+/*
+ * init_ternary_reloption
+ * Allocate and initialize a new ternary reloption
+ */
+static relopt_ternary *
+init_ternary_reloption(bits32 kinds, const char *name, const char *desc,
+ pg_ternary default_val, LOCKMODE lockmode)
+{
+ relopt_ternary *newoption;
+
+ newoption = (relopt_ternary *)
+ allocate_reloption(kinds, RELOPT_TYPE_TERNARY, name, desc, lockmode);
+ newoption->default_val = default_val;
+
+ return newoption;
+}
+
+/*
+ * add_ternary_reloption
+ * Add a new ternary reloption
+ */
+void
+add_ternary_reloption(bits32 kinds, const char *name, const char *desc,
+ pg_ternary default_val, LOCKMODE lockmode)
+{
+ relopt_ternary *newoption;
+
+ newoption =
+ init_ternary_reloption(kinds, name, desc, default_val, lockmode);
+
+ add_reloption((relopt_gen *) newoption);
+}
+
+/*
+ * add_local_ternary_reloption
+ * Add a new ternary local reloption
+ *
+ * 'offset' is offset of ternary-typed field.
+ */
+void
+add_local_ternary_reloption(local_relopts *relopts, const char *name,
+ const char *desc, pg_ternary default_val,
+ int offset)
+{
+ relopt_ternary *newoption;
+
+ newoption =
+ init_ternary_reloption(RELOPT_KIND_LOCAL, name, desc, default_val, 0);
+
+ add_local_reloption(relopts, (relopt_gen *) newoption, offset);
+}
/*
* init_real_reloption
@@ -1626,6 +1705,19 @@ parse_one_reloption(relopt_value *option, char *text_str, int text_len,
option->gen->name, value)));
}
break;
+ case RELOPT_TYPE_TERNARY:
+ {
+ bool b;
+
+ parsed = parse_bool(value, &b);
+ option->values.ternary_val = b ? TERNARY_TRUE : TERNARY_FALSE;
+ if (validate && !parsed)
+ ereport(ERROR,
+ (errcode(ERRCODE_INVALID_PARAMETER_VALUE),
+ errmsg("invalid value for ternary option \"%s\": %s",
+ option->gen->name, value)));
+ }
+ break;
case RELOPT_TYPE_INT:
{
relopt_int *optint = (relopt_int *) option->gen;
@@ -1789,17 +1881,6 @@ fillRelOptions(void *rdopts, Size basesize,
char *itempos = ((char *) rdopts) + elems[j].offset;
char *string_val;
- /*
- * If isset_offset is provided, store whether the reloption is
- * set there.
- */
- if (elems[j].isset_offset > 0)
- {
- char *setpos = ((char *) rdopts) + elems[j].isset_offset;
-
- *(bool *) setpos = options[i].isset;
- }
-
switch (options[i].gen->type)
{
case RELOPT_TYPE_BOOL:
@@ -1807,6 +1888,11 @@ fillRelOptions(void *rdopts, Size basesize,
options[i].values.bool_val :
((relopt_bool *) options[i].gen)->default_val;
break;
+ case RELOPT_TYPE_TERNARY:
+ *(pg_ternary *) itempos = options[i].isset ?
+ options[i].values.ternary_val :
+ ((relopt_ternary *) options[i].gen)->default_val;
+ break;
case RELOPT_TYPE_INT:
*(int *) itempos = options[i].isset ?
options[i].values.int_val :
@@ -1923,8 +2009,8 @@ default_reloptions(Datum reloptions, bool validate, relopt_kind kind)
offsetof(StdRdOptions, parallel_workers)},
{"vacuum_index_cleanup", RELOPT_TYPE_ENUM,
offsetof(StdRdOptions, vacuum_index_cleanup)},
- {"vacuum_truncate", RELOPT_TYPE_BOOL,
- offsetof(StdRdOptions, vacuum_truncate), offsetof(StdRdOptions, vacuum_truncate_set)},
+ {"vacuum_truncate", RELOPT_TYPE_TERNARY,
+ offsetof(StdRdOptions, vacuum_truncate)},
{"vacuum_max_eager_freeze_failure_rate", RELOPT_TYPE_REAL,
offsetof(StdRdOptions, vacuum_max_eager_freeze_failure_rate)}
};
@@ -2004,7 +2090,6 @@ build_local_reloptions(local_relopts *relopts, Datum options, bool validate)
elems[i].optname = opt->option->name;
elems[i].opttype = opt->option->type;
elems[i].offset = opt->offset;
- elems[i].isset_offset = 0; /* not supported for local relopts yet */
i++;
}
diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c
index aa4fbec143f..696eab9bd97 100644
--- a/src/backend/commands/vacuum.c
+++ b/src/backend/commands/vacuum.c
@@ -2224,9 +2224,9 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams params,
{
StdRdOptions *opts = (StdRdOptions *) rel->rd_options;
- if (opts && opts->vacuum_truncate_set)
+ if (opts && opts->vacuum_truncate != TERNARY_UNSET)
{
- if (opts->vacuum_truncate)
+ if (opts->vacuum_truncate == TERNARY_TRUE)
params.truncate = VACOPTVALUE_ENABLED;
else
params.truncate = VACOPTVALUE_DISABLED;
diff --git a/src/include/access/reloptions.h b/src/include/access/reloptions.h
index 2f08e1b0cf0..dfbef2babf2 100644
--- a/src/include/access/reloptions.h
+++ b/src/include/access/reloptions.h
@@ -29,6 +29,7 @@
typedef enum relopt_type
{
RELOPT_TYPE_BOOL,
+ RELOPT_TYPE_TERNARY, /* on, off, unset */
RELOPT_TYPE_INT,
RELOPT_TYPE_REAL,
RELOPT_TYPE_ENUM,
@@ -80,6 +81,7 @@ typedef struct relopt_value
union
{
bool bool_val;
+ pg_ternary ternary_val;
int int_val;
double real_val;
int enum_val;
@@ -94,6 +96,12 @@ typedef struct relopt_bool
bool default_val;
} relopt_bool;
+typedef struct relopt_ternary
+{
+ relopt_gen gen;
+ int default_val;
+} relopt_ternary;
+
typedef struct relopt_int
{
relopt_gen gen;
@@ -152,19 +160,6 @@ typedef struct
const char *optname; /* option's name */
relopt_type opttype; /* option's datatype */
int offset; /* offset of field in result struct */
-
- /*
- * isset_offset is an optional offset of a field in the result struct that
- * stores whether the option is explicitly set for the relation or if it
- * just picked up the default value. In most cases, this can be
- * accomplished by giving the reloption a special out-of-range default
- * value (e.g., some integer reloptions use -2), but this isn't always
- * possible. For example, a Boolean reloption cannot be given an
- * out-of-range default, so we need another way to discover the source of
- * its value. This offset is only used if given a value greater than
- * zero.
- */
- int isset_offset;
} relopt_parse_elt;
/* Local reloption definition */
@@ -195,6 +190,9 @@ typedef struct local_relopts
extern relopt_kind add_reloption_kind(void);
extern void add_bool_reloption(bits32 kinds, const char *name, const char *desc,
bool default_val, LOCKMODE lockmode);
+extern void add_ternary_reloption(bits32 kinds, const char *name,
+ const char *desc, pg_ternary default_val,
+ LOCKMODE lockmode);
extern void add_int_reloption(bits32 kinds, const char *name, const char *desc,
int default_val, int min_val, int max_val,
LOCKMODE lockmode);
@@ -214,6 +212,9 @@ extern void register_reloptions_validator(local_relopts *relopts,
extern void add_local_bool_reloption(local_relopts *relopts, const char *name,
const char *desc, bool default_val,
int offset);
+extern void add_local_ternary_reloption(local_relopts *relopts,
+ const char *name, const char *desc,
+ pg_ternary default_val, int offset);
extern void add_local_int_reloption(local_relopts *relopts, const char *name,
const char *desc, int default_val,
int min_val, int max_val, int offset);
diff --git a/src/include/postgres.h b/src/include/postgres.h
index 7d93fbce709..47c88780776 100644
--- a/src/include/postgres.h
+++ b/src/include/postgres.h
@@ -543,6 +543,21 @@ Float8GetDatum(float8 X)
* ----------------------------------------------------------------
*/
+/*
+ * pg_ternary
+ * Boolean value with an extra "unset" value
+ *
+ * This enum can be used for values that want to distinguish between true,
+ * false, and unset.
+*/
+
+typedef enum pg_ternary
+{
+ TERNARY_FALSE = 0,
+ TERNARY_TRUE = 1,
+ TERNARY_UNSET = -1
+} pg_ternary;
+
/*
* NON_EXEC_STATIC: It's sometimes useful to define a variable or function
* that is normally static but extern when using EXEC_BACKEND (see
diff --git a/src/include/utils/rel.h b/src/include/utils/rel.h
index d03ab247788..5feb18a5373 100644
--- a/src/include/utils/rel.h
+++ b/src/include/utils/rel.h
@@ -347,8 +347,7 @@ typedef struct StdRdOptions
bool user_catalog_table; /* use as an additional catalog relation */
int parallel_workers; /* max number of parallel workers */
StdRdOptIndexCleanup vacuum_index_cleanup; /* controls index vacuuming */
- bool vacuum_truncate; /* enables vacuum to truncate a relation */
- bool vacuum_truncate_set; /* whether vacuum_truncate is set */
+ pg_ternary vacuum_truncate; /* enables vacuum to truncate a relation */
/*
* Fraction of pages in a relation that vacuum can eagerly scan and fail
diff --git a/src/test/regress/expected/reloptions.out b/src/test/regress/expected/reloptions.out
index 9de19b4e3f1..1c99f79ab01 100644
--- a/src/test/regress/expected/reloptions.out
+++ b/src/test/regress/expected/reloptions.out
@@ -98,6 +98,42 @@ SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass;
{fillfactor=13,autovacuum_enabled=false}
(1 row)
+-- Tests for future (FIXME) ternary options
+-- behave as boolean option: accept unassigned name and truncated value
+DROP TABLE reloptions_test;
+CREATE TABLE reloptions_test(i INT) WITH (vacuum_truncate);
+SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass;
+ reloptions
+------------------------
+ {vacuum_truncate=true}
+(1 row)
+
+DROP TABLE reloptions_test;
+CREATE TABLE reloptions_test(i INT) WITH (vacuum_truncate=FaLS);
+SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass;
+ reloptions
+------------------------
+ {vacuum_truncate=fals}
+(1 row)
+
+-- preferred "true" alias is used when storing
+DROP TABLE reloptions_test;
+CREATE TABLE reloptions_test(i INT) WITH (vacuum_index_cleanup=on);
+SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass;
+ reloptions
+---------------------------
+ {vacuum_index_cleanup=on}
+(1 row)
+
+-- custom "third" value is available
+DROP TABLE reloptions_test;
+CREATE TABLE reloptions_test(i INT) WITH (vacuum_index_cleanup=auto);
+SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass;
+ reloptions
+-----------------------------
+ {vacuum_index_cleanup=auto}
+(1 row)
+
-- Test vacuum_truncate option
DROP TABLE reloptions_test;
CREATE TEMP TABLE reloptions_test(i INT NOT NULL, j text)
diff --git a/src/test/regress/sql/reloptions.sql b/src/test/regress/sql/reloptions.sql
index 24fbe0b478d..f5980dafcbc 100644
--- a/src/test/regress/sql/reloptions.sql
+++ b/src/test/regress/sql/reloptions.sql
@@ -59,6 +59,27 @@ UPDATE pg_class
ALTER TABLE reloptions_test RESET (illegal_option);
SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass;
+-- Tests for future (FIXME) ternary options
+
+-- behave as boolean option: accept unassigned name and truncated value
+DROP TABLE reloptions_test;
+CREATE TABLE reloptions_test(i INT) WITH (vacuum_truncate);
+SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass;
+
+DROP TABLE reloptions_test;
+CREATE TABLE reloptions_test(i INT) WITH (vacuum_truncate=FaLS);
+SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass;
+
+-- preferred "true" alias is used when storing
+DROP TABLE reloptions_test;
+CREATE TABLE reloptions_test(i INT) WITH (vacuum_index_cleanup=on);
+SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass;
+
+-- custom "third" value is available
+DROP TABLE reloptions_test;
+CREATE TABLE reloptions_test(i INT) WITH (vacuum_index_cleanup=auto);
+SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass;
+
-- Test vacuum_truncate option
DROP TABLE reloptions_test;
diff --git a/src/tools/pgindent/typedefs.list b/src/tools/pgindent/typedefs.list
index 3f3a888fd0e..1c8610fd46c 100644
--- a/src/tools/pgindent/typedefs.list
+++ b/src/tools/pgindent/typedefs.list
@@ -3952,6 +3952,7 @@ pg_sha512_ctx
pg_snapshot
pg_special_case
pg_stack_base_t
+pg_ternary
pg_time_t
pg_time_usec_t
pg_tz
@@ -4079,6 +4080,7 @@ relopt_kind
relopt_parse_elt
relopt_real
relopt_string
+relopt_ternary
relopt_type
relopt_value
relopts_validator
--
2.47.3
--c36t3fndxhkzbl5z--
^ permalink raw reply [nested|flat] 868+ messages in thread
* [PATCH v3] Introduce ternary reloptions
@ 2026-01-16 15:04 Álvaro Herrera <[email protected]>
0 siblings, 0 replies; 868+ messages in thread
From: Álvaro Herrera @ 2026-01-16 15:04 UTC (permalink / raw)
Introduce ternary reloption as a replacement for current
`vacuum_truncate` implementation. Remove the `vacuum_truncate_set`
separate flag and use TERNARY_UNSET instead.
This could also be used for other options such as `vacuum_index_cleanup`
and `buffering`, but lets get the scaffolding in first.
Discussion: https://postgr.es/m/3474141.usfYGdeWWP@thinkpad-pgpro
---
src/backend/access/common/reloptions.c | 137 ++++++++++++++++++-----
src/backend/commands/vacuum.c | 4 +-
src/include/access/reloptions.h | 27 ++---
src/include/postgres.h | 15 +++
src/include/utils/rel.h | 3 +-
src/test/regress/expected/reloptions.out | 36 ++++++
src/test/regress/sql/reloptions.sql | 21 ++++
src/tools/pgindent/typedefs.list | 2 +
8 files changed, 202 insertions(+), 43 deletions(-)
diff --git a/src/backend/access/common/reloptions.c b/src/backend/access/common/reloptions.c
index 0b83f98ed5f..6f1f577581a 100644
--- a/src/backend/access/common/reloptions.c
+++ b/src/backend/access/common/reloptions.c
@@ -40,9 +40,9 @@
*
* To add an option:
*
- * (i) decide on a type (bool, integer, real, enum, string), name, default
- * value, upper and lower bounds (if applicable); for strings, consider a
- * validation routine.
+ * (i) decide on a type (bool, ternary, integer, real, enum, string), name,
+ * default value, upper and lower bounds (if applicable); for strings,
+ * consider a validation routine.
* (ii) add a record below (or use add_<type>_reloption).
* (iii) add it to the appropriate options struct (perhaps StdRdOptions)
* (iv) add it to the appropriate handling routine (perhaps
@@ -147,15 +147,6 @@ static relopt_bool boolRelOpts[] =
},
false
},
- {
- {
- "vacuum_truncate",
- "Enables vacuum to truncate empty pages at the end of this table",
- RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
- ShareUpdateExclusiveLock
- },
- true
- },
{
{
"deduplicate_items",
@@ -170,6 +161,25 @@ static relopt_bool boolRelOpts[] =
{{NULL}}
};
+static relopt_ternary ternaryRelOpts[] =
+{
+ {
+ {
+ "vacuum_truncate",
+ "Enables vacuum to truncate empty pages at the end of this table",
+ RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
+ ShareUpdateExclusiveLock
+ },
+ TERNARY_UNSET
+ },
+ /* list terminator */
+ {
+ {
+ NULL
+ }
+ }
+};
+
static relopt_int intRelOpts[] =
{
{
@@ -609,6 +619,13 @@ initialize_reloptions(void)
boolRelOpts[i].gen.lockmode));
j++;
}
+ for (i = 0; ternaryRelOpts[i].gen.name; i++)
+ {
+ Assert(DoLockModesConflict(ternaryRelOpts[i].gen.lockmode,
+ ternaryRelOpts[i].gen.lockmode));
+ j++;
+ }
+
for (i = 0; intRelOpts[i].gen.name; i++)
{
Assert(DoLockModesConflict(intRelOpts[i].gen.lockmode,
@@ -649,6 +666,14 @@ initialize_reloptions(void)
j++;
}
+ for (i = 0; ternaryRelOpts[i].gen.name; i++)
+ {
+ relOpts[j] = &ternaryRelOpts[i].gen;
+ relOpts[j]->type = RELOPT_TYPE_TERNARY;
+ relOpts[j]->namelen = strlen(relOpts[j]->name);
+ j++;
+ }
+
for (i = 0; intRelOpts[i].gen.name; i++)
{
relOpts[j] = &intRelOpts[i].gen;
@@ -809,6 +834,9 @@ allocate_reloption(bits32 kinds, int type, const char *name, const char *desc,
case RELOPT_TYPE_BOOL:
size = sizeof(relopt_bool);
break;
+ case RELOPT_TYPE_TERNARY:
+ size = sizeof(relopt_ternary);
+ break;
case RELOPT_TYPE_INT:
size = sizeof(relopt_int);
break;
@@ -892,6 +920,57 @@ add_local_bool_reloption(local_relopts *relopts, const char *name,
add_local_reloption(relopts, (relopt_gen *) newoption, offset);
}
+/*
+ * init_ternary_reloption
+ * Allocate and initialize a new ternary reloption
+ */
+static relopt_ternary *
+init_ternary_reloption(bits32 kinds, const char *name, const char *desc,
+ pg_ternary default_val, LOCKMODE lockmode)
+{
+ relopt_ternary *newoption;
+
+ newoption = (relopt_ternary *)
+ allocate_reloption(kinds, RELOPT_TYPE_TERNARY, name, desc, lockmode);
+ newoption->default_val = default_val;
+
+ return newoption;
+}
+
+/*
+ * add_ternary_reloption
+ * Add a new ternary reloption
+ */
+void
+add_ternary_reloption(bits32 kinds, const char *name, const char *desc,
+ pg_ternary default_val, LOCKMODE lockmode)
+{
+ relopt_ternary *newoption;
+
+ newoption =
+ init_ternary_reloption(kinds, name, desc, default_val, lockmode);
+
+ add_reloption((relopt_gen *) newoption);
+}
+
+/*
+ * add_local_ternary_reloption
+ * Add a new ternary local reloption
+ *
+ * 'offset' is offset of ternary-typed field.
+ */
+void
+add_local_ternary_reloption(local_relopts *relopts, const char *name,
+ const char *desc, pg_ternary default_val,
+ int offset)
+{
+ relopt_ternary *newoption;
+
+ newoption =
+ init_ternary_reloption(RELOPT_KIND_LOCAL, name, desc, default_val, 0);
+
+ add_local_reloption(relopts, (relopt_gen *) newoption, offset);
+}
/*
* init_real_reloption
@@ -1626,6 +1705,19 @@ parse_one_reloption(relopt_value *option, char *text_str, int text_len,
option->gen->name, value)));
}
break;
+ case RELOPT_TYPE_TERNARY:
+ {
+ bool b;
+
+ parsed = parse_bool(value, &b);
+ option->values.ternary_val = b ? TERNARY_TRUE : TERNARY_FALSE;
+ if (validate && !parsed)
+ ereport(ERROR,
+ (errcode(ERRCODE_INVALID_PARAMETER_VALUE),
+ errmsg("invalid value for ternary option \"%s\": %s",
+ option->gen->name, value)));
+ }
+ break;
case RELOPT_TYPE_INT:
{
relopt_int *optint = (relopt_int *) option->gen;
@@ -1789,17 +1881,6 @@ fillRelOptions(void *rdopts, Size basesize,
char *itempos = ((char *) rdopts) + elems[j].offset;
char *string_val;
- /*
- * If isset_offset is provided, store whether the reloption is
- * set there.
- */
- if (elems[j].isset_offset > 0)
- {
- char *setpos = ((char *) rdopts) + elems[j].isset_offset;
-
- *(bool *) setpos = options[i].isset;
- }
-
switch (options[i].gen->type)
{
case RELOPT_TYPE_BOOL:
@@ -1807,6 +1888,11 @@ fillRelOptions(void *rdopts, Size basesize,
options[i].values.bool_val :
((relopt_bool *) options[i].gen)->default_val;
break;
+ case RELOPT_TYPE_TERNARY:
+ *(pg_ternary *) itempos = options[i].isset ?
+ options[i].values.ternary_val :
+ ((relopt_ternary *) options[i].gen)->default_val;
+ break;
case RELOPT_TYPE_INT:
*(int *) itempos = options[i].isset ?
options[i].values.int_val :
@@ -1923,8 +2009,8 @@ default_reloptions(Datum reloptions, bool validate, relopt_kind kind)
offsetof(StdRdOptions, parallel_workers)},
{"vacuum_index_cleanup", RELOPT_TYPE_ENUM,
offsetof(StdRdOptions, vacuum_index_cleanup)},
- {"vacuum_truncate", RELOPT_TYPE_BOOL,
- offsetof(StdRdOptions, vacuum_truncate), offsetof(StdRdOptions, vacuum_truncate_set)},
+ {"vacuum_truncate", RELOPT_TYPE_TERNARY,
+ offsetof(StdRdOptions, vacuum_truncate)},
{"vacuum_max_eager_freeze_failure_rate", RELOPT_TYPE_REAL,
offsetof(StdRdOptions, vacuum_max_eager_freeze_failure_rate)}
};
@@ -2004,7 +2090,6 @@ build_local_reloptions(local_relopts *relopts, Datum options, bool validate)
elems[i].optname = opt->option->name;
elems[i].opttype = opt->option->type;
elems[i].offset = opt->offset;
- elems[i].isset_offset = 0; /* not supported for local relopts yet */
i++;
}
diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c
index aa4fbec143f..696eab9bd97 100644
--- a/src/backend/commands/vacuum.c
+++ b/src/backend/commands/vacuum.c
@@ -2224,9 +2224,9 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams params,
{
StdRdOptions *opts = (StdRdOptions *) rel->rd_options;
- if (opts && opts->vacuum_truncate_set)
+ if (opts && opts->vacuum_truncate != TERNARY_UNSET)
{
- if (opts->vacuum_truncate)
+ if (opts->vacuum_truncate == TERNARY_TRUE)
params.truncate = VACOPTVALUE_ENABLED;
else
params.truncate = VACOPTVALUE_DISABLED;
diff --git a/src/include/access/reloptions.h b/src/include/access/reloptions.h
index 2f08e1b0cf0..dfbef2babf2 100644
--- a/src/include/access/reloptions.h
+++ b/src/include/access/reloptions.h
@@ -29,6 +29,7 @@
typedef enum relopt_type
{
RELOPT_TYPE_BOOL,
+ RELOPT_TYPE_TERNARY, /* on, off, unset */
RELOPT_TYPE_INT,
RELOPT_TYPE_REAL,
RELOPT_TYPE_ENUM,
@@ -80,6 +81,7 @@ typedef struct relopt_value
union
{
bool bool_val;
+ pg_ternary ternary_val;
int int_val;
double real_val;
int enum_val;
@@ -94,6 +96,12 @@ typedef struct relopt_bool
bool default_val;
} relopt_bool;
+typedef struct relopt_ternary
+{
+ relopt_gen gen;
+ int default_val;
+} relopt_ternary;
+
typedef struct relopt_int
{
relopt_gen gen;
@@ -152,19 +160,6 @@ typedef struct
const char *optname; /* option's name */
relopt_type opttype; /* option's datatype */
int offset; /* offset of field in result struct */
-
- /*
- * isset_offset is an optional offset of a field in the result struct that
- * stores whether the option is explicitly set for the relation or if it
- * just picked up the default value. In most cases, this can be
- * accomplished by giving the reloption a special out-of-range default
- * value (e.g., some integer reloptions use -2), but this isn't always
- * possible. For example, a Boolean reloption cannot be given an
- * out-of-range default, so we need another way to discover the source of
- * its value. This offset is only used if given a value greater than
- * zero.
- */
- int isset_offset;
} relopt_parse_elt;
/* Local reloption definition */
@@ -195,6 +190,9 @@ typedef struct local_relopts
extern relopt_kind add_reloption_kind(void);
extern void add_bool_reloption(bits32 kinds, const char *name, const char *desc,
bool default_val, LOCKMODE lockmode);
+extern void add_ternary_reloption(bits32 kinds, const char *name,
+ const char *desc, pg_ternary default_val,
+ LOCKMODE lockmode);
extern void add_int_reloption(bits32 kinds, const char *name, const char *desc,
int default_val, int min_val, int max_val,
LOCKMODE lockmode);
@@ -214,6 +212,9 @@ extern void register_reloptions_validator(local_relopts *relopts,
extern void add_local_bool_reloption(local_relopts *relopts, const char *name,
const char *desc, bool default_val,
int offset);
+extern void add_local_ternary_reloption(local_relopts *relopts,
+ const char *name, const char *desc,
+ pg_ternary default_val, int offset);
extern void add_local_int_reloption(local_relopts *relopts, const char *name,
const char *desc, int default_val,
int min_val, int max_val, int offset);
diff --git a/src/include/postgres.h b/src/include/postgres.h
index 7d93fbce709..47c88780776 100644
--- a/src/include/postgres.h
+++ b/src/include/postgres.h
@@ -543,6 +543,21 @@ Float8GetDatum(float8 X)
* ----------------------------------------------------------------
*/
+/*
+ * pg_ternary
+ * Boolean value with an extra "unset" value
+ *
+ * This enum can be used for values that want to distinguish between true,
+ * false, and unset.
+*/
+
+typedef enum pg_ternary
+{
+ TERNARY_FALSE = 0,
+ TERNARY_TRUE = 1,
+ TERNARY_UNSET = -1
+} pg_ternary;
+
/*
* NON_EXEC_STATIC: It's sometimes useful to define a variable or function
* that is normally static but extern when using EXEC_BACKEND (see
diff --git a/src/include/utils/rel.h b/src/include/utils/rel.h
index d03ab247788..5feb18a5373 100644
--- a/src/include/utils/rel.h
+++ b/src/include/utils/rel.h
@@ -347,8 +347,7 @@ typedef struct StdRdOptions
bool user_catalog_table; /* use as an additional catalog relation */
int parallel_workers; /* max number of parallel workers */
StdRdOptIndexCleanup vacuum_index_cleanup; /* controls index vacuuming */
- bool vacuum_truncate; /* enables vacuum to truncate a relation */
- bool vacuum_truncate_set; /* whether vacuum_truncate is set */
+ pg_ternary vacuum_truncate; /* enables vacuum to truncate a relation */
/*
* Fraction of pages in a relation that vacuum can eagerly scan and fail
diff --git a/src/test/regress/expected/reloptions.out b/src/test/regress/expected/reloptions.out
index 9de19b4e3f1..1c99f79ab01 100644
--- a/src/test/regress/expected/reloptions.out
+++ b/src/test/regress/expected/reloptions.out
@@ -98,6 +98,42 @@ SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass;
{fillfactor=13,autovacuum_enabled=false}
(1 row)
+-- Tests for future (FIXME) ternary options
+-- behave as boolean option: accept unassigned name and truncated value
+DROP TABLE reloptions_test;
+CREATE TABLE reloptions_test(i INT) WITH (vacuum_truncate);
+SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass;
+ reloptions
+------------------------
+ {vacuum_truncate=true}
+(1 row)
+
+DROP TABLE reloptions_test;
+CREATE TABLE reloptions_test(i INT) WITH (vacuum_truncate=FaLS);
+SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass;
+ reloptions
+------------------------
+ {vacuum_truncate=fals}
+(1 row)
+
+-- preferred "true" alias is used when storing
+DROP TABLE reloptions_test;
+CREATE TABLE reloptions_test(i INT) WITH (vacuum_index_cleanup=on);
+SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass;
+ reloptions
+---------------------------
+ {vacuum_index_cleanup=on}
+(1 row)
+
+-- custom "third" value is available
+DROP TABLE reloptions_test;
+CREATE TABLE reloptions_test(i INT) WITH (vacuum_index_cleanup=auto);
+SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass;
+ reloptions
+-----------------------------
+ {vacuum_index_cleanup=auto}
+(1 row)
+
-- Test vacuum_truncate option
DROP TABLE reloptions_test;
CREATE TEMP TABLE reloptions_test(i INT NOT NULL, j text)
diff --git a/src/test/regress/sql/reloptions.sql b/src/test/regress/sql/reloptions.sql
index 24fbe0b478d..f5980dafcbc 100644
--- a/src/test/regress/sql/reloptions.sql
+++ b/src/test/regress/sql/reloptions.sql
@@ -59,6 +59,27 @@ UPDATE pg_class
ALTER TABLE reloptions_test RESET (illegal_option);
SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass;
+-- Tests for future (FIXME) ternary options
+
+-- behave as boolean option: accept unassigned name and truncated value
+DROP TABLE reloptions_test;
+CREATE TABLE reloptions_test(i INT) WITH (vacuum_truncate);
+SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass;
+
+DROP TABLE reloptions_test;
+CREATE TABLE reloptions_test(i INT) WITH (vacuum_truncate=FaLS);
+SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass;
+
+-- preferred "true" alias is used when storing
+DROP TABLE reloptions_test;
+CREATE TABLE reloptions_test(i INT) WITH (vacuum_index_cleanup=on);
+SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass;
+
+-- custom "third" value is available
+DROP TABLE reloptions_test;
+CREATE TABLE reloptions_test(i INT) WITH (vacuum_index_cleanup=auto);
+SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass;
+
-- Test vacuum_truncate option
DROP TABLE reloptions_test;
diff --git a/src/tools/pgindent/typedefs.list b/src/tools/pgindent/typedefs.list
index 3f3a888fd0e..1c8610fd46c 100644
--- a/src/tools/pgindent/typedefs.list
+++ b/src/tools/pgindent/typedefs.list
@@ -3952,6 +3952,7 @@ pg_sha512_ctx
pg_snapshot
pg_special_case
pg_stack_base_t
+pg_ternary
pg_time_t
pg_time_usec_t
pg_tz
@@ -4079,6 +4080,7 @@ relopt_kind
relopt_parse_elt
relopt_real
relopt_string
+relopt_ternary
relopt_type
relopt_value
relopts_validator
--
2.47.3
--c36t3fndxhkzbl5z--
^ permalink raw reply [nested|flat] 868+ messages in thread
* [PATCH v3] Introduce ternary reloptions
@ 2026-01-16 15:04 Álvaro Herrera <[email protected]>
0 siblings, 0 replies; 868+ messages in thread
From: Álvaro Herrera @ 2026-01-16 15:04 UTC (permalink / raw)
Introduce ternary reloption as a replacement for current
`vacuum_truncate` implementation. Remove the `vacuum_truncate_set`
separate flag and use TERNARY_UNSET instead.
This could also be used for other options such as `vacuum_index_cleanup`
and `buffering`, but lets get the scaffolding in first.
Discussion: https://postgr.es/m/3474141.usfYGdeWWP@thinkpad-pgpro
---
src/backend/access/common/reloptions.c | 137 ++++++++++++++++++-----
src/backend/commands/vacuum.c | 4 +-
src/include/access/reloptions.h | 27 ++---
src/include/postgres.h | 15 +++
src/include/utils/rel.h | 3 +-
src/test/regress/expected/reloptions.out | 36 ++++++
src/test/regress/sql/reloptions.sql | 21 ++++
src/tools/pgindent/typedefs.list | 2 +
8 files changed, 202 insertions(+), 43 deletions(-)
diff --git a/src/backend/access/common/reloptions.c b/src/backend/access/common/reloptions.c
index 0b83f98ed5f..6f1f577581a 100644
--- a/src/backend/access/common/reloptions.c
+++ b/src/backend/access/common/reloptions.c
@@ -40,9 +40,9 @@
*
* To add an option:
*
- * (i) decide on a type (bool, integer, real, enum, string), name, default
- * value, upper and lower bounds (if applicable); for strings, consider a
- * validation routine.
+ * (i) decide on a type (bool, ternary, integer, real, enum, string), name,
+ * default value, upper and lower bounds (if applicable); for strings,
+ * consider a validation routine.
* (ii) add a record below (or use add_<type>_reloption).
* (iii) add it to the appropriate options struct (perhaps StdRdOptions)
* (iv) add it to the appropriate handling routine (perhaps
@@ -147,15 +147,6 @@ static relopt_bool boolRelOpts[] =
},
false
},
- {
- {
- "vacuum_truncate",
- "Enables vacuum to truncate empty pages at the end of this table",
- RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
- ShareUpdateExclusiveLock
- },
- true
- },
{
{
"deduplicate_items",
@@ -170,6 +161,25 @@ static relopt_bool boolRelOpts[] =
{{NULL}}
};
+static relopt_ternary ternaryRelOpts[] =
+{
+ {
+ {
+ "vacuum_truncate",
+ "Enables vacuum to truncate empty pages at the end of this table",
+ RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
+ ShareUpdateExclusiveLock
+ },
+ TERNARY_UNSET
+ },
+ /* list terminator */
+ {
+ {
+ NULL
+ }
+ }
+};
+
static relopt_int intRelOpts[] =
{
{
@@ -609,6 +619,13 @@ initialize_reloptions(void)
boolRelOpts[i].gen.lockmode));
j++;
}
+ for (i = 0; ternaryRelOpts[i].gen.name; i++)
+ {
+ Assert(DoLockModesConflict(ternaryRelOpts[i].gen.lockmode,
+ ternaryRelOpts[i].gen.lockmode));
+ j++;
+ }
+
for (i = 0; intRelOpts[i].gen.name; i++)
{
Assert(DoLockModesConflict(intRelOpts[i].gen.lockmode,
@@ -649,6 +666,14 @@ initialize_reloptions(void)
j++;
}
+ for (i = 0; ternaryRelOpts[i].gen.name; i++)
+ {
+ relOpts[j] = &ternaryRelOpts[i].gen;
+ relOpts[j]->type = RELOPT_TYPE_TERNARY;
+ relOpts[j]->namelen = strlen(relOpts[j]->name);
+ j++;
+ }
+
for (i = 0; intRelOpts[i].gen.name; i++)
{
relOpts[j] = &intRelOpts[i].gen;
@@ -809,6 +834,9 @@ allocate_reloption(bits32 kinds, int type, const char *name, const char *desc,
case RELOPT_TYPE_BOOL:
size = sizeof(relopt_bool);
break;
+ case RELOPT_TYPE_TERNARY:
+ size = sizeof(relopt_ternary);
+ break;
case RELOPT_TYPE_INT:
size = sizeof(relopt_int);
break;
@@ -892,6 +920,57 @@ add_local_bool_reloption(local_relopts *relopts, const char *name,
add_local_reloption(relopts, (relopt_gen *) newoption, offset);
}
+/*
+ * init_ternary_reloption
+ * Allocate and initialize a new ternary reloption
+ */
+static relopt_ternary *
+init_ternary_reloption(bits32 kinds, const char *name, const char *desc,
+ pg_ternary default_val, LOCKMODE lockmode)
+{
+ relopt_ternary *newoption;
+
+ newoption = (relopt_ternary *)
+ allocate_reloption(kinds, RELOPT_TYPE_TERNARY, name, desc, lockmode);
+ newoption->default_val = default_val;
+
+ return newoption;
+}
+
+/*
+ * add_ternary_reloption
+ * Add a new ternary reloption
+ */
+void
+add_ternary_reloption(bits32 kinds, const char *name, const char *desc,
+ pg_ternary default_val, LOCKMODE lockmode)
+{
+ relopt_ternary *newoption;
+
+ newoption =
+ init_ternary_reloption(kinds, name, desc, default_val, lockmode);
+
+ add_reloption((relopt_gen *) newoption);
+}
+
+/*
+ * add_local_ternary_reloption
+ * Add a new ternary local reloption
+ *
+ * 'offset' is offset of ternary-typed field.
+ */
+void
+add_local_ternary_reloption(local_relopts *relopts, const char *name,
+ const char *desc, pg_ternary default_val,
+ int offset)
+{
+ relopt_ternary *newoption;
+
+ newoption =
+ init_ternary_reloption(RELOPT_KIND_LOCAL, name, desc, default_val, 0);
+
+ add_local_reloption(relopts, (relopt_gen *) newoption, offset);
+}
/*
* init_real_reloption
@@ -1626,6 +1705,19 @@ parse_one_reloption(relopt_value *option, char *text_str, int text_len,
option->gen->name, value)));
}
break;
+ case RELOPT_TYPE_TERNARY:
+ {
+ bool b;
+
+ parsed = parse_bool(value, &b);
+ option->values.ternary_val = b ? TERNARY_TRUE : TERNARY_FALSE;
+ if (validate && !parsed)
+ ereport(ERROR,
+ (errcode(ERRCODE_INVALID_PARAMETER_VALUE),
+ errmsg("invalid value for ternary option \"%s\": %s",
+ option->gen->name, value)));
+ }
+ break;
case RELOPT_TYPE_INT:
{
relopt_int *optint = (relopt_int *) option->gen;
@@ -1789,17 +1881,6 @@ fillRelOptions(void *rdopts, Size basesize,
char *itempos = ((char *) rdopts) + elems[j].offset;
char *string_val;
- /*
- * If isset_offset is provided, store whether the reloption is
- * set there.
- */
- if (elems[j].isset_offset > 0)
- {
- char *setpos = ((char *) rdopts) + elems[j].isset_offset;
-
- *(bool *) setpos = options[i].isset;
- }
-
switch (options[i].gen->type)
{
case RELOPT_TYPE_BOOL:
@@ -1807,6 +1888,11 @@ fillRelOptions(void *rdopts, Size basesize,
options[i].values.bool_val :
((relopt_bool *) options[i].gen)->default_val;
break;
+ case RELOPT_TYPE_TERNARY:
+ *(pg_ternary *) itempos = options[i].isset ?
+ options[i].values.ternary_val :
+ ((relopt_ternary *) options[i].gen)->default_val;
+ break;
case RELOPT_TYPE_INT:
*(int *) itempos = options[i].isset ?
options[i].values.int_val :
@@ -1923,8 +2009,8 @@ default_reloptions(Datum reloptions, bool validate, relopt_kind kind)
offsetof(StdRdOptions, parallel_workers)},
{"vacuum_index_cleanup", RELOPT_TYPE_ENUM,
offsetof(StdRdOptions, vacuum_index_cleanup)},
- {"vacuum_truncate", RELOPT_TYPE_BOOL,
- offsetof(StdRdOptions, vacuum_truncate), offsetof(StdRdOptions, vacuum_truncate_set)},
+ {"vacuum_truncate", RELOPT_TYPE_TERNARY,
+ offsetof(StdRdOptions, vacuum_truncate)},
{"vacuum_max_eager_freeze_failure_rate", RELOPT_TYPE_REAL,
offsetof(StdRdOptions, vacuum_max_eager_freeze_failure_rate)}
};
@@ -2004,7 +2090,6 @@ build_local_reloptions(local_relopts *relopts, Datum options, bool validate)
elems[i].optname = opt->option->name;
elems[i].opttype = opt->option->type;
elems[i].offset = opt->offset;
- elems[i].isset_offset = 0; /* not supported for local relopts yet */
i++;
}
diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c
index aa4fbec143f..696eab9bd97 100644
--- a/src/backend/commands/vacuum.c
+++ b/src/backend/commands/vacuum.c
@@ -2224,9 +2224,9 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams params,
{
StdRdOptions *opts = (StdRdOptions *) rel->rd_options;
- if (opts && opts->vacuum_truncate_set)
+ if (opts && opts->vacuum_truncate != TERNARY_UNSET)
{
- if (opts->vacuum_truncate)
+ if (opts->vacuum_truncate == TERNARY_TRUE)
params.truncate = VACOPTVALUE_ENABLED;
else
params.truncate = VACOPTVALUE_DISABLED;
diff --git a/src/include/access/reloptions.h b/src/include/access/reloptions.h
index 2f08e1b0cf0..dfbef2babf2 100644
--- a/src/include/access/reloptions.h
+++ b/src/include/access/reloptions.h
@@ -29,6 +29,7 @@
typedef enum relopt_type
{
RELOPT_TYPE_BOOL,
+ RELOPT_TYPE_TERNARY, /* on, off, unset */
RELOPT_TYPE_INT,
RELOPT_TYPE_REAL,
RELOPT_TYPE_ENUM,
@@ -80,6 +81,7 @@ typedef struct relopt_value
union
{
bool bool_val;
+ pg_ternary ternary_val;
int int_val;
double real_val;
int enum_val;
@@ -94,6 +96,12 @@ typedef struct relopt_bool
bool default_val;
} relopt_bool;
+typedef struct relopt_ternary
+{
+ relopt_gen gen;
+ int default_val;
+} relopt_ternary;
+
typedef struct relopt_int
{
relopt_gen gen;
@@ -152,19 +160,6 @@ typedef struct
const char *optname; /* option's name */
relopt_type opttype; /* option's datatype */
int offset; /* offset of field in result struct */
-
- /*
- * isset_offset is an optional offset of a field in the result struct that
- * stores whether the option is explicitly set for the relation or if it
- * just picked up the default value. In most cases, this can be
- * accomplished by giving the reloption a special out-of-range default
- * value (e.g., some integer reloptions use -2), but this isn't always
- * possible. For example, a Boolean reloption cannot be given an
- * out-of-range default, so we need another way to discover the source of
- * its value. This offset is only used if given a value greater than
- * zero.
- */
- int isset_offset;
} relopt_parse_elt;
/* Local reloption definition */
@@ -195,6 +190,9 @@ typedef struct local_relopts
extern relopt_kind add_reloption_kind(void);
extern void add_bool_reloption(bits32 kinds, const char *name, const char *desc,
bool default_val, LOCKMODE lockmode);
+extern void add_ternary_reloption(bits32 kinds, const char *name,
+ const char *desc, pg_ternary default_val,
+ LOCKMODE lockmode);
extern void add_int_reloption(bits32 kinds, const char *name, const char *desc,
int default_val, int min_val, int max_val,
LOCKMODE lockmode);
@@ -214,6 +212,9 @@ extern void register_reloptions_validator(local_relopts *relopts,
extern void add_local_bool_reloption(local_relopts *relopts, const char *name,
const char *desc, bool default_val,
int offset);
+extern void add_local_ternary_reloption(local_relopts *relopts,
+ const char *name, const char *desc,
+ pg_ternary default_val, int offset);
extern void add_local_int_reloption(local_relopts *relopts, const char *name,
const char *desc, int default_val,
int min_val, int max_val, int offset);
diff --git a/src/include/postgres.h b/src/include/postgres.h
index 7d93fbce709..47c88780776 100644
--- a/src/include/postgres.h
+++ b/src/include/postgres.h
@@ -543,6 +543,21 @@ Float8GetDatum(float8 X)
* ----------------------------------------------------------------
*/
+/*
+ * pg_ternary
+ * Boolean value with an extra "unset" value
+ *
+ * This enum can be used for values that want to distinguish between true,
+ * false, and unset.
+*/
+
+typedef enum pg_ternary
+{
+ TERNARY_FALSE = 0,
+ TERNARY_TRUE = 1,
+ TERNARY_UNSET = -1
+} pg_ternary;
+
/*
* NON_EXEC_STATIC: It's sometimes useful to define a variable or function
* that is normally static but extern when using EXEC_BACKEND (see
diff --git a/src/include/utils/rel.h b/src/include/utils/rel.h
index d03ab247788..5feb18a5373 100644
--- a/src/include/utils/rel.h
+++ b/src/include/utils/rel.h
@@ -347,8 +347,7 @@ typedef struct StdRdOptions
bool user_catalog_table; /* use as an additional catalog relation */
int parallel_workers; /* max number of parallel workers */
StdRdOptIndexCleanup vacuum_index_cleanup; /* controls index vacuuming */
- bool vacuum_truncate; /* enables vacuum to truncate a relation */
- bool vacuum_truncate_set; /* whether vacuum_truncate is set */
+ pg_ternary vacuum_truncate; /* enables vacuum to truncate a relation */
/*
* Fraction of pages in a relation that vacuum can eagerly scan and fail
diff --git a/src/test/regress/expected/reloptions.out b/src/test/regress/expected/reloptions.out
index 9de19b4e3f1..1c99f79ab01 100644
--- a/src/test/regress/expected/reloptions.out
+++ b/src/test/regress/expected/reloptions.out
@@ -98,6 +98,42 @@ SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass;
{fillfactor=13,autovacuum_enabled=false}
(1 row)
+-- Tests for future (FIXME) ternary options
+-- behave as boolean option: accept unassigned name and truncated value
+DROP TABLE reloptions_test;
+CREATE TABLE reloptions_test(i INT) WITH (vacuum_truncate);
+SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass;
+ reloptions
+------------------------
+ {vacuum_truncate=true}
+(1 row)
+
+DROP TABLE reloptions_test;
+CREATE TABLE reloptions_test(i INT) WITH (vacuum_truncate=FaLS);
+SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass;
+ reloptions
+------------------------
+ {vacuum_truncate=fals}
+(1 row)
+
+-- preferred "true" alias is used when storing
+DROP TABLE reloptions_test;
+CREATE TABLE reloptions_test(i INT) WITH (vacuum_index_cleanup=on);
+SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass;
+ reloptions
+---------------------------
+ {vacuum_index_cleanup=on}
+(1 row)
+
+-- custom "third" value is available
+DROP TABLE reloptions_test;
+CREATE TABLE reloptions_test(i INT) WITH (vacuum_index_cleanup=auto);
+SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass;
+ reloptions
+-----------------------------
+ {vacuum_index_cleanup=auto}
+(1 row)
+
-- Test vacuum_truncate option
DROP TABLE reloptions_test;
CREATE TEMP TABLE reloptions_test(i INT NOT NULL, j text)
diff --git a/src/test/regress/sql/reloptions.sql b/src/test/regress/sql/reloptions.sql
index 24fbe0b478d..f5980dafcbc 100644
--- a/src/test/regress/sql/reloptions.sql
+++ b/src/test/regress/sql/reloptions.sql
@@ -59,6 +59,27 @@ UPDATE pg_class
ALTER TABLE reloptions_test RESET (illegal_option);
SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass;
+-- Tests for future (FIXME) ternary options
+
+-- behave as boolean option: accept unassigned name and truncated value
+DROP TABLE reloptions_test;
+CREATE TABLE reloptions_test(i INT) WITH (vacuum_truncate);
+SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass;
+
+DROP TABLE reloptions_test;
+CREATE TABLE reloptions_test(i INT) WITH (vacuum_truncate=FaLS);
+SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass;
+
+-- preferred "true" alias is used when storing
+DROP TABLE reloptions_test;
+CREATE TABLE reloptions_test(i INT) WITH (vacuum_index_cleanup=on);
+SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass;
+
+-- custom "third" value is available
+DROP TABLE reloptions_test;
+CREATE TABLE reloptions_test(i INT) WITH (vacuum_index_cleanup=auto);
+SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass;
+
-- Test vacuum_truncate option
DROP TABLE reloptions_test;
diff --git a/src/tools/pgindent/typedefs.list b/src/tools/pgindent/typedefs.list
index 3f3a888fd0e..1c8610fd46c 100644
--- a/src/tools/pgindent/typedefs.list
+++ b/src/tools/pgindent/typedefs.list
@@ -3952,6 +3952,7 @@ pg_sha512_ctx
pg_snapshot
pg_special_case
pg_stack_base_t
+pg_ternary
pg_time_t
pg_time_usec_t
pg_tz
@@ -4079,6 +4080,7 @@ relopt_kind
relopt_parse_elt
relopt_real
relopt_string
+relopt_ternary
relopt_type
relopt_value
relopts_validator
--
2.47.3
--c36t3fndxhkzbl5z--
^ permalink raw reply [nested|flat] 868+ messages in thread
* [PATCH v3] Introduce ternary reloptions
@ 2026-01-16 15:04 Álvaro Herrera <[email protected]>
0 siblings, 0 replies; 868+ messages in thread
From: Álvaro Herrera @ 2026-01-16 15:04 UTC (permalink / raw)
Introduce ternary reloption as a replacement for current
`vacuum_truncate` implementation. Remove the `vacuum_truncate_set`
separate flag and use TERNARY_UNSET instead.
This could also be used for other options such as `vacuum_index_cleanup`
and `buffering`, but lets get the scaffolding in first.
Discussion: https://postgr.es/m/3474141.usfYGdeWWP@thinkpad-pgpro
---
src/backend/access/common/reloptions.c | 137 ++++++++++++++++++-----
src/backend/commands/vacuum.c | 4 +-
src/include/access/reloptions.h | 27 ++---
src/include/postgres.h | 15 +++
src/include/utils/rel.h | 3 +-
src/test/regress/expected/reloptions.out | 36 ++++++
src/test/regress/sql/reloptions.sql | 21 ++++
src/tools/pgindent/typedefs.list | 2 +
8 files changed, 202 insertions(+), 43 deletions(-)
diff --git a/src/backend/access/common/reloptions.c b/src/backend/access/common/reloptions.c
index 0b83f98ed5f..6f1f577581a 100644
--- a/src/backend/access/common/reloptions.c
+++ b/src/backend/access/common/reloptions.c
@@ -40,9 +40,9 @@
*
* To add an option:
*
- * (i) decide on a type (bool, integer, real, enum, string), name, default
- * value, upper and lower bounds (if applicable); for strings, consider a
- * validation routine.
+ * (i) decide on a type (bool, ternary, integer, real, enum, string), name,
+ * default value, upper and lower bounds (if applicable); for strings,
+ * consider a validation routine.
* (ii) add a record below (or use add_<type>_reloption).
* (iii) add it to the appropriate options struct (perhaps StdRdOptions)
* (iv) add it to the appropriate handling routine (perhaps
@@ -147,15 +147,6 @@ static relopt_bool boolRelOpts[] =
},
false
},
- {
- {
- "vacuum_truncate",
- "Enables vacuum to truncate empty pages at the end of this table",
- RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
- ShareUpdateExclusiveLock
- },
- true
- },
{
{
"deduplicate_items",
@@ -170,6 +161,25 @@ static relopt_bool boolRelOpts[] =
{{NULL}}
};
+static relopt_ternary ternaryRelOpts[] =
+{
+ {
+ {
+ "vacuum_truncate",
+ "Enables vacuum to truncate empty pages at the end of this table",
+ RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
+ ShareUpdateExclusiveLock
+ },
+ TERNARY_UNSET
+ },
+ /* list terminator */
+ {
+ {
+ NULL
+ }
+ }
+};
+
static relopt_int intRelOpts[] =
{
{
@@ -609,6 +619,13 @@ initialize_reloptions(void)
boolRelOpts[i].gen.lockmode));
j++;
}
+ for (i = 0; ternaryRelOpts[i].gen.name; i++)
+ {
+ Assert(DoLockModesConflict(ternaryRelOpts[i].gen.lockmode,
+ ternaryRelOpts[i].gen.lockmode));
+ j++;
+ }
+
for (i = 0; intRelOpts[i].gen.name; i++)
{
Assert(DoLockModesConflict(intRelOpts[i].gen.lockmode,
@@ -649,6 +666,14 @@ initialize_reloptions(void)
j++;
}
+ for (i = 0; ternaryRelOpts[i].gen.name; i++)
+ {
+ relOpts[j] = &ternaryRelOpts[i].gen;
+ relOpts[j]->type = RELOPT_TYPE_TERNARY;
+ relOpts[j]->namelen = strlen(relOpts[j]->name);
+ j++;
+ }
+
for (i = 0; intRelOpts[i].gen.name; i++)
{
relOpts[j] = &intRelOpts[i].gen;
@@ -809,6 +834,9 @@ allocate_reloption(bits32 kinds, int type, const char *name, const char *desc,
case RELOPT_TYPE_BOOL:
size = sizeof(relopt_bool);
break;
+ case RELOPT_TYPE_TERNARY:
+ size = sizeof(relopt_ternary);
+ break;
case RELOPT_TYPE_INT:
size = sizeof(relopt_int);
break;
@@ -892,6 +920,57 @@ add_local_bool_reloption(local_relopts *relopts, const char *name,
add_local_reloption(relopts, (relopt_gen *) newoption, offset);
}
+/*
+ * init_ternary_reloption
+ * Allocate and initialize a new ternary reloption
+ */
+static relopt_ternary *
+init_ternary_reloption(bits32 kinds, const char *name, const char *desc,
+ pg_ternary default_val, LOCKMODE lockmode)
+{
+ relopt_ternary *newoption;
+
+ newoption = (relopt_ternary *)
+ allocate_reloption(kinds, RELOPT_TYPE_TERNARY, name, desc, lockmode);
+ newoption->default_val = default_val;
+
+ return newoption;
+}
+
+/*
+ * add_ternary_reloption
+ * Add a new ternary reloption
+ */
+void
+add_ternary_reloption(bits32 kinds, const char *name, const char *desc,
+ pg_ternary default_val, LOCKMODE lockmode)
+{
+ relopt_ternary *newoption;
+
+ newoption =
+ init_ternary_reloption(kinds, name, desc, default_val, lockmode);
+
+ add_reloption((relopt_gen *) newoption);
+}
+
+/*
+ * add_local_ternary_reloption
+ * Add a new ternary local reloption
+ *
+ * 'offset' is offset of ternary-typed field.
+ */
+void
+add_local_ternary_reloption(local_relopts *relopts, const char *name,
+ const char *desc, pg_ternary default_val,
+ int offset)
+{
+ relopt_ternary *newoption;
+
+ newoption =
+ init_ternary_reloption(RELOPT_KIND_LOCAL, name, desc, default_val, 0);
+
+ add_local_reloption(relopts, (relopt_gen *) newoption, offset);
+}
/*
* init_real_reloption
@@ -1626,6 +1705,19 @@ parse_one_reloption(relopt_value *option, char *text_str, int text_len,
option->gen->name, value)));
}
break;
+ case RELOPT_TYPE_TERNARY:
+ {
+ bool b;
+
+ parsed = parse_bool(value, &b);
+ option->values.ternary_val = b ? TERNARY_TRUE : TERNARY_FALSE;
+ if (validate && !parsed)
+ ereport(ERROR,
+ (errcode(ERRCODE_INVALID_PARAMETER_VALUE),
+ errmsg("invalid value for ternary option \"%s\": %s",
+ option->gen->name, value)));
+ }
+ break;
case RELOPT_TYPE_INT:
{
relopt_int *optint = (relopt_int *) option->gen;
@@ -1789,17 +1881,6 @@ fillRelOptions(void *rdopts, Size basesize,
char *itempos = ((char *) rdopts) + elems[j].offset;
char *string_val;
- /*
- * If isset_offset is provided, store whether the reloption is
- * set there.
- */
- if (elems[j].isset_offset > 0)
- {
- char *setpos = ((char *) rdopts) + elems[j].isset_offset;
-
- *(bool *) setpos = options[i].isset;
- }
-
switch (options[i].gen->type)
{
case RELOPT_TYPE_BOOL:
@@ -1807,6 +1888,11 @@ fillRelOptions(void *rdopts, Size basesize,
options[i].values.bool_val :
((relopt_bool *) options[i].gen)->default_val;
break;
+ case RELOPT_TYPE_TERNARY:
+ *(pg_ternary *) itempos = options[i].isset ?
+ options[i].values.ternary_val :
+ ((relopt_ternary *) options[i].gen)->default_val;
+ break;
case RELOPT_TYPE_INT:
*(int *) itempos = options[i].isset ?
options[i].values.int_val :
@@ -1923,8 +2009,8 @@ default_reloptions(Datum reloptions, bool validate, relopt_kind kind)
offsetof(StdRdOptions, parallel_workers)},
{"vacuum_index_cleanup", RELOPT_TYPE_ENUM,
offsetof(StdRdOptions, vacuum_index_cleanup)},
- {"vacuum_truncate", RELOPT_TYPE_BOOL,
- offsetof(StdRdOptions, vacuum_truncate), offsetof(StdRdOptions, vacuum_truncate_set)},
+ {"vacuum_truncate", RELOPT_TYPE_TERNARY,
+ offsetof(StdRdOptions, vacuum_truncate)},
{"vacuum_max_eager_freeze_failure_rate", RELOPT_TYPE_REAL,
offsetof(StdRdOptions, vacuum_max_eager_freeze_failure_rate)}
};
@@ -2004,7 +2090,6 @@ build_local_reloptions(local_relopts *relopts, Datum options, bool validate)
elems[i].optname = opt->option->name;
elems[i].opttype = opt->option->type;
elems[i].offset = opt->offset;
- elems[i].isset_offset = 0; /* not supported for local relopts yet */
i++;
}
diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c
index aa4fbec143f..696eab9bd97 100644
--- a/src/backend/commands/vacuum.c
+++ b/src/backend/commands/vacuum.c
@@ -2224,9 +2224,9 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams params,
{
StdRdOptions *opts = (StdRdOptions *) rel->rd_options;
- if (opts && opts->vacuum_truncate_set)
+ if (opts && opts->vacuum_truncate != TERNARY_UNSET)
{
- if (opts->vacuum_truncate)
+ if (opts->vacuum_truncate == TERNARY_TRUE)
params.truncate = VACOPTVALUE_ENABLED;
else
params.truncate = VACOPTVALUE_DISABLED;
diff --git a/src/include/access/reloptions.h b/src/include/access/reloptions.h
index 2f08e1b0cf0..dfbef2babf2 100644
--- a/src/include/access/reloptions.h
+++ b/src/include/access/reloptions.h
@@ -29,6 +29,7 @@
typedef enum relopt_type
{
RELOPT_TYPE_BOOL,
+ RELOPT_TYPE_TERNARY, /* on, off, unset */
RELOPT_TYPE_INT,
RELOPT_TYPE_REAL,
RELOPT_TYPE_ENUM,
@@ -80,6 +81,7 @@ typedef struct relopt_value
union
{
bool bool_val;
+ pg_ternary ternary_val;
int int_val;
double real_val;
int enum_val;
@@ -94,6 +96,12 @@ typedef struct relopt_bool
bool default_val;
} relopt_bool;
+typedef struct relopt_ternary
+{
+ relopt_gen gen;
+ int default_val;
+} relopt_ternary;
+
typedef struct relopt_int
{
relopt_gen gen;
@@ -152,19 +160,6 @@ typedef struct
const char *optname; /* option's name */
relopt_type opttype; /* option's datatype */
int offset; /* offset of field in result struct */
-
- /*
- * isset_offset is an optional offset of a field in the result struct that
- * stores whether the option is explicitly set for the relation or if it
- * just picked up the default value. In most cases, this can be
- * accomplished by giving the reloption a special out-of-range default
- * value (e.g., some integer reloptions use -2), but this isn't always
- * possible. For example, a Boolean reloption cannot be given an
- * out-of-range default, so we need another way to discover the source of
- * its value. This offset is only used if given a value greater than
- * zero.
- */
- int isset_offset;
} relopt_parse_elt;
/* Local reloption definition */
@@ -195,6 +190,9 @@ typedef struct local_relopts
extern relopt_kind add_reloption_kind(void);
extern void add_bool_reloption(bits32 kinds, const char *name, const char *desc,
bool default_val, LOCKMODE lockmode);
+extern void add_ternary_reloption(bits32 kinds, const char *name,
+ const char *desc, pg_ternary default_val,
+ LOCKMODE lockmode);
extern void add_int_reloption(bits32 kinds, const char *name, const char *desc,
int default_val, int min_val, int max_val,
LOCKMODE lockmode);
@@ -214,6 +212,9 @@ extern void register_reloptions_validator(local_relopts *relopts,
extern void add_local_bool_reloption(local_relopts *relopts, const char *name,
const char *desc, bool default_val,
int offset);
+extern void add_local_ternary_reloption(local_relopts *relopts,
+ const char *name, const char *desc,
+ pg_ternary default_val, int offset);
extern void add_local_int_reloption(local_relopts *relopts, const char *name,
const char *desc, int default_val,
int min_val, int max_val, int offset);
diff --git a/src/include/postgres.h b/src/include/postgres.h
index 7d93fbce709..47c88780776 100644
--- a/src/include/postgres.h
+++ b/src/include/postgres.h
@@ -543,6 +543,21 @@ Float8GetDatum(float8 X)
* ----------------------------------------------------------------
*/
+/*
+ * pg_ternary
+ * Boolean value with an extra "unset" value
+ *
+ * This enum can be used for values that want to distinguish between true,
+ * false, and unset.
+*/
+
+typedef enum pg_ternary
+{
+ TERNARY_FALSE = 0,
+ TERNARY_TRUE = 1,
+ TERNARY_UNSET = -1
+} pg_ternary;
+
/*
* NON_EXEC_STATIC: It's sometimes useful to define a variable or function
* that is normally static but extern when using EXEC_BACKEND (see
diff --git a/src/include/utils/rel.h b/src/include/utils/rel.h
index d03ab247788..5feb18a5373 100644
--- a/src/include/utils/rel.h
+++ b/src/include/utils/rel.h
@@ -347,8 +347,7 @@ typedef struct StdRdOptions
bool user_catalog_table; /* use as an additional catalog relation */
int parallel_workers; /* max number of parallel workers */
StdRdOptIndexCleanup vacuum_index_cleanup; /* controls index vacuuming */
- bool vacuum_truncate; /* enables vacuum to truncate a relation */
- bool vacuum_truncate_set; /* whether vacuum_truncate is set */
+ pg_ternary vacuum_truncate; /* enables vacuum to truncate a relation */
/*
* Fraction of pages in a relation that vacuum can eagerly scan and fail
diff --git a/src/test/regress/expected/reloptions.out b/src/test/regress/expected/reloptions.out
index 9de19b4e3f1..1c99f79ab01 100644
--- a/src/test/regress/expected/reloptions.out
+++ b/src/test/regress/expected/reloptions.out
@@ -98,6 +98,42 @@ SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass;
{fillfactor=13,autovacuum_enabled=false}
(1 row)
+-- Tests for future (FIXME) ternary options
+-- behave as boolean option: accept unassigned name and truncated value
+DROP TABLE reloptions_test;
+CREATE TABLE reloptions_test(i INT) WITH (vacuum_truncate);
+SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass;
+ reloptions
+------------------------
+ {vacuum_truncate=true}
+(1 row)
+
+DROP TABLE reloptions_test;
+CREATE TABLE reloptions_test(i INT) WITH (vacuum_truncate=FaLS);
+SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass;
+ reloptions
+------------------------
+ {vacuum_truncate=fals}
+(1 row)
+
+-- preferred "true" alias is used when storing
+DROP TABLE reloptions_test;
+CREATE TABLE reloptions_test(i INT) WITH (vacuum_index_cleanup=on);
+SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass;
+ reloptions
+---------------------------
+ {vacuum_index_cleanup=on}
+(1 row)
+
+-- custom "third" value is available
+DROP TABLE reloptions_test;
+CREATE TABLE reloptions_test(i INT) WITH (vacuum_index_cleanup=auto);
+SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass;
+ reloptions
+-----------------------------
+ {vacuum_index_cleanup=auto}
+(1 row)
+
-- Test vacuum_truncate option
DROP TABLE reloptions_test;
CREATE TEMP TABLE reloptions_test(i INT NOT NULL, j text)
diff --git a/src/test/regress/sql/reloptions.sql b/src/test/regress/sql/reloptions.sql
index 24fbe0b478d..f5980dafcbc 100644
--- a/src/test/regress/sql/reloptions.sql
+++ b/src/test/regress/sql/reloptions.sql
@@ -59,6 +59,27 @@ UPDATE pg_class
ALTER TABLE reloptions_test RESET (illegal_option);
SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass;
+-- Tests for future (FIXME) ternary options
+
+-- behave as boolean option: accept unassigned name and truncated value
+DROP TABLE reloptions_test;
+CREATE TABLE reloptions_test(i INT) WITH (vacuum_truncate);
+SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass;
+
+DROP TABLE reloptions_test;
+CREATE TABLE reloptions_test(i INT) WITH (vacuum_truncate=FaLS);
+SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass;
+
+-- preferred "true" alias is used when storing
+DROP TABLE reloptions_test;
+CREATE TABLE reloptions_test(i INT) WITH (vacuum_index_cleanup=on);
+SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass;
+
+-- custom "third" value is available
+DROP TABLE reloptions_test;
+CREATE TABLE reloptions_test(i INT) WITH (vacuum_index_cleanup=auto);
+SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass;
+
-- Test vacuum_truncate option
DROP TABLE reloptions_test;
diff --git a/src/tools/pgindent/typedefs.list b/src/tools/pgindent/typedefs.list
index 3f3a888fd0e..1c8610fd46c 100644
--- a/src/tools/pgindent/typedefs.list
+++ b/src/tools/pgindent/typedefs.list
@@ -3952,6 +3952,7 @@ pg_sha512_ctx
pg_snapshot
pg_special_case
pg_stack_base_t
+pg_ternary
pg_time_t
pg_time_usec_t
pg_tz
@@ -4079,6 +4080,7 @@ relopt_kind
relopt_parse_elt
relopt_real
relopt_string
+relopt_ternary
relopt_type
relopt_value
relopts_validator
--
2.47.3
--c36t3fndxhkzbl5z--
^ permalink raw reply [nested|flat] 868+ messages in thread
* [PATCH v3] Introduce ternary reloptions
@ 2026-01-16 15:04 Álvaro Herrera <[email protected]>
0 siblings, 0 replies; 868+ messages in thread
From: Álvaro Herrera @ 2026-01-16 15:04 UTC (permalink / raw)
Introduce ternary reloption as a replacement for current
`vacuum_truncate` implementation. Remove the `vacuum_truncate_set`
separate flag and use TERNARY_UNSET instead.
This could also be used for other options such as `vacuum_index_cleanup`
and `buffering`, but lets get the scaffolding in first.
Discussion: https://postgr.es/m/3474141.usfYGdeWWP@thinkpad-pgpro
---
src/backend/access/common/reloptions.c | 137 ++++++++++++++++++-----
src/backend/commands/vacuum.c | 4 +-
src/include/access/reloptions.h | 27 ++---
src/include/postgres.h | 15 +++
src/include/utils/rel.h | 3 +-
src/test/regress/expected/reloptions.out | 36 ++++++
src/test/regress/sql/reloptions.sql | 21 ++++
src/tools/pgindent/typedefs.list | 2 +
8 files changed, 202 insertions(+), 43 deletions(-)
diff --git a/src/backend/access/common/reloptions.c b/src/backend/access/common/reloptions.c
index 0b83f98ed5f..6f1f577581a 100644
--- a/src/backend/access/common/reloptions.c
+++ b/src/backend/access/common/reloptions.c
@@ -40,9 +40,9 @@
*
* To add an option:
*
- * (i) decide on a type (bool, integer, real, enum, string), name, default
- * value, upper and lower bounds (if applicable); for strings, consider a
- * validation routine.
+ * (i) decide on a type (bool, ternary, integer, real, enum, string), name,
+ * default value, upper and lower bounds (if applicable); for strings,
+ * consider a validation routine.
* (ii) add a record below (or use add_<type>_reloption).
* (iii) add it to the appropriate options struct (perhaps StdRdOptions)
* (iv) add it to the appropriate handling routine (perhaps
@@ -147,15 +147,6 @@ static relopt_bool boolRelOpts[] =
},
false
},
- {
- {
- "vacuum_truncate",
- "Enables vacuum to truncate empty pages at the end of this table",
- RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
- ShareUpdateExclusiveLock
- },
- true
- },
{
{
"deduplicate_items",
@@ -170,6 +161,25 @@ static relopt_bool boolRelOpts[] =
{{NULL}}
};
+static relopt_ternary ternaryRelOpts[] =
+{
+ {
+ {
+ "vacuum_truncate",
+ "Enables vacuum to truncate empty pages at the end of this table",
+ RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
+ ShareUpdateExclusiveLock
+ },
+ TERNARY_UNSET
+ },
+ /* list terminator */
+ {
+ {
+ NULL
+ }
+ }
+};
+
static relopt_int intRelOpts[] =
{
{
@@ -609,6 +619,13 @@ initialize_reloptions(void)
boolRelOpts[i].gen.lockmode));
j++;
}
+ for (i = 0; ternaryRelOpts[i].gen.name; i++)
+ {
+ Assert(DoLockModesConflict(ternaryRelOpts[i].gen.lockmode,
+ ternaryRelOpts[i].gen.lockmode));
+ j++;
+ }
+
for (i = 0; intRelOpts[i].gen.name; i++)
{
Assert(DoLockModesConflict(intRelOpts[i].gen.lockmode,
@@ -649,6 +666,14 @@ initialize_reloptions(void)
j++;
}
+ for (i = 0; ternaryRelOpts[i].gen.name; i++)
+ {
+ relOpts[j] = &ternaryRelOpts[i].gen;
+ relOpts[j]->type = RELOPT_TYPE_TERNARY;
+ relOpts[j]->namelen = strlen(relOpts[j]->name);
+ j++;
+ }
+
for (i = 0; intRelOpts[i].gen.name; i++)
{
relOpts[j] = &intRelOpts[i].gen;
@@ -809,6 +834,9 @@ allocate_reloption(bits32 kinds, int type, const char *name, const char *desc,
case RELOPT_TYPE_BOOL:
size = sizeof(relopt_bool);
break;
+ case RELOPT_TYPE_TERNARY:
+ size = sizeof(relopt_ternary);
+ break;
case RELOPT_TYPE_INT:
size = sizeof(relopt_int);
break;
@@ -892,6 +920,57 @@ add_local_bool_reloption(local_relopts *relopts, const char *name,
add_local_reloption(relopts, (relopt_gen *) newoption, offset);
}
+/*
+ * init_ternary_reloption
+ * Allocate and initialize a new ternary reloption
+ */
+static relopt_ternary *
+init_ternary_reloption(bits32 kinds, const char *name, const char *desc,
+ pg_ternary default_val, LOCKMODE lockmode)
+{
+ relopt_ternary *newoption;
+
+ newoption = (relopt_ternary *)
+ allocate_reloption(kinds, RELOPT_TYPE_TERNARY, name, desc, lockmode);
+ newoption->default_val = default_val;
+
+ return newoption;
+}
+
+/*
+ * add_ternary_reloption
+ * Add a new ternary reloption
+ */
+void
+add_ternary_reloption(bits32 kinds, const char *name, const char *desc,
+ pg_ternary default_val, LOCKMODE lockmode)
+{
+ relopt_ternary *newoption;
+
+ newoption =
+ init_ternary_reloption(kinds, name, desc, default_val, lockmode);
+
+ add_reloption((relopt_gen *) newoption);
+}
+
+/*
+ * add_local_ternary_reloption
+ * Add a new ternary local reloption
+ *
+ * 'offset' is offset of ternary-typed field.
+ */
+void
+add_local_ternary_reloption(local_relopts *relopts, const char *name,
+ const char *desc, pg_ternary default_val,
+ int offset)
+{
+ relopt_ternary *newoption;
+
+ newoption =
+ init_ternary_reloption(RELOPT_KIND_LOCAL, name, desc, default_val, 0);
+
+ add_local_reloption(relopts, (relopt_gen *) newoption, offset);
+}
/*
* init_real_reloption
@@ -1626,6 +1705,19 @@ parse_one_reloption(relopt_value *option, char *text_str, int text_len,
option->gen->name, value)));
}
break;
+ case RELOPT_TYPE_TERNARY:
+ {
+ bool b;
+
+ parsed = parse_bool(value, &b);
+ option->values.ternary_val = b ? TERNARY_TRUE : TERNARY_FALSE;
+ if (validate && !parsed)
+ ereport(ERROR,
+ (errcode(ERRCODE_INVALID_PARAMETER_VALUE),
+ errmsg("invalid value for ternary option \"%s\": %s",
+ option->gen->name, value)));
+ }
+ break;
case RELOPT_TYPE_INT:
{
relopt_int *optint = (relopt_int *) option->gen;
@@ -1789,17 +1881,6 @@ fillRelOptions(void *rdopts, Size basesize,
char *itempos = ((char *) rdopts) + elems[j].offset;
char *string_val;
- /*
- * If isset_offset is provided, store whether the reloption is
- * set there.
- */
- if (elems[j].isset_offset > 0)
- {
- char *setpos = ((char *) rdopts) + elems[j].isset_offset;
-
- *(bool *) setpos = options[i].isset;
- }
-
switch (options[i].gen->type)
{
case RELOPT_TYPE_BOOL:
@@ -1807,6 +1888,11 @@ fillRelOptions(void *rdopts, Size basesize,
options[i].values.bool_val :
((relopt_bool *) options[i].gen)->default_val;
break;
+ case RELOPT_TYPE_TERNARY:
+ *(pg_ternary *) itempos = options[i].isset ?
+ options[i].values.ternary_val :
+ ((relopt_ternary *) options[i].gen)->default_val;
+ break;
case RELOPT_TYPE_INT:
*(int *) itempos = options[i].isset ?
options[i].values.int_val :
@@ -1923,8 +2009,8 @@ default_reloptions(Datum reloptions, bool validate, relopt_kind kind)
offsetof(StdRdOptions, parallel_workers)},
{"vacuum_index_cleanup", RELOPT_TYPE_ENUM,
offsetof(StdRdOptions, vacuum_index_cleanup)},
- {"vacuum_truncate", RELOPT_TYPE_BOOL,
- offsetof(StdRdOptions, vacuum_truncate), offsetof(StdRdOptions, vacuum_truncate_set)},
+ {"vacuum_truncate", RELOPT_TYPE_TERNARY,
+ offsetof(StdRdOptions, vacuum_truncate)},
{"vacuum_max_eager_freeze_failure_rate", RELOPT_TYPE_REAL,
offsetof(StdRdOptions, vacuum_max_eager_freeze_failure_rate)}
};
@@ -2004,7 +2090,6 @@ build_local_reloptions(local_relopts *relopts, Datum options, bool validate)
elems[i].optname = opt->option->name;
elems[i].opttype = opt->option->type;
elems[i].offset = opt->offset;
- elems[i].isset_offset = 0; /* not supported for local relopts yet */
i++;
}
diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c
index aa4fbec143f..696eab9bd97 100644
--- a/src/backend/commands/vacuum.c
+++ b/src/backend/commands/vacuum.c
@@ -2224,9 +2224,9 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams params,
{
StdRdOptions *opts = (StdRdOptions *) rel->rd_options;
- if (opts && opts->vacuum_truncate_set)
+ if (opts && opts->vacuum_truncate != TERNARY_UNSET)
{
- if (opts->vacuum_truncate)
+ if (opts->vacuum_truncate == TERNARY_TRUE)
params.truncate = VACOPTVALUE_ENABLED;
else
params.truncate = VACOPTVALUE_DISABLED;
diff --git a/src/include/access/reloptions.h b/src/include/access/reloptions.h
index 2f08e1b0cf0..dfbef2babf2 100644
--- a/src/include/access/reloptions.h
+++ b/src/include/access/reloptions.h
@@ -29,6 +29,7 @@
typedef enum relopt_type
{
RELOPT_TYPE_BOOL,
+ RELOPT_TYPE_TERNARY, /* on, off, unset */
RELOPT_TYPE_INT,
RELOPT_TYPE_REAL,
RELOPT_TYPE_ENUM,
@@ -80,6 +81,7 @@ typedef struct relopt_value
union
{
bool bool_val;
+ pg_ternary ternary_val;
int int_val;
double real_val;
int enum_val;
@@ -94,6 +96,12 @@ typedef struct relopt_bool
bool default_val;
} relopt_bool;
+typedef struct relopt_ternary
+{
+ relopt_gen gen;
+ int default_val;
+} relopt_ternary;
+
typedef struct relopt_int
{
relopt_gen gen;
@@ -152,19 +160,6 @@ typedef struct
const char *optname; /* option's name */
relopt_type opttype; /* option's datatype */
int offset; /* offset of field in result struct */
-
- /*
- * isset_offset is an optional offset of a field in the result struct that
- * stores whether the option is explicitly set for the relation or if it
- * just picked up the default value. In most cases, this can be
- * accomplished by giving the reloption a special out-of-range default
- * value (e.g., some integer reloptions use -2), but this isn't always
- * possible. For example, a Boolean reloption cannot be given an
- * out-of-range default, so we need another way to discover the source of
- * its value. This offset is only used if given a value greater than
- * zero.
- */
- int isset_offset;
} relopt_parse_elt;
/* Local reloption definition */
@@ -195,6 +190,9 @@ typedef struct local_relopts
extern relopt_kind add_reloption_kind(void);
extern void add_bool_reloption(bits32 kinds, const char *name, const char *desc,
bool default_val, LOCKMODE lockmode);
+extern void add_ternary_reloption(bits32 kinds, const char *name,
+ const char *desc, pg_ternary default_val,
+ LOCKMODE lockmode);
extern void add_int_reloption(bits32 kinds, const char *name, const char *desc,
int default_val, int min_val, int max_val,
LOCKMODE lockmode);
@@ -214,6 +212,9 @@ extern void register_reloptions_validator(local_relopts *relopts,
extern void add_local_bool_reloption(local_relopts *relopts, const char *name,
const char *desc, bool default_val,
int offset);
+extern void add_local_ternary_reloption(local_relopts *relopts,
+ const char *name, const char *desc,
+ pg_ternary default_val, int offset);
extern void add_local_int_reloption(local_relopts *relopts, const char *name,
const char *desc, int default_val,
int min_val, int max_val, int offset);
diff --git a/src/include/postgres.h b/src/include/postgres.h
index 7d93fbce709..47c88780776 100644
--- a/src/include/postgres.h
+++ b/src/include/postgres.h
@@ -543,6 +543,21 @@ Float8GetDatum(float8 X)
* ----------------------------------------------------------------
*/
+/*
+ * pg_ternary
+ * Boolean value with an extra "unset" value
+ *
+ * This enum can be used for values that want to distinguish between true,
+ * false, and unset.
+*/
+
+typedef enum pg_ternary
+{
+ TERNARY_FALSE = 0,
+ TERNARY_TRUE = 1,
+ TERNARY_UNSET = -1
+} pg_ternary;
+
/*
* NON_EXEC_STATIC: It's sometimes useful to define a variable or function
* that is normally static but extern when using EXEC_BACKEND (see
diff --git a/src/include/utils/rel.h b/src/include/utils/rel.h
index d03ab247788..5feb18a5373 100644
--- a/src/include/utils/rel.h
+++ b/src/include/utils/rel.h
@@ -347,8 +347,7 @@ typedef struct StdRdOptions
bool user_catalog_table; /* use as an additional catalog relation */
int parallel_workers; /* max number of parallel workers */
StdRdOptIndexCleanup vacuum_index_cleanup; /* controls index vacuuming */
- bool vacuum_truncate; /* enables vacuum to truncate a relation */
- bool vacuum_truncate_set; /* whether vacuum_truncate is set */
+ pg_ternary vacuum_truncate; /* enables vacuum to truncate a relation */
/*
* Fraction of pages in a relation that vacuum can eagerly scan and fail
diff --git a/src/test/regress/expected/reloptions.out b/src/test/regress/expected/reloptions.out
index 9de19b4e3f1..1c99f79ab01 100644
--- a/src/test/regress/expected/reloptions.out
+++ b/src/test/regress/expected/reloptions.out
@@ -98,6 +98,42 @@ SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass;
{fillfactor=13,autovacuum_enabled=false}
(1 row)
+-- Tests for future (FIXME) ternary options
+-- behave as boolean option: accept unassigned name and truncated value
+DROP TABLE reloptions_test;
+CREATE TABLE reloptions_test(i INT) WITH (vacuum_truncate);
+SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass;
+ reloptions
+------------------------
+ {vacuum_truncate=true}
+(1 row)
+
+DROP TABLE reloptions_test;
+CREATE TABLE reloptions_test(i INT) WITH (vacuum_truncate=FaLS);
+SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass;
+ reloptions
+------------------------
+ {vacuum_truncate=fals}
+(1 row)
+
+-- preferred "true" alias is used when storing
+DROP TABLE reloptions_test;
+CREATE TABLE reloptions_test(i INT) WITH (vacuum_index_cleanup=on);
+SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass;
+ reloptions
+---------------------------
+ {vacuum_index_cleanup=on}
+(1 row)
+
+-- custom "third" value is available
+DROP TABLE reloptions_test;
+CREATE TABLE reloptions_test(i INT) WITH (vacuum_index_cleanup=auto);
+SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass;
+ reloptions
+-----------------------------
+ {vacuum_index_cleanup=auto}
+(1 row)
+
-- Test vacuum_truncate option
DROP TABLE reloptions_test;
CREATE TEMP TABLE reloptions_test(i INT NOT NULL, j text)
diff --git a/src/test/regress/sql/reloptions.sql b/src/test/regress/sql/reloptions.sql
index 24fbe0b478d..f5980dafcbc 100644
--- a/src/test/regress/sql/reloptions.sql
+++ b/src/test/regress/sql/reloptions.sql
@@ -59,6 +59,27 @@ UPDATE pg_class
ALTER TABLE reloptions_test RESET (illegal_option);
SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass;
+-- Tests for future (FIXME) ternary options
+
+-- behave as boolean option: accept unassigned name and truncated value
+DROP TABLE reloptions_test;
+CREATE TABLE reloptions_test(i INT) WITH (vacuum_truncate);
+SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass;
+
+DROP TABLE reloptions_test;
+CREATE TABLE reloptions_test(i INT) WITH (vacuum_truncate=FaLS);
+SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass;
+
+-- preferred "true" alias is used when storing
+DROP TABLE reloptions_test;
+CREATE TABLE reloptions_test(i INT) WITH (vacuum_index_cleanup=on);
+SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass;
+
+-- custom "third" value is available
+DROP TABLE reloptions_test;
+CREATE TABLE reloptions_test(i INT) WITH (vacuum_index_cleanup=auto);
+SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass;
+
-- Test vacuum_truncate option
DROP TABLE reloptions_test;
diff --git a/src/tools/pgindent/typedefs.list b/src/tools/pgindent/typedefs.list
index 3f3a888fd0e..1c8610fd46c 100644
--- a/src/tools/pgindent/typedefs.list
+++ b/src/tools/pgindent/typedefs.list
@@ -3952,6 +3952,7 @@ pg_sha512_ctx
pg_snapshot
pg_special_case
pg_stack_base_t
+pg_ternary
pg_time_t
pg_time_usec_t
pg_tz
@@ -4079,6 +4080,7 @@ relopt_kind
relopt_parse_elt
relopt_real
relopt_string
+relopt_ternary
relopt_type
relopt_value
relopts_validator
--
2.47.3
--c36t3fndxhkzbl5z--
^ permalink raw reply [nested|flat] 868+ messages in thread
* [PATCH v3] Introduce ternary reloptions
@ 2026-01-16 15:04 Álvaro Herrera <[email protected]>
0 siblings, 0 replies; 868+ messages in thread
From: Álvaro Herrera @ 2026-01-16 15:04 UTC (permalink / raw)
Introduce ternary reloption as a replacement for current
`vacuum_truncate` implementation. Remove the `vacuum_truncate_set`
separate flag and use TERNARY_UNSET instead.
This could also be used for other options such as `vacuum_index_cleanup`
and `buffering`, but lets get the scaffolding in first.
Discussion: https://postgr.es/m/3474141.usfYGdeWWP@thinkpad-pgpro
---
src/backend/access/common/reloptions.c | 137 ++++++++++++++++++-----
src/backend/commands/vacuum.c | 4 +-
src/include/access/reloptions.h | 27 ++---
src/include/postgres.h | 15 +++
src/include/utils/rel.h | 3 +-
src/test/regress/expected/reloptions.out | 36 ++++++
src/test/regress/sql/reloptions.sql | 21 ++++
src/tools/pgindent/typedefs.list | 2 +
8 files changed, 202 insertions(+), 43 deletions(-)
diff --git a/src/backend/access/common/reloptions.c b/src/backend/access/common/reloptions.c
index 0b83f98ed5f..6f1f577581a 100644
--- a/src/backend/access/common/reloptions.c
+++ b/src/backend/access/common/reloptions.c
@@ -40,9 +40,9 @@
*
* To add an option:
*
- * (i) decide on a type (bool, integer, real, enum, string), name, default
- * value, upper and lower bounds (if applicable); for strings, consider a
- * validation routine.
+ * (i) decide on a type (bool, ternary, integer, real, enum, string), name,
+ * default value, upper and lower bounds (if applicable); for strings,
+ * consider a validation routine.
* (ii) add a record below (or use add_<type>_reloption).
* (iii) add it to the appropriate options struct (perhaps StdRdOptions)
* (iv) add it to the appropriate handling routine (perhaps
@@ -147,15 +147,6 @@ static relopt_bool boolRelOpts[] =
},
false
},
- {
- {
- "vacuum_truncate",
- "Enables vacuum to truncate empty pages at the end of this table",
- RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
- ShareUpdateExclusiveLock
- },
- true
- },
{
{
"deduplicate_items",
@@ -170,6 +161,25 @@ static relopt_bool boolRelOpts[] =
{{NULL}}
};
+static relopt_ternary ternaryRelOpts[] =
+{
+ {
+ {
+ "vacuum_truncate",
+ "Enables vacuum to truncate empty pages at the end of this table",
+ RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
+ ShareUpdateExclusiveLock
+ },
+ TERNARY_UNSET
+ },
+ /* list terminator */
+ {
+ {
+ NULL
+ }
+ }
+};
+
static relopt_int intRelOpts[] =
{
{
@@ -609,6 +619,13 @@ initialize_reloptions(void)
boolRelOpts[i].gen.lockmode));
j++;
}
+ for (i = 0; ternaryRelOpts[i].gen.name; i++)
+ {
+ Assert(DoLockModesConflict(ternaryRelOpts[i].gen.lockmode,
+ ternaryRelOpts[i].gen.lockmode));
+ j++;
+ }
+
for (i = 0; intRelOpts[i].gen.name; i++)
{
Assert(DoLockModesConflict(intRelOpts[i].gen.lockmode,
@@ -649,6 +666,14 @@ initialize_reloptions(void)
j++;
}
+ for (i = 0; ternaryRelOpts[i].gen.name; i++)
+ {
+ relOpts[j] = &ternaryRelOpts[i].gen;
+ relOpts[j]->type = RELOPT_TYPE_TERNARY;
+ relOpts[j]->namelen = strlen(relOpts[j]->name);
+ j++;
+ }
+
for (i = 0; intRelOpts[i].gen.name; i++)
{
relOpts[j] = &intRelOpts[i].gen;
@@ -809,6 +834,9 @@ allocate_reloption(bits32 kinds, int type, const char *name, const char *desc,
case RELOPT_TYPE_BOOL:
size = sizeof(relopt_bool);
break;
+ case RELOPT_TYPE_TERNARY:
+ size = sizeof(relopt_ternary);
+ break;
case RELOPT_TYPE_INT:
size = sizeof(relopt_int);
break;
@@ -892,6 +920,57 @@ add_local_bool_reloption(local_relopts *relopts, const char *name,
add_local_reloption(relopts, (relopt_gen *) newoption, offset);
}
+/*
+ * init_ternary_reloption
+ * Allocate and initialize a new ternary reloption
+ */
+static relopt_ternary *
+init_ternary_reloption(bits32 kinds, const char *name, const char *desc,
+ pg_ternary default_val, LOCKMODE lockmode)
+{
+ relopt_ternary *newoption;
+
+ newoption = (relopt_ternary *)
+ allocate_reloption(kinds, RELOPT_TYPE_TERNARY, name, desc, lockmode);
+ newoption->default_val = default_val;
+
+ return newoption;
+}
+
+/*
+ * add_ternary_reloption
+ * Add a new ternary reloption
+ */
+void
+add_ternary_reloption(bits32 kinds, const char *name, const char *desc,
+ pg_ternary default_val, LOCKMODE lockmode)
+{
+ relopt_ternary *newoption;
+
+ newoption =
+ init_ternary_reloption(kinds, name, desc, default_val, lockmode);
+
+ add_reloption((relopt_gen *) newoption);
+}
+
+/*
+ * add_local_ternary_reloption
+ * Add a new ternary local reloption
+ *
+ * 'offset' is offset of ternary-typed field.
+ */
+void
+add_local_ternary_reloption(local_relopts *relopts, const char *name,
+ const char *desc, pg_ternary default_val,
+ int offset)
+{
+ relopt_ternary *newoption;
+
+ newoption =
+ init_ternary_reloption(RELOPT_KIND_LOCAL, name, desc, default_val, 0);
+
+ add_local_reloption(relopts, (relopt_gen *) newoption, offset);
+}
/*
* init_real_reloption
@@ -1626,6 +1705,19 @@ parse_one_reloption(relopt_value *option, char *text_str, int text_len,
option->gen->name, value)));
}
break;
+ case RELOPT_TYPE_TERNARY:
+ {
+ bool b;
+
+ parsed = parse_bool(value, &b);
+ option->values.ternary_val = b ? TERNARY_TRUE : TERNARY_FALSE;
+ if (validate && !parsed)
+ ereport(ERROR,
+ (errcode(ERRCODE_INVALID_PARAMETER_VALUE),
+ errmsg("invalid value for ternary option \"%s\": %s",
+ option->gen->name, value)));
+ }
+ break;
case RELOPT_TYPE_INT:
{
relopt_int *optint = (relopt_int *) option->gen;
@@ -1789,17 +1881,6 @@ fillRelOptions(void *rdopts, Size basesize,
char *itempos = ((char *) rdopts) + elems[j].offset;
char *string_val;
- /*
- * If isset_offset is provided, store whether the reloption is
- * set there.
- */
- if (elems[j].isset_offset > 0)
- {
- char *setpos = ((char *) rdopts) + elems[j].isset_offset;
-
- *(bool *) setpos = options[i].isset;
- }
-
switch (options[i].gen->type)
{
case RELOPT_TYPE_BOOL:
@@ -1807,6 +1888,11 @@ fillRelOptions(void *rdopts, Size basesize,
options[i].values.bool_val :
((relopt_bool *) options[i].gen)->default_val;
break;
+ case RELOPT_TYPE_TERNARY:
+ *(pg_ternary *) itempos = options[i].isset ?
+ options[i].values.ternary_val :
+ ((relopt_ternary *) options[i].gen)->default_val;
+ break;
case RELOPT_TYPE_INT:
*(int *) itempos = options[i].isset ?
options[i].values.int_val :
@@ -1923,8 +2009,8 @@ default_reloptions(Datum reloptions, bool validate, relopt_kind kind)
offsetof(StdRdOptions, parallel_workers)},
{"vacuum_index_cleanup", RELOPT_TYPE_ENUM,
offsetof(StdRdOptions, vacuum_index_cleanup)},
- {"vacuum_truncate", RELOPT_TYPE_BOOL,
- offsetof(StdRdOptions, vacuum_truncate), offsetof(StdRdOptions, vacuum_truncate_set)},
+ {"vacuum_truncate", RELOPT_TYPE_TERNARY,
+ offsetof(StdRdOptions, vacuum_truncate)},
{"vacuum_max_eager_freeze_failure_rate", RELOPT_TYPE_REAL,
offsetof(StdRdOptions, vacuum_max_eager_freeze_failure_rate)}
};
@@ -2004,7 +2090,6 @@ build_local_reloptions(local_relopts *relopts, Datum options, bool validate)
elems[i].optname = opt->option->name;
elems[i].opttype = opt->option->type;
elems[i].offset = opt->offset;
- elems[i].isset_offset = 0; /* not supported for local relopts yet */
i++;
}
diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c
index aa4fbec143f..696eab9bd97 100644
--- a/src/backend/commands/vacuum.c
+++ b/src/backend/commands/vacuum.c
@@ -2224,9 +2224,9 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams params,
{
StdRdOptions *opts = (StdRdOptions *) rel->rd_options;
- if (opts && opts->vacuum_truncate_set)
+ if (opts && opts->vacuum_truncate != TERNARY_UNSET)
{
- if (opts->vacuum_truncate)
+ if (opts->vacuum_truncate == TERNARY_TRUE)
params.truncate = VACOPTVALUE_ENABLED;
else
params.truncate = VACOPTVALUE_DISABLED;
diff --git a/src/include/access/reloptions.h b/src/include/access/reloptions.h
index 2f08e1b0cf0..dfbef2babf2 100644
--- a/src/include/access/reloptions.h
+++ b/src/include/access/reloptions.h
@@ -29,6 +29,7 @@
typedef enum relopt_type
{
RELOPT_TYPE_BOOL,
+ RELOPT_TYPE_TERNARY, /* on, off, unset */
RELOPT_TYPE_INT,
RELOPT_TYPE_REAL,
RELOPT_TYPE_ENUM,
@@ -80,6 +81,7 @@ typedef struct relopt_value
union
{
bool bool_val;
+ pg_ternary ternary_val;
int int_val;
double real_val;
int enum_val;
@@ -94,6 +96,12 @@ typedef struct relopt_bool
bool default_val;
} relopt_bool;
+typedef struct relopt_ternary
+{
+ relopt_gen gen;
+ int default_val;
+} relopt_ternary;
+
typedef struct relopt_int
{
relopt_gen gen;
@@ -152,19 +160,6 @@ typedef struct
const char *optname; /* option's name */
relopt_type opttype; /* option's datatype */
int offset; /* offset of field in result struct */
-
- /*
- * isset_offset is an optional offset of a field in the result struct that
- * stores whether the option is explicitly set for the relation or if it
- * just picked up the default value. In most cases, this can be
- * accomplished by giving the reloption a special out-of-range default
- * value (e.g., some integer reloptions use -2), but this isn't always
- * possible. For example, a Boolean reloption cannot be given an
- * out-of-range default, so we need another way to discover the source of
- * its value. This offset is only used if given a value greater than
- * zero.
- */
- int isset_offset;
} relopt_parse_elt;
/* Local reloption definition */
@@ -195,6 +190,9 @@ typedef struct local_relopts
extern relopt_kind add_reloption_kind(void);
extern void add_bool_reloption(bits32 kinds, const char *name, const char *desc,
bool default_val, LOCKMODE lockmode);
+extern void add_ternary_reloption(bits32 kinds, const char *name,
+ const char *desc, pg_ternary default_val,
+ LOCKMODE lockmode);
extern void add_int_reloption(bits32 kinds, const char *name, const char *desc,
int default_val, int min_val, int max_val,
LOCKMODE lockmode);
@@ -214,6 +212,9 @@ extern void register_reloptions_validator(local_relopts *relopts,
extern void add_local_bool_reloption(local_relopts *relopts, const char *name,
const char *desc, bool default_val,
int offset);
+extern void add_local_ternary_reloption(local_relopts *relopts,
+ const char *name, const char *desc,
+ pg_ternary default_val, int offset);
extern void add_local_int_reloption(local_relopts *relopts, const char *name,
const char *desc, int default_val,
int min_val, int max_val, int offset);
diff --git a/src/include/postgres.h b/src/include/postgres.h
index 7d93fbce709..47c88780776 100644
--- a/src/include/postgres.h
+++ b/src/include/postgres.h
@@ -543,6 +543,21 @@ Float8GetDatum(float8 X)
* ----------------------------------------------------------------
*/
+/*
+ * pg_ternary
+ * Boolean value with an extra "unset" value
+ *
+ * This enum can be used for values that want to distinguish between true,
+ * false, and unset.
+*/
+
+typedef enum pg_ternary
+{
+ TERNARY_FALSE = 0,
+ TERNARY_TRUE = 1,
+ TERNARY_UNSET = -1
+} pg_ternary;
+
/*
* NON_EXEC_STATIC: It's sometimes useful to define a variable or function
* that is normally static but extern when using EXEC_BACKEND (see
diff --git a/src/include/utils/rel.h b/src/include/utils/rel.h
index d03ab247788..5feb18a5373 100644
--- a/src/include/utils/rel.h
+++ b/src/include/utils/rel.h
@@ -347,8 +347,7 @@ typedef struct StdRdOptions
bool user_catalog_table; /* use as an additional catalog relation */
int parallel_workers; /* max number of parallel workers */
StdRdOptIndexCleanup vacuum_index_cleanup; /* controls index vacuuming */
- bool vacuum_truncate; /* enables vacuum to truncate a relation */
- bool vacuum_truncate_set; /* whether vacuum_truncate is set */
+ pg_ternary vacuum_truncate; /* enables vacuum to truncate a relation */
/*
* Fraction of pages in a relation that vacuum can eagerly scan and fail
diff --git a/src/test/regress/expected/reloptions.out b/src/test/regress/expected/reloptions.out
index 9de19b4e3f1..1c99f79ab01 100644
--- a/src/test/regress/expected/reloptions.out
+++ b/src/test/regress/expected/reloptions.out
@@ -98,6 +98,42 @@ SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass;
{fillfactor=13,autovacuum_enabled=false}
(1 row)
+-- Tests for future (FIXME) ternary options
+-- behave as boolean option: accept unassigned name and truncated value
+DROP TABLE reloptions_test;
+CREATE TABLE reloptions_test(i INT) WITH (vacuum_truncate);
+SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass;
+ reloptions
+------------------------
+ {vacuum_truncate=true}
+(1 row)
+
+DROP TABLE reloptions_test;
+CREATE TABLE reloptions_test(i INT) WITH (vacuum_truncate=FaLS);
+SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass;
+ reloptions
+------------------------
+ {vacuum_truncate=fals}
+(1 row)
+
+-- preferred "true" alias is used when storing
+DROP TABLE reloptions_test;
+CREATE TABLE reloptions_test(i INT) WITH (vacuum_index_cleanup=on);
+SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass;
+ reloptions
+---------------------------
+ {vacuum_index_cleanup=on}
+(1 row)
+
+-- custom "third" value is available
+DROP TABLE reloptions_test;
+CREATE TABLE reloptions_test(i INT) WITH (vacuum_index_cleanup=auto);
+SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass;
+ reloptions
+-----------------------------
+ {vacuum_index_cleanup=auto}
+(1 row)
+
-- Test vacuum_truncate option
DROP TABLE reloptions_test;
CREATE TEMP TABLE reloptions_test(i INT NOT NULL, j text)
diff --git a/src/test/regress/sql/reloptions.sql b/src/test/regress/sql/reloptions.sql
index 24fbe0b478d..f5980dafcbc 100644
--- a/src/test/regress/sql/reloptions.sql
+++ b/src/test/regress/sql/reloptions.sql
@@ -59,6 +59,27 @@ UPDATE pg_class
ALTER TABLE reloptions_test RESET (illegal_option);
SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass;
+-- Tests for future (FIXME) ternary options
+
+-- behave as boolean option: accept unassigned name and truncated value
+DROP TABLE reloptions_test;
+CREATE TABLE reloptions_test(i INT) WITH (vacuum_truncate);
+SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass;
+
+DROP TABLE reloptions_test;
+CREATE TABLE reloptions_test(i INT) WITH (vacuum_truncate=FaLS);
+SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass;
+
+-- preferred "true" alias is used when storing
+DROP TABLE reloptions_test;
+CREATE TABLE reloptions_test(i INT) WITH (vacuum_index_cleanup=on);
+SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass;
+
+-- custom "third" value is available
+DROP TABLE reloptions_test;
+CREATE TABLE reloptions_test(i INT) WITH (vacuum_index_cleanup=auto);
+SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass;
+
-- Test vacuum_truncate option
DROP TABLE reloptions_test;
diff --git a/src/tools/pgindent/typedefs.list b/src/tools/pgindent/typedefs.list
index 3f3a888fd0e..1c8610fd46c 100644
--- a/src/tools/pgindent/typedefs.list
+++ b/src/tools/pgindent/typedefs.list
@@ -3952,6 +3952,7 @@ pg_sha512_ctx
pg_snapshot
pg_special_case
pg_stack_base_t
+pg_ternary
pg_time_t
pg_time_usec_t
pg_tz
@@ -4079,6 +4080,7 @@ relopt_kind
relopt_parse_elt
relopt_real
relopt_string
+relopt_ternary
relopt_type
relopt_value
relopts_validator
--
2.47.3
--c36t3fndxhkzbl5z--
^ permalink raw reply [nested|flat] 868+ messages in thread
* [PATCH v3] Introduce ternary reloptions
@ 2026-01-16 15:04 Álvaro Herrera <[email protected]>
0 siblings, 0 replies; 868+ messages in thread
From: Álvaro Herrera @ 2026-01-16 15:04 UTC (permalink / raw)
Introduce ternary reloption as a replacement for current
`vacuum_truncate` implementation. Remove the `vacuum_truncate_set`
separate flag and use TERNARY_UNSET instead.
This could also be used for other options such as `vacuum_index_cleanup`
and `buffering`, but lets get the scaffolding in first.
Discussion: https://postgr.es/m/3474141.usfYGdeWWP@thinkpad-pgpro
---
src/backend/access/common/reloptions.c | 137 ++++++++++++++++++-----
src/backend/commands/vacuum.c | 4 +-
src/include/access/reloptions.h | 27 ++---
src/include/postgres.h | 15 +++
src/include/utils/rel.h | 3 +-
src/test/regress/expected/reloptions.out | 36 ++++++
src/test/regress/sql/reloptions.sql | 21 ++++
src/tools/pgindent/typedefs.list | 2 +
8 files changed, 202 insertions(+), 43 deletions(-)
diff --git a/src/backend/access/common/reloptions.c b/src/backend/access/common/reloptions.c
index 0b83f98ed5f..6f1f577581a 100644
--- a/src/backend/access/common/reloptions.c
+++ b/src/backend/access/common/reloptions.c
@@ -40,9 +40,9 @@
*
* To add an option:
*
- * (i) decide on a type (bool, integer, real, enum, string), name, default
- * value, upper and lower bounds (if applicable); for strings, consider a
- * validation routine.
+ * (i) decide on a type (bool, ternary, integer, real, enum, string), name,
+ * default value, upper and lower bounds (if applicable); for strings,
+ * consider a validation routine.
* (ii) add a record below (or use add_<type>_reloption).
* (iii) add it to the appropriate options struct (perhaps StdRdOptions)
* (iv) add it to the appropriate handling routine (perhaps
@@ -147,15 +147,6 @@ static relopt_bool boolRelOpts[] =
},
false
},
- {
- {
- "vacuum_truncate",
- "Enables vacuum to truncate empty pages at the end of this table",
- RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
- ShareUpdateExclusiveLock
- },
- true
- },
{
{
"deduplicate_items",
@@ -170,6 +161,25 @@ static relopt_bool boolRelOpts[] =
{{NULL}}
};
+static relopt_ternary ternaryRelOpts[] =
+{
+ {
+ {
+ "vacuum_truncate",
+ "Enables vacuum to truncate empty pages at the end of this table",
+ RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
+ ShareUpdateExclusiveLock
+ },
+ TERNARY_UNSET
+ },
+ /* list terminator */
+ {
+ {
+ NULL
+ }
+ }
+};
+
static relopt_int intRelOpts[] =
{
{
@@ -609,6 +619,13 @@ initialize_reloptions(void)
boolRelOpts[i].gen.lockmode));
j++;
}
+ for (i = 0; ternaryRelOpts[i].gen.name; i++)
+ {
+ Assert(DoLockModesConflict(ternaryRelOpts[i].gen.lockmode,
+ ternaryRelOpts[i].gen.lockmode));
+ j++;
+ }
+
for (i = 0; intRelOpts[i].gen.name; i++)
{
Assert(DoLockModesConflict(intRelOpts[i].gen.lockmode,
@@ -649,6 +666,14 @@ initialize_reloptions(void)
j++;
}
+ for (i = 0; ternaryRelOpts[i].gen.name; i++)
+ {
+ relOpts[j] = &ternaryRelOpts[i].gen;
+ relOpts[j]->type = RELOPT_TYPE_TERNARY;
+ relOpts[j]->namelen = strlen(relOpts[j]->name);
+ j++;
+ }
+
for (i = 0; intRelOpts[i].gen.name; i++)
{
relOpts[j] = &intRelOpts[i].gen;
@@ -809,6 +834,9 @@ allocate_reloption(bits32 kinds, int type, const char *name, const char *desc,
case RELOPT_TYPE_BOOL:
size = sizeof(relopt_bool);
break;
+ case RELOPT_TYPE_TERNARY:
+ size = sizeof(relopt_ternary);
+ break;
case RELOPT_TYPE_INT:
size = sizeof(relopt_int);
break;
@@ -892,6 +920,57 @@ add_local_bool_reloption(local_relopts *relopts, const char *name,
add_local_reloption(relopts, (relopt_gen *) newoption, offset);
}
+/*
+ * init_ternary_reloption
+ * Allocate and initialize a new ternary reloption
+ */
+static relopt_ternary *
+init_ternary_reloption(bits32 kinds, const char *name, const char *desc,
+ pg_ternary default_val, LOCKMODE lockmode)
+{
+ relopt_ternary *newoption;
+
+ newoption = (relopt_ternary *)
+ allocate_reloption(kinds, RELOPT_TYPE_TERNARY, name, desc, lockmode);
+ newoption->default_val = default_val;
+
+ return newoption;
+}
+
+/*
+ * add_ternary_reloption
+ * Add a new ternary reloption
+ */
+void
+add_ternary_reloption(bits32 kinds, const char *name, const char *desc,
+ pg_ternary default_val, LOCKMODE lockmode)
+{
+ relopt_ternary *newoption;
+
+ newoption =
+ init_ternary_reloption(kinds, name, desc, default_val, lockmode);
+
+ add_reloption((relopt_gen *) newoption);
+}
+
+/*
+ * add_local_ternary_reloption
+ * Add a new ternary local reloption
+ *
+ * 'offset' is offset of ternary-typed field.
+ */
+void
+add_local_ternary_reloption(local_relopts *relopts, const char *name,
+ const char *desc, pg_ternary default_val,
+ int offset)
+{
+ relopt_ternary *newoption;
+
+ newoption =
+ init_ternary_reloption(RELOPT_KIND_LOCAL, name, desc, default_val, 0);
+
+ add_local_reloption(relopts, (relopt_gen *) newoption, offset);
+}
/*
* init_real_reloption
@@ -1626,6 +1705,19 @@ parse_one_reloption(relopt_value *option, char *text_str, int text_len,
option->gen->name, value)));
}
break;
+ case RELOPT_TYPE_TERNARY:
+ {
+ bool b;
+
+ parsed = parse_bool(value, &b);
+ option->values.ternary_val = b ? TERNARY_TRUE : TERNARY_FALSE;
+ if (validate && !parsed)
+ ereport(ERROR,
+ (errcode(ERRCODE_INVALID_PARAMETER_VALUE),
+ errmsg("invalid value for ternary option \"%s\": %s",
+ option->gen->name, value)));
+ }
+ break;
case RELOPT_TYPE_INT:
{
relopt_int *optint = (relopt_int *) option->gen;
@@ -1789,17 +1881,6 @@ fillRelOptions(void *rdopts, Size basesize,
char *itempos = ((char *) rdopts) + elems[j].offset;
char *string_val;
- /*
- * If isset_offset is provided, store whether the reloption is
- * set there.
- */
- if (elems[j].isset_offset > 0)
- {
- char *setpos = ((char *) rdopts) + elems[j].isset_offset;
-
- *(bool *) setpos = options[i].isset;
- }
-
switch (options[i].gen->type)
{
case RELOPT_TYPE_BOOL:
@@ -1807,6 +1888,11 @@ fillRelOptions(void *rdopts, Size basesize,
options[i].values.bool_val :
((relopt_bool *) options[i].gen)->default_val;
break;
+ case RELOPT_TYPE_TERNARY:
+ *(pg_ternary *) itempos = options[i].isset ?
+ options[i].values.ternary_val :
+ ((relopt_ternary *) options[i].gen)->default_val;
+ break;
case RELOPT_TYPE_INT:
*(int *) itempos = options[i].isset ?
options[i].values.int_val :
@@ -1923,8 +2009,8 @@ default_reloptions(Datum reloptions, bool validate, relopt_kind kind)
offsetof(StdRdOptions, parallel_workers)},
{"vacuum_index_cleanup", RELOPT_TYPE_ENUM,
offsetof(StdRdOptions, vacuum_index_cleanup)},
- {"vacuum_truncate", RELOPT_TYPE_BOOL,
- offsetof(StdRdOptions, vacuum_truncate), offsetof(StdRdOptions, vacuum_truncate_set)},
+ {"vacuum_truncate", RELOPT_TYPE_TERNARY,
+ offsetof(StdRdOptions, vacuum_truncate)},
{"vacuum_max_eager_freeze_failure_rate", RELOPT_TYPE_REAL,
offsetof(StdRdOptions, vacuum_max_eager_freeze_failure_rate)}
};
@@ -2004,7 +2090,6 @@ build_local_reloptions(local_relopts *relopts, Datum options, bool validate)
elems[i].optname = opt->option->name;
elems[i].opttype = opt->option->type;
elems[i].offset = opt->offset;
- elems[i].isset_offset = 0; /* not supported for local relopts yet */
i++;
}
diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c
index aa4fbec143f..696eab9bd97 100644
--- a/src/backend/commands/vacuum.c
+++ b/src/backend/commands/vacuum.c
@@ -2224,9 +2224,9 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams params,
{
StdRdOptions *opts = (StdRdOptions *) rel->rd_options;
- if (opts && opts->vacuum_truncate_set)
+ if (opts && opts->vacuum_truncate != TERNARY_UNSET)
{
- if (opts->vacuum_truncate)
+ if (opts->vacuum_truncate == TERNARY_TRUE)
params.truncate = VACOPTVALUE_ENABLED;
else
params.truncate = VACOPTVALUE_DISABLED;
diff --git a/src/include/access/reloptions.h b/src/include/access/reloptions.h
index 2f08e1b0cf0..dfbef2babf2 100644
--- a/src/include/access/reloptions.h
+++ b/src/include/access/reloptions.h
@@ -29,6 +29,7 @@
typedef enum relopt_type
{
RELOPT_TYPE_BOOL,
+ RELOPT_TYPE_TERNARY, /* on, off, unset */
RELOPT_TYPE_INT,
RELOPT_TYPE_REAL,
RELOPT_TYPE_ENUM,
@@ -80,6 +81,7 @@ typedef struct relopt_value
union
{
bool bool_val;
+ pg_ternary ternary_val;
int int_val;
double real_val;
int enum_val;
@@ -94,6 +96,12 @@ typedef struct relopt_bool
bool default_val;
} relopt_bool;
+typedef struct relopt_ternary
+{
+ relopt_gen gen;
+ int default_val;
+} relopt_ternary;
+
typedef struct relopt_int
{
relopt_gen gen;
@@ -152,19 +160,6 @@ typedef struct
const char *optname; /* option's name */
relopt_type opttype; /* option's datatype */
int offset; /* offset of field in result struct */
-
- /*
- * isset_offset is an optional offset of a field in the result struct that
- * stores whether the option is explicitly set for the relation or if it
- * just picked up the default value. In most cases, this can be
- * accomplished by giving the reloption a special out-of-range default
- * value (e.g., some integer reloptions use -2), but this isn't always
- * possible. For example, a Boolean reloption cannot be given an
- * out-of-range default, so we need another way to discover the source of
- * its value. This offset is only used if given a value greater than
- * zero.
- */
- int isset_offset;
} relopt_parse_elt;
/* Local reloption definition */
@@ -195,6 +190,9 @@ typedef struct local_relopts
extern relopt_kind add_reloption_kind(void);
extern void add_bool_reloption(bits32 kinds, const char *name, const char *desc,
bool default_val, LOCKMODE lockmode);
+extern void add_ternary_reloption(bits32 kinds, const char *name,
+ const char *desc, pg_ternary default_val,
+ LOCKMODE lockmode);
extern void add_int_reloption(bits32 kinds, const char *name, const char *desc,
int default_val, int min_val, int max_val,
LOCKMODE lockmode);
@@ -214,6 +212,9 @@ extern void register_reloptions_validator(local_relopts *relopts,
extern void add_local_bool_reloption(local_relopts *relopts, const char *name,
const char *desc, bool default_val,
int offset);
+extern void add_local_ternary_reloption(local_relopts *relopts,
+ const char *name, const char *desc,
+ pg_ternary default_val, int offset);
extern void add_local_int_reloption(local_relopts *relopts, const char *name,
const char *desc, int default_val,
int min_val, int max_val, int offset);
diff --git a/src/include/postgres.h b/src/include/postgres.h
index 7d93fbce709..47c88780776 100644
--- a/src/include/postgres.h
+++ b/src/include/postgres.h
@@ -543,6 +543,21 @@ Float8GetDatum(float8 X)
* ----------------------------------------------------------------
*/
+/*
+ * pg_ternary
+ * Boolean value with an extra "unset" value
+ *
+ * This enum can be used for values that want to distinguish between true,
+ * false, and unset.
+*/
+
+typedef enum pg_ternary
+{
+ TERNARY_FALSE = 0,
+ TERNARY_TRUE = 1,
+ TERNARY_UNSET = -1
+} pg_ternary;
+
/*
* NON_EXEC_STATIC: It's sometimes useful to define a variable or function
* that is normally static but extern when using EXEC_BACKEND (see
diff --git a/src/include/utils/rel.h b/src/include/utils/rel.h
index d03ab247788..5feb18a5373 100644
--- a/src/include/utils/rel.h
+++ b/src/include/utils/rel.h
@@ -347,8 +347,7 @@ typedef struct StdRdOptions
bool user_catalog_table; /* use as an additional catalog relation */
int parallel_workers; /* max number of parallel workers */
StdRdOptIndexCleanup vacuum_index_cleanup; /* controls index vacuuming */
- bool vacuum_truncate; /* enables vacuum to truncate a relation */
- bool vacuum_truncate_set; /* whether vacuum_truncate is set */
+ pg_ternary vacuum_truncate; /* enables vacuum to truncate a relation */
/*
* Fraction of pages in a relation that vacuum can eagerly scan and fail
diff --git a/src/test/regress/expected/reloptions.out b/src/test/regress/expected/reloptions.out
index 9de19b4e3f1..1c99f79ab01 100644
--- a/src/test/regress/expected/reloptions.out
+++ b/src/test/regress/expected/reloptions.out
@@ -98,6 +98,42 @@ SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass;
{fillfactor=13,autovacuum_enabled=false}
(1 row)
+-- Tests for future (FIXME) ternary options
+-- behave as boolean option: accept unassigned name and truncated value
+DROP TABLE reloptions_test;
+CREATE TABLE reloptions_test(i INT) WITH (vacuum_truncate);
+SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass;
+ reloptions
+------------------------
+ {vacuum_truncate=true}
+(1 row)
+
+DROP TABLE reloptions_test;
+CREATE TABLE reloptions_test(i INT) WITH (vacuum_truncate=FaLS);
+SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass;
+ reloptions
+------------------------
+ {vacuum_truncate=fals}
+(1 row)
+
+-- preferred "true" alias is used when storing
+DROP TABLE reloptions_test;
+CREATE TABLE reloptions_test(i INT) WITH (vacuum_index_cleanup=on);
+SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass;
+ reloptions
+---------------------------
+ {vacuum_index_cleanup=on}
+(1 row)
+
+-- custom "third" value is available
+DROP TABLE reloptions_test;
+CREATE TABLE reloptions_test(i INT) WITH (vacuum_index_cleanup=auto);
+SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass;
+ reloptions
+-----------------------------
+ {vacuum_index_cleanup=auto}
+(1 row)
+
-- Test vacuum_truncate option
DROP TABLE reloptions_test;
CREATE TEMP TABLE reloptions_test(i INT NOT NULL, j text)
diff --git a/src/test/regress/sql/reloptions.sql b/src/test/regress/sql/reloptions.sql
index 24fbe0b478d..f5980dafcbc 100644
--- a/src/test/regress/sql/reloptions.sql
+++ b/src/test/regress/sql/reloptions.sql
@@ -59,6 +59,27 @@ UPDATE pg_class
ALTER TABLE reloptions_test RESET (illegal_option);
SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass;
+-- Tests for future (FIXME) ternary options
+
+-- behave as boolean option: accept unassigned name and truncated value
+DROP TABLE reloptions_test;
+CREATE TABLE reloptions_test(i INT) WITH (vacuum_truncate);
+SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass;
+
+DROP TABLE reloptions_test;
+CREATE TABLE reloptions_test(i INT) WITH (vacuum_truncate=FaLS);
+SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass;
+
+-- preferred "true" alias is used when storing
+DROP TABLE reloptions_test;
+CREATE TABLE reloptions_test(i INT) WITH (vacuum_index_cleanup=on);
+SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass;
+
+-- custom "third" value is available
+DROP TABLE reloptions_test;
+CREATE TABLE reloptions_test(i INT) WITH (vacuum_index_cleanup=auto);
+SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass;
+
-- Test vacuum_truncate option
DROP TABLE reloptions_test;
diff --git a/src/tools/pgindent/typedefs.list b/src/tools/pgindent/typedefs.list
index 3f3a888fd0e..1c8610fd46c 100644
--- a/src/tools/pgindent/typedefs.list
+++ b/src/tools/pgindent/typedefs.list
@@ -3952,6 +3952,7 @@ pg_sha512_ctx
pg_snapshot
pg_special_case
pg_stack_base_t
+pg_ternary
pg_time_t
pg_time_usec_t
pg_tz
@@ -4079,6 +4080,7 @@ relopt_kind
relopt_parse_elt
relopt_real
relopt_string
+relopt_ternary
relopt_type
relopt_value
relopts_validator
--
2.47.3
--c36t3fndxhkzbl5z--
^ permalink raw reply [nested|flat] 868+ messages in thread
* [PATCH v3] Introduce ternary reloptions
@ 2026-01-16 15:04 Álvaro Herrera <[email protected]>
0 siblings, 0 replies; 868+ messages in thread
From: Álvaro Herrera @ 2026-01-16 15:04 UTC (permalink / raw)
Introduce ternary reloption as a replacement for current
`vacuum_truncate` implementation. Remove the `vacuum_truncate_set`
separate flag and use TERNARY_UNSET instead.
This could also be used for other options such as `vacuum_index_cleanup`
and `buffering`, but lets get the scaffolding in first.
Discussion: https://postgr.es/m/3474141.usfYGdeWWP@thinkpad-pgpro
---
src/backend/access/common/reloptions.c | 137 ++++++++++++++++++-----
src/backend/commands/vacuum.c | 4 +-
src/include/access/reloptions.h | 27 ++---
src/include/postgres.h | 15 +++
src/include/utils/rel.h | 3 +-
src/test/regress/expected/reloptions.out | 36 ++++++
src/test/regress/sql/reloptions.sql | 21 ++++
src/tools/pgindent/typedefs.list | 2 +
8 files changed, 202 insertions(+), 43 deletions(-)
diff --git a/src/backend/access/common/reloptions.c b/src/backend/access/common/reloptions.c
index 0b83f98ed5f..6f1f577581a 100644
--- a/src/backend/access/common/reloptions.c
+++ b/src/backend/access/common/reloptions.c
@@ -40,9 +40,9 @@
*
* To add an option:
*
- * (i) decide on a type (bool, integer, real, enum, string), name, default
- * value, upper and lower bounds (if applicable); for strings, consider a
- * validation routine.
+ * (i) decide on a type (bool, ternary, integer, real, enum, string), name,
+ * default value, upper and lower bounds (if applicable); for strings,
+ * consider a validation routine.
* (ii) add a record below (or use add_<type>_reloption).
* (iii) add it to the appropriate options struct (perhaps StdRdOptions)
* (iv) add it to the appropriate handling routine (perhaps
@@ -147,15 +147,6 @@ static relopt_bool boolRelOpts[] =
},
false
},
- {
- {
- "vacuum_truncate",
- "Enables vacuum to truncate empty pages at the end of this table",
- RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
- ShareUpdateExclusiveLock
- },
- true
- },
{
{
"deduplicate_items",
@@ -170,6 +161,25 @@ static relopt_bool boolRelOpts[] =
{{NULL}}
};
+static relopt_ternary ternaryRelOpts[] =
+{
+ {
+ {
+ "vacuum_truncate",
+ "Enables vacuum to truncate empty pages at the end of this table",
+ RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
+ ShareUpdateExclusiveLock
+ },
+ TERNARY_UNSET
+ },
+ /* list terminator */
+ {
+ {
+ NULL
+ }
+ }
+};
+
static relopt_int intRelOpts[] =
{
{
@@ -609,6 +619,13 @@ initialize_reloptions(void)
boolRelOpts[i].gen.lockmode));
j++;
}
+ for (i = 0; ternaryRelOpts[i].gen.name; i++)
+ {
+ Assert(DoLockModesConflict(ternaryRelOpts[i].gen.lockmode,
+ ternaryRelOpts[i].gen.lockmode));
+ j++;
+ }
+
for (i = 0; intRelOpts[i].gen.name; i++)
{
Assert(DoLockModesConflict(intRelOpts[i].gen.lockmode,
@@ -649,6 +666,14 @@ initialize_reloptions(void)
j++;
}
+ for (i = 0; ternaryRelOpts[i].gen.name; i++)
+ {
+ relOpts[j] = &ternaryRelOpts[i].gen;
+ relOpts[j]->type = RELOPT_TYPE_TERNARY;
+ relOpts[j]->namelen = strlen(relOpts[j]->name);
+ j++;
+ }
+
for (i = 0; intRelOpts[i].gen.name; i++)
{
relOpts[j] = &intRelOpts[i].gen;
@@ -809,6 +834,9 @@ allocate_reloption(bits32 kinds, int type, const char *name, const char *desc,
case RELOPT_TYPE_BOOL:
size = sizeof(relopt_bool);
break;
+ case RELOPT_TYPE_TERNARY:
+ size = sizeof(relopt_ternary);
+ break;
case RELOPT_TYPE_INT:
size = sizeof(relopt_int);
break;
@@ -892,6 +920,57 @@ add_local_bool_reloption(local_relopts *relopts, const char *name,
add_local_reloption(relopts, (relopt_gen *) newoption, offset);
}
+/*
+ * init_ternary_reloption
+ * Allocate and initialize a new ternary reloption
+ */
+static relopt_ternary *
+init_ternary_reloption(bits32 kinds, const char *name, const char *desc,
+ pg_ternary default_val, LOCKMODE lockmode)
+{
+ relopt_ternary *newoption;
+
+ newoption = (relopt_ternary *)
+ allocate_reloption(kinds, RELOPT_TYPE_TERNARY, name, desc, lockmode);
+ newoption->default_val = default_val;
+
+ return newoption;
+}
+
+/*
+ * add_ternary_reloption
+ * Add a new ternary reloption
+ */
+void
+add_ternary_reloption(bits32 kinds, const char *name, const char *desc,
+ pg_ternary default_val, LOCKMODE lockmode)
+{
+ relopt_ternary *newoption;
+
+ newoption =
+ init_ternary_reloption(kinds, name, desc, default_val, lockmode);
+
+ add_reloption((relopt_gen *) newoption);
+}
+
+/*
+ * add_local_ternary_reloption
+ * Add a new ternary local reloption
+ *
+ * 'offset' is offset of ternary-typed field.
+ */
+void
+add_local_ternary_reloption(local_relopts *relopts, const char *name,
+ const char *desc, pg_ternary default_val,
+ int offset)
+{
+ relopt_ternary *newoption;
+
+ newoption =
+ init_ternary_reloption(RELOPT_KIND_LOCAL, name, desc, default_val, 0);
+
+ add_local_reloption(relopts, (relopt_gen *) newoption, offset);
+}
/*
* init_real_reloption
@@ -1626,6 +1705,19 @@ parse_one_reloption(relopt_value *option, char *text_str, int text_len,
option->gen->name, value)));
}
break;
+ case RELOPT_TYPE_TERNARY:
+ {
+ bool b;
+
+ parsed = parse_bool(value, &b);
+ option->values.ternary_val = b ? TERNARY_TRUE : TERNARY_FALSE;
+ if (validate && !parsed)
+ ereport(ERROR,
+ (errcode(ERRCODE_INVALID_PARAMETER_VALUE),
+ errmsg("invalid value for ternary option \"%s\": %s",
+ option->gen->name, value)));
+ }
+ break;
case RELOPT_TYPE_INT:
{
relopt_int *optint = (relopt_int *) option->gen;
@@ -1789,17 +1881,6 @@ fillRelOptions(void *rdopts, Size basesize,
char *itempos = ((char *) rdopts) + elems[j].offset;
char *string_val;
- /*
- * If isset_offset is provided, store whether the reloption is
- * set there.
- */
- if (elems[j].isset_offset > 0)
- {
- char *setpos = ((char *) rdopts) + elems[j].isset_offset;
-
- *(bool *) setpos = options[i].isset;
- }
-
switch (options[i].gen->type)
{
case RELOPT_TYPE_BOOL:
@@ -1807,6 +1888,11 @@ fillRelOptions(void *rdopts, Size basesize,
options[i].values.bool_val :
((relopt_bool *) options[i].gen)->default_val;
break;
+ case RELOPT_TYPE_TERNARY:
+ *(pg_ternary *) itempos = options[i].isset ?
+ options[i].values.ternary_val :
+ ((relopt_ternary *) options[i].gen)->default_val;
+ break;
case RELOPT_TYPE_INT:
*(int *) itempos = options[i].isset ?
options[i].values.int_val :
@@ -1923,8 +2009,8 @@ default_reloptions(Datum reloptions, bool validate, relopt_kind kind)
offsetof(StdRdOptions, parallel_workers)},
{"vacuum_index_cleanup", RELOPT_TYPE_ENUM,
offsetof(StdRdOptions, vacuum_index_cleanup)},
- {"vacuum_truncate", RELOPT_TYPE_BOOL,
- offsetof(StdRdOptions, vacuum_truncate), offsetof(StdRdOptions, vacuum_truncate_set)},
+ {"vacuum_truncate", RELOPT_TYPE_TERNARY,
+ offsetof(StdRdOptions, vacuum_truncate)},
{"vacuum_max_eager_freeze_failure_rate", RELOPT_TYPE_REAL,
offsetof(StdRdOptions, vacuum_max_eager_freeze_failure_rate)}
};
@@ -2004,7 +2090,6 @@ build_local_reloptions(local_relopts *relopts, Datum options, bool validate)
elems[i].optname = opt->option->name;
elems[i].opttype = opt->option->type;
elems[i].offset = opt->offset;
- elems[i].isset_offset = 0; /* not supported for local relopts yet */
i++;
}
diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c
index aa4fbec143f..696eab9bd97 100644
--- a/src/backend/commands/vacuum.c
+++ b/src/backend/commands/vacuum.c
@@ -2224,9 +2224,9 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams params,
{
StdRdOptions *opts = (StdRdOptions *) rel->rd_options;
- if (opts && opts->vacuum_truncate_set)
+ if (opts && opts->vacuum_truncate != TERNARY_UNSET)
{
- if (opts->vacuum_truncate)
+ if (opts->vacuum_truncate == TERNARY_TRUE)
params.truncate = VACOPTVALUE_ENABLED;
else
params.truncate = VACOPTVALUE_DISABLED;
diff --git a/src/include/access/reloptions.h b/src/include/access/reloptions.h
index 2f08e1b0cf0..dfbef2babf2 100644
--- a/src/include/access/reloptions.h
+++ b/src/include/access/reloptions.h
@@ -29,6 +29,7 @@
typedef enum relopt_type
{
RELOPT_TYPE_BOOL,
+ RELOPT_TYPE_TERNARY, /* on, off, unset */
RELOPT_TYPE_INT,
RELOPT_TYPE_REAL,
RELOPT_TYPE_ENUM,
@@ -80,6 +81,7 @@ typedef struct relopt_value
union
{
bool bool_val;
+ pg_ternary ternary_val;
int int_val;
double real_val;
int enum_val;
@@ -94,6 +96,12 @@ typedef struct relopt_bool
bool default_val;
} relopt_bool;
+typedef struct relopt_ternary
+{
+ relopt_gen gen;
+ int default_val;
+} relopt_ternary;
+
typedef struct relopt_int
{
relopt_gen gen;
@@ -152,19 +160,6 @@ typedef struct
const char *optname; /* option's name */
relopt_type opttype; /* option's datatype */
int offset; /* offset of field in result struct */
-
- /*
- * isset_offset is an optional offset of a field in the result struct that
- * stores whether the option is explicitly set for the relation or if it
- * just picked up the default value. In most cases, this can be
- * accomplished by giving the reloption a special out-of-range default
- * value (e.g., some integer reloptions use -2), but this isn't always
- * possible. For example, a Boolean reloption cannot be given an
- * out-of-range default, so we need another way to discover the source of
- * its value. This offset is only used if given a value greater than
- * zero.
- */
- int isset_offset;
} relopt_parse_elt;
/* Local reloption definition */
@@ -195,6 +190,9 @@ typedef struct local_relopts
extern relopt_kind add_reloption_kind(void);
extern void add_bool_reloption(bits32 kinds, const char *name, const char *desc,
bool default_val, LOCKMODE lockmode);
+extern void add_ternary_reloption(bits32 kinds, const char *name,
+ const char *desc, pg_ternary default_val,
+ LOCKMODE lockmode);
extern void add_int_reloption(bits32 kinds, const char *name, const char *desc,
int default_val, int min_val, int max_val,
LOCKMODE lockmode);
@@ -214,6 +212,9 @@ extern void register_reloptions_validator(local_relopts *relopts,
extern void add_local_bool_reloption(local_relopts *relopts, const char *name,
const char *desc, bool default_val,
int offset);
+extern void add_local_ternary_reloption(local_relopts *relopts,
+ const char *name, const char *desc,
+ pg_ternary default_val, int offset);
extern void add_local_int_reloption(local_relopts *relopts, const char *name,
const char *desc, int default_val,
int min_val, int max_val, int offset);
diff --git a/src/include/postgres.h b/src/include/postgres.h
index 7d93fbce709..47c88780776 100644
--- a/src/include/postgres.h
+++ b/src/include/postgres.h
@@ -543,6 +543,21 @@ Float8GetDatum(float8 X)
* ----------------------------------------------------------------
*/
+/*
+ * pg_ternary
+ * Boolean value with an extra "unset" value
+ *
+ * This enum can be used for values that want to distinguish between true,
+ * false, and unset.
+*/
+
+typedef enum pg_ternary
+{
+ TERNARY_FALSE = 0,
+ TERNARY_TRUE = 1,
+ TERNARY_UNSET = -1
+} pg_ternary;
+
/*
* NON_EXEC_STATIC: It's sometimes useful to define a variable or function
* that is normally static but extern when using EXEC_BACKEND (see
diff --git a/src/include/utils/rel.h b/src/include/utils/rel.h
index d03ab247788..5feb18a5373 100644
--- a/src/include/utils/rel.h
+++ b/src/include/utils/rel.h
@@ -347,8 +347,7 @@ typedef struct StdRdOptions
bool user_catalog_table; /* use as an additional catalog relation */
int parallel_workers; /* max number of parallel workers */
StdRdOptIndexCleanup vacuum_index_cleanup; /* controls index vacuuming */
- bool vacuum_truncate; /* enables vacuum to truncate a relation */
- bool vacuum_truncate_set; /* whether vacuum_truncate is set */
+ pg_ternary vacuum_truncate; /* enables vacuum to truncate a relation */
/*
* Fraction of pages in a relation that vacuum can eagerly scan and fail
diff --git a/src/test/regress/expected/reloptions.out b/src/test/regress/expected/reloptions.out
index 9de19b4e3f1..1c99f79ab01 100644
--- a/src/test/regress/expected/reloptions.out
+++ b/src/test/regress/expected/reloptions.out
@@ -98,6 +98,42 @@ SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass;
{fillfactor=13,autovacuum_enabled=false}
(1 row)
+-- Tests for future (FIXME) ternary options
+-- behave as boolean option: accept unassigned name and truncated value
+DROP TABLE reloptions_test;
+CREATE TABLE reloptions_test(i INT) WITH (vacuum_truncate);
+SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass;
+ reloptions
+------------------------
+ {vacuum_truncate=true}
+(1 row)
+
+DROP TABLE reloptions_test;
+CREATE TABLE reloptions_test(i INT) WITH (vacuum_truncate=FaLS);
+SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass;
+ reloptions
+------------------------
+ {vacuum_truncate=fals}
+(1 row)
+
+-- preferred "true" alias is used when storing
+DROP TABLE reloptions_test;
+CREATE TABLE reloptions_test(i INT) WITH (vacuum_index_cleanup=on);
+SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass;
+ reloptions
+---------------------------
+ {vacuum_index_cleanup=on}
+(1 row)
+
+-- custom "third" value is available
+DROP TABLE reloptions_test;
+CREATE TABLE reloptions_test(i INT) WITH (vacuum_index_cleanup=auto);
+SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass;
+ reloptions
+-----------------------------
+ {vacuum_index_cleanup=auto}
+(1 row)
+
-- Test vacuum_truncate option
DROP TABLE reloptions_test;
CREATE TEMP TABLE reloptions_test(i INT NOT NULL, j text)
diff --git a/src/test/regress/sql/reloptions.sql b/src/test/regress/sql/reloptions.sql
index 24fbe0b478d..f5980dafcbc 100644
--- a/src/test/regress/sql/reloptions.sql
+++ b/src/test/regress/sql/reloptions.sql
@@ -59,6 +59,27 @@ UPDATE pg_class
ALTER TABLE reloptions_test RESET (illegal_option);
SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass;
+-- Tests for future (FIXME) ternary options
+
+-- behave as boolean option: accept unassigned name and truncated value
+DROP TABLE reloptions_test;
+CREATE TABLE reloptions_test(i INT) WITH (vacuum_truncate);
+SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass;
+
+DROP TABLE reloptions_test;
+CREATE TABLE reloptions_test(i INT) WITH (vacuum_truncate=FaLS);
+SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass;
+
+-- preferred "true" alias is used when storing
+DROP TABLE reloptions_test;
+CREATE TABLE reloptions_test(i INT) WITH (vacuum_index_cleanup=on);
+SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass;
+
+-- custom "third" value is available
+DROP TABLE reloptions_test;
+CREATE TABLE reloptions_test(i INT) WITH (vacuum_index_cleanup=auto);
+SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass;
+
-- Test vacuum_truncate option
DROP TABLE reloptions_test;
diff --git a/src/tools/pgindent/typedefs.list b/src/tools/pgindent/typedefs.list
index 3f3a888fd0e..1c8610fd46c 100644
--- a/src/tools/pgindent/typedefs.list
+++ b/src/tools/pgindent/typedefs.list
@@ -3952,6 +3952,7 @@ pg_sha512_ctx
pg_snapshot
pg_special_case
pg_stack_base_t
+pg_ternary
pg_time_t
pg_time_usec_t
pg_tz
@@ -4079,6 +4080,7 @@ relopt_kind
relopt_parse_elt
relopt_real
relopt_string
+relopt_ternary
relopt_type
relopt_value
relopts_validator
--
2.47.3
--c36t3fndxhkzbl5z--
^ permalink raw reply [nested|flat] 868+ messages in thread
* [PATCH v3] Introduce ternary reloptions
@ 2026-01-16 15:04 Álvaro Herrera <[email protected]>
0 siblings, 0 replies; 868+ messages in thread
From: Álvaro Herrera @ 2026-01-16 15:04 UTC (permalink / raw)
Introduce ternary reloption as a replacement for current
`vacuum_truncate` implementation. Remove the `vacuum_truncate_set`
separate flag and use TERNARY_UNSET instead.
This could also be used for other options such as `vacuum_index_cleanup`
and `buffering`, but lets get the scaffolding in first.
Discussion: https://postgr.es/m/3474141.usfYGdeWWP@thinkpad-pgpro
---
src/backend/access/common/reloptions.c | 137 ++++++++++++++++++-----
src/backend/commands/vacuum.c | 4 +-
src/include/access/reloptions.h | 27 ++---
src/include/postgres.h | 15 +++
src/include/utils/rel.h | 3 +-
src/test/regress/expected/reloptions.out | 36 ++++++
src/test/regress/sql/reloptions.sql | 21 ++++
src/tools/pgindent/typedefs.list | 2 +
8 files changed, 202 insertions(+), 43 deletions(-)
diff --git a/src/backend/access/common/reloptions.c b/src/backend/access/common/reloptions.c
index 0b83f98ed5f..6f1f577581a 100644
--- a/src/backend/access/common/reloptions.c
+++ b/src/backend/access/common/reloptions.c
@@ -40,9 +40,9 @@
*
* To add an option:
*
- * (i) decide on a type (bool, integer, real, enum, string), name, default
- * value, upper and lower bounds (if applicable); for strings, consider a
- * validation routine.
+ * (i) decide on a type (bool, ternary, integer, real, enum, string), name,
+ * default value, upper and lower bounds (if applicable); for strings,
+ * consider a validation routine.
* (ii) add a record below (or use add_<type>_reloption).
* (iii) add it to the appropriate options struct (perhaps StdRdOptions)
* (iv) add it to the appropriate handling routine (perhaps
@@ -147,15 +147,6 @@ static relopt_bool boolRelOpts[] =
},
false
},
- {
- {
- "vacuum_truncate",
- "Enables vacuum to truncate empty pages at the end of this table",
- RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
- ShareUpdateExclusiveLock
- },
- true
- },
{
{
"deduplicate_items",
@@ -170,6 +161,25 @@ static relopt_bool boolRelOpts[] =
{{NULL}}
};
+static relopt_ternary ternaryRelOpts[] =
+{
+ {
+ {
+ "vacuum_truncate",
+ "Enables vacuum to truncate empty pages at the end of this table",
+ RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
+ ShareUpdateExclusiveLock
+ },
+ TERNARY_UNSET
+ },
+ /* list terminator */
+ {
+ {
+ NULL
+ }
+ }
+};
+
static relopt_int intRelOpts[] =
{
{
@@ -609,6 +619,13 @@ initialize_reloptions(void)
boolRelOpts[i].gen.lockmode));
j++;
}
+ for (i = 0; ternaryRelOpts[i].gen.name; i++)
+ {
+ Assert(DoLockModesConflict(ternaryRelOpts[i].gen.lockmode,
+ ternaryRelOpts[i].gen.lockmode));
+ j++;
+ }
+
for (i = 0; intRelOpts[i].gen.name; i++)
{
Assert(DoLockModesConflict(intRelOpts[i].gen.lockmode,
@@ -649,6 +666,14 @@ initialize_reloptions(void)
j++;
}
+ for (i = 0; ternaryRelOpts[i].gen.name; i++)
+ {
+ relOpts[j] = &ternaryRelOpts[i].gen;
+ relOpts[j]->type = RELOPT_TYPE_TERNARY;
+ relOpts[j]->namelen = strlen(relOpts[j]->name);
+ j++;
+ }
+
for (i = 0; intRelOpts[i].gen.name; i++)
{
relOpts[j] = &intRelOpts[i].gen;
@@ -809,6 +834,9 @@ allocate_reloption(bits32 kinds, int type, const char *name, const char *desc,
case RELOPT_TYPE_BOOL:
size = sizeof(relopt_bool);
break;
+ case RELOPT_TYPE_TERNARY:
+ size = sizeof(relopt_ternary);
+ break;
case RELOPT_TYPE_INT:
size = sizeof(relopt_int);
break;
@@ -892,6 +920,57 @@ add_local_bool_reloption(local_relopts *relopts, const char *name,
add_local_reloption(relopts, (relopt_gen *) newoption, offset);
}
+/*
+ * init_ternary_reloption
+ * Allocate and initialize a new ternary reloption
+ */
+static relopt_ternary *
+init_ternary_reloption(bits32 kinds, const char *name, const char *desc,
+ pg_ternary default_val, LOCKMODE lockmode)
+{
+ relopt_ternary *newoption;
+
+ newoption = (relopt_ternary *)
+ allocate_reloption(kinds, RELOPT_TYPE_TERNARY, name, desc, lockmode);
+ newoption->default_val = default_val;
+
+ return newoption;
+}
+
+/*
+ * add_ternary_reloption
+ * Add a new ternary reloption
+ */
+void
+add_ternary_reloption(bits32 kinds, const char *name, const char *desc,
+ pg_ternary default_val, LOCKMODE lockmode)
+{
+ relopt_ternary *newoption;
+
+ newoption =
+ init_ternary_reloption(kinds, name, desc, default_val, lockmode);
+
+ add_reloption((relopt_gen *) newoption);
+}
+
+/*
+ * add_local_ternary_reloption
+ * Add a new ternary local reloption
+ *
+ * 'offset' is offset of ternary-typed field.
+ */
+void
+add_local_ternary_reloption(local_relopts *relopts, const char *name,
+ const char *desc, pg_ternary default_val,
+ int offset)
+{
+ relopt_ternary *newoption;
+
+ newoption =
+ init_ternary_reloption(RELOPT_KIND_LOCAL, name, desc, default_val, 0);
+
+ add_local_reloption(relopts, (relopt_gen *) newoption, offset);
+}
/*
* init_real_reloption
@@ -1626,6 +1705,19 @@ parse_one_reloption(relopt_value *option, char *text_str, int text_len,
option->gen->name, value)));
}
break;
+ case RELOPT_TYPE_TERNARY:
+ {
+ bool b;
+
+ parsed = parse_bool(value, &b);
+ option->values.ternary_val = b ? TERNARY_TRUE : TERNARY_FALSE;
+ if (validate && !parsed)
+ ereport(ERROR,
+ (errcode(ERRCODE_INVALID_PARAMETER_VALUE),
+ errmsg("invalid value for ternary option \"%s\": %s",
+ option->gen->name, value)));
+ }
+ break;
case RELOPT_TYPE_INT:
{
relopt_int *optint = (relopt_int *) option->gen;
@@ -1789,17 +1881,6 @@ fillRelOptions(void *rdopts, Size basesize,
char *itempos = ((char *) rdopts) + elems[j].offset;
char *string_val;
- /*
- * If isset_offset is provided, store whether the reloption is
- * set there.
- */
- if (elems[j].isset_offset > 0)
- {
- char *setpos = ((char *) rdopts) + elems[j].isset_offset;
-
- *(bool *) setpos = options[i].isset;
- }
-
switch (options[i].gen->type)
{
case RELOPT_TYPE_BOOL:
@@ -1807,6 +1888,11 @@ fillRelOptions(void *rdopts, Size basesize,
options[i].values.bool_val :
((relopt_bool *) options[i].gen)->default_val;
break;
+ case RELOPT_TYPE_TERNARY:
+ *(pg_ternary *) itempos = options[i].isset ?
+ options[i].values.ternary_val :
+ ((relopt_ternary *) options[i].gen)->default_val;
+ break;
case RELOPT_TYPE_INT:
*(int *) itempos = options[i].isset ?
options[i].values.int_val :
@@ -1923,8 +2009,8 @@ default_reloptions(Datum reloptions, bool validate, relopt_kind kind)
offsetof(StdRdOptions, parallel_workers)},
{"vacuum_index_cleanup", RELOPT_TYPE_ENUM,
offsetof(StdRdOptions, vacuum_index_cleanup)},
- {"vacuum_truncate", RELOPT_TYPE_BOOL,
- offsetof(StdRdOptions, vacuum_truncate), offsetof(StdRdOptions, vacuum_truncate_set)},
+ {"vacuum_truncate", RELOPT_TYPE_TERNARY,
+ offsetof(StdRdOptions, vacuum_truncate)},
{"vacuum_max_eager_freeze_failure_rate", RELOPT_TYPE_REAL,
offsetof(StdRdOptions, vacuum_max_eager_freeze_failure_rate)}
};
@@ -2004,7 +2090,6 @@ build_local_reloptions(local_relopts *relopts, Datum options, bool validate)
elems[i].optname = opt->option->name;
elems[i].opttype = opt->option->type;
elems[i].offset = opt->offset;
- elems[i].isset_offset = 0; /* not supported for local relopts yet */
i++;
}
diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c
index aa4fbec143f..696eab9bd97 100644
--- a/src/backend/commands/vacuum.c
+++ b/src/backend/commands/vacuum.c
@@ -2224,9 +2224,9 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams params,
{
StdRdOptions *opts = (StdRdOptions *) rel->rd_options;
- if (opts && opts->vacuum_truncate_set)
+ if (opts && opts->vacuum_truncate != TERNARY_UNSET)
{
- if (opts->vacuum_truncate)
+ if (opts->vacuum_truncate == TERNARY_TRUE)
params.truncate = VACOPTVALUE_ENABLED;
else
params.truncate = VACOPTVALUE_DISABLED;
diff --git a/src/include/access/reloptions.h b/src/include/access/reloptions.h
index 2f08e1b0cf0..dfbef2babf2 100644
--- a/src/include/access/reloptions.h
+++ b/src/include/access/reloptions.h
@@ -29,6 +29,7 @@
typedef enum relopt_type
{
RELOPT_TYPE_BOOL,
+ RELOPT_TYPE_TERNARY, /* on, off, unset */
RELOPT_TYPE_INT,
RELOPT_TYPE_REAL,
RELOPT_TYPE_ENUM,
@@ -80,6 +81,7 @@ typedef struct relopt_value
union
{
bool bool_val;
+ pg_ternary ternary_val;
int int_val;
double real_val;
int enum_val;
@@ -94,6 +96,12 @@ typedef struct relopt_bool
bool default_val;
} relopt_bool;
+typedef struct relopt_ternary
+{
+ relopt_gen gen;
+ int default_val;
+} relopt_ternary;
+
typedef struct relopt_int
{
relopt_gen gen;
@@ -152,19 +160,6 @@ typedef struct
const char *optname; /* option's name */
relopt_type opttype; /* option's datatype */
int offset; /* offset of field in result struct */
-
- /*
- * isset_offset is an optional offset of a field in the result struct that
- * stores whether the option is explicitly set for the relation or if it
- * just picked up the default value. In most cases, this can be
- * accomplished by giving the reloption a special out-of-range default
- * value (e.g., some integer reloptions use -2), but this isn't always
- * possible. For example, a Boolean reloption cannot be given an
- * out-of-range default, so we need another way to discover the source of
- * its value. This offset is only used if given a value greater than
- * zero.
- */
- int isset_offset;
} relopt_parse_elt;
/* Local reloption definition */
@@ -195,6 +190,9 @@ typedef struct local_relopts
extern relopt_kind add_reloption_kind(void);
extern void add_bool_reloption(bits32 kinds, const char *name, const char *desc,
bool default_val, LOCKMODE lockmode);
+extern void add_ternary_reloption(bits32 kinds, const char *name,
+ const char *desc, pg_ternary default_val,
+ LOCKMODE lockmode);
extern void add_int_reloption(bits32 kinds, const char *name, const char *desc,
int default_val, int min_val, int max_val,
LOCKMODE lockmode);
@@ -214,6 +212,9 @@ extern void register_reloptions_validator(local_relopts *relopts,
extern void add_local_bool_reloption(local_relopts *relopts, const char *name,
const char *desc, bool default_val,
int offset);
+extern void add_local_ternary_reloption(local_relopts *relopts,
+ const char *name, const char *desc,
+ pg_ternary default_val, int offset);
extern void add_local_int_reloption(local_relopts *relopts, const char *name,
const char *desc, int default_val,
int min_val, int max_val, int offset);
diff --git a/src/include/postgres.h b/src/include/postgres.h
index 7d93fbce709..47c88780776 100644
--- a/src/include/postgres.h
+++ b/src/include/postgres.h
@@ -543,6 +543,21 @@ Float8GetDatum(float8 X)
* ----------------------------------------------------------------
*/
+/*
+ * pg_ternary
+ * Boolean value with an extra "unset" value
+ *
+ * This enum can be used for values that want to distinguish between true,
+ * false, and unset.
+*/
+
+typedef enum pg_ternary
+{
+ TERNARY_FALSE = 0,
+ TERNARY_TRUE = 1,
+ TERNARY_UNSET = -1
+} pg_ternary;
+
/*
* NON_EXEC_STATIC: It's sometimes useful to define a variable or function
* that is normally static but extern when using EXEC_BACKEND (see
diff --git a/src/include/utils/rel.h b/src/include/utils/rel.h
index d03ab247788..5feb18a5373 100644
--- a/src/include/utils/rel.h
+++ b/src/include/utils/rel.h
@@ -347,8 +347,7 @@ typedef struct StdRdOptions
bool user_catalog_table; /* use as an additional catalog relation */
int parallel_workers; /* max number of parallel workers */
StdRdOptIndexCleanup vacuum_index_cleanup; /* controls index vacuuming */
- bool vacuum_truncate; /* enables vacuum to truncate a relation */
- bool vacuum_truncate_set; /* whether vacuum_truncate is set */
+ pg_ternary vacuum_truncate; /* enables vacuum to truncate a relation */
/*
* Fraction of pages in a relation that vacuum can eagerly scan and fail
diff --git a/src/test/regress/expected/reloptions.out b/src/test/regress/expected/reloptions.out
index 9de19b4e3f1..1c99f79ab01 100644
--- a/src/test/regress/expected/reloptions.out
+++ b/src/test/regress/expected/reloptions.out
@@ -98,6 +98,42 @@ SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass;
{fillfactor=13,autovacuum_enabled=false}
(1 row)
+-- Tests for future (FIXME) ternary options
+-- behave as boolean option: accept unassigned name and truncated value
+DROP TABLE reloptions_test;
+CREATE TABLE reloptions_test(i INT) WITH (vacuum_truncate);
+SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass;
+ reloptions
+------------------------
+ {vacuum_truncate=true}
+(1 row)
+
+DROP TABLE reloptions_test;
+CREATE TABLE reloptions_test(i INT) WITH (vacuum_truncate=FaLS);
+SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass;
+ reloptions
+------------------------
+ {vacuum_truncate=fals}
+(1 row)
+
+-- preferred "true" alias is used when storing
+DROP TABLE reloptions_test;
+CREATE TABLE reloptions_test(i INT) WITH (vacuum_index_cleanup=on);
+SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass;
+ reloptions
+---------------------------
+ {vacuum_index_cleanup=on}
+(1 row)
+
+-- custom "third" value is available
+DROP TABLE reloptions_test;
+CREATE TABLE reloptions_test(i INT) WITH (vacuum_index_cleanup=auto);
+SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass;
+ reloptions
+-----------------------------
+ {vacuum_index_cleanup=auto}
+(1 row)
+
-- Test vacuum_truncate option
DROP TABLE reloptions_test;
CREATE TEMP TABLE reloptions_test(i INT NOT NULL, j text)
diff --git a/src/test/regress/sql/reloptions.sql b/src/test/regress/sql/reloptions.sql
index 24fbe0b478d..f5980dafcbc 100644
--- a/src/test/regress/sql/reloptions.sql
+++ b/src/test/regress/sql/reloptions.sql
@@ -59,6 +59,27 @@ UPDATE pg_class
ALTER TABLE reloptions_test RESET (illegal_option);
SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass;
+-- Tests for future (FIXME) ternary options
+
+-- behave as boolean option: accept unassigned name and truncated value
+DROP TABLE reloptions_test;
+CREATE TABLE reloptions_test(i INT) WITH (vacuum_truncate);
+SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass;
+
+DROP TABLE reloptions_test;
+CREATE TABLE reloptions_test(i INT) WITH (vacuum_truncate=FaLS);
+SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass;
+
+-- preferred "true" alias is used when storing
+DROP TABLE reloptions_test;
+CREATE TABLE reloptions_test(i INT) WITH (vacuum_index_cleanup=on);
+SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass;
+
+-- custom "third" value is available
+DROP TABLE reloptions_test;
+CREATE TABLE reloptions_test(i INT) WITH (vacuum_index_cleanup=auto);
+SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass;
+
-- Test vacuum_truncate option
DROP TABLE reloptions_test;
diff --git a/src/tools/pgindent/typedefs.list b/src/tools/pgindent/typedefs.list
index 3f3a888fd0e..1c8610fd46c 100644
--- a/src/tools/pgindent/typedefs.list
+++ b/src/tools/pgindent/typedefs.list
@@ -3952,6 +3952,7 @@ pg_sha512_ctx
pg_snapshot
pg_special_case
pg_stack_base_t
+pg_ternary
pg_time_t
pg_time_usec_t
pg_tz
@@ -4079,6 +4080,7 @@ relopt_kind
relopt_parse_elt
relopt_real
relopt_string
+relopt_ternary
relopt_type
relopt_value
relopts_validator
--
2.47.3
--c36t3fndxhkzbl5z--
^ permalink raw reply [nested|flat] 868+ messages in thread
* [PATCH v3] Introduce ternary reloptions
@ 2026-01-16 15:04 Álvaro Herrera <[email protected]>
0 siblings, 0 replies; 868+ messages in thread
From: Álvaro Herrera @ 2026-01-16 15:04 UTC (permalink / raw)
Introduce ternary reloption as a replacement for current
`vacuum_truncate` implementation. Remove the `vacuum_truncate_set`
separate flag and use TERNARY_UNSET instead.
This could also be used for other options such as `vacuum_index_cleanup`
and `buffering`, but lets get the scaffolding in first.
Discussion: https://postgr.es/m/3474141.usfYGdeWWP@thinkpad-pgpro
---
src/backend/access/common/reloptions.c | 137 ++++++++++++++++++-----
src/backend/commands/vacuum.c | 4 +-
src/include/access/reloptions.h | 27 ++---
src/include/postgres.h | 15 +++
src/include/utils/rel.h | 3 +-
src/test/regress/expected/reloptions.out | 36 ++++++
src/test/regress/sql/reloptions.sql | 21 ++++
src/tools/pgindent/typedefs.list | 2 +
8 files changed, 202 insertions(+), 43 deletions(-)
diff --git a/src/backend/access/common/reloptions.c b/src/backend/access/common/reloptions.c
index 0b83f98ed5f..6f1f577581a 100644
--- a/src/backend/access/common/reloptions.c
+++ b/src/backend/access/common/reloptions.c
@@ -40,9 +40,9 @@
*
* To add an option:
*
- * (i) decide on a type (bool, integer, real, enum, string), name, default
- * value, upper and lower bounds (if applicable); for strings, consider a
- * validation routine.
+ * (i) decide on a type (bool, ternary, integer, real, enum, string), name,
+ * default value, upper and lower bounds (if applicable); for strings,
+ * consider a validation routine.
* (ii) add a record below (or use add_<type>_reloption).
* (iii) add it to the appropriate options struct (perhaps StdRdOptions)
* (iv) add it to the appropriate handling routine (perhaps
@@ -147,15 +147,6 @@ static relopt_bool boolRelOpts[] =
},
false
},
- {
- {
- "vacuum_truncate",
- "Enables vacuum to truncate empty pages at the end of this table",
- RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
- ShareUpdateExclusiveLock
- },
- true
- },
{
{
"deduplicate_items",
@@ -170,6 +161,25 @@ static relopt_bool boolRelOpts[] =
{{NULL}}
};
+static relopt_ternary ternaryRelOpts[] =
+{
+ {
+ {
+ "vacuum_truncate",
+ "Enables vacuum to truncate empty pages at the end of this table",
+ RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
+ ShareUpdateExclusiveLock
+ },
+ TERNARY_UNSET
+ },
+ /* list terminator */
+ {
+ {
+ NULL
+ }
+ }
+};
+
static relopt_int intRelOpts[] =
{
{
@@ -609,6 +619,13 @@ initialize_reloptions(void)
boolRelOpts[i].gen.lockmode));
j++;
}
+ for (i = 0; ternaryRelOpts[i].gen.name; i++)
+ {
+ Assert(DoLockModesConflict(ternaryRelOpts[i].gen.lockmode,
+ ternaryRelOpts[i].gen.lockmode));
+ j++;
+ }
+
for (i = 0; intRelOpts[i].gen.name; i++)
{
Assert(DoLockModesConflict(intRelOpts[i].gen.lockmode,
@@ -649,6 +666,14 @@ initialize_reloptions(void)
j++;
}
+ for (i = 0; ternaryRelOpts[i].gen.name; i++)
+ {
+ relOpts[j] = &ternaryRelOpts[i].gen;
+ relOpts[j]->type = RELOPT_TYPE_TERNARY;
+ relOpts[j]->namelen = strlen(relOpts[j]->name);
+ j++;
+ }
+
for (i = 0; intRelOpts[i].gen.name; i++)
{
relOpts[j] = &intRelOpts[i].gen;
@@ -809,6 +834,9 @@ allocate_reloption(bits32 kinds, int type, const char *name, const char *desc,
case RELOPT_TYPE_BOOL:
size = sizeof(relopt_bool);
break;
+ case RELOPT_TYPE_TERNARY:
+ size = sizeof(relopt_ternary);
+ break;
case RELOPT_TYPE_INT:
size = sizeof(relopt_int);
break;
@@ -892,6 +920,57 @@ add_local_bool_reloption(local_relopts *relopts, const char *name,
add_local_reloption(relopts, (relopt_gen *) newoption, offset);
}
+/*
+ * init_ternary_reloption
+ * Allocate and initialize a new ternary reloption
+ */
+static relopt_ternary *
+init_ternary_reloption(bits32 kinds, const char *name, const char *desc,
+ pg_ternary default_val, LOCKMODE lockmode)
+{
+ relopt_ternary *newoption;
+
+ newoption = (relopt_ternary *)
+ allocate_reloption(kinds, RELOPT_TYPE_TERNARY, name, desc, lockmode);
+ newoption->default_val = default_val;
+
+ return newoption;
+}
+
+/*
+ * add_ternary_reloption
+ * Add a new ternary reloption
+ */
+void
+add_ternary_reloption(bits32 kinds, const char *name, const char *desc,
+ pg_ternary default_val, LOCKMODE lockmode)
+{
+ relopt_ternary *newoption;
+
+ newoption =
+ init_ternary_reloption(kinds, name, desc, default_val, lockmode);
+
+ add_reloption((relopt_gen *) newoption);
+}
+
+/*
+ * add_local_ternary_reloption
+ * Add a new ternary local reloption
+ *
+ * 'offset' is offset of ternary-typed field.
+ */
+void
+add_local_ternary_reloption(local_relopts *relopts, const char *name,
+ const char *desc, pg_ternary default_val,
+ int offset)
+{
+ relopt_ternary *newoption;
+
+ newoption =
+ init_ternary_reloption(RELOPT_KIND_LOCAL, name, desc, default_val, 0);
+
+ add_local_reloption(relopts, (relopt_gen *) newoption, offset);
+}
/*
* init_real_reloption
@@ -1626,6 +1705,19 @@ parse_one_reloption(relopt_value *option, char *text_str, int text_len,
option->gen->name, value)));
}
break;
+ case RELOPT_TYPE_TERNARY:
+ {
+ bool b;
+
+ parsed = parse_bool(value, &b);
+ option->values.ternary_val = b ? TERNARY_TRUE : TERNARY_FALSE;
+ if (validate && !parsed)
+ ereport(ERROR,
+ (errcode(ERRCODE_INVALID_PARAMETER_VALUE),
+ errmsg("invalid value for ternary option \"%s\": %s",
+ option->gen->name, value)));
+ }
+ break;
case RELOPT_TYPE_INT:
{
relopt_int *optint = (relopt_int *) option->gen;
@@ -1789,17 +1881,6 @@ fillRelOptions(void *rdopts, Size basesize,
char *itempos = ((char *) rdopts) + elems[j].offset;
char *string_val;
- /*
- * If isset_offset is provided, store whether the reloption is
- * set there.
- */
- if (elems[j].isset_offset > 0)
- {
- char *setpos = ((char *) rdopts) + elems[j].isset_offset;
-
- *(bool *) setpos = options[i].isset;
- }
-
switch (options[i].gen->type)
{
case RELOPT_TYPE_BOOL:
@@ -1807,6 +1888,11 @@ fillRelOptions(void *rdopts, Size basesize,
options[i].values.bool_val :
((relopt_bool *) options[i].gen)->default_val;
break;
+ case RELOPT_TYPE_TERNARY:
+ *(pg_ternary *) itempos = options[i].isset ?
+ options[i].values.ternary_val :
+ ((relopt_ternary *) options[i].gen)->default_val;
+ break;
case RELOPT_TYPE_INT:
*(int *) itempos = options[i].isset ?
options[i].values.int_val :
@@ -1923,8 +2009,8 @@ default_reloptions(Datum reloptions, bool validate, relopt_kind kind)
offsetof(StdRdOptions, parallel_workers)},
{"vacuum_index_cleanup", RELOPT_TYPE_ENUM,
offsetof(StdRdOptions, vacuum_index_cleanup)},
- {"vacuum_truncate", RELOPT_TYPE_BOOL,
- offsetof(StdRdOptions, vacuum_truncate), offsetof(StdRdOptions, vacuum_truncate_set)},
+ {"vacuum_truncate", RELOPT_TYPE_TERNARY,
+ offsetof(StdRdOptions, vacuum_truncate)},
{"vacuum_max_eager_freeze_failure_rate", RELOPT_TYPE_REAL,
offsetof(StdRdOptions, vacuum_max_eager_freeze_failure_rate)}
};
@@ -2004,7 +2090,6 @@ build_local_reloptions(local_relopts *relopts, Datum options, bool validate)
elems[i].optname = opt->option->name;
elems[i].opttype = opt->option->type;
elems[i].offset = opt->offset;
- elems[i].isset_offset = 0; /* not supported for local relopts yet */
i++;
}
diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c
index aa4fbec143f..696eab9bd97 100644
--- a/src/backend/commands/vacuum.c
+++ b/src/backend/commands/vacuum.c
@@ -2224,9 +2224,9 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams params,
{
StdRdOptions *opts = (StdRdOptions *) rel->rd_options;
- if (opts && opts->vacuum_truncate_set)
+ if (opts && opts->vacuum_truncate != TERNARY_UNSET)
{
- if (opts->vacuum_truncate)
+ if (opts->vacuum_truncate == TERNARY_TRUE)
params.truncate = VACOPTVALUE_ENABLED;
else
params.truncate = VACOPTVALUE_DISABLED;
diff --git a/src/include/access/reloptions.h b/src/include/access/reloptions.h
index 2f08e1b0cf0..dfbef2babf2 100644
--- a/src/include/access/reloptions.h
+++ b/src/include/access/reloptions.h
@@ -29,6 +29,7 @@
typedef enum relopt_type
{
RELOPT_TYPE_BOOL,
+ RELOPT_TYPE_TERNARY, /* on, off, unset */
RELOPT_TYPE_INT,
RELOPT_TYPE_REAL,
RELOPT_TYPE_ENUM,
@@ -80,6 +81,7 @@ typedef struct relopt_value
union
{
bool bool_val;
+ pg_ternary ternary_val;
int int_val;
double real_val;
int enum_val;
@@ -94,6 +96,12 @@ typedef struct relopt_bool
bool default_val;
} relopt_bool;
+typedef struct relopt_ternary
+{
+ relopt_gen gen;
+ int default_val;
+} relopt_ternary;
+
typedef struct relopt_int
{
relopt_gen gen;
@@ -152,19 +160,6 @@ typedef struct
const char *optname; /* option's name */
relopt_type opttype; /* option's datatype */
int offset; /* offset of field in result struct */
-
- /*
- * isset_offset is an optional offset of a field in the result struct that
- * stores whether the option is explicitly set for the relation or if it
- * just picked up the default value. In most cases, this can be
- * accomplished by giving the reloption a special out-of-range default
- * value (e.g., some integer reloptions use -2), but this isn't always
- * possible. For example, a Boolean reloption cannot be given an
- * out-of-range default, so we need another way to discover the source of
- * its value. This offset is only used if given a value greater than
- * zero.
- */
- int isset_offset;
} relopt_parse_elt;
/* Local reloption definition */
@@ -195,6 +190,9 @@ typedef struct local_relopts
extern relopt_kind add_reloption_kind(void);
extern void add_bool_reloption(bits32 kinds, const char *name, const char *desc,
bool default_val, LOCKMODE lockmode);
+extern void add_ternary_reloption(bits32 kinds, const char *name,
+ const char *desc, pg_ternary default_val,
+ LOCKMODE lockmode);
extern void add_int_reloption(bits32 kinds, const char *name, const char *desc,
int default_val, int min_val, int max_val,
LOCKMODE lockmode);
@@ -214,6 +212,9 @@ extern void register_reloptions_validator(local_relopts *relopts,
extern void add_local_bool_reloption(local_relopts *relopts, const char *name,
const char *desc, bool default_val,
int offset);
+extern void add_local_ternary_reloption(local_relopts *relopts,
+ const char *name, const char *desc,
+ pg_ternary default_val, int offset);
extern void add_local_int_reloption(local_relopts *relopts, const char *name,
const char *desc, int default_val,
int min_val, int max_val, int offset);
diff --git a/src/include/postgres.h b/src/include/postgres.h
index 7d93fbce709..47c88780776 100644
--- a/src/include/postgres.h
+++ b/src/include/postgres.h
@@ -543,6 +543,21 @@ Float8GetDatum(float8 X)
* ----------------------------------------------------------------
*/
+/*
+ * pg_ternary
+ * Boolean value with an extra "unset" value
+ *
+ * This enum can be used for values that want to distinguish between true,
+ * false, and unset.
+*/
+
+typedef enum pg_ternary
+{
+ TERNARY_FALSE = 0,
+ TERNARY_TRUE = 1,
+ TERNARY_UNSET = -1
+} pg_ternary;
+
/*
* NON_EXEC_STATIC: It's sometimes useful to define a variable or function
* that is normally static but extern when using EXEC_BACKEND (see
diff --git a/src/include/utils/rel.h b/src/include/utils/rel.h
index d03ab247788..5feb18a5373 100644
--- a/src/include/utils/rel.h
+++ b/src/include/utils/rel.h
@@ -347,8 +347,7 @@ typedef struct StdRdOptions
bool user_catalog_table; /* use as an additional catalog relation */
int parallel_workers; /* max number of parallel workers */
StdRdOptIndexCleanup vacuum_index_cleanup; /* controls index vacuuming */
- bool vacuum_truncate; /* enables vacuum to truncate a relation */
- bool vacuum_truncate_set; /* whether vacuum_truncate is set */
+ pg_ternary vacuum_truncate; /* enables vacuum to truncate a relation */
/*
* Fraction of pages in a relation that vacuum can eagerly scan and fail
diff --git a/src/test/regress/expected/reloptions.out b/src/test/regress/expected/reloptions.out
index 9de19b4e3f1..1c99f79ab01 100644
--- a/src/test/regress/expected/reloptions.out
+++ b/src/test/regress/expected/reloptions.out
@@ -98,6 +98,42 @@ SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass;
{fillfactor=13,autovacuum_enabled=false}
(1 row)
+-- Tests for future (FIXME) ternary options
+-- behave as boolean option: accept unassigned name and truncated value
+DROP TABLE reloptions_test;
+CREATE TABLE reloptions_test(i INT) WITH (vacuum_truncate);
+SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass;
+ reloptions
+------------------------
+ {vacuum_truncate=true}
+(1 row)
+
+DROP TABLE reloptions_test;
+CREATE TABLE reloptions_test(i INT) WITH (vacuum_truncate=FaLS);
+SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass;
+ reloptions
+------------------------
+ {vacuum_truncate=fals}
+(1 row)
+
+-- preferred "true" alias is used when storing
+DROP TABLE reloptions_test;
+CREATE TABLE reloptions_test(i INT) WITH (vacuum_index_cleanup=on);
+SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass;
+ reloptions
+---------------------------
+ {vacuum_index_cleanup=on}
+(1 row)
+
+-- custom "third" value is available
+DROP TABLE reloptions_test;
+CREATE TABLE reloptions_test(i INT) WITH (vacuum_index_cleanup=auto);
+SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass;
+ reloptions
+-----------------------------
+ {vacuum_index_cleanup=auto}
+(1 row)
+
-- Test vacuum_truncate option
DROP TABLE reloptions_test;
CREATE TEMP TABLE reloptions_test(i INT NOT NULL, j text)
diff --git a/src/test/regress/sql/reloptions.sql b/src/test/regress/sql/reloptions.sql
index 24fbe0b478d..f5980dafcbc 100644
--- a/src/test/regress/sql/reloptions.sql
+++ b/src/test/regress/sql/reloptions.sql
@@ -59,6 +59,27 @@ UPDATE pg_class
ALTER TABLE reloptions_test RESET (illegal_option);
SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass;
+-- Tests for future (FIXME) ternary options
+
+-- behave as boolean option: accept unassigned name and truncated value
+DROP TABLE reloptions_test;
+CREATE TABLE reloptions_test(i INT) WITH (vacuum_truncate);
+SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass;
+
+DROP TABLE reloptions_test;
+CREATE TABLE reloptions_test(i INT) WITH (vacuum_truncate=FaLS);
+SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass;
+
+-- preferred "true" alias is used when storing
+DROP TABLE reloptions_test;
+CREATE TABLE reloptions_test(i INT) WITH (vacuum_index_cleanup=on);
+SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass;
+
+-- custom "third" value is available
+DROP TABLE reloptions_test;
+CREATE TABLE reloptions_test(i INT) WITH (vacuum_index_cleanup=auto);
+SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass;
+
-- Test vacuum_truncate option
DROP TABLE reloptions_test;
diff --git a/src/tools/pgindent/typedefs.list b/src/tools/pgindent/typedefs.list
index 3f3a888fd0e..1c8610fd46c 100644
--- a/src/tools/pgindent/typedefs.list
+++ b/src/tools/pgindent/typedefs.list
@@ -3952,6 +3952,7 @@ pg_sha512_ctx
pg_snapshot
pg_special_case
pg_stack_base_t
+pg_ternary
pg_time_t
pg_time_usec_t
pg_tz
@@ -4079,6 +4080,7 @@ relopt_kind
relopt_parse_elt
relopt_real
relopt_string
+relopt_ternary
relopt_type
relopt_value
relopts_validator
--
2.47.3
--c36t3fndxhkzbl5z--
^ permalink raw reply [nested|flat] 868+ messages in thread
* [PATCH v3] Introduce ternary reloptions
@ 2026-01-16 15:04 Álvaro Herrera <[email protected]>
0 siblings, 0 replies; 868+ messages in thread
From: Álvaro Herrera @ 2026-01-16 15:04 UTC (permalink / raw)
Introduce ternary reloption as a replacement for current
`vacuum_truncate` implementation. Remove the `vacuum_truncate_set`
separate flag and use TERNARY_UNSET instead.
This could also be used for other options such as `vacuum_index_cleanup`
and `buffering`, but lets get the scaffolding in first.
Discussion: https://postgr.es/m/3474141.usfYGdeWWP@thinkpad-pgpro
---
src/backend/access/common/reloptions.c | 137 ++++++++++++++++++-----
src/backend/commands/vacuum.c | 4 +-
src/include/access/reloptions.h | 27 ++---
src/include/postgres.h | 15 +++
src/include/utils/rel.h | 3 +-
src/test/regress/expected/reloptions.out | 36 ++++++
src/test/regress/sql/reloptions.sql | 21 ++++
src/tools/pgindent/typedefs.list | 2 +
8 files changed, 202 insertions(+), 43 deletions(-)
diff --git a/src/backend/access/common/reloptions.c b/src/backend/access/common/reloptions.c
index 0b83f98ed5f..6f1f577581a 100644
--- a/src/backend/access/common/reloptions.c
+++ b/src/backend/access/common/reloptions.c
@@ -40,9 +40,9 @@
*
* To add an option:
*
- * (i) decide on a type (bool, integer, real, enum, string), name, default
- * value, upper and lower bounds (if applicable); for strings, consider a
- * validation routine.
+ * (i) decide on a type (bool, ternary, integer, real, enum, string), name,
+ * default value, upper and lower bounds (if applicable); for strings,
+ * consider a validation routine.
* (ii) add a record below (or use add_<type>_reloption).
* (iii) add it to the appropriate options struct (perhaps StdRdOptions)
* (iv) add it to the appropriate handling routine (perhaps
@@ -147,15 +147,6 @@ static relopt_bool boolRelOpts[] =
},
false
},
- {
- {
- "vacuum_truncate",
- "Enables vacuum to truncate empty pages at the end of this table",
- RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
- ShareUpdateExclusiveLock
- },
- true
- },
{
{
"deduplicate_items",
@@ -170,6 +161,25 @@ static relopt_bool boolRelOpts[] =
{{NULL}}
};
+static relopt_ternary ternaryRelOpts[] =
+{
+ {
+ {
+ "vacuum_truncate",
+ "Enables vacuum to truncate empty pages at the end of this table",
+ RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
+ ShareUpdateExclusiveLock
+ },
+ TERNARY_UNSET
+ },
+ /* list terminator */
+ {
+ {
+ NULL
+ }
+ }
+};
+
static relopt_int intRelOpts[] =
{
{
@@ -609,6 +619,13 @@ initialize_reloptions(void)
boolRelOpts[i].gen.lockmode));
j++;
}
+ for (i = 0; ternaryRelOpts[i].gen.name; i++)
+ {
+ Assert(DoLockModesConflict(ternaryRelOpts[i].gen.lockmode,
+ ternaryRelOpts[i].gen.lockmode));
+ j++;
+ }
+
for (i = 0; intRelOpts[i].gen.name; i++)
{
Assert(DoLockModesConflict(intRelOpts[i].gen.lockmode,
@@ -649,6 +666,14 @@ initialize_reloptions(void)
j++;
}
+ for (i = 0; ternaryRelOpts[i].gen.name; i++)
+ {
+ relOpts[j] = &ternaryRelOpts[i].gen;
+ relOpts[j]->type = RELOPT_TYPE_TERNARY;
+ relOpts[j]->namelen = strlen(relOpts[j]->name);
+ j++;
+ }
+
for (i = 0; intRelOpts[i].gen.name; i++)
{
relOpts[j] = &intRelOpts[i].gen;
@@ -809,6 +834,9 @@ allocate_reloption(bits32 kinds, int type, const char *name, const char *desc,
case RELOPT_TYPE_BOOL:
size = sizeof(relopt_bool);
break;
+ case RELOPT_TYPE_TERNARY:
+ size = sizeof(relopt_ternary);
+ break;
case RELOPT_TYPE_INT:
size = sizeof(relopt_int);
break;
@@ -892,6 +920,57 @@ add_local_bool_reloption(local_relopts *relopts, const char *name,
add_local_reloption(relopts, (relopt_gen *) newoption, offset);
}
+/*
+ * init_ternary_reloption
+ * Allocate and initialize a new ternary reloption
+ */
+static relopt_ternary *
+init_ternary_reloption(bits32 kinds, const char *name, const char *desc,
+ pg_ternary default_val, LOCKMODE lockmode)
+{
+ relopt_ternary *newoption;
+
+ newoption = (relopt_ternary *)
+ allocate_reloption(kinds, RELOPT_TYPE_TERNARY, name, desc, lockmode);
+ newoption->default_val = default_val;
+
+ return newoption;
+}
+
+/*
+ * add_ternary_reloption
+ * Add a new ternary reloption
+ */
+void
+add_ternary_reloption(bits32 kinds, const char *name, const char *desc,
+ pg_ternary default_val, LOCKMODE lockmode)
+{
+ relopt_ternary *newoption;
+
+ newoption =
+ init_ternary_reloption(kinds, name, desc, default_val, lockmode);
+
+ add_reloption((relopt_gen *) newoption);
+}
+
+/*
+ * add_local_ternary_reloption
+ * Add a new ternary local reloption
+ *
+ * 'offset' is offset of ternary-typed field.
+ */
+void
+add_local_ternary_reloption(local_relopts *relopts, const char *name,
+ const char *desc, pg_ternary default_val,
+ int offset)
+{
+ relopt_ternary *newoption;
+
+ newoption =
+ init_ternary_reloption(RELOPT_KIND_LOCAL, name, desc, default_val, 0);
+
+ add_local_reloption(relopts, (relopt_gen *) newoption, offset);
+}
/*
* init_real_reloption
@@ -1626,6 +1705,19 @@ parse_one_reloption(relopt_value *option, char *text_str, int text_len,
option->gen->name, value)));
}
break;
+ case RELOPT_TYPE_TERNARY:
+ {
+ bool b;
+
+ parsed = parse_bool(value, &b);
+ option->values.ternary_val = b ? TERNARY_TRUE : TERNARY_FALSE;
+ if (validate && !parsed)
+ ereport(ERROR,
+ (errcode(ERRCODE_INVALID_PARAMETER_VALUE),
+ errmsg("invalid value for ternary option \"%s\": %s",
+ option->gen->name, value)));
+ }
+ break;
case RELOPT_TYPE_INT:
{
relopt_int *optint = (relopt_int *) option->gen;
@@ -1789,17 +1881,6 @@ fillRelOptions(void *rdopts, Size basesize,
char *itempos = ((char *) rdopts) + elems[j].offset;
char *string_val;
- /*
- * If isset_offset is provided, store whether the reloption is
- * set there.
- */
- if (elems[j].isset_offset > 0)
- {
- char *setpos = ((char *) rdopts) + elems[j].isset_offset;
-
- *(bool *) setpos = options[i].isset;
- }
-
switch (options[i].gen->type)
{
case RELOPT_TYPE_BOOL:
@@ -1807,6 +1888,11 @@ fillRelOptions(void *rdopts, Size basesize,
options[i].values.bool_val :
((relopt_bool *) options[i].gen)->default_val;
break;
+ case RELOPT_TYPE_TERNARY:
+ *(pg_ternary *) itempos = options[i].isset ?
+ options[i].values.ternary_val :
+ ((relopt_ternary *) options[i].gen)->default_val;
+ break;
case RELOPT_TYPE_INT:
*(int *) itempos = options[i].isset ?
options[i].values.int_val :
@@ -1923,8 +2009,8 @@ default_reloptions(Datum reloptions, bool validate, relopt_kind kind)
offsetof(StdRdOptions, parallel_workers)},
{"vacuum_index_cleanup", RELOPT_TYPE_ENUM,
offsetof(StdRdOptions, vacuum_index_cleanup)},
- {"vacuum_truncate", RELOPT_TYPE_BOOL,
- offsetof(StdRdOptions, vacuum_truncate), offsetof(StdRdOptions, vacuum_truncate_set)},
+ {"vacuum_truncate", RELOPT_TYPE_TERNARY,
+ offsetof(StdRdOptions, vacuum_truncate)},
{"vacuum_max_eager_freeze_failure_rate", RELOPT_TYPE_REAL,
offsetof(StdRdOptions, vacuum_max_eager_freeze_failure_rate)}
};
@@ -2004,7 +2090,6 @@ build_local_reloptions(local_relopts *relopts, Datum options, bool validate)
elems[i].optname = opt->option->name;
elems[i].opttype = opt->option->type;
elems[i].offset = opt->offset;
- elems[i].isset_offset = 0; /* not supported for local relopts yet */
i++;
}
diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c
index aa4fbec143f..696eab9bd97 100644
--- a/src/backend/commands/vacuum.c
+++ b/src/backend/commands/vacuum.c
@@ -2224,9 +2224,9 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams params,
{
StdRdOptions *opts = (StdRdOptions *) rel->rd_options;
- if (opts && opts->vacuum_truncate_set)
+ if (opts && opts->vacuum_truncate != TERNARY_UNSET)
{
- if (opts->vacuum_truncate)
+ if (opts->vacuum_truncate == TERNARY_TRUE)
params.truncate = VACOPTVALUE_ENABLED;
else
params.truncate = VACOPTVALUE_DISABLED;
diff --git a/src/include/access/reloptions.h b/src/include/access/reloptions.h
index 2f08e1b0cf0..dfbef2babf2 100644
--- a/src/include/access/reloptions.h
+++ b/src/include/access/reloptions.h
@@ -29,6 +29,7 @@
typedef enum relopt_type
{
RELOPT_TYPE_BOOL,
+ RELOPT_TYPE_TERNARY, /* on, off, unset */
RELOPT_TYPE_INT,
RELOPT_TYPE_REAL,
RELOPT_TYPE_ENUM,
@@ -80,6 +81,7 @@ typedef struct relopt_value
union
{
bool bool_val;
+ pg_ternary ternary_val;
int int_val;
double real_val;
int enum_val;
@@ -94,6 +96,12 @@ typedef struct relopt_bool
bool default_val;
} relopt_bool;
+typedef struct relopt_ternary
+{
+ relopt_gen gen;
+ int default_val;
+} relopt_ternary;
+
typedef struct relopt_int
{
relopt_gen gen;
@@ -152,19 +160,6 @@ typedef struct
const char *optname; /* option's name */
relopt_type opttype; /* option's datatype */
int offset; /* offset of field in result struct */
-
- /*
- * isset_offset is an optional offset of a field in the result struct that
- * stores whether the option is explicitly set for the relation or if it
- * just picked up the default value. In most cases, this can be
- * accomplished by giving the reloption a special out-of-range default
- * value (e.g., some integer reloptions use -2), but this isn't always
- * possible. For example, a Boolean reloption cannot be given an
- * out-of-range default, so we need another way to discover the source of
- * its value. This offset is only used if given a value greater than
- * zero.
- */
- int isset_offset;
} relopt_parse_elt;
/* Local reloption definition */
@@ -195,6 +190,9 @@ typedef struct local_relopts
extern relopt_kind add_reloption_kind(void);
extern void add_bool_reloption(bits32 kinds, const char *name, const char *desc,
bool default_val, LOCKMODE lockmode);
+extern void add_ternary_reloption(bits32 kinds, const char *name,
+ const char *desc, pg_ternary default_val,
+ LOCKMODE lockmode);
extern void add_int_reloption(bits32 kinds, const char *name, const char *desc,
int default_val, int min_val, int max_val,
LOCKMODE lockmode);
@@ -214,6 +212,9 @@ extern void register_reloptions_validator(local_relopts *relopts,
extern void add_local_bool_reloption(local_relopts *relopts, const char *name,
const char *desc, bool default_val,
int offset);
+extern void add_local_ternary_reloption(local_relopts *relopts,
+ const char *name, const char *desc,
+ pg_ternary default_val, int offset);
extern void add_local_int_reloption(local_relopts *relopts, const char *name,
const char *desc, int default_val,
int min_val, int max_val, int offset);
diff --git a/src/include/postgres.h b/src/include/postgres.h
index 7d93fbce709..47c88780776 100644
--- a/src/include/postgres.h
+++ b/src/include/postgres.h
@@ -543,6 +543,21 @@ Float8GetDatum(float8 X)
* ----------------------------------------------------------------
*/
+/*
+ * pg_ternary
+ * Boolean value with an extra "unset" value
+ *
+ * This enum can be used for values that want to distinguish between true,
+ * false, and unset.
+*/
+
+typedef enum pg_ternary
+{
+ TERNARY_FALSE = 0,
+ TERNARY_TRUE = 1,
+ TERNARY_UNSET = -1
+} pg_ternary;
+
/*
* NON_EXEC_STATIC: It's sometimes useful to define a variable or function
* that is normally static but extern when using EXEC_BACKEND (see
diff --git a/src/include/utils/rel.h b/src/include/utils/rel.h
index d03ab247788..5feb18a5373 100644
--- a/src/include/utils/rel.h
+++ b/src/include/utils/rel.h
@@ -347,8 +347,7 @@ typedef struct StdRdOptions
bool user_catalog_table; /* use as an additional catalog relation */
int parallel_workers; /* max number of parallel workers */
StdRdOptIndexCleanup vacuum_index_cleanup; /* controls index vacuuming */
- bool vacuum_truncate; /* enables vacuum to truncate a relation */
- bool vacuum_truncate_set; /* whether vacuum_truncate is set */
+ pg_ternary vacuum_truncate; /* enables vacuum to truncate a relation */
/*
* Fraction of pages in a relation that vacuum can eagerly scan and fail
diff --git a/src/test/regress/expected/reloptions.out b/src/test/regress/expected/reloptions.out
index 9de19b4e3f1..1c99f79ab01 100644
--- a/src/test/regress/expected/reloptions.out
+++ b/src/test/regress/expected/reloptions.out
@@ -98,6 +98,42 @@ SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass;
{fillfactor=13,autovacuum_enabled=false}
(1 row)
+-- Tests for future (FIXME) ternary options
+-- behave as boolean option: accept unassigned name and truncated value
+DROP TABLE reloptions_test;
+CREATE TABLE reloptions_test(i INT) WITH (vacuum_truncate);
+SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass;
+ reloptions
+------------------------
+ {vacuum_truncate=true}
+(1 row)
+
+DROP TABLE reloptions_test;
+CREATE TABLE reloptions_test(i INT) WITH (vacuum_truncate=FaLS);
+SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass;
+ reloptions
+------------------------
+ {vacuum_truncate=fals}
+(1 row)
+
+-- preferred "true" alias is used when storing
+DROP TABLE reloptions_test;
+CREATE TABLE reloptions_test(i INT) WITH (vacuum_index_cleanup=on);
+SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass;
+ reloptions
+---------------------------
+ {vacuum_index_cleanup=on}
+(1 row)
+
+-- custom "third" value is available
+DROP TABLE reloptions_test;
+CREATE TABLE reloptions_test(i INT) WITH (vacuum_index_cleanup=auto);
+SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass;
+ reloptions
+-----------------------------
+ {vacuum_index_cleanup=auto}
+(1 row)
+
-- Test vacuum_truncate option
DROP TABLE reloptions_test;
CREATE TEMP TABLE reloptions_test(i INT NOT NULL, j text)
diff --git a/src/test/regress/sql/reloptions.sql b/src/test/regress/sql/reloptions.sql
index 24fbe0b478d..f5980dafcbc 100644
--- a/src/test/regress/sql/reloptions.sql
+++ b/src/test/regress/sql/reloptions.sql
@@ -59,6 +59,27 @@ UPDATE pg_class
ALTER TABLE reloptions_test RESET (illegal_option);
SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass;
+-- Tests for future (FIXME) ternary options
+
+-- behave as boolean option: accept unassigned name and truncated value
+DROP TABLE reloptions_test;
+CREATE TABLE reloptions_test(i INT) WITH (vacuum_truncate);
+SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass;
+
+DROP TABLE reloptions_test;
+CREATE TABLE reloptions_test(i INT) WITH (vacuum_truncate=FaLS);
+SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass;
+
+-- preferred "true" alias is used when storing
+DROP TABLE reloptions_test;
+CREATE TABLE reloptions_test(i INT) WITH (vacuum_index_cleanup=on);
+SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass;
+
+-- custom "third" value is available
+DROP TABLE reloptions_test;
+CREATE TABLE reloptions_test(i INT) WITH (vacuum_index_cleanup=auto);
+SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass;
+
-- Test vacuum_truncate option
DROP TABLE reloptions_test;
diff --git a/src/tools/pgindent/typedefs.list b/src/tools/pgindent/typedefs.list
index 3f3a888fd0e..1c8610fd46c 100644
--- a/src/tools/pgindent/typedefs.list
+++ b/src/tools/pgindent/typedefs.list
@@ -3952,6 +3952,7 @@ pg_sha512_ctx
pg_snapshot
pg_special_case
pg_stack_base_t
+pg_ternary
pg_time_t
pg_time_usec_t
pg_tz
@@ -4079,6 +4080,7 @@ relopt_kind
relopt_parse_elt
relopt_real
relopt_string
+relopt_ternary
relopt_type
relopt_value
relopts_validator
--
2.47.3
--c36t3fndxhkzbl5z--
^ permalink raw reply [nested|flat] 868+ messages in thread
* [PATCH v3] Introduce ternary reloptions
@ 2026-01-16 15:04 Álvaro Herrera <[email protected]>
0 siblings, 0 replies; 868+ messages in thread
From: Álvaro Herrera @ 2026-01-16 15:04 UTC (permalink / raw)
Introduce ternary reloption as a replacement for current
`vacuum_truncate` implementation. Remove the `vacuum_truncate_set`
separate flag and use TERNARY_UNSET instead.
This could also be used for other options such as `vacuum_index_cleanup`
and `buffering`, but lets get the scaffolding in first.
Discussion: https://postgr.es/m/3474141.usfYGdeWWP@thinkpad-pgpro
---
src/backend/access/common/reloptions.c | 137 ++++++++++++++++++-----
src/backend/commands/vacuum.c | 4 +-
src/include/access/reloptions.h | 27 ++---
src/include/postgres.h | 15 +++
src/include/utils/rel.h | 3 +-
src/test/regress/expected/reloptions.out | 36 ++++++
src/test/regress/sql/reloptions.sql | 21 ++++
src/tools/pgindent/typedefs.list | 2 +
8 files changed, 202 insertions(+), 43 deletions(-)
diff --git a/src/backend/access/common/reloptions.c b/src/backend/access/common/reloptions.c
index 0b83f98ed5f..6f1f577581a 100644
--- a/src/backend/access/common/reloptions.c
+++ b/src/backend/access/common/reloptions.c
@@ -40,9 +40,9 @@
*
* To add an option:
*
- * (i) decide on a type (bool, integer, real, enum, string), name, default
- * value, upper and lower bounds (if applicable); for strings, consider a
- * validation routine.
+ * (i) decide on a type (bool, ternary, integer, real, enum, string), name,
+ * default value, upper and lower bounds (if applicable); for strings,
+ * consider a validation routine.
* (ii) add a record below (or use add_<type>_reloption).
* (iii) add it to the appropriate options struct (perhaps StdRdOptions)
* (iv) add it to the appropriate handling routine (perhaps
@@ -147,15 +147,6 @@ static relopt_bool boolRelOpts[] =
},
false
},
- {
- {
- "vacuum_truncate",
- "Enables vacuum to truncate empty pages at the end of this table",
- RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
- ShareUpdateExclusiveLock
- },
- true
- },
{
{
"deduplicate_items",
@@ -170,6 +161,25 @@ static relopt_bool boolRelOpts[] =
{{NULL}}
};
+static relopt_ternary ternaryRelOpts[] =
+{
+ {
+ {
+ "vacuum_truncate",
+ "Enables vacuum to truncate empty pages at the end of this table",
+ RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
+ ShareUpdateExclusiveLock
+ },
+ TERNARY_UNSET
+ },
+ /* list terminator */
+ {
+ {
+ NULL
+ }
+ }
+};
+
static relopt_int intRelOpts[] =
{
{
@@ -609,6 +619,13 @@ initialize_reloptions(void)
boolRelOpts[i].gen.lockmode));
j++;
}
+ for (i = 0; ternaryRelOpts[i].gen.name; i++)
+ {
+ Assert(DoLockModesConflict(ternaryRelOpts[i].gen.lockmode,
+ ternaryRelOpts[i].gen.lockmode));
+ j++;
+ }
+
for (i = 0; intRelOpts[i].gen.name; i++)
{
Assert(DoLockModesConflict(intRelOpts[i].gen.lockmode,
@@ -649,6 +666,14 @@ initialize_reloptions(void)
j++;
}
+ for (i = 0; ternaryRelOpts[i].gen.name; i++)
+ {
+ relOpts[j] = &ternaryRelOpts[i].gen;
+ relOpts[j]->type = RELOPT_TYPE_TERNARY;
+ relOpts[j]->namelen = strlen(relOpts[j]->name);
+ j++;
+ }
+
for (i = 0; intRelOpts[i].gen.name; i++)
{
relOpts[j] = &intRelOpts[i].gen;
@@ -809,6 +834,9 @@ allocate_reloption(bits32 kinds, int type, const char *name, const char *desc,
case RELOPT_TYPE_BOOL:
size = sizeof(relopt_bool);
break;
+ case RELOPT_TYPE_TERNARY:
+ size = sizeof(relopt_ternary);
+ break;
case RELOPT_TYPE_INT:
size = sizeof(relopt_int);
break;
@@ -892,6 +920,57 @@ add_local_bool_reloption(local_relopts *relopts, const char *name,
add_local_reloption(relopts, (relopt_gen *) newoption, offset);
}
+/*
+ * init_ternary_reloption
+ * Allocate and initialize a new ternary reloption
+ */
+static relopt_ternary *
+init_ternary_reloption(bits32 kinds, const char *name, const char *desc,
+ pg_ternary default_val, LOCKMODE lockmode)
+{
+ relopt_ternary *newoption;
+
+ newoption = (relopt_ternary *)
+ allocate_reloption(kinds, RELOPT_TYPE_TERNARY, name, desc, lockmode);
+ newoption->default_val = default_val;
+
+ return newoption;
+}
+
+/*
+ * add_ternary_reloption
+ * Add a new ternary reloption
+ */
+void
+add_ternary_reloption(bits32 kinds, const char *name, const char *desc,
+ pg_ternary default_val, LOCKMODE lockmode)
+{
+ relopt_ternary *newoption;
+
+ newoption =
+ init_ternary_reloption(kinds, name, desc, default_val, lockmode);
+
+ add_reloption((relopt_gen *) newoption);
+}
+
+/*
+ * add_local_ternary_reloption
+ * Add a new ternary local reloption
+ *
+ * 'offset' is offset of ternary-typed field.
+ */
+void
+add_local_ternary_reloption(local_relopts *relopts, const char *name,
+ const char *desc, pg_ternary default_val,
+ int offset)
+{
+ relopt_ternary *newoption;
+
+ newoption =
+ init_ternary_reloption(RELOPT_KIND_LOCAL, name, desc, default_val, 0);
+
+ add_local_reloption(relopts, (relopt_gen *) newoption, offset);
+}
/*
* init_real_reloption
@@ -1626,6 +1705,19 @@ parse_one_reloption(relopt_value *option, char *text_str, int text_len,
option->gen->name, value)));
}
break;
+ case RELOPT_TYPE_TERNARY:
+ {
+ bool b;
+
+ parsed = parse_bool(value, &b);
+ option->values.ternary_val = b ? TERNARY_TRUE : TERNARY_FALSE;
+ if (validate && !parsed)
+ ereport(ERROR,
+ (errcode(ERRCODE_INVALID_PARAMETER_VALUE),
+ errmsg("invalid value for ternary option \"%s\": %s",
+ option->gen->name, value)));
+ }
+ break;
case RELOPT_TYPE_INT:
{
relopt_int *optint = (relopt_int *) option->gen;
@@ -1789,17 +1881,6 @@ fillRelOptions(void *rdopts, Size basesize,
char *itempos = ((char *) rdopts) + elems[j].offset;
char *string_val;
- /*
- * If isset_offset is provided, store whether the reloption is
- * set there.
- */
- if (elems[j].isset_offset > 0)
- {
- char *setpos = ((char *) rdopts) + elems[j].isset_offset;
-
- *(bool *) setpos = options[i].isset;
- }
-
switch (options[i].gen->type)
{
case RELOPT_TYPE_BOOL:
@@ -1807,6 +1888,11 @@ fillRelOptions(void *rdopts, Size basesize,
options[i].values.bool_val :
((relopt_bool *) options[i].gen)->default_val;
break;
+ case RELOPT_TYPE_TERNARY:
+ *(pg_ternary *) itempos = options[i].isset ?
+ options[i].values.ternary_val :
+ ((relopt_ternary *) options[i].gen)->default_val;
+ break;
case RELOPT_TYPE_INT:
*(int *) itempos = options[i].isset ?
options[i].values.int_val :
@@ -1923,8 +2009,8 @@ default_reloptions(Datum reloptions, bool validate, relopt_kind kind)
offsetof(StdRdOptions, parallel_workers)},
{"vacuum_index_cleanup", RELOPT_TYPE_ENUM,
offsetof(StdRdOptions, vacuum_index_cleanup)},
- {"vacuum_truncate", RELOPT_TYPE_BOOL,
- offsetof(StdRdOptions, vacuum_truncate), offsetof(StdRdOptions, vacuum_truncate_set)},
+ {"vacuum_truncate", RELOPT_TYPE_TERNARY,
+ offsetof(StdRdOptions, vacuum_truncate)},
{"vacuum_max_eager_freeze_failure_rate", RELOPT_TYPE_REAL,
offsetof(StdRdOptions, vacuum_max_eager_freeze_failure_rate)}
};
@@ -2004,7 +2090,6 @@ build_local_reloptions(local_relopts *relopts, Datum options, bool validate)
elems[i].optname = opt->option->name;
elems[i].opttype = opt->option->type;
elems[i].offset = opt->offset;
- elems[i].isset_offset = 0; /* not supported for local relopts yet */
i++;
}
diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c
index aa4fbec143f..696eab9bd97 100644
--- a/src/backend/commands/vacuum.c
+++ b/src/backend/commands/vacuum.c
@@ -2224,9 +2224,9 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams params,
{
StdRdOptions *opts = (StdRdOptions *) rel->rd_options;
- if (opts && opts->vacuum_truncate_set)
+ if (opts && opts->vacuum_truncate != TERNARY_UNSET)
{
- if (opts->vacuum_truncate)
+ if (opts->vacuum_truncate == TERNARY_TRUE)
params.truncate = VACOPTVALUE_ENABLED;
else
params.truncate = VACOPTVALUE_DISABLED;
diff --git a/src/include/access/reloptions.h b/src/include/access/reloptions.h
index 2f08e1b0cf0..dfbef2babf2 100644
--- a/src/include/access/reloptions.h
+++ b/src/include/access/reloptions.h
@@ -29,6 +29,7 @@
typedef enum relopt_type
{
RELOPT_TYPE_BOOL,
+ RELOPT_TYPE_TERNARY, /* on, off, unset */
RELOPT_TYPE_INT,
RELOPT_TYPE_REAL,
RELOPT_TYPE_ENUM,
@@ -80,6 +81,7 @@ typedef struct relopt_value
union
{
bool bool_val;
+ pg_ternary ternary_val;
int int_val;
double real_val;
int enum_val;
@@ -94,6 +96,12 @@ typedef struct relopt_bool
bool default_val;
} relopt_bool;
+typedef struct relopt_ternary
+{
+ relopt_gen gen;
+ int default_val;
+} relopt_ternary;
+
typedef struct relopt_int
{
relopt_gen gen;
@@ -152,19 +160,6 @@ typedef struct
const char *optname; /* option's name */
relopt_type opttype; /* option's datatype */
int offset; /* offset of field in result struct */
-
- /*
- * isset_offset is an optional offset of a field in the result struct that
- * stores whether the option is explicitly set for the relation or if it
- * just picked up the default value. In most cases, this can be
- * accomplished by giving the reloption a special out-of-range default
- * value (e.g., some integer reloptions use -2), but this isn't always
- * possible. For example, a Boolean reloption cannot be given an
- * out-of-range default, so we need another way to discover the source of
- * its value. This offset is only used if given a value greater than
- * zero.
- */
- int isset_offset;
} relopt_parse_elt;
/* Local reloption definition */
@@ -195,6 +190,9 @@ typedef struct local_relopts
extern relopt_kind add_reloption_kind(void);
extern void add_bool_reloption(bits32 kinds, const char *name, const char *desc,
bool default_val, LOCKMODE lockmode);
+extern void add_ternary_reloption(bits32 kinds, const char *name,
+ const char *desc, pg_ternary default_val,
+ LOCKMODE lockmode);
extern void add_int_reloption(bits32 kinds, const char *name, const char *desc,
int default_val, int min_val, int max_val,
LOCKMODE lockmode);
@@ -214,6 +212,9 @@ extern void register_reloptions_validator(local_relopts *relopts,
extern void add_local_bool_reloption(local_relopts *relopts, const char *name,
const char *desc, bool default_val,
int offset);
+extern void add_local_ternary_reloption(local_relopts *relopts,
+ const char *name, const char *desc,
+ pg_ternary default_val, int offset);
extern void add_local_int_reloption(local_relopts *relopts, const char *name,
const char *desc, int default_val,
int min_val, int max_val, int offset);
diff --git a/src/include/postgres.h b/src/include/postgres.h
index 7d93fbce709..47c88780776 100644
--- a/src/include/postgres.h
+++ b/src/include/postgres.h
@@ -543,6 +543,21 @@ Float8GetDatum(float8 X)
* ----------------------------------------------------------------
*/
+/*
+ * pg_ternary
+ * Boolean value with an extra "unset" value
+ *
+ * This enum can be used for values that want to distinguish between true,
+ * false, and unset.
+*/
+
+typedef enum pg_ternary
+{
+ TERNARY_FALSE = 0,
+ TERNARY_TRUE = 1,
+ TERNARY_UNSET = -1
+} pg_ternary;
+
/*
* NON_EXEC_STATIC: It's sometimes useful to define a variable or function
* that is normally static but extern when using EXEC_BACKEND (see
diff --git a/src/include/utils/rel.h b/src/include/utils/rel.h
index d03ab247788..5feb18a5373 100644
--- a/src/include/utils/rel.h
+++ b/src/include/utils/rel.h
@@ -347,8 +347,7 @@ typedef struct StdRdOptions
bool user_catalog_table; /* use as an additional catalog relation */
int parallel_workers; /* max number of parallel workers */
StdRdOptIndexCleanup vacuum_index_cleanup; /* controls index vacuuming */
- bool vacuum_truncate; /* enables vacuum to truncate a relation */
- bool vacuum_truncate_set; /* whether vacuum_truncate is set */
+ pg_ternary vacuum_truncate; /* enables vacuum to truncate a relation */
/*
* Fraction of pages in a relation that vacuum can eagerly scan and fail
diff --git a/src/test/regress/expected/reloptions.out b/src/test/regress/expected/reloptions.out
index 9de19b4e3f1..1c99f79ab01 100644
--- a/src/test/regress/expected/reloptions.out
+++ b/src/test/regress/expected/reloptions.out
@@ -98,6 +98,42 @@ SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass;
{fillfactor=13,autovacuum_enabled=false}
(1 row)
+-- Tests for future (FIXME) ternary options
+-- behave as boolean option: accept unassigned name and truncated value
+DROP TABLE reloptions_test;
+CREATE TABLE reloptions_test(i INT) WITH (vacuum_truncate);
+SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass;
+ reloptions
+------------------------
+ {vacuum_truncate=true}
+(1 row)
+
+DROP TABLE reloptions_test;
+CREATE TABLE reloptions_test(i INT) WITH (vacuum_truncate=FaLS);
+SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass;
+ reloptions
+------------------------
+ {vacuum_truncate=fals}
+(1 row)
+
+-- preferred "true" alias is used when storing
+DROP TABLE reloptions_test;
+CREATE TABLE reloptions_test(i INT) WITH (vacuum_index_cleanup=on);
+SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass;
+ reloptions
+---------------------------
+ {vacuum_index_cleanup=on}
+(1 row)
+
+-- custom "third" value is available
+DROP TABLE reloptions_test;
+CREATE TABLE reloptions_test(i INT) WITH (vacuum_index_cleanup=auto);
+SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass;
+ reloptions
+-----------------------------
+ {vacuum_index_cleanup=auto}
+(1 row)
+
-- Test vacuum_truncate option
DROP TABLE reloptions_test;
CREATE TEMP TABLE reloptions_test(i INT NOT NULL, j text)
diff --git a/src/test/regress/sql/reloptions.sql b/src/test/regress/sql/reloptions.sql
index 24fbe0b478d..f5980dafcbc 100644
--- a/src/test/regress/sql/reloptions.sql
+++ b/src/test/regress/sql/reloptions.sql
@@ -59,6 +59,27 @@ UPDATE pg_class
ALTER TABLE reloptions_test RESET (illegal_option);
SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass;
+-- Tests for future (FIXME) ternary options
+
+-- behave as boolean option: accept unassigned name and truncated value
+DROP TABLE reloptions_test;
+CREATE TABLE reloptions_test(i INT) WITH (vacuum_truncate);
+SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass;
+
+DROP TABLE reloptions_test;
+CREATE TABLE reloptions_test(i INT) WITH (vacuum_truncate=FaLS);
+SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass;
+
+-- preferred "true" alias is used when storing
+DROP TABLE reloptions_test;
+CREATE TABLE reloptions_test(i INT) WITH (vacuum_index_cleanup=on);
+SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass;
+
+-- custom "third" value is available
+DROP TABLE reloptions_test;
+CREATE TABLE reloptions_test(i INT) WITH (vacuum_index_cleanup=auto);
+SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass;
+
-- Test vacuum_truncate option
DROP TABLE reloptions_test;
diff --git a/src/tools/pgindent/typedefs.list b/src/tools/pgindent/typedefs.list
index 3f3a888fd0e..1c8610fd46c 100644
--- a/src/tools/pgindent/typedefs.list
+++ b/src/tools/pgindent/typedefs.list
@@ -3952,6 +3952,7 @@ pg_sha512_ctx
pg_snapshot
pg_special_case
pg_stack_base_t
+pg_ternary
pg_time_t
pg_time_usec_t
pg_tz
@@ -4079,6 +4080,7 @@ relopt_kind
relopt_parse_elt
relopt_real
relopt_string
+relopt_ternary
relopt_type
relopt_value
relopts_validator
--
2.47.3
--c36t3fndxhkzbl5z--
^ permalink raw reply [nested|flat] 868+ messages in thread
* [PATCH v3] Introduce ternary reloptions
@ 2026-01-16 15:04 Álvaro Herrera <[email protected]>
0 siblings, 0 replies; 868+ messages in thread
From: Álvaro Herrera @ 2026-01-16 15:04 UTC (permalink / raw)
Introduce ternary reloption as a replacement for current
`vacuum_truncate` implementation. Remove the `vacuum_truncate_set`
separate flag and use TERNARY_UNSET instead.
This could also be used for other options such as `vacuum_index_cleanup`
and `buffering`, but lets get the scaffolding in first.
Discussion: https://postgr.es/m/3474141.usfYGdeWWP@thinkpad-pgpro
---
src/backend/access/common/reloptions.c | 137 ++++++++++++++++++-----
src/backend/commands/vacuum.c | 4 +-
src/include/access/reloptions.h | 27 ++---
src/include/postgres.h | 15 +++
src/include/utils/rel.h | 3 +-
src/test/regress/expected/reloptions.out | 36 ++++++
src/test/regress/sql/reloptions.sql | 21 ++++
src/tools/pgindent/typedefs.list | 2 +
8 files changed, 202 insertions(+), 43 deletions(-)
diff --git a/src/backend/access/common/reloptions.c b/src/backend/access/common/reloptions.c
index 0b83f98ed5f..6f1f577581a 100644
--- a/src/backend/access/common/reloptions.c
+++ b/src/backend/access/common/reloptions.c
@@ -40,9 +40,9 @@
*
* To add an option:
*
- * (i) decide on a type (bool, integer, real, enum, string), name, default
- * value, upper and lower bounds (if applicable); for strings, consider a
- * validation routine.
+ * (i) decide on a type (bool, ternary, integer, real, enum, string), name,
+ * default value, upper and lower bounds (if applicable); for strings,
+ * consider a validation routine.
* (ii) add a record below (or use add_<type>_reloption).
* (iii) add it to the appropriate options struct (perhaps StdRdOptions)
* (iv) add it to the appropriate handling routine (perhaps
@@ -147,15 +147,6 @@ static relopt_bool boolRelOpts[] =
},
false
},
- {
- {
- "vacuum_truncate",
- "Enables vacuum to truncate empty pages at the end of this table",
- RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
- ShareUpdateExclusiveLock
- },
- true
- },
{
{
"deduplicate_items",
@@ -170,6 +161,25 @@ static relopt_bool boolRelOpts[] =
{{NULL}}
};
+static relopt_ternary ternaryRelOpts[] =
+{
+ {
+ {
+ "vacuum_truncate",
+ "Enables vacuum to truncate empty pages at the end of this table",
+ RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
+ ShareUpdateExclusiveLock
+ },
+ TERNARY_UNSET
+ },
+ /* list terminator */
+ {
+ {
+ NULL
+ }
+ }
+};
+
static relopt_int intRelOpts[] =
{
{
@@ -609,6 +619,13 @@ initialize_reloptions(void)
boolRelOpts[i].gen.lockmode));
j++;
}
+ for (i = 0; ternaryRelOpts[i].gen.name; i++)
+ {
+ Assert(DoLockModesConflict(ternaryRelOpts[i].gen.lockmode,
+ ternaryRelOpts[i].gen.lockmode));
+ j++;
+ }
+
for (i = 0; intRelOpts[i].gen.name; i++)
{
Assert(DoLockModesConflict(intRelOpts[i].gen.lockmode,
@@ -649,6 +666,14 @@ initialize_reloptions(void)
j++;
}
+ for (i = 0; ternaryRelOpts[i].gen.name; i++)
+ {
+ relOpts[j] = &ternaryRelOpts[i].gen;
+ relOpts[j]->type = RELOPT_TYPE_TERNARY;
+ relOpts[j]->namelen = strlen(relOpts[j]->name);
+ j++;
+ }
+
for (i = 0; intRelOpts[i].gen.name; i++)
{
relOpts[j] = &intRelOpts[i].gen;
@@ -809,6 +834,9 @@ allocate_reloption(bits32 kinds, int type, const char *name, const char *desc,
case RELOPT_TYPE_BOOL:
size = sizeof(relopt_bool);
break;
+ case RELOPT_TYPE_TERNARY:
+ size = sizeof(relopt_ternary);
+ break;
case RELOPT_TYPE_INT:
size = sizeof(relopt_int);
break;
@@ -892,6 +920,57 @@ add_local_bool_reloption(local_relopts *relopts, const char *name,
add_local_reloption(relopts, (relopt_gen *) newoption, offset);
}
+/*
+ * init_ternary_reloption
+ * Allocate and initialize a new ternary reloption
+ */
+static relopt_ternary *
+init_ternary_reloption(bits32 kinds, const char *name, const char *desc,
+ pg_ternary default_val, LOCKMODE lockmode)
+{
+ relopt_ternary *newoption;
+
+ newoption = (relopt_ternary *)
+ allocate_reloption(kinds, RELOPT_TYPE_TERNARY, name, desc, lockmode);
+ newoption->default_val = default_val;
+
+ return newoption;
+}
+
+/*
+ * add_ternary_reloption
+ * Add a new ternary reloption
+ */
+void
+add_ternary_reloption(bits32 kinds, const char *name, const char *desc,
+ pg_ternary default_val, LOCKMODE lockmode)
+{
+ relopt_ternary *newoption;
+
+ newoption =
+ init_ternary_reloption(kinds, name, desc, default_val, lockmode);
+
+ add_reloption((relopt_gen *) newoption);
+}
+
+/*
+ * add_local_ternary_reloption
+ * Add a new ternary local reloption
+ *
+ * 'offset' is offset of ternary-typed field.
+ */
+void
+add_local_ternary_reloption(local_relopts *relopts, const char *name,
+ const char *desc, pg_ternary default_val,
+ int offset)
+{
+ relopt_ternary *newoption;
+
+ newoption =
+ init_ternary_reloption(RELOPT_KIND_LOCAL, name, desc, default_val, 0);
+
+ add_local_reloption(relopts, (relopt_gen *) newoption, offset);
+}
/*
* init_real_reloption
@@ -1626,6 +1705,19 @@ parse_one_reloption(relopt_value *option, char *text_str, int text_len,
option->gen->name, value)));
}
break;
+ case RELOPT_TYPE_TERNARY:
+ {
+ bool b;
+
+ parsed = parse_bool(value, &b);
+ option->values.ternary_val = b ? TERNARY_TRUE : TERNARY_FALSE;
+ if (validate && !parsed)
+ ereport(ERROR,
+ (errcode(ERRCODE_INVALID_PARAMETER_VALUE),
+ errmsg("invalid value for ternary option \"%s\": %s",
+ option->gen->name, value)));
+ }
+ break;
case RELOPT_TYPE_INT:
{
relopt_int *optint = (relopt_int *) option->gen;
@@ -1789,17 +1881,6 @@ fillRelOptions(void *rdopts, Size basesize,
char *itempos = ((char *) rdopts) + elems[j].offset;
char *string_val;
- /*
- * If isset_offset is provided, store whether the reloption is
- * set there.
- */
- if (elems[j].isset_offset > 0)
- {
- char *setpos = ((char *) rdopts) + elems[j].isset_offset;
-
- *(bool *) setpos = options[i].isset;
- }
-
switch (options[i].gen->type)
{
case RELOPT_TYPE_BOOL:
@@ -1807,6 +1888,11 @@ fillRelOptions(void *rdopts, Size basesize,
options[i].values.bool_val :
((relopt_bool *) options[i].gen)->default_val;
break;
+ case RELOPT_TYPE_TERNARY:
+ *(pg_ternary *) itempos = options[i].isset ?
+ options[i].values.ternary_val :
+ ((relopt_ternary *) options[i].gen)->default_val;
+ break;
case RELOPT_TYPE_INT:
*(int *) itempos = options[i].isset ?
options[i].values.int_val :
@@ -1923,8 +2009,8 @@ default_reloptions(Datum reloptions, bool validate, relopt_kind kind)
offsetof(StdRdOptions, parallel_workers)},
{"vacuum_index_cleanup", RELOPT_TYPE_ENUM,
offsetof(StdRdOptions, vacuum_index_cleanup)},
- {"vacuum_truncate", RELOPT_TYPE_BOOL,
- offsetof(StdRdOptions, vacuum_truncate), offsetof(StdRdOptions, vacuum_truncate_set)},
+ {"vacuum_truncate", RELOPT_TYPE_TERNARY,
+ offsetof(StdRdOptions, vacuum_truncate)},
{"vacuum_max_eager_freeze_failure_rate", RELOPT_TYPE_REAL,
offsetof(StdRdOptions, vacuum_max_eager_freeze_failure_rate)}
};
@@ -2004,7 +2090,6 @@ build_local_reloptions(local_relopts *relopts, Datum options, bool validate)
elems[i].optname = opt->option->name;
elems[i].opttype = opt->option->type;
elems[i].offset = opt->offset;
- elems[i].isset_offset = 0; /* not supported for local relopts yet */
i++;
}
diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c
index aa4fbec143f..696eab9bd97 100644
--- a/src/backend/commands/vacuum.c
+++ b/src/backend/commands/vacuum.c
@@ -2224,9 +2224,9 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams params,
{
StdRdOptions *opts = (StdRdOptions *) rel->rd_options;
- if (opts && opts->vacuum_truncate_set)
+ if (opts && opts->vacuum_truncate != TERNARY_UNSET)
{
- if (opts->vacuum_truncate)
+ if (opts->vacuum_truncate == TERNARY_TRUE)
params.truncate = VACOPTVALUE_ENABLED;
else
params.truncate = VACOPTVALUE_DISABLED;
diff --git a/src/include/access/reloptions.h b/src/include/access/reloptions.h
index 2f08e1b0cf0..dfbef2babf2 100644
--- a/src/include/access/reloptions.h
+++ b/src/include/access/reloptions.h
@@ -29,6 +29,7 @@
typedef enum relopt_type
{
RELOPT_TYPE_BOOL,
+ RELOPT_TYPE_TERNARY, /* on, off, unset */
RELOPT_TYPE_INT,
RELOPT_TYPE_REAL,
RELOPT_TYPE_ENUM,
@@ -80,6 +81,7 @@ typedef struct relopt_value
union
{
bool bool_val;
+ pg_ternary ternary_val;
int int_val;
double real_val;
int enum_val;
@@ -94,6 +96,12 @@ typedef struct relopt_bool
bool default_val;
} relopt_bool;
+typedef struct relopt_ternary
+{
+ relopt_gen gen;
+ int default_val;
+} relopt_ternary;
+
typedef struct relopt_int
{
relopt_gen gen;
@@ -152,19 +160,6 @@ typedef struct
const char *optname; /* option's name */
relopt_type opttype; /* option's datatype */
int offset; /* offset of field in result struct */
-
- /*
- * isset_offset is an optional offset of a field in the result struct that
- * stores whether the option is explicitly set for the relation or if it
- * just picked up the default value. In most cases, this can be
- * accomplished by giving the reloption a special out-of-range default
- * value (e.g., some integer reloptions use -2), but this isn't always
- * possible. For example, a Boolean reloption cannot be given an
- * out-of-range default, so we need another way to discover the source of
- * its value. This offset is only used if given a value greater than
- * zero.
- */
- int isset_offset;
} relopt_parse_elt;
/* Local reloption definition */
@@ -195,6 +190,9 @@ typedef struct local_relopts
extern relopt_kind add_reloption_kind(void);
extern void add_bool_reloption(bits32 kinds, const char *name, const char *desc,
bool default_val, LOCKMODE lockmode);
+extern void add_ternary_reloption(bits32 kinds, const char *name,
+ const char *desc, pg_ternary default_val,
+ LOCKMODE lockmode);
extern void add_int_reloption(bits32 kinds, const char *name, const char *desc,
int default_val, int min_val, int max_val,
LOCKMODE lockmode);
@@ -214,6 +212,9 @@ extern void register_reloptions_validator(local_relopts *relopts,
extern void add_local_bool_reloption(local_relopts *relopts, const char *name,
const char *desc, bool default_val,
int offset);
+extern void add_local_ternary_reloption(local_relopts *relopts,
+ const char *name, const char *desc,
+ pg_ternary default_val, int offset);
extern void add_local_int_reloption(local_relopts *relopts, const char *name,
const char *desc, int default_val,
int min_val, int max_val, int offset);
diff --git a/src/include/postgres.h b/src/include/postgres.h
index 7d93fbce709..47c88780776 100644
--- a/src/include/postgres.h
+++ b/src/include/postgres.h
@@ -543,6 +543,21 @@ Float8GetDatum(float8 X)
* ----------------------------------------------------------------
*/
+/*
+ * pg_ternary
+ * Boolean value with an extra "unset" value
+ *
+ * This enum can be used for values that want to distinguish between true,
+ * false, and unset.
+*/
+
+typedef enum pg_ternary
+{
+ TERNARY_FALSE = 0,
+ TERNARY_TRUE = 1,
+ TERNARY_UNSET = -1
+} pg_ternary;
+
/*
* NON_EXEC_STATIC: It's sometimes useful to define a variable or function
* that is normally static but extern when using EXEC_BACKEND (see
diff --git a/src/include/utils/rel.h b/src/include/utils/rel.h
index d03ab247788..5feb18a5373 100644
--- a/src/include/utils/rel.h
+++ b/src/include/utils/rel.h
@@ -347,8 +347,7 @@ typedef struct StdRdOptions
bool user_catalog_table; /* use as an additional catalog relation */
int parallel_workers; /* max number of parallel workers */
StdRdOptIndexCleanup vacuum_index_cleanup; /* controls index vacuuming */
- bool vacuum_truncate; /* enables vacuum to truncate a relation */
- bool vacuum_truncate_set; /* whether vacuum_truncate is set */
+ pg_ternary vacuum_truncate; /* enables vacuum to truncate a relation */
/*
* Fraction of pages in a relation that vacuum can eagerly scan and fail
diff --git a/src/test/regress/expected/reloptions.out b/src/test/regress/expected/reloptions.out
index 9de19b4e3f1..1c99f79ab01 100644
--- a/src/test/regress/expected/reloptions.out
+++ b/src/test/regress/expected/reloptions.out
@@ -98,6 +98,42 @@ SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass;
{fillfactor=13,autovacuum_enabled=false}
(1 row)
+-- Tests for future (FIXME) ternary options
+-- behave as boolean option: accept unassigned name and truncated value
+DROP TABLE reloptions_test;
+CREATE TABLE reloptions_test(i INT) WITH (vacuum_truncate);
+SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass;
+ reloptions
+------------------------
+ {vacuum_truncate=true}
+(1 row)
+
+DROP TABLE reloptions_test;
+CREATE TABLE reloptions_test(i INT) WITH (vacuum_truncate=FaLS);
+SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass;
+ reloptions
+------------------------
+ {vacuum_truncate=fals}
+(1 row)
+
+-- preferred "true" alias is used when storing
+DROP TABLE reloptions_test;
+CREATE TABLE reloptions_test(i INT) WITH (vacuum_index_cleanup=on);
+SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass;
+ reloptions
+---------------------------
+ {vacuum_index_cleanup=on}
+(1 row)
+
+-- custom "third" value is available
+DROP TABLE reloptions_test;
+CREATE TABLE reloptions_test(i INT) WITH (vacuum_index_cleanup=auto);
+SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass;
+ reloptions
+-----------------------------
+ {vacuum_index_cleanup=auto}
+(1 row)
+
-- Test vacuum_truncate option
DROP TABLE reloptions_test;
CREATE TEMP TABLE reloptions_test(i INT NOT NULL, j text)
diff --git a/src/test/regress/sql/reloptions.sql b/src/test/regress/sql/reloptions.sql
index 24fbe0b478d..f5980dafcbc 100644
--- a/src/test/regress/sql/reloptions.sql
+++ b/src/test/regress/sql/reloptions.sql
@@ -59,6 +59,27 @@ UPDATE pg_class
ALTER TABLE reloptions_test RESET (illegal_option);
SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass;
+-- Tests for future (FIXME) ternary options
+
+-- behave as boolean option: accept unassigned name and truncated value
+DROP TABLE reloptions_test;
+CREATE TABLE reloptions_test(i INT) WITH (vacuum_truncate);
+SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass;
+
+DROP TABLE reloptions_test;
+CREATE TABLE reloptions_test(i INT) WITH (vacuum_truncate=FaLS);
+SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass;
+
+-- preferred "true" alias is used when storing
+DROP TABLE reloptions_test;
+CREATE TABLE reloptions_test(i INT) WITH (vacuum_index_cleanup=on);
+SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass;
+
+-- custom "third" value is available
+DROP TABLE reloptions_test;
+CREATE TABLE reloptions_test(i INT) WITH (vacuum_index_cleanup=auto);
+SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass;
+
-- Test vacuum_truncate option
DROP TABLE reloptions_test;
diff --git a/src/tools/pgindent/typedefs.list b/src/tools/pgindent/typedefs.list
index 3f3a888fd0e..1c8610fd46c 100644
--- a/src/tools/pgindent/typedefs.list
+++ b/src/tools/pgindent/typedefs.list
@@ -3952,6 +3952,7 @@ pg_sha512_ctx
pg_snapshot
pg_special_case
pg_stack_base_t
+pg_ternary
pg_time_t
pg_time_usec_t
pg_tz
@@ -4079,6 +4080,7 @@ relopt_kind
relopt_parse_elt
relopt_real
relopt_string
+relopt_ternary
relopt_type
relopt_value
relopts_validator
--
2.47.3
--c36t3fndxhkzbl5z--
^ permalink raw reply [nested|flat] 868+ messages in thread
* [PATCH v3] Introduce ternary reloptions
@ 2026-01-16 15:04 Álvaro Herrera <[email protected]>
0 siblings, 0 replies; 868+ messages in thread
From: Álvaro Herrera @ 2026-01-16 15:04 UTC (permalink / raw)
Introduce ternary reloption as a replacement for current
`vacuum_truncate` implementation. Remove the `vacuum_truncate_set`
separate flag and use TERNARY_UNSET instead.
This could also be used for other options such as `vacuum_index_cleanup`
and `buffering`, but lets get the scaffolding in first.
Discussion: https://postgr.es/m/3474141.usfYGdeWWP@thinkpad-pgpro
---
src/backend/access/common/reloptions.c | 137 ++++++++++++++++++-----
src/backend/commands/vacuum.c | 4 +-
src/include/access/reloptions.h | 27 ++---
src/include/postgres.h | 15 +++
src/include/utils/rel.h | 3 +-
src/test/regress/expected/reloptions.out | 36 ++++++
src/test/regress/sql/reloptions.sql | 21 ++++
src/tools/pgindent/typedefs.list | 2 +
8 files changed, 202 insertions(+), 43 deletions(-)
diff --git a/src/backend/access/common/reloptions.c b/src/backend/access/common/reloptions.c
index 0b83f98ed5f..6f1f577581a 100644
--- a/src/backend/access/common/reloptions.c
+++ b/src/backend/access/common/reloptions.c
@@ -40,9 +40,9 @@
*
* To add an option:
*
- * (i) decide on a type (bool, integer, real, enum, string), name, default
- * value, upper and lower bounds (if applicable); for strings, consider a
- * validation routine.
+ * (i) decide on a type (bool, ternary, integer, real, enum, string), name,
+ * default value, upper and lower bounds (if applicable); for strings,
+ * consider a validation routine.
* (ii) add a record below (or use add_<type>_reloption).
* (iii) add it to the appropriate options struct (perhaps StdRdOptions)
* (iv) add it to the appropriate handling routine (perhaps
@@ -147,15 +147,6 @@ static relopt_bool boolRelOpts[] =
},
false
},
- {
- {
- "vacuum_truncate",
- "Enables vacuum to truncate empty pages at the end of this table",
- RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
- ShareUpdateExclusiveLock
- },
- true
- },
{
{
"deduplicate_items",
@@ -170,6 +161,25 @@ static relopt_bool boolRelOpts[] =
{{NULL}}
};
+static relopt_ternary ternaryRelOpts[] =
+{
+ {
+ {
+ "vacuum_truncate",
+ "Enables vacuum to truncate empty pages at the end of this table",
+ RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
+ ShareUpdateExclusiveLock
+ },
+ TERNARY_UNSET
+ },
+ /* list terminator */
+ {
+ {
+ NULL
+ }
+ }
+};
+
static relopt_int intRelOpts[] =
{
{
@@ -609,6 +619,13 @@ initialize_reloptions(void)
boolRelOpts[i].gen.lockmode));
j++;
}
+ for (i = 0; ternaryRelOpts[i].gen.name; i++)
+ {
+ Assert(DoLockModesConflict(ternaryRelOpts[i].gen.lockmode,
+ ternaryRelOpts[i].gen.lockmode));
+ j++;
+ }
+
for (i = 0; intRelOpts[i].gen.name; i++)
{
Assert(DoLockModesConflict(intRelOpts[i].gen.lockmode,
@@ -649,6 +666,14 @@ initialize_reloptions(void)
j++;
}
+ for (i = 0; ternaryRelOpts[i].gen.name; i++)
+ {
+ relOpts[j] = &ternaryRelOpts[i].gen;
+ relOpts[j]->type = RELOPT_TYPE_TERNARY;
+ relOpts[j]->namelen = strlen(relOpts[j]->name);
+ j++;
+ }
+
for (i = 0; intRelOpts[i].gen.name; i++)
{
relOpts[j] = &intRelOpts[i].gen;
@@ -809,6 +834,9 @@ allocate_reloption(bits32 kinds, int type, const char *name, const char *desc,
case RELOPT_TYPE_BOOL:
size = sizeof(relopt_bool);
break;
+ case RELOPT_TYPE_TERNARY:
+ size = sizeof(relopt_ternary);
+ break;
case RELOPT_TYPE_INT:
size = sizeof(relopt_int);
break;
@@ -892,6 +920,57 @@ add_local_bool_reloption(local_relopts *relopts, const char *name,
add_local_reloption(relopts, (relopt_gen *) newoption, offset);
}
+/*
+ * init_ternary_reloption
+ * Allocate and initialize a new ternary reloption
+ */
+static relopt_ternary *
+init_ternary_reloption(bits32 kinds, const char *name, const char *desc,
+ pg_ternary default_val, LOCKMODE lockmode)
+{
+ relopt_ternary *newoption;
+
+ newoption = (relopt_ternary *)
+ allocate_reloption(kinds, RELOPT_TYPE_TERNARY, name, desc, lockmode);
+ newoption->default_val = default_val;
+
+ return newoption;
+}
+
+/*
+ * add_ternary_reloption
+ * Add a new ternary reloption
+ */
+void
+add_ternary_reloption(bits32 kinds, const char *name, const char *desc,
+ pg_ternary default_val, LOCKMODE lockmode)
+{
+ relopt_ternary *newoption;
+
+ newoption =
+ init_ternary_reloption(kinds, name, desc, default_val, lockmode);
+
+ add_reloption((relopt_gen *) newoption);
+}
+
+/*
+ * add_local_ternary_reloption
+ * Add a new ternary local reloption
+ *
+ * 'offset' is offset of ternary-typed field.
+ */
+void
+add_local_ternary_reloption(local_relopts *relopts, const char *name,
+ const char *desc, pg_ternary default_val,
+ int offset)
+{
+ relopt_ternary *newoption;
+
+ newoption =
+ init_ternary_reloption(RELOPT_KIND_LOCAL, name, desc, default_val, 0);
+
+ add_local_reloption(relopts, (relopt_gen *) newoption, offset);
+}
/*
* init_real_reloption
@@ -1626,6 +1705,19 @@ parse_one_reloption(relopt_value *option, char *text_str, int text_len,
option->gen->name, value)));
}
break;
+ case RELOPT_TYPE_TERNARY:
+ {
+ bool b;
+
+ parsed = parse_bool(value, &b);
+ option->values.ternary_val = b ? TERNARY_TRUE : TERNARY_FALSE;
+ if (validate && !parsed)
+ ereport(ERROR,
+ (errcode(ERRCODE_INVALID_PARAMETER_VALUE),
+ errmsg("invalid value for ternary option \"%s\": %s",
+ option->gen->name, value)));
+ }
+ break;
case RELOPT_TYPE_INT:
{
relopt_int *optint = (relopt_int *) option->gen;
@@ -1789,17 +1881,6 @@ fillRelOptions(void *rdopts, Size basesize,
char *itempos = ((char *) rdopts) + elems[j].offset;
char *string_val;
- /*
- * If isset_offset is provided, store whether the reloption is
- * set there.
- */
- if (elems[j].isset_offset > 0)
- {
- char *setpos = ((char *) rdopts) + elems[j].isset_offset;
-
- *(bool *) setpos = options[i].isset;
- }
-
switch (options[i].gen->type)
{
case RELOPT_TYPE_BOOL:
@@ -1807,6 +1888,11 @@ fillRelOptions(void *rdopts, Size basesize,
options[i].values.bool_val :
((relopt_bool *) options[i].gen)->default_val;
break;
+ case RELOPT_TYPE_TERNARY:
+ *(pg_ternary *) itempos = options[i].isset ?
+ options[i].values.ternary_val :
+ ((relopt_ternary *) options[i].gen)->default_val;
+ break;
case RELOPT_TYPE_INT:
*(int *) itempos = options[i].isset ?
options[i].values.int_val :
@@ -1923,8 +2009,8 @@ default_reloptions(Datum reloptions, bool validate, relopt_kind kind)
offsetof(StdRdOptions, parallel_workers)},
{"vacuum_index_cleanup", RELOPT_TYPE_ENUM,
offsetof(StdRdOptions, vacuum_index_cleanup)},
- {"vacuum_truncate", RELOPT_TYPE_BOOL,
- offsetof(StdRdOptions, vacuum_truncate), offsetof(StdRdOptions, vacuum_truncate_set)},
+ {"vacuum_truncate", RELOPT_TYPE_TERNARY,
+ offsetof(StdRdOptions, vacuum_truncate)},
{"vacuum_max_eager_freeze_failure_rate", RELOPT_TYPE_REAL,
offsetof(StdRdOptions, vacuum_max_eager_freeze_failure_rate)}
};
@@ -2004,7 +2090,6 @@ build_local_reloptions(local_relopts *relopts, Datum options, bool validate)
elems[i].optname = opt->option->name;
elems[i].opttype = opt->option->type;
elems[i].offset = opt->offset;
- elems[i].isset_offset = 0; /* not supported for local relopts yet */
i++;
}
diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c
index aa4fbec143f..696eab9bd97 100644
--- a/src/backend/commands/vacuum.c
+++ b/src/backend/commands/vacuum.c
@@ -2224,9 +2224,9 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams params,
{
StdRdOptions *opts = (StdRdOptions *) rel->rd_options;
- if (opts && opts->vacuum_truncate_set)
+ if (opts && opts->vacuum_truncate != TERNARY_UNSET)
{
- if (opts->vacuum_truncate)
+ if (opts->vacuum_truncate == TERNARY_TRUE)
params.truncate = VACOPTVALUE_ENABLED;
else
params.truncate = VACOPTVALUE_DISABLED;
diff --git a/src/include/access/reloptions.h b/src/include/access/reloptions.h
index 2f08e1b0cf0..dfbef2babf2 100644
--- a/src/include/access/reloptions.h
+++ b/src/include/access/reloptions.h
@@ -29,6 +29,7 @@
typedef enum relopt_type
{
RELOPT_TYPE_BOOL,
+ RELOPT_TYPE_TERNARY, /* on, off, unset */
RELOPT_TYPE_INT,
RELOPT_TYPE_REAL,
RELOPT_TYPE_ENUM,
@@ -80,6 +81,7 @@ typedef struct relopt_value
union
{
bool bool_val;
+ pg_ternary ternary_val;
int int_val;
double real_val;
int enum_val;
@@ -94,6 +96,12 @@ typedef struct relopt_bool
bool default_val;
} relopt_bool;
+typedef struct relopt_ternary
+{
+ relopt_gen gen;
+ int default_val;
+} relopt_ternary;
+
typedef struct relopt_int
{
relopt_gen gen;
@@ -152,19 +160,6 @@ typedef struct
const char *optname; /* option's name */
relopt_type opttype; /* option's datatype */
int offset; /* offset of field in result struct */
-
- /*
- * isset_offset is an optional offset of a field in the result struct that
- * stores whether the option is explicitly set for the relation or if it
- * just picked up the default value. In most cases, this can be
- * accomplished by giving the reloption a special out-of-range default
- * value (e.g., some integer reloptions use -2), but this isn't always
- * possible. For example, a Boolean reloption cannot be given an
- * out-of-range default, so we need another way to discover the source of
- * its value. This offset is only used if given a value greater than
- * zero.
- */
- int isset_offset;
} relopt_parse_elt;
/* Local reloption definition */
@@ -195,6 +190,9 @@ typedef struct local_relopts
extern relopt_kind add_reloption_kind(void);
extern void add_bool_reloption(bits32 kinds, const char *name, const char *desc,
bool default_val, LOCKMODE lockmode);
+extern void add_ternary_reloption(bits32 kinds, const char *name,
+ const char *desc, pg_ternary default_val,
+ LOCKMODE lockmode);
extern void add_int_reloption(bits32 kinds, const char *name, const char *desc,
int default_val, int min_val, int max_val,
LOCKMODE lockmode);
@@ -214,6 +212,9 @@ extern void register_reloptions_validator(local_relopts *relopts,
extern void add_local_bool_reloption(local_relopts *relopts, const char *name,
const char *desc, bool default_val,
int offset);
+extern void add_local_ternary_reloption(local_relopts *relopts,
+ const char *name, const char *desc,
+ pg_ternary default_val, int offset);
extern void add_local_int_reloption(local_relopts *relopts, const char *name,
const char *desc, int default_val,
int min_val, int max_val, int offset);
diff --git a/src/include/postgres.h b/src/include/postgres.h
index 7d93fbce709..47c88780776 100644
--- a/src/include/postgres.h
+++ b/src/include/postgres.h
@@ -543,6 +543,21 @@ Float8GetDatum(float8 X)
* ----------------------------------------------------------------
*/
+/*
+ * pg_ternary
+ * Boolean value with an extra "unset" value
+ *
+ * This enum can be used for values that want to distinguish between true,
+ * false, and unset.
+*/
+
+typedef enum pg_ternary
+{
+ TERNARY_FALSE = 0,
+ TERNARY_TRUE = 1,
+ TERNARY_UNSET = -1
+} pg_ternary;
+
/*
* NON_EXEC_STATIC: It's sometimes useful to define a variable or function
* that is normally static but extern when using EXEC_BACKEND (see
diff --git a/src/include/utils/rel.h b/src/include/utils/rel.h
index d03ab247788..5feb18a5373 100644
--- a/src/include/utils/rel.h
+++ b/src/include/utils/rel.h
@@ -347,8 +347,7 @@ typedef struct StdRdOptions
bool user_catalog_table; /* use as an additional catalog relation */
int parallel_workers; /* max number of parallel workers */
StdRdOptIndexCleanup vacuum_index_cleanup; /* controls index vacuuming */
- bool vacuum_truncate; /* enables vacuum to truncate a relation */
- bool vacuum_truncate_set; /* whether vacuum_truncate is set */
+ pg_ternary vacuum_truncate; /* enables vacuum to truncate a relation */
/*
* Fraction of pages in a relation that vacuum can eagerly scan and fail
diff --git a/src/test/regress/expected/reloptions.out b/src/test/regress/expected/reloptions.out
index 9de19b4e3f1..1c99f79ab01 100644
--- a/src/test/regress/expected/reloptions.out
+++ b/src/test/regress/expected/reloptions.out
@@ -98,6 +98,42 @@ SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass;
{fillfactor=13,autovacuum_enabled=false}
(1 row)
+-- Tests for future (FIXME) ternary options
+-- behave as boolean option: accept unassigned name and truncated value
+DROP TABLE reloptions_test;
+CREATE TABLE reloptions_test(i INT) WITH (vacuum_truncate);
+SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass;
+ reloptions
+------------------------
+ {vacuum_truncate=true}
+(1 row)
+
+DROP TABLE reloptions_test;
+CREATE TABLE reloptions_test(i INT) WITH (vacuum_truncate=FaLS);
+SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass;
+ reloptions
+------------------------
+ {vacuum_truncate=fals}
+(1 row)
+
+-- preferred "true" alias is used when storing
+DROP TABLE reloptions_test;
+CREATE TABLE reloptions_test(i INT) WITH (vacuum_index_cleanup=on);
+SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass;
+ reloptions
+---------------------------
+ {vacuum_index_cleanup=on}
+(1 row)
+
+-- custom "third" value is available
+DROP TABLE reloptions_test;
+CREATE TABLE reloptions_test(i INT) WITH (vacuum_index_cleanup=auto);
+SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass;
+ reloptions
+-----------------------------
+ {vacuum_index_cleanup=auto}
+(1 row)
+
-- Test vacuum_truncate option
DROP TABLE reloptions_test;
CREATE TEMP TABLE reloptions_test(i INT NOT NULL, j text)
diff --git a/src/test/regress/sql/reloptions.sql b/src/test/regress/sql/reloptions.sql
index 24fbe0b478d..f5980dafcbc 100644
--- a/src/test/regress/sql/reloptions.sql
+++ b/src/test/regress/sql/reloptions.sql
@@ -59,6 +59,27 @@ UPDATE pg_class
ALTER TABLE reloptions_test RESET (illegal_option);
SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass;
+-- Tests for future (FIXME) ternary options
+
+-- behave as boolean option: accept unassigned name and truncated value
+DROP TABLE reloptions_test;
+CREATE TABLE reloptions_test(i INT) WITH (vacuum_truncate);
+SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass;
+
+DROP TABLE reloptions_test;
+CREATE TABLE reloptions_test(i INT) WITH (vacuum_truncate=FaLS);
+SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass;
+
+-- preferred "true" alias is used when storing
+DROP TABLE reloptions_test;
+CREATE TABLE reloptions_test(i INT) WITH (vacuum_index_cleanup=on);
+SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass;
+
+-- custom "third" value is available
+DROP TABLE reloptions_test;
+CREATE TABLE reloptions_test(i INT) WITH (vacuum_index_cleanup=auto);
+SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass;
+
-- Test vacuum_truncate option
DROP TABLE reloptions_test;
diff --git a/src/tools/pgindent/typedefs.list b/src/tools/pgindent/typedefs.list
index 3f3a888fd0e..1c8610fd46c 100644
--- a/src/tools/pgindent/typedefs.list
+++ b/src/tools/pgindent/typedefs.list
@@ -3952,6 +3952,7 @@ pg_sha512_ctx
pg_snapshot
pg_special_case
pg_stack_base_t
+pg_ternary
pg_time_t
pg_time_usec_t
pg_tz
@@ -4079,6 +4080,7 @@ relopt_kind
relopt_parse_elt
relopt_real
relopt_string
+relopt_ternary
relopt_type
relopt_value
relopts_validator
--
2.47.3
--c36t3fndxhkzbl5z--
^ permalink raw reply [nested|flat] 868+ messages in thread
* [PATCH v3] Introduce ternary reloptions
@ 2026-01-16 15:04 Álvaro Herrera <[email protected]>
0 siblings, 0 replies; 868+ messages in thread
From: Álvaro Herrera @ 2026-01-16 15:04 UTC (permalink / raw)
Introduce ternary reloption as a replacement for current
`vacuum_truncate` implementation. Remove the `vacuum_truncate_set`
separate flag and use TERNARY_UNSET instead.
This could also be used for other options such as `vacuum_index_cleanup`
and `buffering`, but lets get the scaffolding in first.
Discussion: https://postgr.es/m/3474141.usfYGdeWWP@thinkpad-pgpro
---
src/backend/access/common/reloptions.c | 137 ++++++++++++++++++-----
src/backend/commands/vacuum.c | 4 +-
src/include/access/reloptions.h | 27 ++---
src/include/postgres.h | 15 +++
src/include/utils/rel.h | 3 +-
src/test/regress/expected/reloptions.out | 36 ++++++
src/test/regress/sql/reloptions.sql | 21 ++++
src/tools/pgindent/typedefs.list | 2 +
8 files changed, 202 insertions(+), 43 deletions(-)
diff --git a/src/backend/access/common/reloptions.c b/src/backend/access/common/reloptions.c
index 0b83f98ed5f..6f1f577581a 100644
--- a/src/backend/access/common/reloptions.c
+++ b/src/backend/access/common/reloptions.c
@@ -40,9 +40,9 @@
*
* To add an option:
*
- * (i) decide on a type (bool, integer, real, enum, string), name, default
- * value, upper and lower bounds (if applicable); for strings, consider a
- * validation routine.
+ * (i) decide on a type (bool, ternary, integer, real, enum, string), name,
+ * default value, upper and lower bounds (if applicable); for strings,
+ * consider a validation routine.
* (ii) add a record below (or use add_<type>_reloption).
* (iii) add it to the appropriate options struct (perhaps StdRdOptions)
* (iv) add it to the appropriate handling routine (perhaps
@@ -147,15 +147,6 @@ static relopt_bool boolRelOpts[] =
},
false
},
- {
- {
- "vacuum_truncate",
- "Enables vacuum to truncate empty pages at the end of this table",
- RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
- ShareUpdateExclusiveLock
- },
- true
- },
{
{
"deduplicate_items",
@@ -170,6 +161,25 @@ static relopt_bool boolRelOpts[] =
{{NULL}}
};
+static relopt_ternary ternaryRelOpts[] =
+{
+ {
+ {
+ "vacuum_truncate",
+ "Enables vacuum to truncate empty pages at the end of this table",
+ RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
+ ShareUpdateExclusiveLock
+ },
+ TERNARY_UNSET
+ },
+ /* list terminator */
+ {
+ {
+ NULL
+ }
+ }
+};
+
static relopt_int intRelOpts[] =
{
{
@@ -609,6 +619,13 @@ initialize_reloptions(void)
boolRelOpts[i].gen.lockmode));
j++;
}
+ for (i = 0; ternaryRelOpts[i].gen.name; i++)
+ {
+ Assert(DoLockModesConflict(ternaryRelOpts[i].gen.lockmode,
+ ternaryRelOpts[i].gen.lockmode));
+ j++;
+ }
+
for (i = 0; intRelOpts[i].gen.name; i++)
{
Assert(DoLockModesConflict(intRelOpts[i].gen.lockmode,
@@ -649,6 +666,14 @@ initialize_reloptions(void)
j++;
}
+ for (i = 0; ternaryRelOpts[i].gen.name; i++)
+ {
+ relOpts[j] = &ternaryRelOpts[i].gen;
+ relOpts[j]->type = RELOPT_TYPE_TERNARY;
+ relOpts[j]->namelen = strlen(relOpts[j]->name);
+ j++;
+ }
+
for (i = 0; intRelOpts[i].gen.name; i++)
{
relOpts[j] = &intRelOpts[i].gen;
@@ -809,6 +834,9 @@ allocate_reloption(bits32 kinds, int type, const char *name, const char *desc,
case RELOPT_TYPE_BOOL:
size = sizeof(relopt_bool);
break;
+ case RELOPT_TYPE_TERNARY:
+ size = sizeof(relopt_ternary);
+ break;
case RELOPT_TYPE_INT:
size = sizeof(relopt_int);
break;
@@ -892,6 +920,57 @@ add_local_bool_reloption(local_relopts *relopts, const char *name,
add_local_reloption(relopts, (relopt_gen *) newoption, offset);
}
+/*
+ * init_ternary_reloption
+ * Allocate and initialize a new ternary reloption
+ */
+static relopt_ternary *
+init_ternary_reloption(bits32 kinds, const char *name, const char *desc,
+ pg_ternary default_val, LOCKMODE lockmode)
+{
+ relopt_ternary *newoption;
+
+ newoption = (relopt_ternary *)
+ allocate_reloption(kinds, RELOPT_TYPE_TERNARY, name, desc, lockmode);
+ newoption->default_val = default_val;
+
+ return newoption;
+}
+
+/*
+ * add_ternary_reloption
+ * Add a new ternary reloption
+ */
+void
+add_ternary_reloption(bits32 kinds, const char *name, const char *desc,
+ pg_ternary default_val, LOCKMODE lockmode)
+{
+ relopt_ternary *newoption;
+
+ newoption =
+ init_ternary_reloption(kinds, name, desc, default_val, lockmode);
+
+ add_reloption((relopt_gen *) newoption);
+}
+
+/*
+ * add_local_ternary_reloption
+ * Add a new ternary local reloption
+ *
+ * 'offset' is offset of ternary-typed field.
+ */
+void
+add_local_ternary_reloption(local_relopts *relopts, const char *name,
+ const char *desc, pg_ternary default_val,
+ int offset)
+{
+ relopt_ternary *newoption;
+
+ newoption =
+ init_ternary_reloption(RELOPT_KIND_LOCAL, name, desc, default_val, 0);
+
+ add_local_reloption(relopts, (relopt_gen *) newoption, offset);
+}
/*
* init_real_reloption
@@ -1626,6 +1705,19 @@ parse_one_reloption(relopt_value *option, char *text_str, int text_len,
option->gen->name, value)));
}
break;
+ case RELOPT_TYPE_TERNARY:
+ {
+ bool b;
+
+ parsed = parse_bool(value, &b);
+ option->values.ternary_val = b ? TERNARY_TRUE : TERNARY_FALSE;
+ if (validate && !parsed)
+ ereport(ERROR,
+ (errcode(ERRCODE_INVALID_PARAMETER_VALUE),
+ errmsg("invalid value for ternary option \"%s\": %s",
+ option->gen->name, value)));
+ }
+ break;
case RELOPT_TYPE_INT:
{
relopt_int *optint = (relopt_int *) option->gen;
@@ -1789,17 +1881,6 @@ fillRelOptions(void *rdopts, Size basesize,
char *itempos = ((char *) rdopts) + elems[j].offset;
char *string_val;
- /*
- * If isset_offset is provided, store whether the reloption is
- * set there.
- */
- if (elems[j].isset_offset > 0)
- {
- char *setpos = ((char *) rdopts) + elems[j].isset_offset;
-
- *(bool *) setpos = options[i].isset;
- }
-
switch (options[i].gen->type)
{
case RELOPT_TYPE_BOOL:
@@ -1807,6 +1888,11 @@ fillRelOptions(void *rdopts, Size basesize,
options[i].values.bool_val :
((relopt_bool *) options[i].gen)->default_val;
break;
+ case RELOPT_TYPE_TERNARY:
+ *(pg_ternary *) itempos = options[i].isset ?
+ options[i].values.ternary_val :
+ ((relopt_ternary *) options[i].gen)->default_val;
+ break;
case RELOPT_TYPE_INT:
*(int *) itempos = options[i].isset ?
options[i].values.int_val :
@@ -1923,8 +2009,8 @@ default_reloptions(Datum reloptions, bool validate, relopt_kind kind)
offsetof(StdRdOptions, parallel_workers)},
{"vacuum_index_cleanup", RELOPT_TYPE_ENUM,
offsetof(StdRdOptions, vacuum_index_cleanup)},
- {"vacuum_truncate", RELOPT_TYPE_BOOL,
- offsetof(StdRdOptions, vacuum_truncate), offsetof(StdRdOptions, vacuum_truncate_set)},
+ {"vacuum_truncate", RELOPT_TYPE_TERNARY,
+ offsetof(StdRdOptions, vacuum_truncate)},
{"vacuum_max_eager_freeze_failure_rate", RELOPT_TYPE_REAL,
offsetof(StdRdOptions, vacuum_max_eager_freeze_failure_rate)}
};
@@ -2004,7 +2090,6 @@ build_local_reloptions(local_relopts *relopts, Datum options, bool validate)
elems[i].optname = opt->option->name;
elems[i].opttype = opt->option->type;
elems[i].offset = opt->offset;
- elems[i].isset_offset = 0; /* not supported for local relopts yet */
i++;
}
diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c
index aa4fbec143f..696eab9bd97 100644
--- a/src/backend/commands/vacuum.c
+++ b/src/backend/commands/vacuum.c
@@ -2224,9 +2224,9 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams params,
{
StdRdOptions *opts = (StdRdOptions *) rel->rd_options;
- if (opts && opts->vacuum_truncate_set)
+ if (opts && opts->vacuum_truncate != TERNARY_UNSET)
{
- if (opts->vacuum_truncate)
+ if (opts->vacuum_truncate == TERNARY_TRUE)
params.truncate = VACOPTVALUE_ENABLED;
else
params.truncate = VACOPTVALUE_DISABLED;
diff --git a/src/include/access/reloptions.h b/src/include/access/reloptions.h
index 2f08e1b0cf0..dfbef2babf2 100644
--- a/src/include/access/reloptions.h
+++ b/src/include/access/reloptions.h
@@ -29,6 +29,7 @@
typedef enum relopt_type
{
RELOPT_TYPE_BOOL,
+ RELOPT_TYPE_TERNARY, /* on, off, unset */
RELOPT_TYPE_INT,
RELOPT_TYPE_REAL,
RELOPT_TYPE_ENUM,
@@ -80,6 +81,7 @@ typedef struct relopt_value
union
{
bool bool_val;
+ pg_ternary ternary_val;
int int_val;
double real_val;
int enum_val;
@@ -94,6 +96,12 @@ typedef struct relopt_bool
bool default_val;
} relopt_bool;
+typedef struct relopt_ternary
+{
+ relopt_gen gen;
+ int default_val;
+} relopt_ternary;
+
typedef struct relopt_int
{
relopt_gen gen;
@@ -152,19 +160,6 @@ typedef struct
const char *optname; /* option's name */
relopt_type opttype; /* option's datatype */
int offset; /* offset of field in result struct */
-
- /*
- * isset_offset is an optional offset of a field in the result struct that
- * stores whether the option is explicitly set for the relation or if it
- * just picked up the default value. In most cases, this can be
- * accomplished by giving the reloption a special out-of-range default
- * value (e.g., some integer reloptions use -2), but this isn't always
- * possible. For example, a Boolean reloption cannot be given an
- * out-of-range default, so we need another way to discover the source of
- * its value. This offset is only used if given a value greater than
- * zero.
- */
- int isset_offset;
} relopt_parse_elt;
/* Local reloption definition */
@@ -195,6 +190,9 @@ typedef struct local_relopts
extern relopt_kind add_reloption_kind(void);
extern void add_bool_reloption(bits32 kinds, const char *name, const char *desc,
bool default_val, LOCKMODE lockmode);
+extern void add_ternary_reloption(bits32 kinds, const char *name,
+ const char *desc, pg_ternary default_val,
+ LOCKMODE lockmode);
extern void add_int_reloption(bits32 kinds, const char *name, const char *desc,
int default_val, int min_val, int max_val,
LOCKMODE lockmode);
@@ -214,6 +212,9 @@ extern void register_reloptions_validator(local_relopts *relopts,
extern void add_local_bool_reloption(local_relopts *relopts, const char *name,
const char *desc, bool default_val,
int offset);
+extern void add_local_ternary_reloption(local_relopts *relopts,
+ const char *name, const char *desc,
+ pg_ternary default_val, int offset);
extern void add_local_int_reloption(local_relopts *relopts, const char *name,
const char *desc, int default_val,
int min_val, int max_val, int offset);
diff --git a/src/include/postgres.h b/src/include/postgres.h
index 7d93fbce709..47c88780776 100644
--- a/src/include/postgres.h
+++ b/src/include/postgres.h
@@ -543,6 +543,21 @@ Float8GetDatum(float8 X)
* ----------------------------------------------------------------
*/
+/*
+ * pg_ternary
+ * Boolean value with an extra "unset" value
+ *
+ * This enum can be used for values that want to distinguish between true,
+ * false, and unset.
+*/
+
+typedef enum pg_ternary
+{
+ TERNARY_FALSE = 0,
+ TERNARY_TRUE = 1,
+ TERNARY_UNSET = -1
+} pg_ternary;
+
/*
* NON_EXEC_STATIC: It's sometimes useful to define a variable or function
* that is normally static but extern when using EXEC_BACKEND (see
diff --git a/src/include/utils/rel.h b/src/include/utils/rel.h
index d03ab247788..5feb18a5373 100644
--- a/src/include/utils/rel.h
+++ b/src/include/utils/rel.h
@@ -347,8 +347,7 @@ typedef struct StdRdOptions
bool user_catalog_table; /* use as an additional catalog relation */
int parallel_workers; /* max number of parallel workers */
StdRdOptIndexCleanup vacuum_index_cleanup; /* controls index vacuuming */
- bool vacuum_truncate; /* enables vacuum to truncate a relation */
- bool vacuum_truncate_set; /* whether vacuum_truncate is set */
+ pg_ternary vacuum_truncate; /* enables vacuum to truncate a relation */
/*
* Fraction of pages in a relation that vacuum can eagerly scan and fail
diff --git a/src/test/regress/expected/reloptions.out b/src/test/regress/expected/reloptions.out
index 9de19b4e3f1..1c99f79ab01 100644
--- a/src/test/regress/expected/reloptions.out
+++ b/src/test/regress/expected/reloptions.out
@@ -98,6 +98,42 @@ SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass;
{fillfactor=13,autovacuum_enabled=false}
(1 row)
+-- Tests for future (FIXME) ternary options
+-- behave as boolean option: accept unassigned name and truncated value
+DROP TABLE reloptions_test;
+CREATE TABLE reloptions_test(i INT) WITH (vacuum_truncate);
+SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass;
+ reloptions
+------------------------
+ {vacuum_truncate=true}
+(1 row)
+
+DROP TABLE reloptions_test;
+CREATE TABLE reloptions_test(i INT) WITH (vacuum_truncate=FaLS);
+SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass;
+ reloptions
+------------------------
+ {vacuum_truncate=fals}
+(1 row)
+
+-- preferred "true" alias is used when storing
+DROP TABLE reloptions_test;
+CREATE TABLE reloptions_test(i INT) WITH (vacuum_index_cleanup=on);
+SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass;
+ reloptions
+---------------------------
+ {vacuum_index_cleanup=on}
+(1 row)
+
+-- custom "third" value is available
+DROP TABLE reloptions_test;
+CREATE TABLE reloptions_test(i INT) WITH (vacuum_index_cleanup=auto);
+SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass;
+ reloptions
+-----------------------------
+ {vacuum_index_cleanup=auto}
+(1 row)
+
-- Test vacuum_truncate option
DROP TABLE reloptions_test;
CREATE TEMP TABLE reloptions_test(i INT NOT NULL, j text)
diff --git a/src/test/regress/sql/reloptions.sql b/src/test/regress/sql/reloptions.sql
index 24fbe0b478d..f5980dafcbc 100644
--- a/src/test/regress/sql/reloptions.sql
+++ b/src/test/regress/sql/reloptions.sql
@@ -59,6 +59,27 @@ UPDATE pg_class
ALTER TABLE reloptions_test RESET (illegal_option);
SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass;
+-- Tests for future (FIXME) ternary options
+
+-- behave as boolean option: accept unassigned name and truncated value
+DROP TABLE reloptions_test;
+CREATE TABLE reloptions_test(i INT) WITH (vacuum_truncate);
+SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass;
+
+DROP TABLE reloptions_test;
+CREATE TABLE reloptions_test(i INT) WITH (vacuum_truncate=FaLS);
+SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass;
+
+-- preferred "true" alias is used when storing
+DROP TABLE reloptions_test;
+CREATE TABLE reloptions_test(i INT) WITH (vacuum_index_cleanup=on);
+SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass;
+
+-- custom "third" value is available
+DROP TABLE reloptions_test;
+CREATE TABLE reloptions_test(i INT) WITH (vacuum_index_cleanup=auto);
+SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass;
+
-- Test vacuum_truncate option
DROP TABLE reloptions_test;
diff --git a/src/tools/pgindent/typedefs.list b/src/tools/pgindent/typedefs.list
index 3f3a888fd0e..1c8610fd46c 100644
--- a/src/tools/pgindent/typedefs.list
+++ b/src/tools/pgindent/typedefs.list
@@ -3952,6 +3952,7 @@ pg_sha512_ctx
pg_snapshot
pg_special_case
pg_stack_base_t
+pg_ternary
pg_time_t
pg_time_usec_t
pg_tz
@@ -4079,6 +4080,7 @@ relopt_kind
relopt_parse_elt
relopt_real
relopt_string
+relopt_ternary
relopt_type
relopt_value
relopts_validator
--
2.47.3
--c36t3fndxhkzbl5z--
^ permalink raw reply [nested|flat] 868+ messages in thread
* [PATCH v3] Introduce ternary reloptions
@ 2026-01-16 15:04 Álvaro Herrera <[email protected]>
0 siblings, 0 replies; 868+ messages in thread
From: Álvaro Herrera @ 2026-01-16 15:04 UTC (permalink / raw)
Introduce ternary reloption as a replacement for current
`vacuum_truncate` implementation. Remove the `vacuum_truncate_set`
separate flag and use TERNARY_UNSET instead.
This could also be used for other options such as `vacuum_index_cleanup`
and `buffering`, but lets get the scaffolding in first.
Discussion: https://postgr.es/m/3474141.usfYGdeWWP@thinkpad-pgpro
---
src/backend/access/common/reloptions.c | 137 ++++++++++++++++++-----
src/backend/commands/vacuum.c | 4 +-
src/include/access/reloptions.h | 27 ++---
src/include/postgres.h | 15 +++
src/include/utils/rel.h | 3 +-
src/test/regress/expected/reloptions.out | 36 ++++++
src/test/regress/sql/reloptions.sql | 21 ++++
src/tools/pgindent/typedefs.list | 2 +
8 files changed, 202 insertions(+), 43 deletions(-)
diff --git a/src/backend/access/common/reloptions.c b/src/backend/access/common/reloptions.c
index 0b83f98ed5f..6f1f577581a 100644
--- a/src/backend/access/common/reloptions.c
+++ b/src/backend/access/common/reloptions.c
@@ -40,9 +40,9 @@
*
* To add an option:
*
- * (i) decide on a type (bool, integer, real, enum, string), name, default
- * value, upper and lower bounds (if applicable); for strings, consider a
- * validation routine.
+ * (i) decide on a type (bool, ternary, integer, real, enum, string), name,
+ * default value, upper and lower bounds (if applicable); for strings,
+ * consider a validation routine.
* (ii) add a record below (or use add_<type>_reloption).
* (iii) add it to the appropriate options struct (perhaps StdRdOptions)
* (iv) add it to the appropriate handling routine (perhaps
@@ -147,15 +147,6 @@ static relopt_bool boolRelOpts[] =
},
false
},
- {
- {
- "vacuum_truncate",
- "Enables vacuum to truncate empty pages at the end of this table",
- RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
- ShareUpdateExclusiveLock
- },
- true
- },
{
{
"deduplicate_items",
@@ -170,6 +161,25 @@ static relopt_bool boolRelOpts[] =
{{NULL}}
};
+static relopt_ternary ternaryRelOpts[] =
+{
+ {
+ {
+ "vacuum_truncate",
+ "Enables vacuum to truncate empty pages at the end of this table",
+ RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
+ ShareUpdateExclusiveLock
+ },
+ TERNARY_UNSET
+ },
+ /* list terminator */
+ {
+ {
+ NULL
+ }
+ }
+};
+
static relopt_int intRelOpts[] =
{
{
@@ -609,6 +619,13 @@ initialize_reloptions(void)
boolRelOpts[i].gen.lockmode));
j++;
}
+ for (i = 0; ternaryRelOpts[i].gen.name; i++)
+ {
+ Assert(DoLockModesConflict(ternaryRelOpts[i].gen.lockmode,
+ ternaryRelOpts[i].gen.lockmode));
+ j++;
+ }
+
for (i = 0; intRelOpts[i].gen.name; i++)
{
Assert(DoLockModesConflict(intRelOpts[i].gen.lockmode,
@@ -649,6 +666,14 @@ initialize_reloptions(void)
j++;
}
+ for (i = 0; ternaryRelOpts[i].gen.name; i++)
+ {
+ relOpts[j] = &ternaryRelOpts[i].gen;
+ relOpts[j]->type = RELOPT_TYPE_TERNARY;
+ relOpts[j]->namelen = strlen(relOpts[j]->name);
+ j++;
+ }
+
for (i = 0; intRelOpts[i].gen.name; i++)
{
relOpts[j] = &intRelOpts[i].gen;
@@ -809,6 +834,9 @@ allocate_reloption(bits32 kinds, int type, const char *name, const char *desc,
case RELOPT_TYPE_BOOL:
size = sizeof(relopt_bool);
break;
+ case RELOPT_TYPE_TERNARY:
+ size = sizeof(relopt_ternary);
+ break;
case RELOPT_TYPE_INT:
size = sizeof(relopt_int);
break;
@@ -892,6 +920,57 @@ add_local_bool_reloption(local_relopts *relopts, const char *name,
add_local_reloption(relopts, (relopt_gen *) newoption, offset);
}
+/*
+ * init_ternary_reloption
+ * Allocate and initialize a new ternary reloption
+ */
+static relopt_ternary *
+init_ternary_reloption(bits32 kinds, const char *name, const char *desc,
+ pg_ternary default_val, LOCKMODE lockmode)
+{
+ relopt_ternary *newoption;
+
+ newoption = (relopt_ternary *)
+ allocate_reloption(kinds, RELOPT_TYPE_TERNARY, name, desc, lockmode);
+ newoption->default_val = default_val;
+
+ return newoption;
+}
+
+/*
+ * add_ternary_reloption
+ * Add a new ternary reloption
+ */
+void
+add_ternary_reloption(bits32 kinds, const char *name, const char *desc,
+ pg_ternary default_val, LOCKMODE lockmode)
+{
+ relopt_ternary *newoption;
+
+ newoption =
+ init_ternary_reloption(kinds, name, desc, default_val, lockmode);
+
+ add_reloption((relopt_gen *) newoption);
+}
+
+/*
+ * add_local_ternary_reloption
+ * Add a new ternary local reloption
+ *
+ * 'offset' is offset of ternary-typed field.
+ */
+void
+add_local_ternary_reloption(local_relopts *relopts, const char *name,
+ const char *desc, pg_ternary default_val,
+ int offset)
+{
+ relopt_ternary *newoption;
+
+ newoption =
+ init_ternary_reloption(RELOPT_KIND_LOCAL, name, desc, default_val, 0);
+
+ add_local_reloption(relopts, (relopt_gen *) newoption, offset);
+}
/*
* init_real_reloption
@@ -1626,6 +1705,19 @@ parse_one_reloption(relopt_value *option, char *text_str, int text_len,
option->gen->name, value)));
}
break;
+ case RELOPT_TYPE_TERNARY:
+ {
+ bool b;
+
+ parsed = parse_bool(value, &b);
+ option->values.ternary_val = b ? TERNARY_TRUE : TERNARY_FALSE;
+ if (validate && !parsed)
+ ereport(ERROR,
+ (errcode(ERRCODE_INVALID_PARAMETER_VALUE),
+ errmsg("invalid value for ternary option \"%s\": %s",
+ option->gen->name, value)));
+ }
+ break;
case RELOPT_TYPE_INT:
{
relopt_int *optint = (relopt_int *) option->gen;
@@ -1789,17 +1881,6 @@ fillRelOptions(void *rdopts, Size basesize,
char *itempos = ((char *) rdopts) + elems[j].offset;
char *string_val;
- /*
- * If isset_offset is provided, store whether the reloption is
- * set there.
- */
- if (elems[j].isset_offset > 0)
- {
- char *setpos = ((char *) rdopts) + elems[j].isset_offset;
-
- *(bool *) setpos = options[i].isset;
- }
-
switch (options[i].gen->type)
{
case RELOPT_TYPE_BOOL:
@@ -1807,6 +1888,11 @@ fillRelOptions(void *rdopts, Size basesize,
options[i].values.bool_val :
((relopt_bool *) options[i].gen)->default_val;
break;
+ case RELOPT_TYPE_TERNARY:
+ *(pg_ternary *) itempos = options[i].isset ?
+ options[i].values.ternary_val :
+ ((relopt_ternary *) options[i].gen)->default_val;
+ break;
case RELOPT_TYPE_INT:
*(int *) itempos = options[i].isset ?
options[i].values.int_val :
@@ -1923,8 +2009,8 @@ default_reloptions(Datum reloptions, bool validate, relopt_kind kind)
offsetof(StdRdOptions, parallel_workers)},
{"vacuum_index_cleanup", RELOPT_TYPE_ENUM,
offsetof(StdRdOptions, vacuum_index_cleanup)},
- {"vacuum_truncate", RELOPT_TYPE_BOOL,
- offsetof(StdRdOptions, vacuum_truncate), offsetof(StdRdOptions, vacuum_truncate_set)},
+ {"vacuum_truncate", RELOPT_TYPE_TERNARY,
+ offsetof(StdRdOptions, vacuum_truncate)},
{"vacuum_max_eager_freeze_failure_rate", RELOPT_TYPE_REAL,
offsetof(StdRdOptions, vacuum_max_eager_freeze_failure_rate)}
};
@@ -2004,7 +2090,6 @@ build_local_reloptions(local_relopts *relopts, Datum options, bool validate)
elems[i].optname = opt->option->name;
elems[i].opttype = opt->option->type;
elems[i].offset = opt->offset;
- elems[i].isset_offset = 0; /* not supported for local relopts yet */
i++;
}
diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c
index aa4fbec143f..696eab9bd97 100644
--- a/src/backend/commands/vacuum.c
+++ b/src/backend/commands/vacuum.c
@@ -2224,9 +2224,9 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams params,
{
StdRdOptions *opts = (StdRdOptions *) rel->rd_options;
- if (opts && opts->vacuum_truncate_set)
+ if (opts && opts->vacuum_truncate != TERNARY_UNSET)
{
- if (opts->vacuum_truncate)
+ if (opts->vacuum_truncate == TERNARY_TRUE)
params.truncate = VACOPTVALUE_ENABLED;
else
params.truncate = VACOPTVALUE_DISABLED;
diff --git a/src/include/access/reloptions.h b/src/include/access/reloptions.h
index 2f08e1b0cf0..dfbef2babf2 100644
--- a/src/include/access/reloptions.h
+++ b/src/include/access/reloptions.h
@@ -29,6 +29,7 @@
typedef enum relopt_type
{
RELOPT_TYPE_BOOL,
+ RELOPT_TYPE_TERNARY, /* on, off, unset */
RELOPT_TYPE_INT,
RELOPT_TYPE_REAL,
RELOPT_TYPE_ENUM,
@@ -80,6 +81,7 @@ typedef struct relopt_value
union
{
bool bool_val;
+ pg_ternary ternary_val;
int int_val;
double real_val;
int enum_val;
@@ -94,6 +96,12 @@ typedef struct relopt_bool
bool default_val;
} relopt_bool;
+typedef struct relopt_ternary
+{
+ relopt_gen gen;
+ int default_val;
+} relopt_ternary;
+
typedef struct relopt_int
{
relopt_gen gen;
@@ -152,19 +160,6 @@ typedef struct
const char *optname; /* option's name */
relopt_type opttype; /* option's datatype */
int offset; /* offset of field in result struct */
-
- /*
- * isset_offset is an optional offset of a field in the result struct that
- * stores whether the option is explicitly set for the relation or if it
- * just picked up the default value. In most cases, this can be
- * accomplished by giving the reloption a special out-of-range default
- * value (e.g., some integer reloptions use -2), but this isn't always
- * possible. For example, a Boolean reloption cannot be given an
- * out-of-range default, so we need another way to discover the source of
- * its value. This offset is only used if given a value greater than
- * zero.
- */
- int isset_offset;
} relopt_parse_elt;
/* Local reloption definition */
@@ -195,6 +190,9 @@ typedef struct local_relopts
extern relopt_kind add_reloption_kind(void);
extern void add_bool_reloption(bits32 kinds, const char *name, const char *desc,
bool default_val, LOCKMODE lockmode);
+extern void add_ternary_reloption(bits32 kinds, const char *name,
+ const char *desc, pg_ternary default_val,
+ LOCKMODE lockmode);
extern void add_int_reloption(bits32 kinds, const char *name, const char *desc,
int default_val, int min_val, int max_val,
LOCKMODE lockmode);
@@ -214,6 +212,9 @@ extern void register_reloptions_validator(local_relopts *relopts,
extern void add_local_bool_reloption(local_relopts *relopts, const char *name,
const char *desc, bool default_val,
int offset);
+extern void add_local_ternary_reloption(local_relopts *relopts,
+ const char *name, const char *desc,
+ pg_ternary default_val, int offset);
extern void add_local_int_reloption(local_relopts *relopts, const char *name,
const char *desc, int default_val,
int min_val, int max_val, int offset);
diff --git a/src/include/postgres.h b/src/include/postgres.h
index 7d93fbce709..47c88780776 100644
--- a/src/include/postgres.h
+++ b/src/include/postgres.h
@@ -543,6 +543,21 @@ Float8GetDatum(float8 X)
* ----------------------------------------------------------------
*/
+/*
+ * pg_ternary
+ * Boolean value with an extra "unset" value
+ *
+ * This enum can be used for values that want to distinguish between true,
+ * false, and unset.
+*/
+
+typedef enum pg_ternary
+{
+ TERNARY_FALSE = 0,
+ TERNARY_TRUE = 1,
+ TERNARY_UNSET = -1
+} pg_ternary;
+
/*
* NON_EXEC_STATIC: It's sometimes useful to define a variable or function
* that is normally static but extern when using EXEC_BACKEND (see
diff --git a/src/include/utils/rel.h b/src/include/utils/rel.h
index d03ab247788..5feb18a5373 100644
--- a/src/include/utils/rel.h
+++ b/src/include/utils/rel.h
@@ -347,8 +347,7 @@ typedef struct StdRdOptions
bool user_catalog_table; /* use as an additional catalog relation */
int parallel_workers; /* max number of parallel workers */
StdRdOptIndexCleanup vacuum_index_cleanup; /* controls index vacuuming */
- bool vacuum_truncate; /* enables vacuum to truncate a relation */
- bool vacuum_truncate_set; /* whether vacuum_truncate is set */
+ pg_ternary vacuum_truncate; /* enables vacuum to truncate a relation */
/*
* Fraction of pages in a relation that vacuum can eagerly scan and fail
diff --git a/src/test/regress/expected/reloptions.out b/src/test/regress/expected/reloptions.out
index 9de19b4e3f1..1c99f79ab01 100644
--- a/src/test/regress/expected/reloptions.out
+++ b/src/test/regress/expected/reloptions.out
@@ -98,6 +98,42 @@ SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass;
{fillfactor=13,autovacuum_enabled=false}
(1 row)
+-- Tests for future (FIXME) ternary options
+-- behave as boolean option: accept unassigned name and truncated value
+DROP TABLE reloptions_test;
+CREATE TABLE reloptions_test(i INT) WITH (vacuum_truncate);
+SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass;
+ reloptions
+------------------------
+ {vacuum_truncate=true}
+(1 row)
+
+DROP TABLE reloptions_test;
+CREATE TABLE reloptions_test(i INT) WITH (vacuum_truncate=FaLS);
+SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass;
+ reloptions
+------------------------
+ {vacuum_truncate=fals}
+(1 row)
+
+-- preferred "true" alias is used when storing
+DROP TABLE reloptions_test;
+CREATE TABLE reloptions_test(i INT) WITH (vacuum_index_cleanup=on);
+SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass;
+ reloptions
+---------------------------
+ {vacuum_index_cleanup=on}
+(1 row)
+
+-- custom "third" value is available
+DROP TABLE reloptions_test;
+CREATE TABLE reloptions_test(i INT) WITH (vacuum_index_cleanup=auto);
+SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass;
+ reloptions
+-----------------------------
+ {vacuum_index_cleanup=auto}
+(1 row)
+
-- Test vacuum_truncate option
DROP TABLE reloptions_test;
CREATE TEMP TABLE reloptions_test(i INT NOT NULL, j text)
diff --git a/src/test/regress/sql/reloptions.sql b/src/test/regress/sql/reloptions.sql
index 24fbe0b478d..f5980dafcbc 100644
--- a/src/test/regress/sql/reloptions.sql
+++ b/src/test/regress/sql/reloptions.sql
@@ -59,6 +59,27 @@ UPDATE pg_class
ALTER TABLE reloptions_test RESET (illegal_option);
SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass;
+-- Tests for future (FIXME) ternary options
+
+-- behave as boolean option: accept unassigned name and truncated value
+DROP TABLE reloptions_test;
+CREATE TABLE reloptions_test(i INT) WITH (vacuum_truncate);
+SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass;
+
+DROP TABLE reloptions_test;
+CREATE TABLE reloptions_test(i INT) WITH (vacuum_truncate=FaLS);
+SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass;
+
+-- preferred "true" alias is used when storing
+DROP TABLE reloptions_test;
+CREATE TABLE reloptions_test(i INT) WITH (vacuum_index_cleanup=on);
+SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass;
+
+-- custom "third" value is available
+DROP TABLE reloptions_test;
+CREATE TABLE reloptions_test(i INT) WITH (vacuum_index_cleanup=auto);
+SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass;
+
-- Test vacuum_truncate option
DROP TABLE reloptions_test;
diff --git a/src/tools/pgindent/typedefs.list b/src/tools/pgindent/typedefs.list
index 3f3a888fd0e..1c8610fd46c 100644
--- a/src/tools/pgindent/typedefs.list
+++ b/src/tools/pgindent/typedefs.list
@@ -3952,6 +3952,7 @@ pg_sha512_ctx
pg_snapshot
pg_special_case
pg_stack_base_t
+pg_ternary
pg_time_t
pg_time_usec_t
pg_tz
@@ -4079,6 +4080,7 @@ relopt_kind
relopt_parse_elt
relopt_real
relopt_string
+relopt_ternary
relopt_type
relopt_value
relopts_validator
--
2.47.3
--c36t3fndxhkzbl5z--
^ permalink raw reply [nested|flat] 868+ messages in thread
* [PATCH v3] Introduce ternary reloptions
@ 2026-01-16 15:04 Álvaro Herrera <[email protected]>
0 siblings, 0 replies; 868+ messages in thread
From: Álvaro Herrera @ 2026-01-16 15:04 UTC (permalink / raw)
Introduce ternary reloption as a replacement for current
`vacuum_truncate` implementation. Remove the `vacuum_truncate_set`
separate flag and use TERNARY_UNSET instead.
This could also be used for other options such as `vacuum_index_cleanup`
and `buffering`, but lets get the scaffolding in first.
Discussion: https://postgr.es/m/3474141.usfYGdeWWP@thinkpad-pgpro
---
src/backend/access/common/reloptions.c | 137 ++++++++++++++++++-----
src/backend/commands/vacuum.c | 4 +-
src/include/access/reloptions.h | 27 ++---
src/include/postgres.h | 15 +++
src/include/utils/rel.h | 3 +-
src/test/regress/expected/reloptions.out | 36 ++++++
src/test/regress/sql/reloptions.sql | 21 ++++
src/tools/pgindent/typedefs.list | 2 +
8 files changed, 202 insertions(+), 43 deletions(-)
diff --git a/src/backend/access/common/reloptions.c b/src/backend/access/common/reloptions.c
index 0b83f98ed5f..6f1f577581a 100644
--- a/src/backend/access/common/reloptions.c
+++ b/src/backend/access/common/reloptions.c
@@ -40,9 +40,9 @@
*
* To add an option:
*
- * (i) decide on a type (bool, integer, real, enum, string), name, default
- * value, upper and lower bounds (if applicable); for strings, consider a
- * validation routine.
+ * (i) decide on a type (bool, ternary, integer, real, enum, string), name,
+ * default value, upper and lower bounds (if applicable); for strings,
+ * consider a validation routine.
* (ii) add a record below (or use add_<type>_reloption).
* (iii) add it to the appropriate options struct (perhaps StdRdOptions)
* (iv) add it to the appropriate handling routine (perhaps
@@ -147,15 +147,6 @@ static relopt_bool boolRelOpts[] =
},
false
},
- {
- {
- "vacuum_truncate",
- "Enables vacuum to truncate empty pages at the end of this table",
- RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
- ShareUpdateExclusiveLock
- },
- true
- },
{
{
"deduplicate_items",
@@ -170,6 +161,25 @@ static relopt_bool boolRelOpts[] =
{{NULL}}
};
+static relopt_ternary ternaryRelOpts[] =
+{
+ {
+ {
+ "vacuum_truncate",
+ "Enables vacuum to truncate empty pages at the end of this table",
+ RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
+ ShareUpdateExclusiveLock
+ },
+ TERNARY_UNSET
+ },
+ /* list terminator */
+ {
+ {
+ NULL
+ }
+ }
+};
+
static relopt_int intRelOpts[] =
{
{
@@ -609,6 +619,13 @@ initialize_reloptions(void)
boolRelOpts[i].gen.lockmode));
j++;
}
+ for (i = 0; ternaryRelOpts[i].gen.name; i++)
+ {
+ Assert(DoLockModesConflict(ternaryRelOpts[i].gen.lockmode,
+ ternaryRelOpts[i].gen.lockmode));
+ j++;
+ }
+
for (i = 0; intRelOpts[i].gen.name; i++)
{
Assert(DoLockModesConflict(intRelOpts[i].gen.lockmode,
@@ -649,6 +666,14 @@ initialize_reloptions(void)
j++;
}
+ for (i = 0; ternaryRelOpts[i].gen.name; i++)
+ {
+ relOpts[j] = &ternaryRelOpts[i].gen;
+ relOpts[j]->type = RELOPT_TYPE_TERNARY;
+ relOpts[j]->namelen = strlen(relOpts[j]->name);
+ j++;
+ }
+
for (i = 0; intRelOpts[i].gen.name; i++)
{
relOpts[j] = &intRelOpts[i].gen;
@@ -809,6 +834,9 @@ allocate_reloption(bits32 kinds, int type, const char *name, const char *desc,
case RELOPT_TYPE_BOOL:
size = sizeof(relopt_bool);
break;
+ case RELOPT_TYPE_TERNARY:
+ size = sizeof(relopt_ternary);
+ break;
case RELOPT_TYPE_INT:
size = sizeof(relopt_int);
break;
@@ -892,6 +920,57 @@ add_local_bool_reloption(local_relopts *relopts, const char *name,
add_local_reloption(relopts, (relopt_gen *) newoption, offset);
}
+/*
+ * init_ternary_reloption
+ * Allocate and initialize a new ternary reloption
+ */
+static relopt_ternary *
+init_ternary_reloption(bits32 kinds, const char *name, const char *desc,
+ pg_ternary default_val, LOCKMODE lockmode)
+{
+ relopt_ternary *newoption;
+
+ newoption = (relopt_ternary *)
+ allocate_reloption(kinds, RELOPT_TYPE_TERNARY, name, desc, lockmode);
+ newoption->default_val = default_val;
+
+ return newoption;
+}
+
+/*
+ * add_ternary_reloption
+ * Add a new ternary reloption
+ */
+void
+add_ternary_reloption(bits32 kinds, const char *name, const char *desc,
+ pg_ternary default_val, LOCKMODE lockmode)
+{
+ relopt_ternary *newoption;
+
+ newoption =
+ init_ternary_reloption(kinds, name, desc, default_val, lockmode);
+
+ add_reloption((relopt_gen *) newoption);
+}
+
+/*
+ * add_local_ternary_reloption
+ * Add a new ternary local reloption
+ *
+ * 'offset' is offset of ternary-typed field.
+ */
+void
+add_local_ternary_reloption(local_relopts *relopts, const char *name,
+ const char *desc, pg_ternary default_val,
+ int offset)
+{
+ relopt_ternary *newoption;
+
+ newoption =
+ init_ternary_reloption(RELOPT_KIND_LOCAL, name, desc, default_val, 0);
+
+ add_local_reloption(relopts, (relopt_gen *) newoption, offset);
+}
/*
* init_real_reloption
@@ -1626,6 +1705,19 @@ parse_one_reloption(relopt_value *option, char *text_str, int text_len,
option->gen->name, value)));
}
break;
+ case RELOPT_TYPE_TERNARY:
+ {
+ bool b;
+
+ parsed = parse_bool(value, &b);
+ option->values.ternary_val = b ? TERNARY_TRUE : TERNARY_FALSE;
+ if (validate && !parsed)
+ ereport(ERROR,
+ (errcode(ERRCODE_INVALID_PARAMETER_VALUE),
+ errmsg("invalid value for ternary option \"%s\": %s",
+ option->gen->name, value)));
+ }
+ break;
case RELOPT_TYPE_INT:
{
relopt_int *optint = (relopt_int *) option->gen;
@@ -1789,17 +1881,6 @@ fillRelOptions(void *rdopts, Size basesize,
char *itempos = ((char *) rdopts) + elems[j].offset;
char *string_val;
- /*
- * If isset_offset is provided, store whether the reloption is
- * set there.
- */
- if (elems[j].isset_offset > 0)
- {
- char *setpos = ((char *) rdopts) + elems[j].isset_offset;
-
- *(bool *) setpos = options[i].isset;
- }
-
switch (options[i].gen->type)
{
case RELOPT_TYPE_BOOL:
@@ -1807,6 +1888,11 @@ fillRelOptions(void *rdopts, Size basesize,
options[i].values.bool_val :
((relopt_bool *) options[i].gen)->default_val;
break;
+ case RELOPT_TYPE_TERNARY:
+ *(pg_ternary *) itempos = options[i].isset ?
+ options[i].values.ternary_val :
+ ((relopt_ternary *) options[i].gen)->default_val;
+ break;
case RELOPT_TYPE_INT:
*(int *) itempos = options[i].isset ?
options[i].values.int_val :
@@ -1923,8 +2009,8 @@ default_reloptions(Datum reloptions, bool validate, relopt_kind kind)
offsetof(StdRdOptions, parallel_workers)},
{"vacuum_index_cleanup", RELOPT_TYPE_ENUM,
offsetof(StdRdOptions, vacuum_index_cleanup)},
- {"vacuum_truncate", RELOPT_TYPE_BOOL,
- offsetof(StdRdOptions, vacuum_truncate), offsetof(StdRdOptions, vacuum_truncate_set)},
+ {"vacuum_truncate", RELOPT_TYPE_TERNARY,
+ offsetof(StdRdOptions, vacuum_truncate)},
{"vacuum_max_eager_freeze_failure_rate", RELOPT_TYPE_REAL,
offsetof(StdRdOptions, vacuum_max_eager_freeze_failure_rate)}
};
@@ -2004,7 +2090,6 @@ build_local_reloptions(local_relopts *relopts, Datum options, bool validate)
elems[i].optname = opt->option->name;
elems[i].opttype = opt->option->type;
elems[i].offset = opt->offset;
- elems[i].isset_offset = 0; /* not supported for local relopts yet */
i++;
}
diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c
index aa4fbec143f..696eab9bd97 100644
--- a/src/backend/commands/vacuum.c
+++ b/src/backend/commands/vacuum.c
@@ -2224,9 +2224,9 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams params,
{
StdRdOptions *opts = (StdRdOptions *) rel->rd_options;
- if (opts && opts->vacuum_truncate_set)
+ if (opts && opts->vacuum_truncate != TERNARY_UNSET)
{
- if (opts->vacuum_truncate)
+ if (opts->vacuum_truncate == TERNARY_TRUE)
params.truncate = VACOPTVALUE_ENABLED;
else
params.truncate = VACOPTVALUE_DISABLED;
diff --git a/src/include/access/reloptions.h b/src/include/access/reloptions.h
index 2f08e1b0cf0..dfbef2babf2 100644
--- a/src/include/access/reloptions.h
+++ b/src/include/access/reloptions.h
@@ -29,6 +29,7 @@
typedef enum relopt_type
{
RELOPT_TYPE_BOOL,
+ RELOPT_TYPE_TERNARY, /* on, off, unset */
RELOPT_TYPE_INT,
RELOPT_TYPE_REAL,
RELOPT_TYPE_ENUM,
@@ -80,6 +81,7 @@ typedef struct relopt_value
union
{
bool bool_val;
+ pg_ternary ternary_val;
int int_val;
double real_val;
int enum_val;
@@ -94,6 +96,12 @@ typedef struct relopt_bool
bool default_val;
} relopt_bool;
+typedef struct relopt_ternary
+{
+ relopt_gen gen;
+ int default_val;
+} relopt_ternary;
+
typedef struct relopt_int
{
relopt_gen gen;
@@ -152,19 +160,6 @@ typedef struct
const char *optname; /* option's name */
relopt_type opttype; /* option's datatype */
int offset; /* offset of field in result struct */
-
- /*
- * isset_offset is an optional offset of a field in the result struct that
- * stores whether the option is explicitly set for the relation or if it
- * just picked up the default value. In most cases, this can be
- * accomplished by giving the reloption a special out-of-range default
- * value (e.g., some integer reloptions use -2), but this isn't always
- * possible. For example, a Boolean reloption cannot be given an
- * out-of-range default, so we need another way to discover the source of
- * its value. This offset is only used if given a value greater than
- * zero.
- */
- int isset_offset;
} relopt_parse_elt;
/* Local reloption definition */
@@ -195,6 +190,9 @@ typedef struct local_relopts
extern relopt_kind add_reloption_kind(void);
extern void add_bool_reloption(bits32 kinds, const char *name, const char *desc,
bool default_val, LOCKMODE lockmode);
+extern void add_ternary_reloption(bits32 kinds, const char *name,
+ const char *desc, pg_ternary default_val,
+ LOCKMODE lockmode);
extern void add_int_reloption(bits32 kinds, const char *name, const char *desc,
int default_val, int min_val, int max_val,
LOCKMODE lockmode);
@@ -214,6 +212,9 @@ extern void register_reloptions_validator(local_relopts *relopts,
extern void add_local_bool_reloption(local_relopts *relopts, const char *name,
const char *desc, bool default_val,
int offset);
+extern void add_local_ternary_reloption(local_relopts *relopts,
+ const char *name, const char *desc,
+ pg_ternary default_val, int offset);
extern void add_local_int_reloption(local_relopts *relopts, const char *name,
const char *desc, int default_val,
int min_val, int max_val, int offset);
diff --git a/src/include/postgres.h b/src/include/postgres.h
index 7d93fbce709..47c88780776 100644
--- a/src/include/postgres.h
+++ b/src/include/postgres.h
@@ -543,6 +543,21 @@ Float8GetDatum(float8 X)
* ----------------------------------------------------------------
*/
+/*
+ * pg_ternary
+ * Boolean value with an extra "unset" value
+ *
+ * This enum can be used for values that want to distinguish between true,
+ * false, and unset.
+*/
+
+typedef enum pg_ternary
+{
+ TERNARY_FALSE = 0,
+ TERNARY_TRUE = 1,
+ TERNARY_UNSET = -1
+} pg_ternary;
+
/*
* NON_EXEC_STATIC: It's sometimes useful to define a variable or function
* that is normally static but extern when using EXEC_BACKEND (see
diff --git a/src/include/utils/rel.h b/src/include/utils/rel.h
index d03ab247788..5feb18a5373 100644
--- a/src/include/utils/rel.h
+++ b/src/include/utils/rel.h
@@ -347,8 +347,7 @@ typedef struct StdRdOptions
bool user_catalog_table; /* use as an additional catalog relation */
int parallel_workers; /* max number of parallel workers */
StdRdOptIndexCleanup vacuum_index_cleanup; /* controls index vacuuming */
- bool vacuum_truncate; /* enables vacuum to truncate a relation */
- bool vacuum_truncate_set; /* whether vacuum_truncate is set */
+ pg_ternary vacuum_truncate; /* enables vacuum to truncate a relation */
/*
* Fraction of pages in a relation that vacuum can eagerly scan and fail
diff --git a/src/test/regress/expected/reloptions.out b/src/test/regress/expected/reloptions.out
index 9de19b4e3f1..1c99f79ab01 100644
--- a/src/test/regress/expected/reloptions.out
+++ b/src/test/regress/expected/reloptions.out
@@ -98,6 +98,42 @@ SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass;
{fillfactor=13,autovacuum_enabled=false}
(1 row)
+-- Tests for future (FIXME) ternary options
+-- behave as boolean option: accept unassigned name and truncated value
+DROP TABLE reloptions_test;
+CREATE TABLE reloptions_test(i INT) WITH (vacuum_truncate);
+SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass;
+ reloptions
+------------------------
+ {vacuum_truncate=true}
+(1 row)
+
+DROP TABLE reloptions_test;
+CREATE TABLE reloptions_test(i INT) WITH (vacuum_truncate=FaLS);
+SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass;
+ reloptions
+------------------------
+ {vacuum_truncate=fals}
+(1 row)
+
+-- preferred "true" alias is used when storing
+DROP TABLE reloptions_test;
+CREATE TABLE reloptions_test(i INT) WITH (vacuum_index_cleanup=on);
+SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass;
+ reloptions
+---------------------------
+ {vacuum_index_cleanup=on}
+(1 row)
+
+-- custom "third" value is available
+DROP TABLE reloptions_test;
+CREATE TABLE reloptions_test(i INT) WITH (vacuum_index_cleanup=auto);
+SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass;
+ reloptions
+-----------------------------
+ {vacuum_index_cleanup=auto}
+(1 row)
+
-- Test vacuum_truncate option
DROP TABLE reloptions_test;
CREATE TEMP TABLE reloptions_test(i INT NOT NULL, j text)
diff --git a/src/test/regress/sql/reloptions.sql b/src/test/regress/sql/reloptions.sql
index 24fbe0b478d..f5980dafcbc 100644
--- a/src/test/regress/sql/reloptions.sql
+++ b/src/test/regress/sql/reloptions.sql
@@ -59,6 +59,27 @@ UPDATE pg_class
ALTER TABLE reloptions_test RESET (illegal_option);
SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass;
+-- Tests for future (FIXME) ternary options
+
+-- behave as boolean option: accept unassigned name and truncated value
+DROP TABLE reloptions_test;
+CREATE TABLE reloptions_test(i INT) WITH (vacuum_truncate);
+SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass;
+
+DROP TABLE reloptions_test;
+CREATE TABLE reloptions_test(i INT) WITH (vacuum_truncate=FaLS);
+SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass;
+
+-- preferred "true" alias is used when storing
+DROP TABLE reloptions_test;
+CREATE TABLE reloptions_test(i INT) WITH (vacuum_index_cleanup=on);
+SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass;
+
+-- custom "third" value is available
+DROP TABLE reloptions_test;
+CREATE TABLE reloptions_test(i INT) WITH (vacuum_index_cleanup=auto);
+SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass;
+
-- Test vacuum_truncate option
DROP TABLE reloptions_test;
diff --git a/src/tools/pgindent/typedefs.list b/src/tools/pgindent/typedefs.list
index 3f3a888fd0e..1c8610fd46c 100644
--- a/src/tools/pgindent/typedefs.list
+++ b/src/tools/pgindent/typedefs.list
@@ -3952,6 +3952,7 @@ pg_sha512_ctx
pg_snapshot
pg_special_case
pg_stack_base_t
+pg_ternary
pg_time_t
pg_time_usec_t
pg_tz
@@ -4079,6 +4080,7 @@ relopt_kind
relopt_parse_elt
relopt_real
relopt_string
+relopt_ternary
relopt_type
relopt_value
relopts_validator
--
2.47.3
--c36t3fndxhkzbl5z--
^ permalink raw reply [nested|flat] 868+ messages in thread
* [PATCH v3] Introduce ternary reloptions
@ 2026-01-16 15:04 Álvaro Herrera <[email protected]>
0 siblings, 0 replies; 868+ messages in thread
From: Álvaro Herrera @ 2026-01-16 15:04 UTC (permalink / raw)
Introduce ternary reloption as a replacement for current
`vacuum_truncate` implementation. Remove the `vacuum_truncate_set`
separate flag and use TERNARY_UNSET instead.
This could also be used for other options such as `vacuum_index_cleanup`
and `buffering`, but lets get the scaffolding in first.
Discussion: https://postgr.es/m/3474141.usfYGdeWWP@thinkpad-pgpro
---
src/backend/access/common/reloptions.c | 137 ++++++++++++++++++-----
src/backend/commands/vacuum.c | 4 +-
src/include/access/reloptions.h | 27 ++---
src/include/postgres.h | 15 +++
src/include/utils/rel.h | 3 +-
src/test/regress/expected/reloptions.out | 36 ++++++
src/test/regress/sql/reloptions.sql | 21 ++++
src/tools/pgindent/typedefs.list | 2 +
8 files changed, 202 insertions(+), 43 deletions(-)
diff --git a/src/backend/access/common/reloptions.c b/src/backend/access/common/reloptions.c
index 0b83f98ed5f..6f1f577581a 100644
--- a/src/backend/access/common/reloptions.c
+++ b/src/backend/access/common/reloptions.c
@@ -40,9 +40,9 @@
*
* To add an option:
*
- * (i) decide on a type (bool, integer, real, enum, string), name, default
- * value, upper and lower bounds (if applicable); for strings, consider a
- * validation routine.
+ * (i) decide on a type (bool, ternary, integer, real, enum, string), name,
+ * default value, upper and lower bounds (if applicable); for strings,
+ * consider a validation routine.
* (ii) add a record below (or use add_<type>_reloption).
* (iii) add it to the appropriate options struct (perhaps StdRdOptions)
* (iv) add it to the appropriate handling routine (perhaps
@@ -147,15 +147,6 @@ static relopt_bool boolRelOpts[] =
},
false
},
- {
- {
- "vacuum_truncate",
- "Enables vacuum to truncate empty pages at the end of this table",
- RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
- ShareUpdateExclusiveLock
- },
- true
- },
{
{
"deduplicate_items",
@@ -170,6 +161,25 @@ static relopt_bool boolRelOpts[] =
{{NULL}}
};
+static relopt_ternary ternaryRelOpts[] =
+{
+ {
+ {
+ "vacuum_truncate",
+ "Enables vacuum to truncate empty pages at the end of this table",
+ RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
+ ShareUpdateExclusiveLock
+ },
+ TERNARY_UNSET
+ },
+ /* list terminator */
+ {
+ {
+ NULL
+ }
+ }
+};
+
static relopt_int intRelOpts[] =
{
{
@@ -609,6 +619,13 @@ initialize_reloptions(void)
boolRelOpts[i].gen.lockmode));
j++;
}
+ for (i = 0; ternaryRelOpts[i].gen.name; i++)
+ {
+ Assert(DoLockModesConflict(ternaryRelOpts[i].gen.lockmode,
+ ternaryRelOpts[i].gen.lockmode));
+ j++;
+ }
+
for (i = 0; intRelOpts[i].gen.name; i++)
{
Assert(DoLockModesConflict(intRelOpts[i].gen.lockmode,
@@ -649,6 +666,14 @@ initialize_reloptions(void)
j++;
}
+ for (i = 0; ternaryRelOpts[i].gen.name; i++)
+ {
+ relOpts[j] = &ternaryRelOpts[i].gen;
+ relOpts[j]->type = RELOPT_TYPE_TERNARY;
+ relOpts[j]->namelen = strlen(relOpts[j]->name);
+ j++;
+ }
+
for (i = 0; intRelOpts[i].gen.name; i++)
{
relOpts[j] = &intRelOpts[i].gen;
@@ -809,6 +834,9 @@ allocate_reloption(bits32 kinds, int type, const char *name, const char *desc,
case RELOPT_TYPE_BOOL:
size = sizeof(relopt_bool);
break;
+ case RELOPT_TYPE_TERNARY:
+ size = sizeof(relopt_ternary);
+ break;
case RELOPT_TYPE_INT:
size = sizeof(relopt_int);
break;
@@ -892,6 +920,57 @@ add_local_bool_reloption(local_relopts *relopts, const char *name,
add_local_reloption(relopts, (relopt_gen *) newoption, offset);
}
+/*
+ * init_ternary_reloption
+ * Allocate and initialize a new ternary reloption
+ */
+static relopt_ternary *
+init_ternary_reloption(bits32 kinds, const char *name, const char *desc,
+ pg_ternary default_val, LOCKMODE lockmode)
+{
+ relopt_ternary *newoption;
+
+ newoption = (relopt_ternary *)
+ allocate_reloption(kinds, RELOPT_TYPE_TERNARY, name, desc, lockmode);
+ newoption->default_val = default_val;
+
+ return newoption;
+}
+
+/*
+ * add_ternary_reloption
+ * Add a new ternary reloption
+ */
+void
+add_ternary_reloption(bits32 kinds, const char *name, const char *desc,
+ pg_ternary default_val, LOCKMODE lockmode)
+{
+ relopt_ternary *newoption;
+
+ newoption =
+ init_ternary_reloption(kinds, name, desc, default_val, lockmode);
+
+ add_reloption((relopt_gen *) newoption);
+}
+
+/*
+ * add_local_ternary_reloption
+ * Add a new ternary local reloption
+ *
+ * 'offset' is offset of ternary-typed field.
+ */
+void
+add_local_ternary_reloption(local_relopts *relopts, const char *name,
+ const char *desc, pg_ternary default_val,
+ int offset)
+{
+ relopt_ternary *newoption;
+
+ newoption =
+ init_ternary_reloption(RELOPT_KIND_LOCAL, name, desc, default_val, 0);
+
+ add_local_reloption(relopts, (relopt_gen *) newoption, offset);
+}
/*
* init_real_reloption
@@ -1626,6 +1705,19 @@ parse_one_reloption(relopt_value *option, char *text_str, int text_len,
option->gen->name, value)));
}
break;
+ case RELOPT_TYPE_TERNARY:
+ {
+ bool b;
+
+ parsed = parse_bool(value, &b);
+ option->values.ternary_val = b ? TERNARY_TRUE : TERNARY_FALSE;
+ if (validate && !parsed)
+ ereport(ERROR,
+ (errcode(ERRCODE_INVALID_PARAMETER_VALUE),
+ errmsg("invalid value for ternary option \"%s\": %s",
+ option->gen->name, value)));
+ }
+ break;
case RELOPT_TYPE_INT:
{
relopt_int *optint = (relopt_int *) option->gen;
@@ -1789,17 +1881,6 @@ fillRelOptions(void *rdopts, Size basesize,
char *itempos = ((char *) rdopts) + elems[j].offset;
char *string_val;
- /*
- * If isset_offset is provided, store whether the reloption is
- * set there.
- */
- if (elems[j].isset_offset > 0)
- {
- char *setpos = ((char *) rdopts) + elems[j].isset_offset;
-
- *(bool *) setpos = options[i].isset;
- }
-
switch (options[i].gen->type)
{
case RELOPT_TYPE_BOOL:
@@ -1807,6 +1888,11 @@ fillRelOptions(void *rdopts, Size basesize,
options[i].values.bool_val :
((relopt_bool *) options[i].gen)->default_val;
break;
+ case RELOPT_TYPE_TERNARY:
+ *(pg_ternary *) itempos = options[i].isset ?
+ options[i].values.ternary_val :
+ ((relopt_ternary *) options[i].gen)->default_val;
+ break;
case RELOPT_TYPE_INT:
*(int *) itempos = options[i].isset ?
options[i].values.int_val :
@@ -1923,8 +2009,8 @@ default_reloptions(Datum reloptions, bool validate, relopt_kind kind)
offsetof(StdRdOptions, parallel_workers)},
{"vacuum_index_cleanup", RELOPT_TYPE_ENUM,
offsetof(StdRdOptions, vacuum_index_cleanup)},
- {"vacuum_truncate", RELOPT_TYPE_BOOL,
- offsetof(StdRdOptions, vacuum_truncate), offsetof(StdRdOptions, vacuum_truncate_set)},
+ {"vacuum_truncate", RELOPT_TYPE_TERNARY,
+ offsetof(StdRdOptions, vacuum_truncate)},
{"vacuum_max_eager_freeze_failure_rate", RELOPT_TYPE_REAL,
offsetof(StdRdOptions, vacuum_max_eager_freeze_failure_rate)}
};
@@ -2004,7 +2090,6 @@ build_local_reloptions(local_relopts *relopts, Datum options, bool validate)
elems[i].optname = opt->option->name;
elems[i].opttype = opt->option->type;
elems[i].offset = opt->offset;
- elems[i].isset_offset = 0; /* not supported for local relopts yet */
i++;
}
diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c
index aa4fbec143f..696eab9bd97 100644
--- a/src/backend/commands/vacuum.c
+++ b/src/backend/commands/vacuum.c
@@ -2224,9 +2224,9 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams params,
{
StdRdOptions *opts = (StdRdOptions *) rel->rd_options;
- if (opts && opts->vacuum_truncate_set)
+ if (opts && opts->vacuum_truncate != TERNARY_UNSET)
{
- if (opts->vacuum_truncate)
+ if (opts->vacuum_truncate == TERNARY_TRUE)
params.truncate = VACOPTVALUE_ENABLED;
else
params.truncate = VACOPTVALUE_DISABLED;
diff --git a/src/include/access/reloptions.h b/src/include/access/reloptions.h
index 2f08e1b0cf0..dfbef2babf2 100644
--- a/src/include/access/reloptions.h
+++ b/src/include/access/reloptions.h
@@ -29,6 +29,7 @@
typedef enum relopt_type
{
RELOPT_TYPE_BOOL,
+ RELOPT_TYPE_TERNARY, /* on, off, unset */
RELOPT_TYPE_INT,
RELOPT_TYPE_REAL,
RELOPT_TYPE_ENUM,
@@ -80,6 +81,7 @@ typedef struct relopt_value
union
{
bool bool_val;
+ pg_ternary ternary_val;
int int_val;
double real_val;
int enum_val;
@@ -94,6 +96,12 @@ typedef struct relopt_bool
bool default_val;
} relopt_bool;
+typedef struct relopt_ternary
+{
+ relopt_gen gen;
+ int default_val;
+} relopt_ternary;
+
typedef struct relopt_int
{
relopt_gen gen;
@@ -152,19 +160,6 @@ typedef struct
const char *optname; /* option's name */
relopt_type opttype; /* option's datatype */
int offset; /* offset of field in result struct */
-
- /*
- * isset_offset is an optional offset of a field in the result struct that
- * stores whether the option is explicitly set for the relation or if it
- * just picked up the default value. In most cases, this can be
- * accomplished by giving the reloption a special out-of-range default
- * value (e.g., some integer reloptions use -2), but this isn't always
- * possible. For example, a Boolean reloption cannot be given an
- * out-of-range default, so we need another way to discover the source of
- * its value. This offset is only used if given a value greater than
- * zero.
- */
- int isset_offset;
} relopt_parse_elt;
/* Local reloption definition */
@@ -195,6 +190,9 @@ typedef struct local_relopts
extern relopt_kind add_reloption_kind(void);
extern void add_bool_reloption(bits32 kinds, const char *name, const char *desc,
bool default_val, LOCKMODE lockmode);
+extern void add_ternary_reloption(bits32 kinds, const char *name,
+ const char *desc, pg_ternary default_val,
+ LOCKMODE lockmode);
extern void add_int_reloption(bits32 kinds, const char *name, const char *desc,
int default_val, int min_val, int max_val,
LOCKMODE lockmode);
@@ -214,6 +212,9 @@ extern void register_reloptions_validator(local_relopts *relopts,
extern void add_local_bool_reloption(local_relopts *relopts, const char *name,
const char *desc, bool default_val,
int offset);
+extern void add_local_ternary_reloption(local_relopts *relopts,
+ const char *name, const char *desc,
+ pg_ternary default_val, int offset);
extern void add_local_int_reloption(local_relopts *relopts, const char *name,
const char *desc, int default_val,
int min_val, int max_val, int offset);
diff --git a/src/include/postgres.h b/src/include/postgres.h
index 7d93fbce709..47c88780776 100644
--- a/src/include/postgres.h
+++ b/src/include/postgres.h
@@ -543,6 +543,21 @@ Float8GetDatum(float8 X)
* ----------------------------------------------------------------
*/
+/*
+ * pg_ternary
+ * Boolean value with an extra "unset" value
+ *
+ * This enum can be used for values that want to distinguish between true,
+ * false, and unset.
+*/
+
+typedef enum pg_ternary
+{
+ TERNARY_FALSE = 0,
+ TERNARY_TRUE = 1,
+ TERNARY_UNSET = -1
+} pg_ternary;
+
/*
* NON_EXEC_STATIC: It's sometimes useful to define a variable or function
* that is normally static but extern when using EXEC_BACKEND (see
diff --git a/src/include/utils/rel.h b/src/include/utils/rel.h
index d03ab247788..5feb18a5373 100644
--- a/src/include/utils/rel.h
+++ b/src/include/utils/rel.h
@@ -347,8 +347,7 @@ typedef struct StdRdOptions
bool user_catalog_table; /* use as an additional catalog relation */
int parallel_workers; /* max number of parallel workers */
StdRdOptIndexCleanup vacuum_index_cleanup; /* controls index vacuuming */
- bool vacuum_truncate; /* enables vacuum to truncate a relation */
- bool vacuum_truncate_set; /* whether vacuum_truncate is set */
+ pg_ternary vacuum_truncate; /* enables vacuum to truncate a relation */
/*
* Fraction of pages in a relation that vacuum can eagerly scan and fail
diff --git a/src/test/regress/expected/reloptions.out b/src/test/regress/expected/reloptions.out
index 9de19b4e3f1..1c99f79ab01 100644
--- a/src/test/regress/expected/reloptions.out
+++ b/src/test/regress/expected/reloptions.out
@@ -98,6 +98,42 @@ SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass;
{fillfactor=13,autovacuum_enabled=false}
(1 row)
+-- Tests for future (FIXME) ternary options
+-- behave as boolean option: accept unassigned name and truncated value
+DROP TABLE reloptions_test;
+CREATE TABLE reloptions_test(i INT) WITH (vacuum_truncate);
+SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass;
+ reloptions
+------------------------
+ {vacuum_truncate=true}
+(1 row)
+
+DROP TABLE reloptions_test;
+CREATE TABLE reloptions_test(i INT) WITH (vacuum_truncate=FaLS);
+SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass;
+ reloptions
+------------------------
+ {vacuum_truncate=fals}
+(1 row)
+
+-- preferred "true" alias is used when storing
+DROP TABLE reloptions_test;
+CREATE TABLE reloptions_test(i INT) WITH (vacuum_index_cleanup=on);
+SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass;
+ reloptions
+---------------------------
+ {vacuum_index_cleanup=on}
+(1 row)
+
+-- custom "third" value is available
+DROP TABLE reloptions_test;
+CREATE TABLE reloptions_test(i INT) WITH (vacuum_index_cleanup=auto);
+SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass;
+ reloptions
+-----------------------------
+ {vacuum_index_cleanup=auto}
+(1 row)
+
-- Test vacuum_truncate option
DROP TABLE reloptions_test;
CREATE TEMP TABLE reloptions_test(i INT NOT NULL, j text)
diff --git a/src/test/regress/sql/reloptions.sql b/src/test/regress/sql/reloptions.sql
index 24fbe0b478d..f5980dafcbc 100644
--- a/src/test/regress/sql/reloptions.sql
+++ b/src/test/regress/sql/reloptions.sql
@@ -59,6 +59,27 @@ UPDATE pg_class
ALTER TABLE reloptions_test RESET (illegal_option);
SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass;
+-- Tests for future (FIXME) ternary options
+
+-- behave as boolean option: accept unassigned name and truncated value
+DROP TABLE reloptions_test;
+CREATE TABLE reloptions_test(i INT) WITH (vacuum_truncate);
+SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass;
+
+DROP TABLE reloptions_test;
+CREATE TABLE reloptions_test(i INT) WITH (vacuum_truncate=FaLS);
+SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass;
+
+-- preferred "true" alias is used when storing
+DROP TABLE reloptions_test;
+CREATE TABLE reloptions_test(i INT) WITH (vacuum_index_cleanup=on);
+SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass;
+
+-- custom "third" value is available
+DROP TABLE reloptions_test;
+CREATE TABLE reloptions_test(i INT) WITH (vacuum_index_cleanup=auto);
+SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass;
+
-- Test vacuum_truncate option
DROP TABLE reloptions_test;
diff --git a/src/tools/pgindent/typedefs.list b/src/tools/pgindent/typedefs.list
index 3f3a888fd0e..1c8610fd46c 100644
--- a/src/tools/pgindent/typedefs.list
+++ b/src/tools/pgindent/typedefs.list
@@ -3952,6 +3952,7 @@ pg_sha512_ctx
pg_snapshot
pg_special_case
pg_stack_base_t
+pg_ternary
pg_time_t
pg_time_usec_t
pg_tz
@@ -4079,6 +4080,7 @@ relopt_kind
relopt_parse_elt
relopt_real
relopt_string
+relopt_ternary
relopt_type
relopt_value
relopts_validator
--
2.47.3
--c36t3fndxhkzbl5z--
^ permalink raw reply [nested|flat] 868+ messages in thread
* [PATCH v3] Introduce ternary reloptions
@ 2026-01-16 15:04 Álvaro Herrera <[email protected]>
0 siblings, 0 replies; 868+ messages in thread
From: Álvaro Herrera @ 2026-01-16 15:04 UTC (permalink / raw)
Introduce ternary reloption as a replacement for current
`vacuum_truncate` implementation. Remove the `vacuum_truncate_set`
separate flag and use TERNARY_UNSET instead.
This could also be used for other options such as `vacuum_index_cleanup`
and `buffering`, but lets get the scaffolding in first.
Discussion: https://postgr.es/m/3474141.usfYGdeWWP@thinkpad-pgpro
---
src/backend/access/common/reloptions.c | 137 ++++++++++++++++++-----
src/backend/commands/vacuum.c | 4 +-
src/include/access/reloptions.h | 27 ++---
src/include/postgres.h | 15 +++
src/include/utils/rel.h | 3 +-
src/test/regress/expected/reloptions.out | 36 ++++++
src/test/regress/sql/reloptions.sql | 21 ++++
src/tools/pgindent/typedefs.list | 2 +
8 files changed, 202 insertions(+), 43 deletions(-)
diff --git a/src/backend/access/common/reloptions.c b/src/backend/access/common/reloptions.c
index 0b83f98ed5f..6f1f577581a 100644
--- a/src/backend/access/common/reloptions.c
+++ b/src/backend/access/common/reloptions.c
@@ -40,9 +40,9 @@
*
* To add an option:
*
- * (i) decide on a type (bool, integer, real, enum, string), name, default
- * value, upper and lower bounds (if applicable); for strings, consider a
- * validation routine.
+ * (i) decide on a type (bool, ternary, integer, real, enum, string), name,
+ * default value, upper and lower bounds (if applicable); for strings,
+ * consider a validation routine.
* (ii) add a record below (or use add_<type>_reloption).
* (iii) add it to the appropriate options struct (perhaps StdRdOptions)
* (iv) add it to the appropriate handling routine (perhaps
@@ -147,15 +147,6 @@ static relopt_bool boolRelOpts[] =
},
false
},
- {
- {
- "vacuum_truncate",
- "Enables vacuum to truncate empty pages at the end of this table",
- RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
- ShareUpdateExclusiveLock
- },
- true
- },
{
{
"deduplicate_items",
@@ -170,6 +161,25 @@ static relopt_bool boolRelOpts[] =
{{NULL}}
};
+static relopt_ternary ternaryRelOpts[] =
+{
+ {
+ {
+ "vacuum_truncate",
+ "Enables vacuum to truncate empty pages at the end of this table",
+ RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
+ ShareUpdateExclusiveLock
+ },
+ TERNARY_UNSET
+ },
+ /* list terminator */
+ {
+ {
+ NULL
+ }
+ }
+};
+
static relopt_int intRelOpts[] =
{
{
@@ -609,6 +619,13 @@ initialize_reloptions(void)
boolRelOpts[i].gen.lockmode));
j++;
}
+ for (i = 0; ternaryRelOpts[i].gen.name; i++)
+ {
+ Assert(DoLockModesConflict(ternaryRelOpts[i].gen.lockmode,
+ ternaryRelOpts[i].gen.lockmode));
+ j++;
+ }
+
for (i = 0; intRelOpts[i].gen.name; i++)
{
Assert(DoLockModesConflict(intRelOpts[i].gen.lockmode,
@@ -649,6 +666,14 @@ initialize_reloptions(void)
j++;
}
+ for (i = 0; ternaryRelOpts[i].gen.name; i++)
+ {
+ relOpts[j] = &ternaryRelOpts[i].gen;
+ relOpts[j]->type = RELOPT_TYPE_TERNARY;
+ relOpts[j]->namelen = strlen(relOpts[j]->name);
+ j++;
+ }
+
for (i = 0; intRelOpts[i].gen.name; i++)
{
relOpts[j] = &intRelOpts[i].gen;
@@ -809,6 +834,9 @@ allocate_reloption(bits32 kinds, int type, const char *name, const char *desc,
case RELOPT_TYPE_BOOL:
size = sizeof(relopt_bool);
break;
+ case RELOPT_TYPE_TERNARY:
+ size = sizeof(relopt_ternary);
+ break;
case RELOPT_TYPE_INT:
size = sizeof(relopt_int);
break;
@@ -892,6 +920,57 @@ add_local_bool_reloption(local_relopts *relopts, const char *name,
add_local_reloption(relopts, (relopt_gen *) newoption, offset);
}
+/*
+ * init_ternary_reloption
+ * Allocate and initialize a new ternary reloption
+ */
+static relopt_ternary *
+init_ternary_reloption(bits32 kinds, const char *name, const char *desc,
+ pg_ternary default_val, LOCKMODE lockmode)
+{
+ relopt_ternary *newoption;
+
+ newoption = (relopt_ternary *)
+ allocate_reloption(kinds, RELOPT_TYPE_TERNARY, name, desc, lockmode);
+ newoption->default_val = default_val;
+
+ return newoption;
+}
+
+/*
+ * add_ternary_reloption
+ * Add a new ternary reloption
+ */
+void
+add_ternary_reloption(bits32 kinds, const char *name, const char *desc,
+ pg_ternary default_val, LOCKMODE lockmode)
+{
+ relopt_ternary *newoption;
+
+ newoption =
+ init_ternary_reloption(kinds, name, desc, default_val, lockmode);
+
+ add_reloption((relopt_gen *) newoption);
+}
+
+/*
+ * add_local_ternary_reloption
+ * Add a new ternary local reloption
+ *
+ * 'offset' is offset of ternary-typed field.
+ */
+void
+add_local_ternary_reloption(local_relopts *relopts, const char *name,
+ const char *desc, pg_ternary default_val,
+ int offset)
+{
+ relopt_ternary *newoption;
+
+ newoption =
+ init_ternary_reloption(RELOPT_KIND_LOCAL, name, desc, default_val, 0);
+
+ add_local_reloption(relopts, (relopt_gen *) newoption, offset);
+}
/*
* init_real_reloption
@@ -1626,6 +1705,19 @@ parse_one_reloption(relopt_value *option, char *text_str, int text_len,
option->gen->name, value)));
}
break;
+ case RELOPT_TYPE_TERNARY:
+ {
+ bool b;
+
+ parsed = parse_bool(value, &b);
+ option->values.ternary_val = b ? TERNARY_TRUE : TERNARY_FALSE;
+ if (validate && !parsed)
+ ereport(ERROR,
+ (errcode(ERRCODE_INVALID_PARAMETER_VALUE),
+ errmsg("invalid value for ternary option \"%s\": %s",
+ option->gen->name, value)));
+ }
+ break;
case RELOPT_TYPE_INT:
{
relopt_int *optint = (relopt_int *) option->gen;
@@ -1789,17 +1881,6 @@ fillRelOptions(void *rdopts, Size basesize,
char *itempos = ((char *) rdopts) + elems[j].offset;
char *string_val;
- /*
- * If isset_offset is provided, store whether the reloption is
- * set there.
- */
- if (elems[j].isset_offset > 0)
- {
- char *setpos = ((char *) rdopts) + elems[j].isset_offset;
-
- *(bool *) setpos = options[i].isset;
- }
-
switch (options[i].gen->type)
{
case RELOPT_TYPE_BOOL:
@@ -1807,6 +1888,11 @@ fillRelOptions(void *rdopts, Size basesize,
options[i].values.bool_val :
((relopt_bool *) options[i].gen)->default_val;
break;
+ case RELOPT_TYPE_TERNARY:
+ *(pg_ternary *) itempos = options[i].isset ?
+ options[i].values.ternary_val :
+ ((relopt_ternary *) options[i].gen)->default_val;
+ break;
case RELOPT_TYPE_INT:
*(int *) itempos = options[i].isset ?
options[i].values.int_val :
@@ -1923,8 +2009,8 @@ default_reloptions(Datum reloptions, bool validate, relopt_kind kind)
offsetof(StdRdOptions, parallel_workers)},
{"vacuum_index_cleanup", RELOPT_TYPE_ENUM,
offsetof(StdRdOptions, vacuum_index_cleanup)},
- {"vacuum_truncate", RELOPT_TYPE_BOOL,
- offsetof(StdRdOptions, vacuum_truncate), offsetof(StdRdOptions, vacuum_truncate_set)},
+ {"vacuum_truncate", RELOPT_TYPE_TERNARY,
+ offsetof(StdRdOptions, vacuum_truncate)},
{"vacuum_max_eager_freeze_failure_rate", RELOPT_TYPE_REAL,
offsetof(StdRdOptions, vacuum_max_eager_freeze_failure_rate)}
};
@@ -2004,7 +2090,6 @@ build_local_reloptions(local_relopts *relopts, Datum options, bool validate)
elems[i].optname = opt->option->name;
elems[i].opttype = opt->option->type;
elems[i].offset = opt->offset;
- elems[i].isset_offset = 0; /* not supported for local relopts yet */
i++;
}
diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c
index aa4fbec143f..696eab9bd97 100644
--- a/src/backend/commands/vacuum.c
+++ b/src/backend/commands/vacuum.c
@@ -2224,9 +2224,9 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams params,
{
StdRdOptions *opts = (StdRdOptions *) rel->rd_options;
- if (opts && opts->vacuum_truncate_set)
+ if (opts && opts->vacuum_truncate != TERNARY_UNSET)
{
- if (opts->vacuum_truncate)
+ if (opts->vacuum_truncate == TERNARY_TRUE)
params.truncate = VACOPTVALUE_ENABLED;
else
params.truncate = VACOPTVALUE_DISABLED;
diff --git a/src/include/access/reloptions.h b/src/include/access/reloptions.h
index 2f08e1b0cf0..dfbef2babf2 100644
--- a/src/include/access/reloptions.h
+++ b/src/include/access/reloptions.h
@@ -29,6 +29,7 @@
typedef enum relopt_type
{
RELOPT_TYPE_BOOL,
+ RELOPT_TYPE_TERNARY, /* on, off, unset */
RELOPT_TYPE_INT,
RELOPT_TYPE_REAL,
RELOPT_TYPE_ENUM,
@@ -80,6 +81,7 @@ typedef struct relopt_value
union
{
bool bool_val;
+ pg_ternary ternary_val;
int int_val;
double real_val;
int enum_val;
@@ -94,6 +96,12 @@ typedef struct relopt_bool
bool default_val;
} relopt_bool;
+typedef struct relopt_ternary
+{
+ relopt_gen gen;
+ int default_val;
+} relopt_ternary;
+
typedef struct relopt_int
{
relopt_gen gen;
@@ -152,19 +160,6 @@ typedef struct
const char *optname; /* option's name */
relopt_type opttype; /* option's datatype */
int offset; /* offset of field in result struct */
-
- /*
- * isset_offset is an optional offset of a field in the result struct that
- * stores whether the option is explicitly set for the relation or if it
- * just picked up the default value. In most cases, this can be
- * accomplished by giving the reloption a special out-of-range default
- * value (e.g., some integer reloptions use -2), but this isn't always
- * possible. For example, a Boolean reloption cannot be given an
- * out-of-range default, so we need another way to discover the source of
- * its value. This offset is only used if given a value greater than
- * zero.
- */
- int isset_offset;
} relopt_parse_elt;
/* Local reloption definition */
@@ -195,6 +190,9 @@ typedef struct local_relopts
extern relopt_kind add_reloption_kind(void);
extern void add_bool_reloption(bits32 kinds, const char *name, const char *desc,
bool default_val, LOCKMODE lockmode);
+extern void add_ternary_reloption(bits32 kinds, const char *name,
+ const char *desc, pg_ternary default_val,
+ LOCKMODE lockmode);
extern void add_int_reloption(bits32 kinds, const char *name, const char *desc,
int default_val, int min_val, int max_val,
LOCKMODE lockmode);
@@ -214,6 +212,9 @@ extern void register_reloptions_validator(local_relopts *relopts,
extern void add_local_bool_reloption(local_relopts *relopts, const char *name,
const char *desc, bool default_val,
int offset);
+extern void add_local_ternary_reloption(local_relopts *relopts,
+ const char *name, const char *desc,
+ pg_ternary default_val, int offset);
extern void add_local_int_reloption(local_relopts *relopts, const char *name,
const char *desc, int default_val,
int min_val, int max_val, int offset);
diff --git a/src/include/postgres.h b/src/include/postgres.h
index 7d93fbce709..47c88780776 100644
--- a/src/include/postgres.h
+++ b/src/include/postgres.h
@@ -543,6 +543,21 @@ Float8GetDatum(float8 X)
* ----------------------------------------------------------------
*/
+/*
+ * pg_ternary
+ * Boolean value with an extra "unset" value
+ *
+ * This enum can be used for values that want to distinguish between true,
+ * false, and unset.
+*/
+
+typedef enum pg_ternary
+{
+ TERNARY_FALSE = 0,
+ TERNARY_TRUE = 1,
+ TERNARY_UNSET = -1
+} pg_ternary;
+
/*
* NON_EXEC_STATIC: It's sometimes useful to define a variable or function
* that is normally static but extern when using EXEC_BACKEND (see
diff --git a/src/include/utils/rel.h b/src/include/utils/rel.h
index d03ab247788..5feb18a5373 100644
--- a/src/include/utils/rel.h
+++ b/src/include/utils/rel.h
@@ -347,8 +347,7 @@ typedef struct StdRdOptions
bool user_catalog_table; /* use as an additional catalog relation */
int parallel_workers; /* max number of parallel workers */
StdRdOptIndexCleanup vacuum_index_cleanup; /* controls index vacuuming */
- bool vacuum_truncate; /* enables vacuum to truncate a relation */
- bool vacuum_truncate_set; /* whether vacuum_truncate is set */
+ pg_ternary vacuum_truncate; /* enables vacuum to truncate a relation */
/*
* Fraction of pages in a relation that vacuum can eagerly scan and fail
diff --git a/src/test/regress/expected/reloptions.out b/src/test/regress/expected/reloptions.out
index 9de19b4e3f1..1c99f79ab01 100644
--- a/src/test/regress/expected/reloptions.out
+++ b/src/test/regress/expected/reloptions.out
@@ -98,6 +98,42 @@ SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass;
{fillfactor=13,autovacuum_enabled=false}
(1 row)
+-- Tests for future (FIXME) ternary options
+-- behave as boolean option: accept unassigned name and truncated value
+DROP TABLE reloptions_test;
+CREATE TABLE reloptions_test(i INT) WITH (vacuum_truncate);
+SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass;
+ reloptions
+------------------------
+ {vacuum_truncate=true}
+(1 row)
+
+DROP TABLE reloptions_test;
+CREATE TABLE reloptions_test(i INT) WITH (vacuum_truncate=FaLS);
+SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass;
+ reloptions
+------------------------
+ {vacuum_truncate=fals}
+(1 row)
+
+-- preferred "true" alias is used when storing
+DROP TABLE reloptions_test;
+CREATE TABLE reloptions_test(i INT) WITH (vacuum_index_cleanup=on);
+SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass;
+ reloptions
+---------------------------
+ {vacuum_index_cleanup=on}
+(1 row)
+
+-- custom "third" value is available
+DROP TABLE reloptions_test;
+CREATE TABLE reloptions_test(i INT) WITH (vacuum_index_cleanup=auto);
+SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass;
+ reloptions
+-----------------------------
+ {vacuum_index_cleanup=auto}
+(1 row)
+
-- Test vacuum_truncate option
DROP TABLE reloptions_test;
CREATE TEMP TABLE reloptions_test(i INT NOT NULL, j text)
diff --git a/src/test/regress/sql/reloptions.sql b/src/test/regress/sql/reloptions.sql
index 24fbe0b478d..f5980dafcbc 100644
--- a/src/test/regress/sql/reloptions.sql
+++ b/src/test/regress/sql/reloptions.sql
@@ -59,6 +59,27 @@ UPDATE pg_class
ALTER TABLE reloptions_test RESET (illegal_option);
SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass;
+-- Tests for future (FIXME) ternary options
+
+-- behave as boolean option: accept unassigned name and truncated value
+DROP TABLE reloptions_test;
+CREATE TABLE reloptions_test(i INT) WITH (vacuum_truncate);
+SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass;
+
+DROP TABLE reloptions_test;
+CREATE TABLE reloptions_test(i INT) WITH (vacuum_truncate=FaLS);
+SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass;
+
+-- preferred "true" alias is used when storing
+DROP TABLE reloptions_test;
+CREATE TABLE reloptions_test(i INT) WITH (vacuum_index_cleanup=on);
+SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass;
+
+-- custom "third" value is available
+DROP TABLE reloptions_test;
+CREATE TABLE reloptions_test(i INT) WITH (vacuum_index_cleanup=auto);
+SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass;
+
-- Test vacuum_truncate option
DROP TABLE reloptions_test;
diff --git a/src/tools/pgindent/typedefs.list b/src/tools/pgindent/typedefs.list
index 3f3a888fd0e..1c8610fd46c 100644
--- a/src/tools/pgindent/typedefs.list
+++ b/src/tools/pgindent/typedefs.list
@@ -3952,6 +3952,7 @@ pg_sha512_ctx
pg_snapshot
pg_special_case
pg_stack_base_t
+pg_ternary
pg_time_t
pg_time_usec_t
pg_tz
@@ -4079,6 +4080,7 @@ relopt_kind
relopt_parse_elt
relopt_real
relopt_string
+relopt_ternary
relopt_type
relopt_value
relopts_validator
--
2.47.3
--c36t3fndxhkzbl5z--
^ permalink raw reply [nested|flat] 868+ messages in thread
* [PATCH v3] Introduce ternary reloptions
@ 2026-01-16 15:04 Álvaro Herrera <[email protected]>
0 siblings, 0 replies; 868+ messages in thread
From: Álvaro Herrera @ 2026-01-16 15:04 UTC (permalink / raw)
Introduce ternary reloption as a replacement for current
`vacuum_truncate` implementation. Remove the `vacuum_truncate_set`
separate flag and use TERNARY_UNSET instead.
This could also be used for other options such as `vacuum_index_cleanup`
and `buffering`, but lets get the scaffolding in first.
Discussion: https://postgr.es/m/3474141.usfYGdeWWP@thinkpad-pgpro
---
src/backend/access/common/reloptions.c | 137 ++++++++++++++++++-----
src/backend/commands/vacuum.c | 4 +-
src/include/access/reloptions.h | 27 ++---
src/include/postgres.h | 15 +++
src/include/utils/rel.h | 3 +-
src/test/regress/expected/reloptions.out | 36 ++++++
src/test/regress/sql/reloptions.sql | 21 ++++
src/tools/pgindent/typedefs.list | 2 +
8 files changed, 202 insertions(+), 43 deletions(-)
diff --git a/src/backend/access/common/reloptions.c b/src/backend/access/common/reloptions.c
index 0b83f98ed5f..6f1f577581a 100644
--- a/src/backend/access/common/reloptions.c
+++ b/src/backend/access/common/reloptions.c
@@ -40,9 +40,9 @@
*
* To add an option:
*
- * (i) decide on a type (bool, integer, real, enum, string), name, default
- * value, upper and lower bounds (if applicable); for strings, consider a
- * validation routine.
+ * (i) decide on a type (bool, ternary, integer, real, enum, string), name,
+ * default value, upper and lower bounds (if applicable); for strings,
+ * consider a validation routine.
* (ii) add a record below (or use add_<type>_reloption).
* (iii) add it to the appropriate options struct (perhaps StdRdOptions)
* (iv) add it to the appropriate handling routine (perhaps
@@ -147,15 +147,6 @@ static relopt_bool boolRelOpts[] =
},
false
},
- {
- {
- "vacuum_truncate",
- "Enables vacuum to truncate empty pages at the end of this table",
- RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
- ShareUpdateExclusiveLock
- },
- true
- },
{
{
"deduplicate_items",
@@ -170,6 +161,25 @@ static relopt_bool boolRelOpts[] =
{{NULL}}
};
+static relopt_ternary ternaryRelOpts[] =
+{
+ {
+ {
+ "vacuum_truncate",
+ "Enables vacuum to truncate empty pages at the end of this table",
+ RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
+ ShareUpdateExclusiveLock
+ },
+ TERNARY_UNSET
+ },
+ /* list terminator */
+ {
+ {
+ NULL
+ }
+ }
+};
+
static relopt_int intRelOpts[] =
{
{
@@ -609,6 +619,13 @@ initialize_reloptions(void)
boolRelOpts[i].gen.lockmode));
j++;
}
+ for (i = 0; ternaryRelOpts[i].gen.name; i++)
+ {
+ Assert(DoLockModesConflict(ternaryRelOpts[i].gen.lockmode,
+ ternaryRelOpts[i].gen.lockmode));
+ j++;
+ }
+
for (i = 0; intRelOpts[i].gen.name; i++)
{
Assert(DoLockModesConflict(intRelOpts[i].gen.lockmode,
@@ -649,6 +666,14 @@ initialize_reloptions(void)
j++;
}
+ for (i = 0; ternaryRelOpts[i].gen.name; i++)
+ {
+ relOpts[j] = &ternaryRelOpts[i].gen;
+ relOpts[j]->type = RELOPT_TYPE_TERNARY;
+ relOpts[j]->namelen = strlen(relOpts[j]->name);
+ j++;
+ }
+
for (i = 0; intRelOpts[i].gen.name; i++)
{
relOpts[j] = &intRelOpts[i].gen;
@@ -809,6 +834,9 @@ allocate_reloption(bits32 kinds, int type, const char *name, const char *desc,
case RELOPT_TYPE_BOOL:
size = sizeof(relopt_bool);
break;
+ case RELOPT_TYPE_TERNARY:
+ size = sizeof(relopt_ternary);
+ break;
case RELOPT_TYPE_INT:
size = sizeof(relopt_int);
break;
@@ -892,6 +920,57 @@ add_local_bool_reloption(local_relopts *relopts, const char *name,
add_local_reloption(relopts, (relopt_gen *) newoption, offset);
}
+/*
+ * init_ternary_reloption
+ * Allocate and initialize a new ternary reloption
+ */
+static relopt_ternary *
+init_ternary_reloption(bits32 kinds, const char *name, const char *desc,
+ pg_ternary default_val, LOCKMODE lockmode)
+{
+ relopt_ternary *newoption;
+
+ newoption = (relopt_ternary *)
+ allocate_reloption(kinds, RELOPT_TYPE_TERNARY, name, desc, lockmode);
+ newoption->default_val = default_val;
+
+ return newoption;
+}
+
+/*
+ * add_ternary_reloption
+ * Add a new ternary reloption
+ */
+void
+add_ternary_reloption(bits32 kinds, const char *name, const char *desc,
+ pg_ternary default_val, LOCKMODE lockmode)
+{
+ relopt_ternary *newoption;
+
+ newoption =
+ init_ternary_reloption(kinds, name, desc, default_val, lockmode);
+
+ add_reloption((relopt_gen *) newoption);
+}
+
+/*
+ * add_local_ternary_reloption
+ * Add a new ternary local reloption
+ *
+ * 'offset' is offset of ternary-typed field.
+ */
+void
+add_local_ternary_reloption(local_relopts *relopts, const char *name,
+ const char *desc, pg_ternary default_val,
+ int offset)
+{
+ relopt_ternary *newoption;
+
+ newoption =
+ init_ternary_reloption(RELOPT_KIND_LOCAL, name, desc, default_val, 0);
+
+ add_local_reloption(relopts, (relopt_gen *) newoption, offset);
+}
/*
* init_real_reloption
@@ -1626,6 +1705,19 @@ parse_one_reloption(relopt_value *option, char *text_str, int text_len,
option->gen->name, value)));
}
break;
+ case RELOPT_TYPE_TERNARY:
+ {
+ bool b;
+
+ parsed = parse_bool(value, &b);
+ option->values.ternary_val = b ? TERNARY_TRUE : TERNARY_FALSE;
+ if (validate && !parsed)
+ ereport(ERROR,
+ (errcode(ERRCODE_INVALID_PARAMETER_VALUE),
+ errmsg("invalid value for ternary option \"%s\": %s",
+ option->gen->name, value)));
+ }
+ break;
case RELOPT_TYPE_INT:
{
relopt_int *optint = (relopt_int *) option->gen;
@@ -1789,17 +1881,6 @@ fillRelOptions(void *rdopts, Size basesize,
char *itempos = ((char *) rdopts) + elems[j].offset;
char *string_val;
- /*
- * If isset_offset is provided, store whether the reloption is
- * set there.
- */
- if (elems[j].isset_offset > 0)
- {
- char *setpos = ((char *) rdopts) + elems[j].isset_offset;
-
- *(bool *) setpos = options[i].isset;
- }
-
switch (options[i].gen->type)
{
case RELOPT_TYPE_BOOL:
@@ -1807,6 +1888,11 @@ fillRelOptions(void *rdopts, Size basesize,
options[i].values.bool_val :
((relopt_bool *) options[i].gen)->default_val;
break;
+ case RELOPT_TYPE_TERNARY:
+ *(pg_ternary *) itempos = options[i].isset ?
+ options[i].values.ternary_val :
+ ((relopt_ternary *) options[i].gen)->default_val;
+ break;
case RELOPT_TYPE_INT:
*(int *) itempos = options[i].isset ?
options[i].values.int_val :
@@ -1923,8 +2009,8 @@ default_reloptions(Datum reloptions, bool validate, relopt_kind kind)
offsetof(StdRdOptions, parallel_workers)},
{"vacuum_index_cleanup", RELOPT_TYPE_ENUM,
offsetof(StdRdOptions, vacuum_index_cleanup)},
- {"vacuum_truncate", RELOPT_TYPE_BOOL,
- offsetof(StdRdOptions, vacuum_truncate), offsetof(StdRdOptions, vacuum_truncate_set)},
+ {"vacuum_truncate", RELOPT_TYPE_TERNARY,
+ offsetof(StdRdOptions, vacuum_truncate)},
{"vacuum_max_eager_freeze_failure_rate", RELOPT_TYPE_REAL,
offsetof(StdRdOptions, vacuum_max_eager_freeze_failure_rate)}
};
@@ -2004,7 +2090,6 @@ build_local_reloptions(local_relopts *relopts, Datum options, bool validate)
elems[i].optname = opt->option->name;
elems[i].opttype = opt->option->type;
elems[i].offset = opt->offset;
- elems[i].isset_offset = 0; /* not supported for local relopts yet */
i++;
}
diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c
index aa4fbec143f..696eab9bd97 100644
--- a/src/backend/commands/vacuum.c
+++ b/src/backend/commands/vacuum.c
@@ -2224,9 +2224,9 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams params,
{
StdRdOptions *opts = (StdRdOptions *) rel->rd_options;
- if (opts && opts->vacuum_truncate_set)
+ if (opts && opts->vacuum_truncate != TERNARY_UNSET)
{
- if (opts->vacuum_truncate)
+ if (opts->vacuum_truncate == TERNARY_TRUE)
params.truncate = VACOPTVALUE_ENABLED;
else
params.truncate = VACOPTVALUE_DISABLED;
diff --git a/src/include/access/reloptions.h b/src/include/access/reloptions.h
index 2f08e1b0cf0..dfbef2babf2 100644
--- a/src/include/access/reloptions.h
+++ b/src/include/access/reloptions.h
@@ -29,6 +29,7 @@
typedef enum relopt_type
{
RELOPT_TYPE_BOOL,
+ RELOPT_TYPE_TERNARY, /* on, off, unset */
RELOPT_TYPE_INT,
RELOPT_TYPE_REAL,
RELOPT_TYPE_ENUM,
@@ -80,6 +81,7 @@ typedef struct relopt_value
union
{
bool bool_val;
+ pg_ternary ternary_val;
int int_val;
double real_val;
int enum_val;
@@ -94,6 +96,12 @@ typedef struct relopt_bool
bool default_val;
} relopt_bool;
+typedef struct relopt_ternary
+{
+ relopt_gen gen;
+ int default_val;
+} relopt_ternary;
+
typedef struct relopt_int
{
relopt_gen gen;
@@ -152,19 +160,6 @@ typedef struct
const char *optname; /* option's name */
relopt_type opttype; /* option's datatype */
int offset; /* offset of field in result struct */
-
- /*
- * isset_offset is an optional offset of a field in the result struct that
- * stores whether the option is explicitly set for the relation or if it
- * just picked up the default value. In most cases, this can be
- * accomplished by giving the reloption a special out-of-range default
- * value (e.g., some integer reloptions use -2), but this isn't always
- * possible. For example, a Boolean reloption cannot be given an
- * out-of-range default, so we need another way to discover the source of
- * its value. This offset is only used if given a value greater than
- * zero.
- */
- int isset_offset;
} relopt_parse_elt;
/* Local reloption definition */
@@ -195,6 +190,9 @@ typedef struct local_relopts
extern relopt_kind add_reloption_kind(void);
extern void add_bool_reloption(bits32 kinds, const char *name, const char *desc,
bool default_val, LOCKMODE lockmode);
+extern void add_ternary_reloption(bits32 kinds, const char *name,
+ const char *desc, pg_ternary default_val,
+ LOCKMODE lockmode);
extern void add_int_reloption(bits32 kinds, const char *name, const char *desc,
int default_val, int min_val, int max_val,
LOCKMODE lockmode);
@@ -214,6 +212,9 @@ extern void register_reloptions_validator(local_relopts *relopts,
extern void add_local_bool_reloption(local_relopts *relopts, const char *name,
const char *desc, bool default_val,
int offset);
+extern void add_local_ternary_reloption(local_relopts *relopts,
+ const char *name, const char *desc,
+ pg_ternary default_val, int offset);
extern void add_local_int_reloption(local_relopts *relopts, const char *name,
const char *desc, int default_val,
int min_val, int max_val, int offset);
diff --git a/src/include/postgres.h b/src/include/postgres.h
index 7d93fbce709..47c88780776 100644
--- a/src/include/postgres.h
+++ b/src/include/postgres.h
@@ -543,6 +543,21 @@ Float8GetDatum(float8 X)
* ----------------------------------------------------------------
*/
+/*
+ * pg_ternary
+ * Boolean value with an extra "unset" value
+ *
+ * This enum can be used for values that want to distinguish between true,
+ * false, and unset.
+*/
+
+typedef enum pg_ternary
+{
+ TERNARY_FALSE = 0,
+ TERNARY_TRUE = 1,
+ TERNARY_UNSET = -1
+} pg_ternary;
+
/*
* NON_EXEC_STATIC: It's sometimes useful to define a variable or function
* that is normally static but extern when using EXEC_BACKEND (see
diff --git a/src/include/utils/rel.h b/src/include/utils/rel.h
index d03ab247788..5feb18a5373 100644
--- a/src/include/utils/rel.h
+++ b/src/include/utils/rel.h
@@ -347,8 +347,7 @@ typedef struct StdRdOptions
bool user_catalog_table; /* use as an additional catalog relation */
int parallel_workers; /* max number of parallel workers */
StdRdOptIndexCleanup vacuum_index_cleanup; /* controls index vacuuming */
- bool vacuum_truncate; /* enables vacuum to truncate a relation */
- bool vacuum_truncate_set; /* whether vacuum_truncate is set */
+ pg_ternary vacuum_truncate; /* enables vacuum to truncate a relation */
/*
* Fraction of pages in a relation that vacuum can eagerly scan and fail
diff --git a/src/test/regress/expected/reloptions.out b/src/test/regress/expected/reloptions.out
index 9de19b4e3f1..1c99f79ab01 100644
--- a/src/test/regress/expected/reloptions.out
+++ b/src/test/regress/expected/reloptions.out
@@ -98,6 +98,42 @@ SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass;
{fillfactor=13,autovacuum_enabled=false}
(1 row)
+-- Tests for future (FIXME) ternary options
+-- behave as boolean option: accept unassigned name and truncated value
+DROP TABLE reloptions_test;
+CREATE TABLE reloptions_test(i INT) WITH (vacuum_truncate);
+SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass;
+ reloptions
+------------------------
+ {vacuum_truncate=true}
+(1 row)
+
+DROP TABLE reloptions_test;
+CREATE TABLE reloptions_test(i INT) WITH (vacuum_truncate=FaLS);
+SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass;
+ reloptions
+------------------------
+ {vacuum_truncate=fals}
+(1 row)
+
+-- preferred "true" alias is used when storing
+DROP TABLE reloptions_test;
+CREATE TABLE reloptions_test(i INT) WITH (vacuum_index_cleanup=on);
+SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass;
+ reloptions
+---------------------------
+ {vacuum_index_cleanup=on}
+(1 row)
+
+-- custom "third" value is available
+DROP TABLE reloptions_test;
+CREATE TABLE reloptions_test(i INT) WITH (vacuum_index_cleanup=auto);
+SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass;
+ reloptions
+-----------------------------
+ {vacuum_index_cleanup=auto}
+(1 row)
+
-- Test vacuum_truncate option
DROP TABLE reloptions_test;
CREATE TEMP TABLE reloptions_test(i INT NOT NULL, j text)
diff --git a/src/test/regress/sql/reloptions.sql b/src/test/regress/sql/reloptions.sql
index 24fbe0b478d..f5980dafcbc 100644
--- a/src/test/regress/sql/reloptions.sql
+++ b/src/test/regress/sql/reloptions.sql
@@ -59,6 +59,27 @@ UPDATE pg_class
ALTER TABLE reloptions_test RESET (illegal_option);
SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass;
+-- Tests for future (FIXME) ternary options
+
+-- behave as boolean option: accept unassigned name and truncated value
+DROP TABLE reloptions_test;
+CREATE TABLE reloptions_test(i INT) WITH (vacuum_truncate);
+SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass;
+
+DROP TABLE reloptions_test;
+CREATE TABLE reloptions_test(i INT) WITH (vacuum_truncate=FaLS);
+SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass;
+
+-- preferred "true" alias is used when storing
+DROP TABLE reloptions_test;
+CREATE TABLE reloptions_test(i INT) WITH (vacuum_index_cleanup=on);
+SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass;
+
+-- custom "third" value is available
+DROP TABLE reloptions_test;
+CREATE TABLE reloptions_test(i INT) WITH (vacuum_index_cleanup=auto);
+SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass;
+
-- Test vacuum_truncate option
DROP TABLE reloptions_test;
diff --git a/src/tools/pgindent/typedefs.list b/src/tools/pgindent/typedefs.list
index 3f3a888fd0e..1c8610fd46c 100644
--- a/src/tools/pgindent/typedefs.list
+++ b/src/tools/pgindent/typedefs.list
@@ -3952,6 +3952,7 @@ pg_sha512_ctx
pg_snapshot
pg_special_case
pg_stack_base_t
+pg_ternary
pg_time_t
pg_time_usec_t
pg_tz
@@ -4079,6 +4080,7 @@ relopt_kind
relopt_parse_elt
relopt_real
relopt_string
+relopt_ternary
relopt_type
relopt_value
relopts_validator
--
2.47.3
--c36t3fndxhkzbl5z--
^ permalink raw reply [nested|flat] 868+ messages in thread
* [PATCH v3] Introduce ternary reloptions
@ 2026-01-16 15:04 Álvaro Herrera <[email protected]>
0 siblings, 0 replies; 868+ messages in thread
From: Álvaro Herrera @ 2026-01-16 15:04 UTC (permalink / raw)
Introduce ternary reloption as a replacement for current
`vacuum_truncate` implementation. Remove the `vacuum_truncate_set`
separate flag and use TERNARY_UNSET instead.
This could also be used for other options such as `vacuum_index_cleanup`
and `buffering`, but lets get the scaffolding in first.
Discussion: https://postgr.es/m/3474141.usfYGdeWWP@thinkpad-pgpro
---
src/backend/access/common/reloptions.c | 137 ++++++++++++++++++-----
src/backend/commands/vacuum.c | 4 +-
src/include/access/reloptions.h | 27 ++---
src/include/postgres.h | 15 +++
src/include/utils/rel.h | 3 +-
src/test/regress/expected/reloptions.out | 36 ++++++
src/test/regress/sql/reloptions.sql | 21 ++++
src/tools/pgindent/typedefs.list | 2 +
8 files changed, 202 insertions(+), 43 deletions(-)
diff --git a/src/backend/access/common/reloptions.c b/src/backend/access/common/reloptions.c
index 0b83f98ed5f..6f1f577581a 100644
--- a/src/backend/access/common/reloptions.c
+++ b/src/backend/access/common/reloptions.c
@@ -40,9 +40,9 @@
*
* To add an option:
*
- * (i) decide on a type (bool, integer, real, enum, string), name, default
- * value, upper and lower bounds (if applicable); for strings, consider a
- * validation routine.
+ * (i) decide on a type (bool, ternary, integer, real, enum, string), name,
+ * default value, upper and lower bounds (if applicable); for strings,
+ * consider a validation routine.
* (ii) add a record below (or use add_<type>_reloption).
* (iii) add it to the appropriate options struct (perhaps StdRdOptions)
* (iv) add it to the appropriate handling routine (perhaps
@@ -147,15 +147,6 @@ static relopt_bool boolRelOpts[] =
},
false
},
- {
- {
- "vacuum_truncate",
- "Enables vacuum to truncate empty pages at the end of this table",
- RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
- ShareUpdateExclusiveLock
- },
- true
- },
{
{
"deduplicate_items",
@@ -170,6 +161,25 @@ static relopt_bool boolRelOpts[] =
{{NULL}}
};
+static relopt_ternary ternaryRelOpts[] =
+{
+ {
+ {
+ "vacuum_truncate",
+ "Enables vacuum to truncate empty pages at the end of this table",
+ RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
+ ShareUpdateExclusiveLock
+ },
+ TERNARY_UNSET
+ },
+ /* list terminator */
+ {
+ {
+ NULL
+ }
+ }
+};
+
static relopt_int intRelOpts[] =
{
{
@@ -609,6 +619,13 @@ initialize_reloptions(void)
boolRelOpts[i].gen.lockmode));
j++;
}
+ for (i = 0; ternaryRelOpts[i].gen.name; i++)
+ {
+ Assert(DoLockModesConflict(ternaryRelOpts[i].gen.lockmode,
+ ternaryRelOpts[i].gen.lockmode));
+ j++;
+ }
+
for (i = 0; intRelOpts[i].gen.name; i++)
{
Assert(DoLockModesConflict(intRelOpts[i].gen.lockmode,
@@ -649,6 +666,14 @@ initialize_reloptions(void)
j++;
}
+ for (i = 0; ternaryRelOpts[i].gen.name; i++)
+ {
+ relOpts[j] = &ternaryRelOpts[i].gen;
+ relOpts[j]->type = RELOPT_TYPE_TERNARY;
+ relOpts[j]->namelen = strlen(relOpts[j]->name);
+ j++;
+ }
+
for (i = 0; intRelOpts[i].gen.name; i++)
{
relOpts[j] = &intRelOpts[i].gen;
@@ -809,6 +834,9 @@ allocate_reloption(bits32 kinds, int type, const char *name, const char *desc,
case RELOPT_TYPE_BOOL:
size = sizeof(relopt_bool);
break;
+ case RELOPT_TYPE_TERNARY:
+ size = sizeof(relopt_ternary);
+ break;
case RELOPT_TYPE_INT:
size = sizeof(relopt_int);
break;
@@ -892,6 +920,57 @@ add_local_bool_reloption(local_relopts *relopts, const char *name,
add_local_reloption(relopts, (relopt_gen *) newoption, offset);
}
+/*
+ * init_ternary_reloption
+ * Allocate and initialize a new ternary reloption
+ */
+static relopt_ternary *
+init_ternary_reloption(bits32 kinds, const char *name, const char *desc,
+ pg_ternary default_val, LOCKMODE lockmode)
+{
+ relopt_ternary *newoption;
+
+ newoption = (relopt_ternary *)
+ allocate_reloption(kinds, RELOPT_TYPE_TERNARY, name, desc, lockmode);
+ newoption->default_val = default_val;
+
+ return newoption;
+}
+
+/*
+ * add_ternary_reloption
+ * Add a new ternary reloption
+ */
+void
+add_ternary_reloption(bits32 kinds, const char *name, const char *desc,
+ pg_ternary default_val, LOCKMODE lockmode)
+{
+ relopt_ternary *newoption;
+
+ newoption =
+ init_ternary_reloption(kinds, name, desc, default_val, lockmode);
+
+ add_reloption((relopt_gen *) newoption);
+}
+
+/*
+ * add_local_ternary_reloption
+ * Add a new ternary local reloption
+ *
+ * 'offset' is offset of ternary-typed field.
+ */
+void
+add_local_ternary_reloption(local_relopts *relopts, const char *name,
+ const char *desc, pg_ternary default_val,
+ int offset)
+{
+ relopt_ternary *newoption;
+
+ newoption =
+ init_ternary_reloption(RELOPT_KIND_LOCAL, name, desc, default_val, 0);
+
+ add_local_reloption(relopts, (relopt_gen *) newoption, offset);
+}
/*
* init_real_reloption
@@ -1626,6 +1705,19 @@ parse_one_reloption(relopt_value *option, char *text_str, int text_len,
option->gen->name, value)));
}
break;
+ case RELOPT_TYPE_TERNARY:
+ {
+ bool b;
+
+ parsed = parse_bool(value, &b);
+ option->values.ternary_val = b ? TERNARY_TRUE : TERNARY_FALSE;
+ if (validate && !parsed)
+ ereport(ERROR,
+ (errcode(ERRCODE_INVALID_PARAMETER_VALUE),
+ errmsg("invalid value for ternary option \"%s\": %s",
+ option->gen->name, value)));
+ }
+ break;
case RELOPT_TYPE_INT:
{
relopt_int *optint = (relopt_int *) option->gen;
@@ -1789,17 +1881,6 @@ fillRelOptions(void *rdopts, Size basesize,
char *itempos = ((char *) rdopts) + elems[j].offset;
char *string_val;
- /*
- * If isset_offset is provided, store whether the reloption is
- * set there.
- */
- if (elems[j].isset_offset > 0)
- {
- char *setpos = ((char *) rdopts) + elems[j].isset_offset;
-
- *(bool *) setpos = options[i].isset;
- }
-
switch (options[i].gen->type)
{
case RELOPT_TYPE_BOOL:
@@ -1807,6 +1888,11 @@ fillRelOptions(void *rdopts, Size basesize,
options[i].values.bool_val :
((relopt_bool *) options[i].gen)->default_val;
break;
+ case RELOPT_TYPE_TERNARY:
+ *(pg_ternary *) itempos = options[i].isset ?
+ options[i].values.ternary_val :
+ ((relopt_ternary *) options[i].gen)->default_val;
+ break;
case RELOPT_TYPE_INT:
*(int *) itempos = options[i].isset ?
options[i].values.int_val :
@@ -1923,8 +2009,8 @@ default_reloptions(Datum reloptions, bool validate, relopt_kind kind)
offsetof(StdRdOptions, parallel_workers)},
{"vacuum_index_cleanup", RELOPT_TYPE_ENUM,
offsetof(StdRdOptions, vacuum_index_cleanup)},
- {"vacuum_truncate", RELOPT_TYPE_BOOL,
- offsetof(StdRdOptions, vacuum_truncate), offsetof(StdRdOptions, vacuum_truncate_set)},
+ {"vacuum_truncate", RELOPT_TYPE_TERNARY,
+ offsetof(StdRdOptions, vacuum_truncate)},
{"vacuum_max_eager_freeze_failure_rate", RELOPT_TYPE_REAL,
offsetof(StdRdOptions, vacuum_max_eager_freeze_failure_rate)}
};
@@ -2004,7 +2090,6 @@ build_local_reloptions(local_relopts *relopts, Datum options, bool validate)
elems[i].optname = opt->option->name;
elems[i].opttype = opt->option->type;
elems[i].offset = opt->offset;
- elems[i].isset_offset = 0; /* not supported for local relopts yet */
i++;
}
diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c
index aa4fbec143f..696eab9bd97 100644
--- a/src/backend/commands/vacuum.c
+++ b/src/backend/commands/vacuum.c
@@ -2224,9 +2224,9 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams params,
{
StdRdOptions *opts = (StdRdOptions *) rel->rd_options;
- if (opts && opts->vacuum_truncate_set)
+ if (opts && opts->vacuum_truncate != TERNARY_UNSET)
{
- if (opts->vacuum_truncate)
+ if (opts->vacuum_truncate == TERNARY_TRUE)
params.truncate = VACOPTVALUE_ENABLED;
else
params.truncate = VACOPTVALUE_DISABLED;
diff --git a/src/include/access/reloptions.h b/src/include/access/reloptions.h
index 2f08e1b0cf0..dfbef2babf2 100644
--- a/src/include/access/reloptions.h
+++ b/src/include/access/reloptions.h
@@ -29,6 +29,7 @@
typedef enum relopt_type
{
RELOPT_TYPE_BOOL,
+ RELOPT_TYPE_TERNARY, /* on, off, unset */
RELOPT_TYPE_INT,
RELOPT_TYPE_REAL,
RELOPT_TYPE_ENUM,
@@ -80,6 +81,7 @@ typedef struct relopt_value
union
{
bool bool_val;
+ pg_ternary ternary_val;
int int_val;
double real_val;
int enum_val;
@@ -94,6 +96,12 @@ typedef struct relopt_bool
bool default_val;
} relopt_bool;
+typedef struct relopt_ternary
+{
+ relopt_gen gen;
+ int default_val;
+} relopt_ternary;
+
typedef struct relopt_int
{
relopt_gen gen;
@@ -152,19 +160,6 @@ typedef struct
const char *optname; /* option's name */
relopt_type opttype; /* option's datatype */
int offset; /* offset of field in result struct */
-
- /*
- * isset_offset is an optional offset of a field in the result struct that
- * stores whether the option is explicitly set for the relation or if it
- * just picked up the default value. In most cases, this can be
- * accomplished by giving the reloption a special out-of-range default
- * value (e.g., some integer reloptions use -2), but this isn't always
- * possible. For example, a Boolean reloption cannot be given an
- * out-of-range default, so we need another way to discover the source of
- * its value. This offset is only used if given a value greater than
- * zero.
- */
- int isset_offset;
} relopt_parse_elt;
/* Local reloption definition */
@@ -195,6 +190,9 @@ typedef struct local_relopts
extern relopt_kind add_reloption_kind(void);
extern void add_bool_reloption(bits32 kinds, const char *name, const char *desc,
bool default_val, LOCKMODE lockmode);
+extern void add_ternary_reloption(bits32 kinds, const char *name,
+ const char *desc, pg_ternary default_val,
+ LOCKMODE lockmode);
extern void add_int_reloption(bits32 kinds, const char *name, const char *desc,
int default_val, int min_val, int max_val,
LOCKMODE lockmode);
@@ -214,6 +212,9 @@ extern void register_reloptions_validator(local_relopts *relopts,
extern void add_local_bool_reloption(local_relopts *relopts, const char *name,
const char *desc, bool default_val,
int offset);
+extern void add_local_ternary_reloption(local_relopts *relopts,
+ const char *name, const char *desc,
+ pg_ternary default_val, int offset);
extern void add_local_int_reloption(local_relopts *relopts, const char *name,
const char *desc, int default_val,
int min_val, int max_val, int offset);
diff --git a/src/include/postgres.h b/src/include/postgres.h
index 7d93fbce709..47c88780776 100644
--- a/src/include/postgres.h
+++ b/src/include/postgres.h
@@ -543,6 +543,21 @@ Float8GetDatum(float8 X)
* ----------------------------------------------------------------
*/
+/*
+ * pg_ternary
+ * Boolean value with an extra "unset" value
+ *
+ * This enum can be used for values that want to distinguish between true,
+ * false, and unset.
+*/
+
+typedef enum pg_ternary
+{
+ TERNARY_FALSE = 0,
+ TERNARY_TRUE = 1,
+ TERNARY_UNSET = -1
+} pg_ternary;
+
/*
* NON_EXEC_STATIC: It's sometimes useful to define a variable or function
* that is normally static but extern when using EXEC_BACKEND (see
diff --git a/src/include/utils/rel.h b/src/include/utils/rel.h
index d03ab247788..5feb18a5373 100644
--- a/src/include/utils/rel.h
+++ b/src/include/utils/rel.h
@@ -347,8 +347,7 @@ typedef struct StdRdOptions
bool user_catalog_table; /* use as an additional catalog relation */
int parallel_workers; /* max number of parallel workers */
StdRdOptIndexCleanup vacuum_index_cleanup; /* controls index vacuuming */
- bool vacuum_truncate; /* enables vacuum to truncate a relation */
- bool vacuum_truncate_set; /* whether vacuum_truncate is set */
+ pg_ternary vacuum_truncate; /* enables vacuum to truncate a relation */
/*
* Fraction of pages in a relation that vacuum can eagerly scan and fail
diff --git a/src/test/regress/expected/reloptions.out b/src/test/regress/expected/reloptions.out
index 9de19b4e3f1..1c99f79ab01 100644
--- a/src/test/regress/expected/reloptions.out
+++ b/src/test/regress/expected/reloptions.out
@@ -98,6 +98,42 @@ SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass;
{fillfactor=13,autovacuum_enabled=false}
(1 row)
+-- Tests for future (FIXME) ternary options
+-- behave as boolean option: accept unassigned name and truncated value
+DROP TABLE reloptions_test;
+CREATE TABLE reloptions_test(i INT) WITH (vacuum_truncate);
+SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass;
+ reloptions
+------------------------
+ {vacuum_truncate=true}
+(1 row)
+
+DROP TABLE reloptions_test;
+CREATE TABLE reloptions_test(i INT) WITH (vacuum_truncate=FaLS);
+SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass;
+ reloptions
+------------------------
+ {vacuum_truncate=fals}
+(1 row)
+
+-- preferred "true" alias is used when storing
+DROP TABLE reloptions_test;
+CREATE TABLE reloptions_test(i INT) WITH (vacuum_index_cleanup=on);
+SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass;
+ reloptions
+---------------------------
+ {vacuum_index_cleanup=on}
+(1 row)
+
+-- custom "third" value is available
+DROP TABLE reloptions_test;
+CREATE TABLE reloptions_test(i INT) WITH (vacuum_index_cleanup=auto);
+SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass;
+ reloptions
+-----------------------------
+ {vacuum_index_cleanup=auto}
+(1 row)
+
-- Test vacuum_truncate option
DROP TABLE reloptions_test;
CREATE TEMP TABLE reloptions_test(i INT NOT NULL, j text)
diff --git a/src/test/regress/sql/reloptions.sql b/src/test/regress/sql/reloptions.sql
index 24fbe0b478d..f5980dafcbc 100644
--- a/src/test/regress/sql/reloptions.sql
+++ b/src/test/regress/sql/reloptions.sql
@@ -59,6 +59,27 @@ UPDATE pg_class
ALTER TABLE reloptions_test RESET (illegal_option);
SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass;
+-- Tests for future (FIXME) ternary options
+
+-- behave as boolean option: accept unassigned name and truncated value
+DROP TABLE reloptions_test;
+CREATE TABLE reloptions_test(i INT) WITH (vacuum_truncate);
+SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass;
+
+DROP TABLE reloptions_test;
+CREATE TABLE reloptions_test(i INT) WITH (vacuum_truncate=FaLS);
+SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass;
+
+-- preferred "true" alias is used when storing
+DROP TABLE reloptions_test;
+CREATE TABLE reloptions_test(i INT) WITH (vacuum_index_cleanup=on);
+SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass;
+
+-- custom "third" value is available
+DROP TABLE reloptions_test;
+CREATE TABLE reloptions_test(i INT) WITH (vacuum_index_cleanup=auto);
+SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass;
+
-- Test vacuum_truncate option
DROP TABLE reloptions_test;
diff --git a/src/tools/pgindent/typedefs.list b/src/tools/pgindent/typedefs.list
index 3f3a888fd0e..1c8610fd46c 100644
--- a/src/tools/pgindent/typedefs.list
+++ b/src/tools/pgindent/typedefs.list
@@ -3952,6 +3952,7 @@ pg_sha512_ctx
pg_snapshot
pg_special_case
pg_stack_base_t
+pg_ternary
pg_time_t
pg_time_usec_t
pg_tz
@@ -4079,6 +4080,7 @@ relopt_kind
relopt_parse_elt
relopt_real
relopt_string
+relopt_ternary
relopt_type
relopt_value
relopts_validator
--
2.47.3
--c36t3fndxhkzbl5z--
^ permalink raw reply [nested|flat] 868+ messages in thread
* [PATCH v3] Introduce ternary reloptions
@ 2026-01-16 15:04 Álvaro Herrera <[email protected]>
0 siblings, 0 replies; 868+ messages in thread
From: Álvaro Herrera @ 2026-01-16 15:04 UTC (permalink / raw)
Introduce ternary reloption as a replacement for current
`vacuum_truncate` implementation. Remove the `vacuum_truncate_set`
separate flag and use TERNARY_UNSET instead.
This could also be used for other options such as `vacuum_index_cleanup`
and `buffering`, but lets get the scaffolding in first.
Discussion: https://postgr.es/m/3474141.usfYGdeWWP@thinkpad-pgpro
---
src/backend/access/common/reloptions.c | 137 ++++++++++++++++++-----
src/backend/commands/vacuum.c | 4 +-
src/include/access/reloptions.h | 27 ++---
src/include/postgres.h | 15 +++
src/include/utils/rel.h | 3 +-
src/test/regress/expected/reloptions.out | 36 ++++++
src/test/regress/sql/reloptions.sql | 21 ++++
src/tools/pgindent/typedefs.list | 2 +
8 files changed, 202 insertions(+), 43 deletions(-)
diff --git a/src/backend/access/common/reloptions.c b/src/backend/access/common/reloptions.c
index 0b83f98ed5f..6f1f577581a 100644
--- a/src/backend/access/common/reloptions.c
+++ b/src/backend/access/common/reloptions.c
@@ -40,9 +40,9 @@
*
* To add an option:
*
- * (i) decide on a type (bool, integer, real, enum, string), name, default
- * value, upper and lower bounds (if applicable); for strings, consider a
- * validation routine.
+ * (i) decide on a type (bool, ternary, integer, real, enum, string), name,
+ * default value, upper and lower bounds (if applicable); for strings,
+ * consider a validation routine.
* (ii) add a record below (or use add_<type>_reloption).
* (iii) add it to the appropriate options struct (perhaps StdRdOptions)
* (iv) add it to the appropriate handling routine (perhaps
@@ -147,15 +147,6 @@ static relopt_bool boolRelOpts[] =
},
false
},
- {
- {
- "vacuum_truncate",
- "Enables vacuum to truncate empty pages at the end of this table",
- RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
- ShareUpdateExclusiveLock
- },
- true
- },
{
{
"deduplicate_items",
@@ -170,6 +161,25 @@ static relopt_bool boolRelOpts[] =
{{NULL}}
};
+static relopt_ternary ternaryRelOpts[] =
+{
+ {
+ {
+ "vacuum_truncate",
+ "Enables vacuum to truncate empty pages at the end of this table",
+ RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
+ ShareUpdateExclusiveLock
+ },
+ TERNARY_UNSET
+ },
+ /* list terminator */
+ {
+ {
+ NULL
+ }
+ }
+};
+
static relopt_int intRelOpts[] =
{
{
@@ -609,6 +619,13 @@ initialize_reloptions(void)
boolRelOpts[i].gen.lockmode));
j++;
}
+ for (i = 0; ternaryRelOpts[i].gen.name; i++)
+ {
+ Assert(DoLockModesConflict(ternaryRelOpts[i].gen.lockmode,
+ ternaryRelOpts[i].gen.lockmode));
+ j++;
+ }
+
for (i = 0; intRelOpts[i].gen.name; i++)
{
Assert(DoLockModesConflict(intRelOpts[i].gen.lockmode,
@@ -649,6 +666,14 @@ initialize_reloptions(void)
j++;
}
+ for (i = 0; ternaryRelOpts[i].gen.name; i++)
+ {
+ relOpts[j] = &ternaryRelOpts[i].gen;
+ relOpts[j]->type = RELOPT_TYPE_TERNARY;
+ relOpts[j]->namelen = strlen(relOpts[j]->name);
+ j++;
+ }
+
for (i = 0; intRelOpts[i].gen.name; i++)
{
relOpts[j] = &intRelOpts[i].gen;
@@ -809,6 +834,9 @@ allocate_reloption(bits32 kinds, int type, const char *name, const char *desc,
case RELOPT_TYPE_BOOL:
size = sizeof(relopt_bool);
break;
+ case RELOPT_TYPE_TERNARY:
+ size = sizeof(relopt_ternary);
+ break;
case RELOPT_TYPE_INT:
size = sizeof(relopt_int);
break;
@@ -892,6 +920,57 @@ add_local_bool_reloption(local_relopts *relopts, const char *name,
add_local_reloption(relopts, (relopt_gen *) newoption, offset);
}
+/*
+ * init_ternary_reloption
+ * Allocate and initialize a new ternary reloption
+ */
+static relopt_ternary *
+init_ternary_reloption(bits32 kinds, const char *name, const char *desc,
+ pg_ternary default_val, LOCKMODE lockmode)
+{
+ relopt_ternary *newoption;
+
+ newoption = (relopt_ternary *)
+ allocate_reloption(kinds, RELOPT_TYPE_TERNARY, name, desc, lockmode);
+ newoption->default_val = default_val;
+
+ return newoption;
+}
+
+/*
+ * add_ternary_reloption
+ * Add a new ternary reloption
+ */
+void
+add_ternary_reloption(bits32 kinds, const char *name, const char *desc,
+ pg_ternary default_val, LOCKMODE lockmode)
+{
+ relopt_ternary *newoption;
+
+ newoption =
+ init_ternary_reloption(kinds, name, desc, default_val, lockmode);
+
+ add_reloption((relopt_gen *) newoption);
+}
+
+/*
+ * add_local_ternary_reloption
+ * Add a new ternary local reloption
+ *
+ * 'offset' is offset of ternary-typed field.
+ */
+void
+add_local_ternary_reloption(local_relopts *relopts, const char *name,
+ const char *desc, pg_ternary default_val,
+ int offset)
+{
+ relopt_ternary *newoption;
+
+ newoption =
+ init_ternary_reloption(RELOPT_KIND_LOCAL, name, desc, default_val, 0);
+
+ add_local_reloption(relopts, (relopt_gen *) newoption, offset);
+}
/*
* init_real_reloption
@@ -1626,6 +1705,19 @@ parse_one_reloption(relopt_value *option, char *text_str, int text_len,
option->gen->name, value)));
}
break;
+ case RELOPT_TYPE_TERNARY:
+ {
+ bool b;
+
+ parsed = parse_bool(value, &b);
+ option->values.ternary_val = b ? TERNARY_TRUE : TERNARY_FALSE;
+ if (validate && !parsed)
+ ereport(ERROR,
+ (errcode(ERRCODE_INVALID_PARAMETER_VALUE),
+ errmsg("invalid value for ternary option \"%s\": %s",
+ option->gen->name, value)));
+ }
+ break;
case RELOPT_TYPE_INT:
{
relopt_int *optint = (relopt_int *) option->gen;
@@ -1789,17 +1881,6 @@ fillRelOptions(void *rdopts, Size basesize,
char *itempos = ((char *) rdopts) + elems[j].offset;
char *string_val;
- /*
- * If isset_offset is provided, store whether the reloption is
- * set there.
- */
- if (elems[j].isset_offset > 0)
- {
- char *setpos = ((char *) rdopts) + elems[j].isset_offset;
-
- *(bool *) setpos = options[i].isset;
- }
-
switch (options[i].gen->type)
{
case RELOPT_TYPE_BOOL:
@@ -1807,6 +1888,11 @@ fillRelOptions(void *rdopts, Size basesize,
options[i].values.bool_val :
((relopt_bool *) options[i].gen)->default_val;
break;
+ case RELOPT_TYPE_TERNARY:
+ *(pg_ternary *) itempos = options[i].isset ?
+ options[i].values.ternary_val :
+ ((relopt_ternary *) options[i].gen)->default_val;
+ break;
case RELOPT_TYPE_INT:
*(int *) itempos = options[i].isset ?
options[i].values.int_val :
@@ -1923,8 +2009,8 @@ default_reloptions(Datum reloptions, bool validate, relopt_kind kind)
offsetof(StdRdOptions, parallel_workers)},
{"vacuum_index_cleanup", RELOPT_TYPE_ENUM,
offsetof(StdRdOptions, vacuum_index_cleanup)},
- {"vacuum_truncate", RELOPT_TYPE_BOOL,
- offsetof(StdRdOptions, vacuum_truncate), offsetof(StdRdOptions, vacuum_truncate_set)},
+ {"vacuum_truncate", RELOPT_TYPE_TERNARY,
+ offsetof(StdRdOptions, vacuum_truncate)},
{"vacuum_max_eager_freeze_failure_rate", RELOPT_TYPE_REAL,
offsetof(StdRdOptions, vacuum_max_eager_freeze_failure_rate)}
};
@@ -2004,7 +2090,6 @@ build_local_reloptions(local_relopts *relopts, Datum options, bool validate)
elems[i].optname = opt->option->name;
elems[i].opttype = opt->option->type;
elems[i].offset = opt->offset;
- elems[i].isset_offset = 0; /* not supported for local relopts yet */
i++;
}
diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c
index aa4fbec143f..696eab9bd97 100644
--- a/src/backend/commands/vacuum.c
+++ b/src/backend/commands/vacuum.c
@@ -2224,9 +2224,9 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams params,
{
StdRdOptions *opts = (StdRdOptions *) rel->rd_options;
- if (opts && opts->vacuum_truncate_set)
+ if (opts && opts->vacuum_truncate != TERNARY_UNSET)
{
- if (opts->vacuum_truncate)
+ if (opts->vacuum_truncate == TERNARY_TRUE)
params.truncate = VACOPTVALUE_ENABLED;
else
params.truncate = VACOPTVALUE_DISABLED;
diff --git a/src/include/access/reloptions.h b/src/include/access/reloptions.h
index 2f08e1b0cf0..dfbef2babf2 100644
--- a/src/include/access/reloptions.h
+++ b/src/include/access/reloptions.h
@@ -29,6 +29,7 @@
typedef enum relopt_type
{
RELOPT_TYPE_BOOL,
+ RELOPT_TYPE_TERNARY, /* on, off, unset */
RELOPT_TYPE_INT,
RELOPT_TYPE_REAL,
RELOPT_TYPE_ENUM,
@@ -80,6 +81,7 @@ typedef struct relopt_value
union
{
bool bool_val;
+ pg_ternary ternary_val;
int int_val;
double real_val;
int enum_val;
@@ -94,6 +96,12 @@ typedef struct relopt_bool
bool default_val;
} relopt_bool;
+typedef struct relopt_ternary
+{
+ relopt_gen gen;
+ int default_val;
+} relopt_ternary;
+
typedef struct relopt_int
{
relopt_gen gen;
@@ -152,19 +160,6 @@ typedef struct
const char *optname; /* option's name */
relopt_type opttype; /* option's datatype */
int offset; /* offset of field in result struct */
-
- /*
- * isset_offset is an optional offset of a field in the result struct that
- * stores whether the option is explicitly set for the relation or if it
- * just picked up the default value. In most cases, this can be
- * accomplished by giving the reloption a special out-of-range default
- * value (e.g., some integer reloptions use -2), but this isn't always
- * possible. For example, a Boolean reloption cannot be given an
- * out-of-range default, so we need another way to discover the source of
- * its value. This offset is only used if given a value greater than
- * zero.
- */
- int isset_offset;
} relopt_parse_elt;
/* Local reloption definition */
@@ -195,6 +190,9 @@ typedef struct local_relopts
extern relopt_kind add_reloption_kind(void);
extern void add_bool_reloption(bits32 kinds, const char *name, const char *desc,
bool default_val, LOCKMODE lockmode);
+extern void add_ternary_reloption(bits32 kinds, const char *name,
+ const char *desc, pg_ternary default_val,
+ LOCKMODE lockmode);
extern void add_int_reloption(bits32 kinds, const char *name, const char *desc,
int default_val, int min_val, int max_val,
LOCKMODE lockmode);
@@ -214,6 +212,9 @@ extern void register_reloptions_validator(local_relopts *relopts,
extern void add_local_bool_reloption(local_relopts *relopts, const char *name,
const char *desc, bool default_val,
int offset);
+extern void add_local_ternary_reloption(local_relopts *relopts,
+ const char *name, const char *desc,
+ pg_ternary default_val, int offset);
extern void add_local_int_reloption(local_relopts *relopts, const char *name,
const char *desc, int default_val,
int min_val, int max_val, int offset);
diff --git a/src/include/postgres.h b/src/include/postgres.h
index 7d93fbce709..47c88780776 100644
--- a/src/include/postgres.h
+++ b/src/include/postgres.h
@@ -543,6 +543,21 @@ Float8GetDatum(float8 X)
* ----------------------------------------------------------------
*/
+/*
+ * pg_ternary
+ * Boolean value with an extra "unset" value
+ *
+ * This enum can be used for values that want to distinguish between true,
+ * false, and unset.
+*/
+
+typedef enum pg_ternary
+{
+ TERNARY_FALSE = 0,
+ TERNARY_TRUE = 1,
+ TERNARY_UNSET = -1
+} pg_ternary;
+
/*
* NON_EXEC_STATIC: It's sometimes useful to define a variable or function
* that is normally static but extern when using EXEC_BACKEND (see
diff --git a/src/include/utils/rel.h b/src/include/utils/rel.h
index d03ab247788..5feb18a5373 100644
--- a/src/include/utils/rel.h
+++ b/src/include/utils/rel.h
@@ -347,8 +347,7 @@ typedef struct StdRdOptions
bool user_catalog_table; /* use as an additional catalog relation */
int parallel_workers; /* max number of parallel workers */
StdRdOptIndexCleanup vacuum_index_cleanup; /* controls index vacuuming */
- bool vacuum_truncate; /* enables vacuum to truncate a relation */
- bool vacuum_truncate_set; /* whether vacuum_truncate is set */
+ pg_ternary vacuum_truncate; /* enables vacuum to truncate a relation */
/*
* Fraction of pages in a relation that vacuum can eagerly scan and fail
diff --git a/src/test/regress/expected/reloptions.out b/src/test/regress/expected/reloptions.out
index 9de19b4e3f1..1c99f79ab01 100644
--- a/src/test/regress/expected/reloptions.out
+++ b/src/test/regress/expected/reloptions.out
@@ -98,6 +98,42 @@ SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass;
{fillfactor=13,autovacuum_enabled=false}
(1 row)
+-- Tests for future (FIXME) ternary options
+-- behave as boolean option: accept unassigned name and truncated value
+DROP TABLE reloptions_test;
+CREATE TABLE reloptions_test(i INT) WITH (vacuum_truncate);
+SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass;
+ reloptions
+------------------------
+ {vacuum_truncate=true}
+(1 row)
+
+DROP TABLE reloptions_test;
+CREATE TABLE reloptions_test(i INT) WITH (vacuum_truncate=FaLS);
+SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass;
+ reloptions
+------------------------
+ {vacuum_truncate=fals}
+(1 row)
+
+-- preferred "true" alias is used when storing
+DROP TABLE reloptions_test;
+CREATE TABLE reloptions_test(i INT) WITH (vacuum_index_cleanup=on);
+SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass;
+ reloptions
+---------------------------
+ {vacuum_index_cleanup=on}
+(1 row)
+
+-- custom "third" value is available
+DROP TABLE reloptions_test;
+CREATE TABLE reloptions_test(i INT) WITH (vacuum_index_cleanup=auto);
+SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass;
+ reloptions
+-----------------------------
+ {vacuum_index_cleanup=auto}
+(1 row)
+
-- Test vacuum_truncate option
DROP TABLE reloptions_test;
CREATE TEMP TABLE reloptions_test(i INT NOT NULL, j text)
diff --git a/src/test/regress/sql/reloptions.sql b/src/test/regress/sql/reloptions.sql
index 24fbe0b478d..f5980dafcbc 100644
--- a/src/test/regress/sql/reloptions.sql
+++ b/src/test/regress/sql/reloptions.sql
@@ -59,6 +59,27 @@ UPDATE pg_class
ALTER TABLE reloptions_test RESET (illegal_option);
SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass;
+-- Tests for future (FIXME) ternary options
+
+-- behave as boolean option: accept unassigned name and truncated value
+DROP TABLE reloptions_test;
+CREATE TABLE reloptions_test(i INT) WITH (vacuum_truncate);
+SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass;
+
+DROP TABLE reloptions_test;
+CREATE TABLE reloptions_test(i INT) WITH (vacuum_truncate=FaLS);
+SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass;
+
+-- preferred "true" alias is used when storing
+DROP TABLE reloptions_test;
+CREATE TABLE reloptions_test(i INT) WITH (vacuum_index_cleanup=on);
+SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass;
+
+-- custom "third" value is available
+DROP TABLE reloptions_test;
+CREATE TABLE reloptions_test(i INT) WITH (vacuum_index_cleanup=auto);
+SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass;
+
-- Test vacuum_truncate option
DROP TABLE reloptions_test;
diff --git a/src/tools/pgindent/typedefs.list b/src/tools/pgindent/typedefs.list
index 3f3a888fd0e..1c8610fd46c 100644
--- a/src/tools/pgindent/typedefs.list
+++ b/src/tools/pgindent/typedefs.list
@@ -3952,6 +3952,7 @@ pg_sha512_ctx
pg_snapshot
pg_special_case
pg_stack_base_t
+pg_ternary
pg_time_t
pg_time_usec_t
pg_tz
@@ -4079,6 +4080,7 @@ relopt_kind
relopt_parse_elt
relopt_real
relopt_string
+relopt_ternary
relopt_type
relopt_value
relopts_validator
--
2.47.3
--c36t3fndxhkzbl5z--
^ permalink raw reply [nested|flat] 868+ messages in thread
* [PATCH v3] Introduce ternary reloptions
@ 2026-01-16 15:04 Álvaro Herrera <[email protected]>
0 siblings, 0 replies; 868+ messages in thread
From: Álvaro Herrera @ 2026-01-16 15:04 UTC (permalink / raw)
Introduce ternary reloption as a replacement for current
`vacuum_truncate` implementation. Remove the `vacuum_truncate_set`
separate flag and use TERNARY_UNSET instead.
This could also be used for other options such as `vacuum_index_cleanup`
and `buffering`, but lets get the scaffolding in first.
Discussion: https://postgr.es/m/3474141.usfYGdeWWP@thinkpad-pgpro
---
src/backend/access/common/reloptions.c | 137 ++++++++++++++++++-----
src/backend/commands/vacuum.c | 4 +-
src/include/access/reloptions.h | 27 ++---
src/include/postgres.h | 15 +++
src/include/utils/rel.h | 3 +-
src/test/regress/expected/reloptions.out | 36 ++++++
src/test/regress/sql/reloptions.sql | 21 ++++
src/tools/pgindent/typedefs.list | 2 +
8 files changed, 202 insertions(+), 43 deletions(-)
diff --git a/src/backend/access/common/reloptions.c b/src/backend/access/common/reloptions.c
index 0b83f98ed5f..6f1f577581a 100644
--- a/src/backend/access/common/reloptions.c
+++ b/src/backend/access/common/reloptions.c
@@ -40,9 +40,9 @@
*
* To add an option:
*
- * (i) decide on a type (bool, integer, real, enum, string), name, default
- * value, upper and lower bounds (if applicable); for strings, consider a
- * validation routine.
+ * (i) decide on a type (bool, ternary, integer, real, enum, string), name,
+ * default value, upper and lower bounds (if applicable); for strings,
+ * consider a validation routine.
* (ii) add a record below (or use add_<type>_reloption).
* (iii) add it to the appropriate options struct (perhaps StdRdOptions)
* (iv) add it to the appropriate handling routine (perhaps
@@ -147,15 +147,6 @@ static relopt_bool boolRelOpts[] =
},
false
},
- {
- {
- "vacuum_truncate",
- "Enables vacuum to truncate empty pages at the end of this table",
- RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
- ShareUpdateExclusiveLock
- },
- true
- },
{
{
"deduplicate_items",
@@ -170,6 +161,25 @@ static relopt_bool boolRelOpts[] =
{{NULL}}
};
+static relopt_ternary ternaryRelOpts[] =
+{
+ {
+ {
+ "vacuum_truncate",
+ "Enables vacuum to truncate empty pages at the end of this table",
+ RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
+ ShareUpdateExclusiveLock
+ },
+ TERNARY_UNSET
+ },
+ /* list terminator */
+ {
+ {
+ NULL
+ }
+ }
+};
+
static relopt_int intRelOpts[] =
{
{
@@ -609,6 +619,13 @@ initialize_reloptions(void)
boolRelOpts[i].gen.lockmode));
j++;
}
+ for (i = 0; ternaryRelOpts[i].gen.name; i++)
+ {
+ Assert(DoLockModesConflict(ternaryRelOpts[i].gen.lockmode,
+ ternaryRelOpts[i].gen.lockmode));
+ j++;
+ }
+
for (i = 0; intRelOpts[i].gen.name; i++)
{
Assert(DoLockModesConflict(intRelOpts[i].gen.lockmode,
@@ -649,6 +666,14 @@ initialize_reloptions(void)
j++;
}
+ for (i = 0; ternaryRelOpts[i].gen.name; i++)
+ {
+ relOpts[j] = &ternaryRelOpts[i].gen;
+ relOpts[j]->type = RELOPT_TYPE_TERNARY;
+ relOpts[j]->namelen = strlen(relOpts[j]->name);
+ j++;
+ }
+
for (i = 0; intRelOpts[i].gen.name; i++)
{
relOpts[j] = &intRelOpts[i].gen;
@@ -809,6 +834,9 @@ allocate_reloption(bits32 kinds, int type, const char *name, const char *desc,
case RELOPT_TYPE_BOOL:
size = sizeof(relopt_bool);
break;
+ case RELOPT_TYPE_TERNARY:
+ size = sizeof(relopt_ternary);
+ break;
case RELOPT_TYPE_INT:
size = sizeof(relopt_int);
break;
@@ -892,6 +920,57 @@ add_local_bool_reloption(local_relopts *relopts, const char *name,
add_local_reloption(relopts, (relopt_gen *) newoption, offset);
}
+/*
+ * init_ternary_reloption
+ * Allocate and initialize a new ternary reloption
+ */
+static relopt_ternary *
+init_ternary_reloption(bits32 kinds, const char *name, const char *desc,
+ pg_ternary default_val, LOCKMODE lockmode)
+{
+ relopt_ternary *newoption;
+
+ newoption = (relopt_ternary *)
+ allocate_reloption(kinds, RELOPT_TYPE_TERNARY, name, desc, lockmode);
+ newoption->default_val = default_val;
+
+ return newoption;
+}
+
+/*
+ * add_ternary_reloption
+ * Add a new ternary reloption
+ */
+void
+add_ternary_reloption(bits32 kinds, const char *name, const char *desc,
+ pg_ternary default_val, LOCKMODE lockmode)
+{
+ relopt_ternary *newoption;
+
+ newoption =
+ init_ternary_reloption(kinds, name, desc, default_val, lockmode);
+
+ add_reloption((relopt_gen *) newoption);
+}
+
+/*
+ * add_local_ternary_reloption
+ * Add a new ternary local reloption
+ *
+ * 'offset' is offset of ternary-typed field.
+ */
+void
+add_local_ternary_reloption(local_relopts *relopts, const char *name,
+ const char *desc, pg_ternary default_val,
+ int offset)
+{
+ relopt_ternary *newoption;
+
+ newoption =
+ init_ternary_reloption(RELOPT_KIND_LOCAL, name, desc, default_val, 0);
+
+ add_local_reloption(relopts, (relopt_gen *) newoption, offset);
+}
/*
* init_real_reloption
@@ -1626,6 +1705,19 @@ parse_one_reloption(relopt_value *option, char *text_str, int text_len,
option->gen->name, value)));
}
break;
+ case RELOPT_TYPE_TERNARY:
+ {
+ bool b;
+
+ parsed = parse_bool(value, &b);
+ option->values.ternary_val = b ? TERNARY_TRUE : TERNARY_FALSE;
+ if (validate && !parsed)
+ ereport(ERROR,
+ (errcode(ERRCODE_INVALID_PARAMETER_VALUE),
+ errmsg("invalid value for ternary option \"%s\": %s",
+ option->gen->name, value)));
+ }
+ break;
case RELOPT_TYPE_INT:
{
relopt_int *optint = (relopt_int *) option->gen;
@@ -1789,17 +1881,6 @@ fillRelOptions(void *rdopts, Size basesize,
char *itempos = ((char *) rdopts) + elems[j].offset;
char *string_val;
- /*
- * If isset_offset is provided, store whether the reloption is
- * set there.
- */
- if (elems[j].isset_offset > 0)
- {
- char *setpos = ((char *) rdopts) + elems[j].isset_offset;
-
- *(bool *) setpos = options[i].isset;
- }
-
switch (options[i].gen->type)
{
case RELOPT_TYPE_BOOL:
@@ -1807,6 +1888,11 @@ fillRelOptions(void *rdopts, Size basesize,
options[i].values.bool_val :
((relopt_bool *) options[i].gen)->default_val;
break;
+ case RELOPT_TYPE_TERNARY:
+ *(pg_ternary *) itempos = options[i].isset ?
+ options[i].values.ternary_val :
+ ((relopt_ternary *) options[i].gen)->default_val;
+ break;
case RELOPT_TYPE_INT:
*(int *) itempos = options[i].isset ?
options[i].values.int_val :
@@ -1923,8 +2009,8 @@ default_reloptions(Datum reloptions, bool validate, relopt_kind kind)
offsetof(StdRdOptions, parallel_workers)},
{"vacuum_index_cleanup", RELOPT_TYPE_ENUM,
offsetof(StdRdOptions, vacuum_index_cleanup)},
- {"vacuum_truncate", RELOPT_TYPE_BOOL,
- offsetof(StdRdOptions, vacuum_truncate), offsetof(StdRdOptions, vacuum_truncate_set)},
+ {"vacuum_truncate", RELOPT_TYPE_TERNARY,
+ offsetof(StdRdOptions, vacuum_truncate)},
{"vacuum_max_eager_freeze_failure_rate", RELOPT_TYPE_REAL,
offsetof(StdRdOptions, vacuum_max_eager_freeze_failure_rate)}
};
@@ -2004,7 +2090,6 @@ build_local_reloptions(local_relopts *relopts, Datum options, bool validate)
elems[i].optname = opt->option->name;
elems[i].opttype = opt->option->type;
elems[i].offset = opt->offset;
- elems[i].isset_offset = 0; /* not supported for local relopts yet */
i++;
}
diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c
index aa4fbec143f..696eab9bd97 100644
--- a/src/backend/commands/vacuum.c
+++ b/src/backend/commands/vacuum.c
@@ -2224,9 +2224,9 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams params,
{
StdRdOptions *opts = (StdRdOptions *) rel->rd_options;
- if (opts && opts->vacuum_truncate_set)
+ if (opts && opts->vacuum_truncate != TERNARY_UNSET)
{
- if (opts->vacuum_truncate)
+ if (opts->vacuum_truncate == TERNARY_TRUE)
params.truncate = VACOPTVALUE_ENABLED;
else
params.truncate = VACOPTVALUE_DISABLED;
diff --git a/src/include/access/reloptions.h b/src/include/access/reloptions.h
index 2f08e1b0cf0..dfbef2babf2 100644
--- a/src/include/access/reloptions.h
+++ b/src/include/access/reloptions.h
@@ -29,6 +29,7 @@
typedef enum relopt_type
{
RELOPT_TYPE_BOOL,
+ RELOPT_TYPE_TERNARY, /* on, off, unset */
RELOPT_TYPE_INT,
RELOPT_TYPE_REAL,
RELOPT_TYPE_ENUM,
@@ -80,6 +81,7 @@ typedef struct relopt_value
union
{
bool bool_val;
+ pg_ternary ternary_val;
int int_val;
double real_val;
int enum_val;
@@ -94,6 +96,12 @@ typedef struct relopt_bool
bool default_val;
} relopt_bool;
+typedef struct relopt_ternary
+{
+ relopt_gen gen;
+ int default_val;
+} relopt_ternary;
+
typedef struct relopt_int
{
relopt_gen gen;
@@ -152,19 +160,6 @@ typedef struct
const char *optname; /* option's name */
relopt_type opttype; /* option's datatype */
int offset; /* offset of field in result struct */
-
- /*
- * isset_offset is an optional offset of a field in the result struct that
- * stores whether the option is explicitly set for the relation or if it
- * just picked up the default value. In most cases, this can be
- * accomplished by giving the reloption a special out-of-range default
- * value (e.g., some integer reloptions use -2), but this isn't always
- * possible. For example, a Boolean reloption cannot be given an
- * out-of-range default, so we need another way to discover the source of
- * its value. This offset is only used if given a value greater than
- * zero.
- */
- int isset_offset;
} relopt_parse_elt;
/* Local reloption definition */
@@ -195,6 +190,9 @@ typedef struct local_relopts
extern relopt_kind add_reloption_kind(void);
extern void add_bool_reloption(bits32 kinds, const char *name, const char *desc,
bool default_val, LOCKMODE lockmode);
+extern void add_ternary_reloption(bits32 kinds, const char *name,
+ const char *desc, pg_ternary default_val,
+ LOCKMODE lockmode);
extern void add_int_reloption(bits32 kinds, const char *name, const char *desc,
int default_val, int min_val, int max_val,
LOCKMODE lockmode);
@@ -214,6 +212,9 @@ extern void register_reloptions_validator(local_relopts *relopts,
extern void add_local_bool_reloption(local_relopts *relopts, const char *name,
const char *desc, bool default_val,
int offset);
+extern void add_local_ternary_reloption(local_relopts *relopts,
+ const char *name, const char *desc,
+ pg_ternary default_val, int offset);
extern void add_local_int_reloption(local_relopts *relopts, const char *name,
const char *desc, int default_val,
int min_val, int max_val, int offset);
diff --git a/src/include/postgres.h b/src/include/postgres.h
index 7d93fbce709..47c88780776 100644
--- a/src/include/postgres.h
+++ b/src/include/postgres.h
@@ -543,6 +543,21 @@ Float8GetDatum(float8 X)
* ----------------------------------------------------------------
*/
+/*
+ * pg_ternary
+ * Boolean value with an extra "unset" value
+ *
+ * This enum can be used for values that want to distinguish between true,
+ * false, and unset.
+*/
+
+typedef enum pg_ternary
+{
+ TERNARY_FALSE = 0,
+ TERNARY_TRUE = 1,
+ TERNARY_UNSET = -1
+} pg_ternary;
+
/*
* NON_EXEC_STATIC: It's sometimes useful to define a variable or function
* that is normally static but extern when using EXEC_BACKEND (see
diff --git a/src/include/utils/rel.h b/src/include/utils/rel.h
index d03ab247788..5feb18a5373 100644
--- a/src/include/utils/rel.h
+++ b/src/include/utils/rel.h
@@ -347,8 +347,7 @@ typedef struct StdRdOptions
bool user_catalog_table; /* use as an additional catalog relation */
int parallel_workers; /* max number of parallel workers */
StdRdOptIndexCleanup vacuum_index_cleanup; /* controls index vacuuming */
- bool vacuum_truncate; /* enables vacuum to truncate a relation */
- bool vacuum_truncate_set; /* whether vacuum_truncate is set */
+ pg_ternary vacuum_truncate; /* enables vacuum to truncate a relation */
/*
* Fraction of pages in a relation that vacuum can eagerly scan and fail
diff --git a/src/test/regress/expected/reloptions.out b/src/test/regress/expected/reloptions.out
index 9de19b4e3f1..1c99f79ab01 100644
--- a/src/test/regress/expected/reloptions.out
+++ b/src/test/regress/expected/reloptions.out
@@ -98,6 +98,42 @@ SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass;
{fillfactor=13,autovacuum_enabled=false}
(1 row)
+-- Tests for future (FIXME) ternary options
+-- behave as boolean option: accept unassigned name and truncated value
+DROP TABLE reloptions_test;
+CREATE TABLE reloptions_test(i INT) WITH (vacuum_truncate);
+SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass;
+ reloptions
+------------------------
+ {vacuum_truncate=true}
+(1 row)
+
+DROP TABLE reloptions_test;
+CREATE TABLE reloptions_test(i INT) WITH (vacuum_truncate=FaLS);
+SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass;
+ reloptions
+------------------------
+ {vacuum_truncate=fals}
+(1 row)
+
+-- preferred "true" alias is used when storing
+DROP TABLE reloptions_test;
+CREATE TABLE reloptions_test(i INT) WITH (vacuum_index_cleanup=on);
+SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass;
+ reloptions
+---------------------------
+ {vacuum_index_cleanup=on}
+(1 row)
+
+-- custom "third" value is available
+DROP TABLE reloptions_test;
+CREATE TABLE reloptions_test(i INT) WITH (vacuum_index_cleanup=auto);
+SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass;
+ reloptions
+-----------------------------
+ {vacuum_index_cleanup=auto}
+(1 row)
+
-- Test vacuum_truncate option
DROP TABLE reloptions_test;
CREATE TEMP TABLE reloptions_test(i INT NOT NULL, j text)
diff --git a/src/test/regress/sql/reloptions.sql b/src/test/regress/sql/reloptions.sql
index 24fbe0b478d..f5980dafcbc 100644
--- a/src/test/regress/sql/reloptions.sql
+++ b/src/test/regress/sql/reloptions.sql
@@ -59,6 +59,27 @@ UPDATE pg_class
ALTER TABLE reloptions_test RESET (illegal_option);
SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass;
+-- Tests for future (FIXME) ternary options
+
+-- behave as boolean option: accept unassigned name and truncated value
+DROP TABLE reloptions_test;
+CREATE TABLE reloptions_test(i INT) WITH (vacuum_truncate);
+SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass;
+
+DROP TABLE reloptions_test;
+CREATE TABLE reloptions_test(i INT) WITH (vacuum_truncate=FaLS);
+SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass;
+
+-- preferred "true" alias is used when storing
+DROP TABLE reloptions_test;
+CREATE TABLE reloptions_test(i INT) WITH (vacuum_index_cleanup=on);
+SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass;
+
+-- custom "third" value is available
+DROP TABLE reloptions_test;
+CREATE TABLE reloptions_test(i INT) WITH (vacuum_index_cleanup=auto);
+SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass;
+
-- Test vacuum_truncate option
DROP TABLE reloptions_test;
diff --git a/src/tools/pgindent/typedefs.list b/src/tools/pgindent/typedefs.list
index 3f3a888fd0e..1c8610fd46c 100644
--- a/src/tools/pgindent/typedefs.list
+++ b/src/tools/pgindent/typedefs.list
@@ -3952,6 +3952,7 @@ pg_sha512_ctx
pg_snapshot
pg_special_case
pg_stack_base_t
+pg_ternary
pg_time_t
pg_time_usec_t
pg_tz
@@ -4079,6 +4080,7 @@ relopt_kind
relopt_parse_elt
relopt_real
relopt_string
+relopt_ternary
relopt_type
relopt_value
relopts_validator
--
2.47.3
--c36t3fndxhkzbl5z--
^ permalink raw reply [nested|flat] 868+ messages in thread
* [PATCH v3] Introduce ternary reloptions
@ 2026-01-16 15:04 Álvaro Herrera <[email protected]>
0 siblings, 0 replies; 868+ messages in thread
From: Álvaro Herrera @ 2026-01-16 15:04 UTC (permalink / raw)
Introduce ternary reloption as a replacement for current
`vacuum_truncate` implementation. Remove the `vacuum_truncate_set`
separate flag and use TERNARY_UNSET instead.
This could also be used for other options such as `vacuum_index_cleanup`
and `buffering`, but lets get the scaffolding in first.
Discussion: https://postgr.es/m/3474141.usfYGdeWWP@thinkpad-pgpro
---
src/backend/access/common/reloptions.c | 137 ++++++++++++++++++-----
src/backend/commands/vacuum.c | 4 +-
src/include/access/reloptions.h | 27 ++---
src/include/postgres.h | 15 +++
src/include/utils/rel.h | 3 +-
src/test/regress/expected/reloptions.out | 36 ++++++
src/test/regress/sql/reloptions.sql | 21 ++++
src/tools/pgindent/typedefs.list | 2 +
8 files changed, 202 insertions(+), 43 deletions(-)
diff --git a/src/backend/access/common/reloptions.c b/src/backend/access/common/reloptions.c
index 0b83f98ed5f..6f1f577581a 100644
--- a/src/backend/access/common/reloptions.c
+++ b/src/backend/access/common/reloptions.c
@@ -40,9 +40,9 @@
*
* To add an option:
*
- * (i) decide on a type (bool, integer, real, enum, string), name, default
- * value, upper and lower bounds (if applicable); for strings, consider a
- * validation routine.
+ * (i) decide on a type (bool, ternary, integer, real, enum, string), name,
+ * default value, upper and lower bounds (if applicable); for strings,
+ * consider a validation routine.
* (ii) add a record below (or use add_<type>_reloption).
* (iii) add it to the appropriate options struct (perhaps StdRdOptions)
* (iv) add it to the appropriate handling routine (perhaps
@@ -147,15 +147,6 @@ static relopt_bool boolRelOpts[] =
},
false
},
- {
- {
- "vacuum_truncate",
- "Enables vacuum to truncate empty pages at the end of this table",
- RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
- ShareUpdateExclusiveLock
- },
- true
- },
{
{
"deduplicate_items",
@@ -170,6 +161,25 @@ static relopt_bool boolRelOpts[] =
{{NULL}}
};
+static relopt_ternary ternaryRelOpts[] =
+{
+ {
+ {
+ "vacuum_truncate",
+ "Enables vacuum to truncate empty pages at the end of this table",
+ RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
+ ShareUpdateExclusiveLock
+ },
+ TERNARY_UNSET
+ },
+ /* list terminator */
+ {
+ {
+ NULL
+ }
+ }
+};
+
static relopt_int intRelOpts[] =
{
{
@@ -609,6 +619,13 @@ initialize_reloptions(void)
boolRelOpts[i].gen.lockmode));
j++;
}
+ for (i = 0; ternaryRelOpts[i].gen.name; i++)
+ {
+ Assert(DoLockModesConflict(ternaryRelOpts[i].gen.lockmode,
+ ternaryRelOpts[i].gen.lockmode));
+ j++;
+ }
+
for (i = 0; intRelOpts[i].gen.name; i++)
{
Assert(DoLockModesConflict(intRelOpts[i].gen.lockmode,
@@ -649,6 +666,14 @@ initialize_reloptions(void)
j++;
}
+ for (i = 0; ternaryRelOpts[i].gen.name; i++)
+ {
+ relOpts[j] = &ternaryRelOpts[i].gen;
+ relOpts[j]->type = RELOPT_TYPE_TERNARY;
+ relOpts[j]->namelen = strlen(relOpts[j]->name);
+ j++;
+ }
+
for (i = 0; intRelOpts[i].gen.name; i++)
{
relOpts[j] = &intRelOpts[i].gen;
@@ -809,6 +834,9 @@ allocate_reloption(bits32 kinds, int type, const char *name, const char *desc,
case RELOPT_TYPE_BOOL:
size = sizeof(relopt_bool);
break;
+ case RELOPT_TYPE_TERNARY:
+ size = sizeof(relopt_ternary);
+ break;
case RELOPT_TYPE_INT:
size = sizeof(relopt_int);
break;
@@ -892,6 +920,57 @@ add_local_bool_reloption(local_relopts *relopts, const char *name,
add_local_reloption(relopts, (relopt_gen *) newoption, offset);
}
+/*
+ * init_ternary_reloption
+ * Allocate and initialize a new ternary reloption
+ */
+static relopt_ternary *
+init_ternary_reloption(bits32 kinds, const char *name, const char *desc,
+ pg_ternary default_val, LOCKMODE lockmode)
+{
+ relopt_ternary *newoption;
+
+ newoption = (relopt_ternary *)
+ allocate_reloption(kinds, RELOPT_TYPE_TERNARY, name, desc, lockmode);
+ newoption->default_val = default_val;
+
+ return newoption;
+}
+
+/*
+ * add_ternary_reloption
+ * Add a new ternary reloption
+ */
+void
+add_ternary_reloption(bits32 kinds, const char *name, const char *desc,
+ pg_ternary default_val, LOCKMODE lockmode)
+{
+ relopt_ternary *newoption;
+
+ newoption =
+ init_ternary_reloption(kinds, name, desc, default_val, lockmode);
+
+ add_reloption((relopt_gen *) newoption);
+}
+
+/*
+ * add_local_ternary_reloption
+ * Add a new ternary local reloption
+ *
+ * 'offset' is offset of ternary-typed field.
+ */
+void
+add_local_ternary_reloption(local_relopts *relopts, const char *name,
+ const char *desc, pg_ternary default_val,
+ int offset)
+{
+ relopt_ternary *newoption;
+
+ newoption =
+ init_ternary_reloption(RELOPT_KIND_LOCAL, name, desc, default_val, 0);
+
+ add_local_reloption(relopts, (relopt_gen *) newoption, offset);
+}
/*
* init_real_reloption
@@ -1626,6 +1705,19 @@ parse_one_reloption(relopt_value *option, char *text_str, int text_len,
option->gen->name, value)));
}
break;
+ case RELOPT_TYPE_TERNARY:
+ {
+ bool b;
+
+ parsed = parse_bool(value, &b);
+ option->values.ternary_val = b ? TERNARY_TRUE : TERNARY_FALSE;
+ if (validate && !parsed)
+ ereport(ERROR,
+ (errcode(ERRCODE_INVALID_PARAMETER_VALUE),
+ errmsg("invalid value for ternary option \"%s\": %s",
+ option->gen->name, value)));
+ }
+ break;
case RELOPT_TYPE_INT:
{
relopt_int *optint = (relopt_int *) option->gen;
@@ -1789,17 +1881,6 @@ fillRelOptions(void *rdopts, Size basesize,
char *itempos = ((char *) rdopts) + elems[j].offset;
char *string_val;
- /*
- * If isset_offset is provided, store whether the reloption is
- * set there.
- */
- if (elems[j].isset_offset > 0)
- {
- char *setpos = ((char *) rdopts) + elems[j].isset_offset;
-
- *(bool *) setpos = options[i].isset;
- }
-
switch (options[i].gen->type)
{
case RELOPT_TYPE_BOOL:
@@ -1807,6 +1888,11 @@ fillRelOptions(void *rdopts, Size basesize,
options[i].values.bool_val :
((relopt_bool *) options[i].gen)->default_val;
break;
+ case RELOPT_TYPE_TERNARY:
+ *(pg_ternary *) itempos = options[i].isset ?
+ options[i].values.ternary_val :
+ ((relopt_ternary *) options[i].gen)->default_val;
+ break;
case RELOPT_TYPE_INT:
*(int *) itempos = options[i].isset ?
options[i].values.int_val :
@@ -1923,8 +2009,8 @@ default_reloptions(Datum reloptions, bool validate, relopt_kind kind)
offsetof(StdRdOptions, parallel_workers)},
{"vacuum_index_cleanup", RELOPT_TYPE_ENUM,
offsetof(StdRdOptions, vacuum_index_cleanup)},
- {"vacuum_truncate", RELOPT_TYPE_BOOL,
- offsetof(StdRdOptions, vacuum_truncate), offsetof(StdRdOptions, vacuum_truncate_set)},
+ {"vacuum_truncate", RELOPT_TYPE_TERNARY,
+ offsetof(StdRdOptions, vacuum_truncate)},
{"vacuum_max_eager_freeze_failure_rate", RELOPT_TYPE_REAL,
offsetof(StdRdOptions, vacuum_max_eager_freeze_failure_rate)}
};
@@ -2004,7 +2090,6 @@ build_local_reloptions(local_relopts *relopts, Datum options, bool validate)
elems[i].optname = opt->option->name;
elems[i].opttype = opt->option->type;
elems[i].offset = opt->offset;
- elems[i].isset_offset = 0; /* not supported for local relopts yet */
i++;
}
diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c
index aa4fbec143f..696eab9bd97 100644
--- a/src/backend/commands/vacuum.c
+++ b/src/backend/commands/vacuum.c
@@ -2224,9 +2224,9 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams params,
{
StdRdOptions *opts = (StdRdOptions *) rel->rd_options;
- if (opts && opts->vacuum_truncate_set)
+ if (opts && opts->vacuum_truncate != TERNARY_UNSET)
{
- if (opts->vacuum_truncate)
+ if (opts->vacuum_truncate == TERNARY_TRUE)
params.truncate = VACOPTVALUE_ENABLED;
else
params.truncate = VACOPTVALUE_DISABLED;
diff --git a/src/include/access/reloptions.h b/src/include/access/reloptions.h
index 2f08e1b0cf0..dfbef2babf2 100644
--- a/src/include/access/reloptions.h
+++ b/src/include/access/reloptions.h
@@ -29,6 +29,7 @@
typedef enum relopt_type
{
RELOPT_TYPE_BOOL,
+ RELOPT_TYPE_TERNARY, /* on, off, unset */
RELOPT_TYPE_INT,
RELOPT_TYPE_REAL,
RELOPT_TYPE_ENUM,
@@ -80,6 +81,7 @@ typedef struct relopt_value
union
{
bool bool_val;
+ pg_ternary ternary_val;
int int_val;
double real_val;
int enum_val;
@@ -94,6 +96,12 @@ typedef struct relopt_bool
bool default_val;
} relopt_bool;
+typedef struct relopt_ternary
+{
+ relopt_gen gen;
+ int default_val;
+} relopt_ternary;
+
typedef struct relopt_int
{
relopt_gen gen;
@@ -152,19 +160,6 @@ typedef struct
const char *optname; /* option's name */
relopt_type opttype; /* option's datatype */
int offset; /* offset of field in result struct */
-
- /*
- * isset_offset is an optional offset of a field in the result struct that
- * stores whether the option is explicitly set for the relation or if it
- * just picked up the default value. In most cases, this can be
- * accomplished by giving the reloption a special out-of-range default
- * value (e.g., some integer reloptions use -2), but this isn't always
- * possible. For example, a Boolean reloption cannot be given an
- * out-of-range default, so we need another way to discover the source of
- * its value. This offset is only used if given a value greater than
- * zero.
- */
- int isset_offset;
} relopt_parse_elt;
/* Local reloption definition */
@@ -195,6 +190,9 @@ typedef struct local_relopts
extern relopt_kind add_reloption_kind(void);
extern void add_bool_reloption(bits32 kinds, const char *name, const char *desc,
bool default_val, LOCKMODE lockmode);
+extern void add_ternary_reloption(bits32 kinds, const char *name,
+ const char *desc, pg_ternary default_val,
+ LOCKMODE lockmode);
extern void add_int_reloption(bits32 kinds, const char *name, const char *desc,
int default_val, int min_val, int max_val,
LOCKMODE lockmode);
@@ -214,6 +212,9 @@ extern void register_reloptions_validator(local_relopts *relopts,
extern void add_local_bool_reloption(local_relopts *relopts, const char *name,
const char *desc, bool default_val,
int offset);
+extern void add_local_ternary_reloption(local_relopts *relopts,
+ const char *name, const char *desc,
+ pg_ternary default_val, int offset);
extern void add_local_int_reloption(local_relopts *relopts, const char *name,
const char *desc, int default_val,
int min_val, int max_val, int offset);
diff --git a/src/include/postgres.h b/src/include/postgres.h
index 7d93fbce709..47c88780776 100644
--- a/src/include/postgres.h
+++ b/src/include/postgres.h
@@ -543,6 +543,21 @@ Float8GetDatum(float8 X)
* ----------------------------------------------------------------
*/
+/*
+ * pg_ternary
+ * Boolean value with an extra "unset" value
+ *
+ * This enum can be used for values that want to distinguish between true,
+ * false, and unset.
+*/
+
+typedef enum pg_ternary
+{
+ TERNARY_FALSE = 0,
+ TERNARY_TRUE = 1,
+ TERNARY_UNSET = -1
+} pg_ternary;
+
/*
* NON_EXEC_STATIC: It's sometimes useful to define a variable or function
* that is normally static but extern when using EXEC_BACKEND (see
diff --git a/src/include/utils/rel.h b/src/include/utils/rel.h
index d03ab247788..5feb18a5373 100644
--- a/src/include/utils/rel.h
+++ b/src/include/utils/rel.h
@@ -347,8 +347,7 @@ typedef struct StdRdOptions
bool user_catalog_table; /* use as an additional catalog relation */
int parallel_workers; /* max number of parallel workers */
StdRdOptIndexCleanup vacuum_index_cleanup; /* controls index vacuuming */
- bool vacuum_truncate; /* enables vacuum to truncate a relation */
- bool vacuum_truncate_set; /* whether vacuum_truncate is set */
+ pg_ternary vacuum_truncate; /* enables vacuum to truncate a relation */
/*
* Fraction of pages in a relation that vacuum can eagerly scan and fail
diff --git a/src/test/regress/expected/reloptions.out b/src/test/regress/expected/reloptions.out
index 9de19b4e3f1..1c99f79ab01 100644
--- a/src/test/regress/expected/reloptions.out
+++ b/src/test/regress/expected/reloptions.out
@@ -98,6 +98,42 @@ SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass;
{fillfactor=13,autovacuum_enabled=false}
(1 row)
+-- Tests for future (FIXME) ternary options
+-- behave as boolean option: accept unassigned name and truncated value
+DROP TABLE reloptions_test;
+CREATE TABLE reloptions_test(i INT) WITH (vacuum_truncate);
+SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass;
+ reloptions
+------------------------
+ {vacuum_truncate=true}
+(1 row)
+
+DROP TABLE reloptions_test;
+CREATE TABLE reloptions_test(i INT) WITH (vacuum_truncate=FaLS);
+SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass;
+ reloptions
+------------------------
+ {vacuum_truncate=fals}
+(1 row)
+
+-- preferred "true" alias is used when storing
+DROP TABLE reloptions_test;
+CREATE TABLE reloptions_test(i INT) WITH (vacuum_index_cleanup=on);
+SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass;
+ reloptions
+---------------------------
+ {vacuum_index_cleanup=on}
+(1 row)
+
+-- custom "third" value is available
+DROP TABLE reloptions_test;
+CREATE TABLE reloptions_test(i INT) WITH (vacuum_index_cleanup=auto);
+SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass;
+ reloptions
+-----------------------------
+ {vacuum_index_cleanup=auto}
+(1 row)
+
-- Test vacuum_truncate option
DROP TABLE reloptions_test;
CREATE TEMP TABLE reloptions_test(i INT NOT NULL, j text)
diff --git a/src/test/regress/sql/reloptions.sql b/src/test/regress/sql/reloptions.sql
index 24fbe0b478d..f5980dafcbc 100644
--- a/src/test/regress/sql/reloptions.sql
+++ b/src/test/regress/sql/reloptions.sql
@@ -59,6 +59,27 @@ UPDATE pg_class
ALTER TABLE reloptions_test RESET (illegal_option);
SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass;
+-- Tests for future (FIXME) ternary options
+
+-- behave as boolean option: accept unassigned name and truncated value
+DROP TABLE reloptions_test;
+CREATE TABLE reloptions_test(i INT) WITH (vacuum_truncate);
+SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass;
+
+DROP TABLE reloptions_test;
+CREATE TABLE reloptions_test(i INT) WITH (vacuum_truncate=FaLS);
+SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass;
+
+-- preferred "true" alias is used when storing
+DROP TABLE reloptions_test;
+CREATE TABLE reloptions_test(i INT) WITH (vacuum_index_cleanup=on);
+SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass;
+
+-- custom "third" value is available
+DROP TABLE reloptions_test;
+CREATE TABLE reloptions_test(i INT) WITH (vacuum_index_cleanup=auto);
+SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass;
+
-- Test vacuum_truncate option
DROP TABLE reloptions_test;
diff --git a/src/tools/pgindent/typedefs.list b/src/tools/pgindent/typedefs.list
index 3f3a888fd0e..1c8610fd46c 100644
--- a/src/tools/pgindent/typedefs.list
+++ b/src/tools/pgindent/typedefs.list
@@ -3952,6 +3952,7 @@ pg_sha512_ctx
pg_snapshot
pg_special_case
pg_stack_base_t
+pg_ternary
pg_time_t
pg_time_usec_t
pg_tz
@@ -4079,6 +4080,7 @@ relopt_kind
relopt_parse_elt
relopt_real
relopt_string
+relopt_ternary
relopt_type
relopt_value
relopts_validator
--
2.47.3
--c36t3fndxhkzbl5z--
^ permalink raw reply [nested|flat] 868+ messages in thread
* [PATCH v3] Introduce ternary reloptions
@ 2026-01-16 15:04 Álvaro Herrera <[email protected]>
0 siblings, 0 replies; 868+ messages in thread
From: Álvaro Herrera @ 2026-01-16 15:04 UTC (permalink / raw)
Introduce ternary reloption as a replacement for current
`vacuum_truncate` implementation. Remove the `vacuum_truncate_set`
separate flag and use TERNARY_UNSET instead.
This could also be used for other options such as `vacuum_index_cleanup`
and `buffering`, but lets get the scaffolding in first.
Discussion: https://postgr.es/m/3474141.usfYGdeWWP@thinkpad-pgpro
---
src/backend/access/common/reloptions.c | 137 ++++++++++++++++++-----
src/backend/commands/vacuum.c | 4 +-
src/include/access/reloptions.h | 27 ++---
src/include/postgres.h | 15 +++
src/include/utils/rel.h | 3 +-
src/test/regress/expected/reloptions.out | 36 ++++++
src/test/regress/sql/reloptions.sql | 21 ++++
src/tools/pgindent/typedefs.list | 2 +
8 files changed, 202 insertions(+), 43 deletions(-)
diff --git a/src/backend/access/common/reloptions.c b/src/backend/access/common/reloptions.c
index 0b83f98ed5f..6f1f577581a 100644
--- a/src/backend/access/common/reloptions.c
+++ b/src/backend/access/common/reloptions.c
@@ -40,9 +40,9 @@
*
* To add an option:
*
- * (i) decide on a type (bool, integer, real, enum, string), name, default
- * value, upper and lower bounds (if applicable); for strings, consider a
- * validation routine.
+ * (i) decide on a type (bool, ternary, integer, real, enum, string), name,
+ * default value, upper and lower bounds (if applicable); for strings,
+ * consider a validation routine.
* (ii) add a record below (or use add_<type>_reloption).
* (iii) add it to the appropriate options struct (perhaps StdRdOptions)
* (iv) add it to the appropriate handling routine (perhaps
@@ -147,15 +147,6 @@ static relopt_bool boolRelOpts[] =
},
false
},
- {
- {
- "vacuum_truncate",
- "Enables vacuum to truncate empty pages at the end of this table",
- RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
- ShareUpdateExclusiveLock
- },
- true
- },
{
{
"deduplicate_items",
@@ -170,6 +161,25 @@ static relopt_bool boolRelOpts[] =
{{NULL}}
};
+static relopt_ternary ternaryRelOpts[] =
+{
+ {
+ {
+ "vacuum_truncate",
+ "Enables vacuum to truncate empty pages at the end of this table",
+ RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
+ ShareUpdateExclusiveLock
+ },
+ TERNARY_UNSET
+ },
+ /* list terminator */
+ {
+ {
+ NULL
+ }
+ }
+};
+
static relopt_int intRelOpts[] =
{
{
@@ -609,6 +619,13 @@ initialize_reloptions(void)
boolRelOpts[i].gen.lockmode));
j++;
}
+ for (i = 0; ternaryRelOpts[i].gen.name; i++)
+ {
+ Assert(DoLockModesConflict(ternaryRelOpts[i].gen.lockmode,
+ ternaryRelOpts[i].gen.lockmode));
+ j++;
+ }
+
for (i = 0; intRelOpts[i].gen.name; i++)
{
Assert(DoLockModesConflict(intRelOpts[i].gen.lockmode,
@@ -649,6 +666,14 @@ initialize_reloptions(void)
j++;
}
+ for (i = 0; ternaryRelOpts[i].gen.name; i++)
+ {
+ relOpts[j] = &ternaryRelOpts[i].gen;
+ relOpts[j]->type = RELOPT_TYPE_TERNARY;
+ relOpts[j]->namelen = strlen(relOpts[j]->name);
+ j++;
+ }
+
for (i = 0; intRelOpts[i].gen.name; i++)
{
relOpts[j] = &intRelOpts[i].gen;
@@ -809,6 +834,9 @@ allocate_reloption(bits32 kinds, int type, const char *name, const char *desc,
case RELOPT_TYPE_BOOL:
size = sizeof(relopt_bool);
break;
+ case RELOPT_TYPE_TERNARY:
+ size = sizeof(relopt_ternary);
+ break;
case RELOPT_TYPE_INT:
size = sizeof(relopt_int);
break;
@@ -892,6 +920,57 @@ add_local_bool_reloption(local_relopts *relopts, const char *name,
add_local_reloption(relopts, (relopt_gen *) newoption, offset);
}
+/*
+ * init_ternary_reloption
+ * Allocate and initialize a new ternary reloption
+ */
+static relopt_ternary *
+init_ternary_reloption(bits32 kinds, const char *name, const char *desc,
+ pg_ternary default_val, LOCKMODE lockmode)
+{
+ relopt_ternary *newoption;
+
+ newoption = (relopt_ternary *)
+ allocate_reloption(kinds, RELOPT_TYPE_TERNARY, name, desc, lockmode);
+ newoption->default_val = default_val;
+
+ return newoption;
+}
+
+/*
+ * add_ternary_reloption
+ * Add a new ternary reloption
+ */
+void
+add_ternary_reloption(bits32 kinds, const char *name, const char *desc,
+ pg_ternary default_val, LOCKMODE lockmode)
+{
+ relopt_ternary *newoption;
+
+ newoption =
+ init_ternary_reloption(kinds, name, desc, default_val, lockmode);
+
+ add_reloption((relopt_gen *) newoption);
+}
+
+/*
+ * add_local_ternary_reloption
+ * Add a new ternary local reloption
+ *
+ * 'offset' is offset of ternary-typed field.
+ */
+void
+add_local_ternary_reloption(local_relopts *relopts, const char *name,
+ const char *desc, pg_ternary default_val,
+ int offset)
+{
+ relopt_ternary *newoption;
+
+ newoption =
+ init_ternary_reloption(RELOPT_KIND_LOCAL, name, desc, default_val, 0);
+
+ add_local_reloption(relopts, (relopt_gen *) newoption, offset);
+}
/*
* init_real_reloption
@@ -1626,6 +1705,19 @@ parse_one_reloption(relopt_value *option, char *text_str, int text_len,
option->gen->name, value)));
}
break;
+ case RELOPT_TYPE_TERNARY:
+ {
+ bool b;
+
+ parsed = parse_bool(value, &b);
+ option->values.ternary_val = b ? TERNARY_TRUE : TERNARY_FALSE;
+ if (validate && !parsed)
+ ereport(ERROR,
+ (errcode(ERRCODE_INVALID_PARAMETER_VALUE),
+ errmsg("invalid value for ternary option \"%s\": %s",
+ option->gen->name, value)));
+ }
+ break;
case RELOPT_TYPE_INT:
{
relopt_int *optint = (relopt_int *) option->gen;
@@ -1789,17 +1881,6 @@ fillRelOptions(void *rdopts, Size basesize,
char *itempos = ((char *) rdopts) + elems[j].offset;
char *string_val;
- /*
- * If isset_offset is provided, store whether the reloption is
- * set there.
- */
- if (elems[j].isset_offset > 0)
- {
- char *setpos = ((char *) rdopts) + elems[j].isset_offset;
-
- *(bool *) setpos = options[i].isset;
- }
-
switch (options[i].gen->type)
{
case RELOPT_TYPE_BOOL:
@@ -1807,6 +1888,11 @@ fillRelOptions(void *rdopts, Size basesize,
options[i].values.bool_val :
((relopt_bool *) options[i].gen)->default_val;
break;
+ case RELOPT_TYPE_TERNARY:
+ *(pg_ternary *) itempos = options[i].isset ?
+ options[i].values.ternary_val :
+ ((relopt_ternary *) options[i].gen)->default_val;
+ break;
case RELOPT_TYPE_INT:
*(int *) itempos = options[i].isset ?
options[i].values.int_val :
@@ -1923,8 +2009,8 @@ default_reloptions(Datum reloptions, bool validate, relopt_kind kind)
offsetof(StdRdOptions, parallel_workers)},
{"vacuum_index_cleanup", RELOPT_TYPE_ENUM,
offsetof(StdRdOptions, vacuum_index_cleanup)},
- {"vacuum_truncate", RELOPT_TYPE_BOOL,
- offsetof(StdRdOptions, vacuum_truncate), offsetof(StdRdOptions, vacuum_truncate_set)},
+ {"vacuum_truncate", RELOPT_TYPE_TERNARY,
+ offsetof(StdRdOptions, vacuum_truncate)},
{"vacuum_max_eager_freeze_failure_rate", RELOPT_TYPE_REAL,
offsetof(StdRdOptions, vacuum_max_eager_freeze_failure_rate)}
};
@@ -2004,7 +2090,6 @@ build_local_reloptions(local_relopts *relopts, Datum options, bool validate)
elems[i].optname = opt->option->name;
elems[i].opttype = opt->option->type;
elems[i].offset = opt->offset;
- elems[i].isset_offset = 0; /* not supported for local relopts yet */
i++;
}
diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c
index aa4fbec143f..696eab9bd97 100644
--- a/src/backend/commands/vacuum.c
+++ b/src/backend/commands/vacuum.c
@@ -2224,9 +2224,9 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams params,
{
StdRdOptions *opts = (StdRdOptions *) rel->rd_options;
- if (opts && opts->vacuum_truncate_set)
+ if (opts && opts->vacuum_truncate != TERNARY_UNSET)
{
- if (opts->vacuum_truncate)
+ if (opts->vacuum_truncate == TERNARY_TRUE)
params.truncate = VACOPTVALUE_ENABLED;
else
params.truncate = VACOPTVALUE_DISABLED;
diff --git a/src/include/access/reloptions.h b/src/include/access/reloptions.h
index 2f08e1b0cf0..dfbef2babf2 100644
--- a/src/include/access/reloptions.h
+++ b/src/include/access/reloptions.h
@@ -29,6 +29,7 @@
typedef enum relopt_type
{
RELOPT_TYPE_BOOL,
+ RELOPT_TYPE_TERNARY, /* on, off, unset */
RELOPT_TYPE_INT,
RELOPT_TYPE_REAL,
RELOPT_TYPE_ENUM,
@@ -80,6 +81,7 @@ typedef struct relopt_value
union
{
bool bool_val;
+ pg_ternary ternary_val;
int int_val;
double real_val;
int enum_val;
@@ -94,6 +96,12 @@ typedef struct relopt_bool
bool default_val;
} relopt_bool;
+typedef struct relopt_ternary
+{
+ relopt_gen gen;
+ int default_val;
+} relopt_ternary;
+
typedef struct relopt_int
{
relopt_gen gen;
@@ -152,19 +160,6 @@ typedef struct
const char *optname; /* option's name */
relopt_type opttype; /* option's datatype */
int offset; /* offset of field in result struct */
-
- /*
- * isset_offset is an optional offset of a field in the result struct that
- * stores whether the option is explicitly set for the relation or if it
- * just picked up the default value. In most cases, this can be
- * accomplished by giving the reloption a special out-of-range default
- * value (e.g., some integer reloptions use -2), but this isn't always
- * possible. For example, a Boolean reloption cannot be given an
- * out-of-range default, so we need another way to discover the source of
- * its value. This offset is only used if given a value greater than
- * zero.
- */
- int isset_offset;
} relopt_parse_elt;
/* Local reloption definition */
@@ -195,6 +190,9 @@ typedef struct local_relopts
extern relopt_kind add_reloption_kind(void);
extern void add_bool_reloption(bits32 kinds, const char *name, const char *desc,
bool default_val, LOCKMODE lockmode);
+extern void add_ternary_reloption(bits32 kinds, const char *name,
+ const char *desc, pg_ternary default_val,
+ LOCKMODE lockmode);
extern void add_int_reloption(bits32 kinds, const char *name, const char *desc,
int default_val, int min_val, int max_val,
LOCKMODE lockmode);
@@ -214,6 +212,9 @@ extern void register_reloptions_validator(local_relopts *relopts,
extern void add_local_bool_reloption(local_relopts *relopts, const char *name,
const char *desc, bool default_val,
int offset);
+extern void add_local_ternary_reloption(local_relopts *relopts,
+ const char *name, const char *desc,
+ pg_ternary default_val, int offset);
extern void add_local_int_reloption(local_relopts *relopts, const char *name,
const char *desc, int default_val,
int min_val, int max_val, int offset);
diff --git a/src/include/postgres.h b/src/include/postgres.h
index 7d93fbce709..47c88780776 100644
--- a/src/include/postgres.h
+++ b/src/include/postgres.h
@@ -543,6 +543,21 @@ Float8GetDatum(float8 X)
* ----------------------------------------------------------------
*/
+/*
+ * pg_ternary
+ * Boolean value with an extra "unset" value
+ *
+ * This enum can be used for values that want to distinguish between true,
+ * false, and unset.
+*/
+
+typedef enum pg_ternary
+{
+ TERNARY_FALSE = 0,
+ TERNARY_TRUE = 1,
+ TERNARY_UNSET = -1
+} pg_ternary;
+
/*
* NON_EXEC_STATIC: It's sometimes useful to define a variable or function
* that is normally static but extern when using EXEC_BACKEND (see
diff --git a/src/include/utils/rel.h b/src/include/utils/rel.h
index d03ab247788..5feb18a5373 100644
--- a/src/include/utils/rel.h
+++ b/src/include/utils/rel.h
@@ -347,8 +347,7 @@ typedef struct StdRdOptions
bool user_catalog_table; /* use as an additional catalog relation */
int parallel_workers; /* max number of parallel workers */
StdRdOptIndexCleanup vacuum_index_cleanup; /* controls index vacuuming */
- bool vacuum_truncate; /* enables vacuum to truncate a relation */
- bool vacuum_truncate_set; /* whether vacuum_truncate is set */
+ pg_ternary vacuum_truncate; /* enables vacuum to truncate a relation */
/*
* Fraction of pages in a relation that vacuum can eagerly scan and fail
diff --git a/src/test/regress/expected/reloptions.out b/src/test/regress/expected/reloptions.out
index 9de19b4e3f1..1c99f79ab01 100644
--- a/src/test/regress/expected/reloptions.out
+++ b/src/test/regress/expected/reloptions.out
@@ -98,6 +98,42 @@ SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass;
{fillfactor=13,autovacuum_enabled=false}
(1 row)
+-- Tests for future (FIXME) ternary options
+-- behave as boolean option: accept unassigned name and truncated value
+DROP TABLE reloptions_test;
+CREATE TABLE reloptions_test(i INT) WITH (vacuum_truncate);
+SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass;
+ reloptions
+------------------------
+ {vacuum_truncate=true}
+(1 row)
+
+DROP TABLE reloptions_test;
+CREATE TABLE reloptions_test(i INT) WITH (vacuum_truncate=FaLS);
+SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass;
+ reloptions
+------------------------
+ {vacuum_truncate=fals}
+(1 row)
+
+-- preferred "true" alias is used when storing
+DROP TABLE reloptions_test;
+CREATE TABLE reloptions_test(i INT) WITH (vacuum_index_cleanup=on);
+SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass;
+ reloptions
+---------------------------
+ {vacuum_index_cleanup=on}
+(1 row)
+
+-- custom "third" value is available
+DROP TABLE reloptions_test;
+CREATE TABLE reloptions_test(i INT) WITH (vacuum_index_cleanup=auto);
+SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass;
+ reloptions
+-----------------------------
+ {vacuum_index_cleanup=auto}
+(1 row)
+
-- Test vacuum_truncate option
DROP TABLE reloptions_test;
CREATE TEMP TABLE reloptions_test(i INT NOT NULL, j text)
diff --git a/src/test/regress/sql/reloptions.sql b/src/test/regress/sql/reloptions.sql
index 24fbe0b478d..f5980dafcbc 100644
--- a/src/test/regress/sql/reloptions.sql
+++ b/src/test/regress/sql/reloptions.sql
@@ -59,6 +59,27 @@ UPDATE pg_class
ALTER TABLE reloptions_test RESET (illegal_option);
SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass;
+-- Tests for future (FIXME) ternary options
+
+-- behave as boolean option: accept unassigned name and truncated value
+DROP TABLE reloptions_test;
+CREATE TABLE reloptions_test(i INT) WITH (vacuum_truncate);
+SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass;
+
+DROP TABLE reloptions_test;
+CREATE TABLE reloptions_test(i INT) WITH (vacuum_truncate=FaLS);
+SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass;
+
+-- preferred "true" alias is used when storing
+DROP TABLE reloptions_test;
+CREATE TABLE reloptions_test(i INT) WITH (vacuum_index_cleanup=on);
+SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass;
+
+-- custom "third" value is available
+DROP TABLE reloptions_test;
+CREATE TABLE reloptions_test(i INT) WITH (vacuum_index_cleanup=auto);
+SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass;
+
-- Test vacuum_truncate option
DROP TABLE reloptions_test;
diff --git a/src/tools/pgindent/typedefs.list b/src/tools/pgindent/typedefs.list
index 3f3a888fd0e..1c8610fd46c 100644
--- a/src/tools/pgindent/typedefs.list
+++ b/src/tools/pgindent/typedefs.list
@@ -3952,6 +3952,7 @@ pg_sha512_ctx
pg_snapshot
pg_special_case
pg_stack_base_t
+pg_ternary
pg_time_t
pg_time_usec_t
pg_tz
@@ -4079,6 +4080,7 @@ relopt_kind
relopt_parse_elt
relopt_real
relopt_string
+relopt_ternary
relopt_type
relopt_value
relopts_validator
--
2.47.3
--c36t3fndxhkzbl5z--
^ permalink raw reply [nested|flat] 868+ messages in thread
* [PATCH v3] Introduce ternary reloptions
@ 2026-01-16 15:04 Álvaro Herrera <[email protected]>
0 siblings, 0 replies; 868+ messages in thread
From: Álvaro Herrera @ 2026-01-16 15:04 UTC (permalink / raw)
Introduce ternary reloption as a replacement for current
`vacuum_truncate` implementation. Remove the `vacuum_truncate_set`
separate flag and use TERNARY_UNSET instead.
This could also be used for other options such as `vacuum_index_cleanup`
and `buffering`, but lets get the scaffolding in first.
Discussion: https://postgr.es/m/3474141.usfYGdeWWP@thinkpad-pgpro
---
src/backend/access/common/reloptions.c | 137 ++++++++++++++++++-----
src/backend/commands/vacuum.c | 4 +-
src/include/access/reloptions.h | 27 ++---
src/include/postgres.h | 15 +++
src/include/utils/rel.h | 3 +-
src/test/regress/expected/reloptions.out | 36 ++++++
src/test/regress/sql/reloptions.sql | 21 ++++
src/tools/pgindent/typedefs.list | 2 +
8 files changed, 202 insertions(+), 43 deletions(-)
diff --git a/src/backend/access/common/reloptions.c b/src/backend/access/common/reloptions.c
index 0b83f98ed5f..6f1f577581a 100644
--- a/src/backend/access/common/reloptions.c
+++ b/src/backend/access/common/reloptions.c
@@ -40,9 +40,9 @@
*
* To add an option:
*
- * (i) decide on a type (bool, integer, real, enum, string), name, default
- * value, upper and lower bounds (if applicable); for strings, consider a
- * validation routine.
+ * (i) decide on a type (bool, ternary, integer, real, enum, string), name,
+ * default value, upper and lower bounds (if applicable); for strings,
+ * consider a validation routine.
* (ii) add a record below (or use add_<type>_reloption).
* (iii) add it to the appropriate options struct (perhaps StdRdOptions)
* (iv) add it to the appropriate handling routine (perhaps
@@ -147,15 +147,6 @@ static relopt_bool boolRelOpts[] =
},
false
},
- {
- {
- "vacuum_truncate",
- "Enables vacuum to truncate empty pages at the end of this table",
- RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
- ShareUpdateExclusiveLock
- },
- true
- },
{
{
"deduplicate_items",
@@ -170,6 +161,25 @@ static relopt_bool boolRelOpts[] =
{{NULL}}
};
+static relopt_ternary ternaryRelOpts[] =
+{
+ {
+ {
+ "vacuum_truncate",
+ "Enables vacuum to truncate empty pages at the end of this table",
+ RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
+ ShareUpdateExclusiveLock
+ },
+ TERNARY_UNSET
+ },
+ /* list terminator */
+ {
+ {
+ NULL
+ }
+ }
+};
+
static relopt_int intRelOpts[] =
{
{
@@ -609,6 +619,13 @@ initialize_reloptions(void)
boolRelOpts[i].gen.lockmode));
j++;
}
+ for (i = 0; ternaryRelOpts[i].gen.name; i++)
+ {
+ Assert(DoLockModesConflict(ternaryRelOpts[i].gen.lockmode,
+ ternaryRelOpts[i].gen.lockmode));
+ j++;
+ }
+
for (i = 0; intRelOpts[i].gen.name; i++)
{
Assert(DoLockModesConflict(intRelOpts[i].gen.lockmode,
@@ -649,6 +666,14 @@ initialize_reloptions(void)
j++;
}
+ for (i = 0; ternaryRelOpts[i].gen.name; i++)
+ {
+ relOpts[j] = &ternaryRelOpts[i].gen;
+ relOpts[j]->type = RELOPT_TYPE_TERNARY;
+ relOpts[j]->namelen = strlen(relOpts[j]->name);
+ j++;
+ }
+
for (i = 0; intRelOpts[i].gen.name; i++)
{
relOpts[j] = &intRelOpts[i].gen;
@@ -809,6 +834,9 @@ allocate_reloption(bits32 kinds, int type, const char *name, const char *desc,
case RELOPT_TYPE_BOOL:
size = sizeof(relopt_bool);
break;
+ case RELOPT_TYPE_TERNARY:
+ size = sizeof(relopt_ternary);
+ break;
case RELOPT_TYPE_INT:
size = sizeof(relopt_int);
break;
@@ -892,6 +920,57 @@ add_local_bool_reloption(local_relopts *relopts, const char *name,
add_local_reloption(relopts, (relopt_gen *) newoption, offset);
}
+/*
+ * init_ternary_reloption
+ * Allocate and initialize a new ternary reloption
+ */
+static relopt_ternary *
+init_ternary_reloption(bits32 kinds, const char *name, const char *desc,
+ pg_ternary default_val, LOCKMODE lockmode)
+{
+ relopt_ternary *newoption;
+
+ newoption = (relopt_ternary *)
+ allocate_reloption(kinds, RELOPT_TYPE_TERNARY, name, desc, lockmode);
+ newoption->default_val = default_val;
+
+ return newoption;
+}
+
+/*
+ * add_ternary_reloption
+ * Add a new ternary reloption
+ */
+void
+add_ternary_reloption(bits32 kinds, const char *name, const char *desc,
+ pg_ternary default_val, LOCKMODE lockmode)
+{
+ relopt_ternary *newoption;
+
+ newoption =
+ init_ternary_reloption(kinds, name, desc, default_val, lockmode);
+
+ add_reloption((relopt_gen *) newoption);
+}
+
+/*
+ * add_local_ternary_reloption
+ * Add a new ternary local reloption
+ *
+ * 'offset' is offset of ternary-typed field.
+ */
+void
+add_local_ternary_reloption(local_relopts *relopts, const char *name,
+ const char *desc, pg_ternary default_val,
+ int offset)
+{
+ relopt_ternary *newoption;
+
+ newoption =
+ init_ternary_reloption(RELOPT_KIND_LOCAL, name, desc, default_val, 0);
+
+ add_local_reloption(relopts, (relopt_gen *) newoption, offset);
+}
/*
* init_real_reloption
@@ -1626,6 +1705,19 @@ parse_one_reloption(relopt_value *option, char *text_str, int text_len,
option->gen->name, value)));
}
break;
+ case RELOPT_TYPE_TERNARY:
+ {
+ bool b;
+
+ parsed = parse_bool(value, &b);
+ option->values.ternary_val = b ? TERNARY_TRUE : TERNARY_FALSE;
+ if (validate && !parsed)
+ ereport(ERROR,
+ (errcode(ERRCODE_INVALID_PARAMETER_VALUE),
+ errmsg("invalid value for ternary option \"%s\": %s",
+ option->gen->name, value)));
+ }
+ break;
case RELOPT_TYPE_INT:
{
relopt_int *optint = (relopt_int *) option->gen;
@@ -1789,17 +1881,6 @@ fillRelOptions(void *rdopts, Size basesize,
char *itempos = ((char *) rdopts) + elems[j].offset;
char *string_val;
- /*
- * If isset_offset is provided, store whether the reloption is
- * set there.
- */
- if (elems[j].isset_offset > 0)
- {
- char *setpos = ((char *) rdopts) + elems[j].isset_offset;
-
- *(bool *) setpos = options[i].isset;
- }
-
switch (options[i].gen->type)
{
case RELOPT_TYPE_BOOL:
@@ -1807,6 +1888,11 @@ fillRelOptions(void *rdopts, Size basesize,
options[i].values.bool_val :
((relopt_bool *) options[i].gen)->default_val;
break;
+ case RELOPT_TYPE_TERNARY:
+ *(pg_ternary *) itempos = options[i].isset ?
+ options[i].values.ternary_val :
+ ((relopt_ternary *) options[i].gen)->default_val;
+ break;
case RELOPT_TYPE_INT:
*(int *) itempos = options[i].isset ?
options[i].values.int_val :
@@ -1923,8 +2009,8 @@ default_reloptions(Datum reloptions, bool validate, relopt_kind kind)
offsetof(StdRdOptions, parallel_workers)},
{"vacuum_index_cleanup", RELOPT_TYPE_ENUM,
offsetof(StdRdOptions, vacuum_index_cleanup)},
- {"vacuum_truncate", RELOPT_TYPE_BOOL,
- offsetof(StdRdOptions, vacuum_truncate), offsetof(StdRdOptions, vacuum_truncate_set)},
+ {"vacuum_truncate", RELOPT_TYPE_TERNARY,
+ offsetof(StdRdOptions, vacuum_truncate)},
{"vacuum_max_eager_freeze_failure_rate", RELOPT_TYPE_REAL,
offsetof(StdRdOptions, vacuum_max_eager_freeze_failure_rate)}
};
@@ -2004,7 +2090,6 @@ build_local_reloptions(local_relopts *relopts, Datum options, bool validate)
elems[i].optname = opt->option->name;
elems[i].opttype = opt->option->type;
elems[i].offset = opt->offset;
- elems[i].isset_offset = 0; /* not supported for local relopts yet */
i++;
}
diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c
index aa4fbec143f..696eab9bd97 100644
--- a/src/backend/commands/vacuum.c
+++ b/src/backend/commands/vacuum.c
@@ -2224,9 +2224,9 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams params,
{
StdRdOptions *opts = (StdRdOptions *) rel->rd_options;
- if (opts && opts->vacuum_truncate_set)
+ if (opts && opts->vacuum_truncate != TERNARY_UNSET)
{
- if (opts->vacuum_truncate)
+ if (opts->vacuum_truncate == TERNARY_TRUE)
params.truncate = VACOPTVALUE_ENABLED;
else
params.truncate = VACOPTVALUE_DISABLED;
diff --git a/src/include/access/reloptions.h b/src/include/access/reloptions.h
index 2f08e1b0cf0..dfbef2babf2 100644
--- a/src/include/access/reloptions.h
+++ b/src/include/access/reloptions.h
@@ -29,6 +29,7 @@
typedef enum relopt_type
{
RELOPT_TYPE_BOOL,
+ RELOPT_TYPE_TERNARY, /* on, off, unset */
RELOPT_TYPE_INT,
RELOPT_TYPE_REAL,
RELOPT_TYPE_ENUM,
@@ -80,6 +81,7 @@ typedef struct relopt_value
union
{
bool bool_val;
+ pg_ternary ternary_val;
int int_val;
double real_val;
int enum_val;
@@ -94,6 +96,12 @@ typedef struct relopt_bool
bool default_val;
} relopt_bool;
+typedef struct relopt_ternary
+{
+ relopt_gen gen;
+ int default_val;
+} relopt_ternary;
+
typedef struct relopt_int
{
relopt_gen gen;
@@ -152,19 +160,6 @@ typedef struct
const char *optname; /* option's name */
relopt_type opttype; /* option's datatype */
int offset; /* offset of field in result struct */
-
- /*
- * isset_offset is an optional offset of a field in the result struct that
- * stores whether the option is explicitly set for the relation or if it
- * just picked up the default value. In most cases, this can be
- * accomplished by giving the reloption a special out-of-range default
- * value (e.g., some integer reloptions use -2), but this isn't always
- * possible. For example, a Boolean reloption cannot be given an
- * out-of-range default, so we need another way to discover the source of
- * its value. This offset is only used if given a value greater than
- * zero.
- */
- int isset_offset;
} relopt_parse_elt;
/* Local reloption definition */
@@ -195,6 +190,9 @@ typedef struct local_relopts
extern relopt_kind add_reloption_kind(void);
extern void add_bool_reloption(bits32 kinds, const char *name, const char *desc,
bool default_val, LOCKMODE lockmode);
+extern void add_ternary_reloption(bits32 kinds, const char *name,
+ const char *desc, pg_ternary default_val,
+ LOCKMODE lockmode);
extern void add_int_reloption(bits32 kinds, const char *name, const char *desc,
int default_val, int min_val, int max_val,
LOCKMODE lockmode);
@@ -214,6 +212,9 @@ extern void register_reloptions_validator(local_relopts *relopts,
extern void add_local_bool_reloption(local_relopts *relopts, const char *name,
const char *desc, bool default_val,
int offset);
+extern void add_local_ternary_reloption(local_relopts *relopts,
+ const char *name, const char *desc,
+ pg_ternary default_val, int offset);
extern void add_local_int_reloption(local_relopts *relopts, const char *name,
const char *desc, int default_val,
int min_val, int max_val, int offset);
diff --git a/src/include/postgres.h b/src/include/postgres.h
index 7d93fbce709..47c88780776 100644
--- a/src/include/postgres.h
+++ b/src/include/postgres.h
@@ -543,6 +543,21 @@ Float8GetDatum(float8 X)
* ----------------------------------------------------------------
*/
+/*
+ * pg_ternary
+ * Boolean value with an extra "unset" value
+ *
+ * This enum can be used for values that want to distinguish between true,
+ * false, and unset.
+*/
+
+typedef enum pg_ternary
+{
+ TERNARY_FALSE = 0,
+ TERNARY_TRUE = 1,
+ TERNARY_UNSET = -1
+} pg_ternary;
+
/*
* NON_EXEC_STATIC: It's sometimes useful to define a variable or function
* that is normally static but extern when using EXEC_BACKEND (see
diff --git a/src/include/utils/rel.h b/src/include/utils/rel.h
index d03ab247788..5feb18a5373 100644
--- a/src/include/utils/rel.h
+++ b/src/include/utils/rel.h
@@ -347,8 +347,7 @@ typedef struct StdRdOptions
bool user_catalog_table; /* use as an additional catalog relation */
int parallel_workers; /* max number of parallel workers */
StdRdOptIndexCleanup vacuum_index_cleanup; /* controls index vacuuming */
- bool vacuum_truncate; /* enables vacuum to truncate a relation */
- bool vacuum_truncate_set; /* whether vacuum_truncate is set */
+ pg_ternary vacuum_truncate; /* enables vacuum to truncate a relation */
/*
* Fraction of pages in a relation that vacuum can eagerly scan and fail
diff --git a/src/test/regress/expected/reloptions.out b/src/test/regress/expected/reloptions.out
index 9de19b4e3f1..1c99f79ab01 100644
--- a/src/test/regress/expected/reloptions.out
+++ b/src/test/regress/expected/reloptions.out
@@ -98,6 +98,42 @@ SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass;
{fillfactor=13,autovacuum_enabled=false}
(1 row)
+-- Tests for future (FIXME) ternary options
+-- behave as boolean option: accept unassigned name and truncated value
+DROP TABLE reloptions_test;
+CREATE TABLE reloptions_test(i INT) WITH (vacuum_truncate);
+SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass;
+ reloptions
+------------------------
+ {vacuum_truncate=true}
+(1 row)
+
+DROP TABLE reloptions_test;
+CREATE TABLE reloptions_test(i INT) WITH (vacuum_truncate=FaLS);
+SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass;
+ reloptions
+------------------------
+ {vacuum_truncate=fals}
+(1 row)
+
+-- preferred "true" alias is used when storing
+DROP TABLE reloptions_test;
+CREATE TABLE reloptions_test(i INT) WITH (vacuum_index_cleanup=on);
+SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass;
+ reloptions
+---------------------------
+ {vacuum_index_cleanup=on}
+(1 row)
+
+-- custom "third" value is available
+DROP TABLE reloptions_test;
+CREATE TABLE reloptions_test(i INT) WITH (vacuum_index_cleanup=auto);
+SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass;
+ reloptions
+-----------------------------
+ {vacuum_index_cleanup=auto}
+(1 row)
+
-- Test vacuum_truncate option
DROP TABLE reloptions_test;
CREATE TEMP TABLE reloptions_test(i INT NOT NULL, j text)
diff --git a/src/test/regress/sql/reloptions.sql b/src/test/regress/sql/reloptions.sql
index 24fbe0b478d..f5980dafcbc 100644
--- a/src/test/regress/sql/reloptions.sql
+++ b/src/test/regress/sql/reloptions.sql
@@ -59,6 +59,27 @@ UPDATE pg_class
ALTER TABLE reloptions_test RESET (illegal_option);
SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass;
+-- Tests for future (FIXME) ternary options
+
+-- behave as boolean option: accept unassigned name and truncated value
+DROP TABLE reloptions_test;
+CREATE TABLE reloptions_test(i INT) WITH (vacuum_truncate);
+SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass;
+
+DROP TABLE reloptions_test;
+CREATE TABLE reloptions_test(i INT) WITH (vacuum_truncate=FaLS);
+SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass;
+
+-- preferred "true" alias is used when storing
+DROP TABLE reloptions_test;
+CREATE TABLE reloptions_test(i INT) WITH (vacuum_index_cleanup=on);
+SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass;
+
+-- custom "third" value is available
+DROP TABLE reloptions_test;
+CREATE TABLE reloptions_test(i INT) WITH (vacuum_index_cleanup=auto);
+SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass;
+
-- Test vacuum_truncate option
DROP TABLE reloptions_test;
diff --git a/src/tools/pgindent/typedefs.list b/src/tools/pgindent/typedefs.list
index 3f3a888fd0e..1c8610fd46c 100644
--- a/src/tools/pgindent/typedefs.list
+++ b/src/tools/pgindent/typedefs.list
@@ -3952,6 +3952,7 @@ pg_sha512_ctx
pg_snapshot
pg_special_case
pg_stack_base_t
+pg_ternary
pg_time_t
pg_time_usec_t
pg_tz
@@ -4079,6 +4080,7 @@ relopt_kind
relopt_parse_elt
relopt_real
relopt_string
+relopt_ternary
relopt_type
relopt_value
relopts_validator
--
2.47.3
--c36t3fndxhkzbl5z--
^ permalink raw reply [nested|flat] 868+ messages in thread
* [PATCH v3] Introduce ternary reloptions
@ 2026-01-16 15:04 Álvaro Herrera <[email protected]>
0 siblings, 0 replies; 868+ messages in thread
From: Álvaro Herrera @ 2026-01-16 15:04 UTC (permalink / raw)
Introduce ternary reloption as a replacement for current
`vacuum_truncate` implementation. Remove the `vacuum_truncate_set`
separate flag and use TERNARY_UNSET instead.
This could also be used for other options such as `vacuum_index_cleanup`
and `buffering`, but lets get the scaffolding in first.
Discussion: https://postgr.es/m/3474141.usfYGdeWWP@thinkpad-pgpro
---
src/backend/access/common/reloptions.c | 137 ++++++++++++++++++-----
src/backend/commands/vacuum.c | 4 +-
src/include/access/reloptions.h | 27 ++---
src/include/postgres.h | 15 +++
src/include/utils/rel.h | 3 +-
src/test/regress/expected/reloptions.out | 36 ++++++
src/test/regress/sql/reloptions.sql | 21 ++++
src/tools/pgindent/typedefs.list | 2 +
8 files changed, 202 insertions(+), 43 deletions(-)
diff --git a/src/backend/access/common/reloptions.c b/src/backend/access/common/reloptions.c
index 0b83f98ed5f..6f1f577581a 100644
--- a/src/backend/access/common/reloptions.c
+++ b/src/backend/access/common/reloptions.c
@@ -40,9 +40,9 @@
*
* To add an option:
*
- * (i) decide on a type (bool, integer, real, enum, string), name, default
- * value, upper and lower bounds (if applicable); for strings, consider a
- * validation routine.
+ * (i) decide on a type (bool, ternary, integer, real, enum, string), name,
+ * default value, upper and lower bounds (if applicable); for strings,
+ * consider a validation routine.
* (ii) add a record below (or use add_<type>_reloption).
* (iii) add it to the appropriate options struct (perhaps StdRdOptions)
* (iv) add it to the appropriate handling routine (perhaps
@@ -147,15 +147,6 @@ static relopt_bool boolRelOpts[] =
},
false
},
- {
- {
- "vacuum_truncate",
- "Enables vacuum to truncate empty pages at the end of this table",
- RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
- ShareUpdateExclusiveLock
- },
- true
- },
{
{
"deduplicate_items",
@@ -170,6 +161,25 @@ static relopt_bool boolRelOpts[] =
{{NULL}}
};
+static relopt_ternary ternaryRelOpts[] =
+{
+ {
+ {
+ "vacuum_truncate",
+ "Enables vacuum to truncate empty pages at the end of this table",
+ RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
+ ShareUpdateExclusiveLock
+ },
+ TERNARY_UNSET
+ },
+ /* list terminator */
+ {
+ {
+ NULL
+ }
+ }
+};
+
static relopt_int intRelOpts[] =
{
{
@@ -609,6 +619,13 @@ initialize_reloptions(void)
boolRelOpts[i].gen.lockmode));
j++;
}
+ for (i = 0; ternaryRelOpts[i].gen.name; i++)
+ {
+ Assert(DoLockModesConflict(ternaryRelOpts[i].gen.lockmode,
+ ternaryRelOpts[i].gen.lockmode));
+ j++;
+ }
+
for (i = 0; intRelOpts[i].gen.name; i++)
{
Assert(DoLockModesConflict(intRelOpts[i].gen.lockmode,
@@ -649,6 +666,14 @@ initialize_reloptions(void)
j++;
}
+ for (i = 0; ternaryRelOpts[i].gen.name; i++)
+ {
+ relOpts[j] = &ternaryRelOpts[i].gen;
+ relOpts[j]->type = RELOPT_TYPE_TERNARY;
+ relOpts[j]->namelen = strlen(relOpts[j]->name);
+ j++;
+ }
+
for (i = 0; intRelOpts[i].gen.name; i++)
{
relOpts[j] = &intRelOpts[i].gen;
@@ -809,6 +834,9 @@ allocate_reloption(bits32 kinds, int type, const char *name, const char *desc,
case RELOPT_TYPE_BOOL:
size = sizeof(relopt_bool);
break;
+ case RELOPT_TYPE_TERNARY:
+ size = sizeof(relopt_ternary);
+ break;
case RELOPT_TYPE_INT:
size = sizeof(relopt_int);
break;
@@ -892,6 +920,57 @@ add_local_bool_reloption(local_relopts *relopts, const char *name,
add_local_reloption(relopts, (relopt_gen *) newoption, offset);
}
+/*
+ * init_ternary_reloption
+ * Allocate and initialize a new ternary reloption
+ */
+static relopt_ternary *
+init_ternary_reloption(bits32 kinds, const char *name, const char *desc,
+ pg_ternary default_val, LOCKMODE lockmode)
+{
+ relopt_ternary *newoption;
+
+ newoption = (relopt_ternary *)
+ allocate_reloption(kinds, RELOPT_TYPE_TERNARY, name, desc, lockmode);
+ newoption->default_val = default_val;
+
+ return newoption;
+}
+
+/*
+ * add_ternary_reloption
+ * Add a new ternary reloption
+ */
+void
+add_ternary_reloption(bits32 kinds, const char *name, const char *desc,
+ pg_ternary default_val, LOCKMODE lockmode)
+{
+ relopt_ternary *newoption;
+
+ newoption =
+ init_ternary_reloption(kinds, name, desc, default_val, lockmode);
+
+ add_reloption((relopt_gen *) newoption);
+}
+
+/*
+ * add_local_ternary_reloption
+ * Add a new ternary local reloption
+ *
+ * 'offset' is offset of ternary-typed field.
+ */
+void
+add_local_ternary_reloption(local_relopts *relopts, const char *name,
+ const char *desc, pg_ternary default_val,
+ int offset)
+{
+ relopt_ternary *newoption;
+
+ newoption =
+ init_ternary_reloption(RELOPT_KIND_LOCAL, name, desc, default_val, 0);
+
+ add_local_reloption(relopts, (relopt_gen *) newoption, offset);
+}
/*
* init_real_reloption
@@ -1626,6 +1705,19 @@ parse_one_reloption(relopt_value *option, char *text_str, int text_len,
option->gen->name, value)));
}
break;
+ case RELOPT_TYPE_TERNARY:
+ {
+ bool b;
+
+ parsed = parse_bool(value, &b);
+ option->values.ternary_val = b ? TERNARY_TRUE : TERNARY_FALSE;
+ if (validate && !parsed)
+ ereport(ERROR,
+ (errcode(ERRCODE_INVALID_PARAMETER_VALUE),
+ errmsg("invalid value for ternary option \"%s\": %s",
+ option->gen->name, value)));
+ }
+ break;
case RELOPT_TYPE_INT:
{
relopt_int *optint = (relopt_int *) option->gen;
@@ -1789,17 +1881,6 @@ fillRelOptions(void *rdopts, Size basesize,
char *itempos = ((char *) rdopts) + elems[j].offset;
char *string_val;
- /*
- * If isset_offset is provided, store whether the reloption is
- * set there.
- */
- if (elems[j].isset_offset > 0)
- {
- char *setpos = ((char *) rdopts) + elems[j].isset_offset;
-
- *(bool *) setpos = options[i].isset;
- }
-
switch (options[i].gen->type)
{
case RELOPT_TYPE_BOOL:
@@ -1807,6 +1888,11 @@ fillRelOptions(void *rdopts, Size basesize,
options[i].values.bool_val :
((relopt_bool *) options[i].gen)->default_val;
break;
+ case RELOPT_TYPE_TERNARY:
+ *(pg_ternary *) itempos = options[i].isset ?
+ options[i].values.ternary_val :
+ ((relopt_ternary *) options[i].gen)->default_val;
+ break;
case RELOPT_TYPE_INT:
*(int *) itempos = options[i].isset ?
options[i].values.int_val :
@@ -1923,8 +2009,8 @@ default_reloptions(Datum reloptions, bool validate, relopt_kind kind)
offsetof(StdRdOptions, parallel_workers)},
{"vacuum_index_cleanup", RELOPT_TYPE_ENUM,
offsetof(StdRdOptions, vacuum_index_cleanup)},
- {"vacuum_truncate", RELOPT_TYPE_BOOL,
- offsetof(StdRdOptions, vacuum_truncate), offsetof(StdRdOptions, vacuum_truncate_set)},
+ {"vacuum_truncate", RELOPT_TYPE_TERNARY,
+ offsetof(StdRdOptions, vacuum_truncate)},
{"vacuum_max_eager_freeze_failure_rate", RELOPT_TYPE_REAL,
offsetof(StdRdOptions, vacuum_max_eager_freeze_failure_rate)}
};
@@ -2004,7 +2090,6 @@ build_local_reloptions(local_relopts *relopts, Datum options, bool validate)
elems[i].optname = opt->option->name;
elems[i].opttype = opt->option->type;
elems[i].offset = opt->offset;
- elems[i].isset_offset = 0; /* not supported for local relopts yet */
i++;
}
diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c
index aa4fbec143f..696eab9bd97 100644
--- a/src/backend/commands/vacuum.c
+++ b/src/backend/commands/vacuum.c
@@ -2224,9 +2224,9 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams params,
{
StdRdOptions *opts = (StdRdOptions *) rel->rd_options;
- if (opts && opts->vacuum_truncate_set)
+ if (opts && opts->vacuum_truncate != TERNARY_UNSET)
{
- if (opts->vacuum_truncate)
+ if (opts->vacuum_truncate == TERNARY_TRUE)
params.truncate = VACOPTVALUE_ENABLED;
else
params.truncate = VACOPTVALUE_DISABLED;
diff --git a/src/include/access/reloptions.h b/src/include/access/reloptions.h
index 2f08e1b0cf0..dfbef2babf2 100644
--- a/src/include/access/reloptions.h
+++ b/src/include/access/reloptions.h
@@ -29,6 +29,7 @@
typedef enum relopt_type
{
RELOPT_TYPE_BOOL,
+ RELOPT_TYPE_TERNARY, /* on, off, unset */
RELOPT_TYPE_INT,
RELOPT_TYPE_REAL,
RELOPT_TYPE_ENUM,
@@ -80,6 +81,7 @@ typedef struct relopt_value
union
{
bool bool_val;
+ pg_ternary ternary_val;
int int_val;
double real_val;
int enum_val;
@@ -94,6 +96,12 @@ typedef struct relopt_bool
bool default_val;
} relopt_bool;
+typedef struct relopt_ternary
+{
+ relopt_gen gen;
+ int default_val;
+} relopt_ternary;
+
typedef struct relopt_int
{
relopt_gen gen;
@@ -152,19 +160,6 @@ typedef struct
const char *optname; /* option's name */
relopt_type opttype; /* option's datatype */
int offset; /* offset of field in result struct */
-
- /*
- * isset_offset is an optional offset of a field in the result struct that
- * stores whether the option is explicitly set for the relation or if it
- * just picked up the default value. In most cases, this can be
- * accomplished by giving the reloption a special out-of-range default
- * value (e.g., some integer reloptions use -2), but this isn't always
- * possible. For example, a Boolean reloption cannot be given an
- * out-of-range default, so we need another way to discover the source of
- * its value. This offset is only used if given a value greater than
- * zero.
- */
- int isset_offset;
} relopt_parse_elt;
/* Local reloption definition */
@@ -195,6 +190,9 @@ typedef struct local_relopts
extern relopt_kind add_reloption_kind(void);
extern void add_bool_reloption(bits32 kinds, const char *name, const char *desc,
bool default_val, LOCKMODE lockmode);
+extern void add_ternary_reloption(bits32 kinds, const char *name,
+ const char *desc, pg_ternary default_val,
+ LOCKMODE lockmode);
extern void add_int_reloption(bits32 kinds, const char *name, const char *desc,
int default_val, int min_val, int max_val,
LOCKMODE lockmode);
@@ -214,6 +212,9 @@ extern void register_reloptions_validator(local_relopts *relopts,
extern void add_local_bool_reloption(local_relopts *relopts, const char *name,
const char *desc, bool default_val,
int offset);
+extern void add_local_ternary_reloption(local_relopts *relopts,
+ const char *name, const char *desc,
+ pg_ternary default_val, int offset);
extern void add_local_int_reloption(local_relopts *relopts, const char *name,
const char *desc, int default_val,
int min_val, int max_val, int offset);
diff --git a/src/include/postgres.h b/src/include/postgres.h
index 7d93fbce709..47c88780776 100644
--- a/src/include/postgres.h
+++ b/src/include/postgres.h
@@ -543,6 +543,21 @@ Float8GetDatum(float8 X)
* ----------------------------------------------------------------
*/
+/*
+ * pg_ternary
+ * Boolean value with an extra "unset" value
+ *
+ * This enum can be used for values that want to distinguish between true,
+ * false, and unset.
+*/
+
+typedef enum pg_ternary
+{
+ TERNARY_FALSE = 0,
+ TERNARY_TRUE = 1,
+ TERNARY_UNSET = -1
+} pg_ternary;
+
/*
* NON_EXEC_STATIC: It's sometimes useful to define a variable or function
* that is normally static but extern when using EXEC_BACKEND (see
diff --git a/src/include/utils/rel.h b/src/include/utils/rel.h
index d03ab247788..5feb18a5373 100644
--- a/src/include/utils/rel.h
+++ b/src/include/utils/rel.h
@@ -347,8 +347,7 @@ typedef struct StdRdOptions
bool user_catalog_table; /* use as an additional catalog relation */
int parallel_workers; /* max number of parallel workers */
StdRdOptIndexCleanup vacuum_index_cleanup; /* controls index vacuuming */
- bool vacuum_truncate; /* enables vacuum to truncate a relation */
- bool vacuum_truncate_set; /* whether vacuum_truncate is set */
+ pg_ternary vacuum_truncate; /* enables vacuum to truncate a relation */
/*
* Fraction of pages in a relation that vacuum can eagerly scan and fail
diff --git a/src/test/regress/expected/reloptions.out b/src/test/regress/expected/reloptions.out
index 9de19b4e3f1..1c99f79ab01 100644
--- a/src/test/regress/expected/reloptions.out
+++ b/src/test/regress/expected/reloptions.out
@@ -98,6 +98,42 @@ SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass;
{fillfactor=13,autovacuum_enabled=false}
(1 row)
+-- Tests for future (FIXME) ternary options
+-- behave as boolean option: accept unassigned name and truncated value
+DROP TABLE reloptions_test;
+CREATE TABLE reloptions_test(i INT) WITH (vacuum_truncate);
+SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass;
+ reloptions
+------------------------
+ {vacuum_truncate=true}
+(1 row)
+
+DROP TABLE reloptions_test;
+CREATE TABLE reloptions_test(i INT) WITH (vacuum_truncate=FaLS);
+SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass;
+ reloptions
+------------------------
+ {vacuum_truncate=fals}
+(1 row)
+
+-- preferred "true" alias is used when storing
+DROP TABLE reloptions_test;
+CREATE TABLE reloptions_test(i INT) WITH (vacuum_index_cleanup=on);
+SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass;
+ reloptions
+---------------------------
+ {vacuum_index_cleanup=on}
+(1 row)
+
+-- custom "third" value is available
+DROP TABLE reloptions_test;
+CREATE TABLE reloptions_test(i INT) WITH (vacuum_index_cleanup=auto);
+SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass;
+ reloptions
+-----------------------------
+ {vacuum_index_cleanup=auto}
+(1 row)
+
-- Test vacuum_truncate option
DROP TABLE reloptions_test;
CREATE TEMP TABLE reloptions_test(i INT NOT NULL, j text)
diff --git a/src/test/regress/sql/reloptions.sql b/src/test/regress/sql/reloptions.sql
index 24fbe0b478d..f5980dafcbc 100644
--- a/src/test/regress/sql/reloptions.sql
+++ b/src/test/regress/sql/reloptions.sql
@@ -59,6 +59,27 @@ UPDATE pg_class
ALTER TABLE reloptions_test RESET (illegal_option);
SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass;
+-- Tests for future (FIXME) ternary options
+
+-- behave as boolean option: accept unassigned name and truncated value
+DROP TABLE reloptions_test;
+CREATE TABLE reloptions_test(i INT) WITH (vacuum_truncate);
+SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass;
+
+DROP TABLE reloptions_test;
+CREATE TABLE reloptions_test(i INT) WITH (vacuum_truncate=FaLS);
+SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass;
+
+-- preferred "true" alias is used when storing
+DROP TABLE reloptions_test;
+CREATE TABLE reloptions_test(i INT) WITH (vacuum_index_cleanup=on);
+SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass;
+
+-- custom "third" value is available
+DROP TABLE reloptions_test;
+CREATE TABLE reloptions_test(i INT) WITH (vacuum_index_cleanup=auto);
+SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass;
+
-- Test vacuum_truncate option
DROP TABLE reloptions_test;
diff --git a/src/tools/pgindent/typedefs.list b/src/tools/pgindent/typedefs.list
index 3f3a888fd0e..1c8610fd46c 100644
--- a/src/tools/pgindent/typedefs.list
+++ b/src/tools/pgindent/typedefs.list
@@ -3952,6 +3952,7 @@ pg_sha512_ctx
pg_snapshot
pg_special_case
pg_stack_base_t
+pg_ternary
pg_time_t
pg_time_usec_t
pg_tz
@@ -4079,6 +4080,7 @@ relopt_kind
relopt_parse_elt
relopt_real
relopt_string
+relopt_ternary
relopt_type
relopt_value
relopts_validator
--
2.47.3
--c36t3fndxhkzbl5z--
^ permalink raw reply [nested|flat] 868+ messages in thread
* [PATCH v3] Introduce ternary reloptions
@ 2026-01-16 15:04 Álvaro Herrera <[email protected]>
0 siblings, 0 replies; 868+ messages in thread
From: Álvaro Herrera @ 2026-01-16 15:04 UTC (permalink / raw)
Introduce ternary reloption as a replacement for current
`vacuum_truncate` implementation. Remove the `vacuum_truncate_set`
separate flag and use TERNARY_UNSET instead.
This could also be used for other options such as `vacuum_index_cleanup`
and `buffering`, but lets get the scaffolding in first.
Discussion: https://postgr.es/m/3474141.usfYGdeWWP@thinkpad-pgpro
---
src/backend/access/common/reloptions.c | 137 ++++++++++++++++++-----
src/backend/commands/vacuum.c | 4 +-
src/include/access/reloptions.h | 27 ++---
src/include/postgres.h | 15 +++
src/include/utils/rel.h | 3 +-
src/test/regress/expected/reloptions.out | 36 ++++++
src/test/regress/sql/reloptions.sql | 21 ++++
src/tools/pgindent/typedefs.list | 2 +
8 files changed, 202 insertions(+), 43 deletions(-)
diff --git a/src/backend/access/common/reloptions.c b/src/backend/access/common/reloptions.c
index 0b83f98ed5f..6f1f577581a 100644
--- a/src/backend/access/common/reloptions.c
+++ b/src/backend/access/common/reloptions.c
@@ -40,9 +40,9 @@
*
* To add an option:
*
- * (i) decide on a type (bool, integer, real, enum, string), name, default
- * value, upper and lower bounds (if applicable); for strings, consider a
- * validation routine.
+ * (i) decide on a type (bool, ternary, integer, real, enum, string), name,
+ * default value, upper and lower bounds (if applicable); for strings,
+ * consider a validation routine.
* (ii) add a record below (or use add_<type>_reloption).
* (iii) add it to the appropriate options struct (perhaps StdRdOptions)
* (iv) add it to the appropriate handling routine (perhaps
@@ -147,15 +147,6 @@ static relopt_bool boolRelOpts[] =
},
false
},
- {
- {
- "vacuum_truncate",
- "Enables vacuum to truncate empty pages at the end of this table",
- RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
- ShareUpdateExclusiveLock
- },
- true
- },
{
{
"deduplicate_items",
@@ -170,6 +161,25 @@ static relopt_bool boolRelOpts[] =
{{NULL}}
};
+static relopt_ternary ternaryRelOpts[] =
+{
+ {
+ {
+ "vacuum_truncate",
+ "Enables vacuum to truncate empty pages at the end of this table",
+ RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
+ ShareUpdateExclusiveLock
+ },
+ TERNARY_UNSET
+ },
+ /* list terminator */
+ {
+ {
+ NULL
+ }
+ }
+};
+
static relopt_int intRelOpts[] =
{
{
@@ -609,6 +619,13 @@ initialize_reloptions(void)
boolRelOpts[i].gen.lockmode));
j++;
}
+ for (i = 0; ternaryRelOpts[i].gen.name; i++)
+ {
+ Assert(DoLockModesConflict(ternaryRelOpts[i].gen.lockmode,
+ ternaryRelOpts[i].gen.lockmode));
+ j++;
+ }
+
for (i = 0; intRelOpts[i].gen.name; i++)
{
Assert(DoLockModesConflict(intRelOpts[i].gen.lockmode,
@@ -649,6 +666,14 @@ initialize_reloptions(void)
j++;
}
+ for (i = 0; ternaryRelOpts[i].gen.name; i++)
+ {
+ relOpts[j] = &ternaryRelOpts[i].gen;
+ relOpts[j]->type = RELOPT_TYPE_TERNARY;
+ relOpts[j]->namelen = strlen(relOpts[j]->name);
+ j++;
+ }
+
for (i = 0; intRelOpts[i].gen.name; i++)
{
relOpts[j] = &intRelOpts[i].gen;
@@ -809,6 +834,9 @@ allocate_reloption(bits32 kinds, int type, const char *name, const char *desc,
case RELOPT_TYPE_BOOL:
size = sizeof(relopt_bool);
break;
+ case RELOPT_TYPE_TERNARY:
+ size = sizeof(relopt_ternary);
+ break;
case RELOPT_TYPE_INT:
size = sizeof(relopt_int);
break;
@@ -892,6 +920,57 @@ add_local_bool_reloption(local_relopts *relopts, const char *name,
add_local_reloption(relopts, (relopt_gen *) newoption, offset);
}
+/*
+ * init_ternary_reloption
+ * Allocate and initialize a new ternary reloption
+ */
+static relopt_ternary *
+init_ternary_reloption(bits32 kinds, const char *name, const char *desc,
+ pg_ternary default_val, LOCKMODE lockmode)
+{
+ relopt_ternary *newoption;
+
+ newoption = (relopt_ternary *)
+ allocate_reloption(kinds, RELOPT_TYPE_TERNARY, name, desc, lockmode);
+ newoption->default_val = default_val;
+
+ return newoption;
+}
+
+/*
+ * add_ternary_reloption
+ * Add a new ternary reloption
+ */
+void
+add_ternary_reloption(bits32 kinds, const char *name, const char *desc,
+ pg_ternary default_val, LOCKMODE lockmode)
+{
+ relopt_ternary *newoption;
+
+ newoption =
+ init_ternary_reloption(kinds, name, desc, default_val, lockmode);
+
+ add_reloption((relopt_gen *) newoption);
+}
+
+/*
+ * add_local_ternary_reloption
+ * Add a new ternary local reloption
+ *
+ * 'offset' is offset of ternary-typed field.
+ */
+void
+add_local_ternary_reloption(local_relopts *relopts, const char *name,
+ const char *desc, pg_ternary default_val,
+ int offset)
+{
+ relopt_ternary *newoption;
+
+ newoption =
+ init_ternary_reloption(RELOPT_KIND_LOCAL, name, desc, default_val, 0);
+
+ add_local_reloption(relopts, (relopt_gen *) newoption, offset);
+}
/*
* init_real_reloption
@@ -1626,6 +1705,19 @@ parse_one_reloption(relopt_value *option, char *text_str, int text_len,
option->gen->name, value)));
}
break;
+ case RELOPT_TYPE_TERNARY:
+ {
+ bool b;
+
+ parsed = parse_bool(value, &b);
+ option->values.ternary_val = b ? TERNARY_TRUE : TERNARY_FALSE;
+ if (validate && !parsed)
+ ereport(ERROR,
+ (errcode(ERRCODE_INVALID_PARAMETER_VALUE),
+ errmsg("invalid value for ternary option \"%s\": %s",
+ option->gen->name, value)));
+ }
+ break;
case RELOPT_TYPE_INT:
{
relopt_int *optint = (relopt_int *) option->gen;
@@ -1789,17 +1881,6 @@ fillRelOptions(void *rdopts, Size basesize,
char *itempos = ((char *) rdopts) + elems[j].offset;
char *string_val;
- /*
- * If isset_offset is provided, store whether the reloption is
- * set there.
- */
- if (elems[j].isset_offset > 0)
- {
- char *setpos = ((char *) rdopts) + elems[j].isset_offset;
-
- *(bool *) setpos = options[i].isset;
- }
-
switch (options[i].gen->type)
{
case RELOPT_TYPE_BOOL:
@@ -1807,6 +1888,11 @@ fillRelOptions(void *rdopts, Size basesize,
options[i].values.bool_val :
((relopt_bool *) options[i].gen)->default_val;
break;
+ case RELOPT_TYPE_TERNARY:
+ *(pg_ternary *) itempos = options[i].isset ?
+ options[i].values.ternary_val :
+ ((relopt_ternary *) options[i].gen)->default_val;
+ break;
case RELOPT_TYPE_INT:
*(int *) itempos = options[i].isset ?
options[i].values.int_val :
@@ -1923,8 +2009,8 @@ default_reloptions(Datum reloptions, bool validate, relopt_kind kind)
offsetof(StdRdOptions, parallel_workers)},
{"vacuum_index_cleanup", RELOPT_TYPE_ENUM,
offsetof(StdRdOptions, vacuum_index_cleanup)},
- {"vacuum_truncate", RELOPT_TYPE_BOOL,
- offsetof(StdRdOptions, vacuum_truncate), offsetof(StdRdOptions, vacuum_truncate_set)},
+ {"vacuum_truncate", RELOPT_TYPE_TERNARY,
+ offsetof(StdRdOptions, vacuum_truncate)},
{"vacuum_max_eager_freeze_failure_rate", RELOPT_TYPE_REAL,
offsetof(StdRdOptions, vacuum_max_eager_freeze_failure_rate)}
};
@@ -2004,7 +2090,6 @@ build_local_reloptions(local_relopts *relopts, Datum options, bool validate)
elems[i].optname = opt->option->name;
elems[i].opttype = opt->option->type;
elems[i].offset = opt->offset;
- elems[i].isset_offset = 0; /* not supported for local relopts yet */
i++;
}
diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c
index aa4fbec143f..696eab9bd97 100644
--- a/src/backend/commands/vacuum.c
+++ b/src/backend/commands/vacuum.c
@@ -2224,9 +2224,9 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams params,
{
StdRdOptions *opts = (StdRdOptions *) rel->rd_options;
- if (opts && opts->vacuum_truncate_set)
+ if (opts && opts->vacuum_truncate != TERNARY_UNSET)
{
- if (opts->vacuum_truncate)
+ if (opts->vacuum_truncate == TERNARY_TRUE)
params.truncate = VACOPTVALUE_ENABLED;
else
params.truncate = VACOPTVALUE_DISABLED;
diff --git a/src/include/access/reloptions.h b/src/include/access/reloptions.h
index 2f08e1b0cf0..dfbef2babf2 100644
--- a/src/include/access/reloptions.h
+++ b/src/include/access/reloptions.h
@@ -29,6 +29,7 @@
typedef enum relopt_type
{
RELOPT_TYPE_BOOL,
+ RELOPT_TYPE_TERNARY, /* on, off, unset */
RELOPT_TYPE_INT,
RELOPT_TYPE_REAL,
RELOPT_TYPE_ENUM,
@@ -80,6 +81,7 @@ typedef struct relopt_value
union
{
bool bool_val;
+ pg_ternary ternary_val;
int int_val;
double real_val;
int enum_val;
@@ -94,6 +96,12 @@ typedef struct relopt_bool
bool default_val;
} relopt_bool;
+typedef struct relopt_ternary
+{
+ relopt_gen gen;
+ int default_val;
+} relopt_ternary;
+
typedef struct relopt_int
{
relopt_gen gen;
@@ -152,19 +160,6 @@ typedef struct
const char *optname; /* option's name */
relopt_type opttype; /* option's datatype */
int offset; /* offset of field in result struct */
-
- /*
- * isset_offset is an optional offset of a field in the result struct that
- * stores whether the option is explicitly set for the relation or if it
- * just picked up the default value. In most cases, this can be
- * accomplished by giving the reloption a special out-of-range default
- * value (e.g., some integer reloptions use -2), but this isn't always
- * possible. For example, a Boolean reloption cannot be given an
- * out-of-range default, so we need another way to discover the source of
- * its value. This offset is only used if given a value greater than
- * zero.
- */
- int isset_offset;
} relopt_parse_elt;
/* Local reloption definition */
@@ -195,6 +190,9 @@ typedef struct local_relopts
extern relopt_kind add_reloption_kind(void);
extern void add_bool_reloption(bits32 kinds, const char *name, const char *desc,
bool default_val, LOCKMODE lockmode);
+extern void add_ternary_reloption(bits32 kinds, const char *name,
+ const char *desc, pg_ternary default_val,
+ LOCKMODE lockmode);
extern void add_int_reloption(bits32 kinds, const char *name, const char *desc,
int default_val, int min_val, int max_val,
LOCKMODE lockmode);
@@ -214,6 +212,9 @@ extern void register_reloptions_validator(local_relopts *relopts,
extern void add_local_bool_reloption(local_relopts *relopts, const char *name,
const char *desc, bool default_val,
int offset);
+extern void add_local_ternary_reloption(local_relopts *relopts,
+ const char *name, const char *desc,
+ pg_ternary default_val, int offset);
extern void add_local_int_reloption(local_relopts *relopts, const char *name,
const char *desc, int default_val,
int min_val, int max_val, int offset);
diff --git a/src/include/postgres.h b/src/include/postgres.h
index 7d93fbce709..47c88780776 100644
--- a/src/include/postgres.h
+++ b/src/include/postgres.h
@@ -543,6 +543,21 @@ Float8GetDatum(float8 X)
* ----------------------------------------------------------------
*/
+/*
+ * pg_ternary
+ * Boolean value with an extra "unset" value
+ *
+ * This enum can be used for values that want to distinguish between true,
+ * false, and unset.
+*/
+
+typedef enum pg_ternary
+{
+ TERNARY_FALSE = 0,
+ TERNARY_TRUE = 1,
+ TERNARY_UNSET = -1
+} pg_ternary;
+
/*
* NON_EXEC_STATIC: It's sometimes useful to define a variable or function
* that is normally static but extern when using EXEC_BACKEND (see
diff --git a/src/include/utils/rel.h b/src/include/utils/rel.h
index d03ab247788..5feb18a5373 100644
--- a/src/include/utils/rel.h
+++ b/src/include/utils/rel.h
@@ -347,8 +347,7 @@ typedef struct StdRdOptions
bool user_catalog_table; /* use as an additional catalog relation */
int parallel_workers; /* max number of parallel workers */
StdRdOptIndexCleanup vacuum_index_cleanup; /* controls index vacuuming */
- bool vacuum_truncate; /* enables vacuum to truncate a relation */
- bool vacuum_truncate_set; /* whether vacuum_truncate is set */
+ pg_ternary vacuum_truncate; /* enables vacuum to truncate a relation */
/*
* Fraction of pages in a relation that vacuum can eagerly scan and fail
diff --git a/src/test/regress/expected/reloptions.out b/src/test/regress/expected/reloptions.out
index 9de19b4e3f1..1c99f79ab01 100644
--- a/src/test/regress/expected/reloptions.out
+++ b/src/test/regress/expected/reloptions.out
@@ -98,6 +98,42 @@ SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass;
{fillfactor=13,autovacuum_enabled=false}
(1 row)
+-- Tests for future (FIXME) ternary options
+-- behave as boolean option: accept unassigned name and truncated value
+DROP TABLE reloptions_test;
+CREATE TABLE reloptions_test(i INT) WITH (vacuum_truncate);
+SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass;
+ reloptions
+------------------------
+ {vacuum_truncate=true}
+(1 row)
+
+DROP TABLE reloptions_test;
+CREATE TABLE reloptions_test(i INT) WITH (vacuum_truncate=FaLS);
+SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass;
+ reloptions
+------------------------
+ {vacuum_truncate=fals}
+(1 row)
+
+-- preferred "true" alias is used when storing
+DROP TABLE reloptions_test;
+CREATE TABLE reloptions_test(i INT) WITH (vacuum_index_cleanup=on);
+SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass;
+ reloptions
+---------------------------
+ {vacuum_index_cleanup=on}
+(1 row)
+
+-- custom "third" value is available
+DROP TABLE reloptions_test;
+CREATE TABLE reloptions_test(i INT) WITH (vacuum_index_cleanup=auto);
+SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass;
+ reloptions
+-----------------------------
+ {vacuum_index_cleanup=auto}
+(1 row)
+
-- Test vacuum_truncate option
DROP TABLE reloptions_test;
CREATE TEMP TABLE reloptions_test(i INT NOT NULL, j text)
diff --git a/src/test/regress/sql/reloptions.sql b/src/test/regress/sql/reloptions.sql
index 24fbe0b478d..f5980dafcbc 100644
--- a/src/test/regress/sql/reloptions.sql
+++ b/src/test/regress/sql/reloptions.sql
@@ -59,6 +59,27 @@ UPDATE pg_class
ALTER TABLE reloptions_test RESET (illegal_option);
SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass;
+-- Tests for future (FIXME) ternary options
+
+-- behave as boolean option: accept unassigned name and truncated value
+DROP TABLE reloptions_test;
+CREATE TABLE reloptions_test(i INT) WITH (vacuum_truncate);
+SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass;
+
+DROP TABLE reloptions_test;
+CREATE TABLE reloptions_test(i INT) WITH (vacuum_truncate=FaLS);
+SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass;
+
+-- preferred "true" alias is used when storing
+DROP TABLE reloptions_test;
+CREATE TABLE reloptions_test(i INT) WITH (vacuum_index_cleanup=on);
+SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass;
+
+-- custom "third" value is available
+DROP TABLE reloptions_test;
+CREATE TABLE reloptions_test(i INT) WITH (vacuum_index_cleanup=auto);
+SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass;
+
-- Test vacuum_truncate option
DROP TABLE reloptions_test;
diff --git a/src/tools/pgindent/typedefs.list b/src/tools/pgindent/typedefs.list
index 3f3a888fd0e..1c8610fd46c 100644
--- a/src/tools/pgindent/typedefs.list
+++ b/src/tools/pgindent/typedefs.list
@@ -3952,6 +3952,7 @@ pg_sha512_ctx
pg_snapshot
pg_special_case
pg_stack_base_t
+pg_ternary
pg_time_t
pg_time_usec_t
pg_tz
@@ -4079,6 +4080,7 @@ relopt_kind
relopt_parse_elt
relopt_real
relopt_string
+relopt_ternary
relopt_type
relopt_value
relopts_validator
--
2.47.3
--c36t3fndxhkzbl5z--
^ permalink raw reply [nested|flat] 868+ messages in thread
* [PATCH v3] Introduce ternary reloptions
@ 2026-01-16 15:04 Álvaro Herrera <[email protected]>
0 siblings, 0 replies; 868+ messages in thread
From: Álvaro Herrera @ 2026-01-16 15:04 UTC (permalink / raw)
Introduce ternary reloption as a replacement for current
`vacuum_truncate` implementation. Remove the `vacuum_truncate_set`
separate flag and use TERNARY_UNSET instead.
This could also be used for other options such as `vacuum_index_cleanup`
and `buffering`, but lets get the scaffolding in first.
Discussion: https://postgr.es/m/3474141.usfYGdeWWP@thinkpad-pgpro
---
src/backend/access/common/reloptions.c | 137 ++++++++++++++++++-----
src/backend/commands/vacuum.c | 4 +-
src/include/access/reloptions.h | 27 ++---
src/include/postgres.h | 15 +++
src/include/utils/rel.h | 3 +-
src/test/regress/expected/reloptions.out | 36 ++++++
src/test/regress/sql/reloptions.sql | 21 ++++
src/tools/pgindent/typedefs.list | 2 +
8 files changed, 202 insertions(+), 43 deletions(-)
diff --git a/src/backend/access/common/reloptions.c b/src/backend/access/common/reloptions.c
index 0b83f98ed5f..6f1f577581a 100644
--- a/src/backend/access/common/reloptions.c
+++ b/src/backend/access/common/reloptions.c
@@ -40,9 +40,9 @@
*
* To add an option:
*
- * (i) decide on a type (bool, integer, real, enum, string), name, default
- * value, upper and lower bounds (if applicable); for strings, consider a
- * validation routine.
+ * (i) decide on a type (bool, ternary, integer, real, enum, string), name,
+ * default value, upper and lower bounds (if applicable); for strings,
+ * consider a validation routine.
* (ii) add a record below (or use add_<type>_reloption).
* (iii) add it to the appropriate options struct (perhaps StdRdOptions)
* (iv) add it to the appropriate handling routine (perhaps
@@ -147,15 +147,6 @@ static relopt_bool boolRelOpts[] =
},
false
},
- {
- {
- "vacuum_truncate",
- "Enables vacuum to truncate empty pages at the end of this table",
- RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
- ShareUpdateExclusiveLock
- },
- true
- },
{
{
"deduplicate_items",
@@ -170,6 +161,25 @@ static relopt_bool boolRelOpts[] =
{{NULL}}
};
+static relopt_ternary ternaryRelOpts[] =
+{
+ {
+ {
+ "vacuum_truncate",
+ "Enables vacuum to truncate empty pages at the end of this table",
+ RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
+ ShareUpdateExclusiveLock
+ },
+ TERNARY_UNSET
+ },
+ /* list terminator */
+ {
+ {
+ NULL
+ }
+ }
+};
+
static relopt_int intRelOpts[] =
{
{
@@ -609,6 +619,13 @@ initialize_reloptions(void)
boolRelOpts[i].gen.lockmode));
j++;
}
+ for (i = 0; ternaryRelOpts[i].gen.name; i++)
+ {
+ Assert(DoLockModesConflict(ternaryRelOpts[i].gen.lockmode,
+ ternaryRelOpts[i].gen.lockmode));
+ j++;
+ }
+
for (i = 0; intRelOpts[i].gen.name; i++)
{
Assert(DoLockModesConflict(intRelOpts[i].gen.lockmode,
@@ -649,6 +666,14 @@ initialize_reloptions(void)
j++;
}
+ for (i = 0; ternaryRelOpts[i].gen.name; i++)
+ {
+ relOpts[j] = &ternaryRelOpts[i].gen;
+ relOpts[j]->type = RELOPT_TYPE_TERNARY;
+ relOpts[j]->namelen = strlen(relOpts[j]->name);
+ j++;
+ }
+
for (i = 0; intRelOpts[i].gen.name; i++)
{
relOpts[j] = &intRelOpts[i].gen;
@@ -809,6 +834,9 @@ allocate_reloption(bits32 kinds, int type, const char *name, const char *desc,
case RELOPT_TYPE_BOOL:
size = sizeof(relopt_bool);
break;
+ case RELOPT_TYPE_TERNARY:
+ size = sizeof(relopt_ternary);
+ break;
case RELOPT_TYPE_INT:
size = sizeof(relopt_int);
break;
@@ -892,6 +920,57 @@ add_local_bool_reloption(local_relopts *relopts, const char *name,
add_local_reloption(relopts, (relopt_gen *) newoption, offset);
}
+/*
+ * init_ternary_reloption
+ * Allocate and initialize a new ternary reloption
+ */
+static relopt_ternary *
+init_ternary_reloption(bits32 kinds, const char *name, const char *desc,
+ pg_ternary default_val, LOCKMODE lockmode)
+{
+ relopt_ternary *newoption;
+
+ newoption = (relopt_ternary *)
+ allocate_reloption(kinds, RELOPT_TYPE_TERNARY, name, desc, lockmode);
+ newoption->default_val = default_val;
+
+ return newoption;
+}
+
+/*
+ * add_ternary_reloption
+ * Add a new ternary reloption
+ */
+void
+add_ternary_reloption(bits32 kinds, const char *name, const char *desc,
+ pg_ternary default_val, LOCKMODE lockmode)
+{
+ relopt_ternary *newoption;
+
+ newoption =
+ init_ternary_reloption(kinds, name, desc, default_val, lockmode);
+
+ add_reloption((relopt_gen *) newoption);
+}
+
+/*
+ * add_local_ternary_reloption
+ * Add a new ternary local reloption
+ *
+ * 'offset' is offset of ternary-typed field.
+ */
+void
+add_local_ternary_reloption(local_relopts *relopts, const char *name,
+ const char *desc, pg_ternary default_val,
+ int offset)
+{
+ relopt_ternary *newoption;
+
+ newoption =
+ init_ternary_reloption(RELOPT_KIND_LOCAL, name, desc, default_val, 0);
+
+ add_local_reloption(relopts, (relopt_gen *) newoption, offset);
+}
/*
* init_real_reloption
@@ -1626,6 +1705,19 @@ parse_one_reloption(relopt_value *option, char *text_str, int text_len,
option->gen->name, value)));
}
break;
+ case RELOPT_TYPE_TERNARY:
+ {
+ bool b;
+
+ parsed = parse_bool(value, &b);
+ option->values.ternary_val = b ? TERNARY_TRUE : TERNARY_FALSE;
+ if (validate && !parsed)
+ ereport(ERROR,
+ (errcode(ERRCODE_INVALID_PARAMETER_VALUE),
+ errmsg("invalid value for ternary option \"%s\": %s",
+ option->gen->name, value)));
+ }
+ break;
case RELOPT_TYPE_INT:
{
relopt_int *optint = (relopt_int *) option->gen;
@@ -1789,17 +1881,6 @@ fillRelOptions(void *rdopts, Size basesize,
char *itempos = ((char *) rdopts) + elems[j].offset;
char *string_val;
- /*
- * If isset_offset is provided, store whether the reloption is
- * set there.
- */
- if (elems[j].isset_offset > 0)
- {
- char *setpos = ((char *) rdopts) + elems[j].isset_offset;
-
- *(bool *) setpos = options[i].isset;
- }
-
switch (options[i].gen->type)
{
case RELOPT_TYPE_BOOL:
@@ -1807,6 +1888,11 @@ fillRelOptions(void *rdopts, Size basesize,
options[i].values.bool_val :
((relopt_bool *) options[i].gen)->default_val;
break;
+ case RELOPT_TYPE_TERNARY:
+ *(pg_ternary *) itempos = options[i].isset ?
+ options[i].values.ternary_val :
+ ((relopt_ternary *) options[i].gen)->default_val;
+ break;
case RELOPT_TYPE_INT:
*(int *) itempos = options[i].isset ?
options[i].values.int_val :
@@ -1923,8 +2009,8 @@ default_reloptions(Datum reloptions, bool validate, relopt_kind kind)
offsetof(StdRdOptions, parallel_workers)},
{"vacuum_index_cleanup", RELOPT_TYPE_ENUM,
offsetof(StdRdOptions, vacuum_index_cleanup)},
- {"vacuum_truncate", RELOPT_TYPE_BOOL,
- offsetof(StdRdOptions, vacuum_truncate), offsetof(StdRdOptions, vacuum_truncate_set)},
+ {"vacuum_truncate", RELOPT_TYPE_TERNARY,
+ offsetof(StdRdOptions, vacuum_truncate)},
{"vacuum_max_eager_freeze_failure_rate", RELOPT_TYPE_REAL,
offsetof(StdRdOptions, vacuum_max_eager_freeze_failure_rate)}
};
@@ -2004,7 +2090,6 @@ build_local_reloptions(local_relopts *relopts, Datum options, bool validate)
elems[i].optname = opt->option->name;
elems[i].opttype = opt->option->type;
elems[i].offset = opt->offset;
- elems[i].isset_offset = 0; /* not supported for local relopts yet */
i++;
}
diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c
index aa4fbec143f..696eab9bd97 100644
--- a/src/backend/commands/vacuum.c
+++ b/src/backend/commands/vacuum.c
@@ -2224,9 +2224,9 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams params,
{
StdRdOptions *opts = (StdRdOptions *) rel->rd_options;
- if (opts && opts->vacuum_truncate_set)
+ if (opts && opts->vacuum_truncate != TERNARY_UNSET)
{
- if (opts->vacuum_truncate)
+ if (opts->vacuum_truncate == TERNARY_TRUE)
params.truncate = VACOPTVALUE_ENABLED;
else
params.truncate = VACOPTVALUE_DISABLED;
diff --git a/src/include/access/reloptions.h b/src/include/access/reloptions.h
index 2f08e1b0cf0..dfbef2babf2 100644
--- a/src/include/access/reloptions.h
+++ b/src/include/access/reloptions.h
@@ -29,6 +29,7 @@
typedef enum relopt_type
{
RELOPT_TYPE_BOOL,
+ RELOPT_TYPE_TERNARY, /* on, off, unset */
RELOPT_TYPE_INT,
RELOPT_TYPE_REAL,
RELOPT_TYPE_ENUM,
@@ -80,6 +81,7 @@ typedef struct relopt_value
union
{
bool bool_val;
+ pg_ternary ternary_val;
int int_val;
double real_val;
int enum_val;
@@ -94,6 +96,12 @@ typedef struct relopt_bool
bool default_val;
} relopt_bool;
+typedef struct relopt_ternary
+{
+ relopt_gen gen;
+ int default_val;
+} relopt_ternary;
+
typedef struct relopt_int
{
relopt_gen gen;
@@ -152,19 +160,6 @@ typedef struct
const char *optname; /* option's name */
relopt_type opttype; /* option's datatype */
int offset; /* offset of field in result struct */
-
- /*
- * isset_offset is an optional offset of a field in the result struct that
- * stores whether the option is explicitly set for the relation or if it
- * just picked up the default value. In most cases, this can be
- * accomplished by giving the reloption a special out-of-range default
- * value (e.g., some integer reloptions use -2), but this isn't always
- * possible. For example, a Boolean reloption cannot be given an
- * out-of-range default, so we need another way to discover the source of
- * its value. This offset is only used if given a value greater than
- * zero.
- */
- int isset_offset;
} relopt_parse_elt;
/* Local reloption definition */
@@ -195,6 +190,9 @@ typedef struct local_relopts
extern relopt_kind add_reloption_kind(void);
extern void add_bool_reloption(bits32 kinds, const char *name, const char *desc,
bool default_val, LOCKMODE lockmode);
+extern void add_ternary_reloption(bits32 kinds, const char *name,
+ const char *desc, pg_ternary default_val,
+ LOCKMODE lockmode);
extern void add_int_reloption(bits32 kinds, const char *name, const char *desc,
int default_val, int min_val, int max_val,
LOCKMODE lockmode);
@@ -214,6 +212,9 @@ extern void register_reloptions_validator(local_relopts *relopts,
extern void add_local_bool_reloption(local_relopts *relopts, const char *name,
const char *desc, bool default_val,
int offset);
+extern void add_local_ternary_reloption(local_relopts *relopts,
+ const char *name, const char *desc,
+ pg_ternary default_val, int offset);
extern void add_local_int_reloption(local_relopts *relopts, const char *name,
const char *desc, int default_val,
int min_val, int max_val, int offset);
diff --git a/src/include/postgres.h b/src/include/postgres.h
index 7d93fbce709..47c88780776 100644
--- a/src/include/postgres.h
+++ b/src/include/postgres.h
@@ -543,6 +543,21 @@ Float8GetDatum(float8 X)
* ----------------------------------------------------------------
*/
+/*
+ * pg_ternary
+ * Boolean value with an extra "unset" value
+ *
+ * This enum can be used for values that want to distinguish between true,
+ * false, and unset.
+*/
+
+typedef enum pg_ternary
+{
+ TERNARY_FALSE = 0,
+ TERNARY_TRUE = 1,
+ TERNARY_UNSET = -1
+} pg_ternary;
+
/*
* NON_EXEC_STATIC: It's sometimes useful to define a variable or function
* that is normally static but extern when using EXEC_BACKEND (see
diff --git a/src/include/utils/rel.h b/src/include/utils/rel.h
index d03ab247788..5feb18a5373 100644
--- a/src/include/utils/rel.h
+++ b/src/include/utils/rel.h
@@ -347,8 +347,7 @@ typedef struct StdRdOptions
bool user_catalog_table; /* use as an additional catalog relation */
int parallel_workers; /* max number of parallel workers */
StdRdOptIndexCleanup vacuum_index_cleanup; /* controls index vacuuming */
- bool vacuum_truncate; /* enables vacuum to truncate a relation */
- bool vacuum_truncate_set; /* whether vacuum_truncate is set */
+ pg_ternary vacuum_truncate; /* enables vacuum to truncate a relation */
/*
* Fraction of pages in a relation that vacuum can eagerly scan and fail
diff --git a/src/test/regress/expected/reloptions.out b/src/test/regress/expected/reloptions.out
index 9de19b4e3f1..1c99f79ab01 100644
--- a/src/test/regress/expected/reloptions.out
+++ b/src/test/regress/expected/reloptions.out
@@ -98,6 +98,42 @@ SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass;
{fillfactor=13,autovacuum_enabled=false}
(1 row)
+-- Tests for future (FIXME) ternary options
+-- behave as boolean option: accept unassigned name and truncated value
+DROP TABLE reloptions_test;
+CREATE TABLE reloptions_test(i INT) WITH (vacuum_truncate);
+SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass;
+ reloptions
+------------------------
+ {vacuum_truncate=true}
+(1 row)
+
+DROP TABLE reloptions_test;
+CREATE TABLE reloptions_test(i INT) WITH (vacuum_truncate=FaLS);
+SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass;
+ reloptions
+------------------------
+ {vacuum_truncate=fals}
+(1 row)
+
+-- preferred "true" alias is used when storing
+DROP TABLE reloptions_test;
+CREATE TABLE reloptions_test(i INT) WITH (vacuum_index_cleanup=on);
+SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass;
+ reloptions
+---------------------------
+ {vacuum_index_cleanup=on}
+(1 row)
+
+-- custom "third" value is available
+DROP TABLE reloptions_test;
+CREATE TABLE reloptions_test(i INT) WITH (vacuum_index_cleanup=auto);
+SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass;
+ reloptions
+-----------------------------
+ {vacuum_index_cleanup=auto}
+(1 row)
+
-- Test vacuum_truncate option
DROP TABLE reloptions_test;
CREATE TEMP TABLE reloptions_test(i INT NOT NULL, j text)
diff --git a/src/test/regress/sql/reloptions.sql b/src/test/regress/sql/reloptions.sql
index 24fbe0b478d..f5980dafcbc 100644
--- a/src/test/regress/sql/reloptions.sql
+++ b/src/test/regress/sql/reloptions.sql
@@ -59,6 +59,27 @@ UPDATE pg_class
ALTER TABLE reloptions_test RESET (illegal_option);
SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass;
+-- Tests for future (FIXME) ternary options
+
+-- behave as boolean option: accept unassigned name and truncated value
+DROP TABLE reloptions_test;
+CREATE TABLE reloptions_test(i INT) WITH (vacuum_truncate);
+SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass;
+
+DROP TABLE reloptions_test;
+CREATE TABLE reloptions_test(i INT) WITH (vacuum_truncate=FaLS);
+SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass;
+
+-- preferred "true" alias is used when storing
+DROP TABLE reloptions_test;
+CREATE TABLE reloptions_test(i INT) WITH (vacuum_index_cleanup=on);
+SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass;
+
+-- custom "third" value is available
+DROP TABLE reloptions_test;
+CREATE TABLE reloptions_test(i INT) WITH (vacuum_index_cleanup=auto);
+SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass;
+
-- Test vacuum_truncate option
DROP TABLE reloptions_test;
diff --git a/src/tools/pgindent/typedefs.list b/src/tools/pgindent/typedefs.list
index 3f3a888fd0e..1c8610fd46c 100644
--- a/src/tools/pgindent/typedefs.list
+++ b/src/tools/pgindent/typedefs.list
@@ -3952,6 +3952,7 @@ pg_sha512_ctx
pg_snapshot
pg_special_case
pg_stack_base_t
+pg_ternary
pg_time_t
pg_time_usec_t
pg_tz
@@ -4079,6 +4080,7 @@ relopt_kind
relopt_parse_elt
relopt_real
relopt_string
+relopt_ternary
relopt_type
relopt_value
relopts_validator
--
2.47.3
--c36t3fndxhkzbl5z--
^ permalink raw reply [nested|flat] 868+ messages in thread
* [PATCH v3] Introduce ternary reloptions
@ 2026-01-16 15:04 Álvaro Herrera <[email protected]>
0 siblings, 0 replies; 868+ messages in thread
From: Álvaro Herrera @ 2026-01-16 15:04 UTC (permalink / raw)
Introduce ternary reloption as a replacement for current
`vacuum_truncate` implementation. Remove the `vacuum_truncate_set`
separate flag and use TERNARY_UNSET instead.
This could also be used for other options such as `vacuum_index_cleanup`
and `buffering`, but lets get the scaffolding in first.
Discussion: https://postgr.es/m/3474141.usfYGdeWWP@thinkpad-pgpro
---
src/backend/access/common/reloptions.c | 137 ++++++++++++++++++-----
src/backend/commands/vacuum.c | 4 +-
src/include/access/reloptions.h | 27 ++---
src/include/postgres.h | 15 +++
src/include/utils/rel.h | 3 +-
src/test/regress/expected/reloptions.out | 36 ++++++
src/test/regress/sql/reloptions.sql | 21 ++++
src/tools/pgindent/typedefs.list | 2 +
8 files changed, 202 insertions(+), 43 deletions(-)
diff --git a/src/backend/access/common/reloptions.c b/src/backend/access/common/reloptions.c
index 0b83f98ed5f..6f1f577581a 100644
--- a/src/backend/access/common/reloptions.c
+++ b/src/backend/access/common/reloptions.c
@@ -40,9 +40,9 @@
*
* To add an option:
*
- * (i) decide on a type (bool, integer, real, enum, string), name, default
- * value, upper and lower bounds (if applicable); for strings, consider a
- * validation routine.
+ * (i) decide on a type (bool, ternary, integer, real, enum, string), name,
+ * default value, upper and lower bounds (if applicable); for strings,
+ * consider a validation routine.
* (ii) add a record below (or use add_<type>_reloption).
* (iii) add it to the appropriate options struct (perhaps StdRdOptions)
* (iv) add it to the appropriate handling routine (perhaps
@@ -147,15 +147,6 @@ static relopt_bool boolRelOpts[] =
},
false
},
- {
- {
- "vacuum_truncate",
- "Enables vacuum to truncate empty pages at the end of this table",
- RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
- ShareUpdateExclusiveLock
- },
- true
- },
{
{
"deduplicate_items",
@@ -170,6 +161,25 @@ static relopt_bool boolRelOpts[] =
{{NULL}}
};
+static relopt_ternary ternaryRelOpts[] =
+{
+ {
+ {
+ "vacuum_truncate",
+ "Enables vacuum to truncate empty pages at the end of this table",
+ RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
+ ShareUpdateExclusiveLock
+ },
+ TERNARY_UNSET
+ },
+ /* list terminator */
+ {
+ {
+ NULL
+ }
+ }
+};
+
static relopt_int intRelOpts[] =
{
{
@@ -609,6 +619,13 @@ initialize_reloptions(void)
boolRelOpts[i].gen.lockmode));
j++;
}
+ for (i = 0; ternaryRelOpts[i].gen.name; i++)
+ {
+ Assert(DoLockModesConflict(ternaryRelOpts[i].gen.lockmode,
+ ternaryRelOpts[i].gen.lockmode));
+ j++;
+ }
+
for (i = 0; intRelOpts[i].gen.name; i++)
{
Assert(DoLockModesConflict(intRelOpts[i].gen.lockmode,
@@ -649,6 +666,14 @@ initialize_reloptions(void)
j++;
}
+ for (i = 0; ternaryRelOpts[i].gen.name; i++)
+ {
+ relOpts[j] = &ternaryRelOpts[i].gen;
+ relOpts[j]->type = RELOPT_TYPE_TERNARY;
+ relOpts[j]->namelen = strlen(relOpts[j]->name);
+ j++;
+ }
+
for (i = 0; intRelOpts[i].gen.name; i++)
{
relOpts[j] = &intRelOpts[i].gen;
@@ -809,6 +834,9 @@ allocate_reloption(bits32 kinds, int type, const char *name, const char *desc,
case RELOPT_TYPE_BOOL:
size = sizeof(relopt_bool);
break;
+ case RELOPT_TYPE_TERNARY:
+ size = sizeof(relopt_ternary);
+ break;
case RELOPT_TYPE_INT:
size = sizeof(relopt_int);
break;
@@ -892,6 +920,57 @@ add_local_bool_reloption(local_relopts *relopts, const char *name,
add_local_reloption(relopts, (relopt_gen *) newoption, offset);
}
+/*
+ * init_ternary_reloption
+ * Allocate and initialize a new ternary reloption
+ */
+static relopt_ternary *
+init_ternary_reloption(bits32 kinds, const char *name, const char *desc,
+ pg_ternary default_val, LOCKMODE lockmode)
+{
+ relopt_ternary *newoption;
+
+ newoption = (relopt_ternary *)
+ allocate_reloption(kinds, RELOPT_TYPE_TERNARY, name, desc, lockmode);
+ newoption->default_val = default_val;
+
+ return newoption;
+}
+
+/*
+ * add_ternary_reloption
+ * Add a new ternary reloption
+ */
+void
+add_ternary_reloption(bits32 kinds, const char *name, const char *desc,
+ pg_ternary default_val, LOCKMODE lockmode)
+{
+ relopt_ternary *newoption;
+
+ newoption =
+ init_ternary_reloption(kinds, name, desc, default_val, lockmode);
+
+ add_reloption((relopt_gen *) newoption);
+}
+
+/*
+ * add_local_ternary_reloption
+ * Add a new ternary local reloption
+ *
+ * 'offset' is offset of ternary-typed field.
+ */
+void
+add_local_ternary_reloption(local_relopts *relopts, const char *name,
+ const char *desc, pg_ternary default_val,
+ int offset)
+{
+ relopt_ternary *newoption;
+
+ newoption =
+ init_ternary_reloption(RELOPT_KIND_LOCAL, name, desc, default_val, 0);
+
+ add_local_reloption(relopts, (relopt_gen *) newoption, offset);
+}
/*
* init_real_reloption
@@ -1626,6 +1705,19 @@ parse_one_reloption(relopt_value *option, char *text_str, int text_len,
option->gen->name, value)));
}
break;
+ case RELOPT_TYPE_TERNARY:
+ {
+ bool b;
+
+ parsed = parse_bool(value, &b);
+ option->values.ternary_val = b ? TERNARY_TRUE : TERNARY_FALSE;
+ if (validate && !parsed)
+ ereport(ERROR,
+ (errcode(ERRCODE_INVALID_PARAMETER_VALUE),
+ errmsg("invalid value for ternary option \"%s\": %s",
+ option->gen->name, value)));
+ }
+ break;
case RELOPT_TYPE_INT:
{
relopt_int *optint = (relopt_int *) option->gen;
@@ -1789,17 +1881,6 @@ fillRelOptions(void *rdopts, Size basesize,
char *itempos = ((char *) rdopts) + elems[j].offset;
char *string_val;
- /*
- * If isset_offset is provided, store whether the reloption is
- * set there.
- */
- if (elems[j].isset_offset > 0)
- {
- char *setpos = ((char *) rdopts) + elems[j].isset_offset;
-
- *(bool *) setpos = options[i].isset;
- }
-
switch (options[i].gen->type)
{
case RELOPT_TYPE_BOOL:
@@ -1807,6 +1888,11 @@ fillRelOptions(void *rdopts, Size basesize,
options[i].values.bool_val :
((relopt_bool *) options[i].gen)->default_val;
break;
+ case RELOPT_TYPE_TERNARY:
+ *(pg_ternary *) itempos = options[i].isset ?
+ options[i].values.ternary_val :
+ ((relopt_ternary *) options[i].gen)->default_val;
+ break;
case RELOPT_TYPE_INT:
*(int *) itempos = options[i].isset ?
options[i].values.int_val :
@@ -1923,8 +2009,8 @@ default_reloptions(Datum reloptions, bool validate, relopt_kind kind)
offsetof(StdRdOptions, parallel_workers)},
{"vacuum_index_cleanup", RELOPT_TYPE_ENUM,
offsetof(StdRdOptions, vacuum_index_cleanup)},
- {"vacuum_truncate", RELOPT_TYPE_BOOL,
- offsetof(StdRdOptions, vacuum_truncate), offsetof(StdRdOptions, vacuum_truncate_set)},
+ {"vacuum_truncate", RELOPT_TYPE_TERNARY,
+ offsetof(StdRdOptions, vacuum_truncate)},
{"vacuum_max_eager_freeze_failure_rate", RELOPT_TYPE_REAL,
offsetof(StdRdOptions, vacuum_max_eager_freeze_failure_rate)}
};
@@ -2004,7 +2090,6 @@ build_local_reloptions(local_relopts *relopts, Datum options, bool validate)
elems[i].optname = opt->option->name;
elems[i].opttype = opt->option->type;
elems[i].offset = opt->offset;
- elems[i].isset_offset = 0; /* not supported for local relopts yet */
i++;
}
diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c
index aa4fbec143f..696eab9bd97 100644
--- a/src/backend/commands/vacuum.c
+++ b/src/backend/commands/vacuum.c
@@ -2224,9 +2224,9 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams params,
{
StdRdOptions *opts = (StdRdOptions *) rel->rd_options;
- if (opts && opts->vacuum_truncate_set)
+ if (opts && opts->vacuum_truncate != TERNARY_UNSET)
{
- if (opts->vacuum_truncate)
+ if (opts->vacuum_truncate == TERNARY_TRUE)
params.truncate = VACOPTVALUE_ENABLED;
else
params.truncate = VACOPTVALUE_DISABLED;
diff --git a/src/include/access/reloptions.h b/src/include/access/reloptions.h
index 2f08e1b0cf0..dfbef2babf2 100644
--- a/src/include/access/reloptions.h
+++ b/src/include/access/reloptions.h
@@ -29,6 +29,7 @@
typedef enum relopt_type
{
RELOPT_TYPE_BOOL,
+ RELOPT_TYPE_TERNARY, /* on, off, unset */
RELOPT_TYPE_INT,
RELOPT_TYPE_REAL,
RELOPT_TYPE_ENUM,
@@ -80,6 +81,7 @@ typedef struct relopt_value
union
{
bool bool_val;
+ pg_ternary ternary_val;
int int_val;
double real_val;
int enum_val;
@@ -94,6 +96,12 @@ typedef struct relopt_bool
bool default_val;
} relopt_bool;
+typedef struct relopt_ternary
+{
+ relopt_gen gen;
+ int default_val;
+} relopt_ternary;
+
typedef struct relopt_int
{
relopt_gen gen;
@@ -152,19 +160,6 @@ typedef struct
const char *optname; /* option's name */
relopt_type opttype; /* option's datatype */
int offset; /* offset of field in result struct */
-
- /*
- * isset_offset is an optional offset of a field in the result struct that
- * stores whether the option is explicitly set for the relation or if it
- * just picked up the default value. In most cases, this can be
- * accomplished by giving the reloption a special out-of-range default
- * value (e.g., some integer reloptions use -2), but this isn't always
- * possible. For example, a Boolean reloption cannot be given an
- * out-of-range default, so we need another way to discover the source of
- * its value. This offset is only used if given a value greater than
- * zero.
- */
- int isset_offset;
} relopt_parse_elt;
/* Local reloption definition */
@@ -195,6 +190,9 @@ typedef struct local_relopts
extern relopt_kind add_reloption_kind(void);
extern void add_bool_reloption(bits32 kinds, const char *name, const char *desc,
bool default_val, LOCKMODE lockmode);
+extern void add_ternary_reloption(bits32 kinds, const char *name,
+ const char *desc, pg_ternary default_val,
+ LOCKMODE lockmode);
extern void add_int_reloption(bits32 kinds, const char *name, const char *desc,
int default_val, int min_val, int max_val,
LOCKMODE lockmode);
@@ -214,6 +212,9 @@ extern void register_reloptions_validator(local_relopts *relopts,
extern void add_local_bool_reloption(local_relopts *relopts, const char *name,
const char *desc, bool default_val,
int offset);
+extern void add_local_ternary_reloption(local_relopts *relopts,
+ const char *name, const char *desc,
+ pg_ternary default_val, int offset);
extern void add_local_int_reloption(local_relopts *relopts, const char *name,
const char *desc, int default_val,
int min_val, int max_val, int offset);
diff --git a/src/include/postgres.h b/src/include/postgres.h
index 7d93fbce709..47c88780776 100644
--- a/src/include/postgres.h
+++ b/src/include/postgres.h
@@ -543,6 +543,21 @@ Float8GetDatum(float8 X)
* ----------------------------------------------------------------
*/
+/*
+ * pg_ternary
+ * Boolean value with an extra "unset" value
+ *
+ * This enum can be used for values that want to distinguish between true,
+ * false, and unset.
+*/
+
+typedef enum pg_ternary
+{
+ TERNARY_FALSE = 0,
+ TERNARY_TRUE = 1,
+ TERNARY_UNSET = -1
+} pg_ternary;
+
/*
* NON_EXEC_STATIC: It's sometimes useful to define a variable or function
* that is normally static but extern when using EXEC_BACKEND (see
diff --git a/src/include/utils/rel.h b/src/include/utils/rel.h
index d03ab247788..5feb18a5373 100644
--- a/src/include/utils/rel.h
+++ b/src/include/utils/rel.h
@@ -347,8 +347,7 @@ typedef struct StdRdOptions
bool user_catalog_table; /* use as an additional catalog relation */
int parallel_workers; /* max number of parallel workers */
StdRdOptIndexCleanup vacuum_index_cleanup; /* controls index vacuuming */
- bool vacuum_truncate; /* enables vacuum to truncate a relation */
- bool vacuum_truncate_set; /* whether vacuum_truncate is set */
+ pg_ternary vacuum_truncate; /* enables vacuum to truncate a relation */
/*
* Fraction of pages in a relation that vacuum can eagerly scan and fail
diff --git a/src/test/regress/expected/reloptions.out b/src/test/regress/expected/reloptions.out
index 9de19b4e3f1..1c99f79ab01 100644
--- a/src/test/regress/expected/reloptions.out
+++ b/src/test/regress/expected/reloptions.out
@@ -98,6 +98,42 @@ SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass;
{fillfactor=13,autovacuum_enabled=false}
(1 row)
+-- Tests for future (FIXME) ternary options
+-- behave as boolean option: accept unassigned name and truncated value
+DROP TABLE reloptions_test;
+CREATE TABLE reloptions_test(i INT) WITH (vacuum_truncate);
+SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass;
+ reloptions
+------------------------
+ {vacuum_truncate=true}
+(1 row)
+
+DROP TABLE reloptions_test;
+CREATE TABLE reloptions_test(i INT) WITH (vacuum_truncate=FaLS);
+SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass;
+ reloptions
+------------------------
+ {vacuum_truncate=fals}
+(1 row)
+
+-- preferred "true" alias is used when storing
+DROP TABLE reloptions_test;
+CREATE TABLE reloptions_test(i INT) WITH (vacuum_index_cleanup=on);
+SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass;
+ reloptions
+---------------------------
+ {vacuum_index_cleanup=on}
+(1 row)
+
+-- custom "third" value is available
+DROP TABLE reloptions_test;
+CREATE TABLE reloptions_test(i INT) WITH (vacuum_index_cleanup=auto);
+SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass;
+ reloptions
+-----------------------------
+ {vacuum_index_cleanup=auto}
+(1 row)
+
-- Test vacuum_truncate option
DROP TABLE reloptions_test;
CREATE TEMP TABLE reloptions_test(i INT NOT NULL, j text)
diff --git a/src/test/regress/sql/reloptions.sql b/src/test/regress/sql/reloptions.sql
index 24fbe0b478d..f5980dafcbc 100644
--- a/src/test/regress/sql/reloptions.sql
+++ b/src/test/regress/sql/reloptions.sql
@@ -59,6 +59,27 @@ UPDATE pg_class
ALTER TABLE reloptions_test RESET (illegal_option);
SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass;
+-- Tests for future (FIXME) ternary options
+
+-- behave as boolean option: accept unassigned name and truncated value
+DROP TABLE reloptions_test;
+CREATE TABLE reloptions_test(i INT) WITH (vacuum_truncate);
+SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass;
+
+DROP TABLE reloptions_test;
+CREATE TABLE reloptions_test(i INT) WITH (vacuum_truncate=FaLS);
+SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass;
+
+-- preferred "true" alias is used when storing
+DROP TABLE reloptions_test;
+CREATE TABLE reloptions_test(i INT) WITH (vacuum_index_cleanup=on);
+SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass;
+
+-- custom "third" value is available
+DROP TABLE reloptions_test;
+CREATE TABLE reloptions_test(i INT) WITH (vacuum_index_cleanup=auto);
+SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass;
+
-- Test vacuum_truncate option
DROP TABLE reloptions_test;
diff --git a/src/tools/pgindent/typedefs.list b/src/tools/pgindent/typedefs.list
index 3f3a888fd0e..1c8610fd46c 100644
--- a/src/tools/pgindent/typedefs.list
+++ b/src/tools/pgindent/typedefs.list
@@ -3952,6 +3952,7 @@ pg_sha512_ctx
pg_snapshot
pg_special_case
pg_stack_base_t
+pg_ternary
pg_time_t
pg_time_usec_t
pg_tz
@@ -4079,6 +4080,7 @@ relopt_kind
relopt_parse_elt
relopt_real
relopt_string
+relopt_ternary
relopt_type
relopt_value
relopts_validator
--
2.47.3
--c36t3fndxhkzbl5z--
^ permalink raw reply [nested|flat] 868+ messages in thread
* [PATCH v3] Introduce ternary reloptions
@ 2026-01-16 15:04 Álvaro Herrera <[email protected]>
0 siblings, 0 replies; 868+ messages in thread
From: Álvaro Herrera @ 2026-01-16 15:04 UTC (permalink / raw)
Introduce ternary reloption as a replacement for current
`vacuum_truncate` implementation. Remove the `vacuum_truncate_set`
separate flag and use TERNARY_UNSET instead.
This could also be used for other options such as `vacuum_index_cleanup`
and `buffering`, but lets get the scaffolding in first.
Discussion: https://postgr.es/m/3474141.usfYGdeWWP@thinkpad-pgpro
---
src/backend/access/common/reloptions.c | 137 ++++++++++++++++++-----
src/backend/commands/vacuum.c | 4 +-
src/include/access/reloptions.h | 27 ++---
src/include/postgres.h | 15 +++
src/include/utils/rel.h | 3 +-
src/test/regress/expected/reloptions.out | 36 ++++++
src/test/regress/sql/reloptions.sql | 21 ++++
src/tools/pgindent/typedefs.list | 2 +
8 files changed, 202 insertions(+), 43 deletions(-)
diff --git a/src/backend/access/common/reloptions.c b/src/backend/access/common/reloptions.c
index 0b83f98ed5f..6f1f577581a 100644
--- a/src/backend/access/common/reloptions.c
+++ b/src/backend/access/common/reloptions.c
@@ -40,9 +40,9 @@
*
* To add an option:
*
- * (i) decide on a type (bool, integer, real, enum, string), name, default
- * value, upper and lower bounds (if applicable); for strings, consider a
- * validation routine.
+ * (i) decide on a type (bool, ternary, integer, real, enum, string), name,
+ * default value, upper and lower bounds (if applicable); for strings,
+ * consider a validation routine.
* (ii) add a record below (or use add_<type>_reloption).
* (iii) add it to the appropriate options struct (perhaps StdRdOptions)
* (iv) add it to the appropriate handling routine (perhaps
@@ -147,15 +147,6 @@ static relopt_bool boolRelOpts[] =
},
false
},
- {
- {
- "vacuum_truncate",
- "Enables vacuum to truncate empty pages at the end of this table",
- RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
- ShareUpdateExclusiveLock
- },
- true
- },
{
{
"deduplicate_items",
@@ -170,6 +161,25 @@ static relopt_bool boolRelOpts[] =
{{NULL}}
};
+static relopt_ternary ternaryRelOpts[] =
+{
+ {
+ {
+ "vacuum_truncate",
+ "Enables vacuum to truncate empty pages at the end of this table",
+ RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
+ ShareUpdateExclusiveLock
+ },
+ TERNARY_UNSET
+ },
+ /* list terminator */
+ {
+ {
+ NULL
+ }
+ }
+};
+
static relopt_int intRelOpts[] =
{
{
@@ -609,6 +619,13 @@ initialize_reloptions(void)
boolRelOpts[i].gen.lockmode));
j++;
}
+ for (i = 0; ternaryRelOpts[i].gen.name; i++)
+ {
+ Assert(DoLockModesConflict(ternaryRelOpts[i].gen.lockmode,
+ ternaryRelOpts[i].gen.lockmode));
+ j++;
+ }
+
for (i = 0; intRelOpts[i].gen.name; i++)
{
Assert(DoLockModesConflict(intRelOpts[i].gen.lockmode,
@@ -649,6 +666,14 @@ initialize_reloptions(void)
j++;
}
+ for (i = 0; ternaryRelOpts[i].gen.name; i++)
+ {
+ relOpts[j] = &ternaryRelOpts[i].gen;
+ relOpts[j]->type = RELOPT_TYPE_TERNARY;
+ relOpts[j]->namelen = strlen(relOpts[j]->name);
+ j++;
+ }
+
for (i = 0; intRelOpts[i].gen.name; i++)
{
relOpts[j] = &intRelOpts[i].gen;
@@ -809,6 +834,9 @@ allocate_reloption(bits32 kinds, int type, const char *name, const char *desc,
case RELOPT_TYPE_BOOL:
size = sizeof(relopt_bool);
break;
+ case RELOPT_TYPE_TERNARY:
+ size = sizeof(relopt_ternary);
+ break;
case RELOPT_TYPE_INT:
size = sizeof(relopt_int);
break;
@@ -892,6 +920,57 @@ add_local_bool_reloption(local_relopts *relopts, const char *name,
add_local_reloption(relopts, (relopt_gen *) newoption, offset);
}
+/*
+ * init_ternary_reloption
+ * Allocate and initialize a new ternary reloption
+ */
+static relopt_ternary *
+init_ternary_reloption(bits32 kinds, const char *name, const char *desc,
+ pg_ternary default_val, LOCKMODE lockmode)
+{
+ relopt_ternary *newoption;
+
+ newoption = (relopt_ternary *)
+ allocate_reloption(kinds, RELOPT_TYPE_TERNARY, name, desc, lockmode);
+ newoption->default_val = default_val;
+
+ return newoption;
+}
+
+/*
+ * add_ternary_reloption
+ * Add a new ternary reloption
+ */
+void
+add_ternary_reloption(bits32 kinds, const char *name, const char *desc,
+ pg_ternary default_val, LOCKMODE lockmode)
+{
+ relopt_ternary *newoption;
+
+ newoption =
+ init_ternary_reloption(kinds, name, desc, default_val, lockmode);
+
+ add_reloption((relopt_gen *) newoption);
+}
+
+/*
+ * add_local_ternary_reloption
+ * Add a new ternary local reloption
+ *
+ * 'offset' is offset of ternary-typed field.
+ */
+void
+add_local_ternary_reloption(local_relopts *relopts, const char *name,
+ const char *desc, pg_ternary default_val,
+ int offset)
+{
+ relopt_ternary *newoption;
+
+ newoption =
+ init_ternary_reloption(RELOPT_KIND_LOCAL, name, desc, default_val, 0);
+
+ add_local_reloption(relopts, (relopt_gen *) newoption, offset);
+}
/*
* init_real_reloption
@@ -1626,6 +1705,19 @@ parse_one_reloption(relopt_value *option, char *text_str, int text_len,
option->gen->name, value)));
}
break;
+ case RELOPT_TYPE_TERNARY:
+ {
+ bool b;
+
+ parsed = parse_bool(value, &b);
+ option->values.ternary_val = b ? TERNARY_TRUE : TERNARY_FALSE;
+ if (validate && !parsed)
+ ereport(ERROR,
+ (errcode(ERRCODE_INVALID_PARAMETER_VALUE),
+ errmsg("invalid value for ternary option \"%s\": %s",
+ option->gen->name, value)));
+ }
+ break;
case RELOPT_TYPE_INT:
{
relopt_int *optint = (relopt_int *) option->gen;
@@ -1789,17 +1881,6 @@ fillRelOptions(void *rdopts, Size basesize,
char *itempos = ((char *) rdopts) + elems[j].offset;
char *string_val;
- /*
- * If isset_offset is provided, store whether the reloption is
- * set there.
- */
- if (elems[j].isset_offset > 0)
- {
- char *setpos = ((char *) rdopts) + elems[j].isset_offset;
-
- *(bool *) setpos = options[i].isset;
- }
-
switch (options[i].gen->type)
{
case RELOPT_TYPE_BOOL:
@@ -1807,6 +1888,11 @@ fillRelOptions(void *rdopts, Size basesize,
options[i].values.bool_val :
((relopt_bool *) options[i].gen)->default_val;
break;
+ case RELOPT_TYPE_TERNARY:
+ *(pg_ternary *) itempos = options[i].isset ?
+ options[i].values.ternary_val :
+ ((relopt_ternary *) options[i].gen)->default_val;
+ break;
case RELOPT_TYPE_INT:
*(int *) itempos = options[i].isset ?
options[i].values.int_val :
@@ -1923,8 +2009,8 @@ default_reloptions(Datum reloptions, bool validate, relopt_kind kind)
offsetof(StdRdOptions, parallel_workers)},
{"vacuum_index_cleanup", RELOPT_TYPE_ENUM,
offsetof(StdRdOptions, vacuum_index_cleanup)},
- {"vacuum_truncate", RELOPT_TYPE_BOOL,
- offsetof(StdRdOptions, vacuum_truncate), offsetof(StdRdOptions, vacuum_truncate_set)},
+ {"vacuum_truncate", RELOPT_TYPE_TERNARY,
+ offsetof(StdRdOptions, vacuum_truncate)},
{"vacuum_max_eager_freeze_failure_rate", RELOPT_TYPE_REAL,
offsetof(StdRdOptions, vacuum_max_eager_freeze_failure_rate)}
};
@@ -2004,7 +2090,6 @@ build_local_reloptions(local_relopts *relopts, Datum options, bool validate)
elems[i].optname = opt->option->name;
elems[i].opttype = opt->option->type;
elems[i].offset = opt->offset;
- elems[i].isset_offset = 0; /* not supported for local relopts yet */
i++;
}
diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c
index aa4fbec143f..696eab9bd97 100644
--- a/src/backend/commands/vacuum.c
+++ b/src/backend/commands/vacuum.c
@@ -2224,9 +2224,9 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams params,
{
StdRdOptions *opts = (StdRdOptions *) rel->rd_options;
- if (opts && opts->vacuum_truncate_set)
+ if (opts && opts->vacuum_truncate != TERNARY_UNSET)
{
- if (opts->vacuum_truncate)
+ if (opts->vacuum_truncate == TERNARY_TRUE)
params.truncate = VACOPTVALUE_ENABLED;
else
params.truncate = VACOPTVALUE_DISABLED;
diff --git a/src/include/access/reloptions.h b/src/include/access/reloptions.h
index 2f08e1b0cf0..dfbef2babf2 100644
--- a/src/include/access/reloptions.h
+++ b/src/include/access/reloptions.h
@@ -29,6 +29,7 @@
typedef enum relopt_type
{
RELOPT_TYPE_BOOL,
+ RELOPT_TYPE_TERNARY, /* on, off, unset */
RELOPT_TYPE_INT,
RELOPT_TYPE_REAL,
RELOPT_TYPE_ENUM,
@@ -80,6 +81,7 @@ typedef struct relopt_value
union
{
bool bool_val;
+ pg_ternary ternary_val;
int int_val;
double real_val;
int enum_val;
@@ -94,6 +96,12 @@ typedef struct relopt_bool
bool default_val;
} relopt_bool;
+typedef struct relopt_ternary
+{
+ relopt_gen gen;
+ int default_val;
+} relopt_ternary;
+
typedef struct relopt_int
{
relopt_gen gen;
@@ -152,19 +160,6 @@ typedef struct
const char *optname; /* option's name */
relopt_type opttype; /* option's datatype */
int offset; /* offset of field in result struct */
-
- /*
- * isset_offset is an optional offset of a field in the result struct that
- * stores whether the option is explicitly set for the relation or if it
- * just picked up the default value. In most cases, this can be
- * accomplished by giving the reloption a special out-of-range default
- * value (e.g., some integer reloptions use -2), but this isn't always
- * possible. For example, a Boolean reloption cannot be given an
- * out-of-range default, so we need another way to discover the source of
- * its value. This offset is only used if given a value greater than
- * zero.
- */
- int isset_offset;
} relopt_parse_elt;
/* Local reloption definition */
@@ -195,6 +190,9 @@ typedef struct local_relopts
extern relopt_kind add_reloption_kind(void);
extern void add_bool_reloption(bits32 kinds, const char *name, const char *desc,
bool default_val, LOCKMODE lockmode);
+extern void add_ternary_reloption(bits32 kinds, const char *name,
+ const char *desc, pg_ternary default_val,
+ LOCKMODE lockmode);
extern void add_int_reloption(bits32 kinds, const char *name, const char *desc,
int default_val, int min_val, int max_val,
LOCKMODE lockmode);
@@ -214,6 +212,9 @@ extern void register_reloptions_validator(local_relopts *relopts,
extern void add_local_bool_reloption(local_relopts *relopts, const char *name,
const char *desc, bool default_val,
int offset);
+extern void add_local_ternary_reloption(local_relopts *relopts,
+ const char *name, const char *desc,
+ pg_ternary default_val, int offset);
extern void add_local_int_reloption(local_relopts *relopts, const char *name,
const char *desc, int default_val,
int min_val, int max_val, int offset);
diff --git a/src/include/postgres.h b/src/include/postgres.h
index 7d93fbce709..47c88780776 100644
--- a/src/include/postgres.h
+++ b/src/include/postgres.h
@@ -543,6 +543,21 @@ Float8GetDatum(float8 X)
* ----------------------------------------------------------------
*/
+/*
+ * pg_ternary
+ * Boolean value with an extra "unset" value
+ *
+ * This enum can be used for values that want to distinguish between true,
+ * false, and unset.
+*/
+
+typedef enum pg_ternary
+{
+ TERNARY_FALSE = 0,
+ TERNARY_TRUE = 1,
+ TERNARY_UNSET = -1
+} pg_ternary;
+
/*
* NON_EXEC_STATIC: It's sometimes useful to define a variable or function
* that is normally static but extern when using EXEC_BACKEND (see
diff --git a/src/include/utils/rel.h b/src/include/utils/rel.h
index d03ab247788..5feb18a5373 100644
--- a/src/include/utils/rel.h
+++ b/src/include/utils/rel.h
@@ -347,8 +347,7 @@ typedef struct StdRdOptions
bool user_catalog_table; /* use as an additional catalog relation */
int parallel_workers; /* max number of parallel workers */
StdRdOptIndexCleanup vacuum_index_cleanup; /* controls index vacuuming */
- bool vacuum_truncate; /* enables vacuum to truncate a relation */
- bool vacuum_truncate_set; /* whether vacuum_truncate is set */
+ pg_ternary vacuum_truncate; /* enables vacuum to truncate a relation */
/*
* Fraction of pages in a relation that vacuum can eagerly scan and fail
diff --git a/src/test/regress/expected/reloptions.out b/src/test/regress/expected/reloptions.out
index 9de19b4e3f1..1c99f79ab01 100644
--- a/src/test/regress/expected/reloptions.out
+++ b/src/test/regress/expected/reloptions.out
@@ -98,6 +98,42 @@ SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass;
{fillfactor=13,autovacuum_enabled=false}
(1 row)
+-- Tests for future (FIXME) ternary options
+-- behave as boolean option: accept unassigned name and truncated value
+DROP TABLE reloptions_test;
+CREATE TABLE reloptions_test(i INT) WITH (vacuum_truncate);
+SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass;
+ reloptions
+------------------------
+ {vacuum_truncate=true}
+(1 row)
+
+DROP TABLE reloptions_test;
+CREATE TABLE reloptions_test(i INT) WITH (vacuum_truncate=FaLS);
+SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass;
+ reloptions
+------------------------
+ {vacuum_truncate=fals}
+(1 row)
+
+-- preferred "true" alias is used when storing
+DROP TABLE reloptions_test;
+CREATE TABLE reloptions_test(i INT) WITH (vacuum_index_cleanup=on);
+SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass;
+ reloptions
+---------------------------
+ {vacuum_index_cleanup=on}
+(1 row)
+
+-- custom "third" value is available
+DROP TABLE reloptions_test;
+CREATE TABLE reloptions_test(i INT) WITH (vacuum_index_cleanup=auto);
+SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass;
+ reloptions
+-----------------------------
+ {vacuum_index_cleanup=auto}
+(1 row)
+
-- Test vacuum_truncate option
DROP TABLE reloptions_test;
CREATE TEMP TABLE reloptions_test(i INT NOT NULL, j text)
diff --git a/src/test/regress/sql/reloptions.sql b/src/test/regress/sql/reloptions.sql
index 24fbe0b478d..f5980dafcbc 100644
--- a/src/test/regress/sql/reloptions.sql
+++ b/src/test/regress/sql/reloptions.sql
@@ -59,6 +59,27 @@ UPDATE pg_class
ALTER TABLE reloptions_test RESET (illegal_option);
SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass;
+-- Tests for future (FIXME) ternary options
+
+-- behave as boolean option: accept unassigned name and truncated value
+DROP TABLE reloptions_test;
+CREATE TABLE reloptions_test(i INT) WITH (vacuum_truncate);
+SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass;
+
+DROP TABLE reloptions_test;
+CREATE TABLE reloptions_test(i INT) WITH (vacuum_truncate=FaLS);
+SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass;
+
+-- preferred "true" alias is used when storing
+DROP TABLE reloptions_test;
+CREATE TABLE reloptions_test(i INT) WITH (vacuum_index_cleanup=on);
+SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass;
+
+-- custom "third" value is available
+DROP TABLE reloptions_test;
+CREATE TABLE reloptions_test(i INT) WITH (vacuum_index_cleanup=auto);
+SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass;
+
-- Test vacuum_truncate option
DROP TABLE reloptions_test;
diff --git a/src/tools/pgindent/typedefs.list b/src/tools/pgindent/typedefs.list
index 3f3a888fd0e..1c8610fd46c 100644
--- a/src/tools/pgindent/typedefs.list
+++ b/src/tools/pgindent/typedefs.list
@@ -3952,6 +3952,7 @@ pg_sha512_ctx
pg_snapshot
pg_special_case
pg_stack_base_t
+pg_ternary
pg_time_t
pg_time_usec_t
pg_tz
@@ -4079,6 +4080,7 @@ relopt_kind
relopt_parse_elt
relopt_real
relopt_string
+relopt_ternary
relopt_type
relopt_value
relopts_validator
--
2.47.3
--c36t3fndxhkzbl5z--
^ permalink raw reply [nested|flat] 868+ messages in thread
* [PATCH v3] Introduce ternary reloptions
@ 2026-01-16 15:04 Álvaro Herrera <[email protected]>
0 siblings, 0 replies; 868+ messages in thread
From: Álvaro Herrera @ 2026-01-16 15:04 UTC (permalink / raw)
Introduce ternary reloption as a replacement for current
`vacuum_truncate` implementation. Remove the `vacuum_truncate_set`
separate flag and use TERNARY_UNSET instead.
This could also be used for other options such as `vacuum_index_cleanup`
and `buffering`, but lets get the scaffolding in first.
Discussion: https://postgr.es/m/3474141.usfYGdeWWP@thinkpad-pgpro
---
src/backend/access/common/reloptions.c | 137 ++++++++++++++++++-----
src/backend/commands/vacuum.c | 4 +-
src/include/access/reloptions.h | 27 ++---
src/include/postgres.h | 15 +++
src/include/utils/rel.h | 3 +-
src/test/regress/expected/reloptions.out | 36 ++++++
src/test/regress/sql/reloptions.sql | 21 ++++
src/tools/pgindent/typedefs.list | 2 +
8 files changed, 202 insertions(+), 43 deletions(-)
diff --git a/src/backend/access/common/reloptions.c b/src/backend/access/common/reloptions.c
index 0b83f98ed5f..6f1f577581a 100644
--- a/src/backend/access/common/reloptions.c
+++ b/src/backend/access/common/reloptions.c
@@ -40,9 +40,9 @@
*
* To add an option:
*
- * (i) decide on a type (bool, integer, real, enum, string), name, default
- * value, upper and lower bounds (if applicable); for strings, consider a
- * validation routine.
+ * (i) decide on a type (bool, ternary, integer, real, enum, string), name,
+ * default value, upper and lower bounds (if applicable); for strings,
+ * consider a validation routine.
* (ii) add a record below (or use add_<type>_reloption).
* (iii) add it to the appropriate options struct (perhaps StdRdOptions)
* (iv) add it to the appropriate handling routine (perhaps
@@ -147,15 +147,6 @@ static relopt_bool boolRelOpts[] =
},
false
},
- {
- {
- "vacuum_truncate",
- "Enables vacuum to truncate empty pages at the end of this table",
- RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
- ShareUpdateExclusiveLock
- },
- true
- },
{
{
"deduplicate_items",
@@ -170,6 +161,25 @@ static relopt_bool boolRelOpts[] =
{{NULL}}
};
+static relopt_ternary ternaryRelOpts[] =
+{
+ {
+ {
+ "vacuum_truncate",
+ "Enables vacuum to truncate empty pages at the end of this table",
+ RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
+ ShareUpdateExclusiveLock
+ },
+ TERNARY_UNSET
+ },
+ /* list terminator */
+ {
+ {
+ NULL
+ }
+ }
+};
+
static relopt_int intRelOpts[] =
{
{
@@ -609,6 +619,13 @@ initialize_reloptions(void)
boolRelOpts[i].gen.lockmode));
j++;
}
+ for (i = 0; ternaryRelOpts[i].gen.name; i++)
+ {
+ Assert(DoLockModesConflict(ternaryRelOpts[i].gen.lockmode,
+ ternaryRelOpts[i].gen.lockmode));
+ j++;
+ }
+
for (i = 0; intRelOpts[i].gen.name; i++)
{
Assert(DoLockModesConflict(intRelOpts[i].gen.lockmode,
@@ -649,6 +666,14 @@ initialize_reloptions(void)
j++;
}
+ for (i = 0; ternaryRelOpts[i].gen.name; i++)
+ {
+ relOpts[j] = &ternaryRelOpts[i].gen;
+ relOpts[j]->type = RELOPT_TYPE_TERNARY;
+ relOpts[j]->namelen = strlen(relOpts[j]->name);
+ j++;
+ }
+
for (i = 0; intRelOpts[i].gen.name; i++)
{
relOpts[j] = &intRelOpts[i].gen;
@@ -809,6 +834,9 @@ allocate_reloption(bits32 kinds, int type, const char *name, const char *desc,
case RELOPT_TYPE_BOOL:
size = sizeof(relopt_bool);
break;
+ case RELOPT_TYPE_TERNARY:
+ size = sizeof(relopt_ternary);
+ break;
case RELOPT_TYPE_INT:
size = sizeof(relopt_int);
break;
@@ -892,6 +920,57 @@ add_local_bool_reloption(local_relopts *relopts, const char *name,
add_local_reloption(relopts, (relopt_gen *) newoption, offset);
}
+/*
+ * init_ternary_reloption
+ * Allocate and initialize a new ternary reloption
+ */
+static relopt_ternary *
+init_ternary_reloption(bits32 kinds, const char *name, const char *desc,
+ pg_ternary default_val, LOCKMODE lockmode)
+{
+ relopt_ternary *newoption;
+
+ newoption = (relopt_ternary *)
+ allocate_reloption(kinds, RELOPT_TYPE_TERNARY, name, desc, lockmode);
+ newoption->default_val = default_val;
+
+ return newoption;
+}
+
+/*
+ * add_ternary_reloption
+ * Add a new ternary reloption
+ */
+void
+add_ternary_reloption(bits32 kinds, const char *name, const char *desc,
+ pg_ternary default_val, LOCKMODE lockmode)
+{
+ relopt_ternary *newoption;
+
+ newoption =
+ init_ternary_reloption(kinds, name, desc, default_val, lockmode);
+
+ add_reloption((relopt_gen *) newoption);
+}
+
+/*
+ * add_local_ternary_reloption
+ * Add a new ternary local reloption
+ *
+ * 'offset' is offset of ternary-typed field.
+ */
+void
+add_local_ternary_reloption(local_relopts *relopts, const char *name,
+ const char *desc, pg_ternary default_val,
+ int offset)
+{
+ relopt_ternary *newoption;
+
+ newoption =
+ init_ternary_reloption(RELOPT_KIND_LOCAL, name, desc, default_val, 0);
+
+ add_local_reloption(relopts, (relopt_gen *) newoption, offset);
+}
/*
* init_real_reloption
@@ -1626,6 +1705,19 @@ parse_one_reloption(relopt_value *option, char *text_str, int text_len,
option->gen->name, value)));
}
break;
+ case RELOPT_TYPE_TERNARY:
+ {
+ bool b;
+
+ parsed = parse_bool(value, &b);
+ option->values.ternary_val = b ? TERNARY_TRUE : TERNARY_FALSE;
+ if (validate && !parsed)
+ ereport(ERROR,
+ (errcode(ERRCODE_INVALID_PARAMETER_VALUE),
+ errmsg("invalid value for ternary option \"%s\": %s",
+ option->gen->name, value)));
+ }
+ break;
case RELOPT_TYPE_INT:
{
relopt_int *optint = (relopt_int *) option->gen;
@@ -1789,17 +1881,6 @@ fillRelOptions(void *rdopts, Size basesize,
char *itempos = ((char *) rdopts) + elems[j].offset;
char *string_val;
- /*
- * If isset_offset is provided, store whether the reloption is
- * set there.
- */
- if (elems[j].isset_offset > 0)
- {
- char *setpos = ((char *) rdopts) + elems[j].isset_offset;
-
- *(bool *) setpos = options[i].isset;
- }
-
switch (options[i].gen->type)
{
case RELOPT_TYPE_BOOL:
@@ -1807,6 +1888,11 @@ fillRelOptions(void *rdopts, Size basesize,
options[i].values.bool_val :
((relopt_bool *) options[i].gen)->default_val;
break;
+ case RELOPT_TYPE_TERNARY:
+ *(pg_ternary *) itempos = options[i].isset ?
+ options[i].values.ternary_val :
+ ((relopt_ternary *) options[i].gen)->default_val;
+ break;
case RELOPT_TYPE_INT:
*(int *) itempos = options[i].isset ?
options[i].values.int_val :
@@ -1923,8 +2009,8 @@ default_reloptions(Datum reloptions, bool validate, relopt_kind kind)
offsetof(StdRdOptions, parallel_workers)},
{"vacuum_index_cleanup", RELOPT_TYPE_ENUM,
offsetof(StdRdOptions, vacuum_index_cleanup)},
- {"vacuum_truncate", RELOPT_TYPE_BOOL,
- offsetof(StdRdOptions, vacuum_truncate), offsetof(StdRdOptions, vacuum_truncate_set)},
+ {"vacuum_truncate", RELOPT_TYPE_TERNARY,
+ offsetof(StdRdOptions, vacuum_truncate)},
{"vacuum_max_eager_freeze_failure_rate", RELOPT_TYPE_REAL,
offsetof(StdRdOptions, vacuum_max_eager_freeze_failure_rate)}
};
@@ -2004,7 +2090,6 @@ build_local_reloptions(local_relopts *relopts, Datum options, bool validate)
elems[i].optname = opt->option->name;
elems[i].opttype = opt->option->type;
elems[i].offset = opt->offset;
- elems[i].isset_offset = 0; /* not supported for local relopts yet */
i++;
}
diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c
index aa4fbec143f..696eab9bd97 100644
--- a/src/backend/commands/vacuum.c
+++ b/src/backend/commands/vacuum.c
@@ -2224,9 +2224,9 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams params,
{
StdRdOptions *opts = (StdRdOptions *) rel->rd_options;
- if (opts && opts->vacuum_truncate_set)
+ if (opts && opts->vacuum_truncate != TERNARY_UNSET)
{
- if (opts->vacuum_truncate)
+ if (opts->vacuum_truncate == TERNARY_TRUE)
params.truncate = VACOPTVALUE_ENABLED;
else
params.truncate = VACOPTVALUE_DISABLED;
diff --git a/src/include/access/reloptions.h b/src/include/access/reloptions.h
index 2f08e1b0cf0..dfbef2babf2 100644
--- a/src/include/access/reloptions.h
+++ b/src/include/access/reloptions.h
@@ -29,6 +29,7 @@
typedef enum relopt_type
{
RELOPT_TYPE_BOOL,
+ RELOPT_TYPE_TERNARY, /* on, off, unset */
RELOPT_TYPE_INT,
RELOPT_TYPE_REAL,
RELOPT_TYPE_ENUM,
@@ -80,6 +81,7 @@ typedef struct relopt_value
union
{
bool bool_val;
+ pg_ternary ternary_val;
int int_val;
double real_val;
int enum_val;
@@ -94,6 +96,12 @@ typedef struct relopt_bool
bool default_val;
} relopt_bool;
+typedef struct relopt_ternary
+{
+ relopt_gen gen;
+ int default_val;
+} relopt_ternary;
+
typedef struct relopt_int
{
relopt_gen gen;
@@ -152,19 +160,6 @@ typedef struct
const char *optname; /* option's name */
relopt_type opttype; /* option's datatype */
int offset; /* offset of field in result struct */
-
- /*
- * isset_offset is an optional offset of a field in the result struct that
- * stores whether the option is explicitly set for the relation or if it
- * just picked up the default value. In most cases, this can be
- * accomplished by giving the reloption a special out-of-range default
- * value (e.g., some integer reloptions use -2), but this isn't always
- * possible. For example, a Boolean reloption cannot be given an
- * out-of-range default, so we need another way to discover the source of
- * its value. This offset is only used if given a value greater than
- * zero.
- */
- int isset_offset;
} relopt_parse_elt;
/* Local reloption definition */
@@ -195,6 +190,9 @@ typedef struct local_relopts
extern relopt_kind add_reloption_kind(void);
extern void add_bool_reloption(bits32 kinds, const char *name, const char *desc,
bool default_val, LOCKMODE lockmode);
+extern void add_ternary_reloption(bits32 kinds, const char *name,
+ const char *desc, pg_ternary default_val,
+ LOCKMODE lockmode);
extern void add_int_reloption(bits32 kinds, const char *name, const char *desc,
int default_val, int min_val, int max_val,
LOCKMODE lockmode);
@@ -214,6 +212,9 @@ extern void register_reloptions_validator(local_relopts *relopts,
extern void add_local_bool_reloption(local_relopts *relopts, const char *name,
const char *desc, bool default_val,
int offset);
+extern void add_local_ternary_reloption(local_relopts *relopts,
+ const char *name, const char *desc,
+ pg_ternary default_val, int offset);
extern void add_local_int_reloption(local_relopts *relopts, const char *name,
const char *desc, int default_val,
int min_val, int max_val, int offset);
diff --git a/src/include/postgres.h b/src/include/postgres.h
index 7d93fbce709..47c88780776 100644
--- a/src/include/postgres.h
+++ b/src/include/postgres.h
@@ -543,6 +543,21 @@ Float8GetDatum(float8 X)
* ----------------------------------------------------------------
*/
+/*
+ * pg_ternary
+ * Boolean value with an extra "unset" value
+ *
+ * This enum can be used for values that want to distinguish between true,
+ * false, and unset.
+*/
+
+typedef enum pg_ternary
+{
+ TERNARY_FALSE = 0,
+ TERNARY_TRUE = 1,
+ TERNARY_UNSET = -1
+} pg_ternary;
+
/*
* NON_EXEC_STATIC: It's sometimes useful to define a variable or function
* that is normally static but extern when using EXEC_BACKEND (see
diff --git a/src/include/utils/rel.h b/src/include/utils/rel.h
index d03ab247788..5feb18a5373 100644
--- a/src/include/utils/rel.h
+++ b/src/include/utils/rel.h
@@ -347,8 +347,7 @@ typedef struct StdRdOptions
bool user_catalog_table; /* use as an additional catalog relation */
int parallel_workers; /* max number of parallel workers */
StdRdOptIndexCleanup vacuum_index_cleanup; /* controls index vacuuming */
- bool vacuum_truncate; /* enables vacuum to truncate a relation */
- bool vacuum_truncate_set; /* whether vacuum_truncate is set */
+ pg_ternary vacuum_truncate; /* enables vacuum to truncate a relation */
/*
* Fraction of pages in a relation that vacuum can eagerly scan and fail
diff --git a/src/test/regress/expected/reloptions.out b/src/test/regress/expected/reloptions.out
index 9de19b4e3f1..1c99f79ab01 100644
--- a/src/test/regress/expected/reloptions.out
+++ b/src/test/regress/expected/reloptions.out
@@ -98,6 +98,42 @@ SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass;
{fillfactor=13,autovacuum_enabled=false}
(1 row)
+-- Tests for future (FIXME) ternary options
+-- behave as boolean option: accept unassigned name and truncated value
+DROP TABLE reloptions_test;
+CREATE TABLE reloptions_test(i INT) WITH (vacuum_truncate);
+SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass;
+ reloptions
+------------------------
+ {vacuum_truncate=true}
+(1 row)
+
+DROP TABLE reloptions_test;
+CREATE TABLE reloptions_test(i INT) WITH (vacuum_truncate=FaLS);
+SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass;
+ reloptions
+------------------------
+ {vacuum_truncate=fals}
+(1 row)
+
+-- preferred "true" alias is used when storing
+DROP TABLE reloptions_test;
+CREATE TABLE reloptions_test(i INT) WITH (vacuum_index_cleanup=on);
+SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass;
+ reloptions
+---------------------------
+ {vacuum_index_cleanup=on}
+(1 row)
+
+-- custom "third" value is available
+DROP TABLE reloptions_test;
+CREATE TABLE reloptions_test(i INT) WITH (vacuum_index_cleanup=auto);
+SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass;
+ reloptions
+-----------------------------
+ {vacuum_index_cleanup=auto}
+(1 row)
+
-- Test vacuum_truncate option
DROP TABLE reloptions_test;
CREATE TEMP TABLE reloptions_test(i INT NOT NULL, j text)
diff --git a/src/test/regress/sql/reloptions.sql b/src/test/regress/sql/reloptions.sql
index 24fbe0b478d..f5980dafcbc 100644
--- a/src/test/regress/sql/reloptions.sql
+++ b/src/test/regress/sql/reloptions.sql
@@ -59,6 +59,27 @@ UPDATE pg_class
ALTER TABLE reloptions_test RESET (illegal_option);
SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass;
+-- Tests for future (FIXME) ternary options
+
+-- behave as boolean option: accept unassigned name and truncated value
+DROP TABLE reloptions_test;
+CREATE TABLE reloptions_test(i INT) WITH (vacuum_truncate);
+SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass;
+
+DROP TABLE reloptions_test;
+CREATE TABLE reloptions_test(i INT) WITH (vacuum_truncate=FaLS);
+SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass;
+
+-- preferred "true" alias is used when storing
+DROP TABLE reloptions_test;
+CREATE TABLE reloptions_test(i INT) WITH (vacuum_index_cleanup=on);
+SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass;
+
+-- custom "third" value is available
+DROP TABLE reloptions_test;
+CREATE TABLE reloptions_test(i INT) WITH (vacuum_index_cleanup=auto);
+SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass;
+
-- Test vacuum_truncate option
DROP TABLE reloptions_test;
diff --git a/src/tools/pgindent/typedefs.list b/src/tools/pgindent/typedefs.list
index 3f3a888fd0e..1c8610fd46c 100644
--- a/src/tools/pgindent/typedefs.list
+++ b/src/tools/pgindent/typedefs.list
@@ -3952,6 +3952,7 @@ pg_sha512_ctx
pg_snapshot
pg_special_case
pg_stack_base_t
+pg_ternary
pg_time_t
pg_time_usec_t
pg_tz
@@ -4079,6 +4080,7 @@ relopt_kind
relopt_parse_elt
relopt_real
relopt_string
+relopt_ternary
relopt_type
relopt_value
relopts_validator
--
2.47.3
--c36t3fndxhkzbl5z--
^ permalink raw reply [nested|flat] 868+ messages in thread
* [PATCH v3] Introduce ternary reloptions
@ 2026-01-16 15:04 Álvaro Herrera <[email protected]>
0 siblings, 0 replies; 868+ messages in thread
From: Álvaro Herrera @ 2026-01-16 15:04 UTC (permalink / raw)
Introduce ternary reloption as a replacement for current
`vacuum_truncate` implementation. Remove the `vacuum_truncate_set`
separate flag and use TERNARY_UNSET instead.
This could also be used for other options such as `vacuum_index_cleanup`
and `buffering`, but lets get the scaffolding in first.
Discussion: https://postgr.es/m/3474141.usfYGdeWWP@thinkpad-pgpro
---
src/backend/access/common/reloptions.c | 137 ++++++++++++++++++-----
src/backend/commands/vacuum.c | 4 +-
src/include/access/reloptions.h | 27 ++---
src/include/postgres.h | 15 +++
src/include/utils/rel.h | 3 +-
src/test/regress/expected/reloptions.out | 36 ++++++
src/test/regress/sql/reloptions.sql | 21 ++++
src/tools/pgindent/typedefs.list | 2 +
8 files changed, 202 insertions(+), 43 deletions(-)
diff --git a/src/backend/access/common/reloptions.c b/src/backend/access/common/reloptions.c
index 0b83f98ed5f..6f1f577581a 100644
--- a/src/backend/access/common/reloptions.c
+++ b/src/backend/access/common/reloptions.c
@@ -40,9 +40,9 @@
*
* To add an option:
*
- * (i) decide on a type (bool, integer, real, enum, string), name, default
- * value, upper and lower bounds (if applicable); for strings, consider a
- * validation routine.
+ * (i) decide on a type (bool, ternary, integer, real, enum, string), name,
+ * default value, upper and lower bounds (if applicable); for strings,
+ * consider a validation routine.
* (ii) add a record below (or use add_<type>_reloption).
* (iii) add it to the appropriate options struct (perhaps StdRdOptions)
* (iv) add it to the appropriate handling routine (perhaps
@@ -147,15 +147,6 @@ static relopt_bool boolRelOpts[] =
},
false
},
- {
- {
- "vacuum_truncate",
- "Enables vacuum to truncate empty pages at the end of this table",
- RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
- ShareUpdateExclusiveLock
- },
- true
- },
{
{
"deduplicate_items",
@@ -170,6 +161,25 @@ static relopt_bool boolRelOpts[] =
{{NULL}}
};
+static relopt_ternary ternaryRelOpts[] =
+{
+ {
+ {
+ "vacuum_truncate",
+ "Enables vacuum to truncate empty pages at the end of this table",
+ RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
+ ShareUpdateExclusiveLock
+ },
+ TERNARY_UNSET
+ },
+ /* list terminator */
+ {
+ {
+ NULL
+ }
+ }
+};
+
static relopt_int intRelOpts[] =
{
{
@@ -609,6 +619,13 @@ initialize_reloptions(void)
boolRelOpts[i].gen.lockmode));
j++;
}
+ for (i = 0; ternaryRelOpts[i].gen.name; i++)
+ {
+ Assert(DoLockModesConflict(ternaryRelOpts[i].gen.lockmode,
+ ternaryRelOpts[i].gen.lockmode));
+ j++;
+ }
+
for (i = 0; intRelOpts[i].gen.name; i++)
{
Assert(DoLockModesConflict(intRelOpts[i].gen.lockmode,
@@ -649,6 +666,14 @@ initialize_reloptions(void)
j++;
}
+ for (i = 0; ternaryRelOpts[i].gen.name; i++)
+ {
+ relOpts[j] = &ternaryRelOpts[i].gen;
+ relOpts[j]->type = RELOPT_TYPE_TERNARY;
+ relOpts[j]->namelen = strlen(relOpts[j]->name);
+ j++;
+ }
+
for (i = 0; intRelOpts[i].gen.name; i++)
{
relOpts[j] = &intRelOpts[i].gen;
@@ -809,6 +834,9 @@ allocate_reloption(bits32 kinds, int type, const char *name, const char *desc,
case RELOPT_TYPE_BOOL:
size = sizeof(relopt_bool);
break;
+ case RELOPT_TYPE_TERNARY:
+ size = sizeof(relopt_ternary);
+ break;
case RELOPT_TYPE_INT:
size = sizeof(relopt_int);
break;
@@ -892,6 +920,57 @@ add_local_bool_reloption(local_relopts *relopts, const char *name,
add_local_reloption(relopts, (relopt_gen *) newoption, offset);
}
+/*
+ * init_ternary_reloption
+ * Allocate and initialize a new ternary reloption
+ */
+static relopt_ternary *
+init_ternary_reloption(bits32 kinds, const char *name, const char *desc,
+ pg_ternary default_val, LOCKMODE lockmode)
+{
+ relopt_ternary *newoption;
+
+ newoption = (relopt_ternary *)
+ allocate_reloption(kinds, RELOPT_TYPE_TERNARY, name, desc, lockmode);
+ newoption->default_val = default_val;
+
+ return newoption;
+}
+
+/*
+ * add_ternary_reloption
+ * Add a new ternary reloption
+ */
+void
+add_ternary_reloption(bits32 kinds, const char *name, const char *desc,
+ pg_ternary default_val, LOCKMODE lockmode)
+{
+ relopt_ternary *newoption;
+
+ newoption =
+ init_ternary_reloption(kinds, name, desc, default_val, lockmode);
+
+ add_reloption((relopt_gen *) newoption);
+}
+
+/*
+ * add_local_ternary_reloption
+ * Add a new ternary local reloption
+ *
+ * 'offset' is offset of ternary-typed field.
+ */
+void
+add_local_ternary_reloption(local_relopts *relopts, const char *name,
+ const char *desc, pg_ternary default_val,
+ int offset)
+{
+ relopt_ternary *newoption;
+
+ newoption =
+ init_ternary_reloption(RELOPT_KIND_LOCAL, name, desc, default_val, 0);
+
+ add_local_reloption(relopts, (relopt_gen *) newoption, offset);
+}
/*
* init_real_reloption
@@ -1626,6 +1705,19 @@ parse_one_reloption(relopt_value *option, char *text_str, int text_len,
option->gen->name, value)));
}
break;
+ case RELOPT_TYPE_TERNARY:
+ {
+ bool b;
+
+ parsed = parse_bool(value, &b);
+ option->values.ternary_val = b ? TERNARY_TRUE : TERNARY_FALSE;
+ if (validate && !parsed)
+ ereport(ERROR,
+ (errcode(ERRCODE_INVALID_PARAMETER_VALUE),
+ errmsg("invalid value for ternary option \"%s\": %s",
+ option->gen->name, value)));
+ }
+ break;
case RELOPT_TYPE_INT:
{
relopt_int *optint = (relopt_int *) option->gen;
@@ -1789,17 +1881,6 @@ fillRelOptions(void *rdopts, Size basesize,
char *itempos = ((char *) rdopts) + elems[j].offset;
char *string_val;
- /*
- * If isset_offset is provided, store whether the reloption is
- * set there.
- */
- if (elems[j].isset_offset > 0)
- {
- char *setpos = ((char *) rdopts) + elems[j].isset_offset;
-
- *(bool *) setpos = options[i].isset;
- }
-
switch (options[i].gen->type)
{
case RELOPT_TYPE_BOOL:
@@ -1807,6 +1888,11 @@ fillRelOptions(void *rdopts, Size basesize,
options[i].values.bool_val :
((relopt_bool *) options[i].gen)->default_val;
break;
+ case RELOPT_TYPE_TERNARY:
+ *(pg_ternary *) itempos = options[i].isset ?
+ options[i].values.ternary_val :
+ ((relopt_ternary *) options[i].gen)->default_val;
+ break;
case RELOPT_TYPE_INT:
*(int *) itempos = options[i].isset ?
options[i].values.int_val :
@@ -1923,8 +2009,8 @@ default_reloptions(Datum reloptions, bool validate, relopt_kind kind)
offsetof(StdRdOptions, parallel_workers)},
{"vacuum_index_cleanup", RELOPT_TYPE_ENUM,
offsetof(StdRdOptions, vacuum_index_cleanup)},
- {"vacuum_truncate", RELOPT_TYPE_BOOL,
- offsetof(StdRdOptions, vacuum_truncate), offsetof(StdRdOptions, vacuum_truncate_set)},
+ {"vacuum_truncate", RELOPT_TYPE_TERNARY,
+ offsetof(StdRdOptions, vacuum_truncate)},
{"vacuum_max_eager_freeze_failure_rate", RELOPT_TYPE_REAL,
offsetof(StdRdOptions, vacuum_max_eager_freeze_failure_rate)}
};
@@ -2004,7 +2090,6 @@ build_local_reloptions(local_relopts *relopts, Datum options, bool validate)
elems[i].optname = opt->option->name;
elems[i].opttype = opt->option->type;
elems[i].offset = opt->offset;
- elems[i].isset_offset = 0; /* not supported for local relopts yet */
i++;
}
diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c
index aa4fbec143f..696eab9bd97 100644
--- a/src/backend/commands/vacuum.c
+++ b/src/backend/commands/vacuum.c
@@ -2224,9 +2224,9 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams params,
{
StdRdOptions *opts = (StdRdOptions *) rel->rd_options;
- if (opts && opts->vacuum_truncate_set)
+ if (opts && opts->vacuum_truncate != TERNARY_UNSET)
{
- if (opts->vacuum_truncate)
+ if (opts->vacuum_truncate == TERNARY_TRUE)
params.truncate = VACOPTVALUE_ENABLED;
else
params.truncate = VACOPTVALUE_DISABLED;
diff --git a/src/include/access/reloptions.h b/src/include/access/reloptions.h
index 2f08e1b0cf0..dfbef2babf2 100644
--- a/src/include/access/reloptions.h
+++ b/src/include/access/reloptions.h
@@ -29,6 +29,7 @@
typedef enum relopt_type
{
RELOPT_TYPE_BOOL,
+ RELOPT_TYPE_TERNARY, /* on, off, unset */
RELOPT_TYPE_INT,
RELOPT_TYPE_REAL,
RELOPT_TYPE_ENUM,
@@ -80,6 +81,7 @@ typedef struct relopt_value
union
{
bool bool_val;
+ pg_ternary ternary_val;
int int_val;
double real_val;
int enum_val;
@@ -94,6 +96,12 @@ typedef struct relopt_bool
bool default_val;
} relopt_bool;
+typedef struct relopt_ternary
+{
+ relopt_gen gen;
+ int default_val;
+} relopt_ternary;
+
typedef struct relopt_int
{
relopt_gen gen;
@@ -152,19 +160,6 @@ typedef struct
const char *optname; /* option's name */
relopt_type opttype; /* option's datatype */
int offset; /* offset of field in result struct */
-
- /*
- * isset_offset is an optional offset of a field in the result struct that
- * stores whether the option is explicitly set for the relation or if it
- * just picked up the default value. In most cases, this can be
- * accomplished by giving the reloption a special out-of-range default
- * value (e.g., some integer reloptions use -2), but this isn't always
- * possible. For example, a Boolean reloption cannot be given an
- * out-of-range default, so we need another way to discover the source of
- * its value. This offset is only used if given a value greater than
- * zero.
- */
- int isset_offset;
} relopt_parse_elt;
/* Local reloption definition */
@@ -195,6 +190,9 @@ typedef struct local_relopts
extern relopt_kind add_reloption_kind(void);
extern void add_bool_reloption(bits32 kinds, const char *name, const char *desc,
bool default_val, LOCKMODE lockmode);
+extern void add_ternary_reloption(bits32 kinds, const char *name,
+ const char *desc, pg_ternary default_val,
+ LOCKMODE lockmode);
extern void add_int_reloption(bits32 kinds, const char *name, const char *desc,
int default_val, int min_val, int max_val,
LOCKMODE lockmode);
@@ -214,6 +212,9 @@ extern void register_reloptions_validator(local_relopts *relopts,
extern void add_local_bool_reloption(local_relopts *relopts, const char *name,
const char *desc, bool default_val,
int offset);
+extern void add_local_ternary_reloption(local_relopts *relopts,
+ const char *name, const char *desc,
+ pg_ternary default_val, int offset);
extern void add_local_int_reloption(local_relopts *relopts, const char *name,
const char *desc, int default_val,
int min_val, int max_val, int offset);
diff --git a/src/include/postgres.h b/src/include/postgres.h
index 7d93fbce709..47c88780776 100644
--- a/src/include/postgres.h
+++ b/src/include/postgres.h
@@ -543,6 +543,21 @@ Float8GetDatum(float8 X)
* ----------------------------------------------------------------
*/
+/*
+ * pg_ternary
+ * Boolean value with an extra "unset" value
+ *
+ * This enum can be used for values that want to distinguish between true,
+ * false, and unset.
+*/
+
+typedef enum pg_ternary
+{
+ TERNARY_FALSE = 0,
+ TERNARY_TRUE = 1,
+ TERNARY_UNSET = -1
+} pg_ternary;
+
/*
* NON_EXEC_STATIC: It's sometimes useful to define a variable or function
* that is normally static but extern when using EXEC_BACKEND (see
diff --git a/src/include/utils/rel.h b/src/include/utils/rel.h
index d03ab247788..5feb18a5373 100644
--- a/src/include/utils/rel.h
+++ b/src/include/utils/rel.h
@@ -347,8 +347,7 @@ typedef struct StdRdOptions
bool user_catalog_table; /* use as an additional catalog relation */
int parallel_workers; /* max number of parallel workers */
StdRdOptIndexCleanup vacuum_index_cleanup; /* controls index vacuuming */
- bool vacuum_truncate; /* enables vacuum to truncate a relation */
- bool vacuum_truncate_set; /* whether vacuum_truncate is set */
+ pg_ternary vacuum_truncate; /* enables vacuum to truncate a relation */
/*
* Fraction of pages in a relation that vacuum can eagerly scan and fail
diff --git a/src/test/regress/expected/reloptions.out b/src/test/regress/expected/reloptions.out
index 9de19b4e3f1..1c99f79ab01 100644
--- a/src/test/regress/expected/reloptions.out
+++ b/src/test/regress/expected/reloptions.out
@@ -98,6 +98,42 @@ SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass;
{fillfactor=13,autovacuum_enabled=false}
(1 row)
+-- Tests for future (FIXME) ternary options
+-- behave as boolean option: accept unassigned name and truncated value
+DROP TABLE reloptions_test;
+CREATE TABLE reloptions_test(i INT) WITH (vacuum_truncate);
+SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass;
+ reloptions
+------------------------
+ {vacuum_truncate=true}
+(1 row)
+
+DROP TABLE reloptions_test;
+CREATE TABLE reloptions_test(i INT) WITH (vacuum_truncate=FaLS);
+SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass;
+ reloptions
+------------------------
+ {vacuum_truncate=fals}
+(1 row)
+
+-- preferred "true" alias is used when storing
+DROP TABLE reloptions_test;
+CREATE TABLE reloptions_test(i INT) WITH (vacuum_index_cleanup=on);
+SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass;
+ reloptions
+---------------------------
+ {vacuum_index_cleanup=on}
+(1 row)
+
+-- custom "third" value is available
+DROP TABLE reloptions_test;
+CREATE TABLE reloptions_test(i INT) WITH (vacuum_index_cleanup=auto);
+SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass;
+ reloptions
+-----------------------------
+ {vacuum_index_cleanup=auto}
+(1 row)
+
-- Test vacuum_truncate option
DROP TABLE reloptions_test;
CREATE TEMP TABLE reloptions_test(i INT NOT NULL, j text)
diff --git a/src/test/regress/sql/reloptions.sql b/src/test/regress/sql/reloptions.sql
index 24fbe0b478d..f5980dafcbc 100644
--- a/src/test/regress/sql/reloptions.sql
+++ b/src/test/regress/sql/reloptions.sql
@@ -59,6 +59,27 @@ UPDATE pg_class
ALTER TABLE reloptions_test RESET (illegal_option);
SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass;
+-- Tests for future (FIXME) ternary options
+
+-- behave as boolean option: accept unassigned name and truncated value
+DROP TABLE reloptions_test;
+CREATE TABLE reloptions_test(i INT) WITH (vacuum_truncate);
+SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass;
+
+DROP TABLE reloptions_test;
+CREATE TABLE reloptions_test(i INT) WITH (vacuum_truncate=FaLS);
+SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass;
+
+-- preferred "true" alias is used when storing
+DROP TABLE reloptions_test;
+CREATE TABLE reloptions_test(i INT) WITH (vacuum_index_cleanup=on);
+SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass;
+
+-- custom "third" value is available
+DROP TABLE reloptions_test;
+CREATE TABLE reloptions_test(i INT) WITH (vacuum_index_cleanup=auto);
+SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass;
+
-- Test vacuum_truncate option
DROP TABLE reloptions_test;
diff --git a/src/tools/pgindent/typedefs.list b/src/tools/pgindent/typedefs.list
index 3f3a888fd0e..1c8610fd46c 100644
--- a/src/tools/pgindent/typedefs.list
+++ b/src/tools/pgindent/typedefs.list
@@ -3952,6 +3952,7 @@ pg_sha512_ctx
pg_snapshot
pg_special_case
pg_stack_base_t
+pg_ternary
pg_time_t
pg_time_usec_t
pg_tz
@@ -4079,6 +4080,7 @@ relopt_kind
relopt_parse_elt
relopt_real
relopt_string
+relopt_ternary
relopt_type
relopt_value
relopts_validator
--
2.47.3
--c36t3fndxhkzbl5z--
^ permalink raw reply [nested|flat] 868+ messages in thread
* [PATCH v3] Introduce ternary reloptions
@ 2026-01-16 15:04 Álvaro Herrera <[email protected]>
0 siblings, 0 replies; 868+ messages in thread
From: Álvaro Herrera @ 2026-01-16 15:04 UTC (permalink / raw)
Introduce ternary reloption as a replacement for current
`vacuum_truncate` implementation. Remove the `vacuum_truncate_set`
separate flag and use TERNARY_UNSET instead.
This could also be used for other options such as `vacuum_index_cleanup`
and `buffering`, but lets get the scaffolding in first.
Discussion: https://postgr.es/m/3474141.usfYGdeWWP@thinkpad-pgpro
---
src/backend/access/common/reloptions.c | 137 ++++++++++++++++++-----
src/backend/commands/vacuum.c | 4 +-
src/include/access/reloptions.h | 27 ++---
src/include/postgres.h | 15 +++
src/include/utils/rel.h | 3 +-
src/test/regress/expected/reloptions.out | 36 ++++++
src/test/regress/sql/reloptions.sql | 21 ++++
src/tools/pgindent/typedefs.list | 2 +
8 files changed, 202 insertions(+), 43 deletions(-)
diff --git a/src/backend/access/common/reloptions.c b/src/backend/access/common/reloptions.c
index 0b83f98ed5f..6f1f577581a 100644
--- a/src/backend/access/common/reloptions.c
+++ b/src/backend/access/common/reloptions.c
@@ -40,9 +40,9 @@
*
* To add an option:
*
- * (i) decide on a type (bool, integer, real, enum, string), name, default
- * value, upper and lower bounds (if applicable); for strings, consider a
- * validation routine.
+ * (i) decide on a type (bool, ternary, integer, real, enum, string), name,
+ * default value, upper and lower bounds (if applicable); for strings,
+ * consider a validation routine.
* (ii) add a record below (or use add_<type>_reloption).
* (iii) add it to the appropriate options struct (perhaps StdRdOptions)
* (iv) add it to the appropriate handling routine (perhaps
@@ -147,15 +147,6 @@ static relopt_bool boolRelOpts[] =
},
false
},
- {
- {
- "vacuum_truncate",
- "Enables vacuum to truncate empty pages at the end of this table",
- RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
- ShareUpdateExclusiveLock
- },
- true
- },
{
{
"deduplicate_items",
@@ -170,6 +161,25 @@ static relopt_bool boolRelOpts[] =
{{NULL}}
};
+static relopt_ternary ternaryRelOpts[] =
+{
+ {
+ {
+ "vacuum_truncate",
+ "Enables vacuum to truncate empty pages at the end of this table",
+ RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
+ ShareUpdateExclusiveLock
+ },
+ TERNARY_UNSET
+ },
+ /* list terminator */
+ {
+ {
+ NULL
+ }
+ }
+};
+
static relopt_int intRelOpts[] =
{
{
@@ -609,6 +619,13 @@ initialize_reloptions(void)
boolRelOpts[i].gen.lockmode));
j++;
}
+ for (i = 0; ternaryRelOpts[i].gen.name; i++)
+ {
+ Assert(DoLockModesConflict(ternaryRelOpts[i].gen.lockmode,
+ ternaryRelOpts[i].gen.lockmode));
+ j++;
+ }
+
for (i = 0; intRelOpts[i].gen.name; i++)
{
Assert(DoLockModesConflict(intRelOpts[i].gen.lockmode,
@@ -649,6 +666,14 @@ initialize_reloptions(void)
j++;
}
+ for (i = 0; ternaryRelOpts[i].gen.name; i++)
+ {
+ relOpts[j] = &ternaryRelOpts[i].gen;
+ relOpts[j]->type = RELOPT_TYPE_TERNARY;
+ relOpts[j]->namelen = strlen(relOpts[j]->name);
+ j++;
+ }
+
for (i = 0; intRelOpts[i].gen.name; i++)
{
relOpts[j] = &intRelOpts[i].gen;
@@ -809,6 +834,9 @@ allocate_reloption(bits32 kinds, int type, const char *name, const char *desc,
case RELOPT_TYPE_BOOL:
size = sizeof(relopt_bool);
break;
+ case RELOPT_TYPE_TERNARY:
+ size = sizeof(relopt_ternary);
+ break;
case RELOPT_TYPE_INT:
size = sizeof(relopt_int);
break;
@@ -892,6 +920,57 @@ add_local_bool_reloption(local_relopts *relopts, const char *name,
add_local_reloption(relopts, (relopt_gen *) newoption, offset);
}
+/*
+ * init_ternary_reloption
+ * Allocate and initialize a new ternary reloption
+ */
+static relopt_ternary *
+init_ternary_reloption(bits32 kinds, const char *name, const char *desc,
+ pg_ternary default_val, LOCKMODE lockmode)
+{
+ relopt_ternary *newoption;
+
+ newoption = (relopt_ternary *)
+ allocate_reloption(kinds, RELOPT_TYPE_TERNARY, name, desc, lockmode);
+ newoption->default_val = default_val;
+
+ return newoption;
+}
+
+/*
+ * add_ternary_reloption
+ * Add a new ternary reloption
+ */
+void
+add_ternary_reloption(bits32 kinds, const char *name, const char *desc,
+ pg_ternary default_val, LOCKMODE lockmode)
+{
+ relopt_ternary *newoption;
+
+ newoption =
+ init_ternary_reloption(kinds, name, desc, default_val, lockmode);
+
+ add_reloption((relopt_gen *) newoption);
+}
+
+/*
+ * add_local_ternary_reloption
+ * Add a new ternary local reloption
+ *
+ * 'offset' is offset of ternary-typed field.
+ */
+void
+add_local_ternary_reloption(local_relopts *relopts, const char *name,
+ const char *desc, pg_ternary default_val,
+ int offset)
+{
+ relopt_ternary *newoption;
+
+ newoption =
+ init_ternary_reloption(RELOPT_KIND_LOCAL, name, desc, default_val, 0);
+
+ add_local_reloption(relopts, (relopt_gen *) newoption, offset);
+}
/*
* init_real_reloption
@@ -1626,6 +1705,19 @@ parse_one_reloption(relopt_value *option, char *text_str, int text_len,
option->gen->name, value)));
}
break;
+ case RELOPT_TYPE_TERNARY:
+ {
+ bool b;
+
+ parsed = parse_bool(value, &b);
+ option->values.ternary_val = b ? TERNARY_TRUE : TERNARY_FALSE;
+ if (validate && !parsed)
+ ereport(ERROR,
+ (errcode(ERRCODE_INVALID_PARAMETER_VALUE),
+ errmsg("invalid value for ternary option \"%s\": %s",
+ option->gen->name, value)));
+ }
+ break;
case RELOPT_TYPE_INT:
{
relopt_int *optint = (relopt_int *) option->gen;
@@ -1789,17 +1881,6 @@ fillRelOptions(void *rdopts, Size basesize,
char *itempos = ((char *) rdopts) + elems[j].offset;
char *string_val;
- /*
- * If isset_offset is provided, store whether the reloption is
- * set there.
- */
- if (elems[j].isset_offset > 0)
- {
- char *setpos = ((char *) rdopts) + elems[j].isset_offset;
-
- *(bool *) setpos = options[i].isset;
- }
-
switch (options[i].gen->type)
{
case RELOPT_TYPE_BOOL:
@@ -1807,6 +1888,11 @@ fillRelOptions(void *rdopts, Size basesize,
options[i].values.bool_val :
((relopt_bool *) options[i].gen)->default_val;
break;
+ case RELOPT_TYPE_TERNARY:
+ *(pg_ternary *) itempos = options[i].isset ?
+ options[i].values.ternary_val :
+ ((relopt_ternary *) options[i].gen)->default_val;
+ break;
case RELOPT_TYPE_INT:
*(int *) itempos = options[i].isset ?
options[i].values.int_val :
@@ -1923,8 +2009,8 @@ default_reloptions(Datum reloptions, bool validate, relopt_kind kind)
offsetof(StdRdOptions, parallel_workers)},
{"vacuum_index_cleanup", RELOPT_TYPE_ENUM,
offsetof(StdRdOptions, vacuum_index_cleanup)},
- {"vacuum_truncate", RELOPT_TYPE_BOOL,
- offsetof(StdRdOptions, vacuum_truncate), offsetof(StdRdOptions, vacuum_truncate_set)},
+ {"vacuum_truncate", RELOPT_TYPE_TERNARY,
+ offsetof(StdRdOptions, vacuum_truncate)},
{"vacuum_max_eager_freeze_failure_rate", RELOPT_TYPE_REAL,
offsetof(StdRdOptions, vacuum_max_eager_freeze_failure_rate)}
};
@@ -2004,7 +2090,6 @@ build_local_reloptions(local_relopts *relopts, Datum options, bool validate)
elems[i].optname = opt->option->name;
elems[i].opttype = opt->option->type;
elems[i].offset = opt->offset;
- elems[i].isset_offset = 0; /* not supported for local relopts yet */
i++;
}
diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c
index aa4fbec143f..696eab9bd97 100644
--- a/src/backend/commands/vacuum.c
+++ b/src/backend/commands/vacuum.c
@@ -2224,9 +2224,9 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams params,
{
StdRdOptions *opts = (StdRdOptions *) rel->rd_options;
- if (opts && opts->vacuum_truncate_set)
+ if (opts && opts->vacuum_truncate != TERNARY_UNSET)
{
- if (opts->vacuum_truncate)
+ if (opts->vacuum_truncate == TERNARY_TRUE)
params.truncate = VACOPTVALUE_ENABLED;
else
params.truncate = VACOPTVALUE_DISABLED;
diff --git a/src/include/access/reloptions.h b/src/include/access/reloptions.h
index 2f08e1b0cf0..dfbef2babf2 100644
--- a/src/include/access/reloptions.h
+++ b/src/include/access/reloptions.h
@@ -29,6 +29,7 @@
typedef enum relopt_type
{
RELOPT_TYPE_BOOL,
+ RELOPT_TYPE_TERNARY, /* on, off, unset */
RELOPT_TYPE_INT,
RELOPT_TYPE_REAL,
RELOPT_TYPE_ENUM,
@@ -80,6 +81,7 @@ typedef struct relopt_value
union
{
bool bool_val;
+ pg_ternary ternary_val;
int int_val;
double real_val;
int enum_val;
@@ -94,6 +96,12 @@ typedef struct relopt_bool
bool default_val;
} relopt_bool;
+typedef struct relopt_ternary
+{
+ relopt_gen gen;
+ int default_val;
+} relopt_ternary;
+
typedef struct relopt_int
{
relopt_gen gen;
@@ -152,19 +160,6 @@ typedef struct
const char *optname; /* option's name */
relopt_type opttype; /* option's datatype */
int offset; /* offset of field in result struct */
-
- /*
- * isset_offset is an optional offset of a field in the result struct that
- * stores whether the option is explicitly set for the relation or if it
- * just picked up the default value. In most cases, this can be
- * accomplished by giving the reloption a special out-of-range default
- * value (e.g., some integer reloptions use -2), but this isn't always
- * possible. For example, a Boolean reloption cannot be given an
- * out-of-range default, so we need another way to discover the source of
- * its value. This offset is only used if given a value greater than
- * zero.
- */
- int isset_offset;
} relopt_parse_elt;
/* Local reloption definition */
@@ -195,6 +190,9 @@ typedef struct local_relopts
extern relopt_kind add_reloption_kind(void);
extern void add_bool_reloption(bits32 kinds, const char *name, const char *desc,
bool default_val, LOCKMODE lockmode);
+extern void add_ternary_reloption(bits32 kinds, const char *name,
+ const char *desc, pg_ternary default_val,
+ LOCKMODE lockmode);
extern void add_int_reloption(bits32 kinds, const char *name, const char *desc,
int default_val, int min_val, int max_val,
LOCKMODE lockmode);
@@ -214,6 +212,9 @@ extern void register_reloptions_validator(local_relopts *relopts,
extern void add_local_bool_reloption(local_relopts *relopts, const char *name,
const char *desc, bool default_val,
int offset);
+extern void add_local_ternary_reloption(local_relopts *relopts,
+ const char *name, const char *desc,
+ pg_ternary default_val, int offset);
extern void add_local_int_reloption(local_relopts *relopts, const char *name,
const char *desc, int default_val,
int min_val, int max_val, int offset);
diff --git a/src/include/postgres.h b/src/include/postgres.h
index 7d93fbce709..47c88780776 100644
--- a/src/include/postgres.h
+++ b/src/include/postgres.h
@@ -543,6 +543,21 @@ Float8GetDatum(float8 X)
* ----------------------------------------------------------------
*/
+/*
+ * pg_ternary
+ * Boolean value with an extra "unset" value
+ *
+ * This enum can be used for values that want to distinguish between true,
+ * false, and unset.
+*/
+
+typedef enum pg_ternary
+{
+ TERNARY_FALSE = 0,
+ TERNARY_TRUE = 1,
+ TERNARY_UNSET = -1
+} pg_ternary;
+
/*
* NON_EXEC_STATIC: It's sometimes useful to define a variable or function
* that is normally static but extern when using EXEC_BACKEND (see
diff --git a/src/include/utils/rel.h b/src/include/utils/rel.h
index d03ab247788..5feb18a5373 100644
--- a/src/include/utils/rel.h
+++ b/src/include/utils/rel.h
@@ -347,8 +347,7 @@ typedef struct StdRdOptions
bool user_catalog_table; /* use as an additional catalog relation */
int parallel_workers; /* max number of parallel workers */
StdRdOptIndexCleanup vacuum_index_cleanup; /* controls index vacuuming */
- bool vacuum_truncate; /* enables vacuum to truncate a relation */
- bool vacuum_truncate_set; /* whether vacuum_truncate is set */
+ pg_ternary vacuum_truncate; /* enables vacuum to truncate a relation */
/*
* Fraction of pages in a relation that vacuum can eagerly scan and fail
diff --git a/src/test/regress/expected/reloptions.out b/src/test/regress/expected/reloptions.out
index 9de19b4e3f1..1c99f79ab01 100644
--- a/src/test/regress/expected/reloptions.out
+++ b/src/test/regress/expected/reloptions.out
@@ -98,6 +98,42 @@ SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass;
{fillfactor=13,autovacuum_enabled=false}
(1 row)
+-- Tests for future (FIXME) ternary options
+-- behave as boolean option: accept unassigned name and truncated value
+DROP TABLE reloptions_test;
+CREATE TABLE reloptions_test(i INT) WITH (vacuum_truncate);
+SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass;
+ reloptions
+------------------------
+ {vacuum_truncate=true}
+(1 row)
+
+DROP TABLE reloptions_test;
+CREATE TABLE reloptions_test(i INT) WITH (vacuum_truncate=FaLS);
+SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass;
+ reloptions
+------------------------
+ {vacuum_truncate=fals}
+(1 row)
+
+-- preferred "true" alias is used when storing
+DROP TABLE reloptions_test;
+CREATE TABLE reloptions_test(i INT) WITH (vacuum_index_cleanup=on);
+SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass;
+ reloptions
+---------------------------
+ {vacuum_index_cleanup=on}
+(1 row)
+
+-- custom "third" value is available
+DROP TABLE reloptions_test;
+CREATE TABLE reloptions_test(i INT) WITH (vacuum_index_cleanup=auto);
+SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass;
+ reloptions
+-----------------------------
+ {vacuum_index_cleanup=auto}
+(1 row)
+
-- Test vacuum_truncate option
DROP TABLE reloptions_test;
CREATE TEMP TABLE reloptions_test(i INT NOT NULL, j text)
diff --git a/src/test/regress/sql/reloptions.sql b/src/test/regress/sql/reloptions.sql
index 24fbe0b478d..f5980dafcbc 100644
--- a/src/test/regress/sql/reloptions.sql
+++ b/src/test/regress/sql/reloptions.sql
@@ -59,6 +59,27 @@ UPDATE pg_class
ALTER TABLE reloptions_test RESET (illegal_option);
SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass;
+-- Tests for future (FIXME) ternary options
+
+-- behave as boolean option: accept unassigned name and truncated value
+DROP TABLE reloptions_test;
+CREATE TABLE reloptions_test(i INT) WITH (vacuum_truncate);
+SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass;
+
+DROP TABLE reloptions_test;
+CREATE TABLE reloptions_test(i INT) WITH (vacuum_truncate=FaLS);
+SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass;
+
+-- preferred "true" alias is used when storing
+DROP TABLE reloptions_test;
+CREATE TABLE reloptions_test(i INT) WITH (vacuum_index_cleanup=on);
+SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass;
+
+-- custom "third" value is available
+DROP TABLE reloptions_test;
+CREATE TABLE reloptions_test(i INT) WITH (vacuum_index_cleanup=auto);
+SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass;
+
-- Test vacuum_truncate option
DROP TABLE reloptions_test;
diff --git a/src/tools/pgindent/typedefs.list b/src/tools/pgindent/typedefs.list
index 3f3a888fd0e..1c8610fd46c 100644
--- a/src/tools/pgindent/typedefs.list
+++ b/src/tools/pgindent/typedefs.list
@@ -3952,6 +3952,7 @@ pg_sha512_ctx
pg_snapshot
pg_special_case
pg_stack_base_t
+pg_ternary
pg_time_t
pg_time_usec_t
pg_tz
@@ -4079,6 +4080,7 @@ relopt_kind
relopt_parse_elt
relopt_real
relopt_string
+relopt_ternary
relopt_type
relopt_value
relopts_validator
--
2.47.3
--c36t3fndxhkzbl5z--
^ permalink raw reply [nested|flat] 868+ messages in thread
* [PATCH v3] Introduce ternary reloptions
@ 2026-01-16 15:04 Álvaro Herrera <[email protected]>
0 siblings, 0 replies; 868+ messages in thread
From: Álvaro Herrera @ 2026-01-16 15:04 UTC (permalink / raw)
Introduce ternary reloption as a replacement for current
`vacuum_truncate` implementation. Remove the `vacuum_truncate_set`
separate flag and use TERNARY_UNSET instead.
This could also be used for other options such as `vacuum_index_cleanup`
and `buffering`, but lets get the scaffolding in first.
Discussion: https://postgr.es/m/3474141.usfYGdeWWP@thinkpad-pgpro
---
src/backend/access/common/reloptions.c | 137 ++++++++++++++++++-----
src/backend/commands/vacuum.c | 4 +-
src/include/access/reloptions.h | 27 ++---
src/include/postgres.h | 15 +++
src/include/utils/rel.h | 3 +-
src/test/regress/expected/reloptions.out | 36 ++++++
src/test/regress/sql/reloptions.sql | 21 ++++
src/tools/pgindent/typedefs.list | 2 +
8 files changed, 202 insertions(+), 43 deletions(-)
diff --git a/src/backend/access/common/reloptions.c b/src/backend/access/common/reloptions.c
index 0b83f98ed5f..6f1f577581a 100644
--- a/src/backend/access/common/reloptions.c
+++ b/src/backend/access/common/reloptions.c
@@ -40,9 +40,9 @@
*
* To add an option:
*
- * (i) decide on a type (bool, integer, real, enum, string), name, default
- * value, upper and lower bounds (if applicable); for strings, consider a
- * validation routine.
+ * (i) decide on a type (bool, ternary, integer, real, enum, string), name,
+ * default value, upper and lower bounds (if applicable); for strings,
+ * consider a validation routine.
* (ii) add a record below (or use add_<type>_reloption).
* (iii) add it to the appropriate options struct (perhaps StdRdOptions)
* (iv) add it to the appropriate handling routine (perhaps
@@ -147,15 +147,6 @@ static relopt_bool boolRelOpts[] =
},
false
},
- {
- {
- "vacuum_truncate",
- "Enables vacuum to truncate empty pages at the end of this table",
- RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
- ShareUpdateExclusiveLock
- },
- true
- },
{
{
"deduplicate_items",
@@ -170,6 +161,25 @@ static relopt_bool boolRelOpts[] =
{{NULL}}
};
+static relopt_ternary ternaryRelOpts[] =
+{
+ {
+ {
+ "vacuum_truncate",
+ "Enables vacuum to truncate empty pages at the end of this table",
+ RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
+ ShareUpdateExclusiveLock
+ },
+ TERNARY_UNSET
+ },
+ /* list terminator */
+ {
+ {
+ NULL
+ }
+ }
+};
+
static relopt_int intRelOpts[] =
{
{
@@ -609,6 +619,13 @@ initialize_reloptions(void)
boolRelOpts[i].gen.lockmode));
j++;
}
+ for (i = 0; ternaryRelOpts[i].gen.name; i++)
+ {
+ Assert(DoLockModesConflict(ternaryRelOpts[i].gen.lockmode,
+ ternaryRelOpts[i].gen.lockmode));
+ j++;
+ }
+
for (i = 0; intRelOpts[i].gen.name; i++)
{
Assert(DoLockModesConflict(intRelOpts[i].gen.lockmode,
@@ -649,6 +666,14 @@ initialize_reloptions(void)
j++;
}
+ for (i = 0; ternaryRelOpts[i].gen.name; i++)
+ {
+ relOpts[j] = &ternaryRelOpts[i].gen;
+ relOpts[j]->type = RELOPT_TYPE_TERNARY;
+ relOpts[j]->namelen = strlen(relOpts[j]->name);
+ j++;
+ }
+
for (i = 0; intRelOpts[i].gen.name; i++)
{
relOpts[j] = &intRelOpts[i].gen;
@@ -809,6 +834,9 @@ allocate_reloption(bits32 kinds, int type, const char *name, const char *desc,
case RELOPT_TYPE_BOOL:
size = sizeof(relopt_bool);
break;
+ case RELOPT_TYPE_TERNARY:
+ size = sizeof(relopt_ternary);
+ break;
case RELOPT_TYPE_INT:
size = sizeof(relopt_int);
break;
@@ -892,6 +920,57 @@ add_local_bool_reloption(local_relopts *relopts, const char *name,
add_local_reloption(relopts, (relopt_gen *) newoption, offset);
}
+/*
+ * init_ternary_reloption
+ * Allocate and initialize a new ternary reloption
+ */
+static relopt_ternary *
+init_ternary_reloption(bits32 kinds, const char *name, const char *desc,
+ pg_ternary default_val, LOCKMODE lockmode)
+{
+ relopt_ternary *newoption;
+
+ newoption = (relopt_ternary *)
+ allocate_reloption(kinds, RELOPT_TYPE_TERNARY, name, desc, lockmode);
+ newoption->default_val = default_val;
+
+ return newoption;
+}
+
+/*
+ * add_ternary_reloption
+ * Add a new ternary reloption
+ */
+void
+add_ternary_reloption(bits32 kinds, const char *name, const char *desc,
+ pg_ternary default_val, LOCKMODE lockmode)
+{
+ relopt_ternary *newoption;
+
+ newoption =
+ init_ternary_reloption(kinds, name, desc, default_val, lockmode);
+
+ add_reloption((relopt_gen *) newoption);
+}
+
+/*
+ * add_local_ternary_reloption
+ * Add a new ternary local reloption
+ *
+ * 'offset' is offset of ternary-typed field.
+ */
+void
+add_local_ternary_reloption(local_relopts *relopts, const char *name,
+ const char *desc, pg_ternary default_val,
+ int offset)
+{
+ relopt_ternary *newoption;
+
+ newoption =
+ init_ternary_reloption(RELOPT_KIND_LOCAL, name, desc, default_val, 0);
+
+ add_local_reloption(relopts, (relopt_gen *) newoption, offset);
+}
/*
* init_real_reloption
@@ -1626,6 +1705,19 @@ parse_one_reloption(relopt_value *option, char *text_str, int text_len,
option->gen->name, value)));
}
break;
+ case RELOPT_TYPE_TERNARY:
+ {
+ bool b;
+
+ parsed = parse_bool(value, &b);
+ option->values.ternary_val = b ? TERNARY_TRUE : TERNARY_FALSE;
+ if (validate && !parsed)
+ ereport(ERROR,
+ (errcode(ERRCODE_INVALID_PARAMETER_VALUE),
+ errmsg("invalid value for ternary option \"%s\": %s",
+ option->gen->name, value)));
+ }
+ break;
case RELOPT_TYPE_INT:
{
relopt_int *optint = (relopt_int *) option->gen;
@@ -1789,17 +1881,6 @@ fillRelOptions(void *rdopts, Size basesize,
char *itempos = ((char *) rdopts) + elems[j].offset;
char *string_val;
- /*
- * If isset_offset is provided, store whether the reloption is
- * set there.
- */
- if (elems[j].isset_offset > 0)
- {
- char *setpos = ((char *) rdopts) + elems[j].isset_offset;
-
- *(bool *) setpos = options[i].isset;
- }
-
switch (options[i].gen->type)
{
case RELOPT_TYPE_BOOL:
@@ -1807,6 +1888,11 @@ fillRelOptions(void *rdopts, Size basesize,
options[i].values.bool_val :
((relopt_bool *) options[i].gen)->default_val;
break;
+ case RELOPT_TYPE_TERNARY:
+ *(pg_ternary *) itempos = options[i].isset ?
+ options[i].values.ternary_val :
+ ((relopt_ternary *) options[i].gen)->default_val;
+ break;
case RELOPT_TYPE_INT:
*(int *) itempos = options[i].isset ?
options[i].values.int_val :
@@ -1923,8 +2009,8 @@ default_reloptions(Datum reloptions, bool validate, relopt_kind kind)
offsetof(StdRdOptions, parallel_workers)},
{"vacuum_index_cleanup", RELOPT_TYPE_ENUM,
offsetof(StdRdOptions, vacuum_index_cleanup)},
- {"vacuum_truncate", RELOPT_TYPE_BOOL,
- offsetof(StdRdOptions, vacuum_truncate), offsetof(StdRdOptions, vacuum_truncate_set)},
+ {"vacuum_truncate", RELOPT_TYPE_TERNARY,
+ offsetof(StdRdOptions, vacuum_truncate)},
{"vacuum_max_eager_freeze_failure_rate", RELOPT_TYPE_REAL,
offsetof(StdRdOptions, vacuum_max_eager_freeze_failure_rate)}
};
@@ -2004,7 +2090,6 @@ build_local_reloptions(local_relopts *relopts, Datum options, bool validate)
elems[i].optname = opt->option->name;
elems[i].opttype = opt->option->type;
elems[i].offset = opt->offset;
- elems[i].isset_offset = 0; /* not supported for local relopts yet */
i++;
}
diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c
index aa4fbec143f..696eab9bd97 100644
--- a/src/backend/commands/vacuum.c
+++ b/src/backend/commands/vacuum.c
@@ -2224,9 +2224,9 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams params,
{
StdRdOptions *opts = (StdRdOptions *) rel->rd_options;
- if (opts && opts->vacuum_truncate_set)
+ if (opts && opts->vacuum_truncate != TERNARY_UNSET)
{
- if (opts->vacuum_truncate)
+ if (opts->vacuum_truncate == TERNARY_TRUE)
params.truncate = VACOPTVALUE_ENABLED;
else
params.truncate = VACOPTVALUE_DISABLED;
diff --git a/src/include/access/reloptions.h b/src/include/access/reloptions.h
index 2f08e1b0cf0..dfbef2babf2 100644
--- a/src/include/access/reloptions.h
+++ b/src/include/access/reloptions.h
@@ -29,6 +29,7 @@
typedef enum relopt_type
{
RELOPT_TYPE_BOOL,
+ RELOPT_TYPE_TERNARY, /* on, off, unset */
RELOPT_TYPE_INT,
RELOPT_TYPE_REAL,
RELOPT_TYPE_ENUM,
@@ -80,6 +81,7 @@ typedef struct relopt_value
union
{
bool bool_val;
+ pg_ternary ternary_val;
int int_val;
double real_val;
int enum_val;
@@ -94,6 +96,12 @@ typedef struct relopt_bool
bool default_val;
} relopt_bool;
+typedef struct relopt_ternary
+{
+ relopt_gen gen;
+ int default_val;
+} relopt_ternary;
+
typedef struct relopt_int
{
relopt_gen gen;
@@ -152,19 +160,6 @@ typedef struct
const char *optname; /* option's name */
relopt_type opttype; /* option's datatype */
int offset; /* offset of field in result struct */
-
- /*
- * isset_offset is an optional offset of a field in the result struct that
- * stores whether the option is explicitly set for the relation or if it
- * just picked up the default value. In most cases, this can be
- * accomplished by giving the reloption a special out-of-range default
- * value (e.g., some integer reloptions use -2), but this isn't always
- * possible. For example, a Boolean reloption cannot be given an
- * out-of-range default, so we need another way to discover the source of
- * its value. This offset is only used if given a value greater than
- * zero.
- */
- int isset_offset;
} relopt_parse_elt;
/* Local reloption definition */
@@ -195,6 +190,9 @@ typedef struct local_relopts
extern relopt_kind add_reloption_kind(void);
extern void add_bool_reloption(bits32 kinds, const char *name, const char *desc,
bool default_val, LOCKMODE lockmode);
+extern void add_ternary_reloption(bits32 kinds, const char *name,
+ const char *desc, pg_ternary default_val,
+ LOCKMODE lockmode);
extern void add_int_reloption(bits32 kinds, const char *name, const char *desc,
int default_val, int min_val, int max_val,
LOCKMODE lockmode);
@@ -214,6 +212,9 @@ extern void register_reloptions_validator(local_relopts *relopts,
extern void add_local_bool_reloption(local_relopts *relopts, const char *name,
const char *desc, bool default_val,
int offset);
+extern void add_local_ternary_reloption(local_relopts *relopts,
+ const char *name, const char *desc,
+ pg_ternary default_val, int offset);
extern void add_local_int_reloption(local_relopts *relopts, const char *name,
const char *desc, int default_val,
int min_val, int max_val, int offset);
diff --git a/src/include/postgres.h b/src/include/postgres.h
index 7d93fbce709..47c88780776 100644
--- a/src/include/postgres.h
+++ b/src/include/postgres.h
@@ -543,6 +543,21 @@ Float8GetDatum(float8 X)
* ----------------------------------------------------------------
*/
+/*
+ * pg_ternary
+ * Boolean value with an extra "unset" value
+ *
+ * This enum can be used for values that want to distinguish between true,
+ * false, and unset.
+*/
+
+typedef enum pg_ternary
+{
+ TERNARY_FALSE = 0,
+ TERNARY_TRUE = 1,
+ TERNARY_UNSET = -1
+} pg_ternary;
+
/*
* NON_EXEC_STATIC: It's sometimes useful to define a variable or function
* that is normally static but extern when using EXEC_BACKEND (see
diff --git a/src/include/utils/rel.h b/src/include/utils/rel.h
index d03ab247788..5feb18a5373 100644
--- a/src/include/utils/rel.h
+++ b/src/include/utils/rel.h
@@ -347,8 +347,7 @@ typedef struct StdRdOptions
bool user_catalog_table; /* use as an additional catalog relation */
int parallel_workers; /* max number of parallel workers */
StdRdOptIndexCleanup vacuum_index_cleanup; /* controls index vacuuming */
- bool vacuum_truncate; /* enables vacuum to truncate a relation */
- bool vacuum_truncate_set; /* whether vacuum_truncate is set */
+ pg_ternary vacuum_truncate; /* enables vacuum to truncate a relation */
/*
* Fraction of pages in a relation that vacuum can eagerly scan and fail
diff --git a/src/test/regress/expected/reloptions.out b/src/test/regress/expected/reloptions.out
index 9de19b4e3f1..1c99f79ab01 100644
--- a/src/test/regress/expected/reloptions.out
+++ b/src/test/regress/expected/reloptions.out
@@ -98,6 +98,42 @@ SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass;
{fillfactor=13,autovacuum_enabled=false}
(1 row)
+-- Tests for future (FIXME) ternary options
+-- behave as boolean option: accept unassigned name and truncated value
+DROP TABLE reloptions_test;
+CREATE TABLE reloptions_test(i INT) WITH (vacuum_truncate);
+SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass;
+ reloptions
+------------------------
+ {vacuum_truncate=true}
+(1 row)
+
+DROP TABLE reloptions_test;
+CREATE TABLE reloptions_test(i INT) WITH (vacuum_truncate=FaLS);
+SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass;
+ reloptions
+------------------------
+ {vacuum_truncate=fals}
+(1 row)
+
+-- preferred "true" alias is used when storing
+DROP TABLE reloptions_test;
+CREATE TABLE reloptions_test(i INT) WITH (vacuum_index_cleanup=on);
+SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass;
+ reloptions
+---------------------------
+ {vacuum_index_cleanup=on}
+(1 row)
+
+-- custom "third" value is available
+DROP TABLE reloptions_test;
+CREATE TABLE reloptions_test(i INT) WITH (vacuum_index_cleanup=auto);
+SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass;
+ reloptions
+-----------------------------
+ {vacuum_index_cleanup=auto}
+(1 row)
+
-- Test vacuum_truncate option
DROP TABLE reloptions_test;
CREATE TEMP TABLE reloptions_test(i INT NOT NULL, j text)
diff --git a/src/test/regress/sql/reloptions.sql b/src/test/regress/sql/reloptions.sql
index 24fbe0b478d..f5980dafcbc 100644
--- a/src/test/regress/sql/reloptions.sql
+++ b/src/test/regress/sql/reloptions.sql
@@ -59,6 +59,27 @@ UPDATE pg_class
ALTER TABLE reloptions_test RESET (illegal_option);
SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass;
+-- Tests for future (FIXME) ternary options
+
+-- behave as boolean option: accept unassigned name and truncated value
+DROP TABLE reloptions_test;
+CREATE TABLE reloptions_test(i INT) WITH (vacuum_truncate);
+SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass;
+
+DROP TABLE reloptions_test;
+CREATE TABLE reloptions_test(i INT) WITH (vacuum_truncate=FaLS);
+SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass;
+
+-- preferred "true" alias is used when storing
+DROP TABLE reloptions_test;
+CREATE TABLE reloptions_test(i INT) WITH (vacuum_index_cleanup=on);
+SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass;
+
+-- custom "third" value is available
+DROP TABLE reloptions_test;
+CREATE TABLE reloptions_test(i INT) WITH (vacuum_index_cleanup=auto);
+SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass;
+
-- Test vacuum_truncate option
DROP TABLE reloptions_test;
diff --git a/src/tools/pgindent/typedefs.list b/src/tools/pgindent/typedefs.list
index 3f3a888fd0e..1c8610fd46c 100644
--- a/src/tools/pgindent/typedefs.list
+++ b/src/tools/pgindent/typedefs.list
@@ -3952,6 +3952,7 @@ pg_sha512_ctx
pg_snapshot
pg_special_case
pg_stack_base_t
+pg_ternary
pg_time_t
pg_time_usec_t
pg_tz
@@ -4079,6 +4080,7 @@ relopt_kind
relopt_parse_elt
relopt_real
relopt_string
+relopt_ternary
relopt_type
relopt_value
relopts_validator
--
2.47.3
--c36t3fndxhkzbl5z--
^ permalink raw reply [nested|flat] 868+ messages in thread
* [PATCH v3] Introduce ternary reloptions
@ 2026-01-16 15:04 Álvaro Herrera <[email protected]>
0 siblings, 0 replies; 868+ messages in thread
From: Álvaro Herrera @ 2026-01-16 15:04 UTC (permalink / raw)
Introduce ternary reloption as a replacement for current
`vacuum_truncate` implementation. Remove the `vacuum_truncate_set`
separate flag and use TERNARY_UNSET instead.
This could also be used for other options such as `vacuum_index_cleanup`
and `buffering`, but lets get the scaffolding in first.
Discussion: https://postgr.es/m/3474141.usfYGdeWWP@thinkpad-pgpro
---
src/backend/access/common/reloptions.c | 137 ++++++++++++++++++-----
src/backend/commands/vacuum.c | 4 +-
src/include/access/reloptions.h | 27 ++---
src/include/postgres.h | 15 +++
src/include/utils/rel.h | 3 +-
src/test/regress/expected/reloptions.out | 36 ++++++
src/test/regress/sql/reloptions.sql | 21 ++++
src/tools/pgindent/typedefs.list | 2 +
8 files changed, 202 insertions(+), 43 deletions(-)
diff --git a/src/backend/access/common/reloptions.c b/src/backend/access/common/reloptions.c
index 0b83f98ed5f..6f1f577581a 100644
--- a/src/backend/access/common/reloptions.c
+++ b/src/backend/access/common/reloptions.c
@@ -40,9 +40,9 @@
*
* To add an option:
*
- * (i) decide on a type (bool, integer, real, enum, string), name, default
- * value, upper and lower bounds (if applicable); for strings, consider a
- * validation routine.
+ * (i) decide on a type (bool, ternary, integer, real, enum, string), name,
+ * default value, upper and lower bounds (if applicable); for strings,
+ * consider a validation routine.
* (ii) add a record below (or use add_<type>_reloption).
* (iii) add it to the appropriate options struct (perhaps StdRdOptions)
* (iv) add it to the appropriate handling routine (perhaps
@@ -147,15 +147,6 @@ static relopt_bool boolRelOpts[] =
},
false
},
- {
- {
- "vacuum_truncate",
- "Enables vacuum to truncate empty pages at the end of this table",
- RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
- ShareUpdateExclusiveLock
- },
- true
- },
{
{
"deduplicate_items",
@@ -170,6 +161,25 @@ static relopt_bool boolRelOpts[] =
{{NULL}}
};
+static relopt_ternary ternaryRelOpts[] =
+{
+ {
+ {
+ "vacuum_truncate",
+ "Enables vacuum to truncate empty pages at the end of this table",
+ RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
+ ShareUpdateExclusiveLock
+ },
+ TERNARY_UNSET
+ },
+ /* list terminator */
+ {
+ {
+ NULL
+ }
+ }
+};
+
static relopt_int intRelOpts[] =
{
{
@@ -609,6 +619,13 @@ initialize_reloptions(void)
boolRelOpts[i].gen.lockmode));
j++;
}
+ for (i = 0; ternaryRelOpts[i].gen.name; i++)
+ {
+ Assert(DoLockModesConflict(ternaryRelOpts[i].gen.lockmode,
+ ternaryRelOpts[i].gen.lockmode));
+ j++;
+ }
+
for (i = 0; intRelOpts[i].gen.name; i++)
{
Assert(DoLockModesConflict(intRelOpts[i].gen.lockmode,
@@ -649,6 +666,14 @@ initialize_reloptions(void)
j++;
}
+ for (i = 0; ternaryRelOpts[i].gen.name; i++)
+ {
+ relOpts[j] = &ternaryRelOpts[i].gen;
+ relOpts[j]->type = RELOPT_TYPE_TERNARY;
+ relOpts[j]->namelen = strlen(relOpts[j]->name);
+ j++;
+ }
+
for (i = 0; intRelOpts[i].gen.name; i++)
{
relOpts[j] = &intRelOpts[i].gen;
@@ -809,6 +834,9 @@ allocate_reloption(bits32 kinds, int type, const char *name, const char *desc,
case RELOPT_TYPE_BOOL:
size = sizeof(relopt_bool);
break;
+ case RELOPT_TYPE_TERNARY:
+ size = sizeof(relopt_ternary);
+ break;
case RELOPT_TYPE_INT:
size = sizeof(relopt_int);
break;
@@ -892,6 +920,57 @@ add_local_bool_reloption(local_relopts *relopts, const char *name,
add_local_reloption(relopts, (relopt_gen *) newoption, offset);
}
+/*
+ * init_ternary_reloption
+ * Allocate and initialize a new ternary reloption
+ */
+static relopt_ternary *
+init_ternary_reloption(bits32 kinds, const char *name, const char *desc,
+ pg_ternary default_val, LOCKMODE lockmode)
+{
+ relopt_ternary *newoption;
+
+ newoption = (relopt_ternary *)
+ allocate_reloption(kinds, RELOPT_TYPE_TERNARY, name, desc, lockmode);
+ newoption->default_val = default_val;
+
+ return newoption;
+}
+
+/*
+ * add_ternary_reloption
+ * Add a new ternary reloption
+ */
+void
+add_ternary_reloption(bits32 kinds, const char *name, const char *desc,
+ pg_ternary default_val, LOCKMODE lockmode)
+{
+ relopt_ternary *newoption;
+
+ newoption =
+ init_ternary_reloption(kinds, name, desc, default_val, lockmode);
+
+ add_reloption((relopt_gen *) newoption);
+}
+
+/*
+ * add_local_ternary_reloption
+ * Add a new ternary local reloption
+ *
+ * 'offset' is offset of ternary-typed field.
+ */
+void
+add_local_ternary_reloption(local_relopts *relopts, const char *name,
+ const char *desc, pg_ternary default_val,
+ int offset)
+{
+ relopt_ternary *newoption;
+
+ newoption =
+ init_ternary_reloption(RELOPT_KIND_LOCAL, name, desc, default_val, 0);
+
+ add_local_reloption(relopts, (relopt_gen *) newoption, offset);
+}
/*
* init_real_reloption
@@ -1626,6 +1705,19 @@ parse_one_reloption(relopt_value *option, char *text_str, int text_len,
option->gen->name, value)));
}
break;
+ case RELOPT_TYPE_TERNARY:
+ {
+ bool b;
+
+ parsed = parse_bool(value, &b);
+ option->values.ternary_val = b ? TERNARY_TRUE : TERNARY_FALSE;
+ if (validate && !parsed)
+ ereport(ERROR,
+ (errcode(ERRCODE_INVALID_PARAMETER_VALUE),
+ errmsg("invalid value for ternary option \"%s\": %s",
+ option->gen->name, value)));
+ }
+ break;
case RELOPT_TYPE_INT:
{
relopt_int *optint = (relopt_int *) option->gen;
@@ -1789,17 +1881,6 @@ fillRelOptions(void *rdopts, Size basesize,
char *itempos = ((char *) rdopts) + elems[j].offset;
char *string_val;
- /*
- * If isset_offset is provided, store whether the reloption is
- * set there.
- */
- if (elems[j].isset_offset > 0)
- {
- char *setpos = ((char *) rdopts) + elems[j].isset_offset;
-
- *(bool *) setpos = options[i].isset;
- }
-
switch (options[i].gen->type)
{
case RELOPT_TYPE_BOOL:
@@ -1807,6 +1888,11 @@ fillRelOptions(void *rdopts, Size basesize,
options[i].values.bool_val :
((relopt_bool *) options[i].gen)->default_val;
break;
+ case RELOPT_TYPE_TERNARY:
+ *(pg_ternary *) itempos = options[i].isset ?
+ options[i].values.ternary_val :
+ ((relopt_ternary *) options[i].gen)->default_val;
+ break;
case RELOPT_TYPE_INT:
*(int *) itempos = options[i].isset ?
options[i].values.int_val :
@@ -1923,8 +2009,8 @@ default_reloptions(Datum reloptions, bool validate, relopt_kind kind)
offsetof(StdRdOptions, parallel_workers)},
{"vacuum_index_cleanup", RELOPT_TYPE_ENUM,
offsetof(StdRdOptions, vacuum_index_cleanup)},
- {"vacuum_truncate", RELOPT_TYPE_BOOL,
- offsetof(StdRdOptions, vacuum_truncate), offsetof(StdRdOptions, vacuum_truncate_set)},
+ {"vacuum_truncate", RELOPT_TYPE_TERNARY,
+ offsetof(StdRdOptions, vacuum_truncate)},
{"vacuum_max_eager_freeze_failure_rate", RELOPT_TYPE_REAL,
offsetof(StdRdOptions, vacuum_max_eager_freeze_failure_rate)}
};
@@ -2004,7 +2090,6 @@ build_local_reloptions(local_relopts *relopts, Datum options, bool validate)
elems[i].optname = opt->option->name;
elems[i].opttype = opt->option->type;
elems[i].offset = opt->offset;
- elems[i].isset_offset = 0; /* not supported for local relopts yet */
i++;
}
diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c
index aa4fbec143f..696eab9bd97 100644
--- a/src/backend/commands/vacuum.c
+++ b/src/backend/commands/vacuum.c
@@ -2224,9 +2224,9 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams params,
{
StdRdOptions *opts = (StdRdOptions *) rel->rd_options;
- if (opts && opts->vacuum_truncate_set)
+ if (opts && opts->vacuum_truncate != TERNARY_UNSET)
{
- if (opts->vacuum_truncate)
+ if (opts->vacuum_truncate == TERNARY_TRUE)
params.truncate = VACOPTVALUE_ENABLED;
else
params.truncate = VACOPTVALUE_DISABLED;
diff --git a/src/include/access/reloptions.h b/src/include/access/reloptions.h
index 2f08e1b0cf0..dfbef2babf2 100644
--- a/src/include/access/reloptions.h
+++ b/src/include/access/reloptions.h
@@ -29,6 +29,7 @@
typedef enum relopt_type
{
RELOPT_TYPE_BOOL,
+ RELOPT_TYPE_TERNARY, /* on, off, unset */
RELOPT_TYPE_INT,
RELOPT_TYPE_REAL,
RELOPT_TYPE_ENUM,
@@ -80,6 +81,7 @@ typedef struct relopt_value
union
{
bool bool_val;
+ pg_ternary ternary_val;
int int_val;
double real_val;
int enum_val;
@@ -94,6 +96,12 @@ typedef struct relopt_bool
bool default_val;
} relopt_bool;
+typedef struct relopt_ternary
+{
+ relopt_gen gen;
+ int default_val;
+} relopt_ternary;
+
typedef struct relopt_int
{
relopt_gen gen;
@@ -152,19 +160,6 @@ typedef struct
const char *optname; /* option's name */
relopt_type opttype; /* option's datatype */
int offset; /* offset of field in result struct */
-
- /*
- * isset_offset is an optional offset of a field in the result struct that
- * stores whether the option is explicitly set for the relation or if it
- * just picked up the default value. In most cases, this can be
- * accomplished by giving the reloption a special out-of-range default
- * value (e.g., some integer reloptions use -2), but this isn't always
- * possible. For example, a Boolean reloption cannot be given an
- * out-of-range default, so we need another way to discover the source of
- * its value. This offset is only used if given a value greater than
- * zero.
- */
- int isset_offset;
} relopt_parse_elt;
/* Local reloption definition */
@@ -195,6 +190,9 @@ typedef struct local_relopts
extern relopt_kind add_reloption_kind(void);
extern void add_bool_reloption(bits32 kinds, const char *name, const char *desc,
bool default_val, LOCKMODE lockmode);
+extern void add_ternary_reloption(bits32 kinds, const char *name,
+ const char *desc, pg_ternary default_val,
+ LOCKMODE lockmode);
extern void add_int_reloption(bits32 kinds, const char *name, const char *desc,
int default_val, int min_val, int max_val,
LOCKMODE lockmode);
@@ -214,6 +212,9 @@ extern void register_reloptions_validator(local_relopts *relopts,
extern void add_local_bool_reloption(local_relopts *relopts, const char *name,
const char *desc, bool default_val,
int offset);
+extern void add_local_ternary_reloption(local_relopts *relopts,
+ const char *name, const char *desc,
+ pg_ternary default_val, int offset);
extern void add_local_int_reloption(local_relopts *relopts, const char *name,
const char *desc, int default_val,
int min_val, int max_val, int offset);
diff --git a/src/include/postgres.h b/src/include/postgres.h
index 7d93fbce709..47c88780776 100644
--- a/src/include/postgres.h
+++ b/src/include/postgres.h
@@ -543,6 +543,21 @@ Float8GetDatum(float8 X)
* ----------------------------------------------------------------
*/
+/*
+ * pg_ternary
+ * Boolean value with an extra "unset" value
+ *
+ * This enum can be used for values that want to distinguish between true,
+ * false, and unset.
+*/
+
+typedef enum pg_ternary
+{
+ TERNARY_FALSE = 0,
+ TERNARY_TRUE = 1,
+ TERNARY_UNSET = -1
+} pg_ternary;
+
/*
* NON_EXEC_STATIC: It's sometimes useful to define a variable or function
* that is normally static but extern when using EXEC_BACKEND (see
diff --git a/src/include/utils/rel.h b/src/include/utils/rel.h
index d03ab247788..5feb18a5373 100644
--- a/src/include/utils/rel.h
+++ b/src/include/utils/rel.h
@@ -347,8 +347,7 @@ typedef struct StdRdOptions
bool user_catalog_table; /* use as an additional catalog relation */
int parallel_workers; /* max number of parallel workers */
StdRdOptIndexCleanup vacuum_index_cleanup; /* controls index vacuuming */
- bool vacuum_truncate; /* enables vacuum to truncate a relation */
- bool vacuum_truncate_set; /* whether vacuum_truncate is set */
+ pg_ternary vacuum_truncate; /* enables vacuum to truncate a relation */
/*
* Fraction of pages in a relation that vacuum can eagerly scan and fail
diff --git a/src/test/regress/expected/reloptions.out b/src/test/regress/expected/reloptions.out
index 9de19b4e3f1..1c99f79ab01 100644
--- a/src/test/regress/expected/reloptions.out
+++ b/src/test/regress/expected/reloptions.out
@@ -98,6 +98,42 @@ SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass;
{fillfactor=13,autovacuum_enabled=false}
(1 row)
+-- Tests for future (FIXME) ternary options
+-- behave as boolean option: accept unassigned name and truncated value
+DROP TABLE reloptions_test;
+CREATE TABLE reloptions_test(i INT) WITH (vacuum_truncate);
+SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass;
+ reloptions
+------------------------
+ {vacuum_truncate=true}
+(1 row)
+
+DROP TABLE reloptions_test;
+CREATE TABLE reloptions_test(i INT) WITH (vacuum_truncate=FaLS);
+SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass;
+ reloptions
+------------------------
+ {vacuum_truncate=fals}
+(1 row)
+
+-- preferred "true" alias is used when storing
+DROP TABLE reloptions_test;
+CREATE TABLE reloptions_test(i INT) WITH (vacuum_index_cleanup=on);
+SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass;
+ reloptions
+---------------------------
+ {vacuum_index_cleanup=on}
+(1 row)
+
+-- custom "third" value is available
+DROP TABLE reloptions_test;
+CREATE TABLE reloptions_test(i INT) WITH (vacuum_index_cleanup=auto);
+SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass;
+ reloptions
+-----------------------------
+ {vacuum_index_cleanup=auto}
+(1 row)
+
-- Test vacuum_truncate option
DROP TABLE reloptions_test;
CREATE TEMP TABLE reloptions_test(i INT NOT NULL, j text)
diff --git a/src/test/regress/sql/reloptions.sql b/src/test/regress/sql/reloptions.sql
index 24fbe0b478d..f5980dafcbc 100644
--- a/src/test/regress/sql/reloptions.sql
+++ b/src/test/regress/sql/reloptions.sql
@@ -59,6 +59,27 @@ UPDATE pg_class
ALTER TABLE reloptions_test RESET (illegal_option);
SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass;
+-- Tests for future (FIXME) ternary options
+
+-- behave as boolean option: accept unassigned name and truncated value
+DROP TABLE reloptions_test;
+CREATE TABLE reloptions_test(i INT) WITH (vacuum_truncate);
+SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass;
+
+DROP TABLE reloptions_test;
+CREATE TABLE reloptions_test(i INT) WITH (vacuum_truncate=FaLS);
+SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass;
+
+-- preferred "true" alias is used when storing
+DROP TABLE reloptions_test;
+CREATE TABLE reloptions_test(i INT) WITH (vacuum_index_cleanup=on);
+SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass;
+
+-- custom "third" value is available
+DROP TABLE reloptions_test;
+CREATE TABLE reloptions_test(i INT) WITH (vacuum_index_cleanup=auto);
+SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass;
+
-- Test vacuum_truncate option
DROP TABLE reloptions_test;
diff --git a/src/tools/pgindent/typedefs.list b/src/tools/pgindent/typedefs.list
index 3f3a888fd0e..1c8610fd46c 100644
--- a/src/tools/pgindent/typedefs.list
+++ b/src/tools/pgindent/typedefs.list
@@ -3952,6 +3952,7 @@ pg_sha512_ctx
pg_snapshot
pg_special_case
pg_stack_base_t
+pg_ternary
pg_time_t
pg_time_usec_t
pg_tz
@@ -4079,6 +4080,7 @@ relopt_kind
relopt_parse_elt
relopt_real
relopt_string
+relopt_ternary
relopt_type
relopt_value
relopts_validator
--
2.47.3
--c36t3fndxhkzbl5z--
^ permalink raw reply [nested|flat] 868+ messages in thread
* [PATCH v3] Introduce ternary reloptions
@ 2026-01-16 15:04 Álvaro Herrera <[email protected]>
0 siblings, 0 replies; 868+ messages in thread
From: Álvaro Herrera @ 2026-01-16 15:04 UTC (permalink / raw)
Introduce ternary reloption as a replacement for current
`vacuum_truncate` implementation. Remove the `vacuum_truncate_set`
separate flag and use TERNARY_UNSET instead.
This could also be used for other options such as `vacuum_index_cleanup`
and `buffering`, but lets get the scaffolding in first.
Discussion: https://postgr.es/m/3474141.usfYGdeWWP@thinkpad-pgpro
---
src/backend/access/common/reloptions.c | 137 ++++++++++++++++++-----
src/backend/commands/vacuum.c | 4 +-
src/include/access/reloptions.h | 27 ++---
src/include/postgres.h | 15 +++
src/include/utils/rel.h | 3 +-
src/test/regress/expected/reloptions.out | 36 ++++++
src/test/regress/sql/reloptions.sql | 21 ++++
src/tools/pgindent/typedefs.list | 2 +
8 files changed, 202 insertions(+), 43 deletions(-)
diff --git a/src/backend/access/common/reloptions.c b/src/backend/access/common/reloptions.c
index 0b83f98ed5f..6f1f577581a 100644
--- a/src/backend/access/common/reloptions.c
+++ b/src/backend/access/common/reloptions.c
@@ -40,9 +40,9 @@
*
* To add an option:
*
- * (i) decide on a type (bool, integer, real, enum, string), name, default
- * value, upper and lower bounds (if applicable); for strings, consider a
- * validation routine.
+ * (i) decide on a type (bool, ternary, integer, real, enum, string), name,
+ * default value, upper and lower bounds (if applicable); for strings,
+ * consider a validation routine.
* (ii) add a record below (or use add_<type>_reloption).
* (iii) add it to the appropriate options struct (perhaps StdRdOptions)
* (iv) add it to the appropriate handling routine (perhaps
@@ -147,15 +147,6 @@ static relopt_bool boolRelOpts[] =
},
false
},
- {
- {
- "vacuum_truncate",
- "Enables vacuum to truncate empty pages at the end of this table",
- RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
- ShareUpdateExclusiveLock
- },
- true
- },
{
{
"deduplicate_items",
@@ -170,6 +161,25 @@ static relopt_bool boolRelOpts[] =
{{NULL}}
};
+static relopt_ternary ternaryRelOpts[] =
+{
+ {
+ {
+ "vacuum_truncate",
+ "Enables vacuum to truncate empty pages at the end of this table",
+ RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
+ ShareUpdateExclusiveLock
+ },
+ TERNARY_UNSET
+ },
+ /* list terminator */
+ {
+ {
+ NULL
+ }
+ }
+};
+
static relopt_int intRelOpts[] =
{
{
@@ -609,6 +619,13 @@ initialize_reloptions(void)
boolRelOpts[i].gen.lockmode));
j++;
}
+ for (i = 0; ternaryRelOpts[i].gen.name; i++)
+ {
+ Assert(DoLockModesConflict(ternaryRelOpts[i].gen.lockmode,
+ ternaryRelOpts[i].gen.lockmode));
+ j++;
+ }
+
for (i = 0; intRelOpts[i].gen.name; i++)
{
Assert(DoLockModesConflict(intRelOpts[i].gen.lockmode,
@@ -649,6 +666,14 @@ initialize_reloptions(void)
j++;
}
+ for (i = 0; ternaryRelOpts[i].gen.name; i++)
+ {
+ relOpts[j] = &ternaryRelOpts[i].gen;
+ relOpts[j]->type = RELOPT_TYPE_TERNARY;
+ relOpts[j]->namelen = strlen(relOpts[j]->name);
+ j++;
+ }
+
for (i = 0; intRelOpts[i].gen.name; i++)
{
relOpts[j] = &intRelOpts[i].gen;
@@ -809,6 +834,9 @@ allocate_reloption(bits32 kinds, int type, const char *name, const char *desc,
case RELOPT_TYPE_BOOL:
size = sizeof(relopt_bool);
break;
+ case RELOPT_TYPE_TERNARY:
+ size = sizeof(relopt_ternary);
+ break;
case RELOPT_TYPE_INT:
size = sizeof(relopt_int);
break;
@@ -892,6 +920,57 @@ add_local_bool_reloption(local_relopts *relopts, const char *name,
add_local_reloption(relopts, (relopt_gen *) newoption, offset);
}
+/*
+ * init_ternary_reloption
+ * Allocate and initialize a new ternary reloption
+ */
+static relopt_ternary *
+init_ternary_reloption(bits32 kinds, const char *name, const char *desc,
+ pg_ternary default_val, LOCKMODE lockmode)
+{
+ relopt_ternary *newoption;
+
+ newoption = (relopt_ternary *)
+ allocate_reloption(kinds, RELOPT_TYPE_TERNARY, name, desc, lockmode);
+ newoption->default_val = default_val;
+
+ return newoption;
+}
+
+/*
+ * add_ternary_reloption
+ * Add a new ternary reloption
+ */
+void
+add_ternary_reloption(bits32 kinds, const char *name, const char *desc,
+ pg_ternary default_val, LOCKMODE lockmode)
+{
+ relopt_ternary *newoption;
+
+ newoption =
+ init_ternary_reloption(kinds, name, desc, default_val, lockmode);
+
+ add_reloption((relopt_gen *) newoption);
+}
+
+/*
+ * add_local_ternary_reloption
+ * Add a new ternary local reloption
+ *
+ * 'offset' is offset of ternary-typed field.
+ */
+void
+add_local_ternary_reloption(local_relopts *relopts, const char *name,
+ const char *desc, pg_ternary default_val,
+ int offset)
+{
+ relopt_ternary *newoption;
+
+ newoption =
+ init_ternary_reloption(RELOPT_KIND_LOCAL, name, desc, default_val, 0);
+
+ add_local_reloption(relopts, (relopt_gen *) newoption, offset);
+}
/*
* init_real_reloption
@@ -1626,6 +1705,19 @@ parse_one_reloption(relopt_value *option, char *text_str, int text_len,
option->gen->name, value)));
}
break;
+ case RELOPT_TYPE_TERNARY:
+ {
+ bool b;
+
+ parsed = parse_bool(value, &b);
+ option->values.ternary_val = b ? TERNARY_TRUE : TERNARY_FALSE;
+ if (validate && !parsed)
+ ereport(ERROR,
+ (errcode(ERRCODE_INVALID_PARAMETER_VALUE),
+ errmsg("invalid value for ternary option \"%s\": %s",
+ option->gen->name, value)));
+ }
+ break;
case RELOPT_TYPE_INT:
{
relopt_int *optint = (relopt_int *) option->gen;
@@ -1789,17 +1881,6 @@ fillRelOptions(void *rdopts, Size basesize,
char *itempos = ((char *) rdopts) + elems[j].offset;
char *string_val;
- /*
- * If isset_offset is provided, store whether the reloption is
- * set there.
- */
- if (elems[j].isset_offset > 0)
- {
- char *setpos = ((char *) rdopts) + elems[j].isset_offset;
-
- *(bool *) setpos = options[i].isset;
- }
-
switch (options[i].gen->type)
{
case RELOPT_TYPE_BOOL:
@@ -1807,6 +1888,11 @@ fillRelOptions(void *rdopts, Size basesize,
options[i].values.bool_val :
((relopt_bool *) options[i].gen)->default_val;
break;
+ case RELOPT_TYPE_TERNARY:
+ *(pg_ternary *) itempos = options[i].isset ?
+ options[i].values.ternary_val :
+ ((relopt_ternary *) options[i].gen)->default_val;
+ break;
case RELOPT_TYPE_INT:
*(int *) itempos = options[i].isset ?
options[i].values.int_val :
@@ -1923,8 +2009,8 @@ default_reloptions(Datum reloptions, bool validate, relopt_kind kind)
offsetof(StdRdOptions, parallel_workers)},
{"vacuum_index_cleanup", RELOPT_TYPE_ENUM,
offsetof(StdRdOptions, vacuum_index_cleanup)},
- {"vacuum_truncate", RELOPT_TYPE_BOOL,
- offsetof(StdRdOptions, vacuum_truncate), offsetof(StdRdOptions, vacuum_truncate_set)},
+ {"vacuum_truncate", RELOPT_TYPE_TERNARY,
+ offsetof(StdRdOptions, vacuum_truncate)},
{"vacuum_max_eager_freeze_failure_rate", RELOPT_TYPE_REAL,
offsetof(StdRdOptions, vacuum_max_eager_freeze_failure_rate)}
};
@@ -2004,7 +2090,6 @@ build_local_reloptions(local_relopts *relopts, Datum options, bool validate)
elems[i].optname = opt->option->name;
elems[i].opttype = opt->option->type;
elems[i].offset = opt->offset;
- elems[i].isset_offset = 0; /* not supported for local relopts yet */
i++;
}
diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c
index aa4fbec143f..696eab9bd97 100644
--- a/src/backend/commands/vacuum.c
+++ b/src/backend/commands/vacuum.c
@@ -2224,9 +2224,9 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams params,
{
StdRdOptions *opts = (StdRdOptions *) rel->rd_options;
- if (opts && opts->vacuum_truncate_set)
+ if (opts && opts->vacuum_truncate != TERNARY_UNSET)
{
- if (opts->vacuum_truncate)
+ if (opts->vacuum_truncate == TERNARY_TRUE)
params.truncate = VACOPTVALUE_ENABLED;
else
params.truncate = VACOPTVALUE_DISABLED;
diff --git a/src/include/access/reloptions.h b/src/include/access/reloptions.h
index 2f08e1b0cf0..dfbef2babf2 100644
--- a/src/include/access/reloptions.h
+++ b/src/include/access/reloptions.h
@@ -29,6 +29,7 @@
typedef enum relopt_type
{
RELOPT_TYPE_BOOL,
+ RELOPT_TYPE_TERNARY, /* on, off, unset */
RELOPT_TYPE_INT,
RELOPT_TYPE_REAL,
RELOPT_TYPE_ENUM,
@@ -80,6 +81,7 @@ typedef struct relopt_value
union
{
bool bool_val;
+ pg_ternary ternary_val;
int int_val;
double real_val;
int enum_val;
@@ -94,6 +96,12 @@ typedef struct relopt_bool
bool default_val;
} relopt_bool;
+typedef struct relopt_ternary
+{
+ relopt_gen gen;
+ int default_val;
+} relopt_ternary;
+
typedef struct relopt_int
{
relopt_gen gen;
@@ -152,19 +160,6 @@ typedef struct
const char *optname; /* option's name */
relopt_type opttype; /* option's datatype */
int offset; /* offset of field in result struct */
-
- /*
- * isset_offset is an optional offset of a field in the result struct that
- * stores whether the option is explicitly set for the relation or if it
- * just picked up the default value. In most cases, this can be
- * accomplished by giving the reloption a special out-of-range default
- * value (e.g., some integer reloptions use -2), but this isn't always
- * possible. For example, a Boolean reloption cannot be given an
- * out-of-range default, so we need another way to discover the source of
- * its value. This offset is only used if given a value greater than
- * zero.
- */
- int isset_offset;
} relopt_parse_elt;
/* Local reloption definition */
@@ -195,6 +190,9 @@ typedef struct local_relopts
extern relopt_kind add_reloption_kind(void);
extern void add_bool_reloption(bits32 kinds, const char *name, const char *desc,
bool default_val, LOCKMODE lockmode);
+extern void add_ternary_reloption(bits32 kinds, const char *name,
+ const char *desc, pg_ternary default_val,
+ LOCKMODE lockmode);
extern void add_int_reloption(bits32 kinds, const char *name, const char *desc,
int default_val, int min_val, int max_val,
LOCKMODE lockmode);
@@ -214,6 +212,9 @@ extern void register_reloptions_validator(local_relopts *relopts,
extern void add_local_bool_reloption(local_relopts *relopts, const char *name,
const char *desc, bool default_val,
int offset);
+extern void add_local_ternary_reloption(local_relopts *relopts,
+ const char *name, const char *desc,
+ pg_ternary default_val, int offset);
extern void add_local_int_reloption(local_relopts *relopts, const char *name,
const char *desc, int default_val,
int min_val, int max_val, int offset);
diff --git a/src/include/postgres.h b/src/include/postgres.h
index 7d93fbce709..47c88780776 100644
--- a/src/include/postgres.h
+++ b/src/include/postgres.h
@@ -543,6 +543,21 @@ Float8GetDatum(float8 X)
* ----------------------------------------------------------------
*/
+/*
+ * pg_ternary
+ * Boolean value with an extra "unset" value
+ *
+ * This enum can be used for values that want to distinguish between true,
+ * false, and unset.
+*/
+
+typedef enum pg_ternary
+{
+ TERNARY_FALSE = 0,
+ TERNARY_TRUE = 1,
+ TERNARY_UNSET = -1
+} pg_ternary;
+
/*
* NON_EXEC_STATIC: It's sometimes useful to define a variable or function
* that is normally static but extern when using EXEC_BACKEND (see
diff --git a/src/include/utils/rel.h b/src/include/utils/rel.h
index d03ab247788..5feb18a5373 100644
--- a/src/include/utils/rel.h
+++ b/src/include/utils/rel.h
@@ -347,8 +347,7 @@ typedef struct StdRdOptions
bool user_catalog_table; /* use as an additional catalog relation */
int parallel_workers; /* max number of parallel workers */
StdRdOptIndexCleanup vacuum_index_cleanup; /* controls index vacuuming */
- bool vacuum_truncate; /* enables vacuum to truncate a relation */
- bool vacuum_truncate_set; /* whether vacuum_truncate is set */
+ pg_ternary vacuum_truncate; /* enables vacuum to truncate a relation */
/*
* Fraction of pages in a relation that vacuum can eagerly scan and fail
diff --git a/src/test/regress/expected/reloptions.out b/src/test/regress/expected/reloptions.out
index 9de19b4e3f1..1c99f79ab01 100644
--- a/src/test/regress/expected/reloptions.out
+++ b/src/test/regress/expected/reloptions.out
@@ -98,6 +98,42 @@ SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass;
{fillfactor=13,autovacuum_enabled=false}
(1 row)
+-- Tests for future (FIXME) ternary options
+-- behave as boolean option: accept unassigned name and truncated value
+DROP TABLE reloptions_test;
+CREATE TABLE reloptions_test(i INT) WITH (vacuum_truncate);
+SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass;
+ reloptions
+------------------------
+ {vacuum_truncate=true}
+(1 row)
+
+DROP TABLE reloptions_test;
+CREATE TABLE reloptions_test(i INT) WITH (vacuum_truncate=FaLS);
+SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass;
+ reloptions
+------------------------
+ {vacuum_truncate=fals}
+(1 row)
+
+-- preferred "true" alias is used when storing
+DROP TABLE reloptions_test;
+CREATE TABLE reloptions_test(i INT) WITH (vacuum_index_cleanup=on);
+SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass;
+ reloptions
+---------------------------
+ {vacuum_index_cleanup=on}
+(1 row)
+
+-- custom "third" value is available
+DROP TABLE reloptions_test;
+CREATE TABLE reloptions_test(i INT) WITH (vacuum_index_cleanup=auto);
+SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass;
+ reloptions
+-----------------------------
+ {vacuum_index_cleanup=auto}
+(1 row)
+
-- Test vacuum_truncate option
DROP TABLE reloptions_test;
CREATE TEMP TABLE reloptions_test(i INT NOT NULL, j text)
diff --git a/src/test/regress/sql/reloptions.sql b/src/test/regress/sql/reloptions.sql
index 24fbe0b478d..f5980dafcbc 100644
--- a/src/test/regress/sql/reloptions.sql
+++ b/src/test/regress/sql/reloptions.sql
@@ -59,6 +59,27 @@ UPDATE pg_class
ALTER TABLE reloptions_test RESET (illegal_option);
SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass;
+-- Tests for future (FIXME) ternary options
+
+-- behave as boolean option: accept unassigned name and truncated value
+DROP TABLE reloptions_test;
+CREATE TABLE reloptions_test(i INT) WITH (vacuum_truncate);
+SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass;
+
+DROP TABLE reloptions_test;
+CREATE TABLE reloptions_test(i INT) WITH (vacuum_truncate=FaLS);
+SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass;
+
+-- preferred "true" alias is used when storing
+DROP TABLE reloptions_test;
+CREATE TABLE reloptions_test(i INT) WITH (vacuum_index_cleanup=on);
+SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass;
+
+-- custom "third" value is available
+DROP TABLE reloptions_test;
+CREATE TABLE reloptions_test(i INT) WITH (vacuum_index_cleanup=auto);
+SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass;
+
-- Test vacuum_truncate option
DROP TABLE reloptions_test;
diff --git a/src/tools/pgindent/typedefs.list b/src/tools/pgindent/typedefs.list
index 3f3a888fd0e..1c8610fd46c 100644
--- a/src/tools/pgindent/typedefs.list
+++ b/src/tools/pgindent/typedefs.list
@@ -3952,6 +3952,7 @@ pg_sha512_ctx
pg_snapshot
pg_special_case
pg_stack_base_t
+pg_ternary
pg_time_t
pg_time_usec_t
pg_tz
@@ -4079,6 +4080,7 @@ relopt_kind
relopt_parse_elt
relopt_real
relopt_string
+relopt_ternary
relopt_type
relopt_value
relopts_validator
--
2.47.3
--c36t3fndxhkzbl5z--
^ permalink raw reply [nested|flat] 868+ messages in thread
* [PATCH v3] Introduce ternary reloptions
@ 2026-01-16 15:04 Álvaro Herrera <[email protected]>
0 siblings, 0 replies; 868+ messages in thread
From: Álvaro Herrera @ 2026-01-16 15:04 UTC (permalink / raw)
Introduce ternary reloption as a replacement for current
`vacuum_truncate` implementation. Remove the `vacuum_truncate_set`
separate flag and use TERNARY_UNSET instead.
This could also be used for other options such as `vacuum_index_cleanup`
and `buffering`, but lets get the scaffolding in first.
Discussion: https://postgr.es/m/3474141.usfYGdeWWP@thinkpad-pgpro
---
src/backend/access/common/reloptions.c | 137 ++++++++++++++++++-----
src/backend/commands/vacuum.c | 4 +-
src/include/access/reloptions.h | 27 ++---
src/include/postgres.h | 15 +++
src/include/utils/rel.h | 3 +-
src/test/regress/expected/reloptions.out | 36 ++++++
src/test/regress/sql/reloptions.sql | 21 ++++
src/tools/pgindent/typedefs.list | 2 +
8 files changed, 202 insertions(+), 43 deletions(-)
diff --git a/src/backend/access/common/reloptions.c b/src/backend/access/common/reloptions.c
index 0b83f98ed5f..6f1f577581a 100644
--- a/src/backend/access/common/reloptions.c
+++ b/src/backend/access/common/reloptions.c
@@ -40,9 +40,9 @@
*
* To add an option:
*
- * (i) decide on a type (bool, integer, real, enum, string), name, default
- * value, upper and lower bounds (if applicable); for strings, consider a
- * validation routine.
+ * (i) decide on a type (bool, ternary, integer, real, enum, string), name,
+ * default value, upper and lower bounds (if applicable); for strings,
+ * consider a validation routine.
* (ii) add a record below (or use add_<type>_reloption).
* (iii) add it to the appropriate options struct (perhaps StdRdOptions)
* (iv) add it to the appropriate handling routine (perhaps
@@ -147,15 +147,6 @@ static relopt_bool boolRelOpts[] =
},
false
},
- {
- {
- "vacuum_truncate",
- "Enables vacuum to truncate empty pages at the end of this table",
- RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
- ShareUpdateExclusiveLock
- },
- true
- },
{
{
"deduplicate_items",
@@ -170,6 +161,25 @@ static relopt_bool boolRelOpts[] =
{{NULL}}
};
+static relopt_ternary ternaryRelOpts[] =
+{
+ {
+ {
+ "vacuum_truncate",
+ "Enables vacuum to truncate empty pages at the end of this table",
+ RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
+ ShareUpdateExclusiveLock
+ },
+ TERNARY_UNSET
+ },
+ /* list terminator */
+ {
+ {
+ NULL
+ }
+ }
+};
+
static relopt_int intRelOpts[] =
{
{
@@ -609,6 +619,13 @@ initialize_reloptions(void)
boolRelOpts[i].gen.lockmode));
j++;
}
+ for (i = 0; ternaryRelOpts[i].gen.name; i++)
+ {
+ Assert(DoLockModesConflict(ternaryRelOpts[i].gen.lockmode,
+ ternaryRelOpts[i].gen.lockmode));
+ j++;
+ }
+
for (i = 0; intRelOpts[i].gen.name; i++)
{
Assert(DoLockModesConflict(intRelOpts[i].gen.lockmode,
@@ -649,6 +666,14 @@ initialize_reloptions(void)
j++;
}
+ for (i = 0; ternaryRelOpts[i].gen.name; i++)
+ {
+ relOpts[j] = &ternaryRelOpts[i].gen;
+ relOpts[j]->type = RELOPT_TYPE_TERNARY;
+ relOpts[j]->namelen = strlen(relOpts[j]->name);
+ j++;
+ }
+
for (i = 0; intRelOpts[i].gen.name; i++)
{
relOpts[j] = &intRelOpts[i].gen;
@@ -809,6 +834,9 @@ allocate_reloption(bits32 kinds, int type, const char *name, const char *desc,
case RELOPT_TYPE_BOOL:
size = sizeof(relopt_bool);
break;
+ case RELOPT_TYPE_TERNARY:
+ size = sizeof(relopt_ternary);
+ break;
case RELOPT_TYPE_INT:
size = sizeof(relopt_int);
break;
@@ -892,6 +920,57 @@ add_local_bool_reloption(local_relopts *relopts, const char *name,
add_local_reloption(relopts, (relopt_gen *) newoption, offset);
}
+/*
+ * init_ternary_reloption
+ * Allocate and initialize a new ternary reloption
+ */
+static relopt_ternary *
+init_ternary_reloption(bits32 kinds, const char *name, const char *desc,
+ pg_ternary default_val, LOCKMODE lockmode)
+{
+ relopt_ternary *newoption;
+
+ newoption = (relopt_ternary *)
+ allocate_reloption(kinds, RELOPT_TYPE_TERNARY, name, desc, lockmode);
+ newoption->default_val = default_val;
+
+ return newoption;
+}
+
+/*
+ * add_ternary_reloption
+ * Add a new ternary reloption
+ */
+void
+add_ternary_reloption(bits32 kinds, const char *name, const char *desc,
+ pg_ternary default_val, LOCKMODE lockmode)
+{
+ relopt_ternary *newoption;
+
+ newoption =
+ init_ternary_reloption(kinds, name, desc, default_val, lockmode);
+
+ add_reloption((relopt_gen *) newoption);
+}
+
+/*
+ * add_local_ternary_reloption
+ * Add a new ternary local reloption
+ *
+ * 'offset' is offset of ternary-typed field.
+ */
+void
+add_local_ternary_reloption(local_relopts *relopts, const char *name,
+ const char *desc, pg_ternary default_val,
+ int offset)
+{
+ relopt_ternary *newoption;
+
+ newoption =
+ init_ternary_reloption(RELOPT_KIND_LOCAL, name, desc, default_val, 0);
+
+ add_local_reloption(relopts, (relopt_gen *) newoption, offset);
+}
/*
* init_real_reloption
@@ -1626,6 +1705,19 @@ parse_one_reloption(relopt_value *option, char *text_str, int text_len,
option->gen->name, value)));
}
break;
+ case RELOPT_TYPE_TERNARY:
+ {
+ bool b;
+
+ parsed = parse_bool(value, &b);
+ option->values.ternary_val = b ? TERNARY_TRUE : TERNARY_FALSE;
+ if (validate && !parsed)
+ ereport(ERROR,
+ (errcode(ERRCODE_INVALID_PARAMETER_VALUE),
+ errmsg("invalid value for ternary option \"%s\": %s",
+ option->gen->name, value)));
+ }
+ break;
case RELOPT_TYPE_INT:
{
relopt_int *optint = (relopt_int *) option->gen;
@@ -1789,17 +1881,6 @@ fillRelOptions(void *rdopts, Size basesize,
char *itempos = ((char *) rdopts) + elems[j].offset;
char *string_val;
- /*
- * If isset_offset is provided, store whether the reloption is
- * set there.
- */
- if (elems[j].isset_offset > 0)
- {
- char *setpos = ((char *) rdopts) + elems[j].isset_offset;
-
- *(bool *) setpos = options[i].isset;
- }
-
switch (options[i].gen->type)
{
case RELOPT_TYPE_BOOL:
@@ -1807,6 +1888,11 @@ fillRelOptions(void *rdopts, Size basesize,
options[i].values.bool_val :
((relopt_bool *) options[i].gen)->default_val;
break;
+ case RELOPT_TYPE_TERNARY:
+ *(pg_ternary *) itempos = options[i].isset ?
+ options[i].values.ternary_val :
+ ((relopt_ternary *) options[i].gen)->default_val;
+ break;
case RELOPT_TYPE_INT:
*(int *) itempos = options[i].isset ?
options[i].values.int_val :
@@ -1923,8 +2009,8 @@ default_reloptions(Datum reloptions, bool validate, relopt_kind kind)
offsetof(StdRdOptions, parallel_workers)},
{"vacuum_index_cleanup", RELOPT_TYPE_ENUM,
offsetof(StdRdOptions, vacuum_index_cleanup)},
- {"vacuum_truncate", RELOPT_TYPE_BOOL,
- offsetof(StdRdOptions, vacuum_truncate), offsetof(StdRdOptions, vacuum_truncate_set)},
+ {"vacuum_truncate", RELOPT_TYPE_TERNARY,
+ offsetof(StdRdOptions, vacuum_truncate)},
{"vacuum_max_eager_freeze_failure_rate", RELOPT_TYPE_REAL,
offsetof(StdRdOptions, vacuum_max_eager_freeze_failure_rate)}
};
@@ -2004,7 +2090,6 @@ build_local_reloptions(local_relopts *relopts, Datum options, bool validate)
elems[i].optname = opt->option->name;
elems[i].opttype = opt->option->type;
elems[i].offset = opt->offset;
- elems[i].isset_offset = 0; /* not supported for local relopts yet */
i++;
}
diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c
index aa4fbec143f..696eab9bd97 100644
--- a/src/backend/commands/vacuum.c
+++ b/src/backend/commands/vacuum.c
@@ -2224,9 +2224,9 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams params,
{
StdRdOptions *opts = (StdRdOptions *) rel->rd_options;
- if (opts && opts->vacuum_truncate_set)
+ if (opts && opts->vacuum_truncate != TERNARY_UNSET)
{
- if (opts->vacuum_truncate)
+ if (opts->vacuum_truncate == TERNARY_TRUE)
params.truncate = VACOPTVALUE_ENABLED;
else
params.truncate = VACOPTVALUE_DISABLED;
diff --git a/src/include/access/reloptions.h b/src/include/access/reloptions.h
index 2f08e1b0cf0..dfbef2babf2 100644
--- a/src/include/access/reloptions.h
+++ b/src/include/access/reloptions.h
@@ -29,6 +29,7 @@
typedef enum relopt_type
{
RELOPT_TYPE_BOOL,
+ RELOPT_TYPE_TERNARY, /* on, off, unset */
RELOPT_TYPE_INT,
RELOPT_TYPE_REAL,
RELOPT_TYPE_ENUM,
@@ -80,6 +81,7 @@ typedef struct relopt_value
union
{
bool bool_val;
+ pg_ternary ternary_val;
int int_val;
double real_val;
int enum_val;
@@ -94,6 +96,12 @@ typedef struct relopt_bool
bool default_val;
} relopt_bool;
+typedef struct relopt_ternary
+{
+ relopt_gen gen;
+ int default_val;
+} relopt_ternary;
+
typedef struct relopt_int
{
relopt_gen gen;
@@ -152,19 +160,6 @@ typedef struct
const char *optname; /* option's name */
relopt_type opttype; /* option's datatype */
int offset; /* offset of field in result struct */
-
- /*
- * isset_offset is an optional offset of a field in the result struct that
- * stores whether the option is explicitly set for the relation or if it
- * just picked up the default value. In most cases, this can be
- * accomplished by giving the reloption a special out-of-range default
- * value (e.g., some integer reloptions use -2), but this isn't always
- * possible. For example, a Boolean reloption cannot be given an
- * out-of-range default, so we need another way to discover the source of
- * its value. This offset is only used if given a value greater than
- * zero.
- */
- int isset_offset;
} relopt_parse_elt;
/* Local reloption definition */
@@ -195,6 +190,9 @@ typedef struct local_relopts
extern relopt_kind add_reloption_kind(void);
extern void add_bool_reloption(bits32 kinds, const char *name, const char *desc,
bool default_val, LOCKMODE lockmode);
+extern void add_ternary_reloption(bits32 kinds, const char *name,
+ const char *desc, pg_ternary default_val,
+ LOCKMODE lockmode);
extern void add_int_reloption(bits32 kinds, const char *name, const char *desc,
int default_val, int min_val, int max_val,
LOCKMODE lockmode);
@@ -214,6 +212,9 @@ extern void register_reloptions_validator(local_relopts *relopts,
extern void add_local_bool_reloption(local_relopts *relopts, const char *name,
const char *desc, bool default_val,
int offset);
+extern void add_local_ternary_reloption(local_relopts *relopts,
+ const char *name, const char *desc,
+ pg_ternary default_val, int offset);
extern void add_local_int_reloption(local_relopts *relopts, const char *name,
const char *desc, int default_val,
int min_val, int max_val, int offset);
diff --git a/src/include/postgres.h b/src/include/postgres.h
index 7d93fbce709..47c88780776 100644
--- a/src/include/postgres.h
+++ b/src/include/postgres.h
@@ -543,6 +543,21 @@ Float8GetDatum(float8 X)
* ----------------------------------------------------------------
*/
+/*
+ * pg_ternary
+ * Boolean value with an extra "unset" value
+ *
+ * This enum can be used for values that want to distinguish between true,
+ * false, and unset.
+*/
+
+typedef enum pg_ternary
+{
+ TERNARY_FALSE = 0,
+ TERNARY_TRUE = 1,
+ TERNARY_UNSET = -1
+} pg_ternary;
+
/*
* NON_EXEC_STATIC: It's sometimes useful to define a variable or function
* that is normally static but extern when using EXEC_BACKEND (see
diff --git a/src/include/utils/rel.h b/src/include/utils/rel.h
index d03ab247788..5feb18a5373 100644
--- a/src/include/utils/rel.h
+++ b/src/include/utils/rel.h
@@ -347,8 +347,7 @@ typedef struct StdRdOptions
bool user_catalog_table; /* use as an additional catalog relation */
int parallel_workers; /* max number of parallel workers */
StdRdOptIndexCleanup vacuum_index_cleanup; /* controls index vacuuming */
- bool vacuum_truncate; /* enables vacuum to truncate a relation */
- bool vacuum_truncate_set; /* whether vacuum_truncate is set */
+ pg_ternary vacuum_truncate; /* enables vacuum to truncate a relation */
/*
* Fraction of pages in a relation that vacuum can eagerly scan and fail
diff --git a/src/test/regress/expected/reloptions.out b/src/test/regress/expected/reloptions.out
index 9de19b4e3f1..1c99f79ab01 100644
--- a/src/test/regress/expected/reloptions.out
+++ b/src/test/regress/expected/reloptions.out
@@ -98,6 +98,42 @@ SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass;
{fillfactor=13,autovacuum_enabled=false}
(1 row)
+-- Tests for future (FIXME) ternary options
+-- behave as boolean option: accept unassigned name and truncated value
+DROP TABLE reloptions_test;
+CREATE TABLE reloptions_test(i INT) WITH (vacuum_truncate);
+SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass;
+ reloptions
+------------------------
+ {vacuum_truncate=true}
+(1 row)
+
+DROP TABLE reloptions_test;
+CREATE TABLE reloptions_test(i INT) WITH (vacuum_truncate=FaLS);
+SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass;
+ reloptions
+------------------------
+ {vacuum_truncate=fals}
+(1 row)
+
+-- preferred "true" alias is used when storing
+DROP TABLE reloptions_test;
+CREATE TABLE reloptions_test(i INT) WITH (vacuum_index_cleanup=on);
+SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass;
+ reloptions
+---------------------------
+ {vacuum_index_cleanup=on}
+(1 row)
+
+-- custom "third" value is available
+DROP TABLE reloptions_test;
+CREATE TABLE reloptions_test(i INT) WITH (vacuum_index_cleanup=auto);
+SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass;
+ reloptions
+-----------------------------
+ {vacuum_index_cleanup=auto}
+(1 row)
+
-- Test vacuum_truncate option
DROP TABLE reloptions_test;
CREATE TEMP TABLE reloptions_test(i INT NOT NULL, j text)
diff --git a/src/test/regress/sql/reloptions.sql b/src/test/regress/sql/reloptions.sql
index 24fbe0b478d..f5980dafcbc 100644
--- a/src/test/regress/sql/reloptions.sql
+++ b/src/test/regress/sql/reloptions.sql
@@ -59,6 +59,27 @@ UPDATE pg_class
ALTER TABLE reloptions_test RESET (illegal_option);
SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass;
+-- Tests for future (FIXME) ternary options
+
+-- behave as boolean option: accept unassigned name and truncated value
+DROP TABLE reloptions_test;
+CREATE TABLE reloptions_test(i INT) WITH (vacuum_truncate);
+SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass;
+
+DROP TABLE reloptions_test;
+CREATE TABLE reloptions_test(i INT) WITH (vacuum_truncate=FaLS);
+SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass;
+
+-- preferred "true" alias is used when storing
+DROP TABLE reloptions_test;
+CREATE TABLE reloptions_test(i INT) WITH (vacuum_index_cleanup=on);
+SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass;
+
+-- custom "third" value is available
+DROP TABLE reloptions_test;
+CREATE TABLE reloptions_test(i INT) WITH (vacuum_index_cleanup=auto);
+SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass;
+
-- Test vacuum_truncate option
DROP TABLE reloptions_test;
diff --git a/src/tools/pgindent/typedefs.list b/src/tools/pgindent/typedefs.list
index 3f3a888fd0e..1c8610fd46c 100644
--- a/src/tools/pgindent/typedefs.list
+++ b/src/tools/pgindent/typedefs.list
@@ -3952,6 +3952,7 @@ pg_sha512_ctx
pg_snapshot
pg_special_case
pg_stack_base_t
+pg_ternary
pg_time_t
pg_time_usec_t
pg_tz
@@ -4079,6 +4080,7 @@ relopt_kind
relopt_parse_elt
relopt_real
relopt_string
+relopt_ternary
relopt_type
relopt_value
relopts_validator
--
2.47.3
--c36t3fndxhkzbl5z--
^ permalink raw reply [nested|flat] 868+ messages in thread
* [PATCH v3] Introduce ternary reloptions
@ 2026-01-16 15:04 Álvaro Herrera <[email protected]>
0 siblings, 0 replies; 868+ messages in thread
From: Álvaro Herrera @ 2026-01-16 15:04 UTC (permalink / raw)
Introduce ternary reloption as a replacement for current
`vacuum_truncate` implementation. Remove the `vacuum_truncate_set`
separate flag and use TERNARY_UNSET instead.
This could also be used for other options such as `vacuum_index_cleanup`
and `buffering`, but lets get the scaffolding in first.
Discussion: https://postgr.es/m/3474141.usfYGdeWWP@thinkpad-pgpro
---
src/backend/access/common/reloptions.c | 137 ++++++++++++++++++-----
src/backend/commands/vacuum.c | 4 +-
src/include/access/reloptions.h | 27 ++---
src/include/postgres.h | 15 +++
src/include/utils/rel.h | 3 +-
src/test/regress/expected/reloptions.out | 36 ++++++
src/test/regress/sql/reloptions.sql | 21 ++++
src/tools/pgindent/typedefs.list | 2 +
8 files changed, 202 insertions(+), 43 deletions(-)
diff --git a/src/backend/access/common/reloptions.c b/src/backend/access/common/reloptions.c
index 0b83f98ed5f..6f1f577581a 100644
--- a/src/backend/access/common/reloptions.c
+++ b/src/backend/access/common/reloptions.c
@@ -40,9 +40,9 @@
*
* To add an option:
*
- * (i) decide on a type (bool, integer, real, enum, string), name, default
- * value, upper and lower bounds (if applicable); for strings, consider a
- * validation routine.
+ * (i) decide on a type (bool, ternary, integer, real, enum, string), name,
+ * default value, upper and lower bounds (if applicable); for strings,
+ * consider a validation routine.
* (ii) add a record below (or use add_<type>_reloption).
* (iii) add it to the appropriate options struct (perhaps StdRdOptions)
* (iv) add it to the appropriate handling routine (perhaps
@@ -147,15 +147,6 @@ static relopt_bool boolRelOpts[] =
},
false
},
- {
- {
- "vacuum_truncate",
- "Enables vacuum to truncate empty pages at the end of this table",
- RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
- ShareUpdateExclusiveLock
- },
- true
- },
{
{
"deduplicate_items",
@@ -170,6 +161,25 @@ static relopt_bool boolRelOpts[] =
{{NULL}}
};
+static relopt_ternary ternaryRelOpts[] =
+{
+ {
+ {
+ "vacuum_truncate",
+ "Enables vacuum to truncate empty pages at the end of this table",
+ RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
+ ShareUpdateExclusiveLock
+ },
+ TERNARY_UNSET
+ },
+ /* list terminator */
+ {
+ {
+ NULL
+ }
+ }
+};
+
static relopt_int intRelOpts[] =
{
{
@@ -609,6 +619,13 @@ initialize_reloptions(void)
boolRelOpts[i].gen.lockmode));
j++;
}
+ for (i = 0; ternaryRelOpts[i].gen.name; i++)
+ {
+ Assert(DoLockModesConflict(ternaryRelOpts[i].gen.lockmode,
+ ternaryRelOpts[i].gen.lockmode));
+ j++;
+ }
+
for (i = 0; intRelOpts[i].gen.name; i++)
{
Assert(DoLockModesConflict(intRelOpts[i].gen.lockmode,
@@ -649,6 +666,14 @@ initialize_reloptions(void)
j++;
}
+ for (i = 0; ternaryRelOpts[i].gen.name; i++)
+ {
+ relOpts[j] = &ternaryRelOpts[i].gen;
+ relOpts[j]->type = RELOPT_TYPE_TERNARY;
+ relOpts[j]->namelen = strlen(relOpts[j]->name);
+ j++;
+ }
+
for (i = 0; intRelOpts[i].gen.name; i++)
{
relOpts[j] = &intRelOpts[i].gen;
@@ -809,6 +834,9 @@ allocate_reloption(bits32 kinds, int type, const char *name, const char *desc,
case RELOPT_TYPE_BOOL:
size = sizeof(relopt_bool);
break;
+ case RELOPT_TYPE_TERNARY:
+ size = sizeof(relopt_ternary);
+ break;
case RELOPT_TYPE_INT:
size = sizeof(relopt_int);
break;
@@ -892,6 +920,57 @@ add_local_bool_reloption(local_relopts *relopts, const char *name,
add_local_reloption(relopts, (relopt_gen *) newoption, offset);
}
+/*
+ * init_ternary_reloption
+ * Allocate and initialize a new ternary reloption
+ */
+static relopt_ternary *
+init_ternary_reloption(bits32 kinds, const char *name, const char *desc,
+ pg_ternary default_val, LOCKMODE lockmode)
+{
+ relopt_ternary *newoption;
+
+ newoption = (relopt_ternary *)
+ allocate_reloption(kinds, RELOPT_TYPE_TERNARY, name, desc, lockmode);
+ newoption->default_val = default_val;
+
+ return newoption;
+}
+
+/*
+ * add_ternary_reloption
+ * Add a new ternary reloption
+ */
+void
+add_ternary_reloption(bits32 kinds, const char *name, const char *desc,
+ pg_ternary default_val, LOCKMODE lockmode)
+{
+ relopt_ternary *newoption;
+
+ newoption =
+ init_ternary_reloption(kinds, name, desc, default_val, lockmode);
+
+ add_reloption((relopt_gen *) newoption);
+}
+
+/*
+ * add_local_ternary_reloption
+ * Add a new ternary local reloption
+ *
+ * 'offset' is offset of ternary-typed field.
+ */
+void
+add_local_ternary_reloption(local_relopts *relopts, const char *name,
+ const char *desc, pg_ternary default_val,
+ int offset)
+{
+ relopt_ternary *newoption;
+
+ newoption =
+ init_ternary_reloption(RELOPT_KIND_LOCAL, name, desc, default_val, 0);
+
+ add_local_reloption(relopts, (relopt_gen *) newoption, offset);
+}
/*
* init_real_reloption
@@ -1626,6 +1705,19 @@ parse_one_reloption(relopt_value *option, char *text_str, int text_len,
option->gen->name, value)));
}
break;
+ case RELOPT_TYPE_TERNARY:
+ {
+ bool b;
+
+ parsed = parse_bool(value, &b);
+ option->values.ternary_val = b ? TERNARY_TRUE : TERNARY_FALSE;
+ if (validate && !parsed)
+ ereport(ERROR,
+ (errcode(ERRCODE_INVALID_PARAMETER_VALUE),
+ errmsg("invalid value for ternary option \"%s\": %s",
+ option->gen->name, value)));
+ }
+ break;
case RELOPT_TYPE_INT:
{
relopt_int *optint = (relopt_int *) option->gen;
@@ -1789,17 +1881,6 @@ fillRelOptions(void *rdopts, Size basesize,
char *itempos = ((char *) rdopts) + elems[j].offset;
char *string_val;
- /*
- * If isset_offset is provided, store whether the reloption is
- * set there.
- */
- if (elems[j].isset_offset > 0)
- {
- char *setpos = ((char *) rdopts) + elems[j].isset_offset;
-
- *(bool *) setpos = options[i].isset;
- }
-
switch (options[i].gen->type)
{
case RELOPT_TYPE_BOOL:
@@ -1807,6 +1888,11 @@ fillRelOptions(void *rdopts, Size basesize,
options[i].values.bool_val :
((relopt_bool *) options[i].gen)->default_val;
break;
+ case RELOPT_TYPE_TERNARY:
+ *(pg_ternary *) itempos = options[i].isset ?
+ options[i].values.ternary_val :
+ ((relopt_ternary *) options[i].gen)->default_val;
+ break;
case RELOPT_TYPE_INT:
*(int *) itempos = options[i].isset ?
options[i].values.int_val :
@@ -1923,8 +2009,8 @@ default_reloptions(Datum reloptions, bool validate, relopt_kind kind)
offsetof(StdRdOptions, parallel_workers)},
{"vacuum_index_cleanup", RELOPT_TYPE_ENUM,
offsetof(StdRdOptions, vacuum_index_cleanup)},
- {"vacuum_truncate", RELOPT_TYPE_BOOL,
- offsetof(StdRdOptions, vacuum_truncate), offsetof(StdRdOptions, vacuum_truncate_set)},
+ {"vacuum_truncate", RELOPT_TYPE_TERNARY,
+ offsetof(StdRdOptions, vacuum_truncate)},
{"vacuum_max_eager_freeze_failure_rate", RELOPT_TYPE_REAL,
offsetof(StdRdOptions, vacuum_max_eager_freeze_failure_rate)}
};
@@ -2004,7 +2090,6 @@ build_local_reloptions(local_relopts *relopts, Datum options, bool validate)
elems[i].optname = opt->option->name;
elems[i].opttype = opt->option->type;
elems[i].offset = opt->offset;
- elems[i].isset_offset = 0; /* not supported for local relopts yet */
i++;
}
diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c
index aa4fbec143f..696eab9bd97 100644
--- a/src/backend/commands/vacuum.c
+++ b/src/backend/commands/vacuum.c
@@ -2224,9 +2224,9 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams params,
{
StdRdOptions *opts = (StdRdOptions *) rel->rd_options;
- if (opts && opts->vacuum_truncate_set)
+ if (opts && opts->vacuum_truncate != TERNARY_UNSET)
{
- if (opts->vacuum_truncate)
+ if (opts->vacuum_truncate == TERNARY_TRUE)
params.truncate = VACOPTVALUE_ENABLED;
else
params.truncate = VACOPTVALUE_DISABLED;
diff --git a/src/include/access/reloptions.h b/src/include/access/reloptions.h
index 2f08e1b0cf0..dfbef2babf2 100644
--- a/src/include/access/reloptions.h
+++ b/src/include/access/reloptions.h
@@ -29,6 +29,7 @@
typedef enum relopt_type
{
RELOPT_TYPE_BOOL,
+ RELOPT_TYPE_TERNARY, /* on, off, unset */
RELOPT_TYPE_INT,
RELOPT_TYPE_REAL,
RELOPT_TYPE_ENUM,
@@ -80,6 +81,7 @@ typedef struct relopt_value
union
{
bool bool_val;
+ pg_ternary ternary_val;
int int_val;
double real_val;
int enum_val;
@@ -94,6 +96,12 @@ typedef struct relopt_bool
bool default_val;
} relopt_bool;
+typedef struct relopt_ternary
+{
+ relopt_gen gen;
+ int default_val;
+} relopt_ternary;
+
typedef struct relopt_int
{
relopt_gen gen;
@@ -152,19 +160,6 @@ typedef struct
const char *optname; /* option's name */
relopt_type opttype; /* option's datatype */
int offset; /* offset of field in result struct */
-
- /*
- * isset_offset is an optional offset of a field in the result struct that
- * stores whether the option is explicitly set for the relation or if it
- * just picked up the default value. In most cases, this can be
- * accomplished by giving the reloption a special out-of-range default
- * value (e.g., some integer reloptions use -2), but this isn't always
- * possible. For example, a Boolean reloption cannot be given an
- * out-of-range default, so we need another way to discover the source of
- * its value. This offset is only used if given a value greater than
- * zero.
- */
- int isset_offset;
} relopt_parse_elt;
/* Local reloption definition */
@@ -195,6 +190,9 @@ typedef struct local_relopts
extern relopt_kind add_reloption_kind(void);
extern void add_bool_reloption(bits32 kinds, const char *name, const char *desc,
bool default_val, LOCKMODE lockmode);
+extern void add_ternary_reloption(bits32 kinds, const char *name,
+ const char *desc, pg_ternary default_val,
+ LOCKMODE lockmode);
extern void add_int_reloption(bits32 kinds, const char *name, const char *desc,
int default_val, int min_val, int max_val,
LOCKMODE lockmode);
@@ -214,6 +212,9 @@ extern void register_reloptions_validator(local_relopts *relopts,
extern void add_local_bool_reloption(local_relopts *relopts, const char *name,
const char *desc, bool default_val,
int offset);
+extern void add_local_ternary_reloption(local_relopts *relopts,
+ const char *name, const char *desc,
+ pg_ternary default_val, int offset);
extern void add_local_int_reloption(local_relopts *relopts, const char *name,
const char *desc, int default_val,
int min_val, int max_val, int offset);
diff --git a/src/include/postgres.h b/src/include/postgres.h
index 7d93fbce709..47c88780776 100644
--- a/src/include/postgres.h
+++ b/src/include/postgres.h
@@ -543,6 +543,21 @@ Float8GetDatum(float8 X)
* ----------------------------------------------------------------
*/
+/*
+ * pg_ternary
+ * Boolean value with an extra "unset" value
+ *
+ * This enum can be used for values that want to distinguish between true,
+ * false, and unset.
+*/
+
+typedef enum pg_ternary
+{
+ TERNARY_FALSE = 0,
+ TERNARY_TRUE = 1,
+ TERNARY_UNSET = -1
+} pg_ternary;
+
/*
* NON_EXEC_STATIC: It's sometimes useful to define a variable or function
* that is normally static but extern when using EXEC_BACKEND (see
diff --git a/src/include/utils/rel.h b/src/include/utils/rel.h
index d03ab247788..5feb18a5373 100644
--- a/src/include/utils/rel.h
+++ b/src/include/utils/rel.h
@@ -347,8 +347,7 @@ typedef struct StdRdOptions
bool user_catalog_table; /* use as an additional catalog relation */
int parallel_workers; /* max number of parallel workers */
StdRdOptIndexCleanup vacuum_index_cleanup; /* controls index vacuuming */
- bool vacuum_truncate; /* enables vacuum to truncate a relation */
- bool vacuum_truncate_set; /* whether vacuum_truncate is set */
+ pg_ternary vacuum_truncate; /* enables vacuum to truncate a relation */
/*
* Fraction of pages in a relation that vacuum can eagerly scan and fail
diff --git a/src/test/regress/expected/reloptions.out b/src/test/regress/expected/reloptions.out
index 9de19b4e3f1..1c99f79ab01 100644
--- a/src/test/regress/expected/reloptions.out
+++ b/src/test/regress/expected/reloptions.out
@@ -98,6 +98,42 @@ SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass;
{fillfactor=13,autovacuum_enabled=false}
(1 row)
+-- Tests for future (FIXME) ternary options
+-- behave as boolean option: accept unassigned name and truncated value
+DROP TABLE reloptions_test;
+CREATE TABLE reloptions_test(i INT) WITH (vacuum_truncate);
+SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass;
+ reloptions
+------------------------
+ {vacuum_truncate=true}
+(1 row)
+
+DROP TABLE reloptions_test;
+CREATE TABLE reloptions_test(i INT) WITH (vacuum_truncate=FaLS);
+SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass;
+ reloptions
+------------------------
+ {vacuum_truncate=fals}
+(1 row)
+
+-- preferred "true" alias is used when storing
+DROP TABLE reloptions_test;
+CREATE TABLE reloptions_test(i INT) WITH (vacuum_index_cleanup=on);
+SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass;
+ reloptions
+---------------------------
+ {vacuum_index_cleanup=on}
+(1 row)
+
+-- custom "third" value is available
+DROP TABLE reloptions_test;
+CREATE TABLE reloptions_test(i INT) WITH (vacuum_index_cleanup=auto);
+SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass;
+ reloptions
+-----------------------------
+ {vacuum_index_cleanup=auto}
+(1 row)
+
-- Test vacuum_truncate option
DROP TABLE reloptions_test;
CREATE TEMP TABLE reloptions_test(i INT NOT NULL, j text)
diff --git a/src/test/regress/sql/reloptions.sql b/src/test/regress/sql/reloptions.sql
index 24fbe0b478d..f5980dafcbc 100644
--- a/src/test/regress/sql/reloptions.sql
+++ b/src/test/regress/sql/reloptions.sql
@@ -59,6 +59,27 @@ UPDATE pg_class
ALTER TABLE reloptions_test RESET (illegal_option);
SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass;
+-- Tests for future (FIXME) ternary options
+
+-- behave as boolean option: accept unassigned name and truncated value
+DROP TABLE reloptions_test;
+CREATE TABLE reloptions_test(i INT) WITH (vacuum_truncate);
+SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass;
+
+DROP TABLE reloptions_test;
+CREATE TABLE reloptions_test(i INT) WITH (vacuum_truncate=FaLS);
+SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass;
+
+-- preferred "true" alias is used when storing
+DROP TABLE reloptions_test;
+CREATE TABLE reloptions_test(i INT) WITH (vacuum_index_cleanup=on);
+SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass;
+
+-- custom "third" value is available
+DROP TABLE reloptions_test;
+CREATE TABLE reloptions_test(i INT) WITH (vacuum_index_cleanup=auto);
+SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass;
+
-- Test vacuum_truncate option
DROP TABLE reloptions_test;
diff --git a/src/tools/pgindent/typedefs.list b/src/tools/pgindent/typedefs.list
index 3f3a888fd0e..1c8610fd46c 100644
--- a/src/tools/pgindent/typedefs.list
+++ b/src/tools/pgindent/typedefs.list
@@ -3952,6 +3952,7 @@ pg_sha512_ctx
pg_snapshot
pg_special_case
pg_stack_base_t
+pg_ternary
pg_time_t
pg_time_usec_t
pg_tz
@@ -4079,6 +4080,7 @@ relopt_kind
relopt_parse_elt
relopt_real
relopt_string
+relopt_ternary
relopt_type
relopt_value
relopts_validator
--
2.47.3
--c36t3fndxhkzbl5z--
^ permalink raw reply [nested|flat] 868+ messages in thread
* [PATCH v3] Introduce ternary reloptions
@ 2026-01-16 15:04 Álvaro Herrera <[email protected]>
0 siblings, 0 replies; 868+ messages in thread
From: Álvaro Herrera @ 2026-01-16 15:04 UTC (permalink / raw)
Introduce ternary reloption as a replacement for current
`vacuum_truncate` implementation. Remove the `vacuum_truncate_set`
separate flag and use TERNARY_UNSET instead.
This could also be used for other options such as `vacuum_index_cleanup`
and `buffering`, but lets get the scaffolding in first.
Discussion: https://postgr.es/m/3474141.usfYGdeWWP@thinkpad-pgpro
---
src/backend/access/common/reloptions.c | 137 ++++++++++++++++++-----
src/backend/commands/vacuum.c | 4 +-
src/include/access/reloptions.h | 27 ++---
src/include/postgres.h | 15 +++
src/include/utils/rel.h | 3 +-
src/test/regress/expected/reloptions.out | 36 ++++++
src/test/regress/sql/reloptions.sql | 21 ++++
src/tools/pgindent/typedefs.list | 2 +
8 files changed, 202 insertions(+), 43 deletions(-)
diff --git a/src/backend/access/common/reloptions.c b/src/backend/access/common/reloptions.c
index 0b83f98ed5f..6f1f577581a 100644
--- a/src/backend/access/common/reloptions.c
+++ b/src/backend/access/common/reloptions.c
@@ -40,9 +40,9 @@
*
* To add an option:
*
- * (i) decide on a type (bool, integer, real, enum, string), name, default
- * value, upper and lower bounds (if applicable); for strings, consider a
- * validation routine.
+ * (i) decide on a type (bool, ternary, integer, real, enum, string), name,
+ * default value, upper and lower bounds (if applicable); for strings,
+ * consider a validation routine.
* (ii) add a record below (or use add_<type>_reloption).
* (iii) add it to the appropriate options struct (perhaps StdRdOptions)
* (iv) add it to the appropriate handling routine (perhaps
@@ -147,15 +147,6 @@ static relopt_bool boolRelOpts[] =
},
false
},
- {
- {
- "vacuum_truncate",
- "Enables vacuum to truncate empty pages at the end of this table",
- RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
- ShareUpdateExclusiveLock
- },
- true
- },
{
{
"deduplicate_items",
@@ -170,6 +161,25 @@ static relopt_bool boolRelOpts[] =
{{NULL}}
};
+static relopt_ternary ternaryRelOpts[] =
+{
+ {
+ {
+ "vacuum_truncate",
+ "Enables vacuum to truncate empty pages at the end of this table",
+ RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
+ ShareUpdateExclusiveLock
+ },
+ TERNARY_UNSET
+ },
+ /* list terminator */
+ {
+ {
+ NULL
+ }
+ }
+};
+
static relopt_int intRelOpts[] =
{
{
@@ -609,6 +619,13 @@ initialize_reloptions(void)
boolRelOpts[i].gen.lockmode));
j++;
}
+ for (i = 0; ternaryRelOpts[i].gen.name; i++)
+ {
+ Assert(DoLockModesConflict(ternaryRelOpts[i].gen.lockmode,
+ ternaryRelOpts[i].gen.lockmode));
+ j++;
+ }
+
for (i = 0; intRelOpts[i].gen.name; i++)
{
Assert(DoLockModesConflict(intRelOpts[i].gen.lockmode,
@@ -649,6 +666,14 @@ initialize_reloptions(void)
j++;
}
+ for (i = 0; ternaryRelOpts[i].gen.name; i++)
+ {
+ relOpts[j] = &ternaryRelOpts[i].gen;
+ relOpts[j]->type = RELOPT_TYPE_TERNARY;
+ relOpts[j]->namelen = strlen(relOpts[j]->name);
+ j++;
+ }
+
for (i = 0; intRelOpts[i].gen.name; i++)
{
relOpts[j] = &intRelOpts[i].gen;
@@ -809,6 +834,9 @@ allocate_reloption(bits32 kinds, int type, const char *name, const char *desc,
case RELOPT_TYPE_BOOL:
size = sizeof(relopt_bool);
break;
+ case RELOPT_TYPE_TERNARY:
+ size = sizeof(relopt_ternary);
+ break;
case RELOPT_TYPE_INT:
size = sizeof(relopt_int);
break;
@@ -892,6 +920,57 @@ add_local_bool_reloption(local_relopts *relopts, const char *name,
add_local_reloption(relopts, (relopt_gen *) newoption, offset);
}
+/*
+ * init_ternary_reloption
+ * Allocate and initialize a new ternary reloption
+ */
+static relopt_ternary *
+init_ternary_reloption(bits32 kinds, const char *name, const char *desc,
+ pg_ternary default_val, LOCKMODE lockmode)
+{
+ relopt_ternary *newoption;
+
+ newoption = (relopt_ternary *)
+ allocate_reloption(kinds, RELOPT_TYPE_TERNARY, name, desc, lockmode);
+ newoption->default_val = default_val;
+
+ return newoption;
+}
+
+/*
+ * add_ternary_reloption
+ * Add a new ternary reloption
+ */
+void
+add_ternary_reloption(bits32 kinds, const char *name, const char *desc,
+ pg_ternary default_val, LOCKMODE lockmode)
+{
+ relopt_ternary *newoption;
+
+ newoption =
+ init_ternary_reloption(kinds, name, desc, default_val, lockmode);
+
+ add_reloption((relopt_gen *) newoption);
+}
+
+/*
+ * add_local_ternary_reloption
+ * Add a new ternary local reloption
+ *
+ * 'offset' is offset of ternary-typed field.
+ */
+void
+add_local_ternary_reloption(local_relopts *relopts, const char *name,
+ const char *desc, pg_ternary default_val,
+ int offset)
+{
+ relopt_ternary *newoption;
+
+ newoption =
+ init_ternary_reloption(RELOPT_KIND_LOCAL, name, desc, default_val, 0);
+
+ add_local_reloption(relopts, (relopt_gen *) newoption, offset);
+}
/*
* init_real_reloption
@@ -1626,6 +1705,19 @@ parse_one_reloption(relopt_value *option, char *text_str, int text_len,
option->gen->name, value)));
}
break;
+ case RELOPT_TYPE_TERNARY:
+ {
+ bool b;
+
+ parsed = parse_bool(value, &b);
+ option->values.ternary_val = b ? TERNARY_TRUE : TERNARY_FALSE;
+ if (validate && !parsed)
+ ereport(ERROR,
+ (errcode(ERRCODE_INVALID_PARAMETER_VALUE),
+ errmsg("invalid value for ternary option \"%s\": %s",
+ option->gen->name, value)));
+ }
+ break;
case RELOPT_TYPE_INT:
{
relopt_int *optint = (relopt_int *) option->gen;
@@ -1789,17 +1881,6 @@ fillRelOptions(void *rdopts, Size basesize,
char *itempos = ((char *) rdopts) + elems[j].offset;
char *string_val;
- /*
- * If isset_offset is provided, store whether the reloption is
- * set there.
- */
- if (elems[j].isset_offset > 0)
- {
- char *setpos = ((char *) rdopts) + elems[j].isset_offset;
-
- *(bool *) setpos = options[i].isset;
- }
-
switch (options[i].gen->type)
{
case RELOPT_TYPE_BOOL:
@@ -1807,6 +1888,11 @@ fillRelOptions(void *rdopts, Size basesize,
options[i].values.bool_val :
((relopt_bool *) options[i].gen)->default_val;
break;
+ case RELOPT_TYPE_TERNARY:
+ *(pg_ternary *) itempos = options[i].isset ?
+ options[i].values.ternary_val :
+ ((relopt_ternary *) options[i].gen)->default_val;
+ break;
case RELOPT_TYPE_INT:
*(int *) itempos = options[i].isset ?
options[i].values.int_val :
@@ -1923,8 +2009,8 @@ default_reloptions(Datum reloptions, bool validate, relopt_kind kind)
offsetof(StdRdOptions, parallel_workers)},
{"vacuum_index_cleanup", RELOPT_TYPE_ENUM,
offsetof(StdRdOptions, vacuum_index_cleanup)},
- {"vacuum_truncate", RELOPT_TYPE_BOOL,
- offsetof(StdRdOptions, vacuum_truncate), offsetof(StdRdOptions, vacuum_truncate_set)},
+ {"vacuum_truncate", RELOPT_TYPE_TERNARY,
+ offsetof(StdRdOptions, vacuum_truncate)},
{"vacuum_max_eager_freeze_failure_rate", RELOPT_TYPE_REAL,
offsetof(StdRdOptions, vacuum_max_eager_freeze_failure_rate)}
};
@@ -2004,7 +2090,6 @@ build_local_reloptions(local_relopts *relopts, Datum options, bool validate)
elems[i].optname = opt->option->name;
elems[i].opttype = opt->option->type;
elems[i].offset = opt->offset;
- elems[i].isset_offset = 0; /* not supported for local relopts yet */
i++;
}
diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c
index aa4fbec143f..696eab9bd97 100644
--- a/src/backend/commands/vacuum.c
+++ b/src/backend/commands/vacuum.c
@@ -2224,9 +2224,9 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams params,
{
StdRdOptions *opts = (StdRdOptions *) rel->rd_options;
- if (opts && opts->vacuum_truncate_set)
+ if (opts && opts->vacuum_truncate != TERNARY_UNSET)
{
- if (opts->vacuum_truncate)
+ if (opts->vacuum_truncate == TERNARY_TRUE)
params.truncate = VACOPTVALUE_ENABLED;
else
params.truncate = VACOPTVALUE_DISABLED;
diff --git a/src/include/access/reloptions.h b/src/include/access/reloptions.h
index 2f08e1b0cf0..dfbef2babf2 100644
--- a/src/include/access/reloptions.h
+++ b/src/include/access/reloptions.h
@@ -29,6 +29,7 @@
typedef enum relopt_type
{
RELOPT_TYPE_BOOL,
+ RELOPT_TYPE_TERNARY, /* on, off, unset */
RELOPT_TYPE_INT,
RELOPT_TYPE_REAL,
RELOPT_TYPE_ENUM,
@@ -80,6 +81,7 @@ typedef struct relopt_value
union
{
bool bool_val;
+ pg_ternary ternary_val;
int int_val;
double real_val;
int enum_val;
@@ -94,6 +96,12 @@ typedef struct relopt_bool
bool default_val;
} relopt_bool;
+typedef struct relopt_ternary
+{
+ relopt_gen gen;
+ int default_val;
+} relopt_ternary;
+
typedef struct relopt_int
{
relopt_gen gen;
@@ -152,19 +160,6 @@ typedef struct
const char *optname; /* option's name */
relopt_type opttype; /* option's datatype */
int offset; /* offset of field in result struct */
-
- /*
- * isset_offset is an optional offset of a field in the result struct that
- * stores whether the option is explicitly set for the relation or if it
- * just picked up the default value. In most cases, this can be
- * accomplished by giving the reloption a special out-of-range default
- * value (e.g., some integer reloptions use -2), but this isn't always
- * possible. For example, a Boolean reloption cannot be given an
- * out-of-range default, so we need another way to discover the source of
- * its value. This offset is only used if given a value greater than
- * zero.
- */
- int isset_offset;
} relopt_parse_elt;
/* Local reloption definition */
@@ -195,6 +190,9 @@ typedef struct local_relopts
extern relopt_kind add_reloption_kind(void);
extern void add_bool_reloption(bits32 kinds, const char *name, const char *desc,
bool default_val, LOCKMODE lockmode);
+extern void add_ternary_reloption(bits32 kinds, const char *name,
+ const char *desc, pg_ternary default_val,
+ LOCKMODE lockmode);
extern void add_int_reloption(bits32 kinds, const char *name, const char *desc,
int default_val, int min_val, int max_val,
LOCKMODE lockmode);
@@ -214,6 +212,9 @@ extern void register_reloptions_validator(local_relopts *relopts,
extern void add_local_bool_reloption(local_relopts *relopts, const char *name,
const char *desc, bool default_val,
int offset);
+extern void add_local_ternary_reloption(local_relopts *relopts,
+ const char *name, const char *desc,
+ pg_ternary default_val, int offset);
extern void add_local_int_reloption(local_relopts *relopts, const char *name,
const char *desc, int default_val,
int min_val, int max_val, int offset);
diff --git a/src/include/postgres.h b/src/include/postgres.h
index 7d93fbce709..47c88780776 100644
--- a/src/include/postgres.h
+++ b/src/include/postgres.h
@@ -543,6 +543,21 @@ Float8GetDatum(float8 X)
* ----------------------------------------------------------------
*/
+/*
+ * pg_ternary
+ * Boolean value with an extra "unset" value
+ *
+ * This enum can be used for values that want to distinguish between true,
+ * false, and unset.
+*/
+
+typedef enum pg_ternary
+{
+ TERNARY_FALSE = 0,
+ TERNARY_TRUE = 1,
+ TERNARY_UNSET = -1
+} pg_ternary;
+
/*
* NON_EXEC_STATIC: It's sometimes useful to define a variable or function
* that is normally static but extern when using EXEC_BACKEND (see
diff --git a/src/include/utils/rel.h b/src/include/utils/rel.h
index d03ab247788..5feb18a5373 100644
--- a/src/include/utils/rel.h
+++ b/src/include/utils/rel.h
@@ -347,8 +347,7 @@ typedef struct StdRdOptions
bool user_catalog_table; /* use as an additional catalog relation */
int parallel_workers; /* max number of parallel workers */
StdRdOptIndexCleanup vacuum_index_cleanup; /* controls index vacuuming */
- bool vacuum_truncate; /* enables vacuum to truncate a relation */
- bool vacuum_truncate_set; /* whether vacuum_truncate is set */
+ pg_ternary vacuum_truncate; /* enables vacuum to truncate a relation */
/*
* Fraction of pages in a relation that vacuum can eagerly scan and fail
diff --git a/src/test/regress/expected/reloptions.out b/src/test/regress/expected/reloptions.out
index 9de19b4e3f1..1c99f79ab01 100644
--- a/src/test/regress/expected/reloptions.out
+++ b/src/test/regress/expected/reloptions.out
@@ -98,6 +98,42 @@ SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass;
{fillfactor=13,autovacuum_enabled=false}
(1 row)
+-- Tests for future (FIXME) ternary options
+-- behave as boolean option: accept unassigned name and truncated value
+DROP TABLE reloptions_test;
+CREATE TABLE reloptions_test(i INT) WITH (vacuum_truncate);
+SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass;
+ reloptions
+------------------------
+ {vacuum_truncate=true}
+(1 row)
+
+DROP TABLE reloptions_test;
+CREATE TABLE reloptions_test(i INT) WITH (vacuum_truncate=FaLS);
+SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass;
+ reloptions
+------------------------
+ {vacuum_truncate=fals}
+(1 row)
+
+-- preferred "true" alias is used when storing
+DROP TABLE reloptions_test;
+CREATE TABLE reloptions_test(i INT) WITH (vacuum_index_cleanup=on);
+SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass;
+ reloptions
+---------------------------
+ {vacuum_index_cleanup=on}
+(1 row)
+
+-- custom "third" value is available
+DROP TABLE reloptions_test;
+CREATE TABLE reloptions_test(i INT) WITH (vacuum_index_cleanup=auto);
+SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass;
+ reloptions
+-----------------------------
+ {vacuum_index_cleanup=auto}
+(1 row)
+
-- Test vacuum_truncate option
DROP TABLE reloptions_test;
CREATE TEMP TABLE reloptions_test(i INT NOT NULL, j text)
diff --git a/src/test/regress/sql/reloptions.sql b/src/test/regress/sql/reloptions.sql
index 24fbe0b478d..f5980dafcbc 100644
--- a/src/test/regress/sql/reloptions.sql
+++ b/src/test/regress/sql/reloptions.sql
@@ -59,6 +59,27 @@ UPDATE pg_class
ALTER TABLE reloptions_test RESET (illegal_option);
SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass;
+-- Tests for future (FIXME) ternary options
+
+-- behave as boolean option: accept unassigned name and truncated value
+DROP TABLE reloptions_test;
+CREATE TABLE reloptions_test(i INT) WITH (vacuum_truncate);
+SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass;
+
+DROP TABLE reloptions_test;
+CREATE TABLE reloptions_test(i INT) WITH (vacuum_truncate=FaLS);
+SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass;
+
+-- preferred "true" alias is used when storing
+DROP TABLE reloptions_test;
+CREATE TABLE reloptions_test(i INT) WITH (vacuum_index_cleanup=on);
+SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass;
+
+-- custom "third" value is available
+DROP TABLE reloptions_test;
+CREATE TABLE reloptions_test(i INT) WITH (vacuum_index_cleanup=auto);
+SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass;
+
-- Test vacuum_truncate option
DROP TABLE reloptions_test;
diff --git a/src/tools/pgindent/typedefs.list b/src/tools/pgindent/typedefs.list
index 3f3a888fd0e..1c8610fd46c 100644
--- a/src/tools/pgindent/typedefs.list
+++ b/src/tools/pgindent/typedefs.list
@@ -3952,6 +3952,7 @@ pg_sha512_ctx
pg_snapshot
pg_special_case
pg_stack_base_t
+pg_ternary
pg_time_t
pg_time_usec_t
pg_tz
@@ -4079,6 +4080,7 @@ relopt_kind
relopt_parse_elt
relopt_real
relopt_string
+relopt_ternary
relopt_type
relopt_value
relopts_validator
--
2.47.3
--c36t3fndxhkzbl5z--
^ permalink raw reply [nested|flat] 868+ messages in thread
* [PATCH v3] Introduce ternary reloptions
@ 2026-01-16 15:04 Álvaro Herrera <[email protected]>
0 siblings, 0 replies; 868+ messages in thread
From: Álvaro Herrera @ 2026-01-16 15:04 UTC (permalink / raw)
Introduce ternary reloption as a replacement for current
`vacuum_truncate` implementation. Remove the `vacuum_truncate_set`
separate flag and use TERNARY_UNSET instead.
This could also be used for other options such as `vacuum_index_cleanup`
and `buffering`, but lets get the scaffolding in first.
Discussion: https://postgr.es/m/3474141.usfYGdeWWP@thinkpad-pgpro
---
src/backend/access/common/reloptions.c | 137 ++++++++++++++++++-----
src/backend/commands/vacuum.c | 4 +-
src/include/access/reloptions.h | 27 ++---
src/include/postgres.h | 15 +++
src/include/utils/rel.h | 3 +-
src/test/regress/expected/reloptions.out | 36 ++++++
src/test/regress/sql/reloptions.sql | 21 ++++
src/tools/pgindent/typedefs.list | 2 +
8 files changed, 202 insertions(+), 43 deletions(-)
diff --git a/src/backend/access/common/reloptions.c b/src/backend/access/common/reloptions.c
index 0b83f98ed5f..6f1f577581a 100644
--- a/src/backend/access/common/reloptions.c
+++ b/src/backend/access/common/reloptions.c
@@ -40,9 +40,9 @@
*
* To add an option:
*
- * (i) decide on a type (bool, integer, real, enum, string), name, default
- * value, upper and lower bounds (if applicable); for strings, consider a
- * validation routine.
+ * (i) decide on a type (bool, ternary, integer, real, enum, string), name,
+ * default value, upper and lower bounds (if applicable); for strings,
+ * consider a validation routine.
* (ii) add a record below (or use add_<type>_reloption).
* (iii) add it to the appropriate options struct (perhaps StdRdOptions)
* (iv) add it to the appropriate handling routine (perhaps
@@ -147,15 +147,6 @@ static relopt_bool boolRelOpts[] =
},
false
},
- {
- {
- "vacuum_truncate",
- "Enables vacuum to truncate empty pages at the end of this table",
- RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
- ShareUpdateExclusiveLock
- },
- true
- },
{
{
"deduplicate_items",
@@ -170,6 +161,25 @@ static relopt_bool boolRelOpts[] =
{{NULL}}
};
+static relopt_ternary ternaryRelOpts[] =
+{
+ {
+ {
+ "vacuum_truncate",
+ "Enables vacuum to truncate empty pages at the end of this table",
+ RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
+ ShareUpdateExclusiveLock
+ },
+ TERNARY_UNSET
+ },
+ /* list terminator */
+ {
+ {
+ NULL
+ }
+ }
+};
+
static relopt_int intRelOpts[] =
{
{
@@ -609,6 +619,13 @@ initialize_reloptions(void)
boolRelOpts[i].gen.lockmode));
j++;
}
+ for (i = 0; ternaryRelOpts[i].gen.name; i++)
+ {
+ Assert(DoLockModesConflict(ternaryRelOpts[i].gen.lockmode,
+ ternaryRelOpts[i].gen.lockmode));
+ j++;
+ }
+
for (i = 0; intRelOpts[i].gen.name; i++)
{
Assert(DoLockModesConflict(intRelOpts[i].gen.lockmode,
@@ -649,6 +666,14 @@ initialize_reloptions(void)
j++;
}
+ for (i = 0; ternaryRelOpts[i].gen.name; i++)
+ {
+ relOpts[j] = &ternaryRelOpts[i].gen;
+ relOpts[j]->type = RELOPT_TYPE_TERNARY;
+ relOpts[j]->namelen = strlen(relOpts[j]->name);
+ j++;
+ }
+
for (i = 0; intRelOpts[i].gen.name; i++)
{
relOpts[j] = &intRelOpts[i].gen;
@@ -809,6 +834,9 @@ allocate_reloption(bits32 kinds, int type, const char *name, const char *desc,
case RELOPT_TYPE_BOOL:
size = sizeof(relopt_bool);
break;
+ case RELOPT_TYPE_TERNARY:
+ size = sizeof(relopt_ternary);
+ break;
case RELOPT_TYPE_INT:
size = sizeof(relopt_int);
break;
@@ -892,6 +920,57 @@ add_local_bool_reloption(local_relopts *relopts, const char *name,
add_local_reloption(relopts, (relopt_gen *) newoption, offset);
}
+/*
+ * init_ternary_reloption
+ * Allocate and initialize a new ternary reloption
+ */
+static relopt_ternary *
+init_ternary_reloption(bits32 kinds, const char *name, const char *desc,
+ pg_ternary default_val, LOCKMODE lockmode)
+{
+ relopt_ternary *newoption;
+
+ newoption = (relopt_ternary *)
+ allocate_reloption(kinds, RELOPT_TYPE_TERNARY, name, desc, lockmode);
+ newoption->default_val = default_val;
+
+ return newoption;
+}
+
+/*
+ * add_ternary_reloption
+ * Add a new ternary reloption
+ */
+void
+add_ternary_reloption(bits32 kinds, const char *name, const char *desc,
+ pg_ternary default_val, LOCKMODE lockmode)
+{
+ relopt_ternary *newoption;
+
+ newoption =
+ init_ternary_reloption(kinds, name, desc, default_val, lockmode);
+
+ add_reloption((relopt_gen *) newoption);
+}
+
+/*
+ * add_local_ternary_reloption
+ * Add a new ternary local reloption
+ *
+ * 'offset' is offset of ternary-typed field.
+ */
+void
+add_local_ternary_reloption(local_relopts *relopts, const char *name,
+ const char *desc, pg_ternary default_val,
+ int offset)
+{
+ relopt_ternary *newoption;
+
+ newoption =
+ init_ternary_reloption(RELOPT_KIND_LOCAL, name, desc, default_val, 0);
+
+ add_local_reloption(relopts, (relopt_gen *) newoption, offset);
+}
/*
* init_real_reloption
@@ -1626,6 +1705,19 @@ parse_one_reloption(relopt_value *option, char *text_str, int text_len,
option->gen->name, value)));
}
break;
+ case RELOPT_TYPE_TERNARY:
+ {
+ bool b;
+
+ parsed = parse_bool(value, &b);
+ option->values.ternary_val = b ? TERNARY_TRUE : TERNARY_FALSE;
+ if (validate && !parsed)
+ ereport(ERROR,
+ (errcode(ERRCODE_INVALID_PARAMETER_VALUE),
+ errmsg("invalid value for ternary option \"%s\": %s",
+ option->gen->name, value)));
+ }
+ break;
case RELOPT_TYPE_INT:
{
relopt_int *optint = (relopt_int *) option->gen;
@@ -1789,17 +1881,6 @@ fillRelOptions(void *rdopts, Size basesize,
char *itempos = ((char *) rdopts) + elems[j].offset;
char *string_val;
- /*
- * If isset_offset is provided, store whether the reloption is
- * set there.
- */
- if (elems[j].isset_offset > 0)
- {
- char *setpos = ((char *) rdopts) + elems[j].isset_offset;
-
- *(bool *) setpos = options[i].isset;
- }
-
switch (options[i].gen->type)
{
case RELOPT_TYPE_BOOL:
@@ -1807,6 +1888,11 @@ fillRelOptions(void *rdopts, Size basesize,
options[i].values.bool_val :
((relopt_bool *) options[i].gen)->default_val;
break;
+ case RELOPT_TYPE_TERNARY:
+ *(pg_ternary *) itempos = options[i].isset ?
+ options[i].values.ternary_val :
+ ((relopt_ternary *) options[i].gen)->default_val;
+ break;
case RELOPT_TYPE_INT:
*(int *) itempos = options[i].isset ?
options[i].values.int_val :
@@ -1923,8 +2009,8 @@ default_reloptions(Datum reloptions, bool validate, relopt_kind kind)
offsetof(StdRdOptions, parallel_workers)},
{"vacuum_index_cleanup", RELOPT_TYPE_ENUM,
offsetof(StdRdOptions, vacuum_index_cleanup)},
- {"vacuum_truncate", RELOPT_TYPE_BOOL,
- offsetof(StdRdOptions, vacuum_truncate), offsetof(StdRdOptions, vacuum_truncate_set)},
+ {"vacuum_truncate", RELOPT_TYPE_TERNARY,
+ offsetof(StdRdOptions, vacuum_truncate)},
{"vacuum_max_eager_freeze_failure_rate", RELOPT_TYPE_REAL,
offsetof(StdRdOptions, vacuum_max_eager_freeze_failure_rate)}
};
@@ -2004,7 +2090,6 @@ build_local_reloptions(local_relopts *relopts, Datum options, bool validate)
elems[i].optname = opt->option->name;
elems[i].opttype = opt->option->type;
elems[i].offset = opt->offset;
- elems[i].isset_offset = 0; /* not supported for local relopts yet */
i++;
}
diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c
index aa4fbec143f..696eab9bd97 100644
--- a/src/backend/commands/vacuum.c
+++ b/src/backend/commands/vacuum.c
@@ -2224,9 +2224,9 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams params,
{
StdRdOptions *opts = (StdRdOptions *) rel->rd_options;
- if (opts && opts->vacuum_truncate_set)
+ if (opts && opts->vacuum_truncate != TERNARY_UNSET)
{
- if (opts->vacuum_truncate)
+ if (opts->vacuum_truncate == TERNARY_TRUE)
params.truncate = VACOPTVALUE_ENABLED;
else
params.truncate = VACOPTVALUE_DISABLED;
diff --git a/src/include/access/reloptions.h b/src/include/access/reloptions.h
index 2f08e1b0cf0..dfbef2babf2 100644
--- a/src/include/access/reloptions.h
+++ b/src/include/access/reloptions.h
@@ -29,6 +29,7 @@
typedef enum relopt_type
{
RELOPT_TYPE_BOOL,
+ RELOPT_TYPE_TERNARY, /* on, off, unset */
RELOPT_TYPE_INT,
RELOPT_TYPE_REAL,
RELOPT_TYPE_ENUM,
@@ -80,6 +81,7 @@ typedef struct relopt_value
union
{
bool bool_val;
+ pg_ternary ternary_val;
int int_val;
double real_val;
int enum_val;
@@ -94,6 +96,12 @@ typedef struct relopt_bool
bool default_val;
} relopt_bool;
+typedef struct relopt_ternary
+{
+ relopt_gen gen;
+ int default_val;
+} relopt_ternary;
+
typedef struct relopt_int
{
relopt_gen gen;
@@ -152,19 +160,6 @@ typedef struct
const char *optname; /* option's name */
relopt_type opttype; /* option's datatype */
int offset; /* offset of field in result struct */
-
- /*
- * isset_offset is an optional offset of a field in the result struct that
- * stores whether the option is explicitly set for the relation or if it
- * just picked up the default value. In most cases, this can be
- * accomplished by giving the reloption a special out-of-range default
- * value (e.g., some integer reloptions use -2), but this isn't always
- * possible. For example, a Boolean reloption cannot be given an
- * out-of-range default, so we need another way to discover the source of
- * its value. This offset is only used if given a value greater than
- * zero.
- */
- int isset_offset;
} relopt_parse_elt;
/* Local reloption definition */
@@ -195,6 +190,9 @@ typedef struct local_relopts
extern relopt_kind add_reloption_kind(void);
extern void add_bool_reloption(bits32 kinds, const char *name, const char *desc,
bool default_val, LOCKMODE lockmode);
+extern void add_ternary_reloption(bits32 kinds, const char *name,
+ const char *desc, pg_ternary default_val,
+ LOCKMODE lockmode);
extern void add_int_reloption(bits32 kinds, const char *name, const char *desc,
int default_val, int min_val, int max_val,
LOCKMODE lockmode);
@@ -214,6 +212,9 @@ extern void register_reloptions_validator(local_relopts *relopts,
extern void add_local_bool_reloption(local_relopts *relopts, const char *name,
const char *desc, bool default_val,
int offset);
+extern void add_local_ternary_reloption(local_relopts *relopts,
+ const char *name, const char *desc,
+ pg_ternary default_val, int offset);
extern void add_local_int_reloption(local_relopts *relopts, const char *name,
const char *desc, int default_val,
int min_val, int max_val, int offset);
diff --git a/src/include/postgres.h b/src/include/postgres.h
index 7d93fbce709..47c88780776 100644
--- a/src/include/postgres.h
+++ b/src/include/postgres.h
@@ -543,6 +543,21 @@ Float8GetDatum(float8 X)
* ----------------------------------------------------------------
*/
+/*
+ * pg_ternary
+ * Boolean value with an extra "unset" value
+ *
+ * This enum can be used for values that want to distinguish between true,
+ * false, and unset.
+*/
+
+typedef enum pg_ternary
+{
+ TERNARY_FALSE = 0,
+ TERNARY_TRUE = 1,
+ TERNARY_UNSET = -1
+} pg_ternary;
+
/*
* NON_EXEC_STATIC: It's sometimes useful to define a variable or function
* that is normally static but extern when using EXEC_BACKEND (see
diff --git a/src/include/utils/rel.h b/src/include/utils/rel.h
index d03ab247788..5feb18a5373 100644
--- a/src/include/utils/rel.h
+++ b/src/include/utils/rel.h
@@ -347,8 +347,7 @@ typedef struct StdRdOptions
bool user_catalog_table; /* use as an additional catalog relation */
int parallel_workers; /* max number of parallel workers */
StdRdOptIndexCleanup vacuum_index_cleanup; /* controls index vacuuming */
- bool vacuum_truncate; /* enables vacuum to truncate a relation */
- bool vacuum_truncate_set; /* whether vacuum_truncate is set */
+ pg_ternary vacuum_truncate; /* enables vacuum to truncate a relation */
/*
* Fraction of pages in a relation that vacuum can eagerly scan and fail
diff --git a/src/test/regress/expected/reloptions.out b/src/test/regress/expected/reloptions.out
index 9de19b4e3f1..1c99f79ab01 100644
--- a/src/test/regress/expected/reloptions.out
+++ b/src/test/regress/expected/reloptions.out
@@ -98,6 +98,42 @@ SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass;
{fillfactor=13,autovacuum_enabled=false}
(1 row)
+-- Tests for future (FIXME) ternary options
+-- behave as boolean option: accept unassigned name and truncated value
+DROP TABLE reloptions_test;
+CREATE TABLE reloptions_test(i INT) WITH (vacuum_truncate);
+SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass;
+ reloptions
+------------------------
+ {vacuum_truncate=true}
+(1 row)
+
+DROP TABLE reloptions_test;
+CREATE TABLE reloptions_test(i INT) WITH (vacuum_truncate=FaLS);
+SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass;
+ reloptions
+------------------------
+ {vacuum_truncate=fals}
+(1 row)
+
+-- preferred "true" alias is used when storing
+DROP TABLE reloptions_test;
+CREATE TABLE reloptions_test(i INT) WITH (vacuum_index_cleanup=on);
+SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass;
+ reloptions
+---------------------------
+ {vacuum_index_cleanup=on}
+(1 row)
+
+-- custom "third" value is available
+DROP TABLE reloptions_test;
+CREATE TABLE reloptions_test(i INT) WITH (vacuum_index_cleanup=auto);
+SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass;
+ reloptions
+-----------------------------
+ {vacuum_index_cleanup=auto}
+(1 row)
+
-- Test vacuum_truncate option
DROP TABLE reloptions_test;
CREATE TEMP TABLE reloptions_test(i INT NOT NULL, j text)
diff --git a/src/test/regress/sql/reloptions.sql b/src/test/regress/sql/reloptions.sql
index 24fbe0b478d..f5980dafcbc 100644
--- a/src/test/regress/sql/reloptions.sql
+++ b/src/test/regress/sql/reloptions.sql
@@ -59,6 +59,27 @@ UPDATE pg_class
ALTER TABLE reloptions_test RESET (illegal_option);
SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass;
+-- Tests for future (FIXME) ternary options
+
+-- behave as boolean option: accept unassigned name and truncated value
+DROP TABLE reloptions_test;
+CREATE TABLE reloptions_test(i INT) WITH (vacuum_truncate);
+SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass;
+
+DROP TABLE reloptions_test;
+CREATE TABLE reloptions_test(i INT) WITH (vacuum_truncate=FaLS);
+SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass;
+
+-- preferred "true" alias is used when storing
+DROP TABLE reloptions_test;
+CREATE TABLE reloptions_test(i INT) WITH (vacuum_index_cleanup=on);
+SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass;
+
+-- custom "third" value is available
+DROP TABLE reloptions_test;
+CREATE TABLE reloptions_test(i INT) WITH (vacuum_index_cleanup=auto);
+SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass;
+
-- Test vacuum_truncate option
DROP TABLE reloptions_test;
diff --git a/src/tools/pgindent/typedefs.list b/src/tools/pgindent/typedefs.list
index 3f3a888fd0e..1c8610fd46c 100644
--- a/src/tools/pgindent/typedefs.list
+++ b/src/tools/pgindent/typedefs.list
@@ -3952,6 +3952,7 @@ pg_sha512_ctx
pg_snapshot
pg_special_case
pg_stack_base_t
+pg_ternary
pg_time_t
pg_time_usec_t
pg_tz
@@ -4079,6 +4080,7 @@ relopt_kind
relopt_parse_elt
relopt_real
relopt_string
+relopt_ternary
relopt_type
relopt_value
relopts_validator
--
2.47.3
--c36t3fndxhkzbl5z--
^ permalink raw reply [nested|flat] 868+ messages in thread
* [PATCH v3] Introduce ternary reloptions
@ 2026-01-16 15:04 Álvaro Herrera <[email protected]>
0 siblings, 0 replies; 868+ messages in thread
From: Álvaro Herrera @ 2026-01-16 15:04 UTC (permalink / raw)
Introduce ternary reloption as a replacement for current
`vacuum_truncate` implementation. Remove the `vacuum_truncate_set`
separate flag and use TERNARY_UNSET instead.
This could also be used for other options such as `vacuum_index_cleanup`
and `buffering`, but lets get the scaffolding in first.
Discussion: https://postgr.es/m/3474141.usfYGdeWWP@thinkpad-pgpro
---
src/backend/access/common/reloptions.c | 137 ++++++++++++++++++-----
src/backend/commands/vacuum.c | 4 +-
src/include/access/reloptions.h | 27 ++---
src/include/postgres.h | 15 +++
src/include/utils/rel.h | 3 +-
src/test/regress/expected/reloptions.out | 36 ++++++
src/test/regress/sql/reloptions.sql | 21 ++++
src/tools/pgindent/typedefs.list | 2 +
8 files changed, 202 insertions(+), 43 deletions(-)
diff --git a/src/backend/access/common/reloptions.c b/src/backend/access/common/reloptions.c
index 0b83f98ed5f..6f1f577581a 100644
--- a/src/backend/access/common/reloptions.c
+++ b/src/backend/access/common/reloptions.c
@@ -40,9 +40,9 @@
*
* To add an option:
*
- * (i) decide on a type (bool, integer, real, enum, string), name, default
- * value, upper and lower bounds (if applicable); for strings, consider a
- * validation routine.
+ * (i) decide on a type (bool, ternary, integer, real, enum, string), name,
+ * default value, upper and lower bounds (if applicable); for strings,
+ * consider a validation routine.
* (ii) add a record below (or use add_<type>_reloption).
* (iii) add it to the appropriate options struct (perhaps StdRdOptions)
* (iv) add it to the appropriate handling routine (perhaps
@@ -147,15 +147,6 @@ static relopt_bool boolRelOpts[] =
},
false
},
- {
- {
- "vacuum_truncate",
- "Enables vacuum to truncate empty pages at the end of this table",
- RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
- ShareUpdateExclusiveLock
- },
- true
- },
{
{
"deduplicate_items",
@@ -170,6 +161,25 @@ static relopt_bool boolRelOpts[] =
{{NULL}}
};
+static relopt_ternary ternaryRelOpts[] =
+{
+ {
+ {
+ "vacuum_truncate",
+ "Enables vacuum to truncate empty pages at the end of this table",
+ RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
+ ShareUpdateExclusiveLock
+ },
+ TERNARY_UNSET
+ },
+ /* list terminator */
+ {
+ {
+ NULL
+ }
+ }
+};
+
static relopt_int intRelOpts[] =
{
{
@@ -609,6 +619,13 @@ initialize_reloptions(void)
boolRelOpts[i].gen.lockmode));
j++;
}
+ for (i = 0; ternaryRelOpts[i].gen.name; i++)
+ {
+ Assert(DoLockModesConflict(ternaryRelOpts[i].gen.lockmode,
+ ternaryRelOpts[i].gen.lockmode));
+ j++;
+ }
+
for (i = 0; intRelOpts[i].gen.name; i++)
{
Assert(DoLockModesConflict(intRelOpts[i].gen.lockmode,
@@ -649,6 +666,14 @@ initialize_reloptions(void)
j++;
}
+ for (i = 0; ternaryRelOpts[i].gen.name; i++)
+ {
+ relOpts[j] = &ternaryRelOpts[i].gen;
+ relOpts[j]->type = RELOPT_TYPE_TERNARY;
+ relOpts[j]->namelen = strlen(relOpts[j]->name);
+ j++;
+ }
+
for (i = 0; intRelOpts[i].gen.name; i++)
{
relOpts[j] = &intRelOpts[i].gen;
@@ -809,6 +834,9 @@ allocate_reloption(bits32 kinds, int type, const char *name, const char *desc,
case RELOPT_TYPE_BOOL:
size = sizeof(relopt_bool);
break;
+ case RELOPT_TYPE_TERNARY:
+ size = sizeof(relopt_ternary);
+ break;
case RELOPT_TYPE_INT:
size = sizeof(relopt_int);
break;
@@ -892,6 +920,57 @@ add_local_bool_reloption(local_relopts *relopts, const char *name,
add_local_reloption(relopts, (relopt_gen *) newoption, offset);
}
+/*
+ * init_ternary_reloption
+ * Allocate and initialize a new ternary reloption
+ */
+static relopt_ternary *
+init_ternary_reloption(bits32 kinds, const char *name, const char *desc,
+ pg_ternary default_val, LOCKMODE lockmode)
+{
+ relopt_ternary *newoption;
+
+ newoption = (relopt_ternary *)
+ allocate_reloption(kinds, RELOPT_TYPE_TERNARY, name, desc, lockmode);
+ newoption->default_val = default_val;
+
+ return newoption;
+}
+
+/*
+ * add_ternary_reloption
+ * Add a new ternary reloption
+ */
+void
+add_ternary_reloption(bits32 kinds, const char *name, const char *desc,
+ pg_ternary default_val, LOCKMODE lockmode)
+{
+ relopt_ternary *newoption;
+
+ newoption =
+ init_ternary_reloption(kinds, name, desc, default_val, lockmode);
+
+ add_reloption((relopt_gen *) newoption);
+}
+
+/*
+ * add_local_ternary_reloption
+ * Add a new ternary local reloption
+ *
+ * 'offset' is offset of ternary-typed field.
+ */
+void
+add_local_ternary_reloption(local_relopts *relopts, const char *name,
+ const char *desc, pg_ternary default_val,
+ int offset)
+{
+ relopt_ternary *newoption;
+
+ newoption =
+ init_ternary_reloption(RELOPT_KIND_LOCAL, name, desc, default_val, 0);
+
+ add_local_reloption(relopts, (relopt_gen *) newoption, offset);
+}
/*
* init_real_reloption
@@ -1626,6 +1705,19 @@ parse_one_reloption(relopt_value *option, char *text_str, int text_len,
option->gen->name, value)));
}
break;
+ case RELOPT_TYPE_TERNARY:
+ {
+ bool b;
+
+ parsed = parse_bool(value, &b);
+ option->values.ternary_val = b ? TERNARY_TRUE : TERNARY_FALSE;
+ if (validate && !parsed)
+ ereport(ERROR,
+ (errcode(ERRCODE_INVALID_PARAMETER_VALUE),
+ errmsg("invalid value for ternary option \"%s\": %s",
+ option->gen->name, value)));
+ }
+ break;
case RELOPT_TYPE_INT:
{
relopt_int *optint = (relopt_int *) option->gen;
@@ -1789,17 +1881,6 @@ fillRelOptions(void *rdopts, Size basesize,
char *itempos = ((char *) rdopts) + elems[j].offset;
char *string_val;
- /*
- * If isset_offset is provided, store whether the reloption is
- * set there.
- */
- if (elems[j].isset_offset > 0)
- {
- char *setpos = ((char *) rdopts) + elems[j].isset_offset;
-
- *(bool *) setpos = options[i].isset;
- }
-
switch (options[i].gen->type)
{
case RELOPT_TYPE_BOOL:
@@ -1807,6 +1888,11 @@ fillRelOptions(void *rdopts, Size basesize,
options[i].values.bool_val :
((relopt_bool *) options[i].gen)->default_val;
break;
+ case RELOPT_TYPE_TERNARY:
+ *(pg_ternary *) itempos = options[i].isset ?
+ options[i].values.ternary_val :
+ ((relopt_ternary *) options[i].gen)->default_val;
+ break;
case RELOPT_TYPE_INT:
*(int *) itempos = options[i].isset ?
options[i].values.int_val :
@@ -1923,8 +2009,8 @@ default_reloptions(Datum reloptions, bool validate, relopt_kind kind)
offsetof(StdRdOptions, parallel_workers)},
{"vacuum_index_cleanup", RELOPT_TYPE_ENUM,
offsetof(StdRdOptions, vacuum_index_cleanup)},
- {"vacuum_truncate", RELOPT_TYPE_BOOL,
- offsetof(StdRdOptions, vacuum_truncate), offsetof(StdRdOptions, vacuum_truncate_set)},
+ {"vacuum_truncate", RELOPT_TYPE_TERNARY,
+ offsetof(StdRdOptions, vacuum_truncate)},
{"vacuum_max_eager_freeze_failure_rate", RELOPT_TYPE_REAL,
offsetof(StdRdOptions, vacuum_max_eager_freeze_failure_rate)}
};
@@ -2004,7 +2090,6 @@ build_local_reloptions(local_relopts *relopts, Datum options, bool validate)
elems[i].optname = opt->option->name;
elems[i].opttype = opt->option->type;
elems[i].offset = opt->offset;
- elems[i].isset_offset = 0; /* not supported for local relopts yet */
i++;
}
diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c
index aa4fbec143f..696eab9bd97 100644
--- a/src/backend/commands/vacuum.c
+++ b/src/backend/commands/vacuum.c
@@ -2224,9 +2224,9 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams params,
{
StdRdOptions *opts = (StdRdOptions *) rel->rd_options;
- if (opts && opts->vacuum_truncate_set)
+ if (opts && opts->vacuum_truncate != TERNARY_UNSET)
{
- if (opts->vacuum_truncate)
+ if (opts->vacuum_truncate == TERNARY_TRUE)
params.truncate = VACOPTVALUE_ENABLED;
else
params.truncate = VACOPTVALUE_DISABLED;
diff --git a/src/include/access/reloptions.h b/src/include/access/reloptions.h
index 2f08e1b0cf0..dfbef2babf2 100644
--- a/src/include/access/reloptions.h
+++ b/src/include/access/reloptions.h
@@ -29,6 +29,7 @@
typedef enum relopt_type
{
RELOPT_TYPE_BOOL,
+ RELOPT_TYPE_TERNARY, /* on, off, unset */
RELOPT_TYPE_INT,
RELOPT_TYPE_REAL,
RELOPT_TYPE_ENUM,
@@ -80,6 +81,7 @@ typedef struct relopt_value
union
{
bool bool_val;
+ pg_ternary ternary_val;
int int_val;
double real_val;
int enum_val;
@@ -94,6 +96,12 @@ typedef struct relopt_bool
bool default_val;
} relopt_bool;
+typedef struct relopt_ternary
+{
+ relopt_gen gen;
+ int default_val;
+} relopt_ternary;
+
typedef struct relopt_int
{
relopt_gen gen;
@@ -152,19 +160,6 @@ typedef struct
const char *optname; /* option's name */
relopt_type opttype; /* option's datatype */
int offset; /* offset of field in result struct */
-
- /*
- * isset_offset is an optional offset of a field in the result struct that
- * stores whether the option is explicitly set for the relation or if it
- * just picked up the default value. In most cases, this can be
- * accomplished by giving the reloption a special out-of-range default
- * value (e.g., some integer reloptions use -2), but this isn't always
- * possible. For example, a Boolean reloption cannot be given an
- * out-of-range default, so we need another way to discover the source of
- * its value. This offset is only used if given a value greater than
- * zero.
- */
- int isset_offset;
} relopt_parse_elt;
/* Local reloption definition */
@@ -195,6 +190,9 @@ typedef struct local_relopts
extern relopt_kind add_reloption_kind(void);
extern void add_bool_reloption(bits32 kinds, const char *name, const char *desc,
bool default_val, LOCKMODE lockmode);
+extern void add_ternary_reloption(bits32 kinds, const char *name,
+ const char *desc, pg_ternary default_val,
+ LOCKMODE lockmode);
extern void add_int_reloption(bits32 kinds, const char *name, const char *desc,
int default_val, int min_val, int max_val,
LOCKMODE lockmode);
@@ -214,6 +212,9 @@ extern void register_reloptions_validator(local_relopts *relopts,
extern void add_local_bool_reloption(local_relopts *relopts, const char *name,
const char *desc, bool default_val,
int offset);
+extern void add_local_ternary_reloption(local_relopts *relopts,
+ const char *name, const char *desc,
+ pg_ternary default_val, int offset);
extern void add_local_int_reloption(local_relopts *relopts, const char *name,
const char *desc, int default_val,
int min_val, int max_val, int offset);
diff --git a/src/include/postgres.h b/src/include/postgres.h
index 7d93fbce709..47c88780776 100644
--- a/src/include/postgres.h
+++ b/src/include/postgres.h
@@ -543,6 +543,21 @@ Float8GetDatum(float8 X)
* ----------------------------------------------------------------
*/
+/*
+ * pg_ternary
+ * Boolean value with an extra "unset" value
+ *
+ * This enum can be used for values that want to distinguish between true,
+ * false, and unset.
+*/
+
+typedef enum pg_ternary
+{
+ TERNARY_FALSE = 0,
+ TERNARY_TRUE = 1,
+ TERNARY_UNSET = -1
+} pg_ternary;
+
/*
* NON_EXEC_STATIC: It's sometimes useful to define a variable or function
* that is normally static but extern when using EXEC_BACKEND (see
diff --git a/src/include/utils/rel.h b/src/include/utils/rel.h
index d03ab247788..5feb18a5373 100644
--- a/src/include/utils/rel.h
+++ b/src/include/utils/rel.h
@@ -347,8 +347,7 @@ typedef struct StdRdOptions
bool user_catalog_table; /* use as an additional catalog relation */
int parallel_workers; /* max number of parallel workers */
StdRdOptIndexCleanup vacuum_index_cleanup; /* controls index vacuuming */
- bool vacuum_truncate; /* enables vacuum to truncate a relation */
- bool vacuum_truncate_set; /* whether vacuum_truncate is set */
+ pg_ternary vacuum_truncate; /* enables vacuum to truncate a relation */
/*
* Fraction of pages in a relation that vacuum can eagerly scan and fail
diff --git a/src/test/regress/expected/reloptions.out b/src/test/regress/expected/reloptions.out
index 9de19b4e3f1..1c99f79ab01 100644
--- a/src/test/regress/expected/reloptions.out
+++ b/src/test/regress/expected/reloptions.out
@@ -98,6 +98,42 @@ SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass;
{fillfactor=13,autovacuum_enabled=false}
(1 row)
+-- Tests for future (FIXME) ternary options
+-- behave as boolean option: accept unassigned name and truncated value
+DROP TABLE reloptions_test;
+CREATE TABLE reloptions_test(i INT) WITH (vacuum_truncate);
+SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass;
+ reloptions
+------------------------
+ {vacuum_truncate=true}
+(1 row)
+
+DROP TABLE reloptions_test;
+CREATE TABLE reloptions_test(i INT) WITH (vacuum_truncate=FaLS);
+SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass;
+ reloptions
+------------------------
+ {vacuum_truncate=fals}
+(1 row)
+
+-- preferred "true" alias is used when storing
+DROP TABLE reloptions_test;
+CREATE TABLE reloptions_test(i INT) WITH (vacuum_index_cleanup=on);
+SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass;
+ reloptions
+---------------------------
+ {vacuum_index_cleanup=on}
+(1 row)
+
+-- custom "third" value is available
+DROP TABLE reloptions_test;
+CREATE TABLE reloptions_test(i INT) WITH (vacuum_index_cleanup=auto);
+SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass;
+ reloptions
+-----------------------------
+ {vacuum_index_cleanup=auto}
+(1 row)
+
-- Test vacuum_truncate option
DROP TABLE reloptions_test;
CREATE TEMP TABLE reloptions_test(i INT NOT NULL, j text)
diff --git a/src/test/regress/sql/reloptions.sql b/src/test/regress/sql/reloptions.sql
index 24fbe0b478d..f5980dafcbc 100644
--- a/src/test/regress/sql/reloptions.sql
+++ b/src/test/regress/sql/reloptions.sql
@@ -59,6 +59,27 @@ UPDATE pg_class
ALTER TABLE reloptions_test RESET (illegal_option);
SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass;
+-- Tests for future (FIXME) ternary options
+
+-- behave as boolean option: accept unassigned name and truncated value
+DROP TABLE reloptions_test;
+CREATE TABLE reloptions_test(i INT) WITH (vacuum_truncate);
+SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass;
+
+DROP TABLE reloptions_test;
+CREATE TABLE reloptions_test(i INT) WITH (vacuum_truncate=FaLS);
+SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass;
+
+-- preferred "true" alias is used when storing
+DROP TABLE reloptions_test;
+CREATE TABLE reloptions_test(i INT) WITH (vacuum_index_cleanup=on);
+SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass;
+
+-- custom "third" value is available
+DROP TABLE reloptions_test;
+CREATE TABLE reloptions_test(i INT) WITH (vacuum_index_cleanup=auto);
+SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass;
+
-- Test vacuum_truncate option
DROP TABLE reloptions_test;
diff --git a/src/tools/pgindent/typedefs.list b/src/tools/pgindent/typedefs.list
index 3f3a888fd0e..1c8610fd46c 100644
--- a/src/tools/pgindent/typedefs.list
+++ b/src/tools/pgindent/typedefs.list
@@ -3952,6 +3952,7 @@ pg_sha512_ctx
pg_snapshot
pg_special_case
pg_stack_base_t
+pg_ternary
pg_time_t
pg_time_usec_t
pg_tz
@@ -4079,6 +4080,7 @@ relopt_kind
relopt_parse_elt
relopt_real
relopt_string
+relopt_ternary
relopt_type
relopt_value
relopts_validator
--
2.47.3
--c36t3fndxhkzbl5z--
^ permalink raw reply [nested|flat] 868+ messages in thread
* [PATCH v3] Introduce ternary reloptions
@ 2026-01-16 15:04 Álvaro Herrera <[email protected]>
0 siblings, 0 replies; 868+ messages in thread
From: Álvaro Herrera @ 2026-01-16 15:04 UTC (permalink / raw)
Introduce ternary reloption as a replacement for current
`vacuum_truncate` implementation. Remove the `vacuum_truncate_set`
separate flag and use TERNARY_UNSET instead.
This could also be used for other options such as `vacuum_index_cleanup`
and `buffering`, but lets get the scaffolding in first.
Discussion: https://postgr.es/m/3474141.usfYGdeWWP@thinkpad-pgpro
---
src/backend/access/common/reloptions.c | 137 ++++++++++++++++++-----
src/backend/commands/vacuum.c | 4 +-
src/include/access/reloptions.h | 27 ++---
src/include/postgres.h | 15 +++
src/include/utils/rel.h | 3 +-
src/test/regress/expected/reloptions.out | 36 ++++++
src/test/regress/sql/reloptions.sql | 21 ++++
src/tools/pgindent/typedefs.list | 2 +
8 files changed, 202 insertions(+), 43 deletions(-)
diff --git a/src/backend/access/common/reloptions.c b/src/backend/access/common/reloptions.c
index 0b83f98ed5f..6f1f577581a 100644
--- a/src/backend/access/common/reloptions.c
+++ b/src/backend/access/common/reloptions.c
@@ -40,9 +40,9 @@
*
* To add an option:
*
- * (i) decide on a type (bool, integer, real, enum, string), name, default
- * value, upper and lower bounds (if applicable); for strings, consider a
- * validation routine.
+ * (i) decide on a type (bool, ternary, integer, real, enum, string), name,
+ * default value, upper and lower bounds (if applicable); for strings,
+ * consider a validation routine.
* (ii) add a record below (or use add_<type>_reloption).
* (iii) add it to the appropriate options struct (perhaps StdRdOptions)
* (iv) add it to the appropriate handling routine (perhaps
@@ -147,15 +147,6 @@ static relopt_bool boolRelOpts[] =
},
false
},
- {
- {
- "vacuum_truncate",
- "Enables vacuum to truncate empty pages at the end of this table",
- RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
- ShareUpdateExclusiveLock
- },
- true
- },
{
{
"deduplicate_items",
@@ -170,6 +161,25 @@ static relopt_bool boolRelOpts[] =
{{NULL}}
};
+static relopt_ternary ternaryRelOpts[] =
+{
+ {
+ {
+ "vacuum_truncate",
+ "Enables vacuum to truncate empty pages at the end of this table",
+ RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
+ ShareUpdateExclusiveLock
+ },
+ TERNARY_UNSET
+ },
+ /* list terminator */
+ {
+ {
+ NULL
+ }
+ }
+};
+
static relopt_int intRelOpts[] =
{
{
@@ -609,6 +619,13 @@ initialize_reloptions(void)
boolRelOpts[i].gen.lockmode));
j++;
}
+ for (i = 0; ternaryRelOpts[i].gen.name; i++)
+ {
+ Assert(DoLockModesConflict(ternaryRelOpts[i].gen.lockmode,
+ ternaryRelOpts[i].gen.lockmode));
+ j++;
+ }
+
for (i = 0; intRelOpts[i].gen.name; i++)
{
Assert(DoLockModesConflict(intRelOpts[i].gen.lockmode,
@@ -649,6 +666,14 @@ initialize_reloptions(void)
j++;
}
+ for (i = 0; ternaryRelOpts[i].gen.name; i++)
+ {
+ relOpts[j] = &ternaryRelOpts[i].gen;
+ relOpts[j]->type = RELOPT_TYPE_TERNARY;
+ relOpts[j]->namelen = strlen(relOpts[j]->name);
+ j++;
+ }
+
for (i = 0; intRelOpts[i].gen.name; i++)
{
relOpts[j] = &intRelOpts[i].gen;
@@ -809,6 +834,9 @@ allocate_reloption(bits32 kinds, int type, const char *name, const char *desc,
case RELOPT_TYPE_BOOL:
size = sizeof(relopt_bool);
break;
+ case RELOPT_TYPE_TERNARY:
+ size = sizeof(relopt_ternary);
+ break;
case RELOPT_TYPE_INT:
size = sizeof(relopt_int);
break;
@@ -892,6 +920,57 @@ add_local_bool_reloption(local_relopts *relopts, const char *name,
add_local_reloption(relopts, (relopt_gen *) newoption, offset);
}
+/*
+ * init_ternary_reloption
+ * Allocate and initialize a new ternary reloption
+ */
+static relopt_ternary *
+init_ternary_reloption(bits32 kinds, const char *name, const char *desc,
+ pg_ternary default_val, LOCKMODE lockmode)
+{
+ relopt_ternary *newoption;
+
+ newoption = (relopt_ternary *)
+ allocate_reloption(kinds, RELOPT_TYPE_TERNARY, name, desc, lockmode);
+ newoption->default_val = default_val;
+
+ return newoption;
+}
+
+/*
+ * add_ternary_reloption
+ * Add a new ternary reloption
+ */
+void
+add_ternary_reloption(bits32 kinds, const char *name, const char *desc,
+ pg_ternary default_val, LOCKMODE lockmode)
+{
+ relopt_ternary *newoption;
+
+ newoption =
+ init_ternary_reloption(kinds, name, desc, default_val, lockmode);
+
+ add_reloption((relopt_gen *) newoption);
+}
+
+/*
+ * add_local_ternary_reloption
+ * Add a new ternary local reloption
+ *
+ * 'offset' is offset of ternary-typed field.
+ */
+void
+add_local_ternary_reloption(local_relopts *relopts, const char *name,
+ const char *desc, pg_ternary default_val,
+ int offset)
+{
+ relopt_ternary *newoption;
+
+ newoption =
+ init_ternary_reloption(RELOPT_KIND_LOCAL, name, desc, default_val, 0);
+
+ add_local_reloption(relopts, (relopt_gen *) newoption, offset);
+}
/*
* init_real_reloption
@@ -1626,6 +1705,19 @@ parse_one_reloption(relopt_value *option, char *text_str, int text_len,
option->gen->name, value)));
}
break;
+ case RELOPT_TYPE_TERNARY:
+ {
+ bool b;
+
+ parsed = parse_bool(value, &b);
+ option->values.ternary_val = b ? TERNARY_TRUE : TERNARY_FALSE;
+ if (validate && !parsed)
+ ereport(ERROR,
+ (errcode(ERRCODE_INVALID_PARAMETER_VALUE),
+ errmsg("invalid value for ternary option \"%s\": %s",
+ option->gen->name, value)));
+ }
+ break;
case RELOPT_TYPE_INT:
{
relopt_int *optint = (relopt_int *) option->gen;
@@ -1789,17 +1881,6 @@ fillRelOptions(void *rdopts, Size basesize,
char *itempos = ((char *) rdopts) + elems[j].offset;
char *string_val;
- /*
- * If isset_offset is provided, store whether the reloption is
- * set there.
- */
- if (elems[j].isset_offset > 0)
- {
- char *setpos = ((char *) rdopts) + elems[j].isset_offset;
-
- *(bool *) setpos = options[i].isset;
- }
-
switch (options[i].gen->type)
{
case RELOPT_TYPE_BOOL:
@@ -1807,6 +1888,11 @@ fillRelOptions(void *rdopts, Size basesize,
options[i].values.bool_val :
((relopt_bool *) options[i].gen)->default_val;
break;
+ case RELOPT_TYPE_TERNARY:
+ *(pg_ternary *) itempos = options[i].isset ?
+ options[i].values.ternary_val :
+ ((relopt_ternary *) options[i].gen)->default_val;
+ break;
case RELOPT_TYPE_INT:
*(int *) itempos = options[i].isset ?
options[i].values.int_val :
@@ -1923,8 +2009,8 @@ default_reloptions(Datum reloptions, bool validate, relopt_kind kind)
offsetof(StdRdOptions, parallel_workers)},
{"vacuum_index_cleanup", RELOPT_TYPE_ENUM,
offsetof(StdRdOptions, vacuum_index_cleanup)},
- {"vacuum_truncate", RELOPT_TYPE_BOOL,
- offsetof(StdRdOptions, vacuum_truncate), offsetof(StdRdOptions, vacuum_truncate_set)},
+ {"vacuum_truncate", RELOPT_TYPE_TERNARY,
+ offsetof(StdRdOptions, vacuum_truncate)},
{"vacuum_max_eager_freeze_failure_rate", RELOPT_TYPE_REAL,
offsetof(StdRdOptions, vacuum_max_eager_freeze_failure_rate)}
};
@@ -2004,7 +2090,6 @@ build_local_reloptions(local_relopts *relopts, Datum options, bool validate)
elems[i].optname = opt->option->name;
elems[i].opttype = opt->option->type;
elems[i].offset = opt->offset;
- elems[i].isset_offset = 0; /* not supported for local relopts yet */
i++;
}
diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c
index aa4fbec143f..696eab9bd97 100644
--- a/src/backend/commands/vacuum.c
+++ b/src/backend/commands/vacuum.c
@@ -2224,9 +2224,9 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams params,
{
StdRdOptions *opts = (StdRdOptions *) rel->rd_options;
- if (opts && opts->vacuum_truncate_set)
+ if (opts && opts->vacuum_truncate != TERNARY_UNSET)
{
- if (opts->vacuum_truncate)
+ if (opts->vacuum_truncate == TERNARY_TRUE)
params.truncate = VACOPTVALUE_ENABLED;
else
params.truncate = VACOPTVALUE_DISABLED;
diff --git a/src/include/access/reloptions.h b/src/include/access/reloptions.h
index 2f08e1b0cf0..dfbef2babf2 100644
--- a/src/include/access/reloptions.h
+++ b/src/include/access/reloptions.h
@@ -29,6 +29,7 @@
typedef enum relopt_type
{
RELOPT_TYPE_BOOL,
+ RELOPT_TYPE_TERNARY, /* on, off, unset */
RELOPT_TYPE_INT,
RELOPT_TYPE_REAL,
RELOPT_TYPE_ENUM,
@@ -80,6 +81,7 @@ typedef struct relopt_value
union
{
bool bool_val;
+ pg_ternary ternary_val;
int int_val;
double real_val;
int enum_val;
@@ -94,6 +96,12 @@ typedef struct relopt_bool
bool default_val;
} relopt_bool;
+typedef struct relopt_ternary
+{
+ relopt_gen gen;
+ int default_val;
+} relopt_ternary;
+
typedef struct relopt_int
{
relopt_gen gen;
@@ -152,19 +160,6 @@ typedef struct
const char *optname; /* option's name */
relopt_type opttype; /* option's datatype */
int offset; /* offset of field in result struct */
-
- /*
- * isset_offset is an optional offset of a field in the result struct that
- * stores whether the option is explicitly set for the relation or if it
- * just picked up the default value. In most cases, this can be
- * accomplished by giving the reloption a special out-of-range default
- * value (e.g., some integer reloptions use -2), but this isn't always
- * possible. For example, a Boolean reloption cannot be given an
- * out-of-range default, so we need another way to discover the source of
- * its value. This offset is only used if given a value greater than
- * zero.
- */
- int isset_offset;
} relopt_parse_elt;
/* Local reloption definition */
@@ -195,6 +190,9 @@ typedef struct local_relopts
extern relopt_kind add_reloption_kind(void);
extern void add_bool_reloption(bits32 kinds, const char *name, const char *desc,
bool default_val, LOCKMODE lockmode);
+extern void add_ternary_reloption(bits32 kinds, const char *name,
+ const char *desc, pg_ternary default_val,
+ LOCKMODE lockmode);
extern void add_int_reloption(bits32 kinds, const char *name, const char *desc,
int default_val, int min_val, int max_val,
LOCKMODE lockmode);
@@ -214,6 +212,9 @@ extern void register_reloptions_validator(local_relopts *relopts,
extern void add_local_bool_reloption(local_relopts *relopts, const char *name,
const char *desc, bool default_val,
int offset);
+extern void add_local_ternary_reloption(local_relopts *relopts,
+ const char *name, const char *desc,
+ pg_ternary default_val, int offset);
extern void add_local_int_reloption(local_relopts *relopts, const char *name,
const char *desc, int default_val,
int min_val, int max_val, int offset);
diff --git a/src/include/postgres.h b/src/include/postgres.h
index 7d93fbce709..47c88780776 100644
--- a/src/include/postgres.h
+++ b/src/include/postgres.h
@@ -543,6 +543,21 @@ Float8GetDatum(float8 X)
* ----------------------------------------------------------------
*/
+/*
+ * pg_ternary
+ * Boolean value with an extra "unset" value
+ *
+ * This enum can be used for values that want to distinguish between true,
+ * false, and unset.
+*/
+
+typedef enum pg_ternary
+{
+ TERNARY_FALSE = 0,
+ TERNARY_TRUE = 1,
+ TERNARY_UNSET = -1
+} pg_ternary;
+
/*
* NON_EXEC_STATIC: It's sometimes useful to define a variable or function
* that is normally static but extern when using EXEC_BACKEND (see
diff --git a/src/include/utils/rel.h b/src/include/utils/rel.h
index d03ab247788..5feb18a5373 100644
--- a/src/include/utils/rel.h
+++ b/src/include/utils/rel.h
@@ -347,8 +347,7 @@ typedef struct StdRdOptions
bool user_catalog_table; /* use as an additional catalog relation */
int parallel_workers; /* max number of parallel workers */
StdRdOptIndexCleanup vacuum_index_cleanup; /* controls index vacuuming */
- bool vacuum_truncate; /* enables vacuum to truncate a relation */
- bool vacuum_truncate_set; /* whether vacuum_truncate is set */
+ pg_ternary vacuum_truncate; /* enables vacuum to truncate a relation */
/*
* Fraction of pages in a relation that vacuum can eagerly scan and fail
diff --git a/src/test/regress/expected/reloptions.out b/src/test/regress/expected/reloptions.out
index 9de19b4e3f1..1c99f79ab01 100644
--- a/src/test/regress/expected/reloptions.out
+++ b/src/test/regress/expected/reloptions.out
@@ -98,6 +98,42 @@ SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass;
{fillfactor=13,autovacuum_enabled=false}
(1 row)
+-- Tests for future (FIXME) ternary options
+-- behave as boolean option: accept unassigned name and truncated value
+DROP TABLE reloptions_test;
+CREATE TABLE reloptions_test(i INT) WITH (vacuum_truncate);
+SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass;
+ reloptions
+------------------------
+ {vacuum_truncate=true}
+(1 row)
+
+DROP TABLE reloptions_test;
+CREATE TABLE reloptions_test(i INT) WITH (vacuum_truncate=FaLS);
+SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass;
+ reloptions
+------------------------
+ {vacuum_truncate=fals}
+(1 row)
+
+-- preferred "true" alias is used when storing
+DROP TABLE reloptions_test;
+CREATE TABLE reloptions_test(i INT) WITH (vacuum_index_cleanup=on);
+SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass;
+ reloptions
+---------------------------
+ {vacuum_index_cleanup=on}
+(1 row)
+
+-- custom "third" value is available
+DROP TABLE reloptions_test;
+CREATE TABLE reloptions_test(i INT) WITH (vacuum_index_cleanup=auto);
+SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass;
+ reloptions
+-----------------------------
+ {vacuum_index_cleanup=auto}
+(1 row)
+
-- Test vacuum_truncate option
DROP TABLE reloptions_test;
CREATE TEMP TABLE reloptions_test(i INT NOT NULL, j text)
diff --git a/src/test/regress/sql/reloptions.sql b/src/test/regress/sql/reloptions.sql
index 24fbe0b478d..f5980dafcbc 100644
--- a/src/test/regress/sql/reloptions.sql
+++ b/src/test/regress/sql/reloptions.sql
@@ -59,6 +59,27 @@ UPDATE pg_class
ALTER TABLE reloptions_test RESET (illegal_option);
SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass;
+-- Tests for future (FIXME) ternary options
+
+-- behave as boolean option: accept unassigned name and truncated value
+DROP TABLE reloptions_test;
+CREATE TABLE reloptions_test(i INT) WITH (vacuum_truncate);
+SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass;
+
+DROP TABLE reloptions_test;
+CREATE TABLE reloptions_test(i INT) WITH (vacuum_truncate=FaLS);
+SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass;
+
+-- preferred "true" alias is used when storing
+DROP TABLE reloptions_test;
+CREATE TABLE reloptions_test(i INT) WITH (vacuum_index_cleanup=on);
+SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass;
+
+-- custom "third" value is available
+DROP TABLE reloptions_test;
+CREATE TABLE reloptions_test(i INT) WITH (vacuum_index_cleanup=auto);
+SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass;
+
-- Test vacuum_truncate option
DROP TABLE reloptions_test;
diff --git a/src/tools/pgindent/typedefs.list b/src/tools/pgindent/typedefs.list
index 3f3a888fd0e..1c8610fd46c 100644
--- a/src/tools/pgindent/typedefs.list
+++ b/src/tools/pgindent/typedefs.list
@@ -3952,6 +3952,7 @@ pg_sha512_ctx
pg_snapshot
pg_special_case
pg_stack_base_t
+pg_ternary
pg_time_t
pg_time_usec_t
pg_tz
@@ -4079,6 +4080,7 @@ relopt_kind
relopt_parse_elt
relopt_real
relopt_string
+relopt_ternary
relopt_type
relopt_value
relopts_validator
--
2.47.3
--c36t3fndxhkzbl5z--
^ permalink raw reply [nested|flat] 868+ messages in thread
* [PATCH v3] Introduce ternary reloptions
@ 2026-01-16 15:04 Álvaro Herrera <[email protected]>
0 siblings, 0 replies; 868+ messages in thread
From: Álvaro Herrera @ 2026-01-16 15:04 UTC (permalink / raw)
Introduce ternary reloption as a replacement for current
`vacuum_truncate` implementation. Remove the `vacuum_truncate_set`
separate flag and use TERNARY_UNSET instead.
This could also be used for other options such as `vacuum_index_cleanup`
and `buffering`, but lets get the scaffolding in first.
Discussion: https://postgr.es/m/3474141.usfYGdeWWP@thinkpad-pgpro
---
src/backend/access/common/reloptions.c | 137 ++++++++++++++++++-----
src/backend/commands/vacuum.c | 4 +-
src/include/access/reloptions.h | 27 ++---
src/include/postgres.h | 15 +++
src/include/utils/rel.h | 3 +-
src/test/regress/expected/reloptions.out | 36 ++++++
src/test/regress/sql/reloptions.sql | 21 ++++
src/tools/pgindent/typedefs.list | 2 +
8 files changed, 202 insertions(+), 43 deletions(-)
diff --git a/src/backend/access/common/reloptions.c b/src/backend/access/common/reloptions.c
index 0b83f98ed5f..6f1f577581a 100644
--- a/src/backend/access/common/reloptions.c
+++ b/src/backend/access/common/reloptions.c
@@ -40,9 +40,9 @@
*
* To add an option:
*
- * (i) decide on a type (bool, integer, real, enum, string), name, default
- * value, upper and lower bounds (if applicable); for strings, consider a
- * validation routine.
+ * (i) decide on a type (bool, ternary, integer, real, enum, string), name,
+ * default value, upper and lower bounds (if applicable); for strings,
+ * consider a validation routine.
* (ii) add a record below (or use add_<type>_reloption).
* (iii) add it to the appropriate options struct (perhaps StdRdOptions)
* (iv) add it to the appropriate handling routine (perhaps
@@ -147,15 +147,6 @@ static relopt_bool boolRelOpts[] =
},
false
},
- {
- {
- "vacuum_truncate",
- "Enables vacuum to truncate empty pages at the end of this table",
- RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
- ShareUpdateExclusiveLock
- },
- true
- },
{
{
"deduplicate_items",
@@ -170,6 +161,25 @@ static relopt_bool boolRelOpts[] =
{{NULL}}
};
+static relopt_ternary ternaryRelOpts[] =
+{
+ {
+ {
+ "vacuum_truncate",
+ "Enables vacuum to truncate empty pages at the end of this table",
+ RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
+ ShareUpdateExclusiveLock
+ },
+ TERNARY_UNSET
+ },
+ /* list terminator */
+ {
+ {
+ NULL
+ }
+ }
+};
+
static relopt_int intRelOpts[] =
{
{
@@ -609,6 +619,13 @@ initialize_reloptions(void)
boolRelOpts[i].gen.lockmode));
j++;
}
+ for (i = 0; ternaryRelOpts[i].gen.name; i++)
+ {
+ Assert(DoLockModesConflict(ternaryRelOpts[i].gen.lockmode,
+ ternaryRelOpts[i].gen.lockmode));
+ j++;
+ }
+
for (i = 0; intRelOpts[i].gen.name; i++)
{
Assert(DoLockModesConflict(intRelOpts[i].gen.lockmode,
@@ -649,6 +666,14 @@ initialize_reloptions(void)
j++;
}
+ for (i = 0; ternaryRelOpts[i].gen.name; i++)
+ {
+ relOpts[j] = &ternaryRelOpts[i].gen;
+ relOpts[j]->type = RELOPT_TYPE_TERNARY;
+ relOpts[j]->namelen = strlen(relOpts[j]->name);
+ j++;
+ }
+
for (i = 0; intRelOpts[i].gen.name; i++)
{
relOpts[j] = &intRelOpts[i].gen;
@@ -809,6 +834,9 @@ allocate_reloption(bits32 kinds, int type, const char *name, const char *desc,
case RELOPT_TYPE_BOOL:
size = sizeof(relopt_bool);
break;
+ case RELOPT_TYPE_TERNARY:
+ size = sizeof(relopt_ternary);
+ break;
case RELOPT_TYPE_INT:
size = sizeof(relopt_int);
break;
@@ -892,6 +920,57 @@ add_local_bool_reloption(local_relopts *relopts, const char *name,
add_local_reloption(relopts, (relopt_gen *) newoption, offset);
}
+/*
+ * init_ternary_reloption
+ * Allocate and initialize a new ternary reloption
+ */
+static relopt_ternary *
+init_ternary_reloption(bits32 kinds, const char *name, const char *desc,
+ pg_ternary default_val, LOCKMODE lockmode)
+{
+ relopt_ternary *newoption;
+
+ newoption = (relopt_ternary *)
+ allocate_reloption(kinds, RELOPT_TYPE_TERNARY, name, desc, lockmode);
+ newoption->default_val = default_val;
+
+ return newoption;
+}
+
+/*
+ * add_ternary_reloption
+ * Add a new ternary reloption
+ */
+void
+add_ternary_reloption(bits32 kinds, const char *name, const char *desc,
+ pg_ternary default_val, LOCKMODE lockmode)
+{
+ relopt_ternary *newoption;
+
+ newoption =
+ init_ternary_reloption(kinds, name, desc, default_val, lockmode);
+
+ add_reloption((relopt_gen *) newoption);
+}
+
+/*
+ * add_local_ternary_reloption
+ * Add a new ternary local reloption
+ *
+ * 'offset' is offset of ternary-typed field.
+ */
+void
+add_local_ternary_reloption(local_relopts *relopts, const char *name,
+ const char *desc, pg_ternary default_val,
+ int offset)
+{
+ relopt_ternary *newoption;
+
+ newoption =
+ init_ternary_reloption(RELOPT_KIND_LOCAL, name, desc, default_val, 0);
+
+ add_local_reloption(relopts, (relopt_gen *) newoption, offset);
+}
/*
* init_real_reloption
@@ -1626,6 +1705,19 @@ parse_one_reloption(relopt_value *option, char *text_str, int text_len,
option->gen->name, value)));
}
break;
+ case RELOPT_TYPE_TERNARY:
+ {
+ bool b;
+
+ parsed = parse_bool(value, &b);
+ option->values.ternary_val = b ? TERNARY_TRUE : TERNARY_FALSE;
+ if (validate && !parsed)
+ ereport(ERROR,
+ (errcode(ERRCODE_INVALID_PARAMETER_VALUE),
+ errmsg("invalid value for ternary option \"%s\": %s",
+ option->gen->name, value)));
+ }
+ break;
case RELOPT_TYPE_INT:
{
relopt_int *optint = (relopt_int *) option->gen;
@@ -1789,17 +1881,6 @@ fillRelOptions(void *rdopts, Size basesize,
char *itempos = ((char *) rdopts) + elems[j].offset;
char *string_val;
- /*
- * If isset_offset is provided, store whether the reloption is
- * set there.
- */
- if (elems[j].isset_offset > 0)
- {
- char *setpos = ((char *) rdopts) + elems[j].isset_offset;
-
- *(bool *) setpos = options[i].isset;
- }
-
switch (options[i].gen->type)
{
case RELOPT_TYPE_BOOL:
@@ -1807,6 +1888,11 @@ fillRelOptions(void *rdopts, Size basesize,
options[i].values.bool_val :
((relopt_bool *) options[i].gen)->default_val;
break;
+ case RELOPT_TYPE_TERNARY:
+ *(pg_ternary *) itempos = options[i].isset ?
+ options[i].values.ternary_val :
+ ((relopt_ternary *) options[i].gen)->default_val;
+ break;
case RELOPT_TYPE_INT:
*(int *) itempos = options[i].isset ?
options[i].values.int_val :
@@ -1923,8 +2009,8 @@ default_reloptions(Datum reloptions, bool validate, relopt_kind kind)
offsetof(StdRdOptions, parallel_workers)},
{"vacuum_index_cleanup", RELOPT_TYPE_ENUM,
offsetof(StdRdOptions, vacuum_index_cleanup)},
- {"vacuum_truncate", RELOPT_TYPE_BOOL,
- offsetof(StdRdOptions, vacuum_truncate), offsetof(StdRdOptions, vacuum_truncate_set)},
+ {"vacuum_truncate", RELOPT_TYPE_TERNARY,
+ offsetof(StdRdOptions, vacuum_truncate)},
{"vacuum_max_eager_freeze_failure_rate", RELOPT_TYPE_REAL,
offsetof(StdRdOptions, vacuum_max_eager_freeze_failure_rate)}
};
@@ -2004,7 +2090,6 @@ build_local_reloptions(local_relopts *relopts, Datum options, bool validate)
elems[i].optname = opt->option->name;
elems[i].opttype = opt->option->type;
elems[i].offset = opt->offset;
- elems[i].isset_offset = 0; /* not supported for local relopts yet */
i++;
}
diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c
index aa4fbec143f..696eab9bd97 100644
--- a/src/backend/commands/vacuum.c
+++ b/src/backend/commands/vacuum.c
@@ -2224,9 +2224,9 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams params,
{
StdRdOptions *opts = (StdRdOptions *) rel->rd_options;
- if (opts && opts->vacuum_truncate_set)
+ if (opts && opts->vacuum_truncate != TERNARY_UNSET)
{
- if (opts->vacuum_truncate)
+ if (opts->vacuum_truncate == TERNARY_TRUE)
params.truncate = VACOPTVALUE_ENABLED;
else
params.truncate = VACOPTVALUE_DISABLED;
diff --git a/src/include/access/reloptions.h b/src/include/access/reloptions.h
index 2f08e1b0cf0..dfbef2babf2 100644
--- a/src/include/access/reloptions.h
+++ b/src/include/access/reloptions.h
@@ -29,6 +29,7 @@
typedef enum relopt_type
{
RELOPT_TYPE_BOOL,
+ RELOPT_TYPE_TERNARY, /* on, off, unset */
RELOPT_TYPE_INT,
RELOPT_TYPE_REAL,
RELOPT_TYPE_ENUM,
@@ -80,6 +81,7 @@ typedef struct relopt_value
union
{
bool bool_val;
+ pg_ternary ternary_val;
int int_val;
double real_val;
int enum_val;
@@ -94,6 +96,12 @@ typedef struct relopt_bool
bool default_val;
} relopt_bool;
+typedef struct relopt_ternary
+{
+ relopt_gen gen;
+ int default_val;
+} relopt_ternary;
+
typedef struct relopt_int
{
relopt_gen gen;
@@ -152,19 +160,6 @@ typedef struct
const char *optname; /* option's name */
relopt_type opttype; /* option's datatype */
int offset; /* offset of field in result struct */
-
- /*
- * isset_offset is an optional offset of a field in the result struct that
- * stores whether the option is explicitly set for the relation or if it
- * just picked up the default value. In most cases, this can be
- * accomplished by giving the reloption a special out-of-range default
- * value (e.g., some integer reloptions use -2), but this isn't always
- * possible. For example, a Boolean reloption cannot be given an
- * out-of-range default, so we need another way to discover the source of
- * its value. This offset is only used if given a value greater than
- * zero.
- */
- int isset_offset;
} relopt_parse_elt;
/* Local reloption definition */
@@ -195,6 +190,9 @@ typedef struct local_relopts
extern relopt_kind add_reloption_kind(void);
extern void add_bool_reloption(bits32 kinds, const char *name, const char *desc,
bool default_val, LOCKMODE lockmode);
+extern void add_ternary_reloption(bits32 kinds, const char *name,
+ const char *desc, pg_ternary default_val,
+ LOCKMODE lockmode);
extern void add_int_reloption(bits32 kinds, const char *name, const char *desc,
int default_val, int min_val, int max_val,
LOCKMODE lockmode);
@@ -214,6 +212,9 @@ extern void register_reloptions_validator(local_relopts *relopts,
extern void add_local_bool_reloption(local_relopts *relopts, const char *name,
const char *desc, bool default_val,
int offset);
+extern void add_local_ternary_reloption(local_relopts *relopts,
+ const char *name, const char *desc,
+ pg_ternary default_val, int offset);
extern void add_local_int_reloption(local_relopts *relopts, const char *name,
const char *desc, int default_val,
int min_val, int max_val, int offset);
diff --git a/src/include/postgres.h b/src/include/postgres.h
index 7d93fbce709..47c88780776 100644
--- a/src/include/postgres.h
+++ b/src/include/postgres.h
@@ -543,6 +543,21 @@ Float8GetDatum(float8 X)
* ----------------------------------------------------------------
*/
+/*
+ * pg_ternary
+ * Boolean value with an extra "unset" value
+ *
+ * This enum can be used for values that want to distinguish between true,
+ * false, and unset.
+*/
+
+typedef enum pg_ternary
+{
+ TERNARY_FALSE = 0,
+ TERNARY_TRUE = 1,
+ TERNARY_UNSET = -1
+} pg_ternary;
+
/*
* NON_EXEC_STATIC: It's sometimes useful to define a variable or function
* that is normally static but extern when using EXEC_BACKEND (see
diff --git a/src/include/utils/rel.h b/src/include/utils/rel.h
index d03ab247788..5feb18a5373 100644
--- a/src/include/utils/rel.h
+++ b/src/include/utils/rel.h
@@ -347,8 +347,7 @@ typedef struct StdRdOptions
bool user_catalog_table; /* use as an additional catalog relation */
int parallel_workers; /* max number of parallel workers */
StdRdOptIndexCleanup vacuum_index_cleanup; /* controls index vacuuming */
- bool vacuum_truncate; /* enables vacuum to truncate a relation */
- bool vacuum_truncate_set; /* whether vacuum_truncate is set */
+ pg_ternary vacuum_truncate; /* enables vacuum to truncate a relation */
/*
* Fraction of pages in a relation that vacuum can eagerly scan and fail
diff --git a/src/test/regress/expected/reloptions.out b/src/test/regress/expected/reloptions.out
index 9de19b4e3f1..1c99f79ab01 100644
--- a/src/test/regress/expected/reloptions.out
+++ b/src/test/regress/expected/reloptions.out
@@ -98,6 +98,42 @@ SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass;
{fillfactor=13,autovacuum_enabled=false}
(1 row)
+-- Tests for future (FIXME) ternary options
+-- behave as boolean option: accept unassigned name and truncated value
+DROP TABLE reloptions_test;
+CREATE TABLE reloptions_test(i INT) WITH (vacuum_truncate);
+SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass;
+ reloptions
+------------------------
+ {vacuum_truncate=true}
+(1 row)
+
+DROP TABLE reloptions_test;
+CREATE TABLE reloptions_test(i INT) WITH (vacuum_truncate=FaLS);
+SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass;
+ reloptions
+------------------------
+ {vacuum_truncate=fals}
+(1 row)
+
+-- preferred "true" alias is used when storing
+DROP TABLE reloptions_test;
+CREATE TABLE reloptions_test(i INT) WITH (vacuum_index_cleanup=on);
+SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass;
+ reloptions
+---------------------------
+ {vacuum_index_cleanup=on}
+(1 row)
+
+-- custom "third" value is available
+DROP TABLE reloptions_test;
+CREATE TABLE reloptions_test(i INT) WITH (vacuum_index_cleanup=auto);
+SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass;
+ reloptions
+-----------------------------
+ {vacuum_index_cleanup=auto}
+(1 row)
+
-- Test vacuum_truncate option
DROP TABLE reloptions_test;
CREATE TEMP TABLE reloptions_test(i INT NOT NULL, j text)
diff --git a/src/test/regress/sql/reloptions.sql b/src/test/regress/sql/reloptions.sql
index 24fbe0b478d..f5980dafcbc 100644
--- a/src/test/regress/sql/reloptions.sql
+++ b/src/test/regress/sql/reloptions.sql
@@ -59,6 +59,27 @@ UPDATE pg_class
ALTER TABLE reloptions_test RESET (illegal_option);
SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass;
+-- Tests for future (FIXME) ternary options
+
+-- behave as boolean option: accept unassigned name and truncated value
+DROP TABLE reloptions_test;
+CREATE TABLE reloptions_test(i INT) WITH (vacuum_truncate);
+SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass;
+
+DROP TABLE reloptions_test;
+CREATE TABLE reloptions_test(i INT) WITH (vacuum_truncate=FaLS);
+SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass;
+
+-- preferred "true" alias is used when storing
+DROP TABLE reloptions_test;
+CREATE TABLE reloptions_test(i INT) WITH (vacuum_index_cleanup=on);
+SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass;
+
+-- custom "third" value is available
+DROP TABLE reloptions_test;
+CREATE TABLE reloptions_test(i INT) WITH (vacuum_index_cleanup=auto);
+SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass;
+
-- Test vacuum_truncate option
DROP TABLE reloptions_test;
diff --git a/src/tools/pgindent/typedefs.list b/src/tools/pgindent/typedefs.list
index 3f3a888fd0e..1c8610fd46c 100644
--- a/src/tools/pgindent/typedefs.list
+++ b/src/tools/pgindent/typedefs.list
@@ -3952,6 +3952,7 @@ pg_sha512_ctx
pg_snapshot
pg_special_case
pg_stack_base_t
+pg_ternary
pg_time_t
pg_time_usec_t
pg_tz
@@ -4079,6 +4080,7 @@ relopt_kind
relopt_parse_elt
relopt_real
relopt_string
+relopt_ternary
relopt_type
relopt_value
relopts_validator
--
2.47.3
--c36t3fndxhkzbl5z--
^ permalink raw reply [nested|flat] 868+ messages in thread
* [PATCH v3] Introduce ternary reloptions
@ 2026-01-16 15:04 Álvaro Herrera <[email protected]>
0 siblings, 0 replies; 868+ messages in thread
From: Álvaro Herrera @ 2026-01-16 15:04 UTC (permalink / raw)
Introduce ternary reloption as a replacement for current
`vacuum_truncate` implementation. Remove the `vacuum_truncate_set`
separate flag and use TERNARY_UNSET instead.
This could also be used for other options such as `vacuum_index_cleanup`
and `buffering`, but lets get the scaffolding in first.
Discussion: https://postgr.es/m/3474141.usfYGdeWWP@thinkpad-pgpro
---
src/backend/access/common/reloptions.c | 137 ++++++++++++++++++-----
src/backend/commands/vacuum.c | 4 +-
src/include/access/reloptions.h | 27 ++---
src/include/postgres.h | 15 +++
src/include/utils/rel.h | 3 +-
src/test/regress/expected/reloptions.out | 36 ++++++
src/test/regress/sql/reloptions.sql | 21 ++++
src/tools/pgindent/typedefs.list | 2 +
8 files changed, 202 insertions(+), 43 deletions(-)
diff --git a/src/backend/access/common/reloptions.c b/src/backend/access/common/reloptions.c
index 0b83f98ed5f..6f1f577581a 100644
--- a/src/backend/access/common/reloptions.c
+++ b/src/backend/access/common/reloptions.c
@@ -40,9 +40,9 @@
*
* To add an option:
*
- * (i) decide on a type (bool, integer, real, enum, string), name, default
- * value, upper and lower bounds (if applicable); for strings, consider a
- * validation routine.
+ * (i) decide on a type (bool, ternary, integer, real, enum, string), name,
+ * default value, upper and lower bounds (if applicable); for strings,
+ * consider a validation routine.
* (ii) add a record below (or use add_<type>_reloption).
* (iii) add it to the appropriate options struct (perhaps StdRdOptions)
* (iv) add it to the appropriate handling routine (perhaps
@@ -147,15 +147,6 @@ static relopt_bool boolRelOpts[] =
},
false
},
- {
- {
- "vacuum_truncate",
- "Enables vacuum to truncate empty pages at the end of this table",
- RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
- ShareUpdateExclusiveLock
- },
- true
- },
{
{
"deduplicate_items",
@@ -170,6 +161,25 @@ static relopt_bool boolRelOpts[] =
{{NULL}}
};
+static relopt_ternary ternaryRelOpts[] =
+{
+ {
+ {
+ "vacuum_truncate",
+ "Enables vacuum to truncate empty pages at the end of this table",
+ RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
+ ShareUpdateExclusiveLock
+ },
+ TERNARY_UNSET
+ },
+ /* list terminator */
+ {
+ {
+ NULL
+ }
+ }
+};
+
static relopt_int intRelOpts[] =
{
{
@@ -609,6 +619,13 @@ initialize_reloptions(void)
boolRelOpts[i].gen.lockmode));
j++;
}
+ for (i = 0; ternaryRelOpts[i].gen.name; i++)
+ {
+ Assert(DoLockModesConflict(ternaryRelOpts[i].gen.lockmode,
+ ternaryRelOpts[i].gen.lockmode));
+ j++;
+ }
+
for (i = 0; intRelOpts[i].gen.name; i++)
{
Assert(DoLockModesConflict(intRelOpts[i].gen.lockmode,
@@ -649,6 +666,14 @@ initialize_reloptions(void)
j++;
}
+ for (i = 0; ternaryRelOpts[i].gen.name; i++)
+ {
+ relOpts[j] = &ternaryRelOpts[i].gen;
+ relOpts[j]->type = RELOPT_TYPE_TERNARY;
+ relOpts[j]->namelen = strlen(relOpts[j]->name);
+ j++;
+ }
+
for (i = 0; intRelOpts[i].gen.name; i++)
{
relOpts[j] = &intRelOpts[i].gen;
@@ -809,6 +834,9 @@ allocate_reloption(bits32 kinds, int type, const char *name, const char *desc,
case RELOPT_TYPE_BOOL:
size = sizeof(relopt_bool);
break;
+ case RELOPT_TYPE_TERNARY:
+ size = sizeof(relopt_ternary);
+ break;
case RELOPT_TYPE_INT:
size = sizeof(relopt_int);
break;
@@ -892,6 +920,57 @@ add_local_bool_reloption(local_relopts *relopts, const char *name,
add_local_reloption(relopts, (relopt_gen *) newoption, offset);
}
+/*
+ * init_ternary_reloption
+ * Allocate and initialize a new ternary reloption
+ */
+static relopt_ternary *
+init_ternary_reloption(bits32 kinds, const char *name, const char *desc,
+ pg_ternary default_val, LOCKMODE lockmode)
+{
+ relopt_ternary *newoption;
+
+ newoption = (relopt_ternary *)
+ allocate_reloption(kinds, RELOPT_TYPE_TERNARY, name, desc, lockmode);
+ newoption->default_val = default_val;
+
+ return newoption;
+}
+
+/*
+ * add_ternary_reloption
+ * Add a new ternary reloption
+ */
+void
+add_ternary_reloption(bits32 kinds, const char *name, const char *desc,
+ pg_ternary default_val, LOCKMODE lockmode)
+{
+ relopt_ternary *newoption;
+
+ newoption =
+ init_ternary_reloption(kinds, name, desc, default_val, lockmode);
+
+ add_reloption((relopt_gen *) newoption);
+}
+
+/*
+ * add_local_ternary_reloption
+ * Add a new ternary local reloption
+ *
+ * 'offset' is offset of ternary-typed field.
+ */
+void
+add_local_ternary_reloption(local_relopts *relopts, const char *name,
+ const char *desc, pg_ternary default_val,
+ int offset)
+{
+ relopt_ternary *newoption;
+
+ newoption =
+ init_ternary_reloption(RELOPT_KIND_LOCAL, name, desc, default_val, 0);
+
+ add_local_reloption(relopts, (relopt_gen *) newoption, offset);
+}
/*
* init_real_reloption
@@ -1626,6 +1705,19 @@ parse_one_reloption(relopt_value *option, char *text_str, int text_len,
option->gen->name, value)));
}
break;
+ case RELOPT_TYPE_TERNARY:
+ {
+ bool b;
+
+ parsed = parse_bool(value, &b);
+ option->values.ternary_val = b ? TERNARY_TRUE : TERNARY_FALSE;
+ if (validate && !parsed)
+ ereport(ERROR,
+ (errcode(ERRCODE_INVALID_PARAMETER_VALUE),
+ errmsg("invalid value for ternary option \"%s\": %s",
+ option->gen->name, value)));
+ }
+ break;
case RELOPT_TYPE_INT:
{
relopt_int *optint = (relopt_int *) option->gen;
@@ -1789,17 +1881,6 @@ fillRelOptions(void *rdopts, Size basesize,
char *itempos = ((char *) rdopts) + elems[j].offset;
char *string_val;
- /*
- * If isset_offset is provided, store whether the reloption is
- * set there.
- */
- if (elems[j].isset_offset > 0)
- {
- char *setpos = ((char *) rdopts) + elems[j].isset_offset;
-
- *(bool *) setpos = options[i].isset;
- }
-
switch (options[i].gen->type)
{
case RELOPT_TYPE_BOOL:
@@ -1807,6 +1888,11 @@ fillRelOptions(void *rdopts, Size basesize,
options[i].values.bool_val :
((relopt_bool *) options[i].gen)->default_val;
break;
+ case RELOPT_TYPE_TERNARY:
+ *(pg_ternary *) itempos = options[i].isset ?
+ options[i].values.ternary_val :
+ ((relopt_ternary *) options[i].gen)->default_val;
+ break;
case RELOPT_TYPE_INT:
*(int *) itempos = options[i].isset ?
options[i].values.int_val :
@@ -1923,8 +2009,8 @@ default_reloptions(Datum reloptions, bool validate, relopt_kind kind)
offsetof(StdRdOptions, parallel_workers)},
{"vacuum_index_cleanup", RELOPT_TYPE_ENUM,
offsetof(StdRdOptions, vacuum_index_cleanup)},
- {"vacuum_truncate", RELOPT_TYPE_BOOL,
- offsetof(StdRdOptions, vacuum_truncate), offsetof(StdRdOptions, vacuum_truncate_set)},
+ {"vacuum_truncate", RELOPT_TYPE_TERNARY,
+ offsetof(StdRdOptions, vacuum_truncate)},
{"vacuum_max_eager_freeze_failure_rate", RELOPT_TYPE_REAL,
offsetof(StdRdOptions, vacuum_max_eager_freeze_failure_rate)}
};
@@ -2004,7 +2090,6 @@ build_local_reloptions(local_relopts *relopts, Datum options, bool validate)
elems[i].optname = opt->option->name;
elems[i].opttype = opt->option->type;
elems[i].offset = opt->offset;
- elems[i].isset_offset = 0; /* not supported for local relopts yet */
i++;
}
diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c
index aa4fbec143f..696eab9bd97 100644
--- a/src/backend/commands/vacuum.c
+++ b/src/backend/commands/vacuum.c
@@ -2224,9 +2224,9 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams params,
{
StdRdOptions *opts = (StdRdOptions *) rel->rd_options;
- if (opts && opts->vacuum_truncate_set)
+ if (opts && opts->vacuum_truncate != TERNARY_UNSET)
{
- if (opts->vacuum_truncate)
+ if (opts->vacuum_truncate == TERNARY_TRUE)
params.truncate = VACOPTVALUE_ENABLED;
else
params.truncate = VACOPTVALUE_DISABLED;
diff --git a/src/include/access/reloptions.h b/src/include/access/reloptions.h
index 2f08e1b0cf0..dfbef2babf2 100644
--- a/src/include/access/reloptions.h
+++ b/src/include/access/reloptions.h
@@ -29,6 +29,7 @@
typedef enum relopt_type
{
RELOPT_TYPE_BOOL,
+ RELOPT_TYPE_TERNARY, /* on, off, unset */
RELOPT_TYPE_INT,
RELOPT_TYPE_REAL,
RELOPT_TYPE_ENUM,
@@ -80,6 +81,7 @@ typedef struct relopt_value
union
{
bool bool_val;
+ pg_ternary ternary_val;
int int_val;
double real_val;
int enum_val;
@@ -94,6 +96,12 @@ typedef struct relopt_bool
bool default_val;
} relopt_bool;
+typedef struct relopt_ternary
+{
+ relopt_gen gen;
+ int default_val;
+} relopt_ternary;
+
typedef struct relopt_int
{
relopt_gen gen;
@@ -152,19 +160,6 @@ typedef struct
const char *optname; /* option's name */
relopt_type opttype; /* option's datatype */
int offset; /* offset of field in result struct */
-
- /*
- * isset_offset is an optional offset of a field in the result struct that
- * stores whether the option is explicitly set for the relation or if it
- * just picked up the default value. In most cases, this can be
- * accomplished by giving the reloption a special out-of-range default
- * value (e.g., some integer reloptions use -2), but this isn't always
- * possible. For example, a Boolean reloption cannot be given an
- * out-of-range default, so we need another way to discover the source of
- * its value. This offset is only used if given a value greater than
- * zero.
- */
- int isset_offset;
} relopt_parse_elt;
/* Local reloption definition */
@@ -195,6 +190,9 @@ typedef struct local_relopts
extern relopt_kind add_reloption_kind(void);
extern void add_bool_reloption(bits32 kinds, const char *name, const char *desc,
bool default_val, LOCKMODE lockmode);
+extern void add_ternary_reloption(bits32 kinds, const char *name,
+ const char *desc, pg_ternary default_val,
+ LOCKMODE lockmode);
extern void add_int_reloption(bits32 kinds, const char *name, const char *desc,
int default_val, int min_val, int max_val,
LOCKMODE lockmode);
@@ -214,6 +212,9 @@ extern void register_reloptions_validator(local_relopts *relopts,
extern void add_local_bool_reloption(local_relopts *relopts, const char *name,
const char *desc, bool default_val,
int offset);
+extern void add_local_ternary_reloption(local_relopts *relopts,
+ const char *name, const char *desc,
+ pg_ternary default_val, int offset);
extern void add_local_int_reloption(local_relopts *relopts, const char *name,
const char *desc, int default_val,
int min_val, int max_val, int offset);
diff --git a/src/include/postgres.h b/src/include/postgres.h
index 7d93fbce709..47c88780776 100644
--- a/src/include/postgres.h
+++ b/src/include/postgres.h
@@ -543,6 +543,21 @@ Float8GetDatum(float8 X)
* ----------------------------------------------------------------
*/
+/*
+ * pg_ternary
+ * Boolean value with an extra "unset" value
+ *
+ * This enum can be used for values that want to distinguish between true,
+ * false, and unset.
+*/
+
+typedef enum pg_ternary
+{
+ TERNARY_FALSE = 0,
+ TERNARY_TRUE = 1,
+ TERNARY_UNSET = -1
+} pg_ternary;
+
/*
* NON_EXEC_STATIC: It's sometimes useful to define a variable or function
* that is normally static but extern when using EXEC_BACKEND (see
diff --git a/src/include/utils/rel.h b/src/include/utils/rel.h
index d03ab247788..5feb18a5373 100644
--- a/src/include/utils/rel.h
+++ b/src/include/utils/rel.h
@@ -347,8 +347,7 @@ typedef struct StdRdOptions
bool user_catalog_table; /* use as an additional catalog relation */
int parallel_workers; /* max number of parallel workers */
StdRdOptIndexCleanup vacuum_index_cleanup; /* controls index vacuuming */
- bool vacuum_truncate; /* enables vacuum to truncate a relation */
- bool vacuum_truncate_set; /* whether vacuum_truncate is set */
+ pg_ternary vacuum_truncate; /* enables vacuum to truncate a relation */
/*
* Fraction of pages in a relation that vacuum can eagerly scan and fail
diff --git a/src/test/regress/expected/reloptions.out b/src/test/regress/expected/reloptions.out
index 9de19b4e3f1..1c99f79ab01 100644
--- a/src/test/regress/expected/reloptions.out
+++ b/src/test/regress/expected/reloptions.out
@@ -98,6 +98,42 @@ SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass;
{fillfactor=13,autovacuum_enabled=false}
(1 row)
+-- Tests for future (FIXME) ternary options
+-- behave as boolean option: accept unassigned name and truncated value
+DROP TABLE reloptions_test;
+CREATE TABLE reloptions_test(i INT) WITH (vacuum_truncate);
+SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass;
+ reloptions
+------------------------
+ {vacuum_truncate=true}
+(1 row)
+
+DROP TABLE reloptions_test;
+CREATE TABLE reloptions_test(i INT) WITH (vacuum_truncate=FaLS);
+SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass;
+ reloptions
+------------------------
+ {vacuum_truncate=fals}
+(1 row)
+
+-- preferred "true" alias is used when storing
+DROP TABLE reloptions_test;
+CREATE TABLE reloptions_test(i INT) WITH (vacuum_index_cleanup=on);
+SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass;
+ reloptions
+---------------------------
+ {vacuum_index_cleanup=on}
+(1 row)
+
+-- custom "third" value is available
+DROP TABLE reloptions_test;
+CREATE TABLE reloptions_test(i INT) WITH (vacuum_index_cleanup=auto);
+SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass;
+ reloptions
+-----------------------------
+ {vacuum_index_cleanup=auto}
+(1 row)
+
-- Test vacuum_truncate option
DROP TABLE reloptions_test;
CREATE TEMP TABLE reloptions_test(i INT NOT NULL, j text)
diff --git a/src/test/regress/sql/reloptions.sql b/src/test/regress/sql/reloptions.sql
index 24fbe0b478d..f5980dafcbc 100644
--- a/src/test/regress/sql/reloptions.sql
+++ b/src/test/regress/sql/reloptions.sql
@@ -59,6 +59,27 @@ UPDATE pg_class
ALTER TABLE reloptions_test RESET (illegal_option);
SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass;
+-- Tests for future (FIXME) ternary options
+
+-- behave as boolean option: accept unassigned name and truncated value
+DROP TABLE reloptions_test;
+CREATE TABLE reloptions_test(i INT) WITH (vacuum_truncate);
+SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass;
+
+DROP TABLE reloptions_test;
+CREATE TABLE reloptions_test(i INT) WITH (vacuum_truncate=FaLS);
+SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass;
+
+-- preferred "true" alias is used when storing
+DROP TABLE reloptions_test;
+CREATE TABLE reloptions_test(i INT) WITH (vacuum_index_cleanup=on);
+SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass;
+
+-- custom "third" value is available
+DROP TABLE reloptions_test;
+CREATE TABLE reloptions_test(i INT) WITH (vacuum_index_cleanup=auto);
+SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass;
+
-- Test vacuum_truncate option
DROP TABLE reloptions_test;
diff --git a/src/tools/pgindent/typedefs.list b/src/tools/pgindent/typedefs.list
index 3f3a888fd0e..1c8610fd46c 100644
--- a/src/tools/pgindent/typedefs.list
+++ b/src/tools/pgindent/typedefs.list
@@ -3952,6 +3952,7 @@ pg_sha512_ctx
pg_snapshot
pg_special_case
pg_stack_base_t
+pg_ternary
pg_time_t
pg_time_usec_t
pg_tz
@@ -4079,6 +4080,7 @@ relopt_kind
relopt_parse_elt
relopt_real
relopt_string
+relopt_ternary
relopt_type
relopt_value
relopts_validator
--
2.47.3
--c36t3fndxhkzbl5z--
^ permalink raw reply [nested|flat] 868+ messages in thread
* [PATCH v3] Introduce ternary reloptions
@ 2026-01-16 15:04 Álvaro Herrera <[email protected]>
0 siblings, 0 replies; 868+ messages in thread
From: Álvaro Herrera @ 2026-01-16 15:04 UTC (permalink / raw)
Introduce ternary reloption as a replacement for current
`vacuum_truncate` implementation. Remove the `vacuum_truncate_set`
separate flag and use TERNARY_UNSET instead.
This could also be used for other options such as `vacuum_index_cleanup`
and `buffering`, but lets get the scaffolding in first.
Discussion: https://postgr.es/m/3474141.usfYGdeWWP@thinkpad-pgpro
---
src/backend/access/common/reloptions.c | 137 ++++++++++++++++++-----
src/backend/commands/vacuum.c | 4 +-
src/include/access/reloptions.h | 27 ++---
src/include/postgres.h | 15 +++
src/include/utils/rel.h | 3 +-
src/test/regress/expected/reloptions.out | 36 ++++++
src/test/regress/sql/reloptions.sql | 21 ++++
src/tools/pgindent/typedefs.list | 2 +
8 files changed, 202 insertions(+), 43 deletions(-)
diff --git a/src/backend/access/common/reloptions.c b/src/backend/access/common/reloptions.c
index 0b83f98ed5f..6f1f577581a 100644
--- a/src/backend/access/common/reloptions.c
+++ b/src/backend/access/common/reloptions.c
@@ -40,9 +40,9 @@
*
* To add an option:
*
- * (i) decide on a type (bool, integer, real, enum, string), name, default
- * value, upper and lower bounds (if applicable); for strings, consider a
- * validation routine.
+ * (i) decide on a type (bool, ternary, integer, real, enum, string), name,
+ * default value, upper and lower bounds (if applicable); for strings,
+ * consider a validation routine.
* (ii) add a record below (or use add_<type>_reloption).
* (iii) add it to the appropriate options struct (perhaps StdRdOptions)
* (iv) add it to the appropriate handling routine (perhaps
@@ -147,15 +147,6 @@ static relopt_bool boolRelOpts[] =
},
false
},
- {
- {
- "vacuum_truncate",
- "Enables vacuum to truncate empty pages at the end of this table",
- RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
- ShareUpdateExclusiveLock
- },
- true
- },
{
{
"deduplicate_items",
@@ -170,6 +161,25 @@ static relopt_bool boolRelOpts[] =
{{NULL}}
};
+static relopt_ternary ternaryRelOpts[] =
+{
+ {
+ {
+ "vacuum_truncate",
+ "Enables vacuum to truncate empty pages at the end of this table",
+ RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
+ ShareUpdateExclusiveLock
+ },
+ TERNARY_UNSET
+ },
+ /* list terminator */
+ {
+ {
+ NULL
+ }
+ }
+};
+
static relopt_int intRelOpts[] =
{
{
@@ -609,6 +619,13 @@ initialize_reloptions(void)
boolRelOpts[i].gen.lockmode));
j++;
}
+ for (i = 0; ternaryRelOpts[i].gen.name; i++)
+ {
+ Assert(DoLockModesConflict(ternaryRelOpts[i].gen.lockmode,
+ ternaryRelOpts[i].gen.lockmode));
+ j++;
+ }
+
for (i = 0; intRelOpts[i].gen.name; i++)
{
Assert(DoLockModesConflict(intRelOpts[i].gen.lockmode,
@@ -649,6 +666,14 @@ initialize_reloptions(void)
j++;
}
+ for (i = 0; ternaryRelOpts[i].gen.name; i++)
+ {
+ relOpts[j] = &ternaryRelOpts[i].gen;
+ relOpts[j]->type = RELOPT_TYPE_TERNARY;
+ relOpts[j]->namelen = strlen(relOpts[j]->name);
+ j++;
+ }
+
for (i = 0; intRelOpts[i].gen.name; i++)
{
relOpts[j] = &intRelOpts[i].gen;
@@ -809,6 +834,9 @@ allocate_reloption(bits32 kinds, int type, const char *name, const char *desc,
case RELOPT_TYPE_BOOL:
size = sizeof(relopt_bool);
break;
+ case RELOPT_TYPE_TERNARY:
+ size = sizeof(relopt_ternary);
+ break;
case RELOPT_TYPE_INT:
size = sizeof(relopt_int);
break;
@@ -892,6 +920,57 @@ add_local_bool_reloption(local_relopts *relopts, const char *name,
add_local_reloption(relopts, (relopt_gen *) newoption, offset);
}
+/*
+ * init_ternary_reloption
+ * Allocate and initialize a new ternary reloption
+ */
+static relopt_ternary *
+init_ternary_reloption(bits32 kinds, const char *name, const char *desc,
+ pg_ternary default_val, LOCKMODE lockmode)
+{
+ relopt_ternary *newoption;
+
+ newoption = (relopt_ternary *)
+ allocate_reloption(kinds, RELOPT_TYPE_TERNARY, name, desc, lockmode);
+ newoption->default_val = default_val;
+
+ return newoption;
+}
+
+/*
+ * add_ternary_reloption
+ * Add a new ternary reloption
+ */
+void
+add_ternary_reloption(bits32 kinds, const char *name, const char *desc,
+ pg_ternary default_val, LOCKMODE lockmode)
+{
+ relopt_ternary *newoption;
+
+ newoption =
+ init_ternary_reloption(kinds, name, desc, default_val, lockmode);
+
+ add_reloption((relopt_gen *) newoption);
+}
+
+/*
+ * add_local_ternary_reloption
+ * Add a new ternary local reloption
+ *
+ * 'offset' is offset of ternary-typed field.
+ */
+void
+add_local_ternary_reloption(local_relopts *relopts, const char *name,
+ const char *desc, pg_ternary default_val,
+ int offset)
+{
+ relopt_ternary *newoption;
+
+ newoption =
+ init_ternary_reloption(RELOPT_KIND_LOCAL, name, desc, default_val, 0);
+
+ add_local_reloption(relopts, (relopt_gen *) newoption, offset);
+}
/*
* init_real_reloption
@@ -1626,6 +1705,19 @@ parse_one_reloption(relopt_value *option, char *text_str, int text_len,
option->gen->name, value)));
}
break;
+ case RELOPT_TYPE_TERNARY:
+ {
+ bool b;
+
+ parsed = parse_bool(value, &b);
+ option->values.ternary_val = b ? TERNARY_TRUE : TERNARY_FALSE;
+ if (validate && !parsed)
+ ereport(ERROR,
+ (errcode(ERRCODE_INVALID_PARAMETER_VALUE),
+ errmsg("invalid value for ternary option \"%s\": %s",
+ option->gen->name, value)));
+ }
+ break;
case RELOPT_TYPE_INT:
{
relopt_int *optint = (relopt_int *) option->gen;
@@ -1789,17 +1881,6 @@ fillRelOptions(void *rdopts, Size basesize,
char *itempos = ((char *) rdopts) + elems[j].offset;
char *string_val;
- /*
- * If isset_offset is provided, store whether the reloption is
- * set there.
- */
- if (elems[j].isset_offset > 0)
- {
- char *setpos = ((char *) rdopts) + elems[j].isset_offset;
-
- *(bool *) setpos = options[i].isset;
- }
-
switch (options[i].gen->type)
{
case RELOPT_TYPE_BOOL:
@@ -1807,6 +1888,11 @@ fillRelOptions(void *rdopts, Size basesize,
options[i].values.bool_val :
((relopt_bool *) options[i].gen)->default_val;
break;
+ case RELOPT_TYPE_TERNARY:
+ *(pg_ternary *) itempos = options[i].isset ?
+ options[i].values.ternary_val :
+ ((relopt_ternary *) options[i].gen)->default_val;
+ break;
case RELOPT_TYPE_INT:
*(int *) itempos = options[i].isset ?
options[i].values.int_val :
@@ -1923,8 +2009,8 @@ default_reloptions(Datum reloptions, bool validate, relopt_kind kind)
offsetof(StdRdOptions, parallel_workers)},
{"vacuum_index_cleanup", RELOPT_TYPE_ENUM,
offsetof(StdRdOptions, vacuum_index_cleanup)},
- {"vacuum_truncate", RELOPT_TYPE_BOOL,
- offsetof(StdRdOptions, vacuum_truncate), offsetof(StdRdOptions, vacuum_truncate_set)},
+ {"vacuum_truncate", RELOPT_TYPE_TERNARY,
+ offsetof(StdRdOptions, vacuum_truncate)},
{"vacuum_max_eager_freeze_failure_rate", RELOPT_TYPE_REAL,
offsetof(StdRdOptions, vacuum_max_eager_freeze_failure_rate)}
};
@@ -2004,7 +2090,6 @@ build_local_reloptions(local_relopts *relopts, Datum options, bool validate)
elems[i].optname = opt->option->name;
elems[i].opttype = opt->option->type;
elems[i].offset = opt->offset;
- elems[i].isset_offset = 0; /* not supported for local relopts yet */
i++;
}
diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c
index aa4fbec143f..696eab9bd97 100644
--- a/src/backend/commands/vacuum.c
+++ b/src/backend/commands/vacuum.c
@@ -2224,9 +2224,9 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams params,
{
StdRdOptions *opts = (StdRdOptions *) rel->rd_options;
- if (opts && opts->vacuum_truncate_set)
+ if (opts && opts->vacuum_truncate != TERNARY_UNSET)
{
- if (opts->vacuum_truncate)
+ if (opts->vacuum_truncate == TERNARY_TRUE)
params.truncate = VACOPTVALUE_ENABLED;
else
params.truncate = VACOPTVALUE_DISABLED;
diff --git a/src/include/access/reloptions.h b/src/include/access/reloptions.h
index 2f08e1b0cf0..dfbef2babf2 100644
--- a/src/include/access/reloptions.h
+++ b/src/include/access/reloptions.h
@@ -29,6 +29,7 @@
typedef enum relopt_type
{
RELOPT_TYPE_BOOL,
+ RELOPT_TYPE_TERNARY, /* on, off, unset */
RELOPT_TYPE_INT,
RELOPT_TYPE_REAL,
RELOPT_TYPE_ENUM,
@@ -80,6 +81,7 @@ typedef struct relopt_value
union
{
bool bool_val;
+ pg_ternary ternary_val;
int int_val;
double real_val;
int enum_val;
@@ -94,6 +96,12 @@ typedef struct relopt_bool
bool default_val;
} relopt_bool;
+typedef struct relopt_ternary
+{
+ relopt_gen gen;
+ int default_val;
+} relopt_ternary;
+
typedef struct relopt_int
{
relopt_gen gen;
@@ -152,19 +160,6 @@ typedef struct
const char *optname; /* option's name */
relopt_type opttype; /* option's datatype */
int offset; /* offset of field in result struct */
-
- /*
- * isset_offset is an optional offset of a field in the result struct that
- * stores whether the option is explicitly set for the relation or if it
- * just picked up the default value. In most cases, this can be
- * accomplished by giving the reloption a special out-of-range default
- * value (e.g., some integer reloptions use -2), but this isn't always
- * possible. For example, a Boolean reloption cannot be given an
- * out-of-range default, so we need another way to discover the source of
- * its value. This offset is only used if given a value greater than
- * zero.
- */
- int isset_offset;
} relopt_parse_elt;
/* Local reloption definition */
@@ -195,6 +190,9 @@ typedef struct local_relopts
extern relopt_kind add_reloption_kind(void);
extern void add_bool_reloption(bits32 kinds, const char *name, const char *desc,
bool default_val, LOCKMODE lockmode);
+extern void add_ternary_reloption(bits32 kinds, const char *name,
+ const char *desc, pg_ternary default_val,
+ LOCKMODE lockmode);
extern void add_int_reloption(bits32 kinds, const char *name, const char *desc,
int default_val, int min_val, int max_val,
LOCKMODE lockmode);
@@ -214,6 +212,9 @@ extern void register_reloptions_validator(local_relopts *relopts,
extern void add_local_bool_reloption(local_relopts *relopts, const char *name,
const char *desc, bool default_val,
int offset);
+extern void add_local_ternary_reloption(local_relopts *relopts,
+ const char *name, const char *desc,
+ pg_ternary default_val, int offset);
extern void add_local_int_reloption(local_relopts *relopts, const char *name,
const char *desc, int default_val,
int min_val, int max_val, int offset);
diff --git a/src/include/postgres.h b/src/include/postgres.h
index 7d93fbce709..47c88780776 100644
--- a/src/include/postgres.h
+++ b/src/include/postgres.h
@@ -543,6 +543,21 @@ Float8GetDatum(float8 X)
* ----------------------------------------------------------------
*/
+/*
+ * pg_ternary
+ * Boolean value with an extra "unset" value
+ *
+ * This enum can be used for values that want to distinguish between true,
+ * false, and unset.
+*/
+
+typedef enum pg_ternary
+{
+ TERNARY_FALSE = 0,
+ TERNARY_TRUE = 1,
+ TERNARY_UNSET = -1
+} pg_ternary;
+
/*
* NON_EXEC_STATIC: It's sometimes useful to define a variable or function
* that is normally static but extern when using EXEC_BACKEND (see
diff --git a/src/include/utils/rel.h b/src/include/utils/rel.h
index d03ab247788..5feb18a5373 100644
--- a/src/include/utils/rel.h
+++ b/src/include/utils/rel.h
@@ -347,8 +347,7 @@ typedef struct StdRdOptions
bool user_catalog_table; /* use as an additional catalog relation */
int parallel_workers; /* max number of parallel workers */
StdRdOptIndexCleanup vacuum_index_cleanup; /* controls index vacuuming */
- bool vacuum_truncate; /* enables vacuum to truncate a relation */
- bool vacuum_truncate_set; /* whether vacuum_truncate is set */
+ pg_ternary vacuum_truncate; /* enables vacuum to truncate a relation */
/*
* Fraction of pages in a relation that vacuum can eagerly scan and fail
diff --git a/src/test/regress/expected/reloptions.out b/src/test/regress/expected/reloptions.out
index 9de19b4e3f1..1c99f79ab01 100644
--- a/src/test/regress/expected/reloptions.out
+++ b/src/test/regress/expected/reloptions.out
@@ -98,6 +98,42 @@ SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass;
{fillfactor=13,autovacuum_enabled=false}
(1 row)
+-- Tests for future (FIXME) ternary options
+-- behave as boolean option: accept unassigned name and truncated value
+DROP TABLE reloptions_test;
+CREATE TABLE reloptions_test(i INT) WITH (vacuum_truncate);
+SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass;
+ reloptions
+------------------------
+ {vacuum_truncate=true}
+(1 row)
+
+DROP TABLE reloptions_test;
+CREATE TABLE reloptions_test(i INT) WITH (vacuum_truncate=FaLS);
+SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass;
+ reloptions
+------------------------
+ {vacuum_truncate=fals}
+(1 row)
+
+-- preferred "true" alias is used when storing
+DROP TABLE reloptions_test;
+CREATE TABLE reloptions_test(i INT) WITH (vacuum_index_cleanup=on);
+SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass;
+ reloptions
+---------------------------
+ {vacuum_index_cleanup=on}
+(1 row)
+
+-- custom "third" value is available
+DROP TABLE reloptions_test;
+CREATE TABLE reloptions_test(i INT) WITH (vacuum_index_cleanup=auto);
+SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass;
+ reloptions
+-----------------------------
+ {vacuum_index_cleanup=auto}
+(1 row)
+
-- Test vacuum_truncate option
DROP TABLE reloptions_test;
CREATE TEMP TABLE reloptions_test(i INT NOT NULL, j text)
diff --git a/src/test/regress/sql/reloptions.sql b/src/test/regress/sql/reloptions.sql
index 24fbe0b478d..f5980dafcbc 100644
--- a/src/test/regress/sql/reloptions.sql
+++ b/src/test/regress/sql/reloptions.sql
@@ -59,6 +59,27 @@ UPDATE pg_class
ALTER TABLE reloptions_test RESET (illegal_option);
SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass;
+-- Tests for future (FIXME) ternary options
+
+-- behave as boolean option: accept unassigned name and truncated value
+DROP TABLE reloptions_test;
+CREATE TABLE reloptions_test(i INT) WITH (vacuum_truncate);
+SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass;
+
+DROP TABLE reloptions_test;
+CREATE TABLE reloptions_test(i INT) WITH (vacuum_truncate=FaLS);
+SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass;
+
+-- preferred "true" alias is used when storing
+DROP TABLE reloptions_test;
+CREATE TABLE reloptions_test(i INT) WITH (vacuum_index_cleanup=on);
+SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass;
+
+-- custom "third" value is available
+DROP TABLE reloptions_test;
+CREATE TABLE reloptions_test(i INT) WITH (vacuum_index_cleanup=auto);
+SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass;
+
-- Test vacuum_truncate option
DROP TABLE reloptions_test;
diff --git a/src/tools/pgindent/typedefs.list b/src/tools/pgindent/typedefs.list
index 3f3a888fd0e..1c8610fd46c 100644
--- a/src/tools/pgindent/typedefs.list
+++ b/src/tools/pgindent/typedefs.list
@@ -3952,6 +3952,7 @@ pg_sha512_ctx
pg_snapshot
pg_special_case
pg_stack_base_t
+pg_ternary
pg_time_t
pg_time_usec_t
pg_tz
@@ -4079,6 +4080,7 @@ relopt_kind
relopt_parse_elt
relopt_real
relopt_string
+relopt_ternary
relopt_type
relopt_value
relopts_validator
--
2.47.3
--c36t3fndxhkzbl5z--
^ permalink raw reply [nested|flat] 868+ messages in thread
* [PATCH v3] Introduce ternary reloptions
@ 2026-01-16 15:04 Álvaro Herrera <[email protected]>
0 siblings, 0 replies; 868+ messages in thread
From: Álvaro Herrera @ 2026-01-16 15:04 UTC (permalink / raw)
Introduce ternary reloption as a replacement for current
`vacuum_truncate` implementation. Remove the `vacuum_truncate_set`
separate flag and use TERNARY_UNSET instead.
This could also be used for other options such as `vacuum_index_cleanup`
and `buffering`, but lets get the scaffolding in first.
Discussion: https://postgr.es/m/3474141.usfYGdeWWP@thinkpad-pgpro
---
src/backend/access/common/reloptions.c | 137 ++++++++++++++++++-----
src/backend/commands/vacuum.c | 4 +-
src/include/access/reloptions.h | 27 ++---
src/include/postgres.h | 15 +++
src/include/utils/rel.h | 3 +-
src/test/regress/expected/reloptions.out | 36 ++++++
src/test/regress/sql/reloptions.sql | 21 ++++
src/tools/pgindent/typedefs.list | 2 +
8 files changed, 202 insertions(+), 43 deletions(-)
diff --git a/src/backend/access/common/reloptions.c b/src/backend/access/common/reloptions.c
index 0b83f98ed5f..6f1f577581a 100644
--- a/src/backend/access/common/reloptions.c
+++ b/src/backend/access/common/reloptions.c
@@ -40,9 +40,9 @@
*
* To add an option:
*
- * (i) decide on a type (bool, integer, real, enum, string), name, default
- * value, upper and lower bounds (if applicable); for strings, consider a
- * validation routine.
+ * (i) decide on a type (bool, ternary, integer, real, enum, string), name,
+ * default value, upper and lower bounds (if applicable); for strings,
+ * consider a validation routine.
* (ii) add a record below (or use add_<type>_reloption).
* (iii) add it to the appropriate options struct (perhaps StdRdOptions)
* (iv) add it to the appropriate handling routine (perhaps
@@ -147,15 +147,6 @@ static relopt_bool boolRelOpts[] =
},
false
},
- {
- {
- "vacuum_truncate",
- "Enables vacuum to truncate empty pages at the end of this table",
- RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
- ShareUpdateExclusiveLock
- },
- true
- },
{
{
"deduplicate_items",
@@ -170,6 +161,25 @@ static relopt_bool boolRelOpts[] =
{{NULL}}
};
+static relopt_ternary ternaryRelOpts[] =
+{
+ {
+ {
+ "vacuum_truncate",
+ "Enables vacuum to truncate empty pages at the end of this table",
+ RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
+ ShareUpdateExclusiveLock
+ },
+ TERNARY_UNSET
+ },
+ /* list terminator */
+ {
+ {
+ NULL
+ }
+ }
+};
+
static relopt_int intRelOpts[] =
{
{
@@ -609,6 +619,13 @@ initialize_reloptions(void)
boolRelOpts[i].gen.lockmode));
j++;
}
+ for (i = 0; ternaryRelOpts[i].gen.name; i++)
+ {
+ Assert(DoLockModesConflict(ternaryRelOpts[i].gen.lockmode,
+ ternaryRelOpts[i].gen.lockmode));
+ j++;
+ }
+
for (i = 0; intRelOpts[i].gen.name; i++)
{
Assert(DoLockModesConflict(intRelOpts[i].gen.lockmode,
@@ -649,6 +666,14 @@ initialize_reloptions(void)
j++;
}
+ for (i = 0; ternaryRelOpts[i].gen.name; i++)
+ {
+ relOpts[j] = &ternaryRelOpts[i].gen;
+ relOpts[j]->type = RELOPT_TYPE_TERNARY;
+ relOpts[j]->namelen = strlen(relOpts[j]->name);
+ j++;
+ }
+
for (i = 0; intRelOpts[i].gen.name; i++)
{
relOpts[j] = &intRelOpts[i].gen;
@@ -809,6 +834,9 @@ allocate_reloption(bits32 kinds, int type, const char *name, const char *desc,
case RELOPT_TYPE_BOOL:
size = sizeof(relopt_bool);
break;
+ case RELOPT_TYPE_TERNARY:
+ size = sizeof(relopt_ternary);
+ break;
case RELOPT_TYPE_INT:
size = sizeof(relopt_int);
break;
@@ -892,6 +920,57 @@ add_local_bool_reloption(local_relopts *relopts, const char *name,
add_local_reloption(relopts, (relopt_gen *) newoption, offset);
}
+/*
+ * init_ternary_reloption
+ * Allocate and initialize a new ternary reloption
+ */
+static relopt_ternary *
+init_ternary_reloption(bits32 kinds, const char *name, const char *desc,
+ pg_ternary default_val, LOCKMODE lockmode)
+{
+ relopt_ternary *newoption;
+
+ newoption = (relopt_ternary *)
+ allocate_reloption(kinds, RELOPT_TYPE_TERNARY, name, desc, lockmode);
+ newoption->default_val = default_val;
+
+ return newoption;
+}
+
+/*
+ * add_ternary_reloption
+ * Add a new ternary reloption
+ */
+void
+add_ternary_reloption(bits32 kinds, const char *name, const char *desc,
+ pg_ternary default_val, LOCKMODE lockmode)
+{
+ relopt_ternary *newoption;
+
+ newoption =
+ init_ternary_reloption(kinds, name, desc, default_val, lockmode);
+
+ add_reloption((relopt_gen *) newoption);
+}
+
+/*
+ * add_local_ternary_reloption
+ * Add a new ternary local reloption
+ *
+ * 'offset' is offset of ternary-typed field.
+ */
+void
+add_local_ternary_reloption(local_relopts *relopts, const char *name,
+ const char *desc, pg_ternary default_val,
+ int offset)
+{
+ relopt_ternary *newoption;
+
+ newoption =
+ init_ternary_reloption(RELOPT_KIND_LOCAL, name, desc, default_val, 0);
+
+ add_local_reloption(relopts, (relopt_gen *) newoption, offset);
+}
/*
* init_real_reloption
@@ -1626,6 +1705,19 @@ parse_one_reloption(relopt_value *option, char *text_str, int text_len,
option->gen->name, value)));
}
break;
+ case RELOPT_TYPE_TERNARY:
+ {
+ bool b;
+
+ parsed = parse_bool(value, &b);
+ option->values.ternary_val = b ? TERNARY_TRUE : TERNARY_FALSE;
+ if (validate && !parsed)
+ ereport(ERROR,
+ (errcode(ERRCODE_INVALID_PARAMETER_VALUE),
+ errmsg("invalid value for ternary option \"%s\": %s",
+ option->gen->name, value)));
+ }
+ break;
case RELOPT_TYPE_INT:
{
relopt_int *optint = (relopt_int *) option->gen;
@@ -1789,17 +1881,6 @@ fillRelOptions(void *rdopts, Size basesize,
char *itempos = ((char *) rdopts) + elems[j].offset;
char *string_val;
- /*
- * If isset_offset is provided, store whether the reloption is
- * set there.
- */
- if (elems[j].isset_offset > 0)
- {
- char *setpos = ((char *) rdopts) + elems[j].isset_offset;
-
- *(bool *) setpos = options[i].isset;
- }
-
switch (options[i].gen->type)
{
case RELOPT_TYPE_BOOL:
@@ -1807,6 +1888,11 @@ fillRelOptions(void *rdopts, Size basesize,
options[i].values.bool_val :
((relopt_bool *) options[i].gen)->default_val;
break;
+ case RELOPT_TYPE_TERNARY:
+ *(pg_ternary *) itempos = options[i].isset ?
+ options[i].values.ternary_val :
+ ((relopt_ternary *) options[i].gen)->default_val;
+ break;
case RELOPT_TYPE_INT:
*(int *) itempos = options[i].isset ?
options[i].values.int_val :
@@ -1923,8 +2009,8 @@ default_reloptions(Datum reloptions, bool validate, relopt_kind kind)
offsetof(StdRdOptions, parallel_workers)},
{"vacuum_index_cleanup", RELOPT_TYPE_ENUM,
offsetof(StdRdOptions, vacuum_index_cleanup)},
- {"vacuum_truncate", RELOPT_TYPE_BOOL,
- offsetof(StdRdOptions, vacuum_truncate), offsetof(StdRdOptions, vacuum_truncate_set)},
+ {"vacuum_truncate", RELOPT_TYPE_TERNARY,
+ offsetof(StdRdOptions, vacuum_truncate)},
{"vacuum_max_eager_freeze_failure_rate", RELOPT_TYPE_REAL,
offsetof(StdRdOptions, vacuum_max_eager_freeze_failure_rate)}
};
@@ -2004,7 +2090,6 @@ build_local_reloptions(local_relopts *relopts, Datum options, bool validate)
elems[i].optname = opt->option->name;
elems[i].opttype = opt->option->type;
elems[i].offset = opt->offset;
- elems[i].isset_offset = 0; /* not supported for local relopts yet */
i++;
}
diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c
index aa4fbec143f..696eab9bd97 100644
--- a/src/backend/commands/vacuum.c
+++ b/src/backend/commands/vacuum.c
@@ -2224,9 +2224,9 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams params,
{
StdRdOptions *opts = (StdRdOptions *) rel->rd_options;
- if (opts && opts->vacuum_truncate_set)
+ if (opts && opts->vacuum_truncate != TERNARY_UNSET)
{
- if (opts->vacuum_truncate)
+ if (opts->vacuum_truncate == TERNARY_TRUE)
params.truncate = VACOPTVALUE_ENABLED;
else
params.truncate = VACOPTVALUE_DISABLED;
diff --git a/src/include/access/reloptions.h b/src/include/access/reloptions.h
index 2f08e1b0cf0..dfbef2babf2 100644
--- a/src/include/access/reloptions.h
+++ b/src/include/access/reloptions.h
@@ -29,6 +29,7 @@
typedef enum relopt_type
{
RELOPT_TYPE_BOOL,
+ RELOPT_TYPE_TERNARY, /* on, off, unset */
RELOPT_TYPE_INT,
RELOPT_TYPE_REAL,
RELOPT_TYPE_ENUM,
@@ -80,6 +81,7 @@ typedef struct relopt_value
union
{
bool bool_val;
+ pg_ternary ternary_val;
int int_val;
double real_val;
int enum_val;
@@ -94,6 +96,12 @@ typedef struct relopt_bool
bool default_val;
} relopt_bool;
+typedef struct relopt_ternary
+{
+ relopt_gen gen;
+ int default_val;
+} relopt_ternary;
+
typedef struct relopt_int
{
relopt_gen gen;
@@ -152,19 +160,6 @@ typedef struct
const char *optname; /* option's name */
relopt_type opttype; /* option's datatype */
int offset; /* offset of field in result struct */
-
- /*
- * isset_offset is an optional offset of a field in the result struct that
- * stores whether the option is explicitly set for the relation or if it
- * just picked up the default value. In most cases, this can be
- * accomplished by giving the reloption a special out-of-range default
- * value (e.g., some integer reloptions use -2), but this isn't always
- * possible. For example, a Boolean reloption cannot be given an
- * out-of-range default, so we need another way to discover the source of
- * its value. This offset is only used if given a value greater than
- * zero.
- */
- int isset_offset;
} relopt_parse_elt;
/* Local reloption definition */
@@ -195,6 +190,9 @@ typedef struct local_relopts
extern relopt_kind add_reloption_kind(void);
extern void add_bool_reloption(bits32 kinds, const char *name, const char *desc,
bool default_val, LOCKMODE lockmode);
+extern void add_ternary_reloption(bits32 kinds, const char *name,
+ const char *desc, pg_ternary default_val,
+ LOCKMODE lockmode);
extern void add_int_reloption(bits32 kinds, const char *name, const char *desc,
int default_val, int min_val, int max_val,
LOCKMODE lockmode);
@@ -214,6 +212,9 @@ extern void register_reloptions_validator(local_relopts *relopts,
extern void add_local_bool_reloption(local_relopts *relopts, const char *name,
const char *desc, bool default_val,
int offset);
+extern void add_local_ternary_reloption(local_relopts *relopts,
+ const char *name, const char *desc,
+ pg_ternary default_val, int offset);
extern void add_local_int_reloption(local_relopts *relopts, const char *name,
const char *desc, int default_val,
int min_val, int max_val, int offset);
diff --git a/src/include/postgres.h b/src/include/postgres.h
index 7d93fbce709..47c88780776 100644
--- a/src/include/postgres.h
+++ b/src/include/postgres.h
@@ -543,6 +543,21 @@ Float8GetDatum(float8 X)
* ----------------------------------------------------------------
*/
+/*
+ * pg_ternary
+ * Boolean value with an extra "unset" value
+ *
+ * This enum can be used for values that want to distinguish between true,
+ * false, and unset.
+*/
+
+typedef enum pg_ternary
+{
+ TERNARY_FALSE = 0,
+ TERNARY_TRUE = 1,
+ TERNARY_UNSET = -1
+} pg_ternary;
+
/*
* NON_EXEC_STATIC: It's sometimes useful to define a variable or function
* that is normally static but extern when using EXEC_BACKEND (see
diff --git a/src/include/utils/rel.h b/src/include/utils/rel.h
index d03ab247788..5feb18a5373 100644
--- a/src/include/utils/rel.h
+++ b/src/include/utils/rel.h
@@ -347,8 +347,7 @@ typedef struct StdRdOptions
bool user_catalog_table; /* use as an additional catalog relation */
int parallel_workers; /* max number of parallel workers */
StdRdOptIndexCleanup vacuum_index_cleanup; /* controls index vacuuming */
- bool vacuum_truncate; /* enables vacuum to truncate a relation */
- bool vacuum_truncate_set; /* whether vacuum_truncate is set */
+ pg_ternary vacuum_truncate; /* enables vacuum to truncate a relation */
/*
* Fraction of pages in a relation that vacuum can eagerly scan and fail
diff --git a/src/test/regress/expected/reloptions.out b/src/test/regress/expected/reloptions.out
index 9de19b4e3f1..1c99f79ab01 100644
--- a/src/test/regress/expected/reloptions.out
+++ b/src/test/regress/expected/reloptions.out
@@ -98,6 +98,42 @@ SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass;
{fillfactor=13,autovacuum_enabled=false}
(1 row)
+-- Tests for future (FIXME) ternary options
+-- behave as boolean option: accept unassigned name and truncated value
+DROP TABLE reloptions_test;
+CREATE TABLE reloptions_test(i INT) WITH (vacuum_truncate);
+SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass;
+ reloptions
+------------------------
+ {vacuum_truncate=true}
+(1 row)
+
+DROP TABLE reloptions_test;
+CREATE TABLE reloptions_test(i INT) WITH (vacuum_truncate=FaLS);
+SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass;
+ reloptions
+------------------------
+ {vacuum_truncate=fals}
+(1 row)
+
+-- preferred "true" alias is used when storing
+DROP TABLE reloptions_test;
+CREATE TABLE reloptions_test(i INT) WITH (vacuum_index_cleanup=on);
+SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass;
+ reloptions
+---------------------------
+ {vacuum_index_cleanup=on}
+(1 row)
+
+-- custom "third" value is available
+DROP TABLE reloptions_test;
+CREATE TABLE reloptions_test(i INT) WITH (vacuum_index_cleanup=auto);
+SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass;
+ reloptions
+-----------------------------
+ {vacuum_index_cleanup=auto}
+(1 row)
+
-- Test vacuum_truncate option
DROP TABLE reloptions_test;
CREATE TEMP TABLE reloptions_test(i INT NOT NULL, j text)
diff --git a/src/test/regress/sql/reloptions.sql b/src/test/regress/sql/reloptions.sql
index 24fbe0b478d..f5980dafcbc 100644
--- a/src/test/regress/sql/reloptions.sql
+++ b/src/test/regress/sql/reloptions.sql
@@ -59,6 +59,27 @@ UPDATE pg_class
ALTER TABLE reloptions_test RESET (illegal_option);
SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass;
+-- Tests for future (FIXME) ternary options
+
+-- behave as boolean option: accept unassigned name and truncated value
+DROP TABLE reloptions_test;
+CREATE TABLE reloptions_test(i INT) WITH (vacuum_truncate);
+SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass;
+
+DROP TABLE reloptions_test;
+CREATE TABLE reloptions_test(i INT) WITH (vacuum_truncate=FaLS);
+SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass;
+
+-- preferred "true" alias is used when storing
+DROP TABLE reloptions_test;
+CREATE TABLE reloptions_test(i INT) WITH (vacuum_index_cleanup=on);
+SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass;
+
+-- custom "third" value is available
+DROP TABLE reloptions_test;
+CREATE TABLE reloptions_test(i INT) WITH (vacuum_index_cleanup=auto);
+SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass;
+
-- Test vacuum_truncate option
DROP TABLE reloptions_test;
diff --git a/src/tools/pgindent/typedefs.list b/src/tools/pgindent/typedefs.list
index 3f3a888fd0e..1c8610fd46c 100644
--- a/src/tools/pgindent/typedefs.list
+++ b/src/tools/pgindent/typedefs.list
@@ -3952,6 +3952,7 @@ pg_sha512_ctx
pg_snapshot
pg_special_case
pg_stack_base_t
+pg_ternary
pg_time_t
pg_time_usec_t
pg_tz
@@ -4079,6 +4080,7 @@ relopt_kind
relopt_parse_elt
relopt_real
relopt_string
+relopt_ternary
relopt_type
relopt_value
relopts_validator
--
2.47.3
--c36t3fndxhkzbl5z--
^ permalink raw reply [nested|flat] 868+ messages in thread
* [PATCH v3] Introduce ternary reloptions
@ 2026-01-16 15:04 Álvaro Herrera <[email protected]>
0 siblings, 0 replies; 868+ messages in thread
From: Álvaro Herrera @ 2026-01-16 15:04 UTC (permalink / raw)
Introduce ternary reloption as a replacement for current
`vacuum_truncate` implementation. Remove the `vacuum_truncate_set`
separate flag and use TERNARY_UNSET instead.
This could also be used for other options such as `vacuum_index_cleanup`
and `buffering`, but lets get the scaffolding in first.
Discussion: https://postgr.es/m/3474141.usfYGdeWWP@thinkpad-pgpro
---
src/backend/access/common/reloptions.c | 137 ++++++++++++++++++-----
src/backend/commands/vacuum.c | 4 +-
src/include/access/reloptions.h | 27 ++---
src/include/postgres.h | 15 +++
src/include/utils/rel.h | 3 +-
src/test/regress/expected/reloptions.out | 36 ++++++
src/test/regress/sql/reloptions.sql | 21 ++++
src/tools/pgindent/typedefs.list | 2 +
8 files changed, 202 insertions(+), 43 deletions(-)
diff --git a/src/backend/access/common/reloptions.c b/src/backend/access/common/reloptions.c
index 0b83f98ed5f..6f1f577581a 100644
--- a/src/backend/access/common/reloptions.c
+++ b/src/backend/access/common/reloptions.c
@@ -40,9 +40,9 @@
*
* To add an option:
*
- * (i) decide on a type (bool, integer, real, enum, string), name, default
- * value, upper and lower bounds (if applicable); for strings, consider a
- * validation routine.
+ * (i) decide on a type (bool, ternary, integer, real, enum, string), name,
+ * default value, upper and lower bounds (if applicable); for strings,
+ * consider a validation routine.
* (ii) add a record below (or use add_<type>_reloption).
* (iii) add it to the appropriate options struct (perhaps StdRdOptions)
* (iv) add it to the appropriate handling routine (perhaps
@@ -147,15 +147,6 @@ static relopt_bool boolRelOpts[] =
},
false
},
- {
- {
- "vacuum_truncate",
- "Enables vacuum to truncate empty pages at the end of this table",
- RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
- ShareUpdateExclusiveLock
- },
- true
- },
{
{
"deduplicate_items",
@@ -170,6 +161,25 @@ static relopt_bool boolRelOpts[] =
{{NULL}}
};
+static relopt_ternary ternaryRelOpts[] =
+{
+ {
+ {
+ "vacuum_truncate",
+ "Enables vacuum to truncate empty pages at the end of this table",
+ RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
+ ShareUpdateExclusiveLock
+ },
+ TERNARY_UNSET
+ },
+ /* list terminator */
+ {
+ {
+ NULL
+ }
+ }
+};
+
static relopt_int intRelOpts[] =
{
{
@@ -609,6 +619,13 @@ initialize_reloptions(void)
boolRelOpts[i].gen.lockmode));
j++;
}
+ for (i = 0; ternaryRelOpts[i].gen.name; i++)
+ {
+ Assert(DoLockModesConflict(ternaryRelOpts[i].gen.lockmode,
+ ternaryRelOpts[i].gen.lockmode));
+ j++;
+ }
+
for (i = 0; intRelOpts[i].gen.name; i++)
{
Assert(DoLockModesConflict(intRelOpts[i].gen.lockmode,
@@ -649,6 +666,14 @@ initialize_reloptions(void)
j++;
}
+ for (i = 0; ternaryRelOpts[i].gen.name; i++)
+ {
+ relOpts[j] = &ternaryRelOpts[i].gen;
+ relOpts[j]->type = RELOPT_TYPE_TERNARY;
+ relOpts[j]->namelen = strlen(relOpts[j]->name);
+ j++;
+ }
+
for (i = 0; intRelOpts[i].gen.name; i++)
{
relOpts[j] = &intRelOpts[i].gen;
@@ -809,6 +834,9 @@ allocate_reloption(bits32 kinds, int type, const char *name, const char *desc,
case RELOPT_TYPE_BOOL:
size = sizeof(relopt_bool);
break;
+ case RELOPT_TYPE_TERNARY:
+ size = sizeof(relopt_ternary);
+ break;
case RELOPT_TYPE_INT:
size = sizeof(relopt_int);
break;
@@ -892,6 +920,57 @@ add_local_bool_reloption(local_relopts *relopts, const char *name,
add_local_reloption(relopts, (relopt_gen *) newoption, offset);
}
+/*
+ * init_ternary_reloption
+ * Allocate and initialize a new ternary reloption
+ */
+static relopt_ternary *
+init_ternary_reloption(bits32 kinds, const char *name, const char *desc,
+ pg_ternary default_val, LOCKMODE lockmode)
+{
+ relopt_ternary *newoption;
+
+ newoption = (relopt_ternary *)
+ allocate_reloption(kinds, RELOPT_TYPE_TERNARY, name, desc, lockmode);
+ newoption->default_val = default_val;
+
+ return newoption;
+}
+
+/*
+ * add_ternary_reloption
+ * Add a new ternary reloption
+ */
+void
+add_ternary_reloption(bits32 kinds, const char *name, const char *desc,
+ pg_ternary default_val, LOCKMODE lockmode)
+{
+ relopt_ternary *newoption;
+
+ newoption =
+ init_ternary_reloption(kinds, name, desc, default_val, lockmode);
+
+ add_reloption((relopt_gen *) newoption);
+}
+
+/*
+ * add_local_ternary_reloption
+ * Add a new ternary local reloption
+ *
+ * 'offset' is offset of ternary-typed field.
+ */
+void
+add_local_ternary_reloption(local_relopts *relopts, const char *name,
+ const char *desc, pg_ternary default_val,
+ int offset)
+{
+ relopt_ternary *newoption;
+
+ newoption =
+ init_ternary_reloption(RELOPT_KIND_LOCAL, name, desc, default_val, 0);
+
+ add_local_reloption(relopts, (relopt_gen *) newoption, offset);
+}
/*
* init_real_reloption
@@ -1626,6 +1705,19 @@ parse_one_reloption(relopt_value *option, char *text_str, int text_len,
option->gen->name, value)));
}
break;
+ case RELOPT_TYPE_TERNARY:
+ {
+ bool b;
+
+ parsed = parse_bool(value, &b);
+ option->values.ternary_val = b ? TERNARY_TRUE : TERNARY_FALSE;
+ if (validate && !parsed)
+ ereport(ERROR,
+ (errcode(ERRCODE_INVALID_PARAMETER_VALUE),
+ errmsg("invalid value for ternary option \"%s\": %s",
+ option->gen->name, value)));
+ }
+ break;
case RELOPT_TYPE_INT:
{
relopt_int *optint = (relopt_int *) option->gen;
@@ -1789,17 +1881,6 @@ fillRelOptions(void *rdopts, Size basesize,
char *itempos = ((char *) rdopts) + elems[j].offset;
char *string_val;
- /*
- * If isset_offset is provided, store whether the reloption is
- * set there.
- */
- if (elems[j].isset_offset > 0)
- {
- char *setpos = ((char *) rdopts) + elems[j].isset_offset;
-
- *(bool *) setpos = options[i].isset;
- }
-
switch (options[i].gen->type)
{
case RELOPT_TYPE_BOOL:
@@ -1807,6 +1888,11 @@ fillRelOptions(void *rdopts, Size basesize,
options[i].values.bool_val :
((relopt_bool *) options[i].gen)->default_val;
break;
+ case RELOPT_TYPE_TERNARY:
+ *(pg_ternary *) itempos = options[i].isset ?
+ options[i].values.ternary_val :
+ ((relopt_ternary *) options[i].gen)->default_val;
+ break;
case RELOPT_TYPE_INT:
*(int *) itempos = options[i].isset ?
options[i].values.int_val :
@@ -1923,8 +2009,8 @@ default_reloptions(Datum reloptions, bool validate, relopt_kind kind)
offsetof(StdRdOptions, parallel_workers)},
{"vacuum_index_cleanup", RELOPT_TYPE_ENUM,
offsetof(StdRdOptions, vacuum_index_cleanup)},
- {"vacuum_truncate", RELOPT_TYPE_BOOL,
- offsetof(StdRdOptions, vacuum_truncate), offsetof(StdRdOptions, vacuum_truncate_set)},
+ {"vacuum_truncate", RELOPT_TYPE_TERNARY,
+ offsetof(StdRdOptions, vacuum_truncate)},
{"vacuum_max_eager_freeze_failure_rate", RELOPT_TYPE_REAL,
offsetof(StdRdOptions, vacuum_max_eager_freeze_failure_rate)}
};
@@ -2004,7 +2090,6 @@ build_local_reloptions(local_relopts *relopts, Datum options, bool validate)
elems[i].optname = opt->option->name;
elems[i].opttype = opt->option->type;
elems[i].offset = opt->offset;
- elems[i].isset_offset = 0; /* not supported for local relopts yet */
i++;
}
diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c
index aa4fbec143f..696eab9bd97 100644
--- a/src/backend/commands/vacuum.c
+++ b/src/backend/commands/vacuum.c
@@ -2224,9 +2224,9 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams params,
{
StdRdOptions *opts = (StdRdOptions *) rel->rd_options;
- if (opts && opts->vacuum_truncate_set)
+ if (opts && opts->vacuum_truncate != TERNARY_UNSET)
{
- if (opts->vacuum_truncate)
+ if (opts->vacuum_truncate == TERNARY_TRUE)
params.truncate = VACOPTVALUE_ENABLED;
else
params.truncate = VACOPTVALUE_DISABLED;
diff --git a/src/include/access/reloptions.h b/src/include/access/reloptions.h
index 2f08e1b0cf0..dfbef2babf2 100644
--- a/src/include/access/reloptions.h
+++ b/src/include/access/reloptions.h
@@ -29,6 +29,7 @@
typedef enum relopt_type
{
RELOPT_TYPE_BOOL,
+ RELOPT_TYPE_TERNARY, /* on, off, unset */
RELOPT_TYPE_INT,
RELOPT_TYPE_REAL,
RELOPT_TYPE_ENUM,
@@ -80,6 +81,7 @@ typedef struct relopt_value
union
{
bool bool_val;
+ pg_ternary ternary_val;
int int_val;
double real_val;
int enum_val;
@@ -94,6 +96,12 @@ typedef struct relopt_bool
bool default_val;
} relopt_bool;
+typedef struct relopt_ternary
+{
+ relopt_gen gen;
+ int default_val;
+} relopt_ternary;
+
typedef struct relopt_int
{
relopt_gen gen;
@@ -152,19 +160,6 @@ typedef struct
const char *optname; /* option's name */
relopt_type opttype; /* option's datatype */
int offset; /* offset of field in result struct */
-
- /*
- * isset_offset is an optional offset of a field in the result struct that
- * stores whether the option is explicitly set for the relation or if it
- * just picked up the default value. In most cases, this can be
- * accomplished by giving the reloption a special out-of-range default
- * value (e.g., some integer reloptions use -2), but this isn't always
- * possible. For example, a Boolean reloption cannot be given an
- * out-of-range default, so we need another way to discover the source of
- * its value. This offset is only used if given a value greater than
- * zero.
- */
- int isset_offset;
} relopt_parse_elt;
/* Local reloption definition */
@@ -195,6 +190,9 @@ typedef struct local_relopts
extern relopt_kind add_reloption_kind(void);
extern void add_bool_reloption(bits32 kinds, const char *name, const char *desc,
bool default_val, LOCKMODE lockmode);
+extern void add_ternary_reloption(bits32 kinds, const char *name,
+ const char *desc, pg_ternary default_val,
+ LOCKMODE lockmode);
extern void add_int_reloption(bits32 kinds, const char *name, const char *desc,
int default_val, int min_val, int max_val,
LOCKMODE lockmode);
@@ -214,6 +212,9 @@ extern void register_reloptions_validator(local_relopts *relopts,
extern void add_local_bool_reloption(local_relopts *relopts, const char *name,
const char *desc, bool default_val,
int offset);
+extern void add_local_ternary_reloption(local_relopts *relopts,
+ const char *name, const char *desc,
+ pg_ternary default_val, int offset);
extern void add_local_int_reloption(local_relopts *relopts, const char *name,
const char *desc, int default_val,
int min_val, int max_val, int offset);
diff --git a/src/include/postgres.h b/src/include/postgres.h
index 7d93fbce709..47c88780776 100644
--- a/src/include/postgres.h
+++ b/src/include/postgres.h
@@ -543,6 +543,21 @@ Float8GetDatum(float8 X)
* ----------------------------------------------------------------
*/
+/*
+ * pg_ternary
+ * Boolean value with an extra "unset" value
+ *
+ * This enum can be used for values that want to distinguish between true,
+ * false, and unset.
+*/
+
+typedef enum pg_ternary
+{
+ TERNARY_FALSE = 0,
+ TERNARY_TRUE = 1,
+ TERNARY_UNSET = -1
+} pg_ternary;
+
/*
* NON_EXEC_STATIC: It's sometimes useful to define a variable or function
* that is normally static but extern when using EXEC_BACKEND (see
diff --git a/src/include/utils/rel.h b/src/include/utils/rel.h
index d03ab247788..5feb18a5373 100644
--- a/src/include/utils/rel.h
+++ b/src/include/utils/rel.h
@@ -347,8 +347,7 @@ typedef struct StdRdOptions
bool user_catalog_table; /* use as an additional catalog relation */
int parallel_workers; /* max number of parallel workers */
StdRdOptIndexCleanup vacuum_index_cleanup; /* controls index vacuuming */
- bool vacuum_truncate; /* enables vacuum to truncate a relation */
- bool vacuum_truncate_set; /* whether vacuum_truncate is set */
+ pg_ternary vacuum_truncate; /* enables vacuum to truncate a relation */
/*
* Fraction of pages in a relation that vacuum can eagerly scan and fail
diff --git a/src/test/regress/expected/reloptions.out b/src/test/regress/expected/reloptions.out
index 9de19b4e3f1..1c99f79ab01 100644
--- a/src/test/regress/expected/reloptions.out
+++ b/src/test/regress/expected/reloptions.out
@@ -98,6 +98,42 @@ SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass;
{fillfactor=13,autovacuum_enabled=false}
(1 row)
+-- Tests for future (FIXME) ternary options
+-- behave as boolean option: accept unassigned name and truncated value
+DROP TABLE reloptions_test;
+CREATE TABLE reloptions_test(i INT) WITH (vacuum_truncate);
+SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass;
+ reloptions
+------------------------
+ {vacuum_truncate=true}
+(1 row)
+
+DROP TABLE reloptions_test;
+CREATE TABLE reloptions_test(i INT) WITH (vacuum_truncate=FaLS);
+SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass;
+ reloptions
+------------------------
+ {vacuum_truncate=fals}
+(1 row)
+
+-- preferred "true" alias is used when storing
+DROP TABLE reloptions_test;
+CREATE TABLE reloptions_test(i INT) WITH (vacuum_index_cleanup=on);
+SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass;
+ reloptions
+---------------------------
+ {vacuum_index_cleanup=on}
+(1 row)
+
+-- custom "third" value is available
+DROP TABLE reloptions_test;
+CREATE TABLE reloptions_test(i INT) WITH (vacuum_index_cleanup=auto);
+SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass;
+ reloptions
+-----------------------------
+ {vacuum_index_cleanup=auto}
+(1 row)
+
-- Test vacuum_truncate option
DROP TABLE reloptions_test;
CREATE TEMP TABLE reloptions_test(i INT NOT NULL, j text)
diff --git a/src/test/regress/sql/reloptions.sql b/src/test/regress/sql/reloptions.sql
index 24fbe0b478d..f5980dafcbc 100644
--- a/src/test/regress/sql/reloptions.sql
+++ b/src/test/regress/sql/reloptions.sql
@@ -59,6 +59,27 @@ UPDATE pg_class
ALTER TABLE reloptions_test RESET (illegal_option);
SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass;
+-- Tests for future (FIXME) ternary options
+
+-- behave as boolean option: accept unassigned name and truncated value
+DROP TABLE reloptions_test;
+CREATE TABLE reloptions_test(i INT) WITH (vacuum_truncate);
+SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass;
+
+DROP TABLE reloptions_test;
+CREATE TABLE reloptions_test(i INT) WITH (vacuum_truncate=FaLS);
+SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass;
+
+-- preferred "true" alias is used when storing
+DROP TABLE reloptions_test;
+CREATE TABLE reloptions_test(i INT) WITH (vacuum_index_cleanup=on);
+SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass;
+
+-- custom "third" value is available
+DROP TABLE reloptions_test;
+CREATE TABLE reloptions_test(i INT) WITH (vacuum_index_cleanup=auto);
+SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass;
+
-- Test vacuum_truncate option
DROP TABLE reloptions_test;
diff --git a/src/tools/pgindent/typedefs.list b/src/tools/pgindent/typedefs.list
index 3f3a888fd0e..1c8610fd46c 100644
--- a/src/tools/pgindent/typedefs.list
+++ b/src/tools/pgindent/typedefs.list
@@ -3952,6 +3952,7 @@ pg_sha512_ctx
pg_snapshot
pg_special_case
pg_stack_base_t
+pg_ternary
pg_time_t
pg_time_usec_t
pg_tz
@@ -4079,6 +4080,7 @@ relopt_kind
relopt_parse_elt
relopt_real
relopt_string
+relopt_ternary
relopt_type
relopt_value
relopts_validator
--
2.47.3
--c36t3fndxhkzbl5z--
^ permalink raw reply [nested|flat] 868+ messages in thread
* [PATCH v3] Introduce ternary reloptions
@ 2026-01-16 15:04 Álvaro Herrera <[email protected]>
0 siblings, 0 replies; 868+ messages in thread
From: Álvaro Herrera @ 2026-01-16 15:04 UTC (permalink / raw)
Introduce ternary reloption as a replacement for current
`vacuum_truncate` implementation. Remove the `vacuum_truncate_set`
separate flag and use TERNARY_UNSET instead.
This could also be used for other options such as `vacuum_index_cleanup`
and `buffering`, but lets get the scaffolding in first.
Discussion: https://postgr.es/m/3474141.usfYGdeWWP@thinkpad-pgpro
---
src/backend/access/common/reloptions.c | 137 ++++++++++++++++++-----
src/backend/commands/vacuum.c | 4 +-
src/include/access/reloptions.h | 27 ++---
src/include/postgres.h | 15 +++
src/include/utils/rel.h | 3 +-
src/test/regress/expected/reloptions.out | 36 ++++++
src/test/regress/sql/reloptions.sql | 21 ++++
src/tools/pgindent/typedefs.list | 2 +
8 files changed, 202 insertions(+), 43 deletions(-)
diff --git a/src/backend/access/common/reloptions.c b/src/backend/access/common/reloptions.c
index 0b83f98ed5f..6f1f577581a 100644
--- a/src/backend/access/common/reloptions.c
+++ b/src/backend/access/common/reloptions.c
@@ -40,9 +40,9 @@
*
* To add an option:
*
- * (i) decide on a type (bool, integer, real, enum, string), name, default
- * value, upper and lower bounds (if applicable); for strings, consider a
- * validation routine.
+ * (i) decide on a type (bool, ternary, integer, real, enum, string), name,
+ * default value, upper and lower bounds (if applicable); for strings,
+ * consider a validation routine.
* (ii) add a record below (or use add_<type>_reloption).
* (iii) add it to the appropriate options struct (perhaps StdRdOptions)
* (iv) add it to the appropriate handling routine (perhaps
@@ -147,15 +147,6 @@ static relopt_bool boolRelOpts[] =
},
false
},
- {
- {
- "vacuum_truncate",
- "Enables vacuum to truncate empty pages at the end of this table",
- RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
- ShareUpdateExclusiveLock
- },
- true
- },
{
{
"deduplicate_items",
@@ -170,6 +161,25 @@ static relopt_bool boolRelOpts[] =
{{NULL}}
};
+static relopt_ternary ternaryRelOpts[] =
+{
+ {
+ {
+ "vacuum_truncate",
+ "Enables vacuum to truncate empty pages at the end of this table",
+ RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
+ ShareUpdateExclusiveLock
+ },
+ TERNARY_UNSET
+ },
+ /* list terminator */
+ {
+ {
+ NULL
+ }
+ }
+};
+
static relopt_int intRelOpts[] =
{
{
@@ -609,6 +619,13 @@ initialize_reloptions(void)
boolRelOpts[i].gen.lockmode));
j++;
}
+ for (i = 0; ternaryRelOpts[i].gen.name; i++)
+ {
+ Assert(DoLockModesConflict(ternaryRelOpts[i].gen.lockmode,
+ ternaryRelOpts[i].gen.lockmode));
+ j++;
+ }
+
for (i = 0; intRelOpts[i].gen.name; i++)
{
Assert(DoLockModesConflict(intRelOpts[i].gen.lockmode,
@@ -649,6 +666,14 @@ initialize_reloptions(void)
j++;
}
+ for (i = 0; ternaryRelOpts[i].gen.name; i++)
+ {
+ relOpts[j] = &ternaryRelOpts[i].gen;
+ relOpts[j]->type = RELOPT_TYPE_TERNARY;
+ relOpts[j]->namelen = strlen(relOpts[j]->name);
+ j++;
+ }
+
for (i = 0; intRelOpts[i].gen.name; i++)
{
relOpts[j] = &intRelOpts[i].gen;
@@ -809,6 +834,9 @@ allocate_reloption(bits32 kinds, int type, const char *name, const char *desc,
case RELOPT_TYPE_BOOL:
size = sizeof(relopt_bool);
break;
+ case RELOPT_TYPE_TERNARY:
+ size = sizeof(relopt_ternary);
+ break;
case RELOPT_TYPE_INT:
size = sizeof(relopt_int);
break;
@@ -892,6 +920,57 @@ add_local_bool_reloption(local_relopts *relopts, const char *name,
add_local_reloption(relopts, (relopt_gen *) newoption, offset);
}
+/*
+ * init_ternary_reloption
+ * Allocate and initialize a new ternary reloption
+ */
+static relopt_ternary *
+init_ternary_reloption(bits32 kinds, const char *name, const char *desc,
+ pg_ternary default_val, LOCKMODE lockmode)
+{
+ relopt_ternary *newoption;
+
+ newoption = (relopt_ternary *)
+ allocate_reloption(kinds, RELOPT_TYPE_TERNARY, name, desc, lockmode);
+ newoption->default_val = default_val;
+
+ return newoption;
+}
+
+/*
+ * add_ternary_reloption
+ * Add a new ternary reloption
+ */
+void
+add_ternary_reloption(bits32 kinds, const char *name, const char *desc,
+ pg_ternary default_val, LOCKMODE lockmode)
+{
+ relopt_ternary *newoption;
+
+ newoption =
+ init_ternary_reloption(kinds, name, desc, default_val, lockmode);
+
+ add_reloption((relopt_gen *) newoption);
+}
+
+/*
+ * add_local_ternary_reloption
+ * Add a new ternary local reloption
+ *
+ * 'offset' is offset of ternary-typed field.
+ */
+void
+add_local_ternary_reloption(local_relopts *relopts, const char *name,
+ const char *desc, pg_ternary default_val,
+ int offset)
+{
+ relopt_ternary *newoption;
+
+ newoption =
+ init_ternary_reloption(RELOPT_KIND_LOCAL, name, desc, default_val, 0);
+
+ add_local_reloption(relopts, (relopt_gen *) newoption, offset);
+}
/*
* init_real_reloption
@@ -1626,6 +1705,19 @@ parse_one_reloption(relopt_value *option, char *text_str, int text_len,
option->gen->name, value)));
}
break;
+ case RELOPT_TYPE_TERNARY:
+ {
+ bool b;
+
+ parsed = parse_bool(value, &b);
+ option->values.ternary_val = b ? TERNARY_TRUE : TERNARY_FALSE;
+ if (validate && !parsed)
+ ereport(ERROR,
+ (errcode(ERRCODE_INVALID_PARAMETER_VALUE),
+ errmsg("invalid value for ternary option \"%s\": %s",
+ option->gen->name, value)));
+ }
+ break;
case RELOPT_TYPE_INT:
{
relopt_int *optint = (relopt_int *) option->gen;
@@ -1789,17 +1881,6 @@ fillRelOptions(void *rdopts, Size basesize,
char *itempos = ((char *) rdopts) + elems[j].offset;
char *string_val;
- /*
- * If isset_offset is provided, store whether the reloption is
- * set there.
- */
- if (elems[j].isset_offset > 0)
- {
- char *setpos = ((char *) rdopts) + elems[j].isset_offset;
-
- *(bool *) setpos = options[i].isset;
- }
-
switch (options[i].gen->type)
{
case RELOPT_TYPE_BOOL:
@@ -1807,6 +1888,11 @@ fillRelOptions(void *rdopts, Size basesize,
options[i].values.bool_val :
((relopt_bool *) options[i].gen)->default_val;
break;
+ case RELOPT_TYPE_TERNARY:
+ *(pg_ternary *) itempos = options[i].isset ?
+ options[i].values.ternary_val :
+ ((relopt_ternary *) options[i].gen)->default_val;
+ break;
case RELOPT_TYPE_INT:
*(int *) itempos = options[i].isset ?
options[i].values.int_val :
@@ -1923,8 +2009,8 @@ default_reloptions(Datum reloptions, bool validate, relopt_kind kind)
offsetof(StdRdOptions, parallel_workers)},
{"vacuum_index_cleanup", RELOPT_TYPE_ENUM,
offsetof(StdRdOptions, vacuum_index_cleanup)},
- {"vacuum_truncate", RELOPT_TYPE_BOOL,
- offsetof(StdRdOptions, vacuum_truncate), offsetof(StdRdOptions, vacuum_truncate_set)},
+ {"vacuum_truncate", RELOPT_TYPE_TERNARY,
+ offsetof(StdRdOptions, vacuum_truncate)},
{"vacuum_max_eager_freeze_failure_rate", RELOPT_TYPE_REAL,
offsetof(StdRdOptions, vacuum_max_eager_freeze_failure_rate)}
};
@@ -2004,7 +2090,6 @@ build_local_reloptions(local_relopts *relopts, Datum options, bool validate)
elems[i].optname = opt->option->name;
elems[i].opttype = opt->option->type;
elems[i].offset = opt->offset;
- elems[i].isset_offset = 0; /* not supported for local relopts yet */
i++;
}
diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c
index aa4fbec143f..696eab9bd97 100644
--- a/src/backend/commands/vacuum.c
+++ b/src/backend/commands/vacuum.c
@@ -2224,9 +2224,9 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams params,
{
StdRdOptions *opts = (StdRdOptions *) rel->rd_options;
- if (opts && opts->vacuum_truncate_set)
+ if (opts && opts->vacuum_truncate != TERNARY_UNSET)
{
- if (opts->vacuum_truncate)
+ if (opts->vacuum_truncate == TERNARY_TRUE)
params.truncate = VACOPTVALUE_ENABLED;
else
params.truncate = VACOPTVALUE_DISABLED;
diff --git a/src/include/access/reloptions.h b/src/include/access/reloptions.h
index 2f08e1b0cf0..dfbef2babf2 100644
--- a/src/include/access/reloptions.h
+++ b/src/include/access/reloptions.h
@@ -29,6 +29,7 @@
typedef enum relopt_type
{
RELOPT_TYPE_BOOL,
+ RELOPT_TYPE_TERNARY, /* on, off, unset */
RELOPT_TYPE_INT,
RELOPT_TYPE_REAL,
RELOPT_TYPE_ENUM,
@@ -80,6 +81,7 @@ typedef struct relopt_value
union
{
bool bool_val;
+ pg_ternary ternary_val;
int int_val;
double real_val;
int enum_val;
@@ -94,6 +96,12 @@ typedef struct relopt_bool
bool default_val;
} relopt_bool;
+typedef struct relopt_ternary
+{
+ relopt_gen gen;
+ int default_val;
+} relopt_ternary;
+
typedef struct relopt_int
{
relopt_gen gen;
@@ -152,19 +160,6 @@ typedef struct
const char *optname; /* option's name */
relopt_type opttype; /* option's datatype */
int offset; /* offset of field in result struct */
-
- /*
- * isset_offset is an optional offset of a field in the result struct that
- * stores whether the option is explicitly set for the relation or if it
- * just picked up the default value. In most cases, this can be
- * accomplished by giving the reloption a special out-of-range default
- * value (e.g., some integer reloptions use -2), but this isn't always
- * possible. For example, a Boolean reloption cannot be given an
- * out-of-range default, so we need another way to discover the source of
- * its value. This offset is only used if given a value greater than
- * zero.
- */
- int isset_offset;
} relopt_parse_elt;
/* Local reloption definition */
@@ -195,6 +190,9 @@ typedef struct local_relopts
extern relopt_kind add_reloption_kind(void);
extern void add_bool_reloption(bits32 kinds, const char *name, const char *desc,
bool default_val, LOCKMODE lockmode);
+extern void add_ternary_reloption(bits32 kinds, const char *name,
+ const char *desc, pg_ternary default_val,
+ LOCKMODE lockmode);
extern void add_int_reloption(bits32 kinds, const char *name, const char *desc,
int default_val, int min_val, int max_val,
LOCKMODE lockmode);
@@ -214,6 +212,9 @@ extern void register_reloptions_validator(local_relopts *relopts,
extern void add_local_bool_reloption(local_relopts *relopts, const char *name,
const char *desc, bool default_val,
int offset);
+extern void add_local_ternary_reloption(local_relopts *relopts,
+ const char *name, const char *desc,
+ pg_ternary default_val, int offset);
extern void add_local_int_reloption(local_relopts *relopts, const char *name,
const char *desc, int default_val,
int min_val, int max_val, int offset);
diff --git a/src/include/postgres.h b/src/include/postgres.h
index 7d93fbce709..47c88780776 100644
--- a/src/include/postgres.h
+++ b/src/include/postgres.h
@@ -543,6 +543,21 @@ Float8GetDatum(float8 X)
* ----------------------------------------------------------------
*/
+/*
+ * pg_ternary
+ * Boolean value with an extra "unset" value
+ *
+ * This enum can be used for values that want to distinguish between true,
+ * false, and unset.
+*/
+
+typedef enum pg_ternary
+{
+ TERNARY_FALSE = 0,
+ TERNARY_TRUE = 1,
+ TERNARY_UNSET = -1
+} pg_ternary;
+
/*
* NON_EXEC_STATIC: It's sometimes useful to define a variable or function
* that is normally static but extern when using EXEC_BACKEND (see
diff --git a/src/include/utils/rel.h b/src/include/utils/rel.h
index d03ab247788..5feb18a5373 100644
--- a/src/include/utils/rel.h
+++ b/src/include/utils/rel.h
@@ -347,8 +347,7 @@ typedef struct StdRdOptions
bool user_catalog_table; /* use as an additional catalog relation */
int parallel_workers; /* max number of parallel workers */
StdRdOptIndexCleanup vacuum_index_cleanup; /* controls index vacuuming */
- bool vacuum_truncate; /* enables vacuum to truncate a relation */
- bool vacuum_truncate_set; /* whether vacuum_truncate is set */
+ pg_ternary vacuum_truncate; /* enables vacuum to truncate a relation */
/*
* Fraction of pages in a relation that vacuum can eagerly scan and fail
diff --git a/src/test/regress/expected/reloptions.out b/src/test/regress/expected/reloptions.out
index 9de19b4e3f1..1c99f79ab01 100644
--- a/src/test/regress/expected/reloptions.out
+++ b/src/test/regress/expected/reloptions.out
@@ -98,6 +98,42 @@ SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass;
{fillfactor=13,autovacuum_enabled=false}
(1 row)
+-- Tests for future (FIXME) ternary options
+-- behave as boolean option: accept unassigned name and truncated value
+DROP TABLE reloptions_test;
+CREATE TABLE reloptions_test(i INT) WITH (vacuum_truncate);
+SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass;
+ reloptions
+------------------------
+ {vacuum_truncate=true}
+(1 row)
+
+DROP TABLE reloptions_test;
+CREATE TABLE reloptions_test(i INT) WITH (vacuum_truncate=FaLS);
+SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass;
+ reloptions
+------------------------
+ {vacuum_truncate=fals}
+(1 row)
+
+-- preferred "true" alias is used when storing
+DROP TABLE reloptions_test;
+CREATE TABLE reloptions_test(i INT) WITH (vacuum_index_cleanup=on);
+SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass;
+ reloptions
+---------------------------
+ {vacuum_index_cleanup=on}
+(1 row)
+
+-- custom "third" value is available
+DROP TABLE reloptions_test;
+CREATE TABLE reloptions_test(i INT) WITH (vacuum_index_cleanup=auto);
+SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass;
+ reloptions
+-----------------------------
+ {vacuum_index_cleanup=auto}
+(1 row)
+
-- Test vacuum_truncate option
DROP TABLE reloptions_test;
CREATE TEMP TABLE reloptions_test(i INT NOT NULL, j text)
diff --git a/src/test/regress/sql/reloptions.sql b/src/test/regress/sql/reloptions.sql
index 24fbe0b478d..f5980dafcbc 100644
--- a/src/test/regress/sql/reloptions.sql
+++ b/src/test/regress/sql/reloptions.sql
@@ -59,6 +59,27 @@ UPDATE pg_class
ALTER TABLE reloptions_test RESET (illegal_option);
SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass;
+-- Tests for future (FIXME) ternary options
+
+-- behave as boolean option: accept unassigned name and truncated value
+DROP TABLE reloptions_test;
+CREATE TABLE reloptions_test(i INT) WITH (vacuum_truncate);
+SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass;
+
+DROP TABLE reloptions_test;
+CREATE TABLE reloptions_test(i INT) WITH (vacuum_truncate=FaLS);
+SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass;
+
+-- preferred "true" alias is used when storing
+DROP TABLE reloptions_test;
+CREATE TABLE reloptions_test(i INT) WITH (vacuum_index_cleanup=on);
+SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass;
+
+-- custom "third" value is available
+DROP TABLE reloptions_test;
+CREATE TABLE reloptions_test(i INT) WITH (vacuum_index_cleanup=auto);
+SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass;
+
-- Test vacuum_truncate option
DROP TABLE reloptions_test;
diff --git a/src/tools/pgindent/typedefs.list b/src/tools/pgindent/typedefs.list
index 3f3a888fd0e..1c8610fd46c 100644
--- a/src/tools/pgindent/typedefs.list
+++ b/src/tools/pgindent/typedefs.list
@@ -3952,6 +3952,7 @@ pg_sha512_ctx
pg_snapshot
pg_special_case
pg_stack_base_t
+pg_ternary
pg_time_t
pg_time_usec_t
pg_tz
@@ -4079,6 +4080,7 @@ relopt_kind
relopt_parse_elt
relopt_real
relopt_string
+relopt_ternary
relopt_type
relopt_value
relopts_validator
--
2.47.3
--c36t3fndxhkzbl5z--
^ permalink raw reply [nested|flat] 868+ messages in thread
* [PATCH v3] Introduce ternary reloptions
@ 2026-01-16 15:04 Álvaro Herrera <[email protected]>
0 siblings, 0 replies; 868+ messages in thread
From: Álvaro Herrera @ 2026-01-16 15:04 UTC (permalink / raw)
Introduce ternary reloption as a replacement for current
`vacuum_truncate` implementation. Remove the `vacuum_truncate_set`
separate flag and use TERNARY_UNSET instead.
This could also be used for other options such as `vacuum_index_cleanup`
and `buffering`, but lets get the scaffolding in first.
Discussion: https://postgr.es/m/3474141.usfYGdeWWP@thinkpad-pgpro
---
src/backend/access/common/reloptions.c | 137 ++++++++++++++++++-----
src/backend/commands/vacuum.c | 4 +-
src/include/access/reloptions.h | 27 ++---
src/include/postgres.h | 15 +++
src/include/utils/rel.h | 3 +-
src/test/regress/expected/reloptions.out | 36 ++++++
src/test/regress/sql/reloptions.sql | 21 ++++
src/tools/pgindent/typedefs.list | 2 +
8 files changed, 202 insertions(+), 43 deletions(-)
diff --git a/src/backend/access/common/reloptions.c b/src/backend/access/common/reloptions.c
index 0b83f98ed5f..6f1f577581a 100644
--- a/src/backend/access/common/reloptions.c
+++ b/src/backend/access/common/reloptions.c
@@ -40,9 +40,9 @@
*
* To add an option:
*
- * (i) decide on a type (bool, integer, real, enum, string), name, default
- * value, upper and lower bounds (if applicable); for strings, consider a
- * validation routine.
+ * (i) decide on a type (bool, ternary, integer, real, enum, string), name,
+ * default value, upper and lower bounds (if applicable); for strings,
+ * consider a validation routine.
* (ii) add a record below (or use add_<type>_reloption).
* (iii) add it to the appropriate options struct (perhaps StdRdOptions)
* (iv) add it to the appropriate handling routine (perhaps
@@ -147,15 +147,6 @@ static relopt_bool boolRelOpts[] =
},
false
},
- {
- {
- "vacuum_truncate",
- "Enables vacuum to truncate empty pages at the end of this table",
- RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
- ShareUpdateExclusiveLock
- },
- true
- },
{
{
"deduplicate_items",
@@ -170,6 +161,25 @@ static relopt_bool boolRelOpts[] =
{{NULL}}
};
+static relopt_ternary ternaryRelOpts[] =
+{
+ {
+ {
+ "vacuum_truncate",
+ "Enables vacuum to truncate empty pages at the end of this table",
+ RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
+ ShareUpdateExclusiveLock
+ },
+ TERNARY_UNSET
+ },
+ /* list terminator */
+ {
+ {
+ NULL
+ }
+ }
+};
+
static relopt_int intRelOpts[] =
{
{
@@ -609,6 +619,13 @@ initialize_reloptions(void)
boolRelOpts[i].gen.lockmode));
j++;
}
+ for (i = 0; ternaryRelOpts[i].gen.name; i++)
+ {
+ Assert(DoLockModesConflict(ternaryRelOpts[i].gen.lockmode,
+ ternaryRelOpts[i].gen.lockmode));
+ j++;
+ }
+
for (i = 0; intRelOpts[i].gen.name; i++)
{
Assert(DoLockModesConflict(intRelOpts[i].gen.lockmode,
@@ -649,6 +666,14 @@ initialize_reloptions(void)
j++;
}
+ for (i = 0; ternaryRelOpts[i].gen.name; i++)
+ {
+ relOpts[j] = &ternaryRelOpts[i].gen;
+ relOpts[j]->type = RELOPT_TYPE_TERNARY;
+ relOpts[j]->namelen = strlen(relOpts[j]->name);
+ j++;
+ }
+
for (i = 0; intRelOpts[i].gen.name; i++)
{
relOpts[j] = &intRelOpts[i].gen;
@@ -809,6 +834,9 @@ allocate_reloption(bits32 kinds, int type, const char *name, const char *desc,
case RELOPT_TYPE_BOOL:
size = sizeof(relopt_bool);
break;
+ case RELOPT_TYPE_TERNARY:
+ size = sizeof(relopt_ternary);
+ break;
case RELOPT_TYPE_INT:
size = sizeof(relopt_int);
break;
@@ -892,6 +920,57 @@ add_local_bool_reloption(local_relopts *relopts, const char *name,
add_local_reloption(relopts, (relopt_gen *) newoption, offset);
}
+/*
+ * init_ternary_reloption
+ * Allocate and initialize a new ternary reloption
+ */
+static relopt_ternary *
+init_ternary_reloption(bits32 kinds, const char *name, const char *desc,
+ pg_ternary default_val, LOCKMODE lockmode)
+{
+ relopt_ternary *newoption;
+
+ newoption = (relopt_ternary *)
+ allocate_reloption(kinds, RELOPT_TYPE_TERNARY, name, desc, lockmode);
+ newoption->default_val = default_val;
+
+ return newoption;
+}
+
+/*
+ * add_ternary_reloption
+ * Add a new ternary reloption
+ */
+void
+add_ternary_reloption(bits32 kinds, const char *name, const char *desc,
+ pg_ternary default_val, LOCKMODE lockmode)
+{
+ relopt_ternary *newoption;
+
+ newoption =
+ init_ternary_reloption(kinds, name, desc, default_val, lockmode);
+
+ add_reloption((relopt_gen *) newoption);
+}
+
+/*
+ * add_local_ternary_reloption
+ * Add a new ternary local reloption
+ *
+ * 'offset' is offset of ternary-typed field.
+ */
+void
+add_local_ternary_reloption(local_relopts *relopts, const char *name,
+ const char *desc, pg_ternary default_val,
+ int offset)
+{
+ relopt_ternary *newoption;
+
+ newoption =
+ init_ternary_reloption(RELOPT_KIND_LOCAL, name, desc, default_val, 0);
+
+ add_local_reloption(relopts, (relopt_gen *) newoption, offset);
+}
/*
* init_real_reloption
@@ -1626,6 +1705,19 @@ parse_one_reloption(relopt_value *option, char *text_str, int text_len,
option->gen->name, value)));
}
break;
+ case RELOPT_TYPE_TERNARY:
+ {
+ bool b;
+
+ parsed = parse_bool(value, &b);
+ option->values.ternary_val = b ? TERNARY_TRUE : TERNARY_FALSE;
+ if (validate && !parsed)
+ ereport(ERROR,
+ (errcode(ERRCODE_INVALID_PARAMETER_VALUE),
+ errmsg("invalid value for ternary option \"%s\": %s",
+ option->gen->name, value)));
+ }
+ break;
case RELOPT_TYPE_INT:
{
relopt_int *optint = (relopt_int *) option->gen;
@@ -1789,17 +1881,6 @@ fillRelOptions(void *rdopts, Size basesize,
char *itempos = ((char *) rdopts) + elems[j].offset;
char *string_val;
- /*
- * If isset_offset is provided, store whether the reloption is
- * set there.
- */
- if (elems[j].isset_offset > 0)
- {
- char *setpos = ((char *) rdopts) + elems[j].isset_offset;
-
- *(bool *) setpos = options[i].isset;
- }
-
switch (options[i].gen->type)
{
case RELOPT_TYPE_BOOL:
@@ -1807,6 +1888,11 @@ fillRelOptions(void *rdopts, Size basesize,
options[i].values.bool_val :
((relopt_bool *) options[i].gen)->default_val;
break;
+ case RELOPT_TYPE_TERNARY:
+ *(pg_ternary *) itempos = options[i].isset ?
+ options[i].values.ternary_val :
+ ((relopt_ternary *) options[i].gen)->default_val;
+ break;
case RELOPT_TYPE_INT:
*(int *) itempos = options[i].isset ?
options[i].values.int_val :
@@ -1923,8 +2009,8 @@ default_reloptions(Datum reloptions, bool validate, relopt_kind kind)
offsetof(StdRdOptions, parallel_workers)},
{"vacuum_index_cleanup", RELOPT_TYPE_ENUM,
offsetof(StdRdOptions, vacuum_index_cleanup)},
- {"vacuum_truncate", RELOPT_TYPE_BOOL,
- offsetof(StdRdOptions, vacuum_truncate), offsetof(StdRdOptions, vacuum_truncate_set)},
+ {"vacuum_truncate", RELOPT_TYPE_TERNARY,
+ offsetof(StdRdOptions, vacuum_truncate)},
{"vacuum_max_eager_freeze_failure_rate", RELOPT_TYPE_REAL,
offsetof(StdRdOptions, vacuum_max_eager_freeze_failure_rate)}
};
@@ -2004,7 +2090,6 @@ build_local_reloptions(local_relopts *relopts, Datum options, bool validate)
elems[i].optname = opt->option->name;
elems[i].opttype = opt->option->type;
elems[i].offset = opt->offset;
- elems[i].isset_offset = 0; /* not supported for local relopts yet */
i++;
}
diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c
index aa4fbec143f..696eab9bd97 100644
--- a/src/backend/commands/vacuum.c
+++ b/src/backend/commands/vacuum.c
@@ -2224,9 +2224,9 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams params,
{
StdRdOptions *opts = (StdRdOptions *) rel->rd_options;
- if (opts && opts->vacuum_truncate_set)
+ if (opts && opts->vacuum_truncate != TERNARY_UNSET)
{
- if (opts->vacuum_truncate)
+ if (opts->vacuum_truncate == TERNARY_TRUE)
params.truncate = VACOPTVALUE_ENABLED;
else
params.truncate = VACOPTVALUE_DISABLED;
diff --git a/src/include/access/reloptions.h b/src/include/access/reloptions.h
index 2f08e1b0cf0..dfbef2babf2 100644
--- a/src/include/access/reloptions.h
+++ b/src/include/access/reloptions.h
@@ -29,6 +29,7 @@
typedef enum relopt_type
{
RELOPT_TYPE_BOOL,
+ RELOPT_TYPE_TERNARY, /* on, off, unset */
RELOPT_TYPE_INT,
RELOPT_TYPE_REAL,
RELOPT_TYPE_ENUM,
@@ -80,6 +81,7 @@ typedef struct relopt_value
union
{
bool bool_val;
+ pg_ternary ternary_val;
int int_val;
double real_val;
int enum_val;
@@ -94,6 +96,12 @@ typedef struct relopt_bool
bool default_val;
} relopt_bool;
+typedef struct relopt_ternary
+{
+ relopt_gen gen;
+ int default_val;
+} relopt_ternary;
+
typedef struct relopt_int
{
relopt_gen gen;
@@ -152,19 +160,6 @@ typedef struct
const char *optname; /* option's name */
relopt_type opttype; /* option's datatype */
int offset; /* offset of field in result struct */
-
- /*
- * isset_offset is an optional offset of a field in the result struct that
- * stores whether the option is explicitly set for the relation or if it
- * just picked up the default value. In most cases, this can be
- * accomplished by giving the reloption a special out-of-range default
- * value (e.g., some integer reloptions use -2), but this isn't always
- * possible. For example, a Boolean reloption cannot be given an
- * out-of-range default, so we need another way to discover the source of
- * its value. This offset is only used if given a value greater than
- * zero.
- */
- int isset_offset;
} relopt_parse_elt;
/* Local reloption definition */
@@ -195,6 +190,9 @@ typedef struct local_relopts
extern relopt_kind add_reloption_kind(void);
extern void add_bool_reloption(bits32 kinds, const char *name, const char *desc,
bool default_val, LOCKMODE lockmode);
+extern void add_ternary_reloption(bits32 kinds, const char *name,
+ const char *desc, pg_ternary default_val,
+ LOCKMODE lockmode);
extern void add_int_reloption(bits32 kinds, const char *name, const char *desc,
int default_val, int min_val, int max_val,
LOCKMODE lockmode);
@@ -214,6 +212,9 @@ extern void register_reloptions_validator(local_relopts *relopts,
extern void add_local_bool_reloption(local_relopts *relopts, const char *name,
const char *desc, bool default_val,
int offset);
+extern void add_local_ternary_reloption(local_relopts *relopts,
+ const char *name, const char *desc,
+ pg_ternary default_val, int offset);
extern void add_local_int_reloption(local_relopts *relopts, const char *name,
const char *desc, int default_val,
int min_val, int max_val, int offset);
diff --git a/src/include/postgres.h b/src/include/postgres.h
index 7d93fbce709..47c88780776 100644
--- a/src/include/postgres.h
+++ b/src/include/postgres.h
@@ -543,6 +543,21 @@ Float8GetDatum(float8 X)
* ----------------------------------------------------------------
*/
+/*
+ * pg_ternary
+ * Boolean value with an extra "unset" value
+ *
+ * This enum can be used for values that want to distinguish between true,
+ * false, and unset.
+*/
+
+typedef enum pg_ternary
+{
+ TERNARY_FALSE = 0,
+ TERNARY_TRUE = 1,
+ TERNARY_UNSET = -1
+} pg_ternary;
+
/*
* NON_EXEC_STATIC: It's sometimes useful to define a variable or function
* that is normally static but extern when using EXEC_BACKEND (see
diff --git a/src/include/utils/rel.h b/src/include/utils/rel.h
index d03ab247788..5feb18a5373 100644
--- a/src/include/utils/rel.h
+++ b/src/include/utils/rel.h
@@ -347,8 +347,7 @@ typedef struct StdRdOptions
bool user_catalog_table; /* use as an additional catalog relation */
int parallel_workers; /* max number of parallel workers */
StdRdOptIndexCleanup vacuum_index_cleanup; /* controls index vacuuming */
- bool vacuum_truncate; /* enables vacuum to truncate a relation */
- bool vacuum_truncate_set; /* whether vacuum_truncate is set */
+ pg_ternary vacuum_truncate; /* enables vacuum to truncate a relation */
/*
* Fraction of pages in a relation that vacuum can eagerly scan and fail
diff --git a/src/test/regress/expected/reloptions.out b/src/test/regress/expected/reloptions.out
index 9de19b4e3f1..1c99f79ab01 100644
--- a/src/test/regress/expected/reloptions.out
+++ b/src/test/regress/expected/reloptions.out
@@ -98,6 +98,42 @@ SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass;
{fillfactor=13,autovacuum_enabled=false}
(1 row)
+-- Tests for future (FIXME) ternary options
+-- behave as boolean option: accept unassigned name and truncated value
+DROP TABLE reloptions_test;
+CREATE TABLE reloptions_test(i INT) WITH (vacuum_truncate);
+SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass;
+ reloptions
+------------------------
+ {vacuum_truncate=true}
+(1 row)
+
+DROP TABLE reloptions_test;
+CREATE TABLE reloptions_test(i INT) WITH (vacuum_truncate=FaLS);
+SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass;
+ reloptions
+------------------------
+ {vacuum_truncate=fals}
+(1 row)
+
+-- preferred "true" alias is used when storing
+DROP TABLE reloptions_test;
+CREATE TABLE reloptions_test(i INT) WITH (vacuum_index_cleanup=on);
+SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass;
+ reloptions
+---------------------------
+ {vacuum_index_cleanup=on}
+(1 row)
+
+-- custom "third" value is available
+DROP TABLE reloptions_test;
+CREATE TABLE reloptions_test(i INT) WITH (vacuum_index_cleanup=auto);
+SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass;
+ reloptions
+-----------------------------
+ {vacuum_index_cleanup=auto}
+(1 row)
+
-- Test vacuum_truncate option
DROP TABLE reloptions_test;
CREATE TEMP TABLE reloptions_test(i INT NOT NULL, j text)
diff --git a/src/test/regress/sql/reloptions.sql b/src/test/regress/sql/reloptions.sql
index 24fbe0b478d..f5980dafcbc 100644
--- a/src/test/regress/sql/reloptions.sql
+++ b/src/test/regress/sql/reloptions.sql
@@ -59,6 +59,27 @@ UPDATE pg_class
ALTER TABLE reloptions_test RESET (illegal_option);
SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass;
+-- Tests for future (FIXME) ternary options
+
+-- behave as boolean option: accept unassigned name and truncated value
+DROP TABLE reloptions_test;
+CREATE TABLE reloptions_test(i INT) WITH (vacuum_truncate);
+SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass;
+
+DROP TABLE reloptions_test;
+CREATE TABLE reloptions_test(i INT) WITH (vacuum_truncate=FaLS);
+SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass;
+
+-- preferred "true" alias is used when storing
+DROP TABLE reloptions_test;
+CREATE TABLE reloptions_test(i INT) WITH (vacuum_index_cleanup=on);
+SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass;
+
+-- custom "third" value is available
+DROP TABLE reloptions_test;
+CREATE TABLE reloptions_test(i INT) WITH (vacuum_index_cleanup=auto);
+SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass;
+
-- Test vacuum_truncate option
DROP TABLE reloptions_test;
diff --git a/src/tools/pgindent/typedefs.list b/src/tools/pgindent/typedefs.list
index 3f3a888fd0e..1c8610fd46c 100644
--- a/src/tools/pgindent/typedefs.list
+++ b/src/tools/pgindent/typedefs.list
@@ -3952,6 +3952,7 @@ pg_sha512_ctx
pg_snapshot
pg_special_case
pg_stack_base_t
+pg_ternary
pg_time_t
pg_time_usec_t
pg_tz
@@ -4079,6 +4080,7 @@ relopt_kind
relopt_parse_elt
relopt_real
relopt_string
+relopt_ternary
relopt_type
relopt_value
relopts_validator
--
2.47.3
--c36t3fndxhkzbl5z--
^ permalink raw reply [nested|flat] 868+ messages in thread
* [PATCH v3] Introduce ternary reloptions
@ 2026-01-16 15:04 Álvaro Herrera <[email protected]>
0 siblings, 0 replies; 868+ messages in thread
From: Álvaro Herrera @ 2026-01-16 15:04 UTC (permalink / raw)
Introduce ternary reloption as a replacement for current
`vacuum_truncate` implementation. Remove the `vacuum_truncate_set`
separate flag and use TERNARY_UNSET instead.
This could also be used for other options such as `vacuum_index_cleanup`
and `buffering`, but lets get the scaffolding in first.
Discussion: https://postgr.es/m/3474141.usfYGdeWWP@thinkpad-pgpro
---
src/backend/access/common/reloptions.c | 137 ++++++++++++++++++-----
src/backend/commands/vacuum.c | 4 +-
src/include/access/reloptions.h | 27 ++---
src/include/postgres.h | 15 +++
src/include/utils/rel.h | 3 +-
src/test/regress/expected/reloptions.out | 36 ++++++
src/test/regress/sql/reloptions.sql | 21 ++++
src/tools/pgindent/typedefs.list | 2 +
8 files changed, 202 insertions(+), 43 deletions(-)
diff --git a/src/backend/access/common/reloptions.c b/src/backend/access/common/reloptions.c
index 0b83f98ed5f..6f1f577581a 100644
--- a/src/backend/access/common/reloptions.c
+++ b/src/backend/access/common/reloptions.c
@@ -40,9 +40,9 @@
*
* To add an option:
*
- * (i) decide on a type (bool, integer, real, enum, string), name, default
- * value, upper and lower bounds (if applicable); for strings, consider a
- * validation routine.
+ * (i) decide on a type (bool, ternary, integer, real, enum, string), name,
+ * default value, upper and lower bounds (if applicable); for strings,
+ * consider a validation routine.
* (ii) add a record below (or use add_<type>_reloption).
* (iii) add it to the appropriate options struct (perhaps StdRdOptions)
* (iv) add it to the appropriate handling routine (perhaps
@@ -147,15 +147,6 @@ static relopt_bool boolRelOpts[] =
},
false
},
- {
- {
- "vacuum_truncate",
- "Enables vacuum to truncate empty pages at the end of this table",
- RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
- ShareUpdateExclusiveLock
- },
- true
- },
{
{
"deduplicate_items",
@@ -170,6 +161,25 @@ static relopt_bool boolRelOpts[] =
{{NULL}}
};
+static relopt_ternary ternaryRelOpts[] =
+{
+ {
+ {
+ "vacuum_truncate",
+ "Enables vacuum to truncate empty pages at the end of this table",
+ RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
+ ShareUpdateExclusiveLock
+ },
+ TERNARY_UNSET
+ },
+ /* list terminator */
+ {
+ {
+ NULL
+ }
+ }
+};
+
static relopt_int intRelOpts[] =
{
{
@@ -609,6 +619,13 @@ initialize_reloptions(void)
boolRelOpts[i].gen.lockmode));
j++;
}
+ for (i = 0; ternaryRelOpts[i].gen.name; i++)
+ {
+ Assert(DoLockModesConflict(ternaryRelOpts[i].gen.lockmode,
+ ternaryRelOpts[i].gen.lockmode));
+ j++;
+ }
+
for (i = 0; intRelOpts[i].gen.name; i++)
{
Assert(DoLockModesConflict(intRelOpts[i].gen.lockmode,
@@ -649,6 +666,14 @@ initialize_reloptions(void)
j++;
}
+ for (i = 0; ternaryRelOpts[i].gen.name; i++)
+ {
+ relOpts[j] = &ternaryRelOpts[i].gen;
+ relOpts[j]->type = RELOPT_TYPE_TERNARY;
+ relOpts[j]->namelen = strlen(relOpts[j]->name);
+ j++;
+ }
+
for (i = 0; intRelOpts[i].gen.name; i++)
{
relOpts[j] = &intRelOpts[i].gen;
@@ -809,6 +834,9 @@ allocate_reloption(bits32 kinds, int type, const char *name, const char *desc,
case RELOPT_TYPE_BOOL:
size = sizeof(relopt_bool);
break;
+ case RELOPT_TYPE_TERNARY:
+ size = sizeof(relopt_ternary);
+ break;
case RELOPT_TYPE_INT:
size = sizeof(relopt_int);
break;
@@ -892,6 +920,57 @@ add_local_bool_reloption(local_relopts *relopts, const char *name,
add_local_reloption(relopts, (relopt_gen *) newoption, offset);
}
+/*
+ * init_ternary_reloption
+ * Allocate and initialize a new ternary reloption
+ */
+static relopt_ternary *
+init_ternary_reloption(bits32 kinds, const char *name, const char *desc,
+ pg_ternary default_val, LOCKMODE lockmode)
+{
+ relopt_ternary *newoption;
+
+ newoption = (relopt_ternary *)
+ allocate_reloption(kinds, RELOPT_TYPE_TERNARY, name, desc, lockmode);
+ newoption->default_val = default_val;
+
+ return newoption;
+}
+
+/*
+ * add_ternary_reloption
+ * Add a new ternary reloption
+ */
+void
+add_ternary_reloption(bits32 kinds, const char *name, const char *desc,
+ pg_ternary default_val, LOCKMODE lockmode)
+{
+ relopt_ternary *newoption;
+
+ newoption =
+ init_ternary_reloption(kinds, name, desc, default_val, lockmode);
+
+ add_reloption((relopt_gen *) newoption);
+}
+
+/*
+ * add_local_ternary_reloption
+ * Add a new ternary local reloption
+ *
+ * 'offset' is offset of ternary-typed field.
+ */
+void
+add_local_ternary_reloption(local_relopts *relopts, const char *name,
+ const char *desc, pg_ternary default_val,
+ int offset)
+{
+ relopt_ternary *newoption;
+
+ newoption =
+ init_ternary_reloption(RELOPT_KIND_LOCAL, name, desc, default_val, 0);
+
+ add_local_reloption(relopts, (relopt_gen *) newoption, offset);
+}
/*
* init_real_reloption
@@ -1626,6 +1705,19 @@ parse_one_reloption(relopt_value *option, char *text_str, int text_len,
option->gen->name, value)));
}
break;
+ case RELOPT_TYPE_TERNARY:
+ {
+ bool b;
+
+ parsed = parse_bool(value, &b);
+ option->values.ternary_val = b ? TERNARY_TRUE : TERNARY_FALSE;
+ if (validate && !parsed)
+ ereport(ERROR,
+ (errcode(ERRCODE_INVALID_PARAMETER_VALUE),
+ errmsg("invalid value for ternary option \"%s\": %s",
+ option->gen->name, value)));
+ }
+ break;
case RELOPT_TYPE_INT:
{
relopt_int *optint = (relopt_int *) option->gen;
@@ -1789,17 +1881,6 @@ fillRelOptions(void *rdopts, Size basesize,
char *itempos = ((char *) rdopts) + elems[j].offset;
char *string_val;
- /*
- * If isset_offset is provided, store whether the reloption is
- * set there.
- */
- if (elems[j].isset_offset > 0)
- {
- char *setpos = ((char *) rdopts) + elems[j].isset_offset;
-
- *(bool *) setpos = options[i].isset;
- }
-
switch (options[i].gen->type)
{
case RELOPT_TYPE_BOOL:
@@ -1807,6 +1888,11 @@ fillRelOptions(void *rdopts, Size basesize,
options[i].values.bool_val :
((relopt_bool *) options[i].gen)->default_val;
break;
+ case RELOPT_TYPE_TERNARY:
+ *(pg_ternary *) itempos = options[i].isset ?
+ options[i].values.ternary_val :
+ ((relopt_ternary *) options[i].gen)->default_val;
+ break;
case RELOPT_TYPE_INT:
*(int *) itempos = options[i].isset ?
options[i].values.int_val :
@@ -1923,8 +2009,8 @@ default_reloptions(Datum reloptions, bool validate, relopt_kind kind)
offsetof(StdRdOptions, parallel_workers)},
{"vacuum_index_cleanup", RELOPT_TYPE_ENUM,
offsetof(StdRdOptions, vacuum_index_cleanup)},
- {"vacuum_truncate", RELOPT_TYPE_BOOL,
- offsetof(StdRdOptions, vacuum_truncate), offsetof(StdRdOptions, vacuum_truncate_set)},
+ {"vacuum_truncate", RELOPT_TYPE_TERNARY,
+ offsetof(StdRdOptions, vacuum_truncate)},
{"vacuum_max_eager_freeze_failure_rate", RELOPT_TYPE_REAL,
offsetof(StdRdOptions, vacuum_max_eager_freeze_failure_rate)}
};
@@ -2004,7 +2090,6 @@ build_local_reloptions(local_relopts *relopts, Datum options, bool validate)
elems[i].optname = opt->option->name;
elems[i].opttype = opt->option->type;
elems[i].offset = opt->offset;
- elems[i].isset_offset = 0; /* not supported for local relopts yet */
i++;
}
diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c
index aa4fbec143f..696eab9bd97 100644
--- a/src/backend/commands/vacuum.c
+++ b/src/backend/commands/vacuum.c
@@ -2224,9 +2224,9 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams params,
{
StdRdOptions *opts = (StdRdOptions *) rel->rd_options;
- if (opts && opts->vacuum_truncate_set)
+ if (opts && opts->vacuum_truncate != TERNARY_UNSET)
{
- if (opts->vacuum_truncate)
+ if (opts->vacuum_truncate == TERNARY_TRUE)
params.truncate = VACOPTVALUE_ENABLED;
else
params.truncate = VACOPTVALUE_DISABLED;
diff --git a/src/include/access/reloptions.h b/src/include/access/reloptions.h
index 2f08e1b0cf0..dfbef2babf2 100644
--- a/src/include/access/reloptions.h
+++ b/src/include/access/reloptions.h
@@ -29,6 +29,7 @@
typedef enum relopt_type
{
RELOPT_TYPE_BOOL,
+ RELOPT_TYPE_TERNARY, /* on, off, unset */
RELOPT_TYPE_INT,
RELOPT_TYPE_REAL,
RELOPT_TYPE_ENUM,
@@ -80,6 +81,7 @@ typedef struct relopt_value
union
{
bool bool_val;
+ pg_ternary ternary_val;
int int_val;
double real_val;
int enum_val;
@@ -94,6 +96,12 @@ typedef struct relopt_bool
bool default_val;
} relopt_bool;
+typedef struct relopt_ternary
+{
+ relopt_gen gen;
+ int default_val;
+} relopt_ternary;
+
typedef struct relopt_int
{
relopt_gen gen;
@@ -152,19 +160,6 @@ typedef struct
const char *optname; /* option's name */
relopt_type opttype; /* option's datatype */
int offset; /* offset of field in result struct */
-
- /*
- * isset_offset is an optional offset of a field in the result struct that
- * stores whether the option is explicitly set for the relation or if it
- * just picked up the default value. In most cases, this can be
- * accomplished by giving the reloption a special out-of-range default
- * value (e.g., some integer reloptions use -2), but this isn't always
- * possible. For example, a Boolean reloption cannot be given an
- * out-of-range default, so we need another way to discover the source of
- * its value. This offset is only used if given a value greater than
- * zero.
- */
- int isset_offset;
} relopt_parse_elt;
/* Local reloption definition */
@@ -195,6 +190,9 @@ typedef struct local_relopts
extern relopt_kind add_reloption_kind(void);
extern void add_bool_reloption(bits32 kinds, const char *name, const char *desc,
bool default_val, LOCKMODE lockmode);
+extern void add_ternary_reloption(bits32 kinds, const char *name,
+ const char *desc, pg_ternary default_val,
+ LOCKMODE lockmode);
extern void add_int_reloption(bits32 kinds, const char *name, const char *desc,
int default_val, int min_val, int max_val,
LOCKMODE lockmode);
@@ -214,6 +212,9 @@ extern void register_reloptions_validator(local_relopts *relopts,
extern void add_local_bool_reloption(local_relopts *relopts, const char *name,
const char *desc, bool default_val,
int offset);
+extern void add_local_ternary_reloption(local_relopts *relopts,
+ const char *name, const char *desc,
+ pg_ternary default_val, int offset);
extern void add_local_int_reloption(local_relopts *relopts, const char *name,
const char *desc, int default_val,
int min_val, int max_val, int offset);
diff --git a/src/include/postgres.h b/src/include/postgres.h
index 7d93fbce709..47c88780776 100644
--- a/src/include/postgres.h
+++ b/src/include/postgres.h
@@ -543,6 +543,21 @@ Float8GetDatum(float8 X)
* ----------------------------------------------------------------
*/
+/*
+ * pg_ternary
+ * Boolean value with an extra "unset" value
+ *
+ * This enum can be used for values that want to distinguish between true,
+ * false, and unset.
+*/
+
+typedef enum pg_ternary
+{
+ TERNARY_FALSE = 0,
+ TERNARY_TRUE = 1,
+ TERNARY_UNSET = -1
+} pg_ternary;
+
/*
* NON_EXEC_STATIC: It's sometimes useful to define a variable or function
* that is normally static but extern when using EXEC_BACKEND (see
diff --git a/src/include/utils/rel.h b/src/include/utils/rel.h
index d03ab247788..5feb18a5373 100644
--- a/src/include/utils/rel.h
+++ b/src/include/utils/rel.h
@@ -347,8 +347,7 @@ typedef struct StdRdOptions
bool user_catalog_table; /* use as an additional catalog relation */
int parallel_workers; /* max number of parallel workers */
StdRdOptIndexCleanup vacuum_index_cleanup; /* controls index vacuuming */
- bool vacuum_truncate; /* enables vacuum to truncate a relation */
- bool vacuum_truncate_set; /* whether vacuum_truncate is set */
+ pg_ternary vacuum_truncate; /* enables vacuum to truncate a relation */
/*
* Fraction of pages in a relation that vacuum can eagerly scan and fail
diff --git a/src/test/regress/expected/reloptions.out b/src/test/regress/expected/reloptions.out
index 9de19b4e3f1..1c99f79ab01 100644
--- a/src/test/regress/expected/reloptions.out
+++ b/src/test/regress/expected/reloptions.out
@@ -98,6 +98,42 @@ SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass;
{fillfactor=13,autovacuum_enabled=false}
(1 row)
+-- Tests for future (FIXME) ternary options
+-- behave as boolean option: accept unassigned name and truncated value
+DROP TABLE reloptions_test;
+CREATE TABLE reloptions_test(i INT) WITH (vacuum_truncate);
+SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass;
+ reloptions
+------------------------
+ {vacuum_truncate=true}
+(1 row)
+
+DROP TABLE reloptions_test;
+CREATE TABLE reloptions_test(i INT) WITH (vacuum_truncate=FaLS);
+SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass;
+ reloptions
+------------------------
+ {vacuum_truncate=fals}
+(1 row)
+
+-- preferred "true" alias is used when storing
+DROP TABLE reloptions_test;
+CREATE TABLE reloptions_test(i INT) WITH (vacuum_index_cleanup=on);
+SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass;
+ reloptions
+---------------------------
+ {vacuum_index_cleanup=on}
+(1 row)
+
+-- custom "third" value is available
+DROP TABLE reloptions_test;
+CREATE TABLE reloptions_test(i INT) WITH (vacuum_index_cleanup=auto);
+SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass;
+ reloptions
+-----------------------------
+ {vacuum_index_cleanup=auto}
+(1 row)
+
-- Test vacuum_truncate option
DROP TABLE reloptions_test;
CREATE TEMP TABLE reloptions_test(i INT NOT NULL, j text)
diff --git a/src/test/regress/sql/reloptions.sql b/src/test/regress/sql/reloptions.sql
index 24fbe0b478d..f5980dafcbc 100644
--- a/src/test/regress/sql/reloptions.sql
+++ b/src/test/regress/sql/reloptions.sql
@@ -59,6 +59,27 @@ UPDATE pg_class
ALTER TABLE reloptions_test RESET (illegal_option);
SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass;
+-- Tests for future (FIXME) ternary options
+
+-- behave as boolean option: accept unassigned name and truncated value
+DROP TABLE reloptions_test;
+CREATE TABLE reloptions_test(i INT) WITH (vacuum_truncate);
+SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass;
+
+DROP TABLE reloptions_test;
+CREATE TABLE reloptions_test(i INT) WITH (vacuum_truncate=FaLS);
+SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass;
+
+-- preferred "true" alias is used when storing
+DROP TABLE reloptions_test;
+CREATE TABLE reloptions_test(i INT) WITH (vacuum_index_cleanup=on);
+SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass;
+
+-- custom "third" value is available
+DROP TABLE reloptions_test;
+CREATE TABLE reloptions_test(i INT) WITH (vacuum_index_cleanup=auto);
+SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass;
+
-- Test vacuum_truncate option
DROP TABLE reloptions_test;
diff --git a/src/tools/pgindent/typedefs.list b/src/tools/pgindent/typedefs.list
index 3f3a888fd0e..1c8610fd46c 100644
--- a/src/tools/pgindent/typedefs.list
+++ b/src/tools/pgindent/typedefs.list
@@ -3952,6 +3952,7 @@ pg_sha512_ctx
pg_snapshot
pg_special_case
pg_stack_base_t
+pg_ternary
pg_time_t
pg_time_usec_t
pg_tz
@@ -4079,6 +4080,7 @@ relopt_kind
relopt_parse_elt
relopt_real
relopt_string
+relopt_ternary
relopt_type
relopt_value
relopts_validator
--
2.47.3
--c36t3fndxhkzbl5z--
^ permalink raw reply [nested|flat] 868+ messages in thread
* [PATCH v3] Introduce ternary reloptions
@ 2026-01-16 15:04 Álvaro Herrera <[email protected]>
0 siblings, 0 replies; 868+ messages in thread
From: Álvaro Herrera @ 2026-01-16 15:04 UTC (permalink / raw)
Introduce ternary reloption as a replacement for current
`vacuum_truncate` implementation. Remove the `vacuum_truncate_set`
separate flag and use TERNARY_UNSET instead.
This could also be used for other options such as `vacuum_index_cleanup`
and `buffering`, but lets get the scaffolding in first.
Discussion: https://postgr.es/m/3474141.usfYGdeWWP@thinkpad-pgpro
---
src/backend/access/common/reloptions.c | 137 ++++++++++++++++++-----
src/backend/commands/vacuum.c | 4 +-
src/include/access/reloptions.h | 27 ++---
src/include/postgres.h | 15 +++
src/include/utils/rel.h | 3 +-
src/test/regress/expected/reloptions.out | 36 ++++++
src/test/regress/sql/reloptions.sql | 21 ++++
src/tools/pgindent/typedefs.list | 2 +
8 files changed, 202 insertions(+), 43 deletions(-)
diff --git a/src/backend/access/common/reloptions.c b/src/backend/access/common/reloptions.c
index 0b83f98ed5f..6f1f577581a 100644
--- a/src/backend/access/common/reloptions.c
+++ b/src/backend/access/common/reloptions.c
@@ -40,9 +40,9 @@
*
* To add an option:
*
- * (i) decide on a type (bool, integer, real, enum, string), name, default
- * value, upper and lower bounds (if applicable); for strings, consider a
- * validation routine.
+ * (i) decide on a type (bool, ternary, integer, real, enum, string), name,
+ * default value, upper and lower bounds (if applicable); for strings,
+ * consider a validation routine.
* (ii) add a record below (or use add_<type>_reloption).
* (iii) add it to the appropriate options struct (perhaps StdRdOptions)
* (iv) add it to the appropriate handling routine (perhaps
@@ -147,15 +147,6 @@ static relopt_bool boolRelOpts[] =
},
false
},
- {
- {
- "vacuum_truncate",
- "Enables vacuum to truncate empty pages at the end of this table",
- RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
- ShareUpdateExclusiveLock
- },
- true
- },
{
{
"deduplicate_items",
@@ -170,6 +161,25 @@ static relopt_bool boolRelOpts[] =
{{NULL}}
};
+static relopt_ternary ternaryRelOpts[] =
+{
+ {
+ {
+ "vacuum_truncate",
+ "Enables vacuum to truncate empty pages at the end of this table",
+ RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
+ ShareUpdateExclusiveLock
+ },
+ TERNARY_UNSET
+ },
+ /* list terminator */
+ {
+ {
+ NULL
+ }
+ }
+};
+
static relopt_int intRelOpts[] =
{
{
@@ -609,6 +619,13 @@ initialize_reloptions(void)
boolRelOpts[i].gen.lockmode));
j++;
}
+ for (i = 0; ternaryRelOpts[i].gen.name; i++)
+ {
+ Assert(DoLockModesConflict(ternaryRelOpts[i].gen.lockmode,
+ ternaryRelOpts[i].gen.lockmode));
+ j++;
+ }
+
for (i = 0; intRelOpts[i].gen.name; i++)
{
Assert(DoLockModesConflict(intRelOpts[i].gen.lockmode,
@@ -649,6 +666,14 @@ initialize_reloptions(void)
j++;
}
+ for (i = 0; ternaryRelOpts[i].gen.name; i++)
+ {
+ relOpts[j] = &ternaryRelOpts[i].gen;
+ relOpts[j]->type = RELOPT_TYPE_TERNARY;
+ relOpts[j]->namelen = strlen(relOpts[j]->name);
+ j++;
+ }
+
for (i = 0; intRelOpts[i].gen.name; i++)
{
relOpts[j] = &intRelOpts[i].gen;
@@ -809,6 +834,9 @@ allocate_reloption(bits32 kinds, int type, const char *name, const char *desc,
case RELOPT_TYPE_BOOL:
size = sizeof(relopt_bool);
break;
+ case RELOPT_TYPE_TERNARY:
+ size = sizeof(relopt_ternary);
+ break;
case RELOPT_TYPE_INT:
size = sizeof(relopt_int);
break;
@@ -892,6 +920,57 @@ add_local_bool_reloption(local_relopts *relopts, const char *name,
add_local_reloption(relopts, (relopt_gen *) newoption, offset);
}
+/*
+ * init_ternary_reloption
+ * Allocate and initialize a new ternary reloption
+ */
+static relopt_ternary *
+init_ternary_reloption(bits32 kinds, const char *name, const char *desc,
+ pg_ternary default_val, LOCKMODE lockmode)
+{
+ relopt_ternary *newoption;
+
+ newoption = (relopt_ternary *)
+ allocate_reloption(kinds, RELOPT_TYPE_TERNARY, name, desc, lockmode);
+ newoption->default_val = default_val;
+
+ return newoption;
+}
+
+/*
+ * add_ternary_reloption
+ * Add a new ternary reloption
+ */
+void
+add_ternary_reloption(bits32 kinds, const char *name, const char *desc,
+ pg_ternary default_val, LOCKMODE lockmode)
+{
+ relopt_ternary *newoption;
+
+ newoption =
+ init_ternary_reloption(kinds, name, desc, default_val, lockmode);
+
+ add_reloption((relopt_gen *) newoption);
+}
+
+/*
+ * add_local_ternary_reloption
+ * Add a new ternary local reloption
+ *
+ * 'offset' is offset of ternary-typed field.
+ */
+void
+add_local_ternary_reloption(local_relopts *relopts, const char *name,
+ const char *desc, pg_ternary default_val,
+ int offset)
+{
+ relopt_ternary *newoption;
+
+ newoption =
+ init_ternary_reloption(RELOPT_KIND_LOCAL, name, desc, default_val, 0);
+
+ add_local_reloption(relopts, (relopt_gen *) newoption, offset);
+}
/*
* init_real_reloption
@@ -1626,6 +1705,19 @@ parse_one_reloption(relopt_value *option, char *text_str, int text_len,
option->gen->name, value)));
}
break;
+ case RELOPT_TYPE_TERNARY:
+ {
+ bool b;
+
+ parsed = parse_bool(value, &b);
+ option->values.ternary_val = b ? TERNARY_TRUE : TERNARY_FALSE;
+ if (validate && !parsed)
+ ereport(ERROR,
+ (errcode(ERRCODE_INVALID_PARAMETER_VALUE),
+ errmsg("invalid value for ternary option \"%s\": %s",
+ option->gen->name, value)));
+ }
+ break;
case RELOPT_TYPE_INT:
{
relopt_int *optint = (relopt_int *) option->gen;
@@ -1789,17 +1881,6 @@ fillRelOptions(void *rdopts, Size basesize,
char *itempos = ((char *) rdopts) + elems[j].offset;
char *string_val;
- /*
- * If isset_offset is provided, store whether the reloption is
- * set there.
- */
- if (elems[j].isset_offset > 0)
- {
- char *setpos = ((char *) rdopts) + elems[j].isset_offset;
-
- *(bool *) setpos = options[i].isset;
- }
-
switch (options[i].gen->type)
{
case RELOPT_TYPE_BOOL:
@@ -1807,6 +1888,11 @@ fillRelOptions(void *rdopts, Size basesize,
options[i].values.bool_val :
((relopt_bool *) options[i].gen)->default_val;
break;
+ case RELOPT_TYPE_TERNARY:
+ *(pg_ternary *) itempos = options[i].isset ?
+ options[i].values.ternary_val :
+ ((relopt_ternary *) options[i].gen)->default_val;
+ break;
case RELOPT_TYPE_INT:
*(int *) itempos = options[i].isset ?
options[i].values.int_val :
@@ -1923,8 +2009,8 @@ default_reloptions(Datum reloptions, bool validate, relopt_kind kind)
offsetof(StdRdOptions, parallel_workers)},
{"vacuum_index_cleanup", RELOPT_TYPE_ENUM,
offsetof(StdRdOptions, vacuum_index_cleanup)},
- {"vacuum_truncate", RELOPT_TYPE_BOOL,
- offsetof(StdRdOptions, vacuum_truncate), offsetof(StdRdOptions, vacuum_truncate_set)},
+ {"vacuum_truncate", RELOPT_TYPE_TERNARY,
+ offsetof(StdRdOptions, vacuum_truncate)},
{"vacuum_max_eager_freeze_failure_rate", RELOPT_TYPE_REAL,
offsetof(StdRdOptions, vacuum_max_eager_freeze_failure_rate)}
};
@@ -2004,7 +2090,6 @@ build_local_reloptions(local_relopts *relopts, Datum options, bool validate)
elems[i].optname = opt->option->name;
elems[i].opttype = opt->option->type;
elems[i].offset = opt->offset;
- elems[i].isset_offset = 0; /* not supported for local relopts yet */
i++;
}
diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c
index aa4fbec143f..696eab9bd97 100644
--- a/src/backend/commands/vacuum.c
+++ b/src/backend/commands/vacuum.c
@@ -2224,9 +2224,9 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams params,
{
StdRdOptions *opts = (StdRdOptions *) rel->rd_options;
- if (opts && opts->vacuum_truncate_set)
+ if (opts && opts->vacuum_truncate != TERNARY_UNSET)
{
- if (opts->vacuum_truncate)
+ if (opts->vacuum_truncate == TERNARY_TRUE)
params.truncate = VACOPTVALUE_ENABLED;
else
params.truncate = VACOPTVALUE_DISABLED;
diff --git a/src/include/access/reloptions.h b/src/include/access/reloptions.h
index 2f08e1b0cf0..dfbef2babf2 100644
--- a/src/include/access/reloptions.h
+++ b/src/include/access/reloptions.h
@@ -29,6 +29,7 @@
typedef enum relopt_type
{
RELOPT_TYPE_BOOL,
+ RELOPT_TYPE_TERNARY, /* on, off, unset */
RELOPT_TYPE_INT,
RELOPT_TYPE_REAL,
RELOPT_TYPE_ENUM,
@@ -80,6 +81,7 @@ typedef struct relopt_value
union
{
bool bool_val;
+ pg_ternary ternary_val;
int int_val;
double real_val;
int enum_val;
@@ -94,6 +96,12 @@ typedef struct relopt_bool
bool default_val;
} relopt_bool;
+typedef struct relopt_ternary
+{
+ relopt_gen gen;
+ int default_val;
+} relopt_ternary;
+
typedef struct relopt_int
{
relopt_gen gen;
@@ -152,19 +160,6 @@ typedef struct
const char *optname; /* option's name */
relopt_type opttype; /* option's datatype */
int offset; /* offset of field in result struct */
-
- /*
- * isset_offset is an optional offset of a field in the result struct that
- * stores whether the option is explicitly set for the relation or if it
- * just picked up the default value. In most cases, this can be
- * accomplished by giving the reloption a special out-of-range default
- * value (e.g., some integer reloptions use -2), but this isn't always
- * possible. For example, a Boolean reloption cannot be given an
- * out-of-range default, so we need another way to discover the source of
- * its value. This offset is only used if given a value greater than
- * zero.
- */
- int isset_offset;
} relopt_parse_elt;
/* Local reloption definition */
@@ -195,6 +190,9 @@ typedef struct local_relopts
extern relopt_kind add_reloption_kind(void);
extern void add_bool_reloption(bits32 kinds, const char *name, const char *desc,
bool default_val, LOCKMODE lockmode);
+extern void add_ternary_reloption(bits32 kinds, const char *name,
+ const char *desc, pg_ternary default_val,
+ LOCKMODE lockmode);
extern void add_int_reloption(bits32 kinds, const char *name, const char *desc,
int default_val, int min_val, int max_val,
LOCKMODE lockmode);
@@ -214,6 +212,9 @@ extern void register_reloptions_validator(local_relopts *relopts,
extern void add_local_bool_reloption(local_relopts *relopts, const char *name,
const char *desc, bool default_val,
int offset);
+extern void add_local_ternary_reloption(local_relopts *relopts,
+ const char *name, const char *desc,
+ pg_ternary default_val, int offset);
extern void add_local_int_reloption(local_relopts *relopts, const char *name,
const char *desc, int default_val,
int min_val, int max_val, int offset);
diff --git a/src/include/postgres.h b/src/include/postgres.h
index 7d93fbce709..47c88780776 100644
--- a/src/include/postgres.h
+++ b/src/include/postgres.h
@@ -543,6 +543,21 @@ Float8GetDatum(float8 X)
* ----------------------------------------------------------------
*/
+/*
+ * pg_ternary
+ * Boolean value with an extra "unset" value
+ *
+ * This enum can be used for values that want to distinguish between true,
+ * false, and unset.
+*/
+
+typedef enum pg_ternary
+{
+ TERNARY_FALSE = 0,
+ TERNARY_TRUE = 1,
+ TERNARY_UNSET = -1
+} pg_ternary;
+
/*
* NON_EXEC_STATIC: It's sometimes useful to define a variable or function
* that is normally static but extern when using EXEC_BACKEND (see
diff --git a/src/include/utils/rel.h b/src/include/utils/rel.h
index d03ab247788..5feb18a5373 100644
--- a/src/include/utils/rel.h
+++ b/src/include/utils/rel.h
@@ -347,8 +347,7 @@ typedef struct StdRdOptions
bool user_catalog_table; /* use as an additional catalog relation */
int parallel_workers; /* max number of parallel workers */
StdRdOptIndexCleanup vacuum_index_cleanup; /* controls index vacuuming */
- bool vacuum_truncate; /* enables vacuum to truncate a relation */
- bool vacuum_truncate_set; /* whether vacuum_truncate is set */
+ pg_ternary vacuum_truncate; /* enables vacuum to truncate a relation */
/*
* Fraction of pages in a relation that vacuum can eagerly scan and fail
diff --git a/src/test/regress/expected/reloptions.out b/src/test/regress/expected/reloptions.out
index 9de19b4e3f1..1c99f79ab01 100644
--- a/src/test/regress/expected/reloptions.out
+++ b/src/test/regress/expected/reloptions.out
@@ -98,6 +98,42 @@ SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass;
{fillfactor=13,autovacuum_enabled=false}
(1 row)
+-- Tests for future (FIXME) ternary options
+-- behave as boolean option: accept unassigned name and truncated value
+DROP TABLE reloptions_test;
+CREATE TABLE reloptions_test(i INT) WITH (vacuum_truncate);
+SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass;
+ reloptions
+------------------------
+ {vacuum_truncate=true}
+(1 row)
+
+DROP TABLE reloptions_test;
+CREATE TABLE reloptions_test(i INT) WITH (vacuum_truncate=FaLS);
+SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass;
+ reloptions
+------------------------
+ {vacuum_truncate=fals}
+(1 row)
+
+-- preferred "true" alias is used when storing
+DROP TABLE reloptions_test;
+CREATE TABLE reloptions_test(i INT) WITH (vacuum_index_cleanup=on);
+SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass;
+ reloptions
+---------------------------
+ {vacuum_index_cleanup=on}
+(1 row)
+
+-- custom "third" value is available
+DROP TABLE reloptions_test;
+CREATE TABLE reloptions_test(i INT) WITH (vacuum_index_cleanup=auto);
+SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass;
+ reloptions
+-----------------------------
+ {vacuum_index_cleanup=auto}
+(1 row)
+
-- Test vacuum_truncate option
DROP TABLE reloptions_test;
CREATE TEMP TABLE reloptions_test(i INT NOT NULL, j text)
diff --git a/src/test/regress/sql/reloptions.sql b/src/test/regress/sql/reloptions.sql
index 24fbe0b478d..f5980dafcbc 100644
--- a/src/test/regress/sql/reloptions.sql
+++ b/src/test/regress/sql/reloptions.sql
@@ -59,6 +59,27 @@ UPDATE pg_class
ALTER TABLE reloptions_test RESET (illegal_option);
SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass;
+-- Tests for future (FIXME) ternary options
+
+-- behave as boolean option: accept unassigned name and truncated value
+DROP TABLE reloptions_test;
+CREATE TABLE reloptions_test(i INT) WITH (vacuum_truncate);
+SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass;
+
+DROP TABLE reloptions_test;
+CREATE TABLE reloptions_test(i INT) WITH (vacuum_truncate=FaLS);
+SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass;
+
+-- preferred "true" alias is used when storing
+DROP TABLE reloptions_test;
+CREATE TABLE reloptions_test(i INT) WITH (vacuum_index_cleanup=on);
+SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass;
+
+-- custom "third" value is available
+DROP TABLE reloptions_test;
+CREATE TABLE reloptions_test(i INT) WITH (vacuum_index_cleanup=auto);
+SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass;
+
-- Test vacuum_truncate option
DROP TABLE reloptions_test;
diff --git a/src/tools/pgindent/typedefs.list b/src/tools/pgindent/typedefs.list
index 3f3a888fd0e..1c8610fd46c 100644
--- a/src/tools/pgindent/typedefs.list
+++ b/src/tools/pgindent/typedefs.list
@@ -3952,6 +3952,7 @@ pg_sha512_ctx
pg_snapshot
pg_special_case
pg_stack_base_t
+pg_ternary
pg_time_t
pg_time_usec_t
pg_tz
@@ -4079,6 +4080,7 @@ relopt_kind
relopt_parse_elt
relopt_real
relopt_string
+relopt_ternary
relopt_type
relopt_value
relopts_validator
--
2.47.3
--c36t3fndxhkzbl5z--
^ permalink raw reply [nested|flat] 868+ messages in thread
* [PATCH v3] Introduce ternary reloptions
@ 2026-01-16 15:04 Álvaro Herrera <[email protected]>
0 siblings, 0 replies; 868+ messages in thread
From: Álvaro Herrera @ 2026-01-16 15:04 UTC (permalink / raw)
Introduce ternary reloption as a replacement for current
`vacuum_truncate` implementation. Remove the `vacuum_truncate_set`
separate flag and use TERNARY_UNSET instead.
This could also be used for other options such as `vacuum_index_cleanup`
and `buffering`, but lets get the scaffolding in first.
Discussion: https://postgr.es/m/3474141.usfYGdeWWP@thinkpad-pgpro
---
src/backend/access/common/reloptions.c | 137 ++++++++++++++++++-----
src/backend/commands/vacuum.c | 4 +-
src/include/access/reloptions.h | 27 ++---
src/include/postgres.h | 15 +++
src/include/utils/rel.h | 3 +-
src/test/regress/expected/reloptions.out | 36 ++++++
src/test/regress/sql/reloptions.sql | 21 ++++
src/tools/pgindent/typedefs.list | 2 +
8 files changed, 202 insertions(+), 43 deletions(-)
diff --git a/src/backend/access/common/reloptions.c b/src/backend/access/common/reloptions.c
index 0b83f98ed5f..6f1f577581a 100644
--- a/src/backend/access/common/reloptions.c
+++ b/src/backend/access/common/reloptions.c
@@ -40,9 +40,9 @@
*
* To add an option:
*
- * (i) decide on a type (bool, integer, real, enum, string), name, default
- * value, upper and lower bounds (if applicable); for strings, consider a
- * validation routine.
+ * (i) decide on a type (bool, ternary, integer, real, enum, string), name,
+ * default value, upper and lower bounds (if applicable); for strings,
+ * consider a validation routine.
* (ii) add a record below (or use add_<type>_reloption).
* (iii) add it to the appropriate options struct (perhaps StdRdOptions)
* (iv) add it to the appropriate handling routine (perhaps
@@ -147,15 +147,6 @@ static relopt_bool boolRelOpts[] =
},
false
},
- {
- {
- "vacuum_truncate",
- "Enables vacuum to truncate empty pages at the end of this table",
- RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
- ShareUpdateExclusiveLock
- },
- true
- },
{
{
"deduplicate_items",
@@ -170,6 +161,25 @@ static relopt_bool boolRelOpts[] =
{{NULL}}
};
+static relopt_ternary ternaryRelOpts[] =
+{
+ {
+ {
+ "vacuum_truncate",
+ "Enables vacuum to truncate empty pages at the end of this table",
+ RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
+ ShareUpdateExclusiveLock
+ },
+ TERNARY_UNSET
+ },
+ /* list terminator */
+ {
+ {
+ NULL
+ }
+ }
+};
+
static relopt_int intRelOpts[] =
{
{
@@ -609,6 +619,13 @@ initialize_reloptions(void)
boolRelOpts[i].gen.lockmode));
j++;
}
+ for (i = 0; ternaryRelOpts[i].gen.name; i++)
+ {
+ Assert(DoLockModesConflict(ternaryRelOpts[i].gen.lockmode,
+ ternaryRelOpts[i].gen.lockmode));
+ j++;
+ }
+
for (i = 0; intRelOpts[i].gen.name; i++)
{
Assert(DoLockModesConflict(intRelOpts[i].gen.lockmode,
@@ -649,6 +666,14 @@ initialize_reloptions(void)
j++;
}
+ for (i = 0; ternaryRelOpts[i].gen.name; i++)
+ {
+ relOpts[j] = &ternaryRelOpts[i].gen;
+ relOpts[j]->type = RELOPT_TYPE_TERNARY;
+ relOpts[j]->namelen = strlen(relOpts[j]->name);
+ j++;
+ }
+
for (i = 0; intRelOpts[i].gen.name; i++)
{
relOpts[j] = &intRelOpts[i].gen;
@@ -809,6 +834,9 @@ allocate_reloption(bits32 kinds, int type, const char *name, const char *desc,
case RELOPT_TYPE_BOOL:
size = sizeof(relopt_bool);
break;
+ case RELOPT_TYPE_TERNARY:
+ size = sizeof(relopt_ternary);
+ break;
case RELOPT_TYPE_INT:
size = sizeof(relopt_int);
break;
@@ -892,6 +920,57 @@ add_local_bool_reloption(local_relopts *relopts, const char *name,
add_local_reloption(relopts, (relopt_gen *) newoption, offset);
}
+/*
+ * init_ternary_reloption
+ * Allocate and initialize a new ternary reloption
+ */
+static relopt_ternary *
+init_ternary_reloption(bits32 kinds, const char *name, const char *desc,
+ pg_ternary default_val, LOCKMODE lockmode)
+{
+ relopt_ternary *newoption;
+
+ newoption = (relopt_ternary *)
+ allocate_reloption(kinds, RELOPT_TYPE_TERNARY, name, desc, lockmode);
+ newoption->default_val = default_val;
+
+ return newoption;
+}
+
+/*
+ * add_ternary_reloption
+ * Add a new ternary reloption
+ */
+void
+add_ternary_reloption(bits32 kinds, const char *name, const char *desc,
+ pg_ternary default_val, LOCKMODE lockmode)
+{
+ relopt_ternary *newoption;
+
+ newoption =
+ init_ternary_reloption(kinds, name, desc, default_val, lockmode);
+
+ add_reloption((relopt_gen *) newoption);
+}
+
+/*
+ * add_local_ternary_reloption
+ * Add a new ternary local reloption
+ *
+ * 'offset' is offset of ternary-typed field.
+ */
+void
+add_local_ternary_reloption(local_relopts *relopts, const char *name,
+ const char *desc, pg_ternary default_val,
+ int offset)
+{
+ relopt_ternary *newoption;
+
+ newoption =
+ init_ternary_reloption(RELOPT_KIND_LOCAL, name, desc, default_val, 0);
+
+ add_local_reloption(relopts, (relopt_gen *) newoption, offset);
+}
/*
* init_real_reloption
@@ -1626,6 +1705,19 @@ parse_one_reloption(relopt_value *option, char *text_str, int text_len,
option->gen->name, value)));
}
break;
+ case RELOPT_TYPE_TERNARY:
+ {
+ bool b;
+
+ parsed = parse_bool(value, &b);
+ option->values.ternary_val = b ? TERNARY_TRUE : TERNARY_FALSE;
+ if (validate && !parsed)
+ ereport(ERROR,
+ (errcode(ERRCODE_INVALID_PARAMETER_VALUE),
+ errmsg("invalid value for ternary option \"%s\": %s",
+ option->gen->name, value)));
+ }
+ break;
case RELOPT_TYPE_INT:
{
relopt_int *optint = (relopt_int *) option->gen;
@@ -1789,17 +1881,6 @@ fillRelOptions(void *rdopts, Size basesize,
char *itempos = ((char *) rdopts) + elems[j].offset;
char *string_val;
- /*
- * If isset_offset is provided, store whether the reloption is
- * set there.
- */
- if (elems[j].isset_offset > 0)
- {
- char *setpos = ((char *) rdopts) + elems[j].isset_offset;
-
- *(bool *) setpos = options[i].isset;
- }
-
switch (options[i].gen->type)
{
case RELOPT_TYPE_BOOL:
@@ -1807,6 +1888,11 @@ fillRelOptions(void *rdopts, Size basesize,
options[i].values.bool_val :
((relopt_bool *) options[i].gen)->default_val;
break;
+ case RELOPT_TYPE_TERNARY:
+ *(pg_ternary *) itempos = options[i].isset ?
+ options[i].values.ternary_val :
+ ((relopt_ternary *) options[i].gen)->default_val;
+ break;
case RELOPT_TYPE_INT:
*(int *) itempos = options[i].isset ?
options[i].values.int_val :
@@ -1923,8 +2009,8 @@ default_reloptions(Datum reloptions, bool validate, relopt_kind kind)
offsetof(StdRdOptions, parallel_workers)},
{"vacuum_index_cleanup", RELOPT_TYPE_ENUM,
offsetof(StdRdOptions, vacuum_index_cleanup)},
- {"vacuum_truncate", RELOPT_TYPE_BOOL,
- offsetof(StdRdOptions, vacuum_truncate), offsetof(StdRdOptions, vacuum_truncate_set)},
+ {"vacuum_truncate", RELOPT_TYPE_TERNARY,
+ offsetof(StdRdOptions, vacuum_truncate)},
{"vacuum_max_eager_freeze_failure_rate", RELOPT_TYPE_REAL,
offsetof(StdRdOptions, vacuum_max_eager_freeze_failure_rate)}
};
@@ -2004,7 +2090,6 @@ build_local_reloptions(local_relopts *relopts, Datum options, bool validate)
elems[i].optname = opt->option->name;
elems[i].opttype = opt->option->type;
elems[i].offset = opt->offset;
- elems[i].isset_offset = 0; /* not supported for local relopts yet */
i++;
}
diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c
index aa4fbec143f..696eab9bd97 100644
--- a/src/backend/commands/vacuum.c
+++ b/src/backend/commands/vacuum.c
@@ -2224,9 +2224,9 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams params,
{
StdRdOptions *opts = (StdRdOptions *) rel->rd_options;
- if (opts && opts->vacuum_truncate_set)
+ if (opts && opts->vacuum_truncate != TERNARY_UNSET)
{
- if (opts->vacuum_truncate)
+ if (opts->vacuum_truncate == TERNARY_TRUE)
params.truncate = VACOPTVALUE_ENABLED;
else
params.truncate = VACOPTVALUE_DISABLED;
diff --git a/src/include/access/reloptions.h b/src/include/access/reloptions.h
index 2f08e1b0cf0..dfbef2babf2 100644
--- a/src/include/access/reloptions.h
+++ b/src/include/access/reloptions.h
@@ -29,6 +29,7 @@
typedef enum relopt_type
{
RELOPT_TYPE_BOOL,
+ RELOPT_TYPE_TERNARY, /* on, off, unset */
RELOPT_TYPE_INT,
RELOPT_TYPE_REAL,
RELOPT_TYPE_ENUM,
@@ -80,6 +81,7 @@ typedef struct relopt_value
union
{
bool bool_val;
+ pg_ternary ternary_val;
int int_val;
double real_val;
int enum_val;
@@ -94,6 +96,12 @@ typedef struct relopt_bool
bool default_val;
} relopt_bool;
+typedef struct relopt_ternary
+{
+ relopt_gen gen;
+ int default_val;
+} relopt_ternary;
+
typedef struct relopt_int
{
relopt_gen gen;
@@ -152,19 +160,6 @@ typedef struct
const char *optname; /* option's name */
relopt_type opttype; /* option's datatype */
int offset; /* offset of field in result struct */
-
- /*
- * isset_offset is an optional offset of a field in the result struct that
- * stores whether the option is explicitly set for the relation or if it
- * just picked up the default value. In most cases, this can be
- * accomplished by giving the reloption a special out-of-range default
- * value (e.g., some integer reloptions use -2), but this isn't always
- * possible. For example, a Boolean reloption cannot be given an
- * out-of-range default, so we need another way to discover the source of
- * its value. This offset is only used if given a value greater than
- * zero.
- */
- int isset_offset;
} relopt_parse_elt;
/* Local reloption definition */
@@ -195,6 +190,9 @@ typedef struct local_relopts
extern relopt_kind add_reloption_kind(void);
extern void add_bool_reloption(bits32 kinds, const char *name, const char *desc,
bool default_val, LOCKMODE lockmode);
+extern void add_ternary_reloption(bits32 kinds, const char *name,
+ const char *desc, pg_ternary default_val,
+ LOCKMODE lockmode);
extern void add_int_reloption(bits32 kinds, const char *name, const char *desc,
int default_val, int min_val, int max_val,
LOCKMODE lockmode);
@@ -214,6 +212,9 @@ extern void register_reloptions_validator(local_relopts *relopts,
extern void add_local_bool_reloption(local_relopts *relopts, const char *name,
const char *desc, bool default_val,
int offset);
+extern void add_local_ternary_reloption(local_relopts *relopts,
+ const char *name, const char *desc,
+ pg_ternary default_val, int offset);
extern void add_local_int_reloption(local_relopts *relopts, const char *name,
const char *desc, int default_val,
int min_val, int max_val, int offset);
diff --git a/src/include/postgres.h b/src/include/postgres.h
index 7d93fbce709..47c88780776 100644
--- a/src/include/postgres.h
+++ b/src/include/postgres.h
@@ -543,6 +543,21 @@ Float8GetDatum(float8 X)
* ----------------------------------------------------------------
*/
+/*
+ * pg_ternary
+ * Boolean value with an extra "unset" value
+ *
+ * This enum can be used for values that want to distinguish between true,
+ * false, and unset.
+*/
+
+typedef enum pg_ternary
+{
+ TERNARY_FALSE = 0,
+ TERNARY_TRUE = 1,
+ TERNARY_UNSET = -1
+} pg_ternary;
+
/*
* NON_EXEC_STATIC: It's sometimes useful to define a variable or function
* that is normally static but extern when using EXEC_BACKEND (see
diff --git a/src/include/utils/rel.h b/src/include/utils/rel.h
index d03ab247788..5feb18a5373 100644
--- a/src/include/utils/rel.h
+++ b/src/include/utils/rel.h
@@ -347,8 +347,7 @@ typedef struct StdRdOptions
bool user_catalog_table; /* use as an additional catalog relation */
int parallel_workers; /* max number of parallel workers */
StdRdOptIndexCleanup vacuum_index_cleanup; /* controls index vacuuming */
- bool vacuum_truncate; /* enables vacuum to truncate a relation */
- bool vacuum_truncate_set; /* whether vacuum_truncate is set */
+ pg_ternary vacuum_truncate; /* enables vacuum to truncate a relation */
/*
* Fraction of pages in a relation that vacuum can eagerly scan and fail
diff --git a/src/test/regress/expected/reloptions.out b/src/test/regress/expected/reloptions.out
index 9de19b4e3f1..1c99f79ab01 100644
--- a/src/test/regress/expected/reloptions.out
+++ b/src/test/regress/expected/reloptions.out
@@ -98,6 +98,42 @@ SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass;
{fillfactor=13,autovacuum_enabled=false}
(1 row)
+-- Tests for future (FIXME) ternary options
+-- behave as boolean option: accept unassigned name and truncated value
+DROP TABLE reloptions_test;
+CREATE TABLE reloptions_test(i INT) WITH (vacuum_truncate);
+SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass;
+ reloptions
+------------------------
+ {vacuum_truncate=true}
+(1 row)
+
+DROP TABLE reloptions_test;
+CREATE TABLE reloptions_test(i INT) WITH (vacuum_truncate=FaLS);
+SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass;
+ reloptions
+------------------------
+ {vacuum_truncate=fals}
+(1 row)
+
+-- preferred "true" alias is used when storing
+DROP TABLE reloptions_test;
+CREATE TABLE reloptions_test(i INT) WITH (vacuum_index_cleanup=on);
+SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass;
+ reloptions
+---------------------------
+ {vacuum_index_cleanup=on}
+(1 row)
+
+-- custom "third" value is available
+DROP TABLE reloptions_test;
+CREATE TABLE reloptions_test(i INT) WITH (vacuum_index_cleanup=auto);
+SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass;
+ reloptions
+-----------------------------
+ {vacuum_index_cleanup=auto}
+(1 row)
+
-- Test vacuum_truncate option
DROP TABLE reloptions_test;
CREATE TEMP TABLE reloptions_test(i INT NOT NULL, j text)
diff --git a/src/test/regress/sql/reloptions.sql b/src/test/regress/sql/reloptions.sql
index 24fbe0b478d..f5980dafcbc 100644
--- a/src/test/regress/sql/reloptions.sql
+++ b/src/test/regress/sql/reloptions.sql
@@ -59,6 +59,27 @@ UPDATE pg_class
ALTER TABLE reloptions_test RESET (illegal_option);
SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass;
+-- Tests for future (FIXME) ternary options
+
+-- behave as boolean option: accept unassigned name and truncated value
+DROP TABLE reloptions_test;
+CREATE TABLE reloptions_test(i INT) WITH (vacuum_truncate);
+SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass;
+
+DROP TABLE reloptions_test;
+CREATE TABLE reloptions_test(i INT) WITH (vacuum_truncate=FaLS);
+SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass;
+
+-- preferred "true" alias is used when storing
+DROP TABLE reloptions_test;
+CREATE TABLE reloptions_test(i INT) WITH (vacuum_index_cleanup=on);
+SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass;
+
+-- custom "third" value is available
+DROP TABLE reloptions_test;
+CREATE TABLE reloptions_test(i INT) WITH (vacuum_index_cleanup=auto);
+SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass;
+
-- Test vacuum_truncate option
DROP TABLE reloptions_test;
diff --git a/src/tools/pgindent/typedefs.list b/src/tools/pgindent/typedefs.list
index 3f3a888fd0e..1c8610fd46c 100644
--- a/src/tools/pgindent/typedefs.list
+++ b/src/tools/pgindent/typedefs.list
@@ -3952,6 +3952,7 @@ pg_sha512_ctx
pg_snapshot
pg_special_case
pg_stack_base_t
+pg_ternary
pg_time_t
pg_time_usec_t
pg_tz
@@ -4079,6 +4080,7 @@ relopt_kind
relopt_parse_elt
relopt_real
relopt_string
+relopt_ternary
relopt_type
relopt_value
relopts_validator
--
2.47.3
--c36t3fndxhkzbl5z--
^ permalink raw reply [nested|flat] 868+ messages in thread
* [PATCH v3] Introduce ternary reloptions
@ 2026-01-16 15:04 Álvaro Herrera <[email protected]>
0 siblings, 0 replies; 868+ messages in thread
From: Álvaro Herrera @ 2026-01-16 15:04 UTC (permalink / raw)
Introduce ternary reloption as a replacement for current
`vacuum_truncate` implementation. Remove the `vacuum_truncate_set`
separate flag and use TERNARY_UNSET instead.
This could also be used for other options such as `vacuum_index_cleanup`
and `buffering`, but lets get the scaffolding in first.
Discussion: https://postgr.es/m/3474141.usfYGdeWWP@thinkpad-pgpro
---
src/backend/access/common/reloptions.c | 137 ++++++++++++++++++-----
src/backend/commands/vacuum.c | 4 +-
src/include/access/reloptions.h | 27 ++---
src/include/postgres.h | 15 +++
src/include/utils/rel.h | 3 +-
src/test/regress/expected/reloptions.out | 36 ++++++
src/test/regress/sql/reloptions.sql | 21 ++++
src/tools/pgindent/typedefs.list | 2 +
8 files changed, 202 insertions(+), 43 deletions(-)
diff --git a/src/backend/access/common/reloptions.c b/src/backend/access/common/reloptions.c
index 0b83f98ed5f..6f1f577581a 100644
--- a/src/backend/access/common/reloptions.c
+++ b/src/backend/access/common/reloptions.c
@@ -40,9 +40,9 @@
*
* To add an option:
*
- * (i) decide on a type (bool, integer, real, enum, string), name, default
- * value, upper and lower bounds (if applicable); for strings, consider a
- * validation routine.
+ * (i) decide on a type (bool, ternary, integer, real, enum, string), name,
+ * default value, upper and lower bounds (if applicable); for strings,
+ * consider a validation routine.
* (ii) add a record below (or use add_<type>_reloption).
* (iii) add it to the appropriate options struct (perhaps StdRdOptions)
* (iv) add it to the appropriate handling routine (perhaps
@@ -147,15 +147,6 @@ static relopt_bool boolRelOpts[] =
},
false
},
- {
- {
- "vacuum_truncate",
- "Enables vacuum to truncate empty pages at the end of this table",
- RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
- ShareUpdateExclusiveLock
- },
- true
- },
{
{
"deduplicate_items",
@@ -170,6 +161,25 @@ static relopt_bool boolRelOpts[] =
{{NULL}}
};
+static relopt_ternary ternaryRelOpts[] =
+{
+ {
+ {
+ "vacuum_truncate",
+ "Enables vacuum to truncate empty pages at the end of this table",
+ RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
+ ShareUpdateExclusiveLock
+ },
+ TERNARY_UNSET
+ },
+ /* list terminator */
+ {
+ {
+ NULL
+ }
+ }
+};
+
static relopt_int intRelOpts[] =
{
{
@@ -609,6 +619,13 @@ initialize_reloptions(void)
boolRelOpts[i].gen.lockmode));
j++;
}
+ for (i = 0; ternaryRelOpts[i].gen.name; i++)
+ {
+ Assert(DoLockModesConflict(ternaryRelOpts[i].gen.lockmode,
+ ternaryRelOpts[i].gen.lockmode));
+ j++;
+ }
+
for (i = 0; intRelOpts[i].gen.name; i++)
{
Assert(DoLockModesConflict(intRelOpts[i].gen.lockmode,
@@ -649,6 +666,14 @@ initialize_reloptions(void)
j++;
}
+ for (i = 0; ternaryRelOpts[i].gen.name; i++)
+ {
+ relOpts[j] = &ternaryRelOpts[i].gen;
+ relOpts[j]->type = RELOPT_TYPE_TERNARY;
+ relOpts[j]->namelen = strlen(relOpts[j]->name);
+ j++;
+ }
+
for (i = 0; intRelOpts[i].gen.name; i++)
{
relOpts[j] = &intRelOpts[i].gen;
@@ -809,6 +834,9 @@ allocate_reloption(bits32 kinds, int type, const char *name, const char *desc,
case RELOPT_TYPE_BOOL:
size = sizeof(relopt_bool);
break;
+ case RELOPT_TYPE_TERNARY:
+ size = sizeof(relopt_ternary);
+ break;
case RELOPT_TYPE_INT:
size = sizeof(relopt_int);
break;
@@ -892,6 +920,57 @@ add_local_bool_reloption(local_relopts *relopts, const char *name,
add_local_reloption(relopts, (relopt_gen *) newoption, offset);
}
+/*
+ * init_ternary_reloption
+ * Allocate and initialize a new ternary reloption
+ */
+static relopt_ternary *
+init_ternary_reloption(bits32 kinds, const char *name, const char *desc,
+ pg_ternary default_val, LOCKMODE lockmode)
+{
+ relopt_ternary *newoption;
+
+ newoption = (relopt_ternary *)
+ allocate_reloption(kinds, RELOPT_TYPE_TERNARY, name, desc, lockmode);
+ newoption->default_val = default_val;
+
+ return newoption;
+}
+
+/*
+ * add_ternary_reloption
+ * Add a new ternary reloption
+ */
+void
+add_ternary_reloption(bits32 kinds, const char *name, const char *desc,
+ pg_ternary default_val, LOCKMODE lockmode)
+{
+ relopt_ternary *newoption;
+
+ newoption =
+ init_ternary_reloption(kinds, name, desc, default_val, lockmode);
+
+ add_reloption((relopt_gen *) newoption);
+}
+
+/*
+ * add_local_ternary_reloption
+ * Add a new ternary local reloption
+ *
+ * 'offset' is offset of ternary-typed field.
+ */
+void
+add_local_ternary_reloption(local_relopts *relopts, const char *name,
+ const char *desc, pg_ternary default_val,
+ int offset)
+{
+ relopt_ternary *newoption;
+
+ newoption =
+ init_ternary_reloption(RELOPT_KIND_LOCAL, name, desc, default_val, 0);
+
+ add_local_reloption(relopts, (relopt_gen *) newoption, offset);
+}
/*
* init_real_reloption
@@ -1626,6 +1705,19 @@ parse_one_reloption(relopt_value *option, char *text_str, int text_len,
option->gen->name, value)));
}
break;
+ case RELOPT_TYPE_TERNARY:
+ {
+ bool b;
+
+ parsed = parse_bool(value, &b);
+ option->values.ternary_val = b ? TERNARY_TRUE : TERNARY_FALSE;
+ if (validate && !parsed)
+ ereport(ERROR,
+ (errcode(ERRCODE_INVALID_PARAMETER_VALUE),
+ errmsg("invalid value for ternary option \"%s\": %s",
+ option->gen->name, value)));
+ }
+ break;
case RELOPT_TYPE_INT:
{
relopt_int *optint = (relopt_int *) option->gen;
@@ -1789,17 +1881,6 @@ fillRelOptions(void *rdopts, Size basesize,
char *itempos = ((char *) rdopts) + elems[j].offset;
char *string_val;
- /*
- * If isset_offset is provided, store whether the reloption is
- * set there.
- */
- if (elems[j].isset_offset > 0)
- {
- char *setpos = ((char *) rdopts) + elems[j].isset_offset;
-
- *(bool *) setpos = options[i].isset;
- }
-
switch (options[i].gen->type)
{
case RELOPT_TYPE_BOOL:
@@ -1807,6 +1888,11 @@ fillRelOptions(void *rdopts, Size basesize,
options[i].values.bool_val :
((relopt_bool *) options[i].gen)->default_val;
break;
+ case RELOPT_TYPE_TERNARY:
+ *(pg_ternary *) itempos = options[i].isset ?
+ options[i].values.ternary_val :
+ ((relopt_ternary *) options[i].gen)->default_val;
+ break;
case RELOPT_TYPE_INT:
*(int *) itempos = options[i].isset ?
options[i].values.int_val :
@@ -1923,8 +2009,8 @@ default_reloptions(Datum reloptions, bool validate, relopt_kind kind)
offsetof(StdRdOptions, parallel_workers)},
{"vacuum_index_cleanup", RELOPT_TYPE_ENUM,
offsetof(StdRdOptions, vacuum_index_cleanup)},
- {"vacuum_truncate", RELOPT_TYPE_BOOL,
- offsetof(StdRdOptions, vacuum_truncate), offsetof(StdRdOptions, vacuum_truncate_set)},
+ {"vacuum_truncate", RELOPT_TYPE_TERNARY,
+ offsetof(StdRdOptions, vacuum_truncate)},
{"vacuum_max_eager_freeze_failure_rate", RELOPT_TYPE_REAL,
offsetof(StdRdOptions, vacuum_max_eager_freeze_failure_rate)}
};
@@ -2004,7 +2090,6 @@ build_local_reloptions(local_relopts *relopts, Datum options, bool validate)
elems[i].optname = opt->option->name;
elems[i].opttype = opt->option->type;
elems[i].offset = opt->offset;
- elems[i].isset_offset = 0; /* not supported for local relopts yet */
i++;
}
diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c
index aa4fbec143f..696eab9bd97 100644
--- a/src/backend/commands/vacuum.c
+++ b/src/backend/commands/vacuum.c
@@ -2224,9 +2224,9 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams params,
{
StdRdOptions *opts = (StdRdOptions *) rel->rd_options;
- if (opts && opts->vacuum_truncate_set)
+ if (opts && opts->vacuum_truncate != TERNARY_UNSET)
{
- if (opts->vacuum_truncate)
+ if (opts->vacuum_truncate == TERNARY_TRUE)
params.truncate = VACOPTVALUE_ENABLED;
else
params.truncate = VACOPTVALUE_DISABLED;
diff --git a/src/include/access/reloptions.h b/src/include/access/reloptions.h
index 2f08e1b0cf0..dfbef2babf2 100644
--- a/src/include/access/reloptions.h
+++ b/src/include/access/reloptions.h
@@ -29,6 +29,7 @@
typedef enum relopt_type
{
RELOPT_TYPE_BOOL,
+ RELOPT_TYPE_TERNARY, /* on, off, unset */
RELOPT_TYPE_INT,
RELOPT_TYPE_REAL,
RELOPT_TYPE_ENUM,
@@ -80,6 +81,7 @@ typedef struct relopt_value
union
{
bool bool_val;
+ pg_ternary ternary_val;
int int_val;
double real_val;
int enum_val;
@@ -94,6 +96,12 @@ typedef struct relopt_bool
bool default_val;
} relopt_bool;
+typedef struct relopt_ternary
+{
+ relopt_gen gen;
+ int default_val;
+} relopt_ternary;
+
typedef struct relopt_int
{
relopt_gen gen;
@@ -152,19 +160,6 @@ typedef struct
const char *optname; /* option's name */
relopt_type opttype; /* option's datatype */
int offset; /* offset of field in result struct */
-
- /*
- * isset_offset is an optional offset of a field in the result struct that
- * stores whether the option is explicitly set for the relation or if it
- * just picked up the default value. In most cases, this can be
- * accomplished by giving the reloption a special out-of-range default
- * value (e.g., some integer reloptions use -2), but this isn't always
- * possible. For example, a Boolean reloption cannot be given an
- * out-of-range default, so we need another way to discover the source of
- * its value. This offset is only used if given a value greater than
- * zero.
- */
- int isset_offset;
} relopt_parse_elt;
/* Local reloption definition */
@@ -195,6 +190,9 @@ typedef struct local_relopts
extern relopt_kind add_reloption_kind(void);
extern void add_bool_reloption(bits32 kinds, const char *name, const char *desc,
bool default_val, LOCKMODE lockmode);
+extern void add_ternary_reloption(bits32 kinds, const char *name,
+ const char *desc, pg_ternary default_val,
+ LOCKMODE lockmode);
extern void add_int_reloption(bits32 kinds, const char *name, const char *desc,
int default_val, int min_val, int max_val,
LOCKMODE lockmode);
@@ -214,6 +212,9 @@ extern void register_reloptions_validator(local_relopts *relopts,
extern void add_local_bool_reloption(local_relopts *relopts, const char *name,
const char *desc, bool default_val,
int offset);
+extern void add_local_ternary_reloption(local_relopts *relopts,
+ const char *name, const char *desc,
+ pg_ternary default_val, int offset);
extern void add_local_int_reloption(local_relopts *relopts, const char *name,
const char *desc, int default_val,
int min_val, int max_val, int offset);
diff --git a/src/include/postgres.h b/src/include/postgres.h
index 7d93fbce709..47c88780776 100644
--- a/src/include/postgres.h
+++ b/src/include/postgres.h
@@ -543,6 +543,21 @@ Float8GetDatum(float8 X)
* ----------------------------------------------------------------
*/
+/*
+ * pg_ternary
+ * Boolean value with an extra "unset" value
+ *
+ * This enum can be used for values that want to distinguish between true,
+ * false, and unset.
+*/
+
+typedef enum pg_ternary
+{
+ TERNARY_FALSE = 0,
+ TERNARY_TRUE = 1,
+ TERNARY_UNSET = -1
+} pg_ternary;
+
/*
* NON_EXEC_STATIC: It's sometimes useful to define a variable or function
* that is normally static but extern when using EXEC_BACKEND (see
diff --git a/src/include/utils/rel.h b/src/include/utils/rel.h
index d03ab247788..5feb18a5373 100644
--- a/src/include/utils/rel.h
+++ b/src/include/utils/rel.h
@@ -347,8 +347,7 @@ typedef struct StdRdOptions
bool user_catalog_table; /* use as an additional catalog relation */
int parallel_workers; /* max number of parallel workers */
StdRdOptIndexCleanup vacuum_index_cleanup; /* controls index vacuuming */
- bool vacuum_truncate; /* enables vacuum to truncate a relation */
- bool vacuum_truncate_set; /* whether vacuum_truncate is set */
+ pg_ternary vacuum_truncate; /* enables vacuum to truncate a relation */
/*
* Fraction of pages in a relation that vacuum can eagerly scan and fail
diff --git a/src/test/regress/expected/reloptions.out b/src/test/regress/expected/reloptions.out
index 9de19b4e3f1..1c99f79ab01 100644
--- a/src/test/regress/expected/reloptions.out
+++ b/src/test/regress/expected/reloptions.out
@@ -98,6 +98,42 @@ SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass;
{fillfactor=13,autovacuum_enabled=false}
(1 row)
+-- Tests for future (FIXME) ternary options
+-- behave as boolean option: accept unassigned name and truncated value
+DROP TABLE reloptions_test;
+CREATE TABLE reloptions_test(i INT) WITH (vacuum_truncate);
+SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass;
+ reloptions
+------------------------
+ {vacuum_truncate=true}
+(1 row)
+
+DROP TABLE reloptions_test;
+CREATE TABLE reloptions_test(i INT) WITH (vacuum_truncate=FaLS);
+SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass;
+ reloptions
+------------------------
+ {vacuum_truncate=fals}
+(1 row)
+
+-- preferred "true" alias is used when storing
+DROP TABLE reloptions_test;
+CREATE TABLE reloptions_test(i INT) WITH (vacuum_index_cleanup=on);
+SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass;
+ reloptions
+---------------------------
+ {vacuum_index_cleanup=on}
+(1 row)
+
+-- custom "third" value is available
+DROP TABLE reloptions_test;
+CREATE TABLE reloptions_test(i INT) WITH (vacuum_index_cleanup=auto);
+SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass;
+ reloptions
+-----------------------------
+ {vacuum_index_cleanup=auto}
+(1 row)
+
-- Test vacuum_truncate option
DROP TABLE reloptions_test;
CREATE TEMP TABLE reloptions_test(i INT NOT NULL, j text)
diff --git a/src/test/regress/sql/reloptions.sql b/src/test/regress/sql/reloptions.sql
index 24fbe0b478d..f5980dafcbc 100644
--- a/src/test/regress/sql/reloptions.sql
+++ b/src/test/regress/sql/reloptions.sql
@@ -59,6 +59,27 @@ UPDATE pg_class
ALTER TABLE reloptions_test RESET (illegal_option);
SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass;
+-- Tests for future (FIXME) ternary options
+
+-- behave as boolean option: accept unassigned name and truncated value
+DROP TABLE reloptions_test;
+CREATE TABLE reloptions_test(i INT) WITH (vacuum_truncate);
+SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass;
+
+DROP TABLE reloptions_test;
+CREATE TABLE reloptions_test(i INT) WITH (vacuum_truncate=FaLS);
+SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass;
+
+-- preferred "true" alias is used when storing
+DROP TABLE reloptions_test;
+CREATE TABLE reloptions_test(i INT) WITH (vacuum_index_cleanup=on);
+SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass;
+
+-- custom "third" value is available
+DROP TABLE reloptions_test;
+CREATE TABLE reloptions_test(i INT) WITH (vacuum_index_cleanup=auto);
+SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass;
+
-- Test vacuum_truncate option
DROP TABLE reloptions_test;
diff --git a/src/tools/pgindent/typedefs.list b/src/tools/pgindent/typedefs.list
index 3f3a888fd0e..1c8610fd46c 100644
--- a/src/tools/pgindent/typedefs.list
+++ b/src/tools/pgindent/typedefs.list
@@ -3952,6 +3952,7 @@ pg_sha512_ctx
pg_snapshot
pg_special_case
pg_stack_base_t
+pg_ternary
pg_time_t
pg_time_usec_t
pg_tz
@@ -4079,6 +4080,7 @@ relopt_kind
relopt_parse_elt
relopt_real
relopt_string
+relopt_ternary
relopt_type
relopt_value
relopts_validator
--
2.47.3
--c36t3fndxhkzbl5z--
^ permalink raw reply [nested|flat] 868+ messages in thread
* [PATCH v3] Introduce ternary reloptions
@ 2026-01-16 15:04 Álvaro Herrera <[email protected]>
0 siblings, 0 replies; 868+ messages in thread
From: Álvaro Herrera @ 2026-01-16 15:04 UTC (permalink / raw)
Introduce ternary reloption as a replacement for current
`vacuum_truncate` implementation. Remove the `vacuum_truncate_set`
separate flag and use TERNARY_UNSET instead.
This could also be used for other options such as `vacuum_index_cleanup`
and `buffering`, but lets get the scaffolding in first.
Discussion: https://postgr.es/m/3474141.usfYGdeWWP@thinkpad-pgpro
---
src/backend/access/common/reloptions.c | 137 ++++++++++++++++++-----
src/backend/commands/vacuum.c | 4 +-
src/include/access/reloptions.h | 27 ++---
src/include/postgres.h | 15 +++
src/include/utils/rel.h | 3 +-
src/test/regress/expected/reloptions.out | 36 ++++++
src/test/regress/sql/reloptions.sql | 21 ++++
src/tools/pgindent/typedefs.list | 2 +
8 files changed, 202 insertions(+), 43 deletions(-)
diff --git a/src/backend/access/common/reloptions.c b/src/backend/access/common/reloptions.c
index 0b83f98ed5f..6f1f577581a 100644
--- a/src/backend/access/common/reloptions.c
+++ b/src/backend/access/common/reloptions.c
@@ -40,9 +40,9 @@
*
* To add an option:
*
- * (i) decide on a type (bool, integer, real, enum, string), name, default
- * value, upper and lower bounds (if applicable); for strings, consider a
- * validation routine.
+ * (i) decide on a type (bool, ternary, integer, real, enum, string), name,
+ * default value, upper and lower bounds (if applicable); for strings,
+ * consider a validation routine.
* (ii) add a record below (or use add_<type>_reloption).
* (iii) add it to the appropriate options struct (perhaps StdRdOptions)
* (iv) add it to the appropriate handling routine (perhaps
@@ -147,15 +147,6 @@ static relopt_bool boolRelOpts[] =
},
false
},
- {
- {
- "vacuum_truncate",
- "Enables vacuum to truncate empty pages at the end of this table",
- RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
- ShareUpdateExclusiveLock
- },
- true
- },
{
{
"deduplicate_items",
@@ -170,6 +161,25 @@ static relopt_bool boolRelOpts[] =
{{NULL}}
};
+static relopt_ternary ternaryRelOpts[] =
+{
+ {
+ {
+ "vacuum_truncate",
+ "Enables vacuum to truncate empty pages at the end of this table",
+ RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
+ ShareUpdateExclusiveLock
+ },
+ TERNARY_UNSET
+ },
+ /* list terminator */
+ {
+ {
+ NULL
+ }
+ }
+};
+
static relopt_int intRelOpts[] =
{
{
@@ -609,6 +619,13 @@ initialize_reloptions(void)
boolRelOpts[i].gen.lockmode));
j++;
}
+ for (i = 0; ternaryRelOpts[i].gen.name; i++)
+ {
+ Assert(DoLockModesConflict(ternaryRelOpts[i].gen.lockmode,
+ ternaryRelOpts[i].gen.lockmode));
+ j++;
+ }
+
for (i = 0; intRelOpts[i].gen.name; i++)
{
Assert(DoLockModesConflict(intRelOpts[i].gen.lockmode,
@@ -649,6 +666,14 @@ initialize_reloptions(void)
j++;
}
+ for (i = 0; ternaryRelOpts[i].gen.name; i++)
+ {
+ relOpts[j] = &ternaryRelOpts[i].gen;
+ relOpts[j]->type = RELOPT_TYPE_TERNARY;
+ relOpts[j]->namelen = strlen(relOpts[j]->name);
+ j++;
+ }
+
for (i = 0; intRelOpts[i].gen.name; i++)
{
relOpts[j] = &intRelOpts[i].gen;
@@ -809,6 +834,9 @@ allocate_reloption(bits32 kinds, int type, const char *name, const char *desc,
case RELOPT_TYPE_BOOL:
size = sizeof(relopt_bool);
break;
+ case RELOPT_TYPE_TERNARY:
+ size = sizeof(relopt_ternary);
+ break;
case RELOPT_TYPE_INT:
size = sizeof(relopt_int);
break;
@@ -892,6 +920,57 @@ add_local_bool_reloption(local_relopts *relopts, const char *name,
add_local_reloption(relopts, (relopt_gen *) newoption, offset);
}
+/*
+ * init_ternary_reloption
+ * Allocate and initialize a new ternary reloption
+ */
+static relopt_ternary *
+init_ternary_reloption(bits32 kinds, const char *name, const char *desc,
+ pg_ternary default_val, LOCKMODE lockmode)
+{
+ relopt_ternary *newoption;
+
+ newoption = (relopt_ternary *)
+ allocate_reloption(kinds, RELOPT_TYPE_TERNARY, name, desc, lockmode);
+ newoption->default_val = default_val;
+
+ return newoption;
+}
+
+/*
+ * add_ternary_reloption
+ * Add a new ternary reloption
+ */
+void
+add_ternary_reloption(bits32 kinds, const char *name, const char *desc,
+ pg_ternary default_val, LOCKMODE lockmode)
+{
+ relopt_ternary *newoption;
+
+ newoption =
+ init_ternary_reloption(kinds, name, desc, default_val, lockmode);
+
+ add_reloption((relopt_gen *) newoption);
+}
+
+/*
+ * add_local_ternary_reloption
+ * Add a new ternary local reloption
+ *
+ * 'offset' is offset of ternary-typed field.
+ */
+void
+add_local_ternary_reloption(local_relopts *relopts, const char *name,
+ const char *desc, pg_ternary default_val,
+ int offset)
+{
+ relopt_ternary *newoption;
+
+ newoption =
+ init_ternary_reloption(RELOPT_KIND_LOCAL, name, desc, default_val, 0);
+
+ add_local_reloption(relopts, (relopt_gen *) newoption, offset);
+}
/*
* init_real_reloption
@@ -1626,6 +1705,19 @@ parse_one_reloption(relopt_value *option, char *text_str, int text_len,
option->gen->name, value)));
}
break;
+ case RELOPT_TYPE_TERNARY:
+ {
+ bool b;
+
+ parsed = parse_bool(value, &b);
+ option->values.ternary_val = b ? TERNARY_TRUE : TERNARY_FALSE;
+ if (validate && !parsed)
+ ereport(ERROR,
+ (errcode(ERRCODE_INVALID_PARAMETER_VALUE),
+ errmsg("invalid value for ternary option \"%s\": %s",
+ option->gen->name, value)));
+ }
+ break;
case RELOPT_TYPE_INT:
{
relopt_int *optint = (relopt_int *) option->gen;
@@ -1789,17 +1881,6 @@ fillRelOptions(void *rdopts, Size basesize,
char *itempos = ((char *) rdopts) + elems[j].offset;
char *string_val;
- /*
- * If isset_offset is provided, store whether the reloption is
- * set there.
- */
- if (elems[j].isset_offset > 0)
- {
- char *setpos = ((char *) rdopts) + elems[j].isset_offset;
-
- *(bool *) setpos = options[i].isset;
- }
-
switch (options[i].gen->type)
{
case RELOPT_TYPE_BOOL:
@@ -1807,6 +1888,11 @@ fillRelOptions(void *rdopts, Size basesize,
options[i].values.bool_val :
((relopt_bool *) options[i].gen)->default_val;
break;
+ case RELOPT_TYPE_TERNARY:
+ *(pg_ternary *) itempos = options[i].isset ?
+ options[i].values.ternary_val :
+ ((relopt_ternary *) options[i].gen)->default_val;
+ break;
case RELOPT_TYPE_INT:
*(int *) itempos = options[i].isset ?
options[i].values.int_val :
@@ -1923,8 +2009,8 @@ default_reloptions(Datum reloptions, bool validate, relopt_kind kind)
offsetof(StdRdOptions, parallel_workers)},
{"vacuum_index_cleanup", RELOPT_TYPE_ENUM,
offsetof(StdRdOptions, vacuum_index_cleanup)},
- {"vacuum_truncate", RELOPT_TYPE_BOOL,
- offsetof(StdRdOptions, vacuum_truncate), offsetof(StdRdOptions, vacuum_truncate_set)},
+ {"vacuum_truncate", RELOPT_TYPE_TERNARY,
+ offsetof(StdRdOptions, vacuum_truncate)},
{"vacuum_max_eager_freeze_failure_rate", RELOPT_TYPE_REAL,
offsetof(StdRdOptions, vacuum_max_eager_freeze_failure_rate)}
};
@@ -2004,7 +2090,6 @@ build_local_reloptions(local_relopts *relopts, Datum options, bool validate)
elems[i].optname = opt->option->name;
elems[i].opttype = opt->option->type;
elems[i].offset = opt->offset;
- elems[i].isset_offset = 0; /* not supported for local relopts yet */
i++;
}
diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c
index aa4fbec143f..696eab9bd97 100644
--- a/src/backend/commands/vacuum.c
+++ b/src/backend/commands/vacuum.c
@@ -2224,9 +2224,9 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams params,
{
StdRdOptions *opts = (StdRdOptions *) rel->rd_options;
- if (opts && opts->vacuum_truncate_set)
+ if (opts && opts->vacuum_truncate != TERNARY_UNSET)
{
- if (opts->vacuum_truncate)
+ if (opts->vacuum_truncate == TERNARY_TRUE)
params.truncate = VACOPTVALUE_ENABLED;
else
params.truncate = VACOPTVALUE_DISABLED;
diff --git a/src/include/access/reloptions.h b/src/include/access/reloptions.h
index 2f08e1b0cf0..dfbef2babf2 100644
--- a/src/include/access/reloptions.h
+++ b/src/include/access/reloptions.h
@@ -29,6 +29,7 @@
typedef enum relopt_type
{
RELOPT_TYPE_BOOL,
+ RELOPT_TYPE_TERNARY, /* on, off, unset */
RELOPT_TYPE_INT,
RELOPT_TYPE_REAL,
RELOPT_TYPE_ENUM,
@@ -80,6 +81,7 @@ typedef struct relopt_value
union
{
bool bool_val;
+ pg_ternary ternary_val;
int int_val;
double real_val;
int enum_val;
@@ -94,6 +96,12 @@ typedef struct relopt_bool
bool default_val;
} relopt_bool;
+typedef struct relopt_ternary
+{
+ relopt_gen gen;
+ int default_val;
+} relopt_ternary;
+
typedef struct relopt_int
{
relopt_gen gen;
@@ -152,19 +160,6 @@ typedef struct
const char *optname; /* option's name */
relopt_type opttype; /* option's datatype */
int offset; /* offset of field in result struct */
-
- /*
- * isset_offset is an optional offset of a field in the result struct that
- * stores whether the option is explicitly set for the relation or if it
- * just picked up the default value. In most cases, this can be
- * accomplished by giving the reloption a special out-of-range default
- * value (e.g., some integer reloptions use -2), but this isn't always
- * possible. For example, a Boolean reloption cannot be given an
- * out-of-range default, so we need another way to discover the source of
- * its value. This offset is only used if given a value greater than
- * zero.
- */
- int isset_offset;
} relopt_parse_elt;
/* Local reloption definition */
@@ -195,6 +190,9 @@ typedef struct local_relopts
extern relopt_kind add_reloption_kind(void);
extern void add_bool_reloption(bits32 kinds, const char *name, const char *desc,
bool default_val, LOCKMODE lockmode);
+extern void add_ternary_reloption(bits32 kinds, const char *name,
+ const char *desc, pg_ternary default_val,
+ LOCKMODE lockmode);
extern void add_int_reloption(bits32 kinds, const char *name, const char *desc,
int default_val, int min_val, int max_val,
LOCKMODE lockmode);
@@ -214,6 +212,9 @@ extern void register_reloptions_validator(local_relopts *relopts,
extern void add_local_bool_reloption(local_relopts *relopts, const char *name,
const char *desc, bool default_val,
int offset);
+extern void add_local_ternary_reloption(local_relopts *relopts,
+ const char *name, const char *desc,
+ pg_ternary default_val, int offset);
extern void add_local_int_reloption(local_relopts *relopts, const char *name,
const char *desc, int default_val,
int min_val, int max_val, int offset);
diff --git a/src/include/postgres.h b/src/include/postgres.h
index 7d93fbce709..47c88780776 100644
--- a/src/include/postgres.h
+++ b/src/include/postgres.h
@@ -543,6 +543,21 @@ Float8GetDatum(float8 X)
* ----------------------------------------------------------------
*/
+/*
+ * pg_ternary
+ * Boolean value with an extra "unset" value
+ *
+ * This enum can be used for values that want to distinguish between true,
+ * false, and unset.
+*/
+
+typedef enum pg_ternary
+{
+ TERNARY_FALSE = 0,
+ TERNARY_TRUE = 1,
+ TERNARY_UNSET = -1
+} pg_ternary;
+
/*
* NON_EXEC_STATIC: It's sometimes useful to define a variable or function
* that is normally static but extern when using EXEC_BACKEND (see
diff --git a/src/include/utils/rel.h b/src/include/utils/rel.h
index d03ab247788..5feb18a5373 100644
--- a/src/include/utils/rel.h
+++ b/src/include/utils/rel.h
@@ -347,8 +347,7 @@ typedef struct StdRdOptions
bool user_catalog_table; /* use as an additional catalog relation */
int parallel_workers; /* max number of parallel workers */
StdRdOptIndexCleanup vacuum_index_cleanup; /* controls index vacuuming */
- bool vacuum_truncate; /* enables vacuum to truncate a relation */
- bool vacuum_truncate_set; /* whether vacuum_truncate is set */
+ pg_ternary vacuum_truncate; /* enables vacuum to truncate a relation */
/*
* Fraction of pages in a relation that vacuum can eagerly scan and fail
diff --git a/src/test/regress/expected/reloptions.out b/src/test/regress/expected/reloptions.out
index 9de19b4e3f1..1c99f79ab01 100644
--- a/src/test/regress/expected/reloptions.out
+++ b/src/test/regress/expected/reloptions.out
@@ -98,6 +98,42 @@ SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass;
{fillfactor=13,autovacuum_enabled=false}
(1 row)
+-- Tests for future (FIXME) ternary options
+-- behave as boolean option: accept unassigned name and truncated value
+DROP TABLE reloptions_test;
+CREATE TABLE reloptions_test(i INT) WITH (vacuum_truncate);
+SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass;
+ reloptions
+------------------------
+ {vacuum_truncate=true}
+(1 row)
+
+DROP TABLE reloptions_test;
+CREATE TABLE reloptions_test(i INT) WITH (vacuum_truncate=FaLS);
+SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass;
+ reloptions
+------------------------
+ {vacuum_truncate=fals}
+(1 row)
+
+-- preferred "true" alias is used when storing
+DROP TABLE reloptions_test;
+CREATE TABLE reloptions_test(i INT) WITH (vacuum_index_cleanup=on);
+SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass;
+ reloptions
+---------------------------
+ {vacuum_index_cleanup=on}
+(1 row)
+
+-- custom "third" value is available
+DROP TABLE reloptions_test;
+CREATE TABLE reloptions_test(i INT) WITH (vacuum_index_cleanup=auto);
+SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass;
+ reloptions
+-----------------------------
+ {vacuum_index_cleanup=auto}
+(1 row)
+
-- Test vacuum_truncate option
DROP TABLE reloptions_test;
CREATE TEMP TABLE reloptions_test(i INT NOT NULL, j text)
diff --git a/src/test/regress/sql/reloptions.sql b/src/test/regress/sql/reloptions.sql
index 24fbe0b478d..f5980dafcbc 100644
--- a/src/test/regress/sql/reloptions.sql
+++ b/src/test/regress/sql/reloptions.sql
@@ -59,6 +59,27 @@ UPDATE pg_class
ALTER TABLE reloptions_test RESET (illegal_option);
SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass;
+-- Tests for future (FIXME) ternary options
+
+-- behave as boolean option: accept unassigned name and truncated value
+DROP TABLE reloptions_test;
+CREATE TABLE reloptions_test(i INT) WITH (vacuum_truncate);
+SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass;
+
+DROP TABLE reloptions_test;
+CREATE TABLE reloptions_test(i INT) WITH (vacuum_truncate=FaLS);
+SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass;
+
+-- preferred "true" alias is used when storing
+DROP TABLE reloptions_test;
+CREATE TABLE reloptions_test(i INT) WITH (vacuum_index_cleanup=on);
+SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass;
+
+-- custom "third" value is available
+DROP TABLE reloptions_test;
+CREATE TABLE reloptions_test(i INT) WITH (vacuum_index_cleanup=auto);
+SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass;
+
-- Test vacuum_truncate option
DROP TABLE reloptions_test;
diff --git a/src/tools/pgindent/typedefs.list b/src/tools/pgindent/typedefs.list
index 3f3a888fd0e..1c8610fd46c 100644
--- a/src/tools/pgindent/typedefs.list
+++ b/src/tools/pgindent/typedefs.list
@@ -3952,6 +3952,7 @@ pg_sha512_ctx
pg_snapshot
pg_special_case
pg_stack_base_t
+pg_ternary
pg_time_t
pg_time_usec_t
pg_tz
@@ -4079,6 +4080,7 @@ relopt_kind
relopt_parse_elt
relopt_real
relopt_string
+relopt_ternary
relopt_type
relopt_value
relopts_validator
--
2.47.3
--c36t3fndxhkzbl5z--
^ permalink raw reply [nested|flat] 868+ messages in thread
* [PATCH v3] Introduce ternary reloptions
@ 2026-01-16 15:04 Álvaro Herrera <[email protected]>
0 siblings, 0 replies; 868+ messages in thread
From: Álvaro Herrera @ 2026-01-16 15:04 UTC (permalink / raw)
Introduce ternary reloption as a replacement for current
`vacuum_truncate` implementation. Remove the `vacuum_truncate_set`
separate flag and use TERNARY_UNSET instead.
This could also be used for other options such as `vacuum_index_cleanup`
and `buffering`, but lets get the scaffolding in first.
Discussion: https://postgr.es/m/3474141.usfYGdeWWP@thinkpad-pgpro
---
src/backend/access/common/reloptions.c | 137 ++++++++++++++++++-----
src/backend/commands/vacuum.c | 4 +-
src/include/access/reloptions.h | 27 ++---
src/include/postgres.h | 15 +++
src/include/utils/rel.h | 3 +-
src/test/regress/expected/reloptions.out | 36 ++++++
src/test/regress/sql/reloptions.sql | 21 ++++
src/tools/pgindent/typedefs.list | 2 +
8 files changed, 202 insertions(+), 43 deletions(-)
diff --git a/src/backend/access/common/reloptions.c b/src/backend/access/common/reloptions.c
index 0b83f98ed5f..6f1f577581a 100644
--- a/src/backend/access/common/reloptions.c
+++ b/src/backend/access/common/reloptions.c
@@ -40,9 +40,9 @@
*
* To add an option:
*
- * (i) decide on a type (bool, integer, real, enum, string), name, default
- * value, upper and lower bounds (if applicable); for strings, consider a
- * validation routine.
+ * (i) decide on a type (bool, ternary, integer, real, enum, string), name,
+ * default value, upper and lower bounds (if applicable); for strings,
+ * consider a validation routine.
* (ii) add a record below (or use add_<type>_reloption).
* (iii) add it to the appropriate options struct (perhaps StdRdOptions)
* (iv) add it to the appropriate handling routine (perhaps
@@ -147,15 +147,6 @@ static relopt_bool boolRelOpts[] =
},
false
},
- {
- {
- "vacuum_truncate",
- "Enables vacuum to truncate empty pages at the end of this table",
- RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
- ShareUpdateExclusiveLock
- },
- true
- },
{
{
"deduplicate_items",
@@ -170,6 +161,25 @@ static relopt_bool boolRelOpts[] =
{{NULL}}
};
+static relopt_ternary ternaryRelOpts[] =
+{
+ {
+ {
+ "vacuum_truncate",
+ "Enables vacuum to truncate empty pages at the end of this table",
+ RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
+ ShareUpdateExclusiveLock
+ },
+ TERNARY_UNSET
+ },
+ /* list terminator */
+ {
+ {
+ NULL
+ }
+ }
+};
+
static relopt_int intRelOpts[] =
{
{
@@ -609,6 +619,13 @@ initialize_reloptions(void)
boolRelOpts[i].gen.lockmode));
j++;
}
+ for (i = 0; ternaryRelOpts[i].gen.name; i++)
+ {
+ Assert(DoLockModesConflict(ternaryRelOpts[i].gen.lockmode,
+ ternaryRelOpts[i].gen.lockmode));
+ j++;
+ }
+
for (i = 0; intRelOpts[i].gen.name; i++)
{
Assert(DoLockModesConflict(intRelOpts[i].gen.lockmode,
@@ -649,6 +666,14 @@ initialize_reloptions(void)
j++;
}
+ for (i = 0; ternaryRelOpts[i].gen.name; i++)
+ {
+ relOpts[j] = &ternaryRelOpts[i].gen;
+ relOpts[j]->type = RELOPT_TYPE_TERNARY;
+ relOpts[j]->namelen = strlen(relOpts[j]->name);
+ j++;
+ }
+
for (i = 0; intRelOpts[i].gen.name; i++)
{
relOpts[j] = &intRelOpts[i].gen;
@@ -809,6 +834,9 @@ allocate_reloption(bits32 kinds, int type, const char *name, const char *desc,
case RELOPT_TYPE_BOOL:
size = sizeof(relopt_bool);
break;
+ case RELOPT_TYPE_TERNARY:
+ size = sizeof(relopt_ternary);
+ break;
case RELOPT_TYPE_INT:
size = sizeof(relopt_int);
break;
@@ -892,6 +920,57 @@ add_local_bool_reloption(local_relopts *relopts, const char *name,
add_local_reloption(relopts, (relopt_gen *) newoption, offset);
}
+/*
+ * init_ternary_reloption
+ * Allocate and initialize a new ternary reloption
+ */
+static relopt_ternary *
+init_ternary_reloption(bits32 kinds, const char *name, const char *desc,
+ pg_ternary default_val, LOCKMODE lockmode)
+{
+ relopt_ternary *newoption;
+
+ newoption = (relopt_ternary *)
+ allocate_reloption(kinds, RELOPT_TYPE_TERNARY, name, desc, lockmode);
+ newoption->default_val = default_val;
+
+ return newoption;
+}
+
+/*
+ * add_ternary_reloption
+ * Add a new ternary reloption
+ */
+void
+add_ternary_reloption(bits32 kinds, const char *name, const char *desc,
+ pg_ternary default_val, LOCKMODE lockmode)
+{
+ relopt_ternary *newoption;
+
+ newoption =
+ init_ternary_reloption(kinds, name, desc, default_val, lockmode);
+
+ add_reloption((relopt_gen *) newoption);
+}
+
+/*
+ * add_local_ternary_reloption
+ * Add a new ternary local reloption
+ *
+ * 'offset' is offset of ternary-typed field.
+ */
+void
+add_local_ternary_reloption(local_relopts *relopts, const char *name,
+ const char *desc, pg_ternary default_val,
+ int offset)
+{
+ relopt_ternary *newoption;
+
+ newoption =
+ init_ternary_reloption(RELOPT_KIND_LOCAL, name, desc, default_val, 0);
+
+ add_local_reloption(relopts, (relopt_gen *) newoption, offset);
+}
/*
* init_real_reloption
@@ -1626,6 +1705,19 @@ parse_one_reloption(relopt_value *option, char *text_str, int text_len,
option->gen->name, value)));
}
break;
+ case RELOPT_TYPE_TERNARY:
+ {
+ bool b;
+
+ parsed = parse_bool(value, &b);
+ option->values.ternary_val = b ? TERNARY_TRUE : TERNARY_FALSE;
+ if (validate && !parsed)
+ ereport(ERROR,
+ (errcode(ERRCODE_INVALID_PARAMETER_VALUE),
+ errmsg("invalid value for ternary option \"%s\": %s",
+ option->gen->name, value)));
+ }
+ break;
case RELOPT_TYPE_INT:
{
relopt_int *optint = (relopt_int *) option->gen;
@@ -1789,17 +1881,6 @@ fillRelOptions(void *rdopts, Size basesize,
char *itempos = ((char *) rdopts) + elems[j].offset;
char *string_val;
- /*
- * If isset_offset is provided, store whether the reloption is
- * set there.
- */
- if (elems[j].isset_offset > 0)
- {
- char *setpos = ((char *) rdopts) + elems[j].isset_offset;
-
- *(bool *) setpos = options[i].isset;
- }
-
switch (options[i].gen->type)
{
case RELOPT_TYPE_BOOL:
@@ -1807,6 +1888,11 @@ fillRelOptions(void *rdopts, Size basesize,
options[i].values.bool_val :
((relopt_bool *) options[i].gen)->default_val;
break;
+ case RELOPT_TYPE_TERNARY:
+ *(pg_ternary *) itempos = options[i].isset ?
+ options[i].values.ternary_val :
+ ((relopt_ternary *) options[i].gen)->default_val;
+ break;
case RELOPT_TYPE_INT:
*(int *) itempos = options[i].isset ?
options[i].values.int_val :
@@ -1923,8 +2009,8 @@ default_reloptions(Datum reloptions, bool validate, relopt_kind kind)
offsetof(StdRdOptions, parallel_workers)},
{"vacuum_index_cleanup", RELOPT_TYPE_ENUM,
offsetof(StdRdOptions, vacuum_index_cleanup)},
- {"vacuum_truncate", RELOPT_TYPE_BOOL,
- offsetof(StdRdOptions, vacuum_truncate), offsetof(StdRdOptions, vacuum_truncate_set)},
+ {"vacuum_truncate", RELOPT_TYPE_TERNARY,
+ offsetof(StdRdOptions, vacuum_truncate)},
{"vacuum_max_eager_freeze_failure_rate", RELOPT_TYPE_REAL,
offsetof(StdRdOptions, vacuum_max_eager_freeze_failure_rate)}
};
@@ -2004,7 +2090,6 @@ build_local_reloptions(local_relopts *relopts, Datum options, bool validate)
elems[i].optname = opt->option->name;
elems[i].opttype = opt->option->type;
elems[i].offset = opt->offset;
- elems[i].isset_offset = 0; /* not supported for local relopts yet */
i++;
}
diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c
index aa4fbec143f..696eab9bd97 100644
--- a/src/backend/commands/vacuum.c
+++ b/src/backend/commands/vacuum.c
@@ -2224,9 +2224,9 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams params,
{
StdRdOptions *opts = (StdRdOptions *) rel->rd_options;
- if (opts && opts->vacuum_truncate_set)
+ if (opts && opts->vacuum_truncate != TERNARY_UNSET)
{
- if (opts->vacuum_truncate)
+ if (opts->vacuum_truncate == TERNARY_TRUE)
params.truncate = VACOPTVALUE_ENABLED;
else
params.truncate = VACOPTVALUE_DISABLED;
diff --git a/src/include/access/reloptions.h b/src/include/access/reloptions.h
index 2f08e1b0cf0..dfbef2babf2 100644
--- a/src/include/access/reloptions.h
+++ b/src/include/access/reloptions.h
@@ -29,6 +29,7 @@
typedef enum relopt_type
{
RELOPT_TYPE_BOOL,
+ RELOPT_TYPE_TERNARY, /* on, off, unset */
RELOPT_TYPE_INT,
RELOPT_TYPE_REAL,
RELOPT_TYPE_ENUM,
@@ -80,6 +81,7 @@ typedef struct relopt_value
union
{
bool bool_val;
+ pg_ternary ternary_val;
int int_val;
double real_val;
int enum_val;
@@ -94,6 +96,12 @@ typedef struct relopt_bool
bool default_val;
} relopt_bool;
+typedef struct relopt_ternary
+{
+ relopt_gen gen;
+ int default_val;
+} relopt_ternary;
+
typedef struct relopt_int
{
relopt_gen gen;
@@ -152,19 +160,6 @@ typedef struct
const char *optname; /* option's name */
relopt_type opttype; /* option's datatype */
int offset; /* offset of field in result struct */
-
- /*
- * isset_offset is an optional offset of a field in the result struct that
- * stores whether the option is explicitly set for the relation or if it
- * just picked up the default value. In most cases, this can be
- * accomplished by giving the reloption a special out-of-range default
- * value (e.g., some integer reloptions use -2), but this isn't always
- * possible. For example, a Boolean reloption cannot be given an
- * out-of-range default, so we need another way to discover the source of
- * its value. This offset is only used if given a value greater than
- * zero.
- */
- int isset_offset;
} relopt_parse_elt;
/* Local reloption definition */
@@ -195,6 +190,9 @@ typedef struct local_relopts
extern relopt_kind add_reloption_kind(void);
extern void add_bool_reloption(bits32 kinds, const char *name, const char *desc,
bool default_val, LOCKMODE lockmode);
+extern void add_ternary_reloption(bits32 kinds, const char *name,
+ const char *desc, pg_ternary default_val,
+ LOCKMODE lockmode);
extern void add_int_reloption(bits32 kinds, const char *name, const char *desc,
int default_val, int min_val, int max_val,
LOCKMODE lockmode);
@@ -214,6 +212,9 @@ extern void register_reloptions_validator(local_relopts *relopts,
extern void add_local_bool_reloption(local_relopts *relopts, const char *name,
const char *desc, bool default_val,
int offset);
+extern void add_local_ternary_reloption(local_relopts *relopts,
+ const char *name, const char *desc,
+ pg_ternary default_val, int offset);
extern void add_local_int_reloption(local_relopts *relopts, const char *name,
const char *desc, int default_val,
int min_val, int max_val, int offset);
diff --git a/src/include/postgres.h b/src/include/postgres.h
index 7d93fbce709..47c88780776 100644
--- a/src/include/postgres.h
+++ b/src/include/postgres.h
@@ -543,6 +543,21 @@ Float8GetDatum(float8 X)
* ----------------------------------------------------------------
*/
+/*
+ * pg_ternary
+ * Boolean value with an extra "unset" value
+ *
+ * This enum can be used for values that want to distinguish between true,
+ * false, and unset.
+*/
+
+typedef enum pg_ternary
+{
+ TERNARY_FALSE = 0,
+ TERNARY_TRUE = 1,
+ TERNARY_UNSET = -1
+} pg_ternary;
+
/*
* NON_EXEC_STATIC: It's sometimes useful to define a variable or function
* that is normally static but extern when using EXEC_BACKEND (see
diff --git a/src/include/utils/rel.h b/src/include/utils/rel.h
index d03ab247788..5feb18a5373 100644
--- a/src/include/utils/rel.h
+++ b/src/include/utils/rel.h
@@ -347,8 +347,7 @@ typedef struct StdRdOptions
bool user_catalog_table; /* use as an additional catalog relation */
int parallel_workers; /* max number of parallel workers */
StdRdOptIndexCleanup vacuum_index_cleanup; /* controls index vacuuming */
- bool vacuum_truncate; /* enables vacuum to truncate a relation */
- bool vacuum_truncate_set; /* whether vacuum_truncate is set */
+ pg_ternary vacuum_truncate; /* enables vacuum to truncate a relation */
/*
* Fraction of pages in a relation that vacuum can eagerly scan and fail
diff --git a/src/test/regress/expected/reloptions.out b/src/test/regress/expected/reloptions.out
index 9de19b4e3f1..1c99f79ab01 100644
--- a/src/test/regress/expected/reloptions.out
+++ b/src/test/regress/expected/reloptions.out
@@ -98,6 +98,42 @@ SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass;
{fillfactor=13,autovacuum_enabled=false}
(1 row)
+-- Tests for future (FIXME) ternary options
+-- behave as boolean option: accept unassigned name and truncated value
+DROP TABLE reloptions_test;
+CREATE TABLE reloptions_test(i INT) WITH (vacuum_truncate);
+SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass;
+ reloptions
+------------------------
+ {vacuum_truncate=true}
+(1 row)
+
+DROP TABLE reloptions_test;
+CREATE TABLE reloptions_test(i INT) WITH (vacuum_truncate=FaLS);
+SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass;
+ reloptions
+------------------------
+ {vacuum_truncate=fals}
+(1 row)
+
+-- preferred "true" alias is used when storing
+DROP TABLE reloptions_test;
+CREATE TABLE reloptions_test(i INT) WITH (vacuum_index_cleanup=on);
+SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass;
+ reloptions
+---------------------------
+ {vacuum_index_cleanup=on}
+(1 row)
+
+-- custom "third" value is available
+DROP TABLE reloptions_test;
+CREATE TABLE reloptions_test(i INT) WITH (vacuum_index_cleanup=auto);
+SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass;
+ reloptions
+-----------------------------
+ {vacuum_index_cleanup=auto}
+(1 row)
+
-- Test vacuum_truncate option
DROP TABLE reloptions_test;
CREATE TEMP TABLE reloptions_test(i INT NOT NULL, j text)
diff --git a/src/test/regress/sql/reloptions.sql b/src/test/regress/sql/reloptions.sql
index 24fbe0b478d..f5980dafcbc 100644
--- a/src/test/regress/sql/reloptions.sql
+++ b/src/test/regress/sql/reloptions.sql
@@ -59,6 +59,27 @@ UPDATE pg_class
ALTER TABLE reloptions_test RESET (illegal_option);
SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass;
+-- Tests for future (FIXME) ternary options
+
+-- behave as boolean option: accept unassigned name and truncated value
+DROP TABLE reloptions_test;
+CREATE TABLE reloptions_test(i INT) WITH (vacuum_truncate);
+SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass;
+
+DROP TABLE reloptions_test;
+CREATE TABLE reloptions_test(i INT) WITH (vacuum_truncate=FaLS);
+SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass;
+
+-- preferred "true" alias is used when storing
+DROP TABLE reloptions_test;
+CREATE TABLE reloptions_test(i INT) WITH (vacuum_index_cleanup=on);
+SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass;
+
+-- custom "third" value is available
+DROP TABLE reloptions_test;
+CREATE TABLE reloptions_test(i INT) WITH (vacuum_index_cleanup=auto);
+SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass;
+
-- Test vacuum_truncate option
DROP TABLE reloptions_test;
diff --git a/src/tools/pgindent/typedefs.list b/src/tools/pgindent/typedefs.list
index 3f3a888fd0e..1c8610fd46c 100644
--- a/src/tools/pgindent/typedefs.list
+++ b/src/tools/pgindent/typedefs.list
@@ -3952,6 +3952,7 @@ pg_sha512_ctx
pg_snapshot
pg_special_case
pg_stack_base_t
+pg_ternary
pg_time_t
pg_time_usec_t
pg_tz
@@ -4079,6 +4080,7 @@ relopt_kind
relopt_parse_elt
relopt_real
relopt_string
+relopt_ternary
relopt_type
relopt_value
relopts_validator
--
2.47.3
--c36t3fndxhkzbl5z--
^ permalink raw reply [nested|flat] 868+ messages in thread
* [PATCH v3] Introduce ternary reloptions
@ 2026-01-16 15:04 Álvaro Herrera <[email protected]>
0 siblings, 0 replies; 868+ messages in thread
From: Álvaro Herrera @ 2026-01-16 15:04 UTC (permalink / raw)
Introduce ternary reloption as a replacement for current
`vacuum_truncate` implementation. Remove the `vacuum_truncate_set`
separate flag and use TERNARY_UNSET instead.
This could also be used for other options such as `vacuum_index_cleanup`
and `buffering`, but lets get the scaffolding in first.
Discussion: https://postgr.es/m/3474141.usfYGdeWWP@thinkpad-pgpro
---
src/backend/access/common/reloptions.c | 137 ++++++++++++++++++-----
src/backend/commands/vacuum.c | 4 +-
src/include/access/reloptions.h | 27 ++---
src/include/postgres.h | 15 +++
src/include/utils/rel.h | 3 +-
src/test/regress/expected/reloptions.out | 36 ++++++
src/test/regress/sql/reloptions.sql | 21 ++++
src/tools/pgindent/typedefs.list | 2 +
8 files changed, 202 insertions(+), 43 deletions(-)
diff --git a/src/backend/access/common/reloptions.c b/src/backend/access/common/reloptions.c
index 0b83f98ed5f..6f1f577581a 100644
--- a/src/backend/access/common/reloptions.c
+++ b/src/backend/access/common/reloptions.c
@@ -40,9 +40,9 @@
*
* To add an option:
*
- * (i) decide on a type (bool, integer, real, enum, string), name, default
- * value, upper and lower bounds (if applicable); for strings, consider a
- * validation routine.
+ * (i) decide on a type (bool, ternary, integer, real, enum, string), name,
+ * default value, upper and lower bounds (if applicable); for strings,
+ * consider a validation routine.
* (ii) add a record below (or use add_<type>_reloption).
* (iii) add it to the appropriate options struct (perhaps StdRdOptions)
* (iv) add it to the appropriate handling routine (perhaps
@@ -147,15 +147,6 @@ static relopt_bool boolRelOpts[] =
},
false
},
- {
- {
- "vacuum_truncate",
- "Enables vacuum to truncate empty pages at the end of this table",
- RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
- ShareUpdateExclusiveLock
- },
- true
- },
{
{
"deduplicate_items",
@@ -170,6 +161,25 @@ static relopt_bool boolRelOpts[] =
{{NULL}}
};
+static relopt_ternary ternaryRelOpts[] =
+{
+ {
+ {
+ "vacuum_truncate",
+ "Enables vacuum to truncate empty pages at the end of this table",
+ RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
+ ShareUpdateExclusiveLock
+ },
+ TERNARY_UNSET
+ },
+ /* list terminator */
+ {
+ {
+ NULL
+ }
+ }
+};
+
static relopt_int intRelOpts[] =
{
{
@@ -609,6 +619,13 @@ initialize_reloptions(void)
boolRelOpts[i].gen.lockmode));
j++;
}
+ for (i = 0; ternaryRelOpts[i].gen.name; i++)
+ {
+ Assert(DoLockModesConflict(ternaryRelOpts[i].gen.lockmode,
+ ternaryRelOpts[i].gen.lockmode));
+ j++;
+ }
+
for (i = 0; intRelOpts[i].gen.name; i++)
{
Assert(DoLockModesConflict(intRelOpts[i].gen.lockmode,
@@ -649,6 +666,14 @@ initialize_reloptions(void)
j++;
}
+ for (i = 0; ternaryRelOpts[i].gen.name; i++)
+ {
+ relOpts[j] = &ternaryRelOpts[i].gen;
+ relOpts[j]->type = RELOPT_TYPE_TERNARY;
+ relOpts[j]->namelen = strlen(relOpts[j]->name);
+ j++;
+ }
+
for (i = 0; intRelOpts[i].gen.name; i++)
{
relOpts[j] = &intRelOpts[i].gen;
@@ -809,6 +834,9 @@ allocate_reloption(bits32 kinds, int type, const char *name, const char *desc,
case RELOPT_TYPE_BOOL:
size = sizeof(relopt_bool);
break;
+ case RELOPT_TYPE_TERNARY:
+ size = sizeof(relopt_ternary);
+ break;
case RELOPT_TYPE_INT:
size = sizeof(relopt_int);
break;
@@ -892,6 +920,57 @@ add_local_bool_reloption(local_relopts *relopts, const char *name,
add_local_reloption(relopts, (relopt_gen *) newoption, offset);
}
+/*
+ * init_ternary_reloption
+ * Allocate and initialize a new ternary reloption
+ */
+static relopt_ternary *
+init_ternary_reloption(bits32 kinds, const char *name, const char *desc,
+ pg_ternary default_val, LOCKMODE lockmode)
+{
+ relopt_ternary *newoption;
+
+ newoption = (relopt_ternary *)
+ allocate_reloption(kinds, RELOPT_TYPE_TERNARY, name, desc, lockmode);
+ newoption->default_val = default_val;
+
+ return newoption;
+}
+
+/*
+ * add_ternary_reloption
+ * Add a new ternary reloption
+ */
+void
+add_ternary_reloption(bits32 kinds, const char *name, const char *desc,
+ pg_ternary default_val, LOCKMODE lockmode)
+{
+ relopt_ternary *newoption;
+
+ newoption =
+ init_ternary_reloption(kinds, name, desc, default_val, lockmode);
+
+ add_reloption((relopt_gen *) newoption);
+}
+
+/*
+ * add_local_ternary_reloption
+ * Add a new ternary local reloption
+ *
+ * 'offset' is offset of ternary-typed field.
+ */
+void
+add_local_ternary_reloption(local_relopts *relopts, const char *name,
+ const char *desc, pg_ternary default_val,
+ int offset)
+{
+ relopt_ternary *newoption;
+
+ newoption =
+ init_ternary_reloption(RELOPT_KIND_LOCAL, name, desc, default_val, 0);
+
+ add_local_reloption(relopts, (relopt_gen *) newoption, offset);
+}
/*
* init_real_reloption
@@ -1626,6 +1705,19 @@ parse_one_reloption(relopt_value *option, char *text_str, int text_len,
option->gen->name, value)));
}
break;
+ case RELOPT_TYPE_TERNARY:
+ {
+ bool b;
+
+ parsed = parse_bool(value, &b);
+ option->values.ternary_val = b ? TERNARY_TRUE : TERNARY_FALSE;
+ if (validate && !parsed)
+ ereport(ERROR,
+ (errcode(ERRCODE_INVALID_PARAMETER_VALUE),
+ errmsg("invalid value for ternary option \"%s\": %s",
+ option->gen->name, value)));
+ }
+ break;
case RELOPT_TYPE_INT:
{
relopt_int *optint = (relopt_int *) option->gen;
@@ -1789,17 +1881,6 @@ fillRelOptions(void *rdopts, Size basesize,
char *itempos = ((char *) rdopts) + elems[j].offset;
char *string_val;
- /*
- * If isset_offset is provided, store whether the reloption is
- * set there.
- */
- if (elems[j].isset_offset > 0)
- {
- char *setpos = ((char *) rdopts) + elems[j].isset_offset;
-
- *(bool *) setpos = options[i].isset;
- }
-
switch (options[i].gen->type)
{
case RELOPT_TYPE_BOOL:
@@ -1807,6 +1888,11 @@ fillRelOptions(void *rdopts, Size basesize,
options[i].values.bool_val :
((relopt_bool *) options[i].gen)->default_val;
break;
+ case RELOPT_TYPE_TERNARY:
+ *(pg_ternary *) itempos = options[i].isset ?
+ options[i].values.ternary_val :
+ ((relopt_ternary *) options[i].gen)->default_val;
+ break;
case RELOPT_TYPE_INT:
*(int *) itempos = options[i].isset ?
options[i].values.int_val :
@@ -1923,8 +2009,8 @@ default_reloptions(Datum reloptions, bool validate, relopt_kind kind)
offsetof(StdRdOptions, parallel_workers)},
{"vacuum_index_cleanup", RELOPT_TYPE_ENUM,
offsetof(StdRdOptions, vacuum_index_cleanup)},
- {"vacuum_truncate", RELOPT_TYPE_BOOL,
- offsetof(StdRdOptions, vacuum_truncate), offsetof(StdRdOptions, vacuum_truncate_set)},
+ {"vacuum_truncate", RELOPT_TYPE_TERNARY,
+ offsetof(StdRdOptions, vacuum_truncate)},
{"vacuum_max_eager_freeze_failure_rate", RELOPT_TYPE_REAL,
offsetof(StdRdOptions, vacuum_max_eager_freeze_failure_rate)}
};
@@ -2004,7 +2090,6 @@ build_local_reloptions(local_relopts *relopts, Datum options, bool validate)
elems[i].optname = opt->option->name;
elems[i].opttype = opt->option->type;
elems[i].offset = opt->offset;
- elems[i].isset_offset = 0; /* not supported for local relopts yet */
i++;
}
diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c
index aa4fbec143f..696eab9bd97 100644
--- a/src/backend/commands/vacuum.c
+++ b/src/backend/commands/vacuum.c
@@ -2224,9 +2224,9 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams params,
{
StdRdOptions *opts = (StdRdOptions *) rel->rd_options;
- if (opts && opts->vacuum_truncate_set)
+ if (opts && opts->vacuum_truncate != TERNARY_UNSET)
{
- if (opts->vacuum_truncate)
+ if (opts->vacuum_truncate == TERNARY_TRUE)
params.truncate = VACOPTVALUE_ENABLED;
else
params.truncate = VACOPTVALUE_DISABLED;
diff --git a/src/include/access/reloptions.h b/src/include/access/reloptions.h
index 2f08e1b0cf0..dfbef2babf2 100644
--- a/src/include/access/reloptions.h
+++ b/src/include/access/reloptions.h
@@ -29,6 +29,7 @@
typedef enum relopt_type
{
RELOPT_TYPE_BOOL,
+ RELOPT_TYPE_TERNARY, /* on, off, unset */
RELOPT_TYPE_INT,
RELOPT_TYPE_REAL,
RELOPT_TYPE_ENUM,
@@ -80,6 +81,7 @@ typedef struct relopt_value
union
{
bool bool_val;
+ pg_ternary ternary_val;
int int_val;
double real_val;
int enum_val;
@@ -94,6 +96,12 @@ typedef struct relopt_bool
bool default_val;
} relopt_bool;
+typedef struct relopt_ternary
+{
+ relopt_gen gen;
+ int default_val;
+} relopt_ternary;
+
typedef struct relopt_int
{
relopt_gen gen;
@@ -152,19 +160,6 @@ typedef struct
const char *optname; /* option's name */
relopt_type opttype; /* option's datatype */
int offset; /* offset of field in result struct */
-
- /*
- * isset_offset is an optional offset of a field in the result struct that
- * stores whether the option is explicitly set for the relation or if it
- * just picked up the default value. In most cases, this can be
- * accomplished by giving the reloption a special out-of-range default
- * value (e.g., some integer reloptions use -2), but this isn't always
- * possible. For example, a Boolean reloption cannot be given an
- * out-of-range default, so we need another way to discover the source of
- * its value. This offset is only used if given a value greater than
- * zero.
- */
- int isset_offset;
} relopt_parse_elt;
/* Local reloption definition */
@@ -195,6 +190,9 @@ typedef struct local_relopts
extern relopt_kind add_reloption_kind(void);
extern void add_bool_reloption(bits32 kinds, const char *name, const char *desc,
bool default_val, LOCKMODE lockmode);
+extern void add_ternary_reloption(bits32 kinds, const char *name,
+ const char *desc, pg_ternary default_val,
+ LOCKMODE lockmode);
extern void add_int_reloption(bits32 kinds, const char *name, const char *desc,
int default_val, int min_val, int max_val,
LOCKMODE lockmode);
@@ -214,6 +212,9 @@ extern void register_reloptions_validator(local_relopts *relopts,
extern void add_local_bool_reloption(local_relopts *relopts, const char *name,
const char *desc, bool default_val,
int offset);
+extern void add_local_ternary_reloption(local_relopts *relopts,
+ const char *name, const char *desc,
+ pg_ternary default_val, int offset);
extern void add_local_int_reloption(local_relopts *relopts, const char *name,
const char *desc, int default_val,
int min_val, int max_val, int offset);
diff --git a/src/include/postgres.h b/src/include/postgres.h
index 7d93fbce709..47c88780776 100644
--- a/src/include/postgres.h
+++ b/src/include/postgres.h
@@ -543,6 +543,21 @@ Float8GetDatum(float8 X)
* ----------------------------------------------------------------
*/
+/*
+ * pg_ternary
+ * Boolean value with an extra "unset" value
+ *
+ * This enum can be used for values that want to distinguish between true,
+ * false, and unset.
+*/
+
+typedef enum pg_ternary
+{
+ TERNARY_FALSE = 0,
+ TERNARY_TRUE = 1,
+ TERNARY_UNSET = -1
+} pg_ternary;
+
/*
* NON_EXEC_STATIC: It's sometimes useful to define a variable or function
* that is normally static but extern when using EXEC_BACKEND (see
diff --git a/src/include/utils/rel.h b/src/include/utils/rel.h
index d03ab247788..5feb18a5373 100644
--- a/src/include/utils/rel.h
+++ b/src/include/utils/rel.h
@@ -347,8 +347,7 @@ typedef struct StdRdOptions
bool user_catalog_table; /* use as an additional catalog relation */
int parallel_workers; /* max number of parallel workers */
StdRdOptIndexCleanup vacuum_index_cleanup; /* controls index vacuuming */
- bool vacuum_truncate; /* enables vacuum to truncate a relation */
- bool vacuum_truncate_set; /* whether vacuum_truncate is set */
+ pg_ternary vacuum_truncate; /* enables vacuum to truncate a relation */
/*
* Fraction of pages in a relation that vacuum can eagerly scan and fail
diff --git a/src/test/regress/expected/reloptions.out b/src/test/regress/expected/reloptions.out
index 9de19b4e3f1..1c99f79ab01 100644
--- a/src/test/regress/expected/reloptions.out
+++ b/src/test/regress/expected/reloptions.out
@@ -98,6 +98,42 @@ SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass;
{fillfactor=13,autovacuum_enabled=false}
(1 row)
+-- Tests for future (FIXME) ternary options
+-- behave as boolean option: accept unassigned name and truncated value
+DROP TABLE reloptions_test;
+CREATE TABLE reloptions_test(i INT) WITH (vacuum_truncate);
+SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass;
+ reloptions
+------------------------
+ {vacuum_truncate=true}
+(1 row)
+
+DROP TABLE reloptions_test;
+CREATE TABLE reloptions_test(i INT) WITH (vacuum_truncate=FaLS);
+SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass;
+ reloptions
+------------------------
+ {vacuum_truncate=fals}
+(1 row)
+
+-- preferred "true" alias is used when storing
+DROP TABLE reloptions_test;
+CREATE TABLE reloptions_test(i INT) WITH (vacuum_index_cleanup=on);
+SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass;
+ reloptions
+---------------------------
+ {vacuum_index_cleanup=on}
+(1 row)
+
+-- custom "third" value is available
+DROP TABLE reloptions_test;
+CREATE TABLE reloptions_test(i INT) WITH (vacuum_index_cleanup=auto);
+SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass;
+ reloptions
+-----------------------------
+ {vacuum_index_cleanup=auto}
+(1 row)
+
-- Test vacuum_truncate option
DROP TABLE reloptions_test;
CREATE TEMP TABLE reloptions_test(i INT NOT NULL, j text)
diff --git a/src/test/regress/sql/reloptions.sql b/src/test/regress/sql/reloptions.sql
index 24fbe0b478d..f5980dafcbc 100644
--- a/src/test/regress/sql/reloptions.sql
+++ b/src/test/regress/sql/reloptions.sql
@@ -59,6 +59,27 @@ UPDATE pg_class
ALTER TABLE reloptions_test RESET (illegal_option);
SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass;
+-- Tests for future (FIXME) ternary options
+
+-- behave as boolean option: accept unassigned name and truncated value
+DROP TABLE reloptions_test;
+CREATE TABLE reloptions_test(i INT) WITH (vacuum_truncate);
+SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass;
+
+DROP TABLE reloptions_test;
+CREATE TABLE reloptions_test(i INT) WITH (vacuum_truncate=FaLS);
+SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass;
+
+-- preferred "true" alias is used when storing
+DROP TABLE reloptions_test;
+CREATE TABLE reloptions_test(i INT) WITH (vacuum_index_cleanup=on);
+SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass;
+
+-- custom "third" value is available
+DROP TABLE reloptions_test;
+CREATE TABLE reloptions_test(i INT) WITH (vacuum_index_cleanup=auto);
+SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass;
+
-- Test vacuum_truncate option
DROP TABLE reloptions_test;
diff --git a/src/tools/pgindent/typedefs.list b/src/tools/pgindent/typedefs.list
index 3f3a888fd0e..1c8610fd46c 100644
--- a/src/tools/pgindent/typedefs.list
+++ b/src/tools/pgindent/typedefs.list
@@ -3952,6 +3952,7 @@ pg_sha512_ctx
pg_snapshot
pg_special_case
pg_stack_base_t
+pg_ternary
pg_time_t
pg_time_usec_t
pg_tz
@@ -4079,6 +4080,7 @@ relopt_kind
relopt_parse_elt
relopt_real
relopt_string
+relopt_ternary
relopt_type
relopt_value
relopts_validator
--
2.47.3
--c36t3fndxhkzbl5z--
^ permalink raw reply [nested|flat] 868+ messages in thread
* [PATCH v3] Introduce ternary reloptions
@ 2026-01-16 15:04 Álvaro Herrera <[email protected]>
0 siblings, 0 replies; 868+ messages in thread
From: Álvaro Herrera @ 2026-01-16 15:04 UTC (permalink / raw)
Introduce ternary reloption as a replacement for current
`vacuum_truncate` implementation. Remove the `vacuum_truncate_set`
separate flag and use TERNARY_UNSET instead.
This could also be used for other options such as `vacuum_index_cleanup`
and `buffering`, but lets get the scaffolding in first.
Discussion: https://postgr.es/m/3474141.usfYGdeWWP@thinkpad-pgpro
---
src/backend/access/common/reloptions.c | 137 ++++++++++++++++++-----
src/backend/commands/vacuum.c | 4 +-
src/include/access/reloptions.h | 27 ++---
src/include/postgres.h | 15 +++
src/include/utils/rel.h | 3 +-
src/test/regress/expected/reloptions.out | 36 ++++++
src/test/regress/sql/reloptions.sql | 21 ++++
src/tools/pgindent/typedefs.list | 2 +
8 files changed, 202 insertions(+), 43 deletions(-)
diff --git a/src/backend/access/common/reloptions.c b/src/backend/access/common/reloptions.c
index 0b83f98ed5f..6f1f577581a 100644
--- a/src/backend/access/common/reloptions.c
+++ b/src/backend/access/common/reloptions.c
@@ -40,9 +40,9 @@
*
* To add an option:
*
- * (i) decide on a type (bool, integer, real, enum, string), name, default
- * value, upper and lower bounds (if applicable); for strings, consider a
- * validation routine.
+ * (i) decide on a type (bool, ternary, integer, real, enum, string), name,
+ * default value, upper and lower bounds (if applicable); for strings,
+ * consider a validation routine.
* (ii) add a record below (or use add_<type>_reloption).
* (iii) add it to the appropriate options struct (perhaps StdRdOptions)
* (iv) add it to the appropriate handling routine (perhaps
@@ -147,15 +147,6 @@ static relopt_bool boolRelOpts[] =
},
false
},
- {
- {
- "vacuum_truncate",
- "Enables vacuum to truncate empty pages at the end of this table",
- RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
- ShareUpdateExclusiveLock
- },
- true
- },
{
{
"deduplicate_items",
@@ -170,6 +161,25 @@ static relopt_bool boolRelOpts[] =
{{NULL}}
};
+static relopt_ternary ternaryRelOpts[] =
+{
+ {
+ {
+ "vacuum_truncate",
+ "Enables vacuum to truncate empty pages at the end of this table",
+ RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
+ ShareUpdateExclusiveLock
+ },
+ TERNARY_UNSET
+ },
+ /* list terminator */
+ {
+ {
+ NULL
+ }
+ }
+};
+
static relopt_int intRelOpts[] =
{
{
@@ -609,6 +619,13 @@ initialize_reloptions(void)
boolRelOpts[i].gen.lockmode));
j++;
}
+ for (i = 0; ternaryRelOpts[i].gen.name; i++)
+ {
+ Assert(DoLockModesConflict(ternaryRelOpts[i].gen.lockmode,
+ ternaryRelOpts[i].gen.lockmode));
+ j++;
+ }
+
for (i = 0; intRelOpts[i].gen.name; i++)
{
Assert(DoLockModesConflict(intRelOpts[i].gen.lockmode,
@@ -649,6 +666,14 @@ initialize_reloptions(void)
j++;
}
+ for (i = 0; ternaryRelOpts[i].gen.name; i++)
+ {
+ relOpts[j] = &ternaryRelOpts[i].gen;
+ relOpts[j]->type = RELOPT_TYPE_TERNARY;
+ relOpts[j]->namelen = strlen(relOpts[j]->name);
+ j++;
+ }
+
for (i = 0; intRelOpts[i].gen.name; i++)
{
relOpts[j] = &intRelOpts[i].gen;
@@ -809,6 +834,9 @@ allocate_reloption(bits32 kinds, int type, const char *name, const char *desc,
case RELOPT_TYPE_BOOL:
size = sizeof(relopt_bool);
break;
+ case RELOPT_TYPE_TERNARY:
+ size = sizeof(relopt_ternary);
+ break;
case RELOPT_TYPE_INT:
size = sizeof(relopt_int);
break;
@@ -892,6 +920,57 @@ add_local_bool_reloption(local_relopts *relopts, const char *name,
add_local_reloption(relopts, (relopt_gen *) newoption, offset);
}
+/*
+ * init_ternary_reloption
+ * Allocate and initialize a new ternary reloption
+ */
+static relopt_ternary *
+init_ternary_reloption(bits32 kinds, const char *name, const char *desc,
+ pg_ternary default_val, LOCKMODE lockmode)
+{
+ relopt_ternary *newoption;
+
+ newoption = (relopt_ternary *)
+ allocate_reloption(kinds, RELOPT_TYPE_TERNARY, name, desc, lockmode);
+ newoption->default_val = default_val;
+
+ return newoption;
+}
+
+/*
+ * add_ternary_reloption
+ * Add a new ternary reloption
+ */
+void
+add_ternary_reloption(bits32 kinds, const char *name, const char *desc,
+ pg_ternary default_val, LOCKMODE lockmode)
+{
+ relopt_ternary *newoption;
+
+ newoption =
+ init_ternary_reloption(kinds, name, desc, default_val, lockmode);
+
+ add_reloption((relopt_gen *) newoption);
+}
+
+/*
+ * add_local_ternary_reloption
+ * Add a new ternary local reloption
+ *
+ * 'offset' is offset of ternary-typed field.
+ */
+void
+add_local_ternary_reloption(local_relopts *relopts, const char *name,
+ const char *desc, pg_ternary default_val,
+ int offset)
+{
+ relopt_ternary *newoption;
+
+ newoption =
+ init_ternary_reloption(RELOPT_KIND_LOCAL, name, desc, default_val, 0);
+
+ add_local_reloption(relopts, (relopt_gen *) newoption, offset);
+}
/*
* init_real_reloption
@@ -1626,6 +1705,19 @@ parse_one_reloption(relopt_value *option, char *text_str, int text_len,
option->gen->name, value)));
}
break;
+ case RELOPT_TYPE_TERNARY:
+ {
+ bool b;
+
+ parsed = parse_bool(value, &b);
+ option->values.ternary_val = b ? TERNARY_TRUE : TERNARY_FALSE;
+ if (validate && !parsed)
+ ereport(ERROR,
+ (errcode(ERRCODE_INVALID_PARAMETER_VALUE),
+ errmsg("invalid value for ternary option \"%s\": %s",
+ option->gen->name, value)));
+ }
+ break;
case RELOPT_TYPE_INT:
{
relopt_int *optint = (relopt_int *) option->gen;
@@ -1789,17 +1881,6 @@ fillRelOptions(void *rdopts, Size basesize,
char *itempos = ((char *) rdopts) + elems[j].offset;
char *string_val;
- /*
- * If isset_offset is provided, store whether the reloption is
- * set there.
- */
- if (elems[j].isset_offset > 0)
- {
- char *setpos = ((char *) rdopts) + elems[j].isset_offset;
-
- *(bool *) setpos = options[i].isset;
- }
-
switch (options[i].gen->type)
{
case RELOPT_TYPE_BOOL:
@@ -1807,6 +1888,11 @@ fillRelOptions(void *rdopts, Size basesize,
options[i].values.bool_val :
((relopt_bool *) options[i].gen)->default_val;
break;
+ case RELOPT_TYPE_TERNARY:
+ *(pg_ternary *) itempos = options[i].isset ?
+ options[i].values.ternary_val :
+ ((relopt_ternary *) options[i].gen)->default_val;
+ break;
case RELOPT_TYPE_INT:
*(int *) itempos = options[i].isset ?
options[i].values.int_val :
@@ -1923,8 +2009,8 @@ default_reloptions(Datum reloptions, bool validate, relopt_kind kind)
offsetof(StdRdOptions, parallel_workers)},
{"vacuum_index_cleanup", RELOPT_TYPE_ENUM,
offsetof(StdRdOptions, vacuum_index_cleanup)},
- {"vacuum_truncate", RELOPT_TYPE_BOOL,
- offsetof(StdRdOptions, vacuum_truncate), offsetof(StdRdOptions, vacuum_truncate_set)},
+ {"vacuum_truncate", RELOPT_TYPE_TERNARY,
+ offsetof(StdRdOptions, vacuum_truncate)},
{"vacuum_max_eager_freeze_failure_rate", RELOPT_TYPE_REAL,
offsetof(StdRdOptions, vacuum_max_eager_freeze_failure_rate)}
};
@@ -2004,7 +2090,6 @@ build_local_reloptions(local_relopts *relopts, Datum options, bool validate)
elems[i].optname = opt->option->name;
elems[i].opttype = opt->option->type;
elems[i].offset = opt->offset;
- elems[i].isset_offset = 0; /* not supported for local relopts yet */
i++;
}
diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c
index aa4fbec143f..696eab9bd97 100644
--- a/src/backend/commands/vacuum.c
+++ b/src/backend/commands/vacuum.c
@@ -2224,9 +2224,9 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams params,
{
StdRdOptions *opts = (StdRdOptions *) rel->rd_options;
- if (opts && opts->vacuum_truncate_set)
+ if (opts && opts->vacuum_truncate != TERNARY_UNSET)
{
- if (opts->vacuum_truncate)
+ if (opts->vacuum_truncate == TERNARY_TRUE)
params.truncate = VACOPTVALUE_ENABLED;
else
params.truncate = VACOPTVALUE_DISABLED;
diff --git a/src/include/access/reloptions.h b/src/include/access/reloptions.h
index 2f08e1b0cf0..dfbef2babf2 100644
--- a/src/include/access/reloptions.h
+++ b/src/include/access/reloptions.h
@@ -29,6 +29,7 @@
typedef enum relopt_type
{
RELOPT_TYPE_BOOL,
+ RELOPT_TYPE_TERNARY, /* on, off, unset */
RELOPT_TYPE_INT,
RELOPT_TYPE_REAL,
RELOPT_TYPE_ENUM,
@@ -80,6 +81,7 @@ typedef struct relopt_value
union
{
bool bool_val;
+ pg_ternary ternary_val;
int int_val;
double real_val;
int enum_val;
@@ -94,6 +96,12 @@ typedef struct relopt_bool
bool default_val;
} relopt_bool;
+typedef struct relopt_ternary
+{
+ relopt_gen gen;
+ int default_val;
+} relopt_ternary;
+
typedef struct relopt_int
{
relopt_gen gen;
@@ -152,19 +160,6 @@ typedef struct
const char *optname; /* option's name */
relopt_type opttype; /* option's datatype */
int offset; /* offset of field in result struct */
-
- /*
- * isset_offset is an optional offset of a field in the result struct that
- * stores whether the option is explicitly set for the relation or if it
- * just picked up the default value. In most cases, this can be
- * accomplished by giving the reloption a special out-of-range default
- * value (e.g., some integer reloptions use -2), but this isn't always
- * possible. For example, a Boolean reloption cannot be given an
- * out-of-range default, so we need another way to discover the source of
- * its value. This offset is only used if given a value greater than
- * zero.
- */
- int isset_offset;
} relopt_parse_elt;
/* Local reloption definition */
@@ -195,6 +190,9 @@ typedef struct local_relopts
extern relopt_kind add_reloption_kind(void);
extern void add_bool_reloption(bits32 kinds, const char *name, const char *desc,
bool default_val, LOCKMODE lockmode);
+extern void add_ternary_reloption(bits32 kinds, const char *name,
+ const char *desc, pg_ternary default_val,
+ LOCKMODE lockmode);
extern void add_int_reloption(bits32 kinds, const char *name, const char *desc,
int default_val, int min_val, int max_val,
LOCKMODE lockmode);
@@ -214,6 +212,9 @@ extern void register_reloptions_validator(local_relopts *relopts,
extern void add_local_bool_reloption(local_relopts *relopts, const char *name,
const char *desc, bool default_val,
int offset);
+extern void add_local_ternary_reloption(local_relopts *relopts,
+ const char *name, const char *desc,
+ pg_ternary default_val, int offset);
extern void add_local_int_reloption(local_relopts *relopts, const char *name,
const char *desc, int default_val,
int min_val, int max_val, int offset);
diff --git a/src/include/postgres.h b/src/include/postgres.h
index 7d93fbce709..47c88780776 100644
--- a/src/include/postgres.h
+++ b/src/include/postgres.h
@@ -543,6 +543,21 @@ Float8GetDatum(float8 X)
* ----------------------------------------------------------------
*/
+/*
+ * pg_ternary
+ * Boolean value with an extra "unset" value
+ *
+ * This enum can be used for values that want to distinguish between true,
+ * false, and unset.
+*/
+
+typedef enum pg_ternary
+{
+ TERNARY_FALSE = 0,
+ TERNARY_TRUE = 1,
+ TERNARY_UNSET = -1
+} pg_ternary;
+
/*
* NON_EXEC_STATIC: It's sometimes useful to define a variable or function
* that is normally static but extern when using EXEC_BACKEND (see
diff --git a/src/include/utils/rel.h b/src/include/utils/rel.h
index d03ab247788..5feb18a5373 100644
--- a/src/include/utils/rel.h
+++ b/src/include/utils/rel.h
@@ -347,8 +347,7 @@ typedef struct StdRdOptions
bool user_catalog_table; /* use as an additional catalog relation */
int parallel_workers; /* max number of parallel workers */
StdRdOptIndexCleanup vacuum_index_cleanup; /* controls index vacuuming */
- bool vacuum_truncate; /* enables vacuum to truncate a relation */
- bool vacuum_truncate_set; /* whether vacuum_truncate is set */
+ pg_ternary vacuum_truncate; /* enables vacuum to truncate a relation */
/*
* Fraction of pages in a relation that vacuum can eagerly scan and fail
diff --git a/src/test/regress/expected/reloptions.out b/src/test/regress/expected/reloptions.out
index 9de19b4e3f1..1c99f79ab01 100644
--- a/src/test/regress/expected/reloptions.out
+++ b/src/test/regress/expected/reloptions.out
@@ -98,6 +98,42 @@ SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass;
{fillfactor=13,autovacuum_enabled=false}
(1 row)
+-- Tests for future (FIXME) ternary options
+-- behave as boolean option: accept unassigned name and truncated value
+DROP TABLE reloptions_test;
+CREATE TABLE reloptions_test(i INT) WITH (vacuum_truncate);
+SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass;
+ reloptions
+------------------------
+ {vacuum_truncate=true}
+(1 row)
+
+DROP TABLE reloptions_test;
+CREATE TABLE reloptions_test(i INT) WITH (vacuum_truncate=FaLS);
+SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass;
+ reloptions
+------------------------
+ {vacuum_truncate=fals}
+(1 row)
+
+-- preferred "true" alias is used when storing
+DROP TABLE reloptions_test;
+CREATE TABLE reloptions_test(i INT) WITH (vacuum_index_cleanup=on);
+SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass;
+ reloptions
+---------------------------
+ {vacuum_index_cleanup=on}
+(1 row)
+
+-- custom "third" value is available
+DROP TABLE reloptions_test;
+CREATE TABLE reloptions_test(i INT) WITH (vacuum_index_cleanup=auto);
+SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass;
+ reloptions
+-----------------------------
+ {vacuum_index_cleanup=auto}
+(1 row)
+
-- Test vacuum_truncate option
DROP TABLE reloptions_test;
CREATE TEMP TABLE reloptions_test(i INT NOT NULL, j text)
diff --git a/src/test/regress/sql/reloptions.sql b/src/test/regress/sql/reloptions.sql
index 24fbe0b478d..f5980dafcbc 100644
--- a/src/test/regress/sql/reloptions.sql
+++ b/src/test/regress/sql/reloptions.sql
@@ -59,6 +59,27 @@ UPDATE pg_class
ALTER TABLE reloptions_test RESET (illegal_option);
SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass;
+-- Tests for future (FIXME) ternary options
+
+-- behave as boolean option: accept unassigned name and truncated value
+DROP TABLE reloptions_test;
+CREATE TABLE reloptions_test(i INT) WITH (vacuum_truncate);
+SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass;
+
+DROP TABLE reloptions_test;
+CREATE TABLE reloptions_test(i INT) WITH (vacuum_truncate=FaLS);
+SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass;
+
+-- preferred "true" alias is used when storing
+DROP TABLE reloptions_test;
+CREATE TABLE reloptions_test(i INT) WITH (vacuum_index_cleanup=on);
+SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass;
+
+-- custom "third" value is available
+DROP TABLE reloptions_test;
+CREATE TABLE reloptions_test(i INT) WITH (vacuum_index_cleanup=auto);
+SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass;
+
-- Test vacuum_truncate option
DROP TABLE reloptions_test;
diff --git a/src/tools/pgindent/typedefs.list b/src/tools/pgindent/typedefs.list
index 3f3a888fd0e..1c8610fd46c 100644
--- a/src/tools/pgindent/typedefs.list
+++ b/src/tools/pgindent/typedefs.list
@@ -3952,6 +3952,7 @@ pg_sha512_ctx
pg_snapshot
pg_special_case
pg_stack_base_t
+pg_ternary
pg_time_t
pg_time_usec_t
pg_tz
@@ -4079,6 +4080,7 @@ relopt_kind
relopt_parse_elt
relopt_real
relopt_string
+relopt_ternary
relopt_type
relopt_value
relopts_validator
--
2.47.3
--c36t3fndxhkzbl5z--
^ permalink raw reply [nested|flat] 868+ messages in thread
* [PATCH v3] Introduce ternary reloptions
@ 2026-01-16 15:04 Álvaro Herrera <[email protected]>
0 siblings, 0 replies; 868+ messages in thread
From: Álvaro Herrera @ 2026-01-16 15:04 UTC (permalink / raw)
Introduce ternary reloption as a replacement for current
`vacuum_truncate` implementation. Remove the `vacuum_truncate_set`
separate flag and use TERNARY_UNSET instead.
This could also be used for other options such as `vacuum_index_cleanup`
and `buffering`, but lets get the scaffolding in first.
Discussion: https://postgr.es/m/3474141.usfYGdeWWP@thinkpad-pgpro
---
src/backend/access/common/reloptions.c | 137 ++++++++++++++++++-----
src/backend/commands/vacuum.c | 4 +-
src/include/access/reloptions.h | 27 ++---
src/include/postgres.h | 15 +++
src/include/utils/rel.h | 3 +-
src/test/regress/expected/reloptions.out | 36 ++++++
src/test/regress/sql/reloptions.sql | 21 ++++
src/tools/pgindent/typedefs.list | 2 +
8 files changed, 202 insertions(+), 43 deletions(-)
diff --git a/src/backend/access/common/reloptions.c b/src/backend/access/common/reloptions.c
index 0b83f98ed5f..6f1f577581a 100644
--- a/src/backend/access/common/reloptions.c
+++ b/src/backend/access/common/reloptions.c
@@ -40,9 +40,9 @@
*
* To add an option:
*
- * (i) decide on a type (bool, integer, real, enum, string), name, default
- * value, upper and lower bounds (if applicable); for strings, consider a
- * validation routine.
+ * (i) decide on a type (bool, ternary, integer, real, enum, string), name,
+ * default value, upper and lower bounds (if applicable); for strings,
+ * consider a validation routine.
* (ii) add a record below (or use add_<type>_reloption).
* (iii) add it to the appropriate options struct (perhaps StdRdOptions)
* (iv) add it to the appropriate handling routine (perhaps
@@ -147,15 +147,6 @@ static relopt_bool boolRelOpts[] =
},
false
},
- {
- {
- "vacuum_truncate",
- "Enables vacuum to truncate empty pages at the end of this table",
- RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
- ShareUpdateExclusiveLock
- },
- true
- },
{
{
"deduplicate_items",
@@ -170,6 +161,25 @@ static relopt_bool boolRelOpts[] =
{{NULL}}
};
+static relopt_ternary ternaryRelOpts[] =
+{
+ {
+ {
+ "vacuum_truncate",
+ "Enables vacuum to truncate empty pages at the end of this table",
+ RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
+ ShareUpdateExclusiveLock
+ },
+ TERNARY_UNSET
+ },
+ /* list terminator */
+ {
+ {
+ NULL
+ }
+ }
+};
+
static relopt_int intRelOpts[] =
{
{
@@ -609,6 +619,13 @@ initialize_reloptions(void)
boolRelOpts[i].gen.lockmode));
j++;
}
+ for (i = 0; ternaryRelOpts[i].gen.name; i++)
+ {
+ Assert(DoLockModesConflict(ternaryRelOpts[i].gen.lockmode,
+ ternaryRelOpts[i].gen.lockmode));
+ j++;
+ }
+
for (i = 0; intRelOpts[i].gen.name; i++)
{
Assert(DoLockModesConflict(intRelOpts[i].gen.lockmode,
@@ -649,6 +666,14 @@ initialize_reloptions(void)
j++;
}
+ for (i = 0; ternaryRelOpts[i].gen.name; i++)
+ {
+ relOpts[j] = &ternaryRelOpts[i].gen;
+ relOpts[j]->type = RELOPT_TYPE_TERNARY;
+ relOpts[j]->namelen = strlen(relOpts[j]->name);
+ j++;
+ }
+
for (i = 0; intRelOpts[i].gen.name; i++)
{
relOpts[j] = &intRelOpts[i].gen;
@@ -809,6 +834,9 @@ allocate_reloption(bits32 kinds, int type, const char *name, const char *desc,
case RELOPT_TYPE_BOOL:
size = sizeof(relopt_bool);
break;
+ case RELOPT_TYPE_TERNARY:
+ size = sizeof(relopt_ternary);
+ break;
case RELOPT_TYPE_INT:
size = sizeof(relopt_int);
break;
@@ -892,6 +920,57 @@ add_local_bool_reloption(local_relopts *relopts, const char *name,
add_local_reloption(relopts, (relopt_gen *) newoption, offset);
}
+/*
+ * init_ternary_reloption
+ * Allocate and initialize a new ternary reloption
+ */
+static relopt_ternary *
+init_ternary_reloption(bits32 kinds, const char *name, const char *desc,
+ pg_ternary default_val, LOCKMODE lockmode)
+{
+ relopt_ternary *newoption;
+
+ newoption = (relopt_ternary *)
+ allocate_reloption(kinds, RELOPT_TYPE_TERNARY, name, desc, lockmode);
+ newoption->default_val = default_val;
+
+ return newoption;
+}
+
+/*
+ * add_ternary_reloption
+ * Add a new ternary reloption
+ */
+void
+add_ternary_reloption(bits32 kinds, const char *name, const char *desc,
+ pg_ternary default_val, LOCKMODE lockmode)
+{
+ relopt_ternary *newoption;
+
+ newoption =
+ init_ternary_reloption(kinds, name, desc, default_val, lockmode);
+
+ add_reloption((relopt_gen *) newoption);
+}
+
+/*
+ * add_local_ternary_reloption
+ * Add a new ternary local reloption
+ *
+ * 'offset' is offset of ternary-typed field.
+ */
+void
+add_local_ternary_reloption(local_relopts *relopts, const char *name,
+ const char *desc, pg_ternary default_val,
+ int offset)
+{
+ relopt_ternary *newoption;
+
+ newoption =
+ init_ternary_reloption(RELOPT_KIND_LOCAL, name, desc, default_val, 0);
+
+ add_local_reloption(relopts, (relopt_gen *) newoption, offset);
+}
/*
* init_real_reloption
@@ -1626,6 +1705,19 @@ parse_one_reloption(relopt_value *option, char *text_str, int text_len,
option->gen->name, value)));
}
break;
+ case RELOPT_TYPE_TERNARY:
+ {
+ bool b;
+
+ parsed = parse_bool(value, &b);
+ option->values.ternary_val = b ? TERNARY_TRUE : TERNARY_FALSE;
+ if (validate && !parsed)
+ ereport(ERROR,
+ (errcode(ERRCODE_INVALID_PARAMETER_VALUE),
+ errmsg("invalid value for ternary option \"%s\": %s",
+ option->gen->name, value)));
+ }
+ break;
case RELOPT_TYPE_INT:
{
relopt_int *optint = (relopt_int *) option->gen;
@@ -1789,17 +1881,6 @@ fillRelOptions(void *rdopts, Size basesize,
char *itempos = ((char *) rdopts) + elems[j].offset;
char *string_val;
- /*
- * If isset_offset is provided, store whether the reloption is
- * set there.
- */
- if (elems[j].isset_offset > 0)
- {
- char *setpos = ((char *) rdopts) + elems[j].isset_offset;
-
- *(bool *) setpos = options[i].isset;
- }
-
switch (options[i].gen->type)
{
case RELOPT_TYPE_BOOL:
@@ -1807,6 +1888,11 @@ fillRelOptions(void *rdopts, Size basesize,
options[i].values.bool_val :
((relopt_bool *) options[i].gen)->default_val;
break;
+ case RELOPT_TYPE_TERNARY:
+ *(pg_ternary *) itempos = options[i].isset ?
+ options[i].values.ternary_val :
+ ((relopt_ternary *) options[i].gen)->default_val;
+ break;
case RELOPT_TYPE_INT:
*(int *) itempos = options[i].isset ?
options[i].values.int_val :
@@ -1923,8 +2009,8 @@ default_reloptions(Datum reloptions, bool validate, relopt_kind kind)
offsetof(StdRdOptions, parallel_workers)},
{"vacuum_index_cleanup", RELOPT_TYPE_ENUM,
offsetof(StdRdOptions, vacuum_index_cleanup)},
- {"vacuum_truncate", RELOPT_TYPE_BOOL,
- offsetof(StdRdOptions, vacuum_truncate), offsetof(StdRdOptions, vacuum_truncate_set)},
+ {"vacuum_truncate", RELOPT_TYPE_TERNARY,
+ offsetof(StdRdOptions, vacuum_truncate)},
{"vacuum_max_eager_freeze_failure_rate", RELOPT_TYPE_REAL,
offsetof(StdRdOptions, vacuum_max_eager_freeze_failure_rate)}
};
@@ -2004,7 +2090,6 @@ build_local_reloptions(local_relopts *relopts, Datum options, bool validate)
elems[i].optname = opt->option->name;
elems[i].opttype = opt->option->type;
elems[i].offset = opt->offset;
- elems[i].isset_offset = 0; /* not supported for local relopts yet */
i++;
}
diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c
index aa4fbec143f..696eab9bd97 100644
--- a/src/backend/commands/vacuum.c
+++ b/src/backend/commands/vacuum.c
@@ -2224,9 +2224,9 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams params,
{
StdRdOptions *opts = (StdRdOptions *) rel->rd_options;
- if (opts && opts->vacuum_truncate_set)
+ if (opts && opts->vacuum_truncate != TERNARY_UNSET)
{
- if (opts->vacuum_truncate)
+ if (opts->vacuum_truncate == TERNARY_TRUE)
params.truncate = VACOPTVALUE_ENABLED;
else
params.truncate = VACOPTVALUE_DISABLED;
diff --git a/src/include/access/reloptions.h b/src/include/access/reloptions.h
index 2f08e1b0cf0..dfbef2babf2 100644
--- a/src/include/access/reloptions.h
+++ b/src/include/access/reloptions.h
@@ -29,6 +29,7 @@
typedef enum relopt_type
{
RELOPT_TYPE_BOOL,
+ RELOPT_TYPE_TERNARY, /* on, off, unset */
RELOPT_TYPE_INT,
RELOPT_TYPE_REAL,
RELOPT_TYPE_ENUM,
@@ -80,6 +81,7 @@ typedef struct relopt_value
union
{
bool bool_val;
+ pg_ternary ternary_val;
int int_val;
double real_val;
int enum_val;
@@ -94,6 +96,12 @@ typedef struct relopt_bool
bool default_val;
} relopt_bool;
+typedef struct relopt_ternary
+{
+ relopt_gen gen;
+ int default_val;
+} relopt_ternary;
+
typedef struct relopt_int
{
relopt_gen gen;
@@ -152,19 +160,6 @@ typedef struct
const char *optname; /* option's name */
relopt_type opttype; /* option's datatype */
int offset; /* offset of field in result struct */
-
- /*
- * isset_offset is an optional offset of a field in the result struct that
- * stores whether the option is explicitly set for the relation or if it
- * just picked up the default value. In most cases, this can be
- * accomplished by giving the reloption a special out-of-range default
- * value (e.g., some integer reloptions use -2), but this isn't always
- * possible. For example, a Boolean reloption cannot be given an
- * out-of-range default, so we need another way to discover the source of
- * its value. This offset is only used if given a value greater than
- * zero.
- */
- int isset_offset;
} relopt_parse_elt;
/* Local reloption definition */
@@ -195,6 +190,9 @@ typedef struct local_relopts
extern relopt_kind add_reloption_kind(void);
extern void add_bool_reloption(bits32 kinds, const char *name, const char *desc,
bool default_val, LOCKMODE lockmode);
+extern void add_ternary_reloption(bits32 kinds, const char *name,
+ const char *desc, pg_ternary default_val,
+ LOCKMODE lockmode);
extern void add_int_reloption(bits32 kinds, const char *name, const char *desc,
int default_val, int min_val, int max_val,
LOCKMODE lockmode);
@@ -214,6 +212,9 @@ extern void register_reloptions_validator(local_relopts *relopts,
extern void add_local_bool_reloption(local_relopts *relopts, const char *name,
const char *desc, bool default_val,
int offset);
+extern void add_local_ternary_reloption(local_relopts *relopts,
+ const char *name, const char *desc,
+ pg_ternary default_val, int offset);
extern void add_local_int_reloption(local_relopts *relopts, const char *name,
const char *desc, int default_val,
int min_val, int max_val, int offset);
diff --git a/src/include/postgres.h b/src/include/postgres.h
index 7d93fbce709..47c88780776 100644
--- a/src/include/postgres.h
+++ b/src/include/postgres.h
@@ -543,6 +543,21 @@ Float8GetDatum(float8 X)
* ----------------------------------------------------------------
*/
+/*
+ * pg_ternary
+ * Boolean value with an extra "unset" value
+ *
+ * This enum can be used for values that want to distinguish between true,
+ * false, and unset.
+*/
+
+typedef enum pg_ternary
+{
+ TERNARY_FALSE = 0,
+ TERNARY_TRUE = 1,
+ TERNARY_UNSET = -1
+} pg_ternary;
+
/*
* NON_EXEC_STATIC: It's sometimes useful to define a variable or function
* that is normally static but extern when using EXEC_BACKEND (see
diff --git a/src/include/utils/rel.h b/src/include/utils/rel.h
index d03ab247788..5feb18a5373 100644
--- a/src/include/utils/rel.h
+++ b/src/include/utils/rel.h
@@ -347,8 +347,7 @@ typedef struct StdRdOptions
bool user_catalog_table; /* use as an additional catalog relation */
int parallel_workers; /* max number of parallel workers */
StdRdOptIndexCleanup vacuum_index_cleanup; /* controls index vacuuming */
- bool vacuum_truncate; /* enables vacuum to truncate a relation */
- bool vacuum_truncate_set; /* whether vacuum_truncate is set */
+ pg_ternary vacuum_truncate; /* enables vacuum to truncate a relation */
/*
* Fraction of pages in a relation that vacuum can eagerly scan and fail
diff --git a/src/test/regress/expected/reloptions.out b/src/test/regress/expected/reloptions.out
index 9de19b4e3f1..1c99f79ab01 100644
--- a/src/test/regress/expected/reloptions.out
+++ b/src/test/regress/expected/reloptions.out
@@ -98,6 +98,42 @@ SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass;
{fillfactor=13,autovacuum_enabled=false}
(1 row)
+-- Tests for future (FIXME) ternary options
+-- behave as boolean option: accept unassigned name and truncated value
+DROP TABLE reloptions_test;
+CREATE TABLE reloptions_test(i INT) WITH (vacuum_truncate);
+SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass;
+ reloptions
+------------------------
+ {vacuum_truncate=true}
+(1 row)
+
+DROP TABLE reloptions_test;
+CREATE TABLE reloptions_test(i INT) WITH (vacuum_truncate=FaLS);
+SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass;
+ reloptions
+------------------------
+ {vacuum_truncate=fals}
+(1 row)
+
+-- preferred "true" alias is used when storing
+DROP TABLE reloptions_test;
+CREATE TABLE reloptions_test(i INT) WITH (vacuum_index_cleanup=on);
+SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass;
+ reloptions
+---------------------------
+ {vacuum_index_cleanup=on}
+(1 row)
+
+-- custom "third" value is available
+DROP TABLE reloptions_test;
+CREATE TABLE reloptions_test(i INT) WITH (vacuum_index_cleanup=auto);
+SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass;
+ reloptions
+-----------------------------
+ {vacuum_index_cleanup=auto}
+(1 row)
+
-- Test vacuum_truncate option
DROP TABLE reloptions_test;
CREATE TEMP TABLE reloptions_test(i INT NOT NULL, j text)
diff --git a/src/test/regress/sql/reloptions.sql b/src/test/regress/sql/reloptions.sql
index 24fbe0b478d..f5980dafcbc 100644
--- a/src/test/regress/sql/reloptions.sql
+++ b/src/test/regress/sql/reloptions.sql
@@ -59,6 +59,27 @@ UPDATE pg_class
ALTER TABLE reloptions_test RESET (illegal_option);
SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass;
+-- Tests for future (FIXME) ternary options
+
+-- behave as boolean option: accept unassigned name and truncated value
+DROP TABLE reloptions_test;
+CREATE TABLE reloptions_test(i INT) WITH (vacuum_truncate);
+SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass;
+
+DROP TABLE reloptions_test;
+CREATE TABLE reloptions_test(i INT) WITH (vacuum_truncate=FaLS);
+SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass;
+
+-- preferred "true" alias is used when storing
+DROP TABLE reloptions_test;
+CREATE TABLE reloptions_test(i INT) WITH (vacuum_index_cleanup=on);
+SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass;
+
+-- custom "third" value is available
+DROP TABLE reloptions_test;
+CREATE TABLE reloptions_test(i INT) WITH (vacuum_index_cleanup=auto);
+SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass;
+
-- Test vacuum_truncate option
DROP TABLE reloptions_test;
diff --git a/src/tools/pgindent/typedefs.list b/src/tools/pgindent/typedefs.list
index 3f3a888fd0e..1c8610fd46c 100644
--- a/src/tools/pgindent/typedefs.list
+++ b/src/tools/pgindent/typedefs.list
@@ -3952,6 +3952,7 @@ pg_sha512_ctx
pg_snapshot
pg_special_case
pg_stack_base_t
+pg_ternary
pg_time_t
pg_time_usec_t
pg_tz
@@ -4079,6 +4080,7 @@ relopt_kind
relopt_parse_elt
relopt_real
relopt_string
+relopt_ternary
relopt_type
relopt_value
relopts_validator
--
2.47.3
--c36t3fndxhkzbl5z--
^ permalink raw reply [nested|flat] 868+ messages in thread
* [PATCH v3] Introduce ternary reloptions
@ 2026-01-16 15:04 Álvaro Herrera <[email protected]>
0 siblings, 0 replies; 868+ messages in thread
From: Álvaro Herrera @ 2026-01-16 15:04 UTC (permalink / raw)
Introduce ternary reloption as a replacement for current
`vacuum_truncate` implementation. Remove the `vacuum_truncate_set`
separate flag and use TERNARY_UNSET instead.
This could also be used for other options such as `vacuum_index_cleanup`
and `buffering`, but lets get the scaffolding in first.
Discussion: https://postgr.es/m/3474141.usfYGdeWWP@thinkpad-pgpro
---
src/backend/access/common/reloptions.c | 137 ++++++++++++++++++-----
src/backend/commands/vacuum.c | 4 +-
src/include/access/reloptions.h | 27 ++---
src/include/postgres.h | 15 +++
src/include/utils/rel.h | 3 +-
src/test/regress/expected/reloptions.out | 36 ++++++
src/test/regress/sql/reloptions.sql | 21 ++++
src/tools/pgindent/typedefs.list | 2 +
8 files changed, 202 insertions(+), 43 deletions(-)
diff --git a/src/backend/access/common/reloptions.c b/src/backend/access/common/reloptions.c
index 0b83f98ed5f..6f1f577581a 100644
--- a/src/backend/access/common/reloptions.c
+++ b/src/backend/access/common/reloptions.c
@@ -40,9 +40,9 @@
*
* To add an option:
*
- * (i) decide on a type (bool, integer, real, enum, string), name, default
- * value, upper and lower bounds (if applicable); for strings, consider a
- * validation routine.
+ * (i) decide on a type (bool, ternary, integer, real, enum, string), name,
+ * default value, upper and lower bounds (if applicable); for strings,
+ * consider a validation routine.
* (ii) add a record below (or use add_<type>_reloption).
* (iii) add it to the appropriate options struct (perhaps StdRdOptions)
* (iv) add it to the appropriate handling routine (perhaps
@@ -147,15 +147,6 @@ static relopt_bool boolRelOpts[] =
},
false
},
- {
- {
- "vacuum_truncate",
- "Enables vacuum to truncate empty pages at the end of this table",
- RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
- ShareUpdateExclusiveLock
- },
- true
- },
{
{
"deduplicate_items",
@@ -170,6 +161,25 @@ static relopt_bool boolRelOpts[] =
{{NULL}}
};
+static relopt_ternary ternaryRelOpts[] =
+{
+ {
+ {
+ "vacuum_truncate",
+ "Enables vacuum to truncate empty pages at the end of this table",
+ RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
+ ShareUpdateExclusiveLock
+ },
+ TERNARY_UNSET
+ },
+ /* list terminator */
+ {
+ {
+ NULL
+ }
+ }
+};
+
static relopt_int intRelOpts[] =
{
{
@@ -609,6 +619,13 @@ initialize_reloptions(void)
boolRelOpts[i].gen.lockmode));
j++;
}
+ for (i = 0; ternaryRelOpts[i].gen.name; i++)
+ {
+ Assert(DoLockModesConflict(ternaryRelOpts[i].gen.lockmode,
+ ternaryRelOpts[i].gen.lockmode));
+ j++;
+ }
+
for (i = 0; intRelOpts[i].gen.name; i++)
{
Assert(DoLockModesConflict(intRelOpts[i].gen.lockmode,
@@ -649,6 +666,14 @@ initialize_reloptions(void)
j++;
}
+ for (i = 0; ternaryRelOpts[i].gen.name; i++)
+ {
+ relOpts[j] = &ternaryRelOpts[i].gen;
+ relOpts[j]->type = RELOPT_TYPE_TERNARY;
+ relOpts[j]->namelen = strlen(relOpts[j]->name);
+ j++;
+ }
+
for (i = 0; intRelOpts[i].gen.name; i++)
{
relOpts[j] = &intRelOpts[i].gen;
@@ -809,6 +834,9 @@ allocate_reloption(bits32 kinds, int type, const char *name, const char *desc,
case RELOPT_TYPE_BOOL:
size = sizeof(relopt_bool);
break;
+ case RELOPT_TYPE_TERNARY:
+ size = sizeof(relopt_ternary);
+ break;
case RELOPT_TYPE_INT:
size = sizeof(relopt_int);
break;
@@ -892,6 +920,57 @@ add_local_bool_reloption(local_relopts *relopts, const char *name,
add_local_reloption(relopts, (relopt_gen *) newoption, offset);
}
+/*
+ * init_ternary_reloption
+ * Allocate and initialize a new ternary reloption
+ */
+static relopt_ternary *
+init_ternary_reloption(bits32 kinds, const char *name, const char *desc,
+ pg_ternary default_val, LOCKMODE lockmode)
+{
+ relopt_ternary *newoption;
+
+ newoption = (relopt_ternary *)
+ allocate_reloption(kinds, RELOPT_TYPE_TERNARY, name, desc, lockmode);
+ newoption->default_val = default_val;
+
+ return newoption;
+}
+
+/*
+ * add_ternary_reloption
+ * Add a new ternary reloption
+ */
+void
+add_ternary_reloption(bits32 kinds, const char *name, const char *desc,
+ pg_ternary default_val, LOCKMODE lockmode)
+{
+ relopt_ternary *newoption;
+
+ newoption =
+ init_ternary_reloption(kinds, name, desc, default_val, lockmode);
+
+ add_reloption((relopt_gen *) newoption);
+}
+
+/*
+ * add_local_ternary_reloption
+ * Add a new ternary local reloption
+ *
+ * 'offset' is offset of ternary-typed field.
+ */
+void
+add_local_ternary_reloption(local_relopts *relopts, const char *name,
+ const char *desc, pg_ternary default_val,
+ int offset)
+{
+ relopt_ternary *newoption;
+
+ newoption =
+ init_ternary_reloption(RELOPT_KIND_LOCAL, name, desc, default_val, 0);
+
+ add_local_reloption(relopts, (relopt_gen *) newoption, offset);
+}
/*
* init_real_reloption
@@ -1626,6 +1705,19 @@ parse_one_reloption(relopt_value *option, char *text_str, int text_len,
option->gen->name, value)));
}
break;
+ case RELOPT_TYPE_TERNARY:
+ {
+ bool b;
+
+ parsed = parse_bool(value, &b);
+ option->values.ternary_val = b ? TERNARY_TRUE : TERNARY_FALSE;
+ if (validate && !parsed)
+ ereport(ERROR,
+ (errcode(ERRCODE_INVALID_PARAMETER_VALUE),
+ errmsg("invalid value for ternary option \"%s\": %s",
+ option->gen->name, value)));
+ }
+ break;
case RELOPT_TYPE_INT:
{
relopt_int *optint = (relopt_int *) option->gen;
@@ -1789,17 +1881,6 @@ fillRelOptions(void *rdopts, Size basesize,
char *itempos = ((char *) rdopts) + elems[j].offset;
char *string_val;
- /*
- * If isset_offset is provided, store whether the reloption is
- * set there.
- */
- if (elems[j].isset_offset > 0)
- {
- char *setpos = ((char *) rdopts) + elems[j].isset_offset;
-
- *(bool *) setpos = options[i].isset;
- }
-
switch (options[i].gen->type)
{
case RELOPT_TYPE_BOOL:
@@ -1807,6 +1888,11 @@ fillRelOptions(void *rdopts, Size basesize,
options[i].values.bool_val :
((relopt_bool *) options[i].gen)->default_val;
break;
+ case RELOPT_TYPE_TERNARY:
+ *(pg_ternary *) itempos = options[i].isset ?
+ options[i].values.ternary_val :
+ ((relopt_ternary *) options[i].gen)->default_val;
+ break;
case RELOPT_TYPE_INT:
*(int *) itempos = options[i].isset ?
options[i].values.int_val :
@@ -1923,8 +2009,8 @@ default_reloptions(Datum reloptions, bool validate, relopt_kind kind)
offsetof(StdRdOptions, parallel_workers)},
{"vacuum_index_cleanup", RELOPT_TYPE_ENUM,
offsetof(StdRdOptions, vacuum_index_cleanup)},
- {"vacuum_truncate", RELOPT_TYPE_BOOL,
- offsetof(StdRdOptions, vacuum_truncate), offsetof(StdRdOptions, vacuum_truncate_set)},
+ {"vacuum_truncate", RELOPT_TYPE_TERNARY,
+ offsetof(StdRdOptions, vacuum_truncate)},
{"vacuum_max_eager_freeze_failure_rate", RELOPT_TYPE_REAL,
offsetof(StdRdOptions, vacuum_max_eager_freeze_failure_rate)}
};
@@ -2004,7 +2090,6 @@ build_local_reloptions(local_relopts *relopts, Datum options, bool validate)
elems[i].optname = opt->option->name;
elems[i].opttype = opt->option->type;
elems[i].offset = opt->offset;
- elems[i].isset_offset = 0; /* not supported for local relopts yet */
i++;
}
diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c
index aa4fbec143f..696eab9bd97 100644
--- a/src/backend/commands/vacuum.c
+++ b/src/backend/commands/vacuum.c
@@ -2224,9 +2224,9 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams params,
{
StdRdOptions *opts = (StdRdOptions *) rel->rd_options;
- if (opts && opts->vacuum_truncate_set)
+ if (opts && opts->vacuum_truncate != TERNARY_UNSET)
{
- if (opts->vacuum_truncate)
+ if (opts->vacuum_truncate == TERNARY_TRUE)
params.truncate = VACOPTVALUE_ENABLED;
else
params.truncate = VACOPTVALUE_DISABLED;
diff --git a/src/include/access/reloptions.h b/src/include/access/reloptions.h
index 2f08e1b0cf0..dfbef2babf2 100644
--- a/src/include/access/reloptions.h
+++ b/src/include/access/reloptions.h
@@ -29,6 +29,7 @@
typedef enum relopt_type
{
RELOPT_TYPE_BOOL,
+ RELOPT_TYPE_TERNARY, /* on, off, unset */
RELOPT_TYPE_INT,
RELOPT_TYPE_REAL,
RELOPT_TYPE_ENUM,
@@ -80,6 +81,7 @@ typedef struct relopt_value
union
{
bool bool_val;
+ pg_ternary ternary_val;
int int_val;
double real_val;
int enum_val;
@@ -94,6 +96,12 @@ typedef struct relopt_bool
bool default_val;
} relopt_bool;
+typedef struct relopt_ternary
+{
+ relopt_gen gen;
+ int default_val;
+} relopt_ternary;
+
typedef struct relopt_int
{
relopt_gen gen;
@@ -152,19 +160,6 @@ typedef struct
const char *optname; /* option's name */
relopt_type opttype; /* option's datatype */
int offset; /* offset of field in result struct */
-
- /*
- * isset_offset is an optional offset of a field in the result struct that
- * stores whether the option is explicitly set for the relation or if it
- * just picked up the default value. In most cases, this can be
- * accomplished by giving the reloption a special out-of-range default
- * value (e.g., some integer reloptions use -2), but this isn't always
- * possible. For example, a Boolean reloption cannot be given an
- * out-of-range default, so we need another way to discover the source of
- * its value. This offset is only used if given a value greater than
- * zero.
- */
- int isset_offset;
} relopt_parse_elt;
/* Local reloption definition */
@@ -195,6 +190,9 @@ typedef struct local_relopts
extern relopt_kind add_reloption_kind(void);
extern void add_bool_reloption(bits32 kinds, const char *name, const char *desc,
bool default_val, LOCKMODE lockmode);
+extern void add_ternary_reloption(bits32 kinds, const char *name,
+ const char *desc, pg_ternary default_val,
+ LOCKMODE lockmode);
extern void add_int_reloption(bits32 kinds, const char *name, const char *desc,
int default_val, int min_val, int max_val,
LOCKMODE lockmode);
@@ -214,6 +212,9 @@ extern void register_reloptions_validator(local_relopts *relopts,
extern void add_local_bool_reloption(local_relopts *relopts, const char *name,
const char *desc, bool default_val,
int offset);
+extern void add_local_ternary_reloption(local_relopts *relopts,
+ const char *name, const char *desc,
+ pg_ternary default_val, int offset);
extern void add_local_int_reloption(local_relopts *relopts, const char *name,
const char *desc, int default_val,
int min_val, int max_val, int offset);
diff --git a/src/include/postgres.h b/src/include/postgres.h
index 7d93fbce709..47c88780776 100644
--- a/src/include/postgres.h
+++ b/src/include/postgres.h
@@ -543,6 +543,21 @@ Float8GetDatum(float8 X)
* ----------------------------------------------------------------
*/
+/*
+ * pg_ternary
+ * Boolean value with an extra "unset" value
+ *
+ * This enum can be used for values that want to distinguish between true,
+ * false, and unset.
+*/
+
+typedef enum pg_ternary
+{
+ TERNARY_FALSE = 0,
+ TERNARY_TRUE = 1,
+ TERNARY_UNSET = -1
+} pg_ternary;
+
/*
* NON_EXEC_STATIC: It's sometimes useful to define a variable or function
* that is normally static but extern when using EXEC_BACKEND (see
diff --git a/src/include/utils/rel.h b/src/include/utils/rel.h
index d03ab247788..5feb18a5373 100644
--- a/src/include/utils/rel.h
+++ b/src/include/utils/rel.h
@@ -347,8 +347,7 @@ typedef struct StdRdOptions
bool user_catalog_table; /* use as an additional catalog relation */
int parallel_workers; /* max number of parallel workers */
StdRdOptIndexCleanup vacuum_index_cleanup; /* controls index vacuuming */
- bool vacuum_truncate; /* enables vacuum to truncate a relation */
- bool vacuum_truncate_set; /* whether vacuum_truncate is set */
+ pg_ternary vacuum_truncate; /* enables vacuum to truncate a relation */
/*
* Fraction of pages in a relation that vacuum can eagerly scan and fail
diff --git a/src/test/regress/expected/reloptions.out b/src/test/regress/expected/reloptions.out
index 9de19b4e3f1..1c99f79ab01 100644
--- a/src/test/regress/expected/reloptions.out
+++ b/src/test/regress/expected/reloptions.out
@@ -98,6 +98,42 @@ SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass;
{fillfactor=13,autovacuum_enabled=false}
(1 row)
+-- Tests for future (FIXME) ternary options
+-- behave as boolean option: accept unassigned name and truncated value
+DROP TABLE reloptions_test;
+CREATE TABLE reloptions_test(i INT) WITH (vacuum_truncate);
+SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass;
+ reloptions
+------------------------
+ {vacuum_truncate=true}
+(1 row)
+
+DROP TABLE reloptions_test;
+CREATE TABLE reloptions_test(i INT) WITH (vacuum_truncate=FaLS);
+SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass;
+ reloptions
+------------------------
+ {vacuum_truncate=fals}
+(1 row)
+
+-- preferred "true" alias is used when storing
+DROP TABLE reloptions_test;
+CREATE TABLE reloptions_test(i INT) WITH (vacuum_index_cleanup=on);
+SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass;
+ reloptions
+---------------------------
+ {vacuum_index_cleanup=on}
+(1 row)
+
+-- custom "third" value is available
+DROP TABLE reloptions_test;
+CREATE TABLE reloptions_test(i INT) WITH (vacuum_index_cleanup=auto);
+SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass;
+ reloptions
+-----------------------------
+ {vacuum_index_cleanup=auto}
+(1 row)
+
-- Test vacuum_truncate option
DROP TABLE reloptions_test;
CREATE TEMP TABLE reloptions_test(i INT NOT NULL, j text)
diff --git a/src/test/regress/sql/reloptions.sql b/src/test/regress/sql/reloptions.sql
index 24fbe0b478d..f5980dafcbc 100644
--- a/src/test/regress/sql/reloptions.sql
+++ b/src/test/regress/sql/reloptions.sql
@@ -59,6 +59,27 @@ UPDATE pg_class
ALTER TABLE reloptions_test RESET (illegal_option);
SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass;
+-- Tests for future (FIXME) ternary options
+
+-- behave as boolean option: accept unassigned name and truncated value
+DROP TABLE reloptions_test;
+CREATE TABLE reloptions_test(i INT) WITH (vacuum_truncate);
+SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass;
+
+DROP TABLE reloptions_test;
+CREATE TABLE reloptions_test(i INT) WITH (vacuum_truncate=FaLS);
+SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass;
+
+-- preferred "true" alias is used when storing
+DROP TABLE reloptions_test;
+CREATE TABLE reloptions_test(i INT) WITH (vacuum_index_cleanup=on);
+SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass;
+
+-- custom "third" value is available
+DROP TABLE reloptions_test;
+CREATE TABLE reloptions_test(i INT) WITH (vacuum_index_cleanup=auto);
+SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass;
+
-- Test vacuum_truncate option
DROP TABLE reloptions_test;
diff --git a/src/tools/pgindent/typedefs.list b/src/tools/pgindent/typedefs.list
index 3f3a888fd0e..1c8610fd46c 100644
--- a/src/tools/pgindent/typedefs.list
+++ b/src/tools/pgindent/typedefs.list
@@ -3952,6 +3952,7 @@ pg_sha512_ctx
pg_snapshot
pg_special_case
pg_stack_base_t
+pg_ternary
pg_time_t
pg_time_usec_t
pg_tz
@@ -4079,6 +4080,7 @@ relopt_kind
relopt_parse_elt
relopt_real
relopt_string
+relopt_ternary
relopt_type
relopt_value
relopts_validator
--
2.47.3
--c36t3fndxhkzbl5z--
^ permalink raw reply [nested|flat] 868+ messages in thread
* [PATCH v3] Introduce ternary reloptions
@ 2026-01-16 15:04 Álvaro Herrera <[email protected]>
0 siblings, 0 replies; 868+ messages in thread
From: Álvaro Herrera @ 2026-01-16 15:04 UTC (permalink / raw)
Introduce ternary reloption as a replacement for current
`vacuum_truncate` implementation. Remove the `vacuum_truncate_set`
separate flag and use TERNARY_UNSET instead.
This could also be used for other options such as `vacuum_index_cleanup`
and `buffering`, but lets get the scaffolding in first.
Discussion: https://postgr.es/m/3474141.usfYGdeWWP@thinkpad-pgpro
---
src/backend/access/common/reloptions.c | 137 ++++++++++++++++++-----
src/backend/commands/vacuum.c | 4 +-
src/include/access/reloptions.h | 27 ++---
src/include/postgres.h | 15 +++
src/include/utils/rel.h | 3 +-
src/test/regress/expected/reloptions.out | 36 ++++++
src/test/regress/sql/reloptions.sql | 21 ++++
src/tools/pgindent/typedefs.list | 2 +
8 files changed, 202 insertions(+), 43 deletions(-)
diff --git a/src/backend/access/common/reloptions.c b/src/backend/access/common/reloptions.c
index 0b83f98ed5f..6f1f577581a 100644
--- a/src/backend/access/common/reloptions.c
+++ b/src/backend/access/common/reloptions.c
@@ -40,9 +40,9 @@
*
* To add an option:
*
- * (i) decide on a type (bool, integer, real, enum, string), name, default
- * value, upper and lower bounds (if applicable); for strings, consider a
- * validation routine.
+ * (i) decide on a type (bool, ternary, integer, real, enum, string), name,
+ * default value, upper and lower bounds (if applicable); for strings,
+ * consider a validation routine.
* (ii) add a record below (or use add_<type>_reloption).
* (iii) add it to the appropriate options struct (perhaps StdRdOptions)
* (iv) add it to the appropriate handling routine (perhaps
@@ -147,15 +147,6 @@ static relopt_bool boolRelOpts[] =
},
false
},
- {
- {
- "vacuum_truncate",
- "Enables vacuum to truncate empty pages at the end of this table",
- RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
- ShareUpdateExclusiveLock
- },
- true
- },
{
{
"deduplicate_items",
@@ -170,6 +161,25 @@ static relopt_bool boolRelOpts[] =
{{NULL}}
};
+static relopt_ternary ternaryRelOpts[] =
+{
+ {
+ {
+ "vacuum_truncate",
+ "Enables vacuum to truncate empty pages at the end of this table",
+ RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
+ ShareUpdateExclusiveLock
+ },
+ TERNARY_UNSET
+ },
+ /* list terminator */
+ {
+ {
+ NULL
+ }
+ }
+};
+
static relopt_int intRelOpts[] =
{
{
@@ -609,6 +619,13 @@ initialize_reloptions(void)
boolRelOpts[i].gen.lockmode));
j++;
}
+ for (i = 0; ternaryRelOpts[i].gen.name; i++)
+ {
+ Assert(DoLockModesConflict(ternaryRelOpts[i].gen.lockmode,
+ ternaryRelOpts[i].gen.lockmode));
+ j++;
+ }
+
for (i = 0; intRelOpts[i].gen.name; i++)
{
Assert(DoLockModesConflict(intRelOpts[i].gen.lockmode,
@@ -649,6 +666,14 @@ initialize_reloptions(void)
j++;
}
+ for (i = 0; ternaryRelOpts[i].gen.name; i++)
+ {
+ relOpts[j] = &ternaryRelOpts[i].gen;
+ relOpts[j]->type = RELOPT_TYPE_TERNARY;
+ relOpts[j]->namelen = strlen(relOpts[j]->name);
+ j++;
+ }
+
for (i = 0; intRelOpts[i].gen.name; i++)
{
relOpts[j] = &intRelOpts[i].gen;
@@ -809,6 +834,9 @@ allocate_reloption(bits32 kinds, int type, const char *name, const char *desc,
case RELOPT_TYPE_BOOL:
size = sizeof(relopt_bool);
break;
+ case RELOPT_TYPE_TERNARY:
+ size = sizeof(relopt_ternary);
+ break;
case RELOPT_TYPE_INT:
size = sizeof(relopt_int);
break;
@@ -892,6 +920,57 @@ add_local_bool_reloption(local_relopts *relopts, const char *name,
add_local_reloption(relopts, (relopt_gen *) newoption, offset);
}
+/*
+ * init_ternary_reloption
+ * Allocate and initialize a new ternary reloption
+ */
+static relopt_ternary *
+init_ternary_reloption(bits32 kinds, const char *name, const char *desc,
+ pg_ternary default_val, LOCKMODE lockmode)
+{
+ relopt_ternary *newoption;
+
+ newoption = (relopt_ternary *)
+ allocate_reloption(kinds, RELOPT_TYPE_TERNARY, name, desc, lockmode);
+ newoption->default_val = default_val;
+
+ return newoption;
+}
+
+/*
+ * add_ternary_reloption
+ * Add a new ternary reloption
+ */
+void
+add_ternary_reloption(bits32 kinds, const char *name, const char *desc,
+ pg_ternary default_val, LOCKMODE lockmode)
+{
+ relopt_ternary *newoption;
+
+ newoption =
+ init_ternary_reloption(kinds, name, desc, default_val, lockmode);
+
+ add_reloption((relopt_gen *) newoption);
+}
+
+/*
+ * add_local_ternary_reloption
+ * Add a new ternary local reloption
+ *
+ * 'offset' is offset of ternary-typed field.
+ */
+void
+add_local_ternary_reloption(local_relopts *relopts, const char *name,
+ const char *desc, pg_ternary default_val,
+ int offset)
+{
+ relopt_ternary *newoption;
+
+ newoption =
+ init_ternary_reloption(RELOPT_KIND_LOCAL, name, desc, default_val, 0);
+
+ add_local_reloption(relopts, (relopt_gen *) newoption, offset);
+}
/*
* init_real_reloption
@@ -1626,6 +1705,19 @@ parse_one_reloption(relopt_value *option, char *text_str, int text_len,
option->gen->name, value)));
}
break;
+ case RELOPT_TYPE_TERNARY:
+ {
+ bool b;
+
+ parsed = parse_bool(value, &b);
+ option->values.ternary_val = b ? TERNARY_TRUE : TERNARY_FALSE;
+ if (validate && !parsed)
+ ereport(ERROR,
+ (errcode(ERRCODE_INVALID_PARAMETER_VALUE),
+ errmsg("invalid value for ternary option \"%s\": %s",
+ option->gen->name, value)));
+ }
+ break;
case RELOPT_TYPE_INT:
{
relopt_int *optint = (relopt_int *) option->gen;
@@ -1789,17 +1881,6 @@ fillRelOptions(void *rdopts, Size basesize,
char *itempos = ((char *) rdopts) + elems[j].offset;
char *string_val;
- /*
- * If isset_offset is provided, store whether the reloption is
- * set there.
- */
- if (elems[j].isset_offset > 0)
- {
- char *setpos = ((char *) rdopts) + elems[j].isset_offset;
-
- *(bool *) setpos = options[i].isset;
- }
-
switch (options[i].gen->type)
{
case RELOPT_TYPE_BOOL:
@@ -1807,6 +1888,11 @@ fillRelOptions(void *rdopts, Size basesize,
options[i].values.bool_val :
((relopt_bool *) options[i].gen)->default_val;
break;
+ case RELOPT_TYPE_TERNARY:
+ *(pg_ternary *) itempos = options[i].isset ?
+ options[i].values.ternary_val :
+ ((relopt_ternary *) options[i].gen)->default_val;
+ break;
case RELOPT_TYPE_INT:
*(int *) itempos = options[i].isset ?
options[i].values.int_val :
@@ -1923,8 +2009,8 @@ default_reloptions(Datum reloptions, bool validate, relopt_kind kind)
offsetof(StdRdOptions, parallel_workers)},
{"vacuum_index_cleanup", RELOPT_TYPE_ENUM,
offsetof(StdRdOptions, vacuum_index_cleanup)},
- {"vacuum_truncate", RELOPT_TYPE_BOOL,
- offsetof(StdRdOptions, vacuum_truncate), offsetof(StdRdOptions, vacuum_truncate_set)},
+ {"vacuum_truncate", RELOPT_TYPE_TERNARY,
+ offsetof(StdRdOptions, vacuum_truncate)},
{"vacuum_max_eager_freeze_failure_rate", RELOPT_TYPE_REAL,
offsetof(StdRdOptions, vacuum_max_eager_freeze_failure_rate)}
};
@@ -2004,7 +2090,6 @@ build_local_reloptions(local_relopts *relopts, Datum options, bool validate)
elems[i].optname = opt->option->name;
elems[i].opttype = opt->option->type;
elems[i].offset = opt->offset;
- elems[i].isset_offset = 0; /* not supported for local relopts yet */
i++;
}
diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c
index aa4fbec143f..696eab9bd97 100644
--- a/src/backend/commands/vacuum.c
+++ b/src/backend/commands/vacuum.c
@@ -2224,9 +2224,9 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams params,
{
StdRdOptions *opts = (StdRdOptions *) rel->rd_options;
- if (opts && opts->vacuum_truncate_set)
+ if (opts && opts->vacuum_truncate != TERNARY_UNSET)
{
- if (opts->vacuum_truncate)
+ if (opts->vacuum_truncate == TERNARY_TRUE)
params.truncate = VACOPTVALUE_ENABLED;
else
params.truncate = VACOPTVALUE_DISABLED;
diff --git a/src/include/access/reloptions.h b/src/include/access/reloptions.h
index 2f08e1b0cf0..dfbef2babf2 100644
--- a/src/include/access/reloptions.h
+++ b/src/include/access/reloptions.h
@@ -29,6 +29,7 @@
typedef enum relopt_type
{
RELOPT_TYPE_BOOL,
+ RELOPT_TYPE_TERNARY, /* on, off, unset */
RELOPT_TYPE_INT,
RELOPT_TYPE_REAL,
RELOPT_TYPE_ENUM,
@@ -80,6 +81,7 @@ typedef struct relopt_value
union
{
bool bool_val;
+ pg_ternary ternary_val;
int int_val;
double real_val;
int enum_val;
@@ -94,6 +96,12 @@ typedef struct relopt_bool
bool default_val;
} relopt_bool;
+typedef struct relopt_ternary
+{
+ relopt_gen gen;
+ int default_val;
+} relopt_ternary;
+
typedef struct relopt_int
{
relopt_gen gen;
@@ -152,19 +160,6 @@ typedef struct
const char *optname; /* option's name */
relopt_type opttype; /* option's datatype */
int offset; /* offset of field in result struct */
-
- /*
- * isset_offset is an optional offset of a field in the result struct that
- * stores whether the option is explicitly set for the relation or if it
- * just picked up the default value. In most cases, this can be
- * accomplished by giving the reloption a special out-of-range default
- * value (e.g., some integer reloptions use -2), but this isn't always
- * possible. For example, a Boolean reloption cannot be given an
- * out-of-range default, so we need another way to discover the source of
- * its value. This offset is only used if given a value greater than
- * zero.
- */
- int isset_offset;
} relopt_parse_elt;
/* Local reloption definition */
@@ -195,6 +190,9 @@ typedef struct local_relopts
extern relopt_kind add_reloption_kind(void);
extern void add_bool_reloption(bits32 kinds, const char *name, const char *desc,
bool default_val, LOCKMODE lockmode);
+extern void add_ternary_reloption(bits32 kinds, const char *name,
+ const char *desc, pg_ternary default_val,
+ LOCKMODE lockmode);
extern void add_int_reloption(bits32 kinds, const char *name, const char *desc,
int default_val, int min_val, int max_val,
LOCKMODE lockmode);
@@ -214,6 +212,9 @@ extern void register_reloptions_validator(local_relopts *relopts,
extern void add_local_bool_reloption(local_relopts *relopts, const char *name,
const char *desc, bool default_val,
int offset);
+extern void add_local_ternary_reloption(local_relopts *relopts,
+ const char *name, const char *desc,
+ pg_ternary default_val, int offset);
extern void add_local_int_reloption(local_relopts *relopts, const char *name,
const char *desc, int default_val,
int min_val, int max_val, int offset);
diff --git a/src/include/postgres.h b/src/include/postgres.h
index 7d93fbce709..47c88780776 100644
--- a/src/include/postgres.h
+++ b/src/include/postgres.h
@@ -543,6 +543,21 @@ Float8GetDatum(float8 X)
* ----------------------------------------------------------------
*/
+/*
+ * pg_ternary
+ * Boolean value with an extra "unset" value
+ *
+ * This enum can be used for values that want to distinguish between true,
+ * false, and unset.
+*/
+
+typedef enum pg_ternary
+{
+ TERNARY_FALSE = 0,
+ TERNARY_TRUE = 1,
+ TERNARY_UNSET = -1
+} pg_ternary;
+
/*
* NON_EXEC_STATIC: It's sometimes useful to define a variable or function
* that is normally static but extern when using EXEC_BACKEND (see
diff --git a/src/include/utils/rel.h b/src/include/utils/rel.h
index d03ab247788..5feb18a5373 100644
--- a/src/include/utils/rel.h
+++ b/src/include/utils/rel.h
@@ -347,8 +347,7 @@ typedef struct StdRdOptions
bool user_catalog_table; /* use as an additional catalog relation */
int parallel_workers; /* max number of parallel workers */
StdRdOptIndexCleanup vacuum_index_cleanup; /* controls index vacuuming */
- bool vacuum_truncate; /* enables vacuum to truncate a relation */
- bool vacuum_truncate_set; /* whether vacuum_truncate is set */
+ pg_ternary vacuum_truncate; /* enables vacuum to truncate a relation */
/*
* Fraction of pages in a relation that vacuum can eagerly scan and fail
diff --git a/src/test/regress/expected/reloptions.out b/src/test/regress/expected/reloptions.out
index 9de19b4e3f1..1c99f79ab01 100644
--- a/src/test/regress/expected/reloptions.out
+++ b/src/test/regress/expected/reloptions.out
@@ -98,6 +98,42 @@ SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass;
{fillfactor=13,autovacuum_enabled=false}
(1 row)
+-- Tests for future (FIXME) ternary options
+-- behave as boolean option: accept unassigned name and truncated value
+DROP TABLE reloptions_test;
+CREATE TABLE reloptions_test(i INT) WITH (vacuum_truncate);
+SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass;
+ reloptions
+------------------------
+ {vacuum_truncate=true}
+(1 row)
+
+DROP TABLE reloptions_test;
+CREATE TABLE reloptions_test(i INT) WITH (vacuum_truncate=FaLS);
+SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass;
+ reloptions
+------------------------
+ {vacuum_truncate=fals}
+(1 row)
+
+-- preferred "true" alias is used when storing
+DROP TABLE reloptions_test;
+CREATE TABLE reloptions_test(i INT) WITH (vacuum_index_cleanup=on);
+SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass;
+ reloptions
+---------------------------
+ {vacuum_index_cleanup=on}
+(1 row)
+
+-- custom "third" value is available
+DROP TABLE reloptions_test;
+CREATE TABLE reloptions_test(i INT) WITH (vacuum_index_cleanup=auto);
+SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass;
+ reloptions
+-----------------------------
+ {vacuum_index_cleanup=auto}
+(1 row)
+
-- Test vacuum_truncate option
DROP TABLE reloptions_test;
CREATE TEMP TABLE reloptions_test(i INT NOT NULL, j text)
diff --git a/src/test/regress/sql/reloptions.sql b/src/test/regress/sql/reloptions.sql
index 24fbe0b478d..f5980dafcbc 100644
--- a/src/test/regress/sql/reloptions.sql
+++ b/src/test/regress/sql/reloptions.sql
@@ -59,6 +59,27 @@ UPDATE pg_class
ALTER TABLE reloptions_test RESET (illegal_option);
SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass;
+-- Tests for future (FIXME) ternary options
+
+-- behave as boolean option: accept unassigned name and truncated value
+DROP TABLE reloptions_test;
+CREATE TABLE reloptions_test(i INT) WITH (vacuum_truncate);
+SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass;
+
+DROP TABLE reloptions_test;
+CREATE TABLE reloptions_test(i INT) WITH (vacuum_truncate=FaLS);
+SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass;
+
+-- preferred "true" alias is used when storing
+DROP TABLE reloptions_test;
+CREATE TABLE reloptions_test(i INT) WITH (vacuum_index_cleanup=on);
+SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass;
+
+-- custom "third" value is available
+DROP TABLE reloptions_test;
+CREATE TABLE reloptions_test(i INT) WITH (vacuum_index_cleanup=auto);
+SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass;
+
-- Test vacuum_truncate option
DROP TABLE reloptions_test;
diff --git a/src/tools/pgindent/typedefs.list b/src/tools/pgindent/typedefs.list
index 3f3a888fd0e..1c8610fd46c 100644
--- a/src/tools/pgindent/typedefs.list
+++ b/src/tools/pgindent/typedefs.list
@@ -3952,6 +3952,7 @@ pg_sha512_ctx
pg_snapshot
pg_special_case
pg_stack_base_t
+pg_ternary
pg_time_t
pg_time_usec_t
pg_tz
@@ -4079,6 +4080,7 @@ relopt_kind
relopt_parse_elt
relopt_real
relopt_string
+relopt_ternary
relopt_type
relopt_value
relopts_validator
--
2.47.3
--c36t3fndxhkzbl5z--
^ permalink raw reply [nested|flat] 868+ messages in thread
* [PATCH v3] Introduce ternary reloptions
@ 2026-01-16 15:04 Álvaro Herrera <[email protected]>
0 siblings, 0 replies; 868+ messages in thread
From: Álvaro Herrera @ 2026-01-16 15:04 UTC (permalink / raw)
Introduce ternary reloption as a replacement for current
`vacuum_truncate` implementation. Remove the `vacuum_truncate_set`
separate flag and use TERNARY_UNSET instead.
This could also be used for other options such as `vacuum_index_cleanup`
and `buffering`, but lets get the scaffolding in first.
Discussion: https://postgr.es/m/3474141.usfYGdeWWP@thinkpad-pgpro
---
src/backend/access/common/reloptions.c | 137 ++++++++++++++++++-----
src/backend/commands/vacuum.c | 4 +-
src/include/access/reloptions.h | 27 ++---
src/include/postgres.h | 15 +++
src/include/utils/rel.h | 3 +-
src/test/regress/expected/reloptions.out | 36 ++++++
src/test/regress/sql/reloptions.sql | 21 ++++
src/tools/pgindent/typedefs.list | 2 +
8 files changed, 202 insertions(+), 43 deletions(-)
diff --git a/src/backend/access/common/reloptions.c b/src/backend/access/common/reloptions.c
index 0b83f98ed5f..6f1f577581a 100644
--- a/src/backend/access/common/reloptions.c
+++ b/src/backend/access/common/reloptions.c
@@ -40,9 +40,9 @@
*
* To add an option:
*
- * (i) decide on a type (bool, integer, real, enum, string), name, default
- * value, upper and lower bounds (if applicable); for strings, consider a
- * validation routine.
+ * (i) decide on a type (bool, ternary, integer, real, enum, string), name,
+ * default value, upper and lower bounds (if applicable); for strings,
+ * consider a validation routine.
* (ii) add a record below (or use add_<type>_reloption).
* (iii) add it to the appropriate options struct (perhaps StdRdOptions)
* (iv) add it to the appropriate handling routine (perhaps
@@ -147,15 +147,6 @@ static relopt_bool boolRelOpts[] =
},
false
},
- {
- {
- "vacuum_truncate",
- "Enables vacuum to truncate empty pages at the end of this table",
- RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
- ShareUpdateExclusiveLock
- },
- true
- },
{
{
"deduplicate_items",
@@ -170,6 +161,25 @@ static relopt_bool boolRelOpts[] =
{{NULL}}
};
+static relopt_ternary ternaryRelOpts[] =
+{
+ {
+ {
+ "vacuum_truncate",
+ "Enables vacuum to truncate empty pages at the end of this table",
+ RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
+ ShareUpdateExclusiveLock
+ },
+ TERNARY_UNSET
+ },
+ /* list terminator */
+ {
+ {
+ NULL
+ }
+ }
+};
+
static relopt_int intRelOpts[] =
{
{
@@ -609,6 +619,13 @@ initialize_reloptions(void)
boolRelOpts[i].gen.lockmode));
j++;
}
+ for (i = 0; ternaryRelOpts[i].gen.name; i++)
+ {
+ Assert(DoLockModesConflict(ternaryRelOpts[i].gen.lockmode,
+ ternaryRelOpts[i].gen.lockmode));
+ j++;
+ }
+
for (i = 0; intRelOpts[i].gen.name; i++)
{
Assert(DoLockModesConflict(intRelOpts[i].gen.lockmode,
@@ -649,6 +666,14 @@ initialize_reloptions(void)
j++;
}
+ for (i = 0; ternaryRelOpts[i].gen.name; i++)
+ {
+ relOpts[j] = &ternaryRelOpts[i].gen;
+ relOpts[j]->type = RELOPT_TYPE_TERNARY;
+ relOpts[j]->namelen = strlen(relOpts[j]->name);
+ j++;
+ }
+
for (i = 0; intRelOpts[i].gen.name; i++)
{
relOpts[j] = &intRelOpts[i].gen;
@@ -809,6 +834,9 @@ allocate_reloption(bits32 kinds, int type, const char *name, const char *desc,
case RELOPT_TYPE_BOOL:
size = sizeof(relopt_bool);
break;
+ case RELOPT_TYPE_TERNARY:
+ size = sizeof(relopt_ternary);
+ break;
case RELOPT_TYPE_INT:
size = sizeof(relopt_int);
break;
@@ -892,6 +920,57 @@ add_local_bool_reloption(local_relopts *relopts, const char *name,
add_local_reloption(relopts, (relopt_gen *) newoption, offset);
}
+/*
+ * init_ternary_reloption
+ * Allocate and initialize a new ternary reloption
+ */
+static relopt_ternary *
+init_ternary_reloption(bits32 kinds, const char *name, const char *desc,
+ pg_ternary default_val, LOCKMODE lockmode)
+{
+ relopt_ternary *newoption;
+
+ newoption = (relopt_ternary *)
+ allocate_reloption(kinds, RELOPT_TYPE_TERNARY, name, desc, lockmode);
+ newoption->default_val = default_val;
+
+ return newoption;
+}
+
+/*
+ * add_ternary_reloption
+ * Add a new ternary reloption
+ */
+void
+add_ternary_reloption(bits32 kinds, const char *name, const char *desc,
+ pg_ternary default_val, LOCKMODE lockmode)
+{
+ relopt_ternary *newoption;
+
+ newoption =
+ init_ternary_reloption(kinds, name, desc, default_val, lockmode);
+
+ add_reloption((relopt_gen *) newoption);
+}
+
+/*
+ * add_local_ternary_reloption
+ * Add a new ternary local reloption
+ *
+ * 'offset' is offset of ternary-typed field.
+ */
+void
+add_local_ternary_reloption(local_relopts *relopts, const char *name,
+ const char *desc, pg_ternary default_val,
+ int offset)
+{
+ relopt_ternary *newoption;
+
+ newoption =
+ init_ternary_reloption(RELOPT_KIND_LOCAL, name, desc, default_val, 0);
+
+ add_local_reloption(relopts, (relopt_gen *) newoption, offset);
+}
/*
* init_real_reloption
@@ -1626,6 +1705,19 @@ parse_one_reloption(relopt_value *option, char *text_str, int text_len,
option->gen->name, value)));
}
break;
+ case RELOPT_TYPE_TERNARY:
+ {
+ bool b;
+
+ parsed = parse_bool(value, &b);
+ option->values.ternary_val = b ? TERNARY_TRUE : TERNARY_FALSE;
+ if (validate && !parsed)
+ ereport(ERROR,
+ (errcode(ERRCODE_INVALID_PARAMETER_VALUE),
+ errmsg("invalid value for ternary option \"%s\": %s",
+ option->gen->name, value)));
+ }
+ break;
case RELOPT_TYPE_INT:
{
relopt_int *optint = (relopt_int *) option->gen;
@@ -1789,17 +1881,6 @@ fillRelOptions(void *rdopts, Size basesize,
char *itempos = ((char *) rdopts) + elems[j].offset;
char *string_val;
- /*
- * If isset_offset is provided, store whether the reloption is
- * set there.
- */
- if (elems[j].isset_offset > 0)
- {
- char *setpos = ((char *) rdopts) + elems[j].isset_offset;
-
- *(bool *) setpos = options[i].isset;
- }
-
switch (options[i].gen->type)
{
case RELOPT_TYPE_BOOL:
@@ -1807,6 +1888,11 @@ fillRelOptions(void *rdopts, Size basesize,
options[i].values.bool_val :
((relopt_bool *) options[i].gen)->default_val;
break;
+ case RELOPT_TYPE_TERNARY:
+ *(pg_ternary *) itempos = options[i].isset ?
+ options[i].values.ternary_val :
+ ((relopt_ternary *) options[i].gen)->default_val;
+ break;
case RELOPT_TYPE_INT:
*(int *) itempos = options[i].isset ?
options[i].values.int_val :
@@ -1923,8 +2009,8 @@ default_reloptions(Datum reloptions, bool validate, relopt_kind kind)
offsetof(StdRdOptions, parallel_workers)},
{"vacuum_index_cleanup", RELOPT_TYPE_ENUM,
offsetof(StdRdOptions, vacuum_index_cleanup)},
- {"vacuum_truncate", RELOPT_TYPE_BOOL,
- offsetof(StdRdOptions, vacuum_truncate), offsetof(StdRdOptions, vacuum_truncate_set)},
+ {"vacuum_truncate", RELOPT_TYPE_TERNARY,
+ offsetof(StdRdOptions, vacuum_truncate)},
{"vacuum_max_eager_freeze_failure_rate", RELOPT_TYPE_REAL,
offsetof(StdRdOptions, vacuum_max_eager_freeze_failure_rate)}
};
@@ -2004,7 +2090,6 @@ build_local_reloptions(local_relopts *relopts, Datum options, bool validate)
elems[i].optname = opt->option->name;
elems[i].opttype = opt->option->type;
elems[i].offset = opt->offset;
- elems[i].isset_offset = 0; /* not supported for local relopts yet */
i++;
}
diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c
index aa4fbec143f..696eab9bd97 100644
--- a/src/backend/commands/vacuum.c
+++ b/src/backend/commands/vacuum.c
@@ -2224,9 +2224,9 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams params,
{
StdRdOptions *opts = (StdRdOptions *) rel->rd_options;
- if (opts && opts->vacuum_truncate_set)
+ if (opts && opts->vacuum_truncate != TERNARY_UNSET)
{
- if (opts->vacuum_truncate)
+ if (opts->vacuum_truncate == TERNARY_TRUE)
params.truncate = VACOPTVALUE_ENABLED;
else
params.truncate = VACOPTVALUE_DISABLED;
diff --git a/src/include/access/reloptions.h b/src/include/access/reloptions.h
index 2f08e1b0cf0..dfbef2babf2 100644
--- a/src/include/access/reloptions.h
+++ b/src/include/access/reloptions.h
@@ -29,6 +29,7 @@
typedef enum relopt_type
{
RELOPT_TYPE_BOOL,
+ RELOPT_TYPE_TERNARY, /* on, off, unset */
RELOPT_TYPE_INT,
RELOPT_TYPE_REAL,
RELOPT_TYPE_ENUM,
@@ -80,6 +81,7 @@ typedef struct relopt_value
union
{
bool bool_val;
+ pg_ternary ternary_val;
int int_val;
double real_val;
int enum_val;
@@ -94,6 +96,12 @@ typedef struct relopt_bool
bool default_val;
} relopt_bool;
+typedef struct relopt_ternary
+{
+ relopt_gen gen;
+ int default_val;
+} relopt_ternary;
+
typedef struct relopt_int
{
relopt_gen gen;
@@ -152,19 +160,6 @@ typedef struct
const char *optname; /* option's name */
relopt_type opttype; /* option's datatype */
int offset; /* offset of field in result struct */
-
- /*
- * isset_offset is an optional offset of a field in the result struct that
- * stores whether the option is explicitly set for the relation or if it
- * just picked up the default value. In most cases, this can be
- * accomplished by giving the reloption a special out-of-range default
- * value (e.g., some integer reloptions use -2), but this isn't always
- * possible. For example, a Boolean reloption cannot be given an
- * out-of-range default, so we need another way to discover the source of
- * its value. This offset is only used if given a value greater than
- * zero.
- */
- int isset_offset;
} relopt_parse_elt;
/* Local reloption definition */
@@ -195,6 +190,9 @@ typedef struct local_relopts
extern relopt_kind add_reloption_kind(void);
extern void add_bool_reloption(bits32 kinds, const char *name, const char *desc,
bool default_val, LOCKMODE lockmode);
+extern void add_ternary_reloption(bits32 kinds, const char *name,
+ const char *desc, pg_ternary default_val,
+ LOCKMODE lockmode);
extern void add_int_reloption(bits32 kinds, const char *name, const char *desc,
int default_val, int min_val, int max_val,
LOCKMODE lockmode);
@@ -214,6 +212,9 @@ extern void register_reloptions_validator(local_relopts *relopts,
extern void add_local_bool_reloption(local_relopts *relopts, const char *name,
const char *desc, bool default_val,
int offset);
+extern void add_local_ternary_reloption(local_relopts *relopts,
+ const char *name, const char *desc,
+ pg_ternary default_val, int offset);
extern void add_local_int_reloption(local_relopts *relopts, const char *name,
const char *desc, int default_val,
int min_val, int max_val, int offset);
diff --git a/src/include/postgres.h b/src/include/postgres.h
index 7d93fbce709..47c88780776 100644
--- a/src/include/postgres.h
+++ b/src/include/postgres.h
@@ -543,6 +543,21 @@ Float8GetDatum(float8 X)
* ----------------------------------------------------------------
*/
+/*
+ * pg_ternary
+ * Boolean value with an extra "unset" value
+ *
+ * This enum can be used for values that want to distinguish between true,
+ * false, and unset.
+*/
+
+typedef enum pg_ternary
+{
+ TERNARY_FALSE = 0,
+ TERNARY_TRUE = 1,
+ TERNARY_UNSET = -1
+} pg_ternary;
+
/*
* NON_EXEC_STATIC: It's sometimes useful to define a variable or function
* that is normally static but extern when using EXEC_BACKEND (see
diff --git a/src/include/utils/rel.h b/src/include/utils/rel.h
index d03ab247788..5feb18a5373 100644
--- a/src/include/utils/rel.h
+++ b/src/include/utils/rel.h
@@ -347,8 +347,7 @@ typedef struct StdRdOptions
bool user_catalog_table; /* use as an additional catalog relation */
int parallel_workers; /* max number of parallel workers */
StdRdOptIndexCleanup vacuum_index_cleanup; /* controls index vacuuming */
- bool vacuum_truncate; /* enables vacuum to truncate a relation */
- bool vacuum_truncate_set; /* whether vacuum_truncate is set */
+ pg_ternary vacuum_truncate; /* enables vacuum to truncate a relation */
/*
* Fraction of pages in a relation that vacuum can eagerly scan and fail
diff --git a/src/test/regress/expected/reloptions.out b/src/test/regress/expected/reloptions.out
index 9de19b4e3f1..1c99f79ab01 100644
--- a/src/test/regress/expected/reloptions.out
+++ b/src/test/regress/expected/reloptions.out
@@ -98,6 +98,42 @@ SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass;
{fillfactor=13,autovacuum_enabled=false}
(1 row)
+-- Tests for future (FIXME) ternary options
+-- behave as boolean option: accept unassigned name and truncated value
+DROP TABLE reloptions_test;
+CREATE TABLE reloptions_test(i INT) WITH (vacuum_truncate);
+SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass;
+ reloptions
+------------------------
+ {vacuum_truncate=true}
+(1 row)
+
+DROP TABLE reloptions_test;
+CREATE TABLE reloptions_test(i INT) WITH (vacuum_truncate=FaLS);
+SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass;
+ reloptions
+------------------------
+ {vacuum_truncate=fals}
+(1 row)
+
+-- preferred "true" alias is used when storing
+DROP TABLE reloptions_test;
+CREATE TABLE reloptions_test(i INT) WITH (vacuum_index_cleanup=on);
+SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass;
+ reloptions
+---------------------------
+ {vacuum_index_cleanup=on}
+(1 row)
+
+-- custom "third" value is available
+DROP TABLE reloptions_test;
+CREATE TABLE reloptions_test(i INT) WITH (vacuum_index_cleanup=auto);
+SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass;
+ reloptions
+-----------------------------
+ {vacuum_index_cleanup=auto}
+(1 row)
+
-- Test vacuum_truncate option
DROP TABLE reloptions_test;
CREATE TEMP TABLE reloptions_test(i INT NOT NULL, j text)
diff --git a/src/test/regress/sql/reloptions.sql b/src/test/regress/sql/reloptions.sql
index 24fbe0b478d..f5980dafcbc 100644
--- a/src/test/regress/sql/reloptions.sql
+++ b/src/test/regress/sql/reloptions.sql
@@ -59,6 +59,27 @@ UPDATE pg_class
ALTER TABLE reloptions_test RESET (illegal_option);
SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass;
+-- Tests for future (FIXME) ternary options
+
+-- behave as boolean option: accept unassigned name and truncated value
+DROP TABLE reloptions_test;
+CREATE TABLE reloptions_test(i INT) WITH (vacuum_truncate);
+SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass;
+
+DROP TABLE reloptions_test;
+CREATE TABLE reloptions_test(i INT) WITH (vacuum_truncate=FaLS);
+SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass;
+
+-- preferred "true" alias is used when storing
+DROP TABLE reloptions_test;
+CREATE TABLE reloptions_test(i INT) WITH (vacuum_index_cleanup=on);
+SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass;
+
+-- custom "third" value is available
+DROP TABLE reloptions_test;
+CREATE TABLE reloptions_test(i INT) WITH (vacuum_index_cleanup=auto);
+SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass;
+
-- Test vacuum_truncate option
DROP TABLE reloptions_test;
diff --git a/src/tools/pgindent/typedefs.list b/src/tools/pgindent/typedefs.list
index 3f3a888fd0e..1c8610fd46c 100644
--- a/src/tools/pgindent/typedefs.list
+++ b/src/tools/pgindent/typedefs.list
@@ -3952,6 +3952,7 @@ pg_sha512_ctx
pg_snapshot
pg_special_case
pg_stack_base_t
+pg_ternary
pg_time_t
pg_time_usec_t
pg_tz
@@ -4079,6 +4080,7 @@ relopt_kind
relopt_parse_elt
relopt_real
relopt_string
+relopt_ternary
relopt_type
relopt_value
relopts_validator
--
2.47.3
--c36t3fndxhkzbl5z--
^ permalink raw reply [nested|flat] 868+ messages in thread
* [PATCH v3] Introduce ternary reloptions
@ 2026-01-16 15:04 Álvaro Herrera <[email protected]>
0 siblings, 0 replies; 868+ messages in thread
From: Álvaro Herrera @ 2026-01-16 15:04 UTC (permalink / raw)
Introduce ternary reloption as a replacement for current
`vacuum_truncate` implementation. Remove the `vacuum_truncate_set`
separate flag and use TERNARY_UNSET instead.
This could also be used for other options such as `vacuum_index_cleanup`
and `buffering`, but lets get the scaffolding in first.
Discussion: https://postgr.es/m/3474141.usfYGdeWWP@thinkpad-pgpro
---
src/backend/access/common/reloptions.c | 137 ++++++++++++++++++-----
src/backend/commands/vacuum.c | 4 +-
src/include/access/reloptions.h | 27 ++---
src/include/postgres.h | 15 +++
src/include/utils/rel.h | 3 +-
src/test/regress/expected/reloptions.out | 36 ++++++
src/test/regress/sql/reloptions.sql | 21 ++++
src/tools/pgindent/typedefs.list | 2 +
8 files changed, 202 insertions(+), 43 deletions(-)
diff --git a/src/backend/access/common/reloptions.c b/src/backend/access/common/reloptions.c
index 0b83f98ed5f..6f1f577581a 100644
--- a/src/backend/access/common/reloptions.c
+++ b/src/backend/access/common/reloptions.c
@@ -40,9 +40,9 @@
*
* To add an option:
*
- * (i) decide on a type (bool, integer, real, enum, string), name, default
- * value, upper and lower bounds (if applicable); for strings, consider a
- * validation routine.
+ * (i) decide on a type (bool, ternary, integer, real, enum, string), name,
+ * default value, upper and lower bounds (if applicable); for strings,
+ * consider a validation routine.
* (ii) add a record below (or use add_<type>_reloption).
* (iii) add it to the appropriate options struct (perhaps StdRdOptions)
* (iv) add it to the appropriate handling routine (perhaps
@@ -147,15 +147,6 @@ static relopt_bool boolRelOpts[] =
},
false
},
- {
- {
- "vacuum_truncate",
- "Enables vacuum to truncate empty pages at the end of this table",
- RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
- ShareUpdateExclusiveLock
- },
- true
- },
{
{
"deduplicate_items",
@@ -170,6 +161,25 @@ static relopt_bool boolRelOpts[] =
{{NULL}}
};
+static relopt_ternary ternaryRelOpts[] =
+{
+ {
+ {
+ "vacuum_truncate",
+ "Enables vacuum to truncate empty pages at the end of this table",
+ RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
+ ShareUpdateExclusiveLock
+ },
+ TERNARY_UNSET
+ },
+ /* list terminator */
+ {
+ {
+ NULL
+ }
+ }
+};
+
static relopt_int intRelOpts[] =
{
{
@@ -609,6 +619,13 @@ initialize_reloptions(void)
boolRelOpts[i].gen.lockmode));
j++;
}
+ for (i = 0; ternaryRelOpts[i].gen.name; i++)
+ {
+ Assert(DoLockModesConflict(ternaryRelOpts[i].gen.lockmode,
+ ternaryRelOpts[i].gen.lockmode));
+ j++;
+ }
+
for (i = 0; intRelOpts[i].gen.name; i++)
{
Assert(DoLockModesConflict(intRelOpts[i].gen.lockmode,
@@ -649,6 +666,14 @@ initialize_reloptions(void)
j++;
}
+ for (i = 0; ternaryRelOpts[i].gen.name; i++)
+ {
+ relOpts[j] = &ternaryRelOpts[i].gen;
+ relOpts[j]->type = RELOPT_TYPE_TERNARY;
+ relOpts[j]->namelen = strlen(relOpts[j]->name);
+ j++;
+ }
+
for (i = 0; intRelOpts[i].gen.name; i++)
{
relOpts[j] = &intRelOpts[i].gen;
@@ -809,6 +834,9 @@ allocate_reloption(bits32 kinds, int type, const char *name, const char *desc,
case RELOPT_TYPE_BOOL:
size = sizeof(relopt_bool);
break;
+ case RELOPT_TYPE_TERNARY:
+ size = sizeof(relopt_ternary);
+ break;
case RELOPT_TYPE_INT:
size = sizeof(relopt_int);
break;
@@ -892,6 +920,57 @@ add_local_bool_reloption(local_relopts *relopts, const char *name,
add_local_reloption(relopts, (relopt_gen *) newoption, offset);
}
+/*
+ * init_ternary_reloption
+ * Allocate and initialize a new ternary reloption
+ */
+static relopt_ternary *
+init_ternary_reloption(bits32 kinds, const char *name, const char *desc,
+ pg_ternary default_val, LOCKMODE lockmode)
+{
+ relopt_ternary *newoption;
+
+ newoption = (relopt_ternary *)
+ allocate_reloption(kinds, RELOPT_TYPE_TERNARY, name, desc, lockmode);
+ newoption->default_val = default_val;
+
+ return newoption;
+}
+
+/*
+ * add_ternary_reloption
+ * Add a new ternary reloption
+ */
+void
+add_ternary_reloption(bits32 kinds, const char *name, const char *desc,
+ pg_ternary default_val, LOCKMODE lockmode)
+{
+ relopt_ternary *newoption;
+
+ newoption =
+ init_ternary_reloption(kinds, name, desc, default_val, lockmode);
+
+ add_reloption((relopt_gen *) newoption);
+}
+
+/*
+ * add_local_ternary_reloption
+ * Add a new ternary local reloption
+ *
+ * 'offset' is offset of ternary-typed field.
+ */
+void
+add_local_ternary_reloption(local_relopts *relopts, const char *name,
+ const char *desc, pg_ternary default_val,
+ int offset)
+{
+ relopt_ternary *newoption;
+
+ newoption =
+ init_ternary_reloption(RELOPT_KIND_LOCAL, name, desc, default_val, 0);
+
+ add_local_reloption(relopts, (relopt_gen *) newoption, offset);
+}
/*
* init_real_reloption
@@ -1626,6 +1705,19 @@ parse_one_reloption(relopt_value *option, char *text_str, int text_len,
option->gen->name, value)));
}
break;
+ case RELOPT_TYPE_TERNARY:
+ {
+ bool b;
+
+ parsed = parse_bool(value, &b);
+ option->values.ternary_val = b ? TERNARY_TRUE : TERNARY_FALSE;
+ if (validate && !parsed)
+ ereport(ERROR,
+ (errcode(ERRCODE_INVALID_PARAMETER_VALUE),
+ errmsg("invalid value for ternary option \"%s\": %s",
+ option->gen->name, value)));
+ }
+ break;
case RELOPT_TYPE_INT:
{
relopt_int *optint = (relopt_int *) option->gen;
@@ -1789,17 +1881,6 @@ fillRelOptions(void *rdopts, Size basesize,
char *itempos = ((char *) rdopts) + elems[j].offset;
char *string_val;
- /*
- * If isset_offset is provided, store whether the reloption is
- * set there.
- */
- if (elems[j].isset_offset > 0)
- {
- char *setpos = ((char *) rdopts) + elems[j].isset_offset;
-
- *(bool *) setpos = options[i].isset;
- }
-
switch (options[i].gen->type)
{
case RELOPT_TYPE_BOOL:
@@ -1807,6 +1888,11 @@ fillRelOptions(void *rdopts, Size basesize,
options[i].values.bool_val :
((relopt_bool *) options[i].gen)->default_val;
break;
+ case RELOPT_TYPE_TERNARY:
+ *(pg_ternary *) itempos = options[i].isset ?
+ options[i].values.ternary_val :
+ ((relopt_ternary *) options[i].gen)->default_val;
+ break;
case RELOPT_TYPE_INT:
*(int *) itempos = options[i].isset ?
options[i].values.int_val :
@@ -1923,8 +2009,8 @@ default_reloptions(Datum reloptions, bool validate, relopt_kind kind)
offsetof(StdRdOptions, parallel_workers)},
{"vacuum_index_cleanup", RELOPT_TYPE_ENUM,
offsetof(StdRdOptions, vacuum_index_cleanup)},
- {"vacuum_truncate", RELOPT_TYPE_BOOL,
- offsetof(StdRdOptions, vacuum_truncate), offsetof(StdRdOptions, vacuum_truncate_set)},
+ {"vacuum_truncate", RELOPT_TYPE_TERNARY,
+ offsetof(StdRdOptions, vacuum_truncate)},
{"vacuum_max_eager_freeze_failure_rate", RELOPT_TYPE_REAL,
offsetof(StdRdOptions, vacuum_max_eager_freeze_failure_rate)}
};
@@ -2004,7 +2090,6 @@ build_local_reloptions(local_relopts *relopts, Datum options, bool validate)
elems[i].optname = opt->option->name;
elems[i].opttype = opt->option->type;
elems[i].offset = opt->offset;
- elems[i].isset_offset = 0; /* not supported for local relopts yet */
i++;
}
diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c
index aa4fbec143f..696eab9bd97 100644
--- a/src/backend/commands/vacuum.c
+++ b/src/backend/commands/vacuum.c
@@ -2224,9 +2224,9 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams params,
{
StdRdOptions *opts = (StdRdOptions *) rel->rd_options;
- if (opts && opts->vacuum_truncate_set)
+ if (opts && opts->vacuum_truncate != TERNARY_UNSET)
{
- if (opts->vacuum_truncate)
+ if (opts->vacuum_truncate == TERNARY_TRUE)
params.truncate = VACOPTVALUE_ENABLED;
else
params.truncate = VACOPTVALUE_DISABLED;
diff --git a/src/include/access/reloptions.h b/src/include/access/reloptions.h
index 2f08e1b0cf0..dfbef2babf2 100644
--- a/src/include/access/reloptions.h
+++ b/src/include/access/reloptions.h
@@ -29,6 +29,7 @@
typedef enum relopt_type
{
RELOPT_TYPE_BOOL,
+ RELOPT_TYPE_TERNARY, /* on, off, unset */
RELOPT_TYPE_INT,
RELOPT_TYPE_REAL,
RELOPT_TYPE_ENUM,
@@ -80,6 +81,7 @@ typedef struct relopt_value
union
{
bool bool_val;
+ pg_ternary ternary_val;
int int_val;
double real_val;
int enum_val;
@@ -94,6 +96,12 @@ typedef struct relopt_bool
bool default_val;
} relopt_bool;
+typedef struct relopt_ternary
+{
+ relopt_gen gen;
+ int default_val;
+} relopt_ternary;
+
typedef struct relopt_int
{
relopt_gen gen;
@@ -152,19 +160,6 @@ typedef struct
const char *optname; /* option's name */
relopt_type opttype; /* option's datatype */
int offset; /* offset of field in result struct */
-
- /*
- * isset_offset is an optional offset of a field in the result struct that
- * stores whether the option is explicitly set for the relation or if it
- * just picked up the default value. In most cases, this can be
- * accomplished by giving the reloption a special out-of-range default
- * value (e.g., some integer reloptions use -2), but this isn't always
- * possible. For example, a Boolean reloption cannot be given an
- * out-of-range default, so we need another way to discover the source of
- * its value. This offset is only used if given a value greater than
- * zero.
- */
- int isset_offset;
} relopt_parse_elt;
/* Local reloption definition */
@@ -195,6 +190,9 @@ typedef struct local_relopts
extern relopt_kind add_reloption_kind(void);
extern void add_bool_reloption(bits32 kinds, const char *name, const char *desc,
bool default_val, LOCKMODE lockmode);
+extern void add_ternary_reloption(bits32 kinds, const char *name,
+ const char *desc, pg_ternary default_val,
+ LOCKMODE lockmode);
extern void add_int_reloption(bits32 kinds, const char *name, const char *desc,
int default_val, int min_val, int max_val,
LOCKMODE lockmode);
@@ -214,6 +212,9 @@ extern void register_reloptions_validator(local_relopts *relopts,
extern void add_local_bool_reloption(local_relopts *relopts, const char *name,
const char *desc, bool default_val,
int offset);
+extern void add_local_ternary_reloption(local_relopts *relopts,
+ const char *name, const char *desc,
+ pg_ternary default_val, int offset);
extern void add_local_int_reloption(local_relopts *relopts, const char *name,
const char *desc, int default_val,
int min_val, int max_val, int offset);
diff --git a/src/include/postgres.h b/src/include/postgres.h
index 7d93fbce709..47c88780776 100644
--- a/src/include/postgres.h
+++ b/src/include/postgres.h
@@ -543,6 +543,21 @@ Float8GetDatum(float8 X)
* ----------------------------------------------------------------
*/
+/*
+ * pg_ternary
+ * Boolean value with an extra "unset" value
+ *
+ * This enum can be used for values that want to distinguish between true,
+ * false, and unset.
+*/
+
+typedef enum pg_ternary
+{
+ TERNARY_FALSE = 0,
+ TERNARY_TRUE = 1,
+ TERNARY_UNSET = -1
+} pg_ternary;
+
/*
* NON_EXEC_STATIC: It's sometimes useful to define a variable or function
* that is normally static but extern when using EXEC_BACKEND (see
diff --git a/src/include/utils/rel.h b/src/include/utils/rel.h
index d03ab247788..5feb18a5373 100644
--- a/src/include/utils/rel.h
+++ b/src/include/utils/rel.h
@@ -347,8 +347,7 @@ typedef struct StdRdOptions
bool user_catalog_table; /* use as an additional catalog relation */
int parallel_workers; /* max number of parallel workers */
StdRdOptIndexCleanup vacuum_index_cleanup; /* controls index vacuuming */
- bool vacuum_truncate; /* enables vacuum to truncate a relation */
- bool vacuum_truncate_set; /* whether vacuum_truncate is set */
+ pg_ternary vacuum_truncate; /* enables vacuum to truncate a relation */
/*
* Fraction of pages in a relation that vacuum can eagerly scan and fail
diff --git a/src/test/regress/expected/reloptions.out b/src/test/regress/expected/reloptions.out
index 9de19b4e3f1..1c99f79ab01 100644
--- a/src/test/regress/expected/reloptions.out
+++ b/src/test/regress/expected/reloptions.out
@@ -98,6 +98,42 @@ SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass;
{fillfactor=13,autovacuum_enabled=false}
(1 row)
+-- Tests for future (FIXME) ternary options
+-- behave as boolean option: accept unassigned name and truncated value
+DROP TABLE reloptions_test;
+CREATE TABLE reloptions_test(i INT) WITH (vacuum_truncate);
+SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass;
+ reloptions
+------------------------
+ {vacuum_truncate=true}
+(1 row)
+
+DROP TABLE reloptions_test;
+CREATE TABLE reloptions_test(i INT) WITH (vacuum_truncate=FaLS);
+SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass;
+ reloptions
+------------------------
+ {vacuum_truncate=fals}
+(1 row)
+
+-- preferred "true" alias is used when storing
+DROP TABLE reloptions_test;
+CREATE TABLE reloptions_test(i INT) WITH (vacuum_index_cleanup=on);
+SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass;
+ reloptions
+---------------------------
+ {vacuum_index_cleanup=on}
+(1 row)
+
+-- custom "third" value is available
+DROP TABLE reloptions_test;
+CREATE TABLE reloptions_test(i INT) WITH (vacuum_index_cleanup=auto);
+SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass;
+ reloptions
+-----------------------------
+ {vacuum_index_cleanup=auto}
+(1 row)
+
-- Test vacuum_truncate option
DROP TABLE reloptions_test;
CREATE TEMP TABLE reloptions_test(i INT NOT NULL, j text)
diff --git a/src/test/regress/sql/reloptions.sql b/src/test/regress/sql/reloptions.sql
index 24fbe0b478d..f5980dafcbc 100644
--- a/src/test/regress/sql/reloptions.sql
+++ b/src/test/regress/sql/reloptions.sql
@@ -59,6 +59,27 @@ UPDATE pg_class
ALTER TABLE reloptions_test RESET (illegal_option);
SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass;
+-- Tests for future (FIXME) ternary options
+
+-- behave as boolean option: accept unassigned name and truncated value
+DROP TABLE reloptions_test;
+CREATE TABLE reloptions_test(i INT) WITH (vacuum_truncate);
+SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass;
+
+DROP TABLE reloptions_test;
+CREATE TABLE reloptions_test(i INT) WITH (vacuum_truncate=FaLS);
+SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass;
+
+-- preferred "true" alias is used when storing
+DROP TABLE reloptions_test;
+CREATE TABLE reloptions_test(i INT) WITH (vacuum_index_cleanup=on);
+SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass;
+
+-- custom "third" value is available
+DROP TABLE reloptions_test;
+CREATE TABLE reloptions_test(i INT) WITH (vacuum_index_cleanup=auto);
+SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass;
+
-- Test vacuum_truncate option
DROP TABLE reloptions_test;
diff --git a/src/tools/pgindent/typedefs.list b/src/tools/pgindent/typedefs.list
index 3f3a888fd0e..1c8610fd46c 100644
--- a/src/tools/pgindent/typedefs.list
+++ b/src/tools/pgindent/typedefs.list
@@ -3952,6 +3952,7 @@ pg_sha512_ctx
pg_snapshot
pg_special_case
pg_stack_base_t
+pg_ternary
pg_time_t
pg_time_usec_t
pg_tz
@@ -4079,6 +4080,7 @@ relopt_kind
relopt_parse_elt
relopt_real
relopt_string
+relopt_ternary
relopt_type
relopt_value
relopts_validator
--
2.47.3
--c36t3fndxhkzbl5z--
^ permalink raw reply [nested|flat] 868+ messages in thread
* [PATCH v3] Introduce ternary reloptions
@ 2026-01-16 15:04 Álvaro Herrera <[email protected]>
0 siblings, 0 replies; 868+ messages in thread
From: Álvaro Herrera @ 2026-01-16 15:04 UTC (permalink / raw)
Introduce ternary reloption as a replacement for current
`vacuum_truncate` implementation. Remove the `vacuum_truncate_set`
separate flag and use TERNARY_UNSET instead.
This could also be used for other options such as `vacuum_index_cleanup`
and `buffering`, but lets get the scaffolding in first.
Discussion: https://postgr.es/m/3474141.usfYGdeWWP@thinkpad-pgpro
---
src/backend/access/common/reloptions.c | 137 ++++++++++++++++++-----
src/backend/commands/vacuum.c | 4 +-
src/include/access/reloptions.h | 27 ++---
src/include/postgres.h | 15 +++
src/include/utils/rel.h | 3 +-
src/test/regress/expected/reloptions.out | 36 ++++++
src/test/regress/sql/reloptions.sql | 21 ++++
src/tools/pgindent/typedefs.list | 2 +
8 files changed, 202 insertions(+), 43 deletions(-)
diff --git a/src/backend/access/common/reloptions.c b/src/backend/access/common/reloptions.c
index 0b83f98ed5f..6f1f577581a 100644
--- a/src/backend/access/common/reloptions.c
+++ b/src/backend/access/common/reloptions.c
@@ -40,9 +40,9 @@
*
* To add an option:
*
- * (i) decide on a type (bool, integer, real, enum, string), name, default
- * value, upper and lower bounds (if applicable); for strings, consider a
- * validation routine.
+ * (i) decide on a type (bool, ternary, integer, real, enum, string), name,
+ * default value, upper and lower bounds (if applicable); for strings,
+ * consider a validation routine.
* (ii) add a record below (or use add_<type>_reloption).
* (iii) add it to the appropriate options struct (perhaps StdRdOptions)
* (iv) add it to the appropriate handling routine (perhaps
@@ -147,15 +147,6 @@ static relopt_bool boolRelOpts[] =
},
false
},
- {
- {
- "vacuum_truncate",
- "Enables vacuum to truncate empty pages at the end of this table",
- RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
- ShareUpdateExclusiveLock
- },
- true
- },
{
{
"deduplicate_items",
@@ -170,6 +161,25 @@ static relopt_bool boolRelOpts[] =
{{NULL}}
};
+static relopt_ternary ternaryRelOpts[] =
+{
+ {
+ {
+ "vacuum_truncate",
+ "Enables vacuum to truncate empty pages at the end of this table",
+ RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
+ ShareUpdateExclusiveLock
+ },
+ TERNARY_UNSET
+ },
+ /* list terminator */
+ {
+ {
+ NULL
+ }
+ }
+};
+
static relopt_int intRelOpts[] =
{
{
@@ -609,6 +619,13 @@ initialize_reloptions(void)
boolRelOpts[i].gen.lockmode));
j++;
}
+ for (i = 0; ternaryRelOpts[i].gen.name; i++)
+ {
+ Assert(DoLockModesConflict(ternaryRelOpts[i].gen.lockmode,
+ ternaryRelOpts[i].gen.lockmode));
+ j++;
+ }
+
for (i = 0; intRelOpts[i].gen.name; i++)
{
Assert(DoLockModesConflict(intRelOpts[i].gen.lockmode,
@@ -649,6 +666,14 @@ initialize_reloptions(void)
j++;
}
+ for (i = 0; ternaryRelOpts[i].gen.name; i++)
+ {
+ relOpts[j] = &ternaryRelOpts[i].gen;
+ relOpts[j]->type = RELOPT_TYPE_TERNARY;
+ relOpts[j]->namelen = strlen(relOpts[j]->name);
+ j++;
+ }
+
for (i = 0; intRelOpts[i].gen.name; i++)
{
relOpts[j] = &intRelOpts[i].gen;
@@ -809,6 +834,9 @@ allocate_reloption(bits32 kinds, int type, const char *name, const char *desc,
case RELOPT_TYPE_BOOL:
size = sizeof(relopt_bool);
break;
+ case RELOPT_TYPE_TERNARY:
+ size = sizeof(relopt_ternary);
+ break;
case RELOPT_TYPE_INT:
size = sizeof(relopt_int);
break;
@@ -892,6 +920,57 @@ add_local_bool_reloption(local_relopts *relopts, const char *name,
add_local_reloption(relopts, (relopt_gen *) newoption, offset);
}
+/*
+ * init_ternary_reloption
+ * Allocate and initialize a new ternary reloption
+ */
+static relopt_ternary *
+init_ternary_reloption(bits32 kinds, const char *name, const char *desc,
+ pg_ternary default_val, LOCKMODE lockmode)
+{
+ relopt_ternary *newoption;
+
+ newoption = (relopt_ternary *)
+ allocate_reloption(kinds, RELOPT_TYPE_TERNARY, name, desc, lockmode);
+ newoption->default_val = default_val;
+
+ return newoption;
+}
+
+/*
+ * add_ternary_reloption
+ * Add a new ternary reloption
+ */
+void
+add_ternary_reloption(bits32 kinds, const char *name, const char *desc,
+ pg_ternary default_val, LOCKMODE lockmode)
+{
+ relopt_ternary *newoption;
+
+ newoption =
+ init_ternary_reloption(kinds, name, desc, default_val, lockmode);
+
+ add_reloption((relopt_gen *) newoption);
+}
+
+/*
+ * add_local_ternary_reloption
+ * Add a new ternary local reloption
+ *
+ * 'offset' is offset of ternary-typed field.
+ */
+void
+add_local_ternary_reloption(local_relopts *relopts, const char *name,
+ const char *desc, pg_ternary default_val,
+ int offset)
+{
+ relopt_ternary *newoption;
+
+ newoption =
+ init_ternary_reloption(RELOPT_KIND_LOCAL, name, desc, default_val, 0);
+
+ add_local_reloption(relopts, (relopt_gen *) newoption, offset);
+}
/*
* init_real_reloption
@@ -1626,6 +1705,19 @@ parse_one_reloption(relopt_value *option, char *text_str, int text_len,
option->gen->name, value)));
}
break;
+ case RELOPT_TYPE_TERNARY:
+ {
+ bool b;
+
+ parsed = parse_bool(value, &b);
+ option->values.ternary_val = b ? TERNARY_TRUE : TERNARY_FALSE;
+ if (validate && !parsed)
+ ereport(ERROR,
+ (errcode(ERRCODE_INVALID_PARAMETER_VALUE),
+ errmsg("invalid value for ternary option \"%s\": %s",
+ option->gen->name, value)));
+ }
+ break;
case RELOPT_TYPE_INT:
{
relopt_int *optint = (relopt_int *) option->gen;
@@ -1789,17 +1881,6 @@ fillRelOptions(void *rdopts, Size basesize,
char *itempos = ((char *) rdopts) + elems[j].offset;
char *string_val;
- /*
- * If isset_offset is provided, store whether the reloption is
- * set there.
- */
- if (elems[j].isset_offset > 0)
- {
- char *setpos = ((char *) rdopts) + elems[j].isset_offset;
-
- *(bool *) setpos = options[i].isset;
- }
-
switch (options[i].gen->type)
{
case RELOPT_TYPE_BOOL:
@@ -1807,6 +1888,11 @@ fillRelOptions(void *rdopts, Size basesize,
options[i].values.bool_val :
((relopt_bool *) options[i].gen)->default_val;
break;
+ case RELOPT_TYPE_TERNARY:
+ *(pg_ternary *) itempos = options[i].isset ?
+ options[i].values.ternary_val :
+ ((relopt_ternary *) options[i].gen)->default_val;
+ break;
case RELOPT_TYPE_INT:
*(int *) itempos = options[i].isset ?
options[i].values.int_val :
@@ -1923,8 +2009,8 @@ default_reloptions(Datum reloptions, bool validate, relopt_kind kind)
offsetof(StdRdOptions, parallel_workers)},
{"vacuum_index_cleanup", RELOPT_TYPE_ENUM,
offsetof(StdRdOptions, vacuum_index_cleanup)},
- {"vacuum_truncate", RELOPT_TYPE_BOOL,
- offsetof(StdRdOptions, vacuum_truncate), offsetof(StdRdOptions, vacuum_truncate_set)},
+ {"vacuum_truncate", RELOPT_TYPE_TERNARY,
+ offsetof(StdRdOptions, vacuum_truncate)},
{"vacuum_max_eager_freeze_failure_rate", RELOPT_TYPE_REAL,
offsetof(StdRdOptions, vacuum_max_eager_freeze_failure_rate)}
};
@@ -2004,7 +2090,6 @@ build_local_reloptions(local_relopts *relopts, Datum options, bool validate)
elems[i].optname = opt->option->name;
elems[i].opttype = opt->option->type;
elems[i].offset = opt->offset;
- elems[i].isset_offset = 0; /* not supported for local relopts yet */
i++;
}
diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c
index aa4fbec143f..696eab9bd97 100644
--- a/src/backend/commands/vacuum.c
+++ b/src/backend/commands/vacuum.c
@@ -2224,9 +2224,9 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams params,
{
StdRdOptions *opts = (StdRdOptions *) rel->rd_options;
- if (opts && opts->vacuum_truncate_set)
+ if (opts && opts->vacuum_truncate != TERNARY_UNSET)
{
- if (opts->vacuum_truncate)
+ if (opts->vacuum_truncate == TERNARY_TRUE)
params.truncate = VACOPTVALUE_ENABLED;
else
params.truncate = VACOPTVALUE_DISABLED;
diff --git a/src/include/access/reloptions.h b/src/include/access/reloptions.h
index 2f08e1b0cf0..dfbef2babf2 100644
--- a/src/include/access/reloptions.h
+++ b/src/include/access/reloptions.h
@@ -29,6 +29,7 @@
typedef enum relopt_type
{
RELOPT_TYPE_BOOL,
+ RELOPT_TYPE_TERNARY, /* on, off, unset */
RELOPT_TYPE_INT,
RELOPT_TYPE_REAL,
RELOPT_TYPE_ENUM,
@@ -80,6 +81,7 @@ typedef struct relopt_value
union
{
bool bool_val;
+ pg_ternary ternary_val;
int int_val;
double real_val;
int enum_val;
@@ -94,6 +96,12 @@ typedef struct relopt_bool
bool default_val;
} relopt_bool;
+typedef struct relopt_ternary
+{
+ relopt_gen gen;
+ int default_val;
+} relopt_ternary;
+
typedef struct relopt_int
{
relopt_gen gen;
@@ -152,19 +160,6 @@ typedef struct
const char *optname; /* option's name */
relopt_type opttype; /* option's datatype */
int offset; /* offset of field in result struct */
-
- /*
- * isset_offset is an optional offset of a field in the result struct that
- * stores whether the option is explicitly set for the relation or if it
- * just picked up the default value. In most cases, this can be
- * accomplished by giving the reloption a special out-of-range default
- * value (e.g., some integer reloptions use -2), but this isn't always
- * possible. For example, a Boolean reloption cannot be given an
- * out-of-range default, so we need another way to discover the source of
- * its value. This offset is only used if given a value greater than
- * zero.
- */
- int isset_offset;
} relopt_parse_elt;
/* Local reloption definition */
@@ -195,6 +190,9 @@ typedef struct local_relopts
extern relopt_kind add_reloption_kind(void);
extern void add_bool_reloption(bits32 kinds, const char *name, const char *desc,
bool default_val, LOCKMODE lockmode);
+extern void add_ternary_reloption(bits32 kinds, const char *name,
+ const char *desc, pg_ternary default_val,
+ LOCKMODE lockmode);
extern void add_int_reloption(bits32 kinds, const char *name, const char *desc,
int default_val, int min_val, int max_val,
LOCKMODE lockmode);
@@ -214,6 +212,9 @@ extern void register_reloptions_validator(local_relopts *relopts,
extern void add_local_bool_reloption(local_relopts *relopts, const char *name,
const char *desc, bool default_val,
int offset);
+extern void add_local_ternary_reloption(local_relopts *relopts,
+ const char *name, const char *desc,
+ pg_ternary default_val, int offset);
extern void add_local_int_reloption(local_relopts *relopts, const char *name,
const char *desc, int default_val,
int min_val, int max_val, int offset);
diff --git a/src/include/postgres.h b/src/include/postgres.h
index 7d93fbce709..47c88780776 100644
--- a/src/include/postgres.h
+++ b/src/include/postgres.h
@@ -543,6 +543,21 @@ Float8GetDatum(float8 X)
* ----------------------------------------------------------------
*/
+/*
+ * pg_ternary
+ * Boolean value with an extra "unset" value
+ *
+ * This enum can be used for values that want to distinguish between true,
+ * false, and unset.
+*/
+
+typedef enum pg_ternary
+{
+ TERNARY_FALSE = 0,
+ TERNARY_TRUE = 1,
+ TERNARY_UNSET = -1
+} pg_ternary;
+
/*
* NON_EXEC_STATIC: It's sometimes useful to define a variable or function
* that is normally static but extern when using EXEC_BACKEND (see
diff --git a/src/include/utils/rel.h b/src/include/utils/rel.h
index d03ab247788..5feb18a5373 100644
--- a/src/include/utils/rel.h
+++ b/src/include/utils/rel.h
@@ -347,8 +347,7 @@ typedef struct StdRdOptions
bool user_catalog_table; /* use as an additional catalog relation */
int parallel_workers; /* max number of parallel workers */
StdRdOptIndexCleanup vacuum_index_cleanup; /* controls index vacuuming */
- bool vacuum_truncate; /* enables vacuum to truncate a relation */
- bool vacuum_truncate_set; /* whether vacuum_truncate is set */
+ pg_ternary vacuum_truncate; /* enables vacuum to truncate a relation */
/*
* Fraction of pages in a relation that vacuum can eagerly scan and fail
diff --git a/src/test/regress/expected/reloptions.out b/src/test/regress/expected/reloptions.out
index 9de19b4e3f1..1c99f79ab01 100644
--- a/src/test/regress/expected/reloptions.out
+++ b/src/test/regress/expected/reloptions.out
@@ -98,6 +98,42 @@ SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass;
{fillfactor=13,autovacuum_enabled=false}
(1 row)
+-- Tests for future (FIXME) ternary options
+-- behave as boolean option: accept unassigned name and truncated value
+DROP TABLE reloptions_test;
+CREATE TABLE reloptions_test(i INT) WITH (vacuum_truncate);
+SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass;
+ reloptions
+------------------------
+ {vacuum_truncate=true}
+(1 row)
+
+DROP TABLE reloptions_test;
+CREATE TABLE reloptions_test(i INT) WITH (vacuum_truncate=FaLS);
+SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass;
+ reloptions
+------------------------
+ {vacuum_truncate=fals}
+(1 row)
+
+-- preferred "true" alias is used when storing
+DROP TABLE reloptions_test;
+CREATE TABLE reloptions_test(i INT) WITH (vacuum_index_cleanup=on);
+SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass;
+ reloptions
+---------------------------
+ {vacuum_index_cleanup=on}
+(1 row)
+
+-- custom "third" value is available
+DROP TABLE reloptions_test;
+CREATE TABLE reloptions_test(i INT) WITH (vacuum_index_cleanup=auto);
+SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass;
+ reloptions
+-----------------------------
+ {vacuum_index_cleanup=auto}
+(1 row)
+
-- Test vacuum_truncate option
DROP TABLE reloptions_test;
CREATE TEMP TABLE reloptions_test(i INT NOT NULL, j text)
diff --git a/src/test/regress/sql/reloptions.sql b/src/test/regress/sql/reloptions.sql
index 24fbe0b478d..f5980dafcbc 100644
--- a/src/test/regress/sql/reloptions.sql
+++ b/src/test/regress/sql/reloptions.sql
@@ -59,6 +59,27 @@ UPDATE pg_class
ALTER TABLE reloptions_test RESET (illegal_option);
SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass;
+-- Tests for future (FIXME) ternary options
+
+-- behave as boolean option: accept unassigned name and truncated value
+DROP TABLE reloptions_test;
+CREATE TABLE reloptions_test(i INT) WITH (vacuum_truncate);
+SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass;
+
+DROP TABLE reloptions_test;
+CREATE TABLE reloptions_test(i INT) WITH (vacuum_truncate=FaLS);
+SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass;
+
+-- preferred "true" alias is used when storing
+DROP TABLE reloptions_test;
+CREATE TABLE reloptions_test(i INT) WITH (vacuum_index_cleanup=on);
+SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass;
+
+-- custom "third" value is available
+DROP TABLE reloptions_test;
+CREATE TABLE reloptions_test(i INT) WITH (vacuum_index_cleanup=auto);
+SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass;
+
-- Test vacuum_truncate option
DROP TABLE reloptions_test;
diff --git a/src/tools/pgindent/typedefs.list b/src/tools/pgindent/typedefs.list
index 3f3a888fd0e..1c8610fd46c 100644
--- a/src/tools/pgindent/typedefs.list
+++ b/src/tools/pgindent/typedefs.list
@@ -3952,6 +3952,7 @@ pg_sha512_ctx
pg_snapshot
pg_special_case
pg_stack_base_t
+pg_ternary
pg_time_t
pg_time_usec_t
pg_tz
@@ -4079,6 +4080,7 @@ relopt_kind
relopt_parse_elt
relopt_real
relopt_string
+relopt_ternary
relopt_type
relopt_value
relopts_validator
--
2.47.3
--c36t3fndxhkzbl5z--
^ permalink raw reply [nested|flat] 868+ messages in thread
* [PATCH v3] Introduce ternary reloptions
@ 2026-01-16 15:04 Álvaro Herrera <[email protected]>
0 siblings, 0 replies; 868+ messages in thread
From: Álvaro Herrera @ 2026-01-16 15:04 UTC (permalink / raw)
Introduce ternary reloption as a replacement for current
`vacuum_truncate` implementation. Remove the `vacuum_truncate_set`
separate flag and use TERNARY_UNSET instead.
This could also be used for other options such as `vacuum_index_cleanup`
and `buffering`, but lets get the scaffolding in first.
Discussion: https://postgr.es/m/3474141.usfYGdeWWP@thinkpad-pgpro
---
src/backend/access/common/reloptions.c | 137 ++++++++++++++++++-----
src/backend/commands/vacuum.c | 4 +-
src/include/access/reloptions.h | 27 ++---
src/include/postgres.h | 15 +++
src/include/utils/rel.h | 3 +-
src/test/regress/expected/reloptions.out | 36 ++++++
src/test/regress/sql/reloptions.sql | 21 ++++
src/tools/pgindent/typedefs.list | 2 +
8 files changed, 202 insertions(+), 43 deletions(-)
diff --git a/src/backend/access/common/reloptions.c b/src/backend/access/common/reloptions.c
index 0b83f98ed5f..6f1f577581a 100644
--- a/src/backend/access/common/reloptions.c
+++ b/src/backend/access/common/reloptions.c
@@ -40,9 +40,9 @@
*
* To add an option:
*
- * (i) decide on a type (bool, integer, real, enum, string), name, default
- * value, upper and lower bounds (if applicable); for strings, consider a
- * validation routine.
+ * (i) decide on a type (bool, ternary, integer, real, enum, string), name,
+ * default value, upper and lower bounds (if applicable); for strings,
+ * consider a validation routine.
* (ii) add a record below (or use add_<type>_reloption).
* (iii) add it to the appropriate options struct (perhaps StdRdOptions)
* (iv) add it to the appropriate handling routine (perhaps
@@ -147,15 +147,6 @@ static relopt_bool boolRelOpts[] =
},
false
},
- {
- {
- "vacuum_truncate",
- "Enables vacuum to truncate empty pages at the end of this table",
- RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
- ShareUpdateExclusiveLock
- },
- true
- },
{
{
"deduplicate_items",
@@ -170,6 +161,25 @@ static relopt_bool boolRelOpts[] =
{{NULL}}
};
+static relopt_ternary ternaryRelOpts[] =
+{
+ {
+ {
+ "vacuum_truncate",
+ "Enables vacuum to truncate empty pages at the end of this table",
+ RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
+ ShareUpdateExclusiveLock
+ },
+ TERNARY_UNSET
+ },
+ /* list terminator */
+ {
+ {
+ NULL
+ }
+ }
+};
+
static relopt_int intRelOpts[] =
{
{
@@ -609,6 +619,13 @@ initialize_reloptions(void)
boolRelOpts[i].gen.lockmode));
j++;
}
+ for (i = 0; ternaryRelOpts[i].gen.name; i++)
+ {
+ Assert(DoLockModesConflict(ternaryRelOpts[i].gen.lockmode,
+ ternaryRelOpts[i].gen.lockmode));
+ j++;
+ }
+
for (i = 0; intRelOpts[i].gen.name; i++)
{
Assert(DoLockModesConflict(intRelOpts[i].gen.lockmode,
@@ -649,6 +666,14 @@ initialize_reloptions(void)
j++;
}
+ for (i = 0; ternaryRelOpts[i].gen.name; i++)
+ {
+ relOpts[j] = &ternaryRelOpts[i].gen;
+ relOpts[j]->type = RELOPT_TYPE_TERNARY;
+ relOpts[j]->namelen = strlen(relOpts[j]->name);
+ j++;
+ }
+
for (i = 0; intRelOpts[i].gen.name; i++)
{
relOpts[j] = &intRelOpts[i].gen;
@@ -809,6 +834,9 @@ allocate_reloption(bits32 kinds, int type, const char *name, const char *desc,
case RELOPT_TYPE_BOOL:
size = sizeof(relopt_bool);
break;
+ case RELOPT_TYPE_TERNARY:
+ size = sizeof(relopt_ternary);
+ break;
case RELOPT_TYPE_INT:
size = sizeof(relopt_int);
break;
@@ -892,6 +920,57 @@ add_local_bool_reloption(local_relopts *relopts, const char *name,
add_local_reloption(relopts, (relopt_gen *) newoption, offset);
}
+/*
+ * init_ternary_reloption
+ * Allocate and initialize a new ternary reloption
+ */
+static relopt_ternary *
+init_ternary_reloption(bits32 kinds, const char *name, const char *desc,
+ pg_ternary default_val, LOCKMODE lockmode)
+{
+ relopt_ternary *newoption;
+
+ newoption = (relopt_ternary *)
+ allocate_reloption(kinds, RELOPT_TYPE_TERNARY, name, desc, lockmode);
+ newoption->default_val = default_val;
+
+ return newoption;
+}
+
+/*
+ * add_ternary_reloption
+ * Add a new ternary reloption
+ */
+void
+add_ternary_reloption(bits32 kinds, const char *name, const char *desc,
+ pg_ternary default_val, LOCKMODE lockmode)
+{
+ relopt_ternary *newoption;
+
+ newoption =
+ init_ternary_reloption(kinds, name, desc, default_val, lockmode);
+
+ add_reloption((relopt_gen *) newoption);
+}
+
+/*
+ * add_local_ternary_reloption
+ * Add a new ternary local reloption
+ *
+ * 'offset' is offset of ternary-typed field.
+ */
+void
+add_local_ternary_reloption(local_relopts *relopts, const char *name,
+ const char *desc, pg_ternary default_val,
+ int offset)
+{
+ relopt_ternary *newoption;
+
+ newoption =
+ init_ternary_reloption(RELOPT_KIND_LOCAL, name, desc, default_val, 0);
+
+ add_local_reloption(relopts, (relopt_gen *) newoption, offset);
+}
/*
* init_real_reloption
@@ -1626,6 +1705,19 @@ parse_one_reloption(relopt_value *option, char *text_str, int text_len,
option->gen->name, value)));
}
break;
+ case RELOPT_TYPE_TERNARY:
+ {
+ bool b;
+
+ parsed = parse_bool(value, &b);
+ option->values.ternary_val = b ? TERNARY_TRUE : TERNARY_FALSE;
+ if (validate && !parsed)
+ ereport(ERROR,
+ (errcode(ERRCODE_INVALID_PARAMETER_VALUE),
+ errmsg("invalid value for ternary option \"%s\": %s",
+ option->gen->name, value)));
+ }
+ break;
case RELOPT_TYPE_INT:
{
relopt_int *optint = (relopt_int *) option->gen;
@@ -1789,17 +1881,6 @@ fillRelOptions(void *rdopts, Size basesize,
char *itempos = ((char *) rdopts) + elems[j].offset;
char *string_val;
- /*
- * If isset_offset is provided, store whether the reloption is
- * set there.
- */
- if (elems[j].isset_offset > 0)
- {
- char *setpos = ((char *) rdopts) + elems[j].isset_offset;
-
- *(bool *) setpos = options[i].isset;
- }
-
switch (options[i].gen->type)
{
case RELOPT_TYPE_BOOL:
@@ -1807,6 +1888,11 @@ fillRelOptions(void *rdopts, Size basesize,
options[i].values.bool_val :
((relopt_bool *) options[i].gen)->default_val;
break;
+ case RELOPT_TYPE_TERNARY:
+ *(pg_ternary *) itempos = options[i].isset ?
+ options[i].values.ternary_val :
+ ((relopt_ternary *) options[i].gen)->default_val;
+ break;
case RELOPT_TYPE_INT:
*(int *) itempos = options[i].isset ?
options[i].values.int_val :
@@ -1923,8 +2009,8 @@ default_reloptions(Datum reloptions, bool validate, relopt_kind kind)
offsetof(StdRdOptions, parallel_workers)},
{"vacuum_index_cleanup", RELOPT_TYPE_ENUM,
offsetof(StdRdOptions, vacuum_index_cleanup)},
- {"vacuum_truncate", RELOPT_TYPE_BOOL,
- offsetof(StdRdOptions, vacuum_truncate), offsetof(StdRdOptions, vacuum_truncate_set)},
+ {"vacuum_truncate", RELOPT_TYPE_TERNARY,
+ offsetof(StdRdOptions, vacuum_truncate)},
{"vacuum_max_eager_freeze_failure_rate", RELOPT_TYPE_REAL,
offsetof(StdRdOptions, vacuum_max_eager_freeze_failure_rate)}
};
@@ -2004,7 +2090,6 @@ build_local_reloptions(local_relopts *relopts, Datum options, bool validate)
elems[i].optname = opt->option->name;
elems[i].opttype = opt->option->type;
elems[i].offset = opt->offset;
- elems[i].isset_offset = 0; /* not supported for local relopts yet */
i++;
}
diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c
index aa4fbec143f..696eab9bd97 100644
--- a/src/backend/commands/vacuum.c
+++ b/src/backend/commands/vacuum.c
@@ -2224,9 +2224,9 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams params,
{
StdRdOptions *opts = (StdRdOptions *) rel->rd_options;
- if (opts && opts->vacuum_truncate_set)
+ if (opts && opts->vacuum_truncate != TERNARY_UNSET)
{
- if (opts->vacuum_truncate)
+ if (opts->vacuum_truncate == TERNARY_TRUE)
params.truncate = VACOPTVALUE_ENABLED;
else
params.truncate = VACOPTVALUE_DISABLED;
diff --git a/src/include/access/reloptions.h b/src/include/access/reloptions.h
index 2f08e1b0cf0..dfbef2babf2 100644
--- a/src/include/access/reloptions.h
+++ b/src/include/access/reloptions.h
@@ -29,6 +29,7 @@
typedef enum relopt_type
{
RELOPT_TYPE_BOOL,
+ RELOPT_TYPE_TERNARY, /* on, off, unset */
RELOPT_TYPE_INT,
RELOPT_TYPE_REAL,
RELOPT_TYPE_ENUM,
@@ -80,6 +81,7 @@ typedef struct relopt_value
union
{
bool bool_val;
+ pg_ternary ternary_val;
int int_val;
double real_val;
int enum_val;
@@ -94,6 +96,12 @@ typedef struct relopt_bool
bool default_val;
} relopt_bool;
+typedef struct relopt_ternary
+{
+ relopt_gen gen;
+ int default_val;
+} relopt_ternary;
+
typedef struct relopt_int
{
relopt_gen gen;
@@ -152,19 +160,6 @@ typedef struct
const char *optname; /* option's name */
relopt_type opttype; /* option's datatype */
int offset; /* offset of field in result struct */
-
- /*
- * isset_offset is an optional offset of a field in the result struct that
- * stores whether the option is explicitly set for the relation or if it
- * just picked up the default value. In most cases, this can be
- * accomplished by giving the reloption a special out-of-range default
- * value (e.g., some integer reloptions use -2), but this isn't always
- * possible. For example, a Boolean reloption cannot be given an
- * out-of-range default, so we need another way to discover the source of
- * its value. This offset is only used if given a value greater than
- * zero.
- */
- int isset_offset;
} relopt_parse_elt;
/* Local reloption definition */
@@ -195,6 +190,9 @@ typedef struct local_relopts
extern relopt_kind add_reloption_kind(void);
extern void add_bool_reloption(bits32 kinds, const char *name, const char *desc,
bool default_val, LOCKMODE lockmode);
+extern void add_ternary_reloption(bits32 kinds, const char *name,
+ const char *desc, pg_ternary default_val,
+ LOCKMODE lockmode);
extern void add_int_reloption(bits32 kinds, const char *name, const char *desc,
int default_val, int min_val, int max_val,
LOCKMODE lockmode);
@@ -214,6 +212,9 @@ extern void register_reloptions_validator(local_relopts *relopts,
extern void add_local_bool_reloption(local_relopts *relopts, const char *name,
const char *desc, bool default_val,
int offset);
+extern void add_local_ternary_reloption(local_relopts *relopts,
+ const char *name, const char *desc,
+ pg_ternary default_val, int offset);
extern void add_local_int_reloption(local_relopts *relopts, const char *name,
const char *desc, int default_val,
int min_val, int max_val, int offset);
diff --git a/src/include/postgres.h b/src/include/postgres.h
index 7d93fbce709..47c88780776 100644
--- a/src/include/postgres.h
+++ b/src/include/postgres.h
@@ -543,6 +543,21 @@ Float8GetDatum(float8 X)
* ----------------------------------------------------------------
*/
+/*
+ * pg_ternary
+ * Boolean value with an extra "unset" value
+ *
+ * This enum can be used for values that want to distinguish between true,
+ * false, and unset.
+*/
+
+typedef enum pg_ternary
+{
+ TERNARY_FALSE = 0,
+ TERNARY_TRUE = 1,
+ TERNARY_UNSET = -1
+} pg_ternary;
+
/*
* NON_EXEC_STATIC: It's sometimes useful to define a variable or function
* that is normally static but extern when using EXEC_BACKEND (see
diff --git a/src/include/utils/rel.h b/src/include/utils/rel.h
index d03ab247788..5feb18a5373 100644
--- a/src/include/utils/rel.h
+++ b/src/include/utils/rel.h
@@ -347,8 +347,7 @@ typedef struct StdRdOptions
bool user_catalog_table; /* use as an additional catalog relation */
int parallel_workers; /* max number of parallel workers */
StdRdOptIndexCleanup vacuum_index_cleanup; /* controls index vacuuming */
- bool vacuum_truncate; /* enables vacuum to truncate a relation */
- bool vacuum_truncate_set; /* whether vacuum_truncate is set */
+ pg_ternary vacuum_truncate; /* enables vacuum to truncate a relation */
/*
* Fraction of pages in a relation that vacuum can eagerly scan and fail
diff --git a/src/test/regress/expected/reloptions.out b/src/test/regress/expected/reloptions.out
index 9de19b4e3f1..1c99f79ab01 100644
--- a/src/test/regress/expected/reloptions.out
+++ b/src/test/regress/expected/reloptions.out
@@ -98,6 +98,42 @@ SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass;
{fillfactor=13,autovacuum_enabled=false}
(1 row)
+-- Tests for future (FIXME) ternary options
+-- behave as boolean option: accept unassigned name and truncated value
+DROP TABLE reloptions_test;
+CREATE TABLE reloptions_test(i INT) WITH (vacuum_truncate);
+SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass;
+ reloptions
+------------------------
+ {vacuum_truncate=true}
+(1 row)
+
+DROP TABLE reloptions_test;
+CREATE TABLE reloptions_test(i INT) WITH (vacuum_truncate=FaLS);
+SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass;
+ reloptions
+------------------------
+ {vacuum_truncate=fals}
+(1 row)
+
+-- preferred "true" alias is used when storing
+DROP TABLE reloptions_test;
+CREATE TABLE reloptions_test(i INT) WITH (vacuum_index_cleanup=on);
+SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass;
+ reloptions
+---------------------------
+ {vacuum_index_cleanup=on}
+(1 row)
+
+-- custom "third" value is available
+DROP TABLE reloptions_test;
+CREATE TABLE reloptions_test(i INT) WITH (vacuum_index_cleanup=auto);
+SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass;
+ reloptions
+-----------------------------
+ {vacuum_index_cleanup=auto}
+(1 row)
+
-- Test vacuum_truncate option
DROP TABLE reloptions_test;
CREATE TEMP TABLE reloptions_test(i INT NOT NULL, j text)
diff --git a/src/test/regress/sql/reloptions.sql b/src/test/regress/sql/reloptions.sql
index 24fbe0b478d..f5980dafcbc 100644
--- a/src/test/regress/sql/reloptions.sql
+++ b/src/test/regress/sql/reloptions.sql
@@ -59,6 +59,27 @@ UPDATE pg_class
ALTER TABLE reloptions_test RESET (illegal_option);
SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass;
+-- Tests for future (FIXME) ternary options
+
+-- behave as boolean option: accept unassigned name and truncated value
+DROP TABLE reloptions_test;
+CREATE TABLE reloptions_test(i INT) WITH (vacuum_truncate);
+SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass;
+
+DROP TABLE reloptions_test;
+CREATE TABLE reloptions_test(i INT) WITH (vacuum_truncate=FaLS);
+SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass;
+
+-- preferred "true" alias is used when storing
+DROP TABLE reloptions_test;
+CREATE TABLE reloptions_test(i INT) WITH (vacuum_index_cleanup=on);
+SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass;
+
+-- custom "third" value is available
+DROP TABLE reloptions_test;
+CREATE TABLE reloptions_test(i INT) WITH (vacuum_index_cleanup=auto);
+SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass;
+
-- Test vacuum_truncate option
DROP TABLE reloptions_test;
diff --git a/src/tools/pgindent/typedefs.list b/src/tools/pgindent/typedefs.list
index 3f3a888fd0e..1c8610fd46c 100644
--- a/src/tools/pgindent/typedefs.list
+++ b/src/tools/pgindent/typedefs.list
@@ -3952,6 +3952,7 @@ pg_sha512_ctx
pg_snapshot
pg_special_case
pg_stack_base_t
+pg_ternary
pg_time_t
pg_time_usec_t
pg_tz
@@ -4079,6 +4080,7 @@ relopt_kind
relopt_parse_elt
relopt_real
relopt_string
+relopt_ternary
relopt_type
relopt_value
relopts_validator
--
2.47.3
--c36t3fndxhkzbl5z--
^ permalink raw reply [nested|flat] 868+ messages in thread
* [PATCH v3] Introduce ternary reloptions
@ 2026-01-16 15:04 Álvaro Herrera <[email protected]>
0 siblings, 0 replies; 868+ messages in thread
From: Álvaro Herrera @ 2026-01-16 15:04 UTC (permalink / raw)
Introduce ternary reloption as a replacement for current
`vacuum_truncate` implementation. Remove the `vacuum_truncate_set`
separate flag and use TERNARY_UNSET instead.
This could also be used for other options such as `vacuum_index_cleanup`
and `buffering`, but lets get the scaffolding in first.
Discussion: https://postgr.es/m/3474141.usfYGdeWWP@thinkpad-pgpro
---
src/backend/access/common/reloptions.c | 137 ++++++++++++++++++-----
src/backend/commands/vacuum.c | 4 +-
src/include/access/reloptions.h | 27 ++---
src/include/postgres.h | 15 +++
src/include/utils/rel.h | 3 +-
src/test/regress/expected/reloptions.out | 36 ++++++
src/test/regress/sql/reloptions.sql | 21 ++++
src/tools/pgindent/typedefs.list | 2 +
8 files changed, 202 insertions(+), 43 deletions(-)
diff --git a/src/backend/access/common/reloptions.c b/src/backend/access/common/reloptions.c
index 0b83f98ed5f..6f1f577581a 100644
--- a/src/backend/access/common/reloptions.c
+++ b/src/backend/access/common/reloptions.c
@@ -40,9 +40,9 @@
*
* To add an option:
*
- * (i) decide on a type (bool, integer, real, enum, string), name, default
- * value, upper and lower bounds (if applicable); for strings, consider a
- * validation routine.
+ * (i) decide on a type (bool, ternary, integer, real, enum, string), name,
+ * default value, upper and lower bounds (if applicable); for strings,
+ * consider a validation routine.
* (ii) add a record below (or use add_<type>_reloption).
* (iii) add it to the appropriate options struct (perhaps StdRdOptions)
* (iv) add it to the appropriate handling routine (perhaps
@@ -147,15 +147,6 @@ static relopt_bool boolRelOpts[] =
},
false
},
- {
- {
- "vacuum_truncate",
- "Enables vacuum to truncate empty pages at the end of this table",
- RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
- ShareUpdateExclusiveLock
- },
- true
- },
{
{
"deduplicate_items",
@@ -170,6 +161,25 @@ static relopt_bool boolRelOpts[] =
{{NULL}}
};
+static relopt_ternary ternaryRelOpts[] =
+{
+ {
+ {
+ "vacuum_truncate",
+ "Enables vacuum to truncate empty pages at the end of this table",
+ RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
+ ShareUpdateExclusiveLock
+ },
+ TERNARY_UNSET
+ },
+ /* list terminator */
+ {
+ {
+ NULL
+ }
+ }
+};
+
static relopt_int intRelOpts[] =
{
{
@@ -609,6 +619,13 @@ initialize_reloptions(void)
boolRelOpts[i].gen.lockmode));
j++;
}
+ for (i = 0; ternaryRelOpts[i].gen.name; i++)
+ {
+ Assert(DoLockModesConflict(ternaryRelOpts[i].gen.lockmode,
+ ternaryRelOpts[i].gen.lockmode));
+ j++;
+ }
+
for (i = 0; intRelOpts[i].gen.name; i++)
{
Assert(DoLockModesConflict(intRelOpts[i].gen.lockmode,
@@ -649,6 +666,14 @@ initialize_reloptions(void)
j++;
}
+ for (i = 0; ternaryRelOpts[i].gen.name; i++)
+ {
+ relOpts[j] = &ternaryRelOpts[i].gen;
+ relOpts[j]->type = RELOPT_TYPE_TERNARY;
+ relOpts[j]->namelen = strlen(relOpts[j]->name);
+ j++;
+ }
+
for (i = 0; intRelOpts[i].gen.name; i++)
{
relOpts[j] = &intRelOpts[i].gen;
@@ -809,6 +834,9 @@ allocate_reloption(bits32 kinds, int type, const char *name, const char *desc,
case RELOPT_TYPE_BOOL:
size = sizeof(relopt_bool);
break;
+ case RELOPT_TYPE_TERNARY:
+ size = sizeof(relopt_ternary);
+ break;
case RELOPT_TYPE_INT:
size = sizeof(relopt_int);
break;
@@ -892,6 +920,57 @@ add_local_bool_reloption(local_relopts *relopts, const char *name,
add_local_reloption(relopts, (relopt_gen *) newoption, offset);
}
+/*
+ * init_ternary_reloption
+ * Allocate and initialize a new ternary reloption
+ */
+static relopt_ternary *
+init_ternary_reloption(bits32 kinds, const char *name, const char *desc,
+ pg_ternary default_val, LOCKMODE lockmode)
+{
+ relopt_ternary *newoption;
+
+ newoption = (relopt_ternary *)
+ allocate_reloption(kinds, RELOPT_TYPE_TERNARY, name, desc, lockmode);
+ newoption->default_val = default_val;
+
+ return newoption;
+}
+
+/*
+ * add_ternary_reloption
+ * Add a new ternary reloption
+ */
+void
+add_ternary_reloption(bits32 kinds, const char *name, const char *desc,
+ pg_ternary default_val, LOCKMODE lockmode)
+{
+ relopt_ternary *newoption;
+
+ newoption =
+ init_ternary_reloption(kinds, name, desc, default_val, lockmode);
+
+ add_reloption((relopt_gen *) newoption);
+}
+
+/*
+ * add_local_ternary_reloption
+ * Add a new ternary local reloption
+ *
+ * 'offset' is offset of ternary-typed field.
+ */
+void
+add_local_ternary_reloption(local_relopts *relopts, const char *name,
+ const char *desc, pg_ternary default_val,
+ int offset)
+{
+ relopt_ternary *newoption;
+
+ newoption =
+ init_ternary_reloption(RELOPT_KIND_LOCAL, name, desc, default_val, 0);
+
+ add_local_reloption(relopts, (relopt_gen *) newoption, offset);
+}
/*
* init_real_reloption
@@ -1626,6 +1705,19 @@ parse_one_reloption(relopt_value *option, char *text_str, int text_len,
option->gen->name, value)));
}
break;
+ case RELOPT_TYPE_TERNARY:
+ {
+ bool b;
+
+ parsed = parse_bool(value, &b);
+ option->values.ternary_val = b ? TERNARY_TRUE : TERNARY_FALSE;
+ if (validate && !parsed)
+ ereport(ERROR,
+ (errcode(ERRCODE_INVALID_PARAMETER_VALUE),
+ errmsg("invalid value for ternary option \"%s\": %s",
+ option->gen->name, value)));
+ }
+ break;
case RELOPT_TYPE_INT:
{
relopt_int *optint = (relopt_int *) option->gen;
@@ -1789,17 +1881,6 @@ fillRelOptions(void *rdopts, Size basesize,
char *itempos = ((char *) rdopts) + elems[j].offset;
char *string_val;
- /*
- * If isset_offset is provided, store whether the reloption is
- * set there.
- */
- if (elems[j].isset_offset > 0)
- {
- char *setpos = ((char *) rdopts) + elems[j].isset_offset;
-
- *(bool *) setpos = options[i].isset;
- }
-
switch (options[i].gen->type)
{
case RELOPT_TYPE_BOOL:
@@ -1807,6 +1888,11 @@ fillRelOptions(void *rdopts, Size basesize,
options[i].values.bool_val :
((relopt_bool *) options[i].gen)->default_val;
break;
+ case RELOPT_TYPE_TERNARY:
+ *(pg_ternary *) itempos = options[i].isset ?
+ options[i].values.ternary_val :
+ ((relopt_ternary *) options[i].gen)->default_val;
+ break;
case RELOPT_TYPE_INT:
*(int *) itempos = options[i].isset ?
options[i].values.int_val :
@@ -1923,8 +2009,8 @@ default_reloptions(Datum reloptions, bool validate, relopt_kind kind)
offsetof(StdRdOptions, parallel_workers)},
{"vacuum_index_cleanup", RELOPT_TYPE_ENUM,
offsetof(StdRdOptions, vacuum_index_cleanup)},
- {"vacuum_truncate", RELOPT_TYPE_BOOL,
- offsetof(StdRdOptions, vacuum_truncate), offsetof(StdRdOptions, vacuum_truncate_set)},
+ {"vacuum_truncate", RELOPT_TYPE_TERNARY,
+ offsetof(StdRdOptions, vacuum_truncate)},
{"vacuum_max_eager_freeze_failure_rate", RELOPT_TYPE_REAL,
offsetof(StdRdOptions, vacuum_max_eager_freeze_failure_rate)}
};
@@ -2004,7 +2090,6 @@ build_local_reloptions(local_relopts *relopts, Datum options, bool validate)
elems[i].optname = opt->option->name;
elems[i].opttype = opt->option->type;
elems[i].offset = opt->offset;
- elems[i].isset_offset = 0; /* not supported for local relopts yet */
i++;
}
diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c
index aa4fbec143f..696eab9bd97 100644
--- a/src/backend/commands/vacuum.c
+++ b/src/backend/commands/vacuum.c
@@ -2224,9 +2224,9 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams params,
{
StdRdOptions *opts = (StdRdOptions *) rel->rd_options;
- if (opts && opts->vacuum_truncate_set)
+ if (opts && opts->vacuum_truncate != TERNARY_UNSET)
{
- if (opts->vacuum_truncate)
+ if (opts->vacuum_truncate == TERNARY_TRUE)
params.truncate = VACOPTVALUE_ENABLED;
else
params.truncate = VACOPTVALUE_DISABLED;
diff --git a/src/include/access/reloptions.h b/src/include/access/reloptions.h
index 2f08e1b0cf0..dfbef2babf2 100644
--- a/src/include/access/reloptions.h
+++ b/src/include/access/reloptions.h
@@ -29,6 +29,7 @@
typedef enum relopt_type
{
RELOPT_TYPE_BOOL,
+ RELOPT_TYPE_TERNARY, /* on, off, unset */
RELOPT_TYPE_INT,
RELOPT_TYPE_REAL,
RELOPT_TYPE_ENUM,
@@ -80,6 +81,7 @@ typedef struct relopt_value
union
{
bool bool_val;
+ pg_ternary ternary_val;
int int_val;
double real_val;
int enum_val;
@@ -94,6 +96,12 @@ typedef struct relopt_bool
bool default_val;
} relopt_bool;
+typedef struct relopt_ternary
+{
+ relopt_gen gen;
+ int default_val;
+} relopt_ternary;
+
typedef struct relopt_int
{
relopt_gen gen;
@@ -152,19 +160,6 @@ typedef struct
const char *optname; /* option's name */
relopt_type opttype; /* option's datatype */
int offset; /* offset of field in result struct */
-
- /*
- * isset_offset is an optional offset of a field in the result struct that
- * stores whether the option is explicitly set for the relation or if it
- * just picked up the default value. In most cases, this can be
- * accomplished by giving the reloption a special out-of-range default
- * value (e.g., some integer reloptions use -2), but this isn't always
- * possible. For example, a Boolean reloption cannot be given an
- * out-of-range default, so we need another way to discover the source of
- * its value. This offset is only used if given a value greater than
- * zero.
- */
- int isset_offset;
} relopt_parse_elt;
/* Local reloption definition */
@@ -195,6 +190,9 @@ typedef struct local_relopts
extern relopt_kind add_reloption_kind(void);
extern void add_bool_reloption(bits32 kinds, const char *name, const char *desc,
bool default_val, LOCKMODE lockmode);
+extern void add_ternary_reloption(bits32 kinds, const char *name,
+ const char *desc, pg_ternary default_val,
+ LOCKMODE lockmode);
extern void add_int_reloption(bits32 kinds, const char *name, const char *desc,
int default_val, int min_val, int max_val,
LOCKMODE lockmode);
@@ -214,6 +212,9 @@ extern void register_reloptions_validator(local_relopts *relopts,
extern void add_local_bool_reloption(local_relopts *relopts, const char *name,
const char *desc, bool default_val,
int offset);
+extern void add_local_ternary_reloption(local_relopts *relopts,
+ const char *name, const char *desc,
+ pg_ternary default_val, int offset);
extern void add_local_int_reloption(local_relopts *relopts, const char *name,
const char *desc, int default_val,
int min_val, int max_val, int offset);
diff --git a/src/include/postgres.h b/src/include/postgres.h
index 7d93fbce709..47c88780776 100644
--- a/src/include/postgres.h
+++ b/src/include/postgres.h
@@ -543,6 +543,21 @@ Float8GetDatum(float8 X)
* ----------------------------------------------------------------
*/
+/*
+ * pg_ternary
+ * Boolean value with an extra "unset" value
+ *
+ * This enum can be used for values that want to distinguish between true,
+ * false, and unset.
+*/
+
+typedef enum pg_ternary
+{
+ TERNARY_FALSE = 0,
+ TERNARY_TRUE = 1,
+ TERNARY_UNSET = -1
+} pg_ternary;
+
/*
* NON_EXEC_STATIC: It's sometimes useful to define a variable or function
* that is normally static but extern when using EXEC_BACKEND (see
diff --git a/src/include/utils/rel.h b/src/include/utils/rel.h
index d03ab247788..5feb18a5373 100644
--- a/src/include/utils/rel.h
+++ b/src/include/utils/rel.h
@@ -347,8 +347,7 @@ typedef struct StdRdOptions
bool user_catalog_table; /* use as an additional catalog relation */
int parallel_workers; /* max number of parallel workers */
StdRdOptIndexCleanup vacuum_index_cleanup; /* controls index vacuuming */
- bool vacuum_truncate; /* enables vacuum to truncate a relation */
- bool vacuum_truncate_set; /* whether vacuum_truncate is set */
+ pg_ternary vacuum_truncate; /* enables vacuum to truncate a relation */
/*
* Fraction of pages in a relation that vacuum can eagerly scan and fail
diff --git a/src/test/regress/expected/reloptions.out b/src/test/regress/expected/reloptions.out
index 9de19b4e3f1..1c99f79ab01 100644
--- a/src/test/regress/expected/reloptions.out
+++ b/src/test/regress/expected/reloptions.out
@@ -98,6 +98,42 @@ SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass;
{fillfactor=13,autovacuum_enabled=false}
(1 row)
+-- Tests for future (FIXME) ternary options
+-- behave as boolean option: accept unassigned name and truncated value
+DROP TABLE reloptions_test;
+CREATE TABLE reloptions_test(i INT) WITH (vacuum_truncate);
+SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass;
+ reloptions
+------------------------
+ {vacuum_truncate=true}
+(1 row)
+
+DROP TABLE reloptions_test;
+CREATE TABLE reloptions_test(i INT) WITH (vacuum_truncate=FaLS);
+SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass;
+ reloptions
+------------------------
+ {vacuum_truncate=fals}
+(1 row)
+
+-- preferred "true" alias is used when storing
+DROP TABLE reloptions_test;
+CREATE TABLE reloptions_test(i INT) WITH (vacuum_index_cleanup=on);
+SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass;
+ reloptions
+---------------------------
+ {vacuum_index_cleanup=on}
+(1 row)
+
+-- custom "third" value is available
+DROP TABLE reloptions_test;
+CREATE TABLE reloptions_test(i INT) WITH (vacuum_index_cleanup=auto);
+SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass;
+ reloptions
+-----------------------------
+ {vacuum_index_cleanup=auto}
+(1 row)
+
-- Test vacuum_truncate option
DROP TABLE reloptions_test;
CREATE TEMP TABLE reloptions_test(i INT NOT NULL, j text)
diff --git a/src/test/regress/sql/reloptions.sql b/src/test/regress/sql/reloptions.sql
index 24fbe0b478d..f5980dafcbc 100644
--- a/src/test/regress/sql/reloptions.sql
+++ b/src/test/regress/sql/reloptions.sql
@@ -59,6 +59,27 @@ UPDATE pg_class
ALTER TABLE reloptions_test RESET (illegal_option);
SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass;
+-- Tests for future (FIXME) ternary options
+
+-- behave as boolean option: accept unassigned name and truncated value
+DROP TABLE reloptions_test;
+CREATE TABLE reloptions_test(i INT) WITH (vacuum_truncate);
+SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass;
+
+DROP TABLE reloptions_test;
+CREATE TABLE reloptions_test(i INT) WITH (vacuum_truncate=FaLS);
+SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass;
+
+-- preferred "true" alias is used when storing
+DROP TABLE reloptions_test;
+CREATE TABLE reloptions_test(i INT) WITH (vacuum_index_cleanup=on);
+SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass;
+
+-- custom "third" value is available
+DROP TABLE reloptions_test;
+CREATE TABLE reloptions_test(i INT) WITH (vacuum_index_cleanup=auto);
+SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass;
+
-- Test vacuum_truncate option
DROP TABLE reloptions_test;
diff --git a/src/tools/pgindent/typedefs.list b/src/tools/pgindent/typedefs.list
index 3f3a888fd0e..1c8610fd46c 100644
--- a/src/tools/pgindent/typedefs.list
+++ b/src/tools/pgindent/typedefs.list
@@ -3952,6 +3952,7 @@ pg_sha512_ctx
pg_snapshot
pg_special_case
pg_stack_base_t
+pg_ternary
pg_time_t
pg_time_usec_t
pg_tz
@@ -4079,6 +4080,7 @@ relopt_kind
relopt_parse_elt
relopt_real
relopt_string
+relopt_ternary
relopt_type
relopt_value
relopts_validator
--
2.47.3
--c36t3fndxhkzbl5z--
^ permalink raw reply [nested|flat] 868+ messages in thread
* [PATCH v3] Introduce ternary reloptions
@ 2026-01-16 15:04 Álvaro Herrera <[email protected]>
0 siblings, 0 replies; 868+ messages in thread
From: Álvaro Herrera @ 2026-01-16 15:04 UTC (permalink / raw)
Introduce ternary reloption as a replacement for current
`vacuum_truncate` implementation. Remove the `vacuum_truncate_set`
separate flag and use TERNARY_UNSET instead.
This could also be used for other options such as `vacuum_index_cleanup`
and `buffering`, but lets get the scaffolding in first.
Discussion: https://postgr.es/m/3474141.usfYGdeWWP@thinkpad-pgpro
---
src/backend/access/common/reloptions.c | 137 ++++++++++++++++++-----
src/backend/commands/vacuum.c | 4 +-
src/include/access/reloptions.h | 27 ++---
src/include/postgres.h | 15 +++
src/include/utils/rel.h | 3 +-
src/test/regress/expected/reloptions.out | 36 ++++++
src/test/regress/sql/reloptions.sql | 21 ++++
src/tools/pgindent/typedefs.list | 2 +
8 files changed, 202 insertions(+), 43 deletions(-)
diff --git a/src/backend/access/common/reloptions.c b/src/backend/access/common/reloptions.c
index 0b83f98ed5f..6f1f577581a 100644
--- a/src/backend/access/common/reloptions.c
+++ b/src/backend/access/common/reloptions.c
@@ -40,9 +40,9 @@
*
* To add an option:
*
- * (i) decide on a type (bool, integer, real, enum, string), name, default
- * value, upper and lower bounds (if applicable); for strings, consider a
- * validation routine.
+ * (i) decide on a type (bool, ternary, integer, real, enum, string), name,
+ * default value, upper and lower bounds (if applicable); for strings,
+ * consider a validation routine.
* (ii) add a record below (or use add_<type>_reloption).
* (iii) add it to the appropriate options struct (perhaps StdRdOptions)
* (iv) add it to the appropriate handling routine (perhaps
@@ -147,15 +147,6 @@ static relopt_bool boolRelOpts[] =
},
false
},
- {
- {
- "vacuum_truncate",
- "Enables vacuum to truncate empty pages at the end of this table",
- RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
- ShareUpdateExclusiveLock
- },
- true
- },
{
{
"deduplicate_items",
@@ -170,6 +161,25 @@ static relopt_bool boolRelOpts[] =
{{NULL}}
};
+static relopt_ternary ternaryRelOpts[] =
+{
+ {
+ {
+ "vacuum_truncate",
+ "Enables vacuum to truncate empty pages at the end of this table",
+ RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
+ ShareUpdateExclusiveLock
+ },
+ TERNARY_UNSET
+ },
+ /* list terminator */
+ {
+ {
+ NULL
+ }
+ }
+};
+
static relopt_int intRelOpts[] =
{
{
@@ -609,6 +619,13 @@ initialize_reloptions(void)
boolRelOpts[i].gen.lockmode));
j++;
}
+ for (i = 0; ternaryRelOpts[i].gen.name; i++)
+ {
+ Assert(DoLockModesConflict(ternaryRelOpts[i].gen.lockmode,
+ ternaryRelOpts[i].gen.lockmode));
+ j++;
+ }
+
for (i = 0; intRelOpts[i].gen.name; i++)
{
Assert(DoLockModesConflict(intRelOpts[i].gen.lockmode,
@@ -649,6 +666,14 @@ initialize_reloptions(void)
j++;
}
+ for (i = 0; ternaryRelOpts[i].gen.name; i++)
+ {
+ relOpts[j] = &ternaryRelOpts[i].gen;
+ relOpts[j]->type = RELOPT_TYPE_TERNARY;
+ relOpts[j]->namelen = strlen(relOpts[j]->name);
+ j++;
+ }
+
for (i = 0; intRelOpts[i].gen.name; i++)
{
relOpts[j] = &intRelOpts[i].gen;
@@ -809,6 +834,9 @@ allocate_reloption(bits32 kinds, int type, const char *name, const char *desc,
case RELOPT_TYPE_BOOL:
size = sizeof(relopt_bool);
break;
+ case RELOPT_TYPE_TERNARY:
+ size = sizeof(relopt_ternary);
+ break;
case RELOPT_TYPE_INT:
size = sizeof(relopt_int);
break;
@@ -892,6 +920,57 @@ add_local_bool_reloption(local_relopts *relopts, const char *name,
add_local_reloption(relopts, (relopt_gen *) newoption, offset);
}
+/*
+ * init_ternary_reloption
+ * Allocate and initialize a new ternary reloption
+ */
+static relopt_ternary *
+init_ternary_reloption(bits32 kinds, const char *name, const char *desc,
+ pg_ternary default_val, LOCKMODE lockmode)
+{
+ relopt_ternary *newoption;
+
+ newoption = (relopt_ternary *)
+ allocate_reloption(kinds, RELOPT_TYPE_TERNARY, name, desc, lockmode);
+ newoption->default_val = default_val;
+
+ return newoption;
+}
+
+/*
+ * add_ternary_reloption
+ * Add a new ternary reloption
+ */
+void
+add_ternary_reloption(bits32 kinds, const char *name, const char *desc,
+ pg_ternary default_val, LOCKMODE lockmode)
+{
+ relopt_ternary *newoption;
+
+ newoption =
+ init_ternary_reloption(kinds, name, desc, default_val, lockmode);
+
+ add_reloption((relopt_gen *) newoption);
+}
+
+/*
+ * add_local_ternary_reloption
+ * Add a new ternary local reloption
+ *
+ * 'offset' is offset of ternary-typed field.
+ */
+void
+add_local_ternary_reloption(local_relopts *relopts, const char *name,
+ const char *desc, pg_ternary default_val,
+ int offset)
+{
+ relopt_ternary *newoption;
+
+ newoption =
+ init_ternary_reloption(RELOPT_KIND_LOCAL, name, desc, default_val, 0);
+
+ add_local_reloption(relopts, (relopt_gen *) newoption, offset);
+}
/*
* init_real_reloption
@@ -1626,6 +1705,19 @@ parse_one_reloption(relopt_value *option, char *text_str, int text_len,
option->gen->name, value)));
}
break;
+ case RELOPT_TYPE_TERNARY:
+ {
+ bool b;
+
+ parsed = parse_bool(value, &b);
+ option->values.ternary_val = b ? TERNARY_TRUE : TERNARY_FALSE;
+ if (validate && !parsed)
+ ereport(ERROR,
+ (errcode(ERRCODE_INVALID_PARAMETER_VALUE),
+ errmsg("invalid value for ternary option \"%s\": %s",
+ option->gen->name, value)));
+ }
+ break;
case RELOPT_TYPE_INT:
{
relopt_int *optint = (relopt_int *) option->gen;
@@ -1789,17 +1881,6 @@ fillRelOptions(void *rdopts, Size basesize,
char *itempos = ((char *) rdopts) + elems[j].offset;
char *string_val;
- /*
- * If isset_offset is provided, store whether the reloption is
- * set there.
- */
- if (elems[j].isset_offset > 0)
- {
- char *setpos = ((char *) rdopts) + elems[j].isset_offset;
-
- *(bool *) setpos = options[i].isset;
- }
-
switch (options[i].gen->type)
{
case RELOPT_TYPE_BOOL:
@@ -1807,6 +1888,11 @@ fillRelOptions(void *rdopts, Size basesize,
options[i].values.bool_val :
((relopt_bool *) options[i].gen)->default_val;
break;
+ case RELOPT_TYPE_TERNARY:
+ *(pg_ternary *) itempos = options[i].isset ?
+ options[i].values.ternary_val :
+ ((relopt_ternary *) options[i].gen)->default_val;
+ break;
case RELOPT_TYPE_INT:
*(int *) itempos = options[i].isset ?
options[i].values.int_val :
@@ -1923,8 +2009,8 @@ default_reloptions(Datum reloptions, bool validate, relopt_kind kind)
offsetof(StdRdOptions, parallel_workers)},
{"vacuum_index_cleanup", RELOPT_TYPE_ENUM,
offsetof(StdRdOptions, vacuum_index_cleanup)},
- {"vacuum_truncate", RELOPT_TYPE_BOOL,
- offsetof(StdRdOptions, vacuum_truncate), offsetof(StdRdOptions, vacuum_truncate_set)},
+ {"vacuum_truncate", RELOPT_TYPE_TERNARY,
+ offsetof(StdRdOptions, vacuum_truncate)},
{"vacuum_max_eager_freeze_failure_rate", RELOPT_TYPE_REAL,
offsetof(StdRdOptions, vacuum_max_eager_freeze_failure_rate)}
};
@@ -2004,7 +2090,6 @@ build_local_reloptions(local_relopts *relopts, Datum options, bool validate)
elems[i].optname = opt->option->name;
elems[i].opttype = opt->option->type;
elems[i].offset = opt->offset;
- elems[i].isset_offset = 0; /* not supported for local relopts yet */
i++;
}
diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c
index aa4fbec143f..696eab9bd97 100644
--- a/src/backend/commands/vacuum.c
+++ b/src/backend/commands/vacuum.c
@@ -2224,9 +2224,9 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams params,
{
StdRdOptions *opts = (StdRdOptions *) rel->rd_options;
- if (opts && opts->vacuum_truncate_set)
+ if (opts && opts->vacuum_truncate != TERNARY_UNSET)
{
- if (opts->vacuum_truncate)
+ if (opts->vacuum_truncate == TERNARY_TRUE)
params.truncate = VACOPTVALUE_ENABLED;
else
params.truncate = VACOPTVALUE_DISABLED;
diff --git a/src/include/access/reloptions.h b/src/include/access/reloptions.h
index 2f08e1b0cf0..dfbef2babf2 100644
--- a/src/include/access/reloptions.h
+++ b/src/include/access/reloptions.h
@@ -29,6 +29,7 @@
typedef enum relopt_type
{
RELOPT_TYPE_BOOL,
+ RELOPT_TYPE_TERNARY, /* on, off, unset */
RELOPT_TYPE_INT,
RELOPT_TYPE_REAL,
RELOPT_TYPE_ENUM,
@@ -80,6 +81,7 @@ typedef struct relopt_value
union
{
bool bool_val;
+ pg_ternary ternary_val;
int int_val;
double real_val;
int enum_val;
@@ -94,6 +96,12 @@ typedef struct relopt_bool
bool default_val;
} relopt_bool;
+typedef struct relopt_ternary
+{
+ relopt_gen gen;
+ int default_val;
+} relopt_ternary;
+
typedef struct relopt_int
{
relopt_gen gen;
@@ -152,19 +160,6 @@ typedef struct
const char *optname; /* option's name */
relopt_type opttype; /* option's datatype */
int offset; /* offset of field in result struct */
-
- /*
- * isset_offset is an optional offset of a field in the result struct that
- * stores whether the option is explicitly set for the relation or if it
- * just picked up the default value. In most cases, this can be
- * accomplished by giving the reloption a special out-of-range default
- * value (e.g., some integer reloptions use -2), but this isn't always
- * possible. For example, a Boolean reloption cannot be given an
- * out-of-range default, so we need another way to discover the source of
- * its value. This offset is only used if given a value greater than
- * zero.
- */
- int isset_offset;
} relopt_parse_elt;
/* Local reloption definition */
@@ -195,6 +190,9 @@ typedef struct local_relopts
extern relopt_kind add_reloption_kind(void);
extern void add_bool_reloption(bits32 kinds, const char *name, const char *desc,
bool default_val, LOCKMODE lockmode);
+extern void add_ternary_reloption(bits32 kinds, const char *name,
+ const char *desc, pg_ternary default_val,
+ LOCKMODE lockmode);
extern void add_int_reloption(bits32 kinds, const char *name, const char *desc,
int default_val, int min_val, int max_val,
LOCKMODE lockmode);
@@ -214,6 +212,9 @@ extern void register_reloptions_validator(local_relopts *relopts,
extern void add_local_bool_reloption(local_relopts *relopts, const char *name,
const char *desc, bool default_val,
int offset);
+extern void add_local_ternary_reloption(local_relopts *relopts,
+ const char *name, const char *desc,
+ pg_ternary default_val, int offset);
extern void add_local_int_reloption(local_relopts *relopts, const char *name,
const char *desc, int default_val,
int min_val, int max_val, int offset);
diff --git a/src/include/postgres.h b/src/include/postgres.h
index 7d93fbce709..47c88780776 100644
--- a/src/include/postgres.h
+++ b/src/include/postgres.h
@@ -543,6 +543,21 @@ Float8GetDatum(float8 X)
* ----------------------------------------------------------------
*/
+/*
+ * pg_ternary
+ * Boolean value with an extra "unset" value
+ *
+ * This enum can be used for values that want to distinguish between true,
+ * false, and unset.
+*/
+
+typedef enum pg_ternary
+{
+ TERNARY_FALSE = 0,
+ TERNARY_TRUE = 1,
+ TERNARY_UNSET = -1
+} pg_ternary;
+
/*
* NON_EXEC_STATIC: It's sometimes useful to define a variable or function
* that is normally static but extern when using EXEC_BACKEND (see
diff --git a/src/include/utils/rel.h b/src/include/utils/rel.h
index d03ab247788..5feb18a5373 100644
--- a/src/include/utils/rel.h
+++ b/src/include/utils/rel.h
@@ -347,8 +347,7 @@ typedef struct StdRdOptions
bool user_catalog_table; /* use as an additional catalog relation */
int parallel_workers; /* max number of parallel workers */
StdRdOptIndexCleanup vacuum_index_cleanup; /* controls index vacuuming */
- bool vacuum_truncate; /* enables vacuum to truncate a relation */
- bool vacuum_truncate_set; /* whether vacuum_truncate is set */
+ pg_ternary vacuum_truncate; /* enables vacuum to truncate a relation */
/*
* Fraction of pages in a relation that vacuum can eagerly scan and fail
diff --git a/src/test/regress/expected/reloptions.out b/src/test/regress/expected/reloptions.out
index 9de19b4e3f1..1c99f79ab01 100644
--- a/src/test/regress/expected/reloptions.out
+++ b/src/test/regress/expected/reloptions.out
@@ -98,6 +98,42 @@ SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass;
{fillfactor=13,autovacuum_enabled=false}
(1 row)
+-- Tests for future (FIXME) ternary options
+-- behave as boolean option: accept unassigned name and truncated value
+DROP TABLE reloptions_test;
+CREATE TABLE reloptions_test(i INT) WITH (vacuum_truncate);
+SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass;
+ reloptions
+------------------------
+ {vacuum_truncate=true}
+(1 row)
+
+DROP TABLE reloptions_test;
+CREATE TABLE reloptions_test(i INT) WITH (vacuum_truncate=FaLS);
+SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass;
+ reloptions
+------------------------
+ {vacuum_truncate=fals}
+(1 row)
+
+-- preferred "true" alias is used when storing
+DROP TABLE reloptions_test;
+CREATE TABLE reloptions_test(i INT) WITH (vacuum_index_cleanup=on);
+SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass;
+ reloptions
+---------------------------
+ {vacuum_index_cleanup=on}
+(1 row)
+
+-- custom "third" value is available
+DROP TABLE reloptions_test;
+CREATE TABLE reloptions_test(i INT) WITH (vacuum_index_cleanup=auto);
+SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass;
+ reloptions
+-----------------------------
+ {vacuum_index_cleanup=auto}
+(1 row)
+
-- Test vacuum_truncate option
DROP TABLE reloptions_test;
CREATE TEMP TABLE reloptions_test(i INT NOT NULL, j text)
diff --git a/src/test/regress/sql/reloptions.sql b/src/test/regress/sql/reloptions.sql
index 24fbe0b478d..f5980dafcbc 100644
--- a/src/test/regress/sql/reloptions.sql
+++ b/src/test/regress/sql/reloptions.sql
@@ -59,6 +59,27 @@ UPDATE pg_class
ALTER TABLE reloptions_test RESET (illegal_option);
SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass;
+-- Tests for future (FIXME) ternary options
+
+-- behave as boolean option: accept unassigned name and truncated value
+DROP TABLE reloptions_test;
+CREATE TABLE reloptions_test(i INT) WITH (vacuum_truncate);
+SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass;
+
+DROP TABLE reloptions_test;
+CREATE TABLE reloptions_test(i INT) WITH (vacuum_truncate=FaLS);
+SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass;
+
+-- preferred "true" alias is used when storing
+DROP TABLE reloptions_test;
+CREATE TABLE reloptions_test(i INT) WITH (vacuum_index_cleanup=on);
+SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass;
+
+-- custom "third" value is available
+DROP TABLE reloptions_test;
+CREATE TABLE reloptions_test(i INT) WITH (vacuum_index_cleanup=auto);
+SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass;
+
-- Test vacuum_truncate option
DROP TABLE reloptions_test;
diff --git a/src/tools/pgindent/typedefs.list b/src/tools/pgindent/typedefs.list
index 3f3a888fd0e..1c8610fd46c 100644
--- a/src/tools/pgindent/typedefs.list
+++ b/src/tools/pgindent/typedefs.list
@@ -3952,6 +3952,7 @@ pg_sha512_ctx
pg_snapshot
pg_special_case
pg_stack_base_t
+pg_ternary
pg_time_t
pg_time_usec_t
pg_tz
@@ -4079,6 +4080,7 @@ relopt_kind
relopt_parse_elt
relopt_real
relopt_string
+relopt_ternary
relopt_type
relopt_value
relopts_validator
--
2.47.3
--c36t3fndxhkzbl5z--
^ permalink raw reply [nested|flat] 868+ messages in thread
* [PATCH v3] Introduce ternary reloptions
@ 2026-01-16 15:04 Álvaro Herrera <[email protected]>
0 siblings, 0 replies; 868+ messages in thread
From: Álvaro Herrera @ 2026-01-16 15:04 UTC (permalink / raw)
Introduce ternary reloption as a replacement for current
`vacuum_truncate` implementation. Remove the `vacuum_truncate_set`
separate flag and use TERNARY_UNSET instead.
This could also be used for other options such as `vacuum_index_cleanup`
and `buffering`, but lets get the scaffolding in first.
Discussion: https://postgr.es/m/3474141.usfYGdeWWP@thinkpad-pgpro
---
src/backend/access/common/reloptions.c | 137 ++++++++++++++++++-----
src/backend/commands/vacuum.c | 4 +-
src/include/access/reloptions.h | 27 ++---
src/include/postgres.h | 15 +++
src/include/utils/rel.h | 3 +-
src/test/regress/expected/reloptions.out | 36 ++++++
src/test/regress/sql/reloptions.sql | 21 ++++
src/tools/pgindent/typedefs.list | 2 +
8 files changed, 202 insertions(+), 43 deletions(-)
diff --git a/src/backend/access/common/reloptions.c b/src/backend/access/common/reloptions.c
index 0b83f98ed5f..6f1f577581a 100644
--- a/src/backend/access/common/reloptions.c
+++ b/src/backend/access/common/reloptions.c
@@ -40,9 +40,9 @@
*
* To add an option:
*
- * (i) decide on a type (bool, integer, real, enum, string), name, default
- * value, upper and lower bounds (if applicable); for strings, consider a
- * validation routine.
+ * (i) decide on a type (bool, ternary, integer, real, enum, string), name,
+ * default value, upper and lower bounds (if applicable); for strings,
+ * consider a validation routine.
* (ii) add a record below (or use add_<type>_reloption).
* (iii) add it to the appropriate options struct (perhaps StdRdOptions)
* (iv) add it to the appropriate handling routine (perhaps
@@ -147,15 +147,6 @@ static relopt_bool boolRelOpts[] =
},
false
},
- {
- {
- "vacuum_truncate",
- "Enables vacuum to truncate empty pages at the end of this table",
- RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
- ShareUpdateExclusiveLock
- },
- true
- },
{
{
"deduplicate_items",
@@ -170,6 +161,25 @@ static relopt_bool boolRelOpts[] =
{{NULL}}
};
+static relopt_ternary ternaryRelOpts[] =
+{
+ {
+ {
+ "vacuum_truncate",
+ "Enables vacuum to truncate empty pages at the end of this table",
+ RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
+ ShareUpdateExclusiveLock
+ },
+ TERNARY_UNSET
+ },
+ /* list terminator */
+ {
+ {
+ NULL
+ }
+ }
+};
+
static relopt_int intRelOpts[] =
{
{
@@ -609,6 +619,13 @@ initialize_reloptions(void)
boolRelOpts[i].gen.lockmode));
j++;
}
+ for (i = 0; ternaryRelOpts[i].gen.name; i++)
+ {
+ Assert(DoLockModesConflict(ternaryRelOpts[i].gen.lockmode,
+ ternaryRelOpts[i].gen.lockmode));
+ j++;
+ }
+
for (i = 0; intRelOpts[i].gen.name; i++)
{
Assert(DoLockModesConflict(intRelOpts[i].gen.lockmode,
@@ -649,6 +666,14 @@ initialize_reloptions(void)
j++;
}
+ for (i = 0; ternaryRelOpts[i].gen.name; i++)
+ {
+ relOpts[j] = &ternaryRelOpts[i].gen;
+ relOpts[j]->type = RELOPT_TYPE_TERNARY;
+ relOpts[j]->namelen = strlen(relOpts[j]->name);
+ j++;
+ }
+
for (i = 0; intRelOpts[i].gen.name; i++)
{
relOpts[j] = &intRelOpts[i].gen;
@@ -809,6 +834,9 @@ allocate_reloption(bits32 kinds, int type, const char *name, const char *desc,
case RELOPT_TYPE_BOOL:
size = sizeof(relopt_bool);
break;
+ case RELOPT_TYPE_TERNARY:
+ size = sizeof(relopt_ternary);
+ break;
case RELOPT_TYPE_INT:
size = sizeof(relopt_int);
break;
@@ -892,6 +920,57 @@ add_local_bool_reloption(local_relopts *relopts, const char *name,
add_local_reloption(relopts, (relopt_gen *) newoption, offset);
}
+/*
+ * init_ternary_reloption
+ * Allocate and initialize a new ternary reloption
+ */
+static relopt_ternary *
+init_ternary_reloption(bits32 kinds, const char *name, const char *desc,
+ pg_ternary default_val, LOCKMODE lockmode)
+{
+ relopt_ternary *newoption;
+
+ newoption = (relopt_ternary *)
+ allocate_reloption(kinds, RELOPT_TYPE_TERNARY, name, desc, lockmode);
+ newoption->default_val = default_val;
+
+ return newoption;
+}
+
+/*
+ * add_ternary_reloption
+ * Add a new ternary reloption
+ */
+void
+add_ternary_reloption(bits32 kinds, const char *name, const char *desc,
+ pg_ternary default_val, LOCKMODE lockmode)
+{
+ relopt_ternary *newoption;
+
+ newoption =
+ init_ternary_reloption(kinds, name, desc, default_val, lockmode);
+
+ add_reloption((relopt_gen *) newoption);
+}
+
+/*
+ * add_local_ternary_reloption
+ * Add a new ternary local reloption
+ *
+ * 'offset' is offset of ternary-typed field.
+ */
+void
+add_local_ternary_reloption(local_relopts *relopts, const char *name,
+ const char *desc, pg_ternary default_val,
+ int offset)
+{
+ relopt_ternary *newoption;
+
+ newoption =
+ init_ternary_reloption(RELOPT_KIND_LOCAL, name, desc, default_val, 0);
+
+ add_local_reloption(relopts, (relopt_gen *) newoption, offset);
+}
/*
* init_real_reloption
@@ -1626,6 +1705,19 @@ parse_one_reloption(relopt_value *option, char *text_str, int text_len,
option->gen->name, value)));
}
break;
+ case RELOPT_TYPE_TERNARY:
+ {
+ bool b;
+
+ parsed = parse_bool(value, &b);
+ option->values.ternary_val = b ? TERNARY_TRUE : TERNARY_FALSE;
+ if (validate && !parsed)
+ ereport(ERROR,
+ (errcode(ERRCODE_INVALID_PARAMETER_VALUE),
+ errmsg("invalid value for ternary option \"%s\": %s",
+ option->gen->name, value)));
+ }
+ break;
case RELOPT_TYPE_INT:
{
relopt_int *optint = (relopt_int *) option->gen;
@@ -1789,17 +1881,6 @@ fillRelOptions(void *rdopts, Size basesize,
char *itempos = ((char *) rdopts) + elems[j].offset;
char *string_val;
- /*
- * If isset_offset is provided, store whether the reloption is
- * set there.
- */
- if (elems[j].isset_offset > 0)
- {
- char *setpos = ((char *) rdopts) + elems[j].isset_offset;
-
- *(bool *) setpos = options[i].isset;
- }
-
switch (options[i].gen->type)
{
case RELOPT_TYPE_BOOL:
@@ -1807,6 +1888,11 @@ fillRelOptions(void *rdopts, Size basesize,
options[i].values.bool_val :
((relopt_bool *) options[i].gen)->default_val;
break;
+ case RELOPT_TYPE_TERNARY:
+ *(pg_ternary *) itempos = options[i].isset ?
+ options[i].values.ternary_val :
+ ((relopt_ternary *) options[i].gen)->default_val;
+ break;
case RELOPT_TYPE_INT:
*(int *) itempos = options[i].isset ?
options[i].values.int_val :
@@ -1923,8 +2009,8 @@ default_reloptions(Datum reloptions, bool validate, relopt_kind kind)
offsetof(StdRdOptions, parallel_workers)},
{"vacuum_index_cleanup", RELOPT_TYPE_ENUM,
offsetof(StdRdOptions, vacuum_index_cleanup)},
- {"vacuum_truncate", RELOPT_TYPE_BOOL,
- offsetof(StdRdOptions, vacuum_truncate), offsetof(StdRdOptions, vacuum_truncate_set)},
+ {"vacuum_truncate", RELOPT_TYPE_TERNARY,
+ offsetof(StdRdOptions, vacuum_truncate)},
{"vacuum_max_eager_freeze_failure_rate", RELOPT_TYPE_REAL,
offsetof(StdRdOptions, vacuum_max_eager_freeze_failure_rate)}
};
@@ -2004,7 +2090,6 @@ build_local_reloptions(local_relopts *relopts, Datum options, bool validate)
elems[i].optname = opt->option->name;
elems[i].opttype = opt->option->type;
elems[i].offset = opt->offset;
- elems[i].isset_offset = 0; /* not supported for local relopts yet */
i++;
}
diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c
index aa4fbec143f..696eab9bd97 100644
--- a/src/backend/commands/vacuum.c
+++ b/src/backend/commands/vacuum.c
@@ -2224,9 +2224,9 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams params,
{
StdRdOptions *opts = (StdRdOptions *) rel->rd_options;
- if (opts && opts->vacuum_truncate_set)
+ if (opts && opts->vacuum_truncate != TERNARY_UNSET)
{
- if (opts->vacuum_truncate)
+ if (opts->vacuum_truncate == TERNARY_TRUE)
params.truncate = VACOPTVALUE_ENABLED;
else
params.truncate = VACOPTVALUE_DISABLED;
diff --git a/src/include/access/reloptions.h b/src/include/access/reloptions.h
index 2f08e1b0cf0..dfbef2babf2 100644
--- a/src/include/access/reloptions.h
+++ b/src/include/access/reloptions.h
@@ -29,6 +29,7 @@
typedef enum relopt_type
{
RELOPT_TYPE_BOOL,
+ RELOPT_TYPE_TERNARY, /* on, off, unset */
RELOPT_TYPE_INT,
RELOPT_TYPE_REAL,
RELOPT_TYPE_ENUM,
@@ -80,6 +81,7 @@ typedef struct relopt_value
union
{
bool bool_val;
+ pg_ternary ternary_val;
int int_val;
double real_val;
int enum_val;
@@ -94,6 +96,12 @@ typedef struct relopt_bool
bool default_val;
} relopt_bool;
+typedef struct relopt_ternary
+{
+ relopt_gen gen;
+ int default_val;
+} relopt_ternary;
+
typedef struct relopt_int
{
relopt_gen gen;
@@ -152,19 +160,6 @@ typedef struct
const char *optname; /* option's name */
relopt_type opttype; /* option's datatype */
int offset; /* offset of field in result struct */
-
- /*
- * isset_offset is an optional offset of a field in the result struct that
- * stores whether the option is explicitly set for the relation or if it
- * just picked up the default value. In most cases, this can be
- * accomplished by giving the reloption a special out-of-range default
- * value (e.g., some integer reloptions use -2), but this isn't always
- * possible. For example, a Boolean reloption cannot be given an
- * out-of-range default, so we need another way to discover the source of
- * its value. This offset is only used if given a value greater than
- * zero.
- */
- int isset_offset;
} relopt_parse_elt;
/* Local reloption definition */
@@ -195,6 +190,9 @@ typedef struct local_relopts
extern relopt_kind add_reloption_kind(void);
extern void add_bool_reloption(bits32 kinds, const char *name, const char *desc,
bool default_val, LOCKMODE lockmode);
+extern void add_ternary_reloption(bits32 kinds, const char *name,
+ const char *desc, pg_ternary default_val,
+ LOCKMODE lockmode);
extern void add_int_reloption(bits32 kinds, const char *name, const char *desc,
int default_val, int min_val, int max_val,
LOCKMODE lockmode);
@@ -214,6 +212,9 @@ extern void register_reloptions_validator(local_relopts *relopts,
extern void add_local_bool_reloption(local_relopts *relopts, const char *name,
const char *desc, bool default_val,
int offset);
+extern void add_local_ternary_reloption(local_relopts *relopts,
+ const char *name, const char *desc,
+ pg_ternary default_val, int offset);
extern void add_local_int_reloption(local_relopts *relopts, const char *name,
const char *desc, int default_val,
int min_val, int max_val, int offset);
diff --git a/src/include/postgres.h b/src/include/postgres.h
index 7d93fbce709..47c88780776 100644
--- a/src/include/postgres.h
+++ b/src/include/postgres.h
@@ -543,6 +543,21 @@ Float8GetDatum(float8 X)
* ----------------------------------------------------------------
*/
+/*
+ * pg_ternary
+ * Boolean value with an extra "unset" value
+ *
+ * This enum can be used for values that want to distinguish between true,
+ * false, and unset.
+*/
+
+typedef enum pg_ternary
+{
+ TERNARY_FALSE = 0,
+ TERNARY_TRUE = 1,
+ TERNARY_UNSET = -1
+} pg_ternary;
+
/*
* NON_EXEC_STATIC: It's sometimes useful to define a variable or function
* that is normally static but extern when using EXEC_BACKEND (see
diff --git a/src/include/utils/rel.h b/src/include/utils/rel.h
index d03ab247788..5feb18a5373 100644
--- a/src/include/utils/rel.h
+++ b/src/include/utils/rel.h
@@ -347,8 +347,7 @@ typedef struct StdRdOptions
bool user_catalog_table; /* use as an additional catalog relation */
int parallel_workers; /* max number of parallel workers */
StdRdOptIndexCleanup vacuum_index_cleanup; /* controls index vacuuming */
- bool vacuum_truncate; /* enables vacuum to truncate a relation */
- bool vacuum_truncate_set; /* whether vacuum_truncate is set */
+ pg_ternary vacuum_truncate; /* enables vacuum to truncate a relation */
/*
* Fraction of pages in a relation that vacuum can eagerly scan and fail
diff --git a/src/test/regress/expected/reloptions.out b/src/test/regress/expected/reloptions.out
index 9de19b4e3f1..1c99f79ab01 100644
--- a/src/test/regress/expected/reloptions.out
+++ b/src/test/regress/expected/reloptions.out
@@ -98,6 +98,42 @@ SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass;
{fillfactor=13,autovacuum_enabled=false}
(1 row)
+-- Tests for future (FIXME) ternary options
+-- behave as boolean option: accept unassigned name and truncated value
+DROP TABLE reloptions_test;
+CREATE TABLE reloptions_test(i INT) WITH (vacuum_truncate);
+SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass;
+ reloptions
+------------------------
+ {vacuum_truncate=true}
+(1 row)
+
+DROP TABLE reloptions_test;
+CREATE TABLE reloptions_test(i INT) WITH (vacuum_truncate=FaLS);
+SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass;
+ reloptions
+------------------------
+ {vacuum_truncate=fals}
+(1 row)
+
+-- preferred "true" alias is used when storing
+DROP TABLE reloptions_test;
+CREATE TABLE reloptions_test(i INT) WITH (vacuum_index_cleanup=on);
+SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass;
+ reloptions
+---------------------------
+ {vacuum_index_cleanup=on}
+(1 row)
+
+-- custom "third" value is available
+DROP TABLE reloptions_test;
+CREATE TABLE reloptions_test(i INT) WITH (vacuum_index_cleanup=auto);
+SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass;
+ reloptions
+-----------------------------
+ {vacuum_index_cleanup=auto}
+(1 row)
+
-- Test vacuum_truncate option
DROP TABLE reloptions_test;
CREATE TEMP TABLE reloptions_test(i INT NOT NULL, j text)
diff --git a/src/test/regress/sql/reloptions.sql b/src/test/regress/sql/reloptions.sql
index 24fbe0b478d..f5980dafcbc 100644
--- a/src/test/regress/sql/reloptions.sql
+++ b/src/test/regress/sql/reloptions.sql
@@ -59,6 +59,27 @@ UPDATE pg_class
ALTER TABLE reloptions_test RESET (illegal_option);
SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass;
+-- Tests for future (FIXME) ternary options
+
+-- behave as boolean option: accept unassigned name and truncated value
+DROP TABLE reloptions_test;
+CREATE TABLE reloptions_test(i INT) WITH (vacuum_truncate);
+SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass;
+
+DROP TABLE reloptions_test;
+CREATE TABLE reloptions_test(i INT) WITH (vacuum_truncate=FaLS);
+SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass;
+
+-- preferred "true" alias is used when storing
+DROP TABLE reloptions_test;
+CREATE TABLE reloptions_test(i INT) WITH (vacuum_index_cleanup=on);
+SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass;
+
+-- custom "third" value is available
+DROP TABLE reloptions_test;
+CREATE TABLE reloptions_test(i INT) WITH (vacuum_index_cleanup=auto);
+SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass;
+
-- Test vacuum_truncate option
DROP TABLE reloptions_test;
diff --git a/src/tools/pgindent/typedefs.list b/src/tools/pgindent/typedefs.list
index 3f3a888fd0e..1c8610fd46c 100644
--- a/src/tools/pgindent/typedefs.list
+++ b/src/tools/pgindent/typedefs.list
@@ -3952,6 +3952,7 @@ pg_sha512_ctx
pg_snapshot
pg_special_case
pg_stack_base_t
+pg_ternary
pg_time_t
pg_time_usec_t
pg_tz
@@ -4079,6 +4080,7 @@ relopt_kind
relopt_parse_elt
relopt_real
relopt_string
+relopt_ternary
relopt_type
relopt_value
relopts_validator
--
2.47.3
--c36t3fndxhkzbl5z--
^ permalink raw reply [nested|flat] 868+ messages in thread
* [PATCH v3] Introduce ternary reloptions
@ 2026-01-16 15:04 Álvaro Herrera <[email protected]>
0 siblings, 0 replies; 868+ messages in thread
From: Álvaro Herrera @ 2026-01-16 15:04 UTC (permalink / raw)
Introduce ternary reloption as a replacement for current
`vacuum_truncate` implementation. Remove the `vacuum_truncate_set`
separate flag and use TERNARY_UNSET instead.
This could also be used for other options such as `vacuum_index_cleanup`
and `buffering`, but lets get the scaffolding in first.
Discussion: https://postgr.es/m/3474141.usfYGdeWWP@thinkpad-pgpro
---
src/backend/access/common/reloptions.c | 137 ++++++++++++++++++-----
src/backend/commands/vacuum.c | 4 +-
src/include/access/reloptions.h | 27 ++---
src/include/postgres.h | 15 +++
src/include/utils/rel.h | 3 +-
src/test/regress/expected/reloptions.out | 36 ++++++
src/test/regress/sql/reloptions.sql | 21 ++++
src/tools/pgindent/typedefs.list | 2 +
8 files changed, 202 insertions(+), 43 deletions(-)
diff --git a/src/backend/access/common/reloptions.c b/src/backend/access/common/reloptions.c
index 0b83f98ed5f..6f1f577581a 100644
--- a/src/backend/access/common/reloptions.c
+++ b/src/backend/access/common/reloptions.c
@@ -40,9 +40,9 @@
*
* To add an option:
*
- * (i) decide on a type (bool, integer, real, enum, string), name, default
- * value, upper and lower bounds (if applicable); for strings, consider a
- * validation routine.
+ * (i) decide on a type (bool, ternary, integer, real, enum, string), name,
+ * default value, upper and lower bounds (if applicable); for strings,
+ * consider a validation routine.
* (ii) add a record below (or use add_<type>_reloption).
* (iii) add it to the appropriate options struct (perhaps StdRdOptions)
* (iv) add it to the appropriate handling routine (perhaps
@@ -147,15 +147,6 @@ static relopt_bool boolRelOpts[] =
},
false
},
- {
- {
- "vacuum_truncate",
- "Enables vacuum to truncate empty pages at the end of this table",
- RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
- ShareUpdateExclusiveLock
- },
- true
- },
{
{
"deduplicate_items",
@@ -170,6 +161,25 @@ static relopt_bool boolRelOpts[] =
{{NULL}}
};
+static relopt_ternary ternaryRelOpts[] =
+{
+ {
+ {
+ "vacuum_truncate",
+ "Enables vacuum to truncate empty pages at the end of this table",
+ RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
+ ShareUpdateExclusiveLock
+ },
+ TERNARY_UNSET
+ },
+ /* list terminator */
+ {
+ {
+ NULL
+ }
+ }
+};
+
static relopt_int intRelOpts[] =
{
{
@@ -609,6 +619,13 @@ initialize_reloptions(void)
boolRelOpts[i].gen.lockmode));
j++;
}
+ for (i = 0; ternaryRelOpts[i].gen.name; i++)
+ {
+ Assert(DoLockModesConflict(ternaryRelOpts[i].gen.lockmode,
+ ternaryRelOpts[i].gen.lockmode));
+ j++;
+ }
+
for (i = 0; intRelOpts[i].gen.name; i++)
{
Assert(DoLockModesConflict(intRelOpts[i].gen.lockmode,
@@ -649,6 +666,14 @@ initialize_reloptions(void)
j++;
}
+ for (i = 0; ternaryRelOpts[i].gen.name; i++)
+ {
+ relOpts[j] = &ternaryRelOpts[i].gen;
+ relOpts[j]->type = RELOPT_TYPE_TERNARY;
+ relOpts[j]->namelen = strlen(relOpts[j]->name);
+ j++;
+ }
+
for (i = 0; intRelOpts[i].gen.name; i++)
{
relOpts[j] = &intRelOpts[i].gen;
@@ -809,6 +834,9 @@ allocate_reloption(bits32 kinds, int type, const char *name, const char *desc,
case RELOPT_TYPE_BOOL:
size = sizeof(relopt_bool);
break;
+ case RELOPT_TYPE_TERNARY:
+ size = sizeof(relopt_ternary);
+ break;
case RELOPT_TYPE_INT:
size = sizeof(relopt_int);
break;
@@ -892,6 +920,57 @@ add_local_bool_reloption(local_relopts *relopts, const char *name,
add_local_reloption(relopts, (relopt_gen *) newoption, offset);
}
+/*
+ * init_ternary_reloption
+ * Allocate and initialize a new ternary reloption
+ */
+static relopt_ternary *
+init_ternary_reloption(bits32 kinds, const char *name, const char *desc,
+ pg_ternary default_val, LOCKMODE lockmode)
+{
+ relopt_ternary *newoption;
+
+ newoption = (relopt_ternary *)
+ allocate_reloption(kinds, RELOPT_TYPE_TERNARY, name, desc, lockmode);
+ newoption->default_val = default_val;
+
+ return newoption;
+}
+
+/*
+ * add_ternary_reloption
+ * Add a new ternary reloption
+ */
+void
+add_ternary_reloption(bits32 kinds, const char *name, const char *desc,
+ pg_ternary default_val, LOCKMODE lockmode)
+{
+ relopt_ternary *newoption;
+
+ newoption =
+ init_ternary_reloption(kinds, name, desc, default_val, lockmode);
+
+ add_reloption((relopt_gen *) newoption);
+}
+
+/*
+ * add_local_ternary_reloption
+ * Add a new ternary local reloption
+ *
+ * 'offset' is offset of ternary-typed field.
+ */
+void
+add_local_ternary_reloption(local_relopts *relopts, const char *name,
+ const char *desc, pg_ternary default_val,
+ int offset)
+{
+ relopt_ternary *newoption;
+
+ newoption =
+ init_ternary_reloption(RELOPT_KIND_LOCAL, name, desc, default_val, 0);
+
+ add_local_reloption(relopts, (relopt_gen *) newoption, offset);
+}
/*
* init_real_reloption
@@ -1626,6 +1705,19 @@ parse_one_reloption(relopt_value *option, char *text_str, int text_len,
option->gen->name, value)));
}
break;
+ case RELOPT_TYPE_TERNARY:
+ {
+ bool b;
+
+ parsed = parse_bool(value, &b);
+ option->values.ternary_val = b ? TERNARY_TRUE : TERNARY_FALSE;
+ if (validate && !parsed)
+ ereport(ERROR,
+ (errcode(ERRCODE_INVALID_PARAMETER_VALUE),
+ errmsg("invalid value for ternary option \"%s\": %s",
+ option->gen->name, value)));
+ }
+ break;
case RELOPT_TYPE_INT:
{
relopt_int *optint = (relopt_int *) option->gen;
@@ -1789,17 +1881,6 @@ fillRelOptions(void *rdopts, Size basesize,
char *itempos = ((char *) rdopts) + elems[j].offset;
char *string_val;
- /*
- * If isset_offset is provided, store whether the reloption is
- * set there.
- */
- if (elems[j].isset_offset > 0)
- {
- char *setpos = ((char *) rdopts) + elems[j].isset_offset;
-
- *(bool *) setpos = options[i].isset;
- }
-
switch (options[i].gen->type)
{
case RELOPT_TYPE_BOOL:
@@ -1807,6 +1888,11 @@ fillRelOptions(void *rdopts, Size basesize,
options[i].values.bool_val :
((relopt_bool *) options[i].gen)->default_val;
break;
+ case RELOPT_TYPE_TERNARY:
+ *(pg_ternary *) itempos = options[i].isset ?
+ options[i].values.ternary_val :
+ ((relopt_ternary *) options[i].gen)->default_val;
+ break;
case RELOPT_TYPE_INT:
*(int *) itempos = options[i].isset ?
options[i].values.int_val :
@@ -1923,8 +2009,8 @@ default_reloptions(Datum reloptions, bool validate, relopt_kind kind)
offsetof(StdRdOptions, parallel_workers)},
{"vacuum_index_cleanup", RELOPT_TYPE_ENUM,
offsetof(StdRdOptions, vacuum_index_cleanup)},
- {"vacuum_truncate", RELOPT_TYPE_BOOL,
- offsetof(StdRdOptions, vacuum_truncate), offsetof(StdRdOptions, vacuum_truncate_set)},
+ {"vacuum_truncate", RELOPT_TYPE_TERNARY,
+ offsetof(StdRdOptions, vacuum_truncate)},
{"vacuum_max_eager_freeze_failure_rate", RELOPT_TYPE_REAL,
offsetof(StdRdOptions, vacuum_max_eager_freeze_failure_rate)}
};
@@ -2004,7 +2090,6 @@ build_local_reloptions(local_relopts *relopts, Datum options, bool validate)
elems[i].optname = opt->option->name;
elems[i].opttype = opt->option->type;
elems[i].offset = opt->offset;
- elems[i].isset_offset = 0; /* not supported for local relopts yet */
i++;
}
diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c
index aa4fbec143f..696eab9bd97 100644
--- a/src/backend/commands/vacuum.c
+++ b/src/backend/commands/vacuum.c
@@ -2224,9 +2224,9 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams params,
{
StdRdOptions *opts = (StdRdOptions *) rel->rd_options;
- if (opts && opts->vacuum_truncate_set)
+ if (opts && opts->vacuum_truncate != TERNARY_UNSET)
{
- if (opts->vacuum_truncate)
+ if (opts->vacuum_truncate == TERNARY_TRUE)
params.truncate = VACOPTVALUE_ENABLED;
else
params.truncate = VACOPTVALUE_DISABLED;
diff --git a/src/include/access/reloptions.h b/src/include/access/reloptions.h
index 2f08e1b0cf0..dfbef2babf2 100644
--- a/src/include/access/reloptions.h
+++ b/src/include/access/reloptions.h
@@ -29,6 +29,7 @@
typedef enum relopt_type
{
RELOPT_TYPE_BOOL,
+ RELOPT_TYPE_TERNARY, /* on, off, unset */
RELOPT_TYPE_INT,
RELOPT_TYPE_REAL,
RELOPT_TYPE_ENUM,
@@ -80,6 +81,7 @@ typedef struct relopt_value
union
{
bool bool_val;
+ pg_ternary ternary_val;
int int_val;
double real_val;
int enum_val;
@@ -94,6 +96,12 @@ typedef struct relopt_bool
bool default_val;
} relopt_bool;
+typedef struct relopt_ternary
+{
+ relopt_gen gen;
+ int default_val;
+} relopt_ternary;
+
typedef struct relopt_int
{
relopt_gen gen;
@@ -152,19 +160,6 @@ typedef struct
const char *optname; /* option's name */
relopt_type opttype; /* option's datatype */
int offset; /* offset of field in result struct */
-
- /*
- * isset_offset is an optional offset of a field in the result struct that
- * stores whether the option is explicitly set for the relation or if it
- * just picked up the default value. In most cases, this can be
- * accomplished by giving the reloption a special out-of-range default
- * value (e.g., some integer reloptions use -2), but this isn't always
- * possible. For example, a Boolean reloption cannot be given an
- * out-of-range default, so we need another way to discover the source of
- * its value. This offset is only used if given a value greater than
- * zero.
- */
- int isset_offset;
} relopt_parse_elt;
/* Local reloption definition */
@@ -195,6 +190,9 @@ typedef struct local_relopts
extern relopt_kind add_reloption_kind(void);
extern void add_bool_reloption(bits32 kinds, const char *name, const char *desc,
bool default_val, LOCKMODE lockmode);
+extern void add_ternary_reloption(bits32 kinds, const char *name,
+ const char *desc, pg_ternary default_val,
+ LOCKMODE lockmode);
extern void add_int_reloption(bits32 kinds, const char *name, const char *desc,
int default_val, int min_val, int max_val,
LOCKMODE lockmode);
@@ -214,6 +212,9 @@ extern void register_reloptions_validator(local_relopts *relopts,
extern void add_local_bool_reloption(local_relopts *relopts, const char *name,
const char *desc, bool default_val,
int offset);
+extern void add_local_ternary_reloption(local_relopts *relopts,
+ const char *name, const char *desc,
+ pg_ternary default_val, int offset);
extern void add_local_int_reloption(local_relopts *relopts, const char *name,
const char *desc, int default_val,
int min_val, int max_val, int offset);
diff --git a/src/include/postgres.h b/src/include/postgres.h
index 7d93fbce709..47c88780776 100644
--- a/src/include/postgres.h
+++ b/src/include/postgres.h
@@ -543,6 +543,21 @@ Float8GetDatum(float8 X)
* ----------------------------------------------------------------
*/
+/*
+ * pg_ternary
+ * Boolean value with an extra "unset" value
+ *
+ * This enum can be used for values that want to distinguish between true,
+ * false, and unset.
+*/
+
+typedef enum pg_ternary
+{
+ TERNARY_FALSE = 0,
+ TERNARY_TRUE = 1,
+ TERNARY_UNSET = -1
+} pg_ternary;
+
/*
* NON_EXEC_STATIC: It's sometimes useful to define a variable or function
* that is normally static but extern when using EXEC_BACKEND (see
diff --git a/src/include/utils/rel.h b/src/include/utils/rel.h
index d03ab247788..5feb18a5373 100644
--- a/src/include/utils/rel.h
+++ b/src/include/utils/rel.h
@@ -347,8 +347,7 @@ typedef struct StdRdOptions
bool user_catalog_table; /* use as an additional catalog relation */
int parallel_workers; /* max number of parallel workers */
StdRdOptIndexCleanup vacuum_index_cleanup; /* controls index vacuuming */
- bool vacuum_truncate; /* enables vacuum to truncate a relation */
- bool vacuum_truncate_set; /* whether vacuum_truncate is set */
+ pg_ternary vacuum_truncate; /* enables vacuum to truncate a relation */
/*
* Fraction of pages in a relation that vacuum can eagerly scan and fail
diff --git a/src/test/regress/expected/reloptions.out b/src/test/regress/expected/reloptions.out
index 9de19b4e3f1..1c99f79ab01 100644
--- a/src/test/regress/expected/reloptions.out
+++ b/src/test/regress/expected/reloptions.out
@@ -98,6 +98,42 @@ SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass;
{fillfactor=13,autovacuum_enabled=false}
(1 row)
+-- Tests for future (FIXME) ternary options
+-- behave as boolean option: accept unassigned name and truncated value
+DROP TABLE reloptions_test;
+CREATE TABLE reloptions_test(i INT) WITH (vacuum_truncate);
+SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass;
+ reloptions
+------------------------
+ {vacuum_truncate=true}
+(1 row)
+
+DROP TABLE reloptions_test;
+CREATE TABLE reloptions_test(i INT) WITH (vacuum_truncate=FaLS);
+SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass;
+ reloptions
+------------------------
+ {vacuum_truncate=fals}
+(1 row)
+
+-- preferred "true" alias is used when storing
+DROP TABLE reloptions_test;
+CREATE TABLE reloptions_test(i INT) WITH (vacuum_index_cleanup=on);
+SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass;
+ reloptions
+---------------------------
+ {vacuum_index_cleanup=on}
+(1 row)
+
+-- custom "third" value is available
+DROP TABLE reloptions_test;
+CREATE TABLE reloptions_test(i INT) WITH (vacuum_index_cleanup=auto);
+SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass;
+ reloptions
+-----------------------------
+ {vacuum_index_cleanup=auto}
+(1 row)
+
-- Test vacuum_truncate option
DROP TABLE reloptions_test;
CREATE TEMP TABLE reloptions_test(i INT NOT NULL, j text)
diff --git a/src/test/regress/sql/reloptions.sql b/src/test/regress/sql/reloptions.sql
index 24fbe0b478d..f5980dafcbc 100644
--- a/src/test/regress/sql/reloptions.sql
+++ b/src/test/regress/sql/reloptions.sql
@@ -59,6 +59,27 @@ UPDATE pg_class
ALTER TABLE reloptions_test RESET (illegal_option);
SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass;
+-- Tests for future (FIXME) ternary options
+
+-- behave as boolean option: accept unassigned name and truncated value
+DROP TABLE reloptions_test;
+CREATE TABLE reloptions_test(i INT) WITH (vacuum_truncate);
+SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass;
+
+DROP TABLE reloptions_test;
+CREATE TABLE reloptions_test(i INT) WITH (vacuum_truncate=FaLS);
+SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass;
+
+-- preferred "true" alias is used when storing
+DROP TABLE reloptions_test;
+CREATE TABLE reloptions_test(i INT) WITH (vacuum_index_cleanup=on);
+SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass;
+
+-- custom "third" value is available
+DROP TABLE reloptions_test;
+CREATE TABLE reloptions_test(i INT) WITH (vacuum_index_cleanup=auto);
+SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass;
+
-- Test vacuum_truncate option
DROP TABLE reloptions_test;
diff --git a/src/tools/pgindent/typedefs.list b/src/tools/pgindent/typedefs.list
index 3f3a888fd0e..1c8610fd46c 100644
--- a/src/tools/pgindent/typedefs.list
+++ b/src/tools/pgindent/typedefs.list
@@ -3952,6 +3952,7 @@ pg_sha512_ctx
pg_snapshot
pg_special_case
pg_stack_base_t
+pg_ternary
pg_time_t
pg_time_usec_t
pg_tz
@@ -4079,6 +4080,7 @@ relopt_kind
relopt_parse_elt
relopt_real
relopt_string
+relopt_ternary
relopt_type
relopt_value
relopts_validator
--
2.47.3
--c36t3fndxhkzbl5z--
^ permalink raw reply [nested|flat] 868+ messages in thread
* [PATCH v3] Introduce ternary reloptions
@ 2026-01-16 15:04 Álvaro Herrera <[email protected]>
0 siblings, 0 replies; 868+ messages in thread
From: Álvaro Herrera @ 2026-01-16 15:04 UTC (permalink / raw)
Introduce ternary reloption as a replacement for current
`vacuum_truncate` implementation. Remove the `vacuum_truncate_set`
separate flag and use TERNARY_UNSET instead.
This could also be used for other options such as `vacuum_index_cleanup`
and `buffering`, but lets get the scaffolding in first.
Discussion: https://postgr.es/m/3474141.usfYGdeWWP@thinkpad-pgpro
---
src/backend/access/common/reloptions.c | 137 ++++++++++++++++++-----
src/backend/commands/vacuum.c | 4 +-
src/include/access/reloptions.h | 27 ++---
src/include/postgres.h | 15 +++
src/include/utils/rel.h | 3 +-
src/test/regress/expected/reloptions.out | 36 ++++++
src/test/regress/sql/reloptions.sql | 21 ++++
src/tools/pgindent/typedefs.list | 2 +
8 files changed, 202 insertions(+), 43 deletions(-)
diff --git a/src/backend/access/common/reloptions.c b/src/backend/access/common/reloptions.c
index 0b83f98ed5f..6f1f577581a 100644
--- a/src/backend/access/common/reloptions.c
+++ b/src/backend/access/common/reloptions.c
@@ -40,9 +40,9 @@
*
* To add an option:
*
- * (i) decide on a type (bool, integer, real, enum, string), name, default
- * value, upper and lower bounds (if applicable); for strings, consider a
- * validation routine.
+ * (i) decide on a type (bool, ternary, integer, real, enum, string), name,
+ * default value, upper and lower bounds (if applicable); for strings,
+ * consider a validation routine.
* (ii) add a record below (or use add_<type>_reloption).
* (iii) add it to the appropriate options struct (perhaps StdRdOptions)
* (iv) add it to the appropriate handling routine (perhaps
@@ -147,15 +147,6 @@ static relopt_bool boolRelOpts[] =
},
false
},
- {
- {
- "vacuum_truncate",
- "Enables vacuum to truncate empty pages at the end of this table",
- RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
- ShareUpdateExclusiveLock
- },
- true
- },
{
{
"deduplicate_items",
@@ -170,6 +161,25 @@ static relopt_bool boolRelOpts[] =
{{NULL}}
};
+static relopt_ternary ternaryRelOpts[] =
+{
+ {
+ {
+ "vacuum_truncate",
+ "Enables vacuum to truncate empty pages at the end of this table",
+ RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
+ ShareUpdateExclusiveLock
+ },
+ TERNARY_UNSET
+ },
+ /* list terminator */
+ {
+ {
+ NULL
+ }
+ }
+};
+
static relopt_int intRelOpts[] =
{
{
@@ -609,6 +619,13 @@ initialize_reloptions(void)
boolRelOpts[i].gen.lockmode));
j++;
}
+ for (i = 0; ternaryRelOpts[i].gen.name; i++)
+ {
+ Assert(DoLockModesConflict(ternaryRelOpts[i].gen.lockmode,
+ ternaryRelOpts[i].gen.lockmode));
+ j++;
+ }
+
for (i = 0; intRelOpts[i].gen.name; i++)
{
Assert(DoLockModesConflict(intRelOpts[i].gen.lockmode,
@@ -649,6 +666,14 @@ initialize_reloptions(void)
j++;
}
+ for (i = 0; ternaryRelOpts[i].gen.name; i++)
+ {
+ relOpts[j] = &ternaryRelOpts[i].gen;
+ relOpts[j]->type = RELOPT_TYPE_TERNARY;
+ relOpts[j]->namelen = strlen(relOpts[j]->name);
+ j++;
+ }
+
for (i = 0; intRelOpts[i].gen.name; i++)
{
relOpts[j] = &intRelOpts[i].gen;
@@ -809,6 +834,9 @@ allocate_reloption(bits32 kinds, int type, const char *name, const char *desc,
case RELOPT_TYPE_BOOL:
size = sizeof(relopt_bool);
break;
+ case RELOPT_TYPE_TERNARY:
+ size = sizeof(relopt_ternary);
+ break;
case RELOPT_TYPE_INT:
size = sizeof(relopt_int);
break;
@@ -892,6 +920,57 @@ add_local_bool_reloption(local_relopts *relopts, const char *name,
add_local_reloption(relopts, (relopt_gen *) newoption, offset);
}
+/*
+ * init_ternary_reloption
+ * Allocate and initialize a new ternary reloption
+ */
+static relopt_ternary *
+init_ternary_reloption(bits32 kinds, const char *name, const char *desc,
+ pg_ternary default_val, LOCKMODE lockmode)
+{
+ relopt_ternary *newoption;
+
+ newoption = (relopt_ternary *)
+ allocate_reloption(kinds, RELOPT_TYPE_TERNARY, name, desc, lockmode);
+ newoption->default_val = default_val;
+
+ return newoption;
+}
+
+/*
+ * add_ternary_reloption
+ * Add a new ternary reloption
+ */
+void
+add_ternary_reloption(bits32 kinds, const char *name, const char *desc,
+ pg_ternary default_val, LOCKMODE lockmode)
+{
+ relopt_ternary *newoption;
+
+ newoption =
+ init_ternary_reloption(kinds, name, desc, default_val, lockmode);
+
+ add_reloption((relopt_gen *) newoption);
+}
+
+/*
+ * add_local_ternary_reloption
+ * Add a new ternary local reloption
+ *
+ * 'offset' is offset of ternary-typed field.
+ */
+void
+add_local_ternary_reloption(local_relopts *relopts, const char *name,
+ const char *desc, pg_ternary default_val,
+ int offset)
+{
+ relopt_ternary *newoption;
+
+ newoption =
+ init_ternary_reloption(RELOPT_KIND_LOCAL, name, desc, default_val, 0);
+
+ add_local_reloption(relopts, (relopt_gen *) newoption, offset);
+}
/*
* init_real_reloption
@@ -1626,6 +1705,19 @@ parse_one_reloption(relopt_value *option, char *text_str, int text_len,
option->gen->name, value)));
}
break;
+ case RELOPT_TYPE_TERNARY:
+ {
+ bool b;
+
+ parsed = parse_bool(value, &b);
+ option->values.ternary_val = b ? TERNARY_TRUE : TERNARY_FALSE;
+ if (validate && !parsed)
+ ereport(ERROR,
+ (errcode(ERRCODE_INVALID_PARAMETER_VALUE),
+ errmsg("invalid value for ternary option \"%s\": %s",
+ option->gen->name, value)));
+ }
+ break;
case RELOPT_TYPE_INT:
{
relopt_int *optint = (relopt_int *) option->gen;
@@ -1789,17 +1881,6 @@ fillRelOptions(void *rdopts, Size basesize,
char *itempos = ((char *) rdopts) + elems[j].offset;
char *string_val;
- /*
- * If isset_offset is provided, store whether the reloption is
- * set there.
- */
- if (elems[j].isset_offset > 0)
- {
- char *setpos = ((char *) rdopts) + elems[j].isset_offset;
-
- *(bool *) setpos = options[i].isset;
- }
-
switch (options[i].gen->type)
{
case RELOPT_TYPE_BOOL:
@@ -1807,6 +1888,11 @@ fillRelOptions(void *rdopts, Size basesize,
options[i].values.bool_val :
((relopt_bool *) options[i].gen)->default_val;
break;
+ case RELOPT_TYPE_TERNARY:
+ *(pg_ternary *) itempos = options[i].isset ?
+ options[i].values.ternary_val :
+ ((relopt_ternary *) options[i].gen)->default_val;
+ break;
case RELOPT_TYPE_INT:
*(int *) itempos = options[i].isset ?
options[i].values.int_val :
@@ -1923,8 +2009,8 @@ default_reloptions(Datum reloptions, bool validate, relopt_kind kind)
offsetof(StdRdOptions, parallel_workers)},
{"vacuum_index_cleanup", RELOPT_TYPE_ENUM,
offsetof(StdRdOptions, vacuum_index_cleanup)},
- {"vacuum_truncate", RELOPT_TYPE_BOOL,
- offsetof(StdRdOptions, vacuum_truncate), offsetof(StdRdOptions, vacuum_truncate_set)},
+ {"vacuum_truncate", RELOPT_TYPE_TERNARY,
+ offsetof(StdRdOptions, vacuum_truncate)},
{"vacuum_max_eager_freeze_failure_rate", RELOPT_TYPE_REAL,
offsetof(StdRdOptions, vacuum_max_eager_freeze_failure_rate)}
};
@@ -2004,7 +2090,6 @@ build_local_reloptions(local_relopts *relopts, Datum options, bool validate)
elems[i].optname = opt->option->name;
elems[i].opttype = opt->option->type;
elems[i].offset = opt->offset;
- elems[i].isset_offset = 0; /* not supported for local relopts yet */
i++;
}
diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c
index aa4fbec143f..696eab9bd97 100644
--- a/src/backend/commands/vacuum.c
+++ b/src/backend/commands/vacuum.c
@@ -2224,9 +2224,9 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams params,
{
StdRdOptions *opts = (StdRdOptions *) rel->rd_options;
- if (opts && opts->vacuum_truncate_set)
+ if (opts && opts->vacuum_truncate != TERNARY_UNSET)
{
- if (opts->vacuum_truncate)
+ if (opts->vacuum_truncate == TERNARY_TRUE)
params.truncate = VACOPTVALUE_ENABLED;
else
params.truncate = VACOPTVALUE_DISABLED;
diff --git a/src/include/access/reloptions.h b/src/include/access/reloptions.h
index 2f08e1b0cf0..dfbef2babf2 100644
--- a/src/include/access/reloptions.h
+++ b/src/include/access/reloptions.h
@@ -29,6 +29,7 @@
typedef enum relopt_type
{
RELOPT_TYPE_BOOL,
+ RELOPT_TYPE_TERNARY, /* on, off, unset */
RELOPT_TYPE_INT,
RELOPT_TYPE_REAL,
RELOPT_TYPE_ENUM,
@@ -80,6 +81,7 @@ typedef struct relopt_value
union
{
bool bool_val;
+ pg_ternary ternary_val;
int int_val;
double real_val;
int enum_val;
@@ -94,6 +96,12 @@ typedef struct relopt_bool
bool default_val;
} relopt_bool;
+typedef struct relopt_ternary
+{
+ relopt_gen gen;
+ int default_val;
+} relopt_ternary;
+
typedef struct relopt_int
{
relopt_gen gen;
@@ -152,19 +160,6 @@ typedef struct
const char *optname; /* option's name */
relopt_type opttype; /* option's datatype */
int offset; /* offset of field in result struct */
-
- /*
- * isset_offset is an optional offset of a field in the result struct that
- * stores whether the option is explicitly set for the relation or if it
- * just picked up the default value. In most cases, this can be
- * accomplished by giving the reloption a special out-of-range default
- * value (e.g., some integer reloptions use -2), but this isn't always
- * possible. For example, a Boolean reloption cannot be given an
- * out-of-range default, so we need another way to discover the source of
- * its value. This offset is only used if given a value greater than
- * zero.
- */
- int isset_offset;
} relopt_parse_elt;
/* Local reloption definition */
@@ -195,6 +190,9 @@ typedef struct local_relopts
extern relopt_kind add_reloption_kind(void);
extern void add_bool_reloption(bits32 kinds, const char *name, const char *desc,
bool default_val, LOCKMODE lockmode);
+extern void add_ternary_reloption(bits32 kinds, const char *name,
+ const char *desc, pg_ternary default_val,
+ LOCKMODE lockmode);
extern void add_int_reloption(bits32 kinds, const char *name, const char *desc,
int default_val, int min_val, int max_val,
LOCKMODE lockmode);
@@ -214,6 +212,9 @@ extern void register_reloptions_validator(local_relopts *relopts,
extern void add_local_bool_reloption(local_relopts *relopts, const char *name,
const char *desc, bool default_val,
int offset);
+extern void add_local_ternary_reloption(local_relopts *relopts,
+ const char *name, const char *desc,
+ pg_ternary default_val, int offset);
extern void add_local_int_reloption(local_relopts *relopts, const char *name,
const char *desc, int default_val,
int min_val, int max_val, int offset);
diff --git a/src/include/postgres.h b/src/include/postgres.h
index 7d93fbce709..47c88780776 100644
--- a/src/include/postgres.h
+++ b/src/include/postgres.h
@@ -543,6 +543,21 @@ Float8GetDatum(float8 X)
* ----------------------------------------------------------------
*/
+/*
+ * pg_ternary
+ * Boolean value with an extra "unset" value
+ *
+ * This enum can be used for values that want to distinguish between true,
+ * false, and unset.
+*/
+
+typedef enum pg_ternary
+{
+ TERNARY_FALSE = 0,
+ TERNARY_TRUE = 1,
+ TERNARY_UNSET = -1
+} pg_ternary;
+
/*
* NON_EXEC_STATIC: It's sometimes useful to define a variable or function
* that is normally static but extern when using EXEC_BACKEND (see
diff --git a/src/include/utils/rel.h b/src/include/utils/rel.h
index d03ab247788..5feb18a5373 100644
--- a/src/include/utils/rel.h
+++ b/src/include/utils/rel.h
@@ -347,8 +347,7 @@ typedef struct StdRdOptions
bool user_catalog_table; /* use as an additional catalog relation */
int parallel_workers; /* max number of parallel workers */
StdRdOptIndexCleanup vacuum_index_cleanup; /* controls index vacuuming */
- bool vacuum_truncate; /* enables vacuum to truncate a relation */
- bool vacuum_truncate_set; /* whether vacuum_truncate is set */
+ pg_ternary vacuum_truncate; /* enables vacuum to truncate a relation */
/*
* Fraction of pages in a relation that vacuum can eagerly scan and fail
diff --git a/src/test/regress/expected/reloptions.out b/src/test/regress/expected/reloptions.out
index 9de19b4e3f1..1c99f79ab01 100644
--- a/src/test/regress/expected/reloptions.out
+++ b/src/test/regress/expected/reloptions.out
@@ -98,6 +98,42 @@ SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass;
{fillfactor=13,autovacuum_enabled=false}
(1 row)
+-- Tests for future (FIXME) ternary options
+-- behave as boolean option: accept unassigned name and truncated value
+DROP TABLE reloptions_test;
+CREATE TABLE reloptions_test(i INT) WITH (vacuum_truncate);
+SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass;
+ reloptions
+------------------------
+ {vacuum_truncate=true}
+(1 row)
+
+DROP TABLE reloptions_test;
+CREATE TABLE reloptions_test(i INT) WITH (vacuum_truncate=FaLS);
+SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass;
+ reloptions
+------------------------
+ {vacuum_truncate=fals}
+(1 row)
+
+-- preferred "true" alias is used when storing
+DROP TABLE reloptions_test;
+CREATE TABLE reloptions_test(i INT) WITH (vacuum_index_cleanup=on);
+SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass;
+ reloptions
+---------------------------
+ {vacuum_index_cleanup=on}
+(1 row)
+
+-- custom "third" value is available
+DROP TABLE reloptions_test;
+CREATE TABLE reloptions_test(i INT) WITH (vacuum_index_cleanup=auto);
+SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass;
+ reloptions
+-----------------------------
+ {vacuum_index_cleanup=auto}
+(1 row)
+
-- Test vacuum_truncate option
DROP TABLE reloptions_test;
CREATE TEMP TABLE reloptions_test(i INT NOT NULL, j text)
diff --git a/src/test/regress/sql/reloptions.sql b/src/test/regress/sql/reloptions.sql
index 24fbe0b478d..f5980dafcbc 100644
--- a/src/test/regress/sql/reloptions.sql
+++ b/src/test/regress/sql/reloptions.sql
@@ -59,6 +59,27 @@ UPDATE pg_class
ALTER TABLE reloptions_test RESET (illegal_option);
SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass;
+-- Tests for future (FIXME) ternary options
+
+-- behave as boolean option: accept unassigned name and truncated value
+DROP TABLE reloptions_test;
+CREATE TABLE reloptions_test(i INT) WITH (vacuum_truncate);
+SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass;
+
+DROP TABLE reloptions_test;
+CREATE TABLE reloptions_test(i INT) WITH (vacuum_truncate=FaLS);
+SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass;
+
+-- preferred "true" alias is used when storing
+DROP TABLE reloptions_test;
+CREATE TABLE reloptions_test(i INT) WITH (vacuum_index_cleanup=on);
+SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass;
+
+-- custom "third" value is available
+DROP TABLE reloptions_test;
+CREATE TABLE reloptions_test(i INT) WITH (vacuum_index_cleanup=auto);
+SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass;
+
-- Test vacuum_truncate option
DROP TABLE reloptions_test;
diff --git a/src/tools/pgindent/typedefs.list b/src/tools/pgindent/typedefs.list
index 3f3a888fd0e..1c8610fd46c 100644
--- a/src/tools/pgindent/typedefs.list
+++ b/src/tools/pgindent/typedefs.list
@@ -3952,6 +3952,7 @@ pg_sha512_ctx
pg_snapshot
pg_special_case
pg_stack_base_t
+pg_ternary
pg_time_t
pg_time_usec_t
pg_tz
@@ -4079,6 +4080,7 @@ relopt_kind
relopt_parse_elt
relopt_real
relopt_string
+relopt_ternary
relopt_type
relopt_value
relopts_validator
--
2.47.3
--c36t3fndxhkzbl5z--
^ permalink raw reply [nested|flat] 868+ messages in thread
* [PATCH v3] Introduce ternary reloptions
@ 2026-01-16 15:04 Álvaro Herrera <[email protected]>
0 siblings, 0 replies; 868+ messages in thread
From: Álvaro Herrera @ 2026-01-16 15:04 UTC (permalink / raw)
Introduce ternary reloption as a replacement for current
`vacuum_truncate` implementation. Remove the `vacuum_truncate_set`
separate flag and use TERNARY_UNSET instead.
This could also be used for other options such as `vacuum_index_cleanup`
and `buffering`, but lets get the scaffolding in first.
Discussion: https://postgr.es/m/3474141.usfYGdeWWP@thinkpad-pgpro
---
src/backend/access/common/reloptions.c | 137 ++++++++++++++++++-----
src/backend/commands/vacuum.c | 4 +-
src/include/access/reloptions.h | 27 ++---
src/include/postgres.h | 15 +++
src/include/utils/rel.h | 3 +-
src/test/regress/expected/reloptions.out | 36 ++++++
src/test/regress/sql/reloptions.sql | 21 ++++
src/tools/pgindent/typedefs.list | 2 +
8 files changed, 202 insertions(+), 43 deletions(-)
diff --git a/src/backend/access/common/reloptions.c b/src/backend/access/common/reloptions.c
index 0b83f98ed5f..6f1f577581a 100644
--- a/src/backend/access/common/reloptions.c
+++ b/src/backend/access/common/reloptions.c
@@ -40,9 +40,9 @@
*
* To add an option:
*
- * (i) decide on a type (bool, integer, real, enum, string), name, default
- * value, upper and lower bounds (if applicable); for strings, consider a
- * validation routine.
+ * (i) decide on a type (bool, ternary, integer, real, enum, string), name,
+ * default value, upper and lower bounds (if applicable); for strings,
+ * consider a validation routine.
* (ii) add a record below (or use add_<type>_reloption).
* (iii) add it to the appropriate options struct (perhaps StdRdOptions)
* (iv) add it to the appropriate handling routine (perhaps
@@ -147,15 +147,6 @@ static relopt_bool boolRelOpts[] =
},
false
},
- {
- {
- "vacuum_truncate",
- "Enables vacuum to truncate empty pages at the end of this table",
- RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
- ShareUpdateExclusiveLock
- },
- true
- },
{
{
"deduplicate_items",
@@ -170,6 +161,25 @@ static relopt_bool boolRelOpts[] =
{{NULL}}
};
+static relopt_ternary ternaryRelOpts[] =
+{
+ {
+ {
+ "vacuum_truncate",
+ "Enables vacuum to truncate empty pages at the end of this table",
+ RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
+ ShareUpdateExclusiveLock
+ },
+ TERNARY_UNSET
+ },
+ /* list terminator */
+ {
+ {
+ NULL
+ }
+ }
+};
+
static relopt_int intRelOpts[] =
{
{
@@ -609,6 +619,13 @@ initialize_reloptions(void)
boolRelOpts[i].gen.lockmode));
j++;
}
+ for (i = 0; ternaryRelOpts[i].gen.name; i++)
+ {
+ Assert(DoLockModesConflict(ternaryRelOpts[i].gen.lockmode,
+ ternaryRelOpts[i].gen.lockmode));
+ j++;
+ }
+
for (i = 0; intRelOpts[i].gen.name; i++)
{
Assert(DoLockModesConflict(intRelOpts[i].gen.lockmode,
@@ -649,6 +666,14 @@ initialize_reloptions(void)
j++;
}
+ for (i = 0; ternaryRelOpts[i].gen.name; i++)
+ {
+ relOpts[j] = &ternaryRelOpts[i].gen;
+ relOpts[j]->type = RELOPT_TYPE_TERNARY;
+ relOpts[j]->namelen = strlen(relOpts[j]->name);
+ j++;
+ }
+
for (i = 0; intRelOpts[i].gen.name; i++)
{
relOpts[j] = &intRelOpts[i].gen;
@@ -809,6 +834,9 @@ allocate_reloption(bits32 kinds, int type, const char *name, const char *desc,
case RELOPT_TYPE_BOOL:
size = sizeof(relopt_bool);
break;
+ case RELOPT_TYPE_TERNARY:
+ size = sizeof(relopt_ternary);
+ break;
case RELOPT_TYPE_INT:
size = sizeof(relopt_int);
break;
@@ -892,6 +920,57 @@ add_local_bool_reloption(local_relopts *relopts, const char *name,
add_local_reloption(relopts, (relopt_gen *) newoption, offset);
}
+/*
+ * init_ternary_reloption
+ * Allocate and initialize a new ternary reloption
+ */
+static relopt_ternary *
+init_ternary_reloption(bits32 kinds, const char *name, const char *desc,
+ pg_ternary default_val, LOCKMODE lockmode)
+{
+ relopt_ternary *newoption;
+
+ newoption = (relopt_ternary *)
+ allocate_reloption(kinds, RELOPT_TYPE_TERNARY, name, desc, lockmode);
+ newoption->default_val = default_val;
+
+ return newoption;
+}
+
+/*
+ * add_ternary_reloption
+ * Add a new ternary reloption
+ */
+void
+add_ternary_reloption(bits32 kinds, const char *name, const char *desc,
+ pg_ternary default_val, LOCKMODE lockmode)
+{
+ relopt_ternary *newoption;
+
+ newoption =
+ init_ternary_reloption(kinds, name, desc, default_val, lockmode);
+
+ add_reloption((relopt_gen *) newoption);
+}
+
+/*
+ * add_local_ternary_reloption
+ * Add a new ternary local reloption
+ *
+ * 'offset' is offset of ternary-typed field.
+ */
+void
+add_local_ternary_reloption(local_relopts *relopts, const char *name,
+ const char *desc, pg_ternary default_val,
+ int offset)
+{
+ relopt_ternary *newoption;
+
+ newoption =
+ init_ternary_reloption(RELOPT_KIND_LOCAL, name, desc, default_val, 0);
+
+ add_local_reloption(relopts, (relopt_gen *) newoption, offset);
+}
/*
* init_real_reloption
@@ -1626,6 +1705,19 @@ parse_one_reloption(relopt_value *option, char *text_str, int text_len,
option->gen->name, value)));
}
break;
+ case RELOPT_TYPE_TERNARY:
+ {
+ bool b;
+
+ parsed = parse_bool(value, &b);
+ option->values.ternary_val = b ? TERNARY_TRUE : TERNARY_FALSE;
+ if (validate && !parsed)
+ ereport(ERROR,
+ (errcode(ERRCODE_INVALID_PARAMETER_VALUE),
+ errmsg("invalid value for ternary option \"%s\": %s",
+ option->gen->name, value)));
+ }
+ break;
case RELOPT_TYPE_INT:
{
relopt_int *optint = (relopt_int *) option->gen;
@@ -1789,17 +1881,6 @@ fillRelOptions(void *rdopts, Size basesize,
char *itempos = ((char *) rdopts) + elems[j].offset;
char *string_val;
- /*
- * If isset_offset is provided, store whether the reloption is
- * set there.
- */
- if (elems[j].isset_offset > 0)
- {
- char *setpos = ((char *) rdopts) + elems[j].isset_offset;
-
- *(bool *) setpos = options[i].isset;
- }
-
switch (options[i].gen->type)
{
case RELOPT_TYPE_BOOL:
@@ -1807,6 +1888,11 @@ fillRelOptions(void *rdopts, Size basesize,
options[i].values.bool_val :
((relopt_bool *) options[i].gen)->default_val;
break;
+ case RELOPT_TYPE_TERNARY:
+ *(pg_ternary *) itempos = options[i].isset ?
+ options[i].values.ternary_val :
+ ((relopt_ternary *) options[i].gen)->default_val;
+ break;
case RELOPT_TYPE_INT:
*(int *) itempos = options[i].isset ?
options[i].values.int_val :
@@ -1923,8 +2009,8 @@ default_reloptions(Datum reloptions, bool validate, relopt_kind kind)
offsetof(StdRdOptions, parallel_workers)},
{"vacuum_index_cleanup", RELOPT_TYPE_ENUM,
offsetof(StdRdOptions, vacuum_index_cleanup)},
- {"vacuum_truncate", RELOPT_TYPE_BOOL,
- offsetof(StdRdOptions, vacuum_truncate), offsetof(StdRdOptions, vacuum_truncate_set)},
+ {"vacuum_truncate", RELOPT_TYPE_TERNARY,
+ offsetof(StdRdOptions, vacuum_truncate)},
{"vacuum_max_eager_freeze_failure_rate", RELOPT_TYPE_REAL,
offsetof(StdRdOptions, vacuum_max_eager_freeze_failure_rate)}
};
@@ -2004,7 +2090,6 @@ build_local_reloptions(local_relopts *relopts, Datum options, bool validate)
elems[i].optname = opt->option->name;
elems[i].opttype = opt->option->type;
elems[i].offset = opt->offset;
- elems[i].isset_offset = 0; /* not supported for local relopts yet */
i++;
}
diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c
index aa4fbec143f..696eab9bd97 100644
--- a/src/backend/commands/vacuum.c
+++ b/src/backend/commands/vacuum.c
@@ -2224,9 +2224,9 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams params,
{
StdRdOptions *opts = (StdRdOptions *) rel->rd_options;
- if (opts && opts->vacuum_truncate_set)
+ if (opts && opts->vacuum_truncate != TERNARY_UNSET)
{
- if (opts->vacuum_truncate)
+ if (opts->vacuum_truncate == TERNARY_TRUE)
params.truncate = VACOPTVALUE_ENABLED;
else
params.truncate = VACOPTVALUE_DISABLED;
diff --git a/src/include/access/reloptions.h b/src/include/access/reloptions.h
index 2f08e1b0cf0..dfbef2babf2 100644
--- a/src/include/access/reloptions.h
+++ b/src/include/access/reloptions.h
@@ -29,6 +29,7 @@
typedef enum relopt_type
{
RELOPT_TYPE_BOOL,
+ RELOPT_TYPE_TERNARY, /* on, off, unset */
RELOPT_TYPE_INT,
RELOPT_TYPE_REAL,
RELOPT_TYPE_ENUM,
@@ -80,6 +81,7 @@ typedef struct relopt_value
union
{
bool bool_val;
+ pg_ternary ternary_val;
int int_val;
double real_val;
int enum_val;
@@ -94,6 +96,12 @@ typedef struct relopt_bool
bool default_val;
} relopt_bool;
+typedef struct relopt_ternary
+{
+ relopt_gen gen;
+ int default_val;
+} relopt_ternary;
+
typedef struct relopt_int
{
relopt_gen gen;
@@ -152,19 +160,6 @@ typedef struct
const char *optname; /* option's name */
relopt_type opttype; /* option's datatype */
int offset; /* offset of field in result struct */
-
- /*
- * isset_offset is an optional offset of a field in the result struct that
- * stores whether the option is explicitly set for the relation or if it
- * just picked up the default value. In most cases, this can be
- * accomplished by giving the reloption a special out-of-range default
- * value (e.g., some integer reloptions use -2), but this isn't always
- * possible. For example, a Boolean reloption cannot be given an
- * out-of-range default, so we need another way to discover the source of
- * its value. This offset is only used if given a value greater than
- * zero.
- */
- int isset_offset;
} relopt_parse_elt;
/* Local reloption definition */
@@ -195,6 +190,9 @@ typedef struct local_relopts
extern relopt_kind add_reloption_kind(void);
extern void add_bool_reloption(bits32 kinds, const char *name, const char *desc,
bool default_val, LOCKMODE lockmode);
+extern void add_ternary_reloption(bits32 kinds, const char *name,
+ const char *desc, pg_ternary default_val,
+ LOCKMODE lockmode);
extern void add_int_reloption(bits32 kinds, const char *name, const char *desc,
int default_val, int min_val, int max_val,
LOCKMODE lockmode);
@@ -214,6 +212,9 @@ extern void register_reloptions_validator(local_relopts *relopts,
extern void add_local_bool_reloption(local_relopts *relopts, const char *name,
const char *desc, bool default_val,
int offset);
+extern void add_local_ternary_reloption(local_relopts *relopts,
+ const char *name, const char *desc,
+ pg_ternary default_val, int offset);
extern void add_local_int_reloption(local_relopts *relopts, const char *name,
const char *desc, int default_val,
int min_val, int max_val, int offset);
diff --git a/src/include/postgres.h b/src/include/postgres.h
index 7d93fbce709..47c88780776 100644
--- a/src/include/postgres.h
+++ b/src/include/postgres.h
@@ -543,6 +543,21 @@ Float8GetDatum(float8 X)
* ----------------------------------------------------------------
*/
+/*
+ * pg_ternary
+ * Boolean value with an extra "unset" value
+ *
+ * This enum can be used for values that want to distinguish between true,
+ * false, and unset.
+*/
+
+typedef enum pg_ternary
+{
+ TERNARY_FALSE = 0,
+ TERNARY_TRUE = 1,
+ TERNARY_UNSET = -1
+} pg_ternary;
+
/*
* NON_EXEC_STATIC: It's sometimes useful to define a variable or function
* that is normally static but extern when using EXEC_BACKEND (see
diff --git a/src/include/utils/rel.h b/src/include/utils/rel.h
index d03ab247788..5feb18a5373 100644
--- a/src/include/utils/rel.h
+++ b/src/include/utils/rel.h
@@ -347,8 +347,7 @@ typedef struct StdRdOptions
bool user_catalog_table; /* use as an additional catalog relation */
int parallel_workers; /* max number of parallel workers */
StdRdOptIndexCleanup vacuum_index_cleanup; /* controls index vacuuming */
- bool vacuum_truncate; /* enables vacuum to truncate a relation */
- bool vacuum_truncate_set; /* whether vacuum_truncate is set */
+ pg_ternary vacuum_truncate; /* enables vacuum to truncate a relation */
/*
* Fraction of pages in a relation that vacuum can eagerly scan and fail
diff --git a/src/test/regress/expected/reloptions.out b/src/test/regress/expected/reloptions.out
index 9de19b4e3f1..1c99f79ab01 100644
--- a/src/test/regress/expected/reloptions.out
+++ b/src/test/regress/expected/reloptions.out
@@ -98,6 +98,42 @@ SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass;
{fillfactor=13,autovacuum_enabled=false}
(1 row)
+-- Tests for future (FIXME) ternary options
+-- behave as boolean option: accept unassigned name and truncated value
+DROP TABLE reloptions_test;
+CREATE TABLE reloptions_test(i INT) WITH (vacuum_truncate);
+SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass;
+ reloptions
+------------------------
+ {vacuum_truncate=true}
+(1 row)
+
+DROP TABLE reloptions_test;
+CREATE TABLE reloptions_test(i INT) WITH (vacuum_truncate=FaLS);
+SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass;
+ reloptions
+------------------------
+ {vacuum_truncate=fals}
+(1 row)
+
+-- preferred "true" alias is used when storing
+DROP TABLE reloptions_test;
+CREATE TABLE reloptions_test(i INT) WITH (vacuum_index_cleanup=on);
+SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass;
+ reloptions
+---------------------------
+ {vacuum_index_cleanup=on}
+(1 row)
+
+-- custom "third" value is available
+DROP TABLE reloptions_test;
+CREATE TABLE reloptions_test(i INT) WITH (vacuum_index_cleanup=auto);
+SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass;
+ reloptions
+-----------------------------
+ {vacuum_index_cleanup=auto}
+(1 row)
+
-- Test vacuum_truncate option
DROP TABLE reloptions_test;
CREATE TEMP TABLE reloptions_test(i INT NOT NULL, j text)
diff --git a/src/test/regress/sql/reloptions.sql b/src/test/regress/sql/reloptions.sql
index 24fbe0b478d..f5980dafcbc 100644
--- a/src/test/regress/sql/reloptions.sql
+++ b/src/test/regress/sql/reloptions.sql
@@ -59,6 +59,27 @@ UPDATE pg_class
ALTER TABLE reloptions_test RESET (illegal_option);
SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass;
+-- Tests for future (FIXME) ternary options
+
+-- behave as boolean option: accept unassigned name and truncated value
+DROP TABLE reloptions_test;
+CREATE TABLE reloptions_test(i INT) WITH (vacuum_truncate);
+SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass;
+
+DROP TABLE reloptions_test;
+CREATE TABLE reloptions_test(i INT) WITH (vacuum_truncate=FaLS);
+SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass;
+
+-- preferred "true" alias is used when storing
+DROP TABLE reloptions_test;
+CREATE TABLE reloptions_test(i INT) WITH (vacuum_index_cleanup=on);
+SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass;
+
+-- custom "third" value is available
+DROP TABLE reloptions_test;
+CREATE TABLE reloptions_test(i INT) WITH (vacuum_index_cleanup=auto);
+SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass;
+
-- Test vacuum_truncate option
DROP TABLE reloptions_test;
diff --git a/src/tools/pgindent/typedefs.list b/src/tools/pgindent/typedefs.list
index 3f3a888fd0e..1c8610fd46c 100644
--- a/src/tools/pgindent/typedefs.list
+++ b/src/tools/pgindent/typedefs.list
@@ -3952,6 +3952,7 @@ pg_sha512_ctx
pg_snapshot
pg_special_case
pg_stack_base_t
+pg_ternary
pg_time_t
pg_time_usec_t
pg_tz
@@ -4079,6 +4080,7 @@ relopt_kind
relopt_parse_elt
relopt_real
relopt_string
+relopt_ternary
relopt_type
relopt_value
relopts_validator
--
2.47.3
--c36t3fndxhkzbl5z--
^ permalink raw reply [nested|flat] 868+ messages in thread
* [PATCH v3] Introduce ternary reloptions
@ 2026-01-16 15:04 Álvaro Herrera <[email protected]>
0 siblings, 0 replies; 868+ messages in thread
From: Álvaro Herrera @ 2026-01-16 15:04 UTC (permalink / raw)
Introduce ternary reloption as a replacement for current
`vacuum_truncate` implementation. Remove the `vacuum_truncate_set`
separate flag and use TERNARY_UNSET instead.
This could also be used for other options such as `vacuum_index_cleanup`
and `buffering`, but lets get the scaffolding in first.
Discussion: https://postgr.es/m/3474141.usfYGdeWWP@thinkpad-pgpro
---
src/backend/access/common/reloptions.c | 137 ++++++++++++++++++-----
src/backend/commands/vacuum.c | 4 +-
src/include/access/reloptions.h | 27 ++---
src/include/postgres.h | 15 +++
src/include/utils/rel.h | 3 +-
src/test/regress/expected/reloptions.out | 36 ++++++
src/test/regress/sql/reloptions.sql | 21 ++++
src/tools/pgindent/typedefs.list | 2 +
8 files changed, 202 insertions(+), 43 deletions(-)
diff --git a/src/backend/access/common/reloptions.c b/src/backend/access/common/reloptions.c
index 0b83f98ed5f..6f1f577581a 100644
--- a/src/backend/access/common/reloptions.c
+++ b/src/backend/access/common/reloptions.c
@@ -40,9 +40,9 @@
*
* To add an option:
*
- * (i) decide on a type (bool, integer, real, enum, string), name, default
- * value, upper and lower bounds (if applicable); for strings, consider a
- * validation routine.
+ * (i) decide on a type (bool, ternary, integer, real, enum, string), name,
+ * default value, upper and lower bounds (if applicable); for strings,
+ * consider a validation routine.
* (ii) add a record below (or use add_<type>_reloption).
* (iii) add it to the appropriate options struct (perhaps StdRdOptions)
* (iv) add it to the appropriate handling routine (perhaps
@@ -147,15 +147,6 @@ static relopt_bool boolRelOpts[] =
},
false
},
- {
- {
- "vacuum_truncate",
- "Enables vacuum to truncate empty pages at the end of this table",
- RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
- ShareUpdateExclusiveLock
- },
- true
- },
{
{
"deduplicate_items",
@@ -170,6 +161,25 @@ static relopt_bool boolRelOpts[] =
{{NULL}}
};
+static relopt_ternary ternaryRelOpts[] =
+{
+ {
+ {
+ "vacuum_truncate",
+ "Enables vacuum to truncate empty pages at the end of this table",
+ RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
+ ShareUpdateExclusiveLock
+ },
+ TERNARY_UNSET
+ },
+ /* list terminator */
+ {
+ {
+ NULL
+ }
+ }
+};
+
static relopt_int intRelOpts[] =
{
{
@@ -609,6 +619,13 @@ initialize_reloptions(void)
boolRelOpts[i].gen.lockmode));
j++;
}
+ for (i = 0; ternaryRelOpts[i].gen.name; i++)
+ {
+ Assert(DoLockModesConflict(ternaryRelOpts[i].gen.lockmode,
+ ternaryRelOpts[i].gen.lockmode));
+ j++;
+ }
+
for (i = 0; intRelOpts[i].gen.name; i++)
{
Assert(DoLockModesConflict(intRelOpts[i].gen.lockmode,
@@ -649,6 +666,14 @@ initialize_reloptions(void)
j++;
}
+ for (i = 0; ternaryRelOpts[i].gen.name; i++)
+ {
+ relOpts[j] = &ternaryRelOpts[i].gen;
+ relOpts[j]->type = RELOPT_TYPE_TERNARY;
+ relOpts[j]->namelen = strlen(relOpts[j]->name);
+ j++;
+ }
+
for (i = 0; intRelOpts[i].gen.name; i++)
{
relOpts[j] = &intRelOpts[i].gen;
@@ -809,6 +834,9 @@ allocate_reloption(bits32 kinds, int type, const char *name, const char *desc,
case RELOPT_TYPE_BOOL:
size = sizeof(relopt_bool);
break;
+ case RELOPT_TYPE_TERNARY:
+ size = sizeof(relopt_ternary);
+ break;
case RELOPT_TYPE_INT:
size = sizeof(relopt_int);
break;
@@ -892,6 +920,57 @@ add_local_bool_reloption(local_relopts *relopts, const char *name,
add_local_reloption(relopts, (relopt_gen *) newoption, offset);
}
+/*
+ * init_ternary_reloption
+ * Allocate and initialize a new ternary reloption
+ */
+static relopt_ternary *
+init_ternary_reloption(bits32 kinds, const char *name, const char *desc,
+ pg_ternary default_val, LOCKMODE lockmode)
+{
+ relopt_ternary *newoption;
+
+ newoption = (relopt_ternary *)
+ allocate_reloption(kinds, RELOPT_TYPE_TERNARY, name, desc, lockmode);
+ newoption->default_val = default_val;
+
+ return newoption;
+}
+
+/*
+ * add_ternary_reloption
+ * Add a new ternary reloption
+ */
+void
+add_ternary_reloption(bits32 kinds, const char *name, const char *desc,
+ pg_ternary default_val, LOCKMODE lockmode)
+{
+ relopt_ternary *newoption;
+
+ newoption =
+ init_ternary_reloption(kinds, name, desc, default_val, lockmode);
+
+ add_reloption((relopt_gen *) newoption);
+}
+
+/*
+ * add_local_ternary_reloption
+ * Add a new ternary local reloption
+ *
+ * 'offset' is offset of ternary-typed field.
+ */
+void
+add_local_ternary_reloption(local_relopts *relopts, const char *name,
+ const char *desc, pg_ternary default_val,
+ int offset)
+{
+ relopt_ternary *newoption;
+
+ newoption =
+ init_ternary_reloption(RELOPT_KIND_LOCAL, name, desc, default_val, 0);
+
+ add_local_reloption(relopts, (relopt_gen *) newoption, offset);
+}
/*
* init_real_reloption
@@ -1626,6 +1705,19 @@ parse_one_reloption(relopt_value *option, char *text_str, int text_len,
option->gen->name, value)));
}
break;
+ case RELOPT_TYPE_TERNARY:
+ {
+ bool b;
+
+ parsed = parse_bool(value, &b);
+ option->values.ternary_val = b ? TERNARY_TRUE : TERNARY_FALSE;
+ if (validate && !parsed)
+ ereport(ERROR,
+ (errcode(ERRCODE_INVALID_PARAMETER_VALUE),
+ errmsg("invalid value for ternary option \"%s\": %s",
+ option->gen->name, value)));
+ }
+ break;
case RELOPT_TYPE_INT:
{
relopt_int *optint = (relopt_int *) option->gen;
@@ -1789,17 +1881,6 @@ fillRelOptions(void *rdopts, Size basesize,
char *itempos = ((char *) rdopts) + elems[j].offset;
char *string_val;
- /*
- * If isset_offset is provided, store whether the reloption is
- * set there.
- */
- if (elems[j].isset_offset > 0)
- {
- char *setpos = ((char *) rdopts) + elems[j].isset_offset;
-
- *(bool *) setpos = options[i].isset;
- }
-
switch (options[i].gen->type)
{
case RELOPT_TYPE_BOOL:
@@ -1807,6 +1888,11 @@ fillRelOptions(void *rdopts, Size basesize,
options[i].values.bool_val :
((relopt_bool *) options[i].gen)->default_val;
break;
+ case RELOPT_TYPE_TERNARY:
+ *(pg_ternary *) itempos = options[i].isset ?
+ options[i].values.ternary_val :
+ ((relopt_ternary *) options[i].gen)->default_val;
+ break;
case RELOPT_TYPE_INT:
*(int *) itempos = options[i].isset ?
options[i].values.int_val :
@@ -1923,8 +2009,8 @@ default_reloptions(Datum reloptions, bool validate, relopt_kind kind)
offsetof(StdRdOptions, parallel_workers)},
{"vacuum_index_cleanup", RELOPT_TYPE_ENUM,
offsetof(StdRdOptions, vacuum_index_cleanup)},
- {"vacuum_truncate", RELOPT_TYPE_BOOL,
- offsetof(StdRdOptions, vacuum_truncate), offsetof(StdRdOptions, vacuum_truncate_set)},
+ {"vacuum_truncate", RELOPT_TYPE_TERNARY,
+ offsetof(StdRdOptions, vacuum_truncate)},
{"vacuum_max_eager_freeze_failure_rate", RELOPT_TYPE_REAL,
offsetof(StdRdOptions, vacuum_max_eager_freeze_failure_rate)}
};
@@ -2004,7 +2090,6 @@ build_local_reloptions(local_relopts *relopts, Datum options, bool validate)
elems[i].optname = opt->option->name;
elems[i].opttype = opt->option->type;
elems[i].offset = opt->offset;
- elems[i].isset_offset = 0; /* not supported for local relopts yet */
i++;
}
diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c
index aa4fbec143f..696eab9bd97 100644
--- a/src/backend/commands/vacuum.c
+++ b/src/backend/commands/vacuum.c
@@ -2224,9 +2224,9 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams params,
{
StdRdOptions *opts = (StdRdOptions *) rel->rd_options;
- if (opts && opts->vacuum_truncate_set)
+ if (opts && opts->vacuum_truncate != TERNARY_UNSET)
{
- if (opts->vacuum_truncate)
+ if (opts->vacuum_truncate == TERNARY_TRUE)
params.truncate = VACOPTVALUE_ENABLED;
else
params.truncate = VACOPTVALUE_DISABLED;
diff --git a/src/include/access/reloptions.h b/src/include/access/reloptions.h
index 2f08e1b0cf0..dfbef2babf2 100644
--- a/src/include/access/reloptions.h
+++ b/src/include/access/reloptions.h
@@ -29,6 +29,7 @@
typedef enum relopt_type
{
RELOPT_TYPE_BOOL,
+ RELOPT_TYPE_TERNARY, /* on, off, unset */
RELOPT_TYPE_INT,
RELOPT_TYPE_REAL,
RELOPT_TYPE_ENUM,
@@ -80,6 +81,7 @@ typedef struct relopt_value
union
{
bool bool_val;
+ pg_ternary ternary_val;
int int_val;
double real_val;
int enum_val;
@@ -94,6 +96,12 @@ typedef struct relopt_bool
bool default_val;
} relopt_bool;
+typedef struct relopt_ternary
+{
+ relopt_gen gen;
+ int default_val;
+} relopt_ternary;
+
typedef struct relopt_int
{
relopt_gen gen;
@@ -152,19 +160,6 @@ typedef struct
const char *optname; /* option's name */
relopt_type opttype; /* option's datatype */
int offset; /* offset of field in result struct */
-
- /*
- * isset_offset is an optional offset of a field in the result struct that
- * stores whether the option is explicitly set for the relation or if it
- * just picked up the default value. In most cases, this can be
- * accomplished by giving the reloption a special out-of-range default
- * value (e.g., some integer reloptions use -2), but this isn't always
- * possible. For example, a Boolean reloption cannot be given an
- * out-of-range default, so we need another way to discover the source of
- * its value. This offset is only used if given a value greater than
- * zero.
- */
- int isset_offset;
} relopt_parse_elt;
/* Local reloption definition */
@@ -195,6 +190,9 @@ typedef struct local_relopts
extern relopt_kind add_reloption_kind(void);
extern void add_bool_reloption(bits32 kinds, const char *name, const char *desc,
bool default_val, LOCKMODE lockmode);
+extern void add_ternary_reloption(bits32 kinds, const char *name,
+ const char *desc, pg_ternary default_val,
+ LOCKMODE lockmode);
extern void add_int_reloption(bits32 kinds, const char *name, const char *desc,
int default_val, int min_val, int max_val,
LOCKMODE lockmode);
@@ -214,6 +212,9 @@ extern void register_reloptions_validator(local_relopts *relopts,
extern void add_local_bool_reloption(local_relopts *relopts, const char *name,
const char *desc, bool default_val,
int offset);
+extern void add_local_ternary_reloption(local_relopts *relopts,
+ const char *name, const char *desc,
+ pg_ternary default_val, int offset);
extern void add_local_int_reloption(local_relopts *relopts, const char *name,
const char *desc, int default_val,
int min_val, int max_val, int offset);
diff --git a/src/include/postgres.h b/src/include/postgres.h
index 7d93fbce709..47c88780776 100644
--- a/src/include/postgres.h
+++ b/src/include/postgres.h
@@ -543,6 +543,21 @@ Float8GetDatum(float8 X)
* ----------------------------------------------------------------
*/
+/*
+ * pg_ternary
+ * Boolean value with an extra "unset" value
+ *
+ * This enum can be used for values that want to distinguish between true,
+ * false, and unset.
+*/
+
+typedef enum pg_ternary
+{
+ TERNARY_FALSE = 0,
+ TERNARY_TRUE = 1,
+ TERNARY_UNSET = -1
+} pg_ternary;
+
/*
* NON_EXEC_STATIC: It's sometimes useful to define a variable or function
* that is normally static but extern when using EXEC_BACKEND (see
diff --git a/src/include/utils/rel.h b/src/include/utils/rel.h
index d03ab247788..5feb18a5373 100644
--- a/src/include/utils/rel.h
+++ b/src/include/utils/rel.h
@@ -347,8 +347,7 @@ typedef struct StdRdOptions
bool user_catalog_table; /* use as an additional catalog relation */
int parallel_workers; /* max number of parallel workers */
StdRdOptIndexCleanup vacuum_index_cleanup; /* controls index vacuuming */
- bool vacuum_truncate; /* enables vacuum to truncate a relation */
- bool vacuum_truncate_set; /* whether vacuum_truncate is set */
+ pg_ternary vacuum_truncate; /* enables vacuum to truncate a relation */
/*
* Fraction of pages in a relation that vacuum can eagerly scan and fail
diff --git a/src/test/regress/expected/reloptions.out b/src/test/regress/expected/reloptions.out
index 9de19b4e3f1..1c99f79ab01 100644
--- a/src/test/regress/expected/reloptions.out
+++ b/src/test/regress/expected/reloptions.out
@@ -98,6 +98,42 @@ SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass;
{fillfactor=13,autovacuum_enabled=false}
(1 row)
+-- Tests for future (FIXME) ternary options
+-- behave as boolean option: accept unassigned name and truncated value
+DROP TABLE reloptions_test;
+CREATE TABLE reloptions_test(i INT) WITH (vacuum_truncate);
+SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass;
+ reloptions
+------------------------
+ {vacuum_truncate=true}
+(1 row)
+
+DROP TABLE reloptions_test;
+CREATE TABLE reloptions_test(i INT) WITH (vacuum_truncate=FaLS);
+SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass;
+ reloptions
+------------------------
+ {vacuum_truncate=fals}
+(1 row)
+
+-- preferred "true" alias is used when storing
+DROP TABLE reloptions_test;
+CREATE TABLE reloptions_test(i INT) WITH (vacuum_index_cleanup=on);
+SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass;
+ reloptions
+---------------------------
+ {vacuum_index_cleanup=on}
+(1 row)
+
+-- custom "third" value is available
+DROP TABLE reloptions_test;
+CREATE TABLE reloptions_test(i INT) WITH (vacuum_index_cleanup=auto);
+SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass;
+ reloptions
+-----------------------------
+ {vacuum_index_cleanup=auto}
+(1 row)
+
-- Test vacuum_truncate option
DROP TABLE reloptions_test;
CREATE TEMP TABLE reloptions_test(i INT NOT NULL, j text)
diff --git a/src/test/regress/sql/reloptions.sql b/src/test/regress/sql/reloptions.sql
index 24fbe0b478d..f5980dafcbc 100644
--- a/src/test/regress/sql/reloptions.sql
+++ b/src/test/regress/sql/reloptions.sql
@@ -59,6 +59,27 @@ UPDATE pg_class
ALTER TABLE reloptions_test RESET (illegal_option);
SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass;
+-- Tests for future (FIXME) ternary options
+
+-- behave as boolean option: accept unassigned name and truncated value
+DROP TABLE reloptions_test;
+CREATE TABLE reloptions_test(i INT) WITH (vacuum_truncate);
+SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass;
+
+DROP TABLE reloptions_test;
+CREATE TABLE reloptions_test(i INT) WITH (vacuum_truncate=FaLS);
+SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass;
+
+-- preferred "true" alias is used when storing
+DROP TABLE reloptions_test;
+CREATE TABLE reloptions_test(i INT) WITH (vacuum_index_cleanup=on);
+SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass;
+
+-- custom "third" value is available
+DROP TABLE reloptions_test;
+CREATE TABLE reloptions_test(i INT) WITH (vacuum_index_cleanup=auto);
+SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass;
+
-- Test vacuum_truncate option
DROP TABLE reloptions_test;
diff --git a/src/tools/pgindent/typedefs.list b/src/tools/pgindent/typedefs.list
index 3f3a888fd0e..1c8610fd46c 100644
--- a/src/tools/pgindent/typedefs.list
+++ b/src/tools/pgindent/typedefs.list
@@ -3952,6 +3952,7 @@ pg_sha512_ctx
pg_snapshot
pg_special_case
pg_stack_base_t
+pg_ternary
pg_time_t
pg_time_usec_t
pg_tz
@@ -4079,6 +4080,7 @@ relopt_kind
relopt_parse_elt
relopt_real
relopt_string
+relopt_ternary
relopt_type
relopt_value
relopts_validator
--
2.47.3
--c36t3fndxhkzbl5z--
^ permalink raw reply [nested|flat] 868+ messages in thread
* [PATCH v3] Introduce ternary reloptions
@ 2026-01-16 15:04 Álvaro Herrera <[email protected]>
0 siblings, 0 replies; 868+ messages in thread
From: Álvaro Herrera @ 2026-01-16 15:04 UTC (permalink / raw)
Introduce ternary reloption as a replacement for current
`vacuum_truncate` implementation. Remove the `vacuum_truncate_set`
separate flag and use TERNARY_UNSET instead.
This could also be used for other options such as `vacuum_index_cleanup`
and `buffering`, but lets get the scaffolding in first.
Discussion: https://postgr.es/m/3474141.usfYGdeWWP@thinkpad-pgpro
---
src/backend/access/common/reloptions.c | 137 ++++++++++++++++++-----
src/backend/commands/vacuum.c | 4 +-
src/include/access/reloptions.h | 27 ++---
src/include/postgres.h | 15 +++
src/include/utils/rel.h | 3 +-
src/test/regress/expected/reloptions.out | 36 ++++++
src/test/regress/sql/reloptions.sql | 21 ++++
src/tools/pgindent/typedefs.list | 2 +
8 files changed, 202 insertions(+), 43 deletions(-)
diff --git a/src/backend/access/common/reloptions.c b/src/backend/access/common/reloptions.c
index 0b83f98ed5f..6f1f577581a 100644
--- a/src/backend/access/common/reloptions.c
+++ b/src/backend/access/common/reloptions.c
@@ -40,9 +40,9 @@
*
* To add an option:
*
- * (i) decide on a type (bool, integer, real, enum, string), name, default
- * value, upper and lower bounds (if applicable); for strings, consider a
- * validation routine.
+ * (i) decide on a type (bool, ternary, integer, real, enum, string), name,
+ * default value, upper and lower bounds (if applicable); for strings,
+ * consider a validation routine.
* (ii) add a record below (or use add_<type>_reloption).
* (iii) add it to the appropriate options struct (perhaps StdRdOptions)
* (iv) add it to the appropriate handling routine (perhaps
@@ -147,15 +147,6 @@ static relopt_bool boolRelOpts[] =
},
false
},
- {
- {
- "vacuum_truncate",
- "Enables vacuum to truncate empty pages at the end of this table",
- RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
- ShareUpdateExclusiveLock
- },
- true
- },
{
{
"deduplicate_items",
@@ -170,6 +161,25 @@ static relopt_bool boolRelOpts[] =
{{NULL}}
};
+static relopt_ternary ternaryRelOpts[] =
+{
+ {
+ {
+ "vacuum_truncate",
+ "Enables vacuum to truncate empty pages at the end of this table",
+ RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
+ ShareUpdateExclusiveLock
+ },
+ TERNARY_UNSET
+ },
+ /* list terminator */
+ {
+ {
+ NULL
+ }
+ }
+};
+
static relopt_int intRelOpts[] =
{
{
@@ -609,6 +619,13 @@ initialize_reloptions(void)
boolRelOpts[i].gen.lockmode));
j++;
}
+ for (i = 0; ternaryRelOpts[i].gen.name; i++)
+ {
+ Assert(DoLockModesConflict(ternaryRelOpts[i].gen.lockmode,
+ ternaryRelOpts[i].gen.lockmode));
+ j++;
+ }
+
for (i = 0; intRelOpts[i].gen.name; i++)
{
Assert(DoLockModesConflict(intRelOpts[i].gen.lockmode,
@@ -649,6 +666,14 @@ initialize_reloptions(void)
j++;
}
+ for (i = 0; ternaryRelOpts[i].gen.name; i++)
+ {
+ relOpts[j] = &ternaryRelOpts[i].gen;
+ relOpts[j]->type = RELOPT_TYPE_TERNARY;
+ relOpts[j]->namelen = strlen(relOpts[j]->name);
+ j++;
+ }
+
for (i = 0; intRelOpts[i].gen.name; i++)
{
relOpts[j] = &intRelOpts[i].gen;
@@ -809,6 +834,9 @@ allocate_reloption(bits32 kinds, int type, const char *name, const char *desc,
case RELOPT_TYPE_BOOL:
size = sizeof(relopt_bool);
break;
+ case RELOPT_TYPE_TERNARY:
+ size = sizeof(relopt_ternary);
+ break;
case RELOPT_TYPE_INT:
size = sizeof(relopt_int);
break;
@@ -892,6 +920,57 @@ add_local_bool_reloption(local_relopts *relopts, const char *name,
add_local_reloption(relopts, (relopt_gen *) newoption, offset);
}
+/*
+ * init_ternary_reloption
+ * Allocate and initialize a new ternary reloption
+ */
+static relopt_ternary *
+init_ternary_reloption(bits32 kinds, const char *name, const char *desc,
+ pg_ternary default_val, LOCKMODE lockmode)
+{
+ relopt_ternary *newoption;
+
+ newoption = (relopt_ternary *)
+ allocate_reloption(kinds, RELOPT_TYPE_TERNARY, name, desc, lockmode);
+ newoption->default_val = default_val;
+
+ return newoption;
+}
+
+/*
+ * add_ternary_reloption
+ * Add a new ternary reloption
+ */
+void
+add_ternary_reloption(bits32 kinds, const char *name, const char *desc,
+ pg_ternary default_val, LOCKMODE lockmode)
+{
+ relopt_ternary *newoption;
+
+ newoption =
+ init_ternary_reloption(kinds, name, desc, default_val, lockmode);
+
+ add_reloption((relopt_gen *) newoption);
+}
+
+/*
+ * add_local_ternary_reloption
+ * Add a new ternary local reloption
+ *
+ * 'offset' is offset of ternary-typed field.
+ */
+void
+add_local_ternary_reloption(local_relopts *relopts, const char *name,
+ const char *desc, pg_ternary default_val,
+ int offset)
+{
+ relopt_ternary *newoption;
+
+ newoption =
+ init_ternary_reloption(RELOPT_KIND_LOCAL, name, desc, default_val, 0);
+
+ add_local_reloption(relopts, (relopt_gen *) newoption, offset);
+}
/*
* init_real_reloption
@@ -1626,6 +1705,19 @@ parse_one_reloption(relopt_value *option, char *text_str, int text_len,
option->gen->name, value)));
}
break;
+ case RELOPT_TYPE_TERNARY:
+ {
+ bool b;
+
+ parsed = parse_bool(value, &b);
+ option->values.ternary_val = b ? TERNARY_TRUE : TERNARY_FALSE;
+ if (validate && !parsed)
+ ereport(ERROR,
+ (errcode(ERRCODE_INVALID_PARAMETER_VALUE),
+ errmsg("invalid value for ternary option \"%s\": %s",
+ option->gen->name, value)));
+ }
+ break;
case RELOPT_TYPE_INT:
{
relopt_int *optint = (relopt_int *) option->gen;
@@ -1789,17 +1881,6 @@ fillRelOptions(void *rdopts, Size basesize,
char *itempos = ((char *) rdopts) + elems[j].offset;
char *string_val;
- /*
- * If isset_offset is provided, store whether the reloption is
- * set there.
- */
- if (elems[j].isset_offset > 0)
- {
- char *setpos = ((char *) rdopts) + elems[j].isset_offset;
-
- *(bool *) setpos = options[i].isset;
- }
-
switch (options[i].gen->type)
{
case RELOPT_TYPE_BOOL:
@@ -1807,6 +1888,11 @@ fillRelOptions(void *rdopts, Size basesize,
options[i].values.bool_val :
((relopt_bool *) options[i].gen)->default_val;
break;
+ case RELOPT_TYPE_TERNARY:
+ *(pg_ternary *) itempos = options[i].isset ?
+ options[i].values.ternary_val :
+ ((relopt_ternary *) options[i].gen)->default_val;
+ break;
case RELOPT_TYPE_INT:
*(int *) itempos = options[i].isset ?
options[i].values.int_val :
@@ -1923,8 +2009,8 @@ default_reloptions(Datum reloptions, bool validate, relopt_kind kind)
offsetof(StdRdOptions, parallel_workers)},
{"vacuum_index_cleanup", RELOPT_TYPE_ENUM,
offsetof(StdRdOptions, vacuum_index_cleanup)},
- {"vacuum_truncate", RELOPT_TYPE_BOOL,
- offsetof(StdRdOptions, vacuum_truncate), offsetof(StdRdOptions, vacuum_truncate_set)},
+ {"vacuum_truncate", RELOPT_TYPE_TERNARY,
+ offsetof(StdRdOptions, vacuum_truncate)},
{"vacuum_max_eager_freeze_failure_rate", RELOPT_TYPE_REAL,
offsetof(StdRdOptions, vacuum_max_eager_freeze_failure_rate)}
};
@@ -2004,7 +2090,6 @@ build_local_reloptions(local_relopts *relopts, Datum options, bool validate)
elems[i].optname = opt->option->name;
elems[i].opttype = opt->option->type;
elems[i].offset = opt->offset;
- elems[i].isset_offset = 0; /* not supported for local relopts yet */
i++;
}
diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c
index aa4fbec143f..696eab9bd97 100644
--- a/src/backend/commands/vacuum.c
+++ b/src/backend/commands/vacuum.c
@@ -2224,9 +2224,9 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams params,
{
StdRdOptions *opts = (StdRdOptions *) rel->rd_options;
- if (opts && opts->vacuum_truncate_set)
+ if (opts && opts->vacuum_truncate != TERNARY_UNSET)
{
- if (opts->vacuum_truncate)
+ if (opts->vacuum_truncate == TERNARY_TRUE)
params.truncate = VACOPTVALUE_ENABLED;
else
params.truncate = VACOPTVALUE_DISABLED;
diff --git a/src/include/access/reloptions.h b/src/include/access/reloptions.h
index 2f08e1b0cf0..dfbef2babf2 100644
--- a/src/include/access/reloptions.h
+++ b/src/include/access/reloptions.h
@@ -29,6 +29,7 @@
typedef enum relopt_type
{
RELOPT_TYPE_BOOL,
+ RELOPT_TYPE_TERNARY, /* on, off, unset */
RELOPT_TYPE_INT,
RELOPT_TYPE_REAL,
RELOPT_TYPE_ENUM,
@@ -80,6 +81,7 @@ typedef struct relopt_value
union
{
bool bool_val;
+ pg_ternary ternary_val;
int int_val;
double real_val;
int enum_val;
@@ -94,6 +96,12 @@ typedef struct relopt_bool
bool default_val;
} relopt_bool;
+typedef struct relopt_ternary
+{
+ relopt_gen gen;
+ int default_val;
+} relopt_ternary;
+
typedef struct relopt_int
{
relopt_gen gen;
@@ -152,19 +160,6 @@ typedef struct
const char *optname; /* option's name */
relopt_type opttype; /* option's datatype */
int offset; /* offset of field in result struct */
-
- /*
- * isset_offset is an optional offset of a field in the result struct that
- * stores whether the option is explicitly set for the relation or if it
- * just picked up the default value. In most cases, this can be
- * accomplished by giving the reloption a special out-of-range default
- * value (e.g., some integer reloptions use -2), but this isn't always
- * possible. For example, a Boolean reloption cannot be given an
- * out-of-range default, so we need another way to discover the source of
- * its value. This offset is only used if given a value greater than
- * zero.
- */
- int isset_offset;
} relopt_parse_elt;
/* Local reloption definition */
@@ -195,6 +190,9 @@ typedef struct local_relopts
extern relopt_kind add_reloption_kind(void);
extern void add_bool_reloption(bits32 kinds, const char *name, const char *desc,
bool default_val, LOCKMODE lockmode);
+extern void add_ternary_reloption(bits32 kinds, const char *name,
+ const char *desc, pg_ternary default_val,
+ LOCKMODE lockmode);
extern void add_int_reloption(bits32 kinds, const char *name, const char *desc,
int default_val, int min_val, int max_val,
LOCKMODE lockmode);
@@ -214,6 +212,9 @@ extern void register_reloptions_validator(local_relopts *relopts,
extern void add_local_bool_reloption(local_relopts *relopts, const char *name,
const char *desc, bool default_val,
int offset);
+extern void add_local_ternary_reloption(local_relopts *relopts,
+ const char *name, const char *desc,
+ pg_ternary default_val, int offset);
extern void add_local_int_reloption(local_relopts *relopts, const char *name,
const char *desc, int default_val,
int min_val, int max_val, int offset);
diff --git a/src/include/postgres.h b/src/include/postgres.h
index 7d93fbce709..47c88780776 100644
--- a/src/include/postgres.h
+++ b/src/include/postgres.h
@@ -543,6 +543,21 @@ Float8GetDatum(float8 X)
* ----------------------------------------------------------------
*/
+/*
+ * pg_ternary
+ * Boolean value with an extra "unset" value
+ *
+ * This enum can be used for values that want to distinguish between true,
+ * false, and unset.
+*/
+
+typedef enum pg_ternary
+{
+ TERNARY_FALSE = 0,
+ TERNARY_TRUE = 1,
+ TERNARY_UNSET = -1
+} pg_ternary;
+
/*
* NON_EXEC_STATIC: It's sometimes useful to define a variable or function
* that is normally static but extern when using EXEC_BACKEND (see
diff --git a/src/include/utils/rel.h b/src/include/utils/rel.h
index d03ab247788..5feb18a5373 100644
--- a/src/include/utils/rel.h
+++ b/src/include/utils/rel.h
@@ -347,8 +347,7 @@ typedef struct StdRdOptions
bool user_catalog_table; /* use as an additional catalog relation */
int parallel_workers; /* max number of parallel workers */
StdRdOptIndexCleanup vacuum_index_cleanup; /* controls index vacuuming */
- bool vacuum_truncate; /* enables vacuum to truncate a relation */
- bool vacuum_truncate_set; /* whether vacuum_truncate is set */
+ pg_ternary vacuum_truncate; /* enables vacuum to truncate a relation */
/*
* Fraction of pages in a relation that vacuum can eagerly scan and fail
diff --git a/src/test/regress/expected/reloptions.out b/src/test/regress/expected/reloptions.out
index 9de19b4e3f1..1c99f79ab01 100644
--- a/src/test/regress/expected/reloptions.out
+++ b/src/test/regress/expected/reloptions.out
@@ -98,6 +98,42 @@ SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass;
{fillfactor=13,autovacuum_enabled=false}
(1 row)
+-- Tests for future (FIXME) ternary options
+-- behave as boolean option: accept unassigned name and truncated value
+DROP TABLE reloptions_test;
+CREATE TABLE reloptions_test(i INT) WITH (vacuum_truncate);
+SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass;
+ reloptions
+------------------------
+ {vacuum_truncate=true}
+(1 row)
+
+DROP TABLE reloptions_test;
+CREATE TABLE reloptions_test(i INT) WITH (vacuum_truncate=FaLS);
+SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass;
+ reloptions
+------------------------
+ {vacuum_truncate=fals}
+(1 row)
+
+-- preferred "true" alias is used when storing
+DROP TABLE reloptions_test;
+CREATE TABLE reloptions_test(i INT) WITH (vacuum_index_cleanup=on);
+SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass;
+ reloptions
+---------------------------
+ {vacuum_index_cleanup=on}
+(1 row)
+
+-- custom "third" value is available
+DROP TABLE reloptions_test;
+CREATE TABLE reloptions_test(i INT) WITH (vacuum_index_cleanup=auto);
+SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass;
+ reloptions
+-----------------------------
+ {vacuum_index_cleanup=auto}
+(1 row)
+
-- Test vacuum_truncate option
DROP TABLE reloptions_test;
CREATE TEMP TABLE reloptions_test(i INT NOT NULL, j text)
diff --git a/src/test/regress/sql/reloptions.sql b/src/test/regress/sql/reloptions.sql
index 24fbe0b478d..f5980dafcbc 100644
--- a/src/test/regress/sql/reloptions.sql
+++ b/src/test/regress/sql/reloptions.sql
@@ -59,6 +59,27 @@ UPDATE pg_class
ALTER TABLE reloptions_test RESET (illegal_option);
SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass;
+-- Tests for future (FIXME) ternary options
+
+-- behave as boolean option: accept unassigned name and truncated value
+DROP TABLE reloptions_test;
+CREATE TABLE reloptions_test(i INT) WITH (vacuum_truncate);
+SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass;
+
+DROP TABLE reloptions_test;
+CREATE TABLE reloptions_test(i INT) WITH (vacuum_truncate=FaLS);
+SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass;
+
+-- preferred "true" alias is used when storing
+DROP TABLE reloptions_test;
+CREATE TABLE reloptions_test(i INT) WITH (vacuum_index_cleanup=on);
+SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass;
+
+-- custom "third" value is available
+DROP TABLE reloptions_test;
+CREATE TABLE reloptions_test(i INT) WITH (vacuum_index_cleanup=auto);
+SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass;
+
-- Test vacuum_truncate option
DROP TABLE reloptions_test;
diff --git a/src/tools/pgindent/typedefs.list b/src/tools/pgindent/typedefs.list
index 3f3a888fd0e..1c8610fd46c 100644
--- a/src/tools/pgindent/typedefs.list
+++ b/src/tools/pgindent/typedefs.list
@@ -3952,6 +3952,7 @@ pg_sha512_ctx
pg_snapshot
pg_special_case
pg_stack_base_t
+pg_ternary
pg_time_t
pg_time_usec_t
pg_tz
@@ -4079,6 +4080,7 @@ relopt_kind
relopt_parse_elt
relopt_real
relopt_string
+relopt_ternary
relopt_type
relopt_value
relopts_validator
--
2.47.3
--c36t3fndxhkzbl5z--
^ permalink raw reply [nested|flat] 868+ messages in thread
* [PATCH v3] Introduce ternary reloptions
@ 2026-01-16 15:04 Álvaro Herrera <[email protected]>
0 siblings, 0 replies; 868+ messages in thread
From: Álvaro Herrera @ 2026-01-16 15:04 UTC (permalink / raw)
Introduce ternary reloption as a replacement for current
`vacuum_truncate` implementation. Remove the `vacuum_truncate_set`
separate flag and use TERNARY_UNSET instead.
This could also be used for other options such as `vacuum_index_cleanup`
and `buffering`, but lets get the scaffolding in first.
Discussion: https://postgr.es/m/3474141.usfYGdeWWP@thinkpad-pgpro
---
src/backend/access/common/reloptions.c | 137 ++++++++++++++++++-----
src/backend/commands/vacuum.c | 4 +-
src/include/access/reloptions.h | 27 ++---
src/include/postgres.h | 15 +++
src/include/utils/rel.h | 3 +-
src/test/regress/expected/reloptions.out | 36 ++++++
src/test/regress/sql/reloptions.sql | 21 ++++
src/tools/pgindent/typedefs.list | 2 +
8 files changed, 202 insertions(+), 43 deletions(-)
diff --git a/src/backend/access/common/reloptions.c b/src/backend/access/common/reloptions.c
index 0b83f98ed5f..6f1f577581a 100644
--- a/src/backend/access/common/reloptions.c
+++ b/src/backend/access/common/reloptions.c
@@ -40,9 +40,9 @@
*
* To add an option:
*
- * (i) decide on a type (bool, integer, real, enum, string), name, default
- * value, upper and lower bounds (if applicable); for strings, consider a
- * validation routine.
+ * (i) decide on a type (bool, ternary, integer, real, enum, string), name,
+ * default value, upper and lower bounds (if applicable); for strings,
+ * consider a validation routine.
* (ii) add a record below (or use add_<type>_reloption).
* (iii) add it to the appropriate options struct (perhaps StdRdOptions)
* (iv) add it to the appropriate handling routine (perhaps
@@ -147,15 +147,6 @@ static relopt_bool boolRelOpts[] =
},
false
},
- {
- {
- "vacuum_truncate",
- "Enables vacuum to truncate empty pages at the end of this table",
- RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
- ShareUpdateExclusiveLock
- },
- true
- },
{
{
"deduplicate_items",
@@ -170,6 +161,25 @@ static relopt_bool boolRelOpts[] =
{{NULL}}
};
+static relopt_ternary ternaryRelOpts[] =
+{
+ {
+ {
+ "vacuum_truncate",
+ "Enables vacuum to truncate empty pages at the end of this table",
+ RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
+ ShareUpdateExclusiveLock
+ },
+ TERNARY_UNSET
+ },
+ /* list terminator */
+ {
+ {
+ NULL
+ }
+ }
+};
+
static relopt_int intRelOpts[] =
{
{
@@ -609,6 +619,13 @@ initialize_reloptions(void)
boolRelOpts[i].gen.lockmode));
j++;
}
+ for (i = 0; ternaryRelOpts[i].gen.name; i++)
+ {
+ Assert(DoLockModesConflict(ternaryRelOpts[i].gen.lockmode,
+ ternaryRelOpts[i].gen.lockmode));
+ j++;
+ }
+
for (i = 0; intRelOpts[i].gen.name; i++)
{
Assert(DoLockModesConflict(intRelOpts[i].gen.lockmode,
@@ -649,6 +666,14 @@ initialize_reloptions(void)
j++;
}
+ for (i = 0; ternaryRelOpts[i].gen.name; i++)
+ {
+ relOpts[j] = &ternaryRelOpts[i].gen;
+ relOpts[j]->type = RELOPT_TYPE_TERNARY;
+ relOpts[j]->namelen = strlen(relOpts[j]->name);
+ j++;
+ }
+
for (i = 0; intRelOpts[i].gen.name; i++)
{
relOpts[j] = &intRelOpts[i].gen;
@@ -809,6 +834,9 @@ allocate_reloption(bits32 kinds, int type, const char *name, const char *desc,
case RELOPT_TYPE_BOOL:
size = sizeof(relopt_bool);
break;
+ case RELOPT_TYPE_TERNARY:
+ size = sizeof(relopt_ternary);
+ break;
case RELOPT_TYPE_INT:
size = sizeof(relopt_int);
break;
@@ -892,6 +920,57 @@ add_local_bool_reloption(local_relopts *relopts, const char *name,
add_local_reloption(relopts, (relopt_gen *) newoption, offset);
}
+/*
+ * init_ternary_reloption
+ * Allocate and initialize a new ternary reloption
+ */
+static relopt_ternary *
+init_ternary_reloption(bits32 kinds, const char *name, const char *desc,
+ pg_ternary default_val, LOCKMODE lockmode)
+{
+ relopt_ternary *newoption;
+
+ newoption = (relopt_ternary *)
+ allocate_reloption(kinds, RELOPT_TYPE_TERNARY, name, desc, lockmode);
+ newoption->default_val = default_val;
+
+ return newoption;
+}
+
+/*
+ * add_ternary_reloption
+ * Add a new ternary reloption
+ */
+void
+add_ternary_reloption(bits32 kinds, const char *name, const char *desc,
+ pg_ternary default_val, LOCKMODE lockmode)
+{
+ relopt_ternary *newoption;
+
+ newoption =
+ init_ternary_reloption(kinds, name, desc, default_val, lockmode);
+
+ add_reloption((relopt_gen *) newoption);
+}
+
+/*
+ * add_local_ternary_reloption
+ * Add a new ternary local reloption
+ *
+ * 'offset' is offset of ternary-typed field.
+ */
+void
+add_local_ternary_reloption(local_relopts *relopts, const char *name,
+ const char *desc, pg_ternary default_val,
+ int offset)
+{
+ relopt_ternary *newoption;
+
+ newoption =
+ init_ternary_reloption(RELOPT_KIND_LOCAL, name, desc, default_val, 0);
+
+ add_local_reloption(relopts, (relopt_gen *) newoption, offset);
+}
/*
* init_real_reloption
@@ -1626,6 +1705,19 @@ parse_one_reloption(relopt_value *option, char *text_str, int text_len,
option->gen->name, value)));
}
break;
+ case RELOPT_TYPE_TERNARY:
+ {
+ bool b;
+
+ parsed = parse_bool(value, &b);
+ option->values.ternary_val = b ? TERNARY_TRUE : TERNARY_FALSE;
+ if (validate && !parsed)
+ ereport(ERROR,
+ (errcode(ERRCODE_INVALID_PARAMETER_VALUE),
+ errmsg("invalid value for ternary option \"%s\": %s",
+ option->gen->name, value)));
+ }
+ break;
case RELOPT_TYPE_INT:
{
relopt_int *optint = (relopt_int *) option->gen;
@@ -1789,17 +1881,6 @@ fillRelOptions(void *rdopts, Size basesize,
char *itempos = ((char *) rdopts) + elems[j].offset;
char *string_val;
- /*
- * If isset_offset is provided, store whether the reloption is
- * set there.
- */
- if (elems[j].isset_offset > 0)
- {
- char *setpos = ((char *) rdopts) + elems[j].isset_offset;
-
- *(bool *) setpos = options[i].isset;
- }
-
switch (options[i].gen->type)
{
case RELOPT_TYPE_BOOL:
@@ -1807,6 +1888,11 @@ fillRelOptions(void *rdopts, Size basesize,
options[i].values.bool_val :
((relopt_bool *) options[i].gen)->default_val;
break;
+ case RELOPT_TYPE_TERNARY:
+ *(pg_ternary *) itempos = options[i].isset ?
+ options[i].values.ternary_val :
+ ((relopt_ternary *) options[i].gen)->default_val;
+ break;
case RELOPT_TYPE_INT:
*(int *) itempos = options[i].isset ?
options[i].values.int_val :
@@ -1923,8 +2009,8 @@ default_reloptions(Datum reloptions, bool validate, relopt_kind kind)
offsetof(StdRdOptions, parallel_workers)},
{"vacuum_index_cleanup", RELOPT_TYPE_ENUM,
offsetof(StdRdOptions, vacuum_index_cleanup)},
- {"vacuum_truncate", RELOPT_TYPE_BOOL,
- offsetof(StdRdOptions, vacuum_truncate), offsetof(StdRdOptions, vacuum_truncate_set)},
+ {"vacuum_truncate", RELOPT_TYPE_TERNARY,
+ offsetof(StdRdOptions, vacuum_truncate)},
{"vacuum_max_eager_freeze_failure_rate", RELOPT_TYPE_REAL,
offsetof(StdRdOptions, vacuum_max_eager_freeze_failure_rate)}
};
@@ -2004,7 +2090,6 @@ build_local_reloptions(local_relopts *relopts, Datum options, bool validate)
elems[i].optname = opt->option->name;
elems[i].opttype = opt->option->type;
elems[i].offset = opt->offset;
- elems[i].isset_offset = 0; /* not supported for local relopts yet */
i++;
}
diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c
index aa4fbec143f..696eab9bd97 100644
--- a/src/backend/commands/vacuum.c
+++ b/src/backend/commands/vacuum.c
@@ -2224,9 +2224,9 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams params,
{
StdRdOptions *opts = (StdRdOptions *) rel->rd_options;
- if (opts && opts->vacuum_truncate_set)
+ if (opts && opts->vacuum_truncate != TERNARY_UNSET)
{
- if (opts->vacuum_truncate)
+ if (opts->vacuum_truncate == TERNARY_TRUE)
params.truncate = VACOPTVALUE_ENABLED;
else
params.truncate = VACOPTVALUE_DISABLED;
diff --git a/src/include/access/reloptions.h b/src/include/access/reloptions.h
index 2f08e1b0cf0..dfbef2babf2 100644
--- a/src/include/access/reloptions.h
+++ b/src/include/access/reloptions.h
@@ -29,6 +29,7 @@
typedef enum relopt_type
{
RELOPT_TYPE_BOOL,
+ RELOPT_TYPE_TERNARY, /* on, off, unset */
RELOPT_TYPE_INT,
RELOPT_TYPE_REAL,
RELOPT_TYPE_ENUM,
@@ -80,6 +81,7 @@ typedef struct relopt_value
union
{
bool bool_val;
+ pg_ternary ternary_val;
int int_val;
double real_val;
int enum_val;
@@ -94,6 +96,12 @@ typedef struct relopt_bool
bool default_val;
} relopt_bool;
+typedef struct relopt_ternary
+{
+ relopt_gen gen;
+ int default_val;
+} relopt_ternary;
+
typedef struct relopt_int
{
relopt_gen gen;
@@ -152,19 +160,6 @@ typedef struct
const char *optname; /* option's name */
relopt_type opttype; /* option's datatype */
int offset; /* offset of field in result struct */
-
- /*
- * isset_offset is an optional offset of a field in the result struct that
- * stores whether the option is explicitly set for the relation or if it
- * just picked up the default value. In most cases, this can be
- * accomplished by giving the reloption a special out-of-range default
- * value (e.g., some integer reloptions use -2), but this isn't always
- * possible. For example, a Boolean reloption cannot be given an
- * out-of-range default, so we need another way to discover the source of
- * its value. This offset is only used if given a value greater than
- * zero.
- */
- int isset_offset;
} relopt_parse_elt;
/* Local reloption definition */
@@ -195,6 +190,9 @@ typedef struct local_relopts
extern relopt_kind add_reloption_kind(void);
extern void add_bool_reloption(bits32 kinds, const char *name, const char *desc,
bool default_val, LOCKMODE lockmode);
+extern void add_ternary_reloption(bits32 kinds, const char *name,
+ const char *desc, pg_ternary default_val,
+ LOCKMODE lockmode);
extern void add_int_reloption(bits32 kinds, const char *name, const char *desc,
int default_val, int min_val, int max_val,
LOCKMODE lockmode);
@@ -214,6 +212,9 @@ extern void register_reloptions_validator(local_relopts *relopts,
extern void add_local_bool_reloption(local_relopts *relopts, const char *name,
const char *desc, bool default_val,
int offset);
+extern void add_local_ternary_reloption(local_relopts *relopts,
+ const char *name, const char *desc,
+ pg_ternary default_val, int offset);
extern void add_local_int_reloption(local_relopts *relopts, const char *name,
const char *desc, int default_val,
int min_val, int max_val, int offset);
diff --git a/src/include/postgres.h b/src/include/postgres.h
index 7d93fbce709..47c88780776 100644
--- a/src/include/postgres.h
+++ b/src/include/postgres.h
@@ -543,6 +543,21 @@ Float8GetDatum(float8 X)
* ----------------------------------------------------------------
*/
+/*
+ * pg_ternary
+ * Boolean value with an extra "unset" value
+ *
+ * This enum can be used for values that want to distinguish between true,
+ * false, and unset.
+*/
+
+typedef enum pg_ternary
+{
+ TERNARY_FALSE = 0,
+ TERNARY_TRUE = 1,
+ TERNARY_UNSET = -1
+} pg_ternary;
+
/*
* NON_EXEC_STATIC: It's sometimes useful to define a variable or function
* that is normally static but extern when using EXEC_BACKEND (see
diff --git a/src/include/utils/rel.h b/src/include/utils/rel.h
index d03ab247788..5feb18a5373 100644
--- a/src/include/utils/rel.h
+++ b/src/include/utils/rel.h
@@ -347,8 +347,7 @@ typedef struct StdRdOptions
bool user_catalog_table; /* use as an additional catalog relation */
int parallel_workers; /* max number of parallel workers */
StdRdOptIndexCleanup vacuum_index_cleanup; /* controls index vacuuming */
- bool vacuum_truncate; /* enables vacuum to truncate a relation */
- bool vacuum_truncate_set; /* whether vacuum_truncate is set */
+ pg_ternary vacuum_truncate; /* enables vacuum to truncate a relation */
/*
* Fraction of pages in a relation that vacuum can eagerly scan and fail
diff --git a/src/test/regress/expected/reloptions.out b/src/test/regress/expected/reloptions.out
index 9de19b4e3f1..1c99f79ab01 100644
--- a/src/test/regress/expected/reloptions.out
+++ b/src/test/regress/expected/reloptions.out
@@ -98,6 +98,42 @@ SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass;
{fillfactor=13,autovacuum_enabled=false}
(1 row)
+-- Tests for future (FIXME) ternary options
+-- behave as boolean option: accept unassigned name and truncated value
+DROP TABLE reloptions_test;
+CREATE TABLE reloptions_test(i INT) WITH (vacuum_truncate);
+SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass;
+ reloptions
+------------------------
+ {vacuum_truncate=true}
+(1 row)
+
+DROP TABLE reloptions_test;
+CREATE TABLE reloptions_test(i INT) WITH (vacuum_truncate=FaLS);
+SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass;
+ reloptions
+------------------------
+ {vacuum_truncate=fals}
+(1 row)
+
+-- preferred "true" alias is used when storing
+DROP TABLE reloptions_test;
+CREATE TABLE reloptions_test(i INT) WITH (vacuum_index_cleanup=on);
+SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass;
+ reloptions
+---------------------------
+ {vacuum_index_cleanup=on}
+(1 row)
+
+-- custom "third" value is available
+DROP TABLE reloptions_test;
+CREATE TABLE reloptions_test(i INT) WITH (vacuum_index_cleanup=auto);
+SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass;
+ reloptions
+-----------------------------
+ {vacuum_index_cleanup=auto}
+(1 row)
+
-- Test vacuum_truncate option
DROP TABLE reloptions_test;
CREATE TEMP TABLE reloptions_test(i INT NOT NULL, j text)
diff --git a/src/test/regress/sql/reloptions.sql b/src/test/regress/sql/reloptions.sql
index 24fbe0b478d..f5980dafcbc 100644
--- a/src/test/regress/sql/reloptions.sql
+++ b/src/test/regress/sql/reloptions.sql
@@ -59,6 +59,27 @@ UPDATE pg_class
ALTER TABLE reloptions_test RESET (illegal_option);
SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass;
+-- Tests for future (FIXME) ternary options
+
+-- behave as boolean option: accept unassigned name and truncated value
+DROP TABLE reloptions_test;
+CREATE TABLE reloptions_test(i INT) WITH (vacuum_truncate);
+SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass;
+
+DROP TABLE reloptions_test;
+CREATE TABLE reloptions_test(i INT) WITH (vacuum_truncate=FaLS);
+SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass;
+
+-- preferred "true" alias is used when storing
+DROP TABLE reloptions_test;
+CREATE TABLE reloptions_test(i INT) WITH (vacuum_index_cleanup=on);
+SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass;
+
+-- custom "third" value is available
+DROP TABLE reloptions_test;
+CREATE TABLE reloptions_test(i INT) WITH (vacuum_index_cleanup=auto);
+SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass;
+
-- Test vacuum_truncate option
DROP TABLE reloptions_test;
diff --git a/src/tools/pgindent/typedefs.list b/src/tools/pgindent/typedefs.list
index 3f3a888fd0e..1c8610fd46c 100644
--- a/src/tools/pgindent/typedefs.list
+++ b/src/tools/pgindent/typedefs.list
@@ -3952,6 +3952,7 @@ pg_sha512_ctx
pg_snapshot
pg_special_case
pg_stack_base_t
+pg_ternary
pg_time_t
pg_time_usec_t
pg_tz
@@ -4079,6 +4080,7 @@ relopt_kind
relopt_parse_elt
relopt_real
relopt_string
+relopt_ternary
relopt_type
relopt_value
relopts_validator
--
2.47.3
--c36t3fndxhkzbl5z--
^ permalink raw reply [nested|flat] 868+ messages in thread
* [PATCH v3] Introduce ternary reloptions
@ 2026-01-16 15:04 Álvaro Herrera <[email protected]>
0 siblings, 0 replies; 868+ messages in thread
From: Álvaro Herrera @ 2026-01-16 15:04 UTC (permalink / raw)
Introduce ternary reloption as a replacement for current
`vacuum_truncate` implementation. Remove the `vacuum_truncate_set`
separate flag and use TERNARY_UNSET instead.
This could also be used for other options such as `vacuum_index_cleanup`
and `buffering`, but lets get the scaffolding in first.
Discussion: https://postgr.es/m/3474141.usfYGdeWWP@thinkpad-pgpro
---
src/backend/access/common/reloptions.c | 137 ++++++++++++++++++-----
src/backend/commands/vacuum.c | 4 +-
src/include/access/reloptions.h | 27 ++---
src/include/postgres.h | 15 +++
src/include/utils/rel.h | 3 +-
src/test/regress/expected/reloptions.out | 36 ++++++
src/test/regress/sql/reloptions.sql | 21 ++++
src/tools/pgindent/typedefs.list | 2 +
8 files changed, 202 insertions(+), 43 deletions(-)
diff --git a/src/backend/access/common/reloptions.c b/src/backend/access/common/reloptions.c
index 0b83f98ed5f..6f1f577581a 100644
--- a/src/backend/access/common/reloptions.c
+++ b/src/backend/access/common/reloptions.c
@@ -40,9 +40,9 @@
*
* To add an option:
*
- * (i) decide on a type (bool, integer, real, enum, string), name, default
- * value, upper and lower bounds (if applicable); for strings, consider a
- * validation routine.
+ * (i) decide on a type (bool, ternary, integer, real, enum, string), name,
+ * default value, upper and lower bounds (if applicable); for strings,
+ * consider a validation routine.
* (ii) add a record below (or use add_<type>_reloption).
* (iii) add it to the appropriate options struct (perhaps StdRdOptions)
* (iv) add it to the appropriate handling routine (perhaps
@@ -147,15 +147,6 @@ static relopt_bool boolRelOpts[] =
},
false
},
- {
- {
- "vacuum_truncate",
- "Enables vacuum to truncate empty pages at the end of this table",
- RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
- ShareUpdateExclusiveLock
- },
- true
- },
{
{
"deduplicate_items",
@@ -170,6 +161,25 @@ static relopt_bool boolRelOpts[] =
{{NULL}}
};
+static relopt_ternary ternaryRelOpts[] =
+{
+ {
+ {
+ "vacuum_truncate",
+ "Enables vacuum to truncate empty pages at the end of this table",
+ RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
+ ShareUpdateExclusiveLock
+ },
+ TERNARY_UNSET
+ },
+ /* list terminator */
+ {
+ {
+ NULL
+ }
+ }
+};
+
static relopt_int intRelOpts[] =
{
{
@@ -609,6 +619,13 @@ initialize_reloptions(void)
boolRelOpts[i].gen.lockmode));
j++;
}
+ for (i = 0; ternaryRelOpts[i].gen.name; i++)
+ {
+ Assert(DoLockModesConflict(ternaryRelOpts[i].gen.lockmode,
+ ternaryRelOpts[i].gen.lockmode));
+ j++;
+ }
+
for (i = 0; intRelOpts[i].gen.name; i++)
{
Assert(DoLockModesConflict(intRelOpts[i].gen.lockmode,
@@ -649,6 +666,14 @@ initialize_reloptions(void)
j++;
}
+ for (i = 0; ternaryRelOpts[i].gen.name; i++)
+ {
+ relOpts[j] = &ternaryRelOpts[i].gen;
+ relOpts[j]->type = RELOPT_TYPE_TERNARY;
+ relOpts[j]->namelen = strlen(relOpts[j]->name);
+ j++;
+ }
+
for (i = 0; intRelOpts[i].gen.name; i++)
{
relOpts[j] = &intRelOpts[i].gen;
@@ -809,6 +834,9 @@ allocate_reloption(bits32 kinds, int type, const char *name, const char *desc,
case RELOPT_TYPE_BOOL:
size = sizeof(relopt_bool);
break;
+ case RELOPT_TYPE_TERNARY:
+ size = sizeof(relopt_ternary);
+ break;
case RELOPT_TYPE_INT:
size = sizeof(relopt_int);
break;
@@ -892,6 +920,57 @@ add_local_bool_reloption(local_relopts *relopts, const char *name,
add_local_reloption(relopts, (relopt_gen *) newoption, offset);
}
+/*
+ * init_ternary_reloption
+ * Allocate and initialize a new ternary reloption
+ */
+static relopt_ternary *
+init_ternary_reloption(bits32 kinds, const char *name, const char *desc,
+ pg_ternary default_val, LOCKMODE lockmode)
+{
+ relopt_ternary *newoption;
+
+ newoption = (relopt_ternary *)
+ allocate_reloption(kinds, RELOPT_TYPE_TERNARY, name, desc, lockmode);
+ newoption->default_val = default_val;
+
+ return newoption;
+}
+
+/*
+ * add_ternary_reloption
+ * Add a new ternary reloption
+ */
+void
+add_ternary_reloption(bits32 kinds, const char *name, const char *desc,
+ pg_ternary default_val, LOCKMODE lockmode)
+{
+ relopt_ternary *newoption;
+
+ newoption =
+ init_ternary_reloption(kinds, name, desc, default_val, lockmode);
+
+ add_reloption((relopt_gen *) newoption);
+}
+
+/*
+ * add_local_ternary_reloption
+ * Add a new ternary local reloption
+ *
+ * 'offset' is offset of ternary-typed field.
+ */
+void
+add_local_ternary_reloption(local_relopts *relopts, const char *name,
+ const char *desc, pg_ternary default_val,
+ int offset)
+{
+ relopt_ternary *newoption;
+
+ newoption =
+ init_ternary_reloption(RELOPT_KIND_LOCAL, name, desc, default_val, 0);
+
+ add_local_reloption(relopts, (relopt_gen *) newoption, offset);
+}
/*
* init_real_reloption
@@ -1626,6 +1705,19 @@ parse_one_reloption(relopt_value *option, char *text_str, int text_len,
option->gen->name, value)));
}
break;
+ case RELOPT_TYPE_TERNARY:
+ {
+ bool b;
+
+ parsed = parse_bool(value, &b);
+ option->values.ternary_val = b ? TERNARY_TRUE : TERNARY_FALSE;
+ if (validate && !parsed)
+ ereport(ERROR,
+ (errcode(ERRCODE_INVALID_PARAMETER_VALUE),
+ errmsg("invalid value for ternary option \"%s\": %s",
+ option->gen->name, value)));
+ }
+ break;
case RELOPT_TYPE_INT:
{
relopt_int *optint = (relopt_int *) option->gen;
@@ -1789,17 +1881,6 @@ fillRelOptions(void *rdopts, Size basesize,
char *itempos = ((char *) rdopts) + elems[j].offset;
char *string_val;
- /*
- * If isset_offset is provided, store whether the reloption is
- * set there.
- */
- if (elems[j].isset_offset > 0)
- {
- char *setpos = ((char *) rdopts) + elems[j].isset_offset;
-
- *(bool *) setpos = options[i].isset;
- }
-
switch (options[i].gen->type)
{
case RELOPT_TYPE_BOOL:
@@ -1807,6 +1888,11 @@ fillRelOptions(void *rdopts, Size basesize,
options[i].values.bool_val :
((relopt_bool *) options[i].gen)->default_val;
break;
+ case RELOPT_TYPE_TERNARY:
+ *(pg_ternary *) itempos = options[i].isset ?
+ options[i].values.ternary_val :
+ ((relopt_ternary *) options[i].gen)->default_val;
+ break;
case RELOPT_TYPE_INT:
*(int *) itempos = options[i].isset ?
options[i].values.int_val :
@@ -1923,8 +2009,8 @@ default_reloptions(Datum reloptions, bool validate, relopt_kind kind)
offsetof(StdRdOptions, parallel_workers)},
{"vacuum_index_cleanup", RELOPT_TYPE_ENUM,
offsetof(StdRdOptions, vacuum_index_cleanup)},
- {"vacuum_truncate", RELOPT_TYPE_BOOL,
- offsetof(StdRdOptions, vacuum_truncate), offsetof(StdRdOptions, vacuum_truncate_set)},
+ {"vacuum_truncate", RELOPT_TYPE_TERNARY,
+ offsetof(StdRdOptions, vacuum_truncate)},
{"vacuum_max_eager_freeze_failure_rate", RELOPT_TYPE_REAL,
offsetof(StdRdOptions, vacuum_max_eager_freeze_failure_rate)}
};
@@ -2004,7 +2090,6 @@ build_local_reloptions(local_relopts *relopts, Datum options, bool validate)
elems[i].optname = opt->option->name;
elems[i].opttype = opt->option->type;
elems[i].offset = opt->offset;
- elems[i].isset_offset = 0; /* not supported for local relopts yet */
i++;
}
diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c
index aa4fbec143f..696eab9bd97 100644
--- a/src/backend/commands/vacuum.c
+++ b/src/backend/commands/vacuum.c
@@ -2224,9 +2224,9 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams params,
{
StdRdOptions *opts = (StdRdOptions *) rel->rd_options;
- if (opts && opts->vacuum_truncate_set)
+ if (opts && opts->vacuum_truncate != TERNARY_UNSET)
{
- if (opts->vacuum_truncate)
+ if (opts->vacuum_truncate == TERNARY_TRUE)
params.truncate = VACOPTVALUE_ENABLED;
else
params.truncate = VACOPTVALUE_DISABLED;
diff --git a/src/include/access/reloptions.h b/src/include/access/reloptions.h
index 2f08e1b0cf0..dfbef2babf2 100644
--- a/src/include/access/reloptions.h
+++ b/src/include/access/reloptions.h
@@ -29,6 +29,7 @@
typedef enum relopt_type
{
RELOPT_TYPE_BOOL,
+ RELOPT_TYPE_TERNARY, /* on, off, unset */
RELOPT_TYPE_INT,
RELOPT_TYPE_REAL,
RELOPT_TYPE_ENUM,
@@ -80,6 +81,7 @@ typedef struct relopt_value
union
{
bool bool_val;
+ pg_ternary ternary_val;
int int_val;
double real_val;
int enum_val;
@@ -94,6 +96,12 @@ typedef struct relopt_bool
bool default_val;
} relopt_bool;
+typedef struct relopt_ternary
+{
+ relopt_gen gen;
+ int default_val;
+} relopt_ternary;
+
typedef struct relopt_int
{
relopt_gen gen;
@@ -152,19 +160,6 @@ typedef struct
const char *optname; /* option's name */
relopt_type opttype; /* option's datatype */
int offset; /* offset of field in result struct */
-
- /*
- * isset_offset is an optional offset of a field in the result struct that
- * stores whether the option is explicitly set for the relation or if it
- * just picked up the default value. In most cases, this can be
- * accomplished by giving the reloption a special out-of-range default
- * value (e.g., some integer reloptions use -2), but this isn't always
- * possible. For example, a Boolean reloption cannot be given an
- * out-of-range default, so we need another way to discover the source of
- * its value. This offset is only used if given a value greater than
- * zero.
- */
- int isset_offset;
} relopt_parse_elt;
/* Local reloption definition */
@@ -195,6 +190,9 @@ typedef struct local_relopts
extern relopt_kind add_reloption_kind(void);
extern void add_bool_reloption(bits32 kinds, const char *name, const char *desc,
bool default_val, LOCKMODE lockmode);
+extern void add_ternary_reloption(bits32 kinds, const char *name,
+ const char *desc, pg_ternary default_val,
+ LOCKMODE lockmode);
extern void add_int_reloption(bits32 kinds, const char *name, const char *desc,
int default_val, int min_val, int max_val,
LOCKMODE lockmode);
@@ -214,6 +212,9 @@ extern void register_reloptions_validator(local_relopts *relopts,
extern void add_local_bool_reloption(local_relopts *relopts, const char *name,
const char *desc, bool default_val,
int offset);
+extern void add_local_ternary_reloption(local_relopts *relopts,
+ const char *name, const char *desc,
+ pg_ternary default_val, int offset);
extern void add_local_int_reloption(local_relopts *relopts, const char *name,
const char *desc, int default_val,
int min_val, int max_val, int offset);
diff --git a/src/include/postgres.h b/src/include/postgres.h
index 7d93fbce709..47c88780776 100644
--- a/src/include/postgres.h
+++ b/src/include/postgres.h
@@ -543,6 +543,21 @@ Float8GetDatum(float8 X)
* ----------------------------------------------------------------
*/
+/*
+ * pg_ternary
+ * Boolean value with an extra "unset" value
+ *
+ * This enum can be used for values that want to distinguish between true,
+ * false, and unset.
+*/
+
+typedef enum pg_ternary
+{
+ TERNARY_FALSE = 0,
+ TERNARY_TRUE = 1,
+ TERNARY_UNSET = -1
+} pg_ternary;
+
/*
* NON_EXEC_STATIC: It's sometimes useful to define a variable or function
* that is normally static but extern when using EXEC_BACKEND (see
diff --git a/src/include/utils/rel.h b/src/include/utils/rel.h
index d03ab247788..5feb18a5373 100644
--- a/src/include/utils/rel.h
+++ b/src/include/utils/rel.h
@@ -347,8 +347,7 @@ typedef struct StdRdOptions
bool user_catalog_table; /* use as an additional catalog relation */
int parallel_workers; /* max number of parallel workers */
StdRdOptIndexCleanup vacuum_index_cleanup; /* controls index vacuuming */
- bool vacuum_truncate; /* enables vacuum to truncate a relation */
- bool vacuum_truncate_set; /* whether vacuum_truncate is set */
+ pg_ternary vacuum_truncate; /* enables vacuum to truncate a relation */
/*
* Fraction of pages in a relation that vacuum can eagerly scan and fail
diff --git a/src/test/regress/expected/reloptions.out b/src/test/regress/expected/reloptions.out
index 9de19b4e3f1..1c99f79ab01 100644
--- a/src/test/regress/expected/reloptions.out
+++ b/src/test/regress/expected/reloptions.out
@@ -98,6 +98,42 @@ SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass;
{fillfactor=13,autovacuum_enabled=false}
(1 row)
+-- Tests for future (FIXME) ternary options
+-- behave as boolean option: accept unassigned name and truncated value
+DROP TABLE reloptions_test;
+CREATE TABLE reloptions_test(i INT) WITH (vacuum_truncate);
+SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass;
+ reloptions
+------------------------
+ {vacuum_truncate=true}
+(1 row)
+
+DROP TABLE reloptions_test;
+CREATE TABLE reloptions_test(i INT) WITH (vacuum_truncate=FaLS);
+SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass;
+ reloptions
+------------------------
+ {vacuum_truncate=fals}
+(1 row)
+
+-- preferred "true" alias is used when storing
+DROP TABLE reloptions_test;
+CREATE TABLE reloptions_test(i INT) WITH (vacuum_index_cleanup=on);
+SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass;
+ reloptions
+---------------------------
+ {vacuum_index_cleanup=on}
+(1 row)
+
+-- custom "third" value is available
+DROP TABLE reloptions_test;
+CREATE TABLE reloptions_test(i INT) WITH (vacuum_index_cleanup=auto);
+SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass;
+ reloptions
+-----------------------------
+ {vacuum_index_cleanup=auto}
+(1 row)
+
-- Test vacuum_truncate option
DROP TABLE reloptions_test;
CREATE TEMP TABLE reloptions_test(i INT NOT NULL, j text)
diff --git a/src/test/regress/sql/reloptions.sql b/src/test/regress/sql/reloptions.sql
index 24fbe0b478d..f5980dafcbc 100644
--- a/src/test/regress/sql/reloptions.sql
+++ b/src/test/regress/sql/reloptions.sql
@@ -59,6 +59,27 @@ UPDATE pg_class
ALTER TABLE reloptions_test RESET (illegal_option);
SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass;
+-- Tests for future (FIXME) ternary options
+
+-- behave as boolean option: accept unassigned name and truncated value
+DROP TABLE reloptions_test;
+CREATE TABLE reloptions_test(i INT) WITH (vacuum_truncate);
+SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass;
+
+DROP TABLE reloptions_test;
+CREATE TABLE reloptions_test(i INT) WITH (vacuum_truncate=FaLS);
+SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass;
+
+-- preferred "true" alias is used when storing
+DROP TABLE reloptions_test;
+CREATE TABLE reloptions_test(i INT) WITH (vacuum_index_cleanup=on);
+SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass;
+
+-- custom "third" value is available
+DROP TABLE reloptions_test;
+CREATE TABLE reloptions_test(i INT) WITH (vacuum_index_cleanup=auto);
+SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass;
+
-- Test vacuum_truncate option
DROP TABLE reloptions_test;
diff --git a/src/tools/pgindent/typedefs.list b/src/tools/pgindent/typedefs.list
index 3f3a888fd0e..1c8610fd46c 100644
--- a/src/tools/pgindent/typedefs.list
+++ b/src/tools/pgindent/typedefs.list
@@ -3952,6 +3952,7 @@ pg_sha512_ctx
pg_snapshot
pg_special_case
pg_stack_base_t
+pg_ternary
pg_time_t
pg_time_usec_t
pg_tz
@@ -4079,6 +4080,7 @@ relopt_kind
relopt_parse_elt
relopt_real
relopt_string
+relopt_ternary
relopt_type
relopt_value
relopts_validator
--
2.47.3
--c36t3fndxhkzbl5z--
^ permalink raw reply [nested|flat] 868+ messages in thread
* [PATCH v3] Introduce ternary reloptions
@ 2026-01-16 15:04 Álvaro Herrera <[email protected]>
0 siblings, 0 replies; 868+ messages in thread
From: Álvaro Herrera @ 2026-01-16 15:04 UTC (permalink / raw)
Introduce ternary reloption as a replacement for current
`vacuum_truncate` implementation. Remove the `vacuum_truncate_set`
separate flag and use TERNARY_UNSET instead.
This could also be used for other options such as `vacuum_index_cleanup`
and `buffering`, but lets get the scaffolding in first.
Discussion: https://postgr.es/m/3474141.usfYGdeWWP@thinkpad-pgpro
---
src/backend/access/common/reloptions.c | 137 ++++++++++++++++++-----
src/backend/commands/vacuum.c | 4 +-
src/include/access/reloptions.h | 27 ++---
src/include/postgres.h | 15 +++
src/include/utils/rel.h | 3 +-
src/test/regress/expected/reloptions.out | 36 ++++++
src/test/regress/sql/reloptions.sql | 21 ++++
src/tools/pgindent/typedefs.list | 2 +
8 files changed, 202 insertions(+), 43 deletions(-)
diff --git a/src/backend/access/common/reloptions.c b/src/backend/access/common/reloptions.c
index 0b83f98ed5f..6f1f577581a 100644
--- a/src/backend/access/common/reloptions.c
+++ b/src/backend/access/common/reloptions.c
@@ -40,9 +40,9 @@
*
* To add an option:
*
- * (i) decide on a type (bool, integer, real, enum, string), name, default
- * value, upper and lower bounds (if applicable); for strings, consider a
- * validation routine.
+ * (i) decide on a type (bool, ternary, integer, real, enum, string), name,
+ * default value, upper and lower bounds (if applicable); for strings,
+ * consider a validation routine.
* (ii) add a record below (or use add_<type>_reloption).
* (iii) add it to the appropriate options struct (perhaps StdRdOptions)
* (iv) add it to the appropriate handling routine (perhaps
@@ -147,15 +147,6 @@ static relopt_bool boolRelOpts[] =
},
false
},
- {
- {
- "vacuum_truncate",
- "Enables vacuum to truncate empty pages at the end of this table",
- RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
- ShareUpdateExclusiveLock
- },
- true
- },
{
{
"deduplicate_items",
@@ -170,6 +161,25 @@ static relopt_bool boolRelOpts[] =
{{NULL}}
};
+static relopt_ternary ternaryRelOpts[] =
+{
+ {
+ {
+ "vacuum_truncate",
+ "Enables vacuum to truncate empty pages at the end of this table",
+ RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
+ ShareUpdateExclusiveLock
+ },
+ TERNARY_UNSET
+ },
+ /* list terminator */
+ {
+ {
+ NULL
+ }
+ }
+};
+
static relopt_int intRelOpts[] =
{
{
@@ -609,6 +619,13 @@ initialize_reloptions(void)
boolRelOpts[i].gen.lockmode));
j++;
}
+ for (i = 0; ternaryRelOpts[i].gen.name; i++)
+ {
+ Assert(DoLockModesConflict(ternaryRelOpts[i].gen.lockmode,
+ ternaryRelOpts[i].gen.lockmode));
+ j++;
+ }
+
for (i = 0; intRelOpts[i].gen.name; i++)
{
Assert(DoLockModesConflict(intRelOpts[i].gen.lockmode,
@@ -649,6 +666,14 @@ initialize_reloptions(void)
j++;
}
+ for (i = 0; ternaryRelOpts[i].gen.name; i++)
+ {
+ relOpts[j] = &ternaryRelOpts[i].gen;
+ relOpts[j]->type = RELOPT_TYPE_TERNARY;
+ relOpts[j]->namelen = strlen(relOpts[j]->name);
+ j++;
+ }
+
for (i = 0; intRelOpts[i].gen.name; i++)
{
relOpts[j] = &intRelOpts[i].gen;
@@ -809,6 +834,9 @@ allocate_reloption(bits32 kinds, int type, const char *name, const char *desc,
case RELOPT_TYPE_BOOL:
size = sizeof(relopt_bool);
break;
+ case RELOPT_TYPE_TERNARY:
+ size = sizeof(relopt_ternary);
+ break;
case RELOPT_TYPE_INT:
size = sizeof(relopt_int);
break;
@@ -892,6 +920,57 @@ add_local_bool_reloption(local_relopts *relopts, const char *name,
add_local_reloption(relopts, (relopt_gen *) newoption, offset);
}
+/*
+ * init_ternary_reloption
+ * Allocate and initialize a new ternary reloption
+ */
+static relopt_ternary *
+init_ternary_reloption(bits32 kinds, const char *name, const char *desc,
+ pg_ternary default_val, LOCKMODE lockmode)
+{
+ relopt_ternary *newoption;
+
+ newoption = (relopt_ternary *)
+ allocate_reloption(kinds, RELOPT_TYPE_TERNARY, name, desc, lockmode);
+ newoption->default_val = default_val;
+
+ return newoption;
+}
+
+/*
+ * add_ternary_reloption
+ * Add a new ternary reloption
+ */
+void
+add_ternary_reloption(bits32 kinds, const char *name, const char *desc,
+ pg_ternary default_val, LOCKMODE lockmode)
+{
+ relopt_ternary *newoption;
+
+ newoption =
+ init_ternary_reloption(kinds, name, desc, default_val, lockmode);
+
+ add_reloption((relopt_gen *) newoption);
+}
+
+/*
+ * add_local_ternary_reloption
+ * Add a new ternary local reloption
+ *
+ * 'offset' is offset of ternary-typed field.
+ */
+void
+add_local_ternary_reloption(local_relopts *relopts, const char *name,
+ const char *desc, pg_ternary default_val,
+ int offset)
+{
+ relopt_ternary *newoption;
+
+ newoption =
+ init_ternary_reloption(RELOPT_KIND_LOCAL, name, desc, default_val, 0);
+
+ add_local_reloption(relopts, (relopt_gen *) newoption, offset);
+}
/*
* init_real_reloption
@@ -1626,6 +1705,19 @@ parse_one_reloption(relopt_value *option, char *text_str, int text_len,
option->gen->name, value)));
}
break;
+ case RELOPT_TYPE_TERNARY:
+ {
+ bool b;
+
+ parsed = parse_bool(value, &b);
+ option->values.ternary_val = b ? TERNARY_TRUE : TERNARY_FALSE;
+ if (validate && !parsed)
+ ereport(ERROR,
+ (errcode(ERRCODE_INVALID_PARAMETER_VALUE),
+ errmsg("invalid value for ternary option \"%s\": %s",
+ option->gen->name, value)));
+ }
+ break;
case RELOPT_TYPE_INT:
{
relopt_int *optint = (relopt_int *) option->gen;
@@ -1789,17 +1881,6 @@ fillRelOptions(void *rdopts, Size basesize,
char *itempos = ((char *) rdopts) + elems[j].offset;
char *string_val;
- /*
- * If isset_offset is provided, store whether the reloption is
- * set there.
- */
- if (elems[j].isset_offset > 0)
- {
- char *setpos = ((char *) rdopts) + elems[j].isset_offset;
-
- *(bool *) setpos = options[i].isset;
- }
-
switch (options[i].gen->type)
{
case RELOPT_TYPE_BOOL:
@@ -1807,6 +1888,11 @@ fillRelOptions(void *rdopts, Size basesize,
options[i].values.bool_val :
((relopt_bool *) options[i].gen)->default_val;
break;
+ case RELOPT_TYPE_TERNARY:
+ *(pg_ternary *) itempos = options[i].isset ?
+ options[i].values.ternary_val :
+ ((relopt_ternary *) options[i].gen)->default_val;
+ break;
case RELOPT_TYPE_INT:
*(int *) itempos = options[i].isset ?
options[i].values.int_val :
@@ -1923,8 +2009,8 @@ default_reloptions(Datum reloptions, bool validate, relopt_kind kind)
offsetof(StdRdOptions, parallel_workers)},
{"vacuum_index_cleanup", RELOPT_TYPE_ENUM,
offsetof(StdRdOptions, vacuum_index_cleanup)},
- {"vacuum_truncate", RELOPT_TYPE_BOOL,
- offsetof(StdRdOptions, vacuum_truncate), offsetof(StdRdOptions, vacuum_truncate_set)},
+ {"vacuum_truncate", RELOPT_TYPE_TERNARY,
+ offsetof(StdRdOptions, vacuum_truncate)},
{"vacuum_max_eager_freeze_failure_rate", RELOPT_TYPE_REAL,
offsetof(StdRdOptions, vacuum_max_eager_freeze_failure_rate)}
};
@@ -2004,7 +2090,6 @@ build_local_reloptions(local_relopts *relopts, Datum options, bool validate)
elems[i].optname = opt->option->name;
elems[i].opttype = opt->option->type;
elems[i].offset = opt->offset;
- elems[i].isset_offset = 0; /* not supported for local relopts yet */
i++;
}
diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c
index aa4fbec143f..696eab9bd97 100644
--- a/src/backend/commands/vacuum.c
+++ b/src/backend/commands/vacuum.c
@@ -2224,9 +2224,9 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams params,
{
StdRdOptions *opts = (StdRdOptions *) rel->rd_options;
- if (opts && opts->vacuum_truncate_set)
+ if (opts && opts->vacuum_truncate != TERNARY_UNSET)
{
- if (opts->vacuum_truncate)
+ if (opts->vacuum_truncate == TERNARY_TRUE)
params.truncate = VACOPTVALUE_ENABLED;
else
params.truncate = VACOPTVALUE_DISABLED;
diff --git a/src/include/access/reloptions.h b/src/include/access/reloptions.h
index 2f08e1b0cf0..dfbef2babf2 100644
--- a/src/include/access/reloptions.h
+++ b/src/include/access/reloptions.h
@@ -29,6 +29,7 @@
typedef enum relopt_type
{
RELOPT_TYPE_BOOL,
+ RELOPT_TYPE_TERNARY, /* on, off, unset */
RELOPT_TYPE_INT,
RELOPT_TYPE_REAL,
RELOPT_TYPE_ENUM,
@@ -80,6 +81,7 @@ typedef struct relopt_value
union
{
bool bool_val;
+ pg_ternary ternary_val;
int int_val;
double real_val;
int enum_val;
@@ -94,6 +96,12 @@ typedef struct relopt_bool
bool default_val;
} relopt_bool;
+typedef struct relopt_ternary
+{
+ relopt_gen gen;
+ int default_val;
+} relopt_ternary;
+
typedef struct relopt_int
{
relopt_gen gen;
@@ -152,19 +160,6 @@ typedef struct
const char *optname; /* option's name */
relopt_type opttype; /* option's datatype */
int offset; /* offset of field in result struct */
-
- /*
- * isset_offset is an optional offset of a field in the result struct that
- * stores whether the option is explicitly set for the relation or if it
- * just picked up the default value. In most cases, this can be
- * accomplished by giving the reloption a special out-of-range default
- * value (e.g., some integer reloptions use -2), but this isn't always
- * possible. For example, a Boolean reloption cannot be given an
- * out-of-range default, so we need another way to discover the source of
- * its value. This offset is only used if given a value greater than
- * zero.
- */
- int isset_offset;
} relopt_parse_elt;
/* Local reloption definition */
@@ -195,6 +190,9 @@ typedef struct local_relopts
extern relopt_kind add_reloption_kind(void);
extern void add_bool_reloption(bits32 kinds, const char *name, const char *desc,
bool default_val, LOCKMODE lockmode);
+extern void add_ternary_reloption(bits32 kinds, const char *name,
+ const char *desc, pg_ternary default_val,
+ LOCKMODE lockmode);
extern void add_int_reloption(bits32 kinds, const char *name, const char *desc,
int default_val, int min_val, int max_val,
LOCKMODE lockmode);
@@ -214,6 +212,9 @@ extern void register_reloptions_validator(local_relopts *relopts,
extern void add_local_bool_reloption(local_relopts *relopts, const char *name,
const char *desc, bool default_val,
int offset);
+extern void add_local_ternary_reloption(local_relopts *relopts,
+ const char *name, const char *desc,
+ pg_ternary default_val, int offset);
extern void add_local_int_reloption(local_relopts *relopts, const char *name,
const char *desc, int default_val,
int min_val, int max_val, int offset);
diff --git a/src/include/postgres.h b/src/include/postgres.h
index 7d93fbce709..47c88780776 100644
--- a/src/include/postgres.h
+++ b/src/include/postgres.h
@@ -543,6 +543,21 @@ Float8GetDatum(float8 X)
* ----------------------------------------------------------------
*/
+/*
+ * pg_ternary
+ * Boolean value with an extra "unset" value
+ *
+ * This enum can be used for values that want to distinguish between true,
+ * false, and unset.
+*/
+
+typedef enum pg_ternary
+{
+ TERNARY_FALSE = 0,
+ TERNARY_TRUE = 1,
+ TERNARY_UNSET = -1
+} pg_ternary;
+
/*
* NON_EXEC_STATIC: It's sometimes useful to define a variable or function
* that is normally static but extern when using EXEC_BACKEND (see
diff --git a/src/include/utils/rel.h b/src/include/utils/rel.h
index d03ab247788..5feb18a5373 100644
--- a/src/include/utils/rel.h
+++ b/src/include/utils/rel.h
@@ -347,8 +347,7 @@ typedef struct StdRdOptions
bool user_catalog_table; /* use as an additional catalog relation */
int parallel_workers; /* max number of parallel workers */
StdRdOptIndexCleanup vacuum_index_cleanup; /* controls index vacuuming */
- bool vacuum_truncate; /* enables vacuum to truncate a relation */
- bool vacuum_truncate_set; /* whether vacuum_truncate is set */
+ pg_ternary vacuum_truncate; /* enables vacuum to truncate a relation */
/*
* Fraction of pages in a relation that vacuum can eagerly scan and fail
diff --git a/src/test/regress/expected/reloptions.out b/src/test/regress/expected/reloptions.out
index 9de19b4e3f1..1c99f79ab01 100644
--- a/src/test/regress/expected/reloptions.out
+++ b/src/test/regress/expected/reloptions.out
@@ -98,6 +98,42 @@ SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass;
{fillfactor=13,autovacuum_enabled=false}
(1 row)
+-- Tests for future (FIXME) ternary options
+-- behave as boolean option: accept unassigned name and truncated value
+DROP TABLE reloptions_test;
+CREATE TABLE reloptions_test(i INT) WITH (vacuum_truncate);
+SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass;
+ reloptions
+------------------------
+ {vacuum_truncate=true}
+(1 row)
+
+DROP TABLE reloptions_test;
+CREATE TABLE reloptions_test(i INT) WITH (vacuum_truncate=FaLS);
+SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass;
+ reloptions
+------------------------
+ {vacuum_truncate=fals}
+(1 row)
+
+-- preferred "true" alias is used when storing
+DROP TABLE reloptions_test;
+CREATE TABLE reloptions_test(i INT) WITH (vacuum_index_cleanup=on);
+SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass;
+ reloptions
+---------------------------
+ {vacuum_index_cleanup=on}
+(1 row)
+
+-- custom "third" value is available
+DROP TABLE reloptions_test;
+CREATE TABLE reloptions_test(i INT) WITH (vacuum_index_cleanup=auto);
+SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass;
+ reloptions
+-----------------------------
+ {vacuum_index_cleanup=auto}
+(1 row)
+
-- Test vacuum_truncate option
DROP TABLE reloptions_test;
CREATE TEMP TABLE reloptions_test(i INT NOT NULL, j text)
diff --git a/src/test/regress/sql/reloptions.sql b/src/test/regress/sql/reloptions.sql
index 24fbe0b478d..f5980dafcbc 100644
--- a/src/test/regress/sql/reloptions.sql
+++ b/src/test/regress/sql/reloptions.sql
@@ -59,6 +59,27 @@ UPDATE pg_class
ALTER TABLE reloptions_test RESET (illegal_option);
SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass;
+-- Tests for future (FIXME) ternary options
+
+-- behave as boolean option: accept unassigned name and truncated value
+DROP TABLE reloptions_test;
+CREATE TABLE reloptions_test(i INT) WITH (vacuum_truncate);
+SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass;
+
+DROP TABLE reloptions_test;
+CREATE TABLE reloptions_test(i INT) WITH (vacuum_truncate=FaLS);
+SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass;
+
+-- preferred "true" alias is used when storing
+DROP TABLE reloptions_test;
+CREATE TABLE reloptions_test(i INT) WITH (vacuum_index_cleanup=on);
+SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass;
+
+-- custom "third" value is available
+DROP TABLE reloptions_test;
+CREATE TABLE reloptions_test(i INT) WITH (vacuum_index_cleanup=auto);
+SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass;
+
-- Test vacuum_truncate option
DROP TABLE reloptions_test;
diff --git a/src/tools/pgindent/typedefs.list b/src/tools/pgindent/typedefs.list
index 3f3a888fd0e..1c8610fd46c 100644
--- a/src/tools/pgindent/typedefs.list
+++ b/src/tools/pgindent/typedefs.list
@@ -3952,6 +3952,7 @@ pg_sha512_ctx
pg_snapshot
pg_special_case
pg_stack_base_t
+pg_ternary
pg_time_t
pg_time_usec_t
pg_tz
@@ -4079,6 +4080,7 @@ relopt_kind
relopt_parse_elt
relopt_real
relopt_string
+relopt_ternary
relopt_type
relopt_value
relopts_validator
--
2.47.3
--c36t3fndxhkzbl5z--
^ permalink raw reply [nested|flat] 868+ messages in thread
* [PATCH v3] Introduce ternary reloptions
@ 2026-01-16 15:04 Álvaro Herrera <[email protected]>
0 siblings, 0 replies; 868+ messages in thread
From: Álvaro Herrera @ 2026-01-16 15:04 UTC (permalink / raw)
Introduce ternary reloption as a replacement for current
`vacuum_truncate` implementation. Remove the `vacuum_truncate_set`
separate flag and use TERNARY_UNSET instead.
This could also be used for other options such as `vacuum_index_cleanup`
and `buffering`, but lets get the scaffolding in first.
Discussion: https://postgr.es/m/3474141.usfYGdeWWP@thinkpad-pgpro
---
src/backend/access/common/reloptions.c | 137 ++++++++++++++++++-----
src/backend/commands/vacuum.c | 4 +-
src/include/access/reloptions.h | 27 ++---
src/include/postgres.h | 15 +++
src/include/utils/rel.h | 3 +-
src/test/regress/expected/reloptions.out | 36 ++++++
src/test/regress/sql/reloptions.sql | 21 ++++
src/tools/pgindent/typedefs.list | 2 +
8 files changed, 202 insertions(+), 43 deletions(-)
diff --git a/src/backend/access/common/reloptions.c b/src/backend/access/common/reloptions.c
index 0b83f98ed5f..6f1f577581a 100644
--- a/src/backend/access/common/reloptions.c
+++ b/src/backend/access/common/reloptions.c
@@ -40,9 +40,9 @@
*
* To add an option:
*
- * (i) decide on a type (bool, integer, real, enum, string), name, default
- * value, upper and lower bounds (if applicable); for strings, consider a
- * validation routine.
+ * (i) decide on a type (bool, ternary, integer, real, enum, string), name,
+ * default value, upper and lower bounds (if applicable); for strings,
+ * consider a validation routine.
* (ii) add a record below (or use add_<type>_reloption).
* (iii) add it to the appropriate options struct (perhaps StdRdOptions)
* (iv) add it to the appropriate handling routine (perhaps
@@ -147,15 +147,6 @@ static relopt_bool boolRelOpts[] =
},
false
},
- {
- {
- "vacuum_truncate",
- "Enables vacuum to truncate empty pages at the end of this table",
- RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
- ShareUpdateExclusiveLock
- },
- true
- },
{
{
"deduplicate_items",
@@ -170,6 +161,25 @@ static relopt_bool boolRelOpts[] =
{{NULL}}
};
+static relopt_ternary ternaryRelOpts[] =
+{
+ {
+ {
+ "vacuum_truncate",
+ "Enables vacuum to truncate empty pages at the end of this table",
+ RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
+ ShareUpdateExclusiveLock
+ },
+ TERNARY_UNSET
+ },
+ /* list terminator */
+ {
+ {
+ NULL
+ }
+ }
+};
+
static relopt_int intRelOpts[] =
{
{
@@ -609,6 +619,13 @@ initialize_reloptions(void)
boolRelOpts[i].gen.lockmode));
j++;
}
+ for (i = 0; ternaryRelOpts[i].gen.name; i++)
+ {
+ Assert(DoLockModesConflict(ternaryRelOpts[i].gen.lockmode,
+ ternaryRelOpts[i].gen.lockmode));
+ j++;
+ }
+
for (i = 0; intRelOpts[i].gen.name; i++)
{
Assert(DoLockModesConflict(intRelOpts[i].gen.lockmode,
@@ -649,6 +666,14 @@ initialize_reloptions(void)
j++;
}
+ for (i = 0; ternaryRelOpts[i].gen.name; i++)
+ {
+ relOpts[j] = &ternaryRelOpts[i].gen;
+ relOpts[j]->type = RELOPT_TYPE_TERNARY;
+ relOpts[j]->namelen = strlen(relOpts[j]->name);
+ j++;
+ }
+
for (i = 0; intRelOpts[i].gen.name; i++)
{
relOpts[j] = &intRelOpts[i].gen;
@@ -809,6 +834,9 @@ allocate_reloption(bits32 kinds, int type, const char *name, const char *desc,
case RELOPT_TYPE_BOOL:
size = sizeof(relopt_bool);
break;
+ case RELOPT_TYPE_TERNARY:
+ size = sizeof(relopt_ternary);
+ break;
case RELOPT_TYPE_INT:
size = sizeof(relopt_int);
break;
@@ -892,6 +920,57 @@ add_local_bool_reloption(local_relopts *relopts, const char *name,
add_local_reloption(relopts, (relopt_gen *) newoption, offset);
}
+/*
+ * init_ternary_reloption
+ * Allocate and initialize a new ternary reloption
+ */
+static relopt_ternary *
+init_ternary_reloption(bits32 kinds, const char *name, const char *desc,
+ pg_ternary default_val, LOCKMODE lockmode)
+{
+ relopt_ternary *newoption;
+
+ newoption = (relopt_ternary *)
+ allocate_reloption(kinds, RELOPT_TYPE_TERNARY, name, desc, lockmode);
+ newoption->default_val = default_val;
+
+ return newoption;
+}
+
+/*
+ * add_ternary_reloption
+ * Add a new ternary reloption
+ */
+void
+add_ternary_reloption(bits32 kinds, const char *name, const char *desc,
+ pg_ternary default_val, LOCKMODE lockmode)
+{
+ relopt_ternary *newoption;
+
+ newoption =
+ init_ternary_reloption(kinds, name, desc, default_val, lockmode);
+
+ add_reloption((relopt_gen *) newoption);
+}
+
+/*
+ * add_local_ternary_reloption
+ * Add a new ternary local reloption
+ *
+ * 'offset' is offset of ternary-typed field.
+ */
+void
+add_local_ternary_reloption(local_relopts *relopts, const char *name,
+ const char *desc, pg_ternary default_val,
+ int offset)
+{
+ relopt_ternary *newoption;
+
+ newoption =
+ init_ternary_reloption(RELOPT_KIND_LOCAL, name, desc, default_val, 0);
+
+ add_local_reloption(relopts, (relopt_gen *) newoption, offset);
+}
/*
* init_real_reloption
@@ -1626,6 +1705,19 @@ parse_one_reloption(relopt_value *option, char *text_str, int text_len,
option->gen->name, value)));
}
break;
+ case RELOPT_TYPE_TERNARY:
+ {
+ bool b;
+
+ parsed = parse_bool(value, &b);
+ option->values.ternary_val = b ? TERNARY_TRUE : TERNARY_FALSE;
+ if (validate && !parsed)
+ ereport(ERROR,
+ (errcode(ERRCODE_INVALID_PARAMETER_VALUE),
+ errmsg("invalid value for ternary option \"%s\": %s",
+ option->gen->name, value)));
+ }
+ break;
case RELOPT_TYPE_INT:
{
relopt_int *optint = (relopt_int *) option->gen;
@@ -1789,17 +1881,6 @@ fillRelOptions(void *rdopts, Size basesize,
char *itempos = ((char *) rdopts) + elems[j].offset;
char *string_val;
- /*
- * If isset_offset is provided, store whether the reloption is
- * set there.
- */
- if (elems[j].isset_offset > 0)
- {
- char *setpos = ((char *) rdopts) + elems[j].isset_offset;
-
- *(bool *) setpos = options[i].isset;
- }
-
switch (options[i].gen->type)
{
case RELOPT_TYPE_BOOL:
@@ -1807,6 +1888,11 @@ fillRelOptions(void *rdopts, Size basesize,
options[i].values.bool_val :
((relopt_bool *) options[i].gen)->default_val;
break;
+ case RELOPT_TYPE_TERNARY:
+ *(pg_ternary *) itempos = options[i].isset ?
+ options[i].values.ternary_val :
+ ((relopt_ternary *) options[i].gen)->default_val;
+ break;
case RELOPT_TYPE_INT:
*(int *) itempos = options[i].isset ?
options[i].values.int_val :
@@ -1923,8 +2009,8 @@ default_reloptions(Datum reloptions, bool validate, relopt_kind kind)
offsetof(StdRdOptions, parallel_workers)},
{"vacuum_index_cleanup", RELOPT_TYPE_ENUM,
offsetof(StdRdOptions, vacuum_index_cleanup)},
- {"vacuum_truncate", RELOPT_TYPE_BOOL,
- offsetof(StdRdOptions, vacuum_truncate), offsetof(StdRdOptions, vacuum_truncate_set)},
+ {"vacuum_truncate", RELOPT_TYPE_TERNARY,
+ offsetof(StdRdOptions, vacuum_truncate)},
{"vacuum_max_eager_freeze_failure_rate", RELOPT_TYPE_REAL,
offsetof(StdRdOptions, vacuum_max_eager_freeze_failure_rate)}
};
@@ -2004,7 +2090,6 @@ build_local_reloptions(local_relopts *relopts, Datum options, bool validate)
elems[i].optname = opt->option->name;
elems[i].opttype = opt->option->type;
elems[i].offset = opt->offset;
- elems[i].isset_offset = 0; /* not supported for local relopts yet */
i++;
}
diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c
index aa4fbec143f..696eab9bd97 100644
--- a/src/backend/commands/vacuum.c
+++ b/src/backend/commands/vacuum.c
@@ -2224,9 +2224,9 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams params,
{
StdRdOptions *opts = (StdRdOptions *) rel->rd_options;
- if (opts && opts->vacuum_truncate_set)
+ if (opts && opts->vacuum_truncate != TERNARY_UNSET)
{
- if (opts->vacuum_truncate)
+ if (opts->vacuum_truncate == TERNARY_TRUE)
params.truncate = VACOPTVALUE_ENABLED;
else
params.truncate = VACOPTVALUE_DISABLED;
diff --git a/src/include/access/reloptions.h b/src/include/access/reloptions.h
index 2f08e1b0cf0..dfbef2babf2 100644
--- a/src/include/access/reloptions.h
+++ b/src/include/access/reloptions.h
@@ -29,6 +29,7 @@
typedef enum relopt_type
{
RELOPT_TYPE_BOOL,
+ RELOPT_TYPE_TERNARY, /* on, off, unset */
RELOPT_TYPE_INT,
RELOPT_TYPE_REAL,
RELOPT_TYPE_ENUM,
@@ -80,6 +81,7 @@ typedef struct relopt_value
union
{
bool bool_val;
+ pg_ternary ternary_val;
int int_val;
double real_val;
int enum_val;
@@ -94,6 +96,12 @@ typedef struct relopt_bool
bool default_val;
} relopt_bool;
+typedef struct relopt_ternary
+{
+ relopt_gen gen;
+ int default_val;
+} relopt_ternary;
+
typedef struct relopt_int
{
relopt_gen gen;
@@ -152,19 +160,6 @@ typedef struct
const char *optname; /* option's name */
relopt_type opttype; /* option's datatype */
int offset; /* offset of field in result struct */
-
- /*
- * isset_offset is an optional offset of a field in the result struct that
- * stores whether the option is explicitly set for the relation or if it
- * just picked up the default value. In most cases, this can be
- * accomplished by giving the reloption a special out-of-range default
- * value (e.g., some integer reloptions use -2), but this isn't always
- * possible. For example, a Boolean reloption cannot be given an
- * out-of-range default, so we need another way to discover the source of
- * its value. This offset is only used if given a value greater than
- * zero.
- */
- int isset_offset;
} relopt_parse_elt;
/* Local reloption definition */
@@ -195,6 +190,9 @@ typedef struct local_relopts
extern relopt_kind add_reloption_kind(void);
extern void add_bool_reloption(bits32 kinds, const char *name, const char *desc,
bool default_val, LOCKMODE lockmode);
+extern void add_ternary_reloption(bits32 kinds, const char *name,
+ const char *desc, pg_ternary default_val,
+ LOCKMODE lockmode);
extern void add_int_reloption(bits32 kinds, const char *name, const char *desc,
int default_val, int min_val, int max_val,
LOCKMODE lockmode);
@@ -214,6 +212,9 @@ extern void register_reloptions_validator(local_relopts *relopts,
extern void add_local_bool_reloption(local_relopts *relopts, const char *name,
const char *desc, bool default_val,
int offset);
+extern void add_local_ternary_reloption(local_relopts *relopts,
+ const char *name, const char *desc,
+ pg_ternary default_val, int offset);
extern void add_local_int_reloption(local_relopts *relopts, const char *name,
const char *desc, int default_val,
int min_val, int max_val, int offset);
diff --git a/src/include/postgres.h b/src/include/postgres.h
index 7d93fbce709..47c88780776 100644
--- a/src/include/postgres.h
+++ b/src/include/postgres.h
@@ -543,6 +543,21 @@ Float8GetDatum(float8 X)
* ----------------------------------------------------------------
*/
+/*
+ * pg_ternary
+ * Boolean value with an extra "unset" value
+ *
+ * This enum can be used for values that want to distinguish between true,
+ * false, and unset.
+*/
+
+typedef enum pg_ternary
+{
+ TERNARY_FALSE = 0,
+ TERNARY_TRUE = 1,
+ TERNARY_UNSET = -1
+} pg_ternary;
+
/*
* NON_EXEC_STATIC: It's sometimes useful to define a variable or function
* that is normally static but extern when using EXEC_BACKEND (see
diff --git a/src/include/utils/rel.h b/src/include/utils/rel.h
index d03ab247788..5feb18a5373 100644
--- a/src/include/utils/rel.h
+++ b/src/include/utils/rel.h
@@ -347,8 +347,7 @@ typedef struct StdRdOptions
bool user_catalog_table; /* use as an additional catalog relation */
int parallel_workers; /* max number of parallel workers */
StdRdOptIndexCleanup vacuum_index_cleanup; /* controls index vacuuming */
- bool vacuum_truncate; /* enables vacuum to truncate a relation */
- bool vacuum_truncate_set; /* whether vacuum_truncate is set */
+ pg_ternary vacuum_truncate; /* enables vacuum to truncate a relation */
/*
* Fraction of pages in a relation that vacuum can eagerly scan and fail
diff --git a/src/test/regress/expected/reloptions.out b/src/test/regress/expected/reloptions.out
index 9de19b4e3f1..1c99f79ab01 100644
--- a/src/test/regress/expected/reloptions.out
+++ b/src/test/regress/expected/reloptions.out
@@ -98,6 +98,42 @@ SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass;
{fillfactor=13,autovacuum_enabled=false}
(1 row)
+-- Tests for future (FIXME) ternary options
+-- behave as boolean option: accept unassigned name and truncated value
+DROP TABLE reloptions_test;
+CREATE TABLE reloptions_test(i INT) WITH (vacuum_truncate);
+SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass;
+ reloptions
+------------------------
+ {vacuum_truncate=true}
+(1 row)
+
+DROP TABLE reloptions_test;
+CREATE TABLE reloptions_test(i INT) WITH (vacuum_truncate=FaLS);
+SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass;
+ reloptions
+------------------------
+ {vacuum_truncate=fals}
+(1 row)
+
+-- preferred "true" alias is used when storing
+DROP TABLE reloptions_test;
+CREATE TABLE reloptions_test(i INT) WITH (vacuum_index_cleanup=on);
+SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass;
+ reloptions
+---------------------------
+ {vacuum_index_cleanup=on}
+(1 row)
+
+-- custom "third" value is available
+DROP TABLE reloptions_test;
+CREATE TABLE reloptions_test(i INT) WITH (vacuum_index_cleanup=auto);
+SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass;
+ reloptions
+-----------------------------
+ {vacuum_index_cleanup=auto}
+(1 row)
+
-- Test vacuum_truncate option
DROP TABLE reloptions_test;
CREATE TEMP TABLE reloptions_test(i INT NOT NULL, j text)
diff --git a/src/test/regress/sql/reloptions.sql b/src/test/regress/sql/reloptions.sql
index 24fbe0b478d..f5980dafcbc 100644
--- a/src/test/regress/sql/reloptions.sql
+++ b/src/test/regress/sql/reloptions.sql
@@ -59,6 +59,27 @@ UPDATE pg_class
ALTER TABLE reloptions_test RESET (illegal_option);
SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass;
+-- Tests for future (FIXME) ternary options
+
+-- behave as boolean option: accept unassigned name and truncated value
+DROP TABLE reloptions_test;
+CREATE TABLE reloptions_test(i INT) WITH (vacuum_truncate);
+SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass;
+
+DROP TABLE reloptions_test;
+CREATE TABLE reloptions_test(i INT) WITH (vacuum_truncate=FaLS);
+SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass;
+
+-- preferred "true" alias is used when storing
+DROP TABLE reloptions_test;
+CREATE TABLE reloptions_test(i INT) WITH (vacuum_index_cleanup=on);
+SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass;
+
+-- custom "third" value is available
+DROP TABLE reloptions_test;
+CREATE TABLE reloptions_test(i INT) WITH (vacuum_index_cleanup=auto);
+SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass;
+
-- Test vacuum_truncate option
DROP TABLE reloptions_test;
diff --git a/src/tools/pgindent/typedefs.list b/src/tools/pgindent/typedefs.list
index 3f3a888fd0e..1c8610fd46c 100644
--- a/src/tools/pgindent/typedefs.list
+++ b/src/tools/pgindent/typedefs.list
@@ -3952,6 +3952,7 @@ pg_sha512_ctx
pg_snapshot
pg_special_case
pg_stack_base_t
+pg_ternary
pg_time_t
pg_time_usec_t
pg_tz
@@ -4079,6 +4080,7 @@ relopt_kind
relopt_parse_elt
relopt_real
relopt_string
+relopt_ternary
relopt_type
relopt_value
relopts_validator
--
2.47.3
--c36t3fndxhkzbl5z--
^ permalink raw reply [nested|flat] 868+ messages in thread
* [PATCH v3] Introduce ternary reloptions
@ 2026-01-16 15:04 Álvaro Herrera <[email protected]>
0 siblings, 0 replies; 868+ messages in thread
From: Álvaro Herrera @ 2026-01-16 15:04 UTC (permalink / raw)
Introduce ternary reloption as a replacement for current
`vacuum_truncate` implementation. Remove the `vacuum_truncate_set`
separate flag and use TERNARY_UNSET instead.
This could also be used for other options such as `vacuum_index_cleanup`
and `buffering`, but lets get the scaffolding in first.
Discussion: https://postgr.es/m/3474141.usfYGdeWWP@thinkpad-pgpro
---
src/backend/access/common/reloptions.c | 137 ++++++++++++++++++-----
src/backend/commands/vacuum.c | 4 +-
src/include/access/reloptions.h | 27 ++---
src/include/postgres.h | 15 +++
src/include/utils/rel.h | 3 +-
src/test/regress/expected/reloptions.out | 36 ++++++
src/test/regress/sql/reloptions.sql | 21 ++++
src/tools/pgindent/typedefs.list | 2 +
8 files changed, 202 insertions(+), 43 deletions(-)
diff --git a/src/backend/access/common/reloptions.c b/src/backend/access/common/reloptions.c
index 0b83f98ed5f..6f1f577581a 100644
--- a/src/backend/access/common/reloptions.c
+++ b/src/backend/access/common/reloptions.c
@@ -40,9 +40,9 @@
*
* To add an option:
*
- * (i) decide on a type (bool, integer, real, enum, string), name, default
- * value, upper and lower bounds (if applicable); for strings, consider a
- * validation routine.
+ * (i) decide on a type (bool, ternary, integer, real, enum, string), name,
+ * default value, upper and lower bounds (if applicable); for strings,
+ * consider a validation routine.
* (ii) add a record below (or use add_<type>_reloption).
* (iii) add it to the appropriate options struct (perhaps StdRdOptions)
* (iv) add it to the appropriate handling routine (perhaps
@@ -147,15 +147,6 @@ static relopt_bool boolRelOpts[] =
},
false
},
- {
- {
- "vacuum_truncate",
- "Enables vacuum to truncate empty pages at the end of this table",
- RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
- ShareUpdateExclusiveLock
- },
- true
- },
{
{
"deduplicate_items",
@@ -170,6 +161,25 @@ static relopt_bool boolRelOpts[] =
{{NULL}}
};
+static relopt_ternary ternaryRelOpts[] =
+{
+ {
+ {
+ "vacuum_truncate",
+ "Enables vacuum to truncate empty pages at the end of this table",
+ RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
+ ShareUpdateExclusiveLock
+ },
+ TERNARY_UNSET
+ },
+ /* list terminator */
+ {
+ {
+ NULL
+ }
+ }
+};
+
static relopt_int intRelOpts[] =
{
{
@@ -609,6 +619,13 @@ initialize_reloptions(void)
boolRelOpts[i].gen.lockmode));
j++;
}
+ for (i = 0; ternaryRelOpts[i].gen.name; i++)
+ {
+ Assert(DoLockModesConflict(ternaryRelOpts[i].gen.lockmode,
+ ternaryRelOpts[i].gen.lockmode));
+ j++;
+ }
+
for (i = 0; intRelOpts[i].gen.name; i++)
{
Assert(DoLockModesConflict(intRelOpts[i].gen.lockmode,
@@ -649,6 +666,14 @@ initialize_reloptions(void)
j++;
}
+ for (i = 0; ternaryRelOpts[i].gen.name; i++)
+ {
+ relOpts[j] = &ternaryRelOpts[i].gen;
+ relOpts[j]->type = RELOPT_TYPE_TERNARY;
+ relOpts[j]->namelen = strlen(relOpts[j]->name);
+ j++;
+ }
+
for (i = 0; intRelOpts[i].gen.name; i++)
{
relOpts[j] = &intRelOpts[i].gen;
@@ -809,6 +834,9 @@ allocate_reloption(bits32 kinds, int type, const char *name, const char *desc,
case RELOPT_TYPE_BOOL:
size = sizeof(relopt_bool);
break;
+ case RELOPT_TYPE_TERNARY:
+ size = sizeof(relopt_ternary);
+ break;
case RELOPT_TYPE_INT:
size = sizeof(relopt_int);
break;
@@ -892,6 +920,57 @@ add_local_bool_reloption(local_relopts *relopts, const char *name,
add_local_reloption(relopts, (relopt_gen *) newoption, offset);
}
+/*
+ * init_ternary_reloption
+ * Allocate and initialize a new ternary reloption
+ */
+static relopt_ternary *
+init_ternary_reloption(bits32 kinds, const char *name, const char *desc,
+ pg_ternary default_val, LOCKMODE lockmode)
+{
+ relopt_ternary *newoption;
+
+ newoption = (relopt_ternary *)
+ allocate_reloption(kinds, RELOPT_TYPE_TERNARY, name, desc, lockmode);
+ newoption->default_val = default_val;
+
+ return newoption;
+}
+
+/*
+ * add_ternary_reloption
+ * Add a new ternary reloption
+ */
+void
+add_ternary_reloption(bits32 kinds, const char *name, const char *desc,
+ pg_ternary default_val, LOCKMODE lockmode)
+{
+ relopt_ternary *newoption;
+
+ newoption =
+ init_ternary_reloption(kinds, name, desc, default_val, lockmode);
+
+ add_reloption((relopt_gen *) newoption);
+}
+
+/*
+ * add_local_ternary_reloption
+ * Add a new ternary local reloption
+ *
+ * 'offset' is offset of ternary-typed field.
+ */
+void
+add_local_ternary_reloption(local_relopts *relopts, const char *name,
+ const char *desc, pg_ternary default_val,
+ int offset)
+{
+ relopt_ternary *newoption;
+
+ newoption =
+ init_ternary_reloption(RELOPT_KIND_LOCAL, name, desc, default_val, 0);
+
+ add_local_reloption(relopts, (relopt_gen *) newoption, offset);
+}
/*
* init_real_reloption
@@ -1626,6 +1705,19 @@ parse_one_reloption(relopt_value *option, char *text_str, int text_len,
option->gen->name, value)));
}
break;
+ case RELOPT_TYPE_TERNARY:
+ {
+ bool b;
+
+ parsed = parse_bool(value, &b);
+ option->values.ternary_val = b ? TERNARY_TRUE : TERNARY_FALSE;
+ if (validate && !parsed)
+ ereport(ERROR,
+ (errcode(ERRCODE_INVALID_PARAMETER_VALUE),
+ errmsg("invalid value for ternary option \"%s\": %s",
+ option->gen->name, value)));
+ }
+ break;
case RELOPT_TYPE_INT:
{
relopt_int *optint = (relopt_int *) option->gen;
@@ -1789,17 +1881,6 @@ fillRelOptions(void *rdopts, Size basesize,
char *itempos = ((char *) rdopts) + elems[j].offset;
char *string_val;
- /*
- * If isset_offset is provided, store whether the reloption is
- * set there.
- */
- if (elems[j].isset_offset > 0)
- {
- char *setpos = ((char *) rdopts) + elems[j].isset_offset;
-
- *(bool *) setpos = options[i].isset;
- }
-
switch (options[i].gen->type)
{
case RELOPT_TYPE_BOOL:
@@ -1807,6 +1888,11 @@ fillRelOptions(void *rdopts, Size basesize,
options[i].values.bool_val :
((relopt_bool *) options[i].gen)->default_val;
break;
+ case RELOPT_TYPE_TERNARY:
+ *(pg_ternary *) itempos = options[i].isset ?
+ options[i].values.ternary_val :
+ ((relopt_ternary *) options[i].gen)->default_val;
+ break;
case RELOPT_TYPE_INT:
*(int *) itempos = options[i].isset ?
options[i].values.int_val :
@@ -1923,8 +2009,8 @@ default_reloptions(Datum reloptions, bool validate, relopt_kind kind)
offsetof(StdRdOptions, parallel_workers)},
{"vacuum_index_cleanup", RELOPT_TYPE_ENUM,
offsetof(StdRdOptions, vacuum_index_cleanup)},
- {"vacuum_truncate", RELOPT_TYPE_BOOL,
- offsetof(StdRdOptions, vacuum_truncate), offsetof(StdRdOptions, vacuum_truncate_set)},
+ {"vacuum_truncate", RELOPT_TYPE_TERNARY,
+ offsetof(StdRdOptions, vacuum_truncate)},
{"vacuum_max_eager_freeze_failure_rate", RELOPT_TYPE_REAL,
offsetof(StdRdOptions, vacuum_max_eager_freeze_failure_rate)}
};
@@ -2004,7 +2090,6 @@ build_local_reloptions(local_relopts *relopts, Datum options, bool validate)
elems[i].optname = opt->option->name;
elems[i].opttype = opt->option->type;
elems[i].offset = opt->offset;
- elems[i].isset_offset = 0; /* not supported for local relopts yet */
i++;
}
diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c
index aa4fbec143f..696eab9bd97 100644
--- a/src/backend/commands/vacuum.c
+++ b/src/backend/commands/vacuum.c
@@ -2224,9 +2224,9 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams params,
{
StdRdOptions *opts = (StdRdOptions *) rel->rd_options;
- if (opts && opts->vacuum_truncate_set)
+ if (opts && opts->vacuum_truncate != TERNARY_UNSET)
{
- if (opts->vacuum_truncate)
+ if (opts->vacuum_truncate == TERNARY_TRUE)
params.truncate = VACOPTVALUE_ENABLED;
else
params.truncate = VACOPTVALUE_DISABLED;
diff --git a/src/include/access/reloptions.h b/src/include/access/reloptions.h
index 2f08e1b0cf0..dfbef2babf2 100644
--- a/src/include/access/reloptions.h
+++ b/src/include/access/reloptions.h
@@ -29,6 +29,7 @@
typedef enum relopt_type
{
RELOPT_TYPE_BOOL,
+ RELOPT_TYPE_TERNARY, /* on, off, unset */
RELOPT_TYPE_INT,
RELOPT_TYPE_REAL,
RELOPT_TYPE_ENUM,
@@ -80,6 +81,7 @@ typedef struct relopt_value
union
{
bool bool_val;
+ pg_ternary ternary_val;
int int_val;
double real_val;
int enum_val;
@@ -94,6 +96,12 @@ typedef struct relopt_bool
bool default_val;
} relopt_bool;
+typedef struct relopt_ternary
+{
+ relopt_gen gen;
+ int default_val;
+} relopt_ternary;
+
typedef struct relopt_int
{
relopt_gen gen;
@@ -152,19 +160,6 @@ typedef struct
const char *optname; /* option's name */
relopt_type opttype; /* option's datatype */
int offset; /* offset of field in result struct */
-
- /*
- * isset_offset is an optional offset of a field in the result struct that
- * stores whether the option is explicitly set for the relation or if it
- * just picked up the default value. In most cases, this can be
- * accomplished by giving the reloption a special out-of-range default
- * value (e.g., some integer reloptions use -2), but this isn't always
- * possible. For example, a Boolean reloption cannot be given an
- * out-of-range default, so we need another way to discover the source of
- * its value. This offset is only used if given a value greater than
- * zero.
- */
- int isset_offset;
} relopt_parse_elt;
/* Local reloption definition */
@@ -195,6 +190,9 @@ typedef struct local_relopts
extern relopt_kind add_reloption_kind(void);
extern void add_bool_reloption(bits32 kinds, const char *name, const char *desc,
bool default_val, LOCKMODE lockmode);
+extern void add_ternary_reloption(bits32 kinds, const char *name,
+ const char *desc, pg_ternary default_val,
+ LOCKMODE lockmode);
extern void add_int_reloption(bits32 kinds, const char *name, const char *desc,
int default_val, int min_val, int max_val,
LOCKMODE lockmode);
@@ -214,6 +212,9 @@ extern void register_reloptions_validator(local_relopts *relopts,
extern void add_local_bool_reloption(local_relopts *relopts, const char *name,
const char *desc, bool default_val,
int offset);
+extern void add_local_ternary_reloption(local_relopts *relopts,
+ const char *name, const char *desc,
+ pg_ternary default_val, int offset);
extern void add_local_int_reloption(local_relopts *relopts, const char *name,
const char *desc, int default_val,
int min_val, int max_val, int offset);
diff --git a/src/include/postgres.h b/src/include/postgres.h
index 7d93fbce709..47c88780776 100644
--- a/src/include/postgres.h
+++ b/src/include/postgres.h
@@ -543,6 +543,21 @@ Float8GetDatum(float8 X)
* ----------------------------------------------------------------
*/
+/*
+ * pg_ternary
+ * Boolean value with an extra "unset" value
+ *
+ * This enum can be used for values that want to distinguish between true,
+ * false, and unset.
+*/
+
+typedef enum pg_ternary
+{
+ TERNARY_FALSE = 0,
+ TERNARY_TRUE = 1,
+ TERNARY_UNSET = -1
+} pg_ternary;
+
/*
* NON_EXEC_STATIC: It's sometimes useful to define a variable or function
* that is normally static but extern when using EXEC_BACKEND (see
diff --git a/src/include/utils/rel.h b/src/include/utils/rel.h
index d03ab247788..5feb18a5373 100644
--- a/src/include/utils/rel.h
+++ b/src/include/utils/rel.h
@@ -347,8 +347,7 @@ typedef struct StdRdOptions
bool user_catalog_table; /* use as an additional catalog relation */
int parallel_workers; /* max number of parallel workers */
StdRdOptIndexCleanup vacuum_index_cleanup; /* controls index vacuuming */
- bool vacuum_truncate; /* enables vacuum to truncate a relation */
- bool vacuum_truncate_set; /* whether vacuum_truncate is set */
+ pg_ternary vacuum_truncate; /* enables vacuum to truncate a relation */
/*
* Fraction of pages in a relation that vacuum can eagerly scan and fail
diff --git a/src/test/regress/expected/reloptions.out b/src/test/regress/expected/reloptions.out
index 9de19b4e3f1..1c99f79ab01 100644
--- a/src/test/regress/expected/reloptions.out
+++ b/src/test/regress/expected/reloptions.out
@@ -98,6 +98,42 @@ SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass;
{fillfactor=13,autovacuum_enabled=false}
(1 row)
+-- Tests for future (FIXME) ternary options
+-- behave as boolean option: accept unassigned name and truncated value
+DROP TABLE reloptions_test;
+CREATE TABLE reloptions_test(i INT) WITH (vacuum_truncate);
+SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass;
+ reloptions
+------------------------
+ {vacuum_truncate=true}
+(1 row)
+
+DROP TABLE reloptions_test;
+CREATE TABLE reloptions_test(i INT) WITH (vacuum_truncate=FaLS);
+SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass;
+ reloptions
+------------------------
+ {vacuum_truncate=fals}
+(1 row)
+
+-- preferred "true" alias is used when storing
+DROP TABLE reloptions_test;
+CREATE TABLE reloptions_test(i INT) WITH (vacuum_index_cleanup=on);
+SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass;
+ reloptions
+---------------------------
+ {vacuum_index_cleanup=on}
+(1 row)
+
+-- custom "third" value is available
+DROP TABLE reloptions_test;
+CREATE TABLE reloptions_test(i INT) WITH (vacuum_index_cleanup=auto);
+SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass;
+ reloptions
+-----------------------------
+ {vacuum_index_cleanup=auto}
+(1 row)
+
-- Test vacuum_truncate option
DROP TABLE reloptions_test;
CREATE TEMP TABLE reloptions_test(i INT NOT NULL, j text)
diff --git a/src/test/regress/sql/reloptions.sql b/src/test/regress/sql/reloptions.sql
index 24fbe0b478d..f5980dafcbc 100644
--- a/src/test/regress/sql/reloptions.sql
+++ b/src/test/regress/sql/reloptions.sql
@@ -59,6 +59,27 @@ UPDATE pg_class
ALTER TABLE reloptions_test RESET (illegal_option);
SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass;
+-- Tests for future (FIXME) ternary options
+
+-- behave as boolean option: accept unassigned name and truncated value
+DROP TABLE reloptions_test;
+CREATE TABLE reloptions_test(i INT) WITH (vacuum_truncate);
+SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass;
+
+DROP TABLE reloptions_test;
+CREATE TABLE reloptions_test(i INT) WITH (vacuum_truncate=FaLS);
+SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass;
+
+-- preferred "true" alias is used when storing
+DROP TABLE reloptions_test;
+CREATE TABLE reloptions_test(i INT) WITH (vacuum_index_cleanup=on);
+SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass;
+
+-- custom "third" value is available
+DROP TABLE reloptions_test;
+CREATE TABLE reloptions_test(i INT) WITH (vacuum_index_cleanup=auto);
+SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass;
+
-- Test vacuum_truncate option
DROP TABLE reloptions_test;
diff --git a/src/tools/pgindent/typedefs.list b/src/tools/pgindent/typedefs.list
index 3f3a888fd0e..1c8610fd46c 100644
--- a/src/tools/pgindent/typedefs.list
+++ b/src/tools/pgindent/typedefs.list
@@ -3952,6 +3952,7 @@ pg_sha512_ctx
pg_snapshot
pg_special_case
pg_stack_base_t
+pg_ternary
pg_time_t
pg_time_usec_t
pg_tz
@@ -4079,6 +4080,7 @@ relopt_kind
relopt_parse_elt
relopt_real
relopt_string
+relopt_ternary
relopt_type
relopt_value
relopts_validator
--
2.47.3
--c36t3fndxhkzbl5z--
^ permalink raw reply [nested|flat] 868+ messages in thread
* [PATCH v3] Introduce ternary reloptions
@ 2026-01-16 15:04 Álvaro Herrera <[email protected]>
0 siblings, 0 replies; 868+ messages in thread
From: Álvaro Herrera @ 2026-01-16 15:04 UTC (permalink / raw)
Introduce ternary reloption as a replacement for current
`vacuum_truncate` implementation. Remove the `vacuum_truncate_set`
separate flag and use TERNARY_UNSET instead.
This could also be used for other options such as `vacuum_index_cleanup`
and `buffering`, but lets get the scaffolding in first.
Discussion: https://postgr.es/m/3474141.usfYGdeWWP@thinkpad-pgpro
---
src/backend/access/common/reloptions.c | 137 ++++++++++++++++++-----
src/backend/commands/vacuum.c | 4 +-
src/include/access/reloptions.h | 27 ++---
src/include/postgres.h | 15 +++
src/include/utils/rel.h | 3 +-
src/test/regress/expected/reloptions.out | 36 ++++++
src/test/regress/sql/reloptions.sql | 21 ++++
src/tools/pgindent/typedefs.list | 2 +
8 files changed, 202 insertions(+), 43 deletions(-)
diff --git a/src/backend/access/common/reloptions.c b/src/backend/access/common/reloptions.c
index 0b83f98ed5f..6f1f577581a 100644
--- a/src/backend/access/common/reloptions.c
+++ b/src/backend/access/common/reloptions.c
@@ -40,9 +40,9 @@
*
* To add an option:
*
- * (i) decide on a type (bool, integer, real, enum, string), name, default
- * value, upper and lower bounds (if applicable); for strings, consider a
- * validation routine.
+ * (i) decide on a type (bool, ternary, integer, real, enum, string), name,
+ * default value, upper and lower bounds (if applicable); for strings,
+ * consider a validation routine.
* (ii) add a record below (or use add_<type>_reloption).
* (iii) add it to the appropriate options struct (perhaps StdRdOptions)
* (iv) add it to the appropriate handling routine (perhaps
@@ -147,15 +147,6 @@ static relopt_bool boolRelOpts[] =
},
false
},
- {
- {
- "vacuum_truncate",
- "Enables vacuum to truncate empty pages at the end of this table",
- RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
- ShareUpdateExclusiveLock
- },
- true
- },
{
{
"deduplicate_items",
@@ -170,6 +161,25 @@ static relopt_bool boolRelOpts[] =
{{NULL}}
};
+static relopt_ternary ternaryRelOpts[] =
+{
+ {
+ {
+ "vacuum_truncate",
+ "Enables vacuum to truncate empty pages at the end of this table",
+ RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
+ ShareUpdateExclusiveLock
+ },
+ TERNARY_UNSET
+ },
+ /* list terminator */
+ {
+ {
+ NULL
+ }
+ }
+};
+
static relopt_int intRelOpts[] =
{
{
@@ -609,6 +619,13 @@ initialize_reloptions(void)
boolRelOpts[i].gen.lockmode));
j++;
}
+ for (i = 0; ternaryRelOpts[i].gen.name; i++)
+ {
+ Assert(DoLockModesConflict(ternaryRelOpts[i].gen.lockmode,
+ ternaryRelOpts[i].gen.lockmode));
+ j++;
+ }
+
for (i = 0; intRelOpts[i].gen.name; i++)
{
Assert(DoLockModesConflict(intRelOpts[i].gen.lockmode,
@@ -649,6 +666,14 @@ initialize_reloptions(void)
j++;
}
+ for (i = 0; ternaryRelOpts[i].gen.name; i++)
+ {
+ relOpts[j] = &ternaryRelOpts[i].gen;
+ relOpts[j]->type = RELOPT_TYPE_TERNARY;
+ relOpts[j]->namelen = strlen(relOpts[j]->name);
+ j++;
+ }
+
for (i = 0; intRelOpts[i].gen.name; i++)
{
relOpts[j] = &intRelOpts[i].gen;
@@ -809,6 +834,9 @@ allocate_reloption(bits32 kinds, int type, const char *name, const char *desc,
case RELOPT_TYPE_BOOL:
size = sizeof(relopt_bool);
break;
+ case RELOPT_TYPE_TERNARY:
+ size = sizeof(relopt_ternary);
+ break;
case RELOPT_TYPE_INT:
size = sizeof(relopt_int);
break;
@@ -892,6 +920,57 @@ add_local_bool_reloption(local_relopts *relopts, const char *name,
add_local_reloption(relopts, (relopt_gen *) newoption, offset);
}
+/*
+ * init_ternary_reloption
+ * Allocate and initialize a new ternary reloption
+ */
+static relopt_ternary *
+init_ternary_reloption(bits32 kinds, const char *name, const char *desc,
+ pg_ternary default_val, LOCKMODE lockmode)
+{
+ relopt_ternary *newoption;
+
+ newoption = (relopt_ternary *)
+ allocate_reloption(kinds, RELOPT_TYPE_TERNARY, name, desc, lockmode);
+ newoption->default_val = default_val;
+
+ return newoption;
+}
+
+/*
+ * add_ternary_reloption
+ * Add a new ternary reloption
+ */
+void
+add_ternary_reloption(bits32 kinds, const char *name, const char *desc,
+ pg_ternary default_val, LOCKMODE lockmode)
+{
+ relopt_ternary *newoption;
+
+ newoption =
+ init_ternary_reloption(kinds, name, desc, default_val, lockmode);
+
+ add_reloption((relopt_gen *) newoption);
+}
+
+/*
+ * add_local_ternary_reloption
+ * Add a new ternary local reloption
+ *
+ * 'offset' is offset of ternary-typed field.
+ */
+void
+add_local_ternary_reloption(local_relopts *relopts, const char *name,
+ const char *desc, pg_ternary default_val,
+ int offset)
+{
+ relopt_ternary *newoption;
+
+ newoption =
+ init_ternary_reloption(RELOPT_KIND_LOCAL, name, desc, default_val, 0);
+
+ add_local_reloption(relopts, (relopt_gen *) newoption, offset);
+}
/*
* init_real_reloption
@@ -1626,6 +1705,19 @@ parse_one_reloption(relopt_value *option, char *text_str, int text_len,
option->gen->name, value)));
}
break;
+ case RELOPT_TYPE_TERNARY:
+ {
+ bool b;
+
+ parsed = parse_bool(value, &b);
+ option->values.ternary_val = b ? TERNARY_TRUE : TERNARY_FALSE;
+ if (validate && !parsed)
+ ereport(ERROR,
+ (errcode(ERRCODE_INVALID_PARAMETER_VALUE),
+ errmsg("invalid value for ternary option \"%s\": %s",
+ option->gen->name, value)));
+ }
+ break;
case RELOPT_TYPE_INT:
{
relopt_int *optint = (relopt_int *) option->gen;
@@ -1789,17 +1881,6 @@ fillRelOptions(void *rdopts, Size basesize,
char *itempos = ((char *) rdopts) + elems[j].offset;
char *string_val;
- /*
- * If isset_offset is provided, store whether the reloption is
- * set there.
- */
- if (elems[j].isset_offset > 0)
- {
- char *setpos = ((char *) rdopts) + elems[j].isset_offset;
-
- *(bool *) setpos = options[i].isset;
- }
-
switch (options[i].gen->type)
{
case RELOPT_TYPE_BOOL:
@@ -1807,6 +1888,11 @@ fillRelOptions(void *rdopts, Size basesize,
options[i].values.bool_val :
((relopt_bool *) options[i].gen)->default_val;
break;
+ case RELOPT_TYPE_TERNARY:
+ *(pg_ternary *) itempos = options[i].isset ?
+ options[i].values.ternary_val :
+ ((relopt_ternary *) options[i].gen)->default_val;
+ break;
case RELOPT_TYPE_INT:
*(int *) itempos = options[i].isset ?
options[i].values.int_val :
@@ -1923,8 +2009,8 @@ default_reloptions(Datum reloptions, bool validate, relopt_kind kind)
offsetof(StdRdOptions, parallel_workers)},
{"vacuum_index_cleanup", RELOPT_TYPE_ENUM,
offsetof(StdRdOptions, vacuum_index_cleanup)},
- {"vacuum_truncate", RELOPT_TYPE_BOOL,
- offsetof(StdRdOptions, vacuum_truncate), offsetof(StdRdOptions, vacuum_truncate_set)},
+ {"vacuum_truncate", RELOPT_TYPE_TERNARY,
+ offsetof(StdRdOptions, vacuum_truncate)},
{"vacuum_max_eager_freeze_failure_rate", RELOPT_TYPE_REAL,
offsetof(StdRdOptions, vacuum_max_eager_freeze_failure_rate)}
};
@@ -2004,7 +2090,6 @@ build_local_reloptions(local_relopts *relopts, Datum options, bool validate)
elems[i].optname = opt->option->name;
elems[i].opttype = opt->option->type;
elems[i].offset = opt->offset;
- elems[i].isset_offset = 0; /* not supported for local relopts yet */
i++;
}
diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c
index aa4fbec143f..696eab9bd97 100644
--- a/src/backend/commands/vacuum.c
+++ b/src/backend/commands/vacuum.c
@@ -2224,9 +2224,9 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams params,
{
StdRdOptions *opts = (StdRdOptions *) rel->rd_options;
- if (opts && opts->vacuum_truncate_set)
+ if (opts && opts->vacuum_truncate != TERNARY_UNSET)
{
- if (opts->vacuum_truncate)
+ if (opts->vacuum_truncate == TERNARY_TRUE)
params.truncate = VACOPTVALUE_ENABLED;
else
params.truncate = VACOPTVALUE_DISABLED;
diff --git a/src/include/access/reloptions.h b/src/include/access/reloptions.h
index 2f08e1b0cf0..dfbef2babf2 100644
--- a/src/include/access/reloptions.h
+++ b/src/include/access/reloptions.h
@@ -29,6 +29,7 @@
typedef enum relopt_type
{
RELOPT_TYPE_BOOL,
+ RELOPT_TYPE_TERNARY, /* on, off, unset */
RELOPT_TYPE_INT,
RELOPT_TYPE_REAL,
RELOPT_TYPE_ENUM,
@@ -80,6 +81,7 @@ typedef struct relopt_value
union
{
bool bool_val;
+ pg_ternary ternary_val;
int int_val;
double real_val;
int enum_val;
@@ -94,6 +96,12 @@ typedef struct relopt_bool
bool default_val;
} relopt_bool;
+typedef struct relopt_ternary
+{
+ relopt_gen gen;
+ int default_val;
+} relopt_ternary;
+
typedef struct relopt_int
{
relopt_gen gen;
@@ -152,19 +160,6 @@ typedef struct
const char *optname; /* option's name */
relopt_type opttype; /* option's datatype */
int offset; /* offset of field in result struct */
-
- /*
- * isset_offset is an optional offset of a field in the result struct that
- * stores whether the option is explicitly set for the relation or if it
- * just picked up the default value. In most cases, this can be
- * accomplished by giving the reloption a special out-of-range default
- * value (e.g., some integer reloptions use -2), but this isn't always
- * possible. For example, a Boolean reloption cannot be given an
- * out-of-range default, so we need another way to discover the source of
- * its value. This offset is only used if given a value greater than
- * zero.
- */
- int isset_offset;
} relopt_parse_elt;
/* Local reloption definition */
@@ -195,6 +190,9 @@ typedef struct local_relopts
extern relopt_kind add_reloption_kind(void);
extern void add_bool_reloption(bits32 kinds, const char *name, const char *desc,
bool default_val, LOCKMODE lockmode);
+extern void add_ternary_reloption(bits32 kinds, const char *name,
+ const char *desc, pg_ternary default_val,
+ LOCKMODE lockmode);
extern void add_int_reloption(bits32 kinds, const char *name, const char *desc,
int default_val, int min_val, int max_val,
LOCKMODE lockmode);
@@ -214,6 +212,9 @@ extern void register_reloptions_validator(local_relopts *relopts,
extern void add_local_bool_reloption(local_relopts *relopts, const char *name,
const char *desc, bool default_val,
int offset);
+extern void add_local_ternary_reloption(local_relopts *relopts,
+ const char *name, const char *desc,
+ pg_ternary default_val, int offset);
extern void add_local_int_reloption(local_relopts *relopts, const char *name,
const char *desc, int default_val,
int min_val, int max_val, int offset);
diff --git a/src/include/postgres.h b/src/include/postgres.h
index 7d93fbce709..47c88780776 100644
--- a/src/include/postgres.h
+++ b/src/include/postgres.h
@@ -543,6 +543,21 @@ Float8GetDatum(float8 X)
* ----------------------------------------------------------------
*/
+/*
+ * pg_ternary
+ * Boolean value with an extra "unset" value
+ *
+ * This enum can be used for values that want to distinguish between true,
+ * false, and unset.
+*/
+
+typedef enum pg_ternary
+{
+ TERNARY_FALSE = 0,
+ TERNARY_TRUE = 1,
+ TERNARY_UNSET = -1
+} pg_ternary;
+
/*
* NON_EXEC_STATIC: It's sometimes useful to define a variable or function
* that is normally static but extern when using EXEC_BACKEND (see
diff --git a/src/include/utils/rel.h b/src/include/utils/rel.h
index d03ab247788..5feb18a5373 100644
--- a/src/include/utils/rel.h
+++ b/src/include/utils/rel.h
@@ -347,8 +347,7 @@ typedef struct StdRdOptions
bool user_catalog_table; /* use as an additional catalog relation */
int parallel_workers; /* max number of parallel workers */
StdRdOptIndexCleanup vacuum_index_cleanup; /* controls index vacuuming */
- bool vacuum_truncate; /* enables vacuum to truncate a relation */
- bool vacuum_truncate_set; /* whether vacuum_truncate is set */
+ pg_ternary vacuum_truncate; /* enables vacuum to truncate a relation */
/*
* Fraction of pages in a relation that vacuum can eagerly scan and fail
diff --git a/src/test/regress/expected/reloptions.out b/src/test/regress/expected/reloptions.out
index 9de19b4e3f1..1c99f79ab01 100644
--- a/src/test/regress/expected/reloptions.out
+++ b/src/test/regress/expected/reloptions.out
@@ -98,6 +98,42 @@ SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass;
{fillfactor=13,autovacuum_enabled=false}
(1 row)
+-- Tests for future (FIXME) ternary options
+-- behave as boolean option: accept unassigned name and truncated value
+DROP TABLE reloptions_test;
+CREATE TABLE reloptions_test(i INT) WITH (vacuum_truncate);
+SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass;
+ reloptions
+------------------------
+ {vacuum_truncate=true}
+(1 row)
+
+DROP TABLE reloptions_test;
+CREATE TABLE reloptions_test(i INT) WITH (vacuum_truncate=FaLS);
+SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass;
+ reloptions
+------------------------
+ {vacuum_truncate=fals}
+(1 row)
+
+-- preferred "true" alias is used when storing
+DROP TABLE reloptions_test;
+CREATE TABLE reloptions_test(i INT) WITH (vacuum_index_cleanup=on);
+SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass;
+ reloptions
+---------------------------
+ {vacuum_index_cleanup=on}
+(1 row)
+
+-- custom "third" value is available
+DROP TABLE reloptions_test;
+CREATE TABLE reloptions_test(i INT) WITH (vacuum_index_cleanup=auto);
+SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass;
+ reloptions
+-----------------------------
+ {vacuum_index_cleanup=auto}
+(1 row)
+
-- Test vacuum_truncate option
DROP TABLE reloptions_test;
CREATE TEMP TABLE reloptions_test(i INT NOT NULL, j text)
diff --git a/src/test/regress/sql/reloptions.sql b/src/test/regress/sql/reloptions.sql
index 24fbe0b478d..f5980dafcbc 100644
--- a/src/test/regress/sql/reloptions.sql
+++ b/src/test/regress/sql/reloptions.sql
@@ -59,6 +59,27 @@ UPDATE pg_class
ALTER TABLE reloptions_test RESET (illegal_option);
SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass;
+-- Tests for future (FIXME) ternary options
+
+-- behave as boolean option: accept unassigned name and truncated value
+DROP TABLE reloptions_test;
+CREATE TABLE reloptions_test(i INT) WITH (vacuum_truncate);
+SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass;
+
+DROP TABLE reloptions_test;
+CREATE TABLE reloptions_test(i INT) WITH (vacuum_truncate=FaLS);
+SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass;
+
+-- preferred "true" alias is used when storing
+DROP TABLE reloptions_test;
+CREATE TABLE reloptions_test(i INT) WITH (vacuum_index_cleanup=on);
+SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass;
+
+-- custom "third" value is available
+DROP TABLE reloptions_test;
+CREATE TABLE reloptions_test(i INT) WITH (vacuum_index_cleanup=auto);
+SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass;
+
-- Test vacuum_truncate option
DROP TABLE reloptions_test;
diff --git a/src/tools/pgindent/typedefs.list b/src/tools/pgindent/typedefs.list
index 3f3a888fd0e..1c8610fd46c 100644
--- a/src/tools/pgindent/typedefs.list
+++ b/src/tools/pgindent/typedefs.list
@@ -3952,6 +3952,7 @@ pg_sha512_ctx
pg_snapshot
pg_special_case
pg_stack_base_t
+pg_ternary
pg_time_t
pg_time_usec_t
pg_tz
@@ -4079,6 +4080,7 @@ relopt_kind
relopt_parse_elt
relopt_real
relopt_string
+relopt_ternary
relopt_type
relopt_value
relopts_validator
--
2.47.3
--c36t3fndxhkzbl5z--
^ permalink raw reply [nested|flat] 868+ messages in thread
* [PATCH v3] Introduce ternary reloptions
@ 2026-01-16 15:04 Álvaro Herrera <[email protected]>
0 siblings, 0 replies; 868+ messages in thread
From: Álvaro Herrera @ 2026-01-16 15:04 UTC (permalink / raw)
Introduce ternary reloption as a replacement for current
`vacuum_truncate` implementation. Remove the `vacuum_truncate_set`
separate flag and use TERNARY_UNSET instead.
This could also be used for other options such as `vacuum_index_cleanup`
and `buffering`, but lets get the scaffolding in first.
Discussion: https://postgr.es/m/3474141.usfYGdeWWP@thinkpad-pgpro
---
src/backend/access/common/reloptions.c | 137 ++++++++++++++++++-----
src/backend/commands/vacuum.c | 4 +-
src/include/access/reloptions.h | 27 ++---
src/include/postgres.h | 15 +++
src/include/utils/rel.h | 3 +-
src/test/regress/expected/reloptions.out | 36 ++++++
src/test/regress/sql/reloptions.sql | 21 ++++
src/tools/pgindent/typedefs.list | 2 +
8 files changed, 202 insertions(+), 43 deletions(-)
diff --git a/src/backend/access/common/reloptions.c b/src/backend/access/common/reloptions.c
index 0b83f98ed5f..6f1f577581a 100644
--- a/src/backend/access/common/reloptions.c
+++ b/src/backend/access/common/reloptions.c
@@ -40,9 +40,9 @@
*
* To add an option:
*
- * (i) decide on a type (bool, integer, real, enum, string), name, default
- * value, upper and lower bounds (if applicable); for strings, consider a
- * validation routine.
+ * (i) decide on a type (bool, ternary, integer, real, enum, string), name,
+ * default value, upper and lower bounds (if applicable); for strings,
+ * consider a validation routine.
* (ii) add a record below (or use add_<type>_reloption).
* (iii) add it to the appropriate options struct (perhaps StdRdOptions)
* (iv) add it to the appropriate handling routine (perhaps
@@ -147,15 +147,6 @@ static relopt_bool boolRelOpts[] =
},
false
},
- {
- {
- "vacuum_truncate",
- "Enables vacuum to truncate empty pages at the end of this table",
- RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
- ShareUpdateExclusiveLock
- },
- true
- },
{
{
"deduplicate_items",
@@ -170,6 +161,25 @@ static relopt_bool boolRelOpts[] =
{{NULL}}
};
+static relopt_ternary ternaryRelOpts[] =
+{
+ {
+ {
+ "vacuum_truncate",
+ "Enables vacuum to truncate empty pages at the end of this table",
+ RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
+ ShareUpdateExclusiveLock
+ },
+ TERNARY_UNSET
+ },
+ /* list terminator */
+ {
+ {
+ NULL
+ }
+ }
+};
+
static relopt_int intRelOpts[] =
{
{
@@ -609,6 +619,13 @@ initialize_reloptions(void)
boolRelOpts[i].gen.lockmode));
j++;
}
+ for (i = 0; ternaryRelOpts[i].gen.name; i++)
+ {
+ Assert(DoLockModesConflict(ternaryRelOpts[i].gen.lockmode,
+ ternaryRelOpts[i].gen.lockmode));
+ j++;
+ }
+
for (i = 0; intRelOpts[i].gen.name; i++)
{
Assert(DoLockModesConflict(intRelOpts[i].gen.lockmode,
@@ -649,6 +666,14 @@ initialize_reloptions(void)
j++;
}
+ for (i = 0; ternaryRelOpts[i].gen.name; i++)
+ {
+ relOpts[j] = &ternaryRelOpts[i].gen;
+ relOpts[j]->type = RELOPT_TYPE_TERNARY;
+ relOpts[j]->namelen = strlen(relOpts[j]->name);
+ j++;
+ }
+
for (i = 0; intRelOpts[i].gen.name; i++)
{
relOpts[j] = &intRelOpts[i].gen;
@@ -809,6 +834,9 @@ allocate_reloption(bits32 kinds, int type, const char *name, const char *desc,
case RELOPT_TYPE_BOOL:
size = sizeof(relopt_bool);
break;
+ case RELOPT_TYPE_TERNARY:
+ size = sizeof(relopt_ternary);
+ break;
case RELOPT_TYPE_INT:
size = sizeof(relopt_int);
break;
@@ -892,6 +920,57 @@ add_local_bool_reloption(local_relopts *relopts, const char *name,
add_local_reloption(relopts, (relopt_gen *) newoption, offset);
}
+/*
+ * init_ternary_reloption
+ * Allocate and initialize a new ternary reloption
+ */
+static relopt_ternary *
+init_ternary_reloption(bits32 kinds, const char *name, const char *desc,
+ pg_ternary default_val, LOCKMODE lockmode)
+{
+ relopt_ternary *newoption;
+
+ newoption = (relopt_ternary *)
+ allocate_reloption(kinds, RELOPT_TYPE_TERNARY, name, desc, lockmode);
+ newoption->default_val = default_val;
+
+ return newoption;
+}
+
+/*
+ * add_ternary_reloption
+ * Add a new ternary reloption
+ */
+void
+add_ternary_reloption(bits32 kinds, const char *name, const char *desc,
+ pg_ternary default_val, LOCKMODE lockmode)
+{
+ relopt_ternary *newoption;
+
+ newoption =
+ init_ternary_reloption(kinds, name, desc, default_val, lockmode);
+
+ add_reloption((relopt_gen *) newoption);
+}
+
+/*
+ * add_local_ternary_reloption
+ * Add a new ternary local reloption
+ *
+ * 'offset' is offset of ternary-typed field.
+ */
+void
+add_local_ternary_reloption(local_relopts *relopts, const char *name,
+ const char *desc, pg_ternary default_val,
+ int offset)
+{
+ relopt_ternary *newoption;
+
+ newoption =
+ init_ternary_reloption(RELOPT_KIND_LOCAL, name, desc, default_val, 0);
+
+ add_local_reloption(relopts, (relopt_gen *) newoption, offset);
+}
/*
* init_real_reloption
@@ -1626,6 +1705,19 @@ parse_one_reloption(relopt_value *option, char *text_str, int text_len,
option->gen->name, value)));
}
break;
+ case RELOPT_TYPE_TERNARY:
+ {
+ bool b;
+
+ parsed = parse_bool(value, &b);
+ option->values.ternary_val = b ? TERNARY_TRUE : TERNARY_FALSE;
+ if (validate && !parsed)
+ ereport(ERROR,
+ (errcode(ERRCODE_INVALID_PARAMETER_VALUE),
+ errmsg("invalid value for ternary option \"%s\": %s",
+ option->gen->name, value)));
+ }
+ break;
case RELOPT_TYPE_INT:
{
relopt_int *optint = (relopt_int *) option->gen;
@@ -1789,17 +1881,6 @@ fillRelOptions(void *rdopts, Size basesize,
char *itempos = ((char *) rdopts) + elems[j].offset;
char *string_val;
- /*
- * If isset_offset is provided, store whether the reloption is
- * set there.
- */
- if (elems[j].isset_offset > 0)
- {
- char *setpos = ((char *) rdopts) + elems[j].isset_offset;
-
- *(bool *) setpos = options[i].isset;
- }
-
switch (options[i].gen->type)
{
case RELOPT_TYPE_BOOL:
@@ -1807,6 +1888,11 @@ fillRelOptions(void *rdopts, Size basesize,
options[i].values.bool_val :
((relopt_bool *) options[i].gen)->default_val;
break;
+ case RELOPT_TYPE_TERNARY:
+ *(pg_ternary *) itempos = options[i].isset ?
+ options[i].values.ternary_val :
+ ((relopt_ternary *) options[i].gen)->default_val;
+ break;
case RELOPT_TYPE_INT:
*(int *) itempos = options[i].isset ?
options[i].values.int_val :
@@ -1923,8 +2009,8 @@ default_reloptions(Datum reloptions, bool validate, relopt_kind kind)
offsetof(StdRdOptions, parallel_workers)},
{"vacuum_index_cleanup", RELOPT_TYPE_ENUM,
offsetof(StdRdOptions, vacuum_index_cleanup)},
- {"vacuum_truncate", RELOPT_TYPE_BOOL,
- offsetof(StdRdOptions, vacuum_truncate), offsetof(StdRdOptions, vacuum_truncate_set)},
+ {"vacuum_truncate", RELOPT_TYPE_TERNARY,
+ offsetof(StdRdOptions, vacuum_truncate)},
{"vacuum_max_eager_freeze_failure_rate", RELOPT_TYPE_REAL,
offsetof(StdRdOptions, vacuum_max_eager_freeze_failure_rate)}
};
@@ -2004,7 +2090,6 @@ build_local_reloptions(local_relopts *relopts, Datum options, bool validate)
elems[i].optname = opt->option->name;
elems[i].opttype = opt->option->type;
elems[i].offset = opt->offset;
- elems[i].isset_offset = 0; /* not supported for local relopts yet */
i++;
}
diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c
index aa4fbec143f..696eab9bd97 100644
--- a/src/backend/commands/vacuum.c
+++ b/src/backend/commands/vacuum.c
@@ -2224,9 +2224,9 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams params,
{
StdRdOptions *opts = (StdRdOptions *) rel->rd_options;
- if (opts && opts->vacuum_truncate_set)
+ if (opts && opts->vacuum_truncate != TERNARY_UNSET)
{
- if (opts->vacuum_truncate)
+ if (opts->vacuum_truncate == TERNARY_TRUE)
params.truncate = VACOPTVALUE_ENABLED;
else
params.truncate = VACOPTVALUE_DISABLED;
diff --git a/src/include/access/reloptions.h b/src/include/access/reloptions.h
index 2f08e1b0cf0..dfbef2babf2 100644
--- a/src/include/access/reloptions.h
+++ b/src/include/access/reloptions.h
@@ -29,6 +29,7 @@
typedef enum relopt_type
{
RELOPT_TYPE_BOOL,
+ RELOPT_TYPE_TERNARY, /* on, off, unset */
RELOPT_TYPE_INT,
RELOPT_TYPE_REAL,
RELOPT_TYPE_ENUM,
@@ -80,6 +81,7 @@ typedef struct relopt_value
union
{
bool bool_val;
+ pg_ternary ternary_val;
int int_val;
double real_val;
int enum_val;
@@ -94,6 +96,12 @@ typedef struct relopt_bool
bool default_val;
} relopt_bool;
+typedef struct relopt_ternary
+{
+ relopt_gen gen;
+ int default_val;
+} relopt_ternary;
+
typedef struct relopt_int
{
relopt_gen gen;
@@ -152,19 +160,6 @@ typedef struct
const char *optname; /* option's name */
relopt_type opttype; /* option's datatype */
int offset; /* offset of field in result struct */
-
- /*
- * isset_offset is an optional offset of a field in the result struct that
- * stores whether the option is explicitly set for the relation or if it
- * just picked up the default value. In most cases, this can be
- * accomplished by giving the reloption a special out-of-range default
- * value (e.g., some integer reloptions use -2), but this isn't always
- * possible. For example, a Boolean reloption cannot be given an
- * out-of-range default, so we need another way to discover the source of
- * its value. This offset is only used if given a value greater than
- * zero.
- */
- int isset_offset;
} relopt_parse_elt;
/* Local reloption definition */
@@ -195,6 +190,9 @@ typedef struct local_relopts
extern relopt_kind add_reloption_kind(void);
extern void add_bool_reloption(bits32 kinds, const char *name, const char *desc,
bool default_val, LOCKMODE lockmode);
+extern void add_ternary_reloption(bits32 kinds, const char *name,
+ const char *desc, pg_ternary default_val,
+ LOCKMODE lockmode);
extern void add_int_reloption(bits32 kinds, const char *name, const char *desc,
int default_val, int min_val, int max_val,
LOCKMODE lockmode);
@@ -214,6 +212,9 @@ extern void register_reloptions_validator(local_relopts *relopts,
extern void add_local_bool_reloption(local_relopts *relopts, const char *name,
const char *desc, bool default_val,
int offset);
+extern void add_local_ternary_reloption(local_relopts *relopts,
+ const char *name, const char *desc,
+ pg_ternary default_val, int offset);
extern void add_local_int_reloption(local_relopts *relopts, const char *name,
const char *desc, int default_val,
int min_val, int max_val, int offset);
diff --git a/src/include/postgres.h b/src/include/postgres.h
index 7d93fbce709..47c88780776 100644
--- a/src/include/postgres.h
+++ b/src/include/postgres.h
@@ -543,6 +543,21 @@ Float8GetDatum(float8 X)
* ----------------------------------------------------------------
*/
+/*
+ * pg_ternary
+ * Boolean value with an extra "unset" value
+ *
+ * This enum can be used for values that want to distinguish between true,
+ * false, and unset.
+*/
+
+typedef enum pg_ternary
+{
+ TERNARY_FALSE = 0,
+ TERNARY_TRUE = 1,
+ TERNARY_UNSET = -1
+} pg_ternary;
+
/*
* NON_EXEC_STATIC: It's sometimes useful to define a variable or function
* that is normally static but extern when using EXEC_BACKEND (see
diff --git a/src/include/utils/rel.h b/src/include/utils/rel.h
index d03ab247788..5feb18a5373 100644
--- a/src/include/utils/rel.h
+++ b/src/include/utils/rel.h
@@ -347,8 +347,7 @@ typedef struct StdRdOptions
bool user_catalog_table; /* use as an additional catalog relation */
int parallel_workers; /* max number of parallel workers */
StdRdOptIndexCleanup vacuum_index_cleanup; /* controls index vacuuming */
- bool vacuum_truncate; /* enables vacuum to truncate a relation */
- bool vacuum_truncate_set; /* whether vacuum_truncate is set */
+ pg_ternary vacuum_truncate; /* enables vacuum to truncate a relation */
/*
* Fraction of pages in a relation that vacuum can eagerly scan and fail
diff --git a/src/test/regress/expected/reloptions.out b/src/test/regress/expected/reloptions.out
index 9de19b4e3f1..1c99f79ab01 100644
--- a/src/test/regress/expected/reloptions.out
+++ b/src/test/regress/expected/reloptions.out
@@ -98,6 +98,42 @@ SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass;
{fillfactor=13,autovacuum_enabled=false}
(1 row)
+-- Tests for future (FIXME) ternary options
+-- behave as boolean option: accept unassigned name and truncated value
+DROP TABLE reloptions_test;
+CREATE TABLE reloptions_test(i INT) WITH (vacuum_truncate);
+SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass;
+ reloptions
+------------------------
+ {vacuum_truncate=true}
+(1 row)
+
+DROP TABLE reloptions_test;
+CREATE TABLE reloptions_test(i INT) WITH (vacuum_truncate=FaLS);
+SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass;
+ reloptions
+------------------------
+ {vacuum_truncate=fals}
+(1 row)
+
+-- preferred "true" alias is used when storing
+DROP TABLE reloptions_test;
+CREATE TABLE reloptions_test(i INT) WITH (vacuum_index_cleanup=on);
+SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass;
+ reloptions
+---------------------------
+ {vacuum_index_cleanup=on}
+(1 row)
+
+-- custom "third" value is available
+DROP TABLE reloptions_test;
+CREATE TABLE reloptions_test(i INT) WITH (vacuum_index_cleanup=auto);
+SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass;
+ reloptions
+-----------------------------
+ {vacuum_index_cleanup=auto}
+(1 row)
+
-- Test vacuum_truncate option
DROP TABLE reloptions_test;
CREATE TEMP TABLE reloptions_test(i INT NOT NULL, j text)
diff --git a/src/test/regress/sql/reloptions.sql b/src/test/regress/sql/reloptions.sql
index 24fbe0b478d..f5980dafcbc 100644
--- a/src/test/regress/sql/reloptions.sql
+++ b/src/test/regress/sql/reloptions.sql
@@ -59,6 +59,27 @@ UPDATE pg_class
ALTER TABLE reloptions_test RESET (illegal_option);
SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass;
+-- Tests for future (FIXME) ternary options
+
+-- behave as boolean option: accept unassigned name and truncated value
+DROP TABLE reloptions_test;
+CREATE TABLE reloptions_test(i INT) WITH (vacuum_truncate);
+SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass;
+
+DROP TABLE reloptions_test;
+CREATE TABLE reloptions_test(i INT) WITH (vacuum_truncate=FaLS);
+SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass;
+
+-- preferred "true" alias is used when storing
+DROP TABLE reloptions_test;
+CREATE TABLE reloptions_test(i INT) WITH (vacuum_index_cleanup=on);
+SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass;
+
+-- custom "third" value is available
+DROP TABLE reloptions_test;
+CREATE TABLE reloptions_test(i INT) WITH (vacuum_index_cleanup=auto);
+SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass;
+
-- Test vacuum_truncate option
DROP TABLE reloptions_test;
diff --git a/src/tools/pgindent/typedefs.list b/src/tools/pgindent/typedefs.list
index 3f3a888fd0e..1c8610fd46c 100644
--- a/src/tools/pgindent/typedefs.list
+++ b/src/tools/pgindent/typedefs.list
@@ -3952,6 +3952,7 @@ pg_sha512_ctx
pg_snapshot
pg_special_case
pg_stack_base_t
+pg_ternary
pg_time_t
pg_time_usec_t
pg_tz
@@ -4079,6 +4080,7 @@ relopt_kind
relopt_parse_elt
relopt_real
relopt_string
+relopt_ternary
relopt_type
relopt_value
relopts_validator
--
2.47.3
--c36t3fndxhkzbl5z--
^ permalink raw reply [nested|flat] 868+ messages in thread
* [PATCH v3] Introduce ternary reloptions
@ 2026-01-16 15:04 Álvaro Herrera <[email protected]>
0 siblings, 0 replies; 868+ messages in thread
From: Álvaro Herrera @ 2026-01-16 15:04 UTC (permalink / raw)
Introduce ternary reloption as a replacement for current
`vacuum_truncate` implementation. Remove the `vacuum_truncate_set`
separate flag and use TERNARY_UNSET instead.
This could also be used for other options such as `vacuum_index_cleanup`
and `buffering`, but lets get the scaffolding in first.
Discussion: https://postgr.es/m/3474141.usfYGdeWWP@thinkpad-pgpro
---
src/backend/access/common/reloptions.c | 137 ++++++++++++++++++-----
src/backend/commands/vacuum.c | 4 +-
src/include/access/reloptions.h | 27 ++---
src/include/postgres.h | 15 +++
src/include/utils/rel.h | 3 +-
src/test/regress/expected/reloptions.out | 36 ++++++
src/test/regress/sql/reloptions.sql | 21 ++++
src/tools/pgindent/typedefs.list | 2 +
8 files changed, 202 insertions(+), 43 deletions(-)
diff --git a/src/backend/access/common/reloptions.c b/src/backend/access/common/reloptions.c
index 0b83f98ed5f..6f1f577581a 100644
--- a/src/backend/access/common/reloptions.c
+++ b/src/backend/access/common/reloptions.c
@@ -40,9 +40,9 @@
*
* To add an option:
*
- * (i) decide on a type (bool, integer, real, enum, string), name, default
- * value, upper and lower bounds (if applicable); for strings, consider a
- * validation routine.
+ * (i) decide on a type (bool, ternary, integer, real, enum, string), name,
+ * default value, upper and lower bounds (if applicable); for strings,
+ * consider a validation routine.
* (ii) add a record below (or use add_<type>_reloption).
* (iii) add it to the appropriate options struct (perhaps StdRdOptions)
* (iv) add it to the appropriate handling routine (perhaps
@@ -147,15 +147,6 @@ static relopt_bool boolRelOpts[] =
},
false
},
- {
- {
- "vacuum_truncate",
- "Enables vacuum to truncate empty pages at the end of this table",
- RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
- ShareUpdateExclusiveLock
- },
- true
- },
{
{
"deduplicate_items",
@@ -170,6 +161,25 @@ static relopt_bool boolRelOpts[] =
{{NULL}}
};
+static relopt_ternary ternaryRelOpts[] =
+{
+ {
+ {
+ "vacuum_truncate",
+ "Enables vacuum to truncate empty pages at the end of this table",
+ RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
+ ShareUpdateExclusiveLock
+ },
+ TERNARY_UNSET
+ },
+ /* list terminator */
+ {
+ {
+ NULL
+ }
+ }
+};
+
static relopt_int intRelOpts[] =
{
{
@@ -609,6 +619,13 @@ initialize_reloptions(void)
boolRelOpts[i].gen.lockmode));
j++;
}
+ for (i = 0; ternaryRelOpts[i].gen.name; i++)
+ {
+ Assert(DoLockModesConflict(ternaryRelOpts[i].gen.lockmode,
+ ternaryRelOpts[i].gen.lockmode));
+ j++;
+ }
+
for (i = 0; intRelOpts[i].gen.name; i++)
{
Assert(DoLockModesConflict(intRelOpts[i].gen.lockmode,
@@ -649,6 +666,14 @@ initialize_reloptions(void)
j++;
}
+ for (i = 0; ternaryRelOpts[i].gen.name; i++)
+ {
+ relOpts[j] = &ternaryRelOpts[i].gen;
+ relOpts[j]->type = RELOPT_TYPE_TERNARY;
+ relOpts[j]->namelen = strlen(relOpts[j]->name);
+ j++;
+ }
+
for (i = 0; intRelOpts[i].gen.name; i++)
{
relOpts[j] = &intRelOpts[i].gen;
@@ -809,6 +834,9 @@ allocate_reloption(bits32 kinds, int type, const char *name, const char *desc,
case RELOPT_TYPE_BOOL:
size = sizeof(relopt_bool);
break;
+ case RELOPT_TYPE_TERNARY:
+ size = sizeof(relopt_ternary);
+ break;
case RELOPT_TYPE_INT:
size = sizeof(relopt_int);
break;
@@ -892,6 +920,57 @@ add_local_bool_reloption(local_relopts *relopts, const char *name,
add_local_reloption(relopts, (relopt_gen *) newoption, offset);
}
+/*
+ * init_ternary_reloption
+ * Allocate and initialize a new ternary reloption
+ */
+static relopt_ternary *
+init_ternary_reloption(bits32 kinds, const char *name, const char *desc,
+ pg_ternary default_val, LOCKMODE lockmode)
+{
+ relopt_ternary *newoption;
+
+ newoption = (relopt_ternary *)
+ allocate_reloption(kinds, RELOPT_TYPE_TERNARY, name, desc, lockmode);
+ newoption->default_val = default_val;
+
+ return newoption;
+}
+
+/*
+ * add_ternary_reloption
+ * Add a new ternary reloption
+ */
+void
+add_ternary_reloption(bits32 kinds, const char *name, const char *desc,
+ pg_ternary default_val, LOCKMODE lockmode)
+{
+ relopt_ternary *newoption;
+
+ newoption =
+ init_ternary_reloption(kinds, name, desc, default_val, lockmode);
+
+ add_reloption((relopt_gen *) newoption);
+}
+
+/*
+ * add_local_ternary_reloption
+ * Add a new ternary local reloption
+ *
+ * 'offset' is offset of ternary-typed field.
+ */
+void
+add_local_ternary_reloption(local_relopts *relopts, const char *name,
+ const char *desc, pg_ternary default_val,
+ int offset)
+{
+ relopt_ternary *newoption;
+
+ newoption =
+ init_ternary_reloption(RELOPT_KIND_LOCAL, name, desc, default_val, 0);
+
+ add_local_reloption(relopts, (relopt_gen *) newoption, offset);
+}
/*
* init_real_reloption
@@ -1626,6 +1705,19 @@ parse_one_reloption(relopt_value *option, char *text_str, int text_len,
option->gen->name, value)));
}
break;
+ case RELOPT_TYPE_TERNARY:
+ {
+ bool b;
+
+ parsed = parse_bool(value, &b);
+ option->values.ternary_val = b ? TERNARY_TRUE : TERNARY_FALSE;
+ if (validate && !parsed)
+ ereport(ERROR,
+ (errcode(ERRCODE_INVALID_PARAMETER_VALUE),
+ errmsg("invalid value for ternary option \"%s\": %s",
+ option->gen->name, value)));
+ }
+ break;
case RELOPT_TYPE_INT:
{
relopt_int *optint = (relopt_int *) option->gen;
@@ -1789,17 +1881,6 @@ fillRelOptions(void *rdopts, Size basesize,
char *itempos = ((char *) rdopts) + elems[j].offset;
char *string_val;
- /*
- * If isset_offset is provided, store whether the reloption is
- * set there.
- */
- if (elems[j].isset_offset > 0)
- {
- char *setpos = ((char *) rdopts) + elems[j].isset_offset;
-
- *(bool *) setpos = options[i].isset;
- }
-
switch (options[i].gen->type)
{
case RELOPT_TYPE_BOOL:
@@ -1807,6 +1888,11 @@ fillRelOptions(void *rdopts, Size basesize,
options[i].values.bool_val :
((relopt_bool *) options[i].gen)->default_val;
break;
+ case RELOPT_TYPE_TERNARY:
+ *(pg_ternary *) itempos = options[i].isset ?
+ options[i].values.ternary_val :
+ ((relopt_ternary *) options[i].gen)->default_val;
+ break;
case RELOPT_TYPE_INT:
*(int *) itempos = options[i].isset ?
options[i].values.int_val :
@@ -1923,8 +2009,8 @@ default_reloptions(Datum reloptions, bool validate, relopt_kind kind)
offsetof(StdRdOptions, parallel_workers)},
{"vacuum_index_cleanup", RELOPT_TYPE_ENUM,
offsetof(StdRdOptions, vacuum_index_cleanup)},
- {"vacuum_truncate", RELOPT_TYPE_BOOL,
- offsetof(StdRdOptions, vacuum_truncate), offsetof(StdRdOptions, vacuum_truncate_set)},
+ {"vacuum_truncate", RELOPT_TYPE_TERNARY,
+ offsetof(StdRdOptions, vacuum_truncate)},
{"vacuum_max_eager_freeze_failure_rate", RELOPT_TYPE_REAL,
offsetof(StdRdOptions, vacuum_max_eager_freeze_failure_rate)}
};
@@ -2004,7 +2090,6 @@ build_local_reloptions(local_relopts *relopts, Datum options, bool validate)
elems[i].optname = opt->option->name;
elems[i].opttype = opt->option->type;
elems[i].offset = opt->offset;
- elems[i].isset_offset = 0; /* not supported for local relopts yet */
i++;
}
diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c
index aa4fbec143f..696eab9bd97 100644
--- a/src/backend/commands/vacuum.c
+++ b/src/backend/commands/vacuum.c
@@ -2224,9 +2224,9 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams params,
{
StdRdOptions *opts = (StdRdOptions *) rel->rd_options;
- if (opts && opts->vacuum_truncate_set)
+ if (opts && opts->vacuum_truncate != TERNARY_UNSET)
{
- if (opts->vacuum_truncate)
+ if (opts->vacuum_truncate == TERNARY_TRUE)
params.truncate = VACOPTVALUE_ENABLED;
else
params.truncate = VACOPTVALUE_DISABLED;
diff --git a/src/include/access/reloptions.h b/src/include/access/reloptions.h
index 2f08e1b0cf0..dfbef2babf2 100644
--- a/src/include/access/reloptions.h
+++ b/src/include/access/reloptions.h
@@ -29,6 +29,7 @@
typedef enum relopt_type
{
RELOPT_TYPE_BOOL,
+ RELOPT_TYPE_TERNARY, /* on, off, unset */
RELOPT_TYPE_INT,
RELOPT_TYPE_REAL,
RELOPT_TYPE_ENUM,
@@ -80,6 +81,7 @@ typedef struct relopt_value
union
{
bool bool_val;
+ pg_ternary ternary_val;
int int_val;
double real_val;
int enum_val;
@@ -94,6 +96,12 @@ typedef struct relopt_bool
bool default_val;
} relopt_bool;
+typedef struct relopt_ternary
+{
+ relopt_gen gen;
+ int default_val;
+} relopt_ternary;
+
typedef struct relopt_int
{
relopt_gen gen;
@@ -152,19 +160,6 @@ typedef struct
const char *optname; /* option's name */
relopt_type opttype; /* option's datatype */
int offset; /* offset of field in result struct */
-
- /*
- * isset_offset is an optional offset of a field in the result struct that
- * stores whether the option is explicitly set for the relation or if it
- * just picked up the default value. In most cases, this can be
- * accomplished by giving the reloption a special out-of-range default
- * value (e.g., some integer reloptions use -2), but this isn't always
- * possible. For example, a Boolean reloption cannot be given an
- * out-of-range default, so we need another way to discover the source of
- * its value. This offset is only used if given a value greater than
- * zero.
- */
- int isset_offset;
} relopt_parse_elt;
/* Local reloption definition */
@@ -195,6 +190,9 @@ typedef struct local_relopts
extern relopt_kind add_reloption_kind(void);
extern void add_bool_reloption(bits32 kinds, const char *name, const char *desc,
bool default_val, LOCKMODE lockmode);
+extern void add_ternary_reloption(bits32 kinds, const char *name,
+ const char *desc, pg_ternary default_val,
+ LOCKMODE lockmode);
extern void add_int_reloption(bits32 kinds, const char *name, const char *desc,
int default_val, int min_val, int max_val,
LOCKMODE lockmode);
@@ -214,6 +212,9 @@ extern void register_reloptions_validator(local_relopts *relopts,
extern void add_local_bool_reloption(local_relopts *relopts, const char *name,
const char *desc, bool default_val,
int offset);
+extern void add_local_ternary_reloption(local_relopts *relopts,
+ const char *name, const char *desc,
+ pg_ternary default_val, int offset);
extern void add_local_int_reloption(local_relopts *relopts, const char *name,
const char *desc, int default_val,
int min_val, int max_val, int offset);
diff --git a/src/include/postgres.h b/src/include/postgres.h
index 7d93fbce709..47c88780776 100644
--- a/src/include/postgres.h
+++ b/src/include/postgres.h
@@ -543,6 +543,21 @@ Float8GetDatum(float8 X)
* ----------------------------------------------------------------
*/
+/*
+ * pg_ternary
+ * Boolean value with an extra "unset" value
+ *
+ * This enum can be used for values that want to distinguish between true,
+ * false, and unset.
+*/
+
+typedef enum pg_ternary
+{
+ TERNARY_FALSE = 0,
+ TERNARY_TRUE = 1,
+ TERNARY_UNSET = -1
+} pg_ternary;
+
/*
* NON_EXEC_STATIC: It's sometimes useful to define a variable or function
* that is normally static but extern when using EXEC_BACKEND (see
diff --git a/src/include/utils/rel.h b/src/include/utils/rel.h
index d03ab247788..5feb18a5373 100644
--- a/src/include/utils/rel.h
+++ b/src/include/utils/rel.h
@@ -347,8 +347,7 @@ typedef struct StdRdOptions
bool user_catalog_table; /* use as an additional catalog relation */
int parallel_workers; /* max number of parallel workers */
StdRdOptIndexCleanup vacuum_index_cleanup; /* controls index vacuuming */
- bool vacuum_truncate; /* enables vacuum to truncate a relation */
- bool vacuum_truncate_set; /* whether vacuum_truncate is set */
+ pg_ternary vacuum_truncate; /* enables vacuum to truncate a relation */
/*
* Fraction of pages in a relation that vacuum can eagerly scan and fail
diff --git a/src/test/regress/expected/reloptions.out b/src/test/regress/expected/reloptions.out
index 9de19b4e3f1..1c99f79ab01 100644
--- a/src/test/regress/expected/reloptions.out
+++ b/src/test/regress/expected/reloptions.out
@@ -98,6 +98,42 @@ SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass;
{fillfactor=13,autovacuum_enabled=false}
(1 row)
+-- Tests for future (FIXME) ternary options
+-- behave as boolean option: accept unassigned name and truncated value
+DROP TABLE reloptions_test;
+CREATE TABLE reloptions_test(i INT) WITH (vacuum_truncate);
+SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass;
+ reloptions
+------------------------
+ {vacuum_truncate=true}
+(1 row)
+
+DROP TABLE reloptions_test;
+CREATE TABLE reloptions_test(i INT) WITH (vacuum_truncate=FaLS);
+SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass;
+ reloptions
+------------------------
+ {vacuum_truncate=fals}
+(1 row)
+
+-- preferred "true" alias is used when storing
+DROP TABLE reloptions_test;
+CREATE TABLE reloptions_test(i INT) WITH (vacuum_index_cleanup=on);
+SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass;
+ reloptions
+---------------------------
+ {vacuum_index_cleanup=on}
+(1 row)
+
+-- custom "third" value is available
+DROP TABLE reloptions_test;
+CREATE TABLE reloptions_test(i INT) WITH (vacuum_index_cleanup=auto);
+SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass;
+ reloptions
+-----------------------------
+ {vacuum_index_cleanup=auto}
+(1 row)
+
-- Test vacuum_truncate option
DROP TABLE reloptions_test;
CREATE TEMP TABLE reloptions_test(i INT NOT NULL, j text)
diff --git a/src/test/regress/sql/reloptions.sql b/src/test/regress/sql/reloptions.sql
index 24fbe0b478d..f5980dafcbc 100644
--- a/src/test/regress/sql/reloptions.sql
+++ b/src/test/regress/sql/reloptions.sql
@@ -59,6 +59,27 @@ UPDATE pg_class
ALTER TABLE reloptions_test RESET (illegal_option);
SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass;
+-- Tests for future (FIXME) ternary options
+
+-- behave as boolean option: accept unassigned name and truncated value
+DROP TABLE reloptions_test;
+CREATE TABLE reloptions_test(i INT) WITH (vacuum_truncate);
+SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass;
+
+DROP TABLE reloptions_test;
+CREATE TABLE reloptions_test(i INT) WITH (vacuum_truncate=FaLS);
+SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass;
+
+-- preferred "true" alias is used when storing
+DROP TABLE reloptions_test;
+CREATE TABLE reloptions_test(i INT) WITH (vacuum_index_cleanup=on);
+SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass;
+
+-- custom "third" value is available
+DROP TABLE reloptions_test;
+CREATE TABLE reloptions_test(i INT) WITH (vacuum_index_cleanup=auto);
+SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass;
+
-- Test vacuum_truncate option
DROP TABLE reloptions_test;
diff --git a/src/tools/pgindent/typedefs.list b/src/tools/pgindent/typedefs.list
index 3f3a888fd0e..1c8610fd46c 100644
--- a/src/tools/pgindent/typedefs.list
+++ b/src/tools/pgindent/typedefs.list
@@ -3952,6 +3952,7 @@ pg_sha512_ctx
pg_snapshot
pg_special_case
pg_stack_base_t
+pg_ternary
pg_time_t
pg_time_usec_t
pg_tz
@@ -4079,6 +4080,7 @@ relopt_kind
relopt_parse_elt
relopt_real
relopt_string
+relopt_ternary
relopt_type
relopt_value
relopts_validator
--
2.47.3
--c36t3fndxhkzbl5z--
^ permalink raw reply [nested|flat] 868+ messages in thread
* [PATCH v3] Introduce ternary reloptions
@ 2026-01-16 15:04 Álvaro Herrera <[email protected]>
0 siblings, 0 replies; 868+ messages in thread
From: Álvaro Herrera @ 2026-01-16 15:04 UTC (permalink / raw)
Introduce ternary reloption as a replacement for current
`vacuum_truncate` implementation. Remove the `vacuum_truncate_set`
separate flag and use TERNARY_UNSET instead.
This could also be used for other options such as `vacuum_index_cleanup`
and `buffering`, but lets get the scaffolding in first.
Discussion: https://postgr.es/m/3474141.usfYGdeWWP@thinkpad-pgpro
---
src/backend/access/common/reloptions.c | 137 ++++++++++++++++++-----
src/backend/commands/vacuum.c | 4 +-
src/include/access/reloptions.h | 27 ++---
src/include/postgres.h | 15 +++
src/include/utils/rel.h | 3 +-
src/test/regress/expected/reloptions.out | 36 ++++++
src/test/regress/sql/reloptions.sql | 21 ++++
src/tools/pgindent/typedefs.list | 2 +
8 files changed, 202 insertions(+), 43 deletions(-)
diff --git a/src/backend/access/common/reloptions.c b/src/backend/access/common/reloptions.c
index 0b83f98ed5f..6f1f577581a 100644
--- a/src/backend/access/common/reloptions.c
+++ b/src/backend/access/common/reloptions.c
@@ -40,9 +40,9 @@
*
* To add an option:
*
- * (i) decide on a type (bool, integer, real, enum, string), name, default
- * value, upper and lower bounds (if applicable); for strings, consider a
- * validation routine.
+ * (i) decide on a type (bool, ternary, integer, real, enum, string), name,
+ * default value, upper and lower bounds (if applicable); for strings,
+ * consider a validation routine.
* (ii) add a record below (or use add_<type>_reloption).
* (iii) add it to the appropriate options struct (perhaps StdRdOptions)
* (iv) add it to the appropriate handling routine (perhaps
@@ -147,15 +147,6 @@ static relopt_bool boolRelOpts[] =
},
false
},
- {
- {
- "vacuum_truncate",
- "Enables vacuum to truncate empty pages at the end of this table",
- RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
- ShareUpdateExclusiveLock
- },
- true
- },
{
{
"deduplicate_items",
@@ -170,6 +161,25 @@ static relopt_bool boolRelOpts[] =
{{NULL}}
};
+static relopt_ternary ternaryRelOpts[] =
+{
+ {
+ {
+ "vacuum_truncate",
+ "Enables vacuum to truncate empty pages at the end of this table",
+ RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
+ ShareUpdateExclusiveLock
+ },
+ TERNARY_UNSET
+ },
+ /* list terminator */
+ {
+ {
+ NULL
+ }
+ }
+};
+
static relopt_int intRelOpts[] =
{
{
@@ -609,6 +619,13 @@ initialize_reloptions(void)
boolRelOpts[i].gen.lockmode));
j++;
}
+ for (i = 0; ternaryRelOpts[i].gen.name; i++)
+ {
+ Assert(DoLockModesConflict(ternaryRelOpts[i].gen.lockmode,
+ ternaryRelOpts[i].gen.lockmode));
+ j++;
+ }
+
for (i = 0; intRelOpts[i].gen.name; i++)
{
Assert(DoLockModesConflict(intRelOpts[i].gen.lockmode,
@@ -649,6 +666,14 @@ initialize_reloptions(void)
j++;
}
+ for (i = 0; ternaryRelOpts[i].gen.name; i++)
+ {
+ relOpts[j] = &ternaryRelOpts[i].gen;
+ relOpts[j]->type = RELOPT_TYPE_TERNARY;
+ relOpts[j]->namelen = strlen(relOpts[j]->name);
+ j++;
+ }
+
for (i = 0; intRelOpts[i].gen.name; i++)
{
relOpts[j] = &intRelOpts[i].gen;
@@ -809,6 +834,9 @@ allocate_reloption(bits32 kinds, int type, const char *name, const char *desc,
case RELOPT_TYPE_BOOL:
size = sizeof(relopt_bool);
break;
+ case RELOPT_TYPE_TERNARY:
+ size = sizeof(relopt_ternary);
+ break;
case RELOPT_TYPE_INT:
size = sizeof(relopt_int);
break;
@@ -892,6 +920,57 @@ add_local_bool_reloption(local_relopts *relopts, const char *name,
add_local_reloption(relopts, (relopt_gen *) newoption, offset);
}
+/*
+ * init_ternary_reloption
+ * Allocate and initialize a new ternary reloption
+ */
+static relopt_ternary *
+init_ternary_reloption(bits32 kinds, const char *name, const char *desc,
+ pg_ternary default_val, LOCKMODE lockmode)
+{
+ relopt_ternary *newoption;
+
+ newoption = (relopt_ternary *)
+ allocate_reloption(kinds, RELOPT_TYPE_TERNARY, name, desc, lockmode);
+ newoption->default_val = default_val;
+
+ return newoption;
+}
+
+/*
+ * add_ternary_reloption
+ * Add a new ternary reloption
+ */
+void
+add_ternary_reloption(bits32 kinds, const char *name, const char *desc,
+ pg_ternary default_val, LOCKMODE lockmode)
+{
+ relopt_ternary *newoption;
+
+ newoption =
+ init_ternary_reloption(kinds, name, desc, default_val, lockmode);
+
+ add_reloption((relopt_gen *) newoption);
+}
+
+/*
+ * add_local_ternary_reloption
+ * Add a new ternary local reloption
+ *
+ * 'offset' is offset of ternary-typed field.
+ */
+void
+add_local_ternary_reloption(local_relopts *relopts, const char *name,
+ const char *desc, pg_ternary default_val,
+ int offset)
+{
+ relopt_ternary *newoption;
+
+ newoption =
+ init_ternary_reloption(RELOPT_KIND_LOCAL, name, desc, default_val, 0);
+
+ add_local_reloption(relopts, (relopt_gen *) newoption, offset);
+}
/*
* init_real_reloption
@@ -1626,6 +1705,19 @@ parse_one_reloption(relopt_value *option, char *text_str, int text_len,
option->gen->name, value)));
}
break;
+ case RELOPT_TYPE_TERNARY:
+ {
+ bool b;
+
+ parsed = parse_bool(value, &b);
+ option->values.ternary_val = b ? TERNARY_TRUE : TERNARY_FALSE;
+ if (validate && !parsed)
+ ereport(ERROR,
+ (errcode(ERRCODE_INVALID_PARAMETER_VALUE),
+ errmsg("invalid value for ternary option \"%s\": %s",
+ option->gen->name, value)));
+ }
+ break;
case RELOPT_TYPE_INT:
{
relopt_int *optint = (relopt_int *) option->gen;
@@ -1789,17 +1881,6 @@ fillRelOptions(void *rdopts, Size basesize,
char *itempos = ((char *) rdopts) + elems[j].offset;
char *string_val;
- /*
- * If isset_offset is provided, store whether the reloption is
- * set there.
- */
- if (elems[j].isset_offset > 0)
- {
- char *setpos = ((char *) rdopts) + elems[j].isset_offset;
-
- *(bool *) setpos = options[i].isset;
- }
-
switch (options[i].gen->type)
{
case RELOPT_TYPE_BOOL:
@@ -1807,6 +1888,11 @@ fillRelOptions(void *rdopts, Size basesize,
options[i].values.bool_val :
((relopt_bool *) options[i].gen)->default_val;
break;
+ case RELOPT_TYPE_TERNARY:
+ *(pg_ternary *) itempos = options[i].isset ?
+ options[i].values.ternary_val :
+ ((relopt_ternary *) options[i].gen)->default_val;
+ break;
case RELOPT_TYPE_INT:
*(int *) itempos = options[i].isset ?
options[i].values.int_val :
@@ -1923,8 +2009,8 @@ default_reloptions(Datum reloptions, bool validate, relopt_kind kind)
offsetof(StdRdOptions, parallel_workers)},
{"vacuum_index_cleanup", RELOPT_TYPE_ENUM,
offsetof(StdRdOptions, vacuum_index_cleanup)},
- {"vacuum_truncate", RELOPT_TYPE_BOOL,
- offsetof(StdRdOptions, vacuum_truncate), offsetof(StdRdOptions, vacuum_truncate_set)},
+ {"vacuum_truncate", RELOPT_TYPE_TERNARY,
+ offsetof(StdRdOptions, vacuum_truncate)},
{"vacuum_max_eager_freeze_failure_rate", RELOPT_TYPE_REAL,
offsetof(StdRdOptions, vacuum_max_eager_freeze_failure_rate)}
};
@@ -2004,7 +2090,6 @@ build_local_reloptions(local_relopts *relopts, Datum options, bool validate)
elems[i].optname = opt->option->name;
elems[i].opttype = opt->option->type;
elems[i].offset = opt->offset;
- elems[i].isset_offset = 0; /* not supported for local relopts yet */
i++;
}
diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c
index aa4fbec143f..696eab9bd97 100644
--- a/src/backend/commands/vacuum.c
+++ b/src/backend/commands/vacuum.c
@@ -2224,9 +2224,9 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams params,
{
StdRdOptions *opts = (StdRdOptions *) rel->rd_options;
- if (opts && opts->vacuum_truncate_set)
+ if (opts && opts->vacuum_truncate != TERNARY_UNSET)
{
- if (opts->vacuum_truncate)
+ if (opts->vacuum_truncate == TERNARY_TRUE)
params.truncate = VACOPTVALUE_ENABLED;
else
params.truncate = VACOPTVALUE_DISABLED;
diff --git a/src/include/access/reloptions.h b/src/include/access/reloptions.h
index 2f08e1b0cf0..dfbef2babf2 100644
--- a/src/include/access/reloptions.h
+++ b/src/include/access/reloptions.h
@@ -29,6 +29,7 @@
typedef enum relopt_type
{
RELOPT_TYPE_BOOL,
+ RELOPT_TYPE_TERNARY, /* on, off, unset */
RELOPT_TYPE_INT,
RELOPT_TYPE_REAL,
RELOPT_TYPE_ENUM,
@@ -80,6 +81,7 @@ typedef struct relopt_value
union
{
bool bool_val;
+ pg_ternary ternary_val;
int int_val;
double real_val;
int enum_val;
@@ -94,6 +96,12 @@ typedef struct relopt_bool
bool default_val;
} relopt_bool;
+typedef struct relopt_ternary
+{
+ relopt_gen gen;
+ int default_val;
+} relopt_ternary;
+
typedef struct relopt_int
{
relopt_gen gen;
@@ -152,19 +160,6 @@ typedef struct
const char *optname; /* option's name */
relopt_type opttype; /* option's datatype */
int offset; /* offset of field in result struct */
-
- /*
- * isset_offset is an optional offset of a field in the result struct that
- * stores whether the option is explicitly set for the relation or if it
- * just picked up the default value. In most cases, this can be
- * accomplished by giving the reloption a special out-of-range default
- * value (e.g., some integer reloptions use -2), but this isn't always
- * possible. For example, a Boolean reloption cannot be given an
- * out-of-range default, so we need another way to discover the source of
- * its value. This offset is only used if given a value greater than
- * zero.
- */
- int isset_offset;
} relopt_parse_elt;
/* Local reloption definition */
@@ -195,6 +190,9 @@ typedef struct local_relopts
extern relopt_kind add_reloption_kind(void);
extern void add_bool_reloption(bits32 kinds, const char *name, const char *desc,
bool default_val, LOCKMODE lockmode);
+extern void add_ternary_reloption(bits32 kinds, const char *name,
+ const char *desc, pg_ternary default_val,
+ LOCKMODE lockmode);
extern void add_int_reloption(bits32 kinds, const char *name, const char *desc,
int default_val, int min_val, int max_val,
LOCKMODE lockmode);
@@ -214,6 +212,9 @@ extern void register_reloptions_validator(local_relopts *relopts,
extern void add_local_bool_reloption(local_relopts *relopts, const char *name,
const char *desc, bool default_val,
int offset);
+extern void add_local_ternary_reloption(local_relopts *relopts,
+ const char *name, const char *desc,
+ pg_ternary default_val, int offset);
extern void add_local_int_reloption(local_relopts *relopts, const char *name,
const char *desc, int default_val,
int min_val, int max_val, int offset);
diff --git a/src/include/postgres.h b/src/include/postgres.h
index 7d93fbce709..47c88780776 100644
--- a/src/include/postgres.h
+++ b/src/include/postgres.h
@@ -543,6 +543,21 @@ Float8GetDatum(float8 X)
* ----------------------------------------------------------------
*/
+/*
+ * pg_ternary
+ * Boolean value with an extra "unset" value
+ *
+ * This enum can be used for values that want to distinguish between true,
+ * false, and unset.
+*/
+
+typedef enum pg_ternary
+{
+ TERNARY_FALSE = 0,
+ TERNARY_TRUE = 1,
+ TERNARY_UNSET = -1
+} pg_ternary;
+
/*
* NON_EXEC_STATIC: It's sometimes useful to define a variable or function
* that is normally static but extern when using EXEC_BACKEND (see
diff --git a/src/include/utils/rel.h b/src/include/utils/rel.h
index d03ab247788..5feb18a5373 100644
--- a/src/include/utils/rel.h
+++ b/src/include/utils/rel.h
@@ -347,8 +347,7 @@ typedef struct StdRdOptions
bool user_catalog_table; /* use as an additional catalog relation */
int parallel_workers; /* max number of parallel workers */
StdRdOptIndexCleanup vacuum_index_cleanup; /* controls index vacuuming */
- bool vacuum_truncate; /* enables vacuum to truncate a relation */
- bool vacuum_truncate_set; /* whether vacuum_truncate is set */
+ pg_ternary vacuum_truncate; /* enables vacuum to truncate a relation */
/*
* Fraction of pages in a relation that vacuum can eagerly scan and fail
diff --git a/src/test/regress/expected/reloptions.out b/src/test/regress/expected/reloptions.out
index 9de19b4e3f1..1c99f79ab01 100644
--- a/src/test/regress/expected/reloptions.out
+++ b/src/test/regress/expected/reloptions.out
@@ -98,6 +98,42 @@ SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass;
{fillfactor=13,autovacuum_enabled=false}
(1 row)
+-- Tests for future (FIXME) ternary options
+-- behave as boolean option: accept unassigned name and truncated value
+DROP TABLE reloptions_test;
+CREATE TABLE reloptions_test(i INT) WITH (vacuum_truncate);
+SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass;
+ reloptions
+------------------------
+ {vacuum_truncate=true}
+(1 row)
+
+DROP TABLE reloptions_test;
+CREATE TABLE reloptions_test(i INT) WITH (vacuum_truncate=FaLS);
+SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass;
+ reloptions
+------------------------
+ {vacuum_truncate=fals}
+(1 row)
+
+-- preferred "true" alias is used when storing
+DROP TABLE reloptions_test;
+CREATE TABLE reloptions_test(i INT) WITH (vacuum_index_cleanup=on);
+SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass;
+ reloptions
+---------------------------
+ {vacuum_index_cleanup=on}
+(1 row)
+
+-- custom "third" value is available
+DROP TABLE reloptions_test;
+CREATE TABLE reloptions_test(i INT) WITH (vacuum_index_cleanup=auto);
+SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass;
+ reloptions
+-----------------------------
+ {vacuum_index_cleanup=auto}
+(1 row)
+
-- Test vacuum_truncate option
DROP TABLE reloptions_test;
CREATE TEMP TABLE reloptions_test(i INT NOT NULL, j text)
diff --git a/src/test/regress/sql/reloptions.sql b/src/test/regress/sql/reloptions.sql
index 24fbe0b478d..f5980dafcbc 100644
--- a/src/test/regress/sql/reloptions.sql
+++ b/src/test/regress/sql/reloptions.sql
@@ -59,6 +59,27 @@ UPDATE pg_class
ALTER TABLE reloptions_test RESET (illegal_option);
SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass;
+-- Tests for future (FIXME) ternary options
+
+-- behave as boolean option: accept unassigned name and truncated value
+DROP TABLE reloptions_test;
+CREATE TABLE reloptions_test(i INT) WITH (vacuum_truncate);
+SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass;
+
+DROP TABLE reloptions_test;
+CREATE TABLE reloptions_test(i INT) WITH (vacuum_truncate=FaLS);
+SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass;
+
+-- preferred "true" alias is used when storing
+DROP TABLE reloptions_test;
+CREATE TABLE reloptions_test(i INT) WITH (vacuum_index_cleanup=on);
+SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass;
+
+-- custom "third" value is available
+DROP TABLE reloptions_test;
+CREATE TABLE reloptions_test(i INT) WITH (vacuum_index_cleanup=auto);
+SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass;
+
-- Test vacuum_truncate option
DROP TABLE reloptions_test;
diff --git a/src/tools/pgindent/typedefs.list b/src/tools/pgindent/typedefs.list
index 3f3a888fd0e..1c8610fd46c 100644
--- a/src/tools/pgindent/typedefs.list
+++ b/src/tools/pgindent/typedefs.list
@@ -3952,6 +3952,7 @@ pg_sha512_ctx
pg_snapshot
pg_special_case
pg_stack_base_t
+pg_ternary
pg_time_t
pg_time_usec_t
pg_tz
@@ -4079,6 +4080,7 @@ relopt_kind
relopt_parse_elt
relopt_real
relopt_string
+relopt_ternary
relopt_type
relopt_value
relopts_validator
--
2.47.3
--c36t3fndxhkzbl5z--
^ permalink raw reply [nested|flat] 868+ messages in thread
* [PATCH v3] Introduce ternary reloptions
@ 2026-01-16 15:04 Álvaro Herrera <[email protected]>
0 siblings, 0 replies; 868+ messages in thread
From: Álvaro Herrera @ 2026-01-16 15:04 UTC (permalink / raw)
Introduce ternary reloption as a replacement for current
`vacuum_truncate` implementation. Remove the `vacuum_truncate_set`
separate flag and use TERNARY_UNSET instead.
This could also be used for other options such as `vacuum_index_cleanup`
and `buffering`, but lets get the scaffolding in first.
Discussion: https://postgr.es/m/3474141.usfYGdeWWP@thinkpad-pgpro
---
src/backend/access/common/reloptions.c | 137 ++++++++++++++++++-----
src/backend/commands/vacuum.c | 4 +-
src/include/access/reloptions.h | 27 ++---
src/include/postgres.h | 15 +++
src/include/utils/rel.h | 3 +-
src/test/regress/expected/reloptions.out | 36 ++++++
src/test/regress/sql/reloptions.sql | 21 ++++
src/tools/pgindent/typedefs.list | 2 +
8 files changed, 202 insertions(+), 43 deletions(-)
diff --git a/src/backend/access/common/reloptions.c b/src/backend/access/common/reloptions.c
index 0b83f98ed5f..6f1f577581a 100644
--- a/src/backend/access/common/reloptions.c
+++ b/src/backend/access/common/reloptions.c
@@ -40,9 +40,9 @@
*
* To add an option:
*
- * (i) decide on a type (bool, integer, real, enum, string), name, default
- * value, upper and lower bounds (if applicable); for strings, consider a
- * validation routine.
+ * (i) decide on a type (bool, ternary, integer, real, enum, string), name,
+ * default value, upper and lower bounds (if applicable); for strings,
+ * consider a validation routine.
* (ii) add a record below (or use add_<type>_reloption).
* (iii) add it to the appropriate options struct (perhaps StdRdOptions)
* (iv) add it to the appropriate handling routine (perhaps
@@ -147,15 +147,6 @@ static relopt_bool boolRelOpts[] =
},
false
},
- {
- {
- "vacuum_truncate",
- "Enables vacuum to truncate empty pages at the end of this table",
- RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
- ShareUpdateExclusiveLock
- },
- true
- },
{
{
"deduplicate_items",
@@ -170,6 +161,25 @@ static relopt_bool boolRelOpts[] =
{{NULL}}
};
+static relopt_ternary ternaryRelOpts[] =
+{
+ {
+ {
+ "vacuum_truncate",
+ "Enables vacuum to truncate empty pages at the end of this table",
+ RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
+ ShareUpdateExclusiveLock
+ },
+ TERNARY_UNSET
+ },
+ /* list terminator */
+ {
+ {
+ NULL
+ }
+ }
+};
+
static relopt_int intRelOpts[] =
{
{
@@ -609,6 +619,13 @@ initialize_reloptions(void)
boolRelOpts[i].gen.lockmode));
j++;
}
+ for (i = 0; ternaryRelOpts[i].gen.name; i++)
+ {
+ Assert(DoLockModesConflict(ternaryRelOpts[i].gen.lockmode,
+ ternaryRelOpts[i].gen.lockmode));
+ j++;
+ }
+
for (i = 0; intRelOpts[i].gen.name; i++)
{
Assert(DoLockModesConflict(intRelOpts[i].gen.lockmode,
@@ -649,6 +666,14 @@ initialize_reloptions(void)
j++;
}
+ for (i = 0; ternaryRelOpts[i].gen.name; i++)
+ {
+ relOpts[j] = &ternaryRelOpts[i].gen;
+ relOpts[j]->type = RELOPT_TYPE_TERNARY;
+ relOpts[j]->namelen = strlen(relOpts[j]->name);
+ j++;
+ }
+
for (i = 0; intRelOpts[i].gen.name; i++)
{
relOpts[j] = &intRelOpts[i].gen;
@@ -809,6 +834,9 @@ allocate_reloption(bits32 kinds, int type, const char *name, const char *desc,
case RELOPT_TYPE_BOOL:
size = sizeof(relopt_bool);
break;
+ case RELOPT_TYPE_TERNARY:
+ size = sizeof(relopt_ternary);
+ break;
case RELOPT_TYPE_INT:
size = sizeof(relopt_int);
break;
@@ -892,6 +920,57 @@ add_local_bool_reloption(local_relopts *relopts, const char *name,
add_local_reloption(relopts, (relopt_gen *) newoption, offset);
}
+/*
+ * init_ternary_reloption
+ * Allocate and initialize a new ternary reloption
+ */
+static relopt_ternary *
+init_ternary_reloption(bits32 kinds, const char *name, const char *desc,
+ pg_ternary default_val, LOCKMODE lockmode)
+{
+ relopt_ternary *newoption;
+
+ newoption = (relopt_ternary *)
+ allocate_reloption(kinds, RELOPT_TYPE_TERNARY, name, desc, lockmode);
+ newoption->default_val = default_val;
+
+ return newoption;
+}
+
+/*
+ * add_ternary_reloption
+ * Add a new ternary reloption
+ */
+void
+add_ternary_reloption(bits32 kinds, const char *name, const char *desc,
+ pg_ternary default_val, LOCKMODE lockmode)
+{
+ relopt_ternary *newoption;
+
+ newoption =
+ init_ternary_reloption(kinds, name, desc, default_val, lockmode);
+
+ add_reloption((relopt_gen *) newoption);
+}
+
+/*
+ * add_local_ternary_reloption
+ * Add a new ternary local reloption
+ *
+ * 'offset' is offset of ternary-typed field.
+ */
+void
+add_local_ternary_reloption(local_relopts *relopts, const char *name,
+ const char *desc, pg_ternary default_val,
+ int offset)
+{
+ relopt_ternary *newoption;
+
+ newoption =
+ init_ternary_reloption(RELOPT_KIND_LOCAL, name, desc, default_val, 0);
+
+ add_local_reloption(relopts, (relopt_gen *) newoption, offset);
+}
/*
* init_real_reloption
@@ -1626,6 +1705,19 @@ parse_one_reloption(relopt_value *option, char *text_str, int text_len,
option->gen->name, value)));
}
break;
+ case RELOPT_TYPE_TERNARY:
+ {
+ bool b;
+
+ parsed = parse_bool(value, &b);
+ option->values.ternary_val = b ? TERNARY_TRUE : TERNARY_FALSE;
+ if (validate && !parsed)
+ ereport(ERROR,
+ (errcode(ERRCODE_INVALID_PARAMETER_VALUE),
+ errmsg("invalid value for ternary option \"%s\": %s",
+ option->gen->name, value)));
+ }
+ break;
case RELOPT_TYPE_INT:
{
relopt_int *optint = (relopt_int *) option->gen;
@@ -1789,17 +1881,6 @@ fillRelOptions(void *rdopts, Size basesize,
char *itempos = ((char *) rdopts) + elems[j].offset;
char *string_val;
- /*
- * If isset_offset is provided, store whether the reloption is
- * set there.
- */
- if (elems[j].isset_offset > 0)
- {
- char *setpos = ((char *) rdopts) + elems[j].isset_offset;
-
- *(bool *) setpos = options[i].isset;
- }
-
switch (options[i].gen->type)
{
case RELOPT_TYPE_BOOL:
@@ -1807,6 +1888,11 @@ fillRelOptions(void *rdopts, Size basesize,
options[i].values.bool_val :
((relopt_bool *) options[i].gen)->default_val;
break;
+ case RELOPT_TYPE_TERNARY:
+ *(pg_ternary *) itempos = options[i].isset ?
+ options[i].values.ternary_val :
+ ((relopt_ternary *) options[i].gen)->default_val;
+ break;
case RELOPT_TYPE_INT:
*(int *) itempos = options[i].isset ?
options[i].values.int_val :
@@ -1923,8 +2009,8 @@ default_reloptions(Datum reloptions, bool validate, relopt_kind kind)
offsetof(StdRdOptions, parallel_workers)},
{"vacuum_index_cleanup", RELOPT_TYPE_ENUM,
offsetof(StdRdOptions, vacuum_index_cleanup)},
- {"vacuum_truncate", RELOPT_TYPE_BOOL,
- offsetof(StdRdOptions, vacuum_truncate), offsetof(StdRdOptions, vacuum_truncate_set)},
+ {"vacuum_truncate", RELOPT_TYPE_TERNARY,
+ offsetof(StdRdOptions, vacuum_truncate)},
{"vacuum_max_eager_freeze_failure_rate", RELOPT_TYPE_REAL,
offsetof(StdRdOptions, vacuum_max_eager_freeze_failure_rate)}
};
@@ -2004,7 +2090,6 @@ build_local_reloptions(local_relopts *relopts, Datum options, bool validate)
elems[i].optname = opt->option->name;
elems[i].opttype = opt->option->type;
elems[i].offset = opt->offset;
- elems[i].isset_offset = 0; /* not supported for local relopts yet */
i++;
}
diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c
index aa4fbec143f..696eab9bd97 100644
--- a/src/backend/commands/vacuum.c
+++ b/src/backend/commands/vacuum.c
@@ -2224,9 +2224,9 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams params,
{
StdRdOptions *opts = (StdRdOptions *) rel->rd_options;
- if (opts && opts->vacuum_truncate_set)
+ if (opts && opts->vacuum_truncate != TERNARY_UNSET)
{
- if (opts->vacuum_truncate)
+ if (opts->vacuum_truncate == TERNARY_TRUE)
params.truncate = VACOPTVALUE_ENABLED;
else
params.truncate = VACOPTVALUE_DISABLED;
diff --git a/src/include/access/reloptions.h b/src/include/access/reloptions.h
index 2f08e1b0cf0..dfbef2babf2 100644
--- a/src/include/access/reloptions.h
+++ b/src/include/access/reloptions.h
@@ -29,6 +29,7 @@
typedef enum relopt_type
{
RELOPT_TYPE_BOOL,
+ RELOPT_TYPE_TERNARY, /* on, off, unset */
RELOPT_TYPE_INT,
RELOPT_TYPE_REAL,
RELOPT_TYPE_ENUM,
@@ -80,6 +81,7 @@ typedef struct relopt_value
union
{
bool bool_val;
+ pg_ternary ternary_val;
int int_val;
double real_val;
int enum_val;
@@ -94,6 +96,12 @@ typedef struct relopt_bool
bool default_val;
} relopt_bool;
+typedef struct relopt_ternary
+{
+ relopt_gen gen;
+ int default_val;
+} relopt_ternary;
+
typedef struct relopt_int
{
relopt_gen gen;
@@ -152,19 +160,6 @@ typedef struct
const char *optname; /* option's name */
relopt_type opttype; /* option's datatype */
int offset; /* offset of field in result struct */
-
- /*
- * isset_offset is an optional offset of a field in the result struct that
- * stores whether the option is explicitly set for the relation or if it
- * just picked up the default value. In most cases, this can be
- * accomplished by giving the reloption a special out-of-range default
- * value (e.g., some integer reloptions use -2), but this isn't always
- * possible. For example, a Boolean reloption cannot be given an
- * out-of-range default, so we need another way to discover the source of
- * its value. This offset is only used if given a value greater than
- * zero.
- */
- int isset_offset;
} relopt_parse_elt;
/* Local reloption definition */
@@ -195,6 +190,9 @@ typedef struct local_relopts
extern relopt_kind add_reloption_kind(void);
extern void add_bool_reloption(bits32 kinds, const char *name, const char *desc,
bool default_val, LOCKMODE lockmode);
+extern void add_ternary_reloption(bits32 kinds, const char *name,
+ const char *desc, pg_ternary default_val,
+ LOCKMODE lockmode);
extern void add_int_reloption(bits32 kinds, const char *name, const char *desc,
int default_val, int min_val, int max_val,
LOCKMODE lockmode);
@@ -214,6 +212,9 @@ extern void register_reloptions_validator(local_relopts *relopts,
extern void add_local_bool_reloption(local_relopts *relopts, const char *name,
const char *desc, bool default_val,
int offset);
+extern void add_local_ternary_reloption(local_relopts *relopts,
+ const char *name, const char *desc,
+ pg_ternary default_val, int offset);
extern void add_local_int_reloption(local_relopts *relopts, const char *name,
const char *desc, int default_val,
int min_val, int max_val, int offset);
diff --git a/src/include/postgres.h b/src/include/postgres.h
index 7d93fbce709..47c88780776 100644
--- a/src/include/postgres.h
+++ b/src/include/postgres.h
@@ -543,6 +543,21 @@ Float8GetDatum(float8 X)
* ----------------------------------------------------------------
*/
+/*
+ * pg_ternary
+ * Boolean value with an extra "unset" value
+ *
+ * This enum can be used for values that want to distinguish between true,
+ * false, and unset.
+*/
+
+typedef enum pg_ternary
+{
+ TERNARY_FALSE = 0,
+ TERNARY_TRUE = 1,
+ TERNARY_UNSET = -1
+} pg_ternary;
+
/*
* NON_EXEC_STATIC: It's sometimes useful to define a variable or function
* that is normally static but extern when using EXEC_BACKEND (see
diff --git a/src/include/utils/rel.h b/src/include/utils/rel.h
index d03ab247788..5feb18a5373 100644
--- a/src/include/utils/rel.h
+++ b/src/include/utils/rel.h
@@ -347,8 +347,7 @@ typedef struct StdRdOptions
bool user_catalog_table; /* use as an additional catalog relation */
int parallel_workers; /* max number of parallel workers */
StdRdOptIndexCleanup vacuum_index_cleanup; /* controls index vacuuming */
- bool vacuum_truncate; /* enables vacuum to truncate a relation */
- bool vacuum_truncate_set; /* whether vacuum_truncate is set */
+ pg_ternary vacuum_truncate; /* enables vacuum to truncate a relation */
/*
* Fraction of pages in a relation that vacuum can eagerly scan and fail
diff --git a/src/test/regress/expected/reloptions.out b/src/test/regress/expected/reloptions.out
index 9de19b4e3f1..1c99f79ab01 100644
--- a/src/test/regress/expected/reloptions.out
+++ b/src/test/regress/expected/reloptions.out
@@ -98,6 +98,42 @@ SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass;
{fillfactor=13,autovacuum_enabled=false}
(1 row)
+-- Tests for future (FIXME) ternary options
+-- behave as boolean option: accept unassigned name and truncated value
+DROP TABLE reloptions_test;
+CREATE TABLE reloptions_test(i INT) WITH (vacuum_truncate);
+SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass;
+ reloptions
+------------------------
+ {vacuum_truncate=true}
+(1 row)
+
+DROP TABLE reloptions_test;
+CREATE TABLE reloptions_test(i INT) WITH (vacuum_truncate=FaLS);
+SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass;
+ reloptions
+------------------------
+ {vacuum_truncate=fals}
+(1 row)
+
+-- preferred "true" alias is used when storing
+DROP TABLE reloptions_test;
+CREATE TABLE reloptions_test(i INT) WITH (vacuum_index_cleanup=on);
+SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass;
+ reloptions
+---------------------------
+ {vacuum_index_cleanup=on}
+(1 row)
+
+-- custom "third" value is available
+DROP TABLE reloptions_test;
+CREATE TABLE reloptions_test(i INT) WITH (vacuum_index_cleanup=auto);
+SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass;
+ reloptions
+-----------------------------
+ {vacuum_index_cleanup=auto}
+(1 row)
+
-- Test vacuum_truncate option
DROP TABLE reloptions_test;
CREATE TEMP TABLE reloptions_test(i INT NOT NULL, j text)
diff --git a/src/test/regress/sql/reloptions.sql b/src/test/regress/sql/reloptions.sql
index 24fbe0b478d..f5980dafcbc 100644
--- a/src/test/regress/sql/reloptions.sql
+++ b/src/test/regress/sql/reloptions.sql
@@ -59,6 +59,27 @@ UPDATE pg_class
ALTER TABLE reloptions_test RESET (illegal_option);
SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass;
+-- Tests for future (FIXME) ternary options
+
+-- behave as boolean option: accept unassigned name and truncated value
+DROP TABLE reloptions_test;
+CREATE TABLE reloptions_test(i INT) WITH (vacuum_truncate);
+SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass;
+
+DROP TABLE reloptions_test;
+CREATE TABLE reloptions_test(i INT) WITH (vacuum_truncate=FaLS);
+SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass;
+
+-- preferred "true" alias is used when storing
+DROP TABLE reloptions_test;
+CREATE TABLE reloptions_test(i INT) WITH (vacuum_index_cleanup=on);
+SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass;
+
+-- custom "third" value is available
+DROP TABLE reloptions_test;
+CREATE TABLE reloptions_test(i INT) WITH (vacuum_index_cleanup=auto);
+SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass;
+
-- Test vacuum_truncate option
DROP TABLE reloptions_test;
diff --git a/src/tools/pgindent/typedefs.list b/src/tools/pgindent/typedefs.list
index 3f3a888fd0e..1c8610fd46c 100644
--- a/src/tools/pgindent/typedefs.list
+++ b/src/tools/pgindent/typedefs.list
@@ -3952,6 +3952,7 @@ pg_sha512_ctx
pg_snapshot
pg_special_case
pg_stack_base_t
+pg_ternary
pg_time_t
pg_time_usec_t
pg_tz
@@ -4079,6 +4080,7 @@ relopt_kind
relopt_parse_elt
relopt_real
relopt_string
+relopt_ternary
relopt_type
relopt_value
relopts_validator
--
2.47.3
--c36t3fndxhkzbl5z--
^ permalink raw reply [nested|flat] 868+ messages in thread
* [PATCH v3] Introduce ternary reloptions
@ 2026-01-16 15:04 Álvaro Herrera <[email protected]>
0 siblings, 0 replies; 868+ messages in thread
From: Álvaro Herrera @ 2026-01-16 15:04 UTC (permalink / raw)
Introduce ternary reloption as a replacement for current
`vacuum_truncate` implementation. Remove the `vacuum_truncate_set`
separate flag and use TERNARY_UNSET instead.
This could also be used for other options such as `vacuum_index_cleanup`
and `buffering`, but lets get the scaffolding in first.
Discussion: https://postgr.es/m/3474141.usfYGdeWWP@thinkpad-pgpro
---
src/backend/access/common/reloptions.c | 137 ++++++++++++++++++-----
src/backend/commands/vacuum.c | 4 +-
src/include/access/reloptions.h | 27 ++---
src/include/postgres.h | 15 +++
src/include/utils/rel.h | 3 +-
src/test/regress/expected/reloptions.out | 36 ++++++
src/test/regress/sql/reloptions.sql | 21 ++++
src/tools/pgindent/typedefs.list | 2 +
8 files changed, 202 insertions(+), 43 deletions(-)
diff --git a/src/backend/access/common/reloptions.c b/src/backend/access/common/reloptions.c
index 0b83f98ed5f..6f1f577581a 100644
--- a/src/backend/access/common/reloptions.c
+++ b/src/backend/access/common/reloptions.c
@@ -40,9 +40,9 @@
*
* To add an option:
*
- * (i) decide on a type (bool, integer, real, enum, string), name, default
- * value, upper and lower bounds (if applicable); for strings, consider a
- * validation routine.
+ * (i) decide on a type (bool, ternary, integer, real, enum, string), name,
+ * default value, upper and lower bounds (if applicable); for strings,
+ * consider a validation routine.
* (ii) add a record below (or use add_<type>_reloption).
* (iii) add it to the appropriate options struct (perhaps StdRdOptions)
* (iv) add it to the appropriate handling routine (perhaps
@@ -147,15 +147,6 @@ static relopt_bool boolRelOpts[] =
},
false
},
- {
- {
- "vacuum_truncate",
- "Enables vacuum to truncate empty pages at the end of this table",
- RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
- ShareUpdateExclusiveLock
- },
- true
- },
{
{
"deduplicate_items",
@@ -170,6 +161,25 @@ static relopt_bool boolRelOpts[] =
{{NULL}}
};
+static relopt_ternary ternaryRelOpts[] =
+{
+ {
+ {
+ "vacuum_truncate",
+ "Enables vacuum to truncate empty pages at the end of this table",
+ RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
+ ShareUpdateExclusiveLock
+ },
+ TERNARY_UNSET
+ },
+ /* list terminator */
+ {
+ {
+ NULL
+ }
+ }
+};
+
static relopt_int intRelOpts[] =
{
{
@@ -609,6 +619,13 @@ initialize_reloptions(void)
boolRelOpts[i].gen.lockmode));
j++;
}
+ for (i = 0; ternaryRelOpts[i].gen.name; i++)
+ {
+ Assert(DoLockModesConflict(ternaryRelOpts[i].gen.lockmode,
+ ternaryRelOpts[i].gen.lockmode));
+ j++;
+ }
+
for (i = 0; intRelOpts[i].gen.name; i++)
{
Assert(DoLockModesConflict(intRelOpts[i].gen.lockmode,
@@ -649,6 +666,14 @@ initialize_reloptions(void)
j++;
}
+ for (i = 0; ternaryRelOpts[i].gen.name; i++)
+ {
+ relOpts[j] = &ternaryRelOpts[i].gen;
+ relOpts[j]->type = RELOPT_TYPE_TERNARY;
+ relOpts[j]->namelen = strlen(relOpts[j]->name);
+ j++;
+ }
+
for (i = 0; intRelOpts[i].gen.name; i++)
{
relOpts[j] = &intRelOpts[i].gen;
@@ -809,6 +834,9 @@ allocate_reloption(bits32 kinds, int type, const char *name, const char *desc,
case RELOPT_TYPE_BOOL:
size = sizeof(relopt_bool);
break;
+ case RELOPT_TYPE_TERNARY:
+ size = sizeof(relopt_ternary);
+ break;
case RELOPT_TYPE_INT:
size = sizeof(relopt_int);
break;
@@ -892,6 +920,57 @@ add_local_bool_reloption(local_relopts *relopts, const char *name,
add_local_reloption(relopts, (relopt_gen *) newoption, offset);
}
+/*
+ * init_ternary_reloption
+ * Allocate and initialize a new ternary reloption
+ */
+static relopt_ternary *
+init_ternary_reloption(bits32 kinds, const char *name, const char *desc,
+ pg_ternary default_val, LOCKMODE lockmode)
+{
+ relopt_ternary *newoption;
+
+ newoption = (relopt_ternary *)
+ allocate_reloption(kinds, RELOPT_TYPE_TERNARY, name, desc, lockmode);
+ newoption->default_val = default_val;
+
+ return newoption;
+}
+
+/*
+ * add_ternary_reloption
+ * Add a new ternary reloption
+ */
+void
+add_ternary_reloption(bits32 kinds, const char *name, const char *desc,
+ pg_ternary default_val, LOCKMODE lockmode)
+{
+ relopt_ternary *newoption;
+
+ newoption =
+ init_ternary_reloption(kinds, name, desc, default_val, lockmode);
+
+ add_reloption((relopt_gen *) newoption);
+}
+
+/*
+ * add_local_ternary_reloption
+ * Add a new ternary local reloption
+ *
+ * 'offset' is offset of ternary-typed field.
+ */
+void
+add_local_ternary_reloption(local_relopts *relopts, const char *name,
+ const char *desc, pg_ternary default_val,
+ int offset)
+{
+ relopt_ternary *newoption;
+
+ newoption =
+ init_ternary_reloption(RELOPT_KIND_LOCAL, name, desc, default_val, 0);
+
+ add_local_reloption(relopts, (relopt_gen *) newoption, offset);
+}
/*
* init_real_reloption
@@ -1626,6 +1705,19 @@ parse_one_reloption(relopt_value *option, char *text_str, int text_len,
option->gen->name, value)));
}
break;
+ case RELOPT_TYPE_TERNARY:
+ {
+ bool b;
+
+ parsed = parse_bool(value, &b);
+ option->values.ternary_val = b ? TERNARY_TRUE : TERNARY_FALSE;
+ if (validate && !parsed)
+ ereport(ERROR,
+ (errcode(ERRCODE_INVALID_PARAMETER_VALUE),
+ errmsg("invalid value for ternary option \"%s\": %s",
+ option->gen->name, value)));
+ }
+ break;
case RELOPT_TYPE_INT:
{
relopt_int *optint = (relopt_int *) option->gen;
@@ -1789,17 +1881,6 @@ fillRelOptions(void *rdopts, Size basesize,
char *itempos = ((char *) rdopts) + elems[j].offset;
char *string_val;
- /*
- * If isset_offset is provided, store whether the reloption is
- * set there.
- */
- if (elems[j].isset_offset > 0)
- {
- char *setpos = ((char *) rdopts) + elems[j].isset_offset;
-
- *(bool *) setpos = options[i].isset;
- }
-
switch (options[i].gen->type)
{
case RELOPT_TYPE_BOOL:
@@ -1807,6 +1888,11 @@ fillRelOptions(void *rdopts, Size basesize,
options[i].values.bool_val :
((relopt_bool *) options[i].gen)->default_val;
break;
+ case RELOPT_TYPE_TERNARY:
+ *(pg_ternary *) itempos = options[i].isset ?
+ options[i].values.ternary_val :
+ ((relopt_ternary *) options[i].gen)->default_val;
+ break;
case RELOPT_TYPE_INT:
*(int *) itempos = options[i].isset ?
options[i].values.int_val :
@@ -1923,8 +2009,8 @@ default_reloptions(Datum reloptions, bool validate, relopt_kind kind)
offsetof(StdRdOptions, parallel_workers)},
{"vacuum_index_cleanup", RELOPT_TYPE_ENUM,
offsetof(StdRdOptions, vacuum_index_cleanup)},
- {"vacuum_truncate", RELOPT_TYPE_BOOL,
- offsetof(StdRdOptions, vacuum_truncate), offsetof(StdRdOptions, vacuum_truncate_set)},
+ {"vacuum_truncate", RELOPT_TYPE_TERNARY,
+ offsetof(StdRdOptions, vacuum_truncate)},
{"vacuum_max_eager_freeze_failure_rate", RELOPT_TYPE_REAL,
offsetof(StdRdOptions, vacuum_max_eager_freeze_failure_rate)}
};
@@ -2004,7 +2090,6 @@ build_local_reloptions(local_relopts *relopts, Datum options, bool validate)
elems[i].optname = opt->option->name;
elems[i].opttype = opt->option->type;
elems[i].offset = opt->offset;
- elems[i].isset_offset = 0; /* not supported for local relopts yet */
i++;
}
diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c
index aa4fbec143f..696eab9bd97 100644
--- a/src/backend/commands/vacuum.c
+++ b/src/backend/commands/vacuum.c
@@ -2224,9 +2224,9 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams params,
{
StdRdOptions *opts = (StdRdOptions *) rel->rd_options;
- if (opts && opts->vacuum_truncate_set)
+ if (opts && opts->vacuum_truncate != TERNARY_UNSET)
{
- if (opts->vacuum_truncate)
+ if (opts->vacuum_truncate == TERNARY_TRUE)
params.truncate = VACOPTVALUE_ENABLED;
else
params.truncate = VACOPTVALUE_DISABLED;
diff --git a/src/include/access/reloptions.h b/src/include/access/reloptions.h
index 2f08e1b0cf0..dfbef2babf2 100644
--- a/src/include/access/reloptions.h
+++ b/src/include/access/reloptions.h
@@ -29,6 +29,7 @@
typedef enum relopt_type
{
RELOPT_TYPE_BOOL,
+ RELOPT_TYPE_TERNARY, /* on, off, unset */
RELOPT_TYPE_INT,
RELOPT_TYPE_REAL,
RELOPT_TYPE_ENUM,
@@ -80,6 +81,7 @@ typedef struct relopt_value
union
{
bool bool_val;
+ pg_ternary ternary_val;
int int_val;
double real_val;
int enum_val;
@@ -94,6 +96,12 @@ typedef struct relopt_bool
bool default_val;
} relopt_bool;
+typedef struct relopt_ternary
+{
+ relopt_gen gen;
+ int default_val;
+} relopt_ternary;
+
typedef struct relopt_int
{
relopt_gen gen;
@@ -152,19 +160,6 @@ typedef struct
const char *optname; /* option's name */
relopt_type opttype; /* option's datatype */
int offset; /* offset of field in result struct */
-
- /*
- * isset_offset is an optional offset of a field in the result struct that
- * stores whether the option is explicitly set for the relation or if it
- * just picked up the default value. In most cases, this can be
- * accomplished by giving the reloption a special out-of-range default
- * value (e.g., some integer reloptions use -2), but this isn't always
- * possible. For example, a Boolean reloption cannot be given an
- * out-of-range default, so we need another way to discover the source of
- * its value. This offset is only used if given a value greater than
- * zero.
- */
- int isset_offset;
} relopt_parse_elt;
/* Local reloption definition */
@@ -195,6 +190,9 @@ typedef struct local_relopts
extern relopt_kind add_reloption_kind(void);
extern void add_bool_reloption(bits32 kinds, const char *name, const char *desc,
bool default_val, LOCKMODE lockmode);
+extern void add_ternary_reloption(bits32 kinds, const char *name,
+ const char *desc, pg_ternary default_val,
+ LOCKMODE lockmode);
extern void add_int_reloption(bits32 kinds, const char *name, const char *desc,
int default_val, int min_val, int max_val,
LOCKMODE lockmode);
@@ -214,6 +212,9 @@ extern void register_reloptions_validator(local_relopts *relopts,
extern void add_local_bool_reloption(local_relopts *relopts, const char *name,
const char *desc, bool default_val,
int offset);
+extern void add_local_ternary_reloption(local_relopts *relopts,
+ const char *name, const char *desc,
+ pg_ternary default_val, int offset);
extern void add_local_int_reloption(local_relopts *relopts, const char *name,
const char *desc, int default_val,
int min_val, int max_val, int offset);
diff --git a/src/include/postgres.h b/src/include/postgres.h
index 7d93fbce709..47c88780776 100644
--- a/src/include/postgres.h
+++ b/src/include/postgres.h
@@ -543,6 +543,21 @@ Float8GetDatum(float8 X)
* ----------------------------------------------------------------
*/
+/*
+ * pg_ternary
+ * Boolean value with an extra "unset" value
+ *
+ * This enum can be used for values that want to distinguish between true,
+ * false, and unset.
+*/
+
+typedef enum pg_ternary
+{
+ TERNARY_FALSE = 0,
+ TERNARY_TRUE = 1,
+ TERNARY_UNSET = -1
+} pg_ternary;
+
/*
* NON_EXEC_STATIC: It's sometimes useful to define a variable or function
* that is normally static but extern when using EXEC_BACKEND (see
diff --git a/src/include/utils/rel.h b/src/include/utils/rel.h
index d03ab247788..5feb18a5373 100644
--- a/src/include/utils/rel.h
+++ b/src/include/utils/rel.h
@@ -347,8 +347,7 @@ typedef struct StdRdOptions
bool user_catalog_table; /* use as an additional catalog relation */
int parallel_workers; /* max number of parallel workers */
StdRdOptIndexCleanup vacuum_index_cleanup; /* controls index vacuuming */
- bool vacuum_truncate; /* enables vacuum to truncate a relation */
- bool vacuum_truncate_set; /* whether vacuum_truncate is set */
+ pg_ternary vacuum_truncate; /* enables vacuum to truncate a relation */
/*
* Fraction of pages in a relation that vacuum can eagerly scan and fail
diff --git a/src/test/regress/expected/reloptions.out b/src/test/regress/expected/reloptions.out
index 9de19b4e3f1..1c99f79ab01 100644
--- a/src/test/regress/expected/reloptions.out
+++ b/src/test/regress/expected/reloptions.out
@@ -98,6 +98,42 @@ SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass;
{fillfactor=13,autovacuum_enabled=false}
(1 row)
+-- Tests for future (FIXME) ternary options
+-- behave as boolean option: accept unassigned name and truncated value
+DROP TABLE reloptions_test;
+CREATE TABLE reloptions_test(i INT) WITH (vacuum_truncate);
+SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass;
+ reloptions
+------------------------
+ {vacuum_truncate=true}
+(1 row)
+
+DROP TABLE reloptions_test;
+CREATE TABLE reloptions_test(i INT) WITH (vacuum_truncate=FaLS);
+SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass;
+ reloptions
+------------------------
+ {vacuum_truncate=fals}
+(1 row)
+
+-- preferred "true" alias is used when storing
+DROP TABLE reloptions_test;
+CREATE TABLE reloptions_test(i INT) WITH (vacuum_index_cleanup=on);
+SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass;
+ reloptions
+---------------------------
+ {vacuum_index_cleanup=on}
+(1 row)
+
+-- custom "third" value is available
+DROP TABLE reloptions_test;
+CREATE TABLE reloptions_test(i INT) WITH (vacuum_index_cleanup=auto);
+SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass;
+ reloptions
+-----------------------------
+ {vacuum_index_cleanup=auto}
+(1 row)
+
-- Test vacuum_truncate option
DROP TABLE reloptions_test;
CREATE TEMP TABLE reloptions_test(i INT NOT NULL, j text)
diff --git a/src/test/regress/sql/reloptions.sql b/src/test/regress/sql/reloptions.sql
index 24fbe0b478d..f5980dafcbc 100644
--- a/src/test/regress/sql/reloptions.sql
+++ b/src/test/regress/sql/reloptions.sql
@@ -59,6 +59,27 @@ UPDATE pg_class
ALTER TABLE reloptions_test RESET (illegal_option);
SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass;
+-- Tests for future (FIXME) ternary options
+
+-- behave as boolean option: accept unassigned name and truncated value
+DROP TABLE reloptions_test;
+CREATE TABLE reloptions_test(i INT) WITH (vacuum_truncate);
+SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass;
+
+DROP TABLE reloptions_test;
+CREATE TABLE reloptions_test(i INT) WITH (vacuum_truncate=FaLS);
+SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass;
+
+-- preferred "true" alias is used when storing
+DROP TABLE reloptions_test;
+CREATE TABLE reloptions_test(i INT) WITH (vacuum_index_cleanup=on);
+SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass;
+
+-- custom "third" value is available
+DROP TABLE reloptions_test;
+CREATE TABLE reloptions_test(i INT) WITH (vacuum_index_cleanup=auto);
+SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass;
+
-- Test vacuum_truncate option
DROP TABLE reloptions_test;
diff --git a/src/tools/pgindent/typedefs.list b/src/tools/pgindent/typedefs.list
index 3f3a888fd0e..1c8610fd46c 100644
--- a/src/tools/pgindent/typedefs.list
+++ b/src/tools/pgindent/typedefs.list
@@ -3952,6 +3952,7 @@ pg_sha512_ctx
pg_snapshot
pg_special_case
pg_stack_base_t
+pg_ternary
pg_time_t
pg_time_usec_t
pg_tz
@@ -4079,6 +4080,7 @@ relopt_kind
relopt_parse_elt
relopt_real
relopt_string
+relopt_ternary
relopt_type
relopt_value
relopts_validator
--
2.47.3
--c36t3fndxhkzbl5z--
^ permalink raw reply [nested|flat] 868+ messages in thread
* [PATCH v3] Introduce ternary reloptions
@ 2026-01-16 15:04 Álvaro Herrera <[email protected]>
0 siblings, 0 replies; 868+ messages in thread
From: Álvaro Herrera @ 2026-01-16 15:04 UTC (permalink / raw)
Introduce ternary reloption as a replacement for current
`vacuum_truncate` implementation. Remove the `vacuum_truncate_set`
separate flag and use TERNARY_UNSET instead.
This could also be used for other options such as `vacuum_index_cleanup`
and `buffering`, but lets get the scaffolding in first.
Discussion: https://postgr.es/m/3474141.usfYGdeWWP@thinkpad-pgpro
---
src/backend/access/common/reloptions.c | 137 ++++++++++++++++++-----
src/backend/commands/vacuum.c | 4 +-
src/include/access/reloptions.h | 27 ++---
src/include/postgres.h | 15 +++
src/include/utils/rel.h | 3 +-
src/test/regress/expected/reloptions.out | 36 ++++++
src/test/regress/sql/reloptions.sql | 21 ++++
src/tools/pgindent/typedefs.list | 2 +
8 files changed, 202 insertions(+), 43 deletions(-)
diff --git a/src/backend/access/common/reloptions.c b/src/backend/access/common/reloptions.c
index 0b83f98ed5f..6f1f577581a 100644
--- a/src/backend/access/common/reloptions.c
+++ b/src/backend/access/common/reloptions.c
@@ -40,9 +40,9 @@
*
* To add an option:
*
- * (i) decide on a type (bool, integer, real, enum, string), name, default
- * value, upper and lower bounds (if applicable); for strings, consider a
- * validation routine.
+ * (i) decide on a type (bool, ternary, integer, real, enum, string), name,
+ * default value, upper and lower bounds (if applicable); for strings,
+ * consider a validation routine.
* (ii) add a record below (or use add_<type>_reloption).
* (iii) add it to the appropriate options struct (perhaps StdRdOptions)
* (iv) add it to the appropriate handling routine (perhaps
@@ -147,15 +147,6 @@ static relopt_bool boolRelOpts[] =
},
false
},
- {
- {
- "vacuum_truncate",
- "Enables vacuum to truncate empty pages at the end of this table",
- RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
- ShareUpdateExclusiveLock
- },
- true
- },
{
{
"deduplicate_items",
@@ -170,6 +161,25 @@ static relopt_bool boolRelOpts[] =
{{NULL}}
};
+static relopt_ternary ternaryRelOpts[] =
+{
+ {
+ {
+ "vacuum_truncate",
+ "Enables vacuum to truncate empty pages at the end of this table",
+ RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
+ ShareUpdateExclusiveLock
+ },
+ TERNARY_UNSET
+ },
+ /* list terminator */
+ {
+ {
+ NULL
+ }
+ }
+};
+
static relopt_int intRelOpts[] =
{
{
@@ -609,6 +619,13 @@ initialize_reloptions(void)
boolRelOpts[i].gen.lockmode));
j++;
}
+ for (i = 0; ternaryRelOpts[i].gen.name; i++)
+ {
+ Assert(DoLockModesConflict(ternaryRelOpts[i].gen.lockmode,
+ ternaryRelOpts[i].gen.lockmode));
+ j++;
+ }
+
for (i = 0; intRelOpts[i].gen.name; i++)
{
Assert(DoLockModesConflict(intRelOpts[i].gen.lockmode,
@@ -649,6 +666,14 @@ initialize_reloptions(void)
j++;
}
+ for (i = 0; ternaryRelOpts[i].gen.name; i++)
+ {
+ relOpts[j] = &ternaryRelOpts[i].gen;
+ relOpts[j]->type = RELOPT_TYPE_TERNARY;
+ relOpts[j]->namelen = strlen(relOpts[j]->name);
+ j++;
+ }
+
for (i = 0; intRelOpts[i].gen.name; i++)
{
relOpts[j] = &intRelOpts[i].gen;
@@ -809,6 +834,9 @@ allocate_reloption(bits32 kinds, int type, const char *name, const char *desc,
case RELOPT_TYPE_BOOL:
size = sizeof(relopt_bool);
break;
+ case RELOPT_TYPE_TERNARY:
+ size = sizeof(relopt_ternary);
+ break;
case RELOPT_TYPE_INT:
size = sizeof(relopt_int);
break;
@@ -892,6 +920,57 @@ add_local_bool_reloption(local_relopts *relopts, const char *name,
add_local_reloption(relopts, (relopt_gen *) newoption, offset);
}
+/*
+ * init_ternary_reloption
+ * Allocate and initialize a new ternary reloption
+ */
+static relopt_ternary *
+init_ternary_reloption(bits32 kinds, const char *name, const char *desc,
+ pg_ternary default_val, LOCKMODE lockmode)
+{
+ relopt_ternary *newoption;
+
+ newoption = (relopt_ternary *)
+ allocate_reloption(kinds, RELOPT_TYPE_TERNARY, name, desc, lockmode);
+ newoption->default_val = default_val;
+
+ return newoption;
+}
+
+/*
+ * add_ternary_reloption
+ * Add a new ternary reloption
+ */
+void
+add_ternary_reloption(bits32 kinds, const char *name, const char *desc,
+ pg_ternary default_val, LOCKMODE lockmode)
+{
+ relopt_ternary *newoption;
+
+ newoption =
+ init_ternary_reloption(kinds, name, desc, default_val, lockmode);
+
+ add_reloption((relopt_gen *) newoption);
+}
+
+/*
+ * add_local_ternary_reloption
+ * Add a new ternary local reloption
+ *
+ * 'offset' is offset of ternary-typed field.
+ */
+void
+add_local_ternary_reloption(local_relopts *relopts, const char *name,
+ const char *desc, pg_ternary default_val,
+ int offset)
+{
+ relopt_ternary *newoption;
+
+ newoption =
+ init_ternary_reloption(RELOPT_KIND_LOCAL, name, desc, default_val, 0);
+
+ add_local_reloption(relopts, (relopt_gen *) newoption, offset);
+}
/*
* init_real_reloption
@@ -1626,6 +1705,19 @@ parse_one_reloption(relopt_value *option, char *text_str, int text_len,
option->gen->name, value)));
}
break;
+ case RELOPT_TYPE_TERNARY:
+ {
+ bool b;
+
+ parsed = parse_bool(value, &b);
+ option->values.ternary_val = b ? TERNARY_TRUE : TERNARY_FALSE;
+ if (validate && !parsed)
+ ereport(ERROR,
+ (errcode(ERRCODE_INVALID_PARAMETER_VALUE),
+ errmsg("invalid value for ternary option \"%s\": %s",
+ option->gen->name, value)));
+ }
+ break;
case RELOPT_TYPE_INT:
{
relopt_int *optint = (relopt_int *) option->gen;
@@ -1789,17 +1881,6 @@ fillRelOptions(void *rdopts, Size basesize,
char *itempos = ((char *) rdopts) + elems[j].offset;
char *string_val;
- /*
- * If isset_offset is provided, store whether the reloption is
- * set there.
- */
- if (elems[j].isset_offset > 0)
- {
- char *setpos = ((char *) rdopts) + elems[j].isset_offset;
-
- *(bool *) setpos = options[i].isset;
- }
-
switch (options[i].gen->type)
{
case RELOPT_TYPE_BOOL:
@@ -1807,6 +1888,11 @@ fillRelOptions(void *rdopts, Size basesize,
options[i].values.bool_val :
((relopt_bool *) options[i].gen)->default_val;
break;
+ case RELOPT_TYPE_TERNARY:
+ *(pg_ternary *) itempos = options[i].isset ?
+ options[i].values.ternary_val :
+ ((relopt_ternary *) options[i].gen)->default_val;
+ break;
case RELOPT_TYPE_INT:
*(int *) itempos = options[i].isset ?
options[i].values.int_val :
@@ -1923,8 +2009,8 @@ default_reloptions(Datum reloptions, bool validate, relopt_kind kind)
offsetof(StdRdOptions, parallel_workers)},
{"vacuum_index_cleanup", RELOPT_TYPE_ENUM,
offsetof(StdRdOptions, vacuum_index_cleanup)},
- {"vacuum_truncate", RELOPT_TYPE_BOOL,
- offsetof(StdRdOptions, vacuum_truncate), offsetof(StdRdOptions, vacuum_truncate_set)},
+ {"vacuum_truncate", RELOPT_TYPE_TERNARY,
+ offsetof(StdRdOptions, vacuum_truncate)},
{"vacuum_max_eager_freeze_failure_rate", RELOPT_TYPE_REAL,
offsetof(StdRdOptions, vacuum_max_eager_freeze_failure_rate)}
};
@@ -2004,7 +2090,6 @@ build_local_reloptions(local_relopts *relopts, Datum options, bool validate)
elems[i].optname = opt->option->name;
elems[i].opttype = opt->option->type;
elems[i].offset = opt->offset;
- elems[i].isset_offset = 0; /* not supported for local relopts yet */
i++;
}
diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c
index aa4fbec143f..696eab9bd97 100644
--- a/src/backend/commands/vacuum.c
+++ b/src/backend/commands/vacuum.c
@@ -2224,9 +2224,9 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams params,
{
StdRdOptions *opts = (StdRdOptions *) rel->rd_options;
- if (opts && opts->vacuum_truncate_set)
+ if (opts && opts->vacuum_truncate != TERNARY_UNSET)
{
- if (opts->vacuum_truncate)
+ if (opts->vacuum_truncate == TERNARY_TRUE)
params.truncate = VACOPTVALUE_ENABLED;
else
params.truncate = VACOPTVALUE_DISABLED;
diff --git a/src/include/access/reloptions.h b/src/include/access/reloptions.h
index 2f08e1b0cf0..dfbef2babf2 100644
--- a/src/include/access/reloptions.h
+++ b/src/include/access/reloptions.h
@@ -29,6 +29,7 @@
typedef enum relopt_type
{
RELOPT_TYPE_BOOL,
+ RELOPT_TYPE_TERNARY, /* on, off, unset */
RELOPT_TYPE_INT,
RELOPT_TYPE_REAL,
RELOPT_TYPE_ENUM,
@@ -80,6 +81,7 @@ typedef struct relopt_value
union
{
bool bool_val;
+ pg_ternary ternary_val;
int int_val;
double real_val;
int enum_val;
@@ -94,6 +96,12 @@ typedef struct relopt_bool
bool default_val;
} relopt_bool;
+typedef struct relopt_ternary
+{
+ relopt_gen gen;
+ int default_val;
+} relopt_ternary;
+
typedef struct relopt_int
{
relopt_gen gen;
@@ -152,19 +160,6 @@ typedef struct
const char *optname; /* option's name */
relopt_type opttype; /* option's datatype */
int offset; /* offset of field in result struct */
-
- /*
- * isset_offset is an optional offset of a field in the result struct that
- * stores whether the option is explicitly set for the relation or if it
- * just picked up the default value. In most cases, this can be
- * accomplished by giving the reloption a special out-of-range default
- * value (e.g., some integer reloptions use -2), but this isn't always
- * possible. For example, a Boolean reloption cannot be given an
- * out-of-range default, so we need another way to discover the source of
- * its value. This offset is only used if given a value greater than
- * zero.
- */
- int isset_offset;
} relopt_parse_elt;
/* Local reloption definition */
@@ -195,6 +190,9 @@ typedef struct local_relopts
extern relopt_kind add_reloption_kind(void);
extern void add_bool_reloption(bits32 kinds, const char *name, const char *desc,
bool default_val, LOCKMODE lockmode);
+extern void add_ternary_reloption(bits32 kinds, const char *name,
+ const char *desc, pg_ternary default_val,
+ LOCKMODE lockmode);
extern void add_int_reloption(bits32 kinds, const char *name, const char *desc,
int default_val, int min_val, int max_val,
LOCKMODE lockmode);
@@ -214,6 +212,9 @@ extern void register_reloptions_validator(local_relopts *relopts,
extern void add_local_bool_reloption(local_relopts *relopts, const char *name,
const char *desc, bool default_val,
int offset);
+extern void add_local_ternary_reloption(local_relopts *relopts,
+ const char *name, const char *desc,
+ pg_ternary default_val, int offset);
extern void add_local_int_reloption(local_relopts *relopts, const char *name,
const char *desc, int default_val,
int min_val, int max_val, int offset);
diff --git a/src/include/postgres.h b/src/include/postgres.h
index 7d93fbce709..47c88780776 100644
--- a/src/include/postgres.h
+++ b/src/include/postgres.h
@@ -543,6 +543,21 @@ Float8GetDatum(float8 X)
* ----------------------------------------------------------------
*/
+/*
+ * pg_ternary
+ * Boolean value with an extra "unset" value
+ *
+ * This enum can be used for values that want to distinguish between true,
+ * false, and unset.
+*/
+
+typedef enum pg_ternary
+{
+ TERNARY_FALSE = 0,
+ TERNARY_TRUE = 1,
+ TERNARY_UNSET = -1
+} pg_ternary;
+
/*
* NON_EXEC_STATIC: It's sometimes useful to define a variable or function
* that is normally static but extern when using EXEC_BACKEND (see
diff --git a/src/include/utils/rel.h b/src/include/utils/rel.h
index d03ab247788..5feb18a5373 100644
--- a/src/include/utils/rel.h
+++ b/src/include/utils/rel.h
@@ -347,8 +347,7 @@ typedef struct StdRdOptions
bool user_catalog_table; /* use as an additional catalog relation */
int parallel_workers; /* max number of parallel workers */
StdRdOptIndexCleanup vacuum_index_cleanup; /* controls index vacuuming */
- bool vacuum_truncate; /* enables vacuum to truncate a relation */
- bool vacuum_truncate_set; /* whether vacuum_truncate is set */
+ pg_ternary vacuum_truncate; /* enables vacuum to truncate a relation */
/*
* Fraction of pages in a relation that vacuum can eagerly scan and fail
diff --git a/src/test/regress/expected/reloptions.out b/src/test/regress/expected/reloptions.out
index 9de19b4e3f1..1c99f79ab01 100644
--- a/src/test/regress/expected/reloptions.out
+++ b/src/test/regress/expected/reloptions.out
@@ -98,6 +98,42 @@ SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass;
{fillfactor=13,autovacuum_enabled=false}
(1 row)
+-- Tests for future (FIXME) ternary options
+-- behave as boolean option: accept unassigned name and truncated value
+DROP TABLE reloptions_test;
+CREATE TABLE reloptions_test(i INT) WITH (vacuum_truncate);
+SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass;
+ reloptions
+------------------------
+ {vacuum_truncate=true}
+(1 row)
+
+DROP TABLE reloptions_test;
+CREATE TABLE reloptions_test(i INT) WITH (vacuum_truncate=FaLS);
+SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass;
+ reloptions
+------------------------
+ {vacuum_truncate=fals}
+(1 row)
+
+-- preferred "true" alias is used when storing
+DROP TABLE reloptions_test;
+CREATE TABLE reloptions_test(i INT) WITH (vacuum_index_cleanup=on);
+SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass;
+ reloptions
+---------------------------
+ {vacuum_index_cleanup=on}
+(1 row)
+
+-- custom "third" value is available
+DROP TABLE reloptions_test;
+CREATE TABLE reloptions_test(i INT) WITH (vacuum_index_cleanup=auto);
+SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass;
+ reloptions
+-----------------------------
+ {vacuum_index_cleanup=auto}
+(1 row)
+
-- Test vacuum_truncate option
DROP TABLE reloptions_test;
CREATE TEMP TABLE reloptions_test(i INT NOT NULL, j text)
diff --git a/src/test/regress/sql/reloptions.sql b/src/test/regress/sql/reloptions.sql
index 24fbe0b478d..f5980dafcbc 100644
--- a/src/test/regress/sql/reloptions.sql
+++ b/src/test/regress/sql/reloptions.sql
@@ -59,6 +59,27 @@ UPDATE pg_class
ALTER TABLE reloptions_test RESET (illegal_option);
SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass;
+-- Tests for future (FIXME) ternary options
+
+-- behave as boolean option: accept unassigned name and truncated value
+DROP TABLE reloptions_test;
+CREATE TABLE reloptions_test(i INT) WITH (vacuum_truncate);
+SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass;
+
+DROP TABLE reloptions_test;
+CREATE TABLE reloptions_test(i INT) WITH (vacuum_truncate=FaLS);
+SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass;
+
+-- preferred "true" alias is used when storing
+DROP TABLE reloptions_test;
+CREATE TABLE reloptions_test(i INT) WITH (vacuum_index_cleanup=on);
+SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass;
+
+-- custom "third" value is available
+DROP TABLE reloptions_test;
+CREATE TABLE reloptions_test(i INT) WITH (vacuum_index_cleanup=auto);
+SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass;
+
-- Test vacuum_truncate option
DROP TABLE reloptions_test;
diff --git a/src/tools/pgindent/typedefs.list b/src/tools/pgindent/typedefs.list
index 3f3a888fd0e..1c8610fd46c 100644
--- a/src/tools/pgindent/typedefs.list
+++ b/src/tools/pgindent/typedefs.list
@@ -3952,6 +3952,7 @@ pg_sha512_ctx
pg_snapshot
pg_special_case
pg_stack_base_t
+pg_ternary
pg_time_t
pg_time_usec_t
pg_tz
@@ -4079,6 +4080,7 @@ relopt_kind
relopt_parse_elt
relopt_real
relopt_string
+relopt_ternary
relopt_type
relopt_value
relopts_validator
--
2.47.3
--c36t3fndxhkzbl5z--
^ permalink raw reply [nested|flat] 868+ messages in thread
* [PATCH v3] Introduce ternary reloptions
@ 2026-01-16 15:04 Álvaro Herrera <[email protected]>
0 siblings, 0 replies; 868+ messages in thread
From: Álvaro Herrera @ 2026-01-16 15:04 UTC (permalink / raw)
Introduce ternary reloption as a replacement for current
`vacuum_truncate` implementation. Remove the `vacuum_truncate_set`
separate flag and use TERNARY_UNSET instead.
This could also be used for other options such as `vacuum_index_cleanup`
and `buffering`, but lets get the scaffolding in first.
Discussion: https://postgr.es/m/3474141.usfYGdeWWP@thinkpad-pgpro
---
src/backend/access/common/reloptions.c | 137 ++++++++++++++++++-----
src/backend/commands/vacuum.c | 4 +-
src/include/access/reloptions.h | 27 ++---
src/include/postgres.h | 15 +++
src/include/utils/rel.h | 3 +-
src/test/regress/expected/reloptions.out | 36 ++++++
src/test/regress/sql/reloptions.sql | 21 ++++
src/tools/pgindent/typedefs.list | 2 +
8 files changed, 202 insertions(+), 43 deletions(-)
diff --git a/src/backend/access/common/reloptions.c b/src/backend/access/common/reloptions.c
index 0b83f98ed5f..6f1f577581a 100644
--- a/src/backend/access/common/reloptions.c
+++ b/src/backend/access/common/reloptions.c
@@ -40,9 +40,9 @@
*
* To add an option:
*
- * (i) decide on a type (bool, integer, real, enum, string), name, default
- * value, upper and lower bounds (if applicable); for strings, consider a
- * validation routine.
+ * (i) decide on a type (bool, ternary, integer, real, enum, string), name,
+ * default value, upper and lower bounds (if applicable); for strings,
+ * consider a validation routine.
* (ii) add a record below (or use add_<type>_reloption).
* (iii) add it to the appropriate options struct (perhaps StdRdOptions)
* (iv) add it to the appropriate handling routine (perhaps
@@ -147,15 +147,6 @@ static relopt_bool boolRelOpts[] =
},
false
},
- {
- {
- "vacuum_truncate",
- "Enables vacuum to truncate empty pages at the end of this table",
- RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
- ShareUpdateExclusiveLock
- },
- true
- },
{
{
"deduplicate_items",
@@ -170,6 +161,25 @@ static relopt_bool boolRelOpts[] =
{{NULL}}
};
+static relopt_ternary ternaryRelOpts[] =
+{
+ {
+ {
+ "vacuum_truncate",
+ "Enables vacuum to truncate empty pages at the end of this table",
+ RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
+ ShareUpdateExclusiveLock
+ },
+ TERNARY_UNSET
+ },
+ /* list terminator */
+ {
+ {
+ NULL
+ }
+ }
+};
+
static relopt_int intRelOpts[] =
{
{
@@ -609,6 +619,13 @@ initialize_reloptions(void)
boolRelOpts[i].gen.lockmode));
j++;
}
+ for (i = 0; ternaryRelOpts[i].gen.name; i++)
+ {
+ Assert(DoLockModesConflict(ternaryRelOpts[i].gen.lockmode,
+ ternaryRelOpts[i].gen.lockmode));
+ j++;
+ }
+
for (i = 0; intRelOpts[i].gen.name; i++)
{
Assert(DoLockModesConflict(intRelOpts[i].gen.lockmode,
@@ -649,6 +666,14 @@ initialize_reloptions(void)
j++;
}
+ for (i = 0; ternaryRelOpts[i].gen.name; i++)
+ {
+ relOpts[j] = &ternaryRelOpts[i].gen;
+ relOpts[j]->type = RELOPT_TYPE_TERNARY;
+ relOpts[j]->namelen = strlen(relOpts[j]->name);
+ j++;
+ }
+
for (i = 0; intRelOpts[i].gen.name; i++)
{
relOpts[j] = &intRelOpts[i].gen;
@@ -809,6 +834,9 @@ allocate_reloption(bits32 kinds, int type, const char *name, const char *desc,
case RELOPT_TYPE_BOOL:
size = sizeof(relopt_bool);
break;
+ case RELOPT_TYPE_TERNARY:
+ size = sizeof(relopt_ternary);
+ break;
case RELOPT_TYPE_INT:
size = sizeof(relopt_int);
break;
@@ -892,6 +920,57 @@ add_local_bool_reloption(local_relopts *relopts, const char *name,
add_local_reloption(relopts, (relopt_gen *) newoption, offset);
}
+/*
+ * init_ternary_reloption
+ * Allocate and initialize a new ternary reloption
+ */
+static relopt_ternary *
+init_ternary_reloption(bits32 kinds, const char *name, const char *desc,
+ pg_ternary default_val, LOCKMODE lockmode)
+{
+ relopt_ternary *newoption;
+
+ newoption = (relopt_ternary *)
+ allocate_reloption(kinds, RELOPT_TYPE_TERNARY, name, desc, lockmode);
+ newoption->default_val = default_val;
+
+ return newoption;
+}
+
+/*
+ * add_ternary_reloption
+ * Add a new ternary reloption
+ */
+void
+add_ternary_reloption(bits32 kinds, const char *name, const char *desc,
+ pg_ternary default_val, LOCKMODE lockmode)
+{
+ relopt_ternary *newoption;
+
+ newoption =
+ init_ternary_reloption(kinds, name, desc, default_val, lockmode);
+
+ add_reloption((relopt_gen *) newoption);
+}
+
+/*
+ * add_local_ternary_reloption
+ * Add a new ternary local reloption
+ *
+ * 'offset' is offset of ternary-typed field.
+ */
+void
+add_local_ternary_reloption(local_relopts *relopts, const char *name,
+ const char *desc, pg_ternary default_val,
+ int offset)
+{
+ relopt_ternary *newoption;
+
+ newoption =
+ init_ternary_reloption(RELOPT_KIND_LOCAL, name, desc, default_val, 0);
+
+ add_local_reloption(relopts, (relopt_gen *) newoption, offset);
+}
/*
* init_real_reloption
@@ -1626,6 +1705,19 @@ parse_one_reloption(relopt_value *option, char *text_str, int text_len,
option->gen->name, value)));
}
break;
+ case RELOPT_TYPE_TERNARY:
+ {
+ bool b;
+
+ parsed = parse_bool(value, &b);
+ option->values.ternary_val = b ? TERNARY_TRUE : TERNARY_FALSE;
+ if (validate && !parsed)
+ ereport(ERROR,
+ (errcode(ERRCODE_INVALID_PARAMETER_VALUE),
+ errmsg("invalid value for ternary option \"%s\": %s",
+ option->gen->name, value)));
+ }
+ break;
case RELOPT_TYPE_INT:
{
relopt_int *optint = (relopt_int *) option->gen;
@@ -1789,17 +1881,6 @@ fillRelOptions(void *rdopts, Size basesize,
char *itempos = ((char *) rdopts) + elems[j].offset;
char *string_val;
- /*
- * If isset_offset is provided, store whether the reloption is
- * set there.
- */
- if (elems[j].isset_offset > 0)
- {
- char *setpos = ((char *) rdopts) + elems[j].isset_offset;
-
- *(bool *) setpos = options[i].isset;
- }
-
switch (options[i].gen->type)
{
case RELOPT_TYPE_BOOL:
@@ -1807,6 +1888,11 @@ fillRelOptions(void *rdopts, Size basesize,
options[i].values.bool_val :
((relopt_bool *) options[i].gen)->default_val;
break;
+ case RELOPT_TYPE_TERNARY:
+ *(pg_ternary *) itempos = options[i].isset ?
+ options[i].values.ternary_val :
+ ((relopt_ternary *) options[i].gen)->default_val;
+ break;
case RELOPT_TYPE_INT:
*(int *) itempos = options[i].isset ?
options[i].values.int_val :
@@ -1923,8 +2009,8 @@ default_reloptions(Datum reloptions, bool validate, relopt_kind kind)
offsetof(StdRdOptions, parallel_workers)},
{"vacuum_index_cleanup", RELOPT_TYPE_ENUM,
offsetof(StdRdOptions, vacuum_index_cleanup)},
- {"vacuum_truncate", RELOPT_TYPE_BOOL,
- offsetof(StdRdOptions, vacuum_truncate), offsetof(StdRdOptions, vacuum_truncate_set)},
+ {"vacuum_truncate", RELOPT_TYPE_TERNARY,
+ offsetof(StdRdOptions, vacuum_truncate)},
{"vacuum_max_eager_freeze_failure_rate", RELOPT_TYPE_REAL,
offsetof(StdRdOptions, vacuum_max_eager_freeze_failure_rate)}
};
@@ -2004,7 +2090,6 @@ build_local_reloptions(local_relopts *relopts, Datum options, bool validate)
elems[i].optname = opt->option->name;
elems[i].opttype = opt->option->type;
elems[i].offset = opt->offset;
- elems[i].isset_offset = 0; /* not supported for local relopts yet */
i++;
}
diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c
index aa4fbec143f..696eab9bd97 100644
--- a/src/backend/commands/vacuum.c
+++ b/src/backend/commands/vacuum.c
@@ -2224,9 +2224,9 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams params,
{
StdRdOptions *opts = (StdRdOptions *) rel->rd_options;
- if (opts && opts->vacuum_truncate_set)
+ if (opts && opts->vacuum_truncate != TERNARY_UNSET)
{
- if (opts->vacuum_truncate)
+ if (opts->vacuum_truncate == TERNARY_TRUE)
params.truncate = VACOPTVALUE_ENABLED;
else
params.truncate = VACOPTVALUE_DISABLED;
diff --git a/src/include/access/reloptions.h b/src/include/access/reloptions.h
index 2f08e1b0cf0..dfbef2babf2 100644
--- a/src/include/access/reloptions.h
+++ b/src/include/access/reloptions.h
@@ -29,6 +29,7 @@
typedef enum relopt_type
{
RELOPT_TYPE_BOOL,
+ RELOPT_TYPE_TERNARY, /* on, off, unset */
RELOPT_TYPE_INT,
RELOPT_TYPE_REAL,
RELOPT_TYPE_ENUM,
@@ -80,6 +81,7 @@ typedef struct relopt_value
union
{
bool bool_val;
+ pg_ternary ternary_val;
int int_val;
double real_val;
int enum_val;
@@ -94,6 +96,12 @@ typedef struct relopt_bool
bool default_val;
} relopt_bool;
+typedef struct relopt_ternary
+{
+ relopt_gen gen;
+ int default_val;
+} relopt_ternary;
+
typedef struct relopt_int
{
relopt_gen gen;
@@ -152,19 +160,6 @@ typedef struct
const char *optname; /* option's name */
relopt_type opttype; /* option's datatype */
int offset; /* offset of field in result struct */
-
- /*
- * isset_offset is an optional offset of a field in the result struct that
- * stores whether the option is explicitly set for the relation or if it
- * just picked up the default value. In most cases, this can be
- * accomplished by giving the reloption a special out-of-range default
- * value (e.g., some integer reloptions use -2), but this isn't always
- * possible. For example, a Boolean reloption cannot be given an
- * out-of-range default, so we need another way to discover the source of
- * its value. This offset is only used if given a value greater than
- * zero.
- */
- int isset_offset;
} relopt_parse_elt;
/* Local reloption definition */
@@ -195,6 +190,9 @@ typedef struct local_relopts
extern relopt_kind add_reloption_kind(void);
extern void add_bool_reloption(bits32 kinds, const char *name, const char *desc,
bool default_val, LOCKMODE lockmode);
+extern void add_ternary_reloption(bits32 kinds, const char *name,
+ const char *desc, pg_ternary default_val,
+ LOCKMODE lockmode);
extern void add_int_reloption(bits32 kinds, const char *name, const char *desc,
int default_val, int min_val, int max_val,
LOCKMODE lockmode);
@@ -214,6 +212,9 @@ extern void register_reloptions_validator(local_relopts *relopts,
extern void add_local_bool_reloption(local_relopts *relopts, const char *name,
const char *desc, bool default_val,
int offset);
+extern void add_local_ternary_reloption(local_relopts *relopts,
+ const char *name, const char *desc,
+ pg_ternary default_val, int offset);
extern void add_local_int_reloption(local_relopts *relopts, const char *name,
const char *desc, int default_val,
int min_val, int max_val, int offset);
diff --git a/src/include/postgres.h b/src/include/postgres.h
index 7d93fbce709..47c88780776 100644
--- a/src/include/postgres.h
+++ b/src/include/postgres.h
@@ -543,6 +543,21 @@ Float8GetDatum(float8 X)
* ----------------------------------------------------------------
*/
+/*
+ * pg_ternary
+ * Boolean value with an extra "unset" value
+ *
+ * This enum can be used for values that want to distinguish between true,
+ * false, and unset.
+*/
+
+typedef enum pg_ternary
+{
+ TERNARY_FALSE = 0,
+ TERNARY_TRUE = 1,
+ TERNARY_UNSET = -1
+} pg_ternary;
+
/*
* NON_EXEC_STATIC: It's sometimes useful to define a variable or function
* that is normally static but extern when using EXEC_BACKEND (see
diff --git a/src/include/utils/rel.h b/src/include/utils/rel.h
index d03ab247788..5feb18a5373 100644
--- a/src/include/utils/rel.h
+++ b/src/include/utils/rel.h
@@ -347,8 +347,7 @@ typedef struct StdRdOptions
bool user_catalog_table; /* use as an additional catalog relation */
int parallel_workers; /* max number of parallel workers */
StdRdOptIndexCleanup vacuum_index_cleanup; /* controls index vacuuming */
- bool vacuum_truncate; /* enables vacuum to truncate a relation */
- bool vacuum_truncate_set; /* whether vacuum_truncate is set */
+ pg_ternary vacuum_truncate; /* enables vacuum to truncate a relation */
/*
* Fraction of pages in a relation that vacuum can eagerly scan and fail
diff --git a/src/test/regress/expected/reloptions.out b/src/test/regress/expected/reloptions.out
index 9de19b4e3f1..1c99f79ab01 100644
--- a/src/test/regress/expected/reloptions.out
+++ b/src/test/regress/expected/reloptions.out
@@ -98,6 +98,42 @@ SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass;
{fillfactor=13,autovacuum_enabled=false}
(1 row)
+-- Tests for future (FIXME) ternary options
+-- behave as boolean option: accept unassigned name and truncated value
+DROP TABLE reloptions_test;
+CREATE TABLE reloptions_test(i INT) WITH (vacuum_truncate);
+SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass;
+ reloptions
+------------------------
+ {vacuum_truncate=true}
+(1 row)
+
+DROP TABLE reloptions_test;
+CREATE TABLE reloptions_test(i INT) WITH (vacuum_truncate=FaLS);
+SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass;
+ reloptions
+------------------------
+ {vacuum_truncate=fals}
+(1 row)
+
+-- preferred "true" alias is used when storing
+DROP TABLE reloptions_test;
+CREATE TABLE reloptions_test(i INT) WITH (vacuum_index_cleanup=on);
+SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass;
+ reloptions
+---------------------------
+ {vacuum_index_cleanup=on}
+(1 row)
+
+-- custom "third" value is available
+DROP TABLE reloptions_test;
+CREATE TABLE reloptions_test(i INT) WITH (vacuum_index_cleanup=auto);
+SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass;
+ reloptions
+-----------------------------
+ {vacuum_index_cleanup=auto}
+(1 row)
+
-- Test vacuum_truncate option
DROP TABLE reloptions_test;
CREATE TEMP TABLE reloptions_test(i INT NOT NULL, j text)
diff --git a/src/test/regress/sql/reloptions.sql b/src/test/regress/sql/reloptions.sql
index 24fbe0b478d..f5980dafcbc 100644
--- a/src/test/regress/sql/reloptions.sql
+++ b/src/test/regress/sql/reloptions.sql
@@ -59,6 +59,27 @@ UPDATE pg_class
ALTER TABLE reloptions_test RESET (illegal_option);
SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass;
+-- Tests for future (FIXME) ternary options
+
+-- behave as boolean option: accept unassigned name and truncated value
+DROP TABLE reloptions_test;
+CREATE TABLE reloptions_test(i INT) WITH (vacuum_truncate);
+SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass;
+
+DROP TABLE reloptions_test;
+CREATE TABLE reloptions_test(i INT) WITH (vacuum_truncate=FaLS);
+SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass;
+
+-- preferred "true" alias is used when storing
+DROP TABLE reloptions_test;
+CREATE TABLE reloptions_test(i INT) WITH (vacuum_index_cleanup=on);
+SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass;
+
+-- custom "third" value is available
+DROP TABLE reloptions_test;
+CREATE TABLE reloptions_test(i INT) WITH (vacuum_index_cleanup=auto);
+SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass;
+
-- Test vacuum_truncate option
DROP TABLE reloptions_test;
diff --git a/src/tools/pgindent/typedefs.list b/src/tools/pgindent/typedefs.list
index 3f3a888fd0e..1c8610fd46c 100644
--- a/src/tools/pgindent/typedefs.list
+++ b/src/tools/pgindent/typedefs.list
@@ -3952,6 +3952,7 @@ pg_sha512_ctx
pg_snapshot
pg_special_case
pg_stack_base_t
+pg_ternary
pg_time_t
pg_time_usec_t
pg_tz
@@ -4079,6 +4080,7 @@ relopt_kind
relopt_parse_elt
relopt_real
relopt_string
+relopt_ternary
relopt_type
relopt_value
relopts_validator
--
2.47.3
--c36t3fndxhkzbl5z--
^ permalink raw reply [nested|flat] 868+ messages in thread
* [PATCH v3] Introduce ternary reloptions
@ 2026-01-16 15:04 Álvaro Herrera <[email protected]>
0 siblings, 0 replies; 868+ messages in thread
From: Álvaro Herrera @ 2026-01-16 15:04 UTC (permalink / raw)
Introduce ternary reloption as a replacement for current
`vacuum_truncate` implementation. Remove the `vacuum_truncate_set`
separate flag and use TERNARY_UNSET instead.
This could also be used for other options such as `vacuum_index_cleanup`
and `buffering`, but lets get the scaffolding in first.
Discussion: https://postgr.es/m/3474141.usfYGdeWWP@thinkpad-pgpro
---
src/backend/access/common/reloptions.c | 137 ++++++++++++++++++-----
src/backend/commands/vacuum.c | 4 +-
src/include/access/reloptions.h | 27 ++---
src/include/postgres.h | 15 +++
src/include/utils/rel.h | 3 +-
src/test/regress/expected/reloptions.out | 36 ++++++
src/test/regress/sql/reloptions.sql | 21 ++++
src/tools/pgindent/typedefs.list | 2 +
8 files changed, 202 insertions(+), 43 deletions(-)
diff --git a/src/backend/access/common/reloptions.c b/src/backend/access/common/reloptions.c
index 0b83f98ed5f..6f1f577581a 100644
--- a/src/backend/access/common/reloptions.c
+++ b/src/backend/access/common/reloptions.c
@@ -40,9 +40,9 @@
*
* To add an option:
*
- * (i) decide on a type (bool, integer, real, enum, string), name, default
- * value, upper and lower bounds (if applicable); for strings, consider a
- * validation routine.
+ * (i) decide on a type (bool, ternary, integer, real, enum, string), name,
+ * default value, upper and lower bounds (if applicable); for strings,
+ * consider a validation routine.
* (ii) add a record below (or use add_<type>_reloption).
* (iii) add it to the appropriate options struct (perhaps StdRdOptions)
* (iv) add it to the appropriate handling routine (perhaps
@@ -147,15 +147,6 @@ static relopt_bool boolRelOpts[] =
},
false
},
- {
- {
- "vacuum_truncate",
- "Enables vacuum to truncate empty pages at the end of this table",
- RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
- ShareUpdateExclusiveLock
- },
- true
- },
{
{
"deduplicate_items",
@@ -170,6 +161,25 @@ static relopt_bool boolRelOpts[] =
{{NULL}}
};
+static relopt_ternary ternaryRelOpts[] =
+{
+ {
+ {
+ "vacuum_truncate",
+ "Enables vacuum to truncate empty pages at the end of this table",
+ RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
+ ShareUpdateExclusiveLock
+ },
+ TERNARY_UNSET
+ },
+ /* list terminator */
+ {
+ {
+ NULL
+ }
+ }
+};
+
static relopt_int intRelOpts[] =
{
{
@@ -609,6 +619,13 @@ initialize_reloptions(void)
boolRelOpts[i].gen.lockmode));
j++;
}
+ for (i = 0; ternaryRelOpts[i].gen.name; i++)
+ {
+ Assert(DoLockModesConflict(ternaryRelOpts[i].gen.lockmode,
+ ternaryRelOpts[i].gen.lockmode));
+ j++;
+ }
+
for (i = 0; intRelOpts[i].gen.name; i++)
{
Assert(DoLockModesConflict(intRelOpts[i].gen.lockmode,
@@ -649,6 +666,14 @@ initialize_reloptions(void)
j++;
}
+ for (i = 0; ternaryRelOpts[i].gen.name; i++)
+ {
+ relOpts[j] = &ternaryRelOpts[i].gen;
+ relOpts[j]->type = RELOPT_TYPE_TERNARY;
+ relOpts[j]->namelen = strlen(relOpts[j]->name);
+ j++;
+ }
+
for (i = 0; intRelOpts[i].gen.name; i++)
{
relOpts[j] = &intRelOpts[i].gen;
@@ -809,6 +834,9 @@ allocate_reloption(bits32 kinds, int type, const char *name, const char *desc,
case RELOPT_TYPE_BOOL:
size = sizeof(relopt_bool);
break;
+ case RELOPT_TYPE_TERNARY:
+ size = sizeof(relopt_ternary);
+ break;
case RELOPT_TYPE_INT:
size = sizeof(relopt_int);
break;
@@ -892,6 +920,57 @@ add_local_bool_reloption(local_relopts *relopts, const char *name,
add_local_reloption(relopts, (relopt_gen *) newoption, offset);
}
+/*
+ * init_ternary_reloption
+ * Allocate and initialize a new ternary reloption
+ */
+static relopt_ternary *
+init_ternary_reloption(bits32 kinds, const char *name, const char *desc,
+ pg_ternary default_val, LOCKMODE lockmode)
+{
+ relopt_ternary *newoption;
+
+ newoption = (relopt_ternary *)
+ allocate_reloption(kinds, RELOPT_TYPE_TERNARY, name, desc, lockmode);
+ newoption->default_val = default_val;
+
+ return newoption;
+}
+
+/*
+ * add_ternary_reloption
+ * Add a new ternary reloption
+ */
+void
+add_ternary_reloption(bits32 kinds, const char *name, const char *desc,
+ pg_ternary default_val, LOCKMODE lockmode)
+{
+ relopt_ternary *newoption;
+
+ newoption =
+ init_ternary_reloption(kinds, name, desc, default_val, lockmode);
+
+ add_reloption((relopt_gen *) newoption);
+}
+
+/*
+ * add_local_ternary_reloption
+ * Add a new ternary local reloption
+ *
+ * 'offset' is offset of ternary-typed field.
+ */
+void
+add_local_ternary_reloption(local_relopts *relopts, const char *name,
+ const char *desc, pg_ternary default_val,
+ int offset)
+{
+ relopt_ternary *newoption;
+
+ newoption =
+ init_ternary_reloption(RELOPT_KIND_LOCAL, name, desc, default_val, 0);
+
+ add_local_reloption(relopts, (relopt_gen *) newoption, offset);
+}
/*
* init_real_reloption
@@ -1626,6 +1705,19 @@ parse_one_reloption(relopt_value *option, char *text_str, int text_len,
option->gen->name, value)));
}
break;
+ case RELOPT_TYPE_TERNARY:
+ {
+ bool b;
+
+ parsed = parse_bool(value, &b);
+ option->values.ternary_val = b ? TERNARY_TRUE : TERNARY_FALSE;
+ if (validate && !parsed)
+ ereport(ERROR,
+ (errcode(ERRCODE_INVALID_PARAMETER_VALUE),
+ errmsg("invalid value for ternary option \"%s\": %s",
+ option->gen->name, value)));
+ }
+ break;
case RELOPT_TYPE_INT:
{
relopt_int *optint = (relopt_int *) option->gen;
@@ -1789,17 +1881,6 @@ fillRelOptions(void *rdopts, Size basesize,
char *itempos = ((char *) rdopts) + elems[j].offset;
char *string_val;
- /*
- * If isset_offset is provided, store whether the reloption is
- * set there.
- */
- if (elems[j].isset_offset > 0)
- {
- char *setpos = ((char *) rdopts) + elems[j].isset_offset;
-
- *(bool *) setpos = options[i].isset;
- }
-
switch (options[i].gen->type)
{
case RELOPT_TYPE_BOOL:
@@ -1807,6 +1888,11 @@ fillRelOptions(void *rdopts, Size basesize,
options[i].values.bool_val :
((relopt_bool *) options[i].gen)->default_val;
break;
+ case RELOPT_TYPE_TERNARY:
+ *(pg_ternary *) itempos = options[i].isset ?
+ options[i].values.ternary_val :
+ ((relopt_ternary *) options[i].gen)->default_val;
+ break;
case RELOPT_TYPE_INT:
*(int *) itempos = options[i].isset ?
options[i].values.int_val :
@@ -1923,8 +2009,8 @@ default_reloptions(Datum reloptions, bool validate, relopt_kind kind)
offsetof(StdRdOptions, parallel_workers)},
{"vacuum_index_cleanup", RELOPT_TYPE_ENUM,
offsetof(StdRdOptions, vacuum_index_cleanup)},
- {"vacuum_truncate", RELOPT_TYPE_BOOL,
- offsetof(StdRdOptions, vacuum_truncate), offsetof(StdRdOptions, vacuum_truncate_set)},
+ {"vacuum_truncate", RELOPT_TYPE_TERNARY,
+ offsetof(StdRdOptions, vacuum_truncate)},
{"vacuum_max_eager_freeze_failure_rate", RELOPT_TYPE_REAL,
offsetof(StdRdOptions, vacuum_max_eager_freeze_failure_rate)}
};
@@ -2004,7 +2090,6 @@ build_local_reloptions(local_relopts *relopts, Datum options, bool validate)
elems[i].optname = opt->option->name;
elems[i].opttype = opt->option->type;
elems[i].offset = opt->offset;
- elems[i].isset_offset = 0; /* not supported for local relopts yet */
i++;
}
diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c
index aa4fbec143f..696eab9bd97 100644
--- a/src/backend/commands/vacuum.c
+++ b/src/backend/commands/vacuum.c
@@ -2224,9 +2224,9 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams params,
{
StdRdOptions *opts = (StdRdOptions *) rel->rd_options;
- if (opts && opts->vacuum_truncate_set)
+ if (opts && opts->vacuum_truncate != TERNARY_UNSET)
{
- if (opts->vacuum_truncate)
+ if (opts->vacuum_truncate == TERNARY_TRUE)
params.truncate = VACOPTVALUE_ENABLED;
else
params.truncate = VACOPTVALUE_DISABLED;
diff --git a/src/include/access/reloptions.h b/src/include/access/reloptions.h
index 2f08e1b0cf0..dfbef2babf2 100644
--- a/src/include/access/reloptions.h
+++ b/src/include/access/reloptions.h
@@ -29,6 +29,7 @@
typedef enum relopt_type
{
RELOPT_TYPE_BOOL,
+ RELOPT_TYPE_TERNARY, /* on, off, unset */
RELOPT_TYPE_INT,
RELOPT_TYPE_REAL,
RELOPT_TYPE_ENUM,
@@ -80,6 +81,7 @@ typedef struct relopt_value
union
{
bool bool_val;
+ pg_ternary ternary_val;
int int_val;
double real_val;
int enum_val;
@@ -94,6 +96,12 @@ typedef struct relopt_bool
bool default_val;
} relopt_bool;
+typedef struct relopt_ternary
+{
+ relopt_gen gen;
+ int default_val;
+} relopt_ternary;
+
typedef struct relopt_int
{
relopt_gen gen;
@@ -152,19 +160,6 @@ typedef struct
const char *optname; /* option's name */
relopt_type opttype; /* option's datatype */
int offset; /* offset of field in result struct */
-
- /*
- * isset_offset is an optional offset of a field in the result struct that
- * stores whether the option is explicitly set for the relation or if it
- * just picked up the default value. In most cases, this can be
- * accomplished by giving the reloption a special out-of-range default
- * value (e.g., some integer reloptions use -2), but this isn't always
- * possible. For example, a Boolean reloption cannot be given an
- * out-of-range default, so we need another way to discover the source of
- * its value. This offset is only used if given a value greater than
- * zero.
- */
- int isset_offset;
} relopt_parse_elt;
/* Local reloption definition */
@@ -195,6 +190,9 @@ typedef struct local_relopts
extern relopt_kind add_reloption_kind(void);
extern void add_bool_reloption(bits32 kinds, const char *name, const char *desc,
bool default_val, LOCKMODE lockmode);
+extern void add_ternary_reloption(bits32 kinds, const char *name,
+ const char *desc, pg_ternary default_val,
+ LOCKMODE lockmode);
extern void add_int_reloption(bits32 kinds, const char *name, const char *desc,
int default_val, int min_val, int max_val,
LOCKMODE lockmode);
@@ -214,6 +212,9 @@ extern void register_reloptions_validator(local_relopts *relopts,
extern void add_local_bool_reloption(local_relopts *relopts, const char *name,
const char *desc, bool default_val,
int offset);
+extern void add_local_ternary_reloption(local_relopts *relopts,
+ const char *name, const char *desc,
+ pg_ternary default_val, int offset);
extern void add_local_int_reloption(local_relopts *relopts, const char *name,
const char *desc, int default_val,
int min_val, int max_val, int offset);
diff --git a/src/include/postgres.h b/src/include/postgres.h
index 7d93fbce709..47c88780776 100644
--- a/src/include/postgres.h
+++ b/src/include/postgres.h
@@ -543,6 +543,21 @@ Float8GetDatum(float8 X)
* ----------------------------------------------------------------
*/
+/*
+ * pg_ternary
+ * Boolean value with an extra "unset" value
+ *
+ * This enum can be used for values that want to distinguish between true,
+ * false, and unset.
+*/
+
+typedef enum pg_ternary
+{
+ TERNARY_FALSE = 0,
+ TERNARY_TRUE = 1,
+ TERNARY_UNSET = -1
+} pg_ternary;
+
/*
* NON_EXEC_STATIC: It's sometimes useful to define a variable or function
* that is normally static but extern when using EXEC_BACKEND (see
diff --git a/src/include/utils/rel.h b/src/include/utils/rel.h
index d03ab247788..5feb18a5373 100644
--- a/src/include/utils/rel.h
+++ b/src/include/utils/rel.h
@@ -347,8 +347,7 @@ typedef struct StdRdOptions
bool user_catalog_table; /* use as an additional catalog relation */
int parallel_workers; /* max number of parallel workers */
StdRdOptIndexCleanup vacuum_index_cleanup; /* controls index vacuuming */
- bool vacuum_truncate; /* enables vacuum to truncate a relation */
- bool vacuum_truncate_set; /* whether vacuum_truncate is set */
+ pg_ternary vacuum_truncate; /* enables vacuum to truncate a relation */
/*
* Fraction of pages in a relation that vacuum can eagerly scan and fail
diff --git a/src/test/regress/expected/reloptions.out b/src/test/regress/expected/reloptions.out
index 9de19b4e3f1..1c99f79ab01 100644
--- a/src/test/regress/expected/reloptions.out
+++ b/src/test/regress/expected/reloptions.out
@@ -98,6 +98,42 @@ SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass;
{fillfactor=13,autovacuum_enabled=false}
(1 row)
+-- Tests for future (FIXME) ternary options
+-- behave as boolean option: accept unassigned name and truncated value
+DROP TABLE reloptions_test;
+CREATE TABLE reloptions_test(i INT) WITH (vacuum_truncate);
+SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass;
+ reloptions
+------------------------
+ {vacuum_truncate=true}
+(1 row)
+
+DROP TABLE reloptions_test;
+CREATE TABLE reloptions_test(i INT) WITH (vacuum_truncate=FaLS);
+SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass;
+ reloptions
+------------------------
+ {vacuum_truncate=fals}
+(1 row)
+
+-- preferred "true" alias is used when storing
+DROP TABLE reloptions_test;
+CREATE TABLE reloptions_test(i INT) WITH (vacuum_index_cleanup=on);
+SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass;
+ reloptions
+---------------------------
+ {vacuum_index_cleanup=on}
+(1 row)
+
+-- custom "third" value is available
+DROP TABLE reloptions_test;
+CREATE TABLE reloptions_test(i INT) WITH (vacuum_index_cleanup=auto);
+SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass;
+ reloptions
+-----------------------------
+ {vacuum_index_cleanup=auto}
+(1 row)
+
-- Test vacuum_truncate option
DROP TABLE reloptions_test;
CREATE TEMP TABLE reloptions_test(i INT NOT NULL, j text)
diff --git a/src/test/regress/sql/reloptions.sql b/src/test/regress/sql/reloptions.sql
index 24fbe0b478d..f5980dafcbc 100644
--- a/src/test/regress/sql/reloptions.sql
+++ b/src/test/regress/sql/reloptions.sql
@@ -59,6 +59,27 @@ UPDATE pg_class
ALTER TABLE reloptions_test RESET (illegal_option);
SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass;
+-- Tests for future (FIXME) ternary options
+
+-- behave as boolean option: accept unassigned name and truncated value
+DROP TABLE reloptions_test;
+CREATE TABLE reloptions_test(i INT) WITH (vacuum_truncate);
+SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass;
+
+DROP TABLE reloptions_test;
+CREATE TABLE reloptions_test(i INT) WITH (vacuum_truncate=FaLS);
+SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass;
+
+-- preferred "true" alias is used when storing
+DROP TABLE reloptions_test;
+CREATE TABLE reloptions_test(i INT) WITH (vacuum_index_cleanup=on);
+SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass;
+
+-- custom "third" value is available
+DROP TABLE reloptions_test;
+CREATE TABLE reloptions_test(i INT) WITH (vacuum_index_cleanup=auto);
+SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass;
+
-- Test vacuum_truncate option
DROP TABLE reloptions_test;
diff --git a/src/tools/pgindent/typedefs.list b/src/tools/pgindent/typedefs.list
index 3f3a888fd0e..1c8610fd46c 100644
--- a/src/tools/pgindent/typedefs.list
+++ b/src/tools/pgindent/typedefs.list
@@ -3952,6 +3952,7 @@ pg_sha512_ctx
pg_snapshot
pg_special_case
pg_stack_base_t
+pg_ternary
pg_time_t
pg_time_usec_t
pg_tz
@@ -4079,6 +4080,7 @@ relopt_kind
relopt_parse_elt
relopt_real
relopt_string
+relopt_ternary
relopt_type
relopt_value
relopts_validator
--
2.47.3
--c36t3fndxhkzbl5z--
^ permalink raw reply [nested|flat] 868+ messages in thread
* [PATCH v3] Introduce ternary reloptions
@ 2026-01-16 15:04 Álvaro Herrera <[email protected]>
0 siblings, 0 replies; 868+ messages in thread
From: Álvaro Herrera @ 2026-01-16 15:04 UTC (permalink / raw)
Introduce ternary reloption as a replacement for current
`vacuum_truncate` implementation. Remove the `vacuum_truncate_set`
separate flag and use TERNARY_UNSET instead.
This could also be used for other options such as `vacuum_index_cleanup`
and `buffering`, but lets get the scaffolding in first.
Discussion: https://postgr.es/m/3474141.usfYGdeWWP@thinkpad-pgpro
---
src/backend/access/common/reloptions.c | 137 ++++++++++++++++++-----
src/backend/commands/vacuum.c | 4 +-
src/include/access/reloptions.h | 27 ++---
src/include/postgres.h | 15 +++
src/include/utils/rel.h | 3 +-
src/test/regress/expected/reloptions.out | 36 ++++++
src/test/regress/sql/reloptions.sql | 21 ++++
src/tools/pgindent/typedefs.list | 2 +
8 files changed, 202 insertions(+), 43 deletions(-)
diff --git a/src/backend/access/common/reloptions.c b/src/backend/access/common/reloptions.c
index 0b83f98ed5f..6f1f577581a 100644
--- a/src/backend/access/common/reloptions.c
+++ b/src/backend/access/common/reloptions.c
@@ -40,9 +40,9 @@
*
* To add an option:
*
- * (i) decide on a type (bool, integer, real, enum, string), name, default
- * value, upper and lower bounds (if applicable); for strings, consider a
- * validation routine.
+ * (i) decide on a type (bool, ternary, integer, real, enum, string), name,
+ * default value, upper and lower bounds (if applicable); for strings,
+ * consider a validation routine.
* (ii) add a record below (or use add_<type>_reloption).
* (iii) add it to the appropriate options struct (perhaps StdRdOptions)
* (iv) add it to the appropriate handling routine (perhaps
@@ -147,15 +147,6 @@ static relopt_bool boolRelOpts[] =
},
false
},
- {
- {
- "vacuum_truncate",
- "Enables vacuum to truncate empty pages at the end of this table",
- RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
- ShareUpdateExclusiveLock
- },
- true
- },
{
{
"deduplicate_items",
@@ -170,6 +161,25 @@ static relopt_bool boolRelOpts[] =
{{NULL}}
};
+static relopt_ternary ternaryRelOpts[] =
+{
+ {
+ {
+ "vacuum_truncate",
+ "Enables vacuum to truncate empty pages at the end of this table",
+ RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
+ ShareUpdateExclusiveLock
+ },
+ TERNARY_UNSET
+ },
+ /* list terminator */
+ {
+ {
+ NULL
+ }
+ }
+};
+
static relopt_int intRelOpts[] =
{
{
@@ -609,6 +619,13 @@ initialize_reloptions(void)
boolRelOpts[i].gen.lockmode));
j++;
}
+ for (i = 0; ternaryRelOpts[i].gen.name; i++)
+ {
+ Assert(DoLockModesConflict(ternaryRelOpts[i].gen.lockmode,
+ ternaryRelOpts[i].gen.lockmode));
+ j++;
+ }
+
for (i = 0; intRelOpts[i].gen.name; i++)
{
Assert(DoLockModesConflict(intRelOpts[i].gen.lockmode,
@@ -649,6 +666,14 @@ initialize_reloptions(void)
j++;
}
+ for (i = 0; ternaryRelOpts[i].gen.name; i++)
+ {
+ relOpts[j] = &ternaryRelOpts[i].gen;
+ relOpts[j]->type = RELOPT_TYPE_TERNARY;
+ relOpts[j]->namelen = strlen(relOpts[j]->name);
+ j++;
+ }
+
for (i = 0; intRelOpts[i].gen.name; i++)
{
relOpts[j] = &intRelOpts[i].gen;
@@ -809,6 +834,9 @@ allocate_reloption(bits32 kinds, int type, const char *name, const char *desc,
case RELOPT_TYPE_BOOL:
size = sizeof(relopt_bool);
break;
+ case RELOPT_TYPE_TERNARY:
+ size = sizeof(relopt_ternary);
+ break;
case RELOPT_TYPE_INT:
size = sizeof(relopt_int);
break;
@@ -892,6 +920,57 @@ add_local_bool_reloption(local_relopts *relopts, const char *name,
add_local_reloption(relopts, (relopt_gen *) newoption, offset);
}
+/*
+ * init_ternary_reloption
+ * Allocate and initialize a new ternary reloption
+ */
+static relopt_ternary *
+init_ternary_reloption(bits32 kinds, const char *name, const char *desc,
+ pg_ternary default_val, LOCKMODE lockmode)
+{
+ relopt_ternary *newoption;
+
+ newoption = (relopt_ternary *)
+ allocate_reloption(kinds, RELOPT_TYPE_TERNARY, name, desc, lockmode);
+ newoption->default_val = default_val;
+
+ return newoption;
+}
+
+/*
+ * add_ternary_reloption
+ * Add a new ternary reloption
+ */
+void
+add_ternary_reloption(bits32 kinds, const char *name, const char *desc,
+ pg_ternary default_val, LOCKMODE lockmode)
+{
+ relopt_ternary *newoption;
+
+ newoption =
+ init_ternary_reloption(kinds, name, desc, default_val, lockmode);
+
+ add_reloption((relopt_gen *) newoption);
+}
+
+/*
+ * add_local_ternary_reloption
+ * Add a new ternary local reloption
+ *
+ * 'offset' is offset of ternary-typed field.
+ */
+void
+add_local_ternary_reloption(local_relopts *relopts, const char *name,
+ const char *desc, pg_ternary default_val,
+ int offset)
+{
+ relopt_ternary *newoption;
+
+ newoption =
+ init_ternary_reloption(RELOPT_KIND_LOCAL, name, desc, default_val, 0);
+
+ add_local_reloption(relopts, (relopt_gen *) newoption, offset);
+}
/*
* init_real_reloption
@@ -1626,6 +1705,19 @@ parse_one_reloption(relopt_value *option, char *text_str, int text_len,
option->gen->name, value)));
}
break;
+ case RELOPT_TYPE_TERNARY:
+ {
+ bool b;
+
+ parsed = parse_bool(value, &b);
+ option->values.ternary_val = b ? TERNARY_TRUE : TERNARY_FALSE;
+ if (validate && !parsed)
+ ereport(ERROR,
+ (errcode(ERRCODE_INVALID_PARAMETER_VALUE),
+ errmsg("invalid value for ternary option \"%s\": %s",
+ option->gen->name, value)));
+ }
+ break;
case RELOPT_TYPE_INT:
{
relopt_int *optint = (relopt_int *) option->gen;
@@ -1789,17 +1881,6 @@ fillRelOptions(void *rdopts, Size basesize,
char *itempos = ((char *) rdopts) + elems[j].offset;
char *string_val;
- /*
- * If isset_offset is provided, store whether the reloption is
- * set there.
- */
- if (elems[j].isset_offset > 0)
- {
- char *setpos = ((char *) rdopts) + elems[j].isset_offset;
-
- *(bool *) setpos = options[i].isset;
- }
-
switch (options[i].gen->type)
{
case RELOPT_TYPE_BOOL:
@@ -1807,6 +1888,11 @@ fillRelOptions(void *rdopts, Size basesize,
options[i].values.bool_val :
((relopt_bool *) options[i].gen)->default_val;
break;
+ case RELOPT_TYPE_TERNARY:
+ *(pg_ternary *) itempos = options[i].isset ?
+ options[i].values.ternary_val :
+ ((relopt_ternary *) options[i].gen)->default_val;
+ break;
case RELOPT_TYPE_INT:
*(int *) itempos = options[i].isset ?
options[i].values.int_val :
@@ -1923,8 +2009,8 @@ default_reloptions(Datum reloptions, bool validate, relopt_kind kind)
offsetof(StdRdOptions, parallel_workers)},
{"vacuum_index_cleanup", RELOPT_TYPE_ENUM,
offsetof(StdRdOptions, vacuum_index_cleanup)},
- {"vacuum_truncate", RELOPT_TYPE_BOOL,
- offsetof(StdRdOptions, vacuum_truncate), offsetof(StdRdOptions, vacuum_truncate_set)},
+ {"vacuum_truncate", RELOPT_TYPE_TERNARY,
+ offsetof(StdRdOptions, vacuum_truncate)},
{"vacuum_max_eager_freeze_failure_rate", RELOPT_TYPE_REAL,
offsetof(StdRdOptions, vacuum_max_eager_freeze_failure_rate)}
};
@@ -2004,7 +2090,6 @@ build_local_reloptions(local_relopts *relopts, Datum options, bool validate)
elems[i].optname = opt->option->name;
elems[i].opttype = opt->option->type;
elems[i].offset = opt->offset;
- elems[i].isset_offset = 0; /* not supported for local relopts yet */
i++;
}
diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c
index aa4fbec143f..696eab9bd97 100644
--- a/src/backend/commands/vacuum.c
+++ b/src/backend/commands/vacuum.c
@@ -2224,9 +2224,9 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams params,
{
StdRdOptions *opts = (StdRdOptions *) rel->rd_options;
- if (opts && opts->vacuum_truncate_set)
+ if (opts && opts->vacuum_truncate != TERNARY_UNSET)
{
- if (opts->vacuum_truncate)
+ if (opts->vacuum_truncate == TERNARY_TRUE)
params.truncate = VACOPTVALUE_ENABLED;
else
params.truncate = VACOPTVALUE_DISABLED;
diff --git a/src/include/access/reloptions.h b/src/include/access/reloptions.h
index 2f08e1b0cf0..dfbef2babf2 100644
--- a/src/include/access/reloptions.h
+++ b/src/include/access/reloptions.h
@@ -29,6 +29,7 @@
typedef enum relopt_type
{
RELOPT_TYPE_BOOL,
+ RELOPT_TYPE_TERNARY, /* on, off, unset */
RELOPT_TYPE_INT,
RELOPT_TYPE_REAL,
RELOPT_TYPE_ENUM,
@@ -80,6 +81,7 @@ typedef struct relopt_value
union
{
bool bool_val;
+ pg_ternary ternary_val;
int int_val;
double real_val;
int enum_val;
@@ -94,6 +96,12 @@ typedef struct relopt_bool
bool default_val;
} relopt_bool;
+typedef struct relopt_ternary
+{
+ relopt_gen gen;
+ int default_val;
+} relopt_ternary;
+
typedef struct relopt_int
{
relopt_gen gen;
@@ -152,19 +160,6 @@ typedef struct
const char *optname; /* option's name */
relopt_type opttype; /* option's datatype */
int offset; /* offset of field in result struct */
-
- /*
- * isset_offset is an optional offset of a field in the result struct that
- * stores whether the option is explicitly set for the relation or if it
- * just picked up the default value. In most cases, this can be
- * accomplished by giving the reloption a special out-of-range default
- * value (e.g., some integer reloptions use -2), but this isn't always
- * possible. For example, a Boolean reloption cannot be given an
- * out-of-range default, so we need another way to discover the source of
- * its value. This offset is only used if given a value greater than
- * zero.
- */
- int isset_offset;
} relopt_parse_elt;
/* Local reloption definition */
@@ -195,6 +190,9 @@ typedef struct local_relopts
extern relopt_kind add_reloption_kind(void);
extern void add_bool_reloption(bits32 kinds, const char *name, const char *desc,
bool default_val, LOCKMODE lockmode);
+extern void add_ternary_reloption(bits32 kinds, const char *name,
+ const char *desc, pg_ternary default_val,
+ LOCKMODE lockmode);
extern void add_int_reloption(bits32 kinds, const char *name, const char *desc,
int default_val, int min_val, int max_val,
LOCKMODE lockmode);
@@ -214,6 +212,9 @@ extern void register_reloptions_validator(local_relopts *relopts,
extern void add_local_bool_reloption(local_relopts *relopts, const char *name,
const char *desc, bool default_val,
int offset);
+extern void add_local_ternary_reloption(local_relopts *relopts,
+ const char *name, const char *desc,
+ pg_ternary default_val, int offset);
extern void add_local_int_reloption(local_relopts *relopts, const char *name,
const char *desc, int default_val,
int min_val, int max_val, int offset);
diff --git a/src/include/postgres.h b/src/include/postgres.h
index 7d93fbce709..47c88780776 100644
--- a/src/include/postgres.h
+++ b/src/include/postgres.h
@@ -543,6 +543,21 @@ Float8GetDatum(float8 X)
* ----------------------------------------------------------------
*/
+/*
+ * pg_ternary
+ * Boolean value with an extra "unset" value
+ *
+ * This enum can be used for values that want to distinguish between true,
+ * false, and unset.
+*/
+
+typedef enum pg_ternary
+{
+ TERNARY_FALSE = 0,
+ TERNARY_TRUE = 1,
+ TERNARY_UNSET = -1
+} pg_ternary;
+
/*
* NON_EXEC_STATIC: It's sometimes useful to define a variable or function
* that is normally static but extern when using EXEC_BACKEND (see
diff --git a/src/include/utils/rel.h b/src/include/utils/rel.h
index d03ab247788..5feb18a5373 100644
--- a/src/include/utils/rel.h
+++ b/src/include/utils/rel.h
@@ -347,8 +347,7 @@ typedef struct StdRdOptions
bool user_catalog_table; /* use as an additional catalog relation */
int parallel_workers; /* max number of parallel workers */
StdRdOptIndexCleanup vacuum_index_cleanup; /* controls index vacuuming */
- bool vacuum_truncate; /* enables vacuum to truncate a relation */
- bool vacuum_truncate_set; /* whether vacuum_truncate is set */
+ pg_ternary vacuum_truncate; /* enables vacuum to truncate a relation */
/*
* Fraction of pages in a relation that vacuum can eagerly scan and fail
diff --git a/src/test/regress/expected/reloptions.out b/src/test/regress/expected/reloptions.out
index 9de19b4e3f1..1c99f79ab01 100644
--- a/src/test/regress/expected/reloptions.out
+++ b/src/test/regress/expected/reloptions.out
@@ -98,6 +98,42 @@ SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass;
{fillfactor=13,autovacuum_enabled=false}
(1 row)
+-- Tests for future (FIXME) ternary options
+-- behave as boolean option: accept unassigned name and truncated value
+DROP TABLE reloptions_test;
+CREATE TABLE reloptions_test(i INT) WITH (vacuum_truncate);
+SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass;
+ reloptions
+------------------------
+ {vacuum_truncate=true}
+(1 row)
+
+DROP TABLE reloptions_test;
+CREATE TABLE reloptions_test(i INT) WITH (vacuum_truncate=FaLS);
+SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass;
+ reloptions
+------------------------
+ {vacuum_truncate=fals}
+(1 row)
+
+-- preferred "true" alias is used when storing
+DROP TABLE reloptions_test;
+CREATE TABLE reloptions_test(i INT) WITH (vacuum_index_cleanup=on);
+SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass;
+ reloptions
+---------------------------
+ {vacuum_index_cleanup=on}
+(1 row)
+
+-- custom "third" value is available
+DROP TABLE reloptions_test;
+CREATE TABLE reloptions_test(i INT) WITH (vacuum_index_cleanup=auto);
+SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass;
+ reloptions
+-----------------------------
+ {vacuum_index_cleanup=auto}
+(1 row)
+
-- Test vacuum_truncate option
DROP TABLE reloptions_test;
CREATE TEMP TABLE reloptions_test(i INT NOT NULL, j text)
diff --git a/src/test/regress/sql/reloptions.sql b/src/test/regress/sql/reloptions.sql
index 24fbe0b478d..f5980dafcbc 100644
--- a/src/test/regress/sql/reloptions.sql
+++ b/src/test/regress/sql/reloptions.sql
@@ -59,6 +59,27 @@ UPDATE pg_class
ALTER TABLE reloptions_test RESET (illegal_option);
SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass;
+-- Tests for future (FIXME) ternary options
+
+-- behave as boolean option: accept unassigned name and truncated value
+DROP TABLE reloptions_test;
+CREATE TABLE reloptions_test(i INT) WITH (vacuum_truncate);
+SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass;
+
+DROP TABLE reloptions_test;
+CREATE TABLE reloptions_test(i INT) WITH (vacuum_truncate=FaLS);
+SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass;
+
+-- preferred "true" alias is used when storing
+DROP TABLE reloptions_test;
+CREATE TABLE reloptions_test(i INT) WITH (vacuum_index_cleanup=on);
+SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass;
+
+-- custom "third" value is available
+DROP TABLE reloptions_test;
+CREATE TABLE reloptions_test(i INT) WITH (vacuum_index_cleanup=auto);
+SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass;
+
-- Test vacuum_truncate option
DROP TABLE reloptions_test;
diff --git a/src/tools/pgindent/typedefs.list b/src/tools/pgindent/typedefs.list
index 3f3a888fd0e..1c8610fd46c 100644
--- a/src/tools/pgindent/typedefs.list
+++ b/src/tools/pgindent/typedefs.list
@@ -3952,6 +3952,7 @@ pg_sha512_ctx
pg_snapshot
pg_special_case
pg_stack_base_t
+pg_ternary
pg_time_t
pg_time_usec_t
pg_tz
@@ -4079,6 +4080,7 @@ relopt_kind
relopt_parse_elt
relopt_real
relopt_string
+relopt_ternary
relopt_type
relopt_value
relopts_validator
--
2.47.3
--c36t3fndxhkzbl5z--
^ permalink raw reply [nested|flat] 868+ messages in thread
* [PATCH v3] Introduce ternary reloptions
@ 2026-01-16 15:04 Álvaro Herrera <[email protected]>
0 siblings, 0 replies; 868+ messages in thread
From: Álvaro Herrera @ 2026-01-16 15:04 UTC (permalink / raw)
Introduce ternary reloption as a replacement for current
`vacuum_truncate` implementation. Remove the `vacuum_truncate_set`
separate flag and use TERNARY_UNSET instead.
This could also be used for other options such as `vacuum_index_cleanup`
and `buffering`, but lets get the scaffolding in first.
Discussion: https://postgr.es/m/3474141.usfYGdeWWP@thinkpad-pgpro
---
src/backend/access/common/reloptions.c | 137 ++++++++++++++++++-----
src/backend/commands/vacuum.c | 4 +-
src/include/access/reloptions.h | 27 ++---
src/include/postgres.h | 15 +++
src/include/utils/rel.h | 3 +-
src/test/regress/expected/reloptions.out | 36 ++++++
src/test/regress/sql/reloptions.sql | 21 ++++
src/tools/pgindent/typedefs.list | 2 +
8 files changed, 202 insertions(+), 43 deletions(-)
diff --git a/src/backend/access/common/reloptions.c b/src/backend/access/common/reloptions.c
index 0b83f98ed5f..6f1f577581a 100644
--- a/src/backend/access/common/reloptions.c
+++ b/src/backend/access/common/reloptions.c
@@ -40,9 +40,9 @@
*
* To add an option:
*
- * (i) decide on a type (bool, integer, real, enum, string), name, default
- * value, upper and lower bounds (if applicable); for strings, consider a
- * validation routine.
+ * (i) decide on a type (bool, ternary, integer, real, enum, string), name,
+ * default value, upper and lower bounds (if applicable); for strings,
+ * consider a validation routine.
* (ii) add a record below (or use add_<type>_reloption).
* (iii) add it to the appropriate options struct (perhaps StdRdOptions)
* (iv) add it to the appropriate handling routine (perhaps
@@ -147,15 +147,6 @@ static relopt_bool boolRelOpts[] =
},
false
},
- {
- {
- "vacuum_truncate",
- "Enables vacuum to truncate empty pages at the end of this table",
- RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
- ShareUpdateExclusiveLock
- },
- true
- },
{
{
"deduplicate_items",
@@ -170,6 +161,25 @@ static relopt_bool boolRelOpts[] =
{{NULL}}
};
+static relopt_ternary ternaryRelOpts[] =
+{
+ {
+ {
+ "vacuum_truncate",
+ "Enables vacuum to truncate empty pages at the end of this table",
+ RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
+ ShareUpdateExclusiveLock
+ },
+ TERNARY_UNSET
+ },
+ /* list terminator */
+ {
+ {
+ NULL
+ }
+ }
+};
+
static relopt_int intRelOpts[] =
{
{
@@ -609,6 +619,13 @@ initialize_reloptions(void)
boolRelOpts[i].gen.lockmode));
j++;
}
+ for (i = 0; ternaryRelOpts[i].gen.name; i++)
+ {
+ Assert(DoLockModesConflict(ternaryRelOpts[i].gen.lockmode,
+ ternaryRelOpts[i].gen.lockmode));
+ j++;
+ }
+
for (i = 0; intRelOpts[i].gen.name; i++)
{
Assert(DoLockModesConflict(intRelOpts[i].gen.lockmode,
@@ -649,6 +666,14 @@ initialize_reloptions(void)
j++;
}
+ for (i = 0; ternaryRelOpts[i].gen.name; i++)
+ {
+ relOpts[j] = &ternaryRelOpts[i].gen;
+ relOpts[j]->type = RELOPT_TYPE_TERNARY;
+ relOpts[j]->namelen = strlen(relOpts[j]->name);
+ j++;
+ }
+
for (i = 0; intRelOpts[i].gen.name; i++)
{
relOpts[j] = &intRelOpts[i].gen;
@@ -809,6 +834,9 @@ allocate_reloption(bits32 kinds, int type, const char *name, const char *desc,
case RELOPT_TYPE_BOOL:
size = sizeof(relopt_bool);
break;
+ case RELOPT_TYPE_TERNARY:
+ size = sizeof(relopt_ternary);
+ break;
case RELOPT_TYPE_INT:
size = sizeof(relopt_int);
break;
@@ -892,6 +920,57 @@ add_local_bool_reloption(local_relopts *relopts, const char *name,
add_local_reloption(relopts, (relopt_gen *) newoption, offset);
}
+/*
+ * init_ternary_reloption
+ * Allocate and initialize a new ternary reloption
+ */
+static relopt_ternary *
+init_ternary_reloption(bits32 kinds, const char *name, const char *desc,
+ pg_ternary default_val, LOCKMODE lockmode)
+{
+ relopt_ternary *newoption;
+
+ newoption = (relopt_ternary *)
+ allocate_reloption(kinds, RELOPT_TYPE_TERNARY, name, desc, lockmode);
+ newoption->default_val = default_val;
+
+ return newoption;
+}
+
+/*
+ * add_ternary_reloption
+ * Add a new ternary reloption
+ */
+void
+add_ternary_reloption(bits32 kinds, const char *name, const char *desc,
+ pg_ternary default_val, LOCKMODE lockmode)
+{
+ relopt_ternary *newoption;
+
+ newoption =
+ init_ternary_reloption(kinds, name, desc, default_val, lockmode);
+
+ add_reloption((relopt_gen *) newoption);
+}
+
+/*
+ * add_local_ternary_reloption
+ * Add a new ternary local reloption
+ *
+ * 'offset' is offset of ternary-typed field.
+ */
+void
+add_local_ternary_reloption(local_relopts *relopts, const char *name,
+ const char *desc, pg_ternary default_val,
+ int offset)
+{
+ relopt_ternary *newoption;
+
+ newoption =
+ init_ternary_reloption(RELOPT_KIND_LOCAL, name, desc, default_val, 0);
+
+ add_local_reloption(relopts, (relopt_gen *) newoption, offset);
+}
/*
* init_real_reloption
@@ -1626,6 +1705,19 @@ parse_one_reloption(relopt_value *option, char *text_str, int text_len,
option->gen->name, value)));
}
break;
+ case RELOPT_TYPE_TERNARY:
+ {
+ bool b;
+
+ parsed = parse_bool(value, &b);
+ option->values.ternary_val = b ? TERNARY_TRUE : TERNARY_FALSE;
+ if (validate && !parsed)
+ ereport(ERROR,
+ (errcode(ERRCODE_INVALID_PARAMETER_VALUE),
+ errmsg("invalid value for ternary option \"%s\": %s",
+ option->gen->name, value)));
+ }
+ break;
case RELOPT_TYPE_INT:
{
relopt_int *optint = (relopt_int *) option->gen;
@@ -1789,17 +1881,6 @@ fillRelOptions(void *rdopts, Size basesize,
char *itempos = ((char *) rdopts) + elems[j].offset;
char *string_val;
- /*
- * If isset_offset is provided, store whether the reloption is
- * set there.
- */
- if (elems[j].isset_offset > 0)
- {
- char *setpos = ((char *) rdopts) + elems[j].isset_offset;
-
- *(bool *) setpos = options[i].isset;
- }
-
switch (options[i].gen->type)
{
case RELOPT_TYPE_BOOL:
@@ -1807,6 +1888,11 @@ fillRelOptions(void *rdopts, Size basesize,
options[i].values.bool_val :
((relopt_bool *) options[i].gen)->default_val;
break;
+ case RELOPT_TYPE_TERNARY:
+ *(pg_ternary *) itempos = options[i].isset ?
+ options[i].values.ternary_val :
+ ((relopt_ternary *) options[i].gen)->default_val;
+ break;
case RELOPT_TYPE_INT:
*(int *) itempos = options[i].isset ?
options[i].values.int_val :
@@ -1923,8 +2009,8 @@ default_reloptions(Datum reloptions, bool validate, relopt_kind kind)
offsetof(StdRdOptions, parallel_workers)},
{"vacuum_index_cleanup", RELOPT_TYPE_ENUM,
offsetof(StdRdOptions, vacuum_index_cleanup)},
- {"vacuum_truncate", RELOPT_TYPE_BOOL,
- offsetof(StdRdOptions, vacuum_truncate), offsetof(StdRdOptions, vacuum_truncate_set)},
+ {"vacuum_truncate", RELOPT_TYPE_TERNARY,
+ offsetof(StdRdOptions, vacuum_truncate)},
{"vacuum_max_eager_freeze_failure_rate", RELOPT_TYPE_REAL,
offsetof(StdRdOptions, vacuum_max_eager_freeze_failure_rate)}
};
@@ -2004,7 +2090,6 @@ build_local_reloptions(local_relopts *relopts, Datum options, bool validate)
elems[i].optname = opt->option->name;
elems[i].opttype = opt->option->type;
elems[i].offset = opt->offset;
- elems[i].isset_offset = 0; /* not supported for local relopts yet */
i++;
}
diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c
index aa4fbec143f..696eab9bd97 100644
--- a/src/backend/commands/vacuum.c
+++ b/src/backend/commands/vacuum.c
@@ -2224,9 +2224,9 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams params,
{
StdRdOptions *opts = (StdRdOptions *) rel->rd_options;
- if (opts && opts->vacuum_truncate_set)
+ if (opts && opts->vacuum_truncate != TERNARY_UNSET)
{
- if (opts->vacuum_truncate)
+ if (opts->vacuum_truncate == TERNARY_TRUE)
params.truncate = VACOPTVALUE_ENABLED;
else
params.truncate = VACOPTVALUE_DISABLED;
diff --git a/src/include/access/reloptions.h b/src/include/access/reloptions.h
index 2f08e1b0cf0..dfbef2babf2 100644
--- a/src/include/access/reloptions.h
+++ b/src/include/access/reloptions.h
@@ -29,6 +29,7 @@
typedef enum relopt_type
{
RELOPT_TYPE_BOOL,
+ RELOPT_TYPE_TERNARY, /* on, off, unset */
RELOPT_TYPE_INT,
RELOPT_TYPE_REAL,
RELOPT_TYPE_ENUM,
@@ -80,6 +81,7 @@ typedef struct relopt_value
union
{
bool bool_val;
+ pg_ternary ternary_val;
int int_val;
double real_val;
int enum_val;
@@ -94,6 +96,12 @@ typedef struct relopt_bool
bool default_val;
} relopt_bool;
+typedef struct relopt_ternary
+{
+ relopt_gen gen;
+ int default_val;
+} relopt_ternary;
+
typedef struct relopt_int
{
relopt_gen gen;
@@ -152,19 +160,6 @@ typedef struct
const char *optname; /* option's name */
relopt_type opttype; /* option's datatype */
int offset; /* offset of field in result struct */
-
- /*
- * isset_offset is an optional offset of a field in the result struct that
- * stores whether the option is explicitly set for the relation or if it
- * just picked up the default value. In most cases, this can be
- * accomplished by giving the reloption a special out-of-range default
- * value (e.g., some integer reloptions use -2), but this isn't always
- * possible. For example, a Boolean reloption cannot be given an
- * out-of-range default, so we need another way to discover the source of
- * its value. This offset is only used if given a value greater than
- * zero.
- */
- int isset_offset;
} relopt_parse_elt;
/* Local reloption definition */
@@ -195,6 +190,9 @@ typedef struct local_relopts
extern relopt_kind add_reloption_kind(void);
extern void add_bool_reloption(bits32 kinds, const char *name, const char *desc,
bool default_val, LOCKMODE lockmode);
+extern void add_ternary_reloption(bits32 kinds, const char *name,
+ const char *desc, pg_ternary default_val,
+ LOCKMODE lockmode);
extern void add_int_reloption(bits32 kinds, const char *name, const char *desc,
int default_val, int min_val, int max_val,
LOCKMODE lockmode);
@@ -214,6 +212,9 @@ extern void register_reloptions_validator(local_relopts *relopts,
extern void add_local_bool_reloption(local_relopts *relopts, const char *name,
const char *desc, bool default_val,
int offset);
+extern void add_local_ternary_reloption(local_relopts *relopts,
+ const char *name, const char *desc,
+ pg_ternary default_val, int offset);
extern void add_local_int_reloption(local_relopts *relopts, const char *name,
const char *desc, int default_val,
int min_val, int max_val, int offset);
diff --git a/src/include/postgres.h b/src/include/postgres.h
index 7d93fbce709..47c88780776 100644
--- a/src/include/postgres.h
+++ b/src/include/postgres.h
@@ -543,6 +543,21 @@ Float8GetDatum(float8 X)
* ----------------------------------------------------------------
*/
+/*
+ * pg_ternary
+ * Boolean value with an extra "unset" value
+ *
+ * This enum can be used for values that want to distinguish between true,
+ * false, and unset.
+*/
+
+typedef enum pg_ternary
+{
+ TERNARY_FALSE = 0,
+ TERNARY_TRUE = 1,
+ TERNARY_UNSET = -1
+} pg_ternary;
+
/*
* NON_EXEC_STATIC: It's sometimes useful to define a variable or function
* that is normally static but extern when using EXEC_BACKEND (see
diff --git a/src/include/utils/rel.h b/src/include/utils/rel.h
index d03ab247788..5feb18a5373 100644
--- a/src/include/utils/rel.h
+++ b/src/include/utils/rel.h
@@ -347,8 +347,7 @@ typedef struct StdRdOptions
bool user_catalog_table; /* use as an additional catalog relation */
int parallel_workers; /* max number of parallel workers */
StdRdOptIndexCleanup vacuum_index_cleanup; /* controls index vacuuming */
- bool vacuum_truncate; /* enables vacuum to truncate a relation */
- bool vacuum_truncate_set; /* whether vacuum_truncate is set */
+ pg_ternary vacuum_truncate; /* enables vacuum to truncate a relation */
/*
* Fraction of pages in a relation that vacuum can eagerly scan and fail
diff --git a/src/test/regress/expected/reloptions.out b/src/test/regress/expected/reloptions.out
index 9de19b4e3f1..1c99f79ab01 100644
--- a/src/test/regress/expected/reloptions.out
+++ b/src/test/regress/expected/reloptions.out
@@ -98,6 +98,42 @@ SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass;
{fillfactor=13,autovacuum_enabled=false}
(1 row)
+-- Tests for future (FIXME) ternary options
+-- behave as boolean option: accept unassigned name and truncated value
+DROP TABLE reloptions_test;
+CREATE TABLE reloptions_test(i INT) WITH (vacuum_truncate);
+SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass;
+ reloptions
+------------------------
+ {vacuum_truncate=true}
+(1 row)
+
+DROP TABLE reloptions_test;
+CREATE TABLE reloptions_test(i INT) WITH (vacuum_truncate=FaLS);
+SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass;
+ reloptions
+------------------------
+ {vacuum_truncate=fals}
+(1 row)
+
+-- preferred "true" alias is used when storing
+DROP TABLE reloptions_test;
+CREATE TABLE reloptions_test(i INT) WITH (vacuum_index_cleanup=on);
+SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass;
+ reloptions
+---------------------------
+ {vacuum_index_cleanup=on}
+(1 row)
+
+-- custom "third" value is available
+DROP TABLE reloptions_test;
+CREATE TABLE reloptions_test(i INT) WITH (vacuum_index_cleanup=auto);
+SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass;
+ reloptions
+-----------------------------
+ {vacuum_index_cleanup=auto}
+(1 row)
+
-- Test vacuum_truncate option
DROP TABLE reloptions_test;
CREATE TEMP TABLE reloptions_test(i INT NOT NULL, j text)
diff --git a/src/test/regress/sql/reloptions.sql b/src/test/regress/sql/reloptions.sql
index 24fbe0b478d..f5980dafcbc 100644
--- a/src/test/regress/sql/reloptions.sql
+++ b/src/test/regress/sql/reloptions.sql
@@ -59,6 +59,27 @@ UPDATE pg_class
ALTER TABLE reloptions_test RESET (illegal_option);
SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass;
+-- Tests for future (FIXME) ternary options
+
+-- behave as boolean option: accept unassigned name and truncated value
+DROP TABLE reloptions_test;
+CREATE TABLE reloptions_test(i INT) WITH (vacuum_truncate);
+SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass;
+
+DROP TABLE reloptions_test;
+CREATE TABLE reloptions_test(i INT) WITH (vacuum_truncate=FaLS);
+SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass;
+
+-- preferred "true" alias is used when storing
+DROP TABLE reloptions_test;
+CREATE TABLE reloptions_test(i INT) WITH (vacuum_index_cleanup=on);
+SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass;
+
+-- custom "third" value is available
+DROP TABLE reloptions_test;
+CREATE TABLE reloptions_test(i INT) WITH (vacuum_index_cleanup=auto);
+SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass;
+
-- Test vacuum_truncate option
DROP TABLE reloptions_test;
diff --git a/src/tools/pgindent/typedefs.list b/src/tools/pgindent/typedefs.list
index 3f3a888fd0e..1c8610fd46c 100644
--- a/src/tools/pgindent/typedefs.list
+++ b/src/tools/pgindent/typedefs.list
@@ -3952,6 +3952,7 @@ pg_sha512_ctx
pg_snapshot
pg_special_case
pg_stack_base_t
+pg_ternary
pg_time_t
pg_time_usec_t
pg_tz
@@ -4079,6 +4080,7 @@ relopt_kind
relopt_parse_elt
relopt_real
relopt_string
+relopt_ternary
relopt_type
relopt_value
relopts_validator
--
2.47.3
--c36t3fndxhkzbl5z--
^ permalink raw reply [nested|flat] 868+ messages in thread
* [PATCH v3] Introduce ternary reloptions
@ 2026-01-16 15:04 Álvaro Herrera <[email protected]>
0 siblings, 0 replies; 868+ messages in thread
From: Álvaro Herrera @ 2026-01-16 15:04 UTC (permalink / raw)
Introduce ternary reloption as a replacement for current
`vacuum_truncate` implementation. Remove the `vacuum_truncate_set`
separate flag and use TERNARY_UNSET instead.
This could also be used for other options such as `vacuum_index_cleanup`
and `buffering`, but lets get the scaffolding in first.
Discussion: https://postgr.es/m/3474141.usfYGdeWWP@thinkpad-pgpro
---
src/backend/access/common/reloptions.c | 137 ++++++++++++++++++-----
src/backend/commands/vacuum.c | 4 +-
src/include/access/reloptions.h | 27 ++---
src/include/postgres.h | 15 +++
src/include/utils/rel.h | 3 +-
src/test/regress/expected/reloptions.out | 36 ++++++
src/test/regress/sql/reloptions.sql | 21 ++++
src/tools/pgindent/typedefs.list | 2 +
8 files changed, 202 insertions(+), 43 deletions(-)
diff --git a/src/backend/access/common/reloptions.c b/src/backend/access/common/reloptions.c
index 0b83f98ed5f..6f1f577581a 100644
--- a/src/backend/access/common/reloptions.c
+++ b/src/backend/access/common/reloptions.c
@@ -40,9 +40,9 @@
*
* To add an option:
*
- * (i) decide on a type (bool, integer, real, enum, string), name, default
- * value, upper and lower bounds (if applicable); for strings, consider a
- * validation routine.
+ * (i) decide on a type (bool, ternary, integer, real, enum, string), name,
+ * default value, upper and lower bounds (if applicable); for strings,
+ * consider a validation routine.
* (ii) add a record below (or use add_<type>_reloption).
* (iii) add it to the appropriate options struct (perhaps StdRdOptions)
* (iv) add it to the appropriate handling routine (perhaps
@@ -147,15 +147,6 @@ static relopt_bool boolRelOpts[] =
},
false
},
- {
- {
- "vacuum_truncate",
- "Enables vacuum to truncate empty pages at the end of this table",
- RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
- ShareUpdateExclusiveLock
- },
- true
- },
{
{
"deduplicate_items",
@@ -170,6 +161,25 @@ static relopt_bool boolRelOpts[] =
{{NULL}}
};
+static relopt_ternary ternaryRelOpts[] =
+{
+ {
+ {
+ "vacuum_truncate",
+ "Enables vacuum to truncate empty pages at the end of this table",
+ RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
+ ShareUpdateExclusiveLock
+ },
+ TERNARY_UNSET
+ },
+ /* list terminator */
+ {
+ {
+ NULL
+ }
+ }
+};
+
static relopt_int intRelOpts[] =
{
{
@@ -609,6 +619,13 @@ initialize_reloptions(void)
boolRelOpts[i].gen.lockmode));
j++;
}
+ for (i = 0; ternaryRelOpts[i].gen.name; i++)
+ {
+ Assert(DoLockModesConflict(ternaryRelOpts[i].gen.lockmode,
+ ternaryRelOpts[i].gen.lockmode));
+ j++;
+ }
+
for (i = 0; intRelOpts[i].gen.name; i++)
{
Assert(DoLockModesConflict(intRelOpts[i].gen.lockmode,
@@ -649,6 +666,14 @@ initialize_reloptions(void)
j++;
}
+ for (i = 0; ternaryRelOpts[i].gen.name; i++)
+ {
+ relOpts[j] = &ternaryRelOpts[i].gen;
+ relOpts[j]->type = RELOPT_TYPE_TERNARY;
+ relOpts[j]->namelen = strlen(relOpts[j]->name);
+ j++;
+ }
+
for (i = 0; intRelOpts[i].gen.name; i++)
{
relOpts[j] = &intRelOpts[i].gen;
@@ -809,6 +834,9 @@ allocate_reloption(bits32 kinds, int type, const char *name, const char *desc,
case RELOPT_TYPE_BOOL:
size = sizeof(relopt_bool);
break;
+ case RELOPT_TYPE_TERNARY:
+ size = sizeof(relopt_ternary);
+ break;
case RELOPT_TYPE_INT:
size = sizeof(relopt_int);
break;
@@ -892,6 +920,57 @@ add_local_bool_reloption(local_relopts *relopts, const char *name,
add_local_reloption(relopts, (relopt_gen *) newoption, offset);
}
+/*
+ * init_ternary_reloption
+ * Allocate and initialize a new ternary reloption
+ */
+static relopt_ternary *
+init_ternary_reloption(bits32 kinds, const char *name, const char *desc,
+ pg_ternary default_val, LOCKMODE lockmode)
+{
+ relopt_ternary *newoption;
+
+ newoption = (relopt_ternary *)
+ allocate_reloption(kinds, RELOPT_TYPE_TERNARY, name, desc, lockmode);
+ newoption->default_val = default_val;
+
+ return newoption;
+}
+
+/*
+ * add_ternary_reloption
+ * Add a new ternary reloption
+ */
+void
+add_ternary_reloption(bits32 kinds, const char *name, const char *desc,
+ pg_ternary default_val, LOCKMODE lockmode)
+{
+ relopt_ternary *newoption;
+
+ newoption =
+ init_ternary_reloption(kinds, name, desc, default_val, lockmode);
+
+ add_reloption((relopt_gen *) newoption);
+}
+
+/*
+ * add_local_ternary_reloption
+ * Add a new ternary local reloption
+ *
+ * 'offset' is offset of ternary-typed field.
+ */
+void
+add_local_ternary_reloption(local_relopts *relopts, const char *name,
+ const char *desc, pg_ternary default_val,
+ int offset)
+{
+ relopt_ternary *newoption;
+
+ newoption =
+ init_ternary_reloption(RELOPT_KIND_LOCAL, name, desc, default_val, 0);
+
+ add_local_reloption(relopts, (relopt_gen *) newoption, offset);
+}
/*
* init_real_reloption
@@ -1626,6 +1705,19 @@ parse_one_reloption(relopt_value *option, char *text_str, int text_len,
option->gen->name, value)));
}
break;
+ case RELOPT_TYPE_TERNARY:
+ {
+ bool b;
+
+ parsed = parse_bool(value, &b);
+ option->values.ternary_val = b ? TERNARY_TRUE : TERNARY_FALSE;
+ if (validate && !parsed)
+ ereport(ERROR,
+ (errcode(ERRCODE_INVALID_PARAMETER_VALUE),
+ errmsg("invalid value for ternary option \"%s\": %s",
+ option->gen->name, value)));
+ }
+ break;
case RELOPT_TYPE_INT:
{
relopt_int *optint = (relopt_int *) option->gen;
@@ -1789,17 +1881,6 @@ fillRelOptions(void *rdopts, Size basesize,
char *itempos = ((char *) rdopts) + elems[j].offset;
char *string_val;
- /*
- * If isset_offset is provided, store whether the reloption is
- * set there.
- */
- if (elems[j].isset_offset > 0)
- {
- char *setpos = ((char *) rdopts) + elems[j].isset_offset;
-
- *(bool *) setpos = options[i].isset;
- }
-
switch (options[i].gen->type)
{
case RELOPT_TYPE_BOOL:
@@ -1807,6 +1888,11 @@ fillRelOptions(void *rdopts, Size basesize,
options[i].values.bool_val :
((relopt_bool *) options[i].gen)->default_val;
break;
+ case RELOPT_TYPE_TERNARY:
+ *(pg_ternary *) itempos = options[i].isset ?
+ options[i].values.ternary_val :
+ ((relopt_ternary *) options[i].gen)->default_val;
+ break;
case RELOPT_TYPE_INT:
*(int *) itempos = options[i].isset ?
options[i].values.int_val :
@@ -1923,8 +2009,8 @@ default_reloptions(Datum reloptions, bool validate, relopt_kind kind)
offsetof(StdRdOptions, parallel_workers)},
{"vacuum_index_cleanup", RELOPT_TYPE_ENUM,
offsetof(StdRdOptions, vacuum_index_cleanup)},
- {"vacuum_truncate", RELOPT_TYPE_BOOL,
- offsetof(StdRdOptions, vacuum_truncate), offsetof(StdRdOptions, vacuum_truncate_set)},
+ {"vacuum_truncate", RELOPT_TYPE_TERNARY,
+ offsetof(StdRdOptions, vacuum_truncate)},
{"vacuum_max_eager_freeze_failure_rate", RELOPT_TYPE_REAL,
offsetof(StdRdOptions, vacuum_max_eager_freeze_failure_rate)}
};
@@ -2004,7 +2090,6 @@ build_local_reloptions(local_relopts *relopts, Datum options, bool validate)
elems[i].optname = opt->option->name;
elems[i].opttype = opt->option->type;
elems[i].offset = opt->offset;
- elems[i].isset_offset = 0; /* not supported for local relopts yet */
i++;
}
diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c
index aa4fbec143f..696eab9bd97 100644
--- a/src/backend/commands/vacuum.c
+++ b/src/backend/commands/vacuum.c
@@ -2224,9 +2224,9 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams params,
{
StdRdOptions *opts = (StdRdOptions *) rel->rd_options;
- if (opts && opts->vacuum_truncate_set)
+ if (opts && opts->vacuum_truncate != TERNARY_UNSET)
{
- if (opts->vacuum_truncate)
+ if (opts->vacuum_truncate == TERNARY_TRUE)
params.truncate = VACOPTVALUE_ENABLED;
else
params.truncate = VACOPTVALUE_DISABLED;
diff --git a/src/include/access/reloptions.h b/src/include/access/reloptions.h
index 2f08e1b0cf0..dfbef2babf2 100644
--- a/src/include/access/reloptions.h
+++ b/src/include/access/reloptions.h
@@ -29,6 +29,7 @@
typedef enum relopt_type
{
RELOPT_TYPE_BOOL,
+ RELOPT_TYPE_TERNARY, /* on, off, unset */
RELOPT_TYPE_INT,
RELOPT_TYPE_REAL,
RELOPT_TYPE_ENUM,
@@ -80,6 +81,7 @@ typedef struct relopt_value
union
{
bool bool_val;
+ pg_ternary ternary_val;
int int_val;
double real_val;
int enum_val;
@@ -94,6 +96,12 @@ typedef struct relopt_bool
bool default_val;
} relopt_bool;
+typedef struct relopt_ternary
+{
+ relopt_gen gen;
+ int default_val;
+} relopt_ternary;
+
typedef struct relopt_int
{
relopt_gen gen;
@@ -152,19 +160,6 @@ typedef struct
const char *optname; /* option's name */
relopt_type opttype; /* option's datatype */
int offset; /* offset of field in result struct */
-
- /*
- * isset_offset is an optional offset of a field in the result struct that
- * stores whether the option is explicitly set for the relation or if it
- * just picked up the default value. In most cases, this can be
- * accomplished by giving the reloption a special out-of-range default
- * value (e.g., some integer reloptions use -2), but this isn't always
- * possible. For example, a Boolean reloption cannot be given an
- * out-of-range default, so we need another way to discover the source of
- * its value. This offset is only used if given a value greater than
- * zero.
- */
- int isset_offset;
} relopt_parse_elt;
/* Local reloption definition */
@@ -195,6 +190,9 @@ typedef struct local_relopts
extern relopt_kind add_reloption_kind(void);
extern void add_bool_reloption(bits32 kinds, const char *name, const char *desc,
bool default_val, LOCKMODE lockmode);
+extern void add_ternary_reloption(bits32 kinds, const char *name,
+ const char *desc, pg_ternary default_val,
+ LOCKMODE lockmode);
extern void add_int_reloption(bits32 kinds, const char *name, const char *desc,
int default_val, int min_val, int max_val,
LOCKMODE lockmode);
@@ -214,6 +212,9 @@ extern void register_reloptions_validator(local_relopts *relopts,
extern void add_local_bool_reloption(local_relopts *relopts, const char *name,
const char *desc, bool default_val,
int offset);
+extern void add_local_ternary_reloption(local_relopts *relopts,
+ const char *name, const char *desc,
+ pg_ternary default_val, int offset);
extern void add_local_int_reloption(local_relopts *relopts, const char *name,
const char *desc, int default_val,
int min_val, int max_val, int offset);
diff --git a/src/include/postgres.h b/src/include/postgres.h
index 7d93fbce709..47c88780776 100644
--- a/src/include/postgres.h
+++ b/src/include/postgres.h
@@ -543,6 +543,21 @@ Float8GetDatum(float8 X)
* ----------------------------------------------------------------
*/
+/*
+ * pg_ternary
+ * Boolean value with an extra "unset" value
+ *
+ * This enum can be used for values that want to distinguish between true,
+ * false, and unset.
+*/
+
+typedef enum pg_ternary
+{
+ TERNARY_FALSE = 0,
+ TERNARY_TRUE = 1,
+ TERNARY_UNSET = -1
+} pg_ternary;
+
/*
* NON_EXEC_STATIC: It's sometimes useful to define a variable or function
* that is normally static but extern when using EXEC_BACKEND (see
diff --git a/src/include/utils/rel.h b/src/include/utils/rel.h
index d03ab247788..5feb18a5373 100644
--- a/src/include/utils/rel.h
+++ b/src/include/utils/rel.h
@@ -347,8 +347,7 @@ typedef struct StdRdOptions
bool user_catalog_table; /* use as an additional catalog relation */
int parallel_workers; /* max number of parallel workers */
StdRdOptIndexCleanup vacuum_index_cleanup; /* controls index vacuuming */
- bool vacuum_truncate; /* enables vacuum to truncate a relation */
- bool vacuum_truncate_set; /* whether vacuum_truncate is set */
+ pg_ternary vacuum_truncate; /* enables vacuum to truncate a relation */
/*
* Fraction of pages in a relation that vacuum can eagerly scan and fail
diff --git a/src/test/regress/expected/reloptions.out b/src/test/regress/expected/reloptions.out
index 9de19b4e3f1..1c99f79ab01 100644
--- a/src/test/regress/expected/reloptions.out
+++ b/src/test/regress/expected/reloptions.out
@@ -98,6 +98,42 @@ SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass;
{fillfactor=13,autovacuum_enabled=false}
(1 row)
+-- Tests for future (FIXME) ternary options
+-- behave as boolean option: accept unassigned name and truncated value
+DROP TABLE reloptions_test;
+CREATE TABLE reloptions_test(i INT) WITH (vacuum_truncate);
+SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass;
+ reloptions
+------------------------
+ {vacuum_truncate=true}
+(1 row)
+
+DROP TABLE reloptions_test;
+CREATE TABLE reloptions_test(i INT) WITH (vacuum_truncate=FaLS);
+SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass;
+ reloptions
+------------------------
+ {vacuum_truncate=fals}
+(1 row)
+
+-- preferred "true" alias is used when storing
+DROP TABLE reloptions_test;
+CREATE TABLE reloptions_test(i INT) WITH (vacuum_index_cleanup=on);
+SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass;
+ reloptions
+---------------------------
+ {vacuum_index_cleanup=on}
+(1 row)
+
+-- custom "third" value is available
+DROP TABLE reloptions_test;
+CREATE TABLE reloptions_test(i INT) WITH (vacuum_index_cleanup=auto);
+SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass;
+ reloptions
+-----------------------------
+ {vacuum_index_cleanup=auto}
+(1 row)
+
-- Test vacuum_truncate option
DROP TABLE reloptions_test;
CREATE TEMP TABLE reloptions_test(i INT NOT NULL, j text)
diff --git a/src/test/regress/sql/reloptions.sql b/src/test/regress/sql/reloptions.sql
index 24fbe0b478d..f5980dafcbc 100644
--- a/src/test/regress/sql/reloptions.sql
+++ b/src/test/regress/sql/reloptions.sql
@@ -59,6 +59,27 @@ UPDATE pg_class
ALTER TABLE reloptions_test RESET (illegal_option);
SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass;
+-- Tests for future (FIXME) ternary options
+
+-- behave as boolean option: accept unassigned name and truncated value
+DROP TABLE reloptions_test;
+CREATE TABLE reloptions_test(i INT) WITH (vacuum_truncate);
+SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass;
+
+DROP TABLE reloptions_test;
+CREATE TABLE reloptions_test(i INT) WITH (vacuum_truncate=FaLS);
+SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass;
+
+-- preferred "true" alias is used when storing
+DROP TABLE reloptions_test;
+CREATE TABLE reloptions_test(i INT) WITH (vacuum_index_cleanup=on);
+SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass;
+
+-- custom "third" value is available
+DROP TABLE reloptions_test;
+CREATE TABLE reloptions_test(i INT) WITH (vacuum_index_cleanup=auto);
+SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass;
+
-- Test vacuum_truncate option
DROP TABLE reloptions_test;
diff --git a/src/tools/pgindent/typedefs.list b/src/tools/pgindent/typedefs.list
index 3f3a888fd0e..1c8610fd46c 100644
--- a/src/tools/pgindent/typedefs.list
+++ b/src/tools/pgindent/typedefs.list
@@ -3952,6 +3952,7 @@ pg_sha512_ctx
pg_snapshot
pg_special_case
pg_stack_base_t
+pg_ternary
pg_time_t
pg_time_usec_t
pg_tz
@@ -4079,6 +4080,7 @@ relopt_kind
relopt_parse_elt
relopt_real
relopt_string
+relopt_ternary
relopt_type
relopt_value
relopts_validator
--
2.47.3
--c36t3fndxhkzbl5z--
^ permalink raw reply [nested|flat] 868+ messages in thread
* [PATCH v3] Introduce ternary reloptions
@ 2026-01-16 15:04 Álvaro Herrera <[email protected]>
0 siblings, 0 replies; 868+ messages in thread
From: Álvaro Herrera @ 2026-01-16 15:04 UTC (permalink / raw)
Introduce ternary reloption as a replacement for current
`vacuum_truncate` implementation. Remove the `vacuum_truncate_set`
separate flag and use TERNARY_UNSET instead.
This could also be used for other options such as `vacuum_index_cleanup`
and `buffering`, but lets get the scaffolding in first.
Discussion: https://postgr.es/m/3474141.usfYGdeWWP@thinkpad-pgpro
---
src/backend/access/common/reloptions.c | 137 ++++++++++++++++++-----
src/backend/commands/vacuum.c | 4 +-
src/include/access/reloptions.h | 27 ++---
src/include/postgres.h | 15 +++
src/include/utils/rel.h | 3 +-
src/test/regress/expected/reloptions.out | 36 ++++++
src/test/regress/sql/reloptions.sql | 21 ++++
src/tools/pgindent/typedefs.list | 2 +
8 files changed, 202 insertions(+), 43 deletions(-)
diff --git a/src/backend/access/common/reloptions.c b/src/backend/access/common/reloptions.c
index 0b83f98ed5f..6f1f577581a 100644
--- a/src/backend/access/common/reloptions.c
+++ b/src/backend/access/common/reloptions.c
@@ -40,9 +40,9 @@
*
* To add an option:
*
- * (i) decide on a type (bool, integer, real, enum, string), name, default
- * value, upper and lower bounds (if applicable); for strings, consider a
- * validation routine.
+ * (i) decide on a type (bool, ternary, integer, real, enum, string), name,
+ * default value, upper and lower bounds (if applicable); for strings,
+ * consider a validation routine.
* (ii) add a record below (or use add_<type>_reloption).
* (iii) add it to the appropriate options struct (perhaps StdRdOptions)
* (iv) add it to the appropriate handling routine (perhaps
@@ -147,15 +147,6 @@ static relopt_bool boolRelOpts[] =
},
false
},
- {
- {
- "vacuum_truncate",
- "Enables vacuum to truncate empty pages at the end of this table",
- RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
- ShareUpdateExclusiveLock
- },
- true
- },
{
{
"deduplicate_items",
@@ -170,6 +161,25 @@ static relopt_bool boolRelOpts[] =
{{NULL}}
};
+static relopt_ternary ternaryRelOpts[] =
+{
+ {
+ {
+ "vacuum_truncate",
+ "Enables vacuum to truncate empty pages at the end of this table",
+ RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
+ ShareUpdateExclusiveLock
+ },
+ TERNARY_UNSET
+ },
+ /* list terminator */
+ {
+ {
+ NULL
+ }
+ }
+};
+
static relopt_int intRelOpts[] =
{
{
@@ -609,6 +619,13 @@ initialize_reloptions(void)
boolRelOpts[i].gen.lockmode));
j++;
}
+ for (i = 0; ternaryRelOpts[i].gen.name; i++)
+ {
+ Assert(DoLockModesConflict(ternaryRelOpts[i].gen.lockmode,
+ ternaryRelOpts[i].gen.lockmode));
+ j++;
+ }
+
for (i = 0; intRelOpts[i].gen.name; i++)
{
Assert(DoLockModesConflict(intRelOpts[i].gen.lockmode,
@@ -649,6 +666,14 @@ initialize_reloptions(void)
j++;
}
+ for (i = 0; ternaryRelOpts[i].gen.name; i++)
+ {
+ relOpts[j] = &ternaryRelOpts[i].gen;
+ relOpts[j]->type = RELOPT_TYPE_TERNARY;
+ relOpts[j]->namelen = strlen(relOpts[j]->name);
+ j++;
+ }
+
for (i = 0; intRelOpts[i].gen.name; i++)
{
relOpts[j] = &intRelOpts[i].gen;
@@ -809,6 +834,9 @@ allocate_reloption(bits32 kinds, int type, const char *name, const char *desc,
case RELOPT_TYPE_BOOL:
size = sizeof(relopt_bool);
break;
+ case RELOPT_TYPE_TERNARY:
+ size = sizeof(relopt_ternary);
+ break;
case RELOPT_TYPE_INT:
size = sizeof(relopt_int);
break;
@@ -892,6 +920,57 @@ add_local_bool_reloption(local_relopts *relopts, const char *name,
add_local_reloption(relopts, (relopt_gen *) newoption, offset);
}
+/*
+ * init_ternary_reloption
+ * Allocate and initialize a new ternary reloption
+ */
+static relopt_ternary *
+init_ternary_reloption(bits32 kinds, const char *name, const char *desc,
+ pg_ternary default_val, LOCKMODE lockmode)
+{
+ relopt_ternary *newoption;
+
+ newoption = (relopt_ternary *)
+ allocate_reloption(kinds, RELOPT_TYPE_TERNARY, name, desc, lockmode);
+ newoption->default_val = default_val;
+
+ return newoption;
+}
+
+/*
+ * add_ternary_reloption
+ * Add a new ternary reloption
+ */
+void
+add_ternary_reloption(bits32 kinds, const char *name, const char *desc,
+ pg_ternary default_val, LOCKMODE lockmode)
+{
+ relopt_ternary *newoption;
+
+ newoption =
+ init_ternary_reloption(kinds, name, desc, default_val, lockmode);
+
+ add_reloption((relopt_gen *) newoption);
+}
+
+/*
+ * add_local_ternary_reloption
+ * Add a new ternary local reloption
+ *
+ * 'offset' is offset of ternary-typed field.
+ */
+void
+add_local_ternary_reloption(local_relopts *relopts, const char *name,
+ const char *desc, pg_ternary default_val,
+ int offset)
+{
+ relopt_ternary *newoption;
+
+ newoption =
+ init_ternary_reloption(RELOPT_KIND_LOCAL, name, desc, default_val, 0);
+
+ add_local_reloption(relopts, (relopt_gen *) newoption, offset);
+}
/*
* init_real_reloption
@@ -1626,6 +1705,19 @@ parse_one_reloption(relopt_value *option, char *text_str, int text_len,
option->gen->name, value)));
}
break;
+ case RELOPT_TYPE_TERNARY:
+ {
+ bool b;
+
+ parsed = parse_bool(value, &b);
+ option->values.ternary_val = b ? TERNARY_TRUE : TERNARY_FALSE;
+ if (validate && !parsed)
+ ereport(ERROR,
+ (errcode(ERRCODE_INVALID_PARAMETER_VALUE),
+ errmsg("invalid value for ternary option \"%s\": %s",
+ option->gen->name, value)));
+ }
+ break;
case RELOPT_TYPE_INT:
{
relopt_int *optint = (relopt_int *) option->gen;
@@ -1789,17 +1881,6 @@ fillRelOptions(void *rdopts, Size basesize,
char *itempos = ((char *) rdopts) + elems[j].offset;
char *string_val;
- /*
- * If isset_offset is provided, store whether the reloption is
- * set there.
- */
- if (elems[j].isset_offset > 0)
- {
- char *setpos = ((char *) rdopts) + elems[j].isset_offset;
-
- *(bool *) setpos = options[i].isset;
- }
-
switch (options[i].gen->type)
{
case RELOPT_TYPE_BOOL:
@@ -1807,6 +1888,11 @@ fillRelOptions(void *rdopts, Size basesize,
options[i].values.bool_val :
((relopt_bool *) options[i].gen)->default_val;
break;
+ case RELOPT_TYPE_TERNARY:
+ *(pg_ternary *) itempos = options[i].isset ?
+ options[i].values.ternary_val :
+ ((relopt_ternary *) options[i].gen)->default_val;
+ break;
case RELOPT_TYPE_INT:
*(int *) itempos = options[i].isset ?
options[i].values.int_val :
@@ -1923,8 +2009,8 @@ default_reloptions(Datum reloptions, bool validate, relopt_kind kind)
offsetof(StdRdOptions, parallel_workers)},
{"vacuum_index_cleanup", RELOPT_TYPE_ENUM,
offsetof(StdRdOptions, vacuum_index_cleanup)},
- {"vacuum_truncate", RELOPT_TYPE_BOOL,
- offsetof(StdRdOptions, vacuum_truncate), offsetof(StdRdOptions, vacuum_truncate_set)},
+ {"vacuum_truncate", RELOPT_TYPE_TERNARY,
+ offsetof(StdRdOptions, vacuum_truncate)},
{"vacuum_max_eager_freeze_failure_rate", RELOPT_TYPE_REAL,
offsetof(StdRdOptions, vacuum_max_eager_freeze_failure_rate)}
};
@@ -2004,7 +2090,6 @@ build_local_reloptions(local_relopts *relopts, Datum options, bool validate)
elems[i].optname = opt->option->name;
elems[i].opttype = opt->option->type;
elems[i].offset = opt->offset;
- elems[i].isset_offset = 0; /* not supported for local relopts yet */
i++;
}
diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c
index aa4fbec143f..696eab9bd97 100644
--- a/src/backend/commands/vacuum.c
+++ b/src/backend/commands/vacuum.c
@@ -2224,9 +2224,9 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams params,
{
StdRdOptions *opts = (StdRdOptions *) rel->rd_options;
- if (opts && opts->vacuum_truncate_set)
+ if (opts && opts->vacuum_truncate != TERNARY_UNSET)
{
- if (opts->vacuum_truncate)
+ if (opts->vacuum_truncate == TERNARY_TRUE)
params.truncate = VACOPTVALUE_ENABLED;
else
params.truncate = VACOPTVALUE_DISABLED;
diff --git a/src/include/access/reloptions.h b/src/include/access/reloptions.h
index 2f08e1b0cf0..dfbef2babf2 100644
--- a/src/include/access/reloptions.h
+++ b/src/include/access/reloptions.h
@@ -29,6 +29,7 @@
typedef enum relopt_type
{
RELOPT_TYPE_BOOL,
+ RELOPT_TYPE_TERNARY, /* on, off, unset */
RELOPT_TYPE_INT,
RELOPT_TYPE_REAL,
RELOPT_TYPE_ENUM,
@@ -80,6 +81,7 @@ typedef struct relopt_value
union
{
bool bool_val;
+ pg_ternary ternary_val;
int int_val;
double real_val;
int enum_val;
@@ -94,6 +96,12 @@ typedef struct relopt_bool
bool default_val;
} relopt_bool;
+typedef struct relopt_ternary
+{
+ relopt_gen gen;
+ int default_val;
+} relopt_ternary;
+
typedef struct relopt_int
{
relopt_gen gen;
@@ -152,19 +160,6 @@ typedef struct
const char *optname; /* option's name */
relopt_type opttype; /* option's datatype */
int offset; /* offset of field in result struct */
-
- /*
- * isset_offset is an optional offset of a field in the result struct that
- * stores whether the option is explicitly set for the relation or if it
- * just picked up the default value. In most cases, this can be
- * accomplished by giving the reloption a special out-of-range default
- * value (e.g., some integer reloptions use -2), but this isn't always
- * possible. For example, a Boolean reloption cannot be given an
- * out-of-range default, so we need another way to discover the source of
- * its value. This offset is only used if given a value greater than
- * zero.
- */
- int isset_offset;
} relopt_parse_elt;
/* Local reloption definition */
@@ -195,6 +190,9 @@ typedef struct local_relopts
extern relopt_kind add_reloption_kind(void);
extern void add_bool_reloption(bits32 kinds, const char *name, const char *desc,
bool default_val, LOCKMODE lockmode);
+extern void add_ternary_reloption(bits32 kinds, const char *name,
+ const char *desc, pg_ternary default_val,
+ LOCKMODE lockmode);
extern void add_int_reloption(bits32 kinds, const char *name, const char *desc,
int default_val, int min_val, int max_val,
LOCKMODE lockmode);
@@ -214,6 +212,9 @@ extern void register_reloptions_validator(local_relopts *relopts,
extern void add_local_bool_reloption(local_relopts *relopts, const char *name,
const char *desc, bool default_val,
int offset);
+extern void add_local_ternary_reloption(local_relopts *relopts,
+ const char *name, const char *desc,
+ pg_ternary default_val, int offset);
extern void add_local_int_reloption(local_relopts *relopts, const char *name,
const char *desc, int default_val,
int min_val, int max_val, int offset);
diff --git a/src/include/postgres.h b/src/include/postgres.h
index 7d93fbce709..47c88780776 100644
--- a/src/include/postgres.h
+++ b/src/include/postgres.h
@@ -543,6 +543,21 @@ Float8GetDatum(float8 X)
* ----------------------------------------------------------------
*/
+/*
+ * pg_ternary
+ * Boolean value with an extra "unset" value
+ *
+ * This enum can be used for values that want to distinguish between true,
+ * false, and unset.
+*/
+
+typedef enum pg_ternary
+{
+ TERNARY_FALSE = 0,
+ TERNARY_TRUE = 1,
+ TERNARY_UNSET = -1
+} pg_ternary;
+
/*
* NON_EXEC_STATIC: It's sometimes useful to define a variable or function
* that is normally static but extern when using EXEC_BACKEND (see
diff --git a/src/include/utils/rel.h b/src/include/utils/rel.h
index d03ab247788..5feb18a5373 100644
--- a/src/include/utils/rel.h
+++ b/src/include/utils/rel.h
@@ -347,8 +347,7 @@ typedef struct StdRdOptions
bool user_catalog_table; /* use as an additional catalog relation */
int parallel_workers; /* max number of parallel workers */
StdRdOptIndexCleanup vacuum_index_cleanup; /* controls index vacuuming */
- bool vacuum_truncate; /* enables vacuum to truncate a relation */
- bool vacuum_truncate_set; /* whether vacuum_truncate is set */
+ pg_ternary vacuum_truncate; /* enables vacuum to truncate a relation */
/*
* Fraction of pages in a relation that vacuum can eagerly scan and fail
diff --git a/src/test/regress/expected/reloptions.out b/src/test/regress/expected/reloptions.out
index 9de19b4e3f1..1c99f79ab01 100644
--- a/src/test/regress/expected/reloptions.out
+++ b/src/test/regress/expected/reloptions.out
@@ -98,6 +98,42 @@ SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass;
{fillfactor=13,autovacuum_enabled=false}
(1 row)
+-- Tests for future (FIXME) ternary options
+-- behave as boolean option: accept unassigned name and truncated value
+DROP TABLE reloptions_test;
+CREATE TABLE reloptions_test(i INT) WITH (vacuum_truncate);
+SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass;
+ reloptions
+------------------------
+ {vacuum_truncate=true}
+(1 row)
+
+DROP TABLE reloptions_test;
+CREATE TABLE reloptions_test(i INT) WITH (vacuum_truncate=FaLS);
+SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass;
+ reloptions
+------------------------
+ {vacuum_truncate=fals}
+(1 row)
+
+-- preferred "true" alias is used when storing
+DROP TABLE reloptions_test;
+CREATE TABLE reloptions_test(i INT) WITH (vacuum_index_cleanup=on);
+SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass;
+ reloptions
+---------------------------
+ {vacuum_index_cleanup=on}
+(1 row)
+
+-- custom "third" value is available
+DROP TABLE reloptions_test;
+CREATE TABLE reloptions_test(i INT) WITH (vacuum_index_cleanup=auto);
+SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass;
+ reloptions
+-----------------------------
+ {vacuum_index_cleanup=auto}
+(1 row)
+
-- Test vacuum_truncate option
DROP TABLE reloptions_test;
CREATE TEMP TABLE reloptions_test(i INT NOT NULL, j text)
diff --git a/src/test/regress/sql/reloptions.sql b/src/test/regress/sql/reloptions.sql
index 24fbe0b478d..f5980dafcbc 100644
--- a/src/test/regress/sql/reloptions.sql
+++ b/src/test/regress/sql/reloptions.sql
@@ -59,6 +59,27 @@ UPDATE pg_class
ALTER TABLE reloptions_test RESET (illegal_option);
SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass;
+-- Tests for future (FIXME) ternary options
+
+-- behave as boolean option: accept unassigned name and truncated value
+DROP TABLE reloptions_test;
+CREATE TABLE reloptions_test(i INT) WITH (vacuum_truncate);
+SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass;
+
+DROP TABLE reloptions_test;
+CREATE TABLE reloptions_test(i INT) WITH (vacuum_truncate=FaLS);
+SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass;
+
+-- preferred "true" alias is used when storing
+DROP TABLE reloptions_test;
+CREATE TABLE reloptions_test(i INT) WITH (vacuum_index_cleanup=on);
+SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass;
+
+-- custom "third" value is available
+DROP TABLE reloptions_test;
+CREATE TABLE reloptions_test(i INT) WITH (vacuum_index_cleanup=auto);
+SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass;
+
-- Test vacuum_truncate option
DROP TABLE reloptions_test;
diff --git a/src/tools/pgindent/typedefs.list b/src/tools/pgindent/typedefs.list
index 3f3a888fd0e..1c8610fd46c 100644
--- a/src/tools/pgindent/typedefs.list
+++ b/src/tools/pgindent/typedefs.list
@@ -3952,6 +3952,7 @@ pg_sha512_ctx
pg_snapshot
pg_special_case
pg_stack_base_t
+pg_ternary
pg_time_t
pg_time_usec_t
pg_tz
@@ -4079,6 +4080,7 @@ relopt_kind
relopt_parse_elt
relopt_real
relopt_string
+relopt_ternary
relopt_type
relopt_value
relopts_validator
--
2.47.3
--c36t3fndxhkzbl5z--
^ permalink raw reply [nested|flat] 868+ messages in thread
* [PATCH v3] Introduce ternary reloptions
@ 2026-01-16 15:04 Álvaro Herrera <[email protected]>
0 siblings, 0 replies; 868+ messages in thread
From: Álvaro Herrera @ 2026-01-16 15:04 UTC (permalink / raw)
Introduce ternary reloption as a replacement for current
`vacuum_truncate` implementation. Remove the `vacuum_truncate_set`
separate flag and use TERNARY_UNSET instead.
This could also be used for other options such as `vacuum_index_cleanup`
and `buffering`, but lets get the scaffolding in first.
Discussion: https://postgr.es/m/3474141.usfYGdeWWP@thinkpad-pgpro
---
src/backend/access/common/reloptions.c | 137 ++++++++++++++++++-----
src/backend/commands/vacuum.c | 4 +-
src/include/access/reloptions.h | 27 ++---
src/include/postgres.h | 15 +++
src/include/utils/rel.h | 3 +-
src/test/regress/expected/reloptions.out | 36 ++++++
src/test/regress/sql/reloptions.sql | 21 ++++
src/tools/pgindent/typedefs.list | 2 +
8 files changed, 202 insertions(+), 43 deletions(-)
diff --git a/src/backend/access/common/reloptions.c b/src/backend/access/common/reloptions.c
index 0b83f98ed5f..6f1f577581a 100644
--- a/src/backend/access/common/reloptions.c
+++ b/src/backend/access/common/reloptions.c
@@ -40,9 +40,9 @@
*
* To add an option:
*
- * (i) decide on a type (bool, integer, real, enum, string), name, default
- * value, upper and lower bounds (if applicable); for strings, consider a
- * validation routine.
+ * (i) decide on a type (bool, ternary, integer, real, enum, string), name,
+ * default value, upper and lower bounds (if applicable); for strings,
+ * consider a validation routine.
* (ii) add a record below (or use add_<type>_reloption).
* (iii) add it to the appropriate options struct (perhaps StdRdOptions)
* (iv) add it to the appropriate handling routine (perhaps
@@ -147,15 +147,6 @@ static relopt_bool boolRelOpts[] =
},
false
},
- {
- {
- "vacuum_truncate",
- "Enables vacuum to truncate empty pages at the end of this table",
- RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
- ShareUpdateExclusiveLock
- },
- true
- },
{
{
"deduplicate_items",
@@ -170,6 +161,25 @@ static relopt_bool boolRelOpts[] =
{{NULL}}
};
+static relopt_ternary ternaryRelOpts[] =
+{
+ {
+ {
+ "vacuum_truncate",
+ "Enables vacuum to truncate empty pages at the end of this table",
+ RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
+ ShareUpdateExclusiveLock
+ },
+ TERNARY_UNSET
+ },
+ /* list terminator */
+ {
+ {
+ NULL
+ }
+ }
+};
+
static relopt_int intRelOpts[] =
{
{
@@ -609,6 +619,13 @@ initialize_reloptions(void)
boolRelOpts[i].gen.lockmode));
j++;
}
+ for (i = 0; ternaryRelOpts[i].gen.name; i++)
+ {
+ Assert(DoLockModesConflict(ternaryRelOpts[i].gen.lockmode,
+ ternaryRelOpts[i].gen.lockmode));
+ j++;
+ }
+
for (i = 0; intRelOpts[i].gen.name; i++)
{
Assert(DoLockModesConflict(intRelOpts[i].gen.lockmode,
@@ -649,6 +666,14 @@ initialize_reloptions(void)
j++;
}
+ for (i = 0; ternaryRelOpts[i].gen.name; i++)
+ {
+ relOpts[j] = &ternaryRelOpts[i].gen;
+ relOpts[j]->type = RELOPT_TYPE_TERNARY;
+ relOpts[j]->namelen = strlen(relOpts[j]->name);
+ j++;
+ }
+
for (i = 0; intRelOpts[i].gen.name; i++)
{
relOpts[j] = &intRelOpts[i].gen;
@@ -809,6 +834,9 @@ allocate_reloption(bits32 kinds, int type, const char *name, const char *desc,
case RELOPT_TYPE_BOOL:
size = sizeof(relopt_bool);
break;
+ case RELOPT_TYPE_TERNARY:
+ size = sizeof(relopt_ternary);
+ break;
case RELOPT_TYPE_INT:
size = sizeof(relopt_int);
break;
@@ -892,6 +920,57 @@ add_local_bool_reloption(local_relopts *relopts, const char *name,
add_local_reloption(relopts, (relopt_gen *) newoption, offset);
}
+/*
+ * init_ternary_reloption
+ * Allocate and initialize a new ternary reloption
+ */
+static relopt_ternary *
+init_ternary_reloption(bits32 kinds, const char *name, const char *desc,
+ pg_ternary default_val, LOCKMODE lockmode)
+{
+ relopt_ternary *newoption;
+
+ newoption = (relopt_ternary *)
+ allocate_reloption(kinds, RELOPT_TYPE_TERNARY, name, desc, lockmode);
+ newoption->default_val = default_val;
+
+ return newoption;
+}
+
+/*
+ * add_ternary_reloption
+ * Add a new ternary reloption
+ */
+void
+add_ternary_reloption(bits32 kinds, const char *name, const char *desc,
+ pg_ternary default_val, LOCKMODE lockmode)
+{
+ relopt_ternary *newoption;
+
+ newoption =
+ init_ternary_reloption(kinds, name, desc, default_val, lockmode);
+
+ add_reloption((relopt_gen *) newoption);
+}
+
+/*
+ * add_local_ternary_reloption
+ * Add a new ternary local reloption
+ *
+ * 'offset' is offset of ternary-typed field.
+ */
+void
+add_local_ternary_reloption(local_relopts *relopts, const char *name,
+ const char *desc, pg_ternary default_val,
+ int offset)
+{
+ relopt_ternary *newoption;
+
+ newoption =
+ init_ternary_reloption(RELOPT_KIND_LOCAL, name, desc, default_val, 0);
+
+ add_local_reloption(relopts, (relopt_gen *) newoption, offset);
+}
/*
* init_real_reloption
@@ -1626,6 +1705,19 @@ parse_one_reloption(relopt_value *option, char *text_str, int text_len,
option->gen->name, value)));
}
break;
+ case RELOPT_TYPE_TERNARY:
+ {
+ bool b;
+
+ parsed = parse_bool(value, &b);
+ option->values.ternary_val = b ? TERNARY_TRUE : TERNARY_FALSE;
+ if (validate && !parsed)
+ ereport(ERROR,
+ (errcode(ERRCODE_INVALID_PARAMETER_VALUE),
+ errmsg("invalid value for ternary option \"%s\": %s",
+ option->gen->name, value)));
+ }
+ break;
case RELOPT_TYPE_INT:
{
relopt_int *optint = (relopt_int *) option->gen;
@@ -1789,17 +1881,6 @@ fillRelOptions(void *rdopts, Size basesize,
char *itempos = ((char *) rdopts) + elems[j].offset;
char *string_val;
- /*
- * If isset_offset is provided, store whether the reloption is
- * set there.
- */
- if (elems[j].isset_offset > 0)
- {
- char *setpos = ((char *) rdopts) + elems[j].isset_offset;
-
- *(bool *) setpos = options[i].isset;
- }
-
switch (options[i].gen->type)
{
case RELOPT_TYPE_BOOL:
@@ -1807,6 +1888,11 @@ fillRelOptions(void *rdopts, Size basesize,
options[i].values.bool_val :
((relopt_bool *) options[i].gen)->default_val;
break;
+ case RELOPT_TYPE_TERNARY:
+ *(pg_ternary *) itempos = options[i].isset ?
+ options[i].values.ternary_val :
+ ((relopt_ternary *) options[i].gen)->default_val;
+ break;
case RELOPT_TYPE_INT:
*(int *) itempos = options[i].isset ?
options[i].values.int_val :
@@ -1923,8 +2009,8 @@ default_reloptions(Datum reloptions, bool validate, relopt_kind kind)
offsetof(StdRdOptions, parallel_workers)},
{"vacuum_index_cleanup", RELOPT_TYPE_ENUM,
offsetof(StdRdOptions, vacuum_index_cleanup)},
- {"vacuum_truncate", RELOPT_TYPE_BOOL,
- offsetof(StdRdOptions, vacuum_truncate), offsetof(StdRdOptions, vacuum_truncate_set)},
+ {"vacuum_truncate", RELOPT_TYPE_TERNARY,
+ offsetof(StdRdOptions, vacuum_truncate)},
{"vacuum_max_eager_freeze_failure_rate", RELOPT_TYPE_REAL,
offsetof(StdRdOptions, vacuum_max_eager_freeze_failure_rate)}
};
@@ -2004,7 +2090,6 @@ build_local_reloptions(local_relopts *relopts, Datum options, bool validate)
elems[i].optname = opt->option->name;
elems[i].opttype = opt->option->type;
elems[i].offset = opt->offset;
- elems[i].isset_offset = 0; /* not supported for local relopts yet */
i++;
}
diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c
index aa4fbec143f..696eab9bd97 100644
--- a/src/backend/commands/vacuum.c
+++ b/src/backend/commands/vacuum.c
@@ -2224,9 +2224,9 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams params,
{
StdRdOptions *opts = (StdRdOptions *) rel->rd_options;
- if (opts && opts->vacuum_truncate_set)
+ if (opts && opts->vacuum_truncate != TERNARY_UNSET)
{
- if (opts->vacuum_truncate)
+ if (opts->vacuum_truncate == TERNARY_TRUE)
params.truncate = VACOPTVALUE_ENABLED;
else
params.truncate = VACOPTVALUE_DISABLED;
diff --git a/src/include/access/reloptions.h b/src/include/access/reloptions.h
index 2f08e1b0cf0..dfbef2babf2 100644
--- a/src/include/access/reloptions.h
+++ b/src/include/access/reloptions.h
@@ -29,6 +29,7 @@
typedef enum relopt_type
{
RELOPT_TYPE_BOOL,
+ RELOPT_TYPE_TERNARY, /* on, off, unset */
RELOPT_TYPE_INT,
RELOPT_TYPE_REAL,
RELOPT_TYPE_ENUM,
@@ -80,6 +81,7 @@ typedef struct relopt_value
union
{
bool bool_val;
+ pg_ternary ternary_val;
int int_val;
double real_val;
int enum_val;
@@ -94,6 +96,12 @@ typedef struct relopt_bool
bool default_val;
} relopt_bool;
+typedef struct relopt_ternary
+{
+ relopt_gen gen;
+ int default_val;
+} relopt_ternary;
+
typedef struct relopt_int
{
relopt_gen gen;
@@ -152,19 +160,6 @@ typedef struct
const char *optname; /* option's name */
relopt_type opttype; /* option's datatype */
int offset; /* offset of field in result struct */
-
- /*
- * isset_offset is an optional offset of a field in the result struct that
- * stores whether the option is explicitly set for the relation or if it
- * just picked up the default value. In most cases, this can be
- * accomplished by giving the reloption a special out-of-range default
- * value (e.g., some integer reloptions use -2), but this isn't always
- * possible. For example, a Boolean reloption cannot be given an
- * out-of-range default, so we need another way to discover the source of
- * its value. This offset is only used if given a value greater than
- * zero.
- */
- int isset_offset;
} relopt_parse_elt;
/* Local reloption definition */
@@ -195,6 +190,9 @@ typedef struct local_relopts
extern relopt_kind add_reloption_kind(void);
extern void add_bool_reloption(bits32 kinds, const char *name, const char *desc,
bool default_val, LOCKMODE lockmode);
+extern void add_ternary_reloption(bits32 kinds, const char *name,
+ const char *desc, pg_ternary default_val,
+ LOCKMODE lockmode);
extern void add_int_reloption(bits32 kinds, const char *name, const char *desc,
int default_val, int min_val, int max_val,
LOCKMODE lockmode);
@@ -214,6 +212,9 @@ extern void register_reloptions_validator(local_relopts *relopts,
extern void add_local_bool_reloption(local_relopts *relopts, const char *name,
const char *desc, bool default_val,
int offset);
+extern void add_local_ternary_reloption(local_relopts *relopts,
+ const char *name, const char *desc,
+ pg_ternary default_val, int offset);
extern void add_local_int_reloption(local_relopts *relopts, const char *name,
const char *desc, int default_val,
int min_val, int max_val, int offset);
diff --git a/src/include/postgres.h b/src/include/postgres.h
index 7d93fbce709..47c88780776 100644
--- a/src/include/postgres.h
+++ b/src/include/postgres.h
@@ -543,6 +543,21 @@ Float8GetDatum(float8 X)
* ----------------------------------------------------------------
*/
+/*
+ * pg_ternary
+ * Boolean value with an extra "unset" value
+ *
+ * This enum can be used for values that want to distinguish between true,
+ * false, and unset.
+*/
+
+typedef enum pg_ternary
+{
+ TERNARY_FALSE = 0,
+ TERNARY_TRUE = 1,
+ TERNARY_UNSET = -1
+} pg_ternary;
+
/*
* NON_EXEC_STATIC: It's sometimes useful to define a variable or function
* that is normally static but extern when using EXEC_BACKEND (see
diff --git a/src/include/utils/rel.h b/src/include/utils/rel.h
index d03ab247788..5feb18a5373 100644
--- a/src/include/utils/rel.h
+++ b/src/include/utils/rel.h
@@ -347,8 +347,7 @@ typedef struct StdRdOptions
bool user_catalog_table; /* use as an additional catalog relation */
int parallel_workers; /* max number of parallel workers */
StdRdOptIndexCleanup vacuum_index_cleanup; /* controls index vacuuming */
- bool vacuum_truncate; /* enables vacuum to truncate a relation */
- bool vacuum_truncate_set; /* whether vacuum_truncate is set */
+ pg_ternary vacuum_truncate; /* enables vacuum to truncate a relation */
/*
* Fraction of pages in a relation that vacuum can eagerly scan and fail
diff --git a/src/test/regress/expected/reloptions.out b/src/test/regress/expected/reloptions.out
index 9de19b4e3f1..1c99f79ab01 100644
--- a/src/test/regress/expected/reloptions.out
+++ b/src/test/regress/expected/reloptions.out
@@ -98,6 +98,42 @@ SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass;
{fillfactor=13,autovacuum_enabled=false}
(1 row)
+-- Tests for future (FIXME) ternary options
+-- behave as boolean option: accept unassigned name and truncated value
+DROP TABLE reloptions_test;
+CREATE TABLE reloptions_test(i INT) WITH (vacuum_truncate);
+SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass;
+ reloptions
+------------------------
+ {vacuum_truncate=true}
+(1 row)
+
+DROP TABLE reloptions_test;
+CREATE TABLE reloptions_test(i INT) WITH (vacuum_truncate=FaLS);
+SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass;
+ reloptions
+------------------------
+ {vacuum_truncate=fals}
+(1 row)
+
+-- preferred "true" alias is used when storing
+DROP TABLE reloptions_test;
+CREATE TABLE reloptions_test(i INT) WITH (vacuum_index_cleanup=on);
+SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass;
+ reloptions
+---------------------------
+ {vacuum_index_cleanup=on}
+(1 row)
+
+-- custom "third" value is available
+DROP TABLE reloptions_test;
+CREATE TABLE reloptions_test(i INT) WITH (vacuum_index_cleanup=auto);
+SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass;
+ reloptions
+-----------------------------
+ {vacuum_index_cleanup=auto}
+(1 row)
+
-- Test vacuum_truncate option
DROP TABLE reloptions_test;
CREATE TEMP TABLE reloptions_test(i INT NOT NULL, j text)
diff --git a/src/test/regress/sql/reloptions.sql b/src/test/regress/sql/reloptions.sql
index 24fbe0b478d..f5980dafcbc 100644
--- a/src/test/regress/sql/reloptions.sql
+++ b/src/test/regress/sql/reloptions.sql
@@ -59,6 +59,27 @@ UPDATE pg_class
ALTER TABLE reloptions_test RESET (illegal_option);
SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass;
+-- Tests for future (FIXME) ternary options
+
+-- behave as boolean option: accept unassigned name and truncated value
+DROP TABLE reloptions_test;
+CREATE TABLE reloptions_test(i INT) WITH (vacuum_truncate);
+SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass;
+
+DROP TABLE reloptions_test;
+CREATE TABLE reloptions_test(i INT) WITH (vacuum_truncate=FaLS);
+SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass;
+
+-- preferred "true" alias is used when storing
+DROP TABLE reloptions_test;
+CREATE TABLE reloptions_test(i INT) WITH (vacuum_index_cleanup=on);
+SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass;
+
+-- custom "third" value is available
+DROP TABLE reloptions_test;
+CREATE TABLE reloptions_test(i INT) WITH (vacuum_index_cleanup=auto);
+SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass;
+
-- Test vacuum_truncate option
DROP TABLE reloptions_test;
diff --git a/src/tools/pgindent/typedefs.list b/src/tools/pgindent/typedefs.list
index 3f3a888fd0e..1c8610fd46c 100644
--- a/src/tools/pgindent/typedefs.list
+++ b/src/tools/pgindent/typedefs.list
@@ -3952,6 +3952,7 @@ pg_sha512_ctx
pg_snapshot
pg_special_case
pg_stack_base_t
+pg_ternary
pg_time_t
pg_time_usec_t
pg_tz
@@ -4079,6 +4080,7 @@ relopt_kind
relopt_parse_elt
relopt_real
relopt_string
+relopt_ternary
relopt_type
relopt_value
relopts_validator
--
2.47.3
--c36t3fndxhkzbl5z--
^ permalink raw reply [nested|flat] 868+ messages in thread
* [PATCH v3] Introduce ternary reloptions
@ 2026-01-16 15:04 Álvaro Herrera <[email protected]>
0 siblings, 0 replies; 868+ messages in thread
From: Álvaro Herrera @ 2026-01-16 15:04 UTC (permalink / raw)
Introduce ternary reloption as a replacement for current
`vacuum_truncate` implementation. Remove the `vacuum_truncate_set`
separate flag and use TERNARY_UNSET instead.
This could also be used for other options such as `vacuum_index_cleanup`
and `buffering`, but lets get the scaffolding in first.
Discussion: https://postgr.es/m/3474141.usfYGdeWWP@thinkpad-pgpro
---
src/backend/access/common/reloptions.c | 137 ++++++++++++++++++-----
src/backend/commands/vacuum.c | 4 +-
src/include/access/reloptions.h | 27 ++---
src/include/postgres.h | 15 +++
src/include/utils/rel.h | 3 +-
src/test/regress/expected/reloptions.out | 36 ++++++
src/test/regress/sql/reloptions.sql | 21 ++++
src/tools/pgindent/typedefs.list | 2 +
8 files changed, 202 insertions(+), 43 deletions(-)
diff --git a/src/backend/access/common/reloptions.c b/src/backend/access/common/reloptions.c
index 0b83f98ed5f..6f1f577581a 100644
--- a/src/backend/access/common/reloptions.c
+++ b/src/backend/access/common/reloptions.c
@@ -40,9 +40,9 @@
*
* To add an option:
*
- * (i) decide on a type (bool, integer, real, enum, string), name, default
- * value, upper and lower bounds (if applicable); for strings, consider a
- * validation routine.
+ * (i) decide on a type (bool, ternary, integer, real, enum, string), name,
+ * default value, upper and lower bounds (if applicable); for strings,
+ * consider a validation routine.
* (ii) add a record below (or use add_<type>_reloption).
* (iii) add it to the appropriate options struct (perhaps StdRdOptions)
* (iv) add it to the appropriate handling routine (perhaps
@@ -147,15 +147,6 @@ static relopt_bool boolRelOpts[] =
},
false
},
- {
- {
- "vacuum_truncate",
- "Enables vacuum to truncate empty pages at the end of this table",
- RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
- ShareUpdateExclusiveLock
- },
- true
- },
{
{
"deduplicate_items",
@@ -170,6 +161,25 @@ static relopt_bool boolRelOpts[] =
{{NULL}}
};
+static relopt_ternary ternaryRelOpts[] =
+{
+ {
+ {
+ "vacuum_truncate",
+ "Enables vacuum to truncate empty pages at the end of this table",
+ RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
+ ShareUpdateExclusiveLock
+ },
+ TERNARY_UNSET
+ },
+ /* list terminator */
+ {
+ {
+ NULL
+ }
+ }
+};
+
static relopt_int intRelOpts[] =
{
{
@@ -609,6 +619,13 @@ initialize_reloptions(void)
boolRelOpts[i].gen.lockmode));
j++;
}
+ for (i = 0; ternaryRelOpts[i].gen.name; i++)
+ {
+ Assert(DoLockModesConflict(ternaryRelOpts[i].gen.lockmode,
+ ternaryRelOpts[i].gen.lockmode));
+ j++;
+ }
+
for (i = 0; intRelOpts[i].gen.name; i++)
{
Assert(DoLockModesConflict(intRelOpts[i].gen.lockmode,
@@ -649,6 +666,14 @@ initialize_reloptions(void)
j++;
}
+ for (i = 0; ternaryRelOpts[i].gen.name; i++)
+ {
+ relOpts[j] = &ternaryRelOpts[i].gen;
+ relOpts[j]->type = RELOPT_TYPE_TERNARY;
+ relOpts[j]->namelen = strlen(relOpts[j]->name);
+ j++;
+ }
+
for (i = 0; intRelOpts[i].gen.name; i++)
{
relOpts[j] = &intRelOpts[i].gen;
@@ -809,6 +834,9 @@ allocate_reloption(bits32 kinds, int type, const char *name, const char *desc,
case RELOPT_TYPE_BOOL:
size = sizeof(relopt_bool);
break;
+ case RELOPT_TYPE_TERNARY:
+ size = sizeof(relopt_ternary);
+ break;
case RELOPT_TYPE_INT:
size = sizeof(relopt_int);
break;
@@ -892,6 +920,57 @@ add_local_bool_reloption(local_relopts *relopts, const char *name,
add_local_reloption(relopts, (relopt_gen *) newoption, offset);
}
+/*
+ * init_ternary_reloption
+ * Allocate and initialize a new ternary reloption
+ */
+static relopt_ternary *
+init_ternary_reloption(bits32 kinds, const char *name, const char *desc,
+ pg_ternary default_val, LOCKMODE lockmode)
+{
+ relopt_ternary *newoption;
+
+ newoption = (relopt_ternary *)
+ allocate_reloption(kinds, RELOPT_TYPE_TERNARY, name, desc, lockmode);
+ newoption->default_val = default_val;
+
+ return newoption;
+}
+
+/*
+ * add_ternary_reloption
+ * Add a new ternary reloption
+ */
+void
+add_ternary_reloption(bits32 kinds, const char *name, const char *desc,
+ pg_ternary default_val, LOCKMODE lockmode)
+{
+ relopt_ternary *newoption;
+
+ newoption =
+ init_ternary_reloption(kinds, name, desc, default_val, lockmode);
+
+ add_reloption((relopt_gen *) newoption);
+}
+
+/*
+ * add_local_ternary_reloption
+ * Add a new ternary local reloption
+ *
+ * 'offset' is offset of ternary-typed field.
+ */
+void
+add_local_ternary_reloption(local_relopts *relopts, const char *name,
+ const char *desc, pg_ternary default_val,
+ int offset)
+{
+ relopt_ternary *newoption;
+
+ newoption =
+ init_ternary_reloption(RELOPT_KIND_LOCAL, name, desc, default_val, 0);
+
+ add_local_reloption(relopts, (relopt_gen *) newoption, offset);
+}
/*
* init_real_reloption
@@ -1626,6 +1705,19 @@ parse_one_reloption(relopt_value *option, char *text_str, int text_len,
option->gen->name, value)));
}
break;
+ case RELOPT_TYPE_TERNARY:
+ {
+ bool b;
+
+ parsed = parse_bool(value, &b);
+ option->values.ternary_val = b ? TERNARY_TRUE : TERNARY_FALSE;
+ if (validate && !parsed)
+ ereport(ERROR,
+ (errcode(ERRCODE_INVALID_PARAMETER_VALUE),
+ errmsg("invalid value for ternary option \"%s\": %s",
+ option->gen->name, value)));
+ }
+ break;
case RELOPT_TYPE_INT:
{
relopt_int *optint = (relopt_int *) option->gen;
@@ -1789,17 +1881,6 @@ fillRelOptions(void *rdopts, Size basesize,
char *itempos = ((char *) rdopts) + elems[j].offset;
char *string_val;
- /*
- * If isset_offset is provided, store whether the reloption is
- * set there.
- */
- if (elems[j].isset_offset > 0)
- {
- char *setpos = ((char *) rdopts) + elems[j].isset_offset;
-
- *(bool *) setpos = options[i].isset;
- }
-
switch (options[i].gen->type)
{
case RELOPT_TYPE_BOOL:
@@ -1807,6 +1888,11 @@ fillRelOptions(void *rdopts, Size basesize,
options[i].values.bool_val :
((relopt_bool *) options[i].gen)->default_val;
break;
+ case RELOPT_TYPE_TERNARY:
+ *(pg_ternary *) itempos = options[i].isset ?
+ options[i].values.ternary_val :
+ ((relopt_ternary *) options[i].gen)->default_val;
+ break;
case RELOPT_TYPE_INT:
*(int *) itempos = options[i].isset ?
options[i].values.int_val :
@@ -1923,8 +2009,8 @@ default_reloptions(Datum reloptions, bool validate, relopt_kind kind)
offsetof(StdRdOptions, parallel_workers)},
{"vacuum_index_cleanup", RELOPT_TYPE_ENUM,
offsetof(StdRdOptions, vacuum_index_cleanup)},
- {"vacuum_truncate", RELOPT_TYPE_BOOL,
- offsetof(StdRdOptions, vacuum_truncate), offsetof(StdRdOptions, vacuum_truncate_set)},
+ {"vacuum_truncate", RELOPT_TYPE_TERNARY,
+ offsetof(StdRdOptions, vacuum_truncate)},
{"vacuum_max_eager_freeze_failure_rate", RELOPT_TYPE_REAL,
offsetof(StdRdOptions, vacuum_max_eager_freeze_failure_rate)}
};
@@ -2004,7 +2090,6 @@ build_local_reloptions(local_relopts *relopts, Datum options, bool validate)
elems[i].optname = opt->option->name;
elems[i].opttype = opt->option->type;
elems[i].offset = opt->offset;
- elems[i].isset_offset = 0; /* not supported for local relopts yet */
i++;
}
diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c
index aa4fbec143f..696eab9bd97 100644
--- a/src/backend/commands/vacuum.c
+++ b/src/backend/commands/vacuum.c
@@ -2224,9 +2224,9 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams params,
{
StdRdOptions *opts = (StdRdOptions *) rel->rd_options;
- if (opts && opts->vacuum_truncate_set)
+ if (opts && opts->vacuum_truncate != TERNARY_UNSET)
{
- if (opts->vacuum_truncate)
+ if (opts->vacuum_truncate == TERNARY_TRUE)
params.truncate = VACOPTVALUE_ENABLED;
else
params.truncate = VACOPTVALUE_DISABLED;
diff --git a/src/include/access/reloptions.h b/src/include/access/reloptions.h
index 2f08e1b0cf0..dfbef2babf2 100644
--- a/src/include/access/reloptions.h
+++ b/src/include/access/reloptions.h
@@ -29,6 +29,7 @@
typedef enum relopt_type
{
RELOPT_TYPE_BOOL,
+ RELOPT_TYPE_TERNARY, /* on, off, unset */
RELOPT_TYPE_INT,
RELOPT_TYPE_REAL,
RELOPT_TYPE_ENUM,
@@ -80,6 +81,7 @@ typedef struct relopt_value
union
{
bool bool_val;
+ pg_ternary ternary_val;
int int_val;
double real_val;
int enum_val;
@@ -94,6 +96,12 @@ typedef struct relopt_bool
bool default_val;
} relopt_bool;
+typedef struct relopt_ternary
+{
+ relopt_gen gen;
+ int default_val;
+} relopt_ternary;
+
typedef struct relopt_int
{
relopt_gen gen;
@@ -152,19 +160,6 @@ typedef struct
const char *optname; /* option's name */
relopt_type opttype; /* option's datatype */
int offset; /* offset of field in result struct */
-
- /*
- * isset_offset is an optional offset of a field in the result struct that
- * stores whether the option is explicitly set for the relation or if it
- * just picked up the default value. In most cases, this can be
- * accomplished by giving the reloption a special out-of-range default
- * value (e.g., some integer reloptions use -2), but this isn't always
- * possible. For example, a Boolean reloption cannot be given an
- * out-of-range default, so we need another way to discover the source of
- * its value. This offset is only used if given a value greater than
- * zero.
- */
- int isset_offset;
} relopt_parse_elt;
/* Local reloption definition */
@@ -195,6 +190,9 @@ typedef struct local_relopts
extern relopt_kind add_reloption_kind(void);
extern void add_bool_reloption(bits32 kinds, const char *name, const char *desc,
bool default_val, LOCKMODE lockmode);
+extern void add_ternary_reloption(bits32 kinds, const char *name,
+ const char *desc, pg_ternary default_val,
+ LOCKMODE lockmode);
extern void add_int_reloption(bits32 kinds, const char *name, const char *desc,
int default_val, int min_val, int max_val,
LOCKMODE lockmode);
@@ -214,6 +212,9 @@ extern void register_reloptions_validator(local_relopts *relopts,
extern void add_local_bool_reloption(local_relopts *relopts, const char *name,
const char *desc, bool default_val,
int offset);
+extern void add_local_ternary_reloption(local_relopts *relopts,
+ const char *name, const char *desc,
+ pg_ternary default_val, int offset);
extern void add_local_int_reloption(local_relopts *relopts, const char *name,
const char *desc, int default_val,
int min_val, int max_val, int offset);
diff --git a/src/include/postgres.h b/src/include/postgres.h
index 7d93fbce709..47c88780776 100644
--- a/src/include/postgres.h
+++ b/src/include/postgres.h
@@ -543,6 +543,21 @@ Float8GetDatum(float8 X)
* ----------------------------------------------------------------
*/
+/*
+ * pg_ternary
+ * Boolean value with an extra "unset" value
+ *
+ * This enum can be used for values that want to distinguish between true,
+ * false, and unset.
+*/
+
+typedef enum pg_ternary
+{
+ TERNARY_FALSE = 0,
+ TERNARY_TRUE = 1,
+ TERNARY_UNSET = -1
+} pg_ternary;
+
/*
* NON_EXEC_STATIC: It's sometimes useful to define a variable or function
* that is normally static but extern when using EXEC_BACKEND (see
diff --git a/src/include/utils/rel.h b/src/include/utils/rel.h
index d03ab247788..5feb18a5373 100644
--- a/src/include/utils/rel.h
+++ b/src/include/utils/rel.h
@@ -347,8 +347,7 @@ typedef struct StdRdOptions
bool user_catalog_table; /* use as an additional catalog relation */
int parallel_workers; /* max number of parallel workers */
StdRdOptIndexCleanup vacuum_index_cleanup; /* controls index vacuuming */
- bool vacuum_truncate; /* enables vacuum to truncate a relation */
- bool vacuum_truncate_set; /* whether vacuum_truncate is set */
+ pg_ternary vacuum_truncate; /* enables vacuum to truncate a relation */
/*
* Fraction of pages in a relation that vacuum can eagerly scan and fail
diff --git a/src/test/regress/expected/reloptions.out b/src/test/regress/expected/reloptions.out
index 9de19b4e3f1..1c99f79ab01 100644
--- a/src/test/regress/expected/reloptions.out
+++ b/src/test/regress/expected/reloptions.out
@@ -98,6 +98,42 @@ SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass;
{fillfactor=13,autovacuum_enabled=false}
(1 row)
+-- Tests for future (FIXME) ternary options
+-- behave as boolean option: accept unassigned name and truncated value
+DROP TABLE reloptions_test;
+CREATE TABLE reloptions_test(i INT) WITH (vacuum_truncate);
+SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass;
+ reloptions
+------------------------
+ {vacuum_truncate=true}
+(1 row)
+
+DROP TABLE reloptions_test;
+CREATE TABLE reloptions_test(i INT) WITH (vacuum_truncate=FaLS);
+SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass;
+ reloptions
+------------------------
+ {vacuum_truncate=fals}
+(1 row)
+
+-- preferred "true" alias is used when storing
+DROP TABLE reloptions_test;
+CREATE TABLE reloptions_test(i INT) WITH (vacuum_index_cleanup=on);
+SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass;
+ reloptions
+---------------------------
+ {vacuum_index_cleanup=on}
+(1 row)
+
+-- custom "third" value is available
+DROP TABLE reloptions_test;
+CREATE TABLE reloptions_test(i INT) WITH (vacuum_index_cleanup=auto);
+SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass;
+ reloptions
+-----------------------------
+ {vacuum_index_cleanup=auto}
+(1 row)
+
-- Test vacuum_truncate option
DROP TABLE reloptions_test;
CREATE TEMP TABLE reloptions_test(i INT NOT NULL, j text)
diff --git a/src/test/regress/sql/reloptions.sql b/src/test/regress/sql/reloptions.sql
index 24fbe0b478d..f5980dafcbc 100644
--- a/src/test/regress/sql/reloptions.sql
+++ b/src/test/regress/sql/reloptions.sql
@@ -59,6 +59,27 @@ UPDATE pg_class
ALTER TABLE reloptions_test RESET (illegal_option);
SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass;
+-- Tests for future (FIXME) ternary options
+
+-- behave as boolean option: accept unassigned name and truncated value
+DROP TABLE reloptions_test;
+CREATE TABLE reloptions_test(i INT) WITH (vacuum_truncate);
+SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass;
+
+DROP TABLE reloptions_test;
+CREATE TABLE reloptions_test(i INT) WITH (vacuum_truncate=FaLS);
+SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass;
+
+-- preferred "true" alias is used when storing
+DROP TABLE reloptions_test;
+CREATE TABLE reloptions_test(i INT) WITH (vacuum_index_cleanup=on);
+SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass;
+
+-- custom "third" value is available
+DROP TABLE reloptions_test;
+CREATE TABLE reloptions_test(i INT) WITH (vacuum_index_cleanup=auto);
+SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass;
+
-- Test vacuum_truncate option
DROP TABLE reloptions_test;
diff --git a/src/tools/pgindent/typedefs.list b/src/tools/pgindent/typedefs.list
index 3f3a888fd0e..1c8610fd46c 100644
--- a/src/tools/pgindent/typedefs.list
+++ b/src/tools/pgindent/typedefs.list
@@ -3952,6 +3952,7 @@ pg_sha512_ctx
pg_snapshot
pg_special_case
pg_stack_base_t
+pg_ternary
pg_time_t
pg_time_usec_t
pg_tz
@@ -4079,6 +4080,7 @@ relopt_kind
relopt_parse_elt
relopt_real
relopt_string
+relopt_ternary
relopt_type
relopt_value
relopts_validator
--
2.47.3
--c36t3fndxhkzbl5z--
^ permalink raw reply [nested|flat] 868+ messages in thread
* [PATCH v3] Introduce ternary reloptions
@ 2026-01-16 15:04 Álvaro Herrera <[email protected]>
0 siblings, 0 replies; 868+ messages in thread
From: Álvaro Herrera @ 2026-01-16 15:04 UTC (permalink / raw)
Introduce ternary reloption as a replacement for current
`vacuum_truncate` implementation. Remove the `vacuum_truncate_set`
separate flag and use TERNARY_UNSET instead.
This could also be used for other options such as `vacuum_index_cleanup`
and `buffering`, but lets get the scaffolding in first.
Discussion: https://postgr.es/m/3474141.usfYGdeWWP@thinkpad-pgpro
---
src/backend/access/common/reloptions.c | 137 ++++++++++++++++++-----
src/backend/commands/vacuum.c | 4 +-
src/include/access/reloptions.h | 27 ++---
src/include/postgres.h | 15 +++
src/include/utils/rel.h | 3 +-
src/test/regress/expected/reloptions.out | 36 ++++++
src/test/regress/sql/reloptions.sql | 21 ++++
src/tools/pgindent/typedefs.list | 2 +
8 files changed, 202 insertions(+), 43 deletions(-)
diff --git a/src/backend/access/common/reloptions.c b/src/backend/access/common/reloptions.c
index 0b83f98ed5f..6f1f577581a 100644
--- a/src/backend/access/common/reloptions.c
+++ b/src/backend/access/common/reloptions.c
@@ -40,9 +40,9 @@
*
* To add an option:
*
- * (i) decide on a type (bool, integer, real, enum, string), name, default
- * value, upper and lower bounds (if applicable); for strings, consider a
- * validation routine.
+ * (i) decide on a type (bool, ternary, integer, real, enum, string), name,
+ * default value, upper and lower bounds (if applicable); for strings,
+ * consider a validation routine.
* (ii) add a record below (or use add_<type>_reloption).
* (iii) add it to the appropriate options struct (perhaps StdRdOptions)
* (iv) add it to the appropriate handling routine (perhaps
@@ -147,15 +147,6 @@ static relopt_bool boolRelOpts[] =
},
false
},
- {
- {
- "vacuum_truncate",
- "Enables vacuum to truncate empty pages at the end of this table",
- RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
- ShareUpdateExclusiveLock
- },
- true
- },
{
{
"deduplicate_items",
@@ -170,6 +161,25 @@ static relopt_bool boolRelOpts[] =
{{NULL}}
};
+static relopt_ternary ternaryRelOpts[] =
+{
+ {
+ {
+ "vacuum_truncate",
+ "Enables vacuum to truncate empty pages at the end of this table",
+ RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
+ ShareUpdateExclusiveLock
+ },
+ TERNARY_UNSET
+ },
+ /* list terminator */
+ {
+ {
+ NULL
+ }
+ }
+};
+
static relopt_int intRelOpts[] =
{
{
@@ -609,6 +619,13 @@ initialize_reloptions(void)
boolRelOpts[i].gen.lockmode));
j++;
}
+ for (i = 0; ternaryRelOpts[i].gen.name; i++)
+ {
+ Assert(DoLockModesConflict(ternaryRelOpts[i].gen.lockmode,
+ ternaryRelOpts[i].gen.lockmode));
+ j++;
+ }
+
for (i = 0; intRelOpts[i].gen.name; i++)
{
Assert(DoLockModesConflict(intRelOpts[i].gen.lockmode,
@@ -649,6 +666,14 @@ initialize_reloptions(void)
j++;
}
+ for (i = 0; ternaryRelOpts[i].gen.name; i++)
+ {
+ relOpts[j] = &ternaryRelOpts[i].gen;
+ relOpts[j]->type = RELOPT_TYPE_TERNARY;
+ relOpts[j]->namelen = strlen(relOpts[j]->name);
+ j++;
+ }
+
for (i = 0; intRelOpts[i].gen.name; i++)
{
relOpts[j] = &intRelOpts[i].gen;
@@ -809,6 +834,9 @@ allocate_reloption(bits32 kinds, int type, const char *name, const char *desc,
case RELOPT_TYPE_BOOL:
size = sizeof(relopt_bool);
break;
+ case RELOPT_TYPE_TERNARY:
+ size = sizeof(relopt_ternary);
+ break;
case RELOPT_TYPE_INT:
size = sizeof(relopt_int);
break;
@@ -892,6 +920,57 @@ add_local_bool_reloption(local_relopts *relopts, const char *name,
add_local_reloption(relopts, (relopt_gen *) newoption, offset);
}
+/*
+ * init_ternary_reloption
+ * Allocate and initialize a new ternary reloption
+ */
+static relopt_ternary *
+init_ternary_reloption(bits32 kinds, const char *name, const char *desc,
+ pg_ternary default_val, LOCKMODE lockmode)
+{
+ relopt_ternary *newoption;
+
+ newoption = (relopt_ternary *)
+ allocate_reloption(kinds, RELOPT_TYPE_TERNARY, name, desc, lockmode);
+ newoption->default_val = default_val;
+
+ return newoption;
+}
+
+/*
+ * add_ternary_reloption
+ * Add a new ternary reloption
+ */
+void
+add_ternary_reloption(bits32 kinds, const char *name, const char *desc,
+ pg_ternary default_val, LOCKMODE lockmode)
+{
+ relopt_ternary *newoption;
+
+ newoption =
+ init_ternary_reloption(kinds, name, desc, default_val, lockmode);
+
+ add_reloption((relopt_gen *) newoption);
+}
+
+/*
+ * add_local_ternary_reloption
+ * Add a new ternary local reloption
+ *
+ * 'offset' is offset of ternary-typed field.
+ */
+void
+add_local_ternary_reloption(local_relopts *relopts, const char *name,
+ const char *desc, pg_ternary default_val,
+ int offset)
+{
+ relopt_ternary *newoption;
+
+ newoption =
+ init_ternary_reloption(RELOPT_KIND_LOCAL, name, desc, default_val, 0);
+
+ add_local_reloption(relopts, (relopt_gen *) newoption, offset);
+}
/*
* init_real_reloption
@@ -1626,6 +1705,19 @@ parse_one_reloption(relopt_value *option, char *text_str, int text_len,
option->gen->name, value)));
}
break;
+ case RELOPT_TYPE_TERNARY:
+ {
+ bool b;
+
+ parsed = parse_bool(value, &b);
+ option->values.ternary_val = b ? TERNARY_TRUE : TERNARY_FALSE;
+ if (validate && !parsed)
+ ereport(ERROR,
+ (errcode(ERRCODE_INVALID_PARAMETER_VALUE),
+ errmsg("invalid value for ternary option \"%s\": %s",
+ option->gen->name, value)));
+ }
+ break;
case RELOPT_TYPE_INT:
{
relopt_int *optint = (relopt_int *) option->gen;
@@ -1789,17 +1881,6 @@ fillRelOptions(void *rdopts, Size basesize,
char *itempos = ((char *) rdopts) + elems[j].offset;
char *string_val;
- /*
- * If isset_offset is provided, store whether the reloption is
- * set there.
- */
- if (elems[j].isset_offset > 0)
- {
- char *setpos = ((char *) rdopts) + elems[j].isset_offset;
-
- *(bool *) setpos = options[i].isset;
- }
-
switch (options[i].gen->type)
{
case RELOPT_TYPE_BOOL:
@@ -1807,6 +1888,11 @@ fillRelOptions(void *rdopts, Size basesize,
options[i].values.bool_val :
((relopt_bool *) options[i].gen)->default_val;
break;
+ case RELOPT_TYPE_TERNARY:
+ *(pg_ternary *) itempos = options[i].isset ?
+ options[i].values.ternary_val :
+ ((relopt_ternary *) options[i].gen)->default_val;
+ break;
case RELOPT_TYPE_INT:
*(int *) itempos = options[i].isset ?
options[i].values.int_val :
@@ -1923,8 +2009,8 @@ default_reloptions(Datum reloptions, bool validate, relopt_kind kind)
offsetof(StdRdOptions, parallel_workers)},
{"vacuum_index_cleanup", RELOPT_TYPE_ENUM,
offsetof(StdRdOptions, vacuum_index_cleanup)},
- {"vacuum_truncate", RELOPT_TYPE_BOOL,
- offsetof(StdRdOptions, vacuum_truncate), offsetof(StdRdOptions, vacuum_truncate_set)},
+ {"vacuum_truncate", RELOPT_TYPE_TERNARY,
+ offsetof(StdRdOptions, vacuum_truncate)},
{"vacuum_max_eager_freeze_failure_rate", RELOPT_TYPE_REAL,
offsetof(StdRdOptions, vacuum_max_eager_freeze_failure_rate)}
};
@@ -2004,7 +2090,6 @@ build_local_reloptions(local_relopts *relopts, Datum options, bool validate)
elems[i].optname = opt->option->name;
elems[i].opttype = opt->option->type;
elems[i].offset = opt->offset;
- elems[i].isset_offset = 0; /* not supported for local relopts yet */
i++;
}
diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c
index aa4fbec143f..696eab9bd97 100644
--- a/src/backend/commands/vacuum.c
+++ b/src/backend/commands/vacuum.c
@@ -2224,9 +2224,9 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams params,
{
StdRdOptions *opts = (StdRdOptions *) rel->rd_options;
- if (opts && opts->vacuum_truncate_set)
+ if (opts && opts->vacuum_truncate != TERNARY_UNSET)
{
- if (opts->vacuum_truncate)
+ if (opts->vacuum_truncate == TERNARY_TRUE)
params.truncate = VACOPTVALUE_ENABLED;
else
params.truncate = VACOPTVALUE_DISABLED;
diff --git a/src/include/access/reloptions.h b/src/include/access/reloptions.h
index 2f08e1b0cf0..dfbef2babf2 100644
--- a/src/include/access/reloptions.h
+++ b/src/include/access/reloptions.h
@@ -29,6 +29,7 @@
typedef enum relopt_type
{
RELOPT_TYPE_BOOL,
+ RELOPT_TYPE_TERNARY, /* on, off, unset */
RELOPT_TYPE_INT,
RELOPT_TYPE_REAL,
RELOPT_TYPE_ENUM,
@@ -80,6 +81,7 @@ typedef struct relopt_value
union
{
bool bool_val;
+ pg_ternary ternary_val;
int int_val;
double real_val;
int enum_val;
@@ -94,6 +96,12 @@ typedef struct relopt_bool
bool default_val;
} relopt_bool;
+typedef struct relopt_ternary
+{
+ relopt_gen gen;
+ int default_val;
+} relopt_ternary;
+
typedef struct relopt_int
{
relopt_gen gen;
@@ -152,19 +160,6 @@ typedef struct
const char *optname; /* option's name */
relopt_type opttype; /* option's datatype */
int offset; /* offset of field in result struct */
-
- /*
- * isset_offset is an optional offset of a field in the result struct that
- * stores whether the option is explicitly set for the relation or if it
- * just picked up the default value. In most cases, this can be
- * accomplished by giving the reloption a special out-of-range default
- * value (e.g., some integer reloptions use -2), but this isn't always
- * possible. For example, a Boolean reloption cannot be given an
- * out-of-range default, so we need another way to discover the source of
- * its value. This offset is only used if given a value greater than
- * zero.
- */
- int isset_offset;
} relopt_parse_elt;
/* Local reloption definition */
@@ -195,6 +190,9 @@ typedef struct local_relopts
extern relopt_kind add_reloption_kind(void);
extern void add_bool_reloption(bits32 kinds, const char *name, const char *desc,
bool default_val, LOCKMODE lockmode);
+extern void add_ternary_reloption(bits32 kinds, const char *name,
+ const char *desc, pg_ternary default_val,
+ LOCKMODE lockmode);
extern void add_int_reloption(bits32 kinds, const char *name, const char *desc,
int default_val, int min_val, int max_val,
LOCKMODE lockmode);
@@ -214,6 +212,9 @@ extern void register_reloptions_validator(local_relopts *relopts,
extern void add_local_bool_reloption(local_relopts *relopts, const char *name,
const char *desc, bool default_val,
int offset);
+extern void add_local_ternary_reloption(local_relopts *relopts,
+ const char *name, const char *desc,
+ pg_ternary default_val, int offset);
extern void add_local_int_reloption(local_relopts *relopts, const char *name,
const char *desc, int default_val,
int min_val, int max_val, int offset);
diff --git a/src/include/postgres.h b/src/include/postgres.h
index 7d93fbce709..47c88780776 100644
--- a/src/include/postgres.h
+++ b/src/include/postgres.h
@@ -543,6 +543,21 @@ Float8GetDatum(float8 X)
* ----------------------------------------------------------------
*/
+/*
+ * pg_ternary
+ * Boolean value with an extra "unset" value
+ *
+ * This enum can be used for values that want to distinguish between true,
+ * false, and unset.
+*/
+
+typedef enum pg_ternary
+{
+ TERNARY_FALSE = 0,
+ TERNARY_TRUE = 1,
+ TERNARY_UNSET = -1
+} pg_ternary;
+
/*
* NON_EXEC_STATIC: It's sometimes useful to define a variable or function
* that is normally static but extern when using EXEC_BACKEND (see
diff --git a/src/include/utils/rel.h b/src/include/utils/rel.h
index d03ab247788..5feb18a5373 100644
--- a/src/include/utils/rel.h
+++ b/src/include/utils/rel.h
@@ -347,8 +347,7 @@ typedef struct StdRdOptions
bool user_catalog_table; /* use as an additional catalog relation */
int parallel_workers; /* max number of parallel workers */
StdRdOptIndexCleanup vacuum_index_cleanup; /* controls index vacuuming */
- bool vacuum_truncate; /* enables vacuum to truncate a relation */
- bool vacuum_truncate_set; /* whether vacuum_truncate is set */
+ pg_ternary vacuum_truncate; /* enables vacuum to truncate a relation */
/*
* Fraction of pages in a relation that vacuum can eagerly scan and fail
diff --git a/src/test/regress/expected/reloptions.out b/src/test/regress/expected/reloptions.out
index 9de19b4e3f1..1c99f79ab01 100644
--- a/src/test/regress/expected/reloptions.out
+++ b/src/test/regress/expected/reloptions.out
@@ -98,6 +98,42 @@ SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass;
{fillfactor=13,autovacuum_enabled=false}
(1 row)
+-- Tests for future (FIXME) ternary options
+-- behave as boolean option: accept unassigned name and truncated value
+DROP TABLE reloptions_test;
+CREATE TABLE reloptions_test(i INT) WITH (vacuum_truncate);
+SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass;
+ reloptions
+------------------------
+ {vacuum_truncate=true}
+(1 row)
+
+DROP TABLE reloptions_test;
+CREATE TABLE reloptions_test(i INT) WITH (vacuum_truncate=FaLS);
+SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass;
+ reloptions
+------------------------
+ {vacuum_truncate=fals}
+(1 row)
+
+-- preferred "true" alias is used when storing
+DROP TABLE reloptions_test;
+CREATE TABLE reloptions_test(i INT) WITH (vacuum_index_cleanup=on);
+SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass;
+ reloptions
+---------------------------
+ {vacuum_index_cleanup=on}
+(1 row)
+
+-- custom "third" value is available
+DROP TABLE reloptions_test;
+CREATE TABLE reloptions_test(i INT) WITH (vacuum_index_cleanup=auto);
+SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass;
+ reloptions
+-----------------------------
+ {vacuum_index_cleanup=auto}
+(1 row)
+
-- Test vacuum_truncate option
DROP TABLE reloptions_test;
CREATE TEMP TABLE reloptions_test(i INT NOT NULL, j text)
diff --git a/src/test/regress/sql/reloptions.sql b/src/test/regress/sql/reloptions.sql
index 24fbe0b478d..f5980dafcbc 100644
--- a/src/test/regress/sql/reloptions.sql
+++ b/src/test/regress/sql/reloptions.sql
@@ -59,6 +59,27 @@ UPDATE pg_class
ALTER TABLE reloptions_test RESET (illegal_option);
SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass;
+-- Tests for future (FIXME) ternary options
+
+-- behave as boolean option: accept unassigned name and truncated value
+DROP TABLE reloptions_test;
+CREATE TABLE reloptions_test(i INT) WITH (vacuum_truncate);
+SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass;
+
+DROP TABLE reloptions_test;
+CREATE TABLE reloptions_test(i INT) WITH (vacuum_truncate=FaLS);
+SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass;
+
+-- preferred "true" alias is used when storing
+DROP TABLE reloptions_test;
+CREATE TABLE reloptions_test(i INT) WITH (vacuum_index_cleanup=on);
+SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass;
+
+-- custom "third" value is available
+DROP TABLE reloptions_test;
+CREATE TABLE reloptions_test(i INT) WITH (vacuum_index_cleanup=auto);
+SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass;
+
-- Test vacuum_truncate option
DROP TABLE reloptions_test;
diff --git a/src/tools/pgindent/typedefs.list b/src/tools/pgindent/typedefs.list
index 3f3a888fd0e..1c8610fd46c 100644
--- a/src/tools/pgindent/typedefs.list
+++ b/src/tools/pgindent/typedefs.list
@@ -3952,6 +3952,7 @@ pg_sha512_ctx
pg_snapshot
pg_special_case
pg_stack_base_t
+pg_ternary
pg_time_t
pg_time_usec_t
pg_tz
@@ -4079,6 +4080,7 @@ relopt_kind
relopt_parse_elt
relopt_real
relopt_string
+relopt_ternary
relopt_type
relopt_value
relopts_validator
--
2.47.3
--c36t3fndxhkzbl5z--
^ permalink raw reply [nested|flat] 868+ messages in thread
* [PATCH v3] Introduce ternary reloptions
@ 2026-01-16 15:04 Álvaro Herrera <[email protected]>
0 siblings, 0 replies; 868+ messages in thread
From: Álvaro Herrera @ 2026-01-16 15:04 UTC (permalink / raw)
Introduce ternary reloption as a replacement for current
`vacuum_truncate` implementation. Remove the `vacuum_truncate_set`
separate flag and use TERNARY_UNSET instead.
This could also be used for other options such as `vacuum_index_cleanup`
and `buffering`, but lets get the scaffolding in first.
Discussion: https://postgr.es/m/3474141.usfYGdeWWP@thinkpad-pgpro
---
src/backend/access/common/reloptions.c | 137 ++++++++++++++++++-----
src/backend/commands/vacuum.c | 4 +-
src/include/access/reloptions.h | 27 ++---
src/include/postgres.h | 15 +++
src/include/utils/rel.h | 3 +-
src/test/regress/expected/reloptions.out | 36 ++++++
src/test/regress/sql/reloptions.sql | 21 ++++
src/tools/pgindent/typedefs.list | 2 +
8 files changed, 202 insertions(+), 43 deletions(-)
diff --git a/src/backend/access/common/reloptions.c b/src/backend/access/common/reloptions.c
index 0b83f98ed5f..6f1f577581a 100644
--- a/src/backend/access/common/reloptions.c
+++ b/src/backend/access/common/reloptions.c
@@ -40,9 +40,9 @@
*
* To add an option:
*
- * (i) decide on a type (bool, integer, real, enum, string), name, default
- * value, upper and lower bounds (if applicable); for strings, consider a
- * validation routine.
+ * (i) decide on a type (bool, ternary, integer, real, enum, string), name,
+ * default value, upper and lower bounds (if applicable); for strings,
+ * consider a validation routine.
* (ii) add a record below (or use add_<type>_reloption).
* (iii) add it to the appropriate options struct (perhaps StdRdOptions)
* (iv) add it to the appropriate handling routine (perhaps
@@ -147,15 +147,6 @@ static relopt_bool boolRelOpts[] =
},
false
},
- {
- {
- "vacuum_truncate",
- "Enables vacuum to truncate empty pages at the end of this table",
- RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
- ShareUpdateExclusiveLock
- },
- true
- },
{
{
"deduplicate_items",
@@ -170,6 +161,25 @@ static relopt_bool boolRelOpts[] =
{{NULL}}
};
+static relopt_ternary ternaryRelOpts[] =
+{
+ {
+ {
+ "vacuum_truncate",
+ "Enables vacuum to truncate empty pages at the end of this table",
+ RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
+ ShareUpdateExclusiveLock
+ },
+ TERNARY_UNSET
+ },
+ /* list terminator */
+ {
+ {
+ NULL
+ }
+ }
+};
+
static relopt_int intRelOpts[] =
{
{
@@ -609,6 +619,13 @@ initialize_reloptions(void)
boolRelOpts[i].gen.lockmode));
j++;
}
+ for (i = 0; ternaryRelOpts[i].gen.name; i++)
+ {
+ Assert(DoLockModesConflict(ternaryRelOpts[i].gen.lockmode,
+ ternaryRelOpts[i].gen.lockmode));
+ j++;
+ }
+
for (i = 0; intRelOpts[i].gen.name; i++)
{
Assert(DoLockModesConflict(intRelOpts[i].gen.lockmode,
@@ -649,6 +666,14 @@ initialize_reloptions(void)
j++;
}
+ for (i = 0; ternaryRelOpts[i].gen.name; i++)
+ {
+ relOpts[j] = &ternaryRelOpts[i].gen;
+ relOpts[j]->type = RELOPT_TYPE_TERNARY;
+ relOpts[j]->namelen = strlen(relOpts[j]->name);
+ j++;
+ }
+
for (i = 0; intRelOpts[i].gen.name; i++)
{
relOpts[j] = &intRelOpts[i].gen;
@@ -809,6 +834,9 @@ allocate_reloption(bits32 kinds, int type, const char *name, const char *desc,
case RELOPT_TYPE_BOOL:
size = sizeof(relopt_bool);
break;
+ case RELOPT_TYPE_TERNARY:
+ size = sizeof(relopt_ternary);
+ break;
case RELOPT_TYPE_INT:
size = sizeof(relopt_int);
break;
@@ -892,6 +920,57 @@ add_local_bool_reloption(local_relopts *relopts, const char *name,
add_local_reloption(relopts, (relopt_gen *) newoption, offset);
}
+/*
+ * init_ternary_reloption
+ * Allocate and initialize a new ternary reloption
+ */
+static relopt_ternary *
+init_ternary_reloption(bits32 kinds, const char *name, const char *desc,
+ pg_ternary default_val, LOCKMODE lockmode)
+{
+ relopt_ternary *newoption;
+
+ newoption = (relopt_ternary *)
+ allocate_reloption(kinds, RELOPT_TYPE_TERNARY, name, desc, lockmode);
+ newoption->default_val = default_val;
+
+ return newoption;
+}
+
+/*
+ * add_ternary_reloption
+ * Add a new ternary reloption
+ */
+void
+add_ternary_reloption(bits32 kinds, const char *name, const char *desc,
+ pg_ternary default_val, LOCKMODE lockmode)
+{
+ relopt_ternary *newoption;
+
+ newoption =
+ init_ternary_reloption(kinds, name, desc, default_val, lockmode);
+
+ add_reloption((relopt_gen *) newoption);
+}
+
+/*
+ * add_local_ternary_reloption
+ * Add a new ternary local reloption
+ *
+ * 'offset' is offset of ternary-typed field.
+ */
+void
+add_local_ternary_reloption(local_relopts *relopts, const char *name,
+ const char *desc, pg_ternary default_val,
+ int offset)
+{
+ relopt_ternary *newoption;
+
+ newoption =
+ init_ternary_reloption(RELOPT_KIND_LOCAL, name, desc, default_val, 0);
+
+ add_local_reloption(relopts, (relopt_gen *) newoption, offset);
+}
/*
* init_real_reloption
@@ -1626,6 +1705,19 @@ parse_one_reloption(relopt_value *option, char *text_str, int text_len,
option->gen->name, value)));
}
break;
+ case RELOPT_TYPE_TERNARY:
+ {
+ bool b;
+
+ parsed = parse_bool(value, &b);
+ option->values.ternary_val = b ? TERNARY_TRUE : TERNARY_FALSE;
+ if (validate && !parsed)
+ ereport(ERROR,
+ (errcode(ERRCODE_INVALID_PARAMETER_VALUE),
+ errmsg("invalid value for ternary option \"%s\": %s",
+ option->gen->name, value)));
+ }
+ break;
case RELOPT_TYPE_INT:
{
relopt_int *optint = (relopt_int *) option->gen;
@@ -1789,17 +1881,6 @@ fillRelOptions(void *rdopts, Size basesize,
char *itempos = ((char *) rdopts) + elems[j].offset;
char *string_val;
- /*
- * If isset_offset is provided, store whether the reloption is
- * set there.
- */
- if (elems[j].isset_offset > 0)
- {
- char *setpos = ((char *) rdopts) + elems[j].isset_offset;
-
- *(bool *) setpos = options[i].isset;
- }
-
switch (options[i].gen->type)
{
case RELOPT_TYPE_BOOL:
@@ -1807,6 +1888,11 @@ fillRelOptions(void *rdopts, Size basesize,
options[i].values.bool_val :
((relopt_bool *) options[i].gen)->default_val;
break;
+ case RELOPT_TYPE_TERNARY:
+ *(pg_ternary *) itempos = options[i].isset ?
+ options[i].values.ternary_val :
+ ((relopt_ternary *) options[i].gen)->default_val;
+ break;
case RELOPT_TYPE_INT:
*(int *) itempos = options[i].isset ?
options[i].values.int_val :
@@ -1923,8 +2009,8 @@ default_reloptions(Datum reloptions, bool validate, relopt_kind kind)
offsetof(StdRdOptions, parallel_workers)},
{"vacuum_index_cleanup", RELOPT_TYPE_ENUM,
offsetof(StdRdOptions, vacuum_index_cleanup)},
- {"vacuum_truncate", RELOPT_TYPE_BOOL,
- offsetof(StdRdOptions, vacuum_truncate), offsetof(StdRdOptions, vacuum_truncate_set)},
+ {"vacuum_truncate", RELOPT_TYPE_TERNARY,
+ offsetof(StdRdOptions, vacuum_truncate)},
{"vacuum_max_eager_freeze_failure_rate", RELOPT_TYPE_REAL,
offsetof(StdRdOptions, vacuum_max_eager_freeze_failure_rate)}
};
@@ -2004,7 +2090,6 @@ build_local_reloptions(local_relopts *relopts, Datum options, bool validate)
elems[i].optname = opt->option->name;
elems[i].opttype = opt->option->type;
elems[i].offset = opt->offset;
- elems[i].isset_offset = 0; /* not supported for local relopts yet */
i++;
}
diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c
index aa4fbec143f..696eab9bd97 100644
--- a/src/backend/commands/vacuum.c
+++ b/src/backend/commands/vacuum.c
@@ -2224,9 +2224,9 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams params,
{
StdRdOptions *opts = (StdRdOptions *) rel->rd_options;
- if (opts && opts->vacuum_truncate_set)
+ if (opts && opts->vacuum_truncate != TERNARY_UNSET)
{
- if (opts->vacuum_truncate)
+ if (opts->vacuum_truncate == TERNARY_TRUE)
params.truncate = VACOPTVALUE_ENABLED;
else
params.truncate = VACOPTVALUE_DISABLED;
diff --git a/src/include/access/reloptions.h b/src/include/access/reloptions.h
index 2f08e1b0cf0..dfbef2babf2 100644
--- a/src/include/access/reloptions.h
+++ b/src/include/access/reloptions.h
@@ -29,6 +29,7 @@
typedef enum relopt_type
{
RELOPT_TYPE_BOOL,
+ RELOPT_TYPE_TERNARY, /* on, off, unset */
RELOPT_TYPE_INT,
RELOPT_TYPE_REAL,
RELOPT_TYPE_ENUM,
@@ -80,6 +81,7 @@ typedef struct relopt_value
union
{
bool bool_val;
+ pg_ternary ternary_val;
int int_val;
double real_val;
int enum_val;
@@ -94,6 +96,12 @@ typedef struct relopt_bool
bool default_val;
} relopt_bool;
+typedef struct relopt_ternary
+{
+ relopt_gen gen;
+ int default_val;
+} relopt_ternary;
+
typedef struct relopt_int
{
relopt_gen gen;
@@ -152,19 +160,6 @@ typedef struct
const char *optname; /* option's name */
relopt_type opttype; /* option's datatype */
int offset; /* offset of field in result struct */
-
- /*
- * isset_offset is an optional offset of a field in the result struct that
- * stores whether the option is explicitly set for the relation or if it
- * just picked up the default value. In most cases, this can be
- * accomplished by giving the reloption a special out-of-range default
- * value (e.g., some integer reloptions use -2), but this isn't always
- * possible. For example, a Boolean reloption cannot be given an
- * out-of-range default, so we need another way to discover the source of
- * its value. This offset is only used if given a value greater than
- * zero.
- */
- int isset_offset;
} relopt_parse_elt;
/* Local reloption definition */
@@ -195,6 +190,9 @@ typedef struct local_relopts
extern relopt_kind add_reloption_kind(void);
extern void add_bool_reloption(bits32 kinds, const char *name, const char *desc,
bool default_val, LOCKMODE lockmode);
+extern void add_ternary_reloption(bits32 kinds, const char *name,
+ const char *desc, pg_ternary default_val,
+ LOCKMODE lockmode);
extern void add_int_reloption(bits32 kinds, const char *name, const char *desc,
int default_val, int min_val, int max_val,
LOCKMODE lockmode);
@@ -214,6 +212,9 @@ extern void register_reloptions_validator(local_relopts *relopts,
extern void add_local_bool_reloption(local_relopts *relopts, const char *name,
const char *desc, bool default_val,
int offset);
+extern void add_local_ternary_reloption(local_relopts *relopts,
+ const char *name, const char *desc,
+ pg_ternary default_val, int offset);
extern void add_local_int_reloption(local_relopts *relopts, const char *name,
const char *desc, int default_val,
int min_val, int max_val, int offset);
diff --git a/src/include/postgres.h b/src/include/postgres.h
index 7d93fbce709..47c88780776 100644
--- a/src/include/postgres.h
+++ b/src/include/postgres.h
@@ -543,6 +543,21 @@ Float8GetDatum(float8 X)
* ----------------------------------------------------------------
*/
+/*
+ * pg_ternary
+ * Boolean value with an extra "unset" value
+ *
+ * This enum can be used for values that want to distinguish between true,
+ * false, and unset.
+*/
+
+typedef enum pg_ternary
+{
+ TERNARY_FALSE = 0,
+ TERNARY_TRUE = 1,
+ TERNARY_UNSET = -1
+} pg_ternary;
+
/*
* NON_EXEC_STATIC: It's sometimes useful to define a variable or function
* that is normally static but extern when using EXEC_BACKEND (see
diff --git a/src/include/utils/rel.h b/src/include/utils/rel.h
index d03ab247788..5feb18a5373 100644
--- a/src/include/utils/rel.h
+++ b/src/include/utils/rel.h
@@ -347,8 +347,7 @@ typedef struct StdRdOptions
bool user_catalog_table; /* use as an additional catalog relation */
int parallel_workers; /* max number of parallel workers */
StdRdOptIndexCleanup vacuum_index_cleanup; /* controls index vacuuming */
- bool vacuum_truncate; /* enables vacuum to truncate a relation */
- bool vacuum_truncate_set; /* whether vacuum_truncate is set */
+ pg_ternary vacuum_truncate; /* enables vacuum to truncate a relation */
/*
* Fraction of pages in a relation that vacuum can eagerly scan and fail
diff --git a/src/test/regress/expected/reloptions.out b/src/test/regress/expected/reloptions.out
index 9de19b4e3f1..1c99f79ab01 100644
--- a/src/test/regress/expected/reloptions.out
+++ b/src/test/regress/expected/reloptions.out
@@ -98,6 +98,42 @@ SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass;
{fillfactor=13,autovacuum_enabled=false}
(1 row)
+-- Tests for future (FIXME) ternary options
+-- behave as boolean option: accept unassigned name and truncated value
+DROP TABLE reloptions_test;
+CREATE TABLE reloptions_test(i INT) WITH (vacuum_truncate);
+SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass;
+ reloptions
+------------------------
+ {vacuum_truncate=true}
+(1 row)
+
+DROP TABLE reloptions_test;
+CREATE TABLE reloptions_test(i INT) WITH (vacuum_truncate=FaLS);
+SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass;
+ reloptions
+------------------------
+ {vacuum_truncate=fals}
+(1 row)
+
+-- preferred "true" alias is used when storing
+DROP TABLE reloptions_test;
+CREATE TABLE reloptions_test(i INT) WITH (vacuum_index_cleanup=on);
+SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass;
+ reloptions
+---------------------------
+ {vacuum_index_cleanup=on}
+(1 row)
+
+-- custom "third" value is available
+DROP TABLE reloptions_test;
+CREATE TABLE reloptions_test(i INT) WITH (vacuum_index_cleanup=auto);
+SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass;
+ reloptions
+-----------------------------
+ {vacuum_index_cleanup=auto}
+(1 row)
+
-- Test vacuum_truncate option
DROP TABLE reloptions_test;
CREATE TEMP TABLE reloptions_test(i INT NOT NULL, j text)
diff --git a/src/test/regress/sql/reloptions.sql b/src/test/regress/sql/reloptions.sql
index 24fbe0b478d..f5980dafcbc 100644
--- a/src/test/regress/sql/reloptions.sql
+++ b/src/test/regress/sql/reloptions.sql
@@ -59,6 +59,27 @@ UPDATE pg_class
ALTER TABLE reloptions_test RESET (illegal_option);
SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass;
+-- Tests for future (FIXME) ternary options
+
+-- behave as boolean option: accept unassigned name and truncated value
+DROP TABLE reloptions_test;
+CREATE TABLE reloptions_test(i INT) WITH (vacuum_truncate);
+SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass;
+
+DROP TABLE reloptions_test;
+CREATE TABLE reloptions_test(i INT) WITH (vacuum_truncate=FaLS);
+SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass;
+
+-- preferred "true" alias is used when storing
+DROP TABLE reloptions_test;
+CREATE TABLE reloptions_test(i INT) WITH (vacuum_index_cleanup=on);
+SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass;
+
+-- custom "third" value is available
+DROP TABLE reloptions_test;
+CREATE TABLE reloptions_test(i INT) WITH (vacuum_index_cleanup=auto);
+SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass;
+
-- Test vacuum_truncate option
DROP TABLE reloptions_test;
diff --git a/src/tools/pgindent/typedefs.list b/src/tools/pgindent/typedefs.list
index 3f3a888fd0e..1c8610fd46c 100644
--- a/src/tools/pgindent/typedefs.list
+++ b/src/tools/pgindent/typedefs.list
@@ -3952,6 +3952,7 @@ pg_sha512_ctx
pg_snapshot
pg_special_case
pg_stack_base_t
+pg_ternary
pg_time_t
pg_time_usec_t
pg_tz
@@ -4079,6 +4080,7 @@ relopt_kind
relopt_parse_elt
relopt_real
relopt_string
+relopt_ternary
relopt_type
relopt_value
relopts_validator
--
2.47.3
--c36t3fndxhkzbl5z--
^ permalink raw reply [nested|flat] 868+ messages in thread
* [PATCH v3] Introduce ternary reloptions
@ 2026-01-16 15:04 Álvaro Herrera <[email protected]>
0 siblings, 0 replies; 868+ messages in thread
From: Álvaro Herrera @ 2026-01-16 15:04 UTC (permalink / raw)
Introduce ternary reloption as a replacement for current
`vacuum_truncate` implementation. Remove the `vacuum_truncate_set`
separate flag and use TERNARY_UNSET instead.
This could also be used for other options such as `vacuum_index_cleanup`
and `buffering`, but lets get the scaffolding in first.
Discussion: https://postgr.es/m/3474141.usfYGdeWWP@thinkpad-pgpro
---
src/backend/access/common/reloptions.c | 137 ++++++++++++++++++-----
src/backend/commands/vacuum.c | 4 +-
src/include/access/reloptions.h | 27 ++---
src/include/postgres.h | 15 +++
src/include/utils/rel.h | 3 +-
src/test/regress/expected/reloptions.out | 36 ++++++
src/test/regress/sql/reloptions.sql | 21 ++++
src/tools/pgindent/typedefs.list | 2 +
8 files changed, 202 insertions(+), 43 deletions(-)
diff --git a/src/backend/access/common/reloptions.c b/src/backend/access/common/reloptions.c
index 0b83f98ed5f..6f1f577581a 100644
--- a/src/backend/access/common/reloptions.c
+++ b/src/backend/access/common/reloptions.c
@@ -40,9 +40,9 @@
*
* To add an option:
*
- * (i) decide on a type (bool, integer, real, enum, string), name, default
- * value, upper and lower bounds (if applicable); for strings, consider a
- * validation routine.
+ * (i) decide on a type (bool, ternary, integer, real, enum, string), name,
+ * default value, upper and lower bounds (if applicable); for strings,
+ * consider a validation routine.
* (ii) add a record below (or use add_<type>_reloption).
* (iii) add it to the appropriate options struct (perhaps StdRdOptions)
* (iv) add it to the appropriate handling routine (perhaps
@@ -147,15 +147,6 @@ static relopt_bool boolRelOpts[] =
},
false
},
- {
- {
- "vacuum_truncate",
- "Enables vacuum to truncate empty pages at the end of this table",
- RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
- ShareUpdateExclusiveLock
- },
- true
- },
{
{
"deduplicate_items",
@@ -170,6 +161,25 @@ static relopt_bool boolRelOpts[] =
{{NULL}}
};
+static relopt_ternary ternaryRelOpts[] =
+{
+ {
+ {
+ "vacuum_truncate",
+ "Enables vacuum to truncate empty pages at the end of this table",
+ RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
+ ShareUpdateExclusiveLock
+ },
+ TERNARY_UNSET
+ },
+ /* list terminator */
+ {
+ {
+ NULL
+ }
+ }
+};
+
static relopt_int intRelOpts[] =
{
{
@@ -609,6 +619,13 @@ initialize_reloptions(void)
boolRelOpts[i].gen.lockmode));
j++;
}
+ for (i = 0; ternaryRelOpts[i].gen.name; i++)
+ {
+ Assert(DoLockModesConflict(ternaryRelOpts[i].gen.lockmode,
+ ternaryRelOpts[i].gen.lockmode));
+ j++;
+ }
+
for (i = 0; intRelOpts[i].gen.name; i++)
{
Assert(DoLockModesConflict(intRelOpts[i].gen.lockmode,
@@ -649,6 +666,14 @@ initialize_reloptions(void)
j++;
}
+ for (i = 0; ternaryRelOpts[i].gen.name; i++)
+ {
+ relOpts[j] = &ternaryRelOpts[i].gen;
+ relOpts[j]->type = RELOPT_TYPE_TERNARY;
+ relOpts[j]->namelen = strlen(relOpts[j]->name);
+ j++;
+ }
+
for (i = 0; intRelOpts[i].gen.name; i++)
{
relOpts[j] = &intRelOpts[i].gen;
@@ -809,6 +834,9 @@ allocate_reloption(bits32 kinds, int type, const char *name, const char *desc,
case RELOPT_TYPE_BOOL:
size = sizeof(relopt_bool);
break;
+ case RELOPT_TYPE_TERNARY:
+ size = sizeof(relopt_ternary);
+ break;
case RELOPT_TYPE_INT:
size = sizeof(relopt_int);
break;
@@ -892,6 +920,57 @@ add_local_bool_reloption(local_relopts *relopts, const char *name,
add_local_reloption(relopts, (relopt_gen *) newoption, offset);
}
+/*
+ * init_ternary_reloption
+ * Allocate and initialize a new ternary reloption
+ */
+static relopt_ternary *
+init_ternary_reloption(bits32 kinds, const char *name, const char *desc,
+ pg_ternary default_val, LOCKMODE lockmode)
+{
+ relopt_ternary *newoption;
+
+ newoption = (relopt_ternary *)
+ allocate_reloption(kinds, RELOPT_TYPE_TERNARY, name, desc, lockmode);
+ newoption->default_val = default_val;
+
+ return newoption;
+}
+
+/*
+ * add_ternary_reloption
+ * Add a new ternary reloption
+ */
+void
+add_ternary_reloption(bits32 kinds, const char *name, const char *desc,
+ pg_ternary default_val, LOCKMODE lockmode)
+{
+ relopt_ternary *newoption;
+
+ newoption =
+ init_ternary_reloption(kinds, name, desc, default_val, lockmode);
+
+ add_reloption((relopt_gen *) newoption);
+}
+
+/*
+ * add_local_ternary_reloption
+ * Add a new ternary local reloption
+ *
+ * 'offset' is offset of ternary-typed field.
+ */
+void
+add_local_ternary_reloption(local_relopts *relopts, const char *name,
+ const char *desc, pg_ternary default_val,
+ int offset)
+{
+ relopt_ternary *newoption;
+
+ newoption =
+ init_ternary_reloption(RELOPT_KIND_LOCAL, name, desc, default_val, 0);
+
+ add_local_reloption(relopts, (relopt_gen *) newoption, offset);
+}
/*
* init_real_reloption
@@ -1626,6 +1705,19 @@ parse_one_reloption(relopt_value *option, char *text_str, int text_len,
option->gen->name, value)));
}
break;
+ case RELOPT_TYPE_TERNARY:
+ {
+ bool b;
+
+ parsed = parse_bool(value, &b);
+ option->values.ternary_val = b ? TERNARY_TRUE : TERNARY_FALSE;
+ if (validate && !parsed)
+ ereport(ERROR,
+ (errcode(ERRCODE_INVALID_PARAMETER_VALUE),
+ errmsg("invalid value for ternary option \"%s\": %s",
+ option->gen->name, value)));
+ }
+ break;
case RELOPT_TYPE_INT:
{
relopt_int *optint = (relopt_int *) option->gen;
@@ -1789,17 +1881,6 @@ fillRelOptions(void *rdopts, Size basesize,
char *itempos = ((char *) rdopts) + elems[j].offset;
char *string_val;
- /*
- * If isset_offset is provided, store whether the reloption is
- * set there.
- */
- if (elems[j].isset_offset > 0)
- {
- char *setpos = ((char *) rdopts) + elems[j].isset_offset;
-
- *(bool *) setpos = options[i].isset;
- }
-
switch (options[i].gen->type)
{
case RELOPT_TYPE_BOOL:
@@ -1807,6 +1888,11 @@ fillRelOptions(void *rdopts, Size basesize,
options[i].values.bool_val :
((relopt_bool *) options[i].gen)->default_val;
break;
+ case RELOPT_TYPE_TERNARY:
+ *(pg_ternary *) itempos = options[i].isset ?
+ options[i].values.ternary_val :
+ ((relopt_ternary *) options[i].gen)->default_val;
+ break;
case RELOPT_TYPE_INT:
*(int *) itempos = options[i].isset ?
options[i].values.int_val :
@@ -1923,8 +2009,8 @@ default_reloptions(Datum reloptions, bool validate, relopt_kind kind)
offsetof(StdRdOptions, parallel_workers)},
{"vacuum_index_cleanup", RELOPT_TYPE_ENUM,
offsetof(StdRdOptions, vacuum_index_cleanup)},
- {"vacuum_truncate", RELOPT_TYPE_BOOL,
- offsetof(StdRdOptions, vacuum_truncate), offsetof(StdRdOptions, vacuum_truncate_set)},
+ {"vacuum_truncate", RELOPT_TYPE_TERNARY,
+ offsetof(StdRdOptions, vacuum_truncate)},
{"vacuum_max_eager_freeze_failure_rate", RELOPT_TYPE_REAL,
offsetof(StdRdOptions, vacuum_max_eager_freeze_failure_rate)}
};
@@ -2004,7 +2090,6 @@ build_local_reloptions(local_relopts *relopts, Datum options, bool validate)
elems[i].optname = opt->option->name;
elems[i].opttype = opt->option->type;
elems[i].offset = opt->offset;
- elems[i].isset_offset = 0; /* not supported for local relopts yet */
i++;
}
diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c
index aa4fbec143f..696eab9bd97 100644
--- a/src/backend/commands/vacuum.c
+++ b/src/backend/commands/vacuum.c
@@ -2224,9 +2224,9 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams params,
{
StdRdOptions *opts = (StdRdOptions *) rel->rd_options;
- if (opts && opts->vacuum_truncate_set)
+ if (opts && opts->vacuum_truncate != TERNARY_UNSET)
{
- if (opts->vacuum_truncate)
+ if (opts->vacuum_truncate == TERNARY_TRUE)
params.truncate = VACOPTVALUE_ENABLED;
else
params.truncate = VACOPTVALUE_DISABLED;
diff --git a/src/include/access/reloptions.h b/src/include/access/reloptions.h
index 2f08e1b0cf0..dfbef2babf2 100644
--- a/src/include/access/reloptions.h
+++ b/src/include/access/reloptions.h
@@ -29,6 +29,7 @@
typedef enum relopt_type
{
RELOPT_TYPE_BOOL,
+ RELOPT_TYPE_TERNARY, /* on, off, unset */
RELOPT_TYPE_INT,
RELOPT_TYPE_REAL,
RELOPT_TYPE_ENUM,
@@ -80,6 +81,7 @@ typedef struct relopt_value
union
{
bool bool_val;
+ pg_ternary ternary_val;
int int_val;
double real_val;
int enum_val;
@@ -94,6 +96,12 @@ typedef struct relopt_bool
bool default_val;
} relopt_bool;
+typedef struct relopt_ternary
+{
+ relopt_gen gen;
+ int default_val;
+} relopt_ternary;
+
typedef struct relopt_int
{
relopt_gen gen;
@@ -152,19 +160,6 @@ typedef struct
const char *optname; /* option's name */
relopt_type opttype; /* option's datatype */
int offset; /* offset of field in result struct */
-
- /*
- * isset_offset is an optional offset of a field in the result struct that
- * stores whether the option is explicitly set for the relation or if it
- * just picked up the default value. In most cases, this can be
- * accomplished by giving the reloption a special out-of-range default
- * value (e.g., some integer reloptions use -2), but this isn't always
- * possible. For example, a Boolean reloption cannot be given an
- * out-of-range default, so we need another way to discover the source of
- * its value. This offset is only used if given a value greater than
- * zero.
- */
- int isset_offset;
} relopt_parse_elt;
/* Local reloption definition */
@@ -195,6 +190,9 @@ typedef struct local_relopts
extern relopt_kind add_reloption_kind(void);
extern void add_bool_reloption(bits32 kinds, const char *name, const char *desc,
bool default_val, LOCKMODE lockmode);
+extern void add_ternary_reloption(bits32 kinds, const char *name,
+ const char *desc, pg_ternary default_val,
+ LOCKMODE lockmode);
extern void add_int_reloption(bits32 kinds, const char *name, const char *desc,
int default_val, int min_val, int max_val,
LOCKMODE lockmode);
@@ -214,6 +212,9 @@ extern void register_reloptions_validator(local_relopts *relopts,
extern void add_local_bool_reloption(local_relopts *relopts, const char *name,
const char *desc, bool default_val,
int offset);
+extern void add_local_ternary_reloption(local_relopts *relopts,
+ const char *name, const char *desc,
+ pg_ternary default_val, int offset);
extern void add_local_int_reloption(local_relopts *relopts, const char *name,
const char *desc, int default_val,
int min_val, int max_val, int offset);
diff --git a/src/include/postgres.h b/src/include/postgres.h
index 7d93fbce709..47c88780776 100644
--- a/src/include/postgres.h
+++ b/src/include/postgres.h
@@ -543,6 +543,21 @@ Float8GetDatum(float8 X)
* ----------------------------------------------------------------
*/
+/*
+ * pg_ternary
+ * Boolean value with an extra "unset" value
+ *
+ * This enum can be used for values that want to distinguish between true,
+ * false, and unset.
+*/
+
+typedef enum pg_ternary
+{
+ TERNARY_FALSE = 0,
+ TERNARY_TRUE = 1,
+ TERNARY_UNSET = -1
+} pg_ternary;
+
/*
* NON_EXEC_STATIC: It's sometimes useful to define a variable or function
* that is normally static but extern when using EXEC_BACKEND (see
diff --git a/src/include/utils/rel.h b/src/include/utils/rel.h
index d03ab247788..5feb18a5373 100644
--- a/src/include/utils/rel.h
+++ b/src/include/utils/rel.h
@@ -347,8 +347,7 @@ typedef struct StdRdOptions
bool user_catalog_table; /* use as an additional catalog relation */
int parallel_workers; /* max number of parallel workers */
StdRdOptIndexCleanup vacuum_index_cleanup; /* controls index vacuuming */
- bool vacuum_truncate; /* enables vacuum to truncate a relation */
- bool vacuum_truncate_set; /* whether vacuum_truncate is set */
+ pg_ternary vacuum_truncate; /* enables vacuum to truncate a relation */
/*
* Fraction of pages in a relation that vacuum can eagerly scan and fail
diff --git a/src/test/regress/expected/reloptions.out b/src/test/regress/expected/reloptions.out
index 9de19b4e3f1..1c99f79ab01 100644
--- a/src/test/regress/expected/reloptions.out
+++ b/src/test/regress/expected/reloptions.out
@@ -98,6 +98,42 @@ SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass;
{fillfactor=13,autovacuum_enabled=false}
(1 row)
+-- Tests for future (FIXME) ternary options
+-- behave as boolean option: accept unassigned name and truncated value
+DROP TABLE reloptions_test;
+CREATE TABLE reloptions_test(i INT) WITH (vacuum_truncate);
+SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass;
+ reloptions
+------------------------
+ {vacuum_truncate=true}
+(1 row)
+
+DROP TABLE reloptions_test;
+CREATE TABLE reloptions_test(i INT) WITH (vacuum_truncate=FaLS);
+SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass;
+ reloptions
+------------------------
+ {vacuum_truncate=fals}
+(1 row)
+
+-- preferred "true" alias is used when storing
+DROP TABLE reloptions_test;
+CREATE TABLE reloptions_test(i INT) WITH (vacuum_index_cleanup=on);
+SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass;
+ reloptions
+---------------------------
+ {vacuum_index_cleanup=on}
+(1 row)
+
+-- custom "third" value is available
+DROP TABLE reloptions_test;
+CREATE TABLE reloptions_test(i INT) WITH (vacuum_index_cleanup=auto);
+SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass;
+ reloptions
+-----------------------------
+ {vacuum_index_cleanup=auto}
+(1 row)
+
-- Test vacuum_truncate option
DROP TABLE reloptions_test;
CREATE TEMP TABLE reloptions_test(i INT NOT NULL, j text)
diff --git a/src/test/regress/sql/reloptions.sql b/src/test/regress/sql/reloptions.sql
index 24fbe0b478d..f5980dafcbc 100644
--- a/src/test/regress/sql/reloptions.sql
+++ b/src/test/regress/sql/reloptions.sql
@@ -59,6 +59,27 @@ UPDATE pg_class
ALTER TABLE reloptions_test RESET (illegal_option);
SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass;
+-- Tests for future (FIXME) ternary options
+
+-- behave as boolean option: accept unassigned name and truncated value
+DROP TABLE reloptions_test;
+CREATE TABLE reloptions_test(i INT) WITH (vacuum_truncate);
+SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass;
+
+DROP TABLE reloptions_test;
+CREATE TABLE reloptions_test(i INT) WITH (vacuum_truncate=FaLS);
+SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass;
+
+-- preferred "true" alias is used when storing
+DROP TABLE reloptions_test;
+CREATE TABLE reloptions_test(i INT) WITH (vacuum_index_cleanup=on);
+SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass;
+
+-- custom "third" value is available
+DROP TABLE reloptions_test;
+CREATE TABLE reloptions_test(i INT) WITH (vacuum_index_cleanup=auto);
+SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass;
+
-- Test vacuum_truncate option
DROP TABLE reloptions_test;
diff --git a/src/tools/pgindent/typedefs.list b/src/tools/pgindent/typedefs.list
index 3f3a888fd0e..1c8610fd46c 100644
--- a/src/tools/pgindent/typedefs.list
+++ b/src/tools/pgindent/typedefs.list
@@ -3952,6 +3952,7 @@ pg_sha512_ctx
pg_snapshot
pg_special_case
pg_stack_base_t
+pg_ternary
pg_time_t
pg_time_usec_t
pg_tz
@@ -4079,6 +4080,7 @@ relopt_kind
relopt_parse_elt
relopt_real
relopt_string
+relopt_ternary
relopt_type
relopt_value
relopts_validator
--
2.47.3
--c36t3fndxhkzbl5z--
^ permalink raw reply [nested|flat] 868+ messages in thread
* [PATCH v3] Introduce ternary reloptions
@ 2026-01-16 15:04 Álvaro Herrera <[email protected]>
0 siblings, 0 replies; 868+ messages in thread
From: Álvaro Herrera @ 2026-01-16 15:04 UTC (permalink / raw)
Introduce ternary reloption as a replacement for current
`vacuum_truncate` implementation. Remove the `vacuum_truncate_set`
separate flag and use TERNARY_UNSET instead.
This could also be used for other options such as `vacuum_index_cleanup`
and `buffering`, but lets get the scaffolding in first.
Discussion: https://postgr.es/m/3474141.usfYGdeWWP@thinkpad-pgpro
---
src/backend/access/common/reloptions.c | 137 ++++++++++++++++++-----
src/backend/commands/vacuum.c | 4 +-
src/include/access/reloptions.h | 27 ++---
src/include/postgres.h | 15 +++
src/include/utils/rel.h | 3 +-
src/test/regress/expected/reloptions.out | 36 ++++++
src/test/regress/sql/reloptions.sql | 21 ++++
src/tools/pgindent/typedefs.list | 2 +
8 files changed, 202 insertions(+), 43 deletions(-)
diff --git a/src/backend/access/common/reloptions.c b/src/backend/access/common/reloptions.c
index 0b83f98ed5f..6f1f577581a 100644
--- a/src/backend/access/common/reloptions.c
+++ b/src/backend/access/common/reloptions.c
@@ -40,9 +40,9 @@
*
* To add an option:
*
- * (i) decide on a type (bool, integer, real, enum, string), name, default
- * value, upper and lower bounds (if applicable); for strings, consider a
- * validation routine.
+ * (i) decide on a type (bool, ternary, integer, real, enum, string), name,
+ * default value, upper and lower bounds (if applicable); for strings,
+ * consider a validation routine.
* (ii) add a record below (or use add_<type>_reloption).
* (iii) add it to the appropriate options struct (perhaps StdRdOptions)
* (iv) add it to the appropriate handling routine (perhaps
@@ -147,15 +147,6 @@ static relopt_bool boolRelOpts[] =
},
false
},
- {
- {
- "vacuum_truncate",
- "Enables vacuum to truncate empty pages at the end of this table",
- RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
- ShareUpdateExclusiveLock
- },
- true
- },
{
{
"deduplicate_items",
@@ -170,6 +161,25 @@ static relopt_bool boolRelOpts[] =
{{NULL}}
};
+static relopt_ternary ternaryRelOpts[] =
+{
+ {
+ {
+ "vacuum_truncate",
+ "Enables vacuum to truncate empty pages at the end of this table",
+ RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
+ ShareUpdateExclusiveLock
+ },
+ TERNARY_UNSET
+ },
+ /* list terminator */
+ {
+ {
+ NULL
+ }
+ }
+};
+
static relopt_int intRelOpts[] =
{
{
@@ -609,6 +619,13 @@ initialize_reloptions(void)
boolRelOpts[i].gen.lockmode));
j++;
}
+ for (i = 0; ternaryRelOpts[i].gen.name; i++)
+ {
+ Assert(DoLockModesConflict(ternaryRelOpts[i].gen.lockmode,
+ ternaryRelOpts[i].gen.lockmode));
+ j++;
+ }
+
for (i = 0; intRelOpts[i].gen.name; i++)
{
Assert(DoLockModesConflict(intRelOpts[i].gen.lockmode,
@@ -649,6 +666,14 @@ initialize_reloptions(void)
j++;
}
+ for (i = 0; ternaryRelOpts[i].gen.name; i++)
+ {
+ relOpts[j] = &ternaryRelOpts[i].gen;
+ relOpts[j]->type = RELOPT_TYPE_TERNARY;
+ relOpts[j]->namelen = strlen(relOpts[j]->name);
+ j++;
+ }
+
for (i = 0; intRelOpts[i].gen.name; i++)
{
relOpts[j] = &intRelOpts[i].gen;
@@ -809,6 +834,9 @@ allocate_reloption(bits32 kinds, int type, const char *name, const char *desc,
case RELOPT_TYPE_BOOL:
size = sizeof(relopt_bool);
break;
+ case RELOPT_TYPE_TERNARY:
+ size = sizeof(relopt_ternary);
+ break;
case RELOPT_TYPE_INT:
size = sizeof(relopt_int);
break;
@@ -892,6 +920,57 @@ add_local_bool_reloption(local_relopts *relopts, const char *name,
add_local_reloption(relopts, (relopt_gen *) newoption, offset);
}
+/*
+ * init_ternary_reloption
+ * Allocate and initialize a new ternary reloption
+ */
+static relopt_ternary *
+init_ternary_reloption(bits32 kinds, const char *name, const char *desc,
+ pg_ternary default_val, LOCKMODE lockmode)
+{
+ relopt_ternary *newoption;
+
+ newoption = (relopt_ternary *)
+ allocate_reloption(kinds, RELOPT_TYPE_TERNARY, name, desc, lockmode);
+ newoption->default_val = default_val;
+
+ return newoption;
+}
+
+/*
+ * add_ternary_reloption
+ * Add a new ternary reloption
+ */
+void
+add_ternary_reloption(bits32 kinds, const char *name, const char *desc,
+ pg_ternary default_val, LOCKMODE lockmode)
+{
+ relopt_ternary *newoption;
+
+ newoption =
+ init_ternary_reloption(kinds, name, desc, default_val, lockmode);
+
+ add_reloption((relopt_gen *) newoption);
+}
+
+/*
+ * add_local_ternary_reloption
+ * Add a new ternary local reloption
+ *
+ * 'offset' is offset of ternary-typed field.
+ */
+void
+add_local_ternary_reloption(local_relopts *relopts, const char *name,
+ const char *desc, pg_ternary default_val,
+ int offset)
+{
+ relopt_ternary *newoption;
+
+ newoption =
+ init_ternary_reloption(RELOPT_KIND_LOCAL, name, desc, default_val, 0);
+
+ add_local_reloption(relopts, (relopt_gen *) newoption, offset);
+}
/*
* init_real_reloption
@@ -1626,6 +1705,19 @@ parse_one_reloption(relopt_value *option, char *text_str, int text_len,
option->gen->name, value)));
}
break;
+ case RELOPT_TYPE_TERNARY:
+ {
+ bool b;
+
+ parsed = parse_bool(value, &b);
+ option->values.ternary_val = b ? TERNARY_TRUE : TERNARY_FALSE;
+ if (validate && !parsed)
+ ereport(ERROR,
+ (errcode(ERRCODE_INVALID_PARAMETER_VALUE),
+ errmsg("invalid value for ternary option \"%s\": %s",
+ option->gen->name, value)));
+ }
+ break;
case RELOPT_TYPE_INT:
{
relopt_int *optint = (relopt_int *) option->gen;
@@ -1789,17 +1881,6 @@ fillRelOptions(void *rdopts, Size basesize,
char *itempos = ((char *) rdopts) + elems[j].offset;
char *string_val;
- /*
- * If isset_offset is provided, store whether the reloption is
- * set there.
- */
- if (elems[j].isset_offset > 0)
- {
- char *setpos = ((char *) rdopts) + elems[j].isset_offset;
-
- *(bool *) setpos = options[i].isset;
- }
-
switch (options[i].gen->type)
{
case RELOPT_TYPE_BOOL:
@@ -1807,6 +1888,11 @@ fillRelOptions(void *rdopts, Size basesize,
options[i].values.bool_val :
((relopt_bool *) options[i].gen)->default_val;
break;
+ case RELOPT_TYPE_TERNARY:
+ *(pg_ternary *) itempos = options[i].isset ?
+ options[i].values.ternary_val :
+ ((relopt_ternary *) options[i].gen)->default_val;
+ break;
case RELOPT_TYPE_INT:
*(int *) itempos = options[i].isset ?
options[i].values.int_val :
@@ -1923,8 +2009,8 @@ default_reloptions(Datum reloptions, bool validate, relopt_kind kind)
offsetof(StdRdOptions, parallel_workers)},
{"vacuum_index_cleanup", RELOPT_TYPE_ENUM,
offsetof(StdRdOptions, vacuum_index_cleanup)},
- {"vacuum_truncate", RELOPT_TYPE_BOOL,
- offsetof(StdRdOptions, vacuum_truncate), offsetof(StdRdOptions, vacuum_truncate_set)},
+ {"vacuum_truncate", RELOPT_TYPE_TERNARY,
+ offsetof(StdRdOptions, vacuum_truncate)},
{"vacuum_max_eager_freeze_failure_rate", RELOPT_TYPE_REAL,
offsetof(StdRdOptions, vacuum_max_eager_freeze_failure_rate)}
};
@@ -2004,7 +2090,6 @@ build_local_reloptions(local_relopts *relopts, Datum options, bool validate)
elems[i].optname = opt->option->name;
elems[i].opttype = opt->option->type;
elems[i].offset = opt->offset;
- elems[i].isset_offset = 0; /* not supported for local relopts yet */
i++;
}
diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c
index aa4fbec143f..696eab9bd97 100644
--- a/src/backend/commands/vacuum.c
+++ b/src/backend/commands/vacuum.c
@@ -2224,9 +2224,9 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams params,
{
StdRdOptions *opts = (StdRdOptions *) rel->rd_options;
- if (opts && opts->vacuum_truncate_set)
+ if (opts && opts->vacuum_truncate != TERNARY_UNSET)
{
- if (opts->vacuum_truncate)
+ if (opts->vacuum_truncate == TERNARY_TRUE)
params.truncate = VACOPTVALUE_ENABLED;
else
params.truncate = VACOPTVALUE_DISABLED;
diff --git a/src/include/access/reloptions.h b/src/include/access/reloptions.h
index 2f08e1b0cf0..dfbef2babf2 100644
--- a/src/include/access/reloptions.h
+++ b/src/include/access/reloptions.h
@@ -29,6 +29,7 @@
typedef enum relopt_type
{
RELOPT_TYPE_BOOL,
+ RELOPT_TYPE_TERNARY, /* on, off, unset */
RELOPT_TYPE_INT,
RELOPT_TYPE_REAL,
RELOPT_TYPE_ENUM,
@@ -80,6 +81,7 @@ typedef struct relopt_value
union
{
bool bool_val;
+ pg_ternary ternary_val;
int int_val;
double real_val;
int enum_val;
@@ -94,6 +96,12 @@ typedef struct relopt_bool
bool default_val;
} relopt_bool;
+typedef struct relopt_ternary
+{
+ relopt_gen gen;
+ int default_val;
+} relopt_ternary;
+
typedef struct relopt_int
{
relopt_gen gen;
@@ -152,19 +160,6 @@ typedef struct
const char *optname; /* option's name */
relopt_type opttype; /* option's datatype */
int offset; /* offset of field in result struct */
-
- /*
- * isset_offset is an optional offset of a field in the result struct that
- * stores whether the option is explicitly set for the relation or if it
- * just picked up the default value. In most cases, this can be
- * accomplished by giving the reloption a special out-of-range default
- * value (e.g., some integer reloptions use -2), but this isn't always
- * possible. For example, a Boolean reloption cannot be given an
- * out-of-range default, so we need another way to discover the source of
- * its value. This offset is only used if given a value greater than
- * zero.
- */
- int isset_offset;
} relopt_parse_elt;
/* Local reloption definition */
@@ -195,6 +190,9 @@ typedef struct local_relopts
extern relopt_kind add_reloption_kind(void);
extern void add_bool_reloption(bits32 kinds, const char *name, const char *desc,
bool default_val, LOCKMODE lockmode);
+extern void add_ternary_reloption(bits32 kinds, const char *name,
+ const char *desc, pg_ternary default_val,
+ LOCKMODE lockmode);
extern void add_int_reloption(bits32 kinds, const char *name, const char *desc,
int default_val, int min_val, int max_val,
LOCKMODE lockmode);
@@ -214,6 +212,9 @@ extern void register_reloptions_validator(local_relopts *relopts,
extern void add_local_bool_reloption(local_relopts *relopts, const char *name,
const char *desc, bool default_val,
int offset);
+extern void add_local_ternary_reloption(local_relopts *relopts,
+ const char *name, const char *desc,
+ pg_ternary default_val, int offset);
extern void add_local_int_reloption(local_relopts *relopts, const char *name,
const char *desc, int default_val,
int min_val, int max_val, int offset);
diff --git a/src/include/postgres.h b/src/include/postgres.h
index 7d93fbce709..47c88780776 100644
--- a/src/include/postgres.h
+++ b/src/include/postgres.h
@@ -543,6 +543,21 @@ Float8GetDatum(float8 X)
* ----------------------------------------------------------------
*/
+/*
+ * pg_ternary
+ * Boolean value with an extra "unset" value
+ *
+ * This enum can be used for values that want to distinguish between true,
+ * false, and unset.
+*/
+
+typedef enum pg_ternary
+{
+ TERNARY_FALSE = 0,
+ TERNARY_TRUE = 1,
+ TERNARY_UNSET = -1
+} pg_ternary;
+
/*
* NON_EXEC_STATIC: It's sometimes useful to define a variable or function
* that is normally static but extern when using EXEC_BACKEND (see
diff --git a/src/include/utils/rel.h b/src/include/utils/rel.h
index d03ab247788..5feb18a5373 100644
--- a/src/include/utils/rel.h
+++ b/src/include/utils/rel.h
@@ -347,8 +347,7 @@ typedef struct StdRdOptions
bool user_catalog_table; /* use as an additional catalog relation */
int parallel_workers; /* max number of parallel workers */
StdRdOptIndexCleanup vacuum_index_cleanup; /* controls index vacuuming */
- bool vacuum_truncate; /* enables vacuum to truncate a relation */
- bool vacuum_truncate_set; /* whether vacuum_truncate is set */
+ pg_ternary vacuum_truncate; /* enables vacuum to truncate a relation */
/*
* Fraction of pages in a relation that vacuum can eagerly scan and fail
diff --git a/src/test/regress/expected/reloptions.out b/src/test/regress/expected/reloptions.out
index 9de19b4e3f1..1c99f79ab01 100644
--- a/src/test/regress/expected/reloptions.out
+++ b/src/test/regress/expected/reloptions.out
@@ -98,6 +98,42 @@ SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass;
{fillfactor=13,autovacuum_enabled=false}
(1 row)
+-- Tests for future (FIXME) ternary options
+-- behave as boolean option: accept unassigned name and truncated value
+DROP TABLE reloptions_test;
+CREATE TABLE reloptions_test(i INT) WITH (vacuum_truncate);
+SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass;
+ reloptions
+------------------------
+ {vacuum_truncate=true}
+(1 row)
+
+DROP TABLE reloptions_test;
+CREATE TABLE reloptions_test(i INT) WITH (vacuum_truncate=FaLS);
+SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass;
+ reloptions
+------------------------
+ {vacuum_truncate=fals}
+(1 row)
+
+-- preferred "true" alias is used when storing
+DROP TABLE reloptions_test;
+CREATE TABLE reloptions_test(i INT) WITH (vacuum_index_cleanup=on);
+SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass;
+ reloptions
+---------------------------
+ {vacuum_index_cleanup=on}
+(1 row)
+
+-- custom "third" value is available
+DROP TABLE reloptions_test;
+CREATE TABLE reloptions_test(i INT) WITH (vacuum_index_cleanup=auto);
+SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass;
+ reloptions
+-----------------------------
+ {vacuum_index_cleanup=auto}
+(1 row)
+
-- Test vacuum_truncate option
DROP TABLE reloptions_test;
CREATE TEMP TABLE reloptions_test(i INT NOT NULL, j text)
diff --git a/src/test/regress/sql/reloptions.sql b/src/test/regress/sql/reloptions.sql
index 24fbe0b478d..f5980dafcbc 100644
--- a/src/test/regress/sql/reloptions.sql
+++ b/src/test/regress/sql/reloptions.sql
@@ -59,6 +59,27 @@ UPDATE pg_class
ALTER TABLE reloptions_test RESET (illegal_option);
SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass;
+-- Tests for future (FIXME) ternary options
+
+-- behave as boolean option: accept unassigned name and truncated value
+DROP TABLE reloptions_test;
+CREATE TABLE reloptions_test(i INT) WITH (vacuum_truncate);
+SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass;
+
+DROP TABLE reloptions_test;
+CREATE TABLE reloptions_test(i INT) WITH (vacuum_truncate=FaLS);
+SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass;
+
+-- preferred "true" alias is used when storing
+DROP TABLE reloptions_test;
+CREATE TABLE reloptions_test(i INT) WITH (vacuum_index_cleanup=on);
+SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass;
+
+-- custom "third" value is available
+DROP TABLE reloptions_test;
+CREATE TABLE reloptions_test(i INT) WITH (vacuum_index_cleanup=auto);
+SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass;
+
-- Test vacuum_truncate option
DROP TABLE reloptions_test;
diff --git a/src/tools/pgindent/typedefs.list b/src/tools/pgindent/typedefs.list
index 3f3a888fd0e..1c8610fd46c 100644
--- a/src/tools/pgindent/typedefs.list
+++ b/src/tools/pgindent/typedefs.list
@@ -3952,6 +3952,7 @@ pg_sha512_ctx
pg_snapshot
pg_special_case
pg_stack_base_t
+pg_ternary
pg_time_t
pg_time_usec_t
pg_tz
@@ -4079,6 +4080,7 @@ relopt_kind
relopt_parse_elt
relopt_real
relopt_string
+relopt_ternary
relopt_type
relopt_value
relopts_validator
--
2.47.3
--c36t3fndxhkzbl5z--
^ permalink raw reply [nested|flat] 868+ messages in thread
* [PATCH v3] Introduce ternary reloptions
@ 2026-01-16 15:04 Álvaro Herrera <[email protected]>
0 siblings, 0 replies; 868+ messages in thread
From: Álvaro Herrera @ 2026-01-16 15:04 UTC (permalink / raw)
Introduce ternary reloption as a replacement for current
`vacuum_truncate` implementation. Remove the `vacuum_truncate_set`
separate flag and use TERNARY_UNSET instead.
This could also be used for other options such as `vacuum_index_cleanup`
and `buffering`, but lets get the scaffolding in first.
Discussion: https://postgr.es/m/3474141.usfYGdeWWP@thinkpad-pgpro
---
src/backend/access/common/reloptions.c | 137 ++++++++++++++++++-----
src/backend/commands/vacuum.c | 4 +-
src/include/access/reloptions.h | 27 ++---
src/include/postgres.h | 15 +++
src/include/utils/rel.h | 3 +-
src/test/regress/expected/reloptions.out | 36 ++++++
src/test/regress/sql/reloptions.sql | 21 ++++
src/tools/pgindent/typedefs.list | 2 +
8 files changed, 202 insertions(+), 43 deletions(-)
diff --git a/src/backend/access/common/reloptions.c b/src/backend/access/common/reloptions.c
index 0b83f98ed5f..6f1f577581a 100644
--- a/src/backend/access/common/reloptions.c
+++ b/src/backend/access/common/reloptions.c
@@ -40,9 +40,9 @@
*
* To add an option:
*
- * (i) decide on a type (bool, integer, real, enum, string), name, default
- * value, upper and lower bounds (if applicable); for strings, consider a
- * validation routine.
+ * (i) decide on a type (bool, ternary, integer, real, enum, string), name,
+ * default value, upper and lower bounds (if applicable); for strings,
+ * consider a validation routine.
* (ii) add a record below (or use add_<type>_reloption).
* (iii) add it to the appropriate options struct (perhaps StdRdOptions)
* (iv) add it to the appropriate handling routine (perhaps
@@ -147,15 +147,6 @@ static relopt_bool boolRelOpts[] =
},
false
},
- {
- {
- "vacuum_truncate",
- "Enables vacuum to truncate empty pages at the end of this table",
- RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
- ShareUpdateExclusiveLock
- },
- true
- },
{
{
"deduplicate_items",
@@ -170,6 +161,25 @@ static relopt_bool boolRelOpts[] =
{{NULL}}
};
+static relopt_ternary ternaryRelOpts[] =
+{
+ {
+ {
+ "vacuum_truncate",
+ "Enables vacuum to truncate empty pages at the end of this table",
+ RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
+ ShareUpdateExclusiveLock
+ },
+ TERNARY_UNSET
+ },
+ /* list terminator */
+ {
+ {
+ NULL
+ }
+ }
+};
+
static relopt_int intRelOpts[] =
{
{
@@ -609,6 +619,13 @@ initialize_reloptions(void)
boolRelOpts[i].gen.lockmode));
j++;
}
+ for (i = 0; ternaryRelOpts[i].gen.name; i++)
+ {
+ Assert(DoLockModesConflict(ternaryRelOpts[i].gen.lockmode,
+ ternaryRelOpts[i].gen.lockmode));
+ j++;
+ }
+
for (i = 0; intRelOpts[i].gen.name; i++)
{
Assert(DoLockModesConflict(intRelOpts[i].gen.lockmode,
@@ -649,6 +666,14 @@ initialize_reloptions(void)
j++;
}
+ for (i = 0; ternaryRelOpts[i].gen.name; i++)
+ {
+ relOpts[j] = &ternaryRelOpts[i].gen;
+ relOpts[j]->type = RELOPT_TYPE_TERNARY;
+ relOpts[j]->namelen = strlen(relOpts[j]->name);
+ j++;
+ }
+
for (i = 0; intRelOpts[i].gen.name; i++)
{
relOpts[j] = &intRelOpts[i].gen;
@@ -809,6 +834,9 @@ allocate_reloption(bits32 kinds, int type, const char *name, const char *desc,
case RELOPT_TYPE_BOOL:
size = sizeof(relopt_bool);
break;
+ case RELOPT_TYPE_TERNARY:
+ size = sizeof(relopt_ternary);
+ break;
case RELOPT_TYPE_INT:
size = sizeof(relopt_int);
break;
@@ -892,6 +920,57 @@ add_local_bool_reloption(local_relopts *relopts, const char *name,
add_local_reloption(relopts, (relopt_gen *) newoption, offset);
}
+/*
+ * init_ternary_reloption
+ * Allocate and initialize a new ternary reloption
+ */
+static relopt_ternary *
+init_ternary_reloption(bits32 kinds, const char *name, const char *desc,
+ pg_ternary default_val, LOCKMODE lockmode)
+{
+ relopt_ternary *newoption;
+
+ newoption = (relopt_ternary *)
+ allocate_reloption(kinds, RELOPT_TYPE_TERNARY, name, desc, lockmode);
+ newoption->default_val = default_val;
+
+ return newoption;
+}
+
+/*
+ * add_ternary_reloption
+ * Add a new ternary reloption
+ */
+void
+add_ternary_reloption(bits32 kinds, const char *name, const char *desc,
+ pg_ternary default_val, LOCKMODE lockmode)
+{
+ relopt_ternary *newoption;
+
+ newoption =
+ init_ternary_reloption(kinds, name, desc, default_val, lockmode);
+
+ add_reloption((relopt_gen *) newoption);
+}
+
+/*
+ * add_local_ternary_reloption
+ * Add a new ternary local reloption
+ *
+ * 'offset' is offset of ternary-typed field.
+ */
+void
+add_local_ternary_reloption(local_relopts *relopts, const char *name,
+ const char *desc, pg_ternary default_val,
+ int offset)
+{
+ relopt_ternary *newoption;
+
+ newoption =
+ init_ternary_reloption(RELOPT_KIND_LOCAL, name, desc, default_val, 0);
+
+ add_local_reloption(relopts, (relopt_gen *) newoption, offset);
+}
/*
* init_real_reloption
@@ -1626,6 +1705,19 @@ parse_one_reloption(relopt_value *option, char *text_str, int text_len,
option->gen->name, value)));
}
break;
+ case RELOPT_TYPE_TERNARY:
+ {
+ bool b;
+
+ parsed = parse_bool(value, &b);
+ option->values.ternary_val = b ? TERNARY_TRUE : TERNARY_FALSE;
+ if (validate && !parsed)
+ ereport(ERROR,
+ (errcode(ERRCODE_INVALID_PARAMETER_VALUE),
+ errmsg("invalid value for ternary option \"%s\": %s",
+ option->gen->name, value)));
+ }
+ break;
case RELOPT_TYPE_INT:
{
relopt_int *optint = (relopt_int *) option->gen;
@@ -1789,17 +1881,6 @@ fillRelOptions(void *rdopts, Size basesize,
char *itempos = ((char *) rdopts) + elems[j].offset;
char *string_val;
- /*
- * If isset_offset is provided, store whether the reloption is
- * set there.
- */
- if (elems[j].isset_offset > 0)
- {
- char *setpos = ((char *) rdopts) + elems[j].isset_offset;
-
- *(bool *) setpos = options[i].isset;
- }
-
switch (options[i].gen->type)
{
case RELOPT_TYPE_BOOL:
@@ -1807,6 +1888,11 @@ fillRelOptions(void *rdopts, Size basesize,
options[i].values.bool_val :
((relopt_bool *) options[i].gen)->default_val;
break;
+ case RELOPT_TYPE_TERNARY:
+ *(pg_ternary *) itempos = options[i].isset ?
+ options[i].values.ternary_val :
+ ((relopt_ternary *) options[i].gen)->default_val;
+ break;
case RELOPT_TYPE_INT:
*(int *) itempos = options[i].isset ?
options[i].values.int_val :
@@ -1923,8 +2009,8 @@ default_reloptions(Datum reloptions, bool validate, relopt_kind kind)
offsetof(StdRdOptions, parallel_workers)},
{"vacuum_index_cleanup", RELOPT_TYPE_ENUM,
offsetof(StdRdOptions, vacuum_index_cleanup)},
- {"vacuum_truncate", RELOPT_TYPE_BOOL,
- offsetof(StdRdOptions, vacuum_truncate), offsetof(StdRdOptions, vacuum_truncate_set)},
+ {"vacuum_truncate", RELOPT_TYPE_TERNARY,
+ offsetof(StdRdOptions, vacuum_truncate)},
{"vacuum_max_eager_freeze_failure_rate", RELOPT_TYPE_REAL,
offsetof(StdRdOptions, vacuum_max_eager_freeze_failure_rate)}
};
@@ -2004,7 +2090,6 @@ build_local_reloptions(local_relopts *relopts, Datum options, bool validate)
elems[i].optname = opt->option->name;
elems[i].opttype = opt->option->type;
elems[i].offset = opt->offset;
- elems[i].isset_offset = 0; /* not supported for local relopts yet */
i++;
}
diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c
index aa4fbec143f..696eab9bd97 100644
--- a/src/backend/commands/vacuum.c
+++ b/src/backend/commands/vacuum.c
@@ -2224,9 +2224,9 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams params,
{
StdRdOptions *opts = (StdRdOptions *) rel->rd_options;
- if (opts && opts->vacuum_truncate_set)
+ if (opts && opts->vacuum_truncate != TERNARY_UNSET)
{
- if (opts->vacuum_truncate)
+ if (opts->vacuum_truncate == TERNARY_TRUE)
params.truncate = VACOPTVALUE_ENABLED;
else
params.truncate = VACOPTVALUE_DISABLED;
diff --git a/src/include/access/reloptions.h b/src/include/access/reloptions.h
index 2f08e1b0cf0..dfbef2babf2 100644
--- a/src/include/access/reloptions.h
+++ b/src/include/access/reloptions.h
@@ -29,6 +29,7 @@
typedef enum relopt_type
{
RELOPT_TYPE_BOOL,
+ RELOPT_TYPE_TERNARY, /* on, off, unset */
RELOPT_TYPE_INT,
RELOPT_TYPE_REAL,
RELOPT_TYPE_ENUM,
@@ -80,6 +81,7 @@ typedef struct relopt_value
union
{
bool bool_val;
+ pg_ternary ternary_val;
int int_val;
double real_val;
int enum_val;
@@ -94,6 +96,12 @@ typedef struct relopt_bool
bool default_val;
} relopt_bool;
+typedef struct relopt_ternary
+{
+ relopt_gen gen;
+ int default_val;
+} relopt_ternary;
+
typedef struct relopt_int
{
relopt_gen gen;
@@ -152,19 +160,6 @@ typedef struct
const char *optname; /* option's name */
relopt_type opttype; /* option's datatype */
int offset; /* offset of field in result struct */
-
- /*
- * isset_offset is an optional offset of a field in the result struct that
- * stores whether the option is explicitly set for the relation or if it
- * just picked up the default value. In most cases, this can be
- * accomplished by giving the reloption a special out-of-range default
- * value (e.g., some integer reloptions use -2), but this isn't always
- * possible. For example, a Boolean reloption cannot be given an
- * out-of-range default, so we need another way to discover the source of
- * its value. This offset is only used if given a value greater than
- * zero.
- */
- int isset_offset;
} relopt_parse_elt;
/* Local reloption definition */
@@ -195,6 +190,9 @@ typedef struct local_relopts
extern relopt_kind add_reloption_kind(void);
extern void add_bool_reloption(bits32 kinds, const char *name, const char *desc,
bool default_val, LOCKMODE lockmode);
+extern void add_ternary_reloption(bits32 kinds, const char *name,
+ const char *desc, pg_ternary default_val,
+ LOCKMODE lockmode);
extern void add_int_reloption(bits32 kinds, const char *name, const char *desc,
int default_val, int min_val, int max_val,
LOCKMODE lockmode);
@@ -214,6 +212,9 @@ extern void register_reloptions_validator(local_relopts *relopts,
extern void add_local_bool_reloption(local_relopts *relopts, const char *name,
const char *desc, bool default_val,
int offset);
+extern void add_local_ternary_reloption(local_relopts *relopts,
+ const char *name, const char *desc,
+ pg_ternary default_val, int offset);
extern void add_local_int_reloption(local_relopts *relopts, const char *name,
const char *desc, int default_val,
int min_val, int max_val, int offset);
diff --git a/src/include/postgres.h b/src/include/postgres.h
index 7d93fbce709..47c88780776 100644
--- a/src/include/postgres.h
+++ b/src/include/postgres.h
@@ -543,6 +543,21 @@ Float8GetDatum(float8 X)
* ----------------------------------------------------------------
*/
+/*
+ * pg_ternary
+ * Boolean value with an extra "unset" value
+ *
+ * This enum can be used for values that want to distinguish between true,
+ * false, and unset.
+*/
+
+typedef enum pg_ternary
+{
+ TERNARY_FALSE = 0,
+ TERNARY_TRUE = 1,
+ TERNARY_UNSET = -1
+} pg_ternary;
+
/*
* NON_EXEC_STATIC: It's sometimes useful to define a variable or function
* that is normally static but extern when using EXEC_BACKEND (see
diff --git a/src/include/utils/rel.h b/src/include/utils/rel.h
index d03ab247788..5feb18a5373 100644
--- a/src/include/utils/rel.h
+++ b/src/include/utils/rel.h
@@ -347,8 +347,7 @@ typedef struct StdRdOptions
bool user_catalog_table; /* use as an additional catalog relation */
int parallel_workers; /* max number of parallel workers */
StdRdOptIndexCleanup vacuum_index_cleanup; /* controls index vacuuming */
- bool vacuum_truncate; /* enables vacuum to truncate a relation */
- bool vacuum_truncate_set; /* whether vacuum_truncate is set */
+ pg_ternary vacuum_truncate; /* enables vacuum to truncate a relation */
/*
* Fraction of pages in a relation that vacuum can eagerly scan and fail
diff --git a/src/test/regress/expected/reloptions.out b/src/test/regress/expected/reloptions.out
index 9de19b4e3f1..1c99f79ab01 100644
--- a/src/test/regress/expected/reloptions.out
+++ b/src/test/regress/expected/reloptions.out
@@ -98,6 +98,42 @@ SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass;
{fillfactor=13,autovacuum_enabled=false}
(1 row)
+-- Tests for future (FIXME) ternary options
+-- behave as boolean option: accept unassigned name and truncated value
+DROP TABLE reloptions_test;
+CREATE TABLE reloptions_test(i INT) WITH (vacuum_truncate);
+SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass;
+ reloptions
+------------------------
+ {vacuum_truncate=true}
+(1 row)
+
+DROP TABLE reloptions_test;
+CREATE TABLE reloptions_test(i INT) WITH (vacuum_truncate=FaLS);
+SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass;
+ reloptions
+------------------------
+ {vacuum_truncate=fals}
+(1 row)
+
+-- preferred "true" alias is used when storing
+DROP TABLE reloptions_test;
+CREATE TABLE reloptions_test(i INT) WITH (vacuum_index_cleanup=on);
+SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass;
+ reloptions
+---------------------------
+ {vacuum_index_cleanup=on}
+(1 row)
+
+-- custom "third" value is available
+DROP TABLE reloptions_test;
+CREATE TABLE reloptions_test(i INT) WITH (vacuum_index_cleanup=auto);
+SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass;
+ reloptions
+-----------------------------
+ {vacuum_index_cleanup=auto}
+(1 row)
+
-- Test vacuum_truncate option
DROP TABLE reloptions_test;
CREATE TEMP TABLE reloptions_test(i INT NOT NULL, j text)
diff --git a/src/test/regress/sql/reloptions.sql b/src/test/regress/sql/reloptions.sql
index 24fbe0b478d..f5980dafcbc 100644
--- a/src/test/regress/sql/reloptions.sql
+++ b/src/test/regress/sql/reloptions.sql
@@ -59,6 +59,27 @@ UPDATE pg_class
ALTER TABLE reloptions_test RESET (illegal_option);
SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass;
+-- Tests for future (FIXME) ternary options
+
+-- behave as boolean option: accept unassigned name and truncated value
+DROP TABLE reloptions_test;
+CREATE TABLE reloptions_test(i INT) WITH (vacuum_truncate);
+SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass;
+
+DROP TABLE reloptions_test;
+CREATE TABLE reloptions_test(i INT) WITH (vacuum_truncate=FaLS);
+SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass;
+
+-- preferred "true" alias is used when storing
+DROP TABLE reloptions_test;
+CREATE TABLE reloptions_test(i INT) WITH (vacuum_index_cleanup=on);
+SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass;
+
+-- custom "third" value is available
+DROP TABLE reloptions_test;
+CREATE TABLE reloptions_test(i INT) WITH (vacuum_index_cleanup=auto);
+SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass;
+
-- Test vacuum_truncate option
DROP TABLE reloptions_test;
diff --git a/src/tools/pgindent/typedefs.list b/src/tools/pgindent/typedefs.list
index 3f3a888fd0e..1c8610fd46c 100644
--- a/src/tools/pgindent/typedefs.list
+++ b/src/tools/pgindent/typedefs.list
@@ -3952,6 +3952,7 @@ pg_sha512_ctx
pg_snapshot
pg_special_case
pg_stack_base_t
+pg_ternary
pg_time_t
pg_time_usec_t
pg_tz
@@ -4079,6 +4080,7 @@ relopt_kind
relopt_parse_elt
relopt_real
relopt_string
+relopt_ternary
relopt_type
relopt_value
relopts_validator
--
2.47.3
--c36t3fndxhkzbl5z--
^ permalink raw reply [nested|flat] 868+ messages in thread
* [PATCH v3] Introduce ternary reloptions
@ 2026-01-16 15:04 Álvaro Herrera <[email protected]>
0 siblings, 0 replies; 868+ messages in thread
From: Álvaro Herrera @ 2026-01-16 15:04 UTC (permalink / raw)
Introduce ternary reloption as a replacement for current
`vacuum_truncate` implementation. Remove the `vacuum_truncate_set`
separate flag and use TERNARY_UNSET instead.
This could also be used for other options such as `vacuum_index_cleanup`
and `buffering`, but lets get the scaffolding in first.
Discussion: https://postgr.es/m/3474141.usfYGdeWWP@thinkpad-pgpro
---
src/backend/access/common/reloptions.c | 137 ++++++++++++++++++-----
src/backend/commands/vacuum.c | 4 +-
src/include/access/reloptions.h | 27 ++---
src/include/postgres.h | 15 +++
src/include/utils/rel.h | 3 +-
src/test/regress/expected/reloptions.out | 36 ++++++
src/test/regress/sql/reloptions.sql | 21 ++++
src/tools/pgindent/typedefs.list | 2 +
8 files changed, 202 insertions(+), 43 deletions(-)
diff --git a/src/backend/access/common/reloptions.c b/src/backend/access/common/reloptions.c
index 0b83f98ed5f..6f1f577581a 100644
--- a/src/backend/access/common/reloptions.c
+++ b/src/backend/access/common/reloptions.c
@@ -40,9 +40,9 @@
*
* To add an option:
*
- * (i) decide on a type (bool, integer, real, enum, string), name, default
- * value, upper and lower bounds (if applicable); for strings, consider a
- * validation routine.
+ * (i) decide on a type (bool, ternary, integer, real, enum, string), name,
+ * default value, upper and lower bounds (if applicable); for strings,
+ * consider a validation routine.
* (ii) add a record below (or use add_<type>_reloption).
* (iii) add it to the appropriate options struct (perhaps StdRdOptions)
* (iv) add it to the appropriate handling routine (perhaps
@@ -147,15 +147,6 @@ static relopt_bool boolRelOpts[] =
},
false
},
- {
- {
- "vacuum_truncate",
- "Enables vacuum to truncate empty pages at the end of this table",
- RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
- ShareUpdateExclusiveLock
- },
- true
- },
{
{
"deduplicate_items",
@@ -170,6 +161,25 @@ static relopt_bool boolRelOpts[] =
{{NULL}}
};
+static relopt_ternary ternaryRelOpts[] =
+{
+ {
+ {
+ "vacuum_truncate",
+ "Enables vacuum to truncate empty pages at the end of this table",
+ RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
+ ShareUpdateExclusiveLock
+ },
+ TERNARY_UNSET
+ },
+ /* list terminator */
+ {
+ {
+ NULL
+ }
+ }
+};
+
static relopt_int intRelOpts[] =
{
{
@@ -609,6 +619,13 @@ initialize_reloptions(void)
boolRelOpts[i].gen.lockmode));
j++;
}
+ for (i = 0; ternaryRelOpts[i].gen.name; i++)
+ {
+ Assert(DoLockModesConflict(ternaryRelOpts[i].gen.lockmode,
+ ternaryRelOpts[i].gen.lockmode));
+ j++;
+ }
+
for (i = 0; intRelOpts[i].gen.name; i++)
{
Assert(DoLockModesConflict(intRelOpts[i].gen.lockmode,
@@ -649,6 +666,14 @@ initialize_reloptions(void)
j++;
}
+ for (i = 0; ternaryRelOpts[i].gen.name; i++)
+ {
+ relOpts[j] = &ternaryRelOpts[i].gen;
+ relOpts[j]->type = RELOPT_TYPE_TERNARY;
+ relOpts[j]->namelen = strlen(relOpts[j]->name);
+ j++;
+ }
+
for (i = 0; intRelOpts[i].gen.name; i++)
{
relOpts[j] = &intRelOpts[i].gen;
@@ -809,6 +834,9 @@ allocate_reloption(bits32 kinds, int type, const char *name, const char *desc,
case RELOPT_TYPE_BOOL:
size = sizeof(relopt_bool);
break;
+ case RELOPT_TYPE_TERNARY:
+ size = sizeof(relopt_ternary);
+ break;
case RELOPT_TYPE_INT:
size = sizeof(relopt_int);
break;
@@ -892,6 +920,57 @@ add_local_bool_reloption(local_relopts *relopts, const char *name,
add_local_reloption(relopts, (relopt_gen *) newoption, offset);
}
+/*
+ * init_ternary_reloption
+ * Allocate and initialize a new ternary reloption
+ */
+static relopt_ternary *
+init_ternary_reloption(bits32 kinds, const char *name, const char *desc,
+ pg_ternary default_val, LOCKMODE lockmode)
+{
+ relopt_ternary *newoption;
+
+ newoption = (relopt_ternary *)
+ allocate_reloption(kinds, RELOPT_TYPE_TERNARY, name, desc, lockmode);
+ newoption->default_val = default_val;
+
+ return newoption;
+}
+
+/*
+ * add_ternary_reloption
+ * Add a new ternary reloption
+ */
+void
+add_ternary_reloption(bits32 kinds, const char *name, const char *desc,
+ pg_ternary default_val, LOCKMODE lockmode)
+{
+ relopt_ternary *newoption;
+
+ newoption =
+ init_ternary_reloption(kinds, name, desc, default_val, lockmode);
+
+ add_reloption((relopt_gen *) newoption);
+}
+
+/*
+ * add_local_ternary_reloption
+ * Add a new ternary local reloption
+ *
+ * 'offset' is offset of ternary-typed field.
+ */
+void
+add_local_ternary_reloption(local_relopts *relopts, const char *name,
+ const char *desc, pg_ternary default_val,
+ int offset)
+{
+ relopt_ternary *newoption;
+
+ newoption =
+ init_ternary_reloption(RELOPT_KIND_LOCAL, name, desc, default_val, 0);
+
+ add_local_reloption(relopts, (relopt_gen *) newoption, offset);
+}
/*
* init_real_reloption
@@ -1626,6 +1705,19 @@ parse_one_reloption(relopt_value *option, char *text_str, int text_len,
option->gen->name, value)));
}
break;
+ case RELOPT_TYPE_TERNARY:
+ {
+ bool b;
+
+ parsed = parse_bool(value, &b);
+ option->values.ternary_val = b ? TERNARY_TRUE : TERNARY_FALSE;
+ if (validate && !parsed)
+ ereport(ERROR,
+ (errcode(ERRCODE_INVALID_PARAMETER_VALUE),
+ errmsg("invalid value for ternary option \"%s\": %s",
+ option->gen->name, value)));
+ }
+ break;
case RELOPT_TYPE_INT:
{
relopt_int *optint = (relopt_int *) option->gen;
@@ -1789,17 +1881,6 @@ fillRelOptions(void *rdopts, Size basesize,
char *itempos = ((char *) rdopts) + elems[j].offset;
char *string_val;
- /*
- * If isset_offset is provided, store whether the reloption is
- * set there.
- */
- if (elems[j].isset_offset > 0)
- {
- char *setpos = ((char *) rdopts) + elems[j].isset_offset;
-
- *(bool *) setpos = options[i].isset;
- }
-
switch (options[i].gen->type)
{
case RELOPT_TYPE_BOOL:
@@ -1807,6 +1888,11 @@ fillRelOptions(void *rdopts, Size basesize,
options[i].values.bool_val :
((relopt_bool *) options[i].gen)->default_val;
break;
+ case RELOPT_TYPE_TERNARY:
+ *(pg_ternary *) itempos = options[i].isset ?
+ options[i].values.ternary_val :
+ ((relopt_ternary *) options[i].gen)->default_val;
+ break;
case RELOPT_TYPE_INT:
*(int *) itempos = options[i].isset ?
options[i].values.int_val :
@@ -1923,8 +2009,8 @@ default_reloptions(Datum reloptions, bool validate, relopt_kind kind)
offsetof(StdRdOptions, parallel_workers)},
{"vacuum_index_cleanup", RELOPT_TYPE_ENUM,
offsetof(StdRdOptions, vacuum_index_cleanup)},
- {"vacuum_truncate", RELOPT_TYPE_BOOL,
- offsetof(StdRdOptions, vacuum_truncate), offsetof(StdRdOptions, vacuum_truncate_set)},
+ {"vacuum_truncate", RELOPT_TYPE_TERNARY,
+ offsetof(StdRdOptions, vacuum_truncate)},
{"vacuum_max_eager_freeze_failure_rate", RELOPT_TYPE_REAL,
offsetof(StdRdOptions, vacuum_max_eager_freeze_failure_rate)}
};
@@ -2004,7 +2090,6 @@ build_local_reloptions(local_relopts *relopts, Datum options, bool validate)
elems[i].optname = opt->option->name;
elems[i].opttype = opt->option->type;
elems[i].offset = opt->offset;
- elems[i].isset_offset = 0; /* not supported for local relopts yet */
i++;
}
diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c
index aa4fbec143f..696eab9bd97 100644
--- a/src/backend/commands/vacuum.c
+++ b/src/backend/commands/vacuum.c
@@ -2224,9 +2224,9 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams params,
{
StdRdOptions *opts = (StdRdOptions *) rel->rd_options;
- if (opts && opts->vacuum_truncate_set)
+ if (opts && opts->vacuum_truncate != TERNARY_UNSET)
{
- if (opts->vacuum_truncate)
+ if (opts->vacuum_truncate == TERNARY_TRUE)
params.truncate = VACOPTVALUE_ENABLED;
else
params.truncate = VACOPTVALUE_DISABLED;
diff --git a/src/include/access/reloptions.h b/src/include/access/reloptions.h
index 2f08e1b0cf0..dfbef2babf2 100644
--- a/src/include/access/reloptions.h
+++ b/src/include/access/reloptions.h
@@ -29,6 +29,7 @@
typedef enum relopt_type
{
RELOPT_TYPE_BOOL,
+ RELOPT_TYPE_TERNARY, /* on, off, unset */
RELOPT_TYPE_INT,
RELOPT_TYPE_REAL,
RELOPT_TYPE_ENUM,
@@ -80,6 +81,7 @@ typedef struct relopt_value
union
{
bool bool_val;
+ pg_ternary ternary_val;
int int_val;
double real_val;
int enum_val;
@@ -94,6 +96,12 @@ typedef struct relopt_bool
bool default_val;
} relopt_bool;
+typedef struct relopt_ternary
+{
+ relopt_gen gen;
+ int default_val;
+} relopt_ternary;
+
typedef struct relopt_int
{
relopt_gen gen;
@@ -152,19 +160,6 @@ typedef struct
const char *optname; /* option's name */
relopt_type opttype; /* option's datatype */
int offset; /* offset of field in result struct */
-
- /*
- * isset_offset is an optional offset of a field in the result struct that
- * stores whether the option is explicitly set for the relation or if it
- * just picked up the default value. In most cases, this can be
- * accomplished by giving the reloption a special out-of-range default
- * value (e.g., some integer reloptions use -2), but this isn't always
- * possible. For example, a Boolean reloption cannot be given an
- * out-of-range default, so we need another way to discover the source of
- * its value. This offset is only used if given a value greater than
- * zero.
- */
- int isset_offset;
} relopt_parse_elt;
/* Local reloption definition */
@@ -195,6 +190,9 @@ typedef struct local_relopts
extern relopt_kind add_reloption_kind(void);
extern void add_bool_reloption(bits32 kinds, const char *name, const char *desc,
bool default_val, LOCKMODE lockmode);
+extern void add_ternary_reloption(bits32 kinds, const char *name,
+ const char *desc, pg_ternary default_val,
+ LOCKMODE lockmode);
extern void add_int_reloption(bits32 kinds, const char *name, const char *desc,
int default_val, int min_val, int max_val,
LOCKMODE lockmode);
@@ -214,6 +212,9 @@ extern void register_reloptions_validator(local_relopts *relopts,
extern void add_local_bool_reloption(local_relopts *relopts, const char *name,
const char *desc, bool default_val,
int offset);
+extern void add_local_ternary_reloption(local_relopts *relopts,
+ const char *name, const char *desc,
+ pg_ternary default_val, int offset);
extern void add_local_int_reloption(local_relopts *relopts, const char *name,
const char *desc, int default_val,
int min_val, int max_val, int offset);
diff --git a/src/include/postgres.h b/src/include/postgres.h
index 7d93fbce709..47c88780776 100644
--- a/src/include/postgres.h
+++ b/src/include/postgres.h
@@ -543,6 +543,21 @@ Float8GetDatum(float8 X)
* ----------------------------------------------------------------
*/
+/*
+ * pg_ternary
+ * Boolean value with an extra "unset" value
+ *
+ * This enum can be used for values that want to distinguish between true,
+ * false, and unset.
+*/
+
+typedef enum pg_ternary
+{
+ TERNARY_FALSE = 0,
+ TERNARY_TRUE = 1,
+ TERNARY_UNSET = -1
+} pg_ternary;
+
/*
* NON_EXEC_STATIC: It's sometimes useful to define a variable or function
* that is normally static but extern when using EXEC_BACKEND (see
diff --git a/src/include/utils/rel.h b/src/include/utils/rel.h
index d03ab247788..5feb18a5373 100644
--- a/src/include/utils/rel.h
+++ b/src/include/utils/rel.h
@@ -347,8 +347,7 @@ typedef struct StdRdOptions
bool user_catalog_table; /* use as an additional catalog relation */
int parallel_workers; /* max number of parallel workers */
StdRdOptIndexCleanup vacuum_index_cleanup; /* controls index vacuuming */
- bool vacuum_truncate; /* enables vacuum to truncate a relation */
- bool vacuum_truncate_set; /* whether vacuum_truncate is set */
+ pg_ternary vacuum_truncate; /* enables vacuum to truncate a relation */
/*
* Fraction of pages in a relation that vacuum can eagerly scan and fail
diff --git a/src/test/regress/expected/reloptions.out b/src/test/regress/expected/reloptions.out
index 9de19b4e3f1..1c99f79ab01 100644
--- a/src/test/regress/expected/reloptions.out
+++ b/src/test/regress/expected/reloptions.out
@@ -98,6 +98,42 @@ SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass;
{fillfactor=13,autovacuum_enabled=false}
(1 row)
+-- Tests for future (FIXME) ternary options
+-- behave as boolean option: accept unassigned name and truncated value
+DROP TABLE reloptions_test;
+CREATE TABLE reloptions_test(i INT) WITH (vacuum_truncate);
+SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass;
+ reloptions
+------------------------
+ {vacuum_truncate=true}
+(1 row)
+
+DROP TABLE reloptions_test;
+CREATE TABLE reloptions_test(i INT) WITH (vacuum_truncate=FaLS);
+SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass;
+ reloptions
+------------------------
+ {vacuum_truncate=fals}
+(1 row)
+
+-- preferred "true" alias is used when storing
+DROP TABLE reloptions_test;
+CREATE TABLE reloptions_test(i INT) WITH (vacuum_index_cleanup=on);
+SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass;
+ reloptions
+---------------------------
+ {vacuum_index_cleanup=on}
+(1 row)
+
+-- custom "third" value is available
+DROP TABLE reloptions_test;
+CREATE TABLE reloptions_test(i INT) WITH (vacuum_index_cleanup=auto);
+SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass;
+ reloptions
+-----------------------------
+ {vacuum_index_cleanup=auto}
+(1 row)
+
-- Test vacuum_truncate option
DROP TABLE reloptions_test;
CREATE TEMP TABLE reloptions_test(i INT NOT NULL, j text)
diff --git a/src/test/regress/sql/reloptions.sql b/src/test/regress/sql/reloptions.sql
index 24fbe0b478d..f5980dafcbc 100644
--- a/src/test/regress/sql/reloptions.sql
+++ b/src/test/regress/sql/reloptions.sql
@@ -59,6 +59,27 @@ UPDATE pg_class
ALTER TABLE reloptions_test RESET (illegal_option);
SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass;
+-- Tests for future (FIXME) ternary options
+
+-- behave as boolean option: accept unassigned name and truncated value
+DROP TABLE reloptions_test;
+CREATE TABLE reloptions_test(i INT) WITH (vacuum_truncate);
+SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass;
+
+DROP TABLE reloptions_test;
+CREATE TABLE reloptions_test(i INT) WITH (vacuum_truncate=FaLS);
+SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass;
+
+-- preferred "true" alias is used when storing
+DROP TABLE reloptions_test;
+CREATE TABLE reloptions_test(i INT) WITH (vacuum_index_cleanup=on);
+SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass;
+
+-- custom "third" value is available
+DROP TABLE reloptions_test;
+CREATE TABLE reloptions_test(i INT) WITH (vacuum_index_cleanup=auto);
+SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass;
+
-- Test vacuum_truncate option
DROP TABLE reloptions_test;
diff --git a/src/tools/pgindent/typedefs.list b/src/tools/pgindent/typedefs.list
index 3f3a888fd0e..1c8610fd46c 100644
--- a/src/tools/pgindent/typedefs.list
+++ b/src/tools/pgindent/typedefs.list
@@ -3952,6 +3952,7 @@ pg_sha512_ctx
pg_snapshot
pg_special_case
pg_stack_base_t
+pg_ternary
pg_time_t
pg_time_usec_t
pg_tz
@@ -4079,6 +4080,7 @@ relopt_kind
relopt_parse_elt
relopt_real
relopt_string
+relopt_ternary
relopt_type
relopt_value
relopts_validator
--
2.47.3
--c36t3fndxhkzbl5z--
^ permalink raw reply [nested|flat] 868+ messages in thread
* [PATCH v3] Introduce ternary reloptions
@ 2026-01-16 15:04 Álvaro Herrera <[email protected]>
0 siblings, 0 replies; 868+ messages in thread
From: Álvaro Herrera @ 2026-01-16 15:04 UTC (permalink / raw)
Introduce ternary reloption as a replacement for current
`vacuum_truncate` implementation. Remove the `vacuum_truncate_set`
separate flag and use TERNARY_UNSET instead.
This could also be used for other options such as `vacuum_index_cleanup`
and `buffering`, but lets get the scaffolding in first.
Discussion: https://postgr.es/m/3474141.usfYGdeWWP@thinkpad-pgpro
---
src/backend/access/common/reloptions.c | 137 ++++++++++++++++++-----
src/backend/commands/vacuum.c | 4 +-
src/include/access/reloptions.h | 27 ++---
src/include/postgres.h | 15 +++
src/include/utils/rel.h | 3 +-
src/test/regress/expected/reloptions.out | 36 ++++++
src/test/regress/sql/reloptions.sql | 21 ++++
src/tools/pgindent/typedefs.list | 2 +
8 files changed, 202 insertions(+), 43 deletions(-)
diff --git a/src/backend/access/common/reloptions.c b/src/backend/access/common/reloptions.c
index 0b83f98ed5f..6f1f577581a 100644
--- a/src/backend/access/common/reloptions.c
+++ b/src/backend/access/common/reloptions.c
@@ -40,9 +40,9 @@
*
* To add an option:
*
- * (i) decide on a type (bool, integer, real, enum, string), name, default
- * value, upper and lower bounds (if applicable); for strings, consider a
- * validation routine.
+ * (i) decide on a type (bool, ternary, integer, real, enum, string), name,
+ * default value, upper and lower bounds (if applicable); for strings,
+ * consider a validation routine.
* (ii) add a record below (or use add_<type>_reloption).
* (iii) add it to the appropriate options struct (perhaps StdRdOptions)
* (iv) add it to the appropriate handling routine (perhaps
@@ -147,15 +147,6 @@ static relopt_bool boolRelOpts[] =
},
false
},
- {
- {
- "vacuum_truncate",
- "Enables vacuum to truncate empty pages at the end of this table",
- RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
- ShareUpdateExclusiveLock
- },
- true
- },
{
{
"deduplicate_items",
@@ -170,6 +161,25 @@ static relopt_bool boolRelOpts[] =
{{NULL}}
};
+static relopt_ternary ternaryRelOpts[] =
+{
+ {
+ {
+ "vacuum_truncate",
+ "Enables vacuum to truncate empty pages at the end of this table",
+ RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
+ ShareUpdateExclusiveLock
+ },
+ TERNARY_UNSET
+ },
+ /* list terminator */
+ {
+ {
+ NULL
+ }
+ }
+};
+
static relopt_int intRelOpts[] =
{
{
@@ -609,6 +619,13 @@ initialize_reloptions(void)
boolRelOpts[i].gen.lockmode));
j++;
}
+ for (i = 0; ternaryRelOpts[i].gen.name; i++)
+ {
+ Assert(DoLockModesConflict(ternaryRelOpts[i].gen.lockmode,
+ ternaryRelOpts[i].gen.lockmode));
+ j++;
+ }
+
for (i = 0; intRelOpts[i].gen.name; i++)
{
Assert(DoLockModesConflict(intRelOpts[i].gen.lockmode,
@@ -649,6 +666,14 @@ initialize_reloptions(void)
j++;
}
+ for (i = 0; ternaryRelOpts[i].gen.name; i++)
+ {
+ relOpts[j] = &ternaryRelOpts[i].gen;
+ relOpts[j]->type = RELOPT_TYPE_TERNARY;
+ relOpts[j]->namelen = strlen(relOpts[j]->name);
+ j++;
+ }
+
for (i = 0; intRelOpts[i].gen.name; i++)
{
relOpts[j] = &intRelOpts[i].gen;
@@ -809,6 +834,9 @@ allocate_reloption(bits32 kinds, int type, const char *name, const char *desc,
case RELOPT_TYPE_BOOL:
size = sizeof(relopt_bool);
break;
+ case RELOPT_TYPE_TERNARY:
+ size = sizeof(relopt_ternary);
+ break;
case RELOPT_TYPE_INT:
size = sizeof(relopt_int);
break;
@@ -892,6 +920,57 @@ add_local_bool_reloption(local_relopts *relopts, const char *name,
add_local_reloption(relopts, (relopt_gen *) newoption, offset);
}
+/*
+ * init_ternary_reloption
+ * Allocate and initialize a new ternary reloption
+ */
+static relopt_ternary *
+init_ternary_reloption(bits32 kinds, const char *name, const char *desc,
+ pg_ternary default_val, LOCKMODE lockmode)
+{
+ relopt_ternary *newoption;
+
+ newoption = (relopt_ternary *)
+ allocate_reloption(kinds, RELOPT_TYPE_TERNARY, name, desc, lockmode);
+ newoption->default_val = default_val;
+
+ return newoption;
+}
+
+/*
+ * add_ternary_reloption
+ * Add a new ternary reloption
+ */
+void
+add_ternary_reloption(bits32 kinds, const char *name, const char *desc,
+ pg_ternary default_val, LOCKMODE lockmode)
+{
+ relopt_ternary *newoption;
+
+ newoption =
+ init_ternary_reloption(kinds, name, desc, default_val, lockmode);
+
+ add_reloption((relopt_gen *) newoption);
+}
+
+/*
+ * add_local_ternary_reloption
+ * Add a new ternary local reloption
+ *
+ * 'offset' is offset of ternary-typed field.
+ */
+void
+add_local_ternary_reloption(local_relopts *relopts, const char *name,
+ const char *desc, pg_ternary default_val,
+ int offset)
+{
+ relopt_ternary *newoption;
+
+ newoption =
+ init_ternary_reloption(RELOPT_KIND_LOCAL, name, desc, default_val, 0);
+
+ add_local_reloption(relopts, (relopt_gen *) newoption, offset);
+}
/*
* init_real_reloption
@@ -1626,6 +1705,19 @@ parse_one_reloption(relopt_value *option, char *text_str, int text_len,
option->gen->name, value)));
}
break;
+ case RELOPT_TYPE_TERNARY:
+ {
+ bool b;
+
+ parsed = parse_bool(value, &b);
+ option->values.ternary_val = b ? TERNARY_TRUE : TERNARY_FALSE;
+ if (validate && !parsed)
+ ereport(ERROR,
+ (errcode(ERRCODE_INVALID_PARAMETER_VALUE),
+ errmsg("invalid value for ternary option \"%s\": %s",
+ option->gen->name, value)));
+ }
+ break;
case RELOPT_TYPE_INT:
{
relopt_int *optint = (relopt_int *) option->gen;
@@ -1789,17 +1881,6 @@ fillRelOptions(void *rdopts, Size basesize,
char *itempos = ((char *) rdopts) + elems[j].offset;
char *string_val;
- /*
- * If isset_offset is provided, store whether the reloption is
- * set there.
- */
- if (elems[j].isset_offset > 0)
- {
- char *setpos = ((char *) rdopts) + elems[j].isset_offset;
-
- *(bool *) setpos = options[i].isset;
- }
-
switch (options[i].gen->type)
{
case RELOPT_TYPE_BOOL:
@@ -1807,6 +1888,11 @@ fillRelOptions(void *rdopts, Size basesize,
options[i].values.bool_val :
((relopt_bool *) options[i].gen)->default_val;
break;
+ case RELOPT_TYPE_TERNARY:
+ *(pg_ternary *) itempos = options[i].isset ?
+ options[i].values.ternary_val :
+ ((relopt_ternary *) options[i].gen)->default_val;
+ break;
case RELOPT_TYPE_INT:
*(int *) itempos = options[i].isset ?
options[i].values.int_val :
@@ -1923,8 +2009,8 @@ default_reloptions(Datum reloptions, bool validate, relopt_kind kind)
offsetof(StdRdOptions, parallel_workers)},
{"vacuum_index_cleanup", RELOPT_TYPE_ENUM,
offsetof(StdRdOptions, vacuum_index_cleanup)},
- {"vacuum_truncate", RELOPT_TYPE_BOOL,
- offsetof(StdRdOptions, vacuum_truncate), offsetof(StdRdOptions, vacuum_truncate_set)},
+ {"vacuum_truncate", RELOPT_TYPE_TERNARY,
+ offsetof(StdRdOptions, vacuum_truncate)},
{"vacuum_max_eager_freeze_failure_rate", RELOPT_TYPE_REAL,
offsetof(StdRdOptions, vacuum_max_eager_freeze_failure_rate)}
};
@@ -2004,7 +2090,6 @@ build_local_reloptions(local_relopts *relopts, Datum options, bool validate)
elems[i].optname = opt->option->name;
elems[i].opttype = opt->option->type;
elems[i].offset = opt->offset;
- elems[i].isset_offset = 0; /* not supported for local relopts yet */
i++;
}
diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c
index aa4fbec143f..696eab9bd97 100644
--- a/src/backend/commands/vacuum.c
+++ b/src/backend/commands/vacuum.c
@@ -2224,9 +2224,9 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams params,
{
StdRdOptions *opts = (StdRdOptions *) rel->rd_options;
- if (opts && opts->vacuum_truncate_set)
+ if (opts && opts->vacuum_truncate != TERNARY_UNSET)
{
- if (opts->vacuum_truncate)
+ if (opts->vacuum_truncate == TERNARY_TRUE)
params.truncate = VACOPTVALUE_ENABLED;
else
params.truncate = VACOPTVALUE_DISABLED;
diff --git a/src/include/access/reloptions.h b/src/include/access/reloptions.h
index 2f08e1b0cf0..dfbef2babf2 100644
--- a/src/include/access/reloptions.h
+++ b/src/include/access/reloptions.h
@@ -29,6 +29,7 @@
typedef enum relopt_type
{
RELOPT_TYPE_BOOL,
+ RELOPT_TYPE_TERNARY, /* on, off, unset */
RELOPT_TYPE_INT,
RELOPT_TYPE_REAL,
RELOPT_TYPE_ENUM,
@@ -80,6 +81,7 @@ typedef struct relopt_value
union
{
bool bool_val;
+ pg_ternary ternary_val;
int int_val;
double real_val;
int enum_val;
@@ -94,6 +96,12 @@ typedef struct relopt_bool
bool default_val;
} relopt_bool;
+typedef struct relopt_ternary
+{
+ relopt_gen gen;
+ int default_val;
+} relopt_ternary;
+
typedef struct relopt_int
{
relopt_gen gen;
@@ -152,19 +160,6 @@ typedef struct
const char *optname; /* option's name */
relopt_type opttype; /* option's datatype */
int offset; /* offset of field in result struct */
-
- /*
- * isset_offset is an optional offset of a field in the result struct that
- * stores whether the option is explicitly set for the relation or if it
- * just picked up the default value. In most cases, this can be
- * accomplished by giving the reloption a special out-of-range default
- * value (e.g., some integer reloptions use -2), but this isn't always
- * possible. For example, a Boolean reloption cannot be given an
- * out-of-range default, so we need another way to discover the source of
- * its value. This offset is only used if given a value greater than
- * zero.
- */
- int isset_offset;
} relopt_parse_elt;
/* Local reloption definition */
@@ -195,6 +190,9 @@ typedef struct local_relopts
extern relopt_kind add_reloption_kind(void);
extern void add_bool_reloption(bits32 kinds, const char *name, const char *desc,
bool default_val, LOCKMODE lockmode);
+extern void add_ternary_reloption(bits32 kinds, const char *name,
+ const char *desc, pg_ternary default_val,
+ LOCKMODE lockmode);
extern void add_int_reloption(bits32 kinds, const char *name, const char *desc,
int default_val, int min_val, int max_val,
LOCKMODE lockmode);
@@ -214,6 +212,9 @@ extern void register_reloptions_validator(local_relopts *relopts,
extern void add_local_bool_reloption(local_relopts *relopts, const char *name,
const char *desc, bool default_val,
int offset);
+extern void add_local_ternary_reloption(local_relopts *relopts,
+ const char *name, const char *desc,
+ pg_ternary default_val, int offset);
extern void add_local_int_reloption(local_relopts *relopts, const char *name,
const char *desc, int default_val,
int min_val, int max_val, int offset);
diff --git a/src/include/postgres.h b/src/include/postgres.h
index 7d93fbce709..47c88780776 100644
--- a/src/include/postgres.h
+++ b/src/include/postgres.h
@@ -543,6 +543,21 @@ Float8GetDatum(float8 X)
* ----------------------------------------------------------------
*/
+/*
+ * pg_ternary
+ * Boolean value with an extra "unset" value
+ *
+ * This enum can be used for values that want to distinguish between true,
+ * false, and unset.
+*/
+
+typedef enum pg_ternary
+{
+ TERNARY_FALSE = 0,
+ TERNARY_TRUE = 1,
+ TERNARY_UNSET = -1
+} pg_ternary;
+
/*
* NON_EXEC_STATIC: It's sometimes useful to define a variable or function
* that is normally static but extern when using EXEC_BACKEND (see
diff --git a/src/include/utils/rel.h b/src/include/utils/rel.h
index d03ab247788..5feb18a5373 100644
--- a/src/include/utils/rel.h
+++ b/src/include/utils/rel.h
@@ -347,8 +347,7 @@ typedef struct StdRdOptions
bool user_catalog_table; /* use as an additional catalog relation */
int parallel_workers; /* max number of parallel workers */
StdRdOptIndexCleanup vacuum_index_cleanup; /* controls index vacuuming */
- bool vacuum_truncate; /* enables vacuum to truncate a relation */
- bool vacuum_truncate_set; /* whether vacuum_truncate is set */
+ pg_ternary vacuum_truncate; /* enables vacuum to truncate a relation */
/*
* Fraction of pages in a relation that vacuum can eagerly scan and fail
diff --git a/src/test/regress/expected/reloptions.out b/src/test/regress/expected/reloptions.out
index 9de19b4e3f1..1c99f79ab01 100644
--- a/src/test/regress/expected/reloptions.out
+++ b/src/test/regress/expected/reloptions.out
@@ -98,6 +98,42 @@ SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass;
{fillfactor=13,autovacuum_enabled=false}
(1 row)
+-- Tests for future (FIXME) ternary options
+-- behave as boolean option: accept unassigned name and truncated value
+DROP TABLE reloptions_test;
+CREATE TABLE reloptions_test(i INT) WITH (vacuum_truncate);
+SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass;
+ reloptions
+------------------------
+ {vacuum_truncate=true}
+(1 row)
+
+DROP TABLE reloptions_test;
+CREATE TABLE reloptions_test(i INT) WITH (vacuum_truncate=FaLS);
+SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass;
+ reloptions
+------------------------
+ {vacuum_truncate=fals}
+(1 row)
+
+-- preferred "true" alias is used when storing
+DROP TABLE reloptions_test;
+CREATE TABLE reloptions_test(i INT) WITH (vacuum_index_cleanup=on);
+SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass;
+ reloptions
+---------------------------
+ {vacuum_index_cleanup=on}
+(1 row)
+
+-- custom "third" value is available
+DROP TABLE reloptions_test;
+CREATE TABLE reloptions_test(i INT) WITH (vacuum_index_cleanup=auto);
+SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass;
+ reloptions
+-----------------------------
+ {vacuum_index_cleanup=auto}
+(1 row)
+
-- Test vacuum_truncate option
DROP TABLE reloptions_test;
CREATE TEMP TABLE reloptions_test(i INT NOT NULL, j text)
diff --git a/src/test/regress/sql/reloptions.sql b/src/test/regress/sql/reloptions.sql
index 24fbe0b478d..f5980dafcbc 100644
--- a/src/test/regress/sql/reloptions.sql
+++ b/src/test/regress/sql/reloptions.sql
@@ -59,6 +59,27 @@ UPDATE pg_class
ALTER TABLE reloptions_test RESET (illegal_option);
SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass;
+-- Tests for future (FIXME) ternary options
+
+-- behave as boolean option: accept unassigned name and truncated value
+DROP TABLE reloptions_test;
+CREATE TABLE reloptions_test(i INT) WITH (vacuum_truncate);
+SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass;
+
+DROP TABLE reloptions_test;
+CREATE TABLE reloptions_test(i INT) WITH (vacuum_truncate=FaLS);
+SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass;
+
+-- preferred "true" alias is used when storing
+DROP TABLE reloptions_test;
+CREATE TABLE reloptions_test(i INT) WITH (vacuum_index_cleanup=on);
+SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass;
+
+-- custom "third" value is available
+DROP TABLE reloptions_test;
+CREATE TABLE reloptions_test(i INT) WITH (vacuum_index_cleanup=auto);
+SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass;
+
-- Test vacuum_truncate option
DROP TABLE reloptions_test;
diff --git a/src/tools/pgindent/typedefs.list b/src/tools/pgindent/typedefs.list
index 3f3a888fd0e..1c8610fd46c 100644
--- a/src/tools/pgindent/typedefs.list
+++ b/src/tools/pgindent/typedefs.list
@@ -3952,6 +3952,7 @@ pg_sha512_ctx
pg_snapshot
pg_special_case
pg_stack_base_t
+pg_ternary
pg_time_t
pg_time_usec_t
pg_tz
@@ -4079,6 +4080,7 @@ relopt_kind
relopt_parse_elt
relopt_real
relopt_string
+relopt_ternary
relopt_type
relopt_value
relopts_validator
--
2.47.3
--c36t3fndxhkzbl5z--
^ permalink raw reply [nested|flat] 868+ messages in thread
* [PATCH v3] Introduce ternary reloptions
@ 2026-01-16 15:04 Álvaro Herrera <[email protected]>
0 siblings, 0 replies; 868+ messages in thread
From: Álvaro Herrera @ 2026-01-16 15:04 UTC (permalink / raw)
Introduce ternary reloption as a replacement for current
`vacuum_truncate` implementation. Remove the `vacuum_truncate_set`
separate flag and use TERNARY_UNSET instead.
This could also be used for other options such as `vacuum_index_cleanup`
and `buffering`, but lets get the scaffolding in first.
Discussion: https://postgr.es/m/3474141.usfYGdeWWP@thinkpad-pgpro
---
src/backend/access/common/reloptions.c | 137 ++++++++++++++++++-----
src/backend/commands/vacuum.c | 4 +-
src/include/access/reloptions.h | 27 ++---
src/include/postgres.h | 15 +++
src/include/utils/rel.h | 3 +-
src/test/regress/expected/reloptions.out | 36 ++++++
src/test/regress/sql/reloptions.sql | 21 ++++
src/tools/pgindent/typedefs.list | 2 +
8 files changed, 202 insertions(+), 43 deletions(-)
diff --git a/src/backend/access/common/reloptions.c b/src/backend/access/common/reloptions.c
index 0b83f98ed5f..6f1f577581a 100644
--- a/src/backend/access/common/reloptions.c
+++ b/src/backend/access/common/reloptions.c
@@ -40,9 +40,9 @@
*
* To add an option:
*
- * (i) decide on a type (bool, integer, real, enum, string), name, default
- * value, upper and lower bounds (if applicable); for strings, consider a
- * validation routine.
+ * (i) decide on a type (bool, ternary, integer, real, enum, string), name,
+ * default value, upper and lower bounds (if applicable); for strings,
+ * consider a validation routine.
* (ii) add a record below (or use add_<type>_reloption).
* (iii) add it to the appropriate options struct (perhaps StdRdOptions)
* (iv) add it to the appropriate handling routine (perhaps
@@ -147,15 +147,6 @@ static relopt_bool boolRelOpts[] =
},
false
},
- {
- {
- "vacuum_truncate",
- "Enables vacuum to truncate empty pages at the end of this table",
- RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
- ShareUpdateExclusiveLock
- },
- true
- },
{
{
"deduplicate_items",
@@ -170,6 +161,25 @@ static relopt_bool boolRelOpts[] =
{{NULL}}
};
+static relopt_ternary ternaryRelOpts[] =
+{
+ {
+ {
+ "vacuum_truncate",
+ "Enables vacuum to truncate empty pages at the end of this table",
+ RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
+ ShareUpdateExclusiveLock
+ },
+ TERNARY_UNSET
+ },
+ /* list terminator */
+ {
+ {
+ NULL
+ }
+ }
+};
+
static relopt_int intRelOpts[] =
{
{
@@ -609,6 +619,13 @@ initialize_reloptions(void)
boolRelOpts[i].gen.lockmode));
j++;
}
+ for (i = 0; ternaryRelOpts[i].gen.name; i++)
+ {
+ Assert(DoLockModesConflict(ternaryRelOpts[i].gen.lockmode,
+ ternaryRelOpts[i].gen.lockmode));
+ j++;
+ }
+
for (i = 0; intRelOpts[i].gen.name; i++)
{
Assert(DoLockModesConflict(intRelOpts[i].gen.lockmode,
@@ -649,6 +666,14 @@ initialize_reloptions(void)
j++;
}
+ for (i = 0; ternaryRelOpts[i].gen.name; i++)
+ {
+ relOpts[j] = &ternaryRelOpts[i].gen;
+ relOpts[j]->type = RELOPT_TYPE_TERNARY;
+ relOpts[j]->namelen = strlen(relOpts[j]->name);
+ j++;
+ }
+
for (i = 0; intRelOpts[i].gen.name; i++)
{
relOpts[j] = &intRelOpts[i].gen;
@@ -809,6 +834,9 @@ allocate_reloption(bits32 kinds, int type, const char *name, const char *desc,
case RELOPT_TYPE_BOOL:
size = sizeof(relopt_bool);
break;
+ case RELOPT_TYPE_TERNARY:
+ size = sizeof(relopt_ternary);
+ break;
case RELOPT_TYPE_INT:
size = sizeof(relopt_int);
break;
@@ -892,6 +920,57 @@ add_local_bool_reloption(local_relopts *relopts, const char *name,
add_local_reloption(relopts, (relopt_gen *) newoption, offset);
}
+/*
+ * init_ternary_reloption
+ * Allocate and initialize a new ternary reloption
+ */
+static relopt_ternary *
+init_ternary_reloption(bits32 kinds, const char *name, const char *desc,
+ pg_ternary default_val, LOCKMODE lockmode)
+{
+ relopt_ternary *newoption;
+
+ newoption = (relopt_ternary *)
+ allocate_reloption(kinds, RELOPT_TYPE_TERNARY, name, desc, lockmode);
+ newoption->default_val = default_val;
+
+ return newoption;
+}
+
+/*
+ * add_ternary_reloption
+ * Add a new ternary reloption
+ */
+void
+add_ternary_reloption(bits32 kinds, const char *name, const char *desc,
+ pg_ternary default_val, LOCKMODE lockmode)
+{
+ relopt_ternary *newoption;
+
+ newoption =
+ init_ternary_reloption(kinds, name, desc, default_val, lockmode);
+
+ add_reloption((relopt_gen *) newoption);
+}
+
+/*
+ * add_local_ternary_reloption
+ * Add a new ternary local reloption
+ *
+ * 'offset' is offset of ternary-typed field.
+ */
+void
+add_local_ternary_reloption(local_relopts *relopts, const char *name,
+ const char *desc, pg_ternary default_val,
+ int offset)
+{
+ relopt_ternary *newoption;
+
+ newoption =
+ init_ternary_reloption(RELOPT_KIND_LOCAL, name, desc, default_val, 0);
+
+ add_local_reloption(relopts, (relopt_gen *) newoption, offset);
+}
/*
* init_real_reloption
@@ -1626,6 +1705,19 @@ parse_one_reloption(relopt_value *option, char *text_str, int text_len,
option->gen->name, value)));
}
break;
+ case RELOPT_TYPE_TERNARY:
+ {
+ bool b;
+
+ parsed = parse_bool(value, &b);
+ option->values.ternary_val = b ? TERNARY_TRUE : TERNARY_FALSE;
+ if (validate && !parsed)
+ ereport(ERROR,
+ (errcode(ERRCODE_INVALID_PARAMETER_VALUE),
+ errmsg("invalid value for ternary option \"%s\": %s",
+ option->gen->name, value)));
+ }
+ break;
case RELOPT_TYPE_INT:
{
relopt_int *optint = (relopt_int *) option->gen;
@@ -1789,17 +1881,6 @@ fillRelOptions(void *rdopts, Size basesize,
char *itempos = ((char *) rdopts) + elems[j].offset;
char *string_val;
- /*
- * If isset_offset is provided, store whether the reloption is
- * set there.
- */
- if (elems[j].isset_offset > 0)
- {
- char *setpos = ((char *) rdopts) + elems[j].isset_offset;
-
- *(bool *) setpos = options[i].isset;
- }
-
switch (options[i].gen->type)
{
case RELOPT_TYPE_BOOL:
@@ -1807,6 +1888,11 @@ fillRelOptions(void *rdopts, Size basesize,
options[i].values.bool_val :
((relopt_bool *) options[i].gen)->default_val;
break;
+ case RELOPT_TYPE_TERNARY:
+ *(pg_ternary *) itempos = options[i].isset ?
+ options[i].values.ternary_val :
+ ((relopt_ternary *) options[i].gen)->default_val;
+ break;
case RELOPT_TYPE_INT:
*(int *) itempos = options[i].isset ?
options[i].values.int_val :
@@ -1923,8 +2009,8 @@ default_reloptions(Datum reloptions, bool validate, relopt_kind kind)
offsetof(StdRdOptions, parallel_workers)},
{"vacuum_index_cleanup", RELOPT_TYPE_ENUM,
offsetof(StdRdOptions, vacuum_index_cleanup)},
- {"vacuum_truncate", RELOPT_TYPE_BOOL,
- offsetof(StdRdOptions, vacuum_truncate), offsetof(StdRdOptions, vacuum_truncate_set)},
+ {"vacuum_truncate", RELOPT_TYPE_TERNARY,
+ offsetof(StdRdOptions, vacuum_truncate)},
{"vacuum_max_eager_freeze_failure_rate", RELOPT_TYPE_REAL,
offsetof(StdRdOptions, vacuum_max_eager_freeze_failure_rate)}
};
@@ -2004,7 +2090,6 @@ build_local_reloptions(local_relopts *relopts, Datum options, bool validate)
elems[i].optname = opt->option->name;
elems[i].opttype = opt->option->type;
elems[i].offset = opt->offset;
- elems[i].isset_offset = 0; /* not supported for local relopts yet */
i++;
}
diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c
index aa4fbec143f..696eab9bd97 100644
--- a/src/backend/commands/vacuum.c
+++ b/src/backend/commands/vacuum.c
@@ -2224,9 +2224,9 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams params,
{
StdRdOptions *opts = (StdRdOptions *) rel->rd_options;
- if (opts && opts->vacuum_truncate_set)
+ if (opts && opts->vacuum_truncate != TERNARY_UNSET)
{
- if (opts->vacuum_truncate)
+ if (opts->vacuum_truncate == TERNARY_TRUE)
params.truncate = VACOPTVALUE_ENABLED;
else
params.truncate = VACOPTVALUE_DISABLED;
diff --git a/src/include/access/reloptions.h b/src/include/access/reloptions.h
index 2f08e1b0cf0..dfbef2babf2 100644
--- a/src/include/access/reloptions.h
+++ b/src/include/access/reloptions.h
@@ -29,6 +29,7 @@
typedef enum relopt_type
{
RELOPT_TYPE_BOOL,
+ RELOPT_TYPE_TERNARY, /* on, off, unset */
RELOPT_TYPE_INT,
RELOPT_TYPE_REAL,
RELOPT_TYPE_ENUM,
@@ -80,6 +81,7 @@ typedef struct relopt_value
union
{
bool bool_val;
+ pg_ternary ternary_val;
int int_val;
double real_val;
int enum_val;
@@ -94,6 +96,12 @@ typedef struct relopt_bool
bool default_val;
} relopt_bool;
+typedef struct relopt_ternary
+{
+ relopt_gen gen;
+ int default_val;
+} relopt_ternary;
+
typedef struct relopt_int
{
relopt_gen gen;
@@ -152,19 +160,6 @@ typedef struct
const char *optname; /* option's name */
relopt_type opttype; /* option's datatype */
int offset; /* offset of field in result struct */
-
- /*
- * isset_offset is an optional offset of a field in the result struct that
- * stores whether the option is explicitly set for the relation or if it
- * just picked up the default value. In most cases, this can be
- * accomplished by giving the reloption a special out-of-range default
- * value (e.g., some integer reloptions use -2), but this isn't always
- * possible. For example, a Boolean reloption cannot be given an
- * out-of-range default, so we need another way to discover the source of
- * its value. This offset is only used if given a value greater than
- * zero.
- */
- int isset_offset;
} relopt_parse_elt;
/* Local reloption definition */
@@ -195,6 +190,9 @@ typedef struct local_relopts
extern relopt_kind add_reloption_kind(void);
extern void add_bool_reloption(bits32 kinds, const char *name, const char *desc,
bool default_val, LOCKMODE lockmode);
+extern void add_ternary_reloption(bits32 kinds, const char *name,
+ const char *desc, pg_ternary default_val,
+ LOCKMODE lockmode);
extern void add_int_reloption(bits32 kinds, const char *name, const char *desc,
int default_val, int min_val, int max_val,
LOCKMODE lockmode);
@@ -214,6 +212,9 @@ extern void register_reloptions_validator(local_relopts *relopts,
extern void add_local_bool_reloption(local_relopts *relopts, const char *name,
const char *desc, bool default_val,
int offset);
+extern void add_local_ternary_reloption(local_relopts *relopts,
+ const char *name, const char *desc,
+ pg_ternary default_val, int offset);
extern void add_local_int_reloption(local_relopts *relopts, const char *name,
const char *desc, int default_val,
int min_val, int max_val, int offset);
diff --git a/src/include/postgres.h b/src/include/postgres.h
index 7d93fbce709..47c88780776 100644
--- a/src/include/postgres.h
+++ b/src/include/postgres.h
@@ -543,6 +543,21 @@ Float8GetDatum(float8 X)
* ----------------------------------------------------------------
*/
+/*
+ * pg_ternary
+ * Boolean value with an extra "unset" value
+ *
+ * This enum can be used for values that want to distinguish between true,
+ * false, and unset.
+*/
+
+typedef enum pg_ternary
+{
+ TERNARY_FALSE = 0,
+ TERNARY_TRUE = 1,
+ TERNARY_UNSET = -1
+} pg_ternary;
+
/*
* NON_EXEC_STATIC: It's sometimes useful to define a variable or function
* that is normally static but extern when using EXEC_BACKEND (see
diff --git a/src/include/utils/rel.h b/src/include/utils/rel.h
index d03ab247788..5feb18a5373 100644
--- a/src/include/utils/rel.h
+++ b/src/include/utils/rel.h
@@ -347,8 +347,7 @@ typedef struct StdRdOptions
bool user_catalog_table; /* use as an additional catalog relation */
int parallel_workers; /* max number of parallel workers */
StdRdOptIndexCleanup vacuum_index_cleanup; /* controls index vacuuming */
- bool vacuum_truncate; /* enables vacuum to truncate a relation */
- bool vacuum_truncate_set; /* whether vacuum_truncate is set */
+ pg_ternary vacuum_truncate; /* enables vacuum to truncate a relation */
/*
* Fraction of pages in a relation that vacuum can eagerly scan and fail
diff --git a/src/test/regress/expected/reloptions.out b/src/test/regress/expected/reloptions.out
index 9de19b4e3f1..1c99f79ab01 100644
--- a/src/test/regress/expected/reloptions.out
+++ b/src/test/regress/expected/reloptions.out
@@ -98,6 +98,42 @@ SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass;
{fillfactor=13,autovacuum_enabled=false}
(1 row)
+-- Tests for future (FIXME) ternary options
+-- behave as boolean option: accept unassigned name and truncated value
+DROP TABLE reloptions_test;
+CREATE TABLE reloptions_test(i INT) WITH (vacuum_truncate);
+SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass;
+ reloptions
+------------------------
+ {vacuum_truncate=true}
+(1 row)
+
+DROP TABLE reloptions_test;
+CREATE TABLE reloptions_test(i INT) WITH (vacuum_truncate=FaLS);
+SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass;
+ reloptions
+------------------------
+ {vacuum_truncate=fals}
+(1 row)
+
+-- preferred "true" alias is used when storing
+DROP TABLE reloptions_test;
+CREATE TABLE reloptions_test(i INT) WITH (vacuum_index_cleanup=on);
+SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass;
+ reloptions
+---------------------------
+ {vacuum_index_cleanup=on}
+(1 row)
+
+-- custom "third" value is available
+DROP TABLE reloptions_test;
+CREATE TABLE reloptions_test(i INT) WITH (vacuum_index_cleanup=auto);
+SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass;
+ reloptions
+-----------------------------
+ {vacuum_index_cleanup=auto}
+(1 row)
+
-- Test vacuum_truncate option
DROP TABLE reloptions_test;
CREATE TEMP TABLE reloptions_test(i INT NOT NULL, j text)
diff --git a/src/test/regress/sql/reloptions.sql b/src/test/regress/sql/reloptions.sql
index 24fbe0b478d..f5980dafcbc 100644
--- a/src/test/regress/sql/reloptions.sql
+++ b/src/test/regress/sql/reloptions.sql
@@ -59,6 +59,27 @@ UPDATE pg_class
ALTER TABLE reloptions_test RESET (illegal_option);
SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass;
+-- Tests for future (FIXME) ternary options
+
+-- behave as boolean option: accept unassigned name and truncated value
+DROP TABLE reloptions_test;
+CREATE TABLE reloptions_test(i INT) WITH (vacuum_truncate);
+SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass;
+
+DROP TABLE reloptions_test;
+CREATE TABLE reloptions_test(i INT) WITH (vacuum_truncate=FaLS);
+SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass;
+
+-- preferred "true" alias is used when storing
+DROP TABLE reloptions_test;
+CREATE TABLE reloptions_test(i INT) WITH (vacuum_index_cleanup=on);
+SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass;
+
+-- custom "third" value is available
+DROP TABLE reloptions_test;
+CREATE TABLE reloptions_test(i INT) WITH (vacuum_index_cleanup=auto);
+SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass;
+
-- Test vacuum_truncate option
DROP TABLE reloptions_test;
diff --git a/src/tools/pgindent/typedefs.list b/src/tools/pgindent/typedefs.list
index 3f3a888fd0e..1c8610fd46c 100644
--- a/src/tools/pgindent/typedefs.list
+++ b/src/tools/pgindent/typedefs.list
@@ -3952,6 +3952,7 @@ pg_sha512_ctx
pg_snapshot
pg_special_case
pg_stack_base_t
+pg_ternary
pg_time_t
pg_time_usec_t
pg_tz
@@ -4079,6 +4080,7 @@ relopt_kind
relopt_parse_elt
relopt_real
relopt_string
+relopt_ternary
relopt_type
relopt_value
relopts_validator
--
2.47.3
--c36t3fndxhkzbl5z--
^ permalink raw reply [nested|flat] 868+ messages in thread
* [PATCH v3] Introduce ternary reloptions
@ 2026-01-16 15:04 Álvaro Herrera <[email protected]>
0 siblings, 0 replies; 868+ messages in thread
From: Álvaro Herrera @ 2026-01-16 15:04 UTC (permalink / raw)
Introduce ternary reloption as a replacement for current
`vacuum_truncate` implementation. Remove the `vacuum_truncate_set`
separate flag and use TERNARY_UNSET instead.
This could also be used for other options such as `vacuum_index_cleanup`
and `buffering`, but lets get the scaffolding in first.
Discussion: https://postgr.es/m/3474141.usfYGdeWWP@thinkpad-pgpro
---
src/backend/access/common/reloptions.c | 137 ++++++++++++++++++-----
src/backend/commands/vacuum.c | 4 +-
src/include/access/reloptions.h | 27 ++---
src/include/postgres.h | 15 +++
src/include/utils/rel.h | 3 +-
src/test/regress/expected/reloptions.out | 36 ++++++
src/test/regress/sql/reloptions.sql | 21 ++++
src/tools/pgindent/typedefs.list | 2 +
8 files changed, 202 insertions(+), 43 deletions(-)
diff --git a/src/backend/access/common/reloptions.c b/src/backend/access/common/reloptions.c
index 0b83f98ed5f..6f1f577581a 100644
--- a/src/backend/access/common/reloptions.c
+++ b/src/backend/access/common/reloptions.c
@@ -40,9 +40,9 @@
*
* To add an option:
*
- * (i) decide on a type (bool, integer, real, enum, string), name, default
- * value, upper and lower bounds (if applicable); for strings, consider a
- * validation routine.
+ * (i) decide on a type (bool, ternary, integer, real, enum, string), name,
+ * default value, upper and lower bounds (if applicable); for strings,
+ * consider a validation routine.
* (ii) add a record below (or use add_<type>_reloption).
* (iii) add it to the appropriate options struct (perhaps StdRdOptions)
* (iv) add it to the appropriate handling routine (perhaps
@@ -147,15 +147,6 @@ static relopt_bool boolRelOpts[] =
},
false
},
- {
- {
- "vacuum_truncate",
- "Enables vacuum to truncate empty pages at the end of this table",
- RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
- ShareUpdateExclusiveLock
- },
- true
- },
{
{
"deduplicate_items",
@@ -170,6 +161,25 @@ static relopt_bool boolRelOpts[] =
{{NULL}}
};
+static relopt_ternary ternaryRelOpts[] =
+{
+ {
+ {
+ "vacuum_truncate",
+ "Enables vacuum to truncate empty pages at the end of this table",
+ RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
+ ShareUpdateExclusiveLock
+ },
+ TERNARY_UNSET
+ },
+ /* list terminator */
+ {
+ {
+ NULL
+ }
+ }
+};
+
static relopt_int intRelOpts[] =
{
{
@@ -609,6 +619,13 @@ initialize_reloptions(void)
boolRelOpts[i].gen.lockmode));
j++;
}
+ for (i = 0; ternaryRelOpts[i].gen.name; i++)
+ {
+ Assert(DoLockModesConflict(ternaryRelOpts[i].gen.lockmode,
+ ternaryRelOpts[i].gen.lockmode));
+ j++;
+ }
+
for (i = 0; intRelOpts[i].gen.name; i++)
{
Assert(DoLockModesConflict(intRelOpts[i].gen.lockmode,
@@ -649,6 +666,14 @@ initialize_reloptions(void)
j++;
}
+ for (i = 0; ternaryRelOpts[i].gen.name; i++)
+ {
+ relOpts[j] = &ternaryRelOpts[i].gen;
+ relOpts[j]->type = RELOPT_TYPE_TERNARY;
+ relOpts[j]->namelen = strlen(relOpts[j]->name);
+ j++;
+ }
+
for (i = 0; intRelOpts[i].gen.name; i++)
{
relOpts[j] = &intRelOpts[i].gen;
@@ -809,6 +834,9 @@ allocate_reloption(bits32 kinds, int type, const char *name, const char *desc,
case RELOPT_TYPE_BOOL:
size = sizeof(relopt_bool);
break;
+ case RELOPT_TYPE_TERNARY:
+ size = sizeof(relopt_ternary);
+ break;
case RELOPT_TYPE_INT:
size = sizeof(relopt_int);
break;
@@ -892,6 +920,57 @@ add_local_bool_reloption(local_relopts *relopts, const char *name,
add_local_reloption(relopts, (relopt_gen *) newoption, offset);
}
+/*
+ * init_ternary_reloption
+ * Allocate and initialize a new ternary reloption
+ */
+static relopt_ternary *
+init_ternary_reloption(bits32 kinds, const char *name, const char *desc,
+ pg_ternary default_val, LOCKMODE lockmode)
+{
+ relopt_ternary *newoption;
+
+ newoption = (relopt_ternary *)
+ allocate_reloption(kinds, RELOPT_TYPE_TERNARY, name, desc, lockmode);
+ newoption->default_val = default_val;
+
+ return newoption;
+}
+
+/*
+ * add_ternary_reloption
+ * Add a new ternary reloption
+ */
+void
+add_ternary_reloption(bits32 kinds, const char *name, const char *desc,
+ pg_ternary default_val, LOCKMODE lockmode)
+{
+ relopt_ternary *newoption;
+
+ newoption =
+ init_ternary_reloption(kinds, name, desc, default_val, lockmode);
+
+ add_reloption((relopt_gen *) newoption);
+}
+
+/*
+ * add_local_ternary_reloption
+ * Add a new ternary local reloption
+ *
+ * 'offset' is offset of ternary-typed field.
+ */
+void
+add_local_ternary_reloption(local_relopts *relopts, const char *name,
+ const char *desc, pg_ternary default_val,
+ int offset)
+{
+ relopt_ternary *newoption;
+
+ newoption =
+ init_ternary_reloption(RELOPT_KIND_LOCAL, name, desc, default_val, 0);
+
+ add_local_reloption(relopts, (relopt_gen *) newoption, offset);
+}
/*
* init_real_reloption
@@ -1626,6 +1705,19 @@ parse_one_reloption(relopt_value *option, char *text_str, int text_len,
option->gen->name, value)));
}
break;
+ case RELOPT_TYPE_TERNARY:
+ {
+ bool b;
+
+ parsed = parse_bool(value, &b);
+ option->values.ternary_val = b ? TERNARY_TRUE : TERNARY_FALSE;
+ if (validate && !parsed)
+ ereport(ERROR,
+ (errcode(ERRCODE_INVALID_PARAMETER_VALUE),
+ errmsg("invalid value for ternary option \"%s\": %s",
+ option->gen->name, value)));
+ }
+ break;
case RELOPT_TYPE_INT:
{
relopt_int *optint = (relopt_int *) option->gen;
@@ -1789,17 +1881,6 @@ fillRelOptions(void *rdopts, Size basesize,
char *itempos = ((char *) rdopts) + elems[j].offset;
char *string_val;
- /*
- * If isset_offset is provided, store whether the reloption is
- * set there.
- */
- if (elems[j].isset_offset > 0)
- {
- char *setpos = ((char *) rdopts) + elems[j].isset_offset;
-
- *(bool *) setpos = options[i].isset;
- }
-
switch (options[i].gen->type)
{
case RELOPT_TYPE_BOOL:
@@ -1807,6 +1888,11 @@ fillRelOptions(void *rdopts, Size basesize,
options[i].values.bool_val :
((relopt_bool *) options[i].gen)->default_val;
break;
+ case RELOPT_TYPE_TERNARY:
+ *(pg_ternary *) itempos = options[i].isset ?
+ options[i].values.ternary_val :
+ ((relopt_ternary *) options[i].gen)->default_val;
+ break;
case RELOPT_TYPE_INT:
*(int *) itempos = options[i].isset ?
options[i].values.int_val :
@@ -1923,8 +2009,8 @@ default_reloptions(Datum reloptions, bool validate, relopt_kind kind)
offsetof(StdRdOptions, parallel_workers)},
{"vacuum_index_cleanup", RELOPT_TYPE_ENUM,
offsetof(StdRdOptions, vacuum_index_cleanup)},
- {"vacuum_truncate", RELOPT_TYPE_BOOL,
- offsetof(StdRdOptions, vacuum_truncate), offsetof(StdRdOptions, vacuum_truncate_set)},
+ {"vacuum_truncate", RELOPT_TYPE_TERNARY,
+ offsetof(StdRdOptions, vacuum_truncate)},
{"vacuum_max_eager_freeze_failure_rate", RELOPT_TYPE_REAL,
offsetof(StdRdOptions, vacuum_max_eager_freeze_failure_rate)}
};
@@ -2004,7 +2090,6 @@ build_local_reloptions(local_relopts *relopts, Datum options, bool validate)
elems[i].optname = opt->option->name;
elems[i].opttype = opt->option->type;
elems[i].offset = opt->offset;
- elems[i].isset_offset = 0; /* not supported for local relopts yet */
i++;
}
diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c
index aa4fbec143f..696eab9bd97 100644
--- a/src/backend/commands/vacuum.c
+++ b/src/backend/commands/vacuum.c
@@ -2224,9 +2224,9 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams params,
{
StdRdOptions *opts = (StdRdOptions *) rel->rd_options;
- if (opts && opts->vacuum_truncate_set)
+ if (opts && opts->vacuum_truncate != TERNARY_UNSET)
{
- if (opts->vacuum_truncate)
+ if (opts->vacuum_truncate == TERNARY_TRUE)
params.truncate = VACOPTVALUE_ENABLED;
else
params.truncate = VACOPTVALUE_DISABLED;
diff --git a/src/include/access/reloptions.h b/src/include/access/reloptions.h
index 2f08e1b0cf0..dfbef2babf2 100644
--- a/src/include/access/reloptions.h
+++ b/src/include/access/reloptions.h
@@ -29,6 +29,7 @@
typedef enum relopt_type
{
RELOPT_TYPE_BOOL,
+ RELOPT_TYPE_TERNARY, /* on, off, unset */
RELOPT_TYPE_INT,
RELOPT_TYPE_REAL,
RELOPT_TYPE_ENUM,
@@ -80,6 +81,7 @@ typedef struct relopt_value
union
{
bool bool_val;
+ pg_ternary ternary_val;
int int_val;
double real_val;
int enum_val;
@@ -94,6 +96,12 @@ typedef struct relopt_bool
bool default_val;
} relopt_bool;
+typedef struct relopt_ternary
+{
+ relopt_gen gen;
+ int default_val;
+} relopt_ternary;
+
typedef struct relopt_int
{
relopt_gen gen;
@@ -152,19 +160,6 @@ typedef struct
const char *optname; /* option's name */
relopt_type opttype; /* option's datatype */
int offset; /* offset of field in result struct */
-
- /*
- * isset_offset is an optional offset of a field in the result struct that
- * stores whether the option is explicitly set for the relation or if it
- * just picked up the default value. In most cases, this can be
- * accomplished by giving the reloption a special out-of-range default
- * value (e.g., some integer reloptions use -2), but this isn't always
- * possible. For example, a Boolean reloption cannot be given an
- * out-of-range default, so we need another way to discover the source of
- * its value. This offset is only used if given a value greater than
- * zero.
- */
- int isset_offset;
} relopt_parse_elt;
/* Local reloption definition */
@@ -195,6 +190,9 @@ typedef struct local_relopts
extern relopt_kind add_reloption_kind(void);
extern void add_bool_reloption(bits32 kinds, const char *name, const char *desc,
bool default_val, LOCKMODE lockmode);
+extern void add_ternary_reloption(bits32 kinds, const char *name,
+ const char *desc, pg_ternary default_val,
+ LOCKMODE lockmode);
extern void add_int_reloption(bits32 kinds, const char *name, const char *desc,
int default_val, int min_val, int max_val,
LOCKMODE lockmode);
@@ -214,6 +212,9 @@ extern void register_reloptions_validator(local_relopts *relopts,
extern void add_local_bool_reloption(local_relopts *relopts, const char *name,
const char *desc, bool default_val,
int offset);
+extern void add_local_ternary_reloption(local_relopts *relopts,
+ const char *name, const char *desc,
+ pg_ternary default_val, int offset);
extern void add_local_int_reloption(local_relopts *relopts, const char *name,
const char *desc, int default_val,
int min_val, int max_val, int offset);
diff --git a/src/include/postgres.h b/src/include/postgres.h
index 7d93fbce709..47c88780776 100644
--- a/src/include/postgres.h
+++ b/src/include/postgres.h
@@ -543,6 +543,21 @@ Float8GetDatum(float8 X)
* ----------------------------------------------------------------
*/
+/*
+ * pg_ternary
+ * Boolean value with an extra "unset" value
+ *
+ * This enum can be used for values that want to distinguish between true,
+ * false, and unset.
+*/
+
+typedef enum pg_ternary
+{
+ TERNARY_FALSE = 0,
+ TERNARY_TRUE = 1,
+ TERNARY_UNSET = -1
+} pg_ternary;
+
/*
* NON_EXEC_STATIC: It's sometimes useful to define a variable or function
* that is normally static but extern when using EXEC_BACKEND (see
diff --git a/src/include/utils/rel.h b/src/include/utils/rel.h
index d03ab247788..5feb18a5373 100644
--- a/src/include/utils/rel.h
+++ b/src/include/utils/rel.h
@@ -347,8 +347,7 @@ typedef struct StdRdOptions
bool user_catalog_table; /* use as an additional catalog relation */
int parallel_workers; /* max number of parallel workers */
StdRdOptIndexCleanup vacuum_index_cleanup; /* controls index vacuuming */
- bool vacuum_truncate; /* enables vacuum to truncate a relation */
- bool vacuum_truncate_set; /* whether vacuum_truncate is set */
+ pg_ternary vacuum_truncate; /* enables vacuum to truncate a relation */
/*
* Fraction of pages in a relation that vacuum can eagerly scan and fail
diff --git a/src/test/regress/expected/reloptions.out b/src/test/regress/expected/reloptions.out
index 9de19b4e3f1..1c99f79ab01 100644
--- a/src/test/regress/expected/reloptions.out
+++ b/src/test/regress/expected/reloptions.out
@@ -98,6 +98,42 @@ SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass;
{fillfactor=13,autovacuum_enabled=false}
(1 row)
+-- Tests for future (FIXME) ternary options
+-- behave as boolean option: accept unassigned name and truncated value
+DROP TABLE reloptions_test;
+CREATE TABLE reloptions_test(i INT) WITH (vacuum_truncate);
+SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass;
+ reloptions
+------------------------
+ {vacuum_truncate=true}
+(1 row)
+
+DROP TABLE reloptions_test;
+CREATE TABLE reloptions_test(i INT) WITH (vacuum_truncate=FaLS);
+SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass;
+ reloptions
+------------------------
+ {vacuum_truncate=fals}
+(1 row)
+
+-- preferred "true" alias is used when storing
+DROP TABLE reloptions_test;
+CREATE TABLE reloptions_test(i INT) WITH (vacuum_index_cleanup=on);
+SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass;
+ reloptions
+---------------------------
+ {vacuum_index_cleanup=on}
+(1 row)
+
+-- custom "third" value is available
+DROP TABLE reloptions_test;
+CREATE TABLE reloptions_test(i INT) WITH (vacuum_index_cleanup=auto);
+SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass;
+ reloptions
+-----------------------------
+ {vacuum_index_cleanup=auto}
+(1 row)
+
-- Test vacuum_truncate option
DROP TABLE reloptions_test;
CREATE TEMP TABLE reloptions_test(i INT NOT NULL, j text)
diff --git a/src/test/regress/sql/reloptions.sql b/src/test/regress/sql/reloptions.sql
index 24fbe0b478d..f5980dafcbc 100644
--- a/src/test/regress/sql/reloptions.sql
+++ b/src/test/regress/sql/reloptions.sql
@@ -59,6 +59,27 @@ UPDATE pg_class
ALTER TABLE reloptions_test RESET (illegal_option);
SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass;
+-- Tests for future (FIXME) ternary options
+
+-- behave as boolean option: accept unassigned name and truncated value
+DROP TABLE reloptions_test;
+CREATE TABLE reloptions_test(i INT) WITH (vacuum_truncate);
+SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass;
+
+DROP TABLE reloptions_test;
+CREATE TABLE reloptions_test(i INT) WITH (vacuum_truncate=FaLS);
+SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass;
+
+-- preferred "true" alias is used when storing
+DROP TABLE reloptions_test;
+CREATE TABLE reloptions_test(i INT) WITH (vacuum_index_cleanup=on);
+SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass;
+
+-- custom "third" value is available
+DROP TABLE reloptions_test;
+CREATE TABLE reloptions_test(i INT) WITH (vacuum_index_cleanup=auto);
+SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass;
+
-- Test vacuum_truncate option
DROP TABLE reloptions_test;
diff --git a/src/tools/pgindent/typedefs.list b/src/tools/pgindent/typedefs.list
index 3f3a888fd0e..1c8610fd46c 100644
--- a/src/tools/pgindent/typedefs.list
+++ b/src/tools/pgindent/typedefs.list
@@ -3952,6 +3952,7 @@ pg_sha512_ctx
pg_snapshot
pg_special_case
pg_stack_base_t
+pg_ternary
pg_time_t
pg_time_usec_t
pg_tz
@@ -4079,6 +4080,7 @@ relopt_kind
relopt_parse_elt
relopt_real
relopt_string
+relopt_ternary
relopt_type
relopt_value
relopts_validator
--
2.47.3
--c36t3fndxhkzbl5z--
^ permalink raw reply [nested|flat] 868+ messages in thread
* [PATCH v3] Introduce ternary reloptions
@ 2026-01-16 15:04 Álvaro Herrera <[email protected]>
0 siblings, 0 replies; 868+ messages in thread
From: Álvaro Herrera @ 2026-01-16 15:04 UTC (permalink / raw)
Introduce ternary reloption as a replacement for current
`vacuum_truncate` implementation. Remove the `vacuum_truncate_set`
separate flag and use TERNARY_UNSET instead.
This could also be used for other options such as `vacuum_index_cleanup`
and `buffering`, but lets get the scaffolding in first.
Discussion: https://postgr.es/m/3474141.usfYGdeWWP@thinkpad-pgpro
---
src/backend/access/common/reloptions.c | 137 ++++++++++++++++++-----
src/backend/commands/vacuum.c | 4 +-
src/include/access/reloptions.h | 27 ++---
src/include/postgres.h | 15 +++
src/include/utils/rel.h | 3 +-
src/test/regress/expected/reloptions.out | 36 ++++++
src/test/regress/sql/reloptions.sql | 21 ++++
src/tools/pgindent/typedefs.list | 2 +
8 files changed, 202 insertions(+), 43 deletions(-)
diff --git a/src/backend/access/common/reloptions.c b/src/backend/access/common/reloptions.c
index 0b83f98ed5f..6f1f577581a 100644
--- a/src/backend/access/common/reloptions.c
+++ b/src/backend/access/common/reloptions.c
@@ -40,9 +40,9 @@
*
* To add an option:
*
- * (i) decide on a type (bool, integer, real, enum, string), name, default
- * value, upper and lower bounds (if applicable); for strings, consider a
- * validation routine.
+ * (i) decide on a type (bool, ternary, integer, real, enum, string), name,
+ * default value, upper and lower bounds (if applicable); for strings,
+ * consider a validation routine.
* (ii) add a record below (or use add_<type>_reloption).
* (iii) add it to the appropriate options struct (perhaps StdRdOptions)
* (iv) add it to the appropriate handling routine (perhaps
@@ -147,15 +147,6 @@ static relopt_bool boolRelOpts[] =
},
false
},
- {
- {
- "vacuum_truncate",
- "Enables vacuum to truncate empty pages at the end of this table",
- RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
- ShareUpdateExclusiveLock
- },
- true
- },
{
{
"deduplicate_items",
@@ -170,6 +161,25 @@ static relopt_bool boolRelOpts[] =
{{NULL}}
};
+static relopt_ternary ternaryRelOpts[] =
+{
+ {
+ {
+ "vacuum_truncate",
+ "Enables vacuum to truncate empty pages at the end of this table",
+ RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
+ ShareUpdateExclusiveLock
+ },
+ TERNARY_UNSET
+ },
+ /* list terminator */
+ {
+ {
+ NULL
+ }
+ }
+};
+
static relopt_int intRelOpts[] =
{
{
@@ -609,6 +619,13 @@ initialize_reloptions(void)
boolRelOpts[i].gen.lockmode));
j++;
}
+ for (i = 0; ternaryRelOpts[i].gen.name; i++)
+ {
+ Assert(DoLockModesConflict(ternaryRelOpts[i].gen.lockmode,
+ ternaryRelOpts[i].gen.lockmode));
+ j++;
+ }
+
for (i = 0; intRelOpts[i].gen.name; i++)
{
Assert(DoLockModesConflict(intRelOpts[i].gen.lockmode,
@@ -649,6 +666,14 @@ initialize_reloptions(void)
j++;
}
+ for (i = 0; ternaryRelOpts[i].gen.name; i++)
+ {
+ relOpts[j] = &ternaryRelOpts[i].gen;
+ relOpts[j]->type = RELOPT_TYPE_TERNARY;
+ relOpts[j]->namelen = strlen(relOpts[j]->name);
+ j++;
+ }
+
for (i = 0; intRelOpts[i].gen.name; i++)
{
relOpts[j] = &intRelOpts[i].gen;
@@ -809,6 +834,9 @@ allocate_reloption(bits32 kinds, int type, const char *name, const char *desc,
case RELOPT_TYPE_BOOL:
size = sizeof(relopt_bool);
break;
+ case RELOPT_TYPE_TERNARY:
+ size = sizeof(relopt_ternary);
+ break;
case RELOPT_TYPE_INT:
size = sizeof(relopt_int);
break;
@@ -892,6 +920,57 @@ add_local_bool_reloption(local_relopts *relopts, const char *name,
add_local_reloption(relopts, (relopt_gen *) newoption, offset);
}
+/*
+ * init_ternary_reloption
+ * Allocate and initialize a new ternary reloption
+ */
+static relopt_ternary *
+init_ternary_reloption(bits32 kinds, const char *name, const char *desc,
+ pg_ternary default_val, LOCKMODE lockmode)
+{
+ relopt_ternary *newoption;
+
+ newoption = (relopt_ternary *)
+ allocate_reloption(kinds, RELOPT_TYPE_TERNARY, name, desc, lockmode);
+ newoption->default_val = default_val;
+
+ return newoption;
+}
+
+/*
+ * add_ternary_reloption
+ * Add a new ternary reloption
+ */
+void
+add_ternary_reloption(bits32 kinds, const char *name, const char *desc,
+ pg_ternary default_val, LOCKMODE lockmode)
+{
+ relopt_ternary *newoption;
+
+ newoption =
+ init_ternary_reloption(kinds, name, desc, default_val, lockmode);
+
+ add_reloption((relopt_gen *) newoption);
+}
+
+/*
+ * add_local_ternary_reloption
+ * Add a new ternary local reloption
+ *
+ * 'offset' is offset of ternary-typed field.
+ */
+void
+add_local_ternary_reloption(local_relopts *relopts, const char *name,
+ const char *desc, pg_ternary default_val,
+ int offset)
+{
+ relopt_ternary *newoption;
+
+ newoption =
+ init_ternary_reloption(RELOPT_KIND_LOCAL, name, desc, default_val, 0);
+
+ add_local_reloption(relopts, (relopt_gen *) newoption, offset);
+}
/*
* init_real_reloption
@@ -1626,6 +1705,19 @@ parse_one_reloption(relopt_value *option, char *text_str, int text_len,
option->gen->name, value)));
}
break;
+ case RELOPT_TYPE_TERNARY:
+ {
+ bool b;
+
+ parsed = parse_bool(value, &b);
+ option->values.ternary_val = b ? TERNARY_TRUE : TERNARY_FALSE;
+ if (validate && !parsed)
+ ereport(ERROR,
+ (errcode(ERRCODE_INVALID_PARAMETER_VALUE),
+ errmsg("invalid value for ternary option \"%s\": %s",
+ option->gen->name, value)));
+ }
+ break;
case RELOPT_TYPE_INT:
{
relopt_int *optint = (relopt_int *) option->gen;
@@ -1789,17 +1881,6 @@ fillRelOptions(void *rdopts, Size basesize,
char *itempos = ((char *) rdopts) + elems[j].offset;
char *string_val;
- /*
- * If isset_offset is provided, store whether the reloption is
- * set there.
- */
- if (elems[j].isset_offset > 0)
- {
- char *setpos = ((char *) rdopts) + elems[j].isset_offset;
-
- *(bool *) setpos = options[i].isset;
- }
-
switch (options[i].gen->type)
{
case RELOPT_TYPE_BOOL:
@@ -1807,6 +1888,11 @@ fillRelOptions(void *rdopts, Size basesize,
options[i].values.bool_val :
((relopt_bool *) options[i].gen)->default_val;
break;
+ case RELOPT_TYPE_TERNARY:
+ *(pg_ternary *) itempos = options[i].isset ?
+ options[i].values.ternary_val :
+ ((relopt_ternary *) options[i].gen)->default_val;
+ break;
case RELOPT_TYPE_INT:
*(int *) itempos = options[i].isset ?
options[i].values.int_val :
@@ -1923,8 +2009,8 @@ default_reloptions(Datum reloptions, bool validate, relopt_kind kind)
offsetof(StdRdOptions, parallel_workers)},
{"vacuum_index_cleanup", RELOPT_TYPE_ENUM,
offsetof(StdRdOptions, vacuum_index_cleanup)},
- {"vacuum_truncate", RELOPT_TYPE_BOOL,
- offsetof(StdRdOptions, vacuum_truncate), offsetof(StdRdOptions, vacuum_truncate_set)},
+ {"vacuum_truncate", RELOPT_TYPE_TERNARY,
+ offsetof(StdRdOptions, vacuum_truncate)},
{"vacuum_max_eager_freeze_failure_rate", RELOPT_TYPE_REAL,
offsetof(StdRdOptions, vacuum_max_eager_freeze_failure_rate)}
};
@@ -2004,7 +2090,6 @@ build_local_reloptions(local_relopts *relopts, Datum options, bool validate)
elems[i].optname = opt->option->name;
elems[i].opttype = opt->option->type;
elems[i].offset = opt->offset;
- elems[i].isset_offset = 0; /* not supported for local relopts yet */
i++;
}
diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c
index aa4fbec143f..696eab9bd97 100644
--- a/src/backend/commands/vacuum.c
+++ b/src/backend/commands/vacuum.c
@@ -2224,9 +2224,9 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams params,
{
StdRdOptions *opts = (StdRdOptions *) rel->rd_options;
- if (opts && opts->vacuum_truncate_set)
+ if (opts && opts->vacuum_truncate != TERNARY_UNSET)
{
- if (opts->vacuum_truncate)
+ if (opts->vacuum_truncate == TERNARY_TRUE)
params.truncate = VACOPTVALUE_ENABLED;
else
params.truncate = VACOPTVALUE_DISABLED;
diff --git a/src/include/access/reloptions.h b/src/include/access/reloptions.h
index 2f08e1b0cf0..dfbef2babf2 100644
--- a/src/include/access/reloptions.h
+++ b/src/include/access/reloptions.h
@@ -29,6 +29,7 @@
typedef enum relopt_type
{
RELOPT_TYPE_BOOL,
+ RELOPT_TYPE_TERNARY, /* on, off, unset */
RELOPT_TYPE_INT,
RELOPT_TYPE_REAL,
RELOPT_TYPE_ENUM,
@@ -80,6 +81,7 @@ typedef struct relopt_value
union
{
bool bool_val;
+ pg_ternary ternary_val;
int int_val;
double real_val;
int enum_val;
@@ -94,6 +96,12 @@ typedef struct relopt_bool
bool default_val;
} relopt_bool;
+typedef struct relopt_ternary
+{
+ relopt_gen gen;
+ int default_val;
+} relopt_ternary;
+
typedef struct relopt_int
{
relopt_gen gen;
@@ -152,19 +160,6 @@ typedef struct
const char *optname; /* option's name */
relopt_type opttype; /* option's datatype */
int offset; /* offset of field in result struct */
-
- /*
- * isset_offset is an optional offset of a field in the result struct that
- * stores whether the option is explicitly set for the relation or if it
- * just picked up the default value. In most cases, this can be
- * accomplished by giving the reloption a special out-of-range default
- * value (e.g., some integer reloptions use -2), but this isn't always
- * possible. For example, a Boolean reloption cannot be given an
- * out-of-range default, so we need another way to discover the source of
- * its value. This offset is only used if given a value greater than
- * zero.
- */
- int isset_offset;
} relopt_parse_elt;
/* Local reloption definition */
@@ -195,6 +190,9 @@ typedef struct local_relopts
extern relopt_kind add_reloption_kind(void);
extern void add_bool_reloption(bits32 kinds, const char *name, const char *desc,
bool default_val, LOCKMODE lockmode);
+extern void add_ternary_reloption(bits32 kinds, const char *name,
+ const char *desc, pg_ternary default_val,
+ LOCKMODE lockmode);
extern void add_int_reloption(bits32 kinds, const char *name, const char *desc,
int default_val, int min_val, int max_val,
LOCKMODE lockmode);
@@ -214,6 +212,9 @@ extern void register_reloptions_validator(local_relopts *relopts,
extern void add_local_bool_reloption(local_relopts *relopts, const char *name,
const char *desc, bool default_val,
int offset);
+extern void add_local_ternary_reloption(local_relopts *relopts,
+ const char *name, const char *desc,
+ pg_ternary default_val, int offset);
extern void add_local_int_reloption(local_relopts *relopts, const char *name,
const char *desc, int default_val,
int min_val, int max_val, int offset);
diff --git a/src/include/postgres.h b/src/include/postgres.h
index 7d93fbce709..47c88780776 100644
--- a/src/include/postgres.h
+++ b/src/include/postgres.h
@@ -543,6 +543,21 @@ Float8GetDatum(float8 X)
* ----------------------------------------------------------------
*/
+/*
+ * pg_ternary
+ * Boolean value with an extra "unset" value
+ *
+ * This enum can be used for values that want to distinguish between true,
+ * false, and unset.
+*/
+
+typedef enum pg_ternary
+{
+ TERNARY_FALSE = 0,
+ TERNARY_TRUE = 1,
+ TERNARY_UNSET = -1
+} pg_ternary;
+
/*
* NON_EXEC_STATIC: It's sometimes useful to define a variable or function
* that is normally static but extern when using EXEC_BACKEND (see
diff --git a/src/include/utils/rel.h b/src/include/utils/rel.h
index d03ab247788..5feb18a5373 100644
--- a/src/include/utils/rel.h
+++ b/src/include/utils/rel.h
@@ -347,8 +347,7 @@ typedef struct StdRdOptions
bool user_catalog_table; /* use as an additional catalog relation */
int parallel_workers; /* max number of parallel workers */
StdRdOptIndexCleanup vacuum_index_cleanup; /* controls index vacuuming */
- bool vacuum_truncate; /* enables vacuum to truncate a relation */
- bool vacuum_truncate_set; /* whether vacuum_truncate is set */
+ pg_ternary vacuum_truncate; /* enables vacuum to truncate a relation */
/*
* Fraction of pages in a relation that vacuum can eagerly scan and fail
diff --git a/src/test/regress/expected/reloptions.out b/src/test/regress/expected/reloptions.out
index 9de19b4e3f1..1c99f79ab01 100644
--- a/src/test/regress/expected/reloptions.out
+++ b/src/test/regress/expected/reloptions.out
@@ -98,6 +98,42 @@ SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass;
{fillfactor=13,autovacuum_enabled=false}
(1 row)
+-- Tests for future (FIXME) ternary options
+-- behave as boolean option: accept unassigned name and truncated value
+DROP TABLE reloptions_test;
+CREATE TABLE reloptions_test(i INT) WITH (vacuum_truncate);
+SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass;
+ reloptions
+------------------------
+ {vacuum_truncate=true}
+(1 row)
+
+DROP TABLE reloptions_test;
+CREATE TABLE reloptions_test(i INT) WITH (vacuum_truncate=FaLS);
+SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass;
+ reloptions
+------------------------
+ {vacuum_truncate=fals}
+(1 row)
+
+-- preferred "true" alias is used when storing
+DROP TABLE reloptions_test;
+CREATE TABLE reloptions_test(i INT) WITH (vacuum_index_cleanup=on);
+SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass;
+ reloptions
+---------------------------
+ {vacuum_index_cleanup=on}
+(1 row)
+
+-- custom "third" value is available
+DROP TABLE reloptions_test;
+CREATE TABLE reloptions_test(i INT) WITH (vacuum_index_cleanup=auto);
+SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass;
+ reloptions
+-----------------------------
+ {vacuum_index_cleanup=auto}
+(1 row)
+
-- Test vacuum_truncate option
DROP TABLE reloptions_test;
CREATE TEMP TABLE reloptions_test(i INT NOT NULL, j text)
diff --git a/src/test/regress/sql/reloptions.sql b/src/test/regress/sql/reloptions.sql
index 24fbe0b478d..f5980dafcbc 100644
--- a/src/test/regress/sql/reloptions.sql
+++ b/src/test/regress/sql/reloptions.sql
@@ -59,6 +59,27 @@ UPDATE pg_class
ALTER TABLE reloptions_test RESET (illegal_option);
SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass;
+-- Tests for future (FIXME) ternary options
+
+-- behave as boolean option: accept unassigned name and truncated value
+DROP TABLE reloptions_test;
+CREATE TABLE reloptions_test(i INT) WITH (vacuum_truncate);
+SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass;
+
+DROP TABLE reloptions_test;
+CREATE TABLE reloptions_test(i INT) WITH (vacuum_truncate=FaLS);
+SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass;
+
+-- preferred "true" alias is used when storing
+DROP TABLE reloptions_test;
+CREATE TABLE reloptions_test(i INT) WITH (vacuum_index_cleanup=on);
+SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass;
+
+-- custom "third" value is available
+DROP TABLE reloptions_test;
+CREATE TABLE reloptions_test(i INT) WITH (vacuum_index_cleanup=auto);
+SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass;
+
-- Test vacuum_truncate option
DROP TABLE reloptions_test;
diff --git a/src/tools/pgindent/typedefs.list b/src/tools/pgindent/typedefs.list
index 3f3a888fd0e..1c8610fd46c 100644
--- a/src/tools/pgindent/typedefs.list
+++ b/src/tools/pgindent/typedefs.list
@@ -3952,6 +3952,7 @@ pg_sha512_ctx
pg_snapshot
pg_special_case
pg_stack_base_t
+pg_ternary
pg_time_t
pg_time_usec_t
pg_tz
@@ -4079,6 +4080,7 @@ relopt_kind
relopt_parse_elt
relopt_real
relopt_string
+relopt_ternary
relopt_type
relopt_value
relopts_validator
--
2.47.3
--c36t3fndxhkzbl5z--
^ permalink raw reply [nested|flat] 868+ messages in thread
* [PATCH v3] Introduce ternary reloptions
@ 2026-01-16 15:04 Álvaro Herrera <[email protected]>
0 siblings, 0 replies; 868+ messages in thread
From: Álvaro Herrera @ 2026-01-16 15:04 UTC (permalink / raw)
Introduce ternary reloption as a replacement for current
`vacuum_truncate` implementation. Remove the `vacuum_truncate_set`
separate flag and use TERNARY_UNSET instead.
This could also be used for other options such as `vacuum_index_cleanup`
and `buffering`, but lets get the scaffolding in first.
Discussion: https://postgr.es/m/3474141.usfYGdeWWP@thinkpad-pgpro
---
src/backend/access/common/reloptions.c | 137 ++++++++++++++++++-----
src/backend/commands/vacuum.c | 4 +-
src/include/access/reloptions.h | 27 ++---
src/include/postgres.h | 15 +++
src/include/utils/rel.h | 3 +-
src/test/regress/expected/reloptions.out | 36 ++++++
src/test/regress/sql/reloptions.sql | 21 ++++
src/tools/pgindent/typedefs.list | 2 +
8 files changed, 202 insertions(+), 43 deletions(-)
diff --git a/src/backend/access/common/reloptions.c b/src/backend/access/common/reloptions.c
index 0b83f98ed5f..6f1f577581a 100644
--- a/src/backend/access/common/reloptions.c
+++ b/src/backend/access/common/reloptions.c
@@ -40,9 +40,9 @@
*
* To add an option:
*
- * (i) decide on a type (bool, integer, real, enum, string), name, default
- * value, upper and lower bounds (if applicable); for strings, consider a
- * validation routine.
+ * (i) decide on a type (bool, ternary, integer, real, enum, string), name,
+ * default value, upper and lower bounds (if applicable); for strings,
+ * consider a validation routine.
* (ii) add a record below (or use add_<type>_reloption).
* (iii) add it to the appropriate options struct (perhaps StdRdOptions)
* (iv) add it to the appropriate handling routine (perhaps
@@ -147,15 +147,6 @@ static relopt_bool boolRelOpts[] =
},
false
},
- {
- {
- "vacuum_truncate",
- "Enables vacuum to truncate empty pages at the end of this table",
- RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
- ShareUpdateExclusiveLock
- },
- true
- },
{
{
"deduplicate_items",
@@ -170,6 +161,25 @@ static relopt_bool boolRelOpts[] =
{{NULL}}
};
+static relopt_ternary ternaryRelOpts[] =
+{
+ {
+ {
+ "vacuum_truncate",
+ "Enables vacuum to truncate empty pages at the end of this table",
+ RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
+ ShareUpdateExclusiveLock
+ },
+ TERNARY_UNSET
+ },
+ /* list terminator */
+ {
+ {
+ NULL
+ }
+ }
+};
+
static relopt_int intRelOpts[] =
{
{
@@ -609,6 +619,13 @@ initialize_reloptions(void)
boolRelOpts[i].gen.lockmode));
j++;
}
+ for (i = 0; ternaryRelOpts[i].gen.name; i++)
+ {
+ Assert(DoLockModesConflict(ternaryRelOpts[i].gen.lockmode,
+ ternaryRelOpts[i].gen.lockmode));
+ j++;
+ }
+
for (i = 0; intRelOpts[i].gen.name; i++)
{
Assert(DoLockModesConflict(intRelOpts[i].gen.lockmode,
@@ -649,6 +666,14 @@ initialize_reloptions(void)
j++;
}
+ for (i = 0; ternaryRelOpts[i].gen.name; i++)
+ {
+ relOpts[j] = &ternaryRelOpts[i].gen;
+ relOpts[j]->type = RELOPT_TYPE_TERNARY;
+ relOpts[j]->namelen = strlen(relOpts[j]->name);
+ j++;
+ }
+
for (i = 0; intRelOpts[i].gen.name; i++)
{
relOpts[j] = &intRelOpts[i].gen;
@@ -809,6 +834,9 @@ allocate_reloption(bits32 kinds, int type, const char *name, const char *desc,
case RELOPT_TYPE_BOOL:
size = sizeof(relopt_bool);
break;
+ case RELOPT_TYPE_TERNARY:
+ size = sizeof(relopt_ternary);
+ break;
case RELOPT_TYPE_INT:
size = sizeof(relopt_int);
break;
@@ -892,6 +920,57 @@ add_local_bool_reloption(local_relopts *relopts, const char *name,
add_local_reloption(relopts, (relopt_gen *) newoption, offset);
}
+/*
+ * init_ternary_reloption
+ * Allocate and initialize a new ternary reloption
+ */
+static relopt_ternary *
+init_ternary_reloption(bits32 kinds, const char *name, const char *desc,
+ pg_ternary default_val, LOCKMODE lockmode)
+{
+ relopt_ternary *newoption;
+
+ newoption = (relopt_ternary *)
+ allocate_reloption(kinds, RELOPT_TYPE_TERNARY, name, desc, lockmode);
+ newoption->default_val = default_val;
+
+ return newoption;
+}
+
+/*
+ * add_ternary_reloption
+ * Add a new ternary reloption
+ */
+void
+add_ternary_reloption(bits32 kinds, const char *name, const char *desc,
+ pg_ternary default_val, LOCKMODE lockmode)
+{
+ relopt_ternary *newoption;
+
+ newoption =
+ init_ternary_reloption(kinds, name, desc, default_val, lockmode);
+
+ add_reloption((relopt_gen *) newoption);
+}
+
+/*
+ * add_local_ternary_reloption
+ * Add a new ternary local reloption
+ *
+ * 'offset' is offset of ternary-typed field.
+ */
+void
+add_local_ternary_reloption(local_relopts *relopts, const char *name,
+ const char *desc, pg_ternary default_val,
+ int offset)
+{
+ relopt_ternary *newoption;
+
+ newoption =
+ init_ternary_reloption(RELOPT_KIND_LOCAL, name, desc, default_val, 0);
+
+ add_local_reloption(relopts, (relopt_gen *) newoption, offset);
+}
/*
* init_real_reloption
@@ -1626,6 +1705,19 @@ parse_one_reloption(relopt_value *option, char *text_str, int text_len,
option->gen->name, value)));
}
break;
+ case RELOPT_TYPE_TERNARY:
+ {
+ bool b;
+
+ parsed = parse_bool(value, &b);
+ option->values.ternary_val = b ? TERNARY_TRUE : TERNARY_FALSE;
+ if (validate && !parsed)
+ ereport(ERROR,
+ (errcode(ERRCODE_INVALID_PARAMETER_VALUE),
+ errmsg("invalid value for ternary option \"%s\": %s",
+ option->gen->name, value)));
+ }
+ break;
case RELOPT_TYPE_INT:
{
relopt_int *optint = (relopt_int *) option->gen;
@@ -1789,17 +1881,6 @@ fillRelOptions(void *rdopts, Size basesize,
char *itempos = ((char *) rdopts) + elems[j].offset;
char *string_val;
- /*
- * If isset_offset is provided, store whether the reloption is
- * set there.
- */
- if (elems[j].isset_offset > 0)
- {
- char *setpos = ((char *) rdopts) + elems[j].isset_offset;
-
- *(bool *) setpos = options[i].isset;
- }
-
switch (options[i].gen->type)
{
case RELOPT_TYPE_BOOL:
@@ -1807,6 +1888,11 @@ fillRelOptions(void *rdopts, Size basesize,
options[i].values.bool_val :
((relopt_bool *) options[i].gen)->default_val;
break;
+ case RELOPT_TYPE_TERNARY:
+ *(pg_ternary *) itempos = options[i].isset ?
+ options[i].values.ternary_val :
+ ((relopt_ternary *) options[i].gen)->default_val;
+ break;
case RELOPT_TYPE_INT:
*(int *) itempos = options[i].isset ?
options[i].values.int_val :
@@ -1923,8 +2009,8 @@ default_reloptions(Datum reloptions, bool validate, relopt_kind kind)
offsetof(StdRdOptions, parallel_workers)},
{"vacuum_index_cleanup", RELOPT_TYPE_ENUM,
offsetof(StdRdOptions, vacuum_index_cleanup)},
- {"vacuum_truncate", RELOPT_TYPE_BOOL,
- offsetof(StdRdOptions, vacuum_truncate), offsetof(StdRdOptions, vacuum_truncate_set)},
+ {"vacuum_truncate", RELOPT_TYPE_TERNARY,
+ offsetof(StdRdOptions, vacuum_truncate)},
{"vacuum_max_eager_freeze_failure_rate", RELOPT_TYPE_REAL,
offsetof(StdRdOptions, vacuum_max_eager_freeze_failure_rate)}
};
@@ -2004,7 +2090,6 @@ build_local_reloptions(local_relopts *relopts, Datum options, bool validate)
elems[i].optname = opt->option->name;
elems[i].opttype = opt->option->type;
elems[i].offset = opt->offset;
- elems[i].isset_offset = 0; /* not supported for local relopts yet */
i++;
}
diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c
index aa4fbec143f..696eab9bd97 100644
--- a/src/backend/commands/vacuum.c
+++ b/src/backend/commands/vacuum.c
@@ -2224,9 +2224,9 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams params,
{
StdRdOptions *opts = (StdRdOptions *) rel->rd_options;
- if (opts && opts->vacuum_truncate_set)
+ if (opts && opts->vacuum_truncate != TERNARY_UNSET)
{
- if (opts->vacuum_truncate)
+ if (opts->vacuum_truncate == TERNARY_TRUE)
params.truncate = VACOPTVALUE_ENABLED;
else
params.truncate = VACOPTVALUE_DISABLED;
diff --git a/src/include/access/reloptions.h b/src/include/access/reloptions.h
index 2f08e1b0cf0..dfbef2babf2 100644
--- a/src/include/access/reloptions.h
+++ b/src/include/access/reloptions.h
@@ -29,6 +29,7 @@
typedef enum relopt_type
{
RELOPT_TYPE_BOOL,
+ RELOPT_TYPE_TERNARY, /* on, off, unset */
RELOPT_TYPE_INT,
RELOPT_TYPE_REAL,
RELOPT_TYPE_ENUM,
@@ -80,6 +81,7 @@ typedef struct relopt_value
union
{
bool bool_val;
+ pg_ternary ternary_val;
int int_val;
double real_val;
int enum_val;
@@ -94,6 +96,12 @@ typedef struct relopt_bool
bool default_val;
} relopt_bool;
+typedef struct relopt_ternary
+{
+ relopt_gen gen;
+ int default_val;
+} relopt_ternary;
+
typedef struct relopt_int
{
relopt_gen gen;
@@ -152,19 +160,6 @@ typedef struct
const char *optname; /* option's name */
relopt_type opttype; /* option's datatype */
int offset; /* offset of field in result struct */
-
- /*
- * isset_offset is an optional offset of a field in the result struct that
- * stores whether the option is explicitly set for the relation or if it
- * just picked up the default value. In most cases, this can be
- * accomplished by giving the reloption a special out-of-range default
- * value (e.g., some integer reloptions use -2), but this isn't always
- * possible. For example, a Boolean reloption cannot be given an
- * out-of-range default, so we need another way to discover the source of
- * its value. This offset is only used if given a value greater than
- * zero.
- */
- int isset_offset;
} relopt_parse_elt;
/* Local reloption definition */
@@ -195,6 +190,9 @@ typedef struct local_relopts
extern relopt_kind add_reloption_kind(void);
extern void add_bool_reloption(bits32 kinds, const char *name, const char *desc,
bool default_val, LOCKMODE lockmode);
+extern void add_ternary_reloption(bits32 kinds, const char *name,
+ const char *desc, pg_ternary default_val,
+ LOCKMODE lockmode);
extern void add_int_reloption(bits32 kinds, const char *name, const char *desc,
int default_val, int min_val, int max_val,
LOCKMODE lockmode);
@@ -214,6 +212,9 @@ extern void register_reloptions_validator(local_relopts *relopts,
extern void add_local_bool_reloption(local_relopts *relopts, const char *name,
const char *desc, bool default_val,
int offset);
+extern void add_local_ternary_reloption(local_relopts *relopts,
+ const char *name, const char *desc,
+ pg_ternary default_val, int offset);
extern void add_local_int_reloption(local_relopts *relopts, const char *name,
const char *desc, int default_val,
int min_val, int max_val, int offset);
diff --git a/src/include/postgres.h b/src/include/postgres.h
index 7d93fbce709..47c88780776 100644
--- a/src/include/postgres.h
+++ b/src/include/postgres.h
@@ -543,6 +543,21 @@ Float8GetDatum(float8 X)
* ----------------------------------------------------------------
*/
+/*
+ * pg_ternary
+ * Boolean value with an extra "unset" value
+ *
+ * This enum can be used for values that want to distinguish between true,
+ * false, and unset.
+*/
+
+typedef enum pg_ternary
+{
+ TERNARY_FALSE = 0,
+ TERNARY_TRUE = 1,
+ TERNARY_UNSET = -1
+} pg_ternary;
+
/*
* NON_EXEC_STATIC: It's sometimes useful to define a variable or function
* that is normally static but extern when using EXEC_BACKEND (see
diff --git a/src/include/utils/rel.h b/src/include/utils/rel.h
index d03ab247788..5feb18a5373 100644
--- a/src/include/utils/rel.h
+++ b/src/include/utils/rel.h
@@ -347,8 +347,7 @@ typedef struct StdRdOptions
bool user_catalog_table; /* use as an additional catalog relation */
int parallel_workers; /* max number of parallel workers */
StdRdOptIndexCleanup vacuum_index_cleanup; /* controls index vacuuming */
- bool vacuum_truncate; /* enables vacuum to truncate a relation */
- bool vacuum_truncate_set; /* whether vacuum_truncate is set */
+ pg_ternary vacuum_truncate; /* enables vacuum to truncate a relation */
/*
* Fraction of pages in a relation that vacuum can eagerly scan and fail
diff --git a/src/test/regress/expected/reloptions.out b/src/test/regress/expected/reloptions.out
index 9de19b4e3f1..1c99f79ab01 100644
--- a/src/test/regress/expected/reloptions.out
+++ b/src/test/regress/expected/reloptions.out
@@ -98,6 +98,42 @@ SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass;
{fillfactor=13,autovacuum_enabled=false}
(1 row)
+-- Tests for future (FIXME) ternary options
+-- behave as boolean option: accept unassigned name and truncated value
+DROP TABLE reloptions_test;
+CREATE TABLE reloptions_test(i INT) WITH (vacuum_truncate);
+SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass;
+ reloptions
+------------------------
+ {vacuum_truncate=true}
+(1 row)
+
+DROP TABLE reloptions_test;
+CREATE TABLE reloptions_test(i INT) WITH (vacuum_truncate=FaLS);
+SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass;
+ reloptions
+------------------------
+ {vacuum_truncate=fals}
+(1 row)
+
+-- preferred "true" alias is used when storing
+DROP TABLE reloptions_test;
+CREATE TABLE reloptions_test(i INT) WITH (vacuum_index_cleanup=on);
+SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass;
+ reloptions
+---------------------------
+ {vacuum_index_cleanup=on}
+(1 row)
+
+-- custom "third" value is available
+DROP TABLE reloptions_test;
+CREATE TABLE reloptions_test(i INT) WITH (vacuum_index_cleanup=auto);
+SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass;
+ reloptions
+-----------------------------
+ {vacuum_index_cleanup=auto}
+(1 row)
+
-- Test vacuum_truncate option
DROP TABLE reloptions_test;
CREATE TEMP TABLE reloptions_test(i INT NOT NULL, j text)
diff --git a/src/test/regress/sql/reloptions.sql b/src/test/regress/sql/reloptions.sql
index 24fbe0b478d..f5980dafcbc 100644
--- a/src/test/regress/sql/reloptions.sql
+++ b/src/test/regress/sql/reloptions.sql
@@ -59,6 +59,27 @@ UPDATE pg_class
ALTER TABLE reloptions_test RESET (illegal_option);
SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass;
+-- Tests for future (FIXME) ternary options
+
+-- behave as boolean option: accept unassigned name and truncated value
+DROP TABLE reloptions_test;
+CREATE TABLE reloptions_test(i INT) WITH (vacuum_truncate);
+SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass;
+
+DROP TABLE reloptions_test;
+CREATE TABLE reloptions_test(i INT) WITH (vacuum_truncate=FaLS);
+SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass;
+
+-- preferred "true" alias is used when storing
+DROP TABLE reloptions_test;
+CREATE TABLE reloptions_test(i INT) WITH (vacuum_index_cleanup=on);
+SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass;
+
+-- custom "third" value is available
+DROP TABLE reloptions_test;
+CREATE TABLE reloptions_test(i INT) WITH (vacuum_index_cleanup=auto);
+SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass;
+
-- Test vacuum_truncate option
DROP TABLE reloptions_test;
diff --git a/src/tools/pgindent/typedefs.list b/src/tools/pgindent/typedefs.list
index 3f3a888fd0e..1c8610fd46c 100644
--- a/src/tools/pgindent/typedefs.list
+++ b/src/tools/pgindent/typedefs.list
@@ -3952,6 +3952,7 @@ pg_sha512_ctx
pg_snapshot
pg_special_case
pg_stack_base_t
+pg_ternary
pg_time_t
pg_time_usec_t
pg_tz
@@ -4079,6 +4080,7 @@ relopt_kind
relopt_parse_elt
relopt_real
relopt_string
+relopt_ternary
relopt_type
relopt_value
relopts_validator
--
2.47.3
--c36t3fndxhkzbl5z--
^ permalink raw reply [nested|flat] 868+ messages in thread
* [PATCH v3] Introduce ternary reloptions
@ 2026-01-16 15:04 Álvaro Herrera <[email protected]>
0 siblings, 0 replies; 868+ messages in thread
From: Álvaro Herrera @ 2026-01-16 15:04 UTC (permalink / raw)
Introduce ternary reloption as a replacement for current
`vacuum_truncate` implementation. Remove the `vacuum_truncate_set`
separate flag and use TERNARY_UNSET instead.
This could also be used for other options such as `vacuum_index_cleanup`
and `buffering`, but lets get the scaffolding in first.
Discussion: https://postgr.es/m/3474141.usfYGdeWWP@thinkpad-pgpro
---
src/backend/access/common/reloptions.c | 137 ++++++++++++++++++-----
src/backend/commands/vacuum.c | 4 +-
src/include/access/reloptions.h | 27 ++---
src/include/postgres.h | 15 +++
src/include/utils/rel.h | 3 +-
src/test/regress/expected/reloptions.out | 36 ++++++
src/test/regress/sql/reloptions.sql | 21 ++++
src/tools/pgindent/typedefs.list | 2 +
8 files changed, 202 insertions(+), 43 deletions(-)
diff --git a/src/backend/access/common/reloptions.c b/src/backend/access/common/reloptions.c
index 0b83f98ed5f..6f1f577581a 100644
--- a/src/backend/access/common/reloptions.c
+++ b/src/backend/access/common/reloptions.c
@@ -40,9 +40,9 @@
*
* To add an option:
*
- * (i) decide on a type (bool, integer, real, enum, string), name, default
- * value, upper and lower bounds (if applicable); for strings, consider a
- * validation routine.
+ * (i) decide on a type (bool, ternary, integer, real, enum, string), name,
+ * default value, upper and lower bounds (if applicable); for strings,
+ * consider a validation routine.
* (ii) add a record below (or use add_<type>_reloption).
* (iii) add it to the appropriate options struct (perhaps StdRdOptions)
* (iv) add it to the appropriate handling routine (perhaps
@@ -147,15 +147,6 @@ static relopt_bool boolRelOpts[] =
},
false
},
- {
- {
- "vacuum_truncate",
- "Enables vacuum to truncate empty pages at the end of this table",
- RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
- ShareUpdateExclusiveLock
- },
- true
- },
{
{
"deduplicate_items",
@@ -170,6 +161,25 @@ static relopt_bool boolRelOpts[] =
{{NULL}}
};
+static relopt_ternary ternaryRelOpts[] =
+{
+ {
+ {
+ "vacuum_truncate",
+ "Enables vacuum to truncate empty pages at the end of this table",
+ RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
+ ShareUpdateExclusiveLock
+ },
+ TERNARY_UNSET
+ },
+ /* list terminator */
+ {
+ {
+ NULL
+ }
+ }
+};
+
static relopt_int intRelOpts[] =
{
{
@@ -609,6 +619,13 @@ initialize_reloptions(void)
boolRelOpts[i].gen.lockmode));
j++;
}
+ for (i = 0; ternaryRelOpts[i].gen.name; i++)
+ {
+ Assert(DoLockModesConflict(ternaryRelOpts[i].gen.lockmode,
+ ternaryRelOpts[i].gen.lockmode));
+ j++;
+ }
+
for (i = 0; intRelOpts[i].gen.name; i++)
{
Assert(DoLockModesConflict(intRelOpts[i].gen.lockmode,
@@ -649,6 +666,14 @@ initialize_reloptions(void)
j++;
}
+ for (i = 0; ternaryRelOpts[i].gen.name; i++)
+ {
+ relOpts[j] = &ternaryRelOpts[i].gen;
+ relOpts[j]->type = RELOPT_TYPE_TERNARY;
+ relOpts[j]->namelen = strlen(relOpts[j]->name);
+ j++;
+ }
+
for (i = 0; intRelOpts[i].gen.name; i++)
{
relOpts[j] = &intRelOpts[i].gen;
@@ -809,6 +834,9 @@ allocate_reloption(bits32 kinds, int type, const char *name, const char *desc,
case RELOPT_TYPE_BOOL:
size = sizeof(relopt_bool);
break;
+ case RELOPT_TYPE_TERNARY:
+ size = sizeof(relopt_ternary);
+ break;
case RELOPT_TYPE_INT:
size = sizeof(relopt_int);
break;
@@ -892,6 +920,57 @@ add_local_bool_reloption(local_relopts *relopts, const char *name,
add_local_reloption(relopts, (relopt_gen *) newoption, offset);
}
+/*
+ * init_ternary_reloption
+ * Allocate and initialize a new ternary reloption
+ */
+static relopt_ternary *
+init_ternary_reloption(bits32 kinds, const char *name, const char *desc,
+ pg_ternary default_val, LOCKMODE lockmode)
+{
+ relopt_ternary *newoption;
+
+ newoption = (relopt_ternary *)
+ allocate_reloption(kinds, RELOPT_TYPE_TERNARY, name, desc, lockmode);
+ newoption->default_val = default_val;
+
+ return newoption;
+}
+
+/*
+ * add_ternary_reloption
+ * Add a new ternary reloption
+ */
+void
+add_ternary_reloption(bits32 kinds, const char *name, const char *desc,
+ pg_ternary default_val, LOCKMODE lockmode)
+{
+ relopt_ternary *newoption;
+
+ newoption =
+ init_ternary_reloption(kinds, name, desc, default_val, lockmode);
+
+ add_reloption((relopt_gen *) newoption);
+}
+
+/*
+ * add_local_ternary_reloption
+ * Add a new ternary local reloption
+ *
+ * 'offset' is offset of ternary-typed field.
+ */
+void
+add_local_ternary_reloption(local_relopts *relopts, const char *name,
+ const char *desc, pg_ternary default_val,
+ int offset)
+{
+ relopt_ternary *newoption;
+
+ newoption =
+ init_ternary_reloption(RELOPT_KIND_LOCAL, name, desc, default_val, 0);
+
+ add_local_reloption(relopts, (relopt_gen *) newoption, offset);
+}
/*
* init_real_reloption
@@ -1626,6 +1705,19 @@ parse_one_reloption(relopt_value *option, char *text_str, int text_len,
option->gen->name, value)));
}
break;
+ case RELOPT_TYPE_TERNARY:
+ {
+ bool b;
+
+ parsed = parse_bool(value, &b);
+ option->values.ternary_val = b ? TERNARY_TRUE : TERNARY_FALSE;
+ if (validate && !parsed)
+ ereport(ERROR,
+ (errcode(ERRCODE_INVALID_PARAMETER_VALUE),
+ errmsg("invalid value for ternary option \"%s\": %s",
+ option->gen->name, value)));
+ }
+ break;
case RELOPT_TYPE_INT:
{
relopt_int *optint = (relopt_int *) option->gen;
@@ -1789,17 +1881,6 @@ fillRelOptions(void *rdopts, Size basesize,
char *itempos = ((char *) rdopts) + elems[j].offset;
char *string_val;
- /*
- * If isset_offset is provided, store whether the reloption is
- * set there.
- */
- if (elems[j].isset_offset > 0)
- {
- char *setpos = ((char *) rdopts) + elems[j].isset_offset;
-
- *(bool *) setpos = options[i].isset;
- }
-
switch (options[i].gen->type)
{
case RELOPT_TYPE_BOOL:
@@ -1807,6 +1888,11 @@ fillRelOptions(void *rdopts, Size basesize,
options[i].values.bool_val :
((relopt_bool *) options[i].gen)->default_val;
break;
+ case RELOPT_TYPE_TERNARY:
+ *(pg_ternary *) itempos = options[i].isset ?
+ options[i].values.ternary_val :
+ ((relopt_ternary *) options[i].gen)->default_val;
+ break;
case RELOPT_TYPE_INT:
*(int *) itempos = options[i].isset ?
options[i].values.int_val :
@@ -1923,8 +2009,8 @@ default_reloptions(Datum reloptions, bool validate, relopt_kind kind)
offsetof(StdRdOptions, parallel_workers)},
{"vacuum_index_cleanup", RELOPT_TYPE_ENUM,
offsetof(StdRdOptions, vacuum_index_cleanup)},
- {"vacuum_truncate", RELOPT_TYPE_BOOL,
- offsetof(StdRdOptions, vacuum_truncate), offsetof(StdRdOptions, vacuum_truncate_set)},
+ {"vacuum_truncate", RELOPT_TYPE_TERNARY,
+ offsetof(StdRdOptions, vacuum_truncate)},
{"vacuum_max_eager_freeze_failure_rate", RELOPT_TYPE_REAL,
offsetof(StdRdOptions, vacuum_max_eager_freeze_failure_rate)}
};
@@ -2004,7 +2090,6 @@ build_local_reloptions(local_relopts *relopts, Datum options, bool validate)
elems[i].optname = opt->option->name;
elems[i].opttype = opt->option->type;
elems[i].offset = opt->offset;
- elems[i].isset_offset = 0; /* not supported for local relopts yet */
i++;
}
diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c
index aa4fbec143f..696eab9bd97 100644
--- a/src/backend/commands/vacuum.c
+++ b/src/backend/commands/vacuum.c
@@ -2224,9 +2224,9 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams params,
{
StdRdOptions *opts = (StdRdOptions *) rel->rd_options;
- if (opts && opts->vacuum_truncate_set)
+ if (opts && opts->vacuum_truncate != TERNARY_UNSET)
{
- if (opts->vacuum_truncate)
+ if (opts->vacuum_truncate == TERNARY_TRUE)
params.truncate = VACOPTVALUE_ENABLED;
else
params.truncate = VACOPTVALUE_DISABLED;
diff --git a/src/include/access/reloptions.h b/src/include/access/reloptions.h
index 2f08e1b0cf0..dfbef2babf2 100644
--- a/src/include/access/reloptions.h
+++ b/src/include/access/reloptions.h
@@ -29,6 +29,7 @@
typedef enum relopt_type
{
RELOPT_TYPE_BOOL,
+ RELOPT_TYPE_TERNARY, /* on, off, unset */
RELOPT_TYPE_INT,
RELOPT_TYPE_REAL,
RELOPT_TYPE_ENUM,
@@ -80,6 +81,7 @@ typedef struct relopt_value
union
{
bool bool_val;
+ pg_ternary ternary_val;
int int_val;
double real_val;
int enum_val;
@@ -94,6 +96,12 @@ typedef struct relopt_bool
bool default_val;
} relopt_bool;
+typedef struct relopt_ternary
+{
+ relopt_gen gen;
+ int default_val;
+} relopt_ternary;
+
typedef struct relopt_int
{
relopt_gen gen;
@@ -152,19 +160,6 @@ typedef struct
const char *optname; /* option's name */
relopt_type opttype; /* option's datatype */
int offset; /* offset of field in result struct */
-
- /*
- * isset_offset is an optional offset of a field in the result struct that
- * stores whether the option is explicitly set for the relation or if it
- * just picked up the default value. In most cases, this can be
- * accomplished by giving the reloption a special out-of-range default
- * value (e.g., some integer reloptions use -2), but this isn't always
- * possible. For example, a Boolean reloption cannot be given an
- * out-of-range default, so we need another way to discover the source of
- * its value. This offset is only used if given a value greater than
- * zero.
- */
- int isset_offset;
} relopt_parse_elt;
/* Local reloption definition */
@@ -195,6 +190,9 @@ typedef struct local_relopts
extern relopt_kind add_reloption_kind(void);
extern void add_bool_reloption(bits32 kinds, const char *name, const char *desc,
bool default_val, LOCKMODE lockmode);
+extern void add_ternary_reloption(bits32 kinds, const char *name,
+ const char *desc, pg_ternary default_val,
+ LOCKMODE lockmode);
extern void add_int_reloption(bits32 kinds, const char *name, const char *desc,
int default_val, int min_val, int max_val,
LOCKMODE lockmode);
@@ -214,6 +212,9 @@ extern void register_reloptions_validator(local_relopts *relopts,
extern void add_local_bool_reloption(local_relopts *relopts, const char *name,
const char *desc, bool default_val,
int offset);
+extern void add_local_ternary_reloption(local_relopts *relopts,
+ const char *name, const char *desc,
+ pg_ternary default_val, int offset);
extern void add_local_int_reloption(local_relopts *relopts, const char *name,
const char *desc, int default_val,
int min_val, int max_val, int offset);
diff --git a/src/include/postgres.h b/src/include/postgres.h
index 7d93fbce709..47c88780776 100644
--- a/src/include/postgres.h
+++ b/src/include/postgres.h
@@ -543,6 +543,21 @@ Float8GetDatum(float8 X)
* ----------------------------------------------------------------
*/
+/*
+ * pg_ternary
+ * Boolean value with an extra "unset" value
+ *
+ * This enum can be used for values that want to distinguish between true,
+ * false, and unset.
+*/
+
+typedef enum pg_ternary
+{
+ TERNARY_FALSE = 0,
+ TERNARY_TRUE = 1,
+ TERNARY_UNSET = -1
+} pg_ternary;
+
/*
* NON_EXEC_STATIC: It's sometimes useful to define a variable or function
* that is normally static but extern when using EXEC_BACKEND (see
diff --git a/src/include/utils/rel.h b/src/include/utils/rel.h
index d03ab247788..5feb18a5373 100644
--- a/src/include/utils/rel.h
+++ b/src/include/utils/rel.h
@@ -347,8 +347,7 @@ typedef struct StdRdOptions
bool user_catalog_table; /* use as an additional catalog relation */
int parallel_workers; /* max number of parallel workers */
StdRdOptIndexCleanup vacuum_index_cleanup; /* controls index vacuuming */
- bool vacuum_truncate; /* enables vacuum to truncate a relation */
- bool vacuum_truncate_set; /* whether vacuum_truncate is set */
+ pg_ternary vacuum_truncate; /* enables vacuum to truncate a relation */
/*
* Fraction of pages in a relation that vacuum can eagerly scan and fail
diff --git a/src/test/regress/expected/reloptions.out b/src/test/regress/expected/reloptions.out
index 9de19b4e3f1..1c99f79ab01 100644
--- a/src/test/regress/expected/reloptions.out
+++ b/src/test/regress/expected/reloptions.out
@@ -98,6 +98,42 @@ SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass;
{fillfactor=13,autovacuum_enabled=false}
(1 row)
+-- Tests for future (FIXME) ternary options
+-- behave as boolean option: accept unassigned name and truncated value
+DROP TABLE reloptions_test;
+CREATE TABLE reloptions_test(i INT) WITH (vacuum_truncate);
+SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass;
+ reloptions
+------------------------
+ {vacuum_truncate=true}
+(1 row)
+
+DROP TABLE reloptions_test;
+CREATE TABLE reloptions_test(i INT) WITH (vacuum_truncate=FaLS);
+SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass;
+ reloptions
+------------------------
+ {vacuum_truncate=fals}
+(1 row)
+
+-- preferred "true" alias is used when storing
+DROP TABLE reloptions_test;
+CREATE TABLE reloptions_test(i INT) WITH (vacuum_index_cleanup=on);
+SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass;
+ reloptions
+---------------------------
+ {vacuum_index_cleanup=on}
+(1 row)
+
+-- custom "third" value is available
+DROP TABLE reloptions_test;
+CREATE TABLE reloptions_test(i INT) WITH (vacuum_index_cleanup=auto);
+SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass;
+ reloptions
+-----------------------------
+ {vacuum_index_cleanup=auto}
+(1 row)
+
-- Test vacuum_truncate option
DROP TABLE reloptions_test;
CREATE TEMP TABLE reloptions_test(i INT NOT NULL, j text)
diff --git a/src/test/regress/sql/reloptions.sql b/src/test/regress/sql/reloptions.sql
index 24fbe0b478d..f5980dafcbc 100644
--- a/src/test/regress/sql/reloptions.sql
+++ b/src/test/regress/sql/reloptions.sql
@@ -59,6 +59,27 @@ UPDATE pg_class
ALTER TABLE reloptions_test RESET (illegal_option);
SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass;
+-- Tests for future (FIXME) ternary options
+
+-- behave as boolean option: accept unassigned name and truncated value
+DROP TABLE reloptions_test;
+CREATE TABLE reloptions_test(i INT) WITH (vacuum_truncate);
+SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass;
+
+DROP TABLE reloptions_test;
+CREATE TABLE reloptions_test(i INT) WITH (vacuum_truncate=FaLS);
+SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass;
+
+-- preferred "true" alias is used when storing
+DROP TABLE reloptions_test;
+CREATE TABLE reloptions_test(i INT) WITH (vacuum_index_cleanup=on);
+SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass;
+
+-- custom "third" value is available
+DROP TABLE reloptions_test;
+CREATE TABLE reloptions_test(i INT) WITH (vacuum_index_cleanup=auto);
+SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass;
+
-- Test vacuum_truncate option
DROP TABLE reloptions_test;
diff --git a/src/tools/pgindent/typedefs.list b/src/tools/pgindent/typedefs.list
index 3f3a888fd0e..1c8610fd46c 100644
--- a/src/tools/pgindent/typedefs.list
+++ b/src/tools/pgindent/typedefs.list
@@ -3952,6 +3952,7 @@ pg_sha512_ctx
pg_snapshot
pg_special_case
pg_stack_base_t
+pg_ternary
pg_time_t
pg_time_usec_t
pg_tz
@@ -4079,6 +4080,7 @@ relopt_kind
relopt_parse_elt
relopt_real
relopt_string
+relopt_ternary
relopt_type
relopt_value
relopts_validator
--
2.47.3
--c36t3fndxhkzbl5z--
^ permalink raw reply [nested|flat] 868+ messages in thread
* [PATCH v3] Introduce ternary reloptions
@ 2026-01-16 15:04 Álvaro Herrera <[email protected]>
0 siblings, 0 replies; 868+ messages in thread
From: Álvaro Herrera @ 2026-01-16 15:04 UTC (permalink / raw)
Introduce ternary reloption as a replacement for current
`vacuum_truncate` implementation. Remove the `vacuum_truncate_set`
separate flag and use TERNARY_UNSET instead.
This could also be used for other options such as `vacuum_index_cleanup`
and `buffering`, but lets get the scaffolding in first.
Discussion: https://postgr.es/m/3474141.usfYGdeWWP@thinkpad-pgpro
---
src/backend/access/common/reloptions.c | 137 ++++++++++++++++++-----
src/backend/commands/vacuum.c | 4 +-
src/include/access/reloptions.h | 27 ++---
src/include/postgres.h | 15 +++
src/include/utils/rel.h | 3 +-
src/test/regress/expected/reloptions.out | 36 ++++++
src/test/regress/sql/reloptions.sql | 21 ++++
src/tools/pgindent/typedefs.list | 2 +
8 files changed, 202 insertions(+), 43 deletions(-)
diff --git a/src/backend/access/common/reloptions.c b/src/backend/access/common/reloptions.c
index 0b83f98ed5f..6f1f577581a 100644
--- a/src/backend/access/common/reloptions.c
+++ b/src/backend/access/common/reloptions.c
@@ -40,9 +40,9 @@
*
* To add an option:
*
- * (i) decide on a type (bool, integer, real, enum, string), name, default
- * value, upper and lower bounds (if applicable); for strings, consider a
- * validation routine.
+ * (i) decide on a type (bool, ternary, integer, real, enum, string), name,
+ * default value, upper and lower bounds (if applicable); for strings,
+ * consider a validation routine.
* (ii) add a record below (or use add_<type>_reloption).
* (iii) add it to the appropriate options struct (perhaps StdRdOptions)
* (iv) add it to the appropriate handling routine (perhaps
@@ -147,15 +147,6 @@ static relopt_bool boolRelOpts[] =
},
false
},
- {
- {
- "vacuum_truncate",
- "Enables vacuum to truncate empty pages at the end of this table",
- RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
- ShareUpdateExclusiveLock
- },
- true
- },
{
{
"deduplicate_items",
@@ -170,6 +161,25 @@ static relopt_bool boolRelOpts[] =
{{NULL}}
};
+static relopt_ternary ternaryRelOpts[] =
+{
+ {
+ {
+ "vacuum_truncate",
+ "Enables vacuum to truncate empty pages at the end of this table",
+ RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
+ ShareUpdateExclusiveLock
+ },
+ TERNARY_UNSET
+ },
+ /* list terminator */
+ {
+ {
+ NULL
+ }
+ }
+};
+
static relopt_int intRelOpts[] =
{
{
@@ -609,6 +619,13 @@ initialize_reloptions(void)
boolRelOpts[i].gen.lockmode));
j++;
}
+ for (i = 0; ternaryRelOpts[i].gen.name; i++)
+ {
+ Assert(DoLockModesConflict(ternaryRelOpts[i].gen.lockmode,
+ ternaryRelOpts[i].gen.lockmode));
+ j++;
+ }
+
for (i = 0; intRelOpts[i].gen.name; i++)
{
Assert(DoLockModesConflict(intRelOpts[i].gen.lockmode,
@@ -649,6 +666,14 @@ initialize_reloptions(void)
j++;
}
+ for (i = 0; ternaryRelOpts[i].gen.name; i++)
+ {
+ relOpts[j] = &ternaryRelOpts[i].gen;
+ relOpts[j]->type = RELOPT_TYPE_TERNARY;
+ relOpts[j]->namelen = strlen(relOpts[j]->name);
+ j++;
+ }
+
for (i = 0; intRelOpts[i].gen.name; i++)
{
relOpts[j] = &intRelOpts[i].gen;
@@ -809,6 +834,9 @@ allocate_reloption(bits32 kinds, int type, const char *name, const char *desc,
case RELOPT_TYPE_BOOL:
size = sizeof(relopt_bool);
break;
+ case RELOPT_TYPE_TERNARY:
+ size = sizeof(relopt_ternary);
+ break;
case RELOPT_TYPE_INT:
size = sizeof(relopt_int);
break;
@@ -892,6 +920,57 @@ add_local_bool_reloption(local_relopts *relopts, const char *name,
add_local_reloption(relopts, (relopt_gen *) newoption, offset);
}
+/*
+ * init_ternary_reloption
+ * Allocate and initialize a new ternary reloption
+ */
+static relopt_ternary *
+init_ternary_reloption(bits32 kinds, const char *name, const char *desc,
+ pg_ternary default_val, LOCKMODE lockmode)
+{
+ relopt_ternary *newoption;
+
+ newoption = (relopt_ternary *)
+ allocate_reloption(kinds, RELOPT_TYPE_TERNARY, name, desc, lockmode);
+ newoption->default_val = default_val;
+
+ return newoption;
+}
+
+/*
+ * add_ternary_reloption
+ * Add a new ternary reloption
+ */
+void
+add_ternary_reloption(bits32 kinds, const char *name, const char *desc,
+ pg_ternary default_val, LOCKMODE lockmode)
+{
+ relopt_ternary *newoption;
+
+ newoption =
+ init_ternary_reloption(kinds, name, desc, default_val, lockmode);
+
+ add_reloption((relopt_gen *) newoption);
+}
+
+/*
+ * add_local_ternary_reloption
+ * Add a new ternary local reloption
+ *
+ * 'offset' is offset of ternary-typed field.
+ */
+void
+add_local_ternary_reloption(local_relopts *relopts, const char *name,
+ const char *desc, pg_ternary default_val,
+ int offset)
+{
+ relopt_ternary *newoption;
+
+ newoption =
+ init_ternary_reloption(RELOPT_KIND_LOCAL, name, desc, default_val, 0);
+
+ add_local_reloption(relopts, (relopt_gen *) newoption, offset);
+}
/*
* init_real_reloption
@@ -1626,6 +1705,19 @@ parse_one_reloption(relopt_value *option, char *text_str, int text_len,
option->gen->name, value)));
}
break;
+ case RELOPT_TYPE_TERNARY:
+ {
+ bool b;
+
+ parsed = parse_bool(value, &b);
+ option->values.ternary_val = b ? TERNARY_TRUE : TERNARY_FALSE;
+ if (validate && !parsed)
+ ereport(ERROR,
+ (errcode(ERRCODE_INVALID_PARAMETER_VALUE),
+ errmsg("invalid value for ternary option \"%s\": %s",
+ option->gen->name, value)));
+ }
+ break;
case RELOPT_TYPE_INT:
{
relopt_int *optint = (relopt_int *) option->gen;
@@ -1789,17 +1881,6 @@ fillRelOptions(void *rdopts, Size basesize,
char *itempos = ((char *) rdopts) + elems[j].offset;
char *string_val;
- /*
- * If isset_offset is provided, store whether the reloption is
- * set there.
- */
- if (elems[j].isset_offset > 0)
- {
- char *setpos = ((char *) rdopts) + elems[j].isset_offset;
-
- *(bool *) setpos = options[i].isset;
- }
-
switch (options[i].gen->type)
{
case RELOPT_TYPE_BOOL:
@@ -1807,6 +1888,11 @@ fillRelOptions(void *rdopts, Size basesize,
options[i].values.bool_val :
((relopt_bool *) options[i].gen)->default_val;
break;
+ case RELOPT_TYPE_TERNARY:
+ *(pg_ternary *) itempos = options[i].isset ?
+ options[i].values.ternary_val :
+ ((relopt_ternary *) options[i].gen)->default_val;
+ break;
case RELOPT_TYPE_INT:
*(int *) itempos = options[i].isset ?
options[i].values.int_val :
@@ -1923,8 +2009,8 @@ default_reloptions(Datum reloptions, bool validate, relopt_kind kind)
offsetof(StdRdOptions, parallel_workers)},
{"vacuum_index_cleanup", RELOPT_TYPE_ENUM,
offsetof(StdRdOptions, vacuum_index_cleanup)},
- {"vacuum_truncate", RELOPT_TYPE_BOOL,
- offsetof(StdRdOptions, vacuum_truncate), offsetof(StdRdOptions, vacuum_truncate_set)},
+ {"vacuum_truncate", RELOPT_TYPE_TERNARY,
+ offsetof(StdRdOptions, vacuum_truncate)},
{"vacuum_max_eager_freeze_failure_rate", RELOPT_TYPE_REAL,
offsetof(StdRdOptions, vacuum_max_eager_freeze_failure_rate)}
};
@@ -2004,7 +2090,6 @@ build_local_reloptions(local_relopts *relopts, Datum options, bool validate)
elems[i].optname = opt->option->name;
elems[i].opttype = opt->option->type;
elems[i].offset = opt->offset;
- elems[i].isset_offset = 0; /* not supported for local relopts yet */
i++;
}
diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c
index aa4fbec143f..696eab9bd97 100644
--- a/src/backend/commands/vacuum.c
+++ b/src/backend/commands/vacuum.c
@@ -2224,9 +2224,9 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams params,
{
StdRdOptions *opts = (StdRdOptions *) rel->rd_options;
- if (opts && opts->vacuum_truncate_set)
+ if (opts && opts->vacuum_truncate != TERNARY_UNSET)
{
- if (opts->vacuum_truncate)
+ if (opts->vacuum_truncate == TERNARY_TRUE)
params.truncate = VACOPTVALUE_ENABLED;
else
params.truncate = VACOPTVALUE_DISABLED;
diff --git a/src/include/access/reloptions.h b/src/include/access/reloptions.h
index 2f08e1b0cf0..dfbef2babf2 100644
--- a/src/include/access/reloptions.h
+++ b/src/include/access/reloptions.h
@@ -29,6 +29,7 @@
typedef enum relopt_type
{
RELOPT_TYPE_BOOL,
+ RELOPT_TYPE_TERNARY, /* on, off, unset */
RELOPT_TYPE_INT,
RELOPT_TYPE_REAL,
RELOPT_TYPE_ENUM,
@@ -80,6 +81,7 @@ typedef struct relopt_value
union
{
bool bool_val;
+ pg_ternary ternary_val;
int int_val;
double real_val;
int enum_val;
@@ -94,6 +96,12 @@ typedef struct relopt_bool
bool default_val;
} relopt_bool;
+typedef struct relopt_ternary
+{
+ relopt_gen gen;
+ int default_val;
+} relopt_ternary;
+
typedef struct relopt_int
{
relopt_gen gen;
@@ -152,19 +160,6 @@ typedef struct
const char *optname; /* option's name */
relopt_type opttype; /* option's datatype */
int offset; /* offset of field in result struct */
-
- /*
- * isset_offset is an optional offset of a field in the result struct that
- * stores whether the option is explicitly set for the relation or if it
- * just picked up the default value. In most cases, this can be
- * accomplished by giving the reloption a special out-of-range default
- * value (e.g., some integer reloptions use -2), but this isn't always
- * possible. For example, a Boolean reloption cannot be given an
- * out-of-range default, so we need another way to discover the source of
- * its value. This offset is only used if given a value greater than
- * zero.
- */
- int isset_offset;
} relopt_parse_elt;
/* Local reloption definition */
@@ -195,6 +190,9 @@ typedef struct local_relopts
extern relopt_kind add_reloption_kind(void);
extern void add_bool_reloption(bits32 kinds, const char *name, const char *desc,
bool default_val, LOCKMODE lockmode);
+extern void add_ternary_reloption(bits32 kinds, const char *name,
+ const char *desc, pg_ternary default_val,
+ LOCKMODE lockmode);
extern void add_int_reloption(bits32 kinds, const char *name, const char *desc,
int default_val, int min_val, int max_val,
LOCKMODE lockmode);
@@ -214,6 +212,9 @@ extern void register_reloptions_validator(local_relopts *relopts,
extern void add_local_bool_reloption(local_relopts *relopts, const char *name,
const char *desc, bool default_val,
int offset);
+extern void add_local_ternary_reloption(local_relopts *relopts,
+ const char *name, const char *desc,
+ pg_ternary default_val, int offset);
extern void add_local_int_reloption(local_relopts *relopts, const char *name,
const char *desc, int default_val,
int min_val, int max_val, int offset);
diff --git a/src/include/postgres.h b/src/include/postgres.h
index 7d93fbce709..47c88780776 100644
--- a/src/include/postgres.h
+++ b/src/include/postgres.h
@@ -543,6 +543,21 @@ Float8GetDatum(float8 X)
* ----------------------------------------------------------------
*/
+/*
+ * pg_ternary
+ * Boolean value with an extra "unset" value
+ *
+ * This enum can be used for values that want to distinguish between true,
+ * false, and unset.
+*/
+
+typedef enum pg_ternary
+{
+ TERNARY_FALSE = 0,
+ TERNARY_TRUE = 1,
+ TERNARY_UNSET = -1
+} pg_ternary;
+
/*
* NON_EXEC_STATIC: It's sometimes useful to define a variable or function
* that is normally static but extern when using EXEC_BACKEND (see
diff --git a/src/include/utils/rel.h b/src/include/utils/rel.h
index d03ab247788..5feb18a5373 100644
--- a/src/include/utils/rel.h
+++ b/src/include/utils/rel.h
@@ -347,8 +347,7 @@ typedef struct StdRdOptions
bool user_catalog_table; /* use as an additional catalog relation */
int parallel_workers; /* max number of parallel workers */
StdRdOptIndexCleanup vacuum_index_cleanup; /* controls index vacuuming */
- bool vacuum_truncate; /* enables vacuum to truncate a relation */
- bool vacuum_truncate_set; /* whether vacuum_truncate is set */
+ pg_ternary vacuum_truncate; /* enables vacuum to truncate a relation */
/*
* Fraction of pages in a relation that vacuum can eagerly scan and fail
diff --git a/src/test/regress/expected/reloptions.out b/src/test/regress/expected/reloptions.out
index 9de19b4e3f1..1c99f79ab01 100644
--- a/src/test/regress/expected/reloptions.out
+++ b/src/test/regress/expected/reloptions.out
@@ -98,6 +98,42 @@ SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass;
{fillfactor=13,autovacuum_enabled=false}
(1 row)
+-- Tests for future (FIXME) ternary options
+-- behave as boolean option: accept unassigned name and truncated value
+DROP TABLE reloptions_test;
+CREATE TABLE reloptions_test(i INT) WITH (vacuum_truncate);
+SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass;
+ reloptions
+------------------------
+ {vacuum_truncate=true}
+(1 row)
+
+DROP TABLE reloptions_test;
+CREATE TABLE reloptions_test(i INT) WITH (vacuum_truncate=FaLS);
+SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass;
+ reloptions
+------------------------
+ {vacuum_truncate=fals}
+(1 row)
+
+-- preferred "true" alias is used when storing
+DROP TABLE reloptions_test;
+CREATE TABLE reloptions_test(i INT) WITH (vacuum_index_cleanup=on);
+SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass;
+ reloptions
+---------------------------
+ {vacuum_index_cleanup=on}
+(1 row)
+
+-- custom "third" value is available
+DROP TABLE reloptions_test;
+CREATE TABLE reloptions_test(i INT) WITH (vacuum_index_cleanup=auto);
+SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass;
+ reloptions
+-----------------------------
+ {vacuum_index_cleanup=auto}
+(1 row)
+
-- Test vacuum_truncate option
DROP TABLE reloptions_test;
CREATE TEMP TABLE reloptions_test(i INT NOT NULL, j text)
diff --git a/src/test/regress/sql/reloptions.sql b/src/test/regress/sql/reloptions.sql
index 24fbe0b478d..f5980dafcbc 100644
--- a/src/test/regress/sql/reloptions.sql
+++ b/src/test/regress/sql/reloptions.sql
@@ -59,6 +59,27 @@ UPDATE pg_class
ALTER TABLE reloptions_test RESET (illegal_option);
SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass;
+-- Tests for future (FIXME) ternary options
+
+-- behave as boolean option: accept unassigned name and truncated value
+DROP TABLE reloptions_test;
+CREATE TABLE reloptions_test(i INT) WITH (vacuum_truncate);
+SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass;
+
+DROP TABLE reloptions_test;
+CREATE TABLE reloptions_test(i INT) WITH (vacuum_truncate=FaLS);
+SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass;
+
+-- preferred "true" alias is used when storing
+DROP TABLE reloptions_test;
+CREATE TABLE reloptions_test(i INT) WITH (vacuum_index_cleanup=on);
+SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass;
+
+-- custom "third" value is available
+DROP TABLE reloptions_test;
+CREATE TABLE reloptions_test(i INT) WITH (vacuum_index_cleanup=auto);
+SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass;
+
-- Test vacuum_truncate option
DROP TABLE reloptions_test;
diff --git a/src/tools/pgindent/typedefs.list b/src/tools/pgindent/typedefs.list
index 3f3a888fd0e..1c8610fd46c 100644
--- a/src/tools/pgindent/typedefs.list
+++ b/src/tools/pgindent/typedefs.list
@@ -3952,6 +3952,7 @@ pg_sha512_ctx
pg_snapshot
pg_special_case
pg_stack_base_t
+pg_ternary
pg_time_t
pg_time_usec_t
pg_tz
@@ -4079,6 +4080,7 @@ relopt_kind
relopt_parse_elt
relopt_real
relopt_string
+relopt_ternary
relopt_type
relopt_value
relopts_validator
--
2.47.3
--c36t3fndxhkzbl5z--
^ permalink raw reply [nested|flat] 868+ messages in thread
* [PATCH v3] Introduce ternary reloptions
@ 2026-01-16 15:04 Álvaro Herrera <[email protected]>
0 siblings, 0 replies; 868+ messages in thread
From: Álvaro Herrera @ 2026-01-16 15:04 UTC (permalink / raw)
Introduce ternary reloption as a replacement for current
`vacuum_truncate` implementation. Remove the `vacuum_truncate_set`
separate flag and use TERNARY_UNSET instead.
This could also be used for other options such as `vacuum_index_cleanup`
and `buffering`, but lets get the scaffolding in first.
Discussion: https://postgr.es/m/3474141.usfYGdeWWP@thinkpad-pgpro
---
src/backend/access/common/reloptions.c | 137 ++++++++++++++++++-----
src/backend/commands/vacuum.c | 4 +-
src/include/access/reloptions.h | 27 ++---
src/include/postgres.h | 15 +++
src/include/utils/rel.h | 3 +-
src/test/regress/expected/reloptions.out | 36 ++++++
src/test/regress/sql/reloptions.sql | 21 ++++
src/tools/pgindent/typedefs.list | 2 +
8 files changed, 202 insertions(+), 43 deletions(-)
diff --git a/src/backend/access/common/reloptions.c b/src/backend/access/common/reloptions.c
index 0b83f98ed5f..6f1f577581a 100644
--- a/src/backend/access/common/reloptions.c
+++ b/src/backend/access/common/reloptions.c
@@ -40,9 +40,9 @@
*
* To add an option:
*
- * (i) decide on a type (bool, integer, real, enum, string), name, default
- * value, upper and lower bounds (if applicable); for strings, consider a
- * validation routine.
+ * (i) decide on a type (bool, ternary, integer, real, enum, string), name,
+ * default value, upper and lower bounds (if applicable); for strings,
+ * consider a validation routine.
* (ii) add a record below (or use add_<type>_reloption).
* (iii) add it to the appropriate options struct (perhaps StdRdOptions)
* (iv) add it to the appropriate handling routine (perhaps
@@ -147,15 +147,6 @@ static relopt_bool boolRelOpts[] =
},
false
},
- {
- {
- "vacuum_truncate",
- "Enables vacuum to truncate empty pages at the end of this table",
- RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
- ShareUpdateExclusiveLock
- },
- true
- },
{
{
"deduplicate_items",
@@ -170,6 +161,25 @@ static relopt_bool boolRelOpts[] =
{{NULL}}
};
+static relopt_ternary ternaryRelOpts[] =
+{
+ {
+ {
+ "vacuum_truncate",
+ "Enables vacuum to truncate empty pages at the end of this table",
+ RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
+ ShareUpdateExclusiveLock
+ },
+ TERNARY_UNSET
+ },
+ /* list terminator */
+ {
+ {
+ NULL
+ }
+ }
+};
+
static relopt_int intRelOpts[] =
{
{
@@ -609,6 +619,13 @@ initialize_reloptions(void)
boolRelOpts[i].gen.lockmode));
j++;
}
+ for (i = 0; ternaryRelOpts[i].gen.name; i++)
+ {
+ Assert(DoLockModesConflict(ternaryRelOpts[i].gen.lockmode,
+ ternaryRelOpts[i].gen.lockmode));
+ j++;
+ }
+
for (i = 0; intRelOpts[i].gen.name; i++)
{
Assert(DoLockModesConflict(intRelOpts[i].gen.lockmode,
@@ -649,6 +666,14 @@ initialize_reloptions(void)
j++;
}
+ for (i = 0; ternaryRelOpts[i].gen.name; i++)
+ {
+ relOpts[j] = &ternaryRelOpts[i].gen;
+ relOpts[j]->type = RELOPT_TYPE_TERNARY;
+ relOpts[j]->namelen = strlen(relOpts[j]->name);
+ j++;
+ }
+
for (i = 0; intRelOpts[i].gen.name; i++)
{
relOpts[j] = &intRelOpts[i].gen;
@@ -809,6 +834,9 @@ allocate_reloption(bits32 kinds, int type, const char *name, const char *desc,
case RELOPT_TYPE_BOOL:
size = sizeof(relopt_bool);
break;
+ case RELOPT_TYPE_TERNARY:
+ size = sizeof(relopt_ternary);
+ break;
case RELOPT_TYPE_INT:
size = sizeof(relopt_int);
break;
@@ -892,6 +920,57 @@ add_local_bool_reloption(local_relopts *relopts, const char *name,
add_local_reloption(relopts, (relopt_gen *) newoption, offset);
}
+/*
+ * init_ternary_reloption
+ * Allocate and initialize a new ternary reloption
+ */
+static relopt_ternary *
+init_ternary_reloption(bits32 kinds, const char *name, const char *desc,
+ pg_ternary default_val, LOCKMODE lockmode)
+{
+ relopt_ternary *newoption;
+
+ newoption = (relopt_ternary *)
+ allocate_reloption(kinds, RELOPT_TYPE_TERNARY, name, desc, lockmode);
+ newoption->default_val = default_val;
+
+ return newoption;
+}
+
+/*
+ * add_ternary_reloption
+ * Add a new ternary reloption
+ */
+void
+add_ternary_reloption(bits32 kinds, const char *name, const char *desc,
+ pg_ternary default_val, LOCKMODE lockmode)
+{
+ relopt_ternary *newoption;
+
+ newoption =
+ init_ternary_reloption(kinds, name, desc, default_val, lockmode);
+
+ add_reloption((relopt_gen *) newoption);
+}
+
+/*
+ * add_local_ternary_reloption
+ * Add a new ternary local reloption
+ *
+ * 'offset' is offset of ternary-typed field.
+ */
+void
+add_local_ternary_reloption(local_relopts *relopts, const char *name,
+ const char *desc, pg_ternary default_val,
+ int offset)
+{
+ relopt_ternary *newoption;
+
+ newoption =
+ init_ternary_reloption(RELOPT_KIND_LOCAL, name, desc, default_val, 0);
+
+ add_local_reloption(relopts, (relopt_gen *) newoption, offset);
+}
/*
* init_real_reloption
@@ -1626,6 +1705,19 @@ parse_one_reloption(relopt_value *option, char *text_str, int text_len,
option->gen->name, value)));
}
break;
+ case RELOPT_TYPE_TERNARY:
+ {
+ bool b;
+
+ parsed = parse_bool(value, &b);
+ option->values.ternary_val = b ? TERNARY_TRUE : TERNARY_FALSE;
+ if (validate && !parsed)
+ ereport(ERROR,
+ (errcode(ERRCODE_INVALID_PARAMETER_VALUE),
+ errmsg("invalid value for ternary option \"%s\": %s",
+ option->gen->name, value)));
+ }
+ break;
case RELOPT_TYPE_INT:
{
relopt_int *optint = (relopt_int *) option->gen;
@@ -1789,17 +1881,6 @@ fillRelOptions(void *rdopts, Size basesize,
char *itempos = ((char *) rdopts) + elems[j].offset;
char *string_val;
- /*
- * If isset_offset is provided, store whether the reloption is
- * set there.
- */
- if (elems[j].isset_offset > 0)
- {
- char *setpos = ((char *) rdopts) + elems[j].isset_offset;
-
- *(bool *) setpos = options[i].isset;
- }
-
switch (options[i].gen->type)
{
case RELOPT_TYPE_BOOL:
@@ -1807,6 +1888,11 @@ fillRelOptions(void *rdopts, Size basesize,
options[i].values.bool_val :
((relopt_bool *) options[i].gen)->default_val;
break;
+ case RELOPT_TYPE_TERNARY:
+ *(pg_ternary *) itempos = options[i].isset ?
+ options[i].values.ternary_val :
+ ((relopt_ternary *) options[i].gen)->default_val;
+ break;
case RELOPT_TYPE_INT:
*(int *) itempos = options[i].isset ?
options[i].values.int_val :
@@ -1923,8 +2009,8 @@ default_reloptions(Datum reloptions, bool validate, relopt_kind kind)
offsetof(StdRdOptions, parallel_workers)},
{"vacuum_index_cleanup", RELOPT_TYPE_ENUM,
offsetof(StdRdOptions, vacuum_index_cleanup)},
- {"vacuum_truncate", RELOPT_TYPE_BOOL,
- offsetof(StdRdOptions, vacuum_truncate), offsetof(StdRdOptions, vacuum_truncate_set)},
+ {"vacuum_truncate", RELOPT_TYPE_TERNARY,
+ offsetof(StdRdOptions, vacuum_truncate)},
{"vacuum_max_eager_freeze_failure_rate", RELOPT_TYPE_REAL,
offsetof(StdRdOptions, vacuum_max_eager_freeze_failure_rate)}
};
@@ -2004,7 +2090,6 @@ build_local_reloptions(local_relopts *relopts, Datum options, bool validate)
elems[i].optname = opt->option->name;
elems[i].opttype = opt->option->type;
elems[i].offset = opt->offset;
- elems[i].isset_offset = 0; /* not supported for local relopts yet */
i++;
}
diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c
index aa4fbec143f..696eab9bd97 100644
--- a/src/backend/commands/vacuum.c
+++ b/src/backend/commands/vacuum.c
@@ -2224,9 +2224,9 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams params,
{
StdRdOptions *opts = (StdRdOptions *) rel->rd_options;
- if (opts && opts->vacuum_truncate_set)
+ if (opts && opts->vacuum_truncate != TERNARY_UNSET)
{
- if (opts->vacuum_truncate)
+ if (opts->vacuum_truncate == TERNARY_TRUE)
params.truncate = VACOPTVALUE_ENABLED;
else
params.truncate = VACOPTVALUE_DISABLED;
diff --git a/src/include/access/reloptions.h b/src/include/access/reloptions.h
index 2f08e1b0cf0..dfbef2babf2 100644
--- a/src/include/access/reloptions.h
+++ b/src/include/access/reloptions.h
@@ -29,6 +29,7 @@
typedef enum relopt_type
{
RELOPT_TYPE_BOOL,
+ RELOPT_TYPE_TERNARY, /* on, off, unset */
RELOPT_TYPE_INT,
RELOPT_TYPE_REAL,
RELOPT_TYPE_ENUM,
@@ -80,6 +81,7 @@ typedef struct relopt_value
union
{
bool bool_val;
+ pg_ternary ternary_val;
int int_val;
double real_val;
int enum_val;
@@ -94,6 +96,12 @@ typedef struct relopt_bool
bool default_val;
} relopt_bool;
+typedef struct relopt_ternary
+{
+ relopt_gen gen;
+ int default_val;
+} relopt_ternary;
+
typedef struct relopt_int
{
relopt_gen gen;
@@ -152,19 +160,6 @@ typedef struct
const char *optname; /* option's name */
relopt_type opttype; /* option's datatype */
int offset; /* offset of field in result struct */
-
- /*
- * isset_offset is an optional offset of a field in the result struct that
- * stores whether the option is explicitly set for the relation or if it
- * just picked up the default value. In most cases, this can be
- * accomplished by giving the reloption a special out-of-range default
- * value (e.g., some integer reloptions use -2), but this isn't always
- * possible. For example, a Boolean reloption cannot be given an
- * out-of-range default, so we need another way to discover the source of
- * its value. This offset is only used if given a value greater than
- * zero.
- */
- int isset_offset;
} relopt_parse_elt;
/* Local reloption definition */
@@ -195,6 +190,9 @@ typedef struct local_relopts
extern relopt_kind add_reloption_kind(void);
extern void add_bool_reloption(bits32 kinds, const char *name, const char *desc,
bool default_val, LOCKMODE lockmode);
+extern void add_ternary_reloption(bits32 kinds, const char *name,
+ const char *desc, pg_ternary default_val,
+ LOCKMODE lockmode);
extern void add_int_reloption(bits32 kinds, const char *name, const char *desc,
int default_val, int min_val, int max_val,
LOCKMODE lockmode);
@@ -214,6 +212,9 @@ extern void register_reloptions_validator(local_relopts *relopts,
extern void add_local_bool_reloption(local_relopts *relopts, const char *name,
const char *desc, bool default_val,
int offset);
+extern void add_local_ternary_reloption(local_relopts *relopts,
+ const char *name, const char *desc,
+ pg_ternary default_val, int offset);
extern void add_local_int_reloption(local_relopts *relopts, const char *name,
const char *desc, int default_val,
int min_val, int max_val, int offset);
diff --git a/src/include/postgres.h b/src/include/postgres.h
index 7d93fbce709..47c88780776 100644
--- a/src/include/postgres.h
+++ b/src/include/postgres.h
@@ -543,6 +543,21 @@ Float8GetDatum(float8 X)
* ----------------------------------------------------------------
*/
+/*
+ * pg_ternary
+ * Boolean value with an extra "unset" value
+ *
+ * This enum can be used for values that want to distinguish between true,
+ * false, and unset.
+*/
+
+typedef enum pg_ternary
+{
+ TERNARY_FALSE = 0,
+ TERNARY_TRUE = 1,
+ TERNARY_UNSET = -1
+} pg_ternary;
+
/*
* NON_EXEC_STATIC: It's sometimes useful to define a variable or function
* that is normally static but extern when using EXEC_BACKEND (see
diff --git a/src/include/utils/rel.h b/src/include/utils/rel.h
index d03ab247788..5feb18a5373 100644
--- a/src/include/utils/rel.h
+++ b/src/include/utils/rel.h
@@ -347,8 +347,7 @@ typedef struct StdRdOptions
bool user_catalog_table; /* use as an additional catalog relation */
int parallel_workers; /* max number of parallel workers */
StdRdOptIndexCleanup vacuum_index_cleanup; /* controls index vacuuming */
- bool vacuum_truncate; /* enables vacuum to truncate a relation */
- bool vacuum_truncate_set; /* whether vacuum_truncate is set */
+ pg_ternary vacuum_truncate; /* enables vacuum to truncate a relation */
/*
* Fraction of pages in a relation that vacuum can eagerly scan and fail
diff --git a/src/test/regress/expected/reloptions.out b/src/test/regress/expected/reloptions.out
index 9de19b4e3f1..1c99f79ab01 100644
--- a/src/test/regress/expected/reloptions.out
+++ b/src/test/regress/expected/reloptions.out
@@ -98,6 +98,42 @@ SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass;
{fillfactor=13,autovacuum_enabled=false}
(1 row)
+-- Tests for future (FIXME) ternary options
+-- behave as boolean option: accept unassigned name and truncated value
+DROP TABLE reloptions_test;
+CREATE TABLE reloptions_test(i INT) WITH (vacuum_truncate);
+SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass;
+ reloptions
+------------------------
+ {vacuum_truncate=true}
+(1 row)
+
+DROP TABLE reloptions_test;
+CREATE TABLE reloptions_test(i INT) WITH (vacuum_truncate=FaLS);
+SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass;
+ reloptions
+------------------------
+ {vacuum_truncate=fals}
+(1 row)
+
+-- preferred "true" alias is used when storing
+DROP TABLE reloptions_test;
+CREATE TABLE reloptions_test(i INT) WITH (vacuum_index_cleanup=on);
+SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass;
+ reloptions
+---------------------------
+ {vacuum_index_cleanup=on}
+(1 row)
+
+-- custom "third" value is available
+DROP TABLE reloptions_test;
+CREATE TABLE reloptions_test(i INT) WITH (vacuum_index_cleanup=auto);
+SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass;
+ reloptions
+-----------------------------
+ {vacuum_index_cleanup=auto}
+(1 row)
+
-- Test vacuum_truncate option
DROP TABLE reloptions_test;
CREATE TEMP TABLE reloptions_test(i INT NOT NULL, j text)
diff --git a/src/test/regress/sql/reloptions.sql b/src/test/regress/sql/reloptions.sql
index 24fbe0b478d..f5980dafcbc 100644
--- a/src/test/regress/sql/reloptions.sql
+++ b/src/test/regress/sql/reloptions.sql
@@ -59,6 +59,27 @@ UPDATE pg_class
ALTER TABLE reloptions_test RESET (illegal_option);
SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass;
+-- Tests for future (FIXME) ternary options
+
+-- behave as boolean option: accept unassigned name and truncated value
+DROP TABLE reloptions_test;
+CREATE TABLE reloptions_test(i INT) WITH (vacuum_truncate);
+SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass;
+
+DROP TABLE reloptions_test;
+CREATE TABLE reloptions_test(i INT) WITH (vacuum_truncate=FaLS);
+SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass;
+
+-- preferred "true" alias is used when storing
+DROP TABLE reloptions_test;
+CREATE TABLE reloptions_test(i INT) WITH (vacuum_index_cleanup=on);
+SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass;
+
+-- custom "third" value is available
+DROP TABLE reloptions_test;
+CREATE TABLE reloptions_test(i INT) WITH (vacuum_index_cleanup=auto);
+SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass;
+
-- Test vacuum_truncate option
DROP TABLE reloptions_test;
diff --git a/src/tools/pgindent/typedefs.list b/src/tools/pgindent/typedefs.list
index 3f3a888fd0e..1c8610fd46c 100644
--- a/src/tools/pgindent/typedefs.list
+++ b/src/tools/pgindent/typedefs.list
@@ -3952,6 +3952,7 @@ pg_sha512_ctx
pg_snapshot
pg_special_case
pg_stack_base_t
+pg_ternary
pg_time_t
pg_time_usec_t
pg_tz
@@ -4079,6 +4080,7 @@ relopt_kind
relopt_parse_elt
relopt_real
relopt_string
+relopt_ternary
relopt_type
relopt_value
relopts_validator
--
2.47.3
--c36t3fndxhkzbl5z--
^ permalink raw reply [nested|flat] 868+ messages in thread
* [PATCH v3] Introduce ternary reloptions
@ 2026-01-16 15:04 Álvaro Herrera <[email protected]>
0 siblings, 0 replies; 868+ messages in thread
From: Álvaro Herrera @ 2026-01-16 15:04 UTC (permalink / raw)
Introduce ternary reloption as a replacement for current
`vacuum_truncate` implementation. Remove the `vacuum_truncate_set`
separate flag and use TERNARY_UNSET instead.
This could also be used for other options such as `vacuum_index_cleanup`
and `buffering`, but lets get the scaffolding in first.
Discussion: https://postgr.es/m/3474141.usfYGdeWWP@thinkpad-pgpro
---
src/backend/access/common/reloptions.c | 137 ++++++++++++++++++-----
src/backend/commands/vacuum.c | 4 +-
src/include/access/reloptions.h | 27 ++---
src/include/postgres.h | 15 +++
src/include/utils/rel.h | 3 +-
src/test/regress/expected/reloptions.out | 36 ++++++
src/test/regress/sql/reloptions.sql | 21 ++++
src/tools/pgindent/typedefs.list | 2 +
8 files changed, 202 insertions(+), 43 deletions(-)
diff --git a/src/backend/access/common/reloptions.c b/src/backend/access/common/reloptions.c
index 0b83f98ed5f..6f1f577581a 100644
--- a/src/backend/access/common/reloptions.c
+++ b/src/backend/access/common/reloptions.c
@@ -40,9 +40,9 @@
*
* To add an option:
*
- * (i) decide on a type (bool, integer, real, enum, string), name, default
- * value, upper and lower bounds (if applicable); for strings, consider a
- * validation routine.
+ * (i) decide on a type (bool, ternary, integer, real, enum, string), name,
+ * default value, upper and lower bounds (if applicable); for strings,
+ * consider a validation routine.
* (ii) add a record below (or use add_<type>_reloption).
* (iii) add it to the appropriate options struct (perhaps StdRdOptions)
* (iv) add it to the appropriate handling routine (perhaps
@@ -147,15 +147,6 @@ static relopt_bool boolRelOpts[] =
},
false
},
- {
- {
- "vacuum_truncate",
- "Enables vacuum to truncate empty pages at the end of this table",
- RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
- ShareUpdateExclusiveLock
- },
- true
- },
{
{
"deduplicate_items",
@@ -170,6 +161,25 @@ static relopt_bool boolRelOpts[] =
{{NULL}}
};
+static relopt_ternary ternaryRelOpts[] =
+{
+ {
+ {
+ "vacuum_truncate",
+ "Enables vacuum to truncate empty pages at the end of this table",
+ RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
+ ShareUpdateExclusiveLock
+ },
+ TERNARY_UNSET
+ },
+ /* list terminator */
+ {
+ {
+ NULL
+ }
+ }
+};
+
static relopt_int intRelOpts[] =
{
{
@@ -609,6 +619,13 @@ initialize_reloptions(void)
boolRelOpts[i].gen.lockmode));
j++;
}
+ for (i = 0; ternaryRelOpts[i].gen.name; i++)
+ {
+ Assert(DoLockModesConflict(ternaryRelOpts[i].gen.lockmode,
+ ternaryRelOpts[i].gen.lockmode));
+ j++;
+ }
+
for (i = 0; intRelOpts[i].gen.name; i++)
{
Assert(DoLockModesConflict(intRelOpts[i].gen.lockmode,
@@ -649,6 +666,14 @@ initialize_reloptions(void)
j++;
}
+ for (i = 0; ternaryRelOpts[i].gen.name; i++)
+ {
+ relOpts[j] = &ternaryRelOpts[i].gen;
+ relOpts[j]->type = RELOPT_TYPE_TERNARY;
+ relOpts[j]->namelen = strlen(relOpts[j]->name);
+ j++;
+ }
+
for (i = 0; intRelOpts[i].gen.name; i++)
{
relOpts[j] = &intRelOpts[i].gen;
@@ -809,6 +834,9 @@ allocate_reloption(bits32 kinds, int type, const char *name, const char *desc,
case RELOPT_TYPE_BOOL:
size = sizeof(relopt_bool);
break;
+ case RELOPT_TYPE_TERNARY:
+ size = sizeof(relopt_ternary);
+ break;
case RELOPT_TYPE_INT:
size = sizeof(relopt_int);
break;
@@ -892,6 +920,57 @@ add_local_bool_reloption(local_relopts *relopts, const char *name,
add_local_reloption(relopts, (relopt_gen *) newoption, offset);
}
+/*
+ * init_ternary_reloption
+ * Allocate and initialize a new ternary reloption
+ */
+static relopt_ternary *
+init_ternary_reloption(bits32 kinds, const char *name, const char *desc,
+ pg_ternary default_val, LOCKMODE lockmode)
+{
+ relopt_ternary *newoption;
+
+ newoption = (relopt_ternary *)
+ allocate_reloption(kinds, RELOPT_TYPE_TERNARY, name, desc, lockmode);
+ newoption->default_val = default_val;
+
+ return newoption;
+}
+
+/*
+ * add_ternary_reloption
+ * Add a new ternary reloption
+ */
+void
+add_ternary_reloption(bits32 kinds, const char *name, const char *desc,
+ pg_ternary default_val, LOCKMODE lockmode)
+{
+ relopt_ternary *newoption;
+
+ newoption =
+ init_ternary_reloption(kinds, name, desc, default_val, lockmode);
+
+ add_reloption((relopt_gen *) newoption);
+}
+
+/*
+ * add_local_ternary_reloption
+ * Add a new ternary local reloption
+ *
+ * 'offset' is offset of ternary-typed field.
+ */
+void
+add_local_ternary_reloption(local_relopts *relopts, const char *name,
+ const char *desc, pg_ternary default_val,
+ int offset)
+{
+ relopt_ternary *newoption;
+
+ newoption =
+ init_ternary_reloption(RELOPT_KIND_LOCAL, name, desc, default_val, 0);
+
+ add_local_reloption(relopts, (relopt_gen *) newoption, offset);
+}
/*
* init_real_reloption
@@ -1626,6 +1705,19 @@ parse_one_reloption(relopt_value *option, char *text_str, int text_len,
option->gen->name, value)));
}
break;
+ case RELOPT_TYPE_TERNARY:
+ {
+ bool b;
+
+ parsed = parse_bool(value, &b);
+ option->values.ternary_val = b ? TERNARY_TRUE : TERNARY_FALSE;
+ if (validate && !parsed)
+ ereport(ERROR,
+ (errcode(ERRCODE_INVALID_PARAMETER_VALUE),
+ errmsg("invalid value for ternary option \"%s\": %s",
+ option->gen->name, value)));
+ }
+ break;
case RELOPT_TYPE_INT:
{
relopt_int *optint = (relopt_int *) option->gen;
@@ -1789,17 +1881,6 @@ fillRelOptions(void *rdopts, Size basesize,
char *itempos = ((char *) rdopts) + elems[j].offset;
char *string_val;
- /*
- * If isset_offset is provided, store whether the reloption is
- * set there.
- */
- if (elems[j].isset_offset > 0)
- {
- char *setpos = ((char *) rdopts) + elems[j].isset_offset;
-
- *(bool *) setpos = options[i].isset;
- }
-
switch (options[i].gen->type)
{
case RELOPT_TYPE_BOOL:
@@ -1807,6 +1888,11 @@ fillRelOptions(void *rdopts, Size basesize,
options[i].values.bool_val :
((relopt_bool *) options[i].gen)->default_val;
break;
+ case RELOPT_TYPE_TERNARY:
+ *(pg_ternary *) itempos = options[i].isset ?
+ options[i].values.ternary_val :
+ ((relopt_ternary *) options[i].gen)->default_val;
+ break;
case RELOPT_TYPE_INT:
*(int *) itempos = options[i].isset ?
options[i].values.int_val :
@@ -1923,8 +2009,8 @@ default_reloptions(Datum reloptions, bool validate, relopt_kind kind)
offsetof(StdRdOptions, parallel_workers)},
{"vacuum_index_cleanup", RELOPT_TYPE_ENUM,
offsetof(StdRdOptions, vacuum_index_cleanup)},
- {"vacuum_truncate", RELOPT_TYPE_BOOL,
- offsetof(StdRdOptions, vacuum_truncate), offsetof(StdRdOptions, vacuum_truncate_set)},
+ {"vacuum_truncate", RELOPT_TYPE_TERNARY,
+ offsetof(StdRdOptions, vacuum_truncate)},
{"vacuum_max_eager_freeze_failure_rate", RELOPT_TYPE_REAL,
offsetof(StdRdOptions, vacuum_max_eager_freeze_failure_rate)}
};
@@ -2004,7 +2090,6 @@ build_local_reloptions(local_relopts *relopts, Datum options, bool validate)
elems[i].optname = opt->option->name;
elems[i].opttype = opt->option->type;
elems[i].offset = opt->offset;
- elems[i].isset_offset = 0; /* not supported for local relopts yet */
i++;
}
diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c
index aa4fbec143f..696eab9bd97 100644
--- a/src/backend/commands/vacuum.c
+++ b/src/backend/commands/vacuum.c
@@ -2224,9 +2224,9 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams params,
{
StdRdOptions *opts = (StdRdOptions *) rel->rd_options;
- if (opts && opts->vacuum_truncate_set)
+ if (opts && opts->vacuum_truncate != TERNARY_UNSET)
{
- if (opts->vacuum_truncate)
+ if (opts->vacuum_truncate == TERNARY_TRUE)
params.truncate = VACOPTVALUE_ENABLED;
else
params.truncate = VACOPTVALUE_DISABLED;
diff --git a/src/include/access/reloptions.h b/src/include/access/reloptions.h
index 2f08e1b0cf0..dfbef2babf2 100644
--- a/src/include/access/reloptions.h
+++ b/src/include/access/reloptions.h
@@ -29,6 +29,7 @@
typedef enum relopt_type
{
RELOPT_TYPE_BOOL,
+ RELOPT_TYPE_TERNARY, /* on, off, unset */
RELOPT_TYPE_INT,
RELOPT_TYPE_REAL,
RELOPT_TYPE_ENUM,
@@ -80,6 +81,7 @@ typedef struct relopt_value
union
{
bool bool_val;
+ pg_ternary ternary_val;
int int_val;
double real_val;
int enum_val;
@@ -94,6 +96,12 @@ typedef struct relopt_bool
bool default_val;
} relopt_bool;
+typedef struct relopt_ternary
+{
+ relopt_gen gen;
+ int default_val;
+} relopt_ternary;
+
typedef struct relopt_int
{
relopt_gen gen;
@@ -152,19 +160,6 @@ typedef struct
const char *optname; /* option's name */
relopt_type opttype; /* option's datatype */
int offset; /* offset of field in result struct */
-
- /*
- * isset_offset is an optional offset of a field in the result struct that
- * stores whether the option is explicitly set for the relation or if it
- * just picked up the default value. In most cases, this can be
- * accomplished by giving the reloption a special out-of-range default
- * value (e.g., some integer reloptions use -2), but this isn't always
- * possible. For example, a Boolean reloption cannot be given an
- * out-of-range default, so we need another way to discover the source of
- * its value. This offset is only used if given a value greater than
- * zero.
- */
- int isset_offset;
} relopt_parse_elt;
/* Local reloption definition */
@@ -195,6 +190,9 @@ typedef struct local_relopts
extern relopt_kind add_reloption_kind(void);
extern void add_bool_reloption(bits32 kinds, const char *name, const char *desc,
bool default_val, LOCKMODE lockmode);
+extern void add_ternary_reloption(bits32 kinds, const char *name,
+ const char *desc, pg_ternary default_val,
+ LOCKMODE lockmode);
extern void add_int_reloption(bits32 kinds, const char *name, const char *desc,
int default_val, int min_val, int max_val,
LOCKMODE lockmode);
@@ -214,6 +212,9 @@ extern void register_reloptions_validator(local_relopts *relopts,
extern void add_local_bool_reloption(local_relopts *relopts, const char *name,
const char *desc, bool default_val,
int offset);
+extern void add_local_ternary_reloption(local_relopts *relopts,
+ const char *name, const char *desc,
+ pg_ternary default_val, int offset);
extern void add_local_int_reloption(local_relopts *relopts, const char *name,
const char *desc, int default_val,
int min_val, int max_val, int offset);
diff --git a/src/include/postgres.h b/src/include/postgres.h
index 7d93fbce709..47c88780776 100644
--- a/src/include/postgres.h
+++ b/src/include/postgres.h
@@ -543,6 +543,21 @@ Float8GetDatum(float8 X)
* ----------------------------------------------------------------
*/
+/*
+ * pg_ternary
+ * Boolean value with an extra "unset" value
+ *
+ * This enum can be used for values that want to distinguish between true,
+ * false, and unset.
+*/
+
+typedef enum pg_ternary
+{
+ TERNARY_FALSE = 0,
+ TERNARY_TRUE = 1,
+ TERNARY_UNSET = -1
+} pg_ternary;
+
/*
* NON_EXEC_STATIC: It's sometimes useful to define a variable or function
* that is normally static but extern when using EXEC_BACKEND (see
diff --git a/src/include/utils/rel.h b/src/include/utils/rel.h
index d03ab247788..5feb18a5373 100644
--- a/src/include/utils/rel.h
+++ b/src/include/utils/rel.h
@@ -347,8 +347,7 @@ typedef struct StdRdOptions
bool user_catalog_table; /* use as an additional catalog relation */
int parallel_workers; /* max number of parallel workers */
StdRdOptIndexCleanup vacuum_index_cleanup; /* controls index vacuuming */
- bool vacuum_truncate; /* enables vacuum to truncate a relation */
- bool vacuum_truncate_set; /* whether vacuum_truncate is set */
+ pg_ternary vacuum_truncate; /* enables vacuum to truncate a relation */
/*
* Fraction of pages in a relation that vacuum can eagerly scan and fail
diff --git a/src/test/regress/expected/reloptions.out b/src/test/regress/expected/reloptions.out
index 9de19b4e3f1..1c99f79ab01 100644
--- a/src/test/regress/expected/reloptions.out
+++ b/src/test/regress/expected/reloptions.out
@@ -98,6 +98,42 @@ SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass;
{fillfactor=13,autovacuum_enabled=false}
(1 row)
+-- Tests for future (FIXME) ternary options
+-- behave as boolean option: accept unassigned name and truncated value
+DROP TABLE reloptions_test;
+CREATE TABLE reloptions_test(i INT) WITH (vacuum_truncate);
+SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass;
+ reloptions
+------------------------
+ {vacuum_truncate=true}
+(1 row)
+
+DROP TABLE reloptions_test;
+CREATE TABLE reloptions_test(i INT) WITH (vacuum_truncate=FaLS);
+SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass;
+ reloptions
+------------------------
+ {vacuum_truncate=fals}
+(1 row)
+
+-- preferred "true" alias is used when storing
+DROP TABLE reloptions_test;
+CREATE TABLE reloptions_test(i INT) WITH (vacuum_index_cleanup=on);
+SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass;
+ reloptions
+---------------------------
+ {vacuum_index_cleanup=on}
+(1 row)
+
+-- custom "third" value is available
+DROP TABLE reloptions_test;
+CREATE TABLE reloptions_test(i INT) WITH (vacuum_index_cleanup=auto);
+SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass;
+ reloptions
+-----------------------------
+ {vacuum_index_cleanup=auto}
+(1 row)
+
-- Test vacuum_truncate option
DROP TABLE reloptions_test;
CREATE TEMP TABLE reloptions_test(i INT NOT NULL, j text)
diff --git a/src/test/regress/sql/reloptions.sql b/src/test/regress/sql/reloptions.sql
index 24fbe0b478d..f5980dafcbc 100644
--- a/src/test/regress/sql/reloptions.sql
+++ b/src/test/regress/sql/reloptions.sql
@@ -59,6 +59,27 @@ UPDATE pg_class
ALTER TABLE reloptions_test RESET (illegal_option);
SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass;
+-- Tests for future (FIXME) ternary options
+
+-- behave as boolean option: accept unassigned name and truncated value
+DROP TABLE reloptions_test;
+CREATE TABLE reloptions_test(i INT) WITH (vacuum_truncate);
+SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass;
+
+DROP TABLE reloptions_test;
+CREATE TABLE reloptions_test(i INT) WITH (vacuum_truncate=FaLS);
+SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass;
+
+-- preferred "true" alias is used when storing
+DROP TABLE reloptions_test;
+CREATE TABLE reloptions_test(i INT) WITH (vacuum_index_cleanup=on);
+SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass;
+
+-- custom "third" value is available
+DROP TABLE reloptions_test;
+CREATE TABLE reloptions_test(i INT) WITH (vacuum_index_cleanup=auto);
+SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass;
+
-- Test vacuum_truncate option
DROP TABLE reloptions_test;
diff --git a/src/tools/pgindent/typedefs.list b/src/tools/pgindent/typedefs.list
index 3f3a888fd0e..1c8610fd46c 100644
--- a/src/tools/pgindent/typedefs.list
+++ b/src/tools/pgindent/typedefs.list
@@ -3952,6 +3952,7 @@ pg_sha512_ctx
pg_snapshot
pg_special_case
pg_stack_base_t
+pg_ternary
pg_time_t
pg_time_usec_t
pg_tz
@@ -4079,6 +4080,7 @@ relopt_kind
relopt_parse_elt
relopt_real
relopt_string
+relopt_ternary
relopt_type
relopt_value
relopts_validator
--
2.47.3
--c36t3fndxhkzbl5z--
^ permalink raw reply [nested|flat] 868+ messages in thread
* [PATCH v3] Introduce ternary reloptions
@ 2026-01-16 15:04 Álvaro Herrera <[email protected]>
0 siblings, 0 replies; 868+ messages in thread
From: Álvaro Herrera @ 2026-01-16 15:04 UTC (permalink / raw)
Introduce ternary reloption as a replacement for current
`vacuum_truncate` implementation. Remove the `vacuum_truncate_set`
separate flag and use TERNARY_UNSET instead.
This could also be used for other options such as `vacuum_index_cleanup`
and `buffering`, but lets get the scaffolding in first.
Discussion: https://postgr.es/m/3474141.usfYGdeWWP@thinkpad-pgpro
---
src/backend/access/common/reloptions.c | 137 ++++++++++++++++++-----
src/backend/commands/vacuum.c | 4 +-
src/include/access/reloptions.h | 27 ++---
src/include/postgres.h | 15 +++
src/include/utils/rel.h | 3 +-
src/test/regress/expected/reloptions.out | 36 ++++++
src/test/regress/sql/reloptions.sql | 21 ++++
src/tools/pgindent/typedefs.list | 2 +
8 files changed, 202 insertions(+), 43 deletions(-)
diff --git a/src/backend/access/common/reloptions.c b/src/backend/access/common/reloptions.c
index 0b83f98ed5f..6f1f577581a 100644
--- a/src/backend/access/common/reloptions.c
+++ b/src/backend/access/common/reloptions.c
@@ -40,9 +40,9 @@
*
* To add an option:
*
- * (i) decide on a type (bool, integer, real, enum, string), name, default
- * value, upper and lower bounds (if applicable); for strings, consider a
- * validation routine.
+ * (i) decide on a type (bool, ternary, integer, real, enum, string), name,
+ * default value, upper and lower bounds (if applicable); for strings,
+ * consider a validation routine.
* (ii) add a record below (or use add_<type>_reloption).
* (iii) add it to the appropriate options struct (perhaps StdRdOptions)
* (iv) add it to the appropriate handling routine (perhaps
@@ -147,15 +147,6 @@ static relopt_bool boolRelOpts[] =
},
false
},
- {
- {
- "vacuum_truncate",
- "Enables vacuum to truncate empty pages at the end of this table",
- RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
- ShareUpdateExclusiveLock
- },
- true
- },
{
{
"deduplicate_items",
@@ -170,6 +161,25 @@ static relopt_bool boolRelOpts[] =
{{NULL}}
};
+static relopt_ternary ternaryRelOpts[] =
+{
+ {
+ {
+ "vacuum_truncate",
+ "Enables vacuum to truncate empty pages at the end of this table",
+ RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
+ ShareUpdateExclusiveLock
+ },
+ TERNARY_UNSET
+ },
+ /* list terminator */
+ {
+ {
+ NULL
+ }
+ }
+};
+
static relopt_int intRelOpts[] =
{
{
@@ -609,6 +619,13 @@ initialize_reloptions(void)
boolRelOpts[i].gen.lockmode));
j++;
}
+ for (i = 0; ternaryRelOpts[i].gen.name; i++)
+ {
+ Assert(DoLockModesConflict(ternaryRelOpts[i].gen.lockmode,
+ ternaryRelOpts[i].gen.lockmode));
+ j++;
+ }
+
for (i = 0; intRelOpts[i].gen.name; i++)
{
Assert(DoLockModesConflict(intRelOpts[i].gen.lockmode,
@@ -649,6 +666,14 @@ initialize_reloptions(void)
j++;
}
+ for (i = 0; ternaryRelOpts[i].gen.name; i++)
+ {
+ relOpts[j] = &ternaryRelOpts[i].gen;
+ relOpts[j]->type = RELOPT_TYPE_TERNARY;
+ relOpts[j]->namelen = strlen(relOpts[j]->name);
+ j++;
+ }
+
for (i = 0; intRelOpts[i].gen.name; i++)
{
relOpts[j] = &intRelOpts[i].gen;
@@ -809,6 +834,9 @@ allocate_reloption(bits32 kinds, int type, const char *name, const char *desc,
case RELOPT_TYPE_BOOL:
size = sizeof(relopt_bool);
break;
+ case RELOPT_TYPE_TERNARY:
+ size = sizeof(relopt_ternary);
+ break;
case RELOPT_TYPE_INT:
size = sizeof(relopt_int);
break;
@@ -892,6 +920,57 @@ add_local_bool_reloption(local_relopts *relopts, const char *name,
add_local_reloption(relopts, (relopt_gen *) newoption, offset);
}
+/*
+ * init_ternary_reloption
+ * Allocate and initialize a new ternary reloption
+ */
+static relopt_ternary *
+init_ternary_reloption(bits32 kinds, const char *name, const char *desc,
+ pg_ternary default_val, LOCKMODE lockmode)
+{
+ relopt_ternary *newoption;
+
+ newoption = (relopt_ternary *)
+ allocate_reloption(kinds, RELOPT_TYPE_TERNARY, name, desc, lockmode);
+ newoption->default_val = default_val;
+
+ return newoption;
+}
+
+/*
+ * add_ternary_reloption
+ * Add a new ternary reloption
+ */
+void
+add_ternary_reloption(bits32 kinds, const char *name, const char *desc,
+ pg_ternary default_val, LOCKMODE lockmode)
+{
+ relopt_ternary *newoption;
+
+ newoption =
+ init_ternary_reloption(kinds, name, desc, default_val, lockmode);
+
+ add_reloption((relopt_gen *) newoption);
+}
+
+/*
+ * add_local_ternary_reloption
+ * Add a new ternary local reloption
+ *
+ * 'offset' is offset of ternary-typed field.
+ */
+void
+add_local_ternary_reloption(local_relopts *relopts, const char *name,
+ const char *desc, pg_ternary default_val,
+ int offset)
+{
+ relopt_ternary *newoption;
+
+ newoption =
+ init_ternary_reloption(RELOPT_KIND_LOCAL, name, desc, default_val, 0);
+
+ add_local_reloption(relopts, (relopt_gen *) newoption, offset);
+}
/*
* init_real_reloption
@@ -1626,6 +1705,19 @@ parse_one_reloption(relopt_value *option, char *text_str, int text_len,
option->gen->name, value)));
}
break;
+ case RELOPT_TYPE_TERNARY:
+ {
+ bool b;
+
+ parsed = parse_bool(value, &b);
+ option->values.ternary_val = b ? TERNARY_TRUE : TERNARY_FALSE;
+ if (validate && !parsed)
+ ereport(ERROR,
+ (errcode(ERRCODE_INVALID_PARAMETER_VALUE),
+ errmsg("invalid value for ternary option \"%s\": %s",
+ option->gen->name, value)));
+ }
+ break;
case RELOPT_TYPE_INT:
{
relopt_int *optint = (relopt_int *) option->gen;
@@ -1789,17 +1881,6 @@ fillRelOptions(void *rdopts, Size basesize,
char *itempos = ((char *) rdopts) + elems[j].offset;
char *string_val;
- /*
- * If isset_offset is provided, store whether the reloption is
- * set there.
- */
- if (elems[j].isset_offset > 0)
- {
- char *setpos = ((char *) rdopts) + elems[j].isset_offset;
-
- *(bool *) setpos = options[i].isset;
- }
-
switch (options[i].gen->type)
{
case RELOPT_TYPE_BOOL:
@@ -1807,6 +1888,11 @@ fillRelOptions(void *rdopts, Size basesize,
options[i].values.bool_val :
((relopt_bool *) options[i].gen)->default_val;
break;
+ case RELOPT_TYPE_TERNARY:
+ *(pg_ternary *) itempos = options[i].isset ?
+ options[i].values.ternary_val :
+ ((relopt_ternary *) options[i].gen)->default_val;
+ break;
case RELOPT_TYPE_INT:
*(int *) itempos = options[i].isset ?
options[i].values.int_val :
@@ -1923,8 +2009,8 @@ default_reloptions(Datum reloptions, bool validate, relopt_kind kind)
offsetof(StdRdOptions, parallel_workers)},
{"vacuum_index_cleanup", RELOPT_TYPE_ENUM,
offsetof(StdRdOptions, vacuum_index_cleanup)},
- {"vacuum_truncate", RELOPT_TYPE_BOOL,
- offsetof(StdRdOptions, vacuum_truncate), offsetof(StdRdOptions, vacuum_truncate_set)},
+ {"vacuum_truncate", RELOPT_TYPE_TERNARY,
+ offsetof(StdRdOptions, vacuum_truncate)},
{"vacuum_max_eager_freeze_failure_rate", RELOPT_TYPE_REAL,
offsetof(StdRdOptions, vacuum_max_eager_freeze_failure_rate)}
};
@@ -2004,7 +2090,6 @@ build_local_reloptions(local_relopts *relopts, Datum options, bool validate)
elems[i].optname = opt->option->name;
elems[i].opttype = opt->option->type;
elems[i].offset = opt->offset;
- elems[i].isset_offset = 0; /* not supported for local relopts yet */
i++;
}
diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c
index aa4fbec143f..696eab9bd97 100644
--- a/src/backend/commands/vacuum.c
+++ b/src/backend/commands/vacuum.c
@@ -2224,9 +2224,9 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams params,
{
StdRdOptions *opts = (StdRdOptions *) rel->rd_options;
- if (opts && opts->vacuum_truncate_set)
+ if (opts && opts->vacuum_truncate != TERNARY_UNSET)
{
- if (opts->vacuum_truncate)
+ if (opts->vacuum_truncate == TERNARY_TRUE)
params.truncate = VACOPTVALUE_ENABLED;
else
params.truncate = VACOPTVALUE_DISABLED;
diff --git a/src/include/access/reloptions.h b/src/include/access/reloptions.h
index 2f08e1b0cf0..dfbef2babf2 100644
--- a/src/include/access/reloptions.h
+++ b/src/include/access/reloptions.h
@@ -29,6 +29,7 @@
typedef enum relopt_type
{
RELOPT_TYPE_BOOL,
+ RELOPT_TYPE_TERNARY, /* on, off, unset */
RELOPT_TYPE_INT,
RELOPT_TYPE_REAL,
RELOPT_TYPE_ENUM,
@@ -80,6 +81,7 @@ typedef struct relopt_value
union
{
bool bool_val;
+ pg_ternary ternary_val;
int int_val;
double real_val;
int enum_val;
@@ -94,6 +96,12 @@ typedef struct relopt_bool
bool default_val;
} relopt_bool;
+typedef struct relopt_ternary
+{
+ relopt_gen gen;
+ int default_val;
+} relopt_ternary;
+
typedef struct relopt_int
{
relopt_gen gen;
@@ -152,19 +160,6 @@ typedef struct
const char *optname; /* option's name */
relopt_type opttype; /* option's datatype */
int offset; /* offset of field in result struct */
-
- /*
- * isset_offset is an optional offset of a field in the result struct that
- * stores whether the option is explicitly set for the relation or if it
- * just picked up the default value. In most cases, this can be
- * accomplished by giving the reloption a special out-of-range default
- * value (e.g., some integer reloptions use -2), but this isn't always
- * possible. For example, a Boolean reloption cannot be given an
- * out-of-range default, so we need another way to discover the source of
- * its value. This offset is only used if given a value greater than
- * zero.
- */
- int isset_offset;
} relopt_parse_elt;
/* Local reloption definition */
@@ -195,6 +190,9 @@ typedef struct local_relopts
extern relopt_kind add_reloption_kind(void);
extern void add_bool_reloption(bits32 kinds, const char *name, const char *desc,
bool default_val, LOCKMODE lockmode);
+extern void add_ternary_reloption(bits32 kinds, const char *name,
+ const char *desc, pg_ternary default_val,
+ LOCKMODE lockmode);
extern void add_int_reloption(bits32 kinds, const char *name, const char *desc,
int default_val, int min_val, int max_val,
LOCKMODE lockmode);
@@ -214,6 +212,9 @@ extern void register_reloptions_validator(local_relopts *relopts,
extern void add_local_bool_reloption(local_relopts *relopts, const char *name,
const char *desc, bool default_val,
int offset);
+extern void add_local_ternary_reloption(local_relopts *relopts,
+ const char *name, const char *desc,
+ pg_ternary default_val, int offset);
extern void add_local_int_reloption(local_relopts *relopts, const char *name,
const char *desc, int default_val,
int min_val, int max_val, int offset);
diff --git a/src/include/postgres.h b/src/include/postgres.h
index 7d93fbce709..47c88780776 100644
--- a/src/include/postgres.h
+++ b/src/include/postgres.h
@@ -543,6 +543,21 @@ Float8GetDatum(float8 X)
* ----------------------------------------------------------------
*/
+/*
+ * pg_ternary
+ * Boolean value with an extra "unset" value
+ *
+ * This enum can be used for values that want to distinguish between true,
+ * false, and unset.
+*/
+
+typedef enum pg_ternary
+{
+ TERNARY_FALSE = 0,
+ TERNARY_TRUE = 1,
+ TERNARY_UNSET = -1
+} pg_ternary;
+
/*
* NON_EXEC_STATIC: It's sometimes useful to define a variable or function
* that is normally static but extern when using EXEC_BACKEND (see
diff --git a/src/include/utils/rel.h b/src/include/utils/rel.h
index d03ab247788..5feb18a5373 100644
--- a/src/include/utils/rel.h
+++ b/src/include/utils/rel.h
@@ -347,8 +347,7 @@ typedef struct StdRdOptions
bool user_catalog_table; /* use as an additional catalog relation */
int parallel_workers; /* max number of parallel workers */
StdRdOptIndexCleanup vacuum_index_cleanup; /* controls index vacuuming */
- bool vacuum_truncate; /* enables vacuum to truncate a relation */
- bool vacuum_truncate_set; /* whether vacuum_truncate is set */
+ pg_ternary vacuum_truncate; /* enables vacuum to truncate a relation */
/*
* Fraction of pages in a relation that vacuum can eagerly scan and fail
diff --git a/src/test/regress/expected/reloptions.out b/src/test/regress/expected/reloptions.out
index 9de19b4e3f1..1c99f79ab01 100644
--- a/src/test/regress/expected/reloptions.out
+++ b/src/test/regress/expected/reloptions.out
@@ -98,6 +98,42 @@ SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass;
{fillfactor=13,autovacuum_enabled=false}
(1 row)
+-- Tests for future (FIXME) ternary options
+-- behave as boolean option: accept unassigned name and truncated value
+DROP TABLE reloptions_test;
+CREATE TABLE reloptions_test(i INT) WITH (vacuum_truncate);
+SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass;
+ reloptions
+------------------------
+ {vacuum_truncate=true}
+(1 row)
+
+DROP TABLE reloptions_test;
+CREATE TABLE reloptions_test(i INT) WITH (vacuum_truncate=FaLS);
+SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass;
+ reloptions
+------------------------
+ {vacuum_truncate=fals}
+(1 row)
+
+-- preferred "true" alias is used when storing
+DROP TABLE reloptions_test;
+CREATE TABLE reloptions_test(i INT) WITH (vacuum_index_cleanup=on);
+SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass;
+ reloptions
+---------------------------
+ {vacuum_index_cleanup=on}
+(1 row)
+
+-- custom "third" value is available
+DROP TABLE reloptions_test;
+CREATE TABLE reloptions_test(i INT) WITH (vacuum_index_cleanup=auto);
+SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass;
+ reloptions
+-----------------------------
+ {vacuum_index_cleanup=auto}
+(1 row)
+
-- Test vacuum_truncate option
DROP TABLE reloptions_test;
CREATE TEMP TABLE reloptions_test(i INT NOT NULL, j text)
diff --git a/src/test/regress/sql/reloptions.sql b/src/test/regress/sql/reloptions.sql
index 24fbe0b478d..f5980dafcbc 100644
--- a/src/test/regress/sql/reloptions.sql
+++ b/src/test/regress/sql/reloptions.sql
@@ -59,6 +59,27 @@ UPDATE pg_class
ALTER TABLE reloptions_test RESET (illegal_option);
SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass;
+-- Tests for future (FIXME) ternary options
+
+-- behave as boolean option: accept unassigned name and truncated value
+DROP TABLE reloptions_test;
+CREATE TABLE reloptions_test(i INT) WITH (vacuum_truncate);
+SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass;
+
+DROP TABLE reloptions_test;
+CREATE TABLE reloptions_test(i INT) WITH (vacuum_truncate=FaLS);
+SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass;
+
+-- preferred "true" alias is used when storing
+DROP TABLE reloptions_test;
+CREATE TABLE reloptions_test(i INT) WITH (vacuum_index_cleanup=on);
+SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass;
+
+-- custom "third" value is available
+DROP TABLE reloptions_test;
+CREATE TABLE reloptions_test(i INT) WITH (vacuum_index_cleanup=auto);
+SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass;
+
-- Test vacuum_truncate option
DROP TABLE reloptions_test;
diff --git a/src/tools/pgindent/typedefs.list b/src/tools/pgindent/typedefs.list
index 3f3a888fd0e..1c8610fd46c 100644
--- a/src/tools/pgindent/typedefs.list
+++ b/src/tools/pgindent/typedefs.list
@@ -3952,6 +3952,7 @@ pg_sha512_ctx
pg_snapshot
pg_special_case
pg_stack_base_t
+pg_ternary
pg_time_t
pg_time_usec_t
pg_tz
@@ -4079,6 +4080,7 @@ relopt_kind
relopt_parse_elt
relopt_real
relopt_string
+relopt_ternary
relopt_type
relopt_value
relopts_validator
--
2.47.3
--c36t3fndxhkzbl5z--
^ permalink raw reply [nested|flat] 868+ messages in thread
* [PATCH v3] Introduce ternary reloptions
@ 2026-01-16 15:04 Álvaro Herrera <[email protected]>
0 siblings, 0 replies; 868+ messages in thread
From: Álvaro Herrera @ 2026-01-16 15:04 UTC (permalink / raw)
Introduce ternary reloption as a replacement for current
`vacuum_truncate` implementation. Remove the `vacuum_truncate_set`
separate flag and use TERNARY_UNSET instead.
This could also be used for other options such as `vacuum_index_cleanup`
and `buffering`, but lets get the scaffolding in first.
Discussion: https://postgr.es/m/3474141.usfYGdeWWP@thinkpad-pgpro
---
src/backend/access/common/reloptions.c | 137 ++++++++++++++++++-----
src/backend/commands/vacuum.c | 4 +-
src/include/access/reloptions.h | 27 ++---
src/include/postgres.h | 15 +++
src/include/utils/rel.h | 3 +-
src/test/regress/expected/reloptions.out | 36 ++++++
src/test/regress/sql/reloptions.sql | 21 ++++
src/tools/pgindent/typedefs.list | 2 +
8 files changed, 202 insertions(+), 43 deletions(-)
diff --git a/src/backend/access/common/reloptions.c b/src/backend/access/common/reloptions.c
index 0b83f98ed5f..6f1f577581a 100644
--- a/src/backend/access/common/reloptions.c
+++ b/src/backend/access/common/reloptions.c
@@ -40,9 +40,9 @@
*
* To add an option:
*
- * (i) decide on a type (bool, integer, real, enum, string), name, default
- * value, upper and lower bounds (if applicable); for strings, consider a
- * validation routine.
+ * (i) decide on a type (bool, ternary, integer, real, enum, string), name,
+ * default value, upper and lower bounds (if applicable); for strings,
+ * consider a validation routine.
* (ii) add a record below (or use add_<type>_reloption).
* (iii) add it to the appropriate options struct (perhaps StdRdOptions)
* (iv) add it to the appropriate handling routine (perhaps
@@ -147,15 +147,6 @@ static relopt_bool boolRelOpts[] =
},
false
},
- {
- {
- "vacuum_truncate",
- "Enables vacuum to truncate empty pages at the end of this table",
- RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
- ShareUpdateExclusiveLock
- },
- true
- },
{
{
"deduplicate_items",
@@ -170,6 +161,25 @@ static relopt_bool boolRelOpts[] =
{{NULL}}
};
+static relopt_ternary ternaryRelOpts[] =
+{
+ {
+ {
+ "vacuum_truncate",
+ "Enables vacuum to truncate empty pages at the end of this table",
+ RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
+ ShareUpdateExclusiveLock
+ },
+ TERNARY_UNSET
+ },
+ /* list terminator */
+ {
+ {
+ NULL
+ }
+ }
+};
+
static relopt_int intRelOpts[] =
{
{
@@ -609,6 +619,13 @@ initialize_reloptions(void)
boolRelOpts[i].gen.lockmode));
j++;
}
+ for (i = 0; ternaryRelOpts[i].gen.name; i++)
+ {
+ Assert(DoLockModesConflict(ternaryRelOpts[i].gen.lockmode,
+ ternaryRelOpts[i].gen.lockmode));
+ j++;
+ }
+
for (i = 0; intRelOpts[i].gen.name; i++)
{
Assert(DoLockModesConflict(intRelOpts[i].gen.lockmode,
@@ -649,6 +666,14 @@ initialize_reloptions(void)
j++;
}
+ for (i = 0; ternaryRelOpts[i].gen.name; i++)
+ {
+ relOpts[j] = &ternaryRelOpts[i].gen;
+ relOpts[j]->type = RELOPT_TYPE_TERNARY;
+ relOpts[j]->namelen = strlen(relOpts[j]->name);
+ j++;
+ }
+
for (i = 0; intRelOpts[i].gen.name; i++)
{
relOpts[j] = &intRelOpts[i].gen;
@@ -809,6 +834,9 @@ allocate_reloption(bits32 kinds, int type, const char *name, const char *desc,
case RELOPT_TYPE_BOOL:
size = sizeof(relopt_bool);
break;
+ case RELOPT_TYPE_TERNARY:
+ size = sizeof(relopt_ternary);
+ break;
case RELOPT_TYPE_INT:
size = sizeof(relopt_int);
break;
@@ -892,6 +920,57 @@ add_local_bool_reloption(local_relopts *relopts, const char *name,
add_local_reloption(relopts, (relopt_gen *) newoption, offset);
}
+/*
+ * init_ternary_reloption
+ * Allocate and initialize a new ternary reloption
+ */
+static relopt_ternary *
+init_ternary_reloption(bits32 kinds, const char *name, const char *desc,
+ pg_ternary default_val, LOCKMODE lockmode)
+{
+ relopt_ternary *newoption;
+
+ newoption = (relopt_ternary *)
+ allocate_reloption(kinds, RELOPT_TYPE_TERNARY, name, desc, lockmode);
+ newoption->default_val = default_val;
+
+ return newoption;
+}
+
+/*
+ * add_ternary_reloption
+ * Add a new ternary reloption
+ */
+void
+add_ternary_reloption(bits32 kinds, const char *name, const char *desc,
+ pg_ternary default_val, LOCKMODE lockmode)
+{
+ relopt_ternary *newoption;
+
+ newoption =
+ init_ternary_reloption(kinds, name, desc, default_val, lockmode);
+
+ add_reloption((relopt_gen *) newoption);
+}
+
+/*
+ * add_local_ternary_reloption
+ * Add a new ternary local reloption
+ *
+ * 'offset' is offset of ternary-typed field.
+ */
+void
+add_local_ternary_reloption(local_relopts *relopts, const char *name,
+ const char *desc, pg_ternary default_val,
+ int offset)
+{
+ relopt_ternary *newoption;
+
+ newoption =
+ init_ternary_reloption(RELOPT_KIND_LOCAL, name, desc, default_val, 0);
+
+ add_local_reloption(relopts, (relopt_gen *) newoption, offset);
+}
/*
* init_real_reloption
@@ -1626,6 +1705,19 @@ parse_one_reloption(relopt_value *option, char *text_str, int text_len,
option->gen->name, value)));
}
break;
+ case RELOPT_TYPE_TERNARY:
+ {
+ bool b;
+
+ parsed = parse_bool(value, &b);
+ option->values.ternary_val = b ? TERNARY_TRUE : TERNARY_FALSE;
+ if (validate && !parsed)
+ ereport(ERROR,
+ (errcode(ERRCODE_INVALID_PARAMETER_VALUE),
+ errmsg("invalid value for ternary option \"%s\": %s",
+ option->gen->name, value)));
+ }
+ break;
case RELOPT_TYPE_INT:
{
relopt_int *optint = (relopt_int *) option->gen;
@@ -1789,17 +1881,6 @@ fillRelOptions(void *rdopts, Size basesize,
char *itempos = ((char *) rdopts) + elems[j].offset;
char *string_val;
- /*
- * If isset_offset is provided, store whether the reloption is
- * set there.
- */
- if (elems[j].isset_offset > 0)
- {
- char *setpos = ((char *) rdopts) + elems[j].isset_offset;
-
- *(bool *) setpos = options[i].isset;
- }
-
switch (options[i].gen->type)
{
case RELOPT_TYPE_BOOL:
@@ -1807,6 +1888,11 @@ fillRelOptions(void *rdopts, Size basesize,
options[i].values.bool_val :
((relopt_bool *) options[i].gen)->default_val;
break;
+ case RELOPT_TYPE_TERNARY:
+ *(pg_ternary *) itempos = options[i].isset ?
+ options[i].values.ternary_val :
+ ((relopt_ternary *) options[i].gen)->default_val;
+ break;
case RELOPT_TYPE_INT:
*(int *) itempos = options[i].isset ?
options[i].values.int_val :
@@ -1923,8 +2009,8 @@ default_reloptions(Datum reloptions, bool validate, relopt_kind kind)
offsetof(StdRdOptions, parallel_workers)},
{"vacuum_index_cleanup", RELOPT_TYPE_ENUM,
offsetof(StdRdOptions, vacuum_index_cleanup)},
- {"vacuum_truncate", RELOPT_TYPE_BOOL,
- offsetof(StdRdOptions, vacuum_truncate), offsetof(StdRdOptions, vacuum_truncate_set)},
+ {"vacuum_truncate", RELOPT_TYPE_TERNARY,
+ offsetof(StdRdOptions, vacuum_truncate)},
{"vacuum_max_eager_freeze_failure_rate", RELOPT_TYPE_REAL,
offsetof(StdRdOptions, vacuum_max_eager_freeze_failure_rate)}
};
@@ -2004,7 +2090,6 @@ build_local_reloptions(local_relopts *relopts, Datum options, bool validate)
elems[i].optname = opt->option->name;
elems[i].opttype = opt->option->type;
elems[i].offset = opt->offset;
- elems[i].isset_offset = 0; /* not supported for local relopts yet */
i++;
}
diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c
index aa4fbec143f..696eab9bd97 100644
--- a/src/backend/commands/vacuum.c
+++ b/src/backend/commands/vacuum.c
@@ -2224,9 +2224,9 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams params,
{
StdRdOptions *opts = (StdRdOptions *) rel->rd_options;
- if (opts && opts->vacuum_truncate_set)
+ if (opts && opts->vacuum_truncate != TERNARY_UNSET)
{
- if (opts->vacuum_truncate)
+ if (opts->vacuum_truncate == TERNARY_TRUE)
params.truncate = VACOPTVALUE_ENABLED;
else
params.truncate = VACOPTVALUE_DISABLED;
diff --git a/src/include/access/reloptions.h b/src/include/access/reloptions.h
index 2f08e1b0cf0..dfbef2babf2 100644
--- a/src/include/access/reloptions.h
+++ b/src/include/access/reloptions.h
@@ -29,6 +29,7 @@
typedef enum relopt_type
{
RELOPT_TYPE_BOOL,
+ RELOPT_TYPE_TERNARY, /* on, off, unset */
RELOPT_TYPE_INT,
RELOPT_TYPE_REAL,
RELOPT_TYPE_ENUM,
@@ -80,6 +81,7 @@ typedef struct relopt_value
union
{
bool bool_val;
+ pg_ternary ternary_val;
int int_val;
double real_val;
int enum_val;
@@ -94,6 +96,12 @@ typedef struct relopt_bool
bool default_val;
} relopt_bool;
+typedef struct relopt_ternary
+{
+ relopt_gen gen;
+ int default_val;
+} relopt_ternary;
+
typedef struct relopt_int
{
relopt_gen gen;
@@ -152,19 +160,6 @@ typedef struct
const char *optname; /* option's name */
relopt_type opttype; /* option's datatype */
int offset; /* offset of field in result struct */
-
- /*
- * isset_offset is an optional offset of a field in the result struct that
- * stores whether the option is explicitly set for the relation or if it
- * just picked up the default value. In most cases, this can be
- * accomplished by giving the reloption a special out-of-range default
- * value (e.g., some integer reloptions use -2), but this isn't always
- * possible. For example, a Boolean reloption cannot be given an
- * out-of-range default, so we need another way to discover the source of
- * its value. This offset is only used if given a value greater than
- * zero.
- */
- int isset_offset;
} relopt_parse_elt;
/* Local reloption definition */
@@ -195,6 +190,9 @@ typedef struct local_relopts
extern relopt_kind add_reloption_kind(void);
extern void add_bool_reloption(bits32 kinds, const char *name, const char *desc,
bool default_val, LOCKMODE lockmode);
+extern void add_ternary_reloption(bits32 kinds, const char *name,
+ const char *desc, pg_ternary default_val,
+ LOCKMODE lockmode);
extern void add_int_reloption(bits32 kinds, const char *name, const char *desc,
int default_val, int min_val, int max_val,
LOCKMODE lockmode);
@@ -214,6 +212,9 @@ extern void register_reloptions_validator(local_relopts *relopts,
extern void add_local_bool_reloption(local_relopts *relopts, const char *name,
const char *desc, bool default_val,
int offset);
+extern void add_local_ternary_reloption(local_relopts *relopts,
+ const char *name, const char *desc,
+ pg_ternary default_val, int offset);
extern void add_local_int_reloption(local_relopts *relopts, const char *name,
const char *desc, int default_val,
int min_val, int max_val, int offset);
diff --git a/src/include/postgres.h b/src/include/postgres.h
index 7d93fbce709..47c88780776 100644
--- a/src/include/postgres.h
+++ b/src/include/postgres.h
@@ -543,6 +543,21 @@ Float8GetDatum(float8 X)
* ----------------------------------------------------------------
*/
+/*
+ * pg_ternary
+ * Boolean value with an extra "unset" value
+ *
+ * This enum can be used for values that want to distinguish between true,
+ * false, and unset.
+*/
+
+typedef enum pg_ternary
+{
+ TERNARY_FALSE = 0,
+ TERNARY_TRUE = 1,
+ TERNARY_UNSET = -1
+} pg_ternary;
+
/*
* NON_EXEC_STATIC: It's sometimes useful to define a variable or function
* that is normally static but extern when using EXEC_BACKEND (see
diff --git a/src/include/utils/rel.h b/src/include/utils/rel.h
index d03ab247788..5feb18a5373 100644
--- a/src/include/utils/rel.h
+++ b/src/include/utils/rel.h
@@ -347,8 +347,7 @@ typedef struct StdRdOptions
bool user_catalog_table; /* use as an additional catalog relation */
int parallel_workers; /* max number of parallel workers */
StdRdOptIndexCleanup vacuum_index_cleanup; /* controls index vacuuming */
- bool vacuum_truncate; /* enables vacuum to truncate a relation */
- bool vacuum_truncate_set; /* whether vacuum_truncate is set */
+ pg_ternary vacuum_truncate; /* enables vacuum to truncate a relation */
/*
* Fraction of pages in a relation that vacuum can eagerly scan and fail
diff --git a/src/test/regress/expected/reloptions.out b/src/test/regress/expected/reloptions.out
index 9de19b4e3f1..1c99f79ab01 100644
--- a/src/test/regress/expected/reloptions.out
+++ b/src/test/regress/expected/reloptions.out
@@ -98,6 +98,42 @@ SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass;
{fillfactor=13,autovacuum_enabled=false}
(1 row)
+-- Tests for future (FIXME) ternary options
+-- behave as boolean option: accept unassigned name and truncated value
+DROP TABLE reloptions_test;
+CREATE TABLE reloptions_test(i INT) WITH (vacuum_truncate);
+SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass;
+ reloptions
+------------------------
+ {vacuum_truncate=true}
+(1 row)
+
+DROP TABLE reloptions_test;
+CREATE TABLE reloptions_test(i INT) WITH (vacuum_truncate=FaLS);
+SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass;
+ reloptions
+------------------------
+ {vacuum_truncate=fals}
+(1 row)
+
+-- preferred "true" alias is used when storing
+DROP TABLE reloptions_test;
+CREATE TABLE reloptions_test(i INT) WITH (vacuum_index_cleanup=on);
+SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass;
+ reloptions
+---------------------------
+ {vacuum_index_cleanup=on}
+(1 row)
+
+-- custom "third" value is available
+DROP TABLE reloptions_test;
+CREATE TABLE reloptions_test(i INT) WITH (vacuum_index_cleanup=auto);
+SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass;
+ reloptions
+-----------------------------
+ {vacuum_index_cleanup=auto}
+(1 row)
+
-- Test vacuum_truncate option
DROP TABLE reloptions_test;
CREATE TEMP TABLE reloptions_test(i INT NOT NULL, j text)
diff --git a/src/test/regress/sql/reloptions.sql b/src/test/regress/sql/reloptions.sql
index 24fbe0b478d..f5980dafcbc 100644
--- a/src/test/regress/sql/reloptions.sql
+++ b/src/test/regress/sql/reloptions.sql
@@ -59,6 +59,27 @@ UPDATE pg_class
ALTER TABLE reloptions_test RESET (illegal_option);
SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass;
+-- Tests for future (FIXME) ternary options
+
+-- behave as boolean option: accept unassigned name and truncated value
+DROP TABLE reloptions_test;
+CREATE TABLE reloptions_test(i INT) WITH (vacuum_truncate);
+SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass;
+
+DROP TABLE reloptions_test;
+CREATE TABLE reloptions_test(i INT) WITH (vacuum_truncate=FaLS);
+SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass;
+
+-- preferred "true" alias is used when storing
+DROP TABLE reloptions_test;
+CREATE TABLE reloptions_test(i INT) WITH (vacuum_index_cleanup=on);
+SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass;
+
+-- custom "third" value is available
+DROP TABLE reloptions_test;
+CREATE TABLE reloptions_test(i INT) WITH (vacuum_index_cleanup=auto);
+SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass;
+
-- Test vacuum_truncate option
DROP TABLE reloptions_test;
diff --git a/src/tools/pgindent/typedefs.list b/src/tools/pgindent/typedefs.list
index 3f3a888fd0e..1c8610fd46c 100644
--- a/src/tools/pgindent/typedefs.list
+++ b/src/tools/pgindent/typedefs.list
@@ -3952,6 +3952,7 @@ pg_sha512_ctx
pg_snapshot
pg_special_case
pg_stack_base_t
+pg_ternary
pg_time_t
pg_time_usec_t
pg_tz
@@ -4079,6 +4080,7 @@ relopt_kind
relopt_parse_elt
relopt_real
relopt_string
+relopt_ternary
relopt_type
relopt_value
relopts_validator
--
2.47.3
--c36t3fndxhkzbl5z--
^ permalink raw reply [nested|flat] 868+ messages in thread
* [PATCH v3] Introduce ternary reloptions
@ 2026-01-16 15:04 Álvaro Herrera <[email protected]>
0 siblings, 0 replies; 868+ messages in thread
From: Álvaro Herrera @ 2026-01-16 15:04 UTC (permalink / raw)
Introduce ternary reloption as a replacement for current
`vacuum_truncate` implementation. Remove the `vacuum_truncate_set`
separate flag and use TERNARY_UNSET instead.
This could also be used for other options such as `vacuum_index_cleanup`
and `buffering`, but lets get the scaffolding in first.
Discussion: https://postgr.es/m/3474141.usfYGdeWWP@thinkpad-pgpro
---
src/backend/access/common/reloptions.c | 137 ++++++++++++++++++-----
src/backend/commands/vacuum.c | 4 +-
src/include/access/reloptions.h | 27 ++---
src/include/postgres.h | 15 +++
src/include/utils/rel.h | 3 +-
src/test/regress/expected/reloptions.out | 36 ++++++
src/test/regress/sql/reloptions.sql | 21 ++++
src/tools/pgindent/typedefs.list | 2 +
8 files changed, 202 insertions(+), 43 deletions(-)
diff --git a/src/backend/access/common/reloptions.c b/src/backend/access/common/reloptions.c
index 0b83f98ed5f..6f1f577581a 100644
--- a/src/backend/access/common/reloptions.c
+++ b/src/backend/access/common/reloptions.c
@@ -40,9 +40,9 @@
*
* To add an option:
*
- * (i) decide on a type (bool, integer, real, enum, string), name, default
- * value, upper and lower bounds (if applicable); for strings, consider a
- * validation routine.
+ * (i) decide on a type (bool, ternary, integer, real, enum, string), name,
+ * default value, upper and lower bounds (if applicable); for strings,
+ * consider a validation routine.
* (ii) add a record below (or use add_<type>_reloption).
* (iii) add it to the appropriate options struct (perhaps StdRdOptions)
* (iv) add it to the appropriate handling routine (perhaps
@@ -147,15 +147,6 @@ static relopt_bool boolRelOpts[] =
},
false
},
- {
- {
- "vacuum_truncate",
- "Enables vacuum to truncate empty pages at the end of this table",
- RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
- ShareUpdateExclusiveLock
- },
- true
- },
{
{
"deduplicate_items",
@@ -170,6 +161,25 @@ static relopt_bool boolRelOpts[] =
{{NULL}}
};
+static relopt_ternary ternaryRelOpts[] =
+{
+ {
+ {
+ "vacuum_truncate",
+ "Enables vacuum to truncate empty pages at the end of this table",
+ RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
+ ShareUpdateExclusiveLock
+ },
+ TERNARY_UNSET
+ },
+ /* list terminator */
+ {
+ {
+ NULL
+ }
+ }
+};
+
static relopt_int intRelOpts[] =
{
{
@@ -609,6 +619,13 @@ initialize_reloptions(void)
boolRelOpts[i].gen.lockmode));
j++;
}
+ for (i = 0; ternaryRelOpts[i].gen.name; i++)
+ {
+ Assert(DoLockModesConflict(ternaryRelOpts[i].gen.lockmode,
+ ternaryRelOpts[i].gen.lockmode));
+ j++;
+ }
+
for (i = 0; intRelOpts[i].gen.name; i++)
{
Assert(DoLockModesConflict(intRelOpts[i].gen.lockmode,
@@ -649,6 +666,14 @@ initialize_reloptions(void)
j++;
}
+ for (i = 0; ternaryRelOpts[i].gen.name; i++)
+ {
+ relOpts[j] = &ternaryRelOpts[i].gen;
+ relOpts[j]->type = RELOPT_TYPE_TERNARY;
+ relOpts[j]->namelen = strlen(relOpts[j]->name);
+ j++;
+ }
+
for (i = 0; intRelOpts[i].gen.name; i++)
{
relOpts[j] = &intRelOpts[i].gen;
@@ -809,6 +834,9 @@ allocate_reloption(bits32 kinds, int type, const char *name, const char *desc,
case RELOPT_TYPE_BOOL:
size = sizeof(relopt_bool);
break;
+ case RELOPT_TYPE_TERNARY:
+ size = sizeof(relopt_ternary);
+ break;
case RELOPT_TYPE_INT:
size = sizeof(relopt_int);
break;
@@ -892,6 +920,57 @@ add_local_bool_reloption(local_relopts *relopts, const char *name,
add_local_reloption(relopts, (relopt_gen *) newoption, offset);
}
+/*
+ * init_ternary_reloption
+ * Allocate and initialize a new ternary reloption
+ */
+static relopt_ternary *
+init_ternary_reloption(bits32 kinds, const char *name, const char *desc,
+ pg_ternary default_val, LOCKMODE lockmode)
+{
+ relopt_ternary *newoption;
+
+ newoption = (relopt_ternary *)
+ allocate_reloption(kinds, RELOPT_TYPE_TERNARY, name, desc, lockmode);
+ newoption->default_val = default_val;
+
+ return newoption;
+}
+
+/*
+ * add_ternary_reloption
+ * Add a new ternary reloption
+ */
+void
+add_ternary_reloption(bits32 kinds, const char *name, const char *desc,
+ pg_ternary default_val, LOCKMODE lockmode)
+{
+ relopt_ternary *newoption;
+
+ newoption =
+ init_ternary_reloption(kinds, name, desc, default_val, lockmode);
+
+ add_reloption((relopt_gen *) newoption);
+}
+
+/*
+ * add_local_ternary_reloption
+ * Add a new ternary local reloption
+ *
+ * 'offset' is offset of ternary-typed field.
+ */
+void
+add_local_ternary_reloption(local_relopts *relopts, const char *name,
+ const char *desc, pg_ternary default_val,
+ int offset)
+{
+ relopt_ternary *newoption;
+
+ newoption =
+ init_ternary_reloption(RELOPT_KIND_LOCAL, name, desc, default_val, 0);
+
+ add_local_reloption(relopts, (relopt_gen *) newoption, offset);
+}
/*
* init_real_reloption
@@ -1626,6 +1705,19 @@ parse_one_reloption(relopt_value *option, char *text_str, int text_len,
option->gen->name, value)));
}
break;
+ case RELOPT_TYPE_TERNARY:
+ {
+ bool b;
+
+ parsed = parse_bool(value, &b);
+ option->values.ternary_val = b ? TERNARY_TRUE : TERNARY_FALSE;
+ if (validate && !parsed)
+ ereport(ERROR,
+ (errcode(ERRCODE_INVALID_PARAMETER_VALUE),
+ errmsg("invalid value for ternary option \"%s\": %s",
+ option->gen->name, value)));
+ }
+ break;
case RELOPT_TYPE_INT:
{
relopt_int *optint = (relopt_int *) option->gen;
@@ -1789,17 +1881,6 @@ fillRelOptions(void *rdopts, Size basesize,
char *itempos = ((char *) rdopts) + elems[j].offset;
char *string_val;
- /*
- * If isset_offset is provided, store whether the reloption is
- * set there.
- */
- if (elems[j].isset_offset > 0)
- {
- char *setpos = ((char *) rdopts) + elems[j].isset_offset;
-
- *(bool *) setpos = options[i].isset;
- }
-
switch (options[i].gen->type)
{
case RELOPT_TYPE_BOOL:
@@ -1807,6 +1888,11 @@ fillRelOptions(void *rdopts, Size basesize,
options[i].values.bool_val :
((relopt_bool *) options[i].gen)->default_val;
break;
+ case RELOPT_TYPE_TERNARY:
+ *(pg_ternary *) itempos = options[i].isset ?
+ options[i].values.ternary_val :
+ ((relopt_ternary *) options[i].gen)->default_val;
+ break;
case RELOPT_TYPE_INT:
*(int *) itempos = options[i].isset ?
options[i].values.int_val :
@@ -1923,8 +2009,8 @@ default_reloptions(Datum reloptions, bool validate, relopt_kind kind)
offsetof(StdRdOptions, parallel_workers)},
{"vacuum_index_cleanup", RELOPT_TYPE_ENUM,
offsetof(StdRdOptions, vacuum_index_cleanup)},
- {"vacuum_truncate", RELOPT_TYPE_BOOL,
- offsetof(StdRdOptions, vacuum_truncate), offsetof(StdRdOptions, vacuum_truncate_set)},
+ {"vacuum_truncate", RELOPT_TYPE_TERNARY,
+ offsetof(StdRdOptions, vacuum_truncate)},
{"vacuum_max_eager_freeze_failure_rate", RELOPT_TYPE_REAL,
offsetof(StdRdOptions, vacuum_max_eager_freeze_failure_rate)}
};
@@ -2004,7 +2090,6 @@ build_local_reloptions(local_relopts *relopts, Datum options, bool validate)
elems[i].optname = opt->option->name;
elems[i].opttype = opt->option->type;
elems[i].offset = opt->offset;
- elems[i].isset_offset = 0; /* not supported for local relopts yet */
i++;
}
diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c
index aa4fbec143f..696eab9bd97 100644
--- a/src/backend/commands/vacuum.c
+++ b/src/backend/commands/vacuum.c
@@ -2224,9 +2224,9 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams params,
{
StdRdOptions *opts = (StdRdOptions *) rel->rd_options;
- if (opts && opts->vacuum_truncate_set)
+ if (opts && opts->vacuum_truncate != TERNARY_UNSET)
{
- if (opts->vacuum_truncate)
+ if (opts->vacuum_truncate == TERNARY_TRUE)
params.truncate = VACOPTVALUE_ENABLED;
else
params.truncate = VACOPTVALUE_DISABLED;
diff --git a/src/include/access/reloptions.h b/src/include/access/reloptions.h
index 2f08e1b0cf0..dfbef2babf2 100644
--- a/src/include/access/reloptions.h
+++ b/src/include/access/reloptions.h
@@ -29,6 +29,7 @@
typedef enum relopt_type
{
RELOPT_TYPE_BOOL,
+ RELOPT_TYPE_TERNARY, /* on, off, unset */
RELOPT_TYPE_INT,
RELOPT_TYPE_REAL,
RELOPT_TYPE_ENUM,
@@ -80,6 +81,7 @@ typedef struct relopt_value
union
{
bool bool_val;
+ pg_ternary ternary_val;
int int_val;
double real_val;
int enum_val;
@@ -94,6 +96,12 @@ typedef struct relopt_bool
bool default_val;
} relopt_bool;
+typedef struct relopt_ternary
+{
+ relopt_gen gen;
+ int default_val;
+} relopt_ternary;
+
typedef struct relopt_int
{
relopt_gen gen;
@@ -152,19 +160,6 @@ typedef struct
const char *optname; /* option's name */
relopt_type opttype; /* option's datatype */
int offset; /* offset of field in result struct */
-
- /*
- * isset_offset is an optional offset of a field in the result struct that
- * stores whether the option is explicitly set for the relation or if it
- * just picked up the default value. In most cases, this can be
- * accomplished by giving the reloption a special out-of-range default
- * value (e.g., some integer reloptions use -2), but this isn't always
- * possible. For example, a Boolean reloption cannot be given an
- * out-of-range default, so we need another way to discover the source of
- * its value. This offset is only used if given a value greater than
- * zero.
- */
- int isset_offset;
} relopt_parse_elt;
/* Local reloption definition */
@@ -195,6 +190,9 @@ typedef struct local_relopts
extern relopt_kind add_reloption_kind(void);
extern void add_bool_reloption(bits32 kinds, const char *name, const char *desc,
bool default_val, LOCKMODE lockmode);
+extern void add_ternary_reloption(bits32 kinds, const char *name,
+ const char *desc, pg_ternary default_val,
+ LOCKMODE lockmode);
extern void add_int_reloption(bits32 kinds, const char *name, const char *desc,
int default_val, int min_val, int max_val,
LOCKMODE lockmode);
@@ -214,6 +212,9 @@ extern void register_reloptions_validator(local_relopts *relopts,
extern void add_local_bool_reloption(local_relopts *relopts, const char *name,
const char *desc, bool default_val,
int offset);
+extern void add_local_ternary_reloption(local_relopts *relopts,
+ const char *name, const char *desc,
+ pg_ternary default_val, int offset);
extern void add_local_int_reloption(local_relopts *relopts, const char *name,
const char *desc, int default_val,
int min_val, int max_val, int offset);
diff --git a/src/include/postgres.h b/src/include/postgres.h
index 7d93fbce709..47c88780776 100644
--- a/src/include/postgres.h
+++ b/src/include/postgres.h
@@ -543,6 +543,21 @@ Float8GetDatum(float8 X)
* ----------------------------------------------------------------
*/
+/*
+ * pg_ternary
+ * Boolean value with an extra "unset" value
+ *
+ * This enum can be used for values that want to distinguish between true,
+ * false, and unset.
+*/
+
+typedef enum pg_ternary
+{
+ TERNARY_FALSE = 0,
+ TERNARY_TRUE = 1,
+ TERNARY_UNSET = -1
+} pg_ternary;
+
/*
* NON_EXEC_STATIC: It's sometimes useful to define a variable or function
* that is normally static but extern when using EXEC_BACKEND (see
diff --git a/src/include/utils/rel.h b/src/include/utils/rel.h
index d03ab247788..5feb18a5373 100644
--- a/src/include/utils/rel.h
+++ b/src/include/utils/rel.h
@@ -347,8 +347,7 @@ typedef struct StdRdOptions
bool user_catalog_table; /* use as an additional catalog relation */
int parallel_workers; /* max number of parallel workers */
StdRdOptIndexCleanup vacuum_index_cleanup; /* controls index vacuuming */
- bool vacuum_truncate; /* enables vacuum to truncate a relation */
- bool vacuum_truncate_set; /* whether vacuum_truncate is set */
+ pg_ternary vacuum_truncate; /* enables vacuum to truncate a relation */
/*
* Fraction of pages in a relation that vacuum can eagerly scan and fail
diff --git a/src/test/regress/expected/reloptions.out b/src/test/regress/expected/reloptions.out
index 9de19b4e3f1..1c99f79ab01 100644
--- a/src/test/regress/expected/reloptions.out
+++ b/src/test/regress/expected/reloptions.out
@@ -98,6 +98,42 @@ SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass;
{fillfactor=13,autovacuum_enabled=false}
(1 row)
+-- Tests for future (FIXME) ternary options
+-- behave as boolean option: accept unassigned name and truncated value
+DROP TABLE reloptions_test;
+CREATE TABLE reloptions_test(i INT) WITH (vacuum_truncate);
+SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass;
+ reloptions
+------------------------
+ {vacuum_truncate=true}
+(1 row)
+
+DROP TABLE reloptions_test;
+CREATE TABLE reloptions_test(i INT) WITH (vacuum_truncate=FaLS);
+SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass;
+ reloptions
+------------------------
+ {vacuum_truncate=fals}
+(1 row)
+
+-- preferred "true" alias is used when storing
+DROP TABLE reloptions_test;
+CREATE TABLE reloptions_test(i INT) WITH (vacuum_index_cleanup=on);
+SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass;
+ reloptions
+---------------------------
+ {vacuum_index_cleanup=on}
+(1 row)
+
+-- custom "third" value is available
+DROP TABLE reloptions_test;
+CREATE TABLE reloptions_test(i INT) WITH (vacuum_index_cleanup=auto);
+SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass;
+ reloptions
+-----------------------------
+ {vacuum_index_cleanup=auto}
+(1 row)
+
-- Test vacuum_truncate option
DROP TABLE reloptions_test;
CREATE TEMP TABLE reloptions_test(i INT NOT NULL, j text)
diff --git a/src/test/regress/sql/reloptions.sql b/src/test/regress/sql/reloptions.sql
index 24fbe0b478d..f5980dafcbc 100644
--- a/src/test/regress/sql/reloptions.sql
+++ b/src/test/regress/sql/reloptions.sql
@@ -59,6 +59,27 @@ UPDATE pg_class
ALTER TABLE reloptions_test RESET (illegal_option);
SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass;
+-- Tests for future (FIXME) ternary options
+
+-- behave as boolean option: accept unassigned name and truncated value
+DROP TABLE reloptions_test;
+CREATE TABLE reloptions_test(i INT) WITH (vacuum_truncate);
+SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass;
+
+DROP TABLE reloptions_test;
+CREATE TABLE reloptions_test(i INT) WITH (vacuum_truncate=FaLS);
+SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass;
+
+-- preferred "true" alias is used when storing
+DROP TABLE reloptions_test;
+CREATE TABLE reloptions_test(i INT) WITH (vacuum_index_cleanup=on);
+SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass;
+
+-- custom "third" value is available
+DROP TABLE reloptions_test;
+CREATE TABLE reloptions_test(i INT) WITH (vacuum_index_cleanup=auto);
+SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass;
+
-- Test vacuum_truncate option
DROP TABLE reloptions_test;
diff --git a/src/tools/pgindent/typedefs.list b/src/tools/pgindent/typedefs.list
index 3f3a888fd0e..1c8610fd46c 100644
--- a/src/tools/pgindent/typedefs.list
+++ b/src/tools/pgindent/typedefs.list
@@ -3952,6 +3952,7 @@ pg_sha512_ctx
pg_snapshot
pg_special_case
pg_stack_base_t
+pg_ternary
pg_time_t
pg_time_usec_t
pg_tz
@@ -4079,6 +4080,7 @@ relopt_kind
relopt_parse_elt
relopt_real
relopt_string
+relopt_ternary
relopt_type
relopt_value
relopts_validator
--
2.47.3
--c36t3fndxhkzbl5z--
^ permalink raw reply [nested|flat] 868+ messages in thread
* [PATCH v3] Introduce ternary reloptions
@ 2026-01-16 15:04 Álvaro Herrera <[email protected]>
0 siblings, 0 replies; 868+ messages in thread
From: Álvaro Herrera @ 2026-01-16 15:04 UTC (permalink / raw)
Introduce ternary reloption as a replacement for current
`vacuum_truncate` implementation. Remove the `vacuum_truncate_set`
separate flag and use TERNARY_UNSET instead.
This could also be used for other options such as `vacuum_index_cleanup`
and `buffering`, but lets get the scaffolding in first.
Discussion: https://postgr.es/m/3474141.usfYGdeWWP@thinkpad-pgpro
---
src/backend/access/common/reloptions.c | 137 ++++++++++++++++++-----
src/backend/commands/vacuum.c | 4 +-
src/include/access/reloptions.h | 27 ++---
src/include/postgres.h | 15 +++
src/include/utils/rel.h | 3 +-
src/test/regress/expected/reloptions.out | 36 ++++++
src/test/regress/sql/reloptions.sql | 21 ++++
src/tools/pgindent/typedefs.list | 2 +
8 files changed, 202 insertions(+), 43 deletions(-)
diff --git a/src/backend/access/common/reloptions.c b/src/backend/access/common/reloptions.c
index 0b83f98ed5f..6f1f577581a 100644
--- a/src/backend/access/common/reloptions.c
+++ b/src/backend/access/common/reloptions.c
@@ -40,9 +40,9 @@
*
* To add an option:
*
- * (i) decide on a type (bool, integer, real, enum, string), name, default
- * value, upper and lower bounds (if applicable); for strings, consider a
- * validation routine.
+ * (i) decide on a type (bool, ternary, integer, real, enum, string), name,
+ * default value, upper and lower bounds (if applicable); for strings,
+ * consider a validation routine.
* (ii) add a record below (or use add_<type>_reloption).
* (iii) add it to the appropriate options struct (perhaps StdRdOptions)
* (iv) add it to the appropriate handling routine (perhaps
@@ -147,15 +147,6 @@ static relopt_bool boolRelOpts[] =
},
false
},
- {
- {
- "vacuum_truncate",
- "Enables vacuum to truncate empty pages at the end of this table",
- RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
- ShareUpdateExclusiveLock
- },
- true
- },
{
{
"deduplicate_items",
@@ -170,6 +161,25 @@ static relopt_bool boolRelOpts[] =
{{NULL}}
};
+static relopt_ternary ternaryRelOpts[] =
+{
+ {
+ {
+ "vacuum_truncate",
+ "Enables vacuum to truncate empty pages at the end of this table",
+ RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
+ ShareUpdateExclusiveLock
+ },
+ TERNARY_UNSET
+ },
+ /* list terminator */
+ {
+ {
+ NULL
+ }
+ }
+};
+
static relopt_int intRelOpts[] =
{
{
@@ -609,6 +619,13 @@ initialize_reloptions(void)
boolRelOpts[i].gen.lockmode));
j++;
}
+ for (i = 0; ternaryRelOpts[i].gen.name; i++)
+ {
+ Assert(DoLockModesConflict(ternaryRelOpts[i].gen.lockmode,
+ ternaryRelOpts[i].gen.lockmode));
+ j++;
+ }
+
for (i = 0; intRelOpts[i].gen.name; i++)
{
Assert(DoLockModesConflict(intRelOpts[i].gen.lockmode,
@@ -649,6 +666,14 @@ initialize_reloptions(void)
j++;
}
+ for (i = 0; ternaryRelOpts[i].gen.name; i++)
+ {
+ relOpts[j] = &ternaryRelOpts[i].gen;
+ relOpts[j]->type = RELOPT_TYPE_TERNARY;
+ relOpts[j]->namelen = strlen(relOpts[j]->name);
+ j++;
+ }
+
for (i = 0; intRelOpts[i].gen.name; i++)
{
relOpts[j] = &intRelOpts[i].gen;
@@ -809,6 +834,9 @@ allocate_reloption(bits32 kinds, int type, const char *name, const char *desc,
case RELOPT_TYPE_BOOL:
size = sizeof(relopt_bool);
break;
+ case RELOPT_TYPE_TERNARY:
+ size = sizeof(relopt_ternary);
+ break;
case RELOPT_TYPE_INT:
size = sizeof(relopt_int);
break;
@@ -892,6 +920,57 @@ add_local_bool_reloption(local_relopts *relopts, const char *name,
add_local_reloption(relopts, (relopt_gen *) newoption, offset);
}
+/*
+ * init_ternary_reloption
+ * Allocate and initialize a new ternary reloption
+ */
+static relopt_ternary *
+init_ternary_reloption(bits32 kinds, const char *name, const char *desc,
+ pg_ternary default_val, LOCKMODE lockmode)
+{
+ relopt_ternary *newoption;
+
+ newoption = (relopt_ternary *)
+ allocate_reloption(kinds, RELOPT_TYPE_TERNARY, name, desc, lockmode);
+ newoption->default_val = default_val;
+
+ return newoption;
+}
+
+/*
+ * add_ternary_reloption
+ * Add a new ternary reloption
+ */
+void
+add_ternary_reloption(bits32 kinds, const char *name, const char *desc,
+ pg_ternary default_val, LOCKMODE lockmode)
+{
+ relopt_ternary *newoption;
+
+ newoption =
+ init_ternary_reloption(kinds, name, desc, default_val, lockmode);
+
+ add_reloption((relopt_gen *) newoption);
+}
+
+/*
+ * add_local_ternary_reloption
+ * Add a new ternary local reloption
+ *
+ * 'offset' is offset of ternary-typed field.
+ */
+void
+add_local_ternary_reloption(local_relopts *relopts, const char *name,
+ const char *desc, pg_ternary default_val,
+ int offset)
+{
+ relopt_ternary *newoption;
+
+ newoption =
+ init_ternary_reloption(RELOPT_KIND_LOCAL, name, desc, default_val, 0);
+
+ add_local_reloption(relopts, (relopt_gen *) newoption, offset);
+}
/*
* init_real_reloption
@@ -1626,6 +1705,19 @@ parse_one_reloption(relopt_value *option, char *text_str, int text_len,
option->gen->name, value)));
}
break;
+ case RELOPT_TYPE_TERNARY:
+ {
+ bool b;
+
+ parsed = parse_bool(value, &b);
+ option->values.ternary_val = b ? TERNARY_TRUE : TERNARY_FALSE;
+ if (validate && !parsed)
+ ereport(ERROR,
+ (errcode(ERRCODE_INVALID_PARAMETER_VALUE),
+ errmsg("invalid value for ternary option \"%s\": %s",
+ option->gen->name, value)));
+ }
+ break;
case RELOPT_TYPE_INT:
{
relopt_int *optint = (relopt_int *) option->gen;
@@ -1789,17 +1881,6 @@ fillRelOptions(void *rdopts, Size basesize,
char *itempos = ((char *) rdopts) + elems[j].offset;
char *string_val;
- /*
- * If isset_offset is provided, store whether the reloption is
- * set there.
- */
- if (elems[j].isset_offset > 0)
- {
- char *setpos = ((char *) rdopts) + elems[j].isset_offset;
-
- *(bool *) setpos = options[i].isset;
- }
-
switch (options[i].gen->type)
{
case RELOPT_TYPE_BOOL:
@@ -1807,6 +1888,11 @@ fillRelOptions(void *rdopts, Size basesize,
options[i].values.bool_val :
((relopt_bool *) options[i].gen)->default_val;
break;
+ case RELOPT_TYPE_TERNARY:
+ *(pg_ternary *) itempos = options[i].isset ?
+ options[i].values.ternary_val :
+ ((relopt_ternary *) options[i].gen)->default_val;
+ break;
case RELOPT_TYPE_INT:
*(int *) itempos = options[i].isset ?
options[i].values.int_val :
@@ -1923,8 +2009,8 @@ default_reloptions(Datum reloptions, bool validate, relopt_kind kind)
offsetof(StdRdOptions, parallel_workers)},
{"vacuum_index_cleanup", RELOPT_TYPE_ENUM,
offsetof(StdRdOptions, vacuum_index_cleanup)},
- {"vacuum_truncate", RELOPT_TYPE_BOOL,
- offsetof(StdRdOptions, vacuum_truncate), offsetof(StdRdOptions, vacuum_truncate_set)},
+ {"vacuum_truncate", RELOPT_TYPE_TERNARY,
+ offsetof(StdRdOptions, vacuum_truncate)},
{"vacuum_max_eager_freeze_failure_rate", RELOPT_TYPE_REAL,
offsetof(StdRdOptions, vacuum_max_eager_freeze_failure_rate)}
};
@@ -2004,7 +2090,6 @@ build_local_reloptions(local_relopts *relopts, Datum options, bool validate)
elems[i].optname = opt->option->name;
elems[i].opttype = opt->option->type;
elems[i].offset = opt->offset;
- elems[i].isset_offset = 0; /* not supported for local relopts yet */
i++;
}
diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c
index aa4fbec143f..696eab9bd97 100644
--- a/src/backend/commands/vacuum.c
+++ b/src/backend/commands/vacuum.c
@@ -2224,9 +2224,9 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams params,
{
StdRdOptions *opts = (StdRdOptions *) rel->rd_options;
- if (opts && opts->vacuum_truncate_set)
+ if (opts && opts->vacuum_truncate != TERNARY_UNSET)
{
- if (opts->vacuum_truncate)
+ if (opts->vacuum_truncate == TERNARY_TRUE)
params.truncate = VACOPTVALUE_ENABLED;
else
params.truncate = VACOPTVALUE_DISABLED;
diff --git a/src/include/access/reloptions.h b/src/include/access/reloptions.h
index 2f08e1b0cf0..dfbef2babf2 100644
--- a/src/include/access/reloptions.h
+++ b/src/include/access/reloptions.h
@@ -29,6 +29,7 @@
typedef enum relopt_type
{
RELOPT_TYPE_BOOL,
+ RELOPT_TYPE_TERNARY, /* on, off, unset */
RELOPT_TYPE_INT,
RELOPT_TYPE_REAL,
RELOPT_TYPE_ENUM,
@@ -80,6 +81,7 @@ typedef struct relopt_value
union
{
bool bool_val;
+ pg_ternary ternary_val;
int int_val;
double real_val;
int enum_val;
@@ -94,6 +96,12 @@ typedef struct relopt_bool
bool default_val;
} relopt_bool;
+typedef struct relopt_ternary
+{
+ relopt_gen gen;
+ int default_val;
+} relopt_ternary;
+
typedef struct relopt_int
{
relopt_gen gen;
@@ -152,19 +160,6 @@ typedef struct
const char *optname; /* option's name */
relopt_type opttype; /* option's datatype */
int offset; /* offset of field in result struct */
-
- /*
- * isset_offset is an optional offset of a field in the result struct that
- * stores whether the option is explicitly set for the relation or if it
- * just picked up the default value. In most cases, this can be
- * accomplished by giving the reloption a special out-of-range default
- * value (e.g., some integer reloptions use -2), but this isn't always
- * possible. For example, a Boolean reloption cannot be given an
- * out-of-range default, so we need another way to discover the source of
- * its value. This offset is only used if given a value greater than
- * zero.
- */
- int isset_offset;
} relopt_parse_elt;
/* Local reloption definition */
@@ -195,6 +190,9 @@ typedef struct local_relopts
extern relopt_kind add_reloption_kind(void);
extern void add_bool_reloption(bits32 kinds, const char *name, const char *desc,
bool default_val, LOCKMODE lockmode);
+extern void add_ternary_reloption(bits32 kinds, const char *name,
+ const char *desc, pg_ternary default_val,
+ LOCKMODE lockmode);
extern void add_int_reloption(bits32 kinds, const char *name, const char *desc,
int default_val, int min_val, int max_val,
LOCKMODE lockmode);
@@ -214,6 +212,9 @@ extern void register_reloptions_validator(local_relopts *relopts,
extern void add_local_bool_reloption(local_relopts *relopts, const char *name,
const char *desc, bool default_val,
int offset);
+extern void add_local_ternary_reloption(local_relopts *relopts,
+ const char *name, const char *desc,
+ pg_ternary default_val, int offset);
extern void add_local_int_reloption(local_relopts *relopts, const char *name,
const char *desc, int default_val,
int min_val, int max_val, int offset);
diff --git a/src/include/postgres.h b/src/include/postgres.h
index 7d93fbce709..47c88780776 100644
--- a/src/include/postgres.h
+++ b/src/include/postgres.h
@@ -543,6 +543,21 @@ Float8GetDatum(float8 X)
* ----------------------------------------------------------------
*/
+/*
+ * pg_ternary
+ * Boolean value with an extra "unset" value
+ *
+ * This enum can be used for values that want to distinguish between true,
+ * false, and unset.
+*/
+
+typedef enum pg_ternary
+{
+ TERNARY_FALSE = 0,
+ TERNARY_TRUE = 1,
+ TERNARY_UNSET = -1
+} pg_ternary;
+
/*
* NON_EXEC_STATIC: It's sometimes useful to define a variable or function
* that is normally static but extern when using EXEC_BACKEND (see
diff --git a/src/include/utils/rel.h b/src/include/utils/rel.h
index d03ab247788..5feb18a5373 100644
--- a/src/include/utils/rel.h
+++ b/src/include/utils/rel.h
@@ -347,8 +347,7 @@ typedef struct StdRdOptions
bool user_catalog_table; /* use as an additional catalog relation */
int parallel_workers; /* max number of parallel workers */
StdRdOptIndexCleanup vacuum_index_cleanup; /* controls index vacuuming */
- bool vacuum_truncate; /* enables vacuum to truncate a relation */
- bool vacuum_truncate_set; /* whether vacuum_truncate is set */
+ pg_ternary vacuum_truncate; /* enables vacuum to truncate a relation */
/*
* Fraction of pages in a relation that vacuum can eagerly scan and fail
diff --git a/src/test/regress/expected/reloptions.out b/src/test/regress/expected/reloptions.out
index 9de19b4e3f1..1c99f79ab01 100644
--- a/src/test/regress/expected/reloptions.out
+++ b/src/test/regress/expected/reloptions.out
@@ -98,6 +98,42 @@ SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass;
{fillfactor=13,autovacuum_enabled=false}
(1 row)
+-- Tests for future (FIXME) ternary options
+-- behave as boolean option: accept unassigned name and truncated value
+DROP TABLE reloptions_test;
+CREATE TABLE reloptions_test(i INT) WITH (vacuum_truncate);
+SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass;
+ reloptions
+------------------------
+ {vacuum_truncate=true}
+(1 row)
+
+DROP TABLE reloptions_test;
+CREATE TABLE reloptions_test(i INT) WITH (vacuum_truncate=FaLS);
+SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass;
+ reloptions
+------------------------
+ {vacuum_truncate=fals}
+(1 row)
+
+-- preferred "true" alias is used when storing
+DROP TABLE reloptions_test;
+CREATE TABLE reloptions_test(i INT) WITH (vacuum_index_cleanup=on);
+SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass;
+ reloptions
+---------------------------
+ {vacuum_index_cleanup=on}
+(1 row)
+
+-- custom "third" value is available
+DROP TABLE reloptions_test;
+CREATE TABLE reloptions_test(i INT) WITH (vacuum_index_cleanup=auto);
+SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass;
+ reloptions
+-----------------------------
+ {vacuum_index_cleanup=auto}
+(1 row)
+
-- Test vacuum_truncate option
DROP TABLE reloptions_test;
CREATE TEMP TABLE reloptions_test(i INT NOT NULL, j text)
diff --git a/src/test/regress/sql/reloptions.sql b/src/test/regress/sql/reloptions.sql
index 24fbe0b478d..f5980dafcbc 100644
--- a/src/test/regress/sql/reloptions.sql
+++ b/src/test/regress/sql/reloptions.sql
@@ -59,6 +59,27 @@ UPDATE pg_class
ALTER TABLE reloptions_test RESET (illegal_option);
SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass;
+-- Tests for future (FIXME) ternary options
+
+-- behave as boolean option: accept unassigned name and truncated value
+DROP TABLE reloptions_test;
+CREATE TABLE reloptions_test(i INT) WITH (vacuum_truncate);
+SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass;
+
+DROP TABLE reloptions_test;
+CREATE TABLE reloptions_test(i INT) WITH (vacuum_truncate=FaLS);
+SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass;
+
+-- preferred "true" alias is used when storing
+DROP TABLE reloptions_test;
+CREATE TABLE reloptions_test(i INT) WITH (vacuum_index_cleanup=on);
+SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass;
+
+-- custom "third" value is available
+DROP TABLE reloptions_test;
+CREATE TABLE reloptions_test(i INT) WITH (vacuum_index_cleanup=auto);
+SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass;
+
-- Test vacuum_truncate option
DROP TABLE reloptions_test;
diff --git a/src/tools/pgindent/typedefs.list b/src/tools/pgindent/typedefs.list
index 3f3a888fd0e..1c8610fd46c 100644
--- a/src/tools/pgindent/typedefs.list
+++ b/src/tools/pgindent/typedefs.list
@@ -3952,6 +3952,7 @@ pg_sha512_ctx
pg_snapshot
pg_special_case
pg_stack_base_t
+pg_ternary
pg_time_t
pg_time_usec_t
pg_tz
@@ -4079,6 +4080,7 @@ relopt_kind
relopt_parse_elt
relopt_real
relopt_string
+relopt_ternary
relopt_type
relopt_value
relopts_validator
--
2.47.3
--c36t3fndxhkzbl5z--
^ permalink raw reply [nested|flat] 868+ messages in thread
* [PATCH v3] Introduce ternary reloptions
@ 2026-01-16 15:04 Álvaro Herrera <[email protected]>
0 siblings, 0 replies; 868+ messages in thread
From: Álvaro Herrera @ 2026-01-16 15:04 UTC (permalink / raw)
Introduce ternary reloption as a replacement for current
`vacuum_truncate` implementation. Remove the `vacuum_truncate_set`
separate flag and use TERNARY_UNSET instead.
This could also be used for other options such as `vacuum_index_cleanup`
and `buffering`, but lets get the scaffolding in first.
Discussion: https://postgr.es/m/3474141.usfYGdeWWP@thinkpad-pgpro
---
src/backend/access/common/reloptions.c | 137 ++++++++++++++++++-----
src/backend/commands/vacuum.c | 4 +-
src/include/access/reloptions.h | 27 ++---
src/include/postgres.h | 15 +++
src/include/utils/rel.h | 3 +-
src/test/regress/expected/reloptions.out | 36 ++++++
src/test/regress/sql/reloptions.sql | 21 ++++
src/tools/pgindent/typedefs.list | 2 +
8 files changed, 202 insertions(+), 43 deletions(-)
diff --git a/src/backend/access/common/reloptions.c b/src/backend/access/common/reloptions.c
index 0b83f98ed5f..6f1f577581a 100644
--- a/src/backend/access/common/reloptions.c
+++ b/src/backend/access/common/reloptions.c
@@ -40,9 +40,9 @@
*
* To add an option:
*
- * (i) decide on a type (bool, integer, real, enum, string), name, default
- * value, upper and lower bounds (if applicable); for strings, consider a
- * validation routine.
+ * (i) decide on a type (bool, ternary, integer, real, enum, string), name,
+ * default value, upper and lower bounds (if applicable); for strings,
+ * consider a validation routine.
* (ii) add a record below (or use add_<type>_reloption).
* (iii) add it to the appropriate options struct (perhaps StdRdOptions)
* (iv) add it to the appropriate handling routine (perhaps
@@ -147,15 +147,6 @@ static relopt_bool boolRelOpts[] =
},
false
},
- {
- {
- "vacuum_truncate",
- "Enables vacuum to truncate empty pages at the end of this table",
- RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
- ShareUpdateExclusiveLock
- },
- true
- },
{
{
"deduplicate_items",
@@ -170,6 +161,25 @@ static relopt_bool boolRelOpts[] =
{{NULL}}
};
+static relopt_ternary ternaryRelOpts[] =
+{
+ {
+ {
+ "vacuum_truncate",
+ "Enables vacuum to truncate empty pages at the end of this table",
+ RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
+ ShareUpdateExclusiveLock
+ },
+ TERNARY_UNSET
+ },
+ /* list terminator */
+ {
+ {
+ NULL
+ }
+ }
+};
+
static relopt_int intRelOpts[] =
{
{
@@ -609,6 +619,13 @@ initialize_reloptions(void)
boolRelOpts[i].gen.lockmode));
j++;
}
+ for (i = 0; ternaryRelOpts[i].gen.name; i++)
+ {
+ Assert(DoLockModesConflict(ternaryRelOpts[i].gen.lockmode,
+ ternaryRelOpts[i].gen.lockmode));
+ j++;
+ }
+
for (i = 0; intRelOpts[i].gen.name; i++)
{
Assert(DoLockModesConflict(intRelOpts[i].gen.lockmode,
@@ -649,6 +666,14 @@ initialize_reloptions(void)
j++;
}
+ for (i = 0; ternaryRelOpts[i].gen.name; i++)
+ {
+ relOpts[j] = &ternaryRelOpts[i].gen;
+ relOpts[j]->type = RELOPT_TYPE_TERNARY;
+ relOpts[j]->namelen = strlen(relOpts[j]->name);
+ j++;
+ }
+
for (i = 0; intRelOpts[i].gen.name; i++)
{
relOpts[j] = &intRelOpts[i].gen;
@@ -809,6 +834,9 @@ allocate_reloption(bits32 kinds, int type, const char *name, const char *desc,
case RELOPT_TYPE_BOOL:
size = sizeof(relopt_bool);
break;
+ case RELOPT_TYPE_TERNARY:
+ size = sizeof(relopt_ternary);
+ break;
case RELOPT_TYPE_INT:
size = sizeof(relopt_int);
break;
@@ -892,6 +920,57 @@ add_local_bool_reloption(local_relopts *relopts, const char *name,
add_local_reloption(relopts, (relopt_gen *) newoption, offset);
}
+/*
+ * init_ternary_reloption
+ * Allocate and initialize a new ternary reloption
+ */
+static relopt_ternary *
+init_ternary_reloption(bits32 kinds, const char *name, const char *desc,
+ pg_ternary default_val, LOCKMODE lockmode)
+{
+ relopt_ternary *newoption;
+
+ newoption = (relopt_ternary *)
+ allocate_reloption(kinds, RELOPT_TYPE_TERNARY, name, desc, lockmode);
+ newoption->default_val = default_val;
+
+ return newoption;
+}
+
+/*
+ * add_ternary_reloption
+ * Add a new ternary reloption
+ */
+void
+add_ternary_reloption(bits32 kinds, const char *name, const char *desc,
+ pg_ternary default_val, LOCKMODE lockmode)
+{
+ relopt_ternary *newoption;
+
+ newoption =
+ init_ternary_reloption(kinds, name, desc, default_val, lockmode);
+
+ add_reloption((relopt_gen *) newoption);
+}
+
+/*
+ * add_local_ternary_reloption
+ * Add a new ternary local reloption
+ *
+ * 'offset' is offset of ternary-typed field.
+ */
+void
+add_local_ternary_reloption(local_relopts *relopts, const char *name,
+ const char *desc, pg_ternary default_val,
+ int offset)
+{
+ relopt_ternary *newoption;
+
+ newoption =
+ init_ternary_reloption(RELOPT_KIND_LOCAL, name, desc, default_val, 0);
+
+ add_local_reloption(relopts, (relopt_gen *) newoption, offset);
+}
/*
* init_real_reloption
@@ -1626,6 +1705,19 @@ parse_one_reloption(relopt_value *option, char *text_str, int text_len,
option->gen->name, value)));
}
break;
+ case RELOPT_TYPE_TERNARY:
+ {
+ bool b;
+
+ parsed = parse_bool(value, &b);
+ option->values.ternary_val = b ? TERNARY_TRUE : TERNARY_FALSE;
+ if (validate && !parsed)
+ ereport(ERROR,
+ (errcode(ERRCODE_INVALID_PARAMETER_VALUE),
+ errmsg("invalid value for ternary option \"%s\": %s",
+ option->gen->name, value)));
+ }
+ break;
case RELOPT_TYPE_INT:
{
relopt_int *optint = (relopt_int *) option->gen;
@@ -1789,17 +1881,6 @@ fillRelOptions(void *rdopts, Size basesize,
char *itempos = ((char *) rdopts) + elems[j].offset;
char *string_val;
- /*
- * If isset_offset is provided, store whether the reloption is
- * set there.
- */
- if (elems[j].isset_offset > 0)
- {
- char *setpos = ((char *) rdopts) + elems[j].isset_offset;
-
- *(bool *) setpos = options[i].isset;
- }
-
switch (options[i].gen->type)
{
case RELOPT_TYPE_BOOL:
@@ -1807,6 +1888,11 @@ fillRelOptions(void *rdopts, Size basesize,
options[i].values.bool_val :
((relopt_bool *) options[i].gen)->default_val;
break;
+ case RELOPT_TYPE_TERNARY:
+ *(pg_ternary *) itempos = options[i].isset ?
+ options[i].values.ternary_val :
+ ((relopt_ternary *) options[i].gen)->default_val;
+ break;
case RELOPT_TYPE_INT:
*(int *) itempos = options[i].isset ?
options[i].values.int_val :
@@ -1923,8 +2009,8 @@ default_reloptions(Datum reloptions, bool validate, relopt_kind kind)
offsetof(StdRdOptions, parallel_workers)},
{"vacuum_index_cleanup", RELOPT_TYPE_ENUM,
offsetof(StdRdOptions, vacuum_index_cleanup)},
- {"vacuum_truncate", RELOPT_TYPE_BOOL,
- offsetof(StdRdOptions, vacuum_truncate), offsetof(StdRdOptions, vacuum_truncate_set)},
+ {"vacuum_truncate", RELOPT_TYPE_TERNARY,
+ offsetof(StdRdOptions, vacuum_truncate)},
{"vacuum_max_eager_freeze_failure_rate", RELOPT_TYPE_REAL,
offsetof(StdRdOptions, vacuum_max_eager_freeze_failure_rate)}
};
@@ -2004,7 +2090,6 @@ build_local_reloptions(local_relopts *relopts, Datum options, bool validate)
elems[i].optname = opt->option->name;
elems[i].opttype = opt->option->type;
elems[i].offset = opt->offset;
- elems[i].isset_offset = 0; /* not supported for local relopts yet */
i++;
}
diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c
index aa4fbec143f..696eab9bd97 100644
--- a/src/backend/commands/vacuum.c
+++ b/src/backend/commands/vacuum.c
@@ -2224,9 +2224,9 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams params,
{
StdRdOptions *opts = (StdRdOptions *) rel->rd_options;
- if (opts && opts->vacuum_truncate_set)
+ if (opts && opts->vacuum_truncate != TERNARY_UNSET)
{
- if (opts->vacuum_truncate)
+ if (opts->vacuum_truncate == TERNARY_TRUE)
params.truncate = VACOPTVALUE_ENABLED;
else
params.truncate = VACOPTVALUE_DISABLED;
diff --git a/src/include/access/reloptions.h b/src/include/access/reloptions.h
index 2f08e1b0cf0..dfbef2babf2 100644
--- a/src/include/access/reloptions.h
+++ b/src/include/access/reloptions.h
@@ -29,6 +29,7 @@
typedef enum relopt_type
{
RELOPT_TYPE_BOOL,
+ RELOPT_TYPE_TERNARY, /* on, off, unset */
RELOPT_TYPE_INT,
RELOPT_TYPE_REAL,
RELOPT_TYPE_ENUM,
@@ -80,6 +81,7 @@ typedef struct relopt_value
union
{
bool bool_val;
+ pg_ternary ternary_val;
int int_val;
double real_val;
int enum_val;
@@ -94,6 +96,12 @@ typedef struct relopt_bool
bool default_val;
} relopt_bool;
+typedef struct relopt_ternary
+{
+ relopt_gen gen;
+ int default_val;
+} relopt_ternary;
+
typedef struct relopt_int
{
relopt_gen gen;
@@ -152,19 +160,6 @@ typedef struct
const char *optname; /* option's name */
relopt_type opttype; /* option's datatype */
int offset; /* offset of field in result struct */
-
- /*
- * isset_offset is an optional offset of a field in the result struct that
- * stores whether the option is explicitly set for the relation or if it
- * just picked up the default value. In most cases, this can be
- * accomplished by giving the reloption a special out-of-range default
- * value (e.g., some integer reloptions use -2), but this isn't always
- * possible. For example, a Boolean reloption cannot be given an
- * out-of-range default, so we need another way to discover the source of
- * its value. This offset is only used if given a value greater than
- * zero.
- */
- int isset_offset;
} relopt_parse_elt;
/* Local reloption definition */
@@ -195,6 +190,9 @@ typedef struct local_relopts
extern relopt_kind add_reloption_kind(void);
extern void add_bool_reloption(bits32 kinds, const char *name, const char *desc,
bool default_val, LOCKMODE lockmode);
+extern void add_ternary_reloption(bits32 kinds, const char *name,
+ const char *desc, pg_ternary default_val,
+ LOCKMODE lockmode);
extern void add_int_reloption(bits32 kinds, const char *name, const char *desc,
int default_val, int min_val, int max_val,
LOCKMODE lockmode);
@@ -214,6 +212,9 @@ extern void register_reloptions_validator(local_relopts *relopts,
extern void add_local_bool_reloption(local_relopts *relopts, const char *name,
const char *desc, bool default_val,
int offset);
+extern void add_local_ternary_reloption(local_relopts *relopts,
+ const char *name, const char *desc,
+ pg_ternary default_val, int offset);
extern void add_local_int_reloption(local_relopts *relopts, const char *name,
const char *desc, int default_val,
int min_val, int max_val, int offset);
diff --git a/src/include/postgres.h b/src/include/postgres.h
index 7d93fbce709..47c88780776 100644
--- a/src/include/postgres.h
+++ b/src/include/postgres.h
@@ -543,6 +543,21 @@ Float8GetDatum(float8 X)
* ----------------------------------------------------------------
*/
+/*
+ * pg_ternary
+ * Boolean value with an extra "unset" value
+ *
+ * This enum can be used for values that want to distinguish between true,
+ * false, and unset.
+*/
+
+typedef enum pg_ternary
+{
+ TERNARY_FALSE = 0,
+ TERNARY_TRUE = 1,
+ TERNARY_UNSET = -1
+} pg_ternary;
+
/*
* NON_EXEC_STATIC: It's sometimes useful to define a variable or function
* that is normally static but extern when using EXEC_BACKEND (see
diff --git a/src/include/utils/rel.h b/src/include/utils/rel.h
index d03ab247788..5feb18a5373 100644
--- a/src/include/utils/rel.h
+++ b/src/include/utils/rel.h
@@ -347,8 +347,7 @@ typedef struct StdRdOptions
bool user_catalog_table; /* use as an additional catalog relation */
int parallel_workers; /* max number of parallel workers */
StdRdOptIndexCleanup vacuum_index_cleanup; /* controls index vacuuming */
- bool vacuum_truncate; /* enables vacuum to truncate a relation */
- bool vacuum_truncate_set; /* whether vacuum_truncate is set */
+ pg_ternary vacuum_truncate; /* enables vacuum to truncate a relation */
/*
* Fraction of pages in a relation that vacuum can eagerly scan and fail
diff --git a/src/test/regress/expected/reloptions.out b/src/test/regress/expected/reloptions.out
index 9de19b4e3f1..1c99f79ab01 100644
--- a/src/test/regress/expected/reloptions.out
+++ b/src/test/regress/expected/reloptions.out
@@ -98,6 +98,42 @@ SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass;
{fillfactor=13,autovacuum_enabled=false}
(1 row)
+-- Tests for future (FIXME) ternary options
+-- behave as boolean option: accept unassigned name and truncated value
+DROP TABLE reloptions_test;
+CREATE TABLE reloptions_test(i INT) WITH (vacuum_truncate);
+SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass;
+ reloptions
+------------------------
+ {vacuum_truncate=true}
+(1 row)
+
+DROP TABLE reloptions_test;
+CREATE TABLE reloptions_test(i INT) WITH (vacuum_truncate=FaLS);
+SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass;
+ reloptions
+------------------------
+ {vacuum_truncate=fals}
+(1 row)
+
+-- preferred "true" alias is used when storing
+DROP TABLE reloptions_test;
+CREATE TABLE reloptions_test(i INT) WITH (vacuum_index_cleanup=on);
+SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass;
+ reloptions
+---------------------------
+ {vacuum_index_cleanup=on}
+(1 row)
+
+-- custom "third" value is available
+DROP TABLE reloptions_test;
+CREATE TABLE reloptions_test(i INT) WITH (vacuum_index_cleanup=auto);
+SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass;
+ reloptions
+-----------------------------
+ {vacuum_index_cleanup=auto}
+(1 row)
+
-- Test vacuum_truncate option
DROP TABLE reloptions_test;
CREATE TEMP TABLE reloptions_test(i INT NOT NULL, j text)
diff --git a/src/test/regress/sql/reloptions.sql b/src/test/regress/sql/reloptions.sql
index 24fbe0b478d..f5980dafcbc 100644
--- a/src/test/regress/sql/reloptions.sql
+++ b/src/test/regress/sql/reloptions.sql
@@ -59,6 +59,27 @@ UPDATE pg_class
ALTER TABLE reloptions_test RESET (illegal_option);
SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass;
+-- Tests for future (FIXME) ternary options
+
+-- behave as boolean option: accept unassigned name and truncated value
+DROP TABLE reloptions_test;
+CREATE TABLE reloptions_test(i INT) WITH (vacuum_truncate);
+SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass;
+
+DROP TABLE reloptions_test;
+CREATE TABLE reloptions_test(i INT) WITH (vacuum_truncate=FaLS);
+SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass;
+
+-- preferred "true" alias is used when storing
+DROP TABLE reloptions_test;
+CREATE TABLE reloptions_test(i INT) WITH (vacuum_index_cleanup=on);
+SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass;
+
+-- custom "third" value is available
+DROP TABLE reloptions_test;
+CREATE TABLE reloptions_test(i INT) WITH (vacuum_index_cleanup=auto);
+SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass;
+
-- Test vacuum_truncate option
DROP TABLE reloptions_test;
diff --git a/src/tools/pgindent/typedefs.list b/src/tools/pgindent/typedefs.list
index 3f3a888fd0e..1c8610fd46c 100644
--- a/src/tools/pgindent/typedefs.list
+++ b/src/tools/pgindent/typedefs.list
@@ -3952,6 +3952,7 @@ pg_sha512_ctx
pg_snapshot
pg_special_case
pg_stack_base_t
+pg_ternary
pg_time_t
pg_time_usec_t
pg_tz
@@ -4079,6 +4080,7 @@ relopt_kind
relopt_parse_elt
relopt_real
relopt_string
+relopt_ternary
relopt_type
relopt_value
relopts_validator
--
2.47.3
--c36t3fndxhkzbl5z--
^ permalink raw reply [nested|flat] 868+ messages in thread
* [PATCH v3] Introduce ternary reloptions
@ 2026-01-16 15:04 Álvaro Herrera <[email protected]>
0 siblings, 0 replies; 868+ messages in thread
From: Álvaro Herrera @ 2026-01-16 15:04 UTC (permalink / raw)
Introduce ternary reloption as a replacement for current
`vacuum_truncate` implementation. Remove the `vacuum_truncate_set`
separate flag and use TERNARY_UNSET instead.
This could also be used for other options such as `vacuum_index_cleanup`
and `buffering`, but lets get the scaffolding in first.
Discussion: https://postgr.es/m/3474141.usfYGdeWWP@thinkpad-pgpro
---
src/backend/access/common/reloptions.c | 137 ++++++++++++++++++-----
src/backend/commands/vacuum.c | 4 +-
src/include/access/reloptions.h | 27 ++---
src/include/postgres.h | 15 +++
src/include/utils/rel.h | 3 +-
src/test/regress/expected/reloptions.out | 36 ++++++
src/test/regress/sql/reloptions.sql | 21 ++++
src/tools/pgindent/typedefs.list | 2 +
8 files changed, 202 insertions(+), 43 deletions(-)
diff --git a/src/backend/access/common/reloptions.c b/src/backend/access/common/reloptions.c
index 0b83f98ed5f..6f1f577581a 100644
--- a/src/backend/access/common/reloptions.c
+++ b/src/backend/access/common/reloptions.c
@@ -40,9 +40,9 @@
*
* To add an option:
*
- * (i) decide on a type (bool, integer, real, enum, string), name, default
- * value, upper and lower bounds (if applicable); for strings, consider a
- * validation routine.
+ * (i) decide on a type (bool, ternary, integer, real, enum, string), name,
+ * default value, upper and lower bounds (if applicable); for strings,
+ * consider a validation routine.
* (ii) add a record below (or use add_<type>_reloption).
* (iii) add it to the appropriate options struct (perhaps StdRdOptions)
* (iv) add it to the appropriate handling routine (perhaps
@@ -147,15 +147,6 @@ static relopt_bool boolRelOpts[] =
},
false
},
- {
- {
- "vacuum_truncate",
- "Enables vacuum to truncate empty pages at the end of this table",
- RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
- ShareUpdateExclusiveLock
- },
- true
- },
{
{
"deduplicate_items",
@@ -170,6 +161,25 @@ static relopt_bool boolRelOpts[] =
{{NULL}}
};
+static relopt_ternary ternaryRelOpts[] =
+{
+ {
+ {
+ "vacuum_truncate",
+ "Enables vacuum to truncate empty pages at the end of this table",
+ RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
+ ShareUpdateExclusiveLock
+ },
+ TERNARY_UNSET
+ },
+ /* list terminator */
+ {
+ {
+ NULL
+ }
+ }
+};
+
static relopt_int intRelOpts[] =
{
{
@@ -609,6 +619,13 @@ initialize_reloptions(void)
boolRelOpts[i].gen.lockmode));
j++;
}
+ for (i = 0; ternaryRelOpts[i].gen.name; i++)
+ {
+ Assert(DoLockModesConflict(ternaryRelOpts[i].gen.lockmode,
+ ternaryRelOpts[i].gen.lockmode));
+ j++;
+ }
+
for (i = 0; intRelOpts[i].gen.name; i++)
{
Assert(DoLockModesConflict(intRelOpts[i].gen.lockmode,
@@ -649,6 +666,14 @@ initialize_reloptions(void)
j++;
}
+ for (i = 0; ternaryRelOpts[i].gen.name; i++)
+ {
+ relOpts[j] = &ternaryRelOpts[i].gen;
+ relOpts[j]->type = RELOPT_TYPE_TERNARY;
+ relOpts[j]->namelen = strlen(relOpts[j]->name);
+ j++;
+ }
+
for (i = 0; intRelOpts[i].gen.name; i++)
{
relOpts[j] = &intRelOpts[i].gen;
@@ -809,6 +834,9 @@ allocate_reloption(bits32 kinds, int type, const char *name, const char *desc,
case RELOPT_TYPE_BOOL:
size = sizeof(relopt_bool);
break;
+ case RELOPT_TYPE_TERNARY:
+ size = sizeof(relopt_ternary);
+ break;
case RELOPT_TYPE_INT:
size = sizeof(relopt_int);
break;
@@ -892,6 +920,57 @@ add_local_bool_reloption(local_relopts *relopts, const char *name,
add_local_reloption(relopts, (relopt_gen *) newoption, offset);
}
+/*
+ * init_ternary_reloption
+ * Allocate and initialize a new ternary reloption
+ */
+static relopt_ternary *
+init_ternary_reloption(bits32 kinds, const char *name, const char *desc,
+ pg_ternary default_val, LOCKMODE lockmode)
+{
+ relopt_ternary *newoption;
+
+ newoption = (relopt_ternary *)
+ allocate_reloption(kinds, RELOPT_TYPE_TERNARY, name, desc, lockmode);
+ newoption->default_val = default_val;
+
+ return newoption;
+}
+
+/*
+ * add_ternary_reloption
+ * Add a new ternary reloption
+ */
+void
+add_ternary_reloption(bits32 kinds, const char *name, const char *desc,
+ pg_ternary default_val, LOCKMODE lockmode)
+{
+ relopt_ternary *newoption;
+
+ newoption =
+ init_ternary_reloption(kinds, name, desc, default_val, lockmode);
+
+ add_reloption((relopt_gen *) newoption);
+}
+
+/*
+ * add_local_ternary_reloption
+ * Add a new ternary local reloption
+ *
+ * 'offset' is offset of ternary-typed field.
+ */
+void
+add_local_ternary_reloption(local_relopts *relopts, const char *name,
+ const char *desc, pg_ternary default_val,
+ int offset)
+{
+ relopt_ternary *newoption;
+
+ newoption =
+ init_ternary_reloption(RELOPT_KIND_LOCAL, name, desc, default_val, 0);
+
+ add_local_reloption(relopts, (relopt_gen *) newoption, offset);
+}
/*
* init_real_reloption
@@ -1626,6 +1705,19 @@ parse_one_reloption(relopt_value *option, char *text_str, int text_len,
option->gen->name, value)));
}
break;
+ case RELOPT_TYPE_TERNARY:
+ {
+ bool b;
+
+ parsed = parse_bool(value, &b);
+ option->values.ternary_val = b ? TERNARY_TRUE : TERNARY_FALSE;
+ if (validate && !parsed)
+ ereport(ERROR,
+ (errcode(ERRCODE_INVALID_PARAMETER_VALUE),
+ errmsg("invalid value for ternary option \"%s\": %s",
+ option->gen->name, value)));
+ }
+ break;
case RELOPT_TYPE_INT:
{
relopt_int *optint = (relopt_int *) option->gen;
@@ -1789,17 +1881,6 @@ fillRelOptions(void *rdopts, Size basesize,
char *itempos = ((char *) rdopts) + elems[j].offset;
char *string_val;
- /*
- * If isset_offset is provided, store whether the reloption is
- * set there.
- */
- if (elems[j].isset_offset > 0)
- {
- char *setpos = ((char *) rdopts) + elems[j].isset_offset;
-
- *(bool *) setpos = options[i].isset;
- }
-
switch (options[i].gen->type)
{
case RELOPT_TYPE_BOOL:
@@ -1807,6 +1888,11 @@ fillRelOptions(void *rdopts, Size basesize,
options[i].values.bool_val :
((relopt_bool *) options[i].gen)->default_val;
break;
+ case RELOPT_TYPE_TERNARY:
+ *(pg_ternary *) itempos = options[i].isset ?
+ options[i].values.ternary_val :
+ ((relopt_ternary *) options[i].gen)->default_val;
+ break;
case RELOPT_TYPE_INT:
*(int *) itempos = options[i].isset ?
options[i].values.int_val :
@@ -1923,8 +2009,8 @@ default_reloptions(Datum reloptions, bool validate, relopt_kind kind)
offsetof(StdRdOptions, parallel_workers)},
{"vacuum_index_cleanup", RELOPT_TYPE_ENUM,
offsetof(StdRdOptions, vacuum_index_cleanup)},
- {"vacuum_truncate", RELOPT_TYPE_BOOL,
- offsetof(StdRdOptions, vacuum_truncate), offsetof(StdRdOptions, vacuum_truncate_set)},
+ {"vacuum_truncate", RELOPT_TYPE_TERNARY,
+ offsetof(StdRdOptions, vacuum_truncate)},
{"vacuum_max_eager_freeze_failure_rate", RELOPT_TYPE_REAL,
offsetof(StdRdOptions, vacuum_max_eager_freeze_failure_rate)}
};
@@ -2004,7 +2090,6 @@ build_local_reloptions(local_relopts *relopts, Datum options, bool validate)
elems[i].optname = opt->option->name;
elems[i].opttype = opt->option->type;
elems[i].offset = opt->offset;
- elems[i].isset_offset = 0; /* not supported for local relopts yet */
i++;
}
diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c
index aa4fbec143f..696eab9bd97 100644
--- a/src/backend/commands/vacuum.c
+++ b/src/backend/commands/vacuum.c
@@ -2224,9 +2224,9 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams params,
{
StdRdOptions *opts = (StdRdOptions *) rel->rd_options;
- if (opts && opts->vacuum_truncate_set)
+ if (opts && opts->vacuum_truncate != TERNARY_UNSET)
{
- if (opts->vacuum_truncate)
+ if (opts->vacuum_truncate == TERNARY_TRUE)
params.truncate = VACOPTVALUE_ENABLED;
else
params.truncate = VACOPTVALUE_DISABLED;
diff --git a/src/include/access/reloptions.h b/src/include/access/reloptions.h
index 2f08e1b0cf0..dfbef2babf2 100644
--- a/src/include/access/reloptions.h
+++ b/src/include/access/reloptions.h
@@ -29,6 +29,7 @@
typedef enum relopt_type
{
RELOPT_TYPE_BOOL,
+ RELOPT_TYPE_TERNARY, /* on, off, unset */
RELOPT_TYPE_INT,
RELOPT_TYPE_REAL,
RELOPT_TYPE_ENUM,
@@ -80,6 +81,7 @@ typedef struct relopt_value
union
{
bool bool_val;
+ pg_ternary ternary_val;
int int_val;
double real_val;
int enum_val;
@@ -94,6 +96,12 @@ typedef struct relopt_bool
bool default_val;
} relopt_bool;
+typedef struct relopt_ternary
+{
+ relopt_gen gen;
+ int default_val;
+} relopt_ternary;
+
typedef struct relopt_int
{
relopt_gen gen;
@@ -152,19 +160,6 @@ typedef struct
const char *optname; /* option's name */
relopt_type opttype; /* option's datatype */
int offset; /* offset of field in result struct */
-
- /*
- * isset_offset is an optional offset of a field in the result struct that
- * stores whether the option is explicitly set for the relation or if it
- * just picked up the default value. In most cases, this can be
- * accomplished by giving the reloption a special out-of-range default
- * value (e.g., some integer reloptions use -2), but this isn't always
- * possible. For example, a Boolean reloption cannot be given an
- * out-of-range default, so we need another way to discover the source of
- * its value. This offset is only used if given a value greater than
- * zero.
- */
- int isset_offset;
} relopt_parse_elt;
/* Local reloption definition */
@@ -195,6 +190,9 @@ typedef struct local_relopts
extern relopt_kind add_reloption_kind(void);
extern void add_bool_reloption(bits32 kinds, const char *name, const char *desc,
bool default_val, LOCKMODE lockmode);
+extern void add_ternary_reloption(bits32 kinds, const char *name,
+ const char *desc, pg_ternary default_val,
+ LOCKMODE lockmode);
extern void add_int_reloption(bits32 kinds, const char *name, const char *desc,
int default_val, int min_val, int max_val,
LOCKMODE lockmode);
@@ -214,6 +212,9 @@ extern void register_reloptions_validator(local_relopts *relopts,
extern void add_local_bool_reloption(local_relopts *relopts, const char *name,
const char *desc, bool default_val,
int offset);
+extern void add_local_ternary_reloption(local_relopts *relopts,
+ const char *name, const char *desc,
+ pg_ternary default_val, int offset);
extern void add_local_int_reloption(local_relopts *relopts, const char *name,
const char *desc, int default_val,
int min_val, int max_val, int offset);
diff --git a/src/include/postgres.h b/src/include/postgres.h
index 7d93fbce709..47c88780776 100644
--- a/src/include/postgres.h
+++ b/src/include/postgres.h
@@ -543,6 +543,21 @@ Float8GetDatum(float8 X)
* ----------------------------------------------------------------
*/
+/*
+ * pg_ternary
+ * Boolean value with an extra "unset" value
+ *
+ * This enum can be used for values that want to distinguish between true,
+ * false, and unset.
+*/
+
+typedef enum pg_ternary
+{
+ TERNARY_FALSE = 0,
+ TERNARY_TRUE = 1,
+ TERNARY_UNSET = -1
+} pg_ternary;
+
/*
* NON_EXEC_STATIC: It's sometimes useful to define a variable or function
* that is normally static but extern when using EXEC_BACKEND (see
diff --git a/src/include/utils/rel.h b/src/include/utils/rel.h
index d03ab247788..5feb18a5373 100644
--- a/src/include/utils/rel.h
+++ b/src/include/utils/rel.h
@@ -347,8 +347,7 @@ typedef struct StdRdOptions
bool user_catalog_table; /* use as an additional catalog relation */
int parallel_workers; /* max number of parallel workers */
StdRdOptIndexCleanup vacuum_index_cleanup; /* controls index vacuuming */
- bool vacuum_truncate; /* enables vacuum to truncate a relation */
- bool vacuum_truncate_set; /* whether vacuum_truncate is set */
+ pg_ternary vacuum_truncate; /* enables vacuum to truncate a relation */
/*
* Fraction of pages in a relation that vacuum can eagerly scan and fail
diff --git a/src/test/regress/expected/reloptions.out b/src/test/regress/expected/reloptions.out
index 9de19b4e3f1..1c99f79ab01 100644
--- a/src/test/regress/expected/reloptions.out
+++ b/src/test/regress/expected/reloptions.out
@@ -98,6 +98,42 @@ SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass;
{fillfactor=13,autovacuum_enabled=false}
(1 row)
+-- Tests for future (FIXME) ternary options
+-- behave as boolean option: accept unassigned name and truncated value
+DROP TABLE reloptions_test;
+CREATE TABLE reloptions_test(i INT) WITH (vacuum_truncate);
+SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass;
+ reloptions
+------------------------
+ {vacuum_truncate=true}
+(1 row)
+
+DROP TABLE reloptions_test;
+CREATE TABLE reloptions_test(i INT) WITH (vacuum_truncate=FaLS);
+SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass;
+ reloptions
+------------------------
+ {vacuum_truncate=fals}
+(1 row)
+
+-- preferred "true" alias is used when storing
+DROP TABLE reloptions_test;
+CREATE TABLE reloptions_test(i INT) WITH (vacuum_index_cleanup=on);
+SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass;
+ reloptions
+---------------------------
+ {vacuum_index_cleanup=on}
+(1 row)
+
+-- custom "third" value is available
+DROP TABLE reloptions_test;
+CREATE TABLE reloptions_test(i INT) WITH (vacuum_index_cleanup=auto);
+SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass;
+ reloptions
+-----------------------------
+ {vacuum_index_cleanup=auto}
+(1 row)
+
-- Test vacuum_truncate option
DROP TABLE reloptions_test;
CREATE TEMP TABLE reloptions_test(i INT NOT NULL, j text)
diff --git a/src/test/regress/sql/reloptions.sql b/src/test/regress/sql/reloptions.sql
index 24fbe0b478d..f5980dafcbc 100644
--- a/src/test/regress/sql/reloptions.sql
+++ b/src/test/regress/sql/reloptions.sql
@@ -59,6 +59,27 @@ UPDATE pg_class
ALTER TABLE reloptions_test RESET (illegal_option);
SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass;
+-- Tests for future (FIXME) ternary options
+
+-- behave as boolean option: accept unassigned name and truncated value
+DROP TABLE reloptions_test;
+CREATE TABLE reloptions_test(i INT) WITH (vacuum_truncate);
+SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass;
+
+DROP TABLE reloptions_test;
+CREATE TABLE reloptions_test(i INT) WITH (vacuum_truncate=FaLS);
+SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass;
+
+-- preferred "true" alias is used when storing
+DROP TABLE reloptions_test;
+CREATE TABLE reloptions_test(i INT) WITH (vacuum_index_cleanup=on);
+SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass;
+
+-- custom "third" value is available
+DROP TABLE reloptions_test;
+CREATE TABLE reloptions_test(i INT) WITH (vacuum_index_cleanup=auto);
+SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass;
+
-- Test vacuum_truncate option
DROP TABLE reloptions_test;
diff --git a/src/tools/pgindent/typedefs.list b/src/tools/pgindent/typedefs.list
index 3f3a888fd0e..1c8610fd46c 100644
--- a/src/tools/pgindent/typedefs.list
+++ b/src/tools/pgindent/typedefs.list
@@ -3952,6 +3952,7 @@ pg_sha512_ctx
pg_snapshot
pg_special_case
pg_stack_base_t
+pg_ternary
pg_time_t
pg_time_usec_t
pg_tz
@@ -4079,6 +4080,7 @@ relopt_kind
relopt_parse_elt
relopt_real
relopt_string
+relopt_ternary
relopt_type
relopt_value
relopts_validator
--
2.47.3
--c36t3fndxhkzbl5z--
^ permalink raw reply [nested|flat] 868+ messages in thread
* [PATCH v3] Introduce ternary reloptions
@ 2026-01-16 15:04 Álvaro Herrera <[email protected]>
0 siblings, 0 replies; 868+ messages in thread
From: Álvaro Herrera @ 2026-01-16 15:04 UTC (permalink / raw)
Introduce ternary reloption as a replacement for current
`vacuum_truncate` implementation. Remove the `vacuum_truncate_set`
separate flag and use TERNARY_UNSET instead.
This could also be used for other options such as `vacuum_index_cleanup`
and `buffering`, but lets get the scaffolding in first.
Discussion: https://postgr.es/m/3474141.usfYGdeWWP@thinkpad-pgpro
---
src/backend/access/common/reloptions.c | 137 ++++++++++++++++++-----
src/backend/commands/vacuum.c | 4 +-
src/include/access/reloptions.h | 27 ++---
src/include/postgres.h | 15 +++
src/include/utils/rel.h | 3 +-
src/test/regress/expected/reloptions.out | 36 ++++++
src/test/regress/sql/reloptions.sql | 21 ++++
src/tools/pgindent/typedefs.list | 2 +
8 files changed, 202 insertions(+), 43 deletions(-)
diff --git a/src/backend/access/common/reloptions.c b/src/backend/access/common/reloptions.c
index 0b83f98ed5f..6f1f577581a 100644
--- a/src/backend/access/common/reloptions.c
+++ b/src/backend/access/common/reloptions.c
@@ -40,9 +40,9 @@
*
* To add an option:
*
- * (i) decide on a type (bool, integer, real, enum, string), name, default
- * value, upper and lower bounds (if applicable); for strings, consider a
- * validation routine.
+ * (i) decide on a type (bool, ternary, integer, real, enum, string), name,
+ * default value, upper and lower bounds (if applicable); for strings,
+ * consider a validation routine.
* (ii) add a record below (or use add_<type>_reloption).
* (iii) add it to the appropriate options struct (perhaps StdRdOptions)
* (iv) add it to the appropriate handling routine (perhaps
@@ -147,15 +147,6 @@ static relopt_bool boolRelOpts[] =
},
false
},
- {
- {
- "vacuum_truncate",
- "Enables vacuum to truncate empty pages at the end of this table",
- RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
- ShareUpdateExclusiveLock
- },
- true
- },
{
{
"deduplicate_items",
@@ -170,6 +161,25 @@ static relopt_bool boolRelOpts[] =
{{NULL}}
};
+static relopt_ternary ternaryRelOpts[] =
+{
+ {
+ {
+ "vacuum_truncate",
+ "Enables vacuum to truncate empty pages at the end of this table",
+ RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
+ ShareUpdateExclusiveLock
+ },
+ TERNARY_UNSET
+ },
+ /* list terminator */
+ {
+ {
+ NULL
+ }
+ }
+};
+
static relopt_int intRelOpts[] =
{
{
@@ -609,6 +619,13 @@ initialize_reloptions(void)
boolRelOpts[i].gen.lockmode));
j++;
}
+ for (i = 0; ternaryRelOpts[i].gen.name; i++)
+ {
+ Assert(DoLockModesConflict(ternaryRelOpts[i].gen.lockmode,
+ ternaryRelOpts[i].gen.lockmode));
+ j++;
+ }
+
for (i = 0; intRelOpts[i].gen.name; i++)
{
Assert(DoLockModesConflict(intRelOpts[i].gen.lockmode,
@@ -649,6 +666,14 @@ initialize_reloptions(void)
j++;
}
+ for (i = 0; ternaryRelOpts[i].gen.name; i++)
+ {
+ relOpts[j] = &ternaryRelOpts[i].gen;
+ relOpts[j]->type = RELOPT_TYPE_TERNARY;
+ relOpts[j]->namelen = strlen(relOpts[j]->name);
+ j++;
+ }
+
for (i = 0; intRelOpts[i].gen.name; i++)
{
relOpts[j] = &intRelOpts[i].gen;
@@ -809,6 +834,9 @@ allocate_reloption(bits32 kinds, int type, const char *name, const char *desc,
case RELOPT_TYPE_BOOL:
size = sizeof(relopt_bool);
break;
+ case RELOPT_TYPE_TERNARY:
+ size = sizeof(relopt_ternary);
+ break;
case RELOPT_TYPE_INT:
size = sizeof(relopt_int);
break;
@@ -892,6 +920,57 @@ add_local_bool_reloption(local_relopts *relopts, const char *name,
add_local_reloption(relopts, (relopt_gen *) newoption, offset);
}
+/*
+ * init_ternary_reloption
+ * Allocate and initialize a new ternary reloption
+ */
+static relopt_ternary *
+init_ternary_reloption(bits32 kinds, const char *name, const char *desc,
+ pg_ternary default_val, LOCKMODE lockmode)
+{
+ relopt_ternary *newoption;
+
+ newoption = (relopt_ternary *)
+ allocate_reloption(kinds, RELOPT_TYPE_TERNARY, name, desc, lockmode);
+ newoption->default_val = default_val;
+
+ return newoption;
+}
+
+/*
+ * add_ternary_reloption
+ * Add a new ternary reloption
+ */
+void
+add_ternary_reloption(bits32 kinds, const char *name, const char *desc,
+ pg_ternary default_val, LOCKMODE lockmode)
+{
+ relopt_ternary *newoption;
+
+ newoption =
+ init_ternary_reloption(kinds, name, desc, default_val, lockmode);
+
+ add_reloption((relopt_gen *) newoption);
+}
+
+/*
+ * add_local_ternary_reloption
+ * Add a new ternary local reloption
+ *
+ * 'offset' is offset of ternary-typed field.
+ */
+void
+add_local_ternary_reloption(local_relopts *relopts, const char *name,
+ const char *desc, pg_ternary default_val,
+ int offset)
+{
+ relopt_ternary *newoption;
+
+ newoption =
+ init_ternary_reloption(RELOPT_KIND_LOCAL, name, desc, default_val, 0);
+
+ add_local_reloption(relopts, (relopt_gen *) newoption, offset);
+}
/*
* init_real_reloption
@@ -1626,6 +1705,19 @@ parse_one_reloption(relopt_value *option, char *text_str, int text_len,
option->gen->name, value)));
}
break;
+ case RELOPT_TYPE_TERNARY:
+ {
+ bool b;
+
+ parsed = parse_bool(value, &b);
+ option->values.ternary_val = b ? TERNARY_TRUE : TERNARY_FALSE;
+ if (validate && !parsed)
+ ereport(ERROR,
+ (errcode(ERRCODE_INVALID_PARAMETER_VALUE),
+ errmsg("invalid value for ternary option \"%s\": %s",
+ option->gen->name, value)));
+ }
+ break;
case RELOPT_TYPE_INT:
{
relopt_int *optint = (relopt_int *) option->gen;
@@ -1789,17 +1881,6 @@ fillRelOptions(void *rdopts, Size basesize,
char *itempos = ((char *) rdopts) + elems[j].offset;
char *string_val;
- /*
- * If isset_offset is provided, store whether the reloption is
- * set there.
- */
- if (elems[j].isset_offset > 0)
- {
- char *setpos = ((char *) rdopts) + elems[j].isset_offset;
-
- *(bool *) setpos = options[i].isset;
- }
-
switch (options[i].gen->type)
{
case RELOPT_TYPE_BOOL:
@@ -1807,6 +1888,11 @@ fillRelOptions(void *rdopts, Size basesize,
options[i].values.bool_val :
((relopt_bool *) options[i].gen)->default_val;
break;
+ case RELOPT_TYPE_TERNARY:
+ *(pg_ternary *) itempos = options[i].isset ?
+ options[i].values.ternary_val :
+ ((relopt_ternary *) options[i].gen)->default_val;
+ break;
case RELOPT_TYPE_INT:
*(int *) itempos = options[i].isset ?
options[i].values.int_val :
@@ -1923,8 +2009,8 @@ default_reloptions(Datum reloptions, bool validate, relopt_kind kind)
offsetof(StdRdOptions, parallel_workers)},
{"vacuum_index_cleanup", RELOPT_TYPE_ENUM,
offsetof(StdRdOptions, vacuum_index_cleanup)},
- {"vacuum_truncate", RELOPT_TYPE_BOOL,
- offsetof(StdRdOptions, vacuum_truncate), offsetof(StdRdOptions, vacuum_truncate_set)},
+ {"vacuum_truncate", RELOPT_TYPE_TERNARY,
+ offsetof(StdRdOptions, vacuum_truncate)},
{"vacuum_max_eager_freeze_failure_rate", RELOPT_TYPE_REAL,
offsetof(StdRdOptions, vacuum_max_eager_freeze_failure_rate)}
};
@@ -2004,7 +2090,6 @@ build_local_reloptions(local_relopts *relopts, Datum options, bool validate)
elems[i].optname = opt->option->name;
elems[i].opttype = opt->option->type;
elems[i].offset = opt->offset;
- elems[i].isset_offset = 0; /* not supported for local relopts yet */
i++;
}
diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c
index aa4fbec143f..696eab9bd97 100644
--- a/src/backend/commands/vacuum.c
+++ b/src/backend/commands/vacuum.c
@@ -2224,9 +2224,9 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams params,
{
StdRdOptions *opts = (StdRdOptions *) rel->rd_options;
- if (opts && opts->vacuum_truncate_set)
+ if (opts && opts->vacuum_truncate != TERNARY_UNSET)
{
- if (opts->vacuum_truncate)
+ if (opts->vacuum_truncate == TERNARY_TRUE)
params.truncate = VACOPTVALUE_ENABLED;
else
params.truncate = VACOPTVALUE_DISABLED;
diff --git a/src/include/access/reloptions.h b/src/include/access/reloptions.h
index 2f08e1b0cf0..dfbef2babf2 100644
--- a/src/include/access/reloptions.h
+++ b/src/include/access/reloptions.h
@@ -29,6 +29,7 @@
typedef enum relopt_type
{
RELOPT_TYPE_BOOL,
+ RELOPT_TYPE_TERNARY, /* on, off, unset */
RELOPT_TYPE_INT,
RELOPT_TYPE_REAL,
RELOPT_TYPE_ENUM,
@@ -80,6 +81,7 @@ typedef struct relopt_value
union
{
bool bool_val;
+ pg_ternary ternary_val;
int int_val;
double real_val;
int enum_val;
@@ -94,6 +96,12 @@ typedef struct relopt_bool
bool default_val;
} relopt_bool;
+typedef struct relopt_ternary
+{
+ relopt_gen gen;
+ int default_val;
+} relopt_ternary;
+
typedef struct relopt_int
{
relopt_gen gen;
@@ -152,19 +160,6 @@ typedef struct
const char *optname; /* option's name */
relopt_type opttype; /* option's datatype */
int offset; /* offset of field in result struct */
-
- /*
- * isset_offset is an optional offset of a field in the result struct that
- * stores whether the option is explicitly set for the relation or if it
- * just picked up the default value. In most cases, this can be
- * accomplished by giving the reloption a special out-of-range default
- * value (e.g., some integer reloptions use -2), but this isn't always
- * possible. For example, a Boolean reloption cannot be given an
- * out-of-range default, so we need another way to discover the source of
- * its value. This offset is only used if given a value greater than
- * zero.
- */
- int isset_offset;
} relopt_parse_elt;
/* Local reloption definition */
@@ -195,6 +190,9 @@ typedef struct local_relopts
extern relopt_kind add_reloption_kind(void);
extern void add_bool_reloption(bits32 kinds, const char *name, const char *desc,
bool default_val, LOCKMODE lockmode);
+extern void add_ternary_reloption(bits32 kinds, const char *name,
+ const char *desc, pg_ternary default_val,
+ LOCKMODE lockmode);
extern void add_int_reloption(bits32 kinds, const char *name, const char *desc,
int default_val, int min_val, int max_val,
LOCKMODE lockmode);
@@ -214,6 +212,9 @@ extern void register_reloptions_validator(local_relopts *relopts,
extern void add_local_bool_reloption(local_relopts *relopts, const char *name,
const char *desc, bool default_val,
int offset);
+extern void add_local_ternary_reloption(local_relopts *relopts,
+ const char *name, const char *desc,
+ pg_ternary default_val, int offset);
extern void add_local_int_reloption(local_relopts *relopts, const char *name,
const char *desc, int default_val,
int min_val, int max_val, int offset);
diff --git a/src/include/postgres.h b/src/include/postgres.h
index 7d93fbce709..47c88780776 100644
--- a/src/include/postgres.h
+++ b/src/include/postgres.h
@@ -543,6 +543,21 @@ Float8GetDatum(float8 X)
* ----------------------------------------------------------------
*/
+/*
+ * pg_ternary
+ * Boolean value with an extra "unset" value
+ *
+ * This enum can be used for values that want to distinguish between true,
+ * false, and unset.
+*/
+
+typedef enum pg_ternary
+{
+ TERNARY_FALSE = 0,
+ TERNARY_TRUE = 1,
+ TERNARY_UNSET = -1
+} pg_ternary;
+
/*
* NON_EXEC_STATIC: It's sometimes useful to define a variable or function
* that is normally static but extern when using EXEC_BACKEND (see
diff --git a/src/include/utils/rel.h b/src/include/utils/rel.h
index d03ab247788..5feb18a5373 100644
--- a/src/include/utils/rel.h
+++ b/src/include/utils/rel.h
@@ -347,8 +347,7 @@ typedef struct StdRdOptions
bool user_catalog_table; /* use as an additional catalog relation */
int parallel_workers; /* max number of parallel workers */
StdRdOptIndexCleanup vacuum_index_cleanup; /* controls index vacuuming */
- bool vacuum_truncate; /* enables vacuum to truncate a relation */
- bool vacuum_truncate_set; /* whether vacuum_truncate is set */
+ pg_ternary vacuum_truncate; /* enables vacuum to truncate a relation */
/*
* Fraction of pages in a relation that vacuum can eagerly scan and fail
diff --git a/src/test/regress/expected/reloptions.out b/src/test/regress/expected/reloptions.out
index 9de19b4e3f1..1c99f79ab01 100644
--- a/src/test/regress/expected/reloptions.out
+++ b/src/test/regress/expected/reloptions.out
@@ -98,6 +98,42 @@ SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass;
{fillfactor=13,autovacuum_enabled=false}
(1 row)
+-- Tests for future (FIXME) ternary options
+-- behave as boolean option: accept unassigned name and truncated value
+DROP TABLE reloptions_test;
+CREATE TABLE reloptions_test(i INT) WITH (vacuum_truncate);
+SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass;
+ reloptions
+------------------------
+ {vacuum_truncate=true}
+(1 row)
+
+DROP TABLE reloptions_test;
+CREATE TABLE reloptions_test(i INT) WITH (vacuum_truncate=FaLS);
+SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass;
+ reloptions
+------------------------
+ {vacuum_truncate=fals}
+(1 row)
+
+-- preferred "true" alias is used when storing
+DROP TABLE reloptions_test;
+CREATE TABLE reloptions_test(i INT) WITH (vacuum_index_cleanup=on);
+SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass;
+ reloptions
+---------------------------
+ {vacuum_index_cleanup=on}
+(1 row)
+
+-- custom "third" value is available
+DROP TABLE reloptions_test;
+CREATE TABLE reloptions_test(i INT) WITH (vacuum_index_cleanup=auto);
+SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass;
+ reloptions
+-----------------------------
+ {vacuum_index_cleanup=auto}
+(1 row)
+
-- Test vacuum_truncate option
DROP TABLE reloptions_test;
CREATE TEMP TABLE reloptions_test(i INT NOT NULL, j text)
diff --git a/src/test/regress/sql/reloptions.sql b/src/test/regress/sql/reloptions.sql
index 24fbe0b478d..f5980dafcbc 100644
--- a/src/test/regress/sql/reloptions.sql
+++ b/src/test/regress/sql/reloptions.sql
@@ -59,6 +59,27 @@ UPDATE pg_class
ALTER TABLE reloptions_test RESET (illegal_option);
SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass;
+-- Tests for future (FIXME) ternary options
+
+-- behave as boolean option: accept unassigned name and truncated value
+DROP TABLE reloptions_test;
+CREATE TABLE reloptions_test(i INT) WITH (vacuum_truncate);
+SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass;
+
+DROP TABLE reloptions_test;
+CREATE TABLE reloptions_test(i INT) WITH (vacuum_truncate=FaLS);
+SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass;
+
+-- preferred "true" alias is used when storing
+DROP TABLE reloptions_test;
+CREATE TABLE reloptions_test(i INT) WITH (vacuum_index_cleanup=on);
+SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass;
+
+-- custom "third" value is available
+DROP TABLE reloptions_test;
+CREATE TABLE reloptions_test(i INT) WITH (vacuum_index_cleanup=auto);
+SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass;
+
-- Test vacuum_truncate option
DROP TABLE reloptions_test;
diff --git a/src/tools/pgindent/typedefs.list b/src/tools/pgindent/typedefs.list
index 3f3a888fd0e..1c8610fd46c 100644
--- a/src/tools/pgindent/typedefs.list
+++ b/src/tools/pgindent/typedefs.list
@@ -3952,6 +3952,7 @@ pg_sha512_ctx
pg_snapshot
pg_special_case
pg_stack_base_t
+pg_ternary
pg_time_t
pg_time_usec_t
pg_tz
@@ -4079,6 +4080,7 @@ relopt_kind
relopt_parse_elt
relopt_real
relopt_string
+relopt_ternary
relopt_type
relopt_value
relopts_validator
--
2.47.3
--c36t3fndxhkzbl5z--
^ permalink raw reply [nested|flat] 868+ messages in thread
* [PATCH v3] Introduce ternary reloptions
@ 2026-01-16 15:04 Álvaro Herrera <[email protected]>
0 siblings, 0 replies; 868+ messages in thread
From: Álvaro Herrera @ 2026-01-16 15:04 UTC (permalink / raw)
Introduce ternary reloption as a replacement for current
`vacuum_truncate` implementation. Remove the `vacuum_truncate_set`
separate flag and use TERNARY_UNSET instead.
This could also be used for other options such as `vacuum_index_cleanup`
and `buffering`, but lets get the scaffolding in first.
Discussion: https://postgr.es/m/3474141.usfYGdeWWP@thinkpad-pgpro
---
src/backend/access/common/reloptions.c | 137 ++++++++++++++++++-----
src/backend/commands/vacuum.c | 4 +-
src/include/access/reloptions.h | 27 ++---
src/include/postgres.h | 15 +++
src/include/utils/rel.h | 3 +-
src/test/regress/expected/reloptions.out | 36 ++++++
src/test/regress/sql/reloptions.sql | 21 ++++
src/tools/pgindent/typedefs.list | 2 +
8 files changed, 202 insertions(+), 43 deletions(-)
diff --git a/src/backend/access/common/reloptions.c b/src/backend/access/common/reloptions.c
index 0b83f98ed5f..6f1f577581a 100644
--- a/src/backend/access/common/reloptions.c
+++ b/src/backend/access/common/reloptions.c
@@ -40,9 +40,9 @@
*
* To add an option:
*
- * (i) decide on a type (bool, integer, real, enum, string), name, default
- * value, upper and lower bounds (if applicable); for strings, consider a
- * validation routine.
+ * (i) decide on a type (bool, ternary, integer, real, enum, string), name,
+ * default value, upper and lower bounds (if applicable); for strings,
+ * consider a validation routine.
* (ii) add a record below (or use add_<type>_reloption).
* (iii) add it to the appropriate options struct (perhaps StdRdOptions)
* (iv) add it to the appropriate handling routine (perhaps
@@ -147,15 +147,6 @@ static relopt_bool boolRelOpts[] =
},
false
},
- {
- {
- "vacuum_truncate",
- "Enables vacuum to truncate empty pages at the end of this table",
- RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
- ShareUpdateExclusiveLock
- },
- true
- },
{
{
"deduplicate_items",
@@ -170,6 +161,25 @@ static relopt_bool boolRelOpts[] =
{{NULL}}
};
+static relopt_ternary ternaryRelOpts[] =
+{
+ {
+ {
+ "vacuum_truncate",
+ "Enables vacuum to truncate empty pages at the end of this table",
+ RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
+ ShareUpdateExclusiveLock
+ },
+ TERNARY_UNSET
+ },
+ /* list terminator */
+ {
+ {
+ NULL
+ }
+ }
+};
+
static relopt_int intRelOpts[] =
{
{
@@ -609,6 +619,13 @@ initialize_reloptions(void)
boolRelOpts[i].gen.lockmode));
j++;
}
+ for (i = 0; ternaryRelOpts[i].gen.name; i++)
+ {
+ Assert(DoLockModesConflict(ternaryRelOpts[i].gen.lockmode,
+ ternaryRelOpts[i].gen.lockmode));
+ j++;
+ }
+
for (i = 0; intRelOpts[i].gen.name; i++)
{
Assert(DoLockModesConflict(intRelOpts[i].gen.lockmode,
@@ -649,6 +666,14 @@ initialize_reloptions(void)
j++;
}
+ for (i = 0; ternaryRelOpts[i].gen.name; i++)
+ {
+ relOpts[j] = &ternaryRelOpts[i].gen;
+ relOpts[j]->type = RELOPT_TYPE_TERNARY;
+ relOpts[j]->namelen = strlen(relOpts[j]->name);
+ j++;
+ }
+
for (i = 0; intRelOpts[i].gen.name; i++)
{
relOpts[j] = &intRelOpts[i].gen;
@@ -809,6 +834,9 @@ allocate_reloption(bits32 kinds, int type, const char *name, const char *desc,
case RELOPT_TYPE_BOOL:
size = sizeof(relopt_bool);
break;
+ case RELOPT_TYPE_TERNARY:
+ size = sizeof(relopt_ternary);
+ break;
case RELOPT_TYPE_INT:
size = sizeof(relopt_int);
break;
@@ -892,6 +920,57 @@ add_local_bool_reloption(local_relopts *relopts, const char *name,
add_local_reloption(relopts, (relopt_gen *) newoption, offset);
}
+/*
+ * init_ternary_reloption
+ * Allocate and initialize a new ternary reloption
+ */
+static relopt_ternary *
+init_ternary_reloption(bits32 kinds, const char *name, const char *desc,
+ pg_ternary default_val, LOCKMODE lockmode)
+{
+ relopt_ternary *newoption;
+
+ newoption = (relopt_ternary *)
+ allocate_reloption(kinds, RELOPT_TYPE_TERNARY, name, desc, lockmode);
+ newoption->default_val = default_val;
+
+ return newoption;
+}
+
+/*
+ * add_ternary_reloption
+ * Add a new ternary reloption
+ */
+void
+add_ternary_reloption(bits32 kinds, const char *name, const char *desc,
+ pg_ternary default_val, LOCKMODE lockmode)
+{
+ relopt_ternary *newoption;
+
+ newoption =
+ init_ternary_reloption(kinds, name, desc, default_val, lockmode);
+
+ add_reloption((relopt_gen *) newoption);
+}
+
+/*
+ * add_local_ternary_reloption
+ * Add a new ternary local reloption
+ *
+ * 'offset' is offset of ternary-typed field.
+ */
+void
+add_local_ternary_reloption(local_relopts *relopts, const char *name,
+ const char *desc, pg_ternary default_val,
+ int offset)
+{
+ relopt_ternary *newoption;
+
+ newoption =
+ init_ternary_reloption(RELOPT_KIND_LOCAL, name, desc, default_val, 0);
+
+ add_local_reloption(relopts, (relopt_gen *) newoption, offset);
+}
/*
* init_real_reloption
@@ -1626,6 +1705,19 @@ parse_one_reloption(relopt_value *option, char *text_str, int text_len,
option->gen->name, value)));
}
break;
+ case RELOPT_TYPE_TERNARY:
+ {
+ bool b;
+
+ parsed = parse_bool(value, &b);
+ option->values.ternary_val = b ? TERNARY_TRUE : TERNARY_FALSE;
+ if (validate && !parsed)
+ ereport(ERROR,
+ (errcode(ERRCODE_INVALID_PARAMETER_VALUE),
+ errmsg("invalid value for ternary option \"%s\": %s",
+ option->gen->name, value)));
+ }
+ break;
case RELOPT_TYPE_INT:
{
relopt_int *optint = (relopt_int *) option->gen;
@@ -1789,17 +1881,6 @@ fillRelOptions(void *rdopts, Size basesize,
char *itempos = ((char *) rdopts) + elems[j].offset;
char *string_val;
- /*
- * If isset_offset is provided, store whether the reloption is
- * set there.
- */
- if (elems[j].isset_offset > 0)
- {
- char *setpos = ((char *) rdopts) + elems[j].isset_offset;
-
- *(bool *) setpos = options[i].isset;
- }
-
switch (options[i].gen->type)
{
case RELOPT_TYPE_BOOL:
@@ -1807,6 +1888,11 @@ fillRelOptions(void *rdopts, Size basesize,
options[i].values.bool_val :
((relopt_bool *) options[i].gen)->default_val;
break;
+ case RELOPT_TYPE_TERNARY:
+ *(pg_ternary *) itempos = options[i].isset ?
+ options[i].values.ternary_val :
+ ((relopt_ternary *) options[i].gen)->default_val;
+ break;
case RELOPT_TYPE_INT:
*(int *) itempos = options[i].isset ?
options[i].values.int_val :
@@ -1923,8 +2009,8 @@ default_reloptions(Datum reloptions, bool validate, relopt_kind kind)
offsetof(StdRdOptions, parallel_workers)},
{"vacuum_index_cleanup", RELOPT_TYPE_ENUM,
offsetof(StdRdOptions, vacuum_index_cleanup)},
- {"vacuum_truncate", RELOPT_TYPE_BOOL,
- offsetof(StdRdOptions, vacuum_truncate), offsetof(StdRdOptions, vacuum_truncate_set)},
+ {"vacuum_truncate", RELOPT_TYPE_TERNARY,
+ offsetof(StdRdOptions, vacuum_truncate)},
{"vacuum_max_eager_freeze_failure_rate", RELOPT_TYPE_REAL,
offsetof(StdRdOptions, vacuum_max_eager_freeze_failure_rate)}
};
@@ -2004,7 +2090,6 @@ build_local_reloptions(local_relopts *relopts, Datum options, bool validate)
elems[i].optname = opt->option->name;
elems[i].opttype = opt->option->type;
elems[i].offset = opt->offset;
- elems[i].isset_offset = 0; /* not supported for local relopts yet */
i++;
}
diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c
index aa4fbec143f..696eab9bd97 100644
--- a/src/backend/commands/vacuum.c
+++ b/src/backend/commands/vacuum.c
@@ -2224,9 +2224,9 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams params,
{
StdRdOptions *opts = (StdRdOptions *) rel->rd_options;
- if (opts && opts->vacuum_truncate_set)
+ if (opts && opts->vacuum_truncate != TERNARY_UNSET)
{
- if (opts->vacuum_truncate)
+ if (opts->vacuum_truncate == TERNARY_TRUE)
params.truncate = VACOPTVALUE_ENABLED;
else
params.truncate = VACOPTVALUE_DISABLED;
diff --git a/src/include/access/reloptions.h b/src/include/access/reloptions.h
index 2f08e1b0cf0..dfbef2babf2 100644
--- a/src/include/access/reloptions.h
+++ b/src/include/access/reloptions.h
@@ -29,6 +29,7 @@
typedef enum relopt_type
{
RELOPT_TYPE_BOOL,
+ RELOPT_TYPE_TERNARY, /* on, off, unset */
RELOPT_TYPE_INT,
RELOPT_TYPE_REAL,
RELOPT_TYPE_ENUM,
@@ -80,6 +81,7 @@ typedef struct relopt_value
union
{
bool bool_val;
+ pg_ternary ternary_val;
int int_val;
double real_val;
int enum_val;
@@ -94,6 +96,12 @@ typedef struct relopt_bool
bool default_val;
} relopt_bool;
+typedef struct relopt_ternary
+{
+ relopt_gen gen;
+ int default_val;
+} relopt_ternary;
+
typedef struct relopt_int
{
relopt_gen gen;
@@ -152,19 +160,6 @@ typedef struct
const char *optname; /* option's name */
relopt_type opttype; /* option's datatype */
int offset; /* offset of field in result struct */
-
- /*
- * isset_offset is an optional offset of a field in the result struct that
- * stores whether the option is explicitly set for the relation or if it
- * just picked up the default value. In most cases, this can be
- * accomplished by giving the reloption a special out-of-range default
- * value (e.g., some integer reloptions use -2), but this isn't always
- * possible. For example, a Boolean reloption cannot be given an
- * out-of-range default, so we need another way to discover the source of
- * its value. This offset is only used if given a value greater than
- * zero.
- */
- int isset_offset;
} relopt_parse_elt;
/* Local reloption definition */
@@ -195,6 +190,9 @@ typedef struct local_relopts
extern relopt_kind add_reloption_kind(void);
extern void add_bool_reloption(bits32 kinds, const char *name, const char *desc,
bool default_val, LOCKMODE lockmode);
+extern void add_ternary_reloption(bits32 kinds, const char *name,
+ const char *desc, pg_ternary default_val,
+ LOCKMODE lockmode);
extern void add_int_reloption(bits32 kinds, const char *name, const char *desc,
int default_val, int min_val, int max_val,
LOCKMODE lockmode);
@@ -214,6 +212,9 @@ extern void register_reloptions_validator(local_relopts *relopts,
extern void add_local_bool_reloption(local_relopts *relopts, const char *name,
const char *desc, bool default_val,
int offset);
+extern void add_local_ternary_reloption(local_relopts *relopts,
+ const char *name, const char *desc,
+ pg_ternary default_val, int offset);
extern void add_local_int_reloption(local_relopts *relopts, const char *name,
const char *desc, int default_val,
int min_val, int max_val, int offset);
diff --git a/src/include/postgres.h b/src/include/postgres.h
index 7d93fbce709..47c88780776 100644
--- a/src/include/postgres.h
+++ b/src/include/postgres.h
@@ -543,6 +543,21 @@ Float8GetDatum(float8 X)
* ----------------------------------------------------------------
*/
+/*
+ * pg_ternary
+ * Boolean value with an extra "unset" value
+ *
+ * This enum can be used for values that want to distinguish between true,
+ * false, and unset.
+*/
+
+typedef enum pg_ternary
+{
+ TERNARY_FALSE = 0,
+ TERNARY_TRUE = 1,
+ TERNARY_UNSET = -1
+} pg_ternary;
+
/*
* NON_EXEC_STATIC: It's sometimes useful to define a variable or function
* that is normally static but extern when using EXEC_BACKEND (see
diff --git a/src/include/utils/rel.h b/src/include/utils/rel.h
index d03ab247788..5feb18a5373 100644
--- a/src/include/utils/rel.h
+++ b/src/include/utils/rel.h
@@ -347,8 +347,7 @@ typedef struct StdRdOptions
bool user_catalog_table; /* use as an additional catalog relation */
int parallel_workers; /* max number of parallel workers */
StdRdOptIndexCleanup vacuum_index_cleanup; /* controls index vacuuming */
- bool vacuum_truncate; /* enables vacuum to truncate a relation */
- bool vacuum_truncate_set; /* whether vacuum_truncate is set */
+ pg_ternary vacuum_truncate; /* enables vacuum to truncate a relation */
/*
* Fraction of pages in a relation that vacuum can eagerly scan and fail
diff --git a/src/test/regress/expected/reloptions.out b/src/test/regress/expected/reloptions.out
index 9de19b4e3f1..1c99f79ab01 100644
--- a/src/test/regress/expected/reloptions.out
+++ b/src/test/regress/expected/reloptions.out
@@ -98,6 +98,42 @@ SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass;
{fillfactor=13,autovacuum_enabled=false}
(1 row)
+-- Tests for future (FIXME) ternary options
+-- behave as boolean option: accept unassigned name and truncated value
+DROP TABLE reloptions_test;
+CREATE TABLE reloptions_test(i INT) WITH (vacuum_truncate);
+SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass;
+ reloptions
+------------------------
+ {vacuum_truncate=true}
+(1 row)
+
+DROP TABLE reloptions_test;
+CREATE TABLE reloptions_test(i INT) WITH (vacuum_truncate=FaLS);
+SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass;
+ reloptions
+------------------------
+ {vacuum_truncate=fals}
+(1 row)
+
+-- preferred "true" alias is used when storing
+DROP TABLE reloptions_test;
+CREATE TABLE reloptions_test(i INT) WITH (vacuum_index_cleanup=on);
+SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass;
+ reloptions
+---------------------------
+ {vacuum_index_cleanup=on}
+(1 row)
+
+-- custom "third" value is available
+DROP TABLE reloptions_test;
+CREATE TABLE reloptions_test(i INT) WITH (vacuum_index_cleanup=auto);
+SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass;
+ reloptions
+-----------------------------
+ {vacuum_index_cleanup=auto}
+(1 row)
+
-- Test vacuum_truncate option
DROP TABLE reloptions_test;
CREATE TEMP TABLE reloptions_test(i INT NOT NULL, j text)
diff --git a/src/test/regress/sql/reloptions.sql b/src/test/regress/sql/reloptions.sql
index 24fbe0b478d..f5980dafcbc 100644
--- a/src/test/regress/sql/reloptions.sql
+++ b/src/test/regress/sql/reloptions.sql
@@ -59,6 +59,27 @@ UPDATE pg_class
ALTER TABLE reloptions_test RESET (illegal_option);
SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass;
+-- Tests for future (FIXME) ternary options
+
+-- behave as boolean option: accept unassigned name and truncated value
+DROP TABLE reloptions_test;
+CREATE TABLE reloptions_test(i INT) WITH (vacuum_truncate);
+SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass;
+
+DROP TABLE reloptions_test;
+CREATE TABLE reloptions_test(i INT) WITH (vacuum_truncate=FaLS);
+SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass;
+
+-- preferred "true" alias is used when storing
+DROP TABLE reloptions_test;
+CREATE TABLE reloptions_test(i INT) WITH (vacuum_index_cleanup=on);
+SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass;
+
+-- custom "third" value is available
+DROP TABLE reloptions_test;
+CREATE TABLE reloptions_test(i INT) WITH (vacuum_index_cleanup=auto);
+SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass;
+
-- Test vacuum_truncate option
DROP TABLE reloptions_test;
diff --git a/src/tools/pgindent/typedefs.list b/src/tools/pgindent/typedefs.list
index 3f3a888fd0e..1c8610fd46c 100644
--- a/src/tools/pgindent/typedefs.list
+++ b/src/tools/pgindent/typedefs.list
@@ -3952,6 +3952,7 @@ pg_sha512_ctx
pg_snapshot
pg_special_case
pg_stack_base_t
+pg_ternary
pg_time_t
pg_time_usec_t
pg_tz
@@ -4079,6 +4080,7 @@ relopt_kind
relopt_parse_elt
relopt_real
relopt_string
+relopt_ternary
relopt_type
relopt_value
relopts_validator
--
2.47.3
--c36t3fndxhkzbl5z--
^ permalink raw reply [nested|flat] 868+ messages in thread
* [PATCH v3] Introduce ternary reloptions
@ 2026-01-16 15:04 Álvaro Herrera <[email protected]>
0 siblings, 0 replies; 868+ messages in thread
From: Álvaro Herrera @ 2026-01-16 15:04 UTC (permalink / raw)
Introduce ternary reloption as a replacement for current
`vacuum_truncate` implementation. Remove the `vacuum_truncate_set`
separate flag and use TERNARY_UNSET instead.
This could also be used for other options such as `vacuum_index_cleanup`
and `buffering`, but lets get the scaffolding in first.
Discussion: https://postgr.es/m/3474141.usfYGdeWWP@thinkpad-pgpro
---
src/backend/access/common/reloptions.c | 137 ++++++++++++++++++-----
src/backend/commands/vacuum.c | 4 +-
src/include/access/reloptions.h | 27 ++---
src/include/postgres.h | 15 +++
src/include/utils/rel.h | 3 +-
src/test/regress/expected/reloptions.out | 36 ++++++
src/test/regress/sql/reloptions.sql | 21 ++++
src/tools/pgindent/typedefs.list | 2 +
8 files changed, 202 insertions(+), 43 deletions(-)
diff --git a/src/backend/access/common/reloptions.c b/src/backend/access/common/reloptions.c
index 0b83f98ed5f..6f1f577581a 100644
--- a/src/backend/access/common/reloptions.c
+++ b/src/backend/access/common/reloptions.c
@@ -40,9 +40,9 @@
*
* To add an option:
*
- * (i) decide on a type (bool, integer, real, enum, string), name, default
- * value, upper and lower bounds (if applicable); for strings, consider a
- * validation routine.
+ * (i) decide on a type (bool, ternary, integer, real, enum, string), name,
+ * default value, upper and lower bounds (if applicable); for strings,
+ * consider a validation routine.
* (ii) add a record below (or use add_<type>_reloption).
* (iii) add it to the appropriate options struct (perhaps StdRdOptions)
* (iv) add it to the appropriate handling routine (perhaps
@@ -147,15 +147,6 @@ static relopt_bool boolRelOpts[] =
},
false
},
- {
- {
- "vacuum_truncate",
- "Enables vacuum to truncate empty pages at the end of this table",
- RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
- ShareUpdateExclusiveLock
- },
- true
- },
{
{
"deduplicate_items",
@@ -170,6 +161,25 @@ static relopt_bool boolRelOpts[] =
{{NULL}}
};
+static relopt_ternary ternaryRelOpts[] =
+{
+ {
+ {
+ "vacuum_truncate",
+ "Enables vacuum to truncate empty pages at the end of this table",
+ RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
+ ShareUpdateExclusiveLock
+ },
+ TERNARY_UNSET
+ },
+ /* list terminator */
+ {
+ {
+ NULL
+ }
+ }
+};
+
static relopt_int intRelOpts[] =
{
{
@@ -609,6 +619,13 @@ initialize_reloptions(void)
boolRelOpts[i].gen.lockmode));
j++;
}
+ for (i = 0; ternaryRelOpts[i].gen.name; i++)
+ {
+ Assert(DoLockModesConflict(ternaryRelOpts[i].gen.lockmode,
+ ternaryRelOpts[i].gen.lockmode));
+ j++;
+ }
+
for (i = 0; intRelOpts[i].gen.name; i++)
{
Assert(DoLockModesConflict(intRelOpts[i].gen.lockmode,
@@ -649,6 +666,14 @@ initialize_reloptions(void)
j++;
}
+ for (i = 0; ternaryRelOpts[i].gen.name; i++)
+ {
+ relOpts[j] = &ternaryRelOpts[i].gen;
+ relOpts[j]->type = RELOPT_TYPE_TERNARY;
+ relOpts[j]->namelen = strlen(relOpts[j]->name);
+ j++;
+ }
+
for (i = 0; intRelOpts[i].gen.name; i++)
{
relOpts[j] = &intRelOpts[i].gen;
@@ -809,6 +834,9 @@ allocate_reloption(bits32 kinds, int type, const char *name, const char *desc,
case RELOPT_TYPE_BOOL:
size = sizeof(relopt_bool);
break;
+ case RELOPT_TYPE_TERNARY:
+ size = sizeof(relopt_ternary);
+ break;
case RELOPT_TYPE_INT:
size = sizeof(relopt_int);
break;
@@ -892,6 +920,57 @@ add_local_bool_reloption(local_relopts *relopts, const char *name,
add_local_reloption(relopts, (relopt_gen *) newoption, offset);
}
+/*
+ * init_ternary_reloption
+ * Allocate and initialize a new ternary reloption
+ */
+static relopt_ternary *
+init_ternary_reloption(bits32 kinds, const char *name, const char *desc,
+ pg_ternary default_val, LOCKMODE lockmode)
+{
+ relopt_ternary *newoption;
+
+ newoption = (relopt_ternary *)
+ allocate_reloption(kinds, RELOPT_TYPE_TERNARY, name, desc, lockmode);
+ newoption->default_val = default_val;
+
+ return newoption;
+}
+
+/*
+ * add_ternary_reloption
+ * Add a new ternary reloption
+ */
+void
+add_ternary_reloption(bits32 kinds, const char *name, const char *desc,
+ pg_ternary default_val, LOCKMODE lockmode)
+{
+ relopt_ternary *newoption;
+
+ newoption =
+ init_ternary_reloption(kinds, name, desc, default_val, lockmode);
+
+ add_reloption((relopt_gen *) newoption);
+}
+
+/*
+ * add_local_ternary_reloption
+ * Add a new ternary local reloption
+ *
+ * 'offset' is offset of ternary-typed field.
+ */
+void
+add_local_ternary_reloption(local_relopts *relopts, const char *name,
+ const char *desc, pg_ternary default_val,
+ int offset)
+{
+ relopt_ternary *newoption;
+
+ newoption =
+ init_ternary_reloption(RELOPT_KIND_LOCAL, name, desc, default_val, 0);
+
+ add_local_reloption(relopts, (relopt_gen *) newoption, offset);
+}
/*
* init_real_reloption
@@ -1626,6 +1705,19 @@ parse_one_reloption(relopt_value *option, char *text_str, int text_len,
option->gen->name, value)));
}
break;
+ case RELOPT_TYPE_TERNARY:
+ {
+ bool b;
+
+ parsed = parse_bool(value, &b);
+ option->values.ternary_val = b ? TERNARY_TRUE : TERNARY_FALSE;
+ if (validate && !parsed)
+ ereport(ERROR,
+ (errcode(ERRCODE_INVALID_PARAMETER_VALUE),
+ errmsg("invalid value for ternary option \"%s\": %s",
+ option->gen->name, value)));
+ }
+ break;
case RELOPT_TYPE_INT:
{
relopt_int *optint = (relopt_int *) option->gen;
@@ -1789,17 +1881,6 @@ fillRelOptions(void *rdopts, Size basesize,
char *itempos = ((char *) rdopts) + elems[j].offset;
char *string_val;
- /*
- * If isset_offset is provided, store whether the reloption is
- * set there.
- */
- if (elems[j].isset_offset > 0)
- {
- char *setpos = ((char *) rdopts) + elems[j].isset_offset;
-
- *(bool *) setpos = options[i].isset;
- }
-
switch (options[i].gen->type)
{
case RELOPT_TYPE_BOOL:
@@ -1807,6 +1888,11 @@ fillRelOptions(void *rdopts, Size basesize,
options[i].values.bool_val :
((relopt_bool *) options[i].gen)->default_val;
break;
+ case RELOPT_TYPE_TERNARY:
+ *(pg_ternary *) itempos = options[i].isset ?
+ options[i].values.ternary_val :
+ ((relopt_ternary *) options[i].gen)->default_val;
+ break;
case RELOPT_TYPE_INT:
*(int *) itempos = options[i].isset ?
options[i].values.int_val :
@@ -1923,8 +2009,8 @@ default_reloptions(Datum reloptions, bool validate, relopt_kind kind)
offsetof(StdRdOptions, parallel_workers)},
{"vacuum_index_cleanup", RELOPT_TYPE_ENUM,
offsetof(StdRdOptions, vacuum_index_cleanup)},
- {"vacuum_truncate", RELOPT_TYPE_BOOL,
- offsetof(StdRdOptions, vacuum_truncate), offsetof(StdRdOptions, vacuum_truncate_set)},
+ {"vacuum_truncate", RELOPT_TYPE_TERNARY,
+ offsetof(StdRdOptions, vacuum_truncate)},
{"vacuum_max_eager_freeze_failure_rate", RELOPT_TYPE_REAL,
offsetof(StdRdOptions, vacuum_max_eager_freeze_failure_rate)}
};
@@ -2004,7 +2090,6 @@ build_local_reloptions(local_relopts *relopts, Datum options, bool validate)
elems[i].optname = opt->option->name;
elems[i].opttype = opt->option->type;
elems[i].offset = opt->offset;
- elems[i].isset_offset = 0; /* not supported for local relopts yet */
i++;
}
diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c
index aa4fbec143f..696eab9bd97 100644
--- a/src/backend/commands/vacuum.c
+++ b/src/backend/commands/vacuum.c
@@ -2224,9 +2224,9 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams params,
{
StdRdOptions *opts = (StdRdOptions *) rel->rd_options;
- if (opts && opts->vacuum_truncate_set)
+ if (opts && opts->vacuum_truncate != TERNARY_UNSET)
{
- if (opts->vacuum_truncate)
+ if (opts->vacuum_truncate == TERNARY_TRUE)
params.truncate = VACOPTVALUE_ENABLED;
else
params.truncate = VACOPTVALUE_DISABLED;
diff --git a/src/include/access/reloptions.h b/src/include/access/reloptions.h
index 2f08e1b0cf0..dfbef2babf2 100644
--- a/src/include/access/reloptions.h
+++ b/src/include/access/reloptions.h
@@ -29,6 +29,7 @@
typedef enum relopt_type
{
RELOPT_TYPE_BOOL,
+ RELOPT_TYPE_TERNARY, /* on, off, unset */
RELOPT_TYPE_INT,
RELOPT_TYPE_REAL,
RELOPT_TYPE_ENUM,
@@ -80,6 +81,7 @@ typedef struct relopt_value
union
{
bool bool_val;
+ pg_ternary ternary_val;
int int_val;
double real_val;
int enum_val;
@@ -94,6 +96,12 @@ typedef struct relopt_bool
bool default_val;
} relopt_bool;
+typedef struct relopt_ternary
+{
+ relopt_gen gen;
+ int default_val;
+} relopt_ternary;
+
typedef struct relopt_int
{
relopt_gen gen;
@@ -152,19 +160,6 @@ typedef struct
const char *optname; /* option's name */
relopt_type opttype; /* option's datatype */
int offset; /* offset of field in result struct */
-
- /*
- * isset_offset is an optional offset of a field in the result struct that
- * stores whether the option is explicitly set for the relation or if it
- * just picked up the default value. In most cases, this can be
- * accomplished by giving the reloption a special out-of-range default
- * value (e.g., some integer reloptions use -2), but this isn't always
- * possible. For example, a Boolean reloption cannot be given an
- * out-of-range default, so we need another way to discover the source of
- * its value. This offset is only used if given a value greater than
- * zero.
- */
- int isset_offset;
} relopt_parse_elt;
/* Local reloption definition */
@@ -195,6 +190,9 @@ typedef struct local_relopts
extern relopt_kind add_reloption_kind(void);
extern void add_bool_reloption(bits32 kinds, const char *name, const char *desc,
bool default_val, LOCKMODE lockmode);
+extern void add_ternary_reloption(bits32 kinds, const char *name,
+ const char *desc, pg_ternary default_val,
+ LOCKMODE lockmode);
extern void add_int_reloption(bits32 kinds, const char *name, const char *desc,
int default_val, int min_val, int max_val,
LOCKMODE lockmode);
@@ -214,6 +212,9 @@ extern void register_reloptions_validator(local_relopts *relopts,
extern void add_local_bool_reloption(local_relopts *relopts, const char *name,
const char *desc, bool default_val,
int offset);
+extern void add_local_ternary_reloption(local_relopts *relopts,
+ const char *name, const char *desc,
+ pg_ternary default_val, int offset);
extern void add_local_int_reloption(local_relopts *relopts, const char *name,
const char *desc, int default_val,
int min_val, int max_val, int offset);
diff --git a/src/include/postgres.h b/src/include/postgres.h
index 7d93fbce709..47c88780776 100644
--- a/src/include/postgres.h
+++ b/src/include/postgres.h
@@ -543,6 +543,21 @@ Float8GetDatum(float8 X)
* ----------------------------------------------------------------
*/
+/*
+ * pg_ternary
+ * Boolean value with an extra "unset" value
+ *
+ * This enum can be used for values that want to distinguish between true,
+ * false, and unset.
+*/
+
+typedef enum pg_ternary
+{
+ TERNARY_FALSE = 0,
+ TERNARY_TRUE = 1,
+ TERNARY_UNSET = -1
+} pg_ternary;
+
/*
* NON_EXEC_STATIC: It's sometimes useful to define a variable or function
* that is normally static but extern when using EXEC_BACKEND (see
diff --git a/src/include/utils/rel.h b/src/include/utils/rel.h
index d03ab247788..5feb18a5373 100644
--- a/src/include/utils/rel.h
+++ b/src/include/utils/rel.h
@@ -347,8 +347,7 @@ typedef struct StdRdOptions
bool user_catalog_table; /* use as an additional catalog relation */
int parallel_workers; /* max number of parallel workers */
StdRdOptIndexCleanup vacuum_index_cleanup; /* controls index vacuuming */
- bool vacuum_truncate; /* enables vacuum to truncate a relation */
- bool vacuum_truncate_set; /* whether vacuum_truncate is set */
+ pg_ternary vacuum_truncate; /* enables vacuum to truncate a relation */
/*
* Fraction of pages in a relation that vacuum can eagerly scan and fail
diff --git a/src/test/regress/expected/reloptions.out b/src/test/regress/expected/reloptions.out
index 9de19b4e3f1..1c99f79ab01 100644
--- a/src/test/regress/expected/reloptions.out
+++ b/src/test/regress/expected/reloptions.out
@@ -98,6 +98,42 @@ SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass;
{fillfactor=13,autovacuum_enabled=false}
(1 row)
+-- Tests for future (FIXME) ternary options
+-- behave as boolean option: accept unassigned name and truncated value
+DROP TABLE reloptions_test;
+CREATE TABLE reloptions_test(i INT) WITH (vacuum_truncate);
+SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass;
+ reloptions
+------------------------
+ {vacuum_truncate=true}
+(1 row)
+
+DROP TABLE reloptions_test;
+CREATE TABLE reloptions_test(i INT) WITH (vacuum_truncate=FaLS);
+SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass;
+ reloptions
+------------------------
+ {vacuum_truncate=fals}
+(1 row)
+
+-- preferred "true" alias is used when storing
+DROP TABLE reloptions_test;
+CREATE TABLE reloptions_test(i INT) WITH (vacuum_index_cleanup=on);
+SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass;
+ reloptions
+---------------------------
+ {vacuum_index_cleanup=on}
+(1 row)
+
+-- custom "third" value is available
+DROP TABLE reloptions_test;
+CREATE TABLE reloptions_test(i INT) WITH (vacuum_index_cleanup=auto);
+SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass;
+ reloptions
+-----------------------------
+ {vacuum_index_cleanup=auto}
+(1 row)
+
-- Test vacuum_truncate option
DROP TABLE reloptions_test;
CREATE TEMP TABLE reloptions_test(i INT NOT NULL, j text)
diff --git a/src/test/regress/sql/reloptions.sql b/src/test/regress/sql/reloptions.sql
index 24fbe0b478d..f5980dafcbc 100644
--- a/src/test/regress/sql/reloptions.sql
+++ b/src/test/regress/sql/reloptions.sql
@@ -59,6 +59,27 @@ UPDATE pg_class
ALTER TABLE reloptions_test RESET (illegal_option);
SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass;
+-- Tests for future (FIXME) ternary options
+
+-- behave as boolean option: accept unassigned name and truncated value
+DROP TABLE reloptions_test;
+CREATE TABLE reloptions_test(i INT) WITH (vacuum_truncate);
+SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass;
+
+DROP TABLE reloptions_test;
+CREATE TABLE reloptions_test(i INT) WITH (vacuum_truncate=FaLS);
+SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass;
+
+-- preferred "true" alias is used when storing
+DROP TABLE reloptions_test;
+CREATE TABLE reloptions_test(i INT) WITH (vacuum_index_cleanup=on);
+SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass;
+
+-- custom "third" value is available
+DROP TABLE reloptions_test;
+CREATE TABLE reloptions_test(i INT) WITH (vacuum_index_cleanup=auto);
+SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass;
+
-- Test vacuum_truncate option
DROP TABLE reloptions_test;
diff --git a/src/tools/pgindent/typedefs.list b/src/tools/pgindent/typedefs.list
index 3f3a888fd0e..1c8610fd46c 100644
--- a/src/tools/pgindent/typedefs.list
+++ b/src/tools/pgindent/typedefs.list
@@ -3952,6 +3952,7 @@ pg_sha512_ctx
pg_snapshot
pg_special_case
pg_stack_base_t
+pg_ternary
pg_time_t
pg_time_usec_t
pg_tz
@@ -4079,6 +4080,7 @@ relopt_kind
relopt_parse_elt
relopt_real
relopt_string
+relopt_ternary
relopt_type
relopt_value
relopts_validator
--
2.47.3
--c36t3fndxhkzbl5z--
^ permalink raw reply [nested|flat] 868+ messages in thread
* [PATCH v3] Introduce ternary reloptions
@ 2026-01-16 15:04 Álvaro Herrera <[email protected]>
0 siblings, 0 replies; 868+ messages in thread
From: Álvaro Herrera @ 2026-01-16 15:04 UTC (permalink / raw)
Introduce ternary reloption as a replacement for current
`vacuum_truncate` implementation. Remove the `vacuum_truncate_set`
separate flag and use TERNARY_UNSET instead.
This could also be used for other options such as `vacuum_index_cleanup`
and `buffering`, but lets get the scaffolding in first.
Discussion: https://postgr.es/m/3474141.usfYGdeWWP@thinkpad-pgpro
---
src/backend/access/common/reloptions.c | 137 ++++++++++++++++++-----
src/backend/commands/vacuum.c | 4 +-
src/include/access/reloptions.h | 27 ++---
src/include/postgres.h | 15 +++
src/include/utils/rel.h | 3 +-
src/test/regress/expected/reloptions.out | 36 ++++++
src/test/regress/sql/reloptions.sql | 21 ++++
src/tools/pgindent/typedefs.list | 2 +
8 files changed, 202 insertions(+), 43 deletions(-)
diff --git a/src/backend/access/common/reloptions.c b/src/backend/access/common/reloptions.c
index 0b83f98ed5f..6f1f577581a 100644
--- a/src/backend/access/common/reloptions.c
+++ b/src/backend/access/common/reloptions.c
@@ -40,9 +40,9 @@
*
* To add an option:
*
- * (i) decide on a type (bool, integer, real, enum, string), name, default
- * value, upper and lower bounds (if applicable); for strings, consider a
- * validation routine.
+ * (i) decide on a type (bool, ternary, integer, real, enum, string), name,
+ * default value, upper and lower bounds (if applicable); for strings,
+ * consider a validation routine.
* (ii) add a record below (or use add_<type>_reloption).
* (iii) add it to the appropriate options struct (perhaps StdRdOptions)
* (iv) add it to the appropriate handling routine (perhaps
@@ -147,15 +147,6 @@ static relopt_bool boolRelOpts[] =
},
false
},
- {
- {
- "vacuum_truncate",
- "Enables vacuum to truncate empty pages at the end of this table",
- RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
- ShareUpdateExclusiveLock
- },
- true
- },
{
{
"deduplicate_items",
@@ -170,6 +161,25 @@ static relopt_bool boolRelOpts[] =
{{NULL}}
};
+static relopt_ternary ternaryRelOpts[] =
+{
+ {
+ {
+ "vacuum_truncate",
+ "Enables vacuum to truncate empty pages at the end of this table",
+ RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
+ ShareUpdateExclusiveLock
+ },
+ TERNARY_UNSET
+ },
+ /* list terminator */
+ {
+ {
+ NULL
+ }
+ }
+};
+
static relopt_int intRelOpts[] =
{
{
@@ -609,6 +619,13 @@ initialize_reloptions(void)
boolRelOpts[i].gen.lockmode));
j++;
}
+ for (i = 0; ternaryRelOpts[i].gen.name; i++)
+ {
+ Assert(DoLockModesConflict(ternaryRelOpts[i].gen.lockmode,
+ ternaryRelOpts[i].gen.lockmode));
+ j++;
+ }
+
for (i = 0; intRelOpts[i].gen.name; i++)
{
Assert(DoLockModesConflict(intRelOpts[i].gen.lockmode,
@@ -649,6 +666,14 @@ initialize_reloptions(void)
j++;
}
+ for (i = 0; ternaryRelOpts[i].gen.name; i++)
+ {
+ relOpts[j] = &ternaryRelOpts[i].gen;
+ relOpts[j]->type = RELOPT_TYPE_TERNARY;
+ relOpts[j]->namelen = strlen(relOpts[j]->name);
+ j++;
+ }
+
for (i = 0; intRelOpts[i].gen.name; i++)
{
relOpts[j] = &intRelOpts[i].gen;
@@ -809,6 +834,9 @@ allocate_reloption(bits32 kinds, int type, const char *name, const char *desc,
case RELOPT_TYPE_BOOL:
size = sizeof(relopt_bool);
break;
+ case RELOPT_TYPE_TERNARY:
+ size = sizeof(relopt_ternary);
+ break;
case RELOPT_TYPE_INT:
size = sizeof(relopt_int);
break;
@@ -892,6 +920,57 @@ add_local_bool_reloption(local_relopts *relopts, const char *name,
add_local_reloption(relopts, (relopt_gen *) newoption, offset);
}
+/*
+ * init_ternary_reloption
+ * Allocate and initialize a new ternary reloption
+ */
+static relopt_ternary *
+init_ternary_reloption(bits32 kinds, const char *name, const char *desc,
+ pg_ternary default_val, LOCKMODE lockmode)
+{
+ relopt_ternary *newoption;
+
+ newoption = (relopt_ternary *)
+ allocate_reloption(kinds, RELOPT_TYPE_TERNARY, name, desc, lockmode);
+ newoption->default_val = default_val;
+
+ return newoption;
+}
+
+/*
+ * add_ternary_reloption
+ * Add a new ternary reloption
+ */
+void
+add_ternary_reloption(bits32 kinds, const char *name, const char *desc,
+ pg_ternary default_val, LOCKMODE lockmode)
+{
+ relopt_ternary *newoption;
+
+ newoption =
+ init_ternary_reloption(kinds, name, desc, default_val, lockmode);
+
+ add_reloption((relopt_gen *) newoption);
+}
+
+/*
+ * add_local_ternary_reloption
+ * Add a new ternary local reloption
+ *
+ * 'offset' is offset of ternary-typed field.
+ */
+void
+add_local_ternary_reloption(local_relopts *relopts, const char *name,
+ const char *desc, pg_ternary default_val,
+ int offset)
+{
+ relopt_ternary *newoption;
+
+ newoption =
+ init_ternary_reloption(RELOPT_KIND_LOCAL, name, desc, default_val, 0);
+
+ add_local_reloption(relopts, (relopt_gen *) newoption, offset);
+}
/*
* init_real_reloption
@@ -1626,6 +1705,19 @@ parse_one_reloption(relopt_value *option, char *text_str, int text_len,
option->gen->name, value)));
}
break;
+ case RELOPT_TYPE_TERNARY:
+ {
+ bool b;
+
+ parsed = parse_bool(value, &b);
+ option->values.ternary_val = b ? TERNARY_TRUE : TERNARY_FALSE;
+ if (validate && !parsed)
+ ereport(ERROR,
+ (errcode(ERRCODE_INVALID_PARAMETER_VALUE),
+ errmsg("invalid value for ternary option \"%s\": %s",
+ option->gen->name, value)));
+ }
+ break;
case RELOPT_TYPE_INT:
{
relopt_int *optint = (relopt_int *) option->gen;
@@ -1789,17 +1881,6 @@ fillRelOptions(void *rdopts, Size basesize,
char *itempos = ((char *) rdopts) + elems[j].offset;
char *string_val;
- /*
- * If isset_offset is provided, store whether the reloption is
- * set there.
- */
- if (elems[j].isset_offset > 0)
- {
- char *setpos = ((char *) rdopts) + elems[j].isset_offset;
-
- *(bool *) setpos = options[i].isset;
- }
-
switch (options[i].gen->type)
{
case RELOPT_TYPE_BOOL:
@@ -1807,6 +1888,11 @@ fillRelOptions(void *rdopts, Size basesize,
options[i].values.bool_val :
((relopt_bool *) options[i].gen)->default_val;
break;
+ case RELOPT_TYPE_TERNARY:
+ *(pg_ternary *) itempos = options[i].isset ?
+ options[i].values.ternary_val :
+ ((relopt_ternary *) options[i].gen)->default_val;
+ break;
case RELOPT_TYPE_INT:
*(int *) itempos = options[i].isset ?
options[i].values.int_val :
@@ -1923,8 +2009,8 @@ default_reloptions(Datum reloptions, bool validate, relopt_kind kind)
offsetof(StdRdOptions, parallel_workers)},
{"vacuum_index_cleanup", RELOPT_TYPE_ENUM,
offsetof(StdRdOptions, vacuum_index_cleanup)},
- {"vacuum_truncate", RELOPT_TYPE_BOOL,
- offsetof(StdRdOptions, vacuum_truncate), offsetof(StdRdOptions, vacuum_truncate_set)},
+ {"vacuum_truncate", RELOPT_TYPE_TERNARY,
+ offsetof(StdRdOptions, vacuum_truncate)},
{"vacuum_max_eager_freeze_failure_rate", RELOPT_TYPE_REAL,
offsetof(StdRdOptions, vacuum_max_eager_freeze_failure_rate)}
};
@@ -2004,7 +2090,6 @@ build_local_reloptions(local_relopts *relopts, Datum options, bool validate)
elems[i].optname = opt->option->name;
elems[i].opttype = opt->option->type;
elems[i].offset = opt->offset;
- elems[i].isset_offset = 0; /* not supported for local relopts yet */
i++;
}
diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c
index aa4fbec143f..696eab9bd97 100644
--- a/src/backend/commands/vacuum.c
+++ b/src/backend/commands/vacuum.c
@@ -2224,9 +2224,9 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams params,
{
StdRdOptions *opts = (StdRdOptions *) rel->rd_options;
- if (opts && opts->vacuum_truncate_set)
+ if (opts && opts->vacuum_truncate != TERNARY_UNSET)
{
- if (opts->vacuum_truncate)
+ if (opts->vacuum_truncate == TERNARY_TRUE)
params.truncate = VACOPTVALUE_ENABLED;
else
params.truncate = VACOPTVALUE_DISABLED;
diff --git a/src/include/access/reloptions.h b/src/include/access/reloptions.h
index 2f08e1b0cf0..dfbef2babf2 100644
--- a/src/include/access/reloptions.h
+++ b/src/include/access/reloptions.h
@@ -29,6 +29,7 @@
typedef enum relopt_type
{
RELOPT_TYPE_BOOL,
+ RELOPT_TYPE_TERNARY, /* on, off, unset */
RELOPT_TYPE_INT,
RELOPT_TYPE_REAL,
RELOPT_TYPE_ENUM,
@@ -80,6 +81,7 @@ typedef struct relopt_value
union
{
bool bool_val;
+ pg_ternary ternary_val;
int int_val;
double real_val;
int enum_val;
@@ -94,6 +96,12 @@ typedef struct relopt_bool
bool default_val;
} relopt_bool;
+typedef struct relopt_ternary
+{
+ relopt_gen gen;
+ int default_val;
+} relopt_ternary;
+
typedef struct relopt_int
{
relopt_gen gen;
@@ -152,19 +160,6 @@ typedef struct
const char *optname; /* option's name */
relopt_type opttype; /* option's datatype */
int offset; /* offset of field in result struct */
-
- /*
- * isset_offset is an optional offset of a field in the result struct that
- * stores whether the option is explicitly set for the relation or if it
- * just picked up the default value. In most cases, this can be
- * accomplished by giving the reloption a special out-of-range default
- * value (e.g., some integer reloptions use -2), but this isn't always
- * possible. For example, a Boolean reloption cannot be given an
- * out-of-range default, so we need another way to discover the source of
- * its value. This offset is only used if given a value greater than
- * zero.
- */
- int isset_offset;
} relopt_parse_elt;
/* Local reloption definition */
@@ -195,6 +190,9 @@ typedef struct local_relopts
extern relopt_kind add_reloption_kind(void);
extern void add_bool_reloption(bits32 kinds, const char *name, const char *desc,
bool default_val, LOCKMODE lockmode);
+extern void add_ternary_reloption(bits32 kinds, const char *name,
+ const char *desc, pg_ternary default_val,
+ LOCKMODE lockmode);
extern void add_int_reloption(bits32 kinds, const char *name, const char *desc,
int default_val, int min_val, int max_val,
LOCKMODE lockmode);
@@ -214,6 +212,9 @@ extern void register_reloptions_validator(local_relopts *relopts,
extern void add_local_bool_reloption(local_relopts *relopts, const char *name,
const char *desc, bool default_val,
int offset);
+extern void add_local_ternary_reloption(local_relopts *relopts,
+ const char *name, const char *desc,
+ pg_ternary default_val, int offset);
extern void add_local_int_reloption(local_relopts *relopts, const char *name,
const char *desc, int default_val,
int min_val, int max_val, int offset);
diff --git a/src/include/postgres.h b/src/include/postgres.h
index 7d93fbce709..47c88780776 100644
--- a/src/include/postgres.h
+++ b/src/include/postgres.h
@@ -543,6 +543,21 @@ Float8GetDatum(float8 X)
* ----------------------------------------------------------------
*/
+/*
+ * pg_ternary
+ * Boolean value with an extra "unset" value
+ *
+ * This enum can be used for values that want to distinguish between true,
+ * false, and unset.
+*/
+
+typedef enum pg_ternary
+{
+ TERNARY_FALSE = 0,
+ TERNARY_TRUE = 1,
+ TERNARY_UNSET = -1
+} pg_ternary;
+
/*
* NON_EXEC_STATIC: It's sometimes useful to define a variable or function
* that is normally static but extern when using EXEC_BACKEND (see
diff --git a/src/include/utils/rel.h b/src/include/utils/rel.h
index d03ab247788..5feb18a5373 100644
--- a/src/include/utils/rel.h
+++ b/src/include/utils/rel.h
@@ -347,8 +347,7 @@ typedef struct StdRdOptions
bool user_catalog_table; /* use as an additional catalog relation */
int parallel_workers; /* max number of parallel workers */
StdRdOptIndexCleanup vacuum_index_cleanup; /* controls index vacuuming */
- bool vacuum_truncate; /* enables vacuum to truncate a relation */
- bool vacuum_truncate_set; /* whether vacuum_truncate is set */
+ pg_ternary vacuum_truncate; /* enables vacuum to truncate a relation */
/*
* Fraction of pages in a relation that vacuum can eagerly scan and fail
diff --git a/src/test/regress/expected/reloptions.out b/src/test/regress/expected/reloptions.out
index 9de19b4e3f1..1c99f79ab01 100644
--- a/src/test/regress/expected/reloptions.out
+++ b/src/test/regress/expected/reloptions.out
@@ -98,6 +98,42 @@ SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass;
{fillfactor=13,autovacuum_enabled=false}
(1 row)
+-- Tests for future (FIXME) ternary options
+-- behave as boolean option: accept unassigned name and truncated value
+DROP TABLE reloptions_test;
+CREATE TABLE reloptions_test(i INT) WITH (vacuum_truncate);
+SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass;
+ reloptions
+------------------------
+ {vacuum_truncate=true}
+(1 row)
+
+DROP TABLE reloptions_test;
+CREATE TABLE reloptions_test(i INT) WITH (vacuum_truncate=FaLS);
+SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass;
+ reloptions
+------------------------
+ {vacuum_truncate=fals}
+(1 row)
+
+-- preferred "true" alias is used when storing
+DROP TABLE reloptions_test;
+CREATE TABLE reloptions_test(i INT) WITH (vacuum_index_cleanup=on);
+SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass;
+ reloptions
+---------------------------
+ {vacuum_index_cleanup=on}
+(1 row)
+
+-- custom "third" value is available
+DROP TABLE reloptions_test;
+CREATE TABLE reloptions_test(i INT) WITH (vacuum_index_cleanup=auto);
+SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass;
+ reloptions
+-----------------------------
+ {vacuum_index_cleanup=auto}
+(1 row)
+
-- Test vacuum_truncate option
DROP TABLE reloptions_test;
CREATE TEMP TABLE reloptions_test(i INT NOT NULL, j text)
diff --git a/src/test/regress/sql/reloptions.sql b/src/test/regress/sql/reloptions.sql
index 24fbe0b478d..f5980dafcbc 100644
--- a/src/test/regress/sql/reloptions.sql
+++ b/src/test/regress/sql/reloptions.sql
@@ -59,6 +59,27 @@ UPDATE pg_class
ALTER TABLE reloptions_test RESET (illegal_option);
SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass;
+-- Tests for future (FIXME) ternary options
+
+-- behave as boolean option: accept unassigned name and truncated value
+DROP TABLE reloptions_test;
+CREATE TABLE reloptions_test(i INT) WITH (vacuum_truncate);
+SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass;
+
+DROP TABLE reloptions_test;
+CREATE TABLE reloptions_test(i INT) WITH (vacuum_truncate=FaLS);
+SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass;
+
+-- preferred "true" alias is used when storing
+DROP TABLE reloptions_test;
+CREATE TABLE reloptions_test(i INT) WITH (vacuum_index_cleanup=on);
+SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass;
+
+-- custom "third" value is available
+DROP TABLE reloptions_test;
+CREATE TABLE reloptions_test(i INT) WITH (vacuum_index_cleanup=auto);
+SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass;
+
-- Test vacuum_truncate option
DROP TABLE reloptions_test;
diff --git a/src/tools/pgindent/typedefs.list b/src/tools/pgindent/typedefs.list
index 3f3a888fd0e..1c8610fd46c 100644
--- a/src/tools/pgindent/typedefs.list
+++ b/src/tools/pgindent/typedefs.list
@@ -3952,6 +3952,7 @@ pg_sha512_ctx
pg_snapshot
pg_special_case
pg_stack_base_t
+pg_ternary
pg_time_t
pg_time_usec_t
pg_tz
@@ -4079,6 +4080,7 @@ relopt_kind
relopt_parse_elt
relopt_real
relopt_string
+relopt_ternary
relopt_type
relopt_value
relopts_validator
--
2.47.3
--c36t3fndxhkzbl5z--
^ permalink raw reply [nested|flat] 868+ messages in thread
* [PATCH v3] Introduce ternary reloptions
@ 2026-01-16 15:04 Álvaro Herrera <[email protected]>
0 siblings, 0 replies; 868+ messages in thread
From: Álvaro Herrera @ 2026-01-16 15:04 UTC (permalink / raw)
Introduce ternary reloption as a replacement for current
`vacuum_truncate` implementation. Remove the `vacuum_truncate_set`
separate flag and use TERNARY_UNSET instead.
This could also be used for other options such as `vacuum_index_cleanup`
and `buffering`, but lets get the scaffolding in first.
Discussion: https://postgr.es/m/3474141.usfYGdeWWP@thinkpad-pgpro
---
src/backend/access/common/reloptions.c | 137 ++++++++++++++++++-----
src/backend/commands/vacuum.c | 4 +-
src/include/access/reloptions.h | 27 ++---
src/include/postgres.h | 15 +++
src/include/utils/rel.h | 3 +-
src/test/regress/expected/reloptions.out | 36 ++++++
src/test/regress/sql/reloptions.sql | 21 ++++
src/tools/pgindent/typedefs.list | 2 +
8 files changed, 202 insertions(+), 43 deletions(-)
diff --git a/src/backend/access/common/reloptions.c b/src/backend/access/common/reloptions.c
index 0b83f98ed5f..6f1f577581a 100644
--- a/src/backend/access/common/reloptions.c
+++ b/src/backend/access/common/reloptions.c
@@ -40,9 +40,9 @@
*
* To add an option:
*
- * (i) decide on a type (bool, integer, real, enum, string), name, default
- * value, upper and lower bounds (if applicable); for strings, consider a
- * validation routine.
+ * (i) decide on a type (bool, ternary, integer, real, enum, string), name,
+ * default value, upper and lower bounds (if applicable); for strings,
+ * consider a validation routine.
* (ii) add a record below (or use add_<type>_reloption).
* (iii) add it to the appropriate options struct (perhaps StdRdOptions)
* (iv) add it to the appropriate handling routine (perhaps
@@ -147,15 +147,6 @@ static relopt_bool boolRelOpts[] =
},
false
},
- {
- {
- "vacuum_truncate",
- "Enables vacuum to truncate empty pages at the end of this table",
- RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
- ShareUpdateExclusiveLock
- },
- true
- },
{
{
"deduplicate_items",
@@ -170,6 +161,25 @@ static relopt_bool boolRelOpts[] =
{{NULL}}
};
+static relopt_ternary ternaryRelOpts[] =
+{
+ {
+ {
+ "vacuum_truncate",
+ "Enables vacuum to truncate empty pages at the end of this table",
+ RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
+ ShareUpdateExclusiveLock
+ },
+ TERNARY_UNSET
+ },
+ /* list terminator */
+ {
+ {
+ NULL
+ }
+ }
+};
+
static relopt_int intRelOpts[] =
{
{
@@ -609,6 +619,13 @@ initialize_reloptions(void)
boolRelOpts[i].gen.lockmode));
j++;
}
+ for (i = 0; ternaryRelOpts[i].gen.name; i++)
+ {
+ Assert(DoLockModesConflict(ternaryRelOpts[i].gen.lockmode,
+ ternaryRelOpts[i].gen.lockmode));
+ j++;
+ }
+
for (i = 0; intRelOpts[i].gen.name; i++)
{
Assert(DoLockModesConflict(intRelOpts[i].gen.lockmode,
@@ -649,6 +666,14 @@ initialize_reloptions(void)
j++;
}
+ for (i = 0; ternaryRelOpts[i].gen.name; i++)
+ {
+ relOpts[j] = &ternaryRelOpts[i].gen;
+ relOpts[j]->type = RELOPT_TYPE_TERNARY;
+ relOpts[j]->namelen = strlen(relOpts[j]->name);
+ j++;
+ }
+
for (i = 0; intRelOpts[i].gen.name; i++)
{
relOpts[j] = &intRelOpts[i].gen;
@@ -809,6 +834,9 @@ allocate_reloption(bits32 kinds, int type, const char *name, const char *desc,
case RELOPT_TYPE_BOOL:
size = sizeof(relopt_bool);
break;
+ case RELOPT_TYPE_TERNARY:
+ size = sizeof(relopt_ternary);
+ break;
case RELOPT_TYPE_INT:
size = sizeof(relopt_int);
break;
@@ -892,6 +920,57 @@ add_local_bool_reloption(local_relopts *relopts, const char *name,
add_local_reloption(relopts, (relopt_gen *) newoption, offset);
}
+/*
+ * init_ternary_reloption
+ * Allocate and initialize a new ternary reloption
+ */
+static relopt_ternary *
+init_ternary_reloption(bits32 kinds, const char *name, const char *desc,
+ pg_ternary default_val, LOCKMODE lockmode)
+{
+ relopt_ternary *newoption;
+
+ newoption = (relopt_ternary *)
+ allocate_reloption(kinds, RELOPT_TYPE_TERNARY, name, desc, lockmode);
+ newoption->default_val = default_val;
+
+ return newoption;
+}
+
+/*
+ * add_ternary_reloption
+ * Add a new ternary reloption
+ */
+void
+add_ternary_reloption(bits32 kinds, const char *name, const char *desc,
+ pg_ternary default_val, LOCKMODE lockmode)
+{
+ relopt_ternary *newoption;
+
+ newoption =
+ init_ternary_reloption(kinds, name, desc, default_val, lockmode);
+
+ add_reloption((relopt_gen *) newoption);
+}
+
+/*
+ * add_local_ternary_reloption
+ * Add a new ternary local reloption
+ *
+ * 'offset' is offset of ternary-typed field.
+ */
+void
+add_local_ternary_reloption(local_relopts *relopts, const char *name,
+ const char *desc, pg_ternary default_val,
+ int offset)
+{
+ relopt_ternary *newoption;
+
+ newoption =
+ init_ternary_reloption(RELOPT_KIND_LOCAL, name, desc, default_val, 0);
+
+ add_local_reloption(relopts, (relopt_gen *) newoption, offset);
+}
/*
* init_real_reloption
@@ -1626,6 +1705,19 @@ parse_one_reloption(relopt_value *option, char *text_str, int text_len,
option->gen->name, value)));
}
break;
+ case RELOPT_TYPE_TERNARY:
+ {
+ bool b;
+
+ parsed = parse_bool(value, &b);
+ option->values.ternary_val = b ? TERNARY_TRUE : TERNARY_FALSE;
+ if (validate && !parsed)
+ ereport(ERROR,
+ (errcode(ERRCODE_INVALID_PARAMETER_VALUE),
+ errmsg("invalid value for ternary option \"%s\": %s",
+ option->gen->name, value)));
+ }
+ break;
case RELOPT_TYPE_INT:
{
relopt_int *optint = (relopt_int *) option->gen;
@@ -1789,17 +1881,6 @@ fillRelOptions(void *rdopts, Size basesize,
char *itempos = ((char *) rdopts) + elems[j].offset;
char *string_val;
- /*
- * If isset_offset is provided, store whether the reloption is
- * set there.
- */
- if (elems[j].isset_offset > 0)
- {
- char *setpos = ((char *) rdopts) + elems[j].isset_offset;
-
- *(bool *) setpos = options[i].isset;
- }
-
switch (options[i].gen->type)
{
case RELOPT_TYPE_BOOL:
@@ -1807,6 +1888,11 @@ fillRelOptions(void *rdopts, Size basesize,
options[i].values.bool_val :
((relopt_bool *) options[i].gen)->default_val;
break;
+ case RELOPT_TYPE_TERNARY:
+ *(pg_ternary *) itempos = options[i].isset ?
+ options[i].values.ternary_val :
+ ((relopt_ternary *) options[i].gen)->default_val;
+ break;
case RELOPT_TYPE_INT:
*(int *) itempos = options[i].isset ?
options[i].values.int_val :
@@ -1923,8 +2009,8 @@ default_reloptions(Datum reloptions, bool validate, relopt_kind kind)
offsetof(StdRdOptions, parallel_workers)},
{"vacuum_index_cleanup", RELOPT_TYPE_ENUM,
offsetof(StdRdOptions, vacuum_index_cleanup)},
- {"vacuum_truncate", RELOPT_TYPE_BOOL,
- offsetof(StdRdOptions, vacuum_truncate), offsetof(StdRdOptions, vacuum_truncate_set)},
+ {"vacuum_truncate", RELOPT_TYPE_TERNARY,
+ offsetof(StdRdOptions, vacuum_truncate)},
{"vacuum_max_eager_freeze_failure_rate", RELOPT_TYPE_REAL,
offsetof(StdRdOptions, vacuum_max_eager_freeze_failure_rate)}
};
@@ -2004,7 +2090,6 @@ build_local_reloptions(local_relopts *relopts, Datum options, bool validate)
elems[i].optname = opt->option->name;
elems[i].opttype = opt->option->type;
elems[i].offset = opt->offset;
- elems[i].isset_offset = 0; /* not supported for local relopts yet */
i++;
}
diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c
index aa4fbec143f..696eab9bd97 100644
--- a/src/backend/commands/vacuum.c
+++ b/src/backend/commands/vacuum.c
@@ -2224,9 +2224,9 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams params,
{
StdRdOptions *opts = (StdRdOptions *) rel->rd_options;
- if (opts && opts->vacuum_truncate_set)
+ if (opts && opts->vacuum_truncate != TERNARY_UNSET)
{
- if (opts->vacuum_truncate)
+ if (opts->vacuum_truncate == TERNARY_TRUE)
params.truncate = VACOPTVALUE_ENABLED;
else
params.truncate = VACOPTVALUE_DISABLED;
diff --git a/src/include/access/reloptions.h b/src/include/access/reloptions.h
index 2f08e1b0cf0..dfbef2babf2 100644
--- a/src/include/access/reloptions.h
+++ b/src/include/access/reloptions.h
@@ -29,6 +29,7 @@
typedef enum relopt_type
{
RELOPT_TYPE_BOOL,
+ RELOPT_TYPE_TERNARY, /* on, off, unset */
RELOPT_TYPE_INT,
RELOPT_TYPE_REAL,
RELOPT_TYPE_ENUM,
@@ -80,6 +81,7 @@ typedef struct relopt_value
union
{
bool bool_val;
+ pg_ternary ternary_val;
int int_val;
double real_val;
int enum_val;
@@ -94,6 +96,12 @@ typedef struct relopt_bool
bool default_val;
} relopt_bool;
+typedef struct relopt_ternary
+{
+ relopt_gen gen;
+ int default_val;
+} relopt_ternary;
+
typedef struct relopt_int
{
relopt_gen gen;
@@ -152,19 +160,6 @@ typedef struct
const char *optname; /* option's name */
relopt_type opttype; /* option's datatype */
int offset; /* offset of field in result struct */
-
- /*
- * isset_offset is an optional offset of a field in the result struct that
- * stores whether the option is explicitly set for the relation or if it
- * just picked up the default value. In most cases, this can be
- * accomplished by giving the reloption a special out-of-range default
- * value (e.g., some integer reloptions use -2), but this isn't always
- * possible. For example, a Boolean reloption cannot be given an
- * out-of-range default, so we need another way to discover the source of
- * its value. This offset is only used if given a value greater than
- * zero.
- */
- int isset_offset;
} relopt_parse_elt;
/* Local reloption definition */
@@ -195,6 +190,9 @@ typedef struct local_relopts
extern relopt_kind add_reloption_kind(void);
extern void add_bool_reloption(bits32 kinds, const char *name, const char *desc,
bool default_val, LOCKMODE lockmode);
+extern void add_ternary_reloption(bits32 kinds, const char *name,
+ const char *desc, pg_ternary default_val,
+ LOCKMODE lockmode);
extern void add_int_reloption(bits32 kinds, const char *name, const char *desc,
int default_val, int min_val, int max_val,
LOCKMODE lockmode);
@@ -214,6 +212,9 @@ extern void register_reloptions_validator(local_relopts *relopts,
extern void add_local_bool_reloption(local_relopts *relopts, const char *name,
const char *desc, bool default_val,
int offset);
+extern void add_local_ternary_reloption(local_relopts *relopts,
+ const char *name, const char *desc,
+ pg_ternary default_val, int offset);
extern void add_local_int_reloption(local_relopts *relopts, const char *name,
const char *desc, int default_val,
int min_val, int max_val, int offset);
diff --git a/src/include/postgres.h b/src/include/postgres.h
index 7d93fbce709..47c88780776 100644
--- a/src/include/postgres.h
+++ b/src/include/postgres.h
@@ -543,6 +543,21 @@ Float8GetDatum(float8 X)
* ----------------------------------------------------------------
*/
+/*
+ * pg_ternary
+ * Boolean value with an extra "unset" value
+ *
+ * This enum can be used for values that want to distinguish between true,
+ * false, and unset.
+*/
+
+typedef enum pg_ternary
+{
+ TERNARY_FALSE = 0,
+ TERNARY_TRUE = 1,
+ TERNARY_UNSET = -1
+} pg_ternary;
+
/*
* NON_EXEC_STATIC: It's sometimes useful to define a variable or function
* that is normally static but extern when using EXEC_BACKEND (see
diff --git a/src/include/utils/rel.h b/src/include/utils/rel.h
index d03ab247788..5feb18a5373 100644
--- a/src/include/utils/rel.h
+++ b/src/include/utils/rel.h
@@ -347,8 +347,7 @@ typedef struct StdRdOptions
bool user_catalog_table; /* use as an additional catalog relation */
int parallel_workers; /* max number of parallel workers */
StdRdOptIndexCleanup vacuum_index_cleanup; /* controls index vacuuming */
- bool vacuum_truncate; /* enables vacuum to truncate a relation */
- bool vacuum_truncate_set; /* whether vacuum_truncate is set */
+ pg_ternary vacuum_truncate; /* enables vacuum to truncate a relation */
/*
* Fraction of pages in a relation that vacuum can eagerly scan and fail
diff --git a/src/test/regress/expected/reloptions.out b/src/test/regress/expected/reloptions.out
index 9de19b4e3f1..1c99f79ab01 100644
--- a/src/test/regress/expected/reloptions.out
+++ b/src/test/regress/expected/reloptions.out
@@ -98,6 +98,42 @@ SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass;
{fillfactor=13,autovacuum_enabled=false}
(1 row)
+-- Tests for future (FIXME) ternary options
+-- behave as boolean option: accept unassigned name and truncated value
+DROP TABLE reloptions_test;
+CREATE TABLE reloptions_test(i INT) WITH (vacuum_truncate);
+SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass;
+ reloptions
+------------------------
+ {vacuum_truncate=true}
+(1 row)
+
+DROP TABLE reloptions_test;
+CREATE TABLE reloptions_test(i INT) WITH (vacuum_truncate=FaLS);
+SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass;
+ reloptions
+------------------------
+ {vacuum_truncate=fals}
+(1 row)
+
+-- preferred "true" alias is used when storing
+DROP TABLE reloptions_test;
+CREATE TABLE reloptions_test(i INT) WITH (vacuum_index_cleanup=on);
+SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass;
+ reloptions
+---------------------------
+ {vacuum_index_cleanup=on}
+(1 row)
+
+-- custom "third" value is available
+DROP TABLE reloptions_test;
+CREATE TABLE reloptions_test(i INT) WITH (vacuum_index_cleanup=auto);
+SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass;
+ reloptions
+-----------------------------
+ {vacuum_index_cleanup=auto}
+(1 row)
+
-- Test vacuum_truncate option
DROP TABLE reloptions_test;
CREATE TEMP TABLE reloptions_test(i INT NOT NULL, j text)
diff --git a/src/test/regress/sql/reloptions.sql b/src/test/regress/sql/reloptions.sql
index 24fbe0b478d..f5980dafcbc 100644
--- a/src/test/regress/sql/reloptions.sql
+++ b/src/test/regress/sql/reloptions.sql
@@ -59,6 +59,27 @@ UPDATE pg_class
ALTER TABLE reloptions_test RESET (illegal_option);
SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass;
+-- Tests for future (FIXME) ternary options
+
+-- behave as boolean option: accept unassigned name and truncated value
+DROP TABLE reloptions_test;
+CREATE TABLE reloptions_test(i INT) WITH (vacuum_truncate);
+SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass;
+
+DROP TABLE reloptions_test;
+CREATE TABLE reloptions_test(i INT) WITH (vacuum_truncate=FaLS);
+SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass;
+
+-- preferred "true" alias is used when storing
+DROP TABLE reloptions_test;
+CREATE TABLE reloptions_test(i INT) WITH (vacuum_index_cleanup=on);
+SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass;
+
+-- custom "third" value is available
+DROP TABLE reloptions_test;
+CREATE TABLE reloptions_test(i INT) WITH (vacuum_index_cleanup=auto);
+SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass;
+
-- Test vacuum_truncate option
DROP TABLE reloptions_test;
diff --git a/src/tools/pgindent/typedefs.list b/src/tools/pgindent/typedefs.list
index 3f3a888fd0e..1c8610fd46c 100644
--- a/src/tools/pgindent/typedefs.list
+++ b/src/tools/pgindent/typedefs.list
@@ -3952,6 +3952,7 @@ pg_sha512_ctx
pg_snapshot
pg_special_case
pg_stack_base_t
+pg_ternary
pg_time_t
pg_time_usec_t
pg_tz
@@ -4079,6 +4080,7 @@ relopt_kind
relopt_parse_elt
relopt_real
relopt_string
+relopt_ternary
relopt_type
relopt_value
relopts_validator
--
2.47.3
--c36t3fndxhkzbl5z--
^ permalink raw reply [nested|flat] 868+ messages in thread
* [PATCH v3] Introduce ternary reloptions
@ 2026-01-16 15:04 Álvaro Herrera <[email protected]>
0 siblings, 0 replies; 868+ messages in thread
From: Álvaro Herrera @ 2026-01-16 15:04 UTC (permalink / raw)
Introduce ternary reloption as a replacement for current
`vacuum_truncate` implementation. Remove the `vacuum_truncate_set`
separate flag and use TERNARY_UNSET instead.
This could also be used for other options such as `vacuum_index_cleanup`
and `buffering`, but lets get the scaffolding in first.
Discussion: https://postgr.es/m/3474141.usfYGdeWWP@thinkpad-pgpro
---
src/backend/access/common/reloptions.c | 137 ++++++++++++++++++-----
src/backend/commands/vacuum.c | 4 +-
src/include/access/reloptions.h | 27 ++---
src/include/postgres.h | 15 +++
src/include/utils/rel.h | 3 +-
src/test/regress/expected/reloptions.out | 36 ++++++
src/test/regress/sql/reloptions.sql | 21 ++++
src/tools/pgindent/typedefs.list | 2 +
8 files changed, 202 insertions(+), 43 deletions(-)
diff --git a/src/backend/access/common/reloptions.c b/src/backend/access/common/reloptions.c
index 0b83f98ed5f..6f1f577581a 100644
--- a/src/backend/access/common/reloptions.c
+++ b/src/backend/access/common/reloptions.c
@@ -40,9 +40,9 @@
*
* To add an option:
*
- * (i) decide on a type (bool, integer, real, enum, string), name, default
- * value, upper and lower bounds (if applicable); for strings, consider a
- * validation routine.
+ * (i) decide on a type (bool, ternary, integer, real, enum, string), name,
+ * default value, upper and lower bounds (if applicable); for strings,
+ * consider a validation routine.
* (ii) add a record below (or use add_<type>_reloption).
* (iii) add it to the appropriate options struct (perhaps StdRdOptions)
* (iv) add it to the appropriate handling routine (perhaps
@@ -147,15 +147,6 @@ static relopt_bool boolRelOpts[] =
},
false
},
- {
- {
- "vacuum_truncate",
- "Enables vacuum to truncate empty pages at the end of this table",
- RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
- ShareUpdateExclusiveLock
- },
- true
- },
{
{
"deduplicate_items",
@@ -170,6 +161,25 @@ static relopt_bool boolRelOpts[] =
{{NULL}}
};
+static relopt_ternary ternaryRelOpts[] =
+{
+ {
+ {
+ "vacuum_truncate",
+ "Enables vacuum to truncate empty pages at the end of this table",
+ RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
+ ShareUpdateExclusiveLock
+ },
+ TERNARY_UNSET
+ },
+ /* list terminator */
+ {
+ {
+ NULL
+ }
+ }
+};
+
static relopt_int intRelOpts[] =
{
{
@@ -609,6 +619,13 @@ initialize_reloptions(void)
boolRelOpts[i].gen.lockmode));
j++;
}
+ for (i = 0; ternaryRelOpts[i].gen.name; i++)
+ {
+ Assert(DoLockModesConflict(ternaryRelOpts[i].gen.lockmode,
+ ternaryRelOpts[i].gen.lockmode));
+ j++;
+ }
+
for (i = 0; intRelOpts[i].gen.name; i++)
{
Assert(DoLockModesConflict(intRelOpts[i].gen.lockmode,
@@ -649,6 +666,14 @@ initialize_reloptions(void)
j++;
}
+ for (i = 0; ternaryRelOpts[i].gen.name; i++)
+ {
+ relOpts[j] = &ternaryRelOpts[i].gen;
+ relOpts[j]->type = RELOPT_TYPE_TERNARY;
+ relOpts[j]->namelen = strlen(relOpts[j]->name);
+ j++;
+ }
+
for (i = 0; intRelOpts[i].gen.name; i++)
{
relOpts[j] = &intRelOpts[i].gen;
@@ -809,6 +834,9 @@ allocate_reloption(bits32 kinds, int type, const char *name, const char *desc,
case RELOPT_TYPE_BOOL:
size = sizeof(relopt_bool);
break;
+ case RELOPT_TYPE_TERNARY:
+ size = sizeof(relopt_ternary);
+ break;
case RELOPT_TYPE_INT:
size = sizeof(relopt_int);
break;
@@ -892,6 +920,57 @@ add_local_bool_reloption(local_relopts *relopts, const char *name,
add_local_reloption(relopts, (relopt_gen *) newoption, offset);
}
+/*
+ * init_ternary_reloption
+ * Allocate and initialize a new ternary reloption
+ */
+static relopt_ternary *
+init_ternary_reloption(bits32 kinds, const char *name, const char *desc,
+ pg_ternary default_val, LOCKMODE lockmode)
+{
+ relopt_ternary *newoption;
+
+ newoption = (relopt_ternary *)
+ allocate_reloption(kinds, RELOPT_TYPE_TERNARY, name, desc, lockmode);
+ newoption->default_val = default_val;
+
+ return newoption;
+}
+
+/*
+ * add_ternary_reloption
+ * Add a new ternary reloption
+ */
+void
+add_ternary_reloption(bits32 kinds, const char *name, const char *desc,
+ pg_ternary default_val, LOCKMODE lockmode)
+{
+ relopt_ternary *newoption;
+
+ newoption =
+ init_ternary_reloption(kinds, name, desc, default_val, lockmode);
+
+ add_reloption((relopt_gen *) newoption);
+}
+
+/*
+ * add_local_ternary_reloption
+ * Add a new ternary local reloption
+ *
+ * 'offset' is offset of ternary-typed field.
+ */
+void
+add_local_ternary_reloption(local_relopts *relopts, const char *name,
+ const char *desc, pg_ternary default_val,
+ int offset)
+{
+ relopt_ternary *newoption;
+
+ newoption =
+ init_ternary_reloption(RELOPT_KIND_LOCAL, name, desc, default_val, 0);
+
+ add_local_reloption(relopts, (relopt_gen *) newoption, offset);
+}
/*
* init_real_reloption
@@ -1626,6 +1705,19 @@ parse_one_reloption(relopt_value *option, char *text_str, int text_len,
option->gen->name, value)));
}
break;
+ case RELOPT_TYPE_TERNARY:
+ {
+ bool b;
+
+ parsed = parse_bool(value, &b);
+ option->values.ternary_val = b ? TERNARY_TRUE : TERNARY_FALSE;
+ if (validate && !parsed)
+ ereport(ERROR,
+ (errcode(ERRCODE_INVALID_PARAMETER_VALUE),
+ errmsg("invalid value for ternary option \"%s\": %s",
+ option->gen->name, value)));
+ }
+ break;
case RELOPT_TYPE_INT:
{
relopt_int *optint = (relopt_int *) option->gen;
@@ -1789,17 +1881,6 @@ fillRelOptions(void *rdopts, Size basesize,
char *itempos = ((char *) rdopts) + elems[j].offset;
char *string_val;
- /*
- * If isset_offset is provided, store whether the reloption is
- * set there.
- */
- if (elems[j].isset_offset > 0)
- {
- char *setpos = ((char *) rdopts) + elems[j].isset_offset;
-
- *(bool *) setpos = options[i].isset;
- }
-
switch (options[i].gen->type)
{
case RELOPT_TYPE_BOOL:
@@ -1807,6 +1888,11 @@ fillRelOptions(void *rdopts, Size basesize,
options[i].values.bool_val :
((relopt_bool *) options[i].gen)->default_val;
break;
+ case RELOPT_TYPE_TERNARY:
+ *(pg_ternary *) itempos = options[i].isset ?
+ options[i].values.ternary_val :
+ ((relopt_ternary *) options[i].gen)->default_val;
+ break;
case RELOPT_TYPE_INT:
*(int *) itempos = options[i].isset ?
options[i].values.int_val :
@@ -1923,8 +2009,8 @@ default_reloptions(Datum reloptions, bool validate, relopt_kind kind)
offsetof(StdRdOptions, parallel_workers)},
{"vacuum_index_cleanup", RELOPT_TYPE_ENUM,
offsetof(StdRdOptions, vacuum_index_cleanup)},
- {"vacuum_truncate", RELOPT_TYPE_BOOL,
- offsetof(StdRdOptions, vacuum_truncate), offsetof(StdRdOptions, vacuum_truncate_set)},
+ {"vacuum_truncate", RELOPT_TYPE_TERNARY,
+ offsetof(StdRdOptions, vacuum_truncate)},
{"vacuum_max_eager_freeze_failure_rate", RELOPT_TYPE_REAL,
offsetof(StdRdOptions, vacuum_max_eager_freeze_failure_rate)}
};
@@ -2004,7 +2090,6 @@ build_local_reloptions(local_relopts *relopts, Datum options, bool validate)
elems[i].optname = opt->option->name;
elems[i].opttype = opt->option->type;
elems[i].offset = opt->offset;
- elems[i].isset_offset = 0; /* not supported for local relopts yet */
i++;
}
diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c
index aa4fbec143f..696eab9bd97 100644
--- a/src/backend/commands/vacuum.c
+++ b/src/backend/commands/vacuum.c
@@ -2224,9 +2224,9 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams params,
{
StdRdOptions *opts = (StdRdOptions *) rel->rd_options;
- if (opts && opts->vacuum_truncate_set)
+ if (opts && opts->vacuum_truncate != TERNARY_UNSET)
{
- if (opts->vacuum_truncate)
+ if (opts->vacuum_truncate == TERNARY_TRUE)
params.truncate = VACOPTVALUE_ENABLED;
else
params.truncate = VACOPTVALUE_DISABLED;
diff --git a/src/include/access/reloptions.h b/src/include/access/reloptions.h
index 2f08e1b0cf0..dfbef2babf2 100644
--- a/src/include/access/reloptions.h
+++ b/src/include/access/reloptions.h
@@ -29,6 +29,7 @@
typedef enum relopt_type
{
RELOPT_TYPE_BOOL,
+ RELOPT_TYPE_TERNARY, /* on, off, unset */
RELOPT_TYPE_INT,
RELOPT_TYPE_REAL,
RELOPT_TYPE_ENUM,
@@ -80,6 +81,7 @@ typedef struct relopt_value
union
{
bool bool_val;
+ pg_ternary ternary_val;
int int_val;
double real_val;
int enum_val;
@@ -94,6 +96,12 @@ typedef struct relopt_bool
bool default_val;
} relopt_bool;
+typedef struct relopt_ternary
+{
+ relopt_gen gen;
+ int default_val;
+} relopt_ternary;
+
typedef struct relopt_int
{
relopt_gen gen;
@@ -152,19 +160,6 @@ typedef struct
const char *optname; /* option's name */
relopt_type opttype; /* option's datatype */
int offset; /* offset of field in result struct */
-
- /*
- * isset_offset is an optional offset of a field in the result struct that
- * stores whether the option is explicitly set for the relation or if it
- * just picked up the default value. In most cases, this can be
- * accomplished by giving the reloption a special out-of-range default
- * value (e.g., some integer reloptions use -2), but this isn't always
- * possible. For example, a Boolean reloption cannot be given an
- * out-of-range default, so we need another way to discover the source of
- * its value. This offset is only used if given a value greater than
- * zero.
- */
- int isset_offset;
} relopt_parse_elt;
/* Local reloption definition */
@@ -195,6 +190,9 @@ typedef struct local_relopts
extern relopt_kind add_reloption_kind(void);
extern void add_bool_reloption(bits32 kinds, const char *name, const char *desc,
bool default_val, LOCKMODE lockmode);
+extern void add_ternary_reloption(bits32 kinds, const char *name,
+ const char *desc, pg_ternary default_val,
+ LOCKMODE lockmode);
extern void add_int_reloption(bits32 kinds, const char *name, const char *desc,
int default_val, int min_val, int max_val,
LOCKMODE lockmode);
@@ -214,6 +212,9 @@ extern void register_reloptions_validator(local_relopts *relopts,
extern void add_local_bool_reloption(local_relopts *relopts, const char *name,
const char *desc, bool default_val,
int offset);
+extern void add_local_ternary_reloption(local_relopts *relopts,
+ const char *name, const char *desc,
+ pg_ternary default_val, int offset);
extern void add_local_int_reloption(local_relopts *relopts, const char *name,
const char *desc, int default_val,
int min_val, int max_val, int offset);
diff --git a/src/include/postgres.h b/src/include/postgres.h
index 7d93fbce709..47c88780776 100644
--- a/src/include/postgres.h
+++ b/src/include/postgres.h
@@ -543,6 +543,21 @@ Float8GetDatum(float8 X)
* ----------------------------------------------------------------
*/
+/*
+ * pg_ternary
+ * Boolean value with an extra "unset" value
+ *
+ * This enum can be used for values that want to distinguish between true,
+ * false, and unset.
+*/
+
+typedef enum pg_ternary
+{
+ TERNARY_FALSE = 0,
+ TERNARY_TRUE = 1,
+ TERNARY_UNSET = -1
+} pg_ternary;
+
/*
* NON_EXEC_STATIC: It's sometimes useful to define a variable or function
* that is normally static but extern when using EXEC_BACKEND (see
diff --git a/src/include/utils/rel.h b/src/include/utils/rel.h
index d03ab247788..5feb18a5373 100644
--- a/src/include/utils/rel.h
+++ b/src/include/utils/rel.h
@@ -347,8 +347,7 @@ typedef struct StdRdOptions
bool user_catalog_table; /* use as an additional catalog relation */
int parallel_workers; /* max number of parallel workers */
StdRdOptIndexCleanup vacuum_index_cleanup; /* controls index vacuuming */
- bool vacuum_truncate; /* enables vacuum to truncate a relation */
- bool vacuum_truncate_set; /* whether vacuum_truncate is set */
+ pg_ternary vacuum_truncate; /* enables vacuum to truncate a relation */
/*
* Fraction of pages in a relation that vacuum can eagerly scan and fail
diff --git a/src/test/regress/expected/reloptions.out b/src/test/regress/expected/reloptions.out
index 9de19b4e3f1..1c99f79ab01 100644
--- a/src/test/regress/expected/reloptions.out
+++ b/src/test/regress/expected/reloptions.out
@@ -98,6 +98,42 @@ SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass;
{fillfactor=13,autovacuum_enabled=false}
(1 row)
+-- Tests for future (FIXME) ternary options
+-- behave as boolean option: accept unassigned name and truncated value
+DROP TABLE reloptions_test;
+CREATE TABLE reloptions_test(i INT) WITH (vacuum_truncate);
+SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass;
+ reloptions
+------------------------
+ {vacuum_truncate=true}
+(1 row)
+
+DROP TABLE reloptions_test;
+CREATE TABLE reloptions_test(i INT) WITH (vacuum_truncate=FaLS);
+SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass;
+ reloptions
+------------------------
+ {vacuum_truncate=fals}
+(1 row)
+
+-- preferred "true" alias is used when storing
+DROP TABLE reloptions_test;
+CREATE TABLE reloptions_test(i INT) WITH (vacuum_index_cleanup=on);
+SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass;
+ reloptions
+---------------------------
+ {vacuum_index_cleanup=on}
+(1 row)
+
+-- custom "third" value is available
+DROP TABLE reloptions_test;
+CREATE TABLE reloptions_test(i INT) WITH (vacuum_index_cleanup=auto);
+SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass;
+ reloptions
+-----------------------------
+ {vacuum_index_cleanup=auto}
+(1 row)
+
-- Test vacuum_truncate option
DROP TABLE reloptions_test;
CREATE TEMP TABLE reloptions_test(i INT NOT NULL, j text)
diff --git a/src/test/regress/sql/reloptions.sql b/src/test/regress/sql/reloptions.sql
index 24fbe0b478d..f5980dafcbc 100644
--- a/src/test/regress/sql/reloptions.sql
+++ b/src/test/regress/sql/reloptions.sql
@@ -59,6 +59,27 @@ UPDATE pg_class
ALTER TABLE reloptions_test RESET (illegal_option);
SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass;
+-- Tests for future (FIXME) ternary options
+
+-- behave as boolean option: accept unassigned name and truncated value
+DROP TABLE reloptions_test;
+CREATE TABLE reloptions_test(i INT) WITH (vacuum_truncate);
+SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass;
+
+DROP TABLE reloptions_test;
+CREATE TABLE reloptions_test(i INT) WITH (vacuum_truncate=FaLS);
+SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass;
+
+-- preferred "true" alias is used when storing
+DROP TABLE reloptions_test;
+CREATE TABLE reloptions_test(i INT) WITH (vacuum_index_cleanup=on);
+SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass;
+
+-- custom "third" value is available
+DROP TABLE reloptions_test;
+CREATE TABLE reloptions_test(i INT) WITH (vacuum_index_cleanup=auto);
+SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass;
+
-- Test vacuum_truncate option
DROP TABLE reloptions_test;
diff --git a/src/tools/pgindent/typedefs.list b/src/tools/pgindent/typedefs.list
index 3f3a888fd0e..1c8610fd46c 100644
--- a/src/tools/pgindent/typedefs.list
+++ b/src/tools/pgindent/typedefs.list
@@ -3952,6 +3952,7 @@ pg_sha512_ctx
pg_snapshot
pg_special_case
pg_stack_base_t
+pg_ternary
pg_time_t
pg_time_usec_t
pg_tz
@@ -4079,6 +4080,7 @@ relopt_kind
relopt_parse_elt
relopt_real
relopt_string
+relopt_ternary
relopt_type
relopt_value
relopts_validator
--
2.47.3
--c36t3fndxhkzbl5z--
^ permalink raw reply [nested|flat] 868+ messages in thread
* [PATCH v3] Introduce ternary reloptions
@ 2026-01-16 15:04 Álvaro Herrera <[email protected]>
0 siblings, 0 replies; 868+ messages in thread
From: Álvaro Herrera @ 2026-01-16 15:04 UTC (permalink / raw)
Introduce ternary reloption as a replacement for current
`vacuum_truncate` implementation. Remove the `vacuum_truncate_set`
separate flag and use TERNARY_UNSET instead.
This could also be used for other options such as `vacuum_index_cleanup`
and `buffering`, but lets get the scaffolding in first.
Discussion: https://postgr.es/m/3474141.usfYGdeWWP@thinkpad-pgpro
---
src/backend/access/common/reloptions.c | 137 ++++++++++++++++++-----
src/backend/commands/vacuum.c | 4 +-
src/include/access/reloptions.h | 27 ++---
src/include/postgres.h | 15 +++
src/include/utils/rel.h | 3 +-
src/test/regress/expected/reloptions.out | 36 ++++++
src/test/regress/sql/reloptions.sql | 21 ++++
src/tools/pgindent/typedefs.list | 2 +
8 files changed, 202 insertions(+), 43 deletions(-)
diff --git a/src/backend/access/common/reloptions.c b/src/backend/access/common/reloptions.c
index 0b83f98ed5f..6f1f577581a 100644
--- a/src/backend/access/common/reloptions.c
+++ b/src/backend/access/common/reloptions.c
@@ -40,9 +40,9 @@
*
* To add an option:
*
- * (i) decide on a type (bool, integer, real, enum, string), name, default
- * value, upper and lower bounds (if applicable); for strings, consider a
- * validation routine.
+ * (i) decide on a type (bool, ternary, integer, real, enum, string), name,
+ * default value, upper and lower bounds (if applicable); for strings,
+ * consider a validation routine.
* (ii) add a record below (or use add_<type>_reloption).
* (iii) add it to the appropriate options struct (perhaps StdRdOptions)
* (iv) add it to the appropriate handling routine (perhaps
@@ -147,15 +147,6 @@ static relopt_bool boolRelOpts[] =
},
false
},
- {
- {
- "vacuum_truncate",
- "Enables vacuum to truncate empty pages at the end of this table",
- RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
- ShareUpdateExclusiveLock
- },
- true
- },
{
{
"deduplicate_items",
@@ -170,6 +161,25 @@ static relopt_bool boolRelOpts[] =
{{NULL}}
};
+static relopt_ternary ternaryRelOpts[] =
+{
+ {
+ {
+ "vacuum_truncate",
+ "Enables vacuum to truncate empty pages at the end of this table",
+ RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
+ ShareUpdateExclusiveLock
+ },
+ TERNARY_UNSET
+ },
+ /* list terminator */
+ {
+ {
+ NULL
+ }
+ }
+};
+
static relopt_int intRelOpts[] =
{
{
@@ -609,6 +619,13 @@ initialize_reloptions(void)
boolRelOpts[i].gen.lockmode));
j++;
}
+ for (i = 0; ternaryRelOpts[i].gen.name; i++)
+ {
+ Assert(DoLockModesConflict(ternaryRelOpts[i].gen.lockmode,
+ ternaryRelOpts[i].gen.lockmode));
+ j++;
+ }
+
for (i = 0; intRelOpts[i].gen.name; i++)
{
Assert(DoLockModesConflict(intRelOpts[i].gen.lockmode,
@@ -649,6 +666,14 @@ initialize_reloptions(void)
j++;
}
+ for (i = 0; ternaryRelOpts[i].gen.name; i++)
+ {
+ relOpts[j] = &ternaryRelOpts[i].gen;
+ relOpts[j]->type = RELOPT_TYPE_TERNARY;
+ relOpts[j]->namelen = strlen(relOpts[j]->name);
+ j++;
+ }
+
for (i = 0; intRelOpts[i].gen.name; i++)
{
relOpts[j] = &intRelOpts[i].gen;
@@ -809,6 +834,9 @@ allocate_reloption(bits32 kinds, int type, const char *name, const char *desc,
case RELOPT_TYPE_BOOL:
size = sizeof(relopt_bool);
break;
+ case RELOPT_TYPE_TERNARY:
+ size = sizeof(relopt_ternary);
+ break;
case RELOPT_TYPE_INT:
size = sizeof(relopt_int);
break;
@@ -892,6 +920,57 @@ add_local_bool_reloption(local_relopts *relopts, const char *name,
add_local_reloption(relopts, (relopt_gen *) newoption, offset);
}
+/*
+ * init_ternary_reloption
+ * Allocate and initialize a new ternary reloption
+ */
+static relopt_ternary *
+init_ternary_reloption(bits32 kinds, const char *name, const char *desc,
+ pg_ternary default_val, LOCKMODE lockmode)
+{
+ relopt_ternary *newoption;
+
+ newoption = (relopt_ternary *)
+ allocate_reloption(kinds, RELOPT_TYPE_TERNARY, name, desc, lockmode);
+ newoption->default_val = default_val;
+
+ return newoption;
+}
+
+/*
+ * add_ternary_reloption
+ * Add a new ternary reloption
+ */
+void
+add_ternary_reloption(bits32 kinds, const char *name, const char *desc,
+ pg_ternary default_val, LOCKMODE lockmode)
+{
+ relopt_ternary *newoption;
+
+ newoption =
+ init_ternary_reloption(kinds, name, desc, default_val, lockmode);
+
+ add_reloption((relopt_gen *) newoption);
+}
+
+/*
+ * add_local_ternary_reloption
+ * Add a new ternary local reloption
+ *
+ * 'offset' is offset of ternary-typed field.
+ */
+void
+add_local_ternary_reloption(local_relopts *relopts, const char *name,
+ const char *desc, pg_ternary default_val,
+ int offset)
+{
+ relopt_ternary *newoption;
+
+ newoption =
+ init_ternary_reloption(RELOPT_KIND_LOCAL, name, desc, default_val, 0);
+
+ add_local_reloption(relopts, (relopt_gen *) newoption, offset);
+}
/*
* init_real_reloption
@@ -1626,6 +1705,19 @@ parse_one_reloption(relopt_value *option, char *text_str, int text_len,
option->gen->name, value)));
}
break;
+ case RELOPT_TYPE_TERNARY:
+ {
+ bool b;
+
+ parsed = parse_bool(value, &b);
+ option->values.ternary_val = b ? TERNARY_TRUE : TERNARY_FALSE;
+ if (validate && !parsed)
+ ereport(ERROR,
+ (errcode(ERRCODE_INVALID_PARAMETER_VALUE),
+ errmsg("invalid value for ternary option \"%s\": %s",
+ option->gen->name, value)));
+ }
+ break;
case RELOPT_TYPE_INT:
{
relopt_int *optint = (relopt_int *) option->gen;
@@ -1789,17 +1881,6 @@ fillRelOptions(void *rdopts, Size basesize,
char *itempos = ((char *) rdopts) + elems[j].offset;
char *string_val;
- /*
- * If isset_offset is provided, store whether the reloption is
- * set there.
- */
- if (elems[j].isset_offset > 0)
- {
- char *setpos = ((char *) rdopts) + elems[j].isset_offset;
-
- *(bool *) setpos = options[i].isset;
- }
-
switch (options[i].gen->type)
{
case RELOPT_TYPE_BOOL:
@@ -1807,6 +1888,11 @@ fillRelOptions(void *rdopts, Size basesize,
options[i].values.bool_val :
((relopt_bool *) options[i].gen)->default_val;
break;
+ case RELOPT_TYPE_TERNARY:
+ *(pg_ternary *) itempos = options[i].isset ?
+ options[i].values.ternary_val :
+ ((relopt_ternary *) options[i].gen)->default_val;
+ break;
case RELOPT_TYPE_INT:
*(int *) itempos = options[i].isset ?
options[i].values.int_val :
@@ -1923,8 +2009,8 @@ default_reloptions(Datum reloptions, bool validate, relopt_kind kind)
offsetof(StdRdOptions, parallel_workers)},
{"vacuum_index_cleanup", RELOPT_TYPE_ENUM,
offsetof(StdRdOptions, vacuum_index_cleanup)},
- {"vacuum_truncate", RELOPT_TYPE_BOOL,
- offsetof(StdRdOptions, vacuum_truncate), offsetof(StdRdOptions, vacuum_truncate_set)},
+ {"vacuum_truncate", RELOPT_TYPE_TERNARY,
+ offsetof(StdRdOptions, vacuum_truncate)},
{"vacuum_max_eager_freeze_failure_rate", RELOPT_TYPE_REAL,
offsetof(StdRdOptions, vacuum_max_eager_freeze_failure_rate)}
};
@@ -2004,7 +2090,6 @@ build_local_reloptions(local_relopts *relopts, Datum options, bool validate)
elems[i].optname = opt->option->name;
elems[i].opttype = opt->option->type;
elems[i].offset = opt->offset;
- elems[i].isset_offset = 0; /* not supported for local relopts yet */
i++;
}
diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c
index aa4fbec143f..696eab9bd97 100644
--- a/src/backend/commands/vacuum.c
+++ b/src/backend/commands/vacuum.c
@@ -2224,9 +2224,9 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams params,
{
StdRdOptions *opts = (StdRdOptions *) rel->rd_options;
- if (opts && opts->vacuum_truncate_set)
+ if (opts && opts->vacuum_truncate != TERNARY_UNSET)
{
- if (opts->vacuum_truncate)
+ if (opts->vacuum_truncate == TERNARY_TRUE)
params.truncate = VACOPTVALUE_ENABLED;
else
params.truncate = VACOPTVALUE_DISABLED;
diff --git a/src/include/access/reloptions.h b/src/include/access/reloptions.h
index 2f08e1b0cf0..dfbef2babf2 100644
--- a/src/include/access/reloptions.h
+++ b/src/include/access/reloptions.h
@@ -29,6 +29,7 @@
typedef enum relopt_type
{
RELOPT_TYPE_BOOL,
+ RELOPT_TYPE_TERNARY, /* on, off, unset */
RELOPT_TYPE_INT,
RELOPT_TYPE_REAL,
RELOPT_TYPE_ENUM,
@@ -80,6 +81,7 @@ typedef struct relopt_value
union
{
bool bool_val;
+ pg_ternary ternary_val;
int int_val;
double real_val;
int enum_val;
@@ -94,6 +96,12 @@ typedef struct relopt_bool
bool default_val;
} relopt_bool;
+typedef struct relopt_ternary
+{
+ relopt_gen gen;
+ int default_val;
+} relopt_ternary;
+
typedef struct relopt_int
{
relopt_gen gen;
@@ -152,19 +160,6 @@ typedef struct
const char *optname; /* option's name */
relopt_type opttype; /* option's datatype */
int offset; /* offset of field in result struct */
-
- /*
- * isset_offset is an optional offset of a field in the result struct that
- * stores whether the option is explicitly set for the relation or if it
- * just picked up the default value. In most cases, this can be
- * accomplished by giving the reloption a special out-of-range default
- * value (e.g., some integer reloptions use -2), but this isn't always
- * possible. For example, a Boolean reloption cannot be given an
- * out-of-range default, so we need another way to discover the source of
- * its value. This offset is only used if given a value greater than
- * zero.
- */
- int isset_offset;
} relopt_parse_elt;
/* Local reloption definition */
@@ -195,6 +190,9 @@ typedef struct local_relopts
extern relopt_kind add_reloption_kind(void);
extern void add_bool_reloption(bits32 kinds, const char *name, const char *desc,
bool default_val, LOCKMODE lockmode);
+extern void add_ternary_reloption(bits32 kinds, const char *name,
+ const char *desc, pg_ternary default_val,
+ LOCKMODE lockmode);
extern void add_int_reloption(bits32 kinds, const char *name, const char *desc,
int default_val, int min_val, int max_val,
LOCKMODE lockmode);
@@ -214,6 +212,9 @@ extern void register_reloptions_validator(local_relopts *relopts,
extern void add_local_bool_reloption(local_relopts *relopts, const char *name,
const char *desc, bool default_val,
int offset);
+extern void add_local_ternary_reloption(local_relopts *relopts,
+ const char *name, const char *desc,
+ pg_ternary default_val, int offset);
extern void add_local_int_reloption(local_relopts *relopts, const char *name,
const char *desc, int default_val,
int min_val, int max_val, int offset);
diff --git a/src/include/postgres.h b/src/include/postgres.h
index 7d93fbce709..47c88780776 100644
--- a/src/include/postgres.h
+++ b/src/include/postgres.h
@@ -543,6 +543,21 @@ Float8GetDatum(float8 X)
* ----------------------------------------------------------------
*/
+/*
+ * pg_ternary
+ * Boolean value with an extra "unset" value
+ *
+ * This enum can be used for values that want to distinguish between true,
+ * false, and unset.
+*/
+
+typedef enum pg_ternary
+{
+ TERNARY_FALSE = 0,
+ TERNARY_TRUE = 1,
+ TERNARY_UNSET = -1
+} pg_ternary;
+
/*
* NON_EXEC_STATIC: It's sometimes useful to define a variable or function
* that is normally static but extern when using EXEC_BACKEND (see
diff --git a/src/include/utils/rel.h b/src/include/utils/rel.h
index d03ab247788..5feb18a5373 100644
--- a/src/include/utils/rel.h
+++ b/src/include/utils/rel.h
@@ -347,8 +347,7 @@ typedef struct StdRdOptions
bool user_catalog_table; /* use as an additional catalog relation */
int parallel_workers; /* max number of parallel workers */
StdRdOptIndexCleanup vacuum_index_cleanup; /* controls index vacuuming */
- bool vacuum_truncate; /* enables vacuum to truncate a relation */
- bool vacuum_truncate_set; /* whether vacuum_truncate is set */
+ pg_ternary vacuum_truncate; /* enables vacuum to truncate a relation */
/*
* Fraction of pages in a relation that vacuum can eagerly scan and fail
diff --git a/src/test/regress/expected/reloptions.out b/src/test/regress/expected/reloptions.out
index 9de19b4e3f1..1c99f79ab01 100644
--- a/src/test/regress/expected/reloptions.out
+++ b/src/test/regress/expected/reloptions.out
@@ -98,6 +98,42 @@ SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass;
{fillfactor=13,autovacuum_enabled=false}
(1 row)
+-- Tests for future (FIXME) ternary options
+-- behave as boolean option: accept unassigned name and truncated value
+DROP TABLE reloptions_test;
+CREATE TABLE reloptions_test(i INT) WITH (vacuum_truncate);
+SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass;
+ reloptions
+------------------------
+ {vacuum_truncate=true}
+(1 row)
+
+DROP TABLE reloptions_test;
+CREATE TABLE reloptions_test(i INT) WITH (vacuum_truncate=FaLS);
+SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass;
+ reloptions
+------------------------
+ {vacuum_truncate=fals}
+(1 row)
+
+-- preferred "true" alias is used when storing
+DROP TABLE reloptions_test;
+CREATE TABLE reloptions_test(i INT) WITH (vacuum_index_cleanup=on);
+SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass;
+ reloptions
+---------------------------
+ {vacuum_index_cleanup=on}
+(1 row)
+
+-- custom "third" value is available
+DROP TABLE reloptions_test;
+CREATE TABLE reloptions_test(i INT) WITH (vacuum_index_cleanup=auto);
+SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass;
+ reloptions
+-----------------------------
+ {vacuum_index_cleanup=auto}
+(1 row)
+
-- Test vacuum_truncate option
DROP TABLE reloptions_test;
CREATE TEMP TABLE reloptions_test(i INT NOT NULL, j text)
diff --git a/src/test/regress/sql/reloptions.sql b/src/test/regress/sql/reloptions.sql
index 24fbe0b478d..f5980dafcbc 100644
--- a/src/test/regress/sql/reloptions.sql
+++ b/src/test/regress/sql/reloptions.sql
@@ -59,6 +59,27 @@ UPDATE pg_class
ALTER TABLE reloptions_test RESET (illegal_option);
SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass;
+-- Tests for future (FIXME) ternary options
+
+-- behave as boolean option: accept unassigned name and truncated value
+DROP TABLE reloptions_test;
+CREATE TABLE reloptions_test(i INT) WITH (vacuum_truncate);
+SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass;
+
+DROP TABLE reloptions_test;
+CREATE TABLE reloptions_test(i INT) WITH (vacuum_truncate=FaLS);
+SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass;
+
+-- preferred "true" alias is used when storing
+DROP TABLE reloptions_test;
+CREATE TABLE reloptions_test(i INT) WITH (vacuum_index_cleanup=on);
+SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass;
+
+-- custom "third" value is available
+DROP TABLE reloptions_test;
+CREATE TABLE reloptions_test(i INT) WITH (vacuum_index_cleanup=auto);
+SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass;
+
-- Test vacuum_truncate option
DROP TABLE reloptions_test;
diff --git a/src/tools/pgindent/typedefs.list b/src/tools/pgindent/typedefs.list
index 3f3a888fd0e..1c8610fd46c 100644
--- a/src/tools/pgindent/typedefs.list
+++ b/src/tools/pgindent/typedefs.list
@@ -3952,6 +3952,7 @@ pg_sha512_ctx
pg_snapshot
pg_special_case
pg_stack_base_t
+pg_ternary
pg_time_t
pg_time_usec_t
pg_tz
@@ -4079,6 +4080,7 @@ relopt_kind
relopt_parse_elt
relopt_real
relopt_string
+relopt_ternary
relopt_type
relopt_value
relopts_validator
--
2.47.3
--c36t3fndxhkzbl5z--
^ permalink raw reply [nested|flat] 868+ messages in thread
* [PATCH v3] Introduce ternary reloptions
@ 2026-01-16 15:04 Álvaro Herrera <[email protected]>
0 siblings, 0 replies; 868+ messages in thread
From: Álvaro Herrera @ 2026-01-16 15:04 UTC (permalink / raw)
Introduce ternary reloption as a replacement for current
`vacuum_truncate` implementation. Remove the `vacuum_truncate_set`
separate flag and use TERNARY_UNSET instead.
This could also be used for other options such as `vacuum_index_cleanup`
and `buffering`, but lets get the scaffolding in first.
Discussion: https://postgr.es/m/3474141.usfYGdeWWP@thinkpad-pgpro
---
src/backend/access/common/reloptions.c | 137 ++++++++++++++++++-----
src/backend/commands/vacuum.c | 4 +-
src/include/access/reloptions.h | 27 ++---
src/include/postgres.h | 15 +++
src/include/utils/rel.h | 3 +-
src/test/regress/expected/reloptions.out | 36 ++++++
src/test/regress/sql/reloptions.sql | 21 ++++
src/tools/pgindent/typedefs.list | 2 +
8 files changed, 202 insertions(+), 43 deletions(-)
diff --git a/src/backend/access/common/reloptions.c b/src/backend/access/common/reloptions.c
index 0b83f98ed5f..6f1f577581a 100644
--- a/src/backend/access/common/reloptions.c
+++ b/src/backend/access/common/reloptions.c
@@ -40,9 +40,9 @@
*
* To add an option:
*
- * (i) decide on a type (bool, integer, real, enum, string), name, default
- * value, upper and lower bounds (if applicable); for strings, consider a
- * validation routine.
+ * (i) decide on a type (bool, ternary, integer, real, enum, string), name,
+ * default value, upper and lower bounds (if applicable); for strings,
+ * consider a validation routine.
* (ii) add a record below (or use add_<type>_reloption).
* (iii) add it to the appropriate options struct (perhaps StdRdOptions)
* (iv) add it to the appropriate handling routine (perhaps
@@ -147,15 +147,6 @@ static relopt_bool boolRelOpts[] =
},
false
},
- {
- {
- "vacuum_truncate",
- "Enables vacuum to truncate empty pages at the end of this table",
- RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
- ShareUpdateExclusiveLock
- },
- true
- },
{
{
"deduplicate_items",
@@ -170,6 +161,25 @@ static relopt_bool boolRelOpts[] =
{{NULL}}
};
+static relopt_ternary ternaryRelOpts[] =
+{
+ {
+ {
+ "vacuum_truncate",
+ "Enables vacuum to truncate empty pages at the end of this table",
+ RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
+ ShareUpdateExclusiveLock
+ },
+ TERNARY_UNSET
+ },
+ /* list terminator */
+ {
+ {
+ NULL
+ }
+ }
+};
+
static relopt_int intRelOpts[] =
{
{
@@ -609,6 +619,13 @@ initialize_reloptions(void)
boolRelOpts[i].gen.lockmode));
j++;
}
+ for (i = 0; ternaryRelOpts[i].gen.name; i++)
+ {
+ Assert(DoLockModesConflict(ternaryRelOpts[i].gen.lockmode,
+ ternaryRelOpts[i].gen.lockmode));
+ j++;
+ }
+
for (i = 0; intRelOpts[i].gen.name; i++)
{
Assert(DoLockModesConflict(intRelOpts[i].gen.lockmode,
@@ -649,6 +666,14 @@ initialize_reloptions(void)
j++;
}
+ for (i = 0; ternaryRelOpts[i].gen.name; i++)
+ {
+ relOpts[j] = &ternaryRelOpts[i].gen;
+ relOpts[j]->type = RELOPT_TYPE_TERNARY;
+ relOpts[j]->namelen = strlen(relOpts[j]->name);
+ j++;
+ }
+
for (i = 0; intRelOpts[i].gen.name; i++)
{
relOpts[j] = &intRelOpts[i].gen;
@@ -809,6 +834,9 @@ allocate_reloption(bits32 kinds, int type, const char *name, const char *desc,
case RELOPT_TYPE_BOOL:
size = sizeof(relopt_bool);
break;
+ case RELOPT_TYPE_TERNARY:
+ size = sizeof(relopt_ternary);
+ break;
case RELOPT_TYPE_INT:
size = sizeof(relopt_int);
break;
@@ -892,6 +920,57 @@ add_local_bool_reloption(local_relopts *relopts, const char *name,
add_local_reloption(relopts, (relopt_gen *) newoption, offset);
}
+/*
+ * init_ternary_reloption
+ * Allocate and initialize a new ternary reloption
+ */
+static relopt_ternary *
+init_ternary_reloption(bits32 kinds, const char *name, const char *desc,
+ pg_ternary default_val, LOCKMODE lockmode)
+{
+ relopt_ternary *newoption;
+
+ newoption = (relopt_ternary *)
+ allocate_reloption(kinds, RELOPT_TYPE_TERNARY, name, desc, lockmode);
+ newoption->default_val = default_val;
+
+ return newoption;
+}
+
+/*
+ * add_ternary_reloption
+ * Add a new ternary reloption
+ */
+void
+add_ternary_reloption(bits32 kinds, const char *name, const char *desc,
+ pg_ternary default_val, LOCKMODE lockmode)
+{
+ relopt_ternary *newoption;
+
+ newoption =
+ init_ternary_reloption(kinds, name, desc, default_val, lockmode);
+
+ add_reloption((relopt_gen *) newoption);
+}
+
+/*
+ * add_local_ternary_reloption
+ * Add a new ternary local reloption
+ *
+ * 'offset' is offset of ternary-typed field.
+ */
+void
+add_local_ternary_reloption(local_relopts *relopts, const char *name,
+ const char *desc, pg_ternary default_val,
+ int offset)
+{
+ relopt_ternary *newoption;
+
+ newoption =
+ init_ternary_reloption(RELOPT_KIND_LOCAL, name, desc, default_val, 0);
+
+ add_local_reloption(relopts, (relopt_gen *) newoption, offset);
+}
/*
* init_real_reloption
@@ -1626,6 +1705,19 @@ parse_one_reloption(relopt_value *option, char *text_str, int text_len,
option->gen->name, value)));
}
break;
+ case RELOPT_TYPE_TERNARY:
+ {
+ bool b;
+
+ parsed = parse_bool(value, &b);
+ option->values.ternary_val = b ? TERNARY_TRUE : TERNARY_FALSE;
+ if (validate && !parsed)
+ ereport(ERROR,
+ (errcode(ERRCODE_INVALID_PARAMETER_VALUE),
+ errmsg("invalid value for ternary option \"%s\": %s",
+ option->gen->name, value)));
+ }
+ break;
case RELOPT_TYPE_INT:
{
relopt_int *optint = (relopt_int *) option->gen;
@@ -1789,17 +1881,6 @@ fillRelOptions(void *rdopts, Size basesize,
char *itempos = ((char *) rdopts) + elems[j].offset;
char *string_val;
- /*
- * If isset_offset is provided, store whether the reloption is
- * set there.
- */
- if (elems[j].isset_offset > 0)
- {
- char *setpos = ((char *) rdopts) + elems[j].isset_offset;
-
- *(bool *) setpos = options[i].isset;
- }
-
switch (options[i].gen->type)
{
case RELOPT_TYPE_BOOL:
@@ -1807,6 +1888,11 @@ fillRelOptions(void *rdopts, Size basesize,
options[i].values.bool_val :
((relopt_bool *) options[i].gen)->default_val;
break;
+ case RELOPT_TYPE_TERNARY:
+ *(pg_ternary *) itempos = options[i].isset ?
+ options[i].values.ternary_val :
+ ((relopt_ternary *) options[i].gen)->default_val;
+ break;
case RELOPT_TYPE_INT:
*(int *) itempos = options[i].isset ?
options[i].values.int_val :
@@ -1923,8 +2009,8 @@ default_reloptions(Datum reloptions, bool validate, relopt_kind kind)
offsetof(StdRdOptions, parallel_workers)},
{"vacuum_index_cleanup", RELOPT_TYPE_ENUM,
offsetof(StdRdOptions, vacuum_index_cleanup)},
- {"vacuum_truncate", RELOPT_TYPE_BOOL,
- offsetof(StdRdOptions, vacuum_truncate), offsetof(StdRdOptions, vacuum_truncate_set)},
+ {"vacuum_truncate", RELOPT_TYPE_TERNARY,
+ offsetof(StdRdOptions, vacuum_truncate)},
{"vacuum_max_eager_freeze_failure_rate", RELOPT_TYPE_REAL,
offsetof(StdRdOptions, vacuum_max_eager_freeze_failure_rate)}
};
@@ -2004,7 +2090,6 @@ build_local_reloptions(local_relopts *relopts, Datum options, bool validate)
elems[i].optname = opt->option->name;
elems[i].opttype = opt->option->type;
elems[i].offset = opt->offset;
- elems[i].isset_offset = 0; /* not supported for local relopts yet */
i++;
}
diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c
index aa4fbec143f..696eab9bd97 100644
--- a/src/backend/commands/vacuum.c
+++ b/src/backend/commands/vacuum.c
@@ -2224,9 +2224,9 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams params,
{
StdRdOptions *opts = (StdRdOptions *) rel->rd_options;
- if (opts && opts->vacuum_truncate_set)
+ if (opts && opts->vacuum_truncate != TERNARY_UNSET)
{
- if (opts->vacuum_truncate)
+ if (opts->vacuum_truncate == TERNARY_TRUE)
params.truncate = VACOPTVALUE_ENABLED;
else
params.truncate = VACOPTVALUE_DISABLED;
diff --git a/src/include/access/reloptions.h b/src/include/access/reloptions.h
index 2f08e1b0cf0..dfbef2babf2 100644
--- a/src/include/access/reloptions.h
+++ b/src/include/access/reloptions.h
@@ -29,6 +29,7 @@
typedef enum relopt_type
{
RELOPT_TYPE_BOOL,
+ RELOPT_TYPE_TERNARY, /* on, off, unset */
RELOPT_TYPE_INT,
RELOPT_TYPE_REAL,
RELOPT_TYPE_ENUM,
@@ -80,6 +81,7 @@ typedef struct relopt_value
union
{
bool bool_val;
+ pg_ternary ternary_val;
int int_val;
double real_val;
int enum_val;
@@ -94,6 +96,12 @@ typedef struct relopt_bool
bool default_val;
} relopt_bool;
+typedef struct relopt_ternary
+{
+ relopt_gen gen;
+ int default_val;
+} relopt_ternary;
+
typedef struct relopt_int
{
relopt_gen gen;
@@ -152,19 +160,6 @@ typedef struct
const char *optname; /* option's name */
relopt_type opttype; /* option's datatype */
int offset; /* offset of field in result struct */
-
- /*
- * isset_offset is an optional offset of a field in the result struct that
- * stores whether the option is explicitly set for the relation or if it
- * just picked up the default value. In most cases, this can be
- * accomplished by giving the reloption a special out-of-range default
- * value (e.g., some integer reloptions use -2), but this isn't always
- * possible. For example, a Boolean reloption cannot be given an
- * out-of-range default, so we need another way to discover the source of
- * its value. This offset is only used if given a value greater than
- * zero.
- */
- int isset_offset;
} relopt_parse_elt;
/* Local reloption definition */
@@ -195,6 +190,9 @@ typedef struct local_relopts
extern relopt_kind add_reloption_kind(void);
extern void add_bool_reloption(bits32 kinds, const char *name, const char *desc,
bool default_val, LOCKMODE lockmode);
+extern void add_ternary_reloption(bits32 kinds, const char *name,
+ const char *desc, pg_ternary default_val,
+ LOCKMODE lockmode);
extern void add_int_reloption(bits32 kinds, const char *name, const char *desc,
int default_val, int min_val, int max_val,
LOCKMODE lockmode);
@@ -214,6 +212,9 @@ extern void register_reloptions_validator(local_relopts *relopts,
extern void add_local_bool_reloption(local_relopts *relopts, const char *name,
const char *desc, bool default_val,
int offset);
+extern void add_local_ternary_reloption(local_relopts *relopts,
+ const char *name, const char *desc,
+ pg_ternary default_val, int offset);
extern void add_local_int_reloption(local_relopts *relopts, const char *name,
const char *desc, int default_val,
int min_val, int max_val, int offset);
diff --git a/src/include/postgres.h b/src/include/postgres.h
index 7d93fbce709..47c88780776 100644
--- a/src/include/postgres.h
+++ b/src/include/postgres.h
@@ -543,6 +543,21 @@ Float8GetDatum(float8 X)
* ----------------------------------------------------------------
*/
+/*
+ * pg_ternary
+ * Boolean value with an extra "unset" value
+ *
+ * This enum can be used for values that want to distinguish between true,
+ * false, and unset.
+*/
+
+typedef enum pg_ternary
+{
+ TERNARY_FALSE = 0,
+ TERNARY_TRUE = 1,
+ TERNARY_UNSET = -1
+} pg_ternary;
+
/*
* NON_EXEC_STATIC: It's sometimes useful to define a variable or function
* that is normally static but extern when using EXEC_BACKEND (see
diff --git a/src/include/utils/rel.h b/src/include/utils/rel.h
index d03ab247788..5feb18a5373 100644
--- a/src/include/utils/rel.h
+++ b/src/include/utils/rel.h
@@ -347,8 +347,7 @@ typedef struct StdRdOptions
bool user_catalog_table; /* use as an additional catalog relation */
int parallel_workers; /* max number of parallel workers */
StdRdOptIndexCleanup vacuum_index_cleanup; /* controls index vacuuming */
- bool vacuum_truncate; /* enables vacuum to truncate a relation */
- bool vacuum_truncate_set; /* whether vacuum_truncate is set */
+ pg_ternary vacuum_truncate; /* enables vacuum to truncate a relation */
/*
* Fraction of pages in a relation that vacuum can eagerly scan and fail
diff --git a/src/test/regress/expected/reloptions.out b/src/test/regress/expected/reloptions.out
index 9de19b4e3f1..1c99f79ab01 100644
--- a/src/test/regress/expected/reloptions.out
+++ b/src/test/regress/expected/reloptions.out
@@ -98,6 +98,42 @@ SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass;
{fillfactor=13,autovacuum_enabled=false}
(1 row)
+-- Tests for future (FIXME) ternary options
+-- behave as boolean option: accept unassigned name and truncated value
+DROP TABLE reloptions_test;
+CREATE TABLE reloptions_test(i INT) WITH (vacuum_truncate);
+SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass;
+ reloptions
+------------------------
+ {vacuum_truncate=true}
+(1 row)
+
+DROP TABLE reloptions_test;
+CREATE TABLE reloptions_test(i INT) WITH (vacuum_truncate=FaLS);
+SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass;
+ reloptions
+------------------------
+ {vacuum_truncate=fals}
+(1 row)
+
+-- preferred "true" alias is used when storing
+DROP TABLE reloptions_test;
+CREATE TABLE reloptions_test(i INT) WITH (vacuum_index_cleanup=on);
+SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass;
+ reloptions
+---------------------------
+ {vacuum_index_cleanup=on}
+(1 row)
+
+-- custom "third" value is available
+DROP TABLE reloptions_test;
+CREATE TABLE reloptions_test(i INT) WITH (vacuum_index_cleanup=auto);
+SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass;
+ reloptions
+-----------------------------
+ {vacuum_index_cleanup=auto}
+(1 row)
+
-- Test vacuum_truncate option
DROP TABLE reloptions_test;
CREATE TEMP TABLE reloptions_test(i INT NOT NULL, j text)
diff --git a/src/test/regress/sql/reloptions.sql b/src/test/regress/sql/reloptions.sql
index 24fbe0b478d..f5980dafcbc 100644
--- a/src/test/regress/sql/reloptions.sql
+++ b/src/test/regress/sql/reloptions.sql
@@ -59,6 +59,27 @@ UPDATE pg_class
ALTER TABLE reloptions_test RESET (illegal_option);
SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass;
+-- Tests for future (FIXME) ternary options
+
+-- behave as boolean option: accept unassigned name and truncated value
+DROP TABLE reloptions_test;
+CREATE TABLE reloptions_test(i INT) WITH (vacuum_truncate);
+SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass;
+
+DROP TABLE reloptions_test;
+CREATE TABLE reloptions_test(i INT) WITH (vacuum_truncate=FaLS);
+SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass;
+
+-- preferred "true" alias is used when storing
+DROP TABLE reloptions_test;
+CREATE TABLE reloptions_test(i INT) WITH (vacuum_index_cleanup=on);
+SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass;
+
+-- custom "third" value is available
+DROP TABLE reloptions_test;
+CREATE TABLE reloptions_test(i INT) WITH (vacuum_index_cleanup=auto);
+SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass;
+
-- Test vacuum_truncate option
DROP TABLE reloptions_test;
diff --git a/src/tools/pgindent/typedefs.list b/src/tools/pgindent/typedefs.list
index 3f3a888fd0e..1c8610fd46c 100644
--- a/src/tools/pgindent/typedefs.list
+++ b/src/tools/pgindent/typedefs.list
@@ -3952,6 +3952,7 @@ pg_sha512_ctx
pg_snapshot
pg_special_case
pg_stack_base_t
+pg_ternary
pg_time_t
pg_time_usec_t
pg_tz
@@ -4079,6 +4080,7 @@ relopt_kind
relopt_parse_elt
relopt_real
relopt_string
+relopt_ternary
relopt_type
relopt_value
relopts_validator
--
2.47.3
--c36t3fndxhkzbl5z--
^ permalink raw reply [nested|flat] 868+ messages in thread
* [PATCH v3] Introduce ternary reloptions
@ 2026-01-16 15:04 Álvaro Herrera <[email protected]>
0 siblings, 0 replies; 868+ messages in thread
From: Álvaro Herrera @ 2026-01-16 15:04 UTC (permalink / raw)
Introduce ternary reloption as a replacement for current
`vacuum_truncate` implementation. Remove the `vacuum_truncate_set`
separate flag and use TERNARY_UNSET instead.
This could also be used for other options such as `vacuum_index_cleanup`
and `buffering`, but lets get the scaffolding in first.
Discussion: https://postgr.es/m/3474141.usfYGdeWWP@thinkpad-pgpro
---
src/backend/access/common/reloptions.c | 137 ++++++++++++++++++-----
src/backend/commands/vacuum.c | 4 +-
src/include/access/reloptions.h | 27 ++---
src/include/postgres.h | 15 +++
src/include/utils/rel.h | 3 +-
src/test/regress/expected/reloptions.out | 36 ++++++
src/test/regress/sql/reloptions.sql | 21 ++++
src/tools/pgindent/typedefs.list | 2 +
8 files changed, 202 insertions(+), 43 deletions(-)
diff --git a/src/backend/access/common/reloptions.c b/src/backend/access/common/reloptions.c
index 0b83f98ed5f..6f1f577581a 100644
--- a/src/backend/access/common/reloptions.c
+++ b/src/backend/access/common/reloptions.c
@@ -40,9 +40,9 @@
*
* To add an option:
*
- * (i) decide on a type (bool, integer, real, enum, string), name, default
- * value, upper and lower bounds (if applicable); for strings, consider a
- * validation routine.
+ * (i) decide on a type (bool, ternary, integer, real, enum, string), name,
+ * default value, upper and lower bounds (if applicable); for strings,
+ * consider a validation routine.
* (ii) add a record below (or use add_<type>_reloption).
* (iii) add it to the appropriate options struct (perhaps StdRdOptions)
* (iv) add it to the appropriate handling routine (perhaps
@@ -147,15 +147,6 @@ static relopt_bool boolRelOpts[] =
},
false
},
- {
- {
- "vacuum_truncate",
- "Enables vacuum to truncate empty pages at the end of this table",
- RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
- ShareUpdateExclusiveLock
- },
- true
- },
{
{
"deduplicate_items",
@@ -170,6 +161,25 @@ static relopt_bool boolRelOpts[] =
{{NULL}}
};
+static relopt_ternary ternaryRelOpts[] =
+{
+ {
+ {
+ "vacuum_truncate",
+ "Enables vacuum to truncate empty pages at the end of this table",
+ RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
+ ShareUpdateExclusiveLock
+ },
+ TERNARY_UNSET
+ },
+ /* list terminator */
+ {
+ {
+ NULL
+ }
+ }
+};
+
static relopt_int intRelOpts[] =
{
{
@@ -609,6 +619,13 @@ initialize_reloptions(void)
boolRelOpts[i].gen.lockmode));
j++;
}
+ for (i = 0; ternaryRelOpts[i].gen.name; i++)
+ {
+ Assert(DoLockModesConflict(ternaryRelOpts[i].gen.lockmode,
+ ternaryRelOpts[i].gen.lockmode));
+ j++;
+ }
+
for (i = 0; intRelOpts[i].gen.name; i++)
{
Assert(DoLockModesConflict(intRelOpts[i].gen.lockmode,
@@ -649,6 +666,14 @@ initialize_reloptions(void)
j++;
}
+ for (i = 0; ternaryRelOpts[i].gen.name; i++)
+ {
+ relOpts[j] = &ternaryRelOpts[i].gen;
+ relOpts[j]->type = RELOPT_TYPE_TERNARY;
+ relOpts[j]->namelen = strlen(relOpts[j]->name);
+ j++;
+ }
+
for (i = 0; intRelOpts[i].gen.name; i++)
{
relOpts[j] = &intRelOpts[i].gen;
@@ -809,6 +834,9 @@ allocate_reloption(bits32 kinds, int type, const char *name, const char *desc,
case RELOPT_TYPE_BOOL:
size = sizeof(relopt_bool);
break;
+ case RELOPT_TYPE_TERNARY:
+ size = sizeof(relopt_ternary);
+ break;
case RELOPT_TYPE_INT:
size = sizeof(relopt_int);
break;
@@ -892,6 +920,57 @@ add_local_bool_reloption(local_relopts *relopts, const char *name,
add_local_reloption(relopts, (relopt_gen *) newoption, offset);
}
+/*
+ * init_ternary_reloption
+ * Allocate and initialize a new ternary reloption
+ */
+static relopt_ternary *
+init_ternary_reloption(bits32 kinds, const char *name, const char *desc,
+ pg_ternary default_val, LOCKMODE lockmode)
+{
+ relopt_ternary *newoption;
+
+ newoption = (relopt_ternary *)
+ allocate_reloption(kinds, RELOPT_TYPE_TERNARY, name, desc, lockmode);
+ newoption->default_val = default_val;
+
+ return newoption;
+}
+
+/*
+ * add_ternary_reloption
+ * Add a new ternary reloption
+ */
+void
+add_ternary_reloption(bits32 kinds, const char *name, const char *desc,
+ pg_ternary default_val, LOCKMODE lockmode)
+{
+ relopt_ternary *newoption;
+
+ newoption =
+ init_ternary_reloption(kinds, name, desc, default_val, lockmode);
+
+ add_reloption((relopt_gen *) newoption);
+}
+
+/*
+ * add_local_ternary_reloption
+ * Add a new ternary local reloption
+ *
+ * 'offset' is offset of ternary-typed field.
+ */
+void
+add_local_ternary_reloption(local_relopts *relopts, const char *name,
+ const char *desc, pg_ternary default_val,
+ int offset)
+{
+ relopt_ternary *newoption;
+
+ newoption =
+ init_ternary_reloption(RELOPT_KIND_LOCAL, name, desc, default_val, 0);
+
+ add_local_reloption(relopts, (relopt_gen *) newoption, offset);
+}
/*
* init_real_reloption
@@ -1626,6 +1705,19 @@ parse_one_reloption(relopt_value *option, char *text_str, int text_len,
option->gen->name, value)));
}
break;
+ case RELOPT_TYPE_TERNARY:
+ {
+ bool b;
+
+ parsed = parse_bool(value, &b);
+ option->values.ternary_val = b ? TERNARY_TRUE : TERNARY_FALSE;
+ if (validate && !parsed)
+ ereport(ERROR,
+ (errcode(ERRCODE_INVALID_PARAMETER_VALUE),
+ errmsg("invalid value for ternary option \"%s\": %s",
+ option->gen->name, value)));
+ }
+ break;
case RELOPT_TYPE_INT:
{
relopt_int *optint = (relopt_int *) option->gen;
@@ -1789,17 +1881,6 @@ fillRelOptions(void *rdopts, Size basesize,
char *itempos = ((char *) rdopts) + elems[j].offset;
char *string_val;
- /*
- * If isset_offset is provided, store whether the reloption is
- * set there.
- */
- if (elems[j].isset_offset > 0)
- {
- char *setpos = ((char *) rdopts) + elems[j].isset_offset;
-
- *(bool *) setpos = options[i].isset;
- }
-
switch (options[i].gen->type)
{
case RELOPT_TYPE_BOOL:
@@ -1807,6 +1888,11 @@ fillRelOptions(void *rdopts, Size basesize,
options[i].values.bool_val :
((relopt_bool *) options[i].gen)->default_val;
break;
+ case RELOPT_TYPE_TERNARY:
+ *(pg_ternary *) itempos = options[i].isset ?
+ options[i].values.ternary_val :
+ ((relopt_ternary *) options[i].gen)->default_val;
+ break;
case RELOPT_TYPE_INT:
*(int *) itempos = options[i].isset ?
options[i].values.int_val :
@@ -1923,8 +2009,8 @@ default_reloptions(Datum reloptions, bool validate, relopt_kind kind)
offsetof(StdRdOptions, parallel_workers)},
{"vacuum_index_cleanup", RELOPT_TYPE_ENUM,
offsetof(StdRdOptions, vacuum_index_cleanup)},
- {"vacuum_truncate", RELOPT_TYPE_BOOL,
- offsetof(StdRdOptions, vacuum_truncate), offsetof(StdRdOptions, vacuum_truncate_set)},
+ {"vacuum_truncate", RELOPT_TYPE_TERNARY,
+ offsetof(StdRdOptions, vacuum_truncate)},
{"vacuum_max_eager_freeze_failure_rate", RELOPT_TYPE_REAL,
offsetof(StdRdOptions, vacuum_max_eager_freeze_failure_rate)}
};
@@ -2004,7 +2090,6 @@ build_local_reloptions(local_relopts *relopts, Datum options, bool validate)
elems[i].optname = opt->option->name;
elems[i].opttype = opt->option->type;
elems[i].offset = opt->offset;
- elems[i].isset_offset = 0; /* not supported for local relopts yet */
i++;
}
diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c
index aa4fbec143f..696eab9bd97 100644
--- a/src/backend/commands/vacuum.c
+++ b/src/backend/commands/vacuum.c
@@ -2224,9 +2224,9 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams params,
{
StdRdOptions *opts = (StdRdOptions *) rel->rd_options;
- if (opts && opts->vacuum_truncate_set)
+ if (opts && opts->vacuum_truncate != TERNARY_UNSET)
{
- if (opts->vacuum_truncate)
+ if (opts->vacuum_truncate == TERNARY_TRUE)
params.truncate = VACOPTVALUE_ENABLED;
else
params.truncate = VACOPTVALUE_DISABLED;
diff --git a/src/include/access/reloptions.h b/src/include/access/reloptions.h
index 2f08e1b0cf0..dfbef2babf2 100644
--- a/src/include/access/reloptions.h
+++ b/src/include/access/reloptions.h
@@ -29,6 +29,7 @@
typedef enum relopt_type
{
RELOPT_TYPE_BOOL,
+ RELOPT_TYPE_TERNARY, /* on, off, unset */
RELOPT_TYPE_INT,
RELOPT_TYPE_REAL,
RELOPT_TYPE_ENUM,
@@ -80,6 +81,7 @@ typedef struct relopt_value
union
{
bool bool_val;
+ pg_ternary ternary_val;
int int_val;
double real_val;
int enum_val;
@@ -94,6 +96,12 @@ typedef struct relopt_bool
bool default_val;
} relopt_bool;
+typedef struct relopt_ternary
+{
+ relopt_gen gen;
+ int default_val;
+} relopt_ternary;
+
typedef struct relopt_int
{
relopt_gen gen;
@@ -152,19 +160,6 @@ typedef struct
const char *optname; /* option's name */
relopt_type opttype; /* option's datatype */
int offset; /* offset of field in result struct */
-
- /*
- * isset_offset is an optional offset of a field in the result struct that
- * stores whether the option is explicitly set for the relation or if it
- * just picked up the default value. In most cases, this can be
- * accomplished by giving the reloption a special out-of-range default
- * value (e.g., some integer reloptions use -2), but this isn't always
- * possible. For example, a Boolean reloption cannot be given an
- * out-of-range default, so we need another way to discover the source of
- * its value. This offset is only used if given a value greater than
- * zero.
- */
- int isset_offset;
} relopt_parse_elt;
/* Local reloption definition */
@@ -195,6 +190,9 @@ typedef struct local_relopts
extern relopt_kind add_reloption_kind(void);
extern void add_bool_reloption(bits32 kinds, const char *name, const char *desc,
bool default_val, LOCKMODE lockmode);
+extern void add_ternary_reloption(bits32 kinds, const char *name,
+ const char *desc, pg_ternary default_val,
+ LOCKMODE lockmode);
extern void add_int_reloption(bits32 kinds, const char *name, const char *desc,
int default_val, int min_val, int max_val,
LOCKMODE lockmode);
@@ -214,6 +212,9 @@ extern void register_reloptions_validator(local_relopts *relopts,
extern void add_local_bool_reloption(local_relopts *relopts, const char *name,
const char *desc, bool default_val,
int offset);
+extern void add_local_ternary_reloption(local_relopts *relopts,
+ const char *name, const char *desc,
+ pg_ternary default_val, int offset);
extern void add_local_int_reloption(local_relopts *relopts, const char *name,
const char *desc, int default_val,
int min_val, int max_val, int offset);
diff --git a/src/include/postgres.h b/src/include/postgres.h
index 7d93fbce709..47c88780776 100644
--- a/src/include/postgres.h
+++ b/src/include/postgres.h
@@ -543,6 +543,21 @@ Float8GetDatum(float8 X)
* ----------------------------------------------------------------
*/
+/*
+ * pg_ternary
+ * Boolean value with an extra "unset" value
+ *
+ * This enum can be used for values that want to distinguish between true,
+ * false, and unset.
+*/
+
+typedef enum pg_ternary
+{
+ TERNARY_FALSE = 0,
+ TERNARY_TRUE = 1,
+ TERNARY_UNSET = -1
+} pg_ternary;
+
/*
* NON_EXEC_STATIC: It's sometimes useful to define a variable or function
* that is normally static but extern when using EXEC_BACKEND (see
diff --git a/src/include/utils/rel.h b/src/include/utils/rel.h
index d03ab247788..5feb18a5373 100644
--- a/src/include/utils/rel.h
+++ b/src/include/utils/rel.h
@@ -347,8 +347,7 @@ typedef struct StdRdOptions
bool user_catalog_table; /* use as an additional catalog relation */
int parallel_workers; /* max number of parallel workers */
StdRdOptIndexCleanup vacuum_index_cleanup; /* controls index vacuuming */
- bool vacuum_truncate; /* enables vacuum to truncate a relation */
- bool vacuum_truncate_set; /* whether vacuum_truncate is set */
+ pg_ternary vacuum_truncate; /* enables vacuum to truncate a relation */
/*
* Fraction of pages in a relation that vacuum can eagerly scan and fail
diff --git a/src/test/regress/expected/reloptions.out b/src/test/regress/expected/reloptions.out
index 9de19b4e3f1..1c99f79ab01 100644
--- a/src/test/regress/expected/reloptions.out
+++ b/src/test/regress/expected/reloptions.out
@@ -98,6 +98,42 @@ SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass;
{fillfactor=13,autovacuum_enabled=false}
(1 row)
+-- Tests for future (FIXME) ternary options
+-- behave as boolean option: accept unassigned name and truncated value
+DROP TABLE reloptions_test;
+CREATE TABLE reloptions_test(i INT) WITH (vacuum_truncate);
+SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass;
+ reloptions
+------------------------
+ {vacuum_truncate=true}
+(1 row)
+
+DROP TABLE reloptions_test;
+CREATE TABLE reloptions_test(i INT) WITH (vacuum_truncate=FaLS);
+SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass;
+ reloptions
+------------------------
+ {vacuum_truncate=fals}
+(1 row)
+
+-- preferred "true" alias is used when storing
+DROP TABLE reloptions_test;
+CREATE TABLE reloptions_test(i INT) WITH (vacuum_index_cleanup=on);
+SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass;
+ reloptions
+---------------------------
+ {vacuum_index_cleanup=on}
+(1 row)
+
+-- custom "third" value is available
+DROP TABLE reloptions_test;
+CREATE TABLE reloptions_test(i INT) WITH (vacuum_index_cleanup=auto);
+SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass;
+ reloptions
+-----------------------------
+ {vacuum_index_cleanup=auto}
+(1 row)
+
-- Test vacuum_truncate option
DROP TABLE reloptions_test;
CREATE TEMP TABLE reloptions_test(i INT NOT NULL, j text)
diff --git a/src/test/regress/sql/reloptions.sql b/src/test/regress/sql/reloptions.sql
index 24fbe0b478d..f5980dafcbc 100644
--- a/src/test/regress/sql/reloptions.sql
+++ b/src/test/regress/sql/reloptions.sql
@@ -59,6 +59,27 @@ UPDATE pg_class
ALTER TABLE reloptions_test RESET (illegal_option);
SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass;
+-- Tests for future (FIXME) ternary options
+
+-- behave as boolean option: accept unassigned name and truncated value
+DROP TABLE reloptions_test;
+CREATE TABLE reloptions_test(i INT) WITH (vacuum_truncate);
+SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass;
+
+DROP TABLE reloptions_test;
+CREATE TABLE reloptions_test(i INT) WITH (vacuum_truncate=FaLS);
+SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass;
+
+-- preferred "true" alias is used when storing
+DROP TABLE reloptions_test;
+CREATE TABLE reloptions_test(i INT) WITH (vacuum_index_cleanup=on);
+SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass;
+
+-- custom "third" value is available
+DROP TABLE reloptions_test;
+CREATE TABLE reloptions_test(i INT) WITH (vacuum_index_cleanup=auto);
+SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass;
+
-- Test vacuum_truncate option
DROP TABLE reloptions_test;
diff --git a/src/tools/pgindent/typedefs.list b/src/tools/pgindent/typedefs.list
index 3f3a888fd0e..1c8610fd46c 100644
--- a/src/tools/pgindent/typedefs.list
+++ b/src/tools/pgindent/typedefs.list
@@ -3952,6 +3952,7 @@ pg_sha512_ctx
pg_snapshot
pg_special_case
pg_stack_base_t
+pg_ternary
pg_time_t
pg_time_usec_t
pg_tz
@@ -4079,6 +4080,7 @@ relopt_kind
relopt_parse_elt
relopt_real
relopt_string
+relopt_ternary
relopt_type
relopt_value
relopts_validator
--
2.47.3
--c36t3fndxhkzbl5z--
^ permalink raw reply [nested|flat] 868+ messages in thread
* [PATCH v3] Introduce ternary reloptions
@ 2026-01-16 15:04 Álvaro Herrera <[email protected]>
0 siblings, 0 replies; 868+ messages in thread
From: Álvaro Herrera @ 2026-01-16 15:04 UTC (permalink / raw)
Introduce ternary reloption as a replacement for current
`vacuum_truncate` implementation. Remove the `vacuum_truncate_set`
separate flag and use TERNARY_UNSET instead.
This could also be used for other options such as `vacuum_index_cleanup`
and `buffering`, but lets get the scaffolding in first.
Discussion: https://postgr.es/m/3474141.usfYGdeWWP@thinkpad-pgpro
---
src/backend/access/common/reloptions.c | 137 ++++++++++++++++++-----
src/backend/commands/vacuum.c | 4 +-
src/include/access/reloptions.h | 27 ++---
src/include/postgres.h | 15 +++
src/include/utils/rel.h | 3 +-
src/test/regress/expected/reloptions.out | 36 ++++++
src/test/regress/sql/reloptions.sql | 21 ++++
src/tools/pgindent/typedefs.list | 2 +
8 files changed, 202 insertions(+), 43 deletions(-)
diff --git a/src/backend/access/common/reloptions.c b/src/backend/access/common/reloptions.c
index 0b83f98ed5f..6f1f577581a 100644
--- a/src/backend/access/common/reloptions.c
+++ b/src/backend/access/common/reloptions.c
@@ -40,9 +40,9 @@
*
* To add an option:
*
- * (i) decide on a type (bool, integer, real, enum, string), name, default
- * value, upper and lower bounds (if applicable); for strings, consider a
- * validation routine.
+ * (i) decide on a type (bool, ternary, integer, real, enum, string), name,
+ * default value, upper and lower bounds (if applicable); for strings,
+ * consider a validation routine.
* (ii) add a record below (or use add_<type>_reloption).
* (iii) add it to the appropriate options struct (perhaps StdRdOptions)
* (iv) add it to the appropriate handling routine (perhaps
@@ -147,15 +147,6 @@ static relopt_bool boolRelOpts[] =
},
false
},
- {
- {
- "vacuum_truncate",
- "Enables vacuum to truncate empty pages at the end of this table",
- RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
- ShareUpdateExclusiveLock
- },
- true
- },
{
{
"deduplicate_items",
@@ -170,6 +161,25 @@ static relopt_bool boolRelOpts[] =
{{NULL}}
};
+static relopt_ternary ternaryRelOpts[] =
+{
+ {
+ {
+ "vacuum_truncate",
+ "Enables vacuum to truncate empty pages at the end of this table",
+ RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
+ ShareUpdateExclusiveLock
+ },
+ TERNARY_UNSET
+ },
+ /* list terminator */
+ {
+ {
+ NULL
+ }
+ }
+};
+
static relopt_int intRelOpts[] =
{
{
@@ -609,6 +619,13 @@ initialize_reloptions(void)
boolRelOpts[i].gen.lockmode));
j++;
}
+ for (i = 0; ternaryRelOpts[i].gen.name; i++)
+ {
+ Assert(DoLockModesConflict(ternaryRelOpts[i].gen.lockmode,
+ ternaryRelOpts[i].gen.lockmode));
+ j++;
+ }
+
for (i = 0; intRelOpts[i].gen.name; i++)
{
Assert(DoLockModesConflict(intRelOpts[i].gen.lockmode,
@@ -649,6 +666,14 @@ initialize_reloptions(void)
j++;
}
+ for (i = 0; ternaryRelOpts[i].gen.name; i++)
+ {
+ relOpts[j] = &ternaryRelOpts[i].gen;
+ relOpts[j]->type = RELOPT_TYPE_TERNARY;
+ relOpts[j]->namelen = strlen(relOpts[j]->name);
+ j++;
+ }
+
for (i = 0; intRelOpts[i].gen.name; i++)
{
relOpts[j] = &intRelOpts[i].gen;
@@ -809,6 +834,9 @@ allocate_reloption(bits32 kinds, int type, const char *name, const char *desc,
case RELOPT_TYPE_BOOL:
size = sizeof(relopt_bool);
break;
+ case RELOPT_TYPE_TERNARY:
+ size = sizeof(relopt_ternary);
+ break;
case RELOPT_TYPE_INT:
size = sizeof(relopt_int);
break;
@@ -892,6 +920,57 @@ add_local_bool_reloption(local_relopts *relopts, const char *name,
add_local_reloption(relopts, (relopt_gen *) newoption, offset);
}
+/*
+ * init_ternary_reloption
+ * Allocate and initialize a new ternary reloption
+ */
+static relopt_ternary *
+init_ternary_reloption(bits32 kinds, const char *name, const char *desc,
+ pg_ternary default_val, LOCKMODE lockmode)
+{
+ relopt_ternary *newoption;
+
+ newoption = (relopt_ternary *)
+ allocate_reloption(kinds, RELOPT_TYPE_TERNARY, name, desc, lockmode);
+ newoption->default_val = default_val;
+
+ return newoption;
+}
+
+/*
+ * add_ternary_reloption
+ * Add a new ternary reloption
+ */
+void
+add_ternary_reloption(bits32 kinds, const char *name, const char *desc,
+ pg_ternary default_val, LOCKMODE lockmode)
+{
+ relopt_ternary *newoption;
+
+ newoption =
+ init_ternary_reloption(kinds, name, desc, default_val, lockmode);
+
+ add_reloption((relopt_gen *) newoption);
+}
+
+/*
+ * add_local_ternary_reloption
+ * Add a new ternary local reloption
+ *
+ * 'offset' is offset of ternary-typed field.
+ */
+void
+add_local_ternary_reloption(local_relopts *relopts, const char *name,
+ const char *desc, pg_ternary default_val,
+ int offset)
+{
+ relopt_ternary *newoption;
+
+ newoption =
+ init_ternary_reloption(RELOPT_KIND_LOCAL, name, desc, default_val, 0);
+
+ add_local_reloption(relopts, (relopt_gen *) newoption, offset);
+}
/*
* init_real_reloption
@@ -1626,6 +1705,19 @@ parse_one_reloption(relopt_value *option, char *text_str, int text_len,
option->gen->name, value)));
}
break;
+ case RELOPT_TYPE_TERNARY:
+ {
+ bool b;
+
+ parsed = parse_bool(value, &b);
+ option->values.ternary_val = b ? TERNARY_TRUE : TERNARY_FALSE;
+ if (validate && !parsed)
+ ereport(ERROR,
+ (errcode(ERRCODE_INVALID_PARAMETER_VALUE),
+ errmsg("invalid value for ternary option \"%s\": %s",
+ option->gen->name, value)));
+ }
+ break;
case RELOPT_TYPE_INT:
{
relopt_int *optint = (relopt_int *) option->gen;
@@ -1789,17 +1881,6 @@ fillRelOptions(void *rdopts, Size basesize,
char *itempos = ((char *) rdopts) + elems[j].offset;
char *string_val;
- /*
- * If isset_offset is provided, store whether the reloption is
- * set there.
- */
- if (elems[j].isset_offset > 0)
- {
- char *setpos = ((char *) rdopts) + elems[j].isset_offset;
-
- *(bool *) setpos = options[i].isset;
- }
-
switch (options[i].gen->type)
{
case RELOPT_TYPE_BOOL:
@@ -1807,6 +1888,11 @@ fillRelOptions(void *rdopts, Size basesize,
options[i].values.bool_val :
((relopt_bool *) options[i].gen)->default_val;
break;
+ case RELOPT_TYPE_TERNARY:
+ *(pg_ternary *) itempos = options[i].isset ?
+ options[i].values.ternary_val :
+ ((relopt_ternary *) options[i].gen)->default_val;
+ break;
case RELOPT_TYPE_INT:
*(int *) itempos = options[i].isset ?
options[i].values.int_val :
@@ -1923,8 +2009,8 @@ default_reloptions(Datum reloptions, bool validate, relopt_kind kind)
offsetof(StdRdOptions, parallel_workers)},
{"vacuum_index_cleanup", RELOPT_TYPE_ENUM,
offsetof(StdRdOptions, vacuum_index_cleanup)},
- {"vacuum_truncate", RELOPT_TYPE_BOOL,
- offsetof(StdRdOptions, vacuum_truncate), offsetof(StdRdOptions, vacuum_truncate_set)},
+ {"vacuum_truncate", RELOPT_TYPE_TERNARY,
+ offsetof(StdRdOptions, vacuum_truncate)},
{"vacuum_max_eager_freeze_failure_rate", RELOPT_TYPE_REAL,
offsetof(StdRdOptions, vacuum_max_eager_freeze_failure_rate)}
};
@@ -2004,7 +2090,6 @@ build_local_reloptions(local_relopts *relopts, Datum options, bool validate)
elems[i].optname = opt->option->name;
elems[i].opttype = opt->option->type;
elems[i].offset = opt->offset;
- elems[i].isset_offset = 0; /* not supported for local relopts yet */
i++;
}
diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c
index aa4fbec143f..696eab9bd97 100644
--- a/src/backend/commands/vacuum.c
+++ b/src/backend/commands/vacuum.c
@@ -2224,9 +2224,9 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams params,
{
StdRdOptions *opts = (StdRdOptions *) rel->rd_options;
- if (opts && opts->vacuum_truncate_set)
+ if (opts && opts->vacuum_truncate != TERNARY_UNSET)
{
- if (opts->vacuum_truncate)
+ if (opts->vacuum_truncate == TERNARY_TRUE)
params.truncate = VACOPTVALUE_ENABLED;
else
params.truncate = VACOPTVALUE_DISABLED;
diff --git a/src/include/access/reloptions.h b/src/include/access/reloptions.h
index 2f08e1b0cf0..dfbef2babf2 100644
--- a/src/include/access/reloptions.h
+++ b/src/include/access/reloptions.h
@@ -29,6 +29,7 @@
typedef enum relopt_type
{
RELOPT_TYPE_BOOL,
+ RELOPT_TYPE_TERNARY, /* on, off, unset */
RELOPT_TYPE_INT,
RELOPT_TYPE_REAL,
RELOPT_TYPE_ENUM,
@@ -80,6 +81,7 @@ typedef struct relopt_value
union
{
bool bool_val;
+ pg_ternary ternary_val;
int int_val;
double real_val;
int enum_val;
@@ -94,6 +96,12 @@ typedef struct relopt_bool
bool default_val;
} relopt_bool;
+typedef struct relopt_ternary
+{
+ relopt_gen gen;
+ int default_val;
+} relopt_ternary;
+
typedef struct relopt_int
{
relopt_gen gen;
@@ -152,19 +160,6 @@ typedef struct
const char *optname; /* option's name */
relopt_type opttype; /* option's datatype */
int offset; /* offset of field in result struct */
-
- /*
- * isset_offset is an optional offset of a field in the result struct that
- * stores whether the option is explicitly set for the relation or if it
- * just picked up the default value. In most cases, this can be
- * accomplished by giving the reloption a special out-of-range default
- * value (e.g., some integer reloptions use -2), but this isn't always
- * possible. For example, a Boolean reloption cannot be given an
- * out-of-range default, so we need another way to discover the source of
- * its value. This offset is only used if given a value greater than
- * zero.
- */
- int isset_offset;
} relopt_parse_elt;
/* Local reloption definition */
@@ -195,6 +190,9 @@ typedef struct local_relopts
extern relopt_kind add_reloption_kind(void);
extern void add_bool_reloption(bits32 kinds, const char *name, const char *desc,
bool default_val, LOCKMODE lockmode);
+extern void add_ternary_reloption(bits32 kinds, const char *name,
+ const char *desc, pg_ternary default_val,
+ LOCKMODE lockmode);
extern void add_int_reloption(bits32 kinds, const char *name, const char *desc,
int default_val, int min_val, int max_val,
LOCKMODE lockmode);
@@ -214,6 +212,9 @@ extern void register_reloptions_validator(local_relopts *relopts,
extern void add_local_bool_reloption(local_relopts *relopts, const char *name,
const char *desc, bool default_val,
int offset);
+extern void add_local_ternary_reloption(local_relopts *relopts,
+ const char *name, const char *desc,
+ pg_ternary default_val, int offset);
extern void add_local_int_reloption(local_relopts *relopts, const char *name,
const char *desc, int default_val,
int min_val, int max_val, int offset);
diff --git a/src/include/postgres.h b/src/include/postgres.h
index 7d93fbce709..47c88780776 100644
--- a/src/include/postgres.h
+++ b/src/include/postgres.h
@@ -543,6 +543,21 @@ Float8GetDatum(float8 X)
* ----------------------------------------------------------------
*/
+/*
+ * pg_ternary
+ * Boolean value with an extra "unset" value
+ *
+ * This enum can be used for values that want to distinguish between true,
+ * false, and unset.
+*/
+
+typedef enum pg_ternary
+{
+ TERNARY_FALSE = 0,
+ TERNARY_TRUE = 1,
+ TERNARY_UNSET = -1
+} pg_ternary;
+
/*
* NON_EXEC_STATIC: It's sometimes useful to define a variable or function
* that is normally static but extern when using EXEC_BACKEND (see
diff --git a/src/include/utils/rel.h b/src/include/utils/rel.h
index d03ab247788..5feb18a5373 100644
--- a/src/include/utils/rel.h
+++ b/src/include/utils/rel.h
@@ -347,8 +347,7 @@ typedef struct StdRdOptions
bool user_catalog_table; /* use as an additional catalog relation */
int parallel_workers; /* max number of parallel workers */
StdRdOptIndexCleanup vacuum_index_cleanup; /* controls index vacuuming */
- bool vacuum_truncate; /* enables vacuum to truncate a relation */
- bool vacuum_truncate_set; /* whether vacuum_truncate is set */
+ pg_ternary vacuum_truncate; /* enables vacuum to truncate a relation */
/*
* Fraction of pages in a relation that vacuum can eagerly scan and fail
diff --git a/src/test/regress/expected/reloptions.out b/src/test/regress/expected/reloptions.out
index 9de19b4e3f1..1c99f79ab01 100644
--- a/src/test/regress/expected/reloptions.out
+++ b/src/test/regress/expected/reloptions.out
@@ -98,6 +98,42 @@ SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass;
{fillfactor=13,autovacuum_enabled=false}
(1 row)
+-- Tests for future (FIXME) ternary options
+-- behave as boolean option: accept unassigned name and truncated value
+DROP TABLE reloptions_test;
+CREATE TABLE reloptions_test(i INT) WITH (vacuum_truncate);
+SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass;
+ reloptions
+------------------------
+ {vacuum_truncate=true}
+(1 row)
+
+DROP TABLE reloptions_test;
+CREATE TABLE reloptions_test(i INT) WITH (vacuum_truncate=FaLS);
+SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass;
+ reloptions
+------------------------
+ {vacuum_truncate=fals}
+(1 row)
+
+-- preferred "true" alias is used when storing
+DROP TABLE reloptions_test;
+CREATE TABLE reloptions_test(i INT) WITH (vacuum_index_cleanup=on);
+SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass;
+ reloptions
+---------------------------
+ {vacuum_index_cleanup=on}
+(1 row)
+
+-- custom "third" value is available
+DROP TABLE reloptions_test;
+CREATE TABLE reloptions_test(i INT) WITH (vacuum_index_cleanup=auto);
+SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass;
+ reloptions
+-----------------------------
+ {vacuum_index_cleanup=auto}
+(1 row)
+
-- Test vacuum_truncate option
DROP TABLE reloptions_test;
CREATE TEMP TABLE reloptions_test(i INT NOT NULL, j text)
diff --git a/src/test/regress/sql/reloptions.sql b/src/test/regress/sql/reloptions.sql
index 24fbe0b478d..f5980dafcbc 100644
--- a/src/test/regress/sql/reloptions.sql
+++ b/src/test/regress/sql/reloptions.sql
@@ -59,6 +59,27 @@ UPDATE pg_class
ALTER TABLE reloptions_test RESET (illegal_option);
SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass;
+-- Tests for future (FIXME) ternary options
+
+-- behave as boolean option: accept unassigned name and truncated value
+DROP TABLE reloptions_test;
+CREATE TABLE reloptions_test(i INT) WITH (vacuum_truncate);
+SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass;
+
+DROP TABLE reloptions_test;
+CREATE TABLE reloptions_test(i INT) WITH (vacuum_truncate=FaLS);
+SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass;
+
+-- preferred "true" alias is used when storing
+DROP TABLE reloptions_test;
+CREATE TABLE reloptions_test(i INT) WITH (vacuum_index_cleanup=on);
+SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass;
+
+-- custom "third" value is available
+DROP TABLE reloptions_test;
+CREATE TABLE reloptions_test(i INT) WITH (vacuum_index_cleanup=auto);
+SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass;
+
-- Test vacuum_truncate option
DROP TABLE reloptions_test;
diff --git a/src/tools/pgindent/typedefs.list b/src/tools/pgindent/typedefs.list
index 3f3a888fd0e..1c8610fd46c 100644
--- a/src/tools/pgindent/typedefs.list
+++ b/src/tools/pgindent/typedefs.list
@@ -3952,6 +3952,7 @@ pg_sha512_ctx
pg_snapshot
pg_special_case
pg_stack_base_t
+pg_ternary
pg_time_t
pg_time_usec_t
pg_tz
@@ -4079,6 +4080,7 @@ relopt_kind
relopt_parse_elt
relopt_real
relopt_string
+relopt_ternary
relopt_type
relopt_value
relopts_validator
--
2.47.3
--c36t3fndxhkzbl5z--
^ permalink raw reply [nested|flat] 868+ messages in thread
* [PATCH v3] Introduce ternary reloptions
@ 2026-01-16 15:04 Álvaro Herrera <[email protected]>
0 siblings, 0 replies; 868+ messages in thread
From: Álvaro Herrera @ 2026-01-16 15:04 UTC (permalink / raw)
Introduce ternary reloption as a replacement for current
`vacuum_truncate` implementation. Remove the `vacuum_truncate_set`
separate flag and use TERNARY_UNSET instead.
This could also be used for other options such as `vacuum_index_cleanup`
and `buffering`, but lets get the scaffolding in first.
Discussion: https://postgr.es/m/3474141.usfYGdeWWP@thinkpad-pgpro
---
src/backend/access/common/reloptions.c | 137 ++++++++++++++++++-----
src/backend/commands/vacuum.c | 4 +-
src/include/access/reloptions.h | 27 ++---
src/include/postgres.h | 15 +++
src/include/utils/rel.h | 3 +-
src/test/regress/expected/reloptions.out | 36 ++++++
src/test/regress/sql/reloptions.sql | 21 ++++
src/tools/pgindent/typedefs.list | 2 +
8 files changed, 202 insertions(+), 43 deletions(-)
diff --git a/src/backend/access/common/reloptions.c b/src/backend/access/common/reloptions.c
index 0b83f98ed5f..6f1f577581a 100644
--- a/src/backend/access/common/reloptions.c
+++ b/src/backend/access/common/reloptions.c
@@ -40,9 +40,9 @@
*
* To add an option:
*
- * (i) decide on a type (bool, integer, real, enum, string), name, default
- * value, upper and lower bounds (if applicable); for strings, consider a
- * validation routine.
+ * (i) decide on a type (bool, ternary, integer, real, enum, string), name,
+ * default value, upper and lower bounds (if applicable); for strings,
+ * consider a validation routine.
* (ii) add a record below (or use add_<type>_reloption).
* (iii) add it to the appropriate options struct (perhaps StdRdOptions)
* (iv) add it to the appropriate handling routine (perhaps
@@ -147,15 +147,6 @@ static relopt_bool boolRelOpts[] =
},
false
},
- {
- {
- "vacuum_truncate",
- "Enables vacuum to truncate empty pages at the end of this table",
- RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
- ShareUpdateExclusiveLock
- },
- true
- },
{
{
"deduplicate_items",
@@ -170,6 +161,25 @@ static relopt_bool boolRelOpts[] =
{{NULL}}
};
+static relopt_ternary ternaryRelOpts[] =
+{
+ {
+ {
+ "vacuum_truncate",
+ "Enables vacuum to truncate empty pages at the end of this table",
+ RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
+ ShareUpdateExclusiveLock
+ },
+ TERNARY_UNSET
+ },
+ /* list terminator */
+ {
+ {
+ NULL
+ }
+ }
+};
+
static relopt_int intRelOpts[] =
{
{
@@ -609,6 +619,13 @@ initialize_reloptions(void)
boolRelOpts[i].gen.lockmode));
j++;
}
+ for (i = 0; ternaryRelOpts[i].gen.name; i++)
+ {
+ Assert(DoLockModesConflict(ternaryRelOpts[i].gen.lockmode,
+ ternaryRelOpts[i].gen.lockmode));
+ j++;
+ }
+
for (i = 0; intRelOpts[i].gen.name; i++)
{
Assert(DoLockModesConflict(intRelOpts[i].gen.lockmode,
@@ -649,6 +666,14 @@ initialize_reloptions(void)
j++;
}
+ for (i = 0; ternaryRelOpts[i].gen.name; i++)
+ {
+ relOpts[j] = &ternaryRelOpts[i].gen;
+ relOpts[j]->type = RELOPT_TYPE_TERNARY;
+ relOpts[j]->namelen = strlen(relOpts[j]->name);
+ j++;
+ }
+
for (i = 0; intRelOpts[i].gen.name; i++)
{
relOpts[j] = &intRelOpts[i].gen;
@@ -809,6 +834,9 @@ allocate_reloption(bits32 kinds, int type, const char *name, const char *desc,
case RELOPT_TYPE_BOOL:
size = sizeof(relopt_bool);
break;
+ case RELOPT_TYPE_TERNARY:
+ size = sizeof(relopt_ternary);
+ break;
case RELOPT_TYPE_INT:
size = sizeof(relopt_int);
break;
@@ -892,6 +920,57 @@ add_local_bool_reloption(local_relopts *relopts, const char *name,
add_local_reloption(relopts, (relopt_gen *) newoption, offset);
}
+/*
+ * init_ternary_reloption
+ * Allocate and initialize a new ternary reloption
+ */
+static relopt_ternary *
+init_ternary_reloption(bits32 kinds, const char *name, const char *desc,
+ pg_ternary default_val, LOCKMODE lockmode)
+{
+ relopt_ternary *newoption;
+
+ newoption = (relopt_ternary *)
+ allocate_reloption(kinds, RELOPT_TYPE_TERNARY, name, desc, lockmode);
+ newoption->default_val = default_val;
+
+ return newoption;
+}
+
+/*
+ * add_ternary_reloption
+ * Add a new ternary reloption
+ */
+void
+add_ternary_reloption(bits32 kinds, const char *name, const char *desc,
+ pg_ternary default_val, LOCKMODE lockmode)
+{
+ relopt_ternary *newoption;
+
+ newoption =
+ init_ternary_reloption(kinds, name, desc, default_val, lockmode);
+
+ add_reloption((relopt_gen *) newoption);
+}
+
+/*
+ * add_local_ternary_reloption
+ * Add a new ternary local reloption
+ *
+ * 'offset' is offset of ternary-typed field.
+ */
+void
+add_local_ternary_reloption(local_relopts *relopts, const char *name,
+ const char *desc, pg_ternary default_val,
+ int offset)
+{
+ relopt_ternary *newoption;
+
+ newoption =
+ init_ternary_reloption(RELOPT_KIND_LOCAL, name, desc, default_val, 0);
+
+ add_local_reloption(relopts, (relopt_gen *) newoption, offset);
+}
/*
* init_real_reloption
@@ -1626,6 +1705,19 @@ parse_one_reloption(relopt_value *option, char *text_str, int text_len,
option->gen->name, value)));
}
break;
+ case RELOPT_TYPE_TERNARY:
+ {
+ bool b;
+
+ parsed = parse_bool(value, &b);
+ option->values.ternary_val = b ? TERNARY_TRUE : TERNARY_FALSE;
+ if (validate && !parsed)
+ ereport(ERROR,
+ (errcode(ERRCODE_INVALID_PARAMETER_VALUE),
+ errmsg("invalid value for ternary option \"%s\": %s",
+ option->gen->name, value)));
+ }
+ break;
case RELOPT_TYPE_INT:
{
relopt_int *optint = (relopt_int *) option->gen;
@@ -1789,17 +1881,6 @@ fillRelOptions(void *rdopts, Size basesize,
char *itempos = ((char *) rdopts) + elems[j].offset;
char *string_val;
- /*
- * If isset_offset is provided, store whether the reloption is
- * set there.
- */
- if (elems[j].isset_offset > 0)
- {
- char *setpos = ((char *) rdopts) + elems[j].isset_offset;
-
- *(bool *) setpos = options[i].isset;
- }
-
switch (options[i].gen->type)
{
case RELOPT_TYPE_BOOL:
@@ -1807,6 +1888,11 @@ fillRelOptions(void *rdopts, Size basesize,
options[i].values.bool_val :
((relopt_bool *) options[i].gen)->default_val;
break;
+ case RELOPT_TYPE_TERNARY:
+ *(pg_ternary *) itempos = options[i].isset ?
+ options[i].values.ternary_val :
+ ((relopt_ternary *) options[i].gen)->default_val;
+ break;
case RELOPT_TYPE_INT:
*(int *) itempos = options[i].isset ?
options[i].values.int_val :
@@ -1923,8 +2009,8 @@ default_reloptions(Datum reloptions, bool validate, relopt_kind kind)
offsetof(StdRdOptions, parallel_workers)},
{"vacuum_index_cleanup", RELOPT_TYPE_ENUM,
offsetof(StdRdOptions, vacuum_index_cleanup)},
- {"vacuum_truncate", RELOPT_TYPE_BOOL,
- offsetof(StdRdOptions, vacuum_truncate), offsetof(StdRdOptions, vacuum_truncate_set)},
+ {"vacuum_truncate", RELOPT_TYPE_TERNARY,
+ offsetof(StdRdOptions, vacuum_truncate)},
{"vacuum_max_eager_freeze_failure_rate", RELOPT_TYPE_REAL,
offsetof(StdRdOptions, vacuum_max_eager_freeze_failure_rate)}
};
@@ -2004,7 +2090,6 @@ build_local_reloptions(local_relopts *relopts, Datum options, bool validate)
elems[i].optname = opt->option->name;
elems[i].opttype = opt->option->type;
elems[i].offset = opt->offset;
- elems[i].isset_offset = 0; /* not supported for local relopts yet */
i++;
}
diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c
index aa4fbec143f..696eab9bd97 100644
--- a/src/backend/commands/vacuum.c
+++ b/src/backend/commands/vacuum.c
@@ -2224,9 +2224,9 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams params,
{
StdRdOptions *opts = (StdRdOptions *) rel->rd_options;
- if (opts && opts->vacuum_truncate_set)
+ if (opts && opts->vacuum_truncate != TERNARY_UNSET)
{
- if (opts->vacuum_truncate)
+ if (opts->vacuum_truncate == TERNARY_TRUE)
params.truncate = VACOPTVALUE_ENABLED;
else
params.truncate = VACOPTVALUE_DISABLED;
diff --git a/src/include/access/reloptions.h b/src/include/access/reloptions.h
index 2f08e1b0cf0..dfbef2babf2 100644
--- a/src/include/access/reloptions.h
+++ b/src/include/access/reloptions.h
@@ -29,6 +29,7 @@
typedef enum relopt_type
{
RELOPT_TYPE_BOOL,
+ RELOPT_TYPE_TERNARY, /* on, off, unset */
RELOPT_TYPE_INT,
RELOPT_TYPE_REAL,
RELOPT_TYPE_ENUM,
@@ -80,6 +81,7 @@ typedef struct relopt_value
union
{
bool bool_val;
+ pg_ternary ternary_val;
int int_val;
double real_val;
int enum_val;
@@ -94,6 +96,12 @@ typedef struct relopt_bool
bool default_val;
} relopt_bool;
+typedef struct relopt_ternary
+{
+ relopt_gen gen;
+ int default_val;
+} relopt_ternary;
+
typedef struct relopt_int
{
relopt_gen gen;
@@ -152,19 +160,6 @@ typedef struct
const char *optname; /* option's name */
relopt_type opttype; /* option's datatype */
int offset; /* offset of field in result struct */
-
- /*
- * isset_offset is an optional offset of a field in the result struct that
- * stores whether the option is explicitly set for the relation or if it
- * just picked up the default value. In most cases, this can be
- * accomplished by giving the reloption a special out-of-range default
- * value (e.g., some integer reloptions use -2), but this isn't always
- * possible. For example, a Boolean reloption cannot be given an
- * out-of-range default, so we need another way to discover the source of
- * its value. This offset is only used if given a value greater than
- * zero.
- */
- int isset_offset;
} relopt_parse_elt;
/* Local reloption definition */
@@ -195,6 +190,9 @@ typedef struct local_relopts
extern relopt_kind add_reloption_kind(void);
extern void add_bool_reloption(bits32 kinds, const char *name, const char *desc,
bool default_val, LOCKMODE lockmode);
+extern void add_ternary_reloption(bits32 kinds, const char *name,
+ const char *desc, pg_ternary default_val,
+ LOCKMODE lockmode);
extern void add_int_reloption(bits32 kinds, const char *name, const char *desc,
int default_val, int min_val, int max_val,
LOCKMODE lockmode);
@@ -214,6 +212,9 @@ extern void register_reloptions_validator(local_relopts *relopts,
extern void add_local_bool_reloption(local_relopts *relopts, const char *name,
const char *desc, bool default_val,
int offset);
+extern void add_local_ternary_reloption(local_relopts *relopts,
+ const char *name, const char *desc,
+ pg_ternary default_val, int offset);
extern void add_local_int_reloption(local_relopts *relopts, const char *name,
const char *desc, int default_val,
int min_val, int max_val, int offset);
diff --git a/src/include/postgres.h b/src/include/postgres.h
index 7d93fbce709..47c88780776 100644
--- a/src/include/postgres.h
+++ b/src/include/postgres.h
@@ -543,6 +543,21 @@ Float8GetDatum(float8 X)
* ----------------------------------------------------------------
*/
+/*
+ * pg_ternary
+ * Boolean value with an extra "unset" value
+ *
+ * This enum can be used for values that want to distinguish between true,
+ * false, and unset.
+*/
+
+typedef enum pg_ternary
+{
+ TERNARY_FALSE = 0,
+ TERNARY_TRUE = 1,
+ TERNARY_UNSET = -1
+} pg_ternary;
+
/*
* NON_EXEC_STATIC: It's sometimes useful to define a variable or function
* that is normally static but extern when using EXEC_BACKEND (see
diff --git a/src/include/utils/rel.h b/src/include/utils/rel.h
index d03ab247788..5feb18a5373 100644
--- a/src/include/utils/rel.h
+++ b/src/include/utils/rel.h
@@ -347,8 +347,7 @@ typedef struct StdRdOptions
bool user_catalog_table; /* use as an additional catalog relation */
int parallel_workers; /* max number of parallel workers */
StdRdOptIndexCleanup vacuum_index_cleanup; /* controls index vacuuming */
- bool vacuum_truncate; /* enables vacuum to truncate a relation */
- bool vacuum_truncate_set; /* whether vacuum_truncate is set */
+ pg_ternary vacuum_truncate; /* enables vacuum to truncate a relation */
/*
* Fraction of pages in a relation that vacuum can eagerly scan and fail
diff --git a/src/test/regress/expected/reloptions.out b/src/test/regress/expected/reloptions.out
index 9de19b4e3f1..1c99f79ab01 100644
--- a/src/test/regress/expected/reloptions.out
+++ b/src/test/regress/expected/reloptions.out
@@ -98,6 +98,42 @@ SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass;
{fillfactor=13,autovacuum_enabled=false}
(1 row)
+-- Tests for future (FIXME) ternary options
+-- behave as boolean option: accept unassigned name and truncated value
+DROP TABLE reloptions_test;
+CREATE TABLE reloptions_test(i INT) WITH (vacuum_truncate);
+SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass;
+ reloptions
+------------------------
+ {vacuum_truncate=true}
+(1 row)
+
+DROP TABLE reloptions_test;
+CREATE TABLE reloptions_test(i INT) WITH (vacuum_truncate=FaLS);
+SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass;
+ reloptions
+------------------------
+ {vacuum_truncate=fals}
+(1 row)
+
+-- preferred "true" alias is used when storing
+DROP TABLE reloptions_test;
+CREATE TABLE reloptions_test(i INT) WITH (vacuum_index_cleanup=on);
+SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass;
+ reloptions
+---------------------------
+ {vacuum_index_cleanup=on}
+(1 row)
+
+-- custom "third" value is available
+DROP TABLE reloptions_test;
+CREATE TABLE reloptions_test(i INT) WITH (vacuum_index_cleanup=auto);
+SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass;
+ reloptions
+-----------------------------
+ {vacuum_index_cleanup=auto}
+(1 row)
+
-- Test vacuum_truncate option
DROP TABLE reloptions_test;
CREATE TEMP TABLE reloptions_test(i INT NOT NULL, j text)
diff --git a/src/test/regress/sql/reloptions.sql b/src/test/regress/sql/reloptions.sql
index 24fbe0b478d..f5980dafcbc 100644
--- a/src/test/regress/sql/reloptions.sql
+++ b/src/test/regress/sql/reloptions.sql
@@ -59,6 +59,27 @@ UPDATE pg_class
ALTER TABLE reloptions_test RESET (illegal_option);
SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass;
+-- Tests for future (FIXME) ternary options
+
+-- behave as boolean option: accept unassigned name and truncated value
+DROP TABLE reloptions_test;
+CREATE TABLE reloptions_test(i INT) WITH (vacuum_truncate);
+SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass;
+
+DROP TABLE reloptions_test;
+CREATE TABLE reloptions_test(i INT) WITH (vacuum_truncate=FaLS);
+SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass;
+
+-- preferred "true" alias is used when storing
+DROP TABLE reloptions_test;
+CREATE TABLE reloptions_test(i INT) WITH (vacuum_index_cleanup=on);
+SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass;
+
+-- custom "third" value is available
+DROP TABLE reloptions_test;
+CREATE TABLE reloptions_test(i INT) WITH (vacuum_index_cleanup=auto);
+SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass;
+
-- Test vacuum_truncate option
DROP TABLE reloptions_test;
diff --git a/src/tools/pgindent/typedefs.list b/src/tools/pgindent/typedefs.list
index 3f3a888fd0e..1c8610fd46c 100644
--- a/src/tools/pgindent/typedefs.list
+++ b/src/tools/pgindent/typedefs.list
@@ -3952,6 +3952,7 @@ pg_sha512_ctx
pg_snapshot
pg_special_case
pg_stack_base_t
+pg_ternary
pg_time_t
pg_time_usec_t
pg_tz
@@ -4079,6 +4080,7 @@ relopt_kind
relopt_parse_elt
relopt_real
relopt_string
+relopt_ternary
relopt_type
relopt_value
relopts_validator
--
2.47.3
--c36t3fndxhkzbl5z--
^ permalink raw reply [nested|flat] 868+ messages in thread
* [PATCH v3] Introduce ternary reloptions
@ 2026-01-16 15:04 Álvaro Herrera <[email protected]>
0 siblings, 0 replies; 868+ messages in thread
From: Álvaro Herrera @ 2026-01-16 15:04 UTC (permalink / raw)
Introduce ternary reloption as a replacement for current
`vacuum_truncate` implementation. Remove the `vacuum_truncate_set`
separate flag and use TERNARY_UNSET instead.
This could also be used for other options such as `vacuum_index_cleanup`
and `buffering`, but lets get the scaffolding in first.
Discussion: https://postgr.es/m/3474141.usfYGdeWWP@thinkpad-pgpro
---
src/backend/access/common/reloptions.c | 137 ++++++++++++++++++-----
src/backend/commands/vacuum.c | 4 +-
src/include/access/reloptions.h | 27 ++---
src/include/postgres.h | 15 +++
src/include/utils/rel.h | 3 +-
src/test/regress/expected/reloptions.out | 36 ++++++
src/test/regress/sql/reloptions.sql | 21 ++++
src/tools/pgindent/typedefs.list | 2 +
8 files changed, 202 insertions(+), 43 deletions(-)
diff --git a/src/backend/access/common/reloptions.c b/src/backend/access/common/reloptions.c
index 0b83f98ed5f..6f1f577581a 100644
--- a/src/backend/access/common/reloptions.c
+++ b/src/backend/access/common/reloptions.c
@@ -40,9 +40,9 @@
*
* To add an option:
*
- * (i) decide on a type (bool, integer, real, enum, string), name, default
- * value, upper and lower bounds (if applicable); for strings, consider a
- * validation routine.
+ * (i) decide on a type (bool, ternary, integer, real, enum, string), name,
+ * default value, upper and lower bounds (if applicable); for strings,
+ * consider a validation routine.
* (ii) add a record below (or use add_<type>_reloption).
* (iii) add it to the appropriate options struct (perhaps StdRdOptions)
* (iv) add it to the appropriate handling routine (perhaps
@@ -147,15 +147,6 @@ static relopt_bool boolRelOpts[] =
},
false
},
- {
- {
- "vacuum_truncate",
- "Enables vacuum to truncate empty pages at the end of this table",
- RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
- ShareUpdateExclusiveLock
- },
- true
- },
{
{
"deduplicate_items",
@@ -170,6 +161,25 @@ static relopt_bool boolRelOpts[] =
{{NULL}}
};
+static relopt_ternary ternaryRelOpts[] =
+{
+ {
+ {
+ "vacuum_truncate",
+ "Enables vacuum to truncate empty pages at the end of this table",
+ RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
+ ShareUpdateExclusiveLock
+ },
+ TERNARY_UNSET
+ },
+ /* list terminator */
+ {
+ {
+ NULL
+ }
+ }
+};
+
static relopt_int intRelOpts[] =
{
{
@@ -609,6 +619,13 @@ initialize_reloptions(void)
boolRelOpts[i].gen.lockmode));
j++;
}
+ for (i = 0; ternaryRelOpts[i].gen.name; i++)
+ {
+ Assert(DoLockModesConflict(ternaryRelOpts[i].gen.lockmode,
+ ternaryRelOpts[i].gen.lockmode));
+ j++;
+ }
+
for (i = 0; intRelOpts[i].gen.name; i++)
{
Assert(DoLockModesConflict(intRelOpts[i].gen.lockmode,
@@ -649,6 +666,14 @@ initialize_reloptions(void)
j++;
}
+ for (i = 0; ternaryRelOpts[i].gen.name; i++)
+ {
+ relOpts[j] = &ternaryRelOpts[i].gen;
+ relOpts[j]->type = RELOPT_TYPE_TERNARY;
+ relOpts[j]->namelen = strlen(relOpts[j]->name);
+ j++;
+ }
+
for (i = 0; intRelOpts[i].gen.name; i++)
{
relOpts[j] = &intRelOpts[i].gen;
@@ -809,6 +834,9 @@ allocate_reloption(bits32 kinds, int type, const char *name, const char *desc,
case RELOPT_TYPE_BOOL:
size = sizeof(relopt_bool);
break;
+ case RELOPT_TYPE_TERNARY:
+ size = sizeof(relopt_ternary);
+ break;
case RELOPT_TYPE_INT:
size = sizeof(relopt_int);
break;
@@ -892,6 +920,57 @@ add_local_bool_reloption(local_relopts *relopts, const char *name,
add_local_reloption(relopts, (relopt_gen *) newoption, offset);
}
+/*
+ * init_ternary_reloption
+ * Allocate and initialize a new ternary reloption
+ */
+static relopt_ternary *
+init_ternary_reloption(bits32 kinds, const char *name, const char *desc,
+ pg_ternary default_val, LOCKMODE lockmode)
+{
+ relopt_ternary *newoption;
+
+ newoption = (relopt_ternary *)
+ allocate_reloption(kinds, RELOPT_TYPE_TERNARY, name, desc, lockmode);
+ newoption->default_val = default_val;
+
+ return newoption;
+}
+
+/*
+ * add_ternary_reloption
+ * Add a new ternary reloption
+ */
+void
+add_ternary_reloption(bits32 kinds, const char *name, const char *desc,
+ pg_ternary default_val, LOCKMODE lockmode)
+{
+ relopt_ternary *newoption;
+
+ newoption =
+ init_ternary_reloption(kinds, name, desc, default_val, lockmode);
+
+ add_reloption((relopt_gen *) newoption);
+}
+
+/*
+ * add_local_ternary_reloption
+ * Add a new ternary local reloption
+ *
+ * 'offset' is offset of ternary-typed field.
+ */
+void
+add_local_ternary_reloption(local_relopts *relopts, const char *name,
+ const char *desc, pg_ternary default_val,
+ int offset)
+{
+ relopt_ternary *newoption;
+
+ newoption =
+ init_ternary_reloption(RELOPT_KIND_LOCAL, name, desc, default_val, 0);
+
+ add_local_reloption(relopts, (relopt_gen *) newoption, offset);
+}
/*
* init_real_reloption
@@ -1626,6 +1705,19 @@ parse_one_reloption(relopt_value *option, char *text_str, int text_len,
option->gen->name, value)));
}
break;
+ case RELOPT_TYPE_TERNARY:
+ {
+ bool b;
+
+ parsed = parse_bool(value, &b);
+ option->values.ternary_val = b ? TERNARY_TRUE : TERNARY_FALSE;
+ if (validate && !parsed)
+ ereport(ERROR,
+ (errcode(ERRCODE_INVALID_PARAMETER_VALUE),
+ errmsg("invalid value for ternary option \"%s\": %s",
+ option->gen->name, value)));
+ }
+ break;
case RELOPT_TYPE_INT:
{
relopt_int *optint = (relopt_int *) option->gen;
@@ -1789,17 +1881,6 @@ fillRelOptions(void *rdopts, Size basesize,
char *itempos = ((char *) rdopts) + elems[j].offset;
char *string_val;
- /*
- * If isset_offset is provided, store whether the reloption is
- * set there.
- */
- if (elems[j].isset_offset > 0)
- {
- char *setpos = ((char *) rdopts) + elems[j].isset_offset;
-
- *(bool *) setpos = options[i].isset;
- }
-
switch (options[i].gen->type)
{
case RELOPT_TYPE_BOOL:
@@ -1807,6 +1888,11 @@ fillRelOptions(void *rdopts, Size basesize,
options[i].values.bool_val :
((relopt_bool *) options[i].gen)->default_val;
break;
+ case RELOPT_TYPE_TERNARY:
+ *(pg_ternary *) itempos = options[i].isset ?
+ options[i].values.ternary_val :
+ ((relopt_ternary *) options[i].gen)->default_val;
+ break;
case RELOPT_TYPE_INT:
*(int *) itempos = options[i].isset ?
options[i].values.int_val :
@@ -1923,8 +2009,8 @@ default_reloptions(Datum reloptions, bool validate, relopt_kind kind)
offsetof(StdRdOptions, parallel_workers)},
{"vacuum_index_cleanup", RELOPT_TYPE_ENUM,
offsetof(StdRdOptions, vacuum_index_cleanup)},
- {"vacuum_truncate", RELOPT_TYPE_BOOL,
- offsetof(StdRdOptions, vacuum_truncate), offsetof(StdRdOptions, vacuum_truncate_set)},
+ {"vacuum_truncate", RELOPT_TYPE_TERNARY,
+ offsetof(StdRdOptions, vacuum_truncate)},
{"vacuum_max_eager_freeze_failure_rate", RELOPT_TYPE_REAL,
offsetof(StdRdOptions, vacuum_max_eager_freeze_failure_rate)}
};
@@ -2004,7 +2090,6 @@ build_local_reloptions(local_relopts *relopts, Datum options, bool validate)
elems[i].optname = opt->option->name;
elems[i].opttype = opt->option->type;
elems[i].offset = opt->offset;
- elems[i].isset_offset = 0; /* not supported for local relopts yet */
i++;
}
diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c
index aa4fbec143f..696eab9bd97 100644
--- a/src/backend/commands/vacuum.c
+++ b/src/backend/commands/vacuum.c
@@ -2224,9 +2224,9 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams params,
{
StdRdOptions *opts = (StdRdOptions *) rel->rd_options;
- if (opts && opts->vacuum_truncate_set)
+ if (opts && opts->vacuum_truncate != TERNARY_UNSET)
{
- if (opts->vacuum_truncate)
+ if (opts->vacuum_truncate == TERNARY_TRUE)
params.truncate = VACOPTVALUE_ENABLED;
else
params.truncate = VACOPTVALUE_DISABLED;
diff --git a/src/include/access/reloptions.h b/src/include/access/reloptions.h
index 2f08e1b0cf0..dfbef2babf2 100644
--- a/src/include/access/reloptions.h
+++ b/src/include/access/reloptions.h
@@ -29,6 +29,7 @@
typedef enum relopt_type
{
RELOPT_TYPE_BOOL,
+ RELOPT_TYPE_TERNARY, /* on, off, unset */
RELOPT_TYPE_INT,
RELOPT_TYPE_REAL,
RELOPT_TYPE_ENUM,
@@ -80,6 +81,7 @@ typedef struct relopt_value
union
{
bool bool_val;
+ pg_ternary ternary_val;
int int_val;
double real_val;
int enum_val;
@@ -94,6 +96,12 @@ typedef struct relopt_bool
bool default_val;
} relopt_bool;
+typedef struct relopt_ternary
+{
+ relopt_gen gen;
+ int default_val;
+} relopt_ternary;
+
typedef struct relopt_int
{
relopt_gen gen;
@@ -152,19 +160,6 @@ typedef struct
const char *optname; /* option's name */
relopt_type opttype; /* option's datatype */
int offset; /* offset of field in result struct */
-
- /*
- * isset_offset is an optional offset of a field in the result struct that
- * stores whether the option is explicitly set for the relation or if it
- * just picked up the default value. In most cases, this can be
- * accomplished by giving the reloption a special out-of-range default
- * value (e.g., some integer reloptions use -2), but this isn't always
- * possible. For example, a Boolean reloption cannot be given an
- * out-of-range default, so we need another way to discover the source of
- * its value. This offset is only used if given a value greater than
- * zero.
- */
- int isset_offset;
} relopt_parse_elt;
/* Local reloption definition */
@@ -195,6 +190,9 @@ typedef struct local_relopts
extern relopt_kind add_reloption_kind(void);
extern void add_bool_reloption(bits32 kinds, const char *name, const char *desc,
bool default_val, LOCKMODE lockmode);
+extern void add_ternary_reloption(bits32 kinds, const char *name,
+ const char *desc, pg_ternary default_val,
+ LOCKMODE lockmode);
extern void add_int_reloption(bits32 kinds, const char *name, const char *desc,
int default_val, int min_val, int max_val,
LOCKMODE lockmode);
@@ -214,6 +212,9 @@ extern void register_reloptions_validator(local_relopts *relopts,
extern void add_local_bool_reloption(local_relopts *relopts, const char *name,
const char *desc, bool default_val,
int offset);
+extern void add_local_ternary_reloption(local_relopts *relopts,
+ const char *name, const char *desc,
+ pg_ternary default_val, int offset);
extern void add_local_int_reloption(local_relopts *relopts, const char *name,
const char *desc, int default_val,
int min_val, int max_val, int offset);
diff --git a/src/include/postgres.h b/src/include/postgres.h
index 7d93fbce709..47c88780776 100644
--- a/src/include/postgres.h
+++ b/src/include/postgres.h
@@ -543,6 +543,21 @@ Float8GetDatum(float8 X)
* ----------------------------------------------------------------
*/
+/*
+ * pg_ternary
+ * Boolean value with an extra "unset" value
+ *
+ * This enum can be used for values that want to distinguish between true,
+ * false, and unset.
+*/
+
+typedef enum pg_ternary
+{
+ TERNARY_FALSE = 0,
+ TERNARY_TRUE = 1,
+ TERNARY_UNSET = -1
+} pg_ternary;
+
/*
* NON_EXEC_STATIC: It's sometimes useful to define a variable or function
* that is normally static but extern when using EXEC_BACKEND (see
diff --git a/src/include/utils/rel.h b/src/include/utils/rel.h
index d03ab247788..5feb18a5373 100644
--- a/src/include/utils/rel.h
+++ b/src/include/utils/rel.h
@@ -347,8 +347,7 @@ typedef struct StdRdOptions
bool user_catalog_table; /* use as an additional catalog relation */
int parallel_workers; /* max number of parallel workers */
StdRdOptIndexCleanup vacuum_index_cleanup; /* controls index vacuuming */
- bool vacuum_truncate; /* enables vacuum to truncate a relation */
- bool vacuum_truncate_set; /* whether vacuum_truncate is set */
+ pg_ternary vacuum_truncate; /* enables vacuum to truncate a relation */
/*
* Fraction of pages in a relation that vacuum can eagerly scan and fail
diff --git a/src/test/regress/expected/reloptions.out b/src/test/regress/expected/reloptions.out
index 9de19b4e3f1..1c99f79ab01 100644
--- a/src/test/regress/expected/reloptions.out
+++ b/src/test/regress/expected/reloptions.out
@@ -98,6 +98,42 @@ SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass;
{fillfactor=13,autovacuum_enabled=false}
(1 row)
+-- Tests for future (FIXME) ternary options
+-- behave as boolean option: accept unassigned name and truncated value
+DROP TABLE reloptions_test;
+CREATE TABLE reloptions_test(i INT) WITH (vacuum_truncate);
+SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass;
+ reloptions
+------------------------
+ {vacuum_truncate=true}
+(1 row)
+
+DROP TABLE reloptions_test;
+CREATE TABLE reloptions_test(i INT) WITH (vacuum_truncate=FaLS);
+SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass;
+ reloptions
+------------------------
+ {vacuum_truncate=fals}
+(1 row)
+
+-- preferred "true" alias is used when storing
+DROP TABLE reloptions_test;
+CREATE TABLE reloptions_test(i INT) WITH (vacuum_index_cleanup=on);
+SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass;
+ reloptions
+---------------------------
+ {vacuum_index_cleanup=on}
+(1 row)
+
+-- custom "third" value is available
+DROP TABLE reloptions_test;
+CREATE TABLE reloptions_test(i INT) WITH (vacuum_index_cleanup=auto);
+SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass;
+ reloptions
+-----------------------------
+ {vacuum_index_cleanup=auto}
+(1 row)
+
-- Test vacuum_truncate option
DROP TABLE reloptions_test;
CREATE TEMP TABLE reloptions_test(i INT NOT NULL, j text)
diff --git a/src/test/regress/sql/reloptions.sql b/src/test/regress/sql/reloptions.sql
index 24fbe0b478d..f5980dafcbc 100644
--- a/src/test/regress/sql/reloptions.sql
+++ b/src/test/regress/sql/reloptions.sql
@@ -59,6 +59,27 @@ UPDATE pg_class
ALTER TABLE reloptions_test RESET (illegal_option);
SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass;
+-- Tests for future (FIXME) ternary options
+
+-- behave as boolean option: accept unassigned name and truncated value
+DROP TABLE reloptions_test;
+CREATE TABLE reloptions_test(i INT) WITH (vacuum_truncate);
+SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass;
+
+DROP TABLE reloptions_test;
+CREATE TABLE reloptions_test(i INT) WITH (vacuum_truncate=FaLS);
+SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass;
+
+-- preferred "true" alias is used when storing
+DROP TABLE reloptions_test;
+CREATE TABLE reloptions_test(i INT) WITH (vacuum_index_cleanup=on);
+SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass;
+
+-- custom "third" value is available
+DROP TABLE reloptions_test;
+CREATE TABLE reloptions_test(i INT) WITH (vacuum_index_cleanup=auto);
+SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass;
+
-- Test vacuum_truncate option
DROP TABLE reloptions_test;
diff --git a/src/tools/pgindent/typedefs.list b/src/tools/pgindent/typedefs.list
index 3f3a888fd0e..1c8610fd46c 100644
--- a/src/tools/pgindent/typedefs.list
+++ b/src/tools/pgindent/typedefs.list
@@ -3952,6 +3952,7 @@ pg_sha512_ctx
pg_snapshot
pg_special_case
pg_stack_base_t
+pg_ternary
pg_time_t
pg_time_usec_t
pg_tz
@@ -4079,6 +4080,7 @@ relopt_kind
relopt_parse_elt
relopt_real
relopt_string
+relopt_ternary
relopt_type
relopt_value
relopts_validator
--
2.47.3
--c36t3fndxhkzbl5z--
^ permalink raw reply [nested|flat] 868+ messages in thread
* [PATCH v3] Introduce ternary reloptions
@ 2026-01-16 15:04 Álvaro Herrera <[email protected]>
0 siblings, 0 replies; 868+ messages in thread
From: Álvaro Herrera @ 2026-01-16 15:04 UTC (permalink / raw)
Introduce ternary reloption as a replacement for current
`vacuum_truncate` implementation. Remove the `vacuum_truncate_set`
separate flag and use TERNARY_UNSET instead.
This could also be used for other options such as `vacuum_index_cleanup`
and `buffering`, but lets get the scaffolding in first.
Discussion: https://postgr.es/m/3474141.usfYGdeWWP@thinkpad-pgpro
---
src/backend/access/common/reloptions.c | 137 ++++++++++++++++++-----
src/backend/commands/vacuum.c | 4 +-
src/include/access/reloptions.h | 27 ++---
src/include/postgres.h | 15 +++
src/include/utils/rel.h | 3 +-
src/test/regress/expected/reloptions.out | 36 ++++++
src/test/regress/sql/reloptions.sql | 21 ++++
src/tools/pgindent/typedefs.list | 2 +
8 files changed, 202 insertions(+), 43 deletions(-)
diff --git a/src/backend/access/common/reloptions.c b/src/backend/access/common/reloptions.c
index 0b83f98ed5f..6f1f577581a 100644
--- a/src/backend/access/common/reloptions.c
+++ b/src/backend/access/common/reloptions.c
@@ -40,9 +40,9 @@
*
* To add an option:
*
- * (i) decide on a type (bool, integer, real, enum, string), name, default
- * value, upper and lower bounds (if applicable); for strings, consider a
- * validation routine.
+ * (i) decide on a type (bool, ternary, integer, real, enum, string), name,
+ * default value, upper and lower bounds (if applicable); for strings,
+ * consider a validation routine.
* (ii) add a record below (or use add_<type>_reloption).
* (iii) add it to the appropriate options struct (perhaps StdRdOptions)
* (iv) add it to the appropriate handling routine (perhaps
@@ -147,15 +147,6 @@ static relopt_bool boolRelOpts[] =
},
false
},
- {
- {
- "vacuum_truncate",
- "Enables vacuum to truncate empty pages at the end of this table",
- RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
- ShareUpdateExclusiveLock
- },
- true
- },
{
{
"deduplicate_items",
@@ -170,6 +161,25 @@ static relopt_bool boolRelOpts[] =
{{NULL}}
};
+static relopt_ternary ternaryRelOpts[] =
+{
+ {
+ {
+ "vacuum_truncate",
+ "Enables vacuum to truncate empty pages at the end of this table",
+ RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
+ ShareUpdateExclusiveLock
+ },
+ TERNARY_UNSET
+ },
+ /* list terminator */
+ {
+ {
+ NULL
+ }
+ }
+};
+
static relopt_int intRelOpts[] =
{
{
@@ -609,6 +619,13 @@ initialize_reloptions(void)
boolRelOpts[i].gen.lockmode));
j++;
}
+ for (i = 0; ternaryRelOpts[i].gen.name; i++)
+ {
+ Assert(DoLockModesConflict(ternaryRelOpts[i].gen.lockmode,
+ ternaryRelOpts[i].gen.lockmode));
+ j++;
+ }
+
for (i = 0; intRelOpts[i].gen.name; i++)
{
Assert(DoLockModesConflict(intRelOpts[i].gen.lockmode,
@@ -649,6 +666,14 @@ initialize_reloptions(void)
j++;
}
+ for (i = 0; ternaryRelOpts[i].gen.name; i++)
+ {
+ relOpts[j] = &ternaryRelOpts[i].gen;
+ relOpts[j]->type = RELOPT_TYPE_TERNARY;
+ relOpts[j]->namelen = strlen(relOpts[j]->name);
+ j++;
+ }
+
for (i = 0; intRelOpts[i].gen.name; i++)
{
relOpts[j] = &intRelOpts[i].gen;
@@ -809,6 +834,9 @@ allocate_reloption(bits32 kinds, int type, const char *name, const char *desc,
case RELOPT_TYPE_BOOL:
size = sizeof(relopt_bool);
break;
+ case RELOPT_TYPE_TERNARY:
+ size = sizeof(relopt_ternary);
+ break;
case RELOPT_TYPE_INT:
size = sizeof(relopt_int);
break;
@@ -892,6 +920,57 @@ add_local_bool_reloption(local_relopts *relopts, const char *name,
add_local_reloption(relopts, (relopt_gen *) newoption, offset);
}
+/*
+ * init_ternary_reloption
+ * Allocate and initialize a new ternary reloption
+ */
+static relopt_ternary *
+init_ternary_reloption(bits32 kinds, const char *name, const char *desc,
+ pg_ternary default_val, LOCKMODE lockmode)
+{
+ relopt_ternary *newoption;
+
+ newoption = (relopt_ternary *)
+ allocate_reloption(kinds, RELOPT_TYPE_TERNARY, name, desc, lockmode);
+ newoption->default_val = default_val;
+
+ return newoption;
+}
+
+/*
+ * add_ternary_reloption
+ * Add a new ternary reloption
+ */
+void
+add_ternary_reloption(bits32 kinds, const char *name, const char *desc,
+ pg_ternary default_val, LOCKMODE lockmode)
+{
+ relopt_ternary *newoption;
+
+ newoption =
+ init_ternary_reloption(kinds, name, desc, default_val, lockmode);
+
+ add_reloption((relopt_gen *) newoption);
+}
+
+/*
+ * add_local_ternary_reloption
+ * Add a new ternary local reloption
+ *
+ * 'offset' is offset of ternary-typed field.
+ */
+void
+add_local_ternary_reloption(local_relopts *relopts, const char *name,
+ const char *desc, pg_ternary default_val,
+ int offset)
+{
+ relopt_ternary *newoption;
+
+ newoption =
+ init_ternary_reloption(RELOPT_KIND_LOCAL, name, desc, default_val, 0);
+
+ add_local_reloption(relopts, (relopt_gen *) newoption, offset);
+}
/*
* init_real_reloption
@@ -1626,6 +1705,19 @@ parse_one_reloption(relopt_value *option, char *text_str, int text_len,
option->gen->name, value)));
}
break;
+ case RELOPT_TYPE_TERNARY:
+ {
+ bool b;
+
+ parsed = parse_bool(value, &b);
+ option->values.ternary_val = b ? TERNARY_TRUE : TERNARY_FALSE;
+ if (validate && !parsed)
+ ereport(ERROR,
+ (errcode(ERRCODE_INVALID_PARAMETER_VALUE),
+ errmsg("invalid value for ternary option \"%s\": %s",
+ option->gen->name, value)));
+ }
+ break;
case RELOPT_TYPE_INT:
{
relopt_int *optint = (relopt_int *) option->gen;
@@ -1789,17 +1881,6 @@ fillRelOptions(void *rdopts, Size basesize,
char *itempos = ((char *) rdopts) + elems[j].offset;
char *string_val;
- /*
- * If isset_offset is provided, store whether the reloption is
- * set there.
- */
- if (elems[j].isset_offset > 0)
- {
- char *setpos = ((char *) rdopts) + elems[j].isset_offset;
-
- *(bool *) setpos = options[i].isset;
- }
-
switch (options[i].gen->type)
{
case RELOPT_TYPE_BOOL:
@@ -1807,6 +1888,11 @@ fillRelOptions(void *rdopts, Size basesize,
options[i].values.bool_val :
((relopt_bool *) options[i].gen)->default_val;
break;
+ case RELOPT_TYPE_TERNARY:
+ *(pg_ternary *) itempos = options[i].isset ?
+ options[i].values.ternary_val :
+ ((relopt_ternary *) options[i].gen)->default_val;
+ break;
case RELOPT_TYPE_INT:
*(int *) itempos = options[i].isset ?
options[i].values.int_val :
@@ -1923,8 +2009,8 @@ default_reloptions(Datum reloptions, bool validate, relopt_kind kind)
offsetof(StdRdOptions, parallel_workers)},
{"vacuum_index_cleanup", RELOPT_TYPE_ENUM,
offsetof(StdRdOptions, vacuum_index_cleanup)},
- {"vacuum_truncate", RELOPT_TYPE_BOOL,
- offsetof(StdRdOptions, vacuum_truncate), offsetof(StdRdOptions, vacuum_truncate_set)},
+ {"vacuum_truncate", RELOPT_TYPE_TERNARY,
+ offsetof(StdRdOptions, vacuum_truncate)},
{"vacuum_max_eager_freeze_failure_rate", RELOPT_TYPE_REAL,
offsetof(StdRdOptions, vacuum_max_eager_freeze_failure_rate)}
};
@@ -2004,7 +2090,6 @@ build_local_reloptions(local_relopts *relopts, Datum options, bool validate)
elems[i].optname = opt->option->name;
elems[i].opttype = opt->option->type;
elems[i].offset = opt->offset;
- elems[i].isset_offset = 0; /* not supported for local relopts yet */
i++;
}
diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c
index aa4fbec143f..696eab9bd97 100644
--- a/src/backend/commands/vacuum.c
+++ b/src/backend/commands/vacuum.c
@@ -2224,9 +2224,9 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams params,
{
StdRdOptions *opts = (StdRdOptions *) rel->rd_options;
- if (opts && opts->vacuum_truncate_set)
+ if (opts && opts->vacuum_truncate != TERNARY_UNSET)
{
- if (opts->vacuum_truncate)
+ if (opts->vacuum_truncate == TERNARY_TRUE)
params.truncate = VACOPTVALUE_ENABLED;
else
params.truncate = VACOPTVALUE_DISABLED;
diff --git a/src/include/access/reloptions.h b/src/include/access/reloptions.h
index 2f08e1b0cf0..dfbef2babf2 100644
--- a/src/include/access/reloptions.h
+++ b/src/include/access/reloptions.h
@@ -29,6 +29,7 @@
typedef enum relopt_type
{
RELOPT_TYPE_BOOL,
+ RELOPT_TYPE_TERNARY, /* on, off, unset */
RELOPT_TYPE_INT,
RELOPT_TYPE_REAL,
RELOPT_TYPE_ENUM,
@@ -80,6 +81,7 @@ typedef struct relopt_value
union
{
bool bool_val;
+ pg_ternary ternary_val;
int int_val;
double real_val;
int enum_val;
@@ -94,6 +96,12 @@ typedef struct relopt_bool
bool default_val;
} relopt_bool;
+typedef struct relopt_ternary
+{
+ relopt_gen gen;
+ int default_val;
+} relopt_ternary;
+
typedef struct relopt_int
{
relopt_gen gen;
@@ -152,19 +160,6 @@ typedef struct
const char *optname; /* option's name */
relopt_type opttype; /* option's datatype */
int offset; /* offset of field in result struct */
-
- /*
- * isset_offset is an optional offset of a field in the result struct that
- * stores whether the option is explicitly set for the relation or if it
- * just picked up the default value. In most cases, this can be
- * accomplished by giving the reloption a special out-of-range default
- * value (e.g., some integer reloptions use -2), but this isn't always
- * possible. For example, a Boolean reloption cannot be given an
- * out-of-range default, so we need another way to discover the source of
- * its value. This offset is only used if given a value greater than
- * zero.
- */
- int isset_offset;
} relopt_parse_elt;
/* Local reloption definition */
@@ -195,6 +190,9 @@ typedef struct local_relopts
extern relopt_kind add_reloption_kind(void);
extern void add_bool_reloption(bits32 kinds, const char *name, const char *desc,
bool default_val, LOCKMODE lockmode);
+extern void add_ternary_reloption(bits32 kinds, const char *name,
+ const char *desc, pg_ternary default_val,
+ LOCKMODE lockmode);
extern void add_int_reloption(bits32 kinds, const char *name, const char *desc,
int default_val, int min_val, int max_val,
LOCKMODE lockmode);
@@ -214,6 +212,9 @@ extern void register_reloptions_validator(local_relopts *relopts,
extern void add_local_bool_reloption(local_relopts *relopts, const char *name,
const char *desc, bool default_val,
int offset);
+extern void add_local_ternary_reloption(local_relopts *relopts,
+ const char *name, const char *desc,
+ pg_ternary default_val, int offset);
extern void add_local_int_reloption(local_relopts *relopts, const char *name,
const char *desc, int default_val,
int min_val, int max_val, int offset);
diff --git a/src/include/postgres.h b/src/include/postgres.h
index 7d93fbce709..47c88780776 100644
--- a/src/include/postgres.h
+++ b/src/include/postgres.h
@@ -543,6 +543,21 @@ Float8GetDatum(float8 X)
* ----------------------------------------------------------------
*/
+/*
+ * pg_ternary
+ * Boolean value with an extra "unset" value
+ *
+ * This enum can be used for values that want to distinguish between true,
+ * false, and unset.
+*/
+
+typedef enum pg_ternary
+{
+ TERNARY_FALSE = 0,
+ TERNARY_TRUE = 1,
+ TERNARY_UNSET = -1
+} pg_ternary;
+
/*
* NON_EXEC_STATIC: It's sometimes useful to define a variable or function
* that is normally static but extern when using EXEC_BACKEND (see
diff --git a/src/include/utils/rel.h b/src/include/utils/rel.h
index d03ab247788..5feb18a5373 100644
--- a/src/include/utils/rel.h
+++ b/src/include/utils/rel.h
@@ -347,8 +347,7 @@ typedef struct StdRdOptions
bool user_catalog_table; /* use as an additional catalog relation */
int parallel_workers; /* max number of parallel workers */
StdRdOptIndexCleanup vacuum_index_cleanup; /* controls index vacuuming */
- bool vacuum_truncate; /* enables vacuum to truncate a relation */
- bool vacuum_truncate_set; /* whether vacuum_truncate is set */
+ pg_ternary vacuum_truncate; /* enables vacuum to truncate a relation */
/*
* Fraction of pages in a relation that vacuum can eagerly scan and fail
diff --git a/src/test/regress/expected/reloptions.out b/src/test/regress/expected/reloptions.out
index 9de19b4e3f1..1c99f79ab01 100644
--- a/src/test/regress/expected/reloptions.out
+++ b/src/test/regress/expected/reloptions.out
@@ -98,6 +98,42 @@ SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass;
{fillfactor=13,autovacuum_enabled=false}
(1 row)
+-- Tests for future (FIXME) ternary options
+-- behave as boolean option: accept unassigned name and truncated value
+DROP TABLE reloptions_test;
+CREATE TABLE reloptions_test(i INT) WITH (vacuum_truncate);
+SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass;
+ reloptions
+------------------------
+ {vacuum_truncate=true}
+(1 row)
+
+DROP TABLE reloptions_test;
+CREATE TABLE reloptions_test(i INT) WITH (vacuum_truncate=FaLS);
+SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass;
+ reloptions
+------------------------
+ {vacuum_truncate=fals}
+(1 row)
+
+-- preferred "true" alias is used when storing
+DROP TABLE reloptions_test;
+CREATE TABLE reloptions_test(i INT) WITH (vacuum_index_cleanup=on);
+SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass;
+ reloptions
+---------------------------
+ {vacuum_index_cleanup=on}
+(1 row)
+
+-- custom "third" value is available
+DROP TABLE reloptions_test;
+CREATE TABLE reloptions_test(i INT) WITH (vacuum_index_cleanup=auto);
+SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass;
+ reloptions
+-----------------------------
+ {vacuum_index_cleanup=auto}
+(1 row)
+
-- Test vacuum_truncate option
DROP TABLE reloptions_test;
CREATE TEMP TABLE reloptions_test(i INT NOT NULL, j text)
diff --git a/src/test/regress/sql/reloptions.sql b/src/test/regress/sql/reloptions.sql
index 24fbe0b478d..f5980dafcbc 100644
--- a/src/test/regress/sql/reloptions.sql
+++ b/src/test/regress/sql/reloptions.sql
@@ -59,6 +59,27 @@ UPDATE pg_class
ALTER TABLE reloptions_test RESET (illegal_option);
SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass;
+-- Tests for future (FIXME) ternary options
+
+-- behave as boolean option: accept unassigned name and truncated value
+DROP TABLE reloptions_test;
+CREATE TABLE reloptions_test(i INT) WITH (vacuum_truncate);
+SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass;
+
+DROP TABLE reloptions_test;
+CREATE TABLE reloptions_test(i INT) WITH (vacuum_truncate=FaLS);
+SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass;
+
+-- preferred "true" alias is used when storing
+DROP TABLE reloptions_test;
+CREATE TABLE reloptions_test(i INT) WITH (vacuum_index_cleanup=on);
+SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass;
+
+-- custom "third" value is available
+DROP TABLE reloptions_test;
+CREATE TABLE reloptions_test(i INT) WITH (vacuum_index_cleanup=auto);
+SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass;
+
-- Test vacuum_truncate option
DROP TABLE reloptions_test;
diff --git a/src/tools/pgindent/typedefs.list b/src/tools/pgindent/typedefs.list
index 3f3a888fd0e..1c8610fd46c 100644
--- a/src/tools/pgindent/typedefs.list
+++ b/src/tools/pgindent/typedefs.list
@@ -3952,6 +3952,7 @@ pg_sha512_ctx
pg_snapshot
pg_special_case
pg_stack_base_t
+pg_ternary
pg_time_t
pg_time_usec_t
pg_tz
@@ -4079,6 +4080,7 @@ relopt_kind
relopt_parse_elt
relopt_real
relopt_string
+relopt_ternary
relopt_type
relopt_value
relopts_validator
--
2.47.3
--c36t3fndxhkzbl5z--
^ permalink raw reply [nested|flat] 868+ messages in thread
* [PATCH v3] Introduce ternary reloptions
@ 2026-01-16 15:04 Álvaro Herrera <[email protected]>
0 siblings, 0 replies; 868+ messages in thread
From: Álvaro Herrera @ 2026-01-16 15:04 UTC (permalink / raw)
Introduce ternary reloption as a replacement for current
`vacuum_truncate` implementation. Remove the `vacuum_truncate_set`
separate flag and use TERNARY_UNSET instead.
This could also be used for other options such as `vacuum_index_cleanup`
and `buffering`, but lets get the scaffolding in first.
Discussion: https://postgr.es/m/3474141.usfYGdeWWP@thinkpad-pgpro
---
src/backend/access/common/reloptions.c | 137 ++++++++++++++++++-----
src/backend/commands/vacuum.c | 4 +-
src/include/access/reloptions.h | 27 ++---
src/include/postgres.h | 15 +++
src/include/utils/rel.h | 3 +-
src/test/regress/expected/reloptions.out | 36 ++++++
src/test/regress/sql/reloptions.sql | 21 ++++
src/tools/pgindent/typedefs.list | 2 +
8 files changed, 202 insertions(+), 43 deletions(-)
diff --git a/src/backend/access/common/reloptions.c b/src/backend/access/common/reloptions.c
index 0b83f98ed5f..6f1f577581a 100644
--- a/src/backend/access/common/reloptions.c
+++ b/src/backend/access/common/reloptions.c
@@ -40,9 +40,9 @@
*
* To add an option:
*
- * (i) decide on a type (bool, integer, real, enum, string), name, default
- * value, upper and lower bounds (if applicable); for strings, consider a
- * validation routine.
+ * (i) decide on a type (bool, ternary, integer, real, enum, string), name,
+ * default value, upper and lower bounds (if applicable); for strings,
+ * consider a validation routine.
* (ii) add a record below (or use add_<type>_reloption).
* (iii) add it to the appropriate options struct (perhaps StdRdOptions)
* (iv) add it to the appropriate handling routine (perhaps
@@ -147,15 +147,6 @@ static relopt_bool boolRelOpts[] =
},
false
},
- {
- {
- "vacuum_truncate",
- "Enables vacuum to truncate empty pages at the end of this table",
- RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
- ShareUpdateExclusiveLock
- },
- true
- },
{
{
"deduplicate_items",
@@ -170,6 +161,25 @@ static relopt_bool boolRelOpts[] =
{{NULL}}
};
+static relopt_ternary ternaryRelOpts[] =
+{
+ {
+ {
+ "vacuum_truncate",
+ "Enables vacuum to truncate empty pages at the end of this table",
+ RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
+ ShareUpdateExclusiveLock
+ },
+ TERNARY_UNSET
+ },
+ /* list terminator */
+ {
+ {
+ NULL
+ }
+ }
+};
+
static relopt_int intRelOpts[] =
{
{
@@ -609,6 +619,13 @@ initialize_reloptions(void)
boolRelOpts[i].gen.lockmode));
j++;
}
+ for (i = 0; ternaryRelOpts[i].gen.name; i++)
+ {
+ Assert(DoLockModesConflict(ternaryRelOpts[i].gen.lockmode,
+ ternaryRelOpts[i].gen.lockmode));
+ j++;
+ }
+
for (i = 0; intRelOpts[i].gen.name; i++)
{
Assert(DoLockModesConflict(intRelOpts[i].gen.lockmode,
@@ -649,6 +666,14 @@ initialize_reloptions(void)
j++;
}
+ for (i = 0; ternaryRelOpts[i].gen.name; i++)
+ {
+ relOpts[j] = &ternaryRelOpts[i].gen;
+ relOpts[j]->type = RELOPT_TYPE_TERNARY;
+ relOpts[j]->namelen = strlen(relOpts[j]->name);
+ j++;
+ }
+
for (i = 0; intRelOpts[i].gen.name; i++)
{
relOpts[j] = &intRelOpts[i].gen;
@@ -809,6 +834,9 @@ allocate_reloption(bits32 kinds, int type, const char *name, const char *desc,
case RELOPT_TYPE_BOOL:
size = sizeof(relopt_bool);
break;
+ case RELOPT_TYPE_TERNARY:
+ size = sizeof(relopt_ternary);
+ break;
case RELOPT_TYPE_INT:
size = sizeof(relopt_int);
break;
@@ -892,6 +920,57 @@ add_local_bool_reloption(local_relopts *relopts, const char *name,
add_local_reloption(relopts, (relopt_gen *) newoption, offset);
}
+/*
+ * init_ternary_reloption
+ * Allocate and initialize a new ternary reloption
+ */
+static relopt_ternary *
+init_ternary_reloption(bits32 kinds, const char *name, const char *desc,
+ pg_ternary default_val, LOCKMODE lockmode)
+{
+ relopt_ternary *newoption;
+
+ newoption = (relopt_ternary *)
+ allocate_reloption(kinds, RELOPT_TYPE_TERNARY, name, desc, lockmode);
+ newoption->default_val = default_val;
+
+ return newoption;
+}
+
+/*
+ * add_ternary_reloption
+ * Add a new ternary reloption
+ */
+void
+add_ternary_reloption(bits32 kinds, const char *name, const char *desc,
+ pg_ternary default_val, LOCKMODE lockmode)
+{
+ relopt_ternary *newoption;
+
+ newoption =
+ init_ternary_reloption(kinds, name, desc, default_val, lockmode);
+
+ add_reloption((relopt_gen *) newoption);
+}
+
+/*
+ * add_local_ternary_reloption
+ * Add a new ternary local reloption
+ *
+ * 'offset' is offset of ternary-typed field.
+ */
+void
+add_local_ternary_reloption(local_relopts *relopts, const char *name,
+ const char *desc, pg_ternary default_val,
+ int offset)
+{
+ relopt_ternary *newoption;
+
+ newoption =
+ init_ternary_reloption(RELOPT_KIND_LOCAL, name, desc, default_val, 0);
+
+ add_local_reloption(relopts, (relopt_gen *) newoption, offset);
+}
/*
* init_real_reloption
@@ -1626,6 +1705,19 @@ parse_one_reloption(relopt_value *option, char *text_str, int text_len,
option->gen->name, value)));
}
break;
+ case RELOPT_TYPE_TERNARY:
+ {
+ bool b;
+
+ parsed = parse_bool(value, &b);
+ option->values.ternary_val = b ? TERNARY_TRUE : TERNARY_FALSE;
+ if (validate && !parsed)
+ ereport(ERROR,
+ (errcode(ERRCODE_INVALID_PARAMETER_VALUE),
+ errmsg("invalid value for ternary option \"%s\": %s",
+ option->gen->name, value)));
+ }
+ break;
case RELOPT_TYPE_INT:
{
relopt_int *optint = (relopt_int *) option->gen;
@@ -1789,17 +1881,6 @@ fillRelOptions(void *rdopts, Size basesize,
char *itempos = ((char *) rdopts) + elems[j].offset;
char *string_val;
- /*
- * If isset_offset is provided, store whether the reloption is
- * set there.
- */
- if (elems[j].isset_offset > 0)
- {
- char *setpos = ((char *) rdopts) + elems[j].isset_offset;
-
- *(bool *) setpos = options[i].isset;
- }
-
switch (options[i].gen->type)
{
case RELOPT_TYPE_BOOL:
@@ -1807,6 +1888,11 @@ fillRelOptions(void *rdopts, Size basesize,
options[i].values.bool_val :
((relopt_bool *) options[i].gen)->default_val;
break;
+ case RELOPT_TYPE_TERNARY:
+ *(pg_ternary *) itempos = options[i].isset ?
+ options[i].values.ternary_val :
+ ((relopt_ternary *) options[i].gen)->default_val;
+ break;
case RELOPT_TYPE_INT:
*(int *) itempos = options[i].isset ?
options[i].values.int_val :
@@ -1923,8 +2009,8 @@ default_reloptions(Datum reloptions, bool validate, relopt_kind kind)
offsetof(StdRdOptions, parallel_workers)},
{"vacuum_index_cleanup", RELOPT_TYPE_ENUM,
offsetof(StdRdOptions, vacuum_index_cleanup)},
- {"vacuum_truncate", RELOPT_TYPE_BOOL,
- offsetof(StdRdOptions, vacuum_truncate), offsetof(StdRdOptions, vacuum_truncate_set)},
+ {"vacuum_truncate", RELOPT_TYPE_TERNARY,
+ offsetof(StdRdOptions, vacuum_truncate)},
{"vacuum_max_eager_freeze_failure_rate", RELOPT_TYPE_REAL,
offsetof(StdRdOptions, vacuum_max_eager_freeze_failure_rate)}
};
@@ -2004,7 +2090,6 @@ build_local_reloptions(local_relopts *relopts, Datum options, bool validate)
elems[i].optname = opt->option->name;
elems[i].opttype = opt->option->type;
elems[i].offset = opt->offset;
- elems[i].isset_offset = 0; /* not supported for local relopts yet */
i++;
}
diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c
index aa4fbec143f..696eab9bd97 100644
--- a/src/backend/commands/vacuum.c
+++ b/src/backend/commands/vacuum.c
@@ -2224,9 +2224,9 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams params,
{
StdRdOptions *opts = (StdRdOptions *) rel->rd_options;
- if (opts && opts->vacuum_truncate_set)
+ if (opts && opts->vacuum_truncate != TERNARY_UNSET)
{
- if (opts->vacuum_truncate)
+ if (opts->vacuum_truncate == TERNARY_TRUE)
params.truncate = VACOPTVALUE_ENABLED;
else
params.truncate = VACOPTVALUE_DISABLED;
diff --git a/src/include/access/reloptions.h b/src/include/access/reloptions.h
index 2f08e1b0cf0..dfbef2babf2 100644
--- a/src/include/access/reloptions.h
+++ b/src/include/access/reloptions.h
@@ -29,6 +29,7 @@
typedef enum relopt_type
{
RELOPT_TYPE_BOOL,
+ RELOPT_TYPE_TERNARY, /* on, off, unset */
RELOPT_TYPE_INT,
RELOPT_TYPE_REAL,
RELOPT_TYPE_ENUM,
@@ -80,6 +81,7 @@ typedef struct relopt_value
union
{
bool bool_val;
+ pg_ternary ternary_val;
int int_val;
double real_val;
int enum_val;
@@ -94,6 +96,12 @@ typedef struct relopt_bool
bool default_val;
} relopt_bool;
+typedef struct relopt_ternary
+{
+ relopt_gen gen;
+ int default_val;
+} relopt_ternary;
+
typedef struct relopt_int
{
relopt_gen gen;
@@ -152,19 +160,6 @@ typedef struct
const char *optname; /* option's name */
relopt_type opttype; /* option's datatype */
int offset; /* offset of field in result struct */
-
- /*
- * isset_offset is an optional offset of a field in the result struct that
- * stores whether the option is explicitly set for the relation or if it
- * just picked up the default value. In most cases, this can be
- * accomplished by giving the reloption a special out-of-range default
- * value (e.g., some integer reloptions use -2), but this isn't always
- * possible. For example, a Boolean reloption cannot be given an
- * out-of-range default, so we need another way to discover the source of
- * its value. This offset is only used if given a value greater than
- * zero.
- */
- int isset_offset;
} relopt_parse_elt;
/* Local reloption definition */
@@ -195,6 +190,9 @@ typedef struct local_relopts
extern relopt_kind add_reloption_kind(void);
extern void add_bool_reloption(bits32 kinds, const char *name, const char *desc,
bool default_val, LOCKMODE lockmode);
+extern void add_ternary_reloption(bits32 kinds, const char *name,
+ const char *desc, pg_ternary default_val,
+ LOCKMODE lockmode);
extern void add_int_reloption(bits32 kinds, const char *name, const char *desc,
int default_val, int min_val, int max_val,
LOCKMODE lockmode);
@@ -214,6 +212,9 @@ extern void register_reloptions_validator(local_relopts *relopts,
extern void add_local_bool_reloption(local_relopts *relopts, const char *name,
const char *desc, bool default_val,
int offset);
+extern void add_local_ternary_reloption(local_relopts *relopts,
+ const char *name, const char *desc,
+ pg_ternary default_val, int offset);
extern void add_local_int_reloption(local_relopts *relopts, const char *name,
const char *desc, int default_val,
int min_val, int max_val, int offset);
diff --git a/src/include/postgres.h b/src/include/postgres.h
index 7d93fbce709..47c88780776 100644
--- a/src/include/postgres.h
+++ b/src/include/postgres.h
@@ -543,6 +543,21 @@ Float8GetDatum(float8 X)
* ----------------------------------------------------------------
*/
+/*
+ * pg_ternary
+ * Boolean value with an extra "unset" value
+ *
+ * This enum can be used for values that want to distinguish between true,
+ * false, and unset.
+*/
+
+typedef enum pg_ternary
+{
+ TERNARY_FALSE = 0,
+ TERNARY_TRUE = 1,
+ TERNARY_UNSET = -1
+} pg_ternary;
+
/*
* NON_EXEC_STATIC: It's sometimes useful to define a variable or function
* that is normally static but extern when using EXEC_BACKEND (see
diff --git a/src/include/utils/rel.h b/src/include/utils/rel.h
index d03ab247788..5feb18a5373 100644
--- a/src/include/utils/rel.h
+++ b/src/include/utils/rel.h
@@ -347,8 +347,7 @@ typedef struct StdRdOptions
bool user_catalog_table; /* use as an additional catalog relation */
int parallel_workers; /* max number of parallel workers */
StdRdOptIndexCleanup vacuum_index_cleanup; /* controls index vacuuming */
- bool vacuum_truncate; /* enables vacuum to truncate a relation */
- bool vacuum_truncate_set; /* whether vacuum_truncate is set */
+ pg_ternary vacuum_truncate; /* enables vacuum to truncate a relation */
/*
* Fraction of pages in a relation that vacuum can eagerly scan and fail
diff --git a/src/test/regress/expected/reloptions.out b/src/test/regress/expected/reloptions.out
index 9de19b4e3f1..1c99f79ab01 100644
--- a/src/test/regress/expected/reloptions.out
+++ b/src/test/regress/expected/reloptions.out
@@ -98,6 +98,42 @@ SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass;
{fillfactor=13,autovacuum_enabled=false}
(1 row)
+-- Tests for future (FIXME) ternary options
+-- behave as boolean option: accept unassigned name and truncated value
+DROP TABLE reloptions_test;
+CREATE TABLE reloptions_test(i INT) WITH (vacuum_truncate);
+SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass;
+ reloptions
+------------------------
+ {vacuum_truncate=true}
+(1 row)
+
+DROP TABLE reloptions_test;
+CREATE TABLE reloptions_test(i INT) WITH (vacuum_truncate=FaLS);
+SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass;
+ reloptions
+------------------------
+ {vacuum_truncate=fals}
+(1 row)
+
+-- preferred "true" alias is used when storing
+DROP TABLE reloptions_test;
+CREATE TABLE reloptions_test(i INT) WITH (vacuum_index_cleanup=on);
+SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass;
+ reloptions
+---------------------------
+ {vacuum_index_cleanup=on}
+(1 row)
+
+-- custom "third" value is available
+DROP TABLE reloptions_test;
+CREATE TABLE reloptions_test(i INT) WITH (vacuum_index_cleanup=auto);
+SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass;
+ reloptions
+-----------------------------
+ {vacuum_index_cleanup=auto}
+(1 row)
+
-- Test vacuum_truncate option
DROP TABLE reloptions_test;
CREATE TEMP TABLE reloptions_test(i INT NOT NULL, j text)
diff --git a/src/test/regress/sql/reloptions.sql b/src/test/regress/sql/reloptions.sql
index 24fbe0b478d..f5980dafcbc 100644
--- a/src/test/regress/sql/reloptions.sql
+++ b/src/test/regress/sql/reloptions.sql
@@ -59,6 +59,27 @@ UPDATE pg_class
ALTER TABLE reloptions_test RESET (illegal_option);
SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass;
+-- Tests for future (FIXME) ternary options
+
+-- behave as boolean option: accept unassigned name and truncated value
+DROP TABLE reloptions_test;
+CREATE TABLE reloptions_test(i INT) WITH (vacuum_truncate);
+SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass;
+
+DROP TABLE reloptions_test;
+CREATE TABLE reloptions_test(i INT) WITH (vacuum_truncate=FaLS);
+SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass;
+
+-- preferred "true" alias is used when storing
+DROP TABLE reloptions_test;
+CREATE TABLE reloptions_test(i INT) WITH (vacuum_index_cleanup=on);
+SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass;
+
+-- custom "third" value is available
+DROP TABLE reloptions_test;
+CREATE TABLE reloptions_test(i INT) WITH (vacuum_index_cleanup=auto);
+SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass;
+
-- Test vacuum_truncate option
DROP TABLE reloptions_test;
diff --git a/src/tools/pgindent/typedefs.list b/src/tools/pgindent/typedefs.list
index 3f3a888fd0e..1c8610fd46c 100644
--- a/src/tools/pgindent/typedefs.list
+++ b/src/tools/pgindent/typedefs.list
@@ -3952,6 +3952,7 @@ pg_sha512_ctx
pg_snapshot
pg_special_case
pg_stack_base_t
+pg_ternary
pg_time_t
pg_time_usec_t
pg_tz
@@ -4079,6 +4080,7 @@ relopt_kind
relopt_parse_elt
relopt_real
relopt_string
+relopt_ternary
relopt_type
relopt_value
relopts_validator
--
2.47.3
--c36t3fndxhkzbl5z--
^ permalink raw reply [nested|flat] 868+ messages in thread
* [PATCH v3] Introduce ternary reloptions
@ 2026-01-16 15:04 Álvaro Herrera <[email protected]>
0 siblings, 0 replies; 868+ messages in thread
From: Álvaro Herrera @ 2026-01-16 15:04 UTC (permalink / raw)
Introduce ternary reloption as a replacement for current
`vacuum_truncate` implementation. Remove the `vacuum_truncate_set`
separate flag and use TERNARY_UNSET instead.
This could also be used for other options such as `vacuum_index_cleanup`
and `buffering`, but lets get the scaffolding in first.
Discussion: https://postgr.es/m/3474141.usfYGdeWWP@thinkpad-pgpro
---
src/backend/access/common/reloptions.c | 137 ++++++++++++++++++-----
src/backend/commands/vacuum.c | 4 +-
src/include/access/reloptions.h | 27 ++---
src/include/postgres.h | 15 +++
src/include/utils/rel.h | 3 +-
src/test/regress/expected/reloptions.out | 36 ++++++
src/test/regress/sql/reloptions.sql | 21 ++++
src/tools/pgindent/typedefs.list | 2 +
8 files changed, 202 insertions(+), 43 deletions(-)
diff --git a/src/backend/access/common/reloptions.c b/src/backend/access/common/reloptions.c
index 0b83f98ed5f..6f1f577581a 100644
--- a/src/backend/access/common/reloptions.c
+++ b/src/backend/access/common/reloptions.c
@@ -40,9 +40,9 @@
*
* To add an option:
*
- * (i) decide on a type (bool, integer, real, enum, string), name, default
- * value, upper and lower bounds (if applicable); for strings, consider a
- * validation routine.
+ * (i) decide on a type (bool, ternary, integer, real, enum, string), name,
+ * default value, upper and lower bounds (if applicable); for strings,
+ * consider a validation routine.
* (ii) add a record below (or use add_<type>_reloption).
* (iii) add it to the appropriate options struct (perhaps StdRdOptions)
* (iv) add it to the appropriate handling routine (perhaps
@@ -147,15 +147,6 @@ static relopt_bool boolRelOpts[] =
},
false
},
- {
- {
- "vacuum_truncate",
- "Enables vacuum to truncate empty pages at the end of this table",
- RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
- ShareUpdateExclusiveLock
- },
- true
- },
{
{
"deduplicate_items",
@@ -170,6 +161,25 @@ static relopt_bool boolRelOpts[] =
{{NULL}}
};
+static relopt_ternary ternaryRelOpts[] =
+{
+ {
+ {
+ "vacuum_truncate",
+ "Enables vacuum to truncate empty pages at the end of this table",
+ RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
+ ShareUpdateExclusiveLock
+ },
+ TERNARY_UNSET
+ },
+ /* list terminator */
+ {
+ {
+ NULL
+ }
+ }
+};
+
static relopt_int intRelOpts[] =
{
{
@@ -609,6 +619,13 @@ initialize_reloptions(void)
boolRelOpts[i].gen.lockmode));
j++;
}
+ for (i = 0; ternaryRelOpts[i].gen.name; i++)
+ {
+ Assert(DoLockModesConflict(ternaryRelOpts[i].gen.lockmode,
+ ternaryRelOpts[i].gen.lockmode));
+ j++;
+ }
+
for (i = 0; intRelOpts[i].gen.name; i++)
{
Assert(DoLockModesConflict(intRelOpts[i].gen.lockmode,
@@ -649,6 +666,14 @@ initialize_reloptions(void)
j++;
}
+ for (i = 0; ternaryRelOpts[i].gen.name; i++)
+ {
+ relOpts[j] = &ternaryRelOpts[i].gen;
+ relOpts[j]->type = RELOPT_TYPE_TERNARY;
+ relOpts[j]->namelen = strlen(relOpts[j]->name);
+ j++;
+ }
+
for (i = 0; intRelOpts[i].gen.name; i++)
{
relOpts[j] = &intRelOpts[i].gen;
@@ -809,6 +834,9 @@ allocate_reloption(bits32 kinds, int type, const char *name, const char *desc,
case RELOPT_TYPE_BOOL:
size = sizeof(relopt_bool);
break;
+ case RELOPT_TYPE_TERNARY:
+ size = sizeof(relopt_ternary);
+ break;
case RELOPT_TYPE_INT:
size = sizeof(relopt_int);
break;
@@ -892,6 +920,57 @@ add_local_bool_reloption(local_relopts *relopts, const char *name,
add_local_reloption(relopts, (relopt_gen *) newoption, offset);
}
+/*
+ * init_ternary_reloption
+ * Allocate and initialize a new ternary reloption
+ */
+static relopt_ternary *
+init_ternary_reloption(bits32 kinds, const char *name, const char *desc,
+ pg_ternary default_val, LOCKMODE lockmode)
+{
+ relopt_ternary *newoption;
+
+ newoption = (relopt_ternary *)
+ allocate_reloption(kinds, RELOPT_TYPE_TERNARY, name, desc, lockmode);
+ newoption->default_val = default_val;
+
+ return newoption;
+}
+
+/*
+ * add_ternary_reloption
+ * Add a new ternary reloption
+ */
+void
+add_ternary_reloption(bits32 kinds, const char *name, const char *desc,
+ pg_ternary default_val, LOCKMODE lockmode)
+{
+ relopt_ternary *newoption;
+
+ newoption =
+ init_ternary_reloption(kinds, name, desc, default_val, lockmode);
+
+ add_reloption((relopt_gen *) newoption);
+}
+
+/*
+ * add_local_ternary_reloption
+ * Add a new ternary local reloption
+ *
+ * 'offset' is offset of ternary-typed field.
+ */
+void
+add_local_ternary_reloption(local_relopts *relopts, const char *name,
+ const char *desc, pg_ternary default_val,
+ int offset)
+{
+ relopt_ternary *newoption;
+
+ newoption =
+ init_ternary_reloption(RELOPT_KIND_LOCAL, name, desc, default_val, 0);
+
+ add_local_reloption(relopts, (relopt_gen *) newoption, offset);
+}
/*
* init_real_reloption
@@ -1626,6 +1705,19 @@ parse_one_reloption(relopt_value *option, char *text_str, int text_len,
option->gen->name, value)));
}
break;
+ case RELOPT_TYPE_TERNARY:
+ {
+ bool b;
+
+ parsed = parse_bool(value, &b);
+ option->values.ternary_val = b ? TERNARY_TRUE : TERNARY_FALSE;
+ if (validate && !parsed)
+ ereport(ERROR,
+ (errcode(ERRCODE_INVALID_PARAMETER_VALUE),
+ errmsg("invalid value for ternary option \"%s\": %s",
+ option->gen->name, value)));
+ }
+ break;
case RELOPT_TYPE_INT:
{
relopt_int *optint = (relopt_int *) option->gen;
@@ -1789,17 +1881,6 @@ fillRelOptions(void *rdopts, Size basesize,
char *itempos = ((char *) rdopts) + elems[j].offset;
char *string_val;
- /*
- * If isset_offset is provided, store whether the reloption is
- * set there.
- */
- if (elems[j].isset_offset > 0)
- {
- char *setpos = ((char *) rdopts) + elems[j].isset_offset;
-
- *(bool *) setpos = options[i].isset;
- }
-
switch (options[i].gen->type)
{
case RELOPT_TYPE_BOOL:
@@ -1807,6 +1888,11 @@ fillRelOptions(void *rdopts, Size basesize,
options[i].values.bool_val :
((relopt_bool *) options[i].gen)->default_val;
break;
+ case RELOPT_TYPE_TERNARY:
+ *(pg_ternary *) itempos = options[i].isset ?
+ options[i].values.ternary_val :
+ ((relopt_ternary *) options[i].gen)->default_val;
+ break;
case RELOPT_TYPE_INT:
*(int *) itempos = options[i].isset ?
options[i].values.int_val :
@@ -1923,8 +2009,8 @@ default_reloptions(Datum reloptions, bool validate, relopt_kind kind)
offsetof(StdRdOptions, parallel_workers)},
{"vacuum_index_cleanup", RELOPT_TYPE_ENUM,
offsetof(StdRdOptions, vacuum_index_cleanup)},
- {"vacuum_truncate", RELOPT_TYPE_BOOL,
- offsetof(StdRdOptions, vacuum_truncate), offsetof(StdRdOptions, vacuum_truncate_set)},
+ {"vacuum_truncate", RELOPT_TYPE_TERNARY,
+ offsetof(StdRdOptions, vacuum_truncate)},
{"vacuum_max_eager_freeze_failure_rate", RELOPT_TYPE_REAL,
offsetof(StdRdOptions, vacuum_max_eager_freeze_failure_rate)}
};
@@ -2004,7 +2090,6 @@ build_local_reloptions(local_relopts *relopts, Datum options, bool validate)
elems[i].optname = opt->option->name;
elems[i].opttype = opt->option->type;
elems[i].offset = opt->offset;
- elems[i].isset_offset = 0; /* not supported for local relopts yet */
i++;
}
diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c
index aa4fbec143f..696eab9bd97 100644
--- a/src/backend/commands/vacuum.c
+++ b/src/backend/commands/vacuum.c
@@ -2224,9 +2224,9 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams params,
{
StdRdOptions *opts = (StdRdOptions *) rel->rd_options;
- if (opts && opts->vacuum_truncate_set)
+ if (opts && opts->vacuum_truncate != TERNARY_UNSET)
{
- if (opts->vacuum_truncate)
+ if (opts->vacuum_truncate == TERNARY_TRUE)
params.truncate = VACOPTVALUE_ENABLED;
else
params.truncate = VACOPTVALUE_DISABLED;
diff --git a/src/include/access/reloptions.h b/src/include/access/reloptions.h
index 2f08e1b0cf0..dfbef2babf2 100644
--- a/src/include/access/reloptions.h
+++ b/src/include/access/reloptions.h
@@ -29,6 +29,7 @@
typedef enum relopt_type
{
RELOPT_TYPE_BOOL,
+ RELOPT_TYPE_TERNARY, /* on, off, unset */
RELOPT_TYPE_INT,
RELOPT_TYPE_REAL,
RELOPT_TYPE_ENUM,
@@ -80,6 +81,7 @@ typedef struct relopt_value
union
{
bool bool_val;
+ pg_ternary ternary_val;
int int_val;
double real_val;
int enum_val;
@@ -94,6 +96,12 @@ typedef struct relopt_bool
bool default_val;
} relopt_bool;
+typedef struct relopt_ternary
+{
+ relopt_gen gen;
+ int default_val;
+} relopt_ternary;
+
typedef struct relopt_int
{
relopt_gen gen;
@@ -152,19 +160,6 @@ typedef struct
const char *optname; /* option's name */
relopt_type opttype; /* option's datatype */
int offset; /* offset of field in result struct */
-
- /*
- * isset_offset is an optional offset of a field in the result struct that
- * stores whether the option is explicitly set for the relation or if it
- * just picked up the default value. In most cases, this can be
- * accomplished by giving the reloption a special out-of-range default
- * value (e.g., some integer reloptions use -2), but this isn't always
- * possible. For example, a Boolean reloption cannot be given an
- * out-of-range default, so we need another way to discover the source of
- * its value. This offset is only used if given a value greater than
- * zero.
- */
- int isset_offset;
} relopt_parse_elt;
/* Local reloption definition */
@@ -195,6 +190,9 @@ typedef struct local_relopts
extern relopt_kind add_reloption_kind(void);
extern void add_bool_reloption(bits32 kinds, const char *name, const char *desc,
bool default_val, LOCKMODE lockmode);
+extern void add_ternary_reloption(bits32 kinds, const char *name,
+ const char *desc, pg_ternary default_val,
+ LOCKMODE lockmode);
extern void add_int_reloption(bits32 kinds, const char *name, const char *desc,
int default_val, int min_val, int max_val,
LOCKMODE lockmode);
@@ -214,6 +212,9 @@ extern void register_reloptions_validator(local_relopts *relopts,
extern void add_local_bool_reloption(local_relopts *relopts, const char *name,
const char *desc, bool default_val,
int offset);
+extern void add_local_ternary_reloption(local_relopts *relopts,
+ const char *name, const char *desc,
+ pg_ternary default_val, int offset);
extern void add_local_int_reloption(local_relopts *relopts, const char *name,
const char *desc, int default_val,
int min_val, int max_val, int offset);
diff --git a/src/include/postgres.h b/src/include/postgres.h
index 7d93fbce709..47c88780776 100644
--- a/src/include/postgres.h
+++ b/src/include/postgres.h
@@ -543,6 +543,21 @@ Float8GetDatum(float8 X)
* ----------------------------------------------------------------
*/
+/*
+ * pg_ternary
+ * Boolean value with an extra "unset" value
+ *
+ * This enum can be used for values that want to distinguish between true,
+ * false, and unset.
+*/
+
+typedef enum pg_ternary
+{
+ TERNARY_FALSE = 0,
+ TERNARY_TRUE = 1,
+ TERNARY_UNSET = -1
+} pg_ternary;
+
/*
* NON_EXEC_STATIC: It's sometimes useful to define a variable or function
* that is normally static but extern when using EXEC_BACKEND (see
diff --git a/src/include/utils/rel.h b/src/include/utils/rel.h
index d03ab247788..5feb18a5373 100644
--- a/src/include/utils/rel.h
+++ b/src/include/utils/rel.h
@@ -347,8 +347,7 @@ typedef struct StdRdOptions
bool user_catalog_table; /* use as an additional catalog relation */
int parallel_workers; /* max number of parallel workers */
StdRdOptIndexCleanup vacuum_index_cleanup; /* controls index vacuuming */
- bool vacuum_truncate; /* enables vacuum to truncate a relation */
- bool vacuum_truncate_set; /* whether vacuum_truncate is set */
+ pg_ternary vacuum_truncate; /* enables vacuum to truncate a relation */
/*
* Fraction of pages in a relation that vacuum can eagerly scan and fail
diff --git a/src/test/regress/expected/reloptions.out b/src/test/regress/expected/reloptions.out
index 9de19b4e3f1..1c99f79ab01 100644
--- a/src/test/regress/expected/reloptions.out
+++ b/src/test/regress/expected/reloptions.out
@@ -98,6 +98,42 @@ SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass;
{fillfactor=13,autovacuum_enabled=false}
(1 row)
+-- Tests for future (FIXME) ternary options
+-- behave as boolean option: accept unassigned name and truncated value
+DROP TABLE reloptions_test;
+CREATE TABLE reloptions_test(i INT) WITH (vacuum_truncate);
+SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass;
+ reloptions
+------------------------
+ {vacuum_truncate=true}
+(1 row)
+
+DROP TABLE reloptions_test;
+CREATE TABLE reloptions_test(i INT) WITH (vacuum_truncate=FaLS);
+SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass;
+ reloptions
+------------------------
+ {vacuum_truncate=fals}
+(1 row)
+
+-- preferred "true" alias is used when storing
+DROP TABLE reloptions_test;
+CREATE TABLE reloptions_test(i INT) WITH (vacuum_index_cleanup=on);
+SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass;
+ reloptions
+---------------------------
+ {vacuum_index_cleanup=on}
+(1 row)
+
+-- custom "third" value is available
+DROP TABLE reloptions_test;
+CREATE TABLE reloptions_test(i INT) WITH (vacuum_index_cleanup=auto);
+SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass;
+ reloptions
+-----------------------------
+ {vacuum_index_cleanup=auto}
+(1 row)
+
-- Test vacuum_truncate option
DROP TABLE reloptions_test;
CREATE TEMP TABLE reloptions_test(i INT NOT NULL, j text)
diff --git a/src/test/regress/sql/reloptions.sql b/src/test/regress/sql/reloptions.sql
index 24fbe0b478d..f5980dafcbc 100644
--- a/src/test/regress/sql/reloptions.sql
+++ b/src/test/regress/sql/reloptions.sql
@@ -59,6 +59,27 @@ UPDATE pg_class
ALTER TABLE reloptions_test RESET (illegal_option);
SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass;
+-- Tests for future (FIXME) ternary options
+
+-- behave as boolean option: accept unassigned name and truncated value
+DROP TABLE reloptions_test;
+CREATE TABLE reloptions_test(i INT) WITH (vacuum_truncate);
+SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass;
+
+DROP TABLE reloptions_test;
+CREATE TABLE reloptions_test(i INT) WITH (vacuum_truncate=FaLS);
+SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass;
+
+-- preferred "true" alias is used when storing
+DROP TABLE reloptions_test;
+CREATE TABLE reloptions_test(i INT) WITH (vacuum_index_cleanup=on);
+SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass;
+
+-- custom "third" value is available
+DROP TABLE reloptions_test;
+CREATE TABLE reloptions_test(i INT) WITH (vacuum_index_cleanup=auto);
+SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass;
+
-- Test vacuum_truncate option
DROP TABLE reloptions_test;
diff --git a/src/tools/pgindent/typedefs.list b/src/tools/pgindent/typedefs.list
index 3f3a888fd0e..1c8610fd46c 100644
--- a/src/tools/pgindent/typedefs.list
+++ b/src/tools/pgindent/typedefs.list
@@ -3952,6 +3952,7 @@ pg_sha512_ctx
pg_snapshot
pg_special_case
pg_stack_base_t
+pg_ternary
pg_time_t
pg_time_usec_t
pg_tz
@@ -4079,6 +4080,7 @@ relopt_kind
relopt_parse_elt
relopt_real
relopt_string
+relopt_ternary
relopt_type
relopt_value
relopts_validator
--
2.47.3
--c36t3fndxhkzbl5z--
^ permalink raw reply [nested|flat] 868+ messages in thread
* [PATCH v3] Introduce ternary reloptions
@ 2026-01-16 15:04 Álvaro Herrera <[email protected]>
0 siblings, 0 replies; 868+ messages in thread
From: Álvaro Herrera @ 2026-01-16 15:04 UTC (permalink / raw)
Introduce ternary reloption as a replacement for current
`vacuum_truncate` implementation. Remove the `vacuum_truncate_set`
separate flag and use TERNARY_UNSET instead.
This could also be used for other options such as `vacuum_index_cleanup`
and `buffering`, but lets get the scaffolding in first.
Discussion: https://postgr.es/m/3474141.usfYGdeWWP@thinkpad-pgpro
---
src/backend/access/common/reloptions.c | 137 ++++++++++++++++++-----
src/backend/commands/vacuum.c | 4 +-
src/include/access/reloptions.h | 27 ++---
src/include/postgres.h | 15 +++
src/include/utils/rel.h | 3 +-
src/test/regress/expected/reloptions.out | 36 ++++++
src/test/regress/sql/reloptions.sql | 21 ++++
src/tools/pgindent/typedefs.list | 2 +
8 files changed, 202 insertions(+), 43 deletions(-)
diff --git a/src/backend/access/common/reloptions.c b/src/backend/access/common/reloptions.c
index 0b83f98ed5f..6f1f577581a 100644
--- a/src/backend/access/common/reloptions.c
+++ b/src/backend/access/common/reloptions.c
@@ -40,9 +40,9 @@
*
* To add an option:
*
- * (i) decide on a type (bool, integer, real, enum, string), name, default
- * value, upper and lower bounds (if applicable); for strings, consider a
- * validation routine.
+ * (i) decide on a type (bool, ternary, integer, real, enum, string), name,
+ * default value, upper and lower bounds (if applicable); for strings,
+ * consider a validation routine.
* (ii) add a record below (or use add_<type>_reloption).
* (iii) add it to the appropriate options struct (perhaps StdRdOptions)
* (iv) add it to the appropriate handling routine (perhaps
@@ -147,15 +147,6 @@ static relopt_bool boolRelOpts[] =
},
false
},
- {
- {
- "vacuum_truncate",
- "Enables vacuum to truncate empty pages at the end of this table",
- RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
- ShareUpdateExclusiveLock
- },
- true
- },
{
{
"deduplicate_items",
@@ -170,6 +161,25 @@ static relopt_bool boolRelOpts[] =
{{NULL}}
};
+static relopt_ternary ternaryRelOpts[] =
+{
+ {
+ {
+ "vacuum_truncate",
+ "Enables vacuum to truncate empty pages at the end of this table",
+ RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
+ ShareUpdateExclusiveLock
+ },
+ TERNARY_UNSET
+ },
+ /* list terminator */
+ {
+ {
+ NULL
+ }
+ }
+};
+
static relopt_int intRelOpts[] =
{
{
@@ -609,6 +619,13 @@ initialize_reloptions(void)
boolRelOpts[i].gen.lockmode));
j++;
}
+ for (i = 0; ternaryRelOpts[i].gen.name; i++)
+ {
+ Assert(DoLockModesConflict(ternaryRelOpts[i].gen.lockmode,
+ ternaryRelOpts[i].gen.lockmode));
+ j++;
+ }
+
for (i = 0; intRelOpts[i].gen.name; i++)
{
Assert(DoLockModesConflict(intRelOpts[i].gen.lockmode,
@@ -649,6 +666,14 @@ initialize_reloptions(void)
j++;
}
+ for (i = 0; ternaryRelOpts[i].gen.name; i++)
+ {
+ relOpts[j] = &ternaryRelOpts[i].gen;
+ relOpts[j]->type = RELOPT_TYPE_TERNARY;
+ relOpts[j]->namelen = strlen(relOpts[j]->name);
+ j++;
+ }
+
for (i = 0; intRelOpts[i].gen.name; i++)
{
relOpts[j] = &intRelOpts[i].gen;
@@ -809,6 +834,9 @@ allocate_reloption(bits32 kinds, int type, const char *name, const char *desc,
case RELOPT_TYPE_BOOL:
size = sizeof(relopt_bool);
break;
+ case RELOPT_TYPE_TERNARY:
+ size = sizeof(relopt_ternary);
+ break;
case RELOPT_TYPE_INT:
size = sizeof(relopt_int);
break;
@@ -892,6 +920,57 @@ add_local_bool_reloption(local_relopts *relopts, const char *name,
add_local_reloption(relopts, (relopt_gen *) newoption, offset);
}
+/*
+ * init_ternary_reloption
+ * Allocate and initialize a new ternary reloption
+ */
+static relopt_ternary *
+init_ternary_reloption(bits32 kinds, const char *name, const char *desc,
+ pg_ternary default_val, LOCKMODE lockmode)
+{
+ relopt_ternary *newoption;
+
+ newoption = (relopt_ternary *)
+ allocate_reloption(kinds, RELOPT_TYPE_TERNARY, name, desc, lockmode);
+ newoption->default_val = default_val;
+
+ return newoption;
+}
+
+/*
+ * add_ternary_reloption
+ * Add a new ternary reloption
+ */
+void
+add_ternary_reloption(bits32 kinds, const char *name, const char *desc,
+ pg_ternary default_val, LOCKMODE lockmode)
+{
+ relopt_ternary *newoption;
+
+ newoption =
+ init_ternary_reloption(kinds, name, desc, default_val, lockmode);
+
+ add_reloption((relopt_gen *) newoption);
+}
+
+/*
+ * add_local_ternary_reloption
+ * Add a new ternary local reloption
+ *
+ * 'offset' is offset of ternary-typed field.
+ */
+void
+add_local_ternary_reloption(local_relopts *relopts, const char *name,
+ const char *desc, pg_ternary default_val,
+ int offset)
+{
+ relopt_ternary *newoption;
+
+ newoption =
+ init_ternary_reloption(RELOPT_KIND_LOCAL, name, desc, default_val, 0);
+
+ add_local_reloption(relopts, (relopt_gen *) newoption, offset);
+}
/*
* init_real_reloption
@@ -1626,6 +1705,19 @@ parse_one_reloption(relopt_value *option, char *text_str, int text_len,
option->gen->name, value)));
}
break;
+ case RELOPT_TYPE_TERNARY:
+ {
+ bool b;
+
+ parsed = parse_bool(value, &b);
+ option->values.ternary_val = b ? TERNARY_TRUE : TERNARY_FALSE;
+ if (validate && !parsed)
+ ereport(ERROR,
+ (errcode(ERRCODE_INVALID_PARAMETER_VALUE),
+ errmsg("invalid value for ternary option \"%s\": %s",
+ option->gen->name, value)));
+ }
+ break;
case RELOPT_TYPE_INT:
{
relopt_int *optint = (relopt_int *) option->gen;
@@ -1789,17 +1881,6 @@ fillRelOptions(void *rdopts, Size basesize,
char *itempos = ((char *) rdopts) + elems[j].offset;
char *string_val;
- /*
- * If isset_offset is provided, store whether the reloption is
- * set there.
- */
- if (elems[j].isset_offset > 0)
- {
- char *setpos = ((char *) rdopts) + elems[j].isset_offset;
-
- *(bool *) setpos = options[i].isset;
- }
-
switch (options[i].gen->type)
{
case RELOPT_TYPE_BOOL:
@@ -1807,6 +1888,11 @@ fillRelOptions(void *rdopts, Size basesize,
options[i].values.bool_val :
((relopt_bool *) options[i].gen)->default_val;
break;
+ case RELOPT_TYPE_TERNARY:
+ *(pg_ternary *) itempos = options[i].isset ?
+ options[i].values.ternary_val :
+ ((relopt_ternary *) options[i].gen)->default_val;
+ break;
case RELOPT_TYPE_INT:
*(int *) itempos = options[i].isset ?
options[i].values.int_val :
@@ -1923,8 +2009,8 @@ default_reloptions(Datum reloptions, bool validate, relopt_kind kind)
offsetof(StdRdOptions, parallel_workers)},
{"vacuum_index_cleanup", RELOPT_TYPE_ENUM,
offsetof(StdRdOptions, vacuum_index_cleanup)},
- {"vacuum_truncate", RELOPT_TYPE_BOOL,
- offsetof(StdRdOptions, vacuum_truncate), offsetof(StdRdOptions, vacuum_truncate_set)},
+ {"vacuum_truncate", RELOPT_TYPE_TERNARY,
+ offsetof(StdRdOptions, vacuum_truncate)},
{"vacuum_max_eager_freeze_failure_rate", RELOPT_TYPE_REAL,
offsetof(StdRdOptions, vacuum_max_eager_freeze_failure_rate)}
};
@@ -2004,7 +2090,6 @@ build_local_reloptions(local_relopts *relopts, Datum options, bool validate)
elems[i].optname = opt->option->name;
elems[i].opttype = opt->option->type;
elems[i].offset = opt->offset;
- elems[i].isset_offset = 0; /* not supported for local relopts yet */
i++;
}
diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c
index aa4fbec143f..696eab9bd97 100644
--- a/src/backend/commands/vacuum.c
+++ b/src/backend/commands/vacuum.c
@@ -2224,9 +2224,9 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams params,
{
StdRdOptions *opts = (StdRdOptions *) rel->rd_options;
- if (opts && opts->vacuum_truncate_set)
+ if (opts && opts->vacuum_truncate != TERNARY_UNSET)
{
- if (opts->vacuum_truncate)
+ if (opts->vacuum_truncate == TERNARY_TRUE)
params.truncate = VACOPTVALUE_ENABLED;
else
params.truncate = VACOPTVALUE_DISABLED;
diff --git a/src/include/access/reloptions.h b/src/include/access/reloptions.h
index 2f08e1b0cf0..dfbef2babf2 100644
--- a/src/include/access/reloptions.h
+++ b/src/include/access/reloptions.h
@@ -29,6 +29,7 @@
typedef enum relopt_type
{
RELOPT_TYPE_BOOL,
+ RELOPT_TYPE_TERNARY, /* on, off, unset */
RELOPT_TYPE_INT,
RELOPT_TYPE_REAL,
RELOPT_TYPE_ENUM,
@@ -80,6 +81,7 @@ typedef struct relopt_value
union
{
bool bool_val;
+ pg_ternary ternary_val;
int int_val;
double real_val;
int enum_val;
@@ -94,6 +96,12 @@ typedef struct relopt_bool
bool default_val;
} relopt_bool;
+typedef struct relopt_ternary
+{
+ relopt_gen gen;
+ int default_val;
+} relopt_ternary;
+
typedef struct relopt_int
{
relopt_gen gen;
@@ -152,19 +160,6 @@ typedef struct
const char *optname; /* option's name */
relopt_type opttype; /* option's datatype */
int offset; /* offset of field in result struct */
-
- /*
- * isset_offset is an optional offset of a field in the result struct that
- * stores whether the option is explicitly set for the relation or if it
- * just picked up the default value. In most cases, this can be
- * accomplished by giving the reloption a special out-of-range default
- * value (e.g., some integer reloptions use -2), but this isn't always
- * possible. For example, a Boolean reloption cannot be given an
- * out-of-range default, so we need another way to discover the source of
- * its value. This offset is only used if given a value greater than
- * zero.
- */
- int isset_offset;
} relopt_parse_elt;
/* Local reloption definition */
@@ -195,6 +190,9 @@ typedef struct local_relopts
extern relopt_kind add_reloption_kind(void);
extern void add_bool_reloption(bits32 kinds, const char *name, const char *desc,
bool default_val, LOCKMODE lockmode);
+extern void add_ternary_reloption(bits32 kinds, const char *name,
+ const char *desc, pg_ternary default_val,
+ LOCKMODE lockmode);
extern void add_int_reloption(bits32 kinds, const char *name, const char *desc,
int default_val, int min_val, int max_val,
LOCKMODE lockmode);
@@ -214,6 +212,9 @@ extern void register_reloptions_validator(local_relopts *relopts,
extern void add_local_bool_reloption(local_relopts *relopts, const char *name,
const char *desc, bool default_val,
int offset);
+extern void add_local_ternary_reloption(local_relopts *relopts,
+ const char *name, const char *desc,
+ pg_ternary default_val, int offset);
extern void add_local_int_reloption(local_relopts *relopts, const char *name,
const char *desc, int default_val,
int min_val, int max_val, int offset);
diff --git a/src/include/postgres.h b/src/include/postgres.h
index 7d93fbce709..47c88780776 100644
--- a/src/include/postgres.h
+++ b/src/include/postgres.h
@@ -543,6 +543,21 @@ Float8GetDatum(float8 X)
* ----------------------------------------------------------------
*/
+/*
+ * pg_ternary
+ * Boolean value with an extra "unset" value
+ *
+ * This enum can be used for values that want to distinguish between true,
+ * false, and unset.
+*/
+
+typedef enum pg_ternary
+{
+ TERNARY_FALSE = 0,
+ TERNARY_TRUE = 1,
+ TERNARY_UNSET = -1
+} pg_ternary;
+
/*
* NON_EXEC_STATIC: It's sometimes useful to define a variable or function
* that is normally static but extern when using EXEC_BACKEND (see
diff --git a/src/include/utils/rel.h b/src/include/utils/rel.h
index d03ab247788..5feb18a5373 100644
--- a/src/include/utils/rel.h
+++ b/src/include/utils/rel.h
@@ -347,8 +347,7 @@ typedef struct StdRdOptions
bool user_catalog_table; /* use as an additional catalog relation */
int parallel_workers; /* max number of parallel workers */
StdRdOptIndexCleanup vacuum_index_cleanup; /* controls index vacuuming */
- bool vacuum_truncate; /* enables vacuum to truncate a relation */
- bool vacuum_truncate_set; /* whether vacuum_truncate is set */
+ pg_ternary vacuum_truncate; /* enables vacuum to truncate a relation */
/*
* Fraction of pages in a relation that vacuum can eagerly scan and fail
diff --git a/src/test/regress/expected/reloptions.out b/src/test/regress/expected/reloptions.out
index 9de19b4e3f1..1c99f79ab01 100644
--- a/src/test/regress/expected/reloptions.out
+++ b/src/test/regress/expected/reloptions.out
@@ -98,6 +98,42 @@ SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass;
{fillfactor=13,autovacuum_enabled=false}
(1 row)
+-- Tests for future (FIXME) ternary options
+-- behave as boolean option: accept unassigned name and truncated value
+DROP TABLE reloptions_test;
+CREATE TABLE reloptions_test(i INT) WITH (vacuum_truncate);
+SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass;
+ reloptions
+------------------------
+ {vacuum_truncate=true}
+(1 row)
+
+DROP TABLE reloptions_test;
+CREATE TABLE reloptions_test(i INT) WITH (vacuum_truncate=FaLS);
+SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass;
+ reloptions
+------------------------
+ {vacuum_truncate=fals}
+(1 row)
+
+-- preferred "true" alias is used when storing
+DROP TABLE reloptions_test;
+CREATE TABLE reloptions_test(i INT) WITH (vacuum_index_cleanup=on);
+SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass;
+ reloptions
+---------------------------
+ {vacuum_index_cleanup=on}
+(1 row)
+
+-- custom "third" value is available
+DROP TABLE reloptions_test;
+CREATE TABLE reloptions_test(i INT) WITH (vacuum_index_cleanup=auto);
+SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass;
+ reloptions
+-----------------------------
+ {vacuum_index_cleanup=auto}
+(1 row)
+
-- Test vacuum_truncate option
DROP TABLE reloptions_test;
CREATE TEMP TABLE reloptions_test(i INT NOT NULL, j text)
diff --git a/src/test/regress/sql/reloptions.sql b/src/test/regress/sql/reloptions.sql
index 24fbe0b478d..f5980dafcbc 100644
--- a/src/test/regress/sql/reloptions.sql
+++ b/src/test/regress/sql/reloptions.sql
@@ -59,6 +59,27 @@ UPDATE pg_class
ALTER TABLE reloptions_test RESET (illegal_option);
SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass;
+-- Tests for future (FIXME) ternary options
+
+-- behave as boolean option: accept unassigned name and truncated value
+DROP TABLE reloptions_test;
+CREATE TABLE reloptions_test(i INT) WITH (vacuum_truncate);
+SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass;
+
+DROP TABLE reloptions_test;
+CREATE TABLE reloptions_test(i INT) WITH (vacuum_truncate=FaLS);
+SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass;
+
+-- preferred "true" alias is used when storing
+DROP TABLE reloptions_test;
+CREATE TABLE reloptions_test(i INT) WITH (vacuum_index_cleanup=on);
+SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass;
+
+-- custom "third" value is available
+DROP TABLE reloptions_test;
+CREATE TABLE reloptions_test(i INT) WITH (vacuum_index_cleanup=auto);
+SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass;
+
-- Test vacuum_truncate option
DROP TABLE reloptions_test;
diff --git a/src/tools/pgindent/typedefs.list b/src/tools/pgindent/typedefs.list
index 3f3a888fd0e..1c8610fd46c 100644
--- a/src/tools/pgindent/typedefs.list
+++ b/src/tools/pgindent/typedefs.list
@@ -3952,6 +3952,7 @@ pg_sha512_ctx
pg_snapshot
pg_special_case
pg_stack_base_t
+pg_ternary
pg_time_t
pg_time_usec_t
pg_tz
@@ -4079,6 +4080,7 @@ relopt_kind
relopt_parse_elt
relopt_real
relopt_string
+relopt_ternary
relopt_type
relopt_value
relopts_validator
--
2.47.3
--c36t3fndxhkzbl5z--
^ permalink raw reply [nested|flat] 868+ messages in thread
* [PATCH v3] Introduce ternary reloptions
@ 2026-01-16 15:04 Álvaro Herrera <[email protected]>
0 siblings, 0 replies; 868+ messages in thread
From: Álvaro Herrera @ 2026-01-16 15:04 UTC (permalink / raw)
Introduce ternary reloption as a replacement for current
`vacuum_truncate` implementation. Remove the `vacuum_truncate_set`
separate flag and use TERNARY_UNSET instead.
This could also be used for other options such as `vacuum_index_cleanup`
and `buffering`, but lets get the scaffolding in first.
Discussion: https://postgr.es/m/3474141.usfYGdeWWP@thinkpad-pgpro
---
src/backend/access/common/reloptions.c | 137 ++++++++++++++++++-----
src/backend/commands/vacuum.c | 4 +-
src/include/access/reloptions.h | 27 ++---
src/include/postgres.h | 15 +++
src/include/utils/rel.h | 3 +-
src/test/regress/expected/reloptions.out | 36 ++++++
src/test/regress/sql/reloptions.sql | 21 ++++
src/tools/pgindent/typedefs.list | 2 +
8 files changed, 202 insertions(+), 43 deletions(-)
diff --git a/src/backend/access/common/reloptions.c b/src/backend/access/common/reloptions.c
index 0b83f98ed5f..6f1f577581a 100644
--- a/src/backend/access/common/reloptions.c
+++ b/src/backend/access/common/reloptions.c
@@ -40,9 +40,9 @@
*
* To add an option:
*
- * (i) decide on a type (bool, integer, real, enum, string), name, default
- * value, upper and lower bounds (if applicable); for strings, consider a
- * validation routine.
+ * (i) decide on a type (bool, ternary, integer, real, enum, string), name,
+ * default value, upper and lower bounds (if applicable); for strings,
+ * consider a validation routine.
* (ii) add a record below (or use add_<type>_reloption).
* (iii) add it to the appropriate options struct (perhaps StdRdOptions)
* (iv) add it to the appropriate handling routine (perhaps
@@ -147,15 +147,6 @@ static relopt_bool boolRelOpts[] =
},
false
},
- {
- {
- "vacuum_truncate",
- "Enables vacuum to truncate empty pages at the end of this table",
- RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
- ShareUpdateExclusiveLock
- },
- true
- },
{
{
"deduplicate_items",
@@ -170,6 +161,25 @@ static relopt_bool boolRelOpts[] =
{{NULL}}
};
+static relopt_ternary ternaryRelOpts[] =
+{
+ {
+ {
+ "vacuum_truncate",
+ "Enables vacuum to truncate empty pages at the end of this table",
+ RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
+ ShareUpdateExclusiveLock
+ },
+ TERNARY_UNSET
+ },
+ /* list terminator */
+ {
+ {
+ NULL
+ }
+ }
+};
+
static relopt_int intRelOpts[] =
{
{
@@ -609,6 +619,13 @@ initialize_reloptions(void)
boolRelOpts[i].gen.lockmode));
j++;
}
+ for (i = 0; ternaryRelOpts[i].gen.name; i++)
+ {
+ Assert(DoLockModesConflict(ternaryRelOpts[i].gen.lockmode,
+ ternaryRelOpts[i].gen.lockmode));
+ j++;
+ }
+
for (i = 0; intRelOpts[i].gen.name; i++)
{
Assert(DoLockModesConflict(intRelOpts[i].gen.lockmode,
@@ -649,6 +666,14 @@ initialize_reloptions(void)
j++;
}
+ for (i = 0; ternaryRelOpts[i].gen.name; i++)
+ {
+ relOpts[j] = &ternaryRelOpts[i].gen;
+ relOpts[j]->type = RELOPT_TYPE_TERNARY;
+ relOpts[j]->namelen = strlen(relOpts[j]->name);
+ j++;
+ }
+
for (i = 0; intRelOpts[i].gen.name; i++)
{
relOpts[j] = &intRelOpts[i].gen;
@@ -809,6 +834,9 @@ allocate_reloption(bits32 kinds, int type, const char *name, const char *desc,
case RELOPT_TYPE_BOOL:
size = sizeof(relopt_bool);
break;
+ case RELOPT_TYPE_TERNARY:
+ size = sizeof(relopt_ternary);
+ break;
case RELOPT_TYPE_INT:
size = sizeof(relopt_int);
break;
@@ -892,6 +920,57 @@ add_local_bool_reloption(local_relopts *relopts, const char *name,
add_local_reloption(relopts, (relopt_gen *) newoption, offset);
}
+/*
+ * init_ternary_reloption
+ * Allocate and initialize a new ternary reloption
+ */
+static relopt_ternary *
+init_ternary_reloption(bits32 kinds, const char *name, const char *desc,
+ pg_ternary default_val, LOCKMODE lockmode)
+{
+ relopt_ternary *newoption;
+
+ newoption = (relopt_ternary *)
+ allocate_reloption(kinds, RELOPT_TYPE_TERNARY, name, desc, lockmode);
+ newoption->default_val = default_val;
+
+ return newoption;
+}
+
+/*
+ * add_ternary_reloption
+ * Add a new ternary reloption
+ */
+void
+add_ternary_reloption(bits32 kinds, const char *name, const char *desc,
+ pg_ternary default_val, LOCKMODE lockmode)
+{
+ relopt_ternary *newoption;
+
+ newoption =
+ init_ternary_reloption(kinds, name, desc, default_val, lockmode);
+
+ add_reloption((relopt_gen *) newoption);
+}
+
+/*
+ * add_local_ternary_reloption
+ * Add a new ternary local reloption
+ *
+ * 'offset' is offset of ternary-typed field.
+ */
+void
+add_local_ternary_reloption(local_relopts *relopts, const char *name,
+ const char *desc, pg_ternary default_val,
+ int offset)
+{
+ relopt_ternary *newoption;
+
+ newoption =
+ init_ternary_reloption(RELOPT_KIND_LOCAL, name, desc, default_val, 0);
+
+ add_local_reloption(relopts, (relopt_gen *) newoption, offset);
+}
/*
* init_real_reloption
@@ -1626,6 +1705,19 @@ parse_one_reloption(relopt_value *option, char *text_str, int text_len,
option->gen->name, value)));
}
break;
+ case RELOPT_TYPE_TERNARY:
+ {
+ bool b;
+
+ parsed = parse_bool(value, &b);
+ option->values.ternary_val = b ? TERNARY_TRUE : TERNARY_FALSE;
+ if (validate && !parsed)
+ ereport(ERROR,
+ (errcode(ERRCODE_INVALID_PARAMETER_VALUE),
+ errmsg("invalid value for ternary option \"%s\": %s",
+ option->gen->name, value)));
+ }
+ break;
case RELOPT_TYPE_INT:
{
relopt_int *optint = (relopt_int *) option->gen;
@@ -1789,17 +1881,6 @@ fillRelOptions(void *rdopts, Size basesize,
char *itempos = ((char *) rdopts) + elems[j].offset;
char *string_val;
- /*
- * If isset_offset is provided, store whether the reloption is
- * set there.
- */
- if (elems[j].isset_offset > 0)
- {
- char *setpos = ((char *) rdopts) + elems[j].isset_offset;
-
- *(bool *) setpos = options[i].isset;
- }
-
switch (options[i].gen->type)
{
case RELOPT_TYPE_BOOL:
@@ -1807,6 +1888,11 @@ fillRelOptions(void *rdopts, Size basesize,
options[i].values.bool_val :
((relopt_bool *) options[i].gen)->default_val;
break;
+ case RELOPT_TYPE_TERNARY:
+ *(pg_ternary *) itempos = options[i].isset ?
+ options[i].values.ternary_val :
+ ((relopt_ternary *) options[i].gen)->default_val;
+ break;
case RELOPT_TYPE_INT:
*(int *) itempos = options[i].isset ?
options[i].values.int_val :
@@ -1923,8 +2009,8 @@ default_reloptions(Datum reloptions, bool validate, relopt_kind kind)
offsetof(StdRdOptions, parallel_workers)},
{"vacuum_index_cleanup", RELOPT_TYPE_ENUM,
offsetof(StdRdOptions, vacuum_index_cleanup)},
- {"vacuum_truncate", RELOPT_TYPE_BOOL,
- offsetof(StdRdOptions, vacuum_truncate), offsetof(StdRdOptions, vacuum_truncate_set)},
+ {"vacuum_truncate", RELOPT_TYPE_TERNARY,
+ offsetof(StdRdOptions, vacuum_truncate)},
{"vacuum_max_eager_freeze_failure_rate", RELOPT_TYPE_REAL,
offsetof(StdRdOptions, vacuum_max_eager_freeze_failure_rate)}
};
@@ -2004,7 +2090,6 @@ build_local_reloptions(local_relopts *relopts, Datum options, bool validate)
elems[i].optname = opt->option->name;
elems[i].opttype = opt->option->type;
elems[i].offset = opt->offset;
- elems[i].isset_offset = 0; /* not supported for local relopts yet */
i++;
}
diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c
index aa4fbec143f..696eab9bd97 100644
--- a/src/backend/commands/vacuum.c
+++ b/src/backend/commands/vacuum.c
@@ -2224,9 +2224,9 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams params,
{
StdRdOptions *opts = (StdRdOptions *) rel->rd_options;
- if (opts && opts->vacuum_truncate_set)
+ if (opts && opts->vacuum_truncate != TERNARY_UNSET)
{
- if (opts->vacuum_truncate)
+ if (opts->vacuum_truncate == TERNARY_TRUE)
params.truncate = VACOPTVALUE_ENABLED;
else
params.truncate = VACOPTVALUE_DISABLED;
diff --git a/src/include/access/reloptions.h b/src/include/access/reloptions.h
index 2f08e1b0cf0..dfbef2babf2 100644
--- a/src/include/access/reloptions.h
+++ b/src/include/access/reloptions.h
@@ -29,6 +29,7 @@
typedef enum relopt_type
{
RELOPT_TYPE_BOOL,
+ RELOPT_TYPE_TERNARY, /* on, off, unset */
RELOPT_TYPE_INT,
RELOPT_TYPE_REAL,
RELOPT_TYPE_ENUM,
@@ -80,6 +81,7 @@ typedef struct relopt_value
union
{
bool bool_val;
+ pg_ternary ternary_val;
int int_val;
double real_val;
int enum_val;
@@ -94,6 +96,12 @@ typedef struct relopt_bool
bool default_val;
} relopt_bool;
+typedef struct relopt_ternary
+{
+ relopt_gen gen;
+ int default_val;
+} relopt_ternary;
+
typedef struct relopt_int
{
relopt_gen gen;
@@ -152,19 +160,6 @@ typedef struct
const char *optname; /* option's name */
relopt_type opttype; /* option's datatype */
int offset; /* offset of field in result struct */
-
- /*
- * isset_offset is an optional offset of a field in the result struct that
- * stores whether the option is explicitly set for the relation or if it
- * just picked up the default value. In most cases, this can be
- * accomplished by giving the reloption a special out-of-range default
- * value (e.g., some integer reloptions use -2), but this isn't always
- * possible. For example, a Boolean reloption cannot be given an
- * out-of-range default, so we need another way to discover the source of
- * its value. This offset is only used if given a value greater than
- * zero.
- */
- int isset_offset;
} relopt_parse_elt;
/* Local reloption definition */
@@ -195,6 +190,9 @@ typedef struct local_relopts
extern relopt_kind add_reloption_kind(void);
extern void add_bool_reloption(bits32 kinds, const char *name, const char *desc,
bool default_val, LOCKMODE lockmode);
+extern void add_ternary_reloption(bits32 kinds, const char *name,
+ const char *desc, pg_ternary default_val,
+ LOCKMODE lockmode);
extern void add_int_reloption(bits32 kinds, const char *name, const char *desc,
int default_val, int min_val, int max_val,
LOCKMODE lockmode);
@@ -214,6 +212,9 @@ extern void register_reloptions_validator(local_relopts *relopts,
extern void add_local_bool_reloption(local_relopts *relopts, const char *name,
const char *desc, bool default_val,
int offset);
+extern void add_local_ternary_reloption(local_relopts *relopts,
+ const char *name, const char *desc,
+ pg_ternary default_val, int offset);
extern void add_local_int_reloption(local_relopts *relopts, const char *name,
const char *desc, int default_val,
int min_val, int max_val, int offset);
diff --git a/src/include/postgres.h b/src/include/postgres.h
index 7d93fbce709..47c88780776 100644
--- a/src/include/postgres.h
+++ b/src/include/postgres.h
@@ -543,6 +543,21 @@ Float8GetDatum(float8 X)
* ----------------------------------------------------------------
*/
+/*
+ * pg_ternary
+ * Boolean value with an extra "unset" value
+ *
+ * This enum can be used for values that want to distinguish between true,
+ * false, and unset.
+*/
+
+typedef enum pg_ternary
+{
+ TERNARY_FALSE = 0,
+ TERNARY_TRUE = 1,
+ TERNARY_UNSET = -1
+} pg_ternary;
+
/*
* NON_EXEC_STATIC: It's sometimes useful to define a variable or function
* that is normally static but extern when using EXEC_BACKEND (see
diff --git a/src/include/utils/rel.h b/src/include/utils/rel.h
index d03ab247788..5feb18a5373 100644
--- a/src/include/utils/rel.h
+++ b/src/include/utils/rel.h
@@ -347,8 +347,7 @@ typedef struct StdRdOptions
bool user_catalog_table; /* use as an additional catalog relation */
int parallel_workers; /* max number of parallel workers */
StdRdOptIndexCleanup vacuum_index_cleanup; /* controls index vacuuming */
- bool vacuum_truncate; /* enables vacuum to truncate a relation */
- bool vacuum_truncate_set; /* whether vacuum_truncate is set */
+ pg_ternary vacuum_truncate; /* enables vacuum to truncate a relation */
/*
* Fraction of pages in a relation that vacuum can eagerly scan and fail
diff --git a/src/test/regress/expected/reloptions.out b/src/test/regress/expected/reloptions.out
index 9de19b4e3f1..1c99f79ab01 100644
--- a/src/test/regress/expected/reloptions.out
+++ b/src/test/regress/expected/reloptions.out
@@ -98,6 +98,42 @@ SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass;
{fillfactor=13,autovacuum_enabled=false}
(1 row)
+-- Tests for future (FIXME) ternary options
+-- behave as boolean option: accept unassigned name and truncated value
+DROP TABLE reloptions_test;
+CREATE TABLE reloptions_test(i INT) WITH (vacuum_truncate);
+SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass;
+ reloptions
+------------------------
+ {vacuum_truncate=true}
+(1 row)
+
+DROP TABLE reloptions_test;
+CREATE TABLE reloptions_test(i INT) WITH (vacuum_truncate=FaLS);
+SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass;
+ reloptions
+------------------------
+ {vacuum_truncate=fals}
+(1 row)
+
+-- preferred "true" alias is used when storing
+DROP TABLE reloptions_test;
+CREATE TABLE reloptions_test(i INT) WITH (vacuum_index_cleanup=on);
+SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass;
+ reloptions
+---------------------------
+ {vacuum_index_cleanup=on}
+(1 row)
+
+-- custom "third" value is available
+DROP TABLE reloptions_test;
+CREATE TABLE reloptions_test(i INT) WITH (vacuum_index_cleanup=auto);
+SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass;
+ reloptions
+-----------------------------
+ {vacuum_index_cleanup=auto}
+(1 row)
+
-- Test vacuum_truncate option
DROP TABLE reloptions_test;
CREATE TEMP TABLE reloptions_test(i INT NOT NULL, j text)
diff --git a/src/test/regress/sql/reloptions.sql b/src/test/regress/sql/reloptions.sql
index 24fbe0b478d..f5980dafcbc 100644
--- a/src/test/regress/sql/reloptions.sql
+++ b/src/test/regress/sql/reloptions.sql
@@ -59,6 +59,27 @@ UPDATE pg_class
ALTER TABLE reloptions_test RESET (illegal_option);
SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass;
+-- Tests for future (FIXME) ternary options
+
+-- behave as boolean option: accept unassigned name and truncated value
+DROP TABLE reloptions_test;
+CREATE TABLE reloptions_test(i INT) WITH (vacuum_truncate);
+SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass;
+
+DROP TABLE reloptions_test;
+CREATE TABLE reloptions_test(i INT) WITH (vacuum_truncate=FaLS);
+SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass;
+
+-- preferred "true" alias is used when storing
+DROP TABLE reloptions_test;
+CREATE TABLE reloptions_test(i INT) WITH (vacuum_index_cleanup=on);
+SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass;
+
+-- custom "third" value is available
+DROP TABLE reloptions_test;
+CREATE TABLE reloptions_test(i INT) WITH (vacuum_index_cleanup=auto);
+SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass;
+
-- Test vacuum_truncate option
DROP TABLE reloptions_test;
diff --git a/src/tools/pgindent/typedefs.list b/src/tools/pgindent/typedefs.list
index 3f3a888fd0e..1c8610fd46c 100644
--- a/src/tools/pgindent/typedefs.list
+++ b/src/tools/pgindent/typedefs.list
@@ -3952,6 +3952,7 @@ pg_sha512_ctx
pg_snapshot
pg_special_case
pg_stack_base_t
+pg_ternary
pg_time_t
pg_time_usec_t
pg_tz
@@ -4079,6 +4080,7 @@ relopt_kind
relopt_parse_elt
relopt_real
relopt_string
+relopt_ternary
relopt_type
relopt_value
relopts_validator
--
2.47.3
--c36t3fndxhkzbl5z--
^ permalink raw reply [nested|flat] 868+ messages in thread
* [PATCH v3] Introduce ternary reloptions
@ 2026-01-16 15:04 Álvaro Herrera <[email protected]>
0 siblings, 0 replies; 868+ messages in thread
From: Álvaro Herrera @ 2026-01-16 15:04 UTC (permalink / raw)
Introduce ternary reloption as a replacement for current
`vacuum_truncate` implementation. Remove the `vacuum_truncate_set`
separate flag and use TERNARY_UNSET instead.
This could also be used for other options such as `vacuum_index_cleanup`
and `buffering`, but lets get the scaffolding in first.
Discussion: https://postgr.es/m/3474141.usfYGdeWWP@thinkpad-pgpro
---
src/backend/access/common/reloptions.c | 137 ++++++++++++++++++-----
src/backend/commands/vacuum.c | 4 +-
src/include/access/reloptions.h | 27 ++---
src/include/postgres.h | 15 +++
src/include/utils/rel.h | 3 +-
src/test/regress/expected/reloptions.out | 36 ++++++
src/test/regress/sql/reloptions.sql | 21 ++++
src/tools/pgindent/typedefs.list | 2 +
8 files changed, 202 insertions(+), 43 deletions(-)
diff --git a/src/backend/access/common/reloptions.c b/src/backend/access/common/reloptions.c
index 0b83f98ed5f..6f1f577581a 100644
--- a/src/backend/access/common/reloptions.c
+++ b/src/backend/access/common/reloptions.c
@@ -40,9 +40,9 @@
*
* To add an option:
*
- * (i) decide on a type (bool, integer, real, enum, string), name, default
- * value, upper and lower bounds (if applicable); for strings, consider a
- * validation routine.
+ * (i) decide on a type (bool, ternary, integer, real, enum, string), name,
+ * default value, upper and lower bounds (if applicable); for strings,
+ * consider a validation routine.
* (ii) add a record below (or use add_<type>_reloption).
* (iii) add it to the appropriate options struct (perhaps StdRdOptions)
* (iv) add it to the appropriate handling routine (perhaps
@@ -147,15 +147,6 @@ static relopt_bool boolRelOpts[] =
},
false
},
- {
- {
- "vacuum_truncate",
- "Enables vacuum to truncate empty pages at the end of this table",
- RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
- ShareUpdateExclusiveLock
- },
- true
- },
{
{
"deduplicate_items",
@@ -170,6 +161,25 @@ static relopt_bool boolRelOpts[] =
{{NULL}}
};
+static relopt_ternary ternaryRelOpts[] =
+{
+ {
+ {
+ "vacuum_truncate",
+ "Enables vacuum to truncate empty pages at the end of this table",
+ RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
+ ShareUpdateExclusiveLock
+ },
+ TERNARY_UNSET
+ },
+ /* list terminator */
+ {
+ {
+ NULL
+ }
+ }
+};
+
static relopt_int intRelOpts[] =
{
{
@@ -609,6 +619,13 @@ initialize_reloptions(void)
boolRelOpts[i].gen.lockmode));
j++;
}
+ for (i = 0; ternaryRelOpts[i].gen.name; i++)
+ {
+ Assert(DoLockModesConflict(ternaryRelOpts[i].gen.lockmode,
+ ternaryRelOpts[i].gen.lockmode));
+ j++;
+ }
+
for (i = 0; intRelOpts[i].gen.name; i++)
{
Assert(DoLockModesConflict(intRelOpts[i].gen.lockmode,
@@ -649,6 +666,14 @@ initialize_reloptions(void)
j++;
}
+ for (i = 0; ternaryRelOpts[i].gen.name; i++)
+ {
+ relOpts[j] = &ternaryRelOpts[i].gen;
+ relOpts[j]->type = RELOPT_TYPE_TERNARY;
+ relOpts[j]->namelen = strlen(relOpts[j]->name);
+ j++;
+ }
+
for (i = 0; intRelOpts[i].gen.name; i++)
{
relOpts[j] = &intRelOpts[i].gen;
@@ -809,6 +834,9 @@ allocate_reloption(bits32 kinds, int type, const char *name, const char *desc,
case RELOPT_TYPE_BOOL:
size = sizeof(relopt_bool);
break;
+ case RELOPT_TYPE_TERNARY:
+ size = sizeof(relopt_ternary);
+ break;
case RELOPT_TYPE_INT:
size = sizeof(relopt_int);
break;
@@ -892,6 +920,57 @@ add_local_bool_reloption(local_relopts *relopts, const char *name,
add_local_reloption(relopts, (relopt_gen *) newoption, offset);
}
+/*
+ * init_ternary_reloption
+ * Allocate and initialize a new ternary reloption
+ */
+static relopt_ternary *
+init_ternary_reloption(bits32 kinds, const char *name, const char *desc,
+ pg_ternary default_val, LOCKMODE lockmode)
+{
+ relopt_ternary *newoption;
+
+ newoption = (relopt_ternary *)
+ allocate_reloption(kinds, RELOPT_TYPE_TERNARY, name, desc, lockmode);
+ newoption->default_val = default_val;
+
+ return newoption;
+}
+
+/*
+ * add_ternary_reloption
+ * Add a new ternary reloption
+ */
+void
+add_ternary_reloption(bits32 kinds, const char *name, const char *desc,
+ pg_ternary default_val, LOCKMODE lockmode)
+{
+ relopt_ternary *newoption;
+
+ newoption =
+ init_ternary_reloption(kinds, name, desc, default_val, lockmode);
+
+ add_reloption((relopt_gen *) newoption);
+}
+
+/*
+ * add_local_ternary_reloption
+ * Add a new ternary local reloption
+ *
+ * 'offset' is offset of ternary-typed field.
+ */
+void
+add_local_ternary_reloption(local_relopts *relopts, const char *name,
+ const char *desc, pg_ternary default_val,
+ int offset)
+{
+ relopt_ternary *newoption;
+
+ newoption =
+ init_ternary_reloption(RELOPT_KIND_LOCAL, name, desc, default_val, 0);
+
+ add_local_reloption(relopts, (relopt_gen *) newoption, offset);
+}
/*
* init_real_reloption
@@ -1626,6 +1705,19 @@ parse_one_reloption(relopt_value *option, char *text_str, int text_len,
option->gen->name, value)));
}
break;
+ case RELOPT_TYPE_TERNARY:
+ {
+ bool b;
+
+ parsed = parse_bool(value, &b);
+ option->values.ternary_val = b ? TERNARY_TRUE : TERNARY_FALSE;
+ if (validate && !parsed)
+ ereport(ERROR,
+ (errcode(ERRCODE_INVALID_PARAMETER_VALUE),
+ errmsg("invalid value for ternary option \"%s\": %s",
+ option->gen->name, value)));
+ }
+ break;
case RELOPT_TYPE_INT:
{
relopt_int *optint = (relopt_int *) option->gen;
@@ -1789,17 +1881,6 @@ fillRelOptions(void *rdopts, Size basesize,
char *itempos = ((char *) rdopts) + elems[j].offset;
char *string_val;
- /*
- * If isset_offset is provided, store whether the reloption is
- * set there.
- */
- if (elems[j].isset_offset > 0)
- {
- char *setpos = ((char *) rdopts) + elems[j].isset_offset;
-
- *(bool *) setpos = options[i].isset;
- }
-
switch (options[i].gen->type)
{
case RELOPT_TYPE_BOOL:
@@ -1807,6 +1888,11 @@ fillRelOptions(void *rdopts, Size basesize,
options[i].values.bool_val :
((relopt_bool *) options[i].gen)->default_val;
break;
+ case RELOPT_TYPE_TERNARY:
+ *(pg_ternary *) itempos = options[i].isset ?
+ options[i].values.ternary_val :
+ ((relopt_ternary *) options[i].gen)->default_val;
+ break;
case RELOPT_TYPE_INT:
*(int *) itempos = options[i].isset ?
options[i].values.int_val :
@@ -1923,8 +2009,8 @@ default_reloptions(Datum reloptions, bool validate, relopt_kind kind)
offsetof(StdRdOptions, parallel_workers)},
{"vacuum_index_cleanup", RELOPT_TYPE_ENUM,
offsetof(StdRdOptions, vacuum_index_cleanup)},
- {"vacuum_truncate", RELOPT_TYPE_BOOL,
- offsetof(StdRdOptions, vacuum_truncate), offsetof(StdRdOptions, vacuum_truncate_set)},
+ {"vacuum_truncate", RELOPT_TYPE_TERNARY,
+ offsetof(StdRdOptions, vacuum_truncate)},
{"vacuum_max_eager_freeze_failure_rate", RELOPT_TYPE_REAL,
offsetof(StdRdOptions, vacuum_max_eager_freeze_failure_rate)}
};
@@ -2004,7 +2090,6 @@ build_local_reloptions(local_relopts *relopts, Datum options, bool validate)
elems[i].optname = opt->option->name;
elems[i].opttype = opt->option->type;
elems[i].offset = opt->offset;
- elems[i].isset_offset = 0; /* not supported for local relopts yet */
i++;
}
diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c
index aa4fbec143f..696eab9bd97 100644
--- a/src/backend/commands/vacuum.c
+++ b/src/backend/commands/vacuum.c
@@ -2224,9 +2224,9 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams params,
{
StdRdOptions *opts = (StdRdOptions *) rel->rd_options;
- if (opts && opts->vacuum_truncate_set)
+ if (opts && opts->vacuum_truncate != TERNARY_UNSET)
{
- if (opts->vacuum_truncate)
+ if (opts->vacuum_truncate == TERNARY_TRUE)
params.truncate = VACOPTVALUE_ENABLED;
else
params.truncate = VACOPTVALUE_DISABLED;
diff --git a/src/include/access/reloptions.h b/src/include/access/reloptions.h
index 2f08e1b0cf0..dfbef2babf2 100644
--- a/src/include/access/reloptions.h
+++ b/src/include/access/reloptions.h
@@ -29,6 +29,7 @@
typedef enum relopt_type
{
RELOPT_TYPE_BOOL,
+ RELOPT_TYPE_TERNARY, /* on, off, unset */
RELOPT_TYPE_INT,
RELOPT_TYPE_REAL,
RELOPT_TYPE_ENUM,
@@ -80,6 +81,7 @@ typedef struct relopt_value
union
{
bool bool_val;
+ pg_ternary ternary_val;
int int_val;
double real_val;
int enum_val;
@@ -94,6 +96,12 @@ typedef struct relopt_bool
bool default_val;
} relopt_bool;
+typedef struct relopt_ternary
+{
+ relopt_gen gen;
+ int default_val;
+} relopt_ternary;
+
typedef struct relopt_int
{
relopt_gen gen;
@@ -152,19 +160,6 @@ typedef struct
const char *optname; /* option's name */
relopt_type opttype; /* option's datatype */
int offset; /* offset of field in result struct */
-
- /*
- * isset_offset is an optional offset of a field in the result struct that
- * stores whether the option is explicitly set for the relation or if it
- * just picked up the default value. In most cases, this can be
- * accomplished by giving the reloption a special out-of-range default
- * value (e.g., some integer reloptions use -2), but this isn't always
- * possible. For example, a Boolean reloption cannot be given an
- * out-of-range default, so we need another way to discover the source of
- * its value. This offset is only used if given a value greater than
- * zero.
- */
- int isset_offset;
} relopt_parse_elt;
/* Local reloption definition */
@@ -195,6 +190,9 @@ typedef struct local_relopts
extern relopt_kind add_reloption_kind(void);
extern void add_bool_reloption(bits32 kinds, const char *name, const char *desc,
bool default_val, LOCKMODE lockmode);
+extern void add_ternary_reloption(bits32 kinds, const char *name,
+ const char *desc, pg_ternary default_val,
+ LOCKMODE lockmode);
extern void add_int_reloption(bits32 kinds, const char *name, const char *desc,
int default_val, int min_val, int max_val,
LOCKMODE lockmode);
@@ -214,6 +212,9 @@ extern void register_reloptions_validator(local_relopts *relopts,
extern void add_local_bool_reloption(local_relopts *relopts, const char *name,
const char *desc, bool default_val,
int offset);
+extern void add_local_ternary_reloption(local_relopts *relopts,
+ const char *name, const char *desc,
+ pg_ternary default_val, int offset);
extern void add_local_int_reloption(local_relopts *relopts, const char *name,
const char *desc, int default_val,
int min_val, int max_val, int offset);
diff --git a/src/include/postgres.h b/src/include/postgres.h
index 7d93fbce709..47c88780776 100644
--- a/src/include/postgres.h
+++ b/src/include/postgres.h
@@ -543,6 +543,21 @@ Float8GetDatum(float8 X)
* ----------------------------------------------------------------
*/
+/*
+ * pg_ternary
+ * Boolean value with an extra "unset" value
+ *
+ * This enum can be used for values that want to distinguish between true,
+ * false, and unset.
+*/
+
+typedef enum pg_ternary
+{
+ TERNARY_FALSE = 0,
+ TERNARY_TRUE = 1,
+ TERNARY_UNSET = -1
+} pg_ternary;
+
/*
* NON_EXEC_STATIC: It's sometimes useful to define a variable or function
* that is normally static but extern when using EXEC_BACKEND (see
diff --git a/src/include/utils/rel.h b/src/include/utils/rel.h
index d03ab247788..5feb18a5373 100644
--- a/src/include/utils/rel.h
+++ b/src/include/utils/rel.h
@@ -347,8 +347,7 @@ typedef struct StdRdOptions
bool user_catalog_table; /* use as an additional catalog relation */
int parallel_workers; /* max number of parallel workers */
StdRdOptIndexCleanup vacuum_index_cleanup; /* controls index vacuuming */
- bool vacuum_truncate; /* enables vacuum to truncate a relation */
- bool vacuum_truncate_set; /* whether vacuum_truncate is set */
+ pg_ternary vacuum_truncate; /* enables vacuum to truncate a relation */
/*
* Fraction of pages in a relation that vacuum can eagerly scan and fail
diff --git a/src/test/regress/expected/reloptions.out b/src/test/regress/expected/reloptions.out
index 9de19b4e3f1..1c99f79ab01 100644
--- a/src/test/regress/expected/reloptions.out
+++ b/src/test/regress/expected/reloptions.out
@@ -98,6 +98,42 @@ SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass;
{fillfactor=13,autovacuum_enabled=false}
(1 row)
+-- Tests for future (FIXME) ternary options
+-- behave as boolean option: accept unassigned name and truncated value
+DROP TABLE reloptions_test;
+CREATE TABLE reloptions_test(i INT) WITH (vacuum_truncate);
+SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass;
+ reloptions
+------------------------
+ {vacuum_truncate=true}
+(1 row)
+
+DROP TABLE reloptions_test;
+CREATE TABLE reloptions_test(i INT) WITH (vacuum_truncate=FaLS);
+SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass;
+ reloptions
+------------------------
+ {vacuum_truncate=fals}
+(1 row)
+
+-- preferred "true" alias is used when storing
+DROP TABLE reloptions_test;
+CREATE TABLE reloptions_test(i INT) WITH (vacuum_index_cleanup=on);
+SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass;
+ reloptions
+---------------------------
+ {vacuum_index_cleanup=on}
+(1 row)
+
+-- custom "third" value is available
+DROP TABLE reloptions_test;
+CREATE TABLE reloptions_test(i INT) WITH (vacuum_index_cleanup=auto);
+SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass;
+ reloptions
+-----------------------------
+ {vacuum_index_cleanup=auto}
+(1 row)
+
-- Test vacuum_truncate option
DROP TABLE reloptions_test;
CREATE TEMP TABLE reloptions_test(i INT NOT NULL, j text)
diff --git a/src/test/regress/sql/reloptions.sql b/src/test/regress/sql/reloptions.sql
index 24fbe0b478d..f5980dafcbc 100644
--- a/src/test/regress/sql/reloptions.sql
+++ b/src/test/regress/sql/reloptions.sql
@@ -59,6 +59,27 @@ UPDATE pg_class
ALTER TABLE reloptions_test RESET (illegal_option);
SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass;
+-- Tests for future (FIXME) ternary options
+
+-- behave as boolean option: accept unassigned name and truncated value
+DROP TABLE reloptions_test;
+CREATE TABLE reloptions_test(i INT) WITH (vacuum_truncate);
+SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass;
+
+DROP TABLE reloptions_test;
+CREATE TABLE reloptions_test(i INT) WITH (vacuum_truncate=FaLS);
+SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass;
+
+-- preferred "true" alias is used when storing
+DROP TABLE reloptions_test;
+CREATE TABLE reloptions_test(i INT) WITH (vacuum_index_cleanup=on);
+SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass;
+
+-- custom "third" value is available
+DROP TABLE reloptions_test;
+CREATE TABLE reloptions_test(i INT) WITH (vacuum_index_cleanup=auto);
+SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass;
+
-- Test vacuum_truncate option
DROP TABLE reloptions_test;
diff --git a/src/tools/pgindent/typedefs.list b/src/tools/pgindent/typedefs.list
index 3f3a888fd0e..1c8610fd46c 100644
--- a/src/tools/pgindent/typedefs.list
+++ b/src/tools/pgindent/typedefs.list
@@ -3952,6 +3952,7 @@ pg_sha512_ctx
pg_snapshot
pg_special_case
pg_stack_base_t
+pg_ternary
pg_time_t
pg_time_usec_t
pg_tz
@@ -4079,6 +4080,7 @@ relopt_kind
relopt_parse_elt
relopt_real
relopt_string
+relopt_ternary
relopt_type
relopt_value
relopts_validator
--
2.47.3
--c36t3fndxhkzbl5z--
^ permalink raw reply [nested|flat] 868+ messages in thread
* [PATCH v3] Introduce ternary reloptions
@ 2026-01-16 15:04 Álvaro Herrera <[email protected]>
0 siblings, 0 replies; 868+ messages in thread
From: Álvaro Herrera @ 2026-01-16 15:04 UTC (permalink / raw)
Introduce ternary reloption as a replacement for current
`vacuum_truncate` implementation. Remove the `vacuum_truncate_set`
separate flag and use TERNARY_UNSET instead.
This could also be used for other options such as `vacuum_index_cleanup`
and `buffering`, but lets get the scaffolding in first.
Discussion: https://postgr.es/m/3474141.usfYGdeWWP@thinkpad-pgpro
---
src/backend/access/common/reloptions.c | 137 ++++++++++++++++++-----
src/backend/commands/vacuum.c | 4 +-
src/include/access/reloptions.h | 27 ++---
src/include/postgres.h | 15 +++
src/include/utils/rel.h | 3 +-
src/test/regress/expected/reloptions.out | 36 ++++++
src/test/regress/sql/reloptions.sql | 21 ++++
src/tools/pgindent/typedefs.list | 2 +
8 files changed, 202 insertions(+), 43 deletions(-)
diff --git a/src/backend/access/common/reloptions.c b/src/backend/access/common/reloptions.c
index 0b83f98ed5f..6f1f577581a 100644
--- a/src/backend/access/common/reloptions.c
+++ b/src/backend/access/common/reloptions.c
@@ -40,9 +40,9 @@
*
* To add an option:
*
- * (i) decide on a type (bool, integer, real, enum, string), name, default
- * value, upper and lower bounds (if applicable); for strings, consider a
- * validation routine.
+ * (i) decide on a type (bool, ternary, integer, real, enum, string), name,
+ * default value, upper and lower bounds (if applicable); for strings,
+ * consider a validation routine.
* (ii) add a record below (or use add_<type>_reloption).
* (iii) add it to the appropriate options struct (perhaps StdRdOptions)
* (iv) add it to the appropriate handling routine (perhaps
@@ -147,15 +147,6 @@ static relopt_bool boolRelOpts[] =
},
false
},
- {
- {
- "vacuum_truncate",
- "Enables vacuum to truncate empty pages at the end of this table",
- RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
- ShareUpdateExclusiveLock
- },
- true
- },
{
{
"deduplicate_items",
@@ -170,6 +161,25 @@ static relopt_bool boolRelOpts[] =
{{NULL}}
};
+static relopt_ternary ternaryRelOpts[] =
+{
+ {
+ {
+ "vacuum_truncate",
+ "Enables vacuum to truncate empty pages at the end of this table",
+ RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
+ ShareUpdateExclusiveLock
+ },
+ TERNARY_UNSET
+ },
+ /* list terminator */
+ {
+ {
+ NULL
+ }
+ }
+};
+
static relopt_int intRelOpts[] =
{
{
@@ -609,6 +619,13 @@ initialize_reloptions(void)
boolRelOpts[i].gen.lockmode));
j++;
}
+ for (i = 0; ternaryRelOpts[i].gen.name; i++)
+ {
+ Assert(DoLockModesConflict(ternaryRelOpts[i].gen.lockmode,
+ ternaryRelOpts[i].gen.lockmode));
+ j++;
+ }
+
for (i = 0; intRelOpts[i].gen.name; i++)
{
Assert(DoLockModesConflict(intRelOpts[i].gen.lockmode,
@@ -649,6 +666,14 @@ initialize_reloptions(void)
j++;
}
+ for (i = 0; ternaryRelOpts[i].gen.name; i++)
+ {
+ relOpts[j] = &ternaryRelOpts[i].gen;
+ relOpts[j]->type = RELOPT_TYPE_TERNARY;
+ relOpts[j]->namelen = strlen(relOpts[j]->name);
+ j++;
+ }
+
for (i = 0; intRelOpts[i].gen.name; i++)
{
relOpts[j] = &intRelOpts[i].gen;
@@ -809,6 +834,9 @@ allocate_reloption(bits32 kinds, int type, const char *name, const char *desc,
case RELOPT_TYPE_BOOL:
size = sizeof(relopt_bool);
break;
+ case RELOPT_TYPE_TERNARY:
+ size = sizeof(relopt_ternary);
+ break;
case RELOPT_TYPE_INT:
size = sizeof(relopt_int);
break;
@@ -892,6 +920,57 @@ add_local_bool_reloption(local_relopts *relopts, const char *name,
add_local_reloption(relopts, (relopt_gen *) newoption, offset);
}
+/*
+ * init_ternary_reloption
+ * Allocate and initialize a new ternary reloption
+ */
+static relopt_ternary *
+init_ternary_reloption(bits32 kinds, const char *name, const char *desc,
+ pg_ternary default_val, LOCKMODE lockmode)
+{
+ relopt_ternary *newoption;
+
+ newoption = (relopt_ternary *)
+ allocate_reloption(kinds, RELOPT_TYPE_TERNARY, name, desc, lockmode);
+ newoption->default_val = default_val;
+
+ return newoption;
+}
+
+/*
+ * add_ternary_reloption
+ * Add a new ternary reloption
+ */
+void
+add_ternary_reloption(bits32 kinds, const char *name, const char *desc,
+ pg_ternary default_val, LOCKMODE lockmode)
+{
+ relopt_ternary *newoption;
+
+ newoption =
+ init_ternary_reloption(kinds, name, desc, default_val, lockmode);
+
+ add_reloption((relopt_gen *) newoption);
+}
+
+/*
+ * add_local_ternary_reloption
+ * Add a new ternary local reloption
+ *
+ * 'offset' is offset of ternary-typed field.
+ */
+void
+add_local_ternary_reloption(local_relopts *relopts, const char *name,
+ const char *desc, pg_ternary default_val,
+ int offset)
+{
+ relopt_ternary *newoption;
+
+ newoption =
+ init_ternary_reloption(RELOPT_KIND_LOCAL, name, desc, default_val, 0);
+
+ add_local_reloption(relopts, (relopt_gen *) newoption, offset);
+}
/*
* init_real_reloption
@@ -1626,6 +1705,19 @@ parse_one_reloption(relopt_value *option, char *text_str, int text_len,
option->gen->name, value)));
}
break;
+ case RELOPT_TYPE_TERNARY:
+ {
+ bool b;
+
+ parsed = parse_bool(value, &b);
+ option->values.ternary_val = b ? TERNARY_TRUE : TERNARY_FALSE;
+ if (validate && !parsed)
+ ereport(ERROR,
+ (errcode(ERRCODE_INVALID_PARAMETER_VALUE),
+ errmsg("invalid value for ternary option \"%s\": %s",
+ option->gen->name, value)));
+ }
+ break;
case RELOPT_TYPE_INT:
{
relopt_int *optint = (relopt_int *) option->gen;
@@ -1789,17 +1881,6 @@ fillRelOptions(void *rdopts, Size basesize,
char *itempos = ((char *) rdopts) + elems[j].offset;
char *string_val;
- /*
- * If isset_offset is provided, store whether the reloption is
- * set there.
- */
- if (elems[j].isset_offset > 0)
- {
- char *setpos = ((char *) rdopts) + elems[j].isset_offset;
-
- *(bool *) setpos = options[i].isset;
- }
-
switch (options[i].gen->type)
{
case RELOPT_TYPE_BOOL:
@@ -1807,6 +1888,11 @@ fillRelOptions(void *rdopts, Size basesize,
options[i].values.bool_val :
((relopt_bool *) options[i].gen)->default_val;
break;
+ case RELOPT_TYPE_TERNARY:
+ *(pg_ternary *) itempos = options[i].isset ?
+ options[i].values.ternary_val :
+ ((relopt_ternary *) options[i].gen)->default_val;
+ break;
case RELOPT_TYPE_INT:
*(int *) itempos = options[i].isset ?
options[i].values.int_val :
@@ -1923,8 +2009,8 @@ default_reloptions(Datum reloptions, bool validate, relopt_kind kind)
offsetof(StdRdOptions, parallel_workers)},
{"vacuum_index_cleanup", RELOPT_TYPE_ENUM,
offsetof(StdRdOptions, vacuum_index_cleanup)},
- {"vacuum_truncate", RELOPT_TYPE_BOOL,
- offsetof(StdRdOptions, vacuum_truncate), offsetof(StdRdOptions, vacuum_truncate_set)},
+ {"vacuum_truncate", RELOPT_TYPE_TERNARY,
+ offsetof(StdRdOptions, vacuum_truncate)},
{"vacuum_max_eager_freeze_failure_rate", RELOPT_TYPE_REAL,
offsetof(StdRdOptions, vacuum_max_eager_freeze_failure_rate)}
};
@@ -2004,7 +2090,6 @@ build_local_reloptions(local_relopts *relopts, Datum options, bool validate)
elems[i].optname = opt->option->name;
elems[i].opttype = opt->option->type;
elems[i].offset = opt->offset;
- elems[i].isset_offset = 0; /* not supported for local relopts yet */
i++;
}
diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c
index aa4fbec143f..696eab9bd97 100644
--- a/src/backend/commands/vacuum.c
+++ b/src/backend/commands/vacuum.c
@@ -2224,9 +2224,9 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams params,
{
StdRdOptions *opts = (StdRdOptions *) rel->rd_options;
- if (opts && opts->vacuum_truncate_set)
+ if (opts && opts->vacuum_truncate != TERNARY_UNSET)
{
- if (opts->vacuum_truncate)
+ if (opts->vacuum_truncate == TERNARY_TRUE)
params.truncate = VACOPTVALUE_ENABLED;
else
params.truncate = VACOPTVALUE_DISABLED;
diff --git a/src/include/access/reloptions.h b/src/include/access/reloptions.h
index 2f08e1b0cf0..dfbef2babf2 100644
--- a/src/include/access/reloptions.h
+++ b/src/include/access/reloptions.h
@@ -29,6 +29,7 @@
typedef enum relopt_type
{
RELOPT_TYPE_BOOL,
+ RELOPT_TYPE_TERNARY, /* on, off, unset */
RELOPT_TYPE_INT,
RELOPT_TYPE_REAL,
RELOPT_TYPE_ENUM,
@@ -80,6 +81,7 @@ typedef struct relopt_value
union
{
bool bool_val;
+ pg_ternary ternary_val;
int int_val;
double real_val;
int enum_val;
@@ -94,6 +96,12 @@ typedef struct relopt_bool
bool default_val;
} relopt_bool;
+typedef struct relopt_ternary
+{
+ relopt_gen gen;
+ int default_val;
+} relopt_ternary;
+
typedef struct relopt_int
{
relopt_gen gen;
@@ -152,19 +160,6 @@ typedef struct
const char *optname; /* option's name */
relopt_type opttype; /* option's datatype */
int offset; /* offset of field in result struct */
-
- /*
- * isset_offset is an optional offset of a field in the result struct that
- * stores whether the option is explicitly set for the relation or if it
- * just picked up the default value. In most cases, this can be
- * accomplished by giving the reloption a special out-of-range default
- * value (e.g., some integer reloptions use -2), but this isn't always
- * possible. For example, a Boolean reloption cannot be given an
- * out-of-range default, so we need another way to discover the source of
- * its value. This offset is only used if given a value greater than
- * zero.
- */
- int isset_offset;
} relopt_parse_elt;
/* Local reloption definition */
@@ -195,6 +190,9 @@ typedef struct local_relopts
extern relopt_kind add_reloption_kind(void);
extern void add_bool_reloption(bits32 kinds, const char *name, const char *desc,
bool default_val, LOCKMODE lockmode);
+extern void add_ternary_reloption(bits32 kinds, const char *name,
+ const char *desc, pg_ternary default_val,
+ LOCKMODE lockmode);
extern void add_int_reloption(bits32 kinds, const char *name, const char *desc,
int default_val, int min_val, int max_val,
LOCKMODE lockmode);
@@ -214,6 +212,9 @@ extern void register_reloptions_validator(local_relopts *relopts,
extern void add_local_bool_reloption(local_relopts *relopts, const char *name,
const char *desc, bool default_val,
int offset);
+extern void add_local_ternary_reloption(local_relopts *relopts,
+ const char *name, const char *desc,
+ pg_ternary default_val, int offset);
extern void add_local_int_reloption(local_relopts *relopts, const char *name,
const char *desc, int default_val,
int min_val, int max_val, int offset);
diff --git a/src/include/postgres.h b/src/include/postgres.h
index 7d93fbce709..47c88780776 100644
--- a/src/include/postgres.h
+++ b/src/include/postgres.h
@@ -543,6 +543,21 @@ Float8GetDatum(float8 X)
* ----------------------------------------------------------------
*/
+/*
+ * pg_ternary
+ * Boolean value with an extra "unset" value
+ *
+ * This enum can be used for values that want to distinguish between true,
+ * false, and unset.
+*/
+
+typedef enum pg_ternary
+{
+ TERNARY_FALSE = 0,
+ TERNARY_TRUE = 1,
+ TERNARY_UNSET = -1
+} pg_ternary;
+
/*
* NON_EXEC_STATIC: It's sometimes useful to define a variable or function
* that is normally static but extern when using EXEC_BACKEND (see
diff --git a/src/include/utils/rel.h b/src/include/utils/rel.h
index d03ab247788..5feb18a5373 100644
--- a/src/include/utils/rel.h
+++ b/src/include/utils/rel.h
@@ -347,8 +347,7 @@ typedef struct StdRdOptions
bool user_catalog_table; /* use as an additional catalog relation */
int parallel_workers; /* max number of parallel workers */
StdRdOptIndexCleanup vacuum_index_cleanup; /* controls index vacuuming */
- bool vacuum_truncate; /* enables vacuum to truncate a relation */
- bool vacuum_truncate_set; /* whether vacuum_truncate is set */
+ pg_ternary vacuum_truncate; /* enables vacuum to truncate a relation */
/*
* Fraction of pages in a relation that vacuum can eagerly scan and fail
diff --git a/src/test/regress/expected/reloptions.out b/src/test/regress/expected/reloptions.out
index 9de19b4e3f1..1c99f79ab01 100644
--- a/src/test/regress/expected/reloptions.out
+++ b/src/test/regress/expected/reloptions.out
@@ -98,6 +98,42 @@ SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass;
{fillfactor=13,autovacuum_enabled=false}
(1 row)
+-- Tests for future (FIXME) ternary options
+-- behave as boolean option: accept unassigned name and truncated value
+DROP TABLE reloptions_test;
+CREATE TABLE reloptions_test(i INT) WITH (vacuum_truncate);
+SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass;
+ reloptions
+------------------------
+ {vacuum_truncate=true}
+(1 row)
+
+DROP TABLE reloptions_test;
+CREATE TABLE reloptions_test(i INT) WITH (vacuum_truncate=FaLS);
+SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass;
+ reloptions
+------------------------
+ {vacuum_truncate=fals}
+(1 row)
+
+-- preferred "true" alias is used when storing
+DROP TABLE reloptions_test;
+CREATE TABLE reloptions_test(i INT) WITH (vacuum_index_cleanup=on);
+SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass;
+ reloptions
+---------------------------
+ {vacuum_index_cleanup=on}
+(1 row)
+
+-- custom "third" value is available
+DROP TABLE reloptions_test;
+CREATE TABLE reloptions_test(i INT) WITH (vacuum_index_cleanup=auto);
+SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass;
+ reloptions
+-----------------------------
+ {vacuum_index_cleanup=auto}
+(1 row)
+
-- Test vacuum_truncate option
DROP TABLE reloptions_test;
CREATE TEMP TABLE reloptions_test(i INT NOT NULL, j text)
diff --git a/src/test/regress/sql/reloptions.sql b/src/test/regress/sql/reloptions.sql
index 24fbe0b478d..f5980dafcbc 100644
--- a/src/test/regress/sql/reloptions.sql
+++ b/src/test/regress/sql/reloptions.sql
@@ -59,6 +59,27 @@ UPDATE pg_class
ALTER TABLE reloptions_test RESET (illegal_option);
SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass;
+-- Tests for future (FIXME) ternary options
+
+-- behave as boolean option: accept unassigned name and truncated value
+DROP TABLE reloptions_test;
+CREATE TABLE reloptions_test(i INT) WITH (vacuum_truncate);
+SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass;
+
+DROP TABLE reloptions_test;
+CREATE TABLE reloptions_test(i INT) WITH (vacuum_truncate=FaLS);
+SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass;
+
+-- preferred "true" alias is used when storing
+DROP TABLE reloptions_test;
+CREATE TABLE reloptions_test(i INT) WITH (vacuum_index_cleanup=on);
+SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass;
+
+-- custom "third" value is available
+DROP TABLE reloptions_test;
+CREATE TABLE reloptions_test(i INT) WITH (vacuum_index_cleanup=auto);
+SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass;
+
-- Test vacuum_truncate option
DROP TABLE reloptions_test;
diff --git a/src/tools/pgindent/typedefs.list b/src/tools/pgindent/typedefs.list
index 3f3a888fd0e..1c8610fd46c 100644
--- a/src/tools/pgindent/typedefs.list
+++ b/src/tools/pgindent/typedefs.list
@@ -3952,6 +3952,7 @@ pg_sha512_ctx
pg_snapshot
pg_special_case
pg_stack_base_t
+pg_ternary
pg_time_t
pg_time_usec_t
pg_tz
@@ -4079,6 +4080,7 @@ relopt_kind
relopt_parse_elt
relopt_real
relopt_string
+relopt_ternary
relopt_type
relopt_value
relopts_validator
--
2.47.3
--c36t3fndxhkzbl5z--
^ permalink raw reply [nested|flat] 868+ messages in thread
* [PATCH v3] Introduce ternary reloptions
@ 2026-01-16 15:04 Álvaro Herrera <[email protected]>
0 siblings, 0 replies; 868+ messages in thread
From: Álvaro Herrera @ 2026-01-16 15:04 UTC (permalink / raw)
Introduce ternary reloption as a replacement for current
`vacuum_truncate` implementation. Remove the `vacuum_truncate_set`
separate flag and use TERNARY_UNSET instead.
This could also be used for other options such as `vacuum_index_cleanup`
and `buffering`, but lets get the scaffolding in first.
Discussion: https://postgr.es/m/3474141.usfYGdeWWP@thinkpad-pgpro
---
src/backend/access/common/reloptions.c | 137 ++++++++++++++++++-----
src/backend/commands/vacuum.c | 4 +-
src/include/access/reloptions.h | 27 ++---
src/include/postgres.h | 15 +++
src/include/utils/rel.h | 3 +-
src/test/regress/expected/reloptions.out | 36 ++++++
src/test/regress/sql/reloptions.sql | 21 ++++
src/tools/pgindent/typedefs.list | 2 +
8 files changed, 202 insertions(+), 43 deletions(-)
diff --git a/src/backend/access/common/reloptions.c b/src/backend/access/common/reloptions.c
index 0b83f98ed5f..6f1f577581a 100644
--- a/src/backend/access/common/reloptions.c
+++ b/src/backend/access/common/reloptions.c
@@ -40,9 +40,9 @@
*
* To add an option:
*
- * (i) decide on a type (bool, integer, real, enum, string), name, default
- * value, upper and lower bounds (if applicable); for strings, consider a
- * validation routine.
+ * (i) decide on a type (bool, ternary, integer, real, enum, string), name,
+ * default value, upper and lower bounds (if applicable); for strings,
+ * consider a validation routine.
* (ii) add a record below (or use add_<type>_reloption).
* (iii) add it to the appropriate options struct (perhaps StdRdOptions)
* (iv) add it to the appropriate handling routine (perhaps
@@ -147,15 +147,6 @@ static relopt_bool boolRelOpts[] =
},
false
},
- {
- {
- "vacuum_truncate",
- "Enables vacuum to truncate empty pages at the end of this table",
- RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
- ShareUpdateExclusiveLock
- },
- true
- },
{
{
"deduplicate_items",
@@ -170,6 +161,25 @@ static relopt_bool boolRelOpts[] =
{{NULL}}
};
+static relopt_ternary ternaryRelOpts[] =
+{
+ {
+ {
+ "vacuum_truncate",
+ "Enables vacuum to truncate empty pages at the end of this table",
+ RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
+ ShareUpdateExclusiveLock
+ },
+ TERNARY_UNSET
+ },
+ /* list terminator */
+ {
+ {
+ NULL
+ }
+ }
+};
+
static relopt_int intRelOpts[] =
{
{
@@ -609,6 +619,13 @@ initialize_reloptions(void)
boolRelOpts[i].gen.lockmode));
j++;
}
+ for (i = 0; ternaryRelOpts[i].gen.name; i++)
+ {
+ Assert(DoLockModesConflict(ternaryRelOpts[i].gen.lockmode,
+ ternaryRelOpts[i].gen.lockmode));
+ j++;
+ }
+
for (i = 0; intRelOpts[i].gen.name; i++)
{
Assert(DoLockModesConflict(intRelOpts[i].gen.lockmode,
@@ -649,6 +666,14 @@ initialize_reloptions(void)
j++;
}
+ for (i = 0; ternaryRelOpts[i].gen.name; i++)
+ {
+ relOpts[j] = &ternaryRelOpts[i].gen;
+ relOpts[j]->type = RELOPT_TYPE_TERNARY;
+ relOpts[j]->namelen = strlen(relOpts[j]->name);
+ j++;
+ }
+
for (i = 0; intRelOpts[i].gen.name; i++)
{
relOpts[j] = &intRelOpts[i].gen;
@@ -809,6 +834,9 @@ allocate_reloption(bits32 kinds, int type, const char *name, const char *desc,
case RELOPT_TYPE_BOOL:
size = sizeof(relopt_bool);
break;
+ case RELOPT_TYPE_TERNARY:
+ size = sizeof(relopt_ternary);
+ break;
case RELOPT_TYPE_INT:
size = sizeof(relopt_int);
break;
@@ -892,6 +920,57 @@ add_local_bool_reloption(local_relopts *relopts, const char *name,
add_local_reloption(relopts, (relopt_gen *) newoption, offset);
}
+/*
+ * init_ternary_reloption
+ * Allocate and initialize a new ternary reloption
+ */
+static relopt_ternary *
+init_ternary_reloption(bits32 kinds, const char *name, const char *desc,
+ pg_ternary default_val, LOCKMODE lockmode)
+{
+ relopt_ternary *newoption;
+
+ newoption = (relopt_ternary *)
+ allocate_reloption(kinds, RELOPT_TYPE_TERNARY, name, desc, lockmode);
+ newoption->default_val = default_val;
+
+ return newoption;
+}
+
+/*
+ * add_ternary_reloption
+ * Add a new ternary reloption
+ */
+void
+add_ternary_reloption(bits32 kinds, const char *name, const char *desc,
+ pg_ternary default_val, LOCKMODE lockmode)
+{
+ relopt_ternary *newoption;
+
+ newoption =
+ init_ternary_reloption(kinds, name, desc, default_val, lockmode);
+
+ add_reloption((relopt_gen *) newoption);
+}
+
+/*
+ * add_local_ternary_reloption
+ * Add a new ternary local reloption
+ *
+ * 'offset' is offset of ternary-typed field.
+ */
+void
+add_local_ternary_reloption(local_relopts *relopts, const char *name,
+ const char *desc, pg_ternary default_val,
+ int offset)
+{
+ relopt_ternary *newoption;
+
+ newoption =
+ init_ternary_reloption(RELOPT_KIND_LOCAL, name, desc, default_val, 0);
+
+ add_local_reloption(relopts, (relopt_gen *) newoption, offset);
+}
/*
* init_real_reloption
@@ -1626,6 +1705,19 @@ parse_one_reloption(relopt_value *option, char *text_str, int text_len,
option->gen->name, value)));
}
break;
+ case RELOPT_TYPE_TERNARY:
+ {
+ bool b;
+
+ parsed = parse_bool(value, &b);
+ option->values.ternary_val = b ? TERNARY_TRUE : TERNARY_FALSE;
+ if (validate && !parsed)
+ ereport(ERROR,
+ (errcode(ERRCODE_INVALID_PARAMETER_VALUE),
+ errmsg("invalid value for ternary option \"%s\": %s",
+ option->gen->name, value)));
+ }
+ break;
case RELOPT_TYPE_INT:
{
relopt_int *optint = (relopt_int *) option->gen;
@@ -1789,17 +1881,6 @@ fillRelOptions(void *rdopts, Size basesize,
char *itempos = ((char *) rdopts) + elems[j].offset;
char *string_val;
- /*
- * If isset_offset is provided, store whether the reloption is
- * set there.
- */
- if (elems[j].isset_offset > 0)
- {
- char *setpos = ((char *) rdopts) + elems[j].isset_offset;
-
- *(bool *) setpos = options[i].isset;
- }
-
switch (options[i].gen->type)
{
case RELOPT_TYPE_BOOL:
@@ -1807,6 +1888,11 @@ fillRelOptions(void *rdopts, Size basesize,
options[i].values.bool_val :
((relopt_bool *) options[i].gen)->default_val;
break;
+ case RELOPT_TYPE_TERNARY:
+ *(pg_ternary *) itempos = options[i].isset ?
+ options[i].values.ternary_val :
+ ((relopt_ternary *) options[i].gen)->default_val;
+ break;
case RELOPT_TYPE_INT:
*(int *) itempos = options[i].isset ?
options[i].values.int_val :
@@ -1923,8 +2009,8 @@ default_reloptions(Datum reloptions, bool validate, relopt_kind kind)
offsetof(StdRdOptions, parallel_workers)},
{"vacuum_index_cleanup", RELOPT_TYPE_ENUM,
offsetof(StdRdOptions, vacuum_index_cleanup)},
- {"vacuum_truncate", RELOPT_TYPE_BOOL,
- offsetof(StdRdOptions, vacuum_truncate), offsetof(StdRdOptions, vacuum_truncate_set)},
+ {"vacuum_truncate", RELOPT_TYPE_TERNARY,
+ offsetof(StdRdOptions, vacuum_truncate)},
{"vacuum_max_eager_freeze_failure_rate", RELOPT_TYPE_REAL,
offsetof(StdRdOptions, vacuum_max_eager_freeze_failure_rate)}
};
@@ -2004,7 +2090,6 @@ build_local_reloptions(local_relopts *relopts, Datum options, bool validate)
elems[i].optname = opt->option->name;
elems[i].opttype = opt->option->type;
elems[i].offset = opt->offset;
- elems[i].isset_offset = 0; /* not supported for local relopts yet */
i++;
}
diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c
index aa4fbec143f..696eab9bd97 100644
--- a/src/backend/commands/vacuum.c
+++ b/src/backend/commands/vacuum.c
@@ -2224,9 +2224,9 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams params,
{
StdRdOptions *opts = (StdRdOptions *) rel->rd_options;
- if (opts && opts->vacuum_truncate_set)
+ if (opts && opts->vacuum_truncate != TERNARY_UNSET)
{
- if (opts->vacuum_truncate)
+ if (opts->vacuum_truncate == TERNARY_TRUE)
params.truncate = VACOPTVALUE_ENABLED;
else
params.truncate = VACOPTVALUE_DISABLED;
diff --git a/src/include/access/reloptions.h b/src/include/access/reloptions.h
index 2f08e1b0cf0..dfbef2babf2 100644
--- a/src/include/access/reloptions.h
+++ b/src/include/access/reloptions.h
@@ -29,6 +29,7 @@
typedef enum relopt_type
{
RELOPT_TYPE_BOOL,
+ RELOPT_TYPE_TERNARY, /* on, off, unset */
RELOPT_TYPE_INT,
RELOPT_TYPE_REAL,
RELOPT_TYPE_ENUM,
@@ -80,6 +81,7 @@ typedef struct relopt_value
union
{
bool bool_val;
+ pg_ternary ternary_val;
int int_val;
double real_val;
int enum_val;
@@ -94,6 +96,12 @@ typedef struct relopt_bool
bool default_val;
} relopt_bool;
+typedef struct relopt_ternary
+{
+ relopt_gen gen;
+ int default_val;
+} relopt_ternary;
+
typedef struct relopt_int
{
relopt_gen gen;
@@ -152,19 +160,6 @@ typedef struct
const char *optname; /* option's name */
relopt_type opttype; /* option's datatype */
int offset; /* offset of field in result struct */
-
- /*
- * isset_offset is an optional offset of a field in the result struct that
- * stores whether the option is explicitly set for the relation or if it
- * just picked up the default value. In most cases, this can be
- * accomplished by giving the reloption a special out-of-range default
- * value (e.g., some integer reloptions use -2), but this isn't always
- * possible. For example, a Boolean reloption cannot be given an
- * out-of-range default, so we need another way to discover the source of
- * its value. This offset is only used if given a value greater than
- * zero.
- */
- int isset_offset;
} relopt_parse_elt;
/* Local reloption definition */
@@ -195,6 +190,9 @@ typedef struct local_relopts
extern relopt_kind add_reloption_kind(void);
extern void add_bool_reloption(bits32 kinds, const char *name, const char *desc,
bool default_val, LOCKMODE lockmode);
+extern void add_ternary_reloption(bits32 kinds, const char *name,
+ const char *desc, pg_ternary default_val,
+ LOCKMODE lockmode);
extern void add_int_reloption(bits32 kinds, const char *name, const char *desc,
int default_val, int min_val, int max_val,
LOCKMODE lockmode);
@@ -214,6 +212,9 @@ extern void register_reloptions_validator(local_relopts *relopts,
extern void add_local_bool_reloption(local_relopts *relopts, const char *name,
const char *desc, bool default_val,
int offset);
+extern void add_local_ternary_reloption(local_relopts *relopts,
+ const char *name, const char *desc,
+ pg_ternary default_val, int offset);
extern void add_local_int_reloption(local_relopts *relopts, const char *name,
const char *desc, int default_val,
int min_val, int max_val, int offset);
diff --git a/src/include/postgres.h b/src/include/postgres.h
index 7d93fbce709..47c88780776 100644
--- a/src/include/postgres.h
+++ b/src/include/postgres.h
@@ -543,6 +543,21 @@ Float8GetDatum(float8 X)
* ----------------------------------------------------------------
*/
+/*
+ * pg_ternary
+ * Boolean value with an extra "unset" value
+ *
+ * This enum can be used for values that want to distinguish between true,
+ * false, and unset.
+*/
+
+typedef enum pg_ternary
+{
+ TERNARY_FALSE = 0,
+ TERNARY_TRUE = 1,
+ TERNARY_UNSET = -1
+} pg_ternary;
+
/*
* NON_EXEC_STATIC: It's sometimes useful to define a variable or function
* that is normally static but extern when using EXEC_BACKEND (see
diff --git a/src/include/utils/rel.h b/src/include/utils/rel.h
index d03ab247788..5feb18a5373 100644
--- a/src/include/utils/rel.h
+++ b/src/include/utils/rel.h
@@ -347,8 +347,7 @@ typedef struct StdRdOptions
bool user_catalog_table; /* use as an additional catalog relation */
int parallel_workers; /* max number of parallel workers */
StdRdOptIndexCleanup vacuum_index_cleanup; /* controls index vacuuming */
- bool vacuum_truncate; /* enables vacuum to truncate a relation */
- bool vacuum_truncate_set; /* whether vacuum_truncate is set */
+ pg_ternary vacuum_truncate; /* enables vacuum to truncate a relation */
/*
* Fraction of pages in a relation that vacuum can eagerly scan and fail
diff --git a/src/test/regress/expected/reloptions.out b/src/test/regress/expected/reloptions.out
index 9de19b4e3f1..1c99f79ab01 100644
--- a/src/test/regress/expected/reloptions.out
+++ b/src/test/regress/expected/reloptions.out
@@ -98,6 +98,42 @@ SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass;
{fillfactor=13,autovacuum_enabled=false}
(1 row)
+-- Tests for future (FIXME) ternary options
+-- behave as boolean option: accept unassigned name and truncated value
+DROP TABLE reloptions_test;
+CREATE TABLE reloptions_test(i INT) WITH (vacuum_truncate);
+SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass;
+ reloptions
+------------------------
+ {vacuum_truncate=true}
+(1 row)
+
+DROP TABLE reloptions_test;
+CREATE TABLE reloptions_test(i INT) WITH (vacuum_truncate=FaLS);
+SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass;
+ reloptions
+------------------------
+ {vacuum_truncate=fals}
+(1 row)
+
+-- preferred "true" alias is used when storing
+DROP TABLE reloptions_test;
+CREATE TABLE reloptions_test(i INT) WITH (vacuum_index_cleanup=on);
+SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass;
+ reloptions
+---------------------------
+ {vacuum_index_cleanup=on}
+(1 row)
+
+-- custom "third" value is available
+DROP TABLE reloptions_test;
+CREATE TABLE reloptions_test(i INT) WITH (vacuum_index_cleanup=auto);
+SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass;
+ reloptions
+-----------------------------
+ {vacuum_index_cleanup=auto}
+(1 row)
+
-- Test vacuum_truncate option
DROP TABLE reloptions_test;
CREATE TEMP TABLE reloptions_test(i INT NOT NULL, j text)
diff --git a/src/test/regress/sql/reloptions.sql b/src/test/regress/sql/reloptions.sql
index 24fbe0b478d..f5980dafcbc 100644
--- a/src/test/regress/sql/reloptions.sql
+++ b/src/test/regress/sql/reloptions.sql
@@ -59,6 +59,27 @@ UPDATE pg_class
ALTER TABLE reloptions_test RESET (illegal_option);
SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass;
+-- Tests for future (FIXME) ternary options
+
+-- behave as boolean option: accept unassigned name and truncated value
+DROP TABLE reloptions_test;
+CREATE TABLE reloptions_test(i INT) WITH (vacuum_truncate);
+SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass;
+
+DROP TABLE reloptions_test;
+CREATE TABLE reloptions_test(i INT) WITH (vacuum_truncate=FaLS);
+SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass;
+
+-- preferred "true" alias is used when storing
+DROP TABLE reloptions_test;
+CREATE TABLE reloptions_test(i INT) WITH (vacuum_index_cleanup=on);
+SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass;
+
+-- custom "third" value is available
+DROP TABLE reloptions_test;
+CREATE TABLE reloptions_test(i INT) WITH (vacuum_index_cleanup=auto);
+SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass;
+
-- Test vacuum_truncate option
DROP TABLE reloptions_test;
diff --git a/src/tools/pgindent/typedefs.list b/src/tools/pgindent/typedefs.list
index 3f3a888fd0e..1c8610fd46c 100644
--- a/src/tools/pgindent/typedefs.list
+++ b/src/tools/pgindent/typedefs.list
@@ -3952,6 +3952,7 @@ pg_sha512_ctx
pg_snapshot
pg_special_case
pg_stack_base_t
+pg_ternary
pg_time_t
pg_time_usec_t
pg_tz
@@ -4079,6 +4080,7 @@ relopt_kind
relopt_parse_elt
relopt_real
relopt_string
+relopt_ternary
relopt_type
relopt_value
relopts_validator
--
2.47.3
--c36t3fndxhkzbl5z--
^ permalink raw reply [nested|flat] 868+ messages in thread
* [PATCH v3] Introduce ternary reloptions
@ 2026-01-16 15:04 Álvaro Herrera <[email protected]>
0 siblings, 0 replies; 868+ messages in thread
From: Álvaro Herrera @ 2026-01-16 15:04 UTC (permalink / raw)
Introduce ternary reloption as a replacement for current
`vacuum_truncate` implementation. Remove the `vacuum_truncate_set`
separate flag and use TERNARY_UNSET instead.
This could also be used for other options such as `vacuum_index_cleanup`
and `buffering`, but lets get the scaffolding in first.
Discussion: https://postgr.es/m/3474141.usfYGdeWWP@thinkpad-pgpro
---
src/backend/access/common/reloptions.c | 137 ++++++++++++++++++-----
src/backend/commands/vacuum.c | 4 +-
src/include/access/reloptions.h | 27 ++---
src/include/postgres.h | 15 +++
src/include/utils/rel.h | 3 +-
src/test/regress/expected/reloptions.out | 36 ++++++
src/test/regress/sql/reloptions.sql | 21 ++++
src/tools/pgindent/typedefs.list | 2 +
8 files changed, 202 insertions(+), 43 deletions(-)
diff --git a/src/backend/access/common/reloptions.c b/src/backend/access/common/reloptions.c
index 0b83f98ed5f..6f1f577581a 100644
--- a/src/backend/access/common/reloptions.c
+++ b/src/backend/access/common/reloptions.c
@@ -40,9 +40,9 @@
*
* To add an option:
*
- * (i) decide on a type (bool, integer, real, enum, string), name, default
- * value, upper and lower bounds (if applicable); for strings, consider a
- * validation routine.
+ * (i) decide on a type (bool, ternary, integer, real, enum, string), name,
+ * default value, upper and lower bounds (if applicable); for strings,
+ * consider a validation routine.
* (ii) add a record below (or use add_<type>_reloption).
* (iii) add it to the appropriate options struct (perhaps StdRdOptions)
* (iv) add it to the appropriate handling routine (perhaps
@@ -147,15 +147,6 @@ static relopt_bool boolRelOpts[] =
},
false
},
- {
- {
- "vacuum_truncate",
- "Enables vacuum to truncate empty pages at the end of this table",
- RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
- ShareUpdateExclusiveLock
- },
- true
- },
{
{
"deduplicate_items",
@@ -170,6 +161,25 @@ static relopt_bool boolRelOpts[] =
{{NULL}}
};
+static relopt_ternary ternaryRelOpts[] =
+{
+ {
+ {
+ "vacuum_truncate",
+ "Enables vacuum to truncate empty pages at the end of this table",
+ RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
+ ShareUpdateExclusiveLock
+ },
+ TERNARY_UNSET
+ },
+ /* list terminator */
+ {
+ {
+ NULL
+ }
+ }
+};
+
static relopt_int intRelOpts[] =
{
{
@@ -609,6 +619,13 @@ initialize_reloptions(void)
boolRelOpts[i].gen.lockmode));
j++;
}
+ for (i = 0; ternaryRelOpts[i].gen.name; i++)
+ {
+ Assert(DoLockModesConflict(ternaryRelOpts[i].gen.lockmode,
+ ternaryRelOpts[i].gen.lockmode));
+ j++;
+ }
+
for (i = 0; intRelOpts[i].gen.name; i++)
{
Assert(DoLockModesConflict(intRelOpts[i].gen.lockmode,
@@ -649,6 +666,14 @@ initialize_reloptions(void)
j++;
}
+ for (i = 0; ternaryRelOpts[i].gen.name; i++)
+ {
+ relOpts[j] = &ternaryRelOpts[i].gen;
+ relOpts[j]->type = RELOPT_TYPE_TERNARY;
+ relOpts[j]->namelen = strlen(relOpts[j]->name);
+ j++;
+ }
+
for (i = 0; intRelOpts[i].gen.name; i++)
{
relOpts[j] = &intRelOpts[i].gen;
@@ -809,6 +834,9 @@ allocate_reloption(bits32 kinds, int type, const char *name, const char *desc,
case RELOPT_TYPE_BOOL:
size = sizeof(relopt_bool);
break;
+ case RELOPT_TYPE_TERNARY:
+ size = sizeof(relopt_ternary);
+ break;
case RELOPT_TYPE_INT:
size = sizeof(relopt_int);
break;
@@ -892,6 +920,57 @@ add_local_bool_reloption(local_relopts *relopts, const char *name,
add_local_reloption(relopts, (relopt_gen *) newoption, offset);
}
+/*
+ * init_ternary_reloption
+ * Allocate and initialize a new ternary reloption
+ */
+static relopt_ternary *
+init_ternary_reloption(bits32 kinds, const char *name, const char *desc,
+ pg_ternary default_val, LOCKMODE lockmode)
+{
+ relopt_ternary *newoption;
+
+ newoption = (relopt_ternary *)
+ allocate_reloption(kinds, RELOPT_TYPE_TERNARY, name, desc, lockmode);
+ newoption->default_val = default_val;
+
+ return newoption;
+}
+
+/*
+ * add_ternary_reloption
+ * Add a new ternary reloption
+ */
+void
+add_ternary_reloption(bits32 kinds, const char *name, const char *desc,
+ pg_ternary default_val, LOCKMODE lockmode)
+{
+ relopt_ternary *newoption;
+
+ newoption =
+ init_ternary_reloption(kinds, name, desc, default_val, lockmode);
+
+ add_reloption((relopt_gen *) newoption);
+}
+
+/*
+ * add_local_ternary_reloption
+ * Add a new ternary local reloption
+ *
+ * 'offset' is offset of ternary-typed field.
+ */
+void
+add_local_ternary_reloption(local_relopts *relopts, const char *name,
+ const char *desc, pg_ternary default_val,
+ int offset)
+{
+ relopt_ternary *newoption;
+
+ newoption =
+ init_ternary_reloption(RELOPT_KIND_LOCAL, name, desc, default_val, 0);
+
+ add_local_reloption(relopts, (relopt_gen *) newoption, offset);
+}
/*
* init_real_reloption
@@ -1626,6 +1705,19 @@ parse_one_reloption(relopt_value *option, char *text_str, int text_len,
option->gen->name, value)));
}
break;
+ case RELOPT_TYPE_TERNARY:
+ {
+ bool b;
+
+ parsed = parse_bool(value, &b);
+ option->values.ternary_val = b ? TERNARY_TRUE : TERNARY_FALSE;
+ if (validate && !parsed)
+ ereport(ERROR,
+ (errcode(ERRCODE_INVALID_PARAMETER_VALUE),
+ errmsg("invalid value for ternary option \"%s\": %s",
+ option->gen->name, value)));
+ }
+ break;
case RELOPT_TYPE_INT:
{
relopt_int *optint = (relopt_int *) option->gen;
@@ -1789,17 +1881,6 @@ fillRelOptions(void *rdopts, Size basesize,
char *itempos = ((char *) rdopts) + elems[j].offset;
char *string_val;
- /*
- * If isset_offset is provided, store whether the reloption is
- * set there.
- */
- if (elems[j].isset_offset > 0)
- {
- char *setpos = ((char *) rdopts) + elems[j].isset_offset;
-
- *(bool *) setpos = options[i].isset;
- }
-
switch (options[i].gen->type)
{
case RELOPT_TYPE_BOOL:
@@ -1807,6 +1888,11 @@ fillRelOptions(void *rdopts, Size basesize,
options[i].values.bool_val :
((relopt_bool *) options[i].gen)->default_val;
break;
+ case RELOPT_TYPE_TERNARY:
+ *(pg_ternary *) itempos = options[i].isset ?
+ options[i].values.ternary_val :
+ ((relopt_ternary *) options[i].gen)->default_val;
+ break;
case RELOPT_TYPE_INT:
*(int *) itempos = options[i].isset ?
options[i].values.int_val :
@@ -1923,8 +2009,8 @@ default_reloptions(Datum reloptions, bool validate, relopt_kind kind)
offsetof(StdRdOptions, parallel_workers)},
{"vacuum_index_cleanup", RELOPT_TYPE_ENUM,
offsetof(StdRdOptions, vacuum_index_cleanup)},
- {"vacuum_truncate", RELOPT_TYPE_BOOL,
- offsetof(StdRdOptions, vacuum_truncate), offsetof(StdRdOptions, vacuum_truncate_set)},
+ {"vacuum_truncate", RELOPT_TYPE_TERNARY,
+ offsetof(StdRdOptions, vacuum_truncate)},
{"vacuum_max_eager_freeze_failure_rate", RELOPT_TYPE_REAL,
offsetof(StdRdOptions, vacuum_max_eager_freeze_failure_rate)}
};
@@ -2004,7 +2090,6 @@ build_local_reloptions(local_relopts *relopts, Datum options, bool validate)
elems[i].optname = opt->option->name;
elems[i].opttype = opt->option->type;
elems[i].offset = opt->offset;
- elems[i].isset_offset = 0; /* not supported for local relopts yet */
i++;
}
diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c
index aa4fbec143f..696eab9bd97 100644
--- a/src/backend/commands/vacuum.c
+++ b/src/backend/commands/vacuum.c
@@ -2224,9 +2224,9 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams params,
{
StdRdOptions *opts = (StdRdOptions *) rel->rd_options;
- if (opts && opts->vacuum_truncate_set)
+ if (opts && opts->vacuum_truncate != TERNARY_UNSET)
{
- if (opts->vacuum_truncate)
+ if (opts->vacuum_truncate == TERNARY_TRUE)
params.truncate = VACOPTVALUE_ENABLED;
else
params.truncate = VACOPTVALUE_DISABLED;
diff --git a/src/include/access/reloptions.h b/src/include/access/reloptions.h
index 2f08e1b0cf0..dfbef2babf2 100644
--- a/src/include/access/reloptions.h
+++ b/src/include/access/reloptions.h
@@ -29,6 +29,7 @@
typedef enum relopt_type
{
RELOPT_TYPE_BOOL,
+ RELOPT_TYPE_TERNARY, /* on, off, unset */
RELOPT_TYPE_INT,
RELOPT_TYPE_REAL,
RELOPT_TYPE_ENUM,
@@ -80,6 +81,7 @@ typedef struct relopt_value
union
{
bool bool_val;
+ pg_ternary ternary_val;
int int_val;
double real_val;
int enum_val;
@@ -94,6 +96,12 @@ typedef struct relopt_bool
bool default_val;
} relopt_bool;
+typedef struct relopt_ternary
+{
+ relopt_gen gen;
+ int default_val;
+} relopt_ternary;
+
typedef struct relopt_int
{
relopt_gen gen;
@@ -152,19 +160,6 @@ typedef struct
const char *optname; /* option's name */
relopt_type opttype; /* option's datatype */
int offset; /* offset of field in result struct */
-
- /*
- * isset_offset is an optional offset of a field in the result struct that
- * stores whether the option is explicitly set for the relation or if it
- * just picked up the default value. In most cases, this can be
- * accomplished by giving the reloption a special out-of-range default
- * value (e.g., some integer reloptions use -2), but this isn't always
- * possible. For example, a Boolean reloption cannot be given an
- * out-of-range default, so we need another way to discover the source of
- * its value. This offset is only used if given a value greater than
- * zero.
- */
- int isset_offset;
} relopt_parse_elt;
/* Local reloption definition */
@@ -195,6 +190,9 @@ typedef struct local_relopts
extern relopt_kind add_reloption_kind(void);
extern void add_bool_reloption(bits32 kinds, const char *name, const char *desc,
bool default_val, LOCKMODE lockmode);
+extern void add_ternary_reloption(bits32 kinds, const char *name,
+ const char *desc, pg_ternary default_val,
+ LOCKMODE lockmode);
extern void add_int_reloption(bits32 kinds, const char *name, const char *desc,
int default_val, int min_val, int max_val,
LOCKMODE lockmode);
@@ -214,6 +212,9 @@ extern void register_reloptions_validator(local_relopts *relopts,
extern void add_local_bool_reloption(local_relopts *relopts, const char *name,
const char *desc, bool default_val,
int offset);
+extern void add_local_ternary_reloption(local_relopts *relopts,
+ const char *name, const char *desc,
+ pg_ternary default_val, int offset);
extern void add_local_int_reloption(local_relopts *relopts, const char *name,
const char *desc, int default_val,
int min_val, int max_val, int offset);
diff --git a/src/include/postgres.h b/src/include/postgres.h
index 7d93fbce709..47c88780776 100644
--- a/src/include/postgres.h
+++ b/src/include/postgres.h
@@ -543,6 +543,21 @@ Float8GetDatum(float8 X)
* ----------------------------------------------------------------
*/
+/*
+ * pg_ternary
+ * Boolean value with an extra "unset" value
+ *
+ * This enum can be used for values that want to distinguish between true,
+ * false, and unset.
+*/
+
+typedef enum pg_ternary
+{
+ TERNARY_FALSE = 0,
+ TERNARY_TRUE = 1,
+ TERNARY_UNSET = -1
+} pg_ternary;
+
/*
* NON_EXEC_STATIC: It's sometimes useful to define a variable or function
* that is normally static but extern when using EXEC_BACKEND (see
diff --git a/src/include/utils/rel.h b/src/include/utils/rel.h
index d03ab247788..5feb18a5373 100644
--- a/src/include/utils/rel.h
+++ b/src/include/utils/rel.h
@@ -347,8 +347,7 @@ typedef struct StdRdOptions
bool user_catalog_table; /* use as an additional catalog relation */
int parallel_workers; /* max number of parallel workers */
StdRdOptIndexCleanup vacuum_index_cleanup; /* controls index vacuuming */
- bool vacuum_truncate; /* enables vacuum to truncate a relation */
- bool vacuum_truncate_set; /* whether vacuum_truncate is set */
+ pg_ternary vacuum_truncate; /* enables vacuum to truncate a relation */
/*
* Fraction of pages in a relation that vacuum can eagerly scan and fail
diff --git a/src/test/regress/expected/reloptions.out b/src/test/regress/expected/reloptions.out
index 9de19b4e3f1..1c99f79ab01 100644
--- a/src/test/regress/expected/reloptions.out
+++ b/src/test/regress/expected/reloptions.out
@@ -98,6 +98,42 @@ SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass;
{fillfactor=13,autovacuum_enabled=false}
(1 row)
+-- Tests for future (FIXME) ternary options
+-- behave as boolean option: accept unassigned name and truncated value
+DROP TABLE reloptions_test;
+CREATE TABLE reloptions_test(i INT) WITH (vacuum_truncate);
+SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass;
+ reloptions
+------------------------
+ {vacuum_truncate=true}
+(1 row)
+
+DROP TABLE reloptions_test;
+CREATE TABLE reloptions_test(i INT) WITH (vacuum_truncate=FaLS);
+SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass;
+ reloptions
+------------------------
+ {vacuum_truncate=fals}
+(1 row)
+
+-- preferred "true" alias is used when storing
+DROP TABLE reloptions_test;
+CREATE TABLE reloptions_test(i INT) WITH (vacuum_index_cleanup=on);
+SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass;
+ reloptions
+---------------------------
+ {vacuum_index_cleanup=on}
+(1 row)
+
+-- custom "third" value is available
+DROP TABLE reloptions_test;
+CREATE TABLE reloptions_test(i INT) WITH (vacuum_index_cleanup=auto);
+SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass;
+ reloptions
+-----------------------------
+ {vacuum_index_cleanup=auto}
+(1 row)
+
-- Test vacuum_truncate option
DROP TABLE reloptions_test;
CREATE TEMP TABLE reloptions_test(i INT NOT NULL, j text)
diff --git a/src/test/regress/sql/reloptions.sql b/src/test/regress/sql/reloptions.sql
index 24fbe0b478d..f5980dafcbc 100644
--- a/src/test/regress/sql/reloptions.sql
+++ b/src/test/regress/sql/reloptions.sql
@@ -59,6 +59,27 @@ UPDATE pg_class
ALTER TABLE reloptions_test RESET (illegal_option);
SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass;
+-- Tests for future (FIXME) ternary options
+
+-- behave as boolean option: accept unassigned name and truncated value
+DROP TABLE reloptions_test;
+CREATE TABLE reloptions_test(i INT) WITH (vacuum_truncate);
+SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass;
+
+DROP TABLE reloptions_test;
+CREATE TABLE reloptions_test(i INT) WITH (vacuum_truncate=FaLS);
+SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass;
+
+-- preferred "true" alias is used when storing
+DROP TABLE reloptions_test;
+CREATE TABLE reloptions_test(i INT) WITH (vacuum_index_cleanup=on);
+SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass;
+
+-- custom "third" value is available
+DROP TABLE reloptions_test;
+CREATE TABLE reloptions_test(i INT) WITH (vacuum_index_cleanup=auto);
+SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass;
+
-- Test vacuum_truncate option
DROP TABLE reloptions_test;
diff --git a/src/tools/pgindent/typedefs.list b/src/tools/pgindent/typedefs.list
index 3f3a888fd0e..1c8610fd46c 100644
--- a/src/tools/pgindent/typedefs.list
+++ b/src/tools/pgindent/typedefs.list
@@ -3952,6 +3952,7 @@ pg_sha512_ctx
pg_snapshot
pg_special_case
pg_stack_base_t
+pg_ternary
pg_time_t
pg_time_usec_t
pg_tz
@@ -4079,6 +4080,7 @@ relopt_kind
relopt_parse_elt
relopt_real
relopt_string
+relopt_ternary
relopt_type
relopt_value
relopts_validator
--
2.47.3
--c36t3fndxhkzbl5z--
^ permalink raw reply [nested|flat] 868+ messages in thread
* [PATCH v3] Introduce ternary reloptions
@ 2026-01-16 15:04 Álvaro Herrera <[email protected]>
0 siblings, 0 replies; 868+ messages in thread
From: Álvaro Herrera @ 2026-01-16 15:04 UTC (permalink / raw)
Introduce ternary reloption as a replacement for current
`vacuum_truncate` implementation. Remove the `vacuum_truncate_set`
separate flag and use TERNARY_UNSET instead.
This could also be used for other options such as `vacuum_index_cleanup`
and `buffering`, but lets get the scaffolding in first.
Discussion: https://postgr.es/m/3474141.usfYGdeWWP@thinkpad-pgpro
---
src/backend/access/common/reloptions.c | 137 ++++++++++++++++++-----
src/backend/commands/vacuum.c | 4 +-
src/include/access/reloptions.h | 27 ++---
src/include/postgres.h | 15 +++
src/include/utils/rel.h | 3 +-
src/test/regress/expected/reloptions.out | 36 ++++++
src/test/regress/sql/reloptions.sql | 21 ++++
src/tools/pgindent/typedefs.list | 2 +
8 files changed, 202 insertions(+), 43 deletions(-)
diff --git a/src/backend/access/common/reloptions.c b/src/backend/access/common/reloptions.c
index 0b83f98ed5f..6f1f577581a 100644
--- a/src/backend/access/common/reloptions.c
+++ b/src/backend/access/common/reloptions.c
@@ -40,9 +40,9 @@
*
* To add an option:
*
- * (i) decide on a type (bool, integer, real, enum, string), name, default
- * value, upper and lower bounds (if applicable); for strings, consider a
- * validation routine.
+ * (i) decide on a type (bool, ternary, integer, real, enum, string), name,
+ * default value, upper and lower bounds (if applicable); for strings,
+ * consider a validation routine.
* (ii) add a record below (or use add_<type>_reloption).
* (iii) add it to the appropriate options struct (perhaps StdRdOptions)
* (iv) add it to the appropriate handling routine (perhaps
@@ -147,15 +147,6 @@ static relopt_bool boolRelOpts[] =
},
false
},
- {
- {
- "vacuum_truncate",
- "Enables vacuum to truncate empty pages at the end of this table",
- RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
- ShareUpdateExclusiveLock
- },
- true
- },
{
{
"deduplicate_items",
@@ -170,6 +161,25 @@ static relopt_bool boolRelOpts[] =
{{NULL}}
};
+static relopt_ternary ternaryRelOpts[] =
+{
+ {
+ {
+ "vacuum_truncate",
+ "Enables vacuum to truncate empty pages at the end of this table",
+ RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
+ ShareUpdateExclusiveLock
+ },
+ TERNARY_UNSET
+ },
+ /* list terminator */
+ {
+ {
+ NULL
+ }
+ }
+};
+
static relopt_int intRelOpts[] =
{
{
@@ -609,6 +619,13 @@ initialize_reloptions(void)
boolRelOpts[i].gen.lockmode));
j++;
}
+ for (i = 0; ternaryRelOpts[i].gen.name; i++)
+ {
+ Assert(DoLockModesConflict(ternaryRelOpts[i].gen.lockmode,
+ ternaryRelOpts[i].gen.lockmode));
+ j++;
+ }
+
for (i = 0; intRelOpts[i].gen.name; i++)
{
Assert(DoLockModesConflict(intRelOpts[i].gen.lockmode,
@@ -649,6 +666,14 @@ initialize_reloptions(void)
j++;
}
+ for (i = 0; ternaryRelOpts[i].gen.name; i++)
+ {
+ relOpts[j] = &ternaryRelOpts[i].gen;
+ relOpts[j]->type = RELOPT_TYPE_TERNARY;
+ relOpts[j]->namelen = strlen(relOpts[j]->name);
+ j++;
+ }
+
for (i = 0; intRelOpts[i].gen.name; i++)
{
relOpts[j] = &intRelOpts[i].gen;
@@ -809,6 +834,9 @@ allocate_reloption(bits32 kinds, int type, const char *name, const char *desc,
case RELOPT_TYPE_BOOL:
size = sizeof(relopt_bool);
break;
+ case RELOPT_TYPE_TERNARY:
+ size = sizeof(relopt_ternary);
+ break;
case RELOPT_TYPE_INT:
size = sizeof(relopt_int);
break;
@@ -892,6 +920,57 @@ add_local_bool_reloption(local_relopts *relopts, const char *name,
add_local_reloption(relopts, (relopt_gen *) newoption, offset);
}
+/*
+ * init_ternary_reloption
+ * Allocate and initialize a new ternary reloption
+ */
+static relopt_ternary *
+init_ternary_reloption(bits32 kinds, const char *name, const char *desc,
+ pg_ternary default_val, LOCKMODE lockmode)
+{
+ relopt_ternary *newoption;
+
+ newoption = (relopt_ternary *)
+ allocate_reloption(kinds, RELOPT_TYPE_TERNARY, name, desc, lockmode);
+ newoption->default_val = default_val;
+
+ return newoption;
+}
+
+/*
+ * add_ternary_reloption
+ * Add a new ternary reloption
+ */
+void
+add_ternary_reloption(bits32 kinds, const char *name, const char *desc,
+ pg_ternary default_val, LOCKMODE lockmode)
+{
+ relopt_ternary *newoption;
+
+ newoption =
+ init_ternary_reloption(kinds, name, desc, default_val, lockmode);
+
+ add_reloption((relopt_gen *) newoption);
+}
+
+/*
+ * add_local_ternary_reloption
+ * Add a new ternary local reloption
+ *
+ * 'offset' is offset of ternary-typed field.
+ */
+void
+add_local_ternary_reloption(local_relopts *relopts, const char *name,
+ const char *desc, pg_ternary default_val,
+ int offset)
+{
+ relopt_ternary *newoption;
+
+ newoption =
+ init_ternary_reloption(RELOPT_KIND_LOCAL, name, desc, default_val, 0);
+
+ add_local_reloption(relopts, (relopt_gen *) newoption, offset);
+}
/*
* init_real_reloption
@@ -1626,6 +1705,19 @@ parse_one_reloption(relopt_value *option, char *text_str, int text_len,
option->gen->name, value)));
}
break;
+ case RELOPT_TYPE_TERNARY:
+ {
+ bool b;
+
+ parsed = parse_bool(value, &b);
+ option->values.ternary_val = b ? TERNARY_TRUE : TERNARY_FALSE;
+ if (validate && !parsed)
+ ereport(ERROR,
+ (errcode(ERRCODE_INVALID_PARAMETER_VALUE),
+ errmsg("invalid value for ternary option \"%s\": %s",
+ option->gen->name, value)));
+ }
+ break;
case RELOPT_TYPE_INT:
{
relopt_int *optint = (relopt_int *) option->gen;
@@ -1789,17 +1881,6 @@ fillRelOptions(void *rdopts, Size basesize,
char *itempos = ((char *) rdopts) + elems[j].offset;
char *string_val;
- /*
- * If isset_offset is provided, store whether the reloption is
- * set there.
- */
- if (elems[j].isset_offset > 0)
- {
- char *setpos = ((char *) rdopts) + elems[j].isset_offset;
-
- *(bool *) setpos = options[i].isset;
- }
-
switch (options[i].gen->type)
{
case RELOPT_TYPE_BOOL:
@@ -1807,6 +1888,11 @@ fillRelOptions(void *rdopts, Size basesize,
options[i].values.bool_val :
((relopt_bool *) options[i].gen)->default_val;
break;
+ case RELOPT_TYPE_TERNARY:
+ *(pg_ternary *) itempos = options[i].isset ?
+ options[i].values.ternary_val :
+ ((relopt_ternary *) options[i].gen)->default_val;
+ break;
case RELOPT_TYPE_INT:
*(int *) itempos = options[i].isset ?
options[i].values.int_val :
@@ -1923,8 +2009,8 @@ default_reloptions(Datum reloptions, bool validate, relopt_kind kind)
offsetof(StdRdOptions, parallel_workers)},
{"vacuum_index_cleanup", RELOPT_TYPE_ENUM,
offsetof(StdRdOptions, vacuum_index_cleanup)},
- {"vacuum_truncate", RELOPT_TYPE_BOOL,
- offsetof(StdRdOptions, vacuum_truncate), offsetof(StdRdOptions, vacuum_truncate_set)},
+ {"vacuum_truncate", RELOPT_TYPE_TERNARY,
+ offsetof(StdRdOptions, vacuum_truncate)},
{"vacuum_max_eager_freeze_failure_rate", RELOPT_TYPE_REAL,
offsetof(StdRdOptions, vacuum_max_eager_freeze_failure_rate)}
};
@@ -2004,7 +2090,6 @@ build_local_reloptions(local_relopts *relopts, Datum options, bool validate)
elems[i].optname = opt->option->name;
elems[i].opttype = opt->option->type;
elems[i].offset = opt->offset;
- elems[i].isset_offset = 0; /* not supported for local relopts yet */
i++;
}
diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c
index aa4fbec143f..696eab9bd97 100644
--- a/src/backend/commands/vacuum.c
+++ b/src/backend/commands/vacuum.c
@@ -2224,9 +2224,9 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams params,
{
StdRdOptions *opts = (StdRdOptions *) rel->rd_options;
- if (opts && opts->vacuum_truncate_set)
+ if (opts && opts->vacuum_truncate != TERNARY_UNSET)
{
- if (opts->vacuum_truncate)
+ if (opts->vacuum_truncate == TERNARY_TRUE)
params.truncate = VACOPTVALUE_ENABLED;
else
params.truncate = VACOPTVALUE_DISABLED;
diff --git a/src/include/access/reloptions.h b/src/include/access/reloptions.h
index 2f08e1b0cf0..dfbef2babf2 100644
--- a/src/include/access/reloptions.h
+++ b/src/include/access/reloptions.h
@@ -29,6 +29,7 @@
typedef enum relopt_type
{
RELOPT_TYPE_BOOL,
+ RELOPT_TYPE_TERNARY, /* on, off, unset */
RELOPT_TYPE_INT,
RELOPT_TYPE_REAL,
RELOPT_TYPE_ENUM,
@@ -80,6 +81,7 @@ typedef struct relopt_value
union
{
bool bool_val;
+ pg_ternary ternary_val;
int int_val;
double real_val;
int enum_val;
@@ -94,6 +96,12 @@ typedef struct relopt_bool
bool default_val;
} relopt_bool;
+typedef struct relopt_ternary
+{
+ relopt_gen gen;
+ int default_val;
+} relopt_ternary;
+
typedef struct relopt_int
{
relopt_gen gen;
@@ -152,19 +160,6 @@ typedef struct
const char *optname; /* option's name */
relopt_type opttype; /* option's datatype */
int offset; /* offset of field in result struct */
-
- /*
- * isset_offset is an optional offset of a field in the result struct that
- * stores whether the option is explicitly set for the relation or if it
- * just picked up the default value. In most cases, this can be
- * accomplished by giving the reloption a special out-of-range default
- * value (e.g., some integer reloptions use -2), but this isn't always
- * possible. For example, a Boolean reloption cannot be given an
- * out-of-range default, so we need another way to discover the source of
- * its value. This offset is only used if given a value greater than
- * zero.
- */
- int isset_offset;
} relopt_parse_elt;
/* Local reloption definition */
@@ -195,6 +190,9 @@ typedef struct local_relopts
extern relopt_kind add_reloption_kind(void);
extern void add_bool_reloption(bits32 kinds, const char *name, const char *desc,
bool default_val, LOCKMODE lockmode);
+extern void add_ternary_reloption(bits32 kinds, const char *name,
+ const char *desc, pg_ternary default_val,
+ LOCKMODE lockmode);
extern void add_int_reloption(bits32 kinds, const char *name, const char *desc,
int default_val, int min_val, int max_val,
LOCKMODE lockmode);
@@ -214,6 +212,9 @@ extern void register_reloptions_validator(local_relopts *relopts,
extern void add_local_bool_reloption(local_relopts *relopts, const char *name,
const char *desc, bool default_val,
int offset);
+extern void add_local_ternary_reloption(local_relopts *relopts,
+ const char *name, const char *desc,
+ pg_ternary default_val, int offset);
extern void add_local_int_reloption(local_relopts *relopts, const char *name,
const char *desc, int default_val,
int min_val, int max_val, int offset);
diff --git a/src/include/postgres.h b/src/include/postgres.h
index 7d93fbce709..47c88780776 100644
--- a/src/include/postgres.h
+++ b/src/include/postgres.h
@@ -543,6 +543,21 @@ Float8GetDatum(float8 X)
* ----------------------------------------------------------------
*/
+/*
+ * pg_ternary
+ * Boolean value with an extra "unset" value
+ *
+ * This enum can be used for values that want to distinguish between true,
+ * false, and unset.
+*/
+
+typedef enum pg_ternary
+{
+ TERNARY_FALSE = 0,
+ TERNARY_TRUE = 1,
+ TERNARY_UNSET = -1
+} pg_ternary;
+
/*
* NON_EXEC_STATIC: It's sometimes useful to define a variable or function
* that is normally static but extern when using EXEC_BACKEND (see
diff --git a/src/include/utils/rel.h b/src/include/utils/rel.h
index d03ab247788..5feb18a5373 100644
--- a/src/include/utils/rel.h
+++ b/src/include/utils/rel.h
@@ -347,8 +347,7 @@ typedef struct StdRdOptions
bool user_catalog_table; /* use as an additional catalog relation */
int parallel_workers; /* max number of parallel workers */
StdRdOptIndexCleanup vacuum_index_cleanup; /* controls index vacuuming */
- bool vacuum_truncate; /* enables vacuum to truncate a relation */
- bool vacuum_truncate_set; /* whether vacuum_truncate is set */
+ pg_ternary vacuum_truncate; /* enables vacuum to truncate a relation */
/*
* Fraction of pages in a relation that vacuum can eagerly scan and fail
diff --git a/src/test/regress/expected/reloptions.out b/src/test/regress/expected/reloptions.out
index 9de19b4e3f1..1c99f79ab01 100644
--- a/src/test/regress/expected/reloptions.out
+++ b/src/test/regress/expected/reloptions.out
@@ -98,6 +98,42 @@ SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass;
{fillfactor=13,autovacuum_enabled=false}
(1 row)
+-- Tests for future (FIXME) ternary options
+-- behave as boolean option: accept unassigned name and truncated value
+DROP TABLE reloptions_test;
+CREATE TABLE reloptions_test(i INT) WITH (vacuum_truncate);
+SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass;
+ reloptions
+------------------------
+ {vacuum_truncate=true}
+(1 row)
+
+DROP TABLE reloptions_test;
+CREATE TABLE reloptions_test(i INT) WITH (vacuum_truncate=FaLS);
+SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass;
+ reloptions
+------------------------
+ {vacuum_truncate=fals}
+(1 row)
+
+-- preferred "true" alias is used when storing
+DROP TABLE reloptions_test;
+CREATE TABLE reloptions_test(i INT) WITH (vacuum_index_cleanup=on);
+SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass;
+ reloptions
+---------------------------
+ {vacuum_index_cleanup=on}
+(1 row)
+
+-- custom "third" value is available
+DROP TABLE reloptions_test;
+CREATE TABLE reloptions_test(i INT) WITH (vacuum_index_cleanup=auto);
+SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass;
+ reloptions
+-----------------------------
+ {vacuum_index_cleanup=auto}
+(1 row)
+
-- Test vacuum_truncate option
DROP TABLE reloptions_test;
CREATE TEMP TABLE reloptions_test(i INT NOT NULL, j text)
diff --git a/src/test/regress/sql/reloptions.sql b/src/test/regress/sql/reloptions.sql
index 24fbe0b478d..f5980dafcbc 100644
--- a/src/test/regress/sql/reloptions.sql
+++ b/src/test/regress/sql/reloptions.sql
@@ -59,6 +59,27 @@ UPDATE pg_class
ALTER TABLE reloptions_test RESET (illegal_option);
SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass;
+-- Tests for future (FIXME) ternary options
+
+-- behave as boolean option: accept unassigned name and truncated value
+DROP TABLE reloptions_test;
+CREATE TABLE reloptions_test(i INT) WITH (vacuum_truncate);
+SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass;
+
+DROP TABLE reloptions_test;
+CREATE TABLE reloptions_test(i INT) WITH (vacuum_truncate=FaLS);
+SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass;
+
+-- preferred "true" alias is used when storing
+DROP TABLE reloptions_test;
+CREATE TABLE reloptions_test(i INT) WITH (vacuum_index_cleanup=on);
+SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass;
+
+-- custom "third" value is available
+DROP TABLE reloptions_test;
+CREATE TABLE reloptions_test(i INT) WITH (vacuum_index_cleanup=auto);
+SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass;
+
-- Test vacuum_truncate option
DROP TABLE reloptions_test;
diff --git a/src/tools/pgindent/typedefs.list b/src/tools/pgindent/typedefs.list
index 3f3a888fd0e..1c8610fd46c 100644
--- a/src/tools/pgindent/typedefs.list
+++ b/src/tools/pgindent/typedefs.list
@@ -3952,6 +3952,7 @@ pg_sha512_ctx
pg_snapshot
pg_special_case
pg_stack_base_t
+pg_ternary
pg_time_t
pg_time_usec_t
pg_tz
@@ -4079,6 +4080,7 @@ relopt_kind
relopt_parse_elt
relopt_real
relopt_string
+relopt_ternary
relopt_type
relopt_value
relopts_validator
--
2.47.3
--c36t3fndxhkzbl5z--
^ permalink raw reply [nested|flat] 868+ messages in thread
* [PATCH v3] Introduce ternary reloptions
@ 2026-01-16 15:04 Álvaro Herrera <[email protected]>
0 siblings, 0 replies; 868+ messages in thread
From: Álvaro Herrera @ 2026-01-16 15:04 UTC (permalink / raw)
Introduce ternary reloption as a replacement for current
`vacuum_truncate` implementation. Remove the `vacuum_truncate_set`
separate flag and use TERNARY_UNSET instead.
This could also be used for other options such as `vacuum_index_cleanup`
and `buffering`, but lets get the scaffolding in first.
Discussion: https://postgr.es/m/3474141.usfYGdeWWP@thinkpad-pgpro
---
src/backend/access/common/reloptions.c | 137 ++++++++++++++++++-----
src/backend/commands/vacuum.c | 4 +-
src/include/access/reloptions.h | 27 ++---
src/include/postgres.h | 15 +++
src/include/utils/rel.h | 3 +-
src/test/regress/expected/reloptions.out | 36 ++++++
src/test/regress/sql/reloptions.sql | 21 ++++
src/tools/pgindent/typedefs.list | 2 +
8 files changed, 202 insertions(+), 43 deletions(-)
diff --git a/src/backend/access/common/reloptions.c b/src/backend/access/common/reloptions.c
index 0b83f98ed5f..6f1f577581a 100644
--- a/src/backend/access/common/reloptions.c
+++ b/src/backend/access/common/reloptions.c
@@ -40,9 +40,9 @@
*
* To add an option:
*
- * (i) decide on a type (bool, integer, real, enum, string), name, default
- * value, upper and lower bounds (if applicable); for strings, consider a
- * validation routine.
+ * (i) decide on a type (bool, ternary, integer, real, enum, string), name,
+ * default value, upper and lower bounds (if applicable); for strings,
+ * consider a validation routine.
* (ii) add a record below (or use add_<type>_reloption).
* (iii) add it to the appropriate options struct (perhaps StdRdOptions)
* (iv) add it to the appropriate handling routine (perhaps
@@ -147,15 +147,6 @@ static relopt_bool boolRelOpts[] =
},
false
},
- {
- {
- "vacuum_truncate",
- "Enables vacuum to truncate empty pages at the end of this table",
- RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
- ShareUpdateExclusiveLock
- },
- true
- },
{
{
"deduplicate_items",
@@ -170,6 +161,25 @@ static relopt_bool boolRelOpts[] =
{{NULL}}
};
+static relopt_ternary ternaryRelOpts[] =
+{
+ {
+ {
+ "vacuum_truncate",
+ "Enables vacuum to truncate empty pages at the end of this table",
+ RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
+ ShareUpdateExclusiveLock
+ },
+ TERNARY_UNSET
+ },
+ /* list terminator */
+ {
+ {
+ NULL
+ }
+ }
+};
+
static relopt_int intRelOpts[] =
{
{
@@ -609,6 +619,13 @@ initialize_reloptions(void)
boolRelOpts[i].gen.lockmode));
j++;
}
+ for (i = 0; ternaryRelOpts[i].gen.name; i++)
+ {
+ Assert(DoLockModesConflict(ternaryRelOpts[i].gen.lockmode,
+ ternaryRelOpts[i].gen.lockmode));
+ j++;
+ }
+
for (i = 0; intRelOpts[i].gen.name; i++)
{
Assert(DoLockModesConflict(intRelOpts[i].gen.lockmode,
@@ -649,6 +666,14 @@ initialize_reloptions(void)
j++;
}
+ for (i = 0; ternaryRelOpts[i].gen.name; i++)
+ {
+ relOpts[j] = &ternaryRelOpts[i].gen;
+ relOpts[j]->type = RELOPT_TYPE_TERNARY;
+ relOpts[j]->namelen = strlen(relOpts[j]->name);
+ j++;
+ }
+
for (i = 0; intRelOpts[i].gen.name; i++)
{
relOpts[j] = &intRelOpts[i].gen;
@@ -809,6 +834,9 @@ allocate_reloption(bits32 kinds, int type, const char *name, const char *desc,
case RELOPT_TYPE_BOOL:
size = sizeof(relopt_bool);
break;
+ case RELOPT_TYPE_TERNARY:
+ size = sizeof(relopt_ternary);
+ break;
case RELOPT_TYPE_INT:
size = sizeof(relopt_int);
break;
@@ -892,6 +920,57 @@ add_local_bool_reloption(local_relopts *relopts, const char *name,
add_local_reloption(relopts, (relopt_gen *) newoption, offset);
}
+/*
+ * init_ternary_reloption
+ * Allocate and initialize a new ternary reloption
+ */
+static relopt_ternary *
+init_ternary_reloption(bits32 kinds, const char *name, const char *desc,
+ pg_ternary default_val, LOCKMODE lockmode)
+{
+ relopt_ternary *newoption;
+
+ newoption = (relopt_ternary *)
+ allocate_reloption(kinds, RELOPT_TYPE_TERNARY, name, desc, lockmode);
+ newoption->default_val = default_val;
+
+ return newoption;
+}
+
+/*
+ * add_ternary_reloption
+ * Add a new ternary reloption
+ */
+void
+add_ternary_reloption(bits32 kinds, const char *name, const char *desc,
+ pg_ternary default_val, LOCKMODE lockmode)
+{
+ relopt_ternary *newoption;
+
+ newoption =
+ init_ternary_reloption(kinds, name, desc, default_val, lockmode);
+
+ add_reloption((relopt_gen *) newoption);
+}
+
+/*
+ * add_local_ternary_reloption
+ * Add a new ternary local reloption
+ *
+ * 'offset' is offset of ternary-typed field.
+ */
+void
+add_local_ternary_reloption(local_relopts *relopts, const char *name,
+ const char *desc, pg_ternary default_val,
+ int offset)
+{
+ relopt_ternary *newoption;
+
+ newoption =
+ init_ternary_reloption(RELOPT_KIND_LOCAL, name, desc, default_val, 0);
+
+ add_local_reloption(relopts, (relopt_gen *) newoption, offset);
+}
/*
* init_real_reloption
@@ -1626,6 +1705,19 @@ parse_one_reloption(relopt_value *option, char *text_str, int text_len,
option->gen->name, value)));
}
break;
+ case RELOPT_TYPE_TERNARY:
+ {
+ bool b;
+
+ parsed = parse_bool(value, &b);
+ option->values.ternary_val = b ? TERNARY_TRUE : TERNARY_FALSE;
+ if (validate && !parsed)
+ ereport(ERROR,
+ (errcode(ERRCODE_INVALID_PARAMETER_VALUE),
+ errmsg("invalid value for ternary option \"%s\": %s",
+ option->gen->name, value)));
+ }
+ break;
case RELOPT_TYPE_INT:
{
relopt_int *optint = (relopt_int *) option->gen;
@@ -1789,17 +1881,6 @@ fillRelOptions(void *rdopts, Size basesize,
char *itempos = ((char *) rdopts) + elems[j].offset;
char *string_val;
- /*
- * If isset_offset is provided, store whether the reloption is
- * set there.
- */
- if (elems[j].isset_offset > 0)
- {
- char *setpos = ((char *) rdopts) + elems[j].isset_offset;
-
- *(bool *) setpos = options[i].isset;
- }
-
switch (options[i].gen->type)
{
case RELOPT_TYPE_BOOL:
@@ -1807,6 +1888,11 @@ fillRelOptions(void *rdopts, Size basesize,
options[i].values.bool_val :
((relopt_bool *) options[i].gen)->default_val;
break;
+ case RELOPT_TYPE_TERNARY:
+ *(pg_ternary *) itempos = options[i].isset ?
+ options[i].values.ternary_val :
+ ((relopt_ternary *) options[i].gen)->default_val;
+ break;
case RELOPT_TYPE_INT:
*(int *) itempos = options[i].isset ?
options[i].values.int_val :
@@ -1923,8 +2009,8 @@ default_reloptions(Datum reloptions, bool validate, relopt_kind kind)
offsetof(StdRdOptions, parallel_workers)},
{"vacuum_index_cleanup", RELOPT_TYPE_ENUM,
offsetof(StdRdOptions, vacuum_index_cleanup)},
- {"vacuum_truncate", RELOPT_TYPE_BOOL,
- offsetof(StdRdOptions, vacuum_truncate), offsetof(StdRdOptions, vacuum_truncate_set)},
+ {"vacuum_truncate", RELOPT_TYPE_TERNARY,
+ offsetof(StdRdOptions, vacuum_truncate)},
{"vacuum_max_eager_freeze_failure_rate", RELOPT_TYPE_REAL,
offsetof(StdRdOptions, vacuum_max_eager_freeze_failure_rate)}
};
@@ -2004,7 +2090,6 @@ build_local_reloptions(local_relopts *relopts, Datum options, bool validate)
elems[i].optname = opt->option->name;
elems[i].opttype = opt->option->type;
elems[i].offset = opt->offset;
- elems[i].isset_offset = 0; /* not supported for local relopts yet */
i++;
}
diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c
index aa4fbec143f..696eab9bd97 100644
--- a/src/backend/commands/vacuum.c
+++ b/src/backend/commands/vacuum.c
@@ -2224,9 +2224,9 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams params,
{
StdRdOptions *opts = (StdRdOptions *) rel->rd_options;
- if (opts && opts->vacuum_truncate_set)
+ if (opts && opts->vacuum_truncate != TERNARY_UNSET)
{
- if (opts->vacuum_truncate)
+ if (opts->vacuum_truncate == TERNARY_TRUE)
params.truncate = VACOPTVALUE_ENABLED;
else
params.truncate = VACOPTVALUE_DISABLED;
diff --git a/src/include/access/reloptions.h b/src/include/access/reloptions.h
index 2f08e1b0cf0..dfbef2babf2 100644
--- a/src/include/access/reloptions.h
+++ b/src/include/access/reloptions.h
@@ -29,6 +29,7 @@
typedef enum relopt_type
{
RELOPT_TYPE_BOOL,
+ RELOPT_TYPE_TERNARY, /* on, off, unset */
RELOPT_TYPE_INT,
RELOPT_TYPE_REAL,
RELOPT_TYPE_ENUM,
@@ -80,6 +81,7 @@ typedef struct relopt_value
union
{
bool bool_val;
+ pg_ternary ternary_val;
int int_val;
double real_val;
int enum_val;
@@ -94,6 +96,12 @@ typedef struct relopt_bool
bool default_val;
} relopt_bool;
+typedef struct relopt_ternary
+{
+ relopt_gen gen;
+ int default_val;
+} relopt_ternary;
+
typedef struct relopt_int
{
relopt_gen gen;
@@ -152,19 +160,6 @@ typedef struct
const char *optname; /* option's name */
relopt_type opttype; /* option's datatype */
int offset; /* offset of field in result struct */
-
- /*
- * isset_offset is an optional offset of a field in the result struct that
- * stores whether the option is explicitly set for the relation or if it
- * just picked up the default value. In most cases, this can be
- * accomplished by giving the reloption a special out-of-range default
- * value (e.g., some integer reloptions use -2), but this isn't always
- * possible. For example, a Boolean reloption cannot be given an
- * out-of-range default, so we need another way to discover the source of
- * its value. This offset is only used if given a value greater than
- * zero.
- */
- int isset_offset;
} relopt_parse_elt;
/* Local reloption definition */
@@ -195,6 +190,9 @@ typedef struct local_relopts
extern relopt_kind add_reloption_kind(void);
extern void add_bool_reloption(bits32 kinds, const char *name, const char *desc,
bool default_val, LOCKMODE lockmode);
+extern void add_ternary_reloption(bits32 kinds, const char *name,
+ const char *desc, pg_ternary default_val,
+ LOCKMODE lockmode);
extern void add_int_reloption(bits32 kinds, const char *name, const char *desc,
int default_val, int min_val, int max_val,
LOCKMODE lockmode);
@@ -214,6 +212,9 @@ extern void register_reloptions_validator(local_relopts *relopts,
extern void add_local_bool_reloption(local_relopts *relopts, const char *name,
const char *desc, bool default_val,
int offset);
+extern void add_local_ternary_reloption(local_relopts *relopts,
+ const char *name, const char *desc,
+ pg_ternary default_val, int offset);
extern void add_local_int_reloption(local_relopts *relopts, const char *name,
const char *desc, int default_val,
int min_val, int max_val, int offset);
diff --git a/src/include/postgres.h b/src/include/postgres.h
index 7d93fbce709..47c88780776 100644
--- a/src/include/postgres.h
+++ b/src/include/postgres.h
@@ -543,6 +543,21 @@ Float8GetDatum(float8 X)
* ----------------------------------------------------------------
*/
+/*
+ * pg_ternary
+ * Boolean value with an extra "unset" value
+ *
+ * This enum can be used for values that want to distinguish between true,
+ * false, and unset.
+*/
+
+typedef enum pg_ternary
+{
+ TERNARY_FALSE = 0,
+ TERNARY_TRUE = 1,
+ TERNARY_UNSET = -1
+} pg_ternary;
+
/*
* NON_EXEC_STATIC: It's sometimes useful to define a variable or function
* that is normally static but extern when using EXEC_BACKEND (see
diff --git a/src/include/utils/rel.h b/src/include/utils/rel.h
index d03ab247788..5feb18a5373 100644
--- a/src/include/utils/rel.h
+++ b/src/include/utils/rel.h
@@ -347,8 +347,7 @@ typedef struct StdRdOptions
bool user_catalog_table; /* use as an additional catalog relation */
int parallel_workers; /* max number of parallel workers */
StdRdOptIndexCleanup vacuum_index_cleanup; /* controls index vacuuming */
- bool vacuum_truncate; /* enables vacuum to truncate a relation */
- bool vacuum_truncate_set; /* whether vacuum_truncate is set */
+ pg_ternary vacuum_truncate; /* enables vacuum to truncate a relation */
/*
* Fraction of pages in a relation that vacuum can eagerly scan and fail
diff --git a/src/test/regress/expected/reloptions.out b/src/test/regress/expected/reloptions.out
index 9de19b4e3f1..1c99f79ab01 100644
--- a/src/test/regress/expected/reloptions.out
+++ b/src/test/regress/expected/reloptions.out
@@ -98,6 +98,42 @@ SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass;
{fillfactor=13,autovacuum_enabled=false}
(1 row)
+-- Tests for future (FIXME) ternary options
+-- behave as boolean option: accept unassigned name and truncated value
+DROP TABLE reloptions_test;
+CREATE TABLE reloptions_test(i INT) WITH (vacuum_truncate);
+SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass;
+ reloptions
+------------------------
+ {vacuum_truncate=true}
+(1 row)
+
+DROP TABLE reloptions_test;
+CREATE TABLE reloptions_test(i INT) WITH (vacuum_truncate=FaLS);
+SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass;
+ reloptions
+------------------------
+ {vacuum_truncate=fals}
+(1 row)
+
+-- preferred "true" alias is used when storing
+DROP TABLE reloptions_test;
+CREATE TABLE reloptions_test(i INT) WITH (vacuum_index_cleanup=on);
+SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass;
+ reloptions
+---------------------------
+ {vacuum_index_cleanup=on}
+(1 row)
+
+-- custom "third" value is available
+DROP TABLE reloptions_test;
+CREATE TABLE reloptions_test(i INT) WITH (vacuum_index_cleanup=auto);
+SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass;
+ reloptions
+-----------------------------
+ {vacuum_index_cleanup=auto}
+(1 row)
+
-- Test vacuum_truncate option
DROP TABLE reloptions_test;
CREATE TEMP TABLE reloptions_test(i INT NOT NULL, j text)
diff --git a/src/test/regress/sql/reloptions.sql b/src/test/regress/sql/reloptions.sql
index 24fbe0b478d..f5980dafcbc 100644
--- a/src/test/regress/sql/reloptions.sql
+++ b/src/test/regress/sql/reloptions.sql
@@ -59,6 +59,27 @@ UPDATE pg_class
ALTER TABLE reloptions_test RESET (illegal_option);
SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass;
+-- Tests for future (FIXME) ternary options
+
+-- behave as boolean option: accept unassigned name and truncated value
+DROP TABLE reloptions_test;
+CREATE TABLE reloptions_test(i INT) WITH (vacuum_truncate);
+SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass;
+
+DROP TABLE reloptions_test;
+CREATE TABLE reloptions_test(i INT) WITH (vacuum_truncate=FaLS);
+SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass;
+
+-- preferred "true" alias is used when storing
+DROP TABLE reloptions_test;
+CREATE TABLE reloptions_test(i INT) WITH (vacuum_index_cleanup=on);
+SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass;
+
+-- custom "third" value is available
+DROP TABLE reloptions_test;
+CREATE TABLE reloptions_test(i INT) WITH (vacuum_index_cleanup=auto);
+SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass;
+
-- Test vacuum_truncate option
DROP TABLE reloptions_test;
diff --git a/src/tools/pgindent/typedefs.list b/src/tools/pgindent/typedefs.list
index 3f3a888fd0e..1c8610fd46c 100644
--- a/src/tools/pgindent/typedefs.list
+++ b/src/tools/pgindent/typedefs.list
@@ -3952,6 +3952,7 @@ pg_sha512_ctx
pg_snapshot
pg_special_case
pg_stack_base_t
+pg_ternary
pg_time_t
pg_time_usec_t
pg_tz
@@ -4079,6 +4080,7 @@ relopt_kind
relopt_parse_elt
relopt_real
relopt_string
+relopt_ternary
relopt_type
relopt_value
relopts_validator
--
2.47.3
--c36t3fndxhkzbl5z--
^ permalink raw reply [nested|flat] 868+ messages in thread
* [PATCH v3] Introduce ternary reloptions
@ 2026-01-16 15:04 Álvaro Herrera <[email protected]>
0 siblings, 0 replies; 868+ messages in thread
From: Álvaro Herrera @ 2026-01-16 15:04 UTC (permalink / raw)
Introduce ternary reloption as a replacement for current
`vacuum_truncate` implementation. Remove the `vacuum_truncate_set`
separate flag and use TERNARY_UNSET instead.
This could also be used for other options such as `vacuum_index_cleanup`
and `buffering`, but lets get the scaffolding in first.
Discussion: https://postgr.es/m/3474141.usfYGdeWWP@thinkpad-pgpro
---
src/backend/access/common/reloptions.c | 137 ++++++++++++++++++-----
src/backend/commands/vacuum.c | 4 +-
src/include/access/reloptions.h | 27 ++---
src/include/postgres.h | 15 +++
src/include/utils/rel.h | 3 +-
src/test/regress/expected/reloptions.out | 36 ++++++
src/test/regress/sql/reloptions.sql | 21 ++++
src/tools/pgindent/typedefs.list | 2 +
8 files changed, 202 insertions(+), 43 deletions(-)
diff --git a/src/backend/access/common/reloptions.c b/src/backend/access/common/reloptions.c
index 0b83f98ed5f..6f1f577581a 100644
--- a/src/backend/access/common/reloptions.c
+++ b/src/backend/access/common/reloptions.c
@@ -40,9 +40,9 @@
*
* To add an option:
*
- * (i) decide on a type (bool, integer, real, enum, string), name, default
- * value, upper and lower bounds (if applicable); for strings, consider a
- * validation routine.
+ * (i) decide on a type (bool, ternary, integer, real, enum, string), name,
+ * default value, upper and lower bounds (if applicable); for strings,
+ * consider a validation routine.
* (ii) add a record below (or use add_<type>_reloption).
* (iii) add it to the appropriate options struct (perhaps StdRdOptions)
* (iv) add it to the appropriate handling routine (perhaps
@@ -147,15 +147,6 @@ static relopt_bool boolRelOpts[] =
},
false
},
- {
- {
- "vacuum_truncate",
- "Enables vacuum to truncate empty pages at the end of this table",
- RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
- ShareUpdateExclusiveLock
- },
- true
- },
{
{
"deduplicate_items",
@@ -170,6 +161,25 @@ static relopt_bool boolRelOpts[] =
{{NULL}}
};
+static relopt_ternary ternaryRelOpts[] =
+{
+ {
+ {
+ "vacuum_truncate",
+ "Enables vacuum to truncate empty pages at the end of this table",
+ RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
+ ShareUpdateExclusiveLock
+ },
+ TERNARY_UNSET
+ },
+ /* list terminator */
+ {
+ {
+ NULL
+ }
+ }
+};
+
static relopt_int intRelOpts[] =
{
{
@@ -609,6 +619,13 @@ initialize_reloptions(void)
boolRelOpts[i].gen.lockmode));
j++;
}
+ for (i = 0; ternaryRelOpts[i].gen.name; i++)
+ {
+ Assert(DoLockModesConflict(ternaryRelOpts[i].gen.lockmode,
+ ternaryRelOpts[i].gen.lockmode));
+ j++;
+ }
+
for (i = 0; intRelOpts[i].gen.name; i++)
{
Assert(DoLockModesConflict(intRelOpts[i].gen.lockmode,
@@ -649,6 +666,14 @@ initialize_reloptions(void)
j++;
}
+ for (i = 0; ternaryRelOpts[i].gen.name; i++)
+ {
+ relOpts[j] = &ternaryRelOpts[i].gen;
+ relOpts[j]->type = RELOPT_TYPE_TERNARY;
+ relOpts[j]->namelen = strlen(relOpts[j]->name);
+ j++;
+ }
+
for (i = 0; intRelOpts[i].gen.name; i++)
{
relOpts[j] = &intRelOpts[i].gen;
@@ -809,6 +834,9 @@ allocate_reloption(bits32 kinds, int type, const char *name, const char *desc,
case RELOPT_TYPE_BOOL:
size = sizeof(relopt_bool);
break;
+ case RELOPT_TYPE_TERNARY:
+ size = sizeof(relopt_ternary);
+ break;
case RELOPT_TYPE_INT:
size = sizeof(relopt_int);
break;
@@ -892,6 +920,57 @@ add_local_bool_reloption(local_relopts *relopts, const char *name,
add_local_reloption(relopts, (relopt_gen *) newoption, offset);
}
+/*
+ * init_ternary_reloption
+ * Allocate and initialize a new ternary reloption
+ */
+static relopt_ternary *
+init_ternary_reloption(bits32 kinds, const char *name, const char *desc,
+ pg_ternary default_val, LOCKMODE lockmode)
+{
+ relopt_ternary *newoption;
+
+ newoption = (relopt_ternary *)
+ allocate_reloption(kinds, RELOPT_TYPE_TERNARY, name, desc, lockmode);
+ newoption->default_val = default_val;
+
+ return newoption;
+}
+
+/*
+ * add_ternary_reloption
+ * Add a new ternary reloption
+ */
+void
+add_ternary_reloption(bits32 kinds, const char *name, const char *desc,
+ pg_ternary default_val, LOCKMODE lockmode)
+{
+ relopt_ternary *newoption;
+
+ newoption =
+ init_ternary_reloption(kinds, name, desc, default_val, lockmode);
+
+ add_reloption((relopt_gen *) newoption);
+}
+
+/*
+ * add_local_ternary_reloption
+ * Add a new ternary local reloption
+ *
+ * 'offset' is offset of ternary-typed field.
+ */
+void
+add_local_ternary_reloption(local_relopts *relopts, const char *name,
+ const char *desc, pg_ternary default_val,
+ int offset)
+{
+ relopt_ternary *newoption;
+
+ newoption =
+ init_ternary_reloption(RELOPT_KIND_LOCAL, name, desc, default_val, 0);
+
+ add_local_reloption(relopts, (relopt_gen *) newoption, offset);
+}
/*
* init_real_reloption
@@ -1626,6 +1705,19 @@ parse_one_reloption(relopt_value *option, char *text_str, int text_len,
option->gen->name, value)));
}
break;
+ case RELOPT_TYPE_TERNARY:
+ {
+ bool b;
+
+ parsed = parse_bool(value, &b);
+ option->values.ternary_val = b ? TERNARY_TRUE : TERNARY_FALSE;
+ if (validate && !parsed)
+ ereport(ERROR,
+ (errcode(ERRCODE_INVALID_PARAMETER_VALUE),
+ errmsg("invalid value for ternary option \"%s\": %s",
+ option->gen->name, value)));
+ }
+ break;
case RELOPT_TYPE_INT:
{
relopt_int *optint = (relopt_int *) option->gen;
@@ -1789,17 +1881,6 @@ fillRelOptions(void *rdopts, Size basesize,
char *itempos = ((char *) rdopts) + elems[j].offset;
char *string_val;
- /*
- * If isset_offset is provided, store whether the reloption is
- * set there.
- */
- if (elems[j].isset_offset > 0)
- {
- char *setpos = ((char *) rdopts) + elems[j].isset_offset;
-
- *(bool *) setpos = options[i].isset;
- }
-
switch (options[i].gen->type)
{
case RELOPT_TYPE_BOOL:
@@ -1807,6 +1888,11 @@ fillRelOptions(void *rdopts, Size basesize,
options[i].values.bool_val :
((relopt_bool *) options[i].gen)->default_val;
break;
+ case RELOPT_TYPE_TERNARY:
+ *(pg_ternary *) itempos = options[i].isset ?
+ options[i].values.ternary_val :
+ ((relopt_ternary *) options[i].gen)->default_val;
+ break;
case RELOPT_TYPE_INT:
*(int *) itempos = options[i].isset ?
options[i].values.int_val :
@@ -1923,8 +2009,8 @@ default_reloptions(Datum reloptions, bool validate, relopt_kind kind)
offsetof(StdRdOptions, parallel_workers)},
{"vacuum_index_cleanup", RELOPT_TYPE_ENUM,
offsetof(StdRdOptions, vacuum_index_cleanup)},
- {"vacuum_truncate", RELOPT_TYPE_BOOL,
- offsetof(StdRdOptions, vacuum_truncate), offsetof(StdRdOptions, vacuum_truncate_set)},
+ {"vacuum_truncate", RELOPT_TYPE_TERNARY,
+ offsetof(StdRdOptions, vacuum_truncate)},
{"vacuum_max_eager_freeze_failure_rate", RELOPT_TYPE_REAL,
offsetof(StdRdOptions, vacuum_max_eager_freeze_failure_rate)}
};
@@ -2004,7 +2090,6 @@ build_local_reloptions(local_relopts *relopts, Datum options, bool validate)
elems[i].optname = opt->option->name;
elems[i].opttype = opt->option->type;
elems[i].offset = opt->offset;
- elems[i].isset_offset = 0; /* not supported for local relopts yet */
i++;
}
diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c
index aa4fbec143f..696eab9bd97 100644
--- a/src/backend/commands/vacuum.c
+++ b/src/backend/commands/vacuum.c
@@ -2224,9 +2224,9 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams params,
{
StdRdOptions *opts = (StdRdOptions *) rel->rd_options;
- if (opts && opts->vacuum_truncate_set)
+ if (opts && opts->vacuum_truncate != TERNARY_UNSET)
{
- if (opts->vacuum_truncate)
+ if (opts->vacuum_truncate == TERNARY_TRUE)
params.truncate = VACOPTVALUE_ENABLED;
else
params.truncate = VACOPTVALUE_DISABLED;
diff --git a/src/include/access/reloptions.h b/src/include/access/reloptions.h
index 2f08e1b0cf0..dfbef2babf2 100644
--- a/src/include/access/reloptions.h
+++ b/src/include/access/reloptions.h
@@ -29,6 +29,7 @@
typedef enum relopt_type
{
RELOPT_TYPE_BOOL,
+ RELOPT_TYPE_TERNARY, /* on, off, unset */
RELOPT_TYPE_INT,
RELOPT_TYPE_REAL,
RELOPT_TYPE_ENUM,
@@ -80,6 +81,7 @@ typedef struct relopt_value
union
{
bool bool_val;
+ pg_ternary ternary_val;
int int_val;
double real_val;
int enum_val;
@@ -94,6 +96,12 @@ typedef struct relopt_bool
bool default_val;
} relopt_bool;
+typedef struct relopt_ternary
+{
+ relopt_gen gen;
+ int default_val;
+} relopt_ternary;
+
typedef struct relopt_int
{
relopt_gen gen;
@@ -152,19 +160,6 @@ typedef struct
const char *optname; /* option's name */
relopt_type opttype; /* option's datatype */
int offset; /* offset of field in result struct */
-
- /*
- * isset_offset is an optional offset of a field in the result struct that
- * stores whether the option is explicitly set for the relation or if it
- * just picked up the default value. In most cases, this can be
- * accomplished by giving the reloption a special out-of-range default
- * value (e.g., some integer reloptions use -2), but this isn't always
- * possible. For example, a Boolean reloption cannot be given an
- * out-of-range default, so we need another way to discover the source of
- * its value. This offset is only used if given a value greater than
- * zero.
- */
- int isset_offset;
} relopt_parse_elt;
/* Local reloption definition */
@@ -195,6 +190,9 @@ typedef struct local_relopts
extern relopt_kind add_reloption_kind(void);
extern void add_bool_reloption(bits32 kinds, const char *name, const char *desc,
bool default_val, LOCKMODE lockmode);
+extern void add_ternary_reloption(bits32 kinds, const char *name,
+ const char *desc, pg_ternary default_val,
+ LOCKMODE lockmode);
extern void add_int_reloption(bits32 kinds, const char *name, const char *desc,
int default_val, int min_val, int max_val,
LOCKMODE lockmode);
@@ -214,6 +212,9 @@ extern void register_reloptions_validator(local_relopts *relopts,
extern void add_local_bool_reloption(local_relopts *relopts, const char *name,
const char *desc, bool default_val,
int offset);
+extern void add_local_ternary_reloption(local_relopts *relopts,
+ const char *name, const char *desc,
+ pg_ternary default_val, int offset);
extern void add_local_int_reloption(local_relopts *relopts, const char *name,
const char *desc, int default_val,
int min_val, int max_val, int offset);
diff --git a/src/include/postgres.h b/src/include/postgres.h
index 7d93fbce709..47c88780776 100644
--- a/src/include/postgres.h
+++ b/src/include/postgres.h
@@ -543,6 +543,21 @@ Float8GetDatum(float8 X)
* ----------------------------------------------------------------
*/
+/*
+ * pg_ternary
+ * Boolean value with an extra "unset" value
+ *
+ * This enum can be used for values that want to distinguish between true,
+ * false, and unset.
+*/
+
+typedef enum pg_ternary
+{
+ TERNARY_FALSE = 0,
+ TERNARY_TRUE = 1,
+ TERNARY_UNSET = -1
+} pg_ternary;
+
/*
* NON_EXEC_STATIC: It's sometimes useful to define a variable or function
* that is normally static but extern when using EXEC_BACKEND (see
diff --git a/src/include/utils/rel.h b/src/include/utils/rel.h
index d03ab247788..5feb18a5373 100644
--- a/src/include/utils/rel.h
+++ b/src/include/utils/rel.h
@@ -347,8 +347,7 @@ typedef struct StdRdOptions
bool user_catalog_table; /* use as an additional catalog relation */
int parallel_workers; /* max number of parallel workers */
StdRdOptIndexCleanup vacuum_index_cleanup; /* controls index vacuuming */
- bool vacuum_truncate; /* enables vacuum to truncate a relation */
- bool vacuum_truncate_set; /* whether vacuum_truncate is set */
+ pg_ternary vacuum_truncate; /* enables vacuum to truncate a relation */
/*
* Fraction of pages in a relation that vacuum can eagerly scan and fail
diff --git a/src/test/regress/expected/reloptions.out b/src/test/regress/expected/reloptions.out
index 9de19b4e3f1..1c99f79ab01 100644
--- a/src/test/regress/expected/reloptions.out
+++ b/src/test/regress/expected/reloptions.out
@@ -98,6 +98,42 @@ SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass;
{fillfactor=13,autovacuum_enabled=false}
(1 row)
+-- Tests for future (FIXME) ternary options
+-- behave as boolean option: accept unassigned name and truncated value
+DROP TABLE reloptions_test;
+CREATE TABLE reloptions_test(i INT) WITH (vacuum_truncate);
+SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass;
+ reloptions
+------------------------
+ {vacuum_truncate=true}
+(1 row)
+
+DROP TABLE reloptions_test;
+CREATE TABLE reloptions_test(i INT) WITH (vacuum_truncate=FaLS);
+SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass;
+ reloptions
+------------------------
+ {vacuum_truncate=fals}
+(1 row)
+
+-- preferred "true" alias is used when storing
+DROP TABLE reloptions_test;
+CREATE TABLE reloptions_test(i INT) WITH (vacuum_index_cleanup=on);
+SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass;
+ reloptions
+---------------------------
+ {vacuum_index_cleanup=on}
+(1 row)
+
+-- custom "third" value is available
+DROP TABLE reloptions_test;
+CREATE TABLE reloptions_test(i INT) WITH (vacuum_index_cleanup=auto);
+SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass;
+ reloptions
+-----------------------------
+ {vacuum_index_cleanup=auto}
+(1 row)
+
-- Test vacuum_truncate option
DROP TABLE reloptions_test;
CREATE TEMP TABLE reloptions_test(i INT NOT NULL, j text)
diff --git a/src/test/regress/sql/reloptions.sql b/src/test/regress/sql/reloptions.sql
index 24fbe0b478d..f5980dafcbc 100644
--- a/src/test/regress/sql/reloptions.sql
+++ b/src/test/regress/sql/reloptions.sql
@@ -59,6 +59,27 @@ UPDATE pg_class
ALTER TABLE reloptions_test RESET (illegal_option);
SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass;
+-- Tests for future (FIXME) ternary options
+
+-- behave as boolean option: accept unassigned name and truncated value
+DROP TABLE reloptions_test;
+CREATE TABLE reloptions_test(i INT) WITH (vacuum_truncate);
+SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass;
+
+DROP TABLE reloptions_test;
+CREATE TABLE reloptions_test(i INT) WITH (vacuum_truncate=FaLS);
+SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass;
+
+-- preferred "true" alias is used when storing
+DROP TABLE reloptions_test;
+CREATE TABLE reloptions_test(i INT) WITH (vacuum_index_cleanup=on);
+SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass;
+
+-- custom "third" value is available
+DROP TABLE reloptions_test;
+CREATE TABLE reloptions_test(i INT) WITH (vacuum_index_cleanup=auto);
+SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass;
+
-- Test vacuum_truncate option
DROP TABLE reloptions_test;
diff --git a/src/tools/pgindent/typedefs.list b/src/tools/pgindent/typedefs.list
index 3f3a888fd0e..1c8610fd46c 100644
--- a/src/tools/pgindent/typedefs.list
+++ b/src/tools/pgindent/typedefs.list
@@ -3952,6 +3952,7 @@ pg_sha512_ctx
pg_snapshot
pg_special_case
pg_stack_base_t
+pg_ternary
pg_time_t
pg_time_usec_t
pg_tz
@@ -4079,6 +4080,7 @@ relopt_kind
relopt_parse_elt
relopt_real
relopt_string
+relopt_ternary
relopt_type
relopt_value
relopts_validator
--
2.47.3
--c36t3fndxhkzbl5z--
^ permalink raw reply [nested|flat] 868+ messages in thread
* [PATCH v3] Introduce ternary reloptions
@ 2026-01-16 15:04 Álvaro Herrera <[email protected]>
0 siblings, 0 replies; 868+ messages in thread
From: Álvaro Herrera @ 2026-01-16 15:04 UTC (permalink / raw)
Introduce ternary reloption as a replacement for current
`vacuum_truncate` implementation. Remove the `vacuum_truncate_set`
separate flag and use TERNARY_UNSET instead.
This could also be used for other options such as `vacuum_index_cleanup`
and `buffering`, but lets get the scaffolding in first.
Discussion: https://postgr.es/m/3474141.usfYGdeWWP@thinkpad-pgpro
---
src/backend/access/common/reloptions.c | 137 ++++++++++++++++++-----
src/backend/commands/vacuum.c | 4 +-
src/include/access/reloptions.h | 27 ++---
src/include/postgres.h | 15 +++
src/include/utils/rel.h | 3 +-
src/test/regress/expected/reloptions.out | 36 ++++++
src/test/regress/sql/reloptions.sql | 21 ++++
src/tools/pgindent/typedefs.list | 2 +
8 files changed, 202 insertions(+), 43 deletions(-)
diff --git a/src/backend/access/common/reloptions.c b/src/backend/access/common/reloptions.c
index 0b83f98ed5f..6f1f577581a 100644
--- a/src/backend/access/common/reloptions.c
+++ b/src/backend/access/common/reloptions.c
@@ -40,9 +40,9 @@
*
* To add an option:
*
- * (i) decide on a type (bool, integer, real, enum, string), name, default
- * value, upper and lower bounds (if applicable); for strings, consider a
- * validation routine.
+ * (i) decide on a type (bool, ternary, integer, real, enum, string), name,
+ * default value, upper and lower bounds (if applicable); for strings,
+ * consider a validation routine.
* (ii) add a record below (or use add_<type>_reloption).
* (iii) add it to the appropriate options struct (perhaps StdRdOptions)
* (iv) add it to the appropriate handling routine (perhaps
@@ -147,15 +147,6 @@ static relopt_bool boolRelOpts[] =
},
false
},
- {
- {
- "vacuum_truncate",
- "Enables vacuum to truncate empty pages at the end of this table",
- RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
- ShareUpdateExclusiveLock
- },
- true
- },
{
{
"deduplicate_items",
@@ -170,6 +161,25 @@ static relopt_bool boolRelOpts[] =
{{NULL}}
};
+static relopt_ternary ternaryRelOpts[] =
+{
+ {
+ {
+ "vacuum_truncate",
+ "Enables vacuum to truncate empty pages at the end of this table",
+ RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
+ ShareUpdateExclusiveLock
+ },
+ TERNARY_UNSET
+ },
+ /* list terminator */
+ {
+ {
+ NULL
+ }
+ }
+};
+
static relopt_int intRelOpts[] =
{
{
@@ -609,6 +619,13 @@ initialize_reloptions(void)
boolRelOpts[i].gen.lockmode));
j++;
}
+ for (i = 0; ternaryRelOpts[i].gen.name; i++)
+ {
+ Assert(DoLockModesConflict(ternaryRelOpts[i].gen.lockmode,
+ ternaryRelOpts[i].gen.lockmode));
+ j++;
+ }
+
for (i = 0; intRelOpts[i].gen.name; i++)
{
Assert(DoLockModesConflict(intRelOpts[i].gen.lockmode,
@@ -649,6 +666,14 @@ initialize_reloptions(void)
j++;
}
+ for (i = 0; ternaryRelOpts[i].gen.name; i++)
+ {
+ relOpts[j] = &ternaryRelOpts[i].gen;
+ relOpts[j]->type = RELOPT_TYPE_TERNARY;
+ relOpts[j]->namelen = strlen(relOpts[j]->name);
+ j++;
+ }
+
for (i = 0; intRelOpts[i].gen.name; i++)
{
relOpts[j] = &intRelOpts[i].gen;
@@ -809,6 +834,9 @@ allocate_reloption(bits32 kinds, int type, const char *name, const char *desc,
case RELOPT_TYPE_BOOL:
size = sizeof(relopt_bool);
break;
+ case RELOPT_TYPE_TERNARY:
+ size = sizeof(relopt_ternary);
+ break;
case RELOPT_TYPE_INT:
size = sizeof(relopt_int);
break;
@@ -892,6 +920,57 @@ add_local_bool_reloption(local_relopts *relopts, const char *name,
add_local_reloption(relopts, (relopt_gen *) newoption, offset);
}
+/*
+ * init_ternary_reloption
+ * Allocate and initialize a new ternary reloption
+ */
+static relopt_ternary *
+init_ternary_reloption(bits32 kinds, const char *name, const char *desc,
+ pg_ternary default_val, LOCKMODE lockmode)
+{
+ relopt_ternary *newoption;
+
+ newoption = (relopt_ternary *)
+ allocate_reloption(kinds, RELOPT_TYPE_TERNARY, name, desc, lockmode);
+ newoption->default_val = default_val;
+
+ return newoption;
+}
+
+/*
+ * add_ternary_reloption
+ * Add a new ternary reloption
+ */
+void
+add_ternary_reloption(bits32 kinds, const char *name, const char *desc,
+ pg_ternary default_val, LOCKMODE lockmode)
+{
+ relopt_ternary *newoption;
+
+ newoption =
+ init_ternary_reloption(kinds, name, desc, default_val, lockmode);
+
+ add_reloption((relopt_gen *) newoption);
+}
+
+/*
+ * add_local_ternary_reloption
+ * Add a new ternary local reloption
+ *
+ * 'offset' is offset of ternary-typed field.
+ */
+void
+add_local_ternary_reloption(local_relopts *relopts, const char *name,
+ const char *desc, pg_ternary default_val,
+ int offset)
+{
+ relopt_ternary *newoption;
+
+ newoption =
+ init_ternary_reloption(RELOPT_KIND_LOCAL, name, desc, default_val, 0);
+
+ add_local_reloption(relopts, (relopt_gen *) newoption, offset);
+}
/*
* init_real_reloption
@@ -1626,6 +1705,19 @@ parse_one_reloption(relopt_value *option, char *text_str, int text_len,
option->gen->name, value)));
}
break;
+ case RELOPT_TYPE_TERNARY:
+ {
+ bool b;
+
+ parsed = parse_bool(value, &b);
+ option->values.ternary_val = b ? TERNARY_TRUE : TERNARY_FALSE;
+ if (validate && !parsed)
+ ereport(ERROR,
+ (errcode(ERRCODE_INVALID_PARAMETER_VALUE),
+ errmsg("invalid value for ternary option \"%s\": %s",
+ option->gen->name, value)));
+ }
+ break;
case RELOPT_TYPE_INT:
{
relopt_int *optint = (relopt_int *) option->gen;
@@ -1789,17 +1881,6 @@ fillRelOptions(void *rdopts, Size basesize,
char *itempos = ((char *) rdopts) + elems[j].offset;
char *string_val;
- /*
- * If isset_offset is provided, store whether the reloption is
- * set there.
- */
- if (elems[j].isset_offset > 0)
- {
- char *setpos = ((char *) rdopts) + elems[j].isset_offset;
-
- *(bool *) setpos = options[i].isset;
- }
-
switch (options[i].gen->type)
{
case RELOPT_TYPE_BOOL:
@@ -1807,6 +1888,11 @@ fillRelOptions(void *rdopts, Size basesize,
options[i].values.bool_val :
((relopt_bool *) options[i].gen)->default_val;
break;
+ case RELOPT_TYPE_TERNARY:
+ *(pg_ternary *) itempos = options[i].isset ?
+ options[i].values.ternary_val :
+ ((relopt_ternary *) options[i].gen)->default_val;
+ break;
case RELOPT_TYPE_INT:
*(int *) itempos = options[i].isset ?
options[i].values.int_val :
@@ -1923,8 +2009,8 @@ default_reloptions(Datum reloptions, bool validate, relopt_kind kind)
offsetof(StdRdOptions, parallel_workers)},
{"vacuum_index_cleanup", RELOPT_TYPE_ENUM,
offsetof(StdRdOptions, vacuum_index_cleanup)},
- {"vacuum_truncate", RELOPT_TYPE_BOOL,
- offsetof(StdRdOptions, vacuum_truncate), offsetof(StdRdOptions, vacuum_truncate_set)},
+ {"vacuum_truncate", RELOPT_TYPE_TERNARY,
+ offsetof(StdRdOptions, vacuum_truncate)},
{"vacuum_max_eager_freeze_failure_rate", RELOPT_TYPE_REAL,
offsetof(StdRdOptions, vacuum_max_eager_freeze_failure_rate)}
};
@@ -2004,7 +2090,6 @@ build_local_reloptions(local_relopts *relopts, Datum options, bool validate)
elems[i].optname = opt->option->name;
elems[i].opttype = opt->option->type;
elems[i].offset = opt->offset;
- elems[i].isset_offset = 0; /* not supported for local relopts yet */
i++;
}
diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c
index aa4fbec143f..696eab9bd97 100644
--- a/src/backend/commands/vacuum.c
+++ b/src/backend/commands/vacuum.c
@@ -2224,9 +2224,9 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams params,
{
StdRdOptions *opts = (StdRdOptions *) rel->rd_options;
- if (opts && opts->vacuum_truncate_set)
+ if (opts && opts->vacuum_truncate != TERNARY_UNSET)
{
- if (opts->vacuum_truncate)
+ if (opts->vacuum_truncate == TERNARY_TRUE)
params.truncate = VACOPTVALUE_ENABLED;
else
params.truncate = VACOPTVALUE_DISABLED;
diff --git a/src/include/access/reloptions.h b/src/include/access/reloptions.h
index 2f08e1b0cf0..dfbef2babf2 100644
--- a/src/include/access/reloptions.h
+++ b/src/include/access/reloptions.h
@@ -29,6 +29,7 @@
typedef enum relopt_type
{
RELOPT_TYPE_BOOL,
+ RELOPT_TYPE_TERNARY, /* on, off, unset */
RELOPT_TYPE_INT,
RELOPT_TYPE_REAL,
RELOPT_TYPE_ENUM,
@@ -80,6 +81,7 @@ typedef struct relopt_value
union
{
bool bool_val;
+ pg_ternary ternary_val;
int int_val;
double real_val;
int enum_val;
@@ -94,6 +96,12 @@ typedef struct relopt_bool
bool default_val;
} relopt_bool;
+typedef struct relopt_ternary
+{
+ relopt_gen gen;
+ int default_val;
+} relopt_ternary;
+
typedef struct relopt_int
{
relopt_gen gen;
@@ -152,19 +160,6 @@ typedef struct
const char *optname; /* option's name */
relopt_type opttype; /* option's datatype */
int offset; /* offset of field in result struct */
-
- /*
- * isset_offset is an optional offset of a field in the result struct that
- * stores whether the option is explicitly set for the relation or if it
- * just picked up the default value. In most cases, this can be
- * accomplished by giving the reloption a special out-of-range default
- * value (e.g., some integer reloptions use -2), but this isn't always
- * possible. For example, a Boolean reloption cannot be given an
- * out-of-range default, so we need another way to discover the source of
- * its value. This offset is only used if given a value greater than
- * zero.
- */
- int isset_offset;
} relopt_parse_elt;
/* Local reloption definition */
@@ -195,6 +190,9 @@ typedef struct local_relopts
extern relopt_kind add_reloption_kind(void);
extern void add_bool_reloption(bits32 kinds, const char *name, const char *desc,
bool default_val, LOCKMODE lockmode);
+extern void add_ternary_reloption(bits32 kinds, const char *name,
+ const char *desc, pg_ternary default_val,
+ LOCKMODE lockmode);
extern void add_int_reloption(bits32 kinds, const char *name, const char *desc,
int default_val, int min_val, int max_val,
LOCKMODE lockmode);
@@ -214,6 +212,9 @@ extern void register_reloptions_validator(local_relopts *relopts,
extern void add_local_bool_reloption(local_relopts *relopts, const char *name,
const char *desc, bool default_val,
int offset);
+extern void add_local_ternary_reloption(local_relopts *relopts,
+ const char *name, const char *desc,
+ pg_ternary default_val, int offset);
extern void add_local_int_reloption(local_relopts *relopts, const char *name,
const char *desc, int default_val,
int min_val, int max_val, int offset);
diff --git a/src/include/postgres.h b/src/include/postgres.h
index 7d93fbce709..47c88780776 100644
--- a/src/include/postgres.h
+++ b/src/include/postgres.h
@@ -543,6 +543,21 @@ Float8GetDatum(float8 X)
* ----------------------------------------------------------------
*/
+/*
+ * pg_ternary
+ * Boolean value with an extra "unset" value
+ *
+ * This enum can be used for values that want to distinguish between true,
+ * false, and unset.
+*/
+
+typedef enum pg_ternary
+{
+ TERNARY_FALSE = 0,
+ TERNARY_TRUE = 1,
+ TERNARY_UNSET = -1
+} pg_ternary;
+
/*
* NON_EXEC_STATIC: It's sometimes useful to define a variable or function
* that is normally static but extern when using EXEC_BACKEND (see
diff --git a/src/include/utils/rel.h b/src/include/utils/rel.h
index d03ab247788..5feb18a5373 100644
--- a/src/include/utils/rel.h
+++ b/src/include/utils/rel.h
@@ -347,8 +347,7 @@ typedef struct StdRdOptions
bool user_catalog_table; /* use as an additional catalog relation */
int parallel_workers; /* max number of parallel workers */
StdRdOptIndexCleanup vacuum_index_cleanup; /* controls index vacuuming */
- bool vacuum_truncate; /* enables vacuum to truncate a relation */
- bool vacuum_truncate_set; /* whether vacuum_truncate is set */
+ pg_ternary vacuum_truncate; /* enables vacuum to truncate a relation */
/*
* Fraction of pages in a relation that vacuum can eagerly scan and fail
diff --git a/src/test/regress/expected/reloptions.out b/src/test/regress/expected/reloptions.out
index 9de19b4e3f1..1c99f79ab01 100644
--- a/src/test/regress/expected/reloptions.out
+++ b/src/test/regress/expected/reloptions.out
@@ -98,6 +98,42 @@ SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass;
{fillfactor=13,autovacuum_enabled=false}
(1 row)
+-- Tests for future (FIXME) ternary options
+-- behave as boolean option: accept unassigned name and truncated value
+DROP TABLE reloptions_test;
+CREATE TABLE reloptions_test(i INT) WITH (vacuum_truncate);
+SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass;
+ reloptions
+------------------------
+ {vacuum_truncate=true}
+(1 row)
+
+DROP TABLE reloptions_test;
+CREATE TABLE reloptions_test(i INT) WITH (vacuum_truncate=FaLS);
+SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass;
+ reloptions
+------------------------
+ {vacuum_truncate=fals}
+(1 row)
+
+-- preferred "true" alias is used when storing
+DROP TABLE reloptions_test;
+CREATE TABLE reloptions_test(i INT) WITH (vacuum_index_cleanup=on);
+SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass;
+ reloptions
+---------------------------
+ {vacuum_index_cleanup=on}
+(1 row)
+
+-- custom "third" value is available
+DROP TABLE reloptions_test;
+CREATE TABLE reloptions_test(i INT) WITH (vacuum_index_cleanup=auto);
+SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass;
+ reloptions
+-----------------------------
+ {vacuum_index_cleanup=auto}
+(1 row)
+
-- Test vacuum_truncate option
DROP TABLE reloptions_test;
CREATE TEMP TABLE reloptions_test(i INT NOT NULL, j text)
diff --git a/src/test/regress/sql/reloptions.sql b/src/test/regress/sql/reloptions.sql
index 24fbe0b478d..f5980dafcbc 100644
--- a/src/test/regress/sql/reloptions.sql
+++ b/src/test/regress/sql/reloptions.sql
@@ -59,6 +59,27 @@ UPDATE pg_class
ALTER TABLE reloptions_test RESET (illegal_option);
SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass;
+-- Tests for future (FIXME) ternary options
+
+-- behave as boolean option: accept unassigned name and truncated value
+DROP TABLE reloptions_test;
+CREATE TABLE reloptions_test(i INT) WITH (vacuum_truncate);
+SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass;
+
+DROP TABLE reloptions_test;
+CREATE TABLE reloptions_test(i INT) WITH (vacuum_truncate=FaLS);
+SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass;
+
+-- preferred "true" alias is used when storing
+DROP TABLE reloptions_test;
+CREATE TABLE reloptions_test(i INT) WITH (vacuum_index_cleanup=on);
+SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass;
+
+-- custom "third" value is available
+DROP TABLE reloptions_test;
+CREATE TABLE reloptions_test(i INT) WITH (vacuum_index_cleanup=auto);
+SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass;
+
-- Test vacuum_truncate option
DROP TABLE reloptions_test;
diff --git a/src/tools/pgindent/typedefs.list b/src/tools/pgindent/typedefs.list
index 3f3a888fd0e..1c8610fd46c 100644
--- a/src/tools/pgindent/typedefs.list
+++ b/src/tools/pgindent/typedefs.list
@@ -3952,6 +3952,7 @@ pg_sha512_ctx
pg_snapshot
pg_special_case
pg_stack_base_t
+pg_ternary
pg_time_t
pg_time_usec_t
pg_tz
@@ -4079,6 +4080,7 @@ relopt_kind
relopt_parse_elt
relopt_real
relopt_string
+relopt_ternary
relopt_type
relopt_value
relopts_validator
--
2.47.3
--c36t3fndxhkzbl5z--
^ permalink raw reply [nested|flat] 868+ messages in thread
* [PATCH v3] Introduce ternary reloptions
@ 2026-01-16 15:04 Álvaro Herrera <[email protected]>
0 siblings, 0 replies; 868+ messages in thread
From: Álvaro Herrera @ 2026-01-16 15:04 UTC (permalink / raw)
Introduce ternary reloption as a replacement for current
`vacuum_truncate` implementation. Remove the `vacuum_truncate_set`
separate flag and use TERNARY_UNSET instead.
This could also be used for other options such as `vacuum_index_cleanup`
and `buffering`, but lets get the scaffolding in first.
Discussion: https://postgr.es/m/3474141.usfYGdeWWP@thinkpad-pgpro
---
src/backend/access/common/reloptions.c | 137 ++++++++++++++++++-----
src/backend/commands/vacuum.c | 4 +-
src/include/access/reloptions.h | 27 ++---
src/include/postgres.h | 15 +++
src/include/utils/rel.h | 3 +-
src/test/regress/expected/reloptions.out | 36 ++++++
src/test/regress/sql/reloptions.sql | 21 ++++
src/tools/pgindent/typedefs.list | 2 +
8 files changed, 202 insertions(+), 43 deletions(-)
diff --git a/src/backend/access/common/reloptions.c b/src/backend/access/common/reloptions.c
index 0b83f98ed5f..6f1f577581a 100644
--- a/src/backend/access/common/reloptions.c
+++ b/src/backend/access/common/reloptions.c
@@ -40,9 +40,9 @@
*
* To add an option:
*
- * (i) decide on a type (bool, integer, real, enum, string), name, default
- * value, upper and lower bounds (if applicable); for strings, consider a
- * validation routine.
+ * (i) decide on a type (bool, ternary, integer, real, enum, string), name,
+ * default value, upper and lower bounds (if applicable); for strings,
+ * consider a validation routine.
* (ii) add a record below (or use add_<type>_reloption).
* (iii) add it to the appropriate options struct (perhaps StdRdOptions)
* (iv) add it to the appropriate handling routine (perhaps
@@ -147,15 +147,6 @@ static relopt_bool boolRelOpts[] =
},
false
},
- {
- {
- "vacuum_truncate",
- "Enables vacuum to truncate empty pages at the end of this table",
- RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
- ShareUpdateExclusiveLock
- },
- true
- },
{
{
"deduplicate_items",
@@ -170,6 +161,25 @@ static relopt_bool boolRelOpts[] =
{{NULL}}
};
+static relopt_ternary ternaryRelOpts[] =
+{
+ {
+ {
+ "vacuum_truncate",
+ "Enables vacuum to truncate empty pages at the end of this table",
+ RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
+ ShareUpdateExclusiveLock
+ },
+ TERNARY_UNSET
+ },
+ /* list terminator */
+ {
+ {
+ NULL
+ }
+ }
+};
+
static relopt_int intRelOpts[] =
{
{
@@ -609,6 +619,13 @@ initialize_reloptions(void)
boolRelOpts[i].gen.lockmode));
j++;
}
+ for (i = 0; ternaryRelOpts[i].gen.name; i++)
+ {
+ Assert(DoLockModesConflict(ternaryRelOpts[i].gen.lockmode,
+ ternaryRelOpts[i].gen.lockmode));
+ j++;
+ }
+
for (i = 0; intRelOpts[i].gen.name; i++)
{
Assert(DoLockModesConflict(intRelOpts[i].gen.lockmode,
@@ -649,6 +666,14 @@ initialize_reloptions(void)
j++;
}
+ for (i = 0; ternaryRelOpts[i].gen.name; i++)
+ {
+ relOpts[j] = &ternaryRelOpts[i].gen;
+ relOpts[j]->type = RELOPT_TYPE_TERNARY;
+ relOpts[j]->namelen = strlen(relOpts[j]->name);
+ j++;
+ }
+
for (i = 0; intRelOpts[i].gen.name; i++)
{
relOpts[j] = &intRelOpts[i].gen;
@@ -809,6 +834,9 @@ allocate_reloption(bits32 kinds, int type, const char *name, const char *desc,
case RELOPT_TYPE_BOOL:
size = sizeof(relopt_bool);
break;
+ case RELOPT_TYPE_TERNARY:
+ size = sizeof(relopt_ternary);
+ break;
case RELOPT_TYPE_INT:
size = sizeof(relopt_int);
break;
@@ -892,6 +920,57 @@ add_local_bool_reloption(local_relopts *relopts, const char *name,
add_local_reloption(relopts, (relopt_gen *) newoption, offset);
}
+/*
+ * init_ternary_reloption
+ * Allocate and initialize a new ternary reloption
+ */
+static relopt_ternary *
+init_ternary_reloption(bits32 kinds, const char *name, const char *desc,
+ pg_ternary default_val, LOCKMODE lockmode)
+{
+ relopt_ternary *newoption;
+
+ newoption = (relopt_ternary *)
+ allocate_reloption(kinds, RELOPT_TYPE_TERNARY, name, desc, lockmode);
+ newoption->default_val = default_val;
+
+ return newoption;
+}
+
+/*
+ * add_ternary_reloption
+ * Add a new ternary reloption
+ */
+void
+add_ternary_reloption(bits32 kinds, const char *name, const char *desc,
+ pg_ternary default_val, LOCKMODE lockmode)
+{
+ relopt_ternary *newoption;
+
+ newoption =
+ init_ternary_reloption(kinds, name, desc, default_val, lockmode);
+
+ add_reloption((relopt_gen *) newoption);
+}
+
+/*
+ * add_local_ternary_reloption
+ * Add a new ternary local reloption
+ *
+ * 'offset' is offset of ternary-typed field.
+ */
+void
+add_local_ternary_reloption(local_relopts *relopts, const char *name,
+ const char *desc, pg_ternary default_val,
+ int offset)
+{
+ relopt_ternary *newoption;
+
+ newoption =
+ init_ternary_reloption(RELOPT_KIND_LOCAL, name, desc, default_val, 0);
+
+ add_local_reloption(relopts, (relopt_gen *) newoption, offset);
+}
/*
* init_real_reloption
@@ -1626,6 +1705,19 @@ parse_one_reloption(relopt_value *option, char *text_str, int text_len,
option->gen->name, value)));
}
break;
+ case RELOPT_TYPE_TERNARY:
+ {
+ bool b;
+
+ parsed = parse_bool(value, &b);
+ option->values.ternary_val = b ? TERNARY_TRUE : TERNARY_FALSE;
+ if (validate && !parsed)
+ ereport(ERROR,
+ (errcode(ERRCODE_INVALID_PARAMETER_VALUE),
+ errmsg("invalid value for ternary option \"%s\": %s",
+ option->gen->name, value)));
+ }
+ break;
case RELOPT_TYPE_INT:
{
relopt_int *optint = (relopt_int *) option->gen;
@@ -1789,17 +1881,6 @@ fillRelOptions(void *rdopts, Size basesize,
char *itempos = ((char *) rdopts) + elems[j].offset;
char *string_val;
- /*
- * If isset_offset is provided, store whether the reloption is
- * set there.
- */
- if (elems[j].isset_offset > 0)
- {
- char *setpos = ((char *) rdopts) + elems[j].isset_offset;
-
- *(bool *) setpos = options[i].isset;
- }
-
switch (options[i].gen->type)
{
case RELOPT_TYPE_BOOL:
@@ -1807,6 +1888,11 @@ fillRelOptions(void *rdopts, Size basesize,
options[i].values.bool_val :
((relopt_bool *) options[i].gen)->default_val;
break;
+ case RELOPT_TYPE_TERNARY:
+ *(pg_ternary *) itempos = options[i].isset ?
+ options[i].values.ternary_val :
+ ((relopt_ternary *) options[i].gen)->default_val;
+ break;
case RELOPT_TYPE_INT:
*(int *) itempos = options[i].isset ?
options[i].values.int_val :
@@ -1923,8 +2009,8 @@ default_reloptions(Datum reloptions, bool validate, relopt_kind kind)
offsetof(StdRdOptions, parallel_workers)},
{"vacuum_index_cleanup", RELOPT_TYPE_ENUM,
offsetof(StdRdOptions, vacuum_index_cleanup)},
- {"vacuum_truncate", RELOPT_TYPE_BOOL,
- offsetof(StdRdOptions, vacuum_truncate), offsetof(StdRdOptions, vacuum_truncate_set)},
+ {"vacuum_truncate", RELOPT_TYPE_TERNARY,
+ offsetof(StdRdOptions, vacuum_truncate)},
{"vacuum_max_eager_freeze_failure_rate", RELOPT_TYPE_REAL,
offsetof(StdRdOptions, vacuum_max_eager_freeze_failure_rate)}
};
@@ -2004,7 +2090,6 @@ build_local_reloptions(local_relopts *relopts, Datum options, bool validate)
elems[i].optname = opt->option->name;
elems[i].opttype = opt->option->type;
elems[i].offset = opt->offset;
- elems[i].isset_offset = 0; /* not supported for local relopts yet */
i++;
}
diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c
index aa4fbec143f..696eab9bd97 100644
--- a/src/backend/commands/vacuum.c
+++ b/src/backend/commands/vacuum.c
@@ -2224,9 +2224,9 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams params,
{
StdRdOptions *opts = (StdRdOptions *) rel->rd_options;
- if (opts && opts->vacuum_truncate_set)
+ if (opts && opts->vacuum_truncate != TERNARY_UNSET)
{
- if (opts->vacuum_truncate)
+ if (opts->vacuum_truncate == TERNARY_TRUE)
params.truncate = VACOPTVALUE_ENABLED;
else
params.truncate = VACOPTVALUE_DISABLED;
diff --git a/src/include/access/reloptions.h b/src/include/access/reloptions.h
index 2f08e1b0cf0..dfbef2babf2 100644
--- a/src/include/access/reloptions.h
+++ b/src/include/access/reloptions.h
@@ -29,6 +29,7 @@
typedef enum relopt_type
{
RELOPT_TYPE_BOOL,
+ RELOPT_TYPE_TERNARY, /* on, off, unset */
RELOPT_TYPE_INT,
RELOPT_TYPE_REAL,
RELOPT_TYPE_ENUM,
@@ -80,6 +81,7 @@ typedef struct relopt_value
union
{
bool bool_val;
+ pg_ternary ternary_val;
int int_val;
double real_val;
int enum_val;
@@ -94,6 +96,12 @@ typedef struct relopt_bool
bool default_val;
} relopt_bool;
+typedef struct relopt_ternary
+{
+ relopt_gen gen;
+ int default_val;
+} relopt_ternary;
+
typedef struct relopt_int
{
relopt_gen gen;
@@ -152,19 +160,6 @@ typedef struct
const char *optname; /* option's name */
relopt_type opttype; /* option's datatype */
int offset; /* offset of field in result struct */
-
- /*
- * isset_offset is an optional offset of a field in the result struct that
- * stores whether the option is explicitly set for the relation or if it
- * just picked up the default value. In most cases, this can be
- * accomplished by giving the reloption a special out-of-range default
- * value (e.g., some integer reloptions use -2), but this isn't always
- * possible. For example, a Boolean reloption cannot be given an
- * out-of-range default, so we need another way to discover the source of
- * its value. This offset is only used if given a value greater than
- * zero.
- */
- int isset_offset;
} relopt_parse_elt;
/* Local reloption definition */
@@ -195,6 +190,9 @@ typedef struct local_relopts
extern relopt_kind add_reloption_kind(void);
extern void add_bool_reloption(bits32 kinds, const char *name, const char *desc,
bool default_val, LOCKMODE lockmode);
+extern void add_ternary_reloption(bits32 kinds, const char *name,
+ const char *desc, pg_ternary default_val,
+ LOCKMODE lockmode);
extern void add_int_reloption(bits32 kinds, const char *name, const char *desc,
int default_val, int min_val, int max_val,
LOCKMODE lockmode);
@@ -214,6 +212,9 @@ extern void register_reloptions_validator(local_relopts *relopts,
extern void add_local_bool_reloption(local_relopts *relopts, const char *name,
const char *desc, bool default_val,
int offset);
+extern void add_local_ternary_reloption(local_relopts *relopts,
+ const char *name, const char *desc,
+ pg_ternary default_val, int offset);
extern void add_local_int_reloption(local_relopts *relopts, const char *name,
const char *desc, int default_val,
int min_val, int max_val, int offset);
diff --git a/src/include/postgres.h b/src/include/postgres.h
index 7d93fbce709..47c88780776 100644
--- a/src/include/postgres.h
+++ b/src/include/postgres.h
@@ -543,6 +543,21 @@ Float8GetDatum(float8 X)
* ----------------------------------------------------------------
*/
+/*
+ * pg_ternary
+ * Boolean value with an extra "unset" value
+ *
+ * This enum can be used for values that want to distinguish between true,
+ * false, and unset.
+*/
+
+typedef enum pg_ternary
+{
+ TERNARY_FALSE = 0,
+ TERNARY_TRUE = 1,
+ TERNARY_UNSET = -1
+} pg_ternary;
+
/*
* NON_EXEC_STATIC: It's sometimes useful to define a variable or function
* that is normally static but extern when using EXEC_BACKEND (see
diff --git a/src/include/utils/rel.h b/src/include/utils/rel.h
index d03ab247788..5feb18a5373 100644
--- a/src/include/utils/rel.h
+++ b/src/include/utils/rel.h
@@ -347,8 +347,7 @@ typedef struct StdRdOptions
bool user_catalog_table; /* use as an additional catalog relation */
int parallel_workers; /* max number of parallel workers */
StdRdOptIndexCleanup vacuum_index_cleanup; /* controls index vacuuming */
- bool vacuum_truncate; /* enables vacuum to truncate a relation */
- bool vacuum_truncate_set; /* whether vacuum_truncate is set */
+ pg_ternary vacuum_truncate; /* enables vacuum to truncate a relation */
/*
* Fraction of pages in a relation that vacuum can eagerly scan and fail
diff --git a/src/test/regress/expected/reloptions.out b/src/test/regress/expected/reloptions.out
index 9de19b4e3f1..1c99f79ab01 100644
--- a/src/test/regress/expected/reloptions.out
+++ b/src/test/regress/expected/reloptions.out
@@ -98,6 +98,42 @@ SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass;
{fillfactor=13,autovacuum_enabled=false}
(1 row)
+-- Tests for future (FIXME) ternary options
+-- behave as boolean option: accept unassigned name and truncated value
+DROP TABLE reloptions_test;
+CREATE TABLE reloptions_test(i INT) WITH (vacuum_truncate);
+SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass;
+ reloptions
+------------------------
+ {vacuum_truncate=true}
+(1 row)
+
+DROP TABLE reloptions_test;
+CREATE TABLE reloptions_test(i INT) WITH (vacuum_truncate=FaLS);
+SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass;
+ reloptions
+------------------------
+ {vacuum_truncate=fals}
+(1 row)
+
+-- preferred "true" alias is used when storing
+DROP TABLE reloptions_test;
+CREATE TABLE reloptions_test(i INT) WITH (vacuum_index_cleanup=on);
+SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass;
+ reloptions
+---------------------------
+ {vacuum_index_cleanup=on}
+(1 row)
+
+-- custom "third" value is available
+DROP TABLE reloptions_test;
+CREATE TABLE reloptions_test(i INT) WITH (vacuum_index_cleanup=auto);
+SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass;
+ reloptions
+-----------------------------
+ {vacuum_index_cleanup=auto}
+(1 row)
+
-- Test vacuum_truncate option
DROP TABLE reloptions_test;
CREATE TEMP TABLE reloptions_test(i INT NOT NULL, j text)
diff --git a/src/test/regress/sql/reloptions.sql b/src/test/regress/sql/reloptions.sql
index 24fbe0b478d..f5980dafcbc 100644
--- a/src/test/regress/sql/reloptions.sql
+++ b/src/test/regress/sql/reloptions.sql
@@ -59,6 +59,27 @@ UPDATE pg_class
ALTER TABLE reloptions_test RESET (illegal_option);
SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass;
+-- Tests for future (FIXME) ternary options
+
+-- behave as boolean option: accept unassigned name and truncated value
+DROP TABLE reloptions_test;
+CREATE TABLE reloptions_test(i INT) WITH (vacuum_truncate);
+SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass;
+
+DROP TABLE reloptions_test;
+CREATE TABLE reloptions_test(i INT) WITH (vacuum_truncate=FaLS);
+SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass;
+
+-- preferred "true" alias is used when storing
+DROP TABLE reloptions_test;
+CREATE TABLE reloptions_test(i INT) WITH (vacuum_index_cleanup=on);
+SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass;
+
+-- custom "third" value is available
+DROP TABLE reloptions_test;
+CREATE TABLE reloptions_test(i INT) WITH (vacuum_index_cleanup=auto);
+SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass;
+
-- Test vacuum_truncate option
DROP TABLE reloptions_test;
diff --git a/src/tools/pgindent/typedefs.list b/src/tools/pgindent/typedefs.list
index 3f3a888fd0e..1c8610fd46c 100644
--- a/src/tools/pgindent/typedefs.list
+++ b/src/tools/pgindent/typedefs.list
@@ -3952,6 +3952,7 @@ pg_sha512_ctx
pg_snapshot
pg_special_case
pg_stack_base_t
+pg_ternary
pg_time_t
pg_time_usec_t
pg_tz
@@ -4079,6 +4080,7 @@ relopt_kind
relopt_parse_elt
relopt_real
relopt_string
+relopt_ternary
relopt_type
relopt_value
relopts_validator
--
2.47.3
--c36t3fndxhkzbl5z--
^ permalink raw reply [nested|flat] 868+ messages in thread
* [PATCH v3] Introduce ternary reloptions
@ 2026-01-16 15:04 Álvaro Herrera <[email protected]>
0 siblings, 0 replies; 868+ messages in thread
From: Álvaro Herrera @ 2026-01-16 15:04 UTC (permalink / raw)
Introduce ternary reloption as a replacement for current
`vacuum_truncate` implementation. Remove the `vacuum_truncate_set`
separate flag and use TERNARY_UNSET instead.
This could also be used for other options such as `vacuum_index_cleanup`
and `buffering`, but lets get the scaffolding in first.
Discussion: https://postgr.es/m/3474141.usfYGdeWWP@thinkpad-pgpro
---
src/backend/access/common/reloptions.c | 137 ++++++++++++++++++-----
src/backend/commands/vacuum.c | 4 +-
src/include/access/reloptions.h | 27 ++---
src/include/postgres.h | 15 +++
src/include/utils/rel.h | 3 +-
src/test/regress/expected/reloptions.out | 36 ++++++
src/test/regress/sql/reloptions.sql | 21 ++++
src/tools/pgindent/typedefs.list | 2 +
8 files changed, 202 insertions(+), 43 deletions(-)
diff --git a/src/backend/access/common/reloptions.c b/src/backend/access/common/reloptions.c
index 0b83f98ed5f..6f1f577581a 100644
--- a/src/backend/access/common/reloptions.c
+++ b/src/backend/access/common/reloptions.c
@@ -40,9 +40,9 @@
*
* To add an option:
*
- * (i) decide on a type (bool, integer, real, enum, string), name, default
- * value, upper and lower bounds (if applicable); for strings, consider a
- * validation routine.
+ * (i) decide on a type (bool, ternary, integer, real, enum, string), name,
+ * default value, upper and lower bounds (if applicable); for strings,
+ * consider a validation routine.
* (ii) add a record below (or use add_<type>_reloption).
* (iii) add it to the appropriate options struct (perhaps StdRdOptions)
* (iv) add it to the appropriate handling routine (perhaps
@@ -147,15 +147,6 @@ static relopt_bool boolRelOpts[] =
},
false
},
- {
- {
- "vacuum_truncate",
- "Enables vacuum to truncate empty pages at the end of this table",
- RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
- ShareUpdateExclusiveLock
- },
- true
- },
{
{
"deduplicate_items",
@@ -170,6 +161,25 @@ static relopt_bool boolRelOpts[] =
{{NULL}}
};
+static relopt_ternary ternaryRelOpts[] =
+{
+ {
+ {
+ "vacuum_truncate",
+ "Enables vacuum to truncate empty pages at the end of this table",
+ RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
+ ShareUpdateExclusiveLock
+ },
+ TERNARY_UNSET
+ },
+ /* list terminator */
+ {
+ {
+ NULL
+ }
+ }
+};
+
static relopt_int intRelOpts[] =
{
{
@@ -609,6 +619,13 @@ initialize_reloptions(void)
boolRelOpts[i].gen.lockmode));
j++;
}
+ for (i = 0; ternaryRelOpts[i].gen.name; i++)
+ {
+ Assert(DoLockModesConflict(ternaryRelOpts[i].gen.lockmode,
+ ternaryRelOpts[i].gen.lockmode));
+ j++;
+ }
+
for (i = 0; intRelOpts[i].gen.name; i++)
{
Assert(DoLockModesConflict(intRelOpts[i].gen.lockmode,
@@ -649,6 +666,14 @@ initialize_reloptions(void)
j++;
}
+ for (i = 0; ternaryRelOpts[i].gen.name; i++)
+ {
+ relOpts[j] = &ternaryRelOpts[i].gen;
+ relOpts[j]->type = RELOPT_TYPE_TERNARY;
+ relOpts[j]->namelen = strlen(relOpts[j]->name);
+ j++;
+ }
+
for (i = 0; intRelOpts[i].gen.name; i++)
{
relOpts[j] = &intRelOpts[i].gen;
@@ -809,6 +834,9 @@ allocate_reloption(bits32 kinds, int type, const char *name, const char *desc,
case RELOPT_TYPE_BOOL:
size = sizeof(relopt_bool);
break;
+ case RELOPT_TYPE_TERNARY:
+ size = sizeof(relopt_ternary);
+ break;
case RELOPT_TYPE_INT:
size = sizeof(relopt_int);
break;
@@ -892,6 +920,57 @@ add_local_bool_reloption(local_relopts *relopts, const char *name,
add_local_reloption(relopts, (relopt_gen *) newoption, offset);
}
+/*
+ * init_ternary_reloption
+ * Allocate and initialize a new ternary reloption
+ */
+static relopt_ternary *
+init_ternary_reloption(bits32 kinds, const char *name, const char *desc,
+ pg_ternary default_val, LOCKMODE lockmode)
+{
+ relopt_ternary *newoption;
+
+ newoption = (relopt_ternary *)
+ allocate_reloption(kinds, RELOPT_TYPE_TERNARY, name, desc, lockmode);
+ newoption->default_val = default_val;
+
+ return newoption;
+}
+
+/*
+ * add_ternary_reloption
+ * Add a new ternary reloption
+ */
+void
+add_ternary_reloption(bits32 kinds, const char *name, const char *desc,
+ pg_ternary default_val, LOCKMODE lockmode)
+{
+ relopt_ternary *newoption;
+
+ newoption =
+ init_ternary_reloption(kinds, name, desc, default_val, lockmode);
+
+ add_reloption((relopt_gen *) newoption);
+}
+
+/*
+ * add_local_ternary_reloption
+ * Add a new ternary local reloption
+ *
+ * 'offset' is offset of ternary-typed field.
+ */
+void
+add_local_ternary_reloption(local_relopts *relopts, const char *name,
+ const char *desc, pg_ternary default_val,
+ int offset)
+{
+ relopt_ternary *newoption;
+
+ newoption =
+ init_ternary_reloption(RELOPT_KIND_LOCAL, name, desc, default_val, 0);
+
+ add_local_reloption(relopts, (relopt_gen *) newoption, offset);
+}
/*
* init_real_reloption
@@ -1626,6 +1705,19 @@ parse_one_reloption(relopt_value *option, char *text_str, int text_len,
option->gen->name, value)));
}
break;
+ case RELOPT_TYPE_TERNARY:
+ {
+ bool b;
+
+ parsed = parse_bool(value, &b);
+ option->values.ternary_val = b ? TERNARY_TRUE : TERNARY_FALSE;
+ if (validate && !parsed)
+ ereport(ERROR,
+ (errcode(ERRCODE_INVALID_PARAMETER_VALUE),
+ errmsg("invalid value for ternary option \"%s\": %s",
+ option->gen->name, value)));
+ }
+ break;
case RELOPT_TYPE_INT:
{
relopt_int *optint = (relopt_int *) option->gen;
@@ -1789,17 +1881,6 @@ fillRelOptions(void *rdopts, Size basesize,
char *itempos = ((char *) rdopts) + elems[j].offset;
char *string_val;
- /*
- * If isset_offset is provided, store whether the reloption is
- * set there.
- */
- if (elems[j].isset_offset > 0)
- {
- char *setpos = ((char *) rdopts) + elems[j].isset_offset;
-
- *(bool *) setpos = options[i].isset;
- }
-
switch (options[i].gen->type)
{
case RELOPT_TYPE_BOOL:
@@ -1807,6 +1888,11 @@ fillRelOptions(void *rdopts, Size basesize,
options[i].values.bool_val :
((relopt_bool *) options[i].gen)->default_val;
break;
+ case RELOPT_TYPE_TERNARY:
+ *(pg_ternary *) itempos = options[i].isset ?
+ options[i].values.ternary_val :
+ ((relopt_ternary *) options[i].gen)->default_val;
+ break;
case RELOPT_TYPE_INT:
*(int *) itempos = options[i].isset ?
options[i].values.int_val :
@@ -1923,8 +2009,8 @@ default_reloptions(Datum reloptions, bool validate, relopt_kind kind)
offsetof(StdRdOptions, parallel_workers)},
{"vacuum_index_cleanup", RELOPT_TYPE_ENUM,
offsetof(StdRdOptions, vacuum_index_cleanup)},
- {"vacuum_truncate", RELOPT_TYPE_BOOL,
- offsetof(StdRdOptions, vacuum_truncate), offsetof(StdRdOptions, vacuum_truncate_set)},
+ {"vacuum_truncate", RELOPT_TYPE_TERNARY,
+ offsetof(StdRdOptions, vacuum_truncate)},
{"vacuum_max_eager_freeze_failure_rate", RELOPT_TYPE_REAL,
offsetof(StdRdOptions, vacuum_max_eager_freeze_failure_rate)}
};
@@ -2004,7 +2090,6 @@ build_local_reloptions(local_relopts *relopts, Datum options, bool validate)
elems[i].optname = opt->option->name;
elems[i].opttype = opt->option->type;
elems[i].offset = opt->offset;
- elems[i].isset_offset = 0; /* not supported for local relopts yet */
i++;
}
diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c
index aa4fbec143f..696eab9bd97 100644
--- a/src/backend/commands/vacuum.c
+++ b/src/backend/commands/vacuum.c
@@ -2224,9 +2224,9 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams params,
{
StdRdOptions *opts = (StdRdOptions *) rel->rd_options;
- if (opts && opts->vacuum_truncate_set)
+ if (opts && opts->vacuum_truncate != TERNARY_UNSET)
{
- if (opts->vacuum_truncate)
+ if (opts->vacuum_truncate == TERNARY_TRUE)
params.truncate = VACOPTVALUE_ENABLED;
else
params.truncate = VACOPTVALUE_DISABLED;
diff --git a/src/include/access/reloptions.h b/src/include/access/reloptions.h
index 2f08e1b0cf0..dfbef2babf2 100644
--- a/src/include/access/reloptions.h
+++ b/src/include/access/reloptions.h
@@ -29,6 +29,7 @@
typedef enum relopt_type
{
RELOPT_TYPE_BOOL,
+ RELOPT_TYPE_TERNARY, /* on, off, unset */
RELOPT_TYPE_INT,
RELOPT_TYPE_REAL,
RELOPT_TYPE_ENUM,
@@ -80,6 +81,7 @@ typedef struct relopt_value
union
{
bool bool_val;
+ pg_ternary ternary_val;
int int_val;
double real_val;
int enum_val;
@@ -94,6 +96,12 @@ typedef struct relopt_bool
bool default_val;
} relopt_bool;
+typedef struct relopt_ternary
+{
+ relopt_gen gen;
+ int default_val;
+} relopt_ternary;
+
typedef struct relopt_int
{
relopt_gen gen;
@@ -152,19 +160,6 @@ typedef struct
const char *optname; /* option's name */
relopt_type opttype; /* option's datatype */
int offset; /* offset of field in result struct */
-
- /*
- * isset_offset is an optional offset of a field in the result struct that
- * stores whether the option is explicitly set for the relation or if it
- * just picked up the default value. In most cases, this can be
- * accomplished by giving the reloption a special out-of-range default
- * value (e.g., some integer reloptions use -2), but this isn't always
- * possible. For example, a Boolean reloption cannot be given an
- * out-of-range default, so we need another way to discover the source of
- * its value. This offset is only used if given a value greater than
- * zero.
- */
- int isset_offset;
} relopt_parse_elt;
/* Local reloption definition */
@@ -195,6 +190,9 @@ typedef struct local_relopts
extern relopt_kind add_reloption_kind(void);
extern void add_bool_reloption(bits32 kinds, const char *name, const char *desc,
bool default_val, LOCKMODE lockmode);
+extern void add_ternary_reloption(bits32 kinds, const char *name,
+ const char *desc, pg_ternary default_val,
+ LOCKMODE lockmode);
extern void add_int_reloption(bits32 kinds, const char *name, const char *desc,
int default_val, int min_val, int max_val,
LOCKMODE lockmode);
@@ -214,6 +212,9 @@ extern void register_reloptions_validator(local_relopts *relopts,
extern void add_local_bool_reloption(local_relopts *relopts, const char *name,
const char *desc, bool default_val,
int offset);
+extern void add_local_ternary_reloption(local_relopts *relopts,
+ const char *name, const char *desc,
+ pg_ternary default_val, int offset);
extern void add_local_int_reloption(local_relopts *relopts, const char *name,
const char *desc, int default_val,
int min_val, int max_val, int offset);
diff --git a/src/include/postgres.h b/src/include/postgres.h
index 7d93fbce709..47c88780776 100644
--- a/src/include/postgres.h
+++ b/src/include/postgres.h
@@ -543,6 +543,21 @@ Float8GetDatum(float8 X)
* ----------------------------------------------------------------
*/
+/*
+ * pg_ternary
+ * Boolean value with an extra "unset" value
+ *
+ * This enum can be used for values that want to distinguish between true,
+ * false, and unset.
+*/
+
+typedef enum pg_ternary
+{
+ TERNARY_FALSE = 0,
+ TERNARY_TRUE = 1,
+ TERNARY_UNSET = -1
+} pg_ternary;
+
/*
* NON_EXEC_STATIC: It's sometimes useful to define a variable or function
* that is normally static but extern when using EXEC_BACKEND (see
diff --git a/src/include/utils/rel.h b/src/include/utils/rel.h
index d03ab247788..5feb18a5373 100644
--- a/src/include/utils/rel.h
+++ b/src/include/utils/rel.h
@@ -347,8 +347,7 @@ typedef struct StdRdOptions
bool user_catalog_table; /* use as an additional catalog relation */
int parallel_workers; /* max number of parallel workers */
StdRdOptIndexCleanup vacuum_index_cleanup; /* controls index vacuuming */
- bool vacuum_truncate; /* enables vacuum to truncate a relation */
- bool vacuum_truncate_set; /* whether vacuum_truncate is set */
+ pg_ternary vacuum_truncate; /* enables vacuum to truncate a relation */
/*
* Fraction of pages in a relation that vacuum can eagerly scan and fail
diff --git a/src/test/regress/expected/reloptions.out b/src/test/regress/expected/reloptions.out
index 9de19b4e3f1..1c99f79ab01 100644
--- a/src/test/regress/expected/reloptions.out
+++ b/src/test/regress/expected/reloptions.out
@@ -98,6 +98,42 @@ SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass;
{fillfactor=13,autovacuum_enabled=false}
(1 row)
+-- Tests for future (FIXME) ternary options
+-- behave as boolean option: accept unassigned name and truncated value
+DROP TABLE reloptions_test;
+CREATE TABLE reloptions_test(i INT) WITH (vacuum_truncate);
+SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass;
+ reloptions
+------------------------
+ {vacuum_truncate=true}
+(1 row)
+
+DROP TABLE reloptions_test;
+CREATE TABLE reloptions_test(i INT) WITH (vacuum_truncate=FaLS);
+SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass;
+ reloptions
+------------------------
+ {vacuum_truncate=fals}
+(1 row)
+
+-- preferred "true" alias is used when storing
+DROP TABLE reloptions_test;
+CREATE TABLE reloptions_test(i INT) WITH (vacuum_index_cleanup=on);
+SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass;
+ reloptions
+---------------------------
+ {vacuum_index_cleanup=on}
+(1 row)
+
+-- custom "third" value is available
+DROP TABLE reloptions_test;
+CREATE TABLE reloptions_test(i INT) WITH (vacuum_index_cleanup=auto);
+SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass;
+ reloptions
+-----------------------------
+ {vacuum_index_cleanup=auto}
+(1 row)
+
-- Test vacuum_truncate option
DROP TABLE reloptions_test;
CREATE TEMP TABLE reloptions_test(i INT NOT NULL, j text)
diff --git a/src/test/regress/sql/reloptions.sql b/src/test/regress/sql/reloptions.sql
index 24fbe0b478d..f5980dafcbc 100644
--- a/src/test/regress/sql/reloptions.sql
+++ b/src/test/regress/sql/reloptions.sql
@@ -59,6 +59,27 @@ UPDATE pg_class
ALTER TABLE reloptions_test RESET (illegal_option);
SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass;
+-- Tests for future (FIXME) ternary options
+
+-- behave as boolean option: accept unassigned name and truncated value
+DROP TABLE reloptions_test;
+CREATE TABLE reloptions_test(i INT) WITH (vacuum_truncate);
+SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass;
+
+DROP TABLE reloptions_test;
+CREATE TABLE reloptions_test(i INT) WITH (vacuum_truncate=FaLS);
+SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass;
+
+-- preferred "true" alias is used when storing
+DROP TABLE reloptions_test;
+CREATE TABLE reloptions_test(i INT) WITH (vacuum_index_cleanup=on);
+SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass;
+
+-- custom "third" value is available
+DROP TABLE reloptions_test;
+CREATE TABLE reloptions_test(i INT) WITH (vacuum_index_cleanup=auto);
+SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass;
+
-- Test vacuum_truncate option
DROP TABLE reloptions_test;
diff --git a/src/tools/pgindent/typedefs.list b/src/tools/pgindent/typedefs.list
index 3f3a888fd0e..1c8610fd46c 100644
--- a/src/tools/pgindent/typedefs.list
+++ b/src/tools/pgindent/typedefs.list
@@ -3952,6 +3952,7 @@ pg_sha512_ctx
pg_snapshot
pg_special_case
pg_stack_base_t
+pg_ternary
pg_time_t
pg_time_usec_t
pg_tz
@@ -4079,6 +4080,7 @@ relopt_kind
relopt_parse_elt
relopt_real
relopt_string
+relopt_ternary
relopt_type
relopt_value
relopts_validator
--
2.47.3
--c36t3fndxhkzbl5z--
^ permalink raw reply [nested|flat] 868+ messages in thread
* [PATCH v3] Introduce ternary reloptions
@ 2026-01-16 15:04 Álvaro Herrera <[email protected]>
0 siblings, 0 replies; 868+ messages in thread
From: Álvaro Herrera @ 2026-01-16 15:04 UTC (permalink / raw)
Introduce ternary reloption as a replacement for current
`vacuum_truncate` implementation. Remove the `vacuum_truncate_set`
separate flag and use TERNARY_UNSET instead.
This could also be used for other options such as `vacuum_index_cleanup`
and `buffering`, but lets get the scaffolding in first.
Discussion: https://postgr.es/m/3474141.usfYGdeWWP@thinkpad-pgpro
---
src/backend/access/common/reloptions.c | 137 ++++++++++++++++++-----
src/backend/commands/vacuum.c | 4 +-
src/include/access/reloptions.h | 27 ++---
src/include/postgres.h | 15 +++
src/include/utils/rel.h | 3 +-
src/test/regress/expected/reloptions.out | 36 ++++++
src/test/regress/sql/reloptions.sql | 21 ++++
src/tools/pgindent/typedefs.list | 2 +
8 files changed, 202 insertions(+), 43 deletions(-)
diff --git a/src/backend/access/common/reloptions.c b/src/backend/access/common/reloptions.c
index 0b83f98ed5f..6f1f577581a 100644
--- a/src/backend/access/common/reloptions.c
+++ b/src/backend/access/common/reloptions.c
@@ -40,9 +40,9 @@
*
* To add an option:
*
- * (i) decide on a type (bool, integer, real, enum, string), name, default
- * value, upper and lower bounds (if applicable); for strings, consider a
- * validation routine.
+ * (i) decide on a type (bool, ternary, integer, real, enum, string), name,
+ * default value, upper and lower bounds (if applicable); for strings,
+ * consider a validation routine.
* (ii) add a record below (or use add_<type>_reloption).
* (iii) add it to the appropriate options struct (perhaps StdRdOptions)
* (iv) add it to the appropriate handling routine (perhaps
@@ -147,15 +147,6 @@ static relopt_bool boolRelOpts[] =
},
false
},
- {
- {
- "vacuum_truncate",
- "Enables vacuum to truncate empty pages at the end of this table",
- RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
- ShareUpdateExclusiveLock
- },
- true
- },
{
{
"deduplicate_items",
@@ -170,6 +161,25 @@ static relopt_bool boolRelOpts[] =
{{NULL}}
};
+static relopt_ternary ternaryRelOpts[] =
+{
+ {
+ {
+ "vacuum_truncate",
+ "Enables vacuum to truncate empty pages at the end of this table",
+ RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
+ ShareUpdateExclusiveLock
+ },
+ TERNARY_UNSET
+ },
+ /* list terminator */
+ {
+ {
+ NULL
+ }
+ }
+};
+
static relopt_int intRelOpts[] =
{
{
@@ -609,6 +619,13 @@ initialize_reloptions(void)
boolRelOpts[i].gen.lockmode));
j++;
}
+ for (i = 0; ternaryRelOpts[i].gen.name; i++)
+ {
+ Assert(DoLockModesConflict(ternaryRelOpts[i].gen.lockmode,
+ ternaryRelOpts[i].gen.lockmode));
+ j++;
+ }
+
for (i = 0; intRelOpts[i].gen.name; i++)
{
Assert(DoLockModesConflict(intRelOpts[i].gen.lockmode,
@@ -649,6 +666,14 @@ initialize_reloptions(void)
j++;
}
+ for (i = 0; ternaryRelOpts[i].gen.name; i++)
+ {
+ relOpts[j] = &ternaryRelOpts[i].gen;
+ relOpts[j]->type = RELOPT_TYPE_TERNARY;
+ relOpts[j]->namelen = strlen(relOpts[j]->name);
+ j++;
+ }
+
for (i = 0; intRelOpts[i].gen.name; i++)
{
relOpts[j] = &intRelOpts[i].gen;
@@ -809,6 +834,9 @@ allocate_reloption(bits32 kinds, int type, const char *name, const char *desc,
case RELOPT_TYPE_BOOL:
size = sizeof(relopt_bool);
break;
+ case RELOPT_TYPE_TERNARY:
+ size = sizeof(relopt_ternary);
+ break;
case RELOPT_TYPE_INT:
size = sizeof(relopt_int);
break;
@@ -892,6 +920,57 @@ add_local_bool_reloption(local_relopts *relopts, const char *name,
add_local_reloption(relopts, (relopt_gen *) newoption, offset);
}
+/*
+ * init_ternary_reloption
+ * Allocate and initialize a new ternary reloption
+ */
+static relopt_ternary *
+init_ternary_reloption(bits32 kinds, const char *name, const char *desc,
+ pg_ternary default_val, LOCKMODE lockmode)
+{
+ relopt_ternary *newoption;
+
+ newoption = (relopt_ternary *)
+ allocate_reloption(kinds, RELOPT_TYPE_TERNARY, name, desc, lockmode);
+ newoption->default_val = default_val;
+
+ return newoption;
+}
+
+/*
+ * add_ternary_reloption
+ * Add a new ternary reloption
+ */
+void
+add_ternary_reloption(bits32 kinds, const char *name, const char *desc,
+ pg_ternary default_val, LOCKMODE lockmode)
+{
+ relopt_ternary *newoption;
+
+ newoption =
+ init_ternary_reloption(kinds, name, desc, default_val, lockmode);
+
+ add_reloption((relopt_gen *) newoption);
+}
+
+/*
+ * add_local_ternary_reloption
+ * Add a new ternary local reloption
+ *
+ * 'offset' is offset of ternary-typed field.
+ */
+void
+add_local_ternary_reloption(local_relopts *relopts, const char *name,
+ const char *desc, pg_ternary default_val,
+ int offset)
+{
+ relopt_ternary *newoption;
+
+ newoption =
+ init_ternary_reloption(RELOPT_KIND_LOCAL, name, desc, default_val, 0);
+
+ add_local_reloption(relopts, (relopt_gen *) newoption, offset);
+}
/*
* init_real_reloption
@@ -1626,6 +1705,19 @@ parse_one_reloption(relopt_value *option, char *text_str, int text_len,
option->gen->name, value)));
}
break;
+ case RELOPT_TYPE_TERNARY:
+ {
+ bool b;
+
+ parsed = parse_bool(value, &b);
+ option->values.ternary_val = b ? TERNARY_TRUE : TERNARY_FALSE;
+ if (validate && !parsed)
+ ereport(ERROR,
+ (errcode(ERRCODE_INVALID_PARAMETER_VALUE),
+ errmsg("invalid value for ternary option \"%s\": %s",
+ option->gen->name, value)));
+ }
+ break;
case RELOPT_TYPE_INT:
{
relopt_int *optint = (relopt_int *) option->gen;
@@ -1789,17 +1881,6 @@ fillRelOptions(void *rdopts, Size basesize,
char *itempos = ((char *) rdopts) + elems[j].offset;
char *string_val;
- /*
- * If isset_offset is provided, store whether the reloption is
- * set there.
- */
- if (elems[j].isset_offset > 0)
- {
- char *setpos = ((char *) rdopts) + elems[j].isset_offset;
-
- *(bool *) setpos = options[i].isset;
- }
-
switch (options[i].gen->type)
{
case RELOPT_TYPE_BOOL:
@@ -1807,6 +1888,11 @@ fillRelOptions(void *rdopts, Size basesize,
options[i].values.bool_val :
((relopt_bool *) options[i].gen)->default_val;
break;
+ case RELOPT_TYPE_TERNARY:
+ *(pg_ternary *) itempos = options[i].isset ?
+ options[i].values.ternary_val :
+ ((relopt_ternary *) options[i].gen)->default_val;
+ break;
case RELOPT_TYPE_INT:
*(int *) itempos = options[i].isset ?
options[i].values.int_val :
@@ -1923,8 +2009,8 @@ default_reloptions(Datum reloptions, bool validate, relopt_kind kind)
offsetof(StdRdOptions, parallel_workers)},
{"vacuum_index_cleanup", RELOPT_TYPE_ENUM,
offsetof(StdRdOptions, vacuum_index_cleanup)},
- {"vacuum_truncate", RELOPT_TYPE_BOOL,
- offsetof(StdRdOptions, vacuum_truncate), offsetof(StdRdOptions, vacuum_truncate_set)},
+ {"vacuum_truncate", RELOPT_TYPE_TERNARY,
+ offsetof(StdRdOptions, vacuum_truncate)},
{"vacuum_max_eager_freeze_failure_rate", RELOPT_TYPE_REAL,
offsetof(StdRdOptions, vacuum_max_eager_freeze_failure_rate)}
};
@@ -2004,7 +2090,6 @@ build_local_reloptions(local_relopts *relopts, Datum options, bool validate)
elems[i].optname = opt->option->name;
elems[i].opttype = opt->option->type;
elems[i].offset = opt->offset;
- elems[i].isset_offset = 0; /* not supported for local relopts yet */
i++;
}
diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c
index aa4fbec143f..696eab9bd97 100644
--- a/src/backend/commands/vacuum.c
+++ b/src/backend/commands/vacuum.c
@@ -2224,9 +2224,9 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams params,
{
StdRdOptions *opts = (StdRdOptions *) rel->rd_options;
- if (opts && opts->vacuum_truncate_set)
+ if (opts && opts->vacuum_truncate != TERNARY_UNSET)
{
- if (opts->vacuum_truncate)
+ if (opts->vacuum_truncate == TERNARY_TRUE)
params.truncate = VACOPTVALUE_ENABLED;
else
params.truncate = VACOPTVALUE_DISABLED;
diff --git a/src/include/access/reloptions.h b/src/include/access/reloptions.h
index 2f08e1b0cf0..dfbef2babf2 100644
--- a/src/include/access/reloptions.h
+++ b/src/include/access/reloptions.h
@@ -29,6 +29,7 @@
typedef enum relopt_type
{
RELOPT_TYPE_BOOL,
+ RELOPT_TYPE_TERNARY, /* on, off, unset */
RELOPT_TYPE_INT,
RELOPT_TYPE_REAL,
RELOPT_TYPE_ENUM,
@@ -80,6 +81,7 @@ typedef struct relopt_value
union
{
bool bool_val;
+ pg_ternary ternary_val;
int int_val;
double real_val;
int enum_val;
@@ -94,6 +96,12 @@ typedef struct relopt_bool
bool default_val;
} relopt_bool;
+typedef struct relopt_ternary
+{
+ relopt_gen gen;
+ int default_val;
+} relopt_ternary;
+
typedef struct relopt_int
{
relopt_gen gen;
@@ -152,19 +160,6 @@ typedef struct
const char *optname; /* option's name */
relopt_type opttype; /* option's datatype */
int offset; /* offset of field in result struct */
-
- /*
- * isset_offset is an optional offset of a field in the result struct that
- * stores whether the option is explicitly set for the relation or if it
- * just picked up the default value. In most cases, this can be
- * accomplished by giving the reloption a special out-of-range default
- * value (e.g., some integer reloptions use -2), but this isn't always
- * possible. For example, a Boolean reloption cannot be given an
- * out-of-range default, so we need another way to discover the source of
- * its value. This offset is only used if given a value greater than
- * zero.
- */
- int isset_offset;
} relopt_parse_elt;
/* Local reloption definition */
@@ -195,6 +190,9 @@ typedef struct local_relopts
extern relopt_kind add_reloption_kind(void);
extern void add_bool_reloption(bits32 kinds, const char *name, const char *desc,
bool default_val, LOCKMODE lockmode);
+extern void add_ternary_reloption(bits32 kinds, const char *name,
+ const char *desc, pg_ternary default_val,
+ LOCKMODE lockmode);
extern void add_int_reloption(bits32 kinds, const char *name, const char *desc,
int default_val, int min_val, int max_val,
LOCKMODE lockmode);
@@ -214,6 +212,9 @@ extern void register_reloptions_validator(local_relopts *relopts,
extern void add_local_bool_reloption(local_relopts *relopts, const char *name,
const char *desc, bool default_val,
int offset);
+extern void add_local_ternary_reloption(local_relopts *relopts,
+ const char *name, const char *desc,
+ pg_ternary default_val, int offset);
extern void add_local_int_reloption(local_relopts *relopts, const char *name,
const char *desc, int default_val,
int min_val, int max_val, int offset);
diff --git a/src/include/postgres.h b/src/include/postgres.h
index 7d93fbce709..47c88780776 100644
--- a/src/include/postgres.h
+++ b/src/include/postgres.h
@@ -543,6 +543,21 @@ Float8GetDatum(float8 X)
* ----------------------------------------------------------------
*/
+/*
+ * pg_ternary
+ * Boolean value with an extra "unset" value
+ *
+ * This enum can be used for values that want to distinguish between true,
+ * false, and unset.
+*/
+
+typedef enum pg_ternary
+{
+ TERNARY_FALSE = 0,
+ TERNARY_TRUE = 1,
+ TERNARY_UNSET = -1
+} pg_ternary;
+
/*
* NON_EXEC_STATIC: It's sometimes useful to define a variable or function
* that is normally static but extern when using EXEC_BACKEND (see
diff --git a/src/include/utils/rel.h b/src/include/utils/rel.h
index d03ab247788..5feb18a5373 100644
--- a/src/include/utils/rel.h
+++ b/src/include/utils/rel.h
@@ -347,8 +347,7 @@ typedef struct StdRdOptions
bool user_catalog_table; /* use as an additional catalog relation */
int parallel_workers; /* max number of parallel workers */
StdRdOptIndexCleanup vacuum_index_cleanup; /* controls index vacuuming */
- bool vacuum_truncate; /* enables vacuum to truncate a relation */
- bool vacuum_truncate_set; /* whether vacuum_truncate is set */
+ pg_ternary vacuum_truncate; /* enables vacuum to truncate a relation */
/*
* Fraction of pages in a relation that vacuum can eagerly scan and fail
diff --git a/src/test/regress/expected/reloptions.out b/src/test/regress/expected/reloptions.out
index 9de19b4e3f1..1c99f79ab01 100644
--- a/src/test/regress/expected/reloptions.out
+++ b/src/test/regress/expected/reloptions.out
@@ -98,6 +98,42 @@ SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass;
{fillfactor=13,autovacuum_enabled=false}
(1 row)
+-- Tests for future (FIXME) ternary options
+-- behave as boolean option: accept unassigned name and truncated value
+DROP TABLE reloptions_test;
+CREATE TABLE reloptions_test(i INT) WITH (vacuum_truncate);
+SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass;
+ reloptions
+------------------------
+ {vacuum_truncate=true}
+(1 row)
+
+DROP TABLE reloptions_test;
+CREATE TABLE reloptions_test(i INT) WITH (vacuum_truncate=FaLS);
+SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass;
+ reloptions
+------------------------
+ {vacuum_truncate=fals}
+(1 row)
+
+-- preferred "true" alias is used when storing
+DROP TABLE reloptions_test;
+CREATE TABLE reloptions_test(i INT) WITH (vacuum_index_cleanup=on);
+SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass;
+ reloptions
+---------------------------
+ {vacuum_index_cleanup=on}
+(1 row)
+
+-- custom "third" value is available
+DROP TABLE reloptions_test;
+CREATE TABLE reloptions_test(i INT) WITH (vacuum_index_cleanup=auto);
+SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass;
+ reloptions
+-----------------------------
+ {vacuum_index_cleanup=auto}
+(1 row)
+
-- Test vacuum_truncate option
DROP TABLE reloptions_test;
CREATE TEMP TABLE reloptions_test(i INT NOT NULL, j text)
diff --git a/src/test/regress/sql/reloptions.sql b/src/test/regress/sql/reloptions.sql
index 24fbe0b478d..f5980dafcbc 100644
--- a/src/test/regress/sql/reloptions.sql
+++ b/src/test/regress/sql/reloptions.sql
@@ -59,6 +59,27 @@ UPDATE pg_class
ALTER TABLE reloptions_test RESET (illegal_option);
SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass;
+-- Tests for future (FIXME) ternary options
+
+-- behave as boolean option: accept unassigned name and truncated value
+DROP TABLE reloptions_test;
+CREATE TABLE reloptions_test(i INT) WITH (vacuum_truncate);
+SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass;
+
+DROP TABLE reloptions_test;
+CREATE TABLE reloptions_test(i INT) WITH (vacuum_truncate=FaLS);
+SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass;
+
+-- preferred "true" alias is used when storing
+DROP TABLE reloptions_test;
+CREATE TABLE reloptions_test(i INT) WITH (vacuum_index_cleanup=on);
+SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass;
+
+-- custom "third" value is available
+DROP TABLE reloptions_test;
+CREATE TABLE reloptions_test(i INT) WITH (vacuum_index_cleanup=auto);
+SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass;
+
-- Test vacuum_truncate option
DROP TABLE reloptions_test;
diff --git a/src/tools/pgindent/typedefs.list b/src/tools/pgindent/typedefs.list
index 3f3a888fd0e..1c8610fd46c 100644
--- a/src/tools/pgindent/typedefs.list
+++ b/src/tools/pgindent/typedefs.list
@@ -3952,6 +3952,7 @@ pg_sha512_ctx
pg_snapshot
pg_special_case
pg_stack_base_t
+pg_ternary
pg_time_t
pg_time_usec_t
pg_tz
@@ -4079,6 +4080,7 @@ relopt_kind
relopt_parse_elt
relopt_real
relopt_string
+relopt_ternary
relopt_type
relopt_value
relopts_validator
--
2.47.3
--c36t3fndxhkzbl5z--
^ permalink raw reply [nested|flat] 868+ messages in thread
* [PATCH v3] Introduce ternary reloptions
@ 2026-01-16 15:04 Álvaro Herrera <[email protected]>
0 siblings, 0 replies; 868+ messages in thread
From: Álvaro Herrera @ 2026-01-16 15:04 UTC (permalink / raw)
Introduce ternary reloption as a replacement for current
`vacuum_truncate` implementation. Remove the `vacuum_truncate_set`
separate flag and use TERNARY_UNSET instead.
This could also be used for other options such as `vacuum_index_cleanup`
and `buffering`, but lets get the scaffolding in first.
Discussion: https://postgr.es/m/3474141.usfYGdeWWP@thinkpad-pgpro
---
src/backend/access/common/reloptions.c | 137 ++++++++++++++++++-----
src/backend/commands/vacuum.c | 4 +-
src/include/access/reloptions.h | 27 ++---
src/include/postgres.h | 15 +++
src/include/utils/rel.h | 3 +-
src/test/regress/expected/reloptions.out | 36 ++++++
src/test/regress/sql/reloptions.sql | 21 ++++
src/tools/pgindent/typedefs.list | 2 +
8 files changed, 202 insertions(+), 43 deletions(-)
diff --git a/src/backend/access/common/reloptions.c b/src/backend/access/common/reloptions.c
index 0b83f98ed5f..6f1f577581a 100644
--- a/src/backend/access/common/reloptions.c
+++ b/src/backend/access/common/reloptions.c
@@ -40,9 +40,9 @@
*
* To add an option:
*
- * (i) decide on a type (bool, integer, real, enum, string), name, default
- * value, upper and lower bounds (if applicable); for strings, consider a
- * validation routine.
+ * (i) decide on a type (bool, ternary, integer, real, enum, string), name,
+ * default value, upper and lower bounds (if applicable); for strings,
+ * consider a validation routine.
* (ii) add a record below (or use add_<type>_reloption).
* (iii) add it to the appropriate options struct (perhaps StdRdOptions)
* (iv) add it to the appropriate handling routine (perhaps
@@ -147,15 +147,6 @@ static relopt_bool boolRelOpts[] =
},
false
},
- {
- {
- "vacuum_truncate",
- "Enables vacuum to truncate empty pages at the end of this table",
- RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
- ShareUpdateExclusiveLock
- },
- true
- },
{
{
"deduplicate_items",
@@ -170,6 +161,25 @@ static relopt_bool boolRelOpts[] =
{{NULL}}
};
+static relopt_ternary ternaryRelOpts[] =
+{
+ {
+ {
+ "vacuum_truncate",
+ "Enables vacuum to truncate empty pages at the end of this table",
+ RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
+ ShareUpdateExclusiveLock
+ },
+ TERNARY_UNSET
+ },
+ /* list terminator */
+ {
+ {
+ NULL
+ }
+ }
+};
+
static relopt_int intRelOpts[] =
{
{
@@ -609,6 +619,13 @@ initialize_reloptions(void)
boolRelOpts[i].gen.lockmode));
j++;
}
+ for (i = 0; ternaryRelOpts[i].gen.name; i++)
+ {
+ Assert(DoLockModesConflict(ternaryRelOpts[i].gen.lockmode,
+ ternaryRelOpts[i].gen.lockmode));
+ j++;
+ }
+
for (i = 0; intRelOpts[i].gen.name; i++)
{
Assert(DoLockModesConflict(intRelOpts[i].gen.lockmode,
@@ -649,6 +666,14 @@ initialize_reloptions(void)
j++;
}
+ for (i = 0; ternaryRelOpts[i].gen.name; i++)
+ {
+ relOpts[j] = &ternaryRelOpts[i].gen;
+ relOpts[j]->type = RELOPT_TYPE_TERNARY;
+ relOpts[j]->namelen = strlen(relOpts[j]->name);
+ j++;
+ }
+
for (i = 0; intRelOpts[i].gen.name; i++)
{
relOpts[j] = &intRelOpts[i].gen;
@@ -809,6 +834,9 @@ allocate_reloption(bits32 kinds, int type, const char *name, const char *desc,
case RELOPT_TYPE_BOOL:
size = sizeof(relopt_bool);
break;
+ case RELOPT_TYPE_TERNARY:
+ size = sizeof(relopt_ternary);
+ break;
case RELOPT_TYPE_INT:
size = sizeof(relopt_int);
break;
@@ -892,6 +920,57 @@ add_local_bool_reloption(local_relopts *relopts, const char *name,
add_local_reloption(relopts, (relopt_gen *) newoption, offset);
}
+/*
+ * init_ternary_reloption
+ * Allocate and initialize a new ternary reloption
+ */
+static relopt_ternary *
+init_ternary_reloption(bits32 kinds, const char *name, const char *desc,
+ pg_ternary default_val, LOCKMODE lockmode)
+{
+ relopt_ternary *newoption;
+
+ newoption = (relopt_ternary *)
+ allocate_reloption(kinds, RELOPT_TYPE_TERNARY, name, desc, lockmode);
+ newoption->default_val = default_val;
+
+ return newoption;
+}
+
+/*
+ * add_ternary_reloption
+ * Add a new ternary reloption
+ */
+void
+add_ternary_reloption(bits32 kinds, const char *name, const char *desc,
+ pg_ternary default_val, LOCKMODE lockmode)
+{
+ relopt_ternary *newoption;
+
+ newoption =
+ init_ternary_reloption(kinds, name, desc, default_val, lockmode);
+
+ add_reloption((relopt_gen *) newoption);
+}
+
+/*
+ * add_local_ternary_reloption
+ * Add a new ternary local reloption
+ *
+ * 'offset' is offset of ternary-typed field.
+ */
+void
+add_local_ternary_reloption(local_relopts *relopts, const char *name,
+ const char *desc, pg_ternary default_val,
+ int offset)
+{
+ relopt_ternary *newoption;
+
+ newoption =
+ init_ternary_reloption(RELOPT_KIND_LOCAL, name, desc, default_val, 0);
+
+ add_local_reloption(relopts, (relopt_gen *) newoption, offset);
+}
/*
* init_real_reloption
@@ -1626,6 +1705,19 @@ parse_one_reloption(relopt_value *option, char *text_str, int text_len,
option->gen->name, value)));
}
break;
+ case RELOPT_TYPE_TERNARY:
+ {
+ bool b;
+
+ parsed = parse_bool(value, &b);
+ option->values.ternary_val = b ? TERNARY_TRUE : TERNARY_FALSE;
+ if (validate && !parsed)
+ ereport(ERROR,
+ (errcode(ERRCODE_INVALID_PARAMETER_VALUE),
+ errmsg("invalid value for ternary option \"%s\": %s",
+ option->gen->name, value)));
+ }
+ break;
case RELOPT_TYPE_INT:
{
relopt_int *optint = (relopt_int *) option->gen;
@@ -1789,17 +1881,6 @@ fillRelOptions(void *rdopts, Size basesize,
char *itempos = ((char *) rdopts) + elems[j].offset;
char *string_val;
- /*
- * If isset_offset is provided, store whether the reloption is
- * set there.
- */
- if (elems[j].isset_offset > 0)
- {
- char *setpos = ((char *) rdopts) + elems[j].isset_offset;
-
- *(bool *) setpos = options[i].isset;
- }
-
switch (options[i].gen->type)
{
case RELOPT_TYPE_BOOL:
@@ -1807,6 +1888,11 @@ fillRelOptions(void *rdopts, Size basesize,
options[i].values.bool_val :
((relopt_bool *) options[i].gen)->default_val;
break;
+ case RELOPT_TYPE_TERNARY:
+ *(pg_ternary *) itempos = options[i].isset ?
+ options[i].values.ternary_val :
+ ((relopt_ternary *) options[i].gen)->default_val;
+ break;
case RELOPT_TYPE_INT:
*(int *) itempos = options[i].isset ?
options[i].values.int_val :
@@ -1923,8 +2009,8 @@ default_reloptions(Datum reloptions, bool validate, relopt_kind kind)
offsetof(StdRdOptions, parallel_workers)},
{"vacuum_index_cleanup", RELOPT_TYPE_ENUM,
offsetof(StdRdOptions, vacuum_index_cleanup)},
- {"vacuum_truncate", RELOPT_TYPE_BOOL,
- offsetof(StdRdOptions, vacuum_truncate), offsetof(StdRdOptions, vacuum_truncate_set)},
+ {"vacuum_truncate", RELOPT_TYPE_TERNARY,
+ offsetof(StdRdOptions, vacuum_truncate)},
{"vacuum_max_eager_freeze_failure_rate", RELOPT_TYPE_REAL,
offsetof(StdRdOptions, vacuum_max_eager_freeze_failure_rate)}
};
@@ -2004,7 +2090,6 @@ build_local_reloptions(local_relopts *relopts, Datum options, bool validate)
elems[i].optname = opt->option->name;
elems[i].opttype = opt->option->type;
elems[i].offset = opt->offset;
- elems[i].isset_offset = 0; /* not supported for local relopts yet */
i++;
}
diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c
index aa4fbec143f..696eab9bd97 100644
--- a/src/backend/commands/vacuum.c
+++ b/src/backend/commands/vacuum.c
@@ -2224,9 +2224,9 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams params,
{
StdRdOptions *opts = (StdRdOptions *) rel->rd_options;
- if (opts && opts->vacuum_truncate_set)
+ if (opts && opts->vacuum_truncate != TERNARY_UNSET)
{
- if (opts->vacuum_truncate)
+ if (opts->vacuum_truncate == TERNARY_TRUE)
params.truncate = VACOPTVALUE_ENABLED;
else
params.truncate = VACOPTVALUE_DISABLED;
diff --git a/src/include/access/reloptions.h b/src/include/access/reloptions.h
index 2f08e1b0cf0..dfbef2babf2 100644
--- a/src/include/access/reloptions.h
+++ b/src/include/access/reloptions.h
@@ -29,6 +29,7 @@
typedef enum relopt_type
{
RELOPT_TYPE_BOOL,
+ RELOPT_TYPE_TERNARY, /* on, off, unset */
RELOPT_TYPE_INT,
RELOPT_TYPE_REAL,
RELOPT_TYPE_ENUM,
@@ -80,6 +81,7 @@ typedef struct relopt_value
union
{
bool bool_val;
+ pg_ternary ternary_val;
int int_val;
double real_val;
int enum_val;
@@ -94,6 +96,12 @@ typedef struct relopt_bool
bool default_val;
} relopt_bool;
+typedef struct relopt_ternary
+{
+ relopt_gen gen;
+ int default_val;
+} relopt_ternary;
+
typedef struct relopt_int
{
relopt_gen gen;
@@ -152,19 +160,6 @@ typedef struct
const char *optname; /* option's name */
relopt_type opttype; /* option's datatype */
int offset; /* offset of field in result struct */
-
- /*
- * isset_offset is an optional offset of a field in the result struct that
- * stores whether the option is explicitly set for the relation or if it
- * just picked up the default value. In most cases, this can be
- * accomplished by giving the reloption a special out-of-range default
- * value (e.g., some integer reloptions use -2), but this isn't always
- * possible. For example, a Boolean reloption cannot be given an
- * out-of-range default, so we need another way to discover the source of
- * its value. This offset is only used if given a value greater than
- * zero.
- */
- int isset_offset;
} relopt_parse_elt;
/* Local reloption definition */
@@ -195,6 +190,9 @@ typedef struct local_relopts
extern relopt_kind add_reloption_kind(void);
extern void add_bool_reloption(bits32 kinds, const char *name, const char *desc,
bool default_val, LOCKMODE lockmode);
+extern void add_ternary_reloption(bits32 kinds, const char *name,
+ const char *desc, pg_ternary default_val,
+ LOCKMODE lockmode);
extern void add_int_reloption(bits32 kinds, const char *name, const char *desc,
int default_val, int min_val, int max_val,
LOCKMODE lockmode);
@@ -214,6 +212,9 @@ extern void register_reloptions_validator(local_relopts *relopts,
extern void add_local_bool_reloption(local_relopts *relopts, const char *name,
const char *desc, bool default_val,
int offset);
+extern void add_local_ternary_reloption(local_relopts *relopts,
+ const char *name, const char *desc,
+ pg_ternary default_val, int offset);
extern void add_local_int_reloption(local_relopts *relopts, const char *name,
const char *desc, int default_val,
int min_val, int max_val, int offset);
diff --git a/src/include/postgres.h b/src/include/postgres.h
index 7d93fbce709..47c88780776 100644
--- a/src/include/postgres.h
+++ b/src/include/postgres.h
@@ -543,6 +543,21 @@ Float8GetDatum(float8 X)
* ----------------------------------------------------------------
*/
+/*
+ * pg_ternary
+ * Boolean value with an extra "unset" value
+ *
+ * This enum can be used for values that want to distinguish between true,
+ * false, and unset.
+*/
+
+typedef enum pg_ternary
+{
+ TERNARY_FALSE = 0,
+ TERNARY_TRUE = 1,
+ TERNARY_UNSET = -1
+} pg_ternary;
+
/*
* NON_EXEC_STATIC: It's sometimes useful to define a variable or function
* that is normally static but extern when using EXEC_BACKEND (see
diff --git a/src/include/utils/rel.h b/src/include/utils/rel.h
index d03ab247788..5feb18a5373 100644
--- a/src/include/utils/rel.h
+++ b/src/include/utils/rel.h
@@ -347,8 +347,7 @@ typedef struct StdRdOptions
bool user_catalog_table; /* use as an additional catalog relation */
int parallel_workers; /* max number of parallel workers */
StdRdOptIndexCleanup vacuum_index_cleanup; /* controls index vacuuming */
- bool vacuum_truncate; /* enables vacuum to truncate a relation */
- bool vacuum_truncate_set; /* whether vacuum_truncate is set */
+ pg_ternary vacuum_truncate; /* enables vacuum to truncate a relation */
/*
* Fraction of pages in a relation that vacuum can eagerly scan and fail
diff --git a/src/test/regress/expected/reloptions.out b/src/test/regress/expected/reloptions.out
index 9de19b4e3f1..1c99f79ab01 100644
--- a/src/test/regress/expected/reloptions.out
+++ b/src/test/regress/expected/reloptions.out
@@ -98,6 +98,42 @@ SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass;
{fillfactor=13,autovacuum_enabled=false}
(1 row)
+-- Tests for future (FIXME) ternary options
+-- behave as boolean option: accept unassigned name and truncated value
+DROP TABLE reloptions_test;
+CREATE TABLE reloptions_test(i INT) WITH (vacuum_truncate);
+SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass;
+ reloptions
+------------------------
+ {vacuum_truncate=true}
+(1 row)
+
+DROP TABLE reloptions_test;
+CREATE TABLE reloptions_test(i INT) WITH (vacuum_truncate=FaLS);
+SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass;
+ reloptions
+------------------------
+ {vacuum_truncate=fals}
+(1 row)
+
+-- preferred "true" alias is used when storing
+DROP TABLE reloptions_test;
+CREATE TABLE reloptions_test(i INT) WITH (vacuum_index_cleanup=on);
+SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass;
+ reloptions
+---------------------------
+ {vacuum_index_cleanup=on}
+(1 row)
+
+-- custom "third" value is available
+DROP TABLE reloptions_test;
+CREATE TABLE reloptions_test(i INT) WITH (vacuum_index_cleanup=auto);
+SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass;
+ reloptions
+-----------------------------
+ {vacuum_index_cleanup=auto}
+(1 row)
+
-- Test vacuum_truncate option
DROP TABLE reloptions_test;
CREATE TEMP TABLE reloptions_test(i INT NOT NULL, j text)
diff --git a/src/test/regress/sql/reloptions.sql b/src/test/regress/sql/reloptions.sql
index 24fbe0b478d..f5980dafcbc 100644
--- a/src/test/regress/sql/reloptions.sql
+++ b/src/test/regress/sql/reloptions.sql
@@ -59,6 +59,27 @@ UPDATE pg_class
ALTER TABLE reloptions_test RESET (illegal_option);
SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass;
+-- Tests for future (FIXME) ternary options
+
+-- behave as boolean option: accept unassigned name and truncated value
+DROP TABLE reloptions_test;
+CREATE TABLE reloptions_test(i INT) WITH (vacuum_truncate);
+SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass;
+
+DROP TABLE reloptions_test;
+CREATE TABLE reloptions_test(i INT) WITH (vacuum_truncate=FaLS);
+SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass;
+
+-- preferred "true" alias is used when storing
+DROP TABLE reloptions_test;
+CREATE TABLE reloptions_test(i INT) WITH (vacuum_index_cleanup=on);
+SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass;
+
+-- custom "third" value is available
+DROP TABLE reloptions_test;
+CREATE TABLE reloptions_test(i INT) WITH (vacuum_index_cleanup=auto);
+SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass;
+
-- Test vacuum_truncate option
DROP TABLE reloptions_test;
diff --git a/src/tools/pgindent/typedefs.list b/src/tools/pgindent/typedefs.list
index 3f3a888fd0e..1c8610fd46c 100644
--- a/src/tools/pgindent/typedefs.list
+++ b/src/tools/pgindent/typedefs.list
@@ -3952,6 +3952,7 @@ pg_sha512_ctx
pg_snapshot
pg_special_case
pg_stack_base_t
+pg_ternary
pg_time_t
pg_time_usec_t
pg_tz
@@ -4079,6 +4080,7 @@ relopt_kind
relopt_parse_elt
relopt_real
relopt_string
+relopt_ternary
relopt_type
relopt_value
relopts_validator
--
2.47.3
--c36t3fndxhkzbl5z--
^ permalink raw reply [nested|flat] 868+ messages in thread
* [PATCH v3] Introduce ternary reloptions
@ 2026-01-16 15:04 Álvaro Herrera <[email protected]>
0 siblings, 0 replies; 868+ messages in thread
From: Álvaro Herrera @ 2026-01-16 15:04 UTC (permalink / raw)
Introduce ternary reloption as a replacement for current
`vacuum_truncate` implementation. Remove the `vacuum_truncate_set`
separate flag and use TERNARY_UNSET instead.
This could also be used for other options such as `vacuum_index_cleanup`
and `buffering`, but lets get the scaffolding in first.
Discussion: https://postgr.es/m/3474141.usfYGdeWWP@thinkpad-pgpro
---
src/backend/access/common/reloptions.c | 137 ++++++++++++++++++-----
src/backend/commands/vacuum.c | 4 +-
src/include/access/reloptions.h | 27 ++---
src/include/postgres.h | 15 +++
src/include/utils/rel.h | 3 +-
src/test/regress/expected/reloptions.out | 36 ++++++
src/test/regress/sql/reloptions.sql | 21 ++++
src/tools/pgindent/typedefs.list | 2 +
8 files changed, 202 insertions(+), 43 deletions(-)
diff --git a/src/backend/access/common/reloptions.c b/src/backend/access/common/reloptions.c
index 0b83f98ed5f..6f1f577581a 100644
--- a/src/backend/access/common/reloptions.c
+++ b/src/backend/access/common/reloptions.c
@@ -40,9 +40,9 @@
*
* To add an option:
*
- * (i) decide on a type (bool, integer, real, enum, string), name, default
- * value, upper and lower bounds (if applicable); for strings, consider a
- * validation routine.
+ * (i) decide on a type (bool, ternary, integer, real, enum, string), name,
+ * default value, upper and lower bounds (if applicable); for strings,
+ * consider a validation routine.
* (ii) add a record below (or use add_<type>_reloption).
* (iii) add it to the appropriate options struct (perhaps StdRdOptions)
* (iv) add it to the appropriate handling routine (perhaps
@@ -147,15 +147,6 @@ static relopt_bool boolRelOpts[] =
},
false
},
- {
- {
- "vacuum_truncate",
- "Enables vacuum to truncate empty pages at the end of this table",
- RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
- ShareUpdateExclusiveLock
- },
- true
- },
{
{
"deduplicate_items",
@@ -170,6 +161,25 @@ static relopt_bool boolRelOpts[] =
{{NULL}}
};
+static relopt_ternary ternaryRelOpts[] =
+{
+ {
+ {
+ "vacuum_truncate",
+ "Enables vacuum to truncate empty pages at the end of this table",
+ RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
+ ShareUpdateExclusiveLock
+ },
+ TERNARY_UNSET
+ },
+ /* list terminator */
+ {
+ {
+ NULL
+ }
+ }
+};
+
static relopt_int intRelOpts[] =
{
{
@@ -609,6 +619,13 @@ initialize_reloptions(void)
boolRelOpts[i].gen.lockmode));
j++;
}
+ for (i = 0; ternaryRelOpts[i].gen.name; i++)
+ {
+ Assert(DoLockModesConflict(ternaryRelOpts[i].gen.lockmode,
+ ternaryRelOpts[i].gen.lockmode));
+ j++;
+ }
+
for (i = 0; intRelOpts[i].gen.name; i++)
{
Assert(DoLockModesConflict(intRelOpts[i].gen.lockmode,
@@ -649,6 +666,14 @@ initialize_reloptions(void)
j++;
}
+ for (i = 0; ternaryRelOpts[i].gen.name; i++)
+ {
+ relOpts[j] = &ternaryRelOpts[i].gen;
+ relOpts[j]->type = RELOPT_TYPE_TERNARY;
+ relOpts[j]->namelen = strlen(relOpts[j]->name);
+ j++;
+ }
+
for (i = 0; intRelOpts[i].gen.name; i++)
{
relOpts[j] = &intRelOpts[i].gen;
@@ -809,6 +834,9 @@ allocate_reloption(bits32 kinds, int type, const char *name, const char *desc,
case RELOPT_TYPE_BOOL:
size = sizeof(relopt_bool);
break;
+ case RELOPT_TYPE_TERNARY:
+ size = sizeof(relopt_ternary);
+ break;
case RELOPT_TYPE_INT:
size = sizeof(relopt_int);
break;
@@ -892,6 +920,57 @@ add_local_bool_reloption(local_relopts *relopts, const char *name,
add_local_reloption(relopts, (relopt_gen *) newoption, offset);
}
+/*
+ * init_ternary_reloption
+ * Allocate and initialize a new ternary reloption
+ */
+static relopt_ternary *
+init_ternary_reloption(bits32 kinds, const char *name, const char *desc,
+ pg_ternary default_val, LOCKMODE lockmode)
+{
+ relopt_ternary *newoption;
+
+ newoption = (relopt_ternary *)
+ allocate_reloption(kinds, RELOPT_TYPE_TERNARY, name, desc, lockmode);
+ newoption->default_val = default_val;
+
+ return newoption;
+}
+
+/*
+ * add_ternary_reloption
+ * Add a new ternary reloption
+ */
+void
+add_ternary_reloption(bits32 kinds, const char *name, const char *desc,
+ pg_ternary default_val, LOCKMODE lockmode)
+{
+ relopt_ternary *newoption;
+
+ newoption =
+ init_ternary_reloption(kinds, name, desc, default_val, lockmode);
+
+ add_reloption((relopt_gen *) newoption);
+}
+
+/*
+ * add_local_ternary_reloption
+ * Add a new ternary local reloption
+ *
+ * 'offset' is offset of ternary-typed field.
+ */
+void
+add_local_ternary_reloption(local_relopts *relopts, const char *name,
+ const char *desc, pg_ternary default_val,
+ int offset)
+{
+ relopt_ternary *newoption;
+
+ newoption =
+ init_ternary_reloption(RELOPT_KIND_LOCAL, name, desc, default_val, 0);
+
+ add_local_reloption(relopts, (relopt_gen *) newoption, offset);
+}
/*
* init_real_reloption
@@ -1626,6 +1705,19 @@ parse_one_reloption(relopt_value *option, char *text_str, int text_len,
option->gen->name, value)));
}
break;
+ case RELOPT_TYPE_TERNARY:
+ {
+ bool b;
+
+ parsed = parse_bool(value, &b);
+ option->values.ternary_val = b ? TERNARY_TRUE : TERNARY_FALSE;
+ if (validate && !parsed)
+ ereport(ERROR,
+ (errcode(ERRCODE_INVALID_PARAMETER_VALUE),
+ errmsg("invalid value for ternary option \"%s\": %s",
+ option->gen->name, value)));
+ }
+ break;
case RELOPT_TYPE_INT:
{
relopt_int *optint = (relopt_int *) option->gen;
@@ -1789,17 +1881,6 @@ fillRelOptions(void *rdopts, Size basesize,
char *itempos = ((char *) rdopts) + elems[j].offset;
char *string_val;
- /*
- * If isset_offset is provided, store whether the reloption is
- * set there.
- */
- if (elems[j].isset_offset > 0)
- {
- char *setpos = ((char *) rdopts) + elems[j].isset_offset;
-
- *(bool *) setpos = options[i].isset;
- }
-
switch (options[i].gen->type)
{
case RELOPT_TYPE_BOOL:
@@ -1807,6 +1888,11 @@ fillRelOptions(void *rdopts, Size basesize,
options[i].values.bool_val :
((relopt_bool *) options[i].gen)->default_val;
break;
+ case RELOPT_TYPE_TERNARY:
+ *(pg_ternary *) itempos = options[i].isset ?
+ options[i].values.ternary_val :
+ ((relopt_ternary *) options[i].gen)->default_val;
+ break;
case RELOPT_TYPE_INT:
*(int *) itempos = options[i].isset ?
options[i].values.int_val :
@@ -1923,8 +2009,8 @@ default_reloptions(Datum reloptions, bool validate, relopt_kind kind)
offsetof(StdRdOptions, parallel_workers)},
{"vacuum_index_cleanup", RELOPT_TYPE_ENUM,
offsetof(StdRdOptions, vacuum_index_cleanup)},
- {"vacuum_truncate", RELOPT_TYPE_BOOL,
- offsetof(StdRdOptions, vacuum_truncate), offsetof(StdRdOptions, vacuum_truncate_set)},
+ {"vacuum_truncate", RELOPT_TYPE_TERNARY,
+ offsetof(StdRdOptions, vacuum_truncate)},
{"vacuum_max_eager_freeze_failure_rate", RELOPT_TYPE_REAL,
offsetof(StdRdOptions, vacuum_max_eager_freeze_failure_rate)}
};
@@ -2004,7 +2090,6 @@ build_local_reloptions(local_relopts *relopts, Datum options, bool validate)
elems[i].optname = opt->option->name;
elems[i].opttype = opt->option->type;
elems[i].offset = opt->offset;
- elems[i].isset_offset = 0; /* not supported for local relopts yet */
i++;
}
diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c
index aa4fbec143f..696eab9bd97 100644
--- a/src/backend/commands/vacuum.c
+++ b/src/backend/commands/vacuum.c
@@ -2224,9 +2224,9 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams params,
{
StdRdOptions *opts = (StdRdOptions *) rel->rd_options;
- if (opts && opts->vacuum_truncate_set)
+ if (opts && opts->vacuum_truncate != TERNARY_UNSET)
{
- if (opts->vacuum_truncate)
+ if (opts->vacuum_truncate == TERNARY_TRUE)
params.truncate = VACOPTVALUE_ENABLED;
else
params.truncate = VACOPTVALUE_DISABLED;
diff --git a/src/include/access/reloptions.h b/src/include/access/reloptions.h
index 2f08e1b0cf0..dfbef2babf2 100644
--- a/src/include/access/reloptions.h
+++ b/src/include/access/reloptions.h
@@ -29,6 +29,7 @@
typedef enum relopt_type
{
RELOPT_TYPE_BOOL,
+ RELOPT_TYPE_TERNARY, /* on, off, unset */
RELOPT_TYPE_INT,
RELOPT_TYPE_REAL,
RELOPT_TYPE_ENUM,
@@ -80,6 +81,7 @@ typedef struct relopt_value
union
{
bool bool_val;
+ pg_ternary ternary_val;
int int_val;
double real_val;
int enum_val;
@@ -94,6 +96,12 @@ typedef struct relopt_bool
bool default_val;
} relopt_bool;
+typedef struct relopt_ternary
+{
+ relopt_gen gen;
+ int default_val;
+} relopt_ternary;
+
typedef struct relopt_int
{
relopt_gen gen;
@@ -152,19 +160,6 @@ typedef struct
const char *optname; /* option's name */
relopt_type opttype; /* option's datatype */
int offset; /* offset of field in result struct */
-
- /*
- * isset_offset is an optional offset of a field in the result struct that
- * stores whether the option is explicitly set for the relation or if it
- * just picked up the default value. In most cases, this can be
- * accomplished by giving the reloption a special out-of-range default
- * value (e.g., some integer reloptions use -2), but this isn't always
- * possible. For example, a Boolean reloption cannot be given an
- * out-of-range default, so we need another way to discover the source of
- * its value. This offset is only used if given a value greater than
- * zero.
- */
- int isset_offset;
} relopt_parse_elt;
/* Local reloption definition */
@@ -195,6 +190,9 @@ typedef struct local_relopts
extern relopt_kind add_reloption_kind(void);
extern void add_bool_reloption(bits32 kinds, const char *name, const char *desc,
bool default_val, LOCKMODE lockmode);
+extern void add_ternary_reloption(bits32 kinds, const char *name,
+ const char *desc, pg_ternary default_val,
+ LOCKMODE lockmode);
extern void add_int_reloption(bits32 kinds, const char *name, const char *desc,
int default_val, int min_val, int max_val,
LOCKMODE lockmode);
@@ -214,6 +212,9 @@ extern void register_reloptions_validator(local_relopts *relopts,
extern void add_local_bool_reloption(local_relopts *relopts, const char *name,
const char *desc, bool default_val,
int offset);
+extern void add_local_ternary_reloption(local_relopts *relopts,
+ const char *name, const char *desc,
+ pg_ternary default_val, int offset);
extern void add_local_int_reloption(local_relopts *relopts, const char *name,
const char *desc, int default_val,
int min_val, int max_val, int offset);
diff --git a/src/include/postgres.h b/src/include/postgres.h
index 7d93fbce709..47c88780776 100644
--- a/src/include/postgres.h
+++ b/src/include/postgres.h
@@ -543,6 +543,21 @@ Float8GetDatum(float8 X)
* ----------------------------------------------------------------
*/
+/*
+ * pg_ternary
+ * Boolean value with an extra "unset" value
+ *
+ * This enum can be used for values that want to distinguish between true,
+ * false, and unset.
+*/
+
+typedef enum pg_ternary
+{
+ TERNARY_FALSE = 0,
+ TERNARY_TRUE = 1,
+ TERNARY_UNSET = -1
+} pg_ternary;
+
/*
* NON_EXEC_STATIC: It's sometimes useful to define a variable or function
* that is normally static but extern when using EXEC_BACKEND (see
diff --git a/src/include/utils/rel.h b/src/include/utils/rel.h
index d03ab247788..5feb18a5373 100644
--- a/src/include/utils/rel.h
+++ b/src/include/utils/rel.h
@@ -347,8 +347,7 @@ typedef struct StdRdOptions
bool user_catalog_table; /* use as an additional catalog relation */
int parallel_workers; /* max number of parallel workers */
StdRdOptIndexCleanup vacuum_index_cleanup; /* controls index vacuuming */
- bool vacuum_truncate; /* enables vacuum to truncate a relation */
- bool vacuum_truncate_set; /* whether vacuum_truncate is set */
+ pg_ternary vacuum_truncate; /* enables vacuum to truncate a relation */
/*
* Fraction of pages in a relation that vacuum can eagerly scan and fail
diff --git a/src/test/regress/expected/reloptions.out b/src/test/regress/expected/reloptions.out
index 9de19b4e3f1..1c99f79ab01 100644
--- a/src/test/regress/expected/reloptions.out
+++ b/src/test/regress/expected/reloptions.out
@@ -98,6 +98,42 @@ SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass;
{fillfactor=13,autovacuum_enabled=false}
(1 row)
+-- Tests for future (FIXME) ternary options
+-- behave as boolean option: accept unassigned name and truncated value
+DROP TABLE reloptions_test;
+CREATE TABLE reloptions_test(i INT) WITH (vacuum_truncate);
+SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass;
+ reloptions
+------------------------
+ {vacuum_truncate=true}
+(1 row)
+
+DROP TABLE reloptions_test;
+CREATE TABLE reloptions_test(i INT) WITH (vacuum_truncate=FaLS);
+SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass;
+ reloptions
+------------------------
+ {vacuum_truncate=fals}
+(1 row)
+
+-- preferred "true" alias is used when storing
+DROP TABLE reloptions_test;
+CREATE TABLE reloptions_test(i INT) WITH (vacuum_index_cleanup=on);
+SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass;
+ reloptions
+---------------------------
+ {vacuum_index_cleanup=on}
+(1 row)
+
+-- custom "third" value is available
+DROP TABLE reloptions_test;
+CREATE TABLE reloptions_test(i INT) WITH (vacuum_index_cleanup=auto);
+SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass;
+ reloptions
+-----------------------------
+ {vacuum_index_cleanup=auto}
+(1 row)
+
-- Test vacuum_truncate option
DROP TABLE reloptions_test;
CREATE TEMP TABLE reloptions_test(i INT NOT NULL, j text)
diff --git a/src/test/regress/sql/reloptions.sql b/src/test/regress/sql/reloptions.sql
index 24fbe0b478d..f5980dafcbc 100644
--- a/src/test/regress/sql/reloptions.sql
+++ b/src/test/regress/sql/reloptions.sql
@@ -59,6 +59,27 @@ UPDATE pg_class
ALTER TABLE reloptions_test RESET (illegal_option);
SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass;
+-- Tests for future (FIXME) ternary options
+
+-- behave as boolean option: accept unassigned name and truncated value
+DROP TABLE reloptions_test;
+CREATE TABLE reloptions_test(i INT) WITH (vacuum_truncate);
+SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass;
+
+DROP TABLE reloptions_test;
+CREATE TABLE reloptions_test(i INT) WITH (vacuum_truncate=FaLS);
+SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass;
+
+-- preferred "true" alias is used when storing
+DROP TABLE reloptions_test;
+CREATE TABLE reloptions_test(i INT) WITH (vacuum_index_cleanup=on);
+SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass;
+
+-- custom "third" value is available
+DROP TABLE reloptions_test;
+CREATE TABLE reloptions_test(i INT) WITH (vacuum_index_cleanup=auto);
+SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass;
+
-- Test vacuum_truncate option
DROP TABLE reloptions_test;
diff --git a/src/tools/pgindent/typedefs.list b/src/tools/pgindent/typedefs.list
index 3f3a888fd0e..1c8610fd46c 100644
--- a/src/tools/pgindent/typedefs.list
+++ b/src/tools/pgindent/typedefs.list
@@ -3952,6 +3952,7 @@ pg_sha512_ctx
pg_snapshot
pg_special_case
pg_stack_base_t
+pg_ternary
pg_time_t
pg_time_usec_t
pg_tz
@@ -4079,6 +4080,7 @@ relopt_kind
relopt_parse_elt
relopt_real
relopt_string
+relopt_ternary
relopt_type
relopt_value
relopts_validator
--
2.47.3
--c36t3fndxhkzbl5z--
^ permalink raw reply [nested|flat] 868+ messages in thread
* [PATCH v3] Introduce ternary reloptions
@ 2026-01-16 15:04 Álvaro Herrera <[email protected]>
0 siblings, 0 replies; 868+ messages in thread
From: Álvaro Herrera @ 2026-01-16 15:04 UTC (permalink / raw)
Introduce ternary reloption as a replacement for current
`vacuum_truncate` implementation. Remove the `vacuum_truncate_set`
separate flag and use TERNARY_UNSET instead.
This could also be used for other options such as `vacuum_index_cleanup`
and `buffering`, but lets get the scaffolding in first.
Discussion: https://postgr.es/m/3474141.usfYGdeWWP@thinkpad-pgpro
---
src/backend/access/common/reloptions.c | 137 ++++++++++++++++++-----
src/backend/commands/vacuum.c | 4 +-
src/include/access/reloptions.h | 27 ++---
src/include/postgres.h | 15 +++
src/include/utils/rel.h | 3 +-
src/test/regress/expected/reloptions.out | 36 ++++++
src/test/regress/sql/reloptions.sql | 21 ++++
src/tools/pgindent/typedefs.list | 2 +
8 files changed, 202 insertions(+), 43 deletions(-)
diff --git a/src/backend/access/common/reloptions.c b/src/backend/access/common/reloptions.c
index 0b83f98ed5f..6f1f577581a 100644
--- a/src/backend/access/common/reloptions.c
+++ b/src/backend/access/common/reloptions.c
@@ -40,9 +40,9 @@
*
* To add an option:
*
- * (i) decide on a type (bool, integer, real, enum, string), name, default
- * value, upper and lower bounds (if applicable); for strings, consider a
- * validation routine.
+ * (i) decide on a type (bool, ternary, integer, real, enum, string), name,
+ * default value, upper and lower bounds (if applicable); for strings,
+ * consider a validation routine.
* (ii) add a record below (or use add_<type>_reloption).
* (iii) add it to the appropriate options struct (perhaps StdRdOptions)
* (iv) add it to the appropriate handling routine (perhaps
@@ -147,15 +147,6 @@ static relopt_bool boolRelOpts[] =
},
false
},
- {
- {
- "vacuum_truncate",
- "Enables vacuum to truncate empty pages at the end of this table",
- RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
- ShareUpdateExclusiveLock
- },
- true
- },
{
{
"deduplicate_items",
@@ -170,6 +161,25 @@ static relopt_bool boolRelOpts[] =
{{NULL}}
};
+static relopt_ternary ternaryRelOpts[] =
+{
+ {
+ {
+ "vacuum_truncate",
+ "Enables vacuum to truncate empty pages at the end of this table",
+ RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
+ ShareUpdateExclusiveLock
+ },
+ TERNARY_UNSET
+ },
+ /* list terminator */
+ {
+ {
+ NULL
+ }
+ }
+};
+
static relopt_int intRelOpts[] =
{
{
@@ -609,6 +619,13 @@ initialize_reloptions(void)
boolRelOpts[i].gen.lockmode));
j++;
}
+ for (i = 0; ternaryRelOpts[i].gen.name; i++)
+ {
+ Assert(DoLockModesConflict(ternaryRelOpts[i].gen.lockmode,
+ ternaryRelOpts[i].gen.lockmode));
+ j++;
+ }
+
for (i = 0; intRelOpts[i].gen.name; i++)
{
Assert(DoLockModesConflict(intRelOpts[i].gen.lockmode,
@@ -649,6 +666,14 @@ initialize_reloptions(void)
j++;
}
+ for (i = 0; ternaryRelOpts[i].gen.name; i++)
+ {
+ relOpts[j] = &ternaryRelOpts[i].gen;
+ relOpts[j]->type = RELOPT_TYPE_TERNARY;
+ relOpts[j]->namelen = strlen(relOpts[j]->name);
+ j++;
+ }
+
for (i = 0; intRelOpts[i].gen.name; i++)
{
relOpts[j] = &intRelOpts[i].gen;
@@ -809,6 +834,9 @@ allocate_reloption(bits32 kinds, int type, const char *name, const char *desc,
case RELOPT_TYPE_BOOL:
size = sizeof(relopt_bool);
break;
+ case RELOPT_TYPE_TERNARY:
+ size = sizeof(relopt_ternary);
+ break;
case RELOPT_TYPE_INT:
size = sizeof(relopt_int);
break;
@@ -892,6 +920,57 @@ add_local_bool_reloption(local_relopts *relopts, const char *name,
add_local_reloption(relopts, (relopt_gen *) newoption, offset);
}
+/*
+ * init_ternary_reloption
+ * Allocate and initialize a new ternary reloption
+ */
+static relopt_ternary *
+init_ternary_reloption(bits32 kinds, const char *name, const char *desc,
+ pg_ternary default_val, LOCKMODE lockmode)
+{
+ relopt_ternary *newoption;
+
+ newoption = (relopt_ternary *)
+ allocate_reloption(kinds, RELOPT_TYPE_TERNARY, name, desc, lockmode);
+ newoption->default_val = default_val;
+
+ return newoption;
+}
+
+/*
+ * add_ternary_reloption
+ * Add a new ternary reloption
+ */
+void
+add_ternary_reloption(bits32 kinds, const char *name, const char *desc,
+ pg_ternary default_val, LOCKMODE lockmode)
+{
+ relopt_ternary *newoption;
+
+ newoption =
+ init_ternary_reloption(kinds, name, desc, default_val, lockmode);
+
+ add_reloption((relopt_gen *) newoption);
+}
+
+/*
+ * add_local_ternary_reloption
+ * Add a new ternary local reloption
+ *
+ * 'offset' is offset of ternary-typed field.
+ */
+void
+add_local_ternary_reloption(local_relopts *relopts, const char *name,
+ const char *desc, pg_ternary default_val,
+ int offset)
+{
+ relopt_ternary *newoption;
+
+ newoption =
+ init_ternary_reloption(RELOPT_KIND_LOCAL, name, desc, default_val, 0);
+
+ add_local_reloption(relopts, (relopt_gen *) newoption, offset);
+}
/*
* init_real_reloption
@@ -1626,6 +1705,19 @@ parse_one_reloption(relopt_value *option, char *text_str, int text_len,
option->gen->name, value)));
}
break;
+ case RELOPT_TYPE_TERNARY:
+ {
+ bool b;
+
+ parsed = parse_bool(value, &b);
+ option->values.ternary_val = b ? TERNARY_TRUE : TERNARY_FALSE;
+ if (validate && !parsed)
+ ereport(ERROR,
+ (errcode(ERRCODE_INVALID_PARAMETER_VALUE),
+ errmsg("invalid value for ternary option \"%s\": %s",
+ option->gen->name, value)));
+ }
+ break;
case RELOPT_TYPE_INT:
{
relopt_int *optint = (relopt_int *) option->gen;
@@ -1789,17 +1881,6 @@ fillRelOptions(void *rdopts, Size basesize,
char *itempos = ((char *) rdopts) + elems[j].offset;
char *string_val;
- /*
- * If isset_offset is provided, store whether the reloption is
- * set there.
- */
- if (elems[j].isset_offset > 0)
- {
- char *setpos = ((char *) rdopts) + elems[j].isset_offset;
-
- *(bool *) setpos = options[i].isset;
- }
-
switch (options[i].gen->type)
{
case RELOPT_TYPE_BOOL:
@@ -1807,6 +1888,11 @@ fillRelOptions(void *rdopts, Size basesize,
options[i].values.bool_val :
((relopt_bool *) options[i].gen)->default_val;
break;
+ case RELOPT_TYPE_TERNARY:
+ *(pg_ternary *) itempos = options[i].isset ?
+ options[i].values.ternary_val :
+ ((relopt_ternary *) options[i].gen)->default_val;
+ break;
case RELOPT_TYPE_INT:
*(int *) itempos = options[i].isset ?
options[i].values.int_val :
@@ -1923,8 +2009,8 @@ default_reloptions(Datum reloptions, bool validate, relopt_kind kind)
offsetof(StdRdOptions, parallel_workers)},
{"vacuum_index_cleanup", RELOPT_TYPE_ENUM,
offsetof(StdRdOptions, vacuum_index_cleanup)},
- {"vacuum_truncate", RELOPT_TYPE_BOOL,
- offsetof(StdRdOptions, vacuum_truncate), offsetof(StdRdOptions, vacuum_truncate_set)},
+ {"vacuum_truncate", RELOPT_TYPE_TERNARY,
+ offsetof(StdRdOptions, vacuum_truncate)},
{"vacuum_max_eager_freeze_failure_rate", RELOPT_TYPE_REAL,
offsetof(StdRdOptions, vacuum_max_eager_freeze_failure_rate)}
};
@@ -2004,7 +2090,6 @@ build_local_reloptions(local_relopts *relopts, Datum options, bool validate)
elems[i].optname = opt->option->name;
elems[i].opttype = opt->option->type;
elems[i].offset = opt->offset;
- elems[i].isset_offset = 0; /* not supported for local relopts yet */
i++;
}
diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c
index aa4fbec143f..696eab9bd97 100644
--- a/src/backend/commands/vacuum.c
+++ b/src/backend/commands/vacuum.c
@@ -2224,9 +2224,9 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams params,
{
StdRdOptions *opts = (StdRdOptions *) rel->rd_options;
- if (opts && opts->vacuum_truncate_set)
+ if (opts && opts->vacuum_truncate != TERNARY_UNSET)
{
- if (opts->vacuum_truncate)
+ if (opts->vacuum_truncate == TERNARY_TRUE)
params.truncate = VACOPTVALUE_ENABLED;
else
params.truncate = VACOPTVALUE_DISABLED;
diff --git a/src/include/access/reloptions.h b/src/include/access/reloptions.h
index 2f08e1b0cf0..dfbef2babf2 100644
--- a/src/include/access/reloptions.h
+++ b/src/include/access/reloptions.h
@@ -29,6 +29,7 @@
typedef enum relopt_type
{
RELOPT_TYPE_BOOL,
+ RELOPT_TYPE_TERNARY, /* on, off, unset */
RELOPT_TYPE_INT,
RELOPT_TYPE_REAL,
RELOPT_TYPE_ENUM,
@@ -80,6 +81,7 @@ typedef struct relopt_value
union
{
bool bool_val;
+ pg_ternary ternary_val;
int int_val;
double real_val;
int enum_val;
@@ -94,6 +96,12 @@ typedef struct relopt_bool
bool default_val;
} relopt_bool;
+typedef struct relopt_ternary
+{
+ relopt_gen gen;
+ int default_val;
+} relopt_ternary;
+
typedef struct relopt_int
{
relopt_gen gen;
@@ -152,19 +160,6 @@ typedef struct
const char *optname; /* option's name */
relopt_type opttype; /* option's datatype */
int offset; /* offset of field in result struct */
-
- /*
- * isset_offset is an optional offset of a field in the result struct that
- * stores whether the option is explicitly set for the relation or if it
- * just picked up the default value. In most cases, this can be
- * accomplished by giving the reloption a special out-of-range default
- * value (e.g., some integer reloptions use -2), but this isn't always
- * possible. For example, a Boolean reloption cannot be given an
- * out-of-range default, so we need another way to discover the source of
- * its value. This offset is only used if given a value greater than
- * zero.
- */
- int isset_offset;
} relopt_parse_elt;
/* Local reloption definition */
@@ -195,6 +190,9 @@ typedef struct local_relopts
extern relopt_kind add_reloption_kind(void);
extern void add_bool_reloption(bits32 kinds, const char *name, const char *desc,
bool default_val, LOCKMODE lockmode);
+extern void add_ternary_reloption(bits32 kinds, const char *name,
+ const char *desc, pg_ternary default_val,
+ LOCKMODE lockmode);
extern void add_int_reloption(bits32 kinds, const char *name, const char *desc,
int default_val, int min_val, int max_val,
LOCKMODE lockmode);
@@ -214,6 +212,9 @@ extern void register_reloptions_validator(local_relopts *relopts,
extern void add_local_bool_reloption(local_relopts *relopts, const char *name,
const char *desc, bool default_val,
int offset);
+extern void add_local_ternary_reloption(local_relopts *relopts,
+ const char *name, const char *desc,
+ pg_ternary default_val, int offset);
extern void add_local_int_reloption(local_relopts *relopts, const char *name,
const char *desc, int default_val,
int min_val, int max_val, int offset);
diff --git a/src/include/postgres.h b/src/include/postgres.h
index 7d93fbce709..47c88780776 100644
--- a/src/include/postgres.h
+++ b/src/include/postgres.h
@@ -543,6 +543,21 @@ Float8GetDatum(float8 X)
* ----------------------------------------------------------------
*/
+/*
+ * pg_ternary
+ * Boolean value with an extra "unset" value
+ *
+ * This enum can be used for values that want to distinguish between true,
+ * false, and unset.
+*/
+
+typedef enum pg_ternary
+{
+ TERNARY_FALSE = 0,
+ TERNARY_TRUE = 1,
+ TERNARY_UNSET = -1
+} pg_ternary;
+
/*
* NON_EXEC_STATIC: It's sometimes useful to define a variable or function
* that is normally static but extern when using EXEC_BACKEND (see
diff --git a/src/include/utils/rel.h b/src/include/utils/rel.h
index d03ab247788..5feb18a5373 100644
--- a/src/include/utils/rel.h
+++ b/src/include/utils/rel.h
@@ -347,8 +347,7 @@ typedef struct StdRdOptions
bool user_catalog_table; /* use as an additional catalog relation */
int parallel_workers; /* max number of parallel workers */
StdRdOptIndexCleanup vacuum_index_cleanup; /* controls index vacuuming */
- bool vacuum_truncate; /* enables vacuum to truncate a relation */
- bool vacuum_truncate_set; /* whether vacuum_truncate is set */
+ pg_ternary vacuum_truncate; /* enables vacuum to truncate a relation */
/*
* Fraction of pages in a relation that vacuum can eagerly scan and fail
diff --git a/src/test/regress/expected/reloptions.out b/src/test/regress/expected/reloptions.out
index 9de19b4e3f1..1c99f79ab01 100644
--- a/src/test/regress/expected/reloptions.out
+++ b/src/test/regress/expected/reloptions.out
@@ -98,6 +98,42 @@ SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass;
{fillfactor=13,autovacuum_enabled=false}
(1 row)
+-- Tests for future (FIXME) ternary options
+-- behave as boolean option: accept unassigned name and truncated value
+DROP TABLE reloptions_test;
+CREATE TABLE reloptions_test(i INT) WITH (vacuum_truncate);
+SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass;
+ reloptions
+------------------------
+ {vacuum_truncate=true}
+(1 row)
+
+DROP TABLE reloptions_test;
+CREATE TABLE reloptions_test(i INT) WITH (vacuum_truncate=FaLS);
+SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass;
+ reloptions
+------------------------
+ {vacuum_truncate=fals}
+(1 row)
+
+-- preferred "true" alias is used when storing
+DROP TABLE reloptions_test;
+CREATE TABLE reloptions_test(i INT) WITH (vacuum_index_cleanup=on);
+SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass;
+ reloptions
+---------------------------
+ {vacuum_index_cleanup=on}
+(1 row)
+
+-- custom "third" value is available
+DROP TABLE reloptions_test;
+CREATE TABLE reloptions_test(i INT) WITH (vacuum_index_cleanup=auto);
+SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass;
+ reloptions
+-----------------------------
+ {vacuum_index_cleanup=auto}
+(1 row)
+
-- Test vacuum_truncate option
DROP TABLE reloptions_test;
CREATE TEMP TABLE reloptions_test(i INT NOT NULL, j text)
diff --git a/src/test/regress/sql/reloptions.sql b/src/test/regress/sql/reloptions.sql
index 24fbe0b478d..f5980dafcbc 100644
--- a/src/test/regress/sql/reloptions.sql
+++ b/src/test/regress/sql/reloptions.sql
@@ -59,6 +59,27 @@ UPDATE pg_class
ALTER TABLE reloptions_test RESET (illegal_option);
SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass;
+-- Tests for future (FIXME) ternary options
+
+-- behave as boolean option: accept unassigned name and truncated value
+DROP TABLE reloptions_test;
+CREATE TABLE reloptions_test(i INT) WITH (vacuum_truncate);
+SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass;
+
+DROP TABLE reloptions_test;
+CREATE TABLE reloptions_test(i INT) WITH (vacuum_truncate=FaLS);
+SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass;
+
+-- preferred "true" alias is used when storing
+DROP TABLE reloptions_test;
+CREATE TABLE reloptions_test(i INT) WITH (vacuum_index_cleanup=on);
+SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass;
+
+-- custom "third" value is available
+DROP TABLE reloptions_test;
+CREATE TABLE reloptions_test(i INT) WITH (vacuum_index_cleanup=auto);
+SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass;
+
-- Test vacuum_truncate option
DROP TABLE reloptions_test;
diff --git a/src/tools/pgindent/typedefs.list b/src/tools/pgindent/typedefs.list
index 3f3a888fd0e..1c8610fd46c 100644
--- a/src/tools/pgindent/typedefs.list
+++ b/src/tools/pgindent/typedefs.list
@@ -3952,6 +3952,7 @@ pg_sha512_ctx
pg_snapshot
pg_special_case
pg_stack_base_t
+pg_ternary
pg_time_t
pg_time_usec_t
pg_tz
@@ -4079,6 +4080,7 @@ relopt_kind
relopt_parse_elt
relopt_real
relopt_string
+relopt_ternary
relopt_type
relopt_value
relopts_validator
--
2.47.3
--c36t3fndxhkzbl5z--
^ permalink raw reply [nested|flat] 868+ messages in thread
* [PATCH v3] Introduce ternary reloptions
@ 2026-01-16 15:04 Álvaro Herrera <[email protected]>
0 siblings, 0 replies; 868+ messages in thread
From: Álvaro Herrera @ 2026-01-16 15:04 UTC (permalink / raw)
Introduce ternary reloption as a replacement for current
`vacuum_truncate` implementation. Remove the `vacuum_truncate_set`
separate flag and use TERNARY_UNSET instead.
This could also be used for other options such as `vacuum_index_cleanup`
and `buffering`, but lets get the scaffolding in first.
Discussion: https://postgr.es/m/3474141.usfYGdeWWP@thinkpad-pgpro
---
src/backend/access/common/reloptions.c | 137 ++++++++++++++++++-----
src/backend/commands/vacuum.c | 4 +-
src/include/access/reloptions.h | 27 ++---
src/include/postgres.h | 15 +++
src/include/utils/rel.h | 3 +-
src/test/regress/expected/reloptions.out | 36 ++++++
src/test/regress/sql/reloptions.sql | 21 ++++
src/tools/pgindent/typedefs.list | 2 +
8 files changed, 202 insertions(+), 43 deletions(-)
diff --git a/src/backend/access/common/reloptions.c b/src/backend/access/common/reloptions.c
index 0b83f98ed5f..6f1f577581a 100644
--- a/src/backend/access/common/reloptions.c
+++ b/src/backend/access/common/reloptions.c
@@ -40,9 +40,9 @@
*
* To add an option:
*
- * (i) decide on a type (bool, integer, real, enum, string), name, default
- * value, upper and lower bounds (if applicable); for strings, consider a
- * validation routine.
+ * (i) decide on a type (bool, ternary, integer, real, enum, string), name,
+ * default value, upper and lower bounds (if applicable); for strings,
+ * consider a validation routine.
* (ii) add a record below (or use add_<type>_reloption).
* (iii) add it to the appropriate options struct (perhaps StdRdOptions)
* (iv) add it to the appropriate handling routine (perhaps
@@ -147,15 +147,6 @@ static relopt_bool boolRelOpts[] =
},
false
},
- {
- {
- "vacuum_truncate",
- "Enables vacuum to truncate empty pages at the end of this table",
- RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
- ShareUpdateExclusiveLock
- },
- true
- },
{
{
"deduplicate_items",
@@ -170,6 +161,25 @@ static relopt_bool boolRelOpts[] =
{{NULL}}
};
+static relopt_ternary ternaryRelOpts[] =
+{
+ {
+ {
+ "vacuum_truncate",
+ "Enables vacuum to truncate empty pages at the end of this table",
+ RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
+ ShareUpdateExclusiveLock
+ },
+ TERNARY_UNSET
+ },
+ /* list terminator */
+ {
+ {
+ NULL
+ }
+ }
+};
+
static relopt_int intRelOpts[] =
{
{
@@ -609,6 +619,13 @@ initialize_reloptions(void)
boolRelOpts[i].gen.lockmode));
j++;
}
+ for (i = 0; ternaryRelOpts[i].gen.name; i++)
+ {
+ Assert(DoLockModesConflict(ternaryRelOpts[i].gen.lockmode,
+ ternaryRelOpts[i].gen.lockmode));
+ j++;
+ }
+
for (i = 0; intRelOpts[i].gen.name; i++)
{
Assert(DoLockModesConflict(intRelOpts[i].gen.lockmode,
@@ -649,6 +666,14 @@ initialize_reloptions(void)
j++;
}
+ for (i = 0; ternaryRelOpts[i].gen.name; i++)
+ {
+ relOpts[j] = &ternaryRelOpts[i].gen;
+ relOpts[j]->type = RELOPT_TYPE_TERNARY;
+ relOpts[j]->namelen = strlen(relOpts[j]->name);
+ j++;
+ }
+
for (i = 0; intRelOpts[i].gen.name; i++)
{
relOpts[j] = &intRelOpts[i].gen;
@@ -809,6 +834,9 @@ allocate_reloption(bits32 kinds, int type, const char *name, const char *desc,
case RELOPT_TYPE_BOOL:
size = sizeof(relopt_bool);
break;
+ case RELOPT_TYPE_TERNARY:
+ size = sizeof(relopt_ternary);
+ break;
case RELOPT_TYPE_INT:
size = sizeof(relopt_int);
break;
@@ -892,6 +920,57 @@ add_local_bool_reloption(local_relopts *relopts, const char *name,
add_local_reloption(relopts, (relopt_gen *) newoption, offset);
}
+/*
+ * init_ternary_reloption
+ * Allocate and initialize a new ternary reloption
+ */
+static relopt_ternary *
+init_ternary_reloption(bits32 kinds, const char *name, const char *desc,
+ pg_ternary default_val, LOCKMODE lockmode)
+{
+ relopt_ternary *newoption;
+
+ newoption = (relopt_ternary *)
+ allocate_reloption(kinds, RELOPT_TYPE_TERNARY, name, desc, lockmode);
+ newoption->default_val = default_val;
+
+ return newoption;
+}
+
+/*
+ * add_ternary_reloption
+ * Add a new ternary reloption
+ */
+void
+add_ternary_reloption(bits32 kinds, const char *name, const char *desc,
+ pg_ternary default_val, LOCKMODE lockmode)
+{
+ relopt_ternary *newoption;
+
+ newoption =
+ init_ternary_reloption(kinds, name, desc, default_val, lockmode);
+
+ add_reloption((relopt_gen *) newoption);
+}
+
+/*
+ * add_local_ternary_reloption
+ * Add a new ternary local reloption
+ *
+ * 'offset' is offset of ternary-typed field.
+ */
+void
+add_local_ternary_reloption(local_relopts *relopts, const char *name,
+ const char *desc, pg_ternary default_val,
+ int offset)
+{
+ relopt_ternary *newoption;
+
+ newoption =
+ init_ternary_reloption(RELOPT_KIND_LOCAL, name, desc, default_val, 0);
+
+ add_local_reloption(relopts, (relopt_gen *) newoption, offset);
+}
/*
* init_real_reloption
@@ -1626,6 +1705,19 @@ parse_one_reloption(relopt_value *option, char *text_str, int text_len,
option->gen->name, value)));
}
break;
+ case RELOPT_TYPE_TERNARY:
+ {
+ bool b;
+
+ parsed = parse_bool(value, &b);
+ option->values.ternary_val = b ? TERNARY_TRUE : TERNARY_FALSE;
+ if (validate && !parsed)
+ ereport(ERROR,
+ (errcode(ERRCODE_INVALID_PARAMETER_VALUE),
+ errmsg("invalid value for ternary option \"%s\": %s",
+ option->gen->name, value)));
+ }
+ break;
case RELOPT_TYPE_INT:
{
relopt_int *optint = (relopt_int *) option->gen;
@@ -1789,17 +1881,6 @@ fillRelOptions(void *rdopts, Size basesize,
char *itempos = ((char *) rdopts) + elems[j].offset;
char *string_val;
- /*
- * If isset_offset is provided, store whether the reloption is
- * set there.
- */
- if (elems[j].isset_offset > 0)
- {
- char *setpos = ((char *) rdopts) + elems[j].isset_offset;
-
- *(bool *) setpos = options[i].isset;
- }
-
switch (options[i].gen->type)
{
case RELOPT_TYPE_BOOL:
@@ -1807,6 +1888,11 @@ fillRelOptions(void *rdopts, Size basesize,
options[i].values.bool_val :
((relopt_bool *) options[i].gen)->default_val;
break;
+ case RELOPT_TYPE_TERNARY:
+ *(pg_ternary *) itempos = options[i].isset ?
+ options[i].values.ternary_val :
+ ((relopt_ternary *) options[i].gen)->default_val;
+ break;
case RELOPT_TYPE_INT:
*(int *) itempos = options[i].isset ?
options[i].values.int_val :
@@ -1923,8 +2009,8 @@ default_reloptions(Datum reloptions, bool validate, relopt_kind kind)
offsetof(StdRdOptions, parallel_workers)},
{"vacuum_index_cleanup", RELOPT_TYPE_ENUM,
offsetof(StdRdOptions, vacuum_index_cleanup)},
- {"vacuum_truncate", RELOPT_TYPE_BOOL,
- offsetof(StdRdOptions, vacuum_truncate), offsetof(StdRdOptions, vacuum_truncate_set)},
+ {"vacuum_truncate", RELOPT_TYPE_TERNARY,
+ offsetof(StdRdOptions, vacuum_truncate)},
{"vacuum_max_eager_freeze_failure_rate", RELOPT_TYPE_REAL,
offsetof(StdRdOptions, vacuum_max_eager_freeze_failure_rate)}
};
@@ -2004,7 +2090,6 @@ build_local_reloptions(local_relopts *relopts, Datum options, bool validate)
elems[i].optname = opt->option->name;
elems[i].opttype = opt->option->type;
elems[i].offset = opt->offset;
- elems[i].isset_offset = 0; /* not supported for local relopts yet */
i++;
}
diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c
index aa4fbec143f..696eab9bd97 100644
--- a/src/backend/commands/vacuum.c
+++ b/src/backend/commands/vacuum.c
@@ -2224,9 +2224,9 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams params,
{
StdRdOptions *opts = (StdRdOptions *) rel->rd_options;
- if (opts && opts->vacuum_truncate_set)
+ if (opts && opts->vacuum_truncate != TERNARY_UNSET)
{
- if (opts->vacuum_truncate)
+ if (opts->vacuum_truncate == TERNARY_TRUE)
params.truncate = VACOPTVALUE_ENABLED;
else
params.truncate = VACOPTVALUE_DISABLED;
diff --git a/src/include/access/reloptions.h b/src/include/access/reloptions.h
index 2f08e1b0cf0..dfbef2babf2 100644
--- a/src/include/access/reloptions.h
+++ b/src/include/access/reloptions.h
@@ -29,6 +29,7 @@
typedef enum relopt_type
{
RELOPT_TYPE_BOOL,
+ RELOPT_TYPE_TERNARY, /* on, off, unset */
RELOPT_TYPE_INT,
RELOPT_TYPE_REAL,
RELOPT_TYPE_ENUM,
@@ -80,6 +81,7 @@ typedef struct relopt_value
union
{
bool bool_val;
+ pg_ternary ternary_val;
int int_val;
double real_val;
int enum_val;
@@ -94,6 +96,12 @@ typedef struct relopt_bool
bool default_val;
} relopt_bool;
+typedef struct relopt_ternary
+{
+ relopt_gen gen;
+ int default_val;
+} relopt_ternary;
+
typedef struct relopt_int
{
relopt_gen gen;
@@ -152,19 +160,6 @@ typedef struct
const char *optname; /* option's name */
relopt_type opttype; /* option's datatype */
int offset; /* offset of field in result struct */
-
- /*
- * isset_offset is an optional offset of a field in the result struct that
- * stores whether the option is explicitly set for the relation or if it
- * just picked up the default value. In most cases, this can be
- * accomplished by giving the reloption a special out-of-range default
- * value (e.g., some integer reloptions use -2), but this isn't always
- * possible. For example, a Boolean reloption cannot be given an
- * out-of-range default, so we need another way to discover the source of
- * its value. This offset is only used if given a value greater than
- * zero.
- */
- int isset_offset;
} relopt_parse_elt;
/* Local reloption definition */
@@ -195,6 +190,9 @@ typedef struct local_relopts
extern relopt_kind add_reloption_kind(void);
extern void add_bool_reloption(bits32 kinds, const char *name, const char *desc,
bool default_val, LOCKMODE lockmode);
+extern void add_ternary_reloption(bits32 kinds, const char *name,
+ const char *desc, pg_ternary default_val,
+ LOCKMODE lockmode);
extern void add_int_reloption(bits32 kinds, const char *name, const char *desc,
int default_val, int min_val, int max_val,
LOCKMODE lockmode);
@@ -214,6 +212,9 @@ extern void register_reloptions_validator(local_relopts *relopts,
extern void add_local_bool_reloption(local_relopts *relopts, const char *name,
const char *desc, bool default_val,
int offset);
+extern void add_local_ternary_reloption(local_relopts *relopts,
+ const char *name, const char *desc,
+ pg_ternary default_val, int offset);
extern void add_local_int_reloption(local_relopts *relopts, const char *name,
const char *desc, int default_val,
int min_val, int max_val, int offset);
diff --git a/src/include/postgres.h b/src/include/postgres.h
index 7d93fbce709..47c88780776 100644
--- a/src/include/postgres.h
+++ b/src/include/postgres.h
@@ -543,6 +543,21 @@ Float8GetDatum(float8 X)
* ----------------------------------------------------------------
*/
+/*
+ * pg_ternary
+ * Boolean value with an extra "unset" value
+ *
+ * This enum can be used for values that want to distinguish between true,
+ * false, and unset.
+*/
+
+typedef enum pg_ternary
+{
+ TERNARY_FALSE = 0,
+ TERNARY_TRUE = 1,
+ TERNARY_UNSET = -1
+} pg_ternary;
+
/*
* NON_EXEC_STATIC: It's sometimes useful to define a variable or function
* that is normally static but extern when using EXEC_BACKEND (see
diff --git a/src/include/utils/rel.h b/src/include/utils/rel.h
index d03ab247788..5feb18a5373 100644
--- a/src/include/utils/rel.h
+++ b/src/include/utils/rel.h
@@ -347,8 +347,7 @@ typedef struct StdRdOptions
bool user_catalog_table; /* use as an additional catalog relation */
int parallel_workers; /* max number of parallel workers */
StdRdOptIndexCleanup vacuum_index_cleanup; /* controls index vacuuming */
- bool vacuum_truncate; /* enables vacuum to truncate a relation */
- bool vacuum_truncate_set; /* whether vacuum_truncate is set */
+ pg_ternary vacuum_truncate; /* enables vacuum to truncate a relation */
/*
* Fraction of pages in a relation that vacuum can eagerly scan and fail
diff --git a/src/test/regress/expected/reloptions.out b/src/test/regress/expected/reloptions.out
index 9de19b4e3f1..1c99f79ab01 100644
--- a/src/test/regress/expected/reloptions.out
+++ b/src/test/regress/expected/reloptions.out
@@ -98,6 +98,42 @@ SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass;
{fillfactor=13,autovacuum_enabled=false}
(1 row)
+-- Tests for future (FIXME) ternary options
+-- behave as boolean option: accept unassigned name and truncated value
+DROP TABLE reloptions_test;
+CREATE TABLE reloptions_test(i INT) WITH (vacuum_truncate);
+SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass;
+ reloptions
+------------------------
+ {vacuum_truncate=true}
+(1 row)
+
+DROP TABLE reloptions_test;
+CREATE TABLE reloptions_test(i INT) WITH (vacuum_truncate=FaLS);
+SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass;
+ reloptions
+------------------------
+ {vacuum_truncate=fals}
+(1 row)
+
+-- preferred "true" alias is used when storing
+DROP TABLE reloptions_test;
+CREATE TABLE reloptions_test(i INT) WITH (vacuum_index_cleanup=on);
+SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass;
+ reloptions
+---------------------------
+ {vacuum_index_cleanup=on}
+(1 row)
+
+-- custom "third" value is available
+DROP TABLE reloptions_test;
+CREATE TABLE reloptions_test(i INT) WITH (vacuum_index_cleanup=auto);
+SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass;
+ reloptions
+-----------------------------
+ {vacuum_index_cleanup=auto}
+(1 row)
+
-- Test vacuum_truncate option
DROP TABLE reloptions_test;
CREATE TEMP TABLE reloptions_test(i INT NOT NULL, j text)
diff --git a/src/test/regress/sql/reloptions.sql b/src/test/regress/sql/reloptions.sql
index 24fbe0b478d..f5980dafcbc 100644
--- a/src/test/regress/sql/reloptions.sql
+++ b/src/test/regress/sql/reloptions.sql
@@ -59,6 +59,27 @@ UPDATE pg_class
ALTER TABLE reloptions_test RESET (illegal_option);
SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass;
+-- Tests for future (FIXME) ternary options
+
+-- behave as boolean option: accept unassigned name and truncated value
+DROP TABLE reloptions_test;
+CREATE TABLE reloptions_test(i INT) WITH (vacuum_truncate);
+SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass;
+
+DROP TABLE reloptions_test;
+CREATE TABLE reloptions_test(i INT) WITH (vacuum_truncate=FaLS);
+SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass;
+
+-- preferred "true" alias is used when storing
+DROP TABLE reloptions_test;
+CREATE TABLE reloptions_test(i INT) WITH (vacuum_index_cleanup=on);
+SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass;
+
+-- custom "third" value is available
+DROP TABLE reloptions_test;
+CREATE TABLE reloptions_test(i INT) WITH (vacuum_index_cleanup=auto);
+SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass;
+
-- Test vacuum_truncate option
DROP TABLE reloptions_test;
diff --git a/src/tools/pgindent/typedefs.list b/src/tools/pgindent/typedefs.list
index 3f3a888fd0e..1c8610fd46c 100644
--- a/src/tools/pgindent/typedefs.list
+++ b/src/tools/pgindent/typedefs.list
@@ -3952,6 +3952,7 @@ pg_sha512_ctx
pg_snapshot
pg_special_case
pg_stack_base_t
+pg_ternary
pg_time_t
pg_time_usec_t
pg_tz
@@ -4079,6 +4080,7 @@ relopt_kind
relopt_parse_elt
relopt_real
relopt_string
+relopt_ternary
relopt_type
relopt_value
relopts_validator
--
2.47.3
--c36t3fndxhkzbl5z--
^ permalink raw reply [nested|flat] 868+ messages in thread
* [PATCH v3] Introduce ternary reloptions
@ 2026-01-16 15:04 Álvaro Herrera <[email protected]>
0 siblings, 0 replies; 868+ messages in thread
From: Álvaro Herrera @ 2026-01-16 15:04 UTC (permalink / raw)
Introduce ternary reloption as a replacement for current
`vacuum_truncate` implementation. Remove the `vacuum_truncate_set`
separate flag and use TERNARY_UNSET instead.
This could also be used for other options such as `vacuum_index_cleanup`
and `buffering`, but lets get the scaffolding in first.
Discussion: https://postgr.es/m/3474141.usfYGdeWWP@thinkpad-pgpro
---
src/backend/access/common/reloptions.c | 137 ++++++++++++++++++-----
src/backend/commands/vacuum.c | 4 +-
src/include/access/reloptions.h | 27 ++---
src/include/postgres.h | 15 +++
src/include/utils/rel.h | 3 +-
src/test/regress/expected/reloptions.out | 36 ++++++
src/test/regress/sql/reloptions.sql | 21 ++++
src/tools/pgindent/typedefs.list | 2 +
8 files changed, 202 insertions(+), 43 deletions(-)
diff --git a/src/backend/access/common/reloptions.c b/src/backend/access/common/reloptions.c
index 0b83f98ed5f..6f1f577581a 100644
--- a/src/backend/access/common/reloptions.c
+++ b/src/backend/access/common/reloptions.c
@@ -40,9 +40,9 @@
*
* To add an option:
*
- * (i) decide on a type (bool, integer, real, enum, string), name, default
- * value, upper and lower bounds (if applicable); for strings, consider a
- * validation routine.
+ * (i) decide on a type (bool, ternary, integer, real, enum, string), name,
+ * default value, upper and lower bounds (if applicable); for strings,
+ * consider a validation routine.
* (ii) add a record below (or use add_<type>_reloption).
* (iii) add it to the appropriate options struct (perhaps StdRdOptions)
* (iv) add it to the appropriate handling routine (perhaps
@@ -147,15 +147,6 @@ static relopt_bool boolRelOpts[] =
},
false
},
- {
- {
- "vacuum_truncate",
- "Enables vacuum to truncate empty pages at the end of this table",
- RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
- ShareUpdateExclusiveLock
- },
- true
- },
{
{
"deduplicate_items",
@@ -170,6 +161,25 @@ static relopt_bool boolRelOpts[] =
{{NULL}}
};
+static relopt_ternary ternaryRelOpts[] =
+{
+ {
+ {
+ "vacuum_truncate",
+ "Enables vacuum to truncate empty pages at the end of this table",
+ RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
+ ShareUpdateExclusiveLock
+ },
+ TERNARY_UNSET
+ },
+ /* list terminator */
+ {
+ {
+ NULL
+ }
+ }
+};
+
static relopt_int intRelOpts[] =
{
{
@@ -609,6 +619,13 @@ initialize_reloptions(void)
boolRelOpts[i].gen.lockmode));
j++;
}
+ for (i = 0; ternaryRelOpts[i].gen.name; i++)
+ {
+ Assert(DoLockModesConflict(ternaryRelOpts[i].gen.lockmode,
+ ternaryRelOpts[i].gen.lockmode));
+ j++;
+ }
+
for (i = 0; intRelOpts[i].gen.name; i++)
{
Assert(DoLockModesConflict(intRelOpts[i].gen.lockmode,
@@ -649,6 +666,14 @@ initialize_reloptions(void)
j++;
}
+ for (i = 0; ternaryRelOpts[i].gen.name; i++)
+ {
+ relOpts[j] = &ternaryRelOpts[i].gen;
+ relOpts[j]->type = RELOPT_TYPE_TERNARY;
+ relOpts[j]->namelen = strlen(relOpts[j]->name);
+ j++;
+ }
+
for (i = 0; intRelOpts[i].gen.name; i++)
{
relOpts[j] = &intRelOpts[i].gen;
@@ -809,6 +834,9 @@ allocate_reloption(bits32 kinds, int type, const char *name, const char *desc,
case RELOPT_TYPE_BOOL:
size = sizeof(relopt_bool);
break;
+ case RELOPT_TYPE_TERNARY:
+ size = sizeof(relopt_ternary);
+ break;
case RELOPT_TYPE_INT:
size = sizeof(relopt_int);
break;
@@ -892,6 +920,57 @@ add_local_bool_reloption(local_relopts *relopts, const char *name,
add_local_reloption(relopts, (relopt_gen *) newoption, offset);
}
+/*
+ * init_ternary_reloption
+ * Allocate and initialize a new ternary reloption
+ */
+static relopt_ternary *
+init_ternary_reloption(bits32 kinds, const char *name, const char *desc,
+ pg_ternary default_val, LOCKMODE lockmode)
+{
+ relopt_ternary *newoption;
+
+ newoption = (relopt_ternary *)
+ allocate_reloption(kinds, RELOPT_TYPE_TERNARY, name, desc, lockmode);
+ newoption->default_val = default_val;
+
+ return newoption;
+}
+
+/*
+ * add_ternary_reloption
+ * Add a new ternary reloption
+ */
+void
+add_ternary_reloption(bits32 kinds, const char *name, const char *desc,
+ pg_ternary default_val, LOCKMODE lockmode)
+{
+ relopt_ternary *newoption;
+
+ newoption =
+ init_ternary_reloption(kinds, name, desc, default_val, lockmode);
+
+ add_reloption((relopt_gen *) newoption);
+}
+
+/*
+ * add_local_ternary_reloption
+ * Add a new ternary local reloption
+ *
+ * 'offset' is offset of ternary-typed field.
+ */
+void
+add_local_ternary_reloption(local_relopts *relopts, const char *name,
+ const char *desc, pg_ternary default_val,
+ int offset)
+{
+ relopt_ternary *newoption;
+
+ newoption =
+ init_ternary_reloption(RELOPT_KIND_LOCAL, name, desc, default_val, 0);
+
+ add_local_reloption(relopts, (relopt_gen *) newoption, offset);
+}
/*
* init_real_reloption
@@ -1626,6 +1705,19 @@ parse_one_reloption(relopt_value *option, char *text_str, int text_len,
option->gen->name, value)));
}
break;
+ case RELOPT_TYPE_TERNARY:
+ {
+ bool b;
+
+ parsed = parse_bool(value, &b);
+ option->values.ternary_val = b ? TERNARY_TRUE : TERNARY_FALSE;
+ if (validate && !parsed)
+ ereport(ERROR,
+ (errcode(ERRCODE_INVALID_PARAMETER_VALUE),
+ errmsg("invalid value for ternary option \"%s\": %s",
+ option->gen->name, value)));
+ }
+ break;
case RELOPT_TYPE_INT:
{
relopt_int *optint = (relopt_int *) option->gen;
@@ -1789,17 +1881,6 @@ fillRelOptions(void *rdopts, Size basesize,
char *itempos = ((char *) rdopts) + elems[j].offset;
char *string_val;
- /*
- * If isset_offset is provided, store whether the reloption is
- * set there.
- */
- if (elems[j].isset_offset > 0)
- {
- char *setpos = ((char *) rdopts) + elems[j].isset_offset;
-
- *(bool *) setpos = options[i].isset;
- }
-
switch (options[i].gen->type)
{
case RELOPT_TYPE_BOOL:
@@ -1807,6 +1888,11 @@ fillRelOptions(void *rdopts, Size basesize,
options[i].values.bool_val :
((relopt_bool *) options[i].gen)->default_val;
break;
+ case RELOPT_TYPE_TERNARY:
+ *(pg_ternary *) itempos = options[i].isset ?
+ options[i].values.ternary_val :
+ ((relopt_ternary *) options[i].gen)->default_val;
+ break;
case RELOPT_TYPE_INT:
*(int *) itempos = options[i].isset ?
options[i].values.int_val :
@@ -1923,8 +2009,8 @@ default_reloptions(Datum reloptions, bool validate, relopt_kind kind)
offsetof(StdRdOptions, parallel_workers)},
{"vacuum_index_cleanup", RELOPT_TYPE_ENUM,
offsetof(StdRdOptions, vacuum_index_cleanup)},
- {"vacuum_truncate", RELOPT_TYPE_BOOL,
- offsetof(StdRdOptions, vacuum_truncate), offsetof(StdRdOptions, vacuum_truncate_set)},
+ {"vacuum_truncate", RELOPT_TYPE_TERNARY,
+ offsetof(StdRdOptions, vacuum_truncate)},
{"vacuum_max_eager_freeze_failure_rate", RELOPT_TYPE_REAL,
offsetof(StdRdOptions, vacuum_max_eager_freeze_failure_rate)}
};
@@ -2004,7 +2090,6 @@ build_local_reloptions(local_relopts *relopts, Datum options, bool validate)
elems[i].optname = opt->option->name;
elems[i].opttype = opt->option->type;
elems[i].offset = opt->offset;
- elems[i].isset_offset = 0; /* not supported for local relopts yet */
i++;
}
diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c
index aa4fbec143f..696eab9bd97 100644
--- a/src/backend/commands/vacuum.c
+++ b/src/backend/commands/vacuum.c
@@ -2224,9 +2224,9 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams params,
{
StdRdOptions *opts = (StdRdOptions *) rel->rd_options;
- if (opts && opts->vacuum_truncate_set)
+ if (opts && opts->vacuum_truncate != TERNARY_UNSET)
{
- if (opts->vacuum_truncate)
+ if (opts->vacuum_truncate == TERNARY_TRUE)
params.truncate = VACOPTVALUE_ENABLED;
else
params.truncate = VACOPTVALUE_DISABLED;
diff --git a/src/include/access/reloptions.h b/src/include/access/reloptions.h
index 2f08e1b0cf0..dfbef2babf2 100644
--- a/src/include/access/reloptions.h
+++ b/src/include/access/reloptions.h
@@ -29,6 +29,7 @@
typedef enum relopt_type
{
RELOPT_TYPE_BOOL,
+ RELOPT_TYPE_TERNARY, /* on, off, unset */
RELOPT_TYPE_INT,
RELOPT_TYPE_REAL,
RELOPT_TYPE_ENUM,
@@ -80,6 +81,7 @@ typedef struct relopt_value
union
{
bool bool_val;
+ pg_ternary ternary_val;
int int_val;
double real_val;
int enum_val;
@@ -94,6 +96,12 @@ typedef struct relopt_bool
bool default_val;
} relopt_bool;
+typedef struct relopt_ternary
+{
+ relopt_gen gen;
+ int default_val;
+} relopt_ternary;
+
typedef struct relopt_int
{
relopt_gen gen;
@@ -152,19 +160,6 @@ typedef struct
const char *optname; /* option's name */
relopt_type opttype; /* option's datatype */
int offset; /* offset of field in result struct */
-
- /*
- * isset_offset is an optional offset of a field in the result struct that
- * stores whether the option is explicitly set for the relation or if it
- * just picked up the default value. In most cases, this can be
- * accomplished by giving the reloption a special out-of-range default
- * value (e.g., some integer reloptions use -2), but this isn't always
- * possible. For example, a Boolean reloption cannot be given an
- * out-of-range default, so we need another way to discover the source of
- * its value. This offset is only used if given a value greater than
- * zero.
- */
- int isset_offset;
} relopt_parse_elt;
/* Local reloption definition */
@@ -195,6 +190,9 @@ typedef struct local_relopts
extern relopt_kind add_reloption_kind(void);
extern void add_bool_reloption(bits32 kinds, const char *name, const char *desc,
bool default_val, LOCKMODE lockmode);
+extern void add_ternary_reloption(bits32 kinds, const char *name,
+ const char *desc, pg_ternary default_val,
+ LOCKMODE lockmode);
extern void add_int_reloption(bits32 kinds, const char *name, const char *desc,
int default_val, int min_val, int max_val,
LOCKMODE lockmode);
@@ -214,6 +212,9 @@ extern void register_reloptions_validator(local_relopts *relopts,
extern void add_local_bool_reloption(local_relopts *relopts, const char *name,
const char *desc, bool default_val,
int offset);
+extern void add_local_ternary_reloption(local_relopts *relopts,
+ const char *name, const char *desc,
+ pg_ternary default_val, int offset);
extern void add_local_int_reloption(local_relopts *relopts, const char *name,
const char *desc, int default_val,
int min_val, int max_val, int offset);
diff --git a/src/include/postgres.h b/src/include/postgres.h
index 7d93fbce709..47c88780776 100644
--- a/src/include/postgres.h
+++ b/src/include/postgres.h
@@ -543,6 +543,21 @@ Float8GetDatum(float8 X)
* ----------------------------------------------------------------
*/
+/*
+ * pg_ternary
+ * Boolean value with an extra "unset" value
+ *
+ * This enum can be used for values that want to distinguish between true,
+ * false, and unset.
+*/
+
+typedef enum pg_ternary
+{
+ TERNARY_FALSE = 0,
+ TERNARY_TRUE = 1,
+ TERNARY_UNSET = -1
+} pg_ternary;
+
/*
* NON_EXEC_STATIC: It's sometimes useful to define a variable or function
* that is normally static but extern when using EXEC_BACKEND (see
diff --git a/src/include/utils/rel.h b/src/include/utils/rel.h
index d03ab247788..5feb18a5373 100644
--- a/src/include/utils/rel.h
+++ b/src/include/utils/rel.h
@@ -347,8 +347,7 @@ typedef struct StdRdOptions
bool user_catalog_table; /* use as an additional catalog relation */
int parallel_workers; /* max number of parallel workers */
StdRdOptIndexCleanup vacuum_index_cleanup; /* controls index vacuuming */
- bool vacuum_truncate; /* enables vacuum to truncate a relation */
- bool vacuum_truncate_set; /* whether vacuum_truncate is set */
+ pg_ternary vacuum_truncate; /* enables vacuum to truncate a relation */
/*
* Fraction of pages in a relation that vacuum can eagerly scan and fail
diff --git a/src/test/regress/expected/reloptions.out b/src/test/regress/expected/reloptions.out
index 9de19b4e3f1..1c99f79ab01 100644
--- a/src/test/regress/expected/reloptions.out
+++ b/src/test/regress/expected/reloptions.out
@@ -98,6 +98,42 @@ SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass;
{fillfactor=13,autovacuum_enabled=false}
(1 row)
+-- Tests for future (FIXME) ternary options
+-- behave as boolean option: accept unassigned name and truncated value
+DROP TABLE reloptions_test;
+CREATE TABLE reloptions_test(i INT) WITH (vacuum_truncate);
+SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass;
+ reloptions
+------------------------
+ {vacuum_truncate=true}
+(1 row)
+
+DROP TABLE reloptions_test;
+CREATE TABLE reloptions_test(i INT) WITH (vacuum_truncate=FaLS);
+SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass;
+ reloptions
+------------------------
+ {vacuum_truncate=fals}
+(1 row)
+
+-- preferred "true" alias is used when storing
+DROP TABLE reloptions_test;
+CREATE TABLE reloptions_test(i INT) WITH (vacuum_index_cleanup=on);
+SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass;
+ reloptions
+---------------------------
+ {vacuum_index_cleanup=on}
+(1 row)
+
+-- custom "third" value is available
+DROP TABLE reloptions_test;
+CREATE TABLE reloptions_test(i INT) WITH (vacuum_index_cleanup=auto);
+SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass;
+ reloptions
+-----------------------------
+ {vacuum_index_cleanup=auto}
+(1 row)
+
-- Test vacuum_truncate option
DROP TABLE reloptions_test;
CREATE TEMP TABLE reloptions_test(i INT NOT NULL, j text)
diff --git a/src/test/regress/sql/reloptions.sql b/src/test/regress/sql/reloptions.sql
index 24fbe0b478d..f5980dafcbc 100644
--- a/src/test/regress/sql/reloptions.sql
+++ b/src/test/regress/sql/reloptions.sql
@@ -59,6 +59,27 @@ UPDATE pg_class
ALTER TABLE reloptions_test RESET (illegal_option);
SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass;
+-- Tests for future (FIXME) ternary options
+
+-- behave as boolean option: accept unassigned name and truncated value
+DROP TABLE reloptions_test;
+CREATE TABLE reloptions_test(i INT) WITH (vacuum_truncate);
+SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass;
+
+DROP TABLE reloptions_test;
+CREATE TABLE reloptions_test(i INT) WITH (vacuum_truncate=FaLS);
+SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass;
+
+-- preferred "true" alias is used when storing
+DROP TABLE reloptions_test;
+CREATE TABLE reloptions_test(i INT) WITH (vacuum_index_cleanup=on);
+SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass;
+
+-- custom "third" value is available
+DROP TABLE reloptions_test;
+CREATE TABLE reloptions_test(i INT) WITH (vacuum_index_cleanup=auto);
+SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass;
+
-- Test vacuum_truncate option
DROP TABLE reloptions_test;
diff --git a/src/tools/pgindent/typedefs.list b/src/tools/pgindent/typedefs.list
index 3f3a888fd0e..1c8610fd46c 100644
--- a/src/tools/pgindent/typedefs.list
+++ b/src/tools/pgindent/typedefs.list
@@ -3952,6 +3952,7 @@ pg_sha512_ctx
pg_snapshot
pg_special_case
pg_stack_base_t
+pg_ternary
pg_time_t
pg_time_usec_t
pg_tz
@@ -4079,6 +4080,7 @@ relopt_kind
relopt_parse_elt
relopt_real
relopt_string
+relopt_ternary
relopt_type
relopt_value
relopts_validator
--
2.47.3
--c36t3fndxhkzbl5z--
^ permalink raw reply [nested|flat] 868+ messages in thread
* [PATCH v3] Introduce ternary reloptions
@ 2026-01-16 15:04 Álvaro Herrera <[email protected]>
0 siblings, 0 replies; 868+ messages in thread
From: Álvaro Herrera @ 2026-01-16 15:04 UTC (permalink / raw)
Introduce ternary reloption as a replacement for current
`vacuum_truncate` implementation. Remove the `vacuum_truncate_set`
separate flag and use TERNARY_UNSET instead.
This could also be used for other options such as `vacuum_index_cleanup`
and `buffering`, but lets get the scaffolding in first.
Discussion: https://postgr.es/m/3474141.usfYGdeWWP@thinkpad-pgpro
---
src/backend/access/common/reloptions.c | 137 ++++++++++++++++++-----
src/backend/commands/vacuum.c | 4 +-
src/include/access/reloptions.h | 27 ++---
src/include/postgres.h | 15 +++
src/include/utils/rel.h | 3 +-
src/test/regress/expected/reloptions.out | 36 ++++++
src/test/regress/sql/reloptions.sql | 21 ++++
src/tools/pgindent/typedefs.list | 2 +
8 files changed, 202 insertions(+), 43 deletions(-)
diff --git a/src/backend/access/common/reloptions.c b/src/backend/access/common/reloptions.c
index 0b83f98ed5f..6f1f577581a 100644
--- a/src/backend/access/common/reloptions.c
+++ b/src/backend/access/common/reloptions.c
@@ -40,9 +40,9 @@
*
* To add an option:
*
- * (i) decide on a type (bool, integer, real, enum, string), name, default
- * value, upper and lower bounds (if applicable); for strings, consider a
- * validation routine.
+ * (i) decide on a type (bool, ternary, integer, real, enum, string), name,
+ * default value, upper and lower bounds (if applicable); for strings,
+ * consider a validation routine.
* (ii) add a record below (or use add_<type>_reloption).
* (iii) add it to the appropriate options struct (perhaps StdRdOptions)
* (iv) add it to the appropriate handling routine (perhaps
@@ -147,15 +147,6 @@ static relopt_bool boolRelOpts[] =
},
false
},
- {
- {
- "vacuum_truncate",
- "Enables vacuum to truncate empty pages at the end of this table",
- RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
- ShareUpdateExclusiveLock
- },
- true
- },
{
{
"deduplicate_items",
@@ -170,6 +161,25 @@ static relopt_bool boolRelOpts[] =
{{NULL}}
};
+static relopt_ternary ternaryRelOpts[] =
+{
+ {
+ {
+ "vacuum_truncate",
+ "Enables vacuum to truncate empty pages at the end of this table",
+ RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
+ ShareUpdateExclusiveLock
+ },
+ TERNARY_UNSET
+ },
+ /* list terminator */
+ {
+ {
+ NULL
+ }
+ }
+};
+
static relopt_int intRelOpts[] =
{
{
@@ -609,6 +619,13 @@ initialize_reloptions(void)
boolRelOpts[i].gen.lockmode));
j++;
}
+ for (i = 0; ternaryRelOpts[i].gen.name; i++)
+ {
+ Assert(DoLockModesConflict(ternaryRelOpts[i].gen.lockmode,
+ ternaryRelOpts[i].gen.lockmode));
+ j++;
+ }
+
for (i = 0; intRelOpts[i].gen.name; i++)
{
Assert(DoLockModesConflict(intRelOpts[i].gen.lockmode,
@@ -649,6 +666,14 @@ initialize_reloptions(void)
j++;
}
+ for (i = 0; ternaryRelOpts[i].gen.name; i++)
+ {
+ relOpts[j] = &ternaryRelOpts[i].gen;
+ relOpts[j]->type = RELOPT_TYPE_TERNARY;
+ relOpts[j]->namelen = strlen(relOpts[j]->name);
+ j++;
+ }
+
for (i = 0; intRelOpts[i].gen.name; i++)
{
relOpts[j] = &intRelOpts[i].gen;
@@ -809,6 +834,9 @@ allocate_reloption(bits32 kinds, int type, const char *name, const char *desc,
case RELOPT_TYPE_BOOL:
size = sizeof(relopt_bool);
break;
+ case RELOPT_TYPE_TERNARY:
+ size = sizeof(relopt_ternary);
+ break;
case RELOPT_TYPE_INT:
size = sizeof(relopt_int);
break;
@@ -892,6 +920,57 @@ add_local_bool_reloption(local_relopts *relopts, const char *name,
add_local_reloption(relopts, (relopt_gen *) newoption, offset);
}
+/*
+ * init_ternary_reloption
+ * Allocate and initialize a new ternary reloption
+ */
+static relopt_ternary *
+init_ternary_reloption(bits32 kinds, const char *name, const char *desc,
+ pg_ternary default_val, LOCKMODE lockmode)
+{
+ relopt_ternary *newoption;
+
+ newoption = (relopt_ternary *)
+ allocate_reloption(kinds, RELOPT_TYPE_TERNARY, name, desc, lockmode);
+ newoption->default_val = default_val;
+
+ return newoption;
+}
+
+/*
+ * add_ternary_reloption
+ * Add a new ternary reloption
+ */
+void
+add_ternary_reloption(bits32 kinds, const char *name, const char *desc,
+ pg_ternary default_val, LOCKMODE lockmode)
+{
+ relopt_ternary *newoption;
+
+ newoption =
+ init_ternary_reloption(kinds, name, desc, default_val, lockmode);
+
+ add_reloption((relopt_gen *) newoption);
+}
+
+/*
+ * add_local_ternary_reloption
+ * Add a new ternary local reloption
+ *
+ * 'offset' is offset of ternary-typed field.
+ */
+void
+add_local_ternary_reloption(local_relopts *relopts, const char *name,
+ const char *desc, pg_ternary default_val,
+ int offset)
+{
+ relopt_ternary *newoption;
+
+ newoption =
+ init_ternary_reloption(RELOPT_KIND_LOCAL, name, desc, default_val, 0);
+
+ add_local_reloption(relopts, (relopt_gen *) newoption, offset);
+}
/*
* init_real_reloption
@@ -1626,6 +1705,19 @@ parse_one_reloption(relopt_value *option, char *text_str, int text_len,
option->gen->name, value)));
}
break;
+ case RELOPT_TYPE_TERNARY:
+ {
+ bool b;
+
+ parsed = parse_bool(value, &b);
+ option->values.ternary_val = b ? TERNARY_TRUE : TERNARY_FALSE;
+ if (validate && !parsed)
+ ereport(ERROR,
+ (errcode(ERRCODE_INVALID_PARAMETER_VALUE),
+ errmsg("invalid value for ternary option \"%s\": %s",
+ option->gen->name, value)));
+ }
+ break;
case RELOPT_TYPE_INT:
{
relopt_int *optint = (relopt_int *) option->gen;
@@ -1789,17 +1881,6 @@ fillRelOptions(void *rdopts, Size basesize,
char *itempos = ((char *) rdopts) + elems[j].offset;
char *string_val;
- /*
- * If isset_offset is provided, store whether the reloption is
- * set there.
- */
- if (elems[j].isset_offset > 0)
- {
- char *setpos = ((char *) rdopts) + elems[j].isset_offset;
-
- *(bool *) setpos = options[i].isset;
- }
-
switch (options[i].gen->type)
{
case RELOPT_TYPE_BOOL:
@@ -1807,6 +1888,11 @@ fillRelOptions(void *rdopts, Size basesize,
options[i].values.bool_val :
((relopt_bool *) options[i].gen)->default_val;
break;
+ case RELOPT_TYPE_TERNARY:
+ *(pg_ternary *) itempos = options[i].isset ?
+ options[i].values.ternary_val :
+ ((relopt_ternary *) options[i].gen)->default_val;
+ break;
case RELOPT_TYPE_INT:
*(int *) itempos = options[i].isset ?
options[i].values.int_val :
@@ -1923,8 +2009,8 @@ default_reloptions(Datum reloptions, bool validate, relopt_kind kind)
offsetof(StdRdOptions, parallel_workers)},
{"vacuum_index_cleanup", RELOPT_TYPE_ENUM,
offsetof(StdRdOptions, vacuum_index_cleanup)},
- {"vacuum_truncate", RELOPT_TYPE_BOOL,
- offsetof(StdRdOptions, vacuum_truncate), offsetof(StdRdOptions, vacuum_truncate_set)},
+ {"vacuum_truncate", RELOPT_TYPE_TERNARY,
+ offsetof(StdRdOptions, vacuum_truncate)},
{"vacuum_max_eager_freeze_failure_rate", RELOPT_TYPE_REAL,
offsetof(StdRdOptions, vacuum_max_eager_freeze_failure_rate)}
};
@@ -2004,7 +2090,6 @@ build_local_reloptions(local_relopts *relopts, Datum options, bool validate)
elems[i].optname = opt->option->name;
elems[i].opttype = opt->option->type;
elems[i].offset = opt->offset;
- elems[i].isset_offset = 0; /* not supported for local relopts yet */
i++;
}
diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c
index aa4fbec143f..696eab9bd97 100644
--- a/src/backend/commands/vacuum.c
+++ b/src/backend/commands/vacuum.c
@@ -2224,9 +2224,9 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams params,
{
StdRdOptions *opts = (StdRdOptions *) rel->rd_options;
- if (opts && opts->vacuum_truncate_set)
+ if (opts && opts->vacuum_truncate != TERNARY_UNSET)
{
- if (opts->vacuum_truncate)
+ if (opts->vacuum_truncate == TERNARY_TRUE)
params.truncate = VACOPTVALUE_ENABLED;
else
params.truncate = VACOPTVALUE_DISABLED;
diff --git a/src/include/access/reloptions.h b/src/include/access/reloptions.h
index 2f08e1b0cf0..dfbef2babf2 100644
--- a/src/include/access/reloptions.h
+++ b/src/include/access/reloptions.h
@@ -29,6 +29,7 @@
typedef enum relopt_type
{
RELOPT_TYPE_BOOL,
+ RELOPT_TYPE_TERNARY, /* on, off, unset */
RELOPT_TYPE_INT,
RELOPT_TYPE_REAL,
RELOPT_TYPE_ENUM,
@@ -80,6 +81,7 @@ typedef struct relopt_value
union
{
bool bool_val;
+ pg_ternary ternary_val;
int int_val;
double real_val;
int enum_val;
@@ -94,6 +96,12 @@ typedef struct relopt_bool
bool default_val;
} relopt_bool;
+typedef struct relopt_ternary
+{
+ relopt_gen gen;
+ int default_val;
+} relopt_ternary;
+
typedef struct relopt_int
{
relopt_gen gen;
@@ -152,19 +160,6 @@ typedef struct
const char *optname; /* option's name */
relopt_type opttype; /* option's datatype */
int offset; /* offset of field in result struct */
-
- /*
- * isset_offset is an optional offset of a field in the result struct that
- * stores whether the option is explicitly set for the relation or if it
- * just picked up the default value. In most cases, this can be
- * accomplished by giving the reloption a special out-of-range default
- * value (e.g., some integer reloptions use -2), but this isn't always
- * possible. For example, a Boolean reloption cannot be given an
- * out-of-range default, so we need another way to discover the source of
- * its value. This offset is only used if given a value greater than
- * zero.
- */
- int isset_offset;
} relopt_parse_elt;
/* Local reloption definition */
@@ -195,6 +190,9 @@ typedef struct local_relopts
extern relopt_kind add_reloption_kind(void);
extern void add_bool_reloption(bits32 kinds, const char *name, const char *desc,
bool default_val, LOCKMODE lockmode);
+extern void add_ternary_reloption(bits32 kinds, const char *name,
+ const char *desc, pg_ternary default_val,
+ LOCKMODE lockmode);
extern void add_int_reloption(bits32 kinds, const char *name, const char *desc,
int default_val, int min_val, int max_val,
LOCKMODE lockmode);
@@ -214,6 +212,9 @@ extern void register_reloptions_validator(local_relopts *relopts,
extern void add_local_bool_reloption(local_relopts *relopts, const char *name,
const char *desc, bool default_val,
int offset);
+extern void add_local_ternary_reloption(local_relopts *relopts,
+ const char *name, const char *desc,
+ pg_ternary default_val, int offset);
extern void add_local_int_reloption(local_relopts *relopts, const char *name,
const char *desc, int default_val,
int min_val, int max_val, int offset);
diff --git a/src/include/postgres.h b/src/include/postgres.h
index 7d93fbce709..47c88780776 100644
--- a/src/include/postgres.h
+++ b/src/include/postgres.h
@@ -543,6 +543,21 @@ Float8GetDatum(float8 X)
* ----------------------------------------------------------------
*/
+/*
+ * pg_ternary
+ * Boolean value with an extra "unset" value
+ *
+ * This enum can be used for values that want to distinguish between true,
+ * false, and unset.
+*/
+
+typedef enum pg_ternary
+{
+ TERNARY_FALSE = 0,
+ TERNARY_TRUE = 1,
+ TERNARY_UNSET = -1
+} pg_ternary;
+
/*
* NON_EXEC_STATIC: It's sometimes useful to define a variable or function
* that is normally static but extern when using EXEC_BACKEND (see
diff --git a/src/include/utils/rel.h b/src/include/utils/rel.h
index d03ab247788..5feb18a5373 100644
--- a/src/include/utils/rel.h
+++ b/src/include/utils/rel.h
@@ -347,8 +347,7 @@ typedef struct StdRdOptions
bool user_catalog_table; /* use as an additional catalog relation */
int parallel_workers; /* max number of parallel workers */
StdRdOptIndexCleanup vacuum_index_cleanup; /* controls index vacuuming */
- bool vacuum_truncate; /* enables vacuum to truncate a relation */
- bool vacuum_truncate_set; /* whether vacuum_truncate is set */
+ pg_ternary vacuum_truncate; /* enables vacuum to truncate a relation */
/*
* Fraction of pages in a relation that vacuum can eagerly scan and fail
diff --git a/src/test/regress/expected/reloptions.out b/src/test/regress/expected/reloptions.out
index 9de19b4e3f1..1c99f79ab01 100644
--- a/src/test/regress/expected/reloptions.out
+++ b/src/test/regress/expected/reloptions.out
@@ -98,6 +98,42 @@ SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass;
{fillfactor=13,autovacuum_enabled=false}
(1 row)
+-- Tests for future (FIXME) ternary options
+-- behave as boolean option: accept unassigned name and truncated value
+DROP TABLE reloptions_test;
+CREATE TABLE reloptions_test(i INT) WITH (vacuum_truncate);
+SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass;
+ reloptions
+------------------------
+ {vacuum_truncate=true}
+(1 row)
+
+DROP TABLE reloptions_test;
+CREATE TABLE reloptions_test(i INT) WITH (vacuum_truncate=FaLS);
+SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass;
+ reloptions
+------------------------
+ {vacuum_truncate=fals}
+(1 row)
+
+-- preferred "true" alias is used when storing
+DROP TABLE reloptions_test;
+CREATE TABLE reloptions_test(i INT) WITH (vacuum_index_cleanup=on);
+SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass;
+ reloptions
+---------------------------
+ {vacuum_index_cleanup=on}
+(1 row)
+
+-- custom "third" value is available
+DROP TABLE reloptions_test;
+CREATE TABLE reloptions_test(i INT) WITH (vacuum_index_cleanup=auto);
+SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass;
+ reloptions
+-----------------------------
+ {vacuum_index_cleanup=auto}
+(1 row)
+
-- Test vacuum_truncate option
DROP TABLE reloptions_test;
CREATE TEMP TABLE reloptions_test(i INT NOT NULL, j text)
diff --git a/src/test/regress/sql/reloptions.sql b/src/test/regress/sql/reloptions.sql
index 24fbe0b478d..f5980dafcbc 100644
--- a/src/test/regress/sql/reloptions.sql
+++ b/src/test/regress/sql/reloptions.sql
@@ -59,6 +59,27 @@ UPDATE pg_class
ALTER TABLE reloptions_test RESET (illegal_option);
SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass;
+-- Tests for future (FIXME) ternary options
+
+-- behave as boolean option: accept unassigned name and truncated value
+DROP TABLE reloptions_test;
+CREATE TABLE reloptions_test(i INT) WITH (vacuum_truncate);
+SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass;
+
+DROP TABLE reloptions_test;
+CREATE TABLE reloptions_test(i INT) WITH (vacuum_truncate=FaLS);
+SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass;
+
+-- preferred "true" alias is used when storing
+DROP TABLE reloptions_test;
+CREATE TABLE reloptions_test(i INT) WITH (vacuum_index_cleanup=on);
+SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass;
+
+-- custom "third" value is available
+DROP TABLE reloptions_test;
+CREATE TABLE reloptions_test(i INT) WITH (vacuum_index_cleanup=auto);
+SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass;
+
-- Test vacuum_truncate option
DROP TABLE reloptions_test;
diff --git a/src/tools/pgindent/typedefs.list b/src/tools/pgindent/typedefs.list
index 3f3a888fd0e..1c8610fd46c 100644
--- a/src/tools/pgindent/typedefs.list
+++ b/src/tools/pgindent/typedefs.list
@@ -3952,6 +3952,7 @@ pg_sha512_ctx
pg_snapshot
pg_special_case
pg_stack_base_t
+pg_ternary
pg_time_t
pg_time_usec_t
pg_tz
@@ -4079,6 +4080,7 @@ relopt_kind
relopt_parse_elt
relopt_real
relopt_string
+relopt_ternary
relopt_type
relopt_value
relopts_validator
--
2.47.3
--c36t3fndxhkzbl5z--
^ permalink raw reply [nested|flat] 868+ messages in thread
* [PATCH v3] Introduce ternary reloptions
@ 2026-01-16 15:04 Álvaro Herrera <[email protected]>
0 siblings, 0 replies; 868+ messages in thread
From: Álvaro Herrera @ 2026-01-16 15:04 UTC (permalink / raw)
Introduce ternary reloption as a replacement for current
`vacuum_truncate` implementation. Remove the `vacuum_truncate_set`
separate flag and use TERNARY_UNSET instead.
This could also be used for other options such as `vacuum_index_cleanup`
and `buffering`, but lets get the scaffolding in first.
Discussion: https://postgr.es/m/3474141.usfYGdeWWP@thinkpad-pgpro
---
src/backend/access/common/reloptions.c | 137 ++++++++++++++++++-----
src/backend/commands/vacuum.c | 4 +-
src/include/access/reloptions.h | 27 ++---
src/include/postgres.h | 15 +++
src/include/utils/rel.h | 3 +-
src/test/regress/expected/reloptions.out | 36 ++++++
src/test/regress/sql/reloptions.sql | 21 ++++
src/tools/pgindent/typedefs.list | 2 +
8 files changed, 202 insertions(+), 43 deletions(-)
diff --git a/src/backend/access/common/reloptions.c b/src/backend/access/common/reloptions.c
index 0b83f98ed5f..6f1f577581a 100644
--- a/src/backend/access/common/reloptions.c
+++ b/src/backend/access/common/reloptions.c
@@ -40,9 +40,9 @@
*
* To add an option:
*
- * (i) decide on a type (bool, integer, real, enum, string), name, default
- * value, upper and lower bounds (if applicable); for strings, consider a
- * validation routine.
+ * (i) decide on a type (bool, ternary, integer, real, enum, string), name,
+ * default value, upper and lower bounds (if applicable); for strings,
+ * consider a validation routine.
* (ii) add a record below (or use add_<type>_reloption).
* (iii) add it to the appropriate options struct (perhaps StdRdOptions)
* (iv) add it to the appropriate handling routine (perhaps
@@ -147,15 +147,6 @@ static relopt_bool boolRelOpts[] =
},
false
},
- {
- {
- "vacuum_truncate",
- "Enables vacuum to truncate empty pages at the end of this table",
- RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
- ShareUpdateExclusiveLock
- },
- true
- },
{
{
"deduplicate_items",
@@ -170,6 +161,25 @@ static relopt_bool boolRelOpts[] =
{{NULL}}
};
+static relopt_ternary ternaryRelOpts[] =
+{
+ {
+ {
+ "vacuum_truncate",
+ "Enables vacuum to truncate empty pages at the end of this table",
+ RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
+ ShareUpdateExclusiveLock
+ },
+ TERNARY_UNSET
+ },
+ /* list terminator */
+ {
+ {
+ NULL
+ }
+ }
+};
+
static relopt_int intRelOpts[] =
{
{
@@ -609,6 +619,13 @@ initialize_reloptions(void)
boolRelOpts[i].gen.lockmode));
j++;
}
+ for (i = 0; ternaryRelOpts[i].gen.name; i++)
+ {
+ Assert(DoLockModesConflict(ternaryRelOpts[i].gen.lockmode,
+ ternaryRelOpts[i].gen.lockmode));
+ j++;
+ }
+
for (i = 0; intRelOpts[i].gen.name; i++)
{
Assert(DoLockModesConflict(intRelOpts[i].gen.lockmode,
@@ -649,6 +666,14 @@ initialize_reloptions(void)
j++;
}
+ for (i = 0; ternaryRelOpts[i].gen.name; i++)
+ {
+ relOpts[j] = &ternaryRelOpts[i].gen;
+ relOpts[j]->type = RELOPT_TYPE_TERNARY;
+ relOpts[j]->namelen = strlen(relOpts[j]->name);
+ j++;
+ }
+
for (i = 0; intRelOpts[i].gen.name; i++)
{
relOpts[j] = &intRelOpts[i].gen;
@@ -809,6 +834,9 @@ allocate_reloption(bits32 kinds, int type, const char *name, const char *desc,
case RELOPT_TYPE_BOOL:
size = sizeof(relopt_bool);
break;
+ case RELOPT_TYPE_TERNARY:
+ size = sizeof(relopt_ternary);
+ break;
case RELOPT_TYPE_INT:
size = sizeof(relopt_int);
break;
@@ -892,6 +920,57 @@ add_local_bool_reloption(local_relopts *relopts, const char *name,
add_local_reloption(relopts, (relopt_gen *) newoption, offset);
}
+/*
+ * init_ternary_reloption
+ * Allocate and initialize a new ternary reloption
+ */
+static relopt_ternary *
+init_ternary_reloption(bits32 kinds, const char *name, const char *desc,
+ pg_ternary default_val, LOCKMODE lockmode)
+{
+ relopt_ternary *newoption;
+
+ newoption = (relopt_ternary *)
+ allocate_reloption(kinds, RELOPT_TYPE_TERNARY, name, desc, lockmode);
+ newoption->default_val = default_val;
+
+ return newoption;
+}
+
+/*
+ * add_ternary_reloption
+ * Add a new ternary reloption
+ */
+void
+add_ternary_reloption(bits32 kinds, const char *name, const char *desc,
+ pg_ternary default_val, LOCKMODE lockmode)
+{
+ relopt_ternary *newoption;
+
+ newoption =
+ init_ternary_reloption(kinds, name, desc, default_val, lockmode);
+
+ add_reloption((relopt_gen *) newoption);
+}
+
+/*
+ * add_local_ternary_reloption
+ * Add a new ternary local reloption
+ *
+ * 'offset' is offset of ternary-typed field.
+ */
+void
+add_local_ternary_reloption(local_relopts *relopts, const char *name,
+ const char *desc, pg_ternary default_val,
+ int offset)
+{
+ relopt_ternary *newoption;
+
+ newoption =
+ init_ternary_reloption(RELOPT_KIND_LOCAL, name, desc, default_val, 0);
+
+ add_local_reloption(relopts, (relopt_gen *) newoption, offset);
+}
/*
* init_real_reloption
@@ -1626,6 +1705,19 @@ parse_one_reloption(relopt_value *option, char *text_str, int text_len,
option->gen->name, value)));
}
break;
+ case RELOPT_TYPE_TERNARY:
+ {
+ bool b;
+
+ parsed = parse_bool(value, &b);
+ option->values.ternary_val = b ? TERNARY_TRUE : TERNARY_FALSE;
+ if (validate && !parsed)
+ ereport(ERROR,
+ (errcode(ERRCODE_INVALID_PARAMETER_VALUE),
+ errmsg("invalid value for ternary option \"%s\": %s",
+ option->gen->name, value)));
+ }
+ break;
case RELOPT_TYPE_INT:
{
relopt_int *optint = (relopt_int *) option->gen;
@@ -1789,17 +1881,6 @@ fillRelOptions(void *rdopts, Size basesize,
char *itempos = ((char *) rdopts) + elems[j].offset;
char *string_val;
- /*
- * If isset_offset is provided, store whether the reloption is
- * set there.
- */
- if (elems[j].isset_offset > 0)
- {
- char *setpos = ((char *) rdopts) + elems[j].isset_offset;
-
- *(bool *) setpos = options[i].isset;
- }
-
switch (options[i].gen->type)
{
case RELOPT_TYPE_BOOL:
@@ -1807,6 +1888,11 @@ fillRelOptions(void *rdopts, Size basesize,
options[i].values.bool_val :
((relopt_bool *) options[i].gen)->default_val;
break;
+ case RELOPT_TYPE_TERNARY:
+ *(pg_ternary *) itempos = options[i].isset ?
+ options[i].values.ternary_val :
+ ((relopt_ternary *) options[i].gen)->default_val;
+ break;
case RELOPT_TYPE_INT:
*(int *) itempos = options[i].isset ?
options[i].values.int_val :
@@ -1923,8 +2009,8 @@ default_reloptions(Datum reloptions, bool validate, relopt_kind kind)
offsetof(StdRdOptions, parallel_workers)},
{"vacuum_index_cleanup", RELOPT_TYPE_ENUM,
offsetof(StdRdOptions, vacuum_index_cleanup)},
- {"vacuum_truncate", RELOPT_TYPE_BOOL,
- offsetof(StdRdOptions, vacuum_truncate), offsetof(StdRdOptions, vacuum_truncate_set)},
+ {"vacuum_truncate", RELOPT_TYPE_TERNARY,
+ offsetof(StdRdOptions, vacuum_truncate)},
{"vacuum_max_eager_freeze_failure_rate", RELOPT_TYPE_REAL,
offsetof(StdRdOptions, vacuum_max_eager_freeze_failure_rate)}
};
@@ -2004,7 +2090,6 @@ build_local_reloptions(local_relopts *relopts, Datum options, bool validate)
elems[i].optname = opt->option->name;
elems[i].opttype = opt->option->type;
elems[i].offset = opt->offset;
- elems[i].isset_offset = 0; /* not supported for local relopts yet */
i++;
}
diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c
index aa4fbec143f..696eab9bd97 100644
--- a/src/backend/commands/vacuum.c
+++ b/src/backend/commands/vacuum.c
@@ -2224,9 +2224,9 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams params,
{
StdRdOptions *opts = (StdRdOptions *) rel->rd_options;
- if (opts && opts->vacuum_truncate_set)
+ if (opts && opts->vacuum_truncate != TERNARY_UNSET)
{
- if (opts->vacuum_truncate)
+ if (opts->vacuum_truncate == TERNARY_TRUE)
params.truncate = VACOPTVALUE_ENABLED;
else
params.truncate = VACOPTVALUE_DISABLED;
diff --git a/src/include/access/reloptions.h b/src/include/access/reloptions.h
index 2f08e1b0cf0..dfbef2babf2 100644
--- a/src/include/access/reloptions.h
+++ b/src/include/access/reloptions.h
@@ -29,6 +29,7 @@
typedef enum relopt_type
{
RELOPT_TYPE_BOOL,
+ RELOPT_TYPE_TERNARY, /* on, off, unset */
RELOPT_TYPE_INT,
RELOPT_TYPE_REAL,
RELOPT_TYPE_ENUM,
@@ -80,6 +81,7 @@ typedef struct relopt_value
union
{
bool bool_val;
+ pg_ternary ternary_val;
int int_val;
double real_val;
int enum_val;
@@ -94,6 +96,12 @@ typedef struct relopt_bool
bool default_val;
} relopt_bool;
+typedef struct relopt_ternary
+{
+ relopt_gen gen;
+ int default_val;
+} relopt_ternary;
+
typedef struct relopt_int
{
relopt_gen gen;
@@ -152,19 +160,6 @@ typedef struct
const char *optname; /* option's name */
relopt_type opttype; /* option's datatype */
int offset; /* offset of field in result struct */
-
- /*
- * isset_offset is an optional offset of a field in the result struct that
- * stores whether the option is explicitly set for the relation or if it
- * just picked up the default value. In most cases, this can be
- * accomplished by giving the reloption a special out-of-range default
- * value (e.g., some integer reloptions use -2), but this isn't always
- * possible. For example, a Boolean reloption cannot be given an
- * out-of-range default, so we need another way to discover the source of
- * its value. This offset is only used if given a value greater than
- * zero.
- */
- int isset_offset;
} relopt_parse_elt;
/* Local reloption definition */
@@ -195,6 +190,9 @@ typedef struct local_relopts
extern relopt_kind add_reloption_kind(void);
extern void add_bool_reloption(bits32 kinds, const char *name, const char *desc,
bool default_val, LOCKMODE lockmode);
+extern void add_ternary_reloption(bits32 kinds, const char *name,
+ const char *desc, pg_ternary default_val,
+ LOCKMODE lockmode);
extern void add_int_reloption(bits32 kinds, const char *name, const char *desc,
int default_val, int min_val, int max_val,
LOCKMODE lockmode);
@@ -214,6 +212,9 @@ extern void register_reloptions_validator(local_relopts *relopts,
extern void add_local_bool_reloption(local_relopts *relopts, const char *name,
const char *desc, bool default_val,
int offset);
+extern void add_local_ternary_reloption(local_relopts *relopts,
+ const char *name, const char *desc,
+ pg_ternary default_val, int offset);
extern void add_local_int_reloption(local_relopts *relopts, const char *name,
const char *desc, int default_val,
int min_val, int max_val, int offset);
diff --git a/src/include/postgres.h b/src/include/postgres.h
index 7d93fbce709..47c88780776 100644
--- a/src/include/postgres.h
+++ b/src/include/postgres.h
@@ -543,6 +543,21 @@ Float8GetDatum(float8 X)
* ----------------------------------------------------------------
*/
+/*
+ * pg_ternary
+ * Boolean value with an extra "unset" value
+ *
+ * This enum can be used for values that want to distinguish between true,
+ * false, and unset.
+*/
+
+typedef enum pg_ternary
+{
+ TERNARY_FALSE = 0,
+ TERNARY_TRUE = 1,
+ TERNARY_UNSET = -1
+} pg_ternary;
+
/*
* NON_EXEC_STATIC: It's sometimes useful to define a variable or function
* that is normally static but extern when using EXEC_BACKEND (see
diff --git a/src/include/utils/rel.h b/src/include/utils/rel.h
index d03ab247788..5feb18a5373 100644
--- a/src/include/utils/rel.h
+++ b/src/include/utils/rel.h
@@ -347,8 +347,7 @@ typedef struct StdRdOptions
bool user_catalog_table; /* use as an additional catalog relation */
int parallel_workers; /* max number of parallel workers */
StdRdOptIndexCleanup vacuum_index_cleanup; /* controls index vacuuming */
- bool vacuum_truncate; /* enables vacuum to truncate a relation */
- bool vacuum_truncate_set; /* whether vacuum_truncate is set */
+ pg_ternary vacuum_truncate; /* enables vacuum to truncate a relation */
/*
* Fraction of pages in a relation that vacuum can eagerly scan and fail
diff --git a/src/test/regress/expected/reloptions.out b/src/test/regress/expected/reloptions.out
index 9de19b4e3f1..1c99f79ab01 100644
--- a/src/test/regress/expected/reloptions.out
+++ b/src/test/regress/expected/reloptions.out
@@ -98,6 +98,42 @@ SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass;
{fillfactor=13,autovacuum_enabled=false}
(1 row)
+-- Tests for future (FIXME) ternary options
+-- behave as boolean option: accept unassigned name and truncated value
+DROP TABLE reloptions_test;
+CREATE TABLE reloptions_test(i INT) WITH (vacuum_truncate);
+SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass;
+ reloptions
+------------------------
+ {vacuum_truncate=true}
+(1 row)
+
+DROP TABLE reloptions_test;
+CREATE TABLE reloptions_test(i INT) WITH (vacuum_truncate=FaLS);
+SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass;
+ reloptions
+------------------------
+ {vacuum_truncate=fals}
+(1 row)
+
+-- preferred "true" alias is used when storing
+DROP TABLE reloptions_test;
+CREATE TABLE reloptions_test(i INT) WITH (vacuum_index_cleanup=on);
+SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass;
+ reloptions
+---------------------------
+ {vacuum_index_cleanup=on}
+(1 row)
+
+-- custom "third" value is available
+DROP TABLE reloptions_test;
+CREATE TABLE reloptions_test(i INT) WITH (vacuum_index_cleanup=auto);
+SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass;
+ reloptions
+-----------------------------
+ {vacuum_index_cleanup=auto}
+(1 row)
+
-- Test vacuum_truncate option
DROP TABLE reloptions_test;
CREATE TEMP TABLE reloptions_test(i INT NOT NULL, j text)
diff --git a/src/test/regress/sql/reloptions.sql b/src/test/regress/sql/reloptions.sql
index 24fbe0b478d..f5980dafcbc 100644
--- a/src/test/regress/sql/reloptions.sql
+++ b/src/test/regress/sql/reloptions.sql
@@ -59,6 +59,27 @@ UPDATE pg_class
ALTER TABLE reloptions_test RESET (illegal_option);
SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass;
+-- Tests for future (FIXME) ternary options
+
+-- behave as boolean option: accept unassigned name and truncated value
+DROP TABLE reloptions_test;
+CREATE TABLE reloptions_test(i INT) WITH (vacuum_truncate);
+SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass;
+
+DROP TABLE reloptions_test;
+CREATE TABLE reloptions_test(i INT) WITH (vacuum_truncate=FaLS);
+SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass;
+
+-- preferred "true" alias is used when storing
+DROP TABLE reloptions_test;
+CREATE TABLE reloptions_test(i INT) WITH (vacuum_index_cleanup=on);
+SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass;
+
+-- custom "third" value is available
+DROP TABLE reloptions_test;
+CREATE TABLE reloptions_test(i INT) WITH (vacuum_index_cleanup=auto);
+SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass;
+
-- Test vacuum_truncate option
DROP TABLE reloptions_test;
diff --git a/src/tools/pgindent/typedefs.list b/src/tools/pgindent/typedefs.list
index 3f3a888fd0e..1c8610fd46c 100644
--- a/src/tools/pgindent/typedefs.list
+++ b/src/tools/pgindent/typedefs.list
@@ -3952,6 +3952,7 @@ pg_sha512_ctx
pg_snapshot
pg_special_case
pg_stack_base_t
+pg_ternary
pg_time_t
pg_time_usec_t
pg_tz
@@ -4079,6 +4080,7 @@ relopt_kind
relopt_parse_elt
relopt_real
relopt_string
+relopt_ternary
relopt_type
relopt_value
relopts_validator
--
2.47.3
--c36t3fndxhkzbl5z--
^ permalink raw reply [nested|flat] 868+ messages in thread
* [PATCH v3] Introduce ternary reloptions
@ 2026-01-16 15:04 Álvaro Herrera <[email protected]>
0 siblings, 0 replies; 868+ messages in thread
From: Álvaro Herrera @ 2026-01-16 15:04 UTC (permalink / raw)
Introduce ternary reloption as a replacement for current
`vacuum_truncate` implementation. Remove the `vacuum_truncate_set`
separate flag and use TERNARY_UNSET instead.
This could also be used for other options such as `vacuum_index_cleanup`
and `buffering`, but lets get the scaffolding in first.
Discussion: https://postgr.es/m/3474141.usfYGdeWWP@thinkpad-pgpro
---
src/backend/access/common/reloptions.c | 137 ++++++++++++++++++-----
src/backend/commands/vacuum.c | 4 +-
src/include/access/reloptions.h | 27 ++---
src/include/postgres.h | 15 +++
src/include/utils/rel.h | 3 +-
src/test/regress/expected/reloptions.out | 36 ++++++
src/test/regress/sql/reloptions.sql | 21 ++++
src/tools/pgindent/typedefs.list | 2 +
8 files changed, 202 insertions(+), 43 deletions(-)
diff --git a/src/backend/access/common/reloptions.c b/src/backend/access/common/reloptions.c
index 0b83f98ed5f..6f1f577581a 100644
--- a/src/backend/access/common/reloptions.c
+++ b/src/backend/access/common/reloptions.c
@@ -40,9 +40,9 @@
*
* To add an option:
*
- * (i) decide on a type (bool, integer, real, enum, string), name, default
- * value, upper and lower bounds (if applicable); for strings, consider a
- * validation routine.
+ * (i) decide on a type (bool, ternary, integer, real, enum, string), name,
+ * default value, upper and lower bounds (if applicable); for strings,
+ * consider a validation routine.
* (ii) add a record below (or use add_<type>_reloption).
* (iii) add it to the appropriate options struct (perhaps StdRdOptions)
* (iv) add it to the appropriate handling routine (perhaps
@@ -147,15 +147,6 @@ static relopt_bool boolRelOpts[] =
},
false
},
- {
- {
- "vacuum_truncate",
- "Enables vacuum to truncate empty pages at the end of this table",
- RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
- ShareUpdateExclusiveLock
- },
- true
- },
{
{
"deduplicate_items",
@@ -170,6 +161,25 @@ static relopt_bool boolRelOpts[] =
{{NULL}}
};
+static relopt_ternary ternaryRelOpts[] =
+{
+ {
+ {
+ "vacuum_truncate",
+ "Enables vacuum to truncate empty pages at the end of this table",
+ RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
+ ShareUpdateExclusiveLock
+ },
+ TERNARY_UNSET
+ },
+ /* list terminator */
+ {
+ {
+ NULL
+ }
+ }
+};
+
static relopt_int intRelOpts[] =
{
{
@@ -609,6 +619,13 @@ initialize_reloptions(void)
boolRelOpts[i].gen.lockmode));
j++;
}
+ for (i = 0; ternaryRelOpts[i].gen.name; i++)
+ {
+ Assert(DoLockModesConflict(ternaryRelOpts[i].gen.lockmode,
+ ternaryRelOpts[i].gen.lockmode));
+ j++;
+ }
+
for (i = 0; intRelOpts[i].gen.name; i++)
{
Assert(DoLockModesConflict(intRelOpts[i].gen.lockmode,
@@ -649,6 +666,14 @@ initialize_reloptions(void)
j++;
}
+ for (i = 0; ternaryRelOpts[i].gen.name; i++)
+ {
+ relOpts[j] = &ternaryRelOpts[i].gen;
+ relOpts[j]->type = RELOPT_TYPE_TERNARY;
+ relOpts[j]->namelen = strlen(relOpts[j]->name);
+ j++;
+ }
+
for (i = 0; intRelOpts[i].gen.name; i++)
{
relOpts[j] = &intRelOpts[i].gen;
@@ -809,6 +834,9 @@ allocate_reloption(bits32 kinds, int type, const char *name, const char *desc,
case RELOPT_TYPE_BOOL:
size = sizeof(relopt_bool);
break;
+ case RELOPT_TYPE_TERNARY:
+ size = sizeof(relopt_ternary);
+ break;
case RELOPT_TYPE_INT:
size = sizeof(relopt_int);
break;
@@ -892,6 +920,57 @@ add_local_bool_reloption(local_relopts *relopts, const char *name,
add_local_reloption(relopts, (relopt_gen *) newoption, offset);
}
+/*
+ * init_ternary_reloption
+ * Allocate and initialize a new ternary reloption
+ */
+static relopt_ternary *
+init_ternary_reloption(bits32 kinds, const char *name, const char *desc,
+ pg_ternary default_val, LOCKMODE lockmode)
+{
+ relopt_ternary *newoption;
+
+ newoption = (relopt_ternary *)
+ allocate_reloption(kinds, RELOPT_TYPE_TERNARY, name, desc, lockmode);
+ newoption->default_val = default_val;
+
+ return newoption;
+}
+
+/*
+ * add_ternary_reloption
+ * Add a new ternary reloption
+ */
+void
+add_ternary_reloption(bits32 kinds, const char *name, const char *desc,
+ pg_ternary default_val, LOCKMODE lockmode)
+{
+ relopt_ternary *newoption;
+
+ newoption =
+ init_ternary_reloption(kinds, name, desc, default_val, lockmode);
+
+ add_reloption((relopt_gen *) newoption);
+}
+
+/*
+ * add_local_ternary_reloption
+ * Add a new ternary local reloption
+ *
+ * 'offset' is offset of ternary-typed field.
+ */
+void
+add_local_ternary_reloption(local_relopts *relopts, const char *name,
+ const char *desc, pg_ternary default_val,
+ int offset)
+{
+ relopt_ternary *newoption;
+
+ newoption =
+ init_ternary_reloption(RELOPT_KIND_LOCAL, name, desc, default_val, 0);
+
+ add_local_reloption(relopts, (relopt_gen *) newoption, offset);
+}
/*
* init_real_reloption
@@ -1626,6 +1705,19 @@ parse_one_reloption(relopt_value *option, char *text_str, int text_len,
option->gen->name, value)));
}
break;
+ case RELOPT_TYPE_TERNARY:
+ {
+ bool b;
+
+ parsed = parse_bool(value, &b);
+ option->values.ternary_val = b ? TERNARY_TRUE : TERNARY_FALSE;
+ if (validate && !parsed)
+ ereport(ERROR,
+ (errcode(ERRCODE_INVALID_PARAMETER_VALUE),
+ errmsg("invalid value for ternary option \"%s\": %s",
+ option->gen->name, value)));
+ }
+ break;
case RELOPT_TYPE_INT:
{
relopt_int *optint = (relopt_int *) option->gen;
@@ -1789,17 +1881,6 @@ fillRelOptions(void *rdopts, Size basesize,
char *itempos = ((char *) rdopts) + elems[j].offset;
char *string_val;
- /*
- * If isset_offset is provided, store whether the reloption is
- * set there.
- */
- if (elems[j].isset_offset > 0)
- {
- char *setpos = ((char *) rdopts) + elems[j].isset_offset;
-
- *(bool *) setpos = options[i].isset;
- }
-
switch (options[i].gen->type)
{
case RELOPT_TYPE_BOOL:
@@ -1807,6 +1888,11 @@ fillRelOptions(void *rdopts, Size basesize,
options[i].values.bool_val :
((relopt_bool *) options[i].gen)->default_val;
break;
+ case RELOPT_TYPE_TERNARY:
+ *(pg_ternary *) itempos = options[i].isset ?
+ options[i].values.ternary_val :
+ ((relopt_ternary *) options[i].gen)->default_val;
+ break;
case RELOPT_TYPE_INT:
*(int *) itempos = options[i].isset ?
options[i].values.int_val :
@@ -1923,8 +2009,8 @@ default_reloptions(Datum reloptions, bool validate, relopt_kind kind)
offsetof(StdRdOptions, parallel_workers)},
{"vacuum_index_cleanup", RELOPT_TYPE_ENUM,
offsetof(StdRdOptions, vacuum_index_cleanup)},
- {"vacuum_truncate", RELOPT_TYPE_BOOL,
- offsetof(StdRdOptions, vacuum_truncate), offsetof(StdRdOptions, vacuum_truncate_set)},
+ {"vacuum_truncate", RELOPT_TYPE_TERNARY,
+ offsetof(StdRdOptions, vacuum_truncate)},
{"vacuum_max_eager_freeze_failure_rate", RELOPT_TYPE_REAL,
offsetof(StdRdOptions, vacuum_max_eager_freeze_failure_rate)}
};
@@ -2004,7 +2090,6 @@ build_local_reloptions(local_relopts *relopts, Datum options, bool validate)
elems[i].optname = opt->option->name;
elems[i].opttype = opt->option->type;
elems[i].offset = opt->offset;
- elems[i].isset_offset = 0; /* not supported for local relopts yet */
i++;
}
diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c
index aa4fbec143f..696eab9bd97 100644
--- a/src/backend/commands/vacuum.c
+++ b/src/backend/commands/vacuum.c
@@ -2224,9 +2224,9 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams params,
{
StdRdOptions *opts = (StdRdOptions *) rel->rd_options;
- if (opts && opts->vacuum_truncate_set)
+ if (opts && opts->vacuum_truncate != TERNARY_UNSET)
{
- if (opts->vacuum_truncate)
+ if (opts->vacuum_truncate == TERNARY_TRUE)
params.truncate = VACOPTVALUE_ENABLED;
else
params.truncate = VACOPTVALUE_DISABLED;
diff --git a/src/include/access/reloptions.h b/src/include/access/reloptions.h
index 2f08e1b0cf0..dfbef2babf2 100644
--- a/src/include/access/reloptions.h
+++ b/src/include/access/reloptions.h
@@ -29,6 +29,7 @@
typedef enum relopt_type
{
RELOPT_TYPE_BOOL,
+ RELOPT_TYPE_TERNARY, /* on, off, unset */
RELOPT_TYPE_INT,
RELOPT_TYPE_REAL,
RELOPT_TYPE_ENUM,
@@ -80,6 +81,7 @@ typedef struct relopt_value
union
{
bool bool_val;
+ pg_ternary ternary_val;
int int_val;
double real_val;
int enum_val;
@@ -94,6 +96,12 @@ typedef struct relopt_bool
bool default_val;
} relopt_bool;
+typedef struct relopt_ternary
+{
+ relopt_gen gen;
+ int default_val;
+} relopt_ternary;
+
typedef struct relopt_int
{
relopt_gen gen;
@@ -152,19 +160,6 @@ typedef struct
const char *optname; /* option's name */
relopt_type opttype; /* option's datatype */
int offset; /* offset of field in result struct */
-
- /*
- * isset_offset is an optional offset of a field in the result struct that
- * stores whether the option is explicitly set for the relation or if it
- * just picked up the default value. In most cases, this can be
- * accomplished by giving the reloption a special out-of-range default
- * value (e.g., some integer reloptions use -2), but this isn't always
- * possible. For example, a Boolean reloption cannot be given an
- * out-of-range default, so we need another way to discover the source of
- * its value. This offset is only used if given a value greater than
- * zero.
- */
- int isset_offset;
} relopt_parse_elt;
/* Local reloption definition */
@@ -195,6 +190,9 @@ typedef struct local_relopts
extern relopt_kind add_reloption_kind(void);
extern void add_bool_reloption(bits32 kinds, const char *name, const char *desc,
bool default_val, LOCKMODE lockmode);
+extern void add_ternary_reloption(bits32 kinds, const char *name,
+ const char *desc, pg_ternary default_val,
+ LOCKMODE lockmode);
extern void add_int_reloption(bits32 kinds, const char *name, const char *desc,
int default_val, int min_val, int max_val,
LOCKMODE lockmode);
@@ -214,6 +212,9 @@ extern void register_reloptions_validator(local_relopts *relopts,
extern void add_local_bool_reloption(local_relopts *relopts, const char *name,
const char *desc, bool default_val,
int offset);
+extern void add_local_ternary_reloption(local_relopts *relopts,
+ const char *name, const char *desc,
+ pg_ternary default_val, int offset);
extern void add_local_int_reloption(local_relopts *relopts, const char *name,
const char *desc, int default_val,
int min_val, int max_val, int offset);
diff --git a/src/include/postgres.h b/src/include/postgres.h
index 7d93fbce709..47c88780776 100644
--- a/src/include/postgres.h
+++ b/src/include/postgres.h
@@ -543,6 +543,21 @@ Float8GetDatum(float8 X)
* ----------------------------------------------------------------
*/
+/*
+ * pg_ternary
+ * Boolean value with an extra "unset" value
+ *
+ * This enum can be used for values that want to distinguish between true,
+ * false, and unset.
+*/
+
+typedef enum pg_ternary
+{
+ TERNARY_FALSE = 0,
+ TERNARY_TRUE = 1,
+ TERNARY_UNSET = -1
+} pg_ternary;
+
/*
* NON_EXEC_STATIC: It's sometimes useful to define a variable or function
* that is normally static but extern when using EXEC_BACKEND (see
diff --git a/src/include/utils/rel.h b/src/include/utils/rel.h
index d03ab247788..5feb18a5373 100644
--- a/src/include/utils/rel.h
+++ b/src/include/utils/rel.h
@@ -347,8 +347,7 @@ typedef struct StdRdOptions
bool user_catalog_table; /* use as an additional catalog relation */
int parallel_workers; /* max number of parallel workers */
StdRdOptIndexCleanup vacuum_index_cleanup; /* controls index vacuuming */
- bool vacuum_truncate; /* enables vacuum to truncate a relation */
- bool vacuum_truncate_set; /* whether vacuum_truncate is set */
+ pg_ternary vacuum_truncate; /* enables vacuum to truncate a relation */
/*
* Fraction of pages in a relation that vacuum can eagerly scan and fail
diff --git a/src/test/regress/expected/reloptions.out b/src/test/regress/expected/reloptions.out
index 9de19b4e3f1..1c99f79ab01 100644
--- a/src/test/regress/expected/reloptions.out
+++ b/src/test/regress/expected/reloptions.out
@@ -98,6 +98,42 @@ SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass;
{fillfactor=13,autovacuum_enabled=false}
(1 row)
+-- Tests for future (FIXME) ternary options
+-- behave as boolean option: accept unassigned name and truncated value
+DROP TABLE reloptions_test;
+CREATE TABLE reloptions_test(i INT) WITH (vacuum_truncate);
+SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass;
+ reloptions
+------------------------
+ {vacuum_truncate=true}
+(1 row)
+
+DROP TABLE reloptions_test;
+CREATE TABLE reloptions_test(i INT) WITH (vacuum_truncate=FaLS);
+SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass;
+ reloptions
+------------------------
+ {vacuum_truncate=fals}
+(1 row)
+
+-- preferred "true" alias is used when storing
+DROP TABLE reloptions_test;
+CREATE TABLE reloptions_test(i INT) WITH (vacuum_index_cleanup=on);
+SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass;
+ reloptions
+---------------------------
+ {vacuum_index_cleanup=on}
+(1 row)
+
+-- custom "third" value is available
+DROP TABLE reloptions_test;
+CREATE TABLE reloptions_test(i INT) WITH (vacuum_index_cleanup=auto);
+SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass;
+ reloptions
+-----------------------------
+ {vacuum_index_cleanup=auto}
+(1 row)
+
-- Test vacuum_truncate option
DROP TABLE reloptions_test;
CREATE TEMP TABLE reloptions_test(i INT NOT NULL, j text)
diff --git a/src/test/regress/sql/reloptions.sql b/src/test/regress/sql/reloptions.sql
index 24fbe0b478d..f5980dafcbc 100644
--- a/src/test/regress/sql/reloptions.sql
+++ b/src/test/regress/sql/reloptions.sql
@@ -59,6 +59,27 @@ UPDATE pg_class
ALTER TABLE reloptions_test RESET (illegal_option);
SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass;
+-- Tests for future (FIXME) ternary options
+
+-- behave as boolean option: accept unassigned name and truncated value
+DROP TABLE reloptions_test;
+CREATE TABLE reloptions_test(i INT) WITH (vacuum_truncate);
+SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass;
+
+DROP TABLE reloptions_test;
+CREATE TABLE reloptions_test(i INT) WITH (vacuum_truncate=FaLS);
+SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass;
+
+-- preferred "true" alias is used when storing
+DROP TABLE reloptions_test;
+CREATE TABLE reloptions_test(i INT) WITH (vacuum_index_cleanup=on);
+SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass;
+
+-- custom "third" value is available
+DROP TABLE reloptions_test;
+CREATE TABLE reloptions_test(i INT) WITH (vacuum_index_cleanup=auto);
+SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass;
+
-- Test vacuum_truncate option
DROP TABLE reloptions_test;
diff --git a/src/tools/pgindent/typedefs.list b/src/tools/pgindent/typedefs.list
index 3f3a888fd0e..1c8610fd46c 100644
--- a/src/tools/pgindent/typedefs.list
+++ b/src/tools/pgindent/typedefs.list
@@ -3952,6 +3952,7 @@ pg_sha512_ctx
pg_snapshot
pg_special_case
pg_stack_base_t
+pg_ternary
pg_time_t
pg_time_usec_t
pg_tz
@@ -4079,6 +4080,7 @@ relopt_kind
relopt_parse_elt
relopt_real
relopt_string
+relopt_ternary
relopt_type
relopt_value
relopts_validator
--
2.47.3
--c36t3fndxhkzbl5z--
^ permalink raw reply [nested|flat] 868+ messages in thread
* [PATCH v3] Introduce ternary reloptions
@ 2026-01-16 15:04 Álvaro Herrera <[email protected]>
0 siblings, 0 replies; 868+ messages in thread
From: Álvaro Herrera @ 2026-01-16 15:04 UTC (permalink / raw)
Introduce ternary reloption as a replacement for current
`vacuum_truncate` implementation. Remove the `vacuum_truncate_set`
separate flag and use TERNARY_UNSET instead.
This could also be used for other options such as `vacuum_index_cleanup`
and `buffering`, but lets get the scaffolding in first.
Discussion: https://postgr.es/m/3474141.usfYGdeWWP@thinkpad-pgpro
---
src/backend/access/common/reloptions.c | 137 ++++++++++++++++++-----
src/backend/commands/vacuum.c | 4 +-
src/include/access/reloptions.h | 27 ++---
src/include/postgres.h | 15 +++
src/include/utils/rel.h | 3 +-
src/test/regress/expected/reloptions.out | 36 ++++++
src/test/regress/sql/reloptions.sql | 21 ++++
src/tools/pgindent/typedefs.list | 2 +
8 files changed, 202 insertions(+), 43 deletions(-)
diff --git a/src/backend/access/common/reloptions.c b/src/backend/access/common/reloptions.c
index 0b83f98ed5f..6f1f577581a 100644
--- a/src/backend/access/common/reloptions.c
+++ b/src/backend/access/common/reloptions.c
@@ -40,9 +40,9 @@
*
* To add an option:
*
- * (i) decide on a type (bool, integer, real, enum, string), name, default
- * value, upper and lower bounds (if applicable); for strings, consider a
- * validation routine.
+ * (i) decide on a type (bool, ternary, integer, real, enum, string), name,
+ * default value, upper and lower bounds (if applicable); for strings,
+ * consider a validation routine.
* (ii) add a record below (or use add_<type>_reloption).
* (iii) add it to the appropriate options struct (perhaps StdRdOptions)
* (iv) add it to the appropriate handling routine (perhaps
@@ -147,15 +147,6 @@ static relopt_bool boolRelOpts[] =
},
false
},
- {
- {
- "vacuum_truncate",
- "Enables vacuum to truncate empty pages at the end of this table",
- RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
- ShareUpdateExclusiveLock
- },
- true
- },
{
{
"deduplicate_items",
@@ -170,6 +161,25 @@ static relopt_bool boolRelOpts[] =
{{NULL}}
};
+static relopt_ternary ternaryRelOpts[] =
+{
+ {
+ {
+ "vacuum_truncate",
+ "Enables vacuum to truncate empty pages at the end of this table",
+ RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
+ ShareUpdateExclusiveLock
+ },
+ TERNARY_UNSET
+ },
+ /* list terminator */
+ {
+ {
+ NULL
+ }
+ }
+};
+
static relopt_int intRelOpts[] =
{
{
@@ -609,6 +619,13 @@ initialize_reloptions(void)
boolRelOpts[i].gen.lockmode));
j++;
}
+ for (i = 0; ternaryRelOpts[i].gen.name; i++)
+ {
+ Assert(DoLockModesConflict(ternaryRelOpts[i].gen.lockmode,
+ ternaryRelOpts[i].gen.lockmode));
+ j++;
+ }
+
for (i = 0; intRelOpts[i].gen.name; i++)
{
Assert(DoLockModesConflict(intRelOpts[i].gen.lockmode,
@@ -649,6 +666,14 @@ initialize_reloptions(void)
j++;
}
+ for (i = 0; ternaryRelOpts[i].gen.name; i++)
+ {
+ relOpts[j] = &ternaryRelOpts[i].gen;
+ relOpts[j]->type = RELOPT_TYPE_TERNARY;
+ relOpts[j]->namelen = strlen(relOpts[j]->name);
+ j++;
+ }
+
for (i = 0; intRelOpts[i].gen.name; i++)
{
relOpts[j] = &intRelOpts[i].gen;
@@ -809,6 +834,9 @@ allocate_reloption(bits32 kinds, int type, const char *name, const char *desc,
case RELOPT_TYPE_BOOL:
size = sizeof(relopt_bool);
break;
+ case RELOPT_TYPE_TERNARY:
+ size = sizeof(relopt_ternary);
+ break;
case RELOPT_TYPE_INT:
size = sizeof(relopt_int);
break;
@@ -892,6 +920,57 @@ add_local_bool_reloption(local_relopts *relopts, const char *name,
add_local_reloption(relopts, (relopt_gen *) newoption, offset);
}
+/*
+ * init_ternary_reloption
+ * Allocate and initialize a new ternary reloption
+ */
+static relopt_ternary *
+init_ternary_reloption(bits32 kinds, const char *name, const char *desc,
+ pg_ternary default_val, LOCKMODE lockmode)
+{
+ relopt_ternary *newoption;
+
+ newoption = (relopt_ternary *)
+ allocate_reloption(kinds, RELOPT_TYPE_TERNARY, name, desc, lockmode);
+ newoption->default_val = default_val;
+
+ return newoption;
+}
+
+/*
+ * add_ternary_reloption
+ * Add a new ternary reloption
+ */
+void
+add_ternary_reloption(bits32 kinds, const char *name, const char *desc,
+ pg_ternary default_val, LOCKMODE lockmode)
+{
+ relopt_ternary *newoption;
+
+ newoption =
+ init_ternary_reloption(kinds, name, desc, default_val, lockmode);
+
+ add_reloption((relopt_gen *) newoption);
+}
+
+/*
+ * add_local_ternary_reloption
+ * Add a new ternary local reloption
+ *
+ * 'offset' is offset of ternary-typed field.
+ */
+void
+add_local_ternary_reloption(local_relopts *relopts, const char *name,
+ const char *desc, pg_ternary default_val,
+ int offset)
+{
+ relopt_ternary *newoption;
+
+ newoption =
+ init_ternary_reloption(RELOPT_KIND_LOCAL, name, desc, default_val, 0);
+
+ add_local_reloption(relopts, (relopt_gen *) newoption, offset);
+}
/*
* init_real_reloption
@@ -1626,6 +1705,19 @@ parse_one_reloption(relopt_value *option, char *text_str, int text_len,
option->gen->name, value)));
}
break;
+ case RELOPT_TYPE_TERNARY:
+ {
+ bool b;
+
+ parsed = parse_bool(value, &b);
+ option->values.ternary_val = b ? TERNARY_TRUE : TERNARY_FALSE;
+ if (validate && !parsed)
+ ereport(ERROR,
+ (errcode(ERRCODE_INVALID_PARAMETER_VALUE),
+ errmsg("invalid value for ternary option \"%s\": %s",
+ option->gen->name, value)));
+ }
+ break;
case RELOPT_TYPE_INT:
{
relopt_int *optint = (relopt_int *) option->gen;
@@ -1789,17 +1881,6 @@ fillRelOptions(void *rdopts, Size basesize,
char *itempos = ((char *) rdopts) + elems[j].offset;
char *string_val;
- /*
- * If isset_offset is provided, store whether the reloption is
- * set there.
- */
- if (elems[j].isset_offset > 0)
- {
- char *setpos = ((char *) rdopts) + elems[j].isset_offset;
-
- *(bool *) setpos = options[i].isset;
- }
-
switch (options[i].gen->type)
{
case RELOPT_TYPE_BOOL:
@@ -1807,6 +1888,11 @@ fillRelOptions(void *rdopts, Size basesize,
options[i].values.bool_val :
((relopt_bool *) options[i].gen)->default_val;
break;
+ case RELOPT_TYPE_TERNARY:
+ *(pg_ternary *) itempos = options[i].isset ?
+ options[i].values.ternary_val :
+ ((relopt_ternary *) options[i].gen)->default_val;
+ break;
case RELOPT_TYPE_INT:
*(int *) itempos = options[i].isset ?
options[i].values.int_val :
@@ -1923,8 +2009,8 @@ default_reloptions(Datum reloptions, bool validate, relopt_kind kind)
offsetof(StdRdOptions, parallel_workers)},
{"vacuum_index_cleanup", RELOPT_TYPE_ENUM,
offsetof(StdRdOptions, vacuum_index_cleanup)},
- {"vacuum_truncate", RELOPT_TYPE_BOOL,
- offsetof(StdRdOptions, vacuum_truncate), offsetof(StdRdOptions, vacuum_truncate_set)},
+ {"vacuum_truncate", RELOPT_TYPE_TERNARY,
+ offsetof(StdRdOptions, vacuum_truncate)},
{"vacuum_max_eager_freeze_failure_rate", RELOPT_TYPE_REAL,
offsetof(StdRdOptions, vacuum_max_eager_freeze_failure_rate)}
};
@@ -2004,7 +2090,6 @@ build_local_reloptions(local_relopts *relopts, Datum options, bool validate)
elems[i].optname = opt->option->name;
elems[i].opttype = opt->option->type;
elems[i].offset = opt->offset;
- elems[i].isset_offset = 0; /* not supported for local relopts yet */
i++;
}
diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c
index aa4fbec143f..696eab9bd97 100644
--- a/src/backend/commands/vacuum.c
+++ b/src/backend/commands/vacuum.c
@@ -2224,9 +2224,9 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams params,
{
StdRdOptions *opts = (StdRdOptions *) rel->rd_options;
- if (opts && opts->vacuum_truncate_set)
+ if (opts && opts->vacuum_truncate != TERNARY_UNSET)
{
- if (opts->vacuum_truncate)
+ if (opts->vacuum_truncate == TERNARY_TRUE)
params.truncate = VACOPTVALUE_ENABLED;
else
params.truncate = VACOPTVALUE_DISABLED;
diff --git a/src/include/access/reloptions.h b/src/include/access/reloptions.h
index 2f08e1b0cf0..dfbef2babf2 100644
--- a/src/include/access/reloptions.h
+++ b/src/include/access/reloptions.h
@@ -29,6 +29,7 @@
typedef enum relopt_type
{
RELOPT_TYPE_BOOL,
+ RELOPT_TYPE_TERNARY, /* on, off, unset */
RELOPT_TYPE_INT,
RELOPT_TYPE_REAL,
RELOPT_TYPE_ENUM,
@@ -80,6 +81,7 @@ typedef struct relopt_value
union
{
bool bool_val;
+ pg_ternary ternary_val;
int int_val;
double real_val;
int enum_val;
@@ -94,6 +96,12 @@ typedef struct relopt_bool
bool default_val;
} relopt_bool;
+typedef struct relopt_ternary
+{
+ relopt_gen gen;
+ int default_val;
+} relopt_ternary;
+
typedef struct relopt_int
{
relopt_gen gen;
@@ -152,19 +160,6 @@ typedef struct
const char *optname; /* option's name */
relopt_type opttype; /* option's datatype */
int offset; /* offset of field in result struct */
-
- /*
- * isset_offset is an optional offset of a field in the result struct that
- * stores whether the option is explicitly set for the relation or if it
- * just picked up the default value. In most cases, this can be
- * accomplished by giving the reloption a special out-of-range default
- * value (e.g., some integer reloptions use -2), but this isn't always
- * possible. For example, a Boolean reloption cannot be given an
- * out-of-range default, so we need another way to discover the source of
- * its value. This offset is only used if given a value greater than
- * zero.
- */
- int isset_offset;
} relopt_parse_elt;
/* Local reloption definition */
@@ -195,6 +190,9 @@ typedef struct local_relopts
extern relopt_kind add_reloption_kind(void);
extern void add_bool_reloption(bits32 kinds, const char *name, const char *desc,
bool default_val, LOCKMODE lockmode);
+extern void add_ternary_reloption(bits32 kinds, const char *name,
+ const char *desc, pg_ternary default_val,
+ LOCKMODE lockmode);
extern void add_int_reloption(bits32 kinds, const char *name, const char *desc,
int default_val, int min_val, int max_val,
LOCKMODE lockmode);
@@ -214,6 +212,9 @@ extern void register_reloptions_validator(local_relopts *relopts,
extern void add_local_bool_reloption(local_relopts *relopts, const char *name,
const char *desc, bool default_val,
int offset);
+extern void add_local_ternary_reloption(local_relopts *relopts,
+ const char *name, const char *desc,
+ pg_ternary default_val, int offset);
extern void add_local_int_reloption(local_relopts *relopts, const char *name,
const char *desc, int default_val,
int min_val, int max_val, int offset);
diff --git a/src/include/postgres.h b/src/include/postgres.h
index 7d93fbce709..47c88780776 100644
--- a/src/include/postgres.h
+++ b/src/include/postgres.h
@@ -543,6 +543,21 @@ Float8GetDatum(float8 X)
* ----------------------------------------------------------------
*/
+/*
+ * pg_ternary
+ * Boolean value with an extra "unset" value
+ *
+ * This enum can be used for values that want to distinguish between true,
+ * false, and unset.
+*/
+
+typedef enum pg_ternary
+{
+ TERNARY_FALSE = 0,
+ TERNARY_TRUE = 1,
+ TERNARY_UNSET = -1
+} pg_ternary;
+
/*
* NON_EXEC_STATIC: It's sometimes useful to define a variable or function
* that is normally static but extern when using EXEC_BACKEND (see
diff --git a/src/include/utils/rel.h b/src/include/utils/rel.h
index d03ab247788..5feb18a5373 100644
--- a/src/include/utils/rel.h
+++ b/src/include/utils/rel.h
@@ -347,8 +347,7 @@ typedef struct StdRdOptions
bool user_catalog_table; /* use as an additional catalog relation */
int parallel_workers; /* max number of parallel workers */
StdRdOptIndexCleanup vacuum_index_cleanup; /* controls index vacuuming */
- bool vacuum_truncate; /* enables vacuum to truncate a relation */
- bool vacuum_truncate_set; /* whether vacuum_truncate is set */
+ pg_ternary vacuum_truncate; /* enables vacuum to truncate a relation */
/*
* Fraction of pages in a relation that vacuum can eagerly scan and fail
diff --git a/src/test/regress/expected/reloptions.out b/src/test/regress/expected/reloptions.out
index 9de19b4e3f1..1c99f79ab01 100644
--- a/src/test/regress/expected/reloptions.out
+++ b/src/test/regress/expected/reloptions.out
@@ -98,6 +98,42 @@ SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass;
{fillfactor=13,autovacuum_enabled=false}
(1 row)
+-- Tests for future (FIXME) ternary options
+-- behave as boolean option: accept unassigned name and truncated value
+DROP TABLE reloptions_test;
+CREATE TABLE reloptions_test(i INT) WITH (vacuum_truncate);
+SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass;
+ reloptions
+------------------------
+ {vacuum_truncate=true}
+(1 row)
+
+DROP TABLE reloptions_test;
+CREATE TABLE reloptions_test(i INT) WITH (vacuum_truncate=FaLS);
+SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass;
+ reloptions
+------------------------
+ {vacuum_truncate=fals}
+(1 row)
+
+-- preferred "true" alias is used when storing
+DROP TABLE reloptions_test;
+CREATE TABLE reloptions_test(i INT) WITH (vacuum_index_cleanup=on);
+SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass;
+ reloptions
+---------------------------
+ {vacuum_index_cleanup=on}
+(1 row)
+
+-- custom "third" value is available
+DROP TABLE reloptions_test;
+CREATE TABLE reloptions_test(i INT) WITH (vacuum_index_cleanup=auto);
+SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass;
+ reloptions
+-----------------------------
+ {vacuum_index_cleanup=auto}
+(1 row)
+
-- Test vacuum_truncate option
DROP TABLE reloptions_test;
CREATE TEMP TABLE reloptions_test(i INT NOT NULL, j text)
diff --git a/src/test/regress/sql/reloptions.sql b/src/test/regress/sql/reloptions.sql
index 24fbe0b478d..f5980dafcbc 100644
--- a/src/test/regress/sql/reloptions.sql
+++ b/src/test/regress/sql/reloptions.sql
@@ -59,6 +59,27 @@ UPDATE pg_class
ALTER TABLE reloptions_test RESET (illegal_option);
SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass;
+-- Tests for future (FIXME) ternary options
+
+-- behave as boolean option: accept unassigned name and truncated value
+DROP TABLE reloptions_test;
+CREATE TABLE reloptions_test(i INT) WITH (vacuum_truncate);
+SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass;
+
+DROP TABLE reloptions_test;
+CREATE TABLE reloptions_test(i INT) WITH (vacuum_truncate=FaLS);
+SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass;
+
+-- preferred "true" alias is used when storing
+DROP TABLE reloptions_test;
+CREATE TABLE reloptions_test(i INT) WITH (vacuum_index_cleanup=on);
+SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass;
+
+-- custom "third" value is available
+DROP TABLE reloptions_test;
+CREATE TABLE reloptions_test(i INT) WITH (vacuum_index_cleanup=auto);
+SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass;
+
-- Test vacuum_truncate option
DROP TABLE reloptions_test;
diff --git a/src/tools/pgindent/typedefs.list b/src/tools/pgindent/typedefs.list
index 3f3a888fd0e..1c8610fd46c 100644
--- a/src/tools/pgindent/typedefs.list
+++ b/src/tools/pgindent/typedefs.list
@@ -3952,6 +3952,7 @@ pg_sha512_ctx
pg_snapshot
pg_special_case
pg_stack_base_t
+pg_ternary
pg_time_t
pg_time_usec_t
pg_tz
@@ -4079,6 +4080,7 @@ relopt_kind
relopt_parse_elt
relopt_real
relopt_string
+relopt_ternary
relopt_type
relopt_value
relopts_validator
--
2.47.3
--c36t3fndxhkzbl5z--
^ permalink raw reply [nested|flat] 868+ messages in thread
* [PATCH v3] Introduce ternary reloptions
@ 2026-01-16 15:04 Álvaro Herrera <[email protected]>
0 siblings, 0 replies; 868+ messages in thread
From: Álvaro Herrera @ 2026-01-16 15:04 UTC (permalink / raw)
Introduce ternary reloption as a replacement for current
`vacuum_truncate` implementation. Remove the `vacuum_truncate_set`
separate flag and use TERNARY_UNSET instead.
This could also be used for other options such as `vacuum_index_cleanup`
and `buffering`, but lets get the scaffolding in first.
Discussion: https://postgr.es/m/3474141.usfYGdeWWP@thinkpad-pgpro
---
src/backend/access/common/reloptions.c | 137 ++++++++++++++++++-----
src/backend/commands/vacuum.c | 4 +-
src/include/access/reloptions.h | 27 ++---
src/include/postgres.h | 15 +++
src/include/utils/rel.h | 3 +-
src/test/regress/expected/reloptions.out | 36 ++++++
src/test/regress/sql/reloptions.sql | 21 ++++
src/tools/pgindent/typedefs.list | 2 +
8 files changed, 202 insertions(+), 43 deletions(-)
diff --git a/src/backend/access/common/reloptions.c b/src/backend/access/common/reloptions.c
index 0b83f98ed5f..6f1f577581a 100644
--- a/src/backend/access/common/reloptions.c
+++ b/src/backend/access/common/reloptions.c
@@ -40,9 +40,9 @@
*
* To add an option:
*
- * (i) decide on a type (bool, integer, real, enum, string), name, default
- * value, upper and lower bounds (if applicable); for strings, consider a
- * validation routine.
+ * (i) decide on a type (bool, ternary, integer, real, enum, string), name,
+ * default value, upper and lower bounds (if applicable); for strings,
+ * consider a validation routine.
* (ii) add a record below (or use add_<type>_reloption).
* (iii) add it to the appropriate options struct (perhaps StdRdOptions)
* (iv) add it to the appropriate handling routine (perhaps
@@ -147,15 +147,6 @@ static relopt_bool boolRelOpts[] =
},
false
},
- {
- {
- "vacuum_truncate",
- "Enables vacuum to truncate empty pages at the end of this table",
- RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
- ShareUpdateExclusiveLock
- },
- true
- },
{
{
"deduplicate_items",
@@ -170,6 +161,25 @@ static relopt_bool boolRelOpts[] =
{{NULL}}
};
+static relopt_ternary ternaryRelOpts[] =
+{
+ {
+ {
+ "vacuum_truncate",
+ "Enables vacuum to truncate empty pages at the end of this table",
+ RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
+ ShareUpdateExclusiveLock
+ },
+ TERNARY_UNSET
+ },
+ /* list terminator */
+ {
+ {
+ NULL
+ }
+ }
+};
+
static relopt_int intRelOpts[] =
{
{
@@ -609,6 +619,13 @@ initialize_reloptions(void)
boolRelOpts[i].gen.lockmode));
j++;
}
+ for (i = 0; ternaryRelOpts[i].gen.name; i++)
+ {
+ Assert(DoLockModesConflict(ternaryRelOpts[i].gen.lockmode,
+ ternaryRelOpts[i].gen.lockmode));
+ j++;
+ }
+
for (i = 0; intRelOpts[i].gen.name; i++)
{
Assert(DoLockModesConflict(intRelOpts[i].gen.lockmode,
@@ -649,6 +666,14 @@ initialize_reloptions(void)
j++;
}
+ for (i = 0; ternaryRelOpts[i].gen.name; i++)
+ {
+ relOpts[j] = &ternaryRelOpts[i].gen;
+ relOpts[j]->type = RELOPT_TYPE_TERNARY;
+ relOpts[j]->namelen = strlen(relOpts[j]->name);
+ j++;
+ }
+
for (i = 0; intRelOpts[i].gen.name; i++)
{
relOpts[j] = &intRelOpts[i].gen;
@@ -809,6 +834,9 @@ allocate_reloption(bits32 kinds, int type, const char *name, const char *desc,
case RELOPT_TYPE_BOOL:
size = sizeof(relopt_bool);
break;
+ case RELOPT_TYPE_TERNARY:
+ size = sizeof(relopt_ternary);
+ break;
case RELOPT_TYPE_INT:
size = sizeof(relopt_int);
break;
@@ -892,6 +920,57 @@ add_local_bool_reloption(local_relopts *relopts, const char *name,
add_local_reloption(relopts, (relopt_gen *) newoption, offset);
}
+/*
+ * init_ternary_reloption
+ * Allocate and initialize a new ternary reloption
+ */
+static relopt_ternary *
+init_ternary_reloption(bits32 kinds, const char *name, const char *desc,
+ pg_ternary default_val, LOCKMODE lockmode)
+{
+ relopt_ternary *newoption;
+
+ newoption = (relopt_ternary *)
+ allocate_reloption(kinds, RELOPT_TYPE_TERNARY, name, desc, lockmode);
+ newoption->default_val = default_val;
+
+ return newoption;
+}
+
+/*
+ * add_ternary_reloption
+ * Add a new ternary reloption
+ */
+void
+add_ternary_reloption(bits32 kinds, const char *name, const char *desc,
+ pg_ternary default_val, LOCKMODE lockmode)
+{
+ relopt_ternary *newoption;
+
+ newoption =
+ init_ternary_reloption(kinds, name, desc, default_val, lockmode);
+
+ add_reloption((relopt_gen *) newoption);
+}
+
+/*
+ * add_local_ternary_reloption
+ * Add a new ternary local reloption
+ *
+ * 'offset' is offset of ternary-typed field.
+ */
+void
+add_local_ternary_reloption(local_relopts *relopts, const char *name,
+ const char *desc, pg_ternary default_val,
+ int offset)
+{
+ relopt_ternary *newoption;
+
+ newoption =
+ init_ternary_reloption(RELOPT_KIND_LOCAL, name, desc, default_val, 0);
+
+ add_local_reloption(relopts, (relopt_gen *) newoption, offset);
+}
/*
* init_real_reloption
@@ -1626,6 +1705,19 @@ parse_one_reloption(relopt_value *option, char *text_str, int text_len,
option->gen->name, value)));
}
break;
+ case RELOPT_TYPE_TERNARY:
+ {
+ bool b;
+
+ parsed = parse_bool(value, &b);
+ option->values.ternary_val = b ? TERNARY_TRUE : TERNARY_FALSE;
+ if (validate && !parsed)
+ ereport(ERROR,
+ (errcode(ERRCODE_INVALID_PARAMETER_VALUE),
+ errmsg("invalid value for ternary option \"%s\": %s",
+ option->gen->name, value)));
+ }
+ break;
case RELOPT_TYPE_INT:
{
relopt_int *optint = (relopt_int *) option->gen;
@@ -1789,17 +1881,6 @@ fillRelOptions(void *rdopts, Size basesize,
char *itempos = ((char *) rdopts) + elems[j].offset;
char *string_val;
- /*
- * If isset_offset is provided, store whether the reloption is
- * set there.
- */
- if (elems[j].isset_offset > 0)
- {
- char *setpos = ((char *) rdopts) + elems[j].isset_offset;
-
- *(bool *) setpos = options[i].isset;
- }
-
switch (options[i].gen->type)
{
case RELOPT_TYPE_BOOL:
@@ -1807,6 +1888,11 @@ fillRelOptions(void *rdopts, Size basesize,
options[i].values.bool_val :
((relopt_bool *) options[i].gen)->default_val;
break;
+ case RELOPT_TYPE_TERNARY:
+ *(pg_ternary *) itempos = options[i].isset ?
+ options[i].values.ternary_val :
+ ((relopt_ternary *) options[i].gen)->default_val;
+ break;
case RELOPT_TYPE_INT:
*(int *) itempos = options[i].isset ?
options[i].values.int_val :
@@ -1923,8 +2009,8 @@ default_reloptions(Datum reloptions, bool validate, relopt_kind kind)
offsetof(StdRdOptions, parallel_workers)},
{"vacuum_index_cleanup", RELOPT_TYPE_ENUM,
offsetof(StdRdOptions, vacuum_index_cleanup)},
- {"vacuum_truncate", RELOPT_TYPE_BOOL,
- offsetof(StdRdOptions, vacuum_truncate), offsetof(StdRdOptions, vacuum_truncate_set)},
+ {"vacuum_truncate", RELOPT_TYPE_TERNARY,
+ offsetof(StdRdOptions, vacuum_truncate)},
{"vacuum_max_eager_freeze_failure_rate", RELOPT_TYPE_REAL,
offsetof(StdRdOptions, vacuum_max_eager_freeze_failure_rate)}
};
@@ -2004,7 +2090,6 @@ build_local_reloptions(local_relopts *relopts, Datum options, bool validate)
elems[i].optname = opt->option->name;
elems[i].opttype = opt->option->type;
elems[i].offset = opt->offset;
- elems[i].isset_offset = 0; /* not supported for local relopts yet */
i++;
}
diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c
index aa4fbec143f..696eab9bd97 100644
--- a/src/backend/commands/vacuum.c
+++ b/src/backend/commands/vacuum.c
@@ -2224,9 +2224,9 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams params,
{
StdRdOptions *opts = (StdRdOptions *) rel->rd_options;
- if (opts && opts->vacuum_truncate_set)
+ if (opts && opts->vacuum_truncate != TERNARY_UNSET)
{
- if (opts->vacuum_truncate)
+ if (opts->vacuum_truncate == TERNARY_TRUE)
params.truncate = VACOPTVALUE_ENABLED;
else
params.truncate = VACOPTVALUE_DISABLED;
diff --git a/src/include/access/reloptions.h b/src/include/access/reloptions.h
index 2f08e1b0cf0..dfbef2babf2 100644
--- a/src/include/access/reloptions.h
+++ b/src/include/access/reloptions.h
@@ -29,6 +29,7 @@
typedef enum relopt_type
{
RELOPT_TYPE_BOOL,
+ RELOPT_TYPE_TERNARY, /* on, off, unset */
RELOPT_TYPE_INT,
RELOPT_TYPE_REAL,
RELOPT_TYPE_ENUM,
@@ -80,6 +81,7 @@ typedef struct relopt_value
union
{
bool bool_val;
+ pg_ternary ternary_val;
int int_val;
double real_val;
int enum_val;
@@ -94,6 +96,12 @@ typedef struct relopt_bool
bool default_val;
} relopt_bool;
+typedef struct relopt_ternary
+{
+ relopt_gen gen;
+ int default_val;
+} relopt_ternary;
+
typedef struct relopt_int
{
relopt_gen gen;
@@ -152,19 +160,6 @@ typedef struct
const char *optname; /* option's name */
relopt_type opttype; /* option's datatype */
int offset; /* offset of field in result struct */
-
- /*
- * isset_offset is an optional offset of a field in the result struct that
- * stores whether the option is explicitly set for the relation or if it
- * just picked up the default value. In most cases, this can be
- * accomplished by giving the reloption a special out-of-range default
- * value (e.g., some integer reloptions use -2), but this isn't always
- * possible. For example, a Boolean reloption cannot be given an
- * out-of-range default, so we need another way to discover the source of
- * its value. This offset is only used if given a value greater than
- * zero.
- */
- int isset_offset;
} relopt_parse_elt;
/* Local reloption definition */
@@ -195,6 +190,9 @@ typedef struct local_relopts
extern relopt_kind add_reloption_kind(void);
extern void add_bool_reloption(bits32 kinds, const char *name, const char *desc,
bool default_val, LOCKMODE lockmode);
+extern void add_ternary_reloption(bits32 kinds, const char *name,
+ const char *desc, pg_ternary default_val,
+ LOCKMODE lockmode);
extern void add_int_reloption(bits32 kinds, const char *name, const char *desc,
int default_val, int min_val, int max_val,
LOCKMODE lockmode);
@@ -214,6 +212,9 @@ extern void register_reloptions_validator(local_relopts *relopts,
extern void add_local_bool_reloption(local_relopts *relopts, const char *name,
const char *desc, bool default_val,
int offset);
+extern void add_local_ternary_reloption(local_relopts *relopts,
+ const char *name, const char *desc,
+ pg_ternary default_val, int offset);
extern void add_local_int_reloption(local_relopts *relopts, const char *name,
const char *desc, int default_val,
int min_val, int max_val, int offset);
diff --git a/src/include/postgres.h b/src/include/postgres.h
index 7d93fbce709..47c88780776 100644
--- a/src/include/postgres.h
+++ b/src/include/postgres.h
@@ -543,6 +543,21 @@ Float8GetDatum(float8 X)
* ----------------------------------------------------------------
*/
+/*
+ * pg_ternary
+ * Boolean value with an extra "unset" value
+ *
+ * This enum can be used for values that want to distinguish between true,
+ * false, and unset.
+*/
+
+typedef enum pg_ternary
+{
+ TERNARY_FALSE = 0,
+ TERNARY_TRUE = 1,
+ TERNARY_UNSET = -1
+} pg_ternary;
+
/*
* NON_EXEC_STATIC: It's sometimes useful to define a variable or function
* that is normally static but extern when using EXEC_BACKEND (see
diff --git a/src/include/utils/rel.h b/src/include/utils/rel.h
index d03ab247788..5feb18a5373 100644
--- a/src/include/utils/rel.h
+++ b/src/include/utils/rel.h
@@ -347,8 +347,7 @@ typedef struct StdRdOptions
bool user_catalog_table; /* use as an additional catalog relation */
int parallel_workers; /* max number of parallel workers */
StdRdOptIndexCleanup vacuum_index_cleanup; /* controls index vacuuming */
- bool vacuum_truncate; /* enables vacuum to truncate a relation */
- bool vacuum_truncate_set; /* whether vacuum_truncate is set */
+ pg_ternary vacuum_truncate; /* enables vacuum to truncate a relation */
/*
* Fraction of pages in a relation that vacuum can eagerly scan and fail
diff --git a/src/test/regress/expected/reloptions.out b/src/test/regress/expected/reloptions.out
index 9de19b4e3f1..1c99f79ab01 100644
--- a/src/test/regress/expected/reloptions.out
+++ b/src/test/regress/expected/reloptions.out
@@ -98,6 +98,42 @@ SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass;
{fillfactor=13,autovacuum_enabled=false}
(1 row)
+-- Tests for future (FIXME) ternary options
+-- behave as boolean option: accept unassigned name and truncated value
+DROP TABLE reloptions_test;
+CREATE TABLE reloptions_test(i INT) WITH (vacuum_truncate);
+SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass;
+ reloptions
+------------------------
+ {vacuum_truncate=true}
+(1 row)
+
+DROP TABLE reloptions_test;
+CREATE TABLE reloptions_test(i INT) WITH (vacuum_truncate=FaLS);
+SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass;
+ reloptions
+------------------------
+ {vacuum_truncate=fals}
+(1 row)
+
+-- preferred "true" alias is used when storing
+DROP TABLE reloptions_test;
+CREATE TABLE reloptions_test(i INT) WITH (vacuum_index_cleanup=on);
+SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass;
+ reloptions
+---------------------------
+ {vacuum_index_cleanup=on}
+(1 row)
+
+-- custom "third" value is available
+DROP TABLE reloptions_test;
+CREATE TABLE reloptions_test(i INT) WITH (vacuum_index_cleanup=auto);
+SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass;
+ reloptions
+-----------------------------
+ {vacuum_index_cleanup=auto}
+(1 row)
+
-- Test vacuum_truncate option
DROP TABLE reloptions_test;
CREATE TEMP TABLE reloptions_test(i INT NOT NULL, j text)
diff --git a/src/test/regress/sql/reloptions.sql b/src/test/regress/sql/reloptions.sql
index 24fbe0b478d..f5980dafcbc 100644
--- a/src/test/regress/sql/reloptions.sql
+++ b/src/test/regress/sql/reloptions.sql
@@ -59,6 +59,27 @@ UPDATE pg_class
ALTER TABLE reloptions_test RESET (illegal_option);
SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass;
+-- Tests for future (FIXME) ternary options
+
+-- behave as boolean option: accept unassigned name and truncated value
+DROP TABLE reloptions_test;
+CREATE TABLE reloptions_test(i INT) WITH (vacuum_truncate);
+SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass;
+
+DROP TABLE reloptions_test;
+CREATE TABLE reloptions_test(i INT) WITH (vacuum_truncate=FaLS);
+SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass;
+
+-- preferred "true" alias is used when storing
+DROP TABLE reloptions_test;
+CREATE TABLE reloptions_test(i INT) WITH (vacuum_index_cleanup=on);
+SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass;
+
+-- custom "third" value is available
+DROP TABLE reloptions_test;
+CREATE TABLE reloptions_test(i INT) WITH (vacuum_index_cleanup=auto);
+SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass;
+
-- Test vacuum_truncate option
DROP TABLE reloptions_test;
diff --git a/src/tools/pgindent/typedefs.list b/src/tools/pgindent/typedefs.list
index 3f3a888fd0e..1c8610fd46c 100644
--- a/src/tools/pgindent/typedefs.list
+++ b/src/tools/pgindent/typedefs.list
@@ -3952,6 +3952,7 @@ pg_sha512_ctx
pg_snapshot
pg_special_case
pg_stack_base_t
+pg_ternary
pg_time_t
pg_time_usec_t
pg_tz
@@ -4079,6 +4080,7 @@ relopt_kind
relopt_parse_elt
relopt_real
relopt_string
+relopt_ternary
relopt_type
relopt_value
relopts_validator
--
2.47.3
--c36t3fndxhkzbl5z--
^ permalink raw reply [nested|flat] 868+ messages in thread
* [PATCH v3] Introduce ternary reloptions
@ 2026-01-16 15:04 Álvaro Herrera <[email protected]>
0 siblings, 0 replies; 868+ messages in thread
From: Álvaro Herrera @ 2026-01-16 15:04 UTC (permalink / raw)
Introduce ternary reloption as a replacement for current
`vacuum_truncate` implementation. Remove the `vacuum_truncate_set`
separate flag and use TERNARY_UNSET instead.
This could also be used for other options such as `vacuum_index_cleanup`
and `buffering`, but lets get the scaffolding in first.
Discussion: https://postgr.es/m/3474141.usfYGdeWWP@thinkpad-pgpro
---
src/backend/access/common/reloptions.c | 137 ++++++++++++++++++-----
src/backend/commands/vacuum.c | 4 +-
src/include/access/reloptions.h | 27 ++---
src/include/postgres.h | 15 +++
src/include/utils/rel.h | 3 +-
src/test/regress/expected/reloptions.out | 36 ++++++
src/test/regress/sql/reloptions.sql | 21 ++++
src/tools/pgindent/typedefs.list | 2 +
8 files changed, 202 insertions(+), 43 deletions(-)
diff --git a/src/backend/access/common/reloptions.c b/src/backend/access/common/reloptions.c
index 0b83f98ed5f..6f1f577581a 100644
--- a/src/backend/access/common/reloptions.c
+++ b/src/backend/access/common/reloptions.c
@@ -40,9 +40,9 @@
*
* To add an option:
*
- * (i) decide on a type (bool, integer, real, enum, string), name, default
- * value, upper and lower bounds (if applicable); for strings, consider a
- * validation routine.
+ * (i) decide on a type (bool, ternary, integer, real, enum, string), name,
+ * default value, upper and lower bounds (if applicable); for strings,
+ * consider a validation routine.
* (ii) add a record below (or use add_<type>_reloption).
* (iii) add it to the appropriate options struct (perhaps StdRdOptions)
* (iv) add it to the appropriate handling routine (perhaps
@@ -147,15 +147,6 @@ static relopt_bool boolRelOpts[] =
},
false
},
- {
- {
- "vacuum_truncate",
- "Enables vacuum to truncate empty pages at the end of this table",
- RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
- ShareUpdateExclusiveLock
- },
- true
- },
{
{
"deduplicate_items",
@@ -170,6 +161,25 @@ static relopt_bool boolRelOpts[] =
{{NULL}}
};
+static relopt_ternary ternaryRelOpts[] =
+{
+ {
+ {
+ "vacuum_truncate",
+ "Enables vacuum to truncate empty pages at the end of this table",
+ RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
+ ShareUpdateExclusiveLock
+ },
+ TERNARY_UNSET
+ },
+ /* list terminator */
+ {
+ {
+ NULL
+ }
+ }
+};
+
static relopt_int intRelOpts[] =
{
{
@@ -609,6 +619,13 @@ initialize_reloptions(void)
boolRelOpts[i].gen.lockmode));
j++;
}
+ for (i = 0; ternaryRelOpts[i].gen.name; i++)
+ {
+ Assert(DoLockModesConflict(ternaryRelOpts[i].gen.lockmode,
+ ternaryRelOpts[i].gen.lockmode));
+ j++;
+ }
+
for (i = 0; intRelOpts[i].gen.name; i++)
{
Assert(DoLockModesConflict(intRelOpts[i].gen.lockmode,
@@ -649,6 +666,14 @@ initialize_reloptions(void)
j++;
}
+ for (i = 0; ternaryRelOpts[i].gen.name; i++)
+ {
+ relOpts[j] = &ternaryRelOpts[i].gen;
+ relOpts[j]->type = RELOPT_TYPE_TERNARY;
+ relOpts[j]->namelen = strlen(relOpts[j]->name);
+ j++;
+ }
+
for (i = 0; intRelOpts[i].gen.name; i++)
{
relOpts[j] = &intRelOpts[i].gen;
@@ -809,6 +834,9 @@ allocate_reloption(bits32 kinds, int type, const char *name, const char *desc,
case RELOPT_TYPE_BOOL:
size = sizeof(relopt_bool);
break;
+ case RELOPT_TYPE_TERNARY:
+ size = sizeof(relopt_ternary);
+ break;
case RELOPT_TYPE_INT:
size = sizeof(relopt_int);
break;
@@ -892,6 +920,57 @@ add_local_bool_reloption(local_relopts *relopts, const char *name,
add_local_reloption(relopts, (relopt_gen *) newoption, offset);
}
+/*
+ * init_ternary_reloption
+ * Allocate and initialize a new ternary reloption
+ */
+static relopt_ternary *
+init_ternary_reloption(bits32 kinds, const char *name, const char *desc,
+ pg_ternary default_val, LOCKMODE lockmode)
+{
+ relopt_ternary *newoption;
+
+ newoption = (relopt_ternary *)
+ allocate_reloption(kinds, RELOPT_TYPE_TERNARY, name, desc, lockmode);
+ newoption->default_val = default_val;
+
+ return newoption;
+}
+
+/*
+ * add_ternary_reloption
+ * Add a new ternary reloption
+ */
+void
+add_ternary_reloption(bits32 kinds, const char *name, const char *desc,
+ pg_ternary default_val, LOCKMODE lockmode)
+{
+ relopt_ternary *newoption;
+
+ newoption =
+ init_ternary_reloption(kinds, name, desc, default_val, lockmode);
+
+ add_reloption((relopt_gen *) newoption);
+}
+
+/*
+ * add_local_ternary_reloption
+ * Add a new ternary local reloption
+ *
+ * 'offset' is offset of ternary-typed field.
+ */
+void
+add_local_ternary_reloption(local_relopts *relopts, const char *name,
+ const char *desc, pg_ternary default_val,
+ int offset)
+{
+ relopt_ternary *newoption;
+
+ newoption =
+ init_ternary_reloption(RELOPT_KIND_LOCAL, name, desc, default_val, 0);
+
+ add_local_reloption(relopts, (relopt_gen *) newoption, offset);
+}
/*
* init_real_reloption
@@ -1626,6 +1705,19 @@ parse_one_reloption(relopt_value *option, char *text_str, int text_len,
option->gen->name, value)));
}
break;
+ case RELOPT_TYPE_TERNARY:
+ {
+ bool b;
+
+ parsed = parse_bool(value, &b);
+ option->values.ternary_val = b ? TERNARY_TRUE : TERNARY_FALSE;
+ if (validate && !parsed)
+ ereport(ERROR,
+ (errcode(ERRCODE_INVALID_PARAMETER_VALUE),
+ errmsg("invalid value for ternary option \"%s\": %s",
+ option->gen->name, value)));
+ }
+ break;
case RELOPT_TYPE_INT:
{
relopt_int *optint = (relopt_int *) option->gen;
@@ -1789,17 +1881,6 @@ fillRelOptions(void *rdopts, Size basesize,
char *itempos = ((char *) rdopts) + elems[j].offset;
char *string_val;
- /*
- * If isset_offset is provided, store whether the reloption is
- * set there.
- */
- if (elems[j].isset_offset > 0)
- {
- char *setpos = ((char *) rdopts) + elems[j].isset_offset;
-
- *(bool *) setpos = options[i].isset;
- }
-
switch (options[i].gen->type)
{
case RELOPT_TYPE_BOOL:
@@ -1807,6 +1888,11 @@ fillRelOptions(void *rdopts, Size basesize,
options[i].values.bool_val :
((relopt_bool *) options[i].gen)->default_val;
break;
+ case RELOPT_TYPE_TERNARY:
+ *(pg_ternary *) itempos = options[i].isset ?
+ options[i].values.ternary_val :
+ ((relopt_ternary *) options[i].gen)->default_val;
+ break;
case RELOPT_TYPE_INT:
*(int *) itempos = options[i].isset ?
options[i].values.int_val :
@@ -1923,8 +2009,8 @@ default_reloptions(Datum reloptions, bool validate, relopt_kind kind)
offsetof(StdRdOptions, parallel_workers)},
{"vacuum_index_cleanup", RELOPT_TYPE_ENUM,
offsetof(StdRdOptions, vacuum_index_cleanup)},
- {"vacuum_truncate", RELOPT_TYPE_BOOL,
- offsetof(StdRdOptions, vacuum_truncate), offsetof(StdRdOptions, vacuum_truncate_set)},
+ {"vacuum_truncate", RELOPT_TYPE_TERNARY,
+ offsetof(StdRdOptions, vacuum_truncate)},
{"vacuum_max_eager_freeze_failure_rate", RELOPT_TYPE_REAL,
offsetof(StdRdOptions, vacuum_max_eager_freeze_failure_rate)}
};
@@ -2004,7 +2090,6 @@ build_local_reloptions(local_relopts *relopts, Datum options, bool validate)
elems[i].optname = opt->option->name;
elems[i].opttype = opt->option->type;
elems[i].offset = opt->offset;
- elems[i].isset_offset = 0; /* not supported for local relopts yet */
i++;
}
diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c
index aa4fbec143f..696eab9bd97 100644
--- a/src/backend/commands/vacuum.c
+++ b/src/backend/commands/vacuum.c
@@ -2224,9 +2224,9 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams params,
{
StdRdOptions *opts = (StdRdOptions *) rel->rd_options;
- if (opts && opts->vacuum_truncate_set)
+ if (opts && opts->vacuum_truncate != TERNARY_UNSET)
{
- if (opts->vacuum_truncate)
+ if (opts->vacuum_truncate == TERNARY_TRUE)
params.truncate = VACOPTVALUE_ENABLED;
else
params.truncate = VACOPTVALUE_DISABLED;
diff --git a/src/include/access/reloptions.h b/src/include/access/reloptions.h
index 2f08e1b0cf0..dfbef2babf2 100644
--- a/src/include/access/reloptions.h
+++ b/src/include/access/reloptions.h
@@ -29,6 +29,7 @@
typedef enum relopt_type
{
RELOPT_TYPE_BOOL,
+ RELOPT_TYPE_TERNARY, /* on, off, unset */
RELOPT_TYPE_INT,
RELOPT_TYPE_REAL,
RELOPT_TYPE_ENUM,
@@ -80,6 +81,7 @@ typedef struct relopt_value
union
{
bool bool_val;
+ pg_ternary ternary_val;
int int_val;
double real_val;
int enum_val;
@@ -94,6 +96,12 @@ typedef struct relopt_bool
bool default_val;
} relopt_bool;
+typedef struct relopt_ternary
+{
+ relopt_gen gen;
+ int default_val;
+} relopt_ternary;
+
typedef struct relopt_int
{
relopt_gen gen;
@@ -152,19 +160,6 @@ typedef struct
const char *optname; /* option's name */
relopt_type opttype; /* option's datatype */
int offset; /* offset of field in result struct */
-
- /*
- * isset_offset is an optional offset of a field in the result struct that
- * stores whether the option is explicitly set for the relation or if it
- * just picked up the default value. In most cases, this can be
- * accomplished by giving the reloption a special out-of-range default
- * value (e.g., some integer reloptions use -2), but this isn't always
- * possible. For example, a Boolean reloption cannot be given an
- * out-of-range default, so we need another way to discover the source of
- * its value. This offset is only used if given a value greater than
- * zero.
- */
- int isset_offset;
} relopt_parse_elt;
/* Local reloption definition */
@@ -195,6 +190,9 @@ typedef struct local_relopts
extern relopt_kind add_reloption_kind(void);
extern void add_bool_reloption(bits32 kinds, const char *name, const char *desc,
bool default_val, LOCKMODE lockmode);
+extern void add_ternary_reloption(bits32 kinds, const char *name,
+ const char *desc, pg_ternary default_val,
+ LOCKMODE lockmode);
extern void add_int_reloption(bits32 kinds, const char *name, const char *desc,
int default_val, int min_val, int max_val,
LOCKMODE lockmode);
@@ -214,6 +212,9 @@ extern void register_reloptions_validator(local_relopts *relopts,
extern void add_local_bool_reloption(local_relopts *relopts, const char *name,
const char *desc, bool default_val,
int offset);
+extern void add_local_ternary_reloption(local_relopts *relopts,
+ const char *name, const char *desc,
+ pg_ternary default_val, int offset);
extern void add_local_int_reloption(local_relopts *relopts, const char *name,
const char *desc, int default_val,
int min_val, int max_val, int offset);
diff --git a/src/include/postgres.h b/src/include/postgres.h
index 7d93fbce709..47c88780776 100644
--- a/src/include/postgres.h
+++ b/src/include/postgres.h
@@ -543,6 +543,21 @@ Float8GetDatum(float8 X)
* ----------------------------------------------------------------
*/
+/*
+ * pg_ternary
+ * Boolean value with an extra "unset" value
+ *
+ * This enum can be used for values that want to distinguish between true,
+ * false, and unset.
+*/
+
+typedef enum pg_ternary
+{
+ TERNARY_FALSE = 0,
+ TERNARY_TRUE = 1,
+ TERNARY_UNSET = -1
+} pg_ternary;
+
/*
* NON_EXEC_STATIC: It's sometimes useful to define a variable or function
* that is normally static but extern when using EXEC_BACKEND (see
diff --git a/src/include/utils/rel.h b/src/include/utils/rel.h
index d03ab247788..5feb18a5373 100644
--- a/src/include/utils/rel.h
+++ b/src/include/utils/rel.h
@@ -347,8 +347,7 @@ typedef struct StdRdOptions
bool user_catalog_table; /* use as an additional catalog relation */
int parallel_workers; /* max number of parallel workers */
StdRdOptIndexCleanup vacuum_index_cleanup; /* controls index vacuuming */
- bool vacuum_truncate; /* enables vacuum to truncate a relation */
- bool vacuum_truncate_set; /* whether vacuum_truncate is set */
+ pg_ternary vacuum_truncate; /* enables vacuum to truncate a relation */
/*
* Fraction of pages in a relation that vacuum can eagerly scan and fail
diff --git a/src/test/regress/expected/reloptions.out b/src/test/regress/expected/reloptions.out
index 9de19b4e3f1..1c99f79ab01 100644
--- a/src/test/regress/expected/reloptions.out
+++ b/src/test/regress/expected/reloptions.out
@@ -98,6 +98,42 @@ SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass;
{fillfactor=13,autovacuum_enabled=false}
(1 row)
+-- Tests for future (FIXME) ternary options
+-- behave as boolean option: accept unassigned name and truncated value
+DROP TABLE reloptions_test;
+CREATE TABLE reloptions_test(i INT) WITH (vacuum_truncate);
+SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass;
+ reloptions
+------------------------
+ {vacuum_truncate=true}
+(1 row)
+
+DROP TABLE reloptions_test;
+CREATE TABLE reloptions_test(i INT) WITH (vacuum_truncate=FaLS);
+SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass;
+ reloptions
+------------------------
+ {vacuum_truncate=fals}
+(1 row)
+
+-- preferred "true" alias is used when storing
+DROP TABLE reloptions_test;
+CREATE TABLE reloptions_test(i INT) WITH (vacuum_index_cleanup=on);
+SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass;
+ reloptions
+---------------------------
+ {vacuum_index_cleanup=on}
+(1 row)
+
+-- custom "third" value is available
+DROP TABLE reloptions_test;
+CREATE TABLE reloptions_test(i INT) WITH (vacuum_index_cleanup=auto);
+SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass;
+ reloptions
+-----------------------------
+ {vacuum_index_cleanup=auto}
+(1 row)
+
-- Test vacuum_truncate option
DROP TABLE reloptions_test;
CREATE TEMP TABLE reloptions_test(i INT NOT NULL, j text)
diff --git a/src/test/regress/sql/reloptions.sql b/src/test/regress/sql/reloptions.sql
index 24fbe0b478d..f5980dafcbc 100644
--- a/src/test/regress/sql/reloptions.sql
+++ b/src/test/regress/sql/reloptions.sql
@@ -59,6 +59,27 @@ UPDATE pg_class
ALTER TABLE reloptions_test RESET (illegal_option);
SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass;
+-- Tests for future (FIXME) ternary options
+
+-- behave as boolean option: accept unassigned name and truncated value
+DROP TABLE reloptions_test;
+CREATE TABLE reloptions_test(i INT) WITH (vacuum_truncate);
+SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass;
+
+DROP TABLE reloptions_test;
+CREATE TABLE reloptions_test(i INT) WITH (vacuum_truncate=FaLS);
+SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass;
+
+-- preferred "true" alias is used when storing
+DROP TABLE reloptions_test;
+CREATE TABLE reloptions_test(i INT) WITH (vacuum_index_cleanup=on);
+SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass;
+
+-- custom "third" value is available
+DROP TABLE reloptions_test;
+CREATE TABLE reloptions_test(i INT) WITH (vacuum_index_cleanup=auto);
+SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass;
+
-- Test vacuum_truncate option
DROP TABLE reloptions_test;
diff --git a/src/tools/pgindent/typedefs.list b/src/tools/pgindent/typedefs.list
index 3f3a888fd0e..1c8610fd46c 100644
--- a/src/tools/pgindent/typedefs.list
+++ b/src/tools/pgindent/typedefs.list
@@ -3952,6 +3952,7 @@ pg_sha512_ctx
pg_snapshot
pg_special_case
pg_stack_base_t
+pg_ternary
pg_time_t
pg_time_usec_t
pg_tz
@@ -4079,6 +4080,7 @@ relopt_kind
relopt_parse_elt
relopt_real
relopt_string
+relopt_ternary
relopt_type
relopt_value
relopts_validator
--
2.47.3
--c36t3fndxhkzbl5z--
^ permalink raw reply [nested|flat] 868+ messages in thread
* [PATCH v3] Introduce ternary reloptions
@ 2026-01-16 15:04 Álvaro Herrera <[email protected]>
0 siblings, 0 replies; 868+ messages in thread
From: Álvaro Herrera @ 2026-01-16 15:04 UTC (permalink / raw)
Introduce ternary reloption as a replacement for current
`vacuum_truncate` implementation. Remove the `vacuum_truncate_set`
separate flag and use TERNARY_UNSET instead.
This could also be used for other options such as `vacuum_index_cleanup`
and `buffering`, but lets get the scaffolding in first.
Discussion: https://postgr.es/m/3474141.usfYGdeWWP@thinkpad-pgpro
---
src/backend/access/common/reloptions.c | 137 ++++++++++++++++++-----
src/backend/commands/vacuum.c | 4 +-
src/include/access/reloptions.h | 27 ++---
src/include/postgres.h | 15 +++
src/include/utils/rel.h | 3 +-
src/test/regress/expected/reloptions.out | 36 ++++++
src/test/regress/sql/reloptions.sql | 21 ++++
src/tools/pgindent/typedefs.list | 2 +
8 files changed, 202 insertions(+), 43 deletions(-)
diff --git a/src/backend/access/common/reloptions.c b/src/backend/access/common/reloptions.c
index 0b83f98ed5f..6f1f577581a 100644
--- a/src/backend/access/common/reloptions.c
+++ b/src/backend/access/common/reloptions.c
@@ -40,9 +40,9 @@
*
* To add an option:
*
- * (i) decide on a type (bool, integer, real, enum, string), name, default
- * value, upper and lower bounds (if applicable); for strings, consider a
- * validation routine.
+ * (i) decide on a type (bool, ternary, integer, real, enum, string), name,
+ * default value, upper and lower bounds (if applicable); for strings,
+ * consider a validation routine.
* (ii) add a record below (or use add_<type>_reloption).
* (iii) add it to the appropriate options struct (perhaps StdRdOptions)
* (iv) add it to the appropriate handling routine (perhaps
@@ -147,15 +147,6 @@ static relopt_bool boolRelOpts[] =
},
false
},
- {
- {
- "vacuum_truncate",
- "Enables vacuum to truncate empty pages at the end of this table",
- RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
- ShareUpdateExclusiveLock
- },
- true
- },
{
{
"deduplicate_items",
@@ -170,6 +161,25 @@ static relopt_bool boolRelOpts[] =
{{NULL}}
};
+static relopt_ternary ternaryRelOpts[] =
+{
+ {
+ {
+ "vacuum_truncate",
+ "Enables vacuum to truncate empty pages at the end of this table",
+ RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
+ ShareUpdateExclusiveLock
+ },
+ TERNARY_UNSET
+ },
+ /* list terminator */
+ {
+ {
+ NULL
+ }
+ }
+};
+
static relopt_int intRelOpts[] =
{
{
@@ -609,6 +619,13 @@ initialize_reloptions(void)
boolRelOpts[i].gen.lockmode));
j++;
}
+ for (i = 0; ternaryRelOpts[i].gen.name; i++)
+ {
+ Assert(DoLockModesConflict(ternaryRelOpts[i].gen.lockmode,
+ ternaryRelOpts[i].gen.lockmode));
+ j++;
+ }
+
for (i = 0; intRelOpts[i].gen.name; i++)
{
Assert(DoLockModesConflict(intRelOpts[i].gen.lockmode,
@@ -649,6 +666,14 @@ initialize_reloptions(void)
j++;
}
+ for (i = 0; ternaryRelOpts[i].gen.name; i++)
+ {
+ relOpts[j] = &ternaryRelOpts[i].gen;
+ relOpts[j]->type = RELOPT_TYPE_TERNARY;
+ relOpts[j]->namelen = strlen(relOpts[j]->name);
+ j++;
+ }
+
for (i = 0; intRelOpts[i].gen.name; i++)
{
relOpts[j] = &intRelOpts[i].gen;
@@ -809,6 +834,9 @@ allocate_reloption(bits32 kinds, int type, const char *name, const char *desc,
case RELOPT_TYPE_BOOL:
size = sizeof(relopt_bool);
break;
+ case RELOPT_TYPE_TERNARY:
+ size = sizeof(relopt_ternary);
+ break;
case RELOPT_TYPE_INT:
size = sizeof(relopt_int);
break;
@@ -892,6 +920,57 @@ add_local_bool_reloption(local_relopts *relopts, const char *name,
add_local_reloption(relopts, (relopt_gen *) newoption, offset);
}
+/*
+ * init_ternary_reloption
+ * Allocate and initialize a new ternary reloption
+ */
+static relopt_ternary *
+init_ternary_reloption(bits32 kinds, const char *name, const char *desc,
+ pg_ternary default_val, LOCKMODE lockmode)
+{
+ relopt_ternary *newoption;
+
+ newoption = (relopt_ternary *)
+ allocate_reloption(kinds, RELOPT_TYPE_TERNARY, name, desc, lockmode);
+ newoption->default_val = default_val;
+
+ return newoption;
+}
+
+/*
+ * add_ternary_reloption
+ * Add a new ternary reloption
+ */
+void
+add_ternary_reloption(bits32 kinds, const char *name, const char *desc,
+ pg_ternary default_val, LOCKMODE lockmode)
+{
+ relopt_ternary *newoption;
+
+ newoption =
+ init_ternary_reloption(kinds, name, desc, default_val, lockmode);
+
+ add_reloption((relopt_gen *) newoption);
+}
+
+/*
+ * add_local_ternary_reloption
+ * Add a new ternary local reloption
+ *
+ * 'offset' is offset of ternary-typed field.
+ */
+void
+add_local_ternary_reloption(local_relopts *relopts, const char *name,
+ const char *desc, pg_ternary default_val,
+ int offset)
+{
+ relopt_ternary *newoption;
+
+ newoption =
+ init_ternary_reloption(RELOPT_KIND_LOCAL, name, desc, default_val, 0);
+
+ add_local_reloption(relopts, (relopt_gen *) newoption, offset);
+}
/*
* init_real_reloption
@@ -1626,6 +1705,19 @@ parse_one_reloption(relopt_value *option, char *text_str, int text_len,
option->gen->name, value)));
}
break;
+ case RELOPT_TYPE_TERNARY:
+ {
+ bool b;
+
+ parsed = parse_bool(value, &b);
+ option->values.ternary_val = b ? TERNARY_TRUE : TERNARY_FALSE;
+ if (validate && !parsed)
+ ereport(ERROR,
+ (errcode(ERRCODE_INVALID_PARAMETER_VALUE),
+ errmsg("invalid value for ternary option \"%s\": %s",
+ option->gen->name, value)));
+ }
+ break;
case RELOPT_TYPE_INT:
{
relopt_int *optint = (relopt_int *) option->gen;
@@ -1789,17 +1881,6 @@ fillRelOptions(void *rdopts, Size basesize,
char *itempos = ((char *) rdopts) + elems[j].offset;
char *string_val;
- /*
- * If isset_offset is provided, store whether the reloption is
- * set there.
- */
- if (elems[j].isset_offset > 0)
- {
- char *setpos = ((char *) rdopts) + elems[j].isset_offset;
-
- *(bool *) setpos = options[i].isset;
- }
-
switch (options[i].gen->type)
{
case RELOPT_TYPE_BOOL:
@@ -1807,6 +1888,11 @@ fillRelOptions(void *rdopts, Size basesize,
options[i].values.bool_val :
((relopt_bool *) options[i].gen)->default_val;
break;
+ case RELOPT_TYPE_TERNARY:
+ *(pg_ternary *) itempos = options[i].isset ?
+ options[i].values.ternary_val :
+ ((relopt_ternary *) options[i].gen)->default_val;
+ break;
case RELOPT_TYPE_INT:
*(int *) itempos = options[i].isset ?
options[i].values.int_val :
@@ -1923,8 +2009,8 @@ default_reloptions(Datum reloptions, bool validate, relopt_kind kind)
offsetof(StdRdOptions, parallel_workers)},
{"vacuum_index_cleanup", RELOPT_TYPE_ENUM,
offsetof(StdRdOptions, vacuum_index_cleanup)},
- {"vacuum_truncate", RELOPT_TYPE_BOOL,
- offsetof(StdRdOptions, vacuum_truncate), offsetof(StdRdOptions, vacuum_truncate_set)},
+ {"vacuum_truncate", RELOPT_TYPE_TERNARY,
+ offsetof(StdRdOptions, vacuum_truncate)},
{"vacuum_max_eager_freeze_failure_rate", RELOPT_TYPE_REAL,
offsetof(StdRdOptions, vacuum_max_eager_freeze_failure_rate)}
};
@@ -2004,7 +2090,6 @@ build_local_reloptions(local_relopts *relopts, Datum options, bool validate)
elems[i].optname = opt->option->name;
elems[i].opttype = opt->option->type;
elems[i].offset = opt->offset;
- elems[i].isset_offset = 0; /* not supported for local relopts yet */
i++;
}
diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c
index aa4fbec143f..696eab9bd97 100644
--- a/src/backend/commands/vacuum.c
+++ b/src/backend/commands/vacuum.c
@@ -2224,9 +2224,9 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams params,
{
StdRdOptions *opts = (StdRdOptions *) rel->rd_options;
- if (opts && opts->vacuum_truncate_set)
+ if (opts && opts->vacuum_truncate != TERNARY_UNSET)
{
- if (opts->vacuum_truncate)
+ if (opts->vacuum_truncate == TERNARY_TRUE)
params.truncate = VACOPTVALUE_ENABLED;
else
params.truncate = VACOPTVALUE_DISABLED;
diff --git a/src/include/access/reloptions.h b/src/include/access/reloptions.h
index 2f08e1b0cf0..dfbef2babf2 100644
--- a/src/include/access/reloptions.h
+++ b/src/include/access/reloptions.h
@@ -29,6 +29,7 @@
typedef enum relopt_type
{
RELOPT_TYPE_BOOL,
+ RELOPT_TYPE_TERNARY, /* on, off, unset */
RELOPT_TYPE_INT,
RELOPT_TYPE_REAL,
RELOPT_TYPE_ENUM,
@@ -80,6 +81,7 @@ typedef struct relopt_value
union
{
bool bool_val;
+ pg_ternary ternary_val;
int int_val;
double real_val;
int enum_val;
@@ -94,6 +96,12 @@ typedef struct relopt_bool
bool default_val;
} relopt_bool;
+typedef struct relopt_ternary
+{
+ relopt_gen gen;
+ int default_val;
+} relopt_ternary;
+
typedef struct relopt_int
{
relopt_gen gen;
@@ -152,19 +160,6 @@ typedef struct
const char *optname; /* option's name */
relopt_type opttype; /* option's datatype */
int offset; /* offset of field in result struct */
-
- /*
- * isset_offset is an optional offset of a field in the result struct that
- * stores whether the option is explicitly set for the relation or if it
- * just picked up the default value. In most cases, this can be
- * accomplished by giving the reloption a special out-of-range default
- * value (e.g., some integer reloptions use -2), but this isn't always
- * possible. For example, a Boolean reloption cannot be given an
- * out-of-range default, so we need another way to discover the source of
- * its value. This offset is only used if given a value greater than
- * zero.
- */
- int isset_offset;
} relopt_parse_elt;
/* Local reloption definition */
@@ -195,6 +190,9 @@ typedef struct local_relopts
extern relopt_kind add_reloption_kind(void);
extern void add_bool_reloption(bits32 kinds, const char *name, const char *desc,
bool default_val, LOCKMODE lockmode);
+extern void add_ternary_reloption(bits32 kinds, const char *name,
+ const char *desc, pg_ternary default_val,
+ LOCKMODE lockmode);
extern void add_int_reloption(bits32 kinds, const char *name, const char *desc,
int default_val, int min_val, int max_val,
LOCKMODE lockmode);
@@ -214,6 +212,9 @@ extern void register_reloptions_validator(local_relopts *relopts,
extern void add_local_bool_reloption(local_relopts *relopts, const char *name,
const char *desc, bool default_val,
int offset);
+extern void add_local_ternary_reloption(local_relopts *relopts,
+ const char *name, const char *desc,
+ pg_ternary default_val, int offset);
extern void add_local_int_reloption(local_relopts *relopts, const char *name,
const char *desc, int default_val,
int min_val, int max_val, int offset);
diff --git a/src/include/postgres.h b/src/include/postgres.h
index 7d93fbce709..47c88780776 100644
--- a/src/include/postgres.h
+++ b/src/include/postgres.h
@@ -543,6 +543,21 @@ Float8GetDatum(float8 X)
* ----------------------------------------------------------------
*/
+/*
+ * pg_ternary
+ * Boolean value with an extra "unset" value
+ *
+ * This enum can be used for values that want to distinguish between true,
+ * false, and unset.
+*/
+
+typedef enum pg_ternary
+{
+ TERNARY_FALSE = 0,
+ TERNARY_TRUE = 1,
+ TERNARY_UNSET = -1
+} pg_ternary;
+
/*
* NON_EXEC_STATIC: It's sometimes useful to define a variable or function
* that is normally static but extern when using EXEC_BACKEND (see
diff --git a/src/include/utils/rel.h b/src/include/utils/rel.h
index d03ab247788..5feb18a5373 100644
--- a/src/include/utils/rel.h
+++ b/src/include/utils/rel.h
@@ -347,8 +347,7 @@ typedef struct StdRdOptions
bool user_catalog_table; /* use as an additional catalog relation */
int parallel_workers; /* max number of parallel workers */
StdRdOptIndexCleanup vacuum_index_cleanup; /* controls index vacuuming */
- bool vacuum_truncate; /* enables vacuum to truncate a relation */
- bool vacuum_truncate_set; /* whether vacuum_truncate is set */
+ pg_ternary vacuum_truncate; /* enables vacuum to truncate a relation */
/*
* Fraction of pages in a relation that vacuum can eagerly scan and fail
diff --git a/src/test/regress/expected/reloptions.out b/src/test/regress/expected/reloptions.out
index 9de19b4e3f1..1c99f79ab01 100644
--- a/src/test/regress/expected/reloptions.out
+++ b/src/test/regress/expected/reloptions.out
@@ -98,6 +98,42 @@ SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass;
{fillfactor=13,autovacuum_enabled=false}
(1 row)
+-- Tests for future (FIXME) ternary options
+-- behave as boolean option: accept unassigned name and truncated value
+DROP TABLE reloptions_test;
+CREATE TABLE reloptions_test(i INT) WITH (vacuum_truncate);
+SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass;
+ reloptions
+------------------------
+ {vacuum_truncate=true}
+(1 row)
+
+DROP TABLE reloptions_test;
+CREATE TABLE reloptions_test(i INT) WITH (vacuum_truncate=FaLS);
+SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass;
+ reloptions
+------------------------
+ {vacuum_truncate=fals}
+(1 row)
+
+-- preferred "true" alias is used when storing
+DROP TABLE reloptions_test;
+CREATE TABLE reloptions_test(i INT) WITH (vacuum_index_cleanup=on);
+SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass;
+ reloptions
+---------------------------
+ {vacuum_index_cleanup=on}
+(1 row)
+
+-- custom "third" value is available
+DROP TABLE reloptions_test;
+CREATE TABLE reloptions_test(i INT) WITH (vacuum_index_cleanup=auto);
+SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass;
+ reloptions
+-----------------------------
+ {vacuum_index_cleanup=auto}
+(1 row)
+
-- Test vacuum_truncate option
DROP TABLE reloptions_test;
CREATE TEMP TABLE reloptions_test(i INT NOT NULL, j text)
diff --git a/src/test/regress/sql/reloptions.sql b/src/test/regress/sql/reloptions.sql
index 24fbe0b478d..f5980dafcbc 100644
--- a/src/test/regress/sql/reloptions.sql
+++ b/src/test/regress/sql/reloptions.sql
@@ -59,6 +59,27 @@ UPDATE pg_class
ALTER TABLE reloptions_test RESET (illegal_option);
SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass;
+-- Tests for future (FIXME) ternary options
+
+-- behave as boolean option: accept unassigned name and truncated value
+DROP TABLE reloptions_test;
+CREATE TABLE reloptions_test(i INT) WITH (vacuum_truncate);
+SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass;
+
+DROP TABLE reloptions_test;
+CREATE TABLE reloptions_test(i INT) WITH (vacuum_truncate=FaLS);
+SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass;
+
+-- preferred "true" alias is used when storing
+DROP TABLE reloptions_test;
+CREATE TABLE reloptions_test(i INT) WITH (vacuum_index_cleanup=on);
+SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass;
+
+-- custom "third" value is available
+DROP TABLE reloptions_test;
+CREATE TABLE reloptions_test(i INT) WITH (vacuum_index_cleanup=auto);
+SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass;
+
-- Test vacuum_truncate option
DROP TABLE reloptions_test;
diff --git a/src/tools/pgindent/typedefs.list b/src/tools/pgindent/typedefs.list
index 3f3a888fd0e..1c8610fd46c 100644
--- a/src/tools/pgindent/typedefs.list
+++ b/src/tools/pgindent/typedefs.list
@@ -3952,6 +3952,7 @@ pg_sha512_ctx
pg_snapshot
pg_special_case
pg_stack_base_t
+pg_ternary
pg_time_t
pg_time_usec_t
pg_tz
@@ -4079,6 +4080,7 @@ relopt_kind
relopt_parse_elt
relopt_real
relopt_string
+relopt_ternary
relopt_type
relopt_value
relopts_validator
--
2.47.3
--c36t3fndxhkzbl5z--
^ permalink raw reply [nested|flat] 868+ messages in thread
* [PATCH v3] Introduce ternary reloptions
@ 2026-01-16 15:04 Álvaro Herrera <[email protected]>
0 siblings, 0 replies; 868+ messages in thread
From: Álvaro Herrera @ 2026-01-16 15:04 UTC (permalink / raw)
Introduce ternary reloption as a replacement for current
`vacuum_truncate` implementation. Remove the `vacuum_truncate_set`
separate flag and use TERNARY_UNSET instead.
This could also be used for other options such as `vacuum_index_cleanup`
and `buffering`, but lets get the scaffolding in first.
Discussion: https://postgr.es/m/3474141.usfYGdeWWP@thinkpad-pgpro
---
src/backend/access/common/reloptions.c | 137 ++++++++++++++++++-----
src/backend/commands/vacuum.c | 4 +-
src/include/access/reloptions.h | 27 ++---
src/include/postgres.h | 15 +++
src/include/utils/rel.h | 3 +-
src/test/regress/expected/reloptions.out | 36 ++++++
src/test/regress/sql/reloptions.sql | 21 ++++
src/tools/pgindent/typedefs.list | 2 +
8 files changed, 202 insertions(+), 43 deletions(-)
diff --git a/src/backend/access/common/reloptions.c b/src/backend/access/common/reloptions.c
index 0b83f98ed5f..6f1f577581a 100644
--- a/src/backend/access/common/reloptions.c
+++ b/src/backend/access/common/reloptions.c
@@ -40,9 +40,9 @@
*
* To add an option:
*
- * (i) decide on a type (bool, integer, real, enum, string), name, default
- * value, upper and lower bounds (if applicable); for strings, consider a
- * validation routine.
+ * (i) decide on a type (bool, ternary, integer, real, enum, string), name,
+ * default value, upper and lower bounds (if applicable); for strings,
+ * consider a validation routine.
* (ii) add a record below (or use add_<type>_reloption).
* (iii) add it to the appropriate options struct (perhaps StdRdOptions)
* (iv) add it to the appropriate handling routine (perhaps
@@ -147,15 +147,6 @@ static relopt_bool boolRelOpts[] =
},
false
},
- {
- {
- "vacuum_truncate",
- "Enables vacuum to truncate empty pages at the end of this table",
- RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
- ShareUpdateExclusiveLock
- },
- true
- },
{
{
"deduplicate_items",
@@ -170,6 +161,25 @@ static relopt_bool boolRelOpts[] =
{{NULL}}
};
+static relopt_ternary ternaryRelOpts[] =
+{
+ {
+ {
+ "vacuum_truncate",
+ "Enables vacuum to truncate empty pages at the end of this table",
+ RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
+ ShareUpdateExclusiveLock
+ },
+ TERNARY_UNSET
+ },
+ /* list terminator */
+ {
+ {
+ NULL
+ }
+ }
+};
+
static relopt_int intRelOpts[] =
{
{
@@ -609,6 +619,13 @@ initialize_reloptions(void)
boolRelOpts[i].gen.lockmode));
j++;
}
+ for (i = 0; ternaryRelOpts[i].gen.name; i++)
+ {
+ Assert(DoLockModesConflict(ternaryRelOpts[i].gen.lockmode,
+ ternaryRelOpts[i].gen.lockmode));
+ j++;
+ }
+
for (i = 0; intRelOpts[i].gen.name; i++)
{
Assert(DoLockModesConflict(intRelOpts[i].gen.lockmode,
@@ -649,6 +666,14 @@ initialize_reloptions(void)
j++;
}
+ for (i = 0; ternaryRelOpts[i].gen.name; i++)
+ {
+ relOpts[j] = &ternaryRelOpts[i].gen;
+ relOpts[j]->type = RELOPT_TYPE_TERNARY;
+ relOpts[j]->namelen = strlen(relOpts[j]->name);
+ j++;
+ }
+
for (i = 0; intRelOpts[i].gen.name; i++)
{
relOpts[j] = &intRelOpts[i].gen;
@@ -809,6 +834,9 @@ allocate_reloption(bits32 kinds, int type, const char *name, const char *desc,
case RELOPT_TYPE_BOOL:
size = sizeof(relopt_bool);
break;
+ case RELOPT_TYPE_TERNARY:
+ size = sizeof(relopt_ternary);
+ break;
case RELOPT_TYPE_INT:
size = sizeof(relopt_int);
break;
@@ -892,6 +920,57 @@ add_local_bool_reloption(local_relopts *relopts, const char *name,
add_local_reloption(relopts, (relopt_gen *) newoption, offset);
}
+/*
+ * init_ternary_reloption
+ * Allocate and initialize a new ternary reloption
+ */
+static relopt_ternary *
+init_ternary_reloption(bits32 kinds, const char *name, const char *desc,
+ pg_ternary default_val, LOCKMODE lockmode)
+{
+ relopt_ternary *newoption;
+
+ newoption = (relopt_ternary *)
+ allocate_reloption(kinds, RELOPT_TYPE_TERNARY, name, desc, lockmode);
+ newoption->default_val = default_val;
+
+ return newoption;
+}
+
+/*
+ * add_ternary_reloption
+ * Add a new ternary reloption
+ */
+void
+add_ternary_reloption(bits32 kinds, const char *name, const char *desc,
+ pg_ternary default_val, LOCKMODE lockmode)
+{
+ relopt_ternary *newoption;
+
+ newoption =
+ init_ternary_reloption(kinds, name, desc, default_val, lockmode);
+
+ add_reloption((relopt_gen *) newoption);
+}
+
+/*
+ * add_local_ternary_reloption
+ * Add a new ternary local reloption
+ *
+ * 'offset' is offset of ternary-typed field.
+ */
+void
+add_local_ternary_reloption(local_relopts *relopts, const char *name,
+ const char *desc, pg_ternary default_val,
+ int offset)
+{
+ relopt_ternary *newoption;
+
+ newoption =
+ init_ternary_reloption(RELOPT_KIND_LOCAL, name, desc, default_val, 0);
+
+ add_local_reloption(relopts, (relopt_gen *) newoption, offset);
+}
/*
* init_real_reloption
@@ -1626,6 +1705,19 @@ parse_one_reloption(relopt_value *option, char *text_str, int text_len,
option->gen->name, value)));
}
break;
+ case RELOPT_TYPE_TERNARY:
+ {
+ bool b;
+
+ parsed = parse_bool(value, &b);
+ option->values.ternary_val = b ? TERNARY_TRUE : TERNARY_FALSE;
+ if (validate && !parsed)
+ ereport(ERROR,
+ (errcode(ERRCODE_INVALID_PARAMETER_VALUE),
+ errmsg("invalid value for ternary option \"%s\": %s",
+ option->gen->name, value)));
+ }
+ break;
case RELOPT_TYPE_INT:
{
relopt_int *optint = (relopt_int *) option->gen;
@@ -1789,17 +1881,6 @@ fillRelOptions(void *rdopts, Size basesize,
char *itempos = ((char *) rdopts) + elems[j].offset;
char *string_val;
- /*
- * If isset_offset is provided, store whether the reloption is
- * set there.
- */
- if (elems[j].isset_offset > 0)
- {
- char *setpos = ((char *) rdopts) + elems[j].isset_offset;
-
- *(bool *) setpos = options[i].isset;
- }
-
switch (options[i].gen->type)
{
case RELOPT_TYPE_BOOL:
@@ -1807,6 +1888,11 @@ fillRelOptions(void *rdopts, Size basesize,
options[i].values.bool_val :
((relopt_bool *) options[i].gen)->default_val;
break;
+ case RELOPT_TYPE_TERNARY:
+ *(pg_ternary *) itempos = options[i].isset ?
+ options[i].values.ternary_val :
+ ((relopt_ternary *) options[i].gen)->default_val;
+ break;
case RELOPT_TYPE_INT:
*(int *) itempos = options[i].isset ?
options[i].values.int_val :
@@ -1923,8 +2009,8 @@ default_reloptions(Datum reloptions, bool validate, relopt_kind kind)
offsetof(StdRdOptions, parallel_workers)},
{"vacuum_index_cleanup", RELOPT_TYPE_ENUM,
offsetof(StdRdOptions, vacuum_index_cleanup)},
- {"vacuum_truncate", RELOPT_TYPE_BOOL,
- offsetof(StdRdOptions, vacuum_truncate), offsetof(StdRdOptions, vacuum_truncate_set)},
+ {"vacuum_truncate", RELOPT_TYPE_TERNARY,
+ offsetof(StdRdOptions, vacuum_truncate)},
{"vacuum_max_eager_freeze_failure_rate", RELOPT_TYPE_REAL,
offsetof(StdRdOptions, vacuum_max_eager_freeze_failure_rate)}
};
@@ -2004,7 +2090,6 @@ build_local_reloptions(local_relopts *relopts, Datum options, bool validate)
elems[i].optname = opt->option->name;
elems[i].opttype = opt->option->type;
elems[i].offset = opt->offset;
- elems[i].isset_offset = 0; /* not supported for local relopts yet */
i++;
}
diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c
index aa4fbec143f..696eab9bd97 100644
--- a/src/backend/commands/vacuum.c
+++ b/src/backend/commands/vacuum.c
@@ -2224,9 +2224,9 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams params,
{
StdRdOptions *opts = (StdRdOptions *) rel->rd_options;
- if (opts && opts->vacuum_truncate_set)
+ if (opts && opts->vacuum_truncate != TERNARY_UNSET)
{
- if (opts->vacuum_truncate)
+ if (opts->vacuum_truncate == TERNARY_TRUE)
params.truncate = VACOPTVALUE_ENABLED;
else
params.truncate = VACOPTVALUE_DISABLED;
diff --git a/src/include/access/reloptions.h b/src/include/access/reloptions.h
index 2f08e1b0cf0..dfbef2babf2 100644
--- a/src/include/access/reloptions.h
+++ b/src/include/access/reloptions.h
@@ -29,6 +29,7 @@
typedef enum relopt_type
{
RELOPT_TYPE_BOOL,
+ RELOPT_TYPE_TERNARY, /* on, off, unset */
RELOPT_TYPE_INT,
RELOPT_TYPE_REAL,
RELOPT_TYPE_ENUM,
@@ -80,6 +81,7 @@ typedef struct relopt_value
union
{
bool bool_val;
+ pg_ternary ternary_val;
int int_val;
double real_val;
int enum_val;
@@ -94,6 +96,12 @@ typedef struct relopt_bool
bool default_val;
} relopt_bool;
+typedef struct relopt_ternary
+{
+ relopt_gen gen;
+ int default_val;
+} relopt_ternary;
+
typedef struct relopt_int
{
relopt_gen gen;
@@ -152,19 +160,6 @@ typedef struct
const char *optname; /* option's name */
relopt_type opttype; /* option's datatype */
int offset; /* offset of field in result struct */
-
- /*
- * isset_offset is an optional offset of a field in the result struct that
- * stores whether the option is explicitly set for the relation or if it
- * just picked up the default value. In most cases, this can be
- * accomplished by giving the reloption a special out-of-range default
- * value (e.g., some integer reloptions use -2), but this isn't always
- * possible. For example, a Boolean reloption cannot be given an
- * out-of-range default, so we need another way to discover the source of
- * its value. This offset is only used if given a value greater than
- * zero.
- */
- int isset_offset;
} relopt_parse_elt;
/* Local reloption definition */
@@ -195,6 +190,9 @@ typedef struct local_relopts
extern relopt_kind add_reloption_kind(void);
extern void add_bool_reloption(bits32 kinds, const char *name, const char *desc,
bool default_val, LOCKMODE lockmode);
+extern void add_ternary_reloption(bits32 kinds, const char *name,
+ const char *desc, pg_ternary default_val,
+ LOCKMODE lockmode);
extern void add_int_reloption(bits32 kinds, const char *name, const char *desc,
int default_val, int min_val, int max_val,
LOCKMODE lockmode);
@@ -214,6 +212,9 @@ extern void register_reloptions_validator(local_relopts *relopts,
extern void add_local_bool_reloption(local_relopts *relopts, const char *name,
const char *desc, bool default_val,
int offset);
+extern void add_local_ternary_reloption(local_relopts *relopts,
+ const char *name, const char *desc,
+ pg_ternary default_val, int offset);
extern void add_local_int_reloption(local_relopts *relopts, const char *name,
const char *desc, int default_val,
int min_val, int max_val, int offset);
diff --git a/src/include/postgres.h b/src/include/postgres.h
index 7d93fbce709..47c88780776 100644
--- a/src/include/postgres.h
+++ b/src/include/postgres.h
@@ -543,6 +543,21 @@ Float8GetDatum(float8 X)
* ----------------------------------------------------------------
*/
+/*
+ * pg_ternary
+ * Boolean value with an extra "unset" value
+ *
+ * This enum can be used for values that want to distinguish between true,
+ * false, and unset.
+*/
+
+typedef enum pg_ternary
+{
+ TERNARY_FALSE = 0,
+ TERNARY_TRUE = 1,
+ TERNARY_UNSET = -1
+} pg_ternary;
+
/*
* NON_EXEC_STATIC: It's sometimes useful to define a variable or function
* that is normally static but extern when using EXEC_BACKEND (see
diff --git a/src/include/utils/rel.h b/src/include/utils/rel.h
index d03ab247788..5feb18a5373 100644
--- a/src/include/utils/rel.h
+++ b/src/include/utils/rel.h
@@ -347,8 +347,7 @@ typedef struct StdRdOptions
bool user_catalog_table; /* use as an additional catalog relation */
int parallel_workers; /* max number of parallel workers */
StdRdOptIndexCleanup vacuum_index_cleanup; /* controls index vacuuming */
- bool vacuum_truncate; /* enables vacuum to truncate a relation */
- bool vacuum_truncate_set; /* whether vacuum_truncate is set */
+ pg_ternary vacuum_truncate; /* enables vacuum to truncate a relation */
/*
* Fraction of pages in a relation that vacuum can eagerly scan and fail
diff --git a/src/test/regress/expected/reloptions.out b/src/test/regress/expected/reloptions.out
index 9de19b4e3f1..1c99f79ab01 100644
--- a/src/test/regress/expected/reloptions.out
+++ b/src/test/regress/expected/reloptions.out
@@ -98,6 +98,42 @@ SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass;
{fillfactor=13,autovacuum_enabled=false}
(1 row)
+-- Tests for future (FIXME) ternary options
+-- behave as boolean option: accept unassigned name and truncated value
+DROP TABLE reloptions_test;
+CREATE TABLE reloptions_test(i INT) WITH (vacuum_truncate);
+SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass;
+ reloptions
+------------------------
+ {vacuum_truncate=true}
+(1 row)
+
+DROP TABLE reloptions_test;
+CREATE TABLE reloptions_test(i INT) WITH (vacuum_truncate=FaLS);
+SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass;
+ reloptions
+------------------------
+ {vacuum_truncate=fals}
+(1 row)
+
+-- preferred "true" alias is used when storing
+DROP TABLE reloptions_test;
+CREATE TABLE reloptions_test(i INT) WITH (vacuum_index_cleanup=on);
+SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass;
+ reloptions
+---------------------------
+ {vacuum_index_cleanup=on}
+(1 row)
+
+-- custom "third" value is available
+DROP TABLE reloptions_test;
+CREATE TABLE reloptions_test(i INT) WITH (vacuum_index_cleanup=auto);
+SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass;
+ reloptions
+-----------------------------
+ {vacuum_index_cleanup=auto}
+(1 row)
+
-- Test vacuum_truncate option
DROP TABLE reloptions_test;
CREATE TEMP TABLE reloptions_test(i INT NOT NULL, j text)
diff --git a/src/test/regress/sql/reloptions.sql b/src/test/regress/sql/reloptions.sql
index 24fbe0b478d..f5980dafcbc 100644
--- a/src/test/regress/sql/reloptions.sql
+++ b/src/test/regress/sql/reloptions.sql
@@ -59,6 +59,27 @@ UPDATE pg_class
ALTER TABLE reloptions_test RESET (illegal_option);
SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass;
+-- Tests for future (FIXME) ternary options
+
+-- behave as boolean option: accept unassigned name and truncated value
+DROP TABLE reloptions_test;
+CREATE TABLE reloptions_test(i INT) WITH (vacuum_truncate);
+SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass;
+
+DROP TABLE reloptions_test;
+CREATE TABLE reloptions_test(i INT) WITH (vacuum_truncate=FaLS);
+SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass;
+
+-- preferred "true" alias is used when storing
+DROP TABLE reloptions_test;
+CREATE TABLE reloptions_test(i INT) WITH (vacuum_index_cleanup=on);
+SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass;
+
+-- custom "third" value is available
+DROP TABLE reloptions_test;
+CREATE TABLE reloptions_test(i INT) WITH (vacuum_index_cleanup=auto);
+SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass;
+
-- Test vacuum_truncate option
DROP TABLE reloptions_test;
diff --git a/src/tools/pgindent/typedefs.list b/src/tools/pgindent/typedefs.list
index 3f3a888fd0e..1c8610fd46c 100644
--- a/src/tools/pgindent/typedefs.list
+++ b/src/tools/pgindent/typedefs.list
@@ -3952,6 +3952,7 @@ pg_sha512_ctx
pg_snapshot
pg_special_case
pg_stack_base_t
+pg_ternary
pg_time_t
pg_time_usec_t
pg_tz
@@ -4079,6 +4080,7 @@ relopt_kind
relopt_parse_elt
relopt_real
relopt_string
+relopt_ternary
relopt_type
relopt_value
relopts_validator
--
2.47.3
--c36t3fndxhkzbl5z--
^ permalink raw reply [nested|flat] 868+ messages in thread
* [PATCH v3] Introduce ternary reloptions
@ 2026-01-16 15:04 Álvaro Herrera <[email protected]>
0 siblings, 0 replies; 868+ messages in thread
From: Álvaro Herrera @ 2026-01-16 15:04 UTC (permalink / raw)
Introduce ternary reloption as a replacement for current
`vacuum_truncate` implementation. Remove the `vacuum_truncate_set`
separate flag and use TERNARY_UNSET instead.
This could also be used for other options such as `vacuum_index_cleanup`
and `buffering`, but lets get the scaffolding in first.
Discussion: https://postgr.es/m/3474141.usfYGdeWWP@thinkpad-pgpro
---
src/backend/access/common/reloptions.c | 137 ++++++++++++++++++-----
src/backend/commands/vacuum.c | 4 +-
src/include/access/reloptions.h | 27 ++---
src/include/postgres.h | 15 +++
src/include/utils/rel.h | 3 +-
src/test/regress/expected/reloptions.out | 36 ++++++
src/test/regress/sql/reloptions.sql | 21 ++++
src/tools/pgindent/typedefs.list | 2 +
8 files changed, 202 insertions(+), 43 deletions(-)
diff --git a/src/backend/access/common/reloptions.c b/src/backend/access/common/reloptions.c
index 0b83f98ed5f..6f1f577581a 100644
--- a/src/backend/access/common/reloptions.c
+++ b/src/backend/access/common/reloptions.c
@@ -40,9 +40,9 @@
*
* To add an option:
*
- * (i) decide on a type (bool, integer, real, enum, string), name, default
- * value, upper and lower bounds (if applicable); for strings, consider a
- * validation routine.
+ * (i) decide on a type (bool, ternary, integer, real, enum, string), name,
+ * default value, upper and lower bounds (if applicable); for strings,
+ * consider a validation routine.
* (ii) add a record below (or use add_<type>_reloption).
* (iii) add it to the appropriate options struct (perhaps StdRdOptions)
* (iv) add it to the appropriate handling routine (perhaps
@@ -147,15 +147,6 @@ static relopt_bool boolRelOpts[] =
},
false
},
- {
- {
- "vacuum_truncate",
- "Enables vacuum to truncate empty pages at the end of this table",
- RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
- ShareUpdateExclusiveLock
- },
- true
- },
{
{
"deduplicate_items",
@@ -170,6 +161,25 @@ static relopt_bool boolRelOpts[] =
{{NULL}}
};
+static relopt_ternary ternaryRelOpts[] =
+{
+ {
+ {
+ "vacuum_truncate",
+ "Enables vacuum to truncate empty pages at the end of this table",
+ RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
+ ShareUpdateExclusiveLock
+ },
+ TERNARY_UNSET
+ },
+ /* list terminator */
+ {
+ {
+ NULL
+ }
+ }
+};
+
static relopt_int intRelOpts[] =
{
{
@@ -609,6 +619,13 @@ initialize_reloptions(void)
boolRelOpts[i].gen.lockmode));
j++;
}
+ for (i = 0; ternaryRelOpts[i].gen.name; i++)
+ {
+ Assert(DoLockModesConflict(ternaryRelOpts[i].gen.lockmode,
+ ternaryRelOpts[i].gen.lockmode));
+ j++;
+ }
+
for (i = 0; intRelOpts[i].gen.name; i++)
{
Assert(DoLockModesConflict(intRelOpts[i].gen.lockmode,
@@ -649,6 +666,14 @@ initialize_reloptions(void)
j++;
}
+ for (i = 0; ternaryRelOpts[i].gen.name; i++)
+ {
+ relOpts[j] = &ternaryRelOpts[i].gen;
+ relOpts[j]->type = RELOPT_TYPE_TERNARY;
+ relOpts[j]->namelen = strlen(relOpts[j]->name);
+ j++;
+ }
+
for (i = 0; intRelOpts[i].gen.name; i++)
{
relOpts[j] = &intRelOpts[i].gen;
@@ -809,6 +834,9 @@ allocate_reloption(bits32 kinds, int type, const char *name, const char *desc,
case RELOPT_TYPE_BOOL:
size = sizeof(relopt_bool);
break;
+ case RELOPT_TYPE_TERNARY:
+ size = sizeof(relopt_ternary);
+ break;
case RELOPT_TYPE_INT:
size = sizeof(relopt_int);
break;
@@ -892,6 +920,57 @@ add_local_bool_reloption(local_relopts *relopts, const char *name,
add_local_reloption(relopts, (relopt_gen *) newoption, offset);
}
+/*
+ * init_ternary_reloption
+ * Allocate and initialize a new ternary reloption
+ */
+static relopt_ternary *
+init_ternary_reloption(bits32 kinds, const char *name, const char *desc,
+ pg_ternary default_val, LOCKMODE lockmode)
+{
+ relopt_ternary *newoption;
+
+ newoption = (relopt_ternary *)
+ allocate_reloption(kinds, RELOPT_TYPE_TERNARY, name, desc, lockmode);
+ newoption->default_val = default_val;
+
+ return newoption;
+}
+
+/*
+ * add_ternary_reloption
+ * Add a new ternary reloption
+ */
+void
+add_ternary_reloption(bits32 kinds, const char *name, const char *desc,
+ pg_ternary default_val, LOCKMODE lockmode)
+{
+ relopt_ternary *newoption;
+
+ newoption =
+ init_ternary_reloption(kinds, name, desc, default_val, lockmode);
+
+ add_reloption((relopt_gen *) newoption);
+}
+
+/*
+ * add_local_ternary_reloption
+ * Add a new ternary local reloption
+ *
+ * 'offset' is offset of ternary-typed field.
+ */
+void
+add_local_ternary_reloption(local_relopts *relopts, const char *name,
+ const char *desc, pg_ternary default_val,
+ int offset)
+{
+ relopt_ternary *newoption;
+
+ newoption =
+ init_ternary_reloption(RELOPT_KIND_LOCAL, name, desc, default_val, 0);
+
+ add_local_reloption(relopts, (relopt_gen *) newoption, offset);
+}
/*
* init_real_reloption
@@ -1626,6 +1705,19 @@ parse_one_reloption(relopt_value *option, char *text_str, int text_len,
option->gen->name, value)));
}
break;
+ case RELOPT_TYPE_TERNARY:
+ {
+ bool b;
+
+ parsed = parse_bool(value, &b);
+ option->values.ternary_val = b ? TERNARY_TRUE : TERNARY_FALSE;
+ if (validate && !parsed)
+ ereport(ERROR,
+ (errcode(ERRCODE_INVALID_PARAMETER_VALUE),
+ errmsg("invalid value for ternary option \"%s\": %s",
+ option->gen->name, value)));
+ }
+ break;
case RELOPT_TYPE_INT:
{
relopt_int *optint = (relopt_int *) option->gen;
@@ -1789,17 +1881,6 @@ fillRelOptions(void *rdopts, Size basesize,
char *itempos = ((char *) rdopts) + elems[j].offset;
char *string_val;
- /*
- * If isset_offset is provided, store whether the reloption is
- * set there.
- */
- if (elems[j].isset_offset > 0)
- {
- char *setpos = ((char *) rdopts) + elems[j].isset_offset;
-
- *(bool *) setpos = options[i].isset;
- }
-
switch (options[i].gen->type)
{
case RELOPT_TYPE_BOOL:
@@ -1807,6 +1888,11 @@ fillRelOptions(void *rdopts, Size basesize,
options[i].values.bool_val :
((relopt_bool *) options[i].gen)->default_val;
break;
+ case RELOPT_TYPE_TERNARY:
+ *(pg_ternary *) itempos = options[i].isset ?
+ options[i].values.ternary_val :
+ ((relopt_ternary *) options[i].gen)->default_val;
+ break;
case RELOPT_TYPE_INT:
*(int *) itempos = options[i].isset ?
options[i].values.int_val :
@@ -1923,8 +2009,8 @@ default_reloptions(Datum reloptions, bool validate, relopt_kind kind)
offsetof(StdRdOptions, parallel_workers)},
{"vacuum_index_cleanup", RELOPT_TYPE_ENUM,
offsetof(StdRdOptions, vacuum_index_cleanup)},
- {"vacuum_truncate", RELOPT_TYPE_BOOL,
- offsetof(StdRdOptions, vacuum_truncate), offsetof(StdRdOptions, vacuum_truncate_set)},
+ {"vacuum_truncate", RELOPT_TYPE_TERNARY,
+ offsetof(StdRdOptions, vacuum_truncate)},
{"vacuum_max_eager_freeze_failure_rate", RELOPT_TYPE_REAL,
offsetof(StdRdOptions, vacuum_max_eager_freeze_failure_rate)}
};
@@ -2004,7 +2090,6 @@ build_local_reloptions(local_relopts *relopts, Datum options, bool validate)
elems[i].optname = opt->option->name;
elems[i].opttype = opt->option->type;
elems[i].offset = opt->offset;
- elems[i].isset_offset = 0; /* not supported for local relopts yet */
i++;
}
diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c
index aa4fbec143f..696eab9bd97 100644
--- a/src/backend/commands/vacuum.c
+++ b/src/backend/commands/vacuum.c
@@ -2224,9 +2224,9 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams params,
{
StdRdOptions *opts = (StdRdOptions *) rel->rd_options;
- if (opts && opts->vacuum_truncate_set)
+ if (opts && opts->vacuum_truncate != TERNARY_UNSET)
{
- if (opts->vacuum_truncate)
+ if (opts->vacuum_truncate == TERNARY_TRUE)
params.truncate = VACOPTVALUE_ENABLED;
else
params.truncate = VACOPTVALUE_DISABLED;
diff --git a/src/include/access/reloptions.h b/src/include/access/reloptions.h
index 2f08e1b0cf0..dfbef2babf2 100644
--- a/src/include/access/reloptions.h
+++ b/src/include/access/reloptions.h
@@ -29,6 +29,7 @@
typedef enum relopt_type
{
RELOPT_TYPE_BOOL,
+ RELOPT_TYPE_TERNARY, /* on, off, unset */
RELOPT_TYPE_INT,
RELOPT_TYPE_REAL,
RELOPT_TYPE_ENUM,
@@ -80,6 +81,7 @@ typedef struct relopt_value
union
{
bool bool_val;
+ pg_ternary ternary_val;
int int_val;
double real_val;
int enum_val;
@@ -94,6 +96,12 @@ typedef struct relopt_bool
bool default_val;
} relopt_bool;
+typedef struct relopt_ternary
+{
+ relopt_gen gen;
+ int default_val;
+} relopt_ternary;
+
typedef struct relopt_int
{
relopt_gen gen;
@@ -152,19 +160,6 @@ typedef struct
const char *optname; /* option's name */
relopt_type opttype; /* option's datatype */
int offset; /* offset of field in result struct */
-
- /*
- * isset_offset is an optional offset of a field in the result struct that
- * stores whether the option is explicitly set for the relation or if it
- * just picked up the default value. In most cases, this can be
- * accomplished by giving the reloption a special out-of-range default
- * value (e.g., some integer reloptions use -2), but this isn't always
- * possible. For example, a Boolean reloption cannot be given an
- * out-of-range default, so we need another way to discover the source of
- * its value. This offset is only used if given a value greater than
- * zero.
- */
- int isset_offset;
} relopt_parse_elt;
/* Local reloption definition */
@@ -195,6 +190,9 @@ typedef struct local_relopts
extern relopt_kind add_reloption_kind(void);
extern void add_bool_reloption(bits32 kinds, const char *name, const char *desc,
bool default_val, LOCKMODE lockmode);
+extern void add_ternary_reloption(bits32 kinds, const char *name,
+ const char *desc, pg_ternary default_val,
+ LOCKMODE lockmode);
extern void add_int_reloption(bits32 kinds, const char *name, const char *desc,
int default_val, int min_val, int max_val,
LOCKMODE lockmode);
@@ -214,6 +212,9 @@ extern void register_reloptions_validator(local_relopts *relopts,
extern void add_local_bool_reloption(local_relopts *relopts, const char *name,
const char *desc, bool default_val,
int offset);
+extern void add_local_ternary_reloption(local_relopts *relopts,
+ const char *name, const char *desc,
+ pg_ternary default_val, int offset);
extern void add_local_int_reloption(local_relopts *relopts, const char *name,
const char *desc, int default_val,
int min_val, int max_val, int offset);
diff --git a/src/include/postgres.h b/src/include/postgres.h
index 7d93fbce709..47c88780776 100644
--- a/src/include/postgres.h
+++ b/src/include/postgres.h
@@ -543,6 +543,21 @@ Float8GetDatum(float8 X)
* ----------------------------------------------------------------
*/
+/*
+ * pg_ternary
+ * Boolean value with an extra "unset" value
+ *
+ * This enum can be used for values that want to distinguish between true,
+ * false, and unset.
+*/
+
+typedef enum pg_ternary
+{
+ TERNARY_FALSE = 0,
+ TERNARY_TRUE = 1,
+ TERNARY_UNSET = -1
+} pg_ternary;
+
/*
* NON_EXEC_STATIC: It's sometimes useful to define a variable or function
* that is normally static but extern when using EXEC_BACKEND (see
diff --git a/src/include/utils/rel.h b/src/include/utils/rel.h
index d03ab247788..5feb18a5373 100644
--- a/src/include/utils/rel.h
+++ b/src/include/utils/rel.h
@@ -347,8 +347,7 @@ typedef struct StdRdOptions
bool user_catalog_table; /* use as an additional catalog relation */
int parallel_workers; /* max number of parallel workers */
StdRdOptIndexCleanup vacuum_index_cleanup; /* controls index vacuuming */
- bool vacuum_truncate; /* enables vacuum to truncate a relation */
- bool vacuum_truncate_set; /* whether vacuum_truncate is set */
+ pg_ternary vacuum_truncate; /* enables vacuum to truncate a relation */
/*
* Fraction of pages in a relation that vacuum can eagerly scan and fail
diff --git a/src/test/regress/expected/reloptions.out b/src/test/regress/expected/reloptions.out
index 9de19b4e3f1..1c99f79ab01 100644
--- a/src/test/regress/expected/reloptions.out
+++ b/src/test/regress/expected/reloptions.out
@@ -98,6 +98,42 @@ SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass;
{fillfactor=13,autovacuum_enabled=false}
(1 row)
+-- Tests for future (FIXME) ternary options
+-- behave as boolean option: accept unassigned name and truncated value
+DROP TABLE reloptions_test;
+CREATE TABLE reloptions_test(i INT) WITH (vacuum_truncate);
+SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass;
+ reloptions
+------------------------
+ {vacuum_truncate=true}
+(1 row)
+
+DROP TABLE reloptions_test;
+CREATE TABLE reloptions_test(i INT) WITH (vacuum_truncate=FaLS);
+SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass;
+ reloptions
+------------------------
+ {vacuum_truncate=fals}
+(1 row)
+
+-- preferred "true" alias is used when storing
+DROP TABLE reloptions_test;
+CREATE TABLE reloptions_test(i INT) WITH (vacuum_index_cleanup=on);
+SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass;
+ reloptions
+---------------------------
+ {vacuum_index_cleanup=on}
+(1 row)
+
+-- custom "third" value is available
+DROP TABLE reloptions_test;
+CREATE TABLE reloptions_test(i INT) WITH (vacuum_index_cleanup=auto);
+SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass;
+ reloptions
+-----------------------------
+ {vacuum_index_cleanup=auto}
+(1 row)
+
-- Test vacuum_truncate option
DROP TABLE reloptions_test;
CREATE TEMP TABLE reloptions_test(i INT NOT NULL, j text)
diff --git a/src/test/regress/sql/reloptions.sql b/src/test/regress/sql/reloptions.sql
index 24fbe0b478d..f5980dafcbc 100644
--- a/src/test/regress/sql/reloptions.sql
+++ b/src/test/regress/sql/reloptions.sql
@@ -59,6 +59,27 @@ UPDATE pg_class
ALTER TABLE reloptions_test RESET (illegal_option);
SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass;
+-- Tests for future (FIXME) ternary options
+
+-- behave as boolean option: accept unassigned name and truncated value
+DROP TABLE reloptions_test;
+CREATE TABLE reloptions_test(i INT) WITH (vacuum_truncate);
+SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass;
+
+DROP TABLE reloptions_test;
+CREATE TABLE reloptions_test(i INT) WITH (vacuum_truncate=FaLS);
+SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass;
+
+-- preferred "true" alias is used when storing
+DROP TABLE reloptions_test;
+CREATE TABLE reloptions_test(i INT) WITH (vacuum_index_cleanup=on);
+SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass;
+
+-- custom "third" value is available
+DROP TABLE reloptions_test;
+CREATE TABLE reloptions_test(i INT) WITH (vacuum_index_cleanup=auto);
+SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass;
+
-- Test vacuum_truncate option
DROP TABLE reloptions_test;
diff --git a/src/tools/pgindent/typedefs.list b/src/tools/pgindent/typedefs.list
index 3f3a888fd0e..1c8610fd46c 100644
--- a/src/tools/pgindent/typedefs.list
+++ b/src/tools/pgindent/typedefs.list
@@ -3952,6 +3952,7 @@ pg_sha512_ctx
pg_snapshot
pg_special_case
pg_stack_base_t
+pg_ternary
pg_time_t
pg_time_usec_t
pg_tz
@@ -4079,6 +4080,7 @@ relopt_kind
relopt_parse_elt
relopt_real
relopt_string
+relopt_ternary
relopt_type
relopt_value
relopts_validator
--
2.47.3
--c36t3fndxhkzbl5z--
^ permalink raw reply [nested|flat] 868+ messages in thread
* [PATCH v3] Introduce ternary reloptions
@ 2026-01-16 15:04 Álvaro Herrera <[email protected]>
0 siblings, 0 replies; 868+ messages in thread
From: Álvaro Herrera @ 2026-01-16 15:04 UTC (permalink / raw)
Introduce ternary reloption as a replacement for current
`vacuum_truncate` implementation. Remove the `vacuum_truncate_set`
separate flag and use TERNARY_UNSET instead.
This could also be used for other options such as `vacuum_index_cleanup`
and `buffering`, but lets get the scaffolding in first.
Discussion: https://postgr.es/m/3474141.usfYGdeWWP@thinkpad-pgpro
---
src/backend/access/common/reloptions.c | 137 ++++++++++++++++++-----
src/backend/commands/vacuum.c | 4 +-
src/include/access/reloptions.h | 27 ++---
src/include/postgres.h | 15 +++
src/include/utils/rel.h | 3 +-
src/test/regress/expected/reloptions.out | 36 ++++++
src/test/regress/sql/reloptions.sql | 21 ++++
src/tools/pgindent/typedefs.list | 2 +
8 files changed, 202 insertions(+), 43 deletions(-)
diff --git a/src/backend/access/common/reloptions.c b/src/backend/access/common/reloptions.c
index 0b83f98ed5f..6f1f577581a 100644
--- a/src/backend/access/common/reloptions.c
+++ b/src/backend/access/common/reloptions.c
@@ -40,9 +40,9 @@
*
* To add an option:
*
- * (i) decide on a type (bool, integer, real, enum, string), name, default
- * value, upper and lower bounds (if applicable); for strings, consider a
- * validation routine.
+ * (i) decide on a type (bool, ternary, integer, real, enum, string), name,
+ * default value, upper and lower bounds (if applicable); for strings,
+ * consider a validation routine.
* (ii) add a record below (or use add_<type>_reloption).
* (iii) add it to the appropriate options struct (perhaps StdRdOptions)
* (iv) add it to the appropriate handling routine (perhaps
@@ -147,15 +147,6 @@ static relopt_bool boolRelOpts[] =
},
false
},
- {
- {
- "vacuum_truncate",
- "Enables vacuum to truncate empty pages at the end of this table",
- RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
- ShareUpdateExclusiveLock
- },
- true
- },
{
{
"deduplicate_items",
@@ -170,6 +161,25 @@ static relopt_bool boolRelOpts[] =
{{NULL}}
};
+static relopt_ternary ternaryRelOpts[] =
+{
+ {
+ {
+ "vacuum_truncate",
+ "Enables vacuum to truncate empty pages at the end of this table",
+ RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
+ ShareUpdateExclusiveLock
+ },
+ TERNARY_UNSET
+ },
+ /* list terminator */
+ {
+ {
+ NULL
+ }
+ }
+};
+
static relopt_int intRelOpts[] =
{
{
@@ -609,6 +619,13 @@ initialize_reloptions(void)
boolRelOpts[i].gen.lockmode));
j++;
}
+ for (i = 0; ternaryRelOpts[i].gen.name; i++)
+ {
+ Assert(DoLockModesConflict(ternaryRelOpts[i].gen.lockmode,
+ ternaryRelOpts[i].gen.lockmode));
+ j++;
+ }
+
for (i = 0; intRelOpts[i].gen.name; i++)
{
Assert(DoLockModesConflict(intRelOpts[i].gen.lockmode,
@@ -649,6 +666,14 @@ initialize_reloptions(void)
j++;
}
+ for (i = 0; ternaryRelOpts[i].gen.name; i++)
+ {
+ relOpts[j] = &ternaryRelOpts[i].gen;
+ relOpts[j]->type = RELOPT_TYPE_TERNARY;
+ relOpts[j]->namelen = strlen(relOpts[j]->name);
+ j++;
+ }
+
for (i = 0; intRelOpts[i].gen.name; i++)
{
relOpts[j] = &intRelOpts[i].gen;
@@ -809,6 +834,9 @@ allocate_reloption(bits32 kinds, int type, const char *name, const char *desc,
case RELOPT_TYPE_BOOL:
size = sizeof(relopt_bool);
break;
+ case RELOPT_TYPE_TERNARY:
+ size = sizeof(relopt_ternary);
+ break;
case RELOPT_TYPE_INT:
size = sizeof(relopt_int);
break;
@@ -892,6 +920,57 @@ add_local_bool_reloption(local_relopts *relopts, const char *name,
add_local_reloption(relopts, (relopt_gen *) newoption, offset);
}
+/*
+ * init_ternary_reloption
+ * Allocate and initialize a new ternary reloption
+ */
+static relopt_ternary *
+init_ternary_reloption(bits32 kinds, const char *name, const char *desc,
+ pg_ternary default_val, LOCKMODE lockmode)
+{
+ relopt_ternary *newoption;
+
+ newoption = (relopt_ternary *)
+ allocate_reloption(kinds, RELOPT_TYPE_TERNARY, name, desc, lockmode);
+ newoption->default_val = default_val;
+
+ return newoption;
+}
+
+/*
+ * add_ternary_reloption
+ * Add a new ternary reloption
+ */
+void
+add_ternary_reloption(bits32 kinds, const char *name, const char *desc,
+ pg_ternary default_val, LOCKMODE lockmode)
+{
+ relopt_ternary *newoption;
+
+ newoption =
+ init_ternary_reloption(kinds, name, desc, default_val, lockmode);
+
+ add_reloption((relopt_gen *) newoption);
+}
+
+/*
+ * add_local_ternary_reloption
+ * Add a new ternary local reloption
+ *
+ * 'offset' is offset of ternary-typed field.
+ */
+void
+add_local_ternary_reloption(local_relopts *relopts, const char *name,
+ const char *desc, pg_ternary default_val,
+ int offset)
+{
+ relopt_ternary *newoption;
+
+ newoption =
+ init_ternary_reloption(RELOPT_KIND_LOCAL, name, desc, default_val, 0);
+
+ add_local_reloption(relopts, (relopt_gen *) newoption, offset);
+}
/*
* init_real_reloption
@@ -1626,6 +1705,19 @@ parse_one_reloption(relopt_value *option, char *text_str, int text_len,
option->gen->name, value)));
}
break;
+ case RELOPT_TYPE_TERNARY:
+ {
+ bool b;
+
+ parsed = parse_bool(value, &b);
+ option->values.ternary_val = b ? TERNARY_TRUE : TERNARY_FALSE;
+ if (validate && !parsed)
+ ereport(ERROR,
+ (errcode(ERRCODE_INVALID_PARAMETER_VALUE),
+ errmsg("invalid value for ternary option \"%s\": %s",
+ option->gen->name, value)));
+ }
+ break;
case RELOPT_TYPE_INT:
{
relopt_int *optint = (relopt_int *) option->gen;
@@ -1789,17 +1881,6 @@ fillRelOptions(void *rdopts, Size basesize,
char *itempos = ((char *) rdopts) + elems[j].offset;
char *string_val;
- /*
- * If isset_offset is provided, store whether the reloption is
- * set there.
- */
- if (elems[j].isset_offset > 0)
- {
- char *setpos = ((char *) rdopts) + elems[j].isset_offset;
-
- *(bool *) setpos = options[i].isset;
- }
-
switch (options[i].gen->type)
{
case RELOPT_TYPE_BOOL:
@@ -1807,6 +1888,11 @@ fillRelOptions(void *rdopts, Size basesize,
options[i].values.bool_val :
((relopt_bool *) options[i].gen)->default_val;
break;
+ case RELOPT_TYPE_TERNARY:
+ *(pg_ternary *) itempos = options[i].isset ?
+ options[i].values.ternary_val :
+ ((relopt_ternary *) options[i].gen)->default_val;
+ break;
case RELOPT_TYPE_INT:
*(int *) itempos = options[i].isset ?
options[i].values.int_val :
@@ -1923,8 +2009,8 @@ default_reloptions(Datum reloptions, bool validate, relopt_kind kind)
offsetof(StdRdOptions, parallel_workers)},
{"vacuum_index_cleanup", RELOPT_TYPE_ENUM,
offsetof(StdRdOptions, vacuum_index_cleanup)},
- {"vacuum_truncate", RELOPT_TYPE_BOOL,
- offsetof(StdRdOptions, vacuum_truncate), offsetof(StdRdOptions, vacuum_truncate_set)},
+ {"vacuum_truncate", RELOPT_TYPE_TERNARY,
+ offsetof(StdRdOptions, vacuum_truncate)},
{"vacuum_max_eager_freeze_failure_rate", RELOPT_TYPE_REAL,
offsetof(StdRdOptions, vacuum_max_eager_freeze_failure_rate)}
};
@@ -2004,7 +2090,6 @@ build_local_reloptions(local_relopts *relopts, Datum options, bool validate)
elems[i].optname = opt->option->name;
elems[i].opttype = opt->option->type;
elems[i].offset = opt->offset;
- elems[i].isset_offset = 0; /* not supported for local relopts yet */
i++;
}
diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c
index aa4fbec143f..696eab9bd97 100644
--- a/src/backend/commands/vacuum.c
+++ b/src/backend/commands/vacuum.c
@@ -2224,9 +2224,9 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams params,
{
StdRdOptions *opts = (StdRdOptions *) rel->rd_options;
- if (opts && opts->vacuum_truncate_set)
+ if (opts && opts->vacuum_truncate != TERNARY_UNSET)
{
- if (opts->vacuum_truncate)
+ if (opts->vacuum_truncate == TERNARY_TRUE)
params.truncate = VACOPTVALUE_ENABLED;
else
params.truncate = VACOPTVALUE_DISABLED;
diff --git a/src/include/access/reloptions.h b/src/include/access/reloptions.h
index 2f08e1b0cf0..dfbef2babf2 100644
--- a/src/include/access/reloptions.h
+++ b/src/include/access/reloptions.h
@@ -29,6 +29,7 @@
typedef enum relopt_type
{
RELOPT_TYPE_BOOL,
+ RELOPT_TYPE_TERNARY, /* on, off, unset */
RELOPT_TYPE_INT,
RELOPT_TYPE_REAL,
RELOPT_TYPE_ENUM,
@@ -80,6 +81,7 @@ typedef struct relopt_value
union
{
bool bool_val;
+ pg_ternary ternary_val;
int int_val;
double real_val;
int enum_val;
@@ -94,6 +96,12 @@ typedef struct relopt_bool
bool default_val;
} relopt_bool;
+typedef struct relopt_ternary
+{
+ relopt_gen gen;
+ int default_val;
+} relopt_ternary;
+
typedef struct relopt_int
{
relopt_gen gen;
@@ -152,19 +160,6 @@ typedef struct
const char *optname; /* option's name */
relopt_type opttype; /* option's datatype */
int offset; /* offset of field in result struct */
-
- /*
- * isset_offset is an optional offset of a field in the result struct that
- * stores whether the option is explicitly set for the relation or if it
- * just picked up the default value. In most cases, this can be
- * accomplished by giving the reloption a special out-of-range default
- * value (e.g., some integer reloptions use -2), but this isn't always
- * possible. For example, a Boolean reloption cannot be given an
- * out-of-range default, so we need another way to discover the source of
- * its value. This offset is only used if given a value greater than
- * zero.
- */
- int isset_offset;
} relopt_parse_elt;
/* Local reloption definition */
@@ -195,6 +190,9 @@ typedef struct local_relopts
extern relopt_kind add_reloption_kind(void);
extern void add_bool_reloption(bits32 kinds, const char *name, const char *desc,
bool default_val, LOCKMODE lockmode);
+extern void add_ternary_reloption(bits32 kinds, const char *name,
+ const char *desc, pg_ternary default_val,
+ LOCKMODE lockmode);
extern void add_int_reloption(bits32 kinds, const char *name, const char *desc,
int default_val, int min_val, int max_val,
LOCKMODE lockmode);
@@ -214,6 +212,9 @@ extern void register_reloptions_validator(local_relopts *relopts,
extern void add_local_bool_reloption(local_relopts *relopts, const char *name,
const char *desc, bool default_val,
int offset);
+extern void add_local_ternary_reloption(local_relopts *relopts,
+ const char *name, const char *desc,
+ pg_ternary default_val, int offset);
extern void add_local_int_reloption(local_relopts *relopts, const char *name,
const char *desc, int default_val,
int min_val, int max_val, int offset);
diff --git a/src/include/postgres.h b/src/include/postgres.h
index 7d93fbce709..47c88780776 100644
--- a/src/include/postgres.h
+++ b/src/include/postgres.h
@@ -543,6 +543,21 @@ Float8GetDatum(float8 X)
* ----------------------------------------------------------------
*/
+/*
+ * pg_ternary
+ * Boolean value with an extra "unset" value
+ *
+ * This enum can be used for values that want to distinguish between true,
+ * false, and unset.
+*/
+
+typedef enum pg_ternary
+{
+ TERNARY_FALSE = 0,
+ TERNARY_TRUE = 1,
+ TERNARY_UNSET = -1
+} pg_ternary;
+
/*
* NON_EXEC_STATIC: It's sometimes useful to define a variable or function
* that is normally static but extern when using EXEC_BACKEND (see
diff --git a/src/include/utils/rel.h b/src/include/utils/rel.h
index d03ab247788..5feb18a5373 100644
--- a/src/include/utils/rel.h
+++ b/src/include/utils/rel.h
@@ -347,8 +347,7 @@ typedef struct StdRdOptions
bool user_catalog_table; /* use as an additional catalog relation */
int parallel_workers; /* max number of parallel workers */
StdRdOptIndexCleanup vacuum_index_cleanup; /* controls index vacuuming */
- bool vacuum_truncate; /* enables vacuum to truncate a relation */
- bool vacuum_truncate_set; /* whether vacuum_truncate is set */
+ pg_ternary vacuum_truncate; /* enables vacuum to truncate a relation */
/*
* Fraction of pages in a relation that vacuum can eagerly scan and fail
diff --git a/src/test/regress/expected/reloptions.out b/src/test/regress/expected/reloptions.out
index 9de19b4e3f1..1c99f79ab01 100644
--- a/src/test/regress/expected/reloptions.out
+++ b/src/test/regress/expected/reloptions.out
@@ -98,6 +98,42 @@ SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass;
{fillfactor=13,autovacuum_enabled=false}
(1 row)
+-- Tests for future (FIXME) ternary options
+-- behave as boolean option: accept unassigned name and truncated value
+DROP TABLE reloptions_test;
+CREATE TABLE reloptions_test(i INT) WITH (vacuum_truncate);
+SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass;
+ reloptions
+------------------------
+ {vacuum_truncate=true}
+(1 row)
+
+DROP TABLE reloptions_test;
+CREATE TABLE reloptions_test(i INT) WITH (vacuum_truncate=FaLS);
+SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass;
+ reloptions
+------------------------
+ {vacuum_truncate=fals}
+(1 row)
+
+-- preferred "true" alias is used when storing
+DROP TABLE reloptions_test;
+CREATE TABLE reloptions_test(i INT) WITH (vacuum_index_cleanup=on);
+SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass;
+ reloptions
+---------------------------
+ {vacuum_index_cleanup=on}
+(1 row)
+
+-- custom "third" value is available
+DROP TABLE reloptions_test;
+CREATE TABLE reloptions_test(i INT) WITH (vacuum_index_cleanup=auto);
+SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass;
+ reloptions
+-----------------------------
+ {vacuum_index_cleanup=auto}
+(1 row)
+
-- Test vacuum_truncate option
DROP TABLE reloptions_test;
CREATE TEMP TABLE reloptions_test(i INT NOT NULL, j text)
diff --git a/src/test/regress/sql/reloptions.sql b/src/test/regress/sql/reloptions.sql
index 24fbe0b478d..f5980dafcbc 100644
--- a/src/test/regress/sql/reloptions.sql
+++ b/src/test/regress/sql/reloptions.sql
@@ -59,6 +59,27 @@ UPDATE pg_class
ALTER TABLE reloptions_test RESET (illegal_option);
SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass;
+-- Tests for future (FIXME) ternary options
+
+-- behave as boolean option: accept unassigned name and truncated value
+DROP TABLE reloptions_test;
+CREATE TABLE reloptions_test(i INT) WITH (vacuum_truncate);
+SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass;
+
+DROP TABLE reloptions_test;
+CREATE TABLE reloptions_test(i INT) WITH (vacuum_truncate=FaLS);
+SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass;
+
+-- preferred "true" alias is used when storing
+DROP TABLE reloptions_test;
+CREATE TABLE reloptions_test(i INT) WITH (vacuum_index_cleanup=on);
+SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass;
+
+-- custom "third" value is available
+DROP TABLE reloptions_test;
+CREATE TABLE reloptions_test(i INT) WITH (vacuum_index_cleanup=auto);
+SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass;
+
-- Test vacuum_truncate option
DROP TABLE reloptions_test;
diff --git a/src/tools/pgindent/typedefs.list b/src/tools/pgindent/typedefs.list
index 3f3a888fd0e..1c8610fd46c 100644
--- a/src/tools/pgindent/typedefs.list
+++ b/src/tools/pgindent/typedefs.list
@@ -3952,6 +3952,7 @@ pg_sha512_ctx
pg_snapshot
pg_special_case
pg_stack_base_t
+pg_ternary
pg_time_t
pg_time_usec_t
pg_tz
@@ -4079,6 +4080,7 @@ relopt_kind
relopt_parse_elt
relopt_real
relopt_string
+relopt_ternary
relopt_type
relopt_value
relopts_validator
--
2.47.3
--c36t3fndxhkzbl5z--
^ permalink raw reply [nested|flat] 868+ messages in thread
* [PATCH v3] Introduce ternary reloptions
@ 2026-01-16 15:04 Álvaro Herrera <[email protected]>
0 siblings, 0 replies; 868+ messages in thread
From: Álvaro Herrera @ 2026-01-16 15:04 UTC (permalink / raw)
Introduce ternary reloption as a replacement for current
`vacuum_truncate` implementation. Remove the `vacuum_truncate_set`
separate flag and use TERNARY_UNSET instead.
This could also be used for other options such as `vacuum_index_cleanup`
and `buffering`, but lets get the scaffolding in first.
Discussion: https://postgr.es/m/3474141.usfYGdeWWP@thinkpad-pgpro
---
src/backend/access/common/reloptions.c | 137 ++++++++++++++++++-----
src/backend/commands/vacuum.c | 4 +-
src/include/access/reloptions.h | 27 ++---
src/include/postgres.h | 15 +++
src/include/utils/rel.h | 3 +-
src/test/regress/expected/reloptions.out | 36 ++++++
src/test/regress/sql/reloptions.sql | 21 ++++
src/tools/pgindent/typedefs.list | 2 +
8 files changed, 202 insertions(+), 43 deletions(-)
diff --git a/src/backend/access/common/reloptions.c b/src/backend/access/common/reloptions.c
index 0b83f98ed5f..6f1f577581a 100644
--- a/src/backend/access/common/reloptions.c
+++ b/src/backend/access/common/reloptions.c
@@ -40,9 +40,9 @@
*
* To add an option:
*
- * (i) decide on a type (bool, integer, real, enum, string), name, default
- * value, upper and lower bounds (if applicable); for strings, consider a
- * validation routine.
+ * (i) decide on a type (bool, ternary, integer, real, enum, string), name,
+ * default value, upper and lower bounds (if applicable); for strings,
+ * consider a validation routine.
* (ii) add a record below (or use add_<type>_reloption).
* (iii) add it to the appropriate options struct (perhaps StdRdOptions)
* (iv) add it to the appropriate handling routine (perhaps
@@ -147,15 +147,6 @@ static relopt_bool boolRelOpts[] =
},
false
},
- {
- {
- "vacuum_truncate",
- "Enables vacuum to truncate empty pages at the end of this table",
- RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
- ShareUpdateExclusiveLock
- },
- true
- },
{
{
"deduplicate_items",
@@ -170,6 +161,25 @@ static relopt_bool boolRelOpts[] =
{{NULL}}
};
+static relopt_ternary ternaryRelOpts[] =
+{
+ {
+ {
+ "vacuum_truncate",
+ "Enables vacuum to truncate empty pages at the end of this table",
+ RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
+ ShareUpdateExclusiveLock
+ },
+ TERNARY_UNSET
+ },
+ /* list terminator */
+ {
+ {
+ NULL
+ }
+ }
+};
+
static relopt_int intRelOpts[] =
{
{
@@ -609,6 +619,13 @@ initialize_reloptions(void)
boolRelOpts[i].gen.lockmode));
j++;
}
+ for (i = 0; ternaryRelOpts[i].gen.name; i++)
+ {
+ Assert(DoLockModesConflict(ternaryRelOpts[i].gen.lockmode,
+ ternaryRelOpts[i].gen.lockmode));
+ j++;
+ }
+
for (i = 0; intRelOpts[i].gen.name; i++)
{
Assert(DoLockModesConflict(intRelOpts[i].gen.lockmode,
@@ -649,6 +666,14 @@ initialize_reloptions(void)
j++;
}
+ for (i = 0; ternaryRelOpts[i].gen.name; i++)
+ {
+ relOpts[j] = &ternaryRelOpts[i].gen;
+ relOpts[j]->type = RELOPT_TYPE_TERNARY;
+ relOpts[j]->namelen = strlen(relOpts[j]->name);
+ j++;
+ }
+
for (i = 0; intRelOpts[i].gen.name; i++)
{
relOpts[j] = &intRelOpts[i].gen;
@@ -809,6 +834,9 @@ allocate_reloption(bits32 kinds, int type, const char *name, const char *desc,
case RELOPT_TYPE_BOOL:
size = sizeof(relopt_bool);
break;
+ case RELOPT_TYPE_TERNARY:
+ size = sizeof(relopt_ternary);
+ break;
case RELOPT_TYPE_INT:
size = sizeof(relopt_int);
break;
@@ -892,6 +920,57 @@ add_local_bool_reloption(local_relopts *relopts, const char *name,
add_local_reloption(relopts, (relopt_gen *) newoption, offset);
}
+/*
+ * init_ternary_reloption
+ * Allocate and initialize a new ternary reloption
+ */
+static relopt_ternary *
+init_ternary_reloption(bits32 kinds, const char *name, const char *desc,
+ pg_ternary default_val, LOCKMODE lockmode)
+{
+ relopt_ternary *newoption;
+
+ newoption = (relopt_ternary *)
+ allocate_reloption(kinds, RELOPT_TYPE_TERNARY, name, desc, lockmode);
+ newoption->default_val = default_val;
+
+ return newoption;
+}
+
+/*
+ * add_ternary_reloption
+ * Add a new ternary reloption
+ */
+void
+add_ternary_reloption(bits32 kinds, const char *name, const char *desc,
+ pg_ternary default_val, LOCKMODE lockmode)
+{
+ relopt_ternary *newoption;
+
+ newoption =
+ init_ternary_reloption(kinds, name, desc, default_val, lockmode);
+
+ add_reloption((relopt_gen *) newoption);
+}
+
+/*
+ * add_local_ternary_reloption
+ * Add a new ternary local reloption
+ *
+ * 'offset' is offset of ternary-typed field.
+ */
+void
+add_local_ternary_reloption(local_relopts *relopts, const char *name,
+ const char *desc, pg_ternary default_val,
+ int offset)
+{
+ relopt_ternary *newoption;
+
+ newoption =
+ init_ternary_reloption(RELOPT_KIND_LOCAL, name, desc, default_val, 0);
+
+ add_local_reloption(relopts, (relopt_gen *) newoption, offset);
+}
/*
* init_real_reloption
@@ -1626,6 +1705,19 @@ parse_one_reloption(relopt_value *option, char *text_str, int text_len,
option->gen->name, value)));
}
break;
+ case RELOPT_TYPE_TERNARY:
+ {
+ bool b;
+
+ parsed = parse_bool(value, &b);
+ option->values.ternary_val = b ? TERNARY_TRUE : TERNARY_FALSE;
+ if (validate && !parsed)
+ ereport(ERROR,
+ (errcode(ERRCODE_INVALID_PARAMETER_VALUE),
+ errmsg("invalid value for ternary option \"%s\": %s",
+ option->gen->name, value)));
+ }
+ break;
case RELOPT_TYPE_INT:
{
relopt_int *optint = (relopt_int *) option->gen;
@@ -1789,17 +1881,6 @@ fillRelOptions(void *rdopts, Size basesize,
char *itempos = ((char *) rdopts) + elems[j].offset;
char *string_val;
- /*
- * If isset_offset is provided, store whether the reloption is
- * set there.
- */
- if (elems[j].isset_offset > 0)
- {
- char *setpos = ((char *) rdopts) + elems[j].isset_offset;
-
- *(bool *) setpos = options[i].isset;
- }
-
switch (options[i].gen->type)
{
case RELOPT_TYPE_BOOL:
@@ -1807,6 +1888,11 @@ fillRelOptions(void *rdopts, Size basesize,
options[i].values.bool_val :
((relopt_bool *) options[i].gen)->default_val;
break;
+ case RELOPT_TYPE_TERNARY:
+ *(pg_ternary *) itempos = options[i].isset ?
+ options[i].values.ternary_val :
+ ((relopt_ternary *) options[i].gen)->default_val;
+ break;
case RELOPT_TYPE_INT:
*(int *) itempos = options[i].isset ?
options[i].values.int_val :
@@ -1923,8 +2009,8 @@ default_reloptions(Datum reloptions, bool validate, relopt_kind kind)
offsetof(StdRdOptions, parallel_workers)},
{"vacuum_index_cleanup", RELOPT_TYPE_ENUM,
offsetof(StdRdOptions, vacuum_index_cleanup)},
- {"vacuum_truncate", RELOPT_TYPE_BOOL,
- offsetof(StdRdOptions, vacuum_truncate), offsetof(StdRdOptions, vacuum_truncate_set)},
+ {"vacuum_truncate", RELOPT_TYPE_TERNARY,
+ offsetof(StdRdOptions, vacuum_truncate)},
{"vacuum_max_eager_freeze_failure_rate", RELOPT_TYPE_REAL,
offsetof(StdRdOptions, vacuum_max_eager_freeze_failure_rate)}
};
@@ -2004,7 +2090,6 @@ build_local_reloptions(local_relopts *relopts, Datum options, bool validate)
elems[i].optname = opt->option->name;
elems[i].opttype = opt->option->type;
elems[i].offset = opt->offset;
- elems[i].isset_offset = 0; /* not supported for local relopts yet */
i++;
}
diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c
index aa4fbec143f..696eab9bd97 100644
--- a/src/backend/commands/vacuum.c
+++ b/src/backend/commands/vacuum.c
@@ -2224,9 +2224,9 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams params,
{
StdRdOptions *opts = (StdRdOptions *) rel->rd_options;
- if (opts && opts->vacuum_truncate_set)
+ if (opts && opts->vacuum_truncate != TERNARY_UNSET)
{
- if (opts->vacuum_truncate)
+ if (opts->vacuum_truncate == TERNARY_TRUE)
params.truncate = VACOPTVALUE_ENABLED;
else
params.truncate = VACOPTVALUE_DISABLED;
diff --git a/src/include/access/reloptions.h b/src/include/access/reloptions.h
index 2f08e1b0cf0..dfbef2babf2 100644
--- a/src/include/access/reloptions.h
+++ b/src/include/access/reloptions.h
@@ -29,6 +29,7 @@
typedef enum relopt_type
{
RELOPT_TYPE_BOOL,
+ RELOPT_TYPE_TERNARY, /* on, off, unset */
RELOPT_TYPE_INT,
RELOPT_TYPE_REAL,
RELOPT_TYPE_ENUM,
@@ -80,6 +81,7 @@ typedef struct relopt_value
union
{
bool bool_val;
+ pg_ternary ternary_val;
int int_val;
double real_val;
int enum_val;
@@ -94,6 +96,12 @@ typedef struct relopt_bool
bool default_val;
} relopt_bool;
+typedef struct relopt_ternary
+{
+ relopt_gen gen;
+ int default_val;
+} relopt_ternary;
+
typedef struct relopt_int
{
relopt_gen gen;
@@ -152,19 +160,6 @@ typedef struct
const char *optname; /* option's name */
relopt_type opttype; /* option's datatype */
int offset; /* offset of field in result struct */
-
- /*
- * isset_offset is an optional offset of a field in the result struct that
- * stores whether the option is explicitly set for the relation or if it
- * just picked up the default value. In most cases, this can be
- * accomplished by giving the reloption a special out-of-range default
- * value (e.g., some integer reloptions use -2), but this isn't always
- * possible. For example, a Boolean reloption cannot be given an
- * out-of-range default, so we need another way to discover the source of
- * its value. This offset is only used if given a value greater than
- * zero.
- */
- int isset_offset;
} relopt_parse_elt;
/* Local reloption definition */
@@ -195,6 +190,9 @@ typedef struct local_relopts
extern relopt_kind add_reloption_kind(void);
extern void add_bool_reloption(bits32 kinds, const char *name, const char *desc,
bool default_val, LOCKMODE lockmode);
+extern void add_ternary_reloption(bits32 kinds, const char *name,
+ const char *desc, pg_ternary default_val,
+ LOCKMODE lockmode);
extern void add_int_reloption(bits32 kinds, const char *name, const char *desc,
int default_val, int min_val, int max_val,
LOCKMODE lockmode);
@@ -214,6 +212,9 @@ extern void register_reloptions_validator(local_relopts *relopts,
extern void add_local_bool_reloption(local_relopts *relopts, const char *name,
const char *desc, bool default_val,
int offset);
+extern void add_local_ternary_reloption(local_relopts *relopts,
+ const char *name, const char *desc,
+ pg_ternary default_val, int offset);
extern void add_local_int_reloption(local_relopts *relopts, const char *name,
const char *desc, int default_val,
int min_val, int max_val, int offset);
diff --git a/src/include/postgres.h b/src/include/postgres.h
index 7d93fbce709..47c88780776 100644
--- a/src/include/postgres.h
+++ b/src/include/postgres.h
@@ -543,6 +543,21 @@ Float8GetDatum(float8 X)
* ----------------------------------------------------------------
*/
+/*
+ * pg_ternary
+ * Boolean value with an extra "unset" value
+ *
+ * This enum can be used for values that want to distinguish between true,
+ * false, and unset.
+*/
+
+typedef enum pg_ternary
+{
+ TERNARY_FALSE = 0,
+ TERNARY_TRUE = 1,
+ TERNARY_UNSET = -1
+} pg_ternary;
+
/*
* NON_EXEC_STATIC: It's sometimes useful to define a variable or function
* that is normally static but extern when using EXEC_BACKEND (see
diff --git a/src/include/utils/rel.h b/src/include/utils/rel.h
index d03ab247788..5feb18a5373 100644
--- a/src/include/utils/rel.h
+++ b/src/include/utils/rel.h
@@ -347,8 +347,7 @@ typedef struct StdRdOptions
bool user_catalog_table; /* use as an additional catalog relation */
int parallel_workers; /* max number of parallel workers */
StdRdOptIndexCleanup vacuum_index_cleanup; /* controls index vacuuming */
- bool vacuum_truncate; /* enables vacuum to truncate a relation */
- bool vacuum_truncate_set; /* whether vacuum_truncate is set */
+ pg_ternary vacuum_truncate; /* enables vacuum to truncate a relation */
/*
* Fraction of pages in a relation that vacuum can eagerly scan and fail
diff --git a/src/test/regress/expected/reloptions.out b/src/test/regress/expected/reloptions.out
index 9de19b4e3f1..1c99f79ab01 100644
--- a/src/test/regress/expected/reloptions.out
+++ b/src/test/regress/expected/reloptions.out
@@ -98,6 +98,42 @@ SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass;
{fillfactor=13,autovacuum_enabled=false}
(1 row)
+-- Tests for future (FIXME) ternary options
+-- behave as boolean option: accept unassigned name and truncated value
+DROP TABLE reloptions_test;
+CREATE TABLE reloptions_test(i INT) WITH (vacuum_truncate);
+SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass;
+ reloptions
+------------------------
+ {vacuum_truncate=true}
+(1 row)
+
+DROP TABLE reloptions_test;
+CREATE TABLE reloptions_test(i INT) WITH (vacuum_truncate=FaLS);
+SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass;
+ reloptions
+------------------------
+ {vacuum_truncate=fals}
+(1 row)
+
+-- preferred "true" alias is used when storing
+DROP TABLE reloptions_test;
+CREATE TABLE reloptions_test(i INT) WITH (vacuum_index_cleanup=on);
+SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass;
+ reloptions
+---------------------------
+ {vacuum_index_cleanup=on}
+(1 row)
+
+-- custom "third" value is available
+DROP TABLE reloptions_test;
+CREATE TABLE reloptions_test(i INT) WITH (vacuum_index_cleanup=auto);
+SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass;
+ reloptions
+-----------------------------
+ {vacuum_index_cleanup=auto}
+(1 row)
+
-- Test vacuum_truncate option
DROP TABLE reloptions_test;
CREATE TEMP TABLE reloptions_test(i INT NOT NULL, j text)
diff --git a/src/test/regress/sql/reloptions.sql b/src/test/regress/sql/reloptions.sql
index 24fbe0b478d..f5980dafcbc 100644
--- a/src/test/regress/sql/reloptions.sql
+++ b/src/test/regress/sql/reloptions.sql
@@ -59,6 +59,27 @@ UPDATE pg_class
ALTER TABLE reloptions_test RESET (illegal_option);
SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass;
+-- Tests for future (FIXME) ternary options
+
+-- behave as boolean option: accept unassigned name and truncated value
+DROP TABLE reloptions_test;
+CREATE TABLE reloptions_test(i INT) WITH (vacuum_truncate);
+SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass;
+
+DROP TABLE reloptions_test;
+CREATE TABLE reloptions_test(i INT) WITH (vacuum_truncate=FaLS);
+SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass;
+
+-- preferred "true" alias is used when storing
+DROP TABLE reloptions_test;
+CREATE TABLE reloptions_test(i INT) WITH (vacuum_index_cleanup=on);
+SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass;
+
+-- custom "third" value is available
+DROP TABLE reloptions_test;
+CREATE TABLE reloptions_test(i INT) WITH (vacuum_index_cleanup=auto);
+SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass;
+
-- Test vacuum_truncate option
DROP TABLE reloptions_test;
diff --git a/src/tools/pgindent/typedefs.list b/src/tools/pgindent/typedefs.list
index 3f3a888fd0e..1c8610fd46c 100644
--- a/src/tools/pgindent/typedefs.list
+++ b/src/tools/pgindent/typedefs.list
@@ -3952,6 +3952,7 @@ pg_sha512_ctx
pg_snapshot
pg_special_case
pg_stack_base_t
+pg_ternary
pg_time_t
pg_time_usec_t
pg_tz
@@ -4079,6 +4080,7 @@ relopt_kind
relopt_parse_elt
relopt_real
relopt_string
+relopt_ternary
relopt_type
relopt_value
relopts_validator
--
2.47.3
--c36t3fndxhkzbl5z--
^ permalink raw reply [nested|flat] 868+ messages in thread
* [PATCH] Don't call ereport(ERROR) from recovery target GUC assign hooks
@ 2026-04-13 08:21 JoongHyuk Shin <[email protected]>
2026-04-24 01:10 ` Re: [PATCH] Don't call ereport(ERROR) from recovery target GUC assign hooks Greg Lamberson <[email protected]>
2026-04-24 13:08 ` Re: [PATCH] Don't call ereport(ERROR) from recovery target GUC assign hooks Fujii Masao <[email protected]>
0 siblings, 2 replies; 868+ messages in thread
From: JoongHyuk Shin @ 2026-04-13 08:21 UTC (permalink / raw)
To: [email protected]
The five recovery target GUC assign hooks -- assign_recovery_target,
assign_recovery_target_lsn, assign_recovery_target_name,
assign_recovery_target_time, and assign_recovery_target_xid -- all call
error_multiple_recovery_targets() when a second conflicting target is
detected, which invokes ereport(ERROR). The GUC README
(src/backend/utils/misc/README) is explicit: "There is no provision for
a failure result code. assign_hooks should never fail." Raising an
error from an assign hook leaves guc.c's internal state inconsistent
before the abort, because the abort handling path was not designed to
run mid-assign.
The code acknowledges this with an XXX comment that has been there for
years:
XXX this code is broken by design. Throwing an error from a GUC
assign hook breaks fundamental assumptions of guc.c. So long as
all the variables for which this can happen are PGC_POSTMASTER, the
consequences are limited, since we'd just abort postmaster startup
anyway. Nonetheless it's likely that we have odd behaviors such as
unexpected GUC ordering dependencies.
The "limited consequences" argument is true enough that this hasn't
caused visible failures in practice, but fixing a known contract
violation seems worthwhile.
The fix is to remove the conflict check from all five assign hooks and
detect conflicts in validateRecoveryParameters() instead. That
function already runs after all GUCs have been loaded (called from
InitWalRecovery() in the startup process), so it can safely read each
GUC's current value via GetConfigOption() and count how many are
non-empty. If more than one is set, it reports FATAL, consistent with
the other validation errors already in that function.
This changes when the error fires: it now happens in the startup process
rather than in the postmaster's ProcessConfigFile. The outcome is the
same (server does not start), but guc.c's state is no longer disturbed.
There is one secondary behavioral change: when recovery is not actually
requested (ArchiveRecoveryRequested is false), validateRecoveryParameters
returns early and never checks for conflicts, so conflicting recovery
target settings are silently ignored. The old code would reject them
even then, since assign hooks fire unconditionally during ProcessConfigFile.
I think the new behavior is arguably more correct -- those GUCs have no
effect when recovery is not requested, so there is no reason to treat
their values as an error. The existing TAP test in 003_recovery_targets.pl
already covers the conflict-in-recovery case; this patch adds a test for
the new behavior (conflicting targets accepted outside recovery).
Patch attached.
Attachments:
[application/octet-stream] 0001-Don-t-call-ereport-ERROR-from-recovery-target-GUC-as.patch (8.9K, ../../CACSdjfPUa4UvKjADgOERXoxNYmCg2mqqiqKkiJk6mX6E4qgVFw@mail.gmail.com/3-0001-Don-t-call-ereport-ERROR-from-recovery-target-GUC-as.patch)
download | inline diff:
From a4be55d99de64e47c368f7512d47164803467a03 Mon Sep 17 00:00:00 2001
From: JoongHyuk Shin <[email protected]>
Date: Mon, 13 Apr 2026 16:52:26 +0900
Subject: [PATCH] Don't call ereport(ERROR) from recovery target GUC assign
hooks
The five recovery target GUC assign hooks (assign_recovery_target,
assign_recovery_target_lsn, assign_recovery_target_name,
assign_recovery_target_time, assign_recovery_target_xid) all called
error_multiple_recovery_targets(), which invoked ereport(ERROR). The
GUC README explicitly states that assign hooks must never fail; raising
an error from an assign hook leaves guc.c's internal state inconsistent
before the abort. A comment in the code itself acknowledged this as
"broken by design."
Fix this by removing the conflict check from all five assign hooks and
detecting multiple recovery targets in validateRecoveryParameters()
instead. That function already runs after all GUCs have been loaded
(called from InitWalRecovery() in the startup process), so it can read
each GUC's current value via GetConfigOption() and count how many are
non-empty. If more than one is set, it reports FATAL, which is
consistent with the other validation errors in that function.
This changes the timing of the error: it now fires in the startup
process rather than in the postmaster's ProcessConfigFile. The outcome
is the same (server does not start), and the GUC infrastructure is left
in a consistent state.
A secondary behavioral change: conflicting recovery target settings are
now silently ignored when ArchiveRecoveryRequested is false (i.e., when
recovery is not actually requested). The previous code would reject
them even in normal startup, which was unnecessary since those GUCs have
no effect when recovery is not requested. A TAP test is added for this
new behavior.
---
src/backend/access/transam/xlogrecovery.c | 87 +++++++++++----------
src/test/recovery/t/003_recovery_targets.pl | 22 ++++++
2 files changed, 66 insertions(+), 43 deletions(-)
diff --git a/src/backend/access/transam/xlogrecovery.c b/src/backend/access/transam/xlogrecovery.c
index c236e2b7969..65eeb1214df 100644
--- a/src/backend/access/transam/xlogrecovery.c
+++ b/src/backend/access/transam/xlogrecovery.c
@@ -1070,6 +1070,43 @@ validateRecoveryParameters(void)
if (!ArchiveRecoveryRequested)
return;
+ /*
+ * Check for conflicting recovery targets. We do this here rather than in
+ * the GUC assign hooks because throwing an error from an assign hook
+ * violates guc.c's contract. By the time we reach this function, all
+ * GUCs have been loaded, so we can safely read their current values.
+ */
+ {
+ int ntargets = 0;
+ const char *val;
+
+ val = GetConfigOption("recovery_target", true, false);
+ if (val != NULL && val[0] != '\0')
+ ntargets++;
+ val = GetConfigOption("recovery_target_lsn", true, false);
+ if (val != NULL && val[0] != '\0')
+ ntargets++;
+ val = GetConfigOption("recovery_target_name", true, false);
+ if (val != NULL && val[0] != '\0')
+ ntargets++;
+ val = GetConfigOption("recovery_target_time", true, false);
+ if (val != NULL && val[0] != '\0')
+ ntargets++;
+ val = GetConfigOption("recovery_target_xid", true, false);
+ if (val != NULL && val[0] != '\0')
+ ntargets++;
+
+ if (ntargets > 1)
+ ereport(FATAL,
+ (errcode(ERRCODE_INVALID_PARAMETER_VALUE),
+ errmsg("multiple recovery targets specified"),
+ errdetail("At most one of \"recovery_target\", "
+ "\"recovery_target_lsn\", "
+ "\"recovery_target_name\", "
+ "\"recovery_target_time\", "
+ "\"recovery_target_xid\" may be set.")));
+ }
+
/*
* Check for compulsory parameters
*/
@@ -4765,31 +4802,15 @@ check_primary_slot_name(char **newval, void **extra, GucSource source)
/*
* Recovery target settings: Only one of the several recovery_target* settings
- * may be set. Setting a second one results in an error. The global variable
- * recoveryTarget tracks which kind of recovery target was chosen. Other
- * variables store the actual target value (for example a string or a xid).
- * The assign functions of the parameters check whether a competing parameter
- * was already set. But we want to allow setting the same parameter multiple
- * times. We also want to allow unsetting a parameter and setting a different
- * one, so we unset recoveryTarget when the parameter is set to an empty
- * string.
- *
- * XXX this code is broken by design. Throwing an error from a GUC assign
- * hook breaks fundamental assumptions of guc.c. So long as all the variables
- * for which this can happen are PGC_POSTMASTER, the consequences are limited,
- * since we'd just abort postmaster startup anyway. Nonetheless it's likely
- * that we have odd behaviors such as unexpected GUC ordering dependencies.
+ * may be set. The global variable recoveryTarget tracks which kind of
+ * recovery target was chosen. Other variables store the actual target value
+ * (for example a string or a xid). We want to allow setting the same
+ * parameter multiple times, and we want to allow unsetting a parameter and
+ * setting a different one, so we unset recoveryTarget when the parameter is
+ * set to an empty string. Conflicts between multiple non-empty settings are
+ * detected in validateRecoveryParameters().
*/
-pg_noreturn static void
-error_multiple_recovery_targets(void)
-{
- ereport(ERROR,
- (errcode(ERRCODE_INVALID_PARAMETER_VALUE),
- errmsg("multiple recovery targets specified"),
- errdetail("At most one of \"recovery_target\", \"recovery_target_lsn\", \"recovery_target_name\", \"recovery_target_time\", \"recovery_target_xid\" may be set.")));
-}
-
/*
* GUC check_hook for recovery_target
*/
@@ -4810,10 +4831,6 @@ check_recovery_target(char **newval, void **extra, GucSource source)
void
assign_recovery_target(const char *newval, void *extra)
{
- if (recoveryTarget != RECOVERY_TARGET_UNSET &&
- recoveryTarget != RECOVERY_TARGET_IMMEDIATE)
- error_multiple_recovery_targets();
-
if (newval && strcmp(newval, "") != 0)
recoveryTarget = RECOVERY_TARGET_IMMEDIATE;
else
@@ -4851,10 +4868,6 @@ check_recovery_target_lsn(char **newval, void **extra, GucSource source)
void
assign_recovery_target_lsn(const char *newval, void *extra)
{
- if (recoveryTarget != RECOVERY_TARGET_UNSET &&
- recoveryTarget != RECOVERY_TARGET_LSN)
- error_multiple_recovery_targets();
-
if (newval && strcmp(newval, "") != 0)
{
recoveryTarget = RECOVERY_TARGET_LSN;
@@ -4886,10 +4899,6 @@ check_recovery_target_name(char **newval, void **extra, GucSource source)
void
assign_recovery_target_name(const char *newval, void *extra)
{
- if (recoveryTarget != RECOVERY_TARGET_UNSET &&
- recoveryTarget != RECOVERY_TARGET_NAME)
- error_multiple_recovery_targets();
-
if (newval && strcmp(newval, "") != 0)
{
recoveryTarget = RECOVERY_TARGET_NAME;
@@ -4966,10 +4975,6 @@ check_recovery_target_time(char **newval, void **extra, GucSource source)
void
assign_recovery_target_time(const char *newval, void *extra)
{
- if (recoveryTarget != RECOVERY_TARGET_UNSET &&
- recoveryTarget != RECOVERY_TARGET_TIME)
- error_multiple_recovery_targets();
-
if (newval && strcmp(newval, "") != 0)
recoveryTarget = RECOVERY_TARGET_TIME;
else
@@ -5094,10 +5099,6 @@ check_recovery_target_xid(char **newval, void **extra, GucSource source)
void
assign_recovery_target_xid(const char *newval, void *extra)
{
- if (recoveryTarget != RECOVERY_TARGET_UNSET &&
- recoveryTarget != RECOVERY_TARGET_XID)
- error_multiple_recovery_targets();
-
if (newval && strcmp(newval, "") != 0)
{
recoveryTarget = RECOVERY_TARGET_XID;
diff --git a/src/test/recovery/t/003_recovery_targets.pl b/src/test/recovery/t/003_recovery_targets.pl
index 047eb13293a..4014c28bf95 100644
--- a/src/test/recovery/t/003_recovery_targets.pl
+++ b/src/test/recovery/t/003_recovery_targets.pl
@@ -190,6 +190,28 @@ like(
qr/FATAL: .* recovery ended before configured recovery target was reached/,
'recovery end before target reached is a fatal error');
+# Conflicting recovery targets without recovery mode
+#
+# When recovery is not requested (no recovery.signal), conflicting recovery
+# target settings should be silently accepted, since those GUCs have no
+# effect outside of recovery.
+$node_primary->stop;
+$node_primary->append_conf(
+ 'postgresql.conf', "recovery_target_name = '$recovery_name'
+recovery_target_time = '$recovery_time'");
+$node_primary->start;
+my $primary_pid = $node_primary->safe_psql('postgres',
+ 'SELECT pg_backend_pid()');
+ok(defined $primary_pid && $primary_pid ne '',
+ 'server starts despite conflicting recovery targets outside recovery');
+
+# Clean up the conflicting settings before continuing
+$node_primary->safe_psql('postgres',
+ "ALTER SYSTEM RESET recovery_target_name");
+$node_primary->safe_psql('postgres',
+ "ALTER SYSTEM RESET recovery_target_time");
+$node_primary->restart;
+
# Invalid recovery_target_timeline tests
my ($result, $stdout, $stderr) = $node_primary->psql('postgres',
"ALTER SYSTEM SET recovery_target_timeline TO 'bogus'");
--
2.52.0
^ permalink raw reply [nested|flat] 868+ messages in thread
* Re: [PATCH] Don't call ereport(ERROR) from recovery target GUC assign hooks
2026-04-13 08:21 [PATCH] Don't call ereport(ERROR) from recovery target GUC assign hooks JoongHyuk Shin <[email protected]>
@ 2026-04-24 01:10 ` Greg Lamberson <[email protected]>
1 sibling, 0 replies; 868+ messages in thread
From: Greg Lamberson @ 2026-04-24 01:10 UTC (permalink / raw)
To: [email protected]; +Cc: [email protected]
Hi Shin,
Thanks for taking this one on. The XXX comment has been there for a
long time and fixing a known contract violation is worthwhile. The
mechanical part of the patch, moving the conflict check from the
assign hooks into validateRecoveryParameters(), looks right to me.
A few things worth discussing before this gets a committer's eye:
1. Behavioral change when recovery is not requested.
The patch's commit message notes that conflicting recovery target
settings are now silently accepted when ArchiveRecoveryRequested is
false, and argues this is "arguably more correct" because those GUCs
have no effect outside recovery. I am not sure I agree. Today a
misconfigured postgresql.conf (say, both recovery_target_time and
recovery_target_xid present) is caught immediately at postmaster
start. After the patch, that same misconfiguration boots
successfully, and the operator only finds out later when they add
recovery.signal and the startup process FATALs. That is a real
downgrade in error-detection timing.
Is it feasible to keep the early-detection behavior without violating
the assign-hook contract? One option: emit a WARNING (not ERROR)
from the assign hook, or defer the check to a post-config-file pass
(GUC_check_hook chain, or a one-shot check in PostmasterMain before
the startup process is forked). I do not have a strong opinion on
the mechanism, but I think the user-visible behavior (conflicting
recovery_target_* settings are caught at server start) is worth
preserving if possible.
If preserving that is not feasible, I think the commit message should
flag the timing change more prominently, since it is a user-visible
change to when postgres refuses to start.
2. Test coverage of the conflict-in-recovery path.
The existing 003_recovery_targets.pl has a test (not modified by
this patch, as far as I can tell) that exercises the multiple-targets
rejection. With v1, that rejection fires from
validateRecoveryParameters() rather than from an assign hook, so the
error message source changes even if the text is identical. CFBot
is green, so the existing regex must still match, but it would be
good to confirm which test covers this and to verify the test's
assertions actually validate the new FATAL path, not just "server
failed to start for some reason".
3. errdetail wording.
errdetail("At most one of \"recovery_target\", ..., "
"\"recovery_target_xid\" may be set.")
The error-message style guide (doc/src/sgml/error-style-guide.sgml)
says to avoid "may" because it reads as permission rather than
ability. Suggest "can be set" instead. Trivial, but since we are
already touching this message.
4. Test cleanup fragility.
The new test case does:
$node_primary->append_conf('postgresql.conf', "recovery_target_name = ...
recovery_target_time = ...");
$node_primary->start;
...
ALTER SYSTEM RESET recovery_target_name;
ALTER SYSTEM RESET recovery_target_time;
$node_primary->restart;
append_conf permanently extends postgresql.conf. The ALTER SYSTEM
RESET writes to postgresql.auto.conf which takes precedence, so the
stale postgresql.conf lines are effectively masked for subsequent
test steps, but they remain in the file, and any later test that
relies on a predictable postgresql.conf content (for example a test
that inspects ConfigFileVar or does its own append) could be
confused. Using ALTER SYSTEM SET for the setup would be cleaner,
or using a dedicated temporary cluster for this one case. Also,
the ok() predicate `defined $primary_pid && $primary_pid ne ''` is
redundant. safe_psql would have died earlier on start failure.
5. Nit: block comment.
The new block inside validateRecoveryParameters() could benefit from
noting that ArchiveRecoveryRequested is guaranteed true at this
point (because of the early return above it), so a reader does not
have to scroll up to confirm.
Nothing here is a blocker. I think the overall direction is right.
The behavioral change question in item 1 is the main design decision
I would want the author's thinking on.
Thanks,
Greg
^ permalink raw reply [nested|flat] 868+ messages in thread
* Re: [PATCH] Don't call ereport(ERROR) from recovery target GUC assign hooks
2026-04-13 08:21 [PATCH] Don't call ereport(ERROR) from recovery target GUC assign hooks JoongHyuk Shin <[email protected]>
@ 2026-04-24 13:08 ` Fujii Masao <[email protected]>
2026-04-27 01:52 ` Re: [PATCH] Don't call ereport(ERROR) from recovery target GUC assign hooks Michael Paquier <[email protected]>
1 sibling, 1 reply; 868+ messages in thread
From: Fujii Masao @ 2026-04-24 13:08 UTC (permalink / raw)
To: JoongHyuk Shin <[email protected]>; +Cc: [email protected]
On Mon, Apr 13, 2026 at 5:21 PM JoongHyuk Shin <[email protected]> wrote:
> The "limited consequences" argument is true enough that this hasn't
> caused visible failures in practice, but fixing a known contract
> violation seems worthwhile.
+1
> The fix is to remove the conflict check from all five assign hooks and
> detect conflicts in validateRecoveryParameters() instead. That
> function already runs after all GUCs have been loaded (called from
> InitWalRecovery() in the startup process), so it can safely read each
> GUC's current value via GetConfigOption() and count how many are
> non-empty. If more than one is set, it reports FATAL, consistent with
> the other validation errors already in that function.
In the master, when the following two recovery targets are specified,
the recovery target assign hook detects that multiple targets were given
and reports an error. With the patch, however, the same settings do not
raise an error, recoveryTarget is set to RECOVERY_TARGET_UNSET, and
recovery unexpectedly proceeds with no target. Could this be a bug
in the patch?
recovery_target_xid = '9999'
recovery_target_time = ''
Regards,
--
Fujii Masao
^ permalink raw reply [nested|flat] 868+ messages in thread
* Re: [PATCH] Don't call ereport(ERROR) from recovery target GUC assign hooks
2026-04-13 08:21 [PATCH] Don't call ereport(ERROR) from recovery target GUC assign hooks JoongHyuk Shin <[email protected]>
2026-04-24 13:08 ` Re: [PATCH] Don't call ereport(ERROR) from recovery target GUC assign hooks Fujii Masao <[email protected]>
@ 2026-04-27 01:52 ` Michael Paquier <[email protected]>
2026-04-27 05:36 ` Re: [PATCH] Don't call ereport(ERROR) from recovery target GUC assign hooks Fujii Masao <[email protected]>
0 siblings, 1 reply; 868+ messages in thread
From: Michael Paquier @ 2026-04-27 01:52 UTC (permalink / raw)
To: Fujii Masao <[email protected]>; +Cc: JoongHyuk Shin <[email protected]>; [email protected]
On Fri, Apr 24, 2026 at 10:08:04PM +0900, Fujii Masao wrote:
> In the master, when the following two recovery targets are specified,
> the recovery target assign hook detects that multiple targets were given
> and reports an error. With the patch, however, the same settings do not
> raise an error, recoveryTarget is set to RECOVERY_TARGET_UNSET, and
> recovery unexpectedly proceeds with no target. Could this be a bug
> in the patch?
>
> recovery_target_xid = '9999'
> recovery_target_time = ''
Don't think so. You are specifying for recovery_target_time the same
thing as the default, as in "I don't know and do nothing about the
time". Why would it matter to make the difference between a default
value set and what's stored by default if nothing is set in this case?
FWIW, I am wondering if we should seriously consider this stuff as
candidate for a backpatch because this is a design mistake: we should
never *ever* rely on the GUC hooks to do cross-checks of multiple
values, f2cbffc7a618 deciding that it was a right thing to do. It's
not. The risk of breaking something may not justify that a backpatch.
+1 for reworking that on HEAD, at least.
--
Michael
Attachments:
[application/pgp-signature] signature.asc (833B, ../../[email protected]/2-signature.asc)
download
^ permalink raw reply [nested|flat] 868+ messages in thread
* Re: [PATCH] Don't call ereport(ERROR) from recovery target GUC assign hooks
2026-04-13 08:21 [PATCH] Don't call ereport(ERROR) from recovery target GUC assign hooks JoongHyuk Shin <[email protected]>
2026-04-24 13:08 ` Re: [PATCH] Don't call ereport(ERROR) from recovery target GUC assign hooks Fujii Masao <[email protected]>
2026-04-27 01:52 ` Re: [PATCH] Don't call ereport(ERROR) from recovery target GUC assign hooks Michael Paquier <[email protected]>
@ 2026-04-27 05:36 ` Fujii Masao <[email protected]>
2026-04-27 05:59 ` Re: [PATCH] Don't call ereport(ERROR) from recovery target GUC assign hooks Michael Paquier <[email protected]>
0 siblings, 1 reply; 868+ messages in thread
From: Fujii Masao @ 2026-04-27 05:36 UTC (permalink / raw)
To: Michael Paquier <[email protected]>; +Cc: JoongHyuk Shin <[email protected]>; [email protected]
On Mon, Apr 27, 2026 at 10:52 AM Michael Paquier <[email protected]> wrote:
>
> On Fri, Apr 24, 2026 at 10:08:04PM +0900, Fujii Masao wrote:
> > In the master, when the following two recovery targets are specified,
> > the recovery target assign hook detects that multiple targets were given
> > and reports an error. With the patch, however, the same settings do not
> > raise an error, recoveryTarget is set to RECOVERY_TARGET_UNSET, and
> > recovery unexpectedly proceeds with no target. Could this be a bug
> > in the patch?
> >
> > recovery_target_xid = '9999'
> > recovery_target_time = ''
>
> Don't think so. You are specifying for recovery_target_time the same
> thing as the default, as in "I don't know and do nothing about the
> time". Why would it matter to make the difference between a default
> value set and what's stored by default if nothing is set in this case?
With those settings, how should recovery behave? I would expect it to
behave as in master, i.e., detect that multiple targets were specified
and report an error. Alternatively, it might be OK for me to proceed
with recovery_target_xid = '9999' and ignore recovery_target_time = '',
since that matches the default.
With the proposed patch, however, both settings are ignored and
recovery starts with no target. That seems unexpected to me.
> +1 for reworking that on HEAD, at least.
I was thinking the same. +1
Regards,
--
Fujii Masao
^ permalink raw reply [nested|flat] 868+ messages in thread
* Re: [PATCH] Don't call ereport(ERROR) from recovery target GUC assign hooks
2026-04-13 08:21 [PATCH] Don't call ereport(ERROR) from recovery target GUC assign hooks JoongHyuk Shin <[email protected]>
2026-04-24 13:08 ` Re: [PATCH] Don't call ereport(ERROR) from recovery target GUC assign hooks Fujii Masao <[email protected]>
2026-04-27 01:52 ` Re: [PATCH] Don't call ereport(ERROR) from recovery target GUC assign hooks Michael Paquier <[email protected]>
2026-04-27 05:36 ` Re: [PATCH] Don't call ereport(ERROR) from recovery target GUC assign hooks Fujii Masao <[email protected]>
@ 2026-04-27 05:59 ` Michael Paquier <[email protected]>
2026-04-29 09:29 ` Re: [PATCH] Don't call ereport(ERROR) from recovery target GUC assign hooks JoongHyuk Shin <[email protected]>
0 siblings, 1 reply; 868+ messages in thread
From: Michael Paquier @ 2026-04-27 05:59 UTC (permalink / raw)
To: Fujii Masao <[email protected]>; +Cc: JoongHyuk Shin <[email protected]>; [email protected]
On Mon, Apr 27, 2026 at 02:36:11PM +0900, Fujii Masao wrote:
> With the proposed patch, however, both settings are ignored and
> recovery starts with no target. That seems unexpected to me.
If that's the case (not tested myself), agreed.
--
Michael
Attachments:
[application/pgp-signature] signature.asc (833B, ../../[email protected]/2-signature.asc)
download
^ permalink raw reply [nested|flat] 868+ messages in thread
* Re: [PATCH] Don't call ereport(ERROR) from recovery target GUC assign hooks
2026-04-13 08:21 [PATCH] Don't call ereport(ERROR) from recovery target GUC assign hooks JoongHyuk Shin <[email protected]>
2026-04-24 13:08 ` Re: [PATCH] Don't call ereport(ERROR) from recovery target GUC assign hooks Fujii Masao <[email protected]>
2026-04-27 01:52 ` Re: [PATCH] Don't call ereport(ERROR) from recovery target GUC assign hooks Michael Paquier <[email protected]>
2026-04-27 05:36 ` Re: [PATCH] Don't call ereport(ERROR) from recovery target GUC assign hooks Fujii Masao <[email protected]>
2026-04-27 05:59 ` Re: [PATCH] Don't call ereport(ERROR) from recovery target GUC assign hooks Michael Paquier <[email protected]>
@ 2026-04-29 09:29 ` JoongHyuk Shin <[email protected]>
2026-05-01 05:53 ` Re: [PATCH] Don't call ereport(ERROR) from recovery target GUC assign hooks Fujii Masao <[email protected]>
0 siblings, 1 reply; 868+ messages in thread
From: JoongHyuk Shin @ 2026-04-29 09:29 UTC (permalink / raw)
To: Michael Paquier <[email protected]>; +Cc: Fujii Masao <[email protected]>; [email protected]
Thanks for the reviews.
v2 attached.
* Conflict check moved to a new static CheckRecoveryTargetConflicts(),
called from validateRecoveryParameters() before its early return.
It runs at every startup, so misconfiguration is caught as in master.
I kept it in startup process rather than PostmasterMain
(Greg'ssuggestion),
matching the existing recovery validation there.
* Removed each assign hook's `else recoveryTarget = UNSET` branch
(B in Fujii's framing). Fixes the empty-string clobber Fujii reported,
`recovery_target_xid='9999' + recovery_target_time=''` was silently
running with no target.
003_recovery_targets.pl now covers it (fails on v1, passes on v2).
* errdetail "may" -> "can" (Greg).
* TAP test that asserted the v1 regression is replaced with one
asserting conflict rejection at every startup.
Agreed: HEAD only, no backpatch.
--
JH Shin
On Mon, Apr 27, 2026 at 3:00 PM Michael Paquier <[email protected]> wrote:
> On Mon, Apr 27, 2026 at 02:36:11PM +0900, Fujii Masao wrote:
> > With the proposed patch, however, both settings are ignored and
> > recovery starts with no target. That seems unexpected to me.
>
> If that's the case (not tested myself), agreed.
> --
> Michael
>
Attachments:
[application/octet-stream] v2-0001-Don-t-call-ereport-ERROR-from-recovery-target-GUC.patch (12.4K, ../../CACSdjfPCPjP9tuzxgarcbkjahu-n+o+LJbUQ22aPNkOsOvrEZg@mail.gmail.com/3-v2-0001-Don-t-call-ereport-ERROR-from-recovery-target-GUC.patch)
download | inline diff:
From 2676f4ad037fec97be49dd4099b65bd8cf569119 Mon Sep 17 00:00:00 2001
From: JoongHyuk Shin <[email protected]>
Date: Wed, 29 Apr 2026 15:32:21 +0900
Subject: [PATCH v2] Don't call ereport(ERROR) from recovery target GUC assign
hooks
The five recovery target GUC assign hooks (assign_recovery_target,
assign_recovery_target_lsn, assign_recovery_target_name,
assign_recovery_target_time, assign_recovery_target_xid) all called
error_multiple_recovery_targets(), which invoked ereport(ERROR). The
GUC README explicitly states that assign hooks must never fail; raising
an error from an assign hook leaves guc.c's internal state inconsistent
before the abort. A comment in the code itself acknowledged this as
"broken by design."
Fix this by removing the conflict check from all five assign hooks and
detecting multiple recovery targets in a new CheckRecoveryTargetConflicts()
function, called from validateRecoveryParameters() before its early
return for !ArchiveRecoveryRequested. The check therefore runs at
every startup regardless of recovery mode, preserving the existing
behavior of detecting misconfiguration at server start time (rather
than only when recovery.signal is added).
Also remove the unconditional `recoveryTarget = RECOVERY_TARGET_UNSET`
fallback in each assign hook's empty-string branch. An empty string is
the GUCs' default value and is now treated as a no-op rather than as
an explicit "unset all targets" signal. This fixes a corner case
where setting one GUC to a value and another to an empty string would
silently clobber the first GUC's target, leaving recovery without any
target. This corner case was reported by Fujii Masao during v1 review.
Reported-by: Fujii Masao
Discussion: https://postgr.es/m/CACSdjfPUa4UvKjADgOERXoxNYmCg2mqqiqKkiJk6mX6E4qgVFw@mail.gmail.com
---
src/backend/access/transam/xlogrecovery.c | 118 +++++++++++---------
src/test/recovery/t/003_recovery_targets.pl | 48 +++++++-
2 files changed, 111 insertions(+), 55 deletions(-)
diff --git a/src/backend/access/transam/xlogrecovery.c b/src/backend/access/transam/xlogrecovery.c
index c236e2b7969..5cabd079d57 100644
--- a/src/backend/access/transam/xlogrecovery.c
+++ b/src/backend/access/transam/xlogrecovery.c
@@ -341,6 +341,7 @@ static void ApplyWalRecord(XLogReaderState *xlogreader, XLogRecord *record, Time
static void EnableStandbyMode(void);
static void readRecoverySignalFile(void);
static void validateRecoveryParameters(void);
+static void CheckRecoveryTargetConflicts(void);
static bool read_backup_label(XLogRecPtr *checkPointLoc,
TimeLineID *backupLabelTLI,
bool *backupEndRequired, bool *backupFromStandby);
@@ -1067,6 +1068,8 @@ readRecoverySignalFile(void)
static void
validateRecoveryParameters(void)
{
+ CheckRecoveryTargetConflicts();
+
if (!ArchiveRecoveryRequested)
return;
@@ -1144,6 +1147,62 @@ validateRecoveryParameters(void)
}
}
+/*
+ * CheckRecoveryTargetConflicts
+ *
+ * Validate that at most one of the recovery_target_* GUCs is set to a
+ * non-empty value. This is called from validateRecoveryParameters() at every
+ * server startup, regardless of whether archive recovery is requested, so
+ * misconfiguration is detected at server start time rather than later when
+ * recovery.signal is added.
+ *
+ * The check is a separate function rather than inlined into
+ * validateRecoveryParameters() because it intentionally runs even when no
+ * recovery is requested, while the rest of validateRecoveryParameters() is
+ * recovery-mode-only. Keeping it as a named function makes that separation
+ * explicit.
+ *
+ * The check used to live in the assign hooks of the recovery_target_* GUCs
+ * (calling ereport(ERROR) on conflict), which violated guc.c's contract that
+ * assign hooks must never fail. Moving the check here keeps the assign hooks
+ * contract-compliant.
+ *
+ * If a future patch adds a sixth recovery_target_* GUC, both this list and
+ * the errdetail below must be updated.
+ */
+static void
+CheckRecoveryTargetConflicts(void)
+{
+ int ntargets = 0;
+ const char *val;
+
+ val = GetConfigOption("recovery_target", false, false);
+ if (val != NULL && val[0] != '\0')
+ ntargets++;
+ val = GetConfigOption("recovery_target_lsn", false, false);
+ if (val != NULL && val[0] != '\0')
+ ntargets++;
+ val = GetConfigOption("recovery_target_name", false, false);
+ if (val != NULL && val[0] != '\0')
+ ntargets++;
+ val = GetConfigOption("recovery_target_time", false, false);
+ if (val != NULL && val[0] != '\0')
+ ntargets++;
+ val = GetConfigOption("recovery_target_xid", false, false);
+ if (val != NULL && val[0] != '\0')
+ ntargets++;
+
+ if (ntargets > 1)
+ ereport(FATAL,
+ (errcode(ERRCODE_INVALID_PARAMETER_VALUE),
+ errmsg("multiple recovery targets specified"),
+ errdetail("At most one of \"recovery_target\", "
+ "\"recovery_target_lsn\", "
+ "\"recovery_target_name\", "
+ "\"recovery_target_time\", "
+ "\"recovery_target_xid\" can be set.")));
+}
+
/*
* read_backup_label: check to see if a backup_label file is present
*
@@ -4764,32 +4823,17 @@ check_primary_slot_name(char **newval, void **extra, GucSource source)
}
/*
- * Recovery target settings: Only one of the several recovery_target* settings
- * may be set. Setting a second one results in an error. The global variable
+ * Recovery target settings: At most one of the several recovery_target*
+ * settings may be set to a non-empty value. The global variable
* recoveryTarget tracks which kind of recovery target was chosen. Other
* variables store the actual target value (for example a string or a xid).
- * The assign functions of the parameters check whether a competing parameter
- * was already set. But we want to allow setting the same parameter multiple
- * times. We also want to allow unsetting a parameter and setting a different
- * one, so we unset recoveryTarget when the parameter is set to an empty
- * string.
- *
- * XXX this code is broken by design. Throwing an error from a GUC assign
- * hook breaks fundamental assumptions of guc.c. So long as all the variables
- * for which this can happen are PGC_POSTMASTER, the consequences are limited,
- * since we'd just abort postmaster startup anyway. Nonetheless it's likely
- * that we have odd behaviors such as unexpected GUC ordering dependencies.
+ * An empty string for any of these GUCs is treated as "not set", equivalent
+ * to the GUC's default; setting a GUC to an empty string is a no-op and does
+ * not clear other already-set targets. Conflicts between multiple non-empty
+ * settings are detected in CheckRecoveryTargetConflicts(), called from
+ * validateRecoveryParameters() at every startup.
*/
-pg_noreturn static void
-error_multiple_recovery_targets(void)
-{
- ereport(ERROR,
- (errcode(ERRCODE_INVALID_PARAMETER_VALUE),
- errmsg("multiple recovery targets specified"),
- errdetail("At most one of \"recovery_target\", \"recovery_target_lsn\", \"recovery_target_name\", \"recovery_target_time\", \"recovery_target_xid\" may be set.")));
-}
-
/*
* GUC check_hook for recovery_target
*/
@@ -4810,14 +4854,8 @@ check_recovery_target(char **newval, void **extra, GucSource source)
void
assign_recovery_target(const char *newval, void *extra)
{
- if (recoveryTarget != RECOVERY_TARGET_UNSET &&
- recoveryTarget != RECOVERY_TARGET_IMMEDIATE)
- error_multiple_recovery_targets();
-
if (newval && strcmp(newval, "") != 0)
recoveryTarget = RECOVERY_TARGET_IMMEDIATE;
- else
- recoveryTarget = RECOVERY_TARGET_UNSET;
}
/*
@@ -4851,17 +4889,11 @@ check_recovery_target_lsn(char **newval, void **extra, GucSource source)
void
assign_recovery_target_lsn(const char *newval, void *extra)
{
- if (recoveryTarget != RECOVERY_TARGET_UNSET &&
- recoveryTarget != RECOVERY_TARGET_LSN)
- error_multiple_recovery_targets();
-
if (newval && strcmp(newval, "") != 0)
{
recoveryTarget = RECOVERY_TARGET_LSN;
recoveryTargetLSN = *((XLogRecPtr *) extra);
}
- else
- recoveryTarget = RECOVERY_TARGET_UNSET;
}
/*
@@ -4886,17 +4918,11 @@ check_recovery_target_name(char **newval, void **extra, GucSource source)
void
assign_recovery_target_name(const char *newval, void *extra)
{
- if (recoveryTarget != RECOVERY_TARGET_UNSET &&
- recoveryTarget != RECOVERY_TARGET_NAME)
- error_multiple_recovery_targets();
-
if (newval && strcmp(newval, "") != 0)
{
recoveryTarget = RECOVERY_TARGET_NAME;
recoveryTargetName = newval;
}
- else
- recoveryTarget = RECOVERY_TARGET_UNSET;
}
/*
@@ -4966,14 +4992,8 @@ check_recovery_target_time(char **newval, void **extra, GucSource source)
void
assign_recovery_target_time(const char *newval, void *extra)
{
- if (recoveryTarget != RECOVERY_TARGET_UNSET &&
- recoveryTarget != RECOVERY_TARGET_TIME)
- error_multiple_recovery_targets();
-
if (newval && strcmp(newval, "") != 0)
recoveryTarget = RECOVERY_TARGET_TIME;
- else
- recoveryTarget = RECOVERY_TARGET_UNSET;
}
/*
@@ -5094,15 +5114,9 @@ check_recovery_target_xid(char **newval, void **extra, GucSource source)
void
assign_recovery_target_xid(const char *newval, void *extra)
{
- if (recoveryTarget != RECOVERY_TARGET_UNSET &&
- recoveryTarget != RECOVERY_TARGET_XID)
- error_multiple_recovery_targets();
-
if (newval && strcmp(newval, "") != 0)
{
recoveryTarget = RECOVERY_TARGET_XID;
recoveryTargetXid = *((TransactionId *) extra);
}
- else
- recoveryTarget = RECOVERY_TARGET_UNSET;
}
diff --git a/src/test/recovery/t/003_recovery_targets.pl b/src/test/recovery/t/003_recovery_targets.pl
index 047eb13293a..ef22a80252b 100644
--- a/src/test/recovery/t/003_recovery_targets.pl
+++ b/src/test/recovery/t/003_recovery_targets.pl
@@ -125,11 +125,24 @@ test_recovery_standby('name', 'standby_4', $node_primary, \@recovery_params,
test_recovery_standby('LSN', 'standby_5', $node_primary, \@recovery_params,
"5000", $lsn5);
+# Regression: empty-string for one recovery_target_* GUC must not clobber
+# another non-empty target. Setting recovery_target_xid + recovery_target_time
+# = '' must recover to the xid, not run as no-target recovery.
+@recovery_params = (
+ "recovery_target_xid = '$recovery_txid'",
+ "recovery_target_time = ''");
+test_recovery_standby('xid with empty time GUC',
+ 'standby_xid_empty_time', $node_primary, \@recovery_params,
+ "2000", $lsn2);
+
# Multiple targets
#
-# Multiple conflicting settings are not allowed, but setting the same
-# parameter multiple times or unsetting a parameter and setting a
-# different one is allowed.
+# Multiple conflicting non-empty settings are not allowed. Setting the same
+# parameter multiple times is allowed (the last value wins, per ProcessConfigFile
+# duplicate handling). An empty string for a recovery_target_* GUC is treated
+# as the GUC's default and is a no-op; it does not clear any other already-set
+# target. Conflict detection runs in CheckRecoveryTargetConflicts() at every
+# server start, regardless of whether recovery is requested.
@recovery_params = (
"recovery_target_name = '$recovery_name'",
@@ -190,6 +203,35 @@ like(
qr/FATAL: .* recovery ended before configured recovery target was reached/,
'recovery end before target reached is a fatal error');
+# Conflicting recovery targets are rejected at every startup, regardless of
+# whether recovery.signal is present. Use a throwaway cluster initialized
+# from the existing backup so we can leave conflicting GUCs in its
+# postgresql.conf without polluting the primary used by later tests.
+# init_from_backup is called without has_restoring, so no recovery.signal
+# is created and the cluster would normally start as a plain primary; the
+# conflicting recovery_target_* GUCs must be rejected anyway.
+my $node_no_signal = PostgreSQL::Test::Cluster->new('multi_target_no_signal');
+$node_no_signal->init_from_backup($node_primary, 'my_backup');
+$node_no_signal->append_conf(
+ 'postgresql.conf', "recovery_target_name = '$recovery_name'
+recovery_target_time = '$recovery_time'");
+
+my $res_no_signal = run_log(
+ [
+ 'pg_ctl',
+ '--pgdata' => $node_no_signal->data_dir,
+ '--log' => $node_no_signal->logfile,
+ 'start',
+ ]);
+ok(!$res_no_signal,
+ 'server fails to start with conflicting recovery targets and no recovery.signal');
+
+my $logfile_no_signal = slurp_file($node_no_signal->logfile());
+like(
+ $logfile_no_signal,
+ qr/multiple recovery targets specified/,
+ 'expected error message logged without recovery.signal');
+
# Invalid recovery_target_timeline tests
my ($result, $stdout, $stderr) = $node_primary->psql('postgres',
"ALTER SYSTEM SET recovery_target_timeline TO 'bogus'");
--
2.52.0
^ permalink raw reply [nested|flat] 868+ messages in thread
* Re: [PATCH] Don't call ereport(ERROR) from recovery target GUC assign hooks
2026-04-13 08:21 [PATCH] Don't call ereport(ERROR) from recovery target GUC assign hooks JoongHyuk Shin <[email protected]>
2026-04-24 13:08 ` Re: [PATCH] Don't call ereport(ERROR) from recovery target GUC assign hooks Fujii Masao <[email protected]>
2026-04-27 01:52 ` Re: [PATCH] Don't call ereport(ERROR) from recovery target GUC assign hooks Michael Paquier <[email protected]>
2026-04-27 05:36 ` Re: [PATCH] Don't call ereport(ERROR) from recovery target GUC assign hooks Fujii Masao <[email protected]>
2026-04-27 05:59 ` Re: [PATCH] Don't call ereport(ERROR) from recovery target GUC assign hooks Michael Paquier <[email protected]>
2026-04-29 09:29 ` Re: [PATCH] Don't call ereport(ERROR) from recovery target GUC assign hooks JoongHyuk Shin <[email protected]>
@ 2026-05-01 05:53 ` Fujii Masao <[email protected]>
2026-05-11 03:17 ` Re: [PATCH] Don't call ereport(ERROR) from recovery target GUC assign hooks JoongHyuk Shin <[email protected]>
0 siblings, 1 reply; 868+ messages in thread
From: Fujii Masao @ 2026-05-01 05:53 UTC (permalink / raw)
To: JoongHyuk Shin <[email protected]>; +Cc: Michael Paquier <[email protected]>; [email protected]
On Wed, Apr 29, 2026 at 6:30 PM JoongHyuk Shin <[email protected]> wrote:
>
> Thanks for the reviews.
>
> v2 attached.
Thanks for updating the patch!
When I started postgres with the following command, recovery_target_xid was
treated as unset in the master, but with the patch the recovery_target_xid=700
setting was used instead. This behavior seems unexpected to me. Thoughts?
postgres -D data -c "recovery_target_xid=700" -c "recovery_target_xid="
Regards,
--
Fujii Masao
^ permalink raw reply [nested|flat] 868+ messages in thread
* Re: [PATCH] Don't call ereport(ERROR) from recovery target GUC assign hooks
2026-04-13 08:21 [PATCH] Don't call ereport(ERROR) from recovery target GUC assign hooks JoongHyuk Shin <[email protected]>
2026-04-24 13:08 ` Re: [PATCH] Don't call ereport(ERROR) from recovery target GUC assign hooks Fujii Masao <[email protected]>
2026-04-27 01:52 ` Re: [PATCH] Don't call ereport(ERROR) from recovery target GUC assign hooks Michael Paquier <[email protected]>
2026-04-27 05:36 ` Re: [PATCH] Don't call ereport(ERROR) from recovery target GUC assign hooks Fujii Masao <[email protected]>
2026-04-27 05:59 ` Re: [PATCH] Don't call ereport(ERROR) from recovery target GUC assign hooks Michael Paquier <[email protected]>
2026-04-29 09:29 ` Re: [PATCH] Don't call ereport(ERROR) from recovery target GUC assign hooks JoongHyuk Shin <[email protected]>
2026-05-01 05:53 ` Re: [PATCH] Don't call ereport(ERROR) from recovery target GUC assign hooks Fujii Masao <[email protected]>
@ 2026-05-11 03:17 ` JoongHyuk Shin <[email protected]>
2026-05-11 06:01 ` Re: [PATCH] Don't call ereport(ERROR) from recovery target GUC assign hooks JoongHyuk Shin <[email protected]>
0 siblings, 1 reply; 868+ messages in thread
From: JoongHyuk Shin @ 2026-05-11 03:17 UTC (permalink / raw)
To: Fujii Masao <[email protected]>; +Cc: Michael Paquier <[email protected]>; [email protected]
Thanks for the reviews.
v3 attached.
Expected behavior matrix:
Same GUC, non-empty then empty -> the empty wins; target unset
Same GUC, empty then non-empty -> the non-empty wins; target set
Cross-GUC, both non-empty -> error (multiple recovery targets)
Cross-GUC, one empty -> the non-empty GUC's target stands
All empty -> no target, end-of-WAL recovery
* Restored same-GUC last-wins (row 1). v2 dropped each hook's
`else recoveryTarget = UNSET`; v3 narrows it to
`else if (recoveryTarget == MY_TYPE)`.
* Cross-GUC empty stays a no-op (row 4), as v2 introduced.
Strict reject via a source-aware variant is feasible
if reviewers prefer.
* 003_recovery_targets.pl gains seven CLI-path cases;
postgresql.conf dedup cannot exercise the same-GUC clear path.
--
JH Shin
On Fri, May 1, 2026 at 2:53 PM Fujii Masao <[email protected]> wrote:
> On Wed, Apr 29, 2026 at 6:30 PM JoongHyuk Shin <[email protected]>
> wrote:
> >
> > Thanks for the reviews.
> >
> > v2 attached.
>
> Thanks for updating the patch!
>
> When I started postgres with the following command, recovery_target_xid was
> treated as unset in the master, but with the patch the
> recovery_target_xid=700
> setting was used instead. This behavior seems unexpected to me. Thoughts?
>
> postgres -D data -c "recovery_target_xid=700" -c "recovery_target_xid="
>
> Regards,
>
>
> --
> Fujii Masao
>
Attachments:
[application/octet-stream] v3-0001-Don-t-call-ereport-ERROR-from-recovery-target-GUC.patch (18.0K, ../../CACSdjfNY=aePEYGYV_5tamz-iTLQLitBuHc8youfsTzfDR1+_g@mail.gmail.com/3-v3-0001-Don-t-call-ereport-ERROR-from-recovery-target-GUC.patch)
download | inline diff:
From ef75dc7e4e070e7b5a7aa9c8693ae4b045f61242 Mon Sep 17 00:00:00 2001
From: JoongHyuk Shin <[email protected]>
Date: Sun, 10 May 2026 20:22:15 +0900
Subject: [PATCH] Don't call ereport(ERROR) from recovery target GUC assign
hooks
The five recovery target GUC assign hooks (assign_recovery_target,
assign_recovery_target_lsn, assign_recovery_target_name,
assign_recovery_target_time, assign_recovery_target_xid) all called
error_multiple_recovery_targets(), which invoked ereport(ERROR). The
GUC README explicitly states that assign hooks must never fail; raising
an error from an assign hook leaves guc.c's internal state inconsistent
before the abort. A comment in the code itself acknowledged this as
"broken by design."
Fix this by removing the conflict check from all five assign hooks and
detecting multiple recovery targets in a new CheckRecoveryTargetConflicts()
function, called from validateRecoveryParameters() before its early
return for !ArchiveRecoveryRequested. The check therefore runs at
every startup regardless of recovery mode, preserving the existing
behavior of detecting misconfiguration at server start time (rather
than only when recovery.signal is added).
In each assign hook's empty-string branch, replace the unconditional
"recoveryTarget = RECOVERY_TARGET_UNSET" assignment with a narrower
"if (recoveryTarget == MY_TYPE)" form. Empty strings for an unrelated
recovery_target_* GUC then leave another GUC's already-set target
intact (this fixes the cross-GUC clobber raised in v1 review), while
still preserving the documented "last value wins" reassignment
semantics for the same GUC: assigning a recovery target and then
reassigning the same parameter to an empty string unsets the target,
matching how the assign hooks behaved before this patch and what
003_recovery_targets.pl already expected.
---
src/backend/access/transam/xlogrecovery.c | 127 +++++++++------
src/test/recovery/t/003_recovery_targets.pl | 166 +++++++++++++++++++-
2 files changed, 244 insertions(+), 49 deletions(-)
diff --git a/src/backend/access/transam/xlogrecovery.c b/src/backend/access/transam/xlogrecovery.c
index c236e2b7969..721d9f0e367 100644
--- a/src/backend/access/transam/xlogrecovery.c
+++ b/src/backend/access/transam/xlogrecovery.c
@@ -341,6 +341,7 @@ static void ApplyWalRecord(XLogReaderState *xlogreader, XLogRecord *record, Time
static void EnableStandbyMode(void);
static void readRecoverySignalFile(void);
static void validateRecoveryParameters(void);
+static void CheckRecoveryTargetConflicts(void);
static bool read_backup_label(XLogRecPtr *checkPointLoc,
TimeLineID *backupLabelTLI,
bool *backupEndRequired, bool *backupFromStandby);
@@ -1067,6 +1068,8 @@ readRecoverySignalFile(void)
static void
validateRecoveryParameters(void)
{
+ CheckRecoveryTargetConflicts();
+
if (!ArchiveRecoveryRequested)
return;
@@ -1144,6 +1147,63 @@ validateRecoveryParameters(void)
}
}
+/*
+ * CheckRecoveryTargetConflicts
+ *
+ * Validate that at most one of the recovery_target_* GUCs is set to a
+ * non-empty value. This is called from validateRecoveryParameters() at every
+ * server startup, regardless of whether archive recovery is requested, so
+ * misconfiguration is detected at server start time rather than later when
+ * recovery.signal is added.
+ *
+ * The check is a separate function rather than inlined into
+ * validateRecoveryParameters() because it intentionally runs even when no
+ * recovery is requested, while the rest of validateRecoveryParameters() is
+ * recovery-mode-only. Keeping it as a named function makes that separation
+ * explicit.
+ *
+ * The check used to live in the assign hooks of the recovery_target_* GUCs
+ * (calling ereport(ERROR) on conflict), which violated guc.c's contract that
+ * assign hooks must never fail. Moving the check here keeps the assign hooks
+ * contract-compliant.
+ *
+ * If a future patch adds a sixth recovery_target_* GUC, both this list and
+ * the errdetail below must be updated.
+ */
+static void
+CheckRecoveryTargetConflicts(void)
+{
+ int ntargets = 0;
+ const char *val;
+
+ /* missing_ok=false guarantees val is non-NULL. */
+ val = GetConfigOption("recovery_target", false, false);
+ if (val[0] != '\0')
+ ntargets++;
+ val = GetConfigOption("recovery_target_lsn", false, false);
+ if (val[0] != '\0')
+ ntargets++;
+ val = GetConfigOption("recovery_target_name", false, false);
+ if (val[0] != '\0')
+ ntargets++;
+ val = GetConfigOption("recovery_target_time", false, false);
+ if (val[0] != '\0')
+ ntargets++;
+ val = GetConfigOption("recovery_target_xid", false, false);
+ if (val[0] != '\0')
+ ntargets++;
+
+ if (ntargets > 1)
+ ereport(FATAL,
+ (errcode(ERRCODE_INVALID_PARAMETER_VALUE),
+ errmsg("multiple recovery targets specified"),
+ errdetail("At most one of \"recovery_target\", "
+ "\"recovery_target_lsn\", "
+ "\"recovery_target_name\", "
+ "\"recovery_target_time\", "
+ "\"recovery_target_xid\" can be set.")));
+}
+
/*
* read_backup_label: check to see if a backup_label file is present
*
@@ -4764,32 +4824,27 @@ check_primary_slot_name(char **newval, void **extra, GucSource source)
}
/*
- * Recovery target settings: Only one of the several recovery_target* settings
- * may be set. Setting a second one results in an error. The global variable
+ * Recovery target settings: At most one of the several recovery_target*
+ * settings may be set to a non-empty value. The global variable
* recoveryTarget tracks which kind of recovery target was chosen. Other
* variables store the actual target value (for example a string or a xid).
- * The assign functions of the parameters check whether a competing parameter
- * was already set. But we want to allow setting the same parameter multiple
- * times. We also want to allow unsetting a parameter and setting a different
- * one, so we unset recoveryTarget when the parameter is set to an empty
- * string.
+ * An empty string for any of these GUCs is treated as "not set", equivalent
+ * to the GUC's default; an empty value cannot clobber another GUC's
+ * already-set target. Conflicts between multiple non-empty settings are
+ * detected in CheckRecoveryTargetConflicts(), called from
+ * validateRecoveryParameters() at every startup.
*
- * XXX this code is broken by design. Throwing an error from a GUC assign
- * hook breaks fundamental assumptions of guc.c. So long as all the variables
- * for which this can happen are PGC_POSTMASTER, the consequences are limited,
- * since we'd just abort postmaster startup anyway. Nonetheless it's likely
- * that we have odd behaviors such as unexpected GUC ordering dependencies.
+ * Each assign hook clears recoveryTarget only when its own GUC is reassigned
+ * to an empty string after the same GUC was previously assigned a non-empty
+ * value, e.g.
+ * postgres -c recovery_target_xid=700 -c recovery_target_xid=''
+ * (postgresql.conf collapses duplicate keys so only the last value reaches
+ * the assign hook; this same-parameter set-then-clear case only arises from
+ * -c). The clear is restricted to the hook's own target type so that an
+ * empty value for one recovery_target_* GUC cannot clobber another GUC's
+ * already-set target.
*/
-pg_noreturn static void
-error_multiple_recovery_targets(void)
-{
- ereport(ERROR,
- (errcode(ERRCODE_INVALID_PARAMETER_VALUE),
- errmsg("multiple recovery targets specified"),
- errdetail("At most one of \"recovery_target\", \"recovery_target_lsn\", \"recovery_target_name\", \"recovery_target_time\", \"recovery_target_xid\" may be set.")));
-}
-
/*
* GUC check_hook for recovery_target
*/
@@ -4810,13 +4865,9 @@ check_recovery_target(char **newval, void **extra, GucSource source)
void
assign_recovery_target(const char *newval, void *extra)
{
- if (recoveryTarget != RECOVERY_TARGET_UNSET &&
- recoveryTarget != RECOVERY_TARGET_IMMEDIATE)
- error_multiple_recovery_targets();
-
if (newval && strcmp(newval, "") != 0)
recoveryTarget = RECOVERY_TARGET_IMMEDIATE;
- else
+ else if (recoveryTarget == RECOVERY_TARGET_IMMEDIATE)
recoveryTarget = RECOVERY_TARGET_UNSET;
}
@@ -4851,16 +4902,12 @@ check_recovery_target_lsn(char **newval, void **extra, GucSource source)
void
assign_recovery_target_lsn(const char *newval, void *extra)
{
- if (recoveryTarget != RECOVERY_TARGET_UNSET &&
- recoveryTarget != RECOVERY_TARGET_LSN)
- error_multiple_recovery_targets();
-
if (newval && strcmp(newval, "") != 0)
{
recoveryTarget = RECOVERY_TARGET_LSN;
recoveryTargetLSN = *((XLogRecPtr *) extra);
}
- else
+ else if (recoveryTarget == RECOVERY_TARGET_LSN)
recoveryTarget = RECOVERY_TARGET_UNSET;
}
@@ -4886,16 +4933,12 @@ check_recovery_target_name(char **newval, void **extra, GucSource source)
void
assign_recovery_target_name(const char *newval, void *extra)
{
- if (recoveryTarget != RECOVERY_TARGET_UNSET &&
- recoveryTarget != RECOVERY_TARGET_NAME)
- error_multiple_recovery_targets();
-
if (newval && strcmp(newval, "") != 0)
{
recoveryTarget = RECOVERY_TARGET_NAME;
recoveryTargetName = newval;
}
- else
+ else if (recoveryTarget == RECOVERY_TARGET_NAME)
recoveryTarget = RECOVERY_TARGET_UNSET;
}
@@ -4966,13 +5009,9 @@ check_recovery_target_time(char **newval, void **extra, GucSource source)
void
assign_recovery_target_time(const char *newval, void *extra)
{
- if (recoveryTarget != RECOVERY_TARGET_UNSET &&
- recoveryTarget != RECOVERY_TARGET_TIME)
- error_multiple_recovery_targets();
-
if (newval && strcmp(newval, "") != 0)
recoveryTarget = RECOVERY_TARGET_TIME;
- else
+ else if (recoveryTarget == RECOVERY_TARGET_TIME)
recoveryTarget = RECOVERY_TARGET_UNSET;
}
@@ -5094,15 +5133,11 @@ check_recovery_target_xid(char **newval, void **extra, GucSource source)
void
assign_recovery_target_xid(const char *newval, void *extra)
{
- if (recoveryTarget != RECOVERY_TARGET_UNSET &&
- recoveryTarget != RECOVERY_TARGET_XID)
- error_multiple_recovery_targets();
-
if (newval && strcmp(newval, "") != 0)
{
recoveryTarget = RECOVERY_TARGET_XID;
recoveryTargetXid = *((TransactionId *) extra);
}
- else
+ else if (recoveryTarget == RECOVERY_TARGET_XID)
recoveryTarget = RECOVERY_TARGET_UNSET;
}
diff --git a/src/test/recovery/t/003_recovery_targets.pl b/src/test/recovery/t/003_recovery_targets.pl
index 047eb13293a..bf871fb95f9 100644
--- a/src/test/recovery/t/003_recovery_targets.pl
+++ b/src/test/recovery/t/003_recovery_targets.pl
@@ -51,6 +51,49 @@ sub test_recovery_standby
return;
}
+# Start a standby with the given pg_ctl --options string and verify that
+# the standby reaches the given LSN and row count. Used to exercise
+# scenarios that require the postmaster command line to receive multiple
+# "-c name=value" instances of the same GUC, which postgresql.conf cannot
+# express because ProcessConfigFile collapses duplicate keys.
+sub test_recovery_standby_with_options
+{
+ local $Test::Builder::Level = $Test::Builder::Level + 1;
+
+ my $test_name = shift;
+ my $node_name = shift;
+ my $node_primary = shift;
+ my $options = shift;
+ my $num_rows = shift;
+ my $until_lsn = shift;
+
+ my $node_standby = PostgreSQL::Test::Cluster->new($node_name);
+ $node_standby->init_from_backup($node_primary, 'my_backup',
+ has_restoring => 1);
+
+ my $res = run_log(
+ [
+ 'pg_ctl',
+ '--pgdata' => $node_standby->data_dir,
+ '--log' => $node_standby->logfile,
+ '--options' => $options,
+ 'start',
+ ]);
+ ok($res, "server starts for $test_name");
+
+ $node_standby->poll_query_until('postgres',
+ "SELECT '$until_lsn'::pg_lsn <= pg_last_wal_replay_lsn()")
+ or die "Timed out while waiting for standby to catch up";
+
+ my $count = $node_standby->safe_psql('postgres',
+ "SELECT count(*) FROM tab_int");
+ is($count, qq($num_rows), "check standby content for $test_name");
+
+ $node_standby->teardown_node;
+
+ return;
+}
+
# Initialize primary node
my $node_primary = PostgreSQL::Test::Cluster->new('primary');
$node_primary->init(has_archiving => 1, allows_streaming => 1);
@@ -108,6 +151,13 @@ $node_primary->safe_psql('postgres',
# Force archiving of WAL file
$node_primary->safe_psql('postgres', "SELECT pg_switch_wal()");
+# LSN after the final 6000-row insert and WAL switch. Used by the
+# set-then-cleared scenarios below where recovery has no target and must
+# replay all archived WAL; polling on $lsn5 would race against the 5001-6000
+# rows.
+my $lsn6 =
+ $node_primary->safe_psql('postgres', "SELECT pg_current_wal_lsn()");
+
# Test recovery targets
my @recovery_params = ("recovery_target = 'immediate'");
test_recovery_standby('immediate target',
@@ -125,11 +175,24 @@ test_recovery_standby('name', 'standby_4', $node_primary, \@recovery_params,
test_recovery_standby('LSN', 'standby_5', $node_primary, \@recovery_params,
"5000", $lsn5);
+# Regression: empty-string for one recovery_target_* GUC must not clobber
+# another non-empty target. Setting recovery_target_xid + recovery_target_time
+# = '' must recover to the xid, not run as no-target recovery.
+@recovery_params = (
+ "recovery_target_xid = '$recovery_txid'",
+ "recovery_target_time = ''");
+test_recovery_standby('xid with empty time GUC',
+ 'standby_xid_empty_time', $node_primary, \@recovery_params,
+ "2000", $lsn2);
+
# Multiple targets
#
-# Multiple conflicting settings are not allowed, but setting the same
-# parameter multiple times or unsetting a parameter and setting a
-# different one is allowed.
+# Multiple conflicting non-empty settings are not allowed. Setting the same
+# parameter multiple times is allowed (the last value wins, per ProcessConfigFile
+# duplicate handling). An empty string for a recovery_target_* GUC is treated
+# as the GUC's default and is a no-op; it does not clear any other already-set
+# target. Conflict detection runs in CheckRecoveryTargetConflicts() at every
+# server start, regardless of whether recovery is requested.
@recovery_params = (
"recovery_target_name = '$recovery_name'",
@@ -190,6 +253,103 @@ like(
qr/FATAL: .* recovery ended before configured recovery target was reached/,
'recovery end before target reached is a fatal error');
+# Conflicting recovery targets are rejected at every startup, regardless of
+# whether recovery.signal is present. Use a throwaway cluster initialized
+# from the existing backup so we can leave conflicting GUCs in its
+# postgresql.conf without polluting the primary used by later tests.
+# init_from_backup is called without has_restoring, so no recovery.signal
+# is created and the cluster would normally start as a plain primary; the
+# conflicting recovery_target_* GUCs must be rejected anyway.
+my $node_no_signal = PostgreSQL::Test::Cluster->new('multi_target_no_signal');
+$node_no_signal->init_from_backup($node_primary, 'my_backup');
+$node_no_signal->append_conf(
+ 'postgresql.conf', "recovery_target_name = '$recovery_name'
+recovery_target_time = '$recovery_time'");
+
+my $res_no_signal = run_log(
+ [
+ 'pg_ctl',
+ '--pgdata' => $node_no_signal->data_dir,
+ '--log' => $node_no_signal->logfile,
+ 'start',
+ ]);
+ok(!$res_no_signal,
+ 'server fails to start with conflicting recovery targets and no recovery.signal');
+
+my $logfile_no_signal = slurp_file($node_no_signal->logfile());
+like(
+ $logfile_no_signal,
+ qr/multiple recovery targets specified/,
+ 'expected error message logged without recovery.signal');
+
+# Same-GUC last-wins (one source of truth for the GUC's value): assigning a
+# recovery_target_* GUC and then assigning the same GUC to an empty string
+# leaves no target set and recovery proceeds to the end of WAL. This is the
+# "set then unset" form of GUC reassignment; the assign hook clears its own
+# type when the new value is empty. postgresql.conf cannot express duplicate
+# keys (ProcessConfigFile collapses them), so the postmaster command line is
+# used via "pg_ctl --options". Each of the five recovery_target_* assign
+# hooks is exercised once.
+
+test_recovery_standby_with_options(
+ 'recovery_target_xid set then cleared',
+ 'standby_xid_set_clear', $node_primary,
+ "-c recovery_target_xid='$recovery_txid' -c recovery_target_xid=''",
+ "6000", $lsn6);
+
+test_recovery_standby_with_options(
+ 'recovery_target_time set then cleared',
+ 'standby_time_set_clear', $node_primary,
+ "-c recovery_target_time='$recovery_time' -c recovery_target_time=''",
+ "6000", $lsn6);
+
+test_recovery_standby_with_options(
+ 'recovery_target_name set then cleared',
+ 'standby_name_set_clear', $node_primary,
+ "-c recovery_target_name='$recovery_name' -c recovery_target_name=''",
+ "6000", $lsn6);
+
+test_recovery_standby_with_options(
+ 'recovery_target_lsn set then cleared',
+ 'standby_lsn_set_clear', $node_primary,
+ "-c recovery_target_lsn='$recovery_lsn' -c recovery_target_lsn=''",
+ "6000", $lsn6);
+
+test_recovery_standby_with_options(
+ 'recovery_target set then cleared',
+ 'standby_immediate_set_clear', $node_primary,
+ "-c recovery_target='immediate' -c recovery_target=''",
+ "6000", $lsn6);
+
+# Same-GUC empty-then-set sanity: assigning an empty string and then a
+# non-empty value to the same GUC must end with the target set. The empty
+# value seen first must not poison a later non-empty assignment.
+my $node_xid_clear_set =
+ PostgreSQL::Test::Cluster->new('standby_xid_clear_set');
+$node_xid_clear_set->init_from_backup($node_primary, 'my_backup',
+ has_restoring => 1);
+
+my $res_xid_clear_set = run_log(
+ [
+ 'pg_ctl',
+ '--pgdata' => $node_xid_clear_set->data_dir,
+ '--log' => $node_xid_clear_set->logfile,
+ '--options' =>
+ "-c recovery_target_xid='' -c recovery_target_xid='$recovery_txid'",
+ 'start',
+ ]);
+ok($res_xid_clear_set,
+ 'server starts with recovery_target_xid cleared then set');
+
+$node_xid_clear_set->poll_query_until('postgres',
+ "SELECT '$lsn2'::pg_lsn <= pg_last_wal_replay_lsn()")
+ or die "Timed out while waiting for standby to catch up";
+my $count_xid_clear_set = $node_xid_clear_set->safe_psql('postgres',
+ "SELECT count(*) FROM tab_int");
+is($count_xid_clear_set, "2000",
+ 'recovery_target_xid honored when cleared then set');
+$node_xid_clear_set->teardown_node;
+
# Invalid recovery_target_timeline tests
my ($result, $stdout, $stderr) = $node_primary->psql('postgres',
"ALTER SYSTEM SET recovery_target_timeline TO 'bogus'");
--
2.52.0
^ permalink raw reply [nested|flat] 868+ messages in thread
* Re: [PATCH] Don't call ereport(ERROR) from recovery target GUC assign hooks
2026-04-13 08:21 [PATCH] Don't call ereport(ERROR) from recovery target GUC assign hooks JoongHyuk Shin <[email protected]>
2026-04-24 13:08 ` Re: [PATCH] Don't call ereport(ERROR) from recovery target GUC assign hooks Fujii Masao <[email protected]>
2026-04-27 01:52 ` Re: [PATCH] Don't call ereport(ERROR) from recovery target GUC assign hooks Michael Paquier <[email protected]>
2026-04-27 05:36 ` Re: [PATCH] Don't call ereport(ERROR) from recovery target GUC assign hooks Fujii Masao <[email protected]>
2026-04-27 05:59 ` Re: [PATCH] Don't call ereport(ERROR) from recovery target GUC assign hooks Michael Paquier <[email protected]>
2026-04-29 09:29 ` Re: [PATCH] Don't call ereport(ERROR) from recovery target GUC assign hooks JoongHyuk Shin <[email protected]>
2026-05-01 05:53 ` Re: [PATCH] Don't call ereport(ERROR) from recovery target GUC assign hooks Fujii Masao <[email protected]>
2026-05-11 03:17 ` Re: [PATCH] Don't call ereport(ERROR) from recovery target GUC assign hooks JoongHyuk Shin <[email protected]>
@ 2026-05-11 06:01 ` JoongHyuk Shin <[email protected]>
2026-05-31 21:11 ` Re: [PATCH] Don't call ereport(ERROR) from recovery target GUC assign hooks Scott Ray <[email protected]>
0 siblings, 1 reply; 868+ messages in thread
From: JoongHyuk Shin @ 2026-05-11 06:01 UTC (permalink / raw)
To: Fujii Masao <[email protected]>; +Cc: Michael Paquier <[email protected]>; [email protected]
CFBot flagged a Windows MSVC failure on v3. Root cause: the new TAP
cases pass GUC values to pg_ctl via "--options" using single-quoted
shell tokens, which Windows cmd.exe does not strip the way POSIX
shells do, so postgres receives the quotes verbatim and rejects the
values.
v4 drops the single quotes and switches recovery_target_time from the
space-containing now() format to ISO 8601 with the T separator, so
each value is a single token without quoting. All other platforms
already passed; this only affected the new TAP cases on Windows MSVC.
No functional change.
--
JH Shin
On Mon, May 11, 2026 at 12:17 PM JoongHyuk Shin <[email protected]> wrote:
> Thanks for the reviews.
>
> v3 attached.
>
> Expected behavior matrix:
>
> Same GUC, non-empty then empty -> the empty wins; target unset
> Same GUC, empty then non-empty -> the non-empty wins; target set
> Cross-GUC, both non-empty -> error (multiple recovery targets)
> Cross-GUC, one empty -> the non-empty GUC's target stands
> All empty -> no target, end-of-WAL recovery
>
> * Restored same-GUC last-wins (row 1). v2 dropped each hook's
> `else recoveryTarget = UNSET`; v3 narrows it to
> `else if (recoveryTarget == MY_TYPE)`.
>
> * Cross-GUC empty stays a no-op (row 4), as v2 introduced.
> Strict reject via a source-aware variant is feasible
> if reviewers prefer.
>
> * 003_recovery_targets.pl gains seven CLI-path cases;
> postgresql.conf dedup cannot exercise the same-GUC clear path.
>
> --
> JH Shin
>
> On Fri, May 1, 2026 at 2:53 PM Fujii Masao <[email protected]> wrote:
>
>> On Wed, Apr 29, 2026 at 6:30 PM JoongHyuk Shin <[email protected]>
>> wrote:
>> >
>> > Thanks for the reviews.
>> >
>> > v2 attached.
>>
>> Thanks for updating the patch!
>>
>> When I started postgres with the following command, recovery_target_xid
>> was
>> treated as unset in the master, but with the patch the
>> recovery_target_xid=700
>> setting was used instead. This behavior seems unexpected to me. Thoughts?
>>
>> postgres -D data -c "recovery_target_xid=700" -c
>> "recovery_target_xid="
>>
>> Regards,
>>
>>
>> --
>> Fujii Masao
>>
>
Attachments:
[application/octet-stream] v4-0001-Don-t-call-ereport-ERROR-from-recovery-target-GUC.patch (18.4K, ../../CACSdjfMohHudUh7Fq=pD=Zk+=J=4E65c6VGgScG3CAXvj_Sorg@mail.gmail.com/3-v4-0001-Don-t-call-ereport-ERROR-from-recovery-target-GUC.patch)
download | inline diff:
From 68212bb8107a119ffe7f87113121230c87415b49 Mon Sep 17 00:00:00 2001
From: JoongHyuk Shin <[email protected]>
Date: Sun, 10 May 2026 20:22:15 +0900
Subject: [PATCH v4] Don't call ereport(ERROR) from recovery target GUC assign
hooks
The five recovery target GUC assign hooks (assign_recovery_target,
assign_recovery_target_lsn, assign_recovery_target_name,
assign_recovery_target_time, assign_recovery_target_xid) all called
error_multiple_recovery_targets(), which invoked ereport(ERROR). The
GUC README explicitly states that assign hooks must never fail; raising
an error from an assign hook leaves guc.c's internal state inconsistent
before the abort. A comment in the code itself acknowledged this as
"broken by design."
Fix this by removing the conflict check from all five assign hooks and
detecting multiple recovery targets in a new CheckRecoveryTargetConflicts()
function, called from validateRecoveryParameters() before its early
return for !ArchiveRecoveryRequested. The check therefore runs at
every startup regardless of recovery mode, preserving the existing
behavior of detecting misconfiguration at server start time (rather
than only when recovery.signal is added).
In each assign hook's empty-string branch, replace the unconditional
"recoveryTarget = RECOVERY_TARGET_UNSET" assignment with a narrower
"if (recoveryTarget == MY_TYPE)" form. Empty strings for an unrelated
recovery_target_* GUC then leave another GUC's already-set target
intact (this fixes the cross-GUC clobber raised in v1 review), while
still preserving the documented "last value wins" reassignment
semantics for the same GUC: assigning a recovery target and then
reassigning the same parameter to an empty string unsets the target,
matching how the assign hooks behaved before this patch and what
003_recovery_targets.pl already expected.
---
src/backend/access/transam/xlogrecovery.c | 127 ++++++++------
src/test/recovery/t/003_recovery_targets.pl | 174 +++++++++++++++++++-
2 files changed, 252 insertions(+), 49 deletions(-)
diff --git a/src/backend/access/transam/xlogrecovery.c b/src/backend/access/transam/xlogrecovery.c
index c236e2b7969..953cdb48a3d 100644
--- a/src/backend/access/transam/xlogrecovery.c
+++ b/src/backend/access/transam/xlogrecovery.c
@@ -341,6 +341,7 @@ static void ApplyWalRecord(XLogReaderState *xlogreader, XLogRecord *record, Time
static void EnableStandbyMode(void);
static void readRecoverySignalFile(void);
static void validateRecoveryParameters(void);
+static void CheckRecoveryTargetConflicts(void);
static bool read_backup_label(XLogRecPtr *checkPointLoc,
TimeLineID *backupLabelTLI,
bool *backupEndRequired, bool *backupFromStandby);
@@ -1067,6 +1068,8 @@ readRecoverySignalFile(void)
static void
validateRecoveryParameters(void)
{
+ CheckRecoveryTargetConflicts();
+
if (!ArchiveRecoveryRequested)
return;
@@ -1144,6 +1147,63 @@ validateRecoveryParameters(void)
}
}
+/*
+ * CheckRecoveryTargetConflicts
+ *
+ * Validate that at most one of the recovery_target_* GUCs is set to a
+ * non-empty value. This is called from validateRecoveryParameters() at every
+ * server startup, regardless of whether archive recovery is requested, so
+ * misconfiguration is detected at server start time rather than later when
+ * recovery.signal is added.
+ *
+ * The check is a separate function rather than inlined into
+ * validateRecoveryParameters() because it intentionally runs even when no
+ * recovery is requested, while the rest of validateRecoveryParameters() is
+ * recovery-mode-only. Keeping it as a named function makes that separation
+ * explicit.
+ *
+ * The check used to live in the assign hooks of the recovery_target_* GUCs
+ * (calling ereport(ERROR) on conflict), which violated guc.c's contract that
+ * assign hooks must never fail. Moving the check here keeps the assign hooks
+ * contract-compliant.
+ *
+ * If a future patch adds a sixth recovery_target_* GUC, both this list and
+ * the errdetail below must be updated.
+ */
+static void
+CheckRecoveryTargetConflicts(void)
+{
+ int ntargets = 0;
+ const char *val;
+
+ /* missing_ok=false guarantees val is non-NULL. */
+ val = GetConfigOption("recovery_target", false, false);
+ if (val[0] != '\0')
+ ntargets++;
+ val = GetConfigOption("recovery_target_lsn", false, false);
+ if (val[0] != '\0')
+ ntargets++;
+ val = GetConfigOption("recovery_target_name", false, false);
+ if (val[0] != '\0')
+ ntargets++;
+ val = GetConfigOption("recovery_target_time", false, false);
+ if (val[0] != '\0')
+ ntargets++;
+ val = GetConfigOption("recovery_target_xid", false, false);
+ if (val[0] != '\0')
+ ntargets++;
+
+ if (ntargets > 1)
+ ereport(FATAL,
+ (errcode(ERRCODE_INVALID_PARAMETER_VALUE),
+ errmsg("multiple recovery targets specified"),
+ errdetail("At most one of \"recovery_target\", "
+ "\"recovery_target_lsn\", "
+ "\"recovery_target_name\", "
+ "\"recovery_target_time\", "
+ "\"recovery_target_xid\" can be set.")));
+}
+
/*
* read_backup_label: check to see if a backup_label file is present
*
@@ -4764,32 +4824,27 @@ check_primary_slot_name(char **newval, void **extra, GucSource source)
}
/*
- * Recovery target settings: Only one of the several recovery_target* settings
- * may be set. Setting a second one results in an error. The global variable
+ * Recovery target settings: At most one of the several recovery_target*
+ * settings may be set to a non-empty value. The global variable
* recoveryTarget tracks which kind of recovery target was chosen. Other
* variables store the actual target value (for example a string or a xid).
- * The assign functions of the parameters check whether a competing parameter
- * was already set. But we want to allow setting the same parameter multiple
- * times. We also want to allow unsetting a parameter and setting a different
- * one, so we unset recoveryTarget when the parameter is set to an empty
- * string.
+ * An empty string for any of these GUCs is treated as "not set", equivalent
+ * to the GUC's default; an empty value cannot clobber another GUC's
+ * already-set target. Conflicts between multiple non-empty settings are
+ * detected in CheckRecoveryTargetConflicts(), called from
+ * validateRecoveryParameters() at every startup.
*
- * XXX this code is broken by design. Throwing an error from a GUC assign
- * hook breaks fundamental assumptions of guc.c. So long as all the variables
- * for which this can happen are PGC_POSTMASTER, the consequences are limited,
- * since we'd just abort postmaster startup anyway. Nonetheless it's likely
- * that we have odd behaviors such as unexpected GUC ordering dependencies.
+ * Each assign hook clears recoveryTarget only when its own GUC is reassigned
+ * to an empty string after the same GUC was previously assigned a non-empty
+ * value, e.g.
+ * postgres -c recovery_target_xid=700 -c recovery_target_xid=
+ * (postgresql.conf collapses duplicate keys so only the last value reaches
+ * the assign hook; this same-parameter set-then-clear case only arises from
+ * -c). The clear is restricted to the hook's own target type so that an
+ * empty value for one recovery_target_* GUC cannot clobber another GUC's
+ * already-set target.
*/
-pg_noreturn static void
-error_multiple_recovery_targets(void)
-{
- ereport(ERROR,
- (errcode(ERRCODE_INVALID_PARAMETER_VALUE),
- errmsg("multiple recovery targets specified"),
- errdetail("At most one of \"recovery_target\", \"recovery_target_lsn\", \"recovery_target_name\", \"recovery_target_time\", \"recovery_target_xid\" may be set.")));
-}
-
/*
* GUC check_hook for recovery_target
*/
@@ -4810,13 +4865,9 @@ check_recovery_target(char **newval, void **extra, GucSource source)
void
assign_recovery_target(const char *newval, void *extra)
{
- if (recoveryTarget != RECOVERY_TARGET_UNSET &&
- recoveryTarget != RECOVERY_TARGET_IMMEDIATE)
- error_multiple_recovery_targets();
-
if (newval && strcmp(newval, "") != 0)
recoveryTarget = RECOVERY_TARGET_IMMEDIATE;
- else
+ else if (recoveryTarget == RECOVERY_TARGET_IMMEDIATE)
recoveryTarget = RECOVERY_TARGET_UNSET;
}
@@ -4851,16 +4902,12 @@ check_recovery_target_lsn(char **newval, void **extra, GucSource source)
void
assign_recovery_target_lsn(const char *newval, void *extra)
{
- if (recoveryTarget != RECOVERY_TARGET_UNSET &&
- recoveryTarget != RECOVERY_TARGET_LSN)
- error_multiple_recovery_targets();
-
if (newval && strcmp(newval, "") != 0)
{
recoveryTarget = RECOVERY_TARGET_LSN;
recoveryTargetLSN = *((XLogRecPtr *) extra);
}
- else
+ else if (recoveryTarget == RECOVERY_TARGET_LSN)
recoveryTarget = RECOVERY_TARGET_UNSET;
}
@@ -4886,16 +4933,12 @@ check_recovery_target_name(char **newval, void **extra, GucSource source)
void
assign_recovery_target_name(const char *newval, void *extra)
{
- if (recoveryTarget != RECOVERY_TARGET_UNSET &&
- recoveryTarget != RECOVERY_TARGET_NAME)
- error_multiple_recovery_targets();
-
if (newval && strcmp(newval, "") != 0)
{
recoveryTarget = RECOVERY_TARGET_NAME;
recoveryTargetName = newval;
}
- else
+ else if (recoveryTarget == RECOVERY_TARGET_NAME)
recoveryTarget = RECOVERY_TARGET_UNSET;
}
@@ -4966,13 +5009,9 @@ check_recovery_target_time(char **newval, void **extra, GucSource source)
void
assign_recovery_target_time(const char *newval, void *extra)
{
- if (recoveryTarget != RECOVERY_TARGET_UNSET &&
- recoveryTarget != RECOVERY_TARGET_TIME)
- error_multiple_recovery_targets();
-
if (newval && strcmp(newval, "") != 0)
recoveryTarget = RECOVERY_TARGET_TIME;
- else
+ else if (recoveryTarget == RECOVERY_TARGET_TIME)
recoveryTarget = RECOVERY_TARGET_UNSET;
}
@@ -5094,15 +5133,11 @@ check_recovery_target_xid(char **newval, void **extra, GucSource source)
void
assign_recovery_target_xid(const char *newval, void *extra)
{
- if (recoveryTarget != RECOVERY_TARGET_UNSET &&
- recoveryTarget != RECOVERY_TARGET_XID)
- error_multiple_recovery_targets();
-
if (newval && strcmp(newval, "") != 0)
{
recoveryTarget = RECOVERY_TARGET_XID;
recoveryTargetXid = *((TransactionId *) extra);
}
- else
+ else if (recoveryTarget == RECOVERY_TARGET_XID)
recoveryTarget = RECOVERY_TARGET_UNSET;
}
diff --git a/src/test/recovery/t/003_recovery_targets.pl b/src/test/recovery/t/003_recovery_targets.pl
index 047eb13293a..5979663b0ab 100644
--- a/src/test/recovery/t/003_recovery_targets.pl
+++ b/src/test/recovery/t/003_recovery_targets.pl
@@ -51,6 +51,49 @@ sub test_recovery_standby
return;
}
+# Start a standby with the given pg_ctl --options string and verify that
+# the standby reaches the given LSN and row count. Used to exercise
+# scenarios that require the postmaster command line to receive multiple
+# "-c name=value" instances of the same GUC, which postgresql.conf cannot
+# express because ProcessConfigFile collapses duplicate keys.
+sub test_recovery_standby_with_options
+{
+ local $Test::Builder::Level = $Test::Builder::Level + 1;
+
+ my $test_name = shift;
+ my $node_name = shift;
+ my $node_primary = shift;
+ my $options = shift;
+ my $num_rows = shift;
+ my $until_lsn = shift;
+
+ my $node_standby = PostgreSQL::Test::Cluster->new($node_name);
+ $node_standby->init_from_backup($node_primary, 'my_backup',
+ has_restoring => 1);
+
+ my $res = run_log(
+ [
+ 'pg_ctl',
+ '--pgdata' => $node_standby->data_dir,
+ '--log' => $node_standby->logfile,
+ '--options' => $options,
+ 'start',
+ ]);
+ ok($res, "server starts for $test_name");
+
+ $node_standby->poll_query_until('postgres',
+ "SELECT '$until_lsn'::pg_lsn <= pg_last_wal_replay_lsn()")
+ or die "Timed out while waiting for standby to catch up";
+
+ my $count = $node_standby->safe_psql('postgres',
+ "SELECT count(*) FROM tab_int");
+ is($count, qq($num_rows), "check standby content for $test_name");
+
+ $node_standby->teardown_node;
+
+ return;
+}
+
# Initialize primary node
my $node_primary = PostgreSQL::Test::Cluster->new('primary');
$node_primary->init(has_archiving => 1, allows_streaming => 1);
@@ -108,6 +151,13 @@ $node_primary->safe_psql('postgres',
# Force archiving of WAL file
$node_primary->safe_psql('postgres', "SELECT pg_switch_wal()");
+# LSN after the final 6000-row insert and WAL switch. Used by the
+# set-then-cleared scenarios below where recovery has no target and must
+# replay all archived WAL; polling on $lsn5 would race against the 5001-6000
+# rows.
+my $lsn6 =
+ $node_primary->safe_psql('postgres', "SELECT pg_current_wal_lsn()");
+
# Test recovery targets
my @recovery_params = ("recovery_target = 'immediate'");
test_recovery_standby('immediate target',
@@ -125,11 +175,24 @@ test_recovery_standby('name', 'standby_4', $node_primary, \@recovery_params,
test_recovery_standby('LSN', 'standby_5', $node_primary, \@recovery_params,
"5000", $lsn5);
+# Regression: empty-string for one recovery_target_* GUC must not clobber
+# another non-empty target. Setting recovery_target_xid + recovery_target_time
+# = '' must recover to the xid, not run as no-target recovery.
+@recovery_params = (
+ "recovery_target_xid = '$recovery_txid'",
+ "recovery_target_time = ''");
+test_recovery_standby('xid with empty time GUC',
+ 'standby_xid_empty_time', $node_primary, \@recovery_params,
+ "2000", $lsn2);
+
# Multiple targets
#
-# Multiple conflicting settings are not allowed, but setting the same
-# parameter multiple times or unsetting a parameter and setting a
-# different one is allowed.
+# Multiple conflicting non-empty settings are not allowed. Setting the same
+# parameter multiple times is allowed (the last value wins, per ProcessConfigFile
+# duplicate handling). An empty string for a recovery_target_* GUC is treated
+# as the GUC's default and is a no-op; it does not clear any other already-set
+# target. Conflict detection runs in CheckRecoveryTargetConflicts() at every
+# server start, regardless of whether recovery is requested.
@recovery_params = (
"recovery_target_name = '$recovery_name'",
@@ -190,6 +253,111 @@ like(
qr/FATAL: .* recovery ended before configured recovery target was reached/,
'recovery end before target reached is a fatal error');
+# Conflicting recovery targets are rejected at every startup, regardless of
+# whether recovery.signal is present. Use a throwaway cluster initialized
+# from the existing backup so we can leave conflicting GUCs in its
+# postgresql.conf without polluting the primary used by later tests.
+# init_from_backup is called without has_restoring, so no recovery.signal
+# is created and the cluster would normally start as a plain primary; the
+# conflicting recovery_target_* GUCs must be rejected anyway.
+my $node_no_signal = PostgreSQL::Test::Cluster->new('multi_target_no_signal');
+$node_no_signal->init_from_backup($node_primary, 'my_backup');
+$node_no_signal->append_conf(
+ 'postgresql.conf', "recovery_target_name = '$recovery_name'
+recovery_target_time = '$recovery_time'");
+
+my $res_no_signal = run_log(
+ [
+ 'pg_ctl',
+ '--pgdata' => $node_no_signal->data_dir,
+ '--log' => $node_no_signal->logfile,
+ 'start',
+ ]);
+ok(!$res_no_signal,
+ 'server fails to start with conflicting recovery targets and no recovery.signal');
+
+my $logfile_no_signal = slurp_file($node_no_signal->logfile());
+like(
+ $logfile_no_signal,
+ qr/multiple recovery targets specified/,
+ 'expected error message logged without recovery.signal');
+
+# Same-GUC last-wins (one source of truth for the GUC's value): assigning a
+# recovery_target_* GUC and then assigning the same GUC to an empty string
+# leaves no target set and recovery proceeds to the end of WAL. This is the
+# "set then unset" form of GUC reassignment; the assign hook clears its own
+# type when the new value is empty. postgresql.conf cannot express duplicate
+# keys (ProcessConfigFile collapses them), so the postmaster command line is
+# used via "pg_ctl --options". Each of the five recovery_target_* assign
+# hooks is exercised once.
+#
+# Note: GUC values below are passed unquoted on the command line. Windows
+# cmd.exe does not strip single quotes the way POSIX shells do, so quoted
+# values would reach the postmaster verbatim and be rejected. recovery_time
+# from now() contains a space; we replace it with the ISO 8601 T separator
+# so the value is a single shell token without quoting.
+my $recovery_time_t = $recovery_time;
+$recovery_time_t =~ s/ /T/;
+
+test_recovery_standby_with_options(
+ 'recovery_target_xid set then cleared',
+ 'standby_xid_set_clear', $node_primary,
+ "-c recovery_target_xid=$recovery_txid -c recovery_target_xid=",
+ "6000", $lsn6);
+
+test_recovery_standby_with_options(
+ 'recovery_target_time set then cleared',
+ 'standby_time_set_clear', $node_primary,
+ "-c recovery_target_time=$recovery_time_t -c recovery_target_time=",
+ "6000", $lsn6);
+
+test_recovery_standby_with_options(
+ 'recovery_target_name set then cleared',
+ 'standby_name_set_clear', $node_primary,
+ "-c recovery_target_name=$recovery_name -c recovery_target_name=",
+ "6000", $lsn6);
+
+test_recovery_standby_with_options(
+ 'recovery_target_lsn set then cleared',
+ 'standby_lsn_set_clear', $node_primary,
+ "-c recovery_target_lsn=$recovery_lsn -c recovery_target_lsn=",
+ "6000", $lsn6);
+
+test_recovery_standby_with_options(
+ 'recovery_target set then cleared',
+ 'standby_immediate_set_clear', $node_primary,
+ "-c recovery_target=immediate -c recovery_target=",
+ "6000", $lsn6);
+
+# Same-GUC empty-then-set sanity: assigning an empty string and then a
+# non-empty value to the same GUC must end with the target set. The empty
+# value seen first must not poison a later non-empty assignment.
+my $node_xid_clear_set =
+ PostgreSQL::Test::Cluster->new('standby_xid_clear_set');
+$node_xid_clear_set->init_from_backup($node_primary, 'my_backup',
+ has_restoring => 1);
+
+my $res_xid_clear_set = run_log(
+ [
+ 'pg_ctl',
+ '--pgdata' => $node_xid_clear_set->data_dir,
+ '--log' => $node_xid_clear_set->logfile,
+ '--options' =>
+ "-c recovery_target_xid= -c recovery_target_xid=$recovery_txid",
+ 'start',
+ ]);
+ok($res_xid_clear_set,
+ 'server starts with recovery_target_xid cleared then set');
+
+$node_xid_clear_set->poll_query_until('postgres',
+ "SELECT '$lsn2'::pg_lsn <= pg_last_wal_replay_lsn()")
+ or die "Timed out while waiting for standby to catch up";
+my $count_xid_clear_set = $node_xid_clear_set->safe_psql('postgres',
+ "SELECT count(*) FROM tab_int");
+is($count_xid_clear_set, "2000",
+ 'recovery_target_xid honored when cleared then set');
+$node_xid_clear_set->teardown_node;
+
# Invalid recovery_target_timeline tests
my ($result, $stdout, $stderr) = $node_primary->psql('postgres',
"ALTER SYSTEM SET recovery_target_timeline TO 'bogus'");
--
2.52.0
^ permalink raw reply [nested|flat] 868+ messages in thread
* Re: [PATCH] Don't call ereport(ERROR) from recovery target GUC assign hooks
2026-04-13 08:21 [PATCH] Don't call ereport(ERROR) from recovery target GUC assign hooks JoongHyuk Shin <[email protected]>
2026-04-24 13:08 ` Re: [PATCH] Don't call ereport(ERROR) from recovery target GUC assign hooks Fujii Masao <[email protected]>
2026-04-27 01:52 ` Re: [PATCH] Don't call ereport(ERROR) from recovery target GUC assign hooks Michael Paquier <[email protected]>
2026-04-27 05:36 ` Re: [PATCH] Don't call ereport(ERROR) from recovery target GUC assign hooks Fujii Masao <[email protected]>
2026-04-27 05:59 ` Re: [PATCH] Don't call ereport(ERROR) from recovery target GUC assign hooks Michael Paquier <[email protected]>
2026-04-29 09:29 ` Re: [PATCH] Don't call ereport(ERROR) from recovery target GUC assign hooks JoongHyuk Shin <[email protected]>
2026-05-01 05:53 ` Re: [PATCH] Don't call ereport(ERROR) from recovery target GUC assign hooks Fujii Masao <[email protected]>
2026-05-11 03:17 ` Re: [PATCH] Don't call ereport(ERROR) from recovery target GUC assign hooks JoongHyuk Shin <[email protected]>
2026-05-11 06:01 ` Re: [PATCH] Don't call ereport(ERROR) from recovery target GUC assign hooks JoongHyuk Shin <[email protected]>
@ 2026-05-31 21:11 ` Scott Ray <[email protected]>
2026-06-04 05:41 ` Re: [PATCH] Don't call ereport(ERROR) from recovery target GUC assign hooks JoongHyuk Shin <[email protected]>
0 siblings, 1 reply; 868+ messages in thread
From: Scott Ray @ 2026-05-31 21:11 UTC (permalink / raw)
To: JoongHyuk Shin <[email protected]>; +Cc: Fujii Masao <[email protected]>; Michael Paquier <[email protected]>; [email protected]
Thanks for the patch. I've attached v1-0001 (atop v4) addressing the
UX and test-coverage items below. Happy to rework or fold in however
you prefer.
1. There's a configuration trap in master and in this branch that
could be prevented using something very similar to
CheckRecoveryTargetConflicts to check pending GUCs:
psql -c "ALTER SYSTEM SET recovery_target_xid TO '700'"
psql -c "ALTER SYSTEM SET recovery_target_time TO '2026-01-01 00:00:00'"
pg_ctl reload
The log shows:
LOG: received SIGHUP, reloading configuration files
LOG: parameter "recovery_target_xid" cannot be changed without restarting the server
LOG: parameter "recovery_target_time" cannot be changed without restarting the server
LOG: configuration file "postgresql.auto.conf" contains errors; unaffected changes were applied
pg_settings shows:
postgres=# SELECT name, setting, pending_restart FROM pg_settings
WHERE name LIKE 'recovery_target%' AND pending_restart;
name | setting | pending_restart
---------------------+---------+-----------------
recovery_target_time | | t
recovery_target_xid | | t
The db runs fine until the next restart, maybe hours later:
FATAL: multiple recovery targets specified
DETAIL: At most one of "recovery_target", "recovery_target_lsn",
"recovery_target_name", "recovery_target_time",
"recovery_target_xid" can be set.
Is it worth a follow-up to report the conflict early and loud?
2. There's an opportunity to provide a better UX by reporting which
flags were set and what the values were, so that the user doesn't have
to search config files or other logs to find this info. For instance,
in the postgresql.auto.conf scenario above, instead of:
DETAIL: At most one of "recovery_target", "recovery_target_lsn",
"recovery_target_name", "recovery_target_time",
"recovery_target_xid" can be set.
The operator could see:
DETAIL: The following recovery target parameters are set:
"recovery_target_time" = "2026-01-01 00:00:00",
"recovery_target_xid" = "700".
HINT: At most one of "recovery_target", "recovery_target_lsn",
"recovery_target_name", "recovery_target_time",
"recovery_target_xid" can be set.
3. 003_recovery_targets.pl:339 currently tests recovery_target_xid's
cleared-then-set behavior. The patch adds the same coverage for the
other four recovery_target_* GUCs.
--
Scott Ray
Attachments:
[application/octet-stream] v1-0001-Report-set-parameters-on-recovery_target-conflict.patch (6.2K, ../../M5a4v6-PJIZSNQlLhGy-GgBPCXNkrW3OnbESqTpaXh2TyZl4TsMyY_wPK-chiMOoZ4mr9Dm7vuqDSVmNeMKfZYNYlI5PxoQyC6iqWZ9MTAk=@scottray.io/2-v1-0001-Report-set-parameters-on-recovery_target-conflict.patch)
download | inline diff:
From c61284c824429778ad8f123e22862931d41a2a6d Mon Sep 17 00:00:00 2001
From: Scott Ray <[email protected]>
Date: Sun, 31 May 2026 13:12:29 -0700
Subject: [PATCH v1] Report set parameters on recovery_target conflict; expand
tests
v4 of "Don't call ereport(ERROR) from recovery target GUC assign
hooks" produces a FATAL with an errdetail that lists all five
recovery_target_* GUCs regardless of which the operator actually
set, and exercises only recovery_target_xid in the cleared-then-set
direction.
This patch makes CheckRecoveryTargetConflicts() report the names
and values of the GUCs that are actually non-empty in errdetail,
moving the "at most one of [list]" enumeration to errhint. The
five hand-written GetConfigOption() calls collapse into a loop over
a static target_names[] array, so adding a sixth recovery_target_*
GUC requires only updating the array; both error messages are
derived from it.
The TAP test gains four cleared-then-set cases covering time, name,
lsn, and the bare recovery_target, mirroring the existing xid case.
A new like() assertion verifies that the errdetail names which GUCs
are set and their values.
Applies atop v4.
Discussion: https://postgr.es/m/CACSdjfPUa4UvKjADgOERXoxNYmCg2mqqiqKkiJk6mX6E4qgVFw@mail.gmail.com
---
src/backend/access/transam/xlogrecovery.c | 61 ++++++++++++---------
src/test/recovery/t/003_recovery_targets.pl | 40 ++++++++++++++
2 files changed, 76 insertions(+), 25 deletions(-)
diff --git a/src/backend/access/transam/xlogrecovery.c b/src/backend/access/transam/xlogrecovery.c
index 1253bed1058..e48e21631b2 100644
--- a/src/backend/access/transam/xlogrecovery.c
+++ b/src/backend/access/transam/xlogrecovery.c
@@ -1167,41 +1167,52 @@ validateRecoveryParameters(void)
* assign hooks must never fail. Moving the check here keeps the assign hooks
* contract-compliant.
*
- * If a future patch adds a sixth recovery_target_* GUC, both this list and
- * the errdetail below must be updated.
+ * If a future patch adds a sixth recovery_target_* GUC, add its name to
+ * target_names below; both error messages are derived from that array.
*/
static void
CheckRecoveryTargetConflicts(void)
{
+ static const char *const target_names[] = {
+ "recovery_target",
+ "recovery_target_lsn",
+ "recovery_target_name",
+ "recovery_target_time",
+ "recovery_target_xid",
+ };
+ StringInfoData set_targets;
+ StringInfoData all_targets;
int ntargets = 0;
- const char *val;
-
- /* missing_ok=false guarantees val is non-NULL. */
- val = GetConfigOption("recovery_target", false, false);
- if (val[0] != '\0')
- ntargets++;
- val = GetConfigOption("recovery_target_lsn", false, false);
- if (val[0] != '\0')
- ntargets++;
- val = GetConfigOption("recovery_target_name", false, false);
- if (val[0] != '\0')
- ntargets++;
- val = GetConfigOption("recovery_target_time", false, false);
- if (val[0] != '\0')
- ntargets++;
- val = GetConfigOption("recovery_target_xid", false, false);
- if (val[0] != '\0')
- ntargets++;
+
+ initStringInfo(&set_targets);
+ initStringInfo(&all_targets);
+
+ for (int i = 0; i < lengthof(target_names); i++)
+ {
+ /* missing_ok=false guarantees val is non-NULL. */
+ const char *val = GetConfigOption(target_names[i], false, false);
+
+ if (i > 0)
+ appendStringInfoString(&all_targets, ", ");
+ appendStringInfo(&all_targets, "\"%s\"", target_names[i]);
+
+ if (val[0] != '\0')
+ {
+ if (ntargets > 0)
+ appendStringInfoString(&set_targets, ", ");
+ appendStringInfo(&set_targets, "\"%s\" = \"%s\"",
+ target_names[i], val);
+ ntargets++;
+ }
+ }
if (ntargets > 1)
ereport(FATAL,
(errcode(ERRCODE_INVALID_PARAMETER_VALUE),
errmsg("multiple recovery targets specified"),
- errdetail("At most one of \"recovery_target\", "
- "\"recovery_target_lsn\", "
- "\"recovery_target_name\", "
- "\"recovery_target_time\", "
- "\"recovery_target_xid\" can be set.")));
+ errdetail("The following recovery target parameters are set: %s.",
+ set_targets.data),
+ errhint("At most one of %s can be set.", all_targets.data)));
}
/*
diff --git a/src/test/recovery/t/003_recovery_targets.pl b/src/test/recovery/t/003_recovery_targets.pl
index 5979663b0ab..3e68c01968b 100644
--- a/src/test/recovery/t/003_recovery_targets.pl
+++ b/src/test/recovery/t/003_recovery_targets.pl
@@ -282,6 +282,14 @@ like(
qr/multiple recovery targets specified/,
'expected error message logged without recovery.signal');
+# Ordering in the errdetail follows target_names[] in CheckRecoveryTargetConflicts:
+# recovery_target, recovery_target_lsn, recovery_target_name,
+# recovery_target_time, recovery_target_xid.
+like(
+ $logfile_no_signal,
+ qr/are set: "recovery_target_name" = "[^"]+", "recovery_target_time" = "[^"]+"/,
+ 'errdetail names which recovery_target_* GUCs are set and their values');
+
# Same-GUC last-wins (one source of truth for the GUC's value): assigning a
# recovery_target_* GUC and then assigning the same GUC to an empty string
# leaves no target set and recovery proceeds to the end of WAL. This is the
@@ -358,6 +366,38 @@ is($count_xid_clear_set, "2000",
'recovery_target_xid honored when cleared then set');
$node_xid_clear_set->teardown_node;
+test_recovery_standby_with_options(
+ 'recovery_target_time cleared then set',
+ 'standby_time_clear_set',
+ $node_primary,
+ "-c recovery_target_time= -c recovery_target_time=$recovery_time_t",
+ "3000",
+ $lsn3);
+
+test_recovery_standby_with_options(
+ 'recovery_target_name cleared then set',
+ 'standby_name_clear_set',
+ $node_primary,
+ "-c recovery_target_name= -c recovery_target_name=$recovery_name",
+ "4000",
+ $lsn4);
+
+test_recovery_standby_with_options(
+ 'recovery_target_lsn cleared then set',
+ 'standby_lsn_clear_set',
+ $node_primary,
+ "-c recovery_target_lsn= -c recovery_target_lsn=$recovery_lsn",
+ "5000",
+ $lsn5);
+
+test_recovery_standby_with_options(
+ 'recovery_target cleared then set',
+ 'standby_immediate_clear_set',
+ $node_primary,
+ "-c recovery_target= -c recovery_target=immediate",
+ "1000",
+ $lsn1);
+
# Invalid recovery_target_timeline tests
my ($result, $stdout, $stderr) = $node_primary->psql('postgres',
"ALTER SYSTEM SET recovery_target_timeline TO 'bogus'");
--
2.50.1 (Apple Git-155)
[application/pgp-signature] signature.asc (343B, ../../M5a4v6-PJIZSNQlLhGy-GgBPCXNkrW3OnbESqTpaXh2TyZl4TsMyY_wPK-chiMOoZ4mr9Dm7vuqDSVmNeMKfZYNYlI5PxoQyC6iqWZ9MTAk=@scottray.io/3-signature.asc)
download
^ permalink raw reply [nested|flat] 868+ messages in thread
* Re: [PATCH] Don't call ereport(ERROR) from recovery target GUC assign hooks
2026-04-13 08:21 [PATCH] Don't call ereport(ERROR) from recovery target GUC assign hooks JoongHyuk Shin <[email protected]>
2026-04-24 13:08 ` Re: [PATCH] Don't call ereport(ERROR) from recovery target GUC assign hooks Fujii Masao <[email protected]>
2026-04-27 01:52 ` Re: [PATCH] Don't call ereport(ERROR) from recovery target GUC assign hooks Michael Paquier <[email protected]>
2026-04-27 05:36 ` Re: [PATCH] Don't call ereport(ERROR) from recovery target GUC assign hooks Fujii Masao <[email protected]>
2026-04-27 05:59 ` Re: [PATCH] Don't call ereport(ERROR) from recovery target GUC assign hooks Michael Paquier <[email protected]>
2026-04-29 09:29 ` Re: [PATCH] Don't call ereport(ERROR) from recovery target GUC assign hooks JoongHyuk Shin <[email protected]>
2026-05-01 05:53 ` Re: [PATCH] Don't call ereport(ERROR) from recovery target GUC assign hooks Fujii Masao <[email protected]>
2026-05-11 03:17 ` Re: [PATCH] Don't call ereport(ERROR) from recovery target GUC assign hooks JoongHyuk Shin <[email protected]>
2026-05-11 06:01 ` Re: [PATCH] Don't call ereport(ERROR) from recovery target GUC assign hooks JoongHyuk Shin <[email protected]>
2026-05-31 21:11 ` Re: [PATCH] Don't call ereport(ERROR) from recovery target GUC assign hooks Scott Ray <[email protected]>
@ 2026-06-04 05:41 ` JoongHyuk Shin <[email protected]>
2026-06-06 20:10 ` Re: [PATCH] Don't call ereport(ERROR) from recovery target GUC assign hooks Scott Ray <[email protected]>
0 siblings, 1 reply; 868+ messages in thread
From: JoongHyuk Shin @ 2026-06-04 05:41 UTC (permalink / raw)
To: [email protected]; +Cc: Fujii Masao <[email protected]>; Michael Paquier <[email protected]>; [email protected]
Thanks for the patch.
I went through 'v1-0001-Report-....patch'
and have a few observations to share.
* Function structure: the recovery_target_* set has been
historically stable, so array + loop abstraction adds limited
value; function size grows ~34% (32 -> 43 lines) for one line of
savings on a hypothetical sixth GUC, while the closest precedent
(archive_command / archive_library in pgarch.c) is a hard-coded literal.
* errhint vs errdetail: errhint("At most one of %s can be set.")
reads more like a constraint than an action hint. The closest
precedent, archive_command / archive_library in pgarch.c
(ProcessPgArchInterrupts() / LoadArchiveLibrary()), keeps the
enumeration in errdetail and omits errhint entirely.
* TAP regex: the added like() uses [^"]+ for the values, which
passes regardless of the actual value. Using quotemeta on the
expected values would verify the actual content, and anchoring
would also avoid accidentally matching the same tokens inside
errhint.
On the reload trap:
I reproduced this on master and confirmed it's there exactly as you noted.
ALTER SYSTEM doesn't trigger the assign hook;
it just writes to postgresql.auto.conf,
so the trap window is intrinsic to PGC_POSTMASTER + ALTER SYSTEM.
A separate follow-up patch in the reload path feels natural.
--
JH Shin
On Mon, Jun 1, 2026 at 6:11 AM Scott Ray <[email protected]> wrote:
> Thanks for the patch. I've attached v1-0001 (atop v4) addressing the
> UX and test-coverage items below. Happy to rework or fold in however
> you prefer.
>
> 1. There's a configuration trap in master and in this branch that
> could be prevented using something very similar to
> CheckRecoveryTargetConflicts to check pending GUCs:
>
> psql -c "ALTER SYSTEM SET recovery_target_xid TO '700'"
> psql -c "ALTER SYSTEM SET recovery_target_time TO '2026-01-01
> 00:00:00'"
> pg_ctl reload
>
> The log shows:
>
> LOG: received SIGHUP, reloading configuration files
> LOG: parameter "recovery_target_xid" cannot be changed without
> restarting the server
> LOG: parameter "recovery_target_time" cannot be changed without
> restarting the server
> LOG: configuration file "postgresql.auto.conf" contains errors;
> unaffected changes were applied
>
> pg_settings shows:
>
> postgres=# SELECT name, setting, pending_restart FROM pg_settings
> WHERE name LIKE 'recovery_target%' AND pending_restart;
> name | setting | pending_restart
> ---------------------+---------+-----------------
> recovery_target_time | | t
> recovery_target_xid | | t
>
> The db runs fine until the next restart, maybe hours later:
>
> FATAL: multiple recovery targets specified
> DETAIL: At most one of "recovery_target", "recovery_target_lsn",
> "recovery_target_name", "recovery_target_time",
> "recovery_target_xid" can be set.
>
> Is it worth a follow-up to report the conflict early and loud?
>
> 2. There's an opportunity to provide a better UX by reporting which
> flags were set and what the values were, so that the user doesn't have
> to search config files or other logs to find this info. For instance,
> in the postgresql.auto.conf scenario above, instead of:
>
> DETAIL: At most one of "recovery_target", "recovery_target_lsn",
> "recovery_target_name", "recovery_target_time",
> "recovery_target_xid" can be set.
>
> The operator could see:
>
> DETAIL: The following recovery target parameters are set:
> "recovery_target_time" = "2026-01-01 00:00:00",
> "recovery_target_xid" = "700".
> HINT: At most one of "recovery_target", "recovery_target_lsn",
> "recovery_target_name", "recovery_target_time",
> "recovery_target_xid" can be set.
>
> 3. 003_recovery_targets.pl:339 currently tests recovery_target_xid's
> cleared-then-set behavior. The patch adds the same coverage for the
> other four recovery_target_* GUCs.
>
>
> --
> Scott Ray
>
^ permalink raw reply [nested|flat] 868+ messages in thread
* Re: [PATCH] Don't call ereport(ERROR) from recovery target GUC assign hooks
2026-04-13 08:21 [PATCH] Don't call ereport(ERROR) from recovery target GUC assign hooks JoongHyuk Shin <[email protected]>
2026-04-24 13:08 ` Re: [PATCH] Don't call ereport(ERROR) from recovery target GUC assign hooks Fujii Masao <[email protected]>
2026-04-27 01:52 ` Re: [PATCH] Don't call ereport(ERROR) from recovery target GUC assign hooks Michael Paquier <[email protected]>
2026-04-27 05:36 ` Re: [PATCH] Don't call ereport(ERROR) from recovery target GUC assign hooks Fujii Masao <[email protected]>
2026-04-27 05:59 ` Re: [PATCH] Don't call ereport(ERROR) from recovery target GUC assign hooks Michael Paquier <[email protected]>
2026-04-29 09:29 ` Re: [PATCH] Don't call ereport(ERROR) from recovery target GUC assign hooks JoongHyuk Shin <[email protected]>
2026-05-01 05:53 ` Re: [PATCH] Don't call ereport(ERROR) from recovery target GUC assign hooks Fujii Masao <[email protected]>
2026-05-11 03:17 ` Re: [PATCH] Don't call ereport(ERROR) from recovery target GUC assign hooks JoongHyuk Shin <[email protected]>
2026-05-11 06:01 ` Re: [PATCH] Don't call ereport(ERROR) from recovery target GUC assign hooks JoongHyuk Shin <[email protected]>
2026-05-31 21:11 ` Re: [PATCH] Don't call ereport(ERROR) from recovery target GUC assign hooks Scott Ray <[email protected]>
2026-06-04 05:41 ` Re: [PATCH] Don't call ereport(ERROR) from recovery target GUC assign hooks JoongHyuk Shin <[email protected]>
@ 2026-06-06 20:10 ` Scott Ray <[email protected]>
2026-06-07 10:30 ` Re: [PATCH] Don't call ereport(ERROR) from recovery target GUC assign hooks JoongHyuk Shin <[email protected]>
0 siblings, 1 reply; 868+ messages in thread
From: Scott Ray @ 2026-06-06 20:10 UTC (permalink / raw)
To: JoongHyuk Shin <[email protected]>; +Cc: Fujii Masao <[email protected]>; Michael Paquier <[email protected]>; [email protected]
> * Function structure: the recovery_target_* set has been
> historically stable, so array + loop abstraction adds limited
> value; function size grows ~34% (32 -> 43 lines) for one line of
> savings on a hypothetical sixth GUC, while the closest precedent
> (archive_command / archive_library in pgarch.c) is a hard-coded literal.
>
> * errhint vs errdetail: errhint("At most one of %s can be set.")
> reads more like a constraint than an action hint. The closest
> precedent, archive_command / archive_library in pgarch.c
> (ProcessPgArchInterrupts() / LoadArchiveLibrary()), keeps the
> enumeration in errdetail and omits errhint entirely.
>
> * TAP regex: the added like() uses [^"]+ for the values, which
> passes regardless of the actual value. Using quotemeta on the
> expected values would verify the actual content, and anchoring
> would also avoid accidentally matching the same tokens inside
> errhint.
Thanks for taking a look. I attached a v2 that applies your suggestions
and uses "set to" instead of "=" to match convention. What do you think?
Sample output:
FATAL: multiple recovery targets specified
DETAIL: At most one of "recovery_target", "recovery_target_lsn",
"recovery_target_name", "recovery_target_time",
"recovery_target_xid" can be set. Currently set:
"recovery_target_time" set to "2026-01-01 00:00:00",
"recovery_target_xid" set to "700".
--
Scott Ray
Attachments:
[application/octet-stream] v2-0001-Report-set-parameters-on-recovery_target-conflict.patch (6.0K, ../../0WrsV1VojwurDot6hdS0norm0nK9QFDk0KSLqBcXK4Xz2b_sMuIdE0zyiXP0p1hTRD7Hwz3C8AQouGyV7yZMhf5joz3zUpQee3l_pWPs4dk=@scottray.io/2-v2-0001-Report-set-parameters-on-recovery_target-conflict.patch)
download | inline diff:
From 9b96e0f327d7ca2f644b259511776ef7286ec72f Mon Sep 17 00:00:00 2001
From: Scott Ray <[email protected]>
Date: Sun, 31 May 2026 13:12:29 -0700
Subject: [PATCH v2] Report set parameters on recovery_target conflict; expand
tests
v4 of "Don't call ereport(ERROR) from recovery target GUC assign
hooks" produces a FATAL with an errdetail that lists all five
recovery_target_* GUCs regardless of which the operator actually
set, and exercises only recovery_target_xid in the cleared-then-set
direction.
This patch appends the names and values of the GUCs that are
actually non-empty as a trailing sentence in the existing errdetail.
The "at most one of [list]" enumeration stays in errdetail and no
errhint is emitted, matching the archive_command / archive_library
precedent in pgarch.c.
The TAP test gains four cleared-then-set cases covering time, name,
lsn, and the bare recovery_target, mirroring the existing xid case.
A new like() assertion verifies that the errdetail names which GUCs
are set and their values, using quotemeta on the expected values.
Applies atop v4.
Discussion: https://postgr.es/m/CACSdjfPUa4UvKjADgOERXoxNYmCg2mqqiqKkiJk6mX6E4qgVFw@mail.gmail.com
---
src/backend/access/transam/xlogrecovery.c | 35 ++++++++++++++++--
src/test/recovery/t/003_recovery_targets.pl | 40 +++++++++++++++++++++
2 files changed, 72 insertions(+), 3 deletions(-)
diff --git a/src/backend/access/transam/xlogrecovery.c b/src/backend/access/transam/xlogrecovery.c
index 1253bed1058..6af36960ddc 100644
--- a/src/backend/access/transam/xlogrecovery.c
+++ b/src/backend/access/transam/xlogrecovery.c
@@ -1167,31 +1167,58 @@ validateRecoveryParameters(void)
* assign hooks must never fail. Moving the check here keeps the assign hooks
* contract-compliant.
*
- * If a future patch adds a sixth recovery_target_* GUC, both this list and
- * the errdetail below must be updated.
+ * If a future patch adds a sixth recovery_target_* GUC, add another
+ * GetConfigOption block below to include it in the "Currently set:"
+ * suffix, and extend the fixed enumeration in the errdetail.
*/
static void
CheckRecoveryTargetConflicts(void)
{
+ StringInfoData set_targets;
int ntargets = 0;
const char *val;
+ initStringInfo(&set_targets);
+
/* missing_ok=false guarantees val is non-NULL. */
val = GetConfigOption("recovery_target", false, false);
if (val[0] != '\0')
+ {
+ appendStringInfo(&set_targets, "\"recovery_target\" set to \"%s\"", val);
ntargets++;
+ }
val = GetConfigOption("recovery_target_lsn", false, false);
if (val[0] != '\0')
+ {
+ if (ntargets > 0)
+ appendStringInfoString(&set_targets, ", ");
+ appendStringInfo(&set_targets, "\"recovery_target_lsn\" set to \"%s\"", val);
ntargets++;
+ }
val = GetConfigOption("recovery_target_name", false, false);
if (val[0] != '\0')
+ {
+ if (ntargets > 0)
+ appendStringInfoString(&set_targets, ", ");
+ appendStringInfo(&set_targets, "\"recovery_target_name\" set to \"%s\"", val);
ntargets++;
+ }
val = GetConfigOption("recovery_target_time", false, false);
if (val[0] != '\0')
+ {
+ if (ntargets > 0)
+ appendStringInfoString(&set_targets, ", ");
+ appendStringInfo(&set_targets, "\"recovery_target_time\" set to \"%s\"", val);
ntargets++;
+ }
val = GetConfigOption("recovery_target_xid", false, false);
if (val[0] != '\0')
+ {
+ if (ntargets > 0)
+ appendStringInfoString(&set_targets, ", ");
+ appendStringInfo(&set_targets, "\"recovery_target_xid\" set to \"%s\"", val);
ntargets++;
+ }
if (ntargets > 1)
ereport(FATAL,
@@ -1201,7 +1228,9 @@ CheckRecoveryTargetConflicts(void)
"\"recovery_target_lsn\", "
"\"recovery_target_name\", "
"\"recovery_target_time\", "
- "\"recovery_target_xid\" can be set.")));
+ "\"recovery_target_xid\" can be set. "
+ "Currently set: %s.",
+ set_targets.data)));
}
/*
diff --git a/src/test/recovery/t/003_recovery_targets.pl b/src/test/recovery/t/003_recovery_targets.pl
index 5979663b0ab..f00a82d4e9e 100644
--- a/src/test/recovery/t/003_recovery_targets.pl
+++ b/src/test/recovery/t/003_recovery_targets.pl
@@ -282,6 +282,14 @@ like(
qr/multiple recovery targets specified/,
'expected error message logged without recovery.signal');
+# Ordering in the errdetail follows the GetConfigOption sequence in
+# CheckRecoveryTargetConflicts: recovery_target, recovery_target_lsn,
+# recovery_target_name, recovery_target_time, recovery_target_xid.
+like(
+ $logfile_no_signal,
+ qr/Currently set: "recovery_target_name" set to "\Q$recovery_name\E", "recovery_target_time" set to "\Q$recovery_time\E"/,
+ 'errdetail names which recovery_target_* GUCs are set and their values');
+
# Same-GUC last-wins (one source of truth for the GUC's value): assigning a
# recovery_target_* GUC and then assigning the same GUC to an empty string
# leaves no target set and recovery proceeds to the end of WAL. This is the
@@ -358,6 +366,38 @@ is($count_xid_clear_set, "2000",
'recovery_target_xid honored when cleared then set');
$node_xid_clear_set->teardown_node;
+test_recovery_standby_with_options(
+ 'recovery_target_time cleared then set',
+ 'standby_time_clear_set',
+ $node_primary,
+ "-c recovery_target_time= -c recovery_target_time=$recovery_time_t",
+ "3000",
+ $lsn3);
+
+test_recovery_standby_with_options(
+ 'recovery_target_name cleared then set',
+ 'standby_name_clear_set',
+ $node_primary,
+ "-c recovery_target_name= -c recovery_target_name=$recovery_name",
+ "4000",
+ $lsn4);
+
+test_recovery_standby_with_options(
+ 'recovery_target_lsn cleared then set',
+ 'standby_lsn_clear_set',
+ $node_primary,
+ "-c recovery_target_lsn= -c recovery_target_lsn=$recovery_lsn",
+ "5000",
+ $lsn5);
+
+test_recovery_standby_with_options(
+ 'recovery_target cleared then set',
+ 'standby_immediate_clear_set',
+ $node_primary,
+ "-c recovery_target= -c recovery_target=immediate",
+ "1000",
+ $lsn1);
+
# Invalid recovery_target_timeline tests
my ($result, $stdout, $stderr) = $node_primary->psql('postgres',
"ALTER SYSTEM SET recovery_target_timeline TO 'bogus'");
--
2.50.1 (Apple Git-155)
[application/pgp-signature] signature.asc (343B, ../../0WrsV1VojwurDot6hdS0norm0nK9QFDk0KSLqBcXK4Xz2b_sMuIdE0zyiXP0p1hTRD7Hwz3C8AQouGyV7yZMhf5joz3zUpQee3l_pWPs4dk=@scottray.io/3-signature.asc)
download
^ permalink raw reply [nested|flat] 868+ messages in thread
* Re: [PATCH] Don't call ereport(ERROR) from recovery target GUC assign hooks
2026-04-13 08:21 [PATCH] Don't call ereport(ERROR) from recovery target GUC assign hooks JoongHyuk Shin <[email protected]>
2026-04-24 13:08 ` Re: [PATCH] Don't call ereport(ERROR) from recovery target GUC assign hooks Fujii Masao <[email protected]>
2026-04-27 01:52 ` Re: [PATCH] Don't call ereport(ERROR) from recovery target GUC assign hooks Michael Paquier <[email protected]>
2026-04-27 05:36 ` Re: [PATCH] Don't call ereport(ERROR) from recovery target GUC assign hooks Fujii Masao <[email protected]>
2026-04-27 05:59 ` Re: [PATCH] Don't call ereport(ERROR) from recovery target GUC assign hooks Michael Paquier <[email protected]>
2026-04-29 09:29 ` Re: [PATCH] Don't call ereport(ERROR) from recovery target GUC assign hooks JoongHyuk Shin <[email protected]>
2026-05-01 05:53 ` Re: [PATCH] Don't call ereport(ERROR) from recovery target GUC assign hooks Fujii Masao <[email protected]>
2026-05-11 03:17 ` Re: [PATCH] Don't call ereport(ERROR) from recovery target GUC assign hooks JoongHyuk Shin <[email protected]>
2026-05-11 06:01 ` Re: [PATCH] Don't call ereport(ERROR) from recovery target GUC assign hooks JoongHyuk Shin <[email protected]>
2026-05-31 21:11 ` Re: [PATCH] Don't call ereport(ERROR) from recovery target GUC assign hooks Scott Ray <[email protected]>
2026-06-04 05:41 ` Re: [PATCH] Don't call ereport(ERROR) from recovery target GUC assign hooks JoongHyuk Shin <[email protected]>
2026-06-06 20:10 ` Re: [PATCH] Don't call ereport(ERROR) from recovery target GUC assign hooks Scott Ray <[email protected]>
@ 2026-06-07 10:30 ` JoongHyuk Shin <[email protected]>
2026-06-07 15:44 ` Re: [PATCH] Don't call ereport(ERROR) from recovery target GUC assign hooks Álvaro Herrera <[email protected]>
0 siblings, 1 reply; 868+ messages in thread
From: JoongHyuk Shin @ 2026-06-07 10:30 UTC (permalink / raw)
To: Scott Ray <[email protected]>; +Cc: Fujii Masao <[email protected]>; Michael Paquier <[email protected]>; [email protected]
On "=" vs "set to": I'd stay with "=".
The list is assembled in appendStringInfo and ends up in the dynamic %s,
so prose like "set to" there never goes through gettext
and would print in English even under a translated lc_messages.
"=" is punctuation, so it sidesteps that.
PG already uses this form in the RI detail,
e.g. Key (%s)=(%s) in ri_triggers.c.
That said, I'm not sure the currently-set list belongs in the errdetail at
all,
since an operator can read the values back from the configuration.
I'd be interested in the committer's view on whether it is worth adding.
--
JH Shin
On Sun, Jun 7, 2026 at 5:10 AM Scott Ray <[email protected]> wrote:
> > * Function structure: the recovery_target_* set has been
> > historically stable, so array + loop abstraction adds limited
> > value; function size grows ~34% (32 -> 43 lines) for one line of
> > savings on a hypothetical sixth GUC, while the closest precedent
> > (archive_command / archive_library in pgarch.c) is a hard-coded literal.
> >
>
> > * errhint vs errdetail: errhint("At most one of %s can be set.")
> > reads more like a constraint than an action hint. The closest
> > precedent, archive_command / archive_library in pgarch.c
> > (ProcessPgArchInterrupts() / LoadArchiveLibrary()), keeps the
> > enumeration in errdetail and omits errhint entirely.
> >
>
> > * TAP regex: the added like() uses [^"]+ for the values, which
> > passes regardless of the actual value. Using quotemeta on the
> > expected values would verify the actual content, and anchoring
> > would also avoid accidentally matching the same tokens inside
> > errhint.
>
> Thanks for taking a look. I attached a v2 that applies your suggestions
> and uses "set to" instead of "=" to match convention. What do you think?
>
> Sample output:
>
> FATAL: multiple recovery targets specified
> DETAIL: At most one of "recovery_target", "recovery_target_lsn",
> "recovery_target_name", "recovery_target_time",
> "recovery_target_xid" can be set. Currently set:
> "recovery_target_time" set to "2026-01-01 00:00:00",
> "recovery_target_xid" set to "700".
>
> --
> Scott Ray
>
^ permalink raw reply [nested|flat] 868+ messages in thread
* Re: [PATCH] Don't call ereport(ERROR) from recovery target GUC assign hooks
2026-04-13 08:21 [PATCH] Don't call ereport(ERROR) from recovery target GUC assign hooks JoongHyuk Shin <[email protected]>
2026-04-24 13:08 ` Re: [PATCH] Don't call ereport(ERROR) from recovery target GUC assign hooks Fujii Masao <[email protected]>
2026-04-27 01:52 ` Re: [PATCH] Don't call ereport(ERROR) from recovery target GUC assign hooks Michael Paquier <[email protected]>
2026-04-27 05:36 ` Re: [PATCH] Don't call ereport(ERROR) from recovery target GUC assign hooks Fujii Masao <[email protected]>
2026-04-27 05:59 ` Re: [PATCH] Don't call ereport(ERROR) from recovery target GUC assign hooks Michael Paquier <[email protected]>
2026-04-29 09:29 ` Re: [PATCH] Don't call ereport(ERROR) from recovery target GUC assign hooks JoongHyuk Shin <[email protected]>
2026-05-01 05:53 ` Re: [PATCH] Don't call ereport(ERROR) from recovery target GUC assign hooks Fujii Masao <[email protected]>
2026-05-11 03:17 ` Re: [PATCH] Don't call ereport(ERROR) from recovery target GUC assign hooks JoongHyuk Shin <[email protected]>
2026-05-11 06:01 ` Re: [PATCH] Don't call ereport(ERROR) from recovery target GUC assign hooks JoongHyuk Shin <[email protected]>
2026-05-31 21:11 ` Re: [PATCH] Don't call ereport(ERROR) from recovery target GUC assign hooks Scott Ray <[email protected]>
2026-06-04 05:41 ` Re: [PATCH] Don't call ereport(ERROR) from recovery target GUC assign hooks JoongHyuk Shin <[email protected]>
2026-06-06 20:10 ` Re: [PATCH] Don't call ereport(ERROR) from recovery target GUC assign hooks Scott Ray <[email protected]>
2026-06-07 10:30 ` Re: [PATCH] Don't call ereport(ERROR) from recovery target GUC assign hooks JoongHyuk Shin <[email protected]>
@ 2026-06-07 15:44 ` Álvaro Herrera <[email protected]>
2026-06-21 09:27 ` Re: [PATCH] Don't call ereport(ERROR) from recovery target GUC assign hooks JoongHyuk Shin <[email protected]>
0 siblings, 1 reply; 868+ messages in thread
From: Álvaro Herrera @ 2026-06-07 15:44 UTC (permalink / raw)
To: JoongHyuk Shin <[email protected]>; +Cc: Scott Ray <[email protected]>; Fujii Masao <[email protected]>; Michael Paquier <[email protected]>; [email protected]
On 2026-Jun-07, JoongHyuk Shin wrote:
> On "=" vs "set to": I'd stay with "=".
> The list is assembled in appendStringInfo and ends up in the dynamic %s,
> so prose like "set to" there never goes through gettext
> and would print in English even under a translated lc_messages.
> "=" is punctuation, so it sidesteps that.
Agreed on using =, although ...
> That said, I'm not sure the currently-set list belongs in the errdetail at
> all,
> since an operator can read the values back from the configuration.
> I'd be interested in the committer's view on whether it is worth adding.
It may be enough to list which ones are set, without listing their values.
Those can be obtained easily from pg_settings, which can be mentioned in
errhint -- useful also to figure out exactly _where_ they are set (e.g.,
in an include file, postgresql.auto.conf, and so on.)
--
Álvaro Herrera Breisgau, Deutschland — https://www.EnterpriseDB.com/
^ permalink raw reply [nested|flat] 868+ messages in thread
* Re: [PATCH] Don't call ereport(ERROR) from recovery target GUC assign hooks
2026-04-13 08:21 [PATCH] Don't call ereport(ERROR) from recovery target GUC assign hooks JoongHyuk Shin <[email protected]>
2026-04-24 13:08 ` Re: [PATCH] Don't call ereport(ERROR) from recovery target GUC assign hooks Fujii Masao <[email protected]>
2026-04-27 01:52 ` Re: [PATCH] Don't call ereport(ERROR) from recovery target GUC assign hooks Michael Paquier <[email protected]>
2026-04-27 05:36 ` Re: [PATCH] Don't call ereport(ERROR) from recovery target GUC assign hooks Fujii Masao <[email protected]>
2026-04-27 05:59 ` Re: [PATCH] Don't call ereport(ERROR) from recovery target GUC assign hooks Michael Paquier <[email protected]>
2026-04-29 09:29 ` Re: [PATCH] Don't call ereport(ERROR) from recovery target GUC assign hooks JoongHyuk Shin <[email protected]>
2026-05-01 05:53 ` Re: [PATCH] Don't call ereport(ERROR) from recovery target GUC assign hooks Fujii Masao <[email protected]>
2026-05-11 03:17 ` Re: [PATCH] Don't call ereport(ERROR) from recovery target GUC assign hooks JoongHyuk Shin <[email protected]>
2026-05-11 06:01 ` Re: [PATCH] Don't call ereport(ERROR) from recovery target GUC assign hooks JoongHyuk Shin <[email protected]>
2026-05-31 21:11 ` Re: [PATCH] Don't call ereport(ERROR) from recovery target GUC assign hooks Scott Ray <[email protected]>
2026-06-04 05:41 ` Re: [PATCH] Don't call ereport(ERROR) from recovery target GUC assign hooks JoongHyuk Shin <[email protected]>
2026-06-06 20:10 ` Re: [PATCH] Don't call ereport(ERROR) from recovery target GUC assign hooks Scott Ray <[email protected]>
2026-06-07 10:30 ` Re: [PATCH] Don't call ereport(ERROR) from recovery target GUC assign hooks JoongHyuk Shin <[email protected]>
2026-06-07 15:44 ` Re: [PATCH] Don't call ereport(ERROR) from recovery target GUC assign hooks Álvaro Herrera <[email protected]>
@ 2026-06-21 09:27 ` JoongHyuk Shin <[email protected]>
2026-06-21 12:31 ` Re: [PATCH] Don't call ereport(ERROR) from recovery target GUC assign hooks Álvaro Herrera <[email protected]>
0 siblings, 1 reply; 868+ messages in thread
From: JoongHyuk Shin @ 2026-06-21 09:27 UTC (permalink / raw)
To: Álvaro Herrera <[email protected]>; +Cc: Scott Ray <[email protected]>; Fujii Masao <[email protected]>; Michael Paquier <[email protected]>; [email protected]
Thanks for the reviews.
v5 attached.
The errdetail now lists which recovery_target_* parameters are actually set,
instead of the full candidate list.
This follows Scott's idea to surface the set targets;
following Álvaro, the values are dropped
and a new errhint points to pg_settings for them and their sources.
I kept the "which ones are set" list in errdetail rather than errhint,
it states the current configuration, which reads as detail,
while the errhint carries the actionable pg_settings pointer.
--
JH Shin
On Mon, Jun 8, 2026 at 12:44 AM Álvaro Herrera <[email protected]> wrote:
> On 2026-Jun-07, JoongHyuk Shin wrote:
>
> > On "=" vs "set to": I'd stay with "=".
> > The list is assembled in appendStringInfo and ends up in the dynamic %s,
> > so prose like "set to" there never goes through gettext
> > and would print in English even under a translated lc_messages.
> > "=" is punctuation, so it sidesteps that.
>
> Agreed on using =, although ...
>
> > That said, I'm not sure the currently-set list belongs in the errdetail
> at
> > all,
> > since an operator can read the values back from the configuration.
> > I'd be interested in the committer's view on whether it is worth adding.
>
> It may be enough to list which ones are set, without listing their values.
> Those can be obtained easily from pg_settings, which can be mentioned in
> errhint -- useful also to figure out exactly _where_ they are set (e.g.,
> in an include file, postgresql.auto.conf, and so on.)
>
> --
> Álvaro Herrera Breisgau, Deutschland —
> https://www.EnterpriseDB.com/
>
Attachments:
[application/octet-stream] v5-0001-Don-t-call-ereport-ERROR-from-recovery-target-GUC-as.patch (22.5K, ../../CACSdjfN7uYLS-+wyBbvZ6rmkSNuPm9H8Y+xdd_aSr-kNG_xu3w@mail.gmail.com/3-v5-0001-Don-t-call-ereport-ERROR-from-recovery-target-GUC-as.patch)
download | inline diff:
From 3638a7ba5037c46e0106989a6679906c03a2fc6b Mon Sep 17 00:00:00 2001
From: JoongHyuk Shin <[email protected]>
Date: Sun, 21 Jun 2026 17:45:25 +0900
Subject: [PATCH] Don't call ereport(ERROR) from recovery target GUC assign
hooks
The five recovery target GUC assign hooks (assign_recovery_target,
assign_recovery_target_lsn, assign_recovery_target_name,
assign_recovery_target_time, assign_recovery_target_xid) all called
error_multiple_recovery_targets(), which invoked ereport(ERROR). The
GUC README explicitly states that assign hooks must never fail; raising
an error from an assign hook leaves guc.c's internal state inconsistent
before the abort. A comment in the code itself acknowledged this as
"broken by design."
Fix this by removing the conflict check from all five assign hooks and
detecting multiple recovery targets in a new CheckRecoveryTargetConflicts()
function, called from validateRecoveryParameters() before its early
return for !ArchiveRecoveryRequested. The check therefore runs at
every startup regardless of recovery mode, preserving the existing
behavior of detecting misconfiguration at server start time rather
than only when recovery.signal is added.
In each assign hook's empty-string branch, replace the unconditional
"recoveryTarget = RECOVERY_TARGET_UNSET" assignment with a narrower
"if (recoveryTarget == MY_TYPE)" form. Empty strings for an unrelated
recovery_target_* GUC then leave another GUC's already-set target
intact, while still preserving the documented "last value wins"
reassignment semantics for the same GUC.
When a conflict is detected, the errdetail now lists which
recovery_target_* parameters are actually set, rather than the full
list of candidate names, and an errhint points to pg_settings for their
values and where each is configured.
---
src/backend/access/transam/xlogrecovery.c | 159 +++++++++----
src/test/recovery/t/003_recovery_targets.pl | 251 +++++++++++++++++++-
2 files changed, 361 insertions(+), 49 deletions(-)
diff --git a/src/backend/access/transam/xlogrecovery.c b/src/backend/access/transam/xlogrecovery.c
index 4d61795b483..dc8b46a6c81 100644
--- a/src/backend/access/transam/xlogrecovery.c
+++ b/src/backend/access/transam/xlogrecovery.c
@@ -61,6 +61,7 @@
#include "storage/subsystems.h"
#include "utils/datetime.h"
#include "utils/fmgrprotos.h"
+#include "utils/guc.h"
#include "utils/guc_hooks.h"
#include "utils/pgstat_internal.h"
#include "utils/pg_lsn.h"
@@ -341,6 +342,7 @@ static void ApplyWalRecord(XLogReaderState *xlogreader, XLogRecord *record, Time
static void EnableStandbyMode(void);
static void readRecoverySignalFile(void);
static void validateRecoveryParameters(void);
+static void CheckRecoveryTargetConflicts(void);
static bool read_backup_label(XLogRecPtr *checkPointLoc,
TimeLineID *backupLabelTLI,
bool *backupEndRequired, bool *backupFromStandby);
@@ -1067,6 +1069,8 @@ readRecoverySignalFile(void)
static void
validateRecoveryParameters(void)
{
+ CheckRecoveryTargetConflicts();
+
if (!ArchiveRecoveryRequested)
return;
@@ -1144,6 +1148,94 @@ validateRecoveryParameters(void)
}
}
+/*
+ * CheckRecoveryTargetConflicts
+ *
+ * Validate that at most one of the recovery_target_* GUCs is set to a
+ * non-empty value. This is called from validateRecoveryParameters() at every
+ * server startup, regardless of whether archive recovery is requested, so
+ * misconfiguration is detected at server start time rather than later when
+ * recovery.signal is added.
+ *
+ * The check is a separate function rather than inlined into
+ * validateRecoveryParameters() because it intentionally runs even when no
+ * recovery is requested, while the rest of validateRecoveryParameters() is
+ * recovery-mode-only. Keeping it as a named function makes that separation
+ * explicit.
+ *
+ * The check used to live in the assign hooks of the recovery_target_* GUCs
+ * (calling ereport(ERROR) on conflict), which violated guc.c's contract that
+ * assign hooks must never fail. Moving the check here keeps the assign hooks
+ * contract-compliant.
+ *
+ * If a future patch adds a sixth recovery_target_* GUC, it must be added to the
+ * list of GetConfigOption() checks below; the errdetail builds its list of set
+ * parameters dynamically, so it needs no change.
+ */
+static void
+CheckRecoveryTargetConflicts(void)
+{
+ int ntargets = 0;
+ const char *val;
+ StringInfoData buf;
+
+ initStringInfo(&buf);
+
+ /*
+ * These GUCs are all PGC_STRING, so GetConfigOption() returns "" (not
+ * NULL) when a parameter is unset. Collect the name of each one that is
+ * set into buf, separated by ", ", for use in the errdetail below.
+ */
+ val = GetConfigOption("recovery_target", false, false);
+ if (val[0] != '\0')
+ {
+ ntargets++;
+ if (buf.len > 0)
+ appendStringInfoString(&buf, ", ");
+ appendStringInfoString(&buf, "\"recovery_target\"");
+ }
+ val = GetConfigOption("recovery_target_lsn", false, false);
+ if (val[0] != '\0')
+ {
+ ntargets++;
+ if (buf.len > 0)
+ appendStringInfoString(&buf, ", ");
+ appendStringInfoString(&buf, "\"recovery_target_lsn\"");
+ }
+ val = GetConfigOption("recovery_target_name", false, false);
+ if (val[0] != '\0')
+ {
+ ntargets++;
+ if (buf.len > 0)
+ appendStringInfoString(&buf, ", ");
+ appendStringInfoString(&buf, "\"recovery_target_name\"");
+ }
+ val = GetConfigOption("recovery_target_time", false, false);
+ if (val[0] != '\0')
+ {
+ ntargets++;
+ if (buf.len > 0)
+ appendStringInfoString(&buf, ", ");
+ appendStringInfoString(&buf, "\"recovery_target_time\"");
+ }
+ val = GetConfigOption("recovery_target_xid", false, false);
+ if (val[0] != '\0')
+ {
+ ntargets++;
+ if (buf.len > 0)
+ appendStringInfoString(&buf, ", ");
+ appendStringInfoString(&buf, "\"recovery_target_xid\"");
+ }
+
+ if (ntargets > 1)
+ ereport(FATAL,
+ (errcode(ERRCODE_INVALID_PARAMETER_VALUE),
+ errmsg("multiple recovery targets specified"),
+ errdetail("Recovery targets %s are set, but only one can be set.",
+ buf.data),
+ errhint("See pg_settings for the parameter values and where each is set.")));
+}
+
/*
* read_backup_label: check to see if a backup_label file is present
*
@@ -4769,32 +4861,27 @@ check_primary_slot_name(char **newval, void **extra, GucSource source)
}
/*
- * Recovery target settings: Only one of the several recovery_target* settings
- * may be set. Setting a second one results in an error. The global variable
+ * Recovery target settings: At most one of the several recovery_target*
+ * settings may be set to a non-empty value. The global variable
* recoveryTarget tracks which kind of recovery target was chosen. Other
* variables store the actual target value (for example a string or a xid).
- * The assign functions of the parameters check whether a competing parameter
- * was already set. But we want to allow setting the same parameter multiple
- * times. We also want to allow unsetting a parameter and setting a different
- * one, so we unset recoveryTarget when the parameter is set to an empty
- * string.
+ * An empty string for any of these GUCs is treated as "not set", equivalent
+ * to the GUC's default; an empty value cannot clobber another GUC's
+ * already-set target. Conflicts between multiple non-empty settings are
+ * detected in CheckRecoveryTargetConflicts(), called from
+ * validateRecoveryParameters() at every startup.
*
- * XXX this code is broken by design. Throwing an error from a GUC assign
- * hook breaks fundamental assumptions of guc.c. So long as all the variables
- * for which this can happen are PGC_POSTMASTER, the consequences are limited,
- * since we'd just abort postmaster startup anyway. Nonetheless it's likely
- * that we have odd behaviors such as unexpected GUC ordering dependencies.
+ * Each assign hook clears recoveryTarget only when its own GUC is reassigned
+ * to an empty string after the same GUC was previously assigned a non-empty
+ * value, e.g.
+ * postgres -c recovery_target_xid=700 -c recovery_target_xid=
+ * (postgresql.conf collapses duplicate keys so only the last value reaches
+ * the assign hook; this same-parameter set-then-clear case only arises from
+ * -c). The clear is restricted to the hook's own target type so that an
+ * empty value for one recovery_target_* GUC cannot clobber another GUC's
+ * already-set target.
*/
-pg_noreturn static void
-error_multiple_recovery_targets(void)
-{
- ereport(ERROR,
- (errcode(ERRCODE_INVALID_PARAMETER_VALUE),
- errmsg("multiple recovery targets specified"),
- errdetail("At most one of \"recovery_target\", \"recovery_target_lsn\", \"recovery_target_name\", \"recovery_target_time\", \"recovery_target_xid\" may be set.")));
-}
-
/*
* GUC check_hook for recovery_target
*/
@@ -4815,13 +4902,9 @@ check_recovery_target(char **newval, void **extra, GucSource source)
void
assign_recovery_target(const char *newval, void *extra)
{
- if (recoveryTarget != RECOVERY_TARGET_UNSET &&
- recoveryTarget != RECOVERY_TARGET_IMMEDIATE)
- error_multiple_recovery_targets();
-
if (newval && strcmp(newval, "") != 0)
recoveryTarget = RECOVERY_TARGET_IMMEDIATE;
- else
+ else if (recoveryTarget == RECOVERY_TARGET_IMMEDIATE)
recoveryTarget = RECOVERY_TARGET_UNSET;
}
@@ -4856,16 +4939,12 @@ check_recovery_target_lsn(char **newval, void **extra, GucSource source)
void
assign_recovery_target_lsn(const char *newval, void *extra)
{
- if (recoveryTarget != RECOVERY_TARGET_UNSET &&
- recoveryTarget != RECOVERY_TARGET_LSN)
- error_multiple_recovery_targets();
-
if (newval && strcmp(newval, "") != 0)
{
recoveryTarget = RECOVERY_TARGET_LSN;
recoveryTargetLSN = *((XLogRecPtr *) extra);
}
- else
+ else if (recoveryTarget == RECOVERY_TARGET_LSN)
recoveryTarget = RECOVERY_TARGET_UNSET;
}
@@ -4891,16 +4970,12 @@ check_recovery_target_name(char **newval, void **extra, GucSource source)
void
assign_recovery_target_name(const char *newval, void *extra)
{
- if (recoveryTarget != RECOVERY_TARGET_UNSET &&
- recoveryTarget != RECOVERY_TARGET_NAME)
- error_multiple_recovery_targets();
-
if (newval && strcmp(newval, "") != 0)
{
recoveryTarget = RECOVERY_TARGET_NAME;
recoveryTargetName = newval;
}
- else
+ else if (recoveryTarget == RECOVERY_TARGET_NAME)
recoveryTarget = RECOVERY_TARGET_UNSET;
}
@@ -4971,13 +5046,9 @@ check_recovery_target_time(char **newval, void **extra, GucSource source)
void
assign_recovery_target_time(const char *newval, void *extra)
{
- if (recoveryTarget != RECOVERY_TARGET_UNSET &&
- recoveryTarget != RECOVERY_TARGET_TIME)
- error_multiple_recovery_targets();
-
if (newval && strcmp(newval, "") != 0)
recoveryTarget = RECOVERY_TARGET_TIME;
- else
+ else if (recoveryTarget == RECOVERY_TARGET_TIME)
recoveryTarget = RECOVERY_TARGET_UNSET;
}
@@ -5099,15 +5170,11 @@ check_recovery_target_xid(char **newval, void **extra, GucSource source)
void
assign_recovery_target_xid(const char *newval, void *extra)
{
- if (recoveryTarget != RECOVERY_TARGET_UNSET &&
- recoveryTarget != RECOVERY_TARGET_XID)
- error_multiple_recovery_targets();
-
if (newval && strcmp(newval, "") != 0)
{
recoveryTarget = RECOVERY_TARGET_XID;
recoveryTargetXid = *((TransactionId *) extra);
}
- else
+ else if (recoveryTarget == RECOVERY_TARGET_XID)
recoveryTarget = RECOVERY_TARGET_UNSET;
}
diff --git a/src/test/recovery/t/003_recovery_targets.pl b/src/test/recovery/t/003_recovery_targets.pl
index 047eb13293a..6e8a2c557c9 100644
--- a/src/test/recovery/t/003_recovery_targets.pl
+++ b/src/test/recovery/t/003_recovery_targets.pl
@@ -51,6 +51,49 @@ sub test_recovery_standby
return;
}
+# Start a standby with the given pg_ctl --options string and verify that
+# the standby reaches the given LSN and row count. Used to exercise
+# scenarios that require the postmaster command line to receive multiple
+# "-c name=value" instances of the same GUC, which postgresql.conf cannot
+# express because ProcessConfigFile collapses duplicate keys.
+sub test_recovery_standby_with_options
+{
+ local $Test::Builder::Level = $Test::Builder::Level + 1;
+
+ my $test_name = shift;
+ my $node_name = shift;
+ my $node_primary = shift;
+ my $options = shift;
+ my $num_rows = shift;
+ my $until_lsn = shift;
+
+ my $node_standby = PostgreSQL::Test::Cluster->new($node_name);
+ $node_standby->init_from_backup($node_primary, 'my_backup',
+ has_restoring => 1);
+
+ my $res = run_log(
+ [
+ 'pg_ctl',
+ '--pgdata' => $node_standby->data_dir,
+ '--log' => $node_standby->logfile,
+ '--options' => $options,
+ 'start',
+ ]);
+ ok($res, "server starts for $test_name");
+
+ $node_standby->poll_query_until('postgres',
+ "SELECT '$until_lsn'::pg_lsn <= pg_last_wal_replay_lsn()")
+ or die "Timed out while waiting for standby to catch up";
+
+ my $count = $node_standby->safe_psql('postgres',
+ "SELECT count(*) FROM tab_int");
+ is($count, qq($num_rows), "check standby content for $test_name");
+
+ $node_standby->teardown_node;
+
+ return;
+}
+
# Initialize primary node
my $node_primary = PostgreSQL::Test::Cluster->new('primary');
$node_primary->init(has_archiving => 1, allows_streaming => 1);
@@ -108,6 +151,13 @@ $node_primary->safe_psql('postgres',
# Force archiving of WAL file
$node_primary->safe_psql('postgres', "SELECT pg_switch_wal()");
+# LSN after the final 6000-row insert and WAL switch. Used by the
+# set-then-cleared scenarios below where recovery has no target and must
+# replay all archived WAL; polling on $lsn5 would race against the 5001-6000
+# rows.
+my $lsn6 =
+ $node_primary->safe_psql('postgres', "SELECT pg_current_wal_lsn()");
+
# Test recovery targets
my @recovery_params = ("recovery_target = 'immediate'");
test_recovery_standby('immediate target',
@@ -125,11 +175,24 @@ test_recovery_standby('name', 'standby_4', $node_primary, \@recovery_params,
test_recovery_standby('LSN', 'standby_5', $node_primary, \@recovery_params,
"5000", $lsn5);
+# Regression: empty-string for one recovery_target_* GUC must not clobber
+# another non-empty target. Setting recovery_target_xid + recovery_target_time
+# = '' must recover to the xid, not run as no-target recovery.
+@recovery_params = (
+ "recovery_target_xid = '$recovery_txid'",
+ "recovery_target_time = ''");
+test_recovery_standby('xid with empty time GUC',
+ 'standby_xid_empty_time', $node_primary, \@recovery_params,
+ "2000", $lsn2);
+
# Multiple targets
#
-# Multiple conflicting settings are not allowed, but setting the same
-# parameter multiple times or unsetting a parameter and setting a
-# different one is allowed.
+# Multiple conflicting non-empty settings are not allowed. Setting the same
+# parameter multiple times is allowed (the last value wins, per ProcessConfigFile
+# duplicate handling). An empty string for a recovery_target_* GUC is treated
+# as the GUC's default and is a no-op; it does not clear any other already-set
+# target. Conflict detection runs in CheckRecoveryTargetConflicts() at every
+# server start, regardless of whether recovery is requested.
@recovery_params = (
"recovery_target_name = '$recovery_name'",
@@ -159,6 +222,21 @@ like(
$logfile,
qr/multiple recovery targets specified/,
'multiple conflicting settings');
+# errdetail lists exactly the parameters that are set, in GUC-check order
+# (recovery_target_name is checked before recovery_target_time).
+like(
+ $logfile,
+ qr/Recovery targets "recovery_target_name", "recovery_target_time" are set/,
+ 'errdetail lists the set parameters in order');
+# The parameter values must not be echoed (only the names are listed).
+unlike(
+ $logfile,
+ qr/are set[^\n]*=/,
+ 'errdetail does not echo parameter values');
+like(
+ $logfile,
+ qr/HINT:.*pg_settings/,
+ 'errhint points to pg_settings');
# Check behavior when recovery ends before target is reached
@@ -190,6 +268,173 @@ like(
qr/FATAL: .* recovery ended before configured recovery target was reached/,
'recovery end before target reached is a fatal error');
+# Conflicting recovery targets are rejected at every startup, regardless of
+# whether recovery.signal is present. Use a throwaway cluster initialized
+# from the existing backup so we can leave conflicting GUCs in its
+# postgresql.conf without polluting the primary used by later tests.
+# init_from_backup is called without has_restoring, so no recovery.signal
+# is created and the cluster would normally start as a plain primary; the
+# conflicting recovery_target_* GUCs must be rejected anyway.
+my $node_no_signal = PostgreSQL::Test::Cluster->new('multi_target_no_signal');
+$node_no_signal->init_from_backup($node_primary, 'my_backup');
+$node_no_signal->append_conf(
+ 'postgresql.conf', "recovery_target_name = '$recovery_name'
+recovery_target_time = '$recovery_time'");
+
+my $res_no_signal = run_log(
+ [
+ 'pg_ctl',
+ '--pgdata' => $node_no_signal->data_dir,
+ '--log' => $node_no_signal->logfile,
+ 'start',
+ ]);
+ok(!$res_no_signal,
+ 'server fails to start with conflicting recovery targets and no recovery.signal');
+
+my $logfile_no_signal = slurp_file($node_no_signal->logfile());
+like(
+ $logfile_no_signal,
+ qr/multiple recovery targets specified/,
+ 'expected error message logged without recovery.signal');
+like(
+ $logfile_no_signal,
+ qr/Recovery targets "recovery_target_name", "recovery_target_time" are set/,
+ 'errdetail lists the set parameters in order without recovery.signal');
+unlike(
+ $logfile_no_signal,
+ qr/are set[^\n]*=/,
+ 'errdetail does not echo parameter values without recovery.signal');
+like(
+ $logfile_no_signal,
+ qr/HINT:.*pg_settings/,
+ 'errhint points to pg_settings without recovery.signal');
+
+# Conflict involving the bare recovery_target (no suffix): it is checked first,
+# so its name appears at the head of the list. The list entries are quoted, so
+# the regex anchors on the closing quote to ensure "recovery_target" does not
+# match a prefix of "recovery_target_xid".
+my $node_bare = PostgreSQL::Test::Cluster->new('multi_target_bare');
+$node_bare->init_from_backup($node_primary, 'my_backup');
+$node_bare->append_conf('postgresql.conf',
+ "recovery_target = 'immediate'\nrecovery_target_xid = '$recovery_txid'");
+
+my $res_bare = run_log(
+ [
+ 'pg_ctl',
+ '--pgdata' => $node_bare->data_dir,
+ '--log' => $node_bare->logfile,
+ 'start',
+ ]);
+ok(!$res_bare,
+ 'server fails to start with bare recovery_target conflict');
+
+my $logfile_bare = slurp_file($node_bare->logfile());
+like(
+ $logfile_bare,
+ qr/Recovery targets "recovery_target", "recovery_target_xid" are set/,
+ 'errdetail lists the bare recovery_target first, without prefix mismatch');
+
+# Three conflicting targets: exercises the ", " separator between more than two
+# entries (no trailing separator after the last entry).
+my $node_three = PostgreSQL::Test::Cluster->new('multi_target_three');
+$node_three->init_from_backup($node_primary, 'my_backup');
+$node_three->append_conf('postgresql.conf',
+ "recovery_target_name = '$recovery_name'\n"
+ . "recovery_target_time = '$recovery_time'\n"
+ . "recovery_target_xid = '$recovery_txid'");
+
+my $res_three = run_log(
+ [
+ 'pg_ctl',
+ '--pgdata' => $node_three->data_dir,
+ '--log' => $node_three->logfile,
+ 'start',
+ ]);
+ok(!$res_three,
+ 'server fails to start with three conflicting recovery targets');
+
+my $logfile_three = slurp_file($node_three->logfile());
+like(
+ $logfile_three,
+ qr/Recovery targets "recovery_target_name", "recovery_target_time", "recovery_target_xid" are set/,
+ 'errdetail lists three set parameters with correct separators');
+
+# Same-GUC last-wins (one source of truth for the GUC's value): assigning a
+# recovery_target_* GUC and then assigning the same GUC to an empty string
+# leaves no target set and recovery proceeds to the end of WAL. This is the
+# "set then unset" form of GUC reassignment; the assign hook clears its own
+# type when the new value is empty. postgresql.conf cannot express duplicate
+# keys (ProcessConfigFile collapses them), so the postmaster command line is
+# used via "pg_ctl --options". Each of the five recovery_target_* assign
+# hooks is exercised once.
+#
+# Note: GUC values below are passed unquoted on the command line. Windows
+# cmd.exe does not strip single quotes the way POSIX shells do, so quoted
+# values would reach the postmaster verbatim and be rejected. recovery_time
+# from now() contains a space; we replace it with the ISO 8601 T separator
+# so the value is a single shell token without quoting.
+my $recovery_time_t = $recovery_time;
+$recovery_time_t =~ s/ /T/;
+
+test_recovery_standby_with_options(
+ 'recovery_target_xid set then cleared',
+ 'standby_xid_set_clear', $node_primary,
+ "-c recovery_target_xid=$recovery_txid -c recovery_target_xid=",
+ "6000", $lsn6);
+
+test_recovery_standby_with_options(
+ 'recovery_target_time set then cleared',
+ 'standby_time_set_clear', $node_primary,
+ "-c recovery_target_time=$recovery_time_t -c recovery_target_time=",
+ "6000", $lsn6);
+
+test_recovery_standby_with_options(
+ 'recovery_target_name set then cleared',
+ 'standby_name_set_clear', $node_primary,
+ "-c recovery_target_name=$recovery_name -c recovery_target_name=",
+ "6000", $lsn6);
+
+test_recovery_standby_with_options(
+ 'recovery_target_lsn set then cleared',
+ 'standby_lsn_set_clear', $node_primary,
+ "-c recovery_target_lsn=$recovery_lsn -c recovery_target_lsn=",
+ "6000", $lsn6);
+
+test_recovery_standby_with_options(
+ 'recovery_target set then cleared',
+ 'standby_immediate_set_clear', $node_primary,
+ "-c recovery_target=immediate -c recovery_target=",
+ "6000", $lsn6);
+
+# Same-GUC empty-then-set sanity: assigning an empty string and then a
+# non-empty value to the same GUC must end with the target set. The empty
+# value seen first must not poison a later non-empty assignment.
+my $node_xid_clear_set =
+ PostgreSQL::Test::Cluster->new('standby_xid_clear_set');
+$node_xid_clear_set->init_from_backup($node_primary, 'my_backup',
+ has_restoring => 1);
+
+my $res_xid_clear_set = run_log(
+ [
+ 'pg_ctl',
+ '--pgdata' => $node_xid_clear_set->data_dir,
+ '--log' => $node_xid_clear_set->logfile,
+ '--options' =>
+ "-c recovery_target_xid= -c recovery_target_xid=$recovery_txid",
+ 'start',
+ ]);
+ok($res_xid_clear_set,
+ 'server starts with recovery_target_xid cleared then set');
+
+$node_xid_clear_set->poll_query_until('postgres',
+ "SELECT '$lsn2'::pg_lsn <= pg_last_wal_replay_lsn()")
+ or die "Timed out while waiting for standby to catch up";
+my $count_xid_clear_set = $node_xid_clear_set->safe_psql('postgres',
+ "SELECT count(*) FROM tab_int");
+is($count_xid_clear_set, "2000",
+ 'recovery_target_xid honored when cleared then set');
+$node_xid_clear_set->teardown_node;
+
# Invalid recovery_target_timeline tests
my ($result, $stdout, $stderr) = $node_primary->psql('postgres',
"ALTER SYSTEM SET recovery_target_timeline TO 'bogus'");
--
2.52.0
^ permalink raw reply [nested|flat] 868+ messages in thread
* Re: [PATCH] Don't call ereport(ERROR) from recovery target GUC assign hooks
2026-04-13 08:21 [PATCH] Don't call ereport(ERROR) from recovery target GUC assign hooks JoongHyuk Shin <[email protected]>
2026-04-24 13:08 ` Re: [PATCH] Don't call ereport(ERROR) from recovery target GUC assign hooks Fujii Masao <[email protected]>
2026-04-27 01:52 ` Re: [PATCH] Don't call ereport(ERROR) from recovery target GUC assign hooks Michael Paquier <[email protected]>
2026-04-27 05:36 ` Re: [PATCH] Don't call ereport(ERROR) from recovery target GUC assign hooks Fujii Masao <[email protected]>
2026-04-27 05:59 ` Re: [PATCH] Don't call ereport(ERROR) from recovery target GUC assign hooks Michael Paquier <[email protected]>
2026-04-29 09:29 ` Re: [PATCH] Don't call ereport(ERROR) from recovery target GUC assign hooks JoongHyuk Shin <[email protected]>
2026-05-01 05:53 ` Re: [PATCH] Don't call ereport(ERROR) from recovery target GUC assign hooks Fujii Masao <[email protected]>
2026-05-11 03:17 ` Re: [PATCH] Don't call ereport(ERROR) from recovery target GUC assign hooks JoongHyuk Shin <[email protected]>
2026-05-11 06:01 ` Re: [PATCH] Don't call ereport(ERROR) from recovery target GUC assign hooks JoongHyuk Shin <[email protected]>
2026-05-31 21:11 ` Re: [PATCH] Don't call ereport(ERROR) from recovery target GUC assign hooks Scott Ray <[email protected]>
2026-06-04 05:41 ` Re: [PATCH] Don't call ereport(ERROR) from recovery target GUC assign hooks JoongHyuk Shin <[email protected]>
2026-06-06 20:10 ` Re: [PATCH] Don't call ereport(ERROR) from recovery target GUC assign hooks Scott Ray <[email protected]>
2026-06-07 10:30 ` Re: [PATCH] Don't call ereport(ERROR) from recovery target GUC assign hooks JoongHyuk Shin <[email protected]>
2026-06-07 15:44 ` Re: [PATCH] Don't call ereport(ERROR) from recovery target GUC assign hooks Álvaro Herrera <[email protected]>
2026-06-21 09:27 ` Re: [PATCH] Don't call ereport(ERROR) from recovery target GUC assign hooks JoongHyuk Shin <[email protected]>
@ 2026-06-21 12:31 ` Álvaro Herrera <[email protected]>
2026-06-22 04:37 ` Re: [PATCH] Don't call ereport(ERROR) from recovery target GUC assign hooks JoongHyuk Shin <[email protected]>
0 siblings, 1 reply; 868+ messages in thread
From: Álvaro Herrera @ 2026-06-21 12:31 UTC (permalink / raw)
To: JoongHyuk Shin <[email protected]>; +Cc: Scott Ray <[email protected]>; Fujii Masao <[email protected]>; Michael Paquier <[email protected]>; [email protected]
Hello,
On 2026-Jun-21, JoongHyuk Shin wrote:
> The errdetail now lists which recovery_target_* parameters are actually set,
> instead of the full candidate list.
> This follows Scott's idea to surface the set targets;
> following Álvaro, the values are dropped
> and a new errhint points to pg_settings for them and their sources.
>
> I kept the "which ones are set" list in errdetail rather than errhint,
> it states the current configuration, which reads as detail,
> while the errhint carries the actionable pg_settings pointer.
Please see https://postgr.es/c/3692a622d3fd for more on translatable
message construction. You should end up with a translatable string in
a _() call like
_(", \"%s\"")
and the GUC names in a separate string in each case.
Maybe you can make this a local macro to avoid repetitive coding,
#define considerAndComplainAboutGUC(gucname, buf) \
do { \
val = GetConfigOption(gucname, false, false); \
if (val[0] != '\0') \
{ \
ntargets++; \
if (buf.len == 0) \
appendStringInfoString(&buf, _("\"%s\""), gucname); \
else \
appendStringInfoString(&buf, _(", \"%s\""), gucname); \
} \
} while (0)
considerAndComplainAboutGUC("recovery_target", buf);
considerAndComplainAboutGUC("recovery_target_lsn", buf);
and so on. (Of course, you should choose a less stupid macro name, but
you get my meaning.)
--
Álvaro Herrera PostgreSQL Developer — https://www.EnterpriseDB.com/
"... In accounting terms this makes perfect sense. To rational humans, it
is insane. Welcome to IBM." (Robert X. Cringely)
https://www.cringely.com/2015/06/03/autodesks-john-walker-explained-hp-and-ibm-in-1991/
^ permalink raw reply [nested|flat] 868+ messages in thread
* Re: [PATCH] Don't call ereport(ERROR) from recovery target GUC assign hooks
2026-04-13 08:21 [PATCH] Don't call ereport(ERROR) from recovery target GUC assign hooks JoongHyuk Shin <[email protected]>
2026-04-24 13:08 ` Re: [PATCH] Don't call ereport(ERROR) from recovery target GUC assign hooks Fujii Masao <[email protected]>
2026-04-27 01:52 ` Re: [PATCH] Don't call ereport(ERROR) from recovery target GUC assign hooks Michael Paquier <[email protected]>
2026-04-27 05:36 ` Re: [PATCH] Don't call ereport(ERROR) from recovery target GUC assign hooks Fujii Masao <[email protected]>
2026-04-27 05:59 ` Re: [PATCH] Don't call ereport(ERROR) from recovery target GUC assign hooks Michael Paquier <[email protected]>
2026-04-29 09:29 ` Re: [PATCH] Don't call ereport(ERROR) from recovery target GUC assign hooks JoongHyuk Shin <[email protected]>
2026-05-01 05:53 ` Re: [PATCH] Don't call ereport(ERROR) from recovery target GUC assign hooks Fujii Masao <[email protected]>
2026-05-11 03:17 ` Re: [PATCH] Don't call ereport(ERROR) from recovery target GUC assign hooks JoongHyuk Shin <[email protected]>
2026-05-11 06:01 ` Re: [PATCH] Don't call ereport(ERROR) from recovery target GUC assign hooks JoongHyuk Shin <[email protected]>
2026-05-31 21:11 ` Re: [PATCH] Don't call ereport(ERROR) from recovery target GUC assign hooks Scott Ray <[email protected]>
2026-06-04 05:41 ` Re: [PATCH] Don't call ereport(ERROR) from recovery target GUC assign hooks JoongHyuk Shin <[email protected]>
2026-06-06 20:10 ` Re: [PATCH] Don't call ereport(ERROR) from recovery target GUC assign hooks Scott Ray <[email protected]>
2026-06-07 10:30 ` Re: [PATCH] Don't call ereport(ERROR) from recovery target GUC assign hooks JoongHyuk Shin <[email protected]>
2026-06-07 15:44 ` Re: [PATCH] Don't call ereport(ERROR) from recovery target GUC assign hooks Álvaro Herrera <[email protected]>
2026-06-21 09:27 ` Re: [PATCH] Don't call ereport(ERROR) from recovery target GUC assign hooks JoongHyuk Shin <[email protected]>
2026-06-21 12:31 ` Re: [PATCH] Don't call ereport(ERROR) from recovery target GUC assign hooks Álvaro Herrera <[email protected]>
@ 2026-06-22 04:37 ` JoongHyuk Shin <[email protected]>
2026-06-25 08:00 ` Re: [PATCH] Don't call ereport(ERROR) from recovery target GUC assign hooks Michael Paquier <[email protected]>
2026-06-26 11:12 ` Re: [PATCH] Don't call ereport(ERROR) from recovery target GUC assign hooks Henson Choi <[email protected]>
0 siblings, 2 replies; 868+ messages in thread
From: JoongHyuk Shin @ 2026-06-22 04:37 UTC (permalink / raw)
To: Álvaro Herrera <[email protected]>; +Cc: Scott Ray <[email protected]>; Fujii Masao <[email protected]>; Michael Paquier <[email protected]>; [email protected]
Thanks for the review.
v6 attached.
The set-parameter list in the errdetail now wraps the separator and quotes
in _()
so the punctuation is translatable, following 3692a622d3fd.
The five GetConfigOption() checks are now a local macro, as suggested.
--
JH Shin
On Sun, Jun 21, 2026 at 9:32 PM Álvaro Herrera <[email protected]> wrote:
> Hello,
>
> On 2026-Jun-21, JoongHyuk Shin wrote:
>
> > The errdetail now lists which recovery_target_* parameters are actually
> set,
> > instead of the full candidate list.
> > This follows Scott's idea to surface the set targets;
> > following Álvaro, the values are dropped
> > and a new errhint points to pg_settings for them and their sources.
> >
> > I kept the "which ones are set" list in errdetail rather than errhint,
> > it states the current configuration, which reads as detail,
> > while the errhint carries the actionable pg_settings pointer.
>
> Please see https://postgr.es/c/3692a622d3fd for more on translatable
> message construction. You should end up with a translatable string in
> a _() call like
> _(", \"%s\"")
> and the GUC names in a separate string in each case.
>
> Maybe you can make this a local macro to avoid repetitive coding,
>
> #define considerAndComplainAboutGUC(gucname, buf) \
> do { \
> val = GetConfigOption(gucname, false, false); \
> if (val[0] != '\0') \
> { \
> ntargets++; \
> if (buf.len == 0) \
> appendStringInfoString(&buf, _("\"%s\""), gucname); \
> else \
> appendStringInfoString(&buf, _(", \"%s\""), gucname); \
> } \
> } while (0)
>
> considerAndComplainAboutGUC("recovery_target", buf);
> considerAndComplainAboutGUC("recovery_target_lsn", buf);
> and so on. (Of course, you should choose a less stupid macro name, but
> you get my meaning.)
>
> --
> Álvaro Herrera PostgreSQL Developer —
> https://www.EnterpriseDB.com/
> "... In accounting terms this makes perfect sense. To rational humans, it
> is insane. Welcome to IBM." (Robert X. Cringely)
>
> https://www.cringely.com/2015/06/03/autodesks-john-walker-explained-hp-and-ibm-in-1991/
>
Attachments:
[application/octet-stream] v6-0001-Don-t-call-ereport-ERROR-from-recovery-target-GUC.patch (22.0K, ../../CACSdjfMt5obQc0QCX8E+v-pxROWFQxmeW6Ki9X16F60D3tSJgg@mail.gmail.com/3-v6-0001-Don-t-call-ereport-ERROR-from-recovery-target-GUC.patch)
download | inline diff:
From 7f05600fb76699a09daa671ddf4d5c85d1c944d9 Mon Sep 17 00:00:00 2001
From: JoongHyuk Shin <[email protected]>
Date: Sun, 21 Jun 2026 17:45:25 +0900
Subject: [PATCH v6] Don't call ereport(ERROR) from recovery target GUC assign
hooks
The five recovery target GUC assign hooks (assign_recovery_target,
assign_recovery_target_lsn, assign_recovery_target_name,
assign_recovery_target_time, assign_recovery_target_xid) all called
error_multiple_recovery_targets(), which invoked ereport(ERROR). The
GUC README explicitly states that assign hooks must never fail; raising
an error from an assign hook leaves guc.c's internal state inconsistent
before the abort. A comment in the code itself acknowledged this as
"broken by design."
Fix this by removing the conflict check from all five assign hooks and
detecting multiple recovery targets in a new CheckRecoveryTargetConflicts()
function, called from validateRecoveryParameters() before its early
return for !ArchiveRecoveryRequested. The check therefore runs at
every startup regardless of recovery mode, preserving the existing
behavior of detecting misconfiguration at server start time rather
than only when recovery.signal is added.
In each assign hook's empty-string branch, replace the unconditional
"recoveryTarget = RECOVERY_TARGET_UNSET" assignment with a narrower
"if (recoveryTarget == MY_TYPE)" form. Empty strings for an unrelated
recovery_target_* GUC then leave another GUC's already-set target
intact, while still preserving the documented "last value wins"
reassignment semantics for the same GUC.
When a conflict is detected, the errdetail now lists which
recovery_target_* parameters are actually set, rather than the full
list of candidate names, and an errhint points to pg_settings for their
values and where each is configured.
---
src/backend/access/transam/xlogrecovery.c | 140 +++++++----
src/test/recovery/t/003_recovery_targets.pl | 251 +++++++++++++++++++-
2 files changed, 342 insertions(+), 49 deletions(-)
diff --git a/src/backend/access/transam/xlogrecovery.c b/src/backend/access/transam/xlogrecovery.c
index 4d61795b483..32d209b3957 100644
--- a/src/backend/access/transam/xlogrecovery.c
+++ b/src/backend/access/transam/xlogrecovery.c
@@ -61,6 +61,7 @@
#include "storage/subsystems.h"
#include "utils/datetime.h"
#include "utils/fmgrprotos.h"
+#include "utils/guc.h"
#include "utils/guc_hooks.h"
#include "utils/pgstat_internal.h"
#include "utils/pg_lsn.h"
@@ -341,6 +342,7 @@ static void ApplyWalRecord(XLogReaderState *xlogreader, XLogRecord *record, Time
static void EnableStandbyMode(void);
static void readRecoverySignalFile(void);
static void validateRecoveryParameters(void);
+static void CheckRecoveryTargetConflicts(void);
static bool read_backup_label(XLogRecPtr *checkPointLoc,
TimeLineID *backupLabelTLI,
bool *backupEndRequired, bool *backupFromStandby);
@@ -1067,6 +1069,8 @@ readRecoverySignalFile(void)
static void
validateRecoveryParameters(void)
{
+ CheckRecoveryTargetConflicts();
+
if (!ArchiveRecoveryRequested)
return;
@@ -1144,6 +1148,75 @@ validateRecoveryParameters(void)
}
}
+/*
+ * CheckRecoveryTargetConflicts
+ *
+ * Validate that at most one of the recovery_target_* GUCs is set to a
+ * non-empty value. This is called from validateRecoveryParameters() at every
+ * server startup, regardless of whether archive recovery is requested, so
+ * misconfiguration is detected at server start time rather than later when
+ * recovery.signal is added.
+ *
+ * The check is a separate function rather than inlined into
+ * validateRecoveryParameters() because it intentionally runs even when no
+ * recovery is requested, while the rest of validateRecoveryParameters() is
+ * recovery-mode-only. Keeping it as a named function makes that separation
+ * explicit.
+ *
+ * The check used to live in the assign hooks of the recovery_target_* GUCs
+ * (calling ereport(ERROR) on conflict), which violated guc.c's contract that
+ * assign hooks must never fail. Moving the check here keeps the assign hooks
+ * contract-compliant.
+ *
+ * If a future patch adds a sixth recovery_target_* GUC, it must be added to the
+ * list of ADD_TARGET_IF_SET() calls below; the errdetail builds its list of set
+ * parameters dynamically, so it needs no change.
+ */
+static void
+CheckRecoveryTargetConflicts(void)
+{
+ int ntargets = 0;
+ const char *val;
+ StringInfoData buf;
+
+ initStringInfo(&buf);
+
+ /*
+ * These GUCs are all PGC_STRING, so GetConfigOption() returns "" (not
+ * NULL) when a parameter is unset. Collect the name of each one that is
+ * set into buf for use in the errdetail below. The separator and the
+ * quotes are wrapped in _() so translators can adapt the list punctuation
+ * to their locale.
+ */
+#define ADD_TARGET_IF_SET(gucname) \
+ do { \
+ val = GetConfigOption(gucname, false, false); \
+ if (val[0] != '\0') \
+ { \
+ ntargets++; \
+ if (buf.len == 0) \
+ appendStringInfo(&buf, _("\"%s\""), gucname); \
+ else \
+ appendStringInfo(&buf, _(", \"%s\""), gucname); \
+ } \
+ } while (0)
+
+ ADD_TARGET_IF_SET("recovery_target");
+ ADD_TARGET_IF_SET("recovery_target_lsn");
+ ADD_TARGET_IF_SET("recovery_target_name");
+ ADD_TARGET_IF_SET("recovery_target_time");
+ ADD_TARGET_IF_SET("recovery_target_xid");
+#undef ADD_TARGET_IF_SET
+
+ if (ntargets > 1)
+ ereport(FATAL,
+ (errcode(ERRCODE_INVALID_PARAMETER_VALUE),
+ errmsg("multiple recovery targets specified"),
+ errdetail("Recovery targets %s are set, but only one can be set.",
+ buf.data),
+ errhint("See pg_settings for the parameter values and where each is set.")));
+}
+
/*
* read_backup_label: check to see if a backup_label file is present
*
@@ -4769,32 +4842,27 @@ check_primary_slot_name(char **newval, void **extra, GucSource source)
}
/*
- * Recovery target settings: Only one of the several recovery_target* settings
- * may be set. Setting a second one results in an error. The global variable
+ * Recovery target settings: At most one of the several recovery_target*
+ * settings may be set to a non-empty value. The global variable
* recoveryTarget tracks which kind of recovery target was chosen. Other
* variables store the actual target value (for example a string or a xid).
- * The assign functions of the parameters check whether a competing parameter
- * was already set. But we want to allow setting the same parameter multiple
- * times. We also want to allow unsetting a parameter and setting a different
- * one, so we unset recoveryTarget when the parameter is set to an empty
- * string.
+ * An empty string for any of these GUCs is treated as "not set", equivalent
+ * to the GUC's default; an empty value cannot clobber another GUC's
+ * already-set target. Conflicts between multiple non-empty settings are
+ * detected in CheckRecoveryTargetConflicts(), called from
+ * validateRecoveryParameters() at every startup.
*
- * XXX this code is broken by design. Throwing an error from a GUC assign
- * hook breaks fundamental assumptions of guc.c. So long as all the variables
- * for which this can happen are PGC_POSTMASTER, the consequences are limited,
- * since we'd just abort postmaster startup anyway. Nonetheless it's likely
- * that we have odd behaviors such as unexpected GUC ordering dependencies.
+ * Each assign hook clears recoveryTarget only when its own GUC is reassigned
+ * to an empty string after the same GUC was previously assigned a non-empty
+ * value, e.g.
+ * postgres -c recovery_target_xid=700 -c recovery_target_xid=
+ * (postgresql.conf collapses duplicate keys so only the last value reaches
+ * the assign hook; this same-parameter set-then-clear case only arises from
+ * -c). The clear is restricted to the hook's own target type so that an
+ * empty value for one recovery_target_* GUC cannot clobber another GUC's
+ * already-set target.
*/
-pg_noreturn static void
-error_multiple_recovery_targets(void)
-{
- ereport(ERROR,
- (errcode(ERRCODE_INVALID_PARAMETER_VALUE),
- errmsg("multiple recovery targets specified"),
- errdetail("At most one of \"recovery_target\", \"recovery_target_lsn\", \"recovery_target_name\", \"recovery_target_time\", \"recovery_target_xid\" may be set.")));
-}
-
/*
* GUC check_hook for recovery_target
*/
@@ -4815,13 +4883,9 @@ check_recovery_target(char **newval, void **extra, GucSource source)
void
assign_recovery_target(const char *newval, void *extra)
{
- if (recoveryTarget != RECOVERY_TARGET_UNSET &&
- recoveryTarget != RECOVERY_TARGET_IMMEDIATE)
- error_multiple_recovery_targets();
-
if (newval && strcmp(newval, "") != 0)
recoveryTarget = RECOVERY_TARGET_IMMEDIATE;
- else
+ else if (recoveryTarget == RECOVERY_TARGET_IMMEDIATE)
recoveryTarget = RECOVERY_TARGET_UNSET;
}
@@ -4856,16 +4920,12 @@ check_recovery_target_lsn(char **newval, void **extra, GucSource source)
void
assign_recovery_target_lsn(const char *newval, void *extra)
{
- if (recoveryTarget != RECOVERY_TARGET_UNSET &&
- recoveryTarget != RECOVERY_TARGET_LSN)
- error_multiple_recovery_targets();
-
if (newval && strcmp(newval, "") != 0)
{
recoveryTarget = RECOVERY_TARGET_LSN;
recoveryTargetLSN = *((XLogRecPtr *) extra);
}
- else
+ else if (recoveryTarget == RECOVERY_TARGET_LSN)
recoveryTarget = RECOVERY_TARGET_UNSET;
}
@@ -4891,16 +4951,12 @@ check_recovery_target_name(char **newval, void **extra, GucSource source)
void
assign_recovery_target_name(const char *newval, void *extra)
{
- if (recoveryTarget != RECOVERY_TARGET_UNSET &&
- recoveryTarget != RECOVERY_TARGET_NAME)
- error_multiple_recovery_targets();
-
if (newval && strcmp(newval, "") != 0)
{
recoveryTarget = RECOVERY_TARGET_NAME;
recoveryTargetName = newval;
}
- else
+ else if (recoveryTarget == RECOVERY_TARGET_NAME)
recoveryTarget = RECOVERY_TARGET_UNSET;
}
@@ -4971,13 +5027,9 @@ check_recovery_target_time(char **newval, void **extra, GucSource source)
void
assign_recovery_target_time(const char *newval, void *extra)
{
- if (recoveryTarget != RECOVERY_TARGET_UNSET &&
- recoveryTarget != RECOVERY_TARGET_TIME)
- error_multiple_recovery_targets();
-
if (newval && strcmp(newval, "") != 0)
recoveryTarget = RECOVERY_TARGET_TIME;
- else
+ else if (recoveryTarget == RECOVERY_TARGET_TIME)
recoveryTarget = RECOVERY_TARGET_UNSET;
}
@@ -5099,15 +5151,11 @@ check_recovery_target_xid(char **newval, void **extra, GucSource source)
void
assign_recovery_target_xid(const char *newval, void *extra)
{
- if (recoveryTarget != RECOVERY_TARGET_UNSET &&
- recoveryTarget != RECOVERY_TARGET_XID)
- error_multiple_recovery_targets();
-
if (newval && strcmp(newval, "") != 0)
{
recoveryTarget = RECOVERY_TARGET_XID;
recoveryTargetXid = *((TransactionId *) extra);
}
- else
+ else if (recoveryTarget == RECOVERY_TARGET_XID)
recoveryTarget = RECOVERY_TARGET_UNSET;
}
diff --git a/src/test/recovery/t/003_recovery_targets.pl b/src/test/recovery/t/003_recovery_targets.pl
index 047eb13293a..6e8a2c557c9 100644
--- a/src/test/recovery/t/003_recovery_targets.pl
+++ b/src/test/recovery/t/003_recovery_targets.pl
@@ -51,6 +51,49 @@ sub test_recovery_standby
return;
}
+# Start a standby with the given pg_ctl --options string and verify that
+# the standby reaches the given LSN and row count. Used to exercise
+# scenarios that require the postmaster command line to receive multiple
+# "-c name=value" instances of the same GUC, which postgresql.conf cannot
+# express because ProcessConfigFile collapses duplicate keys.
+sub test_recovery_standby_with_options
+{
+ local $Test::Builder::Level = $Test::Builder::Level + 1;
+
+ my $test_name = shift;
+ my $node_name = shift;
+ my $node_primary = shift;
+ my $options = shift;
+ my $num_rows = shift;
+ my $until_lsn = shift;
+
+ my $node_standby = PostgreSQL::Test::Cluster->new($node_name);
+ $node_standby->init_from_backup($node_primary, 'my_backup',
+ has_restoring => 1);
+
+ my $res = run_log(
+ [
+ 'pg_ctl',
+ '--pgdata' => $node_standby->data_dir,
+ '--log' => $node_standby->logfile,
+ '--options' => $options,
+ 'start',
+ ]);
+ ok($res, "server starts for $test_name");
+
+ $node_standby->poll_query_until('postgres',
+ "SELECT '$until_lsn'::pg_lsn <= pg_last_wal_replay_lsn()")
+ or die "Timed out while waiting for standby to catch up";
+
+ my $count = $node_standby->safe_psql('postgres',
+ "SELECT count(*) FROM tab_int");
+ is($count, qq($num_rows), "check standby content for $test_name");
+
+ $node_standby->teardown_node;
+
+ return;
+}
+
# Initialize primary node
my $node_primary = PostgreSQL::Test::Cluster->new('primary');
$node_primary->init(has_archiving => 1, allows_streaming => 1);
@@ -108,6 +151,13 @@ $node_primary->safe_psql('postgres',
# Force archiving of WAL file
$node_primary->safe_psql('postgres', "SELECT pg_switch_wal()");
+# LSN after the final 6000-row insert and WAL switch. Used by the
+# set-then-cleared scenarios below where recovery has no target and must
+# replay all archived WAL; polling on $lsn5 would race against the 5001-6000
+# rows.
+my $lsn6 =
+ $node_primary->safe_psql('postgres', "SELECT pg_current_wal_lsn()");
+
# Test recovery targets
my @recovery_params = ("recovery_target = 'immediate'");
test_recovery_standby('immediate target',
@@ -125,11 +175,24 @@ test_recovery_standby('name', 'standby_4', $node_primary, \@recovery_params,
test_recovery_standby('LSN', 'standby_5', $node_primary, \@recovery_params,
"5000", $lsn5);
+# Regression: empty-string for one recovery_target_* GUC must not clobber
+# another non-empty target. Setting recovery_target_xid + recovery_target_time
+# = '' must recover to the xid, not run as no-target recovery.
+@recovery_params = (
+ "recovery_target_xid = '$recovery_txid'",
+ "recovery_target_time = ''");
+test_recovery_standby('xid with empty time GUC',
+ 'standby_xid_empty_time', $node_primary, \@recovery_params,
+ "2000", $lsn2);
+
# Multiple targets
#
-# Multiple conflicting settings are not allowed, but setting the same
-# parameter multiple times or unsetting a parameter and setting a
-# different one is allowed.
+# Multiple conflicting non-empty settings are not allowed. Setting the same
+# parameter multiple times is allowed (the last value wins, per ProcessConfigFile
+# duplicate handling). An empty string for a recovery_target_* GUC is treated
+# as the GUC's default and is a no-op; it does not clear any other already-set
+# target. Conflict detection runs in CheckRecoveryTargetConflicts() at every
+# server start, regardless of whether recovery is requested.
@recovery_params = (
"recovery_target_name = '$recovery_name'",
@@ -159,6 +222,21 @@ like(
$logfile,
qr/multiple recovery targets specified/,
'multiple conflicting settings');
+# errdetail lists exactly the parameters that are set, in GUC-check order
+# (recovery_target_name is checked before recovery_target_time).
+like(
+ $logfile,
+ qr/Recovery targets "recovery_target_name", "recovery_target_time" are set/,
+ 'errdetail lists the set parameters in order');
+# The parameter values must not be echoed (only the names are listed).
+unlike(
+ $logfile,
+ qr/are set[^\n]*=/,
+ 'errdetail does not echo parameter values');
+like(
+ $logfile,
+ qr/HINT:.*pg_settings/,
+ 'errhint points to pg_settings');
# Check behavior when recovery ends before target is reached
@@ -190,6 +268,173 @@ like(
qr/FATAL: .* recovery ended before configured recovery target was reached/,
'recovery end before target reached is a fatal error');
+# Conflicting recovery targets are rejected at every startup, regardless of
+# whether recovery.signal is present. Use a throwaway cluster initialized
+# from the existing backup so we can leave conflicting GUCs in its
+# postgresql.conf without polluting the primary used by later tests.
+# init_from_backup is called without has_restoring, so no recovery.signal
+# is created and the cluster would normally start as a plain primary; the
+# conflicting recovery_target_* GUCs must be rejected anyway.
+my $node_no_signal = PostgreSQL::Test::Cluster->new('multi_target_no_signal');
+$node_no_signal->init_from_backup($node_primary, 'my_backup');
+$node_no_signal->append_conf(
+ 'postgresql.conf', "recovery_target_name = '$recovery_name'
+recovery_target_time = '$recovery_time'");
+
+my $res_no_signal = run_log(
+ [
+ 'pg_ctl',
+ '--pgdata' => $node_no_signal->data_dir,
+ '--log' => $node_no_signal->logfile,
+ 'start',
+ ]);
+ok(!$res_no_signal,
+ 'server fails to start with conflicting recovery targets and no recovery.signal');
+
+my $logfile_no_signal = slurp_file($node_no_signal->logfile());
+like(
+ $logfile_no_signal,
+ qr/multiple recovery targets specified/,
+ 'expected error message logged without recovery.signal');
+like(
+ $logfile_no_signal,
+ qr/Recovery targets "recovery_target_name", "recovery_target_time" are set/,
+ 'errdetail lists the set parameters in order without recovery.signal');
+unlike(
+ $logfile_no_signal,
+ qr/are set[^\n]*=/,
+ 'errdetail does not echo parameter values without recovery.signal');
+like(
+ $logfile_no_signal,
+ qr/HINT:.*pg_settings/,
+ 'errhint points to pg_settings without recovery.signal');
+
+# Conflict involving the bare recovery_target (no suffix): it is checked first,
+# so its name appears at the head of the list. The list entries are quoted, so
+# the regex anchors on the closing quote to ensure "recovery_target" does not
+# match a prefix of "recovery_target_xid".
+my $node_bare = PostgreSQL::Test::Cluster->new('multi_target_bare');
+$node_bare->init_from_backup($node_primary, 'my_backup');
+$node_bare->append_conf('postgresql.conf',
+ "recovery_target = 'immediate'\nrecovery_target_xid = '$recovery_txid'");
+
+my $res_bare = run_log(
+ [
+ 'pg_ctl',
+ '--pgdata' => $node_bare->data_dir,
+ '--log' => $node_bare->logfile,
+ 'start',
+ ]);
+ok(!$res_bare,
+ 'server fails to start with bare recovery_target conflict');
+
+my $logfile_bare = slurp_file($node_bare->logfile());
+like(
+ $logfile_bare,
+ qr/Recovery targets "recovery_target", "recovery_target_xid" are set/,
+ 'errdetail lists the bare recovery_target first, without prefix mismatch');
+
+# Three conflicting targets: exercises the ", " separator between more than two
+# entries (no trailing separator after the last entry).
+my $node_three = PostgreSQL::Test::Cluster->new('multi_target_three');
+$node_three->init_from_backup($node_primary, 'my_backup');
+$node_three->append_conf('postgresql.conf',
+ "recovery_target_name = '$recovery_name'\n"
+ . "recovery_target_time = '$recovery_time'\n"
+ . "recovery_target_xid = '$recovery_txid'");
+
+my $res_three = run_log(
+ [
+ 'pg_ctl',
+ '--pgdata' => $node_three->data_dir,
+ '--log' => $node_three->logfile,
+ 'start',
+ ]);
+ok(!$res_three,
+ 'server fails to start with three conflicting recovery targets');
+
+my $logfile_three = slurp_file($node_three->logfile());
+like(
+ $logfile_three,
+ qr/Recovery targets "recovery_target_name", "recovery_target_time", "recovery_target_xid" are set/,
+ 'errdetail lists three set parameters with correct separators');
+
+# Same-GUC last-wins (one source of truth for the GUC's value): assigning a
+# recovery_target_* GUC and then assigning the same GUC to an empty string
+# leaves no target set and recovery proceeds to the end of WAL. This is the
+# "set then unset" form of GUC reassignment; the assign hook clears its own
+# type when the new value is empty. postgresql.conf cannot express duplicate
+# keys (ProcessConfigFile collapses them), so the postmaster command line is
+# used via "pg_ctl --options". Each of the five recovery_target_* assign
+# hooks is exercised once.
+#
+# Note: GUC values below are passed unquoted on the command line. Windows
+# cmd.exe does not strip single quotes the way POSIX shells do, so quoted
+# values would reach the postmaster verbatim and be rejected. recovery_time
+# from now() contains a space; we replace it with the ISO 8601 T separator
+# so the value is a single shell token without quoting.
+my $recovery_time_t = $recovery_time;
+$recovery_time_t =~ s/ /T/;
+
+test_recovery_standby_with_options(
+ 'recovery_target_xid set then cleared',
+ 'standby_xid_set_clear', $node_primary,
+ "-c recovery_target_xid=$recovery_txid -c recovery_target_xid=",
+ "6000", $lsn6);
+
+test_recovery_standby_with_options(
+ 'recovery_target_time set then cleared',
+ 'standby_time_set_clear', $node_primary,
+ "-c recovery_target_time=$recovery_time_t -c recovery_target_time=",
+ "6000", $lsn6);
+
+test_recovery_standby_with_options(
+ 'recovery_target_name set then cleared',
+ 'standby_name_set_clear', $node_primary,
+ "-c recovery_target_name=$recovery_name -c recovery_target_name=",
+ "6000", $lsn6);
+
+test_recovery_standby_with_options(
+ 'recovery_target_lsn set then cleared',
+ 'standby_lsn_set_clear', $node_primary,
+ "-c recovery_target_lsn=$recovery_lsn -c recovery_target_lsn=",
+ "6000", $lsn6);
+
+test_recovery_standby_with_options(
+ 'recovery_target set then cleared',
+ 'standby_immediate_set_clear', $node_primary,
+ "-c recovery_target=immediate -c recovery_target=",
+ "6000", $lsn6);
+
+# Same-GUC empty-then-set sanity: assigning an empty string and then a
+# non-empty value to the same GUC must end with the target set. The empty
+# value seen first must not poison a later non-empty assignment.
+my $node_xid_clear_set =
+ PostgreSQL::Test::Cluster->new('standby_xid_clear_set');
+$node_xid_clear_set->init_from_backup($node_primary, 'my_backup',
+ has_restoring => 1);
+
+my $res_xid_clear_set = run_log(
+ [
+ 'pg_ctl',
+ '--pgdata' => $node_xid_clear_set->data_dir,
+ '--log' => $node_xid_clear_set->logfile,
+ '--options' =>
+ "-c recovery_target_xid= -c recovery_target_xid=$recovery_txid",
+ 'start',
+ ]);
+ok($res_xid_clear_set,
+ 'server starts with recovery_target_xid cleared then set');
+
+$node_xid_clear_set->poll_query_until('postgres',
+ "SELECT '$lsn2'::pg_lsn <= pg_last_wal_replay_lsn()")
+ or die "Timed out while waiting for standby to catch up";
+my $count_xid_clear_set = $node_xid_clear_set->safe_psql('postgres',
+ "SELECT count(*) FROM tab_int");
+is($count_xid_clear_set, "2000",
+ 'recovery_target_xid honored when cleared then set');
+$node_xid_clear_set->teardown_node;
+
# Invalid recovery_target_timeline tests
my ($result, $stdout, $stderr) = $node_primary->psql('postgres',
"ALTER SYSTEM SET recovery_target_timeline TO 'bogus'");
--
2.52.0
^ permalink raw reply [nested|flat] 868+ messages in thread
* Re: [PATCH] Don't call ereport(ERROR) from recovery target GUC assign hooks
2026-04-13 08:21 [PATCH] Don't call ereport(ERROR) from recovery target GUC assign hooks JoongHyuk Shin <[email protected]>
2026-04-24 13:08 ` Re: [PATCH] Don't call ereport(ERROR) from recovery target GUC assign hooks Fujii Masao <[email protected]>
2026-04-27 01:52 ` Re: [PATCH] Don't call ereport(ERROR) from recovery target GUC assign hooks Michael Paquier <[email protected]>
2026-04-27 05:36 ` Re: [PATCH] Don't call ereport(ERROR) from recovery target GUC assign hooks Fujii Masao <[email protected]>
2026-04-27 05:59 ` Re: [PATCH] Don't call ereport(ERROR) from recovery target GUC assign hooks Michael Paquier <[email protected]>
2026-04-29 09:29 ` Re: [PATCH] Don't call ereport(ERROR) from recovery target GUC assign hooks JoongHyuk Shin <[email protected]>
2026-05-01 05:53 ` Re: [PATCH] Don't call ereport(ERROR) from recovery target GUC assign hooks Fujii Masao <[email protected]>
2026-05-11 03:17 ` Re: [PATCH] Don't call ereport(ERROR) from recovery target GUC assign hooks JoongHyuk Shin <[email protected]>
2026-05-11 06:01 ` Re: [PATCH] Don't call ereport(ERROR) from recovery target GUC assign hooks JoongHyuk Shin <[email protected]>
2026-05-31 21:11 ` Re: [PATCH] Don't call ereport(ERROR) from recovery target GUC assign hooks Scott Ray <[email protected]>
2026-06-04 05:41 ` Re: [PATCH] Don't call ereport(ERROR) from recovery target GUC assign hooks JoongHyuk Shin <[email protected]>
2026-06-06 20:10 ` Re: [PATCH] Don't call ereport(ERROR) from recovery target GUC assign hooks Scott Ray <[email protected]>
2026-06-07 10:30 ` Re: [PATCH] Don't call ereport(ERROR) from recovery target GUC assign hooks JoongHyuk Shin <[email protected]>
2026-06-07 15:44 ` Re: [PATCH] Don't call ereport(ERROR) from recovery target GUC assign hooks Álvaro Herrera <[email protected]>
2026-06-21 09:27 ` Re: [PATCH] Don't call ereport(ERROR) from recovery target GUC assign hooks JoongHyuk Shin <[email protected]>
2026-06-21 12:31 ` Re: [PATCH] Don't call ereport(ERROR) from recovery target GUC assign hooks Álvaro Herrera <[email protected]>
2026-06-22 04:37 ` Re: [PATCH] Don't call ereport(ERROR) from recovery target GUC assign hooks JoongHyuk Shin <[email protected]>
@ 2026-06-25 08:00 ` Michael Paquier <[email protected]>
2026-06-26 07:15 ` Re: [PATCH] Don't call ereport(ERROR) from recovery target GUC assign hooks Kyotaro Horiguchi <[email protected]>
1 sibling, 1 reply; 868+ messages in thread
From: Michael Paquier @ 2026-06-25 08:00 UTC (permalink / raw)
To: JoongHyuk Shin <[email protected]>; +Cc: Álvaro Herrera <[email protected]>; Scott Ray <[email protected]>; Fujii Masao <[email protected]>; [email protected]
On Mon, Jun 22, 2026 at 01:37:27PM +0900, JoongHyuk Shin wrote:
> Thanks for the review.
Please avoid top-posting when replying on the lists. This breaks the
logical flow of the thread.
> The five GetConfigOption() checks are now a local macro, as suggested.
+ (errcode(ERRCODE_INVALID_PARAMETER_VALUE),
+ errmsg("multiple recovery targets specified"),
+ errdetail("Recovery targets %s are set, but only one can be set.",
+ buf.data),
Is this errdetail() the optimal choice, though? It feels a bit odd to
have that mid-sentence. Perhaps we should use a colon to separate the
list of parameters, say simply:
"Parameters set are: %s."
The trick of Alvaro to make the list of parameters translatable is
interesting. Didn't know that.
The tests feel bloated to me, bumping the time it takes to run 003 by
30%~40% or so. The "Three conflicting targets" has for example little
value. I think that we should also get rid of most of the tests where
we do the patterns for "-c recovery_target_foo=bar -c
recovery_target_foo=", repeated for each target type. One should be
enough. Keeping only one failure case should be enough for two
recovery targets (or all of them set, why not).
With the first tests in place, I am not convinced that the
anti-pattern "-c recovery_target_foo= -c recovery_target_foo=bar" is
needed. Let's just remove it.
This code is clearly AI-generated. Comments are equally bloated with
descriptions that can be understood just by reading the code. Let's
simplify all that.
In CheckRecoveryTargetConflicts(), initStringInfo() does an
allocation. Perhaps we should free it after making sure we don't
FATAL. Just a good practice.
--
Michael
Attachments:
[application/pgp-signature] signature.asc (833B, ../../[email protected]/2-signature.asc)
download
^ permalink raw reply [nested|flat] 868+ messages in thread
* Re: [PATCH] Don't call ereport(ERROR) from recovery target GUC assign hooks
2026-04-13 08:21 [PATCH] Don't call ereport(ERROR) from recovery target GUC assign hooks JoongHyuk Shin <[email protected]>
2026-04-24 13:08 ` Re: [PATCH] Don't call ereport(ERROR) from recovery target GUC assign hooks Fujii Masao <[email protected]>
2026-04-27 01:52 ` Re: [PATCH] Don't call ereport(ERROR) from recovery target GUC assign hooks Michael Paquier <[email protected]>
2026-04-27 05:36 ` Re: [PATCH] Don't call ereport(ERROR) from recovery target GUC assign hooks Fujii Masao <[email protected]>
2026-04-27 05:59 ` Re: [PATCH] Don't call ereport(ERROR) from recovery target GUC assign hooks Michael Paquier <[email protected]>
2026-04-29 09:29 ` Re: [PATCH] Don't call ereport(ERROR) from recovery target GUC assign hooks JoongHyuk Shin <[email protected]>
2026-05-01 05:53 ` Re: [PATCH] Don't call ereport(ERROR) from recovery target GUC assign hooks Fujii Masao <[email protected]>
2026-05-11 03:17 ` Re: [PATCH] Don't call ereport(ERROR) from recovery target GUC assign hooks JoongHyuk Shin <[email protected]>
2026-05-11 06:01 ` Re: [PATCH] Don't call ereport(ERROR) from recovery target GUC assign hooks JoongHyuk Shin <[email protected]>
2026-05-31 21:11 ` Re: [PATCH] Don't call ereport(ERROR) from recovery target GUC assign hooks Scott Ray <[email protected]>
2026-06-04 05:41 ` Re: [PATCH] Don't call ereport(ERROR) from recovery target GUC assign hooks JoongHyuk Shin <[email protected]>
2026-06-06 20:10 ` Re: [PATCH] Don't call ereport(ERROR) from recovery target GUC assign hooks Scott Ray <[email protected]>
2026-06-07 10:30 ` Re: [PATCH] Don't call ereport(ERROR) from recovery target GUC assign hooks JoongHyuk Shin <[email protected]>
2026-06-07 15:44 ` Re: [PATCH] Don't call ereport(ERROR) from recovery target GUC assign hooks Álvaro Herrera <[email protected]>
2026-06-21 09:27 ` Re: [PATCH] Don't call ereport(ERROR) from recovery target GUC assign hooks JoongHyuk Shin <[email protected]>
2026-06-21 12:31 ` Re: [PATCH] Don't call ereport(ERROR) from recovery target GUC assign hooks Álvaro Herrera <[email protected]>
2026-06-22 04:37 ` Re: [PATCH] Don't call ereport(ERROR) from recovery target GUC assign hooks JoongHyuk Shin <[email protected]>
2026-06-25 08:00 ` Re: [PATCH] Don't call ereport(ERROR) from recovery target GUC assign hooks Michael Paquier <[email protected]>
@ 2026-06-26 07:15 ` Kyotaro Horiguchi <[email protected]>
2026-06-26 08:12 ` Re: [PATCH] Don't call ereport(ERROR) from recovery target GUC assign hooks Michael Paquier <[email protected]>
0 siblings, 1 reply; 868+ messages in thread
From: Kyotaro Horiguchi @ 2026-06-26 07:15 UTC (permalink / raw)
To: [email protected]; +Cc: [email protected]; [email protected]; [email protected]; [email protected]; [email protected]
Hello,
I spent some time thinking about this issue and the proposed patch,
and I wonder if we can simplify it a bit further.
If we don't want to rely on GUC hooks for cross-checking multiple
recovery target settings, perhaps the assign hooks could stop updating
the shared recoveryTarget state altogether and only maintain their own
underlying variables.
As far as I can tell, the existing recovery target variables already
seem sufficient to determine whether each target is configured. The
only exception appears to be recovery_target = 'immediate', which
would probably need an additional boolean flag (or similar state) to
represent whether it has been specified.
Then validateRecoveryParameters() could determine which recovery
target is configured by inspecting those variables directly, report an
error if more than one target is set, and reconstruct the same
recoveryTarget state that the current code expects.
Does that make sense?
Regards,
--
Kyotaro Horiguchi
NTT Open Source Software Center
^ permalink raw reply [nested|flat] 868+ messages in thread
* Re: [PATCH] Don't call ereport(ERROR) from recovery target GUC assign hooks
2026-04-13 08:21 [PATCH] Don't call ereport(ERROR) from recovery target GUC assign hooks JoongHyuk Shin <[email protected]>
2026-04-24 13:08 ` Re: [PATCH] Don't call ereport(ERROR) from recovery target GUC assign hooks Fujii Masao <[email protected]>
2026-04-27 01:52 ` Re: [PATCH] Don't call ereport(ERROR) from recovery target GUC assign hooks Michael Paquier <[email protected]>
2026-04-27 05:36 ` Re: [PATCH] Don't call ereport(ERROR) from recovery target GUC assign hooks Fujii Masao <[email protected]>
2026-04-27 05:59 ` Re: [PATCH] Don't call ereport(ERROR) from recovery target GUC assign hooks Michael Paquier <[email protected]>
2026-04-29 09:29 ` Re: [PATCH] Don't call ereport(ERROR) from recovery target GUC assign hooks JoongHyuk Shin <[email protected]>
2026-05-01 05:53 ` Re: [PATCH] Don't call ereport(ERROR) from recovery target GUC assign hooks Fujii Masao <[email protected]>
2026-05-11 03:17 ` Re: [PATCH] Don't call ereport(ERROR) from recovery target GUC assign hooks JoongHyuk Shin <[email protected]>
2026-05-11 06:01 ` Re: [PATCH] Don't call ereport(ERROR) from recovery target GUC assign hooks JoongHyuk Shin <[email protected]>
2026-05-31 21:11 ` Re: [PATCH] Don't call ereport(ERROR) from recovery target GUC assign hooks Scott Ray <[email protected]>
2026-06-04 05:41 ` Re: [PATCH] Don't call ereport(ERROR) from recovery target GUC assign hooks JoongHyuk Shin <[email protected]>
2026-06-06 20:10 ` Re: [PATCH] Don't call ereport(ERROR) from recovery target GUC assign hooks Scott Ray <[email protected]>
2026-06-07 10:30 ` Re: [PATCH] Don't call ereport(ERROR) from recovery target GUC assign hooks JoongHyuk Shin <[email protected]>
2026-06-07 15:44 ` Re: [PATCH] Don't call ereport(ERROR) from recovery target GUC assign hooks Álvaro Herrera <[email protected]>
2026-06-21 09:27 ` Re: [PATCH] Don't call ereport(ERROR) from recovery target GUC assign hooks JoongHyuk Shin <[email protected]>
2026-06-21 12:31 ` Re: [PATCH] Don't call ereport(ERROR) from recovery target GUC assign hooks Álvaro Herrera <[email protected]>
2026-06-22 04:37 ` Re: [PATCH] Don't call ereport(ERROR) from recovery target GUC assign hooks JoongHyuk Shin <[email protected]>
2026-06-25 08:00 ` Re: [PATCH] Don't call ereport(ERROR) from recovery target GUC assign hooks Michael Paquier <[email protected]>
2026-06-26 07:15 ` Re: [PATCH] Don't call ereport(ERROR) from recovery target GUC assign hooks Kyotaro Horiguchi <[email protected]>
@ 2026-06-26 08:12 ` Michael Paquier <[email protected]>
0 siblings, 0 replies; 868+ messages in thread
From: Michael Paquier @ 2026-06-26 08:12 UTC (permalink / raw)
To: Kyotaro Horiguchi <[email protected]>; +Cc: [email protected]; [email protected]; [email protected]; [email protected]; [email protected]
On Fri, Jun 26, 2026 at 04:15:53PM +0900, Kyotaro Horiguchi wrote:
> As far as I can tell, the existing recovery target variables already
> seem sufficient to determine whether each target is configured. The
> only exception appears to be recovery_target = 'immediate', which
> would probably need an additional boolean flag (or similar state) to
> represent whether it has been specified.
>
> Then validateRecoveryParameters() could determine which recovery
> target is configured by inspecting those variables directly, report an
> error if more than one target is set, and reconstruct the same
> recoveryTarget state that the current code expects.
And delay setting recoveryTarget at all until we enter the validation
step in the startup process? We don't use it in any of the early
steps before entering validateRecoveryParameters() (if we do so, that
would be an incorrect thing to do anyway).
I don't see on top of my mind why that would not work, skipping all
the RECOVERY_TARGET_UNSET manipulations in the assign hooks. One
source of the confusion, to me, is that we have taken the decision to
link directly recoveryTarget with the result of the GUC, while
recoveryTarget is also used to track if the other target GUCs are set.
It looks to me that we shouldn't use recoveryTarget to track both if a
target is set and if the immediate state is set, and split both
things. This is what I guess you are getting at with your extra
boolean. Let's call it a new recoveryTargetImmediate.
At the end recoveryTarget should never be touched in any of the GUC
hooks, just once we enter validateRecoveryParameters().
--
Michael
Attachments:
[application/pgp-signature] signature.asc (833B, ../../[email protected]/2-signature.asc)
download
^ permalink raw reply [nested|flat] 868+ messages in thread
* Re: [PATCH] Don't call ereport(ERROR) from recovery target GUC assign hooks
2026-04-13 08:21 [PATCH] Don't call ereport(ERROR) from recovery target GUC assign hooks JoongHyuk Shin <[email protected]>
2026-04-24 13:08 ` Re: [PATCH] Don't call ereport(ERROR) from recovery target GUC assign hooks Fujii Masao <[email protected]>
2026-04-27 01:52 ` Re: [PATCH] Don't call ereport(ERROR) from recovery target GUC assign hooks Michael Paquier <[email protected]>
2026-04-27 05:36 ` Re: [PATCH] Don't call ereport(ERROR) from recovery target GUC assign hooks Fujii Masao <[email protected]>
2026-04-27 05:59 ` Re: [PATCH] Don't call ereport(ERROR) from recovery target GUC assign hooks Michael Paquier <[email protected]>
2026-04-29 09:29 ` Re: [PATCH] Don't call ereport(ERROR) from recovery target GUC assign hooks JoongHyuk Shin <[email protected]>
2026-05-01 05:53 ` Re: [PATCH] Don't call ereport(ERROR) from recovery target GUC assign hooks Fujii Masao <[email protected]>
2026-05-11 03:17 ` Re: [PATCH] Don't call ereport(ERROR) from recovery target GUC assign hooks JoongHyuk Shin <[email protected]>
2026-05-11 06:01 ` Re: [PATCH] Don't call ereport(ERROR) from recovery target GUC assign hooks JoongHyuk Shin <[email protected]>
2026-05-31 21:11 ` Re: [PATCH] Don't call ereport(ERROR) from recovery target GUC assign hooks Scott Ray <[email protected]>
2026-06-04 05:41 ` Re: [PATCH] Don't call ereport(ERROR) from recovery target GUC assign hooks JoongHyuk Shin <[email protected]>
2026-06-06 20:10 ` Re: [PATCH] Don't call ereport(ERROR) from recovery target GUC assign hooks Scott Ray <[email protected]>
2026-06-07 10:30 ` Re: [PATCH] Don't call ereport(ERROR) from recovery target GUC assign hooks JoongHyuk Shin <[email protected]>
2026-06-07 15:44 ` Re: [PATCH] Don't call ereport(ERROR) from recovery target GUC assign hooks Álvaro Herrera <[email protected]>
2026-06-21 09:27 ` Re: [PATCH] Don't call ereport(ERROR) from recovery target GUC assign hooks JoongHyuk Shin <[email protected]>
2026-06-21 12:31 ` Re: [PATCH] Don't call ereport(ERROR) from recovery target GUC assign hooks Álvaro Herrera <[email protected]>
2026-06-22 04:37 ` Re: [PATCH] Don't call ereport(ERROR) from recovery target GUC assign hooks JoongHyuk Shin <[email protected]>
@ 2026-06-26 11:12 ` Henson Choi <[email protected]>
2026-06-29 04:45 ` Re: [PATCH] Don't call ereport(ERROR) from recovery target GUC assign hooks JoongHyuk Shin <[email protected]>
1 sibling, 1 reply; 868+ messages in thread
From: Henson Choi @ 2026-06-26 11:12 UTC (permalink / raw)
To: JoongHyuk Shin <[email protected]>; +Cc: Álvaro Herrera <[email protected]>; Scott Ray <[email protected]>; Fujii Masao <[email protected]>; Michael Paquier <[email protected]>; [email protected]
Hi hackers,
Thanks for v6. I think there is still one reachable case on the
command-line (-c) path where the conflict detection is bypassed and a
recovery target is silently dropped, so recovery runs to the end of WAL
instead of stopping at the requested target. On master the same input
is rejected at startup, so for the -c path this is a loud-reject ->
silent behavior change.
Reproduction
------------
Using --check so it is side-effect-free; the conflict is detected during
GUC assignment, so an actual startup behaves the same:
postgres --check -D data \
-c "recovery_target_xid=700" \
-c "recovery_target_name=foo" \
-c "recovery_target_name="
master rejects it at the second assignment:
FATAL: multiple recovery targets specified
DETAIL: At most one of "recovery_target", "recovery_target_lsn",
"recovery_target_name", "recovery_target_time", "recovery_target_xid"
may be set.
v6 passes the check with no error (exit 0); recoveryTarget ends up
RECOVERY_TARGET_UNSET. In an actual PITR this means
recovery_target_xid=700 is silently ignored and the server recovers to
the end of WAL (target overshoot).
postgresql.conf does not reproduce this: duplicate keys are collapsed
there, so only the final value reaches the assign hook. The
set-then-clear of a *second*, different target only happens via -c,
where each option is applied in order and fires the assign hook every
time.
Why it happens
--------------
recoveryTarget is a single enum that the per-GUC assign hooks maintain
incrementally, and it alone drives recovery. CheckRecoveryTargetConflicts()
independently re-reads the committed GUC strings via GetConfigOption().
With the three -c options above the two sides diverge:
step assign hooks -> recoveryTarget final strings
xid=700 XID xid="700"
name=foo NAME (overwrites XID) xid="700", name="foo"
name="" UNSET (NAME was the last type) xid="700", name=""
At "name=foo" two targets are momentarily set, but the single enum
cannot represent that, so it just overwrites; "name=" then clears it.
By the time CheckRecoveryTargetConflicts() runs, the strings have
settled to a single non-empty target (xid), so ntargets == 1 and the
check passes -- while recovery actually runs with recoveryTarget ==
UNSET.
In other words, the v6 matrix row "cross-GUC, both non-empty -> error"
no longer holds once the second GUC is subsequently cleared: the
set-time guard was removed, and the startup check only inspects the
final strings, which no longer show the transient conflict. This is the
same class of unexpected behavior reported earlier in this thread; the
three-step -c form is the variant that still slips through.
One option would be to derive recoveryTarget from the settled GUC strings
in CheckRecoveryTargetConflicts() instead of maintaining it incrementally
in the assign hooks, but any other approach is perfectly fine.
Minor nit
---------
While here: the new ereport() in CheckRecoveryTargetConflicts() still
wraps its arguments in the legacy extra parentheses,
ereport(FATAL,
(errcode(ERRCODE_INVALID_PARAMETER_VALUE),
errmsg("multiple recovery targets specified"),
...));
Since the parenthesis-free ereport() form is available, new code can drop
them:
ereport(FATAL,
errcode(ERRCODE_INVALID_PARAMETER_VALUE),
errmsg("multiple recovery targets specified"),
...);
Regards,
Henson
^ permalink raw reply [nested|flat] 868+ messages in thread
* Re: [PATCH] Don't call ereport(ERROR) from recovery target GUC assign hooks
2026-04-13 08:21 [PATCH] Don't call ereport(ERROR) from recovery target GUC assign hooks JoongHyuk Shin <[email protected]>
2026-04-24 13:08 ` Re: [PATCH] Don't call ereport(ERROR) from recovery target GUC assign hooks Fujii Masao <[email protected]>
2026-04-27 01:52 ` Re: [PATCH] Don't call ereport(ERROR) from recovery target GUC assign hooks Michael Paquier <[email protected]>
2026-04-27 05:36 ` Re: [PATCH] Don't call ereport(ERROR) from recovery target GUC assign hooks Fujii Masao <[email protected]>
2026-04-27 05:59 ` Re: [PATCH] Don't call ereport(ERROR) from recovery target GUC assign hooks Michael Paquier <[email protected]>
2026-04-29 09:29 ` Re: [PATCH] Don't call ereport(ERROR) from recovery target GUC assign hooks JoongHyuk Shin <[email protected]>
2026-05-01 05:53 ` Re: [PATCH] Don't call ereport(ERROR) from recovery target GUC assign hooks Fujii Masao <[email protected]>
2026-05-11 03:17 ` Re: [PATCH] Don't call ereport(ERROR) from recovery target GUC assign hooks JoongHyuk Shin <[email protected]>
2026-05-11 06:01 ` Re: [PATCH] Don't call ereport(ERROR) from recovery target GUC assign hooks JoongHyuk Shin <[email protected]>
2026-05-31 21:11 ` Re: [PATCH] Don't call ereport(ERROR) from recovery target GUC assign hooks Scott Ray <[email protected]>
2026-06-04 05:41 ` Re: [PATCH] Don't call ereport(ERROR) from recovery target GUC assign hooks JoongHyuk Shin <[email protected]>
2026-06-06 20:10 ` Re: [PATCH] Don't call ereport(ERROR) from recovery target GUC assign hooks Scott Ray <[email protected]>
2026-06-07 10:30 ` Re: [PATCH] Don't call ereport(ERROR) from recovery target GUC assign hooks JoongHyuk Shin <[email protected]>
2026-06-07 15:44 ` Re: [PATCH] Don't call ereport(ERROR) from recovery target GUC assign hooks Álvaro Herrera <[email protected]>
2026-06-21 09:27 ` Re: [PATCH] Don't call ereport(ERROR) from recovery target GUC assign hooks JoongHyuk Shin <[email protected]>
2026-06-21 12:31 ` Re: [PATCH] Don't call ereport(ERROR) from recovery target GUC assign hooks Álvaro Herrera <[email protected]>
2026-06-22 04:37 ` Re: [PATCH] Don't call ereport(ERROR) from recovery target GUC assign hooks JoongHyuk Shin <[email protected]>
2026-06-26 11:12 ` Re: [PATCH] Don't call ereport(ERROR) from recovery target GUC assign hooks Henson Choi <[email protected]>
@ 2026-06-29 04:45 ` JoongHyuk Shin <[email protected]>
2026-06-29 05:47 ` Re: [PATCH] Don't call ereport(ERROR) from recovery target GUC assign hooks Michael Paquier <[email protected]>
0 siblings, 1 reply; 868+ messages in thread
From: JoongHyuk Shin @ 2026-06-29 04:45 UTC (permalink / raw)
To: [email protected]; +Cc: Álvaro Herrera <[email protected]>; Scott Ray <[email protected]>; Fujii Masao <[email protected]>; Michael Paquier <[email protected]>; [email protected]
Thanks for the reviews.
v7 attached, reworked along the lines discussed. The assign hooks
no longer touch recoveryTarget; each just stores its own value,
and recoveryTarget is derived once in validateRecoveryParameters()
from the settled recovery_target* settings,
which also rejects setting more than one target.
The v6 review comments are folded in too.
> recovery_target_xid=700 is silently ignored ... target overshoot
> derive recoveryTarget from the settled GUC strings
Done that way. A new test, standby_clobber_clear, sets a competing target
and clears it again; on master that is rejected outright,
while here recovery stops at the xid.
I left out the extra boolean. recovery_target's own string is "immediate"
or empty, so immediate is detected like the other four.
Deriving it from the typed value variables would need the flag,
but those aren't reliable "is set" signals anyway,
since the time value is parsed late and an LSN of 0/0 is valid.
Happy to add it back if you prefer it explicit.
> the legacy extra parentheses in ereport()
Dropped.
--
JH Shin
Attachments:
[application/octet-stream] v7-0001-Don-t-call-ereport-ERROR-from-recovery-target-GUC.patch (17.6K, ../../CACSdjfOFz=i2=YT_2kej-+OA0UTyUO1Hc5kEq-2wqzJcmdR6aw@mail.gmail.com/3-v7-0001-Don-t-call-ereport-ERROR-from-recovery-target-GUC.patch)
download | inline diff:
From 2c0daee6be5ffdb2ac48e4da687247480ebfb8dc Mon Sep 17 00:00:00 2001
From: JoongHyuk Shin <[email protected]>
Date: Sun, 28 Jun 2026 18:25:45 +0900
Subject: [PATCH] Don't call ereport(ERROR) from recovery target GUC assign
hooks
A GUC assign hook must not raise an error, but the recovery_target*
assign hooks did so when a second target was set.
Make the assign hooks store only their own value, and derive
recoveryTarget once in validateRecoveryParameters() from the settled
recovery_target* values, rejecting there a configuration that sets more
than one target.
---
src/backend/access/transam/xlogrecovery.c | 142 ++++++++-----------
src/backend/utils/misc/guc_parameters.dat | 2 -
src/include/utils/guc_hooks.h | 2 -
src/test/recovery/t/003_recovery_targets.pl | 149 ++++++++++++++++----
4 files changed, 183 insertions(+), 112 deletions(-)
diff --git a/src/backend/access/transam/xlogrecovery.c b/src/backend/access/transam/xlogrecovery.c
index c0ae4d3f63f..5c8d019c7bb 100644
--- a/src/backend/access/transam/xlogrecovery.c
+++ b/src/backend/access/transam/xlogrecovery.c
@@ -61,6 +61,7 @@
#include "storage/subsystems.h"
#include "utils/datetime.h"
#include "utils/fmgrprotos.h"
+#include "utils/guc.h"
#include "utils/guc_hooks.h"
#include "utils/pgstat_internal.h"
#include "utils/pg_lsn.h"
@@ -341,6 +342,7 @@ static void ApplyWalRecord(XLogReaderState *xlogreader, XLogRecord *record, Time
static void EnableStandbyMode(void);
static void readRecoverySignalFile(void);
static void validateRecoveryParameters(void);
+static RecoveryTargetType DetermineRecoveryTargetType(void);
static bool read_backup_label(XLogRecPtr *checkPointLoc,
TimeLineID *backupLabelTLI,
bool *backupEndRequired, bool *backupFromStandby);
@@ -1067,6 +1069,14 @@ readRecoverySignalFile(void)
static void
validateRecoveryParameters(void)
{
+ /*
+ * Derive recoveryTarget from the final recovery_target* settings,
+ * rejecting a configuration with more than one of them. This runs before
+ * the early return below so that conflicts are rejected at every startup,
+ * as the assign hooks used to do.
+ */
+ recoveryTarget = DetermineRecoveryTargetType();
+
if (!ArchiveRecoveryRequested)
return;
@@ -4769,30 +4779,59 @@ check_primary_slot_name(char **newval, void **extra, GucSource source)
}
/*
- * Recovery target settings: Only one of the several recovery_target* settings
- * may be set. Setting a second one results in an error. The global variable
- * recoveryTarget tracks which kind of recovery target was chosen. Other
- * variables store the actual target value (for example a string or a xid).
- * The assign functions of the parameters check whether a competing parameter
- * was already set. But we want to allow setting the same parameter multiple
- * times. We also want to allow unsetting a parameter and setting a different
- * one, so we unset recoveryTarget when the parameter is set to an empty
- * string.
- *
- * XXX this code is broken by design. Throwing an error from a GUC assign
- * hook breaks fundamental assumptions of guc.c. So long as all the variables
- * for which this can happen are PGC_POSTMASTER, the consequences are limited,
- * since we'd just abort postmaster startup anyway. Nonetheless it's likely
- * that we have odd behaviors such as unexpected GUC ordering dependencies.
+ * Recovery target settings: at most one of the recovery_target* settings may
+ * be set. The assign hooks just store each parameter's own value; the chosen
+ * target and any conflict are derived here instead, from the final settings,
+ * because an assign hook must not raise an error and cannot see sibling GUCs.
+ * validateRecoveryParameters() calls this once after all GUC processing.
*/
-
-pg_noreturn static void
-error_multiple_recovery_targets(void)
+static RecoveryTargetType
+DetermineRecoveryTargetType(void)
{
- ereport(ERROR,
- (errcode(ERRCODE_INVALID_PARAMETER_VALUE),
- errmsg("multiple recovery targets specified"),
- errdetail("At most one of \"recovery_target\", \"recovery_target_lsn\", \"recovery_target_name\", \"recovery_target_time\", \"recovery_target_xid\" may be set.")));
+ int ntargets = 0;
+ RecoveryTargetType target = RECOVERY_TARGET_UNSET;
+ const char *val;
+ StringInfoData buf;
+
+ initStringInfo(&buf);
+
+ /*
+ * These are all PGC_STRING, so GetConfigOption() returns "" (not NULL)
+ * when unset. The separators and quotes are wrapped in _() so
+ * translators can adapt the list punctuation.
+ */
+#define ADD_TARGET_IF_SET(gucname, kind) \
+ do { \
+ val = GetConfigOption(gucname, false, false); \
+ if (val[0] != '\0') \
+ { \
+ ntargets++; \
+ target = (kind); \
+ if (buf.len == 0) \
+ appendStringInfo(&buf, _("\"%s\""), gucname); \
+ else \
+ appendStringInfo(&buf, _(", \"%s\""), gucname); \
+ } \
+ } while (0)
+
+ ADD_TARGET_IF_SET("recovery_target", RECOVERY_TARGET_IMMEDIATE);
+ ADD_TARGET_IF_SET("recovery_target_lsn", RECOVERY_TARGET_LSN);
+ ADD_TARGET_IF_SET("recovery_target_name", RECOVERY_TARGET_NAME);
+ ADD_TARGET_IF_SET("recovery_target_time", RECOVERY_TARGET_TIME);
+ ADD_TARGET_IF_SET("recovery_target_xid", RECOVERY_TARGET_XID);
+#undef ADD_TARGET_IF_SET
+
+ if (ntargets > 1)
+ ereport(FATAL,
+ errcode(ERRCODE_INVALID_PARAMETER_VALUE),
+ errmsg("multiple recovery targets specified"),
+ errdetail("Only one recovery target can be set. Parameters set: %s.",
+ buf.data),
+ errhint("See pg_settings for the parameter values and where each is set."));
+
+ pfree(buf.data);
+
+ return target;
}
/*
@@ -4809,22 +4848,6 @@ check_recovery_target(char **newval, void **extra, GucSource source)
return true;
}
-/*
- * GUC assign_hook for recovery_target
- */
-void
-assign_recovery_target(const char *newval, void *extra)
-{
- if (recoveryTarget != RECOVERY_TARGET_UNSET &&
- recoveryTarget != RECOVERY_TARGET_IMMEDIATE)
- error_multiple_recovery_targets();
-
- if (newval && strcmp(newval, "") != 0)
- recoveryTarget = RECOVERY_TARGET_IMMEDIATE;
- else
- recoveryTarget = RECOVERY_TARGET_UNSET;
-}
-
/*
* GUC check_hook for recovery_target_lsn
*/
@@ -4856,17 +4879,8 @@ check_recovery_target_lsn(char **newval, void **extra, GucSource source)
void
assign_recovery_target_lsn(const char *newval, void *extra)
{
- if (recoveryTarget != RECOVERY_TARGET_UNSET &&
- recoveryTarget != RECOVERY_TARGET_LSN)
- error_multiple_recovery_targets();
-
if (newval && strcmp(newval, "") != 0)
- {
- recoveryTarget = RECOVERY_TARGET_LSN;
recoveryTargetLSN = *((XLogRecPtr *) extra);
- }
- else
- recoveryTarget = RECOVERY_TARGET_UNSET;
}
/*
@@ -4891,17 +4905,8 @@ check_recovery_target_name(char **newval, void **extra, GucSource source)
void
assign_recovery_target_name(const char *newval, void *extra)
{
- if (recoveryTarget != RECOVERY_TARGET_UNSET &&
- recoveryTarget != RECOVERY_TARGET_NAME)
- error_multiple_recovery_targets();
-
if (newval && strcmp(newval, "") != 0)
- {
- recoveryTarget = RECOVERY_TARGET_NAME;
recoveryTargetName = newval;
- }
- else
- recoveryTarget = RECOVERY_TARGET_UNSET;
}
/*
@@ -4965,22 +4970,6 @@ check_recovery_target_time(char **newval, void **extra, GucSource source)
return true;
}
-/*
- * GUC assign_hook for recovery_target_time
- */
-void
-assign_recovery_target_time(const char *newval, void *extra)
-{
- if (recoveryTarget != RECOVERY_TARGET_UNSET &&
- recoveryTarget != RECOVERY_TARGET_TIME)
- error_multiple_recovery_targets();
-
- if (newval && strcmp(newval, "") != 0)
- recoveryTarget = RECOVERY_TARGET_TIME;
- else
- recoveryTarget = RECOVERY_TARGET_UNSET;
-}
-
/*
* GUC check_hook for recovery_target_timeline
*/
@@ -5099,15 +5088,6 @@ check_recovery_target_xid(char **newval, void **extra, GucSource source)
void
assign_recovery_target_xid(const char *newval, void *extra)
{
- if (recoveryTarget != RECOVERY_TARGET_UNSET &&
- recoveryTarget != RECOVERY_TARGET_XID)
- error_multiple_recovery_targets();
-
if (newval && strcmp(newval, "") != 0)
- {
- recoveryTarget = RECOVERY_TARGET_XID;
recoveryTargetXid = *((TransactionId *) extra);
- }
- else
- recoveryTarget = RECOVERY_TARGET_UNSET;
}
diff --git a/src/backend/utils/misc/guc_parameters.dat b/src/backend/utils/misc/guc_parameters.dat
index 3c1e6b31bf8..7bc967c629f 100644
--- a/src/backend/utils/misc/guc_parameters.dat
+++ b/src/backend/utils/misc/guc_parameters.dat
@@ -2455,7 +2455,6 @@
variable => 'recovery_target_string',
boot_val => '""',
check_hook => 'check_recovery_target',
- assign_hook => 'assign_recovery_target',
},
{ name => 'recovery_target_action', type => 'enum', context => 'PGC_POSTMASTER', group => 'WAL_RECOVERY_TARGET',
@@ -2492,7 +2491,6 @@
variable => 'recovery_target_time_string',
boot_val => '""',
check_hook => 'check_recovery_target_time',
- assign_hook => 'assign_recovery_target_time',
},
{ name => 'recovery_target_timeline', type => 'string', context => 'PGC_POSTMASTER', group => 'WAL_RECOVERY_TARGET',
diff --git a/src/include/utils/guc_hooks.h b/src/include/utils/guc_hooks.h
index 307f4fbaefe..1aec17c67bd 100644
--- a/src/include/utils/guc_hooks.h
+++ b/src/include/utils/guc_hooks.h
@@ -103,7 +103,6 @@ extern bool check_recovery_prefetch(int *new_value, void **extra,
extern void assign_recovery_prefetch(int new_value, void *extra);
extern bool check_recovery_target(char **newval, void **extra,
GucSource source);
-extern void assign_recovery_target(const char *newval, void *extra);
extern bool check_recovery_target_lsn(char **newval, void **extra,
GucSource source);
extern void assign_recovery_target_lsn(const char *newval, void *extra);
@@ -112,7 +111,6 @@ extern bool check_recovery_target_name(char **newval, void **extra,
extern void assign_recovery_target_name(const char *newval, void *extra);
extern bool check_recovery_target_time(char **newval, void **extra,
GucSource source);
-extern void assign_recovery_target_time(const char *newval, void *extra);
extern bool check_recovery_target_timeline(char **newval, void **extra,
GucSource source);
extern void assign_recovery_target_timeline(const char *newval, void *extra);
diff --git a/src/test/recovery/t/003_recovery_targets.pl b/src/test/recovery/t/003_recovery_targets.pl
index 047eb13293a..f4d612e4263 100644
--- a/src/test/recovery/t/003_recovery_targets.pl
+++ b/src/test/recovery/t/003_recovery_targets.pl
@@ -51,6 +51,49 @@ sub test_recovery_standby
return;
}
+# Start a standby with the given pg_ctl --options string and verify that
+# the standby reaches the given LSN and row count. Used to exercise
+# scenarios that require the postmaster command line to receive multiple
+# "-c name=value" instances of the same GUC, which postgresql.conf cannot
+# express because ProcessConfigFile collapses duplicate keys.
+sub test_recovery_standby_with_options
+{
+ local $Test::Builder::Level = $Test::Builder::Level + 1;
+
+ my $test_name = shift;
+ my $node_name = shift;
+ my $node_primary = shift;
+ my $options = shift;
+ my $num_rows = shift;
+ my $until_lsn = shift;
+
+ my $node_standby = PostgreSQL::Test::Cluster->new($node_name);
+ $node_standby->init_from_backup($node_primary, 'my_backup',
+ has_restoring => 1);
+
+ my $res = run_log(
+ [
+ 'pg_ctl',
+ '--pgdata' => $node_standby->data_dir,
+ '--log' => $node_standby->logfile,
+ '--options' => $options,
+ 'start',
+ ]);
+ ok($res, "server starts for $test_name");
+
+ $node_standby->poll_query_until('postgres',
+ "SELECT '$until_lsn'::pg_lsn <= pg_last_wal_replay_lsn()")
+ or die "Timed out while waiting for standby to catch up";
+
+ my $count = $node_standby->safe_psql('postgres',
+ "SELECT count(*) FROM tab_int");
+ is($count, qq($num_rows), "check standby content for $test_name");
+
+ $node_standby->teardown_node;
+
+ return;
+}
+
# Initialize primary node
my $node_primary = PostgreSQL::Test::Cluster->new('primary');
$node_primary->init(has_archiving => 1, allows_streaming => 1);
@@ -108,6 +151,12 @@ $node_primary->safe_psql('postgres',
# Force archiving of WAL file
$node_primary->safe_psql('postgres', "SELECT pg_switch_wal()");
+# LSN after the final 6000-row insert and WAL switch. The set-then-clear case
+# below has no recovery target and replays all WAL, so it polls on this instead
+# of $lsn5, which would race the 5001-6000 rows.
+my $lsn6 =
+ $node_primary->safe_psql('postgres', "SELECT pg_current_wal_lsn()");
+
# Test recovery targets
my @recovery_params = ("recovery_target = 'immediate'");
test_recovery_standby('immediate target',
@@ -125,11 +174,22 @@ test_recovery_standby('name', 'standby_4', $node_primary, \@recovery_params,
test_recovery_standby('LSN', 'standby_5', $node_primary, \@recovery_params,
"5000", $lsn5);
+# Regression: empty-string for one recovery_target_* GUC must not clobber
+# another non-empty target. Setting recovery_target_xid + recovery_target_time
+# = '' must recover to the xid, not run as no-target recovery.
+@recovery_params = (
+ "recovery_target_xid = '$recovery_txid'",
+ "recovery_target_time = ''");
+test_recovery_standby('xid with empty time GUC',
+ 'standby_xid_empty_time', $node_primary, \@recovery_params,
+ "2000", $lsn2);
+
# Multiple targets
#
-# Multiple conflicting settings are not allowed, but setting the same
-# parameter multiple times or unsetting a parameter and setting a
-# different one is allowed.
+# Multiple conflicting non-empty settings are rejected. Setting the same
+# parameter twice is allowed (last value wins), and an empty string is a no-op
+# that does not clear another GUC's target. Conflicts are detected at every
+# server start by DetermineRecoveryTargetType().
@recovery_params = (
"recovery_target_name = '$recovery_name'",
@@ -138,31 +198,9 @@ test_recovery_standby('LSN', 'standby_5', $node_primary, \@recovery_params,
test_recovery_standby('multiple overriding settings',
'standby_6', $node_primary, \@recovery_params, "3000", $lsn3);
-my $node_standby = PostgreSQL::Test::Cluster->new('standby_7');
-$node_standby->init_from_backup($node_primary, 'my_backup',
- has_restoring => 1);
-$node_standby->append_conf(
- 'postgresql.conf', "recovery_target_name = '$recovery_name'
-recovery_target_time = '$recovery_time'");
-
-my $res = run_log(
- [
- 'pg_ctl',
- '--pgdata' => $node_standby->data_dir,
- '--log' => $node_standby->logfile,
- 'start',
- ]);
-ok(!$res, 'invalid recovery startup fails');
-
-my $logfile = slurp_file($node_standby->logfile());
-like(
- $logfile,
- qr/multiple recovery targets specified/,
- 'multiple conflicting settings');
-
# Check behavior when recovery ends before target is reached
-$node_standby = PostgreSQL::Test::Cluster->new('standby_8');
+my $node_standby = PostgreSQL::Test::Cluster->new('standby_8');
$node_standby->init_from_backup(
$node_primary, 'my_backup',
has_restoring => 1,
@@ -184,12 +222,69 @@ foreach my $i (0 .. 10 * $PostgreSQL::Test::Utils::timeout_default)
last if !-f $node_standby->data_dir . '/postmaster.pid';
usleep(100_000);
}
-$logfile = slurp_file($node_standby->logfile());
+my $logfile = slurp_file($node_standby->logfile());
like(
$logfile,
qr/FATAL: .* recovery ended before configured recovery target was reached/,
'recovery end before target reached is a fatal error');
+# Conflicts are rejected at every startup, even without recovery.signal.
+# init_from_backup without has_restoring creates no recovery.signal, so this
+# cluster would otherwise start as a plain primary; the conflict must still be
+# caught.
+my $node_no_signal = PostgreSQL::Test::Cluster->new('multi_target_no_signal');
+$node_no_signal->init_from_backup($node_primary, 'my_backup');
+$node_no_signal->append_conf(
+ 'postgresql.conf', "recovery_target_name = '$recovery_name'
+recovery_target_time = '$recovery_time'");
+
+my $res_no_signal = run_log(
+ [
+ 'pg_ctl',
+ '--pgdata' => $node_no_signal->data_dir,
+ '--log' => $node_no_signal->logfile,
+ 'start',
+ ]);
+ok(!$res_no_signal,
+ 'server fails to start with conflicting recovery targets and no recovery.signal');
+
+my $logfile_no_signal = slurp_file($node_no_signal->logfile());
+like(
+ $logfile_no_signal,
+ qr/multiple recovery targets specified/,
+ 'expected error message logged without recovery.signal');
+like(
+ $logfile_no_signal,
+ qr/Only one recovery target can be set\. Parameters set: "recovery_target_name", "recovery_target_time"/,
+ 'errdetail lists the set parameters in order without recovery.signal');
+unlike(
+ $logfile_no_signal,
+ qr/Parameters set:[^\n]*=/,
+ 'errdetail does not echo parameter values without recovery.signal');
+like(
+ $logfile_no_signal,
+ qr/HINT:.*pg_settings/,
+ 'errhint points to pg_settings without recovery.signal');
+
+# Same-GUC set-then-clear: setting a recovery_target_* GUC and then setting the
+# same GUC to an empty string leaves no target, so recovery runs to the end of
+# WAL. Duplicate keys collapse in postgresql.conf, so "pg_ctl --options" passes
+# both assignments on the postmaster command line.
+test_recovery_standby_with_options(
+ 'recovery_target_xid set then cleared',
+ 'standby_xid_set_clear', $node_primary,
+ "-c recovery_target_xid=$recovery_txid -c recovery_target_xid=",
+ "6000", $lsn6);
+
+# Set recovery_target_xid, then set and clear recovery_target_name. Only the
+# xid remains, so recovery must stop at it rather than running to the end of WAL
+# (a competing target that is set then cleared must not strand the first one).
+test_recovery_standby_with_options(
+ 'recovery target preserved when a competing one is set then cleared',
+ 'standby_clobber_clear', $node_primary,
+ "-c recovery_target_xid=$recovery_txid -c recovery_target_name=$recovery_name -c recovery_target_name=",
+ "2000", $lsn2);
+
# Invalid recovery_target_timeline tests
my ($result, $stdout, $stderr) = $node_primary->psql('postgres',
"ALTER SYSTEM SET recovery_target_timeline TO 'bogus'");
--
2.52.0
^ permalink raw reply [nested|flat] 868+ messages in thread
* Re: [PATCH] Don't call ereport(ERROR) from recovery target GUC assign hooks
2026-04-13 08:21 [PATCH] Don't call ereport(ERROR) from recovery target GUC assign hooks JoongHyuk Shin <[email protected]>
2026-04-24 13:08 ` Re: [PATCH] Don't call ereport(ERROR) from recovery target GUC assign hooks Fujii Masao <[email protected]>
2026-04-27 01:52 ` Re: [PATCH] Don't call ereport(ERROR) from recovery target GUC assign hooks Michael Paquier <[email protected]>
2026-04-27 05:36 ` Re: [PATCH] Don't call ereport(ERROR) from recovery target GUC assign hooks Fujii Masao <[email protected]>
2026-04-27 05:59 ` Re: [PATCH] Don't call ereport(ERROR) from recovery target GUC assign hooks Michael Paquier <[email protected]>
2026-04-29 09:29 ` Re: [PATCH] Don't call ereport(ERROR) from recovery target GUC assign hooks JoongHyuk Shin <[email protected]>
2026-05-01 05:53 ` Re: [PATCH] Don't call ereport(ERROR) from recovery target GUC assign hooks Fujii Masao <[email protected]>
2026-05-11 03:17 ` Re: [PATCH] Don't call ereport(ERROR) from recovery target GUC assign hooks JoongHyuk Shin <[email protected]>
2026-05-11 06:01 ` Re: [PATCH] Don't call ereport(ERROR) from recovery target GUC assign hooks JoongHyuk Shin <[email protected]>
2026-05-31 21:11 ` Re: [PATCH] Don't call ereport(ERROR) from recovery target GUC assign hooks Scott Ray <[email protected]>
2026-06-04 05:41 ` Re: [PATCH] Don't call ereport(ERROR) from recovery target GUC assign hooks JoongHyuk Shin <[email protected]>
2026-06-06 20:10 ` Re: [PATCH] Don't call ereport(ERROR) from recovery target GUC assign hooks Scott Ray <[email protected]>
2026-06-07 10:30 ` Re: [PATCH] Don't call ereport(ERROR) from recovery target GUC assign hooks JoongHyuk Shin <[email protected]>
2026-06-07 15:44 ` Re: [PATCH] Don't call ereport(ERROR) from recovery target GUC assign hooks Álvaro Herrera <[email protected]>
2026-06-21 09:27 ` Re: [PATCH] Don't call ereport(ERROR) from recovery target GUC assign hooks JoongHyuk Shin <[email protected]>
2026-06-21 12:31 ` Re: [PATCH] Don't call ereport(ERROR) from recovery target GUC assign hooks Álvaro Herrera <[email protected]>
2026-06-22 04:37 ` Re: [PATCH] Don't call ereport(ERROR) from recovery target GUC assign hooks JoongHyuk Shin <[email protected]>
2026-06-26 11:12 ` Re: [PATCH] Don't call ereport(ERROR) from recovery target GUC assign hooks Henson Choi <[email protected]>
2026-06-29 04:45 ` Re: [PATCH] Don't call ereport(ERROR) from recovery target GUC assign hooks JoongHyuk Shin <[email protected]>
@ 2026-06-29 05:47 ` Michael Paquier <[email protected]>
2026-06-29 07:59 ` Re: [PATCH] Don't call ereport(ERROR) from recovery target GUC assign hooks JoongHyuk Shin <[email protected]>
2026-06-29 21:16 ` Re: [PATCH] Don't call ereport(ERROR) from recovery target GUC assign hooks Zsolt Parragi <[email protected]>
0 siblings, 2 replies; 868+ messages in thread
From: Michael Paquier @ 2026-06-29 05:47 UTC (permalink / raw)
To: JoongHyuk Shin <[email protected]>; +Cc: [email protected]; Álvaro Herrera <[email protected]>; Scott Ray <[email protected]>; Fujii Masao <[email protected]>; [email protected]
On Mon, Jun 29, 2026 at 01:45:37PM +0900, JoongHyuk Shin wrote:
> v7 attached, reworked along the lines discussed. The assign hooks
> no longer touch recoveryTarget; each just stores its own value,
> and recoveryTarget is derived once in validateRecoveryParameters()
> from the settled recovery_target* settings,
> which also rejects setting more than one target.
> The v6 review comments are folded in too.
Hmm. One aspect of the patch that I have a hard time accepting is
this aspect:
ADD_TARGET_IF_SET("recovery_target", RECOVERY_TARGET_IMMEDIATE);
This part lacks future extensibility, IMO. If we add more values to
the GUC recovery_target in the future, we push down more complication
to the resolution of the immediate target at the beginning of the
startup process. Decoupling entirely RecoveryTargetType and the types
of values that can be assigned in the GUC recovery_target may lead to
a nicer result, I suspect.. It also feels wasteful to add an
hypothetical enum for the supported values if we don't have a use for
it yet. Any opinions from others?
+ errdetail("Only one recovery target can be set. Parameters set: %s.",
+ buf.data),
+ errhint("See pg_settings for the parameter values and where each is set."));
pg_settings is just one way to look at these values. We have also
SHOW and other interfaces. I would keep the errdetail() with your
first sentence, make the errhint the second sentence of the
errdetail().
--
Michael
Attachments:
[application/pgp-signature] signature.asc (833B, ../../[email protected]/2-signature.asc)
download
^ permalink raw reply [nested|flat] 868+ messages in thread
* Re: [PATCH] Don't call ereport(ERROR) from recovery target GUC assign hooks
2026-04-13 08:21 [PATCH] Don't call ereport(ERROR) from recovery target GUC assign hooks JoongHyuk Shin <[email protected]>
2026-04-24 13:08 ` Re: [PATCH] Don't call ereport(ERROR) from recovery target GUC assign hooks Fujii Masao <[email protected]>
2026-04-27 01:52 ` Re: [PATCH] Don't call ereport(ERROR) from recovery target GUC assign hooks Michael Paquier <[email protected]>
2026-04-27 05:36 ` Re: [PATCH] Don't call ereport(ERROR) from recovery target GUC assign hooks Fujii Masao <[email protected]>
2026-04-27 05:59 ` Re: [PATCH] Don't call ereport(ERROR) from recovery target GUC assign hooks Michael Paquier <[email protected]>
2026-04-29 09:29 ` Re: [PATCH] Don't call ereport(ERROR) from recovery target GUC assign hooks JoongHyuk Shin <[email protected]>
2026-05-01 05:53 ` Re: [PATCH] Don't call ereport(ERROR) from recovery target GUC assign hooks Fujii Masao <[email protected]>
2026-05-11 03:17 ` Re: [PATCH] Don't call ereport(ERROR) from recovery target GUC assign hooks JoongHyuk Shin <[email protected]>
2026-05-11 06:01 ` Re: [PATCH] Don't call ereport(ERROR) from recovery target GUC assign hooks JoongHyuk Shin <[email protected]>
2026-05-31 21:11 ` Re: [PATCH] Don't call ereport(ERROR) from recovery target GUC assign hooks Scott Ray <[email protected]>
2026-06-04 05:41 ` Re: [PATCH] Don't call ereport(ERROR) from recovery target GUC assign hooks JoongHyuk Shin <[email protected]>
2026-06-06 20:10 ` Re: [PATCH] Don't call ereport(ERROR) from recovery target GUC assign hooks Scott Ray <[email protected]>
2026-06-07 10:30 ` Re: [PATCH] Don't call ereport(ERROR) from recovery target GUC assign hooks JoongHyuk Shin <[email protected]>
2026-06-07 15:44 ` Re: [PATCH] Don't call ereport(ERROR) from recovery target GUC assign hooks Álvaro Herrera <[email protected]>
2026-06-21 09:27 ` Re: [PATCH] Don't call ereport(ERROR) from recovery target GUC assign hooks JoongHyuk Shin <[email protected]>
2026-06-21 12:31 ` Re: [PATCH] Don't call ereport(ERROR) from recovery target GUC assign hooks Álvaro Herrera <[email protected]>
2026-06-22 04:37 ` Re: [PATCH] Don't call ereport(ERROR) from recovery target GUC assign hooks JoongHyuk Shin <[email protected]>
2026-06-26 11:12 ` Re: [PATCH] Don't call ereport(ERROR) from recovery target GUC assign hooks Henson Choi <[email protected]>
2026-06-29 04:45 ` Re: [PATCH] Don't call ereport(ERROR) from recovery target GUC assign hooks JoongHyuk Shin <[email protected]>
2026-06-29 05:47 ` Re: [PATCH] Don't call ereport(ERROR) from recovery target GUC assign hooks Michael Paquier <[email protected]>
@ 2026-06-29 07:59 ` JoongHyuk Shin <[email protected]>
1 sibling, 0 replies; 868+ messages in thread
From: JoongHyuk Shin @ 2026-06-29 07:59 UTC (permalink / raw)
To: Michael Paquier <[email protected]>; +Cc: [email protected]; Álvaro Herrera <[email protected]>; Scott Ray <[email protected]>; Fujii Masao <[email protected]>; [email protected]
On Mon, Jun 29, 2026 at 2:48 PM Michael Paquier <[email protected]> wrote:
> pg_settings is just one way to look at these values. We have also
> SHOW and other interfaces. I would keep the errdetail() with your
> first sentence, make the errhint the second sentence of the errdetail().
I split it along the message style guide's line, factual information
in the detail and suggestions in the hint. The detail states the facts,
which targets are set, and the "see pg_settings" pointer is guidance
for tracking them down rather than a fact about the conflict,
so it went into the hint.
I'd keep it there rather than fold it into the detail.
SHOW does return the values, but I named pg_settings
specifically because it also reports where each parameter is set,
through its sourcefile and sourceline columns.
With several recovery_target* values possibly spread
across configuration files, finding where each one is set
is usually what it takes to resolve the conflict,
which is the part SHOW leaves out.
--
JH Shin
^ permalink raw reply [nested|flat] 868+ messages in thread
* Re: [PATCH] Don't call ereport(ERROR) from recovery target GUC assign hooks
2026-04-13 08:21 [PATCH] Don't call ereport(ERROR) from recovery target GUC assign hooks JoongHyuk Shin <[email protected]>
2026-04-24 13:08 ` Re: [PATCH] Don't call ereport(ERROR) from recovery target GUC assign hooks Fujii Masao <[email protected]>
2026-04-27 01:52 ` Re: [PATCH] Don't call ereport(ERROR) from recovery target GUC assign hooks Michael Paquier <[email protected]>
2026-04-27 05:36 ` Re: [PATCH] Don't call ereport(ERROR) from recovery target GUC assign hooks Fujii Masao <[email protected]>
2026-04-27 05:59 ` Re: [PATCH] Don't call ereport(ERROR) from recovery target GUC assign hooks Michael Paquier <[email protected]>
2026-04-29 09:29 ` Re: [PATCH] Don't call ereport(ERROR) from recovery target GUC assign hooks JoongHyuk Shin <[email protected]>
2026-05-01 05:53 ` Re: [PATCH] Don't call ereport(ERROR) from recovery target GUC assign hooks Fujii Masao <[email protected]>
2026-05-11 03:17 ` Re: [PATCH] Don't call ereport(ERROR) from recovery target GUC assign hooks JoongHyuk Shin <[email protected]>
2026-05-11 06:01 ` Re: [PATCH] Don't call ereport(ERROR) from recovery target GUC assign hooks JoongHyuk Shin <[email protected]>
2026-05-31 21:11 ` Re: [PATCH] Don't call ereport(ERROR) from recovery target GUC assign hooks Scott Ray <[email protected]>
2026-06-04 05:41 ` Re: [PATCH] Don't call ereport(ERROR) from recovery target GUC assign hooks JoongHyuk Shin <[email protected]>
2026-06-06 20:10 ` Re: [PATCH] Don't call ereport(ERROR) from recovery target GUC assign hooks Scott Ray <[email protected]>
2026-06-07 10:30 ` Re: [PATCH] Don't call ereport(ERROR) from recovery target GUC assign hooks JoongHyuk Shin <[email protected]>
2026-06-07 15:44 ` Re: [PATCH] Don't call ereport(ERROR) from recovery target GUC assign hooks Álvaro Herrera <[email protected]>
2026-06-21 09:27 ` Re: [PATCH] Don't call ereport(ERROR) from recovery target GUC assign hooks JoongHyuk Shin <[email protected]>
2026-06-21 12:31 ` Re: [PATCH] Don't call ereport(ERROR) from recovery target GUC assign hooks Álvaro Herrera <[email protected]>
2026-06-22 04:37 ` Re: [PATCH] Don't call ereport(ERROR) from recovery target GUC assign hooks JoongHyuk Shin <[email protected]>
2026-06-26 11:12 ` Re: [PATCH] Don't call ereport(ERROR) from recovery target GUC assign hooks Henson Choi <[email protected]>
2026-06-29 04:45 ` Re: [PATCH] Don't call ereport(ERROR) from recovery target GUC assign hooks JoongHyuk Shin <[email protected]>
2026-06-29 05:47 ` Re: [PATCH] Don't call ereport(ERROR) from recovery target GUC assign hooks Michael Paquier <[email protected]>
@ 2026-06-29 21:16 ` Zsolt Parragi <[email protected]>
2026-06-29 23:35 ` Re: [PATCH] Don't call ereport(ERROR) from recovery target GUC assign hooks Michael Paquier <[email protected]>
1 sibling, 1 reply; 868+ messages in thread
From: Zsolt Parragi @ 2026-06-29 21:16 UTC (permalink / raw)
To: [email protected]
On Mon, 29 Jun 2026, Michael Paquier <[email protected]> wrote:
> + errdetail("Only one recovery target can be set. Parameters set: %s.",
> + buf.data),
> + errhint("See pg_settings for the parameter values and where each is set."));
>
> pg_settings is just one way to look at these values. We have also
> SHOW and other interfaces. I would keep the errdetail() with your
> first sentence, make the errhint the second sentence of the
> errdetail().
Isn't mentioning pg_settings confusing instead of helpful during a
server restart? With a reload it can help, but when the server can't
start, hinting that the user should query pg_settings doesn't seem
that useful.
^ permalink raw reply [nested|flat] 868+ messages in thread
* Re: [PATCH] Don't call ereport(ERROR) from recovery target GUC assign hooks
2026-04-13 08:21 [PATCH] Don't call ereport(ERROR) from recovery target GUC assign hooks JoongHyuk Shin <[email protected]>
2026-04-24 13:08 ` Re: [PATCH] Don't call ereport(ERROR) from recovery target GUC assign hooks Fujii Masao <[email protected]>
2026-04-27 01:52 ` Re: [PATCH] Don't call ereport(ERROR) from recovery target GUC assign hooks Michael Paquier <[email protected]>
2026-04-27 05:36 ` Re: [PATCH] Don't call ereport(ERROR) from recovery target GUC assign hooks Fujii Masao <[email protected]>
2026-04-27 05:59 ` Re: [PATCH] Don't call ereport(ERROR) from recovery target GUC assign hooks Michael Paquier <[email protected]>
2026-04-29 09:29 ` Re: [PATCH] Don't call ereport(ERROR) from recovery target GUC assign hooks JoongHyuk Shin <[email protected]>
2026-05-01 05:53 ` Re: [PATCH] Don't call ereport(ERROR) from recovery target GUC assign hooks Fujii Masao <[email protected]>
2026-05-11 03:17 ` Re: [PATCH] Don't call ereport(ERROR) from recovery target GUC assign hooks JoongHyuk Shin <[email protected]>
2026-05-11 06:01 ` Re: [PATCH] Don't call ereport(ERROR) from recovery target GUC assign hooks JoongHyuk Shin <[email protected]>
2026-05-31 21:11 ` Re: [PATCH] Don't call ereport(ERROR) from recovery target GUC assign hooks Scott Ray <[email protected]>
2026-06-04 05:41 ` Re: [PATCH] Don't call ereport(ERROR) from recovery target GUC assign hooks JoongHyuk Shin <[email protected]>
2026-06-06 20:10 ` Re: [PATCH] Don't call ereport(ERROR) from recovery target GUC assign hooks Scott Ray <[email protected]>
2026-06-07 10:30 ` Re: [PATCH] Don't call ereport(ERROR) from recovery target GUC assign hooks JoongHyuk Shin <[email protected]>
2026-06-07 15:44 ` Re: [PATCH] Don't call ereport(ERROR) from recovery target GUC assign hooks Álvaro Herrera <[email protected]>
2026-06-21 09:27 ` Re: [PATCH] Don't call ereport(ERROR) from recovery target GUC assign hooks JoongHyuk Shin <[email protected]>
2026-06-21 12:31 ` Re: [PATCH] Don't call ereport(ERROR) from recovery target GUC assign hooks Álvaro Herrera <[email protected]>
2026-06-22 04:37 ` Re: [PATCH] Don't call ereport(ERROR) from recovery target GUC assign hooks JoongHyuk Shin <[email protected]>
2026-06-26 11:12 ` Re: [PATCH] Don't call ereport(ERROR) from recovery target GUC assign hooks Henson Choi <[email protected]>
2026-06-29 04:45 ` Re: [PATCH] Don't call ereport(ERROR) from recovery target GUC assign hooks JoongHyuk Shin <[email protected]>
2026-06-29 05:47 ` Re: [PATCH] Don't call ereport(ERROR) from recovery target GUC assign hooks Michael Paquier <[email protected]>
2026-06-29 21:16 ` Re: [PATCH] Don't call ereport(ERROR) from recovery target GUC assign hooks Zsolt Parragi <[email protected]>
@ 2026-06-29 23:35 ` Michael Paquier <[email protected]>
2026-07-06 07:53 ` Re: [PATCH] Don't call ereport(ERROR) from recovery target GUC assign hooks JoongHyuk Shin <[email protected]>
0 siblings, 1 reply; 868+ messages in thread
From: Michael Paquier @ 2026-06-29 23:35 UTC (permalink / raw)
To: Zsolt Parragi <[email protected]>; +Cc: [email protected]
On Mon, Jun 29, 2026 at 04:16:59PM -0500, Zsolt Parragi wrote:
> Isn't mentioning pg_settings confusing instead of helpful during a
> server restart? With a reload it can help, but when the server can't
> start, hinting that the user should query pg_settings doesn't seem
> that useful.
Yeah, it is. I don't see a reason why we should be specific about the
location where these parameters are set. One has been setting up
recovery parameters in the GUC machine, so they most likely know where
these strings are.
--
Michael
Attachments:
[application/pgp-signature] signature.asc (833B, ../../[email protected]/2-signature.asc)
download
^ permalink raw reply [nested|flat] 868+ messages in thread
* Re: [PATCH] Don't call ereport(ERROR) from recovery target GUC assign hooks
2026-04-13 08:21 [PATCH] Don't call ereport(ERROR) from recovery target GUC assign hooks JoongHyuk Shin <[email protected]>
2026-04-24 13:08 ` Re: [PATCH] Don't call ereport(ERROR) from recovery target GUC assign hooks Fujii Masao <[email protected]>
2026-04-27 01:52 ` Re: [PATCH] Don't call ereport(ERROR) from recovery target GUC assign hooks Michael Paquier <[email protected]>
2026-04-27 05:36 ` Re: [PATCH] Don't call ereport(ERROR) from recovery target GUC assign hooks Fujii Masao <[email protected]>
2026-04-27 05:59 ` Re: [PATCH] Don't call ereport(ERROR) from recovery target GUC assign hooks Michael Paquier <[email protected]>
2026-04-29 09:29 ` Re: [PATCH] Don't call ereport(ERROR) from recovery target GUC assign hooks JoongHyuk Shin <[email protected]>
2026-05-01 05:53 ` Re: [PATCH] Don't call ereport(ERROR) from recovery target GUC assign hooks Fujii Masao <[email protected]>
2026-05-11 03:17 ` Re: [PATCH] Don't call ereport(ERROR) from recovery target GUC assign hooks JoongHyuk Shin <[email protected]>
2026-05-11 06:01 ` Re: [PATCH] Don't call ereport(ERROR) from recovery target GUC assign hooks JoongHyuk Shin <[email protected]>
2026-05-31 21:11 ` Re: [PATCH] Don't call ereport(ERROR) from recovery target GUC assign hooks Scott Ray <[email protected]>
2026-06-04 05:41 ` Re: [PATCH] Don't call ereport(ERROR) from recovery target GUC assign hooks JoongHyuk Shin <[email protected]>
2026-06-06 20:10 ` Re: [PATCH] Don't call ereport(ERROR) from recovery target GUC assign hooks Scott Ray <[email protected]>
2026-06-07 10:30 ` Re: [PATCH] Don't call ereport(ERROR) from recovery target GUC assign hooks JoongHyuk Shin <[email protected]>
2026-06-07 15:44 ` Re: [PATCH] Don't call ereport(ERROR) from recovery target GUC assign hooks Álvaro Herrera <[email protected]>
2026-06-21 09:27 ` Re: [PATCH] Don't call ereport(ERROR) from recovery target GUC assign hooks JoongHyuk Shin <[email protected]>
2026-06-21 12:31 ` Re: [PATCH] Don't call ereport(ERROR) from recovery target GUC assign hooks Álvaro Herrera <[email protected]>
2026-06-22 04:37 ` Re: [PATCH] Don't call ereport(ERROR) from recovery target GUC assign hooks JoongHyuk Shin <[email protected]>
2026-06-26 11:12 ` Re: [PATCH] Don't call ereport(ERROR) from recovery target GUC assign hooks Henson Choi <[email protected]>
2026-06-29 04:45 ` Re: [PATCH] Don't call ereport(ERROR) from recovery target GUC assign hooks JoongHyuk Shin <[email protected]>
2026-06-29 05:47 ` Re: [PATCH] Don't call ereport(ERROR) from recovery target GUC assign hooks Michael Paquier <[email protected]>
2026-06-29 21:16 ` Re: [PATCH] Don't call ereport(ERROR) from recovery target GUC assign hooks Zsolt Parragi <[email protected]>
2026-06-29 23:35 ` Re: [PATCH] Don't call ereport(ERROR) from recovery target GUC assign hooks Michael Paquier <[email protected]>
@ 2026-07-06 07:53 ` JoongHyuk Shin <[email protected]>
2026-07-06 22:30 ` Re: [PATCH] Don't call ereport(ERROR) from recovery target GUC assign hooks Michael Paquier <[email protected]>
0 siblings, 1 reply; 868+ messages in thread
From: JoongHyuk Shin @ 2026-07-06 07:53 UTC (permalink / raw)
To: Michael Paquier <[email protected]>; +Cc: Zsolt Parragi <[email protected]>; [email protected]
On Mon, Jun 29, 2026 at 04:16:59PM -0500, Zsolt Parragi wrote:
> Isn't mentioning pg_settings confusing instead of helpful during a
> server restart? With a reload it can help, but when the server can't
> start, hinting that the user should query pg_settings doesn't seem
> that useful.
You're right, and I had missed this. Thanks.
I would rather drop the errhint from this patch entirely than fold it into
the errdetail.
Any objections?
--
JH Shin
^ permalink raw reply [nested|flat] 868+ messages in thread
* Re: [PATCH] Don't call ereport(ERROR) from recovery target GUC assign hooks
2026-04-13 08:21 [PATCH] Don't call ereport(ERROR) from recovery target GUC assign hooks JoongHyuk Shin <[email protected]>
2026-04-24 13:08 ` Re: [PATCH] Don't call ereport(ERROR) from recovery target GUC assign hooks Fujii Masao <[email protected]>
2026-04-27 01:52 ` Re: [PATCH] Don't call ereport(ERROR) from recovery target GUC assign hooks Michael Paquier <[email protected]>
2026-04-27 05:36 ` Re: [PATCH] Don't call ereport(ERROR) from recovery target GUC assign hooks Fujii Masao <[email protected]>
2026-04-27 05:59 ` Re: [PATCH] Don't call ereport(ERROR) from recovery target GUC assign hooks Michael Paquier <[email protected]>
2026-04-29 09:29 ` Re: [PATCH] Don't call ereport(ERROR) from recovery target GUC assign hooks JoongHyuk Shin <[email protected]>
2026-05-01 05:53 ` Re: [PATCH] Don't call ereport(ERROR) from recovery target GUC assign hooks Fujii Masao <[email protected]>
2026-05-11 03:17 ` Re: [PATCH] Don't call ereport(ERROR) from recovery target GUC assign hooks JoongHyuk Shin <[email protected]>
2026-05-11 06:01 ` Re: [PATCH] Don't call ereport(ERROR) from recovery target GUC assign hooks JoongHyuk Shin <[email protected]>
2026-05-31 21:11 ` Re: [PATCH] Don't call ereport(ERROR) from recovery target GUC assign hooks Scott Ray <[email protected]>
2026-06-04 05:41 ` Re: [PATCH] Don't call ereport(ERROR) from recovery target GUC assign hooks JoongHyuk Shin <[email protected]>
2026-06-06 20:10 ` Re: [PATCH] Don't call ereport(ERROR) from recovery target GUC assign hooks Scott Ray <[email protected]>
2026-06-07 10:30 ` Re: [PATCH] Don't call ereport(ERROR) from recovery target GUC assign hooks JoongHyuk Shin <[email protected]>
2026-06-07 15:44 ` Re: [PATCH] Don't call ereport(ERROR) from recovery target GUC assign hooks Álvaro Herrera <[email protected]>
2026-06-21 09:27 ` Re: [PATCH] Don't call ereport(ERROR) from recovery target GUC assign hooks JoongHyuk Shin <[email protected]>
2026-06-21 12:31 ` Re: [PATCH] Don't call ereport(ERROR) from recovery target GUC assign hooks Álvaro Herrera <[email protected]>
2026-06-22 04:37 ` Re: [PATCH] Don't call ereport(ERROR) from recovery target GUC assign hooks JoongHyuk Shin <[email protected]>
2026-06-26 11:12 ` Re: [PATCH] Don't call ereport(ERROR) from recovery target GUC assign hooks Henson Choi <[email protected]>
2026-06-29 04:45 ` Re: [PATCH] Don't call ereport(ERROR) from recovery target GUC assign hooks JoongHyuk Shin <[email protected]>
2026-06-29 05:47 ` Re: [PATCH] Don't call ereport(ERROR) from recovery target GUC assign hooks Michael Paquier <[email protected]>
2026-06-29 21:16 ` Re: [PATCH] Don't call ereport(ERROR) from recovery target GUC assign hooks Zsolt Parragi <[email protected]>
2026-06-29 23:35 ` Re: [PATCH] Don't call ereport(ERROR) from recovery target GUC assign hooks Michael Paquier <[email protected]>
2026-07-06 07:53 ` Re: [PATCH] Don't call ereport(ERROR) from recovery target GUC assign hooks JoongHyuk Shin <[email protected]>
@ 2026-07-06 22:30 ` Michael Paquier <[email protected]>
2026-07-07 03:01 ` Re: [PATCH] Don't call ereport(ERROR) from recovery target GUC assign hooks JoongHyuk Shin <[email protected]>
0 siblings, 1 reply; 868+ messages in thread
From: Michael Paquier @ 2026-07-06 22:30 UTC (permalink / raw)
To: JoongHyuk Shin <[email protected]>; +Cc: Zsolt Parragi <[email protected]>; [email protected]
On Mon, Jul 06, 2026 at 04:53:44PM +0900, JoongHyuk Shin wrote:
> I would rather drop the errhint from this patch entirely than fold it into
> the errdetail.
> Any objections?
Sounds good here to remove the errhint(). No hint is better than a
potentially wrong hint. Thanks.
--
Michael
Attachments:
[application/pgp-signature] signature.asc (833B, ../../[email protected]/2-signature.asc)
download
^ permalink raw reply [nested|flat] 868+ messages in thread
* Re: [PATCH] Don't call ereport(ERROR) from recovery target GUC assign hooks
2026-04-13 08:21 [PATCH] Don't call ereport(ERROR) from recovery target GUC assign hooks JoongHyuk Shin <[email protected]>
2026-04-24 13:08 ` Re: [PATCH] Don't call ereport(ERROR) from recovery target GUC assign hooks Fujii Masao <[email protected]>
2026-04-27 01:52 ` Re: [PATCH] Don't call ereport(ERROR) from recovery target GUC assign hooks Michael Paquier <[email protected]>
2026-04-27 05:36 ` Re: [PATCH] Don't call ereport(ERROR) from recovery target GUC assign hooks Fujii Masao <[email protected]>
2026-04-27 05:59 ` Re: [PATCH] Don't call ereport(ERROR) from recovery target GUC assign hooks Michael Paquier <[email protected]>
2026-04-29 09:29 ` Re: [PATCH] Don't call ereport(ERROR) from recovery target GUC assign hooks JoongHyuk Shin <[email protected]>
2026-05-01 05:53 ` Re: [PATCH] Don't call ereport(ERROR) from recovery target GUC assign hooks Fujii Masao <[email protected]>
2026-05-11 03:17 ` Re: [PATCH] Don't call ereport(ERROR) from recovery target GUC assign hooks JoongHyuk Shin <[email protected]>
2026-05-11 06:01 ` Re: [PATCH] Don't call ereport(ERROR) from recovery target GUC assign hooks JoongHyuk Shin <[email protected]>
2026-05-31 21:11 ` Re: [PATCH] Don't call ereport(ERROR) from recovery target GUC assign hooks Scott Ray <[email protected]>
2026-06-04 05:41 ` Re: [PATCH] Don't call ereport(ERROR) from recovery target GUC assign hooks JoongHyuk Shin <[email protected]>
2026-06-06 20:10 ` Re: [PATCH] Don't call ereport(ERROR) from recovery target GUC assign hooks Scott Ray <[email protected]>
2026-06-07 10:30 ` Re: [PATCH] Don't call ereport(ERROR) from recovery target GUC assign hooks JoongHyuk Shin <[email protected]>
2026-06-07 15:44 ` Re: [PATCH] Don't call ereport(ERROR) from recovery target GUC assign hooks Álvaro Herrera <[email protected]>
2026-06-21 09:27 ` Re: [PATCH] Don't call ereport(ERROR) from recovery target GUC assign hooks JoongHyuk Shin <[email protected]>
2026-06-21 12:31 ` Re: [PATCH] Don't call ereport(ERROR) from recovery target GUC assign hooks Álvaro Herrera <[email protected]>
2026-06-22 04:37 ` Re: [PATCH] Don't call ereport(ERROR) from recovery target GUC assign hooks JoongHyuk Shin <[email protected]>
2026-06-26 11:12 ` Re: [PATCH] Don't call ereport(ERROR) from recovery target GUC assign hooks Henson Choi <[email protected]>
2026-06-29 04:45 ` Re: [PATCH] Don't call ereport(ERROR) from recovery target GUC assign hooks JoongHyuk Shin <[email protected]>
2026-06-29 05:47 ` Re: [PATCH] Don't call ereport(ERROR) from recovery target GUC assign hooks Michael Paquier <[email protected]>
2026-06-29 21:16 ` Re: [PATCH] Don't call ereport(ERROR) from recovery target GUC assign hooks Zsolt Parragi <[email protected]>
2026-06-29 23:35 ` Re: [PATCH] Don't call ereport(ERROR) from recovery target GUC assign hooks Michael Paquier <[email protected]>
2026-07-06 07:53 ` Re: [PATCH] Don't call ereport(ERROR) from recovery target GUC assign hooks JoongHyuk Shin <[email protected]>
2026-07-06 22:30 ` Re: [PATCH] Don't call ereport(ERROR) from recovery target GUC assign hooks Michael Paquier <[email protected]>
@ 2026-07-07 03:01 ` JoongHyuk Shin <[email protected]>
2026-07-07 06:58 ` Re: [PATCH] Don't call ereport(ERROR) from recovery target GUC assign hooks Michael Paquier <[email protected]>
2026-07-07 06:59 ` Re: [PATCH] Don't call ereport(ERROR) from recovery target GUC assign hooks Michael Paquier <[email protected]>
0 siblings, 2 replies; 868+ messages in thread
From: JoongHyuk Shin @ 2026-07-07 03:01 UTC (permalink / raw)
To: Michael Paquier <[email protected]>; +Cc: Zsolt Parragi <[email protected]>; [email protected]
Attached v8 with the errhint removed. No other changes from v7.
--
JH Shin
Attachments:
[application/octet-stream] v8-0001-Don-t-call-ereport-ERROR-from-recovery-target-GUC.patch (17.4K, ../../CACSdjfPm+_EE2BTmu7oyW_Gt_7o2eZyNdvaT3fQKbaoKCwGOMQ@mail.gmail.com/3-v8-0001-Don-t-call-ereport-ERROR-from-recovery-target-GUC.patch)
download | inline diff:
From decdc03a2177ed31b5bbb464fa8c67d3f6d922bf Mon Sep 17 00:00:00 2001
From: JoongHyuk Shin <[email protected]>
Date: Sun, 28 Jun 2026 18:25:45 +0900
Subject: [PATCH v8] Don't call ereport(ERROR) from recovery target GUC assign
hooks
A GUC assign hook must not raise an error, but the recovery_target*
assign hooks did so when a second target was set.
Make the assign hooks store only their own value, and derive
recoveryTarget once in validateRecoveryParameters() from the settled
recovery_target* values, rejecting there a configuration that sets more
than one target.
---
src/backend/access/transam/xlogrecovery.c | 141 ++++++++-----------
src/backend/utils/misc/guc_parameters.dat | 2 -
src/include/utils/guc_hooks.h | 2 -
src/test/recovery/t/003_recovery_targets.pl | 145 ++++++++++++++++----
4 files changed, 178 insertions(+), 112 deletions(-)
diff --git a/src/backend/access/transam/xlogrecovery.c b/src/backend/access/transam/xlogrecovery.c
index c0ae4d3f63f..e0c9d306e6d 100644
--- a/src/backend/access/transam/xlogrecovery.c
+++ b/src/backend/access/transam/xlogrecovery.c
@@ -61,6 +61,7 @@
#include "storage/subsystems.h"
#include "utils/datetime.h"
#include "utils/fmgrprotos.h"
+#include "utils/guc.h"
#include "utils/guc_hooks.h"
#include "utils/pgstat_internal.h"
#include "utils/pg_lsn.h"
@@ -341,6 +342,7 @@ static void ApplyWalRecord(XLogReaderState *xlogreader, XLogRecord *record, Time
static void EnableStandbyMode(void);
static void readRecoverySignalFile(void);
static void validateRecoveryParameters(void);
+static RecoveryTargetType DetermineRecoveryTargetType(void);
static bool read_backup_label(XLogRecPtr *checkPointLoc,
TimeLineID *backupLabelTLI,
bool *backupEndRequired, bool *backupFromStandby);
@@ -1067,6 +1069,14 @@ readRecoverySignalFile(void)
static void
validateRecoveryParameters(void)
{
+ /*
+ * Derive recoveryTarget from the final recovery_target* settings,
+ * rejecting a configuration with more than one of them. This runs before
+ * the early return below so that conflicts are rejected at every startup,
+ * as the assign hooks used to do.
+ */
+ recoveryTarget = DetermineRecoveryTargetType();
+
if (!ArchiveRecoveryRequested)
return;
@@ -4769,30 +4779,58 @@ check_primary_slot_name(char **newval, void **extra, GucSource source)
}
/*
- * Recovery target settings: Only one of the several recovery_target* settings
- * may be set. Setting a second one results in an error. The global variable
- * recoveryTarget tracks which kind of recovery target was chosen. Other
- * variables store the actual target value (for example a string or a xid).
- * The assign functions of the parameters check whether a competing parameter
- * was already set. But we want to allow setting the same parameter multiple
- * times. We also want to allow unsetting a parameter and setting a different
- * one, so we unset recoveryTarget when the parameter is set to an empty
- * string.
- *
- * XXX this code is broken by design. Throwing an error from a GUC assign
- * hook breaks fundamental assumptions of guc.c. So long as all the variables
- * for which this can happen are PGC_POSTMASTER, the consequences are limited,
- * since we'd just abort postmaster startup anyway. Nonetheless it's likely
- * that we have odd behaviors such as unexpected GUC ordering dependencies.
+ * Recovery target settings: at most one of the recovery_target* settings may
+ * be set. The assign hooks just store each parameter's own value; the chosen
+ * target and any conflict are derived here instead, from the final settings,
+ * because an assign hook must not raise an error and cannot see sibling GUCs.
+ * validateRecoveryParameters() calls this once after all GUC processing.
*/
-
-pg_noreturn static void
-error_multiple_recovery_targets(void)
+static RecoveryTargetType
+DetermineRecoveryTargetType(void)
{
- ereport(ERROR,
- (errcode(ERRCODE_INVALID_PARAMETER_VALUE),
- errmsg("multiple recovery targets specified"),
- errdetail("At most one of \"recovery_target\", \"recovery_target_lsn\", \"recovery_target_name\", \"recovery_target_time\", \"recovery_target_xid\" may be set.")));
+ int ntargets = 0;
+ RecoveryTargetType target = RECOVERY_TARGET_UNSET;
+ const char *val;
+ StringInfoData buf;
+
+ initStringInfo(&buf);
+
+ /*
+ * These are all PGC_STRING, so GetConfigOption() returns "" (not NULL)
+ * when unset. The separators and quotes are wrapped in _() so
+ * translators can adapt the list punctuation.
+ */
+#define ADD_TARGET_IF_SET(gucname, kind) \
+ do { \
+ val = GetConfigOption(gucname, false, false); \
+ if (val[0] != '\0') \
+ { \
+ ntargets++; \
+ target = (kind); \
+ if (buf.len == 0) \
+ appendStringInfo(&buf, _("\"%s\""), gucname); \
+ else \
+ appendStringInfo(&buf, _(", \"%s\""), gucname); \
+ } \
+ } while (0)
+
+ ADD_TARGET_IF_SET("recovery_target", RECOVERY_TARGET_IMMEDIATE);
+ ADD_TARGET_IF_SET("recovery_target_lsn", RECOVERY_TARGET_LSN);
+ ADD_TARGET_IF_SET("recovery_target_name", RECOVERY_TARGET_NAME);
+ ADD_TARGET_IF_SET("recovery_target_time", RECOVERY_TARGET_TIME);
+ ADD_TARGET_IF_SET("recovery_target_xid", RECOVERY_TARGET_XID);
+#undef ADD_TARGET_IF_SET
+
+ if (ntargets > 1)
+ ereport(FATAL,
+ errcode(ERRCODE_INVALID_PARAMETER_VALUE),
+ errmsg("multiple recovery targets specified"),
+ errdetail("Only one recovery target can be set. Parameters set: %s.",
+ buf.data));
+
+ pfree(buf.data);
+
+ return target;
}
/*
@@ -4809,22 +4847,6 @@ check_recovery_target(char **newval, void **extra, GucSource source)
return true;
}
-/*
- * GUC assign_hook for recovery_target
- */
-void
-assign_recovery_target(const char *newval, void *extra)
-{
- if (recoveryTarget != RECOVERY_TARGET_UNSET &&
- recoveryTarget != RECOVERY_TARGET_IMMEDIATE)
- error_multiple_recovery_targets();
-
- if (newval && strcmp(newval, "") != 0)
- recoveryTarget = RECOVERY_TARGET_IMMEDIATE;
- else
- recoveryTarget = RECOVERY_TARGET_UNSET;
-}
-
/*
* GUC check_hook for recovery_target_lsn
*/
@@ -4856,17 +4878,8 @@ check_recovery_target_lsn(char **newval, void **extra, GucSource source)
void
assign_recovery_target_lsn(const char *newval, void *extra)
{
- if (recoveryTarget != RECOVERY_TARGET_UNSET &&
- recoveryTarget != RECOVERY_TARGET_LSN)
- error_multiple_recovery_targets();
-
if (newval && strcmp(newval, "") != 0)
- {
- recoveryTarget = RECOVERY_TARGET_LSN;
recoveryTargetLSN = *((XLogRecPtr *) extra);
- }
- else
- recoveryTarget = RECOVERY_TARGET_UNSET;
}
/*
@@ -4891,17 +4904,8 @@ check_recovery_target_name(char **newval, void **extra, GucSource source)
void
assign_recovery_target_name(const char *newval, void *extra)
{
- if (recoveryTarget != RECOVERY_TARGET_UNSET &&
- recoveryTarget != RECOVERY_TARGET_NAME)
- error_multiple_recovery_targets();
-
if (newval && strcmp(newval, "") != 0)
- {
- recoveryTarget = RECOVERY_TARGET_NAME;
recoveryTargetName = newval;
- }
- else
- recoveryTarget = RECOVERY_TARGET_UNSET;
}
/*
@@ -4965,22 +4969,6 @@ check_recovery_target_time(char **newval, void **extra, GucSource source)
return true;
}
-/*
- * GUC assign_hook for recovery_target_time
- */
-void
-assign_recovery_target_time(const char *newval, void *extra)
-{
- if (recoveryTarget != RECOVERY_TARGET_UNSET &&
- recoveryTarget != RECOVERY_TARGET_TIME)
- error_multiple_recovery_targets();
-
- if (newval && strcmp(newval, "") != 0)
- recoveryTarget = RECOVERY_TARGET_TIME;
- else
- recoveryTarget = RECOVERY_TARGET_UNSET;
-}
-
/*
* GUC check_hook for recovery_target_timeline
*/
@@ -5099,15 +5087,6 @@ check_recovery_target_xid(char **newval, void **extra, GucSource source)
void
assign_recovery_target_xid(const char *newval, void *extra)
{
- if (recoveryTarget != RECOVERY_TARGET_UNSET &&
- recoveryTarget != RECOVERY_TARGET_XID)
- error_multiple_recovery_targets();
-
if (newval && strcmp(newval, "") != 0)
- {
- recoveryTarget = RECOVERY_TARGET_XID;
recoveryTargetXid = *((TransactionId *) extra);
- }
- else
- recoveryTarget = RECOVERY_TARGET_UNSET;
}
diff --git a/src/backend/utils/misc/guc_parameters.dat b/src/backend/utils/misc/guc_parameters.dat
index 3c1e6b31bf8..7bc967c629f 100644
--- a/src/backend/utils/misc/guc_parameters.dat
+++ b/src/backend/utils/misc/guc_parameters.dat
@@ -2455,7 +2455,6 @@
variable => 'recovery_target_string',
boot_val => '""',
check_hook => 'check_recovery_target',
- assign_hook => 'assign_recovery_target',
},
{ name => 'recovery_target_action', type => 'enum', context => 'PGC_POSTMASTER', group => 'WAL_RECOVERY_TARGET',
@@ -2492,7 +2491,6 @@
variable => 'recovery_target_time_string',
boot_val => '""',
check_hook => 'check_recovery_target_time',
- assign_hook => 'assign_recovery_target_time',
},
{ name => 'recovery_target_timeline', type => 'string', context => 'PGC_POSTMASTER', group => 'WAL_RECOVERY_TARGET',
diff --git a/src/include/utils/guc_hooks.h b/src/include/utils/guc_hooks.h
index 307f4fbaefe..1aec17c67bd 100644
--- a/src/include/utils/guc_hooks.h
+++ b/src/include/utils/guc_hooks.h
@@ -103,7 +103,6 @@ extern bool check_recovery_prefetch(int *new_value, void **extra,
extern void assign_recovery_prefetch(int new_value, void *extra);
extern bool check_recovery_target(char **newval, void **extra,
GucSource source);
-extern void assign_recovery_target(const char *newval, void *extra);
extern bool check_recovery_target_lsn(char **newval, void **extra,
GucSource source);
extern void assign_recovery_target_lsn(const char *newval, void *extra);
@@ -112,7 +111,6 @@ extern bool check_recovery_target_name(char **newval, void **extra,
extern void assign_recovery_target_name(const char *newval, void *extra);
extern bool check_recovery_target_time(char **newval, void **extra,
GucSource source);
-extern void assign_recovery_target_time(const char *newval, void *extra);
extern bool check_recovery_target_timeline(char **newval, void **extra,
GucSource source);
extern void assign_recovery_target_timeline(const char *newval, void *extra);
diff --git a/src/test/recovery/t/003_recovery_targets.pl b/src/test/recovery/t/003_recovery_targets.pl
index 047eb13293a..19b25368089 100644
--- a/src/test/recovery/t/003_recovery_targets.pl
+++ b/src/test/recovery/t/003_recovery_targets.pl
@@ -51,6 +51,49 @@ sub test_recovery_standby
return;
}
+# Start a standby with the given pg_ctl --options string and verify that
+# the standby reaches the given LSN and row count. Used to exercise
+# scenarios that require the postmaster command line to receive multiple
+# "-c name=value" instances of the same GUC, which postgresql.conf cannot
+# express because ProcessConfigFile collapses duplicate keys.
+sub test_recovery_standby_with_options
+{
+ local $Test::Builder::Level = $Test::Builder::Level + 1;
+
+ my $test_name = shift;
+ my $node_name = shift;
+ my $node_primary = shift;
+ my $options = shift;
+ my $num_rows = shift;
+ my $until_lsn = shift;
+
+ my $node_standby = PostgreSQL::Test::Cluster->new($node_name);
+ $node_standby->init_from_backup($node_primary, 'my_backup',
+ has_restoring => 1);
+
+ my $res = run_log(
+ [
+ 'pg_ctl',
+ '--pgdata' => $node_standby->data_dir,
+ '--log' => $node_standby->logfile,
+ '--options' => $options,
+ 'start',
+ ]);
+ ok($res, "server starts for $test_name");
+
+ $node_standby->poll_query_until('postgres',
+ "SELECT '$until_lsn'::pg_lsn <= pg_last_wal_replay_lsn()")
+ or die "Timed out while waiting for standby to catch up";
+
+ my $count = $node_standby->safe_psql('postgres',
+ "SELECT count(*) FROM tab_int");
+ is($count, qq($num_rows), "check standby content for $test_name");
+
+ $node_standby->teardown_node;
+
+ return;
+}
+
# Initialize primary node
my $node_primary = PostgreSQL::Test::Cluster->new('primary');
$node_primary->init(has_archiving => 1, allows_streaming => 1);
@@ -108,6 +151,12 @@ $node_primary->safe_psql('postgres',
# Force archiving of WAL file
$node_primary->safe_psql('postgres', "SELECT pg_switch_wal()");
+# LSN after the final 6000-row insert and WAL switch. The set-then-clear case
+# below has no recovery target and replays all WAL, so it polls on this instead
+# of $lsn5, which would race the 5001-6000 rows.
+my $lsn6 =
+ $node_primary->safe_psql('postgres', "SELECT pg_current_wal_lsn()");
+
# Test recovery targets
my @recovery_params = ("recovery_target = 'immediate'");
test_recovery_standby('immediate target',
@@ -125,11 +174,22 @@ test_recovery_standby('name', 'standby_4', $node_primary, \@recovery_params,
test_recovery_standby('LSN', 'standby_5', $node_primary, \@recovery_params,
"5000", $lsn5);
+# Regression: empty-string for one recovery_target_* GUC must not clobber
+# another non-empty target. Setting recovery_target_xid + recovery_target_time
+# = '' must recover to the xid, not run as no-target recovery.
+@recovery_params = (
+ "recovery_target_xid = '$recovery_txid'",
+ "recovery_target_time = ''");
+test_recovery_standby('xid with empty time GUC',
+ 'standby_xid_empty_time', $node_primary, \@recovery_params,
+ "2000", $lsn2);
+
# Multiple targets
#
-# Multiple conflicting settings are not allowed, but setting the same
-# parameter multiple times or unsetting a parameter and setting a
-# different one is allowed.
+# Multiple conflicting non-empty settings are rejected. Setting the same
+# parameter twice is allowed (last value wins), and an empty string is a no-op
+# that does not clear another GUC's target. Conflicts are detected at every
+# server start by DetermineRecoveryTargetType().
@recovery_params = (
"recovery_target_name = '$recovery_name'",
@@ -138,31 +198,9 @@ test_recovery_standby('LSN', 'standby_5', $node_primary, \@recovery_params,
test_recovery_standby('multiple overriding settings',
'standby_6', $node_primary, \@recovery_params, "3000", $lsn3);
-my $node_standby = PostgreSQL::Test::Cluster->new('standby_7');
-$node_standby->init_from_backup($node_primary, 'my_backup',
- has_restoring => 1);
-$node_standby->append_conf(
- 'postgresql.conf', "recovery_target_name = '$recovery_name'
-recovery_target_time = '$recovery_time'");
-
-my $res = run_log(
- [
- 'pg_ctl',
- '--pgdata' => $node_standby->data_dir,
- '--log' => $node_standby->logfile,
- 'start',
- ]);
-ok(!$res, 'invalid recovery startup fails');
-
-my $logfile = slurp_file($node_standby->logfile());
-like(
- $logfile,
- qr/multiple recovery targets specified/,
- 'multiple conflicting settings');
-
# Check behavior when recovery ends before target is reached
-$node_standby = PostgreSQL::Test::Cluster->new('standby_8');
+my $node_standby = PostgreSQL::Test::Cluster->new('standby_8');
$node_standby->init_from_backup(
$node_primary, 'my_backup',
has_restoring => 1,
@@ -184,12 +222,65 @@ foreach my $i (0 .. 10 * $PostgreSQL::Test::Utils::timeout_default)
last if !-f $node_standby->data_dir . '/postmaster.pid';
usleep(100_000);
}
-$logfile = slurp_file($node_standby->logfile());
+my $logfile = slurp_file($node_standby->logfile());
like(
$logfile,
qr/FATAL: .* recovery ended before configured recovery target was reached/,
'recovery end before target reached is a fatal error');
+# Conflicts are rejected at every startup, even without recovery.signal.
+# init_from_backup without has_restoring creates no recovery.signal, so this
+# cluster would otherwise start as a plain primary; the conflict must still be
+# caught.
+my $node_no_signal = PostgreSQL::Test::Cluster->new('multi_target_no_signal');
+$node_no_signal->init_from_backup($node_primary, 'my_backup');
+$node_no_signal->append_conf(
+ 'postgresql.conf', "recovery_target_name = '$recovery_name'
+recovery_target_time = '$recovery_time'");
+
+my $res_no_signal = run_log(
+ [
+ 'pg_ctl',
+ '--pgdata' => $node_no_signal->data_dir,
+ '--log' => $node_no_signal->logfile,
+ 'start',
+ ]);
+ok(!$res_no_signal,
+ 'server fails to start with conflicting recovery targets and no recovery.signal');
+
+my $logfile_no_signal = slurp_file($node_no_signal->logfile());
+like(
+ $logfile_no_signal,
+ qr/multiple recovery targets specified/,
+ 'expected error message logged without recovery.signal');
+like(
+ $logfile_no_signal,
+ qr/Only one recovery target can be set\. Parameters set: "recovery_target_name", "recovery_target_time"/,
+ 'errdetail lists the set parameters in order without recovery.signal');
+unlike(
+ $logfile_no_signal,
+ qr/Parameters set:[^\n]*=/,
+ 'errdetail does not echo parameter values without recovery.signal');
+
+# Same-GUC set-then-clear: setting a recovery_target_* GUC and then setting the
+# same GUC to an empty string leaves no target, so recovery runs to the end of
+# WAL. Duplicate keys collapse in postgresql.conf, so "pg_ctl --options" passes
+# both assignments on the postmaster command line.
+test_recovery_standby_with_options(
+ 'recovery_target_xid set then cleared',
+ 'standby_xid_set_clear', $node_primary,
+ "-c recovery_target_xid=$recovery_txid -c recovery_target_xid=",
+ "6000", $lsn6);
+
+# Set recovery_target_xid, then set and clear recovery_target_name. Only the
+# xid remains, so recovery must stop at it rather than running to the end of WAL
+# (a competing target that is set then cleared must not strand the first one).
+test_recovery_standby_with_options(
+ 'recovery target preserved when a competing one is set then cleared',
+ 'standby_clobber_clear', $node_primary,
+ "-c recovery_target_xid=$recovery_txid -c recovery_target_name=$recovery_name -c recovery_target_name=",
+ "2000", $lsn2);
+
# Invalid recovery_target_timeline tests
my ($result, $stdout, $stderr) = $node_primary->psql('postgres',
"ALTER SYSTEM SET recovery_target_timeline TO 'bogus'");
--
2.52.0
^ permalink raw reply [nested|flat] 868+ messages in thread
* Re: [PATCH] Don't call ereport(ERROR) from recovery target GUC assign hooks
2026-04-13 08:21 [PATCH] Don't call ereport(ERROR) from recovery target GUC assign hooks JoongHyuk Shin <[email protected]>
2026-04-24 13:08 ` Re: [PATCH] Don't call ereport(ERROR) from recovery target GUC assign hooks Fujii Masao <[email protected]>
2026-04-27 01:52 ` Re: [PATCH] Don't call ereport(ERROR) from recovery target GUC assign hooks Michael Paquier <[email protected]>
2026-04-27 05:36 ` Re: [PATCH] Don't call ereport(ERROR) from recovery target GUC assign hooks Fujii Masao <[email protected]>
2026-04-27 05:59 ` Re: [PATCH] Don't call ereport(ERROR) from recovery target GUC assign hooks Michael Paquier <[email protected]>
2026-04-29 09:29 ` Re: [PATCH] Don't call ereport(ERROR) from recovery target GUC assign hooks JoongHyuk Shin <[email protected]>
2026-05-01 05:53 ` Re: [PATCH] Don't call ereport(ERROR) from recovery target GUC assign hooks Fujii Masao <[email protected]>
2026-05-11 03:17 ` Re: [PATCH] Don't call ereport(ERROR) from recovery target GUC assign hooks JoongHyuk Shin <[email protected]>
2026-05-11 06:01 ` Re: [PATCH] Don't call ereport(ERROR) from recovery target GUC assign hooks JoongHyuk Shin <[email protected]>
2026-05-31 21:11 ` Re: [PATCH] Don't call ereport(ERROR) from recovery target GUC assign hooks Scott Ray <[email protected]>
2026-06-04 05:41 ` Re: [PATCH] Don't call ereport(ERROR) from recovery target GUC assign hooks JoongHyuk Shin <[email protected]>
2026-06-06 20:10 ` Re: [PATCH] Don't call ereport(ERROR) from recovery target GUC assign hooks Scott Ray <[email protected]>
2026-06-07 10:30 ` Re: [PATCH] Don't call ereport(ERROR) from recovery target GUC assign hooks JoongHyuk Shin <[email protected]>
2026-06-07 15:44 ` Re: [PATCH] Don't call ereport(ERROR) from recovery target GUC assign hooks Álvaro Herrera <[email protected]>
2026-06-21 09:27 ` Re: [PATCH] Don't call ereport(ERROR) from recovery target GUC assign hooks JoongHyuk Shin <[email protected]>
2026-06-21 12:31 ` Re: [PATCH] Don't call ereport(ERROR) from recovery target GUC assign hooks Álvaro Herrera <[email protected]>
2026-06-22 04:37 ` Re: [PATCH] Don't call ereport(ERROR) from recovery target GUC assign hooks JoongHyuk Shin <[email protected]>
2026-06-26 11:12 ` Re: [PATCH] Don't call ereport(ERROR) from recovery target GUC assign hooks Henson Choi <[email protected]>
2026-06-29 04:45 ` Re: [PATCH] Don't call ereport(ERROR) from recovery target GUC assign hooks JoongHyuk Shin <[email protected]>
2026-06-29 05:47 ` Re: [PATCH] Don't call ereport(ERROR) from recovery target GUC assign hooks Michael Paquier <[email protected]>
2026-06-29 21:16 ` Re: [PATCH] Don't call ereport(ERROR) from recovery target GUC assign hooks Zsolt Parragi <[email protected]>
2026-06-29 23:35 ` Re: [PATCH] Don't call ereport(ERROR) from recovery target GUC assign hooks Michael Paquier <[email protected]>
2026-07-06 07:53 ` Re: [PATCH] Don't call ereport(ERROR) from recovery target GUC assign hooks JoongHyuk Shin <[email protected]>
2026-07-06 22:30 ` Re: [PATCH] Don't call ereport(ERROR) from recovery target GUC assign hooks Michael Paquier <[email protected]>
2026-07-07 03:01 ` Re: [PATCH] Don't call ereport(ERROR) from recovery target GUC assign hooks JoongHyuk Shin <[email protected]>
@ 2026-07-07 06:58 ` Michael Paquier <[email protected]>
1 sibling, 0 replies; 868+ messages in thread
From: Michael Paquier @ 2026-07-07 06:58 UTC (permalink / raw)
To: JoongHyuk Shin <[email protected]>; +Cc: Zsolt Parragi <[email protected]>; [email protected]
On Tue, Jul 07, 2026 at 12:01:59PM +0900, JoongHyuk Shin wrote:
> Attached v8 with the errhint removed. No other changes from v7.
+ errmsg("multiple recovery targets specified"),
+ errdetail("Only one recovery target can be set. Parameters set: %s.",
+ buf.data));
FWIW, I think that this is redundant, as the errmsg and the errdetail
are basically saying the same thing. I'd suggest a simpler:
errmsg: cannot specify more than one recovery target
errdetail: Parameters set are: %s.
+# that does not clear another GUC's target. Conflicts are detected at every
+# server start by DetermineRecoveryTargetType().
We don't really care about the function name here, just that multiple
targets are blocked.
+# LSN after the final 6000-row insert and WAL switch. The set-then-clear case
+# below has no recovery target and replays all WAL, so it polls on this instead
+# of $lsn5, which would race the 5001-6000 rows.
I smell of an AI set of comments. We could just remove the whole and
not lose value in understanding the meaning of the test. A bunch of
the comments added to the TAP script could also be trimmed down quite
a bit, made simpler..
With recovery_target assign hook being removed for the case of
immediate, a test case that checks for a conflict between immediate
and a secondary target may be in order.
Please note that Fujii-san is registered as a committer of this patch,
so I am not planning to go beyond a review here.
--
Michael
Attachments:
[application/pgp-signature] signature.asc (833B, ../../[email protected]/2-signature.asc)
download
^ permalink raw reply [nested|flat] 868+ messages in thread
* Re: [PATCH] Don't call ereport(ERROR) from recovery target GUC assign hooks
2026-04-13 08:21 [PATCH] Don't call ereport(ERROR) from recovery target GUC assign hooks JoongHyuk Shin <[email protected]>
2026-04-24 13:08 ` Re: [PATCH] Don't call ereport(ERROR) from recovery target GUC assign hooks Fujii Masao <[email protected]>
2026-04-27 01:52 ` Re: [PATCH] Don't call ereport(ERROR) from recovery target GUC assign hooks Michael Paquier <[email protected]>
2026-04-27 05:36 ` Re: [PATCH] Don't call ereport(ERROR) from recovery target GUC assign hooks Fujii Masao <[email protected]>
2026-04-27 05:59 ` Re: [PATCH] Don't call ereport(ERROR) from recovery target GUC assign hooks Michael Paquier <[email protected]>
2026-04-29 09:29 ` Re: [PATCH] Don't call ereport(ERROR) from recovery target GUC assign hooks JoongHyuk Shin <[email protected]>
2026-05-01 05:53 ` Re: [PATCH] Don't call ereport(ERROR) from recovery target GUC assign hooks Fujii Masao <[email protected]>
2026-05-11 03:17 ` Re: [PATCH] Don't call ereport(ERROR) from recovery target GUC assign hooks JoongHyuk Shin <[email protected]>
2026-05-11 06:01 ` Re: [PATCH] Don't call ereport(ERROR) from recovery target GUC assign hooks JoongHyuk Shin <[email protected]>
2026-05-31 21:11 ` Re: [PATCH] Don't call ereport(ERROR) from recovery target GUC assign hooks Scott Ray <[email protected]>
2026-06-04 05:41 ` Re: [PATCH] Don't call ereport(ERROR) from recovery target GUC assign hooks JoongHyuk Shin <[email protected]>
2026-06-06 20:10 ` Re: [PATCH] Don't call ereport(ERROR) from recovery target GUC assign hooks Scott Ray <[email protected]>
2026-06-07 10:30 ` Re: [PATCH] Don't call ereport(ERROR) from recovery target GUC assign hooks JoongHyuk Shin <[email protected]>
2026-06-07 15:44 ` Re: [PATCH] Don't call ereport(ERROR) from recovery target GUC assign hooks Álvaro Herrera <[email protected]>
2026-06-21 09:27 ` Re: [PATCH] Don't call ereport(ERROR) from recovery target GUC assign hooks JoongHyuk Shin <[email protected]>
2026-06-21 12:31 ` Re: [PATCH] Don't call ereport(ERROR) from recovery target GUC assign hooks Álvaro Herrera <[email protected]>
2026-06-22 04:37 ` Re: [PATCH] Don't call ereport(ERROR) from recovery target GUC assign hooks JoongHyuk Shin <[email protected]>
2026-06-26 11:12 ` Re: [PATCH] Don't call ereport(ERROR) from recovery target GUC assign hooks Henson Choi <[email protected]>
2026-06-29 04:45 ` Re: [PATCH] Don't call ereport(ERROR) from recovery target GUC assign hooks JoongHyuk Shin <[email protected]>
2026-06-29 05:47 ` Re: [PATCH] Don't call ereport(ERROR) from recovery target GUC assign hooks Michael Paquier <[email protected]>
2026-06-29 21:16 ` Re: [PATCH] Don't call ereport(ERROR) from recovery target GUC assign hooks Zsolt Parragi <[email protected]>
2026-06-29 23:35 ` Re: [PATCH] Don't call ereport(ERROR) from recovery target GUC assign hooks Michael Paquier <[email protected]>
2026-07-06 07:53 ` Re: [PATCH] Don't call ereport(ERROR) from recovery target GUC assign hooks JoongHyuk Shin <[email protected]>
2026-07-06 22:30 ` Re: [PATCH] Don't call ereport(ERROR) from recovery target GUC assign hooks Michael Paquier <[email protected]>
2026-07-07 03:01 ` Re: [PATCH] Don't call ereport(ERROR) from recovery target GUC assign hooks JoongHyuk Shin <[email protected]>
@ 2026-07-07 06:59 ` Michael Paquier <[email protected]>
2026-07-08 01:38 ` Re: [PATCH] Don't call ereport(ERROR) from recovery target GUC assign hooks Fujii Masao <[email protected]>
1 sibling, 1 reply; 868+ messages in thread
From: Michael Paquier @ 2026-07-07 06:59 UTC (permalink / raw)
To: JoongHyuk Shin <[email protected]>; +Cc: Zsolt Parragi <[email protected]>; [email protected]
On Tue, Jul 07, 2026 at 12:01:59PM +0900, JoongHyuk Shin wrote:
> Attached v8 with the errhint removed. No other changes from v7.
+ errmsg("multiple recovery targets specified"),
+ errdetail("Only one recovery target can be set. Parameters set: %s.",
+ buf.data));
FWIW, I think that this is redundant, as the errmsg and the errdetail
are basically saying the same thing. I'd suggest a simpler:
errmsg: cannot specify more than one recovery target
errdetail: Parameters set are: %s.
+# that does not clear another GUC's target. Conflicts are detected at every
+# server start by DetermineRecoveryTargetType().
We don't really care about the function name here, just that multiple
targets are blocked.
+# LSN after the final 6000-row insert and WAL switch. The set-then-clear case
+# below has no recovery target and replays all WAL, so it polls on this instead
+# of $lsn5, which would race the 5001-6000 rows.
I smell of an AI set of comments. We could just remove the whole and
not lose value in understanding the meaning of the test. A bunch of
the comments added to the TAP script could also be trimmed down quite
a bit, made simpler..
With recovery_target assign hook being removed for the case of
immediate, a test case that checks for a conflict between immediate
and a secondary target may be in order.
Please note that Fujii-san is registered as a committer of this patch,
so I am not planning to go beyond a review here.
--
Michael
Attachments:
[application/pgp-signature] signature.asc (833B, ../../[email protected]/2-signature.asc)
download
^ permalink raw reply [nested|flat] 868+ messages in thread
* Re: [PATCH] Don't call ereport(ERROR) from recovery target GUC assign hooks
2026-04-13 08:21 [PATCH] Don't call ereport(ERROR) from recovery target GUC assign hooks JoongHyuk Shin <[email protected]>
2026-04-24 13:08 ` Re: [PATCH] Don't call ereport(ERROR) from recovery target GUC assign hooks Fujii Masao <[email protected]>
2026-04-27 01:52 ` Re: [PATCH] Don't call ereport(ERROR) from recovery target GUC assign hooks Michael Paquier <[email protected]>
2026-04-27 05:36 ` Re: [PATCH] Don't call ereport(ERROR) from recovery target GUC assign hooks Fujii Masao <[email protected]>
2026-04-27 05:59 ` Re: [PATCH] Don't call ereport(ERROR) from recovery target GUC assign hooks Michael Paquier <[email protected]>
2026-04-29 09:29 ` Re: [PATCH] Don't call ereport(ERROR) from recovery target GUC assign hooks JoongHyuk Shin <[email protected]>
2026-05-01 05:53 ` Re: [PATCH] Don't call ereport(ERROR) from recovery target GUC assign hooks Fujii Masao <[email protected]>
2026-05-11 03:17 ` Re: [PATCH] Don't call ereport(ERROR) from recovery target GUC assign hooks JoongHyuk Shin <[email protected]>
2026-05-11 06:01 ` Re: [PATCH] Don't call ereport(ERROR) from recovery target GUC assign hooks JoongHyuk Shin <[email protected]>
2026-05-31 21:11 ` Re: [PATCH] Don't call ereport(ERROR) from recovery target GUC assign hooks Scott Ray <[email protected]>
2026-06-04 05:41 ` Re: [PATCH] Don't call ereport(ERROR) from recovery target GUC assign hooks JoongHyuk Shin <[email protected]>
2026-06-06 20:10 ` Re: [PATCH] Don't call ereport(ERROR) from recovery target GUC assign hooks Scott Ray <[email protected]>
2026-06-07 10:30 ` Re: [PATCH] Don't call ereport(ERROR) from recovery target GUC assign hooks JoongHyuk Shin <[email protected]>
2026-06-07 15:44 ` Re: [PATCH] Don't call ereport(ERROR) from recovery target GUC assign hooks Álvaro Herrera <[email protected]>
2026-06-21 09:27 ` Re: [PATCH] Don't call ereport(ERROR) from recovery target GUC assign hooks JoongHyuk Shin <[email protected]>
2026-06-21 12:31 ` Re: [PATCH] Don't call ereport(ERROR) from recovery target GUC assign hooks Álvaro Herrera <[email protected]>
2026-06-22 04:37 ` Re: [PATCH] Don't call ereport(ERROR) from recovery target GUC assign hooks JoongHyuk Shin <[email protected]>
2026-06-26 11:12 ` Re: [PATCH] Don't call ereport(ERROR) from recovery target GUC assign hooks Henson Choi <[email protected]>
2026-06-29 04:45 ` Re: [PATCH] Don't call ereport(ERROR) from recovery target GUC assign hooks JoongHyuk Shin <[email protected]>
2026-06-29 05:47 ` Re: [PATCH] Don't call ereport(ERROR) from recovery target GUC assign hooks Michael Paquier <[email protected]>
2026-06-29 21:16 ` Re: [PATCH] Don't call ereport(ERROR) from recovery target GUC assign hooks Zsolt Parragi <[email protected]>
2026-06-29 23:35 ` Re: [PATCH] Don't call ereport(ERROR) from recovery target GUC assign hooks Michael Paquier <[email protected]>
2026-07-06 07:53 ` Re: [PATCH] Don't call ereport(ERROR) from recovery target GUC assign hooks JoongHyuk Shin <[email protected]>
2026-07-06 22:30 ` Re: [PATCH] Don't call ereport(ERROR) from recovery target GUC assign hooks Michael Paquier <[email protected]>
2026-07-07 03:01 ` Re: [PATCH] Don't call ereport(ERROR) from recovery target GUC assign hooks JoongHyuk Shin <[email protected]>
2026-07-07 06:59 ` Re: [PATCH] Don't call ereport(ERROR) from recovery target GUC assign hooks Michael Paquier <[email protected]>
@ 2026-07-08 01:38 ` Fujii Masao <[email protected]>
2026-07-08 01:43 ` Re: [PATCH] Don't call ereport(ERROR) from recovery target GUC assign hooks Michael Paquier <[email protected]>
0 siblings, 1 reply; 868+ messages in thread
From: Fujii Masao @ 2026-07-08 01:38 UTC (permalink / raw)
To: Michael Paquier <[email protected]>; +Cc: JoongHyuk Shin <[email protected]>; Zsolt Parragi <[email protected]>; [email protected]
On Tue, Jul 7, 2026 at 3:59 PM Michael Paquier <[email protected]> wrote:
>
> On Tue, Jul 07, 2026 at 12:01:59PM +0900, JoongHyuk Shin wrote:
> > Attached v8 with the errhint removed. No other changes from v7.
We can remove assign_recovery_target_name()? With the v8 patch, it only
assigns the GUC string pointer to recoveryTargetName. It seems we could
instead have recovery_target_name store its value directly in
recoveryTargetName, remove recovery_target_name_string, and drop
the assign hook altogether. Thoughts?
> Please note that Fujii-san is registered as a committer of this patch,
> so I am not planning to go beyond a review here.
Thanks for the review! Yes, I will handle this patch.
Regards,
--
Fujii Masao
^ permalink raw reply [nested|flat] 868+ messages in thread
* Re: [PATCH] Don't call ereport(ERROR) from recovery target GUC assign hooks
2026-04-13 08:21 [PATCH] Don't call ereport(ERROR) from recovery target GUC assign hooks JoongHyuk Shin <[email protected]>
2026-04-24 13:08 ` Re: [PATCH] Don't call ereport(ERROR) from recovery target GUC assign hooks Fujii Masao <[email protected]>
2026-04-27 01:52 ` Re: [PATCH] Don't call ereport(ERROR) from recovery target GUC assign hooks Michael Paquier <[email protected]>
2026-04-27 05:36 ` Re: [PATCH] Don't call ereport(ERROR) from recovery target GUC assign hooks Fujii Masao <[email protected]>
2026-04-27 05:59 ` Re: [PATCH] Don't call ereport(ERROR) from recovery target GUC assign hooks Michael Paquier <[email protected]>
2026-04-29 09:29 ` Re: [PATCH] Don't call ereport(ERROR) from recovery target GUC assign hooks JoongHyuk Shin <[email protected]>
2026-05-01 05:53 ` Re: [PATCH] Don't call ereport(ERROR) from recovery target GUC assign hooks Fujii Masao <[email protected]>
2026-05-11 03:17 ` Re: [PATCH] Don't call ereport(ERROR) from recovery target GUC assign hooks JoongHyuk Shin <[email protected]>
2026-05-11 06:01 ` Re: [PATCH] Don't call ereport(ERROR) from recovery target GUC assign hooks JoongHyuk Shin <[email protected]>
2026-05-31 21:11 ` Re: [PATCH] Don't call ereport(ERROR) from recovery target GUC assign hooks Scott Ray <[email protected]>
2026-06-04 05:41 ` Re: [PATCH] Don't call ereport(ERROR) from recovery target GUC assign hooks JoongHyuk Shin <[email protected]>
2026-06-06 20:10 ` Re: [PATCH] Don't call ereport(ERROR) from recovery target GUC assign hooks Scott Ray <[email protected]>
2026-06-07 10:30 ` Re: [PATCH] Don't call ereport(ERROR) from recovery target GUC assign hooks JoongHyuk Shin <[email protected]>
2026-06-07 15:44 ` Re: [PATCH] Don't call ereport(ERROR) from recovery target GUC assign hooks Álvaro Herrera <[email protected]>
2026-06-21 09:27 ` Re: [PATCH] Don't call ereport(ERROR) from recovery target GUC assign hooks JoongHyuk Shin <[email protected]>
2026-06-21 12:31 ` Re: [PATCH] Don't call ereport(ERROR) from recovery target GUC assign hooks Álvaro Herrera <[email protected]>
2026-06-22 04:37 ` Re: [PATCH] Don't call ereport(ERROR) from recovery target GUC assign hooks JoongHyuk Shin <[email protected]>
2026-06-26 11:12 ` Re: [PATCH] Don't call ereport(ERROR) from recovery target GUC assign hooks Henson Choi <[email protected]>
2026-06-29 04:45 ` Re: [PATCH] Don't call ereport(ERROR) from recovery target GUC assign hooks JoongHyuk Shin <[email protected]>
2026-06-29 05:47 ` Re: [PATCH] Don't call ereport(ERROR) from recovery target GUC assign hooks Michael Paquier <[email protected]>
2026-06-29 21:16 ` Re: [PATCH] Don't call ereport(ERROR) from recovery target GUC assign hooks Zsolt Parragi <[email protected]>
2026-06-29 23:35 ` Re: [PATCH] Don't call ereport(ERROR) from recovery target GUC assign hooks Michael Paquier <[email protected]>
2026-07-06 07:53 ` Re: [PATCH] Don't call ereport(ERROR) from recovery target GUC assign hooks JoongHyuk Shin <[email protected]>
2026-07-06 22:30 ` Re: [PATCH] Don't call ereport(ERROR) from recovery target GUC assign hooks Michael Paquier <[email protected]>
2026-07-07 03:01 ` Re: [PATCH] Don't call ereport(ERROR) from recovery target GUC assign hooks JoongHyuk Shin <[email protected]>
2026-07-07 06:59 ` Re: [PATCH] Don't call ereport(ERROR) from recovery target GUC assign hooks Michael Paquier <[email protected]>
2026-07-08 01:38 ` Re: [PATCH] Don't call ereport(ERROR) from recovery target GUC assign hooks Fujii Masao <[email protected]>
@ 2026-07-08 01:43 ` Michael Paquier <[email protected]>
2026-07-12 10:45 ` Re: [PATCH] Don't call ereport(ERROR) from recovery target GUC assign hooks JoongHyuk Shin <[email protected]>
0 siblings, 1 reply; 868+ messages in thread
From: Michael Paquier @ 2026-07-08 01:43 UTC (permalink / raw)
To: Fujii Masao <[email protected]>; +Cc: JoongHyuk Shin <[email protected]>; Zsolt Parragi <[email protected]>; [email protected]
On Wed, Jul 08, 2026 at 10:38:54AM +0900, Fujii Masao wrote:
> We can remove assign_recovery_target_name()? With the v8 patch, it only
> assigns the GUC string pointer to recoveryTargetName. It seems we could
> instead have recovery_target_name store its value directly in
> recoveryTargetName, remove recovery_target_name_string, and drop
> the assign hook altogether. Thoughts?
Yeah, perhaps we should just do that. I've also found the consistency
with all these _string variables interesting to keep, but that's
minor compared to less code.
--
Michael
Attachments:
[application/pgp-signature] signature.asc (833B, ../../[email protected]/2-signature.asc)
download
^ permalink raw reply [nested|flat] 868+ messages in thread
* Re: [PATCH] Don't call ereport(ERROR) from recovery target GUC assign hooks
2026-04-13 08:21 [PATCH] Don't call ereport(ERROR) from recovery target GUC assign hooks JoongHyuk Shin <[email protected]>
2026-04-24 13:08 ` Re: [PATCH] Don't call ereport(ERROR) from recovery target GUC assign hooks Fujii Masao <[email protected]>
2026-04-27 01:52 ` Re: [PATCH] Don't call ereport(ERROR) from recovery target GUC assign hooks Michael Paquier <[email protected]>
2026-04-27 05:36 ` Re: [PATCH] Don't call ereport(ERROR) from recovery target GUC assign hooks Fujii Masao <[email protected]>
2026-04-27 05:59 ` Re: [PATCH] Don't call ereport(ERROR) from recovery target GUC assign hooks Michael Paquier <[email protected]>
2026-04-29 09:29 ` Re: [PATCH] Don't call ereport(ERROR) from recovery target GUC assign hooks JoongHyuk Shin <[email protected]>
2026-05-01 05:53 ` Re: [PATCH] Don't call ereport(ERROR) from recovery target GUC assign hooks Fujii Masao <[email protected]>
2026-05-11 03:17 ` Re: [PATCH] Don't call ereport(ERROR) from recovery target GUC assign hooks JoongHyuk Shin <[email protected]>
2026-05-11 06:01 ` Re: [PATCH] Don't call ereport(ERROR) from recovery target GUC assign hooks JoongHyuk Shin <[email protected]>
2026-05-31 21:11 ` Re: [PATCH] Don't call ereport(ERROR) from recovery target GUC assign hooks Scott Ray <[email protected]>
2026-06-04 05:41 ` Re: [PATCH] Don't call ereport(ERROR) from recovery target GUC assign hooks JoongHyuk Shin <[email protected]>
2026-06-06 20:10 ` Re: [PATCH] Don't call ereport(ERROR) from recovery target GUC assign hooks Scott Ray <[email protected]>
2026-06-07 10:30 ` Re: [PATCH] Don't call ereport(ERROR) from recovery target GUC assign hooks JoongHyuk Shin <[email protected]>
2026-06-07 15:44 ` Re: [PATCH] Don't call ereport(ERROR) from recovery target GUC assign hooks Álvaro Herrera <[email protected]>
2026-06-21 09:27 ` Re: [PATCH] Don't call ereport(ERROR) from recovery target GUC assign hooks JoongHyuk Shin <[email protected]>
2026-06-21 12:31 ` Re: [PATCH] Don't call ereport(ERROR) from recovery target GUC assign hooks Álvaro Herrera <[email protected]>
2026-06-22 04:37 ` Re: [PATCH] Don't call ereport(ERROR) from recovery target GUC assign hooks JoongHyuk Shin <[email protected]>
2026-06-26 11:12 ` Re: [PATCH] Don't call ereport(ERROR) from recovery target GUC assign hooks Henson Choi <[email protected]>
2026-06-29 04:45 ` Re: [PATCH] Don't call ereport(ERROR) from recovery target GUC assign hooks JoongHyuk Shin <[email protected]>
2026-06-29 05:47 ` Re: [PATCH] Don't call ereport(ERROR) from recovery target GUC assign hooks Michael Paquier <[email protected]>
2026-06-29 21:16 ` Re: [PATCH] Don't call ereport(ERROR) from recovery target GUC assign hooks Zsolt Parragi <[email protected]>
2026-06-29 23:35 ` Re: [PATCH] Don't call ereport(ERROR) from recovery target GUC assign hooks Michael Paquier <[email protected]>
2026-07-06 07:53 ` Re: [PATCH] Don't call ereport(ERROR) from recovery target GUC assign hooks JoongHyuk Shin <[email protected]>
2026-07-06 22:30 ` Re: [PATCH] Don't call ereport(ERROR) from recovery target GUC assign hooks Michael Paquier <[email protected]>
2026-07-07 03:01 ` Re: [PATCH] Don't call ereport(ERROR) from recovery target GUC assign hooks JoongHyuk Shin <[email protected]>
2026-07-07 06:59 ` Re: [PATCH] Don't call ereport(ERROR) from recovery target GUC assign hooks Michael Paquier <[email protected]>
2026-07-08 01:38 ` Re: [PATCH] Don't call ereport(ERROR) from recovery target GUC assign hooks Fujii Masao <[email protected]>
2026-07-08 01:43 ` Re: [PATCH] Don't call ereport(ERROR) from recovery target GUC assign hooks Michael Paquier <[email protected]>
@ 2026-07-12 10:45 ` JoongHyuk Shin <[email protected]>
0 siblings, 0 replies; 868+ messages in thread
From: JoongHyuk Shin @ 2026-07-12 10:45 UTC (permalink / raw)
To: Michael Paquier <[email protected]>; +Cc: Fujii Masao <[email protected]>; Zsolt Parragi <[email protected]>; [email protected]
On Wed, Jul 8, 2026 at 10:39 AM Fujii Masao <[email protected]> wrote:
> We can remove assign_recovery_target_name()?
Yes, done in v9. recovery_target_name now stores directly into
recoveryTargetName, so recovery_target_name_string and the assign hook
are both gone.
On Tue, Jul 7, 2026 at 3:59 PM Michael Paquier <[email protected]> wrote:
> FWIW, I think that this is redundant, as the errmsg and the errdetail
> are basically saying the same thing. I'd suggest a simpler:
> errmsg: cannot specify more than one recovery target
> errdetail: Parameters set are: %s.
Applied in v9.
> I smell of an AI set of comments. We could just remove the whole and
> not lose value in understanding the meaning of the test. A bunch of
> the comments added to the TAP script could also be trimmed down quite
> a bit, made simpler..
Done, and I trimmed the rest of the TAP comments too.
> With recovery_target assign hook being removed for the case of
> immediate, a test case that checks for a conflict between immediate
> and a secondary target may be in order.
Added in v9.
Thanks to you both for the reviews.
--
JH Shin
Attachments:
[application/octet-stream] v9-0001-Don-t-call-ereport-ERROR-from-recovery-target-GUC.patch (19.5K, ../../CACSdjfN8kkwHJ_S_fX4BPxJr4zQ99osk+Z6EytRWpnkQAU+Jcw@mail.gmail.com/3-v9-0001-Don-t-call-ereport-ERROR-from-recovery-target-GUC.patch)
download | inline diff:
From ef7c377ea08d74c4cd279f507e4ab80afe6c5322 Mon Sep 17 00:00:00 2001
From: JoongHyuk Shin <[email protected]>
Date: Sun, 28 Jun 2026 18:25:45 +0900
Subject: [PATCH v9] Don't call ereport(ERROR) from recovery target GUC assign
hooks
A GUC assign hook must not raise an error, but the recovery_target*
assign hooks did so when a second target was set.
Instead, derive recoveryTarget once in validateRecoveryParameters() from
the settled recovery_target* values, rejecting there a configuration that
sets more than one target. The assign hooks now only store each
parameter's own value, and recovery_target_name, which needs no further
processing, uses its GUC variable directly without an assign hook.
---
src/backend/access/transam/xlogrecovery.c | 139 ++++++-----------
src/backend/utils/misc/guc_parameters.dat | 5 +-
src/backend/utils/misc/guc_tables.c | 1 -
src/include/access/xlogrecovery.h | 2 +-
src/include/utils/guc_hooks.h | 3 -
src/test/recovery/t/003_recovery_targets.pl | 161 ++++++++++++++++----
6 files changed, 183 insertions(+), 128 deletions(-)
diff --git a/src/backend/access/transam/xlogrecovery.c b/src/backend/access/transam/xlogrecovery.c
index c0ae4d3f63f..e2ba09594dd 100644
--- a/src/backend/access/transam/xlogrecovery.c
+++ b/src/backend/access/transam/xlogrecovery.c
@@ -61,6 +61,7 @@
#include "storage/subsystems.h"
#include "utils/datetime.h"
#include "utils/fmgrprotos.h"
+#include "utils/guc.h"
#include "utils/guc_hooks.h"
#include "utils/pgstat_internal.h"
#include "utils/pg_lsn.h"
@@ -92,7 +93,7 @@ int recoveryTargetAction = RECOVERY_TARGET_ACTION_PAUSE;
TransactionId recoveryTargetXid;
char *recovery_target_time_string;
TimestampTz recoveryTargetTime;
-const char *recoveryTargetName;
+char *recoveryTargetName;
XLogRecPtr recoveryTargetLSN;
int recovery_min_apply_delay = 0;
@@ -341,6 +342,7 @@ static void ApplyWalRecord(XLogReaderState *xlogreader, XLogRecord *record, Time
static void EnableStandbyMode(void);
static void readRecoverySignalFile(void);
static void validateRecoveryParameters(void);
+static RecoveryTargetType DetermineRecoveryTargetType(void);
static bool read_backup_label(XLogRecPtr *checkPointLoc,
TimeLineID *backupLabelTLI,
bool *backupEndRequired, bool *backupFromStandby);
@@ -1067,6 +1069,8 @@ readRecoverySignalFile(void)
static void
validateRecoveryParameters(void)
{
+ recoveryTarget = DetermineRecoveryTargetType();
+
if (!ArchiveRecoveryRequested)
return;
@@ -4769,30 +4773,50 @@ check_primary_slot_name(char **newval, void **extra, GucSource source)
}
/*
- * Recovery target settings: Only one of the several recovery_target* settings
- * may be set. Setting a second one results in an error. The global variable
- * recoveryTarget tracks which kind of recovery target was chosen. Other
- * variables store the actual target value (for example a string or a xid).
- * The assign functions of the parameters check whether a competing parameter
- * was already set. But we want to allow setting the same parameter multiple
- * times. We also want to allow unsetting a parameter and setting a different
- * one, so we unset recoveryTarget when the parameter is set to an empty
- * string.
- *
- * XXX this code is broken by design. Throwing an error from a GUC assign
- * hook breaks fundamental assumptions of guc.c. So long as all the variables
- * for which this can happen are PGC_POSTMASTER, the consequences are limited,
- * since we'd just abort postmaster startup anyway. Nonetheless it's likely
- * that we have odd behaviors such as unexpected GUC ordering dependencies.
+ * Return the recovery target derived from the recovery_target* settings,
+ * raising an error if more than one of them is set.
*/
-
-pg_noreturn static void
-error_multiple_recovery_targets(void)
+static RecoveryTargetType
+DetermineRecoveryTargetType(void)
{
- ereport(ERROR,
- (errcode(ERRCODE_INVALID_PARAMETER_VALUE),
- errmsg("multiple recovery targets specified"),
- errdetail("At most one of \"recovery_target\", \"recovery_target_lsn\", \"recovery_target_name\", \"recovery_target_time\", \"recovery_target_xid\" may be set.")));
+ int ntargets = 0;
+ RecoveryTargetType target = RECOVERY_TARGET_UNSET;
+ const char *val;
+ StringInfoData buf;
+
+ initStringInfo(&buf);
+
+#define ADD_TARGET_IF_SET(gucname, kind) \
+ do { \
+ val = GetConfigOption(gucname, false, false); \
+ if (val[0] != '\0') \
+ { \
+ ntargets++; \
+ target = (kind); \
+ if (buf.len == 0) \
+ appendStringInfo(&buf, _("\"%s\""), gucname); \
+ else \
+ appendStringInfo(&buf, _(", \"%s\""), gucname); \
+ } \
+ } while (0)
+
+ ADD_TARGET_IF_SET("recovery_target", RECOVERY_TARGET_IMMEDIATE);
+ ADD_TARGET_IF_SET("recovery_target_lsn", RECOVERY_TARGET_LSN);
+ ADD_TARGET_IF_SET("recovery_target_name", RECOVERY_TARGET_NAME);
+ ADD_TARGET_IF_SET("recovery_target_time", RECOVERY_TARGET_TIME);
+ ADD_TARGET_IF_SET("recovery_target_xid", RECOVERY_TARGET_XID);
+#undef ADD_TARGET_IF_SET
+
+ if (ntargets > 1)
+ ereport(FATAL,
+ errcode(ERRCODE_INVALID_PARAMETER_VALUE),
+ errmsg("cannot specify more than one recovery target"),
+ errdetail("Parameters set are: %s.",
+ buf.data));
+
+ pfree(buf.data);
+
+ return target;
}
/*
@@ -4809,22 +4833,6 @@ check_recovery_target(char **newval, void **extra, GucSource source)
return true;
}
-/*
- * GUC assign_hook for recovery_target
- */
-void
-assign_recovery_target(const char *newval, void *extra)
-{
- if (recoveryTarget != RECOVERY_TARGET_UNSET &&
- recoveryTarget != RECOVERY_TARGET_IMMEDIATE)
- error_multiple_recovery_targets();
-
- if (newval && strcmp(newval, "") != 0)
- recoveryTarget = RECOVERY_TARGET_IMMEDIATE;
- else
- recoveryTarget = RECOVERY_TARGET_UNSET;
-}
-
/*
* GUC check_hook for recovery_target_lsn
*/
@@ -4856,17 +4864,8 @@ check_recovery_target_lsn(char **newval, void **extra, GucSource source)
void
assign_recovery_target_lsn(const char *newval, void *extra)
{
- if (recoveryTarget != RECOVERY_TARGET_UNSET &&
- recoveryTarget != RECOVERY_TARGET_LSN)
- error_multiple_recovery_targets();
-
if (newval && strcmp(newval, "") != 0)
- {
- recoveryTarget = RECOVERY_TARGET_LSN;
recoveryTargetLSN = *((XLogRecPtr *) extra);
- }
- else
- recoveryTarget = RECOVERY_TARGET_UNSET;
}
/*
@@ -4885,25 +4884,6 @@ check_recovery_target_name(char **newval, void **extra, GucSource source)
return true;
}
-/*
- * GUC assign_hook for recovery_target_name
- */
-void
-assign_recovery_target_name(const char *newval, void *extra)
-{
- if (recoveryTarget != RECOVERY_TARGET_UNSET &&
- recoveryTarget != RECOVERY_TARGET_NAME)
- error_multiple_recovery_targets();
-
- if (newval && strcmp(newval, "") != 0)
- {
- recoveryTarget = RECOVERY_TARGET_NAME;
- recoveryTargetName = newval;
- }
- else
- recoveryTarget = RECOVERY_TARGET_UNSET;
-}
-
/*
* GUC check_hook for recovery_target_time
*
@@ -4965,22 +4945,6 @@ check_recovery_target_time(char **newval, void **extra, GucSource source)
return true;
}
-/*
- * GUC assign_hook for recovery_target_time
- */
-void
-assign_recovery_target_time(const char *newval, void *extra)
-{
- if (recoveryTarget != RECOVERY_TARGET_UNSET &&
- recoveryTarget != RECOVERY_TARGET_TIME)
- error_multiple_recovery_targets();
-
- if (newval && strcmp(newval, "") != 0)
- recoveryTarget = RECOVERY_TARGET_TIME;
- else
- recoveryTarget = RECOVERY_TARGET_UNSET;
-}
-
/*
* GUC check_hook for recovery_target_timeline
*/
@@ -5099,15 +5063,6 @@ check_recovery_target_xid(char **newval, void **extra, GucSource source)
void
assign_recovery_target_xid(const char *newval, void *extra)
{
- if (recoveryTarget != RECOVERY_TARGET_UNSET &&
- recoveryTarget != RECOVERY_TARGET_XID)
- error_multiple_recovery_targets();
-
if (newval && strcmp(newval, "") != 0)
- {
- recoveryTarget = RECOVERY_TARGET_XID;
recoveryTargetXid = *((TransactionId *) extra);
- }
- else
- recoveryTarget = RECOVERY_TARGET_UNSET;
}
diff --git a/src/backend/utils/misc/guc_parameters.dat b/src/backend/utils/misc/guc_parameters.dat
index c9118e71988..191c69c5bc7 100644
--- a/src/backend/utils/misc/guc_parameters.dat
+++ b/src/backend/utils/misc/guc_parameters.dat
@@ -2465,7 +2465,6 @@
variable => 'recovery_target_string',
boot_val => '""',
check_hook => 'check_recovery_target',
- assign_hook => 'assign_recovery_target',
},
{ name => 'recovery_target_action', type => 'enum', context => 'PGC_POSTMASTER', group => 'WAL_RECOVERY_TARGET',
@@ -2491,10 +2490,9 @@
{ name => 'recovery_target_name', type => 'string', context => 'PGC_POSTMASTER', group => 'WAL_RECOVERY_TARGET',
short_desc => 'Sets the named restore point up to which recovery will proceed.',
- variable => 'recovery_target_name_string',
+ variable => 'recoveryTargetName',
boot_val => '""',
check_hook => 'check_recovery_target_name',
- assign_hook => 'assign_recovery_target_name',
},
{ name => 'recovery_target_time', type => 'string', context => 'PGC_POSTMASTER', group => 'WAL_RECOVERY_TARGET',
@@ -2502,7 +2500,6 @@
variable => 'recovery_target_time_string',
boot_val => '""',
check_hook => 'check_recovery_target_time',
- assign_hook => 'assign_recovery_target_time',
},
{ name => 'recovery_target_timeline', type => 'string', context => 'PGC_POSTMASTER', group => 'WAL_RECOVERY_TARGET',
diff --git a/src/backend/utils/misc/guc_tables.c b/src/backend/utils/misc/guc_tables.c
index 90aa374b3ec..1ec460b6a82 100644
--- a/src/backend/utils/misc/guc_tables.c
+++ b/src/backend/utils/misc/guc_tables.c
@@ -667,7 +667,6 @@ static bool exec_backend_enabled = EXEC_BACKEND_ENABLED;
static char *recovery_target_timeline_string;
static char *recovery_target_string;
static char *recovery_target_xid_string;
-static char *recovery_target_name_string;
static char *recovery_target_lsn_string;
/* should be static, but commands/variable.c needs to get at this */
diff --git a/src/include/access/xlogrecovery.h b/src/include/access/xlogrecovery.h
index ba7750dca0b..9ffd44fcbae 100644
--- a/src/include/access/xlogrecovery.h
+++ b/src/include/access/xlogrecovery.h
@@ -139,7 +139,7 @@ extern PGDLLIMPORT char *archiveCleanupCommand;
extern PGDLLIMPORT TransactionId recoveryTargetXid;
extern PGDLLIMPORT char *recovery_target_time_string;
extern PGDLLIMPORT TimestampTz recoveryTargetTime;
-extern PGDLLIMPORT const char *recoveryTargetName;
+extern PGDLLIMPORT char *recoveryTargetName;
extern PGDLLIMPORT XLogRecPtr recoveryTargetLSN;
extern PGDLLIMPORT RecoveryTargetType recoveryTarget;
extern PGDLLIMPORT bool wal_receiver_create_temp_slot;
diff --git a/src/include/utils/guc_hooks.h b/src/include/utils/guc_hooks.h
index 307f4fbaefe..6a76f8d5ed6 100644
--- a/src/include/utils/guc_hooks.h
+++ b/src/include/utils/guc_hooks.h
@@ -103,16 +103,13 @@ extern bool check_recovery_prefetch(int *new_value, void **extra,
extern void assign_recovery_prefetch(int new_value, void *extra);
extern bool check_recovery_target(char **newval, void **extra,
GucSource source);
-extern void assign_recovery_target(const char *newval, void *extra);
extern bool check_recovery_target_lsn(char **newval, void **extra,
GucSource source);
extern void assign_recovery_target_lsn(const char *newval, void *extra);
extern bool check_recovery_target_name(char **newval, void **extra,
GucSource source);
-extern void assign_recovery_target_name(const char *newval, void *extra);
extern bool check_recovery_target_time(char **newval, void **extra,
GucSource source);
-extern void assign_recovery_target_time(const char *newval, void *extra);
extern bool check_recovery_target_timeline(char **newval, void **extra,
GucSource source);
extern void assign_recovery_target_timeline(const char *newval, void *extra);
diff --git a/src/test/recovery/t/003_recovery_targets.pl b/src/test/recovery/t/003_recovery_targets.pl
index 047eb13293a..71281808983 100644
--- a/src/test/recovery/t/003_recovery_targets.pl
+++ b/src/test/recovery/t/003_recovery_targets.pl
@@ -51,6 +51,49 @@ sub test_recovery_standby
return;
}
+# Start a standby with the given pg_ctl --options string and verify that
+# the standby reaches the given LSN and row count. Used to exercise
+# scenarios that require the postmaster command line to receive multiple
+# "-c name=value" instances of the same GUC, which postgresql.conf cannot
+# express because ProcessConfigFile collapses duplicate keys.
+sub test_recovery_standby_with_options
+{
+ local $Test::Builder::Level = $Test::Builder::Level + 1;
+
+ my $test_name = shift;
+ my $node_name = shift;
+ my $node_primary = shift;
+ my $options = shift;
+ my $num_rows = shift;
+ my $until_lsn = shift;
+
+ my $node_standby = PostgreSQL::Test::Cluster->new($node_name);
+ $node_standby->init_from_backup($node_primary, 'my_backup',
+ has_restoring => 1);
+
+ my $res = run_log(
+ [
+ 'pg_ctl',
+ '--pgdata' => $node_standby->data_dir,
+ '--log' => $node_standby->logfile,
+ '--options' => $options,
+ 'start',
+ ]);
+ ok($res, "server starts for $test_name");
+
+ $node_standby->poll_query_until('postgres',
+ "SELECT '$until_lsn'::pg_lsn <= pg_last_wal_replay_lsn()")
+ or die "Timed out while waiting for standby to catch up";
+
+ my $count = $node_standby->safe_psql('postgres',
+ "SELECT count(*) FROM tab_int");
+ is($count, qq($num_rows), "check standby content for $test_name");
+
+ $node_standby->teardown_node;
+
+ return;
+}
+
# Initialize primary node
my $node_primary = PostgreSQL::Test::Cluster->new('primary');
$node_primary->init(has_archiving => 1, allows_streaming => 1);
@@ -108,6 +151,9 @@ $node_primary->safe_psql('postgres',
# Force archiving of WAL file
$node_primary->safe_psql('postgres', "SELECT pg_switch_wal()");
+my $lsn6 =
+ $node_primary->safe_psql('postgres', "SELECT pg_current_wal_lsn()");
+
# Test recovery targets
my @recovery_params = ("recovery_target = 'immediate'");
test_recovery_standby('immediate target',
@@ -125,11 +171,20 @@ test_recovery_standby('name', 'standby_4', $node_primary, \@recovery_params,
test_recovery_standby('LSN', 'standby_5', $node_primary, \@recovery_params,
"5000", $lsn5);
+# Regression: empty-string for one recovery_target_* GUC must not clobber
+# another non-empty target. Setting recovery_target_xid + recovery_target_time
+# = '' must recover to the xid, not run as no-target recovery.
+@recovery_params = (
+ "recovery_target_xid = '$recovery_txid'",
+ "recovery_target_time = ''");
+test_recovery_standby('xid with empty time GUC',
+ 'standby_xid_empty_time', $node_primary, \@recovery_params,
+ "2000", $lsn2);
+
# Multiple targets
#
-# Multiple conflicting settings are not allowed, but setting the same
-# parameter multiple times or unsetting a parameter and setting a
-# different one is allowed.
+# Multiple conflicting non-empty settings are rejected, but setting the same
+# parameter twice or clearing one with an empty string is allowed.
@recovery_params = (
"recovery_target_name = '$recovery_name'",
@@ -138,31 +193,9 @@ test_recovery_standby('LSN', 'standby_5', $node_primary, \@recovery_params,
test_recovery_standby('multiple overriding settings',
'standby_6', $node_primary, \@recovery_params, "3000", $lsn3);
-my $node_standby = PostgreSQL::Test::Cluster->new('standby_7');
-$node_standby->init_from_backup($node_primary, 'my_backup',
- has_restoring => 1);
-$node_standby->append_conf(
- 'postgresql.conf', "recovery_target_name = '$recovery_name'
-recovery_target_time = '$recovery_time'");
-
-my $res = run_log(
- [
- 'pg_ctl',
- '--pgdata' => $node_standby->data_dir,
- '--log' => $node_standby->logfile,
- 'start',
- ]);
-ok(!$res, 'invalid recovery startup fails');
-
-my $logfile = slurp_file($node_standby->logfile());
-like(
- $logfile,
- qr/multiple recovery targets specified/,
- 'multiple conflicting settings');
-
# Check behavior when recovery ends before target is reached
-$node_standby = PostgreSQL::Test::Cluster->new('standby_8');
+my $node_standby = PostgreSQL::Test::Cluster->new('standby_8');
$node_standby->init_from_backup(
$node_primary, 'my_backup',
has_restoring => 1,
@@ -184,12 +217,86 @@ foreach my $i (0 .. 10 * $PostgreSQL::Test::Utils::timeout_default)
last if !-f $node_standby->data_dir . '/postmaster.pid';
usleep(100_000);
}
-$logfile = slurp_file($node_standby->logfile());
+my $logfile = slurp_file($node_standby->logfile());
like(
$logfile,
qr/FATAL: .* recovery ended before configured recovery target was reached/,
'recovery end before target reached is a fatal error');
+# Conflicts are rejected at every startup, even without recovery.signal.
+# init_from_backup without has_restoring creates no recovery.signal, so this
+# cluster would otherwise start as a plain primary; the conflict must still be
+# caught.
+my $node_no_signal = PostgreSQL::Test::Cluster->new('multi_target_no_signal');
+$node_no_signal->init_from_backup($node_primary, 'my_backup');
+$node_no_signal->append_conf(
+ 'postgresql.conf', "recovery_target_name = '$recovery_name'
+recovery_target_time = '$recovery_time'");
+
+my $res_no_signal = run_log(
+ [
+ 'pg_ctl',
+ '--pgdata' => $node_no_signal->data_dir,
+ '--log' => $node_no_signal->logfile,
+ 'start',
+ ]);
+ok(!$res_no_signal,
+ 'server fails to start with conflicting recovery targets and no recovery.signal');
+
+my $logfile_no_signal = slurp_file($node_no_signal->logfile());
+like(
+ $logfile_no_signal,
+ qr/cannot specify more than one recovery target/,
+ 'expected error message logged without recovery.signal');
+like(
+ $logfile_no_signal,
+ qr/Parameters set are: "recovery_target_name", "recovery_target_time"/,
+ 'errdetail lists the set parameters in order without recovery.signal');
+unlike(
+ $logfile_no_signal,
+ qr/Parameters set are:[^\n]*=/,
+ 'errdetail does not echo parameter values without recovery.signal');
+
+my $node_immediate_conflict =
+ PostgreSQL::Test::Cluster->new('immediate_target_conflict');
+$node_immediate_conflict->init_from_backup($node_primary, 'my_backup');
+$node_immediate_conflict->append_conf('postgresql.conf',
+ "recovery_target = 'immediate'
+recovery_target_xid = '$recovery_txid'");
+
+my $res_immediate = run_log(
+ [
+ 'pg_ctl',
+ '--pgdata' => $node_immediate_conflict->data_dir,
+ '--log' => $node_immediate_conflict->logfile,
+ 'start',
+ ]);
+ok(!$res_immediate,
+ 'server fails to start with recovery_target=immediate and a second target');
+like(
+ slurp_file($node_immediate_conflict->logfile()),
+ qr/cannot specify more than one recovery target/,
+ 'recovery_target=immediate conflicting with another target is rejected');
+
+# Same-GUC set-then-clear: setting a recovery_target_* GUC and then setting the
+# same GUC to an empty string leaves no target, so recovery runs to the end of
+# WAL. Duplicate keys collapse in postgresql.conf, so "pg_ctl --options" passes
+# both assignments on the postmaster command line.
+test_recovery_standby_with_options(
+ 'recovery_target_xid set then cleared',
+ 'standby_xid_set_clear', $node_primary,
+ "-c recovery_target_xid=$recovery_txid -c recovery_target_xid=",
+ "6000", $lsn6);
+
+# Set recovery_target_xid, then set and clear recovery_target_name. Only the
+# xid remains, so recovery must stop at it rather than running to the end of WAL
+# (a competing target that is set then cleared must not strand the first one).
+test_recovery_standby_with_options(
+ 'recovery target preserved when a competing one is set then cleared',
+ 'standby_clobber_clear', $node_primary,
+ "-c recovery_target_xid=$recovery_txid -c recovery_target_name=$recovery_name -c recovery_target_name=",
+ "2000", $lsn2);
+
# Invalid recovery_target_timeline tests
my ($result, $stdout, $stderr) = $node_primary->psql('postgres',
"ALTER SYSTEM SET recovery_target_timeline TO 'bogus'");
--
2.52.0
^ permalink raw reply [nested|flat] 868+ messages in thread
end of thread, other threads:[~2026-07-12 10:45 UTC | newest]
Thread overview: 868+ 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 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]>
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 v22 3/5] Make archiver process an auxiliary process Kyotaro Horiguchi <[email protected]>
2019-07-04 13:25 Re: [PATCH] Incremental sort (was: PoC: Partial sort) James Coleman <[email protected]>
2019-07-08 22:37 ` Re: [PATCH] Incremental sort (was: PoC: Partial sort) Alexander Korotkov <[email protected]>
2019-07-09 13:29 ` Re: [PATCH] Incremental sort (was: PoC: Partial sort) James Coleman <[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 v25 4/8] 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 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 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 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 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 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 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 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 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 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 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 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 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 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 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 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 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 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 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 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 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 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 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 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 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 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 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 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 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 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 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 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 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 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 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 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 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 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 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 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 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 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 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 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 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 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 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 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 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 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 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 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 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 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 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 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 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 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 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 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 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-04-13 08:21 [PATCH] Don't call ereport(ERROR) from recovery target GUC assign hooks JoongHyuk Shin <[email protected]>
2026-04-24 01:10 ` Re: [PATCH] Don't call ereport(ERROR) from recovery target GUC assign hooks Greg Lamberson <[email protected]>
2026-04-24 13:08 ` Re: [PATCH] Don't call ereport(ERROR) from recovery target GUC assign hooks Fujii Masao <[email protected]>
2026-04-27 01:52 ` Re: [PATCH] Don't call ereport(ERROR) from recovery target GUC assign hooks Michael Paquier <[email protected]>
2026-04-27 05:36 ` Re: [PATCH] Don't call ereport(ERROR) from recovery target GUC assign hooks Fujii Masao <[email protected]>
2026-04-27 05:59 ` Re: [PATCH] Don't call ereport(ERROR) from recovery target GUC assign hooks Michael Paquier <[email protected]>
2026-04-29 09:29 ` Re: [PATCH] Don't call ereport(ERROR) from recovery target GUC assign hooks JoongHyuk Shin <[email protected]>
2026-05-01 05:53 ` Re: [PATCH] Don't call ereport(ERROR) from recovery target GUC assign hooks Fujii Masao <[email protected]>
2026-05-11 03:17 ` Re: [PATCH] Don't call ereport(ERROR) from recovery target GUC assign hooks JoongHyuk Shin <[email protected]>
2026-05-11 06:01 ` Re: [PATCH] Don't call ereport(ERROR) from recovery target GUC assign hooks JoongHyuk Shin <[email protected]>
2026-05-31 21:11 ` Re: [PATCH] Don't call ereport(ERROR) from recovery target GUC assign hooks Scott Ray <[email protected]>
2026-06-04 05:41 ` Re: [PATCH] Don't call ereport(ERROR) from recovery target GUC assign hooks JoongHyuk Shin <[email protected]>
2026-06-06 20:10 ` Re: [PATCH] Don't call ereport(ERROR) from recovery target GUC assign hooks Scott Ray <[email protected]>
2026-06-07 10:30 ` Re: [PATCH] Don't call ereport(ERROR) from recovery target GUC assign hooks JoongHyuk Shin <[email protected]>
2026-06-07 15:44 ` Re: [PATCH] Don't call ereport(ERROR) from recovery target GUC assign hooks Álvaro Herrera <[email protected]>
2026-06-21 09:27 ` Re: [PATCH] Don't call ereport(ERROR) from recovery target GUC assign hooks JoongHyuk Shin <[email protected]>
2026-06-21 12:31 ` Re: [PATCH] Don't call ereport(ERROR) from recovery target GUC assign hooks Álvaro Herrera <[email protected]>
2026-06-22 04:37 ` Re: [PATCH] Don't call ereport(ERROR) from recovery target GUC assign hooks JoongHyuk Shin <[email protected]>
2026-06-25 08:00 ` Re: [PATCH] Don't call ereport(ERROR) from recovery target GUC assign hooks Michael Paquier <[email protected]>
2026-06-26 07:15 ` Re: [PATCH] Don't call ereport(ERROR) from recovery target GUC assign hooks Kyotaro Horiguchi <[email protected]>
2026-06-26 08:12 ` Re: [PATCH] Don't call ereport(ERROR) from recovery target GUC assign hooks Michael Paquier <[email protected]>
2026-06-26 11:12 ` Re: [PATCH] Don't call ereport(ERROR) from recovery target GUC assign hooks Henson Choi <[email protected]>
2026-06-29 04:45 ` Re: [PATCH] Don't call ereport(ERROR) from recovery target GUC assign hooks JoongHyuk Shin <[email protected]>
2026-06-29 05:47 ` Re: [PATCH] Don't call ereport(ERROR) from recovery target GUC assign hooks Michael Paquier <[email protected]>
2026-06-29 07:59 ` Re: [PATCH] Don't call ereport(ERROR) from recovery target GUC assign hooks JoongHyuk Shin <[email protected]>
2026-06-29 21:16 ` Re: [PATCH] Don't call ereport(ERROR) from recovery target GUC assign hooks Zsolt Parragi <[email protected]>
2026-06-29 23:35 ` Re: [PATCH] Don't call ereport(ERROR) from recovery target GUC assign hooks Michael Paquier <[email protected]>
2026-07-06 07:53 ` Re: [PATCH] Don't call ereport(ERROR) from recovery target GUC assign hooks JoongHyuk Shin <[email protected]>
2026-07-06 22:30 ` Re: [PATCH] Don't call ereport(ERROR) from recovery target GUC assign hooks Michael Paquier <[email protected]>
2026-07-07 03:01 ` Re: [PATCH] Don't call ereport(ERROR) from recovery target GUC assign hooks JoongHyuk Shin <[email protected]>
2026-07-07 06:58 ` Re: [PATCH] Don't call ereport(ERROR) from recovery target GUC assign hooks Michael Paquier <[email protected]>
2026-07-07 06:59 ` Re: [PATCH] Don't call ereport(ERROR) from recovery target GUC assign hooks Michael Paquier <[email protected]>
2026-07-08 01:38 ` Re: [PATCH] Don't call ereport(ERROR) from recovery target GUC assign hooks Fujii Masao <[email protected]>
2026-07-08 01:43 ` Re: [PATCH] Don't call ereport(ERROR) from recovery target GUC assign hooks Michael Paquier <[email protected]>
2026-07-12 10:45 ` Re: [PATCH] Don't call ereport(ERROR) from recovery target GUC assign hooks JoongHyuk Shin <[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